@swiftwc/ui 0.0.0-dev.11 → 0.0.0-dev.110

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