web-toolkit-x 0.0.1 → 0.71.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 (46) hide show
  1. package/AUTHORS +3 -0
  2. package/LICENSE +1 -1
  3. package/NOTICE +7 -0
  4. package/README.md +169 -6
  5. package/dist/release/global.d.ts +10 -0
  6. package/dist/release/index.d.ts +650 -0
  7. package/dist/release/index.d.ts.map +1 -0
  8. package/dist/release/index.js +2837 -0
  9. package/dist/release/index.js.map +1 -0
  10. package/dist/release/toolkit-0.71.0.js +2840 -0
  11. package/dist/release/toolkit-0.71.0.js.map +1 -0
  12. package/package.json +75 -18
  13. package/src/core/bragi.ts +122 -0
  14. package/src/core/browser.ts +555 -0
  15. package/src/core/child-nodes.ts +77 -0
  16. package/src/core/comment.ts +24 -0
  17. package/src/core/component.ts +190 -0
  18. package/src/core/created-roots.ts +38 -0
  19. package/src/core/custom-element.ts +191 -0
  20. package/src/core/description.ts +241 -0
  21. package/src/core/diff.ts +590 -0
  22. package/src/core/dispatcher.ts +221 -0
  23. package/src/core/dom.ts +124 -0
  24. package/src/core/draft.ts +609 -0
  25. package/src/core/lifecycle.ts +301 -0
  26. package/src/core/nodes.ts +13 -0
  27. package/src/core/patch.ts +412 -0
  28. package/src/core/plugins.ts +231 -0
  29. package/src/core/reconciler.ts +185 -0
  30. package/src/core/renderer.ts +75 -0
  31. package/src/core/runtime.ts +36 -0
  32. package/src/core/sandbox.ts +128 -0
  33. package/src/core/service.ts +39 -0
  34. package/src/core/template.ts +472 -0
  35. package/src/core/text.ts +24 -0
  36. package/src/core/toolkit.ts +220 -0
  37. package/src/core/utils.ts +148 -0
  38. package/src/core/virtual-dom.ts +118 -0
  39. package/src/core/virtual-element.ts +72 -0
  40. package/src/core/virtual-node.ts +87 -0
  41. package/src/core/web-component.ts +285 -0
  42. package/src/global.d.ts +10 -0
  43. package/src/index.ts +62 -0
  44. package/src/plugins/logger.ts +38 -0
  45. package/src/release.ts +3 -0
  46. package/index.js +0 -1
