@skhema/embed 0.1.9 → 0.1.10

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.
Files changed (40) hide show
  1. package/README.md +55 -0
  2. package/dist/components/SkhemaComponent.d.ts +0 -1
  3. package/dist/components/SkhemaComponent.d.ts.map +1 -1
  4. package/dist/components/SkhemaElement.d.ts.map +1 -1
  5. package/dist/embed.min.js +1 -1
  6. package/dist/embed.min.js.map +1 -1
  7. package/dist/index-DBeyneZT.cjs +215 -0
  8. package/dist/index-DBeyneZT.cjs.map +1 -0
  9. package/dist/index-jwx5CXLB.js +216 -0
  10. package/dist/index-jwx5CXLB.js.map +1 -0
  11. package/dist/index.cjs +79 -440
  12. package/dist/index.cjs.map +1 -1
  13. package/dist/index.es.js +81 -442
  14. package/dist/index.es.js.map +1 -1
  15. package/dist/render/index.d.ts +55 -0
  16. package/dist/render/index.d.ts.map +1 -0
  17. package/dist/render.cjs +7 -0
  18. package/dist/render.cjs.map +1 -0
  19. package/dist/render.d.ts +2 -0
  20. package/dist/render.es.js +7 -0
  21. package/dist/render.es.js.map +1 -0
  22. package/dist/styles/design-tokens.d.ts +32 -4
  23. package/dist/styles/design-tokens.d.ts.map +1 -1
  24. package/dist/tokens.cjs +1 -1
  25. package/dist/tokens.d.ts +1 -1
  26. package/dist/tokens.d.ts.map +1 -1
  27. package/dist/tokens.es.js +1 -1
  28. package/dist/utils/color.d.ts +25 -0
  29. package/dist/utils/color.d.ts.map +1 -0
  30. package/dist/utils/sanitization.d.ts +7 -1
  31. package/dist/utils/sanitization.d.ts.map +1 -1
  32. package/dist/{validation-CNWaQfh_.cjs → validation-DCw9rQeH.cjs} +40 -95
  33. package/dist/validation-DCw9rQeH.cjs.map +1 -0
  34. package/dist/{validation-MSiRF5Wt.js → validation-Dt4L_df8.js} +42 -97
  35. package/dist/validation-Dt4L_df8.js.map +1 -0
  36. package/package.json +11 -4
  37. package/dist/utils/author-attribution.d.ts +0 -16
  38. package/dist/utils/author-attribution.d.ts.map +0 -1
  39. package/dist/validation-CNWaQfh_.cjs.map +0 -1
  40. package/dist/validation-MSiRF5Wt.js.map +0 -1
@@ -31,6 +31,38 @@ export declare const COMPONENT_COLORS: {
31
31
  };
32
32
  };
33
33
  export type ComponentColorKey = keyof typeof COMPONENT_COLORS;
34
+ /**
35
+ * Email-safe flat hex palette for the card surface, per theme. These mirror the
36
+ * Tailwind slate values behind the `CARD_VARS` HSL tokens, pre-resolved to hex
37
+ * because email clients ignore `<style>` / CSS vars. The card renderer
38
+ * (`@skhema/embed/render`) inlines these so the live browser embed and email
39
+ * share one colour system (see the "converge on hex" decision).
40
+ */
41
+ export declare const CARD_PALETTE: {
42
+ readonly light: {
43
+ readonly cardBg: "#ffffff";
44
+ readonly border: "#e2e8f0";
45
+ readonly text: "#020817";
46
+ readonly textMuted: "#64748b";
47
+ };
48
+ readonly dark: {
49
+ readonly cardBg: "#020817";
50
+ readonly border: "#1e293b";
51
+ readonly text: "#f8fafc";
52
+ readonly textMuted: "#94a3b8";
53
+ };
54
+ };
55
+ export type CardTheme = keyof typeof CARD_PALETTE;
56
+ /** Save-button brand colours (hex) — match the `--skhema-primary*` HSL tokens. */
57
+ export declare const PRIMARY_HEX = "#cd476a";
58
+ export declare const PRIMARY_HOVER_HEX = "#b53d5e";
59
+ export declare const PRIMARY_PRESSED_HEX = "#9d3552";
60
+ /** Card geometry / typography shared by the renderer and the browser embed. */
61
+ export declare const CARD_RADIUS = "4px";
62
+ export declare const CARD_SHADOW = "0 1px 3px 0 rgba(0,0,0,0.1), 0 1px 2px -1px rgba(0,0,0,0.1)";
63
+ export declare const CARD_SHADOW_LG = "0 10px 15px -3px rgba(0,0,0,0.1), 0 4px 6px -4px rgba(0,0,0,0.1)";
64
+ export declare const FONT_STACK = "-apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Inter', sans-serif";
65
+ export declare const MONO_STACK = "ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, monospace";
34
66
  /**
35
67
  * Shared base card CSS variables for light and dark modes.
36
68
  */
@@ -54,10 +86,6 @@ export declare const CARD_VARS: {
54
86
  readonly '--skhema-surface-subtle': "hsl(217.2 32.6% 12%)";
55
87
  };
56
88
  };
57
- /**
58
- * Shared card structure styles (used by both SkhemaElement and SkhemaComponent).
59
- */
60
- export declare const SHARED_CARD_STYLES = "\n/* Monospace acronym badge */\n.skhema-acronym-badge {\n display: inline-flex;\n align-items: center;\n font-family: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, monospace;\n font-size: 10px;\n font-weight: 600;\n padding: 2px 6px;\n border-radius: 2px;\n letter-spacing: 0.02em;\n flex-shrink: 0;\n}\n\n/* Footer attribution tagline */\n.skhema-footer-attribution {\n font-size: 11px;\n color: var(--skhema-text-muted);\n line-height: 1.4;\n}\n\n.skhema-footer-attribution a {\n color: var(--skhema-text-muted);\n text-decoration: underline;\n text-decoration-color: var(--skhema-border);\n text-underline-offset: 2px;\n}\n\n.skhema-footer-attribution a:hover {\n color: var(--skhema-text);\n}\n\n/* Contributor line in footer */\n.skhema-contributor-line {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 12px;\n color: var(--skhema-text-muted);\n}\n\n.skhema-contributor-line svg {\n width: 14px;\n height: 14px;\n flex-shrink: 0;\n}\n\n.skhema-author-link {\n color: inherit;\n text-decoration: none;\n}\n\n.skhema-author-link:hover {\n color: var(--skhema-text);\n text-decoration: underline;\n}\n\n/* Save button \u2014 primary brand color */\n.skhema-save-btn {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n background: var(--skhema-primary);\n color: white;\n border: none;\n padding: 6px 14px;\n border-radius: calc(var(--skhema-radius) + 2px);\n font-size: 12px;\n font-weight: 500;\n text-decoration: none;\n cursor: pointer;\n transition: background 0.15s ease;\n white-space: nowrap;\n}\n\n.skhema-save-btn:hover {\n background: var(--skhema-primary-hover);\n}\n\n.skhema-save-btn:active {\n background: var(--skhema-primary-pressed);\n}\n\n.skhema-save-btn:focus {\n outline: 2px solid var(--skhema-primary);\n outline-offset: 2px;\n}\n\n.skhema-save-btn::after {\n content: '\\2192';\n font-size: 13px;\n}\n";
61
89
  /**
62
90
  * User SVG icon (inline, no external deps).
63
91
  */
