@swiftwc/ui 0.0.0-dev.6 → 0.0.0-dev.60

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