@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/README.md +30 -0
- package/dist/all-bs5.js +783 -0
- package/dist/all.js +763 -0
- package/dist/dialog-opener-bs5.js +484 -0
- package/dist/dialog-opener.js +467 -0
- package/dist/modal-dialog-bs5.js +259 -0
- package/dist/modal-dialog.js +251 -0
- package/dist/multiselect-dual-list-bs5.js +368 -0
- package/dist/multiselect-dual-list.js +365 -0
- package/package.json +35 -0
package/dist/all-bs5.js
ADDED
|
@@ -0,0 +1,783 @@
|
|
|
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/bs5/dialog-opener.js
|
|
238
|
+
var PwcDialogOpenerBs5 = class extends BaseDialogOpener {
|
|
239
|
+
findOrCreateDialog(src) {
|
|
240
|
+
const tag = "pwc-modal-dialog-bs5";
|
|
241
|
+
if (!this.dialog) {
|
|
242
|
+
this.dialog = this.querySelector(tag) || document.createElement(tag);
|
|
243
|
+
if (!this.dialog.isConnected) {
|
|
244
|
+
this.appendChild(this.dialog);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
this.dialog.open({
|
|
248
|
+
title: this.getAttribute("title") || "",
|
|
249
|
+
size: this.getAttribute("size") || "lg",
|
|
250
|
+
closeText: this.getAttribute("close") || "Close",
|
|
251
|
+
showClose: false,
|
|
252
|
+
backdrop: true,
|
|
253
|
+
keyboard: true,
|
|
254
|
+
focus: true
|
|
255
|
+
});
|
|
256
|
+
const closeText = this.getAttribute("close") || "Close";
|
|
257
|
+
this.dialog.footerEl.innerHTML = `
|
|
258
|
+
<div class="pwc-dialog-opener-actions">
|
|
259
|
+
<button type="button" class="btn btn-secondary" data-pwc-action="close" aria-label="${closeText}">
|
|
260
|
+
${closeText}
|
|
261
|
+
</button>
|
|
262
|
+
</div>
|
|
263
|
+
`;
|
|
264
|
+
const body = this.dialog.bodyEl;
|
|
265
|
+
body.replaceChildren(this.createIFrame(src));
|
|
266
|
+
this.modal = {
|
|
267
|
+
show: () => {
|
|
268
|
+
},
|
|
269
|
+
hide: () => this.dialog.close()
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
_moveOutSelector() {
|
|
273
|
+
let selector = super._moveOutSelector();
|
|
274
|
+
if (selector === "primary") {
|
|
275
|
+
selector = ".btn-primary[type=submit]";
|
|
276
|
+
}
|
|
277
|
+
return selector;
|
|
278
|
+
}
|
|
279
|
+
};
|
|
280
|
+
function define() {
|
|
281
|
+
defineOnce("pwc-dialog-opener-bs5", PwcDialogOpenerBs5);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// src/core/pwc-simple-init-element.js
|
|
285
|
+
var PwcSimpleInitElement = class extends PwcElement {
|
|
286
|
+
connectedCallback() {
|
|
287
|
+
if (this._connected) return;
|
|
288
|
+
super.connectedCallback();
|
|
289
|
+
queueMicrotask(() => {
|
|
290
|
+
if (!this._connected) return;
|
|
291
|
+
this.onConnect();
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
/**
|
|
295
|
+
* Hook for subclasses.
|
|
296
|
+
* Called once per connection, after microtask deferral.
|
|
297
|
+
*/
|
|
298
|
+
onConnect() {
|
|
299
|
+
}
|
|
300
|
+
};
|
|
301
|
+
|
|
302
|
+
// src/modal-dialog/base.js
|
|
303
|
+
var ModalDialogBase = class extends PwcSimpleInitElement {
|
|
304
|
+
static events = ["click"];
|
|
305
|
+
onDisconnect() {
|
|
306
|
+
this._teardown();
|
|
307
|
+
}
|
|
308
|
+
get ui() {
|
|
309
|
+
if (!this._ui) throw new Error("ui is only available after open()");
|
|
310
|
+
return this._ui;
|
|
311
|
+
}
|
|
312
|
+
get rootEl() {
|
|
313
|
+
return this.ui.rootEl;
|
|
314
|
+
}
|
|
315
|
+
get bodyEl() {
|
|
316
|
+
return this.ui.bodyEl;
|
|
317
|
+
}
|
|
318
|
+
get headerEl() {
|
|
319
|
+
return this.ui.headerEl;
|
|
320
|
+
}
|
|
321
|
+
get footerEl() {
|
|
322
|
+
return this.ui.footerEl;
|
|
323
|
+
}
|
|
324
|
+
isOpen() {
|
|
325
|
+
return false;
|
|
326
|
+
}
|
|
327
|
+
open({ title = "", size = "lg", closeText = "Close", ...options }) {
|
|
328
|
+
if (!this.isConnected) {
|
|
329
|
+
this._autoRemove = true;
|
|
330
|
+
document.body.appendChild(this);
|
|
331
|
+
}
|
|
332
|
+
this._teardown();
|
|
333
|
+
const ui = this._render({ title, size, closeText, ...options });
|
|
334
|
+
this._ui = ui;
|
|
335
|
+
const parent = this._getOpenSibling();
|
|
336
|
+
this._parent = parent && parent !== ui.rootEl ? parent : null;
|
|
337
|
+
this._closed = false;
|
|
338
|
+
this._armFinalClose(ui, () => this._onFinalClose());
|
|
339
|
+
if (this._parent) {
|
|
340
|
+
this._parent.dataset.closeReason = "suspend";
|
|
341
|
+
this._suspend(this._parent);
|
|
342
|
+
}
|
|
343
|
+
this._show(ui, { title, size, closeText, ...options });
|
|
344
|
+
}
|
|
345
|
+
close() {
|
|
346
|
+
if (this._closed) return;
|
|
347
|
+
this._closed = true;
|
|
348
|
+
this.dataset.closeReason = "final";
|
|
349
|
+
this._hide(this._ui);
|
|
350
|
+
}
|
|
351
|
+
_onFinalClose() {
|
|
352
|
+
this._closed = true;
|
|
353
|
+
delete this.dataset.closeReason;
|
|
354
|
+
const parent = this._parent;
|
|
355
|
+
this._parent = null;
|
|
356
|
+
this._teardown();
|
|
357
|
+
if (parent && parent.isConnected) {
|
|
358
|
+
delete parent.dataset.closeReason;
|
|
359
|
+
queueMicrotask(() => this._restore(parent));
|
|
360
|
+
}
|
|
361
|
+
if (this._autoRemove && this.isConnected) this.remove();
|
|
362
|
+
}
|
|
363
|
+
handleEvent(e) {
|
|
364
|
+
if (e.type !== "click") return;
|
|
365
|
+
if (e.defaultPrevented) return;
|
|
366
|
+
const ui = this._ui;
|
|
367
|
+
if (!ui?.rootEl) return;
|
|
368
|
+
if (e.target === ui.rootEl) {
|
|
369
|
+
this.close();
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
372
|
+
const closeEl = e.target.closest('[data-pwc-action="close"]');
|
|
373
|
+
if (!closeEl || !this.contains(closeEl)) return;
|
|
374
|
+
this.close();
|
|
375
|
+
}
|
|
376
|
+
_teardown() {
|
|
377
|
+
const ui = this._ui;
|
|
378
|
+
this._ui = null;
|
|
379
|
+
ui?.teardown?.();
|
|
380
|
+
}
|
|
381
|
+
};
|
|
382
|
+
|
|
383
|
+
// src/modal-dialog/bs5/modal-dialog.js
|
|
384
|
+
var PwcModalDialogBs5 = class extends ModalDialogBase {
|
|
385
|
+
static events = ["click", "hidden.bs.modal"];
|
|
386
|
+
onConnect() {
|
|
387
|
+
this.classList.add("modal", "fade");
|
|
388
|
+
this.tabIndex = -1;
|
|
389
|
+
this.setAttribute("aria-hidden", "true");
|
|
390
|
+
}
|
|
391
|
+
isOpen() {
|
|
392
|
+
return this.classList.contains("show");
|
|
393
|
+
}
|
|
394
|
+
requireBsModal() {
|
|
395
|
+
const BsModal = globalThis.bootstrap?.Modal;
|
|
396
|
+
if (!BsModal) throw new Error("Bootstrap Modal required (globalThis.bootstrap.Modal)");
|
|
397
|
+
return BsModal;
|
|
398
|
+
}
|
|
399
|
+
_render({ title, size, closeText, showClose = true }) {
|
|
400
|
+
this.innerHTML = `
|
|
401
|
+
<div class="modal-dialog modal-dialog-centered modal-${size}">
|
|
402
|
+
<div class="modal-content">
|
|
403
|
+
<div class="modal-header">
|
|
404
|
+
<h3 class="modal-title"></h3>
|
|
405
|
+
</div>
|
|
406
|
+
<div class="modal-body"></div>
|
|
407
|
+
<div class="modal-footer"></div>
|
|
408
|
+
</div>
|
|
409
|
+
</div>
|
|
410
|
+
`;
|
|
411
|
+
this.querySelector(".modal-title").textContent = title;
|
|
412
|
+
if (showClose) {
|
|
413
|
+
const btn = document.createElement("button");
|
|
414
|
+
btn.type = "button";
|
|
415
|
+
btn.className = "btn-close";
|
|
416
|
+
btn.setAttribute("aria-label", closeText);
|
|
417
|
+
btn.setAttribute("data-pwc-action", "close");
|
|
418
|
+
this.querySelector(".modal-header").appendChild(btn);
|
|
419
|
+
}
|
|
420
|
+
return {
|
|
421
|
+
rootEl: this,
|
|
422
|
+
bodyEl: this.querySelector(".modal-body"),
|
|
423
|
+
headerEl: this.querySelector(".modal-header"),
|
|
424
|
+
footerEl: this.querySelector(".modal-footer"),
|
|
425
|
+
modal: null,
|
|
426
|
+
teardown: () => {
|
|
427
|
+
const BsModal = this.requireBsModal();
|
|
428
|
+
BsModal.getInstance(this)?.dispose();
|
|
429
|
+
this.innerHTML = "";
|
|
430
|
+
this._finalClose = null;
|
|
431
|
+
}
|
|
432
|
+
};
|
|
433
|
+
}
|
|
434
|
+
_getOpenSibling() {
|
|
435
|
+
const el = document.querySelector(".modal.show");
|
|
436
|
+
if (el === this) return null;
|
|
437
|
+
return el;
|
|
438
|
+
}
|
|
439
|
+
_suspend(el) {
|
|
440
|
+
const BsModal = this.requireBsModal();
|
|
441
|
+
BsModal.getOrCreateInstance(el).hide();
|
|
442
|
+
}
|
|
443
|
+
_restore(el) {
|
|
444
|
+
const BsModal = this.requireBsModal();
|
|
445
|
+
BsModal.getOrCreateInstance(el).show();
|
|
446
|
+
}
|
|
447
|
+
_show(ui, { backdrop = true, keyboard = true, focus = true } = {}) {
|
|
448
|
+
const BsModal = this.requireBsModal();
|
|
449
|
+
ui.modal = BsModal.getOrCreateInstance(this, { backdrop, keyboard, focus });
|
|
450
|
+
ui.modal.show();
|
|
451
|
+
}
|
|
452
|
+
_hide(ui) {
|
|
453
|
+
ui.modal?.hide();
|
|
454
|
+
}
|
|
455
|
+
_armFinalClose(_ui, onFinalClose) {
|
|
456
|
+
this._finalClose = onFinalClose;
|
|
457
|
+
}
|
|
458
|
+
handleEvent(e) {
|
|
459
|
+
if (e.type === "hidden.bs.modal") {
|
|
460
|
+
if (this.dataset.closeReason === "suspend") return;
|
|
461
|
+
const fn = this._finalClose;
|
|
462
|
+
this._finalClose = null;
|
|
463
|
+
if (typeof fn === "function") fn();
|
|
464
|
+
return;
|
|
465
|
+
}
|
|
466
|
+
super.handleEvent(e);
|
|
467
|
+
}
|
|
468
|
+
};
|
|
469
|
+
var define2 = () => defineOnce("pwc-modal-dialog-bs5", PwcModalDialogBs5);
|
|
470
|
+
|
|
471
|
+
// src/modal-dialog/bs5/index.js
|
|
472
|
+
function register() {
|
|
473
|
+
define2();
|
|
474
|
+
}
|
|
475
|
+
register();
|
|
476
|
+
|
|
477
|
+
// src/dialog-opener/bs5/index.js
|
|
478
|
+
function register2() {
|
|
479
|
+
define();
|
|
480
|
+
}
|
|
481
|
+
register2();
|
|
482
|
+
|
|
483
|
+
// src/core/pwc-children-observer-element.js
|
|
484
|
+
var PwcChildrenObserverElement = class extends PwcElement {
|
|
485
|
+
static observeMode = "children";
|
|
486
|
+
// "children" | "tree"
|
|
487
|
+
connectedCallback() {
|
|
488
|
+
if (this._connected) return;
|
|
489
|
+
super.connectedCallback();
|
|
490
|
+
this._startChildrenObserver();
|
|
491
|
+
}
|
|
492
|
+
disconnectedCallback() {
|
|
493
|
+
this._stopChildrenObserver();
|
|
494
|
+
super.disconnectedCallback();
|
|
495
|
+
}
|
|
496
|
+
onChildrenChanged(_mutations) {
|
|
497
|
+
}
|
|
498
|
+
/** Run fn() without triggering onChildrenChanged for the resulting DOM mutations. */
|
|
499
|
+
_withoutChildrenChangedNotification(fn) {
|
|
500
|
+
fn();
|
|
501
|
+
this._childrenObserver?.takeRecords();
|
|
502
|
+
}
|
|
503
|
+
_startChildrenObserver() {
|
|
504
|
+
const mode = this.constructor.observeMode || "children";
|
|
505
|
+
const subtree = mode === "tree";
|
|
506
|
+
this._childrenObserver = new MutationObserver((mutations) => {
|
|
507
|
+
if (!this._connected) return;
|
|
508
|
+
this.onChildrenChanged(mutations);
|
|
509
|
+
});
|
|
510
|
+
this._childrenObserver.observe(this, { childList: true, subtree });
|
|
511
|
+
this.onChildrenChanged([]);
|
|
512
|
+
}
|
|
513
|
+
_stopChildrenObserver() {
|
|
514
|
+
if (!this._childrenObserver) return;
|
|
515
|
+
this._childrenObserver.disconnect();
|
|
516
|
+
this._childrenObserver = null;
|
|
517
|
+
}
|
|
518
|
+
};
|
|
519
|
+
|
|
520
|
+
// src/multiselect-dual-list/base.js
|
|
521
|
+
var MultiselectDualListBase = class extends PwcChildrenObserverElement {
|
|
522
|
+
static observeMode = "tree";
|
|
523
|
+
static events = ["click", "input"];
|
|
524
|
+
get _selectedClass() {
|
|
525
|
+
return "pwc-msdl-item--selected";
|
|
526
|
+
}
|
|
527
|
+
onChildrenChanged() {
|
|
528
|
+
const select = this.querySelector("select");
|
|
529
|
+
if (!select) return;
|
|
530
|
+
this._select = select;
|
|
531
|
+
const items = this._parseOptions(select);
|
|
532
|
+
this._items = items;
|
|
533
|
+
this._itemsByValue = new Map(items.map((item) => [item.value, item]));
|
|
534
|
+
this._withoutChildrenChangedNotification(() => {
|
|
535
|
+
if (!this._availableList) {
|
|
536
|
+
const ui = this._buildUI();
|
|
537
|
+
this._availableList = ui.availableList;
|
|
538
|
+
this._selectedList = ui.selectedList;
|
|
539
|
+
this._filterInput = ui.filterInput;
|
|
540
|
+
}
|
|
541
|
+
this._populateLists(items);
|
|
542
|
+
select.style.display = "none";
|
|
543
|
+
const filterText = this._filterInput?.value;
|
|
544
|
+
if (filterText) this._applyFilter(filterText);
|
|
545
|
+
});
|
|
546
|
+
}
|
|
547
|
+
_populateLists(items) {
|
|
548
|
+
this._availableList.replaceChildren();
|
|
549
|
+
this._selectedList.replaceChildren();
|
|
550
|
+
for (const item of items) {
|
|
551
|
+
this._availableList.appendChild(this._createAvailableEntry(item));
|
|
552
|
+
}
|
|
553
|
+
for (const item of items) {
|
|
554
|
+
if (item.selected) {
|
|
555
|
+
this._selectedList.appendChild(this._createSelectedEntry(item));
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
_parseOptions(select) {
|
|
560
|
+
const options = Array.from(select.options);
|
|
561
|
+
const parentMap = /* @__PURE__ */ new Map();
|
|
562
|
+
for (const opt of options) {
|
|
563
|
+
const parent = opt.dataset.parent;
|
|
564
|
+
if (parent) parentMap.set(opt.value, parent);
|
|
565
|
+
}
|
|
566
|
+
return options.map((opt) => ({
|
|
567
|
+
value: opt.value,
|
|
568
|
+
label: opt.textContent,
|
|
569
|
+
parent: opt.dataset.parent || null,
|
|
570
|
+
depth: this._calculateDepth(opt.value, parentMap),
|
|
571
|
+
selected: opt.selected,
|
|
572
|
+
disabled: opt.disabled,
|
|
573
|
+
warnOnUnselect: opt.dataset.warnOnUnselect || null
|
|
574
|
+
}));
|
|
575
|
+
}
|
|
576
|
+
_calculateDepth(value, parentMap) {
|
|
577
|
+
let depth = 0;
|
|
578
|
+
let current = value;
|
|
579
|
+
const visited = /* @__PURE__ */ new Set();
|
|
580
|
+
while (parentMap.has(current)) {
|
|
581
|
+
if (visited.has(current)) break;
|
|
582
|
+
visited.add(current);
|
|
583
|
+
current = parentMap.get(current);
|
|
584
|
+
depth++;
|
|
585
|
+
}
|
|
586
|
+
return depth;
|
|
587
|
+
}
|
|
588
|
+
handleEvent(e) {
|
|
589
|
+
if (e.type === "click") {
|
|
590
|
+
const actionEl = e.target.closest("[data-action]");
|
|
591
|
+
if (!actionEl || !this.contains(actionEl)) return;
|
|
592
|
+
const action = actionEl.dataset.action;
|
|
593
|
+
const value = actionEl.closest("[data-value]")?.dataset.value;
|
|
594
|
+
if (!value) return;
|
|
595
|
+
if (action === "add") this._addItem(value);
|
|
596
|
+
else if (action === "remove") this._removeItem(value);
|
|
597
|
+
return;
|
|
598
|
+
}
|
|
599
|
+
if (e.type === "input") {
|
|
600
|
+
if (this._filterInput && e.target === this._filterInput) {
|
|
601
|
+
this._applyFilter(this._filterInput.value);
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
_addItem(value) {
|
|
606
|
+
const item = this._itemsByValue.get(value);
|
|
607
|
+
if (!item || item.disabled) return;
|
|
608
|
+
if (!this.select.hasAttribute("multiple")) {
|
|
609
|
+
for (const opt2 of this._select.options) {
|
|
610
|
+
if (opt2.selected) opt2.selected = false;
|
|
611
|
+
}
|
|
612
|
+
for (const el of this._availableList.querySelectorAll(`.${this._selectedClass}`)) {
|
|
613
|
+
el.classList.remove(this._selectedClass);
|
|
614
|
+
el.setAttribute("aria-selected", "false");
|
|
615
|
+
const btn = el.querySelector("[data-action='add']");
|
|
616
|
+
if (btn) btn.style.display = "";
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
const opt = this._select.querySelector(`option[value="${CSS.escape(value)}"]`);
|
|
620
|
+
if (opt) opt.selected = true;
|
|
621
|
+
const availEl = this._availableList.querySelector(`[data-value="${CSS.escape(value)}"]`);
|
|
622
|
+
if (availEl) {
|
|
623
|
+
availEl.classList.add(this._selectedClass);
|
|
624
|
+
availEl.setAttribute("aria-selected", "true");
|
|
625
|
+
const btn = availEl.querySelector("[data-action='add']");
|
|
626
|
+
if (btn) btn.style.display = "none";
|
|
627
|
+
}
|
|
628
|
+
this._withoutChildrenChangedNotification(() => {
|
|
629
|
+
if (!this.select.hasAttribute("multiple")) this._selectedList.replaceChildren();
|
|
630
|
+
this._selectedList.appendChild(this._createSelectedEntry(item));
|
|
631
|
+
});
|
|
632
|
+
}
|
|
633
|
+
_removeItem(value) {
|
|
634
|
+
const item = this._itemsByValue.get(value);
|
|
635
|
+
if (!item) return;
|
|
636
|
+
if (item.warnOnUnselect && !confirm(item.warnOnUnselect)) return;
|
|
637
|
+
const opt = this._select.querySelector(`option[value="${CSS.escape(value)}"]`);
|
|
638
|
+
if (opt) opt.selected = false;
|
|
639
|
+
const availEl = this._availableList.querySelector(`[data-value="${CSS.escape(value)}"]`);
|
|
640
|
+
if (availEl) {
|
|
641
|
+
availEl.classList.remove(this._selectedClass);
|
|
642
|
+
availEl.setAttribute("aria-selected", "false");
|
|
643
|
+
const btn = availEl.querySelector("[data-action='add']");
|
|
644
|
+
if (btn) btn.style.display = "";
|
|
645
|
+
}
|
|
646
|
+
const selEl = this._selectedList.querySelector(`[data-value="${CSS.escape(value)}"]`);
|
|
647
|
+
if (selEl) this._withoutChildrenChangedNotification(() => selEl.remove());
|
|
648
|
+
}
|
|
649
|
+
get select() {
|
|
650
|
+
return this._select;
|
|
651
|
+
}
|
|
652
|
+
get selectedLabel() {
|
|
653
|
+
return this.getAttribute("selected-label") || "Selected";
|
|
654
|
+
}
|
|
655
|
+
get availableLabel() {
|
|
656
|
+
return this.getAttribute("available-label") || "Available";
|
|
657
|
+
}
|
|
658
|
+
get addLabel() {
|
|
659
|
+
return this.getAttribute("add-label") || "\u2190";
|
|
660
|
+
}
|
|
661
|
+
get removeLabel() {
|
|
662
|
+
return this.getAttribute("remove-label") || "\xD7";
|
|
663
|
+
}
|
|
664
|
+
get filterText() {
|
|
665
|
+
return this._filterInput?.value ?? "";
|
|
666
|
+
}
|
|
667
|
+
set filterText(text) {
|
|
668
|
+
if (this._filterInput) this._filterInput.value = text;
|
|
669
|
+
this._applyFilter(text);
|
|
670
|
+
}
|
|
671
|
+
_applyFilter(text) {
|
|
672
|
+
const { matchCount, totalCount } = this._filterAvailable(text);
|
|
673
|
+
this.dispatchEvent(new CustomEvent("pwc-multiselect-dual-list:filter", {
|
|
674
|
+
bubbles: true,
|
|
675
|
+
detail: { filterText: text, matchCount, totalCount }
|
|
676
|
+
}));
|
|
677
|
+
}
|
|
678
|
+
_buildFilterRegex(text) {
|
|
679
|
+
if (!text) return null;
|
|
680
|
+
try {
|
|
681
|
+
return new RegExp(text, "i");
|
|
682
|
+
} catch {
|
|
683
|
+
return new RegExp(text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "i");
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
};
|
|
687
|
+
|
|
688
|
+
// src/multiselect-dual-list/bs5/multiselect-dual-list.js
|
|
689
|
+
var PwcMultiselectDualListBs5 = class extends MultiselectDualListBase {
|
|
690
|
+
get _selectedClass() {
|
|
691
|
+
return "list-group-item-secondary";
|
|
692
|
+
}
|
|
693
|
+
_buildUI() {
|
|
694
|
+
const container = document.createElement("div");
|
|
695
|
+
container.innerHTML = `
|
|
696
|
+
<div class="col">
|
|
697
|
+
<h6>${this.selectedLabel}</h6>
|
|
698
|
+
<div class="list-group" style="max-height:20em;overflow-y:auto" role="listbox" aria-label="${this.selectedLabel}" data-role="selected"></div>
|
|
699
|
+
</div>
|
|
700
|
+
<div class="col">
|
|
701
|
+
<h6>${this.availableLabel}</h6>
|
|
702
|
+
<input type="search" class="form-control form-control-sm mb-2" placeholder="Filter\u2026" aria-label="Filter ${this.availableLabel}" />
|
|
703
|
+
<div class="list-group" style="max-height:20em;overflow-y:auto" role="listbox" aria-label="${this.availableLabel}" data-role="available"></div>
|
|
704
|
+
</div>
|
|
705
|
+
`;
|
|
706
|
+
container.className = "row g-3";
|
|
707
|
+
this.select.after(container);
|
|
708
|
+
return {
|
|
709
|
+
selectedList: container.querySelector("[data-role='selected']"),
|
|
710
|
+
availableList: container.querySelector("[data-role='available']"),
|
|
711
|
+
filterInput: container.querySelector("input[type='search']")
|
|
712
|
+
};
|
|
713
|
+
}
|
|
714
|
+
_createEntry(item) {
|
|
715
|
+
const el = document.createElement("div");
|
|
716
|
+
el.className = "list-group-item d-flex justify-content-between align-items-center";
|
|
717
|
+
el.role = "option";
|
|
718
|
+
el.dataset.value = item.value;
|
|
719
|
+
const label = document.createElement("span");
|
|
720
|
+
label.textContent = item.label;
|
|
721
|
+
el.appendChild(label);
|
|
722
|
+
return el;
|
|
723
|
+
}
|
|
724
|
+
_createAvailableEntry(item) {
|
|
725
|
+
const el = this._createEntry(item);
|
|
726
|
+
el.setAttribute("aria-selected", String(item.selected));
|
|
727
|
+
if (item.disabled) {
|
|
728
|
+
el.classList.add("disabled");
|
|
729
|
+
el.setAttribute("aria-disabled", "true");
|
|
730
|
+
}
|
|
731
|
+
if (item.selected) el.classList.add("list-group-item-secondary");
|
|
732
|
+
if (item.depth > 0) el.style.paddingLeft = `${item.depth * 1.5 + 0.75}em`;
|
|
733
|
+
if (!item.disabled) {
|
|
734
|
+
const btn = document.createElement("button");
|
|
735
|
+
btn.type = "button";
|
|
736
|
+
btn.className = "btn btn-sm btn-outline-primary";
|
|
737
|
+
btn.dataset.action = "add";
|
|
738
|
+
btn.textContent = this.addLabel;
|
|
739
|
+
btn.setAttribute("aria-label", `${this.addLabel} ${item.label}`);
|
|
740
|
+
if (item.selected) btn.style.display = "none";
|
|
741
|
+
el.appendChild(btn);
|
|
742
|
+
}
|
|
743
|
+
return el;
|
|
744
|
+
}
|
|
745
|
+
_createSelectedEntry(item) {
|
|
746
|
+
const el = this._createEntry(item);
|
|
747
|
+
const btn = document.createElement("button");
|
|
748
|
+
btn.type = "button";
|
|
749
|
+
btn.className = "btn btn-sm btn-outline-danger";
|
|
750
|
+
btn.dataset.action = "remove";
|
|
751
|
+
btn.textContent = this.removeLabel;
|
|
752
|
+
btn.setAttribute("aria-label", `${this.removeLabel} ${item.label}`);
|
|
753
|
+
el.appendChild(btn);
|
|
754
|
+
return el;
|
|
755
|
+
}
|
|
756
|
+
_filterAvailable(text) {
|
|
757
|
+
const items = this._availableList.querySelectorAll("[data-value]");
|
|
758
|
+
const totalCount = items.length;
|
|
759
|
+
const regex = this._buildFilterRegex(text);
|
|
760
|
+
if (!regex) {
|
|
761
|
+
for (const el of items) el.classList.remove("d-none");
|
|
762
|
+
return { matchCount: totalCount, totalCount };
|
|
763
|
+
}
|
|
764
|
+
let matchCount = 0;
|
|
765
|
+
for (const el of items) {
|
|
766
|
+
const label = el.querySelector("span")?.textContent || "";
|
|
767
|
+
const match = regex.test(label);
|
|
768
|
+
el.classList.toggle("d-none", !match);
|
|
769
|
+
if (match) matchCount++;
|
|
770
|
+
}
|
|
771
|
+
return { matchCount, totalCount };
|
|
772
|
+
}
|
|
773
|
+
};
|
|
774
|
+
var define3 = () => defineOnce("pwc-multiselect-dual-list-bs5", PwcMultiselectDualListBs5);
|
|
775
|
+
|
|
776
|
+
// src/multiselect-dual-list/bs5/index.js
|
|
777
|
+
function register3() {
|
|
778
|
+
PwcMultiselectDualListBs5.registerCss(
|
|
779
|
+
"pwc-multiselect-dual-list-bs5[hide-selected] .list-group-item-secondary { display: none; }"
|
|
780
|
+
);
|
|
781
|
+
define3();
|
|
782
|
+
}
|
|
783
|
+
register3();
|