@tillsc/progressive-web-components 0.1.0 → 0.1.1

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 (45) hide show
  1. package/package.json +3 -4
  2. package/src/core/pwc-children-observer-element.js +53 -0
  3. package/src/core/pwc-element.js +76 -0
  4. package/src/core/pwc-sentinel-init-element.js +56 -0
  5. package/src/core/pwc-simple-init-element.js +25 -0
  6. package/src/core/utils.js +9 -0
  7. package/src/dialog-opener/INTERNALS.md +47 -0
  8. package/src/dialog-opener/README.md +145 -0
  9. package/src/dialog-opener/base.js +226 -0
  10. package/src/dialog-opener/bs5/dialog-opener.js +73 -0
  11. package/src/dialog-opener/bs5/index.js +10 -0
  12. package/src/dialog-opener/dialog-opener.css +21 -0
  13. package/src/dialog-opener/dialog-opener.js +41 -0
  14. package/src/dialog-opener/index.js +12 -0
  15. package/src/dialog-opener/test/basic.html +109 -0
  16. package/src/dialog-opener/test/bs5-basic.html +100 -0
  17. package/src/dialog-opener/test/bs5-iframe-target.html +41 -0
  18. package/src/dialog-opener/test/iframe-target.html +28 -0
  19. package/src/dialog-opener/test/index.html +19 -0
  20. package/src/index-bs5.js +3 -0
  21. package/src/index.js +3 -0
  22. package/src/modal-dialog/INTERNALS.md +55 -0
  23. package/src/modal-dialog/README.md +139 -0
  24. package/src/modal-dialog/base.js +117 -0
  25. package/src/modal-dialog/bs5/index.js +7 -0
  26. package/src/modal-dialog/bs5/modal-dialog.js +109 -0
  27. package/src/modal-dialog/index.js +9 -0
  28. package/src/modal-dialog/modal-dialog.css +103 -0
  29. package/src/modal-dialog/modal-dialog.js +97 -0
  30. package/src/modal-dialog/test/basic.html +84 -0
  31. package/src/modal-dialog/test/bs5-basic.html +123 -0
  32. package/src/modal-dialog/test/index.html +19 -0
  33. package/src/multiselect-dual-list/INTERNALS.md +101 -0
  34. package/src/multiselect-dual-list/README.md +191 -0
  35. package/src/multiselect-dual-list/base.js +215 -0
  36. package/src/multiselect-dual-list/bs5/index.js +10 -0
  37. package/src/multiselect-dual-list/bs5/multiselect-dual-list.js +103 -0
  38. package/src/multiselect-dual-list/index.js +9 -0
  39. package/src/multiselect-dual-list/multiselect-dual-list.css +123 -0
  40. package/src/multiselect-dual-list/multiselect-dual-list.js +100 -0
  41. package/src/multiselect-dual-list/test/basic.html +115 -0
  42. package/src/multiselect-dual-list/test/bs5-basic.html +106 -0
  43. package/src/multiselect-dual-list/test/dynamic-options.html +70 -0
  44. package/src/multiselect-dual-list/test/filter-api.html +66 -0
  45. package/src/multiselect-dual-list/test/index.html +21 -0
