@xihan-ui/web-components 1.0.0-alpha.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.
@@ -0,0 +1,267 @@
1
+ import { createNormalizer } from "@xihan-ui/kernel";
2
+ import { createService } from "@xihan-ui/machine";
3
+ //#region src/dom/normalize.ts
4
+ const wcNormalize = createNormalizer((props) => props);
5
+ /** 元素自身是角色节点,或其子树里有角色节点。 */
6
+ function containsPart(el) {
7
+ return el.hasAttribute("data-xh-part") || el.querySelector(`[data-xh-part]`) != null;
8
+ }
9
+ function discoverParts(host) {
10
+ const out = /* @__PURE__ */ new Map();
11
+ function walk(node) {
12
+ for (const child of Array.from(node.children)) {
13
+ if (child.tagName.toLowerCase().startsWith("xh-")) continue;
14
+ const el = child;
15
+ const part = el.dataset.xhPart;
16
+ if (part) {
17
+ const arr = out.get(part);
18
+ if (arr) arr.push(el);
19
+ else out.set(part, [el]);
20
+ }
21
+ walk(el);
22
+ }
23
+ }
24
+ walk(host);
25
+ return out;
26
+ }
27
+ //#endregion
28
+ //#region src/dom/spread.ts
29
+ const BOOLEAN_ATTRS = /* @__PURE__ */ new Set([
30
+ "disabled",
31
+ "hidden",
32
+ "inert",
33
+ "readonly",
34
+ "required",
35
+ "checked",
36
+ "selected",
37
+ "open",
38
+ "multiple"
39
+ ]);
40
+ const PROP_KEYS = /* @__PURE__ */ new Set([
41
+ "value",
42
+ "checked",
43
+ "selected"
44
+ ]);
45
+ function eventName(key) {
46
+ if (key.length > 2 && key.startsWith("on") && key[2] >= "A" && key[2] <= "Z") return key.slice(2).toLowerCase();
47
+ return null;
48
+ }
49
+ function setAttr(node, key, value) {
50
+ if (node.getAttribute(key) !== value) node.setAttribute(key, value);
51
+ }
52
+ function removeAttr(node, key) {
53
+ if (node.hasAttribute(key)) node.removeAttribute(key);
54
+ }
55
+ /**
56
+ * 一个角色节点当前归哪台 spreader 管,最后写它的那台就是。
57
+ * 只有仍持有归属的那台交还时才撤属性:两台同类宿主写的属性完全同名,接管后原宿主再撤会把接管方的一起删掉。
58
+ */
59
+ const owners = /* @__PURE__ */ new WeakMap();
60
+ function createSpreader() {
61
+ const owner = Symbol("spreader");
62
+ const state = /* @__PURE__ */ new WeakMap();
63
+ function spread(node, props) {
64
+ owners.set(node, owner);
65
+ let s = state.get(node);
66
+ if (!s) {
67
+ s = {
68
+ listeners: /* @__PURE__ */ new Map(),
69
+ attrs: /* @__PURE__ */ new Set()
70
+ };
71
+ state.set(node, s);
72
+ }
73
+ const nextAttrs = /* @__PURE__ */ new Set();
74
+ const nextEvents = /* @__PURE__ */ new Set();
75
+ for (const [key, value] of Object.entries(props)) {
76
+ const ev = eventName(key);
77
+ if (ev) {
78
+ nextEvents.add(ev);
79
+ const prev = s.listeners.get(ev);
80
+ if (prev) node.removeEventListener(ev, prev);
81
+ if (typeof value === "function") {
82
+ node.addEventListener(ev, value);
83
+ s.listeners.set(ev, value);
84
+ } else s.listeners.delete(ev);
85
+ continue;
86
+ }
87
+ if (key === "style" && value !== null && typeof value === "object") {
88
+ Object.assign(node.style, value);
89
+ continue;
90
+ }
91
+ if (value === void 0 || value === null || value === false) {
92
+ removeAttr(node, key);
93
+ continue;
94
+ }
95
+ nextAttrs.add(key);
96
+ if (PROP_KEYS.has(key)) {
97
+ node[key] = value;
98
+ continue;
99
+ }
100
+ if (BOOLEAN_ATTRS.has(key)) {
101
+ node.toggleAttribute(key, Boolean(value));
102
+ continue;
103
+ }
104
+ setAttr(node, key, String(value));
105
+ }
106
+ for (const key of s.attrs) if (!nextAttrs.has(key)) removeAttr(node, key);
107
+ for (const [ev, fn] of [...s.listeners]) if (!nextEvents.has(ev)) {
108
+ node.removeEventListener(ev, fn);
109
+ s.listeners.delete(ev);
110
+ }
111
+ s.attrs = nextAttrs;
112
+ }
113
+ function release(node) {
114
+ const s = state.get(node);
115
+ if (!s) return;
116
+ for (const [ev, fn] of s.listeners) node.removeEventListener(ev, fn);
117
+ if (owners.get(node) === owner) {
118
+ for (const key of s.attrs) node.removeAttribute(key);
119
+ owners.delete(node);
120
+ }
121
+ state.delete(node);
122
+ }
123
+ return {
124
+ spread,
125
+ release
126
+ };
127
+ }
128
+ //#endregion
129
+ //#region src/runtime/lit-runtime.ts
130
+ /** flush 最多等这么多轮更新;宿主一直自排新一轮时到此为止,回调照跑不吊死。 */
131
+ const MAX_FLUSH_ROUNDS = 100;
132
+ function createLitRuntime(host) {
133
+ const mounts = [];
134
+ const cleanups = [];
135
+ const trackers = [];
136
+ let mounted = false;
137
+ function cell(params) {
138
+ const p0 = params();
139
+ const eq = p0.isEqual ?? Object.is;
140
+ const initial = p0.defaultValue ?? p0.value;
141
+ let inner = initial;
142
+ let lastSeen = initial;
143
+ let version = 0;
144
+ const isControlled = () => params().value !== void 0;
145
+ const read = () => isControlled() ? params().value : inner;
146
+ return {
147
+ initial,
148
+ get: read,
149
+ set(next) {
150
+ const prev = read();
151
+ const value = typeof next === "function" ? next(prev) : next;
152
+ if (eq(value, prev)) return;
153
+ if (!isControlled()) inner = value;
154
+ params().onChange?.(value, prev);
155
+ host.requestUpdate();
156
+ },
157
+ notify: () => host.requestUpdate(),
158
+ version: () => {
159
+ const cur = read();
160
+ if (!eq(cur, lastSeen)) {
161
+ lastSeen = cur;
162
+ version += 1;
163
+ }
164
+ return version;
165
+ }
166
+ };
167
+ }
168
+ return {
169
+ name: "wc",
170
+ isServer: false,
171
+ cell,
172
+ track(deps, fn) {
173
+ trackers.push({
174
+ deps,
175
+ fn,
176
+ last: deps.map((d) => d())
177
+ });
178
+ },
179
+ flush(fn) {
180
+ (async () => {
181
+ await null;
182
+ let rounds = 0;
183
+ while (!await host.updateComplete && rounds < MAX_FLUSH_ROUNDS) rounds += 1;
184
+ fn();
185
+ })();
186
+ },
187
+ onMount(fn) {
188
+ mounts.push(fn);
189
+ if (mounted) fn();
190
+ },
191
+ onCleanup(fn) {
192
+ cleanups.push(fn);
193
+ },
194
+ mount() {
195
+ if (mounted) return;
196
+ mounted = true;
197
+ for (const fn of mounts) fn();
198
+ },
199
+ unmount() {
200
+ if (!mounted) return;
201
+ mounted = false;
202
+ for (let i = cleanups.length - 1; i >= 0; i--) cleanups[i]();
203
+ cleanups.length = 0;
204
+ },
205
+ runTrackers() {
206
+ let errors;
207
+ for (const t of trackers) {
208
+ const next = t.deps.map((d) => d());
209
+ if (!next.some((v, i) => !Object.is(v, t.last[i]))) continue;
210
+ const prev = t.last;
211
+ t.last = next;
212
+ try {
213
+ t.fn();
214
+ } catch (error) {
215
+ t.last = prev;
216
+ errors ??= [];
217
+ errors.push(error);
218
+ }
219
+ }
220
+ if (errors !== void 0) throw errors.length === 1 ? errors[0] : new AggregateError(errors, "tracker 抛错");
221
+ }
222
+ };
223
+ }
224
+ //#endregion
225
+ //#region src/runtime/machine-controller.ts
226
+ var MachineController = class {
227
+ host;
228
+ machine;
229
+ props;
230
+ opts;
231
+ service;
232
+ runtime;
233
+ started = false;
234
+ constructor(host, machine, props, opts = {}) {
235
+ this.host = host;
236
+ this.machine = machine;
237
+ this.props = props;
238
+ this.opts = opts;
239
+ host.addController(this);
240
+ }
241
+ build() {
242
+ this.runtime = createLitRuntime(this.host);
243
+ this.service = createService(this.machine, {
244
+ props: this.props,
245
+ runtime: this.runtime,
246
+ scope: this.opts.scope
247
+ });
248
+ this.opts.onBuilt?.(this.service);
249
+ }
250
+ hostConnected() {
251
+ if (!this.started || this.service.getStatus() === "Stopped") {
252
+ this.build();
253
+ this.started = true;
254
+ }
255
+ this.runtime.mount();
256
+ }
257
+ hostUpdate() {
258
+ this.runtime?.runTrackers();
259
+ }
260
+ hostDisconnected() {
261
+ this.runtime?.unmount();
262
+ }
263
+ };
264
+ //#endregion
265
+ export { discoverParts as a, containsPart as i, createLitRuntime as n, wcNormalize as o, createSpreader as r, MachineController as t };
266
+
267
+ //# sourceMappingURL=machine-controller-Bnr5jbhz.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"machine-controller-Bnr5jbhz.js","names":[],"sources":["../src/dom/normalize.ts","../src/dom/parts.ts","../src/dom/spread.ts","../src/runtime/lit-runtime.ts","../src/runtime/machine-controller.ts"],"sourcesContent":["import { createNormalizer } from '@xihan-ui/kernel'\n\n// connect 产出的键已是 DOM 向,恒等透传,落到真实 DOM 由 spread 完成。\nexport const wcNormalize = createNormalizer(props => props)\n","/** 角色节点标记属性。 */\nexport const PART_ATTR = 'data-xh-part'\n\n/** 元素自身是角色节点,或其子树里有角色节点。 */\nexport function containsPart(el: Element): boolean {\n return el.hasAttribute(PART_ATTR) || el.querySelector(`[${PART_ATTR}]`) != null\n}\n\n// 从宿主向下遍历收集带 data-xh-part 的节点,跳过嵌套 xh-* 子树。\nexport function discoverParts(host: HTMLElement): Map<string, HTMLElement[]> {\n const out = new Map<string, HTMLElement[]>()\n\n function walk(node: Element): void {\n for (const child of Array.from(node.children)) {\n if (child.tagName.toLowerCase().startsWith('xh-'))\n continue\n const el = child as HTMLElement\n const part = el.dataset.xhPart\n if (part) {\n const arr = out.get(part)\n if (arr)\n arr.push(el)\n else\n out.set(part, [el])\n }\n walk(el)\n }\n }\n\n walk(host)\n return out\n}\n","// 把 connect 产出的 prop 字典打到 Light-DOM 角色节点上;事件每帧移旧加新,不碰 class。\n\nconst BOOLEAN_ATTRS = new Set(['disabled', 'hidden', 'inert', 'readonly', 'required', 'checked', 'selected', 'open', 'multiple'])\nconst PROP_KEYS = new Set(['value', 'checked', 'selected'])\n\ninterface NodeState {\n listeners: Map<string, EventListener>\n attrs: Set<string>\n}\n\nfunction eventName(key: string): string | null {\n if (key.length > 2 && key.startsWith('on') && key[2]! >= 'A' && key[2]! <= 'Z')\n return key.slice(2).toLowerCase()\n return null\n}\n\n// 值相同则不写,避免多余的属性变更记录。\nfunction setAttr(node: HTMLElement, key: string, value: string): void {\n if (node.getAttribute(key) !== value)\n node.setAttribute(key, value)\n}\n\nfunction removeAttr(node: HTMLElement, key: string): void {\n if (node.hasAttribute(key))\n node.removeAttribute(key)\n}\n\nexport interface Spreader {\n spread: (node: HTMLElement, props: Record<string, unknown>) => void\n release: (node: HTMLElement) => void\n}\n\n/**\n * 一个角色节点当前归哪台 spreader 管,最后写它的那台就是。\n * 只有仍持有归属的那台交还时才撤属性:两台同类宿主写的属性完全同名,接管后原宿主再撤会把接管方的一起删掉。\n */\nconst owners = new WeakMap<Element, symbol>()\n\nexport function createSpreader(): Spreader {\n const owner = Symbol('spreader')\n const state = new WeakMap<Element, NodeState>()\n\n function spread(node: HTMLElement, props: Record<string, unknown>): void {\n owners.set(node, owner)\n let s = state.get(node)\n if (!s) {\n s = { listeners: new Map(), attrs: new Set() }\n state.set(node, s)\n }\n const nextAttrs = new Set<string>()\n const nextEvents = new Set<string>()\n\n for (const [key, value] of Object.entries(props)) {\n const ev = eventName(key)\n if (ev) {\n nextEvents.add(ev)\n const prev = s.listeners.get(ev)\n if (prev)\n node.removeEventListener(ev, prev)\n if (typeof value === 'function') {\n node.addEventListener(ev, value as EventListener)\n s.listeners.set(ev, value as EventListener)\n }\n else {\n s.listeners.delete(ev)\n }\n continue\n }\n // style 传对象时逐条写内联样式。\n if (key === 'style' && value !== null && typeof value === 'object') {\n Object.assign(node.style, value as Record<string, string>)\n continue\n }\n if (value === undefined || value === null || value === false) {\n removeAttr(node, key)\n continue\n }\n nextAttrs.add(key)\n if (PROP_KEYS.has(key)) {\n (node as unknown as Record<string, unknown>)[key] = value\n continue\n }\n if (BOOLEAN_ATTRS.has(key)) {\n node.toggleAttribute(key, Boolean(value))\n continue\n }\n setAttr(node, key, String(value))\n }\n\n // 移除上一帧写过、这一帧不再写的属性与事件监听器。\n for (const key of s.attrs) {\n if (!nextAttrs.has(key))\n removeAttr(node, key)\n }\n for (const [ev, fn] of [...s.listeners]) {\n if (!nextEvents.has(ev)) {\n node.removeEventListener(ev, fn)\n s.listeners.delete(ev)\n }\n }\n s.attrs = nextAttrs\n }\n\n function release(node: HTMLElement): void {\n const s = state.get(node)\n if (!s)\n return\n for (const [ev, fn] of s.listeners) node.removeEventListener(ev, fn)\n // 节点已被另一台宿主接管时只摘监听器,属性归接管方\n if (owners.get(node) === owner) {\n for (const key of s.attrs) node.removeAttribute(key)\n owners.delete(node)\n }\n state.delete(node)\n }\n\n return { spread, release }\n}\n","import type { Bindable, CellParams, Dep, ReactiveRuntime } from '@xihan-ui/machine'\nimport type { ReactiveControllerHost } from '../reactive'\n\n// 把机器的 ReactiveRuntime 契约桥到自定义元素基类的 controller 生命周期。\n\nexport interface LitRuntime extends ReactiveRuntime {\n /** hostConnected 调用,跑排队的 onMount。 */\n mount: () => void\n /** hostDisconnected 调用,逆序跑一次 cleanup。 */\n unmount: () => void\n /** hostUpdate 调用,逐 tracker 比对依赖、变则触发。 */\n runTrackers: () => void\n}\n\ninterface Tracker {\n deps: Dep[]\n fn: () => void\n last: unknown[]\n}\n\n/** flush 最多等这么多轮更新;宿主一直自排新一轮时到此为止,回调照跑不吊死。 */\nconst MAX_FLUSH_ROUNDS = 100\n\nexport function createLitRuntime(host: ReactiveControllerHost): LitRuntime {\n const mounts: Array<() => void> = []\n const cleanups: Array<() => void> = []\n const trackers: Tracker[] = []\n let mounted = false\n\n function cell<V>(params: () => CellParams<V>): Bindable<V> {\n const p0 = params()\n const eq = p0.isEqual ?? Object.is\n const initial = (p0.defaultValue ?? p0.value) as V\n let inner = initial\n let lastSeen = initial\n let version = 0\n const isControlled = (): boolean => params().value !== undefined\n const read = (): V => (isControlled() ? (params().value as V) : inner)\n return {\n initial,\n get: read,\n set(next) {\n const prev = read()\n const value = typeof next === 'function' ? (next as (p: V) => V)(prev) : next\n if (eq(value, prev))\n return\n if (!isControlled())\n inner = value\n params().onChange?.(value, prev)\n host.requestUpdate()\n },\n notify: () => host.requestUpdate(),\n // 版本号按值拉取比对,受控值不经过 set。\n version: () => {\n const cur = read()\n if (!eq(cur, lastSeen)) {\n lastSeen = cur\n version += 1\n }\n return version\n },\n }\n }\n\n return {\n name: 'wc',\n isServer: false,\n cell,\n track(deps, fn) {\n trackers.push({ deps, fn, last: deps.map(d => d()) })\n },\n flush(fn) {\n void (async () => {\n // 先让出一次微任务,等这次转移把宿主的更新排上队,再去读 updateComplete\n await null\n // updateComplete resolve 成 false 表示更新途中又排了新一轮,DOM 还没落定,接着等下一轮。\n let rounds = 0\n while (!(await host.updateComplete) && rounds < MAX_FLUSH_ROUNDS)\n rounds += 1\n fn()\n })()\n },\n onMount(fn) {\n mounts.push(fn)\n if (mounted)\n fn()\n },\n onCleanup(fn) {\n cleanups.push(fn)\n },\n mount() {\n if (mounted)\n return\n mounted = true\n for (const fn of mounts) fn()\n },\n unmount() {\n if (!mounted)\n return\n mounted = false\n for (let i = cleanups.length - 1; i >= 0; i--) cleanups[i]!()\n cleanups.length = 0\n },\n runTrackers() {\n let errors: unknown[] | undefined\n for (const t of trackers) {\n const next = t.deps.map(d => d())\n if (!next.some((v, i) => !Object.is(v, t.last[i])))\n continue\n const prev = t.last\n // 跑之前先推进:fn 内部若又进到 runTrackers,不会拿同一次变化再触发一遍。\n t.last = next\n try {\n t.fn()\n }\n catch (error) {\n // 回滚到未消费,这次变化留给下一轮补跑;错误攒着,先把余下 tracker 跑完。\n t.last = prev\n errors ??= []\n errors.push(error)\n }\n }\n if (errors !== undefined)\n throw errors.length === 1 ? errors[0] : new AggregateError(errors, 'tracker 抛错')\n },\n }\n}\n","import type { Scope } from '@xihan-ui/kernel'\nimport type { MachineConfig, MachineSchema, Service } from '@xihan-ui/machine'\nimport type { ReactiveController, ReactiveControllerHost } from '../reactive'\nimport type { LitRuntime } from './lit-runtime'\nimport { createService } from '@xihan-ui/machine'\nimport { createLitRuntime } from './lit-runtime'\n\nexport interface MachineControllerOptions<T extends MachineSchema> {\n scope?: Scope\n /** 每次建机器后回调,用于注入 refs。 */\n onBuilt?: (service: Service<T>) => void\n}\n\n// 一台机器一个 controller:hostConnected 建机器并 mount、hostUpdate 跑 trackers、hostDisconnected unmount。\nexport class MachineController<T extends MachineSchema> implements ReactiveController {\n service!: Service<T>\n private runtime: LitRuntime | undefined\n private started = false\n\n constructor(\n private readonly host: ReactiveControllerHost,\n private readonly machine: MachineConfig<T>,\n private readonly props: () => Partial<T['props']>,\n private readonly opts: MachineControllerOptions<T> = {},\n ) {\n host.addController(this)\n // 延到 hostConnected 再 build,构造期 attribute 尚未反射到 reactive property。\n }\n\n private build(): void {\n this.runtime = createLitRuntime(this.host)\n this.service = createService(this.machine, { props: this.props, runtime: this.runtime, scope: this.opts.scope })\n this.opts.onBuilt?.(this.service)\n }\n\n hostConnected(): void {\n // 首次或旧机器已停止时重建,从 initialState 起。\n if (!this.started || this.service.getStatus() === 'Stopped') {\n this.build()\n this.started = true\n }\n this.runtime!.mount()\n }\n\n hostUpdate(): void {\n this.runtime?.runTrackers()\n }\n\n hostDisconnected(): void {\n this.runtime?.unmount()\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1,25 @@
1
+ //#region src/runtime/registry.ts
2
+ const REGISTRY_KEY = "__XIHAN_UI_WC__";
3
+ function registry() {
4
+ const g = globalThis;
5
+ return g[REGISTRY_KEY] ??= /* @__PURE__ */ new Map();
6
+ }
7
+ function defineElement(tag, ctor, version) {
8
+ if (typeof customElements === "undefined") return;
9
+ const reg = registry();
10
+ const prev = reg.get(tag);
11
+ if (prev) {
12
+ if (prev.version === version) return;
13
+ throw new Error(`[xihan-ui] ${tag} 已注册版本 ${prev.version},拒绝以 ${version} 重复注册`);
14
+ }
15
+ if (customElements.get(tag)) throw new Error(`[xihan-ui] 标签 ${tag} 已被非 XiHan.UI 代码占用`);
16
+ reg.set(tag, {
17
+ version,
18
+ ctor
19
+ });
20
+ customElements.define(tag, ctor);
21
+ }
22
+ //#endregion
23
+ export { defineElement as t };
24
+
25
+ //# sourceMappingURL=registry-DR6I2_zF.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry-DR6I2_zF.js","names":[],"sources":["../src/runtime/registry.ts"],"sourcesContent":["// 自定义元素注册表:幂等注册,重复标签或版本冲突时抛错,无 DOM 环境跳过。\nconst REGISTRY_KEY = '__XIHAN_UI_WC__'\n\ninterface Entry {\n version: string\n ctor: CustomElementConstructor\n}\n\nfunction registry(): Map<string, Entry> {\n const g = globalThis as Record<string, unknown>\n return (g[REGISTRY_KEY] ??= new Map<string, Entry>()) as Map<string, Entry>\n}\n\nexport function defineElement(tag: string, ctor: CustomElementConstructor, version: string): void {\n // 无 customElements 的环境静默跳过。\n if (typeof customElements === 'undefined')\n return\n const reg = registry()\n const prev = reg.get(tag)\n if (prev) {\n if (prev.version === version)\n return\n throw new Error(`[xihan-ui] ${tag} 已注册版本 ${prev.version},拒绝以 ${version} 重复注册`)\n }\n if (customElements.get(tag))\n throw new Error(`[xihan-ui] 标签 ${tag} 已被非 XiHan.UI 代码占用`)\n reg.set(tag, { version, ctor })\n customElements.define(tag, ctor)\n}\n"],"mappings":";AACA,MAAM,eAAe;AAOrB,SAAS,WAA+B;CACtC,MAAM,IAAI;CACV,OAAQ,EAAE,kCAAkB,IAAI,IAAmB;AACrD;AAEA,SAAgB,cAAc,KAAa,MAAgC,SAAuB;CAEhG,IAAI,OAAO,mBAAmB,aAC5B;CACF,MAAM,MAAM,SAAS;CACrB,MAAM,OAAO,IAAI,IAAI,GAAG;CACxB,IAAI,MAAM;EACR,IAAI,KAAK,YAAY,SACnB;EACF,MAAM,IAAI,MAAM,cAAc,IAAI,SAAS,KAAK,QAAQ,OAAO,QAAQ,MAAM;CAC/E;CACA,IAAI,eAAe,IAAI,GAAG,GACxB,MAAM,IAAI,MAAM,iBAAiB,IAAI,mBAAmB;CAC1D,IAAI,IAAI,KAAK;EAAE;EAAS;CAAK,CAAC;CAC9B,eAAe,OAAO,KAAK,IAAI;AACjC"}
@@ -0,0 +1,9 @@
1
+ //#region src/dom/spread.d.ts
2
+ interface Spreader {
3
+ spread: (node: HTMLElement, props: Record<string, unknown>) => void;
4
+ release: (node: HTMLElement) => void;
5
+ }
6
+ declare function createSpreader(): Spreader;
7
+ //#endregion
8
+ export { createSpreader as n, Spreader as t };
9
+ //# sourceMappingURL=spread-Ch8ZXZDC.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spread-Ch8ZXZDC.d.ts","names":[],"sources":["../src/dom/spread.ts"],"mappings":";UA2BiB;EACf,SAAS,MAAM,aAAa,OAAO;EACnC,UAAU,MAAM;;iBASF,kBAAkB"}
@@ -0,0 +1,46 @@
1
+ //#region src/reactive/types.d.ts
2
+ /** 属性字符串 → 字段值。本运行时不做属性回写,故没有 toAttribute。 */
3
+ interface ComplexAttributeConverter<Type = unknown> {
4
+ fromAttribute?: (value: string | null, type?: unknown) => Type;
5
+ }
6
+ /** 转换器可写成对象,也可直接写成 fromAttribute 函数。 */
7
+ type AttributeConverter<Type = unknown> = ComplexAttributeConverter<Type> | ((value: string | null, type?: unknown) => Type);
8
+ interface PropertyDeclaration<Type = unknown> {
9
+ /** false=不观察属性;字符串=指定属性名;缺省=字段名转小写(不转短横线)。 */
10
+ readonly attribute?: boolean | string;
11
+ /** 没配 converter 时据此选内建转换:Boolean / Number / Object / Array,其余原样取字符串。 */
12
+ readonly type?: unknown;
13
+ /** 自定义转换器,配了就盖过 type。 */
14
+ readonly converter?: AttributeConverter<Type>;
15
+ /** 未实现:判等固定 Object.is。写了编译期报错,运行期也抛。 */
16
+ readonly hasChanged?: never;
17
+ /** 未实现:本运行时不回写属性。写了编译期报错,运行期也抛。 */
18
+ readonly reflect?: never;
19
+ /** 未实现:每条声明必装访问器。写了编译期报错,运行期也抛。 */
20
+ readonly noAccessor?: never;
21
+ /** 未实现:没有内部状态这一档声明。写了编译期报错,运行期也抛。 */
22
+ readonly state?: never;
23
+ }
24
+ /** `static properties` 的形状:字段名 → 声明。只支持字符串键,symbol 键在定稿时抛。 */
25
+ interface PropertyDeclarations {
26
+ readonly [name: string]: PropertyDeclaration;
27
+ }
28
+ /** 一轮更新里变过的字段 → 变更前的值。 */
29
+ type PropertyValues = Map<PropertyKey, unknown>;
30
+ /** 挂在元素上的一段外部逻辑,跟着宿主的连接与更新走。 */
31
+ interface ReactiveController {
32
+ hostConnected?: () => void;
33
+ hostUpdate?: () => void;
34
+ hostUpdated?: () => void;
35
+ hostDisconnected?: () => void;
36
+ }
37
+ /** 控制器眼中的宿主。 */
38
+ interface ReactiveControllerHost {
39
+ addController: (controller: ReactiveController) => void;
40
+ removeController: (controller: ReactiveController) => void;
41
+ requestUpdate: () => void;
42
+ readonly updateComplete: Promise<boolean>;
43
+ }
44
+ //#endregion
45
+ export { ReactiveControllerHost as a, ReactiveController as i, PropertyDeclarations as n, PropertyValues as r, PropertyDeclaration as t };
46
+ //# sourceMappingURL=types-BvWWqMd_.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types-BvWWqMd_.d.ts","names":[],"sources":["../src/reactive/types.ts"],"mappings":";;UAGiB,0BAA0B;EACzC,iBAAiB,sBAAsB,mBAAmB;;;KAIhD,mBAAmB,kBACzB,0BAA0B,UACxB,sBAAsB,mBAAmB;UAEhC,oBAAoB;;WAE1B;;WAEA;;WAEA,YAAY,mBAAmB;;WAE/B;;WAEA;;WAEA;;WAEA;;;UAIM;YACL,eAAe;;;KAIf,iBAAiB,IAAI;;UAGhB;EACf;EACA;EACA;EACA;;;UAIe;EACf,gBAAgB,YAAY;EAC5B,mBAAmB,YAAY;EAC/B;WACS,gBAAgB"}
package/package.json ADDED
@@ -0,0 +1,96 @@
1
+ {
2
+ "name": "@xihan-ui/web-components",
3
+ "type": "module",
4
+ "version": "1.0.0-alpha.0",
5
+ "description": "XiHan.UI Web Components adapter — Light-DOM behavior hosts on XhReactiveElement.",
6
+ "author": {
7
+ "name": "XiHanFun",
8
+ "url": "https://github.com/XiHanFun"
9
+ },
10
+ "license": "MIT",
11
+ "homepage": "https://ui.docs.xihanfun.com",
12
+ "repository": {
13
+ "type": "git",
14
+ "url": "git+https://github.com/XiHanFun/XiHan.UI.git",
15
+ "directory": "ui/packages/adapters/web-components"
16
+ },
17
+ "bugs": {
18
+ "url": "https://github.com/XiHanFun/XiHan.UI/issues"
19
+ },
20
+ "keywords": [
21
+ "web-components",
22
+ "custom-elements",
23
+ "headless-ui",
24
+ "design-system"
25
+ ],
26
+ "sideEffects": false,
27
+ "exports": {
28
+ ".": {
29
+ "types": "./dist/index.d.ts",
30
+ "import": "./dist/index.js"
31
+ },
32
+ "./define": {
33
+ "types": "./dist/define.d.ts",
34
+ "import": "./dist/define.js"
35
+ },
36
+ "./backgrounds": {
37
+ "types": "./dist/backgrounds.d.ts",
38
+ "import": "./dist/backgrounds.js"
39
+ },
40
+ "./custom-elements.json": "./custom-elements.json"
41
+ },
42
+ "main": "./dist/index.js",
43
+ "module": "./dist/index.js",
44
+ "types": "./dist/index.d.ts",
45
+ "files": [
46
+ "CHANGELOG.md",
47
+ "custom-elements.json",
48
+ "dist"
49
+ ],
50
+ "engines": {
51
+ "node": ">=18"
52
+ },
53
+ "publishConfig": {
54
+ "access": "public",
55
+ "registry": "https://registry.npmjs.org/"
56
+ },
57
+ "customElements": "custom-elements.json",
58
+ "peerDependencies": {
59
+ "@xihan-ui/backgrounds": "^1.0.0-alpha.0"
60
+ },
61
+ "peerDependenciesMeta": {
62
+ "@xihan-ui/backgrounds": {
63
+ "optional": true
64
+ }
65
+ },
66
+ "dependencies": {
67
+ "@xihan-ui/behavior": "^1.0.0-alpha.0",
68
+ "@xihan-ui/code-highlight": "^1.0.0-alpha.0",
69
+ "@xihan-ui/headless": "^1.0.0-alpha.0",
70
+ "@xihan-ui/kernel": "^1.0.0-alpha.0",
71
+ "@xihan-ui/machine": "^1.0.0-alpha.0",
72
+ "@xihan-ui/position": "^1.0.0-alpha.0"
73
+ },
74
+ "devDependencies": {
75
+ "@custom-elements-manifest/analyzer": "^0.11.0",
76
+ "@lit/reactive-element": "2.1.2",
77
+ "@vitest/browser": "^4.1.10",
78
+ "@vitest/browser-playwright": "^4.1.10",
79
+ "jsdom": "^27.0.0",
80
+ "playwright": "^1.62.0",
81
+ "vitest": "^4.1.10",
82
+ "@xihan-ui/backgrounds": "1.0.0-alpha.0",
83
+ "@xihan-ui/build": "0.0.0",
84
+ "@xihan-ui/styles": "1.0.0-alpha.0",
85
+ "@xihan-ui/testing": "0.0.0",
86
+ "@xihan-ui/tokens": "1.0.0-alpha.0",
87
+ "@xihan-ui/tsconfig": "0.0.0"
88
+ },
89
+ "scripts": {
90
+ "build": "tsdown",
91
+ "cem": "cem analyze",
92
+ "typecheck": "tsc --noEmit -p tsconfig.json",
93
+ "test": "vitest run",
94
+ "test:browser": "vitest run --config vitest.browser.config.ts"
95
+ }
96
+ }