@nexxtmove/ui 1.26.0 → 1.27.0

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.
@@ -0,0 +1,31 @@
1
+ import { NexxtIconName } from '../../atoms/Icon/Icon.vue';
2
+ export interface NexxtTreeNode {
3
+ /** Unique within the tree; this is what `selected` and `collapsed` hold. */
4
+ id: string;
5
+ label: string;
6
+ icon?: NexxtIconName;
7
+ /** A node with children gets a chevron and can be folded. */
8
+ children?: NexxtTreeNode[];
9
+ }
10
+ export interface NexxtTreeProps {
11
+ /** The top-level nodes; nest `children` for deeper levels. */
12
+ nodes: NexxtTreeNode[];
13
+ /** Accessible name for the tree, e.g. the heading above it. */
14
+ label: string;
15
+ }
16
+ type __VLS_Props = NexxtTreeProps;
17
+ type __VLS_ModelProps = {
18
+ 'selected'?: string | null;
19
+ /** Ids of the nodes whose children are hidden. Tracking the collapsed ones makes "everything open" the default. */
20
+ 'collapsed'?: string[];
21
+ };
22
+ type __VLS_PublicProps = __VLS_Props & __VLS_ModelProps;
23
+ declare const __VLS_export: import('vue').DefineComponent<__VLS_PublicProps, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {
24
+ "update:selected": (value: string | null) => any;
25
+ "update:collapsed": (value: string[]) => any;
26
+ }, string, import('vue').PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
27
+ "onUpdate:selected"?: ((value: string | null) => any) | undefined;
28
+ "onUpdate:collapsed"?: ((value: string[]) => any) | undefined;
29
+ }>, {}, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {}, any>;
30
+ declare const _default: typeof __VLS_export;
31
+ export default _default;
package/dist/index.d.ts CHANGED
@@ -7,6 +7,7 @@ export type { CustomIconName } from './components/atoms/Icon/customIcons';
7
7
  export type { NexxtTabItem } from './components/molecules/Tabs/Tabs.vue';
8
8
  export type { NexxtPageCardProps, NexxtPageCardSlots, } from './components/molecules/PageCard/PageCard.vue';
9
9
  export type { NexxtPageMockupType } from './components/molecules/PageMockup/PageMockup.vue';
10
+ export type { NexxtTreeNode, NexxtTreeProps } from './components/molecules/Tree/Tree.vue';
10
11
  export type { EditorAlignItemsMessages, EditorAlignItemsValue, } from './components/editor/molecules/EditorAlignItems/EditorAlignItems.vue';
11
12
  export type { EditorFlexDirectionMessages, EditorFlexDirectionValue, } from './components/editor/molecules/EditorFlexDirection/EditorFlexDirection.vue';
12
13
  export type { EditorGapMessages, EditorGapValue, } from './components/editor/molecules/EditorGap/EditorGap.vue';
@@ -93,4 +94,5 @@ export { default as NexxtToast } from './components/molecules/Toast/Toast.vue';
93
94
  export { default as NexxtToaster } from './components/organisms/Toaster/Toaster.vue';
94
95
  export { default as NexxtToggleGroup } from './components/molecules/ToggleGroup/ToggleGroup.vue';
95
96
  export { default as NexxtTooltip } from './components/atoms/Tooltip/Tooltip.vue';
97
+ export { default as NexxtTree } from './components/molecules/Tree/Tree.vue';
96
98
  export { default as NexxtVideoPlayer } from './components/organisms/VideoPlayer/VideoPlayer.vue';
package/dist/index.js CHANGED
@@ -1,9 +1,9 @@
1
1
  import { Fragment as e, Teleport as t, Transition as n, TransitionGroup as r, computed as i, createBlock as a, createCommentVNode as o, createElementBlock as s, createElementVNode as c, createSlots as l, createStaticVNode as u, createTextVNode as d, createVNode as f, defineComponent as p, getCurrentInstance as m, guardReactiveProps as h, mergeModels as g, mergeProps as _, nextTick as v, normalizeClass as y, normalizeProps as b, normalizeStyle as x, onMounted as S, onUnmounted as C, openBlock as w, ref as T, renderList as E, renderSlot as D, resolveDynamicComponent as O, toDisplayString as k, unref as A, useAttrs as j, useCssVars as M, useModel as N, useTemplateRef as P, vModelCheckbox as F, vModelDynamic as I, vModelText as L, watch as R, watchEffect as z, withCtx as B, withDirectives as V, withKeys as H, withModifiers as U } from "vue";
2
2
  import ee from "@number-flow/vue";
3
- import { addMonths as W, eachDayOfInterval as G, endOfDay as te, endOfMonth as K, endOfWeek as ne, format as q, getHours as re, getMinutes as ie, isAfter as ae, isBefore as oe, isSameDay as se, isSameMonth as ce, setHours as le, setMinutes as ue, startOfDay as de, startOfMonth as fe, startOfWeek as pe, subMonths as me } from "date-fns";
4
- import { fromZonedTime as he, toZonedTime as J } from "date-fns-tz";
5
- import { nl as ge } from "date-fns/locale";
6
- import { ColorAreaArea as _e, ColorAreaRoot as ve, ColorAreaThumb as ye, ColorFieldInput as Y, ColorFieldRoot as be, ColorSliderRoot as xe, ColorSliderThumb as Se, ColorSliderTrack as Ce, ColorSwatch as we, DialogClose as Te, DialogContent as Ee, DialogDescription as De, DialogOverlay as Oe, DialogPortal as ke, DialogRoot as Ae, DialogTitle as je, PaginationEllipsis as Me, PaginationFirst as Ne, PaginationLast as Pe, PaginationList as Fe, PaginationListItem as Ie, PaginationNext as Le, PaginationPrev as Re, PaginationRoot as ze, SliderRange as Be, SliderRoot as Ve, SliderThumb as He, SliderTrack as Ue, TooltipContent as We, TooltipPortal as Ge, TooltipProvider as Ke, TooltipRoot as qe, TooltipTrigger as Je, colorToString as Ye, normalizeColor as Xe, setChannelValue as Ze } from "reka-ui";
3
+ import { addMonths as W, eachDayOfInterval as te, endOfDay as ne, endOfMonth as G, endOfWeek as re, format as K, getHours as ie, getMinutes as ae, isAfter as oe, isBefore as se, isSameDay as ce, isSameMonth as le, setHours as ue, setMinutes as de, startOfDay as fe, startOfMonth as pe, startOfWeek as me, subMonths as he } from "date-fns";
4
+ import { fromZonedTime as ge, toZonedTime as q } from "date-fns-tz";
5
+ import { nl as _e } from "date-fns/locale";
6
+ import { ColorAreaArea as ve, ColorAreaRoot as ye, ColorAreaThumb as be, ColorFieldInput as J, ColorFieldRoot as Y, ColorSliderRoot as xe, ColorSliderThumb as Se, ColorSliderTrack as Ce, ColorSwatch as we, DialogClose as Te, DialogContent as Ee, DialogDescription as De, DialogOverlay as Oe, DialogPortal as ke, DialogRoot as Ae, DialogTitle as je, PaginationEllipsis as Me, PaginationFirst as Ne, PaginationLast as Pe, PaginationList as Fe, PaginationListItem as Ie, PaginationNext as Le, PaginationPrev as Re, PaginationRoot as ze, SliderRange as Be, SliderRoot as Ve, SliderThumb as He, SliderTrack as Ue, TooltipContent as We, TooltipPortal as Ge, TooltipProvider as Ke, TooltipRoot as qe, TooltipTrigger as Je, colorToString as Ye, normalizeColor as Xe, setChannelValue as Ze } from "reka-ui";
7
7
  import { autoUpdate as Qe, flip as $e, offset as et, shift as tt, size as nt, useFloating as rt } from "@floating-ui/vue";
8
8
  import { useEventListener as it, useFullscreen as at, useMediaControls as ot, useResizeObserver as st } from "@vueuse/core";
9
9
  //#region src/components/molecules/TimelineEvent/TimelineTypes.ts
