@swiftwc/ui 0.0.0-dev.35 → 0.0.0-dev.37

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 (95) hide show
  1. package/generated/client/index.d.ts +2 -9
  2. package/generated/client/index.js +11 -156
  3. package/generated/components/alert-dialog.js +18 -21
  4. package/generated/components/body-view.js +3 -2
  5. package/generated/components/bordered-button.js +4 -4
  6. package/generated/components/bordered-prominent-button.js +4 -4
  7. package/generated/components/borderless-button.js +4 -4
  8. package/generated/components/confirmation-dialog.js +18 -21
  9. package/generated/components/content-unavailable-view.js +4 -4
  10. package/generated/components/date-picker.d.ts +0 -10
  11. package/generated/components/date-picker.js +43 -28
  12. package/generated/components/disclosure-group.js +5 -5
  13. package/generated/components/fine-tooltip.js +4 -4
  14. package/generated/components/form-view.js +3 -3
  15. package/generated/components/glass-button.js +4 -4
  16. package/generated/components/glass-prominent-button.js +4 -4
  17. package/generated/components/index.d.ts +1 -0
  18. package/generated/components/index.js +1 -0
  19. package/generated/components/label-view.js +4 -4
  20. package/generated/components/list-view.js +15 -17
  21. package/generated/components/menu-view.js +14 -16
  22. package/generated/components/navigation-large-title.js +5 -5
  23. package/generated/components/navigation-split-view.js +4 -3
  24. package/generated/components/navigation-stack.js +4 -3
  25. package/generated/components/navigation-title.js +4 -4
  26. package/generated/components/picker-view.d.ts +2 -2
  27. package/generated/components/picker-view.js +313 -192
  28. package/generated/components/plain-button.js +3 -3
  29. package/generated/components/progress-view.js +6 -6
  30. package/generated/components/screen-view.js +2 -1
  31. package/generated/components/scroll-view-proxy.js +3 -2
  32. package/generated/components/scroll-view.js +15 -18
  33. package/generated/components/search-view.d.ts +8 -0
  34. package/generated/components/search-view.js +27 -0
  35. package/generated/components/section-view.js +4 -4
  36. package/generated/components/sheet-view.js +6 -34
  37. package/generated/components/sidebar-toggle.js +9 -10
  38. package/generated/components/sidebar-view.js +6 -8
  39. package/generated/components/sticky-container.js +3 -3
  40. package/generated/components/tab-bar.js +8 -10
  41. package/generated/components/tab-item.js +8 -8
  42. package/generated/components/tab-view.js +10 -11
  43. package/generated/components/table-view.js +18 -36
  44. package/generated/components/text-field.js +32 -17
  45. package/generated/components/tool-bar-item-group.js +17 -19
  46. package/generated/components/tool-bar-item.js +22 -23
  47. package/generated/components/tool-bar.d.ts +2 -2
  48. package/generated/components/tool-bar.js +7 -7
  49. package/generated/components/v-keyboard.js +5 -5
  50. package/generated/components/v-stack.js +3 -2
  51. package/generated/css/index.css +286 -272
  52. package/generated/events/picker-view.d.ts +6 -0
  53. package/generated/i18n/locales/el.d.ts +1 -0
  54. package/generated/i18n/locales/el.js +1 -0
  55. package/generated/i18n/locales/en.d.ts +1 -0
  56. package/generated/i18n/locales/en.js +1 -0
  57. package/generated/internal/class/css-style-observer.js +2 -2
  58. package/generated/internal/class/form-associated-base.d.ts +0 -4
  59. package/generated/internal/class/form-associated-base.js +21 -41
  60. package/generated/internal/class/mutation-observer-set.d.ts +9 -0
  61. package/generated/internal/class/mutation-observer-set.js +42 -0
  62. package/generated/internal/class/mutation-observer-singleton.js +18 -0
  63. package/generated/internal/class/navigation-view.js +3 -3
  64. package/generated/internal/decorators/adaptive-slot.d.ts +1 -0
  65. package/generated/internal/decorators/adaptive-slot.js +47 -0
  66. package/generated/internal/decorators/custom-element.d.ts +1 -1
  67. package/generated/internal/decorators/custom-element.js +2 -1
  68. package/generated/internal/decorators/index.d.ts +4 -0
  69. package/generated/internal/decorators/index.js +4 -0
  70. package/generated/internal/privateNamespace.d.ts +10 -0
  71. package/generated/internal/privateNamespace.js +159 -1
  72. package/generated/internal/utils/debug.d.ts +1 -0
  73. package/generated/internal/utils/debug.js +3 -0
  74. package/generated/internal/utils/index.d.ts +3 -3
  75. package/generated/internal/utils/index.js +5 -3
  76. package/generated/namespace-browser/base.d.ts +9 -0
  77. package/generated/namespace-browser/base.js +23 -0
  78. package/generated/snapshot/index.d.ts +1 -1
  79. package/generated/snapshot/index.js +4 -4
  80. package/package.json +2 -1
  81. package/scss/_components.scss +2 -0
  82. package/scss/base/_root.scss +10 -8
  83. package/scss/colors/_index.scss +9 -12
  84. package/scss/components/_h-stack.scss +1 -1
  85. package/scss/components/_index.scss +2 -0
  86. package/scss/components/_menu-view.scss +11 -5
  87. package/scss/components/_picker-view.scss +11 -2
  88. package/scss/components/_scroll-view.scss +4 -0
  89. package/scss/components/_search-view.scss +18 -0
  90. package/scss/components/_sheet-view.scss +2 -1
  91. package/scss/components/_sticky-container.scss +1 -1
  92. package/scss/placeholders/_lists.scss +46 -36
  93. package/scss/utils/_index.scss +21 -7
  94. package/scss/utils/_stacks.scss +5 -43
  95. package/web-components.html-data/en.json +11 -0