@@ -1 +1 @@
1
- {"version":3,"file":"design-tokens.d.ts","sourceRoot":"","sources":["../../src/styles/design-tokens.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;;CA0BnB,CAAA;AAEV,MAAM,MAAM,iBAAiB,GAAG,MAAM,OAAO,gBAAgB,CAAA;AAE7D;;GAEG;AACH,eAAO,MAAM,SAAS;;;;;;;;;;;;;;;;;;;CAmBZ,CAAA;AAEV;;GAEG;AACH,eAAO,MAAM,kBAAkB,m4DA4F9B,CAAA;AAED;;GAEG;AACH,eAAO,MAAM,aAAa,kRAA4P,CAAA"}
1
+ {"version":3,"file":"design-tokens.d.ts","sourceRoot":"","sources":["../../src/styles/design-tokens.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;;CA0BnB,CAAA;AAEV,MAAM,MAAM,iBAAiB,GAAG,MAAM,OAAO,gBAAgB,CAAA;AAE7D;;;;;;GAMG;AACH,eAAO,MAAM,YAAY;;;;;;;;;;;;;CAaf,CAAA;AAEV,MAAM,MAAM,SAAS,GAAG,MAAM,OAAO,YAAY,CAAA;AAEjD,kFAAkF;AAClF,eAAO,MAAM,WAAW,YAAY,CAAA;AACpC,eAAO,MAAM,iBAAiB,YAAY,CAAA;AAC1C,eAAO,MAAM,mBAAmB,YAAY,CAAA;AAE5C,+EAA+E;AAC/E,eAAO,MAAM,WAAW,QAAQ,CAAA;AAChC,eAAO,MAAM,WAAW,gEACuC,CAAA;AAC/D,eAAO,MAAM,cAAc,qEACyC,CAAA;AACpE,eAAO,MAAM,UAAU,iFACyD,CAAA;AAChF,eAAO,MAAM,UAAU,wEACgD,CAAA;AAEvE;;GAEG;AACH,eAAO,MAAM,SAAS;;;;;;;;;;;;;;;;;;;CAmBZ,CAAA;AAEV;;GAEG;AACH,eAAO,MAAM,aAAa,kRAA4P,CAAA"}
package/dist/tokens.cjs CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
- const validation = require("./validation-CNWaQfh_.cjs");
3
+ const validation = require("./validation-DCw9rQeH.cjs");
4
4
  exports.CARD_VARS = validation.CARD_VARS;
5
5
  exports.COMPONENT_COLORS = validation.COMPONENT_COLORS;
6
6
  exports.USER_ICON_SVG = validation.USER_ICON_SVG;
package/dist/tokens.d.ts CHANGED
@@ -10,7 +10,7 @@
10
10
  *
11
11
  * Everything re-exported below is pure (no DOM, no side effects).
12
12
  */
13
- export { COMPONENT_COLORS, CARD_VARS, USER_ICON_SVG, } from './styles/design-tokens.js';
13
+ export { CARD_VARS, COMPONENT_COLORS, USER_ICON_SVG, } from './styles/design-tokens.js';
14
14
  export type { ComponentColorKey } from './styles/design-tokens.js';
15
15
  export { getComponentTypeAcronym, getComponentTypeLabel, getElementTypesForComponent, isValidComponentType, resolveComponentType, validateElementBelongsToComponent, } from './utils/component-validation.js';
16
16
  export { getElementTypeAcronym, getElementTypeLabel, isValidElementType, } from './utils/validation.js';
@@ -1 +1 @@
1
- {"version":3,"file":"tokens.d.ts","sourceRoot":"","sources":["../src/tokens.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,EACL,gBAAgB,EAChB,SAAS,EACT,aAAa,GACd,MAAM,2BAA2B,CAAA;AAClC,YAAY,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAA;AAElE,OAAO,EACL,uBAAuB,EACvB,qBAAqB,EACrB,2BAA2B,EAC3B,oBAAoB,EACpB,oBAAoB,EACpB,iCAAiC,GAClC,MAAM,iCAAiC,CAAA;AAExC,OAAO,EACL,qBAAqB,EACrB,mBAAmB,EACnB,kBAAkB,GACnB,MAAM,uBAAuB,CAAA"}
1
+ {"version":3,"file":"tokens.d.ts","sourceRoot":"","sources":["../src/tokens.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,EACL,SAAS,EACT,gBAAgB,EAChB,aAAa,GACd,MAAM,2BAA2B,CAAA;AAClC,YAAY,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAA;AAElE,OAAO,EACL,uBAAuB,EACvB,qBAAqB,EACrB,2BAA2B,EAC3B,oBAAoB,EACpB,oBAAoB,EACpB,iCAAiC,GAClC,MAAM,iCAAiC,CAAA;AAExC,OAAO,EACL,qBAAqB,EACrB,mBAAmB,EACnB,kBAAkB,GACnB,MAAM,uBAAuB,CAAA"}
package/dist/tokens.es.js CHANGED
@@ -1,4 +1,4 @@
1
- import { C, a, U, g, b, c, d, e, i, f, r, v } from "./validation-MSiRF5Wt.js";
1
+ import { C, a, U, g, b, c, d, e, i, f, r, v } from "./validation-Dt4L_df8.js";
2
2
  export {
3
3
  C as CARD_VARS,
4
4
  a as COMPONENT_COLORS,
@@ -0,0 +1,25 @@
1
+ /**
2
+ * DOM-free colour utilities for the email-safe card renderer.
3
+ *
4
+ * The card palette (`COMPONENT_COLORS`) is authored in `oklch()` for the live
5
+ * browser embed, but email clients can't parse `oklch()` and `<style>` is
6
+ * stripped, so every colour must be a pre-computed sRGB hex with all styles
7
+ * inlined. This converts a single `oklch(...)` token to hex, compositing any
8
+ * alpha over a flat background (white for light cards, the dark card surface
9
+ * for dark cards) so the result is opaque and email-safe.
10
+ *
11
+ * This is the single source of the conversion that `sk comms curated` and the
12
+ * `CuratedElements` email template previously duplicated.
13
+ */
14
+ /** Brand pink (hsl(344 57% 54%)) — fallback when a component palette is missing. */
15
+ export declare const BRAND_PINK = "#cd476a";
16
+ /**
17
+ * Convert a single `oklch(...)` token to an email-safe sRGB hex. When the token
18
+ * carries an alpha (e.g. the badge `bg` @ 0.15 / `border` @ 0.3), the colour is
19
+ * composited over `overHex` so the result is a flat opaque hex.
20
+ *
21
+ * @param token An `oklch(L C H)` or `oklch(L C H / A)` string.
22
+ * @param overHex The flat background to composite alpha over (default white).
23
+ */
24
+ export declare function oklchToHex(token: string, overHex?: string): string;
25
+ //# sourceMappingURL=color.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"color.d.ts","sourceRoot":"","sources":["../../src/utils/color.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,oFAAoF;AACpF,eAAO,MAAM,UAAU,YAAY,CAAA;AAWnC;;;;;;;GAOG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,SAAY,GAAG,MAAM,CAqCrE"}
@@ -1,5 +1,11 @@
1
1
  /**
2
- * Content sanitization utilities for Skhema web component
2
+ * Content sanitization utilities for Skhema cards.
3
+ *
4
+ * DOM-free by design: `sanitizeContent` is imported by the email-safe card
5
+ * renderer (`@skhema/embed/render`), which must run in Node / email / CLI
6
+ * runtimes with no `document`. Keep every export here free of DOM access at
7
+ * call time (the legacy `stripHtml` below is the one exception and is not used
8
+ * by the renderer).
3
9
  */
4
10
  /**
5
11
  * Sanitizes content to prevent XSS attacks and removes URLs
@@ -1 +1 @@
1
- {"version":3,"file":"sanitization.d.ts","sourceRoot":"","sources":["../../src/utils/sanitization.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAqBvD;AA4DD;;;;GAIG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,MAAM,GAAG;IACxD,QAAQ,EAAE,OAAO,CAAA;IACjB,MAAM,EAAE,MAAM,EAAE,CAAA;CACjB,CAqCA;AAED;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAIjD;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAUrD"}
1
+ {"version":3,"file":"sanitization.d.ts","sourceRoot":"","sources":["../../src/utils/sanitization.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAcH;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAcvD;AA4DD;;;;GAIG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,MAAM,GAAG;IACxD,QAAQ,EAAE,OAAO,CAAA;IACjB,MAAM,EAAE,MAAM,EAAE,CAAA;CACjB,CAqCA;AAED;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAIjD;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAUrD"}
@@ -27,6 +27,36 @@ const COMPONENT_COLORS = {
27
27
  border: "oklch(0.65 0.06 65 / 0.3)"
28
28
  }
29
29
  };
30
+ const CARD_PALETTE = {
31
+ light: {
32
+ cardBg: "#ffffff",
33
+ // hsl(0 0% 100%)
34
+ border: "#e2e8f0",
35
+ // hsl(214.3 31.8% 91.4%) — slate-200
36
+ text: "#020817",
37
+ // hsl(222.2 84% 4.9%) — slate-950
38
+ textMuted: "#64748b"
39
+ // hsl(215.4 16.3% 46.9%) — slate-500
40
+ },
41
+ dark: {
42
+ cardBg: "#020817",
43
+ // hsl(222.2 84% 4.9%) — slate-950
44
+ border: "#1e293b",
45
+ // hsl(217.2 32.6% 17.5%) — slate-800
46
+ text: "#f8fafc",
47
+ // hsl(210 40% 98%) — slate-50
48
+ textMuted: "#94a3b8"
49
+ // hsl(215 20.2% 65.1%) — slate-400
50
+ }
51
+ };
52
+ const PRIMARY_HEX = "#cd476a";
53
+ const PRIMARY_HOVER_HEX = "#b53d5e";
54
+ const PRIMARY_PRESSED_HEX = "#9d3552";
55
+ const CARD_RADIUS = "4px";
56
+ const CARD_SHADOW = "0 1px 3px 0 rgba(0,0,0,0.1), 0 1px 2px -1px rgba(0,0,0,0.1)";
57
+ const CARD_SHADOW_LG = "0 10px 15px -3px rgba(0,0,0,0.1), 0 4px 6px -4px rgba(0,0,0,0.1)";
58
+ const FONT_STACK = "-apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Inter', sans-serif";
59
+ const MONO_STACK = "ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, monospace";
30
60
  const CARD_VARS = {
31
61
  light: {
32
62
  "--skhema-bg": "hsl(0 0% 100%)",
@@ -47,99 +77,6 @@ const CARD_VARS = {
47
77
  "--skhema-surface-subtle": "hsl(217.2 32.6% 12%)"
48
78
  }
49
79
  };
50
- const SHARED_CARD_STYLES = `
51
- /* Monospace acronym badge */
52
- .skhema-acronym-badge {
53
- display: inline-flex;
54
- align-items: center;
55
- font-family: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, monospace;
56
- font-size: 10px;
57
- font-weight: 600;
58
- padding: 2px 6px;
59
- border-radius: 2px;
60
- letter-spacing: 0.02em;
61
- flex-shrink: 0;
62
- }
63
-
64
- /* Footer attribution tagline */
65
- .skhema-footer-attribution {
66
- font-size: 11px;
67
- color: var(--skhema-text-muted);
68
- line-height: 1.4;
69
- }
70
-
71
- .skhema-footer-attribution a {
72
- color: var(--skhema-text-muted);
73
- text-decoration: underline;
74
- text-decoration-color: var(--skhema-border);
75
- text-underline-offset: 2px;
76
- }
77
-
78
- .skhema-footer-attribution a:hover {
79
- color: var(--skhema-text);
80
- }
81
-
82
- /* Contributor line in footer */
83
- .skhema-contributor-line {
84
- display: flex;
85
- align-items: center;
86
- gap: 6px;
87
- font-size: 12px;
88
- color: var(--skhema-text-muted);
89
- }
90
-
91
- .skhema-contributor-line svg {
92
- width: 14px;
93
- height: 14px;
94
- flex-shrink: 0;
95
- }
96
-
97
- .skhema-author-link {
98
- color: inherit;
99
- text-decoration: none;
100
- }
101
-
102
- .skhema-author-link:hover {
103
- color: var(--skhema-text);
104
- text-decoration: underline;
105
- }
106
-
107
- /* Save button — primary brand color */
108
- .skhema-save-btn {
109
- display: inline-flex;
110
- align-items: center;
111
- gap: 6px;
112
- background: var(--skhema-primary);
113
- color: white;
114
- border: none;
115
- padding: 6px 14px;
116
- border-radius: calc(var(--skhema-radius) + 2px);
117
- font-size: 12px;
118
- font-weight: 500;
119
- text-decoration: none;
120
- cursor: pointer;
121
- transition: background 0.15s ease;
122
- white-space: nowrap;
123
- }
124
-
125
- .skhema-save-btn:hover {
126
- background: var(--skhema-primary-hover);
127
- }
128
-
129
- .skhema-save-btn:active {
130
- background: var(--skhema-primary-pressed);
131
- }
132
-
133
- .skhema-save-btn:focus {
134
- outline: 2px solid var(--skhema-primary);
135
- outline-offset: 2px;
136
- }
137
-
138
- .skhema-save-btn::after {
139
- content: '\\2192';
140
- font-size: 13px;
141
- }
142
- `;
143
80
  const USER_ICON_SVG = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/></svg>`;
144
81
  function isValidComponentType(componentType) {
145
82
  const validTypes = Object.values(types.COMPONENT_TYPES).map((t) => t.value);
@@ -214,9 +151,17 @@ function getElementTypeAcronym(elementType) {
214
151
  const type = Object.values(types.ELEMENT_TYPES).find((t) => t.value === elementType);
215
152
  return type?.acronym || elementType.substring(0, 2).toUpperCase();
216
153
  }
154
+ exports.CARD_PALETTE = CARD_PALETTE;
155
+ exports.CARD_RADIUS = CARD_RADIUS;
156
+ exports.CARD_SHADOW = CARD_SHADOW;
157
+ exports.CARD_SHADOW_LG = CARD_SHADOW_LG;
217
158
  exports.CARD_VARS = CARD_VARS;
218
159
  exports.COMPONENT_COLORS = COMPONENT_COLORS;
219
- exports.SHARED_CARD_STYLES = SHARED_CARD_STYLES;
160
+ exports.FONT_STACK = FONT_STACK;
161
+ exports.MONO_STACK = MONO_STACK;
162
+ exports.PRIMARY_HEX = PRIMARY_HEX;
163
+ exports.PRIMARY_HOVER_HEX = PRIMARY_HOVER_HEX;
164
+ exports.PRIMARY_PRESSED_HEX = PRIMARY_PRESSED_HEX;
220
165
  exports.USER_ICON_SVG = USER_ICON_SVG;
221
166
  exports.getComponentTypeAcronym = getComponentTypeAcronym;
222
167
  exports.getComponentTypeLabel = getComponentTypeLabel;
@@ -228,4 +173,4 @@ exports.isValidElementType = isValidElementType;
228
173
  exports.resolveComponentType = resolveComponentType;
229
174
  exports.validateAttributes = validateAttributes;
230
175
  exports.validateElementBelongsToComponent = validateElementBelongsToComponent;
231
- //# sourceMappingURL=validation-CNWaQfh_.cjs.map
176
+ //# sourceMappingURL=validation-DCw9rQeH.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validation-DCw9rQeH.cjs","sources":["../src/styles/design-tokens.ts","../src/utils/component-validation.ts","../src/utils/validation.ts"],"sourcesContent":["/**\n * Hardcoded color tokens for component-type badge colors.\n * Embeds can't rely on host page CSS vars, so we use inline values.\n * Source: skhema-web/src/app/globals.css oklch tokens converted to HSL-ish for browser compat.\n */\n\nexport const COMPONENT_COLORS = {\n diagnosis: {\n bg: 'oklch(0.65 0.06 25 / 0.15)',\n text: 'oklch(0.65 0.06 25)',\n border: 'oklch(0.65 0.06 25 / 0.3)',\n },\n method: {\n bg: 'oklch(0.6 0.06 250 / 0.15)',\n text: 'oklch(0.6 0.06 250)',\n border: 'oklch(0.6 0.06 250 / 0.3)',\n },\n initiatives: {\n bg: 'oklch(0.6 0.06 155 / 0.15)',\n text: 'oklch(0.6 0.06 155)',\n border: 'oklch(0.6 0.06 155 / 0.3)',\n },\n measures: {\n bg: 'oklch(0.6 0.06 300 / 0.15)',\n text: 'oklch(0.6 0.06 300)',\n border: 'oklch(0.6 0.06 300 / 0.3)',\n },\n support: {\n bg: 'oklch(0.65 0.06 65 / 0.15)',\n text: 'oklch(0.65 0.06 65)',\n border: 'oklch(0.65 0.06 65 / 0.3)',\n },\n} as const\n\nexport type ComponentColorKey = keyof typeof COMPONENT_COLORS\n\n/**\n * Email-safe flat hex palette for the card surface, per theme. These mirror the\n * Tailwind slate values behind the `CARD_VARS` HSL tokens, pre-resolved to hex\n * because email clients ignore `<style>` / CSS vars. The card renderer\n * (`@skhema/embed/render`) inlines these so the live browser embed and email\n * share one colour system (see the \"converge on hex\" decision).\n */\nexport const CARD_PALETTE = {\n light: {\n cardBg: '#ffffff', // hsl(0 0% 100%)\n border: '#e2e8f0', // hsl(214.3 31.8% 91.4%) — slate-200\n text: '#020817', // hsl(222.2 84% 4.9%) — slate-950\n textMuted: '#64748b', // hsl(215.4 16.3% 46.9%) — slate-500\n },\n dark: {\n cardBg: '#020817', // hsl(222.2 84% 4.9%) — slate-950\n border: '#1e293b', // hsl(217.2 32.6% 17.5%) — slate-800\n text: '#f8fafc', // hsl(210 40% 98%) — slate-50\n textMuted: '#94a3b8', // hsl(215 20.2% 65.1%) — slate-400\n },\n} as const\n\nexport type CardTheme = keyof typeof CARD_PALETTE\n\n/** Save-button brand colours (hex) — match the `--skhema-primary*` HSL tokens. */\nexport const PRIMARY_HEX = '#cd476a' // hsl(344 57% 54%)\nexport const PRIMARY_HOVER_HEX = '#b53d5e' // hsl(344 50% 47%)\nexport const PRIMARY_PRESSED_HEX = '#9d3552' // hsl(343 50% 41%)\n\n/** Card geometry / typography shared by the renderer and the browser embed. */\nexport const CARD_RADIUS = '4px' // calc(0.1rem + 2px) ≈ 3.6px\nexport const CARD_SHADOW =\n '0 1px 3px 0 rgba(0,0,0,0.1), 0 1px 2px -1px rgba(0,0,0,0.1)'\nexport const CARD_SHADOW_LG =\n '0 10px 15px -3px rgba(0,0,0,0.1), 0 4px 6px -4px rgba(0,0,0,0.1)'\nexport const FONT_STACK =\n \"-apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Inter', sans-serif\"\nexport const MONO_STACK =\n \"ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, monospace\"\n\n/**\n * Shared base card CSS variables for light and dark modes.\n */\nexport const CARD_VARS = {\n light: {\n '--skhema-bg': 'hsl(0 0% 100%)',\n '--skhema-card': 'hsl(0 0% 100%)',\n '--skhema-border': 'hsl(214.3 31.8% 91.4%)',\n '--skhema-text': 'hsl(222.2 84% 4.9%)',\n '--skhema-text-muted': 'hsl(215.4 16.3% 46.9%)',\n '--skhema-accent': 'hsl(210 40% 96%)',\n '--skhema-surface-subtle': 'hsl(210 40% 97%)',\n },\n dark: {\n '--skhema-bg': 'hsl(222.2 84% 4.9%)',\n '--skhema-card': 'hsl(222.2 84% 4.9%)',\n '--skhema-border': 'hsl(217.2 32.6% 17.5%)',\n '--skhema-text': 'hsl(210 40% 98%)',\n '--skhema-text-muted': 'hsl(215 20.2% 65.1%)',\n '--skhema-accent': 'hsl(217.2 32.6% 17.5%)',\n '--skhema-surface-subtle': 'hsl(217.2 32.6% 12%)',\n },\n} as const\n\n/**\n * User SVG icon (inline, no external deps).\n */\nexport const USER_ICON_SVG = `<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2\"/><circle cx=\"12\" cy=\"7\" r=\"4\"/></svg>`\n","import type { ComponentValue, ElementValue } from '@skhema/types'\nimport { COMPONENT_TYPES, SKHEMA_MAPPING } from '@skhema/types'\n\n/**\n * Validate that a string is a valid component type.\n */\nexport function isValidComponentType(\n componentType: string\n): componentType is ComponentValue {\n const validTypes = Object.values(COMPONENT_TYPES).map((t) => t.value)\n return validTypes.includes(componentType as ComponentValue)\n}\n\n/**\n * Validate that an element type belongs to the given component type.\n */\nexport function validateElementBelongsToComponent(\n elementType: string,\n componentType: string\n): boolean {\n if (!isValidComponentType(componentType)) return false\n\n const validElements =\n SKHEMA_MAPPING.elementFlow[\n componentType as keyof typeof SKHEMA_MAPPING.elementFlow\n ]\n if (!validElements) return false\n\n return validElements.some((e) => e.value === elementType)\n}\n\n/**\n * Get the human-readable label for a component type.\n */\nexport function getComponentTypeLabel(componentType: string): string {\n const type = Object.values(COMPONENT_TYPES).find(\n (t) => t.value === componentType\n )\n return type?.label || componentType\n}\n\n/**\n * Get the acronym for a component type (e.g., 'BD' for diagnosis).\n */\nexport function getComponentTypeAcronym(componentType: string): string {\n const type = Object.values(COMPONENT_TYPES).find(\n (t) => t.value === componentType\n )\n return type?.acronym || componentType.substring(0, 2).toUpperCase()\n}\n\n/**\n * Resolve which component type an element belongs to.\n * Returns the component value, or 'diagnosis' as fallback.\n */\nexport function resolveComponentType(elementType: string): ComponentValue {\n for (const [componentValue, elements] of Object.entries(\n SKHEMA_MAPPING.elementFlow\n )) {\n if (elements.some((e) => e.value === elementType)) {\n return componentValue as ComponentValue\n }\n }\n return 'diagnosis'\n}\n\n/**\n * Get all valid element types for a given component, ordered per the mapping.\n */\nexport function getElementTypesForComponent(\n componentType: string\n): ElementValue[] {\n if (!isValidComponentType(componentType)) return []\n const elements =\n SKHEMA_MAPPING.elementFlow[\n componentType as keyof typeof SKHEMA_MAPPING.elementFlow\n ]\n return elements?.map((e) => e.value) || []\n}\n","import type { ElementValue } from '@skhema/types'\nimport { ELEMENT_TYPES } from '@skhema/types'\n\nexport function isValidElementType(\n elementType: string\n): elementType is ElementValue {\n const validTypes = Object.values(ELEMENT_TYPES).map((type) => type.value)\n return validTypes.includes(elementType as ElementValue)\n}\n\nexport function validateAttributes(element: HTMLElement): {\n isValid: boolean\n errors: string[]\n elementType?: ElementValue\n contributorId?: string\n} {\n const errors: string[] = []\n\n const elementType = element.getAttribute('element-type')\n const contributorId = element.getAttribute('contributor-id')\n\n if (!elementType) {\n errors.push('Missing required attribute: element-type')\n } else if (!isValidElementType(elementType)) {\n const validTypes = Object.values(ELEMENT_TYPES)\n .map((t) => t.value)\n .join(', ')\n errors.push(\n `Invalid element-type \"${elementType}\". Valid types: ${validTypes}`\n )\n }\n\n if (!contributorId) {\n errors.push('Missing required attribute: contributor-id')\n } else if (contributorId.trim().length === 0) {\n errors.push('contributor-id cannot be empty')\n }\n\n return {\n isValid: errors.length === 0,\n errors,\n elementType: isValidElementType(elementType || '')\n ? (elementType as ElementValue)\n : undefined,\n contributorId: contributorId || undefined,\n }\n}\n\nexport function getElementTypeLabel(elementType: ElementValue): string {\n const type = Object.values(ELEMENT_TYPES).find((t) => t.value === elementType)\n return type?.label || elementType\n}\n\nexport function getElementTypeAcronym(elementType: ElementValue): string {\n const type = Object.values(ELEMENT_TYPES).find((t) => t.value === elementType)\n return type?.acronym || elementType.substring(0, 2).toUpperCase()\n}\n"],"names":["COMPONENT_TYPES","SKHEMA_MAPPING","ELEMENT_TYPES"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { SKHEMA_MAPPING, COMPONENT_TYPES, ELEMENT_TYPES } from "@skhema/types";
1
+ import { COMPONENT_TYPES, SKHEMA_MAPPING, ELEMENT_TYPES } from "@skhema/types";
2
2
  const COMPONENT_COLORS = {
3
3
  diagnosis: {
4
4
  bg: "oklch(0.65 0.06 25 / 0.15)",
@@ -26,6 +26,36 @@ const COMPONENT_COLORS = {
26
26
  border: "oklch(0.65 0.06 65 / 0.3)"
27
27
  }
28
28
  };
29
+ const CARD_PALETTE = {
30
+ light: {
31
+ cardBg: "#ffffff",
32
+ // hsl(0 0% 100%)
33
+ border: "#e2e8f0",
34
+ // hsl(214.3 31.8% 91.4%) — slate-200
35
+ text: "#020817",
36
+ // hsl(222.2 84% 4.9%) — slate-950
37
+ textMuted: "#64748b"
38
+ // hsl(215.4 16.3% 46.9%) — slate-500
39
+ },
40
+ dark: {
41
+ cardBg: "#020817",
42
+ // hsl(222.2 84% 4.9%) — slate-950
43
+ border: "#1e293b",
44
+ // hsl(217.2 32.6% 17.5%) — slate-800
45
+ text: "#f8fafc",
46
+ // hsl(210 40% 98%) — slate-50
47
+ textMuted: "#94a3b8"
48
+ // hsl(215 20.2% 65.1%) — slate-400
49
+ }
50
+ };
51
+ const PRIMARY_HEX = "#cd476a";
52
+ const PRIMARY_HOVER_HEX = "#b53d5e";
53
+ const PRIMARY_PRESSED_HEX = "#9d3552";
54
+ const CARD_RADIUS = "4px";
55
+ const CARD_SHADOW = "0 1px 3px 0 rgba(0,0,0,0.1), 0 1px 2px -1px rgba(0,0,0,0.1)";
56
+ const CARD_SHADOW_LG = "0 10px 15px -3px rgba(0,0,0,0.1), 0 4px 6px -4px rgba(0,0,0,0.1)";
57
+ const FONT_STACK = "-apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Inter', sans-serif";
58
+ const MONO_STACK = "ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, monospace";
29
59
  const CARD_VARS = {
30
60
  light: {
31
61
  "--skhema-bg": "hsl(0 0% 100%)",
@@ -46,99 +76,6 @@ const CARD_VARS = {
46
76
  "--skhema-surface-subtle": "hsl(217.2 32.6% 12%)"
47
77
  }
48
78
  };
49
- const SHARED_CARD_STYLES = `
50
- /* Monospace acronym badge */
51
- .skhema-acronym-badge {
52
- display: inline-flex;
53
- align-items: center;
54
- font-family: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, monospace;
55
- font-size: 10px;
56
- font-weight: 600;
57
- padding: 2px 6px;
58
- border-radius: 2px;
59
- letter-spacing: 0.02em;
60
- flex-shrink: 0;
61
- }
62
-
63
- /* Footer attribution tagline */
64
- .skhema-footer-attribution {
65
- font-size: 11px;
66
- color: var(--skhema-text-muted);
67
- line-height: 1.4;
68
- }
69
-
70
- .skhema-footer-attribution a {
71
- color: var(--skhema-text-muted);
72
- text-decoration: underline;
73
- text-decoration-color: var(--skhema-border);
74
- text-underline-offset: 2px;
75
- }
76
-
77
- .skhema-footer-attribution a:hover {
78
- color: var(--skhema-text);
79
- }
80
-
81
- /* Contributor line in footer */
82
- .skhema-contributor-line {
83
- display: flex;
84
- align-items: center;
85
- gap: 6px;
86
- font-size: 12px;
87
- color: var(--skhema-text-muted);
88
- }
89
-
90
- .skhema-contributor-line svg {
91
- width: 14px;
92
- height: 14px;
93
- flex-shrink: 0;
94
- }
95
-
96
- .skhema-author-link {
97
- color: inherit;
98
- text-decoration: none;
99
- }
100
-
101
- .skhema-author-link:hover {
102
- color: var(--skhema-text);
103
- text-decoration: underline;
104
- }
105
-
106
- /* Save button — primary brand color */
107
- .skhema-save-btn {
108
- display: inline-flex;
109
- align-items: center;
110
- gap: 6px;
111
- background: var(--skhema-primary);
112
- color: white;
113
- border: none;
114
- padding: 6px 14px;
115
- border-radius: calc(var(--skhema-radius) + 2px);
116
- font-size: 12px;
117
- font-weight: 500;
118
- text-decoration: none;
119
- cursor: pointer;
120
- transition: background 0.15s ease;
121
- white-space: nowrap;
122
- }
123
-
124
- .skhema-save-btn:hover {
125
- background: var(--skhema-primary-hover);
126
- }
127
-
128
- .skhema-save-btn:active {
129
- background: var(--skhema-primary-pressed);
130
- }
131
-
132
- .skhema-save-btn:focus {
133
- outline: 2px solid var(--skhema-primary);
134
- outline-offset: 2px;
135
- }
136
-
137
- .skhema-save-btn::after {
138
- content: '\\2192';
139
- font-size: 13px;
140
- }
141
- `;
142
79
  const USER_ICON_SVG = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/></svg>`;
143
80
  function isValidComponentType(componentType) {
144
81
  const validTypes = Object.values(COMPONENT_TYPES).map((t) => t.value);
@@ -215,7 +152,9 @@ function getElementTypeAcronym(elementType) {
215
152
  }
216
153
  export {
217
154
  CARD_VARS as C,
218
- SHARED_CARD_STYLES as S,
155
+ FONT_STACK as F,
156
+ MONO_STACK as M,
157
+ PRIMARY_HEX as P,
219
158
  USER_ICON_SVG as U,
220
159
  COMPONENT_COLORS as a,
221
160
  getComponentTypeLabel as b,
@@ -224,9 +163,15 @@ export {
224
163
  getElementTypesForComponent as e,
225
164
  isValidElementType as f,
226
165
  getComponentTypeAcronym as g,
227
- validateAttributes as h,
166
+ CARD_PALETTE as h,
228
167
  isValidComponentType as i,
168
+ CARD_RADIUS as j,
169
+ CARD_SHADOW as k,
170
+ CARD_SHADOW_LG as l,
171
+ PRIMARY_HOVER_HEX as m,
172
+ PRIMARY_PRESSED_HEX as n,
173
+ validateAttributes as o,
229
174
  resolveComponentType as r,
230
175
  validateElementBelongsToComponent as v
231
176
  };
232
- //# sourceMappingURL=validation-MSiRF5Wt.js.map
177
+ //# sourceMappingURL=validation-Dt4L_df8.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validation-Dt4L_df8.js","sources":["../src/styles/design-tokens.ts","../src/utils/component-validation.ts","../src/utils/validation.ts"],"sourcesContent":["/**\n * Hardcoded color tokens for component-type badge colors.\n * Embeds can't rely on host page CSS vars, so we use inline values.\n * Source: skhema-web/src/app/globals.css oklch tokens converted to HSL-ish for browser compat.\n */\n\nexport const COMPONENT_COLORS = {\n diagnosis: {\n bg: 'oklch(0.65 0.06 25 / 0.15)',\n text: 'oklch(0.65 0.06 25)',\n border: 'oklch(0.65 0.06 25 / 0.3)',\n },\n method: {\n bg: 'oklch(0.6 0.06 250 / 0.15)',\n text: 'oklch(0.6 0.06 250)',\n border: 'oklch(0.6 0.06 250 / 0.3)',\n },\n initiatives: {\n bg: 'oklch(0.6 0.06 155 / 0.15)',\n text: 'oklch(0.6 0.06 155)',\n border: 'oklch(0.6 0.06 155 / 0.3)',\n },\n measures: {\n bg: 'oklch(0.6 0.06 300 / 0.15)',\n text: 'oklch(0.6 0.06 300)',\n border: 'oklch(0.6 0.06 300 / 0.3)',\n },\n support: {\n bg: 'oklch(0.65 0.06 65 / 0.15)',\n text: 'oklch(0.65 0.06 65)',\n border: 'oklch(0.65 0.06 65 / 0.3)',\n },\n} as const\n\nexport type ComponentColorKey = keyof typeof COMPONENT_COLORS\n\n/**\n * Email-safe flat hex palette for the card surface, per theme. These mirror the\n * Tailwind slate values behind the `CARD_VARS` HSL tokens, pre-resolved to hex\n * because email clients ignore `<style>` / CSS vars. The card renderer\n * (`@skhema/embed/render`) inlines these so the live browser embed and email\n * share one colour system (see the \"converge on hex\" decision).\n */\nexport const CARD_PALETTE = {\n light: {\n cardBg: '#ffffff', // hsl(0 0% 100%)\n border: '#e2e8f0', // hsl(214.3 31.8% 91.4%) — slate-200\n text: '#020817', // hsl(222.2 84% 4.9%) — slate-950\n textMuted: '#64748b', // hsl(215.4 16.3% 46.9%) — slate-500\n },\n dark: {\n cardBg: '#020817', // hsl(222.2 84% 4.9%) — slate-950\n border: '#1e293b', // hsl(217.2 32.6% 17.5%) — slate-800\n text: '#f8fafc', // hsl(210 40% 98%) — slate-50\n textMuted: '#94a3b8', // hsl(215 20.2% 65.1%) — slate-400\n },\n} as const\n\nexport type CardTheme = keyof typeof CARD_PALETTE\n\n/** Save-button brand colours (hex) — match the `--skhema-primary*` HSL tokens. */\nexport const PRIMARY_HEX = '#cd476a' // hsl(344 57% 54%)\nexport const PRIMARY_HOVER_HEX = '#b53d5e' // hsl(344 50% 47%)\nexport const PRIMARY_PRESSED_HEX = '#9d3552' // hsl(343 50% 41%)\n\n/** Card geometry / typography shared by the renderer and the browser embed. */\nexport const CARD_RADIUS = '4px' // calc(0.1rem + 2px) ≈ 3.6px\nexport const CARD_SHADOW =\n '0 1px 3px 0 rgba(0,0,0,0.1), 0 1px 2px -1px rgba(0,0,0,0.1)'\nexport const CARD_SHADOW_LG =\n '0 10px 15px -3px rgba(0,0,0,0.1), 0 4px 6px -4px rgba(0,0,0,0.1)'\nexport const FONT_STACK =\n \"-apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Inter', sans-serif\"\nexport const MONO_STACK =\n \"ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, monospace\"\n\n/**\n * Shared base card CSS variables for light and dark modes.\n */\nexport const CARD_VARS = {\n light: {\n '--skhema-bg': 'hsl(0 0% 100%)',\n '--skhema-card': 'hsl(0 0% 100%)',\n '--skhema-border': 'hsl(214.3 31.8% 91.4%)',\n '--skhema-text': 'hsl(222.2 84% 4.9%)',\n '--skhema-text-muted': 'hsl(215.4 16.3% 46.9%)',\n '--skhema-accent': 'hsl(210 40% 96%)',\n '--skhema-surface-subtle': 'hsl(210 40% 97%)',\n },\n dark: {\n '--skhema-bg': 'hsl(222.2 84% 4.9%)',\n '--skhema-card': 'hsl(222.2 84% 4.9%)',\n '--skhema-border': 'hsl(217.2 32.6% 17.5%)',\n '--skhema-text': 'hsl(210 40% 98%)',\n '--skhema-text-muted': 'hsl(215 20.2% 65.1%)',\n '--skhema-accent': 'hsl(217.2 32.6% 17.5%)',\n '--skhema-surface-subtle': 'hsl(217.2 32.6% 12%)',\n },\n} as const\n\n/**\n * User SVG icon (inline, no external deps).\n */\nexport const USER_ICON_SVG = `<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2\"/><circle cx=\"12\" cy=\"7\" r=\"4\"/></svg>`\n","import type { ComponentValue, ElementValue } from '@skhema/types'\nimport { COMPONENT_TYPES, SKHEMA_MAPPING } from '@skhema/types'\n\n/**\n * Validate that a string is a valid component type.\n */\nexport function isValidComponentType(\n componentType: string\n): componentType is ComponentValue {\n const validTypes = Object.values(COMPONENT_TYPES).map((t) => t.value)\n return validTypes.includes(componentType as ComponentValue)\n}\n\n/**\n * Validate that an element type belongs to the given component type.\n */\nexport function validateElementBelongsToComponent(\n elementType: string,\n componentType: string\n): boolean {\n if (!isValidComponentType(componentType)) return false\n\n const validElements =\n SKHEMA_MAPPING.elementFlow[\n componentType as keyof typeof SKHEMA_MAPPING.elementFlow\n ]\n if (!validElements) return false\n\n return validElements.some((e) => e.value === elementType)\n}\n\n/**\n * Get the human-readable label for a component type.\n */\nexport function getComponentTypeLabel(componentType: string): string {\n const type = Object.values(COMPONENT_TYPES).find(\n (t) => t.value === componentType\n )\n return type?.label || componentType\n}\n\n/**\n * Get the acronym for a component type (e.g., 'BD' for diagnosis).\n */\nexport function getComponentTypeAcronym(componentType: string): string {\n const type = Object.values(COMPONENT_TYPES).find(\n (t) => t.value === componentType\n )\n return type?.acronym || componentType.substring(0, 2).toUpperCase()\n}\n\n/**\n * Resolve which component type an element belongs to.\n * Returns the component value, or 'diagnosis' as fallback.\n */\nexport function resolveComponentType(elementType: string): ComponentValue {\n for (const [componentValue, elements] of Object.entries(\n SKHEMA_MAPPING.elementFlow\n )) {\n if (elements.some((e) => e.value === elementType)) {\n return componentValue as ComponentValue\n }\n }\n return 'diagnosis'\n}\n\n/**\n * Get all valid element types for a given component, ordered per the mapping.\n */\nexport function getElementTypesForComponent(\n componentType: string\n): ElementValue[] {\n if (!isValidComponentType(componentType)) return []\n const elements =\n SKHEMA_MAPPING.elementFlow[\n componentType as keyof typeof SKHEMA_MAPPING.elementFlow\n ]\n return elements?.map((e) => e.value) || []\n}\n","import type { ElementValue } from '@skhema/types'\nimport { ELEMENT_TYPES } from '@skhema/types'\n\nexport function isValidElementType(\n elementType: string\n): elementType is ElementValue {\n const validTypes = Object.values(ELEMENT_TYPES).map((type) => type.value)\n return validTypes.includes(elementType as ElementValue)\n}\n\nexport function validateAttributes(element: HTMLElement): {\n isValid: boolean\n errors: string[]\n elementType?: ElementValue\n contributorId?: string\n} {\n const errors: string[] = []\n\n const elementType = element.getAttribute('element-type')\n const contributorId = element.getAttribute('contributor-id')\n\n if (!elementType) {\n errors.push('Missing required attribute: element-type')\n } else if (!isValidElementType(elementType)) {\n const validTypes = Object.values(ELEMENT_TYPES)\n .map((t) => t.value)\n .join(', ')\n errors.push(\n `Invalid element-type \"${elementType}\". Valid types: ${validTypes}`\n )\n }\n\n if (!contributorId) {\n errors.push('Missing required attribute: contributor-id')\n } else if (contributorId.trim().length === 0) {\n errors.push('contributor-id cannot be empty')\n }\n\n return {\n isValid: errors.length === 0,\n errors,\n elementType: isValidElementType(elementType || '')\n ? (elementType as ElementValue)\n : undefined,\n contributorId: contributorId || undefined,\n }\n}\n\nexport function getElementTypeLabel(elementType: ElementValue): string {\n const type = Object.values(ELEMENT_TYPES).find((t) => t.value === elementType)\n return type?.label || elementType\n}\n\nexport function getElementTypeAcronym(elementType: ElementValue): string {\n const type = Object.values(ELEMENT_TYPES).find((t) => t.value === elementType)\n return type?.acronym || elementType.substring(0, 2).toUpperCase()\n}\n"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@skhema/embed",
3
- "version": "0.1.9",
3
+ "version": "0.1.10",
4
4
  "description": "Embeddable component for integrating strategic elements with Skhema.",
5
5
  "main": "dist/index.cjs",
6
6
  "module": "dist/index.es.js",
@@ -26,6 +26,12 @@
26
26
  "require": "./dist/tokens.cjs",
27
27
  "default": "./dist/tokens.es.js"
28
28
  },
29
+ "./render": {
30
+ "types": "./dist/render.d.ts",
31
+ "import": "./dist/render.es.js",
32
+ "require": "./dist/render.cjs",
33
+ "default": "./dist/render.es.js"
34
+ },
29
35
  "./cdn": {
30
36
  "types": "./dist/cdn.d.ts",
31
37
  "import": "./dist/embed.min.js"
@@ -45,8 +51,8 @@
45
51
  "lint:fix": "eslint . --fix",
46
52
  "format": "prettier --write .",
47
53
  "format:check": "prettier --check .",
48
- "test": "echo \"No tests configured yet\"",
49
- "test:watch": "echo \"No tests configured yet\"",
54
+ "test": "vitest run",
55
+ "test:watch": "vitest",
50
56
  "check": "npm run typecheck && npm run lint && npm run format:check",
51
57
  "check:fix": "npm run format && npm run lint:fix",
52
58
  "preview": "vite preview",
@@ -76,7 +82,8 @@
76
82
  "terser": "^5.0.0",
77
83
  "typescript": "^5.0.0",
78
84
  "vite": "^7.0.0",
79
- "vite-plugin-dts": "^4.0.0"
85
+ "vite-plugin-dts": "^4.0.0",
86
+ "vitest": "^4.0.17"
80
87
  },
81
88
  "publishConfig": {
82
89
  "registry": "https://registry.npmjs.org/"
@@ -1,16 +0,0 @@
1
- /**
2
- * Resolve author display from element attributes.
3
- * Prefer author-name; contributor-name is deprecated.
4
- */
5
- export interface AuthorAttribution {
6
- authorName: string;
7
- authorSlug?: string;
8
- }
9
- export declare function readAuthorAttribution(element: HTMLElement): AuthorAttribution | null;
10
- /**
11
- * Footer HTML: "By {authorName}", linked when authorSlug is set.
12
- */
13
- export declare function formatAuthorAttributionHtml(authorName: string, authorSlug?: string): string;
14
- export declare function formatContributorIdFallback(contributorId: string): string;
15
- export declare function resolveAuthorFooterHtml(element: HTMLElement, contributorId: string): string;
16
- //# sourceMappingURL=author-attribution.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"author-attribution.d.ts","sourceRoot":"","sources":["../../src/utils/author-attribution.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,MAAM,WAAW,iBAAiB;IAChC,UAAU,EAAE,MAAM,CAAA;IAClB,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB;AAED,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,WAAW,GACnB,iBAAiB,GAAG,IAAI,CAY1B;AAUD;;GAEG;AACH,wBAAgB,2BAA2B,CACzC,UAAU,EAAE,MAAM,EAClB,UAAU,CAAC,EAAE,MAAM,GAClB,MAAM,CAMR;AAED,wBAAgB,2BAA2B,CAAC,aAAa,EAAE,MAAM,GAAG,MAAM,CAKzE;AAED,wBAAgB,uBAAuB,CACrC,OAAO,EAAE,WAAW,EACpB,aAAa,EAAE,MAAM,GACpB,MAAM,CASR"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"validation-CNWaQfh_.cjs","sources":["../src/styles/design-tokens.ts","../src/utils/component-validation.ts","../src/utils/validation.ts"],"sourcesContent":["/**\n * Hardcoded color tokens for component-type badge colors.\n * Embeds can't rely on host page CSS vars, so we use inline values.\n * Source: skhema-web/src/app/globals.css oklch tokens converted to HSL-ish for browser compat.\n */\n\nexport const COMPONENT_COLORS = {\n diagnosis: {\n bg: 'oklch(0.65 0.06 25 / 0.15)',\n text: 'oklch(0.65 0.06 25)',\n border: 'oklch(0.65 0.06 25 / 0.3)',\n },\n method: {\n bg: 'oklch(0.6 0.06 250 / 0.15)',\n text: 'oklch(0.6 0.06 250)',\n border: 'oklch(0.6 0.06 250 / 0.3)',\n },\n initiatives: {\n bg: 'oklch(0.6 0.06 155 / 0.15)',\n text: 'oklch(0.6 0.06 155)',\n border: 'oklch(0.6 0.06 155 / 0.3)',\n },\n measures: {\n bg: 'oklch(0.6 0.06 300 / 0.15)',\n text: 'oklch(0.6 0.06 300)',\n border: 'oklch(0.6 0.06 300 / 0.3)',\n },\n support: {\n bg: 'oklch(0.65 0.06 65 / 0.15)',\n text: 'oklch(0.65 0.06 65)',\n border: 'oklch(0.65 0.06 65 / 0.3)',\n },\n} as const\n\nexport type ComponentColorKey = keyof typeof COMPONENT_COLORS\n\n/**\n * Shared base card CSS variables for light and dark modes.\n */\nexport const CARD_VARS = {\n light: {\n '--skhema-bg': 'hsl(0 0% 100%)',\n '--skhema-card': 'hsl(0 0% 100%)',\n '--skhema-border': 'hsl(214.3 31.8% 91.4%)',\n '--skhema-text': 'hsl(222.2 84% 4.9%)',\n '--skhema-text-muted': 'hsl(215.4 16.3% 46.9%)',\n '--skhema-accent': 'hsl(210 40% 96%)',\n '--skhema-surface-subtle': 'hsl(210 40% 97%)',\n },\n dark: {\n '--skhema-bg': 'hsl(222.2 84% 4.9%)',\n '--skhema-card': 'hsl(222.2 84% 4.9%)',\n '--skhema-border': 'hsl(217.2 32.6% 17.5%)',\n '--skhema-text': 'hsl(210 40% 98%)',\n '--skhema-text-muted': 'hsl(215 20.2% 65.1%)',\n '--skhema-accent': 'hsl(217.2 32.6% 17.5%)',\n '--skhema-surface-subtle': 'hsl(217.2 32.6% 12%)',\n },\n} as const\n\n/**\n * Shared card structure styles (used by both SkhemaElement and SkhemaComponent).\n */\nexport const SHARED_CARD_STYLES = `\n/* Monospace acronym badge */\n.skhema-acronym-badge {\n display: inline-flex;\n align-items: center;\n font-family: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, monospace;\n font-size: 10px;\n font-weight: 600;\n padding: 2px 6px;\n border-radius: 2px;\n letter-spacing: 0.02em;\n flex-shrink: 0;\n}\n\n/* Footer attribution tagline */\n.skhema-footer-attribution {\n font-size: 11px;\n color: var(--skhema-text-muted);\n line-height: 1.4;\n}\n\n.skhema-footer-attribution a {\n color: var(--skhema-text-muted);\n text-decoration: underline;\n text-decoration-color: var(--skhema-border);\n text-underline-offset: 2px;\n}\n\n.skhema-footer-attribution a:hover {\n color: var(--skhema-text);\n}\n\n/* Contributor line in footer */\n.skhema-contributor-line {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 12px;\n color: var(--skhema-text-muted);\n}\n\n.skhema-contributor-line svg {\n width: 14px;\n height: 14px;\n flex-shrink: 0;\n}\n\n.skhema-author-link {\n color: inherit;\n text-decoration: none;\n}\n\n.skhema-author-link:hover {\n color: var(--skhema-text);\n text-decoration: underline;\n}\n\n/* Save button — primary brand color */\n.skhema-save-btn {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n background: var(--skhema-primary);\n color: white;\n border: none;\n padding: 6px 14px;\n border-radius: calc(var(--skhema-radius) + 2px);\n font-size: 12px;\n font-weight: 500;\n text-decoration: none;\n cursor: pointer;\n transition: background 0.15s ease;\n white-space: nowrap;\n}\n\n.skhema-save-btn:hover {\n background: var(--skhema-primary-hover);\n}\n\n.skhema-save-btn:active {\n background: var(--skhema-primary-pressed);\n}\n\n.skhema-save-btn:focus {\n outline: 2px solid var(--skhema-primary);\n outline-offset: 2px;\n}\n\n.skhema-save-btn::after {\n content: '\\\\2192';\n font-size: 13px;\n}\n`\n\n/**\n * User SVG icon (inline, no external deps).\n */\nexport const USER_ICON_SVG = `<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2\"/><circle cx=\"12\" cy=\"7\" r=\"4\"/></svg>`\n","import type { ComponentValue, ElementValue } from '@skhema/types'\nimport { COMPONENT_TYPES, SKHEMA_MAPPING } from '@skhema/types'\n\n/**\n * Validate that a string is a valid component type.\n */\nexport function isValidComponentType(\n componentType: string\n): componentType is ComponentValue {\n const validTypes = Object.values(COMPONENT_TYPES).map((t) => t.value)\n return validTypes.includes(componentType as ComponentValue)\n}\n\n/**\n * Validate that an element type belongs to the given component type.\n */\nexport function validateElementBelongsToComponent(\n elementType: string,\n componentType: string\n): boolean {\n if (!isValidComponentType(componentType)) return false\n\n const validElements =\n SKHEMA_MAPPING.elementFlow[\n componentType as keyof typeof SKHEMA_MAPPING.elementFlow\n ]\n if (!validElements) return false\n\n return validElements.some((e) => e.value === elementType)\n}\n\n/**\n * Get the human-readable label for a component type.\n */\nexport function getComponentTypeLabel(componentType: string): string {\n const type = Object.values(COMPONENT_TYPES).find(\n (t) => t.value === componentType\n )\n return type?.label || componentType\n}\n\n/**\n * Get the acronym for a component type (e.g., 'BD' for diagnosis).\n */\nexport function getComponentTypeAcronym(componentType: string): string {\n const type = Object.values(COMPONENT_TYPES).find(\n (t) => t.value === componentType\n )\n return type?.acronym || componentType.substring(0, 2).toUpperCase()\n}\n\n/**\n * Resolve which component type an element belongs to.\n * Returns the component value, or 'diagnosis' as fallback.\n */\nexport function resolveComponentType(elementType: string): ComponentValue {\n for (const [componentValue, elements] of Object.entries(\n SKHEMA_MAPPING.elementFlow\n )) {\n if (elements.some((e) => e.value === elementType)) {\n return componentValue as ComponentValue\n }\n }\n return 'diagnosis'\n}\n\n/**\n * Get all valid element types for a given component, ordered per the mapping.\n */\nexport function getElementTypesForComponent(\n componentType: string\n): ElementValue[] {\n if (!isValidComponentType(componentType)) return []\n const elements =\n SKHEMA_MAPPING.elementFlow[\n componentType as keyof typeof SKHEMA_MAPPING.elementFlow\n ]\n return elements?.map((e) => e.value) || []\n}\n","import type { ElementValue } from '@skhema/types'\nimport { ELEMENT_TYPES } from '@skhema/types'\n\nexport function isValidElementType(\n elementType: string\n): elementType is ElementValue {\n const validTypes = Object.values(ELEMENT_TYPES).map((type) => type.value)\n return validTypes.includes(elementType as ElementValue)\n}\n\nexport function validateAttributes(element: HTMLElement): {\n isValid: boolean\n errors: string[]\n elementType?: ElementValue\n contributorId?: string\n} {\n const errors: string[] = []\n\n const elementType = element.getAttribute('element-type')\n const contributorId = element.getAttribute('contributor-id')\n\n if (!elementType) {\n errors.push('Missing required attribute: element-type')\n } else if (!isValidElementType(elementType)) {\n const validTypes = Object.values(ELEMENT_TYPES)\n .map((t) => t.value)\n .join(', ')\n errors.push(\n `Invalid element-type \"${elementType}\". Valid types: ${validTypes}`\n )\n }\n\n if (!contributorId) {\n errors.push('Missing required attribute: contributor-id')\n } else if (contributorId.trim().length === 0) {\n errors.push('contributor-id cannot be empty')\n }\n\n return {\n isValid: errors.length === 0,\n errors,\n elementType: isValidElementType(elementType || '')\n ? (elementType as ElementValue)\n : undefined,\n contributorId: contributorId || undefined,\n }\n}\n\nexport function getElementTypeLabel(elementType: ElementValue): string {\n const type = Object.values(ELEMENT_TYPES).find((t) => t.value === elementType)\n return type?.label || elementType\n}\n\nexport function getElementTypeAcronym(elementType: ElementValue): string {\n const type = Object.values(ELEMENT_TYPES).find((t) => t.value === elementType)\n return type?.acronym || elementType.substring(0, 2).toUpperCase()\n}\n"],"names":["COMPONENT_TYPES","SKHEMA_MAPPING","ELEMENT_TYPES"],"mappings":";;AAMO,MAAM,mBAAmB;AAAA,EAC9B,WAAW;AAAA,IACT,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,QAAQ;AAAA,EAAA;AAAA,EAEV,QAAQ;AAAA,IACN,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,QAAQ;AAAA,EAAA;AAAA,EAEV,aAAa;AAAA,IACX,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,QAAQ;AAAA,EAAA;AAAA,EAEV,UAAU;AAAA,IACR,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,QAAQ;AAAA,EAAA;AAAA,EAEV,SAAS;AAAA,IACP,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,QAAQ;AAAA,EAAA;AAEZ;AAOO,MAAM,YAAY;AAAA,EACvB,OAAO;AAAA,IACL,eAAe;AAAA,IACf,iBAAiB;AAAA,IACjB,mBAAmB;AAAA,IACnB,iBAAiB;AAAA,IACjB,uBAAuB;AAAA,IACvB,mBAAmB;AAAA,IACnB,2BAA2B;AAAA,EAAA;AAAA,EAE7B,MAAM;AAAA,IACJ,eAAe;AAAA,IACf,iBAAiB;AAAA,IACjB,mBAAmB;AAAA,IACnB,iBAAiB;AAAA,IACjB,uBAAuB;AAAA,IACvB,mBAAmB;AAAA,IACnB,2BAA2B;AAAA,EAAA;AAE/B;AAKO,MAAM,qBAAqB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAiG3B,MAAM,gBAAgB;AC1JtB,SAAS,qBACd,eACiC;AACjC,QAAM,aAAa,OAAO,OAAOA,MAAAA,eAAe,EAAE,IAAI,CAAC,MAAM,EAAE,KAAK;AACpE,SAAO,WAAW,SAAS,aAA+B;AAC5D;AAKO,SAAS,kCACd,aACA,eACS;AACT,MAAI,CAAC,qBAAqB,aAAa,EAAG,QAAO;AAEjD,QAAM,gBACJC,MAAAA,eAAe,YACb,aACF;AACF,MAAI,CAAC,cAAe,QAAO;AAE3B,SAAO,cAAc,KAAK,CAAC,MAAM,EAAE,UAAU,WAAW;AAC1D;AAKO,SAAS,sBAAsB,eAA+B;AACnE,QAAM,OAAO,OAAO,OAAOD,MAAAA,eAAe,EAAE;AAAA,IAC1C,CAAC,MAAM,EAAE,UAAU;AAAA,EAAA;AAErB,SAAO,MAAM,SAAS;AACxB;AAKO,SAAS,wBAAwB,eAA+B;AACrE,QAAM,OAAO,OAAO,OAAOA,MAAAA,eAAe,EAAE;AAAA,IAC1C,CAAC,MAAM,EAAE,UAAU;AAAA,EAAA;AAErB,SAAO,MAAM,WAAW,cAAc,UAAU,GAAG,CAAC,EAAE,YAAA;AACxD;AAMO,SAAS,qBAAqB,aAAqC;AACxE,aAAW,CAAC,gBAAgB,QAAQ,KAAK,OAAO;AAAA,IAC9CC,qBAAe;AAAA,EAAA,GACd;AACD,QAAI,SAAS,KAAK,CAAC,MAAM,EAAE,UAAU,WAAW,GAAG;AACjD,aAAO;AAAA,IACT;AAAA,EACF;AACA,SAAO;AACT;AAKO,SAAS,4BACd,eACgB;AAChB,MAAI,CAAC,qBAAqB,aAAa,UAAU,CAAA;AACjD,QAAM,WACJA,MAAAA,eAAe,YACb,aACF;AACF,SAAO,UAAU,IAAI,CAAC,MAAM,EAAE,KAAK,KAAK,CAAA;AAC1C;AC3EO,SAAS,mBACd,aAC6B;AAC7B,QAAM,aAAa,OAAO,OAAOC,MAAAA,aAAa,EAAE,IAAI,CAAC,SAAS,KAAK,KAAK;AACxE,SAAO,WAAW,SAAS,WAA2B;AACxD;AAEO,SAAS,mBAAmB,SAKjC;AACA,QAAM,SAAmB,CAAA;AAEzB,QAAM,cAAc,QAAQ,aAAa,cAAc;AACvD,QAAM,gBAAgB,QAAQ,aAAa,gBAAgB;AAE3D,MAAI,CAAC,aAAa;AAChB,WAAO,KAAK,0CAA0C;AAAA,EACxD,WAAW,CAAC,mBAAmB,WAAW,GAAG;AAC3C,UAAM,aAAa,OAAO,OAAOA,MAAAA,aAAa,EAC3C,IAAI,CAAC,MAAM,EAAE,KAAK,EAClB,KAAK,IAAI;AACZ,WAAO;AAAA,MACL,yBAAyB,WAAW,mBAAmB,UAAU;AAAA,IAAA;AAAA,EAErE;AAEA,MAAI,CAAC,eAAe;AAClB,WAAO,KAAK,4CAA4C;AAAA,EAC1D,WAAW,cAAc,KAAA,EAAO,WAAW,GAAG;AAC5C,WAAO,KAAK,gCAAgC;AAAA,EAC9C;AAEA,SAAO;AAAA,IACL,SAAS,OAAO,WAAW;AAAA,IAC3B;AAAA,IACA,aAAa,mBAAmB,eAAe,EAAE,IAC5C,cACD;AAAA,IACJ,eAAe,iBAAiB;AAAA,EAAA;AAEpC;AAEO,SAAS,oBAAoB,aAAmC;AACrE,QAAM,OAAO,OAAO,OAAOA,mBAAa,EAAE,KAAK,CAAC,MAAM,EAAE,UAAU,WAAW;AAC7E,SAAO,MAAM,SAAS;AACxB;AAEO,SAAS,sBAAsB,aAAmC;AACvE,QAAM,OAAO,OAAO,OAAOA,mBAAa,EAAE,KAAK,CAAC,MAAM,EAAE,UAAU,WAAW;AAC7E,SAAO,MAAM,WAAW,YAAY,UAAU,GAAG,CAAC,EAAE,YAAA;AACtD;;;;;;;;;;;;;;;"}