origam 2.14.0 → 2.14.1

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/CHANGELOG.md CHANGED
@@ -13,6 +13,38 @@ This project follows [Semantic Versioning](https://semver.org).
13
13
 
14
14
  ## [Unreleased]
15
15
 
16
+ ## [2.14.1] - 2026-08-13
17
+
18
+ Two fixes, both found while converting variants to props presets on the v3
19
+ line. Neither is caused by that work — they predate it and affect this 2.x
20
+ line, so they are shipped here rather than held back for a major.
21
+
22
+ ### Fixed
23
+
24
+ - **A theme default could be silently ignored.** `usePassedProps` counted a key
25
+ present in `vnode.props` with the value `undefined` as "passed". Vue does not
26
+ drop a dynamically-bound key just because its current value is `undefined`, so
27
+ the most ordinary consumer pattern — `:bg-color="state.bgColor"` — reported
28
+ the prop as declared while it was empty, and the theme default was skipped.
29
+
30
+ The symptom was a component that appeared not to follow its theme, with
31
+ nothing logged and no error. Requiring a non-`undefined` value brings this in
32
+ line with Vue's own `withDefaults()`, where an `undefined` prop falls back to
33
+ its default.
34
+
35
+ - **`color-mix()` was not recognised as a CSS colour.** `isCssColor` matched
36
+ `color(...)` (Color Level 4) but not `color-mix(...)`, which is a full
37
+ `<color>` function and one the design system uses itself. Values passed that
38
+ way fell through silently. Purely additive — no caller could have depended on
39
+ a `color-mix(...)` string being dropped. `useElevation` already listed
40
+ `color-mix` in its equivalent detector; this brings `isCssColor` in line.
41
+
42
+ ### Notes for consumers
43
+
44
+ No API change, no visual change by default. If a component of yours looked like
45
+ it was ignoring a theme default while you bound a possibly-`undefined` value to
46
+ one of its props, that is the first fix above.
47
+
16
48
  ## [2.14.0] - 2026-08-07
17
49
 
18
50
  Reported from a real integration, not from a code review. Each item was
@@ -178,8 +210,13 @@ Nuxt advisories published during this release cycle are cleared too:
178
210
  (server-side RCE via Runtime, unauthenticated OOM crash, CPU exhaustion
179
211
  while parsing). `nuxt` moves to `^4.5.1` and `typeorm` to `^0.3.31`,
180
212
  staying on the `0.3.x` line — the fix landed there, and `1.x` is a
181
- breaking change. `pnpm audit --prod` now reports **no known
182
- vulnerabilities**.
213
+ breaking change. Every advisory listed above is cleared as of this release.
214
+
215
+ Advisories are a moving target, so this is stated as of a date rather than as
216
+ a standing guarantee: on 2026-08-11, `pnpm audit --prod` surfaced three new
217
+ `high` advisories unrelated to the ones above (`image-size` ICNS and JXL/HEIF
218
+ parsers, `nanoid` custom generators). They arrived after this release and are
219
+ tracked separately — run `pnpm audit --prod` yourself for the current state.
183
220
 
184
221
  Scope note for consumers: the published `origam` package depends only on
185
222
  `@mdi/font` and `qrcode-generator`, with `shiki` / `vue` / `vue-i18n` /
package/README.md CHANGED
@@ -5,7 +5,7 @@ and TypeScript types.
5
5
 
6
6
  > 📖 **Full documentation & interactive theme builder:**
7
7
  > [https://origam.dev](https://origam.dev) *(coming soon)* ·
8
- > [GitHub repository](https://github.com/arnaudprioul/origam)
8
+ > [GitHub repository](https://github.com/Elyseranet/origam)
9
9
 
10
10
  ## Installation
11
11
 
@@ -75,9 +75,9 @@ Token stylesheets are exposed as dedicated entries: `origam/styles`
75
75
  ## Links
76
76
 
77
77
  - [Documentation & theme builder](https://origam.dev) *(coming soon)*
78
- - [Changelog](https://github.com/arnaudprioul/origam/blob/main/CHANGELOG.md)
79
- - [Issues](https://github.com/arnaudprioul/origam/issues)
78
+ - [Changelog](https://github.com/Elyseranet/origam/blob/main/CHANGELOG.md)
79
+ - [Issues](https://github.com/Elyseranet/origam/issues)
80
80
 
81
81
  ## License
82
82
 
83
- [MIT](https://github.com/arnaudprioul/origam/blob/main/LICENSE) © Prioul Arnaud
83
+ [MIT](https://github.com/Elyseranet/origam/blob/main/LICENSE) © Prioul Arnaud
@@ -128,7 +128,13 @@ defineExpose({
128
128
  }
129
129
 
130
130
  #{$this}__label {
131
- font-size: .75rem
131
+ // `rem` resolves against the document root, not this component's
132
+ // ancestor — a plain literal here would be immune to the typography
133
+ // bridge `OrigamColorPickerField` republishes on the teleported
134
+ // surface (see `useTeleportTypography`). Generic-first read, same
135
+ // convention as `useTypography`'s rollout: the bridged var wins when
136
+ // present, the historical size is the fallback.
137
+ font-size: var(--origam-color-picker-edit__label---font-size, .75rem);
132
138
  }
133
139
  }
134
140
  }
@@ -128,7 +128,7 @@
128
128
 
129
129
  <script lang="ts" setup>
130
130
  import { OrigamColorPicker, OrigamMenu, OrigamSheet, OrigamTextField, OrigamTranslateScale } from "../../components";
131
- import { useDefaults, useLocale, useProps, useVModel, useStyle } from "../../composables";
131
+ import { useDefaults, useLocale, useProps, useTeleportTypography, useVModel, useStyle } from "../../composables";
132
132
  import { COLOR_NULL, ORIGAM_FORM_KEY } from "../../consts";
133
133
  import { BLOCK, DENSITY, DIRECTION, TEXT_FIELD_TYPE } from "../../enums";
134
134
  import { forwardRefs, HSVtoCSS, matchesSelector } from "../../utils";
@@ -295,13 +295,24 @@ const form = inject(ORIGAM_FORM_KEY, null);
295
295
  const menuDisabled = computed(() => {
296
296
  return props.readonly || form?.isReadonly.value;
297
297
  });
298
+ const { typographyStyles: menuTypographyStyles } = useTeleportTypography(origamTextFieldRef, menu, (fontSize) => ({
299
+ "--origam-picker-title---font-size": fontSize,
300
+ "--origam-color-picker-edit__label---font-size": fontSize
301
+ }));
298
302
  const menuProps = computed(() => {
303
+ const consumerContentProps = props.menuProps?.contentProps ?? {};
299
304
  return {
300
305
  ...props.menuProps,
301
306
  activatorProps: {
302
307
  ...props.menuProps?.activatorProps || {},
303
308
  "aria-haspopup": "colorpickerbox"
304
309
  // Set aria-haspopup to 'listbox'
310
+ },
311
+ contentProps: {
312
+ ...consumerContentProps,
313
+ // The consumer's own style is listed last so it still wins —
314
+ // the bridge is a default, not a lock.
315
+ style: [menuTypographyStyles.value, consumerContentProps.style]
305
316
  }
306
317
  };
307
318
  });
@@ -269,7 +269,12 @@ defineExpose({
269
269
  display: grid;
270
270
  grid-template-rows: repeat(7, 1fr);
271
271
  column-gap: 4px;
272
- font-size: .85rem;
272
+ // `rem` resolves against the document root, not this component's
273
+ // ancestor — a plain literal here would be immune to the typography
274
+ // bridge `OrigamDatePickerField` republishes on the teleported
275
+ // surface (see `useTeleportTypography`). Generic-first read, same
276
+ // convention as `useTypography`'s rollout.
277
+ font-size: var(--origam-date-picker-month__weeks---font-size, .85rem);
273
278
 
274
279
  + #{$this}__days {
275
280
  grid-row-gap: 0;
@@ -277,7 +282,7 @@ defineExpose({
277
282
  }
278
283
 
279
284
  &__weekday {
280
- font-size: .85rem;
285
+ font-size: var(--origam-date-picker-month__weekday---font-size, .85rem);
281
286
  }
282
287
 
283
288
  &__days {
@@ -300,6 +305,13 @@ defineExpose({
300
305
  :deep(.origam-btn) {
301
306
  --origam-btn---height: 24px;
302
307
  --origam-btn---size: .85rem;
308
+ // `OrigamBtn`'s own `--size-*` modifier sets `--origam-btn---font-size`
309
+ // directly on this same element (its size prop is unset here, so it's
310
+ // the default rung) — an inherited custom property from an ancestor
311
+ // loses to that local declaration. A plain `font-size` read here, at
312
+ // equal locality, is what actually wins; same rem-trap as `&__weeks` /
313
+ // `&__weekday` above, bridged token first, historical size as fallback.
314
+ font-size: var(--origam-date-picker-month__day---font-size, .85rem);
303
315
  z-index: 1;
304
316
  }
305
317
 
@@ -180,7 +180,7 @@ import {
180
180
  OrigamTextField,
181
181
  OrigamTranslateScale
182
182
  } from "../../components";
183
- import { useDate, useDefaults, useLocale, useProps, useTextColor, useVModel, useStyle } from "../../composables";
183
+ import { useDate, useDefaults, useLocale, useProps, useTeleportTypography, useTextColor, useVModel, useStyle } from "../../composables";
184
184
  import { ORIGAM_FORM_KEY } from "../../consts";
185
185
  import { BLOCK, DENSITY, DIRECTION, KEYBOARD_VALUES, MDI_ICONS, TEXT_FIELD_TYPE } from "../../enums";
186
186
  import { forwardRefs, isEmpty, matchesSelector, wrapInArray } from "../../utils";
@@ -370,13 +370,26 @@ const form = inject(ORIGAM_FORM_KEY, null);
370
370
  const menuDisabled = computed(() => {
371
371
  return props.readonly || form?.isReadonly.value;
372
372
  });
373
+ const { typographyStyles: menuTypographyStyles } = useTeleportTypography(origamTextFieldRef, menu, (fontSize) => ({
374
+ "--origam-picker-title---font-size": fontSize,
375
+ "--origam-date-picker-month__weekday---font-size": fontSize,
376
+ "--origam-date-picker-month__weeks---font-size": fontSize,
377
+ "--origam-date-picker-month__day---font-size": fontSize
378
+ }));
373
379
  const menuProps = computed(() => {
380
+ const consumerContentProps = props.menuProps?.contentProps ?? {};
374
381
  return {
375
382
  ...props.menuProps,
376
383
  activatorProps: {
377
384
  ...props.menuProps?.activatorProps || {},
378
385
  "aria-haspopup": "datepickerbox"
379
386
  // Set aria-haspopup to 'listbox'
387
+ },
388
+ contentProps: {
389
+ ...consumerContentProps,
390
+ // The consumer's own style is listed last so it still wins —
391
+ // the bridge is a default, not a lock.
392
+ style: [menuTypographyStyles.value, consumerContentProps.style]
380
393
  }
381
394
  };
382
395
  });
@@ -783,15 +783,17 @@ defineExpose({
783
783
  &--prepended {
784
784
  --origam-field---padding-start: 6px;
785
785
 
786
- // A prepend-inner (icon / swatch) already fills the left corner, so
787
- // opt out of the corner-clearing floor: keep the inline padding and
788
- // the start outline leg at the raw padding-start (widening them would
789
- // draw the outline over the prepend content).
786
+ // Le CONTENU se rapproche du bord (l'icone/swatch remplit le coin),
787
+ // donc on retire le plancher de degagement sur le padding.
790
788
  padding-inline: var(--origam-field---padding-start) var(--origam-field---padding-end);
791
789
 
792
- #{$this}__outline--start {
793
- flex-basis: var(--origam-field---padding-start);
794
- }
790
+ // MAIS on ne touche PAS a la largeur de la patte d'outline : ce
791
+ // segment PORTE le rayon du coin gauche, et CSS reduit proportionnellement
792
+ // un `border-radius` qui depasse la boite qui le declare (une patte de
793
+ // 6px ne peut pas rendre un rayon de 12px+ : le coin sort plat). Le
794
+ // couplage n'avait pas lieu d'etre : `__outlines` est `position: absolute`
795
+ // + `pointer-events: none`, une couche decorative qui ne deplace aucun
796
+ // contenu — l'elargir ne dessine rien par-dessus le prepend.
795
797
  }
796
798
 
797
799
  &--appended {
@@ -298,6 +298,7 @@ import {
298
298
  useProps,
299
299
  useScrolling,
300
300
  useStyle,
301
+ useTeleportTypography,
301
302
  useTextColor,
302
303
  useVModel
303
304
  } from "../../composables";
@@ -541,9 +542,23 @@ const displayItems = computed(() => {
541
542
  const menuDisabled = computed(() => {
542
543
  return props.hideNoData && !displayItems.value.length || props.readonly || form?.isReadonly.value;
543
544
  });
545
+ const { typographyStyles: menuTypographyStyles } = useTeleportTypography(origamTextFieldRef, menu, (fontSize) => ({
546
+ "--origam-list-item__title---font-size": fontSize,
547
+ // Kept proportional to the title rather than pinned, so the pair
548
+ // keeps its relationship at any scale.
549
+ "--origam-list-item__subtitle---font-size": `calc(${fontSize} * 0.875)`
550
+ }));
544
551
  const menuProps = computed(() => {
552
+ const consumerProps = props.menuProps ?? {};
553
+ const consumerContentProps = consumerProps.contentProps ?? {};
545
554
  return {
546
- ...props.menuProps
555
+ ...consumerProps,
556
+ contentProps: {
557
+ ...consumerContentProps,
558
+ // The consumer's own style is listed last so it still wins —
559
+ // the bridge is a default, not a lock.
560
+ style: [menuTypographyStyles.value, consumerContentProps.style]
561
+ }
547
562
  };
548
563
  });
549
564
  const comboboxAriaAttrs = computed(() => {
@@ -18,7 +18,7 @@ function usePassedProps(_props, instanceLabel = "usePassedProps") {
18
18
  return key => {
19
19
  const vnodeProps = vm.vnode.props || {};
20
20
  for (const k in vnodeProps) {
21
- if (k === key || camelize(k) === key) return true;
21
+ if (k === key || camelize(k) === key) return vnodeProps[k] !== void 0;
22
22
  }
23
23
  return false;
24
24
  };
@@ -9,7 +9,7 @@ export function usePassedProps(_props, instanceLabel = "usePassedProps") {
9
9
  return (key) => {
10
10
  const vnodeProps = vm.vnode.props || {};
11
11
  for (const k in vnodeProps) {
12
- if (k === key || camelize(k) === key) return true;
12
+ if (k === key || camelize(k) === key) return vnodeProps[k] !== void 0;
13
13
  }
14
14
  return false;
15
15
  };
@@ -63,7 +63,10 @@ function toDeclarations(value) {
63
63
  }
64
64
  function useStyle(styles, uniq = void 0, name = (0, _utils.getCurrentInstanceName)()) {
65
65
  const fallbackId = `${name}-${(0, _utils.getUid)()}`;
66
- const id = (0, _vue.computed)(() => (0, _vue.toValue)(uniq) || fallbackId);
66
+ const id = (0, _vue.computed)(() => {
67
+ const requested = (0, _vue.toValue)(uniq);
68
+ return typeof requested === "string" && requested.length > 0 ? requested : fallbackId;
69
+ });
67
70
  const customCss = (0, _vue.computed)(() => {
68
71
  const stylesArray = toDeclarations(styles.value);
69
72
  return `#${(0, _utils.escapeCssIdent)(id.value)} {${stylesArray.join(";")}}`;
@@ -65,7 +65,10 @@ function toDeclarations(value) {
65
65
  }
66
66
  export function useStyle(styles, uniq = void 0, name = getCurrentInstanceName()) {
67
67
  const fallbackId = `${name}-${getUid()}`;
68
- const id = computed(() => toValue(uniq) || fallbackId);
68
+ const id = computed(() => {
69
+ const requested = toValue(uniq);
70
+ return typeof requested === "string" && requested.length > 0 ? requested : fallbackId;
71
+ });
69
72
  const customCss = computed(() => {
70
73
  const stylesArray = toDeclarations(styles.value);
71
74
  return `#${escapeCssIdent(id.value)} {${stylesArray.join(";")}}`;
@@ -0,0 +1,38 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.useTeleportTypography = useTeleportTypography;
7
+ var _vue = require("vue");
8
+ function useTeleportTypography(fieldRef, isOpen, extraVars, measureSelector = ".origam-field", neutralFontSize = "16px") {
9
+ const typographyStyles = (0, _vue.ref)({});
10
+ const fieldElement = () => {
11
+ const root = fieldRef.value?.$el;
12
+ if (!root || typeof root.querySelector !== "function") return void 0;
13
+ return root.querySelector(measureSelector) ?? root;
14
+ };
15
+ const sync = () => {
16
+ const el = fieldElement();
17
+ if (!el || typeof window === "undefined") return;
18
+ const styles = window.getComputedStyle(el);
19
+ const fontSize = styles.fontSize;
20
+ if (!fontSize) return;
21
+ if (fontSize === neutralFontSize) {
22
+ typographyStyles.value = {};
23
+ return;
24
+ }
25
+ typographyStyles.value = {
26
+ "font-family": styles.fontFamily,
27
+ "font-size": fontSize,
28
+ "letter-spacing": styles.letterSpacing,
29
+ ...extraVars(fontSize)
30
+ };
31
+ };
32
+ (0, _vue.watch)(isOpen, open => {
33
+ if (open) (0, _vue.nextTick)(sync);
34
+ });
35
+ return {
36
+ typographyStyles
37
+ };
38
+ }
@@ -0,0 +1,86 @@
1
+ import type { Ref } from 'vue';
2
+ /**
3
+ * useTeleportTypography — bridges the field's REAL typography across a
4
+ * teleported surface (menu, picker, …).
5
+ *
6
+ * @description
7
+ * Floating surfaces (`OrigamMenu` and everything built on it — Select's
8
+ * option list, ColorPickerField's channel editor, DatePickerField's
9
+ * calendar) are teleported out of the field's DOM subtree, to escape
10
+ * `overflow` and stacking contexts. A CSS rule the consuming application
11
+ * writes against the field — `.my-field * { font-size: 13px }`, a compact
12
+ * form theme, a scaled container — therefore never reaches the popup: the
13
+ * selector simply does not match nodes outside the field's subtree.
14
+ *
15
+ * Re-inheriting `font-size` on the teleported root is NOT enough on its
16
+ * own, either. Descendant text that sizes itself with a `rem`-based token
17
+ * (`var(--origam-list-item__title---font-size, 1rem)`,
18
+ * `var(--origam-picker-title---font-size, .75rem)`, …) resolves `rem`
19
+ * against the DOCUMENT ROOT, not the inherited value — it would keep the
20
+ * root size whatever the teleported ancestor inherits.
21
+ *
22
+ * So this composable measures the typography that ACTUALLY WON on the
23
+ * field when the surface opens (`getComputedStyle`, not the props — the
24
+ * props can't see a consumer's own stylesheet) and hands back a plain
25
+ * style object the caller republishes on the teleported content root as
26
+ * both generic CSS (`font-family` / `font-size` / `letter-spacing`, for
27
+ * whatever inherits normally) AND the specific component tokens the
28
+ * surface's own `rem`-sized text reads (via `extraVars`), so the two
29
+ * layers of the bug are closed together. Originates from `OrigamSelect`
30
+ * (commit `8354407c`) — extracted here so `OrigamColorPickerField` /
31
+ * `OrigamDatePickerField` don't reimplement the same measurement.
32
+ *
33
+ * Measured on `.origam-field` (NOT the raw `<input>`), which is the one
34
+ * element every `<origam-text-field>`-based field renders with a plain,
35
+ * unconditional `font-size: 16px` (no prop/token influences it — see
36
+ * `OrigamField`'s own SCSS). The raw `<input>` is NOT a safe measurement
37
+ * point: on `OrigamColorPickerField` / `OrigamDatePickerField` it is
38
+ * deliberately taken out of flow and carries none of `OrigamInput`'s
39
+ * classes, so it renders at the BROWSER's own default control font
40
+ * (13.3333px in Chrome) rather than the design system's — a value with
41
+ * nothing to do with the field's real typography.
42
+ *
43
+ * That same "16px, unconditionally" fact is also what keeps this bridge a
44
+ * true no-op absent a consumer override: `neutralFontSize` (default
45
+ * `'16px'`) is the value `.origam-field` would show with zero consuming-app
46
+ * CSS involved. When the measured size still equals it, nothing diverges —
47
+ * `typographyStyles` stays empty and every surface keeps its OWN historical
48
+ * default (`.75rem` list-item text, `.85rem` calendar cells, …), instead of
49
+ * being forced to 16px on every open. Only an ACTUAL divergence (a
50
+ * consumer's stylesheet, a different field wrapper with its own baseline)
51
+ * republishes the tokens.
52
+ *
53
+ * @param fieldRef ref to the field sub-component (e.g. the wrapped
54
+ * `<origam-text-field>`). Its `$el` is the DOM root the
55
+ * typography is measured FROM.
56
+ * @param isOpen ref toggled when the teleported surface opens; the
57
+ * measurement re-runs on every open (the consumer's CSS
58
+ * can change between opens — a live theme switch, a
59
+ * responsive breakpoint, …).
60
+ * @param extraVars given the resolved `font-size`, returns the extra
61
+ * `{ '--origam-…---font-size': value }` entries for the
62
+ * specific tokens the surface's own `rem`-sized text
63
+ * reads. Read the surface's SCSS before adding one —
64
+ * republishing a var nothing consumes is a no-op.
65
+ * @param measureSelector selector for the element the typography is read
66
+ * from, resolved within `fieldRef`'s root (defaults to
67
+ * `'.origam-field'`). Falls back to the root itself when
68
+ * no match is found.
69
+ * @param neutralFontSize the `measureSelector` element's OWN unstyled
70
+ * `font-size` (defaults to `'16px'`, `.origam-field`'s
71
+ * literal default). When the measured value still equals
72
+ * this, the bridge is a no-op for this open — see above.
73
+ *
74
+ * @example
75
+ * const { typographyStyles } = useTeleportTypography(origamTextFieldRef, menu, (fontSize) => ({
76
+ * '--origam-list-item__title---font-size': fontSize,
77
+ * '--origam-list-item__subtitle---font-size': `calc(${ fontSize } * 0.875)`
78
+ * }))
79
+ * // …
80
+ * contentProps: { style: [typographyStyles.value, consumerContentProps.style] }
81
+ */
82
+ export declare function useTeleportTypography(fieldRef: Ref<{
83
+ $el?: HTMLElement;
84
+ } | undefined>, isOpen: Ref<boolean>, extraVars: (fontSize: string) => Record<string, string>, measureSelector?: string, neutralFontSize?: string): {
85
+ typographyStyles: Ref<Record<string, string>, Record<string, string>>;
86
+ };
@@ -0,0 +1,30 @@
1
+ import { nextTick, ref, watch } from "vue";
2
+ export function useTeleportTypography(fieldRef, isOpen, extraVars, measureSelector = ".origam-field", neutralFontSize = "16px") {
3
+ const typographyStyles = ref({});
4
+ const fieldElement = () => {
5
+ const root = fieldRef.value?.$el;
6
+ if (!root || typeof root.querySelector !== "function") return void 0;
7
+ return root.querySelector(measureSelector) ?? root;
8
+ };
9
+ const sync = () => {
10
+ const el = fieldElement();
11
+ if (!el || typeof window === "undefined") return;
12
+ const styles = window.getComputedStyle(el);
13
+ const fontSize = styles.fontSize;
14
+ if (!fontSize) return;
15
+ if (fontSize === neutralFontSize) {
16
+ typographyStyles.value = {};
17
+ return;
18
+ }
19
+ typographyStyles.value = {
20
+ "font-family": styles.fontFamily,
21
+ "font-size": fontSize,
22
+ "letter-spacing": styles.letterSpacing,
23
+ ...extraVars(fontSize)
24
+ };
25
+ };
26
+ watch(isOpen, (open) => {
27
+ if (open) nextTick(sync);
28
+ });
29
+ return { typographyStyles };
30
+ }
@@ -588,6 +588,18 @@ Object.keys(_teleport).forEach(function (key) {
588
588
  }
589
589
  });
590
590
  });
591
+ var _teleportTypography = require("./Commons/teleport-typography.composable.cjs");
592
+ Object.keys(_teleportTypography).forEach(function (key) {
593
+ if (key === "default" || key === "__esModule") return;
594
+ if (Object.prototype.hasOwnProperty.call(_exportNames, key)) return;
595
+ if (key in exports && exports[key] === _teleportTypography[key]) return;
596
+ Object.defineProperty(exports, key, {
597
+ enumerable: true,
598
+ get: function () {
599
+ return _teleportTypography[key];
600
+ }
601
+ });
602
+ });
591
603
  var _hydration = require("./Commons/hydration.composable.cjs");
592
604
  Object.keys(_hydration).forEach(function (key) {
593
605
  if (key === "default" || key === "__esModule") return;
@@ -46,6 +46,7 @@ export * from './Commons/margin.composable';
46
46
  export * from './Commons/lazy.composable';
47
47
  export * from './Commons/items.composable';
48
48
  export * from './Commons/teleport.composable';
49
+ export * from './Commons/teleport-typography.composable';
49
50
  export * from './Commons/hydration.composable';
50
51
  export * from './Commons/stack.composable';
51
52
  export * from './Commons/activator.composable';
@@ -46,6 +46,7 @@ export * from "./Commons/margin.composable.js";
46
46
  export * from "./Commons/lazy.composable.js";
47
47
  export * from "./Commons/items.composable.js";
48
48
  export * from "./Commons/teleport.composable.js";
49
+ export * from "./Commons/teleport-typography.composable.js";
49
50
  export * from "./Commons/hydration.composable.js";
50
51
  export * from "./Commons/stack.composable.js";
51
52
  export * from "./Commons/activator.composable.js";
@@ -45,10 +45,10 @@ var _consts = require("../../consts/index.cjs");
45
45
  var _commons = require("./commons.util.cjs");
46
46
  var _console = require("./console.util.cjs");
47
47
  function isCssColor(color) {
48
- return !!color && (/^(#|var\(--|(?:rgba?|hsla?|hwb|lab|lch|oklab|oklch|color)\()/i.test(color) || _consts.CSS_NAMED_COLORS.has(color.toLowerCase()));
48
+ return !!color && (/^(#|var\(--|(?:rgba?|hsla?|hwb|lab|lch|oklab|oklch|color-mix|color)\()/i.test(color) || _consts.CSS_NAMED_COLORS.has(color.toLowerCase()));
49
49
  }
50
50
  function isParsableColor(color) {
51
- return isCssColor(color) && !/^((rgb|hsl)a?\()?var\(--/.test(color);
51
+ return isCssColor(color) && !/^((rgb|hsl)a?\()?var\(--/.test(color) && !/^color-mix\(/i.test(color);
52
52
  }
53
53
  function parseColor(color) {
54
54
  if (typeof color === "number") {
@@ -3,15 +3,38 @@ import type { TBgFgRole, TColor, TColorType, THex, THSLA, THSVA, TIntent, TLAB,
3
3
  * Recognise any string the CSS engine treats as a colour:
4
4
  * - hex (`#abc`, `#aabbcc`, `#aabbccdd`)
5
5
  * - functional notation (`rgb()`, `rgba()`, `hsl()`, `hsla()`,
6
- * `hwb()`, `lab()`, `lch()`, `oklab()`, `oklch()`, `color()`)
6
+ * `hwb()`, `lab()`, `lch()`, `oklab()`, `oklch()`, `color()`,
7
+ * `color-mix()`)
7
8
  * - CSS variable (`var(--…)`)
8
9
  * - one of the 148 named colours OR a CSS-wide keyword
9
10
  * (`transparent`, `currentColor`, `inherit`, …)
11
+ *
12
+ * `color-mix` was added ADR-005 (Kbd pilot): it is a real CSS `<color>`
13
+ * function (e.g. `color-mix(in srgb, currentColor 8%, transparent)`, the
14
+ * literal value the pre-migration `OrigamKbd` `tonal` variant SCSS used for
15
+ * its background). Before this fix the alternation only recognised bare
16
+ * `color(...)` (CSS Color() Level 4), so `color-mix(...)` silently fell
17
+ * through — NOT a regression risk: no caller could have been relying on a
18
+ * `color-mix(...)` string being silently dropped as "expected" behaviour.
19
+ * `useElevation`'s equivalent custom-value detector
20
+ * (`CUSTOM_BOX_SHADOW_REGEX`) already listed `color-mix` — this brings
21
+ * `isCssColor` in line with it.
10
22
  */
11
23
  export declare function isCssColor(color?: string | null | false): boolean;
12
24
  /**
13
25
  * Is parsable color.
14
26
  *
27
+ * @description
28
+ * A CSS colour is "parsable" when `parseColor()` (below) can actually
29
+ * compute its RGBA components in JS — used to gate the auto-contrast
30
+ * (`getForeground`) path. `var(--…)` references are excluded because their
31
+ * final value is only known to the browser. `color-mix(...)` is excluded
32
+ * for the same reason PLUS one more: its operands are frequently
33
+ * `currentColor` or another `var(--…)`, which have no static value at all
34
+ * — `parseColor` has no case for the `color-mix` function and would throw.
35
+ * `isCssColor` recognising `color-mix` (so it still renders verbatim via
36
+ * the "custom value" style channel) does NOT make it parsable.
37
+ *
15
38
  * @param color …
16
39
  * @returns …
17
40
  */
@@ -33,10 +33,10 @@ import {
33
33
  import { chunk, clamp, has, int, padEnd } from "./commons.util.js";
34
34
  import { consoleWarn } from "./console.util.js";
35
35
  export function isCssColor(color) {
36
- return !!color && (/^(#|var\(--|(?:rgba?|hsla?|hwb|lab|lch|oklab|oklch|color)\()/i.test(color) || CSS_NAMED_COLORS.has(color.toLowerCase()));
36
+ return !!color && (/^(#|var\(--|(?:rgba?|hsla?|hwb|lab|lch|oklab|oklch|color-mix|color)\()/i.test(color) || CSS_NAMED_COLORS.has(color.toLowerCase()));
37
37
  }
38
38
  export function isParsableColor(color) {
39
- return isCssColor(color) && !/^((rgb|hsl)a?\()?var\(--/.test(color);
39
+ return isCssColor(color) && !/^((rgb|hsl)a?\()?var\(--/.test(color) && !/^color-mix\(/i.test(color);
40
40
  }
41
41
  export function parseColor(color) {
42
42
  if (typeof color === "number") {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "origam",
3
- "version": "2.14.0",
3
+ "version": "2.14.1",
4
4
  "type": "module",
5
5
  "description": "origam — a Vue 3 design system with multi-theme design tokens, CSS-first components and TypeScript types.",
6
6
  "keywords": [
@@ -106,6 +106,11 @@
106
106
  "styles:build": "sass src/assets/scss/main.scss src/assets/css/main.css --no-source-map --style=compressed",
107
107
  "tokens:watch": "node scripts/build-tokens.mjs --watch",
108
108
  "tokens:lint": "node scripts/build-tokens.mjs --dry-run --strict",
109
+ "guards": "node scripts/guards/run-all.mjs",
110
+ "guards:declarations": "node scripts/guards/no-declarations-in-vue.mjs",
111
+ "guards:variant-css": "node scripts/guards/no-variant-css.mjs",
112
+ "guards:instance-types": "node scripts/guards/instance-types.mjs",
113
+ "guards:naming": "node scripts/guards/file-naming.mjs",
109
114
  "prepublishOnly": "pnpm run build",
110
115
  "prepack": "node -e \"require('fs').copyFileSync('../../CHANGELOG.md','CHANGELOG.md')\""
111
116
  },
@@ -136,7 +141,7 @@
136
141
  "@tokens-studio/sd-transforms": "^2.0.3",
137
142
  "@types/node": "^26.1.0",
138
143
  "@vitejs/plugin-vue": "^6.0.7",
139
- "sass": "^1.89.2",
144
+ "sass": "^1.102.0",
140
145
  "style-dictionary": "^5.5.0",
141
146
  "ts-transformer-keys": "^0.4.4",
142
147
  "typescript": "^6.0.3",