@tillsc/progressive-web-components 0.1.0

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.
package/dist/all.js ADDED
@@ -0,0 +1,763 @@
1
+ // src/core/utils.js
2
+ function defineOnce(name, classDef) {
3
+ if (customElements.get(name)) return;
4
+ customElements.define(name, classDef);
5
+ }
6
+
7
+ // src/core/pwc-element.js
8
+ var PwcElement = class extends HTMLElement {
9
+ /**
10
+ * List of DOM event types to bind on the host element.
11
+ * Subclasses may override.
12
+ *
13
+ * Example:
14
+ * static events = ["click", "input"];
15
+ */
16
+ static events = [];
17
+ static registerCss(cssText) {
18
+ const sheet = new CSSStyleSheet();
19
+ sheet.replaceSync(cssText);
20
+ document.adoptedStyleSheets = [...document.adoptedStyleSheets, sheet];
21
+ }
22
+ connectedCallback() {
23
+ if (this._connected) return;
24
+ this._connected = true;
25
+ this._bindEvents();
26
+ }
27
+ disconnectedCallback() {
28
+ if (!this._connected) return;
29
+ this._connected = false;
30
+ this._unbindEvents();
31
+ this.onDisconnect();
32
+ }
33
+ /**
34
+ * Optional cleanup hook for subclasses.
35
+ */
36
+ onDisconnect() {
37
+ }
38
+ /**
39
+ * Bind declared events using the handleEvent pattern.
40
+ */
41
+ _bindEvents() {
42
+ const events = this.constructor.events ?? [];
43
+ for (const type of events) {
44
+ this.addEventListener(type, this);
45
+ }
46
+ }
47
+ /**
48
+ * Unbind all previously declared events.
49
+ */
50
+ _unbindEvents() {
51
+ const events = this.constructor.events ?? [];
52
+ for (const type of events) {
53
+ this.removeEventListener(type, this);
54
+ }
55
+ }
56
+ /**
57
+ * Default event handler.
58
+ * Subclasses are expected to override this method
59
+ * and route events as needed.
60
+ */
61
+ handleEvent(_event) {
62
+ }
63
+ };
64
+
65
+ // src/dialog-opener/base.js
66
+ var BaseDialogOpener = class extends PwcElement {
67
+ static events = ["click"];
68
+ handleEvent(e) {
69
+ if (e.type !== "click") return;
70
+ if (e.defaultPrevented) return;
71
+ const link = e.target.closest("a");
72
+ if (!link || !this.contains(link)) return;
73
+ e.preventDefault();
74
+ if (this.hasAttribute("local-reload") && !this.id) {
75
+ console.warn("<pwc-dialog-opener> has local-reload attribute but no id", this);
76
+ }
77
+ const href = link.getAttribute("href");
78
+ if (!href) return;
79
+ this.open(href);
80
+ }
81
+ open(href) {
82
+ const src = this.prepareIFrameLink(href);
83
+ this.findOrCreateDialog(src);
84
+ this.enhanceIFrame().then(() => this.modal.show());
85
+ }
86
+ prepareIFrameLink(src) {
87
+ const s = new URL(src, document.location.href);
88
+ const defaultValues = [...this.querySelectorAll("input")].map((input) => {
89
+ if (input.value) return input.value;
90
+ return null;
91
+ }).filter((item) => item !== null);
92
+ if (defaultValues.length > 0) {
93
+ s.searchParams.set("default", defaultValues.join(","));
94
+ }
95
+ s.searchParams.set("_layout", false);
96
+ return s.toString();
97
+ }
98
+ // Variant hook: must set this.dialog and this.modal
99
+ // eslint-disable-next-line no-unused-vars
100
+ findOrCreateDialog(_src) {
101
+ throw new Error("BaseDialogOpener: findOrCreateDialog(src) must be implemented by a variant");
102
+ }
103
+ createIFrame(src) {
104
+ const iframe = document.createElement("iframe");
105
+ iframe.src = src;
106
+ iframe.style.width = "100%";
107
+ iframe.style.height = getComputedStyle(this).getPropertyValue("--pwc-dialog-opener-height").trim() || "550px";
108
+ iframe.style.display = "none";
109
+ return iframe;
110
+ }
111
+ enhanceIFrame() {
112
+ this.iframe = this.dialog.querySelector("iframe");
113
+ return new Promise((resolve) => {
114
+ this.iframe.addEventListener(
115
+ "load",
116
+ (e) => this.iFrameLoad(e).then(resolve)
117
+ );
118
+ });
119
+ }
120
+ async iFrameLoad(_e) {
121
+ let uri;
122
+ try {
123
+ uri = new URL(this.iframe.contentWindow.location);
124
+ } catch (e) {
125
+ throw new Error(`<pwc-dialog-opener> cannot access iframe location (cross-origin?): ${e.message}`);
126
+ }
127
+ if (uri.searchParams.has("dialog_finished_with")) {
128
+ this.modal.hide();
129
+ uri.searchParams.delete("_layout");
130
+ uri.searchParams.set("dummy", Math.floor(Math.random() * 1e5));
131
+ const localReloadWorked = await this.tryLocalReload(uri);
132
+ if (!localReloadWorked) {
133
+ window.location.href = uri.toString();
134
+ }
135
+ return;
136
+ }
137
+ this.moveElementsToOuterActions();
138
+ this.iframe.style.display = "unset";
139
+ }
140
+ async tryLocalReload(newUri) {
141
+ const currentUri = new URL(window.location.href);
142
+ if (currentUri.hostname !== newUri.hostname || currentUri.pathname !== newUri.pathname || currentUri.protocol !== newUri.protocol) {
143
+ console.log(`<dialog-opener> Warning: local-reload got different base uri (${newUri.toString()}) then window has (${currentUri.toString()}). This might lead to problems, but we'll try it anyway.`);
144
+ }
145
+ if (this.hasAttribute("local-reload") && this.id) {
146
+ const localReloadOptionTokens = document.createElement("div").classList;
147
+ if (this.hasAttribute("local-reload")) localReloadOptionTokens.add(...this.getAttribute("local-reload").split(/\s+/));
148
+ const localReloadOptions = {
149
+ replaceUrl: localReloadOptionTokens.contains("replace-url"),
150
+ pushUrl: localReloadOptionTokens.contains("push-url"),
151
+ withScripts: localReloadOptionTokens.contains("with-scripts")
152
+ };
153
+ newUri.searchParams.set("local_reload", this.id);
154
+ const res = await fetch(newUri);
155
+ if (res.ok) {
156
+ const html = await res.text();
157
+ const newDocument = new DOMParser().parseFromString(html, "text/html");
158
+ const fragment = newDocument.getElementById(this.id);
159
+ if (fragment) {
160
+ this.replaceChildren(...fragment.childNodes);
161
+ if (localReloadOptions.replaceUrl || localReloadOptions.pushUrl) {
162
+ if (localReloadOptions.pushUrl) {
163
+ history.pushState(null, "", newUri);
164
+ } else if (localReloadOptions.replaceUrl) {
165
+ history.replaceState(null, "", newUri);
166
+ }
167
+ }
168
+ if (localReloadOptions.withScripts) {
169
+ this.executeInlineScripts(this);
170
+ }
171
+ this.dispatchEvent(
172
+ new CustomEvent("pwc-dialog-opener:local-reload", {
173
+ bubbles: true,
174
+ detail: { url: newUri.toString() }
175
+ })
176
+ );
177
+ return true;
178
+ }
179
+ console.log("local-reload not possible, falling back to full reload");
180
+ }
181
+ }
182
+ return false;
183
+ }
184
+ executeInlineScripts(root) {
185
+ console.log("Executing inline scripts in local-reload fragment", root);
186
+ const scripts = Array.from(root.querySelectorAll("script"));
187
+ for (const old of scripts) {
188
+ if (old.src) {
189
+ console.warn("Ignoring external script in local-reload fragment:", old.src);
190
+ old.remove();
191
+ continue;
192
+ }
193
+ const s = document.createElement("script");
194
+ if (old.type) s.type = old.type;
195
+ if (old.noModule) s.noModule = true;
196
+ s.textContent = old.textContent || "";
197
+ old.replaceWith(s);
198
+ }
199
+ }
200
+ moveElementsToOuterActions() {
201
+ if (!this.getAttribute("move-out")) return;
202
+ const iframeDoc = this.iframe.contentWindow.document;
203
+ if (!iframeDoc) return;
204
+ let buttonContainer = this.dialog.querySelector("dialog-opener-buttons");
205
+ if (!buttonContainer) {
206
+ buttonContainer = document.createElement("dialog-opener-buttons");
207
+ this.dialog.querySelector(".pwc-dialog-opener-actions").prepend(buttonContainer);
208
+ } else {
209
+ buttonContainer.innerHTML = "";
210
+ }
211
+ const elements = iframeDoc.querySelectorAll(this._moveOutSelector());
212
+ for (let i = 0; i < elements.length; i++) {
213
+ const btn = elements[i];
214
+ const outerBtn = document.createElement(btn.tagName);
215
+ for (const attr of btn.attributes) {
216
+ outerBtn.setAttribute(attr.name, attr.value);
217
+ }
218
+ outerBtn.innerHTML = btn.innerHTML;
219
+ outerBtn.addEventListener("click", () => {
220
+ this.iframe.style.display = "none";
221
+ btn.click();
222
+ });
223
+ buttonContainer.append(outerBtn);
224
+ btn.style.visibility = "hidden";
225
+ btn.style.display = "none";
226
+ }
227
+ }
228
+ _moveOutSelector() {
229
+ let selector = this.getAttribute("move-out");
230
+ if (selector === "submit") {
231
+ selector = "button[type=submit], input[type=submit]";
232
+ }
233
+ return selector;
234
+ }
235
+ };
236
+
237
+ // src/dialog-opener/dialog-opener.js
238
+ var PwcDialogOpener = class extends BaseDialogOpener {
239
+ findOrCreateDialog(src) {
240
+ if (!this.modalDialog) {
241
+ this.modalDialog = document.createElement("pwc-modal-dialog");
242
+ document.body.appendChild(this.modalDialog);
243
+ }
244
+ const closeText = this.getAttribute("close") || "Close";
245
+ this.modalDialog.open({
246
+ closeText,
247
+ showClose: false
248
+ });
249
+ this.modalDialog.footerEl.innerHTML = `
250
+ <div class="pwc-dialog-opener-actions pwc-dialog-opener-footer">
251
+ <button type="button" class="pwc-dialog-opener-close" data-pwc-action="close" aria-label="${closeText}">
252
+ ${closeText}
253
+ </button>
254
+ </div>
255
+ `;
256
+ const iframe = this.createIFrame(src);
257
+ this.modalDialog.bodyEl.replaceChildren(iframe);
258
+ this.dialog = this.modalDialog.ui.rootEl;
259
+ this.modal = {
260
+ show: () => {
261
+ },
262
+ // modal-dialog is already shown by open()
263
+ hide: () => this.modalDialog.close()
264
+ };
265
+ }
266
+ };
267
+ function define() {
268
+ defineOnce("pwc-dialog-opener", PwcDialogOpener);
269
+ }
270
+
271
+ // src/dialog-opener/dialog-opener.css
272
+ var dialog_opener_default = "/* Footer actions container (used by move-out) */\n.pwc-dialog-opener-footer {\n display: flex;\n justify-content: flex-end;\n gap: 8px;\n}\n\n/* Close button */\n.pwc-dialog-opener-close {\n appearance: none;\n border: 1px solid rgba(0, 0, 0, 0.25);\n background: transparent;\n font: inherit;\n padding: 6px 12px;\n border-radius: 4px;\n cursor: pointer;\n}\n\n.pwc-dialog-opener-close:hover {\n background: rgba(0, 0, 0, 0.06);\n}\n";
273
+
274
+ // src/core/pwc-simple-init-element.js
275
+ var PwcSimpleInitElement = class extends PwcElement {
276
+ connectedCallback() {
277
+ if (this._connected) return;
278
+ super.connectedCallback();
279
+ queueMicrotask(() => {
280
+ if (!this._connected) return;
281
+ this.onConnect();
282
+ });
283
+ }
284
+ /**
285
+ * Hook for subclasses.
286
+ * Called once per connection, after microtask deferral.
287
+ */
288
+ onConnect() {
289
+ }
290
+ };
291
+
292
+ // src/modal-dialog/base.js
293
+ var ModalDialogBase = class extends PwcSimpleInitElement {
294
+ static events = ["click"];
295
+ onDisconnect() {
296
+ this._teardown();
297
+ }
298
+ get ui() {
299
+ if (!this._ui) throw new Error("ui is only available after open()");
300
+ return this._ui;
301
+ }
302
+ get rootEl() {
303
+ return this.ui.rootEl;
304
+ }
305
+ get bodyEl() {
306
+ return this.ui.bodyEl;
307
+ }
308
+ get headerEl() {
309
+ return this.ui.headerEl;
310
+ }
311
+ get footerEl() {
312
+ return this.ui.footerEl;
313
+ }
314
+ isOpen() {
315
+ return false;
316
+ }
317
+ open({ title = "", size = "lg", closeText = "Close", ...options }) {
318
+ if (!this.isConnected) {
319
+ this._autoRemove = true;
320
+ document.body.appendChild(this);
321
+ }
322
+ this._teardown();
323
+ const ui = this._render({ title, size, closeText, ...options });
324
+ this._ui = ui;
325
+ const parent = this._getOpenSibling();
326
+ this._parent = parent && parent !== ui.rootEl ? parent : null;
327
+ this._closed = false;
328
+ this._armFinalClose(ui, () => this._onFinalClose());
329
+ if (this._parent) {
330
+ this._parent.dataset.closeReason = "suspend";
331
+ this._suspend(this._parent);
332
+ }
333
+ this._show(ui, { title, size, closeText, ...options });
334
+ }
335
+ close() {
336
+ if (this._closed) return;
337
+ this._closed = true;
338
+ this.dataset.closeReason = "final";
339
+ this._hide(this._ui);
340
+ }
341
+ _onFinalClose() {
342
+ this._closed = true;
343
+ delete this.dataset.closeReason;
344
+ const parent = this._parent;
345
+ this._parent = null;
346
+ this._teardown();
347
+ if (parent && parent.isConnected) {
348
+ delete parent.dataset.closeReason;
349
+ queueMicrotask(() => this._restore(parent));
350
+ }
351
+ if (this._autoRemove && this.isConnected) this.remove();
352
+ }
353
+ handleEvent(e) {
354
+ if (e.type !== "click") return;
355
+ if (e.defaultPrevented) return;
356
+ const ui = this._ui;
357
+ if (!ui?.rootEl) return;
358
+ if (e.target === ui.rootEl) {
359
+ this.close();
360
+ return;
361
+ }
362
+ const closeEl = e.target.closest('[data-pwc-action="close"]');
363
+ if (!closeEl || !this.contains(closeEl)) return;
364
+ this.close();
365
+ }
366
+ _teardown() {
367
+ const ui = this._ui;
368
+ this._ui = null;
369
+ ui?.teardown?.();
370
+ }
371
+ };
372
+
373
+ // src/modal-dialog/modal-dialog.js
374
+ var PwcModalDialog = class extends ModalDialogBase {
375
+ isOpen() {
376
+ return Boolean(this._ui?.rootEl?.open);
377
+ }
378
+ _render({ title, size, closeText, showClose = true }) {
379
+ const dlg = document.createElement("dialog");
380
+ dlg.className = `pwc-modal-dialog pwc-modal-dialog--${size}`;
381
+ dlg.innerHTML = `
382
+ <div class="pwc-modal-dialog-surface" role="document">
383
+ <header class="pwc-modal-dialog-header">
384
+ <h3 class="pwc-modal-dialog-title"></h3>
385
+ </header>
386
+ <section class="pwc-modal-dialog-body"></section>
387
+ <footer class="pwc-modal-dialog-footer"></footer>
388
+ </div>
389
+ `;
390
+ dlg.querySelector(".pwc-modal-dialog-title").textContent = title;
391
+ if (showClose) {
392
+ const btn = document.createElement("button");
393
+ btn.type = "button";
394
+ btn.className = "pwc-modal-dialog-x";
395
+ btn.setAttribute("aria-label", closeText);
396
+ btn.setAttribute("data-pwc-action", "close");
397
+ btn.textContent = "\xD7";
398
+ dlg.querySelector(".pwc-modal-dialog-header").appendChild(btn);
399
+ }
400
+ this.replaceChildren(dlg);
401
+ return {
402
+ rootEl: dlg,
403
+ bodyEl: dlg.querySelector(".pwc-modal-dialog-body"),
404
+ headerEl: dlg.querySelector(".pwc-modal-dialog-header"),
405
+ footerEl: dlg.querySelector(".pwc-modal-dialog-footer"),
406
+ teardown: () => {
407
+ if (dlg.open) dlg.close();
408
+ dlg.remove();
409
+ }
410
+ };
411
+ }
412
+ _getOpenSibling() {
413
+ const candidates = Array.from(document.querySelectorAll("pwc-modal-dialog"));
414
+ return candidates.find((el) => el !== this && el._ui?.rootEl?.open === true) || null;
415
+ }
416
+ _suspend(hostEl) {
417
+ if (hostEl.isOpen()) hostEl.rootEl.close();
418
+ }
419
+ _restore(hostEl) {
420
+ const dlg = hostEl.rootEl;
421
+ if (dlg && typeof dlg.showModal === "function" && !dlg.open) dlg.showModal();
422
+ }
423
+ _show(ui) {
424
+ const dlg = ui.rootEl;
425
+ if (typeof dlg?.showModal !== "function") throw new Error("<dialog> not supported");
426
+ if (!dlg.open) dlg.showModal();
427
+ }
428
+ _hide(ui) {
429
+ const dlg = ui?.rootEl;
430
+ if (dlg?.open) dlg.close();
431
+ }
432
+ _armFinalClose(ui, onFinalClose) {
433
+ const dlg = ui?.rootEl;
434
+ if (!dlg) return;
435
+ const onClose = () => {
436
+ if (this.dataset.closeReason === "suspend") return;
437
+ onFinalClose();
438
+ };
439
+ dlg.addEventListener("close", onClose);
440
+ const prevTeardown = ui.teardown;
441
+ ui.teardown = () => {
442
+ dlg.removeEventListener("close", onClose);
443
+ if (prevTeardown) prevTeardown();
444
+ };
445
+ }
446
+ };
447
+ var define2 = () => defineOnce("pwc-modal-dialog", PwcModalDialog);
448
+
449
+ // src/modal-dialog/modal-dialog.css
450
+ var modal_dialog_default = "pwc-modal-dialog {\n /* sizing */\n --pwc-modal-max-width: 720px;\n --pwc-modal-width: 92vw;\n\n /* spacing */\n --pwc-modal-padding-header: 12px 16px;\n --pwc-modal-padding-body: 16px;\n --pwc-modal-padding-footer: 12px 16px;\n --pwc-modal-gap-footer: 8px;\n\n /* visuals */\n --pwc-modal-bg: #fff;\n --pwc-modal-backdrop: rgba(0, 0, 0, 0.45);\n --pwc-modal-border-radius: 6px;\n --pwc-modal-shadow: 0 12px 40px rgba(0, 0, 0, 0.25);\n --pwc-modal-separator: rgba(0, 0, 0, 0.08);\n\n /* controls */\n --pwc-modal-close-radius: 4px;\n --pwc-modal-close-hover-bg: rgba(0, 0, 0, 0.06);\n}\n\npwc-modal-dialog dialog.pwc-modal-dialog {\n border: none;\n padding: 0;\n max-width: min(var(--pwc-modal-max-width), var(--pwc-modal-width));\n width: var(--pwc-modal-width);\n}\n\npwc-modal-dialog dialog.pwc-modal-dialog::backdrop {\n background: var(--pwc-modal-backdrop);\n}\n\npwc-modal-dialog .pwc-modal-dialog-surface {\n background: var(--pwc-modal-bg);\n border-radius: var(--pwc-modal-border-radius);\n box-shadow: var(--pwc-modal-shadow);\n overflow: hidden;\n}\n\n/* Header */\n\npwc-modal-dialog .pwc-modal-dialog-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: var(--pwc-modal-padding-header);\n border-bottom: 1px solid var(--pwc-modal-separator);\n}\n\npwc-modal-dialog .pwc-modal-dialog-title {\n margin: 0;\n font-size: 1.1rem;\n font-weight: 600;\n}\n\n/* Close button */\n\npwc-modal-dialog .pwc-modal-dialog-x {\n appearance: none;\n border: none;\n background: transparent;\n font: inherit;\n font-size: 1.25rem;\n line-height: 1;\n padding: 4px 6px;\n cursor: pointer;\n border-radius: var(--pwc-modal-close-radius);\n}\n\npwc-modal-dialog .pwc-modal-dialog-x:hover {\n background: var(--pwc-modal-close-hover-bg);\n}\n\n/* Body */\n\npwc-modal-dialog .pwc-modal-dialog-body {\n padding: var(--pwc-modal-padding-body);\n}\n\n/* Footer */\n\npwc-modal-dialog .pwc-modal-dialog-footer {\n display: flex;\n justify-content: flex-end;\n gap: var(--pwc-modal-gap-footer);\n padding: var(--pwc-modal-padding-footer);\n border-top: 1px solid var(--pwc-modal-separator);\n}\n\npwc-modal-dialog .pwc-modal-dialog-close {\n appearance: none;\n border: 1px solid rgba(0, 0, 0, 0.25);\n background: transparent;\n padding: 6px 12px;\n border-radius: var(--pwc-modal-close-radius);\n cursor: pointer;\n}\n\npwc-modal-dialog .pwc-modal-dialog-close:hover {\n background: var(--pwc-modal-close-hover-bg);\n}";
451
+
452
+ // src/modal-dialog/index.js
453
+ function register() {
454
+ PwcModalDialog.registerCss(modal_dialog_default);
455
+ define2();
456
+ }
457
+ register();
458
+
459
+ // src/dialog-opener/index.js
460
+ function register2() {
461
+ PwcDialogOpener.registerCss(dialog_opener_default);
462
+ define();
463
+ }
464
+ register2();
465
+
466
+ // src/core/pwc-children-observer-element.js
467
+ var PwcChildrenObserverElement = class extends PwcElement {
468
+ static observeMode = "children";
469
+ // "children" | "tree"
470
+ connectedCallback() {
471
+ if (this._connected) return;
472
+ super.connectedCallback();
473
+ this._startChildrenObserver();
474
+ }
475
+ disconnectedCallback() {
476
+ this._stopChildrenObserver();
477
+ super.disconnectedCallback();
478
+ }
479
+ onChildrenChanged(_mutations) {
480
+ }
481
+ /** Run fn() without triggering onChildrenChanged for the resulting DOM mutations. */
482
+ _withoutChildrenChangedNotification(fn) {
483
+ fn();
484
+ this._childrenObserver?.takeRecords();
485
+ }
486
+ _startChildrenObserver() {
487
+ const mode = this.constructor.observeMode || "children";
488
+ const subtree = mode === "tree";
489
+ this._childrenObserver = new MutationObserver((mutations) => {
490
+ if (!this._connected) return;
491
+ this.onChildrenChanged(mutations);
492
+ });
493
+ this._childrenObserver.observe(this, { childList: true, subtree });
494
+ this.onChildrenChanged([]);
495
+ }
496
+ _stopChildrenObserver() {
497
+ if (!this._childrenObserver) return;
498
+ this._childrenObserver.disconnect();
499
+ this._childrenObserver = null;
500
+ }
501
+ };
502
+
503
+ // src/multiselect-dual-list/base.js
504
+ var MultiselectDualListBase = class extends PwcChildrenObserverElement {
505
+ static observeMode = "tree";
506
+ static events = ["click", "input"];
507
+ get _selectedClass() {
508
+ return "pwc-msdl-item--selected";
509
+ }
510
+ onChildrenChanged() {
511
+ const select = this.querySelector("select");
512
+ if (!select) return;
513
+ this._select = select;
514
+ const items = this._parseOptions(select);
515
+ this._items = items;
516
+ this._itemsByValue = new Map(items.map((item) => [item.value, item]));
517
+ this._withoutChildrenChangedNotification(() => {
518
+ if (!this._availableList) {
519
+ const ui = this._buildUI();
520
+ this._availableList = ui.availableList;
521
+ this._selectedList = ui.selectedList;
522
+ this._filterInput = ui.filterInput;
523
+ }
524
+ this._populateLists(items);
525
+ select.style.display = "none";
526
+ const filterText = this._filterInput?.value;
527
+ if (filterText) this._applyFilter(filterText);
528
+ });
529
+ }
530
+ _populateLists(items) {
531
+ this._availableList.replaceChildren();
532
+ this._selectedList.replaceChildren();
533
+ for (const item of items) {
534
+ this._availableList.appendChild(this._createAvailableEntry(item));
535
+ }
536
+ for (const item of items) {
537
+ if (item.selected) {
538
+ this._selectedList.appendChild(this._createSelectedEntry(item));
539
+ }
540
+ }
541
+ }
542
+ _parseOptions(select) {
543
+ const options = Array.from(select.options);
544
+ const parentMap = /* @__PURE__ */ new Map();
545
+ for (const opt of options) {
546
+ const parent = opt.dataset.parent;
547
+ if (parent) parentMap.set(opt.value, parent);
548
+ }
549
+ return options.map((opt) => ({
550
+ value: opt.value,
551
+ label: opt.textContent,
552
+ parent: opt.dataset.parent || null,
553
+ depth: this._calculateDepth(opt.value, parentMap),
554
+ selected: opt.selected,
555
+ disabled: opt.disabled,
556
+ warnOnUnselect: opt.dataset.warnOnUnselect || null
557
+ }));
558
+ }
559
+ _calculateDepth(value, parentMap) {
560
+ let depth = 0;
561
+ let current = value;
562
+ const visited = /* @__PURE__ */ new Set();
563
+ while (parentMap.has(current)) {
564
+ if (visited.has(current)) break;
565
+ visited.add(current);
566
+ current = parentMap.get(current);
567
+ depth++;
568
+ }
569
+ return depth;
570
+ }
571
+ handleEvent(e) {
572
+ if (e.type === "click") {
573
+ const actionEl = e.target.closest("[data-action]");
574
+ if (!actionEl || !this.contains(actionEl)) return;
575
+ const action = actionEl.dataset.action;
576
+ const value = actionEl.closest("[data-value]")?.dataset.value;
577
+ if (!value) return;
578
+ if (action === "add") this._addItem(value);
579
+ else if (action === "remove") this._removeItem(value);
580
+ return;
581
+ }
582
+ if (e.type === "input") {
583
+ if (this._filterInput && e.target === this._filterInput) {
584
+ this._applyFilter(this._filterInput.value);
585
+ }
586
+ }
587
+ }
588
+ _addItem(value) {
589
+ const item = this._itemsByValue.get(value);
590
+ if (!item || item.disabled) return;
591
+ if (!this.select.hasAttribute("multiple")) {
592
+ for (const opt2 of this._select.options) {
593
+ if (opt2.selected) opt2.selected = false;
594
+ }
595
+ for (const el of this._availableList.querySelectorAll(`.${this._selectedClass}`)) {
596
+ el.classList.remove(this._selectedClass);
597
+ el.setAttribute("aria-selected", "false");
598
+ const btn = el.querySelector("[data-action='add']");
599
+ if (btn) btn.style.display = "";
600
+ }
601
+ }
602
+ const opt = this._select.querySelector(`option[value="${CSS.escape(value)}"]`);
603
+ if (opt) opt.selected = true;
604
+ const availEl = this._availableList.querySelector(`[data-value="${CSS.escape(value)}"]`);
605
+ if (availEl) {
606
+ availEl.classList.add(this._selectedClass);
607
+ availEl.setAttribute("aria-selected", "true");
608
+ const btn = availEl.querySelector("[data-action='add']");
609
+ if (btn) btn.style.display = "none";
610
+ }
611
+ this._withoutChildrenChangedNotification(() => {
612
+ if (!this.select.hasAttribute("multiple")) this._selectedList.replaceChildren();
613
+ this._selectedList.appendChild(this._createSelectedEntry(item));
614
+ });
615
+ }
616
+ _removeItem(value) {
617
+ const item = this._itemsByValue.get(value);
618
+ if (!item) return;
619
+ if (item.warnOnUnselect && !confirm(item.warnOnUnselect)) return;
620
+ const opt = this._select.querySelector(`option[value="${CSS.escape(value)}"]`);
621
+ if (opt) opt.selected = false;
622
+ const availEl = this._availableList.querySelector(`[data-value="${CSS.escape(value)}"]`);
623
+ if (availEl) {
624
+ availEl.classList.remove(this._selectedClass);
625
+ availEl.setAttribute("aria-selected", "false");
626
+ const btn = availEl.querySelector("[data-action='add']");
627
+ if (btn) btn.style.display = "";
628
+ }
629
+ const selEl = this._selectedList.querySelector(`[data-value="${CSS.escape(value)}"]`);
630
+ if (selEl) this._withoutChildrenChangedNotification(() => selEl.remove());
631
+ }
632
+ get select() {
633
+ return this._select;
634
+ }
635
+ get selectedLabel() {
636
+ return this.getAttribute("selected-label") || "Selected";
637
+ }
638
+ get availableLabel() {
639
+ return this.getAttribute("available-label") || "Available";
640
+ }
641
+ get addLabel() {
642
+ return this.getAttribute("add-label") || "\u2190";
643
+ }
644
+ get removeLabel() {
645
+ return this.getAttribute("remove-label") || "\xD7";
646
+ }
647
+ get filterText() {
648
+ return this._filterInput?.value ?? "";
649
+ }
650
+ set filterText(text) {
651
+ if (this._filterInput) this._filterInput.value = text;
652
+ this._applyFilter(text);
653
+ }
654
+ _applyFilter(text) {
655
+ const { matchCount, totalCount } = this._filterAvailable(text);
656
+ this.dispatchEvent(new CustomEvent("pwc-multiselect-dual-list:filter", {
657
+ bubbles: true,
658
+ detail: { filterText: text, matchCount, totalCount }
659
+ }));
660
+ }
661
+ _buildFilterRegex(text) {
662
+ if (!text) return null;
663
+ try {
664
+ return new RegExp(text, "i");
665
+ } catch {
666
+ return new RegExp(text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "i");
667
+ }
668
+ }
669
+ };
670
+
671
+ // src/multiselect-dual-list/multiselect-dual-list.js
672
+ var PwcMultiselectDualList = class extends MultiselectDualListBase {
673
+ _buildUI() {
674
+ const container = document.createElement("div");
675
+ container.innerHTML = `
676
+ <div class="pwc-msdl-selected">
677
+ <div class="pwc-msdl-header">${this.selectedLabel}</div>
678
+ <ul class="pwc-msdl-list" role="listbox" aria-label="${this.selectedLabel}"></ul>
679
+ </div>
680
+ <div class="pwc-msdl-available">
681
+ <div class="pwc-msdl-header">${this.availableLabel}</div>
682
+ <input type="search" class="pwc-msdl-filter" placeholder="Filter\u2026" aria-label="Filter ${this.availableLabel}" />
683
+ <ul class="pwc-msdl-list" role="listbox" aria-label="${this.availableLabel}"></ul>
684
+ </div>
685
+ `;
686
+ container.className = "pwc-msdl-container";
687
+ this.select.after(container);
688
+ return {
689
+ selectedList: container.querySelector(".pwc-msdl-selected .pwc-msdl-list"),
690
+ availableList: container.querySelector(".pwc-msdl-available .pwc-msdl-list"),
691
+ filterInput: container.querySelector(".pwc-msdl-filter")
692
+ };
693
+ }
694
+ _createEntry(item) {
695
+ const li = document.createElement("li");
696
+ li.className = "pwc-msdl-item";
697
+ li.role = "option";
698
+ li.dataset.value = item.value;
699
+ const label = document.createElement("span");
700
+ label.textContent = item.label;
701
+ li.appendChild(label);
702
+ return li;
703
+ }
704
+ _createAvailableEntry(item) {
705
+ const li = this._createEntry(item);
706
+ li.setAttribute("aria-selected", String(item.selected));
707
+ if (item.disabled) {
708
+ li.classList.add("pwc-msdl-item--disabled");
709
+ li.setAttribute("aria-disabled", "true");
710
+ }
711
+ if (item.selected) li.classList.add("pwc-msdl-item--selected");
712
+ if (item.depth > 0) li.style.paddingLeft = `${item.depth * 1.5}em`;
713
+ if (!item.disabled) {
714
+ const btn = document.createElement("button");
715
+ btn.type = "button";
716
+ btn.className = "pwc-msdl-action";
717
+ btn.dataset.action = "add";
718
+ btn.textContent = this.addLabel;
719
+ btn.setAttribute("aria-label", `${this.addLabel} ${item.label}`);
720
+ if (item.selected) btn.style.display = "none";
721
+ li.appendChild(btn);
722
+ }
723
+ return li;
724
+ }
725
+ _createSelectedEntry(item) {
726
+ const li = this._createEntry(item);
727
+ const btn = document.createElement("button");
728
+ btn.type = "button";
729
+ btn.className = "pwc-msdl-action";
730
+ btn.dataset.action = "remove";
731
+ btn.textContent = this.removeLabel;
732
+ btn.setAttribute("aria-label", `${this.removeLabel} ${item.label}`);
733
+ li.appendChild(btn);
734
+ return li;
735
+ }
736
+ _filterAvailable(text) {
737
+ const items = this._availableList.querySelectorAll("[data-value]");
738
+ const totalCount = items.length;
739
+ const regex = this._buildFilterRegex(text);
740
+ if (!regex) {
741
+ for (const el of items) el.style.display = "";
742
+ return { matchCount: totalCount, totalCount };
743
+ }
744
+ let matchCount = 0;
745
+ for (const el of items) {
746
+ const match = regex.test(el.textContent);
747
+ el.style.display = match ? "" : "none";
748
+ if (match) matchCount++;
749
+ }
750
+ return { matchCount, totalCount };
751
+ }
752
+ };
753
+ var define3 = () => defineOnce("pwc-multiselect-dual-list", PwcMultiselectDualList);
754
+
755
+ // src/multiselect-dual-list/multiselect-dual-list.css
756
+ var multiselect_dual_list_default = "pwc-multiselect-dual-list {\n /* sizing */\n --pwc-msdl-width: 100%;\n\n /* spacing */\n --pwc-msdl-gap: 12px;\n --pwc-msdl-padding: 8px;\n --pwc-msdl-item-padding: 6px 10px;\n --pwc-msdl-indent: 1.5em;\n\n /* list */\n --pwc-msdl-list-max-height: 20em;\n\n /* visuals */\n --pwc-msdl-bg: #fff;\n --pwc-msdl-border: 1px solid rgba(0, 0, 0, 0.15);\n --pwc-msdl-border-radius: 4px;\n --pwc-msdl-separator: rgba(0, 0, 0, 0.08);\n\n /* item */\n --pwc-msdl-item-bg: #f8f8f8;\n --pwc-msdl-item-hover-bg: #f0f0f0;\n --pwc-msdl-item-selected-bg: #e8e8e8;\n --pwc-msdl-item-selected-color: #999;\n --pwc-msdl-item-disabled-color: #bbb;\n\n /* button */\n --pwc-msdl-action-bg: transparent;\n --pwc-msdl-action-hover-bg: rgba(0, 0, 0, 0.06);\n --pwc-msdl-action-border: 1px solid rgba(0, 0, 0, 0.2);\n --pwc-msdl-action-radius: 3px;\n\n display: block;\n width: var(--pwc-msdl-width);\n}\n\n.pwc-msdl-container {\n display: flex;\n gap: var(--pwc-msdl-gap);\n}\n\n.pwc-msdl-selected,\n.pwc-msdl-available {\n flex: 1;\n min-width: 0;\n background: var(--pwc-msdl-bg);\n border: var(--pwc-msdl-border);\n border-radius: var(--pwc-msdl-border-radius);\n padding: var(--pwc-msdl-padding);\n}\n\n.pwc-msdl-header {\n font-weight: 600;\n margin-bottom: 6px;\n}\n\n.pwc-msdl-filter {\n display: block;\n width: 100%;\n box-sizing: border-box;\n padding: 4px 8px;\n margin-bottom: 6px;\n border: var(--pwc-msdl-border);\n border-radius: var(--pwc-msdl-border-radius);\n font: inherit;\n font-size: 0.9em;\n}\n\n.pwc-msdl-list {\n list-style: none;\n margin: 0;\n padding: 0;\n max-height: var(--pwc-msdl-list-max-height);\n overflow-y: auto;\n}\n\n.pwc-msdl-item {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: var(--pwc-msdl-item-padding);\n background: var(--pwc-msdl-item-bg);\n border-bottom: 1px solid var(--pwc-msdl-separator);\n}\n\n.pwc-msdl-item:last-child {\n border-bottom: none;\n}\n\n.pwc-msdl-item:hover {\n background: var(--pwc-msdl-item-hover-bg);\n}\n\n.pwc-msdl-item--selected {\n background: var(--pwc-msdl-item-selected-bg);\n color: var(--pwc-msdl-item-selected-color);\n}\n\n.pwc-msdl-item--disabled {\n color: var(--pwc-msdl-item-disabled-color);\n cursor: default;\n}\n\n.pwc-msdl-action {\n appearance: none;\n border: var(--pwc-msdl-action-border);\n background: var(--pwc-msdl-action-bg);\n padding: 2px 8px;\n border-radius: var(--pwc-msdl-action-radius);\n cursor: pointer;\n font: inherit;\n font-size: 0.85em;\n flex-shrink: 0;\n margin-left: 8px;\n}\n\n.pwc-msdl-action:hover {\n background: var(--pwc-msdl-action-hover-bg);\n}\n\npwc-multiselect-dual-list[hide-selected] .pwc-msdl-item--selected {\n display: none;\n}\n";
757
+
758
+ // src/multiselect-dual-list/index.js
759
+ function register3() {
760
+ PwcMultiselectDualList.registerCss(multiselect_dual_list_default);
761
+ define3();
762
+ }
763
+ register3();