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 +39 -2
- package/README.md +4 -4
- package/dist/src/components/ColorPicker/OrigamColorPickerEdit.vue +7 -1
- package/dist/src/components/ColorPickerField/OrigamColorPickerField.vue +12 -1
- package/dist/src/components/DatePicker/OrigamDatePickerMonth.vue +14 -2
- package/dist/src/components/DatePickerField/OrigamDatePickerField.vue +14 -1
- package/dist/src/components/Field/OrigamField.vue +9 -7
- package/dist/src/components/Select/OrigamSelect.vue +16 -1
- package/dist/src/composables/Commons/defaults.composable.cjs +1 -1
- package/dist/src/composables/Commons/defaults.composable.js +1 -1
- package/dist/src/composables/Commons/style.composable.cjs +4 -1
- package/dist/src/composables/Commons/style.composable.js +4 -1
- package/dist/src/composables/Commons/teleport-typography.composable.cjs +38 -0
- package/dist/src/composables/Commons/teleport-typography.composable.d.ts +86 -0
- package/dist/src/composables/Commons/teleport-typography.composable.js +30 -0
- package/dist/src/composables/index.cjs +12 -0
- package/dist/src/composables/index.d.ts +1 -0
- package/dist/src/composables/index.js +1 -0
- package/dist/src/utils/Commons/color.util.cjs +2 -2
- package/dist/src/utils/Commons/color.util.d.ts +24 -1
- package/dist/src/utils/Commons/color.util.js +2 -2
- package/package.json +7 -2
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.
|
|
182
|
-
|
|
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/
|
|
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/
|
|
79
|
-
- [Issues](https://github.com/
|
|
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/
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
787
|
-
//
|
|
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
|
-
|
|
793
|
-
|
|
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
|
-
...
|
|
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
|
|
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
|
|
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)(() =>
|
|
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(() =>
|
|
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.
|
|
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.
|
|
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",
|