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

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