@skhema/embed 0.1.10 → 0.1.11
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/components/types.d.ts +1 -1
- package/dist/components/types.d.ts.map +1 -1
- package/dist/embed.min.js +1 -1
- package/dist/embed.min.js.map +1 -1
- package/dist/{index-DBeyneZT.cjs → index-B8ZF3snD.cjs} +23 -23
- package/dist/index-B8ZF3snD.cjs.map +1 -0
- package/dist/{index-jwx5CXLB.js → index-nkGvTyQR.js} +2 -2
- package/dist/index-nkGvTyQR.js.map +1 -0
- package/dist/index.cjs +43 -43
- package/dist/index.cjs.map +1 -1
- package/dist/index.es.js +11 -11
- package/dist/index.es.js.map +1 -1
- package/dist/method-schema.d.ts +244 -0
- package/dist/method-schema.d.ts.map +1 -0
- package/dist/render.cjs +2 -2
- package/dist/render.es.js +2 -2
- package/dist/{validation-Dt4L_df8.js → tokens-BQf5zJ3j.js} +172 -16
- package/dist/tokens-BQf5zJ3j.js.map +1 -0
- package/dist/{validation-DCw9rQeH.cjs → tokens-BxpYMhDq.cjs} +168 -12
- package/dist/tokens-BxpYMhDq.cjs.map +1 -0
- package/dist/tokens.cjs +13 -13
- package/dist/tokens.es.js +8 -8
- package/dist/utils/component-validation.d.ts +1 -1
- package/dist/utils/component-validation.d.ts.map +1 -1
- package/dist/utils/seo.d.ts +1 -1
- package/dist/utils/seo.d.ts.map +1 -1
- package/dist/utils/validation.d.ts +1 -1
- package/dist/utils/validation.d.ts.map +1 -1
- package/package.json +1 -4
- package/dist/index-DBeyneZT.cjs.map +0 -1
- package/dist/index-jwx5CXLB.js.map +0 -1
- package/dist/validation-DCw9rQeH.cjs.map +0 -1
- package/dist/validation-Dt4L_df8.js.map +0 -1
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
const types = require("@skhema/types");
|
|
3
2
|
const COMPONENT_COLORS = {
|
|
4
3
|
diagnosis: {
|
|
5
4
|
bg: "oklch(0.65 0.06 25 / 0.15)",
|
|
@@ -78,31 +77,188 @@ const CARD_VARS = {
|
|
|
78
77
|
}
|
|
79
78
|
};
|
|
80
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>`;
|
|
80
|
+
const ELEMENT_TYPES = {
|
|
81
|
+
KEY_CHALLENGE: {
|
|
82
|
+
value: "key_challenge",
|
|
83
|
+
label: "Key Challenge",
|
|
84
|
+
acronym: "CHL"
|
|
85
|
+
},
|
|
86
|
+
SUPPORTING_FACT: {
|
|
87
|
+
value: "supporting_fact",
|
|
88
|
+
label: "Supporting Fact",
|
|
89
|
+
acronym: "FCT"
|
|
90
|
+
},
|
|
91
|
+
IMPACT: {
|
|
92
|
+
value: "impact",
|
|
93
|
+
label: "Impact",
|
|
94
|
+
acronym: "IMP"
|
|
95
|
+
},
|
|
96
|
+
GUIDING_POLICY: {
|
|
97
|
+
value: "guiding_policy",
|
|
98
|
+
label: "Guiding Policy",
|
|
99
|
+
acronym: "POL"
|
|
100
|
+
},
|
|
101
|
+
COMPETITOR_MOVE: {
|
|
102
|
+
value: "competitor_move",
|
|
103
|
+
label: "Competitor Move",
|
|
104
|
+
acronym: "CMV"
|
|
105
|
+
},
|
|
106
|
+
SCOPE: {
|
|
107
|
+
value: "scope",
|
|
108
|
+
label: "Scope",
|
|
109
|
+
acronym: "SCP"
|
|
110
|
+
},
|
|
111
|
+
CONSTRAINT: {
|
|
112
|
+
value: "constraint",
|
|
113
|
+
label: "Constraint",
|
|
114
|
+
acronym: "CST"
|
|
115
|
+
},
|
|
116
|
+
SOLUTION: {
|
|
117
|
+
value: "solution",
|
|
118
|
+
label: "Solution",
|
|
119
|
+
acronym: "SOL"
|
|
120
|
+
},
|
|
121
|
+
ASSUMPTION_HYPOTHESIS: {
|
|
122
|
+
value: "assumption_hypothesis",
|
|
123
|
+
label: "Assumption Hypothesis",
|
|
124
|
+
acronym: "HYP"
|
|
125
|
+
},
|
|
126
|
+
EXPERIMENT: {
|
|
127
|
+
value: "experiment",
|
|
128
|
+
label: "Experiment",
|
|
129
|
+
acronym: "EXP"
|
|
130
|
+
},
|
|
131
|
+
ACTION: {
|
|
132
|
+
value: "action",
|
|
133
|
+
label: "Action",
|
|
134
|
+
acronym: "ACT"
|
|
135
|
+
},
|
|
136
|
+
INVESTMENT: {
|
|
137
|
+
value: "investment",
|
|
138
|
+
label: "Investment",
|
|
139
|
+
acronym: "INV"
|
|
140
|
+
},
|
|
141
|
+
ESTIMATE: {
|
|
142
|
+
value: "estimate",
|
|
143
|
+
label: "Estimate",
|
|
144
|
+
acronym: "EST"
|
|
145
|
+
},
|
|
146
|
+
BASELINE: {
|
|
147
|
+
value: "baseline",
|
|
148
|
+
label: "Baseline",
|
|
149
|
+
acronym: "BSL"
|
|
150
|
+
},
|
|
151
|
+
OUTCOME: {
|
|
152
|
+
value: "outcome",
|
|
153
|
+
label: "Outcome",
|
|
154
|
+
acronym: "OCM"
|
|
155
|
+
},
|
|
156
|
+
PERFORMANCE_VARIABLE: {
|
|
157
|
+
value: "performance_variable",
|
|
158
|
+
label: "Performance Variable",
|
|
159
|
+
acronym: "VAR"
|
|
160
|
+
},
|
|
161
|
+
CAPABILITY: {
|
|
162
|
+
value: "capability",
|
|
163
|
+
label: "Capability",
|
|
164
|
+
acronym: "CAP"
|
|
165
|
+
},
|
|
166
|
+
SYSTEM: {
|
|
167
|
+
value: "system",
|
|
168
|
+
label: "System",
|
|
169
|
+
acronym: "SYS"
|
|
170
|
+
},
|
|
171
|
+
PRINCIPLE: {
|
|
172
|
+
value: "principle",
|
|
173
|
+
label: "Principle",
|
|
174
|
+
acronym: "PRN"
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
const COMPONENT_TYPES = {
|
|
178
|
+
DIAGNOSIS: {
|
|
179
|
+
value: "diagnosis",
|
|
180
|
+
label: "Diagnosis",
|
|
181
|
+
acronym: "BD"
|
|
182
|
+
},
|
|
183
|
+
METHOD: {
|
|
184
|
+
value: "method",
|
|
185
|
+
label: "Method & Positioning",
|
|
186
|
+
acronym: "MP"
|
|
187
|
+
},
|
|
188
|
+
INITIATIVES: {
|
|
189
|
+
value: "initiatives",
|
|
190
|
+
label: "Portfolio of Initiatives",
|
|
191
|
+
acronym: "PI"
|
|
192
|
+
},
|
|
193
|
+
MEASURES: {
|
|
194
|
+
value: "measures",
|
|
195
|
+
label: "Measures",
|
|
196
|
+
acronym: "M"
|
|
197
|
+
},
|
|
198
|
+
SUPPORT: {
|
|
199
|
+
value: "support",
|
|
200
|
+
label: "Support Structures",
|
|
201
|
+
acronym: "SS"
|
|
202
|
+
}
|
|
203
|
+
};
|
|
204
|
+
const SKHEMA_MAPPING = {
|
|
205
|
+
elementFlow: {
|
|
206
|
+
[COMPONENT_TYPES.DIAGNOSIS.value]: [
|
|
207
|
+
ELEMENT_TYPES.KEY_CHALLENGE,
|
|
208
|
+
ELEMENT_TYPES.SUPPORTING_FACT,
|
|
209
|
+
ELEMENT_TYPES.IMPACT
|
|
210
|
+
],
|
|
211
|
+
[COMPONENT_TYPES.METHOD.value]: [
|
|
212
|
+
ELEMENT_TYPES.GUIDING_POLICY,
|
|
213
|
+
ELEMENT_TYPES.COMPETITOR_MOVE,
|
|
214
|
+
ELEMENT_TYPES.SCOPE,
|
|
215
|
+
ELEMENT_TYPES.CONSTRAINT
|
|
216
|
+
],
|
|
217
|
+
[COMPONENT_TYPES.INITIATIVES.value]: [
|
|
218
|
+
ELEMENT_TYPES.SOLUTION,
|
|
219
|
+
ELEMENT_TYPES.ASSUMPTION_HYPOTHESIS,
|
|
220
|
+
ELEMENT_TYPES.EXPERIMENT,
|
|
221
|
+
ELEMENT_TYPES.ACTION,
|
|
222
|
+
ELEMENT_TYPES.ESTIMATE,
|
|
223
|
+
ELEMENT_TYPES.INVESTMENT
|
|
224
|
+
],
|
|
225
|
+
[COMPONENT_TYPES.MEASURES.value]: [
|
|
226
|
+
ELEMENT_TYPES.BASELINE,
|
|
227
|
+
ELEMENT_TYPES.OUTCOME,
|
|
228
|
+
ELEMENT_TYPES.PERFORMANCE_VARIABLE
|
|
229
|
+
],
|
|
230
|
+
[COMPONENT_TYPES.SUPPORT.value]: [
|
|
231
|
+
ELEMENT_TYPES.CAPABILITY,
|
|
232
|
+
ELEMENT_TYPES.SYSTEM,
|
|
233
|
+
ELEMENT_TYPES.PRINCIPLE
|
|
234
|
+
]
|
|
235
|
+
}
|
|
236
|
+
};
|
|
81
237
|
function isValidComponentType(componentType) {
|
|
82
|
-
const validTypes = Object.values(
|
|
238
|
+
const validTypes = Object.values(COMPONENT_TYPES).map((t) => t.value);
|
|
83
239
|
return validTypes.includes(componentType);
|
|
84
240
|
}
|
|
85
241
|
function validateElementBelongsToComponent(elementType, componentType) {
|
|
86
242
|
if (!isValidComponentType(componentType)) return false;
|
|
87
|
-
const validElements =
|
|
243
|
+
const validElements = SKHEMA_MAPPING.elementFlow[componentType];
|
|
88
244
|
if (!validElements) return false;
|
|
89
245
|
return validElements.some((e) => e.value === elementType);
|
|
90
246
|
}
|
|
91
247
|
function getComponentTypeLabel(componentType) {
|
|
92
|
-
const type = Object.values(
|
|
248
|
+
const type = Object.values(COMPONENT_TYPES).find(
|
|
93
249
|
(t) => t.value === componentType
|
|
94
250
|
);
|
|
95
251
|
return type?.label || componentType;
|
|
96
252
|
}
|
|
97
253
|
function getComponentTypeAcronym(componentType) {
|
|
98
|
-
const type = Object.values(
|
|
254
|
+
const type = Object.values(COMPONENT_TYPES).find(
|
|
99
255
|
(t) => t.value === componentType
|
|
100
256
|
);
|
|
101
257
|
return type?.acronym || componentType.substring(0, 2).toUpperCase();
|
|
102
258
|
}
|
|
103
259
|
function resolveComponentType(elementType) {
|
|
104
260
|
for (const [componentValue, elements] of Object.entries(
|
|
105
|
-
|
|
261
|
+
SKHEMA_MAPPING.elementFlow
|
|
106
262
|
)) {
|
|
107
263
|
if (elements.some((e) => e.value === elementType)) {
|
|
108
264
|
return componentValue;
|
|
@@ -112,11 +268,11 @@ function resolveComponentType(elementType) {
|
|
|
112
268
|
}
|
|
113
269
|
function getElementTypesForComponent(componentType) {
|
|
114
270
|
if (!isValidComponentType(componentType)) return [];
|
|
115
|
-
const elements =
|
|
271
|
+
const elements = SKHEMA_MAPPING.elementFlow[componentType];
|
|
116
272
|
return elements?.map((e) => e.value) || [];
|
|
117
273
|
}
|
|
118
274
|
function isValidElementType(elementType) {
|
|
119
|
-
const validTypes = Object.values(
|
|
275
|
+
const validTypes = Object.values(ELEMENT_TYPES).map((type) => type.value);
|
|
120
276
|
return validTypes.includes(elementType);
|
|
121
277
|
}
|
|
122
278
|
function validateAttributes(element) {
|
|
@@ -126,7 +282,7 @@ function validateAttributes(element) {
|
|
|
126
282
|
if (!elementType) {
|
|
127
283
|
errors.push("Missing required attribute: element-type");
|
|
128
284
|
} else if (!isValidElementType(elementType)) {
|
|
129
|
-
const validTypes = Object.values(
|
|
285
|
+
const validTypes = Object.values(ELEMENT_TYPES).map((t) => t.value).join(", ");
|
|
130
286
|
errors.push(
|
|
131
287
|
`Invalid element-type "${elementType}". Valid types: ${validTypes}`
|
|
132
288
|
);
|
|
@@ -144,11 +300,11 @@ function validateAttributes(element) {
|
|
|
144
300
|
};
|
|
145
301
|
}
|
|
146
302
|
function getElementTypeLabel(elementType) {
|
|
147
|
-
const type = Object.values(
|
|
303
|
+
const type = Object.values(ELEMENT_TYPES).find((t) => t.value === elementType);
|
|
148
304
|
return type?.label || elementType;
|
|
149
305
|
}
|
|
150
306
|
function getElementTypeAcronym(elementType) {
|
|
151
|
-
const type = Object.values(
|
|
307
|
+
const type = Object.values(ELEMENT_TYPES).find((t) => t.value === elementType);
|
|
152
308
|
return type?.acronym || elementType.substring(0, 2).toUpperCase();
|
|
153
309
|
}
|
|
154
310
|
exports.CARD_PALETTE = CARD_PALETTE;
|
|
@@ -173,4 +329,4 @@ exports.isValidElementType = isValidElementType;
|
|
|
173
329
|
exports.resolveComponentType = resolveComponentType;
|
|
174
330
|
exports.validateAttributes = validateAttributes;
|
|
175
331
|
exports.validateElementBelongsToComponent = validateElementBelongsToComponent;
|
|
176
|
-
//# sourceMappingURL=
|
|
332
|
+
//# sourceMappingURL=tokens-BxpYMhDq.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tokens-BxpYMhDq.cjs","sources":["../src/styles/design-tokens.ts","../src/method-schema.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","/**\n * Vendored from @skhema/types (src/schema/{elements,components,mapping}.ts).\n *\n * @skhema/types is a restricted npm package; @skhema/embed is public, so it\n * must not depend on it at install time. This is the public method vocabulary\n * (it renders verbatim in public embeds) — keep it in sync with @skhema/types\n * when the method schema changes (same pattern as\n * skhema-cli/src/lib/contribute/method-schema.ts).\n */\n\nexport const ELEMENT_TYPES = {\n KEY_CHALLENGE: {\n value: 'key_challenge',\n label: 'Key Challenge',\n acronym: 'CHL',\n },\n SUPPORTING_FACT: {\n value: 'supporting_fact',\n label: 'Supporting Fact',\n acronym: 'FCT',\n },\n IMPACT: {\n value: 'impact',\n label: 'Impact',\n acronym: 'IMP',\n },\n GUIDING_POLICY: {\n value: 'guiding_policy',\n label: 'Guiding Policy',\n acronym: 'POL',\n },\n COMPETITOR_MOVE: {\n value: 'competitor_move',\n label: 'Competitor Move',\n acronym: 'CMV',\n },\n SCOPE: {\n value: 'scope',\n label: 'Scope',\n acronym: 'SCP',\n },\n CONSTRAINT: {\n value: 'constraint',\n label: 'Constraint',\n acronym: 'CST',\n },\n SOLUTION: {\n value: 'solution',\n label: 'Solution',\n acronym: 'SOL',\n },\n ASSUMPTION_HYPOTHESIS: {\n value: 'assumption_hypothesis',\n label: 'Assumption Hypothesis',\n acronym: 'HYP',\n },\n EXPERIMENT: {\n value: 'experiment',\n label: 'Experiment',\n acronym: 'EXP',\n },\n ACTION: {\n value: 'action',\n label: 'Action',\n acronym: 'ACT',\n },\n INVESTMENT: {\n value: 'investment',\n label: 'Investment',\n acronym: 'INV',\n },\n ESTIMATE: {\n value: 'estimate',\n label: 'Estimate',\n acronym: 'EST',\n },\n BASELINE: {\n value: 'baseline',\n label: 'Baseline',\n acronym: 'BSL',\n },\n OUTCOME: {\n value: 'outcome',\n label: 'Outcome',\n acronym: 'OCM',\n },\n PERFORMANCE_VARIABLE: {\n value: 'performance_variable',\n label: 'Performance Variable',\n acronym: 'VAR',\n },\n CAPABILITY: {\n value: 'capability',\n label: 'Capability',\n acronym: 'CAP',\n },\n SYSTEM: {\n value: 'system',\n label: 'System',\n acronym: 'SYS',\n },\n PRINCIPLE: {\n value: 'principle',\n label: 'Principle',\n acronym: 'PRN',\n },\n} as const\n\nexport const COMPONENT_TYPES = {\n DIAGNOSIS: {\n value: 'diagnosis',\n label: 'Diagnosis',\n acronym: 'BD',\n },\n METHOD: {\n value: 'method',\n label: 'Method & Positioning',\n acronym: 'MP',\n },\n INITIATIVES: {\n value: 'initiatives',\n label: 'Portfolio of Initiatives',\n acronym: 'PI',\n },\n MEASURES: {\n value: 'measures',\n label: 'Measures',\n acronym: 'M',\n },\n SUPPORT: {\n value: 'support',\n label: 'Support Structures',\n acronym: 'SS',\n },\n} as const\n\nexport const SKHEMA_MAPPING = {\n componentFlow: [\n COMPONENT_TYPES.DIAGNOSIS,\n COMPONENT_TYPES.METHOD,\n COMPONENT_TYPES.INITIATIVES,\n COMPONENT_TYPES.MEASURES,\n COMPONENT_TYPES.SUPPORT,\n ] as const,\n\n elementFlow: {\n [COMPONENT_TYPES.DIAGNOSIS.value]: [\n ELEMENT_TYPES.KEY_CHALLENGE,\n ELEMENT_TYPES.SUPPORTING_FACT,\n ELEMENT_TYPES.IMPACT,\n ],\n [COMPONENT_TYPES.METHOD.value]: [\n ELEMENT_TYPES.GUIDING_POLICY,\n ELEMENT_TYPES.COMPETITOR_MOVE,\n ELEMENT_TYPES.SCOPE,\n ELEMENT_TYPES.CONSTRAINT,\n ],\n [COMPONENT_TYPES.INITIATIVES.value]: [\n ELEMENT_TYPES.SOLUTION,\n ELEMENT_TYPES.ASSUMPTION_HYPOTHESIS,\n ELEMENT_TYPES.EXPERIMENT,\n ELEMENT_TYPES.ACTION,\n ELEMENT_TYPES.ESTIMATE,\n ELEMENT_TYPES.INVESTMENT,\n ],\n [COMPONENT_TYPES.MEASURES.value]: [\n ELEMENT_TYPES.BASELINE,\n ELEMENT_TYPES.OUTCOME,\n ELEMENT_TYPES.PERFORMANCE_VARIABLE,\n ],\n [COMPONENT_TYPES.SUPPORT.value]: [\n ELEMENT_TYPES.CAPABILITY,\n ELEMENT_TYPES.SYSTEM,\n ELEMENT_TYPES.PRINCIPLE,\n ],\n } as const,\n} as const\n\nexport type ElementType = (typeof ELEMENT_TYPES)[keyof typeof ELEMENT_TYPES]\nexport type ElementValue = ElementType['value']\nexport type ComponentType =\n (typeof COMPONENT_TYPES)[keyof typeof COMPONENT_TYPES]\nexport type ComponentValue = ComponentType['value']\n","import type { ComponentValue, ElementValue } from '../method-schema.js'\nimport { COMPONENT_TYPES, SKHEMA_MAPPING } from '../method-schema.js'\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 '../method-schema.js'\nimport { ELEMENT_TYPES } from '../method-schema.js'\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/dist/tokens.cjs
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
-
const
|
|
4
|
-
exports.CARD_VARS =
|
|
5
|
-
exports.COMPONENT_COLORS =
|
|
6
|
-
exports.USER_ICON_SVG =
|
|
7
|
-
exports.getComponentTypeAcronym =
|
|
8
|
-
exports.getComponentTypeLabel =
|
|
9
|
-
exports.getElementTypeAcronym =
|
|
10
|
-
exports.getElementTypeLabel =
|
|
11
|
-
exports.getElementTypesForComponent =
|
|
12
|
-
exports.isValidComponentType =
|
|
13
|
-
exports.isValidElementType =
|
|
14
|
-
exports.resolveComponentType =
|
|
15
|
-
exports.validateElementBelongsToComponent =
|
|
3
|
+
const tokens = require("./tokens-BxpYMhDq.cjs");
|
|
4
|
+
exports.CARD_VARS = tokens.CARD_VARS;
|
|
5
|
+
exports.COMPONENT_COLORS = tokens.COMPONENT_COLORS;
|
|
6
|
+
exports.USER_ICON_SVG = tokens.USER_ICON_SVG;
|
|
7
|
+
exports.getComponentTypeAcronym = tokens.getComponentTypeAcronym;
|
|
8
|
+
exports.getComponentTypeLabel = tokens.getComponentTypeLabel;
|
|
9
|
+
exports.getElementTypeAcronym = tokens.getElementTypeAcronym;
|
|
10
|
+
exports.getElementTypeLabel = tokens.getElementTypeLabel;
|
|
11
|
+
exports.getElementTypesForComponent = tokens.getElementTypesForComponent;
|
|
12
|
+
exports.isValidComponentType = tokens.isValidComponentType;
|
|
13
|
+
exports.isValidElementType = tokens.isValidElementType;
|
|
14
|
+
exports.resolveComponentType = tokens.resolveComponentType;
|
|
15
|
+
exports.validateElementBelongsToComponent = tokens.validateElementBelongsToComponent;
|
|
16
16
|
//# sourceMappingURL=tokens.cjs.map
|
package/dist/tokens.es.js
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { o, c, U, a, b, m, g, f, i, n, r, v } from "./tokens-BQf5zJ3j.js";
|
|
2
2
|
export {
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
o as CARD_VARS,
|
|
4
|
+
c as COMPONENT_COLORS,
|
|
5
5
|
U as USER_ICON_SVG,
|
|
6
|
-
|
|
6
|
+
a as getComponentTypeAcronym,
|
|
7
7
|
b as getComponentTypeLabel,
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
m as getElementTypeAcronym,
|
|
9
|
+
g as getElementTypeLabel,
|
|
10
|
+
f as getElementTypesForComponent,
|
|
11
11
|
i as isValidComponentType,
|
|
12
|
-
|
|
12
|
+
n as isValidElementType,
|
|
13
13
|
r as resolveComponentType,
|
|
14
14
|
v as validateElementBelongsToComponent
|
|
15
15
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"component-validation.d.ts","sourceRoot":"","sources":["../../src/utils/component-validation.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"component-validation.d.ts","sourceRoot":"","sources":["../../src/utils/component-validation.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAA;AAGvE;;GAEG;AACH,wBAAgB,oBAAoB,CAClC,aAAa,EAAE,MAAM,GACpB,aAAa,IAAI,cAAc,CAGjC;AAED;;GAEG;AACH,wBAAgB,iCAAiC,CAC/C,WAAW,EAAE,MAAM,EACnB,aAAa,EAAE,MAAM,GACpB,OAAO,CAUT;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,aAAa,EAAE,MAAM,GAAG,MAAM,CAKnE;AAED;;GAEG;AACH,wBAAgB,uBAAuB,CAAC,aAAa,EAAE,MAAM,GAAG,MAAM,CAKrE;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,WAAW,EAAE,MAAM,GAAG,cAAc,CASxE;AAED;;GAEG;AACH,wBAAgB,2BAA2B,CACzC,aAAa,EAAE,MAAM,GACpB,YAAY,EAAE,CAOhB"}
|
package/dist/utils/seo.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ComponentValue, ElementValue } from '
|
|
1
|
+
import { ComponentValue, ElementValue } from '../method-schema.js';
|
|
2
2
|
export declare function generateStructuredData(content: string, elementType: ElementValue, contributorId: string, sourceUrl: string): object;
|
|
3
3
|
export declare function generateRedirectUrl(content: string, elementType: ElementValue, contributorId: string, options?: {
|
|
4
4
|
baseUrl?: string;
|
package/dist/utils/seo.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"seo.d.ts","sourceRoot":"","sources":["../../src/utils/seo.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"seo.d.ts","sourceRoot":"","sources":["../../src/utils/seo.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAA;AAKvE,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,MAAM,EACf,WAAW,EAAE,YAAY,EACzB,aAAa,EAAE,MAAM,EACrB,SAAS,EAAE,MAAM,GAChB,MAAM,CAqBR;AAED,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,MAAM,EACf,WAAW,EAAE,YAAY,EACzB,aAAa,EAAE,MAAM,EACrB,OAAO,GAAE;IACP,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,WAAW,CAAC,EAAE,MAAM,CAAA;CAChB,GACL,MAAM,CAiBR;AAED,wBAAgB,cAAc,CAC5B,OAAO,EAAE,MAAM,EACf,WAAW,EAAE,YAAY,EACzB,aAAa,EAAE,MAAM,GACpB,MAAM,CAYR;AAED,wBAAgB,+BAA+B,CAC7C,KAAK,EAAE,MAAM,EACb,aAAa,EAAE,cAAc,EAC7B,aAAa,EAAE,MAAM,EACrB,QAAQ,EAAE,KAAK,CAAC;IAAE,WAAW,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC,EACzD,SAAS,EAAE,MAAM,GAChB,MAAM,CAiCR;AAED,wBAAgB,4BAA4B,CAC1C,aAAa,EAAE,MAAM,EACrB,aAAa,EAAE,cAAc,EAC7B,aAAa,EAAE,MAAM,EACrB,OAAO,GAAE;IACP,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,WAAW,CAAC,EAAE,MAAM,CAAA;CAChB,GACL,MAAM,CAeR;AAED,wBAAgB,oBAAoB,CAClC,WAAW,EAAE,YAAY,GACxB,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAQxB"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ElementValue } from '
|
|
1
|
+
import { ElementValue } from '../method-schema.js';
|
|
2
2
|
export declare function isValidElementType(elementType: string): elementType is ElementValue;
|
|
3
3
|
export declare function validateAttributes(element: HTMLElement): {
|
|
4
4
|
isValid: boolean;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../../src/utils/validation.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../../src/utils/validation.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAA;AAGvD,wBAAgB,kBAAkB,CAChC,WAAW,EAAE,MAAM,GAClB,WAAW,IAAI,YAAY,CAG7B;AAED,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,WAAW,GAAG;IACxD,OAAO,EAAE,OAAO,CAAA;IAChB,MAAM,EAAE,MAAM,EAAE,CAAA;IAChB,WAAW,CAAC,EAAE,YAAY,CAAA;IAC1B,aAAa,CAAC,EAAE,MAAM,CAAA;CACvB,CA+BA;AAED,wBAAgB,mBAAmB,CAAC,WAAW,EAAE,YAAY,GAAG,MAAM,CAGrE;AAED,wBAAgB,qBAAqB,CAAC,WAAW,EAAE,YAAY,GAAG,MAAM,CAGvE"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@skhema/embed",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.11",
|
|
4
4
|
"description": "Embeddable component for integrating strategic elements with Skhema.",
|
|
5
5
|
"main": "dist/index.cjs",
|
|
6
6
|
"module": "dist/index.es.js",
|
|
@@ -69,9 +69,6 @@
|
|
|
69
69
|
],
|
|
70
70
|
"author": "Skhema Team",
|
|
71
71
|
"license": "MIT",
|
|
72
|
-
"dependencies": {
|
|
73
|
-
"@skhema/types": "2.5.0"
|
|
74
|
-
},
|
|
75
72
|
"devDependencies": {
|
|
76
73
|
"@skhema/linter": "2.2.0",
|
|
77
74
|
"@skhema/prettier-config": "1.0.0",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index-DBeyneZT.cjs","sources":["../src/utils/color.ts","../src/utils/sanitization.ts","../src/render/index.ts"],"sourcesContent":["/**\n * DOM-free colour utilities for the email-safe card renderer.\n *\n * The card palette (`COMPONENT_COLORS`) is authored in `oklch()` for the live\n * browser embed, but email clients can't parse `oklch()` and `<style>` is\n * stripped, so every colour must be a pre-computed sRGB hex with all styles\n * inlined. This converts a single `oklch(...)` token to hex, compositing any\n * alpha over a flat background (white for light cards, the dark card surface\n * for dark cards) so the result is opaque and email-safe.\n *\n * This is the single source of the conversion that `sk comms curated` and the\n * `CuratedElements` email template previously duplicated.\n */\n\n/** Brand pink (hsl(344 57% 54%)) — fallback when a component palette is missing. */\nexport const BRAND_PINK = '#cd476a'\n\nfunction hexToRgb(hex: string): [number, number, number] {\n const h = hex.replace('#', '')\n return [\n parseInt(h.slice(0, 2), 16),\n parseInt(h.slice(2, 4), 16),\n parseInt(h.slice(4, 6), 16),\n ]\n}\n\n/**\n * Convert a single `oklch(...)` token to an email-safe sRGB hex. When the token\n * carries an alpha (e.g. the badge `bg` @ 0.15 / `border` @ 0.3), the colour is\n * composited over `overHex` so the result is a flat opaque hex.\n *\n * @param token An `oklch(L C H)` or `oklch(L C H / A)` string.\n * @param overHex The flat background to composite alpha over (default white).\n */\nexport function oklchToHex(token: string, overHex = '#ffffff'): string {\n const m = token.match(\n /oklch\\(\\s*([\\d.]+)\\s+([\\d.]+)\\s+([\\d.]+)\\s*(?:\\/\\s*([\\d.]+)\\s*)?\\)/\n )\n if (!m) return BRAND_PINK\n const L = parseFloat(m[1])\n const C = parseFloat(m[2])\n const H = parseFloat(m[3])\n const alpha = m[4] !== undefined ? parseFloat(m[4]) : 1\n const h = (H * Math.PI) / 180\n const a = C * Math.cos(h)\n const b = C * Math.sin(h)\n const l_ = L + 0.3963377774 * a + 0.2158037573 * b\n const m_ = L - 0.1055613458 * a - 0.0638541728 * b\n const s_ = L - 0.0894841775 * a - 1.291485548 * b\n const lr = l_ ** 3\n const mr = m_ ** 3\n const sr = s_ ** 3\n const R = 4.0767416621 * lr - 3.3077115913 * mr + 0.2309699292 * sr\n const G = -1.2684380046 * lr + 2.6097574011 * mr - 0.3413193965 * sr\n const B = -0.0041960863 * lr - 0.7034186147 * mr + 1.707614701 * sr\n const gamma = (x: number): number => {\n const c = Math.max(0, Math.min(1, x))\n return c <= 0.0031308 ? 12.92 * c : 1.055 * Math.pow(c, 1 / 2.4) - 0.055\n }\n let rgb = [gamma(R) * 255, gamma(G) * 255, gamma(B) * 255]\n if (alpha < 1) {\n const [br, bg, bb] = hexToRgb(overHex)\n rgb = [\n rgb[0] * alpha + br * (1 - alpha),\n rgb[1] * alpha + bg * (1 - alpha),\n rgb[2] * alpha + bb * (1 - alpha),\n ]\n }\n return (\n '#' + rgb.map((c) => Math.round(c).toString(16).padStart(2, '0')).join('')\n )\n}\n","/**\n * Content sanitization utilities for Skhema cards.\n *\n * DOM-free by design: `sanitizeContent` is imported by the email-safe card\n * renderer (`@skhema/embed/render`), which must run in Node / email / CLI\n * runtimes with no `document`. Keep every export here free of DOM access at\n * call time (the legacy `stripHtml` below is the one exception and is not used\n * by the renderer).\n */\n\n/**\n * HTML entity encoding for basic XSS protection. Regex-based (no DOM) so it is\n * safe in non-browser runtimes. Escapes the text-context entities plus quotes.\n */\nfunction htmlEncode(str: string): string {\n return str\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"')\n}\n\n/**\n * Sanitizes content to prevent XSS attacks and removes URLs\n * @param content The raw content to sanitize\n * @returns Sanitized HTML-safe content with URLs removed\n */\nexport function sanitizeContent(content: string): string {\n // Strip URLs first\n let sanitized = stripUrls(content)\n\n // Encode all HTML entities to prevent script injection\n sanitized = htmlEncode(sanitized)\n\n // Preserve line breaks for readability\n sanitized = sanitized.replace(/\\n/g, '<br>')\n\n // Apply text wrapping rules for long text\n sanitized = applyTextWrapping(sanitized)\n\n return sanitized\n}\n\n/**\n * Strips all URLs from the content\n * @param text The text containing potential URLs\n * @returns Text with all URLs removed\n */\nfunction stripUrls(text: string): string {\n // Comprehensive URL patterns to remove\n const patterns = [\n // Standard URLs with protocols\n /https?:\\/\\/[^\\s<>\"{}|\\\\^`[\\]]+/gi,\n // FTP URLs\n /ftp:\\/\\/[^\\s<>\"{}|\\\\^`[\\]]+/gi,\n // URLs without protocol but with www\n /www\\.[^\\s<>\"{}|\\\\^`[\\]]+/gi,\n // Email-like patterns\n /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}/gi,\n // Common domain patterns (anything.com, anything.org, etc.)\n /(?:^|\\s)([a-zA-Z0-9-]+\\.)+[a-zA-Z]{2,}(?:\\/[^\\s]*)?/gi,\n ]\n\n let stripped = text\n patterns.forEach((pattern) => {\n stripped = stripped.replace(pattern, '')\n })\n\n // Clean up any multiple spaces left after URL removal\n stripped = stripped.replace(/\\s+/g, ' ').trim()\n\n return stripped\n}\n\n/**\n * Applies intelligent text wrapping to prevent layout breaking\n * @param text The text to apply wrapping rules to\n * @returns Text with appropriate wrapping hints\n */\nfunction applyTextWrapping(text: string): string {\n // Split text into words\n const words = text.split(/(\\s+)/)\n\n return words\n .map((word) => {\n // Skip if it's whitespace or already contains HTML\n if (/^\\s+$/.test(word) || word.includes('<')) {\n return word\n }\n\n // For very long words (>30 chars), add word-break opportunities\n if (word.length > 30) {\n // Insert zero-width spaces every 10 characters for breaking\n return word.replace(/(.{10})/g, '$1\\u200B')\n }\n\n return word\n })\n .join('')\n}\n\n/**\n * Validates if content contains potentially malicious patterns or URLs\n * @param content The content to validate\n * @returns Object with validation status and detected issues\n */\nexport function validateContentSecurity(content: string): {\n isSecure: boolean\n issues: string[]\n} {\n const issues: string[] = []\n\n // Check for script tags\n if (/<script[\\s>]/i.test(content)) {\n issues.push('Script tags detected')\n }\n\n // Check for event handlers\n if (/on\\w+\\s*=/i.test(content)) {\n issues.push('Event handlers detected')\n }\n\n // Check for javascript: protocol\n if (/javascript:/i.test(content)) {\n issues.push('JavaScript protocol detected')\n }\n\n // Check for data: URLs that could contain scripts\n if (/data:[^,]*script/i.test(content)) {\n issues.push('Data URL with script detected')\n }\n\n // Check for iframe tags\n if (/<iframe[\\s>]/i.test(content)) {\n issues.push('Iframe tags detected')\n }\n\n // Check for URLs (since we want to disallow them)\n if (/https?:\\/\\//i.test(content) || /www\\./i.test(content)) {\n issues.push('URLs detected in content')\n }\n\n return {\n isSecure: issues.length === 0,\n issues,\n }\n}\n\n/**\n * Strips all HTML tags from content\n * @param content The content to strip\n * @returns Plain text content\n */\nexport function stripHtml(content: string): string {\n const div = document.createElement('div')\n div.innerHTML = content\n return div.textContent || div.innerText || ''\n}\n\n/**\n * Checks if content contains any URLs\n * @param content The content to check\n * @returns True if URLs are found\n */\nexport function containsUrls(content: string): boolean {\n const urlPatterns = [\n /https?:\\/\\//i,\n /ftp:\\/\\//i,\n /www\\./i,\n /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}/i,\n /(?:^|\\s)([a-zA-Z0-9-]+\\.)+[a-zA-Z]{2,}(?:\\/[^\\s]*)?/i,\n ]\n\n return urlPatterns.some((pattern) => pattern.test(content))\n}\n","/**\n * `@skhema/embed/render` — the canonical, DOM-free source of truth for the\n * official Skhema element / component card HTML.\n *\n * `renderElementCardHtml(data)` / `renderComponentCardHtml(data)` return\n * EMAIL-SAFE HTML: a `role=\"presentation\"` table layout with every style\n * inlined as flat hex (no shadow DOM, no `<style>`, no `oklch()`, no CSS vars).\n * The same output is used three ways:\n *\n * 1. the live browser embed (`SkhemaElement` / `SkhemaComponent` inject it\n * into shadow DOM and layer hover/transition CSS on top);\n * 2. the `CuratedElements` email template (injected verbatim); and\n * 3. any third-party / contributor email generator.\n *\n * The module is pure — importing it never touches the DOM, so it is safe in\n * Node, edge, and email build runtimes. It builds NO URLs: callers pass the\n * fully-formed `saveUrl` (e.g. the `/save` handoff) so each surface controls\n * its own UTM tagging.\n */\nimport type { ElementValue } from '@skhema/types'\nimport {\n CARD_PALETTE,\n CARD_RADIUS,\n CARD_SHADOW,\n COMPONENT_COLORS,\n FONT_STACK,\n MONO_STACK,\n PRIMARY_HEX,\n USER_ICON_SVG,\n type CardTheme,\n} from '../styles/design-tokens.js'\nimport { oklchToHex } from '../utils/color.js'\nimport {\n getComponentTypeAcronym,\n getComponentTypeLabel,\n resolveComponentType,\n} from '../utils/component-validation.js'\nimport { sanitizeContent } from '../utils/sanitization.js'\nimport { getElementTypeLabel } from '../utils/validation.js'\n\n/* ------------------------------------------------------------------ *\n * Public data shapes (documented contract — see README \"render\") *\n * ------------------------------------------------------------------ */\n\n/** Card theme. Email is always `'light'`; the browser embed passes the\n * detected page theme. Defaults to `'light'` when omitted. */\nexport type { CardTheme }\n\n/** Author attribution shared by both card kinds. */\ninterface AuthorFields {\n /** Display name. When omitted, falls back to a humanised `contributorId`. */\n authorName?: string | null\n /** Public contributor slug — when present, the name links to the profile. */\n authorSlug?: string | null\n /** Contributor id, used only for the name fallback when `authorName` is unset. */\n contributorId?: string | null\n}\n\n/** Input for {@link renderElementCardHtml}. */\nexport interface ElementCardData extends AuthorFields {\n /** Skhema element type value, e.g. `\"key_challenge\"`. */\n elementType: string\n /** The element content / premise (plain text; sanitised + escaped here). */\n content: string\n /** Pre-built handoff URL for the \"Save to Skhema\" button. */\n saveUrl: string\n /** Card theme (default `'light'`). */\n theme?: CardTheme\n}\n\n/** A single element row inside a component card. */\nexport interface ComponentCardElement {\n elementType: string\n content: string\n}\n\n/** Input for {@link renderComponentCardHtml}. */\nexport interface ComponentCardData extends AuthorFields {\n /** Skhema component type value, e.g. `\"diagnosis\"`. */\n componentType: string\n /** Optional component title shown after a \"—\" separator in the header. */\n title?: string | null\n /** The component's elements, in display order. */\n elements: ComponentCardElement[]\n /** Pre-built handoff URL for the \"Save to Skhema\" button. */\n saveUrl: string\n /** Card theme (default `'light'`). */\n theme?: CardTheme\n}\n\n/* ------------------------------------------------------------------ *\n * Internal helpers *\n * ------------------------------------------------------------------ */\n\nfunction escapeHtml(text: string): string {\n return text\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"')\n}\n\nfunction escapeAttr(text: string): string {\n return escapeHtml(text).replace(/'/g, ''')\n}\n\nfunction humaniseContributorId(contributorId: string): string {\n return contributorId\n .split(/[_-]/)\n .map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())\n .join(' ')\n}\n\ninterface BadgePalette {\n badgeBg: string\n badgeText: string\n badgeBorder: string\n topBorder: string\n}\n\n/** Resolve the email-safe badge / top-border hex for a component type + theme. */\nfunction resolveBadgePalette(\n componentType: string,\n theme: CardTheme\n): BadgePalette {\n const surface = CARD_PALETTE[theme].cardBg\n const colors =\n COMPONENT_COLORS[componentType as keyof typeof COMPONENT_COLORS]\n if (!colors) {\n return {\n badgeBg: surface,\n badgeText: PRIMARY_HEX,\n badgeBorder: PRIMARY_HEX,\n topBorder: PRIMARY_HEX,\n }\n }\n const text = oklchToHex(colors.text)\n return {\n badgeBg: oklchToHex(colors.bg, surface),\n badgeText: text,\n badgeBorder: oklchToHex(colors.border, surface),\n topBorder: text,\n }\n}\n\n/** Contributor line inner HTML: person icon + \"By {author}\" (linked if slug). */\nfunction renderAuthorHtml(author: AuthorFields, mutedColor: string): string {\n let label = ''\n if (author.authorName && author.authorName.trim()) {\n const name = escapeHtml(author.authorName.trim())\n label = author.authorSlug\n ? `By <a href=\"https://skhema.com/contributors/${encodeURIComponent(\n author.authorSlug\n )}\" style=\"color:${mutedColor};text-decoration:none;\" target=\"_blank\" rel=\"noopener noreferrer\">${name}</a>`\n : `By ${name}`\n } else if (author.contributorId && author.contributorId.trim()) {\n label = `By ${escapeHtml(humaniseContributorId(author.contributorId.trim()))}`\n }\n\n if (!label) return ' '\n\n return (\n `<span style=\"display:inline-block;width:14px;height:14px;vertical-align:middle;color:${mutedColor};\">${USER_ICON_SVG}</span>` +\n `<span style=\"vertical-align:middle;padding-left:6px;\">${label}</span>`\n )\n}\n\n/** The shared footer (contributor line + save button + attribution). */\nfunction renderFooter(\n saveUrl: string,\n author: AuthorFields,\n theme: CardTheme\n): string {\n const p = CARD_PALETTE[theme]\n return (\n `<tr><td style=\"padding:12px 16px;border-top:1px solid ${p.border};\">` +\n `<table role=\"presentation\" cellpadding=\"0\" cellspacing=\"0\" border=\"0\" style=\"width:100%;border-collapse:collapse;\"><tr>` +\n `<td style=\"vertical-align:middle;\"><span style=\"font-size:12px;color:${p.textMuted};\">${renderAuthorHtml(\n author,\n p.textMuted\n )}</span></td>` +\n `<td style=\"text-align:right;vertical-align:middle;white-space:nowrap;\">` +\n `<a href=\"${escapeAttr(saveUrl)}\" class=\"skhema-save-btn\" target=\"_blank\" rel=\"noopener noreferrer\" title=\"Save this to Skhema\" style=\"display:inline-block;background:${PRIMARY_HEX};color:#ffffff;font-size:12px;font-weight:500;text-decoration:none;padding:6px 14px;border-radius:${CARD_RADIUS};white-space:nowrap;\">Save to Skhema →</a>` +\n `</td></tr></table>` +\n `<div style=\"font-size:11px;line-height:1.4;color:${p.textMuted};margin-top:8px;\">Strategy powered by <a href=\"https://skhema.com\" style=\"color:${p.textMuted};text-decoration:underline;\" target=\"_blank\" rel=\"noopener noreferrer\">Skhema</a></div>` +\n `</td></tr>`\n )\n}\n\n/** Header row: acronym badge + type label (+ optional \"— title\" for components). */\nfunction renderHeader(\n badge: BadgePalette,\n acronym: string,\n typeLabel: string,\n labelColor: string,\n theme: CardTheme,\n title?: string | null\n): string {\n const p = CARD_PALETTE[theme]\n const titleHtml = title\n ? `<span style=\"color:${p.textMuted};\"> — </span><span style=\"font-weight:600;color:${p.text};\">${escapeHtml(\n title\n )}</span>`\n : ''\n return (\n `<tr><td style=\"padding:12px 16px;border-bottom:1px solid ${p.border};\">` +\n `<table role=\"presentation\" cellpadding=\"0\" cellspacing=\"0\" border=\"0\" style=\"width:100%;border-collapse:collapse;\"><tr>` +\n `<td style=\"width:1%;white-space:nowrap;padding-right:8px;vertical-align:middle;\">` +\n `<span class=\"skhema-acronym-badge\" title=\"${escapeAttr(typeLabel)}\" style=\"display:inline-block;font-family:${MONO_STACK};font-size:10px;font-weight:600;letter-spacing:0.02em;padding:2px 6px;border-radius:2px;background:${badge.badgeBg};color:${badge.badgeText};border:1px solid ${badge.badgeBorder};\">${escapeHtml(\n acronym\n )}</span></td>` +\n `<td style=\"vertical-align:middle;\"><span style=\"font-size:13px;font-weight:500;color:${labelColor};\">${escapeHtml(\n typeLabel\n )}${titleHtml}</span></td>` +\n `</tr></table></td></tr>`\n )\n}\n\n/** Open the outer card table with inline card styling for the theme. */\nfunction cardOpen(theme: CardTheme, kind: 'element' | 'component'): string {\n const p = CARD_PALETTE[theme]\n return (\n `<table role=\"presentation\" cellpadding=\"0\" cellspacing=\"0\" border=\"0\" class=\"skhema-card\" data-skhema-kind=\"${kind}\" data-theme=\"${theme}\" ` +\n `style=\"width:100%;max-width:600px;border-collapse:separate;background:${p.cardBg};border:1px solid ${p.border};border-radius:${CARD_RADIUS};box-shadow:${CARD_SHADOW};overflow:hidden;margin:8px 0;font-family:${FONT_STACK};line-height:1.5;color:${p.text};\">`\n )\n}\n\n/* ------------------------------------------------------------------ *\n * Public renderers *\n * ------------------------------------------------------------------ */\n\n/**\n * Render the official Skhema **element** card as email-safe HTML.\n * The badge acronym + palette resolve from the element's owning component type,\n * exactly like the live `<skhema-element>` embed.\n */\nexport function renderElementCardHtml(data: ElementCardData): string {\n const theme = data.theme ?? 'light'\n const p = CARD_PALETTE[theme]\n const label = getElementTypeLabel(data.elementType as ElementValue)\n const componentType = resolveComponentType(data.elementType)\n const acronym = getComponentTypeAcronym(componentType)\n const badge = resolveBadgePalette(componentType, theme)\n\n return (\n cardOpen(theme, 'element') +\n renderHeader(badge, acronym, label, p.text, theme) +\n `<tr><td style=\"padding:16px;\">` +\n `<div style=\"font-size:15px;line-height:1.6;color:${p.text};word-wrap:break-word;overflow-wrap:break-word;\">${sanitizeContent(\n data.content\n )}</div>` +\n `</td></tr>` +\n renderFooter(data.saveUrl, data, theme) +\n `</table>`\n )\n}\n\n/**\n * Render the official Skhema **component** card as email-safe HTML — a coloured\n * top bar, header (badge + type label + optional title), per-element-type\n * groups, and the shared footer.\n */\nexport function renderComponentCardHtml(data: ComponentCardData): string {\n const theme = data.theme ?? 'light'\n const p = CARD_PALETTE[theme]\n const label = getComponentTypeLabel(data.componentType)\n const acronym = getComponentTypeAcronym(data.componentType)\n const badge = resolveBadgePalette(data.componentType, theme)\n\n // Group elements by type (preserving first-occurrence order), mirroring the\n // live `<skhema-component>` body: one small-caps label per type, with each\n // element's content as a left-ruled block beneath it.\n const groups = new Map<string, string[]>()\n for (const el of data.elements) {\n const existing = groups.get(el.elementType)\n if (existing) existing.push(el.content)\n else groups.set(el.elementType, [el.content])\n }\n\n const groupsHtml = Array.from(groups.entries())\n .map(\n ([elementType, contents]) =>\n `<div style=\"margin-bottom:16px;\">` +\n `<div style=\"text-transform:uppercase;letter-spacing:0.05em;font-family:${MONO_STACK};font-size:10px;font-weight:600;color:${p.textMuted};margin:0 0 4px;\">${escapeHtml(\n getElementTypeLabel(elementType as ElementValue)\n )}</div>` +\n contents\n .map(\n (content) =>\n `<div style=\"font-size:14px;line-height:1.6;color:${p.text};padding-left:8px;border-left:2px solid ${p.border};word-wrap:break-word;overflow-wrap:break-word;\">${sanitizeContent(\n content\n )}</div>`\n )\n .join('') +\n `</div>`\n )\n .join('')\n\n return (\n cardOpen(theme, 'component') +\n `<tr><td style=\"height:2px;line-height:2px;font-size:0;background:${badge.topBorder};\"> </td></tr>` +\n renderHeader(badge, acronym, label, p.textMuted, theme, data.title) +\n `<tr><td style=\"padding:16px;\">${groupsHtml}</td></tr>` +\n renderFooter(data.saveUrl, data, theme) +\n `</table>`\n )\n}\n"],"names":["CARD_PALETTE","COMPONENT_COLORS","PRIMARY_HEX","USER_ICON_SVG","CARD_RADIUS","MONO_STACK","CARD_SHADOW","FONT_STACK","getElementTypeLabel","resolveComponentType","getComponentTypeAcronym","getComponentTypeLabel"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index-jwx5CXLB.js","sources":["../src/utils/color.ts","../src/utils/sanitization.ts","../src/render/index.ts"],"sourcesContent":["/**\n * DOM-free colour utilities for the email-safe card renderer.\n *\n * The card palette (`COMPONENT_COLORS`) is authored in `oklch()` for the live\n * browser embed, but email clients can't parse `oklch()` and `<style>` is\n * stripped, so every colour must be a pre-computed sRGB hex with all styles\n * inlined. This converts a single `oklch(...)` token to hex, compositing any\n * alpha over a flat background (white for light cards, the dark card surface\n * for dark cards) so the result is opaque and email-safe.\n *\n * This is the single source of the conversion that `sk comms curated` and the\n * `CuratedElements` email template previously duplicated.\n */\n\n/** Brand pink (hsl(344 57% 54%)) — fallback when a component palette is missing. */\nexport const BRAND_PINK = '#cd476a'\n\nfunction hexToRgb(hex: string): [number, number, number] {\n const h = hex.replace('#', '')\n return [\n parseInt(h.slice(0, 2), 16),\n parseInt(h.slice(2, 4), 16),\n parseInt(h.slice(4, 6), 16),\n ]\n}\n\n/**\n * Convert a single `oklch(...)` token to an email-safe sRGB hex. When the token\n * carries an alpha (e.g. the badge `bg` @ 0.15 / `border` @ 0.3), the colour is\n * composited over `overHex` so the result is a flat opaque hex.\n *\n * @param token An `oklch(L C H)` or `oklch(L C H / A)` string.\n * @param overHex The flat background to composite alpha over (default white).\n */\nexport function oklchToHex(token: string, overHex = '#ffffff'): string {\n const m = token.match(\n /oklch\\(\\s*([\\d.]+)\\s+([\\d.]+)\\s+([\\d.]+)\\s*(?:\\/\\s*([\\d.]+)\\s*)?\\)/\n )\n if (!m) return BRAND_PINK\n const L = parseFloat(m[1])\n const C = parseFloat(m[2])\n const H = parseFloat(m[3])\n const alpha = m[4] !== undefined ? parseFloat(m[4]) : 1\n const h = (H * Math.PI) / 180\n const a = C * Math.cos(h)\n const b = C * Math.sin(h)\n const l_ = L + 0.3963377774 * a + 0.2158037573 * b\n const m_ = L - 0.1055613458 * a - 0.0638541728 * b\n const s_ = L - 0.0894841775 * a - 1.291485548 * b\n const lr = l_ ** 3\n const mr = m_ ** 3\n const sr = s_ ** 3\n const R = 4.0767416621 * lr - 3.3077115913 * mr + 0.2309699292 * sr\n const G = -1.2684380046 * lr + 2.6097574011 * mr - 0.3413193965 * sr\n const B = -0.0041960863 * lr - 0.7034186147 * mr + 1.707614701 * sr\n const gamma = (x: number): number => {\n const c = Math.max(0, Math.min(1, x))\n return c <= 0.0031308 ? 12.92 * c : 1.055 * Math.pow(c, 1 / 2.4) - 0.055\n }\n let rgb = [gamma(R) * 255, gamma(G) * 255, gamma(B) * 255]\n if (alpha < 1) {\n const [br, bg, bb] = hexToRgb(overHex)\n rgb = [\n rgb[0] * alpha + br * (1 - alpha),\n rgb[1] * alpha + bg * (1 - alpha),\n rgb[2] * alpha + bb * (1 - alpha),\n ]\n }\n return (\n '#' + rgb.map((c) => Math.round(c).toString(16).padStart(2, '0')).join('')\n )\n}\n","/**\n * Content sanitization utilities for Skhema cards.\n *\n * DOM-free by design: `sanitizeContent` is imported by the email-safe card\n * renderer (`@skhema/embed/render`), which must run in Node / email / CLI\n * runtimes with no `document`. Keep every export here free of DOM access at\n * call time (the legacy `stripHtml` below is the one exception and is not used\n * by the renderer).\n */\n\n/**\n * HTML entity encoding for basic XSS protection. Regex-based (no DOM) so it is\n * safe in non-browser runtimes. Escapes the text-context entities plus quotes.\n */\nfunction htmlEncode(str: string): string {\n return str\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"')\n}\n\n/**\n * Sanitizes content to prevent XSS attacks and removes URLs\n * @param content The raw content to sanitize\n * @returns Sanitized HTML-safe content with URLs removed\n */\nexport function sanitizeContent(content: string): string {\n // Strip URLs first\n let sanitized = stripUrls(content)\n\n // Encode all HTML entities to prevent script injection\n sanitized = htmlEncode(sanitized)\n\n // Preserve line breaks for readability\n sanitized = sanitized.replace(/\\n/g, '<br>')\n\n // Apply text wrapping rules for long text\n sanitized = applyTextWrapping(sanitized)\n\n return sanitized\n}\n\n/**\n * Strips all URLs from the content\n * @param text The text containing potential URLs\n * @returns Text with all URLs removed\n */\nfunction stripUrls(text: string): string {\n // Comprehensive URL patterns to remove\n const patterns = [\n // Standard URLs with protocols\n /https?:\\/\\/[^\\s<>\"{}|\\\\^`[\\]]+/gi,\n // FTP URLs\n /ftp:\\/\\/[^\\s<>\"{}|\\\\^`[\\]]+/gi,\n // URLs without protocol but with www\n /www\\.[^\\s<>\"{}|\\\\^`[\\]]+/gi,\n // Email-like patterns\n /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}/gi,\n // Common domain patterns (anything.com, anything.org, etc.)\n /(?:^|\\s)([a-zA-Z0-9-]+\\.)+[a-zA-Z]{2,}(?:\\/[^\\s]*)?/gi,\n ]\n\n let stripped = text\n patterns.forEach((pattern) => {\n stripped = stripped.replace(pattern, '')\n })\n\n // Clean up any multiple spaces left after URL removal\n stripped = stripped.replace(/\\s+/g, ' ').trim()\n\n return stripped\n}\n\n/**\n * Applies intelligent text wrapping to prevent layout breaking\n * @param text The text to apply wrapping rules to\n * @returns Text with appropriate wrapping hints\n */\nfunction applyTextWrapping(text: string): string {\n // Split text into words\n const words = text.split(/(\\s+)/)\n\n return words\n .map((word) => {\n // Skip if it's whitespace or already contains HTML\n if (/^\\s+$/.test(word) || word.includes('<')) {\n return word\n }\n\n // For very long words (>30 chars), add word-break opportunities\n if (word.length > 30) {\n // Insert zero-width spaces every 10 characters for breaking\n return word.replace(/(.{10})/g, '$1\\u200B')\n }\n\n return word\n })\n .join('')\n}\n\n/**\n * Validates if content contains potentially malicious patterns or URLs\n * @param content The content to validate\n * @returns Object with validation status and detected issues\n */\nexport function validateContentSecurity(content: string): {\n isSecure: boolean\n issues: string[]\n} {\n const issues: string[] = []\n\n // Check for script tags\n if (/<script[\\s>]/i.test(content)) {\n issues.push('Script tags detected')\n }\n\n // Check for event handlers\n if (/on\\w+\\s*=/i.test(content)) {\n issues.push('Event handlers detected')\n }\n\n // Check for javascript: protocol\n if (/javascript:/i.test(content)) {\n issues.push('JavaScript protocol detected')\n }\n\n // Check for data: URLs that could contain scripts\n if (/data:[^,]*script/i.test(content)) {\n issues.push('Data URL with script detected')\n }\n\n // Check for iframe tags\n if (/<iframe[\\s>]/i.test(content)) {\n issues.push('Iframe tags detected')\n }\n\n // Check for URLs (since we want to disallow them)\n if (/https?:\\/\\//i.test(content) || /www\\./i.test(content)) {\n issues.push('URLs detected in content')\n }\n\n return {\n isSecure: issues.length === 0,\n issues,\n }\n}\n\n/**\n * Strips all HTML tags from content\n * @param content The content to strip\n * @returns Plain text content\n */\nexport function stripHtml(content: string): string {\n const div = document.createElement('div')\n div.innerHTML = content\n return div.textContent || div.innerText || ''\n}\n\n/**\n * Checks if content contains any URLs\n * @param content The content to check\n * @returns True if URLs are found\n */\nexport function containsUrls(content: string): boolean {\n const urlPatterns = [\n /https?:\\/\\//i,\n /ftp:\\/\\//i,\n /www\\./i,\n /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}/i,\n /(?:^|\\s)([a-zA-Z0-9-]+\\.)+[a-zA-Z]{2,}(?:\\/[^\\s]*)?/i,\n ]\n\n return urlPatterns.some((pattern) => pattern.test(content))\n}\n","/**\n * `@skhema/embed/render` — the canonical, DOM-free source of truth for the\n * official Skhema element / component card HTML.\n *\n * `renderElementCardHtml(data)` / `renderComponentCardHtml(data)` return\n * EMAIL-SAFE HTML: a `role=\"presentation\"` table layout with every style\n * inlined as flat hex (no shadow DOM, no `<style>`, no `oklch()`, no CSS vars).\n * The same output is used three ways:\n *\n * 1. the live browser embed (`SkhemaElement` / `SkhemaComponent` inject it\n * into shadow DOM and layer hover/transition CSS on top);\n * 2. the `CuratedElements` email template (injected verbatim); and\n * 3. any third-party / contributor email generator.\n *\n * The module is pure — importing it never touches the DOM, so it is safe in\n * Node, edge, and email build runtimes. It builds NO URLs: callers pass the\n * fully-formed `saveUrl` (e.g. the `/save` handoff) so each surface controls\n * its own UTM tagging.\n */\nimport type { ElementValue } from '@skhema/types'\nimport {\n CARD_PALETTE,\n CARD_RADIUS,\n CARD_SHADOW,\n COMPONENT_COLORS,\n FONT_STACK,\n MONO_STACK,\n PRIMARY_HEX,\n USER_ICON_SVG,\n type CardTheme,\n} from '../styles/design-tokens.js'\nimport { oklchToHex } from '../utils/color.js'\nimport {\n getComponentTypeAcronym,\n getComponentTypeLabel,\n resolveComponentType,\n} from '../utils/component-validation.js'\nimport { sanitizeContent } from '../utils/sanitization.js'\nimport { getElementTypeLabel } from '../utils/validation.js'\n\n/* ------------------------------------------------------------------ *\n * Public data shapes (documented contract — see README \"render\") *\n * ------------------------------------------------------------------ */\n\n/** Card theme. Email is always `'light'`; the browser embed passes the\n * detected page theme. Defaults to `'light'` when omitted. */\nexport type { CardTheme }\n\n/** Author attribution shared by both card kinds. */\ninterface AuthorFields {\n /** Display name. When omitted, falls back to a humanised `contributorId`. */\n authorName?: string | null\n /** Public contributor slug — when present, the name links to the profile. */\n authorSlug?: string | null\n /** Contributor id, used only for the name fallback when `authorName` is unset. */\n contributorId?: string | null\n}\n\n/** Input for {@link renderElementCardHtml}. */\nexport interface ElementCardData extends AuthorFields {\n /** Skhema element type value, e.g. `\"key_challenge\"`. */\n elementType: string\n /** The element content / premise (plain text; sanitised + escaped here). */\n content: string\n /** Pre-built handoff URL for the \"Save to Skhema\" button. */\n saveUrl: string\n /** Card theme (default `'light'`). */\n theme?: CardTheme\n}\n\n/** A single element row inside a component card. */\nexport interface ComponentCardElement {\n elementType: string\n content: string\n}\n\n/** Input for {@link renderComponentCardHtml}. */\nexport interface ComponentCardData extends AuthorFields {\n /** Skhema component type value, e.g. `\"diagnosis\"`. */\n componentType: string\n /** Optional component title shown after a \"—\" separator in the header. */\n title?: string | null\n /** The component's elements, in display order. */\n elements: ComponentCardElement[]\n /** Pre-built handoff URL for the \"Save to Skhema\" button. */\n saveUrl: string\n /** Card theme (default `'light'`). */\n theme?: CardTheme\n}\n\n/* ------------------------------------------------------------------ *\n * Internal helpers *\n * ------------------------------------------------------------------ */\n\nfunction escapeHtml(text: string): string {\n return text\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"')\n}\n\nfunction escapeAttr(text: string): string {\n return escapeHtml(text).replace(/'/g, ''')\n}\n\nfunction humaniseContributorId(contributorId: string): string {\n return contributorId\n .split(/[_-]/)\n .map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())\n .join(' ')\n}\n\ninterface BadgePalette {\n badgeBg: string\n badgeText: string\n badgeBorder: string\n topBorder: string\n}\n\n/** Resolve the email-safe badge / top-border hex for a component type + theme. */\nfunction resolveBadgePalette(\n componentType: string,\n theme: CardTheme\n): BadgePalette {\n const surface = CARD_PALETTE[theme].cardBg\n const colors =\n COMPONENT_COLORS[componentType as keyof typeof COMPONENT_COLORS]\n if (!colors) {\n return {\n badgeBg: surface,\n badgeText: PRIMARY_HEX,\n badgeBorder: PRIMARY_HEX,\n topBorder: PRIMARY_HEX,\n }\n }\n const text = oklchToHex(colors.text)\n return {\n badgeBg: oklchToHex(colors.bg, surface),\n badgeText: text,\n badgeBorder: oklchToHex(colors.border, surface),\n topBorder: text,\n }\n}\n\n/** Contributor line inner HTML: person icon + \"By {author}\" (linked if slug). */\nfunction renderAuthorHtml(author: AuthorFields, mutedColor: string): string {\n let label = ''\n if (author.authorName && author.authorName.trim()) {\n const name = escapeHtml(author.authorName.trim())\n label = author.authorSlug\n ? `By <a href=\"https://skhema.com/contributors/${encodeURIComponent(\n author.authorSlug\n )}\" style=\"color:${mutedColor};text-decoration:none;\" target=\"_blank\" rel=\"noopener noreferrer\">${name}</a>`\n : `By ${name}`\n } else if (author.contributorId && author.contributorId.trim()) {\n label = `By ${escapeHtml(humaniseContributorId(author.contributorId.trim()))}`\n }\n\n if (!label) return ' '\n\n return (\n `<span style=\"display:inline-block;width:14px;height:14px;vertical-align:middle;color:${mutedColor};\">${USER_ICON_SVG}</span>` +\n `<span style=\"vertical-align:middle;padding-left:6px;\">${label}</span>`\n )\n}\n\n/** The shared footer (contributor line + save button + attribution). */\nfunction renderFooter(\n saveUrl: string,\n author: AuthorFields,\n theme: CardTheme\n): string {\n const p = CARD_PALETTE[theme]\n return (\n `<tr><td style=\"padding:12px 16px;border-top:1px solid ${p.border};\">` +\n `<table role=\"presentation\" cellpadding=\"0\" cellspacing=\"0\" border=\"0\" style=\"width:100%;border-collapse:collapse;\"><tr>` +\n `<td style=\"vertical-align:middle;\"><span style=\"font-size:12px;color:${p.textMuted};\">${renderAuthorHtml(\n author,\n p.textMuted\n )}</span></td>` +\n `<td style=\"text-align:right;vertical-align:middle;white-space:nowrap;\">` +\n `<a href=\"${escapeAttr(saveUrl)}\" class=\"skhema-save-btn\" target=\"_blank\" rel=\"noopener noreferrer\" title=\"Save this to Skhema\" style=\"display:inline-block;background:${PRIMARY_HEX};color:#ffffff;font-size:12px;font-weight:500;text-decoration:none;padding:6px 14px;border-radius:${CARD_RADIUS};white-space:nowrap;\">Save to Skhema →</a>` +\n `</td></tr></table>` +\n `<div style=\"font-size:11px;line-height:1.4;color:${p.textMuted};margin-top:8px;\">Strategy powered by <a href=\"https://skhema.com\" style=\"color:${p.textMuted};text-decoration:underline;\" target=\"_blank\" rel=\"noopener noreferrer\">Skhema</a></div>` +\n `</td></tr>`\n )\n}\n\n/** Header row: acronym badge + type label (+ optional \"— title\" for components). */\nfunction renderHeader(\n badge: BadgePalette,\n acronym: string,\n typeLabel: string,\n labelColor: string,\n theme: CardTheme,\n title?: string | null\n): string {\n const p = CARD_PALETTE[theme]\n const titleHtml = title\n ? `<span style=\"color:${p.textMuted};\"> — </span><span style=\"font-weight:600;color:${p.text};\">${escapeHtml(\n title\n )}</span>`\n : ''\n return (\n `<tr><td style=\"padding:12px 16px;border-bottom:1px solid ${p.border};\">` +\n `<table role=\"presentation\" cellpadding=\"0\" cellspacing=\"0\" border=\"0\" style=\"width:100%;border-collapse:collapse;\"><tr>` +\n `<td style=\"width:1%;white-space:nowrap;padding-right:8px;vertical-align:middle;\">` +\n `<span class=\"skhema-acronym-badge\" title=\"${escapeAttr(typeLabel)}\" style=\"display:inline-block;font-family:${MONO_STACK};font-size:10px;font-weight:600;letter-spacing:0.02em;padding:2px 6px;border-radius:2px;background:${badge.badgeBg};color:${badge.badgeText};border:1px solid ${badge.badgeBorder};\">${escapeHtml(\n acronym\n )}</span></td>` +\n `<td style=\"vertical-align:middle;\"><span style=\"font-size:13px;font-weight:500;color:${labelColor};\">${escapeHtml(\n typeLabel\n )}${titleHtml}</span></td>` +\n `</tr></table></td></tr>`\n )\n}\n\n/** Open the outer card table with inline card styling for the theme. */\nfunction cardOpen(theme: CardTheme, kind: 'element' | 'component'): string {\n const p = CARD_PALETTE[theme]\n return (\n `<table role=\"presentation\" cellpadding=\"0\" cellspacing=\"0\" border=\"0\" class=\"skhema-card\" data-skhema-kind=\"${kind}\" data-theme=\"${theme}\" ` +\n `style=\"width:100%;max-width:600px;border-collapse:separate;background:${p.cardBg};border:1px solid ${p.border};border-radius:${CARD_RADIUS};box-shadow:${CARD_SHADOW};overflow:hidden;margin:8px 0;font-family:${FONT_STACK};line-height:1.5;color:${p.text};\">`\n )\n}\n\n/* ------------------------------------------------------------------ *\n * Public renderers *\n * ------------------------------------------------------------------ */\n\n/**\n * Render the official Skhema **element** card as email-safe HTML.\n * The badge acronym + palette resolve from the element's owning component type,\n * exactly like the live `<skhema-element>` embed.\n */\nexport function renderElementCardHtml(data: ElementCardData): string {\n const theme = data.theme ?? 'light'\n const p = CARD_PALETTE[theme]\n const label = getElementTypeLabel(data.elementType as ElementValue)\n const componentType = resolveComponentType(data.elementType)\n const acronym = getComponentTypeAcronym(componentType)\n const badge = resolveBadgePalette(componentType, theme)\n\n return (\n cardOpen(theme, 'element') +\n renderHeader(badge, acronym, label, p.text, theme) +\n `<tr><td style=\"padding:16px;\">` +\n `<div style=\"font-size:15px;line-height:1.6;color:${p.text};word-wrap:break-word;overflow-wrap:break-word;\">${sanitizeContent(\n data.content\n )}</div>` +\n `</td></tr>` +\n renderFooter(data.saveUrl, data, theme) +\n `</table>`\n )\n}\n\n/**\n * Render the official Skhema **component** card as email-safe HTML — a coloured\n * top bar, header (badge + type label + optional title), per-element-type\n * groups, and the shared footer.\n */\nexport function renderComponentCardHtml(data: ComponentCardData): string {\n const theme = data.theme ?? 'light'\n const p = CARD_PALETTE[theme]\n const label = getComponentTypeLabel(data.componentType)\n const acronym = getComponentTypeAcronym(data.componentType)\n const badge = resolveBadgePalette(data.componentType, theme)\n\n // Group elements by type (preserving first-occurrence order), mirroring the\n // live `<skhema-component>` body: one small-caps label per type, with each\n // element's content as a left-ruled block beneath it.\n const groups = new Map<string, string[]>()\n for (const el of data.elements) {\n const existing = groups.get(el.elementType)\n if (existing) existing.push(el.content)\n else groups.set(el.elementType, [el.content])\n }\n\n const groupsHtml = Array.from(groups.entries())\n .map(\n ([elementType, contents]) =>\n `<div style=\"margin-bottom:16px;\">` +\n `<div style=\"text-transform:uppercase;letter-spacing:0.05em;font-family:${MONO_STACK};font-size:10px;font-weight:600;color:${p.textMuted};margin:0 0 4px;\">${escapeHtml(\n getElementTypeLabel(elementType as ElementValue)\n )}</div>` +\n contents\n .map(\n (content) =>\n `<div style=\"font-size:14px;line-height:1.6;color:${p.text};padding-left:8px;border-left:2px solid ${p.border};word-wrap:break-word;overflow-wrap:break-word;\">${sanitizeContent(\n content\n )}</div>`\n )\n .join('') +\n `</div>`\n )\n .join('')\n\n return (\n cardOpen(theme, 'component') +\n `<tr><td style=\"height:2px;line-height:2px;font-size:0;background:${badge.topBorder};\"> </td></tr>` +\n renderHeader(badge, acronym, label, p.textMuted, theme, data.title) +\n `<tr><td style=\"padding:16px;\">${groupsHtml}</td></tr>` +\n renderFooter(data.saveUrl, data, theme) +\n `</table>`\n )\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
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":";;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;AAWO,MAAM,eAAe;AAAA,EAC1B,OAAO;AAAA,IACL,QAAQ;AAAA;AAAA,IACR,QAAQ;AAAA;AAAA,IACR,MAAM;AAAA;AAAA,IACN,WAAW;AAAA;AAAA,EAAA;AAAA,EAEb,MAAM;AAAA,IACJ,QAAQ;AAAA;AAAA,IACR,QAAQ;AAAA;AAAA,IACR,MAAM;AAAA;AAAA,IACN,WAAW;AAAA;AAAA,EAAA;AAEf;AAKO,MAAM,cAAc;AACpB,MAAM,oBAAoB;AAC1B,MAAM,sBAAsB;AAG5B,MAAM,cAAc;AACpB,MAAM,cACX;AACK,MAAM,iBACX;AACK,MAAM,aACX;AACK,MAAM,aACX;AAKK,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,gBAAgB;ACjGtB,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;;;;;;;;;;;;;;;;;;;;;;;"}
|
|
@@ -1 +0,0 @@
|
|
|
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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"}
|