@poveste/plugin-vue 0.11.0 → 0.12.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.
@@ -2,9 +2,9 @@ import { useRenderContext as e } from "./render-context.js";
2
2
  import { syncVariantAutoProps as t } from "./auto-props.js";
3
3
  import { syncStateBundledAndExternal as n, toRawDeep as r } from "./util.js";
4
4
  import { computed as i, defineComponent as a, getCurrentInstance as o, inject as s, onBeforeUnmount as c, useAttrs as l } from "vue";
5
- import { applyState as u } from "@poveste/shared";
5
+ import { applyState as u, autoPropsStateKeys as d } from "@poveste/shared";
6
6
  //#region src/client/app/Variant.ts
7
- var d = a({
7
+ var f = a({
8
8
  name: "Variant",
9
9
  __povesteType: "variant",
10
10
  props: {
@@ -38,47 +38,47 @@ var d = a({
38
38
  }
39
39
  },
40
40
  async setup(a) {
41
- let d = l(), f = o(), p = s("story"), m = s("implicitState"), h = e(), g, _, v, y;
41
+ let f = l(), p = o(), m = s("story"), h = s("implicitState"), g = e(), _, v, y, b;
42
42
  c(() => {
43
- v?.stop(), y?.stop();
43
+ y?.stop(), b?.stop();
44
44
  });
45
- let b = i(() => d.variant);
46
- if (h?.mode !== "render" && typeof a.initState == "function" && b.value) {
45
+ let x = i(() => f.variant);
46
+ if (g?.mode !== "render" && typeof a.initState == "function" && x.value) {
47
47
  let e = await a.initState();
48
- u(b.value.state, r(e));
48
+ u(x.value.state, r(e));
49
49
  }
50
- h?.mode !== "render" && b.value && m && (y = n(b.value.state, m()));
51
- function x(e) {
50
+ g?.mode !== "render" && x.value && h && (b = n(x.value.state, h(), d));
51
+ function S(e) {
52
52
  Object.assign(e, {
53
- slots: () => f.proxy.$slots,
53
+ slots: () => p.proxy.$slots,
54
54
  source: a.source,
55
55
  responsiveDisabled: a.responsiveDisabled,
56
56
  autoPropsDisabled: a.autoPropsDisabled,
57
57
  setupApp: a.setupApp,
58
58
  meta: a.meta,
59
59
  configReady: !0
60
- }), !a.implicit && !p.value?.meta?.hasVariantChildComponents && (p.value.meta || (p.value.meta = {}), Object.assign(p.value.meta, { hasVariantChildComponents: !0 }));
60
+ }), !a.implicit && !m.value?.meta?.hasVariantChildComponents && (m.value.meta || (m.value.meta = {}), Object.assign(m.value.meta, { hasVariantChildComponents: !0 }));
61
61
  }
62
- function S() {
63
- return d.variant ? d.variant : h?.mode === "render" ? (_ || (_ = p.value?.variants[h.nextVariantIndex.value], h.nextVariantIndex.value++), _) : null;
64
- }
65
- function C(e) {
66
- if (h.slotName === "controls") return f.proxy.$slots.controls?.({ state: h.externalState }) ?? null;
67
- if (h.slotName !== "default") return null;
68
- let n = f.proxy.$slots.default?.({ state: h.externalState }) ?? null;
69
- return n && !e.autoPropsDisabled && (g = t(e, n, h.externalState, g)), n;
62
+ function C() {
63
+ return f.variant ? f.variant : g?.mode === "render" ? (v || (v = m.value?.variants[g.nextVariantIndex.value], g.nextVariantIndex.value++), v) : null;
70
64
  }
71
65
  function w(e) {
72
- if (!m) return;
73
- let t = h?.mode === "render" && h.currentVariant?.id === e.id;
74
- t && !v ? v = n(e.state, m()) : !t && v && (v.stop(), v = null);
66
+ if (g.slotName === "controls") return p.proxy.$slots.controls?.({ state: g.externalState }) ?? null;
67
+ if (g.slotName !== "default") return null;
68
+ let n = p.proxy.$slots.default?.({ state: g.externalState }) ?? null;
69
+ return n && !e.autoPropsDisabled && (_ = t(e, n, g.externalState, _)), n;
70
+ }
71
+ function T(e) {
72
+ if (!h) return;
73
+ let t = g?.mode === "render" && g.currentVariant?.id === e.id;
74
+ t && !y ? y = n(e.state, h(), d) : !t && y && (y.stop(), y = null);
75
75
  }
76
- return b.value && x(b.value), {
77
- renderContext: h,
78
- renderVariantSlot: C,
79
- resolveVariant: S,
80
- syncRenderVariantState: w,
81
- updateVariant: x
76
+ return x.value && S(x.value), {
77
+ renderContext: g,
78
+ renderVariantSlot: w,
79
+ resolveVariant: C,
80
+ syncRenderVariantState: T,
81
+ updateVariant: S
82
82
  };
83
83
  },
84
84
  render() {
@@ -87,4 +87,4 @@ var d = a({
87
87
  }
88
88
  });
89
89
  //#endregion
90
- export { d as default };
90
+ export { f as default };
@@ -9,42 +9,61 @@ function r(e, t) {
9
9
  let n = [];
10
10
  return i(e, t, { index: 0 }, n), n.filter((e) => e.props.length);
11
11
  }
12
- function i(t, n, r, o) {
13
- for (let s of a(t)) if (s) {
14
- if (typeof s.type == "object") {
15
- let t = r.index++, i = [];
16
- for (let e in s.type.props) {
17
- let r = s.type.props[e], a, o;
18
- r && (a = (Array.isArray(r.type) ? r.type : typeof r == "function" ? [r] : [r.type]).map((e) => {
19
- switch (e) {
20
- case String: return "string";
21
- case Number: return "number";
22
- case Boolean: return "boolean";
23
- case Object: return "object";
24
- case Array: return "array";
25
- default: return "unknown";
26
- }
27
- }), o = typeof r.default == "function" ? r.default.toString() : r.default), i.push({
12
+ function i(t, n, r, l) {
13
+ for (let u of c(t)) if (u) {
14
+ if (typeof u.type == "object") {
15
+ let t = r.index++, i = [], c = u.type.props, d = Array.isArray(c) ? c : Object.keys(c ?? {});
16
+ for (let e of d) {
17
+ let r = Array.isArray(c) ? void 0 : c[e], l, d;
18
+ if (r && (l = (Array.isArray(r.type) ? r.type : typeof r == "function" ? [r] : [r.type]).map(a), d = typeof r.default == "function" ? r.default.toString() : r.default), !l || l.every((e) => e === "unknown")) {
19
+ let t = o(s(u, e) ?? r?.default);
20
+ t !== "unknown" && (l = [t]);
21
+ }
22
+ i.push({
28
23
  name: e,
29
- types: a,
24
+ types: l,
30
25
  required: r?.required,
31
- default: o
32
- }), n?._hPropState?.[t]?.[e] != null && (s.props ||= {}, s.props[e] = n._hPropState[t][e], s.dynamicProps ||= [], s.dynamicProps.includes(e) || s.dynamicProps.push(e));
26
+ default: d
27
+ }), n?._hPropState?.[t]?.[e] != null && (u.props ||= {}, u.props[e] = n._hPropState[t][e], u.dynamicProps ||= [], u.dynamicProps.includes(e) || u.dynamicProps.push(e));
33
28
  }
34
- o.push({
35
- name: e(s),
29
+ l.push({
30
+ name: e(u),
36
31
  index: t,
37
32
  props: i
38
33
  });
39
34
  }
40
- Array.isArray(s.children) && i(s.children, n, r, o);
35
+ Array.isArray(u.children) && i(u.children, n, r, l);
41
36
  }
42
37
  }
43
38
  function a(e) {
39
+ switch (e) {
40
+ case String: return "string";
41
+ case Number: return "number";
42
+ case Boolean: return "boolean";
43
+ case Object: return "object";
44
+ case Array: return "array";
45
+ default: return "unknown";
46
+ }
47
+ }
48
+ function o(e) {
49
+ if (Array.isArray(e)) return "array";
50
+ switch (typeof e) {
51
+ case "string": return "string";
52
+ case "number": return "number";
53
+ case "boolean": return "boolean";
54
+ case "object": return e === null ? "unknown" : "object";
55
+ default: return "unknown";
56
+ }
57
+ }
58
+ function s(e, t) {
59
+ let n = e.props;
60
+ if (n) return t in n ? n[t] : n[t.replace(/\B([A-Z])/g, "-$1").toLowerCase()];
61
+ }
62
+ function c(e) {
44
63
  if (!Array.isArray(e)) return e == null ? [] : [e];
45
64
  let t = [];
46
- for (let n of e) Array.isArray(n) ? t.push(...a(n)) : n != null && t.push(n);
65
+ for (let n of e) Array.isArray(n) ? t.push(...c(n)) : n != null && t.push(n);
47
66
  return t;
48
67
  }
49
68
  //#endregion
50
- export { n as syncVariantAutoProps };
69
+ export { o as inferredType, s as passedValue, n as syncVariantAutoProps };
@@ -42,24 +42,30 @@ function f(e, t, n = /* @__PURE__ */ new WeakMap()) {
42
42
  });
43
43
  }
44
44
  function p(e, t) {
45
- let r = s(), i = n(() => e, (e) => {
45
+ if (!t.length) return e;
46
+ let n = { ...e };
47
+ for (let e of t) delete n[e];
48
+ return n;
49
+ }
50
+ function m(e, t, r = []) {
51
+ let i = s(), c = n(() => e, (e) => {
46
52
  if (e == null) return;
47
- let n = r.take(d(e));
53
+ let n = i.take(p(d(e), r));
48
54
  n && o(t, n);
49
55
  }, {
50
56
  deep: !0,
51
57
  immediate: !0
52
- }), c = a(() => t, (t) => {
58
+ }), u = a(() => t, (t) => {
53
59
  if (t == null) return;
54
- let n = r.take(l(t));
60
+ let n = i.take(p(l(t), r));
55
61
  n && o(e, n);
56
62
  }, {
57
63
  deep: !0,
58
64
  immediate: !0
59
65
  });
60
66
  return { stop() {
61
- i(), c();
67
+ c(), u();
62
68
  } };
63
69
  }
64
70
  //#endregion
65
- export { d as _toRawDeep, p as syncStateBundledAndExternal, l as toRawDeep };
71
+ export { d as _toRawDeep, m as syncStateBundledAndExternal, l as toRawDeep };
@@ -1,2 +1,5 @@
1
1
  import type { Variant } from '@poveste/shared';
2
2
  export declare function syncVariantAutoProps(variant: Variant, vnodes: any, externalState: Variant['state'], lastSnapshot: string): string;
3
+ /** What the value itself says, when the declaration no longer says anything. */
4
+ export declare function inferredType(value: unknown): string;
5
+ export declare function passedValue(vnode: any, key: string): unknown;
@@ -10,7 +10,8 @@ export declare function _toRawDeep(val: any, seen?: WeakMap<WeakKey, any>): any;
10
10
  * Synchronize states between the bundled and external/user versions of Vue
11
11
  * @param bundledState Reactive state created with the bundled Vue
12
12
  * @param externalState Reactive state created with the external/user Vue
13
+ * @param omit Keys neither side may learn from the other
13
14
  */
14
- export declare function syncStateBundledAndExternal(bundledState: any, externalState: any): {
15
+ export declare function syncStateBundledAndExternal(bundledState: any, externalState: any, omit?: string[]): {
15
16
  stop(): void;
16
17
  };
package/package.json CHANGED
@@ -1,10 +1,11 @@
1
1
  {
2
2
  "name": "@poveste/plugin-vue",
3
3
  "type": "module",
4
- "version": "0.11.0",
4
+ "version": "0.12.0",
5
5
  "description": "Poveste plugin for Vue.js support",
6
6
  "author": {
7
- "name": "Sorin Gitlan"
7
+ "name": "Sorin Gitlan",
8
+ "url": "https://x.com/50rayn"
8
9
  },
9
10
  "license": "MIT",
10
11
  "homepage": "https://poveste.dev",
@@ -69,26 +70,26 @@
69
70
  "node": "^22.22.2 || ^24.15.0 || >=26.0.0"
70
71
  },
71
72
  "peerDependencies": {
72
- "vue": "^3.5.26",
73
- "poveste": "^0.11.0"
73
+ "poveste": "^0.12.0",
74
+ "vue": "^3.5.26"
74
75
  },
75
76
  "dependencies": {
77
+ "@poveste/controls": "^0.12.0",
78
+ "@poveste/shared": "^0.12.0",
79
+ "@poveste/vendors": "^0.12.0",
76
80
  "change-case": "^5.4.4",
77
81
  "globby": "^14.0.2",
78
82
  "launch-editor": "^2.9.1",
79
- "pathe": "^1.1.2",
80
- "@poveste/controls": "^0.11.0",
81
- "@poveste/shared": "^0.11.0",
82
- "@poveste/vendors": "^0.11.0"
83
+ "pathe": "^1.1.2"
83
84
  },
84
85
  "devDependencies": {
85
86
  "@types/node": "^22.10.1",
86
87
  "concurrently": "^9.1.0",
88
+ "poveste": "0.12.0",
87
89
  "typescript": "5.6.3",
88
90
  "vite": "^8.2.0",
89
91
  "vitest": "^4.1.10",
90
- "vue": "^3.5.26",
91
- "poveste": "0.11.0"
92
+ "vue": "^3.5.26"
92
93
  },
93
94
  "scripts": {
94
95
  "build": "rimraf dist && vite build && tsc -d -P tsconfig.build.json && pnpm run build:types",
@@ -1,6 +1,6 @@
1
1
  import type { Variant } from '@poveste/shared'
2
2
  import type { ComputedRef, PropType } from 'vue'
3
- import { applyState } from '@poveste/shared'
3
+ import { applyState, autoPropsStateKeys } from '@poveste/shared'
4
4
  import { computed, defineComponent, getCurrentInstance, inject, onBeforeUnmount, useAttrs } from 'vue'
5
5
  import { syncVariantAutoProps } from './auto-props.js'
6
6
  import { useRenderContext } from './render-context.js'
@@ -84,7 +84,7 @@ export default defineComponent({
84
84
  }
85
85
 
86
86
  if (renderContext?.mode !== 'render' && mountVariant.value && implicitState) {
87
- mountStateSync = syncStateBundledAndExternal(mountVariant.value.state, implicitState())
87
+ mountStateSync = syncStateBundledAndExternal(mountVariant.value.state, implicitState(), autoPropsStateKeys)
88
88
  }
89
89
 
90
90
  function updateVariant(variant: Variant) {
@@ -162,7 +162,7 @@ export default defineComponent({
162
162
  && renderContext.currentVariant?.id === variant.id
163
163
 
164
164
  if (shouldSync && !renderStateSync) {
165
- renderStateSync = syncStateBundledAndExternal(variant.state, implicitState())
165
+ renderStateSync = syncStateBundledAndExternal(variant.state, implicitState(), autoPropsStateKeys)
166
166
  }
167
167
  else if (!shouldSync && renderStateSync) {
168
168
  renderStateSync.stop()
@@ -0,0 +1,49 @@
1
+ import { describe, expect, it } from 'vitest'
2
+ import { inferredType, passedValue } from '../auto-props.js'
3
+
4
+ /*
5
+ * A production build erases the runtime types Vue infers from a type-only
6
+ * `defineProps`, so the declaration says nothing and the value has to (#490).
7
+ */
8
+ describe('the type a value implies', () => {
9
+ it.each([
10
+ ['a string', 'hello', 'string'],
11
+ ['a number', 42, 'number'],
12
+ ['zero', 0, 'number'],
13
+ ['a boolean', false, 'boolean'],
14
+ ['an array', [1, 2], 'array'],
15
+ ['an object', { a: 1 }, 'object'],
16
+ ])('reads %s', (_, value, expected) => {
17
+ expect(inferredType(value)).toBe(expected)
18
+ })
19
+
20
+ // Nothing to go on, and guessing would pick a control the prop cannot hold.
21
+ // A factory default arrives here as the function it is, which is why nothing
22
+ // upstream has to strip it first.
23
+ it.each([
24
+ ['null', null],
25
+ ['undefined', undefined],
26
+ ['a factory default', () => []],
27
+ ])('declines to guess from %s', (_, value) => {
28
+ expect(inferredType(value)).toBe('unknown')
29
+ })
30
+ })
31
+
32
+ describe('the value a vnode passed', () => {
33
+ it('finds a prop under the name it was declared with', () => {
34
+ expect(passedValue({ props: { label: 'x' } }, 'label')).toBe('x')
35
+ })
36
+
37
+ // `vnode.type.props` is keyed as declared, `vnode.props` as authored.
38
+ it('finds a multi-word prop written in kebab case', () => {
39
+ expect(passedValue({ props: { 'my-label': 'x' } }, 'myLabel')).toBe('x')
40
+ })
41
+
42
+ it('prefers the declared spelling when both are present', () => {
43
+ expect(passedValue({ props: { 'myLabel': 'camel', 'my-label': 'kebab' } }, 'myLabel')).toBe('camel')
44
+ })
45
+
46
+ it('is undefined when the component was given no props at all', () => {
47
+ expect(passedValue({}, 'label')).toBeUndefined()
48
+ })
49
+ })
@@ -1,3 +1,4 @@
1
+ import { autoPropsStateKeys } from '@poveste/shared'
1
2
  import { nextTick as bundledNextTick, reactive as bundledReactive } from '@poveste/vendors/vue'
2
3
  import { describe, expect, it } from 'vitest'
3
4
  import { nextTick, reactive, watch } from 'vue'
@@ -17,10 +18,10 @@ async function settle() {
17
18
  }
18
19
  }
19
20
 
20
- function setup(initial: Record<string, any>) {
21
+ function setup(initial: Record<string, any>, omit: string[] = []) {
21
22
  const bundled = bundledReactive(structuredClone(initial))
22
23
  const external = reactive(structuredClone(initial))
23
- const sync = syncStateBundledAndExternal(bundled, external)
24
+ const sync = syncStateBundledAndExternal(bundled, external, omit)
24
25
  return { bundled, external, sync }
25
26
  }
26
27
 
@@ -339,3 +340,48 @@ describe('syncStateBundledAndExternal', () => {
339
340
  sync.stop()
340
341
  })
341
342
  })
343
+
344
+ /*
345
+ * A variant's state is synced with the story's implicit state, which every
346
+ * variant shares — so anything that crosses reaches all of them. `_hPropState`
347
+ * is keyed by a component's index within one variant, so it must not (#473).
348
+ */
349
+ describe('keys the sync is told to omit', () => {
350
+ it('does not carry an auto-props edit to the other side', async () => {
351
+ const { bundled, external, sync } = setup({ _hPropState: {}, label: 'a' }, autoPropsStateKeys)
352
+ await settle()
353
+
354
+ external._hPropState = { 0: { name: 'Bender' } }
355
+ await settle()
356
+
357
+ expect(bundled._hPropState).toEqual({})
358
+
359
+ sync.stop()
360
+ })
361
+
362
+ it('goes on carrying everything else', async () => {
363
+ const { bundled, external, sync } = setup({ _hPropState: {}, label: 'a' }, autoPropsStateKeys)
364
+ await settle()
365
+
366
+ external.label = 'edited'
367
+ await settle()
368
+
369
+ expect(bundled.label).toBe('edited')
370
+
371
+ sync.stop()
372
+ })
373
+
374
+ // The reason the sync exists: a variant with no `initState` picks up the
375
+ // story's own state, and omitting the bookkeeping must not cost that.
376
+ it('still carries a key the story owns', async () => {
377
+ const { bundled, external, sync } = setup({}, autoPropsStateKeys)
378
+ await settle()
379
+
380
+ bundled.fromStory = 'yes'
381
+ await settle()
382
+
383
+ expect(external.fromStory).toBe('yes')
384
+
385
+ sync.stop()
386
+ })
387
+ })
@@ -45,8 +45,13 @@ function visitVNodes(vnodes: any, externalState: Variant['state'], traversalStat
45
45
  const index = traversalState.index++
46
46
  const propDefs: PropDefinition[] = []
47
47
 
48
- for (const key in vnode.type.props) {
49
- const prop = vnode.type.props[key]
48
+ // `for…in` over the array form yields index strings, so every control was
49
+ // named `0` and the prop's own name never appeared (#498).
50
+ const declared = vnode.type.props
51
+ const names = Array.isArray(declared) ? declared : Object.keys(declared ?? {})
52
+
53
+ for (const key of names) {
54
+ const prop = Array.isArray(declared) ? undefined : declared[key]
50
55
  let types
51
56
  let defaultValue
52
57
 
@@ -57,28 +62,26 @@ function visitVNodes(vnodes: any, externalState: Variant['state'], traversalStat
57
62
  ? [prop]
58
63
  : [prop.type]
59
64
 
60
- types = rawTypes.map((type) => {
61
- switch (type) {
62
- case String:
63
- return 'string'
64
- case Number:
65
- return 'number'
66
- case Boolean:
67
- return 'boolean'
68
- case Object:
69
- return 'object'
70
- case Array:
71
- return 'array'
72
- default:
73
- return 'unknown'
74
- }
75
- })
65
+ types = rawTypes.map(declaredType)
76
66
 
77
67
  defaultValue = typeof prop.default === 'function'
78
68
  ? prop.default.toString()
79
69
  : prop.default
80
70
  }
81
71
 
72
+ // A production build erases the runtime types Vue infers from a
73
+ // type-only `defineProps`, leaving `props: { label: {} }` (#490), and
74
+ // the array form declares none to begin with (#498). Defaults survive
75
+ // erasure and the passed value is still there, and either is enough to
76
+ // pick a control. A factory default reaches `inferredType` as the
77
+ // function it is, and is declined there with everything else untyped.
78
+ if (!types || types.every(type => type === 'unknown')) {
79
+ const inferred = inferredType(passedValue(vnode, key) ?? prop?.default)
80
+ if (inferred !== 'unknown') {
81
+ types = [inferred]
82
+ }
83
+ }
84
+
82
85
  propDefs.push({
83
86
  name: key,
84
87
  types,
@@ -115,6 +118,52 @@ function visitVNodes(vnodes: any, externalState: Variant['state'], traversalStat
115
118
  }
116
119
  }
117
120
 
121
+ function declaredType(type: unknown): string {
122
+ switch (type) {
123
+ case String:
124
+ return 'string'
125
+ case Number:
126
+ return 'number'
127
+ case Boolean:
128
+ return 'boolean'
129
+ case Object:
130
+ return 'object'
131
+ case Array:
132
+ return 'array'
133
+ default:
134
+ return 'unknown'
135
+ }
136
+ }
137
+
138
+ /** What the value itself says, when the declaration no longer says anything. */
139
+ export function inferredType(value: unknown): string {
140
+ if (Array.isArray(value)) {
141
+ return 'array'
142
+ }
143
+ switch (typeof value) {
144
+ case 'string':
145
+ return 'string'
146
+ case 'number':
147
+ return 'number'
148
+ case 'boolean':
149
+ return 'boolean'
150
+ case 'object':
151
+ return value === null ? 'unknown' : 'object'
152
+ default:
153
+ return 'unknown'
154
+ }
155
+ }
156
+
157
+ // `vnode.type.props` is keyed as declared and `vnode.props` as authored, so a
158
+ // multi-word prop written `my-label` is not found under `myLabel`.
159
+ export function passedValue(vnode: any, key: string): unknown {
160
+ const props = vnode.props
161
+ if (!props) {
162
+ return undefined
163
+ }
164
+ return key in props ? props[key] : props[key.replace(/\B([A-Z])/g, '-$1').toLowerCase()]
165
+ }
166
+
118
167
  function normalizeVNodes(vnodes: any) {
119
168
  if (!Array.isArray(vnodes)) {
120
169
  return vnodes == null ? [] : [vnodes]
@@ -84,12 +84,26 @@ function _toRawObject(obj: Record<any, any>, target: Record<any, any>, seen = ne
84
84
  })
85
85
  }
86
86
 
87
+ // Kept out of the baseline rather than out of the write, so the far side is
88
+ // never told these changed and an echo cannot carry them back.
89
+ function without(state: Record<string, any>, omit: string[]): Record<string, any> {
90
+ if (!omit.length) {
91
+ return state
92
+ }
93
+ const kept = { ...state }
94
+ for (const key of omit) {
95
+ delete kept[key]
96
+ }
97
+ return kept
98
+ }
99
+
87
100
  /**
88
101
  * Synchronize states between the bundled and external/user versions of Vue
89
102
  * @param bundledState Reactive state created with the bundled Vue
90
103
  * @param externalState Reactive state created with the external/user Vue
104
+ * @param omit Keys neither side may learn from the other
91
105
  */
92
- export function syncStateBundledAndExternal(bundledState, externalState) {
106
+ export function syncStateBundledAndExternal(bundledState, externalState, omit: string[] = []) {
93
107
  // Each side is asked only for what *it* changed, and that is all that crosses.
94
108
  //
95
109
  // Both watchers used to mirror the whole of their side. That works while the
@@ -109,7 +123,7 @@ export function syncStateBundledAndExternal(bundledState, externalState) {
109
123
 
110
124
  const _stop = _watch(() => bundledState, (value) => {
111
125
  if (value == null) return
112
- const changes = baseline.take(_toRawDeep(value))
126
+ const changes = baseline.take(without(_toRawDeep(value), omit))
113
127
  if (changes) applyState(externalState, changes)
114
128
  }, {
115
129
  deep: true,
@@ -118,7 +132,7 @@ export function syncStateBundledAndExternal(bundledState, externalState) {
118
132
 
119
133
  const stop = watch(() => externalState, (value) => {
120
134
  if (value == null) return
121
- const changes = baseline.take(toRawDeep(value))
135
+ const changes = baseline.take(without(toRawDeep(value), omit))
122
136
  if (changes) applyState(bundledState, changes)
123
137
  }, {
124
138
  deep: true,