@swiftwc/ui 1.0.0-canary.2 → 1.0.0-canary.4
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/components/form-view.js +40 -37
- package/generated/components/picker-view.d.ts +2 -10
- package/generated/components/picker-view.js +158 -271
- package/generated/css/index.css +282 -78
- package/generated/internal/class/dict-entry.d.ts +42 -0
- package/generated/internal/class/dict-entry.js +81 -0
- package/generated/internal/privateNamespace.js +1 -1
- package/generated/snapshot/index.d.ts +1 -0
- package/generated/snapshot/index.js +16 -0
- package/html-data/en.json +1 -1
- package/package.json +6 -6
- package/scss/_maps.scss +1 -1
- package/scss/base/_root.scss +7 -0
- package/scss/components/_labeled-content.scss +12 -8
- package/scss/components/_picker-view.scss +282 -92
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
var _a;
|
|
2
2
|
import { I18n } from '../i18n';
|
|
3
3
|
import { CleanupRegistry } from '../internal/class/cleanup-registry';
|
|
4
|
+
import { DictEntry, Dictionary } from '../internal/class/dict-entry';
|
|
4
5
|
import { FormAssociatedBase, getInternals } from '../internal/class/form-associated-base';
|
|
5
6
|
import { MutationObserverSet } from '../internal/class/mutation-observer-set';
|
|
6
7
|
import { NavigationPath } from '../internal/class/navigation-path';
|
|
@@ -8,7 +9,7 @@ import { queryInsertPosition, startViewTransition } from '../internal/privateNam
|
|
|
8
9
|
import { $, ancestors, debug, kebabCase, onoff } from '../internal/utils';
|
|
9
10
|
import { queryMorph } from '../morphdom';
|
|
10
11
|
import { html, render } from '../tpl';
|
|
11
|
-
const pickerStyles = ['menu', 'inline', 'navigation-link', 'sheet', 'radio-group', 'automatic'];
|
|
12
|
+
const pickerStyles = ['menu', 'inline', 'navigation-link', 'sheet', 'radio-group', 'palette', 'segmented', 'automatic'];
|
|
12
13
|
const update = (path, node, overwrite = true) => {
|
|
13
14
|
debug(`PickerView: update`);
|
|
14
15
|
if (!(path instanceof NavigationPath))
|
|
@@ -59,76 +60,6 @@ const reflectSpawnedPage = (current, source) => {
|
|
|
59
60
|
else
|
|
60
61
|
oldSearch?.remove();
|
|
61
62
|
};
|
|
62
|
-
const extractTag = (node) => {
|
|
63
|
-
debug(`PickerView: extractTag`);
|
|
64
|
-
return node instanceof HTMLOptionElement ? (((node.getAttribute('value') ?? node.textContent?.trim()) || node.getAttribute('label')) ?? '') : (node.value ?? '');
|
|
65
|
-
}, extractLabel = (node) => {
|
|
66
|
-
debug(`PickerView: extractLabelFromGroup`);
|
|
67
|
-
if (node instanceof Element)
|
|
68
|
-
if (node instanceof HTMLOptionElement)
|
|
69
|
-
return (node.getAttribute('label') ?? node.getAttribute('value') ?? node.textContent?.trim()) || null;
|
|
70
|
-
else
|
|
71
|
-
return node.getAttribute('DATALIST' === node.tagName ? 'data-label' : 'label');
|
|
72
|
-
else
|
|
73
|
-
return node.title ?? node.value ?? null;
|
|
74
|
-
// return node instanceof Element ? node.getAttribute('DATALIST' === node.tagName ? 'data-label' : 'label') : (node.title ?? node.value ?? null)
|
|
75
|
-
}, extractIcon = (node) => {
|
|
76
|
-
debug(`PickerView: extractIcon`);
|
|
77
|
-
// return node instanceof Element ? node.getAttribute('data-system-image') : (node.systemImage ?? null)
|
|
78
|
-
if (node instanceof Element)
|
|
79
|
-
return node.getAttribute('data-system-image');
|
|
80
|
-
else
|
|
81
|
-
return node.systemImage ?? null;
|
|
82
|
-
}, extractSubtitle = (node) => {
|
|
83
|
-
debug(`PickerView: extractSubtitle`);
|
|
84
|
-
if (node instanceof Element)
|
|
85
|
-
return node.getAttribute('data-subtitle');
|
|
86
|
-
else
|
|
87
|
-
return node.subtitle ?? null;
|
|
88
|
-
};
|
|
89
|
-
const allLeaves = (node) => node.children.every((c) => 0 === c.children.length);
|
|
90
|
-
const parseDictionary = (value) => {
|
|
91
|
-
let dictionary = [];
|
|
92
|
-
try {
|
|
93
|
-
dictionary = JSON.parse(value ?? '[]');
|
|
94
|
-
}
|
|
95
|
-
catch {
|
|
96
|
-
console.error('invalid-dictionary');
|
|
97
|
-
}
|
|
98
|
-
return dictionary;
|
|
99
|
-
};
|
|
100
|
-
const indexGroups = (nodes, parentPath = '') => {
|
|
101
|
-
const map = new Map();
|
|
102
|
-
nodes.forEach((node, i) => {
|
|
103
|
-
const id = parentPath ? `${parentPath}.${i}` : `${i}`;
|
|
104
|
-
map.set(id, node); // no DOM write — works identically for Element and DictEntry
|
|
105
|
-
const children = node instanceof Element ? Array.from(node.children) : node.children;
|
|
106
|
-
for (const [cid, cnode] of indexGroups(children, id))
|
|
107
|
-
map.set(cid, cnode);
|
|
108
|
-
});
|
|
109
|
-
return map;
|
|
110
|
-
};
|
|
111
|
-
const collectLeafValues = (node) => (node.children.length ? node.children.flatMap(collectLeafValues) : [node.value]);
|
|
112
|
-
const flattenDictionary = (tree) => {
|
|
113
|
-
const out1 = {}, out2 = {}, stack = [tree], idx = [0];
|
|
114
|
-
while (stack.length) {
|
|
115
|
-
const frame = stack[stack.length - 1], i = idx[idx.length - 1];
|
|
116
|
-
if (i >= frame.length) {
|
|
117
|
-
stack.pop();
|
|
118
|
-
idx.pop();
|
|
119
|
-
continue;
|
|
120
|
-
}
|
|
121
|
-
idx[idx.length - 1]++;
|
|
122
|
-
const { value, title, systemImage, children } = frame[i];
|
|
123
|
-
out1[value] = title;
|
|
124
|
-
out2[value] = systemImage;
|
|
125
|
-
if (children.length) {
|
|
126
|
-
stack.push(children);
|
|
127
|
-
idx.push(0);
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
return { labels: out1, icons: out2 };
|
|
131
|
-
};
|
|
132
63
|
/**
|
|
133
64
|
* @summary A control that selects one value from a set of options.
|
|
134
65
|
*
|
|
@@ -184,7 +115,7 @@ export class PickerView extends FormAssociatedBase {
|
|
|
184
115
|
const { target } = evt;
|
|
185
116
|
if (!(target instanceof HTMLElement))
|
|
186
117
|
return;
|
|
187
|
-
const newPage = this.#spawnPage(el
|
|
118
|
+
const newPage = this.#spawnPage(el.children, 'content-view', groupId, this.hasAttribute('searchable'), el.title);
|
|
188
119
|
if (!newPage)
|
|
189
120
|
return;
|
|
190
121
|
newPage.dataset.groupId = groupId; // <-- tag the spawned page
|
|
@@ -195,7 +126,7 @@ export class PickerView extends FormAssociatedBase {
|
|
|
195
126
|
});
|
|
196
127
|
};
|
|
197
128
|
}
|
|
198
|
-
#spawnPage = (
|
|
129
|
+
#spawnPage = (entries, tag, parentGroupId, searchable = false, title) => {
|
|
199
130
|
debug(`${_a.name} #spawnPage`);
|
|
200
131
|
const body = tag === 'sheet-view'
|
|
201
132
|
? $(html `<dialog is="sheet-view">
|
|
@@ -274,68 +205,33 @@ export class PickerView extends FormAssociatedBase {
|
|
|
274
205
|
component?.remove();
|
|
275
206
|
});
|
|
276
207
|
});
|
|
277
|
-
for (const [i, el] of
|
|
278
|
-
if (el
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
}
|
|
288
|
-
case 'OPTION': {
|
|
289
|
-
const btn = this.#wrapOptionTag(el);
|
|
290
|
-
btn.addEventListener('click', this.#handlePageBtnClick);
|
|
291
|
-
list?.appendChild(btn);
|
|
292
|
-
break;
|
|
293
|
-
}
|
|
294
|
-
default: {
|
|
295
|
-
const btn = _a.#wrapOptionSpawnTag(el);
|
|
296
|
-
const groupId = parentGroupId ? `${parentGroupId}.${i}` : `${i}`;
|
|
297
|
-
// const btn = $<HTMLButtonElement>(
|
|
298
|
-
// `<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>`,
|
|
299
|
-
// '>1'
|
|
300
|
-
// ),
|
|
301
|
-
// label = btn.querySelector<LabelView>(':scope>h-stack>label-view:nth-child(2)')
|
|
302
|
-
// const node = el as HTMLElement
|
|
303
|
-
// if (label && node.dataset.label) renderLabelTitle(label, node.dataset.label) //label?.setAttribute('title', el.dataset.label)
|
|
304
|
-
btn.dataset.groupId = groupId;
|
|
305
|
-
btn.addEventListener('click', this.#makeGroupClickHandler(el, groupId));
|
|
306
|
-
list?.appendChild(btn);
|
|
307
|
-
break;
|
|
308
|
-
}
|
|
309
|
-
}
|
|
310
|
-
else {
|
|
311
|
-
if (el.children.length)
|
|
312
|
-
if (allLeaves(el)) {
|
|
313
|
-
const group = this.#wrapOptgroupTag(el);
|
|
314
|
-
this.#reflectButtons(el.children, group);
|
|
315
|
-
for (const btn of group.querySelectorAll(':scope>button'))
|
|
316
|
-
btn.addEventListener('click', this.#handlePageBtnClick);
|
|
317
|
-
list?.appendChild(group);
|
|
318
|
-
}
|
|
319
|
-
else {
|
|
320
|
-
const btn = _a.#wrapOptionSpawnTag(el);
|
|
321
|
-
const groupId = parentGroupId ? `${parentGroupId}.${i}` : `${i}`;
|
|
322
|
-
btn.dataset.groupId = groupId;
|
|
323
|
-
btn.addEventListener('click', this.#makeGroupClickHandler(el, groupId));
|
|
324
|
-
list?.appendChild(btn);
|
|
325
|
-
}
|
|
326
|
-
else {
|
|
327
|
-
const btn = this.#wrapOptionTag(el);
|
|
208
|
+
for (const [i, el] of entries.entries()) {
|
|
209
|
+
if (DictEntry.isLeaf(el)) {
|
|
210
|
+
const btn = this.#wrapOptionTag(el);
|
|
211
|
+
btn.addEventListener('click', this.#handlePageBtnClick);
|
|
212
|
+
list?.appendChild(btn);
|
|
213
|
+
}
|
|
214
|
+
else if (DictEntry.isGroup(el)) {
|
|
215
|
+
const group = this.#wrapOptgroupTag(el);
|
|
216
|
+
this.#reflectButtons(el.children, group);
|
|
217
|
+
for (const btn of group.querySelectorAll(':scope>button'))
|
|
328
218
|
btn.addEventListener('click', this.#handlePageBtnClick);
|
|
329
|
-
|
|
330
|
-
|
|
219
|
+
list?.appendChild(group);
|
|
220
|
+
}
|
|
221
|
+
else {
|
|
222
|
+
const btn = _a.#wrapOptionSpawnTag(el);
|
|
223
|
+
const groupId = parentGroupId ? `${parentGroupId}.${i}` : `${i}`;
|
|
224
|
+
btn.dataset.groupId = groupId;
|
|
225
|
+
btn.addEventListener('click', this.#makeGroupClickHandler(el, groupId));
|
|
226
|
+
list?.appendChild(btn);
|
|
331
227
|
}
|
|
332
228
|
}
|
|
333
229
|
return body;
|
|
334
230
|
};
|
|
335
|
-
#resyncSpawnedPages = (
|
|
231
|
+
#resyncSpawnedPages = (fresh) => {
|
|
336
232
|
if (!this.#spawn)
|
|
337
233
|
return;
|
|
338
|
-
const groupMap =
|
|
234
|
+
const groupMap = Dictionary.index(fresh);
|
|
339
235
|
for (const el of this.#spawn.querySelectorAll('content-view')) {
|
|
340
236
|
const depth = ancestors('content-view,[is=sheet-view]', el).indexOf(this.#spawn);
|
|
341
237
|
if (0 >= depth)
|
|
@@ -346,60 +242,19 @@ export class PickerView extends FormAssociatedBase {
|
|
|
346
242
|
el.remove();
|
|
347
243
|
continue;
|
|
348
244
|
}
|
|
349
|
-
const children = source
|
|
350
|
-
const title = source
|
|
245
|
+
const children = source.children;
|
|
246
|
+
const title = source.title ?? null;
|
|
351
247
|
const newPage = this.#spawnPage(children, 'content-view', groupId, this.hasAttribute('searchable'), title);
|
|
352
248
|
newPage.dataset.groupId = groupId;
|
|
353
249
|
reflectSpawnedPage(el, newPage);
|
|
354
250
|
}
|
|
355
251
|
};
|
|
356
|
-
#renderButtons(
|
|
252
|
+
#renderButtons(source) {
|
|
357
253
|
debug(`${_a.name} #renderButtons`);
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
this.#lastRenderedIconMap = icons;
|
|
363
|
-
// const collectLeafValues = (node: DictEntry): string[] => (node.children.length ? node.children.flatMap(collectLeafValues) : [node.value])
|
|
364
|
-
// collectGroups = (nodes: DictEntry[]): string[][] => {
|
|
365
|
-
// const groups: string[][] = []
|
|
366
|
-
// const walk = (list: DictEntry[]) => {
|
|
367
|
-
// for (const node of list) {
|
|
368
|
-
// if (!node.children.length) continue
|
|
369
|
-
// if (!allLeaves(node)) groups.push(collectLeafValues(node))
|
|
370
|
-
// walk(node.children)
|
|
371
|
-
// }
|
|
372
|
-
// }
|
|
373
|
-
// walk(nodes)
|
|
374
|
-
// return groups
|
|
375
|
-
// }
|
|
376
|
-
this.#lastIndexedRoot = input.source; //this.#lastRenderedGroupMap = collectGroups(dictionary)
|
|
377
|
-
break;
|
|
378
|
-
}
|
|
379
|
-
case 'list': {
|
|
380
|
-
this.#lastRenderedLabelMap = {};
|
|
381
|
-
this.#lastRenderedIconMap = {};
|
|
382
|
-
for (const el of input.source)
|
|
383
|
-
if (el.matches('option')) {
|
|
384
|
-
const k = extractTag(el);
|
|
385
|
-
this.#lastRenderedLabelMap[k] = el.getAttribute('label') ?? undefined;
|
|
386
|
-
this.#lastRenderedIconMap[k] = extractIcon(el) ?? undefined;
|
|
387
|
-
}
|
|
388
|
-
else
|
|
389
|
-
for (const opt of el.querySelectorAll('option')) {
|
|
390
|
-
const k = extractTag(opt);
|
|
391
|
-
this.#lastRenderedLabelMap[k] = opt.getAttribute('label') ?? undefined;
|
|
392
|
-
this.#lastRenderedIconMap[k] = extractIcon(opt) ?? undefined;
|
|
393
|
-
}
|
|
394
|
-
this.#lastIndexedRoot = input.source;
|
|
395
|
-
// this.#lastRenderedGroupMap = assigned.flatMap<string[]>((el) =>
|
|
396
|
-
// [...(el.matches('datalist') ? [el as HTMLDataListElement] : []), ...el.querySelectorAll<HTMLDataListElement>('datalist')].map((dl) =>
|
|
397
|
-
// Array.from(dl.options).map((opt) => extractTagFromOption(opt))
|
|
398
|
-
// )
|
|
399
|
-
// )
|
|
400
|
-
break;
|
|
401
|
-
}
|
|
402
|
-
}
|
|
254
|
+
const { labels, icons } = Dictionary.flatten(source);
|
|
255
|
+
this.#lastRenderedLabelMap = labels;
|
|
256
|
+
this.#lastRenderedIconMap = icons;
|
|
257
|
+
this.#lastIndexedRoot = source;
|
|
403
258
|
switch (this.pickerStyle) {
|
|
404
259
|
case 'sheet':
|
|
405
260
|
case 'navigation-link': {
|
|
@@ -422,8 +277,8 @@ export class PickerView extends FormAssociatedBase {
|
|
|
422
277
|
if (!this.#spawn)
|
|
423
278
|
break;
|
|
424
279
|
// rerender level 0
|
|
425
|
-
reflectSpawnedPage(this.#spawn, this.#spawnPage(
|
|
426
|
-
this.#resyncSpawnedPages(
|
|
280
|
+
reflectSpawnedPage(this.#spawn, this.#spawnPage(source, 'DIALOG' === this.#spawn.tagName ? 'sheet-view' : 'content-view', undefined, this.hasAttribute('searchable'), this.getAttribute('label')));
|
|
281
|
+
this.#resyncSpawnedPages(source);
|
|
427
282
|
// // FIXME:
|
|
428
283
|
// for (const el of this.#spawn.querySelectorAll<HTMLElement>('content-view')) {
|
|
429
284
|
// const depth = $.ancestors('content-view,[is=sheet-view]', el).indexOf(this.#spawn)
|
|
@@ -453,9 +308,11 @@ export class PickerView extends FormAssociatedBase {
|
|
|
453
308
|
// }
|
|
454
309
|
if (this.hasAttribute('help'))
|
|
455
310
|
currentValueLabel?.setAttribute('help', this.getAttribute('help') ?? '');
|
|
456
|
-
this.#reflectButtons(
|
|
311
|
+
this.#reflectButtons(source, menu);
|
|
457
312
|
break;
|
|
458
313
|
}
|
|
314
|
+
case 'segmented':
|
|
315
|
+
case 'palette':
|
|
459
316
|
case 'radio-group': {
|
|
460
317
|
// capture
|
|
461
318
|
// const stack = this.querySelector<HTMLElement>(':scope>v-stack:not([slot])') ?? this.appendChild<HTMLElement>($(html`<v-stack></v-stack>`, '>1'))
|
|
@@ -465,7 +322,7 @@ export class PickerView extends FormAssociatedBase {
|
|
|
465
322
|
for (const el of this.querySelectorAll(':scope>:not([slot])'))
|
|
466
323
|
el.remove();
|
|
467
324
|
// add buttons
|
|
468
|
-
this.#reflectButtons(
|
|
325
|
+
this.#reflectButtons(source, this);
|
|
469
326
|
break;
|
|
470
327
|
}
|
|
471
328
|
case 'inline':
|
|
@@ -491,10 +348,7 @@ export class PickerView extends FormAssociatedBase {
|
|
|
491
348
|
// if (label) renderLabelTitle(label, value) //label.setAttribute('title', value)
|
|
492
349
|
section.insertAdjacentElement('beforeend', hStack);
|
|
493
350
|
}
|
|
494
|
-
|
|
495
|
-
this.#reflectButtons(input.source.filter((el) => 0 === el.children.length), section);
|
|
496
|
-
else
|
|
497
|
-
this.#reflectButtons(input.source.filter((el) => el.matches('option')), section);
|
|
351
|
+
this.#reflectButtons(source.filter(DictEntry.isLeaf), section);
|
|
498
352
|
break;
|
|
499
353
|
}
|
|
500
354
|
}
|
|
@@ -575,6 +429,8 @@ export class PickerView extends FormAssociatedBase {
|
|
|
575
429
|
</label>
|
|
576
430
|
`));
|
|
577
431
|
break;
|
|
432
|
+
case 'segmented':
|
|
433
|
+
case 'palette':
|
|
578
434
|
case 'radio-group':
|
|
579
435
|
_a.#templates.set(this.pickerStyle, $(html `
|
|
580
436
|
<label part="root picker-stack">
|
|
@@ -737,8 +593,8 @@ export class PickerView extends FormAssociatedBase {
|
|
|
737
593
|
}
|
|
738
594
|
#resolveSource() {
|
|
739
595
|
if (this.hasAttribute('dictionary'))
|
|
740
|
-
return
|
|
741
|
-
return
|
|
596
|
+
return Dictionary.parse(this.getAttribute('dictionary'));
|
|
597
|
+
return Dictionary.fromElements(this.#slots?.get('list')?.assignedElements({ flatten: true }) ?? []);
|
|
742
598
|
}
|
|
743
599
|
#render() {
|
|
744
600
|
debug(`${_a.name} ⚡️ #render (${this.pickerStyle})`);
|
|
@@ -786,7 +642,7 @@ export class PickerView extends FormAssociatedBase {
|
|
|
786
642
|
if (!(target instanceof HTMLElement))
|
|
787
643
|
return;
|
|
788
644
|
this.#spawn?.remove?.();
|
|
789
|
-
const
|
|
645
|
+
const source = this.#resolveSource();
|
|
790
646
|
const level0 = this.#spawnPage(source, // this.hasAttribute('dictionary') ? parseDictionary(this.getAttribute('dictionary')) : (this.#slots?.get('list')?.assignedElements() ?? []),
|
|
791
647
|
'sheet' === this.pickerStyle ? 'sheet-view' : 'content-view', undefined, this.hasAttribute('searchable'), this.getAttribute('label'));
|
|
792
648
|
if (!level0)
|
|
@@ -838,6 +694,8 @@ export class PickerView extends FormAssociatedBase {
|
|
|
838
694
|
if (!(target instanceof HTMLElement))
|
|
839
695
|
return;
|
|
840
696
|
switch (this.pickerStyle) {
|
|
697
|
+
case 'segmented':
|
|
698
|
+
case 'palette':
|
|
841
699
|
case 'radio-group':
|
|
842
700
|
const radio = target.closest('input[type="radio"]');
|
|
843
701
|
if (!radio)
|
|
@@ -869,25 +727,30 @@ export class PickerView extends FormAssociatedBase {
|
|
|
869
727
|
};
|
|
870
728
|
static #wrapOptionSpawnTag(node) {
|
|
871
729
|
debug(`${_a.name} #wrapOptionSpawnTag`);
|
|
872
|
-
const title = extractLabel(node), subtitle = extractSubtitle(node), icon = extractIcon(node);
|
|
873
730
|
const mount = document.createElement('div');
|
|
874
731
|
render(html `<button type="button" tabindex="0" navigation-link>
|
|
875
732
|
<h-stack distribution="leading" template="auto spacer">
|
|
876
733
|
<label-view data-role="check" style="visibility: hidden">
|
|
877
734
|
<image-view slot="icon" system-name="check"></image-view>
|
|
878
735
|
</label-view>
|
|
879
|
-
${_a.#morphITSLabel(
|
|
736
|
+
${_a.#morphITSLabel(node)}
|
|
880
737
|
</h-stack>
|
|
881
738
|
</button>`, mount);
|
|
882
739
|
return mount.firstElementChild; //btn
|
|
883
740
|
}
|
|
884
741
|
#wrapOptionTag(node) {
|
|
885
742
|
debug(`${_a.name} #wrapOptionTag`);
|
|
886
|
-
const tag =
|
|
743
|
+
const tag = node.value;
|
|
887
744
|
const mount = document.createElement('div');
|
|
888
745
|
switch (this.pickerStyle) {
|
|
746
|
+
case 'segmented':
|
|
747
|
+
render(_a.#morphSegmentedBtn({ name: this.#guuid, node, tag }), mount);
|
|
748
|
+
return mount.firstElementChild; //btn
|
|
749
|
+
case 'palette':
|
|
750
|
+
render(_a.#morphPaletteBtn({ name: this.#guuid, node, tag }), mount);
|
|
751
|
+
return mount.firstElementChild; //btn
|
|
889
752
|
case 'radio-group':
|
|
890
|
-
render(_a.#morphRadioGroupBtn({ name: this.#guuid,
|
|
753
|
+
render(_a.#morphRadioGroupBtn({ name: this.#guuid, node, tag }), mount);
|
|
891
754
|
return mount.firstElementChild; //btn
|
|
892
755
|
default:
|
|
893
756
|
render(html `<button type="button" tabindex="0" value="${tag}">
|
|
@@ -895,31 +758,82 @@ export class PickerView extends FormAssociatedBase {
|
|
|
895
758
|
<label-view data-role="check">
|
|
896
759
|
<image-view slot="icon" system-name="check"></image-view>
|
|
897
760
|
</label-view>
|
|
898
|
-
${_a.#morphITSLabel(
|
|
761
|
+
${_a.#morphITSLabel(node)}
|
|
899
762
|
</h-stack>
|
|
900
763
|
</button>`, mount);
|
|
901
764
|
return mount.firstElementChild; //btn
|
|
902
765
|
}
|
|
903
766
|
}
|
|
904
|
-
static #
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
<
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
767
|
+
static #morphRadioGroupBtn({ name, node, tag }) {
|
|
768
|
+
if ('string' === typeof tag)
|
|
769
|
+
return html `<label tabindex="0">
|
|
770
|
+
<h-stack distribution="fill" template="auto spacer" spacing="5">
|
|
771
|
+
<input type="radio" name="${name}" value="${tag}" />
|
|
772
|
+
${_a.#morphITSLabel(node)}
|
|
773
|
+
</h-stack>
|
|
774
|
+
</label>`;
|
|
775
|
+
else
|
|
776
|
+
return html `<label>
|
|
777
|
+
<h-stack distribution="fill" template="auto spacer" spacing="5">
|
|
778
|
+
<input type="radio" name="${name}" disabled />
|
|
779
|
+
${_a.#morphITSLabel(node)}
|
|
780
|
+
</h-stack>
|
|
781
|
+
</label>`;
|
|
911
782
|
}
|
|
912
|
-
static #
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
783
|
+
static #morphPaletteBtn({ name, node, tag }) {
|
|
784
|
+
if ('string' === typeof tag) {
|
|
785
|
+
const style = node.src ? `background-image: url('${node.src}')` : null;
|
|
786
|
+
return html `<label tabindex="0">
|
|
787
|
+
<v-stack template="auto spacer" spacing="5">
|
|
788
|
+
<input type="radio" name="${name}" value="${tag}" style="${style}" />
|
|
789
|
+
${_a.#morphITSLabel(node)}
|
|
790
|
+
</v-stack>
|
|
791
|
+
</label>`;
|
|
792
|
+
}
|
|
793
|
+
else
|
|
794
|
+
return html `<label>
|
|
795
|
+
<v-stack template="auto spacer" spacing="5">
|
|
796
|
+
<input type="radio" name="${name}" disabled />
|
|
797
|
+
${_a.#morphITSLabel(node)}
|
|
798
|
+
</v-stack>
|
|
799
|
+
</label>`;
|
|
919
800
|
}
|
|
920
|
-
static #
|
|
921
|
-
|
|
922
|
-
|
|
801
|
+
static #morphSegmentedBtn({ name, node, tag }) {
|
|
802
|
+
const cssUrl = (src) => {
|
|
803
|
+
if ('string' !== typeof src)
|
|
804
|
+
return null;
|
|
805
|
+
try {
|
|
806
|
+
const { protocol, href } = new URL(src, location.href);
|
|
807
|
+
if (protocol !== 'https:' && protocol !== 'http:' && protocol !== 'blob:')
|
|
808
|
+
return null;
|
|
809
|
+
// URL.href already percent-encodes " < > ` space and \ in path/query.
|
|
810
|
+
// Encode the remaining chars that matter inside url('...')
|
|
811
|
+
return href.replace(/['()]/g, (c) => `%${c.charCodeAt(0).toString(16).toUpperCase()}`);
|
|
812
|
+
}
|
|
813
|
+
catch {
|
|
814
|
+
return null;
|
|
815
|
+
}
|
|
816
|
+
};
|
|
817
|
+
if ('string' === typeof tag) {
|
|
818
|
+
const style = node.src ? `background-image: url('${cssUrl(node.src)}')` : null;
|
|
819
|
+
return html `<label tabindex="0">
|
|
820
|
+
<v-stack template="auto spacer" spacing="5">
|
|
821
|
+
<input type="radio" name="${name}" value="${tag}" style="${style}" />
|
|
822
|
+
${_a.#morphITSLabel(node)}
|
|
823
|
+
</v-stack>
|
|
824
|
+
</label>`;
|
|
825
|
+
}
|
|
826
|
+
else
|
|
827
|
+
return html `<label>
|
|
828
|
+
<v-stack template="auto spacer" spacing="5">
|
|
829
|
+
<input type="radio" name="${name}" disabled />
|
|
830
|
+
${_a.#morphITSLabel(node)}
|
|
831
|
+
</v-stack>
|
|
832
|
+
</label>`;
|
|
833
|
+
}
|
|
834
|
+
static #morphITSLabel({ title, subtitle, systemImage }) {
|
|
835
|
+
return html `<h-stack distribution="fill" template="${systemImage ? 'auto spacer' : null}">
|
|
836
|
+
${systemImage ? html `<label-view system-image="${systemImage}"></label-view>` : null}
|
|
923
837
|
<v-stack spacing="3" alignment="fill">
|
|
924
838
|
<label-view title="${title}"></label-view>
|
|
925
839
|
${subtitle ? html `<label-view foreground="secondary" font="callout" title="${subtitle}"></label-view>` : null}
|
|
@@ -928,15 +842,16 @@ export class PickerView extends FormAssociatedBase {
|
|
|
928
842
|
}
|
|
929
843
|
#wrapOptgroupTag(node) {
|
|
930
844
|
debug(`${_a.name} #wrapOptgroupTag`);
|
|
931
|
-
const title = extractLabel(node), subtitle = extractSubtitle(node), icon = extractIcon(node);
|
|
932
845
|
const mount = document.createElement('div');
|
|
933
846
|
switch (this.pickerStyle) {
|
|
847
|
+
case 'segmented':
|
|
848
|
+
render(_a.#morphSegmentedBtn({ name: this.#guuid, node }), mount);
|
|
849
|
+
return mount.firstElementChild;
|
|
850
|
+
case 'palette':
|
|
851
|
+
render(_a.#morphPaletteBtn({ name: this.#guuid, node }), mount);
|
|
852
|
+
return mount.firstElementChild;
|
|
934
853
|
case 'radio-group':
|
|
935
|
-
render(_a.#
|
|
936
|
-
// this.hasAttribute('horizontal-radio-group-layout')
|
|
937
|
-
// ? html`<h-flex-stack style="flex-wrap: wrap">${PickerView.#morphRadioGroupGroup({ name: this.#guuid, title, subtitle, icon })}</h-flex-stack>`
|
|
938
|
-
// : html`<v-stack>${PickerView.#morphRadioGroupGroup({ name: this.#guuid, title, subtitle, icon })}</v-stack>`,
|
|
939
|
-
mount);
|
|
854
|
+
render(_a.#morphRadioGroupBtn({ name: this.#guuid, node }), mount);
|
|
940
855
|
return mount.firstElementChild;
|
|
941
856
|
default:
|
|
942
857
|
render(html `<details is="disclosure-group" disclosure-style="marker-trailing">
|
|
@@ -945,7 +860,7 @@ export class PickerView extends FormAssociatedBase {
|
|
|
945
860
|
<label-view data-role="check" style="visibility: hidden">
|
|
946
861
|
<image-view slot="icon" system-name="check"></image-view>
|
|
947
862
|
</label-view>
|
|
948
|
-
${_a.#morphITSLabel(
|
|
863
|
+
${_a.#morphITSLabel(node)}
|
|
949
864
|
</h-stack>
|
|
950
865
|
</summary>
|
|
951
866
|
</details>`, mount);
|
|
@@ -953,15 +868,16 @@ export class PickerView extends FormAssociatedBase {
|
|
|
953
868
|
}
|
|
954
869
|
}
|
|
955
870
|
#wrapDatalistTag(node) {
|
|
956
|
-
const title = extractLabel(node), subtitle = extractSubtitle(node), icon = extractIcon(node);
|
|
957
871
|
const mount = document.createElement('div');
|
|
958
872
|
switch (this.pickerStyle) {
|
|
873
|
+
case 'segmented':
|
|
874
|
+
render(_a.#morphSegmentedBtn({ name: this.#guuid, node }), mount);
|
|
875
|
+
return mount.firstElementChild;
|
|
876
|
+
case 'palette':
|
|
877
|
+
render(_a.#morphPaletteBtn({ name: this.#guuid, node }), mount);
|
|
878
|
+
return mount.firstElementChild;
|
|
959
879
|
case 'radio-group':
|
|
960
|
-
render(_a.#
|
|
961
|
-
// this.hasAttribute('horizontal-radio-group-layout')
|
|
962
|
-
// ? html`<h-flex-stack style="flex-wrap: wrap">${PickerView.#morphRadioGroupGroup({ name: this.#guuid, title, subtitle, icon })}</h-flex-stack>`
|
|
963
|
-
// : html`<v-stack>${PickerView.#morphRadioGroupGroup({ name: this.#guuid, title, subtitle, icon })}</v-stack>`,
|
|
964
|
-
mount);
|
|
880
|
+
render(_a.#morphRadioGroupBtn({ name: this.#guuid, node }), mount);
|
|
965
881
|
return mount.firstElementChild;
|
|
966
882
|
default:
|
|
967
883
|
render(html `<menu-view tabindex="0">
|
|
@@ -969,7 +885,7 @@ export class PickerView extends FormAssociatedBase {
|
|
|
969
885
|
<label-view data-role="check" style="visibility: hidden">
|
|
970
886
|
<image-view slot="icon" system-name="check"></image-view>
|
|
971
887
|
</label-view>
|
|
972
|
-
${_a.#morphITSLabel(
|
|
888
|
+
${_a.#morphITSLabel(node)}
|
|
973
889
|
</h-stack>
|
|
974
890
|
</menu-view>`, mount);
|
|
975
891
|
return mount.firstElementChild;
|
|
@@ -977,53 +893,21 @@ export class PickerView extends FormAssociatedBase {
|
|
|
977
893
|
}
|
|
978
894
|
#reflectButtons(nodes, container) {
|
|
979
895
|
debug(`${_a.name} #reflectButtons`);
|
|
980
|
-
const flatten = 'radio-group'
|
|
981
|
-
for (const node of nodes)
|
|
982
|
-
if (node
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
this.#reflectButtons([...node.children], group);
|
|
991
|
-
container.appendChild(group);
|
|
992
|
-
break;
|
|
993
|
-
}
|
|
994
|
-
case 'OPTGROUP': {
|
|
995
|
-
if (flatten) {
|
|
996
|
-
this.#reflectButtons([...node.children], container);
|
|
997
|
-
break;
|
|
998
|
-
}
|
|
999
|
-
const group = this.#wrapOptgroupTag(node);
|
|
1000
|
-
this.#reflectButtons([...node.children], group);
|
|
1001
|
-
container.appendChild(group);
|
|
1002
|
-
break;
|
|
1003
|
-
}
|
|
1004
|
-
case 'OPTION':
|
|
1005
|
-
default: {
|
|
1006
|
-
container.appendChild(this.#wrapOptionTag(node));
|
|
1007
|
-
break;
|
|
1008
|
-
}
|
|
1009
|
-
}
|
|
1010
|
-
else {
|
|
1011
|
-
if (node.children.length)
|
|
1012
|
-
if (flatten)
|
|
1013
|
-
this.#reflectButtons(node.children, container);
|
|
1014
|
-
else if (allLeaves(node)) {
|
|
1015
|
-
const group = this.#wrapOptgroupTag(node);
|
|
1016
|
-
this.#reflectButtons(node.children, group);
|
|
1017
|
-
container.appendChild(group);
|
|
1018
|
-
}
|
|
1019
|
-
else {
|
|
1020
|
-
const group = this.#wrapDatalistTag(node);
|
|
1021
|
-
this.#reflectButtons(node.children, group);
|
|
1022
|
-
container.appendChild(group);
|
|
1023
|
-
}
|
|
1024
|
-
else
|
|
1025
|
-
container.appendChild(this.#wrapOptionTag(node));
|
|
896
|
+
const flatten = ['radio-group', 'palette', 'segmented'].includes(this.pickerStyle);
|
|
897
|
+
for (const node of nodes) {
|
|
898
|
+
if (DictEntry.isLeaf(node)) {
|
|
899
|
+
container.appendChild(this.#wrapOptionTag(node));
|
|
900
|
+
continue;
|
|
901
|
+
}
|
|
902
|
+
if (flatten) {
|
|
903
|
+
container.appendChild(this.#wrapOptgroupTag(node)); // header row, same markup as the datalist one in these styles
|
|
904
|
+
this.#reflectButtons(node.children, container);
|
|
905
|
+
continue;
|
|
1026
906
|
}
|
|
907
|
+
const group = DictEntry.isGroup(node) ? this.#wrapOptgroupTag(node) : this.#wrapDatalistTag(node);
|
|
908
|
+
this.#reflectButtons(node.children, group);
|
|
909
|
+
container.appendChild(group);
|
|
910
|
+
}
|
|
1027
911
|
}
|
|
1028
912
|
// #reflectPlaceholder(value: string | null) {
|
|
1029
913
|
// debug(`#reflectPlaceholder`)
|
|
@@ -1042,8 +926,8 @@ export class PickerView extends FormAssociatedBase {
|
|
|
1042
926
|
debug(`${_a.name} #reflectSelectionOnButtons`);
|
|
1043
927
|
self.requestAnimationFrame(() => {
|
|
1044
928
|
this.ariaCurrent = this.#selection;
|
|
1045
|
-
const groupMap =
|
|
1046
|
-
const groupContainsSelection = (
|
|
929
|
+
const groupMap = Dictionary.index(this.#lastIndexedRoot);
|
|
930
|
+
const groupContainsSelection = (e) => DictEntry.leafValues(e).includes(this.#selection);
|
|
1047
931
|
const syncButtons = (root) => {
|
|
1048
932
|
// 1. plain value buttons
|
|
1049
933
|
for (const el of root.querySelectorAll('button[value]:not([slot])'))
|
|
@@ -1122,13 +1006,16 @@ export class PickerView extends FormAssociatedBase {
|
|
|
1122
1006
|
case 'sheet':
|
|
1123
1007
|
case 'navigation-link': {
|
|
1124
1008
|
trigger = this.querySelector(':scope>label-view:not([slot])') ?? undefined;
|
|
1009
|
+
break;
|
|
1125
1010
|
}
|
|
1126
1011
|
case 'menu': {
|
|
1127
1012
|
trigger = this.querySelector(':scope>menu-view:not([slot])>label-view[slot=label]') ?? undefined;
|
|
1013
|
+
break;
|
|
1128
1014
|
}
|
|
1129
1015
|
case 'inline':
|
|
1130
1016
|
default: {
|
|
1131
1017
|
//udnefined
|
|
1018
|
+
break;
|
|
1132
1019
|
}
|
|
1133
1020
|
}
|
|
1134
1021
|
if (this.hasAttribute('help'))
|