typed-dom
Advanced tools
Comparing version 0.0.251 to 0.0.252
{ | ||
"name": "typed-dom", | ||
"version": "0.0.251", | ||
"version": "0.0.252", | ||
"description": "A DOM component builder creating type-level DOM structures.", | ||
@@ -5,0 +5,0 @@ "private": false, |
@@ -171,8 +171,7 @@ # typed-dom | ||
```ts | ||
type ComponentTypeIs = | ||
El<"article", Element, { | ||
const component: El<"article", HTMLElement, { | ||
style: El<"style", HTMLStyleElement, string>; | ||
title: El<"h1", HTMLHeadingElement, string>; | ||
content: El<"ul", HTMLUListElement, readonly El<"li", HTMLLIElement, string>[]>; | ||
}>; | ||
}> | ||
@@ -183,3 +182,3 @@ // Note: El type is defined as follows. | ||
E extends Element = Element, | ||
C extends ElChildren = ElChildren, | ||
C extends El.Children = El.Children, | ||
> { | ||
@@ -189,12 +188,14 @@ readonly element: E; | ||
} | ||
type ElChildren | ||
= ElChildren.Void | ||
| ElChildren.Text | ||
| ElChildren.Array | ||
| ElChildren.Struct; | ||
namespace ElChildren { | ||
export type Void = undefined; | ||
export type Text = string; | ||
export type Array = readonly El[]; | ||
export type Struct = { [field: string]: El; }; | ||
export namespace El { | ||
export type Children = | ||
| Children.Void | ||
| Children.Text | ||
| Children.Array | ||
| Children.Struct; | ||
export namespace Children { | ||
export type Void = undefined; | ||
export type Text = string; | ||
export type Array = readonly El[]; | ||
export type Struct = { [field: string]: El; }; | ||
} | ||
} | ||
@@ -337,3 +338,3 @@ ``` | ||
i18n.init((err, t) => | ||
proxy<string>(ev.target as HTMLElement).children = err | ||
proxy<string>(ev.target as HTMLElement)!.children = err | ||
? 'Failed to init i18next.' | ||
@@ -340,0 +341,0 @@ : t(children, data)) |
158
src/proxy.ts
@@ -1,7 +0,5 @@ | ||
import { Mutable } from 'spica/type'; | ||
import { WeakMap, Event } from 'spica/global'; | ||
import { isArray, ObjectDefineProperties, ObjectKeys } from 'spica/alias'; | ||
import { identity } from './util/identity'; | ||
import { text, define } from './util/dom'; | ||
import { splice } from 'spica/array'; | ||
import { text } from './util/dom'; | ||
@@ -50,3 +48,3 @@ const tag = Symbol.for('typed-dom::tag'); | ||
export const isInit = Symbol(); | ||
export const isPartialUpdate = Symbol(); | ||
export const isObserverUpdate = Symbol(); | ||
} | ||
@@ -85,3 +83,3 @@ | ||
} | ||
throwErrorIfNotUsable(this); | ||
throwErrorIfNotUsable(this, null); | ||
proxies.set(this.element, this); | ||
@@ -93,3 +91,3 @@ switch (this[privates.type]) { | ||
case ElChildType.Text: | ||
define(this[privates.container], []); | ||
this[privates.container].replaceChildren(); | ||
this[privates.children] = this[privates.container].appendChild(text('')) as any; | ||
@@ -100,3 +98,3 @@ this.children = children as C; | ||
case ElChildType.Array: | ||
define(this[privates.container], []); | ||
this[privates.container].replaceChildren(); | ||
this[privates.children] = [] as El.Children.Array as C; | ||
@@ -107,4 +105,4 @@ this.children = children; | ||
case ElChildType.Struct: | ||
define(this[privates.container], []); | ||
this[privates.children] = this[privates.observe]({ ...children as El.Children.Struct }) as C; | ||
this[privates.container].replaceChildren(); | ||
this[privates.children] = this[privates.observe](children as El.Children.Struct) as C; | ||
this.children = children; | ||
@@ -153,14 +151,9 @@ this[privates.isInit] = false; | ||
} | ||
private [privates.isPartialUpdate] = false; | ||
private [privates.isObserverUpdate] = false; | ||
private [privates.observe](children: El.Children.Struct): C { | ||
const descs: PropertyDescriptorMap = {}; | ||
let i = -1; | ||
for (const name of ObjectKeys(children)) { | ||
if (name in {}) throw new Error(`TypedDOM: Child names must be different from the object property names.`); | ||
++i; | ||
let child = children[name]; | ||
throwErrorIfNotUsable(child); | ||
if (child.element !== this[privates.container].children[i]) { | ||
this[privates.container].appendChild(child.element); | ||
} | ||
throwErrorIfNotUsable(child, null); | ||
descs[name] = { | ||
@@ -173,24 +166,9 @@ configurable: true, | ||
set: (newChild: El) => { | ||
const oldChild = child; | ||
if (newChild === oldChild) return; | ||
if (this[privates.isPartialUpdate]) { | ||
child = newChild; | ||
if (newChild.element.parentNode === oldChild.element.parentNode) { | ||
const ref = newChild.element.nextSibling !== oldChild.element | ||
? newChild.element.nextSibling | ||
: oldChild.element.nextSibling; | ||
this[privates.container].replaceChild(newChild.element, oldChild.element); | ||
this[privates.container].insertBefore(oldChild.element, ref); | ||
} | ||
else { | ||
this[privates.container].insertBefore(newChild.element, oldChild.element); | ||
this[privates.container].removeChild(oldChild.element); | ||
} | ||
if (!this[privates.isObserverUpdate]) { | ||
this.children = { [name]: newChild } as C; | ||
} | ||
else { | ||
this.children = { | ||
...this[privates.children] as typeof children, | ||
[name]: newChild, | ||
} as C; | ||
this[privates.isObserverUpdate] = false; | ||
} | ||
child = newChild; | ||
}, | ||
@@ -209,3 +187,3 @@ }; | ||
if ((this[privates.children] as unknown as Text).parentNode !== this[privates.container]) { | ||
this[privates.children] = void 0 as unknown as C; | ||
this[privates.children] = void 0 as C; | ||
for (let ns = this[privates.container].childNodes, i = 0, len = ns.length; i < len; ++i) { | ||
@@ -224,2 +202,3 @@ const node = ns[i]; | ||
public set children(children: C) { | ||
assert(!this[privates.isObserverUpdate]); | ||
const removedChildren: El[] = []; | ||
@@ -233,39 +212,36 @@ const addedChildren: El[] = []; | ||
if (!this[privates.isInit] && children === this.children) return; | ||
const targetChildren = this[privates.children] as unknown as Text; | ||
const oldText = targetChildren.data; | ||
const node = this[privates.children] as unknown as Text; | ||
const newText = children as El.Children.Text; | ||
targetChildren.data = newText; | ||
if (newText === oldText) return; | ||
this.element.dispatchEvent(new Event('mutate', { bubbles: false, cancelable: true })); | ||
return; | ||
const oldText = node.data; | ||
isMutated = newText !== oldText; | ||
node.data = newText; | ||
break; | ||
} | ||
case ElChildType.Array: { | ||
const sourceChildren = children as El.Children.Array; | ||
const targetChildren = [] as Mutable<El.Children.Array>; | ||
this[privates.children] = targetChildren as El.Children as C; | ||
const nodeChildren = this[privates.container].children; | ||
const targetChildren = this[privates.children] as El.Children.Array; | ||
isMutated ||= sourceChildren.length !== targetChildren.length; | ||
for (let i = 0; i < sourceChildren.length; ++i) { | ||
const newChild = sourceChildren[i]; | ||
const el = nodeChildren[i]; | ||
const oldChild = targetChildren[i]; | ||
isMutated ||= newChild.element !== oldChild.element; | ||
throwErrorIfNotUsable(newChild, this[privates.container]); | ||
if (newChild.element.parentNode !== this[privates.container]) { | ||
throwErrorIfNotUsable(newChild); | ||
this[privates.scope](newChild); | ||
assert(!addedChildren.includes(newChild)); | ||
addedChildren.push(newChild); | ||
} | ||
if (newChild.element !== el) { | ||
if (newChild.element.parentNode !== this[privates.container]) { | ||
this[privates.scope](newChild); | ||
addedChildren.push(newChild); | ||
} | ||
this[privates.container].insertBefore(newChild.element, el); | ||
isMutated = true; | ||
} | ||
this[privates.container].replaceChildren(...sourceChildren.map(c => c.element)); | ||
this[privates.children] = children; | ||
for (let i = 0; i < targetChildren.length; ++i) { | ||
const oldChild = targetChildren[i]; | ||
if (oldChild.element.parentNode !== this[privates.container]) { | ||
assert(!removedChildren.includes(oldChild)); | ||
removedChildren.push(oldChild); | ||
assert(isMutated); | ||
} | ||
targetChildren.push(newChild); | ||
} | ||
for (let i = nodeChildren.length; sourceChildren.length < i--;) { | ||
const el = nodeChildren[sourceChildren.length]; | ||
if (!proxies.has(el)) continue; | ||
removedChildren.push(proxy(this[privates.container].removeChild(el))); | ||
isMutated = true; | ||
} | ||
assert(this[privates.container].children.length === sourceChildren.length); | ||
assert(targetChildren.every((child, i) => child.element === this[privates.container].children[i])); | ||
assert(sourceChildren.every((child, i) => child.element === this[privates.container].children[i])); | ||
break; | ||
@@ -276,26 +252,35 @@ } | ||
const targetChildren = this[privates.children] as El.Children.Struct; | ||
assert.deepStrictEqual(Object.keys(sourceChildren), Object.keys(targetChildren)); | ||
for (const name of ObjectKeys(targetChildren)) { | ||
for (const name of ObjectKeys(sourceChildren)) { | ||
const newChild = sourceChildren[name]; | ||
const oldChild = targetChildren[name]; | ||
const newChild = sourceChildren[name]; | ||
if (!newChild || !oldChild) continue; | ||
if (!this[privates.isInit] && newChild === oldChild) continue; | ||
if (newChild.element.parentNode !== this[privates.container]) { | ||
throwErrorIfNotUsable(newChild); | ||
} | ||
isMutated = true; | ||
throwErrorIfNotUsable(newChild, this[privates.container]); | ||
if (this[privates.isInit] || newChild !== oldChild && newChild.element.parentNode !== oldChild.element.parentNode) { | ||
this[privates.scope](newChild); | ||
assert(!addedChildren.includes(newChild)); | ||
addedChildren.push(newChild); | ||
if (!this[privates.isInit]) { | ||
let i = 0; | ||
i = removedChildren.lastIndexOf(newChild); | ||
i > -1 && splice(removedChildren, i, 1); | ||
if (this[privates.isInit]) { | ||
this[privates.container].appendChild(newChild.element); | ||
} | ||
else { | ||
this[privates.container].insertBefore(newChild.element, oldChild.element); | ||
this[privates.container].removeChild(oldChild.element); | ||
assert(!removedChildren.includes(oldChild)); | ||
removedChildren.push(oldChild); | ||
i = addedChildren.lastIndexOf(oldChild); | ||
i > -1 && splice(addedChildren, i, 1); | ||
} | ||
} | ||
this[privates.isPartialUpdate] = true; | ||
targetChildren[name] = sourceChildren[name]; | ||
this[privates.isPartialUpdate] = false; | ||
isMutated = true; | ||
else { | ||
assert(newChild.element.parentNode === oldChild.element.parentNode); | ||
const ref = newChild.element.nextSibling !== oldChild.element | ||
? newChild.element.nextSibling | ||
: oldChild.element.nextSibling; | ||
this[privates.container].replaceChild(newChild.element, oldChild.element); | ||
this[privates.container].insertBefore(oldChild.element, ref); | ||
} | ||
this[privates.isObserverUpdate] = true; | ||
targetChildren[name] = newChild; | ||
assert(!this[privates.isObserverUpdate]); | ||
this[privates.isObserverUpdate] = false; | ||
} | ||
@@ -326,14 +311,13 @@ break; | ||
export function proxy<E extends Element>(el: E): El<string, E, El.Children>; | ||
export function proxy<C extends El.Children>(el: Element): El<string, Element, C>; | ||
export function proxy<E extends Element, C extends El.Children>(el: E): El<string, E, C>; | ||
export function proxy(el: Element): El { | ||
const proxy = proxies.get(el); | ||
if (proxy) return proxy; | ||
throw new Error(`TypedDOM: This element has no proxy.`); | ||
export function proxy<C extends El.Children>(el: Element): El<string, Element, C> | undefined; | ||
export function proxy<E extends Element, C extends El.Children = El.Children>(el: E): El<string, E, C> | undefined; | ||
export function proxy<T extends string, E extends Element, C extends El.Children = El.Children>(el: E): El<T, E, C> | undefined; | ||
export function proxy(el: Element): El | undefined { | ||
return proxies.get(el); | ||
} | ||
function throwErrorIfNotUsable({ element }: El): void { | ||
if (!element.parentElement || !proxies.has(element.parentElement)) return; | ||
function throwErrorIfNotUsable(child: El, container: Element | ShadowRoot | null): void { | ||
const parent = child.element.parentElement; | ||
if (!parent || container === parent || !proxies.has(parent)) return; | ||
throw new Error(`TypedDOM: Typed DOM children must not be used to another typed DOM.`); | ||
} |
@@ -343,3 +343,3 @@ import { Shadow, HTML, SVG, API, El, proxy, shadow, frag, html } from '../../index'; | ||
onconnect: (ev, el = ev.target as HTMLElement) => | ||
el.textContent = el.textContent!.toUpperCase(), | ||
el.textContent += el.textContent!.toUpperCase(), | ||
ondisconnect: (ev, el = ev.target as HTMLElement) => | ||
@@ -352,2 +352,8 @@ el.textContent += el.textContent!, | ||
]); | ||
assert.deepStrictEqual( | ||
el.children.map(el => el.children), | ||
[ | ||
'aA', | ||
'bB', | ||
]); | ||
el.children = [ | ||
@@ -360,4 +366,4 @@ el.children[1], | ||
[ | ||
'B', | ||
'C', | ||
'bB', | ||
'cC', | ||
]); | ||
@@ -383,2 +389,11 @@ assert.deepStrictEqual( | ||
}); | ||
assert.deepStrictEqual( | ||
Object.entries(el.children).map(([k, v]) => [k, v.children]), | ||
[ | ||
['a', 'aA'], | ||
['b', 'bB'], | ||
['c', 'cC'], | ||
['d', 'dD'], | ||
['e', 'eE'], | ||
]); | ||
el.children = { | ||
@@ -427,3 +442,3 @@ a: el.children.a, | ||
HTML.li(`item`) | ||
] as const), | ||
]), | ||
}); | ||
@@ -454,3 +469,3 @@ public readonly element = this.dom.element; | ||
HTML.li(`item`) | ||
] as const), | ||
]), | ||
}); | ||
@@ -539,3 +554,3 @@ public readonly element = this.dom.element; | ||
i18n.init((err, t) => | ||
proxy<string>(ev.target as HTMLElement).children = err | ||
proxy<string>(ev.target as HTMLElement)!.children = err | ||
? 'Failed to init i18next.' | ||
@@ -542,0 +557,0 @@ : t(children, data)) |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
33
357
654866
15477