origam 2.37.1 → 2.37.3
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 +19 -0
- package/dist/src/components/Img/OrigamImg.vue +4 -4
- package/dist/src/components/Lazy/OrigamLazy.vue +2 -6
- package/dist/src/components/PasswordField/OrigamPasswordField.vue +2 -2
- package/dist/src/components/TextField/OrigamTextField.vue +2 -4
- package/dist/src/components/TextareaField/OrigamTextareaField.vue +2 -4
- package/dist/src/composables/Responsive/aspect.composable.cjs +6 -1
- package/dist/src/composables/Responsive/aspect.composable.d.ts +13 -0
- package/dist/src/composables/Responsive/aspect.composable.js +7 -2
- package/dist/src/directives/Intersect/intersect.directive.cjs +1 -0
- package/dist/src/directives/Intersect/intersect.directive.js +2 -1
- package/dist/src/utils/Commons/intersect.util.cjs +11 -0
- package/dist/src/utils/Commons/intersect.util.d.ts +12 -0
- package/dist/src/utils/Commons/intersect.util.js +12 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -18,6 +18,25 @@ This project follows [Semantic Versioning](https://semver.org).
|
|
|
18
18
|
|
|
19
19
|
## [Unreleased]
|
|
20
20
|
|
|
21
|
+
## [2.37.3] - 2026-10-19
|
|
22
|
+
|
|
23
|
+
### Fixed — `v-intersect` lié avec le tuple `withDirectives` : handler jamais appelé (#1205)
|
|
24
|
+
|
|
25
|
+
`OrigamLazy`, `OrigamTextField`, `OrigamTextareaField` et `OrigamPasswordField` passaient à `v-intersect` le tuple `[{ handler, options }, null, ['once']]` comme valeur. La directive lit `.handler` / `.options` sur la valeur : sur un tableau, elle obtient `undefined`. Aucune intersection n'appelait donc le handler, `once` n'était jamais lu et les `options` n'atteignaient pas l'`IntersectionObserver`.
|
|
26
|
+
|
|
27
|
+
- **`OrigamLazy`** : le contenu ne s'affichait jamais à l'intersection, et les `options` du consommateur (ex. `rootMargin`) étaient ignorées. C'est corrigé ; mesuré dans Chromium.
|
|
28
|
+
- **Champs texte, textarea et mot de passe** : l'appel JS de `focus()` sur intersection (autofocus) n'avait jamais lieu. Dans Chromium, l'attribut natif `autofocus` masquait le défaut. Safari et Firefox n'ont pas été mesurés.
|
|
29
|
+
- Liaison désormais en `v-intersect.once="{ handler, options }"`.
|
|
30
|
+
- La directive émet un `console.warn` en mode dev (une fois par balise) quand on lui lie un tableau.
|
|
31
|
+
|
|
32
|
+
## [2.37.2] - 2026-10-19
|
|
33
|
+
|
|
34
|
+
### Fixed — `OrigamImg` sous SSR : écart d'hydratation `aspect-ratio`, et image montée côté client jamais chargée (#1192)
|
|
35
|
+
|
|
36
|
+
- **Écart d'hydratation** : sans prop `aspect-ratio`, `useAspectRatio` prend le ratio de la fenêtre (`innerWidth / innerHeight`), une valeur inconnue du serveur. Il était appliqué dès le premier rendu client d'une instance hydratée, d'où un `Hydration style mismatch`. Une instance qui hydrate l'applique désormais après `onMounted`. Une instance montée côté client l'applique tout de suite, comme avant. Un `aspect-ratio` explicite est inchangé.
|
|
37
|
+
- **Image montée côté client jamais chargée** : `v-intersect` était lié avec le tuple `withDirectives` comme valeur. La directive lisait `.handler` sur un tableau, si bien que l'intersection ne déclenchait jamais le chargement. L'`<img>` restait en `display:none` + `loading="lazy"`, que Chromium ne charge pas. La directive est désormais liée en `v-intersect.once="{ handler, options }"`, et l'`<img>` passe en `loading="eager"` dès que son chargement est demandé.
|
|
38
|
+
- La même forme de liaison subsiste dans d'autres composants : suivi en #1205.
|
|
39
|
+
|
|
21
40
|
## [2.37.1] - 2026-10-19
|
|
22
41
|
|
|
23
42
|
### Fixed — `OrigamImg` / `OrigamResponsive` : prop `tag`, `<img>` visible en SSR, fondu en rendu client seul (#1182)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<origam-responsive
|
|
3
3
|
:id="id"
|
|
4
|
-
v-intersect="intersect"
|
|
4
|
+
v-intersect.once="intersect"
|
|
5
5
|
:aria-label="alt"
|
|
6
6
|
:class="imgClasses"
|
|
7
7
|
:role="alt ? 'img' : undefined"
|
|
@@ -327,11 +327,11 @@ const markBooted = (val) => {
|
|
|
327
327
|
};
|
|
328
328
|
stop = watch(aspectRatio, markBooted);
|
|
329
329
|
markBooted(aspectRatio.value);
|
|
330
|
-
const intersect = computed(() =>
|
|
330
|
+
const intersect = computed(() => ({
|
|
331
331
|
handler: init,
|
|
332
332
|
options: props.options
|
|
333
|
-
}
|
|
334
|
-
const nativeLoading = computed(() => props.eager ? "eager" : "lazy");
|
|
333
|
+
}));
|
|
334
|
+
const nativeLoading = computed(() => props.eager || state.value !== IMG_STATE.IDLE ? "eager" : "lazy");
|
|
335
335
|
const isLoaded = computed(() => {
|
|
336
336
|
return state.value === IMG_STATE.LOADED;
|
|
337
337
|
});
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
<component
|
|
3
3
|
:is="tag"
|
|
4
4
|
:id="id"
|
|
5
|
-
v-intersect="intersect"
|
|
5
|
+
v-intersect.once="intersect"
|
|
6
6
|
:aria-busy="!isActive || undefined"
|
|
7
7
|
:class="lazyClasses"
|
|
8
8
|
:style="lazyStyles"
|
|
@@ -52,11 +52,7 @@ const { filterProps } = useProps(props);
|
|
|
52
52
|
const { dimensionStyles } = useDimension(props);
|
|
53
53
|
const isActive = useVModel(props, "modelValue");
|
|
54
54
|
const intersect = computed(() => {
|
|
55
|
-
return
|
|
56
|
-
{ handler: handleIntersect, options: props.options },
|
|
57
|
-
null,
|
|
58
|
-
isActive.value ? [] : ["once"]
|
|
59
|
-
];
|
|
55
|
+
return { handler: handleIntersect, options: props.options };
|
|
60
56
|
});
|
|
61
57
|
const handleIntersect = (isIntersecting) => {
|
|
62
58
|
if (isActive.value) return;
|
|
@@ -124,7 +124,7 @@
|
|
|
124
124
|
/>
|
|
125
125
|
<input
|
|
126
126
|
ref="inputRef"
|
|
127
|
-
v-intersect="intersect"
|
|
127
|
+
v-intersect.once="intersect"
|
|
128
128
|
:autofocus="autofocus"
|
|
129
129
|
:disabled="isDisabled"
|
|
130
130
|
:name="name"
|
|
@@ -501,7 +501,7 @@ const handleIntersect = (isIntersecting, entries) => {
|
|
|
501
501
|
entries[0].target?.focus?.();
|
|
502
502
|
};
|
|
503
503
|
const intersect = computed(() => {
|
|
504
|
-
return
|
|
504
|
+
return { handler: handleIntersect };
|
|
505
505
|
});
|
|
506
506
|
const origamInputRef = ref();
|
|
507
507
|
const origamFieldRef = ref();
|
|
@@ -83,7 +83,7 @@
|
|
|
83
83
|
/>
|
|
84
84
|
<input
|
|
85
85
|
ref="inputRef"
|
|
86
|
-
v-intersect="intersect"
|
|
86
|
+
v-intersect.once="intersect"
|
|
87
87
|
:autofocus="autofocus"
|
|
88
88
|
:disabled="isDisabled"
|
|
89
89
|
:name="name"
|
|
@@ -372,9 +372,7 @@ const max = computed(() => {
|
|
|
372
372
|
return props.counter;
|
|
373
373
|
});
|
|
374
374
|
const intersect = computed(() => {
|
|
375
|
-
return
|
|
376
|
-
handler: handleIntersect
|
|
377
|
-
}, null, ["once"]];
|
|
375
|
+
return { handler: handleIntersect };
|
|
378
376
|
});
|
|
379
377
|
const handleIntersect = (isIntersecting, entries) => {
|
|
380
378
|
if (!props.autofocus || !isIntersecting) return;
|
|
@@ -131,7 +131,7 @@
|
|
|
131
131
|
<textarea
|
|
132
132
|
v-else
|
|
133
133
|
ref="textareaRef"
|
|
134
|
-
v-intersect="intersect"
|
|
134
|
+
v-intersect.once="intersect"
|
|
135
135
|
:autofocus="autofocus"
|
|
136
136
|
:disabled="isDisabled"
|
|
137
137
|
:name="name"
|
|
@@ -499,9 +499,7 @@ const {
|
|
|
499
499
|
onClickAppend: handleClickAppend
|
|
500
500
|
} = useAdjacent(props, toRef(props, "prependIcon"), toRef(props, "appendIcon"));
|
|
501
501
|
const intersect = computed(() => {
|
|
502
|
-
return
|
|
503
|
-
handler: handleIntersect
|
|
504
|
-
}, null, ["once"]];
|
|
502
|
+
return { handler: handleIntersect };
|
|
505
503
|
});
|
|
506
504
|
const handleIntersect = (isIntersecting, entries) => {
|
|
507
505
|
if (!props.autofocus || !isIntersecting) return;
|
|
@@ -8,6 +8,11 @@ var _vue = require("vue");
|
|
|
8
8
|
var _commons = require("../../consts/Commons/commons.const.cjs");
|
|
9
9
|
const isUsableRatio = value => Number.isFinite(value) && value > 0;
|
|
10
10
|
function useAspectRatio(props) {
|
|
11
|
+
const isHydrating = _commons.IN_BROWSER && !!(0, _vue.getCurrentInstance)()?.vnode.el;
|
|
12
|
+
const isViewportReady = (0, _vue.shallowRef)(!isHydrating);
|
|
13
|
+
if (isHydrating) (0, _vue.onMounted)(() => {
|
|
14
|
+
isViewportReady.value = true;
|
|
15
|
+
});
|
|
11
16
|
const ratio = (0, _vue.computed)(() => {
|
|
12
17
|
if (props.aspectRatio) {
|
|
13
18
|
const raw = String(props.aspectRatio).trim();
|
|
@@ -21,7 +26,7 @@ function useAspectRatio(props) {
|
|
|
21
26
|
const value = Number(raw);
|
|
22
27
|
return isUsableRatio(value) ? String(value) : void 0;
|
|
23
28
|
}
|
|
24
|
-
if (!_commons.IN_BROWSER) return void 0;
|
|
29
|
+
if (!_commons.IN_BROWSER || !isViewportReady.value) return void 0;
|
|
25
30
|
const viewport = window.innerWidth / window.innerHeight;
|
|
26
31
|
return isUsableRatio(viewport) ? String(viewport) : void 0;
|
|
27
32
|
});
|
|
@@ -40,6 +40,19 @@
|
|
|
40
40
|
* runtime, never a design-time default a theme would override, so there is
|
|
41
41
|
* nothing for the token pipeline to carry.
|
|
42
42
|
*
|
|
43
|
+
* Hydration (#1192)
|
|
44
|
+
* ─────────────────
|
|
45
|
+
* The viewport fallback is a CLIENT-ONLY value: the server cannot emit it.
|
|
46
|
+
* Applied from the first render of an instance that is being hydrated, it
|
|
47
|
+
* made the client's first render differ from the server HTML
|
|
48
|
+
* (`style="width:120px;"` vs `style="aspect-ratio:1.77…;width:120px;"`),
|
|
49
|
+
* i.e. a "Hydration style mismatch" that Vue does not rectify in
|
|
50
|
+
* production. So an instance hydrating server HTML (`vnode.el` already set
|
|
51
|
+
* when `setup()` runs) holds the fallback back until `onMounted`; the style
|
|
52
|
+
* is then patched by an ordinary reactive update. An instance created on
|
|
53
|
+
* the client (no server HTML to match) applies it at once, as before.
|
|
54
|
+
* An explicit `aspectRatio` is deterministic and is emitted by both sides.
|
|
55
|
+
*
|
|
43
56
|
* Guarding the value
|
|
44
57
|
* ──────────────────
|
|
45
58
|
* The returned ratio rejects NaN, 0, negatives and Infinity. Any of them
|
|
@@ -1,7 +1,12 @@
|
|
|
1
|
-
import { computed } from "vue";
|
|
1
|
+
import { computed, getCurrentInstance, onMounted, shallowRef } from "vue";
|
|
2
2
|
import { IN_BROWSER } from "../../consts/Commons/commons.const.js";
|
|
3
3
|
const isUsableRatio = (value) => Number.isFinite(value) && value > 0;
|
|
4
4
|
export function useAspectRatio(props) {
|
|
5
|
+
const isHydrating = IN_BROWSER && !!getCurrentInstance()?.vnode.el;
|
|
6
|
+
const isViewportReady = shallowRef(!isHydrating);
|
|
7
|
+
if (isHydrating) onMounted(() => {
|
|
8
|
+
isViewportReady.value = true;
|
|
9
|
+
});
|
|
5
10
|
const ratio = computed(() => {
|
|
6
11
|
if (props.aspectRatio) {
|
|
7
12
|
const raw = String(props.aspectRatio).trim();
|
|
@@ -15,7 +20,7 @@ export function useAspectRatio(props) {
|
|
|
15
20
|
const value = Number(raw);
|
|
16
21
|
return isUsableRatio(value) ? String(value) : void 0;
|
|
17
22
|
}
|
|
18
|
-
if (!IN_BROWSER) return void 0;
|
|
23
|
+
if (!IN_BROWSER || !isViewportReady.value) return void 0;
|
|
19
24
|
const viewport = window.innerWidth / window.innerHeight;
|
|
20
25
|
return isUsableRatio(viewport) ? String(viewport) : void 0;
|
|
21
26
|
});
|
|
@@ -9,6 +9,7 @@ var _intersect = require("../../utils/Commons/intersect.util.cjs");
|
|
|
9
9
|
const Intersect = exports.Intersect = {
|
|
10
10
|
mounted(el, binding) {
|
|
11
11
|
if (!_commons.SUPPORTS_INTERSECTION) return;
|
|
12
|
+
(0, _intersect.warnIntersectTupleBinding)(el, binding.value);
|
|
12
13
|
const read = () => {
|
|
13
14
|
const b = el._observe?.[binding.instance.$.uid]?.binding ?? binding;
|
|
14
15
|
const v = b.value;
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { SUPPORTS_INTERSECTION } from "../../consts/Commons/commons.const.js";
|
|
2
|
-
import { unmountIntersect } from "../../utils/Commons/intersect.util.js";
|
|
2
|
+
import { unmountIntersect, warnIntersectTupleBinding } from "../../utils/Commons/intersect.util.js";
|
|
3
3
|
export const Intersect = {
|
|
4
4
|
mounted(el, binding) {
|
|
5
5
|
if (!SUPPORTS_INTERSECTION) return;
|
|
6
|
+
warnIntersectTupleBinding(el, binding.value);
|
|
6
7
|
const read = () => {
|
|
7
8
|
const b = el._observe?.[binding.instance.$.uid]?.binding ?? binding;
|
|
8
9
|
const v = b.value;
|
|
@@ -4,9 +4,20 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
6
|
exports.unmountIntersect = unmountIntersect;
|
|
7
|
+
exports.warnIntersectTupleBinding = warnIntersectTupleBinding;
|
|
7
8
|
function unmountIntersect(el, binding) {
|
|
8
9
|
const observe = el._observe?.[binding.instance.$.uid];
|
|
9
10
|
if (!observe) return;
|
|
10
11
|
observe.observer.unobserve(el);
|
|
11
12
|
delete el._observe[binding.instance.$.uid];
|
|
13
|
+
}
|
|
14
|
+
const _warnedIntersectTupleTags = /* @__PURE__ */new Set();
|
|
15
|
+
function warnIntersectTupleBinding(el, value) {
|
|
16
|
+
if (!Array.isArray(value)) return;
|
|
17
|
+
if (typeof console === "undefined") return;
|
|
18
|
+
if (!process.env?.DEV) return;
|
|
19
|
+
const tag = el.tagName.toLowerCase();
|
|
20
|
+
if (_warnedIntersectTupleTags.has(tag)) return;
|
|
21
|
+
_warnedIntersectTupleTags.add(tag);
|
|
22
|
+
console.warn(`[origam] v-intersect on <${tag}> is bound to an array (the withDirectives tuple): the handler will never be called. Bind { handler, options } and use the modifier, e.g. v-intersect.once="{ handler, options }".`);
|
|
12
23
|
}
|
|
@@ -6,3 +6,15 @@ import type { IIntersectDirectiveBinding, IIntersectHtmlElement } from '../../in
|
|
|
6
6
|
* @param binding …
|
|
7
7
|
*/
|
|
8
8
|
export declare function unmountIntersect(el: IIntersectHtmlElement, binding: IIntersectDirectiveBinding): void;
|
|
9
|
+
/*********************************************************
|
|
10
|
+
* warnIntersectTupleBinding
|
|
11
|
+
*
|
|
12
|
+
* @description
|
|
13
|
+
* Dev-only warning (#1205, once per element tag) when `v-intersect` is bound
|
|
14
|
+
* to an ARRAY. The `withDirectives` tuple `[{ handler, options }, null,
|
|
15
|
+
* ['once']]` is not a valid binding VALUE: the directive reads `.handler` /
|
|
16
|
+
* `.options` off it, gets `undefined`, never calls the handler and never
|
|
17
|
+
* reads the `once`. The correct form is `v-intersect.once="{ handler,
|
|
18
|
+
* options }"`. Gated on `import.meta.env.DEV`, like `warnUnsupportedProp`.
|
|
19
|
+
********************************************************/
|
|
20
|
+
export declare function warnIntersectTupleBinding(el: HTMLElement, value: unknown): void;
|
|
@@ -4,3 +4,15 @@ export function unmountIntersect(el, binding) {
|
|
|
4
4
|
observe.observer.unobserve(el);
|
|
5
5
|
delete el._observe[binding.instance.$.uid];
|
|
6
6
|
}
|
|
7
|
+
const _warnedIntersectTupleTags = /* @__PURE__ */ new Set();
|
|
8
|
+
export function warnIntersectTupleBinding(el, value) {
|
|
9
|
+
if (!Array.isArray(value)) return;
|
|
10
|
+
if (typeof console === "undefined") return;
|
|
11
|
+
if (!import.meta.env?.DEV) return;
|
|
12
|
+
const tag = el.tagName.toLowerCase();
|
|
13
|
+
if (_warnedIntersectTupleTags.has(tag)) return;
|
|
14
|
+
_warnedIntersectTupleTags.add(tag);
|
|
15
|
+
console.warn(
|
|
16
|
+
`[origam] v-intersect on <${tag}> is bound to an array (the withDirectives tuple): the handler will never be called. Bind { handler, options } and use the modifier, e.g. v-intersect.once="{ handler, options }".`
|
|
17
|
+
);
|
|
18
|
+
}
|