UNPKG

sober

Version:

Sober is a library of UI components designed with reference to Material You.

232 lines (231 loc) 6.72 kB
import { useElement, useProps } from './core/element.js'; import { Select } from './core/utils/select.js'; import './ripple.js'; import './scroll-view.js'; const name = 's-picker'; const props = useProps({ disabled: false, $label: '', $value: '' }); const style = /*css*/ ` :host{ display: inline-block; vertical-align: middle; font-size: .875rem; --picker-border-radius: 4px; --picker-border-color: var(--s-color-outline, ${"#70787D" /* Theme.colorOutline */}); --picker-border-width: 1px; --picker-padding: 16px; --picker-height: 48px; } :host([disabled=true]){ pointer-events: none; opacity: .38; } .popup{ display: block; cursor: pointer; position: relative; } .ripple{ border-radius: var(--picker-border-radius); } .field{ --field-border-radius: var(--picker-border-radius); --field-border-color: var(--picker-border-color); --field-border-width: var(--picker-border-width); --field-padding: var(--picker-padding); height: var(--picker-height); width: 100%; position: relative; } .view{ width: 100%; padding-top: 0; padding-bottom: 0; padding: 0 var(--picker-padding); } svg{ width: 24px; height: 24px; padding: 2px; box-sizing: border-box; flex-shrink: 0; margin-left: min(0px, calc((var(--picker-padding) * -1) + 4px)); margin-right: max(0px, calc(var(--picker-padding) - 12px)); fill: var(--s-color-on-surface-variant, ${"#40484C" /* Theme.colorOnSurfaceVariant */}); } .container{ max-height: 408px; box-sizing: border-box; display: flex; flex-direction: column; font-size: .875rem; padding: 4px 0; gap: 4px; } `; const template = /*html*/ ` <s-popup class="popup"> <slot name="trigger" slot="trigger"> <s-field fixed="false" class="field" part="field"> <div class="label" slot="label"></div> <div class="view"></div> <svg viewBox="0 -960 960 960" slot="end"> <path d="M480-360 280-560h400L480-360Z"></path> </svg> <s-ripple slot="custom" class="ripple" attached="true"></s-ripple> </s-field> </slot> <s-scroll-view class="container" part="container"> <slot id="slot"></slot> </s-scroll-view> </s-popup> `; export class Picker extends useElement({ style, template, props, setup(shadowRoot) { const popup = shadowRoot.querySelector('.popup'); const field = shadowRoot.querySelector('.field'); const label = shadowRoot.querySelector('.label'); const view = shadowRoot.querySelector('.view'); const slot = shadowRoot.querySelector('#slot'); const container = shadowRoot.querySelector('.container'); const select = new Select({ context: this, class: PickerItem, slot }); popup.addEventListener('show', () => { field.focused = true; field.fixed = true; if (!select.select) { view.textContent = this.label; view.style.opacity = '0'; } container.style.minWidth = `${this.offsetWidth}px`; if (select.select) { container.scrollTo({ top: (select.select.offsetTop - container.offsetTop) - (container.offsetHeight / 2 - select.select.offsetHeight / 2) }); } }); popup.onclose = () => { field.focused = false; !select.select && (field.fixed = false); }; popup.addEventListener('closed', () => container.style.removeProperty('min-width')); select.onUpdate = () => { if (!select.select) { field.fixed = false; view.textContent = ''; return; } field.fixed = true; view.style.removeProperty('opacity'); view.textContent = select.select.textContent; }; select.onSelect = () => popup.close(); return { expose: { get options() { return select.list; }, get selectedIndex() { return select.selectedIndex; }, get show() { return popup.show; }, get toggle() { return popup.toggle; }, get close() { return popup.close; } }, label: (value) => label.textContent = value, value: { get: () => select.value, set: (value) => select.value = value } }; } }) { } const itemName = 's-picker-item'; const itemProps = useProps({ selected: false, $value: '' }); const itemStyle = /*css*/ ` :host{ display: flex; align-items: center; height: 40px; margin: 0 4px; cursor: pointer; position: relative; border-radius: 4px; flex-shrink: 0; padding: 0 12px; color: var(--s-color-on-surface, ${"#191C1E" /* Theme.colorOnSurface */}); } :host([selected=true]){ background: var(--s-color-secondary-container, ${"#CFE6F1" /* Theme.colorSecondaryContainer */}); color: var(--s-color-on-secondary-container, ${"#354A53" /* Theme.colorOnSecondaryContainer */}); } .text{ flex-grow: 1; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } ::slotted(:is(svg, s-icon)){ color: var(--s-color-on-surface-variant, ${"#40484C" /* Theme.colorOnSurfaceVariant */}); fill: currentColor; height: 24px; width: 24px; } :host([selected=true]) ::slotted(:is(svg, s-icon)){ color: currentColor; } ::slotted([slot]){ flex-shrink: 0; } ::slotted([slot=start]){ margin-left: -4px; margin-right: 8px; } ::slotted([slot=end]){ margin-left: 8px; margin-right: -4px; } `; const itemTemplate = /*html*/ ` <slot name="start"></slot> <div class="text" part="text"> <slot></slot> </div> <slot name="end"></slot> <s-ripple part="ripple" attached="true" ></s-ripple> `; export class PickerItem extends useElement({ style: itemStyle, template: itemTemplate, props: itemProps, setup() { this.addEventListener('click', () => { if (this.selected) return; if (!(this.parentNode instanceof Picker)) return; this.dispatchEvent(new Event(`${name}:select`, { bubbles: true })); }); return { selected: () => { if (!(this.parentNode instanceof Picker)) return; this.dispatchEvent(new Event(`${name}:update`, { bubbles: true })); } }; } }) { } Picker.define(name); PickerItem.define(itemName);