@px-petals/roku.motion-web 1.0.0 → 1.1.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.
Files changed (77) hide show
  1. package/bands/bands.js +3 -1
  2. package/bundle/bundle.js +56 -0
  3. package/list/list.js +12 -1
  4. package/lottie/lottie.layers.js +64 -11
  5. package/lottie/lottie.paint.js +30 -0
  6. package/lottie/lottie.paths.js +19 -4
  7. package/lottie/lottie.props.js +18 -8
  8. package/lottie/lottie.shapes.js +2 -1
  9. package/mixer/mixer.js +31 -8
  10. package/mixer/mixer.mix.js +17 -0
  11. package/motion/motion.convert.js +32 -1
  12. package/motion/motion.flow.js +20 -2
  13. package/motion/motion.machine.js +28 -13
  14. package/motion/motion.values.js +13 -9
  15. package/motion.js +196 -20
  16. package/package.json +10 -16
  17. package/player/player.js +25 -5
  18. package/readme.md +78 -3
  19. package/script/luau/lib/base/base.js +202 -0
  20. package/script/luau/lib/base/base.table.js +189 -0
  21. package/script/luau/lib/lib.args.js +152 -0
  22. package/script/luau/lib/lib.js +23 -0
  23. package/script/luau/lib/math/math.bit32.js +144 -0
  24. package/script/luau/lib/math/math.js +288 -0
  25. package/script/luau/lib/math/math.vector.js +125 -0
  26. package/script/luau/lib/string/string.format.js +143 -0
  27. package/script/luau/lib/string/string.js +160 -0
  28. package/script/luau/lib/string/string.pattern.js +505 -0
  29. package/script/luau/load/load.js +362 -0
  30. package/script/luau/load/load.ops.js +79 -0
  31. package/script/luau/luau.convert.js +259 -0
  32. package/script/luau/luau.js +132 -0
  33. package/script/luau/luau.md +111 -0
  34. package/script/luau/vm/vm.js +167 -0
  35. package/script/luau/vm/vm.meta.js +401 -0
  36. package/script/luau/vm/vm.num.js +236 -0
  37. package/script/luau/vm/vm.run.js +842 -0
  38. package/script/luau/vm/vm.value.js +406 -0
  39. package/script/play/play.js +199 -0
  40. package/script/play/play.objects.js +295 -0
  41. package/script/play/play.scope.js +91 -0
  42. package/script/rive/api/api.statics.js +31 -0
  43. package/script/rive/api/api.type.js +188 -0
  44. package/script/rive/api/artboard/artboard.js +89 -0
  45. package/script/rive/api/assets/assets.js +72 -0
  46. package/script/rive/api/data/data.value.js +99 -0
  47. package/script/rive/api/draw/draw.contours.js +163 -0
  48. package/script/rive/api/draw/draw.measure.js +196 -0
  49. package/script/rive/api/draw/draw.paint.js +235 -0
  50. package/script/rive/api/draw/draw.path.js +153 -0
  51. package/script/rive/api/draw/draw.renderer.js +117 -0
  52. package/script/rive/api/math/math.color.js +96 -0
  53. package/script/rive/api/math/math.mat2d.js +180 -0
  54. package/script/rive/api/math/math.vector.js +121 -0
  55. package/script/rive/api/media/media.audio.js +113 -0
  56. package/script/rive/api/media/media.draw.js +73 -0
  57. package/script/rive/api/media/media.image.js +61 -0
  58. package/script/rive/api/media/media.mesh.js +50 -0
  59. package/script/rive/api/pointer/pointer.event.js +64 -0
  60. package/script/rive/api/pointer/pointer.route.js +69 -0
  61. package/script/rive/api/text/text.font.js +129 -0
  62. package/script/rive/api/text/text.js +278 -0
  63. package/script/rive/api/text/text.layout.js +259 -0
  64. package/script/rive/context/context.js +76 -0
  65. package/script/rive/context/context.memory.js +196 -0
  66. package/script/rive/context/context.viewmodel.js +302 -0
  67. package/script/rive/context/lists/lists.js +160 -0
  68. package/script/rive/context/lists/lists.values.js +281 -0
  69. package/script/rive/host/host.globals.js +44 -0
  70. package/script/rive/host/host.js +533 -0
  71. package/script/rive/host/host.node.js +45 -0
  72. package/script/rive/rive.js +23 -0
  73. package/script/rive/rive.md +180 -0
  74. package/script/script.js +11 -0
  75. package/skin/skin.deform.js +514 -0
  76. package/skin/skin.ik.js +260 -0
  77. package/skin/skin.js +297 -0