package/package.json CHANGED
@@ -1,12 +1,11 @@
1
1
  {
2
2
  "name": "@tillsc/progressive-web-components",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Server-first web components.",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
- "files": [
8
- "dist"
9
- ],
7
+ "publishConfig": { "access": "public" },
8
+ "files": [ "dist", "src"],
10
9
  "exports": {
11
10
  "./all": "./dist/all.js",
12
11
  "./all-bs5": "./dist/all-bs5.js",
@@ -0,0 +1,53 @@
1
+ import {PwcElement} from "./pwc-element.js";
2
+
3
+ /**
4
+ * Children observer element.
5
+ *
6
+ * Calls onChildrenChanged() on connect and on every subsequent child mutation.
7
+ *
8
+ * Modes (static observeMode):
9
+ * - "children": direct children only
10
+ * - "tree": full subtree
11
+ */
12
+ export class PwcChildrenObserverElement extends PwcElement {
13
+ static observeMode = "children"; // "children" | "tree"
14
+
15
+ connectedCallback() {
16
+ if (this._connected) return;
17
+ super.connectedCallback();
18
+ this._startChildrenObserver();
19
+ }
20
+
21
+ disconnectedCallback() {
22
+ this._stopChildrenObserver();
23
+ super.disconnectedCallback();
24
+ }
25
+
26
+ onChildrenChanged(_mutations) {}
27
+
28
+ /** Run fn() without triggering onChildrenChanged for the resulting DOM mutations. */
29
+ _withoutChildrenChangedNotification(fn) {
30
+ fn();
31
+ this._childrenObserver?.takeRecords();
32
+ }
33
+
34
+ _startChildrenObserver() {
35
+ const mode = this.constructor.observeMode || "children";
36
+ const subtree = mode === "tree";
37
+
38
+ this._childrenObserver = new MutationObserver((mutations) => {
39
+ if (!this._connected) return;
40
+ this.onChildrenChanged(mutations);
41
+ });
42
+
43
+ this._childrenObserver.observe(this, { childList: true, subtree });
44
+
45
+ this.onChildrenChanged([]);
46
+ }
47
+
48
+ _stopChildrenObserver() {
49
+ if (!this._childrenObserver) return;
50
+ this._childrenObserver.disconnect();
51
+ this._childrenObserver = null;
52
+ }
53
+ }
@@ -0,0 +1,76 @@
1
+ /**
2
+ * Base class for progressive-web-components.
3
+ *
4
+ * Responsibilities:
5
+ * - Ensure idempotent lifecycle handling
6
+ * - Declaratively bind and unbind host-level DOM events
7
+ * - Provide a consistent cleanup hook
8
+ *
9
+ * This is intentionally minimal.
10
+ * No rendering, no templating, no magic.
11
+ */
12
+ export class PwcElement extends HTMLElement {
13
+ /**
14
+ * List of DOM event types to bind on the host element.
15
+ * Subclasses may override.
16
+ *
17
+ * Example:
18
+ * static events = ["click", "input"];
19
+ */
20
+ static events = [];
21
+
22
+ static registerCss(cssText) {
23
+ const sheet = new CSSStyleSheet();
24
+ sheet.replaceSync(cssText);
25
+ document.adoptedStyleSheets = [...document.adoptedStyleSheets, sheet];
26
+ }
27
+
28
+ connectedCallback() {
29
+ if (this._connected) return;
30
+ this._connected = true;
31
+
32
+ this._bindEvents();
33
+ }
34
+
35
+ disconnectedCallback() {
36
+ if (!this._connected) return;
37
+ this._connected = false;
38
+
39
+ this._unbindEvents();
40
+ this.onDisconnect();
41
+ }
42
+
43
+ /**
44
+ * Optional cleanup hook for subclasses.
45
+ */
46
+ onDisconnect() {}
47
+
48
+ /**
49
+ * Bind declared events using the handleEvent pattern.
50
+ */
51
+ _bindEvents() {
52
+ const events = this.constructor.events ?? [];
53
+ for (const type of events) {
54
+ this.addEventListener(type, this);
55
+ }
56
+ }
57
+
58
+ /**
59
+ * Unbind all previously declared events.
60
+ */
61
+ _unbindEvents() {
62
+ const events = this.constructor.events ?? [];
63
+ for (const type of events) {
64
+ this.removeEventListener(type, this);
65
+ }
66
+ }
67
+
68
+ /**
69
+ * Default event handler.
70
+ * Subclasses are expected to override this method
71
+ * and route events as needed.
72
+ */
73
+ handleEvent(_event) {
74
+ // intentionally empty
75
+ }
76
+ }
@@ -0,0 +1,56 @@
1
+ import {PwcElement} from "./pwc-element.js";
2
+
3
+ /**
4
+ * Sentinel init element.
5
+ *
6
+ * Calls onConnect() once per connection, when a sentinel appears in the light DOM.
7
+ * Uses a MutationObserver only until ready.
8
+ *
9
+ * Subclasses may override sentinelSelector().
10
+ */
11
+ export class PwcSentinelInitElement extends PwcElement {
12
+ static sentinelSelector = "pwc-sentinel, [data-pwc-sentinel]";
13
+
14
+ connectedCallback() {
15
+ if (this._connected) return;
16
+ super.connectedCallback();
17
+
18
+ if (this._hasSentinel()) {
19
+ this.onConnect();
20
+ return;
21
+ }
22
+
23
+ this._sentinelObserver = new MutationObserver(() => {
24
+ if (!this._connected) return;
25
+ if (!this._hasSentinel()) return;
26
+
27
+ this._stopSentinelObserver();
28
+ this.onConnect();
29
+ });
30
+
31
+ // subtree:true so the sentinel can be nested (common with templates/partials)
32
+ this._sentinelObserver.observe(this, { childList: true, subtree: true });
33
+ }
34
+
35
+ disconnectedCallback() {
36
+ this._stopSentinelObserver();
37
+ super.disconnectedCallback();
38
+ }
39
+
40
+ /**
41
+ * Hook for subclasses.
42
+ * Called once per connection, when the sentinel is present.
43
+ */
44
+ onConnect() {}
45
+
46
+ _hasSentinel() {
47
+ const selector = this.constructor.sentinelSelector || PwcSentinelInitElement.sentinelSelector;
48
+ return Boolean(this.querySelector(selector));
49
+ }
50
+
51
+ _stopSentinelObserver() {
52
+ if (!this._sentinelObserver) return;
53
+ this._sentinelObserver.disconnect();
54
+ this._sentinelObserver = null;
55
+ }
56
+ }
@@ -0,0 +1,25 @@
1
+ import {PwcElement} from "./pwc-element.js";
2
+
3
+ /**
4
+ * Simple init element.
5
+ *
6
+ * Calls onConnect() once per connection, deferred to a microtask.
7
+ * Use this when a microtask is sufficient to access server-rendered children.
8
+ */
9
+ export class PwcSimpleInitElement extends PwcElement {
10
+ connectedCallback() {
11
+ if (this._connected) return;
12
+ super.connectedCallback();
13
+
14
+ queueMicrotask(() => {
15
+ if (!this._connected) return;
16
+ this.onConnect();
17
+ });
18
+ }
19
+
20
+ /**
21
+ * Hook for subclasses.
22
+ * Called once per connection, after microtask deferral.
23
+ */
24
+ onConnect() {}
25
+ }
@@ -0,0 +1,9 @@
1
+ export function ensureId(el, prefix = "pwc") {
2
+ if (!el.id) el.id = `${prefix}-${Math.random().toString(36).slice(2)}`;
3
+ return el.id;
4
+ }
5
+
6
+ export function defineOnce(name, classDef) {
7
+ if (customElements.get(name)) return;
8
+ customElements.define(name, classDef);
9
+ }
@@ -0,0 +1,47 @@
1
+ # Dialog-Opener — Internals
2
+
3
+ ## Architecture
4
+
5
+ - `findOrCreateDialog(src)` — creates/reuses a `<pwc-modal-dialog>` (or `-bs5`), opens it,
6
+ places the iframe, and wires up the `this.dialog` / `this.modal` adapter.
7
+
8
+ The base class never touches DOM rendering directly. Variants own the dialog creation and
9
+ provide a uniform adapter interface (`this.modal.show()` / `this.modal.hide()`).
10
+
11
+ ## Flow: link click to dialog
12
+
13
+ 1. Click on an `<a>` inside the component is intercepted (`handleEvent`)
14
+ 2. `prepareIFrameLink()` builds the iframe URL:
15
+ - collects `input` values as `default` query param
16
+ - appends `_layout=false`
17
+ 3. `findOrCreateDialog(src)` (variant hook) creates the modal and iframe
18
+ 4. `enhanceIFrame()` waits for the iframe `load` event, then calls `iFrameLoad()`
19
+ 5. `iFrameLoad()` checks the iframe URL:
20
+ - If `dialog_finished_with` is present → close dialog, trigger reload or navigation
21
+ - Otherwise → run `moveElementsToOuterActions()`, show iframe
22
+
23
+ ## Move-out mechanism
24
+
25
+ When `move-out` is set, buttons are **cloned** from the iframe document into the dialog footer.
26
+ The original buttons are hidden. Clicking a cloned button triggers `click()` on the original
27
+ inside the iframe, then hides the iframe (to show a loading state while the form submits).
28
+
29
+ ## Local reload
30
+
31
+ When the dialog completes and `local-reload` is set:
32
+
33
+ 1. The completion URL is fetched via `fetch()`
34
+ 2. The response HTML is parsed with `DOMParser`
35
+ 3. The element matching `this.id` is extracted from the response
36
+ 4. Its children replace the current component's children
37
+ 5. Optionally: `history.pushState` / `replaceState` updates the URL
38
+ 6. Optionally: inline `<script>` tags are re-executed (cloned into new elements)
39
+ 7. A `pwc-dialog-opener:local-reload` custom event is dispatched
40
+
41
+ If any step fails, it falls back to full page navigation.
42
+
43
+ ## Modal adapter pattern
44
+
45
+ The base class expects `this.modal` with `.show()` and `.hide()`. Since both variants use
46
+ `<pwc-modal-dialog>` (which is already open by the time `findOrCreateDialog` returns),
47
+ `show()` is a no-op and `hide()` delegates to `modalDialog.close()`.
@@ -0,0 +1,145 @@
1
+ # `<pwc-dialog-opener>`
2
+
3
+ Server-first dialog opener web component.
4
+
5
+ `<pwc-dialog-opener>` enhances existing links to open their targets inside a modal dialog.
6
+ It is designed to work with server-rendered HTML and minimal JavaScript.
7
+
8
+ The component does **not** use Shadow DOM and relies on stable, explicit markup hooks.
9
+
10
+ ---
11
+
12
+ ## Basic usage
13
+
14
+ ```html
15
+ <pwc-dialog-opener>
16
+ <a href="/path/to/page">Open</a>
17
+ </pwc-dialog-opener>
18
+ ```
19
+
20
+ Clicking the link opens the target URL inside a dialog using an `<iframe>`.
21
+
22
+ ---
23
+
24
+ ## Attributes
25
+
26
+ ### `close`
27
+ Text for the close button inside the dialog.
28
+
29
+ ```html
30
+ <pwc-dialog-opener close="Cancel">
31
+ ```
32
+
33
+ ### `move-out`
34
+ Moves action buttons from the iframe content into the dialog footer.
35
+
36
+ Supported magic values:
37
+ - `submit`
38
+ - `primary` – **Bootstrap 5 variant only**
39
+
40
+ ```html
41
+ <pwc-dialog-opener move-out="submit">
42
+ ```
43
+
44
+ ### `local-reload`
45
+ Enables server-side fragment reload instead of full page navigation when the dialog
46
+ signals completion.
47
+
48
+ Supported tokens (space-separated):
49
+ - `with-scripts` – re-executes inline scripts in the replaced fragment
50
+ - `replace-url` – updates the browser URL via `history.replaceState`
51
+ - `push-url` – updates the browser URL via `history.pushState`
52
+
53
+ ```html
54
+ <pwc-dialog-opener id="editor" local-reload="with-scripts replace-url">
55
+ ```
56
+
57
+ The element **must have an `id`** for `local-reload` to work.
58
+
59
+ ---
60
+
61
+ ## Dialog completion protocol
62
+
63
+ The iframe content is considered "finished" when its URL contains the query parameter:
64
+
65
+ ```
66
+ dialog_finished_with=ok
67
+ ```
68
+
69
+ When detected:
70
+ - the dialog closes
71
+ - local reload is attempted (if enabled)
72
+ - otherwise a full page navigation is performed
73
+
74
+ ---
75
+
76
+ ## `default` query parameter
77
+
78
+ When opening the iframe URL, the component collects values from the **first-level inputs inside**
79
+ `<pwc-dialog-opener>` and appends them as a `default` query parameter.
80
+
81
+ This mechanism is essential for server-driven workflows where the dialog is used as a
82
+ fallback or completion step.
83
+
84
+ A common example is an autocomplete or reference selector:
85
+ - the user types a value
86
+ - no matching target exists yet
87
+ - the dialog opens a “create” form
88
+ - the previously entered value is forwarded as a default
89
+
90
+ Current behavior:
91
+ - reads the values of all `input` elements inside the component
92
+ - ignores empty values
93
+ - concatenates values from multiple inputs into a single `default` query parameter
94
+
95
+ ```html
96
+ <pwc-dialog-opener>
97
+ <input name="search" value="bar">
98
+ <a href="/teams/new">Open</a>
99
+ </pwc-dialog-opener>
100
+ ```
101
+
102
+ The iframe will be opened with:
103
+
104
+ ```
105
+ /teams/new?default=bar&_layout=false
106
+ ```
107
+
108
+ ---
109
+
110
+ ## Styling
111
+
112
+ The vanilla variant uses the native `<dialog>` element.
113
+
114
+ Key CSS hooks:
115
+ - `.pwc-dialog-opener-modal`
116
+ - `.pwc-dialog-opener-body`
117
+ - `.pwc-dialog-opener-footer`
118
+ - `.pwc-dialog-opener-close`
119
+
120
+ Height can be controlled via CSS variables on the host element:
121
+
122
+ ```css
123
+ pwc-dialog-opener {
124
+ --pwc-dialog-opener-height: 550px;
125
+ }
126
+ ```
127
+
128
+ ---
129
+
130
+ ## Bootstrap 5 variant
131
+
132
+ A Bootstrap 5 styled variant is provided with the same API:
133
+
134
+ ```html
135
+ <pwc-dialog-opener-bs5>
136
+ <a href="/path/to/page">Open</a>
137
+ </pwc-dialog-opener-bs5>
138
+ ```
139
+
140
+ Notes:
141
+ - Same attributes and behavior as the vanilla component
142
+ - Uses Bootstrap modal markup and classes
143
+ - Requires Bootstrap 5 JavaScript and CSS to be present
144
+
145
+ ---
@@ -0,0 +1,226 @@
1
+ import { PwcElement } from "../core/pwc-element.js";
2
+
3
+ export class BaseDialogOpener extends PwcElement {
4
+ static events = ["click"];
5
+
6
+ handleEvent(e) {
7
+ if (e.type !== "click") return;
8
+ if (e.defaultPrevented) return;
9
+
10
+ const link = e.target.closest("a");
11
+ if (!link || !this.contains(link)) return;
12
+
13
+ e.preventDefault();
14
+
15
+ if (this.hasAttribute("local-reload") && !this.id) {
16
+ console.warn("<pwc-dialog-opener> has local-reload attribute but no id", this);
17
+ }
18
+
19
+ const href = link.getAttribute("href");
20
+ if (!href) return;
21
+
22
+ this.open(href);
23
+ }
24
+
25
+ open(href) {
26
+ const src = this.prepareIFrameLink(href);
27
+ this.findOrCreateDialog(src);
28
+ this.enhanceIFrame().then(() => this.modal.show());
29
+ }
30
+
31
+ prepareIFrameLink(src) {
32
+ const s = new URL(src, document.location.href);
33
+
34
+ const defaultValues = [...this.querySelectorAll("input")]
35
+ .map((input) => {
36
+ if (input.value) return input.value;
37
+ return null;
38
+ })
39
+ .filter((item) => item !== null);
40
+
41
+ if (defaultValues.length > 0) {
42
+ s.searchParams.set("default", defaultValues.join(","));
43
+ }
44
+
45
+ s.searchParams.set("_layout", false);
46
+ return s.toString();
47
+ }
48
+
49
+ // Variant hook: must set this.dialog and this.modal
50
+ // eslint-disable-next-line no-unused-vars
51
+ findOrCreateDialog(_src) {
52
+ throw new Error("BaseDialogOpener: findOrCreateDialog(src) must be implemented by a variant");
53
+ }
54
+
55
+ createIFrame(src) {
56
+ const iframe = document.createElement("iframe");
57
+ iframe.src = src;
58
+
59
+ iframe.style.width = "100%";
60
+ iframe.style.height = getComputedStyle(this).getPropertyValue("--pwc-dialog-opener-height").trim() || "550px";
61
+ iframe.style.display = "none";
62
+
63
+ return iframe;
64
+ }
65
+
66
+ enhanceIFrame() {
67
+ this.iframe = this.dialog.querySelector("iframe");
68
+ return new Promise((resolve) => {
69
+ this.iframe.addEventListener("load",
70
+ (e) => this.iFrameLoad(e).then(resolve));
71
+ });
72
+ }
73
+
74
+ async iFrameLoad(_e) {
75
+ let uri;
76
+ try {
77
+ uri = new URL(this.iframe.contentWindow.location);
78
+ } catch (e) {
79
+ throw new Error(`<pwc-dialog-opener> cannot access iframe location (cross-origin?): ${e.message}`);
80
+ }
81
+
82
+ if (uri.searchParams.has("dialog_finished_with")) {
83
+ this.modal.hide();
84
+
85
+ uri.searchParams.delete("_layout");
86
+ uri.searchParams.set("dummy", Math.floor(Math.random() * 100000));
87
+
88
+ const localReloadWorked = await this.tryLocalReload(uri);
89
+ if (!localReloadWorked) {
90
+ window.location.href = uri.toString();
91
+ }
92
+ return;
93
+ }
94
+
95
+ this.moveElementsToOuterActions();
96
+ this.iframe.style.display = "unset";
97
+ }
98
+
99
+ async tryLocalReload(newUri) {
100
+ const currentUri = new URL(window.location.href);
101
+ if (
102
+ currentUri.hostname !== newUri.hostname ||
103
+ currentUri.pathname !== newUri.pathname ||
104
+ currentUri.protocol !== newUri.protocol
105
+ ) {
106
+ 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.`);
107
+ }
108
+
109
+ if (this.hasAttribute("local-reload") && this.id) {
110
+ const localReloadOptionTokens = document.createElement("div").classList;
111
+ if (this.hasAttribute("local-reload")) localReloadOptionTokens.add(...this.getAttribute("local-reload").split(/\s+/));
112
+ const localReloadOptions = {
113
+ replaceUrl: localReloadOptionTokens.contains("replace-url"),
114
+ pushUrl: localReloadOptionTokens.contains("push-url"),
115
+ withScripts: localReloadOptionTokens.contains("with-scripts")
116
+ };
117
+
118
+ newUri.searchParams.set("local_reload", this.id);
119
+
120
+ const res = await fetch(newUri);
121
+ if (res.ok) {
122
+ const html = await res.text();
123
+ const newDocument = new DOMParser().parseFromString(html, "text/html");
124
+ const fragment = newDocument.getElementById(this.id);
125
+
126
+ if (fragment) {
127
+ this.replaceChildren(...fragment.childNodes);
128
+
129
+ // Optional History API update
130
+ if (localReloadOptions.replaceUrl || localReloadOptions.pushUrl) {
131
+
132
+ if (localReloadOptions.pushUrl) {
133
+ history.pushState(null, "", newUri);
134
+ }
135
+ else if (localReloadOptions.replaceUrl) {
136
+ history.replaceState(null, "", newUri);
137
+ }
138
+ }
139
+
140
+ if (localReloadOptions.withScripts) {
141
+ this.executeInlineScripts(this);
142
+ }
143
+
144
+ this.dispatchEvent(
145
+ new CustomEvent("pwc-dialog-opener:local-reload", {
146
+ bubbles: true,
147
+ detail: { url: newUri.toString() }
148
+ })
149
+ );
150
+
151
+ return true;
152
+ }
153
+ console.log("local-reload not possible, falling back to full reload");
154
+ }
155
+ }
156
+
157
+ return false;
158
+ }
159
+
160
+ executeInlineScripts(root) {
161
+ console.log("Executing inline scripts in local-reload fragment", root);
162
+ const scripts = Array.from(root.querySelectorAll("script"));
163
+
164
+ for (const old of scripts) {
165
+ if (old.src) {
166
+ console.warn("Ignoring external script in local-reload fragment:", old.src);
167
+ old.remove();
168
+ continue;
169
+ }
170
+
171
+ // Re-create script to execute it
172
+ const s = document.createElement("script");
173
+ // preserve type if present (default is classic)
174
+ if (old.type) s.type = old.type;
175
+ if (old.noModule) s.noModule = true;
176
+
177
+ s.textContent = old.textContent || "";
178
+
179
+ old.replaceWith(s);
180
+ }
181
+ }
182
+
183
+ moveElementsToOuterActions() {
184
+ if (!this.getAttribute("move-out")) return;
185
+
186
+ const iframeDoc = this.iframe.contentWindow.document;
187
+ if (!iframeDoc) return;
188
+
189
+ let buttonContainer = this.dialog.querySelector("dialog-opener-buttons");
190
+ if (!buttonContainer) {
191
+ buttonContainer = document.createElement("dialog-opener-buttons");
192
+ this.dialog.querySelector(".pwc-dialog-opener-actions").prepend(buttonContainer);
193
+ } else {
194
+ buttonContainer.innerHTML = "";
195
+ }
196
+
197
+ const elements = iframeDoc.querySelectorAll(this._moveOutSelector());
198
+ for (let i = 0; i < elements.length; i++) {
199
+ const btn = elements[i];
200
+
201
+ const outerBtn = document.createElement(btn.tagName);
202
+ for (const attr of btn.attributes) {
203
+ outerBtn.setAttribute(attr.name, attr.value);
204
+ }
205
+ outerBtn.innerHTML = btn.innerHTML;
206
+
207
+ outerBtn.addEventListener("click", () => {
208
+ this.iframe.style.display = "none";
209
+ btn.click();
210
+ });
211
+
212
+ buttonContainer.append(outerBtn);
213
+
214
+ btn.style.visibility = "hidden";
215
+ btn.style.display = "none";
216
+ }
217
+ }
218
+
219
+ _moveOutSelector() {
220
+ let selector = this.getAttribute("move-out");
221
+ if (selector === "submit") {
222
+ selector = "button[type=submit], input[type=submit]";
223
+ }
224
+ return selector;
225
+ }
226
+ }