@floegence/floe-webapp-core 0.67.1 → 0.68.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.
@@ -59,6 +59,8 @@ export interface FloeLayoutConfig {
59
59
  max: number;
60
60
  };
61
61
  defaultActiveTab: string;
62
+ /** Disable when the consumer owns navigation persistence. Defaults to true. */
63
+ persistActiveTab?: boolean;
62
64
  defaultCollapsed: boolean;
63
65
  };
64
66
  terminal: {
@@ -5,16 +5,16 @@ import { useResolvedFloeConfig as v } from "./FloeConfigContext.js";
5
5
  const {
6
6
  Provider: x,
7
7
  use: C,
8
- useOptional: L
8
+ useOptional: A
9
9
  } = u({
10
10
  name: "Layout",
11
11
  init: g
12
12
  });
13
13
  function g() {
14
- const d = v(), i = () => d.config.layout, p = () => typeof window > "u" ? !1 : window.matchMedia(i().mobileQuery).matches, s = d.persist.load(i().storageKey, {}), h = {
14
+ const n = v(), i = () => n.config.layout, p = () => typeof window > "u" ? !1 : window.matchMedia(i().mobileQuery).matches, s = n.persist.load(i().storageKey, {}), h = {
15
15
  sidebar: {
16
16
  width: s.sidebar?.width ?? i().sidebar.defaultWidth,
17
- activeTab: s.sidebar?.activeTab ?? i().sidebar.defaultActiveTab,
17
+ activeTab: (i().sidebar.persistActiveTab !== !1 ? s.sidebar?.activeTab : void 0) ?? i().sidebar.defaultActiveTab,
18
18
  collapsed: s.sidebar?.collapsed ?? i().sidebar.defaultCollapsed
19
19
  },
20
20
  terminal: {
@@ -22,44 +22,46 @@ function g() {
22
22
  height: s.terminal?.height ?? i().terminal.defaultHeight
23
23
  },
24
24
  isMobile: p()
25
- }, [t, o] = c(h), [l, b] = c({
25
+ }, [a, o] = c(h), [l, b] = c({
26
26
  motion: "animated",
27
27
  revision: 0
28
28
  }), m = (e) => Math.max(i().sidebar.clamp.min, Math.min(i().sidebar.clamp.max, e));
29
29
  return f(() => {
30
30
  const e = {
31
31
  sidebar: {
32
- width: t.sidebar.width,
33
- activeTab: t.sidebar.activeTab,
34
- collapsed: t.sidebar.collapsed
32
+ width: a.sidebar.width,
33
+ ...i().sidebar.persistActiveTab !== !1 ? {
34
+ activeTab: a.sidebar.activeTab
35
+ } : {},
36
+ collapsed: a.sidebar.collapsed
35
37
  },
36
38
  terminal: {
37
- opened: t.terminal.opened,
38
- height: t.terminal.height
39
+ opened: a.terminal.opened,
40
+ height: a.terminal.height
39
41
  }
40
42
  };
41
- d.persist.debouncedSave(i().storageKey, e);
43
+ n.persist.debouncedSave(i().storageKey, e);
42
44
  }), {
43
45
  // Sidebar accessors
44
- sidebarWidth: () => t.sidebar.width,
45
- sidebarActiveTab: () => t.sidebar.activeTab,
46
- sidebarCollapsed: () => t.sidebar.collapsed,
46
+ sidebarWidth: () => a.sidebar.width,
47
+ sidebarActiveTab: () => a.sidebar.activeTab,
48
+ sidebarCollapsed: () => a.sidebar.collapsed,
47
49
  sidebarVisibilityMotion: () => l.motion,
48
50
  sidebarVisibilityMotionRevision: () => l.revision,
49
51
  clampSidebarWidth: m,
50
52
  // Sidebar actions
51
- setSidebarWidth: (e) => o(r((a) => {
52
- a.sidebar.width = m(e);
53
+ setSidebarWidth: (e) => o(r((t) => {
54
+ t.sidebar.width = m(e);
53
55
  })),
54
- setSidebarActiveTab: (e, a) => {
55
- o(r((n) => {
56
- n.sidebar.activeTab = e, a?.openSidebar !== !1 && (n.sidebar.collapsed = !1);
57
- })), a?.visibilityMotion === "instant" && b(r((n) => {
58
- n.motion = "instant", n.revision += 1;
56
+ setSidebarActiveTab: (e, t) => {
57
+ o(r((d) => {
58
+ d.sidebar.activeTab = e, t?.openSidebar !== !1 && (d.sidebar.collapsed = !1);
59
+ })), t?.visibilityMotion === "instant" && b(r((d) => {
60
+ d.motion = "instant", d.revision += 1;
59
61
  }));
60
62
  },
61
- setSidebarCollapsed: (e) => o(r((a) => {
62
- a.sidebar.collapsed = e;
63
+ setSidebarCollapsed: (e) => o(r((t) => {
64
+ t.sidebar.collapsed = e;
63
65
  })),
64
66
  toggleSidebarCollapse: () => o(r((e) => {
65
67
  e.sidebar.collapsed = !e.sidebar.collapsed;
@@ -68,17 +70,17 @@ function g() {
68
70
  l.revision === e && b("motion", "animated");
69
71
  },
70
72
  // Terminal accessors
71
- terminalOpened: () => t.terminal.opened,
72
- terminalHeight: () => t.terminal.height,
73
+ terminalOpened: () => a.terminal.opened,
74
+ terminalHeight: () => a.terminal.height,
73
75
  // Terminal actions
74
76
  toggleTerminal: () => o(r((e) => {
75
77
  e.terminal.opened = !e.terminal.opened;
76
78
  })),
77
- setTerminalHeight: (e) => o(r((a) => {
78
- a.terminal.height = Math.max(i().terminal.clamp.min, Math.min(i().terminal.clamp.max, e));
79
+ setTerminalHeight: (e) => o(r((t) => {
80
+ t.terminal.height = Math.max(i().terminal.clamp.min, Math.min(i().terminal.clamp.max, e));
79
81
  })),
80
82
  // Mobile
81
- isMobile: () => t.isMobile,
83
+ isMobile: () => a.isMobile,
82
84
  setIsMobile: (e) => o("isMobile", e)
83
85
  };
84
86
  }
@@ -86,5 +88,5 @@ export {
86
88
  x as LayoutProvider,
87
89
  g as createLayoutService,
88
90
  C as useLayout,
89
- L as useOptionalLayout
91
+ A as useOptionalLayout
90
92
  };
@@ -12,6 +12,8 @@ export interface ResourceCacheStorage {
12
12
  }
13
13
  export interface ResourceSnapshot<T> {
14
14
  readonly data: T | undefined;
15
+ /** True until storage settles, or live data/invalidation supersedes restoration. */
16
+ readonly restoring: boolean;
15
17
  readonly refreshing: boolean;
16
18
  readonly stale: boolean;
17
19
  readonly error: unknown;
@@ -1,96 +1,98 @@
1
- async function N(f, i) {
2
- const g = [...await f.list()].sort((e, t) => e.lastAccessedAt - t.lastAccessedAt);
3
- let d = g.reduce((e, t) => e + t.bytes, 0);
4
- const o = [];
1
+ async function N(h, i) {
2
+ const g = [...await h.list()].sort((e, t) => e.lastAccessedAt - t.lastAccessedAt);
3
+ let l = g.reduce((e, t) => e + t.bytes, 0);
4
+ const n = [];
5
5
  for (const e of g) {
6
- if (d <= Math.max(0, i)) break;
7
- await f.remove(e.key), o.push(e.key), d -= e.bytes;
6
+ if (l <= Math.max(0, i)) break;
7
+ await h.remove(e.key), n.push(e.key), l -= e.bytes;
8
8
  }
9
- return o;
9
+ return n;
10
10
  }
11
- function I(f) {
12
- const i = f.storage, g = Math.max(0, f.maxBytes ?? 32 * 1024 * 1024), d = /* @__PURE__ */ new Map(), o = /* @__PURE__ */ new Map();
13
- let e, t = Promise.resolve(), a = !1;
14
- const l = (s) => (t = t.then(s).catch(() => {
11
+ function I(h) {
12
+ const i = h.storage, g = Math.max(0, h.maxBytes ?? 32 * 1024 * 1024), l = /* @__PURE__ */ new Map(), n = /* @__PURE__ */ new Map();
13
+ let e, t = Promise.resolve(), o = !1;
14
+ const d = (s) => (t = t.then(s).catch(() => {
15
15
  }), t), v = async () => {
16
16
  const s = await N({
17
- list: async () => (await i.list()).map((r) => ({ ...r, lastAccessedAt: Math.max(r.lastAccessedAt, d.get(r.key)?.accessedAt ?? 0) })),
17
+ list: async () => (await i.list()).map((r) => ({ ...r, lastAccessedAt: Math.max(r.lastAccessedAt, l.get(r.key)?.accessedAt ?? 0) })),
18
18
  remove: (r) => i.remove(r)
19
19
  }, g);
20
- for (const r of s) d.get(r)?.evicted();
20
+ for (const r of s) l.get(r)?.evicted();
21
21
  }, y = async () => {
22
22
  clearTimeout(e), e = void 0;
23
- const s = [...o];
24
- o.clear(), s.length && l(async () => {
25
- for (const [r, h] of s)
26
- if (h.valid())
23
+ const s = [...n];
24
+ n.clear(), s.length && d(async () => {
25
+ for (const [r, f] of s)
26
+ if (f.valid())
27
27
  try {
28
- await i.set(r, h.value), h.stored();
28
+ await i.set(r, f.value), f.stored();
29
29
  } catch {
30
30
  }
31
31
  await v();
32
32
  }), await t;
33
- }, k = (s, r, h, c) => {
34
- o.set(s, { value: r, valid: h, stored: c }), e === void 0 && (e = setTimeout(() => {
33
+ }, k = (s, r, f, a) => {
34
+ n.set(s, { value: r, valid: f, stored: a }), e === void 0 && (e = setTimeout(() => {
35
35
  y();
36
- }, f.writeDelayMs ?? 100));
36
+ }, h.writeDelayMs ?? 100));
37
37
  };
38
38
  return {
39
39
  resource: (s) => {
40
- if (a) throw new Error("Resource cache is disposed");
40
+ if (o) throw new Error("Resource cache is disposed");
41
41
  if (!s.scope || !s.key || !Number.isSafeInteger(s.version)) throw new Error("Invalid resource identity");
42
- const r = JSON.stringify([s.scope, s.key, s.version]), h = d.get(r);
43
- if (h)
44
- return h.accessedAt = Date.now(), h.handle;
45
- let c = { data: void 0, refreshing: !1, stale: !0, error: void 0 };
42
+ const r = JSON.stringify([s.scope, s.key, s.version]), f = l.get(r);
43
+ if (f)
44
+ return f.accessedAt = Date.now(), f.handle;
45
+ let a = { data: void 0, restoring: !0, refreshing: !1, stale: !0, error: void 0 };
46
46
  const D = /* @__PURE__ */ new Set();
47
47
  let R = 0, p = 0, q, x, b, w;
48
48
  const m = () => {
49
- for (const n of D) n();
50
- }, E = (n) => {
51
- ++R, c = { data: n, refreshing: c.refreshing, stale: !1, error: void 0 };
49
+ for (const c of D) c();
50
+ }, E = (c) => {
51
+ ++R, a = { data: c, restoring: !1, refreshing: a.refreshing, stale: !1, error: void 0 };
52
52
  try {
53
- const u = JSON.stringify({ version: s.version, data: s.persist ? s.persist(n) : n });
53
+ const u = JSON.stringify({ version: s.version, data: s.persist ? s.persist(c) : c });
54
54
  q = u, u !== x ? k(r, u, () => q === u, () => {
55
55
  x = u;
56
- }) : o.delete(r);
56
+ }) : n.delete(r);
57
57
  } catch {
58
58
  }
59
59
  m();
60
- }, B = R, C = i.get(r).then((n) => {
61
- if (!(a || R !== B || n === null))
60
+ }, B = R, C = i.get(r).then((c) => {
61
+ if (!(o || R !== B || c === null))
62
62
  try {
63
- const u = JSON.parse(n);
63
+ const u = JSON.parse(c);
64
64
  if (u.version !== s.version) throw new Error("Unsupported snapshot");
65
65
  const A = s.decode(u.data);
66
- c = { ...c, data: A, stale: !0 }, q = n, x = n, m();
66
+ a = { ...a, data: A, stale: !0 }, q = c, x = c;
67
67
  } catch {
68
- l(() => i.remove(r));
68
+ d(() => i.remove(r));
69
69
  }
70
70
  }).catch(() => {
71
+ }).finally(() => {
72
+ o || !a.restoring || (a = { ...a, restoring: !1 }, m());
71
73
  }), M = {
72
- snapshot: () => c,
73
- subscribe: (n) => (D.add(n), () => {
74
- D.delete(n);
74
+ snapshot: () => a,
75
+ subscribe: (c) => (D.add(c), () => {
76
+ D.delete(c);
75
77
  }),
76
78
  hydrate: () => C,
77
- refresh: (n) => {
79
+ refresh: (c) => {
78
80
  if (w) return w;
79
81
  const u = ++p, A = new AbortController();
80
- return b = A, c = { ...c, refreshing: !0, error: void 0 }, m(), w = Promise.resolve().then(() => n(A.signal)).then((S) => (!a && u === p && !A.signal.aborted && E(S), S), (S) => {
81
- throw !a && u === p && !A.signal.aborted && (c = { ...c, stale: !0, error: S }, m()), S;
82
+ return b = A, a = { ...a, refreshing: !0, error: void 0 }, m(), w = Promise.resolve().then(() => c(A.signal)).then((S) => (!o && u === p && !A.signal.aborted && E(S), S), (S) => {
83
+ throw !o && u === p && !A.signal.aborted && (a = { ...a, stale: !0, error: S }, m()), S;
82
84
  }).finally(() => {
83
- u === p && (w = void 0, b = void 0, c = { ...c, refreshing: !1 }, m());
85
+ u === p && (w = void 0, b = void 0, a = { ...a, refreshing: !1 }, m());
84
86
  }), w;
85
87
  },
86
- set: (n) => {
87
- ++p, b?.abort(), w = void 0, c = { ...c, refreshing: !1 }, E(n);
88
+ set: (c) => {
89
+ ++p, b?.abort(), w = void 0, a = { ...a, refreshing: !1 }, E(c);
88
90
  },
89
- invalidate: (n = !1) => {
90
- ++R, ++p, b?.abort(), b = void 0, w = void 0, q = void 0, x = void 0, o.delete(r), c = { data: n ? void 0 : c.data, refreshing: !1, stale: !0, error: void 0 }, m(), l(() => i.remove(r));
91
+ invalidate: (c = !1) => {
92
+ ++R, ++p, b?.abort(), b = void 0, w = void 0, q = void 0, x = void 0, n.delete(r), a = { data: c ? void 0 : a.data, restoring: !1, refreshing: !1, stale: !0, error: void 0 }, m(), d(() => i.remove(r));
91
93
  }
92
94
  };
93
- return d.set(r, { scope: s.scope, accessedAt: Date.now(), evicted: () => {
95
+ return l.set(r, { scope: s.scope, accessedAt: Date.now(), evicted: () => {
94
96
  x = void 0;
95
97
  }, handle: M, stop: () => {
96
98
  ++p, b?.abort(), D.clear();
@@ -98,8 +100,8 @@ function I(f) {
98
100
  },
99
101
  flush: y,
100
102
  clearScope: async (s) => {
101
- for (const r of d.values()) r.scope === s && r.handle.invalidate(!0);
102
- await l(async () => {
103
+ for (const r of l.values()) r.scope === s && r.handle.invalidate(!0);
104
+ await d(async () => {
103
105
  for (const r of await i.list())
104
106
  try {
105
107
  JSON.parse(r.key)[0] === s && await i.remove(r.key);
@@ -108,62 +110,62 @@ function I(f) {
108
110
  });
109
111
  },
110
112
  dispose: () => {
111
- if (!a) {
112
- a = !0;
113
- for (const s of d.values()) s.stop();
114
- y(), d.clear();
113
+ if (!o) {
114
+ o = !0;
115
+ for (const s of l.values()) s.stop();
116
+ y(), l.clear();
115
117
  }
116
118
  }
117
119
  };
118
120
  }
119
- function J(f = "floe-resource-cache") {
121
+ function J(h = "floe-resource-cache") {
120
122
  let i;
121
- const g = () => i ??= new Promise((o, e) => {
122
- const t = indexedDB.open(f, 1);
123
+ const g = () => i ??= new Promise((n, e) => {
124
+ const t = indexedDB.open(h, 1);
123
125
  t.onupgradeneeded = () => t.result.createObjectStore("snapshots", { keyPath: "key" }), t.onsuccess = () => {
124
- const a = t.result;
125
- a.onversionchange = () => {
126
- a.close(), i = void 0;
127
- }, o(a);
126
+ const o = t.result;
127
+ o.onversionchange = () => {
128
+ o.close(), i = void 0;
129
+ }, n(o);
128
130
  }, t.onerror = () => {
129
131
  i = void 0, e(t.error);
130
132
  }, t.onblocked = () => {
131
133
  i = void 0, e(new Error("Resource cache database is blocked"));
132
134
  };
133
- }), d = async (o, e) => {
135
+ }), l = async (n, e) => {
134
136
  const t = await g();
135
- return new Promise((a, l) => {
136
- const v = t.transaction("snapshots", o);
137
+ return new Promise((o, d) => {
138
+ const v = t.transaction("snapshots", n);
137
139
  let y;
138
- v.oncomplete = () => a(y), v.onerror = () => l(v.error), v.onabort = () => l(v.error ?? new Error("Resource cache transaction aborted")), e(v.objectStore("snapshots"), (k) => {
140
+ v.oncomplete = () => o(y), v.onerror = () => d(v.error), v.onabort = () => d(v.error ?? new Error("Resource cache transaction aborted")), e(v.objectStore("snapshots"), (k) => {
139
141
  y = k;
140
142
  });
141
143
  });
142
144
  };
143
145
  return {
144
- get: (o) => d("readwrite", (e, t) => {
145
- const a = e.get(o);
146
- a.onsuccess = () => {
147
- const l = a.result;
148
- l && e.put({ ...l, lastAccessedAt: Date.now() }), t(l?.value ?? null);
146
+ get: (n) => l("readwrite", (e, t) => {
147
+ const o = e.get(n);
148
+ o.onsuccess = () => {
149
+ const d = o.result;
150
+ d && e.put({ ...d, lastAccessedAt: Date.now() }), t(d?.value ?? null);
149
151
  };
150
152
  }),
151
- set: (o, e) => d("readwrite", (t, a) => {
152
- t.put({ key: o, value: e, bytes: new TextEncoder().encode(e).byteLength, lastAccessedAt: Date.now() }), a(void 0);
153
+ set: (n, e) => l("readwrite", (t, o) => {
154
+ t.put({ key: n, value: e, bytes: new TextEncoder().encode(e).byteLength, lastAccessedAt: Date.now() }), o(void 0);
153
155
  }),
154
- remove: (o) => d("readwrite", (e, t) => {
155
- e.delete(o), t(void 0);
156
+ remove: (n) => l("readwrite", (e, t) => {
157
+ e.delete(n), t(void 0);
156
158
  }),
157
- list: () => d("readonly", (o, e) => {
158
- const t = [], a = o.openCursor();
159
- a.onsuccess = () => {
160
- const l = a.result;
161
- if (!l) {
159
+ list: () => l("readonly", (n, e) => {
160
+ const t = [], o = n.openCursor();
161
+ o.onsuccess = () => {
162
+ const d = o.result;
163
+ if (!d) {
162
164
  e(t);
163
165
  return;
164
166
  }
165
- const { key: v, bytes: y, lastAccessedAt: k } = l.value;
166
- t.push({ key: v, bytes: y, lastAccessedAt: k }), l.continue();
167
+ const { key: v, bytes: y, lastAccessedAt: k } = d.value;
168
+ t.push({ key: v, bytes: y, lastAccessedAt: k }), d.continue();
167
169
  };
168
170
  })
169
171
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@floegence/floe-webapp-core",
3
- "version": "0.67.1",
3
+ "version": "0.68.0",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",