@kubex/zinc 1.1.85 → 1.1.86

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.
@@ -13,6 +13,17 @@
13
13
  }
14
14
  ]
15
15
  },
16
+ {
17
+ "name": "zn-action-bar",
18
+ "description": "Short summary of the component's intended use.\n---\n\n\n### **Events:**\n - **zn-event-name** - Emitted as an example.\n\n### **Slots:**\n - _default_ - The default slot.\n- **example** - An example slot.\n\n### **CSS Properties:**\n - **--example** - An example CSS custom property. _(default: undefined)_\n\n### **CSS Parts:**\n - **base** - The component's base wrapper.",
19
+ "attributes": [],
20
+ "references": [
21
+ {
22
+ "name": "Documentation",
23
+ "url": "https://zinc.style/components/action-bar"
24
+ }
25
+ ]
26
+ },
16
27
  {
17
28
  "name": "zn-alert",
18
29
  "description": "Short summary of the component's intended use.\n---\n\n\n### **Events:**\n - **zn-event-name** - Emitted as an example.\n\n### **Slots:**\n - _default_ - The default slot.\n- **example** - An example slot.\n\n### **CSS Properties:**\n - **--example** - An example CSS custom property. _(default: undefined)_\n\n### **CSS Parts:**\n - **base** - The component's base wrapper.",
@@ -53,17 +64,6 @@
53
64
  }
54
65
  ]
55
66
  },
56
- {
57
- "name": "zn-action-bar",
58
- "description": "Short summary of the component's intended use.\n---\n\n\n### **Events:**\n - **zn-event-name** - Emitted as an example.\n\n### **Slots:**\n - _default_ - The default slot.\n- **example** - An example slot.\n\n### **CSS Properties:**\n - **--example** - An example CSS custom property. _(default: undefined)_\n\n### **CSS Parts:**\n - **base** - The component's base wrapper.",
59
- "attributes": [],
60
- "references": [
61
- {
62
- "name": "Documentation",
63
- "url": "https://zinc.style/components/action-bar"
64
- }
65
- ]
66
- },
67
67
  {
68
68
  "name": "zn-animated-button",
69
69
  "description": "\n---\n\n\n### **Methods:**\n - **purchase(): _void_** - Programmatically trigger the purchase flow\n- **setSuccess(): _void_** - Set the button to success state manually\n- **setFailure(message): _void_** - Set the button to failure state manually with optional error message\n- **reset(): _void_** - Reset the button to idle state",
@@ -138,21 +138,6 @@
138
138
  }
139
139
  ]
140
140
  },
141
- {
142
- "name": "zn-bulk-actions",
143
- "description": "Short summary of the component's intended use.\n---\n\n\n### **Events:**\n - **zn-event-name** - Emitted as an example.\n\n### **Slots:**\n - _default_ - The default slot.\n- **example** - An example slot.\n\n### **CSS Properties:**\n - **--example** - An example CSS custom property. _(default: undefined)_\n\n### **CSS Parts:**\n - **base** - The component's base wrapper.",
144
- "attributes": [
145
- { "name": "name", "values": [] },
146
- { "name": "value", "values": [{ "name": "PropertyKey" }] },
147
- { "name": "actions", "values": [{ "name": "BulkActionData" }] }
148
- ],
149
- "references": [
150
- {
151
- "name": "Documentation",
152
- "url": "https://zinc.style/components/bulk-actions"
153
- }
154
- ]
155
- },
156
141
  {
157
142
  "name": "zn-button",
158
143
  "description": "Buttons represent actions that are available to the user.\n---\n\n\n### **Events:**\n - **zn-blur** - Emitted when the button loses focus.\n- **zn-focus** - Emitted when the button gains focus.\n- **zn-invalid** - Emitted when the form control has been checked for validity and its constraints aren't satisfied.\n\n### **Slots:**\n - _default_ - The button's label.\n- **prefix** - A presentational prefix icon or similar element.\n- **suffix** - A presentational suffix icon or similar element.\n- **cancel** - Slot for custom cancel button/content when autoClick is active.\n\n### **CSS Parts:**\n - **base** - The component's base wrapper.\n- **prefix** - The container that wraps the prefix.\n- **label** - The button's label.\n- **suffix** - The container that wraps the suffix.\n- **caret** - The button's caret icon, an `<zn-icon>` element.\n- **spinner** - The spinner that shows when the button is in the loading state.",
@@ -286,6 +271,21 @@
286
271
  }
287
272
  ]
288
273
  },
274
+ {
275
+ "name": "zn-bulk-actions",
276
+ "description": "Short summary of the component's intended use.\n---\n\n\n### **Events:**\n - **zn-event-name** - Emitted as an example.\n\n### **Slots:**\n - _default_ - The default slot.\n- **example** - An example slot.\n\n### **CSS Properties:**\n - **--example** - An example CSS custom property. _(default: undefined)_\n\n### **CSS Parts:**\n - **base** - The component's base wrapper.",
277
+ "attributes": [
278
+ { "name": "name", "values": [] },
279
+ { "name": "value", "values": [{ "name": "PropertyKey" }] },
280
+ { "name": "actions", "values": [{ "name": "BulkActionData" }] }
281
+ ],
282
+ "references": [
283
+ {
284
+ "name": "Documentation",
285
+ "url": "https://zinc.style/components/bulk-actions"
286
+ }
287
+ ]
288
+ },
289
289
  {
290
290
  "name": "zn-button-group",
291
291
  "description": "Short summary of the component's intended use.\n---\n\n\n### **Events:**\n - **zn-event-name** - Emitted as an example.\n\n### **Slots:**\n - _default_ - The default slot.\n\n### **CSS Properties:**\n - **--grow** - Use flex-grow to fill available space. _(default: undefined)_\n- **--start** - Justify content at the start of the flex space. _(default: undefined)_\n\n### **CSS Parts:**\n - **base** - The component's base wrapper.",
@@ -3033,7 +3033,7 @@
3033
3033
  },
