diff --git a/Taskfile.yml b/Taskfile.yml index b8141fde0b9aeffb4a1a574b0b889f5437851269..85a4660fb6e8f0f221a5c5aefcd3c722da6b45d7 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -35,11 +35,11 @@ tasks: cmds: - build-and-publish-showroom - create-new-showroom-page: + create-new-showroom-page: silent: true desc: Create a new showroom page cmds: - - create-new-showroom-page + - create-new-showroom-page {{.CLI_ARGS}} run-theme-generator: silent: true @@ -99,7 +99,7 @@ tasks: aliases: - cnc cmds: - - create-new-component-class + - create-new-component-class {{.CLI_ARGS}} build-and-publish: silent: true diff --git a/development/scripts/createNewComponentClass.mjs b/development/scripts/createNewComponentClass.mjs index 003a3f8ade2feaba8aab18731aefba053d20453b..5beafd8c7ae0b799f1523c1701dd07a0ecf07c46 100644 --- a/development/scripts/createNewComponentClass.mjs +++ b/development/scripts/createNewComponentClass.mjs @@ -1,5 +1,6 @@ import {sourcePath, projectRoot, license} from "./import.mjs"; import {writeFileSync, readFileSync, mkdirSync, existsSync} from "fs"; +import {dirname, join} from "path"; import {buildCSS, createScriptFilenameFromStyleFilename} from "./buildStylePostCSS.mjs"; @@ -16,7 +17,7 @@ try { // small hack to get the version from the package.json, should be replaced with a proper version from git const versionParts = version.split('.'); versionParts[1] = parseInt(versionParts[1]) + 1; -versionParts[2] = 0; +versionParts[2] = 0; const nextVersion = versionParts.join('.'); @@ -45,37 +46,17 @@ export { {{CLASSNAME}} }; export const {{CLASSNAME_LOWER_FIRST}}ElementSymbol = Symbol("{{CLASSNAME_LOWER_FIRST}}Element"); /** - * This CustomControl creates a {{CLASSNAME}} element with a variety of options. - * - * <img src="./images/{{HTML_TAG_SUFFIX}}.png"> - * - * You can create this control either by specifying the HTML tag <monster-{{HTML_TAG_SUFFIX}} />\` directly in the HTML or using - * Javascript via the \`document.createElement('monster-{{HTML_TAG_SUFFIX}}');\` method. - * - * \`\`\`html - * <monster-{{HTML_TAG_SUFFIX}}></monster-{{HTML_TAG_SUFFIX}}> - * \`\`\` - * - * Or you can create this CustomControl directly in Javascript: - * - * \`\`\`js - * import {{{CLASSNAME}}} from '@schukai/monster/source/{{NAMESPACE_AS_PATH}}/{{HTML_TAG_SUFFIX}}.mjs'; - * document.createElement('monster-{{HTML_TAG_SUFFIX}}'); - * \`\`\` - * - * @externalExample {{BACK_TO_ROOT_PATH}}../example/{{NAMESPACE_AS_PATH}}/{{HTML_TAG_SUFFIX}}.mjs - * @startuml {{HTML_TAG_SUFFIX}}.png - * skinparam monochrome true - * skinparam shadowing false - * {{UML}} - * @enduml - * + * A {{CLASSNAME}} + * + * @fragments /fragments/components/form/{{HTML_TAG_SUFFIX}}/ + * + * @example /examples/components/form/{{HTML_TAG_SUFFIX}}-simple + * * @since {{VERSION}} * @copyright schukai GmbH - * @memberOf Monster.{{NAMESPACE_WITH_DOTS}} - * @summary A simple {{CLASSNAME}} + * @summary A beautiful {{CLASSNAME}} that can make your life easier and also looks good. */ -class {{CLASSNAME}} extends {{PARENT_CLASS}} { + class {{CLASSNAME}} extends {{PARENT_CLASS}} { /** * This method is called by the \`instanceof\` operator. * @returns {symbol} @@ -131,7 +112,6 @@ class {{CLASSNAME}} extends {{PARENT_CLASS}} { } /** - * * @return {string} */ static getTag() { @@ -139,8 +119,7 @@ class {{CLASSNAME}} extends {{PARENT_CLASS}} { } /** - * - * @return {Array<CSSStyleSheet>} + * @return {CSSStyleSheet[]} */ static getCSSStyleSheet() { return [{{CLASSNAME}}StyleSheet]; @@ -152,7 +131,7 @@ class {{CLASSNAME}} extends {{PARENT_CLASS}} { /** * @private * @return {initEventHandler} - * @fires Monster.Components.Form.event:monster-{{HTML_TAG_SUFFIX}}-clicked + * @fires monster-{{HTML_TAG_SUFFIX}}-clicked */ function initEventHandler() { const self = this; @@ -189,6 +168,7 @@ function initEventHandler() { /** * @private + * @return {void} */ function initControlReferences() { this[{{CLASSNAME_LOWER_FIRST}}ElementSymbol] = this.shadowRoot.querySelector( @@ -216,6 +196,7 @@ const internalTemplate = ` * The {{CLASSNAME}}.click() method simulates a click on the internal element. * * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/click} + * @returns {void} */ click() { if (this.getOption("disabled") === true) { @@ -231,9 +212,10 @@ const internalTemplate = ` } /** - * The Button.focus() method sets focus on the internal element. + * The {{CLASSNAME}}.focus() method sets focus on the internal element. * * @param {Object} options + * @returns {void} * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/focus} */ focus(options) { @@ -250,7 +232,8 @@ const internalTemplate = ` } /** - * The Button.blur() method removes focus from the internal element. + * The {{CLASSNAME}}.blur() method removes focus from the internal element. + * @returns {void} */ blur() { if ( @@ -277,7 +260,7 @@ const internalTemplate = ` * console.log(e.value) * \`\`\` * - * @property {string} + * @return {mixed} The value of the form control */ get value() { return this.getOption("value"); @@ -291,7 +274,8 @@ const internalTemplate = ` * e.value=1 * \`\`\` * - * @property {string} value + * @param {mixed} value + * @return {void} * @throws {Error} unsupported type */ set value(value) { @@ -371,9 +355,12 @@ const styleDirectory = `${directory}/style`; const filename = `${directory}/${htmlTagSuffix}.mjs`; const pcssFilename = `${styleDirectory}/${htmlTagSuffix}.pcss`; -const exampleDirectory = `${projectRoot}/example/${namespaceAsPath}`; +const exampleDirectory = `${projectRoot}/showroom/source/examples/${namespaceAsPath}`; const exampleFilename = `${exampleDirectory}/${htmlTagSuffix}.mjs`; +const fragmentsDirectory = `${projectRoot}/showroom/source/fragments/${namespaceAsPath}`; + + const backToRootPath = '../'.repeat(namespace.length).slice(0, -1); const isFormControl = argsObj.namespace.match(/components\.form/); @@ -381,13 +368,7 @@ const formInternalsCode = isFormControl ? internalTemplate : ''; const parentClass = isFormControl ? 'CustomControl' : 'CustomElement'; const valueDefaults = isFormControl ? ",\n value: null" : ""; -const uml = isFormControl ? `HTMLElement <|-- CustomElement - * CustomElement <|-- CustomControl - * CustomControl <|-- {{CLASSNAME}}` : `HTMLElement <|-- CustomElement - * CustomElement <|-- {{CLASSNAME}}`; - const code = template.replace(/{{HTML_ELEMENT_FORM_INTERNALS}}/g, formInternalsCode) - .replace(/{{UML}}/g, uml) .replace(/{{CLASSNAME}}/g, argsObj.classname) .replace(/{{CLASSNAME_LOWER_FIRST}}/g, lowerFirst) .replace(/{{CLASSNAME_LOWER}}/g, argsObj.classname.toLowerCase()) @@ -397,10 +378,19 @@ const code = template.replace(/{{HTML_ELEMENT_FORM_INTERNALS}}/g, formInternalsC .replace(/{{PARENT_CLASS}}/g, parentClass) .replace(/{{VALUE_DEFAULTS}}/g, valueDefaults) - .replace(/{{VERSION}}/g, nextVersion) + .replace(/{{VERSION}}/g, nextVersion) .replace(/{{NAMESPACE_WITH_DOTS}}/g, namespaceWithDots); try { + + const d = dirname(filename); + console.log(`Creating ${d}`); + if (!existsSync(d)) { + mkdirSync(d, {recursive: true}); + mkdirSync(join(d, "style"), {recursive: true}); + mkdirSync(join(d, "stylesheet"), {recursive: true}); + } + writeFileSync(filename, code); } catch (e) { console.error(e); @@ -439,3 +429,95 @@ buildCSS(pcssFilename, createScriptFilenameFromStyleFilename(pcssFilename)).then console.error(e); process.exit(1); }); + +const overviewHTML = ` +<h2>Introduction</h2> +<p> + This is the Monster {{CLASSNAME}} component. It is a versatile and customizable control element + to an interactive and engaging user experience that integrates seamlessly into various web applications. + Whether you are developing a simple website or a complex enterprise application, the Monster + Button is designed to increase user interaction and satisfaction. +</p> + +<h2>Key Features</h2> +<ul> + <li><strong>Dynamic interaction</strong>: Users can interact with content dynamically, + making the Web experience more intuitive and user-centric. + </li> + <li><strong>Customizable appearance</strong>: Customize the appearance of the button + to match the design of your brand or application to improve visual consistency. + </li> + <li><strong>Accessibility</strong>: Designed with accessibility in mind to ensure all + users have a seamless experience regardless of their browsing context. + </li> + <li><strong>Programmatic Control</strong>: Provides methods such as click, focus, + and blur to programmatically control the behavior of the button, giving developers flexibility. + </li> +</ul> + +<h2>Improving the user experience</h2> +<p> + The Monster {{CLASSNAME}} goes beyond the traditional functions of a {{CLASSNAME}} to provide an enhanced + and interactive user experience. +</p> + +<p> + These improvements are supported by user studies that show a positive impact on user + commitment and satisfaction. +</p> + +<h2>Efficiency in the development process</h2> +<p> + Integrating the Monster {{CLASSNAME}} into your development process is easy. Its compatibility with + standard web technologies and ease of customization allow for seamless integration with + your existing tools and libraries. Whether you are working on a small project or a large + application, Monster {{CLASSNAME}}'s modular design guarantees easy integration that streamlines + your development process and increases your productivity. +</p> +`.replace(/{{CLASSNAME}}/g, argsObj.classname); + + +const designHTML = ` + <h2>Design</h2> + <p>The control element can be adapted to your own requirements. To do this, the control element + can be designed with CSS like almost any other HTML element.</p> + + <p>However, there are a few things to bear in mind. As the innards of the control are located + in a ShadowRoot, they cannot be accessed directly with CSS selectors. Only the elements specified + for this purpose can be accessed. These elements have the attribute <i>part</i>.</p> + + <p>In CSS, these parts can then be used for styling via a CSS pseudo-element Parts. + Here you can see an example of how you can use this.</p> + + <pre><code class="language-css"> + ::part(container) { + border: 1px solid red; + } + </code></pre> + + <!-- p>The following diagram shows the parts and the slots.</p> + + <img src="assets/{{NAMESPACE_WITH_DOTS}}.{{CLASSNAME_LOWER}}.svg" alt="Parts and slots of the {{CLASSNAME}}"--> + `.replace(/{{NAMESPACE_WITH_DOTS}}/g, namespaceWithDots) + .replace(/{{CLASSNAME}}/g, argsObj.classname); + +const showItHTML = ` +<monster-{{HTML_TAG_SUFFIX}}></monster-{{HTML_TAG_SUFFIX}}> +`.replace(/{{HTML_TAG_SUFFIX}}/g, htmlTagSuffix); + + +try { + if (!existsSync(fragmentsDirectory)) { + mkdirSync(fragmentsDirectory, {recursive: true}); + } + + writeFileSync(`${fragmentsDirectory}/overview.html`, overviewHTML); + writeFileSync(`${fragmentsDirectory}/design.html`,designHTML); + writeFileSync(`${fragmentsDirectory}/show-it.html`, showItHTML); + +} catch (e) { + console.error(e); + process.exit(1); +} + + diff --git a/development/scripts/createNewShowrootmPage.mjs b/development/scripts/createNewShowrootmPage.mjs index a3108f9cde957041ef3bef04eafc7001f2f78da8..e19b147a8b680db08c526e07e3de9a313740d214 100644 --- a/development/scripts/createNewShowrootmPage.mjs +++ b/development/scripts/createNewShowrootmPage.mjs @@ -1,22 +1,24 @@ import {sourcePath, projectRoot} from "./import.mjs"; import {writeFileSync, readFileSync, mkdirSync, existsSync} from "fs"; -import {buildCSS, createScriptFilenameFromStyleFilename} from "./buildStylePostCSS.mjs"; +import {join} from "path"; + +import * as espree from "espree"; +import estraverse from 'estraverse'; + const template = `<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> + <meta name="color-scheme" content="dark light"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> - <title>Document</title> - + <title>{{CLASS_NAME}} - the monster component library</title> <script type="module" src="scripts/monster.mjs"></script> - <style> *:not(:defined) { visibility: hidden; } </style> - </head> <body> @@ -28,17 +30,30 @@ const template = `<!DOCTYPE html> </monster-panel> <monster-panel slot="end"> <main class="container"> - <h1>Collapse</h1> - <p>Crates a panel that can collapse</p> - <div class="infoGrid"> + <div class="deco"></div> + + <div class="to-overview"> + <a href="/" class="back-link"> + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" + viewBox="0 0 16 16"> + <path d="M8 6.982C9.664 5.309 13.825 8.236 8 12 2.175 8.236 6.336 5.309 8 6.982"/> + <path d="M8.707 1.5a1 1 0 0 0-1.414 0L.646 8.146a.5.5 0 0 0 .708.707L2 8.207V13.5A1.5 1.5 0 0 0 3.5 15h9a1.5 1.5 0 0 0 1.5-1.5V8.207l.646.646a.5.5 0 0 0 .708-.707L13 5.793V2.5a.5.5 0 0 0-.5-.5h-1a.5.5 0 0 0-.5.5v1.293zM13 7.207V13.5a.5.5 0 0 1-.5.5h-9a.5.5 0 0 1-.5-.5V7.207l5-5z"/> + </svg> + Back to overview + </a> + </div> + + <h1>{{CLASS_NAME}}</h1> + + <p>{{SUMMARY}}</p> + <div class="info-grid"> <div>Import</div> <div><img alt="the javascript logo" title="how to import" src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 630 630'%3E%3Crect width='630' height='630' fill='%23f7df1e'/%3E%3Cpath d='m423.2 492.19c12.69 20.72 29.2 35.95 58.4 35.95 24.53 0 40.2-12.26 40.2-29.2 0-20.3-16.1-27.49-43.1-39.3l-14.8-6.35c-42.72-18.2-71.1-41-71.1-89.2 0-44.4 33.83-78.2 86.7-78.2 37.64 0 64.7 13.1 84.2 47.4l-46.1 29.6c-10.15-18.2-21.1-25.37-38.1-25.37-17.34 0-28.33 11-28.33 25.37 0 17.76 11 24.95 36.4 35.95l14.8 6.34c50.3 21.57 78.7 43.56 78.7 93 0 53.3-41.87 82.5-98.1 82.5-54.98 0-90.5-26.2-107.88-60.54zm-209.13 5.13c9.3 16.5 17.76 30.45 38.1 30.45 19.45 0 31.72-7.61 31.72-37.2v-201.3h59.2v202.1c0 61.3-35.94 89.2-88.4 89.2-47.4 0-74.85-24.53-88.81-54.075z'/%3E%3C/svg%3E"> </div> - <div> - <pre><code - class="language-javascript">import { Collapse } from "@schukai/monster/source/components/layout/collapse.mjs";</code></pre> + <div><code + class="language-javascript">import { {{CLASS_NAME}} } from "{{IMPORT_PATH}}";</code> </div> <div>Source</div> <div><img alt="the git logo" title="View source code" @@ -46,7 +61,7 @@ const template = `<!DOCTYPE html> </div> <div> <a target="_blank" - href="https://gitlab.schukai.com/oss/libraries/javascript/monster/-/blob/master/source/components/layout/collapse.mjs">View + href="https://gitlab.schukai.com/oss/libraries/javascript/monster/-/blob/master/{{SOURCE_PATH}}">View source code</a></div> <div>Package</div> <div><img alt="the npm logo" title="View on npm" @@ -55,385 +70,187 @@ const template = `<!DOCTYPE html> <div><a target="_blank" href="https://npmjs.com/package/@schukai/monster">@schukai/monster</a></div> <div>Since</div> <div></div> - <div>1.0.0</div> + <div>{{SINCE}}</div> </div> + {{SHOW_IT}} + <monster-tabs> + <div data-monster-button-label="Overview" + class="active" + data-monster-button-icon="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16'%3E%3Cpath fill-rule='evenodd' d='m8 2.42-.717-.737c-1.13-1.161-3.243-.777-4.01.72-.35.685-.451 1.707.236 3.062C4.16 6.753 5.52 8.32 8 10.042c2.479-1.723 3.839-3.29 4.491-4.577.687-1.355.587-2.377.236-3.061-.767-1.498-2.88-1.882-4.01-.721zm-.49 8.5c-10.78-7.44-3-13.155.359-10.063q.068.062.132.129.065-.067.132-.129c3.36-3.092 11.137 2.624.357 10.063l.235.468a.25.25 0 1 1-.448.224l-.008-.017c.008.11.02.202.037.29.054.27.161.488.419 1.003.288.578.235 1.15.076 1.629-.157.469-.422.867-.588 1.115l-.004.007a.25.25 0 1 1-.416-.278c.168-.252.4-.6.533-1.003.133-.396.163-.824-.049-1.246l-.013-.028c-.24-.48-.38-.758-.448-1.102a3 3 0 0 1-.052-.45l-.04.08a.25.25 0 1 1-.447-.224l.235-.468ZM6.013 2.06c-.649-.18-1.483.083-1.85.798-.131.258-.245.689-.08 1.335.063.244.414.198.487-.043.21-.697.627-1.447 1.359-1.692.217-.073.304-.337.084-.398'/%3E%3C/svg%3E"> + {{OVERVIEW}} + </div> + <div data-monster-button-label="Usage" + data-monster-button-icon="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16'%3E%3Cpath d='M9.752 6.193c.599.6 1.73.437 2.528-.362s.96-1.932.362-2.531c-.599-.6-1.73-.438-2.528.361-.798.8-.96 1.933-.362 2.532'/%3E%3Cpath d='M15.811 3.312c-.363 1.534-1.334 3.626-3.64 6.218l-.24 2.408a2.56 2.56 0 0 1-.732 1.526L8.817 15.85a.51.51 0 0 1-.867-.434l.27-1.899c.04-.28-.013-.593-.131-.956a9 9 0 0 0-.249-.657l-.082-.202c-.815-.197-1.578-.662-2.191-1.277-.614-.615-1.079-1.379-1.275-2.195l-.203-.083a10 10 0 0 0-.655-.248c-.363-.119-.675-.172-.955-.132l-1.896.27A.51.51 0 0 1 .15 7.17l2.382-2.386c.41-.41.947-.67 1.524-.734h.006l2.4-.238C9.005 1.55 11.087.582 12.623.208c.89-.217 1.59-.232 2.08-.188.244.023.435.06.57.093q.1.026.16.045c.184.06.279.13.351.295l.029.073a3.5 3.5 0 0 1 .157.721c.055.485.051 1.178-.159 2.065m-4.828 7.475.04-.04-.107 1.081a1.54 1.54 0 0 1-.44.913l-1.298 1.3.054-.38c.072-.506-.034-.993-.172-1.418a9 9 0 0 0-.164-.45c.738-.065 1.462-.38 2.087-1.006M5.205 5c-.625.626-.94 1.351-1.004 2.09a9 9 0 0 0-.45-.164c-.424-.138-.91-.244-1.416-.172l-.38.054 1.3-1.3c.245-.246.566-.401.91-.44l1.08-.107zm9.406-3.961c-.38-.034-.967-.027-1.746.163-1.558.38-3.917 1.496-6.937 4.521-.62.62-.799 1.34-.687 2.051.107.676.483 1.362 1.048 1.928.564.565 1.25.941 1.924 1.049.71.112 1.429-.067 2.048-.688 3.079-3.083 4.192-5.444 4.556-6.987.183-.771.18-1.345.138-1.713a3 3 0 0 0-.045-.283 3 3 0 0 0-.3-.041Z'/%3E%3Cpath d='M7.009 12.139a7.6 7.6 0 0 1-1.804-1.352A7.6 7.6 0 0 1 3.794 8.86c-1.102.992-1.965 5.054-1.839 5.18.125.126 3.936-.896 5.054-1.902Z'/%3E%3C/svg%3E"> + {{EXAMPLES}} + </div> + <div data-monster-button-label="Design" + data-monster-button-icon="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16'%3E%3Cpath d='M6.192 2.78c-.458-.677-.927-1.248-1.35-1.643a3 3 0 0 0-.71-.515c-.217-.104-.56-.205-.882-.02-.367.213-.427.63-.43.896-.003.304.064.664.173 1.044.196.687.556 1.528 1.035 2.402L.752 8.22c-.277.277-.269.656-.218.918.055.283.187.593.36.903.348.627.92 1.361 1.626 2.068.707.707 1.441 1.278 2.068 1.626.31.173.62.305.903.36.262.05.64.059.918-.218l5.615-5.615c.118.257.092.512.05.939-.03.292-.068.665-.073 1.176v.123h.003a1 1 0 0 0 1.993 0H14v-.057a1 1 0 0 0-.004-.117c-.055-1.25-.7-2.738-1.86-3.494a4 4 0 0 0-.211-.434c-.349-.626-.92-1.36-1.627-2.067S8.857 3.052 8.23 2.704c-.31-.172-.62-.304-.903-.36-.262-.05-.64-.058-.918.219zM4.16 1.867c.381.356.844.922 1.311 1.632l-.704.705c-.382-.727-.66-1.402-.813-1.938a3.3 3.3 0 0 1-.131-.673q.137.09.337.274m.394 3.965c.54.852 1.107 1.567 1.607 2.033a.5.5 0 1 0 .682-.732c-.453-.422-1.017-1.136-1.564-2.027l1.088-1.088q.081.181.183.365c.349.627.92 1.361 1.627 2.068.706.707 1.44 1.278 2.068 1.626q.183.103.365.183l-4.861 4.862-.068-.01c-.137-.027-.342-.104-.608-.252-.524-.292-1.186-.8-1.846-1.46s-1.168-1.32-1.46-1.846c-.147-.265-.225-.47-.251-.607l-.01-.068zm2.87-1.935a2.4 2.4 0 0 1-.241-.561c.135.033.324.11.562.241.524.292 1.186.8 1.846 1.46.45.45.83.901 1.118 1.31a3.5 3.5 0 0 0-1.066.091 11 11 0 0 1-.76-.694c-.66-.66-1.167-1.322-1.458-1.847z'/%3E%3C/svg%3E"> + {{DESIGN}} + </div> + <div data-monster-button-label="API" + data-monster-button-icon="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16'%3E%3Cpath d='M6 0a.5.5 0 0 1 .5.5V3h3V.5a.5.5 0 0 1 1 0V3h1a.5.5 0 0 1 .5.5v3A3.5 3.5 0 0 1 8.5 10c-.002.434-.01.845-.04 1.22-.041.514-.126 1.003-.317 1.424a2.08 2.08 0 0 1-.97 1.028C6.725 13.9 6.169 14 5.5 14c-.998 0-1.61.33-1.974.718A1.92 1.92 0 0 0 3 16H2c0-.616.232-1.367.797-1.968C3.374 13.42 4.261 13 5.5 13c.581 0 .962-.088 1.218-.219.241-.123.4-.3.514-.55.121-.266.193-.621.23-1.09.027-.34.035-.718.037-1.141A3.5 3.5 0 0 1 4 6.5v-3a.5.5 0 0 1 .5-.5h1V.5A.5.5 0 0 1 6 0M5 4v2.5A2.5 2.5 0 0 0 7.5 9h1A2.5 2.5 0 0 0 11 6.5V4z'/%3E%3C/svg%3E"> + {{API}} + </div> - <div class="show-it"> + </monster-tabs> + </main> + <footer> + <p>© {{DATE_YEAR}} Powered by schukai GmbH - All rights reserved.</p> + <p>Code and documentation licensed by <a target="_blank" + href="https://www.gnu.org/licenses/agpl-3.0.en.html">AGPLv3 + License</a> or commercial license.</p> + </footer> - <monster-collapse data-monster-option-openbydefault="true"> - <p>This is a collapse control that is already open. For this purpose, the control has the - attribute <code>data-monster-option-openbydefault="true"</code>. - </p> - </monster-collapse> + </monster-panel> + </monster-split-panel> +</monster-panel> - </div> +</body> +</html> +`; +const exampleTemplate = ` + <h2>{{NAME}}</h2> + <monster-tabs> + <div class="active" + data-monster-button-label="Component"> + {{DESCRIPTION}} + <div class="show-it"> + {{HTML}} + </div> + </div> + <div data-monster-button-label="Code"> + <h3>Javascript</h3> + <pre><code class="language-javascript example-code">{{SCRIPT}}</code></pre> + <h3>HTML</h3> + <pre><code class="language-html example-code">{{HTML_ESCAPED}}</code></pre> + <h3>Stylesheet</h3> + <pre><code class="language-css example-code">{{STYLE}}</code></pre> + </div> + </monster-tabs> +`; - <monster-tabs> - <div data-monster-button-label="Overview" - class="active" - data-monster-button-icon="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16'%3E%3Cpath fill-rule='evenodd' d='m8 2.42-.717-.737c-1.13-1.161-3.243-.777-4.01.72-.35.685-.451 1.707.236 3.062C4.16 6.753 5.52 8.32 8 10.042c2.479-1.723 3.839-3.29 4.491-4.577.687-1.355.587-2.377.236-3.061-.767-1.498-2.88-1.882-4.01-.721zm-.49 8.5c-10.78-7.44-3-13.155.359-10.063q.068.062.132.129.065-.067.132-.129c3.36-3.092 11.137 2.624.357 10.063l.235.468a.25.25 0 1 1-.448.224l-.008-.017c.008.11.02.202.037.29.054.27.161.488.419 1.003.288.578.235 1.15.076 1.629-.157.469-.422.867-.588 1.115l-.004.007a.25.25 0 1 1-.416-.278c.168-.252.4-.6.533-1.003.133-.396.163-.824-.049-1.246l-.013-.028c-.24-.48-.38-.758-.448-1.102a3 3 0 0 1-.052-.45l-.04.08a.25.25 0 1 1-.447-.224l.235-.468ZM6.013 2.06c-.649-.18-1.483.083-1.85.798-.131.258-.245.689-.08 1.335.063.244.414.198.487-.043.21-.697.627-1.447 1.359-1.692.217-.073.304-.337.084-.398'/%3E%3C/svg%3E"> +const apiTemplate = ` + <h2>HTML Structure</h2> + <pre><code class="language-html">{{HTML}}</code></pre> + <h2>JavaScript Initialization</h2> + <pre><code class="language-js">{{JS}}</code></pre> + <h2>Options</h2> + <div class="option-grid"> + <div class="option-headline">Option</div> + <div class="option-headline">Type</div> + <div class="option-headline">Default</div> + <div class="option-headline">Description</div> + {{OPTIONS}} + </div><br> + <h2>Properties and Attributes</h2> + <ul> + <li><code>data-monster-options</code>: Sets the configuration options for the collapse + component when used as an HTML attribute. + </li> + <li><code>data-monster-option-[name]</name></code>: Sets the value of the configuration + option <code>[name]</code> for the collapse component when used as an HTML attribute. + </li> + </ul> + <h2>Methods</h2> + {{METHODS}}<br> + <h2>Events</h2> + {{EVENTS}} +`; - <h2>Introduction</h2> - - <p>The \`Collapse\` component is an integral part of the Monster library, crafted to enhance the - interactivity and - organization of your web content. This component empowers developers and designers to create - collapsible - sections within web pages, allowing for a cleaner, more user-centric experience. </p> - <p>Whether you're building an extensive FAQ section, a complex product description, or simply - wish - to streamline - content navigation, the \`Collapse\` component is your go-to solution.</p> - - <h2>Key Features</h2> - - <ul> - - <li><strong>Dynamic Content Interaction</strong>: Facilitate an engaging user experience by - allowing visitors - to expand or collapse content as needed. This dynamic interaction not only improves - usability but also caters to the user's preference for content consumption. - - <li><strong>Seamless Integration</strong>: Designed to integrate flawlessly with the - MonsterJS - library, this - component embodies the principles of modularity and reusability. Embedding collapsible - elements into your web pages is intuitive, enhancing your development workflow and - output - quality. - - <li><strong>Customizable Design</strong>: With the \`Collapse\` component, you have the - freedom to - customize - appearance and behavior, ensuring that every collapsible section aligns perfectly with - your - site's aesthetic and functional requirements. - - <li><strong>Optimized for Performance</strong>: The component is built with performance in - mind, - ensuring that - the dynamic show/hide functionality does not compromise the speed and responsiveness of - your - web pages. - - <li><strong>Accessibility-Focused</strong>: Committed to inclusive design, the \`Collapse\` - component adheres to - accessibility standards, ensuring that all users can navigate and interact with your - content - effectively, regardless of how they access your site. - </ul> - - - <h2>Enhancing User Experience</h2> - - <p>The \`Collapse\` component is versatile, catering to various scenarios where content - organization - and - space optimization are key. Its implementation can significantly enhance sections like FAQs, - product - details, or any content that benefits from a structured, user-driven exploration - approach.</p> - - <h2>Streamlined Development Process</h2> - - <p>Integrating the \`Collapse\` component into your project is straightforward, allowing - developers to - implement interactive, collapsible sections without the hassle. This ease of use is central - to - MonsterJS's philosophy, where developer experience and efficiency are paramount.</p> +function compareVersions(versionA, versionB) { + const splitA = versionA.split('.').map(Number); + const splitB = versionB.split('.').map(Number); + for (let i = 0; i < 3; i++) { + if (splitA[i] > splitB[i]) { + return 1; + } else if (splitA[i] < splitB[i]) { + return -1; + } + } - </div> - <!--div data-monster-button-label="Implementation" - data-monster-button-icon="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='currentColor' class='bi bi-code' viewBox='0 0 16 16'%3E%3Cpath d='M5.854 4.854a.5.5 0 1 0-.708-.708l-3.5 3.5a.5.5 0 0 0 0 .708l3.5 3.5a.5.5 0 0 0 .708-.708L2.707 8zm4.292 0a.5.5 0 0 1 .708-.708l3.5 3.5a.5.5 0 0 1 0 .708l-3.5 3.5a.5.5 0 0 1-.708-.708L13.293 8z'/%3E%3C/svg%3E"> - Implementation - </div--> - <div data-monster-button-label="Usage" - data-monster-button-icon="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16'%3E%3Cpath d='M9.752 6.193c.599.6 1.73.437 2.528-.362s.96-1.932.362-2.531c-.599-.6-1.73-.438-2.528.361-.798.8-.96 1.933-.362 2.532'/%3E%3Cpath d='M15.811 3.312c-.363 1.534-1.334 3.626-3.64 6.218l-.24 2.408a2.56 2.56 0 0 1-.732 1.526L8.817 15.85a.51.51 0 0 1-.867-.434l.27-1.899c.04-.28-.013-.593-.131-.956a9 9 0 0 0-.249-.657l-.082-.202c-.815-.197-1.578-.662-2.191-1.277-.614-.615-1.079-1.379-1.275-2.195l-.203-.083a10 10 0 0 0-.655-.248c-.363-.119-.675-.172-.955-.132l-1.896.27A.51.51 0 0 1 .15 7.17l2.382-2.386c.41-.41.947-.67 1.524-.734h.006l2.4-.238C9.005 1.55 11.087.582 12.623.208c.89-.217 1.59-.232 2.08-.188.244.023.435.06.57.093q.1.026.16.045c.184.06.279.13.351.295l.029.073a3.5 3.5 0 0 1 .157.721c.055.485.051 1.178-.159 2.065m-4.828 7.475.04-.04-.107 1.081a1.54 1.54 0 0 1-.44.913l-1.298 1.3.054-.38c.072-.506-.034-.993-.172-1.418a9 9 0 0 0-.164-.45c.738-.065 1.462-.38 2.087-1.006M5.205 5c-.625.626-.94 1.351-1.004 2.09a9 9 0 0 0-.45-.164c-.424-.138-.91-.244-1.416-.172l-.38.054 1.3-1.3c.245-.246.566-.401.91-.44l1.08-.107zm9.406-3.961c-.38-.034-.967-.027-1.746.163-1.558.38-3.917 1.496-6.937 4.521-.62.62-.799 1.34-.687 2.051.107.676.483 1.362 1.048 1.928.564.565 1.25.941 1.924 1.049.71.112 1.429-.067 2.048-.688 3.079-3.083 4.192-5.444 4.556-6.987.183-.771.18-1.345.138-1.713a3 3 0 0 0-.045-.283 3 3 0 0 0-.3-.041Z'/%3E%3Cpath d='M7.009 12.139a7.6 7.6 0 0 1-1.804-1.352A7.6 7.6 0 0 1 3.794 8.86c-1.102.992-1.965 5.054-1.839 5.18.125.126 3.936-.896 5.054-1.902Z'/%3E%3C/svg%3E"> + return 0; +} - <h2>Simple collapse</h2> - <monster-tabs> - <div class="active" - data-monster-button-label="Component"> +const colors = { + red: '\x1b[31m', + green: '\x1b[32m', + blue: '\x1b[34m', + bold: '\x1b[1m', + reset: '\x1b[0m' +}; - <p>Here you can find an interactive example where you can open the collapse by pressing - the - button.</p> +// Check if stdout is TTY to determine if colors should be used +const useColors = process.stdout.isTTY; - <p>The control itself has no buttons with which you can open and close the control. this - must be done either by code or another button.</p> +const { red, green, blue, bold, reset } = useColors ? colors : { + red: '', green: '', blue: '', bold: '', reset: '' +}; - <div class="show-it"> - <button id="exampleX21A3-B">Click me</button> +let currentLevel = 0; - <monster-collapse id="exampleX21A3-C"> - <p>This is the content of the simple collapse.</p> - </monster-collapse> - - </div> - - <script> - const button = document.getElementById("exampleX21A3-B"); - const collapse = document.getElementById("exampleX21A3-C"); - button.addEventListener("click", () => { - collapse.toggle(); - }); - </script> - - - </div> - <div - data-monster-button-label="Code"> - - <!-- p>Open this example on - <a target="_blank" href="https://stackblitz.com/edit/vitejs-vite-rrw1xn?file=collapse.html&view=editor,preview"> - <img alt="The StackBlitz logo" title="Edit on StackBlitz" src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='0.7em' height='1em' viewBox='0 0 256 368'%3E%3Cpath fill='%2349a2f8' d='M109.586 217.013H0L200.34 0l-53.926 150.233H256L55.645 367.246l53.927-150.233z'/%3E%3C/svg%3E"> - StackBlitz</a>.</p --> - - <h3>Javascript</h3> - <pre><code class="language-javascript"> -import "@schukai/monster/source/components/layout/collapse.mjs"; - -</code></pre> - <h3>HTML</h3> - <pre><code class="language-html"><monster-collapse> - <p>This is the content of the simple collapse.</p> -</monster-collapse> -</code></pre> - </div> - </monster-tabs> - - <h2>Open by default</h2> - <monster-tabs> - <div class="active" - data-monster-button-label="Component"> - - <p>Here you can find an interactive example where you can open the collapse by pressing - the button.</p> - - <p>The control itself has no buttons with which you can open and close the control. this - must be done either by code or another button.</p> - - <div class="show-it"> - - <monster-collapse data-monster-option-openbydefault="true"> - <div>lorem ipsum dolor st amet, consteitur sadpselit, sed dam noum - eiimocienaboeyminvit ut - </div> - </monster-collapse> - - </div> - - <p>You can see that the first collapse is open by default and the second one is - closed.</p> - - </div> - <div data-monster-button-label="Code"> - - <!-- p>Open this example on - <a target="_blank" href="https://stackblitz.com/edit/vitejs-vite-rrw1xn?file=collapse.html&view=editor,preview"> - <img alt="The StackBlitz logo" title="Edit on StackBlitz" src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='0.7em' height='1em' viewBox='0 0 256 368'%3E%3Cpath fill='%2349a2f8' d='M109.586 217.013H0L200.34 0l-53.926 150.233H256L55.645 367.246l53.927-150.233z'/%3E%3C/svg%3E"> - StackBlitz</a>.</p --> - - <h3>Javascript</h3> - <pre><code - class="language-javascript">import "@schukai/monster/source/components/layout/collapse.mjs";</code></pre> - <h3>HTML</h3> - <pre><code class="language-html"> -<monster-collapse data-monster-option-openbydefault="true"> - <div>lorem ipsum dolor st amet, consteitur sadpselit, sed dam noum eiimocienaboeyminvit ut</div> -</monster-collapse> - </code></pre> - </div> - </monster-tabs> - - - <h2>Accordion</h2> - <monster-tabs> - <div class="active" - data-monster-button-label="Component"> - - <p>Here you can find an interactive example where you can open the collapse by pressing - the - button.</p> - - <p>The control itself has no buttons with which you can open and close the control. this - must be done either by code or another button.</p> - - <div class="show-it"> - - <button id="exampleX21A4-B1">Toggle first</button> - <button id="exampleX21A4-B2">Toggle second</button> - - <monster-collapse data-monster-option-openbydefault="true" id="exampleX21A4-C1"> - <div>lorem ipsum dolor st amet, consteitur sadpselit, sed dam noum eiimocie - naboeyminvit ut - </div> - </monster-collapse> - <monster-collapse id="exampleX21A4-C2"> - <div>ineimad imnimevinam, qusi nostrud exerci tation ullacmorper susicpti lobrotis - nsil uta lquiip - </div> - </monster-collapse> - - </div> - - <p>You can see that the first collapse is open by default and the second one is - closed.</p> - - <script> - const button1 = document.getElementById("exampleX21A4-B1"); - const button2 = document.getElementById("exampleX21A4-B2"); - const collapse1 = document.getElementById("exampleX21A4-C1"); - const collapse2 = document.getElementById("exampleX21A4-C2"); - button1.addEventListener("click", () => { - collapse1.toggle(); - }); - button2.addEventListener("click", () => { - collapse2.toggle(); - }); - </script> - - - </div> - <div - data-monster-button-label="Code"> - - <!-- p>Open this example on - <a target="_blank" href="https://stackblitz.com/edit/vitejs-vite-rrw1xn?file=collapse.html&view=editor,preview"> - <img alt="The StackBlitz logo" title="Edit on StackBlitz" src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='0.7em' height='1em' viewBox='0 0 256 368'%3E%3Cpath fill='%2349a2f8' d='M109.586 217.013H0L200.34 0l-53.926 150.233H256L55.645 367.246l53.927-150.233z'/%3E%3C/svg%3E"> - StackBlitz</a>.</p --> - - <h3>Javascript</h3> - <pre><code class="language-javascript"> -import "@schukai/monster/source/components/layout/collapse.mjs"; -</code></pre> - <h3>HTML</h3> - <pre><code class="language-html"> -<monster-collapse data-monster-option-openbydefault="true"> - <div>lorem ipsum dolor st amet, consteitur sadpselit, sed dam noum eiimocie naboeyminvit ut</div> -</monster-collapse> -<monster-collapse> - <div>ineimad imnimevinam, qusi nostrud exerci tation ullacmorper susicpti lobrotis nsil uta lquiip</div> -</monster-collapse> -</code></pre> - </div> - </monster-tabs> +function getPadding() { + return ' '.repeat(currentLevel); +} - </div> +function increaseLevel() { + currentLevel++; +} - <div data-monster-button-label="Design" - data-monster-button-icon="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16'%3E%3Cpath d='M6.192 2.78c-.458-.677-.927-1.248-1.35-1.643a3 3 0 0 0-.71-.515c-.217-.104-.56-.205-.882-.02-.367.213-.427.63-.43.896-.003.304.064.664.173 1.044.196.687.556 1.528 1.035 2.402L.752 8.22c-.277.277-.269.656-.218.918.055.283.187.593.36.903.348.627.92 1.361 1.626 2.068.707.707 1.441 1.278 2.068 1.626.31.173.62.305.903.36.262.05.64.059.918-.218l5.615-5.615c.118.257.092.512.05.939-.03.292-.068.665-.073 1.176v.123h.003a1 1 0 0 0 1.993 0H14v-.057a1 1 0 0 0-.004-.117c-.055-1.25-.7-2.738-1.86-3.494a4 4 0 0 0-.211-.434c-.349-.626-.92-1.36-1.627-2.067S8.857 3.052 8.23 2.704c-.31-.172-.62-.304-.903-.36-.262-.05-.64-.058-.918.219zM4.16 1.867c.381.356.844.922 1.311 1.632l-.704.705c-.382-.727-.66-1.402-.813-1.938a3.3 3.3 0 0 1-.131-.673q.137.09.337.274m.394 3.965c.54.852 1.107 1.567 1.607 2.033a.5.5 0 1 0 .682-.732c-.453-.422-1.017-1.136-1.564-2.027l1.088-1.088q.081.181.183.365c.349.627.92 1.361 1.627 2.068.706.707 1.44 1.278 2.068 1.626q.183.103.365.183l-4.861 4.862-.068-.01c-.137-.027-.342-.104-.608-.252-.524-.292-1.186-.8-1.846-1.46s-1.168-1.32-1.46-1.846c-.147-.265-.225-.47-.251-.607l-.01-.068zm2.87-1.935a2.4 2.4 0 0 1-.241-.561c.135.033.324.11.562.241.524.292 1.186.8 1.846 1.46.45.45.83.901 1.118 1.31a3.5 3.5 0 0 0-1.066.091 11 11 0 0 1-.76-.694c-.66-.66-1.167-1.322-1.458-1.847z'/%3E%3C/svg%3E"> +function decreaseLevel() { + currentLevel--; +} - <p> - The control element can be adapted to your own requirements. To do this, the control element - can - be designed with CSS like almost any other HTML element. - </p> - - <p> - However, there are a few things to bear in mind. As the innards of the control are located - in a - ShadowRoot, they cannot be accessed directly with CSS selectors. Only the elements specified - for - this purpose can be accessed. These elements have the attribute <i>part</i>. - </p> - - <p> - In CSS, these parts can then be used for styling via a CSS pseudo-element Parts. - Here you can see an example of how you can use this. - </p> - - <pre><code class="language-css"> -::part(container) { - border: 1px solid red; +function echo(text, color = '') { + const padding = getPadding(); + console.log(`${padding}${color}${text}${reset}`); } - </code></pre> - <p>The following diagram shows the parts and the slots.</p> +function echoOk(message) { + echo(`✔ ${message}`, green); +} +function echoFail(message) { + echo(`✖ ${message}`, red); +} - <img src="assets/component.layout.collapse.design.svg" alt="Design" style="width: 100%;"> +function echoHint(message) { + echo(`→ ${message}`, blue); +} +function echoStep(message) { + echo(`• ${message}`, blue); +} - </div> - <div data-monster-button-label="API" - data-monster-button-icon="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16'%3E%3Cpath d='M6 0a.5.5 0 0 1 .5.5V3h3V.5a.5.5 0 0 1 1 0V3h1a.5.5 0 0 1 .5.5v3A3.5 3.5 0 0 1 8.5 10c-.002.434-.01.845-.04 1.22-.041.514-.126 1.003-.317 1.424a2.08 2.08 0 0 1-.97 1.028C6.725 13.9 6.169 14 5.5 14c-.998 0-1.61.33-1.974.718A1.92 1.92 0 0 0 3 16H2c0-.616.232-1.367.797-1.968C3.374 13.42 4.261 13 5.5 13c.581 0 .962-.088 1.218-.219.241-.123.4-.3.514-.55.121-.266.193-.621.23-1.09.027-.34.035-.718.037-1.141A3.5 3.5 0 0 1 4 6.5v-3a.5.5 0 0 1 .5-.5h1V.5A.5.5 0 0 1 6 0M5 4v2.5A2.5 2.5 0 0 0 7.5 9h1A2.5 2.5 0 0 0 11 6.5V4z'/%3E%3C/svg%3E"> +function echoSection(title) { + decreaseLevel(); + echo(`\n\n${title}`, bold); + increaseLevel(); +} - <h2>HTML Structure</h2> - <pre><code><monster-collapse></monster-collapse></code></pre> - - <h2>JavaScript Initialization</h2> - <pre><code>document.createElement('monster-collapse');</code></pre> - - <h2>Options</h2> - <ul> - <li><code>template.main</code>: The main template of the component.</li> - <li><code>classes.container</code>: The class of the container element. Remember that the - container element is in the shadow Root.</li> - <li><code>features.accordion</code>: Whether the component should behave as an accordion. - If this is set to <code>true</code>, previously opened components will be closed - when a new one is opened. It is important that the elements are siblings.</li> - <li><code>features.persistState</code>: Whether the component should persist its state.</li> - <li><code>features.useScrollValues</code>: Whether the component should use scroll values. - </li> - <li><code>openByDefault</code>: Whether the component should be open by default.</li> - </ul> - - <h2>Properties and Attributes</h2> - <ul> - <li><code>data-monster-options</code>: Sets the configuration options for the collapse - component when used as an HTML attribute. - </li> - <li><code>data-monster-option-[name]</name></code>: Sets the value of the configuration - option <code>[name]</code> for the collapse component when used as an HTML attribute. - - </li> - </ul> - - <h2>Methods</h2> - <ul> - <li><code>toggle()</code>: Toggles the open/close state of the component.</li> - <li><code>open()</code>: Opens the component, revealing the content.</li> - <li><code>close()</code>: Closes the component, hiding the content.</li> - <li><code>adjustHeight()</code>: Adjusts the height of the component based on its content. - </li> - </ul> - - <h2>Events</h2> - <ul> - <li><code>monster-collapse-before-open</code>: Fired before the component opens.</li> - <li><code>monster-collapse-open</code>: Fired when the component has opened.</li> - <li><code>monster-collapse-before-close</code>: Fired before the component closes.</li> - <li><code>monster-collapse-closed</code>: Fired when the component has closed.</li> - <li><code>monster-collapse-adjust-height</code>: Fired when the height of the component is - adjusted. - </li> - </ul> +function echoDelimiter() { + console.log(" "); +} +function isTrue(value) { + return value === 'true' || value === '1'; +} - </div> +let debugMode = process.env.DEBUG_MODE || 'false'; +let ciMode = process.env.CI_JOB_TOKEN ? 'true' : 'false'; - </monster-tabs> - </main> - <footer> - <p>© 2021 Powered by schukai GmbH - All rights reserved.</p> - <p>Code licensed by <a target="_blank" href="https://www.gnu.org/licenses/agpl-3.0.en.html">AGPLv3 - License</a> or commercial license.</p> - </footer> +if (isTrue(debugMode) || isTrue(ciMode)) { + console.log('Debug or CI mode is enabled'); +} + +export { echoOk, echoFail, echoHint, echoStep, echoSection, echoDelimiter, isTrue }; - </monster-panel> - </monster-split-panel> -</monster-panel> -</body> -</html> -`; const args = process.argv.slice(2); @@ -448,24 +265,663 @@ function printUsage() { console.log("Usage: task create-new-component-class --file=filename"); } +function replaceCodeWithTags(text) { + const encodeHtml = html => html.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"').replace(/'/g, '''); + + text = text.replace(/```(\w*)\n([\s\S]*?)```/g, (match, lang, code) => { + code = encodeHtml(code); + const classAttr = lang ? ` class="language-${lang}"` : ''; + return `<pre><code${classAttr}>\n${code}\n</code></pre>`; + }); + + text = text.replace(/`([^`]+)`/g, (match, code) => { + code = encodeHtml(code); + return `<code>${code}</code>`; + }); + + return text; +} + +// const tagCloseMarker = "ZZZZZZZ"; +// const preOpenMarker = "PPPPPPPOPEN" +// const preCloseMarker = "PPPPPPPCLOSE" +// +// function replaceCodeWithTags(text) { +// +// text = text.trim(); +// +// let markerStart = "<code"; +// let markerStartClose = ">"; +// let markerEnd = "</code>"; +// let preOpenTag = "<pre>"; +// let preCloseTag = "</pre>"; +// +// if (open !== undefined && open !== null && open !== "") { +// markerStart = open; +// markerStartClose = tagCloseMarker; +// preOpenTag = preOpenMarker; +// preCloseTag = preCloseMarker; +// } +// +// if (close !== undefined && close !== null && close !== "") { +// markerEnd = close; +// } +// +// +// text = text.replace(/```(\w*)?\n([\s\S]*?)```/g, (match, lang, code) => { +// const languageClass = lang ? ` class="language-${lang}"` : ''; +// return `${preOpenTag}${markerStart}${languageClass}${markerStartClose}${code.trim()}${markerEnd}${preCloseTag}`; +// }); +// console.log("-------1----------") +// console.log(text) +// text = text.replace(/`([^`]+)`/g, `${markerStart}${markerStartClose}$1${markerEnd}`); +// console.log(text) +// console.log("---------2--------") +// +// return text; +// } + +function checkPath(file, projectRoot) { + if (!file.startsWith('/')) { + throw new Error('The path must start with a slash.'); + } + + if (file.startsWith(projectRoot)) { + const relativePath = file.substring(projectRoot.length + 1); + + if (relativePath === '') { + throw new Error('The path must not be the project root.'); + } -if (!argsObj.file||argsObj.file==="") { + return relativePath; + } else { + throw new Error('The path must not be empty and have to be inside the project root.'); + } +} + +function removeFileExtension(filename) { + const lastDotIndex = filename.lastIndexOf('.'); + if (lastDotIndex <= 0) return filename; + return filename.substring(0, lastDotIndex); +} + +if (!argsObj.file || argsObj.file === "") { console.error("No filename provided, you can use --file=filename"); printUsage(); process.exit(1); } -const code = template.replace(/{{HTML_ELEMENT_FORM_INTERNALS}}/g, formInternalsCode) - .replace(/{{VERSION}}/g, nextVersion) - .replace(/{{NAMESPACE_WITH_DOTS}}/g, namespaceWithDots); +const classCode = readFileSync(argsObj.file, 'utf8'); +if (!classCode) { + console.error("File not found or empty"); + process.exit(1); +} -const showroomPageFilename = "x.html"; -const showroomPagePath = path.resolve(projectRoot, `./showroom/source/${argsObj.file}.html`); -console.log(showroomPagePath); + +const ast = espree.parse(classCode, { + range: false, + loc: true, + comment: true, + tokens: false, + ecmaVersion: 11, + allowReserved: false, + sourceType: "module", + ecmaFeatures: { + jsx: false, + globalReturn: false, + impliedStrict: false + } +}); + +const validDocTypes = ["param", "fires", + "return", "throws", "example", "summary", + "see", "link", "since", "deprecated", "fragments", + "version", "property", "type", "typedef"] + +const findNearestCommentAbove = (node, comments) => { + const relevantComments = comments.filter(comment => comment.loc.end.line < node.loc.start.line); + return relevantComments[relevantComments.length - 1]; +}; + +function extractDocTypes(code) { + const lines = code.split('\n'); + const docInfo = {}; + + for (const line of lines) { + const firstIndex = line.indexOf('@'); + if (firstIndex === -1) { + continue; + } + + let endIndex = line.indexOf(' ', firstIndex); + if (endIndex === -1) { + endIndex = line.length; + } + let doctype = line.substring(firstIndex + 1, endIndex); + if (!validDocTypes.includes(doctype)) { + continue; + } + + const value = line.substring(endIndex + 1).trim(); + + //const inner = extractDocTypes(value); + + if (!docInfo[doctype]) { + docInfo[doctype] = {}; + } + + if (!docInfo[doctype].values) docInfo[doctype].values = []; + docInfo[doctype].values.push(value); + } + return docInfo; +} + +const extractClassInformation = (ast) => { + + let classInfo = { + name: null, + methods: [], + type: null, + doc: [], + exports: [], + }; + + estraverse.traverse(ast, { + enter: (node) => { + + if (node.type === 'ExportSpecifier') { + const exportName = node?.exported?.name; + if (exportName) { + classInfo.exports.push(exportName); + } + } else if (node.type === 'ClassDeclaration') { + + if (classInfo.name !== null) { + throw new Error('Only one class per file is supported'); + } + + classInfo.name = node.id.name; + classInfo.type = node.body.type; + + const classComment = findNearestCommentAbove(node, ast.comments); + if (classComment) { + const d = classComment.value.trim(); + const lines = d.split('\n'); + for (const line of lines) { + const dWithoutCommentStar = line.replace(/^\s*\*\s*/, ''); + if (!line.startsWith("/**") && !line.endsWith("*/") && !/^\s*\* @\w+/.test(line)) { + classInfo.doc.push(dWithoutCommentStar); + } + } + + classInfo.docTypes = extractDocTypes(d); + } + + node.body.body.forEach((element) => { + + if (element.type === 'MethodDefinition') { + const method = { + name: element.key.name, + params: element.value.params.map(param => param.name), + type: element.value.type, + doc: [], + }; + + const methodComment = findNearestCommentAbove(element, ast.comments); + if (methodComment) { + const d = methodComment.value.trim(); + const lines = d.split('\n'); + for (const line of lines) { + const dWithoutCommentStar = line.replace(/^\s*\*\s*/, ''); + if (!line.startsWith("/**") && !line.endsWith("*/") && !/^\s*\* @\w+/.test(line)) { + method.doc.push(dWithoutCommentStar); + } + } + } + + method.docTypes = extractDocTypes(methodComment.value.trim()); + + classInfo.methods.push(method); + } + }); + + } + } + }); + + return classInfo; +}; + +const classInfo = extractClassInformation(ast); +//const keys = Object.keys(classInfo); + + +// for (const classInfoElement of classInfo.methods) { +// console.log(JSON.stringify(classInfoElement, null, 2)); +// +// } + +let relFilePath = ""; +let relFilePathWithoutExtension = ""; + +try { + relFilePath = checkPath(argsObj.file, sourcePath); + relFilePathWithoutExtension = removeFileExtension(relFilePath); +} catch (error) { + console.error(error.message); + process.exit(1); +} + +const showroomPageFilename = relFilePathWithoutExtension.toLowerCase().replace(/[^a-z0-9]/g, '.'); +const classSourcePath = "source/" + relFilePath; +const importPath = "@schukai/monster/source/" + relFilePath; + +const since = classInfo.docTypes?.since?.values?.length > 0 ? classInfo.docTypes.since.values[0] : "1.0.0"; +//const overview = classInfo?.doc?.join("\n") || "No overview available"; +const summary = classInfo.docTypes?.summary?.values?.length > 0 ? classInfo.docTypes.summary.values[0] : "No summary available"; + +function toTitleCase(str) { + return str.replace(/\b(\w)/g, function (match) { + return match.toUpperCase(); + }); +} + +const examples = []; + +for (const example of classInfo.docTypes?.example?.values) { + + const headline = toTitleCase(example.split('/').pop().replace(/-/g, ' ').replace(/.mjs/g, '').replace(/.js/g, '')); + let exampleCode = exampleTemplate.replace(/{{NAME}}/g, headline) + + const scriptPath = join(projectRoot, "showroom/source", example, "script.mjs"); + if (existsSync(scriptPath)) { + const script = readFileSync(scriptPath).toString().trim(); + exampleCode = exampleCode.replace(/{{SCRIPT}}/g, script); + } else { + exampleCode = exampleCode.replace(/{{SCRIPT}}/g, "/** this example does not use an extra script **/"); + } + + const htmlPath = join(projectRoot, "showroom/source", example, "main.html"); + if (existsSync(htmlPath)) { + const html = readFileSync(htmlPath).toString().trim(); + exampleCode = exampleCode.replace(/{{HTML}}/g, html); + exampleCode = exampleCode.replace(/{{HTML_ESCAPED}}/g, html.replace(/</g, "<").replace(/>/g, ">")); + } else { + exampleCode = exampleCode.replace(/{{HTML}}/g, ""); + exampleCode = exampleCode.replace(/{{HTML_ESCAPED}}/g, "/** this example does not use an extra html file **/"); + } + + const cssPath = join(projectRoot, "showroom/source", example, "style.css"); + if (existsSync(cssPath)) { + const style = readFileSync(cssPath).toString().trim(); + exampleCode = exampleCode.replace(/{{STYLE}}/g, style); + } else { + exampleCode = exampleCode.replace(/{{STYLE}}/g, "/** no additional stylesheet is defined **/"); + } + + const readmePath = join(projectRoot, "showroom/source", example, "readme.html"); + if (existsSync(readmePath)) { + const readme = readFileSync(readmePath).toString().trim(); + exampleCode = exampleCode.replace(/{{DESCRIPTION}}/g, readme); + } else { + exampleCode = exampleCode.replace(/{{DESCRIPTION}}/g, ""); + } + + examples.push(exampleCode); +} + +let designFragment = []; +let overviewFragment = []; +let showItFragment = []; + +for (const p of classInfo.docTypes?.fragments?.values) { + + const designPath = join(projectRoot, "showroom/source", p, "design.html"); + if (existsSync(designPath)) { + designFragment.push(readFileSync(designPath).toString().trim()); + } + + const overviewPath = join(projectRoot, "showroom/source", p, "overview.html"); + if (existsSync(overviewPath)) { + overviewFragment.push(readFileSync(overviewPath).toString().trim()); + } + + const showItPath = join(projectRoot, "showroom/source", p, "show-it.html"); + if (existsSync(showItPath)) { + showItFragment.push(readFileSync(showItPath).toString().trim()); + } +} + +let controlOptions; + +function findFirstNonQuotedWhitespace(str) { + let inQuotes = false; + for (let i = 0; i < str.length; i++) { + if (str[i] === '"' && (i === 0 || str[i - 1] !== '\\')) { + inQuotes = !inQuotes; + } else if (str[i] === ' ' && !inQuotes) { + return i; + } + } + return -1; // Kein nicht-quotiertes Leerzeichen gefunden +} + +const methodSummary = { + constructor: [], + behavioral: [], + structural: [], + static: [], + other: [], +}; + +const eventsSummary = []; + +function normalizeTypes(type) { + type = type.replace("{", "").replace("}", "").trim(); + let internType = type.toLowerCase(); + + let isArray = false; + if (internType.endsWith("[]")) { + isArray = true; + internType = internType.substring(0, internType.length - 2); + } + if (internType === "string" || internType === "str") { + internType = "string"; + } else if (internType === "Number" || internType === "num" || internType === "number" || internType === "int" || internType === "float" || internType === "integer") { + internType = "number"; + } else if (internType === "boolean" || internType === "bool") { + internType = "boolean"; + } else if (internType === "object" || internType === "obj" || internType === "{}") { + internType = "object"; + } else if (internType === "array" || internType === "[]") { + internType = "array"; + } else if (internType === "symbol" || internType === "sym") { + internType = "symbol"; + } else if (internType === "function") { + internType = "function"; + } else if (internType === "null") { + internType = "null"; + } else if (internType === "undefined") { + internType = "undefined"; + } else if (internType === "any") { + internType = "any"; + } else { + internType = type + isArray = false; + } + + if (isArray) { + internType = internType + "[]"; + } + + return internType; +} + +const eventValues = classInfo.docTypes?.['fires']?.values; +if (eventValues) { + for (const event of eventValues) { + const f1 = event.indexOf(" "); + if (f1 === -1) { + eventsSummary.push("<li><code>" + event + "</code></li>"); + continue; + } + + const name = event.substring(0, f1).trim(); + const description = event.substring(f1 + 1).trim(); + const descriptionHTMLEncoded = description.replace(/</g, "<").replace(/>/g, ">"); + + eventsSummary.push("<li><code>" + name + "</code> " + descriptionHTMLEncoded + "</li>"); + + } +} + +// sort classInfo.methods by name +classInfo.methods.sort((a, b) => { + return a.name.localeCompare(b.name); +}); + + + +for (const method of classInfo.methods) { + if (method.name === "defaults" && method.type === "FunctionExpression") { + controlOptions = method.docTypes?.property?.values?.map((value) => { + + + const f1 = value.indexOf(" "); + if (f1 === -1) { + return "<li><code>" + value + "</code></li>"; + } + const type = normalizeTypes(value.substring(0, f1).trim().toLowerCase()); + + let defaultValue = ""; + let name = ""; + let currentRest = value.substring(f1 + 1).trim(); + const f2 = findFirstNonQuotedWhitespace(currentRest); + if (f2 !== -1) { + const nameAndDefault = currentRest.substring(0, f2).trim(); + name = nameAndDefault; + const indexOfEqual = nameAndDefault.indexOf("="); + if (indexOfEqual !== -1) { + name = nameAndDefault.substring(0, indexOfEqual).trim(); + defaultValue = nameAndDefault.substring(indexOfEqual + 1).trim(); + + if (defaultValue.startsWith('"') && defaultValue.endsWith('"')) { + defaultValue = defaultValue.substring(1, defaultValue.length - 1); + } + currentRest = currentRest.substring(f2 + 1).trim(); + } + + + } + + const description = currentRest.trim(); + + + const defaultValueHTMLEncoded = defaultValue.replace(/</g, "<").replace(/>/g, ">"); + const descriptionHTMLEncoded = description.replace(/</g, "<").replace(/>/g, ">"); + return "<div>" + name + "</div><div>" + type + "</div><div>" + defaultValueHTMLEncoded + "</div><div>" + descriptionHTMLEncoded + "</div>"; + }).join("\n"); + + } else { + + const methodEventsSummary = []; + + const params = []; + const paramsReturnAndThrows = []; + if (method.params.length > 0) { + + paramsReturnAndThrows.push("<strong>Parameters</strong><ul>"); + + for (const param of method.params) { + params.push(param); + + // check doctype + const paramDoc = method.docTypes?.param?.values?.find((value) => { + return (value + " ").indexOf(" " + param + " ") !== -1; + }); + + if (!paramDoc) { + continue; + } + + const f1 = paramDoc.indexOf(" "); + if (f1 === -1) { + paramsReturnAndThrows.push("<li><code>" + param + "</code>: " + paramDoc + "</li>"); + continue; + } + const type = normalizeTypes(paramDoc.substring(0, f1).trim().toLowerCase()); + + let name = ""; + let currentRest = paramDoc.substring(f1 + 1).trim(); + const f2 = findFirstNonQuotedWhitespace(currentRest); + if (f2 !== -1) { + name = currentRest.substring(0, f2).trim(); + currentRest = currentRest.substring(f2 + 1).trim(); + } + + const description = currentRest.trim(); + + const descriptionHTMLEncoded = description.replace(/</g, "<").replace(/>/g, ">"); + + paramsReturnAndThrows.push("<li><code>" + param + "</code> {" + type + "}: " + descriptionHTMLEncoded + "</li>"); + + } + + paramsReturnAndThrows.push("</ul>"); + + + } + if (method.docTypes?.['return']?.values?.length > 0) { + // check doctype + const returnDoc = method.docTypes?.['return']?.values.join("\n").trim(); + + const f1 = returnDoc.indexOf(" "); + if (f1 === -1) { + paramsReturnAndThrows.push("<strong>Returns</strong><ul><li>" + returnDoc + "</li></ul>"); + + } else { + const type = normalizeTypes(returnDoc.substring(0, f1).trim().toLowerCase()); + + + let name = ""; + let currentRest = returnDoc.substring(f1 + 1).trim(); + const f2 = findFirstNonQuotedWhitespace(currentRest); + if (f2 !== -1) { + name = currentRest.substring(0, f2).trim(); + currentRest = currentRest.substring(f2 + 1).trim(); + } + + const description = currentRest.trim(); + + const descriptionHTMLEncoded = description.replace(/</g, "<").replace(/>/g, ">"); + + + paramsReturnAndThrows.push("<strong>Returns</strong><ul><li>{" + type + "}: " + descriptionHTMLEncoded + "</li></ul>"); + } + } + + + //const breakMarker = "\nANii4le"; + const description = replaceCodeWithTags(method.doc.join("\n")); + // const htmlEncodedDescription = description.replace(/</g, "<").replace(/>/g, ">").replace(new RegExp(breakMarker, 'g'), "<br>") + // .replace(/XXXXXX/g, "<pre><code") + // .replace(/YYYYYY/g, "</code></pre>") + // .replace(new RegExp(preOpenMarker, 'g'), "<pre>") + // .replace(new RegExp(preCloseMarker, 'g'), "</pre>") + // .replace(new RegExp(tagCloseMarker, 'g'), ">"); + + + const throwsValues = method.docTypes?.['throws']?.values; + if (throwsValues && throwsValues.length > 0) { + paramsReturnAndThrows.push("<strong>Throws</strong><ul>"); + for (const throws of throwsValues) { + const f1 = throws.indexOf(" "); + if (f1 === -1) { + paramsReturnAndThrows.push("<li><code class='language-js'>" + throws + "</code></li>"); + continue; + } + + const name = throws.substring(0, f1).trim(); + const description = throws.substring(f1 + 1).trim(); + + paramsReturnAndThrows.push("<li><code class='language-js'>" + name + "</code> " + description + "</li>"); + } + paramsReturnAndThrows.push("</ul>"); + } + + + + const eventValues = method.docTypes?.['fires']?.values; + if (eventValues && eventValues.length > 0) { + + paramsReturnAndThrows.push("<strong>Events</strong><ul>"); + + for (const event of eventValues) { + const f1 = event.indexOf(" "); + if (f1 === -1) { + paramsReturnAndThrows.push("<li><code>" + event + "</code></li>"); + continue; + } + + const name = event.substring(0, f1).trim(); + const description = event.substring(f1 + 1).trim(); + //const descriptionHTMLEncoded = description.replace(/</g, "<").replace(/>/g, ">"); + + paramsReturnAndThrows.push("<li><code>" + name + "</code> " + description + "</li>"); + } + + paramsReturnAndThrows.push("</ul>"); + } + + let methodeSince = ""; + if (method.docTypes?.since?.values.length > 0) { + if (method.docTypes.since.values.length > 1) { + echoFail("Method " + method.name + " has more than one @since doctype"); + } + + const checkVersion = compareVersions(method.docTypes.since.values[0], since) ; + if (checkVersion < 0) { + echoFail("Method " + method.name + " of component " + classInfo.name + " has a @since version lower than the component version"); + } else { + methodeSince = method.docTypes?.since?.values?.length > 0 ? "<span class='monster-badge-secondary-pill'>"+method.docTypes.since.values[0]+"</span>" : ""; + } + } + + methodSummary.other.push("<div class='method-signature'><code class='language-javascript'>" + method.name + "(" + params + ")</code>"+methodeSince+"</div>" + + "<div>" + paramsReturnAndThrows.join("\n") + " </div>" + + "<div class='method-description'>" + description + " </div>"); + + + } +} + +let eventSummaryHTML = "<p>this component does not fire any public events.</p>"; +if (eventsSummary.length > 0) { + eventSummaryHTML = "<p>The component emits the following events:</p>"; + eventSummaryHTML += "<ul>" + eventsSummary.join("\n") + "</ul>"; + + eventSummaryHTML += "<p>For more information on how to handle events, see the <a target='_blank' href=\"https://developer.mozilla.org/en-US/docs/Web/Events\">mdn documentation</a>.</p>"; + +} + +const htmlTagFromClassName = classInfo.name.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase(); +let api = apiTemplate.replace(/{{HTML}}/g, "<monster-" + htmlTagFromClassName + "></monster-" + htmlTagFromClassName + ">") + .replace(/{{JS}}/g, "const element = document.createElement('monster-" + htmlTagFromClassName + "');" + + "\ndocument.body.appendChild(element);") + .replace(/{{OPTIONS}}/g, controlOptions) + .replace(/{{METHODS}}/g, "<h3>Other methods</h3><div class=\"method-grid\">" + methodSummary.other.join("\n") + "</div>") + .replace(/{{EVENTS}}/g,eventSummaryHTML); + +let showIt = ""; + if (showItFragment.length > 0) { + showIt = "<div class=\"show-it\">" + showItFragment.join("\n") + "</div>"; +} + + +const code = template.replace(/{{CLASS_NAME}}/g, classInfo.name) + .replace(/{{SOURCE_PATH}}/g, classSourcePath) + .replace(/{{IMPORT_PATH}}/g, importPath) + .replace(/{{SHOW_IT}}/g, showIt) + .replace(/{{OVERVIEW}}/g, overviewFragment.join("\n")) + .replace(/{{DESIGN}}/g, designFragment.join("\n")) + .replace(/{{SUMMARY}}/g, summary) + .replace(/{{API}}/g, api) + .replace(/{{DATE_YEAR}}/g, new Date().getFullYear().toString()) + .replace(/{{EXAMPLES}}/g, examples.join("\n")) + .replace(/{{SINCE}}/g, since); + +const showroomPagePath = join(projectRoot, `./showroom/source/${showroomPageFilename}.html`); try { - //writeFileSync(showroomPagePath, code); + writeFileSync(showroomPagePath, code); } catch (e) { console.error(e); process.exit(1); } + + +// console.log(keys, null, 2); +// +// const docTypes = classInfo.docTypes; +//console.log(JSON.stringify(classInfo, null, 2)); + diff --git a/example/components/form/button.mjs b/example/components/form/button.mjs index e2b6d32a1ada9032f7ca44767ff1419b2f4c1cda..2a90dffd89dc3b5e3e882f818eab0d2b8abd37a5 100644 --- a/example/components/form/button.mjs +++ b/example/components/form/button.mjs @@ -1,4 +1,4 @@ -import {Button} from '@schukai/component-form/source/button.js'; +import {Button} from '@schukai/monster/source/components/form/button.js'; const button = document.createElement('monster-button'); // set label diff --git a/package.json b/package.json index fc6b3c5a3665886065ea0a84e55145bc78f55f4a..5f199d2e14ae5851a0b96c04b4cad5114adb12c6 100644 --- a/package.json +++ b/package.json @@ -42,6 +42,7 @@ "dependencies": { "@floating-ui/dom": "^1.6.3", "@popperjs/core": "^2.11.8", + "estraverse": "^5.3.0", "vite-plugin-directory-index": "^3.0.1" }, "devDependencies": { @@ -64,6 +65,7 @@ "esbuild": "^0.19.12", "esdoc": "^1.1.0", "esdoc-standard-plugin": "^1.0.0", + "espree": "^10.0.1", "flow-bin": "^0.221.0", "fs": "0.0.1-security", "glob": "^10.3.12", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index fcf377e7f43c899f3c1da5361acd491fe0a5c0f7..efe53d3c407ea3621ff46f7bd03448c67899b9ce 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -11,6 +11,9 @@ dependencies: '@popperjs/core': specifier: ^2.11.8 version: 2.11.8 + estraverse: + specifier: ^5.3.0 + version: 5.3.0 vite-plugin-directory-index: specifier: ^3.0.1 version: 3.0.1(vite@5.2.7) @@ -73,6 +76,9 @@ devDependencies: esdoc-standard-plugin: specifier: ^1.0.0 version: 1.0.0 + espree: + specifier: ^10.0.1 + version: 10.0.1 flow-bin: specifier: ^0.221.0 version: 0.221.0 @@ -1746,6 +1752,14 @@ packages: dev: true optional: true + /acorn-jsx@5.3.2(acorn@8.11.3): + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + dependencies: + acorn: 8.11.3 + dev: true + /acorn@2.7.0: resolution: {integrity: sha512-pXK8ez/pVjqFdAgBkF1YPVRacuLQ9EXBKaKWaeh58WNfMkCmZhOZzu+NtKSPD5PHmCCHheQ5cD29qM1K4QTxIg==} engines: {node: '>=0.4.0'} @@ -3336,6 +3350,20 @@ packages: - supports-color dev: true + /eslint-visitor-keys@4.0.0: + resolution: {integrity: sha512-OtIRv/2GyiF6o/d8K7MYKKbXrOUBIK6SfkIRM4Z0dY3w+LiQ0vy3F57m0Z71bjbyeiWFiHJ8brqnmE6H6/jEuw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + dev: true + + /espree@10.0.1: + resolution: {integrity: sha512-MWkrWZbJsL2UwnjxTX3gG8FneachS/Mwg7tdGXce011sJd5b0JG54vat5KHnfSBODZ3Wvzd2WnjxyzsRoVv+ww==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + dependencies: + acorn: 8.11.3 + acorn-jsx: 5.3.2(acorn@8.11.3) + eslint-visitor-keys: 4.0.0 + dev: true + /esprima@4.0.1: resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} engines: {node: '>=4'} @@ -3353,7 +3381,6 @@ packages: /estraverse@5.3.0: resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} engines: {node: '>=4.0'} - dev: true /estree-walker@0.6.1: resolution: {integrity: sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w==} diff --git a/showroom/package.json b/showroom/package.json index e89111aff129271f7e1c500549435ab09d7ac664..c665d0f0ee25042b32e95c1b9493bea5be605199 100644 --- a/showroom/package.json +++ b/showroom/package.json @@ -44,6 +44,7 @@ "postcss-responsive-type": "^1.0.0", "postcss-rtlcss": "^4.0.9", "postcss-strip-units": "^2.0.1", + "prismjs": "^1.29.0", "puppeteer": "^21.11.0", "url": "^0.11.3", "url-exist": "3.0.1", diff --git a/showroom/pnpm-lock.yaml b/showroom/pnpm-lock.yaml index e73fcfee647b0b5d7031d23f2a7669cc4ca21ee6..f040bdea5c2241497121256fecbff2bb723cea07 100644 --- a/showroom/pnpm-lock.yaml +++ b/showroom/pnpm-lock.yaml @@ -91,6 +91,9 @@ devDependencies: postcss-strip-units: specifier: ^2.0.1 version: 2.0.1 + prismjs: + specifier: ^1.29.0 + version: 1.29.0 puppeteer: specifier: ^21.11.0 version: 21.11.0 @@ -4016,6 +4019,11 @@ packages: resolution: {integrity: sha512-mQUvGU6aUFQ+rNvTIAcZuWGRT9a6f6Yrg9bHs4ImKF+HZCEK+plBvnAZYSIQztknZF2qnzNtr6F8s0+IuptdlQ==} engines: {node: ^14.13.1 || >=16.0.0} + /prismjs@1.29.0: + resolution: {integrity: sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q==} + engines: {node: '>=6'} + dev: true + /process-nextick-args@2.0.1: resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} dev: true diff --git a/showroom/source/components.form.button.html b/showroom/source/components.form.button.html new file mode 100644 index 0000000000000000000000000000000000000000..cb6037457dedc4131f09c0db30d75a1a5ec15054 --- /dev/null +++ b/showroom/source/components.form.button.html @@ -0,0 +1,336 @@ +<!DOCTYPE html> +<html lang="en"> +<head> + <meta charset="UTF-8"> + <meta name="color-scheme" content="dark light"> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <title>Button - the monster component library</title> + <script type="module" src="scripts/monster.mjs"></script> + <style> + *:not(:defined) { + visibility: hidden; + } + </style> +</head> +<body> + +<monster-panel> + <monster-split-panel data-monster-option-splittype="vertical" data-monster-option-dimension-initial="0%" + data-monster-option-dimension-min="0%" data-monster-option-dimension-max="100%"> + <monster-panel slot="start"> + + </monster-panel> + <monster-panel slot="end"> + <main class="container"> + + <div class="deco"></div> + + <div class="to-overview"> + <a href="/" class="back-link"> + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" + viewBox="0 0 16 16"> + <path d="M8 6.982C9.664 5.309 13.825 8.236 8 12 2.175 8.236 6.336 5.309 8 6.982"/> + <path d="M8.707 1.5a1 1 0 0 0-1.414 0L.646 8.146a.5.5 0 0 0 .708.707L2 8.207V13.5A1.5 1.5 0 0 0 3.5 15h9a1.5 1.5 0 0 0 1.5-1.5V8.207l.646.646a.5.5 0 0 0 .708-.707L13 5.793V2.5a.5.5 0 0 0-.5-.5h-1a.5.5 0 0 0-.5.5v1.293zM13 7.207V13.5a.5.5 0 0 1-.5.5h-9a.5.5 0 0 1-.5-.5V7.207l5-5z"/> + </svg> + Back to overview + </a> + </div> + + <h1>Button</h1> + + <p>A beautiful button that can make your life easier and also looks good.</p> + <div class="info-grid"> + <div>Import</div> + <div><img alt="the javascript logo" title="how to import" + src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 630 630'%3E%3Crect width='630' height='630' fill='%23f7df1e'/%3E%3Cpath d='m423.2 492.19c12.69 20.72 29.2 35.95 58.4 35.95 24.53 0 40.2-12.26 40.2-29.2 0-20.3-16.1-27.49-43.1-39.3l-14.8-6.35c-42.72-18.2-71.1-41-71.1-89.2 0-44.4 33.83-78.2 86.7-78.2 37.64 0 64.7 13.1 84.2 47.4l-46.1 29.6c-10.15-18.2-21.1-25.37-38.1-25.37-17.34 0-28.33 11-28.33 25.37 0 17.76 11 24.95 36.4 35.95l14.8 6.34c50.3 21.57 78.7 43.56 78.7 93 0 53.3-41.87 82.5-98.1 82.5-54.98 0-90.5-26.2-107.88-60.54zm-209.13 5.13c9.3 16.5 17.76 30.45 38.1 30.45 19.45 0 31.72-7.61 31.72-37.2v-201.3h59.2v202.1c0 61.3-35.94 89.2-88.4 89.2-47.4 0-74.85-24.53-88.81-54.075z'/%3E%3C/svg%3E"> + </div> + <div><code + class="language-javascript">import { Button } from "@schukai/monster/source/components/form/button.mjs";</code> + </div> + <div>Source</div> + <div><img alt="the git logo" title="View source code" + src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='92pt' height='92pt' viewBox='0 0 92 92'%3E%3Cdefs%3E%3CclipPath id='a'%3E%3Cpath d='M0 .113h91.887V92H0Zm0 0'/%3E%3C/clipPath%3E%3C/defs%3E%3Cg clip-path='url(%23a)'%3E%3Cpath style='stroke:none;fill-rule:nonzero;fill:%23f03c2e;fill-opacity:1' d='M90.156 41.965 50.036 1.848a5.918 5.918 0 0 0-8.372 0l-8.328 8.332 10.566 10.566a7.03 7.03 0 0 1 7.23 1.684 7.034 7.034 0 0 1 1.669 7.277l10.187 10.184a7.028 7.028 0 0 1 7.278 1.672 7.04 7.04 0 0 1 0 9.957 7.05 7.05 0 0 1-9.965 0 7.044 7.044 0 0 1-1.528-7.66l-9.5-9.497V59.36a7.04 7.04 0 0 1 1.86 11.29 7.04 7.04 0 0 1-9.957 0 7.04 7.04 0 0 1 0-9.958 7.06 7.06 0 0 1 2.304-1.539V33.926a7.049 7.049 0 0 1-3.82-9.234L29.242 14.272 1.73 41.777a5.925 5.925 0 0 0 0 8.371L41.852 90.27a5.925 5.925 0 0 0 8.37 0l39.934-39.934a5.925 5.925 0 0 0 0-8.371'/%3E%3C/g%3E%3C/svg%3E"> + </div> + <div> + <a target="_blank" + href="https://gitlab.schukai.com/oss/libraries/javascript/monster/-/blob/master/source/components/form/button.mjs">View + source code</a></div> + <div>Package</div> + <div><img alt="the npm logo" title="View on npm" + src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 576 512'%3E%3Cpath d='M288 288h-32v-64h32v64zm288-128v192H288v32H160v-32H0V160h576zm-416 32H32v128h64v-96h32v96h32V192zm160 0H192v160h64v-32h64V192zm224 0H352v128h64v-96h32v96h32v-96h32v96h32V192z'/%3E%3C/svg%3E"> + </div> + <div><a target="_blank" href="https://npmjs.com/package/@schukai/monster">@schukai/monster</a></div> + <div>Since</div> + <div></div> + <div>1.5.0</div> + </div> + <div class="show-it"><monster-button> + click me! +</monster-button></div> + <monster-tabs> + <div data-monster-button-label="Overview" + class="active" + data-monster-button-icon="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16'%3E%3Cpath fill-rule='evenodd' d='m8 2.42-.717-.737c-1.13-1.161-3.243-.777-4.01.72-.35.685-.451 1.707.236 3.062C4.16 6.753 5.52 8.32 8 10.042c2.479-1.723 3.839-3.29 4.491-4.577.687-1.355.587-2.377.236-3.061-.767-1.498-2.88-1.882-4.01-.721zm-.49 8.5c-10.78-7.44-3-13.155.359-10.063q.068.062.132.129.065-.067.132-.129c3.36-3.092 11.137 2.624.357 10.063l.235.468a.25.25 0 1 1-.448.224l-.008-.017c.008.11.02.202.037.29.054.27.161.488.419 1.003.288.578.235 1.15.076 1.629-.157.469-.422.867-.588 1.115l-.004.007a.25.25 0 1 1-.416-.278c.168-.252.4-.6.533-1.003.133-.396.163-.824-.049-1.246l-.013-.028c-.24-.48-.38-.758-.448-1.102a3 3 0 0 1-.052-.45l-.04.08a.25.25 0 1 1-.447-.224l.235-.468ZM6.013 2.06c-.649-.18-1.483.083-1.85.798-.131.258-.245.689-.08 1.335.063.244.414.198.487-.043.21-.697.627-1.447 1.359-1.692.217-.073.304-.337.084-.398'/%3E%3C/svg%3E"> + <h2>Introduction</h2> +<p> + This is the Monster Button web component. More than just a button, this component is the gateway + to an interactive and engaging user experience that integrates seamlessly into various web applications. + Whether you are developing a simple website or a complex enterprise application, the Monster + Button is designed to increase user interaction and satisfaction. !!!! +</p> + +<h2>Key Features</h2> +<ul> + <li><strong>Dynamic interaction</strong>: Users can interact with content dynamically, + making the Web experience more intuitive and user-centric. + </li> + <li><strong>Customizable appearance</strong>: Customize the appearance of the button + to match the design of your brand or application to improve visual consistency. + </li> + <li><strong>Accessibility</strong>: Designed with accessibility in mind to ensure all + users have a seamless experience regardless of their browsing context. + </li> + <li><strong>Ripple effect</strong>: A subtle touch feedback effect that enhances + tactile interaction with the button and makes users' actions more enjoyable. + </li> + <li><strong>Programmatic Control</strong>: Provides methods such as click, focus, + and blur to programmatically control the behavior of the button, giving developers flexibility. + </li> +</ul> + +<h2>Improving the user experience</h2> +<p> + The Monster Button goes beyond the traditional functions of a button to provide an enhanced + and interactive user experience. Its ripple effect combined with dynamic interaction with + content is not only visually appealing, but also provides clear and responsive feedback to + user actions, making the web environment more intuitive and user-friendly. +</p> + +<p> + These improvements are supported by user studies that show a positive impact on user + commitment and satisfaction. +</p> + +<h2>Efficiency in the development process</h2> +<p> + Integrating the Monster Button into your development process is easy. Its compatibility with + standard web technologies and ease of customization allow for seamless integration with + your existing tools and libraries. Whether you are working on a small project or a large + application, Monster Button's modular design guarantees easy integration that streamlines + your development process and increases your productivity. +</p> + </div> + <div data-monster-button-label="Usage" + data-monster-button-icon="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16'%3E%3Cpath d='M9.752 6.193c.599.6 1.73.437 2.528-.362s.96-1.932.362-2.531c-.599-.6-1.73-.438-2.528.361-.798.8-.96 1.933-.362 2.532'/%3E%3Cpath d='M15.811 3.312c-.363 1.534-1.334 3.626-3.64 6.218l-.24 2.408a2.56 2.56 0 0 1-.732 1.526L8.817 15.85a.51.51 0 0 1-.867-.434l.27-1.899c.04-.28-.013-.593-.131-.956a9 9 0 0 0-.249-.657l-.082-.202c-.815-.197-1.578-.662-2.191-1.277-.614-.615-1.079-1.379-1.275-2.195l-.203-.083a10 10 0 0 0-.655-.248c-.363-.119-.675-.172-.955-.132l-1.896.27A.51.51 0 0 1 .15 7.17l2.382-2.386c.41-.41.947-.67 1.524-.734h.006l2.4-.238C9.005 1.55 11.087.582 12.623.208c.89-.217 1.59-.232 2.08-.188.244.023.435.06.57.093q.1.026.16.045c.184.06.279.13.351.295l.029.073a3.5 3.5 0 0 1 .157.721c.055.485.051 1.178-.159 2.065m-4.828 7.475.04-.04-.107 1.081a1.54 1.54 0 0 1-.44.913l-1.298 1.3.054-.38c.072-.506-.034-.993-.172-1.418a9 9 0 0 0-.164-.45c.738-.065 1.462-.38 2.087-1.006M5.205 5c-.625.626-.94 1.351-1.004 2.09a9 9 0 0 0-.45-.164c-.424-.138-.91-.244-1.416-.172l-.38.054 1.3-1.3c.245-.246.566-.401.91-.44l1.08-.107zm9.406-3.961c-.38-.034-.967-.027-1.746.163-1.558.38-3.917 1.496-6.937 4.521-.62.62-.799 1.34-.687 2.051.107.676.483 1.362 1.048 1.928.564.565 1.25.941 1.924 1.049.71.112 1.429-.067 2.048-.688 3.079-3.083 4.192-5.444 4.556-6.987.183-.771.18-1.345.138-1.713a3 3 0 0 0-.045-.283 3 3 0 0 0-.3-.041Z'/%3E%3Cpath d='M7.009 12.139a7.6 7.6 0 0 1-1.804-1.352A7.6 7.6 0 0 1 3.794 8.86c-1.102.992-1.965 5.054-1.839 5.18.125.126 3.936-.896 5.054-1.902Z'/%3E%3C/svg%3E"> + + <h2>Button Simple</h2> + <monster-tabs> + <div class="active" + data-monster-button-label="Component"> + <p> + This is a simple button example. It is a button with the text "click me!". + Nothing more, nothing less. +</p> + <div class="show-it"> + <monster-button>click me!</monster-button> + </div> + </div> + <div data-monster-button-label="Code"> + <h3>Javascript</h3> + <pre><code class="language-javascript example-code">import "@schukai/monster/source/components/form/button.mjs";</code></pre> + <h3>HTML</h3> + <pre><code class="language-html example-code"><monster-button>click me!</monster-button></code></pre> + <h3>Stylesheet</h3> + <pre><code class="language-css example-code">/** no additional stylesheet is defined **/</code></pre> + </div> + </monster-tabs> + + + <h2>Button With Click Event</h2> + <monster-tabs> + <div class="active" + data-monster-button-label="Component"> + <p> + This is a simple button example with click handling. It is a button with the text "click me!". + When you click on the button, the text "Button clicked!" will be displayed below the button. + The text will be removed after 2 seconds. +</p> + +<p> + You can either attach a classic event handler to the element, or you can use the <code>actions.click</code> option. +</p> + <div class="show-it"> + <monster-button id="eeSh6A-button">click me!</monster-button> + +<div id="eeSh6A-result" style="margin-top: 20px;display: flex;align-items: center;justify-content: center;"> + +</div> + +<script> + + // watch for the DOMContentLoaded event + window.addEventListener('DOMContentLoaded', function () { + + // get the button element and set a click event callback + document.getElementById('eeSh6A-button').setOption("actions.click", function () { + + // set a text to the result element + document.getElementById('eeSh6A-result').innerHTML = 'Button clicked!'; + + // Reset the result after 2 seconds. + // This is only there so that you can click more often. + setTimeout(function () { + document.getElementById('eeSh6A-result').innerHTML = ''; + }, 2000); + }); + + }); + +</script> + </div> + </div> + <div data-monster-button-label="Code"> + <h3>Javascript</h3> + <pre><code class="language-javascript example-code">import "@schukai/monster/source/components/form/button.mjs";</code></pre> + <h3>HTML</h3> + <pre><code class="language-html example-code"><monster-button id="eeSh6A-button">click me!</monster-button> + +<div id="eeSh6A-result" style="margin-top: 20px;display: flex;align-items: center;justify-content: center;"> + +</div> + +<script> + + // watch for the DOMContentLoaded event + window.addEventListener('DOMContentLoaded', function () { + + // get the button element and set a click event callback + document.getElementById('eeSh6A-button').setOption("actions.click", function () { + + // set a text to the result element + document.getElementById('eeSh6A-result').innerHTML = 'Button clicked!'; + + // Reset the result after 2 seconds. + // This is only there so that you can click more often. + setTimeout(function () { + document.getElementById('eeSh6A-result').innerHTML = ''; + }, 2000); + }); + + }); + +</script></code></pre> + <h3>Stylesheet</h3> + <pre><code class="language-css example-code">/** no additional stylesheet is defined **/</code></pre> + </div> + </monster-tabs> + + </div> + + <div data-monster-button-label="Design" + data-monster-button-icon="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16'%3E%3Cpath d='M6.192 2.78c-.458-.677-.927-1.248-1.35-1.643a3 3 0 0 0-.71-.515c-.217-.104-.56-.205-.882-.02-.367.213-.427.63-.43.896-.003.304.064.664.173 1.044.196.687.556 1.528 1.035 2.402L.752 8.22c-.277.277-.269.656-.218.918.055.283.187.593.36.903.348.627.92 1.361 1.626 2.068.707.707 1.441 1.278 2.068 1.626.31.173.62.305.903.36.262.05.64.059.918-.218l5.615-5.615c.118.257.092.512.05.939-.03.292-.068.665-.073 1.176v.123h.003a1 1 0 0 0 1.993 0H14v-.057a1 1 0 0 0-.004-.117c-.055-1.25-.7-2.738-1.86-3.494a4 4 0 0 0-.211-.434c-.349-.626-.92-1.36-1.627-2.067S8.857 3.052 8.23 2.704c-.31-.172-.62-.304-.903-.36-.262-.05-.64-.058-.918.219zM4.16 1.867c.381.356.844.922 1.311 1.632l-.704.705c-.382-.727-.66-1.402-.813-1.938a3.3 3.3 0 0 1-.131-.673q.137.09.337.274m.394 3.965c.54.852 1.107 1.567 1.607 2.033a.5.5 0 1 0 .682-.732c-.453-.422-1.017-1.136-1.564-2.027l1.088-1.088q.081.181.183.365c.349.627.92 1.361 1.627 2.068.706.707 1.44 1.278 2.068 1.626q.183.103.365.183l-4.861 4.862-.068-.01c-.137-.027-.342-.104-.608-.252-.524-.292-1.186-.8-1.846-1.46s-1.168-1.32-1.46-1.846c-.147-.265-.225-.47-.251-.607l-.01-.068zm2.87-1.935a2.4 2.4 0 0 1-.241-.561c.135.033.324.11.562.241.524.292 1.186.8 1.846 1.46.45.45.83.901 1.118 1.31a3.5 3.5 0 0 0-1.066.091 11 11 0 0 1-.76-.694c-.66-.66-1.167-1.322-1.458-1.847z'/%3E%3C/svg%3E"> + DESIGN22333dd + </div> + <div data-monster-button-label="API" + data-monster-button-icon="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16'%3E%3Cpath d='M6 0a.5.5 0 0 1 .5.5V3h3V.5a.5.5 0 0 1 1 0V3h1a.5.5 0 0 1 .5.5v3A3.5 3.5 0 0 1 8.5 10c-.002.434-.01.845-.04 1.22-.041.514-.126 1.003-.317 1.424a2.08 2.08 0 0 1-.97 1.028C6.725 13.9 6.169 14 5.5 14c-.998 0-1.61.33-1.974.718A1.92 1.92 0 0 0 3 16H2c0-.616.232-1.367.797-1.968C3.374 13.42 4.261 13 5.5 13c.581 0 .962-.088 1.218-.219.241-.123.4-.3.514-.55.121-.266.193-.621.23-1.09.027-.34.035-.718.037-1.141A3.5 3.5 0 0 1 4 6.5v-3a.5.5 0 0 1 .5-.5h1V.5A.5.5 0 0 1 6 0M5 4v2.5A2.5 2.5 0 0 0 7.5 9h1A2.5 2.5 0 0 0 11 6.5V4z'/%3E%3C/svg%3E"> + + <h2>HTML Structure</h2> + <pre><code class="language-html"><monster-button></monster-button></code></pre> + <h2>JavaScript Initialization</h2> + <pre><code class="language-js">const element = document.createElement('monster-button'); +document.body.appendChild(element);</code></pre> + <h2>Options</h2> + <div class="option-grid"> + <div class="option-headline">Option</div> + <div class="option-headline">Type</div> + <div class="option-headline">Default</div> + <div class="option-headline">Description</div> + <div>templates</div><div>object</div><div></div><div>templates Template definitions</div> +<div>templates.main</div><div>string</div><div></div><div>templates.main Main template</div> +<div>labels</div><div>object</div><div></div><div>labels Labels</div> +<div>labels.button</div><div>string</div><div><slot></slot></div><div>Button label</div> +<div>actions</div><div>object</div><div></div><div>actions Callbacks</div> +<div>actions.click</div><div>string</div><div>throw Error</div><div>Callback when clicked</div> +<div>classes</div><div>object</div><div></div><div>classes CSS classes</div> +<div>classes.button</div><div>string</div><div>monster-button-primary</div><div>CSS class for the button</div> +<div>disabled</div><div>boolean</div><div>false</div><div>Disabled state</div> +<div>effects</div><div>object</div><div></div><div>effects Effects</div> +<div>effects.ripple</div><div>boolean</div><div>true</div><div>Ripple effect</div> + </div><br> + <h2>Properties and Attributes</h2> + <ul> + <li><code>data-monster-options</code>: Sets the configuration options for the collapse + component when used as an HTML attribute. + </li> + <li><code>data-monster-option-[name]</name></code>: Sets the value of the configuration + option <code>[name]</code> for the collapse component when used as an HTML attribute. + </li> + </ul> + <h2>Methods</h2> + <h3>Other methods</h3><div class="method-grid"><div class='method-signature'><code class='language-javascript'>assembleMethodSymbol()</code></div><div><strong>Returns</strong><ul><li>{Button}</li></ul> </div><div class='method-description'> + </div> +<div class='method-signature'><code class='language-javascript'>blur()</code></div><div> </div><div class='method-description'> +The Button.blur() method removes focus from the internal button element. </div> +<div class='method-signature'><code class='language-javascript'>click()</code><span class='monster-badge-secondary-pill'>3.27.0</span></div><div> </div><div class='method-description'> +The <code>Button.click()</code> method simulates a click on the internal button element. + </div> +<div class='method-signature'><code class='language-javascript'>focus(options)</code><span class='monster-badge-secondary-pill'>3.27.0</span></div><div><strong>Parameters</strong><ul> +<li><code>options</code> {object}: options</li> +</ul> </div><div class='method-description'> +The Button.focus() method sets focus on the internal button element. + </div> +<div class='method-signature'><code class='language-javascript'>formAssociated()</code></div><div><strong>Returns</strong><ul><li>{boolean}</li></ul> </div><div class='method-description'> </div> +<div class='method-signature'><code class='language-javascript'>getCSSStyleSheet()</code></div><div><strong>Returns</strong><ul><li>{CSSStyleSheet[]}</li></ul> </div><div class='method-description'> + </div> +<div class='method-signature'><code class='language-javascript'>getTag()</code></div><div><strong>Returns</strong><ul><li>{string}</li></ul> </div><div class='method-description'> + </div> +<div class='method-signature'><code class='language-javascript'>instanceSymbol()</code><span class='monster-badge-secondary-pill'>2.1.0</span></div><div><strong>Returns</strong><ul><li>{symbol}</li></ul> </div><div class='method-description'> +This method is called by the <code>instanceof</code> operator. </div> +<div class='method-signature'><code class='language-javascript'>observedAttributes()</code></div><div><strong>Returns</strong><ul><li>{string[]}</li></ul> </div><div class='method-description'> +This method determines which attributes are to be monitored by <code>attributeChangedCallback()</code>. + </div> +<div class='method-signature'><code class='language-javascript'>value()</code></div><div><strong>Returns</strong><ul><li>{string}: value of the button</li></ul> </div><div class='method-description'> +The current value of the button. + +<pre><code class="language-javascript"> +e = document.querySelector('monster-button'); +console.log(e.value) + +</code></pre> + </div> +<div class='method-signature'><code class='language-javascript'>value(value)</code></div><div><strong>Parameters</strong><ul> +<li><code>value</code> {string}: value</li> +</ul> +<strong>Returns</strong><ul><li>{void}</li></ul> +<strong>Throws</strong><ul> +<li><code class='language-js'>{Error}</code> unsupported type</li> +</ul> </div><div class='method-description'> +Set the value of the button. + +<pre><code class="language-javascript"> +e = document.querySelector('monster-button'); +e.value=1 + +</code></pre> + </div></div><br> + <h2>Events</h2> + <p>The component emits the following events:</p><ul><li><code>monster-button-clicked</code> this event is triggered when the button is clicked. It contains the field {button} with the button instance.</li></ul><p>For more information on how to handle events, see the <a target='_blank' href="https://developer.mozilla.org/en-US/docs/Web/Events">mdn documentation</a>.</p> + + </div> + + </monster-tabs> + </main> + <footer> + <p>© 2024 Powered by schukai GmbH - All rights reserved.</p> + <p>Code and documentation licensed by <a target="_blank" + href="https://www.gnu.org/licenses/agpl-3.0.en.html">AGPLv3 + License</a> or commercial license.</p> + </footer> + + </monster-panel> + </monster-split-panel> + +</monster-panel> + +</body> +</html> diff --git a/showroom/source/component.layout.collapse.html b/showroom/source/components.layout.collapse.html similarity index 99% rename from showroom/source/component.layout.collapse.html rename to showroom/source/components.layout.collapse.html index 52d38c4bbe6597e16bce917c668f05798d2dc029..9f5cc0c6db420878ff38c2c06fce6a73978fb654 100644 --- a/showroom/source/component.layout.collapse.html +++ b/showroom/source/components.layout.collapse.html @@ -327,8 +327,7 @@ import "@schukai/monster/source/components/layout/collapse.mjs"; <p> The control element can be adapted to your own requirements. To do this, the control element - can - be designed with CSS like almost any other HTML element. + can be designed with CSS like almost any other HTML element. </p> <p> diff --git a/showroom/source/examples/components/form/button-simple/main.html b/showroom/source/examples/components/form/button-simple/main.html new file mode 100644 index 0000000000000000000000000000000000000000..bc13cc4be962ee271b94f4f04b915d4eda86650f --- /dev/null +++ b/showroom/source/examples/components/form/button-simple/main.html @@ -0,0 +1 @@ +<monster-button>click me!</monster-button> \ No newline at end of file diff --git a/showroom/source/examples/components/form/button-simple/readme.html b/showroom/source/examples/components/form/button-simple/readme.html new file mode 100644 index 0000000000000000000000000000000000000000..ddc81f8ee3224858d69cc5c7224fd099526198df --- /dev/null +++ b/showroom/source/examples/components/form/button-simple/readme.html @@ -0,0 +1,4 @@ +<p> + This is a simple button example. It is a button with the text "click me!". + Nothing more, nothing less. +</p> \ No newline at end of file diff --git a/showroom/source/examples/components/form/button-simple/script.mjs b/showroom/source/examples/components/form/button-simple/script.mjs new file mode 100644 index 0000000000000000000000000000000000000000..dcc112c93a487ed589e0f19d0bca0b0a51710723 --- /dev/null +++ b/showroom/source/examples/components/form/button-simple/script.mjs @@ -0,0 +1 @@ +import "@schukai/monster/source/components/form/button.mjs"; \ No newline at end of file diff --git a/showroom/source/examples/components/form/button-with-click-event/main.html b/showroom/source/examples/components/form/button-with-click-event/main.html new file mode 100644 index 0000000000000000000000000000000000000000..4f0e72391894b094091b6e52b2ea3cf2eea247b0 --- /dev/null +++ b/showroom/source/examples/components/form/button-with-click-event/main.html @@ -0,0 +1,27 @@ +<monster-button id="eeSh6A-button">click me!</monster-button> + +<div id="eeSh6A-result" style="margin-top: 20px;display: flex;align-items: center;justify-content: center;"> + +</div> + +<script> + + // watch for the DOMContentLoaded event + window.addEventListener('DOMContentLoaded', function () { + + // get the button element and set a click event callback + document.getElementById('eeSh6A-button').setOption("actions.click", function () { + + // set a text to the result element + document.getElementById('eeSh6A-result').innerHTML = 'Button clicked!'; + + // Reset the result after 2 seconds. + // This is only there so that you can click more often. + setTimeout(function () { + document.getElementById('eeSh6A-result').innerHTML = ''; + }, 2000); + }); + + }); + +</script> \ No newline at end of file diff --git a/showroom/source/examples/components/form/button-with-click-event/readme.html b/showroom/source/examples/components/form/button-with-click-event/readme.html new file mode 100644 index 0000000000000000000000000000000000000000..0a0e41c311976c473f10b135f4b504f249b81e97 --- /dev/null +++ b/showroom/source/examples/components/form/button-with-click-event/readme.html @@ -0,0 +1,9 @@ +<p> + This is a simple button example with click handling. It is a button with the text "click me!". + When you click on the button, the text "Button clicked!" will be displayed below the button. + The text will be removed after 2 seconds. +</p> + +<p> + You can either attach a classic event handler to the element, or you can use the <code>actions.click</code> option. +</p> \ No newline at end of file diff --git a/showroom/source/examples/components/form/button-with-click-event/script.mjs b/showroom/source/examples/components/form/button-with-click-event/script.mjs new file mode 100644 index 0000000000000000000000000000000000000000..dcc112c93a487ed589e0f19d0bca0b0a51710723 --- /dev/null +++ b/showroom/source/examples/components/form/button-with-click-event/script.mjs @@ -0,0 +1 @@ +import "@schukai/monster/source/components/form/button.mjs"; \ No newline at end of file diff --git a/showroom/source/fragments/components/form/button/design.html b/showroom/source/fragments/components/form/button/design.html new file mode 100644 index 0000000000000000000000000000000000000000..66d7c5ffa048bac868db49b6c17a6c241b82dcc8 --- /dev/null +++ b/showroom/source/fragments/components/form/button/design.html @@ -0,0 +1 @@ +DESIGN22333dd \ No newline at end of file diff --git a/showroom/source/fragments/components/form/button/overview.html b/showroom/source/fragments/components/form/button/overview.html new file mode 100644 index 0000000000000000000000000000000000000000..cede8710e02cdcc8549958db2e498230bad47124 --- /dev/null +++ b/showroom/source/fragments/components/form/button/overview.html @@ -0,0 +1,48 @@ +<h2>Introduction</h2> +<p> + This is the Monster Button web component. More than just a button, this component is the gateway + to an interactive and engaging user experience that integrates seamlessly into various web applications. + Whether you are developing a simple website or a complex enterprise application, the Monster + Button is designed to increase user interaction and satisfaction. +</p> + +<h2>Key Features</h2> +<ul> + <li><strong>Dynamic interaction</strong>: Users can interact with content dynamically, + making the Web experience more intuitive and user-centric. + </li> + <li><strong>Customizable appearance</strong>: Customize the appearance of the button + to match the design of your brand or application to improve visual consistency. + </li> + <li><strong>Accessibility</strong>: Designed with accessibility in mind to ensure all + users have a seamless experience regardless of their browsing context. + </li> + <li><strong>Ripple effect</strong>: A subtle touch feedback effect that enhances + tactile interaction with the button and makes users' actions more enjoyable. + </li> + <li><strong>Programmatic Control</strong>: Provides methods such as click, focus, + and blur to programmatically control the behavior of the button, giving developers flexibility. + </li> +</ul> + +<h2>Improving the user experience</h2> +<p> + The Monster Button goes beyond the traditional functions of a button to provide an enhanced + and interactive user experience. Its ripple effect combined with dynamic interaction with + content is not only visually appealing, but also provides clear and responsive feedback to + user actions, making the web environment more intuitive and user-friendly. +</p> + +<p> + These improvements are supported by user studies that show a positive impact on user + commitment and satisfaction. +</p> + +<h2>Efficiency in the development process</h2> +<p> + Integrating the Monster Button into your development process is easy. Its compatibility with + standard web technologies and ease of customization allow for seamless integration with + your existing tools and libraries. Whether you are working on a small project or a large + application, Monster Button's modular design guarantees easy integration that streamlines + your development process and increases your productivity. +</p> \ No newline at end of file diff --git a/showroom/source/fragments/components/form/button/show-it.html b/showroom/source/fragments/components/form/button/show-it.html new file mode 100644 index 0000000000000000000000000000000000000000..6f3e714c930561a74fdbcad385a6004455201c3d --- /dev/null +++ b/showroom/source/fragments/components/form/button/show-it.html @@ -0,0 +1,3 @@ +<monster-button> + click me! +</monster-button> \ No newline at end of file diff --git a/showroom/source/scripts/monster.mjs b/showroom/source/scripts/monster.mjs index ff2eec768160dc52415d1106b56d68ab16f6c3ec..7e3290fcf1e1dfed374604a90bbb0c04bfefe38a 100644 --- a/showroom/source/scripts/monster.mjs +++ b/showroom/source/scripts/monster.mjs @@ -7,21 +7,42 @@ import "../../../source/components/style/typography.pcss"; import "../../../source/components/style/color.pcss"; import "../../../source/components/style/theme.pcss"; import "../../../source/components/style/link.pcss"; +import "../../../source/components/style/badge.pcss"; import "../../../source/monster.mjs"; import "../styles/monster.pcss"; - -import hljs from 'highlight.js/lib/core'; -import javascript from 'highlight.js/lib/languages/javascript'; -import html from 'highlight.js/lib/languages/xml'; -import css from 'highlight.js/lib/languages/css'; - -// import css -import 'highlight.js/styles/default.css'; - - -hljs.registerLanguage('javascript', javascript); -hljs.registerLanguage('html', html); -hljs.registerLanguage('css', css); - - -hljs.highlightAll(); \ No newline at end of file +import {domReady} from "../../../source/dom/ready.mjs"; + +// import hljs from 'highlight.js/lib/core'; +// import javascript from 'highlight.js/lib/languages/javascript'; +// import html from 'highlight.js/lib/languages/xml'; +// import css from 'highlight.js/lib/languages/css'; +// +// // import css +// import 'highlight.js/styles/default.css'; + +import 'prismjs'; +//import 'prismjs/themes/prism.css'; +// import {loadLanguages} from 'prismjs'; +// +// loadLanguages(['js', 'css', 'html',"pcss"]); + +// +domReady.then(()=> { + let codeBlocks = document.querySelectorAll('pre code'); + + console.log(codeBlocks); + + codeBlocks.forEach((block) => { + Prism.highlightElement(block); + }); +} ); + + + + +// hljs.registerLanguage('javascript', javascript); +// hljs.registerLanguage('html', html); +// hljs.registerLanguage('css', css); +// +// +// hljs.highlightAll(); \ No newline at end of file diff --git a/showroom/source/styles/monster.pcss b/showroom/source/styles/monster.pcss index 88c000a8dc83825a0141829842cd917068c3a856..c3da7dfd9b5e1e87edc2ee5850c4f2b262b9d999 100644 --- a/showroom/source/styles/monster.pcss +++ b/showroom/source/styles/monster.pcss @@ -1,19 +1,139 @@ +body { + min-height: 100vh; + height: 100%; +} + +:root { + color-scheme: light dark; /* both supported */ +} + monster-panel[slot="end"] { box-sizing: border-box; display: flex; align-items: flex-start; /*padding: 0.6em 0.6em 4rem ;*/ - padding:0 ; + padding: 0; + & .to-overview a { + display: flex; + align-items: center; + + & svg { + margin-right: 0.5em; + } + + } + + & code { font-size: 0.9em; } + & .example-code { + border: 1px solid var(--monster-bg-color-primary-3); + padding: 1em; + display: block; + border-radius: 4px; + background-color: var(--monster-bg-color-primary-1); + } + + + & .option-grid > :nth-child(8n+1), + & .option-grid > :nth-child(8n+2), + & .option-grid > :nth-child(8n+3), + & .option-grid > :nth-child(8n+4) { + background-color: var(--monster-bg-color-primary-2); + } + + & .option-grid > div { + display: flex; + width: 100%; + align-self: stretch; + flex-direction: column; + align-items: start; + } + + & .option-grid { + display: grid; + grid-template-columns: 2fr 1fr 2fr 5fr; + grid-gap: 0; + margin: .5em 0; + align-items: center; + justify-items: start; + + & .option-headline { + font-weight: bold; + font-size: 1.1em; + + } + + div { + padding: 0.5em; + } + + } + + & .method-grid > :nth-child(6n+1), + & .method-grid > :nth-child(6n+2), + & .method-grid > :nth-child(6n+3) { + background-color: var(--monster-bg-color-primary-2); + } + + & .method-grid > div { + display: flex; + flex-direction: column; + width: 100%; + align-items: start; + align-self: stretch; + + &.method-signature { + flex-direction: row; + + & .monster-badge-secondary-pill { + font-size: 0.6em; + } + + } + + + &.method-description { + display: flow; + } + + + } + + & .method-grid { + display: grid; + grid-template-columns: 2fr 2fr 5fr; + grid-gap: 0; + margin: .5em 0; + align-items: center; + justify-items: start; + + & .method-headline { + font-weight: bold; + font-size: 1.1em; + + } + + & div { + padding: 0.5em; + } + + & ul + strong { + padding-top: 1em; + } + + + } + + & .show-it { - + background-color: var(--monster-bg-color-primary-1); color: var(--monster-color-primary-1); padding: 5em; @@ -38,30 +158,38 @@ monster-panel[slot="end"] { font-weight: normal } - + } } - + + & ul { + margin: 0; + padding: calc(0.5em + 1px) 0 0 1.2em; + list-style-type: square; + } + & .container { padding: 0.6em; + min-height: 80vh; } - + & footer { margin: 7em 0 0; padding: 1em 1em 2em; background-color: var(--monster-bg-color-primary-4); - + & p { margin: 0; padding: 0; font-size: 0.8em; color: var(--monster-color-primary-4); + & a { color: var(--monster-color-primary-4); } } } - + & monster-tabs::part(icon) { width: 1.1em; @@ -69,7 +197,7 @@ monster-panel[slot="end"] { margin-top: 4px; } - & .infoGrid { + & .info-grid { margin: 0.5em 0 2em 0; padding: 0.5em; @@ -113,4 +241,183 @@ monster-panel[slot="end"] { } } -} \ No newline at end of file + + +} + + +/** prism */ + + +code[class*="language-"], +pre[class*="language-"] { + + font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace; + text-align: left; + white-space: pre; + word-spacing: normal; + word-break: normal; + word-wrap: normal; + line-height: 1.5; + tab-size: 4; + hyphens: none; +} + +/* Code blocks */ +pre[class*="language-"] { + padding: 1em; + margin: .5em 0; + overflow: auto; + border-radius: 0.3em; +} + +/*:not(pre) > code[class*="language-"],*/ +pre[class*="language-"] { + + color: var(--monster-color-tertiary-1); +} + +/* Inline code */ +:not(pre) > code[class*="language-"] { + padding: .1em; + border-radius: .3em; + white-space: normal; +} + + +.namespace { + opacity: .7; +} + +.token.comment, +.token.prolog, +.token.doctype, +.token.cdata { + color: var(--monster-color-gray-4) +} + +.token.punctuation { + color: var(--monster-color-rose-4); +} + +.token.property, +.token.tag, +.token.constant, +.token.symbol, +.token.deleted { + color: var(--monster-color-pink-5); +} + +.token.boolean, +.token.number { + color: var(--monster-color-orange-5); +} + +.token.selector, +.token.attr-name, +.token.string, +.token.char, +.token.builtin, +.token.inserted { + color: var(--monster-color-green-5); +} + +.token.operator, +.token.entity, +.token.url, +.language-css .token.string, +.style .token.string, +.token.variable { + color: var(--monster-color-blue-5); +} + +.token.atrule, +.token.attr-value, +.token.function, +.token.class-name { + color: var(--monster-color-yellow-5); +} + +.token.keyword { + color: var(--monster-color-red-5); +} + +.token.regex, +.token.important { + color: var(--monster-color-red-5); +} + +@media (prefers-color-scheme: dark) { + + .token.comment, + .token.prolog, + .token.doctype, + .token.cdata { + color: var(--monster-color-gray-4) + } + + .token.punctuation { + color: var(--monster-color-rose-2); + } + + .token.property, + .token.tag, + .token.constant, + .token.symbol, + .token.deleted { + color: var(--monster-color-pink-3); + } + + .token.boolean, + .token.number { + color: var(--monster-color-orange-3); + } + + .token.selector, + .token.attr-name, + .token.string, + .token.char, + .token.builtin, + .token.inserted { + color: var(--monster-color-green-3); + } + + .token.operator, + .token.entity, + .token.url, + .language-css .token.string, + .style .token.string, + .token.variable { + color: var(--monster-color-blue-3); + } + + .token.atrule, + .token.attr-value, + .token.function, + .token.class-name { + color: var(--monster-color-yellow-2); + } + + .token.keyword { + color: var(--monster-color-red-2); + } + + .token.regex, + .token.important { + color: var(--monster-color-red-1); + } +} + + +.token.important, +.token.bold { + font-weight: bold; +} + +.token.italic { + font-style: italic; +} + +.token.entity { + cursor: help; +} \ No newline at end of file diff --git a/source/components/datatable/datatable/header.mjs b/source/components/datatable/datatable/header.mjs index 25e384f049e76cd46a30ca6b9f85345bafac6994..ad4c59a28326d8c41bef62d34e5cc05c71b2adc7 100644 --- a/source/components/datatable/datatable/header.mjs +++ b/source/components/datatable/datatable/header.mjs @@ -1,24 +1,31 @@ /** - * Copyright 2023 schukai GmbH - * SPDX-License-Identifier: AGPL-3.0 + * Copyright © schukai GmbH and all contributing authors, {{copyRightYear}}. All rights reserved. + * Node module: @schukai/monster + * + * This source code is licensed under the GNU Affero General Public License version 3 (AGPLv3). + * The full text of the license can be found at: https://www.gnu.org/licenses/agpl-3.0.en.html + * + * For those who do not wish to adhere to the AGPLv3, a commercial license is available. + * Acquiring a commercial license allows you to use this software without complying with the AGPLv3 terms. + * For more information about purchasing a commercial license, please contact schukai GmbH. */ -import { Base } from "../../../types/base.mjs"; -import { equipWithInternal } from "../../../types/internal.mjs"; -import { instanceSymbol } from "../../../constants.mjs"; -import { Observer } from "../../../types/observer.mjs"; -import { ATTRIBUTE_DATATABLE_SORTABLE } from "../../constants.mjs"; +import {Base} from "../../../types/base.mjs"; +import {equipWithInternal} from "../../../types/internal.mjs"; +import {instanceSymbol} from "../../../constants.mjs"; +import {Observer} from "../../../types/observer.mjs"; +import {ATTRIBUTE_DATATABLE_SORTABLE} from "../../constants.mjs"; import { - validateIterable, - validateInstance, + validateIterable, + validateInstance, } from "../../../types/validate.mjs"; export { - Header, - DIRECTION_ASC, - DIRECTION_DESC, - DIRECTION_NONE, - createOrderStatement, + Header, + DIRECTION_ASC, + DIRECTION_DESC, + DIRECTION_NONE, + createOrderStatement, }; /** @@ -36,7 +43,7 @@ const DIRECTION_ASC = "asc"; const DIRECTION_DESC = "desc"; /** - * no direction and no order statement + * no direction and no order statement * * @private * @type {string} @@ -51,178 +58,178 @@ const DIRECTION_NONE = ""; * @summary A datatable */ class Header extends Base { - constructor() { - super(); - - /** - * - attachInternalObserver - * - detachInternalObserver - * - containsInternalObserver - * - setInternal - * - setInternals - * - getInternal - */ - equipWithInternal.call(this); - - this.attachInternalObserver( - new Observer(() => { - updateStruct.call(this); - }), - ); - } - - /** - * This method is called by the `instanceof` operator. - * @returns {symbol} - */ - static get [instanceSymbol]() { - return Symbol.for("@schukai/monster/components/datatable/header"); - } - - /** - * - * @returns {object} - */ - get internalDefaults() { - return { - field: undefined, - html: undefined, - order: undefined, - direction: undefined, - label: undefined, - labelKey: undefined, - classes: undefined, - index: undefined, - mode: undefined, - grid: undefined, - }; - } - - changeDirection() { - let direction = this.getInternal("direction"); - if (direction === DIRECTION_ASC) { - direction = DIRECTION_DESC; - } else if (direction === DIRECTION_DESC) { - direction = DIRECTION_NONE; - } else { - direction = DIRECTION_ASC; - } - this.setInternal("direction", direction); - } - - /** - * - * @param direction - */ - setDirection(direction) { - this.setInternal("direction", direction); - } - - /** - * @param {string} field - */ - set field(field) { - this.setInternal("field", String(field)); - } - - /** - * @param {string} direction - */ - set direction(direction) { - this.setInternal("direction", String(direction)); - } - - set labelKey(labelKey) { - this.setInternal("labelKey", String(labelKey)); - } - - /** - * @param {string} label - */ - set label(label) { - this.setInternal("label", String(label)); - } - - /** - * @param {string} classes - */ - set classes(classes) { - this.setInternal("classes", String(classes)); - } - - /** - * @param {number} index - */ - set index(index) { - this.setInternal("index", String(index)); - } - - /** - * @param {string} grid - */ - get grid() { - return this.getInternal("grid"); - } - - /** - * @returns {string} - */ - get labelKey() { - return this.getInternal("labelKey"); - } - - /** - * @returns {string} - */ - get html() { - return this.getInternal("html"); - } - - get order() { - return this.getInternal("order"); - } - - /** - * @returns {string} - */ - get field() { - return this.getInternal("field"); - } - - /** - * @returns {string} - */ - get index() { - return this.getInternal("index"); - } - - /** - * @returns {string} - */ - get classes() { - return this.getInternal("classes"); - } - - /** - * @returns {string} - */ - get label() { - return this.getInternal("label"); - } - - /** - * @returns {string} - */ - get direction() { - return this.getInternal("direction"); - } - - /** - * @returns {string} - */ - get mode() { - return this.getInternal("mode"); - } + constructor() { + super(); + + /** + * - attachInternalObserver + * - detachInternalObserver + * - containsInternalObserver + * - setInternal + * - setInternals + * - getInternal + */ + equipWithInternal.call(this); + + this.attachInternalObserver( + new Observer(() => { + updateStruct.call(this); + }), + ); + } + + /** + * This method is called by the `instanceof` operator. + * @returns {symbol} + */ + static get [instanceSymbol]() { + return Symbol.for("@schukai/monster/components/datatable/header@@instance"); + } + + /** + * + * @returns {object} + */ + get internalDefaults() { + return { + field: undefined, + html: undefined, + order: undefined, + direction: undefined, + label: undefined, + labelKey: undefined, + classes: undefined, + index: undefined, + mode: undefined, + grid: undefined, + }; + } + + changeDirection() { + let direction = this.getInternal("direction"); + if (direction === DIRECTION_ASC) { + direction = DIRECTION_DESC; + } else if (direction === DIRECTION_DESC) { + direction = DIRECTION_NONE; + } else { + direction = DIRECTION_ASC; + } + this.setInternal("direction", direction); + } + + /** + * + * @param direction + */ + setDirection(direction) { + this.setInternal("direction", direction); + } + + /** + * @param {string} field + */ + set field(field) { + this.setInternal("field", String(field)); + } + + /** + * @param {string} direction + */ + set direction(direction) { + this.setInternal("direction", String(direction)); + } + + set labelKey(labelKey) { + this.setInternal("labelKey", String(labelKey)); + } + + /** + * @param {string} label + */ + set label(label) { + this.setInternal("label", String(label)); + } + + /** + * @param {string} classes + */ + set classes(classes) { + this.setInternal("classes", String(classes)); + } + + /** + * @param {number} index + */ + set index(index) { + this.setInternal("index", String(index)); + } + + /** + * @param {string} grid + */ + get grid() { + return this.getInternal("grid"); + } + + /** + * @returns {string} + */ + get labelKey() { + return this.getInternal("labelKey"); + } + + /** + * @returns {string} + */ + get html() { + return this.getInternal("html"); + } + + get order() { + return this.getInternal("order"); + } + + /** + * @returns {string} + */ + get field() { + return this.getInternal("field"); + } + + /** + * @returns {string} + */ + get index() { + return this.getInternal("index"); + } + + /** + * @returns {string} + */ + get classes() { + return this.getInternal("classes"); + } + + /** + * @returns {string} + */ + get label() { + return this.getInternal("label"); + } + + /** + * @returns {string} + */ + get direction() { + return this.getInternal("direction"); + } + + /** + * @returns {string} + */ + get mode() { + return this.getInternal("mode"); + } } /** @@ -231,44 +238,44 @@ class Header extends Base { * @returns {string} */ function createOrderStatement(headers) { - validateIterable(headers); - - const oderStatement = []; - for (const header of headers) { - validateInstance(header, Header); - const order = header.getInternal("order"); - - if (!order) { - continue; - } - oderStatement.push(order); - } - return oderStatement.join(","); + validateIterable(headers); + + const oderStatement = []; + for (const header of headers) { + validateInstance(header, Header); + const order = header.getInternal("order"); + + if (!order) { + continue; + } + oderStatement.push(order); + } + return oderStatement.join(","); } /** * @private */ function updateStruct() { - const label = this.getInternal("label"); - const direction = this.getInternal("direction"); - const field = this.getInternal("field"); - - let order = ""; - - if (!field) { - this.setInternal("order", order); - this.setInternal("html", label); - return; - } - - if (direction) { - order = `${field} ${direction}`.trim(); - } - - this.setInternal("order", order); - this.setInternal( - "html", - `<a href="#" ${ATTRIBUTE_DATATABLE_SORTABLE}="${order}">${label}</a>`, - ); + const label = this.getInternal("label"); + const direction = this.getInternal("direction"); + const field = this.getInternal("field"); + + let order = ""; + + if (!field) { + this.setInternal("order", order); + this.setInternal("html", label); + return; + } + + if (direction) { + order = `${field} ${direction}`.trim(); + } + + this.setInternal("order", order); + this.setInternal( + "html", + `<a href="#" ${ATTRIBUTE_DATATABLE_SORTABLE}="${order}">${label}</a>`, + ); } diff --git a/source/components/datatable/style/dataset.pcss b/source/components/datatable/style/dataset.pcss index 0afb16d472c5b5f128c802d1267d6b9e13cfcedc..439f6dd184d901b96f4782c8794b4576cb6b3852 100644 --- a/source/components/datatable/style/dataset.pcss +++ b/source/components/datatable/style/dataset.pcss @@ -10,5 +10,6 @@ @import "../../style/badge.pcss"; @import "../../style/icons.pcss"; @import "../../style/link.pcss"; +@import "../../style/skeleton.pcss"; diff --git a/source/components/datatable/style/datatable.pcss b/source/components/datatable/style/datatable.pcss index 8b41127f219319ef4cca382ce44b572ad50a94ff..b1f99c390e0c395bbbdaf2a58fb11f5d9bdb39c1 100644 --- a/source/components/datatable/style/datatable.pcss +++ b/source/components/datatable/style/datatable.pcss @@ -15,6 +15,7 @@ @import "../../style/mixin/hover.pcss"; @import "../../style/mixin/media.pcss"; @import "../../style/theme.pcss"; +@import "../../style/skeleton.pcss"; [data-monster-role="control"] { display: flex; diff --git a/source/components/form/button.mjs b/source/components/form/button.mjs index eed460b8e3516b48eb2fbb47f66fe96103955f96..92604dd991d3aefa10f1f6cfe692e2af2b9757ef 100644 --- a/source/components/form/button.mjs +++ b/source/components/form/button.mjs @@ -1,29 +1,35 @@ /** - * Copyright schukai GmbH and contributors 2023. All Rights Reserved. + * Copyright © schukai GmbH and all contributing authors, {{copyRightYear}}. All rights reserved. * Node module: @schukai/monster - * This file is licensed under the AGPLv3 License. - * License text available at https://www.gnu.org/licenses/agpl-3.0.en.html + * + * This source code is licensed under the GNU Affero General Public License version 3 (AGPLv3). + * The full text of the license can be found at: https://www.gnu.org/licenses/agpl-3.0.en.html + * + * For those who do not wish to adhere to the AGPLv3, a commercial license is available. + * Acquiring a commercial license allows you to use this software without complying with the AGPLv3 terms. + * For more information about purchasing a commercial license, please contact schukai GmbH. */ -import { instanceSymbol } from "../../constants.mjs"; -import { addAttributeToken } from "../../dom/attributes.mjs"; + +import {instanceSymbol} from "../../constants.mjs"; +import {addAttributeToken} from "../../dom/attributes.mjs"; import { - ATTRIBUTE_ERRORMESSAGE, - ATTRIBUTE_ROLE, + ATTRIBUTE_ERRORMESSAGE, + ATTRIBUTE_ROLE, } from "../../dom/constants.mjs"; -import { CustomControl } from "../../dom/customcontrol.mjs"; +import {CustomControl} from "../../dom/customcontrol.mjs"; import { - assembleMethodSymbol, - attributeObserverSymbol, - registerCustomElement, + assembleMethodSymbol, + attributeObserverSymbol, + registerCustomElement, } from "../../dom/customelement.mjs"; -import { findTargetElementFromEvent } from "../../dom/events.mjs"; -import { isFunction } from "../../types/is.mjs"; -import { ATTRIBUTE_BUTTON_CLASS } from "./constants.mjs"; -import { ButtonStyleSheet } from "./stylesheet/button.mjs"; -import { RippleStyleSheet } from "../stylesheet/ripple.mjs"; -import { fireCustomEvent } from "../../dom/events.mjs"; +import {findTargetElementFromEvent} from "../../dom/events.mjs"; +import {isFunction} from "../../types/is.mjs"; +import {ATTRIBUTE_BUTTON_CLASS} from "./constants.mjs"; +import {ButtonStyleSheet} from "./stylesheet/button.mjs"; +import {RippleStyleSheet} from "../stylesheet/ripple.mjs"; +import {fireCustomEvent} from "../../dom/events.mjs"; -export { Button }; +export {Button}; /** * @private @@ -32,279 +38,257 @@ export { Button }; export const buttonElementSymbol = Symbol("buttonElement"); /** - * This CustomControl creates a button element with a variety of options. - * - * <img src="./images/button.png"> - * - * You can create this control either by specifying the HTML tag <monster-button />` directly in the HTML or using - * Javascript via the `document.createElement('monster-button');` method. - * - * ```html - * <monster-button></monster-button> - * ``` - * - * Or you can create this CustomControl directly in Javascript: - * - * ```js - * import {Button} from '@schukai/monster/components/form/button.mjs'; - * document.createElement('monster-button'); - * ``` - * - * The `data-monster-button-class` attribute can be used to change the CSS class of the button. - * - * @externalExample ../../../example/components/form/button.mjs - * @startuml button.png - * skinparam monochrome true - * skinparam shadowing false - * HTMLElement <|-- CustomElement - * CustomElement <|-- CustomControl - * CustomControl <|-- Button - * @enduml - * + * A button + * + * @fragments /fragments/components/form/button/ + * + * @example /examples/components/form/button-simple + * @example /examples/components/form/button-with-click-event + * * @since 1.5.0 * @copyright schukai GmbH - * @memberOf Monster.Components.Form - * @summary A simple button + * @summary A beautiful button that can make your life easier and also looks good. + * @fires monster-button-clicked this event is triggered when the button is clicked. It contains the field {button} with the button instance. + * */ class Button extends CustomControl { - /** - * This method is called by the `instanceof` operator. - * @returns {symbol} - * @since 2.1.0 - */ - static get [instanceSymbol]() { - return Symbol.for("@schukai/monster/components/form/button@@instance"); - } - - /** - * - * @return {Monster.Components.Form.Button} - */ - [assembleMethodSymbol]() { - super[assembleMethodSymbol](); - initControlReferences.call(this); - initEventHandler.call(this); - return this; - } - - /** - * The Button.click() method simulates a click on the internal button element. - * - * @since 3.27.0 - * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/click} - */ - click() { - if (this.getOption("disabled") === true) { - return; - } - - if ( - this[buttonElementSymbol] && - isFunction(this[buttonElementSymbol].click) - ) { - this[buttonElementSymbol].click(); - } - } - - /** - * The Button.focus() method sets focus on the internal button element. - * - * @since 3.27.0 - * @param {Object} options - * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/focus} - */ - focus(options) { - if (this.getOption("disabled") === true) { - return; - } - - if ( - this[buttonElementSymbol] && - isFunction(this[buttonElementSymbol].focus) - ) { - this[buttonElementSymbol].focus(options); - } - } - - /** - * The Button.blur() method removes focus from the internal button element. - */ - blur() { - if ( - this[buttonElementSymbol] && - isFunction(this[buttonElementSymbol].blur) - ) { - this[buttonElementSymbol].blur(); - } - } - - /** - * This method determines which attributes are to be monitored by `attributeChangedCallback()`. - * - * @return {string[]} - */ - static get observedAttributes() { - const attributes = super.observedAttributes; - attributes.push(ATTRIBUTE_BUTTON_CLASS); - return attributes; - } - - /** - * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/attachInternals} - * @return {boolean} - */ - static get formAssociated() { - return true; - } - - /** - * The current value of the button. - * - * ``` - * e = document.querySelector('monster-button'); - * console.log(e.value) - * ``` - * - * @property {string} - */ - get value() { - return this.getOption("value"); - } - - /** - * Set the value of the button. - * - * ``` - * e = document.querySelector('monster-button'); - * e.value=1 - * ``` - * - * @property {string} value - * @since 1.2.0 - * @throws {Error} unsupported type - */ - set value(value) { - this.setOption("value", value); - try { - this?.setFormValue(this.value); - } catch (e) { - addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, e.message); - } - } - - /** - * To set the options via the html tag the attribute `data-monster-options` must be used. - * @see {@link https://monsterjs.org/en/doc/#configurate-a-monster-control} - * - * The individual configuration values can be found in the table. - * - * @property {Object} templates Template definitions - * @property {string} templates.main Main template - * @property {Object} labels Labels - * @property {string} labels.button=<slot></slot> Button label - * @property {Object} actions Callbacks - * @property {string} actions.click="throw Error" Callback when clicked - * @property {Object} classes CSS classes - * @property {string} classes.button="monster-button-primary" CSS class for the button - * @property {boolean} disabled=false Disabled state - * @property {Object} effects Effects - * @property {boolean} effects.ripple=true Ripple effect - */ - get defaults() { - return Object.assign({}, super.defaults, { - templates: { - main: getTemplate(), - }, - labels: { - button: "<slot></slot>", - }, - classes: { - button: "monster-button-primary", - }, - disabled: false, - actions: { - click: () => { - throw new Error("the click action is not defined"); - }, - }, - effects: { - ripple: true, - }, - value: null, - }); - } - - /** - * - * @return {string} - */ - static getTag() { - return "monster-button"; - } - - /** - * - * @return {Array<CSSStyleSheet>} - */ - static getCSSStyleSheet() { - return [RippleStyleSheet, ButtonStyleSheet]; - } + /** + * This method is called by the <code>instanceof</code> operator. + * @return {symbol} + * @since 2.1.0 + */ + static get [instanceSymbol]() { + return Symbol.for("@schukai/monster/components/form/button@@instance"); + } + + /** + * + * @return {Button} + */ + [assembleMethodSymbol]() { + super[assembleMethodSymbol](); + initControlReferences.call(this); + initEventHandler.call(this); + return this; + } + + /** + * The <code>Button.click()</code> method simulates a click on the internal button element. + * + * @since 3.27.0 + * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/click} + */ + click() { + if (this.getOption("disabled") === true) { + return; + } + + if ( + this[buttonElementSymbol] && + isFunction(this[buttonElementSymbol].click) + ) { + this[buttonElementSymbol].click(); + } + } + + /** + * The Button.focus() method sets focus on the internal button element. + * + * @since 3.27.0 + * @param {Object} options + * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/focus} + */ + focus(options) { + if (this.getOption("disabled") === true) { + return; + } + + if ( + this[buttonElementSymbol] && + isFunction(this[buttonElementSymbol].focus) + ) { + this[buttonElementSymbol].focus(options); + } + } + + /** + * The Button.blur() method removes focus from the internal button element. + */ + blur() { + if ( + this[buttonElementSymbol] && + isFunction(this[buttonElementSymbol].blur) + ) { + this[buttonElementSymbol].blur(); + } + } + + /** + * This method determines which attributes are to be monitored by `attributeChangedCallback()`. + * + * @return {string[]} + */ + static get observedAttributes() { + const attributes = super.observedAttributes; + attributes.push(ATTRIBUTE_BUTTON_CLASS); + return attributes; + } + + /** + * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/attachInternals} + * @return {boolean} + */ + static get formAssociated() { + return true; + } + + /** + * The current value of the button. + * + * ```javascript + * e = document.querySelector('monster-button'); + * console.log(e.value) + * ``` + * + * @return {string} The value of the button + */ + get value() { + return this.getOption("value"); + } + + /** + * Set the value of the button. + * + * ```javascript + * e = document.querySelector('monster-button'); + * e.value=1 + * ``` + * + * @param {string} value + * @return {void} + * @throws {Error} unsupported type + */ + set value(value) { + this.setOption("value", value); + try { + this?.setFormValue(this.value); + } catch (e) { + addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, e.message); + } + } + + /** + * To set the options via the html tag the attribute `data-monster-options` must be used. + * @see {@link https://monsterjs.org/en/doc/#configurate-a-monster-control} + * + * The individual configuration values can be found in the table. + * + * @property {Object} templates Template definitions + * @property {string} templates.main Main template + * @property {Object} labels Labels + * @property {string} labels.button="<slot></slot>" Button label + * @property {Object} actions Callbacks + * @property {string} actions.click="throw Error" Callback when clicked + * @property {Object} classes CSS classes + * @property {string} classes.button="monster-button-primary" CSS class for the button + * @property {boolean} disabled=false Disabled state + * @property {Object} effects Effects + * @property {boolean} effects.ripple=true Ripple effect + */ + get defaults() { + return Object.assign({}, super.defaults, { + templates: { + main: getTemplate(), + }, + labels: { + button: "<slot></slot>", + }, + classes: { + button: "monster-button-primary", + }, + disabled: false, + actions: { + click: () => { + throw new Error("the click action is not defined"); + }, + }, + effects: { + ripple: true, + }, + value: null, + }); + } + + /** + * + * @return {string} + */ + static getTag() { + return "monster-button"; + } + + /** + * + * @return {CSSStyleSheet[]} + */ + static getCSSStyleSheet() { + return [RippleStyleSheet, ButtonStyleSheet]; + } } /** * @private * @return {initEventHandler} - * @fires Monster.Components.Form.event:monster-button-clicked */ function initEventHandler() { - const self = this; - const button = this[buttonElementSymbol]; + const self = this; + const button = this[buttonElementSymbol]; - const type = "click"; + const type = "click"; - button.addEventListener(type, function (event) { - const callback = self.getOption("actions.click"); + button.addEventListener(type, function (event) { + const callback = self.getOption("actions.click"); - fireCustomEvent(self, "monster-button-clicked", { - button: self, - }); + fireCustomEvent(self, "monster-button-clicked", { + button: self, + }); - if (!isFunction(callback)) { - return; - } + if (!isFunction(callback)) { + return; + } - const element = findTargetElementFromEvent( - event, - ATTRIBUTE_ROLE, - "control", - ); + const element = findTargetElementFromEvent( + event, + ATTRIBUTE_ROLE, + "control", + ); - if (!(element instanceof Node && self.hasNode(element))) { - return; - } + if (!(element instanceof Node && self.hasNode(element))) { + return; + } - callback.call(self, event); - }); + callback.call(self, event); + }); - if (self.getOption("effects.ripple")) { - button.addEventListener("click", createRipple.bind(self)); - } + if (self.getOption("effects.ripple")) { + button.addEventListener("click", createRipple.bind(self)); + } - // data-monster-options - self[attributeObserverSymbol][ATTRIBUTE_BUTTON_CLASS] = function (value) { - self.setOption("classes.button", value); - }; + // data-monster-options + self[attributeObserverSymbol][ATTRIBUTE_BUTTON_CLASS] = function (value) { + self.setOption("classes.button", value); + }; - return this; + return this; } /** * @private */ function initControlReferences() { - this[buttonElementSymbol] = this.shadowRoot.querySelector( - `[${ATTRIBUTE_ROLE}=button]`, - ); + this[buttonElementSymbol] = this.shadowRoot.querySelector( + `[${ATTRIBUTE_ROLE}=button]`, + ); } /** @@ -312,8 +296,8 @@ function initControlReferences() { * @return {string} */ function getTemplate() { - // language=HTML - return ` + // language=HTML + return ` <div data-monster-role="control" part="control"> <button data-monster-attributes="disabled path:disabled | if:true, class path:classes.button" data-monster-role="button" @@ -323,23 +307,23 @@ function getTemplate() { } function createRipple(event) { - const button = this[buttonElementSymbol]; + const button = this[buttonElementSymbol]; - const circle = document.createElement("span"); - const diameter = Math.max(button.clientWidth, button.clientHeight); - const radius = diameter / 2; + const circle = document.createElement("span"); + const diameter = Math.max(button.clientWidth, button.clientHeight); + const radius = diameter / 2; - circle.style.width = circle.style.height = `${diameter}px`; - circle.style.left = `${event.clientX - button.offsetLeft - radius}px`; - circle.style.top = `${event.clientY - button.offsetTop - radius}px`; - circle.classList.add("monster-fx-ripple"); + circle.style.width = circle.style.height = `${diameter}px`; + circle.style.left = `${event.clientX - button.offsetLeft - radius}px`; + circle.style.top = `${event.clientY - button.offsetTop - radius}px`; + circle.classList.add("monster-fx-ripple"); - const ripples = button.getElementsByClassName("monster-fx-ripple"); - for (const ripple of ripples) { - ripple.remove(); - } + const ripples = button.getElementsByClassName("monster-fx-ripple"); + for (const ripple of ripples) { + ripple.remove(); + } - button.appendChild(circle); + button.appendChild(circle); } registerCustomElement(Button); diff --git a/source/dom/ready.mjs b/source/dom/ready.mjs index 1465554f3a8cf7e07075769f56f4d25d621f55e1..fc9e66d9897c0316f21b6781f1cc5ec7f846700f 100644 --- a/source/dom/ready.mjs +++ b/source/dom/ready.mjs @@ -1,16 +1,22 @@ /** - * Copyright schukai GmbH and contributors 2023. All Rights Reserved. + * Copyright © schukai GmbH and all contributing authors, {{copyRightYear}}. All rights reserved. * Node module: @schukai/monster - * This file is licensed under the AGPLv3 License. - * License text available at https://www.gnu.org/licenses/agpl-3.0.en.html + * + * This source code is licensed under the GNU Affero General Public License version 3 (AGPLv3). + * The full text of the license can be found at: https://www.gnu.org/licenses/agpl-3.0.en.html + * + * For those who do not wish to adhere to the AGPLv3, a commercial license is available. + * Acquiring a commercial license allows you to use this software without complying with the AGPLv3 terms. + * For more information about purchasing a commercial license, please contact schukai GmbH. */ + import { getDocument, getWindow } from "./util.mjs"; export { domReady, windowReady }; /** - * This variable is a promise that is fulfilled as soon as the dom is available. + * This variable is a promise fulfilled as soon as the dom is available. * * The DOMContentLoaded event is fired when the original HTML document is fully loaded and parsed * without waiting for stylesheets, images, and subframes to finish loading. diff --git a/source/types/proxyobserver.mjs b/source/types/proxyobserver.mjs index 11d0503fe6c04020fdc0029a02ea31bcaf2b7e89..3b8a71c3bc6fd2aba97c0abc2a980ad0a4fcb7fa 100644 --- a/source/types/proxyobserver.mjs +++ b/source/types/proxyobserver.mjs @@ -12,13 +12,14 @@ import { ObserverList } from "./observerlist.mjs"; import { validateObject } from "./validate.mjs"; import { extend } from "../data/extend.mjs"; import { instanceSymbol } from "../constants.mjs"; +import { clone } from "../util/clone.mjs"; export { ProxyObserver }; /** * An observer manages a callback function * * With the ProxyObserver you can attach observer for observation. - * With each change at the object to be observed an update takes place. + * With each change at the object to be observed, an update takes place. * * This also applies to nested objects. * @@ -72,12 +73,13 @@ class ProxyObserver extends Base { */ setSubject(obj) { let i; + const clonedObject = clone(obj); const k = Object.keys(this.subject); for (i = 0; i < k.length; i++) { delete this.subject[k[i]]; } - this.subject = extend(this.subject, obj); + this.subject = extend(this.subject, clonedObject); return this; }