@jxsuite/studio 0.5.0 → 0.5.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jxsuite/studio",
3
- "version": "0.5.0",
3
+ "version": "0.5.1",
4
4
  "description": "Jx Studio — visual builder for Jx documents",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -59,7 +59,7 @@
59
59
  "@spectrum-web-components/textfield": "^1.12.0",
60
60
  "@spectrum-web-components/theme": "^1.12.0",
61
61
  "@spectrum-web-components/tooltip": "^1.12.0",
62
- "lit-html": "^3.3.2",
62
+ "lit-html": "^3.3.3",
63
63
  "monaco-editor": "^0.55.1",
64
64
  "remark-directive": "^4.0.0",
65
65
  "remark-frontmatter": "^5.0.0",
@@ -4,12 +4,15 @@
4
4
  */
5
5
 
6
6
  import { html, render as litRender, nothing } from "lit-html";
7
- import { getState, canvasPanels, pathsEqual } from "../store.js";
7
+ import { getState, canvasPanels, pathsEqual, subscribe } from "../store.js";
8
8
  import { view } from "../view.js";
9
9
 
10
10
  /** @type {any} */
11
11
  let _ctx = null;
12
12
 
13
+ /** @type {(() => void) | null} */
14
+ let _unsub = null;
15
+
13
16
  /**
14
17
  * Mount the overlays panel.
15
18
  *
@@ -18,9 +21,14 @@ let _ctx = null;
18
21
  */
19
22
  export function mount(ctx) {
20
23
  _ctx = ctx;
24
+ _unsub = subscribe((change) => {
25
+ if (change.selection || change.hover || change.mode || change.ui || change.doc) render();
26
+ });
21
27
  }
22
28
 
23
29
  export function unmount() {
30
+ _unsub?.();
31
+ _unsub = null;
24
32
  _ctx = null;
25
33
  }
26
34
 
@@ -5,15 +5,19 @@
5
5
  */
6
6
 
7
7
  import { html, render as litRender, nothing } from "lit-html";
8
- import { getState, updateUi, rightPanel } from "../store.js";
8
+ import { getState, updateUi, rightPanel, subscribe } from "../store.js";
9
9
  import { tabIcon } from "./activity-bar.js";
10
10
  import { eventsSidebarTemplate } from "./events-panel.js";
11
11
  import { isCustomElementDoc } from "./signals-panel.js";
12
12
  import { ensureLitState } from "./shared.js";
13
+ import { isColorPopoverOpen } from "../ui/color-selector.js";
13
14
 
14
15
  /** @type {any} */
15
16
  let _ctx = null;
16
17
 
18
+ /** @type {(() => void) | null} */
19
+ let _unsub = null;
20
+
17
21
  /**
18
22
  * Mount the right panel.
19
23
  *
@@ -22,9 +26,31 @@ let _ctx = null;
22
26
  */
23
27
  export function mount(ctx) {
24
28
  _ctx = ctx;
29
+ _unsub = subscribe((change) => {
30
+ if (!change.selection && !change.ui && !change.doc) return;
31
+
32
+ const colorPopoverOpen = isColorPopoverOpen();
33
+ const activeTag = document.activeElement?.tagName;
34
+ const rightHasFocus =
35
+ !colorPopoverOpen &&
36
+ rightPanel.contains(document.activeElement) &&
37
+ (activeTag === "INPUT" ||
38
+ activeTag === "TEXTAREA" ||
39
+ activeTag === "SP-TEXTFIELD" ||
40
+ activeTag === "SP-NUMBER-FIELD" ||
41
+ activeTag === "SP-PICKER" ||
42
+ activeTag === "SP-COMBOBOX" ||
43
+ activeTag === "SP-SEARCH");
44
+
45
+ if (!rightHasFocus || change.selection || change.ui) {
46
+ render();
47
+ }
48
+ });
25
49
  }
26
50
 
27
51
  export function unmount() {
52
+ _unsub?.();
53
+ _unsub = null;
28
54
  _ctx = null;
29
55
  }
30
56
 
@@ -1,6 +1,6 @@
1
1
  /** Statusbar — status message display for Jx Studio */
2
2
 
3
- import { statusbarEl, getNodeAtPath, nodeLabel } from "../store.js";
3
+ import { statusbarEl, getNodeAtPath, nodeLabel, getState, subscribe } from "../store.js";
4
4
 
