@swiftwc/ui 0.0.0-dev.8 → 0.0.0-dev.80
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/generated/buses/alert-dialog.d.ts +2 -0
- package/generated/buses/alert-dialog.js +1 -0
- package/generated/buses/confirmation-dialog.d.ts +2 -0
- package/generated/buses/confirmation-dialog.js +1 -0
- package/generated/buses/index.d.ts +6 -0
- package/generated/buses/index.js +6 -0
- package/generated/buses/lifecycle-observer.d.ts +2 -0
- package/generated/buses/lifecycle-observer.js +1 -0
- package/generated/client/index.d.ts +21 -10
- package/generated/client/index.js +177 -194
- package/generated/components/alert-dialog.d.ts +8 -0
- package/generated/components/alert-dialog.js +99 -0
- package/generated/components/body-view.js +5 -2
- package/generated/components/bordered-button.d.ts +10 -0
- package/generated/components/bordered-button.js +39 -3
- package/generated/components/bordered-prominent-button.d.ts +10 -0
- package/generated/components/bordered-prominent-button.js +39 -3
- package/generated/components/borderless-button.d.ts +9 -1
- package/generated/components/borderless-button.js +32 -11
- package/generated/components/color-scheme.d.ts +7 -0
- package/generated/components/color-scheme.js +54 -0
- package/generated/components/confirmation-dialog.d.ts +1 -1
- package/generated/components/confirmation-dialog.js +43 -32
- package/generated/components/content-unavailable-view.d.ts +24 -0
- package/generated/components/content-unavailable-view.js +81 -0
- package/generated/components/date-picker.d.ts +2 -12
- package/generated/components/date-picker.js +96 -65
- package/generated/components/disclosure-group.d.ts +5 -0
- package/generated/components/disclosure-group.js +18 -7
- package/generated/components/fine-tooltip.d.ts +7 -0
- package/generated/components/fine-tooltip.js +107 -0
- package/generated/components/form-view.d.ts +3 -0
- package/generated/components/form-view.js +8 -3
- package/generated/components/glass-button.d.ts +10 -0
- package/generated/components/glass-button.js +39 -3
- package/generated/components/glass-prominent-button.d.ts +10 -0
- package/generated/components/glass-prominent-button.js +39 -3
- package/generated/components/h-stack.d.ts +14 -0
- package/generated/components/h-stack.js +23 -0
- package/generated/components/image-view.d.ts +9 -0
- package/generated/components/image-view.js +64 -0
- package/generated/components/index.d.ts +11 -3
- package/generated/components/index.js +11 -3
- package/generated/components/label-view.d.ts +6 -1
- package/generated/components/label-view.js +32 -59
- package/generated/components/labeled-content.d.ts +9 -0
- package/generated/components/labeled-content.js +116 -0
- package/generated/components/list-view.d.ts +3 -0
- package/generated/components/list-view.js +20 -17
- package/generated/components/menu-view.d.ts +4 -1
- package/generated/components/menu-view.js +60 -51
- package/generated/components/navigation-large-title.js +9 -5
- package/generated/components/navigation-split-view.d.ts +4 -1
- package/generated/components/navigation-split-view.js +13 -6
- package/generated/components/navigation-stack.d.ts +4 -1
- package/generated/components/navigation-stack.js +16 -9
- package/generated/components/navigation-title.d.ts +1 -1
- package/generated/components/navigation-title.js +58 -55
- package/generated/components/picker-view.d.ts +32 -4
- package/generated/components/picker-view.js +924 -234
- package/generated/components/plain-button.js +5 -3
- package/generated/components/progress-view.d.ts +13 -0
- package/generated/components/progress-view.js +120 -0
- package/generated/components/screen-view.js +3 -1
- package/generated/components/scroll-view-proxy.d.ts +8 -0
- package/generated/components/scroll-view-proxy.js +17 -0
- package/generated/components/scroll-view.d.ts +1 -1
- package/generated/components/scroll-view.js +101 -85
- package/generated/components/search-view.d.ts +7 -0
- package/generated/components/search-view.js +30 -0
- package/generated/components/section-view.d.ts +1 -1
- package/generated/components/section-view.js +35 -30
- package/generated/components/sheet-view.d.ts +1 -2
- package/generated/components/sheet-view.js +32 -39
- package/generated/components/sidebar-toggle.js +44 -66
- package/generated/components/sidebar-view.js +13 -8
- package/generated/components/sticky-container.js +8 -4
- package/generated/components/tab-bar.d.ts +3 -0
- package/generated/components/tab-bar.js +20 -10
- package/generated/components/tab-item.js +24 -13
- package/generated/components/tab-view.js +25 -17
- package/generated/components/table-view.d.ts +5 -0
- package/generated/components/table-view.js +55 -77
- package/generated/components/text-field.d.ts +2 -2
- package/generated/components/text-field.js +95 -88
- package/generated/components/toggle-view.d.ts +28 -0
- package/generated/components/toggle-view.js +224 -0
- package/generated/components/tool-bar-item-group.js +21 -4
- package/generated/components/tool-bar-item.d.ts +4 -1
- package/generated/components/tool-bar-item.js +57 -10
- package/generated/components/tool-bar.d.ts +12 -0
- package/generated/components/tool-bar.js +27 -11
- package/generated/components/v-keyboard.d.ts +3 -1
- package/generated/components/v-keyboard.js +12 -6
- package/generated/components/v-stack.d.ts +12 -0
- package/generated/components/v-stack.js +17 -2
- package/generated/css/index.css +2533 -941
- package/generated/events/alert-dialog.d.ts +8 -0
- package/generated/events/alert-dialog.js +1 -0
- package/generated/events/confirmation-dialog.d.ts +2 -2
- package/generated/events/index.d.ts +2 -0
- package/generated/events/index.js +2 -0
- package/generated/events/picker-view.d.ts +7 -0
- package/generated/events/scroll-view.js +4 -0
- package/generated/events/tab-view.d.ts +6 -2
- package/generated/events/text-field.js +3 -0
- package/generated/events/toggle-view.d.ts +9 -0
- package/generated/events/toggle-view.js +4 -0
- package/generated/i18n/index.d.ts +20 -10
- package/generated/i18n/index.js +82 -54
- package/generated/i18n/locales/el.d.ts +16 -0
- package/generated/i18n/locales/el.js +16 -0
- package/generated/i18n/locales/en.d.ts +16 -0
- package/generated/i18n/locales/en.js +16 -0
- package/generated/i18n/locales/index.d.ts +4 -0
- package/generated/i18n/locales/index.js +4 -0
- package/generated/internal/class/css-style-observer.js +3 -2
- package/generated/internal/class/form-associated-base.d.ts +0 -4
- package/generated/internal/class/form-associated-base.js +23 -38
- package/generated/internal/class/mutation-observer-set.d.ts +9 -0
- package/generated/internal/class/mutation-observer-set.js +42 -0
- package/generated/internal/class/mutation-observer-singleton.js +18 -0
- package/generated/internal/class/navigation-view.d.ts +1 -1
- package/generated/internal/class/navigation-view.js +38 -43
- package/generated/internal/decorators/adaptive-slot.d.ts +1 -0
- package/generated/internal/decorators/adaptive-slot.js +50 -0
- package/generated/internal/decorators/custom-element.d.ts +1 -1
- package/generated/internal/decorators/custom-element.js +2 -1
- package/generated/internal/decorators/index.d.ts +6 -0
- package/generated/internal/decorators/index.js +6 -0
- package/generated/internal/decorators/microtask-on-connected.d.ts +39 -0
- package/generated/internal/decorators/microtask-on-connected.js +67 -0
- package/generated/internal/privateNamespace.d.ts +10 -0
- package/generated/internal/privateNamespace.js +168 -1
- package/generated/internal/utils/cash/index.d.ts +4 -3
- package/generated/internal/utils/cash/index.js +6 -4
- package/generated/internal/utils/dev-flags.d.ts +3 -0
- package/generated/internal/utils/dev-flags.js +5 -0
- package/generated/internal/utils/ensure-placeholder.d.ts +1 -0
- package/generated/internal/utils/ensure-placeholder.js +55 -0
- package/generated/internal/utils/index.d.ts +7 -2
- package/generated/internal/utils/index.js +9 -2
- package/generated/internal/utils/is-inside.d.ts +1 -0
- package/generated/internal/utils/is-inside.js +4 -0
- package/generated/internal/utils/list-active.js +67 -12
- package/generated/internal/utils/render-label-icon.d.ts +2 -0
- package/generated/internal/utils/render-label-icon.js +19 -0
- package/generated/internal/utils/render-label-title.d.ts +2 -0
- package/generated/internal/utils/render-label-title.js +16 -0
- package/generated/internal/utils/render-label.d.ts +1 -0
- package/generated/internal/utils/render-label.js +19 -0
- package/generated/internal/utils/touch-glass.js +5 -5
- package/generated/namespace-browser/base.d.ts +12 -3
- package/generated/namespace-browser/base.js +41 -18
- package/generated/namespace-browser/event.d.ts +10 -0
- package/generated/namespace-browser/event.js +1 -0
- package/generated/snapshot/index.d.ts +3 -1
- package/generated/snapshot/index.js +25 -2
- package/generated/tpl/html.d.ts +5 -0
- package/generated/tpl/html.js +3 -0
- package/generated/tpl/index.d.ts +4 -0
- package/generated/tpl/index.js +4 -0
- package/generated/tpl/render.d.ts +2 -0
- package/generated/tpl/render.js +132 -0
- package/package.json +18 -4
- package/scss/_components.scss +14 -0
- package/scss/_functions.scss +10 -0
- package/scss/_mixins.scss +4 -0
- package/scss/_transitions.scss +6 -1
- package/scss/_vars.scss +9 -2
- package/scss/base/_layout.scss +17 -7
- package/scss/base/_reboot.scss +34 -1
- package/scss/base/_root.scss +117 -114
- package/scss/colors/_index.scss +304 -85
- package/scss/components/_alert-dialog.scss +111 -0
- package/scss/components/_bordered-button.scss +35 -0
- package/scss/components/_bordered-prominent-button.scss +45 -0
- package/scss/components/_borderless-button.scss +25 -0
- package/scss/components/_confirmation-dialog.scss +41 -49
- package/scss/components/_content-unavailable-view.scss +56 -0
- package/scss/components/_date-picker.scss +15 -15
- package/scss/components/_detail-placeholder.scss +1 -3
- package/scss/components/_disclosure-group.scss +84 -33
- package/scss/components/_fine-tooltip.scss +84 -0
- package/scss/components/_form-view.scss +58 -20
- package/scss/components/_glass-button.scss +35 -0
- package/scss/components/_glass-prominent-button.scss +46 -0
- package/scss/components/_group-box.scss +27 -0
- package/scss/components/_h-flex-stack.scss +23 -0
- package/scss/components/_h-stack.scss +21 -15
- package/scss/components/_image-view.scss +20 -0
- package/scss/components/_index.scss +40 -20
- package/scss/components/_label-view copy.scss.txt +420 -0
- package/scss/components/_label-view.scss +94 -110
- package/scss/components/_label-view.scss.txt +536 -0
- package/scss/components/_labeled-content.scss +59 -1
- package/scss/components/_list-view.scss +65 -25
- package/scss/components/_menu-view.scss +95 -60
- package/scss/components/_navigation-large-title.scss +17 -0
- package/scss/components/_navigation-split-view.scss +3 -3
- package/scss/components/_picker-view.scss +259 -59
- package/scss/components/_plain-button.scss +7 -0
- package/scss/components/_progress-view.scss +98 -0
- package/scss/components/_scroll-view-proxy.scss +22 -20
- package/scss/components/_scroll-view.scss +4 -0
- package/scss/components/_search-view.scss +22 -0
- package/scss/components/_section-view.scss +23 -22
- package/scss/components/_sheet-view.scss +16 -6
- package/scss/components/_sidebar-toggle.scss +1 -4
- package/scss/components/_sticky-container.scss +1 -1
- package/scss/components/_tab-bar.scss +17 -3
- package/scss/components/_tab-view.scss +4 -2
- package/scss/components/_table-column.scss +2 -0
- package/scss/components/_table-row.scss +69 -1
- package/scss/components/_table-view.scss +22 -14
- package/scss/components/_text-field.scss +37 -17
- package/scss/components/_toggle-view.scss +112 -0
- package/scss/components/_tool-bar-item.scss +34 -82
- package/scss/components/_tool-bar.scss +77 -57
- package/scss/components/_v-stack.scss +9 -2
- package/scss/final/_touch-glass.scss +24 -0
- package/scss/mixins/_button.scss +44 -0
- package/scss/mixins/_color.scss +16 -0
- package/scss/mixins/_dialog.scss +1 -0
- package/scss/mixins/_index.scss +64 -2
- package/scss/placeholders/_buttons.scss +44 -10
- package/scss/placeholders/_lists.scss +133 -126
- package/scss/transitions/_alert-dialog.scss +18 -0
- package/scss/transitions/_confirmation-dialog.scss +8 -0
- package/scss/transitions/_fine-tooltip.scss +21 -0
- package/scss/transitions/_menu-view.scss +17 -15
- package/scss/transitions/_progress-view.scss +23 -0
- package/scss/transitions/_sheet-view.scss +30 -0
- package/scss/transitions/_tab-bar.scss +6 -5
- package/scss/utils/_frame.scss +42 -0
- package/scss/utils/_index.scss +67 -52
- package/scss/utils/_stacks.scss +56 -73
- package/web-components.html-data/en.json +623 -24
- package/generated/confirmation-dialog/index.d.ts +0 -1
- package/generated/confirmation-dialog/index.js +0 -1
- package/generated/lifecycle-observer/index.d.ts +0 -1
- package/generated/lifecycle-observer/index.js +0 -1
- package/scss/transitions/_dialog.scss +0 -28
|
@@ -1,47 +1,517 @@
|
|
|
1
1
|
var _a;
|
|
2
|
+
import { I18n } from '../i18n';
|
|
2
3
|
import { CleanupRegistry } from '../internal/class/cleanup-registry';
|
|
3
|
-
import { FormAssociatedBase, getInternals
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
|
|
4
|
+
import { FormAssociatedBase, getInternals } from '../internal/class/form-associated-base';
|
|
5
|
+
import { MutationObserverSet } from '../internal/class/mutation-observer-set';
|
|
6
|
+
import { NavigationPath } from '../internal/class/navigation-path';
|
|
7
|
+
import { queryInsertPosition, startViewTransition } from '../internal/privateNamespace';
|
|
8
|
+
import { $, devFlags, kebabCase, onoff, renderLabel, renderLabelIcon, renderLabelTitle } from '../internal/utils';
|
|
9
|
+
import { html, render } from '../tpl';
|
|
10
|
+
const pickerStyles = ['menu', 'inline', 'navigation-link', 'sheet', 'automatic'];
|
|
11
|
+
const update = (path, node, overwrite = true) => {
|
|
12
|
+
if (devFlags.debug)
|
|
13
|
+
console.debug(`PickerView: update`);
|
|
14
|
+
if (!(path instanceof NavigationPath))
|
|
15
|
+
throw new Error('invalid view');
|
|
16
|
+
const { component, page } = path;
|
|
17
|
+
if (!component)
|
|
18
|
+
return;
|
|
19
|
+
const position = queryInsertPosition(component); //'afterend'
|
|
20
|
+
// const lookFor = 'beforebegin' === position ? 'previousElementSibling' : 'nextElementSibling'
|
|
21
|
+
if (page)
|
|
22
|
+
page.insertAdjacentElement(position, node);
|
|
23
|
+
};
|
|
24
|
+
const reflectSpawnedPage = (current, source) => {
|
|
25
|
+
if (devFlags.debug)
|
|
26
|
+
console.debug(`PickerView: reflectSpawnedPage`);
|
|
27
|
+
const { page: oldSv, toolBarConfig: oldToolbar } = new NavigationPath(current).hydrate(), { page: newSv, toolBarConfig: newToolbar } = new NavigationPath(source).hydrate();
|
|
28
|
+
const oldList = oldSv?.querySelector('list-view'), newList = newSv?.querySelector('list-view'), oldBackBtn = oldToolbar?.at(0)?.querySelector('button'), newBackBtn = newToolbar?.at(0)?.querySelector('button');
|
|
29
|
+
// pre replace list (always exists)
|
|
30
|
+
const oldOpenedDetails = [...(oldList?.querySelectorAll('list-view details[open]>summary') ?? [])].map((item) => item.textContent.trim());
|
|
31
|
+
// replace list (always exists)
|
|
32
|
+
if (oldList && newList)
|
|
33
|
+
oldList.replaceWith(newList);
|
|
34
|
+
// post replace list (always exists)
|
|
35
|
+
for (const label of oldOpenedDetails)
|
|
36
|
+
for (const summary of newList?.querySelectorAll('list-view details>summary') ?? [])
|
|
37
|
+
if (summary.textContent.trim() === label) {
|
|
38
|
+
summary.parentElement?.setAttribute('open', 'open');
|
|
39
|
+
break;
|
|
40
|
+
}
|
|
41
|
+
// replace backbtns (always exist)
|
|
42
|
+
if (oldBackBtn && newBackBtn)
|
|
43
|
+
oldBackBtn.replaceWith(newBackBtn);
|
|
44
|
+
// overwrite navtitle (always exists)
|
|
45
|
+
oldSv?.setAttribute('navigation-inline-title', newSv?.getAttribute('navigation-inline-title') ?? '');
|
|
46
|
+
// replace search (if exists)
|
|
47
|
+
oldList?.style.setProperty('--list--sticky-block-size', newList?.style.getPropertyValue('--list--sticky-block-size') ?? '');
|
|
48
|
+
// replace search (if exists)
|
|
49
|
+
const oldSearch = oldSv?.querySelector('sticky-container'), newSearch = newSv?.querySelector('sticky-container');
|
|
50
|
+
// if already exists, DO NOTHING dont mess up the input element
|
|
51
|
+
if (newSearch) {
|
|
52
|
+
if (!oldSearch)
|
|
53
|
+
oldSv?.insertAdjacentHTML('afterend', `<sticky-container edge="navbar" style="order: -1" padding>
|
|
54
|
+
<v-stack spacing="0" alignment="fill">
|
|
55
|
+
<input is="search-view" placeholder="Search">
|
|
56
|
+
<!--<button type="button">Filters</button>-->
|
|
57
|
+
</v-stack>
|
|
58
|
+
</sticky-container>`);
|
|
59
|
+
}
|
|
60
|
+
else
|
|
61
|
+
oldSearch?.remove();
|
|
62
|
+
};
|
|
63
|
+
const extractTag = (node) => {
|
|
64
|
+
if (devFlags.debug)
|
|
65
|
+
console.debug(`PickerView: extractTag`);
|
|
66
|
+
return node instanceof HTMLOptionElement ? (((node.getAttribute('value') ?? node.textContent?.trim()) || node.getAttribute('label')) ?? '') : (node.value ?? '');
|
|
67
|
+
}, extractLabel = (node) => {
|
|
68
|
+
if (devFlags.debug)
|
|
69
|
+
console.debug(`PickerView: extractLabelFromGroup`);
|
|
70
|
+
if (node instanceof Element)
|
|
71
|
+
if (node instanceof HTMLOptionElement)
|
|
72
|
+
return (node.getAttribute('label') ?? node.getAttribute('value') ?? node.textContent?.trim()) || null;
|
|
73
|
+
else
|
|
74
|
+
return node.getAttribute('DATALIST' === node.tagName ? 'data-label' : 'label');
|
|
75
|
+
else
|
|
76
|
+
return node.title ?? node.value ?? null;
|
|
77
|
+
// return node instanceof Element ? node.getAttribute('DATALIST' === node.tagName ? 'data-label' : 'label') : (node.title ?? node.value ?? null)
|
|
78
|
+
}, extractIcon = (node) => {
|
|
79
|
+
if (devFlags.debug)
|
|
80
|
+
console.debug(`PickerView: extractIcon`);
|
|
81
|
+
// return node instanceof Element ? node.getAttribute('data-system-image') : (node.systemImage ?? null)
|
|
82
|
+
if (node instanceof Element)
|
|
83
|
+
return node.getAttribute('data-system-image');
|
|
84
|
+
else
|
|
85
|
+
return node.systemImage ?? null;
|
|
86
|
+
}, extractSubtitle = (node) => {
|
|
87
|
+
if (devFlags.debug)
|
|
88
|
+
console.debug(`PickerView: extractSubtitle`);
|
|
89
|
+
if (node instanceof Element)
|
|
90
|
+
return node.getAttribute('data-subtitle');
|
|
91
|
+
else
|
|
92
|
+
return node.subtitle ?? null;
|
|
93
|
+
};
|
|
94
|
+
const allLeaves = (node) => node.children.every((c) => 0 === c.children.length);
|
|
95
|
+
const parseDictionary = (value) => {
|
|
96
|
+
let dictionary = [];
|
|
97
|
+
try {
|
|
98
|
+
dictionary = JSON.parse(value ?? '[]');
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
console.error('invalid-dictionary');
|
|
102
|
+
}
|
|
103
|
+
return dictionary;
|
|
104
|
+
};
|
|
105
|
+
const indexGroups = (nodes, parentPath = '') => {
|
|
106
|
+
const map = new Map();
|
|
107
|
+
nodes.forEach((node, i) => {
|
|
108
|
+
const id = parentPath ? `${parentPath}.${i}` : `${i}`;
|
|
109
|
+
map.set(id, node); // no DOM write — works identically for Element and DictEntry
|
|
110
|
+
const children = node instanceof Element ? Array.from(node.children) : node.children;
|
|
111
|
+
for (const [cid, cnode] of indexGroups(children, id))
|
|
112
|
+
map.set(cid, cnode);
|
|
113
|
+
});
|
|
114
|
+
return map;
|
|
115
|
+
};
|
|
116
|
+
const collectLeafValues = (node) => (node.children.length ? node.children.flatMap(collectLeafValues) : [node.value]);
|
|
117
|
+
/**
|
|
118
|
+
*
|
|
119
|
+
* @attr {menu|inline|navigation-link|sheet|automatic} picker-style
|
|
120
|
+
*
|
|
121
|
+
* @attr {string} help - Adds a help tooltip to the trigger of the picker, if style supports one
|
|
122
|
+
*
|
|
123
|
+
* @attr {DictEntry[]} dictionary - Renders all options using this array
|
|
124
|
+
*
|
|
125
|
+
* @attr {vertical|horizontal|auto} label-value-placement
|
|
126
|
+
*
|
|
127
|
+
* @slot list
|
|
128
|
+
*/
|
|
8
129
|
export class PickerView extends FormAssociatedBase {
|
|
9
130
|
static get ATTR() {
|
|
10
131
|
return {
|
|
11
|
-
PLACEHOLDER: '
|
|
132
|
+
PLACEHOLDER: 'prompt',
|
|
133
|
+
PLACEHOLDER_ICON: 'prompt-icon',
|
|
12
134
|
LABEL: 'label',
|
|
13
135
|
PICKER_STYLE: 'picker-style',
|
|
14
136
|
SELECTION: 'selection',
|
|
137
|
+
SEARCHABLE: 'searchable',
|
|
138
|
+
CURRENT_VALUE_LABEL: 'current-value-label',
|
|
139
|
+
CURRENT_VALUE_ICON: 'current-value-icon',
|
|
140
|
+
TRIGGER_HELP: 'help',
|
|
141
|
+
DICTIONARY: 'dictionary',
|
|
142
|
+
REQUIRED: 'required',
|
|
15
143
|
};
|
|
16
144
|
}
|
|
17
145
|
static get observedAttributes() {
|
|
18
146
|
return Object.values(this.ATTR);
|
|
19
147
|
}
|
|
20
148
|
static #templates = new Map();
|
|
149
|
+
#spawn;
|
|
150
|
+
#makeGroupClickHandler(el, groupId) {
|
|
151
|
+
return async (evt) => {
|
|
152
|
+
evt.stopImmediatePropagation();
|
|
153
|
+
evt.preventDefault();
|
|
154
|
+
const { target } = evt;
|
|
155
|
+
if (!(target instanceof HTMLElement))
|
|
156
|
+
return;
|
|
157
|
+
const newPage = this.#spawnPage(el instanceof Element ? Array.from(el.children) : el.children, 'body-view', groupId, this.hasAttribute('searchable'), extractLabel(el));
|
|
158
|
+
if (!newPage)
|
|
159
|
+
return;
|
|
160
|
+
newPage.dataset.groupId = groupId; // <-- tag the spawned page
|
|
161
|
+
const path = new NavigationPath(target)?.hydrate();
|
|
162
|
+
await startViewTransition(target, 'forwards', async () => {
|
|
163
|
+
update(path, newPage);
|
|
164
|
+
this.#reflectSelectionOnButtons();
|
|
165
|
+
});
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
#spawnPage = (elements, tag, parentGroupId, searchable = false, title) => {
|
|
169
|
+
if (devFlags.debug)
|
|
170
|
+
console.debug(`${_a.name} #spawnPage`);
|
|
171
|
+
const body = $(`<${'sheet-view' === tag ? 'dialog is="sheet-view"' : 'body-view'}><scroll-view><v-stack placement="leading fill"><list-view preferred-expanded-style="inset"></list-view></v-stack></scroll-view><tool-bar><tool-bar-item slot="top-bar-leading"><button type="button" tabindex="0"><label-view system-image="${'sheet-view' === tag ? 'x' : 'caret-left'}"></label-view></button></tool-bar-item></tool-bar></${'sheet-view' === tag ? 'dialog' : 'body-view'}>`, '>1'), sv = body.querySelector('scroll-view'), list = body.querySelector('list-view'), backBtn = body.querySelector('button');
|
|
172
|
+
if (title)
|
|
173
|
+
sv?.setAttribute('navigation-inline-title', title);
|
|
174
|
+
if (searchable) {
|
|
175
|
+
list?.style.setProperty('--list--sticky-block-size', '70px');
|
|
176
|
+
list?.insertAdjacentHTML('afterend', `<sticky-container edge="navbar" style="order: -1" padding>
|
|
177
|
+
<v-stack spacing="0" alignment="fill">
|
|
178
|
+
<input is="search-view" placeholder="Search">
|
|
179
|
+
<!--<button type="button">Filters</button>-->
|
|
180
|
+
</v-stack>
|
|
181
|
+
</sticky-container>`);
|
|
182
|
+
const searchInput = sv?.querySelector('input'); // FIXME: compoennt is=search-view??
|
|
183
|
+
searchInput?.addEventListener('focus', ({ target }) => {
|
|
184
|
+
if (this.#spawn)
|
|
185
|
+
this.dispatchEvent(new CustomEvent('picker:searchfocus', {
|
|
186
|
+
detail: { element: this.#spawn, search: target instanceof HTMLInputElement ? target.value : '' },
|
|
187
|
+
bubbles: true,
|
|
188
|
+
composed: true,
|
|
189
|
+
}));
|
|
190
|
+
});
|
|
191
|
+
searchInput?.addEventListener('blur', ({ target }) => {
|
|
192
|
+
if (this.#spawn)
|
|
193
|
+
this.dispatchEvent(new CustomEvent('picker:searchblur', {
|
|
194
|
+
detail: { element: this.#spawn, search: target instanceof HTMLInputElement ? target.value : '' },
|
|
195
|
+
bubbles: true,
|
|
196
|
+
composed: true,
|
|
197
|
+
}));
|
|
198
|
+
});
|
|
199
|
+
searchInput?.addEventListener('input', ({ target }) => {
|
|
200
|
+
if (this.#spawn)
|
|
201
|
+
this.dispatchEvent(new CustomEvent('picker:searchinput', {
|
|
202
|
+
detail: { element: this.#spawn, search: target instanceof HTMLInputElement ? target.value : '' },
|
|
203
|
+
bubbles: true,
|
|
204
|
+
composed: true,
|
|
205
|
+
}));
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
backBtn?.setAttribute('help', I18n.t('ButtonRole').Back);
|
|
209
|
+
backBtn?.addEventListener('click', async (evt) => {
|
|
210
|
+
evt.stopImmediatePropagation();
|
|
211
|
+
evt.preventDefault();
|
|
212
|
+
const { target } = evt;
|
|
213
|
+
if (!(target instanceof HTMLElement))
|
|
214
|
+
return;
|
|
215
|
+
const { component } = new NavigationPath(target)?.hydrate();
|
|
216
|
+
await startViewTransition(target, 'backwards', async () => {
|
|
217
|
+
component?.remove();
|
|
218
|
+
});
|
|
219
|
+
});
|
|
220
|
+
for (const [i, el] of elements.entries()) {
|
|
221
|
+
if (el instanceof Element)
|
|
222
|
+
switch (el.tagName) {
|
|
223
|
+
case 'OPTGROUP': {
|
|
224
|
+
const group = _a.#wrapOptgroupTag(el);
|
|
225
|
+
_a.#reflectButtons([...el.children], group);
|
|
226
|
+
for (const btn of group.querySelectorAll(':scope>button'))
|
|
227
|
+
btn.addEventListener('click', this.#handlePageBtnClick);
|
|
228
|
+
list?.appendChild(group);
|
|
229
|
+
break;
|
|
230
|
+
}
|
|
231
|
+
case 'OPTION': {
|
|
232
|
+
const btn = _a.#wrapOptionTag(el);
|
|
233
|
+
btn.addEventListener('click', this.#handlePageBtnClick);
|
|
234
|
+
list?.appendChild(btn);
|
|
235
|
+
break;
|
|
236
|
+
}
|
|
237
|
+
default: {
|
|
238
|
+
const btn = _a.#wrapOptionSpawnTag(el);
|
|
239
|
+
const groupId = parentGroupId ? `${parentGroupId}.${i}` : `${i}`;
|
|
240
|
+
// const btn = $<HTMLButtonElement>(
|
|
241
|
+
// `<button type="button" tabindex="0" navigation-link><h-stack distribution="leading" template="auto spacer"><label-view data-role="check" style="visibility: hidden"><image-view slot="icon" system-name="check"></image-view></label-view><label-view><span></span></label-view></h-stack></button>`,
|
|
242
|
+
// '>1'
|
|
243
|
+
// ),
|
|
244
|
+
// label = btn.querySelector<LabelView>(':scope>h-stack>label-view:nth-child(2)')
|
|
245
|
+
// const node = el as HTMLElement
|
|
246
|
+
// if (label && node.dataset.label) renderLabelTitle(label, node.dataset.label) //label?.setAttribute('title', el.dataset.label)
|
|
247
|
+
btn.dataset.groupId = groupId;
|
|
248
|
+
btn.addEventListener('click', this.#makeGroupClickHandler(el, groupId));
|
|
249
|
+
list?.appendChild(btn);
|
|
250
|
+
break;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
else {
|
|
254
|
+
if (el.children.length)
|
|
255
|
+
if (allLeaves(el)) {
|
|
256
|
+
const group = _a.#wrapOptgroupTag(el);
|
|
257
|
+
_a.#reflectButtons(el.children, group);
|
|
258
|
+
for (const btn of group.querySelectorAll(':scope>button'))
|
|
259
|
+
btn.addEventListener('click', this.#handlePageBtnClick);
|
|
260
|
+
list?.appendChild(group);
|
|
261
|
+
}
|
|
262
|
+
else {
|
|
263
|
+
const btn = _a.#wrapOptionSpawnTag(el);
|
|
264
|
+
const groupId = parentGroupId ? `${parentGroupId}.${i}` : `${i}`;
|
|
265
|
+
btn.dataset.groupId = groupId;
|
|
266
|
+
btn.addEventListener('click', this.#makeGroupClickHandler(el, groupId));
|
|
267
|
+
list?.appendChild(btn);
|
|
268
|
+
}
|
|
269
|
+
else {
|
|
270
|
+
const btn = _a.#wrapOptionTag(el);
|
|
271
|
+
btn.addEventListener('click', this.#handlePageBtnClick);
|
|
272
|
+
list?.appendChild(btn);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
return body;
|
|
277
|
+
};
|
|
278
|
+
#resyncSpawnedPages = (freshRoot) => {
|
|
279
|
+
if (!this.#spawn)
|
|
280
|
+
return;
|
|
281
|
+
const groupMap = indexGroups(freshRoot);
|
|
282
|
+
for (const el of this.#spawn.querySelectorAll('body-view')) {
|
|
283
|
+
const depth = $.ancestors('body-view,[is=sheet-view]', el).indexOf(this.#spawn);
|
|
284
|
+
if (0 >= depth)
|
|
285
|
+
continue;
|
|
286
|
+
const groupId = el.dataset.groupId;
|
|
287
|
+
const source = groupId ? groupMap.get(groupId) : undefined;
|
|
288
|
+
if (!source) {
|
|
289
|
+
el.remove();
|
|
290
|
+
continue;
|
|
291
|
+
}
|
|
292
|
+
const children = source instanceof Element ? Array.from(source.children) : source.children;
|
|
293
|
+
const title = source instanceof Element ? extractLabel(source) : (source.title ?? null);
|
|
294
|
+
const newPage = this.#spawnPage(children, 'body-view', groupId, this.hasAttribute('searchable'), title);
|
|
295
|
+
newPage.dataset.groupId = groupId;
|
|
296
|
+
reflectSpawnedPage(el, newPage);
|
|
297
|
+
}
|
|
298
|
+
};
|
|
299
|
+
#renderButtons(input) {
|
|
300
|
+
if (devFlags.debug)
|
|
301
|
+
console.debug(`${_a.name} #renderButtons`);
|
|
302
|
+
switch (input.mode) {
|
|
303
|
+
case 'dictionary': {
|
|
304
|
+
const flattenDictionary = (tree) => {
|
|
305
|
+
const out1 = {}, out2 = {}, stack = [tree], idx = [0];
|
|
306
|
+
while (stack.length) {
|
|
307
|
+
const frame = stack[stack.length - 1], i = idx[idx.length - 1];
|
|
308
|
+
if (i >= frame.length) {
|
|
309
|
+
stack.pop();
|
|
310
|
+
idx.pop();
|
|
311
|
+
continue;
|
|
312
|
+
}
|
|
313
|
+
idx[idx.length - 1]++;
|
|
314
|
+
const { value, title, systemImage, children } = frame[i];
|
|
315
|
+
out1[value] = title;
|
|
316
|
+
out2[value] = systemImage;
|
|
317
|
+
if (children.length) {
|
|
318
|
+
stack.push(children);
|
|
319
|
+
idx.push(0);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
return { labels: out1, icons: out2 };
|
|
323
|
+
};
|
|
324
|
+
this.#lastRenderedLabelMap = flattenDictionary(input.source).labels;
|
|
325
|
+
this.#lastRenderedIconMap = flattenDictionary(input.source).icons;
|
|
326
|
+
// const collectLeafValues = (node: DictEntry): string[] => (node.children.length ? node.children.flatMap(collectLeafValues) : [node.value])
|
|
327
|
+
// collectGroups = (nodes: DictEntry[]): string[][] => {
|
|
328
|
+
// const groups: string[][] = []
|
|
329
|
+
// const walk = (list: DictEntry[]) => {
|
|
330
|
+
// for (const node of list) {
|
|
331
|
+
// if (!node.children.length) continue
|
|
332
|
+
// if (!allLeaves(node)) groups.push(collectLeafValues(node))
|
|
333
|
+
// walk(node.children)
|
|
334
|
+
// }
|
|
335
|
+
// }
|
|
336
|
+
// walk(nodes)
|
|
337
|
+
// return groups
|
|
338
|
+
// }
|
|
339
|
+
this.#lastIndexedRoot = input.source; //this.#lastRenderedGroupMap = collectGroups(dictionary)
|
|
340
|
+
break;
|
|
341
|
+
}
|
|
342
|
+
case 'list': {
|
|
343
|
+
this.#lastRenderedLabelMap = {};
|
|
344
|
+
this.#lastRenderedIconMap = {};
|
|
345
|
+
for (const el of input.source)
|
|
346
|
+
if (el.matches('option')) {
|
|
347
|
+
const k = extractTag(el);
|
|
348
|
+
this.#lastRenderedLabelMap[k] = el.getAttribute('label') ?? undefined;
|
|
349
|
+
this.#lastRenderedIconMap[k] = extractIcon(el) ?? undefined;
|
|
350
|
+
}
|
|
351
|
+
else
|
|
352
|
+
for (const opt of el.querySelectorAll('option')) {
|
|
353
|
+
const k = extractTag(opt);
|
|
354
|
+
this.#lastRenderedLabelMap[k] = opt.getAttribute('label') ?? undefined;
|
|
355
|
+
this.#lastRenderedIconMap[k] = extractIcon(opt) ?? undefined;
|
|
356
|
+
}
|
|
357
|
+
this.#lastIndexedRoot = input.source;
|
|
358
|
+
// this.#lastRenderedGroupMap = assigned.flatMap<string[]>((el) =>
|
|
359
|
+
// [...(el.matches('datalist') ? [el as HTMLDataListElement] : []), ...el.querySelectorAll<HTMLDataListElement>('datalist')].map((dl) =>
|
|
360
|
+
// Array.from(dl.options).map((opt) => extractTagFromOption(opt))
|
|
361
|
+
// )
|
|
362
|
+
// )
|
|
363
|
+
break;
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
switch (this.pickerStyle) {
|
|
367
|
+
case 'sheet':
|
|
368
|
+
case 'navigation-link': {
|
|
369
|
+
// current value label only
|
|
370
|
+
const currentValueLabel = this.querySelector(':scope>label-view:not([slot])') ?? this.appendChild($(`<label-view></label-view>`, '>1'));
|
|
371
|
+
// reset state
|
|
372
|
+
// if (currentValueLabel) {
|
|
373
|
+
// renderLabelIcon(currentValueLabel, 'dots-three') // overwritten
|
|
374
|
+
// renderLabelTitle(currentValueLabel, this.#currentValueLabel) // overwritten
|
|
375
|
+
// }
|
|
376
|
+
// clear all siblings
|
|
377
|
+
for (const el of this.querySelectorAll(':scope>:not([slot])'))
|
|
378
|
+
if (currentValueLabel !== el)
|
|
379
|
+
el.remove();
|
|
380
|
+
CleanupRegistry.unregister(this, 'trigger');
|
|
381
|
+
CleanupRegistry.register(this, onoff('click', this.#handleTriggerClick, currentValueLabel).on(), 'trigger');
|
|
382
|
+
if (this.hasAttribute(this.constructor.ATTR.TRIGGER_HELP))
|
|
383
|
+
currentValueLabel?.setAttribute(this.constructor.ATTR.TRIGGER_HELP, this.getAttribute(this.constructor.ATTR.TRIGGER_HELP) ?? '');
|
|
384
|
+
// rebuild snapshot(tree)
|
|
385
|
+
if (!this.#spawn)
|
|
386
|
+
break;
|
|
387
|
+
// rerender level 0
|
|
388
|
+
reflectSpawnedPage(this.#spawn, this.#spawnPage(input.source, 'DIALOG' === this.#spawn.tagName ? 'sheet-view' : 'body-view', undefined, this.hasAttribute('searchable'), this.getAttribute('label')));
|
|
389
|
+
this.#resyncSpawnedPages(input.source);
|
|
390
|
+
// // FIXME:
|
|
391
|
+
// for (const el of this.#spawn.querySelectorAll<HTMLElement>('body-view')) {
|
|
392
|
+
// const depth = $.ancestors('body-view,[is=sheet-view]', el).indexOf(this.#spawn)
|
|
393
|
+
// if (0 >= depth) continue
|
|
394
|
+
// const datalist = this.querySelector<HTMLElement>(`:scope>${Array.from({ length: depth }, () => 'datalist').join('>')}`)
|
|
395
|
+
// if (!datalist) {
|
|
396
|
+
// el.remove()
|
|
397
|
+
// break
|
|
398
|
+
// }
|
|
399
|
+
// reflectSpawnedElement(el, this.#spawnPage(Array.from(datalist.children), 'body-view', this.hasAttribute('searchable'), datalist.dataset.label))
|
|
400
|
+
// }
|
|
401
|
+
break;
|
|
402
|
+
}
|
|
403
|
+
case 'menu': {
|
|
404
|
+
const menu = this.querySelector(':scope>menu-view:not([slot])') ?? this.appendChild($(`<menu-view></menu-view>`, '>1'));
|
|
405
|
+
// reset state
|
|
406
|
+
menu.innerHTML = '';
|
|
407
|
+
menu.tabIndex = 0;
|
|
408
|
+
// clear all siblings
|
|
409
|
+
for (const el of this.querySelectorAll(':scope>:not([slot])'))
|
|
410
|
+
if (menu !== el)
|
|
411
|
+
el.remove();
|
|
412
|
+
const currentValueLabel = menu.querySelector(':scope>label-view[slot=label]') ?? menu.appendChild($(`<label-view slot="label"></label-view>`, '>1'));
|
|
413
|
+
// if (currentValueLabel) {
|
|
414
|
+
// renderLabelIcon(currentValueLabel, 'dots-three') // overwritten
|
|
415
|
+
// renderLabelTitle(currentValueLabel, this.#currentValueLabel) // overwritten
|
|
416
|
+
// }
|
|
417
|
+
if (this.hasAttribute(this.constructor.ATTR.TRIGGER_HELP))
|
|
418
|
+
currentValueLabel?.setAttribute(this.constructor.ATTR.TRIGGER_HELP, this.getAttribute(this.constructor.ATTR.TRIGGER_HELP) ?? '');
|
|
419
|
+
_a.#reflectButtons(input.source, menu);
|
|
420
|
+
break;
|
|
421
|
+
}
|
|
422
|
+
case 'inline':
|
|
423
|
+
default: {
|
|
424
|
+
const sectionTpl = `<section-view></section-view>`;
|
|
425
|
+
const inlineList = this.querySelector(':scope>list-view:not([slot])') ?? this.appendChild($(`<list-view>${sectionTpl}</list-view>`, '>1')), section = inlineList.querySelector(':scope>section-view') ?? inlineList.appendChild($(sectionTpl, '>1'));
|
|
426
|
+
// reset state
|
|
427
|
+
section.innerHTML = '';
|
|
428
|
+
// clear all siblings
|
|
429
|
+
for (const el of this.querySelectorAll(':scope>:not([slot])'))
|
|
430
|
+
if (inlineList !== el)
|
|
431
|
+
el.remove();
|
|
432
|
+
// add label as a plain element
|
|
433
|
+
const value = this.getAttribute(this.constructor.ATTR.LABEL);
|
|
434
|
+
if (value) {
|
|
435
|
+
const hStack = $(`<h-stack distribution="leading" template="auto spacer"><label-view data-role="check" style="visibility: hidden"><image-view slot="icon" system-name="check"></image-view></label-view><label-view><span></span></label-view></h-stack>`, '>1');
|
|
436
|
+
renderLabel(':scope>label-view:nth-child(2)', `<label-view><span></span></label-view>`, hStack, value);
|
|
437
|
+
// if (label) renderLabelTitle(label, value) //label.setAttribute('title', value)
|
|
438
|
+
section.insertAdjacentElement('beforeend', hStack);
|
|
439
|
+
}
|
|
440
|
+
if ('dictionary' === input.mode)
|
|
441
|
+
_a.#reflectButtons(input.source.filter((el) => 0 === el.children.length), section);
|
|
442
|
+
else
|
|
443
|
+
_a.#reflectButtons(input.source.filter((el) => el.matches('option')), section);
|
|
444
|
+
break;
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
this.#reflectSelectionOnButtons();
|
|
448
|
+
this.#reflectSelectionOnCurrentValueLabel();
|
|
449
|
+
}
|
|
450
|
+
#renderDictionary = (dictionary) => {
|
|
451
|
+
if (devFlags.debug)
|
|
452
|
+
console.debug(`${_a.name} ⚡️ mutation`);
|
|
453
|
+
this.#renderButtons({ mode: 'dictionary', source: dictionary });
|
|
454
|
+
};
|
|
455
|
+
#renderSlotted = (entries) => {
|
|
456
|
+
if (devFlags.debug)
|
|
457
|
+
console.debug(`${_a.name} ⚡️ mutation`);
|
|
458
|
+
this.#renderButtons({ mode: 'list', source: this.#slots?.get('list')?.assignedElements({ flatten: true }) ?? [] });
|
|
459
|
+
};
|
|
460
|
+
#renderValidityMsgs = (entries) => {
|
|
461
|
+
if (devFlags.debug)
|
|
462
|
+
console.debug(`${_a.name} ⚡️ mutation`);
|
|
463
|
+
this.setValidity(this.validity, this.validationMessage);
|
|
464
|
+
};
|
|
21
465
|
#lastRenderedStyle; //string | null
|
|
22
466
|
#shadowRoot;
|
|
467
|
+
#slots = new Map();
|
|
468
|
+
#validityObservers = new MutationObserverSet(this.#renderValidityMsgs);
|
|
469
|
+
#observers = new MutationObserverSet(this.#renderSlotted);
|
|
23
470
|
#customValidity = '';
|
|
24
|
-
#validitiesSlot;
|
|
25
|
-
#datalistSlot;
|
|
26
|
-
#tagSlot;
|
|
27
|
-
#trackedElements = new Set();
|
|
28
471
|
#selection = '';
|
|
472
|
+
get selection() {
|
|
473
|
+
return this.#selection;
|
|
474
|
+
}
|
|
475
|
+
set selection(v) {
|
|
476
|
+
if (Object.is(this.#selection, v))
|
|
477
|
+
return;
|
|
478
|
+
this.#selection = v;
|
|
479
|
+
this.#reflectSelectionOnButtons();
|
|
480
|
+
this.#reflectSelectionOnCurrentValueLabel();
|
|
481
|
+
this.#sendValueToForm();
|
|
482
|
+
}
|
|
483
|
+
#lastRenderedLabelMap = {};
|
|
484
|
+
#lastRenderedIconMap = {};
|
|
485
|
+
#lastIndexedRoot = [];
|
|
486
|
+
get #currentValueLabel() {
|
|
487
|
+
return ((this.getAttribute(this.constructor.ATTR.CURRENT_VALUE_LABEL) ?? '').replaceAll('{{selection}}', this.#selection).replaceAll('{{currentValueLabel}}', this.#selection) ||
|
|
488
|
+
this.#lastRenderedLabelMap[this.#selection]);
|
|
489
|
+
}
|
|
490
|
+
get #currentValueIcon() {
|
|
491
|
+
return (this.getAttribute(this.constructor.ATTR.CURRENT_VALUE_ICON) ?? '') || this.#lastRenderedIconMap[this.#selection];
|
|
492
|
+
}
|
|
29
493
|
get #internals() {
|
|
30
494
|
return getInternals(this);
|
|
31
495
|
}
|
|
32
496
|
get template() {
|
|
33
|
-
// const style = this.getAttribute((this.constructor as typeof PickerView).ATTR.PICKER_STYLE) ?? ''
|
|
34
497
|
if (!_a.#templates.has(this.pickerStyle))
|
|
35
498
|
switch (this.pickerStyle) {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
499
|
+
case 'sheet':
|
|
500
|
+
case 'navigation-link':
|
|
501
|
+
_a.#templates.set(this.pickerStyle, $(String.raw `
|
|
502
|
+
<label part="root picker-stack">
|
|
503
|
+
<div part="root picker-label-stack">
|
|
504
|
+
<slot name="label"></slot>
|
|
505
|
+
</div>
|
|
506
|
+
<div part="root picker-input-stack">
|
|
507
|
+
<slot></slot>
|
|
508
|
+
</div>
|
|
509
|
+
<slot name="list" hidden></slot>
|
|
510
|
+
<slot name="validity-options" hidden></slot>
|
|
511
|
+
</label>
|
|
512
|
+
`));
|
|
513
|
+
break;
|
|
43
514
|
case 'menu':
|
|
44
|
-
// template.innerHTML = `<slot name="options"></slot><slot></slot>`
|
|
45
515
|
_a.#templates.set(this.pickerStyle, $(String.raw `
|
|
46
516
|
<label part="root picker-stack">
|
|
47
517
|
<div part="root picker-label-stack">
|
|
@@ -50,11 +520,10 @@ export class PickerView extends FormAssociatedBase {
|
|
|
50
520
|
<div part="root picker-input-stack">
|
|
51
521
|
<slot></slot>
|
|
52
522
|
</div>
|
|
53
|
-
<slot name="
|
|
54
|
-
<slot name="tag" hidden></slot>
|
|
523
|
+
<slot name="list" hidden></slot>
|
|
55
524
|
<slot name="validity-options" hidden></slot>
|
|
56
525
|
</label>
|
|
57
|
-
|
|
526
|
+
`));
|
|
58
527
|
// break
|
|
59
528
|
// case 'compact':
|
|
60
529
|
// PickerView.#templates.set(
|
|
@@ -96,7 +565,7 @@ export class PickerView extends FormAssociatedBase {
|
|
|
96
565
|
// <option value="0" label="0%"></option>
|
|
97
566
|
// </datalist>
|
|
98
567
|
// </div>
|
|
99
|
-
// <slot name="
|
|
568
|
+
// <slot name="list" hidden></slot>
|
|
100
569
|
// </label>`,
|
|
101
570
|
// })
|
|
102
571
|
// )
|
|
@@ -111,10 +580,9 @@ export class PickerView extends FormAssociatedBase {
|
|
|
111
580
|
<div part="root picker-input-stack">
|
|
112
581
|
<slot></slot>
|
|
113
582
|
</div>
|
|
114
|
-
<slot name="
|
|
115
|
-
<slot name="tag" hidden></slot>
|
|
583
|
+
<slot name="list" hidden></slot>
|
|
116
584
|
<slot name="validity-options" hidden></slot>
|
|
117
|
-
</label
|
|
585
|
+
</label>`));
|
|
118
586
|
break;
|
|
119
587
|
}
|
|
120
588
|
return _a.#templates.get(this.pickerStyle);
|
|
@@ -122,97 +590,103 @@ export class PickerView extends FormAssociatedBase {
|
|
|
122
590
|
constructor() {
|
|
123
591
|
super();
|
|
124
592
|
this.#shadowRoot = this.attachShadow({ mode: 'closed' });
|
|
125
|
-
// this.#render() // Snapshot.waitReady.then(this.#render.bind(this))
|
|
126
|
-
}
|
|
127
|
-
connectedCallback() {
|
|
128
|
-
console.debug(`${_a.name} ⚡️ connect`);
|
|
129
|
-
CleanupRegistry.register(this, onoff('click', this.#handleClick, this).on());
|
|
130
|
-
if (!this.hasAttribute(this.constructor.ATTR.PICKER_STYLE))
|
|
131
|
-
this.#render(); // will be picked up by attr-change!
|
|
132
|
-
// finally
|
|
133
|
-
if (!this.hasAttribute('selection'))
|
|
134
|
-
return;
|
|
135
|
-
this.#selection = this.getAttribute('selection') ?? '';
|
|
136
|
-
this.#sendValueToForm(false);
|
|
137
|
-
}
|
|
138
|
-
disconnectedCallback() {
|
|
139
|
-
console.debug(`${_a.name} ⚡️ disconnect`);
|
|
140
|
-
CleanupRegistry.unregister(this);
|
|
141
|
-
observers.clearObservationsSet(this.#trackedElements);
|
|
142
593
|
}
|
|
143
594
|
attributeChangedCallback(name, oldValue, newValue) {
|
|
144
|
-
|
|
595
|
+
if (devFlags.debug)
|
|
596
|
+
console.debug(`${_a.name} ⚡️ attr-change [${name}] ("${oldValue}" → "${newValue}")`);
|
|
145
597
|
switch (name) {
|
|
146
598
|
case this.constructor.ATTR.PLACEHOLDER:
|
|
147
|
-
|
|
599
|
+
case this.constructor.ATTR.PLACEHOLDER_ICON:
|
|
600
|
+
if (oldValue === newValue)
|
|
601
|
+
break;
|
|
602
|
+
this.#reflectSelectionOnCurrentValueLabel(); //this.#reflectPlaceholder(newValue)
|
|
148
603
|
break;
|
|
149
604
|
case this.constructor.ATTR.LABEL:
|
|
150
605
|
this.#reflectLabel(newValue);
|
|
151
|
-
this.#sendValueToForm()
|
|
606
|
+
// this.#sendValueToForm()
|
|
152
607
|
break;
|
|
153
608
|
case this.constructor.ATTR.PICKER_STYLE:
|
|
154
609
|
if (oldValue === newValue)
|
|
155
610
|
break;
|
|
156
611
|
this.#render();
|
|
157
|
-
|
|
612
|
+
break;
|
|
613
|
+
case this.constructor.ATTR.SEARCHABLE:
|
|
614
|
+
if (oldValue === newValue)
|
|
615
|
+
break;
|
|
616
|
+
this.#renderSlotted([]);
|
|
617
|
+
break;
|
|
618
|
+
case this.constructor.ATTR.CURRENT_VALUE_LABEL:
|
|
619
|
+
case this.constructor.ATTR.CURRENT_VALUE_ICON:
|
|
620
|
+
// if (oldValue === newValue) break
|
|
621
|
+
this.#reflectSelectionOnCurrentValueLabel();
|
|
622
|
+
break;
|
|
623
|
+
case this.constructor.ATTR.TRIGGER_HELP:
|
|
624
|
+
// if (oldValue === newValue) break
|
|
625
|
+
this.#reflectTriggerHelp();
|
|
626
|
+
break;
|
|
627
|
+
case this.constructor.ATTR.DICTIONARY:
|
|
628
|
+
if (oldValue === newValue)
|
|
629
|
+
break;
|
|
630
|
+
this.#renderDictionary(parseDictionary(newValue));
|
|
631
|
+
break;
|
|
632
|
+
case this.constructor.ATTR.REQUIRED:
|
|
633
|
+
if (oldValue === newValue)
|
|
634
|
+
break;
|
|
635
|
+
this.#sendValueToForm(false);
|
|
158
636
|
break;
|
|
159
637
|
case this.constructor.ATTR.SELECTION:
|
|
160
638
|
// nothing happens
|
|
161
639
|
break;
|
|
162
640
|
}
|
|
163
641
|
}
|
|
642
|
+
disconnectedCallback() {
|
|
643
|
+
if (devFlags.debug)
|
|
644
|
+
console.debug(`${_a.name} ⚡️ disconnect`);
|
|
645
|
+
CleanupRegistry.unregister(this);
|
|
646
|
+
this.#validityObservers.unobserveAll();
|
|
647
|
+
this.#observers.unobserveAll();
|
|
648
|
+
}
|
|
649
|
+
connectedCallback() {
|
|
650
|
+
if (devFlags.debug)
|
|
651
|
+
console.debug(`${_a.name} ⚡️ connect`);
|
|
652
|
+
CleanupRegistry.register(this, onoff('localechange', () => {
|
|
653
|
+
this.#renderSlotted([]);
|
|
654
|
+
}, I18n.on).on());
|
|
655
|
+
CleanupRegistry.register(this, onoff('click', this.#handleClick, this).on());
|
|
656
|
+
if (!this.hasAttribute(this.constructor.ATTR.PICKER_STYLE))
|
|
657
|
+
this.#render(); // skip if already rendered by attr-change during upgrade!
|
|
658
|
+
// finally
|
|
659
|
+
if (!this.hasAttribute('selection'))
|
|
660
|
+
return;
|
|
661
|
+
this.#selection = this.getAttribute('selection') ?? '';
|
|
662
|
+
this.#sendValueToForm(false);
|
|
663
|
+
}
|
|
164
664
|
get pickerStyle() {
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
: 'automatic';
|
|
665
|
+
const attr = this.constructor.ATTR.PICKER_STYLE;
|
|
666
|
+
return pickerStyles.includes(this.getAttribute(attr) ?? '') ? this.getAttribute(attr) : 'automatic';
|
|
168
667
|
}
|
|
169
668
|
#render() {
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
return;
|
|
669
|
+
if (devFlags.debug)
|
|
670
|
+
console.debug(`${_a.name} ⚡️ render (${this.pickerStyle})`);
|
|
173
671
|
// const style = this.getAttribute((this.constructor as typeof PickerView).ATTR.PICKER_STYLE)
|
|
174
672
|
if (this.#lastRenderedStyle === this.pickerStyle)
|
|
175
673
|
return; // skip if already applied
|
|
176
674
|
this.#lastRenderedStyle = this.pickerStyle;
|
|
177
675
|
// clear shadow DOM
|
|
178
676
|
this.#shadowRoot.replaceChildren(document.importNode(this.template, true));
|
|
179
|
-
this
|
|
180
|
-
|
|
181
|
-
|
|
677
|
+
CleanupRegistry.unregister(this, 'slots');
|
|
678
|
+
for (const slot of this.#shadowRoot.querySelectorAll('slot'))
|
|
679
|
+
this.#slots?.set(slot.name, slot);
|
|
680
|
+
CleanupRegistry.register(this, () => {
|
|
681
|
+
this.#slots = new Map();
|
|
682
|
+
}, 'slots');
|
|
182
683
|
CleanupRegistry.unregister(this, 'validities');
|
|
183
|
-
CleanupRegistry.register(this, onoff(
|
|
684
|
+
CleanupRegistry.register(this, onoff('slotchange', this.#handleValiditiesSlotchange, this.#slots?.get('validity-options')).on(), 'validities');
|
|
184
685
|
CleanupRegistry.unregister(this, 'datalist'); //off1()
|
|
185
|
-
CleanupRegistry.register(this, onoff('slotchange', this.#
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
//
|
|
189
|
-
// if (0 < (this.#tagSlot?.assignedElements({ flatten: true }) ?? []).length) this.#handleTagMutation()
|
|
190
|
-
//this.getAttribute((this.constructor as typeof PickerView).ATTR.PICKER_STYLE)) {
|
|
191
|
-
switch (this.pickerStyle
|
|
192
|
-
// case 'menu':
|
|
193
|
-
// // if (0 < (this.#datalistSlot?.assignedElements({ flatten: true }) ?? []).length) this.#handleMenuDatalistMutation()
|
|
194
|
-
// break
|
|
195
|
-
// case 'gg':
|
|
196
|
-
// // reattach input listener
|
|
197
|
-
// const input = this.#shadowRoot.querySelector('input')
|
|
198
|
-
// if (input) {
|
|
199
|
-
// input.addEventListener('input', () => {
|
|
200
|
-
// this.#internals!.setFormValue(input.value)
|
|
201
|
-
// })
|
|
202
|
-
// // restore placeholder if set
|
|
203
|
-
// const placeholder = this.getAttribute((this.constructor as typeof PickerView).ATTR.PLACEHOLDER)
|
|
204
|
-
// if (placeholder) input.setAttribute('placeholder', placeholder)
|
|
205
|
-
// }
|
|
206
|
-
// // restore label if set
|
|
207
|
-
// const label = this.getAttribute((this.constructor as typeof PickerView).ATTR.LABEL)
|
|
208
|
-
// if (label) this.#reflectLabel(label)
|
|
209
|
-
// break
|
|
210
|
-
// case 'inline':
|
|
211
|
-
// default:
|
|
212
|
-
// // if (0 < (this.#datalistSlot?.assignedElements({ flatten: true }) ?? []).length) this.#handleInlineDatalistMutation()
|
|
213
|
-
// break
|
|
214
|
-
) {
|
|
215
|
-
}
|
|
686
|
+
CleanupRegistry.register(this, onoff('slotchange', this.#handleListSlotchange, this.#slots?.get('list')).on(), 'datalist');
|
|
687
|
+
// #renderSlotted should run automatically now by slotchange initial event
|
|
688
|
+
// switch (this.pickerStyle) {
|
|
689
|
+
// }
|
|
216
690
|
}
|
|
217
691
|
#sendValueToForm = (dispatchEvent = true) => {
|
|
218
692
|
// input.value has already been updated/synced !!
|
|
@@ -233,157 +707,369 @@ export class PickerView extends FormAssociatedBase {
|
|
|
233
707
|
if (dispatchEvent)
|
|
234
708
|
this.dispatchEvent(new CustomEvent('selection', { detail: { selection: this.#selection }, bubbles: true, composed: true }));
|
|
235
709
|
};
|
|
236
|
-
#
|
|
237
|
-
|
|
238
|
-
|
|
710
|
+
#handleTriggerClick = async ({ type, target }) => {
|
|
711
|
+
if (devFlags.debug)
|
|
712
|
+
console.debug(`${_a.name} ⚡️ ${type}`);
|
|
713
|
+
if (!(target instanceof HTMLElement))
|
|
714
|
+
return;
|
|
715
|
+
this.#spawn?.remove?.();
|
|
716
|
+
const level0 = this.#spawnPage(this.hasAttribute(this.constructor.ATTR.DICTIONARY) ? parseDictionary(this.getAttribute('dictionary')) : (this.#slots?.get('list')?.assignedElements() ?? []), 'sheet' === this.pickerStyle ? 'sheet-view' : 'body-view', undefined, this.hasAttribute('searchable'), this.getAttribute('label'));
|
|
717
|
+
if (!level0)
|
|
718
|
+
return;
|
|
719
|
+
const path = new NavigationPath(target)?.hydrate();
|
|
720
|
+
await startViewTransition(target, 'forwards', async () => {
|
|
721
|
+
this.#spawn = level0;
|
|
722
|
+
update(path, level0);
|
|
723
|
+
this.#reflectSelectionOnButtons();
|
|
724
|
+
});
|
|
725
|
+
};
|
|
726
|
+
#handlePageBtnClick = async (evt) => {
|
|
727
|
+
if (devFlags.debug)
|
|
728
|
+
console.debug(`${_a.name} ⚡️ ${evt?.type}`);
|
|
729
|
+
evt.stopImmediatePropagation();
|
|
730
|
+
evt.preventDefault();
|
|
731
|
+
const { currentTarget: btn } = evt;
|
|
732
|
+
if (!(btn instanceof HTMLElement))
|
|
733
|
+
return;
|
|
734
|
+
// const btn = target.closest('button')
|
|
735
|
+
// if (!btn) return
|
|
736
|
+
const proceed = () => {
|
|
737
|
+
this.#spawn?.remove();
|
|
738
|
+
this.#selection = btn.getAttribute('value') ?? '';
|
|
739
|
+
this.#reflectSelectionOnButtons();
|
|
740
|
+
this.#reflectSelectionOnCurrentValueLabel();
|
|
741
|
+
this.#sendValueToForm();
|
|
742
|
+
};
|
|
743
|
+
const { body } = new NavigationPath(this.#spawn)?.hydrate();
|
|
744
|
+
if (!body)
|
|
745
|
+
return proceed();
|
|
746
|
+
await startViewTransition(body, 'backwards', async () => {
|
|
747
|
+
proceed();
|
|
748
|
+
});
|
|
749
|
+
};
|
|
750
|
+
#handleClick({ type, target }) {
|
|
751
|
+
if (devFlags.debug)
|
|
752
|
+
console.debug(`${_a.name} ⚡️ ${type}`);
|
|
753
|
+
if (!(target instanceof HTMLElement))
|
|
754
|
+
return;
|
|
755
|
+
const btn = target.closest('button');
|
|
239
756
|
if (!btn)
|
|
240
757
|
return;
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
else {
|
|
245
|
-
this.#selection = btn.textContent; //this.dispatchEvent(new CustomEvent('selection', { detail: { tag: btn.textContent }, bubbles: true, composed: true }))
|
|
246
|
-
}
|
|
758
|
+
this.#selection = btn.getAttribute('value') ?? '';
|
|
759
|
+
this.#reflectSelectionOnButtons();
|
|
760
|
+
this.#reflectSelectionOnCurrentValueLabel();
|
|
247
761
|
this.#sendValueToForm();
|
|
248
762
|
}
|
|
249
|
-
#
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
//
|
|
257
|
-
|
|
258
|
-
// for (const el of assigned) {
|
|
259
|
-
// if (!this.#trackedElements.has(el))
|
|
260
|
-
// observers.observe(el, this.#handleTagMutation, {
|
|
261
|
-
// attributes: true,
|
|
262
|
-
// characterData: true,
|
|
263
|
-
// subtree: true,
|
|
264
|
-
// childList: true,
|
|
265
|
-
// // attributeFilter: ['value', 'label'],
|
|
266
|
-
// })
|
|
267
|
-
// this.#trackedElements.add(el)
|
|
268
|
-
// }
|
|
269
|
-
if (0 < assigned.length)
|
|
270
|
-
this.#handleTagMutation();
|
|
763
|
+
#handleValiditiesSlotchange = ({ type, target: slot }) => {
|
|
764
|
+
if (devFlags.debug)
|
|
765
|
+
console.debug(`${_a.name} ⚡️ ${type}`);
|
|
766
|
+
if (!(slot instanceof HTMLSlotElement))
|
|
767
|
+
return;
|
|
768
|
+
const assigned = slot.assignedElements();
|
|
769
|
+
this.#validityObservers.syncObservations(assigned, ['value', 'label']);
|
|
770
|
+
// if (0 < assigned.length)
|
|
771
|
+
this.#renderValidityMsgs([]);
|
|
271
772
|
};
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
773
|
+
#handleListSlotchange = ({ type, target: slot }) => {
|
|
774
|
+
if (devFlags.debug)
|
|
775
|
+
console.debug(`${_a.name} ⚡️ ${type}`);
|
|
776
|
+
if (!(slot instanceof HTMLSlotElement))
|
|
777
|
+
return;
|
|
778
|
+
const assigned = slot.assignedElements();
|
|
779
|
+
this.#observers.syncObservations(assigned);
|
|
780
|
+
// if (0 < assigned.length)
|
|
781
|
+
this.#renderSlotted([]);
|
|
782
|
+
};
|
|
783
|
+
static #wrapOptionSpawnTag(node) {
|
|
784
|
+
if (devFlags.debug)
|
|
785
|
+
console.debug(`${_a.name} #wrapOptionSpawnTag`);
|
|
786
|
+
const title = extractLabel(node), subtitle = extractSubtitle(node), icon = extractIcon(node);
|
|
787
|
+
const mount = document.createElement('div');
|
|
788
|
+
render(html `<button type="button" tabindex="0" navigation-link>
|
|
789
|
+
<h-stack distribution="leading" template="auto spacer">
|
|
790
|
+
<label-view data-role="check" style="visibility: hidden">
|
|
791
|
+
<image-view slot="icon" system-name="check"></image-view>
|
|
792
|
+
</label-view>
|
|
793
|
+
<h-stack distribution="fill" template="${icon ? 'auto spacer' : null}">
|
|
794
|
+
${icon ? html `<label-view system-image="${icon}"></label-view>` : null}
|
|
795
|
+
<v-stack spacing="3" alignment="fill">
|
|
796
|
+
<label-view title="${title}"></label-view>
|
|
797
|
+
${subtitle ? html `<label-view foreground="secondary" font="callout" title="${subtitle}"></label-view>` : null}
|
|
798
|
+
</v-stack>
|
|
799
|
+
</h-stack>
|
|
800
|
+
</h-stack>
|
|
801
|
+
</button>`, mount);
|
|
802
|
+
// const btn = $<HTMLButtonElement>(
|
|
803
|
+
// `<button type="button" tabindex="0" navigation-link><h-stack distribution="leading" template="auto spacer"><label-view data-role="check" style="visibility: hidden"><image-view slot="icon" system-name="check"></image-view></label-view><label-view><span></span></label-view></h-stack></button>`,
|
|
804
|
+
// '>1'
|
|
805
|
+
// ),
|
|
806
|
+
// hStack = btn.querySelector<LabelView>(':scope>h-stack') ?? undefined
|
|
807
|
+
// renderLabel(
|
|
808
|
+
// ':scope>label-view:nth-child(2)',
|
|
809
|
+
// `<label-view><span></span></label-view>`,
|
|
810
|
+
// hStack,
|
|
811
|
+
// extractLabelFromGroup(node as HTMLDataListElement),
|
|
812
|
+
// extractImgFromGroup(node as HTMLDataListElement)
|
|
813
|
+
// )
|
|
814
|
+
// // label = btn.querySelector<LabelView>(':scope>h-stack>label-view:nth-child(2)')
|
|
815
|
+
// // if (label) {
|
|
816
|
+
// // const lbl = extractLabelFromGroup(node as HTMLDataListElement),
|
|
817
|
+
// // img =extractImgFromGroup(node as HTMLDataListElement)
|
|
818
|
+
// // if(lbl) renderLabelTitle(label, node.dataset.label) //label?.setAttribute('title', el.dataset.label)
|
|
819
|
+
// // }
|
|
820
|
+
return mount.firstElementChild; //btn
|
|
293
821
|
}
|
|
294
|
-
#
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
822
|
+
static #wrapOptionTag(node) {
|
|
823
|
+
if (devFlags.debug)
|
|
824
|
+
console.debug(`${_a.name} #wrapOptionTag`);
|
|
825
|
+
const tag = extractTag(node), title = extractLabel(node), subtitle = extractSubtitle(node), icon = extractIcon(node);
|
|
826
|
+
const mount = document.createElement('div');
|
|
827
|
+
render(html `<button type="button" tabindex="0" value="${tag}">
|
|
828
|
+
<h-stack distribution="leading" template="auto spacer">
|
|
829
|
+
<label-view data-role="check">
|
|
830
|
+
<image-view slot="icon" system-name="check"></image-view>
|
|
831
|
+
</label-view>
|
|
832
|
+
<h-stack distribution="fill" template="${icon ? 'auto spacer' : null}">
|
|
833
|
+
${icon ? html `<label-view system-image="${icon}"></label-view>` : null}
|
|
834
|
+
<v-stack spacing="3" alignment="fill">
|
|
835
|
+
<label-view title="${title}"></label-view>
|
|
836
|
+
${subtitle ? html `<label-view foreground="secondary" font="callout" title="${subtitle}"></label-view>` : null}
|
|
837
|
+
</v-stack>
|
|
838
|
+
</h-stack>
|
|
839
|
+
</h-stack>
|
|
840
|
+
</button>`, mount);
|
|
841
|
+
// const btn = $(
|
|
842
|
+
// `<button type="button" tabindex="0"><h-stack distribution="leading" template="auto spacer"><label-view data-role="check"><image-view slot="icon" system-name="check"></image-view></label-view></h-stack></button>`,
|
|
843
|
+
// '>1'
|
|
844
|
+
// ),
|
|
845
|
+
// hStack = btn.querySelector<HTMLElement>(':scope>h-stack')
|
|
846
|
+
// // chevron = hStack?.querySelector<HTMLElement>(':scope>label-view')
|
|
847
|
+
// btn.setAttribute('value', extractTagFromOption(node))
|
|
848
|
+
// // if (selection !== btn.getAttribute('value')) chevron?.style.setProperty('visibility', 'hidden')
|
|
849
|
+
// const label = $<LabelView>(`<label-view></label-view>`, '>1')
|
|
850
|
+
// renderLabelTitle(label, extractLabel(node)) // label.querySelector('span')!.textContent = extractCurrentValueFromOption(node) //label.setAttribute('title', extractCurrentValueFromOption(node))
|
|
851
|
+
// renderLabelIcon(label, extractIcon(node))
|
|
852
|
+
// hStack?.appendChild(label)
|
|
853
|
+
return mount.firstElementChild; //btn
|
|
854
|
+
}
|
|
855
|
+
static #wrapOptgroupTag(node) {
|
|
856
|
+
if (devFlags.debug)
|
|
857
|
+
console.debug(`${_a.name} #wrapOptgroupTag`);
|
|
858
|
+
const title = extractLabel(node), subtitle = extractSubtitle(node), icon = extractIcon(node);
|
|
859
|
+
const mount = document.createElement('div');
|
|
860
|
+
render(html `<details is="disclosure-group" disclosure-style="marker-trailing">
|
|
861
|
+
<summary>
|
|
862
|
+
<h-stack distribution="leading" template="auto spacer">
|
|
863
|
+
<label-view data-role="check" style="visibility: hidden">
|
|
864
|
+
<image-view slot="icon" system-name="check"></image-view>
|
|
865
|
+
</label-view>
|
|
866
|
+
<h-stack distribution="fill" template="${icon ? 'auto spacer' : null}">
|
|
867
|
+
${icon ? html `<label-view system-image="${icon}"></label-view>` : null}
|
|
868
|
+
<v-stack spacing="3" alignment="fill">
|
|
869
|
+
<label-view title="${title}"></label-view>
|
|
870
|
+
${subtitle ? html `<label-view foreground="secondary" font="callout" title="${subtitle}"></label-view>` : null}
|
|
871
|
+
</v-stack>
|
|
872
|
+
</h-stack>
|
|
873
|
+
</h-stack>
|
|
874
|
+
</summary>
|
|
875
|
+
</details>`, mount);
|
|
876
|
+
return mount.firstElementChild;
|
|
877
|
+
// const labelT = `<label-view><span></span></label-view>`,
|
|
878
|
+
// summaryT = `<summary><h-stack distribution="leading" template="auto spacer"><label-view data-role="check" style="visibility: hidden"><image-view slot="icon" system-name="check"></image-view></label-view>${labelT}</h-stack></summary>`
|
|
879
|
+
// const group = $(`<details is="disclosure-group" disclosure-style="marker-trailing">${summaryT}</details>`, '>1'), // NOTE: already applied, here it covers spawned sheets
|
|
880
|
+
// hStack = group.querySelector(':scope>summary>h-stack') ?? undefined // ?? group.appendChild($(summaryT, '>1'))
|
|
881
|
+
// // summaryLabel = summary.querySelector(':scope>label-view') ?? summary.appendChild($(labelT, '>1'))
|
|
882
|
+
// // if (node.hasAttribute('label')) summaryLabel.setAttribute('title', node.getAttribute('label') ?? '')
|
|
883
|
+
// // if (node.hasAttribute('data-system-image')) summaryLabel.setAttribute('system-image', node.getAttribute('data-system-image') ?? '')
|
|
884
|
+
// renderLabel(':scope>label-view:nth-child(2)', labelT, hStack, extractLabel(node), extractIcon(node))
|
|
885
|
+
// return group
|
|
886
|
+
}
|
|
887
|
+
// static #reflectButtons(nodes: Element[], container: Element): void
|
|
888
|
+
// static #reflectButtons(nodes: Dictionary, container: Element): void
|
|
889
|
+
static #reflectButtons(nodes, container) {
|
|
890
|
+
if (devFlags.debug)
|
|
891
|
+
console.debug(`${_a.name} #reflectButtons`);
|
|
892
|
+
for (const node of nodes)
|
|
893
|
+
if (node instanceof Element)
|
|
894
|
+
switch (node.tagName) {
|
|
895
|
+
case 'DATALIST': {
|
|
896
|
+
const title = extractLabel(node), subtitle = extractSubtitle(node), icon = extractIcon(node);
|
|
897
|
+
const mount = document.createElement('div');
|
|
898
|
+
render(html `<menu-view tabindex="0">
|
|
899
|
+
<h-stack slot="label" distribution="leading" template="auto spacer">
|
|
900
|
+
<label-view data-role="check" style="visibility: hidden">
|
|
901
|
+
<image-view slot="icon" system-name="check"></image-view>
|
|
902
|
+
</label-view>
|
|
903
|
+
<h-stack distribution="fill" template="${icon ? 'auto spacer' : null}">
|
|
904
|
+
${icon ? html `<label-view system-image="${icon}"></label-view>` : null}
|
|
905
|
+
<v-stack spacing="3" alignment="fill">
|
|
906
|
+
<label-view title="${title}"></label-view>
|
|
907
|
+
${subtitle ? html `<label-view foreground="secondary" font="callout" title="${subtitle}"></label-view>` : null}
|
|
908
|
+
</v-stack>
|
|
909
|
+
</h-stack>
|
|
910
|
+
</h-stack>
|
|
911
|
+
</menu-view>`, mount);
|
|
912
|
+
const group = mount.firstElementChild;
|
|
913
|
+
// const group = $(
|
|
914
|
+
// `<menu-view tabindex="0"><h-stack slot="label" distribution="leading" template="auto spacer"><label-view data-role="check" style="visibility: hidden"><image-view slot="icon" system-name="check"></image-view></label-view><label-view><span></span></label-view></h-stack></menu-view>`,
|
|
915
|
+
// '>1'
|
|
916
|
+
// ),
|
|
917
|
+
// hStack = group.querySelector(':scope>h-stack[slot=label]') ?? undefined
|
|
918
|
+
// // label = group.querySelector(':scope>label-view[slot=label]') ?? group.appendChild($(`<label-view slot="label"></label-view>`, '>1'))
|
|
919
|
+
// // if (node.hasAttribute('data-label')) label.setAttribute('title', node.getAttribute('data-label') ?? '')
|
|
920
|
+
// // if (node.hasAttribute('data-system-image')) label.setAttribute('system-image', node.getAttribute('data-system-image') ?? '')
|
|
921
|
+
// renderLabel(':scope>label-view:nth-child(2)', `<label-view><span></span></label-view>`, hStack, extractLabel(node as HTMLDataListElement), extractIcon(node as HTMLDataListElement))
|
|
922
|
+
_a.#reflectButtons([...node.children], group);
|
|
923
|
+
container.appendChild(group);
|
|
924
|
+
break;
|
|
925
|
+
}
|
|
926
|
+
case 'OPTGROUP': {
|
|
927
|
+
const group = _a.#wrapOptgroupTag(node);
|
|
928
|
+
_a.#reflectButtons([...node.children], group);
|
|
929
|
+
container.appendChild(group);
|
|
930
|
+
break;
|
|
931
|
+
}
|
|
932
|
+
case 'OPTION':
|
|
933
|
+
default: {
|
|
934
|
+
container.appendChild(_a.#wrapOptionTag(node));
|
|
935
|
+
break;
|
|
936
|
+
}
|
|
318
937
|
}
|
|
938
|
+
else {
|
|
939
|
+
if (node.children.length)
|
|
940
|
+
if (allLeaves(node)) {
|
|
941
|
+
const group = _a.#wrapOptgroupTag(node);
|
|
942
|
+
_a.#reflectButtons(node.children, group);
|
|
943
|
+
container.appendChild(group);
|
|
944
|
+
}
|
|
945
|
+
else {
|
|
946
|
+
const title = extractLabel(node), subtitle = extractSubtitle(node), icon = extractIcon(node);
|
|
947
|
+
const mount = document.createElement('div');
|
|
948
|
+
render(html `<menu-view tabindex="0">
|
|
949
|
+
<h-stack slot="label" distribution="leading" template="auto spacer">
|
|
950
|
+
<label-view data-role="check" style="visibility: hidden">
|
|
951
|
+
<image-view slot="icon" system-name="check"></image-view>
|
|
952
|
+
</label-view>
|
|
953
|
+
<h-stack distribution="fill" template="${icon ? 'auto spacer' : null}">
|
|
954
|
+
${icon ? html `<label-view system-image="${icon}"></label-view>` : null}
|
|
955
|
+
<v-stack spacing="3" alignment="fill">
|
|
956
|
+
<label-view title="${title}"></label-view>
|
|
957
|
+
${subtitle ? html `<label-view foreground="secondary" font="callout" title="${subtitle}"></label-view>` : null}
|
|
958
|
+
</v-stack>
|
|
959
|
+
</h-stack>
|
|
960
|
+
</h-stack>
|
|
961
|
+
</menu-view>`, mount);
|
|
962
|
+
const group = mount.firstElementChild;
|
|
963
|
+
// const group = $(`<menu-view tabindex="0"></menu-view>`, '>1')
|
|
964
|
+
// renderLabel(':scope>label-view[slot=label]', `<label-view slot="label"><span></span></label-view>`, group, extractLabel(node), extractIcon(node))
|
|
965
|
+
_a.#reflectButtons(node.children, group);
|
|
966
|
+
container.appendChild(group);
|
|
967
|
+
}
|
|
319
968
|
else
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
section.insertAdjacentElement('beforeend', _a.wrapTag(el, sourceSlot?.name));
|
|
323
|
-
// let possibleLv = this.#slot?.assignedElements({ flatten: true })[0]
|
|
324
|
-
// if ('LIST-VIEW' !== possibleLv?.tagName) possibleLv = this.appendChild(document.createElement('list-view'))
|
|
325
|
-
// const sv = document.createElement('section-view')
|
|
326
|
-
// sv.setAttribute('header', 'section-header')
|
|
327
|
-
// possibleLv.appendChild(sv)
|
|
328
|
-
// for (const el of PickerView.sourceNodes(sourceSlot) ?? []) sv.insertAdjacentElement('beforeend', PickerView.wrapTag(el, sourceSlot?.name))
|
|
329
|
-
// for (const el of this.querySelectorAll(':scope>:not([slot])')) el.remove()
|
|
330
|
-
// for (const el of PickerView.sourceNodes(sourceSlot) ?? []) {
|
|
331
|
-
// const btn = document.createElement('button')
|
|
332
|
-
// btn.type = 'button'
|
|
333
|
-
// btn.tabIndex = 0
|
|
334
|
-
// btn.appendChild(PickerView.wrapTag(el, sourceSlot?.name))
|
|
335
|
-
// this.insertAdjacentElement('beforeend', btn)
|
|
336
|
-
// }
|
|
337
|
-
break;
|
|
338
|
-
}
|
|
339
|
-
};
|
|
340
|
-
#reflectPlaceholder(value) {
|
|
341
|
-
const input = this.#shadowRoot.querySelector('input');
|
|
342
|
-
if (input) {
|
|
343
|
-
if (value)
|
|
344
|
-
input.setAttribute('placeholder', value);
|
|
345
|
-
else
|
|
346
|
-
input.removeAttribute('placeholder');
|
|
347
|
-
}
|
|
969
|
+
container.appendChild(_a.#wrapOptionTag(node));
|
|
970
|
+
}
|
|
348
971
|
}
|
|
972
|
+
// #reflectPlaceholder(value: string | null) {
|
|
973
|
+
// if (devFlags.debug) console.debug(`#reflectPlaceholder`)
|
|
974
|
+
// const input = this.#shadowRoot.querySelector('input')
|
|
975
|
+
// if (input) {
|
|
976
|
+
// if (value) input.setAttribute((this.constructor as typeof PickerView).ATTR.PLACEHOLDER, value)
|
|
977
|
+
// else input.removeAttribute((this.constructor as typeof PickerView).ATTR.PLACEHOLDER)
|
|
978
|
+
// }
|
|
979
|
+
// }
|
|
349
980
|
#reflectLabel(value) {
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
981
|
+
if (devFlags.debug)
|
|
982
|
+
console.debug(`${_a.name} #reflectLabel`);
|
|
983
|
+
renderLabel(':scope>label-view[slot=label]', `<label-view slot="label"><span></span></label-view>`, this, value);
|
|
984
|
+
this.#renderSlotted([]);
|
|
985
|
+
}
|
|
986
|
+
#reflectSelectionOnButtons() {
|
|
987
|
+
if (devFlags.debug)
|
|
988
|
+
console.debug(`${_a.name} #reflectSelectionOnButtons`);
|
|
989
|
+
self.requestAnimationFrame(() => {
|
|
990
|
+
this.ariaCurrent = this.#selection;
|
|
991
|
+
const groupMap = indexGroups(this.#lastIndexedRoot);
|
|
992
|
+
const groupContainsSelection = (source) => source instanceof Element ? [...source.querySelectorAll('option')].some((opt) => extractTag(opt) === this.#selection) : collectLeafValues(source).includes(this.#selection);
|
|
993
|
+
const syncButtons = (root) => {
|
|
994
|
+
// 1. plain value buttons — unchanged
|
|
995
|
+
for (const el of root.querySelectorAll('button[value]:not([slot])'))
|
|
996
|
+
// $.prop('visibility', el.getAttribute('value') === this.#selection ? 'visible' : 'hidden', el.querySelector<HTMLElement>('label-view[data-role="check"]'))
|
|
997
|
+
el.querySelector('label-view[data-role="check"]')?.style.setProperty('visibility', el.getAttribute('value') === this.#selection ? 'visible' : 'hidden');
|
|
998
|
+
// 2. details/optgroups — unchanged
|
|
999
|
+
for (const details of root.querySelectorAll('details[is="disclosure-group"]')) {
|
|
1000
|
+
const hasSelectedDescendant = [...details.querySelectorAll('button[value]')].some((btn) => btn.getAttribute('value') === this.#selection);
|
|
1001
|
+
details.querySelector(':scope>summary label-view[data-role="check"]')?.style.setProperty('visibility', hasSelectedDescendant ? 'visible' : 'hidden');
|
|
1002
|
+
}
|
|
1003
|
+
// 3. nav-link buttons — resolved by groupId, same map used for resync
|
|
1004
|
+
for (const btn of root.querySelectorAll('button[navigation-link]:not([value])')) {
|
|
1005
|
+
const source = btn.dataset.groupId ? groupMap.get(btn.dataset.groupId) : undefined;
|
|
1006
|
+
const hasSelectedDescendant = source ? groupContainsSelection(source) : false;
|
|
1007
|
+
btn.querySelector('label-view[data-role="check"]')?.style.setProperty('visibility', hasSelectedDescendant ? 'visible' : 'hidden');
|
|
1008
|
+
}
|
|
1009
|
+
};
|
|
1010
|
+
syncButtons(this);
|
|
1011
|
+
if (this.#spawn)
|
|
1012
|
+
syncButtons(this.#spawn);
|
|
1013
|
+
});
|
|
1014
|
+
}
|
|
1015
|
+
/**
|
|
1016
|
+
* Overwrite cvlabel with the label prop of the current(find[value === #selection]) option/dictentry
|
|
1017
|
+
*/
|
|
1018
|
+
#reflectSelectionOnCurrentValueLabel() {
|
|
1019
|
+
if (devFlags.debug)
|
|
1020
|
+
console.debug(`${_a.name} #reflectSelectionOnCurrentValueLabel`);
|
|
1021
|
+
self.requestAnimationFrame(() => {
|
|
1022
|
+
switch (this.pickerStyle) {
|
|
1023
|
+
case 'sheet':
|
|
1024
|
+
case 'navigation-link': {
|
|
1025
|
+
const currentValueLabel = this.querySelector(':scope>label-view:not([slot])');
|
|
1026
|
+
if (!currentValueLabel)
|
|
1027
|
+
break;
|
|
1028
|
+
// if (!cvl) currentValueLabel.setAttribute('foreground', 'secondary')
|
|
1029
|
+
// else currentValueLabel.removeAttribute('foreground')
|
|
1030
|
+
renderLabelTitle(currentValueLabel, this.#currentValueLabel || this.#selection || this.getAttribute(this.constructor.ATTR.PLACEHOLDER));
|
|
1031
|
+
renderLabelIcon(currentValueLabel, this.#currentValueIcon || this.getAttribute(this.constructor.ATTR.PLACEHOLDER_ICON));
|
|
1032
|
+
break;
|
|
1033
|
+
}
|
|
1034
|
+
case 'menu': {
|
|
1035
|
+
const currentValueLabel = this.querySelector(':scope>menu-view:not([slot])>label-view[slot=label]');
|
|
1036
|
+
if (!currentValueLabel)
|
|
1037
|
+
break;
|
|
1038
|
+
// if (!cvl) currentValueLabel.setAttribute('foreground', 'secondary')
|
|
1039
|
+
// else currentValueLabel.removeAttribute('foreground')
|
|
1040
|
+
renderLabelTitle(currentValueLabel, this.#currentValueLabel || this.#selection || this.getAttribute(this.constructor.ATTR.PLACEHOLDER));
|
|
1041
|
+
renderLabelIcon(currentValueLabel, this.#currentValueIcon || this.getAttribute(this.constructor.ATTR.PLACEHOLDER_ICON));
|
|
1042
|
+
break;
|
|
1043
|
+
}
|
|
1044
|
+
case 'inline':
|
|
1045
|
+
default: {
|
|
1046
|
+
//
|
|
1047
|
+
break;
|
|
1048
|
+
}
|
|
1049
|
+
}
|
|
1050
|
+
});
|
|
1051
|
+
}
|
|
1052
|
+
#reflectTriggerHelp() {
|
|
1053
|
+
if (devFlags.debug)
|
|
1054
|
+
console.debug(`${_a.name} #reflectTriggerHelp`);
|
|
1055
|
+
let trigger;
|
|
1056
|
+
switch (this.pickerStyle) {
|
|
1057
|
+
case 'sheet':
|
|
1058
|
+
case 'navigation-link': {
|
|
1059
|
+
trigger = this.querySelector(':scope>label-view:not([slot])') ?? undefined;
|
|
1060
|
+
}
|
|
1061
|
+
case 'menu': {
|
|
1062
|
+
trigger = this.querySelector(':scope>menu-view:not([slot])>label-view[slot=label]') ?? undefined;
|
|
1063
|
+
}
|
|
1064
|
+
case 'inline':
|
|
1065
|
+
default: {
|
|
1066
|
+
//udnefined
|
|
1067
|
+
}
|
|
365
1068
|
}
|
|
1069
|
+
if (this.hasAttribute(this.constructor.ATTR.TRIGGER_HELP))
|
|
1070
|
+
trigger?.setAttribute(this.constructor.ATTR.TRIGGER_HELP, this.getAttribute(this.constructor.ATTR.TRIGGER_HELP) ?? '');
|
|
366
1071
|
else
|
|
367
|
-
|
|
368
|
-
this.#handleTagMutation();
|
|
369
|
-
// const el =
|
|
370
|
-
// (this.#labelSlot?.assignedElements({ flatten: true })[0] as HTMLElement) ??
|
|
371
|
-
// (() => {
|
|
372
|
-
// const el = document.createElement('span')
|
|
373
|
-
// el.slot = 'label'
|
|
374
|
-
// return this.appendChild(el)
|
|
375
|
-
// })()
|
|
376
|
-
// el.textContent = value ?? '' // el.replaceChildren(escapeHTMLPolicy.createHTML(newValue))
|
|
377
|
-
// break
|
|
378
|
-
// }
|
|
379
|
-
// const assigned = this.#labelSlot?.assignedElements({ flatten: true }) as HTMLElement[] | undefined
|
|
380
|
-
// let el = assigned?.[0]
|
|
381
|
-
// if (!el) {
|
|
382
|
-
// el = document.createElement('span')
|
|
383
|
-
// el.slot = 'label'
|
|
384
|
-
// this.append(el)
|
|
385
|
-
// }
|
|
386
|
-
// el.textContent = value ?? ''
|
|
1072
|
+
trigger?.removeAttribute(this.constructor.ATTR.TRIGGER_HELP);
|
|
387
1073
|
}
|
|
388
1074
|
// Optional: form participation properties
|
|
389
1075
|
get name() {
|
|
@@ -396,16 +1082,16 @@ export class PickerView extends FormAssociatedBase {
|
|
|
396
1082
|
const key = k; // ✅ type-safe cast
|
|
397
1083
|
if (true !== flags[key])
|
|
398
1084
|
continue;
|
|
399
|
-
for (const el of this.#
|
|
1085
|
+
for (const el of this.#slots?.get('validity-options')?.assignedElements({ flatten: true }) ?? []) {
|
|
400
1086
|
if (!el.matches('option'))
|
|
401
1087
|
continue;
|
|
402
|
-
const
|
|
403
|
-
if (`${kebabCase(key)}` ===
|
|
404
|
-
message =
|
|
1088
|
+
const { label, value } = el;
|
|
1089
|
+
if (`${kebabCase(key)}` === value) {
|
|
1090
|
+
message = label;
|
|
405
1091
|
break;
|
|
406
1092
|
}
|
|
407
|
-
else if (`${kebabCase(key)}:${message}` ===
|
|
408
|
-
message =
|
|
1093
|
+
else if (`${kebabCase(key)}:${message}` === value) {
|
|
1094
|
+
message = label;
|
|
409
1095
|
break;
|
|
410
1096
|
}
|
|
411
1097
|
}
|
|
@@ -418,7 +1104,8 @@ export class PickerView extends FormAssociatedBase {
|
|
|
418
1104
|
message = kebabCase(key);
|
|
419
1105
|
break;
|
|
420
1106
|
}
|
|
421
|
-
|
|
1107
|
+
if (devFlags.debug)
|
|
1108
|
+
console.debug(`${_a.name} ⚡️ validity-change`);
|
|
422
1109
|
return this.#internals.setValidity(flags, this.#customValidity || message, anchor);
|
|
423
1110
|
};
|
|
424
1111
|
setCustomValidity = (message) => {
|
|
@@ -437,6 +1124,9 @@ export class PickerView extends FormAssociatedBase {
|
|
|
437
1124
|
};
|
|
438
1125
|
formResetCallback = () => {
|
|
439
1126
|
this.#selection = '';
|
|
1127
|
+
this.#reflectSelectionOnButtons();
|
|
1128
|
+
this.#reflectSelectionOnCurrentValueLabel();
|
|
1129
|
+
this.#sendValueToForm();
|
|
440
1130
|
};
|
|
441
1131
|
}
|
|
442
1132
|
_a = PickerView;
|