@skhema/embed 0.1.10 → 0.1.12

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.
@@ -1,5 +1,5 @@
1
1
  "use strict";
2
- const types = require("@skhema/types");
2
+ const vocabulary = require("@skhema/method/vocabulary");
3
3
  const COMPONENT_COLORS = {
4
4
  diagnosis: {
5
5
  bg: "oklch(0.65 0.06 25 / 0.15)",
@@ -79,30 +79,30 @@ const CARD_VARS = {
79
79
  };
80
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>`;
81
81
  function isValidComponentType(componentType) {
82
- const validTypes = Object.values(types.COMPONENT_TYPES).map((t) => t.value);
82
+ const validTypes = Object.values(vocabulary.COMPONENT_TYPES).map((t) => t.value);
83
83
  return validTypes.includes(componentType);
84
84
  }
85
85
  function validateElementBelongsToComponent(elementType, componentType) {
86
86
  if (!isValidComponentType(componentType)) return false;
87
- const validElements = types.SKHEMA_MAPPING.elementFlow[componentType];
87
+ const validElements = vocabulary.SKHEMA_MAPPING.elementFlow[componentType];
88
88
  if (!validElements) return false;
89
89
  return validElements.some((e) => e.value === elementType);
90
90
  }
91
91
  function getComponentTypeLabel(componentType) {
92
- const type = Object.values(types.COMPONENT_TYPES).find(
92
+ const type = Object.values(vocabulary.COMPONENT_TYPES).find(
93
93
  (t) => t.value === componentType
94
94
  );
95
95
  return type?.label || componentType;
96
96
  }
97
97
  function getComponentTypeAcronym(componentType) {
98
- const type = Object.values(types.COMPONENT_TYPES).find(
98
+ const type = Object.values(vocabulary.COMPONENT_TYPES).find(
99
99
  (t) => t.value === componentType
100
100
  );
101
101
  return type?.acronym || componentType.substring(0, 2).toUpperCase();
102
102
  }
103
103
  function resolveComponentType(elementType) {
104
104
  for (const [componentValue, elements] of Object.entries(
105
- types.SKHEMA_MAPPING.elementFlow
105
+ vocabulary.SKHEMA_MAPPING.elementFlow
106
106
  )) {
107
107
  if (elements.some((e) => e.value === elementType)) {
108
108
  return componentValue;
@@ -112,11 +112,11 @@ function resolveComponentType(elementType) {
112
112
  }
113
113
  function getElementTypesForComponent(componentType) {
114
114
  if (!isValidComponentType(componentType)) return [];
115
- const elements = types.SKHEMA_MAPPING.elementFlow[componentType];
115
+ const elements = vocabulary.SKHEMA_MAPPING.elementFlow[componentType];
116
116
  return elements?.map((e) => e.value) || [];
117
117
  }
118
118
  function isValidElementType(elementType) {
119
- const validTypes = Object.values(types.ELEMENT_TYPES).map((type) => type.value);
119
+ const validTypes = Object.values(vocabulary.ELEMENT_TYPES).map((type) => type.value);
120
120
  return validTypes.includes(elementType);
121
121
  }
122
122
  function validateAttributes(element) {
@@ -126,7 +126,7 @@ function validateAttributes(element) {
126
126
  if (!elementType) {
127
127
  errors.push("Missing required attribute: element-type");
128
128
  } else if (!isValidElementType(elementType)) {
129
- const validTypes = Object.values(types.ELEMENT_TYPES).map((t) => t.value).join(", ");
129
+ const validTypes = Object.values(vocabulary.ELEMENT_TYPES).map((t) => t.value).join(", ");
130
130
  errors.push(
131
131
  `Invalid element-type "${elementType}". Valid types: ${validTypes}`
132
132
  );
@@ -144,11 +144,11 @@ function validateAttributes(element) {
144
144
  };
145
145
  }
146
146
  function getElementTypeLabel(elementType) {
147
- const type = Object.values(types.ELEMENT_TYPES).find((t) => t.value === elementType);
147
+ const type = Object.values(vocabulary.ELEMENT_TYPES).find((t) => t.value === elementType);
148
148
  return type?.label || elementType;
149
149
  }
150
150
  function getElementTypeAcronym(elementType) {
151
- const type = Object.values(types.ELEMENT_TYPES).find((t) => t.value === elementType);
151
+ const type = Object.values(vocabulary.ELEMENT_TYPES).find((t) => t.value === elementType);
152
152
  return type?.acronym || elementType.substring(0, 2).toUpperCase();
153
153
  }
154
154
  exports.CARD_PALETTE = CARD_PALETTE;
@@ -173,4 +173,4 @@ exports.isValidElementType = isValidElementType;
173
173
  exports.resolveComponentType = resolveComponentType;
174
174
  exports.validateAttributes = validateAttributes;
175
175
  exports.validateElementBelongsToComponent = validateElementBelongsToComponent;
176
- //# sourceMappingURL=validation-DCw9rQeH.cjs.map
176
+ //# sourceMappingURL=validation-IgKmvB17.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validation-IgKmvB17.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/method/vocabulary'\nimport { COMPONENT_TYPES, SKHEMA_MAPPING } from '@skhema/method/vocabulary'\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/method/vocabulary'\nimport { ELEMENT_TYPES } from '@skhema/method/vocabulary'\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,WAAAA,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,WAAAA,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,WAAAA,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,WAAAA,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,0BAAe;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,WAAAA,eAAe,YACb,aACF;AACF,SAAO,UAAU,IAAI,CAAC,MAAM,EAAE,KAAK,KAAK,CAAA;AAC1C;AC3EO,SAAS,mBACd,aAC6B;AAC7B,QAAM,aAAa,OAAO,OAAOC,WAAAA,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,WAAAA,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,wBAAa,EAAE,KAAK,CAAC,MAAM,EAAE,UAAU,WAAW;AAC7E,SAAO,MAAM,SAAS;AACxB;AAEO,SAAS,sBAAsB,aAAmC;AACvE,QAAM,OAAO,OAAO,OAAOA,wBAAa,EAAE,KAAK,CAAC,MAAM,EAAE,UAAU,WAAW;AAC7E,SAAO,MAAM,WAAW,YAAY,UAAU,GAAG,CAAC,EAAE,YAAA;AACtD;;;;;;;;;;;;;;;;;;;;;;;"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@skhema/embed",
3
- "version": "0.1.10",
3
+ "version": "0.1.12",
4
4
  "description": "Embeddable component for integrating strategic elements with Skhema.",
5
5
  "main": "dist/index.cjs",
6
6
  "module": "dist/index.es.js",
@@ -70,7 +70,7 @@
70
70
  "author": "Skhema Team",
71
71
  "license": "MIT",
72
72
  "dependencies": {
73
- "@skhema/types": "2.5.0"
73
+ "@skhema/method": "0.3.0"
74
74
  },
75
75
  "devDependencies": {
76
76
  "@skhema/linter": "2.2.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, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;')\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, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;')\n}\n\nfunction escapeAttr(text: string): string {\n return escapeHtml(text).replace(/'/g, '&#39;')\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 '&nbsp;'\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 &rarr;</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};\"> &mdash; </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};\">&nbsp;</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, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;')\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, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;')\n}\n\nfunction escapeAttr(text: string): string {\n return escapeHtml(text).replace(/'/g, '&#39;')\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 '&nbsp;'\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 &rarr;</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};\"> &mdash; </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};\">&nbsp;</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":"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"}
@@ -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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"}