From 473cd0ffc4812f059cd07550bbc145170ac7ef6d Mon Sep 17 00:00:00 2001 From: Thijs Louisse Date: Fri, 9 Dec 2022 16:09:42 +0100 Subject: [PATCH] chore: copy overlays docs assets inside ui pkg --- .../systems/overlays/assets/ref.js | 38 -- ...ayStyles.js => applyDemoOverlayStyles.mjs} | 1 + .../assets/demo-el-using-overlaymixin.js | 41 -- .../assets/demo-el-using-overlaymixin.mjs | 119 ++++ ...-backdrop.js => demo-overlay-backdrop.mjs} | 1 + .../assets/demo-overlay-positioning.mjs | 537 ++++++++++++++++++ 6 files changed, 658 insertions(+), 79 deletions(-) delete mode 100644 docs/fundamentals/systems/overlays/assets/ref.js rename packages/ui/docs/fundamentals/systems/overlays/assets/{applyDemoOverlayStyles.js => applyDemoOverlayStyles.mjs} (90%) delete mode 100644 packages/ui/docs/fundamentals/systems/overlays/assets/demo-el-using-overlaymixin.js create mode 100644 packages/ui/docs/fundamentals/systems/overlays/assets/demo-el-using-overlaymixin.mjs rename packages/ui/docs/fundamentals/systems/overlays/assets/{demo-overlay-backdrop.js => demo-overlay-backdrop.mjs} (94%) create mode 100644 packages/ui/docs/fundamentals/systems/overlays/assets/demo-overlay-positioning.mjs diff --git a/docs/fundamentals/systems/overlays/assets/ref.js b/docs/fundamentals/systems/overlays/assets/ref.js deleted file mode 100644 index fed6d4307..000000000 --- a/docs/fundamentals/systems/overlays/assets/ref.js +++ /dev/null @@ -1,38 +0,0 @@ -import { directive } from 'lit/directive.js'; - -/** - * @typedef {import('@lion/ui/core.js').PropertyPart} PropertyPart - */ - -/** @type {WeakSet} */ -const cache = new WeakSet(); - -/** - * @desc Allows to have references to different parts of your lit template. - * Without it, seperate renders to different nodes would have been needed, leading to more verbose, - * less readable and less performant code. - * Inspired by Angular template refeference variables: - * https://angular.io/guide/template-syntax#ref-vars - * - * @example - * ```js - * const refObj = {}; - * ``` - * ```html - * - * - * - *``` - * - * @param {object} refObj will be used to store reference to attribute names like #myElement - */ -export const ref = directive(refObj => (/** @type {PropertyPart} */ part) => { - if (cache.has(part.committer.element)) { - return; - } - cache.add(part.committer.element); - const attrName = part.committer.name; - const key = attrName.replace(/^#/, ''); - // eslint-disable-next-line no-param-reassign - refObj[key] = part.committer.element; -}); diff --git a/packages/ui/docs/fundamentals/systems/overlays/assets/applyDemoOverlayStyles.js b/packages/ui/docs/fundamentals/systems/overlays/assets/applyDemoOverlayStyles.mjs similarity index 90% rename from packages/ui/docs/fundamentals/systems/overlays/assets/applyDemoOverlayStyles.js rename to packages/ui/docs/fundamentals/systems/overlays/assets/applyDemoOverlayStyles.mjs index 93a79b2c1..f5203eb44 100644 --- a/packages/ui/docs/fundamentals/systems/overlays/assets/applyDemoOverlayStyles.js +++ b/packages/ui/docs/fundamentals/systems/overlays/assets/applyDemoOverlayStyles.mjs @@ -1,3 +1,4 @@ +/* eslint-disable import/no-extraneous-dependencies */ import { css } from 'lit'; const applyDemoOverlayStyles = () => { diff --git a/packages/ui/docs/fundamentals/systems/overlays/assets/demo-el-using-overlaymixin.js b/packages/ui/docs/fundamentals/systems/overlays/assets/demo-el-using-overlaymixin.js deleted file mode 100644 index 8c0e6c4fe..000000000 --- a/packages/ui/docs/fundamentals/systems/overlays/assets/demo-el-using-overlaymixin.js +++ /dev/null @@ -1,41 +0,0 @@ -import { html, LitElement } from 'lit'; -import { OverlayMixin } from '@lion/ui/overlays.js'; - -/** - * @typedef {import('@lion/ui/types/overlays.js').OverlayConfig} OverlayConfig - */ -class DemoElUsingOverlayMixin extends OverlayMixin(LitElement) { - // eslint-disable-next-line class-methods-use-this - _defineOverlayConfig() { - return /** @type {OverlayConfig} */ ({ - placementMode: 'global', - }); - } - - _setupOpenCloseListeners() { - super._setupOpenCloseListeners(); - - if (this._overlayInvokerNode) { - this._overlayInvokerNode.addEventListener('click', this.toggle); - } - } - - _teardownOpenCloseListeners() { - super._teardownOpenCloseListeners(); - - if (this._overlayInvokerNode) { - this._overlayInvokerNode.removeEventListener('click', this.toggle); - } - } - - render() { - return html` - - -
- -
- `; - } -} -customElements.define('demo-el-using-overlaymixin', DemoElUsingOverlayMixin); diff --git a/packages/ui/docs/fundamentals/systems/overlays/assets/demo-el-using-overlaymixin.mjs b/packages/ui/docs/fundamentals/systems/overlays/assets/demo-el-using-overlaymixin.mjs new file mode 100644 index 000000000..1fe27566a --- /dev/null +++ b/packages/ui/docs/fundamentals/systems/overlays/assets/demo-el-using-overlaymixin.mjs @@ -0,0 +1,119 @@ +/* eslint-disable max-classes-per-file */ +/* eslint-disable import/no-extraneous-dependencies */ +import { html, LitElement, css } from 'lit'; +import { OverlayMixin } from '@lion/ui/overlays.js'; +import { LionButton } from '@lion/ui/button.js'; + +/** + * @typedef {import('@lion/ui/types/overlays.js').OverlayConfig} OverlayConfig + */ +class DemoElUsingOverlayMixin extends OverlayMixin(LitElement) { + // eslint-disable-next-line class-methods-use-this + _defineOverlayConfig() { + return /** @type {OverlayConfig} */ ({ + placementMode: 'global', + }); + } + + _setupOpenCloseListeners() { + super._setupOpenCloseListeners(); + + if (this._overlayInvokerNode) { + this._overlayInvokerNode.addEventListener('click', this.toggle); + } + } + + _teardownOpenCloseListeners() { + super._teardownOpenCloseListeners(); + + if (this._overlayInvokerNode) { + this._overlayInvokerNode.removeEventListener('click', this.toggle); + } + } + + render() { + return html` + + + + `; + } +} +customElements.define('demo-el-using-overlaymixin', DemoElUsingOverlayMixin); + +class DemoOverlay extends OverlayMixin(LitElement) { + static get styles() { + return [ + css` + ::slotted([slot='content']) { + background-color: #333; + color: white; + padding: 8px; + } + + .close-button { + background: none; + border: none; + color: white; + font-weight: bold; + font-size: 16px; + padding: 4px; + } + `, + ]; + } + + // eslint-disable-next-line class-methods-use-this + _defineOverlayConfig() { + return /** @type {OverlayConfig} */ ({ + placementMode: 'global', + }); + } + + _setupOpenCloseListeners() { + super._setupOpenCloseListeners(); + + if (this._overlayInvokerNode) { + this._overlayInvokerNode.addEventListener('click', this.toggle); + } + } + + _teardownOpenCloseListeners() { + super._teardownOpenCloseListeners(); + + if (this._overlayInvokerNode) { + this._overlayInvokerNode.removeEventListener('click', this.toggle); + } + } + + render() { + return html` + + +
+ +
+ `; + } +} +customElements.define('demo-overlay', DemoOverlay); + +class DemoCloseButton extends LionButton { + static get styles() { + return [ + css` + ::host { + background: none; + } + `, + ]; + } + + connectedCallback() { + super.connectedCallback(); + + this.innerText = 'тип'; + this.setAttribute('aria-label', 'Close'); + } +} +customElements.define('demo-close-button', DemoCloseButton); diff --git a/packages/ui/docs/fundamentals/systems/overlays/assets/demo-overlay-backdrop.js b/packages/ui/docs/fundamentals/systems/overlays/assets/demo-overlay-backdrop.mjs similarity index 94% rename from packages/ui/docs/fundamentals/systems/overlays/assets/demo-overlay-backdrop.js rename to packages/ui/docs/fundamentals/systems/overlays/assets/demo-overlay-backdrop.mjs index 47112fcef..36fe51b90 100644 --- a/packages/ui/docs/fundamentals/systems/overlays/assets/demo-overlay-backdrop.js +++ b/packages/ui/docs/fundamentals/systems/overlays/assets/demo-overlay-backdrop.mjs @@ -1,3 +1,4 @@ +/* eslint-disable import/no-extraneous-dependencies */ import { css, LitElement } from 'lit'; /** diff --git a/packages/ui/docs/fundamentals/systems/overlays/assets/demo-overlay-positioning.mjs b/packages/ui/docs/fundamentals/systems/overlays/assets/demo-overlay-positioning.mjs new file mode 100644 index 000000000..ee796d591 --- /dev/null +++ b/packages/ui/docs/fundamentals/systems/overlays/assets/demo-overlay-positioning.mjs @@ -0,0 +1,537 @@ +/* eslint-disable max-classes-per-file */ +/* eslint-disable import/no-extraneous-dependencies */ +import { html, LitElement, css } from 'lit'; +import { ref, createRef } from 'lit/directives/ref.js'; +import { OverlayMixin } from '@lion/ui/overlays.js'; +import './demo-el-using-overlaymixin.mjs'; + +/** + * @typedef {import('@lion/ui/types/overlays.js').OverlayConfig} OverlayConfig + * @typedef {import('@lion/ui/types/overlays.js').OverlayHost} OverlayHost + * @typedef {import('@lion/ui/button.js').LionButton} LionButton + * @typedef {'top-start'|'top'|'top-end'|'right-start'|'right'|'right-end'|'bottom-start'|'bottom'|'bottom-end'|'left-start'|'left'|'left-end'} LocalPlacement + * @typedef {'center'|'top-left'|'top'|'top-right'|'right'|'bottom-right'|'bottom'|'bottom-left'|'left'} ViewportPlacement + * @typedef {{refs: { contentNodeWrapper:{ref: {value:HTMLElement}}; closeButton: {ref: {value:HTMLButtonElement|LionButton}; label:string} }}} TemplateDataForOverlay + */ +class DemoOverlayEl extends OverlayMixin(LitElement) { + /** @type {any} */ + static get properties() { + return { + simulateViewport: { type: Boolean, attribute: 'simulate-viewport', reflect: true }, + noDialogEl: { type: Boolean, attribute: 'no-dialog-el' }, + useAbsolute: { type: Boolean, attribute: 'use-absolute', reflect: true }, + }; + } + + static get styles() { + return [ + super.styles || [], + css` + :host([use-absolute]) dialog { + position: absolute !important; + } + + :host([simulate-viewport]) { + position: absolute; + inset: 0; + z-index: -1; + } + + :host([simulate-viewport]) dialog { + position: absolute !important; + inset: 0; + width: 100%; + height: 100%; + } + + :host([simulate-viewport]) #overlay-content-node-wrapper.overlays__overlay-container { + position: absolute; + } + + /*=== demo invoker and content ===*/ + + :host ::slotted([slot='invoker']) { + border: 4px dashed; + height: 24px; + min-width: 24px; + } + + :host ::slotted([slot='content']) { + background-color: black; + color: white; + height: 54px; + min-width: 54px; + display: flex; + place-items: center; + padding: 20px; + text-align: center; + font-size: 0.8rem; + } + `, + ]; + } + + constructor() { + super(); + + this.simulateViewport = false; + this._noDialogEl = false; + this.useAbsolute = false; + } + + // eslint-disable-next-line class-methods-use-this + _defineOverlayConfig() { + return /** @type {OverlayConfig} */ ({ + placementMode: 'local', + noDialogEl: this._noDialogEl, + popperConfig: { strategy: this.useAbsolute ? 'absolute' : 'fixed' }, + }); + } + + _setupOpenCloseListeners() { + super._setupOpenCloseListeners(); + + if (this._overlayInvokerNode) { + this._overlayInvokerNode.addEventListener('click', this.toggle); + } + } + + _teardownOpenCloseListeners() { + super._teardownOpenCloseListeners(); + + if (this._overlayInvokerNode) { + this._overlayInvokerNode.removeEventListener('click', this.toggle); + } + } + + refs = { + invokerSlot: /** @type {{value: HTMLSlotElement}} */ (createRef()), + backdropSlot: /** @type {{value: HTMLSlotElement}} */ (createRef()), + contentSlot: /** @type {{value: HTMLSlotElement}} */ (createRef()), + closeButton: /** @type {{value: HTMLButtonElement|LionButton}} */ (createRef()), + contentNodeWrapper: /** @type {{value: HTMLElement}} */ (createRef()), + }; + + /** + * @overridable + * @type {TemplateDataForOverlay} + */ + get _templateData() { + return { + refs: { + contentNodeWrapper: { + ref: this.refs.contentNodeWrapper, + }, + closeButton: { + ref: this.refs.closeButton, + label: 'close dialog', + }, + }, + }; + } + + static templates = { + main: (/** @type {TemplateDataForOverlay} */ { refs }) => html` + + +
+ +
+ `, + }; + + render() { + const ctor = /** @type {typeof DemoOverlayEl} */ (this.constructor); + const templates = this.templates || ctor.templates; + return templates.main(this._templateData); + } +} +customElements.define('demo-overlay-el', DemoOverlayEl); + +class DemoOverlayPositioning extends LitElement { + static properties = { + placementMode: { attribute: 'placement-mode', type: String }, + simulateViewport: { type: Boolean, attribute: 'simulate-viewport', reflect: true }, + _activePos: { type: String, reflect: true, attribute: 'active-pos' }, + _activeConfig: { type: Object, state: true }, + }; + + static get styles() { + return [ + css` + /*=== .pos-container ===*/ + + .pos-container { + padding: 0.5rem; + overflow: hidden; + place-items: center; + height: 20rem; + display: grid; + position: relative; + } + + /*=== .pos-btn-wrapper ===*/ + + /** + * We need a wrapper for position transforms, so that we can apply scale transforms on .pos-btn hover + */ + + .pos-btn-wrapper { + position: absolute; + } + + .pos-container--local .pos-btn-wrapper--bottom-start, + .pos-container--local .pos-btn-wrapper--bottom-end, + .pos-container--local .pos-btn-wrapper--top-start, + .pos-container--local .pos-btn-wrapper--top-end, + .pos-container--local .pos-btn-wrapper--top, + .pos-container--local .pos-btn-wrapper--bottom { + left: 50%; + transform: translateX(-50%); + } + + .pos-container--local .pos-btn-wrapper--top-start, + .pos-container--local .pos-btn-wrapper--top-end, + .pos-container--local .pos-btn-wrapper--top { + top: 0; + } + + .pos-container--local .pos-btn-wrapper--bottom-start, + .pos-container--local .pos-btn-wrapper--bottom-end, + .pos-container--local .pos-btn-wrapper--bottom { + bottom: 0; + } + + .pos-container--local .pos-btn-wrapper--left-start, + .pos-container--local .pos-btn-wrapper--left-end, + .pos-container--local .pos-btn-wrapper--right-start, + .pos-container--local .pos-btn-wrapper--right-end, + .pos-container--local .pos-btn-wrapper--left, + .pos-container--local .pos-btn-wrapper--right { + top: 50%; + transform: translateY(-50%); + } + + .pos-container--local .pos-btn-wrapper--left-start, + .pos-container--local .pos-btn-wrapper--left-end, + .pos-container--local .pos-btn-wrapper--left { + left: 0; + } + + .pos-container--local .pos-btn-wrapper--right-start, + .pos-container--local .pos-btn-wrapper--right-end, + .pos-container--local .pos-btn-wrapper--right { + right: 0; + } + + .pos-container--local .pos-btn-wrapper--bottom-start, + .pos-container--local .pos-btn-wrapper--top-start { + transform: translateX(-50%) translateX(-48px); + } + + .pos-container--local .pos-btn-wrapper--bottom-end, + .pos-container--local .pos-btn-wrapper--top-end { + transform: translateX(-50%) translateX(48px); + } + + .pos-container--local .pos-btn-wrapper--left-start, + .pos-container--local .pos-btn-wrapper--right-start { + transform: translateY(calc(-50% - 48px)); + } + + .pos-container--local .pos-btn-wrapper--left-end, + .pos-container--local .pos-btn-wrapper--right-end { + transform: translateY(calc(-50% + 48px)); + } + + .pos-container--global .pos-btn-wrapper { + top: 50%; + left: 50%; + } + + .pos-container--global .pos-btn-wrapper--center { + transform: translateY(-50%) translateX(-50%); + } + + .pos-container--global .pos-btn-wrapper--top-left { + transform: translateY(-50%) translateX(-50%) translateY(-48px) translateX(-48px); + } + + .pos-container--global .pos-btn-wrapper--top { + transform: translateY(-50%) translateX(-50%) translateY(-48px); + } + + .pos-container--global .pos-btn-wrapper--top-right { + transform: translateY(-50%) translateX(-50%) translateY(-48px) translateX(48px); + } + + .pos-container--global .pos-btn-wrapper--bottom-left { + transform: translateY(-50%) translateX(-50%) translateY(48px) translateX(-48px); + } + + .pos-container--global .pos-btn-wrapper--bottom { + transform: translateY(-50%) translateX(-50%) translateY(48px); + } + + .pos-container--global .pos-btn-wrapper--bottom-right { + transform: translateY(-50%) translateX(-50%) translateY(48px) translateX(48px); + } + + .pos-container--global .pos-btn-wrapper--right { + transform: translateY(-50%) translateX(-50%) translateX(48px); + } + + .pos-container--global .pos-btn-wrapper--left { + transform: translateY(-50%) translateX(-50%) translateX(-48px); + } + + /*=== .pos-btn ===*/ + + .pos-btn { + padding: 1rem; + cursor: pointer; + -webkit-appearance: button; + background-color: transparent; + background-image: none; + text-transform: none; + font-family: inherit; + font-size: 100%; + line-height: inherit; + color: inherit; + margin: 0; + box-sizing: border-box; + border: 0 solid #bfc3d9; + } + + .pos-btn__inner { + border-style: solid; + border-width: 2px; + border-radius: 100%; + width: 0.25rem; + height: 0.25rem; + } + + .pos-btn:hover { + transform: scaleX(2) scaleY(2); + } + + .pos-btn--active .pos-btn__inner { + background-color: black; + } + + /*=== .reference-btn ===*/ + + .reference-btn { + background: white; + box-sizing: border-box; + border: 5px dashed black; + cursor: pointer; + padding: 1rem; + width: 8rem; + height: 8rem; + } + + .pos-container--global .reference-btn { + position: absolute; + inset: 0; + width: 100%; + height: 100%; + z-index: -1; + } + + /*=== .close-btn ===*/ + + .close-btn { + background: transparent; + border: none; + position: absolute; + right: 0; + top: 0; + padding: 0.5rem; + } + + .close-btn::after { + content: ''; + clear: both; + } + + /*=== .overlay-content ===*/ + + .overlay-content { + display: flex; + box-sizing: border-box; + border: 1px solid black; + align-items: center; + justify-items: center; + background-color: #000; + padding: 1rem; + width: 2rem; + height: 2rem; + } + + :host([active-pos^='center']) .pos-container--global .overlay-content { + width: 14rem; + height: 16rem; + } + + :host([active-pos^='bottom']) .pos-container--global .overlay-content, + :host([active-pos^='top']) .pos-container--global .overlay-content { + width: 50%; + } + + :host([active-pos^='left']) .pos-container--global .overlay-content, + :host([active-pos^='right']) .pos-container--global .overlay-content { + height: 50%; + } + + :host([active-pos^='center']) .pos-btn { + color: white; + } + `, + ]; + } + + refs = { + overlay: /** @type {{value: OverlayHost}} */ (createRef()), + }; + + constructor() { + super(); + + this.placementMode = 'local'; + /** @type {ViewportPlacement[]|LocalPlacement[]} */ + this._placements = []; + this.simulateViewport = false; + this._activePos = 'top'; + } + + /** + * + * @param {{pos:ViewportPlacement|LocalPlacement}} opts + */ + async _updatePos({ pos }) { + this._activePos = pos; + + const overlayEl = this.refs.overlay.value; + + // @ts-ignore allow protected + if (overlayEl?._overlayCtrl) { + overlayEl.config = /** @type {Partial} */ ({ + popperConfig: { placement: pos }, + viewportConfig: { placement: pos }, + }); + // TODO: these hacks below should not be needed. Fix when moving to floating-ui + // => animate different positions + // @ts-ignore allow protected + await overlayEl._overlayCtrl.hide(); + // @ts-ignore allow protected + overlayEl._overlayCtrl.show(); + overlayEl.config = /** @type {Partial} */ ({ + popperConfig: { placement: pos }, + viewportConfig: { placement: pos }, + }); + } + } + + /** + * @param {import('lit').PropertyValues } changedProperties + */ + firstUpdated(changedProperties) { + super.firstUpdated(changedProperties); + + this._updatePos({ pos: this.placementMode === 'local' ? 'top' : 'center' }); + } + + /** + * @param {import('lit').PropertyValues } changedProperties + */ + update(changedProperties) { + if (changedProperties.has('placementMode')) { + if (this.placementMode === 'local') { + this._placements = [ + `top-start`, + `top`, + `top-end`, + `right-start`, + `right`, + `right-end`, + `bottom-start`, + `bottom`, + `bottom-end`, + `left-start`, + `left`, + `left-end`, + ]; + } else { + this._placements = [ + `center`, + `top-left`, + `top`, + `top-right`, + `right`, + `bottom-right`, + `bottom`, + `bottom-left`, + `left`, + ]; + } + } + super.update(changedProperties); + } + + /** + * @param {import('lit').PropertyValues } changedProperties + */ + updated(changedProperties) { + super.updated(changedProperties); + + if (changedProperties.has('placementMode')) { + this._activeConfig = { + placementMode: this.placementMode, + }; + } + } + + /** + * @param {{pos:string}} opts + * @returns + */ + _isActivePosBtn({ pos }) { + return pos === this._activePos; + } + + render() { + return html` +
+ ${this._placements.map( + pos => + html` +
+ +
+ `, + )} + + + +
+
+
+ `; + } +} +customElements.define('demo-overlay-positioning', DemoOverlayPositioning);