@@ -0,0 +1,259 @@
1
+ /**
2
+ * Lays styled runs out in lines: greedy wrapping at the box width (at spaces,
3
+ * or anywhere with `wordBreak: 'breakAll'`, and inside a word wider than the
4
+ * box), alignment, line and paragraph spacing, an ellipsis for fixed text that
5
+ * overflows. Plain numbers and arrays in and out, so the BrightScript side can
6
+ * follow it. It does not shape (no ligatures, kerning or bidi): a glyph is a
7
+ * codepoint with the advance its font gives it plus the run's letter spacing.
8
+ */
9
+
10
+ /**
11
+ * @typedef {object} Run
12
+ * @property {string} text
13
+ * @property {import('./text.font.js').FontInfo} font completed
14
+ * @property {number} size
15
+ * @property {number} lineHeight 0 for the font's own
16
+ * @property {number} letterSpacing
17
+ */
18
+
19
+ /**
20
+ * @typedef {object} LayoutOptions
21
+ * @property {string} sizing `autoWidth`, `autoHeight` or `fixed`
22
+ * @property {string} overflow `visible`, `hidden`, `clipped` or `ellipsis`
23
+ * @property {string} align `left`, `center` or `right`
24
+ * @property {string} wrap `wrap` or `noWrap`
25
+ * @property {string} wordBreak `normal` or `breakAll`
26
+ * @property {string} origin `top` or `baseline`
27
+ * @property {string} direction `ltr` or `rtl`
28
+ * @property {number} maxWidth 0 for none
29
+ * @property {number} maxHeight 0 for none
30
+ * @property {number} paragraphSpacing
31
+ */
32
+
33
+ /**
34
+ * @typedef {object} Glyph
35
+ * @property {number} id
36
+ * @property {Run} run
37
+ * @property {number} cp
38
+ * @property {number} advance
39
+ * @property {number} x pen position on the line
40
+ * @property {number} y baseline
41
+ * @property {number} textIndex 1-based codepoint index
42
+ */
43
+
44
+ /**
45
+ * @typedef {object} Line
46
+ * @property {number} index
47
+ * @property {number} x
48
+ * @property {number} top
49
+ * @property {number} baseline
50
+ * @property {number} bottom
51
+ * @property {number} width
52
+ * @property {number} firstIndex
53
+ * @property {number} lastIndex
54
+ * @property {Glyph[]} glyphs
55
+ * @property {string} text
56
+ */
57
+
58
+ /**
59
+ * @typedef {object} Layout
60
+ * @property {Line[]} lines
61
+ * @property {number} width
62
+ * @property {number} height
63
+ * @property {number} length codepoints
64
+ */
65
+
66
+ /**
67
+ * @param {string} s
68
+ * @returns {number[]} its codepoints
69
+ */
70
+ export function codepoints(s) {
71
+ /** @type {number[]} */
72
+ const out = []
73
+ for (let i = 0; i < s.length; i++) {
74
+ const c = s.charCodeAt(i)
75
+ const d = i + 1 < s.length ? s.charCodeAt(i + 1) : 0
76
+ if (c >= 0xd800 && c < 0xdc00 && d >= 0xdc00 && d < 0xe000) {
77
+ out.push((c - 0xd800) * 1024 + (d - 0xdc00) + 0x10000)
78
+ i++
79
+ } else out.push(c)
80
+ }
81
+ return out
82
+ }
83
+
84
+ /**
85
+ * @param {number} cp
86
+ * @returns {string}
87
+ */
88
+ function char(cp) {
89
+ if (cp < 0x10000) return String.fromCharCode(cp)
90
+ const v = cp - 0x10000
91
+ return String.fromCharCode(0xd800 + (v >> 10), 0xdc00 + (v & 1023))
92
+ }
93
+
94
+ /**
95
+ * @param {Run} run
96
+ * @param {number} cp
97
+ * @returns {number}
98
+ */
99
+ function advanceOf(run, cp) {
100
+ const f = run.font
101
+ return (f.advance ? f.advance(cp) : 0.5) * run.size + run.letterSpacing
102
+ }
103
+
104
+ /**
105
+ * @param {Run} run
106
+ * @returns {number}
107
+ */
108
+ function heightOf(run) {
109
+ const f = /** @type {any} */ (run.font)
110
+ return run.lineHeight > 0 ? run.lineHeight : (f.descent - f.ascent) * run.size
111
+ }
112
+
113
+ /**
114
+ * @param {Run[]} runs
115
+ * @param {LayoutOptions} o
116
+ * @returns {Layout}
117
+ */
118
+ export function layoutText(runs, o) {
119
+ /** @type {Glyph[]} */
120
+ const all = []
121
+ let count = 0
122
+ for (let r = 0; r < runs.length; r++) {
123
+ const cps = codepoints(runs[r].text)
124
+ const f = runs[r].font
125
+ for (let k = 0; k < cps.length; k++) {
126
+ count++
127
+ all.push({ id: f.glyphId ? f.glyphId(cps[k]) : cps[k], run: runs[r], cp: cps[k], advance: cps[k] === 10 ? 0 : advanceOf(runs[r], cps[k]), x: 0, y: 0, textIndex: count })
128
+ }
129
+ }
130
+
131
+ const limit = o.wrap !== 'noWrap' && o.sizing !== 'autoWidth' && o.maxWidth > 0 ? o.maxWidth : Infinity
132
+ /** @type {{ glyphs: Glyph[], first: boolean }[]} */
133
+ const broken = []
134
+ /** @type {Glyph[]} */
135
+ let line = []
136
+ let width = 0
137
+ let softAt = 0
138
+ let first = true
139
+ const flush = () => {
140
+ broken.push({ glyphs: line, first })
141
+ first = false
142
+ }
143
+ for (let n = 0; n < all.length; n++) {
144
+ const g = all[n]
145
+ if (g.cp === 10) {
146
+ flush()
147
+ first = true
148
+ line = []
149
+ width = 0
150
+ softAt = 0
151
+ continue
152
+ }
153
+ if (g.cp !== 32 && width + g.advance > limit && line.length > 0) {
154
+ if (o.wordBreak !== 'breakAll' && softAt > 0) {
155
+ const rest = line.splice(softAt)
156
+ flush()
157
+ line = rest
158
+ width = 0
159
+ for (let k = 0; k < rest.length; k++) width += rest[k].advance
160
+ } else {
161
+ flush()
162
+ line = []
163
+ width = 0
164
+ }
165
+ softAt = 0
166
+ }
167
+ line.push(g)
168
+ width += g.advance
169
+ if (g.cp === 32) softAt = line.length
170
+ }
171
+ flush()
172
+
173
+ const fallback = runs[0]
174
+ /** @type {Line[]} */
175
+ const lines = []
176
+ let top = 0
177
+ let widest = 0
178
+ for (let n = 0; n < broken.length; n++) {
179
+ const gs = broken[n].glyphs
180
+ let w = 0
181
+ let end = gs.length
182
+ while (end > 0 && gs[end - 1].cp === 32) end--
183
+ for (let k = 0; k < end; k++) w += gs[k].advance
184
+ let lineH = 0
185
+ let asc = 0
186
+ let desc = 0
187
+ /** @type {Run[]} */
188
+ const used = []
189
+ for (let k = 0; k < gs.length; k++) if (used.indexOf(gs[k].run) < 0) used.push(gs[k].run)
190
+ if (!used.length && fallback) used.push(fallback)
191
+ for (let k = 0; k < used.length; k++) {
192
+ const f = /** @type {any} */ (used[k].font)
193
+ lineH = Math.max(lineH, heightOf(used[k]))
194
+ asc = Math.max(asc, -f.ascent * used[k].size)
195
+ desc = Math.max(desc, f.descent * used[k].size)
196
+ }
197
+ if (n > 0 && broken[n].first) top += o.paragraphSpacing
198
+ const baseline = top + asc + (lineH - asc - desc) / 2
199
+ const firstIndex = gs.length ? gs[0].textIndex : n > 0 ? lines[n - 1].lastIndex + 2 : 1
200
+ lines.push({ index: n + 1, x: 0, top, baseline, bottom: top + lineH, width: w, firstIndex, lastIndex: gs.length ? gs[gs.length - 1].textIndex : firstIndex - 1, glyphs: gs, text: '' })
201
+ if (w > widest) widest = w
202
+ top += lineH
203
+ }
204
+
205
+ // fixed text that overflows keeps the lines that fit; ellipsis ends the last in an ellipsis character
206
+ if (o.sizing === 'fixed' && o.maxHeight > 0 && (o.overflow === 'ellipsis' || o.overflow === 'hidden') && lines.length > 1) {
207
+ let keep = lines.length
208
+ while (keep > 1 && lines[keep - 1].bottom > o.maxHeight) keep--
209
+ if (keep < lines.length) {
210
+ lines.length = keep
211
+ if (o.overflow === 'ellipsis') ellipsize(lines[keep - 1], limit)
212
+ top = lines[keep - 1].bottom
213
+ widest = 0
214
+ for (let n = 0; n < lines.length; n++) widest = Math.max(widest, lines[n].width)
215
+ }
216
+ }
217
+
218
+ const boxWidth = o.sizing === 'autoWidth' || !(o.maxWidth > 0) ? widest : o.maxWidth
219
+ const shift = o.origin === 'baseline' && lines.length ? -lines[0].baseline : 0
220
+ for (let n = 0; n < lines.length; n++) {
221
+ const l = lines[n]
222
+ l.x = o.align === 'right' ? boxWidth - l.width : o.align === 'center' ? (boxWidth - l.width) / 2 : 0
223
+ l.top += shift
224
+ l.baseline += shift
225
+ l.bottom += shift
226
+ let pen = l.x
227
+ let s = ''
228
+ for (let k = 0; k < l.glyphs.length; k++) {
229
+ const g = l.glyphs[k]
230
+ g.x = pen
231
+ g.y = l.baseline
232
+ pen += g.advance
233
+ s += char(g.cp)
234
+ }
235
+ l.text = s
236
+ }
237
+ return { lines, width: boxWidth, height: top + shift, length: count }
238
+ }
239
+
240
+ /**
241
+ * @param {Line} line
242
+ * @param {number} limit
243
+ */
244
+ function ellipsize(line, limit) {
245
+ const gs = line.glyphs
246
+ if (!gs.length) return
247
+ const run = gs[gs.length - 1].run
248
+ const dots = advanceOf(run, 0x2026)
249
+ let w = 0
250
+ for (let k = 0; k < gs.length; k++) w += gs[k].advance
251
+ while (gs.length > 1 && w + dots > limit) {
252
+ const g = /** @type {Glyph} */ (gs.pop())
253
+ w -= g.advance
254
+ }
255
+ const at = gs[gs.length - 1].textIndex
256
+ gs.push({ id: 0x2026, run, cp: 0x2026, advance: dots, x: 0, y: 0, textIndex: at + 1 })
257
+ line.width = w + dots
258
+ line.lastIndex = at + 1
259
+ }
@@ -0,0 +1,76 @@
1
+ /**
2
+ * The `Context` a script's `init` receives and the `DataContext` it can ask
3
+ * for: where the script reaches its view model (the one bound to its node,
4
+ * the artboard's root one, global ones by name), asks to be updated again
5
+ * (`markNeedsUpdate`) and logs.
6
+ */
7
+
8
+ import { EMPTY, LuaTable, NativeFunction } from '../../luau/vm/vm.value.js'
9
+ import { decodeUtf8, encodeUtf8 } from '../../luau/luau.convert.js'
10
+ import { defineType, text } from '../api/api.type.js'
11
+ import { viewModelAt } from './context.viewmodel.js'
12
+
13
+ /** @import { Scope, ViewModelAdapter } from './context.viewmodel.js' */
14
+
15
+ /**
16
+ * What a context stands on: the host's view of the world.
17
+ *
18
+ * @typedef {object} ContextEnv
19
+ * @property {(adapter: ViewModelAdapter) => Scope} scopeFor
20
+ * @property {ViewModelAdapter | null} immediate the view model bound to the script's node
21
+ * @property {ViewModelAdapter | null} root the artboard's own view model
22
+ * @property {ViewModelAdapter | null} parent the view model of the enclosing data context
23
+ * @property {Record<string, ViewModelAdapter>} globals global view models by name
24
+ * @property {() => void} markNeedsUpdate
25
+ * @property {{ image: (n: string) => any, font: (n: string) => any, audio: (n: string) => any }} [assets] what `image`, `font` and `audio` hand out
26
+ * @property {(message: string) => void} log
27
+ */
28
+
29
+ /**
30
+ * @param {ContextEnv} env
31
+ * @param {ViewModelAdapter | null | undefined} adapter
32
+ * @returns {any} a ViewModel, or nil
33
+ */
34
+ function viewModelOf(env, adapter) {
35
+ return adapter ? viewModelAt(env.scopeFor(adapter), '') : undefined
36
+ }
37
+
38
+ export const DataContext = defineType('DataContext', {
39
+ methods: {
40
+ viewModel: (d) => viewModelOf(d.env, d.adapter),
41
+ parent: (d) => (d.parent ? DataContext.wrap({ env: d.env, adapter: d.parent, parent: null }) : undefined),
42
+ },
43
+ })
44
+
45
+ export const Context = defineType('Context', {
46
+ // Rive's own docs call it `context.log('x')` and others `context:log('x')`, so it is a field holding a function that takes the message last
47
+ fields: {
48
+ log: {
49
+ get: (env) => new NativeFunction('Context.log', function (vm, args) {
50
+ const last = args[args.length - 1]
51
+ env.log(typeof last === 'string' ? decodeUtf8(last) : '')
52
+ return EMPTY
53
+ }),
54
+ },
55
+ },
56
+ methods: {
57
+ viewModel: (env) => viewModelOf(env, env.immediate),
58
+ rootViewModel: (env) => viewModelOf(env, env.root),
59
+ globalViewModel: (env, args) => viewModelOf(env, env.globals[text(args, 1, 'globalViewModel')]),
60
+ globalViewModelNames: (env) => {
61
+ const t = new LuaTable()
62
+ const names = Object.keys(env.globals).map(encodeUtf8)
63
+ t.setRange(1, names, 0, names.length)
64
+ return t
65
+ },
66
+ dataContext: (env) => (env.immediate ? DataContext.wrap({ env, adapter: env.immediate, parent: env.parent }) : undefined),
67
+ markNeedsUpdate: (env) => {
68
+ env.markNeedsUpdate()
69
+ },
70
+ image: (env, args) => (env.assets ? env.assets.image(text(args, 1, 'image')) : undefined),
71
+ blob: () => undefined,
72
+ font: (env, args) => (env.assets ? env.assets.font(text(args, 1, 'font')) : undefined),
73
+ audio: (env, args) => (env.assets ? env.assets.audio(text(args, 1, 'audio')) : undefined),
74
+ shader: () => undefined,
75
+ },
76
+ })
@@ -0,0 +1,196 @@
1
+ /**
2
+ * A view model held in memory, implementing `ViewModelAdapter`: for tests,
3
+ * for a player with no view model of its own, and as the reference for what
4
+ * an adapter does. Nested objects are nested view models; a property
5
+ * changes (and notifies) only when its value does.
6
+ *
7
+ * ```js
8
+ * const vm = createMemoryViewModel({ score: 0, arrow1: { position: 0 } }, { colors: [ 'tint' ] })
9
+ * vm.set('arrow1.position', 12)
10
+ * vm.get('arrow1.position') // 12
11
+ * ```
12
+ */
13
+
14
+ /** @import { ViewModelAdapter } from './context.viewmodel.js' */
15
+
16
+ /**
17
+ * @typedef {ViewModelAdapter & { values: Record<string, any>, kinds: Record<string, string>, lists: Record<string, MemoryViewModel[]>, snapshot: () => Record<string, any> }} MemoryViewModel
18
+ */
19
+
20
+ /**
21
+ * Dotted keys as nested objects, which is what `createMemoryViewModel` takes.
22
+ *
23
+ * @param {Record<string, any>} flat
24
+ * @returns {Record<string, any>}
25
+ */
26
+ function nest(flat) {
27
+ /** @type {Record<string, any>} */
28
+ const out = {}
29
+ for (const key in flat) {
30
+ const parts = key.split('.')
31
+ let at = out
32
+ for (let n = 0; n < parts.length - 1; n++) at = at[parts[n]] || (at[parts[n]] = {})
33
+ at[parts[parts.length - 1]] = flat[key]
34
+ }
35
+ return out
36
+ }
37
+
38
+ /**
39
+ * @param {Record<string, any>} [initial] property values; an object value is a nested view model
40
+ * @param {object} [options]
41
+ * @param {string[]} [options.colors] dotted paths of colour properties (ARGB numbers)
42
+ * @param {string[]} [options.triggers] paths of triggers
43
+ * @param {string[]} [options.enums] paths of enum properties (strings)
44
+ * @param {string[]} [options.images] paths of image properties (an asset's name)
45
+ * @param {string[]} [options.fonts] paths of font properties (an asset's name)
46
+ * @param {Record<string, Record<string, any>>} [options.instances] named instances for `instance(name)`, as initial objects
47
+ * @returns {MemoryViewModel}
48
+ */
49
+ export function createMemoryViewModel(initial, options) {
50
+ const o = options || {}
51
+ /** @type {Record<string, any>} */
52
+ const values = {}
53
+ /** @type {Record<string, string>} */
54
+ const kinds = {}
55
+ /** @type {Record<string, ((value: any) => void)[]>} */
56
+ const subscribers = {}
57
+ /** @type {Record<string, MemoryViewModel[]>} list properties: their items, each a view model of its own */
58
+ const lists = {}
59
+
60
+ /**
61
+ * @param {string} path
62
+ * @param {any} value
63
+ */
64
+ const define = (path, value) => {
65
+ if (Array.isArray(value)) {
66
+ kinds[path] = 'list'
67
+ lists[path] = value.map((item) => (item && typeof item.subscribe === 'function' ? item : createMemoryViewModel(item, o)))
68
+ return
69
+ }
70
+ if (value !== null && typeof value === 'object') {
71
+ kinds[path] = 'viewModel'
72
+ for (const key in value) define(path === '' ? key : path + '.' + key, value[key])
73
+ return
74
+ }
75
+ values[path] = value
76
+ kinds[path] = typeof value === 'number' ? 'number' : typeof value === 'boolean' ? 'boolean' : 'string'
77
+ }
78
+ if (initial) for (const key in initial) define(key, initial[key])
79
+ /**
80
+ * @param {string[] | undefined} paths
81
+ * @param {string} kind
82
+ */
83
+ const tag = (paths, kind) => {
84
+ if (paths) for (let n = 0; n < paths.length; n++) kinds[paths[n]] = kind
85
+ }
86
+ tag(o.colors, 'color')
87
+ tag(o.triggers, 'trigger')
88
+ tag(o.enums, 'enum')
89
+ tag(o.images, 'image')
90
+ tag(o.fonts, 'font')
91
+
92
+ /**
93
+ * @param {string} path
94
+ * @param {any} value
95
+ */
96
+ const notify = (path, value) => {
97
+ const list = subscribers[path]
98
+ if (!list) return
99
+ const copy = list.slice()
100
+ for (let n = 0; n < copy.length; n++) copy[n](value)
101
+ }
102
+
103
+ /**
104
+ * @param {string} path
105
+ */
106
+ const changed = (path) => notify(path, lists[path].length)
107
+
108
+ return {
109
+ values,
110
+ kinds,
111
+ lists,
112
+ listSize: (path) => (lists[path] ? lists[path].length : 0),
113
+ listGet: (path, index) => (lists[path] ? lists[path][index] : undefined),
114
+ listInsert(path, item, index) {
115
+ const list = lists[path]
116
+ if (!list) return
117
+ list.splice(index, 0, /** @type {MemoryViewModel} */ (item))
118
+ changed(path)
119
+ },
120
+ listRemove(path, index) {
121
+ const list = lists[path]
122
+ if (!list || index < 0 || index >= list.length) return undefined
123
+ const out = list.splice(index, 1)[0]
124
+ changed(path)
125
+ return out
126
+ },
127
+ listClear(path) {
128
+ const list = lists[path]
129
+ if (!list || !list.length) return
130
+ list.length = 0
131
+ changed(path)
132
+ },
133
+ listSwap(path, a, b) {
134
+ const list = lists[path]
135
+ if (!list) return
136
+ const t = list[a]
137
+ list[a] = list[b]
138
+ list[b] = t
139
+ changed(path)
140
+ },
141
+ instance(path, name) {
142
+ if (name !== undefined && o.instances && o.instances[name]) return createMemoryViewModel(o.instances[name], o)
143
+ const prefix = path === '' ? '' : path + '.'
144
+ /** @type {Record<string, any>} */
145
+ const copy = {}
146
+ /** @type {string[]} */
147
+ const colors = []
148
+ /** @type {string[]} */
149
+ const enums = []
150
+ /** @type {string[]} */
151
+ const triggers = []
152
+ /** @type {string[]} */
153
+ const images = []
154
+ /** @type {string[]} */
155
+ const fonts = []
156
+ for (const key in kinds) {
157
+ if (key.indexOf(prefix) !== 0 || key === path) continue
158
+ const rest = key.slice(prefix.length)
159
+ const kind = kinds[key]
160
+ if (kind === 'viewModel') continue
161
+ copy[rest] = kind === 'list' ? [] : values[key]
162
+ if (kind === 'color') colors.push(rest)
163
+ else if (kind === 'enum') enums.push(rest)
164
+ else if (kind === 'trigger') triggers.push(rest)
165
+ else if (kind === 'image') images.push(rest)
166
+ else if (kind === 'font') fonts.push(rest)
167
+ }
168
+ return createMemoryViewModel(nest(copy), { colors, enums, triggers, images, fonts, instances: o.instances })
169
+ },
170
+ get: (path) => values[path],
171
+ kind: (path) => kinds[path],
172
+ set(path, value) {
173
+ if (kinds[path] === undefined) kinds[path] = typeof value === 'number' ? 'number' : typeof value === 'boolean' ? 'boolean' : 'string'
174
+ if (values[path] === value) return
175
+ values[path] = value
176
+ notify(path, value)
177
+ },
178
+ fire(path) {
179
+ notify(path, true)
180
+ },
181
+ subscribe(path, fn) {
182
+ const list = subscribers[path] || (subscribers[path] = [])
183
+ list.push(fn)
184
+ return function () {
185
+ const at = list.indexOf(fn)
186
+ if (at >= 0) list.splice(at, 1)
187
+ }
188
+ },
189
+ snapshot() {
190
+ /** @type {Record<string, any>} */
191
+ const out = {}
192
+ for (const key in values) out[key] = values[key]
193
+ return out
194
+ },
195
+ }
196
+ }