@pindoba/core-code 0.0.0-alpha-20260829223243
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.
- package/LICENSE +12 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +2 -0
- package/dist/src/connect-code.d.ts +32 -0
- package/dist/src/connect-code.d.ts.map +1 -0
- package/dist/src/connect-code.js +200 -0
- package/dist/src/connect-code.js.map +1 -0
- package/dist/src/shiki.d.ts +37 -0
- package/dist/src/shiki.d.ts.map +1 -0
- package/dist/src/shiki.js +44 -0
- package/dist/src/shiki.js.map +1 -0
- package/dist/src/types.d.ts +185 -0
- package/dist/src/types.d.ts.map +1 -0
- package/dist/src/types.js +1 -0
- package/package.json +52 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
Copyright 2025 Eduardo Lopes
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
4
|
+
|
|
5
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
6
|
+
|
|
7
|
+
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
|
8
|
+
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
|
9
|
+
PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
|
|
10
|
+
FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
|
11
|
+
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
12
|
+
DEALINGS IN THE SOFTWARE.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { CodeApi, CodeBaseProps, CodeFeedback, CodeLine, CodeLineItem, CodePassThrough, CodeSize, CodeSlotPassThrough, CodeSlots, CodeToken, CodeTokenItem, ConnectCodeOptions, ElementAttributes, PanelElement, PanelRootProps } from "./src/types.js";
|
|
2
|
+
import { codeDefaults, connectCode } from "./src/connect-code.js";
|
|
3
|
+
export { type CodeApi, type CodeBaseProps, type CodeFeedback, type CodeLine, type CodeLineItem, type CodePassThrough, type CodeSize, type CodeSlotPassThrough, type CodeSlots, type CodeToken, type CodeTokenItem, type ConnectCodeOptions, type ElementAttributes, type PanelElement, type PanelRootProps, codeDefaults, connectCode };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { CodeApi, ConnectCodeOptions } from "./types.js";
|
|
2
|
+
//#region src/connect-code.d.ts
|
|
3
|
+
declare const codeDefaults: {
|
|
4
|
+
readonly size: "sm";
|
|
5
|
+
readonly feedback: "neutral";
|
|
6
|
+
readonly wrap: false;
|
|
7
|
+
readonly showLineNumbers: false;
|
|
8
|
+
readonly startLine: 1;
|
|
9
|
+
readonly collapsible: false;
|
|
10
|
+
readonly expanded: false;
|
|
11
|
+
readonly copyable: false;
|
|
12
|
+
readonly copied: false;
|
|
13
|
+
readonly label: "Code block";
|
|
14
|
+
readonly emphasis: "secondary";
|
|
15
|
+
readonly background: "surface.hill";
|
|
16
|
+
readonly padding: "none";
|
|
17
|
+
readonly radius: "sm";
|
|
18
|
+
readonly border: "muted";
|
|
19
|
+
readonly shadow: "none";
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* Connect Contract v1.1 — the `Code` bags.
|
|
23
|
+
*
|
|
24
|
+
* Highlighting is somebody else's job: this takes already-tokenized `lines` and
|
|
25
|
+
* turns them into spreadable bags. With no `lines` it falls back to rendering
|
|
26
|
+
* `code` as plain text, so a block without a highlighter is still readable
|
|
27
|
+
* rather than empty.
|
|
28
|
+
*/
|
|
29
|
+
declare function connectCode(options?: ConnectCodeOptions): CodeApi;
|
|
30
|
+
//#endregion
|
|
31
|
+
export { codeDefaults, connectCode };
|
|
32
|
+
//# sourceMappingURL=connect-code.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"connect-code.d.ts","names":[],"sources":["../../src/connect-code.ts"],"mappings":";;cAaa;;;;;;;;;;;;;;;;;;;;;;;;;;iBAyDG,YAAY,UAAS,qBAA0B"}
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import { css, cx } from "@pindoba/styled-system/css";
|
|
2
|
+
import { connectPanel } from "@pindoba/core-panel";
|
|
3
|
+
import { defineConnect } from "@pindoba/normalize-props";
|
|
4
|
+
import { codeStyles } from "@pindoba/styles-code";
|
|
5
|
+
//#region src/connect-code.ts
|
|
6
|
+
const codeDefaults = {
|
|
7
|
+
size: "sm",
|
|
8
|
+
feedback: "neutral",
|
|
9
|
+
wrap: false,
|
|
10
|
+
showLineNumbers: false,
|
|
11
|
+
startLine: 1,
|
|
12
|
+
collapsible: false,
|
|
13
|
+
expanded: false,
|
|
14
|
+
copyable: false,
|
|
15
|
+
copied: false,
|
|
16
|
+
label: "Code block",
|
|
17
|
+
emphasis: "secondary",
|
|
18
|
+
background: "surface.hill",
|
|
19
|
+
padding: "none",
|
|
20
|
+
radius: "sm",
|
|
21
|
+
border: "muted",
|
|
22
|
+
shadow: "none"
|
|
23
|
+
};
|
|
24
|
+
/** Split raw source into single-token lines — the unhighlighted fallback. */
|
|
25
|
+
function linesFromSource(code) {
|
|
26
|
+
return (code.endsWith("\n") ? code.slice(0, -1) : code).split("\n").map((text) => ({ tokens: [{ text }] }));
|
|
27
|
+
}
|
|
28
|
+
/** A token's per-instance theme styling, as an inline CSS string. */
|
|
29
|
+
function tokenStyle(token) {
|
|
30
|
+
return [token.color != null ? `color:${token.color}` : "", token.fontStyle != null ? `font-style:${token.fontStyle}` : ""].filter(Boolean).join(";");
|
|
31
|
+
}
|
|
32
|
+
/** Reconstruct the plain text of tokenized lines, for the clipboard. */
|
|
33
|
+
function sourceFromLines(lines) {
|
|
34
|
+
return lines.map((line) => line.tokens.map((t) => t.text).join("")).join("\n");
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Connect Contract v1.1 — the `Code` bags.
|
|
38
|
+
*
|
|
39
|
+
* Highlighting is somebody else's job: this takes already-tokenized `lines` and
|
|
40
|
+
* turns them into spreadable bags. With no `lines` it falls back to rendering
|
|
41
|
+
* `code` as plain text, so a block without a highlighter is still readable
|
|
42
|
+
* rather than empty.
|
|
43
|
+
*/
|
|
44
|
+
function connectCode(options = {}) {
|
|
45
|
+
const { lines, code = "", lang, size = codeDefaults.size, feedback = codeDefaults.feedback, wrap = codeDefaults.wrap, showLineNumbers = codeDefaults.showLineNumbers, startLine = codeDefaults.startLine, highlightLines, maxLines, collapsible = codeDefaults.collapsible, expanded = codeDefaults.expanded, copyable = codeDefaults.copyable, copied = codeDefaults.copied, label = codeDefaults.label, as = "div", background = codeDefaults.background, emphasis = codeDefaults.emphasis, translucent, padding = codeDefaults.padding, radius = codeDefaults.radius, radiusTop, radiusBottom, radiusLeft, radiusRight, border = codeDefaults.border, borderInteract, borderActive, reserveBorderSpace, shadow = codeDefaults.shadow, interactive, active, activeEmphasis, reactive, passThrough, class: className, ...rest } = options;
|
|
46
|
+
const resolvedLines = lines ?? (code ? linesFromSource(code) : []);
|
|
47
|
+
const lineCount = resolvedLines.length;
|
|
48
|
+
const overflowable = maxLines != null && maxLines > 0 && lineCount > maxLines;
|
|
49
|
+
const clamped = overflowable && !expanded;
|
|
50
|
+
const classes = codeStyles.raw({
|
|
51
|
+
size,
|
|
52
|
+
wrap,
|
|
53
|
+
showLineNumbers
|
|
54
|
+
});
|
|
55
|
+
const highlighted = new Set(highlightLines ?? []);
|
|
56
|
+
const items = resolvedLines.map((line, index) => {
|
|
57
|
+
const number = startLine + index;
|
|
58
|
+
const isHighlighted = highlighted.has(number);
|
|
59
|
+
return {
|
|
60
|
+
id: `line-${number}`,
|
|
61
|
+
number,
|
|
62
|
+
marker: line.marker,
|
|
63
|
+
lineProps: {
|
|
64
|
+
"data-line": String(number),
|
|
65
|
+
"data-highlighted": isHighlighted ? "true" : "false",
|
|
66
|
+
class: css(classes.line, passThrough?.line?.style)
|
|
67
|
+
},
|
|
68
|
+
lineNumberProps: {
|
|
69
|
+
"aria-hidden": "true",
|
|
70
|
+
class: css(classes.lineNumber, passThrough?.lineNumber?.style)
|
|
71
|
+
},
|
|
72
|
+
lineContentProps: { class: css(classes.lineContent, passThrough?.lineContent?.style) },
|
|
73
|
+
...line.marker != null ? { markerProps: {
|
|
74
|
+
"aria-hidden": "true",
|
|
75
|
+
class: css(classes.marker, passThrough?.marker?.style)
|
|
76
|
+
} } : {},
|
|
77
|
+
tokens: line.tokens.map((token, tokenIndex) => ({
|
|
78
|
+
id: `line-${number}-token-${tokenIndex}`,
|
|
79
|
+
text: token.text,
|
|
80
|
+
tokenProps: {
|
|
81
|
+
...tokenStyle(token) ? { style: tokenStyle(token) } : {},
|
|
82
|
+
class: cx(css(classes.token, passThrough?.token?.style), token.className)
|
|
83
|
+
}
|
|
84
|
+
}))
|
|
85
|
+
};
|
|
86
|
+
});
|
|
87
|
+
const copyText = lines ? sourceFromLines(lines) : code;
|
|
88
|
+
const showToolbar = Boolean(lang) || copyable;
|
|
89
|
+
const showExpand = Boolean(collapsible && overflowable);
|
|
90
|
+
const panelApi = connectPanel({
|
|
91
|
+
as,
|
|
92
|
+
dataComponent: "code",
|
|
93
|
+
background,
|
|
94
|
+
emphasis,
|
|
95
|
+
feedback,
|
|
96
|
+
translucent,
|
|
97
|
+
padding,
|
|
98
|
+
radius,
|
|
99
|
+
radiusTop,
|
|
100
|
+
radiusBottom,
|
|
101
|
+
radiusLeft,
|
|
102
|
+
radiusRight,
|
|
103
|
+
border,
|
|
104
|
+
borderInteract,
|
|
105
|
+
borderActive,
|
|
106
|
+
reserveBorderSpace,
|
|
107
|
+
shadow,
|
|
108
|
+
interactive,
|
|
109
|
+
active,
|
|
110
|
+
activeEmphasis,
|
|
111
|
+
reactive,
|
|
112
|
+
inheritedStyle: classes.root,
|
|
113
|
+
passThrough: { root: {
|
|
114
|
+
style: passThrough?.root?.style,
|
|
115
|
+
props: {
|
|
116
|
+
...passThrough?.root?.props,
|
|
117
|
+
"data-component": "code",
|
|
118
|
+
"data-lang": lang ?? "",
|
|
119
|
+
"data-clamped": clamped ? "true" : "false",
|
|
120
|
+
...maxLines != null ? { style: `--code-max-lines:${maxLines}` } : {}
|
|
121
|
+
}
|
|
122
|
+
} },
|
|
123
|
+
class: className,
|
|
124
|
+
...rest
|
|
125
|
+
});
|
|
126
|
+
return {
|
|
127
|
+
...defineConnect({
|
|
128
|
+
slots: {
|
|
129
|
+
root: { class: "" },
|
|
130
|
+
toolbar: {
|
|
131
|
+
"data-code-toolbar": "",
|
|
132
|
+
class: css(classes.toolbar, passThrough?.toolbar?.style)
|
|
133
|
+
},
|
|
134
|
+
language: {
|
|
135
|
+
"data-code-language": "",
|
|
136
|
+
class: css(classes.language, passThrough?.language?.style)
|
|
137
|
+
},
|
|
138
|
+
copyButton: {
|
|
139
|
+
"data-code-copy": "",
|
|
140
|
+
type: "button",
|
|
141
|
+
emphasis: "ghost",
|
|
142
|
+
size: "xs",
|
|
143
|
+
"aria-label": copied ? "Copied" : "Copy code",
|
|
144
|
+
"data-copied": copied ? "true" : "false",
|
|
145
|
+
class: css(classes.copyButton, passThrough?.copyButton?.style)
|
|
146
|
+
},
|
|
147
|
+
scroller: {
|
|
148
|
+
"data-code-scroller": "",
|
|
149
|
+
tabindex: 0,
|
|
150
|
+
role: "region",
|
|
151
|
+
"aria-label": label,
|
|
152
|
+
class: css(classes.scroller, passThrough?.scroller?.style)
|
|
153
|
+
},
|
|
154
|
+
pre: { class: css(classes.pre, passThrough?.pre?.style) },
|
|
155
|
+
codeEl: {
|
|
156
|
+
...lang ? { "data-language": lang } : {},
|
|
157
|
+
class: css(classes.codeEl, passThrough?.codeEl?.style)
|
|
158
|
+
},
|
|
159
|
+
lineNumbers: {
|
|
160
|
+
"aria-hidden": "true",
|
|
161
|
+
class: css(classes.lineNumbers, passThrough?.lineNumbers?.style)
|
|
162
|
+
},
|
|
163
|
+
line: { class: css(classes.line, passThrough?.line?.style) },
|
|
164
|
+
lineNumber: { class: css(classes.lineNumber, passThrough?.lineNumber?.style) },
|
|
165
|
+
lineContent: { class: css(classes.lineContent, passThrough?.lineContent?.style) },
|
|
166
|
+
token: { class: css(classes.token, passThrough?.token?.style) },
|
|
167
|
+
marker: { class: css(classes.marker, passThrough?.marker?.style) },
|
|
168
|
+
overflowFade: {
|
|
169
|
+
"data-code-fade": "",
|
|
170
|
+
"aria-hidden": "true",
|
|
171
|
+
class: css(classes.overflowFade, passThrough?.overflowFade?.style)
|
|
172
|
+
},
|
|
173
|
+
expandButton: {
|
|
174
|
+
"data-code-expand": "",
|
|
175
|
+
type: "button",
|
|
176
|
+
emphasis: "secondary",
|
|
177
|
+
size: "xs",
|
|
178
|
+
"aria-expanded": expanded ? "true" : "false",
|
|
179
|
+
class: css(classes.expandButton, passThrough?.expandButton?.style)
|
|
180
|
+
}
|
|
181
|
+
},
|
|
182
|
+
passThrough: {
|
|
183
|
+
...passThrough,
|
|
184
|
+
root: void 0
|
|
185
|
+
}
|
|
186
|
+
}),
|
|
187
|
+
rootProps: panelApi.rootProps,
|
|
188
|
+
element: panelApi.element,
|
|
189
|
+
items,
|
|
190
|
+
copyText,
|
|
191
|
+
showToolbar,
|
|
192
|
+
showExpand,
|
|
193
|
+
overflowable,
|
|
194
|
+
lineCount
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
//#endregion
|
|
198
|
+
export { codeDefaults, connectCode };
|
|
199
|
+
|
|
200
|
+
//# sourceMappingURL=connect-code.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"connect-code.js","names":[],"sources":["../../src/connect-code.ts"],"sourcesContent":["import { css, cx } from \"@pindoba/styled-system/css\";\nimport { connectPanel } from \"@pindoba/core-panel\";\nimport { defineConnect } from \"@pindoba/normalize-props\";\nimport { codeStyles } from \"@pindoba/styles-code\";\nimport type {\n CodeApi,\n CodeBaseProps,\n CodeLine,\n CodeLineItem,\n CodeToken,\n ConnectCodeOptions,\n} from \"./types.js\";\n\nexport const codeDefaults = {\n size: \"sm\",\n feedback: \"neutral\",\n wrap: false,\n showLineNumbers: false,\n startLine: 1,\n collapsible: false,\n expanded: false,\n copyable: false,\n copied: false,\n label: \"Code block\",\n // --- Panel surface defaults ---\n // The root is a Panel; these reproduce the surface the recipe used to paint\n // by hand (`colorPalette.surface.hill` + a 1px muted border + an `sm`\n // corner). `padding: \"none\"` because the block's inset lives on the toolbar /\n // scroller, not the root — so no `--panel-out-padding` republish is needed.\n emphasis: \"secondary\",\n background: \"surface.hill\",\n padding: \"none\",\n radius: \"sm\",\n border: \"muted\",\n shadow: \"none\",\n} as const satisfies Partial<CodeBaseProps>;\n\n/** Split raw source into single-token lines — the unhighlighted fallback. */\nfunction linesFromSource(code: string): CodeLine[] {\n // A trailing newline is a line terminator, not an empty final line; keeping\n // it would render a stray blank row under every file.\n const body = code.endsWith(\"\\n\") ? code.slice(0, -1) : code;\n return body.split(\"\\n\").map((text) => ({ tokens: [{ text }] }));\n}\n\n/** A token's per-instance theme styling, as an inline CSS string. */\nfunction tokenStyle(token: CodeToken): string {\n return [\n token.color != null ? `color:${token.color}` : \"\",\n token.fontStyle != null ? `font-style:${token.fontStyle}` : \"\",\n ]\n .filter(Boolean)\n .join(\";\");\n}\n\n/** Reconstruct the plain text of tokenized lines, for the clipboard. */\nfunction sourceFromLines(lines: CodeLine[]): string {\n return lines\n .map((line) => line.tokens.map((t) => t.text).join(\"\"))\n .join(\"\\n\");\n}\n\n/**\n * Connect Contract v1.1 — the `Code` bags.\n *\n * Highlighting is somebody else's job: this takes already-tokenized `lines` and\n * turns them into spreadable bags. With no `lines` it falls back to rendering\n * `code` as plain text, so a block without a highlighter is still readable\n * rather than empty.\n */\nexport function connectCode(options: ConnectCodeOptions = {}): CodeApi {\n const {\n lines,\n code = \"\",\n lang,\n size = codeDefaults.size,\n feedback = codeDefaults.feedback,\n wrap = codeDefaults.wrap,\n showLineNumbers = codeDefaults.showLineNumbers,\n startLine = codeDefaults.startLine,\n highlightLines,\n maxLines,\n collapsible = codeDefaults.collapsible,\n expanded = codeDefaults.expanded,\n copyable = codeDefaults.copyable,\n copied = codeDefaults.copied,\n label = codeDefaults.label,\n // --- Panel surface axes (delegated to connectPanel) ---\n as = \"div\",\n background = codeDefaults.background,\n emphasis = codeDefaults.emphasis,\n translucent,\n padding = codeDefaults.padding,\n radius = codeDefaults.radius,\n radiusTop,\n radiusBottom,\n radiusLeft,\n radiusRight,\n border = codeDefaults.border,\n borderInteract,\n borderActive,\n reserveBorderSpace,\n shadow = codeDefaults.shadow,\n interactive,\n active,\n activeEmphasis,\n reactive,\n passThrough,\n class: className,\n ...rest\n } = options;\n\n const resolvedLines: CodeLine[] =\n lines ?? (code ? linesFromSource(code) : []);\n const lineCount = resolvedLines.length;\n\n // Clamp only when there is genuinely more than fits — a `maxLines` larger\n // than the content must not paint a fade over nothing.\n const overflowable = maxLines != null && maxLines > 0 && lineCount > maxLines;\n const clamped = overflowable && !expanded;\n\n const classes = codeStyles.raw({\n size,\n wrap,\n showLineNumbers,\n });\n const highlighted = new Set(highlightLines ?? []);\n\n const items: CodeLineItem[] = resolvedLines.map((line, index) => {\n const number = startLine + index;\n const isHighlighted = highlighted.has(number);\n return {\n id: `line-${number}`,\n number,\n marker: line.marker,\n lineProps: {\n \"data-line\": String(number),\n \"data-highlighted\": isHighlighted ? \"true\" : \"false\",\n class: css(classes.line, passThrough?.line?.style),\n },\n lineNumberProps: {\n // The number is decoration duplicated by `data-line`; announcing it\n // before every line would bury the code itself.\n \"aria-hidden\": \"true\",\n class: css(classes.lineNumber, passThrough?.lineNumber?.style),\n },\n lineContentProps: {\n class: css(classes.lineContent, passThrough?.lineContent?.style),\n },\n ...(line.marker != null\n ? {\n markerProps: {\n \"aria-hidden\": \"true\",\n class: css(classes.marker, passThrough?.marker?.style),\n },\n }\n : {}),\n tokens: line.tokens.map((token, tokenIndex) => ({\n id: `line-${number}-token-${tokenIndex}`,\n text: token.text,\n tokenProps: {\n // Theme colour arrives per token and is inherently per-instance, so\n // it stays an inline style (which the contract allowlists) rather\n // than becoming an unbounded set of generated classes.\n //\n // A **string**, not an object: that is what connect bags emit\n // (`reactProps` converts it for React; Vue accepts either), and\n // Svelte drops most properties of a spread style object.\n ...(tokenStyle(token) ? { style: tokenStyle(token) } : {}),\n class: cx(\n css(classes.token, passThrough?.token?.style),\n token.className,\n ),\n },\n })),\n };\n });\n\n const copyText = lines ? sourceFromLines(lines) : code;\n const showToolbar = Boolean(lang) || copyable;\n const showExpand = Boolean(collapsible && overflowable);\n\n // The root's surface (background / border / corner) belongs to Panel; the\n // recipe keeps only layout + typography and rides the `inheritedStyle`\n // channel, so the compose order stays panel recipe → code recipe → consumer\n // `passThrough.root.style` → literal `class`. `feedback` reaches the recipe's\n // `colorPalette.*` reads through the panel's own feedback variant.\n const panelApi = connectPanel({\n as,\n dataComponent: \"code\",\n background,\n emphasis,\n feedback,\n translucent,\n padding,\n radius,\n radiusTop,\n radiusBottom,\n radiusLeft,\n radiusRight,\n border,\n borderInteract,\n borderActive,\n reserveBorderSpace,\n shadow,\n interactive,\n active,\n activeEmphasis,\n reactive,\n inheritedStyle: classes.root,\n passThrough: {\n root: {\n style: passThrough?.root?.style,\n props: {\n // The consumer's overrides go FIRST so everything below stays an\n // invariant, exactly as when these were baked after the rest spread:\n // the Astro boot script keys its copy/expand wiring off\n // `data-component=\"code\"` + `data-clamped`, and the recipe reads the\n // clamp height from the inline `--code-max-lines`. Extra root styling\n // belongs on `passThrough.root.style` (the Panda channel), which is\n // merged into the class rather than fighting this attribute.\n ...passThrough?.root?.props,\n \"data-component\": \"code\",\n \"data-lang\": lang ?? \"\",\n \"data-clamped\": clamped ? \"true\" : \"false\",\n ...(maxLines != null\n ? { style: `--code-max-lines:${maxLines}` }\n : {}),\n },\n },\n },\n class: className,\n ...rest,\n });\n\n const api = defineConnect({\n slots: {\n // Placeholder: the panel owns the real root bag (including its\n // `passThrough.root` merge, where the invariants sit ON TOP of the\n // consumer's props). `defineConnect`'s slot map is keyed by the recipe's\n // slot union so `root` has to be present, but the value below is\n // discarded — `rootProps` is replaced with `panelApi.rootProps` in the\n // returned api. `passThrough.root` is withheld here so the consumer's\n // props are not re-applied last and allowed to clobber the markers.\n root: { class: \"\" },\n toolbar: {\n \"data-code-toolbar\": \"\",\n class: css(classes.toolbar, passThrough?.toolbar?.style),\n },\n language: {\n \"data-code-language\": \"\",\n class: css(classes.language, passThrough?.language?.style),\n },\n copyButton: {\n \"data-code-copy\": \"\",\n type: \"button\",\n emphasis: \"ghost\" as const,\n size: \"xs\" as const,\n // The label carries the state, so a screen reader hears the result of\n // the press rather than a button whose name never changes.\n \"aria-label\": copied ? \"Copied\" : \"Copy code\",\n \"data-copied\": copied ? \"true\" : \"false\",\n class: css(classes.copyButton, passThrough?.copyButton?.style),\n },\n scroller: {\n \"data-code-scroller\": \"\",\n // A horizontally scrollable region must be keyboard-reachable, or the\n // overflow is unreadable without a pointer.\n tabindex: 0,\n role: \"region\",\n \"aria-label\": label,\n class: css(classes.scroller, passThrough?.scroller?.style),\n },\n pre: {\n class: css(classes.pre, passThrough?.pre?.style),\n },\n codeEl: {\n ...(lang ? { \"data-language\": lang } : {}),\n class: css(classes.codeEl, passThrough?.codeEl?.style),\n },\n lineNumbers: {\n \"aria-hidden\": \"true\",\n class: css(classes.lineNumbers, passThrough?.lineNumbers?.style),\n },\n line: {\n class: css(classes.line, passThrough?.line?.style),\n },\n lineNumber: {\n class: css(classes.lineNumber, passThrough?.lineNumber?.style),\n },\n lineContent: {\n class: css(classes.lineContent, passThrough?.lineContent?.style),\n },\n token: {\n class: css(classes.token, passThrough?.token?.style),\n },\n marker: {\n class: css(classes.marker, passThrough?.marker?.style),\n },\n overflowFade: {\n \"data-code-fade\": \"\",\n \"aria-hidden\": \"true\",\n class: css(classes.overflowFade, passThrough?.overflowFade?.style),\n },\n expandButton: {\n \"data-code-expand\": \"\",\n type: \"button\",\n emphasis: \"secondary\" as const,\n size: \"xs\" as const,\n \"aria-expanded\": expanded ? \"true\" : \"false\",\n class: css(classes.expandButton, passThrough?.expandButton?.style),\n },\n },\n passThrough: { ...passThrough, root: undefined },\n });\n\n return {\n ...api,\n rootProps: panelApi.rootProps,\n element: panelApi.element,\n items,\n copyText,\n showToolbar,\n showExpand,\n overflowable,\n lineCount,\n };\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { CodeLine } from "./types.js";
|
|
2
|
+
//#region src/shiki.d.ts
|
|
3
|
+
/** The shape of a Shiki themed token — structurally, not by import. */
|
|
4
|
+
interface ShikiThemedToken {
|
|
5
|
+
content: string;
|
|
6
|
+
color?: string;
|
|
7
|
+
/** Shiki's font-style bitfield: 1 italic, 2 bold, 4 underline. */
|
|
8
|
+
fontStyle?: number;
|
|
9
|
+
htmlStyle?: Record<string, string> | string;
|
|
10
|
+
}
|
|
11
|
+
/** Shiki's `codeToTokens()` result, narrowed to what the adapter reads. */
|
|
12
|
+
interface ShikiTokensResult {
|
|
13
|
+
tokens: ShikiThemedToken[][];
|
|
14
|
+
}
|
|
15
|
+
/** Translate Shiki's bitfield into a CSS declaration string. */
|
|
16
|
+
declare function shikiFontStyle(fontStyle: number | undefined): string | undefined;
|
|
17
|
+
/**
|
|
18
|
+
* Convert `codeToTokens()` output into the `lines` a `Code` component takes.
|
|
19
|
+
*
|
|
20
|
+
* ```ts
|
|
21
|
+
* import { codeToTokens } from "shiki";
|
|
22
|
+
* import { shikiToLines } from "@pindoba/core-code/shiki";
|
|
23
|
+
*
|
|
24
|
+
* const lines = shikiToLines(
|
|
25
|
+
* await codeToTokens(source, { lang: "ts", theme: "github-dark" }),
|
|
26
|
+
* );
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
declare function shikiToLines(result: ShikiTokensResult): CodeLine[];
|
|
30
|
+
/**
|
|
31
|
+
* Same conversion from a bare token grid, for callers holding
|
|
32
|
+
* `result.tokens` rather than the whole result object.
|
|
33
|
+
*/
|
|
34
|
+
declare function shikiTokensToLines(tokens: ShikiThemedToken[][]): CodeLine[];
|
|
35
|
+
//#endregion
|
|
36
|
+
export { ShikiThemedToken, ShikiTokensResult, shikiFontStyle, shikiToLines, shikiTokensToLines };
|
|
37
|
+
//# sourceMappingURL=shiki.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shiki.d.ts","names":[],"sources":["../../src/shiki.ts"],"mappings":";;;UAgBiB;EACf;EACA;;EAEA;EACA,YAAY;;;UAIG;EACf,QAAQ;;;iBASM,eACd;;;;;;;;;;;;;iBAgCc,aAAa,QAAQ,oBAAoB;;;;;iBAQzC,mBAAmB,QAAQ,uBAAuB"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
//#region src/shiki.ts
|
|
2
|
+
const ITALIC = 1;
|
|
3
|
+
/** Translate Shiki's bitfield into a CSS declaration string. */
|
|
4
|
+
function shikiFontStyle(fontStyle) {
|
|
5
|
+
if (!fontStyle) return void 0;
|
|
6
|
+
const parts = [];
|
|
7
|
+
if (fontStyle & ITALIC) parts.push("italic");
|
|
8
|
+
if (parts.length === 0 && fontStyle & 6) return void 0;
|
|
9
|
+
return parts.length > 0 ? parts.join(" ") : void 0;
|
|
10
|
+
}
|
|
11
|
+
function toToken(token) {
|
|
12
|
+
const fontStyle = shikiFontStyle(token.fontStyle);
|
|
13
|
+
return {
|
|
14
|
+
text: token.content,
|
|
15
|
+
...token.color != null ? { color: token.color } : {},
|
|
16
|
+
...fontStyle != null ? { fontStyle } : {}
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Convert `codeToTokens()` output into the `lines` a `Code` component takes.
|
|
21
|
+
*
|
|
22
|
+
* ```ts
|
|
23
|
+
* import { codeToTokens } from "shiki";
|
|
24
|
+
* import { shikiToLines } from "@pindoba/core-code/shiki";
|
|
25
|
+
*
|
|
26
|
+
* const lines = shikiToLines(
|
|
27
|
+
* await codeToTokens(source, { lang: "ts", theme: "github-dark" }),
|
|
28
|
+
* );
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
function shikiToLines(result) {
|
|
32
|
+
return result.tokens.map((tokens) => ({ tokens: tokens.map(toToken) }));
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Same conversion from a bare token grid, for callers holding
|
|
36
|
+
* `result.tokens` rather than the whole result object.
|
|
37
|
+
*/
|
|
38
|
+
function shikiTokensToLines(tokens) {
|
|
39
|
+
return tokens.map((row) => ({ tokens: row.map(toToken) }));
|
|
40
|
+
}
|
|
41
|
+
//#endregion
|
|
42
|
+
export { shikiFontStyle, shikiToLines, shikiTokensToLines };
|
|
43
|
+
|
|
44
|
+
//# sourceMappingURL=shiki.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shiki.js","names":[],"sources":["../../src/shiki.ts"],"sourcesContent":["/**\n * @fileoverview Optional Shiki adapter — converts Shiki output into `CodeLine[]`.\n *\n * A **separate entry point** (`@pindoba/core-code/shiki`) with `shiki` as an\n * optional peer dependency, so importing `Code` never pulls a highlighting\n * engine and its grammars into the bundle. Consumers that want highlighting opt\n * in explicitly; everyone else renders plain text and pays nothing.\n *\n * Nothing here imports Shiki either: the functions take Shiki's *output*. That\n * keeps the adapter usable with any Shiki version (and with a highlighter\n * running in a worker or on the server) without a version lock.\n */\n\nimport type { CodeLine, CodeToken } from \"./types.js\";\n\n/** The shape of a Shiki themed token — structurally, not by import. */\nexport interface ShikiThemedToken {\n content: string;\n color?: string;\n /** Shiki's font-style bitfield: 1 italic, 2 bold, 4 underline. */\n fontStyle?: number;\n htmlStyle?: Record<string, string> | string;\n}\n\n/** Shiki's `codeToTokens()` result, narrowed to what the adapter reads. */\nexport interface ShikiTokensResult {\n tokens: ShikiThemedToken[][];\n}\n\n// Shiki encodes font style as a bitfield rather than CSS.\nconst ITALIC = 1;\nconst BOLD = 2;\nconst UNDERLINE = 4;\n\n/** Translate Shiki's bitfield into a CSS declaration string. */\nexport function shikiFontStyle(\n fontStyle: number | undefined,\n): string | undefined {\n if (!fontStyle) return undefined;\n const parts: string[] = [];\n if (fontStyle & ITALIC) parts.push(\"italic\");\n // `font-style` cannot express weight or decoration; those are dropped rather\n // than silently mis-applied. Callers wanting them can theme via `className`.\n if (parts.length === 0 && fontStyle & (BOLD | UNDERLINE)) return undefined;\n return parts.length > 0 ? parts.join(\" \") : undefined;\n}\n\nfunction toToken(token: ShikiThemedToken): CodeToken {\n const fontStyle = shikiFontStyle(token.fontStyle);\n return {\n text: token.content,\n ...(token.color != null ? { color: token.color } : {}),\n ...(fontStyle != null ? { fontStyle } : {}),\n };\n}\n\n/**\n * Convert `codeToTokens()` output into the `lines` a `Code` component takes.\n *\n * ```ts\n * import { codeToTokens } from \"shiki\";\n * import { shikiToLines } from \"@pindoba/core-code/shiki\";\n *\n * const lines = shikiToLines(\n * await codeToTokens(source, { lang: \"ts\", theme: \"github-dark\" }),\n * );\n * ```\n */\nexport function shikiToLines(result: ShikiTokensResult): CodeLine[] {\n return result.tokens.map((tokens) => ({ tokens: tokens.map(toToken) }));\n}\n\n/**\n * Same conversion from a bare token grid, for callers holding\n * `result.tokens` rather than the whole result object.\n */\nexport function shikiTokensToLines(tokens: ShikiThemedToken[][]): CodeLine[] {\n return tokens.map((row) => ({ tokens: row.map(toToken) }));\n}\n"],"mappings":";AA8BA,MAAM,SAAS;;AAKf,SAAgB,eACd,WACoB;CACpB,IAAI,CAAC,WAAW,OAAO,KAAA;CACvB,MAAM,QAAkB,CAAC;CACzB,IAAI,YAAY,QAAQ,MAAM,KAAK,QAAQ;CAG3C,IAAI,MAAM,WAAW,KAAK,YAAa,GAAmB,OAAO,KAAA;CACjE,OAAO,MAAM,SAAS,IAAI,MAAM,KAAK,GAAG,IAAI,KAAA;AAC9C;AAEA,SAAS,QAAQ,OAAoC;CACnD,MAAM,YAAY,eAAe,MAAM,SAAS;CAChD,OAAO;EACL,MAAM,MAAM;EACZ,GAAI,MAAM,SAAS,OAAO,EAAE,OAAO,MAAM,MAAM,IAAI,CAAC;EACpD,GAAI,aAAa,OAAO,EAAE,UAAU,IAAI,CAAC;CAC3C;AACF;;;;;;;;;;;;;AAcA,SAAgB,aAAa,QAAuC;CAClE,OAAO,OAAO,OAAO,KAAK,YAAY,EAAE,QAAQ,OAAO,IAAI,OAAO,EAAE,EAAE;AACxE;;;;;AAMA,SAAgB,mBAAmB,QAA0C;CAC3E,OAAO,OAAO,KAAK,SAAS,EAAE,QAAQ,IAAI,IAAI,OAAO,EAAE,EAAE;AAC3D"}
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import { PanelElement, PanelElement as PanelElement$1, PanelInheritedProps, PanelRootProps, PanelRootProps as PanelRootProps$1 } from "@pindoba/core-panel";
|
|
2
|
+
import { ConnectReturn } from "@pindoba/normalize-props";
|
|
3
|
+
import { CodeSlots, CodeStyleProps } from "@pindoba/styles-code";
|
|
4
|
+
import { SystemStyleObject } from "@pindoba/styled-system/types";
|
|
5
|
+
import { PanelStyleProps } from "@pindoba/styles-panel";
|
|
6
|
+
//#region src/types.d.ts
|
|
7
|
+
type ElementAttributes = Partial<Record<string, unknown>>;
|
|
8
|
+
type CodeSize = NonNullable<CodeStyleProps["size"]>;
|
|
9
|
+
type CodeFeedback = NonNullable<PanelStyleProps["feedback"]>;
|
|
10
|
+
/**
|
|
11
|
+
* One coloured run of text within a line.
|
|
12
|
+
*
|
|
13
|
+
* This is the entire contract between `Code` and a syntax highlighter, and it
|
|
14
|
+
* is deliberately the smallest thing that can express highlighted output:
|
|
15
|
+
* `core-code` never imports a grammar, a theme, or a highlighting engine. Shiki
|
|
16
|
+
* alone is a multi-megabyte dependency with its own theme format, and baking it
|
|
17
|
+
* in would make every consumer of the design system pay for it whether or not
|
|
18
|
+
* they render code.
|
|
19
|
+
*
|
|
20
|
+
* `@pindoba/core-code/shiki` is an optional adapter that converts Shiki output
|
|
21
|
+
* into this shape; anything else that can produce `{ text, color }` runs works
|
|
22
|
+
* just as well, including a hand-rolled tokenizer or none at all.
|
|
23
|
+
*/
|
|
24
|
+
interface CodeToken {
|
|
25
|
+
text: string;
|
|
26
|
+
/** CSS colour for this run, applied inline (that is how themes ship). */
|
|
27
|
+
color?: string;
|
|
28
|
+
/** Extra class, for highlighters that theme via CSS rather than inline colour. */
|
|
29
|
+
className?: string;
|
|
30
|
+
/** `font-style` / `font-weight` a theme may set on a run. */
|
|
31
|
+
fontStyle?: string;
|
|
32
|
+
}
|
|
33
|
+
/** One rendered line: its tokens, plus any per-line decoration. */
|
|
34
|
+
interface CodeLine {
|
|
35
|
+
tokens: CodeToken[];
|
|
36
|
+
/** Optional gutter marker — `+`, `-`, `>` for a diff or a callout. */
|
|
37
|
+
marker?: string;
|
|
38
|
+
}
|
|
39
|
+
interface CodeSlotPassThrough<T = ElementAttributes> {
|
|
40
|
+
style?: SystemStyleObject;
|
|
41
|
+
props?: T;
|
|
42
|
+
}
|
|
43
|
+
type CodePassThrough<RootElementAttributes = ElementAttributes> = { [K in CodeSlots]?: CodeSlotPassThrough<K extends "root" ? RootElementAttributes : ElementAttributes>; };
|
|
44
|
+
interface CodeBaseProps<RootElementAttributes = ElementAttributes> extends Omit<PanelInheritedProps<RootElementAttributes>, "passThrough"> {
|
|
45
|
+
/**
|
|
46
|
+
* Pre-tokenized lines from a highlighter. When omitted, `code` is rendered
|
|
47
|
+
* as unhighlighted plain text — so the component degrades to a readable
|
|
48
|
+
* block rather than nothing.
|
|
49
|
+
* @group Content
|
|
50
|
+
*/
|
|
51
|
+
lines?: CodeLine[];
|
|
52
|
+
/**
|
|
53
|
+
* Raw source. Used verbatim when `lines` is absent, and always used as the
|
|
54
|
+
* text the copy button puts on the clipboard.
|
|
55
|
+
* @group Content
|
|
56
|
+
*/
|
|
57
|
+
code?: string;
|
|
58
|
+
/**
|
|
59
|
+
* Language label shown in the toolbar. Purely presentational — the component
|
|
60
|
+
* does no highlighting of its own.
|
|
61
|
+
* @group Content
|
|
62
|
+
*/
|
|
63
|
+
lang?: string;
|
|
64
|
+
/**
|
|
65
|
+
* Type scale of the block. `xs` and `sm` currently render at the same font
|
|
66
|
+
* size — the scale's smallest step is `xs`, so separating them means moving
|
|
67
|
+
* `sm`/`md` up, which is a deliberate visual change that hasn't been made.
|
|
68
|
+
* @default "sm"
|
|
69
|
+
* @group Appearance
|
|
70
|
+
*/
|
|
71
|
+
size?: CodeSize;
|
|
72
|
+
/**
|
|
73
|
+
* Wrap long lines instead of scrolling horizontally.
|
|
74
|
+
* @default false
|
|
75
|
+
* @group Layout
|
|
76
|
+
*/
|
|
77
|
+
wrap?: boolean;
|
|
78
|
+
/**
|
|
79
|
+
* Show a line number beside each line.
|
|
80
|
+
* @default false
|
|
81
|
+
* @group Layout
|
|
82
|
+
*/
|
|
83
|
+
showLineNumbers?: boolean;
|
|
84
|
+
/**
|
|
85
|
+
* Number the first line as this. Lets an excerpt report its real position in
|
|
86
|
+
* the file rather than restarting at 1.
|
|
87
|
+
* @default 1
|
|
88
|
+
* @group Layout
|
|
89
|
+
*/
|
|
90
|
+
startLine?: number;
|
|
91
|
+
/**
|
|
92
|
+
* Line numbers (in displayed terms, honouring `startLine`) to emphasise.
|
|
93
|
+
* @group Appearance
|
|
94
|
+
*/
|
|
95
|
+
highlightLines?: number[];
|
|
96
|
+
/**
|
|
97
|
+
* Clamp the block to this many lines, fading the overflow.
|
|
98
|
+
* @group Layout
|
|
99
|
+
*/
|
|
100
|
+
maxLines?: number;
|
|
101
|
+
/**
|
|
102
|
+
* Offer an expand control when clamped by `maxLines`.
|
|
103
|
+
* @default false
|
|
104
|
+
* @group Behavior
|
|
105
|
+
*/
|
|
106
|
+
collapsible?: boolean;
|
|
107
|
+
/**
|
|
108
|
+
* Whether the block is currently expanded past `maxLines`. Frameworks feed
|
|
109
|
+
* their reactive state in; the component is controlled.
|
|
110
|
+
* @default false
|
|
111
|
+
* @group Behavior
|
|
112
|
+
*/
|
|
113
|
+
expanded?: boolean;
|
|
114
|
+
/**
|
|
115
|
+
* Render a copy-to-clipboard button in the toolbar.
|
|
116
|
+
* @default false
|
|
117
|
+
* @group Behavior
|
|
118
|
+
*/
|
|
119
|
+
copyable?: boolean;
|
|
120
|
+
/**
|
|
121
|
+
* Whether the copy button is in its just-copied state. Frameworks own the
|
|
122
|
+
* timer that flips it back.
|
|
123
|
+
* @default false
|
|
124
|
+
* @group Behavior
|
|
125
|
+
*/
|
|
126
|
+
copied?: boolean;
|
|
127
|
+
/**
|
|
128
|
+
* Accessible label for the block.
|
|
129
|
+
* @default "Code block"
|
|
130
|
+
* @group Accessibility
|
|
131
|
+
*/
|
|
132
|
+
label?: string;
|
|
133
|
+
/**
|
|
134
|
+
* Per-slot style and HTML-attribute overrides.
|
|
135
|
+
* @group Advanced
|
|
136
|
+
*/
|
|
137
|
+
passThrough?: CodePassThrough<RootElementAttributes>;
|
|
138
|
+
}
|
|
139
|
+
interface ConnectCodeOptions extends Partial<CodeBaseProps<ElementAttributes>> {
|
|
140
|
+
class?: unknown;
|
|
141
|
+
[key: string]: unknown;
|
|
142
|
+
}
|
|
143
|
+
/** One token's spreadable bag, plus the text to render inside it. */
|
|
144
|
+
interface CodeTokenItem {
|
|
145
|
+
id: string;
|
|
146
|
+
text: string;
|
|
147
|
+
tokenProps: Record<string, unknown>;
|
|
148
|
+
}
|
|
149
|
+
/** One line's bags — Contract v1.1 Rule 3. */
|
|
150
|
+
interface CodeLineItem {
|
|
151
|
+
id: string;
|
|
152
|
+
/** Displayed line number, already offset by `startLine`. */
|
|
153
|
+
number: number;
|
|
154
|
+
marker?: string;
|
|
155
|
+
lineProps: Record<string, unknown>;
|
|
156
|
+
lineNumberProps: Record<string, unknown>;
|
|
157
|
+
lineContentProps: Record<string, unknown>;
|
|
158
|
+
markerProps?: Record<string, unknown>;
|
|
159
|
+
tokens: CodeTokenItem[];
|
|
160
|
+
}
|
|
161
|
+
type CodeApi = ConnectReturn<CodeSlots> & {
|
|
162
|
+
/** The panel-produced root bag (`data-component="code"` + the nest hook). */
|
|
163
|
+
rootProps: PanelRootProps$1;
|
|
164
|
+
/** Resolved root tag from `as` — templates render this element. */
|
|
165
|
+
element: PanelElement$1;
|
|
166
|
+
/** Per-line bags, in order. */
|
|
167
|
+
items: CodeLineItem[];
|
|
168
|
+
/** The exact text the copy button should write to the clipboard. */
|
|
169
|
+
copyText: string;
|
|
170
|
+
/** Whether a toolbar should be rendered at all. */
|
|
171
|
+
showToolbar: boolean;
|
|
172
|
+
/** Whether the expand affordance should be rendered. */
|
|
173
|
+
showExpand: boolean;
|
|
174
|
+
/**
|
|
175
|
+
* Whether the block has more lines than `maxLines`, so the overflow fade
|
|
176
|
+
* should be rendered. Distinct from `showExpand`: a non-`collapsible` block
|
|
177
|
+
* still clamps and still fades, it just offers no way out.
|
|
178
|
+
*/
|
|
179
|
+
overflowable: boolean;
|
|
180
|
+
/** Total line count, before any clamping. */
|
|
181
|
+
lineCount: number;
|
|
182
|
+
};
|
|
183
|
+
//#endregion
|
|
184
|
+
export { CodeApi, CodeBaseProps, CodeFeedback, CodeLine, CodeLineItem, CodePassThrough, CodeSize, CodeSlotPassThrough, type CodeSlots, CodeToken, CodeTokenItem, ConnectCodeOptions, ElementAttributes, type PanelElement, type PanelRootProps };
|
|
185
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","names":[],"sources":["../../src/types.ts"],"mappings":";;;;;;KAaY,oBAAoB,QAAQ;KAE5B,WAAW,YAAY;KACvB,eAAe,YAAY;;;;;;;;;;;;;;;UAkBtB;EACf;;EAEA;;EAEA;;EAEA;;;UAIe;EACf,QAAQ;;EAER;;UAKe,oBAAoB,IAAI;EACvC,QAAQ;EACR,QAAQ;;KAGE,gBAAgB,wBAAwB,wBACjD,KAAK,aAAa,oBACjB,mBAAmB,wBAAwB;UAM9B,cAAc,wBAAwB,2BAM7C,KAAK,oBAAoB;;;;;;;EAOjC,QAAQ;;;;;;EAMR;;;;;;EAMA;;;;;;;;EAQA,OAAO;;;;;;EAMP;;;;;;EAMA;;;;;;;EAOA;;;;;EAKA;;;;;EAKA;;;;;;EAMA;;;;;;;EAOA;;;;;;EAMA;;;;;;;EAOA;;;;;;EAMA;;;;;EAKA,cAAc,gBAAgB;;UAKf,2BAA2B,QAC1C,cAAc;EAEd;GACC;;;UAIc;EACf;EACA;EACA,YAAY;;;UAIG;EACf;;EAEA;EACA;EACA,WAAW;EACX,iBAAiB;EACjB,kBAAkB;EAClB,cAAc;EACd,QAAQ;;KAGE,UAAU,cAAc;;EAElC,WAAW;;EAEX,SAAS;;EAET,OAAO;;EAEP;;EAEA;;EAEA;;;;;;EAMA;;EAEA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@pindoba/core-code",
|
|
3
|
+
"version": "0.0.0-alpha-20260829223243",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"sideEffects": false,
|
|
7
|
+
"main": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.js"
|
|
13
|
+
},
|
|
14
|
+
"./shiki": {
|
|
15
|
+
"types": "./dist/src/shiki.d.ts",
|
|
16
|
+
"import": "./dist/src/shiki.js"
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"files": [
|
|
20
|
+
"dist"
|
|
21
|
+
],
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"@pindoba/core-panel": "0.0.0-alpha-20260829223243",
|
|
24
|
+
"@pindoba/normalize-props": "0.0.0-alpha-20260829223243",
|
|
25
|
+
"@pindoba/styled-system": "0.0.0-alpha-20260829223243",
|
|
26
|
+
"@pindoba/styles-code": "0.0.0-alpha-20260829223243",
|
|
27
|
+
"@pindoba/styles-panel": "0.0.0-alpha-20260829223243"
|
|
28
|
+
},
|
|
29
|
+
"devDependencies": {
|
|
30
|
+
"@pindoba/typescript-config": "0.0.0-alpha-20260320034942",
|
|
31
|
+
"tsdown": "^0.22.14",
|
|
32
|
+
"typescript": "^7.0.2"
|
|
33
|
+
},
|
|
34
|
+
"peerDependencies": {
|
|
35
|
+
"shiki": ">=1"
|
|
36
|
+
},
|
|
37
|
+
"peerDependenciesMeta": {
|
|
38
|
+
"shiki": {
|
|
39
|
+
"optional": true
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
"scripts": {
|
|
43
|
+
"build": "tsdown",
|
|
44
|
+
"dev": "tsdown --watch",
|
|
45
|
+
"clean": "rm -rf dist && rm -rf node_modules && rm -rf .turbo",
|
|
46
|
+
"check": "tsc --noEmit",
|
|
47
|
+
"lint": "TIMING=1 eslint . --max-warnings 0",
|
|
48
|
+
"lint:fix": "TIMING=1 eslint . --fix --max-warnings 0",
|
|
49
|
+
"format": "prettier --check . --ignore-path \"$PRETTIER_IGNORE_PATH\"",
|
|
50
|
+
"format:fix": "prettier --write . --ignore-path \"$PRETTIER_IGNORE_PATH\""
|
|
51
|
+
}
|
|
52
|
+
}
|