@@ -0,0 +1,609 @@
1
+ import { invariant } from './utils.js'
2
+
3
+ /*
4
+ * Drafts let commands change the state as if it was mutable. A draft is
5
+ * a proxy recording the changes on a shallow copy of its object, with the
6
+ * nested objects drafted when accessed. The next state is made of the
7
+ * copies, sharing the parts left unchanged with the previous state.
8
+ */
9
+
10
+ type Kind = 'object' | 'array' | 'map' | 'set' | 'date' | 'typed-array'
11
+
12
+ type Entries = Record<PropertyKey, unknown>
13
+
14
+ /* Any of the typed arrays, as Uint8Array or Float32Array. */
15
+ interface TypedArray extends ArrayBufferView {
16
+ slice(): TypedArray
17
+ }
18
+
19
+ interface Draft {
20
+ kind: Kind
21
+ base: object
22
+ copy: object | null
23
+ parent: Draft | null
24
+ scope: Scope
25
+ modified: boolean
26
+ proxy: object
27
+ result: object | null
28
+ // the drafts of the items of a set, replacing them in the copy
29
+ items: Map<unknown, object> | null
30
+ }
31
+
32
+ interface Scope {
33
+ revokes: (() => void)[]
34
+ // the new values checked for drafts when finalizing
35
+ visited: WeakSet<object>
36
+ }
37
+
38
+ /* The drafts by their proxies. */
39
+ const drafts = new WeakMap<object, Draft>()
40
+
41
+ /* The drafts by the targets of their proxies. */
42
+ const targets = new WeakMap<object, Draft>()
43
+
44
+ // matching the original objects of the drafts as well as the drafts
45
+ const ARRAY_SEARCHES = new Set<PropertyKey>([
46
+ 'includes',
47
+ 'indexOf',
48
+ 'lastIndexOf',
49
+ ])
50
+
51
+ // with subarray() writing to the memory of the array, as a view of it
52
+ const TYPED_ARRAY_MUTATORS = new Set<PropertyKey>([
53
+ 'copyWithin',
54
+ 'fill',
55
+ 'reverse',
56
+ 'set',
57
+ 'sort',
58
+ 'subarray',
59
+ ])
60
+
61
+ const getKind = (value: unknown): Kind | null => {
62
+ if (typeof value !== 'object' || value === null) {
63
+ return null
64
+ }
65
+ if (ArrayBuffer.isView(value)) {
66
+ return value instanceof DataView ? null : 'typed-array'
67
+ }
68
+ switch (Object.getPrototypeOf(value)) {
69
+ case Object.prototype:
70
+ case null:
71
+ return 'object'
72
+ case Array.prototype:
73
+ return 'array'
74
+ case Map.prototype:
75
+ return 'map'
76
+ case Set.prototype:
77
+ return 'set'
78
+ case Date.prototype:
79
+ return 'date'
80
+ default:
81
+ return null
82
+ }
83
+ }
84
+
85
+ const INVALID_VALUE =
86
+ 'The state can contain only primitives, functions, plain objects, arrays, maps, sets, dates and typed arrays'
87
+
88
+ /* Returns the kind of a value allowed in the state, null when not drafted. */
89
+ const checkValue = (value: unknown): Kind | null => {
90
+ const kind = getKind(value)
91
+ invariant(kind || typeof value !== 'object' || value === null, INVALID_VALUE)
92
+ return kind
93
+ }
94
+
95
+ const current = (draft: Draft) => draft.copy ?? draft.base
96
+
97
+ const createDraft = (
98
+ base: object,
99
+ kind: Kind,
100
+ parent: Draft | null,
101
+ scope: Scope,
102
+ ): object => {
103
+ // arrays need an array target to be recognized by Array.isArray()
104
+ const target: object =
105
+ kind === 'array'
106
+ ? []
107
+ : (Object.create(Object.getPrototypeOf(base) as object | null) as object)
108
+ const { proxy, revoke } = Proxy.revocable(target, handler)
109
+ const draft: Draft = {
110
+ kind,
111
+ base,
112
+ copy: null,
113
+ parent,
114
+ scope,
115
+ modified: false,
116
+ proxy,
117
+ result: null,
118
+ items: null,
119
+ }
120
+ drafts.set(proxy, draft)
121
+ targets.set(target, draft)
122
+ scope.revokes.push(revoke)
123
+ return proxy
124
+ }
125
+
126
+ const prepareCopy = (draft: Draft) => {
127
+ if (draft.copy) {
128
+ return
129
+ }
130
+ const { base } = draft
131
+ switch (draft.kind) {
132
+ case 'object':
133
+ draft.copy = Object.assign(
134
+ Object.create(Object.getPrototypeOf(base) as object | null) as object,
135
+ base,
136
+ )
137
+ break
138
+ case 'array':
139
+ draft.copy = (base as unknown[]).slice()
140
+ break
141
+ case 'map':
142
+ draft.copy = new Map(base as Map<unknown, unknown>)
143
+ break
144
+ case 'set': {
145
+ // the items are drafted up front, as sets have no keys to draft them by
146
+ const copy = new Set<unknown>()
147
+ draft.items = new Map()
148
+ for (const item of base as Set<unknown>) {
149
+ const kind = checkValue(item)
150
+ if (kind) {
151
+ const itemDraft = createDraft(
152
+ item as object,
153
+ kind,
154
+ draft,
155
+ draft.scope,
156
+ )
157
+ draft.items.set(item, itemDraft)
158
+ copy.add(itemDraft)
159
+ } else {
160
+ copy.add(item)
161
+ }
162
+ }
163
+ draft.copy = copy
164
+ break
165
+ }
166
+ case 'date':
167
+ draft.copy = new Date((base as Date).getTime())
168
+ break
169
+ case 'typed-array':
170
+ // of the same type, as slice() creates the array with its constructor
171
+ draft.copy = (base as TypedArray).slice()
172
+ break
173
+ }
174
+ }
175
+
176
+ const markModified = (draft: Draft | null) => {
177
+ while (draft && !draft.modified) {
178
+ prepareCopy(draft)
179
+ draft.modified = true
180
+ draft = draft.parent
181
+ }
182
+ }
183
+
184
+ /*
185
+ * Returns the value under the key, drafted when it is a draftable value
186
+ * of the base object, left unchanged.
187
+ */
188
+ const getValue = (
189
+ draft: Draft,
190
+ value: unknown,
191
+ baseValue: unknown,
192
+ store: (copy: object, child: object) => void,
193
+ ): unknown => {
194
+ if (drafts.has(value as object)) {
195
+ return value
196
+ }
197
+ const kind = checkValue(value)
198
+ if (!kind || value !== baseValue) {
199
+ return value
200
+ }
201
+ prepareCopy(draft)
202
+ const child = createDraft(value as object, kind, draft, draft.scope)
203
+ store(draft.copy!, child)
204
+ return child
205
+ }
206
+
207
+ /* Returns the original object of a draft, or the value itself. */
208
+ const getOriginal = (value: unknown): unknown =>
209
+ (typeof value === 'object' && value !== null && drafts.get(value)?.base) ||
210
+ value
211
+
212
+ const getProperty = (draft: Draft, key: PropertyKey): unknown => {
213
+ const source = current(draft) as Entries
214
+ if (draft.kind === 'array' && ARRAY_SEARCHES.has(key)) {
215
+ // the objects passed to commands, e.g. from props, are not the drafts
216
+ return (value: unknown, ...args: [number?]) => {
217
+ const items = (current(draft) as unknown[]).map(getOriginal)
218
+ return items[key as 'indexOf'](getOriginal(value), ...args)
219
+ }
220
+ }
221
+ if (!Object.hasOwn(source, key)) {
222
+ // the array methods work through the proxy
223
+ return Reflect.get(source, key, draft.proxy)
224
+ }
225
+ const base = draft.base as Entries
226
+ return getValue(
227
+ draft,
228
+ source[key],
229
+ Object.hasOwn(base, key) ? base[key] : undefined,
230
+ (copy, child) => ((copy as Entries)[key] = child),
231
+ )
232
+ }
233
+
234
+ const getMapValue = (draft: Draft, key: unknown): unknown => {
235
+ const source = current(draft) as Map<unknown, unknown>
236
+ return getValue(
237
+ draft,
238
+ source.get(key),
239
+ (draft.base as Map<unknown, unknown>).get(key),
240
+ (copy, child) => (copy as Map<unknown, unknown>).set(key, child),
241
+ )
242
+ }
243
+
244
+ /* The methods of a map draft, keeping the copy in sync. */
245
+ const mapMethods = new Map<PropertyKey, (draft: Draft) => unknown>([
246
+ ['get', draft => (key: unknown) => getMapValue(draft, key)],
247
+ [
248
+ 'set',
249
+ draft => (key: unknown, value: unknown) => {
250
+ const source = current(draft) as Map<unknown, unknown>
251
+ if (!source.has(key) || !Object.is(source.get(key), value)) {
252
+ markModified(draft)
253
+ ;(draft.copy as Map<unknown, unknown>).set(key, value)
254
+ }
255
+ return draft.proxy
256
+ },
257
+ ],
258
+ [
259
+ 'delete',
260
+ draft => (key: unknown) => {
261
+ if (!(current(draft) as Map<unknown, unknown>).has(key)) {
262
+ return false
263
+ }
264
+ markModified(draft)
265
+ return (draft.copy as Map<unknown, unknown>).delete(key)
266
+ },
267
+ ],
268
+ [
269
+ 'clear',
270
+ draft => () => {
271
+ if ((current(draft) as Map<unknown, unknown>).size) {
272
+ markModified(draft)
273
+ ;(draft.copy as Map<unknown, unknown>).clear()
274
+ }
275
+ },
276
+ ],
277
+ [
278
+ 'forEach',
279
+ draft =>
280
+ (
281
+ callback: (value: unknown, key: unknown, map: object) => void,
282
+ thisArg?: unknown,
283
+ ) => {
284
+ for (const key of [
285
+ ...(current(draft) as Map<unknown, unknown>).keys(),
286
+ ]) {
287
+ callback.call(thisArg, getMapValue(draft, key), key, draft.proxy)
288
+ }
289
+ },
290
+ ],
291
+ [
292
+ 'keys',
293
+ draft => () =>
294
+ [...(current(draft) as Map<unknown, unknown>).keys()].values(),
295
+ ],
296
+ [
297
+ 'values',
298
+ draft =>
299
+ function* () {
300
+ for (const key of [
301
+ ...(current(draft) as Map<unknown, unknown>).keys(),
302
+ ]) {
303
+ yield getMapValue(draft, key)
304
+ }
305
+ },
306
+ ],
307
+ ['entries', draft => () => mapEntries(draft)],
308
+ [Symbol.iterator, draft => () => mapEntries(draft)],
309
+ ])
310
+
311
+ function* mapEntries(draft: Draft) {
312
+ for (const key of [...(current(draft) as Map<unknown, unknown>).keys()]) {
313
+ yield [key, getMapValue(draft, key)]
314
+ }
315
+ }
316
+
317
+ const hasSetItem = (draft: Draft, item: unknown) => {
318
+ const source = current(draft) as Set<unknown>
319
+ if (source.has(item)) {
320
+ return true
321
+ }
322
+ const itemDraft = draft.items?.get(item)
323
+ return itemDraft !== undefined && source.has(itemDraft)
324
+ }
325
+
326
+ /* The methods of a set draft, iterating over the drafted items. */
327
+ const setMethods = new Map<PropertyKey, (draft: Draft) => unknown>([
328
+ ['has', draft => (item: unknown) => hasSetItem(draft, item)],
329
+ [
330
+ 'add',
331
+ draft => (item: unknown) => {
332
+ if (!hasSetItem(draft, item)) {
333
+ markModified(draft)
334
+ ;(draft.copy as Set<unknown>).add(item)
335
+ }
336
+ return draft.proxy
337
+ },
338
+ ],
339
+ [
340
+ 'delete',
341
+ draft => (item: unknown) => {
342
+ if (!hasSetItem(draft, item)) {
343
+ return false
344
+ }
345
+ markModified(draft)
346
+ const copy = draft.copy as Set<unknown>
347
+ return copy.delete(item) || copy.delete(draft.items!.get(item))
348
+ },
349
+ ],
350
+ [
351
+ 'clear',
352
+ draft => () => {
353
+ if ((current(draft) as Set<unknown>).size) {
354
+ markModified(draft)
355
+ ;(draft.copy as Set<unknown>).clear()
356
+ }
357
+ },
358
+ ],
359
+ [
360
+ 'forEach',
361
+ draft =>
362
+ (
363
+ callback: (value: unknown, key: unknown, set: object) => void,
364
+ thisArg?: unknown,
365
+ ) => {
366
+ for (const item of setItems(draft)) {
367
+ callback.call(thisArg, item, item, draft.proxy)
368
+ }
369
+ },
370
+ ],
371
+ ['keys', draft => () => setItems(draft).values()],
372
+ ['values', draft => () => setItems(draft).values()],
373
+ [Symbol.iterator, draft => () => setItems(draft).values()],
374
+ [
375
+ 'entries',
376
+ draft => () =>
377
+ setItems(draft)
378
+ .map(item => [item, item] as const)
379
+ .values(),
380
+ ],
381
+ ])
382
+
383
+ const setItems = (draft: Draft) => {
384
+ prepareCopy(draft)
385
+ return [...(draft.copy as Set<unknown>)]
386
+ }
387
+
388
+ /*
389
+ * Reads the property of a map, set, date or typed array, keeping their
390
+ * internal slots out of the proxy.
391
+ */
392
+ const getSlotProperty = (draft: Draft, key: PropertyKey): unknown => {
393
+ if (draft.kind === 'map' || draft.kind === 'set') {
394
+ const method = (draft.kind === 'map' ? mapMethods : setMethods).get(key)
395
+ if (method) {
396
+ return method(draft)
397
+ }
398
+ if (draft.kind === 'set' && key !== 'size') {
399
+ // the other methods, as union(), take the drafted items
400
+ prepareCopy(draft)
401
+ }
402
+ }
403
+ if (draft.kind === 'typed-array' && key === 'buffer') {
404
+ // the memory of the copy, as it can be written to
405
+ markModified(draft)
406
+ return (draft.copy as TypedArray).buffer
407
+ }
408
+ const source = current(draft) as Entries
409
+ if (draft.kind === 'typed-array' && Object.hasOwn(source, key)) {
410
+ return source[key]
411
+ }
412
+ const value = Reflect.get(source, key, source)
413
+ if (typeof value !== 'function' || key === 'constructor') {
414
+ return value
415
+ }
416
+ const method = value as (...args: unknown[]) => unknown
417
+ const mutates =
418
+ draft.kind === 'date'
419
+ ? typeof key === 'string' && key.startsWith('set')
420
+ : draft.kind === 'typed-array' && TYPED_ARRAY_MUTATORS.has(key)
421
+ if (!mutates) {
422
+ return method.bind(source)
423
+ }
424
+ return (...args: unknown[]) => {
425
+ prepareCopy(draft)
426
+ const copy = draft.copy!
427
+ const time = draft.kind === 'date' ? (copy as Date).getTime() : null
428
+ const result = method.apply(copy, args)
429
+ if (draft.kind !== 'date' || !Object.is(time, (copy as Date).getTime())) {
430
+ markModified(draft)
431
+ }
432
+ return result === copy ? draft.proxy : result
433
+ }
434
+ }
435
+
436
+ const handler: ProxyHandler<object> = {
437
+ get(target, key) {
438
+ const draft = targets.get(target)!
439
+ return draft.kind === 'object' || draft.kind === 'array'
440
+ ? getProperty(draft, key)
441
+ : getSlotProperty(draft, key)
442
+ },
443
+ set(target, key, value) {
444
+ const draft = targets.get(target)!
445
+ const source = current(draft) as Entries
446
+ if (Object.hasOwn(source, key) && Object.is(source[key], value)) {
447
+ return true
448
+ }
449
+ markModified(draft)
450
+ ;(draft.copy as Entries)[key] = value
451
+ return true
452
+ },
453
+ deleteProperty(target, key) {
454
+ const draft = targets.get(target)!
455
+ if (Object.hasOwn(current(draft), key)) {
456
+ markModified(draft)
457
+ delete (draft.copy as Entries)[key]
458
+ }
459
+ return true
460
+ },
461
+ has(target, key) {
462
+ return key in current(targets.get(target)!)
463
+ },
464
+ ownKeys(target) {
465
+ return Reflect.ownKeys(current(targets.get(target)!))
466
+ },
467
+ getOwnPropertyDescriptor(target, key) {
468
+ const draft = targets.get(target)!
469
+ const source = current(draft) as Entries
470
+ const descriptor = Reflect.getOwnPropertyDescriptor(source, key)
471
+ if (!descriptor) {
472
+ return undefined
473
+ }
474
+ // the length of the array target is not configurable
475
+ const configurable = draft.kind !== 'array' || key !== 'length'
476
+ return 'value' in descriptor
477
+ ? { ...descriptor, value: source[key], writable: true, configurable }
478
+ : { ...descriptor, configurable }
479
+ },
480
+ defineProperty(target, key, descriptor) {
481
+ const draft = targets.get(target)!
482
+ markModified(draft)
483
+ return Reflect.defineProperty(draft.copy!, key, descriptor)
484
+ },
485
+ setPrototypeOf() {
486
+ return false
487
+ },
488
+ preventExtensions() {
489
+ return false
490
+ },
491
+ }
492
+
493
+ /*
494
+ * Replaces the drafts with their results, also in the new values assigned
495
+ * to the drafts, as the drafts are revoked once the next state is created.
496
+ */
497
+ const finalize = (value: unknown, scope: Scope): unknown => {
498
+ if (typeof value !== 'object' || value === null) {
499
+ return value
500
+ }
501
+ const draft = drafts.get(value)
502
+ if (draft) {
503
+ return finalizeDraft(draft)
504
+ }
505
+ const kind = checkValue(value)
506
+ if (scope.visited.has(value) || Object.isFrozen(value)) {
507
+ return value
508
+ }
509
+ scope.visited.add(value)
510
+ switch (kind) {
511
+ case 'object':
512
+ case 'array': {
513
+ const entries = value as Entries
514
+ for (const key of Reflect.ownKeys(entries)) {
515
+ const next = finalize(entries[key], scope)
516
+ if (next !== entries[key]) {
517
+ entries[key] = next
518
+ }
519
+ }
520
+ break
521
+ }
522
+ case 'map': {
523
+ const map = value as Map<unknown, unknown>
524
+ for (const [key, item] of map) {
525
+ const next = finalize(item, scope)
526
+ if (next !== item) {
527
+ map.set(key, next)
528
+ }
529
+ }
530
+ break
531
+ }
532
+ case 'set': {
533
+ const set = value as Set<unknown>
534
+ const items = [...set]
535
+ const nextItems = items.map(item => finalize(item, scope))
536
+ if (nextItems.some((item, index) => item !== items[index])) {
537
+ set.clear()
538
+ nextItems.forEach(item => set.add(item))
539
+ }
540
+ break
541
+ }
542
+ }
543
+ return value
544
+ }
545
+
546
+ const finalizeDraft = (draft: Draft): object => {
547
+ if (draft.result) {
548
+ return draft.result
549
+ }
550
+ if (!draft.modified) {
551
+ return (draft.result = draft.base)
552
+ }
553
+ const { base, copy, scope } = draft
554
+ draft.result = copy
555
+ switch (draft.kind) {
556
+ case 'object':
557
+ case 'array': {
558
+ const entries = copy as Entries
559
+ for (const key of Reflect.ownKeys(entries)) {
560
+ const value = entries[key]
561
+ if (value !== (base as Entries)[key]) {
562
+ const next = finalize(value, scope)
563
+ if (next !== value) {
564
+ entries[key] = next
565
+ }
566
+ }
567
+ }
568
+ break
569
+ }
570
+ case 'map': {
571
+ const map = copy as Map<unknown, unknown>
572
+ for (const [key, value] of map) {
573
+ if (value !== (base as Map<unknown, unknown>).get(key)) {
574
+ map.set(key, finalize(value, scope))
575
+ }
576
+ }
577
+ break
578
+ }
579
+ case 'set': {
580
+ // in place, for the items referring to the set to get the result
581
+ const set = copy as Set<unknown>
582
+ const items = [...set]
583
+ set.clear()
584
+ items.forEach(item => set.add(finalize(item, scope)))
585
+ break
586
+ }
587
+ }
588
+ return draft.result!
589
+ }
590
+
591
+ /*
592
+ * Calls the recipe with a draft of the base object and returns the result
593
+ * of the changes made to it, or the base object when nothing has changed.
594
+ * Plain objects, arrays, maps, sets, dates and typed arrays are drafted,
595
+ * primitives and functions are kept as they are, to be replaced only,
596
+ * and other values, as class instances, throw when read or assigned.
597
+ */
598
+ export const produce = <T>(base: T, recipe: (draft: T) => void): T => {
599
+ const kind = getKind(base)
600
+ invariant(kind, INVALID_VALUE)
601
+ const scope: Scope = { revokes: [], visited: new WeakSet() }
602
+ const root = createDraft(base as object, kind, null, scope)
603
+ try {
604
+ recipe(root as T)
605
+ return finalizeDraft(drafts.get(root)!) as T
606
+ } finally {
607
+ scope.revokes.forEach(revoke => revoke())
608
+ }
609
+ }