@bitstillery/mithril 3.0.2 → 3.0.3
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/README.md +7 -6
- package/api/mount-redraw.ts +130 -12
- package/index.ts +26 -6
- package/package.json +1 -1
- package/render/render.ts +21 -11
- package/render/ssrState.ts +14 -1
- package/signal.ts +11 -6
- package/store.ts +12 -8
package/README.md
CHANGED
|
@@ -10,12 +10,13 @@ bun add @bitstillery/mithril
|
|
|
10
10
|
|
|
11
11
|
Mithril.js uses global `m.redraw()` — one state change re-renders **everything**. This fork adds component-level reactivity via signals so only affected components update.
|
|
12
12
|
|
|
13
|
-
| Feature | Original | This Fork
|
|
14
|
-
| ---------------- | ------------------- |
|
|
15
|
-
| Reactivity | Global `m.redraw()` | Fine-grained component updates
|
|
16
|
-
| State Management | Manual redraw calls | Signals with automatic tracking
|
|
17
|
-
| SSR | No hydration | State serialization + hydration
|
|
18
|
-
| TypeScript | Community types | Native
|
|
13
|
+
| Feature | Original | This Fork |
|
|
14
|
+
| ---------------- | ------------------- | -------------------------------- |
|
|
15
|
+
| Reactivity | Global `m.redraw()` | Fine-grained component updates |
|
|
16
|
+
| State Management | Manual redraw calls | Signals with automatic tracking |
|
|
17
|
+
| SSR | No hydration | State serialization + hydration |
|
|
18
|
+
| TypeScript | Community types | Native |
|
|
19
|
+
| Stream / request | Built-in | Removed (use signals, `fetch()`) |
|
|
19
20
|
|
|
20
21
|
Signals are opt-in. Existing Mithril code works unchanged.
|
|
21
22
|
|
package/api/mount-redraw.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import Vnode from '../render/vnode'
|
|
2
2
|
import {getSignalComponents, type Signal} from '../signal'
|
|
3
|
+
import {getStateMaps} from '../render/render'
|
|
3
4
|
|
|
4
5
|
import type {ComponentType, Children, Vnode as VnodeType} from '../render/vnode'
|
|
5
6
|
|
|
@@ -54,11 +55,9 @@ export default function mountRedrawFactory(render: Render, schedule: Schedule, c
|
|
|
54
55
|
function redrawComponent(componentOrState: ComponentType) {
|
|
55
56
|
// componentOrState might be vnode.state (from signal tracking) or component object
|
|
56
57
|
// Try to find the actual component object if it's vnode.state
|
|
57
|
-
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
component = stateToComponentMap.get(componentOrState)!
|
|
61
|
-
}
|
|
58
|
+
const {stateToComponentMap, stateToDomMap, stateToVnodeMap} = getStateMaps()
|
|
59
|
+
const resolved = stateToComponentMap.get(componentOrState)
|
|
60
|
+
const component = resolved !== undefined ? resolved : componentOrState
|
|
62
61
|
|
|
63
62
|
// First try: find element in componentToElement (for m.mount components)
|
|
64
63
|
// Check this first to ensure synchronous redraws for m.mount components
|
|
@@ -74,14 +73,31 @@ export default function mountRedrawFactory(render: Render, schedule: Schedule, c
|
|
|
74
73
|
}
|
|
75
74
|
}
|
|
76
75
|
|
|
77
|
-
// Second try:
|
|
76
|
+
// Second try: targeted redraw for nested components (stateToDomMap path)
|
|
78
77
|
// Only check this if componentToElement didn't find anything (not an m.mount component)
|
|
79
|
-
const
|
|
80
|
-
if (
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
78
|
+
const nestedElement = stateToDomMap.get(componentOrState)
|
|
79
|
+
if (nestedElement !== undefined) {
|
|
80
|
+
const vnodeInfo = stateToVnodeMap.get(componentOrState)
|
|
81
|
+
if (nestedElement?.isConnected && component != null && vnodeInfo != null) {
|
|
82
|
+
const parent = nestedElement.parentElement
|
|
83
|
+
const oldVnodes = parent != null ? (parent as any).vnodes : null
|
|
84
|
+
if (parent != null && Array.isArray(oldVnodes) && oldVnodes.length > 0) {
|
|
85
|
+
const i = oldVnodes.findIndex((v: any) => v?.state === componentOrState)
|
|
86
|
+
if (i >= 0) {
|
|
87
|
+
const {key, attrs} = vnodeInfo
|
|
88
|
+
const newVnodes = [...oldVnodes]
|
|
89
|
+
newVnodes[i] = Vnode(component, key ?? null, attrs ?? null, null, null, null)
|
|
90
|
+
try {
|
|
91
|
+
render(parent, newVnodes, redraw)
|
|
92
|
+
return
|
|
93
|
+
} catch (e) {
|
|
94
|
+
console.error(e)
|
|
95
|
+
// Fall through to full sync on error
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
// Fallback: full sync for RouterRoot correctness or when targeted path fails
|
|
85
101
|
if (!pending) {
|
|
86
102
|
pending = true
|
|
87
103
|
schedule(function () {
|
|
@@ -117,6 +133,107 @@ export default function mountRedrawFactory(render: Render, schedule: Schedule, c
|
|
|
117
133
|
}
|
|
118
134
|
}
|
|
119
135
|
|
|
136
|
+
function redrawComponents(components: Set<ComponentType>) {
|
|
137
|
+
const {stateToComponentMap, stateToDomMap, stateToVnodeMap} = getStateMaps()
|
|
138
|
+
const mountRoots: ComponentType[] = []
|
|
139
|
+
const nested = new Set<ComponentType>()
|
|
140
|
+
|
|
141
|
+
for (const c of components) {
|
|
142
|
+
const component = stateToComponentMap.get(c) ?? c
|
|
143
|
+
if (componentToElement.get(component)) {
|
|
144
|
+
mountRoots.push(c)
|
|
145
|
+
} else {
|
|
146
|
+
nested.add(c)
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
for (const c of mountRoots) {
|
|
151
|
+
redrawComponent(c)
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (nested.size === 0) return
|
|
155
|
+
|
|
156
|
+
// Group nested components by parent element
|
|
157
|
+
const parentToStates = new Map<Element, Set<ComponentType>>()
|
|
158
|
+
for (const state of nested) {
|
|
159
|
+
const el = stateToDomMap.get(state)
|
|
160
|
+
const parent = el?.parentElement
|
|
161
|
+
if (!parent || !el?.isConnected) {
|
|
162
|
+
// Can't do targeted redraw, fall back to full sync
|
|
163
|
+
if (!pending) {
|
|
164
|
+
pending = true
|
|
165
|
+
schedule(function () {
|
|
166
|
+
pending = false
|
|
167
|
+
sync()
|
|
168
|
+
})
|
|
169
|
+
}
|
|
170
|
+
return
|
|
171
|
+
}
|
|
172
|
+
let set = parentToStates.get(parent)
|
|
173
|
+
if (!set) {
|
|
174
|
+
set = new Set()
|
|
175
|
+
parentToStates.set(parent, set)
|
|
176
|
+
}
|
|
177
|
+
set.add(state)
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
for (const [parent, states] of parentToStates) {
|
|
181
|
+
const oldVnodes = (parent as any).vnodes
|
|
182
|
+
if (!Array.isArray(oldVnodes) || oldVnodes.length === 0) {
|
|
183
|
+
if (!pending) {
|
|
184
|
+
pending = true
|
|
185
|
+
schedule(function () {
|
|
186
|
+
pending = false
|
|
187
|
+
sync()
|
|
188
|
+
})
|
|
189
|
+
}
|
|
190
|
+
return
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const newVnodes = [...oldVnodes]
|
|
194
|
+
let allFound = true
|
|
195
|
+
for (const state of states) {
|
|
196
|
+
const i = oldVnodes.findIndex((v: any) => v?.state === state)
|
|
197
|
+
if (i < 0) {
|
|
198
|
+
allFound = false
|
|
199
|
+
break
|
|
200
|
+
}
|
|
201
|
+
const component = stateToComponentMap.get(state) ?? state
|
|
202
|
+
const vnodeInfo = stateToVnodeMap.get(state)
|
|
203
|
+
if (!vnodeInfo) {
|
|
204
|
+
allFound = false
|
|
205
|
+
break
|
|
206
|
+
}
|
|
207
|
+
const {key, attrs} = vnodeInfo
|
|
208
|
+
newVnodes[i] = Vnode(component, key ?? null, attrs ?? null, null, null, null)
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
if (!allFound) {
|
|
212
|
+
if (!pending) {
|
|
213
|
+
pending = true
|
|
214
|
+
schedule(function () {
|
|
215
|
+
pending = false
|
|
216
|
+
sync()
|
|
217
|
+
})
|
|
218
|
+
}
|
|
219
|
+
return
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
try {
|
|
223
|
+
render(parent, newVnodes, redraw)
|
|
224
|
+
} catch (e) {
|
|
225
|
+
console.error(e)
|
|
226
|
+
if (!pending) {
|
|
227
|
+
pending = true
|
|
228
|
+
schedule(function () {
|
|
229
|
+
pending = false
|
|
230
|
+
sync()
|
|
231
|
+
})
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
120
237
|
function redraw(component?: ComponentType) {
|
|
121
238
|
// Component-level redraw
|
|
122
239
|
if (component !== undefined) {
|
|
@@ -135,6 +252,7 @@ export default function mountRedrawFactory(render: Render, schedule: Schedule, c
|
|
|
135
252
|
}
|
|
136
253
|
|
|
137
254
|
redraw.sync = sync
|
|
255
|
+
;(redraw as any).redrawComponents = redrawComponents
|
|
138
256
|
|
|
139
257
|
// Export function to redraw components affected by signal changes
|
|
140
258
|
;(redraw as any).signal = function (signal: Signal<any>) {
|
package/index.ts
CHANGED
|
@@ -70,14 +70,33 @@ m.censor = censor
|
|
|
70
70
|
m.nextTick = nextTick
|
|
71
71
|
m.domFor = domFor
|
|
72
72
|
|
|
73
|
-
// Set up signal-to-component redraw integration
|
|
73
|
+
// Set up signal-to-component redraw integration with batching.
|
|
74
|
+
// Collects all components needing redraw in the current tick, then flushes once via queueMicrotask.
|
|
75
|
+
// Avoids N synchronous redraws when many signals fire (e.g. 50% of 800 rows).
|
|
76
|
+
let pendingRedrawComponents = new Set<any>()
|
|
77
|
+
let redrawScheduled = false
|
|
78
|
+
|
|
79
|
+
function flushPendingRedraws() {
|
|
80
|
+
const components = new Set(pendingRedrawComponents)
|
|
81
|
+
pendingRedrawComponents.clear()
|
|
82
|
+
redrawScheduled = false
|
|
83
|
+
if (components.size === 1) {
|
|
84
|
+
m.redraw(components.values().next().value)
|
|
85
|
+
} else if (components.size > 1) {
|
|
86
|
+
const fn = (m.redraw as any).redrawComponents
|
|
87
|
+
if (fn) fn(components)
|
|
88
|
+
else m.redraw()
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
74
92
|
setSignalRedrawCallback((sig: Signal<any>) => {
|
|
75
93
|
const components = getSignalComponents(sig)
|
|
76
|
-
if (components) {
|
|
77
|
-
components.forEach((
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
94
|
+
if (components && components.size > 0) {
|
|
95
|
+
components.forEach((c) => pendingRedrawComponents.add(c))
|
|
96
|
+
if (!redrawScheduled) {
|
|
97
|
+
redrawScheduled = true
|
|
98
|
+
queueMicrotask(flushPendingRedraws)
|
|
99
|
+
}
|
|
81
100
|
}
|
|
82
101
|
})
|
|
83
102
|
|
|
@@ -90,6 +109,7 @@ export {Store} from './store'
|
|
|
90
109
|
|
|
91
110
|
// Export SSR utilities
|
|
92
111
|
export {serializeStore, deserializeStore, serializeAllStates, deserializeAllStates} from './render/ssrState'
|
|
112
|
+
export type {DeserializeOptions} from './render/ssrState'
|
|
93
113
|
|
|
94
114
|
// Export SSR request context (for per-request store and state registry)
|
|
95
115
|
export {getSSRContext, runWithContext, runWithContextAsync, cleanupWatchers} from './ssrContext'
|
package/package.json
CHANGED
package/render/render.ts
CHANGED
|
@@ -9,6 +9,15 @@ import cachedAttrsIsStaticMap from './cachedAttrsIsStaticMap'
|
|
|
9
9
|
|
|
10
10
|
import type {Vnode as VnodeType, Children} from './vnode'
|
|
11
11
|
|
|
12
|
+
// Module-level maps for component/state-to-DOM tracking (used by mount-redraw for signal redraws)
|
|
13
|
+
const stateToDomMap = new WeakMap<any, Element>()
|
|
14
|
+
const stateToComponentMap = new WeakMap<any, any>()
|
|
15
|
+
const stateToVnodeMap = new WeakMap<any, {key?: string | number | null; attrs?: Record<string, any>}>()
|
|
16
|
+
|
|
17
|
+
export function getStateMaps() {
|
|
18
|
+
return {stateToDomMap, stateToComponentMap, stateToVnodeMap}
|
|
19
|
+
}
|
|
20
|
+
|
|
12
21
|
export default function renderFactory() {
|
|
13
22
|
const nameSpace: Record<string, string> = {
|
|
14
23
|
svg: 'http://www.w3.org/2000/svg',
|
|
@@ -383,8 +392,7 @@ export default function renderFactory() {
|
|
|
383
392
|
// Track component for signal dependency tracking
|
|
384
393
|
// Store mapping from vnode.state to vnode.tag (component object) for redraw
|
|
385
394
|
if (vnode.state && vnode.tag && !isHydrating) {
|
|
386
|
-
|
|
387
|
-
;(globalThis as any).__mithrilStateToComponent.set(vnode.state, vnode.tag)
|
|
395
|
+
stateToComponentMap.set(vnode.state, vnode.tag)
|
|
388
396
|
}
|
|
389
397
|
// Always track component dependencies for signal tracking (even during hydration)
|
|
390
398
|
// This allows signals to know which components depend on them
|
|
@@ -418,10 +426,10 @@ export default function renderFactory() {
|
|
|
418
426
|
vnode.dom = vnode.instance.dom
|
|
419
427
|
vnode.domSize = vnode.instance.domSize
|
|
420
428
|
|
|
421
|
-
// Store component's DOM element for fine-grained redraw (not during hydration)
|
|
429
|
+
// Store component's DOM element and vnode info for fine-grained redraw (not during hydration)
|
|
422
430
|
if (vnode.state && vnode.dom && !isHydrating) {
|
|
423
|
-
|
|
424
|
-
|
|
431
|
+
stateToDomMap.set(vnode.state, vnode.dom)
|
|
432
|
+
stateToVnodeMap.set(vnode.state, {key: vnode.key, attrs: vnode.attrs})
|
|
425
433
|
}
|
|
426
434
|
} else {
|
|
427
435
|
vnode.domSize = 0
|
|
@@ -697,8 +705,7 @@ export default function renderFactory() {
|
|
|
697
705
|
// Track component for signal dependency tracking
|
|
698
706
|
// Store mapping from vnode.state to vnode.tag (component object) for redraw
|
|
699
707
|
if (vnode.state && vnode.tag && !isHydrating) {
|
|
700
|
-
|
|
701
|
-
;(globalThis as any).__mithrilStateToComponent.set(vnode.state, vnode.tag)
|
|
708
|
+
stateToComponentMap.set(vnode.state, vnode.tag)
|
|
702
709
|
}
|
|
703
710
|
// Always track component dependencies for signal tracking (even during hydration)
|
|
704
711
|
// This allows signals to know which components depend on them
|
|
@@ -723,10 +730,10 @@ export default function renderFactory() {
|
|
|
723
730
|
vnode.dom = vnode.instance.dom
|
|
724
731
|
vnode.domSize = vnode.instance.domSize
|
|
725
732
|
|
|
726
|
-
// Store component's DOM element for fine-grained redraw (not during hydration)
|
|
733
|
+
// Store component's DOM element and vnode info for fine-grained redraw (not during hydration)
|
|
727
734
|
if (vnode.state && vnode.dom && !isHydrating) {
|
|
728
|
-
|
|
729
|
-
|
|
735
|
+
stateToDomMap.set(vnode.state, vnode.dom)
|
|
736
|
+
stateToVnodeMap.set(vnode.state, {key: vnode.key, attrs: vnode.attrs})
|
|
730
737
|
}
|
|
731
738
|
} else {
|
|
732
739
|
if (old.instance != null) removeNode(parent, old.instance)
|
|
@@ -912,9 +919,12 @@ export default function renderFactory() {
|
|
|
912
919
|
}
|
|
913
920
|
|
|
914
921
|
function onremove(vnode: any) {
|
|
915
|
-
// Clean up signal dependencies when component is removed
|
|
922
|
+
// Clean up signal dependencies and state maps when component is removed
|
|
916
923
|
if (typeof vnode.tag !== 'string' && vnode.state != null) {
|
|
917
924
|
clearComponentDependencies(vnode.state)
|
|
925
|
+
stateToDomMap.delete(vnode.state)
|
|
926
|
+
stateToComponentMap.delete(vnode.state)
|
|
927
|
+
stateToVnodeMap.delete(vnode.state)
|
|
918
928
|
}
|
|
919
929
|
if (typeof vnode.tag !== 'string' && typeof vnode.state.onremove === 'function')
|
|
920
930
|
callHook.call(vnode.state.onremove, vnode)
|
package/render/ssrState.ts
CHANGED
|
@@ -326,12 +326,18 @@ function restoreComputedProperties(state: State<any>, initial: any): void {
|
|
|
326
326
|
restore(initial, state)
|
|
327
327
|
}
|
|
328
328
|
|
|
329
|
-
export
|
|
329
|
+
export interface DeserializeOptions {
|
|
330
|
+
/** Skip deserializing these state instances (e.g. Store state that should come from localStorage) */
|
|
331
|
+
skipStates?: Set<any>
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
export function deserializeAllStates(serialized: Record<string, any>, options?: DeserializeOptions): void {
|
|
330
335
|
if (!serialized || typeof serialized !== 'object') {
|
|
331
336
|
return
|
|
332
337
|
}
|
|
333
338
|
|
|
334
339
|
const registeredStates = getRegisteredStates()
|
|
340
|
+
const skipStates = options?.skipStates
|
|
335
341
|
|
|
336
342
|
// First, deserialize all states
|
|
337
343
|
for (const [name, serializedState] of Object.entries(serialized)) {
|
|
@@ -345,6 +351,10 @@ export function deserializeAllStates(serialized: Record<string, any>): void {
|
|
|
345
351
|
continue
|
|
346
352
|
}
|
|
347
353
|
|
|
354
|
+
if (skipStates?.has(entry.state)) {
|
|
355
|
+
continue
|
|
356
|
+
}
|
|
357
|
+
|
|
348
358
|
try {
|
|
349
359
|
deserializeStore(entry.state, serializedState)
|
|
350
360
|
} catch (error) {
|
|
@@ -356,6 +366,9 @@ export function deserializeAllStates(serialized: Record<string, any>): void {
|
|
|
356
366
|
// After deserializing, restore computed properties from original initial states
|
|
357
367
|
// This ensures computed properties work after SSR deserialization
|
|
358
368
|
for (const [name, entry] of registeredStates.entries()) {
|
|
369
|
+
if (skipStates?.has(entry.state)) {
|
|
370
|
+
continue
|
|
371
|
+
}
|
|
359
372
|
try {
|
|
360
373
|
restoreComputedProperties(entry.state, entry.initial)
|
|
361
374
|
} catch (error) {
|
package/signal.ts
CHANGED
|
@@ -25,15 +25,20 @@ export function getCurrentComponent() {
|
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
export function trackComponentSignal(component: any, signal: Signal<any>) {
|
|
28
|
-
|
|
29
|
-
|
|
28
|
+
let set = componentSignalMap.get(component)
|
|
29
|
+
if (!set) {
|
|
30
|
+
set = new Set()
|
|
31
|
+
componentSignalMap.set(component, set)
|
|
30
32
|
}
|
|
31
|
-
|
|
33
|
+
if (set.has(signal)) return
|
|
34
|
+
set.add(signal)
|
|
32
35
|
|
|
33
|
-
|
|
34
|
-
|
|
36
|
+
let compSet = signalComponentMap.get(signal)
|
|
37
|
+
if (!compSet) {
|
|
38
|
+
compSet = new Set()
|
|
39
|
+
signalComponentMap.set(signal, compSet)
|
|
35
40
|
}
|
|
36
|
-
|
|
41
|
+
compSet.add(component)
|
|
37
42
|
}
|
|
38
43
|
|
|
39
44
|
export function getComponentSignals(component: any): Set<Signal<any>> | undefined {
|
package/store.ts
CHANGED
|
@@ -144,9 +144,13 @@ export class Store<T extends Record<string, any> = Record<string, any>> {
|
|
|
144
144
|
private lookup_verify_interval: number | null = null
|
|
145
145
|
private lookup_ttl: number
|
|
146
146
|
private computedPropertiesSetup?: () => void
|
|
147
|
+
private storageKey: string
|
|
148
|
+
private tabStorageKey: string
|
|
147
149
|
|
|
148
|
-
constructor(options: {lookup_ttl?: number} = {lookup_ttl: DEFAULT_LOOKUP_TTL}) {
|
|
150
|
+
constructor(options: {lookup_ttl?: number; storageKey?: string; tabStorageKey?: string} = {lookup_ttl: DEFAULT_LOOKUP_TTL}) {
|
|
149
151
|
this.lookup_ttl = options.lookup_ttl || DEFAULT_LOOKUP_TTL
|
|
152
|
+
this.storageKey = options.storageKey ?? 'store'
|
|
153
|
+
this.tabStorageKey = options.tabStorageKey ?? this.storageKey
|
|
150
154
|
// Initialize with empty state, will be loaded later (ADR-0013: defer computeds until ready() is called)
|
|
151
155
|
const instanceName = `store.instance.${storeInstanceCounter++}`
|
|
152
156
|
this.stateInstance = state({} as T, instanceName, {deferComputed: true})
|
|
@@ -271,8 +275,8 @@ export class Store<T extends Record<string, any> = Record<string, any>> {
|
|
|
271
275
|
|
|
272
276
|
load(saved: Partial<T>, temporary: Partial<T>, tab: Partial<T> = {} as Partial<T>, session: Partial<T> = {} as Partial<T>) {
|
|
273
277
|
const restored_state = {
|
|
274
|
-
tab: this.get_tab_storage(
|
|
275
|
-
store: this.get(
|
|
278
|
+
tab: this.get_tab_storage(this.tabStorageKey),
|
|
279
|
+
store: this.get(this.storageKey),
|
|
276
280
|
}
|
|
277
281
|
|
|
278
282
|
this.templates = {
|
|
@@ -381,7 +385,7 @@ export class Store<T extends Record<string, any> = Record<string, any>> {
|
|
|
381
385
|
|
|
382
386
|
// Save to localStorage (saved state)
|
|
383
387
|
if (saveSaved && this.templates.saved) {
|
|
384
|
-
this.set(
|
|
388
|
+
this.set(this.storageKey, this.blueprint(statePlain, copy_object(this.templates.saved)))
|
|
385
389
|
}
|
|
386
390
|
|
|
387
391
|
// Save to sessionStorage (tab state)
|
|
@@ -397,18 +401,18 @@ export class Store<T extends Record<string, any> = Record<string, any>> {
|
|
|
397
401
|
if (isState(tabState)) {
|
|
398
402
|
const tabPlain = serializeStore(tabState)
|
|
399
403
|
// blueprint expects both arguments to have the same structure
|
|
400
|
-
this.set_tab(
|
|
404
|
+
this.set_tab(this.tabStorageKey, this.blueprint(tabPlain, copy_object(tabTemplate)))
|
|
401
405
|
} else {
|
|
402
406
|
// Plain object tab
|
|
403
|
-
this.set_tab(
|
|
407
|
+
this.set_tab(this.tabStorageKey, this.blueprint(tabState, copy_object(tabTemplate)))
|
|
404
408
|
}
|
|
405
409
|
} else {
|
|
406
410
|
// No tab state - save empty tab based on template structure
|
|
407
411
|
const tabTemplate = (this.templates.tab as any).tab || this.templates.tab
|
|
408
412
|
if (tabTemplate && Object.keys(tabTemplate).length > 0) {
|
|
409
|
-
this.set_tab(
|
|
413
|
+
this.set_tab(this.tabStorageKey, this.blueprint({} as any, copy_object(tabTemplate)))
|
|
410
414
|
} else {
|
|
411
|
-
this.set_tab(
|
|
415
|
+
this.set_tab(this.tabStorageKey, {})
|
|
412
416
|
}
|
|
413
417
|
}
|
|
414
418
|
}
|