@@ -1,10 +1,41 @@
1
1
  var _a;
2
+ import { I18n } from '../i18n';
2
3
  import { CleanupRegistry } from '../internal/class/cleanup-registry';
3
- import { FormAssociatedBase, getInternals, makeSlotchangeHandler } from '../internal/class/form-associated-base';
4
- import { MutationObserverSingleton } from '../internal/class/mutation-observer-singleton';
5
- import { $, kebabCase, onoff } from '../internal/utils';
6
- const pickerStyles = ['menu', 'inline', 'automatic'];
7
- const observers = new MutationObserverSingleton();
4
+ import { FormAssociatedBase, getInternals } from '../internal/class/form-associated-base';
5
+ import { MutationObserverSet } from '../internal/class/mutation-observer-set';
6
+ import { NavigationPath } from '../internal/class/navigation-path';
7
+ import { queryInsertPosition, startViewTransition } from '../internal/privateNamespace';
8
+ import { $, debug, kebabCase, onoff } from '../internal/utils';
9
+ const pickerStyles = ['menu', 'inline', 'navigation-link', 'sheet', 'automatic'];
10
+ const update = (path, node, overwrite = true) => {
11
+ if (!(path instanceof NavigationPath))
12
+ throw new Error('invalid view');
13
+ const { component, page } = path;
14
+ if (!component)
15
+ return;
16
+ const position = queryInsertPosition(component); //'afterend'
17
+ // const lookFor = 'beforebegin' === position ? 'previousElementSibling' : 'nextElementSibling'
18
+ if (page)
19
+ page.insertAdjacentElement(position, node);
20
+ };
21
+ const replaceList = (current, source) => {
22
+ const { page: oldPage, toolBarConfig: oldToolbar } = new NavigationPath(current).hydrate(), { page: newPage, toolBarConfig: newToolbar } = new NavigationPath(source).hydrate();
23
+ const oldList = oldPage?.querySelector('list-view'), newList = newPage?.querySelector('list-view'), oldBackBtn = oldToolbar?.at(0)?.querySelector('button'), newBackBtn = newToolbar?.at(0)?.querySelector('button');
24
+ // pre replace
25
+ const oldOpenedDetails = [...(oldList?.querySelectorAll('details[open]>summary') ?? [])].map((item) => item.textContent.trim());
26
+ // replace
27
+ if (oldList && newList)
28
+ oldList.replaceWith(newList);
29
+ if (oldBackBtn && newBackBtn)
30
+ oldBackBtn.replaceWith(newBackBtn);
31
+ // post replace
32
+ for (const label of oldOpenedDetails)
33
+ for (const summary of newList?.querySelectorAll('details>summary') ?? [])
34
+ if (summary.textContent.trim() === label) {
35
+ summary.parentElement?.setAttribute('open', 'open');
36
+ break;
37
+ }
38
+ };
8
39
  export class PickerView extends FormAssociatedBase {
9
40
  static get ATTR() {
10
41
  return {
@@ -12,37 +43,182 @@ export class PickerView extends FormAssociatedBase {
12
43
  LABEL: 'label',
13
44
  PICKER_STYLE: 'picker-style',
14
45
  SELECTION: 'selection',
46
+ SEARCHABLE: 'searchable',
15
47
  };
16
48
  }
17
49
  static get observedAttributes() {
18
50
  return Object.values(this.ATTR);
19
51
  }
20
52
  static #templates = new Map();
53
+ #spawn;
54
+ #renderPage = (tag, searchable = false, title, node) => {
55
+ 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');
56
+ if (title)
57
+ sv?.setAttribute('navigation-inline-title', title);
58
+ if (searchable) {
59
+ list?.style.setProperty('--list--sticky-block-size', '70px');
60
+ list?.insertAdjacentHTML('afterend', `<sticky-container edge="navbar" style="order: -1" padding>
61
+ <v-stack spacing="0" alignment="fill">
62
+ <input is="search-view" placeholder="Search">
63
+ <button type="button">Filters</button>
64
+ </v-stack>
65
+ </sticky-container>`);
66
+ const searchInput = sv?.querySelector('input[type=search]');
67
+ searchInput?.addEventListener('focus', (evt) => {
68
+ if (this.#spawn)
69
+ this.dispatchEvent(new CustomEvent('picker:searchfocus', { detail: { element: this.#spawn }, bubbles: true, composed: true }));
70
+ });
71
+ searchInput?.addEventListener('blur', (evt) => {
72
+ if (this.#spawn)
73
+ this.dispatchEvent(new CustomEvent('picker:searchblur', { detail: { element: this.#spawn }, bubbles: true, composed: true }));
74
+ });
75
+ searchInput?.addEventListener('input', (evt) => {
76
+ if (this.#spawn)
77
+ this.dispatchEvent(new CustomEvent('picker:searchinput', { detail: { element: this.#spawn }, bubbles: true, composed: true }));
78
+ });
79
+ }
80
+ backBtn?.setAttribute('help', I18n.t('ButtonRole').Back);
81
+ backBtn?.addEventListener('click', async (evt) => {
82
+ evt.stopImmediatePropagation();
83
+ evt.preventDefault();
84
+ const { target } = evt;
85
+ if (!(target instanceof HTMLElement && target))
86
+ return;
87
+ const { component } = new NavigationPath(target)?.hydrate();
88
+ await startViewTransition(target, 'backwards', async () => {
89
+ component?.remove();
90
+ });
91
+ });
92
+ const elements = node ? Array.from(node.children) : (this.#slots?.get('list')?.assignedElements() ?? []);
93
+ for (const el of elements) {
94
+ if (!(el instanceof HTMLElement))
95
+ continue;
96
+ switch (el.tagName) {
97
+ case 'OPTGROUP': {
98
+ const details = _a.#wrapOptgroupTag(el);
99
+ _a.#reflectButtons([...el.children], details);
100
+ for (const btn of details.querySelectorAll(':scope>button'))
101
+ btn.addEventListener('click', this.#handlePageClick);
102
+ list?.appendChild(details);
103
+ break;
104
+ }
105
+ case 'OPTION': {
106
+ const btn = _a.#wrapOptionTag(el);
107
+ btn.addEventListener('click', this.#handlePageClick);
108
+ list?.appendChild(btn);
109
+ break;
110
+ }
111
+ default: {
112
+ const btn = $(`<button type="button" tabindex="0"><label-view></label-view></button>`, '>1'), label = btn.querySelector(':scope>label-view');
113
+ if (el.dataset.label)
114
+ label?.setAttribute('title', el.dataset.label);
115
+ btn.addEventListener('click', async (evt) => {
116
+ evt.stopImmediatePropagation();
117
+ evt.preventDefault();
118
+ const { target } = evt;
119
+ if (!(target instanceof HTMLElement && target))
120
+ return;
121
+ const newPage = this.#renderPage('body-view', this.hasAttribute('searchable'), el.dataset.label, el);
122
+ if (!newPage)
123
+ return;
124
+ const path = new NavigationPath(target)?.hydrate();
125
+ await startViewTransition(target, 'forwards', async () => {
126
+ update(path, newPage);
127
+ });
128
+ });
129
+ list?.appendChild(btn);
130
+ break;
131
+ }
132
+ }
133
+ }
134
+ return body;
135
+ };
136
+ #renderList = (entries) => {
137
+ debug(`${_a.name} ⚡️ mutation`);
138
+ switch (this.pickerStyle) {
139
+ case 'sheet':
140
+ case 'navigation-link': {
141
+ // label
142
+ const label = this.querySelector(':scope>label-view') ?? this.appendChild($(`<label-view system-image="dots-three"></label-view>`, '>1'));
143
+ label.setAttribute('title', 'rtyty');
144
+ CleanupRegistry.unregister(this, 'trigger');
145
+ CleanupRegistry.register(this, onoff('click', this.#handleTriggerClick, label).on(), 'trigger');
146
+ this.appendChild(label);
147
+ // rebuild snapshot(tree)
148
+ if (!this.#spawn)
149
+ break;
150
+ // rerender level 0
151
+ replaceList(this.#spawn, this.#renderPage('body-view', this.hasAttribute('searchable'), this.getAttribute('label')));
152
+ for (const el of this.#spawn.querySelectorAll('body-view')) {
153
+ const depth = $.ancestors('body-view,[is=sheet-view]', el).indexOf(this.#spawn);
154
+ if (0 >= depth)
155
+ continue;
156
+ const datalist = this.querySelector(`:scope>${Array.from({ length: depth }, () => 'datalist').join('>')}`);
157
+ if (!datalist) {
158
+ el.remove();
159
+ break;
160
+ }
161
+ replaceList(el, this.#renderPage('body-view', this.hasAttribute('searchable'), datalist.dataset.label, datalist));
162
+ }
163
+ break;
164
+ }
165
+ case 'menu': {
166
+ const menu = this.querySelector(':scope>menu-view:not([slot])') ?? this.appendChild($(`<menu-view tabindex="0"></menu-view>`, '>1')), label = menu.querySelector(':scope>label-view[slot=label]') ?? menu.appendChild($(`<label-view slot="label" system-image="dots-three"></label-view>`, '>1'));
167
+ label.setAttribute('title', 'rtyty');
168
+ for (const el of menu.querySelectorAll(':scope>:not([slot])'))
169
+ el.remove();
170
+ _a.#reflectButtons([...(this.#slots?.get('list')?.assignedElements() ?? [])], menu);
171
+ break;
172
+ }
173
+ case 'inline':
174
+ default: {
175
+ const inlineList = this.querySelector(':scope>list-view:not([slot])') ?? this.appendChild($(`<list-view><section-view></section-view></list-view>`, '>1')), section = inlineList.querySelector(':scope>section-view') ?? inlineList.appendChild($(`<section-view></section-view>`, '>1'));
176
+ for (const el of section.querySelectorAll(':scope>:not([slot])'))
177
+ el.remove(); // section.innerHTML = ''
178
+ const label = this.getAttribute(this.constructor.ATTR.LABEL);
179
+ if (label) {
180
+ const el = $(`<label-view></label-view>`, '>1');
181
+ el.setAttribute('title', label);
182
+ section.insertAdjacentElement('beforeend', el);
183
+ }
184
+ _a.#reflectButtons([...(this.#slots?.get('list')?.assignedElements({ flatten: true }) ?? [])].filter((el) => el.matches('option')), section);
185
+ break;
186
+ }
187
+ }
188
+ };
189
+ #renderValidityMsgs = (entries) => {
190
+ debug(`${_a.name} ⚡️ mutation`);
191
+ this.setValidity(this.validity, this.validationMessage);
192
+ };
21
193
  #lastRenderedStyle; //string | null
22
194
  #shadowRoot;
23
195
  #slots = new Map();
196
+ #validityObservers = new MutationObserverSet(this.#renderValidityMsgs);
197
+ #observers = new MutationObserverSet(this.#renderList);
24
198
  #customValidity = '';
25
- // #validitiesSlot?: HTMLSlotElement
26
- // #datalistSlot?: HTMLSlotElement
27
- // #tagSlot?: HTMLSlotElement
28
- #trackedElements = new Set();
29
199
  #selection = '';
30
200
  get #internals() {
31
201
  return getInternals(this);
32
202
  }
33
203
  get template() {
34
- // const style = this.getAttribute((this.constructor as typeof PickerView).ATTR.PICKER_STYLE) ?? ''
35
204
  if (!_a.#templates.has(this.pickerStyle))
36
205
  switch (this.pickerStyle) {
37
- // case 'inline':
38
- // template.innerHTML = `
39
- // <list-view frame-width="infinity" part="root inline-form">
40
- // <slot></slot>
41
- // </list-view>
42
- // `
43
- // break
206
+ case 'sheet':
207
+ case 'navigation-link':
208
+ _a.#templates.set(this.pickerStyle, $(String.raw `
209
+ <label part="root picker-stack">
210
+ <div part="root picker-label-stack">
211
+ <slot name="label"></slot>
212
+ </div>
213
+ <div part="root picker-input-stack">
214
+ <slot></slot>
215
+ </div>
216
+ <slot name="list" hidden></slot>
217
+ <slot name="validity-options" hidden></slot>
218
+ </label>
219
+ `));
220
+ break;
44
221
  case 'menu':
45
- // template.innerHTML = `<slot name="options"></slot><slot></slot>`
46
222
  _a.#templates.set(this.pickerStyle, $(String.raw `
47
223
  <label part="root picker-stack">
48
224
  <div part="root picker-label-stack">
@@ -51,8 +227,7 @@ export class PickerView extends FormAssociatedBase {
51
227
  <div part="root picker-input-stack">
52
228
  <slot></slot>
53
229
  </div>
54
- <slot name="options" hidden></slot>
55
- <slot name="tag" hidden></slot>
230
+ <slot name="list" hidden></slot>
56
231
  <slot name="validity-options" hidden></slot>
57
232
  </label>
58
233
  `));
@@ -97,7 +272,7 @@ export class PickerView extends FormAssociatedBase {
97
272
  // <option value="0" label="0%"></option>
98
273
  // </datalist>
99
274
  // </div>
100
- // <slot name="options" hidden></slot>
275
+ // <slot name="list" hidden></slot>
101
276
  // </label>`,
102
277
  // })
103
278
  // )
@@ -112,8 +287,7 @@ export class PickerView extends FormAssociatedBase {
112
287
  <div part="root picker-input-stack">
113
288
  <slot></slot>
114
289
  </div>
115
- <slot name="options" hidden></slot>
116
- <slot name="tag" hidden></slot>
290
+ <slot name="list" hidden></slot>
117
291
  <slot name="validity-options" hidden></slot>
118
292
  </label>`));
119
293
  break;
@@ -123,10 +297,12 @@ export class PickerView extends FormAssociatedBase {
123
297
  constructor() {
124
298
  super();
125
299
  this.#shadowRoot = this.attachShadow({ mode: 'closed' });
126
- // this.#render() // Snapshot.waitReady.then(this.#render.bind(this))
127
300
  }
128
301
  connectedCallback() {
129
- console.debug(`${_a.name} ⚡️ connect`);
302
+ debug(`${_a.name} ⚡️ connect`);
303
+ CleanupRegistry.register(this, onoff('localechange', () => {
304
+ this.#renderList([]);
305
+ }, I18n.on).on());
130
306
  CleanupRegistry.register(this, onoff('click', this.#handleClick, this).on());
131
307
  if (!this.hasAttribute(this.constructor.ATTR.PICKER_STYLE))
132
308
  this.#render(); // will be picked up by attr-change!
@@ -137,12 +313,13 @@ export class PickerView extends FormAssociatedBase {
137
313
  this.#sendValueToForm(false);
138
314
  }
139
315
  disconnectedCallback() {
140
- console.debug(`${_a.name} ⚡️ disconnect`);
316
+ debug(`${_a.name} ⚡️ disconnect`);
141
317
  CleanupRegistry.unregister(this);
142
- observers.clearObservationsSet(this.#trackedElements);
318
+ this.#validityObservers.unobserveAll();
319
+ this.#observers.unobserveAll();
143
320
  }
144
321
  attributeChangedCallback(name, oldValue, newValue) {
145
- console.debug(`${_a.name} ⚡️ attr-change [${name}] ("${oldValue}" → "${newValue}")`);
322
+ debug(`${_a.name} ⚡️ attr-change [${name}] ("${oldValue}" → "${newValue}")`);
146
323
  switch (name) {
147
324
  case this.constructor.ATTR.PLACEHOLDER:
148
325
  this.#reflectPlaceholder(newValue);
@@ -155,7 +332,11 @@ export class PickerView extends FormAssociatedBase {
155
332
  if (oldValue === newValue)
156
333
  break;
157
334
  this.#render();
158
- // this.#sendValueToForm()
335
+ break;
336
+ case this.constructor.ATTR.SEARCHABLE:
337
+ if (oldValue === newValue)
338
+ break;
339
+ this.#renderList([]);
159
340
  break;
160
341
  case this.constructor.ATTR.SELECTION:
161
342
  // nothing happens
@@ -168,7 +349,7 @@ export class PickerView extends FormAssociatedBase {
168
349
  : 'automatic';
169
350
  }
170
351
  #render() {
171
- console.debug(`${_a.name} ⚡️ render (${this.pickerStyle})`);
352
+ debug(`${_a.name} ⚡️ render (${this.pickerStyle})`);
172
353
  if (!this.isConnected)
173
354
  return;
174
355
  // const style = this.getAttribute((this.constructor as typeof PickerView).ATTR.PICKER_STYLE)
@@ -184,39 +365,11 @@ export class PickerView extends FormAssociatedBase {
184
365
  this.#slots = new Map();
185
366
  }, 'slots');
186
367
  CleanupRegistry.unregister(this, 'validities');
187
- CleanupRegistry.register(this, onoff(makeSlotchangeHandler(this), this.#slots?.get('validity-options')).on(), 'validities');
368
+ CleanupRegistry.register(this, onoff('slotchange', this.#handleValiditiesSlotchange, this.#slots?.get('validity-options')).on(), 'validities');
188
369
  CleanupRegistry.unregister(this, 'datalist'); //off1()
189
- CleanupRegistry.register(this, onoff('slotchange', this.#handleSlotchange, this.#slots?.get('options')).on(), 'datalist');
190
- CleanupRegistry.unregister(this, 'tags'); //off2()
191
- CleanupRegistry.register(this, onoff('slotchange', this.#handleSlotchange, this.#slots?.get('tag')).on(), 'tags');
192
- // if (0 < (this.#slots?.get('options')?.assignedElements({ flatten: true }) ?? []).length) this.#handleTagMutation()
193
- // if (0 < (this.#slots?.get('tag')?.assignedElements({ flatten: true }) ?? []).length) this.#handleTagMutation()
194
- //this.getAttribute((this.constructor as typeof PickerView).ATTR.PICKER_STYLE)) {
195
- switch (this.pickerStyle
196
- // case 'menu':
197
- // // if (0 < (this.#slots?.get('options')?.assignedElements({ flatten: true }) ?? []).length) this.#handleMenuDatalistMutation()
198
- // break
199
- // case 'gg':
200
- // // reattach input listener
201
- // const input = this.#shadowRoot.querySelector('input')
202
- // if (input) {
203
- // input.addEventListener('input', () => {
204
- // this.#internals!.setFormValue(input.value)
205
- // })
206
- // // restore placeholder if set
207
- // const placeholder = this.getAttribute((this.constructor as typeof PickerView).ATTR.PLACEHOLDER)
208
- // if (placeholder) input.setAttribute('placeholder', placeholder)
209
- // }
210
- // // restore label if set
211
- // const label = this.getAttribute((this.constructor as typeof PickerView).ATTR.LABEL)
212
- // if (label) this.#reflectLabel(label)
213
- // break
214
- // case 'inline':
215
- // default:
216
- // // if (0 < (this.#slots?.get('options')?.assignedElements({ flatten: true }) ?? []).length) this.#handleInlineDatalistMutation()
217
- // break
218
- ) {
219
- }
370
+ CleanupRegistry.register(this, onoff('slotchange', this.#handleListSlotchange, this.#slots?.get('list')).on(), 'datalist');
371
+ // switch (this.pickerStyle) {
372
+ // }
220
373
  }
221
374
  #sendValueToForm = (dispatchEvent = true) => {
222
375
  // input.value has already been updated/synced !!
@@ -237,116 +390,113 @@ export class PickerView extends FormAssociatedBase {
237
390
  if (dispatchEvent)
238
391
  this.dispatchEvent(new CustomEvent('selection', { detail: { selection: this.#selection }, bubbles: true, composed: true }));
239
392
  };
240
- #handleClick(evt) {
241
- console.debug(`${_a.name} ⚡️ ${evt?.type}`);
242
- const target = evt.target instanceof HTMLElement && evt.target;
243
- if (!target)
393
+ #handleTriggerClick = async ({ type, target }) => {
394
+ debug(`${_a.name} ⚡️ ${type}`);
395
+ if (!(target instanceof HTMLElement && target))
396
+ return;
397
+ this.#spawn?.remove?.();
398
+ const level0 = this.#renderPage('sheet' === this.pickerStyle ? 'sheet-view' : 'body-view', this.hasAttribute('searchable'), this.getAttribute('label'));
399
+ if (!level0)
244
400
  return;
245
- const btn = target?.closest('button');
401
+ const path = new NavigationPath(target)?.hydrate();
402
+ await startViewTransition(target, 'forwards', async () => {
403
+ this.#spawn = level0;
404
+ update(path, level0);
405
+ });
406
+ };
407
+ #handlePageClick = async (evt) => {
408
+ debug(`${_a.name} ⚡️ ${evt?.type}`);
409
+ evt.stopImmediatePropagation();
410
+ evt.preventDefault();
411
+ const { target } = evt;
412
+ if (!(target instanceof HTMLElement && target))
413
+ return;
414
+ const btn = target.closest('button');
246
415
  if (!btn)
247
416
  return;
248
- if (btn.hasAttribute('tag')) {
249
- this.#selection = btn.getAttribute('tag') ?? ''; //return this.dispatchEvent(new CustomEvent('selection', { detail: { tag: btn.getAttribute('tag') }, bubbles: true, composed: true }))
250
- }
251
- else {
252
- this.#selection = btn.textContent; //this.dispatchEvent(new CustomEvent('selection', { detail: { tag: btn.textContent }, bubbles: true, composed: true }))
253
- }
417
+ const proceed = () => {
418
+ this.#spawn?.remove();
419
+ this.#selection = btn.getAttribute('value') ?? btn.textContent?.trim() ?? '';
420
+ this.#sendValueToForm();
421
+ };
422
+ const { body } = new NavigationPath(this.#spawn)?.hydrate();
423
+ if (!body)
424
+ return proceed();
425
+ await startViewTransition(body, 'backwards', async () => {
426
+ proceed();
427
+ });
428
+ };
429
+ #handleClick({ type, target }) {
430
+ debug(`${_a.name} ⚡️ ${type}`);
431
+ if (!(target instanceof HTMLElement && target))
432
+ return;
433
+ const btn = target.closest('button');
434
+ if (!btn)
435
+ return;
436
+ this.#selection = btn.getAttribute('value') ?? btn.textContent?.trim() ?? '';
254
437
  this.#sendValueToForm();
255
438
  }
256
- #handleSlotchange = (evt) => {
257
- console.debug(`${_a.name} ⚡️ ${evt?.type}`);
258
- const slot = evt.target instanceof HTMLSlotElement && evt.target;
259
- if (!slot)
439
+ #handleValiditiesSlotchange = ({ type, target: slot }) => {
440
+ debug(`${_a.name} ⚡️ ${type}`);
441
+ if (!(slot instanceof HTMLSlotElement && slot))
260
442
  return;
261
- const assigned = slot.assignedElements({ flatten: true });
262
- observers.syncObservations(this.#trackedElements, assigned, this.#handleTagMutation);
263
- // for (const el of this.#trackedElements)
264
- // if (!assigned.includes(el)) {
265
- // observers.unobserve(el)
266
- // this.#trackedElements.delete(el)
267
- // }
268
- // for (const el of assigned) {
269
- // if (!this.#trackedElements.has(el))
270
- // observers.observe(el, this.#handleTagMutation, {
271
- // attributes: true,
272
- // characterData: true,
273
- // subtree: true,
274
- // childList: true,
275
- // // attributeFilter: ['value', 'label'],
276
- // })
277
- // this.#trackedElements.add(el)
278
- // }
443
+ const assigned = slot.assignedElements();
444
+ this.#validityObservers.syncObservations(assigned, ['value', 'label']);
279
445
  if (0 < assigned.length)
280
- this.#handleTagMutation();
446
+ this.#renderValidityMsgs([]);
281
447
  };
282
- static wrapTag(node, slotName) {
283
- const btn = $(`<button type="button" tabindex="0"></button>`, '>1'); //document.createElement('button')
284
- // btn.type = 'button'
285
- // btn.tabIndex = 0
286
- switch (slotName) {
287
- case 'tag':
288
- if (node.hasAttribute('tag'))
289
- btn.setAttribute('tag', node.getAttribute('tag') ?? '');
290
- btn.appendChild(node.cloneNode(true));
291
- break;
292
- case 'options':
293
- default:
294
- if (node.hasAttribute('value'))
295
- btn.setAttribute('tag', node.getAttribute('value') ?? '');
296
- const label = $(`<label-view></label-view>`, '>1'); //document.createElement('label-view')
297
- if (node.hasAttribute('label'))
298
- label.setAttribute('title', node.getAttribute('label') ?? '');
299
- btn.appendChild(label);
300
- break;
301
- }
448
+ #handleListSlotchange = ({ type, target: slot }) => {
449
+ debug(`${_a.name} ⚡️ ${type}`);
450
+ if (!(slot instanceof HTMLSlotElement && slot))
451
+ return;
452
+ const assigned = slot.assignedElements();
453
+ this.#observers.syncObservations(assigned);
454
+ if (0 < assigned.length)
455
+ this.#renderList([]);
456
+ };
457
+ static #wrapOptionTag(node) {
458
+ const btn = $(`<button type="button" tabindex="0"></button>`, '>1');
459
+ btn.setAttribute('value', node.getAttribute('value') ?? node.textContent?.trim() ?? '');
460
+ const label = $(`<label-view></label-view>`, '>1');
461
+ label.setAttribute('title', node.getAttribute('label') ?? node.getAttribute('value') ?? node.textContent?.trim() ?? '');
462
+ btn.appendChild(label);
302
463
  return btn;
303
464
  }
304
- #handleTagMutation = (entry) => {
305
- console.debug(`${_a.name} ⚡️ mutation`);
306
- const sourceSlot = 0 < (this.#slots?.get('options')?.assignedElements({ flatten: true }) ?? []).length ? this.#slots?.get('options') : this.#slots?.get('tag');
307
- // switch (this.getAttribute((this.constructor as typeof PickerView).ATTR.PICKER_STYLE)) {
308
- switch (this.pickerStyle) {
309
- case 'menu':
310
- const menu = this.querySelector(':scope>menu-view:not([slot])') ?? this.appendChild($(`<menu-view tabindex="0"></menu-view>`, '>1'));
311
- menu.innerHTML = `<label-view slot="label" system-image="dots-three" title="rtyty"></label-view>`;
312
- for (const el of sourceSlot?.assignedElements({ flatten: true }) ?? [])
313
- menu.insertAdjacentElement('beforeend', _a.wrapTag(el, sourceSlot?.name));
314
- // let possibleMv = this.#slot?.assignedElements({ flatten: true })[0]
315
- // if ('MENU-VIEW' !== possibleMv?.tagName) possibleMv = this.appendChild(document.createElement('menu-view'))
316
- // possibleMv.innerHTML = `<label-view slot="label" system-image="dots-three" title="rtyty"></label-view>`
317
- // for (const el of PickerView.sourceNodes(sourceSlot) ?? []) possibleMv.insertAdjacentElement('beforeend', PickerView.wrapTag(el, sourceSlot?.name))
318
- break;
319
- case 'inline':
320
- default:
321
- const inlineList = this.querySelector(':scope>list-view:not([slot])') ?? this.appendChild($(`<list-view><section-view></section-view></list-view>`, '>1')), section = inlineList.querySelector(':scope>section-view') ?? inlineList.appendChild($(`<section-view></section-view>`, '>1'));
322
- const label = this.getAttribute(this.constructor.ATTR.LABEL);
323
- // if (label) section.setAttribute('header', label)
324
- if (label) {
325
- const el = $(`<label-view></label-view>`, '>1');
326
- el.setAttribute('title', label);
327
- section.insertAdjacentElement('beforeend', el);
465
+ static #wrapOptgroupTag(node) {
466
+ const labelT = `<label-view></label-view>`, summaryT = `<summary>${labelT}</summary>`;
467
+ const group = $(`<details is="disclosure-group">${summaryT}</details>`, '>1'), summary = group.querySelector(':scope>summary') ?? group.appendChild($(summaryT, '>1')), summaryLabel = summary.querySelector(':scope>label-view') ?? summary.appendChild($(labelT, '>1'));
468
+ if (node.hasAttribute('label'))
469
+ summaryLabel.setAttribute('title', node.getAttribute('label') ?? '');
470
+ if (node.hasAttribute('data-system-image'))
471
+ summaryLabel.setAttribute('system-image', node.getAttribute('data-system-image') ?? '');
472
+ return group;
473
+ }
474
+ static #reflectButtons(nodes, container) {
475
+ for (const node of nodes)
476
+ switch (node.tagName) {
477
+ case 'DATALIST': {
478
+ const group = $(`<menu-view tabindex="0"></menu-view>`, '>1'), label = group.querySelector(':scope>label-view[slot=label]') ?? group.appendChild($(`<label-view slot="label"></label-view>`, '>1'));
479
+ if (node.hasAttribute('data-label'))
480
+ label.setAttribute('title', node.getAttribute('data-label') ?? '');
481
+ if (node.hasAttribute('data-system-image'))
482
+ label.setAttribute('system-image', node.getAttribute('data-system-image') ?? '');
483
+ _a.#reflectButtons([...node.children], group);
484
+ container.appendChild(group);
485
+ break;
328
486
  }
329
- else
330
- section.innerHTML = '';
331
- for (const el of sourceSlot?.assignedElements({ flatten: true }) ?? [])
332
- section.insertAdjacentElement('beforeend', _a.wrapTag(el, sourceSlot?.name));
333
- // let possibleLv = this.#slot?.assignedElements({ flatten: true })[0]
334
- // if ('LIST-VIEW' !== possibleLv?.tagName) possibleLv = this.appendChild(document.createElement('list-view'))
335
- // const sv = document.createElement('section-view')
336
- // sv.setAttribute('header', 'section-header')
337
- // possibleLv.appendChild(sv)
338
- // for (const el of PickerView.sourceNodes(sourceSlot) ?? []) sv.insertAdjacentElement('beforeend', PickerView.wrapTag(el, sourceSlot?.name))
339
- // for (const el of this.querySelectorAll(':scope>:not([slot])')) el.remove()
340
- // for (const el of PickerView.sourceNodes(sourceSlot) ?? []) {
341
- // const btn = document.createElement('button')
342
- // btn.type = 'button'
343
- // btn.tabIndex = 0
344
- // btn.appendChild(PickerView.wrapTag(el, sourceSlot?.name))
345
- // this.insertAdjacentElement('beforeend', btn)
346
- // }
347
- break;
348
- }
349
- };
487
+ case 'OPTGROUP': {
488
+ const group = _a.#wrapOptgroupTag(node);
489
+ _a.#reflectButtons([...node.children], group);
490
+ container.appendChild(group);
491
+ break;
492
+ }
493
+ case 'OPTION':
494
+ default: {
495
+ container.appendChild(_a.#wrapOptionTag(node));
496
+ break;
497
+ }
498
+ }
499
+ }
350
500
  #reflectPlaceholder(value) {
351
501
  const input = this.#shadowRoot.querySelector('input');
352
502
  if (input) {
@@ -357,17 +507,6 @@ export class PickerView extends FormAssociatedBase {
357
507
  }
358
508
  }
359
509
  #reflectLabel(value) {
360
- // switch (this.getAttribute('picker-style')) {
361
- // case 'menu':
362
- // this.#slot!.assignedElements({ flatten: true })[0].setAttribute('label', value ?? '')
363
- // // if (!mv) {
364
- // // mv = document.createElement('label-view')
365
- // // mv.slot = 'label'
366
- // // this.append(mv)
367
- // // }
368
- // // mv.textContent = newValue //el2.replaceChildren(escapeHTMLPolicy.createHTML(newValue))
369
- // break
370
- // default:
371
510
  let label = this.querySelector(':scope>[slot=label]');
372
511
  if (value) {
373
512
  label ??= this.appendChild($(`<span slot="label"></span>`, '>1'));
@@ -375,25 +514,7 @@ export class PickerView extends FormAssociatedBase {
375
514
  }
376
515
  else
377
516
  label?.remove();
378
- this.#handleTagMutation();
379
- // const el =
380
- // (this.#labelSlot?.assignedElements({ flatten: true })[0] as HTMLElement) ??
381
- // (() => {
382
- // const el = document.createElement('span')
383
- // el.slot = 'label'
384
- // return this.appendChild(el)
385
- // })()
386
- // el.textContent = value ?? '' // el.replaceChildren(escapeHTMLPolicy.createHTML(newValue))
387
- // break
388
- // }
389
- // const assigned = this.#labelSlot?.assignedElements({ flatten: true }) as HTMLElement[] | undefined
390
- // let el = assigned?.[0]
391
- // if (!el) {
392
- // el = document.createElement('span')
393
- // el.slot = 'label'
394
- // this.append(el)
395
- // }
396
- // el.textContent = value ?? ''
517
+ this.#renderList([]);
397
518
  }
398
519
  // Optional: form participation properties
399
520
  get name() {
@@ -428,7 +549,7 @@ export class PickerView extends FormAssociatedBase {
428
549
  message = kebabCase(key);
429
550
  break;
430
551
  }
431
- console.debug(`${_a.name} ⚡️ validity-change`);
552
+ debug(`${_a.name} ⚡️ validity-change`);
432
553
  return this.#internals.setValidity(flags, this.#customValidity || message, anchor);
433
554
  };
434
555
  setCustomValidity = (message) => {