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

feat: new split-screen #171

parent 2b080938
No related branches found
No related tags found
No related merge requests found
Showing
with 626 additions and 1060 deletions
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>Form</title>
<script src="./main.js" type="module"></script>
</head>
<body>
<main>
<div id="dialog">
<monster-split-screen data-monster-option-splittype="horizontal">
<div slot="start">
<h1>Start Panel</h1>
<p>Some content</p>
<p>Some content</p>
</div>
<div slot="end">
<h1>End Panel</h1>
<p>Some content</p>
<p>Some content</p>
</div>
</monster-split-screen>
</div>
</main>
</body>
</html>
\ No newline at end of file
import "../../source/components/style/property.pcss";
import "../../source/components/style/normalize.pcss";
import "../../source/components/style/color.pcss";
import "../../source/components/style/theme.pcss";
import "../../source/components/style/typography.pcss";
import "../../source/components/style/form.pcss";
import "../../source/components/style/link.pcss";
import "../../source/components/style/button.pcss";
import "../../source/components/style/ripple.pcss";
import "../../source/components/layout/split-screen.mjs";
import "./main.pcss";
.dialog {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 1rem;
border: 1px solid #ccc;
border-radius: 5px;
}
\ No newline at end of file
...@@ -12,10 +12,11 @@ ...@@ -12,10 +12,11 @@
<main> <main>
<h1>Issue 142</h1> <h1>Issues 142</h1>
<p> <ul>
<a href="https://gitlab.schukai.com/oss/libraries/javascript/monster/-/issues/142" target="_blank">issues/142</a> <li><a href="https://gitlab.schukai.com/oss/libraries/javascript/monster/-/issues/142" target="_blank">issues/142</a>
</p> <li><a href="../../">Back to overview</a></li>
</ul>
<div style="width: 450px; margin: 0 auto;"> <div style="width: 450px; margin: 0 auto;">
<!-- <monster-select value="value2,value1">--> <!-- <monster-select value="value2,value1">-->
......
...@@ -28,9 +28,11 @@ ...@@ -28,9 +28,11 @@
<main> <main>
<h1>Issues</h1> <h1>Issues 144</h1>
<ul>
<h2>#144 Buttons in der button-bar nicht gleich hoch</h2> <li><a href="https://gitlab.schukai.com/oss/libraries/javascript/monster/-/issues/144" target="_blank">issues/144</a>
<li><a href="../../">Back to overview</a></li>
</ul>
<div style="width: 550px;margin: 0 auto;"> <div style="width: 550px;margin: 0 auto;">
<monster-button-bar style="width: 550px;" data-monster-option-popper-placement="right"> <monster-button-bar style="width: 550px;" data-monster-option-popper-placement="right">
......
...@@ -13,9 +13,10 @@ ...@@ -13,9 +13,10 @@
<main> <main>
<h1>Issues 152</h1> <h1>Issues 152</h1>
<p> <ul>
<a href="https://gitlab.schukai.com/oss/libraries/javascript/monster/-/issues/152" target="_blank">issues/152</a> <li><a href="https://gitlab.schukai.com/oss/libraries/javascript/monster/-/issues/152" target="_blank">issues/152</a>
</p> <li><a href="../../">Back to overview</a></li>
</ul>
<div style="width: 250px;margin: 0 auto;"> <div style="width: 250px;margin: 0 auto;">
......
...@@ -13,9 +13,10 @@ ...@@ -13,9 +13,10 @@
<main> <main>
<h1>Issues 152</h1> <h1>Issues 152</h1>
<p> <ul>
<a href="https://gitlab.schukai.com/oss/libraries/javascript/monster/-/issues/152" target="_blank">issues/152</a> <li> <a href="https://gitlab.schukai.com/oss/libraries/javascript/monster/-/issues/152" target="_blank">issues/152</a></li>
</p> <li><a href="../../">Back to overview</a></li>
</ul>
<div style="width: 250px;margin: 0 auto;"> <div style="width: 250px;margin: 0 auto;">
......
...@@ -13,10 +13,11 @@ ...@@ -13,10 +13,11 @@
<main> <main>
<h1>Issues 158</h1> <h1>Issues 158</h1>
<p> <ul>
<a href="https://gitlab.schukai.com/oss/libraries/javascript/monster/-/issues/158" <li><a href="https://gitlab.schukai.com/oss/libraries/javascript/monster/-/issues/158"
target="_blank">issues/158</a> target="_blank">issues/158</a></li>
</p> <li><a href="../../">Back to overview</a></li>
</ul>
<monster-datasource-rest id="data1" <monster-datasource-rest id="data1"
......
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>Form</title>
<script src="./main.js" type="module"></script>
</head>
<body>
<main>
<h1>Split Screen</h1>
<ul>
<li><a href="../">Back to overview</a></li>
</ul>
<div id="dialog" style="">
<monster-split-screen data-monster-option-splittype="horizontal" style="">
<div slot="start">
<h1>Start Panel</h1>
<p>Some content</p>
<p>Some content</p>
</div>
<div slot="end">
<monster-split-screen data-monster-option-splittype="vertical" style="">
<div slot="start">
<h1>Start Panel</h1>
<p>Some content</p>
<p>Some content</p>
</div>
<div slot="end">
<monster-split-screen data-monster-option-splittype="horizontal" style="">
<div slot="start">
<h1>Start Panel</h1>
<p>Some content</p>
<p>Some content</p>
</div>
<div slot="end">
<h1>End Panel</h1>
<p>Some content</p>
<p>Some content</p>
</div>
</monster-split-screen>
</div>
</monster-split-screen>
</div>
</monster-split-screen>
</div>
</main>
</body>
</html>
\ No newline at end of file
import "../../source/components/style/property.pcss";
import "../../source/components/style/normalize.pcss";
import "../../source/components/style/color.pcss";
import "../../source/components/style/theme.pcss";
import "../../source/components/style/typography.pcss";
import "../../source/components/style/form.pcss";
import "../../source/components/style/link.pcss";
import "../../source/components/style/button.pcss";
import "../../source/components/style/ripple.pcss";
import "../../source/components/layout/split-screen.mjs";
import "./main.pcss";
.dialog {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 1rem;
border: 1px solid #ccc;
border-radius: 5px;
}
\ No newline at end of file
...@@ -13,7 +13,9 @@ ...@@ -13,7 +13,9 @@
<main style="width:98vw"> <main style="width:98vw">
<h1>Tabs</h1> <h1>Tabs</h1>
<ul>
<li><a href="../">Back to overview</a></li>
</ul>
<monster-tabs <monster-tabs
......
...@@ -11,6 +11,6 @@ import "./main.pcss"; ...@@ -11,6 +11,6 @@ import "./main.pcss";
import "../../source/components/form/tabs.mjs"; import "../../source/components/layout/tabs.mjs";
...@@ -35,6 +35,12 @@ const playgroundDir = join(rootDir, 'playground') ...@@ -35,6 +35,12 @@ const playgroundDir = join(rootDir, 'playground')
console.log(rootDir) console.log(rootDir)
function buildStylesheets() {
debugger
execSync('build-stylesheets', {cwd: rootDir, stdio: 'inherit'})
}
export default defineConfig({ export default defineConfig({
clearScreen: false, clearScreen: false,
...@@ -61,8 +67,16 @@ export default defineConfig({ ...@@ -61,8 +67,16 @@ export default defineConfig({
directoryIndex({ }), directoryIndex({ }),
viteMockServe({ viteMockServe({
mockPath:playgroundDir+ "/mock", // Der Pfad zu Ihren Mock-Dateien mockPath:playgroundDir+ "/mock",
}) }),
{
name: 'postbuild-commands',
closeBundle: async () => {
await buildStylesheets()
},
},
], ],
css: { css: {
......
...@@ -4,134 +4,14 @@ ...@@ -4,134 +4,14 @@
* This file is licensed under the AGPLv3 License. * This file is licensed under the AGPLv3 License.
* License text available at https://www.gnu.org/licenses/agpl-3.0.en.html * License text available at https://www.gnu.org/licenses/agpl-3.0.en.html
*/ */
import { instanceSymbol } from "../../constants.mjs"; import { Tabs as NewTabs } from "../layout/tabs.mjs";
import { createPopper } from "@popperjs/core";
import { extend } from "../../data/extend.mjs";
import { Pathfinder } from "../../data/pathfinder.mjs";
import { import {
addAttributeToken,
addToObjectLink,
hasObjectLink,
} from "../../dom/attributes.mjs";
import {
ATTRIBUTE_ERRORMESSAGE,
ATTRIBUTE_PREFIX,
ATTRIBUTE_ROLE,
} from "../../dom/constants.mjs";
import {
assembleMethodSymbol,
CustomElement,
getSlottedElements,
registerCustomElement, registerCustomElement,
} from "../../dom/customelement.mjs"; } from "../../dom/customelement.mjs";
import {
findTargetElementFromEvent,
fireCustomEvent,
} from "../../dom/events.mjs";
import { getDocument } from "../../dom/util.mjs";
import { random } from "../../math/random.mjs";
import { getGlobal } from "../../types/global.mjs";
import { ID } from "../../types/id.mjs";
import { isArray, isString } from "../../types/is.mjs";
import { TokenList } from "../../types/tokenlist.mjs";
import { clone } from "../../util/clone.mjs";
import { DeadMansSwitch } from "../../util/deadmansswitch.mjs";
import { Processing } from "../../util/processing.mjs";
import {
ATTRIBUTE_BUTTON_LABEL,
ATTRIBUTE_FORM_RELOAD,
ATTRIBUTE_FORM_URL,
STYLE_DISPLAY_MODE_BLOCK,
} from "./constants.mjs";
import { TabsStyleSheet } from "./stylesheet/tabs.mjs";
import { loadAndAssignContent } from "./util/fetch.mjs";
import { ThemeStyleSheet } from "../stylesheet/theme.mjs";
import {
popperInstanceSymbol,
setEventListenersModifiers,
} from "./util/popper.mjs";
export { Tabs }; export { Tabs };
/**
* @private
* @type {symbol}
*/
const popperElementSymbol = Symbol("popperElement");
/**
* @private
* @type {symbol}
*/
const popperNavElementSymbol = Symbol("popperNavElement");
/**
* @private
* @type {symbol}
*/
const controlElementSymbol = Symbol("controlElement");
/**
* @private
* @type {symbol}
*/
const navElementSymbol = Symbol("navElement");
/**
* @private
* @type {symbol}
*/
const switchElementSymbol = Symbol("switchElement");
/**
* @private
* @type {symbol}
*/
const changeTabEventHandler = Symbol("changeTabEventHandler");
/**
* @private
* @type {symbol}
*/
const removeTabEventHandler = Symbol("removeTabEventHandler");
/**
* @private
* @type {symbol}
*/
const popperSwitchEventHandler = Symbol("popperSwitchEventHandler");
/**
* local symbol
* @private
* @type {symbol}
*/
const closeEventHandler = Symbol("closeEventHandler");
/**
* @private
* @type {symbol}
*/
const mutationObserverSymbol = Symbol("mutationObserver");
/**
* @private
* @type {symbol}
*/
const dimensionsSymbol = Symbol("dimensions");
/**
* @private
* @type {symbol}
*/
const timerCallbackSymbol = Symbol("timerCallback");
/**
* local symbol
* @private
* @type {symbol}
*/
const resizeObserverSymbol = Symbol("resizeObserver");
/** /**
* This CustomControl creates a tab element with a variety of options. * This CustomControl creates a tab element with a variety of options.
* *
...@@ -162,7 +42,8 @@ const resizeObserverSymbol = Symbol("resizeObserver"); ...@@ -162,7 +42,8 @@ const resizeObserverSymbol = Symbol("resizeObserver");
* skinparam shadowing false * skinparam shadowing false
* HTMLElement <|-- CustomElement * HTMLElement <|-- CustomElement
* CustomElement <|-- CustomControl * CustomElement <|-- CustomControl
* CustomControl <|-- Tabs * CustomControl <|-- NewTabs
* NewTabs <|-- Tabs
* @enduml * @enduml
* *
* @since 1.10.0 * @since 1.10.0
...@@ -170,907 +51,10 @@ const resizeObserverSymbol = Symbol("resizeObserver"); ...@@ -170,907 +51,10 @@ const resizeObserverSymbol = Symbol("resizeObserver");
* @memberOf Monster.Components.Form * @memberOf Monster.Components.Form
* @summary A configurable tab control * @summary A configurable tab control
* @fires Monster.Components.Form.event:monster-fetched * @fires Monster.Components.Form.event:monster-fetched
* @deprecated since 3.59.0 use {@link Monster.Components.Layout.Tabs}
*/ */
class Tabs extends CustomElement { class Tabs extends NewTabs {
/**
* This method is called by the `instanceof` operator.
* @returns {symbol}
* @since 2.1.0
*/
static get [instanceSymbol]() {
return Symbol.for("@schukai/monster/components/form/tabs");
}
/**
* 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
* @property {string} labels.new-tab-label="New Tab"
* @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
* @property {String} fetch.mode=same-origin
* @property {String} fetch.credentials=same-origin
* @property {Object} fetch.headers={"accept":"text/html"}}
* @property {Object} popper [PopperJS Options](https://popper.js.org/docs/v2/)
* @property {string} popper.placement=bottom PopperJS placement
* @property {Object[]} modifiers={name:offset} PopperJS placement
*/
get defaults() {
return Object.assign({}, super.defaults, {
templates: {
main: getTemplate(),
},
labels: {
"new-tab-label": "New Tab",
},
buttons: {
standard: [],
popper: [],
},
fetch: {
redirect: "error",
method: "GET",
mode: "same-origin",
credentials: "same-origin",
headers: {
accept: "text/html",
},
},
classes: {
button: "monster-theme-primary-1",
popper: "monster-theme-primary-1",
},
popper: {
placement: "bottom",
modifiers: [
{
name: "offset",
options: {
offset: [0, 2],
},
},
{
name: "eventListeners",
enabled: false,
},
],
},
});
}
/**
* This method is called internal and should not be called directly.
*/
[assembleMethodSymbol]() {
super[assembleMethodSymbol]();
initControlReferences.call(this);
this[dimensionsSymbol] = new Pathfinder({ data: {} });
initEventHandler.call(this);
// setup structure
initTabButtons.call(this).then(() => {
initPopperSwitch.call(this);
initPopper.call(this);
attachResizeObserver.call(this);
attachTabChangeObserver.call(this);
});
}
/**
* This method is called internal and should not be called directly.
*
* @return {CSSStyleSheet[]}
*/
static getCSSStyleSheet() {
return [TabsStyleSheet, ThemeStyleSheet];
}
/**
* This method is called internal and should not be called directly.
*
* @return {string}
*/
static getTag() {
return "monster-tabs";
}
/**
* This method is called by the dom and should not be called directly.
*
* @return {void}
*/
connectedCallback() {
super.connectedCallback();
const document = getDocument();
for (const [, type] of Object.entries(["click", "touch"])) {
// close on outside ui-events
document.addEventListener(type, this[closeEventHandler]);
}
}
/**
* This method is called by the dom and should not be called directly.
*
* @return {void}
*/
disconnectedCallback() {
super.disconnectedCallback();
const document = getDocument();
// close on outside ui-events
for (const [, type] of Object.entries(["click", "touch"])) {
document.removeEventListener(type, this[closeEventHandler]);
}
}
}
/**
* @private
*/
function initPopperSwitch() {
const nodes = getSlottedElements.call(this, `[${ATTRIBUTE_ROLE}="switch"]`); // null ↦ only unnamed slots
let switchButton;
if (nodes.size === 0) {
switchButton = document.createElement("button");
switchButton.setAttribute(ATTRIBUTE_ROLE, "switch");
switchButton.setAttribute("part", "switch");
switchButton.classList.add("hidden");
const classList = this.getOption("classes.button");
if (classList) {
switchButton.classList.add(classList);
}
switchButton.innerHTML =
'<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16"><path d="M9.5 13a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0zm0-5a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0zm0-5a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0z"/></svg>';
this[navElementSymbol].prepend(switchButton);
} else {
switchButton = nodes.next();
}
/**
* @param {Event} event
*/
this[popperSwitchEventHandler] = (event) => {
const element = findTargetElementFromEvent(event, ATTRIBUTE_ROLE, "switch");
if (element instanceof HTMLButtonElement) {
togglePopper.call(this);
}
};
for (const type of ["click", "touch"]) {
switchButton.addEventListener(type, this[popperSwitchEventHandler]);
}
this[switchElementSymbol] = switchButton;
}
/**
* @private
*/
function hidePopper() {
if (!this[popperInstanceSymbol]) {
return;
}
this[popperElementSymbol].style.display = "none";
// performance https://popper.js.org/docs/v2/tutorial/#performance
setEventListenersModifiers.call(this, false);
}
/**
* @private
*/
function showPopper() {
if (this[popperElementSymbol].style.display === STYLE_DISPLAY_MODE_BLOCK) {
return;
}
this[popperElementSymbol].style.visibility = "hidden";
this[popperElementSymbol].style.display = STYLE_DISPLAY_MODE_BLOCK;
// performance https://popper.js.org/docs/v2/tutorial/#performance
setEventListenersModifiers.call(this, true);
this[popperInstanceSymbol].update();
new Processing(() => {
this[popperElementSymbol].style.removeProperty("visibility");
})
.run(undefined)
.then(() => {})
.catch((e) => {
addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, e.message);
});
}
/**
* @private
*/
function togglePopper() {
if (this[popperElementSymbol].style.display === STYLE_DISPLAY_MODE_BLOCK) {
hidePopper.call(this);
} else {
showPopper.call(this);
}
}
/**
* @private
*/
function attachResizeObserver() {
// 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, () => {
this[dimensionsSymbol].setVia("data.calculated", false);
checkAndRearrangeButtons.call(this);
});
});
this[resizeObserverSymbol].observe(this[navElementSymbol]);
}
/**
* @private
*/
function attachTabChangeObserver() {
// against flickering
new MutationObserver((mutations) => {
let runUpdate = false;
for (const mutation of mutations) {
if (mutation.type === "childList") {
if (
mutation.addedNodes.length > 0 ||
mutation.removedNodes.length > 0
) {
runUpdate = true;
break;
}
}
}
if (runUpdate === true) {
this[dimensionsSymbol].setVia("data.calculated", false);
initTabButtons.call(this);
}
}).observe(this, {
childList: true,
});
}
/**
* @private
* @return {Select}
* @external "external:createPopper"
* @see {@link Plugins}
*/
function initPopper() {
const self = this;
const options = extend({}, self.getOption("popper"));
self[popperInstanceSymbol] = createPopper(
self[switchElementSymbol],
self[popperElementSymbol],
options,
);
const observer1 = new MutationObserver(function (mutations) {
let runUpdate = false;
for (const mutation of mutations) {
if (mutation.type === "childList") {
if (
mutation.addedNodes.length > 0 ||
mutation.removedNodes.length > 0
) {
runUpdate = true;
break;
}
}
}
if (runUpdate === true) {
self[popperInstanceSymbol].update();
}
});
observer1.observe(self[popperNavElementSymbol], {
childList: true,
subtree: true,
});
return self;
}
/**
* @private
* @param {HTMLElement} element
*/
function show(element) {
if (!this.shadowRoot) {
throw new Error("no shadow-root is defined");
}
const reference = element.getAttribute(`${ATTRIBUTE_PREFIX}tab-reference`);
const nodes = getSlottedElements.call(this);
for (const node of nodes) {
const id = node.getAttribute("id");
if (id === reference) {
node.classList.add("active");
// get all data- from button and filter out data-monster-attributes and data-monster-insert
const data = {};
const mask = [
"data-monster-attributes",
"data-monster-insert-reference",
"data-monster-state",
"data-monster-button-label",
"data-monster-objectlink",
"data-monster-role",
];
for (const [, attr] of Object.entries(node.attributes)) {
if (attr.name.startsWith("data-") && mask.indexOf(attr.name) === -1) {
data[attr.name] = attr.value;
}
}
if (node.hasAttribute(ATTRIBUTE_FORM_URL)) {
const url = node.getAttribute(ATTRIBUTE_FORM_URL);
if (
!node.hasAttribute(ATTRIBUTE_FORM_RELOAD) ||
node.getAttribute(ATTRIBUTE_FORM_RELOAD).toLowerCase() === "onshow"
) {
node.removeAttribute(ATTRIBUTE_FORM_URL);
}
const options = this.getOption("fetch", {});
const filter = undefined;
loadAndAssignContent(node, url, options, filter)
.then(() => {
fireCustomEvent(this, "monster-tab-changed", {
reference,
});
})
.catch((e) => {
addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, e.message);
});
} else {
fireCustomEvent(this, "monster-tab-changed", {
reference,
data,
});
}
} else {
node.classList.remove("active");
}
}
const standardButtons = this.getOption("buttons.standard");
for (const index in standardButtons) {
const button = standardButtons[index];
const state = button["reference"] === reference ? "active" : "inactive";
this.setOption(`buttons.standard.${index}.state`, state);
}
const popperButton = this.getOption("buttons.popper");
for (const index in popperButton) {
const button = popperButton[index];
const state = button["reference"] === reference ? "active" : "inactive";
this.setOption(`buttons.popper.${index}.state`, state);
}
hidePopper.call(this);
}
/**
* @private
*/
function initEventHandler() {
if (!this.shadowRoot) {
throw new Error("no shadow-root is defined");
}
/**
* @param {Event} event
*/
this[changeTabEventHandler] = (event) => {
const element = findTargetElementFromEvent(event, ATTRIBUTE_ROLE, "button");
if (element instanceof HTMLButtonElement && element.disabled !== true) {
show.call(this, element);
}
};
/**
* event:monster-tab-remove
* @event Monster.Components.Form.event:monster-tab-remove
*/
/**
* @param {Event} event
* @fires Monster.Components.Form.event:monster-tab-remove
*/
this[removeTabEventHandler] = (event) => {
const element = findTargetElementFromEvent(
event,
ATTRIBUTE_ROLE,
"remove-tab",
);
if (element instanceof HTMLElement) {
const button = findTargetElementFromEvent(
event,
ATTRIBUTE_ROLE,
"button",
);
if (button instanceof HTMLButtonElement && button.disabled !== true) {
const reference = button.getAttribute(
`${ATTRIBUTE_PREFIX}tab-reference`,
);
if (reference) {
const container = this.querySelector(`[id=${reference}]`);
if (container instanceof HTMLElement) {
container.remove();
initTabButtons.call(this);
fireCustomEvent(this, "monster-tab-remove", {
reference,
});
}
}
}
}
};
this[navElementSymbol].addEventListener("touch", this[changeTabEventHandler]);
this[navElementSymbol].addEventListener("click", this[changeTabEventHandler]);
this[navElementSymbol].addEventListener("touch", this[removeTabEventHandler]);
this[navElementSymbol].addEventListener("click", this[removeTabEventHandler]);
/**
* @param {Event} event
*/
this[closeEventHandler] = (event) => {
const path = event.composedPath();
for (const [, element] of Object.entries(path)) {
if (element === this) {
return;
}
}
hidePopper.call(this);
};
return this;
}
/**
* @private
* @param observedNode
*/
function attachTabMutationObserver(observedNode) {
const self = this;
if (hasObjectLink(observedNode, mutationObserverSymbol)) {
return;
}
/**
* this construct monitors a node whether it is disabled or modified
* @type {MutationObserver}
*/
const observer = new MutationObserver(function (mutations) {
if (isArray(mutations)) {
const mutation = mutations.pop();
if (mutation instanceof MutationRecord) {
initTabButtons.call(self);
}
}
});
observer.observe(observedNode, {
childList: false,
attributes: true,
subtree: false,
attributeFilter: [
"disabled",
ATTRIBUTE_BUTTON_LABEL,
`${ATTRIBUTE_PREFIX}button-icon`,
],
});
addToObjectLink(observedNode, mutationObserverSymbol, observer);
}
/**
* @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(
`[${ATTRIBUTE_ROLE}=control]`,
);
this[navElementSymbol] = this.shadowRoot.querySelector(
`nav[${ATTRIBUTE_ROLE}=nav]`,
);
this[popperElementSymbol] = this.shadowRoot.querySelector(
`[${ATTRIBUTE_ROLE}=popper]`,
);
this[popperNavElementSymbol] = this.shadowRoot.querySelector(
`[${ATTRIBUTE_ROLE}=popper-nav]`,
);
}
/**
* @private
* @return {Promise<unknown>}
* @throws {Error} no shadow-root is defined
*
*/
function initTabButtons() {
if (!this.shadowRoot) {
throw new Error("no shadow-root is defined");
}
let activeReference;
const dimensionsCalculated = this[dimensionsSymbol].getVia(
"data.calculated",
false,
);
const buttons = [];
const nodes = getSlottedElements.call(this, undefined, null); // null ↦ only unnamed slots
for (const node of nodes) {
if (!(node instanceof HTMLElement)) continue;
let label = getButtonLabel.call(this, node);
let reference;
if (node.hasAttribute("id")) {
reference = node.getAttribute("id");
}
let disabled;
if (node.hasAttribute("disabled") || node.disabled === true) {
disabled = true;
}
if (!reference) {
reference = new ID("tab").toString();
node.setAttribute("id", reference);
}
if (node.hasAttribute(`${ATTRIBUTE_PREFIX}button-icon`)) {
label = `<span part="label">${label}</span><img part="icon" src="${node.getAttribute(
`${ATTRIBUTE_PREFIX}button-icon`,
)}">`;
}
let remove = false;
if (node.hasAttribute(`${ATTRIBUTE_PREFIX}removable`)) {
remove = true;
}
if (node.matches(".active") === true && disabled !== true) {
node.classList.remove("active");
activeReference = reference;
}
const state = "";
const classes = dimensionsCalculated ? "" : "invisible";
buttons.push({
reference,
label,
state,
class: classes,
disabled,
remove,
});
attachTabMutationObserver.call(this, node);
}
this.setOption("buttons.standard", clone(buttons));
this.setOption("buttons.popper", []);
this.setOption("marker", random());
return adjustButtonVisibility.call(this).then(() => {
if (activeReference) {
return new Processing(() => {
const button = this.shadowRoot.querySelector(
`[${ATTRIBUTE_PREFIX}tab-reference="${activeReference}"]`,
);
if (button instanceof HTMLButtonElement && button.disabled !== true) {
show.call(this, button);
}
})
.run(undefined)
.then(() => {})
.catch((e) => {
addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, e.message);
});
}
return Promise.resolve();
});
}
function checkAndRearrangeButtons() {
if (this[dimensionsSymbol].getVia("data.calculated", false) !== true) {
calculateNavigationButtonsDimensions.call(this);
}
rearrangeButtons.call(this);
}
/**
* @private
* @return {Promise<unknown>}
*/
function adjustButtonVisibility() {
const self = this;
return new Promise((resolve) => {
const observer = new MutationObserver(function (mutations) {
const defCount = self.getOption("buttons.standard").length;
const domCount = self[navElementSymbol].querySelectorAll(
'button[data-monster-role="button"]',
).length;
// in drawing
if (defCount !== domCount) return;
observer.disconnect();
checkAndRearrangeButtons.call(self);
resolve();
});
observer.observe(self[navElementSymbol], {
attributes: true,
});
});
}
/**
* @private
* @param {string} value
* @return {number}
*/
function getDimValue(value) {
if ([undefined, null].indexOf(value) !== -1) {
return 0;
}
const valueAsInt = parseInt(value, 10);
if (isNaN(valueAsInt)) {
return 0;
}
return valueAsInt;
}
/**
* @private
* @param {HTMLElement} node
* @return {number}
*/
function calcBoxWidth(node) {
const dim = getGlobal("window").getComputedStyle(node);
const bounding = node.getBoundingClientRect();
return (
getDimValue(dim["border-left-width"]) +
getDimValue(dim["padding-left"]) +
getDimValue(dim["margin-left"]) +
getDimValue(bounding["width"]) +
getDimValue(dim["border-right-width"]) +
getDimValue(dim["margin-right"]) +
getDimValue(dim["padding-left"])
);
}
/**
* @private
* @return {Object}
*/
function rearrangeButtons() {
const standardButtons = [];
const popperButtons = [];
let sum = 0;
const space = this[dimensionsSymbol].getVia("data.space");
const buttons = this.getOption("buttons.standard");
for (const [, button] of buttons.entries()) {
const ref = button?.reference;
sum += this[dimensionsSymbol].getVia(`data.button.${ref}`);
if (sum > space) {
popperButtons.push(clone(button));
} else {
standardButtons.push(clone(button));
}
}
this.setOption("buttons.standard", standardButtons);
this.setOption("buttons.popper", popperButtons);
if (this[switchElementSymbol]) {
if (popperButtons.length > 0) {
this[switchElementSymbol].classList.remove("hidden");
} else {
this[switchElementSymbol].classList.add("hidden");
}
}
}
/**
* @private
* @return {Object}
*/
function calculateNavigationButtonsDimensions() {
const width = this[navElementSymbol].getBoundingClientRect().width;
let startEndWidth = 0;
getSlottedElements.call(this, undefined, "start").forEach((node) => {
startEndWidth += calcBoxWidth.call(this, node);
});
getSlottedElements.call(this, undefined, "end").forEach((node) => {
startEndWidth += calcBoxWidth.call(this, node);
});
this[dimensionsSymbol].setVia("data.space", width - startEndWidth - 2);
this[dimensionsSymbol].setVia("data.visible", !(width === 0));
const buttons = this.getOption("buttons.standard").concat(
this.getOption("buttons.popper"),
);
for (const [i, button] of buttons.entries()) {
const ref = button?.reference;
const element = this[navElementSymbol].querySelector(
`:scope > [${ATTRIBUTE_PREFIX}tab-reference="${ref}"]`,
);
if (!(element instanceof HTMLButtonElement)) continue;
this[dimensionsSymbol].setVia(
`data.button.${ref}`,
calcBoxWidth.call(this, element),
);
button["class"] = new TokenList(button["class"])
.remove("invisible")
.toString();
}
const slots = this[controlElementSymbol].querySelectorAll(
`nav[${ATTRIBUTE_PREFIX}role=nav] > slot.invisible, slot[${ATTRIBUTE_PREFIX}role=slot].invisible`,
);
for (const [, slot] of slots.entries()) {
slot.classList.remove("invisible");
}
this[dimensionsSymbol].setVia("data.calculated", true);
this.setOption("buttons.standard", clone(buttons));
}
/**
* @private
* @param {HTMLElement} node
* @return {string}
*/
function getButtonLabel(node) {
let label;
let setLabel = false;
if (node.hasAttribute(ATTRIBUTE_BUTTON_LABEL)) {
label = node.getAttribute(ATTRIBUTE_BUTTON_LABEL);
} else {
label = node.innerText;
setLabel = true;
}
if (!isString(label)) {
label = "";
}
label = label.trim();
if (label === "") {
label = this.getOption("labels.new-tab-label", "New Tab");
}
if (label.length > 100) {
label = `${label.substring(0, 99)}…`;
}
if (setLabel === true) {
node.setAttribute(ATTRIBUTE_BUTTON_LABEL, label);
}
return label;
}
/**
* @private
* @return {string}
*/
function getTemplate() {
// language=HTML
return `
<template id="buttons">
<button part="button"
data-monster-role="button"
data-monster-attributes="
class path:classes.button,
data-monster-state path:buttons.state,
disabled path:buttons.disabled | if:true,
data-monster-tab-reference path:buttons.reference"><span
data-monster-replace="path:buttons.label"></span><span part="remove-tab"
data-monster-attributes="class path:buttons.remove | ?:remove-tab:hidden "
data-monster-role="remove-tab"
tabindex="-1"></span></button>
</template>
<div data-monster-role="control" part="control">
<nav data-monster-role="nav" part="nav"
data-monster-attributes="data-monster-marker path:marker"
data-monster-insert="buttons path:buttons.standard">
<slot name="start" class="invisible"></slot>
<div data-monster-role="popper" part="popper" tabindex="-1"
data-monster-attributes="class path:classes.popper">
<div data-popper-arrow></div>
<div part="popper-nav" data-monster-role="popper-nav"
data-monster-insert="buttons path:buttons.popper"
tabindex="-1"></div>
</div>
<slot name="popper" class="invisible"></slot>
<slot name="end" class="invisible"></slot>
</nav>
<slot data-monster-role="slot" class="invisible"></slot>
</div>`;
} }
registerCustomElement(Tabs); registerCustomElement(Tabs);
...@@ -106,7 +106,7 @@ class Overlay extends CustomElement { ...@@ -106,7 +106,7 @@ class Overlay extends CustomElement {
* @returns {symbol} * @returns {symbol}
*/ */
static get [instanceSymbol]() { static get [instanceSymbol]() {
return Symbol.for("@schukai/component-host/overlay@@instance"); return Symbol.for("@schukai/monster/components/host/overlay@@instance");
} }
/** /**
......
...@@ -39,18 +39,10 @@ const viewerElementSymbol = Symbol("viewerElement"); ...@@ -39,18 +39,10 @@ const viewerElementSymbol = Symbol("viewerElement");
* Or you can create this CustomControl directly in Javascript: * Or you can create this CustomControl directly in Javascript:
* *
* ```js * ```js
* import '@schukai/component-state/source/viewer.mjs'; * import '@schukai/monster/components/host/viewer.mjs';
* document.createElement('monster-viewer'); * document.createElement('monster-viewer');
* ``` * ```
* *
* The Body should have a class "hidden" to ensure that the styles are applied correctly.
*
* ```css
* body.hidden {
* visibility: hidden;
* }
* ```
*
* @startuml viewer.png * @startuml viewer.png
* skinparam monochrome true * skinparam monochrome true
* skinparam shadowing false * skinparam shadowing false
...@@ -62,10 +54,6 @@ const viewerElementSymbol = Symbol("viewerElement"); ...@@ -62,10 +54,6 @@ const viewerElementSymbol = Symbol("viewerElement");
* @copyright schukai GmbH * @copyright schukai GmbH
* @memberOf Monster.Components.Host * @memberOf Monster.Components.Host
* @summary A simple viewer component * @summary A simple viewer component
* @fires Monster.Components.Host.Viewer.event:monster-viewer-before-open
* @fires Monster.Components.Host.Viewer.event:monster-viewer-open
* @fires Monster.Components.Host.Viewer.event:monster-viewer-before-close
* @fires Monster.Components.Host.Viewer.event:monster-viewer-closed
*/ */
class Viewer extends CustomElement { class Viewer extends CustomElement {
/** /**
...@@ -73,7 +61,7 @@ class Viewer extends CustomElement { ...@@ -73,7 +61,7 @@ class Viewer extends CustomElement {
* @returns {symbol} * @returns {symbol}
*/ */
static get [instanceSymbol]() { static get [instanceSymbol]() {
return Symbol.for("@schukai/component-host/viewer@@instance"); return Symbol.for("@schukai/monster/components/host/viewer@@instance");
} }
/** /**
......
/**
* Copyright 2023 schukai GmbH
* SPDX-License-Identifier: AGPL-3.0
*/
/**
* Namespace for all layout related functions.
*
* @namespace Monster.Components.Layout
* @memberOf Monster
* @author schukai GmbH
*/
const ns = {};
/**
* Copyright 2023 schukai GmbH
* SPDX-License-Identifier: AGPL-3.0
*/
import {
assembleMethodSymbol,
CustomElement,
registerCustomElement,
} from "../../dom/customelement.mjs";
import "../notify/notify.mjs";
import {Observer} from "../../types/observer.mjs";
import {SplitScreenStyleSheet} from "./stylesheet/split-screen.mjs";
import {instanceSymbol} from "../../constants.mjs";
import {internalSymbol} from "../../constants.mjs";
export {SplitScreen, TYPE_VERTICAL, TYPE_HORIZONTAL};
/**
* @private
* @type {symbol}
*/
const splitScreenElementSymbol = Symbol("splitScreenElement");
/**
* @private
* @type {symbol}
*/
const draggerElementSymbol = Symbol("draggerElement");
/**
* @private
* @type {symbol}
*/
const startPanelElementSymbol = Symbol("startPanelElement");
/**
* @private
* @type {symbol}
*/
const endPanelElementSymbol = Symbol("endPanelElement");
/**
* @private
* @type {symbol}
*/
const handleElementSymbol = Symbol("handleElement");
/**
*
* @type {string}
*/
const TYPE_VERTICAL = "vertical";
/**
*
* @type {string}
*/
const TYPE_HORIZONTAL = "horizontal";
/**
* The Viewer component is used to show a PDF, HTML or Image.
*
* <img src="./images/splitscreen.png">
*
* You can create this control either by specifying the HTML tag <monster-splitscreen />` directly in the HTML or using
* Javascript via the `document.createElement('monster-split-screen');` method.
*
* ```html
* <monster-split-screen></monster-split-screen>
* ```
*
* Or you can create this CustomControl directly in Javascript:
*
* ```js
* import '@schukai/monster/components/layout/split-screen.mjs';
* document.createElement('monster-split-screen');
* ```
*
* @startuml splitscreen.png
* skinparam monochrome true
* skinparam shadowing false
* HTMLElement <|-- CustomElement
* CustomElement <|-- CustomControl
* CustomControl <|-- SplitScreen
* @enduml
*
* @copyright schukai GmbH
* @memberOf Monster.Components.Layout
* @summary A simple split screen layout
*/
class SplitScreen extends CustomElement {
/**
* This method is called by the `instanceof` operator.
* @returns {symbol}
*/
static get [instanceSymbol]() {
return Symbol.for("@schukai/monster/components/layout/splitscreen");
}
/**
* 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 {Object} features Feature definitions
*/
get defaults() {
return Object.assign({}, super.defaults, {
templates: {
main: getTemplate(),
},
splitType: TYPE_VERTICAL,
dimension: "20%",
classes: {},
features: {},
});
}
setContent(html) {
this.setOption("content", html);
return this;
}
/**
*
* @returns {Monster.Components.Host.Viewer}
*/
[assembleMethodSymbol]() {
super[assembleMethodSymbol]();
initControlReferences.call(this);
initEventHandler.call(this);
applyPanelDimensions.call(this);
}
setDimension(dimension) {
// check if percent and greater than100
if (dimension.includes("%")) {
if (parseInt(dimension) > 100) {
throw new Error("dimension must be less than 100%");
} else if (parseInt(dimension) < 0) {
throw new Error("dimension must be greater than 0%");
}
}
this.setOption("dimension", dimension);
return this;
}
/**
*
* @return {string}
*/
static getTag() {
return "monster-split-screen";
}
/**
* @return {CSSStyleSheet[]}
*/
static getCSSStyleSheet() {
return [SplitScreenStyleSheet];
}
}
/**
* Set the dimensions of the panel based on the split type.
*/
function applyPanelDimensions() {
const splitType = this.getOption("splitType");
const dimension = this.getOption("dimension");
if (splitType === TYPE_VERTICAL) {
this[startPanelElementSymbol].style.width = dimension;
this[endPanelElementSymbol].style.width = `calc(100% - ${dimension} - 5px)`;
this[draggerElementSymbol].style.cursor = "ew-resize";
this[splitScreenElementSymbol].classList.add("vertical");
this[splitScreenElementSymbol].classList.remove("horizontal");
} else {
this[startPanelElementSymbol].style.height = dimension;
this[endPanelElementSymbol].style.height = `calc(100% - ${dimension} - 5px)`;
this[draggerElementSymbol].style.cursor = "ns-resize";
this[splitScreenElementSymbol].classList.add("horizontal");
this[splitScreenElementSymbol].classList.remove("vertical");
}
}
/**
* @private
* @return {Select}
* @throws {Error} no shadow-root is defined
*/
function initControlReferences() {
if (!this.shadowRoot) {
throw new Error("no shadow-root is defined");
}
this[splitScreenElementSymbol] = this.shadowRoot.querySelector("[data-monster-role=split-screen]");
this[draggerElementSymbol] = this.shadowRoot.querySelector("[data-monster-role=dragger]");
this[handleElementSymbol] = this.shadowRoot.querySelector("[data-monster-role=handle]");
this[startPanelElementSymbol] = this.shadowRoot.querySelector("[data-monster-role=startPanel]");
this[endPanelElementSymbol] = this.shadowRoot.querySelector("[data-monster-role=endPanel]");
}
/**
* @private
*/
function initEventHandler() {
const self = this;
this[internalSymbol].getSubject().isDragging = false;
this[draggerElementSymbol].addEventListener('dblclick', () => {
self[internalSymbol].getSubject().isDragging = false;
applyPanelDimensions.call(this);
});
const resizeObserver = new ResizeObserver((entries) => {
for (let entry of entries) {
console.log(entry.contentRect.width);
}
});
resizeObserver.observe(this[splitScreenElementSymbol]);
this[draggerElementSymbol].addEventListener('mousedown', () => {
self[internalSymbol].getSubject().isDragging = true;
document.addEventListener('mousemove', (e) => {
e.preventDefault();
if (!self[internalSymbol].getSubject().isDragging) {
return;
}
if (self.getOption("splitType") === TYPE_HORIZONTAL) {
const containerOffsetTop = self[splitScreenElementSymbol].offsetTop;
const topPanel = self[startPanelElementSymbol];
const bottomPanel = self[endPanelElementSymbol];
const newTopHeight = e.clientY - containerOffsetTop;
topPanel.style.height = `${newTopHeight}px`;
bottomPanel.style.height = `calc(100% - ${newTopHeight}px - 5px)`; // 5px is dragger height
} else {
const containerOffsetLeft = self[splitScreenElementSymbol].offsetLeft;
const leftPanel = self[startPanelElementSymbol];
const rightPanel = self[endPanelElementSymbol];
const newLeftWidth = e.clientX - containerOffsetLeft;
leftPanel.style.width = `${newLeftWidth}px`;
rightPanel.style.width = `calc(100% - ${newLeftWidth}px - 5px)`; // 5px is dragger width
}
});
document.addEventListener('mouseup', (e) => {
self[internalSymbol].getSubject().isDragging = false;
document.removeEventListener('mousemove', (e) => {
if (!self[internalSymbol].getSubject().isDragging) {
return;
}
if (self.getOption("splitType") === TYPE_VERTICAL) {
const containerOffsetTop = self[splitScreenElementSymbol].offsetTop;
const topPanel = self[startPanelElementSymbol];
const bottomPanel = self[endPanelElementSymbol];
const newTopHeight = e.clientY - containerOffsetTop;
topPanel.style.height = `${newTopHeight}px`;
bottomPanel.style.height = `calc(100% - ${newTopHeight}px - 5px)`; // 5px is dragger height
} else {
const containerOffsetLeft = self[splitScreenElementSymbol].offsetLeft;
const newLeftWidth = e.clientX - containerOffsetLeft;
const leftPanel = self[startPanelElementSymbol];
const rightPanel = self[endPanelElementSymbol];
leftPanel.style.width = `${newLeftWidth}px`;
rightPanel.style.width = `calc(100% - ${newLeftWidth}px - 5px)`; // 5px is dragger width
}
});
document.removeEventListener('mouseup', (e) => {
self[internalSymbol].getSubject().isDragging = false;
});
});
});
let lastDimension = this.getOption("dimension");
let lastType = this.getOption("splitType");
this[internalSymbol].attachObserver(
new Observer(() => {
if (lastDimension !== this.getOption("dimension")) {
lastDimension = this.getOption("dimension");
applyPanelDimensions.call(this);
}
if (lastType !== this.getOption("splitType")) {
lastType = this.getOption("splitType");
applyPanelDimensions.call(this);
}
}));
return this;
}
/**
* @private
* @return {string}
*/
function getTemplate() {
// language=HTML
return `
<div data-monster-role="split-screen" part="container">
<div data-monster-role="startPanel" class="panel" part="startPanel">
<slot name="start"></slot>
</div>
<div data-monster-role="dragger" part="dragger">
<div data-monster-role="handle"></div>
</div>
<div data-monster-role="endPanel" class="panel" part="endPanel">
<slot name="end"></slot>
</div>
</div>`;
}
registerCustomElement(SplitScreen);
[data-monster-role="split-screen"] {
box-sizing: border-box;
display: flex;
width: 100%;
height: auto;
flex-direction: row;
margin: 0;
padding: 0;
& .panel {
flex-grow: 1;
overflow: auto;
}
[data-monster-role="dragger"] {
background-color: var(--monster-bg-color-primary-4);
color: var(--monster-color-primary-4);
width: var(--monster-border-width);
height: auto;
position: relative;
& [data-monster-role=handle] {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
cursor: pointer;
width: 5px;
height: 120px;
background-color: var(--monster-bg-color-primary-3);
color: var(--monster-color-primary-3);
z-index: var(--monster-z-index-outline);
}
}
&.horizontal {
flex-direction: column;
[data-monster-role="dragger"] {
width: 100%;
height: var(--monster-border-width);
& [data-monster-role=handle] {
width: 120px;
height: 5px;
}
}
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment