@swiftwc/ui 0.0.0-dev.70 → 0.0.0-dev.72

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.
@@ -3,7 +3,7 @@ declare const pickerStyles: readonly ["menu", "inline", "navigation-link", "shee
3
3
  export type PickerStyle = (typeof pickerStyles)[number];
4
4
  export type DictEntry = {
5
5
  value: string;
6
- label?: string;
6
+ title?: string;
7
7
  subtitle?: string;
8
8
  systemImage?: string;
9
9
  children: DictEntry[];
@@ -6,6 +6,7 @@ import { MutationObserverSet } from '../internal/class/mutation-observer-set';
6
6
  import { NavigationPath } from '../internal/class/navigation-path';
7
7
  import { queryInsertPosition, startViewTransition } from '../internal/privateNamespace';
8
8
  import { $, devFlags, kebabCase, onoff, renderLabel, renderLabelIcon, renderLabelTitle } from '../internal/utils';
9
+ import { html, render } from '../tpl';
9
10
  const pickerStyles = ['menu', 'inline', 'navigation-link', 'sheet', 'automatic'];
10
11
  const update = (path, node, overwrite = true) => {
11
12
  if (devFlags.debug)
@@ -20,9 +21,9 @@ const update = (path, node, overwrite = true) => {
20
21
  if (page)
21
22
  page.insertAdjacentElement(position, node);
22
23
  };
23
- const reflectSpawnedElement = (current, source) => {
24
+ const reflectSpawnedPage = (current, source) => {
24
25
  if (devFlags.debug)
25
- console.debug(`PickerView: reflectSpawnedElement`);
26
+ console.debug(`PickerView: reflectSpawnedPage`);
26
27
  const { page: oldSv, toolBarConfig: oldToolbar } = new NavigationPath(current).hydrate(), { page: newSv, toolBarConfig: newToolbar } = new NavigationPath(source).hydrate();
27
28
  const oldList = oldSv?.querySelector('list-view'), newList = newSv?.querySelector('list-view'), oldBackBtn = oldToolbar?.at(0)?.querySelector('button'), newBackBtn = newToolbar?.at(0)?.querySelector('button');
28
29
  // pre replace list (always exists)
@@ -37,7 +38,7 @@ const reflectSpawnedElement = (current, source) => {
37
38
  summary.parentElement?.setAttribute('open', 'open');
38
39
  break;
39
40
  }
40
- // replace backbtns (always exists)
41
+ // replace backbtns (always exist)
41
42
  if (oldBackBtn && newBackBtn)
42
43
  oldBackBtn.replaceWith(newBackBtn);
43
44
  // overwrite navtitle (always exists)
@@ -52,33 +53,43 @@ const reflectSpawnedElement = (current, source) => {
52
53
  oldSv?.insertAdjacentHTML('afterend', `<sticky-container edge="navbar" style="order: -1" padding>
53
54
  <v-stack spacing="0" alignment="fill">
54
55
  <input is="search-view" placeholder="Search">
55
- <button type="button">Filters</button>
56
+ <!--<button type="button">Filters</button>-->
56
57
  </v-stack>
57
58
  </sticky-container>`);
58
59
  }
59
60
  else
60
61
  oldSearch?.remove();
61
62
  };
62
- const extractTagFromOption = (node) => {
63
+ const extractTag = (node) => {
63
64
  if (devFlags.debug)
64
- console.debug(`PickerView: extractTagFromOption`);
65
+ console.debug(`PickerView: extractTag`);
65
66
  return node instanceof HTMLOptionElement ? (((node.getAttribute('value') ?? node.textContent?.trim()) || node.getAttribute('label')) ?? '') : (node.value ?? '');
66
- }, extractCurrentValueFromOption = (node) => {
67
- if (devFlags.debug)
68
- console.debug(`PickerView: extractCurrentValueFromOption`);
69
- return node instanceof HTMLOptionElement ? (node.getAttribute('label') ?? node.getAttribute('value') ?? node.textContent?.trim()) || '' : (node.label ?? node.value ?? '');
70
- }, extractIconFromOption = (node) => {
71
- if (devFlags.debug)
72
- console.debug(`PickerView: extractIconFromOption`);
73
- return node instanceof HTMLOptionElement ? node.getAttribute('data-system-image') : (node.systemImage ?? null);
74
- }, extractLabelFromGroup = (node) => {
67
+ }, extractLabel = (node) => {
75
68
  if (devFlags.debug)
76
69
  console.debug(`PickerView: extractLabelFromGroup`);
77
- return node instanceof Element ? node.getAttribute('DATALIST' === node.tagName ? 'data-label' : 'label') : (node.label ?? node.value ?? null);
78
- }, extractImgFromGroup = (node) => {
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) => {
79
87
  if (devFlags.debug)
80
- console.debug(`PickerView: extractImgFromGroup`);
81
- return node instanceof Element ? node.getAttribute('data-system-image') : (node.systemImage ?? null);
88
+ console.debug(`PickerView: extractSubtitle`);
89
+ if (node instanceof Element)
90
+ return node.getAttribute('data-subtitle');
91
+ else
92
+ return node.subtitle ?? null;
82
93
  };
83
94
  const allLeaves = (node) => node.children.every((c) => 0 === c.children.length);
84
95
  const parseDictionary = (value) => {
@@ -143,7 +154,7 @@ export class PickerView extends FormAssociatedBase {
143
154
  const { target } = evt;
144
155
  if (!(target instanceof HTMLElement))
145
156
  return;
146
- const newPage = this.#spawnPage(el instanceof Element ? Array.from(el.children) : el.children, 'body-view', groupId, this.hasAttribute('searchable'), extractLabelFromGroup(el));
157
+ const newPage = this.#spawnPage(el instanceof Element ? Array.from(el.children) : el.children, 'body-view', groupId, this.hasAttribute('searchable'), extractLabel(el));
147
158
  if (!newPage)
148
159
  return;
149
160
  newPage.dataset.groupId = groupId; // <-- tag the spawned page
@@ -165,7 +176,7 @@ export class PickerView extends FormAssociatedBase {
165
176
  list?.insertAdjacentHTML('afterend', `<sticky-container edge="navbar" style="order: -1" padding>
166
177
  <v-stack spacing="0" alignment="fill">
167
178
  <input is="search-view" placeholder="Search">
168
- <button type="button">Filters</button>
179
+ <!--<button type="button">Filters</button>-->
169
180
  </v-stack>
170
181
  </sticky-container>`);
171
182
  const searchInput = sv?.querySelector('input'); // FIXME: compoennt is=search-view??
@@ -279,10 +290,10 @@ export class PickerView extends FormAssociatedBase {
279
290
  continue;
280
291
  }
281
292
  const children = source instanceof Element ? Array.from(source.children) : source.children;
282
- const title = source instanceof Element ? extractLabelFromGroup(source) : (source.label ?? null);
293
+ const title = source instanceof Element ? extractLabel(source) : (source.title ?? null);
283
294
  const newPage = this.#spawnPage(children, 'body-view', groupId, this.hasAttribute('searchable'), title);
284
295
  newPage.dataset.groupId = groupId;
285
- reflectSpawnedElement(el, newPage);
296
+ reflectSpawnedPage(el, newPage);
286
297
  }
287
298
  };
288
299
  #renderButtons(input) {
@@ -300,8 +311,8 @@ export class PickerView extends FormAssociatedBase {
300
311
  continue;
301
312
  }
302
313
  idx[idx.length - 1]++;
303
- const { value, label, systemImage, children } = frame[i];
304
- out1[value] = label;
314
+ const { value, title, systemImage, children } = frame[i];
315
+ out1[value] = title;
305
316
  out2[value] = systemImage;
306
317
  if (children.length) {
307
318
  stack.push(children);
@@ -333,15 +344,15 @@ export class PickerView extends FormAssociatedBase {
333
344
  this.#lastRenderedIconMap = {};
334
345
  for (const el of input.source)
335
346
  if (el.matches('option')) {
336
- const k = extractTagFromOption(el);
347
+ const k = extractTag(el);
337
348
  this.#lastRenderedLabelMap[k] = el.getAttribute('label') ?? undefined;
338
- this.#lastRenderedIconMap[k] = extractIconFromOption(el) ?? undefined;
349
+ this.#lastRenderedIconMap[k] = extractIcon(el) ?? undefined;
339
350
  }
340
351
  else
341
352
  for (const opt of el.querySelectorAll('option')) {
342
- const k = extractTagFromOption(opt);
353
+ const k = extractTag(opt);
343
354
  this.#lastRenderedLabelMap[k] = opt.getAttribute('label') ?? undefined;
344
- this.#lastRenderedIconMap[k] = extractIconFromOption(opt) ?? undefined;
355
+ this.#lastRenderedIconMap[k] = extractIcon(opt) ?? undefined;
345
356
  }
346
357
  this.#lastIndexedRoot = input.source;
347
358
  // this.#lastRenderedGroupMap = assigned.flatMap<string[]>((el) =>
@@ -374,7 +385,7 @@ export class PickerView extends FormAssociatedBase {
374
385
  if (!this.#spawn)
375
386
  break;
376
387
  // rerender level 0
377
- reflectSpawnedElement(this.#spawn, this.#spawnPage(input.source, 'body-view', undefined, this.hasAttribute('searchable'), this.getAttribute('label')));
388
+ reflectSpawnedPage(this.#spawn, this.#spawnPage(input.source, 'DIALOG' === this.#spawn.tagName ? 'sheet-view' : 'body-view', undefined, this.hasAttribute('searchable'), this.getAttribute('label')));
378
389
  this.#resyncSpawnedPages(input.source);
379
390
  // // FIXME:
380
391
  // for (const el of this.#spawn.querySelectorAll<HTMLElement>('body-view')) {
@@ -473,13 +484,11 @@ export class PickerView extends FormAssociatedBase {
473
484
  #lastRenderedIconMap = {};
474
485
  #lastIndexedRoot = [];
475
486
  get #currentValueLabel() {
476
- const cvl = this.#lastRenderedLabelMap[this.#selection];
477
487
  return ((this.getAttribute(this.constructor.ATTR.CURRENT_VALUE_LABEL) ?? '').replaceAll('{{selection}}', this.#selection).replaceAll('{{currentValueLabel}}', this.#selection) ||
478
- cvl);
488
+ this.#lastRenderedLabelMap[this.#selection]);
479
489
  }
480
490
  get #currentValueIcon() {
481
- const cvl = this.#lastRenderedIconMap[this.#selection];
482
- return (this.getAttribute(this.constructor.ATTR.CURRENT_VALUE_ICON) ?? '') || cvl;
491
+ return (this.getAttribute(this.constructor.ATTR.CURRENT_VALUE_ICON) ?? '') || this.#lastRenderedIconMap[this.#selection];
483
492
  }
484
493
  get #internals() {
485
494
  return getInternals(this);
@@ -774,40 +783,106 @@ export class PickerView extends FormAssociatedBase {
774
783
  static #wrapOptionSpawnTag(node) {
775
784
  if (devFlags.debug)
776
785
  console.debug(`${_a.name} #wrapOptionSpawnTag`);
777
- const btn = $(`<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>`, '>1'), hStack = btn.querySelector(':scope>h-stack') ?? undefined;
778
- renderLabel(':scope>label-view:nth-child(2)', `<label-view><span></span></label-view>`, hStack, extractLabelFromGroup(node), extractImgFromGroup(node));
779
- // label = btn.querySelector<LabelView>(':scope>h-stack>label-view:nth-child(2)')
780
- // if (label) {
781
- // const lbl = extractLabelFromGroup(node as HTMLDataListElement),
782
- // img =extractImgFromGroup(node as HTMLDataListElement)
783
- // if(lbl) renderLabelTitle(label, node.dataset.label) //label?.setAttribute('title', el.dataset.label)
784
- // }
785
- return btn;
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
786
821
  }
787
822
  static #wrapOptionTag(node) {
788
823
  if (devFlags.debug)
789
824
  console.debug(`${_a.name} #wrapOptionTag`);
790
- const btn = $(`<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>`, '>1'), hStack = btn.querySelector(':scope>h-stack');
791
- // chevron = hStack?.querySelector<HTMLElement>(':scope>label-view')
792
- btn.setAttribute('value', extractTagFromOption(node));
793
- // if (selection !== btn.getAttribute('value')) chevron?.style.setProperty('visibility', 'hidden')
794
- const label = $(`<label-view></label-view>`, '>1');
795
- renderLabelTitle(label, extractCurrentValueFromOption(node)); // label.querySelector('span')!.textContent = extractCurrentValueFromOption(node) //label.setAttribute('title', extractCurrentValueFromOption(node))
796
- renderLabelIcon(label, extractIconFromOption(node));
797
- hStack?.appendChild(label);
798
- return btn;
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
799
854
  }
800
855
  static #wrapOptgroupTag(node) {
801
856
  if (devFlags.debug)
802
857
  console.debug(`${_a.name} #wrapOptgroupTag`);
803
- const labelT = `<label-view><span></span></label-view>`, 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>`;
804
- const group = $(`<details is="disclosure-group" disclosure-style="marker-trailing">${summaryT}</details>`, '>1'), // NOTE: already applied, here it covers spawned sheets
805
- hStack = group.querySelector(':scope>summary>h-stack') ?? undefined; // ?? group.appendChild($(summaryT, '>1'))
806
- // summaryLabel = summary.querySelector(':scope>label-view') ?? summary.appendChild($(labelT, '>1'))
807
- // if (node.hasAttribute('label')) summaryLabel.setAttribute('title', node.getAttribute('label') ?? '')
808
- // if (node.hasAttribute('data-system-image')) summaryLabel.setAttribute('system-image', node.getAttribute('data-system-image') ?? '')
809
- renderLabel(':scope>label-view:nth-child(2)', labelT, hStack, extractLabelFromGroup(node), extractImgFromGroup(node));
810
- return group;
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
811
886
  }
812
887
  // static #reflectButtons(nodes: Element[], container: Element): void
813
888
  // static #reflectButtons(nodes: Dictionary, container: Element): void
@@ -818,11 +893,32 @@ export class PickerView extends FormAssociatedBase {
818
893
  if (node instanceof Element)
819
894
  switch (node.tagName) {
820
895
  case 'DATALIST': {
821
- const group = $(`<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>`, '>1'), hStack = group.querySelector(':scope>h-stack[slot=label]') ?? undefined;
822
- // label = group.querySelector(':scope>label-view[slot=label]') ?? group.appendChild($(`<label-view slot="label"></label-view>`, '>1'))
823
- // if (node.hasAttribute('data-label')) label.setAttribute('title', node.getAttribute('data-label') ?? '')
824
- // if (node.hasAttribute('data-system-image')) label.setAttribute('system-image', node.getAttribute('data-system-image') ?? '')
825
- renderLabel(':scope>label-view:nth-child(2)', `<label-view><span></span></label-view>`, hStack, extractLabelFromGroup(node), extractImgFromGroup(node));
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))
826
922
  _a.#reflectButtons([...node.children], group);
827
923
  container.appendChild(group);
828
924
  break;
@@ -847,8 +943,25 @@ export class PickerView extends FormAssociatedBase {
847
943
  container.appendChild(group);
848
944
  }
849
945
  else {
850
- const group = $(`<menu-view tabindex="0"></menu-view>`, '>1');
851
- renderLabel(':scope>label-view[slot=label]', `<label-view slot="label"><span></span></label-view>`, group, extractLabelFromGroup(node), extractImgFromGroup(node));
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))
852
965
  _a.#reflectButtons(node.children, group);
853
966
  container.appendChild(group);
854
967
  }
@@ -876,9 +989,7 @@ export class PickerView extends FormAssociatedBase {
876
989
  self.requestAnimationFrame(() => {
877
990
  this.ariaCurrent = this.#selection;
878
991
  const groupMap = indexGroups(this.#lastIndexedRoot);
879
- const groupContainsSelection = (source) => source instanceof Element
880
- ? [...source.querySelectorAll('option')].some((opt) => extractTagFromOption(opt) === this.#selection)
881
- : collectLeafValues(source).includes(this.#selection);
992
+ const groupContainsSelection = (source) => source instanceof Element ? [...source.querySelectorAll('option')].some((opt) => extractTag(opt) === this.#selection) : collectLeafValues(source).includes(this.#selection);
882
993
  const syncButtons = (root) => {
883
994
  // 1. plain value buttons — unchanged
884
995
  for (const el of root.querySelectorAll('button[value]:not([slot])'))
@@ -914,22 +1025,20 @@ export class PickerView extends FormAssociatedBase {
914
1025
  const currentValueLabel = this.querySelector(':scope>label-view:not([slot])');
915
1026
  if (!currentValueLabel)
916
1027
  break;
917
- const cvl = this.#currentValueLabel, cvi = this.#currentValueIcon;
918
1028
  // if (!cvl) currentValueLabel.setAttribute('foreground', 'secondary')
919
1029
  // else currentValueLabel.removeAttribute('foreground')
920
- renderLabelTitle(currentValueLabel, cvl || this.getAttribute(this.constructor.ATTR.PLACEHOLDER) || this.#selection);
921
- renderLabelIcon(currentValueLabel, cvi || this.getAttribute(this.constructor.ATTR.PLACEHOLDER_ICON));
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));
922
1032
  break;
923
1033
  }
924
1034
  case 'menu': {
925
1035
  const currentValueLabel = this.querySelector(':scope>menu-view:not([slot])>label-view[slot=label]');
926
1036
  if (!currentValueLabel)
927
1037
  break;
928
- const cvl = this.#currentValueLabel, cvi = this.#currentValueIcon;
929
1038
  // if (!cvl) currentValueLabel.setAttribute('foreground', 'secondary')
930
1039
  // else currentValueLabel.removeAttribute('foreground')
931
- renderLabelTitle(currentValueLabel, cvl || this.getAttribute(this.constructor.ATTR.PLACEHOLDER) || this.#selection);
932
- renderLabelIcon(currentValueLabel, cvi || this.getAttribute(this.constructor.ATTR.PLACEHOLDER_ICON));
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));
933
1042
  break;
934
1043
  }
935
1044
  case 'inline':
@@ -1,5 +1,9 @@
1
+ import type { ScrollViewEventMap } from '../events';
2
+ import type { TypedEventTargetMethods } from '../namespace-browser/event';
1
3
  export declare class ScrollView extends HTMLElement {
2
4
  #private;
5
+ addEventListener: TypedEventTargetMethods<ScrollViewEventMap>['addEventListener'];
6
+ removeEventListener: TypedEventTargetMethods<ScrollViewEventMap>['removeEventListener'];
3
7
  static get observedAttributes(): string[];
4
8
  static get template(): DocumentFragment;
5
9
  constructor();
@@ -1,4 +1,6 @@
1
+ import type { TextFieldEventMap } from '../events';
1
2
  import { FormAssociatedBase } from '../internal/class/form-associated-base';
3
+ import type { TypedEventTargetMethods } from '../namespace-browser/event';
2
4
  declare const keyboardTypes: readonly ["decimal-pad", "number-pad", "default"];
3
5
  export type KeyboardType = (typeof keyboardTypes)[number];
4
6
  /**
@@ -7,6 +9,8 @@ export type KeyboardType = (typeof keyboardTypes)[number];
7
9
  */
8
10
  export declare class TextField extends FormAssociatedBase {
9
11
  #private;
12
+ addEventListener: TypedEventTargetMethods<TextFieldEventMap>['addEventListener'];
13
+ removeEventListener: TypedEventTargetMethods<TextFieldEventMap>['removeEventListener'];
10
14
  static get observedAttributes(): string[];
11
15
  static get template(): DocumentFragment;
12
16
  constructor();
@@ -1,4 +1,6 @@
1
+ import type { ToggleViewEventMap } from '../events';
1
2
  import { FormAssociatedBase } from '../internal/class/form-associated-base';
3
+ import type { TypedEventTargetMethods } from '../namespace-browser/event';
2
4
  declare const toggleStyles: readonly ["switch", "button"];
3
5
  export type ToggleStyle = (typeof toggleStyles)[number];
4
6
  /**
@@ -7,6 +9,8 @@ export type ToggleStyle = (typeof toggleStyles)[number];
7
9
  */
8
10
  export declare class ToggleView extends FormAssociatedBase {
9
11
  #private;
12
+ addEventListener: TypedEventTargetMethods<ToggleViewEventMap>['addEventListener'];
13
+ removeEventListener: TypedEventTargetMethods<ToggleViewEventMap>['removeEventListener'];
10
14
  static get observedAttributes(): string[];
11
15
  static get template(): DocumentFragment;
12
16
  constructor();
@@ -82,7 +82,7 @@ export class ToggleView extends FormAssociatedBase {
82
82
  this.#sendValueToForm(this.#connected);
83
83
  break;
84
84
  case 'label':
85
- renderLabel(':scope>label-view[slot=label]', `<label-view slot="label" foreground="secondary"><span></span></label-view>`, this, newValue);
85
+ renderLabel(':scope>label-view[slot=label]', `<label-view slot="label"><span></span></label-view>`, this, newValue);
86
86
  break;
87
87
  case 'disabled':
88
88
  for (const el of this.#shadowRoot.querySelectorAll('input'))
@@ -2,9 +2,7 @@ import { type ScrollView } from '../components';
2
2
  export type PageRevealSwapDetail = {
3
3
  page: ScrollView;
4
4
  };
5
- declare global {
6
- interface HTMLElementEventMap {
7
- pagereveal: CustomEvent<PageRevealSwapDetail>;
8
- pageswap: CustomEvent<PageRevealSwapDetail>;
9
- }
5
+ export interface ScrollViewEventMap {
6
+ pagereveal: CustomEvent<PageRevealSwapDetail>;
7
+ pageswap: CustomEvent<PageRevealSwapDetail>;
10
8
  }
@@ -1,8 +1,6 @@
1
1
  export type TextFieldCommitDetail = {
2
2
  text: string;
3
3
  };
4
- declare global {
5
- interface HTMLElementEventMap {
6
- commit: CustomEvent<TextFieldCommitDetail>;
7
- }
4
+ export interface TextFieldEventMap {
5
+ commit: CustomEvent<TextFieldCommitDetail>;
8
6
  }
@@ -1 +1,6 @@
1
1
  export {};
2
+ // declare global {
3
+ // interface HTMLElementEventMap {
4
+ // commit: CustomEvent<TextFieldCommitDetail>
5
+ // }
6
+ // }
@@ -2,8 +2,6 @@ export type ToggleChangeDetail = {
2
2
  isOn: boolean;
3
3
  value: string | null;
4
4
  };
5
- declare global {
6
- interface HTMLElementEventMap {
7
- change: CustomEvent<ToggleChangeDetail>;
8
- }
5
+ export interface ToggleViewEventMap {
6
+ commit: CustomEvent<ToggleChangeDetail>;
9
7
  }
@@ -1 +1,6 @@
1
1
  export {};
2
+ // declare global {
3
+ // interface HTMLElementEventMap {
4
+ // commit: CustomEvent<ToggleChangeDetail>
5
+ // }
6
+ // }
@@ -0,0 +1,10 @@
1
+ export interface TypedEventTargetMethods<M> {
2
+ addEventListener: {
3
+ <K extends keyof M>(type: K, listener: (this: HTMLElement, ev: M[K] extends Event ? M[K] : never) => void, options?: boolean | AddEventListenerOptions): void;
4
+ (type: string, listener: EventListenerOrEventListenerObject | null, options?: boolean | AddEventListenerOptions): void;
5
+ };
6
+ removeEventListener: {
7
+ <K extends keyof M>(type: K, listener: (this: HTMLElement, ev: M[K] extends Event ? M[K] : never) => void, options?: boolean | EventListenerOptions): void;
8
+ (type: string, listener: EventListenerOrEventListenerObject | null, options?: boolean | EventListenerOptions): void;
9
+ };
10
+ }
@@ -0,0 +1 @@
1
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@swiftwc/ui",
3
- "version": "0.0.0-dev.70",
3
+ "version": "0.0.0-dev.72",
4
4
  "description": "Elegant SwiftUI-inspired web components for standalone web apps and web extensions.",
5
5
  "publishConfig": {
6
6
  "access": "public"