3034
3034
  {
3035
3035
  "name": "refresh-on",
3036
- "description": "Selector for forms whose saves are left to the shell but should still\nrefresh the preview. These are never intercepted: the shell submits them\n(so its own response handling — alerts, refreshes — runs as normal) and the\npreview re-fetches its config once the shell reports the save complete.\nSet empty to disable.",
3036
+ "description": "Selector for forms whose saves are left to the shell but should still\nrefresh the preview. These are never intercepted: the shell submits them\n(so its own response handling — alerts, refreshes — runs as normal) and the\npreview re-fetches its config once the shell reports the save complete.\nMatched by delegation on the shell's bubbled `complete` event rather than\nby attaching to the forms themselves, so a save anywhere on the page\nrefreshes the preview — including forms in a different DOM root, e.g. a\npage-level form saved while the preview sits inside a tab panel's shadow\nroot (a page's Template select lives on one tab, its preview on another).\nSet empty to disable.",
3037
3037
  "values": []
3038
3038
  },
3039
3039
  {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://raw.githubusercontent.com/JetBrains/web-types/master/schema/web-types.json",
3
3
  "name": "@kubex/zinc",
4
- "version": "1.1.85",
4
+ "version": "1.1.86",
5
5
  "description-markup": "markdown",
6
6
  "contributions": {
7
7
  "html": {
@@ -15,6 +15,28 @@
15
15
  "events": [],
16
16
  "js": { "properties": [], "events": [] }
17
17
  },
18
+ {
19
+ "name": "zn-action-bar",
20
+ "description": "Short summary of the component's intended use.\n---\n\n\n### **Events:**\n - **zn-event-name** - Emitted as an example.\n\n### **Slots:**\n - _default_ - The default slot.\n- **example** - An example slot.\n\n### **CSS Properties:**\n - **--example** - An example CSS custom property. _(default: undefined)_\n\n### **CSS Parts:**\n - **base** - The component's base wrapper.",
21
+ "doc-url": "",
22
+ "attributes": [],
23
+ "slots": [
24
+ { "name": "", "description": "The default slot." },
25
+ { "name": "example", "description": "An example slot." }
26
+ ],
27
+ "events": [
28
+ { "name": "zn-event-name", "description": "Emitted as an example." }
29
+ ],
30
+ "js": {
31
+ "properties": [],
32
+ "events": [
33
+ {
34
+ "name": "zn-event-name",
35
+ "description": "Emitted as an example."
36
+ }
37
+ ]
38
+ }
39
+ },
18
40
  {
19
41
  "name": "zn-alert",
20
42
  "description": "Short summary of the component's intended use.\n---\n\n\n### **Events:**\n - **zn-event-name** - Emitted as an example.\n\n### **Slots:**\n - _default_ - The default slot.\n- **example** - An example slot.\n\n### **CSS Properties:**\n - **--example** - An example CSS custom property. _(default: undefined)_\n\n### **CSS Parts:**\n - **base** - The component's base wrapper.",
@@ -83,28 +105,6 @@
83
105
  ]
84
106
  }
85
107
  },
86
- {
87
- "name": "zn-action-bar",
88
- "description": "Short summary of the component's intended use.\n---\n\n\n### **Events:**\n - **zn-event-name** - Emitted as an example.\n\n### **Slots:**\n - _default_ - The default slot.\n- **example** - An example slot.\n\n### **CSS Properties:**\n - **--example** - An example CSS custom property. _(default: undefined)_\n\n### **CSS Parts:**\n - **base** - The component's base wrapper.",
89
- "doc-url": "",
90
- "attributes": [],
91
- "slots": [
92
- { "name": "", "description": "The default slot." },
93
- { "name": "example", "description": "An example slot." }
94
- ],
95
- "events": [
96
- { "name": "zn-event-name", "description": "Emitted as an example." }
97
- ],
98
- "js": {
99
- "properties": [],
100
- "events": [
101
- {
102
- "name": "zn-event-name",
103
- "description": "Emitted as an example."
104
- }
105
- ]
106
- }
107
- },
108
108
  {
109
109
  "name": "zn-animated-button",
110
110
  "description": "\n---\n\n\n### **Methods:**\n - **purchase(): _void_** - Programmatically trigger the purchase flow\n- **setSuccess(): _void_** - Set the button to success state manually\n- **setFailure(message): _void_** - Set the button to failure state manually with optional error message\n- **reset(): _void_** - Reset the button to idle state",
@@ -250,44 +250,6 @@
250
250
  "events": []
251
251
  }
252
252
  },