5
5
  // ─── Module state ────────────────────────────────────────────────────────────
6
6
 
@@ -9,6 +9,8 @@ let statusMsg = "";
9
9
  let statusTimeout;
10
10
  /** @type {(() => void) | null} */
11
11
  let _rerender = null;
12
+ /** @type {(() => void) | null} */
13
+ let _unsub = null;
12
14
 
13
15
  /**
14
16
  * Register the callback used to re-render the statusbar. Called once from studio.js during init.
@@ -19,6 +21,18 @@ export function setStatusbarRenderer(fn) {
19
21
  _rerender = fn;
20
22
  }
21
23
 
24
+ /** Subscribe the statusbar to state changes. */
25
+ export function mountStatusbar() {
26
+ _unsub = subscribe((change) => {
27
+ if (change.selection || change.mode || change.doc) renderStatusbar(getState());
28
+ });
29
+ }
30
+
31
+ export function unmountStatusbar() {
32
+ _unsub?.();
33
+ _unsub = null;
34
+ }
35
+
22
36
  // ─── Statusbar ───────────────────────────────────────────────────────────────
23
37
 
24
38
  /**
@@ -4,7 +4,7 @@
4
4
  */
5
5
 
6
6
  import { html, render as litRender, nothing } from "lit-html";
7
- import { getState, update, updateSession, updateUi, undo, redo } from "../store.js";
7
+ import { getState, update, updateSession, updateUi, undo, redo, subscribe } from "../store.js";
8
8
  import { getEffectiveMedia } from "../site-context.js";
9
9
  import { mediaDisplayName } from "./shared.js";
10
10
  import { view } from "../view.js";
@@ -15,6 +15,9 @@ let _rootEl = null;
15
15
  /** @type {any} */
16
16
  let _ctx = null;
17
17
 
18
+ /** @type {(() => void) | null} */
19
+ let _unsub = null;
20
+
18
21
  const toolbarIconMap = /** @type {Record<string, any>} */ ({
19
22
  "sp-icon-folder-open": html`<sp-icon-folder-open slot="icon"></sp-icon-folder-open>`,
20
23
  "sp-icon-save-floppy": html`<sp-icon-save-floppy slot="icon"></sp-icon-save-floppy>`,
@@ -56,9 +59,12 @@ function tbBtnTpl(label, onClick, iconTag) {
56
59
  export function mount(rootEl, ctx) {
57
60
  _rootEl = rootEl;
58
61
  _ctx = ctx;
62
+ _unsub = subscribe(() => render());
59
63
  }
60
64
 
61
65
  export function unmount() {
66
+ _unsub?.();
67
+ _unsub = null;
62
68
  _rootEl = null;
63
69
  _ctx = null;
64
70
  }
package/src/store.js CHANGED
@@ -324,6 +324,41 @@ export function updateUi(field, value) {
324
324
  _updateSessionFn({ ui: { [field]: value } });
325
325
  }
326
326
 
327
+ // ─── Subscription system ────────────────────────────────────────────────────
328
+ // Panels subscribe to state changes and decide when to re-render, rather than
329
+ // being called unconditionally from _update/_updateSession.
330
+
331
+ /** @typedef {{ doc: boolean; selection: boolean; hover: boolean; ui: boolean; mode: boolean }} Change */
332
+
333
+ /** @type {Set<(change: Change) => void>} */
334
+ const _subscribers = new Set();
335
+
336
+ /**
337
+ * Subscribe to state changes. Returns an unsubscribe function.
338
+ *
339
+ * @param {(change: Change) => void} fn
340
+ * @returns {() => void}
341
+ */
342
+ export function subscribe(fn) {
343
+ _subscribers.add(fn);
344
+ return () => _subscribers.delete(fn);
345
+ }
346
+
347
+ /**
348
+ * Notify all subscribers of a state change.
349
+ *
350
+ * @param {Change} change
351
+ */
352
+ export function notify(change) {
353
+ for (const fn of _subscribers) {
354
+ try {
355
+ fn(change);
356
+ } catch (e) {
357
+ console.error("Subscriber failed:", e);
358
+ }
359
+ }
360
+ }
361
+
327
362
  /** @type {Function[]} */
328
363
  const _updateMiddleware = [];
329
364