@alfakit/vue 0.1.0 → 0.1.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 ADDED
@@ -0,0 +1,9 @@
1
+ # @alfakit/vue
2
+
3
+ ## 0.1.1
4
+
5
+ ### Patch Changes
6
+
7
+ - Vue: icons and lazily loaded components no longer cause hydration mismatches when another app on the page already loaded them. Package READMEs show the Alfa Six logo on npm.
8
+ - Updated dependencies
9
+ - @alfakit/core@0.1.1
@@ -9,8 +9,8 @@ type __VLS_Props = {
9
9
  class?: HTMLAttributes['class'];
10
10
  };
11
11
  declare const _default: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {
12
- class: string | false | Record<string, any> | import("vue").ClassValue[] | null;
13
12
  size: "xs" | "sm" | "lg" | "icon" | "default" | "icon-sm" | "icon-xs" | null;
13
+ class: string | false | Record<string, any> | import("vue").ClassValue[] | null;
14
14
  variant: "secondary" | "link" | "default" | "destructive" | "outline" | "ghost" | "plain" | null;
15
15
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
16
16
  export default _default;
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "package": "@alfakit/vue",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "adapter": "vue",
5
- "generatedAt": "2026-10-19T06:11:33.237Z",
5
+ "generatedAt": "2026-10-19T06:40:49.122Z",
6
6
  "components": {
7
7
  "TextInput": {
8
8
  "file": "src/components/fields/TextInputField.vue",
@@ -1 +1 @@
1
- {"version":3,"file":"Icon.js","names":["_sfc_main","Icon_vue_vue_type_script_setup_true_lang_default","Icon_default"],"sources":["../../src/lib/Icon.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { iconAlias, iconAliasOverride, iconSource, isIconAlias } from '@alfakit/core'\nimport { computed, onMounted, type Component, type HTMLAttributes } from 'vue'\nimport { useOptionalRegistry } from '../context'\nimport { iconMap, markIconsMounted, resolveIcon } from './icons'\nimport { cn } from './utils'\n\n/**\n * Renders an icon resolved by the server (`Icons::resolve()`): a Lucide name (`users`,\n * `shopping-cart`), an image URL or sanitised `<svg>` markup. `alfakit::` aliases use the\n * app's replacement (`Icons::register()`) or `fallback`. Unknown Lucide names render the\n * `fallback` icon (if any) or nothing. `fallback` may also be a statically imported glyph\n * (`:fallback=\"SearchIcon\"`): renderer chrome uses it so an unreplaced alias draws at once,\n * without loading anything.\n */\nconst props = defineProps<{\n name?: string | null\n fallback?: string | Component | null\n class?: HTMLAttributes['class']\n}>()\n\n// `createAlfakit({ components: { 'panel:icon': MyIcon } })` renders every named icon with\n// another icon set; it receives `name`, `fallback` and `class`.\nconst custom = useOptionalRegistry()?.resolve('panel:icon')\n// Named icons render once the page has hydrated (the server render shows none).\nonMounted(markIconsMounted)\n\nconst fallbackName = computed(() =>\n typeof props.fallback === 'string' ? props.fallback : undefined,\n)\nconst glyph = computed(() =>\n props.fallback && typeof props.fallback !== 'string' ? props.fallback : null,\n)\n\n// Chrome icons (`<Icon name=\"alfakit::…\" :fallback=\"SearchIcon\" />`) draw their bundled glyph\n// synchronously unless the app replaced the alias with `Icons::register()`.\nconst useGlyph = computed(\n () =>\n glyph.value !== null &&\n !!props.name &&\n isIconAlias(props.name) &&\n iconAliasOverride(props.name) === undefined,\n)\n\nconst source = computed(() =>\n props.name && !useGlyph.value ? iconSource(iconAlias(props.name, fallbackName.value)) : null,\n)\n\nconst component = computed(() => {\n if (source.value?.type !== 'name' || custom) return null\n\n const icons = iconMap()\n\n // Nothing until the icon map has loaded (and during the server render and hydration).\n if (!icons) return null\n\n return (\n resolveIcon(source.value.name, icons) ??\n glyph.value ??\n (fallbackName.value ? resolveIcon(fallbackName.value, icons) : null)\n )\n})\n</script>\n\n<template>\n <component :is=\"glyph\" v-if=\"useGlyph && glyph\" :class=\"cn('size-4 shrink-0', props.class)\" />\n <!-- Sanitised on the server (SvgSanitizer): drawing elements only. -->\n <!-- eslint-disable vue/no-v-html -->\n <span\n v-else-if=\"source?.type === 'svg'\"\n aria-hidden=\"true\"\n data-slot=\"icon\"\n :class=\"cn('inline-flex size-4 shrink-0 [&>svg]:size-full', props.class)\"\n v-html=\"source.svg\"\n />\n <!-- eslint-enable vue/no-v-html -->\n <img\n v-else-if=\"source?.type === 'url'\"\n :src=\"source.url\"\n alt=\"\"\n aria-hidden=\"true\"\n data-slot=\"icon\"\n :class=\"cn('size-4 shrink-0 object-contain', props.class)\"\n />\n <component\n :is=\"custom\"\n v-else-if=\"custom && source?.type === 'name'\"\n :name=\"source.name\"\n :fallback=\"fallbackName\"\n :class=\"cn('size-4 shrink-0', props.class)\"\n />\n <component :is=\"component\" v-else-if=\"component\" :class=\"cn('size-4 shrink-0', props.class)\" />\n</template>\n"],"mappings":""}
1
+ {"version":3,"file":"Icon.js","names":["_sfc_main","Icon_vue_vue_type_script_setup_true_lang_default","Icon_default"],"sources":["../../src/lib/Icon.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { iconAlias, iconAliasOverride, iconSource, isIconAlias } from '@alfakit/core'\nimport { computed, type Component, type HTMLAttributes } from 'vue'\nimport { useOptionalRegistry } from '../context'\nimport { useAppMounted } from './hydration'\nimport { iconMap, resolveIcon } from './icons'\nimport { cn } from './utils'\n\n/**\n * Renders an icon resolved by the server (`Icons::resolve()`): a Lucide name (`users`,\n * `shopping-cart`), an image URL or sanitised `<svg>` markup. `alfakit::` aliases use the\n * app's replacement (`Icons::register()`) or `fallback`. Unknown Lucide names render the\n * `fallback` icon (if any) or nothing. `fallback` may also be a statically imported glyph\n * (`:fallback=\"SearchIcon\"`): renderer chrome uses it so an unreplaced alias draws at once,\n * without loading anything.\n */\nconst props = defineProps<{\n name?: string | null\n fallback?: string | Component | null\n class?: HTMLAttributes['class']\n}>()\n\n// `createAlfakit({ components: { 'panel:icon': MyIcon } })` renders every named icon with\n// another icon set; it receives `name`, `fallback` and `class`.\nconst custom = useOptionalRegistry()?.resolve('panel:icon')\n// Named icons render once this app has hydrated (the server render shows none).\nconst mounted = useAppMounted()\n\nconst fallbackName = computed(() =>\n typeof props.fallback === 'string' ? props.fallback : undefined,\n)\nconst glyph = computed(() =>\n props.fallback && typeof props.fallback !== 'string' ? props.fallback : null,\n)\n\n// Chrome icons (`<Icon name=\"alfakit::…\" :fallback=\"SearchIcon\" />`) draw their bundled glyph\n// synchronously unless the app replaced the alias with `Icons::register()`.\nconst useGlyph = computed(\n () =>\n glyph.value !== null &&\n !!props.name &&\n isIconAlias(props.name) &&\n iconAliasOverride(props.name) === undefined,\n)\n\nconst source = computed(() =>\n props.name && !useGlyph.value ? iconSource(iconAlias(props.name, fallbackName.value)) : null,\n)\n\nconst component = computed(() => {\n if (source.value?.type !== 'name' || custom) return null\n\n const icons = iconMap(mounted.value)\n\n // Nothing until the icon map has loaded (and during the server render and hydration).\n if (!icons) return null\n\n return (\n resolveIcon(source.value.name, icons) ??\n glyph.value ??\n (fallbackName.value ? resolveIcon(fallbackName.value, icons) : null)\n )\n})\n</script>\n\n<template>\n <component :is=\"glyph\" v-if=\"useGlyph && glyph\" :class=\"cn('size-4 shrink-0', props.class)\" />\n <!-- Sanitised on the server (SvgSanitizer): drawing elements only. -->\n <!-- eslint-disable vue/no-v-html -->\n <span\n v-else-if=\"source?.type === 'svg'\"\n aria-hidden=\"true\"\n data-slot=\"icon\"\n :class=\"cn('inline-flex size-4 shrink-0 [&>svg]:size-full', props.class)\"\n v-html=\"source.svg\"\n />\n <!-- eslint-enable vue/no-v-html -->\n <img\n v-else-if=\"source?.type === 'url'\"\n :src=\"source.url\"\n alt=\"\"\n aria-hidden=\"true\"\n data-slot=\"icon\"\n :class=\"cn('size-4 shrink-0 object-contain', props.class)\"\n />\n <component\n :is=\"custom\"\n v-else-if=\"custom && source?.type === 'name'\"\n :name=\"source.name\"\n :fallback=\"fallbackName\"\n :class=\"cn('size-4 shrink-0', props.class)\"\n />\n <component :is=\"component\" v-else-if=\"component\" :class=\"cn('size-4 shrink-0', props.class)\" />\n</template>\n"],"mappings":""}
@@ -1 +1 @@
1
- {"version":3,"file":"Icon.vue.d.ts","sourceRoot":"","sources":["../../src/lib/Icon.vue"],"names":[],"mappings":"AAgGA,OAAO,EAAuB,KAAK,SAAS,EAAE,KAAK,cAAc,EAAE,MAAM,KAAK,CAAA;AAK9E;;;;;;;GAOG;AACH,KAAK,WAAW,GAAG;IACjB,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACpB,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CAAA;IACpC,KAAK,CAAC,EAAE,cAAc,CAAC,OAAO,CAAC,CAAA;CAChC,CAAC;;AAuIF,wBAMG"}
1
+ {"version":3,"file":"Icon.vue.d.ts","sourceRoot":"","sources":["../../src/lib/Icon.vue"],"names":[],"mappings":"AAiGA,OAAO,EAAY,KAAK,SAAS,EAAE,KAAK,cAAc,EAAE,MAAM,KAAK,CAAA;AAMnE;;;;;;;GAOG;AACH,KAAK,WAAW,GAAG;IACjB,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACpB,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CAAA;IACpC,KAAK,CAAC,EAAE,cAAc,CAAC,OAAO,CAAC,CAAA;CAChC,CAAC;;AAuIF,wBAMG"}
@@ -1,10 +1,11 @@
1
1
  import { useOptionalRegistry as e } from "../context.js";
2
2
  import { cn as t } from "./utils.js";
3
- import { iconMap as n, markIconsMounted as r, resolveIcon as i } from "./icons.js";
3
+ import { useAppMounted as n } from "./hydration.js";
4
+ import { iconMap as r, resolveIcon as i } from "./icons.js";
4
5
  import { iconAlias as a, iconAliasOverride as o, iconSource as s, isIconAlias as c } from "@alfakit/core";
5
- import { computed as l, createBlock as u, createCommentVNode as d, createElementBlock as f, defineComponent as p, normalizeClass as m, onMounted as h, openBlock as g, resolveDynamicComponent as _, unref as v } from "vue";
6
+ import { computed as l, createBlock as u, createCommentVNode as d, createElementBlock as f, defineComponent as p, normalizeClass as m, openBlock as h, resolveDynamicComponent as g, unref as _ } from "vue";
6
7
  //#region src/lib/Icon.vue?vue&type=script&setup=true&lang.ts
7
- var y = ["innerHTML"], b = ["src"], x = /*@__PURE__*/ p({
8
+ var v = ["innerHTML"], y = ["src"], b = /*@__PURE__*/ p({
8
9
  __name: "Icon",
9
10
  props: {
10
11
  name: {},
@@ -18,45 +19,43 @@ var y = ["innerHTML"], b = ["src"], x = /*@__PURE__*/ p({
18
19
  ] }
19
20
  },
20
21
  setup(p) {
21
- let x = p, S = e()?.resolve("panel:icon");
22
- h(r);
23
- let C = l(() => typeof x.fallback == "string" ? x.fallback : void 0), w = l(() => x.fallback && typeof x.fallback != "string" ? x.fallback : null), T = l(() => w.value !== null && !!x.name && c(x.name) && o(x.name) === void 0), E = l(() => x.name && !T.value ? s(a(x.name, C.value)) : null), D = l(() => {
24
- if (E.value?.type !== "name" || S) return null;
25
- let e = n();
22
+ let b = p, x = e()?.resolve("panel:icon"), S = n(), C = l(() => typeof b.fallback == "string" ? b.fallback : void 0), w = l(() => b.fallback && typeof b.fallback != "string" ? b.fallback : null), T = l(() => w.value !== null && !!b.name && c(b.name) && o(b.name) === void 0), E = l(() => b.name && !T.value ? s(a(b.name, C.value)) : null), D = l(() => {
23
+ if (E.value?.type !== "name" || x) return null;
24
+ let e = r(S.value);
26
25
  return e ? i(E.value.name, e) ?? w.value ?? (C.value ? i(C.value, e) : null) : null;
27
26
  });
28
- return (e, n) => T.value && w.value ? (g(), u(_(w.value), {
27
+ return (e, n) => T.value && w.value ? (h(), u(g(w.value), {
29
28
  key: 0,
30
- class: m(v(t)("size-4 shrink-0", x.class))
31
- }, null, 8, ["class"])) : E.value?.type === "svg" ? (g(), f("span", {
29
+ class: m(_(t)("size-4 shrink-0", b.class))
30
+ }, null, 8, ["class"])) : E.value?.type === "svg" ? (h(), f("span", {
32
31
  key: 1,
33
32
  "aria-hidden": "true",
34
33
  "data-slot": "icon",
35
- class: m(v(t)("inline-flex size-4 shrink-0 [&>svg]:size-full", x.class)),
34
+ class: m(_(t)("inline-flex size-4 shrink-0 [&>svg]:size-full", b.class)),
36
35
  innerHTML: E.value.svg
37
- }, null, 10, y)) : E.value?.type === "url" ? (g(), f("img", {
36
+ }, null, 10, v)) : E.value?.type === "url" ? (h(), f("img", {
38
37
  key: 2,
39
38
  src: E.value.url,
40
39
  alt: "",
41
40
  "aria-hidden": "true",
42
41
  "data-slot": "icon",
43
- class: m(v(t)("size-4 shrink-0 object-contain", x.class))
44
- }, null, 10, b)) : v(S) && E.value?.type === "name" ? (g(), u(_(v(S)), {
42
+ class: m(_(t)("size-4 shrink-0 object-contain", b.class))
43
+ }, null, 10, y)) : _(x) && E.value?.type === "name" ? (h(), u(g(_(x)), {
45
44
  key: 3,
46
45
  name: E.value.name,
47
46
  fallback: C.value,
48
- class: m(v(t)("size-4 shrink-0", x.class))
47
+ class: m(_(t)("size-4 shrink-0", b.class))
49
48
  }, null, 8, [
50
49
  "name",
51
50
  "fallback",
52
51
  "class"
53
- ])) : D.value ? (g(), u(_(D.value), {
52
+ ])) : D.value ? (h(), u(g(D.value), {
54
53
  key: 4,
55
- class: m(v(t)("size-4 shrink-0", x.class))
54
+ class: m(_(t)("size-4 shrink-0", b.class))
56
55
  }, null, 8, ["class"])) : d("", !0);
57
56
  }
58
57
  });
59
58
  //#endregion
60
- export { x as default };
59
+ export { b as default };
61
60
 
62
61
  //# sourceMappingURL=Icon.vue_vue_type_script_setup_true_lang.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"Icon.vue_vue_type_script_setup_true_lang.js","names":["_openBlock","_createBlock","_resolveDynamicComponent","_normalizeClass","_unref","_createElementBlock","_createCommentVNode","_hoisted_1","_hoisted_2","Icon_vue_vue_type_script_setup_true_lang_default","props","__props","custom","useOptionalRegistry","markIconsMounted","fallbackName","computed","glyph","useGlyph","isIconAlias","iconAliasOverride","source","iconSource","iconAlias","component","icons","iconMap","resolveIcon","openBlock","createBlock","resolveDynamicComponent","normalizeClass","unref","cn","createElementBlock","createCommentVNode"],"sources":["../../src/lib/Icon.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { iconAlias, iconAliasOverride, iconSource, isIconAlias } from '@alfakit/core'\nimport { computed, onMounted, type Component, type HTMLAttributes } from 'vue'\nimport { useOptionalRegistry } from '../context'\nimport { iconMap, markIconsMounted, resolveIcon } from './icons'\nimport { cn } from './utils'\n\n/**\n * Renders an icon resolved by the server (`Icons::resolve()`): a Lucide name (`users`,\n * `shopping-cart`), an image URL or sanitised `<svg>` markup. `alfakit::` aliases use the\n * app's replacement (`Icons::register()`) or `fallback`. Unknown Lucide names render the\n * `fallback` icon (if any) or nothing. `fallback` may also be a statically imported glyph\n * (`:fallback=\"SearchIcon\"`): renderer chrome uses it so an unreplaced alias draws at once,\n * without loading anything.\n */\nconst props = defineProps<{\n name?: string | null\n fallback?: string | Component | null\n class?: HTMLAttributes['class']\n}>()\n\n// `createAlfakit({ components: { 'panel:icon': MyIcon } })` renders every named icon with\n// another icon set; it receives `name`, `fallback` and `class`.\nconst custom = useOptionalRegistry()?.resolve('panel:icon')\n// Named icons render once the page has hydrated (the server render shows none).\nonMounted(markIconsMounted)\n\nconst fallbackName = computed(() =>\n typeof props.fallback === 'string' ? props.fallback : undefined,\n)\nconst glyph = computed(() =>\n props.fallback && typeof props.fallback !== 'string' ? props.fallback : null,\n)\n\n// Chrome icons (`<Icon name=\"alfakit::…\" :fallback=\"SearchIcon\" />`) draw their bundled glyph\n// synchronously unless the app replaced the alias with `Icons::register()`.\nconst useGlyph = computed(\n () =>\n glyph.value !== null &&\n !!props.name &&\n isIconAlias(props.name) &&\n iconAliasOverride(props.name) === undefined,\n)\n\nconst source = computed(() =>\n props.name && !useGlyph.value ? iconSource(iconAlias(props.name, fallbackName.value)) : null,\n)\n\nconst component = computed(() => {\n if (source.value?.type !== 'name' || custom) return null\n\n const icons = iconMap()\n\n // Nothing until the icon map has loaded (and during the server render and hydration).\n if (!icons) return null\n\n return (\n resolveIcon(source.value.name, icons) ??\n glyph.value ??\n (fallbackName.value ? resolveIcon(fallbackName.value, icons) : null)\n )\n})\n</script>\n\n<template>\n <component :is=\"glyph\" v-if=\"useGlyph && glyph\" :class=\"cn('size-4 shrink-0', props.class)\" />\n <!-- Sanitised on the server (SvgSanitizer): drawing elements only. -->\n <!-- eslint-disable vue/no-v-html -->\n <span\n v-else-if=\"source?.type === 'svg'\"\n aria-hidden=\"true\"\n data-slot=\"icon\"\n :class=\"cn('inline-flex size-4 shrink-0 [&>svg]:size-full', props.class)\"\n v-html=\"source.svg\"\n />\n <!-- eslint-enable vue/no-v-html -->\n <img\n v-else-if=\"source?.type === 'url'\"\n :src=\"source.url\"\n alt=\"\"\n aria-hidden=\"true\"\n data-slot=\"icon\"\n :class=\"cn('size-4 shrink-0 object-contain', props.class)\"\n />\n <component\n :is=\"custom\"\n v-else-if=\"custom && source?.type === 'name'\"\n :name=\"source.name\"\n :fallback=\"fallbackName\"\n :class=\"cn('size-4 shrink-0', props.class)\"\n />\n <component :is=\"component\" v-else-if=\"component\" :class=\"cn('size-4 shrink-0', props.class)\" />\n</template>\n"],"mappings":";;;;;;;;;;;;;;;;;;;;EAeA,IAAMU,IAAQC,GAQRC,IAASC,EAAoB,CAAC,EAAE,QAAQ,YAAY;EAE1D,EAAUC,CAAgB;EAE1B,IAAMC,IAAeC,QACnB,OAAON,EAAM,YAAa,WAAWA,EAAM,WAAW,KAAA,CACxD,GACMO,IAAQD,QACZN,EAAM,YAAY,OAAOA,EAAM,YAAa,WAAWA,EAAM,WAAW,IAC1E,GAIMQ,IAAWF,QAEbC,EAAM,UAAU,QAChB,CAAC,CAACP,EAAM,QACRS,EAAYT,EAAM,IAAI,KACtBU,EAAkBV,EAAM,IAAI,MAAM,KAAA,CACtC,GAEMW,IAASL,QACbN,EAAM,QAAQ,CAACQ,EAAS,QAAQI,EAAWC,EAAUb,EAAM,MAAMK,EAAa,KAAK,CAAC,IAAI,IAC1F,GAEMS,IAAYR,QAAe;GAC/B,IAAIK,EAAO,OAAO,SAAS,UAAUT,GAAQ,OAAO;GAEpD,IAAMa,IAAQC,EAAQ;GAKtB,OAFKD,IAGHE,EAAYN,EAAO,MAAM,MAAMI,CAAK,KACpCR,EAAM,UACLF,EAAa,QAAQY,EAAYZ,EAAa,OAAOU,CAAK,IAAI,QAL9C;EAOrB,CAAC;mBAI8BP,EAAA,SAAYD,EAAA,SAAzCjB,EAAA,GAAAC,EAA8FC,EAA9Ee,EAAA,KAAK,GAAA;;GAA4B,OAAKd,EAAEC,EAAA6B,CAAA,CAAE,CAAA,mBAAoBvB,EAAM,KAAK,CAAA;EAI5E,GAAA,MAAA,GAAA,CAAA,OAAA,CAAA,KAAAW,EAAA,OAAQ,SAAI,SADzBrB,EAAA,GAAAK,EAME,QAAA;;GAJA,eAAY;GACZ,aAAU;GACT,OAAKF,EAAEC,EAAA6B,CAAA,CAAE,CAAA,iDAAkDvB,EAAM,KAAK,CAAA;GACvE,WAAQW,EAAA,MAAO;EAIJ,GAAA,MAAA,IAAAd,CAAA,KAAAc,EAAA,OAAQ,SAAI,SADzBrB,EAAA,GAAAK,EAOE,OAAA;;GALC,KAAKgB,EAAA,MAAO;GACb,KAAI;GACJ,eAAY;GACZ,aAAU;GACT,OAAKlB,EAAEC,EAAA6B,CAAA,CAAE,CAAA,kCAAmCvB,EAAM,KAAK,CAAA;EAI7CN,GAAAA,MAAAA,IAAAA,CAAAA,KAAAA,EAAAQ,CAAA,KAAUS,EAAA,OAAQ,SAAI,UAFnCrB,EAAA,GAAAC,EAMEC,EALKE,EAAAQ,CAAA,CAAM,GAAA;;GAEV,MAAMS,EAAA,MAAO;GACb,UAAUN,EAAA;GACV,OAAKZ,EAAEC,EAAA6B,CAAA,CAAE,CAAA,mBAAoBvB,EAAM,KAAK,CAAA;;;;;EAEL,CAAA,KAAAc,EAAA,SAAtCxB,EAAA,GAAAC,EAA+FC,EAA/EsB,EAAA,KAAS,GAAA;;GAAyB,OAAKrB,EAAEC,EAAA6B,CAAA,CAAE,CAAA,mBAAoBvB,EAAM,KAAK,CAAA"}
1
+ {"version":3,"file":"Icon.vue_vue_type_script_setup_true_lang.js","names":["_openBlock","_createBlock","_resolveDynamicComponent","_normalizeClass","_unref","_createElementBlock","_createCommentVNode","_hoisted_1","_hoisted_2","Icon_vue_vue_type_script_setup_true_lang_default","props","__props","custom","useOptionalRegistry","mounted","useAppMounted","fallbackName","computed","glyph","useGlyph","isIconAlias","iconAliasOverride","source","iconSource","iconAlias","component","icons","iconMap","resolveIcon","openBlock","createBlock","resolveDynamicComponent","normalizeClass","unref","cn","createElementBlock","createCommentVNode"],"sources":["../../src/lib/Icon.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { iconAlias, iconAliasOverride, iconSource, isIconAlias } from '@alfakit/core'\nimport { computed, type Component, type HTMLAttributes } from 'vue'\nimport { useOptionalRegistry } from '../context'\nimport { useAppMounted } from './hydration'\nimport { iconMap, resolveIcon } from './icons'\nimport { cn } from './utils'\n\n/**\n * Renders an icon resolved by the server (`Icons::resolve()`): a Lucide name (`users`,\n * `shopping-cart`), an image URL or sanitised `<svg>` markup. `alfakit::` aliases use the\n * app's replacement (`Icons::register()`) or `fallback`. Unknown Lucide names render the\n * `fallback` icon (if any) or nothing. `fallback` may also be a statically imported glyph\n * (`:fallback=\"SearchIcon\"`): renderer chrome uses it so an unreplaced alias draws at once,\n * without loading anything.\n */\nconst props = defineProps<{\n name?: string | null\n fallback?: string | Component | null\n class?: HTMLAttributes['class']\n}>()\n\n// `createAlfakit({ components: { 'panel:icon': MyIcon } })` renders every named icon with\n// another icon set; it receives `name`, `fallback` and `class`.\nconst custom = useOptionalRegistry()?.resolve('panel:icon')\n// Named icons render once this app has hydrated (the server render shows none).\nconst mounted = useAppMounted()\n\nconst fallbackName = computed(() =>\n typeof props.fallback === 'string' ? props.fallback : undefined,\n)\nconst glyph = computed(() =>\n props.fallback && typeof props.fallback !== 'string' ? props.fallback : null,\n)\n\n// Chrome icons (`<Icon name=\"alfakit::…\" :fallback=\"SearchIcon\" />`) draw their bundled glyph\n// synchronously unless the app replaced the alias with `Icons::register()`.\nconst useGlyph = computed(\n () =>\n glyph.value !== null &&\n !!props.name &&\n isIconAlias(props.name) &&\n iconAliasOverride(props.name) === undefined,\n)\n\nconst source = computed(() =>\n props.name && !useGlyph.value ? iconSource(iconAlias(props.name, fallbackName.value)) : null,\n)\n\nconst component = computed(() => {\n if (source.value?.type !== 'name' || custom) return null\n\n const icons = iconMap(mounted.value)\n\n // Nothing until the icon map has loaded (and during the server render and hydration).\n if (!icons) return null\n\n return (\n resolveIcon(source.value.name, icons) ??\n glyph.value ??\n (fallbackName.value ? resolveIcon(fallbackName.value, icons) : null)\n )\n})\n</script>\n\n<template>\n <component :is=\"glyph\" v-if=\"useGlyph && glyph\" :class=\"cn('size-4 shrink-0', props.class)\" />\n <!-- Sanitised on the server (SvgSanitizer): drawing elements only. -->\n <!-- eslint-disable vue/no-v-html -->\n <span\n v-else-if=\"source?.type === 'svg'\"\n aria-hidden=\"true\"\n data-slot=\"icon\"\n :class=\"cn('inline-flex size-4 shrink-0 [&>svg]:size-full', props.class)\"\n v-html=\"source.svg\"\n />\n <!-- eslint-enable vue/no-v-html -->\n <img\n v-else-if=\"source?.type === 'url'\"\n :src=\"source.url\"\n alt=\"\"\n aria-hidden=\"true\"\n data-slot=\"icon\"\n :class=\"cn('size-4 shrink-0 object-contain', props.class)\"\n />\n <component\n :is=\"custom\"\n v-else-if=\"custom && source?.type === 'name'\"\n :name=\"source.name\"\n :fallback=\"fallbackName\"\n :class=\"cn('size-4 shrink-0', props.class)\"\n />\n <component :is=\"component\" v-else-if=\"component\" :class=\"cn('size-4 shrink-0', props.class)\" />\n</template>\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;EAgBA,IAAMU,IAAQC,GAQRC,IAASC,EAAoB,CAAC,EAAE,QAAQ,YAAY,GAEpDC,IAAUC,EAAc,GAExBC,IAAeC,QACnB,OAAOP,EAAM,YAAa,WAAWA,EAAM,WAAW,KAAA,CACxD,GACMQ,IAAQD,QACZP,EAAM,YAAY,OAAOA,EAAM,YAAa,WAAWA,EAAM,WAAW,IAC1E,GAIMS,IAAWF,QAEbC,EAAM,UAAU,QAChB,CAAC,CAACR,EAAM,QACRU,EAAYV,EAAM,IAAI,KACtBW,EAAkBX,EAAM,IAAI,MAAM,KAAA,CACtC,GAEMY,IAASL,QACbP,EAAM,QAAQ,CAACS,EAAS,QAAQI,EAAWC,EAAUd,EAAM,MAAMM,EAAa,KAAK,CAAC,IAAI,IAC1F,GAEMS,IAAYR,QAAe;GAC/B,IAAIK,EAAO,OAAO,SAAS,UAAUV,GAAQ,OAAO;GAEpD,IAAMc,IAAQC,EAAQb,EAAQ,KAAK;GAKnC,OAFKY,IAGHE,EAAYN,EAAO,MAAM,MAAMI,CAAK,KACpCR,EAAM,UACLF,EAAa,QAAQY,EAAYZ,EAAa,OAAOU,CAAK,IAAI,QAL9C;EAOrB,CAAC;mBAI8BP,EAAA,SAAYD,EAAA,SAAzClB,EAAA,GAAAC,EAA8FC,EAA9EgB,EAAA,KAAK,GAAA;;GAA4B,OAAKf,EAAEC,EAAA8B,CAAA,CAAE,CAAA,mBAAoBxB,EAAM,KAAK,CAAA;EAI5E,GAAA,MAAA,GAAA,CAAA,OAAA,CAAA,KAAAY,EAAA,OAAQ,SAAI,SADzBtB,EAAA,GAAAK,EAME,QAAA;;GAJA,eAAY;GACZ,aAAU;GACT,OAAKF,EAAEC,EAAA8B,CAAA,CAAE,CAAA,iDAAkDxB,EAAM,KAAK,CAAA;GACvE,WAAQY,EAAA,MAAO;EAIJ,GAAA,MAAA,IAAAf,CAAA,KAAAe,EAAA,OAAQ,SAAI,SADzBtB,EAAA,GAAAK,EAOE,OAAA;;GALC,KAAKiB,EAAA,MAAO;GACb,KAAI;GACJ,eAAY;GACZ,aAAU;GACT,OAAKnB,EAAEC,EAAA8B,CAAA,CAAE,CAAA,kCAAmCxB,EAAM,KAAK,CAAA;EAI7CN,GAAAA,MAAAA,IAAAA,CAAAA,KAAAA,EAAAQ,CAAA,KAAUU,EAAA,OAAQ,SAAI,UAFnCtB,EAAA,GAAAC,EAMEC,EALKE,EAAAQ,CAAA,CAAM,GAAA;;GAEV,MAAMU,EAAA,MAAO;GACb,UAAUN,EAAA;GACV,OAAKb,EAAEC,EAAA8B,CAAA,CAAE,CAAA,mBAAoBxB,EAAM,KAAK,CAAA;;;;;EAEL,CAAA,KAAAe,EAAA,SAAtCzB,EAAA,GAAAC,EAA+FC,EAA/EuB,EAAA,KAAS,GAAA;;GAAyB,OAAKtB,EAAEC,EAAA8B,CAAA,CAAE,CAAA,mBAAoBxB,EAAM,KAAK,CAAA"}
@@ -0,0 +1,9 @@
1
+ import { type Ref } from 'vue';
2
+ /**
3
+ * Call in a component's setup: `false` until the component's app has mounted (so during
4
+ * the server render and hydration), then `true` for every component in that app, so
5
+ * content rendered later draws at once (reactive). Lazily loaded parts (named icons, heavy
6
+ * fields) render their server markup until then, whatever has loaded already.
7
+ */
8
+ export declare function useAppMounted(): Ref<boolean>;
9
+ //# sourceMappingURL=hydration.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hydration.d.ts","sourceRoot":"","sources":["../../src/lib/hydration.ts"],"names":[],"mappings":"AAAA,OAAO,EAAuD,KAAK,GAAG,EAAE,MAAM,KAAK,CAAA;AASnF;;;;;GAKG;AACH,wBAAgB,aAAa,IAAI,GAAG,CAAC,OAAO,CAAC,CAmB5C"}
@@ -0,0 +1,17 @@
1
+ import { getCurrentInstance as e, onMounted as t, ref as n } from "vue";
2
+ //#region src/lib/hydration.ts
3
+ var r = /* @__PURE__ */ new WeakMap();
4
+ function i() {
5
+ let i = e()?.appContext;
6
+ if (!i) return n(!0);
7
+ let a = r.get(i);
8
+ a || (a = n(!1), r.set(i, a));
9
+ let o = a;
10
+ return t(() => {
11
+ o.value = !0;
12
+ }), o;
13
+ }
14
+ //#endregion
15
+ export { i as useAppMounted };
16
+
17
+ //# sourceMappingURL=hydration.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hydration.js","names":["mountedApps","useAppMounted","context","getCurrentInstance","ref","mounted","flag","onMounted"],"sources":["../../src/lib/hydration.ts"],"sourcesContent":["import { getCurrentInstance, onMounted, ref, type AppContext, type Ref } from 'vue'\n\n/**\n * Whether each app has mounted (hydrated). Kept per app, not per module: loaded icon chunks\n * and lazy components outlive an app, so a second app hydrating in the same page would\n * otherwise draw content its server markup does not contain.\n */\nconst mountedApps = new WeakMap<AppContext, Ref<boolean>>()\n\n/**\n * Call in a component's setup: `false` until the component's app has mounted (so during\n * the server render and hydration), then `true` for every component in that app, so\n * content rendered later draws at once (reactive). Lazily loaded parts (named icons, heavy\n * fields) render their server markup until then, whatever has loaded already.\n */\nexport function useAppMounted(): Ref<boolean> {\n const context = getCurrentInstance()?.appContext\n\n // Outside a component there is no hydration to wait for.\n if (!context) return ref(true)\n\n let mounted = mountedApps.get(context)\n\n if (!mounted) {\n mounted = ref(false)\n mountedApps.set(context, mounted)\n }\n\n const flag = mounted\n onMounted(() => {\n flag.value = true\n })\n\n return flag\n}\n"],"mappings":";;AAOA,IAAMA,oBAAc,IAAI,QAAkC;AAQ1D,SAAgBC,IAA8B;CAC5C,IAAMC,IAAUC,EAAmB,CAAC,EAAE;CAGtC,IAAI,CAACD,GAAS,OAAOE,EAAI,EAAI;CAE7B,IAAIC,IAAUL,EAAY,IAAIE,CAAO;CAErC,AAAKG,MACH,IAAUD,EAAI,EAAK,GACnBJ,EAAY,IAAIE,GAASG,CAAO;CAGlC,IAAMC,IAAOD;CAKb,OAJAE,QAAgB;EACd,EAAK,QAAQ;CACf,CAAC,GAEMD;AACT"}
@@ -12,13 +12,11 @@ import type * as IconModules from './icon-modules';
12
12
  export type IconMap = typeof IconModules;
13
13
  /** Start loading the icon map ahead of the first named icon (browser only). */
14
14
  export declare function preloadIcons(): void;
15
- /** `<Icon>`s call this once mounted: from then on named icons render (after hydration). */
16
- export declare function markIconsMounted(): void;
17
15
  /**
18
- * The icon map once it has loaded in the browser and the page has hydrated, else `null`
19
- * (reactive). Starts loading it.
16
+ * The icon map once it has loaded in the browser and the `<Icon>`'s app has mounted
17
+ * (`useAppMounted()`), else `null` (reactive). Starts loading it.
20
18
  */
21
- export declare function iconMap(): IconMap | null;
19
+ export declare function iconMap(mounted: boolean): IconMap | null;
22
20
  /**
23
21
  * The icon component for `name`, or `null` for unknown names (and while the icon map
24
22
  * loads; the result is reactive).
@@ -1 +1 @@
1
- {"version":3,"file":"icons.d.ts","sourceRoot":"","sources":["../../src/lib/icons.ts"],"names":[],"mappings":"AAAA,OAAO,EAAyC,KAAK,SAAS,EAAE,MAAM,KAAK,CAAA;AAC3E,OAAO,KAAK,KAAK,WAAW,MAAM,gBAAgB,CAAA;AAElD;;;;;;;;GAQG;AACH,MAAM,MAAM,OAAO,GAAG,OAAO,WAAW,CAAA;AAsBxC,+EAA+E;AAC/E,wBAAgB,YAAY,IAAI,IAAI,CAEnC;AAED,2FAA2F;AAC3F,wBAAgB,gBAAgB,IAAI,IAAI,CAEvC;AAED;;;GAGG;AACH,wBAAgB,OAAO,IAAI,OAAO,GAAG,IAAI,CAIxC;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,OAAO,GAAG,IAAI,GAAG,SAAS,GAAG,IAAI,CAuBhF;AAED,OAAO,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAA;AACxC,OAAO,EAAE,OAAO,IAAI,IAAI,EAAE,MAAM,YAAY,CAAA"}
1
+ {"version":3,"file":"icons.d.ts","sourceRoot":"","sources":["../../src/lib/icons.ts"],"names":[],"mappings":"AAAA,OAAO,EAAoC,KAAK,SAAS,EAAE,MAAM,KAAK,CAAA;AACtE,OAAO,KAAK,KAAK,WAAW,MAAM,gBAAgB,CAAA;AAElD;;;;;;;;GAQG;AACH,MAAM,MAAM,OAAO,GAAG,OAAO,WAAW,CAAA;AAsBxC,+EAA+E;AAC/E,wBAAgB,YAAY,IAAI,IAAI,CAEnC;AAED;;;GAGG;AACH,wBAAgB,OAAO,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,GAAG,IAAI,CAIxD;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,OAAO,GAAG,IAAI,GAAG,SAAS,GAAG,IAAI,CAuBhF;AAED,OAAO,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAA;AACxC,OAAO,EAAE,OAAO,IAAI,IAAI,EAAE,MAAM,YAAY,CAAA"}
package/dist/lib/icons.js CHANGED
@@ -1,35 +1,32 @@
1
1
  import { hasIcon as e } from "./icon-catalog.js";
2
2
  import t from "./Icon.js";
3
- import { defineAsyncComponent as n, ref as r, shallowRef as i } from "vue";
3
+ import { defineAsyncComponent as n, shallowRef as r } from "vue";
4
4
  //#region src/lib/icons.ts
5
- var a = i(null), o = r(!1), s, c = /* @__PURE__ */ new Map();
6
- function l() {
7
- typeof window < "u" && (s ??= import("./icon-modules.js").then((e) => a.value = e, (e) => {
8
- throw s = void 0, e;
9
- }), s.catch(() => void 0));
5
+ var i = r(null), a, o = /* @__PURE__ */ new Map();
6
+ function s() {
7
+ typeof window < "u" && (a ??= import("./icon-modules.js").then((e) => i.value = e, (e) => {
8
+ throw a = void 0, e;
9
+ }), a.catch(() => void 0));
10
10
  }
11
- function u() {
12
- l();
11
+ function c() {
12
+ s();
13
13
  }
14
- function d() {
15
- o.value = !0;
14
+ function l(e) {
15
+ return i.value || s(), e ? i.value : null;
16
16
  }
17
- function f() {
18
- return a.value || l(), o.value ? a.value : null;
19
- }
20
- function p(e, t) {
21
- t === void 0 && l();
22
- let r = t === void 0 ? a.value : t;
17
+ function u(e, t) {
18
+ t === void 0 && s();
19
+ let r = t === void 0 ? i.value : t;
23
20
  if (!r) return null;
24
- let i = c.get(e);
25
- if (i !== void 0) return i;
26
- let o = r.iconModules[r.iconPath(e, r.iconModules)], s = o ? n({
27
- loader: () => o().then((e) => e.default),
21
+ let a = o.get(e);
22
+ if (a !== void 0) return a;
23
+ let c = r.iconModules[r.iconPath(e, r.iconModules)], l = c ? n({
24
+ loader: () => c().then((e) => e.default),
28
25
  suspensible: !1
29
26
  }) : null;
30
- return c.set(e, s), s;
27
+ return o.set(e, l), l;
31
28
  }
32
29
  //#endregion
33
- export { t as Icon, e as hasIcon, f as iconMap, d as markIconsMounted, u as preloadIcons, p as resolveIcon };
30
+ export { t as Icon, e as hasIcon, l as iconMap, c as preloadIcons, u as resolveIcon };
34
31
 
35
32
  //# sourceMappingURL=icons.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"icons.js","names":["Icon_default","loadedMap","shallowRef","mounted","ref","pending","cache","loadIconMap","module","error","preloadIcons","markIconsMounted","iconMap","resolveIcon","map","icons","cached","name","loader","component","defineAsyncComponent","hasIcon"],"sources":["../../src/lib/icons.ts"],"sourcesContent":["import { defineAsyncComponent, ref, shallowRef, type Component } from 'vue'\nimport type * as IconModules from './icon-modules'\n\n/**\n * Lazily loaded Lucide icons keyed by kebab-case name (`users`, `shopping-cart`). Each icon\n * becomes its own chunk, like `lucide-react/dynamic`, so the main bundle stays small.\n *\n * The name → chunk map (~2,000 entries, `./icon-modules`) is not part of any entry chunk\n * either: panels (`createAlfakit()`, starter kit pages) start loading it while they boot,\n * and elsewhere (a host's sidebar) it loads with the first icon. As in the other renderers,\n * named icons are drawn in the browser only: the server render and hydration show none.\n */\nexport type IconMap = typeof IconModules\n\nconst loadedMap = shallowRef<IconMap | null>(null)\nconst mounted = ref(false)\nlet pending: Promise<IconMap> | undefined\n\nconst cache = new Map<string, Component | null>()\n\nfunction loadIconMap(): void {\n if (typeof window === 'undefined') return\n\n pending ??= import('./icon-modules').then(\n (module) => (loadedMap.value = module),\n (error: unknown) => {\n pending = undefined\n\n throw error\n },\n )\n pending.catch(() => undefined)\n}\n\n/** Start loading the icon map ahead of the first named icon (browser only). */\nexport function preloadIcons(): void {\n loadIconMap()\n}\n\n/** `<Icon>`s call this once mounted: from then on named icons render (after hydration). */\nexport function markIconsMounted(): void {\n mounted.value = true\n}\n\n/**\n * The icon map once it has loaded in the browser and the page has hydrated, else `null`\n * (reactive). Starts loading it.\n */\nexport function iconMap(): IconMap | null {\n if (!loadedMap.value) loadIconMap()\n\n return mounted.value ? loadedMap.value : null\n}\n\n/**\n * The icon component for `name`, or `null` for unknown names (and while the icon map\n * loads; the result is reactive).\n */\nexport function resolveIcon(name: string, map?: IconMap | null): Component | null {\n if (map === undefined) loadIconMap()\n\n const icons = map === undefined ? loadedMap.value : map\n\n if (!icons) return null\n\n const cached = cache.get(name)\n\n if (cached !== undefined) {\n return cached\n }\n\n const loader = icons.iconModules[icons.iconPath(name, icons.iconModules)]\n const component = loader\n ? defineAsyncComponent({\n loader: () => loader().then((module) => module.default),\n suspensible: false,\n })\n : null\n cache.set(name, component)\n\n return component\n}\n\nexport { hasIcon } from './icon-catalog'\nexport { default as Icon } from './Icon.vue'\n"],"mappings":";;;;AAcA,IAAMC,IAAYC,EAA2B,IAAI,GAC3CC,IAAUC,EAAI,EAAK,GACrBC,GAEEC,oBAAQ,IAAI,IAA8B;AAEhD,SAASC,IAAoB;CAC3B,AAAI,OAAO,SAAW,QAEtB,MAAY,OAAO,oBAAiB,CAAC,MAClC,MAAY,EAAU,QAAQC,IAC9B,MAAmB;EAGlB,MAFA,IAAU,KAAA,GAEJC;CACR,CACF,GACAJ,EAAQ,YAAY,KAAA,CAAS;AAC/B;AAGA,SAAgBK,IAAqB;CACnC,EAAY;AACd;AAGA,SAAgBC,IAAyB;CACvC,EAAQ,QAAQ;AAClB;AAMA,SAAgBC,IAA0B;CAGxC,OAFKX,EAAU,SAAOM,EAAY,GAE3BJ,EAAQ,QAAQF,EAAU,QAAQ;AAC3C;AAMA,SAAgBY,EAAY,GAAc,GAAwC;CAChF,AAAIC,MAAQ,KAAA,KAAWP,EAAY;CAEnC,IAAMQ,IAAQD,MAAQ,KAAA,IAAYb,EAAU,QAAQa;CAEpD,IAAI,CAACC,GAAO,OAAO;CAEnB,IAAMC,IAASV,EAAM,IAAIW,CAAI;CAE7B,IAAID,MAAW,KAAA,GACb,OAAOA;CAGT,IAAME,IAASH,EAAM,YAAYA,EAAM,SAASE,GAAMF,EAAM,WAAW,IACjEI,IAAYD,IACdE,EAAqB;EACnB,cAAcF,EAAO,CAAC,CAAC,MAAM,MAAWV,EAAO,OAAO;EACtD,aAAa;CACf,CAAC,IACD;CAGJ,OAFAF,EAAM,IAAIW,GAAME,CAAS,GAElBA;AACT"}
1
+ {"version":3,"file":"icons.js","names":["Icon_default","loadedMap","shallowRef","pending","cache","loadIconMap","module","error","preloadIcons","iconMap","mounted","resolveIcon","map","icons","cached","name","loader","component","defineAsyncComponent","hasIcon"],"sources":["../../src/lib/icons.ts"],"sourcesContent":["import { defineAsyncComponent, shallowRef, type Component } from 'vue'\nimport type * as IconModules from './icon-modules'\n\n/**\n * Lazily loaded Lucide icons keyed by kebab-case name (`users`, `shopping-cart`). Each icon\n * becomes its own chunk, like `lucide-react/dynamic`, so the main bundle stays small.\n *\n * The name → chunk map (~2,000 entries, `./icon-modules`) is not part of any entry chunk\n * either: panels (`createAlfakit()`, starter kit pages) start loading it while they boot,\n * and elsewhere (a host's sidebar) it loads with the first icon. As in the other renderers,\n * named icons are drawn in the browser only: the server render and hydration show none.\n */\nexport type IconMap = typeof IconModules\n\nconst loadedMap = shallowRef<IconMap | null>(null)\n\nlet pending: Promise<IconMap> | undefined\n\nconst cache = new Map<string, Component | null>()\n\nfunction loadIconMap(): void {\n if (typeof window === 'undefined') return\n\n pending ??= import('./icon-modules').then(\n (module) => (loadedMap.value = module),\n (error: unknown) => {\n pending = undefined\n\n throw error\n },\n )\n pending.catch(() => undefined)\n}\n\n/** Start loading the icon map ahead of the first named icon (browser only). */\nexport function preloadIcons(): void {\n loadIconMap()\n}\n\n/**\n * The icon map once it has loaded in the browser and the `<Icon>`'s app has mounted\n * (`useAppMounted()`), else `null` (reactive). Starts loading it.\n */\nexport function iconMap(mounted: boolean): IconMap | null {\n if (!loadedMap.value) loadIconMap()\n\n return mounted ? loadedMap.value : null\n}\n\n/**\n * The icon component for `name`, or `null` for unknown names (and while the icon map\n * loads; the result is reactive).\n */\nexport function resolveIcon(name: string, map?: IconMap | null): Component | null {\n if (map === undefined) loadIconMap()\n\n const icons = map === undefined ? loadedMap.value : map\n\n if (!icons) return null\n\n const cached = cache.get(name)\n\n if (cached !== undefined) {\n return cached\n }\n\n const loader = icons.iconModules[icons.iconPath(name, icons.iconModules)]\n const component = loader\n ? defineAsyncComponent({\n loader: () => loader().then((module) => module.default),\n suspensible: false,\n })\n : null\n cache.set(name, component)\n\n return component\n}\n\nexport { hasIcon } from './icon-catalog'\nexport { default as Icon } from './Icon.vue'\n"],"mappings":";;;;AAcA,IAAMC,IAAYC,EAA2B,IAAI,GAE7CC,GAEEC,oBAAQ,IAAI,IAA8B;AAEhD,SAASC,IAAoB;CAC3B,AAAI,OAAO,SAAW,QAEtB,MAAY,OAAO,oBAAiB,CAAC,MAClC,MAAY,EAAU,QAAQC,IAC9B,MAAmB;EAGlB,MAFA,IAAU,KAAA,GAEJC;CACR,CACF,GACAJ,EAAQ,YAAY,KAAA,CAAS;AAC/B;AAGA,SAAgBK,IAAqB;CACnC,EAAY;AACd;AAMA,SAAgBC,EAAQ,GAAkC;CAGxD,OAFKR,EAAU,SAAOI,EAAY,GAE3BK,IAAUT,EAAU,QAAQ;AACrC;AAMA,SAAgBU,EAAY,GAAc,GAAwC;CAChF,AAAIC,MAAQ,KAAA,KAAWP,EAAY;CAEnC,IAAMQ,IAAQD,MAAQ,KAAA,IAAYX,EAAU,QAAQW;CAEpD,IAAI,CAACC,GAAO,OAAO;CAEnB,IAAMC,IAASV,EAAM,IAAIW,CAAI;CAE7B,IAAID,MAAW,KAAA,GACb,OAAOA;CAGT,IAAME,IAASH,EAAM,YAAYA,EAAM,SAASE,GAAMF,EAAM,WAAW,IACjEI,IAAYD,IACdE,EAAqB;EACnB,cAAcF,EAAO,CAAC,CAAC,MAAM,MAAWV,EAAO,OAAO;EACtD,aAAa;CACf,CAAC,IACD;CAGJ,OAFAF,EAAM,IAAIW,GAAME,CAAS,GAElBA;AACT"}
@@ -2,7 +2,7 @@ import { type Component } from 'vue';
2
2
  /**
3
3
  * A component whose code (and heavy dependencies such as Tiptap or Chart.js) loads on
4
4
  * first use. The server render and hydration show `fallback` and never start the import;
5
- * once loaded, later renders show the component straight away.
5
+ * once the app has mounted, a loaded component renders straight away.
6
6
  */
7
7
  export declare function lazyComponent(name: string, load: () => Promise<Component>, fallback: Component): Component;
8
8
  //# sourceMappingURL=lazy.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"lazy.d.ts","sourceRoot":"","sources":["../../src/lib/lazy.ts"],"names":[],"mappings":"AAAA,OAAO,EAA4D,KAAK,SAAS,EAAE,MAAM,KAAK,CAAA;AAE9F;;;;GAIG;AACH,wBAAgB,aAAa,CAC3B,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,OAAO,CAAC,SAAS,CAAC,EAC9B,QAAQ,EAAE,SAAS,GAClB,SAAS,CAyBX"}
1
+ {"version":3,"file":"lazy.d.ts","sourceRoot":"","sources":["../../src/lib/lazy.ts"],"names":[],"mappings":"AAAA,OAAO,EAA4C,KAAK,SAAS,EAAE,MAAM,KAAK,CAAA;AAG9E;;;;GAIG;AACH,wBAAgB,aAAa,CAC3B,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,OAAO,CAAC,SAAS,CAAC,EAC9B,QAAQ,EAAE,SAAS,GAClB,SAAS,CAuBX"}
package/dist/lib/lazy.js CHANGED
@@ -1,22 +1,23 @@
1
- import { defineAsyncComponent as e, defineComponent as t, h as n, onMounted as r, ref as i } from "vue";
1
+ import { useAppMounted as e } from "./hydration.js";
2
+ import { defineAsyncComponent as t, defineComponent as n, h as r } from "vue";
2
3
  //#region src/lib/lazy.ts
3
- function a(a, o, s) {
4
- let c = null, l = e({
5
- loader: () => o().then((e) => (c = e, e)),
6
- loadingComponent: s,
4
+ function i(i, a, o) {
5
+ let s = null, c = t({
6
+ loader: () => a().then((e) => (s = e, e)),
7
+ loadingComponent: o,
7
8
  delay: 0,
8
9
  suspensible: !1
9
10
  });
10
- return t({
11
- name: a,
11
+ return n({
12
+ name: i,
12
13
  inheritAttrs: !1,
13
- setup(e, { attrs: t }) {
14
- let a = i(!1);
15
- return r(() => a.value = !0), () => n(c ?? (a.value ? l : s), t);
14
+ setup(t, { attrs: n }) {
15
+ let i = e();
16
+ return () => r(i.value ? s ?? c : o, n);
16
17
  }
17
18
  });
18
19
  }
19
20
  //#endregion
20
- export { a as lazyComponent };
21
+ export { i as lazyComponent };
21
22
 
22
23
  //# sourceMappingURL=lazy.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"lazy.js","names":["lazyComponent","loaded","Async","defineAsyncComponent","load","component","fallback","defineComponent","mounted","ref","onMounted","h","attrs"],"sources":["../../src/lib/lazy.ts"],"sourcesContent":["import { defineAsyncComponent, defineComponent, h, onMounted, ref, type Component } from 'vue'\n\n/**\n * A component whose code (and heavy dependencies such as Tiptap or Chart.js) loads on\n * first use. The server render and hydration show `fallback` and never start the import;\n * once loaded, later renders show the component straight away.\n */\nexport function lazyComponent(\n name: string,\n load: () => Promise<Component>,\n fallback: Component,\n): Component {\n let loaded: Component | null = null\n const Async = defineAsyncComponent({\n loader: () =>\n load().then((component) => {\n loaded = component\n\n return component\n }),\n loadingComponent: fallback,\n delay: 0,\n suspensible: false,\n })\n\n return defineComponent({\n name,\n inheritAttrs: false,\n setup(_, { attrs }) {\n const mounted = ref(false)\n\n onMounted(() => (mounted.value = true))\n\n return () => h(loaded ?? (mounted.value ? Async : fallback), attrs)\n },\n })\n}\n"],"mappings":";;AAOA,SAAgBA,EACd,GACA,GACA,GACW;CACX,IAAIC,IAA2B,MACzBC,IAAQC,EAAqB;EACjC,cACEC,EAAK,CAAC,CAAC,MAAM,OACX,IAASC,GAEFA,EACR;EACH,kBAAkBC;EAClB,OAAO;EACP,aAAa;CACf,CAAC;CAED,OAAOC,EAAgB;EACrB;EACA,cAAc;EACd,MAAM,GAAG,EAAE,YAAS;GAClB,IAAMC,IAAUC,EAAI,EAAK;GAIzB,OAFAC,QAAiB,EAAQ,QAAQ,EAAK,SAEzBC,EAAEV,MAAWO,EAAQ,QAAQN,IAAQI,IAAWM,CAAK;EACpE;CACF,CAAC;AACH"}
1
+ {"version":3,"file":"lazy.js","names":["lazyComponent","loaded","Async","defineAsyncComponent","load","component","fallback","defineComponent","mounted","useAppMounted","h","attrs"],"sources":["../../src/lib/lazy.ts"],"sourcesContent":["import { defineAsyncComponent, defineComponent, h, type Component } from 'vue'\nimport { useAppMounted } from './hydration'\n\n/**\n * A component whose code (and heavy dependencies such as Tiptap or Chart.js) loads on\n * first use. The server render and hydration show `fallback` and never start the import;\n * once the app has mounted, a loaded component renders straight away.\n */\nexport function lazyComponent(\n name: string,\n load: () => Promise<Component>,\n fallback: Component,\n): Component {\n let loaded: Component | null = null\n const Async = defineAsyncComponent({\n loader: () =>\n load().then((component) => {\n loaded = component\n\n return component\n }),\n loadingComponent: fallback,\n delay: 0,\n suspensible: false,\n })\n\n return defineComponent({\n name,\n inheritAttrs: false,\n setup(_, { attrs }) {\n const mounted = useAppMounted()\n\n return () => h(mounted.value ? (loaded ?? Async) : fallback, attrs)\n },\n })\n}\n"],"mappings":";;;AAQA,SAAgBA,EACd,GACA,GACA,GACW;CACX,IAAIC,IAA2B,MACzBC,IAAQC,EAAqB;EACjC,cACEC,EAAK,CAAC,CAAC,MAAM,OACX,IAASC,GAEFA,EACR;EACH,kBAAkBC;EAClB,OAAO;EACP,aAAa;CACf,CAAC;CAED,OAAOC,EAAgB;EACrB;EACA,cAAc;EACd,MAAM,GAAG,EAAE,YAAS;GAClB,IAAMC,IAAUC,EAAc;GAE9B,aAAaC,EAAEF,EAAQ,QAASP,KAAUC,IAASI,GAAUK,CAAK;EACpE;CACF,CAAC;AACH"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alfakit/vue",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Vue 3 + shadcn-vue renderer for Alfakit, the Laravel admin panel builder by Alfa Six.",
5
5
  "license": "MIT",
6
6
  "author": "Alfa Six (https://alfasixbh.com)",
@@ -96,7 +96,7 @@
96
96
  "reka-ui": "^2.11.0",
97
97
  "tailwind-merge": "^3.7.0",
98
98
  "vue-sonner": "^2.0.9",
99
- "@alfakit/core": "0.1.0"
99
+ "@alfakit/core": "0.1.1"
100
100
  },
101
101
  "peerDependencies": {
102
102
  "@inertiajs/vue3": "^3.0.0",
package/src/lib/Icon.vue CHANGED
@@ -1,8 +1,9 @@
1
1
  <script setup lang="ts">
2
2
  import { iconAlias, iconAliasOverride, iconSource, isIconAlias } from '@alfakit/core'
3
- import { computed, onMounted, type Component, type HTMLAttributes } from 'vue'
3
+ import { computed, type Component, type HTMLAttributes } from 'vue'
4
4
  import { useOptionalRegistry } from '../context'
5
- import { iconMap, markIconsMounted, resolveIcon } from './icons'
5
+ import { useAppMounted } from './hydration'
6
+ import { iconMap, resolveIcon } from './icons'
6
7
  import { cn } from './utils'
7
8
 
8
9
  /**
@@ -22,8 +23,8 @@ const props = defineProps<{
22
23
  // `createAlfakit({ components: { 'panel:icon': MyIcon } })` renders every named icon with
23
24
  // another icon set; it receives `name`, `fallback` and `class`.
24
25
  const custom = useOptionalRegistry()?.resolve('panel:icon')
25
- // Named icons render once the page has hydrated (the server render shows none).
26
- onMounted(markIconsMounted)
26
+ // Named icons render once this app has hydrated (the server render shows none).
27
+ const mounted = useAppMounted()
27
28
 
28
29
  const fallbackName = computed(() =>
29
30
  typeof props.fallback === 'string' ? props.fallback : undefined,
@@ -49,7 +50,7 @@ const source = computed(() =>
49
50
  const component = computed(() => {
50
51
  if (source.value?.type !== 'name' || custom) return null
51
52
 
52
- const icons = iconMap()
53
+ const icons = iconMap(mounted.value)
53
54
 
54
55
  // Nothing until the icon map has loaded (and during the server render and hydration).
55
56
  if (!icons) return null
@@ -0,0 +1,35 @@
1
+ import { getCurrentInstance, onMounted, ref, type AppContext, type Ref } from 'vue'
2
+
3
+ /**
4
+ * Whether each app has mounted (hydrated). Kept per app, not per module: loaded icon chunks
5
+ * and lazy components outlive an app, so a second app hydrating in the same page would
6
+ * otherwise draw content its server markup does not contain.
7
+ */
8
+ const mountedApps = new WeakMap<AppContext, Ref<boolean>>()
9
+
10
+ /**
11
+ * Call in a component's setup: `false` until the component's app has mounted (so during
12
+ * the server render and hydration), then `true` for every component in that app, so
13
+ * content rendered later draws at once (reactive). Lazily loaded parts (named icons, heavy
14
+ * fields) render their server markup until then, whatever has loaded already.
15
+ */
16
+ export function useAppMounted(): Ref<boolean> {
17
+ const context = getCurrentInstance()?.appContext
18
+
19
+ // Outside a component there is no hydration to wait for.
20
+ if (!context) return ref(true)
21
+
22
+ let mounted = mountedApps.get(context)
23
+
24
+ if (!mounted) {
25
+ mounted = ref(false)
26
+ mountedApps.set(context, mounted)
27
+ }
28
+
29
+ const flag = mounted
30
+ onMounted(() => {
31
+ flag.value = true
32
+ })
33
+
34
+ return flag
35
+ }
package/src/lib/icons.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { defineAsyncComponent, ref, shallowRef, type Component } from 'vue'
1
+ import { defineAsyncComponent, shallowRef, type Component } from 'vue'
2
2
  import type * as IconModules from './icon-modules'
3
3
 
4
4
  /**
@@ -13,7 +13,7 @@ import type * as IconModules from './icon-modules'
13
13
  export type IconMap = typeof IconModules
14
14
 
15
15
  const loadedMap = shallowRef<IconMap | null>(null)
16
- const mounted = ref(false)
16
+
17
17
  let pending: Promise<IconMap> | undefined
18
18
 
19
19
  const cache = new Map<string, Component | null>()
@@ -37,19 +37,14 @@ export function preloadIcons(): void {
37
37
  loadIconMap()
38
38
  }
39
39
 
40
- /** `<Icon>`s call this once mounted: from then on named icons render (after hydration). */
41
- export function markIconsMounted(): void {
42
- mounted.value = true
43
- }
44
-
45
40
  /**
46
- * The icon map once it has loaded in the browser and the page has hydrated, else `null`
47
- * (reactive). Starts loading it.
41
+ * The icon map once it has loaded in the browser and the `<Icon>`'s app has mounted
42
+ * (`useAppMounted()`), else `null` (reactive). Starts loading it.
48
43
  */
49
- export function iconMap(): IconMap | null {
44
+ export function iconMap(mounted: boolean): IconMap | null {
50
45
  if (!loadedMap.value) loadIconMap()
51
46
 
52
- return mounted.value ? loadedMap.value : null
47
+ return mounted ? loadedMap.value : null
53
48
  }
54
49
 
55
50
  /**
package/src/lib/lazy.ts CHANGED
@@ -1,9 +1,10 @@
1
- import { defineAsyncComponent, defineComponent, h, onMounted, ref, type Component } from 'vue'
1
+ import { defineAsyncComponent, defineComponent, h, type Component } from 'vue'
2
+ import { useAppMounted } from './hydration'
2
3
 
3
4
  /**
4
5
  * A component whose code (and heavy dependencies such as Tiptap or Chart.js) loads on
5
6
  * first use. The server render and hydration show `fallback` and never start the import;
6
- * once loaded, later renders show the component straight away.
7
+ * once the app has mounted, a loaded component renders straight away.
7
8
  */
8
9
  export function lazyComponent(
9
10
  name: string,
@@ -27,11 +28,9 @@ export function lazyComponent(
27
28
  name,
28
29
  inheritAttrs: false,
29
30
  setup(_, { attrs }) {
30
- const mounted = ref(false)
31
+ const mounted = useAppMounted()
31
32
 
32
- onMounted(() => (mounted.value = true))
33
-
34
- return () => h(loaded ?? (mounted.value ? Async : fallback), attrs)
33
+ return () => h(mounted.value ? (loaded ?? Async) : fallback, attrs)
35
34
  },
36
35
  })
37
36
  }