253
- {
254
- "name": "zn-bulk-actions",
255
- "description": "Short summary of the component's intended use.\n---\n\n\n### **Events:**\n - **zn-event-name** - Emitted as an example.\n\n### **Slots:**\n - _default_ - The default slot.\n- **example** - An example slot.\n\n### **CSS Properties:**\n - **--example** - An example CSS custom property. _(default: undefined)_\n\n### **CSS Parts:**\n - **base** - The component's base wrapper.",
256
- "doc-url": "",
257
- "attributes": [
258
- { "name": "name", "value": { "type": "string" } },
259
- { "name": "value", "value": { "type": "PropertyKey" } },
260
- {
261
- "name": "actions",
262
- "value": { "type": "BulkActionData", "default": "[]" }
263
- }
264
- ],
265
- "slots": [
266
- { "name": "", "description": "The default slot." },
267
- { "name": "example", "description": "An example slot." }
268
- ],
269
- "events": [
270
- { "name": "zn-event-name", "description": "Emitted as an example." }
271
- ],
272
- "js": {
273
- "properties": [
274
- { "name": "container", "type": "HTMLDivElement" },
275
- { "name": "addRule", "type": "ZnSelect" },
276
- { "name": "input", "type": "HTMLInputElement" },
277
- { "name": "name", "type": "string" },
278
- { "name": "value", "type": "PropertyKey" },
279
- { "name": "actions", "type": "BulkActionData" },
280
- { "name": "validationMessage", "type": "string" },
281
- { "name": "validity", "type": "ValidityState" }
282
- ],
283
- "events": [
284
- {
285
- "name": "zn-event-name",
286
- "description": "Emitted as an example."
287
- }
288
- ]
289
- }
290
- },
291
253
  {
292
254
  "name": "zn-button",
293
255
  "description": "Buttons represent actions that are available to the user.\n---\n\n\n### **Events:**\n - **zn-blur** - Emitted when the button loses focus.\n- **zn-focus** - Emitted when the button gains focus.\n- **zn-invalid** - Emitted when the form control has been checked for validity and its constraints aren't satisfied.\n\n### **Slots:**\n - _default_ - The button's label.\n- **prefix** - A presentational prefix icon or similar element.\n- **suffix** - A presentational suffix icon or similar element.\n- **cancel** - Slot for custom cancel button/content when autoClick is active.\n\n### **CSS Parts:**\n - **base** - The component's base wrapper.\n- **prefix** - The container that wraps the prefix.\n- **label** - The button's label.\n- **suffix** - The container that wraps the suffix.\n- **caret** - The button's caret icon, an `<zn-icon>` element.\n- **spinner** - The spinner that shows when the button is in the loading state.",
@@ -554,6 +516,44 @@
554
516
  ]
555
517
  }
556
518
  },
519
+ {
520
+ "name": "zn-bulk-actions",
521
+ "description": "Short summary of the component's intended use.\n---\n\n\n### **Events:**\n - **zn-event-name** - Emitted as an example.\n\n### **Slots:**\n - _default_ - The default slot.\n- **example** - An example slot.\n\n### **CSS Properties:**\n - **--example** - An example CSS custom property. _(default: undefined)_\n\n### **CSS Parts:**\n - **base** - The component's base wrapper.",
522
+ "doc-url": "",
523
+ "attributes": [
524
+ { "name": "name", "value": { "type": "string" } },
525
+ { "name": "value", "value": { "type": "PropertyKey" } },
526
+ {
527
+ "name": "actions",
528
+ "value": { "type": "BulkActionData", "default": "[]" }
529
+ }
530
+ ],
531
+ "slots": [
532
+ { "name": "", "description": "The default slot." },
533
+ { "name": "example", "description": "An example slot." }
534
+ ],
535
+ "events": [
536
+ { "name": "zn-event-name", "description": "Emitted as an example." }
537
+ ],
538
+ "js": {
539
+ "properties": [
540
+ { "name": "container", "type": "HTMLDivElement" },
541
+ { "name": "addRule", "type": "ZnSelect" },
542
+ { "name": "input", "type": "HTMLInputElement" },
543
+ { "name": "name", "type": "string" },
544
+ { "name": "value", "type": "PropertyKey" },
545
+ { "name": "actions", "type": "BulkActionData" },
546
+ { "name": "validationMessage", "type": "string" },
547
+ { "name": "validity", "type": "ValidityState" }
548
+ ],
549
+ "events": [
550
+ {
551
+ "name": "zn-event-name",
552
+ "description": "Emitted as an example."
553
+ }
554
+ ]
555
+ }
556
+ },
557
557
  {
558
558
  "name": "zn-button-group",
559
559
  "description": "Short summary of the component's intended use.\n---\n\n\n### **Events:**\n - **zn-event-name** - Emitted as an example.\n\n### **Slots:**\n - _default_ - The default slot.\n\n### **CSS Properties:**\n - **--grow** - Use flex-grow to fill available space. _(default: undefined)_\n- **--start** - Justify content at the start of the flex space. _(default: undefined)_\n\n### **CSS Parts:**\n - **base** - The component's base wrapper.",
@@ -7089,7 +7089,7 @@
7089
7089
  },
7090
7090
  {
7091
7091
  "name": "refresh-on",
7092
- "description": "Selector for forms whose saves are left to the shell but should still\nrefresh the preview. These are never intercepted: the shell submits them\n(so its own response handling — alerts, refreshes — runs as normal) and the\npreview re-fetches its config once the shell reports the save complete.\nSet empty to disable.",
7092
+ "description": "Selector for forms whose saves are left to the shell but should still\nrefresh the preview. These are never intercepted: the shell submits them\n(so its own response handling — alerts, refreshes — runs as normal) and the\npreview re-fetches its config once the shell reports the save complete.\nMatched by delegation on the shell's bubbled `complete` event rather than\nby attaching to the forms themselves, so a save anywhere on the page\nrefreshes the preview — including forms in a different DOM root, e.g. a\npage-level form saved while the preview sits inside a tab panel's shadow\nroot (a page's Template select lives on one tab, its preview on another).\nSet empty to disable.",
7093
7093
  "value": { "type": "string", "default": "'form'" }
7094
7094
  },
7095
7095
  {
@@ -7158,7 +7158,7 @@
7158
7158
  },
