@jxsuite/studio 0.10.2 → 0.11.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.
@@ -109,6 +109,111 @@ export function getEffectiveHead(docHead) {
109
109
  return merged;
110
110
  }
111
111
 
112
+ // ─── Layout resolution ──────────────────────────────────────────────────────
113
+
114
+ /** @type {Map<string, any>} */
115
+ const layoutCache = new Map();
116
+
117
+ export function invalidateLayoutCache() {
118
+ layoutCache.clear();
119
+ }
120
+
121
+ /**
122
+ * Determine the effective layout path for a document.
123
+ *
124
+ * @param {any} docLayout - The document's $layout value (string path, false, or undefined)
125
+ * @returns {string | null} The layout path, or null if no layout applies
126
+ */
127
+ export function getEffectiveLayoutPath(docLayout) {
128
+ if (docLayout === false) return null;
129
+ const defaultLayout = projectState?.projectConfig?.defaults?.layout;
130
+ return docLayout || defaultLayout || null;
131
+ }
132
+
133
+ /**
134
+ * Resolve a layout document by path. Fetches and caches the parsed JSON.
135
+ *
136
+ * @param {string} layoutPath - Relative path to the layout file (e.g., "./layouts/base.json")
137
+ * @returns {Promise<any | null>} The parsed layout document, or null on failure
138
+ */
139
+ export async function resolveLayoutDoc(layoutPath) {
140
+ const normalized = layoutPath.replace(/^\.\//, "");
141
+ if (layoutCache.has(normalized)) return structuredClone(layoutCache.get(normalized));
142
+
143
+ try {
144
+ const platform = getPlatform();
145
+ const content = await platform.readFile(normalized);
146
+ const doc = JSON.parse(content);
147
+ layoutCache.set(normalized, doc);
148
+ return structuredClone(doc);
149
+ } catch {
150
+ return null;
151
+ }
152
+ }
153
+
154
+ /**
155
+ * Distribute page children into a layout document's <slot> elements. Returns the merged document
156
+ * with page content injected into slots.
157
+ *
158
+ * @param {any} layoutDoc - Deep-cloned layout document
159
+ * @param {any} pageDoc - The page document
160
+ * @returns {any} The merged document
161
+ */
162
+ export function distributePageIntoLayout(layoutDoc, pageDoc) {
163
+ const pageChildren = pageDoc.children ?? [];
164
+ const children = typeof pageChildren === "string" ? [pageChildren] : pageChildren;
165
+
166
+ const named = new Map();
167
+ const defaults = [];
168
+
169
+ for (const child of children) {
170
+ if (child && typeof child === "object" && child.attributes?.slot) {
171
+ const slotName = child.attributes.slot;
172
+ if (!named.has(slotName)) named.set(slotName, []);
173
+ named.get(slotName).push(child);
174
+ } else {
175
+ defaults.push(child);
176
+ }
177
+ }
178
+
179
+ fillSlots(layoutDoc, named, defaults);
180
+
181
+ if (pageDoc.state) layoutDoc.state = { ...layoutDoc.state, ...pageDoc.state };
182
+ if (pageDoc.$media) layoutDoc.$media = { ...layoutDoc.$media, ...pageDoc.$media };
183
+ if (pageDoc.style) layoutDoc.style = { ...layoutDoc.style, ...pageDoc.style };
184
+ if (pageDoc.attributes) layoutDoc.attributes = { ...layoutDoc.attributes, ...pageDoc.attributes };
185
+
186
+ return layoutDoc;
187
+ }
188
+
189
+ function fillSlots(
190
+ /** @type {any} */ node,
191
+ /** @type {Map<string, any[]>} */ named,
192
+ /** @type {any[]} */ defaults,
193
+ ) {
194
+ if (!node || typeof node !== "object") return;
195
+ if (!Array.isArray(node.children)) return;
196
+
197
+ const newChildren = [];
198
+ for (const child of node.children) {
199
+ if (child && typeof child === "object" && child.tagName === "slot") {
200
+ const slotName = child.attributes?.name;
201
+ if (slotName && named.has(slotName)) {
202
+ newChildren.push(.../** @type {any[]} */ (named.get(slotName)));
203
+ named.delete(slotName);
204
+ } else if (!slotName && defaults.length > 0) {
205
+ newChildren.push(...defaults);
206
+ } else if (child.children) {
207
+ newChildren.push(...child.children);
208
+ }
209
+ } else {
210
+ fillSlots(child, named, defaults);
211
+ newChildren.push(child);
212
+ }
213
+ }
214
+ node.children = newChildren;
215
+ }
216
+
112
217
  /**
113
218
  * Update the project's project.json with a partial patch and persist to disk.
114
219
  *
package/src/store.js CHANGED
@@ -164,7 +164,15 @@ export function stripEventHandlers(node) {
164
164
  const cases = {};
165
165
  for (const [ck, cv] of Object.entries(v)) cases[ck] = stripEventHandlers(cv);
166
166
  out.cases = cases;
167
- } else if (k === "state" || k === "style" || k === "attributes" || k === "$media") {
167
+ } else if (k === "state" && typeof v === "object" && v !== null) {
168
+ /** @type {Record<string, any>} */
169
+ const state = {};
170
+ for (const [sk, sv] of Object.entries(v)) {
171
+ if (sv && typeof sv === "object" && sv.timing === "server") continue;
172
+ state[sk] = sv;
173
+ }
174
+ out.state = state;
175
+ } else if (k === "style" || k === "attributes" || k === "$media") {
168
176
  out[k] = v;
169
177
  } else {
170
178
  out[k] = v;
@@ -47,6 +47,7 @@ import { PickerButton } from "@spectrum-web-components/picker-button/src/PickerB
47
47
  import { Accordion } from "@spectrum-web-components/accordion/src/Accordion.js";
48
48
  import { AccordionItem } from "@spectrum-web-components/accordion/src/AccordionItem.js";
49
49
  import { ActionBar } from "@spectrum-web-components/action-bar/src/ActionBar.js";
50
+ import { Toast } from "@spectrum-web-components/toast/src/Toast.js";
50
51
  import { Table } from "@spectrum-web-components/table/src/Table.js";
51
52
  import { TableHead } from "@spectrum-web-components/table/src/TableHead.js";
52
53
  import { TableHeadCell } from "@spectrum-web-components/table/src/TableHeadCell.js";
@@ -180,6 +181,7 @@ const components = [
180
181
  ["sp-accordion", Accordion],
181
182
  ["sp-accordion-item", AccordionItem],
182
183
  ["sp-action-bar", ActionBar],
184
+ ["sp-toast", Toast],
183
185
  ["sp-table", Table],
184
186
  ["sp-table-head", TableHead],
185
187
  ["sp-table-head-cell", TableHeadCell],
package/src/view.js CHANGED
@@ -61,6 +61,9 @@ export const view = {
61
61
  showAddBreakpointForm: false,
62
62
  addBreakpointPreview: "",
63
63
 
64
+ // Layout selection (when user clicks a layout element)
65
+ layoutSelection: null,
66
+
64
67
  // Autosave
65
68
  autosaveTimer: null,
66
69
  };