Skip to content
Snippets Groups Projects
Verified Commit bff0cc2a authored by Volker Schukai's avatar Volker Schukai :alien:
Browse files

chore: wip showroom

parent 27964ab4
Branches
Tags
No related merge requests found
Showing
with 1509 additions and 1184 deletions
Source diff could not be displayed: it is too large. Options to address this: view the blob.
This diff is collapsed.
//import "@schukai/monster/source/monster.mjs";
import "../../../source/components/style/property.pcss";
import "../../../source/components/style/normalize.pcss";
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/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
monster-panel[slot="end"] {
box-sizing: border-box;
display: flex;
align-items: flex-start;
/*padding: 0.6em 0.6em 4rem ;*/
padding:0 ;
& code {
font-size: 0.9em;
}
& .show-it {
background-color: var(--monster-bg-color-primary-1);
color: var(--monster-color-primary-1);
padding: 5em;
border: 1px solid var(--monster-bg-color-primary-3);
margin: 1em 0 2em 0;
}
& pre {
margin: 0;
padding: 0;
& pre {
margin: 0;
padding: 0;
& code.hljs {
padding: 1rem;
margin: 0;
background-color: var(--monster-bg-color-tertiary-2);
color: var(--monster-color-tertiary-2);
font-size: 0.9em;
font-weight: normal
}
}
}
& .container {
padding: 0.6em;
}
& 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;
height: 1.1em;
margin-top: 4px;
}
& .infoGrid {
margin: 0.5em 0 2em 0;
padding: 0.5em;
border-radius: 4px;
background-color: var(--monster-bg-color-primary-2);
color: var(--monster-color-primary-2);
overflow: auto;
display: grid;
grid-template-columns: max-content 20px 4fr;
grid-gap: 0.5em;
& img {
width: 1em;
height: auto;
margin-right: 0.5em;
vertical-align: middle;
}
& div {
display: flex;
& pre {
margin: 0;
padding: 0;
& code {
}
& code.hljs {
padding: 0;
margin: 0;
background-color: transparent;
font-size: 0.9em;
font-weight: normal
}
}
}
}
}
\ No newline at end of file
// vite.config.js
import {basename, resolve, join} from 'path'
import {resolve, join} from 'path'
import {defineConfig} from 'vite'
import {existsSync} from 'fs'
import banner from 'vite-plugin-banner'
......@@ -14,30 +14,34 @@ import postcssFor from 'postcss-for';
import autoprefixer from 'autoprefixer';
import postcssMixins from 'postcss-mixins';
import postcssResponsiveType from 'postcss-responsive-type';
import {ViteMinifyPlugin} from 'vite-plugin-minify'
import { viteMockServe } from 'vite-plugin-mock';
import {buildCSS,createScriptFilenameFromStyleFilename} from '../development/scripts/buildStylePostCSS.mjs';
import directoryIndex from 'vite-plugin-directory-index';
function getAppRootDir() {
let currentDir = __dirname
while (!existsSync(join(currentDir, 'package.json'))) {
currentDir = join(currentDir, '..')
}
return currentDir
}
import {projectRoot} from '../development/scripts/import.mjs'
const showroomDir = join(projectRoot, 'showroom')
const rootDir = getAppRootDir()
const showRoomDir = rootDir
import {glob} from 'glob';
const sourceDir = join(showroomDir, 'source');
const input = glob.sync(resolve(sourceDir, '**/*.html')).reduce((entries, file) => {
const name = file.replace(`${sourceDir}/`, '').replace(/\/|\\/g, '_').replace('.html', '');
entries[name] = file;
return entries;
}, {});
export default defineConfig({
clearScreen: false,
base: './',
appType: 'mpa',
root: showRoomDir,
root: showroomDir,
mode: 'development',
logLevel: 'info',
esbuild: {
......@@ -47,21 +51,42 @@ export default defineConfig({
target: 'es2015',
legalComments: 'none'
},
build: {
rollupOptions: {
input,
}
},
plugins: [
banner(
`/**\n * name: ${pkg.name}\n * version: v${pkg.version}\n * description: ${pkg.description}\n * author: ${pkg.author}\n * homepage: ${pkg.homepage}\n */`
),
ViteMinifyPlugin(),
// directoryPlugin({
// baseDir: __dirname
// }),
directoryIndex({}),
viteMockServe({
mockPath:showRoomDir+ "/mock",
{
name: 'run-script-on-pcss-change',
handleHotUpdate({file}) {
if (file.endsWith('.pcss')&&!file.startsWith(projectRoot+'/showroom/')) {
const source = file;
const dest = createScriptFilenameFromStyleFilename(file);
buildCSS(source, dest).then(() => {
}).catch((e) => {
console.error(e.message);
})
}
},
}
],
css: {
postcss: {
plugins: [
......@@ -78,7 +103,7 @@ export default defineConfig({
}), // https://github.com/notiv-nt/postcss-fluid
autoprefixer,
cssnano,
postcssResponsiveType
postcssResponsiveType,
]
}
},
......
......@@ -158,30 +158,28 @@ class Button extends CustomControl {
}
/**
* The current selection of the Select
* The current value of the button.
*
* ```
* e = document.querySelector('monster-select');
* e = document.querySelector('monster-button');
* console.log(e.value)
* // ↦ 1
* // ↦ ['1','2']
* ```
*
* @property {string|array}
* @property {string}
*/
get value() {
return this.getOption("value");
}
/**
* Set selection
* Set the value of the button.
*
* ```
* e = document.querySelector('monster-select');
* e = document.querySelector('monster-button');
* e.value=1
* ```
*
* @property {string|array} value
* @property {string} value
* @since 1.2.0
* @throws {Error} unsupported type
*/
......
......@@ -25,6 +25,7 @@ import { findTargetElementFromEvent } from "../../dom/events.mjs";
import { isFunction } from "../../types/is.mjs";
import { FieldSetStyleSheet } from "./stylesheet/field-set.mjs";
import { fireCustomEvent } from "../../dom/events.mjs";
import "../layout/collapse.mjs";
export { FieldSet };
......@@ -289,6 +290,9 @@ function getTemplate() {
// language=HTML
return `
<div data-monster-role="control" part="control">
<monster-collapse>
<slot></slot>
</monster-collapse>
</div>`;
}
......
/**
* Copyright schukai GmbH and contributors 2023. 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
*/
import { instanceSymbol } from "../../constants.mjs";
import { addAttributeToken } from "../../dom/attributes.mjs";
import {
ATTRIBUTE_ERRORMESSAGE,
ATTRIBUTE_ROLE,
} from "../../dom/constants.mjs";
import { CustomControl } from "../../dom/customcontrol.mjs";
import {
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";
export { FormField };
/**
* This CustomControl creates a container for form elements.
*
* <img src="./images/form-field.png">
*
* You can create this control either by specifying the HTML tag <monster-form-field />` directly in the HTML or using
* Javascript via the `document.createElement('monster-form-field');` method.
*
* ```html
* <monster-form-field></monster-form-field>
* ```
*
* Or you can create this CustomControl directly in Javascript:
*
* ```js
* import {Button} from '@schukai/monster/components/form/form-field.mjs';
* document.createElement('monster-form-field');
* ```
*
* The `data-monster-button-class` attribute can be used to change the CSS class of the button.
*
* @startuml form-field.png
* skinparam monochrome true
* skinparam shadowing false
* HTMLElement <|-- CustomElement
* CustomElement <|-- CustomControl
* CustomControl <|-- FormField
* @enduml
*
* @since 1.5.0
* @copyright schukai GmbH
* @memberOf Monster.Components.Form
* @summary A simple button
*/
class FormField 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 selection of the Select
*
* ```
* e = document.querySelector('monster-select');
* console.log(e.value)
* // ↦ 1
* // ↦ ['1','2']
* ```
*
* @property {string|array}
*/
get value() {
return this.getOption("value");
}
/**
* Set selection
*
* ```
* e = document.querySelector('monster-select');
* e.value=1
* ```
*
* @property {string|array} 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-form-field";
}
/**
*
* @return {Array<CSSStyleSheet>}
*/
static getCSSStyleSheet() {
return [FormFieldStyleSheet];
}
}
/**
* @private
* @return {initEventHandler}
* @fires Monster.Components.Form.event:monster-button-clicked
*/
function initEventHandler() {
const self = this;
const button = this[buttonElementSymbol];
const type = "click";
button.addEventListener(type, function (event) {
const callback = self.getOption("actions.click");
fireCustomEvent(self, "monster-button-clicked", {
button: self,
});
if (!isFunction(callback)) {
return;
}
const element = findTargetElementFromEvent(
event,
ATTRIBUTE_ROLE,
"control",
);
if (!(element instanceof Node && self.hasNode(element))) {
return;
}
callback.call(self, event);
});
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);
};
return this;
}
/**
* @private
*/
function initControlReferences() {
this[buttonElementSymbol] = this.shadowRoot.querySelector(
`[${ATTRIBUTE_ROLE}=button]`,
);
}
/**
* @private
* @return {string}
*/
function getTemplate() {
// 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"
part="button"
data-monster-replace="path:labels.button"></button>
</div>`;
}
function createRipple(event) {
const button = this[buttonElementSymbol];
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");
const ripples = button.getElementsByClassName("monster-fx-ripple");
for (const ripple of ripples) {
ripple.remove();
}
button.appendChild(circle);
}
registerCustomElement(FormField);
@import "../../style/display.pcss";
@import "../../style/border.pcss";
@import "../../style/control.pcss";
@import "../../style/badge.pcss";
@import '../../style/mixin/typography.pcss';
@import '../../style/mixin/hover.pcss';
@import "../../style/control.pcss";
@import "../../style/floating-ui.pcss";
[data-monster-role=control] {
border: 1px solid red;
}
\ No newline at end of file
......@@ -24,7 +24,7 @@ const FieldSetStyleSheet = new CSSStyleSheet();
try {
FieldSetStyleSheet.insertRule(`
@layer fieldset {
.block{display:block}.inline{display:inline}.inline-block{display:inline-block}.grid{display:grid}.inline-grid{display:inline-grid}.flex{display:flex}.inline-flex{display:inline-flex}.hidden,.hide,.none{display:none}.visible{visibility:visible}.invisible{visibility:hidden}.monster-border-primary-1,.monster-border-primary-2,.monster-border-primary-3,.monster-border-primary-4{border-radius:var(--monster-border-radius);border-style:var(--monster-border-style);border-width:var(--monster-border-width)}.monster-border-0{border-radius:0;border-style:none;border-width:0}.monster-border-primary-1{border-color:var(--monster-bg-color-primary-1)}.monster-border-primary-2{border-color:var(--monster-bg-color-primary-2)}.monster-border-primary-3{border-color:var(--monster-bg-color-primary-3)}.monster-border-primary-4{border-color:var(--monster-bg-color-primary-4)}.monster-border-secondary-1,.monster-border-secondary-2,.monster-border-secondary-3,.monster-border-secondary-4{border-radius:var(--monster-border-radius);border-style:var(--monster-border-style);border-width:var(--monster-border-width)}.monster-border-secondary-1{border-color:var(--monster-bg-color-secondary-1)}.monster-border-secondary-2{border-color:var(--monster-bg-color-secondary-2)}.monster-border-secondary-3{border-color:var(--monster-bg-color-secondary-3)}.monster-border-secondary-4{border-color:var(--monster-bg-color-secondary-4)}.monster-border-tertiary-1,.monster-border-tertiary-2,.monster-border-tertiary-3,.monster-border-tertiary-4{border-radius:var(--monster-border-radius);border-style:var(--monster-border-style);border-width:var(--monster-border-width)}.monster-border-tertiary-1{border-color:var(--monster-bg-color-tertiary-1)}.monster-border-tertiary-2{border-color:var(--monster-bg-color-tertiary-2)}.monster-border-tertiary-3{border-color:var(--monster-bg-color-tertiary-3)}.monster-border-tertiary-4{border-color:var(--monster-bg-color-tertiary-4)}[data-monster-role=control]{outline:none;width:100%}[data-monster-role=control].flex{align-items:center;display:flex;flex-direction:row}.monster-badge-primary{padding:.25em .4em}.monster-badge-primary,.monster-badge-primary-pill{background-color:var(--monster-bg-color-primary-4);border-radius:.25rem;color:var(--monster-color-primary-4);display:inline-block;font-size:75%;font-weight:700;line-height:1;text-align:center;text-decoration:none;vertical-align:baseline;white-space:nowrap}.monster-badge-primary-pill{border-radius:10rem;padding:.25em .6em}.monster-badge-secondary{padding:.25em .4em}.monster-badge-secondary,.monster-badge-secondary-pill{background-color:var(--monster-bg-color-secondary-3);border-radius:.25rem;color:var(--monster-color-secondary-3);display:inline-block;font-size:75%;font-weight:700;line-height:1;text-align:center;text-decoration:none;vertical-align:baseline;white-space:nowrap}.monster-badge-secondary-pill{border-radius:10rem;padding:.25em .6em}.monster-badge-tertiary{padding:.25em .4em}.monster-badge-tertiary,.monster-badge-tertiary-pill{background-color:var(--monster-bg-color-tertiary-3);border-radius:.25rem;color:var(--monster-color-tertiary-3);display:inline-block;font-size:75%;font-weight:700;line-height:1;text-align:center;text-decoration:none;vertical-align:baseline;white-space:nowrap}.monster-badge-tertiary-pill{border-radius:10rem;padding:.25em .6em}.monster-badge-destructive{padding:.25em .4em}.monster-badge-destructive,.monster-badge-destructive-pill{background-color:var(--monster-bg-color-destructive-1);border-radius:.25rem;color:var(--monster-color-destructive-1);display:inline-block;font-size:75%;font-weight:700;line-height:1;text-align:center;text-decoration:none;vertical-align:baseline;white-space:nowrap}.monster-badge-destructive-pill{border-radius:10rem;padding:.25em .6em}.monster-badge-success{padding:.25em .4em}.monster-badge-success,.monster-badge-success-pill{background-color:var(--monster-bg-color-success-1);border-radius:.25rem;color:var(--monster-color-success-1);display:inline-block;font-size:75%;font-weight:700;line-height:1;text-align:center;text-decoration:none;vertical-align:baseline;white-space:nowrap}.monster-badge-success-pill{border-radius:10rem;padding:.25em .6em}.monster-badge-warning{padding:.25em .4em}.monster-badge-warning,.monster-badge-warning-pill{background-color:var(--monster-bg-color-warning-1);border-radius:.25rem;color:var(--monster-color-warning-1);display:inline-block;font-size:75%;font-weight:700;line-height:1;text-align:center;text-decoration:none;vertical-align:baseline;white-space:nowrap}.monster-badge-warning-pill{border-radius:10rem;padding:.25em .6em}.monster-badge-error{padding:.25em .4em}.monster-badge-error,.monster-badge-error-pill{background-color:var(--monster-bg-color-error-1);border-radius:.25rem;color:var(--monster-color-error-1);display:inline-block;font-size:75%;font-weight:700;line-height:1;text-align:center;text-decoration:none;vertical-align:baseline;white-space:nowrap}.monster-badge-error-pill{border-radius:10rem;padding:.25em .6em}div[data-monster-role=popper]{align-content:center;background:var(--monster-bg-color-primary-1);border-color:var(--monster-bg-color-primary-4);border-radius:var(--monster-border-radius);border-style:var(--monster-border-style);border-width:var(--monster-border-width);box-shadow:var(--monster-box-shadow-1);box-sizing:border-box;color:var(--monster-color-primary-1);display:none;justify-content:space-between;left:0;padding:1.1em;position:absolute;top:0;width:-moz-max-content;width:max-content;z-index:var(--monster-z-index-modal)}div[data-monster-role=popper] div[data-monster-role=arrow]{background:var(--monster-bg-color-primary-1);height:calc(max(var(--monster-popper-witharrrow-distance), -1 * var(--monster-popper-witharrrow-distance))*2);pointer-events:none;position:absolute;width:calc(max(var(--monster-popper-witharrrow-distance), -1 * var(--monster-popper-witharrrow-distance))*2);z-index:-1}[data-monster-role=control]{border:1px solid red}
}`, 0);
} catch (e) {
addAttributeToken(document.getRootNode().querySelector('html'), ATTRIBUTE_ERRORMESSAGE, e + "");
......
/**
* Copyright © schukai GmbH and all contributing authors, 2024. 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 {addAttributeToken} from "../../../dom/attributes.mjs";
import {ATTRIBUTE_ERRORMESSAGE} from "../../../dom/constants.mjs";
export {FormFieldStyleSheet}
/**
* @private
* @type {CSSStyleSheet}
*/
const FormFieldStyleSheet = new CSSStyleSheet();
try {
FormFieldStyleSheet.insertRule(`
@layer formfield {
}`, 0);
} catch (e) {
addAttributeToken(document.getRootNode().querySelector('html'), ATTRIBUTE_ERRORMESSAGE, e + "");
}
/**
* Copyright 2023 schukai GmbH
* SPDX-License-Identifier: AGPL-3.0
*/
import {
assembleMethodSymbol,
CustomElement,
getSlottedElements,
registerCustomElement,
} from "../../dom/customelement.mjs";
import { CollapseStyleSheet } from "./stylesheet/collapse.mjs";
import { fireCustomEvent } from "../../dom/events.mjs";
import { getDocument } from "../../dom/util.mjs";
import { addAttributeToken } from "../../dom/attributes.mjs";
import { ATTRIBUTE_ERRORMESSAGE } from "../../dom/constants.mjs";
import { Host } from "./host.mjs";
import { generateUniqueConfigKey } from "./util.mjs";
import { DeadMansSwitch } from "../../util/deadmansswitch.mjs";
import { instanceSymbol } from "../../constants.mjs";
export { Collapse, nameSymbol };
/**
* @private
* @type {symbol}
*/
const timerCallbackSymbol = Symbol("timerCallback");
/**
* @private
* @type {symbol}
*/
const detailsElementSymbol = Symbol("detailsElement");
/**
* @private
* @type {symbol}
*/
const controlElementSymbol = Symbol("controlElement");
/**
* local symbol
* @private
* @type {symbol}
*/
const resizeObserverSymbol = Symbol("resizeObserver");
/**
* @private
* @type {symbol}
*/
const detailsSlotElementSymbol = Symbol("detailsSlotElement");
/**
* @private
* @type {symbol}
*/
const detailsContainerElementSymbol = Symbol("detailsContainerElement");
/**
* @private
* @type {symbol}
*/
const detailsDecoElementSymbol = Symbol("detailsDecoElement");
/**
* @private
* @type {symbol}
*/
const nameSymbol = Symbol("name");
/**
* The Collapse component is used to show a details.
*
* <img src="./images/collapse.png">
*
* Dependencies: the system uses functions of the [monsterjs](https://monsterjs.org/) library
*
* You can create this control either by specifying the HTML tag <monster-collapse />` directly in the HTML or using
* Javascript via the `document.createElement('monster-collapse');` method.
*
* ```html
* <monster-collapse></monster-collapse>
* ```
*
* Or you can create this CustomControl directly in Javascript:
*
* ```js
* import '@schukai/monster/source/components/host/collapse.mjs';
* document.createElement('monster-collapse');
* ```
*
* The Body should have a class "hidden" to ensure that the styles are applied correctly.
*
* ```css
* body.hidden {
* visibility: hidden;
* }
* ```
*
* @startuml collapse.png
* skinparam monochrome true
* skinparam shadowing false
* HTMLElement <|-- CustomElement
* CustomElement <|-- Collapse
* @enduml
*
* @copyright schukai GmbH
* @memberOf Monster.Components.Host
* @summary A simple collapse component
* @fires Monster.Components.Host.Collapse.event:monster-collapse-before-open
* @fires Monster.Components.Host.Collapse.event:monster-collapse-open
* @fires Monster.Components.Host.Collapse.event:monster-collapse-before-close
* @fires Monster.Components.Host.Collapse.event:monster-collapse-closed
* @fires Monster.Components.Host.Collapse.event:monster-collapse-adjust-height
*/
class Collapse extends CustomElement {
/**
* This method is called by the `instanceof` operator.
* @returns {symbol}
*/
static get [instanceSymbol]() {
return Symbol.for("@schukai/component-host/collapse@@instance");
}
/**
*
*/
constructor() {
super();
// the name is only used for the host config and the event name
this[nameSymbol] = "collapse";
}
/**
* 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} classes CSS classes
* @property {string} classes.container CSS class for the container
* @property {Object} features Feature configuration
* @property {boolean} features.accordion Enable accordion mode
* @property {boolean} features.persistState Enable persist state (Host and Config-Manager required)
* @property {boolean} features.useScrollValues Use scroll values (scrollHeight) instead of clientHeight for the height calculation
*/
get defaults() {
return Object.assign({}, super.defaults, {
templates: {
main: getTemplate(),
},
classes: {
container: "padding",
},
features: {
accordion: true,
persistState: true,
useScrollValues: false,
},
});
}
/**
*
* @returns {Monster.Components.Host.Collapse}
*/
[assembleMethodSymbol]() {
super[assembleMethodSymbol]();
initControlReferences.call(this);
initStateFromHostConfig.call(this);
initResizeObserver.call(this);
initEventHandler.call(this);
}
/**
*
*/
connectedCallback() {
super.connectedCallback();
updateResizeObserverObservation.call(this);
// this[resizeObserverSymbol].observe(getDocument().body);
}
/**
*
*/
disconnectedCallback() {
super.disconnectedCallback();
//this[resizeObserverSymbol].disconnect();
}
/**
*
* @returns {Monster.Components.Host.Collapse}
*/
toggle() {
if (this[detailsElementSymbol].classList.contains("active")) {
this.close();
} else {
this.open();
}
return this;
}
/**
*
* @returns {boolean}
*/
isClosed() {
return !this[detailsElementSymbol].classList.contains("active");
}
/**
*
* @returns {boolean}
*/
isOpen() {
return !this.isClosed();
}
/**
*
* @returns {Monster.Components.Host.Collapse}
* @fires Monster.Components.Host.Collapse.event:monster-collapse-before-open
* @fires Monster.Components.Host.Collapse.event:monster-collapse-open
*/
open() {
let node;
if (this[detailsElementSymbol].classList.contains("active")) {
return this;
}
fireCustomEvent(this, "monster-" + this[nameSymbol] + "-before-open", {});
adjustHeight.call(this);
this[detailsElementSymbol].classList.add("active");
if (this.getOption("features.accordion") === true) {
node = this;
while (node.nextElementSibling instanceof Collapse) {
node = node.nextElementSibling;
node.close();
}
node = this;
while (node.previousElementSibling instanceof Collapse) {
node = node.previousElementSibling;
node.close();
}
}
setTimeout(() => {
setTimeout(() => {
updateStateConfig.call(this);
fireCustomEvent(this, "monster-" + this[nameSymbol] + "-open", {});
setTimeout(() => {
this[controlElementSymbol].classList.remove("overflow-hidden");
}, 500);
}, 0);
}, 0);
return this;
}
/**
* Copyright © schukai GmbH and all contributing authors, {{copyRightYear}}. All rights reserved.
* Node module: @schukai/monster
*
* @returns {Monster.Components.Host.Collapse}
* @fires Monster.Components.Host.Collapse.event:monster-collapse-before-close
* @fires Monster.Components.Host.Collapse.event:monster-collapse-closed
*/
close() {
if (!this[detailsElementSymbol].classList.contains("active")) {
return this;
}
fireCustomEvent(this, "monster-" + this[nameSymbol] + "-before-close", {});
this[controlElementSymbol].classList.add("overflow-hidden");
setTimeout(() => {
this[detailsElementSymbol].classList.remove("active");
setTimeout(() => {
updateStateConfig.call(this);
fireCustomEvent(this, "monster-" + this[nameSymbol] + "-closed", {});
}, 0);
}, 0);
return this;
}
/**
* 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
*
* @return {string}
*/
static getTag() {
return "monster-collapse";
}
/**
* @return {Array<CSSStyleSheet>}
*/
static getCSSStyleSheet() {
return [CollapseStyleSheet];
}
/**
* This method is called when the element is inserted into a document, including into a shadow tree.
* @return {Monster.Components.Host.Collapse}
* @fires Monster.Components.Host.Collapse.event:monster-collapse-adjust-height
*/
adjustHeight() {
adjustHeight.call(this);
return this;
}
}
function adjustHeight() {
let height = 0;
if (this[detailsContainerElementSymbol]) {
if (this.getOption("features.useScrollValues")) {
height += this[detailsContainerElementSymbol].scrollHeight;
} else {
height += this[detailsContainerElementSymbol].clientHeight;
}
}
if (this[detailsDecoElementSymbol]) {
if (this.getOption("features.useScrollValues")) {
height += this[detailsDecoElementSymbol].scrollHeight;
} else {
height += this[detailsDecoElementSymbol].clientHeight + 1;
}
}
if (height === 0) {
if (this.getOption("features.useScrollValues")) {
height = this[detailsElementSymbol].scrollHeight;
} else {
height = this[detailsElementSymbol].clientHeight;
}
if (height === 0) {
height = "auto";
}
} else {
height += "px";
}
this[detailsElementSymbol].style.setProperty(
"--monster-height",
height,
"important",
);
fireCustomEvent(this, "monster-" + this[nameSymbol] + "-adjust-height", {});
}
function updateResizeObserverObservation() {
this[resizeObserverSymbol].disconnect();
const slottedNodes = getSlottedElements.call(this);
slottedNodes.forEach((node) => {
this[resizeObserverSymbol].observe(node);
});
if (this[detailsContainerElementSymbol]) {
this[resizeObserverSymbol].observe(this[detailsContainerElementSymbol]);
}
this.adjustHeight();
}
/**
* @private
*/
function initEventHandler() {
if (!this.shadowRoot) {
throw new Error("no shadow-root is defined");
}
initSlotChangedHandler.call(this);
return this;
}
function initSlotChangedHandler() {
this[detailsSlotElementSymbol].addEventListener("slotchange", () => {
updateResizeObserverObservation.call(this);
});
}
/**
* @private
* @return {Select}
* @throws {Error} no shadow-root is defined
* 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.
*/
function initControlReferences() {
if (!this.shadowRoot) {
throw new Error("no shadow-root is defined");
}
this[controlElementSymbol] = this.shadowRoot.querySelector(
"[data-monster-role=control]",
);
this[detailsElementSymbol] = this.shadowRoot.querySelector(
"[data-monster-role=detail]",
);
this[detailsSlotElementSymbol] = this.shadowRoot.querySelector("slot");
this[detailsContainerElementSymbol] = this.shadowRoot.querySelector(
"[data-monster-role=container]",
);
this[detailsDecoElementSymbol] = this.shadowRoot.querySelector(
"[data-monster-role=deco]",
);
}
/**
* @private
* @returns {string}
*/
function getConfigKey() {
return generateUniqueConfigKey(this[nameSymbol], this.id, "state");
}
import { Collapse as NewCollapse } from "../layout/collapse.mjs";
export { Collapse };
/**
* @private
*/
function updateStateConfig() {
if (!this.getOption("features.persistState")) {
return;
}
if (!this[detailsElementSymbol]) {
return;
}
const document = getDocument();
const host = document.querySelector("monster-host");
if (!(host && this.id)) {
return;
}
const configKey = getConfigKey.call(this);
try {
host.setConfig(configKey, this.isOpen());
} catch (error) {
addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, String(error));
}
}
/**
* @private
* @returns {Promise}
*/
function initStateFromHostConfig() {
if (!this.getOption("features.persistState")) {
return Promise.resolve({});
}
const document = getDocument();
const host = document.querySelector("monster-host");
if (!(host && this.id)) {
return Promise.resolve({});
}
const configKey = getConfigKey.call(this);
return host
.getConfig(configKey)
.then((state) => {
if (state === true) {
this.open();
} else {
this.close();
}
})
.catch((error) => {
addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, error.toString());
});
}
/**
* @private
* @since 1.10.0
* @copyright schukai GmbH
* @memberOf Monster.Components.Form
* @deprecated since 3.64.0 use {@link Monster.Components.Layout.Collapse}
*/
function initResizeObserver() {
// against flickering
this[resizeObserverSymbol] = new ResizeObserver((entries) => {
if (this[timerCallbackSymbol] instanceof DeadMansSwitch) {
try {
this[timerCallbackSymbol].touch();
return;
} catch (e) {
delete this[timerCallbackSymbol];
}
}
class Collapse extends NewCollapse {
this[timerCallbackSymbol] = new DeadMansSwitch(200, () => {
checkAndRearrangeContent.call(this);
});
});
}
function checkAndRearrangeContent() {
this.adjustHeight();
}
/**
* @private
* @return {string}
*/
function getTemplate() {
// language=HTML
return `
<div data-monster-role="control" part="control" class="overflow-hidden">
<div data-monster-role="detail">
<div data-monster-attributes="class path:classes.container" part="container"
data-monster-role="container">
<slot></slot>
</div>
<div class="deco-line" data-monster-role="deco"></div>
</div>
</div>`;
}
registerCustomElement(Collapse);
/**
* 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 {
......
/**
* Copyright schukai GmbH and contributors 2022. All Rights Reserved.
* Node module: @schukai/component-form
* This file is licensed under the AGPLv3 License.
* License text available at https://www.gnu.org/licenses/agpl-3.0.en.html
* 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 { ATTRIBUTE_PREFIX } from "../../dom/constants.mjs";
......
/**
* Copyright 2023 schukai GmbH
* SPDX-License-Identifier: AGPL-3.0
*/
import {
assembleMethodSymbol,
registerCustomElement,
} from "../../dom/customelement.mjs";
import { Host } from "./host.mjs";
import { DetailsStyleSheet } from "./stylesheet/details.mjs";
import { ATTRIBUTE_BUTTON_LABEL } from "./constants.mjs";
import { isString } from "../../types/is.mjs";
import { generateUniqueConfigKey } from "./util.mjs";
import { Collapse, nameSymbol } from "./collapse.mjs";
import { instanceSymbol } from "../../constants.mjs";
export { Details };
/**
* @private
* @type {symbol}
*/
const buttonElementSymbol = Symbol("buttonElement");
/**
* @private
* @type {symbol}
*/
const buttonEventHandlerSymbol = Symbol("buttonEventHandler");
/**
* The Details component is used to show a details.
*
* <img src="./images/details.png">
*
* Dependencies: the system uses functions of the [monsterjs](https://monsterjs.org/) library
*
* You can create this control either by specifying the HTML tag <monster-details />` directly in the HTML or using
* Javascript via the `document.createElement('monster-details');` method.
*
* ```html
* <monster-details></monster-details>
* ```
*
* Or you can create this CustomControl directly in Javascript:
*
* ```js
* import '@schukai/component-state/source/details.mjs';
* document.createElement('monster-details');
* ```
*
* The Body should have a class "hidden" to ensure that the styles are applied correctly.
*
* ```css
* body.hidden {
* visibility: hidden;
* }
* ```
*
* @startuml details.png
* skinparam monochrome true
* skinparam shadowing false
* HTMLElement <|-- CustomElement
* CustomElement <|-- Collapse
* Collapse <|-- Details
* @enduml
*
* @copyright schukai GmbH
* @memberOf Monster.Components.Host
* @summary A simple details component
* @fires Monster.Components.Host.Details.event:monster-details-before-open
* @fires Monster.Components.Host.Details.event:monster-details-open
* @fires Monster.Components.Host.Details.event:monster-details-before-close
* @fires Monster.Components.Host.Details.event:monster-details-closed
*/
class Details extends Collapse {
/**
* This method is called by the `instanceof` operator.
* @returns {symbol}
*/
static get [instanceSymbol]() {
return Symbol.for("@schukai/component-host/details@@instance");
}
/**
*
*/
constructor() {
super();
// the name is only used for the host config and the event name
this[nameSymbol] = "details";
}
/**
* 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.
* Copyright © schukai GmbH and all contributing authors, {{copyRightYear}}. All rights reserved.
* Node module: @schukai/monster
*
* @property {Object} templates Template definitions
* @property {string} templates.main Main template
* @property {Object} classes CSS classes
* @property {string} classes.button CSS class for the button
* @property {Object} button Button configuration
* @property {string} button.label Button label
* @property {Object} features Feature configuration
* @property {boolean} features.accordion Enable accordion mode
* @property {boolean} features.persistState Persist the state in the host configuration
*/
get defaults() {
return Object.assign({}, super.defaults, {
templates: {
main: getTemplate(),
},
labels: {
button: "Details",
},
});
}
/**
*
* @returns {Monster.Components.Host.Details}
*/
[assembleMethodSymbol]() {
super[assembleMethodSymbol]();
initButtonLabel.call(this);
initControlReferences.call(this);
initEventHandler.call(this);
}
connectedCallback() {
super.connectedCallback();
const containDocument = this.shadowRoot;
if (containDocument !== null) {
const previousElement = this.previousElementSibling;
if (previousElement && previousElement.tagName === "MONSTER-DETAILS") {
this[buttonElementSymbol].style.borderTop = "0";
}
}
}
/**
* 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
*
* @return {string}
*/
static getTag() {
return "monster-details";
}
/**
* @return {Array<CSSStyleSheet>}
*/
static getCSSStyleSheet() {
const css = super.getCSSStyleSheet();
css.push(DetailsStyleSheet);
return css;
}
}
/**
* @private
* @return {Select}
* @throws {Error} no shadow-root is defined
*/
function initControlReferences() {
if (!this.shadowRoot) {
throw new Error("no shadow-root is defined");
}
this[buttonElementSymbol] = this.shadowRoot.querySelector(
"[data-monster-role=button]",
);
}
/**
* @private
* 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.
*/
function initEventHandler() {
if (!this.shadowRoot) {
throw new Error("no shadow-root is defined");
}
this[buttonEventHandlerSymbol] = (event) => {
this.toggle();
};
this[buttonElementSymbol].addEventListener(
"click",
this[buttonEventHandlerSymbol],
);
return this;
}
import { Collapse as NewCollapse } from "../layout/collapse.mjs";
export { Details };
/**
* @private
* @return {string}
* @since 1.10.0
* @copyright schukai GmbH
* @memberOf Monster.Components.Form
* @deprecated since 3.64.0 use {@link Monster.Components.Layout.Details}
*/
function initButtonLabel() {
let label;
const setLabel = false;
if (this.hasAttribute(ATTRIBUTE_BUTTON_LABEL)) {
label = this.getAttribute(ATTRIBUTE_BUTTON_LABEL);
} else {
label = this.innerText;
}
if (!isString(label)) {
label = "";
}
label = label.trim();
if (label === "") {
label = this.getOption("labels.button", "Details");
}
if (label.length > 100) {
label = `${label.substring(0, 99)}…`;
}
class Details extends NewCollapse {
this.setAttribute(ATTRIBUTE_BUTTON_LABEL, label);
this.setOption("labels.button", label);
return label;
}
/**
* @private
* @returns {string}
*/
function getConfigKey() {
return generateUniqueConfigKey("details", this.id, "state");
}
/**
* @private
* @return {string}
*/
function getTemplate() {
// language=HTML
return `
<div data-monster-role="control" part="control" class="overflow-hidden">
<div data-monster-role="summary" part="summary">
<button part="button" data-monster-attributes="class path:classes.button"
data-monster-role="button"
data-monster-replace="path:labels.button | default:click me">click me
</button>
</div>
<div data-monster-role="detail">
<div data-monster-attributes="class path:classes.container" part="container"
data-monster-role="container">
<slot></slot>
</div>
<div class="deco-line" data-monster-role="deco"></div>
</div>
</div>`;
}
registerCustomElement(Details);
/**
* 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 {
assembleMethodSymbol,
CustomElement,
getSlottedElements,
registerCustomElement,
} from "../../dom/customelement.mjs";
import { CollapseStyleSheet } from "./stylesheet/collapse.mjs";
import { fireCustomEvent } from "../../dom/events.mjs";
import { getDocument } from "../../dom/util.mjs";
import { addAttributeToken } from "../../dom/attributes.mjs";
import { ATTRIBUTE_ERRORMESSAGE } from "../../dom/constants.mjs";
import { Host } from "../host/host.mjs";
import { generateUniqueConfigKey } from "../host/util.mjs";
import { DeadMansSwitch } from "../../util/deadmansswitch.mjs";
import { instanceSymbol } from "../../constants.mjs";
export { Collapse, nameSymbol };
/**
* @private
* @type {symbol}
*/
const timerCallbackSymbol = Symbol("timerCallback");
/**
* @private
* @type {symbol}
*/
const detailsElementSymbol = Symbol("detailsElement");
/**
* @private
* @type {symbol}
*/
const controlElementSymbol = Symbol("controlElement");
/**
* local symbol
* @private
* @type {symbol}
*/
const resizeObserverSymbol = Symbol("resizeObserver");
/**
* @private
* @type {symbol}
*/
const detailsSlotElementSymbol = Symbol("detailsSlotElement");
/**
* @private
* @type {symbol}
*/
const detailsContainerElementSymbol = Symbol("detailsContainerElement");
/**
* @private
* @type {symbol}
*/
const detailsDecoElementSymbol = Symbol("detailsDecoElement");
/**
* @private
* @type {symbol}
*/
const nameSymbol = Symbol("name");
/**
* The Collapse component is used to show a details.
*
* <img src="./images/collapse.png">
*
* Dependencies: the system uses functions of the [monsterjs](https://monsterjs.org/) library
*
* You can create this control either by specifying the HTML tag <monster-collapse />` directly in the HTML or using
* Javascript via the `document.createElement('monster-collapse');` method.
*
* ```html
* <monster-collapse></monster-collapse>
* ```
*
* Or you can create this CustomControl directly in Javascript:
*
* ```js
* import '@schukai/monster/source/components/host/collapse.mjs';
* document.createElement('monster-collapse');
* ```
*
* The Body should have a class "hidden" to ensure that the styles are applied correctly.
*
* ```css
* body.hidden {
* visibility: hidden;
* }
* ```
*
* @startuml collapse.png
* skinparam monochrome true
* skinparam shadowing false
* HTMLElement <|-- CustomElement
* CustomElement <|-- Collapse
* @enduml
*
* @copyright schukai GmbH
* @memberOf Monster.Components.Host
* @summary A simple collapse component
* @fires Monster.Components.Host.Collapse.event:monster-collapse-before-open
* @fires Monster.Components.Host.Collapse.event:monster-collapse-open
* @fires Monster.Components.Host.Collapse.event:monster-collapse-before-close
* @fires Monster.Components.Host.Collapse.event:monster-collapse-closed
* @fires Monster.Components.Host.Collapse.event:monster-collapse-adjust-height
*/
class Collapse extends CustomElement {
/**
* This method is called by the `instanceof` operator.
* @returns {symbol}
*/
static get [instanceSymbol]() {
return Symbol.for("@schukai/monster/components/layout/collapse@@instance");
}
/**
*
*/
constructor() {
super();
// the name is only used for the host config and the event name
this[nameSymbol] = "collapse";
}
/**
* 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} classes CSS classes
* @property {string} classes.container CSS class for the container
* @property {Object} features Feature configuration
* @property {boolean} features.accordion Enable accordion mode
* @property {boolean} features.persistState Enable persist state (Host and Config-Manager required)
* @property {boolean} features.useScrollValues Use scroll values (scrollHeight) instead of clientHeight for the height calculation
* @property {boolean} openByDefault Open the details by default
*/
get defaults() {
return Object.assign({}, super.defaults, {
templates: {
main: getTemplate(),
},
classes: {
container: "padding",
},
features: {
accordion: true,
persistState: true,
useScrollValues: false,
},
openByDefault: false,
});
}
/**
*
* @returns {Monster.Components.Host.Collapse}
*/
[assembleMethodSymbol]() {
super[assembleMethodSymbol]();
initControlReferences.call(this);
initStateFromHostConfig.call(this);
initResizeObserver.call(this);
initEventHandler.call(this);
if (this.getOption("openByDefault")) {
this.open();
}
}
/**
*
*/
connectedCallback() {
super.connectedCallback();
updateResizeObserverObservation.call(this);
// this[resizeObserverSymbol].observe(getDocument().body);
}
/**
*
*/
disconnectedCallback() {
super.disconnectedCallback();
//this[resizeObserverSymbol].disconnect();
}
/**
*
* @returns {Monster.Components.Host.Collapse}
*/
toggle() {
if (this[detailsElementSymbol].classList.contains("active")) {
this.close();
} else {
this.open();
}
return this;
}
/**
*
* @returns {boolean}
*/
isClosed() {
return !this[detailsElementSymbol].classList.contains("active");
}
/**
*
* @returns {boolean}
*/
isOpen() {
return !this.isClosed();
}
/**
*
* @returns {Monster.Components.Host.Collapse}
* @fires Monster.Components.Host.Collapse.event:monster-collapse-before-open
* @fires Monster.Components.Host.Collapse.event:monster-collapse-open
*/
open() {
let node;
if (this[detailsElementSymbol].classList.contains("active")) {
return this;
}
fireCustomEvent(this, "monster-" + this[nameSymbol] + "-before-open", {});
adjustHeight.call(this);
this[detailsElementSymbol].classList.add("active");
if (this.getOption("features.accordion") === true) {
node = this;
while (node.nextElementSibling instanceof Collapse) {
node = node.nextElementSibling;
node.close();
}
node = this;
while (node.previousElementSibling instanceof Collapse) {
node = node.previousElementSibling;
node.close();
}
}
setTimeout(() => {
setTimeout(() => {
updateStateConfig.call(this);
fireCustomEvent(this, "monster-" + this[nameSymbol] + "-open", {});
setTimeout(() => {
this[controlElementSymbol].classList.remove("overflow-hidden");
}, 500);
}, 0);
}, 0);
return this;
}
/**
*
* @returns {Monster.Components.Host.Collapse}
* @fires Monster.Components.Host.Collapse.event:monster-collapse-before-close
* @fires Monster.Components.Host.Collapse.event:monster-collapse-closed
*/
close() {
if (!this[detailsElementSymbol].classList.contains("active")) {
return this;
}
fireCustomEvent(this, "monster-" + this[nameSymbol] + "-before-close", {});
this[controlElementSymbol].classList.add("overflow-hidden");
setTimeout(() => {
this[detailsElementSymbol].classList.remove("active");
setTimeout(() => {
updateStateConfig.call(this);
fireCustomEvent(this, "monster-" + this[nameSymbol] + "-closed", {});
}, 0);
}, 0);
return this;
}
/**
*
* @return {string}
*/
static getTag() {
return "monster-collapse";
}
/**
* @return {Array<CSSStyleSheet>}
*/
static getCSSStyleSheet() {
return [CollapseStyleSheet];
}
/**
* This method is called when the element is inserted into a document, including into a shadow tree.
* @return {Monster.Components.Host.Collapse}
* @fires Monster.Components.Host.Collapse.event:monster-collapse-adjust-height
*/
adjustHeight() {
adjustHeight.call(this);
return this;
}
}
function adjustHeight() {
let height = 0;
if (this[detailsContainerElementSymbol]) {
if (this.getOption("features.useScrollValues")) {
height += this[detailsContainerElementSymbol].scrollHeight;
} else {
height += this[detailsContainerElementSymbol].clientHeight;
}
}
if (this[detailsDecoElementSymbol]) {
if (this.getOption("features.useScrollValues")) {
height += this[detailsDecoElementSymbol].scrollHeight;
} else {
height += this[detailsDecoElementSymbol].clientHeight + 1;
}
}
if (height === 0) {
if (this.getOption("features.useScrollValues")) {
height = this[detailsElementSymbol].scrollHeight;
} else {
height = this[detailsElementSymbol].clientHeight;
}
if (height === 0) {
height = "auto";
}
} else {
height += "px";
}
this[detailsElementSymbol].style.setProperty(
"--monster-height",
height,
"important",
);
fireCustomEvent(this, "monster-" + this[nameSymbol] + "-adjust-height", {});
}
function updateResizeObserverObservation() {
this[resizeObserverSymbol].disconnect();
const slottedNodes = getSlottedElements.call(this);
slottedNodes.forEach((node) => {
this[resizeObserverSymbol].observe(node);
});
if (this[detailsContainerElementSymbol]) {
this[resizeObserverSymbol].observe(this[detailsContainerElementSymbol]);
}
this.adjustHeight();
}
/**
* @private
*/
function initEventHandler() {
if (!this.shadowRoot) {
throw new Error("no shadow-root is defined");
}
initSlotChangedHandler.call(this);
return this;
}
function initSlotChangedHandler() {
this[detailsSlotElementSymbol].addEventListener("slotchange", () => {
updateResizeObserverObservation.call(this);
});
}
/**
* @private
* @return {Select}
* @throws {Error} no shadow-root is defined
*/
function initControlReferences() {
if (!this.shadowRoot) {
throw new Error("no shadow-root is defined");
}
this[controlElementSymbol] = this.shadowRoot.querySelector(
"[data-monster-role=control]",
);
this[detailsElementSymbol] = this.shadowRoot.querySelector(
"[data-monster-role=detail]",
);
this[detailsSlotElementSymbol] = this.shadowRoot.querySelector("slot");
this[detailsContainerElementSymbol] = this.shadowRoot.querySelector(
"[data-monster-role=container]",
);
this[detailsDecoElementSymbol] = this.shadowRoot.querySelector(
"[data-monster-role=deco]",
);
}
/**
* @private
* @returns {string}
*/
function getConfigKey() {
return generateUniqueConfigKey(this[nameSymbol], this.id, "state");
}
/**
* @private
*/
function updateStateConfig() {
if (!this.getOption("features.persistState")) {
return;
}
if (!this[detailsElementSymbol]) {
return;
}
const document = getDocument();
const host = document.querySelector("monster-host");
if (!(host && this.id)) {
return;
}
const configKey = getConfigKey.call(this);
try {
host.setConfig(configKey, this.isOpen());
} catch (error) {
addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, String(error));
}
}
/**
* @private
* @returns {Promise}
*/
function initStateFromHostConfig() {
if (!this.getOption("features.persistState")) {
return Promise.resolve({});
}
const document = getDocument();
const host = document.querySelector("monster-host");
if (!(host && this.id)) {
return Promise.resolve({});
}
const configKey = getConfigKey.call(this);
return host
.getConfig(configKey)
.then((state) => {
if (state === true) {
this.open();
} else {
this.close();
}
})
.catch((error) => {
addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, error.toString());
});
}
/**
* @private
*/
function initResizeObserver() {
// against flickering
this[resizeObserverSymbol] = new ResizeObserver((entries) => {
if (this[timerCallbackSymbol] instanceof DeadMansSwitch) {
try {
this[timerCallbackSymbol].touch();
return;
} catch (e) {
delete this[timerCallbackSymbol];
}
}
this[timerCallbackSymbol] = new DeadMansSwitch(200, () => {
checkAndRearrangeContent.call(this);
});
});
}
function checkAndRearrangeContent() {
this.adjustHeight();
}
/**
* @private
* @return {string}
*/
function getTemplate() {
// language=HTML
return `
<div data-monster-role="control" part="control" class="overflow-hidden">
<div data-monster-role="detail">
<div data-monster-attributes="class path:classes.container" part="container"
data-monster-role="container">
<slot></slot>
</div>
<div class="deco-line" data-monster-role="deco" part="deco"></div>
</div>
</div>`;
}
registerCustomElement(Collapse);
/**
* 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 {
assembleMethodSymbol,
registerCustomElement,
} from "../../dom/customelement.mjs";
import { DetailsStyleSheet } from "./stylesheet/details.mjs";
import { ATTRIBUTE_BUTTON_LABEL } from "../host/constants.mjs";
import { isString } from "../../types/is.mjs";
import { generateUniqueConfigKey } from "../host/util.mjs";
import { Collapse, nameSymbol } from "./collapse.mjs";
import { instanceSymbol } from "../../constants.mjs";
export { Details };
/**
* @private
* @type {symbol}
*/
const buttonElementSymbol = Symbol("buttonElement");
/**
* @private
* @type {symbol}
*/
const buttonEventHandlerSymbol = Symbol("buttonEventHandler");
/**
* The Details component is used to show a details.
*
* <img src="./images/details.png">
*
* Dependencies: the system uses functions of the [monsterjs](https://monsterjs.org/) library
*
* You can create this control either by specifying the HTML tag <monster-details />` directly in the HTML or using
* Javascript via the `document.createElement('monster-details');` method.
*
* ```html
* <monster-details></monster-details>
* ```
*
* Or you can create this CustomControl directly in Javascript:
*
* ```js
* import '@schukai/component-state/source/details.mjs';
* document.createElement('monster-details');
* ```
*
* The Body should have a class "hidden" to ensure that the styles are applied correctly.
*
* ```css
* body.hidden {
* visibility: hidden;
* }
* ```
*
* @startuml details.png
* skinparam monochrome true
* skinparam shadowing false
* HTMLElement <|-- CustomElement
* CustomElement <|-- Collapse
* Collapse <|-- Details
* @enduml
*
* @copyright schukai GmbH
* @memberOf Monster.Components.Layout
* @summary A simple details component
* @fires Monster.Components.Layout.Details.event:monster-details-before-open
* @fires Monster.Components.Layout.Details.event:monster-details-open
* @fires Monster.Components.Layout.Details.event:monster-details-before-close
* @fires Monster.Components.Layout.Details.event:monster-details-closed
*/
class Details extends Collapse {
/**
* This method is called by the `instanceof` operator.
* @returns {symbol}
*/
static get [instanceSymbol]() {
return Symbol.for("@schukai/monster/components/layout/details@@instance");
}
/**
*
*/
constructor() {
super();
// the name is only used for the host config and the event name
this[nameSymbol] = "details";
}
/**
* 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} classes CSS classes
* @property {string} classes.button CSS class for the button
* @property {Object} button Button configuration
* @property {string} button.label Button label
* @property {Object} features Feature configuration
* @property {boolean} features.accordion Enable accordion mode
* @property {boolean} features.persistState Persist the state in the host configuration
*/
get defaults() {
return Object.assign({}, super.defaults, {
templates: {
main: getTemplate(),
},
labels: {
button: "Details",
},
});
}
/**
*
* @returns {Monster.Components.Layout.Details}
*/
[assembleMethodSymbol]() {
super[assembleMethodSymbol]();
initButtonLabel.call(this);
initControlReferences.call(this);
initEventHandler.call(this);
}
connectedCallback() {
super.connectedCallback();
const containDocument = this.shadowRoot;
if (containDocument !== null) {
const previousElement = this.previousElementSibling;
if (previousElement && previousElement.tagName === "MONSTER-DETAILS") {
this[buttonElementSymbol].style.borderTop = "0";
}
}
}
/**
*
* @return {string}
*/
static getTag() {
return "monster-details";
}
/**
* @return {Array<CSSStyleSheet>}
*/
static getCSSStyleSheet() {
const css = super.getCSSStyleSheet();
css.push(DetailsStyleSheet);
return css;
}
}
/**
* @private
* @return {Select}
* @throws {Error} no shadow-root is defined
*/
function initControlReferences() {
if (!this.shadowRoot) {
throw new Error("no shadow-root is defined");
}
this[buttonElementSymbol] = this.shadowRoot.querySelector(
"[data-monster-role=button]",
);
}
/**
* @private
*/
function initEventHandler() {
if (!this.shadowRoot) {
throw new Error("no shadow-root is defined");
}
this[buttonEventHandlerSymbol] = (event) => {
this.toggle();
};
this[buttonElementSymbol].addEventListener(
"click",
this[buttonEventHandlerSymbol],
);
return this;
}
/**
* @private
* @return {string}
*/
function initButtonLabel() {
let label;
const setLabel = false;
if (this.hasAttribute(ATTRIBUTE_BUTTON_LABEL)) {
label = this.getAttribute(ATTRIBUTE_BUTTON_LABEL);
} else {
label = this.innerText;
}
if (!isString(label)) {
label = "";
}
label = label.trim();
if (label === "") {
label = this.getOption("labels.button", "Details");
}
if (label.length > 100) {
label = `${label.substring(0, 99)}…`;
}
this.setAttribute(ATTRIBUTE_BUTTON_LABEL, label);
this.setOption("labels.button", label);
return label;
}
/**
* @private
* @returns {string}
*/
function getConfigKey() {
return generateUniqueConfigKey("details", this.id, "state");
}
/**
* @private
* @return {string}
*/
function getTemplate() {
// language=HTML
return `
<div data-monster-role="control" part="control" class="overflow-hidden">
<div data-monster-role="summary" part="summary">
<button part="button" data-monster-attributes="class path:classes.button"
data-monster-role="button"
data-monster-replace="path:labels.button | default:click me">click me
</button>
</div>
<div data-monster-role="detail">
<div data-monster-attributes="class path:classes.container" part="container"
data-monster-role="container">
<slot></slot>
</div>
<div class="deco-line" data-monster-role="deco"></div>
</div>
</div>`;
}
registerCustomElement(Details);
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment