@morlay/dsh-client-ui-primitives 0.0.2-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,5 @@
1
+ import { n as ReferenceSpan, r as findReferences, s as parseReferenceToken, t as Reference } from "./reference-Do8Uwujy.cjs";
2
+ //#region src/index.d.ts
3
+ declare function apply(): void;
4
+ //#endregion
5
+ export { type Reference, type ReferenceSpan, apply, findReferences, parseReferenceToken };
@@ -0,0 +1,5 @@
1
+ import { n as ReferenceSpan, r as findReferences, s as parseReferenceToken, t as Reference } from "./reference-Do8Uwujy.mjs";
2
+ //#region src/index.d.ts
3
+ declare function apply(): void;
4
+ //#endregion
5
+ export { type Reference, type ReferenceSpan, apply, findReferences, parseReferenceToken };
package/dist/index.mjs ADDED
@@ -0,0 +1,5 @@
1
+ import { a as parseReferenceToken, t as findReferences } from "./reference-BLcMVsqa.mjs";
2
+ //#region src/index.ts
3
+ function apply() {}
4
+ //#endregion
5
+ export { apply, findReferences, parseReferenceToken };
@@ -0,0 +1,406 @@
1
+ import { fromMarkdown } from "mdast-util-from-markdown";
2
+ import { gfmFromMarkdown } from "mdast-util-gfm";
3
+ import { asciiAlpha, asciiAlphanumeric, asciiDigit, markdownLineEndingOrSpace, unicodePunctuation, unicodeWhitespace } from "micromark-util-character";
4
+ import { gfm } from "micromark-extension-gfm";
5
+ import { codes, types } from "micromark-util-symbol";
6
+ //#region src/reference.ts
7
+ const DEFAULT_PROTOCOL = "file";
8
+ const EXTERNAL_PROTOCOLS = [
9
+ "http",
10
+ "https",
11
+ "mailto"
12
+ ];
13
+ const LINE_FRAGMENT = /^L(\d+)(?:C(\d+))?(?:-L(\d+))?$/u;
14
+ const SCHEME = /^([A-Za-z][A-Za-z0-9+.-]*):/u;
15
+ const BARE_PATH = /^([^:]+):(\d+)(?::(\d+))?$/u;
16
+ const URI_UNSAFE = /[%#()\s<>"`]/gu;
17
+ const PATH_PUNCTUATION = [
18
+ codes.slash,
19
+ codes.dash,
20
+ codes.underscore,
21
+ codes.dot,
22
+ codes.plusSign,
23
+ codes.percentSign,
24
+ codes.atSign,
25
+ codes.tilde,
26
+ codes.equalsTo
27
+ ];
28
+ const TRIGGERS = [
29
+ ":",
30
+ "[",
31
+ "@"
32
+ ];
33
+ function parseReference(value, title) {
34
+ const raw = value.trim();
35
+ if (raw === "") return void 0;
36
+ const matched = SCHEME.exec(raw);
37
+ const protocol = matched === null ? DEFAULT_PROTOCOL : matched[1].toLowerCase();
38
+ let rest = matched === null ? raw : raw.slice(matched[0].length);
39
+ if (matched !== null && /\s/u.test(rest)) return void 0;
40
+ const fragment = splitLineFragment(rest);
41
+ rest = fragment.head;
42
+ let origin;
43
+ if (rest.startsWith("//")) {
44
+ const end = authorityEnd(rest);
45
+ const authority = rest.slice(2, end);
46
+ if (authority === "") return void 0;
47
+ origin = `${protocol}://${authority}`;
48
+ rest = rest.slice(end).replace(/^\//u, "");
49
+ }
50
+ const decoded = protocol === DEFAULT_PROTOCOL ? decodeUri(rest) : rest;
51
+ if (decoded === void 0 || decoded === "" && origin === void 0) return void 0;
52
+ return {
53
+ protocol,
54
+ ...origin === void 0 ? {} : { origin },
55
+ ...decoded === "" ? {} : { path: decoded },
56
+ ...title === void 0 || title === "" ? {} : { title },
57
+ ...fragment.lines
58
+ };
59
+ }
60
+ function parseReferenceToken(value) {
61
+ return SCHEME.test(value) ? parseReference(value) : void 0;
62
+ }
63
+ function formatReference(reference) {
64
+ const protocol = reference.protocol === "" ? DEFAULT_PROTOCOL : reference.protocol;
65
+ const path = reference.path ?? "";
66
+ if (reference.origin !== void 0) return `${reference.origin}${path === "" ? "" : `/${path}`}`;
67
+ return `${protocol}:${encodeUri(path)}${lineFragmentOf(reference)}`;
68
+ }
69
+ function isLocalReference(reference) {
70
+ return !EXTERNAL_PROTOCOLS.includes(reference.protocol);
71
+ }
72
+ function findReferences(text) {
73
+ const spans = [];
74
+ if (text === "" || !TRIGGERS.some((trigger) => text.includes(trigger))) return spans;
75
+ collectReferences(parseReferenceDocument(text), spans);
76
+ return spans;
77
+ }
78
+ function parseReferenceDocument(text) {
79
+ return fromMarkdown(text, {
80
+ extensions: [gfm(), referenceSyntax()],
81
+ mdastExtensions: [gfmFromMarkdown(), referenceFromMarkdown()]
82
+ });
83
+ }
84
+ function collectReferences(node, spans) {
85
+ const position = node.position;
86
+ if (node.type === "reference" && position !== void 0) {
87
+ const reference = referenceOfNode(node);
88
+ if (reference !== void 0) spans.push({
89
+ start: position.start.offset,
90
+ end: position.end.offset,
91
+ reference
92
+ });
93
+ }
94
+ for (const child of node.children ?? []) collectReferences(child, spans);
95
+ }
96
+ function referenceOfNode(node) {
97
+ const protocol = node.protocol;
98
+ if (protocol === void 0) return void 0;
99
+ return {
100
+ protocol,
101
+ ...node.origin === void 0 ? {} : { origin: node.origin },
102
+ ...node.path === void 0 ? {} : { path: node.path },
103
+ ...node.title === void 0 ? {} : { title: node.title },
104
+ ...node.lineStart === void 0 ? {} : { lineStart: node.lineStart },
105
+ ...node.lineEnd === void 0 ? {} : { lineEnd: node.lineEnd },
106
+ ...node.column === void 0 ? {} : { column: node.column }
107
+ };
108
+ }
109
+ const previousReference = function(code) {
110
+ return isBoundary(code);
111
+ };
112
+ const tokenizeReference = function(effects, ok, nok) {
113
+ const { events } = this;
114
+ let at = false;
115
+ let head = "";
116
+ return start;
117
+ function start(code) {
118
+ if (insideLabel(events)) return nok(code);
119
+ effects.enter("reference");
120
+ if (code === codes.atSign) {
121
+ at = true;
122
+ effects.consume(code);
123
+ return afterAt;
124
+ }
125
+ return headStart(code);
126
+ }
127
+ function afterAt(code) {
128
+ return asciiAlpha(code) ? headStart(code) : pathStart(code);
129
+ }
130
+ function headStart(code) {
131
+ if (!asciiAlpha(code)) return nok(code);
132
+ head = "";
133
+ return headInside(code);
134
+ }
135
+ function headInside(code) {
136
+ if (code === null || !asciiAlpha(code)) return afterHead(code);
137
+ head += String.fromCodePoint(code);
138
+ effects.consume(code);
139
+ return headInside;
140
+ }
141
+ function afterHead(code) {
142
+ if (code === codes.colon && isKnownScheme(head)) {
143
+ effects.consume(code);
144
+ return head.toLowerCase() === "skill" ? skillInside : fileInside;
145
+ }
146
+ return at ? pathInside(code) : nok(code);
147
+ }
148
+ function pathStart(code) {
149
+ if (!isPathCode(code) || code === codes.atSign) return nok(code);
150
+ effects.consume(code);
151
+ return pathInside;
152
+ }
153
+ function fileInside(code) {
154
+ if (!isFileCode(code)) return nok(code);
155
+ effects.consume(code);
156
+ return fileRest;
157
+ }
158
+ function fileRest(code) {
159
+ if (isFileCode(code)) {
160
+ effects.consume(code);
161
+ return fileRest;
162
+ }
163
+ return finish(code);
164
+ }
165
+ function skillInside(code) {
166
+ if (!isSkillCode(code)) return nok(code);
167
+ effects.consume(code);
168
+ return skillRest;
169
+ }
170
+ function skillRest(code) {
171
+ if (isSkillCode(code)) {
172
+ effects.consume(code);
173
+ return skillRest;
174
+ }
175
+ return finish(code);
176
+ }
177
+ function pathInside(code) {
178
+ if (isPathCode(code) || code === codes.numberSign) {
179
+ effects.consume(code);
180
+ return pathInside;
181
+ }
182
+ if (code !== codes.colon) return isBoundary(code) ? finish(code) : nok(code);
183
+ effects.consume(code);
184
+ return lineInside;
185
+ }
186
+ function lineInside(code) {
187
+ if (!asciiDigit(code)) return nok(code);
188
+ effects.consume(code);
189
+ return lineRest;
190
+ }
191
+ function lineRest(code) {
192
+ if (asciiDigit(code)) {
193
+ effects.consume(code);
194
+ return lineRest;
195
+ }
196
+ if (code !== codes.colon) return finish(code);
197
+ effects.consume(code);
198
+ return columnInside;
199
+ }
200
+ function columnInside(code) {
201
+ if (!asciiDigit(code)) return nok(code);
202
+ effects.consume(code);
203
+ return columnRest;
204
+ }
205
+ function columnRest(code) {
206
+ if (asciiDigit(code)) {
207
+ effects.consume(code);
208
+ return columnRest;
209
+ }
210
+ return finish(code);
211
+ }
212
+ function finish(code) {
213
+ if (!isBoundary(code)) return nok(code);
214
+ effects.exit("reference");
215
+ return ok(code);
216
+ }
217
+ };
218
+ const referenceConstruct = {
219
+ name: "reference",
220
+ previous: previousReference,
221
+ tokenize: tokenizeReference
222
+ };
223
+ const referenceSyntaxExtension = { text: {
224
+ [codes.atSign]: referenceConstruct,
225
+ [codes.lowercaseF]: referenceConstruct,
226
+ [codes.uppercaseF]: referenceConstruct,
227
+ [codes.lowercaseS]: referenceConstruct,
228
+ [codes.uppercaseS]: referenceConstruct
229
+ } };
230
+ function referenceSyntax() {
231
+ return referenceSyntaxExtension;
232
+ }
233
+ function insideLabel(events) {
234
+ for (let index = events.length - 1; index >= 0; index -= 1) {
235
+ const token = events[index]?.[1];
236
+ if (token === void 0) continue;
237
+ if ((token.type === types.labelLink || token.type === types.labelImage) && token._balanced !== true) return true;
238
+ }
239
+ return false;
240
+ }
241
+ function isBoundary(code) {
242
+ return code === null || markdownLineEndingOrSpace(code) || unicodeWhitespace(code) || unicodePunctuation(code);
243
+ }
244
+ function isPathCode(code) {
245
+ if (code === null || markdownLineEndingOrSpace(code)) return false;
246
+ if (!unicodePunctuation(code)) return true;
247
+ return PATH_PUNCTUATION.includes(code);
248
+ }
249
+ function isFileCode(code) {
250
+ return isPathCode(code) || code === codes.numberSign || code === codes.colon;
251
+ }
252
+ function isSkillCode(code) {
253
+ return asciiAlphanumeric(code) || code === codes.dash || code === codes.underscore || code === codes.dot;
254
+ }
255
+ function enterReference(token) {
256
+ this.enter({ type: "reference" }, token);
257
+ }
258
+ function exitReference(token) {
259
+ const node = this.stack[this.stack.length - 1];
260
+ const raw = this.sliceSerialize(token);
261
+ const reference = parseBareReference(raw);
262
+ if (node !== void 0) {
263
+ if (reference === void 0) {
264
+ node.type = "text";
265
+ node.value = raw;
266
+ } else Object.assign(node, reference);
267
+ }
268
+ this.exit(token);
269
+ }
270
+ const transformLinks = function(tree) {
271
+ convertLinks(tree);
272
+ };
273
+ function convertLinks(parent) {
274
+ const children = parent.children;
275
+ if (children === void 0) return;
276
+ for (let index = 0; index < children.length; index += 1) {
277
+ const child = children[index];
278
+ if (child === void 0) continue;
279
+ if (child.type === "link") {
280
+ const reference = parseReference(child.url ?? "", labelOf(child));
281
+ if (reference !== void 0) {
282
+ children[index] = referenceNodeOf(reference, child, children[index - 1]);
283
+ continue;
284
+ }
285
+ }
286
+ convertLinks(child);
287
+ }
288
+ }
289
+ function referenceNodeOf(reference, link, previous) {
290
+ const node = {
291
+ type: "reference",
292
+ ...reference
293
+ };
294
+ const position = link.position;
295
+ if (position === void 0) return node;
296
+ node.position = startsAfterAt(previous, position.start) ? atStart(position) : position;
297
+ return node;
298
+ }
299
+ function startsAfterAt(previous, start) {
300
+ if (previous?.type !== "text" || previous.value?.endsWith("@") !== true) return false;
301
+ const position = previous.position;
302
+ if (position === void 0 || position.end.offset !== start.offset) return false;
303
+ return position.end.offset - position.start.offset === previous.value.length;
304
+ }
305
+ function atStart(position) {
306
+ return {
307
+ start: {
308
+ line: position.start.line,
309
+ column: position.start.column - 1,
310
+ offset: position.start.offset - 1
311
+ },
312
+ end: position.end
313
+ };
314
+ }
315
+ function labelOf(node) {
316
+ const label = textOf(node);
317
+ return label === "" ? void 0 : label;
318
+ }
319
+ function textOf(node) {
320
+ if (node.value !== void 0) return node.value;
321
+ let text = "";
322
+ for (const child of node.children ?? []) text += textOf(child);
323
+ return text;
324
+ }
325
+ function parseBareReference(raw) {
326
+ const body = raw.startsWith("@") ? raw.slice(1) : raw;
327
+ if (body === "") return void 0;
328
+ const scheme = SCHEME.exec(body);
329
+ if (scheme !== null && isKnownScheme(scheme[1])) return parseReference(body);
330
+ const matched = BARE_PATH.exec(body);
331
+ const path = matched?.[1];
332
+ const line = matched?.[2];
333
+ if (path === void 0 || line === void 0) return parseReference(body);
334
+ const decoded = decodeUri(path);
335
+ if (decoded === void 0 || decoded === "") return void 0;
336
+ const column = matched?.[3];
337
+ return {
338
+ protocol: DEFAULT_PROTOCOL,
339
+ path: decoded,
340
+ lineStart: Number(line),
341
+ ...column === void 0 ? {} : { column: Number(column) }
342
+ };
343
+ }
344
+ function referenceFromMarkdown() {
345
+ return {
346
+ enter: { reference: enterReference },
347
+ exit: { reference: exitReference },
348
+ transforms: [transformLinks]
349
+ };
350
+ }
351
+ function isKnownScheme(name) {
352
+ const protocol = name.toLowerCase();
353
+ return protocol === "file" || protocol === "skill";
354
+ }
355
+ function splitLineFragment(value) {
356
+ const hashAt = value.indexOf("#");
357
+ if (hashAt === -1) return {
358
+ head: value,
359
+ lines: {}
360
+ };
361
+ const matched = LINE_FRAGMENT.exec(value.slice(hashAt + 1));
362
+ if (matched === null) return {
363
+ head: value,
364
+ lines: {}
365
+ };
366
+ const lineStart = matched[1];
367
+ if (lineStart === void 0) return {
368
+ head: value,
369
+ lines: {}
370
+ };
371
+ const column = matched[2];
372
+ const lineEnd = matched[3];
373
+ return {
374
+ head: value.slice(0, hashAt),
375
+ lines: {
376
+ lineStart: Number(lineStart),
377
+ ...column === void 0 ? {} : { column: Number(column) },
378
+ ...lineEnd === void 0 ? {} : { lineEnd: Number(lineEnd) }
379
+ }
380
+ };
381
+ }
382
+ function lineFragmentOf(reference) {
383
+ if (reference.lineStart === void 0) return "";
384
+ const column = reference.column === void 0 ? "" : `C${String(reference.column)}`;
385
+ const lineEnd = reference.lineEnd === void 0 ? "" : `-L${String(reference.lineEnd)}`;
386
+ return `#L${String(reference.lineStart)}${column}${lineEnd}`;
387
+ }
388
+ function authorityEnd(value) {
389
+ for (let index = 2; index < value.length; index += 1) {
390
+ const char = value[index];
391
+ if (char === "/" || char === "?" || char === "#") return index;
392
+ }
393
+ return value.length;
394
+ }
395
+ function encodeUri(uri) {
396
+ return uri.replace(URI_UNSAFE, (char) => `%${char.charCodeAt(0).toString(16).toUpperCase().padStart(2, "0")}`);
397
+ }
398
+ function decodeUri(value) {
399
+ try {
400
+ return decodeURIComponent(value);
401
+ } catch {
402
+ return;
403
+ }
404
+ }
405
+ //#endregion
406
+ export { parseReferenceToken as a, parseReference as i, formatReference as n, isLocalReference as r, findReferences as t };
@@ -0,0 +1,29 @@
1
+ import "mdast-util-from-markdown";
2
+ import "micromark-util-types";
3
+ //#region src/reference.d.ts
4
+ declare module "micromark-util-types" {
5
+ interface TokenTypeMap {
6
+ reference: "reference";
7
+ }
8
+ }
9
+ interface Reference {
10
+ readonly protocol: string;
11
+ readonly origin?: string;
12
+ readonly path?: string;
13
+ readonly title?: string;
14
+ readonly lineStart?: number;
15
+ readonly lineEnd?: number;
16
+ readonly column?: number;
17
+ }
18
+ interface ReferenceSpan {
19
+ readonly start: number;
20
+ readonly end: number;
21
+ readonly reference: Reference;
22
+ }
23
+ declare function parseReference(value: string, title?: string): Reference | undefined;
24
+ declare function parseReferenceToken(value: string): Reference | undefined;
25
+ declare function formatReference(reference: Reference): string;
26
+ declare function isLocalReference(reference: Reference): boolean;
27
+ declare function findReferences(text: string): readonly ReferenceSpan[];
28
+ //#endregion
29
+ export { isLocalReference as a, formatReference as i, ReferenceSpan as n, parseReference as o, findReferences as r, parseReferenceToken as s, Reference as t };
@@ -0,0 +1,29 @@
1
+ import "mdast-util-from-markdown";
2
+ import "micromark-util-types";
3
+ //#region src/reference.d.ts
4
+ declare module "micromark-util-types" {
5
+ interface TokenTypeMap {
6
+ reference: "reference";
7
+ }
8
+ }
9
+ interface Reference {
10
+ readonly protocol: string;
11
+ readonly origin?: string;
12
+ readonly path?: string;
13
+ readonly title?: string;
14
+ readonly lineStart?: number;
15
+ readonly lineEnd?: number;
16
+ readonly column?: number;
17
+ }
18
+ interface ReferenceSpan {
19
+ readonly start: number;
20
+ readonly end: number;
21
+ readonly reference: Reference;
22
+ }
23
+ declare function parseReference(value: string, title?: string): Reference | undefined;
24
+ declare function parseReferenceToken(value: string): Reference | undefined;
25
+ declare function formatReference(reference: Reference): string;
26
+ declare function isLocalReference(reference: Reference): boolean;
27
+ declare function findReferences(text: string): readonly ReferenceSpan[];
28
+ //#endregion
29
+ export { isLocalReference as a, formatReference as i, ReferenceSpan as n, parseReference as o, findReferences as r, parseReferenceToken as s, Reference as t };
package/package.json ADDED
@@ -0,0 +1,66 @@
1
+ {
2
+ "name": "@morlay/dsh-client-ui-primitives",
3
+ "version": "0.0.2-alpha.0",
4
+ "description": "css-in-js styling kit (styled / Token / Styling) for the forked conversation UI: consumes the official --dsw-* design tokens, so no CSS Modules precompile step is needed.",
5
+ "keywords": [
6
+ "client",
7
+ "css-in-js",
8
+ "design-tokens",
9
+ "dsh",
10
+ "dsh-plugin",
11
+ "styled"
12
+ ],
13
+ "license": "MIT",
14
+ "repository": {
15
+ "type": "git",
16
+ "url": "https://github.com/morlay/dsh-plugin.git"
17
+ },
18
+ "files": [
19
+ "dist",
20
+ "src",
21
+ "cordis.patch.yml",
22
+ "!**/__tests__"
23
+ ],
24
+ "type": "module",
25
+ "exports": {
26
+ ".": "./dist/index.mjs",
27
+ "./package.json": "./package.json",
28
+ "./cordis.patch.yml": "./cordis.patch.yml",
29
+ "./client": {
30
+ "types": "./dist/client.d.cts",
31
+ "default": "./dist/client.cjs"
32
+ }
33
+ },
34
+ "dependencies": {
35
+ "mdast-util-from-markdown": "^2.0.3",
36
+ "mdast-util-gfm": "^3.1.0",
37
+ "micromark-extension-gfm": "^3.0.0",
38
+ "micromark-util-character": "^2.1.1",
39
+ "micromark-util-symbol": "^2.0.1",
40
+ "micromark-util-types": "^2.0.2"
41
+ },
42
+ "devDependencies": {
43
+ "@deepseek-ai/dsh-client-ui-primitives": "^0.1.6-alpha.2",
44
+ "@types/mdast": "^4.0.4",
45
+ "@types/react": "~18.3.1",
46
+ "csstype": "^3.1.3",
47
+ "react": "^18.2.0",
48
+ "react-dom": "^18.2.0"
49
+ },
50
+ "peerDependencies": {
51
+ "@deepseek-ai/cordis": "^4.0.2"
52
+ },
53
+ "dsh": {
54
+ "client": {
55
+ "platform": "web",
56
+ "inject": []
57
+ },
58
+ "bundle": {
59
+ "patch": "./cordis.patch.yml"
60
+ }
61
+ },
62
+ "scripts": {
63
+ "build": "pnpm exec tsdown",
64
+ "gen:tokens": "pnpm exec tsx scripts/gen-design-tokens.mts"
65
+ }
66
+ }
@@ -0,0 +1,20 @@
1
+ import type { Context } from "@deepseek-ai/cordis";
2
+
3
+ export * from "./styling/index.ts";
4
+ export * from "./theme.ts";
5
+ export * from "./markdown-labels.ts";
6
+
7
+ export {
8
+ findReferences,
9
+ formatReference,
10
+ isLocalReference,
11
+ parseReference,
12
+ parseReferenceToken,
13
+ } from "../reference.ts";
14
+ export type { Reference, ReferenceSpan } from "../reference.ts";
15
+ export { ReferenceMarkdown, referenceMentions } from "../reference-markdown.tsx";
16
+ export type { ReferenceActions, ReferenceMarkdownProps } from "../reference-markdown.tsx";
17
+
18
+ export const inject: readonly string[] = [];
19
+
20
+ export function apply(_ctx: Context): void {}
@@ -0,0 +1,10 @@
1
+ import type { MarkdownLabels } from "@deepseek-ai/dsh-client-ui-primitives";
2
+
3
+ export type MarkdownCopySeat = (key: "copy" | "copied" | "markdown.footnotes") => string;
4
+
5
+ export function markdownLabels(t: MarkdownCopySeat): MarkdownLabels {
6
+ return {
7
+ code: { copyLabel: t("copy"), copiedLabel: t("copied") },
8
+ footnotes: t("markdown.footnotes"),
9
+ };
10
+ }
@@ -0,0 +1,6 @@
1
+ export { MarkdownText } from "@deepseek-ai/dsh-client-ui-primitives";
2
+ export type {
3
+ MarkdownFileMentions,
4
+ MarkdownLabels,
5
+ MarkdownPathImages,
6
+ } from "@deepseek-ai/dsh-client-ui-primitives";
@@ -0,0 +1,27 @@
1
+ import type { StandardProperties, VendorProperties } from "csstype";
2
+
3
+ type NonStandardProperties = {
4
+ cornerShape?: CSSValue<string>;
5
+ clip?: CSSValue<string>;
6
+ WebkitBoxOrient?: CSSValue<string>;
7
+ };
8
+
9
+ type Properties = Omit<StandardProperties & VendorProperties, keyof NonStandardProperties> &
10
+ NonStandardProperties;
11
+
12
+ export type CSSVar = `--${string}`;
13
+ export type CSSSelector = `&${string}` | `${string}&` | `@${string}`;
14
+
15
+ export type CSSValue<Fallback = string | number> = Fallback | (() => Iterable<Fallback>);
16
+
17
+ export type CSSObject<Props extends object, Fallback = string | number> = {
18
+ [K in keyof Props]?: Props[K] | CSSValue<Fallback>;
19
+ } & {
20
+ [K in CSSVar]?: CSSValue<Fallback>;
21
+ };
22
+
23
+ type Nested<Props extends object> = CSSObject<Props> & {
24
+ [K in CSSSelector]?: Nested<Props>;
25
+ };
26
+
27
+ export type CSSProps = Nested<Properties>;
@@ -0,0 +1,6 @@
1
+ export type * from "./css.ts";
2
+ export { Styling, styling } from "./styling.ts";
3
+ export { Token } from "./token.ts";
4
+ export type { CSSVarRef, TokenVars, Tokens } from "./token.ts";
5
+ export { styled, styleOf } from "./styled.tsx";
6
+ export type { StyledComponent } from "./styled.tsx";
@@ -0,0 +1,56 @@
1
+ // 轻量 styled:intrinsic / 组件 + ref,样式走 data-css 属性(不做 polymorphic,
2
+ // 参考实现里的 asChild 由 ark-ui 提供,我们不需要)。
3
+
4
+ import { createElement, forwardRef, useMemo } from "react";
5
+ import type {
6
+ ComponentProps,
7
+ ComponentPropsWithRef,
8
+ ElementType,
9
+ ForwardRefExoticComponent,
10
+ Ref,
11
+ RefAttributes,
12
+ } from "react";
13
+ import type { CSSProps } from "./css.ts";
14
+ import { styling } from "./styling.ts";
15
+ import { toMerged } from "./toolkit.ts";
16
+
17
+ type StyledProps<T extends ElementType> = Omit<ComponentProps<T>, "ref">;
18
+ type StyledRef<T extends ElementType> =
19
+ ComponentPropsWithRef<T>["ref"] extends Ref<infer R> ? R : never;
20
+
21
+ const sxSymbol: unique symbol = Symbol("styled.sx");
22
+
23
+ export type StyledComponent<T extends ElementType> = ForwardRefExoticComponent<
24
+ StyledProps<T> & RefAttributes<StyledRef<T>>
25
+ > & { readonly [sxSymbol]: CSSProps };
26
+
27
+ /**
28
+ * `styled('div')({ padding: '4px' }, { '&:hover': { … } })` → 带 `data-css-*` 的组件。
29
+ * 多个样式对象按顺序深合并(后者覆盖前者),便于把变体叠在基样式上。
30
+ */
31
+ export function styled<T extends ElementType>(
32
+ Component: T,
33
+ defaultProps: Partial<ComponentProps<T>> = {},
34
+ ) {
35
+ return (...styles: CSSProps[]): StyledComponent<T> => {
36
+ const sx = styles.reduce<CSSProps>((merged, style) => toMerged(merged, style), {});
37
+
38
+ const Styled = forwardRef<StyledRef<T>, StyledProps<T>>((props, ref) => {
39
+ const styleProps = useMemo(() => styling.props(sx), []);
40
+ return createElement(Component as ElementType, {
41
+ ...defaultProps,
42
+ ...props,
43
+ ...styleProps,
44
+ ref,
45
+ });
46
+ });
47
+ Styled.displayName = `styled(${typeof Component === "string" ? Component : (Component.displayName ?? Component.name ?? "Component")})`;
48
+
49
+ return Object.assign(Styled, { [sxSymbol]: sx }) as StyledComponent<T>;
50
+ };
51
+ }
52
+
53
+ /** 取回 styled 组件携带的样式对象(需要在其之上继续组合时用)。 */
54
+ export function styleOf(component: StyledComponent<ElementType>): CSSProps {
55
+ return component[sxSymbol];
56
+ }