base-paint 0.1.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,26 @@
1
+ /** base-paint/contract:渲染契约(版本化输入 Envelope→输出 HTML)。
2
+ *
3
+ * 缺失阻断不返空:data 缺席/版本不对即抛 RenderError,绝不输出静默空页。
4
+ * link-core 只做 typeof 级消费(import type),运行时零依赖红线不断。
5
+ */
6
+ import type { Envelope } from 'base-link-core';
7
+ import type { PageDescriptor } from './ui.js';
8
+ export declare const RENDER_CONTRACT_VERSION: "0.1.0";
9
+ /** 期望的 envelope 版本(与 link-core ENVELOPE_VERSION 同值,漂移由单测钉死)。 */
10
+ export declare const RENDER_ENVELOPE_VERSION: "0.1.0";
11
+ export interface RenderOutput {
12
+ readonly contractVersion: typeof RENDER_CONTRACT_VERSION;
13
+ readonly slotId: string;
14
+ readonly html: string;
15
+ }
16
+ export type RenderErrorCode = 'missing-data' | 'bad-envelope' | 'reco-only';
17
+ export declare class RenderError extends Error {
18
+ readonly code: RenderErrorCode;
19
+ constructor(code: RenderErrorCode, message: string);
20
+ }
21
+ export declare function escapeHtml(s: string): string;
22
+ /** 实页渲染:reco 描述子误入即抛;data 缺席即抛(不返空)。 */
23
+ export declare function renderPage(page: PageDescriptor, env: Envelope): RenderOutput;
24
+ /** 推荐安装占位渲染:显式 data-missing 标记,非静默空。 */
25
+ export declare function renderReco(page: PageDescriptor): RenderOutput;
26
+ //# sourceMappingURL=contract.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"contract.d.ts","sourceRoot":"","sources":["../src/contract.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAC/C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAG9C,eAAO,MAAM,uBAAuB,EAAG,OAAgB,CAAC;AACxD,gEAAgE;AAChE,eAAO,MAAM,uBAAuB,EAAG,OAAgB,CAAC;AAExD,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,eAAe,EAAE,OAAO,uBAAuB,CAAC;IACzD,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,MAAM,eAAe,GAAG,cAAc,GAAG,cAAc,GAAG,WAAW,CAAC;AAE5E,qBAAa,WAAY,SAAQ,KAAK;IACpC,QAAQ,CAAC,IAAI,EAAE,eAAe,CAAC;gBACnB,IAAI,EAAE,eAAe,EAAE,OAAO,EAAE,MAAM;CAKnD;AAED,wBAAgB,UAAU,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAE5C;AAMD,wCAAwC;AACxC,wBAAgB,UAAU,CAAC,IAAI,EAAE,cAAc,EAAE,GAAG,EAAE,QAAQ,GAAG,YAAY,CAO5E;AAED,wCAAwC;AACxC,wBAAgB,UAAU,CAAC,IAAI,EAAE,cAAc,GAAG,YAAY,CAG7D"}
@@ -0,0 +1,37 @@
1
+ import { cx } from './style.js';
2
+ export const RENDER_CONTRACT_VERSION = '0.1.0';
3
+ /** 期望的 envelope 版本(与 link-core ENVELOPE_VERSION 同值,漂移由单测钉死)。 */
4
+ export const RENDER_ENVELOPE_VERSION = '0.1.0';
5
+ export class RenderError extends Error {
6
+ code;
7
+ constructor(code, message) {
8
+ super(message);
9
+ this.name = 'RenderError';
10
+ this.code = code;
11
+ }
12
+ }
13
+ export function escapeHtml(s) {
14
+ return s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
15
+ }
16
+ function head(page) {
17
+ return '<section class="' + cx('page') + '" data-slot="' + escapeHtml(page.slotId) + '" data-skill="' + escapeHtml(page.skill) + '"><h1>' + escapeHtml(page.title) + '</h1>';
18
+ }
19
+ /** 实页渲染:reco 描述子误入即抛;data 缺席即抛(不返空)。 */
20
+ export function renderPage(page, env) {
21
+ if (page.kind === 'reco')
22
+ throw new RenderError('reco-only', 'reco 描述子请走 renderReco:' + page.slotId);
23
+ if (!env || typeof env !== 'object')
24
+ throw new RenderError('bad-envelope', 'envelope 非对象');
25
+ if (env.version !== RENDER_ENVELOPE_VERSION)
26
+ throw new RenderError('bad-envelope', 'envelope 版本不符:' + String(env.version));
27
+ if (env.data === null || env.data === undefined)
28
+ throw new RenderError('missing-data', '缺失阻断取数,不返空:' + page.slotId);
29
+ const body = typeof env.data === 'string' ? escapeHtml(env.data) : escapeHtml(JSON.stringify(env.data));
30
+ return { contractVersion: RENDER_CONTRACT_VERSION, slotId: page.slotId, html: head(page) + '<div>' + body + '</div></section>' };
31
+ }
32
+ /** 推荐安装占位渲染:显式 data-missing 标记,非静默空。 */
33
+ export function renderReco(page) {
34
+ const html = head(page) + '<div data-missing="1">未安装[' + escapeHtml(page.skill) + '],请补装后使用。</div></section>';
35
+ return { contractVersion: RENDER_CONTRACT_VERSION, slotId: page.slotId, html };
36
+ }
37
+ //# sourceMappingURL=contract.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"contract.js","sourceRoot":"","sources":["../src/contract.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,EAAE,EAAE,MAAM,YAAY,CAAC;AAEhC,MAAM,CAAC,MAAM,uBAAuB,GAAG,OAAgB,CAAC;AACxD,gEAAgE;AAChE,MAAM,CAAC,MAAM,uBAAuB,GAAG,OAAgB,CAAC;AAUxD,MAAM,OAAO,WAAY,SAAQ,KAAK;IAC3B,IAAI,CAAkB;IAC/B,YAAY,IAAqB,EAAE,OAAe;QAChD,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,aAAa,CAAC;QAC1B,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;IACnB,CAAC;CACF;AAED,MAAM,UAAU,UAAU,CAAC,CAAS;IAClC,OAAO,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;AACtG,CAAC;AAED,SAAS,IAAI,CAAC,IAAoB;IAChC,OAAO,kBAAkB,GAAG,EAAE,CAAC,MAAM,CAAC,GAAG,eAAe,GAAG,UAAU,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,gBAAgB,GAAG,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,QAAQ,GAAG,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,OAAO,CAAC;AAC/K,CAAC;AAED,wCAAwC;AACxC,MAAM,UAAU,UAAU,CAAC,IAAoB,EAAE,GAAa;IAC5D,IAAI,IAAI,CAAC,IAAI,KAAK,MAAM;QAAE,MAAM,IAAI,WAAW,CAAC,WAAW,EAAE,wBAAwB,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC;IACrG,IAAI,CAAC,GAAG,IAAI,OAAO,GAAG,KAAK,QAAQ;QAAE,MAAM,IAAI,WAAW,CAAC,cAAc,EAAE,cAAc,CAAC,CAAC;IAC3F,IAAI,GAAG,CAAC,OAAO,KAAK,uBAAuB;QAAE,MAAM,IAAI,WAAW,CAAC,cAAc,EAAE,gBAAgB,GAAG,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC;IAC3H,IAAI,GAAG,CAAC,IAAI,KAAK,IAAI,IAAI,GAAG,CAAC,IAAI,KAAK,SAAS;QAAE,MAAM,IAAI,WAAW,CAAC,cAAc,EAAE,aAAa,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC;IACpH,MAAM,IAAI,GAAG,OAAO,GAAG,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC;IACxG,OAAO,EAAE,eAAe,EAAE,uBAAuB,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,CAAC,GAAG,OAAO,GAAG,IAAI,GAAG,kBAAkB,EAAE,CAAC;AACnI,CAAC;AAED,wCAAwC;AACxC,MAAM,UAAU,UAAU,CAAC,IAAoB;IAC7C,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,4BAA4B,GAAG,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,2BAA2B,CAAC;IAC9G,OAAO,EAAE,eAAe,EAAE,uBAAuB,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC;AACjF,CAAC"}
@@ -0,0 +1,10 @@
1
+ /** base-paint 入口:ui+style+injector+渲染契约统一出口。装配唯一 owner。 */
2
+ export { createPageRegistry, pageOrReco, recoDescriptor } from './ui.js';
3
+ export type { PageDescriptor, PageKind, PageRegistry } from './ui.js';
4
+ export { STYLE_PREFIX, STYLE_TOKENS, STYLE_VERSION, cx, token } from './style.js';
5
+ export type { StyleTokenName } from './style.js';
6
+ export { RenderError, RENDER_CONTRACT_VERSION, RENDER_ENVELOPE_VERSION, escapeHtml, renderPage, renderReco } from './contract.js';
7
+ export type { RenderErrorCode, RenderOutput } from './contract.js';
8
+ export { INJECTOR_DEFAULT_MAX_RETRIES, INJECTOR_DEFAULT_RETRY_MS, mountInjector, openPage } from './injector.js';
9
+ export type { MountHandle, MountOptions, SlotsPort, TabEntry, TabScope, TabSeed } from './injector.js';
10
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,2DAA2D;AAC3D,OAAO,EAAE,kBAAkB,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AACzE,YAAY,EAAE,cAAc,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACtE,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,aAAa,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAClF,YAAY,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AACjD,OAAO,EAAE,WAAW,EAAE,uBAAuB,EAAE,uBAAuB,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAClI,YAAY,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AACnE,OAAO,EAAE,4BAA4B,EAAE,yBAAyB,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACjH,YAAY,EAAE,WAAW,EAAE,YAAY,EAAE,SAAS,EAAE,QAAQ,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,6 @@
1
+ /** base-paint 入口:ui+style+injector+渲染契约统一出口。装配唯一 owner。 */
2
+ export { createPageRegistry, pageOrReco, recoDescriptor } from './ui.js';
3
+ export { STYLE_PREFIX, STYLE_TOKENS, STYLE_VERSION, cx, token } from './style.js';
4
+ export { RenderError, RENDER_CONTRACT_VERSION, RENDER_ENVELOPE_VERSION, escapeHtml, renderPage, renderReco } from './contract.js';
5
+ export { INJECTOR_DEFAULT_MAX_RETRIES, INJECTOR_DEFAULT_RETRY_MS, mountInjector, openPage } from './injector.js';
6
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,2DAA2D;AAC3D,OAAO,EAAE,kBAAkB,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAEzE,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,aAAa,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAElF,OAAO,EAAE,WAAW,EAAE,uBAAuB,EAAE,uBAAuB,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAElI,OAAO,EAAE,4BAA4B,EAAE,yBAAyB,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC"}
@@ -0,0 +1,47 @@
1
+ /** base-paint/injector:装配唯一 owner(归一 render)。
2
+ *
3
+ * better-sidebar 槽位注册只许住这里:link-core/combos 禁止自装配
4
+ *(tooling/check-boundaries.mjs 可执行断言)。host 无关设计——
5
+ * 以 SlotsPort 注入真实服务,B 落地实测时接线,单测用假端口。
6
+ * 语义抄 matt 实证:幂等(disposer 非空即跳过)+ 有界重试(1s×10)+
7
+ * 卸载清理(ctx.effect 同构),成功后无轮询;openTab 走 path seed 内容型打开。
8
+ */
9
+ import type { PageDescriptor } from './ui.js';
10
+ export interface TabEntry {
11
+ readonly id: string;
12
+ readonly title: string;
13
+ readonly order: number;
14
+ readonly single: boolean;
15
+ readonly hidden?: boolean;
16
+ readonly component: unknown;
17
+ }
18
+ export interface TabSeed {
19
+ readonly type: string;
20
+ readonly path: string;
21
+ }
22
+ export interface TabScope {
23
+ readonly sessionId?: string;
24
+ }
25
+ export interface SlotsPort {
26
+ /** 能力探测(对应 ctx.get betterSidebar 存在且 registerTab 可用)。 */
27
+ hasTabs(): boolean;
28
+ registerTab(entry: TabEntry): () => void;
29
+ openTab(seed: TabSeed, scope?: TabScope): void;
30
+ }
31
+ export interface MountOptions {
32
+ readonly maxRetries?: number;
33
+ readonly retryMs?: number;
34
+ }
35
+ export interface MountHandle {
36
+ /** 仍在重试等待服务就绪。 */
37
+ pending(): boolean;
38
+ /** 停重试定时器 + 逐个调 tab disposer(幂等)。 */
39
+ dispose(): void;
40
+ }
41
+ export declare const INJECTOR_DEFAULT_MAX_RETRIES = 10;
42
+ export declare const INJECTOR_DEFAULT_RETRY_MS = 1000;
43
+ /** 挂载全部页面描述子(按 order 注册,ilife 星号 id)。幂等:同 slotId 不重复注册。 */
44
+ export declare function mountInjector(port: SlotsPort, pages: readonly PageDescriptor[], opts?: MountOptions): MountHandle;
45
+ /** 打开一页:path seed 内容型打开(折叠面板自动展开);缺席直接返回,由调用方渲染 reco。 */
46
+ export declare function openPage(port: SlotsPort, page: PageDescriptor, sessionId?: string): void;
47
+ //# sourceMappingURL=injector.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"injector.d.ts","sourceRoot":"","sources":["../src/injector.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAE9C,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IACzB,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;CAC7B;AAED,MAAM,WAAW,OAAO;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED,MAAM,WAAW,SAAS;IACxB,yDAAyD;IACzD,OAAO,IAAI,OAAO,CAAC;IACnB,WAAW,CAAC,KAAK,EAAE,QAAQ,GAAG,MAAM,IAAI,CAAC;IACzC,OAAO,CAAC,IAAI,EAAE,OAAO,EAAE,KAAK,CAAC,EAAE,QAAQ,GAAG,IAAI,CAAC;CAChD;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,WAAW;IAC1B,kBAAkB;IAClB,OAAO,IAAI,OAAO,CAAC;IACnB,qCAAqC;IACrC,OAAO,IAAI,IAAI,CAAC;CACjB;AAED,eAAO,MAAM,4BAA4B,KAAK,CAAC;AAC/C,eAAO,MAAM,yBAAyB,OAAO,CAAC;AAE9C,2DAA2D;AAC3D,wBAAgB,aAAa,CAAC,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,SAAS,cAAc,EAAE,EAAE,IAAI,GAAE,YAAiB,GAAG,WAAW,CAiDrH;AAED,yDAAyD;AACzD,wBAAgB,QAAQ,CAAC,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,cAAc,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAGxF"}
@@ -0,0 +1,63 @@
1
+ export const INJECTOR_DEFAULT_MAX_RETRIES = 10;
2
+ export const INJECTOR_DEFAULT_RETRY_MS = 1000;
3
+ /** 挂载全部页面描述子(按 order 注册,ilife 星号 id)。幂等:同 slotId 不重复注册。 */
4
+ export function mountInjector(port, pages, opts = {}) {
5
+ const maxRetries = opts.maxRetries ?? INJECTOR_DEFAULT_MAX_RETRIES;
6
+ const retryMs = opts.retryMs ?? INJECTOR_DEFAULT_RETRY_MS;
7
+ const disposers = new Map();
8
+ const wanted = new Set(pages.map((p) => p.slotId));
9
+ let timer = null;
10
+ let tries = 0;
11
+ let disposed = false;
12
+ const tryMount = () => {
13
+ if (disposed || !port.hasTabs())
14
+ return false;
15
+ const ordered = [...pages].sort((a, b) => a.order - b.order);
16
+ for (const p of ordered) {
17
+ if (disposers.has(p.slotId))
18
+ continue;
19
+ try {
20
+ const d = port.registerTab({ id: p.slotId, title: p.title, order: p.order, single: true, component: { skill: p.skill, kind: p.kind } });
21
+ disposers.set(p.slotId, d);
22
+ }
23
+ catch {
24
+ return false;
25
+ }
26
+ }
27
+ return disposers.size === wanted.size;
28
+ };
29
+ const stopTimer = () => {
30
+ if (timer !== null) {
31
+ clearInterval(timer);
32
+ timer = null;
33
+ }
34
+ };
35
+ if (!tryMount()) {
36
+ timer = setInterval(() => {
37
+ tries += 1;
38
+ if (tryMount() || tries >= maxRetries)
39
+ stopTimer();
40
+ }, retryMs);
41
+ }
42
+ return {
43
+ pending: () => timer !== null,
44
+ dispose: () => {
45
+ disposed = true;
46
+ stopTimer();
47
+ for (const d of disposers.values()) {
48
+ try {
49
+ d();
50
+ }
51
+ catch { /* 忽略 */ }
52
+ }
53
+ disposers.clear();
54
+ },
55
+ };
56
+ }
57
+ /** 打开一页:path seed 内容型打开(折叠面板自动展开);缺席直接返回,由调用方渲染 reco。 */
58
+ export function openPage(port, page, sessionId) {
59
+ if (!port.hasTabs())
60
+ return;
61
+ port.openTab({ type: page.slotId, path: page.slotId }, sessionId ? { sessionId } : undefined);
62
+ }
63
+ //# sourceMappingURL=injector.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"injector.js","sourceRoot":"","sources":["../src/injector.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,24 @@
1
+ /** base-paint/style:样式唯一真相源。
2
+ *
3
+ * 红线:样式只抖 render——改样式只改本文件,link-core/combos/单品包
4
+ * 禁止自带样式常量。类名前缀统一 STYLE_PREFIX,token 表冻结。
5
+ */
6
+ export declare const STYLE_PREFIX = "ilife-";
7
+ export declare const STYLE_VERSION = "0.1.0";
8
+ export declare const STYLE_TOKENS: Readonly<{
9
+ radius: 8;
10
+ gap: 8;
11
+ fontSize: 13;
12
+ fg: "#e6edf3";
13
+ muted: "#8b8b95";
14
+ accent: "#c084fc";
15
+ danger: "#f85149";
16
+ border: "#2a2d35";
17
+ bg: "#16181d";
18
+ }>;
19
+ export type StyleTokenName = keyof typeof STYLE_TOKENS;
20
+ /** 类名拼接(自动加前缀,falsy 跳过)。 */
21
+ export declare function cx(...names: Array<string | false | null | undefined>): string;
22
+ /** 取 token(样式消费唯一入口,换肤时单点改)。 */
23
+ export declare function token(name: StyleTokenName): string;
24
+ //# sourceMappingURL=style.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"style.d.ts","sourceRoot":"","sources":["../src/style.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,eAAO,MAAM,YAAY,WAAW,CAAC;AACrC,eAAO,MAAM,aAAa,UAAU,CAAC;AAErC,eAAO,MAAM,YAAY;;;;;;;;;;EAUvB,CAAC;AAEH,MAAM,MAAM,cAAc,GAAG,MAAM,OAAO,YAAY,CAAC;AAEvD,4BAA4B;AAC5B,wBAAgB,EAAE,CAAC,GAAG,KAAK,EAAE,KAAK,CAAC,MAAM,GAAG,KAAK,GAAG,IAAI,GAAG,SAAS,CAAC,GAAG,MAAM,CAE7E;AAED,gCAAgC;AAChC,wBAAgB,KAAK,CAAC,IAAI,EAAE,cAAc,GAAG,MAAM,CAElD"}
package/dist/style.js ADDED
@@ -0,0 +1,27 @@
1
+ /** base-paint/style:样式唯一真相源。
2
+ *
3
+ * 红线:样式只抖 render——改样式只改本文件,link-core/combos/单品包
4
+ * 禁止自带样式常量。类名前缀统一 STYLE_PREFIX,token 表冻结。
5
+ */
6
+ export const STYLE_PREFIX = 'ilife-';
7
+ export const STYLE_VERSION = '0.1.0';
8
+ export const STYLE_TOKENS = Object.freeze({
9
+ radius: 8,
10
+ gap: 8,
11
+ fontSize: 13,
12
+ fg: '#e6edf3',
13
+ muted: '#8b8b95',
14
+ accent: '#c084fc',
15
+ danger: '#f85149',
16
+ border: '#2a2d35',
17
+ bg: '#16181d',
18
+ });
19
+ /** 类名拼接(自动加前缀,falsy 跳过)。 */
20
+ export function cx(...names) {
21
+ return names.filter((n) => typeof n === 'string' && n.length > 0).map((n) => STYLE_PREFIX + n).join(' ');
22
+ }
23
+ /** 取 token(样式消费唯一入口,换肤时单点改)。 */
24
+ export function token(name) {
25
+ return String(STYLE_TOKENS[name]);
26
+ }
27
+ //# sourceMappingURL=style.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"style.js","sourceRoot":"","sources":["../src/style.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,MAAM,CAAC,MAAM,YAAY,GAAG,QAAQ,CAAC;AACrC,MAAM,CAAC,MAAM,aAAa,GAAG,OAAO,CAAC;AAErC,MAAM,CAAC,MAAM,YAAY,GAAG,MAAM,CAAC,MAAM,CAAC;IACxC,MAAM,EAAE,CAAC;IACT,GAAG,EAAE,CAAC;IACN,QAAQ,EAAE,EAAE;IACZ,EAAE,EAAE,SAAS;IACb,KAAK,EAAE,SAAS;IAChB,MAAM,EAAE,SAAS;IACjB,MAAM,EAAE,SAAS;IACjB,MAAM,EAAE,SAAS;IACjB,EAAE,EAAE,SAAS;CACd,CAAC,CAAC;AAIH,4BAA4B;AAC5B,MAAM,UAAU,EAAE,CAAC,GAAG,KAA+C;IACnE,OAAO,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAe,EAAE,CAAC,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,YAAY,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AACxH,CAAC;AAED,gCAAgC;AAChC,MAAM,UAAU,KAAK,CAAC,IAAoB;IACxC,OAAO,MAAM,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC,CAAC;AACpC,CAAC"}
package/dist/ui.d.ts ADDED
@@ -0,0 +1,31 @@
1
+ /** base-paint/ui:单品页自注册描述层(纯数据,无 DOM、无 host、无运行时依赖)。
2
+ *
3
+ * 归一原则:单品包调用 registerPage 自注册;总管只消费注册表,
4
+ * 禁止 import 单品页组件(P7 #8 验收)。缺席走 pageOrReco 纯条件渲染,不轮询。
5
+ */
6
+ export type PageKind = 'page' | 'reco';
7
+ export interface PageDescriptor {
8
+ /** 所属技能,如 calorie。 */
9
+ readonly skill: string;
10
+ /** better-sidebar tab id,命名空间 ilife 星号,如 ilife:calorie。 */
11
+ readonly slotId: string;
12
+ /** tab 排序,70 段草案(P3 #4 实测前不定案)。 */
13
+ readonly order: number;
14
+ /** 展示标题。 */
15
+ readonly title: string;
16
+ /** page=单品实页,reco=推荐安装占位。 */
17
+ readonly kind: PageKind;
18
+ }
19
+ /** 推荐安装占位描述子(缺席时渲染它,而非空白)。 */
20
+ export declare function recoDescriptor(skill: string, slotId: string, order: number, title: string): PageDescriptor;
21
+ export interface PageRegistry {
22
+ /** 自注册;slotId 重复即抛(大声失败,不静默覆盖)。返回 disposer,幂等。 */
23
+ registerPage(desc: PageDescriptor): () => void;
24
+ /** 按 order 升序。 */
25
+ pages(): PageDescriptor[];
26
+ pageForSlot(slotId: string): PageDescriptor | undefined;
27
+ }
28
+ export declare function createPageRegistry(): PageRegistry;
29
+ /** 缺席纯条件渲染:有页用页,无页用 reco 占位。不轮询、不返空。 */
30
+ export declare function pageOrReco(reg: PageRegistry, reco: PageDescriptor): PageDescriptor;
31
+ //# sourceMappingURL=ui.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ui.d.ts","sourceRoot":"","sources":["../src/ui.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,MAAM,MAAM,QAAQ,GAAG,MAAM,GAAG,MAAM,CAAC;AAEvC,MAAM,WAAW,cAAc;IAC7B,sBAAsB;IACtB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,2DAA2D;IAC3D,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,mCAAmC;IACnC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,YAAY;IACZ,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,6BAA6B;IAC7B,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;CACzB;AAED,8BAA8B;AAC9B,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,cAAc,CAE1G;AAED,MAAM,WAAW,YAAY;IAC3B,kDAAkD;IAClD,YAAY,CAAC,IAAI,EAAE,cAAc,GAAG,MAAM,IAAI,CAAC;IAC/C,kBAAkB;IAClB,KAAK,IAAI,cAAc,EAAE,CAAC;IAC1B,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,cAAc,GAAG,SAAS,CAAC;CACzD;AAED,wBAAgB,kBAAkB,IAAI,YAAY,CAoBjD;AAED,wCAAwC;AACxC,wBAAgB,UAAU,CAAC,GAAG,EAAE,YAAY,EAAE,IAAI,EAAE,cAAc,GAAG,cAAc,CAElF"}
package/dist/ui.js ADDED
@@ -0,0 +1,38 @@
1
+ /** base-paint/ui:单品页自注册描述层(纯数据,无 DOM、无 host、无运行时依赖)。
2
+ *
3
+ * 归一原则:单品包调用 registerPage 自注册;总管只消费注册表,
4
+ * 禁止 import 单品页组件(P7 #8 验收)。缺席走 pageOrReco 纯条件渲染,不轮询。
5
+ */
6
+ /** 推荐安装占位描述子(缺席时渲染它,而非空白)。 */
7
+ export function recoDescriptor(skill, slotId, order, title) {
8
+ return { skill, slotId, order, title, kind: 'reco' };
9
+ }
10
+ export function createPageRegistry() {
11
+ const bySlot = new Map();
12
+ return {
13
+ registerPage(desc) {
14
+ if (bySlot.has(desc.slotId))
15
+ throw new Error('duplicate slotId: ' + desc.slotId);
16
+ bySlot.set(desc.slotId, desc);
17
+ let done = false;
18
+ return () => {
19
+ if (done)
20
+ return;
21
+ done = true;
22
+ if (bySlot.get(desc.slotId) === desc)
23
+ bySlot.delete(desc.slotId);
24
+ };
25
+ },
26
+ pages() {
27
+ return [...bySlot.values()].sort((a, b) => a.order - b.order);
28
+ },
29
+ pageForSlot(slotId) {
30
+ return bySlot.get(slotId);
31
+ },
32
+ };
33
+ }
34
+ /** 缺席纯条件渲染:有页用页,无页用 reco 占位。不轮询、不返空。 */
35
+ export function pageOrReco(reg, reco) {
36
+ return reg.pageForSlot(reco.slotId) ?? reco;
37
+ }
38
+ //# sourceMappingURL=ui.js.map
package/dist/ui.js.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ui.js","sourceRoot":"","sources":["../src/ui.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAiBH,8BAA8B;AAC9B,MAAM,UAAU,cAAc,CAAC,KAAa,EAAE,MAAc,EAAE,KAAa,EAAE,KAAa;IACxF,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;AACvD,CAAC;AAUD,MAAM,UAAU,kBAAkB;IAChC,MAAM,MAAM,GAAG,IAAI,GAAG,EAA0B,CAAC;IACjD,OAAO;QACL,YAAY,CAAC,IAAoB;YAC/B,IAAI,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC;gBAAE,MAAM,IAAI,KAAK,CAAC,oBAAoB,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC;YACjF,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;YAC9B,IAAI,IAAI,GAAG,KAAK,CAAC;YACjB,OAAO,GAAG,EAAE;gBACV,IAAI,IAAI;oBAAE,OAAO;gBACjB,IAAI,GAAG,IAAI,CAAC;gBACZ,IAAI,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,IAAI;oBAAE,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;YACnE,CAAC,CAAC;QACJ,CAAC;QACD,KAAK;YACH,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC;QAChE,CAAC;QACD,WAAW,CAAC,MAAc;YACxB,OAAO,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QAC5B,CAAC;KACF,CAAC;AACJ,CAAC;AAED,wCAAwC;AACxC,MAAM,UAAU,UAAU,CAAC,GAAiB,EAAE,IAAoB;IAChE,OAAO,GAAG,CAAC,WAAW,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,IAAI,CAAC;AAC9C,CAAC"}
package/package.json ADDED
@@ -0,0 +1,24 @@
1
+ {
2
+ "name": "base-paint",
3
+ "version": "0.1.0",
4
+ "description": "渲染包:ui+style+injector+渲染契约,装配唯一 owner",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": "./dist/index.js"
10
+ },
11
+ "files": [
12
+ "dist"
13
+ ],
14
+ "engines": {
15
+ "node": ">=22.13"
16
+ },
17
+ "devDependencies": {
18
+ "base-link-core": "workspace:^0.1.0"
19
+ },
20
+ "scripts": {
21
+ "build": "tsc -b",
22
+ "test": "node --test ../../test/scaffold.test.mjs"
23
+ }
24
+ }