@brust/runtime-dom 2.0.0-alpha.24 → 2.0.0-alpha.25
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/package.json +1 -1
- package/src/directives/bind.ts +17 -0
- package/src/directives/model.ts +4 -3
- package/src/directives/on.ts +5 -1
package/package.json
CHANGED
package/src/directives/bind.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { reactEvent } from './on'
|
|
1
2
|
import { BINDERS, parsed, read, warnOnce } from '../core-api'
|
|
2
3
|
import { BOOLEAN, OVERLOADED, STRINGIFY } from '../attr-kinds'
|
|
3
4
|
import type { Instance } from '../instance'
|
|
@@ -11,6 +12,8 @@ function writeAttr(inst: Instance, attr: string, el: Element, name: string, v: u
|
|
|
11
12
|
// setAttribute: `className` is a getter-only SVGAnimatedString on SVG elements (F96a).
|
|
12
13
|
// F311: undefined/null removes `class` (React does; `className=""` keeps an empty one), and an empty `style` is no attribute (the first paint omits it)
|
|
13
14
|
if (name === 'class') { if (v == null) el.removeAttribute('class'); else el.setAttribute('class', `${v}`); return }
|
|
15
|
+
// F434: a multiple select is bound to an array, one pick per option (`null`/`undefined` leave the picks alone, as React does)
|
|
16
|
+
if (name === 'value' && el instanceof HTMLSelectElement && el.multiple) { if (v != null) { const vs = ([] as unknown[]).concat(v).map(String); for (const o of el.options) o.selected = vs.includes(o.value) } return }
|
|
14
17
|
if (name === 'value' && 'value' in el) { (el as HTMLInputElement).value = v == null ? '' : String(v); return }
|
|
15
18
|
// React passes every attribute of a custom element (a tag with `-`) through as given: `true` is ``, `false` omits it.
|
|
16
19
|
const custom = el.localName.includes('-')
|
|
@@ -54,6 +57,20 @@ BINDERS.push({
|
|
|
54
57
|
match: (a) => a.startsWith('x-prop-'),
|
|
55
58
|
bind: ({ inst, el, attr, raw }) => {
|
|
56
59
|
const name = attr.slice('x-prop-'.length)
|
|
60
|
+
// F369c: `x-prop-on-<event>="<prop key>"` calls the instance's prop (a function a caller handed through the child's rest)
|
|
61
|
+
// with the event, reading it at call time so a prop that changes is the one that runs. The event is `[a-z0-9]+`.
|
|
62
|
+
if (name.startsWith('on-')) {
|
|
63
|
+
const event = name.slice(3)
|
|
64
|
+
if (!raw || !/^[a-z0-9]+$/.test(event)) { warnOnce(`pb:${inst.name}:${name}`, `${attr} in ${inst.name} refused`); return }
|
|
65
|
+
const handler = (e: Event) => {
|
|
66
|
+
reactEvent(e)
|
|
67
|
+
const ps = inst.props(); const fn = Object.hasOwn(ps, raw) ? ps[raw] : undefined
|
|
68
|
+
if (typeof fn === 'function') (fn as (e: Event) => void)(e)
|
|
69
|
+
}
|
|
70
|
+
el.addEventListener(event, handler)
|
|
71
|
+
inst.onCleanup(() => el.removeEventListener(event, handler))
|
|
72
|
+
return
|
|
73
|
+
}
|
|
57
74
|
if (!name || !raw || name.startsWith('on') || name.startsWith('x-') || PROP_REFUSED.has(name)) {
|
|
58
75
|
warnOnce(`pb:${inst.name}:${name}`, `${attr} in ${inst.name} refused`); return
|
|
59
76
|
}
|
package/src/directives/model.ts
CHANGED
|
@@ -17,9 +17,10 @@ BINDERS.push({
|
|
|
17
17
|
const h = () => { if (input.checked) s.set(input.value) }; el.addEventListener('change', h); inst.onCleanup(() => el.removeEventListener('change', h))
|
|
18
18
|
inst.effect(() => { input.checked = s() === input.value })
|
|
19
19
|
} else if (el instanceof HTMLSelectElement) {
|
|
20
|
-
|
|
21
|
-
const
|
|
22
|
-
const
|
|
20
|
+
// F434: a multiple select is an array of the picked values
|
|
21
|
+
const many = el.multiple
|
|
22
|
+
const h = () => s.set(many ? Array.from(el.options).filter((o) => o.selected).map((o) => o.value) : el.value); el.addEventListener('change', h); inst.onCleanup(() => el.removeEventListener('change', h))
|
|
23
|
+
const apply = () => { const v = s(); if (many) { if (v != null) { const vs = ([] as unknown[]).concat(v).map(String); for (const o of el.options) o.selected = vs.includes(o.value) } } else if (el.value !== String(v)) el.value = String(v ?? '') }
|
|
23
24
|
inst.effect(apply)
|
|
24
25
|
const mo = new MutationObserver(() => apply()); mo.observe(el, { childList: true }); inst.onCleanup(() => mo.disconnect())
|
|
25
26
|
} else {
|
package/src/directives/on.ts
CHANGED
|
@@ -10,13 +10,17 @@ const SYNTHETIC: PropertyDescriptorMap = {
|
|
|
10
10
|
isPersistent: { value: () => true },
|
|
11
11
|
nativeEvent: { get(this: Event) { return this } },
|
|
12
12
|
}
|
|
13
|
+
/** Gives a DOM event the React members (once). Shared with `x-prop-on-*`. */
|
|
14
|
+
export function reactEvent(e: Event): void {
|
|
15
|
+
if (!('isPropagationStopped' in e)) Object.defineProperties(e, SYNTHETIC)
|
|
16
|
+
}
|
|
13
17
|
BINDERS.push({
|
|
14
18
|
match: (a) => a.startsWith('x-on-'),
|
|
15
19
|
bind: ({ inst, el, attr, raw, scope }) => {
|
|
16
20
|
const event = attr.slice('x-on-'.length)
|
|
17
21
|
const p = parsed(inst, attr, raw); if (!p) return
|
|
18
22
|
const handler = (e: Event) => {
|
|
19
|
-
|
|
23
|
+
reactEvent(e)
|
|
20
24
|
const fn = member(inst, p, attr)
|
|
21
25
|
if (typeof fn !== 'function') return
|
|
22
26
|
const args = p.bindings.map((b) => scope[b])
|