@@ -590,9 +590,9 @@ var pt = { funda: { render: ft } }, Z = /* @__PURE__ */ p({
590
590
  ],
591
591
  setup(e, { emit: t }) {
592
592
  let r = e, a = t, l = i(() => {
593
- let e = q(r.viewDate, "MMM", { locale: r.locale }).replace(".", "").trim();
593
+ let e = K(r.viewDate, "MMM", { locale: r.locale }).replace(".", "").trim();
594
594
  return e.charAt(0).toUpperCase() + e.slice(1) + ".";
595
- }), u = i(() => q(r.viewDate, "yyyy")), d = {
595
+ }), u = i(() => K(r.viewDate, "yyyy")), d = {
596
596
  enterActiveClass: "transition-all duration-200 ease-out",
597
597
  enterFromClass: "translate-y-full",
598
598
  leaveActiveClass: "transition-all duration-200 ease-out absolute inset-x-0 top-0",
@@ -686,14 +686,14 @@ var pt = { funda: { render: ft } }, Z = /* @__PURE__ */ p({
686
686
  emits: ["select"],
687
687
  setup(t, { expose: n, emit: r }) {
688
688
  let a = t, l = r, u = P("dayButtonsRef"), d = (e) => {
689
- let t = J(e, a.timezone), n = J(/* @__PURE__ */ new Date(), a.timezone);
690
- return se(t, n);
691
- }, f = (e) => a.modelValue ? se(e, a.modelValue) : !1, p = (e) => a.markedDates.some((t) => se(t, e)), m = i(() => {
689
+ let t = q(e, a.timezone), n = q(/* @__PURE__ */ new Date(), a.timezone);
690
+ return ce(t, n);
691
+ }, f = (e) => a.modelValue ? ce(e, a.modelValue) : !1, p = (e) => a.markedDates.some((t) => ce(t, e)), m = i(() => {
692
692
  let e = [];
693
693
  for (let t = 0; t < a.calendarDays.length; t += 7) e.push(a.calendarDays.slice(t, t + 7));
694
694
  return e;
695
695
  }), h = i(() => a.calendarDays.length === 0 ? [] : a.calendarDays.slice(0, 7).map((e) => {
696
- let t = q(e, "EEEEEE", { locale: a.locale });
696
+ let t = K(e, "EEEEEE", { locale: a.locale });
697
697
  return t.charAt(0).toUpperCase() + t.slice(1);
698
698
  })), g = (e, t) => {
699
699
  let n = t;
@@ -722,9 +722,9 @@ var pt = { funda: { render: ft } }, Z = /* @__PURE__ */ p({
722
722
  n >= 0 && n < a.calendarDays.length && (e.preventDefault(), u.value?.[n]?.focus());
723
723
  };
724
724
  return S(() => {
725
- a.calendarDays.findIndex((e) => a.modelValue && se(e, a.modelValue) || !a.modelValue && d(e));
725
+ a.calendarDays.findIndex((e) => a.modelValue && ce(e, a.modelValue) || !a.modelValue && d(e));
726
726
  }), n({ focus: (e) => {
727
- let t = a.calendarDays.findIndex((t) => se(t, e));
727
+ let t = a.calendarDays.findIndex((t) => ce(t, e));
728
728
  t !== -1 && u.value?.[t]?.focus();
729
729
  } }), (n, r) => (w(), s("div", Nt, [c("div", Pt, [(w(!0), s(e, null, E(h.value, (e) => (w(), s("div", {
730
730
  key: e,
@@ -747,11 +747,11 @@ var pt = { funda: { render: ft } }, Z = /* @__PURE__ */ p({
747
747
  ref: u,
748
748
  type: "button",
749
749
  disabled: t.isDisabled(e),
750
- "aria-label": A(q)(e, "d MMMM yyyy", { locale: t.locale }),
751
- tabindex: A(ce)(e, t.viewDate) ? 0 : -1,
750
+ "aria-label": A(K)(e, "d MMMM yyyy", { locale: t.locale }),
751
+ tabindex: A(le)(e, t.viewDate) ? 0 : -1,
752
752
  class: y([
753
753
  "relative flex size-8 items-center justify-center rounded-xl text-xs transition-colors duration-150 outline-none focus:ring-2 focus:ring-cornflower-blue-500 focus:ring-offset-1",
754
- A(ce)(e, t.viewDate) ? "text-gray-900" : "text-gray-300",
754
+ A(le)(e, t.viewDate) ? "text-gray-900" : "text-gray-300",
755
755
  f(e) ? "bg-cornflower-blue-500 font-medium text-white!" : "",
756
756
  d(e) && !f(e) ? "font-medium text-cornflower-blue-500! ring-1 ring-cornflower-blue-500" : "",
757
757
  d(e) && f(e) ? "ring-2 ring-cornflower-blue-500 ring-offset-2" : "",
@@ -760,7 +760,7 @@ var pt = { funda: { render: ft } }, Z = /* @__PURE__ */ p({
760
760
  ]),
761
761
  onClick: (t) => l("select", e),
762
762
  onKeydown: (e) => g(e, r * 7 + n)
763
- }, [c("span", Rt, k(A(q)(e, "d")), 1), p(e) ? (w(), s("span", {
763
+ }, [c("span", Rt, k(A(K)(e, "d")), 1), p(e) ? (w(), s("span", {
764
764
  key: 0,
765
765
  class: y(["absolute bottom-1 left-1/2 size-1 -translate-x-1/2 rounded-full", f(e) ? "bg-white" : "bg-cornflower-blue-500"])
766
766
  }, null, 2)) : o("", !0)], 42, Lt)], 8, It))), 128))]))), 128))]));
@@ -786,17 +786,17 @@ var pt = { funda: { render: ft } }, Z = /* @__PURE__ */ p({
786
786
  emits: ["select"],
787
787
  setup(t, { emit: n }) {
788
788
  let r = t, a = n, o = i(() => Array.from({ length: 12 }, (e, t) => {
789
- let n = J(new Date(Date.UTC(r.viewYear, t, 1)), r.timezone), i = q(n, "LLL", { locale: r.locale }).replace(".", "").trim();
789
+ let n = q(new Date(Date.UTC(r.viewYear, t, 1)), r.timezone), i = K(n, "LLL", { locale: r.locale }).replace(".", "").trim();
790
790
  return {
791
791
  index: t,
792
792
  label: i.charAt(0).toUpperCase() + i.slice(1)
793
793
  };
794
794
  })), c = (e) => {
795
- let t = r.viewYear, n = J(new Date(Date.UTC(t, e, 1)), r.timezone);
796
- return !!(r.minDate && K(n) < de(J(r.minDate, r.timezone)) || r.maxDate && fe(n) > te(J(r.maxDate, r.timezone)));
795
+ let t = r.viewYear, n = q(new Date(Date.UTC(t, e, 1)), r.timezone);
796
+ return !!(r.minDate && G(n) < fe(q(r.minDate, r.timezone)) || r.maxDate && pe(n) > ne(q(r.maxDate, r.timezone)));
797
797
  }, l = (e) => {
798
798
  if (!r.modelValue) return !1;
799
- let t = J(r.modelValue, r.timezone);
799
+ let t = q(r.modelValue, r.timezone);
800
800
  return t.getFullYear() === r.viewYear && t.getMonth() === e;
801
801
  }, u = (e) => e === r.currentMonth;
802
802
  return (t, n) => (w(), s("div", Bt, [(w(!0), s(e, null, E(o.value, (e) => (w(), s("button", {
@@ -826,7 +826,7 @@ var pt = { funda: { render: ft } }, Z = /* @__PURE__ */ p({
826
826
  emits: ["select"],
827
827
  setup(t, { emit: n }) {
828
828
  let r = t, a = n, o = P("yearScrollerRef"), l = i(() => {
829
- let e = r.minDate ? J(r.minDate, r.timezone).getFullYear() : r.viewYear - 100, t = r.maxDate ? J(r.maxDate, r.timezone).getFullYear() : r.viewYear + 20;
829
+ let e = r.minDate ? q(r.minDate, r.timezone).getFullYear() : r.viewYear - 100, t = r.maxDate ? q(r.maxDate, r.timezone).getFullYear() : r.viewYear + 20;
830
830
  return Array.from({ length: t - e + 1 }, (t, n) => e + n);
831
831
  }), u = (e) => {
832
832
  if (!o.value) return;
@@ -862,16 +862,16 @@ var pt = { funda: { render: ft } }, Z = /* @__PURE__ */ p({
862
862
  emits: ["update:modelValue"],
863
863
  setup(e, { emit: t }) {
864
864
  let n = e, r = t, a = i({
865
- get: () => n.modelValue ? re(n.modelValue) : re(/* @__PURE__ */ new Date()),
865
+ get: () => n.modelValue ? ie(n.modelValue) : ie(/* @__PURE__ */ new Date()),
866
866
  set: (e) => {
867
867
  let t = n.modelValue || /* @__PURE__ */ new Date();
868
- r("update:modelValue", le(t, Number(e)));
868
+ r("update:modelValue", ue(t, Number(e)));
869
869
  }
870
870
  }), o = i({
871
- get: () => n.modelValue ? ie(n.modelValue) : ie(/* @__PURE__ */ new Date()),
871
+ get: () => n.modelValue ? ae(n.modelValue) : ae(/* @__PURE__ */ new Date()),
872
872
  set: (e) => {
873
873
  let t = n.modelValue || /* @__PURE__ */ new Date();
874
- r("update:modelValue", ue(t, Number(e)));
874
+ r("update:modelValue", de(t, Number(e)));
875
875
  }
876
876
  }), l = (e) => e.toString().padStart(2, "0"), u = () => {
877
877
  a.value = (a.value + 1) % 24;
@@ -916,7 +916,7 @@ var pt = { funda: { render: ft } }, Z = /* @__PURE__ */ p({
916
916
  name: "NexxtCalendar",
917
917
  __name: "Calendar",
918
918
  props: /*@__PURE__*/ g({
919
- locale: { default: () => ge },
919
+ locale: { default: () => _e },
920
920
  markedDates: { default: () => [] },
921
921
  disabledDates: { default: () => [] },
922
922
  minDate: {},
@@ -936,41 +936,41 @@ var pt = { funda: { render: ft } }, Z = /* @__PURE__ */ p({
936
936
  }),
937
937
  emits: /*@__PURE__*/ g(["select"], ["update:modelValue"]),
938
938
  setup(e, { emit: t }) {
939
- let r = N(e, "modelValue"), l = t, u = T(r.value ? J(new Date(r.value), e.timezone) : J(/* @__PURE__ */ new Date(), e.timezone)), d = T("days"), p = T(null), m = T("fade");
939
+ let r = N(e, "modelValue"), l = t, u = T(r.value ? q(new Date(r.value), e.timezone) : q(/* @__PURE__ */ new Date(), e.timezone)), d = T("days"), p = T(null), m = T("fade");
940
940
  R(r, (t) => {
941
- t && (u.value = J(new Date(t), e.timezone));
941
+ t && (u.value = q(new Date(t), e.timezone));
942
942
  });
943
943
  let h = i(() => {
944
- let e = pe(fe(u.value), { weekStartsOn: 1 }), t = ne(K(u.value), { weekStartsOn: 1 });
945
- return G({
944
+ let e = me(pe(u.value), { weekStartsOn: 1 }), t = re(G(u.value), { weekStartsOn: 1 });
945
+ return te({
946
946
  start: e,
947
947
  end: t
948
948
  });
949
949
  }), g = (t) => {
950
- let n = J(t, e.timezone);
951
- return !!(e.disabledDates.some((t) => se(J(t, e.timezone), n)) || e.minDate && oe(n, de(J(e.minDate, e.timezone))) || e.maxDate && ae(n, te(J(e.maxDate, e.timezone))));
950
+ let n = q(t, e.timezone);
951
+ return !!(e.disabledDates.some((t) => ce(q(t, e.timezone), n)) || e.minDate && se(n, fe(q(e.minDate, e.timezone))) || e.maxDate && oe(n, ne(q(e.maxDate, e.timezone))));
952
952
  }, v = (t) => {
953
- let n = J(t, e.timezone);
954
- g(n) || (ce(n, u.value) || (u.value = fe(n)), r.value = he(n, e.timezone), l("select", r.value));
953
+ let n = q(t, e.timezone);
954
+ g(n) || (le(n, u.value) || (u.value = pe(n)), r.value = ge(n, e.timezone), l("select", r.value));
955
955
  }, b = (t) => {
956
956
  m.value = "fade";
957
- let n = J(new Date(Date.UTC(u.value.getUTCFullYear(), t, 1)), e.timezone);
957
+ let n = q(new Date(Date.UTC(u.value.getUTCFullYear(), t, 1)), e.timezone);
958
958
  u.value = n, d.value = "days";
959
959
  }, x = (t) => {
960
- let n = J(new Date(Date.UTC(t, u.value.getUTCMonth(), 1)), e.timezone);
960
+ let n = q(new Date(Date.UTC(t, u.value.getUTCMonth(), 1)), e.timezone);
961
961
  u.value = n, d.value = "months";
962
962
  }, S = i(() => {
963
963
  if (d.value !== "days") return !1;
964
964
  if (!e.minDate) return !0;
965
- let t = me(u.value, 1);
966
- return !oe(K(t), de(J(e.minDate, e.timezone)));
965
+ let t = he(u.value, 1);
966
+ return !se(G(t), fe(q(e.minDate, e.timezone)));
967
967
  }), C = i(() => {
968
968
  if (d.value !== "days") return !1;
969
969
  if (!e.maxDate) return !0;
970
970
  let t = W(u.value, 1);
971
- return !ae(fe(t), te(J(e.maxDate, e.timezone)));
971
+ return !oe(pe(t), ne(q(e.maxDate, e.timezone)));
972
972
  }), E = () => {
973
- S.value && (m.value = "slide-prev", u.value = me(u.value, 1));
973
+ S.value && (m.value = "slide-prev", u.value = he(u.value, 1));
974
974
  }, D = () => {
975
975
  C.value && (m.value = "slide-next", u.value = W(u.value, 1));
976
976
  }, O = () => {
@@ -1455,7 +1455,7 @@ var pt = { funda: { render: ft } }, Z = /* @__PURE__ */ p({
1455
1455
  }, null, 8, ["class"])
1456
1456
  ], 10, dn)]),
1457
1457
  content: B(() => [c("div", { class: y(["flex flex-col gap-4 rounded-xl bg-white p-4 ring ring-gray-200", e.matchWidth ? "w-full" : "w-70"]) }, [
1458
- f(A(ve), {
1458
+ f(A(ye), {
1459
1459
  "model-value": u.value,
1460
1460
  "color-space": "hsl",
1461
1461
  "x-channel": "saturation",
@@ -1463,11 +1463,11 @@ var pt = { funda: { render: ft } }, Z = /* @__PURE__ */ p({
1463
1463
  class: "relative",
1464
1464
  "onUpdate:color": C
1465
1465
  }, {
1466
- default: B(({ style: e }) => [c("div", fn, [f(A(_e), {
1466
+ default: B(({ style: e }) => [c("div", fn, [f(A(ve), {
1467
1467
  class: "relative h-40 w-full cursor-crosshair outline-none",
1468
1468
  style: x(e)
1469
1469
  }, {
1470
- default: B(() => [f(A(ye), { class: "block size-4 cursor-grab rounded-full border-2 border-white shadow-sm focus:outline-none active:cursor-grabbing" })]),
1470
+ default: B(() => [f(A(be), { class: "block size-4 cursor-grab rounded-full border-2 border-white shadow-sm focus:outline-none active:cursor-grabbing" })]),
1471
1471
  _: 1
1472
1472
  }, 8, ["style"])])]),
1473
1473
  _: 1
@@ -1516,55 +1516,55 @@ var pt = { funda: { render: ft } }, Z = /* @__PURE__ */ p({
1516
1516
  name: "chevron-down",
1517
1517
  class: "text-[12px]",
1518
1518
  "aria-hidden": "true"
1519
- })])])) : o("", !0)]), _.value === "hex" ? (w(), a(A(be), {
1519
+ })])])) : o("", !0)]), _.value === "hex" ? (w(), a(A(Y), {
1520
1520
  key: 0,
1521
1521
  "model-value": g.value,
1522
1522
  class: "w-full",
1523
1523
  "onUpdate:color": C
1524
1524
  }, {
1525
- default: B(() => [c("div", { class: y(["flex h-10 items-center overflow-hidden rounded-lg px-3 ring transition-colors ease-out focus-within:ring-cornflower-blue-500", p.value ? "ring-brick-400" : "ring-gray-200"]) }, [f(A(Y), {
1525
+ default: B(() => [c("div", { class: y(["flex h-10 items-center overflow-hidden rounded-lg px-3 ring transition-colors ease-out focus-within:ring-cornflower-blue-500", p.value ? "ring-brick-400" : "ring-gray-200"]) }, [f(A(J), {
1526
1526
  class: "w-full bg-transparent small-normal-mono text-gray-900 placeholder-gray-400 focus:outline-none",
1527
1527
  placeholder: "#000000",
1528
1528
  onInput: E
1529
1529
  })], 2), p.value ? (w(), s("p", vn, k(p.value), 1)) : o("", !0)]),
1530
1530
  _: 1
1531
1531
  }, 8, ["model-value"])) : _.value === "hsl" ? (w(), s("div", yn, [
1532
- f(A(be), {
1532
+ f(A(Y), {
1533
1533
  "model-value": u.value,
1534
1534
  channel: "hue",
1535
1535
  "color-space": "hsl",
1536
1536
  class: "flex-1",
1537
1537
  "onUpdate:color": C
1538
1538
  }, {
1539
- default: B(() => [c("div", { class: y(["flex h-10 items-center overflow-hidden rounded-lg px-3 ring transition-colors ease-out focus-within:ring-cornflower-blue-500", h.value.hue ? "ring-brick-400" : "ring-gray-200"]) }, [f(A(Y), {
1539
+ default: B(() => [c("div", { class: y(["flex h-10 items-center overflow-hidden rounded-lg px-3 ring transition-colors ease-out focus-within:ring-cornflower-blue-500", h.value.hue ? "ring-brick-400" : "ring-gray-200"]) }, [f(A(J), {
1540
1540
  class: "w-full bg-transparent small-normal-mono text-gray-900 placeholder-gray-400 focus:outline-none",
1541
1541
  placeholder: "H",
1542
1542
  onInput: i[2] ||= (e) => D(e, "hue")
1543
1543
  })], 2)]),
1544
1544
  _: 1
1545
1545
  }, 8, ["model-value"]),
1546
- f(A(be), {
1546
+ f(A(Y), {
1547
1547
  "model-value": u.value,
1548
1548
  channel: "saturation",
1549
1549
  "color-space": "hsl",
1550
1550
  class: "flex-1",
1551
1551
  "onUpdate:color": C
1552
1552
  }, {
1553
- default: B(() => [c("div", { class: y(["flex h-10 items-center overflow-hidden rounded-lg px-3 ring transition-colors ease-out focus-within:ring-cornflower-blue-500", h.value.saturation ? "ring-brick-400" : "ring-gray-200"]) }, [f(A(Y), {
1553
+ default: B(() => [c("div", { class: y(["flex h-10 items-center overflow-hidden rounded-lg px-3 ring transition-colors ease-out focus-within:ring-cornflower-blue-500", h.value.saturation ? "ring-brick-400" : "ring-gray-200"]) }, [f(A(J), {
1554
1554
  class: "w-full bg-transparent small-normal-mono text-gray-900 placeholder-gray-400 focus:outline-none",
1555
1555
  placeholder: "S",
1556
1556
  onInput: i[3] ||= (e) => D(e, "saturation")
1557
1557
  })], 2)]),
1558
1558
  _: 1
1559
1559
  }, 8, ["model-value"]),
1560
- f(A(be), {
1560
+ f(A(Y), {
1561
1561
  "model-value": u.value,
1562
1562
  channel: "lightness",
1563
1563
  "color-space": "hsl",
1564
1564
  class: "flex-1",
1565
1565
  "onUpdate:color": C
1566
1566
  }, {
1567
- default: B(() => [c("div", { class: y(["flex h-10 items-center overflow-hidden rounded-lg px-3 ring transition-colors ease-out focus-within:ring-cornflower-blue-500", h.value.lightness ? "ring-brick-400" : "ring-gray-200"]) }, [f(A(Y), {
1567
+ default: B(() => [c("div", { class: y(["flex h-10 items-center overflow-hidden rounded-lg px-3 ring transition-colors ease-out focus-within:ring-cornflower-blue-500", h.value.lightness ? "ring-brick-400" : "ring-gray-200"]) }, [f(A(J), {
1568
1568
  class: "w-full bg-transparent small-normal-mono text-gray-900 placeholder-gray-400 focus:outline-none",
1569
1569
  placeholder: "L",
1570
1570
  onInput: i[4] ||= (e) => D(e, "lightness")
@@ -1572,42 +1572,42 @@ var pt = { funda: { render: ft } }, Z = /* @__PURE__ */ p({
1572
1572
  _: 1
1573
1573
  }, 8, ["model-value"])
1574
1574
  ])) : _.value === "rgb" ? (w(), s("div", bn, [
1575
- f(A(be), {
1575
+ f(A(Y), {
1576
1576
  "model-value": u.value,
1577
1577
  channel: "red",
1578
1578
  "color-space": "rgb",
1579
1579
  class: "flex-1",
1580
1580
  "onUpdate:color": C
1581
1581
  }, {
1582
- default: B(() => [c("div", { class: y(["flex h-10 items-center overflow-hidden rounded-lg px-3 ring transition-colors ease-out focus-within:ring-cornflower-blue-500", h.value.red ? "ring-brick-400" : "ring-gray-200"]) }, [f(A(Y), {
1582
+ default: B(() => [c("div", { class: y(["flex h-10 items-center overflow-hidden rounded-lg px-3 ring transition-colors ease-out focus-within:ring-cornflower-blue-500", h.value.red ? "ring-brick-400" : "ring-gray-200"]) }, [f(A(J), {
1583
1583
  class: "w-full bg-transparent small-normal-mono text-gray-900 placeholder-gray-400 focus:outline-none",
1584
1584
  placeholder: "R",
1585
1585
  onInput: i[5] ||= (e) => D(e, "red")
1586
1586
  })], 2)]),
1587
1587
  _: 1
1588
1588
  }, 8, ["model-value"]),
1589
- f(A(be), {
1589
+ f(A(Y), {
1590
1590
  "model-value": u.value,
1591
1591
  channel: "green",
1592
1592
  "color-space": "rgb",
1593
1593
  class: "flex-1",
1594
1594
  "onUpdate:color": C
1595
1595
  }, {
1596
- default: B(() => [c("div", { class: y(["flex h-10 items-center overflow-hidden rounded-lg px-3 ring transition-colors ease-out focus-within:ring-cornflower-blue-500", h.value.green ? "ring-brick-400" : "ring-gray-200"]) }, [f(A(Y), {
1596
+ default: B(() => [c("div", { class: y(["flex h-10 items-center overflow-hidden rounded-lg px-3 ring transition-colors ease-out focus-within:ring-cornflower-blue-500", h.value.green ? "ring-brick-400" : "ring-gray-200"]) }, [f(A(J), {
1597
1597
  class: "w-full bg-transparent small-normal-mono text-gray-900 placeholder-gray-400 focus:outline-none",
1598
1598
  placeholder: "G",
1599
1599
  onInput: i[6] ||= (e) => D(e, "green")
1600
1600
  })], 2)]),
1601
1601
  _: 1
1602
1602
  }, 8, ["model-value"]),
1603
- f(A(be), {
1603
+ f(A(Y), {
1604
1604
  "model-value": u.value,
1605
1605
  channel: "blue",
1606
1606
  "color-space": "rgb",
1607
1607
  class: "flex-1",
1608
1608
  "onUpdate:color": C
1609
1609
  }, {
1610
- default: B(() => [c("div", { class: y(["flex h-10 items-center overflow-hidden rounded-lg px-3 ring transition-colors ease-out focus-within:ring-cornflower-blue-500", h.value.blue ? "ring-brick-400" : "ring-gray-200"]) }, [f(A(Y), {
1610
+ default: B(() => [c("div", { class: y(["flex h-10 items-center overflow-hidden rounded-lg px-3 ring transition-colors ease-out focus-within:ring-cornflower-blue-500", h.value.blue ? "ring-brick-400" : "ring-gray-200"]) }, [f(A(J), {
1611
1611
  class: "w-full bg-transparent small-normal-mono text-gray-900 placeholder-gray-400 focus:outline-none",
1612
1612
  placeholder: "B",
1613
1613
  onInput: i[7] ||= (e) => D(e, "blue")
@@ -1796,21 +1796,21 @@ var pt = { funda: { render: ft } }, Z = /* @__PURE__ */ p({
1796
1796
  return !R.value.some((t) => t.label.toLowerCase() === e);
1797
1797
  }), ee = i(() => z.value.length > 0 || V.value), W = (e) => {
1798
1798
  v.value = [...v.value, L(e)], D.value = "";
1799
- }, G = (e) => {
1799
+ }, te = (e) => {
1800
1800
  let t = L(e);
1801
1801
  v.value = v.value[0] === t ? [] : [t], D.value = "", _("select");
1802
- }, te = () => {
1802
+ }, ne = () => {
1803
1803
  if (p.singleSelect) {
1804
- z.value.length > 0 ? G(z.value[0]) : p.allowNew && K();
1804
+ z.value.length > 0 ? te(z.value[0]) : p.allowNew && G();
1805
1805
  return;
1806
1806
  }
1807
- z.value.length === 1 ? W(z.value[0]) : p.allowNew && K();
1808
- }, K = () => {
1807
+ z.value.length === 1 ? W(z.value[0]) : p.allowNew && G();
1808
+ }, G = () => {
1809
1809
  let e = D.value.trim();
1810
1810
  if (!e) return;
1811
1811
  let t = e.toLowerCase(), n = R.value.some((e) => e.label.toLowerCase() === t), r = p.options.some((e) => I(e).toLowerCase() === t);
1812
1812
  n || r || (p.singleSelect ? (v.value = [e], D.value = "", _("select")) : (v.value = [...v.value, e], D.value = ""));
1813
- }, ne = (e) => {
1813
+ }, re = (e) => {
1814
1814
  v.value = v.value.filter((t) => t !== e);
1815
1815
  };
1816
1816
  return l({ focus: () => {
@@ -1830,7 +1830,7 @@ var pt = { funda: { render: ft } }, Z = /* @__PURE__ */ p({
1830
1830
  placeholder: A(h),
1831
1831
  "aria-label": A(h),
1832
1832
  onFocus: l[1] ||= (e) => O.value = !0,
1833
- onKeydown: H(te, ["enter"])
1833
+ onKeydown: H(ne, ["enter"])
1834
1834
  }, null, 8, [
1835
1835
  "modelValue",
1836
1836
  "left-icon",
@@ -1859,7 +1859,7 @@ var pt = { funda: { render: ft } }, Z = /* @__PURE__ */ p({
1859
1859
  key: e.value,
1860
1860
  class: "origin-left cursor-pointer",
1861
1861
  "aria-label": `Verwijder ${e.label}`,
1862
- onClick: (t) => ne(e.value)
1862
+ onClick: (t) => re(e.value)
1863
1863
  }, [f(sn, {
1864
1864
  ghost: !0,
1865
1865
  class: "group",
@@ -1893,7 +1893,7 @@ var pt = { funda: { render: ft } }, Z = /* @__PURE__ */ p({
1893
1893
  role: "option",
1894
1894
  "aria-selected": v.value.includes(L(e)),
1895
1895
  class: y(["flex w-full cursor-pointer items-center gap-2 px-6 py-3 text-left text-sm whitespace-nowrap text-gray-700 transition-colors duration-200 hover:bg-gray-100 hover:text-purple-600 focus:bg-gray-100 focus:outline-none", { "font-medium text-cornflower-blue-600": t.singleSelect && v.value.includes(L(e)) }]),
1896
- onClick: (n) => t.singleSelect ? G(e) : W(e)
1896
+ onClick: (n) => t.singleSelect ? te(e) : W(e)
1897
1897
  }, [d(k(I(e)) + " ", 1), t.singleSelect && v.value.includes(L(e)) ? (w(), a(Z, {
1898
1898
  key: 0,
1899
1899
  name: "check",
@@ -1901,7 +1901,7 @@ var pt = { funda: { render: ft } }, Z = /* @__PURE__ */ p({
1901
1901
  })) : o("", !0)], 10, Pn))), 128))]), V.value ? (w(), s("div", {
1902
1902
  key: 0,
1903
1903
  class: "flex grow-0 items-center gap-4 border-t border-gray-100 px-6 py-3 whitespace-normal text-gray-700",
1904
- onClick: l[2] ||= (e) => K()
1904
+ onClick: l[2] ||= (e) => G()
1905
1905
  }, [f(Z, {
1906
1906
  name: "plus",
1907
1907
  class: "cursor-pointer small-normal",
@@ -1921,7 +1921,7 @@ var pt = { funda: { render: ft } }, Z = /* @__PURE__ */ p({
1921
1921
  class: "w-full",
1922
1922
  "left-icon": A(m),
1923
1923
  placeholder: A(h),
1924
- onKeydown: H(te, ["enter"])
1924
+ onKeydown: H(ne, ["enter"])
1925
1925
  }, null, 8, [
1926
1926
  "modelValue",
1927
1927
  "left-icon",
@@ -1950,7 +1950,7 @@ var pt = { funda: { render: ft } }, Z = /* @__PURE__ */ p({
1950
1950
  key: e.value,
1951
1951
  class: "origin-left cursor-pointer",
1952
1952
  "aria-label": `Verwijder ${e.label}`,
1953
- onClick: (t) => ne(e.value)
1953
+ onClick: (t) => re(e.value)
1954
1954
  }, [f(sn, {
1955
1955
  ghost: !0,
1956
1956
  class: "group",
@@ -1973,7 +1973,7 @@ var pt = { funda: { render: ft } }, Z = /* @__PURE__ */ p({
1973
1973
  role: "option",
1974
1974
  "aria-selected": v.value.includes(L(e)),
1975
1975
  class: y(["flex w-full min-w-42 cursor-pointer items-center gap-2 px-6 py-3 text-left text-sm whitespace-nowrap text-gray-700 transition-colors duration-200 hover:bg-gray-100 hover:text-purple-600 focus:bg-gray-100 focus:outline-none", { "font-medium text-cornflower-blue-600": t.singleSelect && v.value.includes(L(e)) }]),
1976
- onClick: (n) => t.singleSelect ? G(e) : W(e)
1976
+ onClick: (n) => t.singleSelect ? te(e) : W(e)
1977
1977
  }, [d(k(I(e)) + " ", 1), t.singleSelect && v.value.includes(L(e)) ? (w(), a(Z, {
1978
1978
  key: 0,
1979
1979
  name: "check",
@@ -1982,7 +1982,7 @@ var pt = { funda: { render: ft } }, Z = /* @__PURE__ */ p({
1982
1982
  V.value ? (w(), s("div", {
1983
1983
  key: 0,
1984
1984
  class: "flex grow-0 items-center gap-4 border-t border-gray-100 px-6 py-3 whitespace-normal text-gray-700",
1985
- onClick: l[5] ||= (e) => K()
1985
+ onClick: l[5] ||= (e) => G()
1986
1986
  }, [f(Z, {
1987
1987
  name: "plus",
1988
1988
  class: "cursor-pointer small-normal",
@@ -6981,26 +6981,204 @@ var Ks = { render: Gs }, qs = { class: "flex items-center justify-end self-stret
6981
6981
  "leave-to-class"
6982
6982
  ])], 2)]));
6983
6983
  }
6984
- }), Tl = [
6984
+ }), Tl = ["aria-label"], El = [
6985
+ "aria-level",
6986
+ "aria-posinset",
6987
+ "aria-setsize",
6988
+ "aria-selected",
6989
+ "aria-expanded",
6990
+ "tabindex",
6991
+ "data-tree-id",
6992
+ "onClick",
6993
+ "onFocus"
6994
+ ], Dl = ["onClick"], Ol = {
6995
+ key: 1,
6996
+ class: "size-5 shrink-0"
6997
+ }, kl = { class: "truncate" }, Al = 22, jl = "transform 200ms var(--ease-in-out-strong), opacity 80ms linear 120ms", Ml = /* @__PURE__ */ p({
6998
+ name: "NexxtTree",
6999
+ __name: "Tree",
7000
+ props: /*@__PURE__*/ g({
7001
+ nodes: {},
7002
+ label: {}
7003
+ }, {
7004
+ selected: { default: null },
7005
+ selectedModifiers: {},
7006
+ collapsed: { default: () => [] },
7007
+ collapsedModifiers: {}
7008
+ }),
7009
+ emits: ["update:selected", "update:collapsed"],
7010
+ setup(t) {
7011
+ let n = t, l = N(t, "selected"), u = N(t, "collapsed"), d = P("root"), p = T(null), m = i(() => {
7012
+ let e = [], t = (n, r, i) => {
7013
+ n.forEach((a, o) => {
7014
+ let s = !!a.children?.length, c = s && !u.value.includes(a.id);
7015
+ e.push({
7016
+ node: a,
7017
+ depth: r,
7018
+ parentId: i,
7019
+ expandable: s,
7020
+ expanded: c,
7021
+ position: o + 1,
7022
+ setSize: n.length
7023
+ }), c && t(a.children, r + 1, a.id);
7024
+ });
7025
+ };
7026
+ return t(n.nodes, 0, null), e;
7027
+ }), h = i(() => !!l.value && m.value.some((e) => e.node.id === l.value)), g = T({
7028
+ top: 0,
7029
+ height: 0
7030
+ }), _ = P("highlightElement"), b = {
7031
+ none: "",
7032
+ select: "transition-[transform,opacity] duration-200 ease-out-strong",
7033
+ move: "transition-[transform,opacity] duration-200 ease-in-out-strong"
7034
+ }, C = T("none"), D = (e) => Array.from(d.value?.querySelectorAll("[data-tree-id]") ?? []).find((t) => t.dataset.treeId === e && !t.classList.contains("absolute")), O = async () => {
7035
+ if (await v(), !d.value || !h.value) return;
7036
+ let e = D(l.value);
7037
+ e && (g.value = {
7038
+ top: e.offsetTop,
7039
+ height: e.offsetHeight
7040
+ });
7041
+ }, A = async (e) => {
7042
+ let t = C.value;
7043
+ C.value = "none", await e(), await v(), d.value?.getBoundingClientRect(), C.value = t;
7044
+ };
7045
+ R([l, m], ([e], [t]) => {
7046
+ C.value = e === t ? "move" : "select", O();
7047
+ }), st(d, () => {
7048
+ _.value?.getAnimations().length || A(O);
7049
+ }), S(() => A(async () => {
7050
+ await O(), await document.fonts?.ready, await O();
7051
+ }));
7052
+ let j = (e) => {
7053
+ let t = e;
7054
+ if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) return;
7055
+ let n = D(m.value.find((e) => e.node.id === t.dataset.treeId)?.parentId), r = n ? t.offsetTop - (n.offsetTop + n.offsetHeight) : 0;
7056
+ t.style.transition = "none", t.style.transform = `translateY(${-r}px)`, t.style.opacity = "0", t.style.pointerEvents = "none", t.getBoundingClientRect(), t.style.transition = jl, t.style.transform = "", t.style.opacity = "";
7057
+ }, M = (e) => {
7058
+ let t = e;
7059
+ t.style.transition = "", t.style.pointerEvents = "";
7060
+ }, F = i(() => {
7061
+ let e = m.value.map((e) => e.node.id);
7062
+ return p.value && e.includes(p.value) ? p.value : l.value && e.includes(l.value) ? l.value : e[0] ?? null;
7063
+ }), I = (e) => {
7064
+ u.value = u.value.includes(e) ? u.value.filter((t) => t !== e) : [...u.value, e];
7065
+ }, L = async (e) => {
7066
+ p.value = e, await v(), Array.from(d.value?.querySelectorAll("[data-tree-id]") ?? []).find((t) => t.dataset.treeId === e)?.focus();
7067
+ }, z = (e) => {
7068
+ let t = m.value.findIndex((e) => e.node.id === F.value), n = m.value[t];
7069
+ if (!n) return;
7070
+ let r = (e) => {
7071
+ e && L(e.node.id);
7072
+ };
7073
+ switch (e.key) {
7074
+ case "ArrowDown":
7075
+ r(m.value[t + 1]);
7076
+ break;
7077
+ case "ArrowUp":
7078
+ r(m.value[t - 1]);
7079
+ break;
7080
+ case "Home":
7081
+ r(m.value[0]);
7082
+ break;
7083
+ case "End":
7084
+ r(m.value[m.value.length - 1]);
7085
+ break;
7086
+ case "ArrowRight":
7087
+ n.expandable && !n.expanded ? I(n.node.id) : n.expanded && r(m.value[t + 1]);
7088
+ break;
7089
+ case "ArrowLeft":
7090
+ n.expanded ? I(n.node.id) : n.parentId && L(n.parentId);
7091
+ break;
7092
+ case "Enter":
7093
+ case " ":
7094
+ l.value = n.node.id;
7095
+ break;
7096
+ default: return;
7097
+ }
7098
+ e.preventDefault();
7099
+ };
7100
+ return (n, i) => (w(), s("div", {
7101
+ ref_key: "root",
7102
+ ref: d,
7103
+ role: "tree",
7104
+ "aria-label": t.label,
7105
+ class: "relative isolate",
7106
+ onKeydown: z
7107
+ }, [c("div", {
7108
+ ref_key: "highlightElement",
7109
+ ref: _,
7110
+ "aria-hidden": "true",
7111
+ class: y(["pointer-events-none absolute top-0 left-0 -z-10 w-full rounded-md bg-cornflower-blue-50 motion-reduce:transition-none", [b[C.value], h.value ? "opacity-100" : "opacity-0"]]),
7112
+ style: x({
7113
+ transform: `translateY(${g.value.top}px)`,
7114
+ height: `${g.value.height}px`
7115
+ }),
7116
+ "data-tree-highlight": ""
7117
+ }, null, 6), f(r, {
7118
+ tag: "div",
7119
+ role: "group",
7120
+ "leave-active-class": "invisible absolute w-full transition-none!",
7121
+ "move-class": "transition-transform duration-200 ease-in-out-strong motion-reduce:transition-none",
7122
+ onEnter: j,
7123
+ onAfterEnter: M,
7124
+ onEnterCancelled: M
7125
+ }, {
7126
+ default: B(() => [(w(!0), s(e, null, E(m.value, (e) => (w(), s("div", {
7127
+ key: e.node.id,
7128
+ role: "treeitem",
7129
+ "aria-level": e.depth + 1,
7130
+ "aria-posinset": e.position,
7131
+ "aria-setsize": e.setSize,
7132
+ "aria-selected": l.value === e.node.id,
7133
+ "aria-expanded": e.expandable ? e.expanded : void 0,
7134
+ tabindex: e.node.id === F.value ? 0 : -1,
7135
+ "data-tree-id": e.node.id,
7136
+ class: y(["group mb-0.5 flex cursor-pointer items-center gap-1.5 rounded-md py-1 pr-2 small-normal transition-colors duration-150 select-none focus-visible:outline-2 focus-visible:outline-cornflower-blue-500 motion-reduce:transition-none", l.value === e.node.id ? "text-cornflower-blue-600" : "text-gray-800 hover:bg-gray-100"]),
7137
+ style: x({ paddingLeft: `${e.depth * Al + 4}px` }),
7138
+ onClick: (t) => l.value = e.node.id,
7139
+ onFocus: (t) => p.value = e.node.id
7140
+ }, [
7141
+ e.expandable ? (w(), s("button", {
7142
+ key: 0,
7143
+ type: "button",
7144
+ tabindex: "-1",
7145
+ "aria-hidden": "true",
7146
+ class: "flex size-5 shrink-0 cursor-pointer items-center justify-center text-gray-400 transition-colors duration-150 group-hover:text-gray-600 hover:text-gray-600 motion-reduce:transition-none",
7147
+ onClick: U((t) => I(e.node.id), ["stop"])
7148
+ }, [f(Z, {
7149
+ name: "chevron-down",
7150
+ class: y(["extra-small-normal transition-transform duration-150 ease-out motion-reduce:transition-none", !e.expanded && "-rotate-90"])
7151
+ }, null, 8, ["class"])], 8, Dl)) : (w(), s("span", Ol)),
7152
+ e.node.icon ? (w(), a(Z, {
7153
+ key: 2,
7154
+ name: e.node.icon,
7155
+ class: y(["shrink-0 transition-colors duration-150 motion-reduce:transition-none", l.value === e.node.id ? "text-cornflower-blue-600" : "text-gray-600"])
7156
+ }, null, 8, ["name", "class"])) : o("", !0),
7157
+ c("span", kl, k(e.node.label), 1)
7158
+ ], 46, El))), 128))]),
7159
+ _: 1
7160
+ })], 40, Tl));
7161
+ }
7162
+ }), Nl = [
6985
7163
  "src",
6986
7164
  "loop",
6987
7165
  "aria-label"
6988
- ], El = {
7166
+ ], Pl = {
6989
7167
  key: 0,
6990
7168
  role: "alert",
6991
7169
  class: "absolute inset-0 flex flex-col items-center justify-center gap-3 rounded-lg bg-gray-900 text-white"
6992
- }, Dl = { class: "heading-4" }, Ol = { class: "max-w-xs truncate extra-small-normal text-white/50" }, kl = {
7170
+ }, Fl = { class: "heading-4" }, Il = { class: "max-w-xs truncate extra-small-normal text-white/50" }, Ll = {
6993
7171
  key: 0,
6994
7172
  class: "pointer-events-none absolute inset-0 flex items-center justify-center"
6995
- }, Al = {
7173
+ }, Rl = {
6996
7174
  class: "flex size-16 items-center justify-center rounded-full bg-black/50 text-white",
6997
7175
  "aria-hidden": "true"
6998
- }, jl = [
7176
+ }, zl = [
6999
7177
  "aria-label",
7000
7178
  "aria-valuemax",
7001
7179
  "aria-valuenow",
7002
7180
  "aria-valuetext"
7003
- ], Ml = { class: "flex items-center gap-2" }, Nl = ["aria-label"], Pl = { class: "extra-small-normal text-white/70 tabular-nums" }, Fl = ["aria-label"], Il = ["aria-label"], Ll = /* @__PURE__ */ p({
7181
+ ], Bl = { class: "flex items-center gap-2" }, Vl = ["aria-label"], Hl = { class: "extra-small-normal text-white/70 tabular-nums" }, Ul = ["aria-label"], Wl = ["aria-label"], Gl = /* @__PURE__ */ p({
7004
7182
  __name: "VideoPlayer",
7005
7183
  props: {
7006
7184
  src: {},
@@ -7041,27 +7219,27 @@ var Ks = { render: Gs }, qs = { class: "flex items-center justify-end self-stret
7041
7219
  R(g, (e) => {
7042
7220
  !e && H && (H = !1, V.value = null);
7043
7221
  });
7044
- let G = (e) => {
7222
+ let te = (e) => {
7045
7223
  if (!z.value) return 0;
7046
7224
  let t = z.value.getBoundingClientRect(), n = "touches" in e ? e.touches[0]?.clientX ?? 0 : e.clientX;
7047
7225
  return Math.max(0, Math.min(1, (n - t.left) / t.width));
7048
- }, te = (e) => {
7049
- V.value = G(e);
7050
- }, K = (e) => {
7226
+ }, ne = (e) => {
7227
+ V.value = te(e);
7228
+ }, G = (e) => {
7051
7229
  if (!m.value) return;
7052
7230
  let t = Math.max(0, Math.min(m.value, p.value + e));
7053
7231
  H = !0, p.value = t;
7054
- }, ne = (e) => {
7055
- e.key === "ArrowLeft" ? (e.preventDefault(), K(-5)) : e.key === "ArrowRight" ? (e.preventDefault(), K(5)) : e.key === "Home" ? (e.preventDefault(), K(-Infinity)) : e.key === "End" && (e.preventDefault(), K(Infinity));
7056
- }, q = () => {
7232
+ }, re = (e) => {
7233
+ e.key === "ArrowLeft" ? (e.preventDefault(), G(-5)) : e.key === "ArrowRight" ? (e.preventDefault(), G(5)) : e.key === "Home" ? (e.preventDefault(), G(-Infinity)) : e.key === "End" && (e.preventDefault(), G(Infinity));
7234
+ }, K = () => {
7057
7235
  V.value !== null && m.value ? (H = !0, p.value = V.value * m.value) : V.value = null;
7058
7236
  };
7059
7237
  return it(document, "mousemove", (e) => {
7060
- V.value !== null && (V.value = G(e));
7238
+ V.value !== null && (V.value = te(e));
7061
7239
  }), it(document, "mouseup", () => {
7062
- V.value !== null && q();
7240
+ V.value !== null && K();
7063
7241
  }), it(document, "touchend", () => {
7064
- V.value !== null && q();
7242
+ V.value !== null && K();
7065
7243
  }), S(() => {
7066
7244
  r.autoplay && (u.value = !0), r.muted && (h.value = !0);
7067
7245
  }), t({
@@ -7081,15 +7259,15 @@ var Ks = { render: Gs }, qs = { class: "flex items-center justify-end self-stret
7081
7259
  "aria-label": a.value.video,
7082
7260
  onError: r[0] ||= (e) => E.value = !0,
7083
7261
  onClick: r[1] ||= (t) => e.showControls === !1 ? void 0 : I()
7084
- }, null, 42, Tl),
7085
- E.value ? (w(), s("div", El, [
7262
+ }, null, 42, Nl),
7263
+ E.value ? (w(), s("div", Pl, [
7086
7264
  f(Z, {
7087
7265
  name: "circle-exclamation",
7088
7266
  class: "text-3xl text-red-400",
7089
7267
  "aria-hidden": "true"
7090
7268
  }),
7091
- c("p", Dl, k(a.value.error), 1),
7092
- c("p", Ol, k(e.src), 1)
7269
+ c("p", Fl, k(a.value.error), 1),
7270
+ c("p", Il, k(e.src), 1)
7093
7271
  ])) : o("", !0),
7094
7272
  f(n, {
7095
7273
  "enter-active-class": "transition-opacity duration-100 ease-out",
@@ -7097,7 +7275,7 @@ var Ks = { render: Gs }, qs = { class: "flex items-center justify-end self-stret
7097
7275
  "enter-from-class": "opacity-0",
7098
7276
  "leave-to-class": "opacity-0"
7099
7277
  }, {
7100
- default: B(() => [e.showControls !== !1 && D.value && !E.value ? (w(), s("div", kl, [c("div", Al, [f(Z, {
7278
+ default: B(() => [e.showControls !== !1 && D.value && !E.value ? (w(), s("div", Ll, [c("div", Rl, [f(Z, {
7101
7279
  name: D.value,
7102
7280
  class: "text-2xl"
7103
7281
  }, null, 8, ["name"])])])) : o("", !0)]),
@@ -7118,14 +7296,14 @@ var Ks = { render: Gs }, qs = { class: "flex items-center justify-end self-stret
7118
7296
  "aria-valuetext": F(W.value),
7119
7297
  tabindex: "0",
7120
7298
  class: "relative h-1.5 cursor-pointer rounded-full bg-white/20",
7121
- onMousedown: te,
7122
- onTouchstart: U(te, ["prevent"]),
7123
- onKeydown: ne,
7299
+ onMousedown: ne,
7300
+ onTouchstart: U(ne, ["prevent"]),
7301
+ onKeydown: re,
7124
7302
  onTouchmove: r[2] ||= U((e) => {
7125
- V.value !== null && (V.value = G(e));
7303
+ V.value !== null && (V.value = te(e));
7126
7304
  }, ["prevent"]),
7127
7305
  onTouchend: r[3] ||= () => {
7128
- V.value !== null && q();
7306
+ V.value !== null && K();
7129
7307
  }
7130
7308
  }, [
7131
7309
  c("div", {
@@ -7140,7 +7318,7 @@ var Ks = { render: Gs }, qs = { class: "flex items-center justify-end self-stret
7140
7318
  class: "absolute top-1/2 size-3 -translate-1/2 rounded-full bg-sapphire-100 opacity-0 shadow-sm transition-opacity group-hover/player:opacity-100",
7141
7319
  style: x({ left: `${ee.value}%` })
7142
7320
  }, null, 4)
7143
- ], 40, jl), c("div", Ml, [
7321
+ ], 40, zl), c("div", Bl, [
7144
7322
  c("button", {
7145
7323
  type: "button",
7146
7324
  "aria-label": A(u) ? a.value.pause : a.value.play,
@@ -7150,8 +7328,8 @@ var Ks = { render: Gs }, qs = { class: "flex items-center justify-end self-stret
7150
7328
  name: A(u) ? "pause" : "play",
7151
7329
  class: "text-sm",
7152
7330
  "aria-hidden": "true"
7153
- }, null, 8, ["name"])], 8, Nl),
7154
- c("span", Pl, k(F(W.value)) + " / " + k(F(A(m))), 1),
7331
+ }, null, 8, ["name"])], 8, Vl),
7332
+ c("span", Hl, k(F(W.value)) + " / " + k(F(A(m))), 1),
7155
7333
  r[7] ||= c("div", { class: "flex-1" }, null, -1),
7156
7334
  c("button", {
7157
7335
  type: "button",
@@ -7162,7 +7340,7 @@ var Ks = { render: Gs }, qs = { class: "flex items-center justify-end self-stret
7162
7340
  name: A(h) ? "volume-xmark" : "volume-high",
7163
7341
  class: "text-sm",
7164
7342
  "aria-hidden": "true"
7165
- }, null, 8, ["name"])], 8, Fl),
7343
+ }, null, 8, ["name"])], 8, Ul),
7166
7344
  c("button", {
7167
7345
  type: "button",
7168
7346
  "aria-label": A(_) ? a.value.exitFullscreen : a.value.enterFullscreen,
@@ -7172,10 +7350,10 @@ var Ks = { render: Gs }, qs = { class: "flex items-center justify-end self-stret
7172
7350
  name: A(_) ? "compress" : "expand",
7173
7351
  class: "text-sm",
7174
7352
  "aria-hidden": "true"
7175
- }, null, 8, ["name"])], 8, Il)
7353
+ }, null, 8, ["name"])], 8, Wl)
7176
7354
  ])], 4)) : o("", !0)
7177
7355
  ], 32));
7178
7356
  }
7179
7357
  });
7180
7358
  //#endregion
7181
- export { ct as EVENT_CONFIG_MAP, lt as NexxtAnimatedNumber, ht as NexxtAnnouncement, bt as NexxtAvatar, St as NexxtButton, en as NexxtCalendar, rn as NexxtCard, on as NexxtCheckbox, sn as NexxtChip, Sn as NexxtColorPicker, Un as NexxtCombobox, Hn as NexxtComboboxPanel, Xn as NexxtContactInfoCard, lr as NexxtCountrySelect, pr as NexxtCustomerJourneyTile, mr as NexxtDatePicker, Sr as NexxtDoughnutChart, Dr as NexxtDropdownButton, Kr as NexxtEditorAlignItems, ri as NexxtEditorBorder, oi as NexxtEditorColor, ci as NexxtEditorFlexDirection, mi as NexxtEditorGap, Ei as NexxtEditorHeadingType, Oi as NexxtEditorJustifyContent, ea as NexxtEditorPadding, aa as NexxtEditorRadius, $ as NexxtEditorSettingsBlock, sa as NexxtEditorTextAlign, ua as NexxtEnergyLabel, Q as NexxtFloatingPanel, ma as NexxtHeader, Z as NexxtIcon, ga as NexxtInfoBlock, kn as NexxtInputField, wa as NexxtInputSelect, Ea as NexxtLoader, Aa as NexxtModal, Ma as NexxtModalFooter, co as NexxtPageCard, mo as NexxtPageContainer, ro as NexxtPageMockup, go as NexxtPageTypeTile, _o as NexxtPagination, xo as NexxtPresenceIndicator, No as NexxtProgressBar, Wo as NexxtPropertyCard, zo as NexxtPropertyListing, os as NexxtReferenceCard, Ci as NexxtSelect, ls as NexxtSelectableOption, hs as NexxtSidebarMenuItem, _s as NexxtSkeleton, xs as NexxtSkeletonPropertyCard, Xr as NexxtSlider, ws as NexxtSocialIcons, Os as NexxtSocialMediaCustomTemplate, Ls as NexxtSocialMediaTemplate, Zs as NexxtSocialMediaType, uc as NexxtSource, _t as NexxtSquircle, dc as NexxtStepperHeader, kr as NexxtSwitch, vc as NexxtTabbedContent, jc as NexxtTable, mc as NexxtTabs, Gc as NexxtTemplateCard, Yc as NexxtTextarea, nl as NexxtTimelineEvent, ll as NexxtTimelineLanes, ml as NexxtTimelinePhaseblock, gl as NexxtTimelineTrack, Cl as NexxtToast, wl as NexxtToaster, Wr as NexxtToggleGroup, Ir as NexxtTooltip, Ll as NexxtVideoPlayer, X as PHASE_MAP, sc as SOURCE_MAP };
7359
+ export { ct as EVENT_CONFIG_MAP, lt as NexxtAnimatedNumber, ht as NexxtAnnouncement, bt as NexxtAvatar, St as NexxtButton, en as NexxtCalendar, rn as NexxtCard, on as NexxtCheckbox, sn as NexxtChip, Sn as NexxtColorPicker, Un as NexxtCombobox, Hn as NexxtComboboxPanel, Xn as NexxtContactInfoCard, lr as NexxtCountrySelect, pr as NexxtCustomerJourneyTile, mr as NexxtDatePicker, Sr as NexxtDoughnutChart, Dr as NexxtDropdownButton, Kr as NexxtEditorAlignItems, ri as NexxtEditorBorder, oi as NexxtEditorColor, ci as NexxtEditorFlexDirection, mi as NexxtEditorGap, Ei as NexxtEditorHeadingType, Oi as NexxtEditorJustifyContent, ea as NexxtEditorPadding, aa as NexxtEditorRadius, $ as NexxtEditorSettingsBlock, sa as NexxtEditorTextAlign, ua as NexxtEnergyLabel, Q as NexxtFloatingPanel, ma as NexxtHeader, Z as NexxtIcon, ga as NexxtInfoBlock, kn as NexxtInputField, wa as NexxtInputSelect, Ea as NexxtLoader, Aa as NexxtModal, Ma as NexxtModalFooter, co as NexxtPageCard, mo as NexxtPageContainer, ro as NexxtPageMockup, go as NexxtPageTypeTile, _o as NexxtPagination, xo as NexxtPresenceIndicator, No as NexxtProgressBar, Wo as NexxtPropertyCard, zo as NexxtPropertyListing, os as NexxtReferenceCard, Ci as NexxtSelect, ls as NexxtSelectableOption, hs as NexxtSidebarMenuItem, _s as NexxtSkeleton, xs as NexxtSkeletonPropertyCard, Xr as NexxtSlider, ws as NexxtSocialIcons, Os as NexxtSocialMediaCustomTemplate, Ls as NexxtSocialMediaTemplate, Zs as NexxtSocialMediaType, uc as NexxtSource, _t as NexxtSquircle, dc as NexxtStepperHeader, kr as NexxtSwitch, vc as NexxtTabbedContent, jc as NexxtTable, mc as NexxtTabs, Gc as NexxtTemplateCard, Yc as NexxtTextarea, nl as NexxtTimelineEvent, ll as NexxtTimelineLanes, ml as NexxtTimelinePhaseblock, gl as NexxtTimelineTrack, Cl as NexxtToast, wl as NexxtToaster, Wr as NexxtToggleGroup, Ir as NexxtTooltip, Ml as NexxtTree, Gl as NexxtVideoPlayer, X as PHASE_MAP, sc as SOURCE_MAP };
package/dist/nuxt.js CHANGED
@@ -49,6 +49,7 @@ var s = {
49
49
  NexxtTimelineLanes: "components/molecules/TimelineLanes/TimelineLanes.vue",
50
50
  NexxtToast: "components/molecules/Toast/Toast.vue",
51
51
  NexxtToggleGroup: "components/molecules/ToggleGroup/ToggleGroup.vue",
52
+ NexxtTree: "components/molecules/Tree/Tree.vue",
52
53
  NexxtCalendar: "components/organisms/Calendar/Calendar.vue",
53
54
  NexxtColorPicker: "components/organisms/ColorPicker/ColorPicker.vue",
54
55
  NexxtCombobox: "components/organisms/Combobox/Combobox.vue",
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@nexxtmove/ui",
3
3
  "type": "module",
4
- "version": "1.26.0",
4
+ "version": "1.27.0",
5
5
  "exports": {
6
6
  ".": {
7
7
  "types": "./dist/index.d.ts",
@@ -0,0 +1,342 @@
1
+ <script lang="ts" setup>
2
+ import { computed, nextTick, onMounted, ref, useTemplateRef, watch } from 'vue'
3
+ import { useResizeObserver } from '@vueuse/core'
4
+ import NexxtIcon, { type NexxtIconName } from '../../atoms/Icon/Icon.vue'
5
+
6
+ export interface NexxtTreeNode {
7
+ /** Unique within the tree; this is what `selected` and `collapsed` hold. */
8
+ id: string
9
+ label: string
10
+ icon?: NexxtIconName
11
+ /** A node with children gets a chevron and can be folded. */
12
+ children?: NexxtTreeNode[]
13
+ }
14
+
15
+ export interface NexxtTreeProps {
16
+ /** The top-level nodes; nest `children` for deeper levels. */
17
+ nodes: NexxtTreeNode[]
18
+ /** Accessible name for the tree, e.g. the heading above it. */
19
+ label: string
20
+ }
21
+
22
+ interface Row {
23
+ node: NexxtTreeNode
24
+ depth: number
25
+ parentId: string | null
26
+ expandable: boolean
27
+ expanded: boolean
28
+ position: number
29
+ setSize: number
30
+ }
31
+
32
+ defineOptions({ name: 'NexxtTree' })
33
+
34
+ const props = defineProps<NexxtTreeProps>()
35
+
36
+ const selected = defineModel<string | null>('selected', { default: null })
37
+ /** Ids of the nodes whose children are hidden. Tracking the collapsed ones makes "everything open" the default. */
38
+ const collapsed = defineModel<string[]>('collapsed', { default: () => [] })
39
+
40
+ const INDENT = 22
41
+
42
+ const root = useTemplateRef<HTMLElement>('root')
43
+ const focusedId = ref<string | null>(null)
44
+
45
+ // The visible rows in document order; children of a collapsed node are left out
46
+ const rows = computed(() => {
47
+ const result: Row[] = []
48
+
49
+ const walk = (nodes: NexxtTreeNode[], depth: number, parentId: string | null) => {
50
+ nodes.forEach((node, index) => {
51
+ const expandable = !!node.children?.length
52
+ const expanded = expandable && !collapsed.value.includes(node.id)
53
+
54
+ result.push({
55
+ node,
56
+ depth,
57
+ parentId,
58
+ expandable,
59
+ expanded,
60
+ position: index + 1,
61
+ setSize: nodes.length,
62
+ })
63
+
64
+ if (expanded) walk(node.children!, depth + 1, node.id)
65
+ })
66
+ }
67
+
68
+ walk(props.nodes, 0, null)
69
+
70
+ return result
71
+ })
72
+
73
+ // A row is only highlighted while it is actually on screen; collapsing its parent fades the
74
+ // highlight out rather than dropping it, so reopening slides it back from the same spot
75
+ const selectionVisible = computed(
76
+ () => !!selected.value && rows.value.some((row) => row.node.id === selected.value),
77
+ )
78
+
79
+ /** Geometry of the moving highlight, relative to the tree container. */
80
+ const highlight = ref({ top: 0, height: 0 })
81
+ const highlightElement = useTemplateRef<HTMLElement>('highlightElement')
82
+
83
+ /**
84
+ * How the highlight travels to its next spot. A selection arrives on the row it was clicked on,
85
+ * so it decelerates; a row change drags the highlight along with a row that is sliding under
86
+ * the `move` transition, so it has to use that transition's duration and curve or the two come
87
+ * apart on screen. The first render and layout corrections do not travel at all.
88
+ */
89
+ const MOTION = {
90
+ none: '',
91
+ select: 'transition-[transform,opacity] duration-200 ease-out-strong',
92
+ move: 'transition-[transform,opacity] duration-200 ease-in-out-strong',
93
+ }
94
+
95
+ const motion = ref<keyof typeof MOTION>('none')
96
+
97
+ /**
98
+ * Matched on the dataset rather than in a selector, so an id never needs escaping. A row that is
99
+ * leaving keeps its id for another beat, but it is out of the flow — `absolute` marks it — and
100
+ * folding the same node straight back open would otherwise find that stale one.
101
+ */
102
+ const rowElement = (id: string | null | undefined) =>
103
+ Array.from(root.value?.querySelectorAll<HTMLElement>('[data-tree-id]') ?? []).find(
104
+ (element) => element.dataset.treeId === id && !element.classList.contains('absolute'),
105
+ )
106
+
107
+ const updateHighlight = async () => {
108
+ await nextTick()
109
+ if (!root.value || !selectionVisible.value) return
110
+
111
+ const element = rowElement(selected.value)
112
+
113
+ if (element) highlight.value = { top: element.offsetTop, height: element.offsetHeight }
114
+ }
115
+
116
+ /**
117
+ * Reposition without animating, for the first render and for layout corrections. Reading the
118
+ * geometry makes the browser commit the new position while the transition is still off; the
119
+ * class coming back after that has nothing left to animate. Deliberately not `requestAnimationFrame`:
120
+ * that never fires while the tree sits in a hidden tab, which would leave it unable to animate.
121
+ */
122
+ const jumpTo = async (reposition: () => Promise<void>) => {
123
+ const previous = motion.value
124
+ motion.value = 'none'
125
+
126
+ await reposition()
127
+ await nextTick()
128
+ root.value?.getBoundingClientRect()
129
+
130
+ motion.value = previous
131
+ }
132
+
133
+ // Rows above the selection can appear or disappear, so every row change is a reposition too
134
+ watch([selected, rows], ([id], [previousId]) => {
135
+ motion.value = id === previousId ? 'move' : 'select'
136
+ updateHighlight()
137
+ })
138
+
139
+ // A resize corrects the geometry — late webfonts, a narrower panel — so it never animates.
140
+ // Expanding and collapsing resizes the tree too, and there the highlight is mid-flight: forcing
141
+ // the geometry then cancels its transition and teleports it to the row's end position.
142
+ useResizeObserver(root, () => {
143
+ if (highlightElement.value?.getAnimations().length) return
144
+
145
+ jumpTo(updateHighlight)
146
+ })
147
+
148
+ onMounted(() =>
149
+ jumpTo(async () => {
150
+ await updateHighlight()
151
+ // Fallback metrics put the rows at another height, which would animate once the real font lands
152
+ await document.fonts?.ready
153
+ await updateHighlight()
154
+ }),
155
+ )
156
+
157
+ /**
158
+ * Children unfold from under their parent. They are inserted at their final spot, where the rows
159
+ * below them are still sliding down to make room, so each one starts stacked on the parent's
160
+ * bottom edge and travels to its slot on the same duration and curve as the `move` transition.
161
+ * Fading in is what keeps the stack readable: on the way out from under the parent the children
162
+ * cover each other and the rows below, so the label only comes up over the last stretch, once
163
+ * everything has all but landed. Inline rather than in `enter-active-class`, because the two
164
+ * properties need their own timing.
165
+ */
166
+ const ENTER_TRANSITION = 'transform 200ms var(--ease-in-out-strong), opacity 80ms linear 120ms'
167
+
168
+ const onEnter = (element: Element) => {
169
+ const row = element as HTMLElement
170
+ if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) return
171
+
172
+ const parent = rowElement(
173
+ rows.value.find((item) => item.node.id === row.dataset.treeId)?.parentId,
174
+ )
175
+ const travel = parent ? row.offsetTop - (parent.offsetTop + parent.offsetHeight) : 0
176
+
177
+ row.style.transition = 'none'
178
+ row.style.transform = `translateY(${-travel}px)`
179
+ row.style.opacity = '0'
180
+ // Nothing to click on a row that is still on its way and cannot be read yet
181
+ row.style.pointerEvents = 'none'
182
+
183
+ // Commits the start of the travel while the transition is off, so only the way back animates
184
+ row.getBoundingClientRect()
185
+
186
+ row.style.transition = ENTER_TRANSITION
187
+ row.style.transform = ''
188
+ row.style.opacity = ''
189
+ }
190
+
191
+ const onEnterDone = (element: Element) => {
192
+ const row = element as HTMLElement
193
+
194
+ row.style.transition = ''
195
+ row.style.pointerEvents = ''
196
+ }
197
+
198
+ // One tab stop for the whole tree: the focused row, else the selected one, else the first
199
+ const tabStopId = computed(() => {
200
+ const ids = rows.value.map((row) => row.node.id)
201
+
202
+ if (focusedId.value && ids.includes(focusedId.value)) return focusedId.value
203
+ if (selected.value && ids.includes(selected.value)) return selected.value
204
+
205
+ return ids[0] ?? null
206
+ })
207
+
208
+ const toggle = (id: string) => {
209
+ collapsed.value = collapsed.value.includes(id)
210
+ ? collapsed.value.filter((item) => item !== id)
211
+ : [...collapsed.value, id]
212
+ }
213
+
214
+ const focusRow = async (id: string) => {
215
+ focusedId.value = id
216
+ await nextTick()
217
+
218
+ // Matched on the dataset rather than in a selector, so an id never needs escaping
219
+ Array.from(root.value?.querySelectorAll<HTMLElement>('[data-tree-id]') ?? [])
220
+ .find((element) => element.dataset.treeId === id)
221
+ ?.focus()
222
+ }
223
+
224
+ const onKeydown = (event: KeyboardEvent) => {
225
+ const index = rows.value.findIndex((row) => row.node.id === tabStopId.value)
226
+ const row = rows.value[index]
227
+ if (!row) return
228
+
229
+ const move = (target: Row | undefined) => {
230
+ if (target) focusRow(target.node.id)
231
+ }
232
+
233
+ switch (event.key) {
234
+ case 'ArrowDown':
235
+ move(rows.value[index + 1])
236
+ break
237
+ case 'ArrowUp':
238
+ move(rows.value[index - 1])
239
+ break
240
+ case 'Home':
241
+ move(rows.value[0])
242
+ break
243
+ case 'End':
244
+ move(rows.value[rows.value.length - 1])
245
+ break
246
+ case 'ArrowRight':
247
+ if (row.expandable && !row.expanded) toggle(row.node.id)
248
+ else if (row.expanded) move(rows.value[index + 1])
249
+ break
250
+ case 'ArrowLeft':
251
+ if (row.expanded) toggle(row.node.id)
252
+ else if (row.parentId) focusRow(row.parentId)
253
+ break
254
+ case 'Enter':
255
+ case ' ':
256
+ selected.value = row.node.id
257
+ break
258
+ default:
259
+ return
260
+ }
261
+
262
+ event.preventDefault()
263
+ }
264
+ </script>
265
+
266
+ <template>
267
+ <div ref="root" role="tree" :aria-label="label" class="relative isolate" @keydown="onKeydown">
268
+ <!-- One highlight for the whole tree: selecting another row slides it instead of
269
+ repainting two backgrounds. Behind the rows, so their hover still reads. -->
270
+ <div
271
+ ref="highlightElement"
272
+ aria-hidden="true"
273
+ class="pointer-events-none absolute top-0 left-0 -z-10 w-full rounded-md bg-cornflower-blue-50 motion-reduce:transition-none"
274
+ :class="[MOTION[motion], selectionVisible ? 'opacity-100' : 'opacity-0']"
275
+ :style="{ transform: `translateY(${highlight.top}px)`, height: `${highlight.height}px` }"
276
+ data-tree-highlight
277
+ />
278
+
279
+ <!-- A `group` keeps the tree/treeitem relation intact now that the rows sit one level
280
+ deeper. Leaving rows go absolute so the remaining ones slide up right away, and go
281
+ invisible with them: out of the flow they stack on the first one's spot while the rows
282
+ below slide over them, which read as a flash of doubled labels. Folding away is not a
283
+ departure worth animating — the rows sliding up under the parent tell the story. -->
284
+ <TransitionGroup
285
+ tag="div"
286
+ role="group"
287
+ leave-active-class="invisible absolute w-full transition-none!"
288
+ move-class="transition-transform duration-200 ease-in-out-strong motion-reduce:transition-none"
289
+ @enter="onEnter"
290
+ @after-enter="onEnterDone"
291
+ @enter-cancelled="onEnterDone"
292
+ >
293
+ <!-- Every row keeps `small-normal`: switching the selected one to semibold widened its
294
+ label, which made the text jump. Colour carries the selection instead. -->
295
+ <div
296
+ v-for="row in rows"
297
+ :key="row.node.id"
298
+ role="treeitem"
299
+ :aria-level="row.depth + 1"
300
+ :aria-posinset="row.position"
301
+ :aria-setsize="row.setSize"
302
+ :aria-selected="selected === row.node.id"
303
+ :aria-expanded="row.expandable ? row.expanded : undefined"
304
+ :tabindex="row.node.id === tabStopId ? 0 : -1"
305
+ :data-tree-id="row.node.id"
306
+ class="group mb-0.5 flex cursor-pointer items-center gap-1.5 rounded-md py-1 pr-2 small-normal transition-colors duration-150 select-none focus-visible:outline-2 focus-visible:outline-cornflower-blue-500 motion-reduce:transition-none"
307
+ :class="
308
+ selected === row.node.id ? 'text-cornflower-blue-600' : 'text-gray-800 hover:bg-gray-100'
309
+ "
310
+ :style="{ paddingLeft: `${row.depth * INDENT + 4}px` }"
311
+ @click="selected = row.node.id"
312
+ @focus="focusedId = row.node.id"
313
+ >
314
+ <!-- Out of the tab order: the arrow keys expand and collapse for keyboard users.
315
+ Stays grey on the selected row: the chevron is a control, not part of the label. -->
316
+ <button
317
+ v-if="row.expandable"
318
+ type="button"
319
+ tabindex="-1"
320
+ aria-hidden="true"
321
+ class="flex size-5 shrink-0 cursor-pointer items-center justify-center text-gray-400 transition-colors duration-150 group-hover:text-gray-600 hover:text-gray-600 motion-reduce:transition-none"
322
+ @click.stop="toggle(row.node.id)"
323
+ >
324
+ <NexxtIcon
325
+ name="chevron-down"
326
+ class="extra-small-normal transition-transform duration-150 ease-out motion-reduce:transition-none"
327
+ :class="!row.expanded && '-rotate-90'"
328
+ />
329
+ </button>
330
+ <span v-else class="size-5 shrink-0" />
331
+
332
+ <NexxtIcon
333
+ v-if="row.node.icon"
334
+ :name="row.node.icon"
335
+ class="shrink-0 transition-colors duration-150 motion-reduce:transition-none"
336
+ :class="selected === row.node.id ? 'text-cornflower-blue-600' : 'text-gray-600'"
337
+ />
338
+ <span class="truncate">{{ row.node.label }}</span>
339
+ </div>
340
+ </TransitionGroup>
341
+ </div>
342
+ </template>
@@ -46,6 +46,7 @@
46
46
  "NexxtTimelineLanes": "components/molecules/TimelineLanes/TimelineLanes.vue",
47
47
  "NexxtToast": "components/molecules/Toast/Toast.vue",
48
48
  "NexxtToggleGroup": "components/molecules/ToggleGroup/ToggleGroup.vue",
49
+ "NexxtTree": "components/molecules/Tree/Tree.vue",
49
50
  "NexxtCalendar": "components/organisms/Calendar/Calendar.vue",
50
51
  "NexxtColorPicker": "components/organisms/ColorPicker/ColorPicker.vue",
51
52
  "NexxtCombobox": "components/organisms/Combobox/Combobox.vue",
package/src/index.ts CHANGED
@@ -18,6 +18,7 @@ export type {
18
18
  NexxtPageCardSlots,
19
19
  } from './components/molecules/PageCard/PageCard.vue'
20
20
  export type { NexxtPageMockupType } from './components/molecules/PageMockup/PageMockup.vue'
21
+ export type { NexxtTreeNode, NexxtTreeProps } from './components/molecules/Tree/Tree.vue'
21
22
  export type {
22
23
  EditorAlignItemsMessages,
23
24
  EditorAlignItemsValue,
@@ -137,4 +138,5 @@ export { default as NexxtToast } from './components/molecules/Toast/Toast.vue'
137
138
  export { default as NexxtToaster } from './components/organisms/Toaster/Toaster.vue'
138
139
  export { default as NexxtToggleGroup } from './components/molecules/ToggleGroup/ToggleGroup.vue'
139
140
  export { default as NexxtTooltip } from './components/atoms/Tooltip/Tooltip.vue'
141
+ export { default as NexxtTree } from './components/molecules/Tree/Tree.vue'
140
142
  export { default as NexxtVideoPlayer } from './components/organisms/VideoPlayer/VideoPlayer.vue'
@@ -49,4 +49,10 @@
49
49
 
50
50
  --spacing-8xl: 100rem;
51
51
  --spacing-full-width: 116rem; /* 1920px (full hd) minus the sidebar: max app width */
52
+
53
+ /* Motion easings. `ease-out-strong` decelerates hard: things that arrive or
54
+ settle into place. `ease-in-out-strong` for elements that move from one
55
+ spot to another. */
56
+ --ease-out-strong: cubic-bezier(0.23, 1, 0.32, 1);
57
+ --ease-in-out-strong: cubic-bezier(0.77, 0, 0.175, 1);
52
58
  }