refactor(core): CORE-13 type component registries and own control lifetimes

This commit is contained in:
Harvey Zhao committed 2026-09-11 04:54:32 +08:00
1 parent 9eb7c945ca
commit 697817eceb
68 files changed
+10100 -1925

No files matched your search

+29
View File
@@ -0,0 +1,29 @@
import type { ComponentHost, EntryOption } from './types'
import { addClass, append, setStyles, tooltip } from '../utils/dom'
export function renderEntry<Host extends ComponentHost>(element: HTMLDivElement, parent: HTMLElement, kind: string, name: string, id: number, option: EntryOption<Host>): void {
addClass(element, `art-${kind}`)
addClass(element, `art-${kind}-${name}`)
const children = Array.from(parent.children) as HTMLElement[]
element.dataset.index = String(option.index || id)
const next = children.find(child => Number(child.dataset.index) >= Number(element.dataset.index))
if (next)
next.insertAdjacentElement('beforebegin', element)
else
append(parent, element)
if (option.html)
append(element, option.html)
if (option.style)
setStyles(element, option.style)
if (option.tooltip)
tooltip(element, option.tooltip)
}
// Builtin renderers append known element markup or existing icon wrappers.
export function appendElement(parent: Element, child: string | Element): HTMLElement {
return append(parent, child) as HTMLElement
}
export function queryElement(selector: string, parent: ParentNode): HTMLElement {
return parent.querySelector<HTMLElement>(selector)!
}
@@ -0,0 +1,54 @@
import type ResourceScope from '../lifecycle/scope'
import type { ComponentHost } from './types'
import { getScope } from '../lifecycle/instance'
const scopes = new WeakMap<HTMLElement, ResourceScope>()
export function ownEntry(art: ComponentHost, element: HTMLElement): ResourceScope {
const scope = getScope(art).child()
scopes.set(element, scope)
return scope
}
export function entryScope(element: HTMLElement): ResourceScope {
const scope = scopes.get(element)
if (!scope)
throw new Error('ArtPlayer component has not been registered')
return scope
}
export function releaseEntry(element: HTMLElement): void {
scopes.get(element)?.dispose()
}
export function proxyEntry<Name extends keyof HTMLElementEventMap>(art: ComponentHost, element: HTMLElement, target: EventTarget, name: Name, callback: (event: HTMLElementEventMap[Name]) => unknown): () => void {
const scope = entryScope(element)
if (scope.closed)
return () => {}
const cleanup = art.events.proxy(target, name, (event) => {
if (!scope.closed)
return callback(event as HTMLElementEventMap[Name])
})
return scope.add(() => {
art.events.remove(cleanup)
})
}
export interface SubscriptionHost<Events extends { [Name in keyof Events]: readonly unknown[] }> {
on: <Name extends keyof Events>(name: Name, callback: (...args: [...Events[Name]]) => unknown) => unknown
off: <Name extends keyof Events>(name: Name, callback: (...args: [...Events[Name]]) => unknown) => unknown
}
export function subscribeEntry<Events extends { [Name in keyof Events]: readonly unknown[] }, Name extends keyof Events>(art: SubscriptionHost<Events>, element: HTMLElement, name: Name, callback: (...args: [...Events[Name]]) => unknown): void {
const scope = entryScope(element)
if (scope.closed)
return
const guarded = (...args: [...Events[Name]]) => {
if (!scope.closed)
return callback(...args)
}
art.on(name, guarded)
scope.add(() => {
art.off(name, guarded)
})
}
@@ -0,0 +1,15 @@
import type { Cleanup } from '../lifecycle/scope'
const selections = new WeakMap<Event, () => boolean>()
export function trackSelection(event: Event, active: () => boolean): Cleanup {
selections.set(event, active)
return () => {
if (selections.get(event) === active)
selections.delete(event)
}
}
export function captureSelection(event?: Event): () => boolean {
return (event && selections.get(event)) || (() => true)
}
+44
View File
@@ -0,0 +1,44 @@
import type Component from '../utils/component'
export type EventCleanup = () => void
export interface ComponentHost {
template: { $player: HTMLElement }
emit: (name: string, ...args: unknown[]) => unknown
events: {
proxy: (target: EventTarget, name: string, callback: (event: Event) => unknown) => EventCleanup
remove: (cleanup: EventCleanup) => void
}
}
export interface SelectorItem {
html: string | HTMLElement | number
value?: string | number
default?: boolean
readonly $control_option?: SelectorItem[]
readonly $control_item?: HTMLDivElement
readonly $control_value?: HTMLDivElement
}
export interface EntryOption<Host extends ComponentHost> {
html?: string | HTMLElement | number
disable?: boolean
name?: string
index?: number
style?: Partial<CSSStyleDeclaration>
tooltip?: string | number
position?: string
selector?: SelectorItem[]
click?: (this: Host, component: Component<Host>, event: Event) => unknown
mounted?: (this: Host, element: HTMLDivElement) => unknown
beforeUnmount?: (this: Host, element: HTMLDivElement) => unknown
onSelect?: (this: Host, item: SelectorItem, element: HTMLDivElement, event: Event) => unknown
}
export type EntryInput<Host extends ComponentHost> = EntryOption<Host> | ((art: Host) => EntryOption<Host>)
export interface Entry<Host extends ComponentHost> {
$ref: HTMLDivElement
events: EventCleanup[]
option: EntryOption<Host>
}