@getvitops/astro 0.2.0 → 0.3.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.
- package/dist/index.d.mts +2 -120
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +3 -181
- package/dist/index.mjs.map +1 -1
- package/package.json +26 -7
- package/src/components/Cards.astro +32 -0
- package/src/components/Details.astro +76 -0
- package/src/components/Drawer.astro +65 -0
- package/src/components/NodeRenderer.astro +27 -0
- package/src/components/Popover.astro +90 -0
- package/src/components/Subgrid.astro +32 -0
- package/src/components/WebComponentLoader.astro +48 -0
package/dist/index.d.mts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import { DefaultTreeAdapterTypes } from "parse5";
|
|
2
1
|
import { Format } from "@getvitops/generator";
|
|
3
2
|
import { AstroIntegration } from "astro";
|
|
4
|
-
|
|
3
|
+
export * from "@getvitops/utils";
|
|
5
4
|
|
|
6
5
|
//#region src/integration.d.ts
|
|
7
6
|
interface GetvitopsFaviconOptions {
|
|
@@ -31,122 +30,5 @@ interface GetvitopsOptions {
|
|
|
31
30
|
}
|
|
32
31
|
declare function getvitops(opts?: GetvitopsOptions): AstroIntegration;
|
|
33
32
|
//#endregion
|
|
34
|
-
|
|
35
|
-
/** A translatable value: a plain string, or a locale → string map. */
|
|
36
|
-
type Localizable = string | Record<string, string>;
|
|
37
|
-
/**
|
|
38
|
-
* An HTML attribute bag. `class` may be a string or a class:list-style array
|
|
39
|
-
* (falsy entries dropped); everything else is passed through to the element.
|
|
40
|
-
*/
|
|
41
|
-
interface Attrs {
|
|
42
|
-
class?: string | (string | false | null | undefined)[];
|
|
43
|
-
[attr: string]: unknown;
|
|
44
|
-
}
|
|
45
|
-
/** An image reference used by image links. */
|
|
46
|
-
interface ImageRef {
|
|
47
|
-
src: string;
|
|
48
|
-
alt?: Localizable;
|
|
49
|
-
[key: string]: unknown;
|
|
50
|
-
}
|
|
51
|
-
/** A link with a text label. */
|
|
52
|
-
interface TextLink {
|
|
53
|
-
label: Localizable;
|
|
54
|
-
href: string;
|
|
55
|
-
attrs?: Attrs;
|
|
56
|
-
[key: string]: unknown;
|
|
57
|
-
}
|
|
58
|
-
/** A link whose content is an image. */
|
|
59
|
-
interface ImageLink {
|
|
60
|
-
image: ImageRef;
|
|
61
|
-
href: string;
|
|
62
|
-
attrs?: Attrs;
|
|
63
|
-
[key: string]: unknown;
|
|
64
|
-
}
|
|
65
|
-
/** Any link — text or image. */
|
|
66
|
-
type Link = TextLink | ImageLink;
|
|
67
|
-
/**
|
|
68
|
-
* A generic content element wrapper (discriminated by `type: 'element'`),
|
|
69
|
-
* carrying a payload `T` rendered as `Tag`.
|
|
70
|
-
*/
|
|
71
|
-
interface Elmnt<T = unknown, Tag extends HTMLTag = 'div'> {
|
|
72
|
-
type: 'element';
|
|
73
|
-
tag?: Tag;
|
|
74
|
-
attrs?: Attrs;
|
|
75
|
-
content?: T;
|
|
76
|
-
[key: string]: unknown;
|
|
77
|
-
}
|
|
78
|
-
/**
|
|
79
|
-
* A renderable node tree — a text string (Localizable) or an element with
|
|
80
|
-
* optional attrs and either `text` shorthand or `children`.
|
|
81
|
-
*/
|
|
82
|
-
type ContentNode = string | {
|
|
83
|
-
tag: string;
|
|
84
|
-
attrs?: Attrs;
|
|
85
|
-
text?: Localizable;
|
|
86
|
-
children?: ContentNode[];
|
|
87
|
-
};
|
|
88
|
-
//#endregion
|
|
89
|
-
//#region src/html.d.ts
|
|
90
|
-
type ChildNode = DefaultTreeAdapterTypes.ChildNode;
|
|
91
|
-
declare function parseRenderedSlots(html: string): ChildNode[];
|
|
92
|
-
declare function getAttribute(node: ChildNode, attrName: string): string | null;
|
|
93
|
-
declare function serialize(nodes: ChildNode | ChildNode[]): string[];
|
|
94
|
-
/**
|
|
95
|
-
* Style value types that can be combined. StyleObject is structurally
|
|
96
|
-
* compatible with astroHTML.JSX.CSSProperties (camelCase or --custom-prop keys).
|
|
97
|
-
*/
|
|
98
|
-
type StyleObject = Record<string, string | number | null | undefined>;
|
|
99
|
-
type StyleValue = string | StyleObject | null | undefined;
|
|
100
|
-
type StyleList = StyleValue | StyleValue[];
|
|
101
|
-
/**
|
|
102
|
-
* Combine multiple style formats into a single CSS string
|
|
103
|
-
* Similar to Astro's class:list but for inline styles
|
|
104
|
-
*/
|
|
105
|
-
declare function styleList(...styles: StyleList[]): string;
|
|
106
|
-
/**
|
|
107
|
-
* Element descriptor matching the El interface from env.d.ts
|
|
108
|
-
*/
|
|
109
|
-
interface El<Tag extends HTMLTag = HTMLTag> {
|
|
110
|
-
text: string;
|
|
111
|
-
tag: Tag;
|
|
112
|
-
attributes?: HTMLAttributes<Tag>;
|
|
113
|
-
}
|
|
114
|
-
/**
|
|
115
|
-
* Normalize a string or El object into an HTML string for use with set:html
|
|
116
|
-
*/
|
|
117
|
-
declare function toHtml<Tag extends HTMLTag>(content: string | El<Tag>): string;
|
|
118
|
-
/**
|
|
119
|
-
* Render an array of ContentNodes to an HTML string.
|
|
120
|
-
*
|
|
121
|
-
* @param nodes - Array of element nodes and/or text strings
|
|
122
|
-
* @param resolveText - Optional function to resolve Localizable values.
|
|
123
|
-
* Defaults to returning strings as-is and first locale value for maps.
|
|
124
|
-
*/
|
|
125
|
-
declare function nodesToHtml(nodes: ContentNode[], resolveText?: (value: Localizable) => string): string;
|
|
126
|
-
//#endregion
|
|
127
|
-
//#region src/types.d.ts
|
|
128
|
-
declare function isElmnt<T, Tag extends HTMLTag = 'div'>(item: any): item is Elmnt<T, Tag>;
|
|
129
|
-
declare function isLink(item: any): item is Link;
|
|
130
|
-
declare function isTextLink(item: any): item is TextLink;
|
|
131
|
-
declare function isImageLink(item: any): item is ImageLink;
|
|
132
|
-
//#endregion
|
|
133
|
-
//#region src/parts.d.ts
|
|
134
|
-
/**
|
|
135
|
-
* Resolve per-part attrs from a presentation type config.
|
|
136
|
-
* For the 'root' part, merges legacy `attrs` with `parts.root` (backward compat).
|
|
137
|
-
*/
|
|
138
|
-
declare function partAttrs(present: {
|
|
139
|
-
attrs?: Attrs;
|
|
140
|
-
parts?: Record<string, Attrs>;
|
|
141
|
-
} | undefined, part: string): Attrs;
|
|
142
|
-
//#endregion
|
|
143
|
-
//#region src/i18n.d.ts
|
|
144
|
-
/**
|
|
145
|
-
* Resolve a Localizable value to a string for the given locale.
|
|
146
|
-
* If the value is a plain string, it is returned as-is (treated as defaultLocale).
|
|
147
|
-
* If it is a locale map, the best match is returned.
|
|
148
|
-
*/
|
|
149
|
-
declare function t(value: Localizable | undefined, locale: string, defaultLocale: string): string;
|
|
150
|
-
//#endregion
|
|
151
|
-
export { Attrs, ChildNode, ContentNode, El, Elmnt, type GetvitopsCssOptions, type GetvitopsFaviconOptions, type GetvitopsOptions, ImageLink, ImageRef, Link, Localizable, StyleList, StyleValue, TextLink, getvitops as default, getAttribute, isElmnt, isImageLink, isLink, isTextLink, nodesToHtml, parseRenderedSlots, partAttrs, serialize, styleList, t, toHtml };
|
|
33
|
+
export { type GetvitopsCssOptions, type GetvitopsFaviconOptions, type GetvitopsOptions, getvitops as default };
|
|
152
34
|
//# sourceMappingURL=index.d.mts.map
|
package/dist/index.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/integration.ts"
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/integration.ts"],"mappings":";;;;;UAuBiB,uBAAA;;EAEf,MAAA;EACA,YAAA;EAHe;EAKf,IAAA;;EAEA,UAAA;EACA,eAAA;AAAA;AAAA,UAGe,mBAAA;EAJf;EAMA,KAAA;EALe;EAOf,MAAA,GAAS,MAAM;EAJA;EAMf,GAAA;AAAA;AAAA,UAGe,gBAAA;EACf,OAAA,GAAU,uBAAA;EANV;EAQA,aAAA;EANA;EAQA,GAAA,GAAM,mBAAmB;AAAA;AAAA,iBA0BH,SAAA,CAAU,IAAA,GAAM,gBAAA,GAAwB,gBAAgB"}
|
package/dist/index.mjs
CHANGED
|
@@ -4,7 +4,8 @@ import { fileURLToPath } from "node:url";
|
|
|
4
4
|
import { faviconLinks, generateFavicons, writeFaviconManifest } from "@getvitops/utils";
|
|
5
5
|
import vitops from "@getvitops/vite";
|
|
6
6
|
import tailwindcss from "@tailwindcss/vite";
|
|
7
|
-
|
|
7
|
+
|
|
8
|
+
export * from "@getvitops/utils"
|
|
8
9
|
|
|
9
10
|
//#region src/integration.ts
|
|
10
11
|
/**
|
|
@@ -99,184 +100,5 @@ function getvitops(opts = {}) {
|
|
|
99
100
|
}
|
|
100
101
|
|
|
101
102
|
//#endregion
|
|
102
|
-
|
|
103
|
-
function parseRenderedSlots(html) {
|
|
104
|
-
return parseFragment(html).childNodes;
|
|
105
|
-
}
|
|
106
|
-
function getAttribute(node, attrName) {
|
|
107
|
-
if ("attrs" in node) {
|
|
108
|
-
const attr = node.attrs.find((a) => a.name === attrName);
|
|
109
|
-
return attr ? attr.value : null;
|
|
110
|
-
}
|
|
111
|
-
return null;
|
|
112
|
-
}
|
|
113
|
-
function serialize(nodes) {
|
|
114
|
-
return (Array.isArray(nodes) ? nodes : [nodes]).map((node) => serialize$1(node));
|
|
115
|
-
}
|
|
116
|
-
/**
|
|
117
|
-
* Convert camelCase to kebab-case, preserving CSS custom properties (--var-name)
|
|
118
|
-
*/
|
|
119
|
-
function toKebabCase(str) {
|
|
120
|
-
if (str.startsWith("--")) return str;
|
|
121
|
-
return str.replace(/([A-Z])/g, "-$1").toLowerCase();
|
|
122
|
-
}
|
|
123
|
-
function styleObjectToString(obj) {
|
|
124
|
-
return Object.entries(obj).filter(([, value]) => value != null).map(([key, value]) => `${toKebabCase(key)}: ${value}`).join("; ");
|
|
125
|
-
}
|
|
126
|
-
/**
|
|
127
|
-
* Combine multiple style formats into a single CSS string
|
|
128
|
-
* Similar to Astro's class:list but for inline styles
|
|
129
|
-
*/
|
|
130
|
-
function styleList(...styles) {
|
|
131
|
-
const result = [];
|
|
132
|
-
const process = (item) => {
|
|
133
|
-
if (item == null) return;
|
|
134
|
-
if (Array.isArray(item)) item.forEach(process);
|
|
135
|
-
else if (typeof item === "string") {
|
|
136
|
-
const trimmed = item.trim();
|
|
137
|
-
if (trimmed) result.push(trimmed);
|
|
138
|
-
} else {
|
|
139
|
-
const str = styleObjectToString(item);
|
|
140
|
-
if (str) result.push(str);
|
|
141
|
-
}
|
|
142
|
-
};
|
|
143
|
-
styles.forEach(process);
|
|
144
|
-
return result.join("; ");
|
|
145
|
-
}
|
|
146
|
-
/**
|
|
147
|
-
* Type guard to check if a value is an El object
|
|
148
|
-
*/
|
|
149
|
-
function isEl(value) {
|
|
150
|
-
return typeof value === "object" && value !== null && "tag" in value && "text" in value;
|
|
151
|
-
}
|
|
152
|
-
/**
|
|
153
|
-
* Convert HTML attributes object to a string for use in markup
|
|
154
|
-
*/
|
|
155
|
-
function attributesToString(attrs) {
|
|
156
|
-
const parts = [];
|
|
157
|
-
for (const [key, value] of Object.entries(attrs)) {
|
|
158
|
-
if (value == null || value === false) continue;
|
|
159
|
-
if (value === true) parts.push(key);
|
|
160
|
-
else if (key === "style" && typeof value === "object") parts.push(`style="${styleList(value)}"`);
|
|
161
|
-
else {
|
|
162
|
-
const escaped = (Array.isArray(value) ? value.filter((v) => typeof v === "string" && v !== "").join(" ") : typeof value === "object" ? JSON.stringify(value) : String(value)).replace(/"/g, """);
|
|
163
|
-
parts.push(`${key}="${escaped}"`);
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
return parts.join(" ");
|
|
167
|
-
}
|
|
168
|
-
/**
|
|
169
|
-
* Normalize a string or El object into an HTML string for use with set:html
|
|
170
|
-
*/
|
|
171
|
-
function toHtml(content) {
|
|
172
|
-
if (!isEl(content)) return content;
|
|
173
|
-
const { tag, text, attributes } = content;
|
|
174
|
-
const attrStr = attributes ? attributesToString(attributes) : "";
|
|
175
|
-
const t = String(tag);
|
|
176
|
-
return attrStr ? `<${t} ${attrStr}>${text}</${t}>` : `<${t}>${text}</${t}>`;
|
|
177
|
-
}
|
|
178
|
-
/** Void elements that must not have a closing tag */
|
|
179
|
-
const VOID_ELEMENTS = new Set([
|
|
180
|
-
"area",
|
|
181
|
-
"base",
|
|
182
|
-
"br",
|
|
183
|
-
"col",
|
|
184
|
-
"embed",
|
|
185
|
-
"hr",
|
|
186
|
-
"img",
|
|
187
|
-
"input",
|
|
188
|
-
"link",
|
|
189
|
-
"meta",
|
|
190
|
-
"param",
|
|
191
|
-
"source",
|
|
192
|
-
"track",
|
|
193
|
-
"wbr"
|
|
194
|
-
]);
|
|
195
|
-
/** Default text resolver — strings pass through, locale maps return first value */
|
|
196
|
-
function defaultResolveText(value) {
|
|
197
|
-
if (typeof value === "string") return value;
|
|
198
|
-
return Object.values(value)[0] ?? "";
|
|
199
|
-
}
|
|
200
|
-
/** Escape text content for safe HTML output */
|
|
201
|
-
function escapeHtml(text) {
|
|
202
|
-
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
203
|
-
}
|
|
204
|
-
/**
|
|
205
|
-
* Render a single ContentNode to an HTML string.
|
|
206
|
-
*/
|
|
207
|
-
function nodeToHtml(node, resolveText) {
|
|
208
|
-
if (typeof node === "string") return escapeHtml(resolveText(node));
|
|
209
|
-
const { tag, attrs, text, children } = node;
|
|
210
|
-
const attrStr = attrs ? attributesToString(attrs) : "";
|
|
211
|
-
const open = attrStr ? `<${tag} ${attrStr}>` : `<${tag}>`;
|
|
212
|
-
if (VOID_ELEMENTS.has(tag)) return open;
|
|
213
|
-
let inner = "";
|
|
214
|
-
if (text != null) inner = escapeHtml(resolveText(text));
|
|
215
|
-
else if (children?.length) inner = children.map((child) => nodeToHtml(child, resolveText)).join("");
|
|
216
|
-
return `${open}${inner}</${tag}>`;
|
|
217
|
-
}
|
|
218
|
-
/**
|
|
219
|
-
* Render an array of ContentNodes to an HTML string.
|
|
220
|
-
*
|
|
221
|
-
* @param nodes - Array of element nodes and/or text strings
|
|
222
|
-
* @param resolveText - Optional function to resolve Localizable values.
|
|
223
|
-
* Defaults to returning strings as-is and first locale value for maps.
|
|
224
|
-
*/
|
|
225
|
-
function nodesToHtml(nodes, resolveText = defaultResolveText) {
|
|
226
|
-
return nodes.map((node) => nodeToHtml(node, resolveText)).join("");
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
//#endregion
|
|
230
|
-
//#region src/types.ts
|
|
231
|
-
function isElmnt(item) {
|
|
232
|
-
return item && item.type === "element";
|
|
233
|
-
}
|
|
234
|
-
function isLink(item) {
|
|
235
|
-
return item && ("label" in item || "image" in item) && "href" in item;
|
|
236
|
-
}
|
|
237
|
-
function isTextLink(item) {
|
|
238
|
-
return item && "label" in item && "href" in item;
|
|
239
|
-
}
|
|
240
|
-
function isImageLink(item) {
|
|
241
|
-
return item && "image" in item && "href" in item;
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
//#endregion
|
|
245
|
-
//#region src/parts.ts
|
|
246
|
-
/**
|
|
247
|
-
* Resolve per-part attrs from a presentation type config.
|
|
248
|
-
* For the 'root' part, merges legacy `attrs` with `parts.root` (backward compat).
|
|
249
|
-
*/
|
|
250
|
-
function partAttrs(present, part) {
|
|
251
|
-
const fromParts = present?.parts?.[part];
|
|
252
|
-
if (part === "root") {
|
|
253
|
-
const legacy = present?.attrs;
|
|
254
|
-
if (!legacy && !fromParts) return {};
|
|
255
|
-
const { class: lc, ...lr } = legacy ?? {};
|
|
256
|
-
const { class: pc, ...pr } = fromParts ?? {};
|
|
257
|
-
const classes = [lc, pc].flat().filter((c) => typeof c === "string" && c !== "");
|
|
258
|
-
return {
|
|
259
|
-
...classes.length ? { class: classes } : {},
|
|
260
|
-
...lr,
|
|
261
|
-
...pr
|
|
262
|
-
};
|
|
263
|
-
}
|
|
264
|
-
return fromParts ?? {};
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
//#endregion
|
|
268
|
-
//#region src/i18n.ts
|
|
269
|
-
/**
|
|
270
|
-
* Resolve a Localizable value to a string for the given locale.
|
|
271
|
-
* If the value is a plain string, it is returned as-is (treated as defaultLocale).
|
|
272
|
-
* If it is a locale map, the best match is returned.
|
|
273
|
-
*/
|
|
274
|
-
function t(value, locale, defaultLocale) {
|
|
275
|
-
if (value == null) return "";
|
|
276
|
-
if (typeof value === "string") return value;
|
|
277
|
-
return value[locale] ?? value[defaultLocale] ?? "";
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
//#endregion
|
|
281
|
-
export { getvitops as default, getAttribute, isElmnt, isImageLink, isLink, isTextLink, nodesToHtml, parseRenderedSlots, partAttrs, serialize, styleList, t, toHtml };
|
|
103
|
+
export { getvitops as default };
|
|
282
104
|
//# sourceMappingURL=index.mjs.map
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":["s"],"sources":["../src/integration.ts","../src/html.ts","../src/types.ts","../src/parts.ts","../src/i18n.ts"],"sourcesContent":["/**\n * @getvitops/astro — the Astro integration. One entry wires the design system's\n * `<head>` contributions into a consumer site:\n * - favicons + PWA manifest (generated into public/),\n * - the web-component runtime bundles (copied into public/vitops/),\n * - (opt-in) the design-system CSS, generated + auto-injected (no manual import).\n * The `<Head />` component (shipped alongside) renders the tags, reading resolved\n * config from the `virtual:getvitops/head` module this integration provides.\n */\nimport { cpSync, existsSync } from 'node:fs';\nimport { dirname, join, resolve } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport type { Format } from '@getvitops/generator';\nimport {\n type FaviconLink,\n faviconLinks,\n generateFavicons,\n writeFaviconManifest,\n} from '@getvitops/utils';\nimport vitops from '@getvitops/vite';\nimport tailwindcss from '@tailwindcss/vite';\nimport type { AstroIntegration } from 'astro';\n\nexport interface GetvitopsFaviconOptions {\n /** Source SVG or PNG. */\n source: string;\n lowResSource?: string;\n /** App name → enables `site.webmanifest`. */\n name?: string;\n /** PWA theme color (also `<meta name=\"theme-color\">`). */\n themeColor?: string;\n backgroundColor?: string;\n}\n\nexport interface GetvitopsCssOptions {\n /** Path to design-system.json (default 'design-system.json'). */\n input?: string;\n /** Output format (default 'tailwind'). */\n format?: Format;\n /** Directory the generated CSS is written to (default 'src/styles'). */\n out?: string;\n}\n\nexport interface GetvitopsOptions {\n favicon?: GetvitopsFaviconOptions;\n /** Copy + link the web-component bundles (default true). */\n webComponents?: boolean;\n /** Generate + auto-inject the design-system CSS. Off unless provided. */\n css?: GetvitopsCssOptions;\n}\n\ninterface HeadData {\n favicons: boolean;\n faviconLinks: FaviconLink[];\n themeColor: string | null;\n webComponents: boolean;\n wcBase: string;\n}\n\nconst VIRTUAL_ID = 'virtual:getvitops/head';\nconst RESOLVED_ID = `\\0${VIRTUAL_ID}`;\n\nfunction virtualHeadPlugin(data: HeadData) {\n return {\n name: '@getvitops/astro:virtual-head',\n resolveId(id: string) {\n return id === VIRTUAL_ID ? RESOLVED_ID : null;\n },\n load(id: string) {\n return id === RESOLVED_ID ? `export default ${JSON.stringify(data)};` : null;\n },\n };\n}\n\nexport default function getvitops(opts: GetvitopsOptions = {}): AstroIntegration {\n return {\n name: '@getvitops/astro',\n hooks: {\n 'astro:config:setup': async ({ config, updateConfig, injectScript, logger }) => {\n const root = fileURLToPath(config.root);\n const publicDir = fileURLToPath(config.publicDir);\n const webComponents = opts.webComponents !== false;\n const hasManifest = !!(opts.favicon?.name && opts.favicon?.themeColor);\n let hasSvg = false;\n\n // 1. Favicons + manifest → public/\n if (opts.favicon) {\n const source = resolve(root, opts.favicon.source);\n hasSvg = source.endsWith('.svg');\n await generateFavicons({\n source,\n outputDir: publicDir,\n ...(opts.favicon.lowResSource\n ? { lowResSource: resolve(root, opts.favicon.lowResSource) }\n : {}),\n });\n if (opts.favicon.name && opts.favicon.themeColor) {\n await writeFaviconManifest(publicDir, {\n name: opts.favicon.name,\n themeColor: opts.favicon.themeColor,\n ...(opts.favicon.backgroundColor\n ? { backgroundColor: opts.favicon.backgroundColor }\n : {}),\n });\n }\n logger.info('generated favicons + manifest → public/');\n }\n\n // 2. Web-component bundles → public/vitops/\n if (webComponents) {\n const corePkg = fileURLToPath(import.meta.resolve('@getvitops/core/package.json'));\n const coreDist = join(dirname(corePkg), 'dist');\n const dest = join(publicDir, 'vitops');\n if (existsSync(coreDist)) {\n for (const f of ['polyfills.js', 'deferred.js', 'elements.js', 'polyfills']) {\n const src = join(coreDist, f);\n if (existsSync(src)) cpSync(src, join(dest, f), { recursive: true });\n }\n logger.info('copied web-component bundles → public/vitops/');\n } else {\n logger.warn(\n `@getvitops/core dist not found (${coreDist}); run build:core. Skipping WC copy.`,\n );\n }\n }\n\n // 3. Virtual head module for <Head/>\n updateConfig({\n vite: {\n plugins: [\n virtualHeadPlugin({\n favicons: !!opts.favicon,\n faviconLinks: opts.favicon ? faviconLinks({ hasSvg, manifest: hasManifest }) : [],\n themeColor: opts.favicon?.themeColor ?? null,\n webComponents,\n wcBase: '/vitops',\n }),\n ],\n },\n });\n\n // 4. CSS — generate + compile + auto-inject (consumer imports nothing).\n if (opts.css) {\n const format: Format = opts.css.format ?? 'tailwind';\n const out = opts.css.out ?? 'src/styles';\n const plugins = [vitops({ input: opts.css.input ?? 'design-system.json', format, out })];\n // tailwindcss() is typed against a newer vite than @getvitops/vite's\n // peer; the Plugin shapes are structurally compatible at runtime\n // (Astro forwards them verbatim), so bridge the two type identities.\n if (format === 'tailwind') plugins.push(...(tailwindcss() as unknown as typeof plugins));\n updateConfig({ vite: { plugins } });\n const cssFile = format === 'tailwind' ? 'tailwind.css' : 'styles.css';\n const cssPath = resolve(root, out, cssFile);\n injectScript('page-ssr', `import ${JSON.stringify(cssPath)};`);\n logger.info(`design-system CSS (${format}) auto-injected from ${out}/${cssFile}`);\n }\n },\n },\n };\n}\n","import { parseFragment, serialize as s, type DefaultTreeAdapterTypes } from 'parse5';\nimport type { HTMLTag, HTMLAttributes } from 'astro/types';\nimport type { Localizable, ContentNode } from '../types.js';\n\nexport type ChildNode = DefaultTreeAdapterTypes.ChildNode;\n\nexport function parseRenderedSlots(html: string): ChildNode[] {\n const document = parseFragment(html);\n return document.childNodes;\n}\n\nexport function getAttribute(node: ChildNode, attrName: string): string | null {\n if ('attrs' in node) {\n const attr = node.attrs.find((a) => a.name === attrName);\n return attr ? attr.value : null;\n }\n return null;\n}\n\nexport function serialize(nodes: ChildNode | ChildNode[]): string[] {\n return (Array.isArray(nodes) ? nodes : [nodes]).map((node) =>\n s(node as DefaultTreeAdapterTypes.ParentNode),\n );\n}\n\n/**\n * Style value types that can be combined. StyleObject is structurally\n * compatible with astroHTML.JSX.CSSProperties (camelCase or --custom-prop keys).\n */\ntype StyleObject = Record<string, string | number | null | undefined>;\nexport type StyleValue = string | StyleObject | null | undefined;\nexport type StyleList = StyleValue | StyleValue[];\n\n/**\n * Convert camelCase to kebab-case, preserving CSS custom properties (--var-name)\n */\nfunction toKebabCase(str: string): string {\n if (str.startsWith('--')) return str;\n return str.replace(/([A-Z])/g, '-$1').toLowerCase();\n}\nfunction styleObjectToString(obj: StyleObject): string {\n return Object.entries(obj)\n .filter(([, value]) => value != null)\n .map(([key, value]) => `${toKebabCase(key)}: ${value}`)\n .join('; ');\n}\n\n/**\n * Combine multiple style formats into a single CSS string\n * Similar to Astro's class:list but for inline styles\n */\nexport function styleList(...styles: StyleList[]): string {\n const result: string[] = [];\n\n const process = (item: StyleList): void => {\n if (item == null) return;\n if (Array.isArray(item)) {\n item.forEach(process);\n } else if (typeof item === 'string') {\n const trimmed = item.trim();\n if (trimmed) result.push(trimmed);\n } else {\n const str = styleObjectToString(item as StyleObject);\n if (str) result.push(str);\n }\n };\n\n styles.forEach(process);\n return result.join('; ');\n}\n\n/**\n * Element descriptor matching the El interface from env.d.ts\n */\nexport interface El<Tag extends HTMLTag = HTMLTag> {\n text: string;\n tag: Tag;\n attributes?: HTMLAttributes<Tag>;\n}\n\n/**\n * Type guard to check if a value is an El object\n */\nfunction isEl(value: string | El): value is El {\n return typeof value === 'object' && value !== null && 'tag' in value && 'text' in value;\n}\n\n/**\n * Convert HTML attributes object to a string for use in markup\n */\nfunction attributesToString(attrs: Record<string, unknown>): string {\n const parts: string[] = [];\n\n for (const [key, value] of Object.entries(attrs)) {\n if (value == null || value === false) continue;\n if (value === true) {\n parts.push(key);\n } else if (key === 'style' && typeof value === 'object') {\n parts.push(`style=\"${styleList(value as StyleValue)}\"`);\n } else {\n // Class:list arrays join like Astro; other objects stringify as JSON\n // (never \"[object Object]\"). Escape double quotes for the attribute.\n const text = Array.isArray(value)\n ? value.filter((v): v is string => typeof v === 'string' && v !== '').join(' ')\n : typeof value === 'object'\n ? JSON.stringify(value)\n : String(value as string | number | boolean);\n const escaped = text.replace(/\"/g, '"');\n parts.push(`${key}=\"${escaped}\"`);\n }\n }\n\n return parts.join(' ');\n}\n\n/**\n * Normalize a string or El object into an HTML string for use with set:html\n */\nexport function toHtml<Tag extends HTMLTag>(content: string | El<Tag>): string {\n if (!isEl(content)) {\n return content;\n }\n\n const { tag, text, attributes } = content;\n const attrStr = attributes ? attributesToString(attributes) : '';\n // HTMLTag is a key type (string | number | symbol to tsc) — stringify for templates.\n const t = String(tag);\n\n return attrStr ? `<${t} ${attrStr}>${text}</${t}>` : `<${t}>${text}</${t}>`;\n}\n\n// ---------------------------------------------------------------------------\n// Node tree renderer — { tag, attrs, children }\n// ---------------------------------------------------------------------------\n\n/** Void elements that must not have a closing tag */\nconst VOID_ELEMENTS = new Set([\n 'area',\n 'base',\n 'br',\n 'col',\n 'embed',\n 'hr',\n 'img',\n 'input',\n 'link',\n 'meta',\n 'param',\n 'source',\n 'track',\n 'wbr',\n]);\n\n/** Default text resolver — strings pass through, locale maps return first value */\nfunction defaultResolveText(value: Localizable): string {\n if (typeof value === 'string') return value;\n const values = Object.values(value);\n return values[0] ?? '';\n}\n\n/** Escape text content for safe HTML output */\nfunction escapeHtml(text: string): string {\n return text.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');\n}\n\n/**\n * Render a single ContentNode to an HTML string.\n */\nfunction nodeToHtml(node: ContentNode, resolveText: (value: Localizable) => string): string {\n // String child → text node\n if (typeof node === 'string') {\n return escapeHtml(resolveText(node));\n }\n\n const { tag, attrs, text, children } = node;\n const attrStr = attrs ? attributesToString(attrs) : '';\n const open = attrStr ? `<${tag} ${attrStr}>` : `<${tag}>`;\n\n if (VOID_ELEMENTS.has(tag)) {\n return open;\n }\n\n // text is shorthand for children: [\"string\"]\n let inner = '';\n if (text != null) {\n inner = escapeHtml(resolveText(text));\n } else if (children?.length) {\n inner = children.map((child) => nodeToHtml(child, resolveText)).join('');\n }\n\n return `${open}${inner}</${tag}>`;\n}\n\n/**\n * Render an array of ContentNodes to an HTML string.\n *\n * @param nodes - Array of element nodes and/or text strings\n * @param resolveText - Optional function to resolve Localizable values.\n * Defaults to returning strings as-is and first locale value for maps.\n */\nexport function nodesToHtml(\n nodes: ContentNode[],\n resolveText: (value: Localizable) => string = defaultResolveText,\n): string {\n return nodes.map((node) => nodeToHtml(node, resolveText)).join('');\n}\n","import type { Elmnt, Link, TextLink, ImageLink } from '../types.js';\nimport type { HTMLTag } from 'astro/types';\n\nexport function isElmnt<T, Tag extends HTMLTag = 'div'>(item: any): item is Elmnt<T, Tag> {\n return item && item.type === 'element';\n}\n\nexport function isLink(item: any): item is Link {\n return item && ('label' in item || 'image' in item) && 'href' in item;\n}\n\nexport function isTextLink(item: any): item is TextLink {\n return item && 'label' in item && 'href' in item;\n}\n\nexport function isImageLink(item: any): item is ImageLink {\n return item && 'image' in item && 'href' in item;\n}\n","import type { Attrs } from '../types.js';\n\n/**\n * Resolve per-part attrs from a presentation type config.\n * For the 'root' part, merges legacy `attrs` with `parts.root` (backward compat).\n */\nexport function partAttrs(\n present: { attrs?: Attrs; parts?: Record<string, Attrs> } | undefined,\n part: string,\n): Attrs {\n const fromParts = present?.parts?.[part];\n if (part === 'root') {\n const legacy = present?.attrs;\n if (!legacy && !fromParts) return {};\n const { class: lc, ...lr } = legacy ?? {};\n const { class: pc, ...pr } = fromParts ?? {};\n // Flatten class:list-style values and keep only real class strings.\n const classes = [lc, pc].flat().filter((c): c is string => typeof c === 'string' && c !== '');\n return {\n ...(classes.length ? { class: classes } : {}),\n ...lr,\n ...pr,\n };\n }\n return fromParts ?? {};\n}\n","import type { Localizable } from '../types.js';\n\n/**\n * Resolve a Localizable value to a string for the given locale.\n * If the value is a plain string, it is returned as-is (treated as defaultLocale).\n * If it is a locale map, the best match is returned.\n */\nexport function t(\n value: Localizable | undefined,\n locale: string,\n defaultLocale: string,\n): string {\n if (value == null) return '';\n if (typeof value === 'string') return value;\n return value[locale] ?? value[defaultLocale] ?? '';\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../src/integration.ts"],"sourcesContent":["/**\n * @getvitops/astro — the Astro integration. One entry wires the design system's\n * `<head>` contributions into a consumer site:\n * - favicons + PWA manifest (generated into public/),\n * - the web-component runtime bundles (copied into public/vitops/),\n * - (opt-in) the design-system CSS, generated + auto-injected (no manual import).\n * The `<Head />` component (shipped alongside) renders the tags, reading resolved\n * config from the `virtual:getvitops/head` module this integration provides.\n */\nimport { cpSync, existsSync } from 'node:fs';\nimport { dirname, join, resolve } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport type { Format } from '@getvitops/generator';\nimport {\n type FaviconLink,\n faviconLinks,\n generateFavicons,\n writeFaviconManifest,\n} from '@getvitops/utils';\nimport vitops from '@getvitops/vite';\nimport tailwindcss from '@tailwindcss/vite';\nimport type { AstroIntegration } from 'astro';\n\nexport interface GetvitopsFaviconOptions {\n /** Source SVG or PNG. */\n source: string;\n lowResSource?: string;\n /** App name → enables `site.webmanifest`. */\n name?: string;\n /** PWA theme color (also `<meta name=\"theme-color\">`). */\n themeColor?: string;\n backgroundColor?: string;\n}\n\nexport interface GetvitopsCssOptions {\n /** Path to design-system.json (default 'design-system.json'). */\n input?: string;\n /** Output format (default 'tailwind'). */\n format?: Format;\n /** Directory the generated CSS is written to (default 'src/styles'). */\n out?: string;\n}\n\nexport interface GetvitopsOptions {\n favicon?: GetvitopsFaviconOptions;\n /** Copy + link the web-component bundles (default true). */\n webComponents?: boolean;\n /** Generate + auto-inject the design-system CSS. Off unless provided. */\n css?: GetvitopsCssOptions;\n}\n\ninterface HeadData {\n favicons: boolean;\n faviconLinks: FaviconLink[];\n themeColor: string | null;\n webComponents: boolean;\n wcBase: string;\n}\n\nconst VIRTUAL_ID = 'virtual:getvitops/head';\nconst RESOLVED_ID = `\\0${VIRTUAL_ID}`;\n\nfunction virtualHeadPlugin(data: HeadData) {\n return {\n name: '@getvitops/astro:virtual-head',\n resolveId(id: string) {\n return id === VIRTUAL_ID ? RESOLVED_ID : null;\n },\n load(id: string) {\n return id === RESOLVED_ID ? `export default ${JSON.stringify(data)};` : null;\n },\n };\n}\n\nexport default function getvitops(opts: GetvitopsOptions = {}): AstroIntegration {\n return {\n name: '@getvitops/astro',\n hooks: {\n 'astro:config:setup': async ({ config, updateConfig, injectScript, logger }) => {\n const root = fileURLToPath(config.root);\n const publicDir = fileURLToPath(config.publicDir);\n const webComponents = opts.webComponents !== false;\n const hasManifest = !!(opts.favicon?.name && opts.favicon?.themeColor);\n let hasSvg = false;\n\n // 1. Favicons + manifest → public/\n if (opts.favicon) {\n const source = resolve(root, opts.favicon.source);\n hasSvg = source.endsWith('.svg');\n await generateFavicons({\n source,\n outputDir: publicDir,\n ...(opts.favicon.lowResSource\n ? { lowResSource: resolve(root, opts.favicon.lowResSource) }\n : {}),\n });\n if (opts.favicon.name && opts.favicon.themeColor) {\n await writeFaviconManifest(publicDir, {\n name: opts.favicon.name,\n themeColor: opts.favicon.themeColor,\n ...(opts.favicon.backgroundColor\n ? { backgroundColor: opts.favicon.backgroundColor }\n : {}),\n });\n }\n logger.info('generated favicons + manifest → public/');\n }\n\n // 2. Web-component bundles → public/vitops/\n if (webComponents) {\n const corePkg = fileURLToPath(import.meta.resolve('@getvitops/core/package.json'));\n const coreDist = join(dirname(corePkg), 'dist');\n const dest = join(publicDir, 'vitops');\n if (existsSync(coreDist)) {\n for (const f of ['polyfills.js', 'deferred.js', 'elements.js', 'polyfills']) {\n const src = join(coreDist, f);\n if (existsSync(src)) cpSync(src, join(dest, f), { recursive: true });\n }\n logger.info('copied web-component bundles → public/vitops/');\n } else {\n logger.warn(\n `@getvitops/core dist not found (${coreDist}); run build:core. Skipping WC copy.`,\n );\n }\n }\n\n // 3. Virtual head module for <Head/>\n updateConfig({\n vite: {\n plugins: [\n virtualHeadPlugin({\n favicons: !!opts.favicon,\n faviconLinks: opts.favicon ? faviconLinks({ hasSvg, manifest: hasManifest }) : [],\n themeColor: opts.favicon?.themeColor ?? null,\n webComponents,\n wcBase: '/vitops',\n }),\n ],\n },\n });\n\n // 4. CSS — generate + compile + auto-inject (consumer imports nothing).\n if (opts.css) {\n const format: Format = opts.css.format ?? 'tailwind';\n const out = opts.css.out ?? 'src/styles';\n const plugins = [vitops({ input: opts.css.input ?? 'design-system.json', format, out })];\n // tailwindcss() is typed against a newer vite than @getvitops/vite's\n // peer; the Plugin shapes are structurally compatible at runtime\n // (Astro forwards them verbatim), so bridge the two type identities.\n if (format === 'tailwind') plugins.push(...(tailwindcss() as unknown as typeof plugins));\n updateConfig({ vite: { plugins } });\n const cssFile = format === 'tailwind' ? 'tailwind.css' : 'styles.css';\n const cssPath = resolve(root, out, cssFile);\n injectScript('page-ssr', `import ${JSON.stringify(cssPath)};`);\n logger.info(`design-system CSS (${format}) auto-injected from ${out}/${cssFile}`);\n }\n },\n },\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AA2DA,MAAM,aAAa;AACnB,MAAM,cAAc,KAAK;AAEzB,SAAS,kBAAkB,MAAgB;CACzC,OAAO;EACL,MAAM;EACN,UAAU,IAAY;GACpB,OAAO,OAAO,aAAa,cAAc;EAC3C;EACA,KAAK,IAAY;GACf,OAAO,OAAO,cAAc,kBAAkB,KAAK,UAAU,IAAI,EAAE,KAAK;EAC1E;CACF;AACF;AAEA,SAAwB,UAAU,OAAyB,CAAC,GAAqB;CAC/E,OAAO;EACL,MAAM;EACN,OAAO,EACL,sBAAsB,OAAO,EAAE,QAAQ,cAAc,cAAc,aAAa;GAC9E,MAAM,OAAO,cAAc,OAAO,IAAI;GACtC,MAAM,YAAY,cAAc,OAAO,SAAS;GAChD,MAAM,gBAAgB,KAAK,kBAAkB;GAC7C,MAAM,cAAc,CAAC,EAAE,KAAK,SAAS,QAAQ,KAAK,SAAS;GAC3D,IAAI,SAAS;GAGb,IAAI,KAAK,SAAS;IAChB,MAAM,SAAS,QAAQ,MAAM,KAAK,QAAQ,MAAM;IAChD,SAAS,OAAO,SAAS,MAAM;IAC/B,MAAM,iBAAiB;KACrB;KACA,WAAW;KACX,GAAI,KAAK,QAAQ,eACb,EAAE,cAAc,QAAQ,MAAM,KAAK,QAAQ,YAAY,EAAE,IACzD,CAAC;IACP,CAAC;IACD,IAAI,KAAK,QAAQ,QAAQ,KAAK,QAAQ,YACpC,MAAM,qBAAqB,WAAW;KACpC,MAAM,KAAK,QAAQ;KACnB,YAAY,KAAK,QAAQ;KACzB,GAAI,KAAK,QAAQ,kBACb,EAAE,iBAAiB,KAAK,QAAQ,gBAAgB,IAChD,CAAC;IACP,CAAC;IAEH,OAAO,KAAK,yCAAyC;GACvD;GAGA,IAAI,eAAe;IAEjB,MAAM,WAAW,KAAK,QADN,cAAc,OAAO,KAAK,QAAQ,8BAA8B,CAC5C,CAAC,GAAG,MAAM;IAC9C,MAAM,OAAO,KAAK,WAAW,QAAQ;IACrC,IAAI,WAAW,QAAQ,GAAG;KACxB,KAAK,MAAM,KAAK;MAAC;MAAgB;MAAe;MAAe;KAAW,GAAG;MAC3E,MAAM,MAAM,KAAK,UAAU,CAAC;MAC5B,IAAI,WAAW,GAAG,GAAG,OAAO,KAAK,KAAK,MAAM,CAAC,GAAG,EAAE,WAAW,KAAK,CAAC;KACrE;KACA,OAAO,KAAK,+CAA+C;IAC7D,OACE,OAAO,KACL,mCAAmC,SAAS,qCAC9C;GAEJ;GAGA,aAAa,EACX,MAAM,EACJ,SAAS,CACP,kBAAkB;IAChB,UAAU,CAAC,CAAC,KAAK;IACjB,cAAc,KAAK,UAAU,aAAa;KAAE;KAAQ,UAAU;IAAY,CAAC,IAAI,CAAC;IAChF,YAAY,KAAK,SAAS,cAAc;IACxC;IACA,QAAQ;GACV,CAAC,CACH,EACF,EACF,CAAC;GAGD,IAAI,KAAK,KAAK;IACZ,MAAM,SAAiB,KAAK,IAAI,UAAU;IAC1C,MAAM,MAAM,KAAK,IAAI,OAAO;IAC5B,MAAM,UAAU,CAAC,OAAO;KAAE,OAAO,KAAK,IAAI,SAAS;KAAsB;KAAQ;IAAI,CAAC,CAAC;IAIvF,IAAI,WAAW,YAAY,QAAQ,KAAK,GAAI,YAAY,CAA+B;IACvF,aAAa,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC;IAClC,MAAM,UAAU,WAAW,aAAa,iBAAiB;IACzD,MAAM,UAAU,QAAQ,MAAM,KAAK,OAAO;IAC1C,aAAa,YAAY,UAAU,KAAK,UAAU,OAAO,EAAE,EAAE;IAC7D,OAAO,KAAK,sBAAsB,OAAO,uBAAuB,IAAI,GAAG,SAAS;GAClF;EACF,EACF;CACF;AACF"}
|
package/package.json
CHANGED
|
@@ -1,11 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@getvitops/astro",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Astro integration for the Vitops design system — favicons/PWA, web-component scripts, and CSS, via a getvitops() integration + <Head /> component.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"files": [
|
|
7
7
|
"dist",
|
|
8
8
|
"src/components/Head.astro",
|
|
9
|
+
"src/components/Subgrid.astro",
|
|
10
|
+
"src/components/Cards.astro",
|
|
11
|
+
"src/components/NodeRenderer.astro",
|
|
12
|
+
"src/components/WebComponentLoader.astro",
|
|
13
|
+
"src/components/Popover.astro",
|
|
14
|
+
"src/components/Details.astro",
|
|
15
|
+
"src/components/Drawer.astro",
|
|
9
16
|
"src/schemas",
|
|
10
17
|
"virtual.d.ts"
|
|
11
18
|
],
|
|
@@ -17,6 +24,13 @@
|
|
|
17
24
|
"import": "./dist/index.mjs"
|
|
18
25
|
},
|
|
19
26
|
"./Head.astro": "./src/components/Head.astro",
|
|
27
|
+
"./components/Subgrid.astro": "./src/components/Subgrid.astro",
|
|
28
|
+
"./components/Cards.astro": "./src/components/Cards.astro",
|
|
29
|
+
"./components/NodeRenderer.astro": "./src/components/NodeRenderer.astro",
|
|
30
|
+
"./components/WebComponentLoader.astro": "./src/components/WebComponentLoader.astro",
|
|
31
|
+
"./components/Popover.astro": "./src/components/Popover.astro",
|
|
32
|
+
"./components/Details.astro": "./src/components/Details.astro",
|
|
33
|
+
"./components/Drawer.astro": "./src/components/Drawer.astro",
|
|
20
34
|
"./schemas/*": "./src/schemas/*",
|
|
21
35
|
"./package.json": "./package.json"
|
|
22
36
|
},
|
|
@@ -25,17 +39,22 @@
|
|
|
25
39
|
},
|
|
26
40
|
"dependencies": {
|
|
27
41
|
"@tailwindcss/vite": "^4.3.3",
|
|
28
|
-
"parse5": "^7.3.0",
|
|
29
42
|
"tailwindcss": "^4.3.3",
|
|
30
|
-
"@getvitops/
|
|
31
|
-
"@getvitops/
|
|
32
|
-
"@getvitops/
|
|
33
|
-
"@getvitops/
|
|
43
|
+
"@getvitops/generator": "0.3.0",
|
|
44
|
+
"@getvitops/core": "0.3.0",
|
|
45
|
+
"@getvitops/vite": "0.3.0",
|
|
46
|
+
"@getvitops/utils": "0.3.0"
|
|
34
47
|
},
|
|
35
48
|
"devDependencies": {
|
|
36
49
|
"astro": "^7.1.2"
|
|
37
50
|
},
|
|
38
51
|
"peerDependencies": {
|
|
39
|
-
"astro": ">=7"
|
|
52
|
+
"astro": ">=7",
|
|
53
|
+
"astro-icon": ">=1"
|
|
54
|
+
},
|
|
55
|
+
"peerDependenciesMeta": {
|
|
56
|
+
"astro-icon": {
|
|
57
|
+
"optional": true
|
|
58
|
+
}
|
|
40
59
|
}
|
|
41
60
|
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
---
|
|
2
|
+
import type { HTMLAttributes } from 'astro/types';
|
|
3
|
+
import Subgrid from './Subgrid.astro';
|
|
4
|
+
import { parseRenderedSlots, serialize } from '@getvitops/utils';
|
|
5
|
+
|
|
6
|
+
export interface Props extends HTMLAttributes<'ul'> {}
|
|
7
|
+
|
|
8
|
+
const { class: className, ...rest } = Astro.props;
|
|
9
|
+
|
|
10
|
+
const nodes = parseRenderedSlots(await Astro.slots.render('default'));
|
|
11
|
+
const slots = nodes.filter(node => 'tagName' in node).map(node => {
|
|
12
|
+
const cls = node.attrs?.find(attr => attr.name === 'class');
|
|
13
|
+
const value = `card${cls ? ' ' + cls.value : ''}`;
|
|
14
|
+
if (cls) {
|
|
15
|
+
cls.value = value;
|
|
16
|
+
} else {
|
|
17
|
+
node.attrs.push({ name: 'class', value });
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return { html: serialize(node), attrs: node.attrs };
|
|
21
|
+
});
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
<Subgrid
|
|
25
|
+
role="list"
|
|
26
|
+
class:list={["no-list-markers", className]}
|
|
27
|
+
{...rest}
|
|
28
|
+
>
|
|
29
|
+
{slots.map((slot) => (
|
|
30
|
+
<li class="card-wrapper" style={slot.attrs?.find(attr => attr.name === 'style')?.value} set:html={slot.html}></li>
|
|
31
|
+
))}
|
|
32
|
+
</Subgrid>
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { Icon } from 'astro-icon/components';
|
|
3
|
+
|
|
4
|
+
interface Item {
|
|
5
|
+
label: string;
|
|
6
|
+
href: string;
|
|
7
|
+
icon?: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
interface Props {
|
|
11
|
+
label: string;
|
|
12
|
+
icon?: string;
|
|
13
|
+
items: Item[];
|
|
14
|
+
class?: string;
|
|
15
|
+
/** Resolve semantic icon names to fully-qualified astro-icon names.
|
|
16
|
+
* Defaults to pass-through (expects already-qualified names like 'fa7-solid:chevron-down'). */
|
|
17
|
+
iconResolver?: (name: string) => string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const {
|
|
21
|
+
label,
|
|
22
|
+
items,
|
|
23
|
+
class: className,
|
|
24
|
+
iconResolver = (name: string) => name,
|
|
25
|
+
} = Astro.props;
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
<details class={`details-collapse ${className || ''}`}>
|
|
29
|
+
<summary class="details-trigger"><slot>
|
|
30
|
+
{label}
|
|
31
|
+
<Icon name={iconResolver('expand-more')} class="details-icon" /></slot>
|
|
32
|
+
</summary>
|
|
33
|
+
<slot name="content" />
|
|
34
|
+
</details>
|
|
35
|
+
|
|
36
|
+
<style>
|
|
37
|
+
.details-collapse {
|
|
38
|
+
--_details-icon-rotate: 180deg;
|
|
39
|
+
border: none;
|
|
40
|
+
padding: 0;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.details-trigger {
|
|
44
|
+
gap: 0.75rem;
|
|
45
|
+
background: none;
|
|
46
|
+
border: none;
|
|
47
|
+
padding: 0;
|
|
48
|
+
width: 100%;
|
|
49
|
+
text-align: left;
|
|
50
|
+
font-size: inherit;
|
|
51
|
+
font-family: inherit;
|
|
52
|
+
color: inherit;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.details-icon {
|
|
56
|
+
width: 0.75rem;
|
|
57
|
+
height: 0.75rem;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.details-content {
|
|
61
|
+
list-style: none;
|
|
62
|
+
padding-left: 2rem;
|
|
63
|
+
margin-top: 0.75rem;
|
|
64
|
+
display: flex;
|
|
65
|
+
flex-direction: column;
|
|
66
|
+
gap: 0.75rem;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.details-content a {
|
|
70
|
+
display: flex;
|
|
71
|
+
align-items: center;
|
|
72
|
+
gap: 0.5rem;
|
|
73
|
+
text-decoration: none;
|
|
74
|
+
color: inherit;
|
|
75
|
+
}
|
|
76
|
+
</style>
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { Icon } from 'astro-icon/components';
|
|
3
|
+
|
|
4
|
+
type Attrs = Record<string, string | number | boolean>;
|
|
5
|
+
|
|
6
|
+
interface Props {
|
|
7
|
+
id: string;
|
|
8
|
+
position?: 'left' | 'right' | 'top' | 'bottom';
|
|
9
|
+
trigger?: {
|
|
10
|
+
label: string;
|
|
11
|
+
icon?: string;
|
|
12
|
+
class?: string;
|
|
13
|
+
attrs?: Attrs;
|
|
14
|
+
};
|
|
15
|
+
attrs?: Attrs;
|
|
16
|
+
class?: string;
|
|
17
|
+
maxWidth?: string;
|
|
18
|
+
maxHeight?: string;
|
|
19
|
+
/** Resolve semantic icon names to fully-qualified astro-icon names.
|
|
20
|
+
* Defaults to pass-through (expects already-qualified names like 'fa7-solid:bars'). */
|
|
21
|
+
iconResolver?: (name: string) => string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const {
|
|
25
|
+
id,
|
|
26
|
+
position = 'left',
|
|
27
|
+
trigger,
|
|
28
|
+
attrs,
|
|
29
|
+
class: className,
|
|
30
|
+
maxWidth,
|
|
31
|
+
maxHeight,
|
|
32
|
+
iconResolver = (name: string) => name,
|
|
33
|
+
} = Astro.props;
|
|
34
|
+
|
|
35
|
+
const styleVars = [
|
|
36
|
+
maxWidth && `--drawer-max-inline-size: ${maxWidth}`,
|
|
37
|
+
maxHeight && `--drawer-max-block-size: ${maxHeight}`,
|
|
38
|
+
].filter(Boolean).join('; ') || undefined;
|
|
39
|
+
|
|
40
|
+
const { class: triggerAttrClass, ...triggerRestAttrs } = trigger?.attrs ?? {};
|
|
41
|
+
const { class: dialogAttrClass, style: dialogAttrStyle, ...dialogRestAttrs } = attrs ?? {};
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
<slot name="trigger">
|
|
45
|
+
<button
|
|
46
|
+
commandfor={id}
|
|
47
|
+
command="show-modal"
|
|
48
|
+
class:list={[trigger?.class || 'btn', triggerAttrClass]}
|
|
49
|
+
aria-label={trigger?.label || 'Open drawer'}
|
|
50
|
+
{...triggerRestAttrs}
|
|
51
|
+
>
|
|
52
|
+
<span class={trigger?.icon ? 'sr-only' : ''}>{trigger?.label || 'Open'}</span>
|
|
53
|
+
{trigger?.icon && <Icon name={iconResolver(trigger.icon)} class="not-sr-only" />}
|
|
54
|
+
</button>
|
|
55
|
+
</slot>
|
|
56
|
+
|
|
57
|
+
<dialog
|
|
58
|
+
id={id}
|
|
59
|
+
closedby="any"
|
|
60
|
+
class:list={['drawer', `drawer--${position}`, className, dialogAttrClass]}
|
|
61
|
+
style={[styleVars, dialogAttrStyle].filter(Boolean).join('; ') || undefined}
|
|
62
|
+
{...dialogRestAttrs}
|
|
63
|
+
>
|
|
64
|
+
<slot />
|
|
65
|
+
</dialog>
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { nodesToHtml } from '@getvitops/utils';
|
|
3
|
+
import { t } from '@getvitops/utils';
|
|
4
|
+
import type { ContentNode, Localizable, Attrs } from '@getvitops/utils';
|
|
5
|
+
|
|
6
|
+
interface Props {
|
|
7
|
+
nodes: ContentNode[];
|
|
8
|
+
attrs?: Attrs;
|
|
9
|
+
locale?: string;
|
|
10
|
+
defaultLocale?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const {
|
|
14
|
+
nodes,
|
|
15
|
+
attrs,
|
|
16
|
+
locale = 'en',
|
|
17
|
+
defaultLocale = 'en',
|
|
18
|
+
} = Astro.props;
|
|
19
|
+
|
|
20
|
+
const resolveText = (value: Localizable) => t(value, locale, defaultLocale);
|
|
21
|
+
const htmlString = nodesToHtml(nodes, resolveText);
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
{attrs
|
|
25
|
+
? <div {...attrs} set:html={htmlString} />
|
|
26
|
+
: <Fragment set:html={htmlString} />
|
|
27
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { Icon } from 'astro-icon/components';
|
|
3
|
+
|
|
4
|
+
interface Props {
|
|
5
|
+
id: string;
|
|
6
|
+
trigger?: {
|
|
7
|
+
label: string;
|
|
8
|
+
icon?: string;
|
|
9
|
+
class?: string;
|
|
10
|
+
attrs?: Record<string, string | number | boolean>;
|
|
11
|
+
};
|
|
12
|
+
type?: 'auto' | 'manual';
|
|
13
|
+
as?: 'div' | 'dialog';
|
|
14
|
+
class?: string;
|
|
15
|
+
anchorName?: string;
|
|
16
|
+
positionArea?: string;
|
|
17
|
+
/** Resolve semantic icon names to fully-qualified astro-icon names.
|
|
18
|
+
* Defaults to pass-through (expects already-qualified names like 'fa7-solid:bars'). */
|
|
19
|
+
iconResolver?: (name: string) => string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const {
|
|
23
|
+
id,
|
|
24
|
+
trigger,
|
|
25
|
+
type = 'auto',
|
|
26
|
+
as: Element = 'div',
|
|
27
|
+
class: className,
|
|
28
|
+
anchorName,
|
|
29
|
+
positionArea = 'block-end center',
|
|
30
|
+
iconResolver = (name: string) => name,
|
|
31
|
+
} = Astro.props;
|
|
32
|
+
|
|
33
|
+
const styles = anchorName
|
|
34
|
+
? `position-anchor: --${anchorName}; position-area: ${positionArea}`
|
|
35
|
+
: undefined;
|
|
36
|
+
|
|
37
|
+
const anchorStyles = anchorName ? `anchor-name: --${anchorName}` : undefined;
|
|
38
|
+
|
|
39
|
+
const { class: triggerAttrClass, ...triggerRestAttrs } = trigger?.attrs ?? {};
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
{/* Trigger button - slot with default fallback */}
|
|
43
|
+
<slot name="trigger">
|
|
44
|
+
<button
|
|
45
|
+
popovertarget={id}
|
|
46
|
+
class:list={['popover-trigger', trigger?.class, triggerAttrClass]}
|
|
47
|
+
style={anchorStyles}
|
|
48
|
+
aria-label={trigger?.label || 'Open'}
|
|
49
|
+
{...triggerRestAttrs}
|
|
50
|
+
>
|
|
51
|
+
<span class={trigger?.icon ? 'sr-only' : ''}>{trigger?.label || 'Open'}</span>
|
|
52
|
+
{trigger?.icon && <Icon name={iconResolver(trigger.icon)} class="not-sr-only" />}
|
|
53
|
+
</button>
|
|
54
|
+
</slot>
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
<Element
|
|
58
|
+
id={id}
|
|
59
|
+
popover={type}
|
|
60
|
+
class:list={["popover", className]}
|
|
61
|
+
style={styles}
|
|
62
|
+
>
|
|
63
|
+
<slot />
|
|
64
|
+
</Element>
|
|
65
|
+
|
|
66
|
+
<style>
|
|
67
|
+
.popover {
|
|
68
|
+
margin: 0;
|
|
69
|
+
inset: auto;
|
|
70
|
+
border: none;
|
|
71
|
+
padding: 0;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
.popover[popover]::backdrop {
|
|
75
|
+
background: rgba(0, 0, 0, 0.5);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.popover-trigger {
|
|
79
|
+
display: inline-flex;
|
|
80
|
+
align-items: center;
|
|
81
|
+
gap: 0.5rem;
|
|
82
|
+
background: none;
|
|
83
|
+
border: none;
|
|
84
|
+
cursor: pointer;
|
|
85
|
+
padding: 0;
|
|
86
|
+
font-size: inherit;
|
|
87
|
+
font-family: inherit;
|
|
88
|
+
color: inherit;
|
|
89
|
+
}
|
|
90
|
+
</style>
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
---
|
|
2
|
+
import type { HTMLAttributes } from 'astro/types';
|
|
3
|
+
import { parseRenderedSlots, getAttribute, serialize, styleList } from '@getvitops/utils';
|
|
4
|
+
|
|
5
|
+
export interface Props extends HTMLAttributes<'ul'> {};
|
|
6
|
+
|
|
7
|
+
const { class: className, style } = Astro.props;
|
|
8
|
+
|
|
9
|
+
const nodes = parseRenderedSlots(await Astro.slots.render('default'));
|
|
10
|
+
const slots = nodes.filter(node => 'tagName' in node).map(node => ({ html: serialize(node), attrs: node.attrs }));
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
<style>
|
|
14
|
+
ul {
|
|
15
|
+
grid-template-columns: repeat(var(--cols, var(--_cols, 1)), minmax(0, 1fr));
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
li {
|
|
19
|
+
--current-row: round(down, calc((sibling-index() - 1) / var(--cols)), 1);
|
|
20
|
+
|
|
21
|
+
/* Apply the extra gap ONLY if the row index is > 0.
|
|
22
|
+
We use clamp to ensure the multiplier is either 0 or 1.
|
|
23
|
+
*/
|
|
24
|
+
--row-gap-multiplier: clamp(0, var(--current-row), 1);
|
|
25
|
+
margin-block-start: calc(var(--row-gap-multiplier) * var(--row-margin));
|
|
26
|
+
}
|
|
27
|
+
</style>
|
|
28
|
+
<ul class:list={["grid", className]} style={styleList({'--num-items': slots.length}, style)}>
|
|
29
|
+
{slots.map((slot) => (
|
|
30
|
+
<li class:list={["grid grid-rows-subgrid row-span-(--row-span)", getAttribute(slot, 'class')]} style={styleList(getAttribute(slot, 'style'))} set:html={slot.html}></li>
|
|
31
|
+
))}
|
|
32
|
+
</ul>
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
---
|
|
2
|
+
/**
|
|
3
|
+
* Generic loader for web components.
|
|
4
|
+
* Uses import.meta.glob to include all WC*.ts files in the dependency graph,
|
|
5
|
+
* then dynamically loads the requested one on the client.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* <WebComponentLoader src="WCColorSchemeToggle" />
|
|
9
|
+
* <color-scheme-toggle></color-scheme-toggle>
|
|
10
|
+
*/
|
|
11
|
+
interface Props {
|
|
12
|
+
src: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const { src } = Astro.props;
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
<wc-loader data-src={src}></wc-loader>
|
|
19
|
+
|
|
20
|
+
<script>
|
|
21
|
+
// All web components matching this pattern are included in the build
|
|
22
|
+
const modules = import.meta.glob<{ default?: unknown }>(
|
|
23
|
+
'/src/components/web-components/WC*.ts'
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
// Load requested modules
|
|
27
|
+
document.querySelectorAll<HTMLElement>('wc-loader[data-src]').forEach(async (el) => {
|
|
28
|
+
const name = el.dataset.src;
|
|
29
|
+
if (!name) return;
|
|
30
|
+
|
|
31
|
+
// Build the full path from the component name
|
|
32
|
+
const path = `/src/components/web-components/${name}.ts`;
|
|
33
|
+
const loader = modules[path];
|
|
34
|
+
|
|
35
|
+
if (loader) {
|
|
36
|
+
await loader();
|
|
37
|
+
el.remove(); // Clean up the loader element
|
|
38
|
+
} else {
|
|
39
|
+
console.warn(`[WebComponentLoader] Module not found: ${path}`);
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
</script>
|
|
43
|
+
|
|
44
|
+
<style>
|
|
45
|
+
wc-loader {
|
|
46
|
+
display: none;
|
|
47
|
+
}
|
|
48
|
+
</style>
|