7159
7159
  {
7160
7160
  "name": "refreshOn",
7161
- "description": "Selector for forms whose saves are left to the shell but should still\nrefresh the preview. These are never intercepted: the shell submits them\n(so its own response handling — alerts, refreshes — runs as normal) and the\npreview re-fetches its config once the shell reports the save complete.\nSet empty to disable.",
7161
+ "description": "Selector for forms whose saves are left to the shell but should still\nrefresh the preview. These are never intercepted: the shell submits them\n(so its own response handling — alerts, refreshes — runs as normal) and the\npreview re-fetches its config once the shell reports the save complete.\nMatched by delegation on the shell's bubbled `complete` event rather than\nby attaching to the forms themselves, so a save anywhere on the page\nrefreshes the preview — including forms in a different DOM root, e.g. a\npage-level form saved while the preview sits inside a tab panel's shadow\nroot (a page's Template select lives on one tab, its preview on another).\nSet empty to disable.",
7162
7162
  "type": "string"
7163
7163
  },
7164
7164
  {
package/dist/zn.d.ts CHANGED
@@ -10980,6 +10980,11 @@ declare module "components/preview-frame/preview-frame.component" {
10980
10980
  * refresh the preview. These are never intercepted: the shell submits them
10981
10981
  * (so its own response handling — alerts, refreshes — runs as normal) and the
10982
10982
  * preview re-fetches its config once the shell reports the save complete.
10983
+ * Matched by delegation on the shell's bubbled `complete` event rather than
10984
+ * by attaching to the forms themselves, so a save anywhere on the page
10985
+ * refreshes the preview — including forms in a different DOM root, e.g. a
10986
+ * page-level form saved while the preview sits inside a tab panel's shadow
10987
+ * root (a page's Template select lives on one tab, its preview on another).
10983
10988
  * Set empty to disable.
10984
10989
  */
10985
10990
  refreshOn: string;
@@ -11047,8 +11052,9 @@ declare module "components/preview-frame/preview-frame.component" {
11047
11052
  private _theme;
11048
11053
  private _contentObserver;
11049
11054
  private readonly _watchedForms;
11050
- private readonly _refreshForms;
11051
11055
  private readonly _debounceTimers;
11056
+ private _shellSaveRoots;
11057
+ private _lastShellSave;
11052
11058
  private readonly _formObserver;
11053
11059
  connectedCallback(): void;
11054
11060
  protected willUpdate(changed: PropertyValues<this>): void;
package/dist/zn.min.js CHANGED
@@ -5578,7 +5578,7 @@ ${n}`:""}</li></ol>`};return t(0)}parseTable(o,t){this.index++;let r=[],n=[];for
5578
5578
  ${this._renderInspector()}
5579
5579
  <slot name="config" class="declarations" @slotchange="${this._registerSlottedTemplates}"></slot>
5580
5580
  </div>
5581
- `}};At.styles=T(I_),At.dependencies={"zn-collapsible":la,"zn-icon":ct,"zn-input":Se,"zn-page-palette-item":Jf,"zn-page-section-card":Zf},c([p()],At.prototype,"name",2),c([p({reflect:!0})],At.prototype,"form",2),c([p()],At.prototype,"config",2),c([p({reflect:!0})],At.prototype,"heading",2),c([p({reflect:!0})],At.prototype,"subheading",2),c([p({attribute:!1})],At.prototype,"sectionTypes",2),c([p({type:Boolean,reflect:!0,attribute:"palette-collapsed"})],At.prototype,"paletteCollapsed",2),c([p({type:Boolean,reflect:!0,attribute:"inspector-collapsed"})],At.prototype,"inspectorCollapsed",2),c([p({attribute:"auto-save",converter:{fromAttribute:t=>{if(t===null)return null;let r=parseFloat(t);return Number.isFinite(r)&&r>0?r:RM},toAttribute:t=>t===null?null:String(t)}})],At.prototype,"autoSave",2),c([O()],At.prototype,"_state",2),c([O()],At.prototype,"_selectedId",2),c([O()],At.prototype,"_search",2),c([O()],At.prototype,"_pickerIndex",2),c([O()],At.prototype,"_dragOverIndex",2),c([O()],At.prototype,"_slotDragOver",2),c([O()],At.prototype,"_slotPicker",2),c([O()],At.prototype,"_form",2),c([K("config")],At.prototype,"handleConfigChange",1),c([K("sectionTypes")],At.prototype,"handleSectionTypesChange",1),c([O()],At.prototype,"_lastSavedAt",2),c([O()],At.prototype,"_justSaved",2),c([O()],At.prototype,"_statusNow",2),c([O()],At.prototype,"_restorePrompt",2);var PM=At;At.define("zn-page-builder");var N_=E`:host{display:block}:host([fill]){flex:1 1 auto;min-height:0}.preview{position:relative;width:100%;overflow-x:hidden;overflow-y:auto;background:radial-gradient(circle,rgba(var(--zn-text),var(--zn-preview-frame-dot-opacity,0.08)) 1px,transparent 1px) 0 0/var(--zn-preview-frame-dot-spacing,20px) var(--zn-preview-frame-dot-spacing,20px),rgb(var(--zn-body))}:host([backdrop=panel]) .preview{background:rgb(var(--zn-panel))}.preview__stage{height:100%;max-width:100%;margin:0 auto;overflow:hidden}iframe{display:block;width:100%;border:0;pointer-events:none;background:rgb(var(--zn-body))}:host([interactive]) iframe{pointer-events:auto}.preview__error{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;padding:1rem;text-align:center;background:rgba(0,0,0,0.65);color:#fff}`;var R_={desktop:"100%",tablet:"768px",mobile:"390px"},we=class extends k{constructor(){super(...arguments);this.src="";this.frameOrigin="";this.dataUri="";this.watch="form[data-auto-save]";this.refreshOn="form";this.debounce=400;this.zoom=1;this.minHeight=480;this.fill=!1;this.device="desktop";this.backdrop="dots";this.interactive=!1;this.error="";this._contentHeight=0;this._generation=0;this._watchedForms=new Set;this._refreshForms=new Set;this._debounceTimers=new Map;this._formObserver=new Lt(this,{target:null,config:{subtree:!0,childList:!0},callback:()=>this._attachForms()});this._onMessage=t=>{if(t.origin!==this.frameOrigin||!this.frame||t.source!==this.frame.contentWindow)return;let r=t.data;switch(r?.type){case"hp-preview:ready":this._sendConfig().then(()=>this._postTheme());break;case"hp-preview:rendered":this.error="",this._applyContentHeight(r.height);break;case"hp-preview:height":this._applyContentHeight(r.height);break;case"hp-preview:error":this._fail(String(r.message??"Preview failed to render"));break}};this._onFrameLoad=()=>{this._contentObserver?.disconnect(),this._contentObserver=void 0;let t;try{t=this.frame?.contentDocument?.documentElement}catch{return}if(!t)return;let r=()=>{let n=t.getBoundingClientRect().height;n>(this.frame?.clientHeight??0)&&this._applyContentHeight(n)};r(),this._contentObserver=new ResizeObserver(r),this._contentObserver.observe(t)};this._onShellSave=()=>{this._sendConfig()};this._onChange=t=>{let r=t.currentTarget,n=this._debounceTimers.get(r);n&&window.clearTimeout(n),this._debounceTimers.set(r,window.setTimeout(()=>{this._debounceTimers.delete(r),r.isConnected&&r.requestSubmit()},this.debounce))};this._onSubmit=t=>{t.preventDefault(),t.stopImmediatePropagation(),this._save(t.currentTarget)}}connectedCallback(){super.connectedCallback(),window.addEventListener("message",this._onMessage),this._attachForms();let t=this.getRootNode();t instanceof Document?this._formObserver.observe(t.body):t instanceof ShadowRoot&&this._formObserver.observe(t)}willUpdate(t){t.has("src")&&this._resetContentHeight()}disconnectedCallback(){super.disconnectedCallback(),window.removeEventListener("message",this._onMessage),this._contentObserver?.disconnect(),this._contentObserver=void 0,this._watchedForms.forEach(t=>this._detachForm(t)),this._watchedForms.clear(),this._refreshForms.forEach(t=>t.removeEventListener("complete",this._onShellSave)),this._refreshForms.clear()}_applyContentHeight(t){let r=Math.round(Number(t));!Number.isFinite(r)||r<=0||(this._contentHeight=r)}_resetContentHeight(){this._contentHeight=0,this._contentObserver?.disconnect(),this._contentObserver=void 0}refresh(){return this._sendConfig()}setTheme(t){this._theme=t,this._postTheme()}_postTheme(){!this._theme||!this.frameOrigin||this.frame?.contentWindow?.postMessage({type:"hp-preview:theme",...this._theme},this.frameOrigin)}async _sendConfig(){if(!this.dataUri||!this.frameOrigin)return;let t=++this._generation;try{let r=await fetch(this.dataUri,{credentials:"same-origin",headers:{"x-kx-fetch-style":"download"}});if(!r.ok)throw new Error(await r.text()||r.statusText);let n=await r.json();if(t!==this._generation)return;this.frame?.contentWindow?.postMessage({type:"hp-preview:config",...n},this.frameOrigin)}catch(r){t===this._generation&&this._fail(r instanceof Error?r.message:String(r))}}_attachForms(){let t=this.getRootNode(),r=new Set;t.querySelectorAll(this.watch).forEach(a=>{a instanceof HTMLFormElement&&r.add(a)}),this._watchedForms.forEach(a=>{r.has(a)||(this._detachForm(a),this._watchedForms.delete(a))}),r.forEach(a=>{this._watchedForms.has(a)||(this._watchedForms.add(a),a.addEventListener("submit",this._onSubmit,{capture:!0}),a.addEventListener("zn-change",this._onChange),a.addEventListener("zn-input",this._onChange),a.addEventListener("change",this._onChange))});let n=new Set;this.refreshOn&&t.querySelectorAll(this.refreshOn).forEach(a=>{a instanceof HTMLFormElement&&!r.has(a)&&n.add(a)}),this._refreshForms.forEach(a=>{n.has(a)||(a.removeEventListener("complete",this._onShellSave),this._refreshForms.delete(a))}),n.forEach(a=>{this._refreshForms.has(a)||(this._refreshForms.add(a),a.addEventListener("complete",this._onShellSave))})}_detachForm(t){t.removeEventListener("submit",this._onSubmit,{capture:!0}),t.removeEventListener("zn-change",this._onChange),t.removeEventListener("zn-input",this._onChange),t.removeEventListener("change",this._onChange);let r=this._debounceTimers.get(t);r&&window.clearTimeout(r),this._debounceTimers.delete(t)}async _save(t){try{let r=await fetch(t.getAttribute("action")||"",{method:"POST",credentials:"same-origin",headers:{"x-kx-fetch-style":"download"},body:new FormData(t)}),n=r.headers.get("x-kubex-alert-danger");if(n)throw new Error(n);if(!r.ok)throw new Error(await r.text()||r.statusText);await this._sendConfig()}catch(r){this._fail(r instanceof Error?r.message:String(r))}}_fail(t){this.error=t,this.emit("zn-error",{detail:{message:t}})}render(){let t=this.zoom>0&&this.zoom<=1?this.zoom:1,r=this.error||this.interactive?0:this._contentHeight,n=this.fill?{width:"100%",height:r?`max(${r}px, 100%)`:"100%"}:{width:`${100/t}%`,height:`${Math.max(r,this.minHeight/t)}px`,transform:`scale(${t})`,transformOrigin:"0 0"},a=this.fill?{height:"100%",minHeight:`${this.minHeight}px`}:{height:`${this.minHeight}px`},s={width:R_[this.device]??R_.desktop};return r&&(s.height=this.fill?`max(${r}px, 100%)`:`${Math.max(r*t,this.minHeight)}px`),u`
5581
+ `}};At.styles=T(I_),At.dependencies={"zn-collapsible":la,"zn-icon":ct,"zn-input":Se,"zn-page-palette-item":Jf,"zn-page-section-card":Zf},c([p()],At.prototype,"name",2),c([p({reflect:!0})],At.prototype,"form",2),c([p()],At.prototype,"config",2),c([p({reflect:!0})],At.prototype,"heading",2),c([p({reflect:!0})],At.prototype,"subheading",2),c([p({attribute:!1})],At.prototype,"sectionTypes",2),c([p({type:Boolean,reflect:!0,attribute:"palette-collapsed"})],At.prototype,"paletteCollapsed",2),c([p({type:Boolean,reflect:!0,attribute:"inspector-collapsed"})],At.prototype,"inspectorCollapsed",2),c([p({attribute:"auto-save",converter:{fromAttribute:t=>{if(t===null)return null;let r=parseFloat(t);return Number.isFinite(r)&&r>0?r:RM},toAttribute:t=>t===null?null:String(t)}})],At.prototype,"autoSave",2),c([O()],At.prototype,"_state",2),c([O()],At.prototype,"_selectedId",2),c([O()],At.prototype,"_search",2),c([O()],At.prototype,"_pickerIndex",2),c([O()],At.prototype,"_dragOverIndex",2),c([O()],At.prototype,"_slotDragOver",2),c([O()],At.prototype,"_slotPicker",2),c([O()],At.prototype,"_form",2),c([K("config")],At.prototype,"handleConfigChange",1),c([K("sectionTypes")],At.prototype,"handleSectionTypesChange",1),c([O()],At.prototype,"_lastSavedAt",2),c([O()],At.prototype,"_justSaved",2),c([O()],At.prototype,"_statusNow",2),c([O()],At.prototype,"_restorePrompt",2);var PM=At;At.define("zn-page-builder");var N_=E`:host{display:block}:host([fill]){flex:1 1 auto;min-height:0}.preview{position:relative;width:100%;overflow-x:hidden;overflow-y:auto;background:radial-gradient(circle,rgba(var(--zn-text),var(--zn-preview-frame-dot-opacity,0.08)) 1px,transparent 1px) 0 0/var(--zn-preview-frame-dot-spacing,20px) var(--zn-preview-frame-dot-spacing,20px),rgb(var(--zn-body))}:host([backdrop=panel]) .preview{background:rgb(var(--zn-panel))}.preview__stage{height:100%;max-width:100%;margin:0 auto;overflow:hidden}iframe{display:block;width:100%;border:0;pointer-events:none;background:rgb(var(--zn-body))}:host([interactive]) iframe{pointer-events:auto}.preview__error{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;padding:1rem;text-align:center;background:rgba(0,0,0,0.65);color:#fff}`;var R_={desktop:"100%",tablet:"768px",mobile:"390px"},we=class extends k{constructor(){super(...arguments);this.src="";this.frameOrigin="";this.dataUri="";this.watch="form[data-auto-save]";this.refreshOn="form";this.debounce=400;this.zoom=1;this.minHeight=480;this.fill=!1;this.device="desktop";this.backdrop="dots";this.interactive=!1;this.error="";this._contentHeight=0;this._generation=0;this._watchedForms=new Set;this._debounceTimers=new Map;this._shellSaveRoots=[];this._formObserver=new Lt(this,{target:null,config:{subtree:!0,childList:!0},callback:()=>this._attachForms()});this._onMessage=t=>{if(t.origin!==this.frameOrigin||!this.frame||t.source!==this.frame.contentWindow)return;let r=t.data;switch(r?.type){case"hp-preview:ready":this._sendConfig().then(()=>this._postTheme());break;case"hp-preview:rendered":this.error="",this._applyContentHeight(r.height);break;case"hp-preview:height":this._applyContentHeight(r.height);break;case"hp-preview:error":this._fail(String(r.message??"Preview failed to render"));break}};this._onFrameLoad=()=>{this._contentObserver?.disconnect(),this._contentObserver=void 0;let t;try{t=this.frame?.contentDocument?.documentElement}catch{return}if(!t)return;let r=()=>{let n=t.getBoundingClientRect().height;n>(this.frame?.clientHeight??0)&&this._applyContentHeight(n)};r(),this._contentObserver=new ResizeObserver(r),this._contentObserver.observe(t)};this._onShellSave=t=>{if(t===this._lastShellSave||(this._lastShellSave=t,!this.refreshOn))return;let r=t.composedPath()[0];!(r instanceof HTMLFormElement)||!r.matches(this.refreshOn)||this._watchedForms.has(r)||this._sendConfig()};this._onChange=t=>{let r=t.currentTarget,n=this._debounceTimers.get(r);n&&window.clearTimeout(n),this._debounceTimers.set(r,window.setTimeout(()=>{this._debounceTimers.delete(r),r.isConnected&&r.requestSubmit()},this.debounce))};this._onSubmit=t=>{t.preventDefault(),t.stopImmediatePropagation(),this._save(t.currentTarget)}}connectedCallback(){super.connectedCallback(),window.addEventListener("message",this._onMessage),this._attachForms();let t=this.getRootNode();this._shellSaveRoots=t instanceof ShadowRoot?[document,t]:[document],this._shellSaveRoots.forEach(r=>r.addEventListener("complete",this._onShellSave)),t instanceof Document?this._formObserver.observe(t.body):t instanceof ShadowRoot&&this._formObserver.observe(t)}willUpdate(t){t.has("src")&&this._resetContentHeight()}disconnectedCallback(){super.disconnectedCallback(),window.removeEventListener("message",this._onMessage),this._contentObserver?.disconnect(),this._contentObserver=void 0,this._watchedForms.forEach(t=>this._detachForm(t)),this._watchedForms.clear(),this._shellSaveRoots.forEach(t=>t.removeEventListener("complete",this._onShellSave)),this._shellSaveRoots=[]}_applyContentHeight(t){let r=Math.round(Number(t));!Number.isFinite(r)||r<=0||(this._contentHeight=r)}_resetContentHeight(){this._contentHeight=0,this._contentObserver?.disconnect(),this._contentObserver=void 0}refresh(){return this._sendConfig()}setTheme(t){this._theme=t,this._postTheme()}_postTheme(){!this._theme||!this.frameOrigin||this.frame?.contentWindow?.postMessage({type:"hp-preview:theme",...this._theme},this.frameOrigin)}async _sendConfig(){if(!this.dataUri||!this.frameOrigin)return;let t=++this._generation;try{let r=await fetch(this.dataUri,{credentials:"same-origin",headers:{"x-kx-fetch-style":"download"}});if(!r.ok)throw new Error(await r.text()||r.statusText);let n=await r.json();if(t!==this._generation)return;this.frame?.contentWindow?.postMessage({type:"hp-preview:config",...n},this.frameOrigin)}catch(r){t===this._generation&&this._fail(r instanceof Error?r.message:String(r))}}_attachForms(){let t=this.getRootNode(),r=new Set;t.querySelectorAll(this.watch).forEach(n=>{n instanceof HTMLFormElement&&r.add(n)}),this._watchedForms.forEach(n=>{r.has(n)||(this._detachForm(n),this._watchedForms.delete(n))}),r.forEach(n=>{this._watchedForms.has(n)||(this._watchedForms.add(n),n.addEventListener("submit",this._onSubmit,{capture:!0}),n.addEventListener("zn-change",this._onChange),n.addEventListener("zn-input",this._onChange),n.addEventListener("change",this._onChange))})}_detachForm(t){t.removeEventListener("submit",this._onSubmit,{capture:!0}),t.removeEventListener("zn-change",this._onChange),t.removeEventListener("zn-input",this._onChange),t.removeEventListener("change",this._onChange);let r=this._debounceTimers.get(t);r&&window.clearTimeout(r),this._debounceTimers.delete(t)}async _save(t){try{let r=await fetch(t.getAttribute("action")||"",{method:"POST",credentials:"same-origin",headers:{"x-kx-fetch-style":"download"},body:new FormData(t)}),n=r.headers.get("x-kubex-alert-danger");if(n)throw new Error(n);if(!r.ok)throw new Error(await r.text()||r.statusText);await this._sendConfig()}catch(r){this._fail(r instanceof Error?r.message:String(r))}}_fail(t){this.error=t,this.emit("zn-error",{detail:{message:t}})}render(){let t=this.zoom>0&&this.zoom<=1?this.zoom:1,r=this.error||this.interactive?0:this._contentHeight,n=this.fill?{width:"100%",height:r?`max(${r}px, 100%)`:"100%"}:{width:`${100/t}%`,height:`${Math.max(r,this.minHeight/t)}px`,transform:`scale(${t})`,transformOrigin:"0 0"},a=this.fill?{height:"100%",minHeight:`${this.minHeight}px`}:{height:`${this.minHeight}px`},s={width:R_[this.device]??R_.desktop};return r&&(s.height=this.fill?`max(${r}px, 100%)`:`${Math.max(r*t,this.minHeight)}px`),u`
5582
5582
  <div part="base" class="preview" style="${Sr(a)}">
5583
5583
  <div part="stage" class="preview__stage" style="${Sr(s)}">
5584
5584
  <iframe part="iframe"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kubex/zinc",
3
- "version": "1.1.85",
3
+ "version": "1.1.86",
4
4
  "description": "A collection of web components for building web applications based off of @shoelace-style/Shoelace",
5
5
  "keywords": [
6
6
  "web components",
@@ -62,6 +62,11 @@ export default class ZnPreviewFrame extends ZincElement {
62
62
  * refresh the preview. These are never intercepted: the shell submits them
63
63
  * (so its own response handling — alerts, refreshes — runs as normal) and the
64
64
  * preview re-fetches its config once the shell reports the save complete.
65
+ * Matched by delegation on the shell's bubbled `complete` event rather than
66
+ * by attaching to the forms themselves, so a save anywhere on the page
67
+ * refreshes the preview — including forms in a different DOM root, e.g. a
68
+ * page-level form saved while the preview sits inside a tab panel's shadow
69
+ * root (a page's Template select lives on one tab, its preview on another).
65
70
  * Set empty to disable.
66
71
  */
67
72
  @property({attribute: 'refresh-on'}) refreshOn = 'form';
@@ -141,8 +146,13 @@ export default class ZnPreviewFrame extends ZincElement {
141
146
  private _contentObserver: ResizeObserver | undefined;
142
147
 
143
148
  private readonly _watchedForms = new Set<HTMLFormElement>();
144
- private readonly _refreshForms = new Set<HTMLFormElement>();
145
149
  private readonly _debounceTimers = new Map<HTMLFormElement, number>();
150
+ // 'complete' delegation targets: the document always, plus the component's
151
+ // own shadow root when it has one (a non-composed event never reaches the
152
+ // document). A composed event inside the shadow root hits both, so the
153
+ // handler dedupes by event object.
154
+ private _shellSaveRoots: EventTarget[] = [];
155
+ private _lastShellSave: Event | undefined;
146
156
 
147
157
  // Forms are siblings in light DOM and get replaced when other content
148
158
  // re-renders; re-resolve them whenever the surrounding DOM changes.
@@ -158,6 +168,8 @@ export default class ZnPreviewFrame extends ZincElement {
158
168
  window.addEventListener('message', this._onMessage);
159
169
  this._attachForms();
160
170
  const root = this.getRootNode();
171
+ this._shellSaveRoots = root instanceof ShadowRoot ? [document, root] : [document];
172
+ this._shellSaveRoots.forEach(target => target.addEventListener('complete', this._onShellSave));
161
173
  if (root instanceof Document) {
162
174
  this._formObserver.observe(root.body);
163
175
  } else if (root instanceof ShadowRoot) {
@@ -178,8 +190,8 @@ export default class ZnPreviewFrame extends ZincElement {
178
190
  this._contentObserver = undefined;
179
191
  this._watchedForms.forEach(form => this._detachForm(form));
180
192
  this._watchedForms.clear();
181
- this._refreshForms.forEach(form => form.removeEventListener('complete', this._onShellSave));
182
- this._refreshForms.clear();
193
+ this._shellSaveRoots.forEach(target => target.removeEventListener('complete', this._onShellSave));
194
+ this._shellSaveRoots = [];
183
195
  }
184
196
 
185
197
  private readonly _onMessage = (e: MessageEvent) => {
@@ -315,30 +327,18 @@ export default class ZnPreviewFrame extends ZincElement {
315
327
  form.addEventListener('zn-input', this._onChange);
316
328
  form.addEventListener('change', this._onChange);
317
329
  });
318
-
319
- // Shell-saved forms: the shell fires 'complete' on the form it submitted.
320
- const refreshMatched = new Set<HTMLFormElement>();
321
- if (this.refreshOn) {
322
- root.querySelectorAll(this.refreshOn).forEach(node => {
323
- if (node instanceof HTMLFormElement && !matched.has(node)) refreshMatched.add(node);
324
- });
325
- }
326
-
327
- this._refreshForms.forEach(form => {
328
- if (!refreshMatched.has(form)) {
329
- form.removeEventListener('complete', this._onShellSave);
330
- this._refreshForms.delete(form);
331
- }
332
- });
333
-
334
- refreshMatched.forEach(form => {
335
- if (this._refreshForms.has(form)) return;
336
- this._refreshForms.add(form);
337
- form.addEventListener('complete', this._onShellSave);
338
- });
339
330
  }
340
331
 
341
- private readonly _onShellSave = () => {
332
+ // Shell-saved forms: the shell fires a bubbling 'complete' on the form it
333
+ // submitted. composedPath()[0] recovers the form when the event was
334
+ // retargeted crossing a shadow boundary on its way to the document.
335
+ private readonly _onShellSave = (e: Event) => {
336
+ if (e === this._lastShellSave) return;
337
+ this._lastShellSave = e;
338
+ if (!this.refreshOn) return;
339
+ const origin = e.composedPath()[0];
340
+ if (!(origin instanceof HTMLFormElement) || !origin.matches(this.refreshOn)) return;
341
+ if (this._watchedForms.has(origin)) return; // auto-saved forms refresh via _save
342
342
  void this._sendConfig();
343
343
  };
344
344
 
@@ -248,6 +248,54 @@ describe('<zn-preview-frame>', () => {
248
248
  expect(posted[0]['type']).to.equal('hp-preview:config');
249
249
  });
250
250
 
251
+ // A uri tab panel puts the preview in the tabs component's shadow root while
252
+ // page-level forms (e.g. the page's Template select) stay in the document —
253
+ // a save out there must still refresh the preview.
254
+ it('refreshes on a shell save of a form outside the component\'s own root', async () => {
255
+ const wrapper = await fixture(html`
256
+ <div>
257
+ <form action="/save" method="post"><input name="template" value="flow"></form>
258
+ <div id="host"></div>
259
+ </div>`);
260
+
261
+ const shadow = wrapper.querySelector('#host')!.attachShadow({mode: 'open'});
262
+ const el = document.createElement('zn-preview-frame');
263
+ el.setAttribute('src', 'about:blank');
264
+ el.setAttribute('frame-origin', 'https://site.example');
265
+ el.setAttribute('data-uri', '/payload');
266
+ shadow.appendChild(el);
267
+ await (el as HTMLElement & {updateComplete: Promise<boolean>}).updateComplete;
268
+
269
+ wrapper.querySelector('form')!.dispatchEvent(
270
+ new CustomEvent('complete', {bubbles: true, detail: {}}));
271
+
272
+ await waitUntil(() => fetchCalls.length === 1);
273
+ expect(fetchCalls[0].uri).to.equal('/payload');
274
+ });
275
+
276
+ it('refreshes once for a composed complete event inside its own shadow root', async () => {
277
+ const wrapper = await fixture(html`<div><div id="host"></div></div>`);
278
+
279
+ const shadow = wrapper.querySelector('#host')!.attachShadow({mode: 'open'});
280
+ const form = document.createElement('form');
281
+ form.setAttribute('action', '/save');
282
+ shadow.appendChild(form);
283
+ const el = document.createElement('zn-preview-frame');
284
+ el.setAttribute('src', 'about:blank');
285
+ el.setAttribute('frame-origin', 'https://site.example');
286
+ el.setAttribute('data-uri', '/payload');
287
+ shadow.appendChild(el);
288
+ await (el as HTMLElement & {updateComplete: Promise<boolean>}).updateComplete;
289
+
290
+ // the shell fires composed events, so this reaches both the shadow root
291
+ // and the document — it must trigger a single refresh
292
+ form.dispatchEvent(new CustomEvent('complete', {bubbles: true, composed: true, detail: {}}));
293
+
294
+ await waitUntil(() => fetchCalls.length === 1);
295
+ await new Promise(resolve => setTimeout(resolve, 50));
296
+ expect(fetchCalls.length).to.equal(1);
297
+ });
298
+
251
299
  it('does not intercept the submit of a refresh-on form', async () => {
252
300
  const wrapper = await fixture(html`
253
301
  <div>