origam 2.37.1 → 2.37.2

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
@@ -18,6 +18,14 @@ This project follows [Semantic Versioning](https://semver.org).
18
18
 
19
19
  ## [Unreleased]
20
20
 
21
+ ## [2.37.2] - 2026-10-19
22
+
23
+ ### Fixed — `OrigamImg` sous SSR : écart d'hydratation `aspect-ratio`, et image montée côté client jamais chargée (#1192)
24
+
25
+ - **É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é.
26
+ - **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é.
27
+ - La même forme de liaison subsiste dans d'autres composants : suivi en #1205.
28
+
21
29
  ## [2.37.1] - 2026-10-19
22
30
 
23
31
  ### 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
- }, null, ["once"]]);
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
  });
@@ -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
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "origam",
3
- "version": "2.37.1",
3
+ "version": "2.37.2",
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": [