@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.
- package/bands/bands.js +3 -1
- package/bundle/bundle.js +56 -0
- package/list/list.js +12 -1
- package/lottie/lottie.layers.js +64 -11
- package/lottie/lottie.paint.js +30 -0
- package/lottie/lottie.paths.js +19 -4
- package/lottie/lottie.props.js +18 -8
- package/lottie/lottie.shapes.js +2 -1
- package/mixer/mixer.js +31 -8
- package/mixer/mixer.mix.js +17 -0
- package/motion/motion.convert.js +32 -1
- package/motion/motion.flow.js +20 -2
- package/motion/motion.machine.js +28 -13
- package/motion/motion.values.js +13 -9
- package/motion.js +196 -20
- package/package.json +10 -16
- package/player/player.js +25 -5
- package/readme.md +78 -3
- package/script/luau/lib/base/base.js +202 -0
- package/script/luau/lib/base/base.table.js +189 -0
- package/script/luau/lib/lib.args.js +152 -0
- package/script/luau/lib/lib.js +23 -0
- package/script/luau/lib/math/math.bit32.js +144 -0
- package/script/luau/lib/math/math.js +288 -0
- package/script/luau/lib/math/math.vector.js +125 -0
- package/script/luau/lib/string/string.format.js +143 -0
- package/script/luau/lib/string/string.js +160 -0
- package/script/luau/lib/string/string.pattern.js +505 -0
- package/script/luau/load/load.js +362 -0
- package/script/luau/load/load.ops.js +79 -0
- package/script/luau/luau.convert.js +259 -0
- package/script/luau/luau.js +132 -0
- package/script/luau/luau.md +111 -0
- package/script/luau/vm/vm.js +167 -0
- package/script/luau/vm/vm.meta.js +401 -0
- package/script/luau/vm/vm.num.js +236 -0
- package/script/luau/vm/vm.run.js +842 -0
- package/script/luau/vm/vm.value.js +406 -0
- package/script/play/play.js +199 -0
- package/script/play/play.objects.js +295 -0
- package/script/play/play.scope.js +91 -0
- package/script/rive/api/api.statics.js +31 -0
- package/script/rive/api/api.type.js +188 -0
- package/script/rive/api/artboard/artboard.js +89 -0
- package/script/rive/api/assets/assets.js +72 -0
- package/script/rive/api/data/data.value.js +99 -0
- package/script/rive/api/draw/draw.contours.js +163 -0
- package/script/rive/api/draw/draw.measure.js +196 -0
- package/script/rive/api/draw/draw.paint.js +235 -0
- package/script/rive/api/draw/draw.path.js +153 -0
- package/script/rive/api/draw/draw.renderer.js +117 -0
- package/script/rive/api/math/math.color.js +96 -0
- package/script/rive/api/math/math.mat2d.js +180 -0
- package/script/rive/api/math/math.vector.js +121 -0
- package/script/rive/api/media/media.audio.js +113 -0
- package/script/rive/api/media/media.draw.js +73 -0
- package/script/rive/api/media/media.image.js +61 -0
- package/script/rive/api/media/media.mesh.js +50 -0
- package/script/rive/api/pointer/pointer.event.js +64 -0
- package/script/rive/api/pointer/pointer.route.js +69 -0
- package/script/rive/api/text/text.font.js +129 -0
- package/script/rive/api/text/text.js +278 -0
- package/script/rive/api/text/text.layout.js +259 -0
- package/script/rive/context/context.js +76 -0
- package/script/rive/context/context.memory.js +196 -0
- package/script/rive/context/context.viewmodel.js +302 -0
- package/script/rive/context/lists/lists.js +160 -0
- package/script/rive/context/lists/lists.values.js +281 -0
- package/script/rive/host/host.globals.js +44 -0
- package/script/rive/host/host.js +533 -0
- package/script/rive/host/host.node.js +45 -0
- package/script/rive/rive.js +23 -0
- package/script/rive/rive.md +180 -0
- package/script/script.js +11 -0
- package/skin/skin.deform.js +514 -0
- package/skin/skin.ik.js +260 -0
- package/skin/skin.js +297 -0
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The view model a script reaches through its context: `ViewModel` (named
|
|
3
|
+
* properties, nested view models) and the typed properties it hands out
|
|
4
|
+
* (`Property` for numbers, strings, booleans, colours and enums;
|
|
5
|
+
* `PropertyTrigger`; `PropertyViewModel`). All of it is a thin skin over a
|
|
6
|
+
* `ViewModelAdapter` the player implements, so the host never knows which
|
|
7
|
+
* player it runs in.
|
|
8
|
+
*
|
|
9
|
+
* Paths are dotted: a script that asks a view model at `a.b` for `c` reads
|
|
10
|
+
* the adapter at `a.b.c`.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { LuaClosure, NativeFunction, fail } from '../../luau/vm/vm.value.js'
|
|
14
|
+
import { decodeUtf8, encodeUtf8 } from '../../luau/luau.convert.js'
|
|
15
|
+
import { Wrapped, defineType, text } from '../api/api.type.js'
|
|
16
|
+
import { listType } from './lists/lists.js'
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* What a player gives the host to read and write its view model.
|
|
20
|
+
*
|
|
21
|
+
* @typedef {object} ViewModelAdapter
|
|
22
|
+
* @property {(path: string) => any} get the value at a dotted path: a number, string, boolean (a colour is an ARGB number, an enum its value name), or undefined when there is none
|
|
23
|
+
* @property {(path: string, value: any) => void} set
|
|
24
|
+
* @property {(path: string, fn: (value: any) => void) => (() => void)} subscribe calls `fn` when the value changes (or a trigger fires); returns how to stop
|
|
25
|
+
* @property {(path: string) => (string | undefined)} [kind] `number`, `string`, `boolean`, `color`, `enum`, `trigger`, `viewModel`, `list`, or undefined for no such property; without it the kind is read from the value's type
|
|
26
|
+
* @property {(path: string) => void} [fire] fires a trigger; without it a trigger is set to `true`
|
|
27
|
+
* @property {(path: string) => number} [listSize] the items of a list property
|
|
28
|
+
* @property {(path: string, index: number) => (ViewModelAdapter | undefined)} [listGet] the view model of item `index` (0-based), as an adapter of its own whose paths start at its root
|
|
29
|
+
* @property {(path: string, item: ViewModelAdapter, index: number) => void} [listInsert] puts a view model into the list at `index` (0-based); the list notifies its subscribers
|
|
30
|
+
* @property {(path: string, index: number) => (ViewModelAdapter | undefined)} [listRemove] takes item `index` out and returns it
|
|
31
|
+
* @property {(path: string, a: number, b: number) => void} [listSwap] swaps two items
|
|
32
|
+
* @property {(path: string) => void} [listClear] empties the list in one change; without it `clear` removes the items one by one
|
|
33
|
+
* @property {(path: string, name?: string) => (ViewModelAdapter | undefined)} [instance] a new instance of the view model at `path` ('' for the root), from the named instance of the file when `name` is given; not in any list yet
|
|
34
|
+
*
|
|
35
|
+
* The three asset kinds `image`, `font` and `blob` hold an asset's name (`get` gives it, `set` takes it).
|
|
36
|
+
*/
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* The state every view model object of one script host shares.
|
|
40
|
+
*
|
|
41
|
+
* @typedef {object} Scope
|
|
42
|
+
* @property {ViewModelAdapter} adapter
|
|
43
|
+
* @property {Record<string, PropertyData>} props property objects by `kind:path`, so a property is the same object each time it is asked for
|
|
44
|
+
* @property {(fn: any, args: any[]) => void} call calls a Lua function, keeping the host's error handling
|
|
45
|
+
* @property {() => void} wake tells the host something changed
|
|
46
|
+
* @property {(adapter: ViewModelAdapter) => Scope} [scopeFor] the scope of another adapter (a list's item); without it items get scopes of their own
|
|
47
|
+
* @property {(kind: string, name: string) => any} [asset] an asset's Lua value (`image`, `font`, `blob`) by name
|
|
48
|
+
*/
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* @typedef {object} PropertyData
|
|
52
|
+
* @property {Scope} scope
|
|
53
|
+
* @property {string} path
|
|
54
|
+
* @property {string} kind
|
|
55
|
+
* @property {{ cb: any, anchor: any }[]} listeners
|
|
56
|
+
* @property {(() => void) | null} unsubscribe
|
|
57
|
+
* @property {any} wrapper
|
|
58
|
+
*/
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* @param {Scope} scope
|
|
62
|
+
* @param {string} path
|
|
63
|
+
* @returns {string | undefined}
|
|
64
|
+
*/
|
|
65
|
+
function kindOf(scope, path) {
|
|
66
|
+
const a = scope.adapter
|
|
67
|
+
if (a.kind) return a.kind(path)
|
|
68
|
+
const v = a.get(path)
|
|
69
|
+
return typeof v === 'number' ? 'number' : typeof v === 'string' ? 'string' : typeof v === 'boolean' ? 'boolean' : Array.isArray(v) ? 'list' : undefined
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* @param {string} kind
|
|
74
|
+
* @returns {boolean} whether the property holds an asset's name
|
|
75
|
+
*/
|
|
76
|
+
function isAsset(kind) {
|
|
77
|
+
return kind === 'image' || kind === 'font' || kind === 'blob'
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* A host value as the Lua value a property holds.
|
|
82
|
+
*
|
|
83
|
+
* @param {any} v
|
|
84
|
+
* @returns {any}
|
|
85
|
+
*/
|
|
86
|
+
function toLuaValue(v) {
|
|
87
|
+
return typeof v === 'string' ? encodeUtf8(v) : v === null ? undefined : v
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* @param {string} kind
|
|
92
|
+
* @param {any} v a Lua value
|
|
93
|
+
* @returns {any} the host value to store
|
|
94
|
+
*/
|
|
95
|
+
function fromLuaValue(kind, v) {
|
|
96
|
+
if (isAsset(kind)) {
|
|
97
|
+
const name = v instanceof Wrapped && v.host ? v.host.name : undefined
|
|
98
|
+
if (typeof name !== 'string') throw fail('a ' + kind + ' property takes a ' + kind)
|
|
99
|
+
return name
|
|
100
|
+
}
|
|
101
|
+
if (kind === 'number' || kind === 'color') {
|
|
102
|
+
if (typeof v !== 'number') throw fail('a ' + kind + ' property takes a number')
|
|
103
|
+
return v
|
|
104
|
+
}
|
|
105
|
+
if (kind === 'boolean') {
|
|
106
|
+
if (typeof v !== 'boolean') throw fail('a boolean property takes a boolean')
|
|
107
|
+
return v
|
|
108
|
+
}
|
|
109
|
+
if (kind === 'string' || kind === 'enum') {
|
|
110
|
+
if (typeof v !== 'string') throw fail('a ' + kind + ' property takes a string')
|
|
111
|
+
return decodeUtf8(v)
|
|
112
|
+
}
|
|
113
|
+
throw fail('cannot set a ' + kind + ' property')
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Properties
|
|
117
|
+
// ■■■■■■■■■■
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Adds a listener or takes one away; both take `(callback)` or `(anchor, callback)`.
|
|
121
|
+
*
|
|
122
|
+
* @param {PropertyData} p
|
|
123
|
+
* @param {any[]} args with the property first
|
|
124
|
+
* @param {boolean} add
|
|
125
|
+
*/
|
|
126
|
+
function listen(p, args, add) {
|
|
127
|
+
const withAnchor = args[2] !== undefined
|
|
128
|
+
const cb = withAnchor ? args[2] : args[1]
|
|
129
|
+
const anchor = withAnchor ? args[1] : undefined
|
|
130
|
+
if (!(cb instanceof NativeFunction) && !(cb instanceof LuaClosure)) throw fail('addListener needs a function')
|
|
131
|
+
if (add) {
|
|
132
|
+
p.listeners.push({ cb, anchor })
|
|
133
|
+
if (!p.unsubscribe) {
|
|
134
|
+
p.unsubscribe = p.scope.adapter.subscribe(p.path, function () {
|
|
135
|
+
const snapshot = p.listeners.slice()
|
|
136
|
+
for (let n = 0; n < snapshot.length; n++) p.scope.call(snapshot[n].cb, [ snapshot[n].anchor !== undefined ? snapshot[n].anchor : p.wrapper ])
|
|
137
|
+
p.scope.wake()
|
|
138
|
+
})
|
|
139
|
+
}
|
|
140
|
+
return
|
|
141
|
+
}
|
|
142
|
+
for (let n = 0; n < p.listeners.length; n++) {
|
|
143
|
+
if (p.listeners[n].cb === cb) {
|
|
144
|
+
p.listeners.splice(n, 1)
|
|
145
|
+
break
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
if (p.listeners.length === 0 && p.unsubscribe) {
|
|
149
|
+
p.unsubscribe()
|
|
150
|
+
p.unsubscribe = null
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* @param {PropertyData} p
|
|
156
|
+
* @returns {any} the asset the property names, or nil
|
|
157
|
+
*/
|
|
158
|
+
function assetValue(p) {
|
|
159
|
+
const name = p.scope.adapter.get(p.path)
|
|
160
|
+
return typeof name === 'string' && p.scope.asset ? p.scope.asset(p.kind, name) : undefined
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/** @type {Record<string, (p: PropertyData, args: any[]) => any>} */
|
|
164
|
+
export const listenerMethods = {
|
|
165
|
+
addListener: (p, args) => listen(p, args, true),
|
|
166
|
+
removeListener: (p, args) => listen(p, args, false),
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export const Property = defineType('Property', {
|
|
170
|
+
fields: {
|
|
171
|
+
value: {
|
|
172
|
+
get: (p) => (isAsset(p.kind) ? assetValue(p) : toLuaValue(p.scope.adapter.get(p.path))),
|
|
173
|
+
set: (p, v) => {
|
|
174
|
+
p.scope.adapter.set(p.path, fromLuaValue(p.kind, v))
|
|
175
|
+
},
|
|
176
|
+
},
|
|
177
|
+
},
|
|
178
|
+
methods: listenerMethods,
|
|
179
|
+
})
|
|
180
|
+
|
|
181
|
+
export const PropertyTrigger = defineType('PropertyTrigger', {
|
|
182
|
+
methods: {
|
|
183
|
+
addListener: listenerMethods.addListener,
|
|
184
|
+
removeListener: listenerMethods.removeListener,
|
|
185
|
+
fire: (p) => {
|
|
186
|
+
const a = p.scope.adapter
|
|
187
|
+
if (a.fire) a.fire(p.path)
|
|
188
|
+
else a.set(p.path, true)
|
|
189
|
+
},
|
|
190
|
+
},
|
|
191
|
+
})
|
|
192
|
+
|
|
193
|
+
export const PropertyViewModel = defineType('PropertyViewModel', {
|
|
194
|
+
fields: {
|
|
195
|
+
value: { get: (p) => viewModelAt(p.scope, p.path) },
|
|
196
|
+
},
|
|
197
|
+
methods: listenerMethods,
|
|
198
|
+
})
|
|
199
|
+
|
|
200
|
+
const PropertyList = listType({ listenerMethods, scopeOf, viewModelAt })
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* The one property object for a path and kind.
|
|
204
|
+
*
|
|
205
|
+
* @param {Scope} scope
|
|
206
|
+
* @param {string} path
|
|
207
|
+
* @param {string} kind
|
|
208
|
+
* @returns {any} its Lua wrapper
|
|
209
|
+
*/
|
|
210
|
+
function propertyAt(scope, path, kind) {
|
|
211
|
+
const key = kind + ':' + path
|
|
212
|
+
let p = scope.props[key]
|
|
213
|
+
if (!p) {
|
|
214
|
+
p = { scope, path, kind, listeners: [], unsubscribe: null, wrapper: null }
|
|
215
|
+
const type = kind === 'trigger' ? PropertyTrigger : kind === 'viewModel' ? PropertyViewModel : kind === 'list' ? PropertyList : Property
|
|
216
|
+
p.wrapper = type.wrap(p)
|
|
217
|
+
scope.props[key] = p
|
|
218
|
+
}
|
|
219
|
+
return p.wrapper
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// View models
|
|
223
|
+
// ■■■■■■■■■■■
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* @typedef {object} ViewModelData
|
|
227
|
+
* @property {Scope} scope
|
|
228
|
+
* @property {string} prefix '' or a dotted path ending in '.'
|
|
229
|
+
*/
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* @param {Scope} scope
|
|
233
|
+
* @param {string} path the dotted path of the view model; '' for the root
|
|
234
|
+
* @returns {any} a ViewModel wrapper
|
|
235
|
+
*/
|
|
236
|
+
export function viewModelAt(scope, path) {
|
|
237
|
+
return ViewModel.wrap({ scope, prefix: path === '' ? '' : path + '.' })
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* @param {string} kind what the method wants
|
|
242
|
+
* @returns {(h: ViewModelData, args: any[]) => any}
|
|
243
|
+
*/
|
|
244
|
+
function getter(kind) {
|
|
245
|
+
return function (h, args) {
|
|
246
|
+
const name = text(args, 1, 'get' + kind)
|
|
247
|
+
const path = h.prefix + name
|
|
248
|
+
const actual = kindOf(h.scope, path)
|
|
249
|
+
// without kinds from the adapter, a number may be a colour
|
|
250
|
+
const ok = actual === kind || (kind === 'color' && actual === 'number' && !h.scope.adapter.kind) || (kind === 'enum' && actual === 'string' && !h.scope.adapter.kind)
|
|
251
|
+
return ok ? propertyAt(h.scope, path, kind) : undefined
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* The scope of another adapter (a list's item, a new instance) beside this one.
|
|
257
|
+
*
|
|
258
|
+
* @param {Scope} scope
|
|
259
|
+
* @param {ViewModelAdapter} adapter
|
|
260
|
+
* @returns {Scope}
|
|
261
|
+
*/
|
|
262
|
+
export function scopeOf(scope, adapter) {
|
|
263
|
+
if (scope.scopeFor) return scope.scopeFor(adapter)
|
|
264
|
+
return { adapter, props: {}, call: scope.call, wake: scope.wake, asset: scope.asset }
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/** @type {Record<string, (h: ViewModelData, args: any[]) => any>} */
|
|
268
|
+
const viewModelMethods = {
|
|
269
|
+
getNumber: getter('number'),
|
|
270
|
+
getString: getter('string'),
|
|
271
|
+
getBoolean: getter('boolean'),
|
|
272
|
+
getColor: getter('color'),
|
|
273
|
+
getEnum: getter('enum'),
|
|
274
|
+
getTrigger: getter('trigger'),
|
|
275
|
+
getViewModel: getter('viewModel'),
|
|
276
|
+
// lists, assets and creating instances need a player behind them
|
|
277
|
+
getList: getter('list'),
|
|
278
|
+
getImage: getter('image'),
|
|
279
|
+
getFont: getter('font'),
|
|
280
|
+
getBlob: getter('blob'),
|
|
281
|
+
// a new instance of this view model, optionally from a named instance of the file
|
|
282
|
+
instance: (h, args) => {
|
|
283
|
+
const a = h.scope.adapter
|
|
284
|
+
const made = a.instance ? a.instance(h.prefix === '' ? '' : h.prefix.slice(0, -1), args[1] === undefined ? undefined : text(args, 1, 'instance')) : undefined
|
|
285
|
+
return made ? viewModelAt(scopeOf(h.scope, made), '') : undefined
|
|
286
|
+
},
|
|
287
|
+
getIndex: () => -1,
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
export const ViewModel = defineType('ViewModel', {
|
|
291
|
+
fields: {
|
|
292
|
+
name: { get: () => '' },
|
|
293
|
+
},
|
|
294
|
+
methods: viewModelMethods,
|
|
295
|
+
// `vm.title` reads the property of that name, as a typed script's `Data.X` does
|
|
296
|
+
index: (h, key) => {
|
|
297
|
+
if (typeof key !== 'string') return undefined
|
|
298
|
+
const path = h.prefix + decodeUtf8(key)
|
|
299
|
+
const kind = kindOf(h.scope, path)
|
|
300
|
+
return kind === undefined ? undefined : propertyAt(h.scope, path, kind)
|
|
301
|
+
},
|
|
302
|
+
})
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `PropertyList`: a view-model list property a script reads and changes,
|
|
3
|
+
* as Rive documents it. Items are view models, 1-based: `list[1]`,
|
|
4
|
+
* `list.length`, `push`, `insert(vm, index)`, `swap`, `pop`, `shift`,
|
|
5
|
+
* `clear`, `remove`, `removeAllOf`, `removeAt`, and `addListener`.
|
|
6
|
+
*
|
|
7
|
+
* The items are adapters of their own (`ViewModelAdapter.listGet`), so an item
|
|
8
|
+
* is read and written like any view model; what changes the list goes through
|
|
9
|
+
* `listInsert`, `listRemove` and `listSwap`, which the player turns into rows.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { fail } from '../../../luau/vm/vm.value.js'
|
|
13
|
+
import { Wrapped, defineType, num } from '../../api/api.type.js'
|
|
14
|
+
|
|
15
|
+
/** @import { PropertyData, ViewModelData } from '../context.viewmodel.js' */
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* @typedef {object} ListDeps what the view model gives the list type (passed in, so the two modules do not import each other)
|
|
19
|
+
* @property {Record<string, (p: PropertyData, args: any[]) => any>} listenerMethods
|
|
20
|
+
* @property {(scope: any, adapter: any) => any} scopeOf
|
|
21
|
+
* @property {(scope: any, path: string) => any} viewModelAt
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
/** @type {ListDeps} */
|
|
25
|
+
let deps
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* @param {PropertyData} p
|
|
29
|
+
* @returns {import('../context.viewmodel.js').ViewModelAdapter}
|
|
30
|
+
*/
|
|
31
|
+
function adapterOf(p) {
|
|
32
|
+
return p.scope.adapter
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* @param {PropertyData} p
|
|
37
|
+
* @returns {number}
|
|
38
|
+
*/
|
|
39
|
+
function sizeOf(p) {
|
|
40
|
+
const a = adapterOf(p)
|
|
41
|
+
return a.listSize ? a.listSize(p.path) : 0
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* @param {PropertyData} p
|
|
46
|
+
* @param {number} index 0-based
|
|
47
|
+
* @returns {any} the item's ViewModel, or nil
|
|
48
|
+
*/
|
|
49
|
+
function itemAt(p, index) {
|
|
50
|
+
const a = adapterOf(p)
|
|
51
|
+
const item = index >= 0 && a.listGet ? a.listGet(p.path, index) : undefined
|
|
52
|
+
return item ? deps.viewModelAt(deps.scopeOf(p.scope, item), '') : undefined
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* @param {any[]} args
|
|
57
|
+
* @param {number} i
|
|
58
|
+
* @param {string} fname
|
|
59
|
+
* @returns {import('../context.viewmodel.js').ViewModelAdapter} the adapter of a view model argument that is an instance
|
|
60
|
+
*/
|
|
61
|
+
function instanceArg(args, i, fname) {
|
|
62
|
+
const w = args[i]
|
|
63
|
+
/** @type {ViewModelData | undefined} */
|
|
64
|
+
const h = w instanceof Wrapped ? w.host : undefined
|
|
65
|
+
if (!h || !h.scope || h.prefix !== '') throw fail("invalid argument #" + (i + 1) + " to '" + fname + "' (ViewModel instance expected)")
|
|
66
|
+
return h.scope.adapter
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* @param {PropertyData} p
|
|
71
|
+
* @param {number} index 0-based
|
|
72
|
+
* @returns {any} what was removed, as a ViewModel
|
|
73
|
+
*/
|
|
74
|
+
function take(p, index) {
|
|
75
|
+
const a = adapterOf(p)
|
|
76
|
+
if (index < 0 || index >= sizeOf(p) || !a.listRemove) return undefined
|
|
77
|
+
const item = a.listRemove(p.path, index)
|
|
78
|
+
return item ? deps.viewModelAt(deps.scopeOf(p.scope, item), '') : undefined
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* @param {PropertyData} p
|
|
83
|
+
* @param {import('../context.viewmodel.js').ViewModelAdapter} item
|
|
84
|
+
* @returns {number} its 0-based place, -1 when absent
|
|
85
|
+
*/
|
|
86
|
+
function find(p, item) {
|
|
87
|
+
const a = adapterOf(p)
|
|
88
|
+
const n = sizeOf(p)
|
|
89
|
+
for (let i = 0; i < n; i++) if (a.listGet && a.listGet(p.path, i) === item) return i
|
|
90
|
+
return -1
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* @param {PropertyData} p
|
|
95
|
+
* @param {number} n 1-based index, as a script writes it
|
|
96
|
+
* @param {string} fname
|
|
97
|
+
* @param {number} max the highest allowed
|
|
98
|
+
* @returns {number} 0-based
|
|
99
|
+
*/
|
|
100
|
+
function place(p, n, fname, max) {
|
|
101
|
+
if (n !== Math.floor(n) || n < 1 || n > max) throw fail("invalid index to '" + fname + "' (" + n + ' of ' + max + ')')
|
|
102
|
+
return n - 1
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* @param {ListDeps} d
|
|
107
|
+
* @returns {import('../../api/api.type.js').HostType}
|
|
108
|
+
*/
|
|
109
|
+
export function listType(d) {
|
|
110
|
+
deps = d
|
|
111
|
+
const listenerMethods = d.listenerMethods
|
|
112
|
+
return defineType('PropertyList', {
|
|
113
|
+
fields: {
|
|
114
|
+
length: { get: (p) => sizeOf(p) },
|
|
115
|
+
},
|
|
116
|
+
methods: {
|
|
117
|
+
addListener: listenerMethods.addListener,
|
|
118
|
+
removeListener: listenerMethods.removeListener,
|
|
119
|
+
push: (p, args) => {
|
|
120
|
+
const a = adapterOf(p)
|
|
121
|
+
if (a.listInsert) a.listInsert(p.path, instanceArg(args, 1, 'push'), sizeOf(p))
|
|
122
|
+
},
|
|
123
|
+
insert: (p, args) => {
|
|
124
|
+
const a = adapterOf(p)
|
|
125
|
+
const at = place(p, num(args, 2, 'insert'), 'insert', sizeOf(p) + 1)
|
|
126
|
+
if (a.listInsert) a.listInsert(p.path, instanceArg(args, 1, 'insert'), at)
|
|
127
|
+
},
|
|
128
|
+
swap: (p, args) => {
|
|
129
|
+
const a = adapterOf(p)
|
|
130
|
+
const n = sizeOf(p)
|
|
131
|
+
const i = place(p, num(args, 1, 'swap'), 'swap', n)
|
|
132
|
+
const j = place(p, num(args, 2, 'swap'), 'swap', n)
|
|
133
|
+
if (a.listSwap && i !== j) a.listSwap(p.path, i, j)
|
|
134
|
+
},
|
|
135
|
+
pop: (p) => take(p, sizeOf(p) - 1),
|
|
136
|
+
shift: (p) => take(p, 0),
|
|
137
|
+
clear: (p) => {
|
|
138
|
+
const a = adapterOf(p)
|
|
139
|
+
if (a.listClear) return void a.listClear(p.path)
|
|
140
|
+
for (let n = sizeOf(p) - 1; n >= 0; n--) take(p, n)
|
|
141
|
+
},
|
|
142
|
+
removeAt: (p, args) => {
|
|
143
|
+
take(p, place(p, num(args, 1, 'removeAt'), 'removeAt', sizeOf(p)))
|
|
144
|
+
},
|
|
145
|
+
remove: (p, args) => {
|
|
146
|
+
const at = find(p, instanceArg(args, 1, 'remove'))
|
|
147
|
+
if (at >= 0) take(p, at)
|
|
148
|
+
},
|
|
149
|
+
removeAllOf: (p, args) => {
|
|
150
|
+
const item = instanceArg(args, 1, 'removeAllOf')
|
|
151
|
+
for (let at = find(p, item); at >= 0; at = find(p, item)) take(p, at)
|
|
152
|
+
},
|
|
153
|
+
},
|
|
154
|
+
meta: {
|
|
155
|
+
__len: (vm, args) => sizeOf(args[0].host),
|
|
156
|
+
},
|
|
157
|
+
// `list[1]`: items are 1-based
|
|
158
|
+
index: (p, key) => (typeof key === 'number' ? itemAt(p, key - 1) : undefined),
|
|
159
|
+
})
|
|
160
|
+
}
|