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

feat(tabs): new feature flag removeBehavior #268 , update form/template and tree-select

parent ea77564d
No related branches found
No related tags found
No related merge requests found
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>auto activate tab after close #268</title>
<script src="268.mjs" type="module"></script>
</head>
<body>
<h1>auto activate tab after close #268</h1>
<p>When one tab is closed, another should be activated.</p>
<ul>
<li><a href="https://gitlab.schukai.com/oss/libraries/javascript/monster/-/issues/268">Issue #268</a></li>
<li><a href="/">Back to overview</a></li>
</ul>
<main>
<monster-tabs data-monster-option-features-removeBehavior="auto">
<div data-monster-button-label="A1" data-monster-removable>test</div>
<div data-monster-button-label="A2" data-monster-removable>test</div>
<div data-monster-button-label="A3" class="active" data-monster-removable>test</div>
<div data-monster-button-label="A4" data-monster-removable>test</div>
<div data-monster-button-label="A5" data-monster-removable>test</div>
</monster-tabs>
</main>
</body>
</html>
/**
* @file development/issues/open/268.mjs
* @url https://gitlab.schukai.com/oss/libraries/javascript/monster/-/issues/268
* @description auto activate tab after close
* @issue 268
*/
import "../../../source/components/style/property.pcss";
import "../../../source/components/style/link.pcss";
import "../../../source/components/style/color.pcss";
import "../../../source/components/style/theme.pcss";
import "../../../source/components/style/normalize.pcss";
import "../../../source/components/style/typography.pcss";
import "../../../source/components/layout/tabs.mjs";
......@@ -58,6 +58,9 @@ const originValuesSymbol = Symbol("originValues");
*/
const badgeElementSymbol = Symbol("badgeElement");
/**
*
*/
class SaveButton extends CustomElement {
/**
* This method is called by the `instanceof` operator.
......
......@@ -53,23 +53,12 @@ const popperElementSymbol = Symbol("popperElement");
*/
const iconElementSymbol = Symbol("iconElement");
/**
* The ContextError control shows an error message in a popper.
*
* @fragments /fragments/components/form/context-error/
*
* @example /examples/components/form/context-error-simple
*
* @copyright schukai GmbH
* @summary A control that can be used to display a tooltip or a popover with an error message.
**/
/**
* A context error control.
*
* @fragments /fragments/components/form/select/
* @fragments /fragments/components/form/context-error
*
* @example /examples/components/form/select-simple
* @example /examples/components/form/context-error-simple
*
* @since 3.55.0
* @copyright schukai GmbH
......
......@@ -23,7 +23,7 @@ export { ContextHelp };
/**
* A context help control.
*
* @fragments /fragments/components/form/context-help/
* @fragments /fragments/components/form/context-help
*
* @example /examples/components/form/context-help-simple
*
......
......@@ -50,9 +50,9 @@ export const inputElementSymbol = Symbol("inputIconElement");
/**
* A password field
*
* @fragments /fragments/components/components/form/password/
* @fragments /fragments/components/form/password
*
* @example /examples/components/components/form/password-simple
* @example /examples/components/form/password-simple
*
* @since 3.89.0
* @copyright schukai GmbH
......
......@@ -121,7 +121,7 @@ class Reload extends CustomElement {
* @property {string} url=undefined
* @property {string} reload=undefined currently the values defined are `onshow` and `always`. The default `onshow` removes the IntersectionObserver. This means that the content is only loaded once. reloading of the content does not occur.
* @property {string} filter=undefined dom selectors to search for elements, if undefined then everything is taken
* @property {Monster.Components.Form.Processor[]} processors
* @property {Object[]} processors
* @property {Object} fetch Fetch [see Using Fetch mozilla.org](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch)
* @property {String} fetch.redirect=error
* @property {String} fetch.method=GET
......
......@@ -50,7 +50,7 @@ class ShadowReload extends Reload {
* @property {string} url=undefined
* @property {string} reload=undefined currently the values defined are `onshow` and `always`. The default `onshow` removes the IntersectionObserver. This means that the content is only loaded once. reloading of the content does not occur.
* @property {string} filter=undefined dom selectors to search for elements, if undefined then everything is taken
* @property {Monster.Components.Form.Processor[]} processors
* @property {Object[]} processors
* @property {Object} fetch Fetch [see Using Fetch mozilla.org](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch)
* @property {String} fetch.redirect=error
* @property {String} fetch.method=GET
......
......@@ -40,9 +40,12 @@ const intersectionObserverWasInitialized = Symbol("wasInitialized");
/**
* A Template control is a control that can be used to load content from a URL and display it in the ShadowRoot.
*
* @fragments /fragments/components/form/template/
* @fragments /fragments/components/form/template
*
* @example /examples/components/form/template-simple
* @example /examples/components/form/template-with-default
* @example /examples/components/form/template-with-processor
* @example /examples/components/form/template-onshow
*
* @since 1.11.0
* @copyright schukai GmbH
......@@ -69,7 +72,7 @@ class Template extends CustomElement {
* @property {string} templates.main Main template
* @property {string} url=undefined
* @property {string} reload=undefined currently the only value defined is `onshow`. Currently the only value defined is onshow. this removes the IntersectionObserver. this means that the content is only loaded once. reloading of the content does not occur.
* @property {Monster.Components.Form.Processor[]} processors
* @property {Object[]} processors
* @property {Object} fetch Fetch [see Using Fetch mozilla.org](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch)
* @property {String} fetch.redirect=error
* @property {String} fetch.method=GET
......@@ -85,8 +88,8 @@ class Template extends CustomElement {
templates: {
main: getTemplate(),
},
url: undefined,
reload: undefined,
url: null,
reload: null,
processors: [],
fetch: {
redirect: "error",
......@@ -123,8 +126,6 @@ class Template extends CustomElement {
this[attributeObserverSymbol][ATTRIBUTE_FORM_URL] = (url) => {
if (this.hasAttribute(ATTRIBUTE_FORM_URL)) {
this.setOption("url", new URL(url, document.location).toString());
} else {
this.setOption("url", undefined);
}
};
}
......@@ -139,7 +140,7 @@ class Template extends CustomElement {
* @throws {Error} not found
* @throws {Error} undefined status or type
* @fires monster-fetched
* @return {Monster.Components.Form.Form}
* @return {void}
*/
[assembleMethodSymbol]() {
super[assembleMethodSymbol]();
......@@ -181,13 +182,6 @@ class Template extends CustomElement {
}
}
/**
* @typedef {Object} Processor
* @property {String} destination
* @property {String} source
* @since 1.11.8
*/
/**
* This attribute can be used to pass a URL to this select.
*
......@@ -237,6 +231,7 @@ function initIntersectionObserver() {
};
const callback = (entries, observer) => {
for (const [, entry] of entries.entries()) {
if (entry.isIntersecting === true) {
if (this.getOption("reload") === "onshow") {
......@@ -276,7 +271,12 @@ function loadContent() {
throw new Error("no shadow-root is defined");
}
const url = this.getOption("url", undefined);
let url = this.getOption("url", undefined);
if (url instanceof URL) {
url = url.toString();
}
if (!isString(url) || url === "") {
throw new Error("missing url");
}
......@@ -303,6 +303,7 @@ function loadContent() {
loadAndAssignContent(container, url, options)
.then(() => {
defaultSlot.style.display = "none";
container.style.display = "block";
runProcessors.call(this);
})
.catch((e) => {
......@@ -316,15 +317,33 @@ function loadContent() {
*/
function runProcessors() {
const processors = this.getOption("processors");
if (!isArray(processors)) return;
if (!isArray(processors)) return this;
for (const [, processor] of processors.entries()) {
const source = processor?.source;
const destination = processor?.destination;
let destination = processor?.destination;
if (source === null) {
addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, "missing source");
continue;
}
if (destination === null || destination === undefined || destination === "") {
destination = "["+ATTRIBUTE_ROLE+"=container]";
}
if (isString(source) && isString(destination)) {
const sourceNode = this.shadowRoot.querySelector(source);
const destinationNode = document.querySelector(destination);
let destinationNode = document.querySelector(destination);
if (destinationNode===null) {
destinationNode = this.shadowRoot.querySelector(destination);
if (destinationNode===null) {
addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, "destination not found");
continue;
}
}
if (
sourceNode instanceof HTMLElement &&
......@@ -332,6 +351,8 @@ function runProcessors() {
) {
destinationNode.innerHTML = sourceNode.cloneNode(true).innerHTML;
}
} else {
addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, "invalid source or destination");
}
}
......
......@@ -13,8 +13,9 @@
*/
import {buildTree} from "../../data/buildtree.mjs";
import { findClosestByAttribute } from "../../dom/attributes.mjs";
import {addAttributeToken, findClosestByAttribute} from "../../dom/attributes.mjs";
import {
ATTRIBUTE_ERRORMESSAGE,
ATTRIBUTE_ROLE,
ATTRIBUTE_UPDATER_INSERT_REFERENCE,
} from "../../dom/constants.mjs";
......@@ -57,7 +58,7 @@ const keyEventHandler = Symbol("keyEventHandler");
*
* @fragments /fragments/components/form/tree-select
*
* @example /examples/components/form/tree-select
* @example /examples/components/form/tree-select-simple
*
* @since 1.9.0
* @copyright schukai GmbH
......@@ -98,8 +99,12 @@ class TreeSelect extends Select {
{
mapping: {
rootReferences: ["0", undefined, null],
idTemplate: "id",
parentTemplate: "parent",
id: "id",
parent: "parent",
selector: "*",
labelTemplate: "",
valueTemplate: "",
},
formatter: {
selection: formatHierarchicalSelection,
......@@ -145,20 +150,22 @@ class TreeSelect extends Select {
const filter = mappingOptions?.["filter"];
const rootReferences = mappingOptions?.["rootReferences"];
const id = this.getOption("mapping.idTemplate", "id");
const parentID = this.getOption("mapping.parentTemplate", "parent");
const id = this.getOption("mapping.id", "id");
const parentID = this.getOption("mapping.parent", "parent");
const selector = mappingOptions?.["selector"];
const options = [];
try {
const nodes = buildTree(data, selector, id, parentID, {
filter,
rootReferences,
});
const options = [];
for (const node of nodes) {
const iterator = new NodeRecursiveIterator(node);
for (const n of iterator) {
const formattedValues = formatKeyLabel.call(this, n);
const label = formattedValues.label;
......@@ -187,12 +194,18 @@ class TreeSelect extends Select {
options,
});
} catch (e) {
addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, e?.message || e);
}
return this;
}
/**
*
* @return {Monster.Components.Form.Select}
* @return {TreeSelect}
*/
[assembleMethodSymbol]() {
super[assembleMethodSymbol]();
......@@ -251,10 +264,16 @@ function closeOrOpenCurrentOption(event, mode) {
function formatKeyLabel(node) {
validateInstance(node, Node);
const label = new Formatter(node.value).format(
const v = node.value;
if (v === undefined) {
throw new Error("the object has no value for the specified id");
}
const label = new Formatter(v).format(
this.getOption("mapping.labelTemplate", ""),
);
const value = new Formatter(node.value).format(
const value = new Formatter(v).format(
this.getOption("mapping.valueTemplate", ""),
);
......
......@@ -146,6 +146,10 @@ const resizeObserverSymbol = Symbol("resizeObserver");
* @fragments /fragments/components/layout/tabs/
*
* @example /examples/components/layout/tabs-simple
* @example /examples/components/layout/tabs-active
* @example /examples/components/layout/tabs-removable
*
* @issue https://localhost.alvine.dev:8443/development/issues/closed/268.html
*
* @since 3.74.0
* @copyright schukai GmbH
......@@ -172,6 +176,7 @@ class Tabs extends CustomElement {
* @property {string} labels.new-tab-label="New Tab"
* @property {Object} features
* @property {number} features.openDelay=500 Open delay in milliseconds
* @property {string} features.removeBehavior="auto" Remove behavior, auto (default), next, previous and none
* @property {Object} fetch Fetch [see Using Fetch mozilla.org](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch)
* @property {String} fetch.redirect=error
* @property {String} fetch.method=GET
......@@ -206,6 +211,7 @@ class Tabs extends CustomElement {
features: {
openDelay: null,
removeBehavior: "auto",
},
classes: {
......@@ -436,7 +442,8 @@ function showPopper() {
this[popperElementSymbol].style.removeProperty("visibility");
})
.run(undefined)
.then(() => {})
.then(() => {
})
.catch((e) => {
addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, e.message);
});
......@@ -510,7 +517,6 @@ function attachTabChangeObserver() {
* @private
* @return {Select}
* @external "external:createPopper"
* @see {@link Plugins}
*/
function initPopper() {
const self = this;
......@@ -685,6 +691,10 @@ function initEventHandler() {
const reference = button.getAttribute(
`${ATTRIBUTE_PREFIX}tab-reference`,
);
const previous = button.previousElementSibling;
const next = button.nextElementSibling;
if (reference) {
const container = this.querySelector(`[id=${reference}]`);
if (container instanceof HTMLElement) {
......@@ -695,7 +705,34 @@ function initEventHandler() {
});
}
}
switch (this.getOption("features.removeBehavior")) {
case "auto":
if (next instanceof HTMLButtonElement) {
next.click();
} else {
// get previous button
if (previous instanceof HTMLButtonElement) {
previous.click();
}
}
break;
case "next":
if (next instanceof HTMLButtonElement) {
next.click();
}
break;
case "previous":
if (previous instanceof HTMLButtonElement) {
previous.click();
}
break;
default: // and "none"
break;
}
}
}
};
......@@ -826,7 +863,7 @@ function initTabButtons() {
}
if (node.hasAttribute(`${ATTRIBUTE_PREFIX}button-icon`)) {
label = `<span part="label">${label}</span><img part="icon" src="${node.getAttribute(
label = `<span part="label">${label}</span><img part="icon" alt="this is an icon" src="${node.getAttribute(
`${ATTRIBUTE_PREFIX}button-icon`,
)}">`;
}
......@@ -871,7 +908,8 @@ function initTabButtons() {
}
})
.run(undefined)
.then(() => {})
.then(() => {
})
.catch((e) => {
addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, e.message);
});
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment