@bitstillery/mithril 3.9.0 → 3.9.1
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/api/router.ts +13 -1
- package/index.ts +1 -1
- package/package.json +2 -2
- package/render/vnode.ts +15 -8
- package/state.ts +26 -30
package/api/router.ts
CHANGED
|
@@ -26,6 +26,15 @@ export interface RouteResolver<Attrs = Record<string, any>, State = any> {
|
|
|
26
26
|
export type SSRState = Record<string, any>
|
|
27
27
|
export type SSRResult = string | {html: string; state: SSRState}
|
|
28
28
|
|
|
29
|
+
/**
|
|
30
|
+
* A route parameter as the URL can carry it. Path params are always strings; the querystring parser
|
|
31
|
+
* also turns `true`/`false` into booleans and `a[]=`/`a[b]=` keys into arrays and objects. A plain
|
|
32
|
+
* object in `history.state` is merged over the params too, so values put there must keep to this
|
|
33
|
+
* shape for the type to hold.
|
|
34
|
+
*/
|
|
35
|
+
export type RouteParamValue = string | boolean | RouteParamValue[] | {[key: string]: RouteParamValue}
|
|
36
|
+
export type RouteParams = Record<string, RouteParamValue>
|
|
37
|
+
|
|
29
38
|
export interface Route {
|
|
30
39
|
(path: string, params?: Record<string, any>, shouldReplaceHistory?: boolean): void
|
|
31
40
|
(path: string, component: ComponentType, shouldReplaceHistory?: boolean): void
|
|
@@ -33,7 +42,10 @@ export interface Route {
|
|
|
33
42
|
get: () => string
|
|
34
43
|
prefix: string
|
|
35
44
|
link: (vnode: VnodeType) => string
|
|
36
|
-
param:
|
|
45
|
+
param: {
|
|
46
|
+
(key: string): RouteParamValue | undefined
|
|
47
|
+
(): RouteParams
|
|
48
|
+
}
|
|
37
49
|
params: Record<string, any>
|
|
38
50
|
Link: ComponentType
|
|
39
51
|
SKIP: {}
|
package/index.ts
CHANGED
|
@@ -160,7 +160,7 @@ export type {
|
|
|
160
160
|
} from './render/vnode'
|
|
161
161
|
export {MithrilComponent}
|
|
162
162
|
export type {Hyperscript} from './render/hyperscript'
|
|
163
|
-
export type {Route, RouteResolver, RedirectObject} from './api/router'
|
|
163
|
+
export type {Route, RouteParams, RouteParamValue, RouteResolver, RedirectObject} from './api/router'
|
|
164
164
|
export type {Render, Redraw, Mount} from './api/mount-redraw'
|
|
165
165
|
|
|
166
166
|
// Namespace merge: enables m.Vnode<Attrs> and m.Children when using import m from '@bitstillery/mithril'
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bitstillery/mithril",
|
|
3
|
-
"version": "3.9.
|
|
3
|
+
"version": "3.9.1",
|
|
4
4
|
"description": "Mithril + Signals, Store and SSR",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Bitstillery",
|
|
@@ -60,7 +60,7 @@
|
|
|
60
60
|
"bench:signal": "NODE_ENV=production bun run bench/index.ts signal",
|
|
61
61
|
"bench:state": "NODE_ENV=production bun run bench/index.ts state",
|
|
62
62
|
"bench:profile": "bun --cpu-prof-md run bench/index.ts",
|
|
63
|
-
"prepublishOnly": "bun run lint:ts",
|
|
63
|
+
"prepublishOnly": "oxfmt --check && bun run lint:ts-syntax && bun run lint:ts-types",
|
|
64
64
|
"pack:dry-run": "npm pack --dry-run",
|
|
65
65
|
"release": "./scripts/release.sh",
|
|
66
66
|
"release:patch": "./scripts/release.sh --release-as patch",
|
package/render/vnode.ts
CHANGED
|
@@ -28,19 +28,26 @@ export type VnodeDOM<Attrs = Record<string, any>, State = any> = ComponentVnode<
|
|
|
28
28
|
*/
|
|
29
29
|
export type ComponentVnode<Attrs = Record<string, any>, State = any> = Omit<Vnode<Attrs, State>, 'attrs'> & {attrs: Attrs}
|
|
30
30
|
|
|
31
|
+
/**
|
|
32
|
+
* The hooks are declared as methods, not function-typed properties: under `strictFunctionTypes` a
|
|
33
|
+
* property's parameters are checked contravariantly, so `Component<{name: string}>` would not be a
|
|
34
|
+
* `Component<Record<string, any>>` and `m(Icon, attrs)` could not be passed where `Children` is expected.
|
|
35
|
+
* Method parameters are bivariant, which matches how Mithril really calls them — with the vnode it
|
|
36
|
+
* built for that component.
|
|
37
|
+
*/
|
|
31
38
|
export interface Component<Attrs = Record<string, any>, State = any> {
|
|
32
|
-
oninit
|
|
33
|
-
oncreate
|
|
34
|
-
onbeforeupdate
|
|
35
|
-
onupdate
|
|
36
|
-
onbeforeremove
|
|
37
|
-
onremove
|
|
38
|
-
view
|
|
39
|
+
oninit?(vnode: ComponentVnode<Attrs, State>): void
|
|
40
|
+
oncreate?(vnode: ComponentVnode<Attrs, State>): void
|
|
41
|
+
onbeforeupdate?(vnode: ComponentVnode<Attrs, State>, old: ComponentVnode<Attrs, State>): boolean | void
|
|
42
|
+
onupdate?(vnode: ComponentVnode<Attrs, State>): void
|
|
43
|
+
onbeforeremove?(vnode: ComponentVnode<Attrs, State>): Promise<any> | void
|
|
44
|
+
onremove?(vnode: ComponentVnode<Attrs, State>): void
|
|
45
|
+
view(vnode: ComponentVnode<Attrs, State>): Children | Vnode | null
|
|
39
46
|
}
|
|
40
47
|
|
|
41
48
|
export interface ComponentFactory<Attrs = Record<string, any>, State = any> {
|
|
42
49
|
(...args: any[]): Component<Attrs, State>
|
|
43
|
-
view
|
|
50
|
+
view?(vnode: ComponentVnode<Attrs, State>): Children | Vnode | null
|
|
44
51
|
}
|
|
45
52
|
|
|
46
53
|
export type ComponentType<Attrs = Record<string, any>, State = any> =
|
package/state.ts
CHANGED
|
@@ -232,15 +232,17 @@ export function state<T extends Record<string, any>>(initial: T, name?: string,
|
|
|
232
232
|
|
|
233
233
|
// Handle arrays
|
|
234
234
|
if (Array.isArray(obj)) {
|
|
235
|
-
//
|
|
236
|
-
//
|
|
237
|
-
|
|
235
|
+
// Init, push/unshift, splice and index assignment all wrap through here, so an element typed as
|
|
236
|
+
// `State<E>` is one: objects and arrays become their own state proxy (the proxy is the element, not a
|
|
237
|
+
// signal around it), everything else a signal.
|
|
238
|
+
const toElement = (item: any) => {
|
|
238
239
|
if (typeof item === 'object' && item !== null) {
|
|
239
|
-
// Recursively wrap nested objects AND arrays in Proxies
|
|
240
240
|
return initializeSignals(item, undefined, context)
|
|
241
241
|
}
|
|
242
242
|
return toSignal(item)
|
|
243
|
-
}
|
|
243
|
+
}
|
|
244
|
+
// Arrays don't get their own signalMap - they use the parent's
|
|
245
|
+
const signals = obj.map(toElement)
|
|
244
246
|
|
|
245
247
|
// List of mutating array methods that should trigger the parent signal
|
|
246
248
|
const mutatingMethods = new Set([
|
|
@@ -354,14 +356,7 @@ export function state<T extends Record<string, any>>(initial: T, name?: string,
|
|
|
354
356
|
const deleteCount = args[1] ?? signals.length - start
|
|
355
357
|
const newItems = args.slice(2)
|
|
356
358
|
|
|
357
|
-
|
|
358
|
-
const newSignals = newItems.map((item: any) => {
|
|
359
|
-
if (typeof item === 'object' && item !== null) {
|
|
360
|
-
// Wrap objects/arrays in Proxies (NOT in Signals - the Proxy IS the value)
|
|
361
|
-
return initializeSignals(item, undefined, context)
|
|
362
|
-
}
|
|
363
|
-
return toSignal(item)
|
|
364
|
-
})
|
|
359
|
+
const newSignals = newItems.map(toElement)
|
|
365
360
|
|
|
366
361
|
// Update the signals array (target is signals array)
|
|
367
362
|
const removed = signals.splice(start, deleteCount, ...newSignals)
|
|
@@ -399,14 +394,7 @@ export function state<T extends Record<string, any>>(initial: T, name?: string,
|
|
|
399
394
|
let result
|
|
400
395
|
if (propStr === 'push' || propStr === 'unshift') {
|
|
401
396
|
const newItems = args
|
|
402
|
-
|
|
403
|
-
const newSignals = newItems.map((item: any) => {
|
|
404
|
-
if (typeof item === 'object' && item !== null) {
|
|
405
|
-
// Wrap objects/arrays in Proxies (NOT in Signals - the Proxy IS the value)
|
|
406
|
-
return initializeSignals(item, undefined, context)
|
|
407
|
-
}
|
|
408
|
-
return toSignal(item)
|
|
409
|
-
})
|
|
397
|
+
const newSignals = newItems.map(toElement)
|
|
410
398
|
if (propStr === 'push') {
|
|
411
399
|
result = signals.push(...newSignals)
|
|
412
400
|
} else {
|
|
@@ -449,9 +437,11 @@ export function state<T extends Record<string, any>>(initial: T, name?: string,
|
|
|
449
437
|
const fillValue = args[0]
|
|
450
438
|
const start = args[1] ?? 0
|
|
451
439
|
const end = args[2] ?? signals.length
|
|
452
|
-
|
|
440
|
+
// A wrap per slot, so no two slots share a signal. An object fill value still reads
|
|
441
|
+
// back as one shared element in every slot, as native fill shares the reference:
|
|
442
|
+
// `toElement` returns the cached proxy for the same object.
|
|
453
443
|
for (let i = start; i < end; i++) {
|
|
454
|
-
signals[i] =
|
|
444
|
+
signals[i] = toElement(fillValue)
|
|
455
445
|
}
|
|
456
446
|
result = signals.length
|
|
457
447
|
} else {
|
|
@@ -493,12 +483,14 @@ export function state<T extends Record<string, any>>(initial: T, name?: string,
|
|
|
493
483
|
set(target, prop, value) {
|
|
494
484
|
if (typeof prop === 'string' && !isNaN(Number(prop))) {
|
|
495
485
|
const index = Number(prop)
|
|
496
|
-
|
|
486
|
+
// Assigning past the end (`arr[arr.length] = x`) appends, so it wraps and notifies like push.
|
|
487
|
+
if ((index >= 0 && index < signals.length) || (Number.isInteger(index) && index >= signals.length)) {
|
|
497
488
|
const sig = signals[index]
|
|
498
|
-
|
|
489
|
+
// A primitive over a primitive keeps the element's signal, so `arr.$i` subscribers see it.
|
|
490
|
+
if (isSignal(sig) && (typeof value !== 'object' || value === null)) {
|
|
499
491
|
sig.value = value
|
|
500
492
|
} else {
|
|
501
|
-
signals[index] =
|
|
493
|
+
signals[index] = toElement(value)
|
|
502
494
|
}
|
|
503
495
|
// Trigger parent signal on element assignment (look up when called)
|
|
504
496
|
const parentSignal = arrayParentSignalMap.get(wrapped) || (wrapped as any)._parentSignal
|
|
@@ -520,9 +512,13 @@ export function state<T extends Record<string, any>>(initial: T, name?: string,
|
|
|
520
512
|
}
|
|
521
513
|
}
|
|
522
514
|
return true
|
|
523
|
-
}
|
|
524
|
-
|
|
525
|
-
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
if (prop === 'length') {
|
|
518
|
+
const previousLength = signals.length
|
|
519
|
+
const result = Reflect.set(target, prop, value)
|
|
520
|
+
// Resizing adds or drops elements without going through a mutator, so it notifies like splice.
|
|
521
|
+
if (signals.length !== previousLength) {
|
|
526
522
|
const parentSignal = arrayParentSignalMap.get(wrapped) || (wrapped as any)._parentSignal
|
|
527
523
|
if (parentSignal) {
|
|
528
524
|
// Notify subscribers directly since the array reference hasn't changed
|
|
@@ -541,8 +537,8 @@ export function state<T extends Record<string, any>>(initial: T, name?: string,
|
|
|
541
537
|
;(signal as any).__redrawCallback(parentSignal)
|
|
542
538
|
}
|
|
543
539
|
}
|
|
544
|
-
return true
|
|
545
540
|
}
|
|
541
|
+
return result
|
|
546
542
|
}
|
|
547
543
|
return Reflect.set(target, prop, value)
|
|
548
544
|
},
|