@irisui/utils 0.1.1 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -63,6 +63,7 @@ function useReducedMotion() {
63
63
 
64
64
  // src/use-controllable-state.ts
65
65
  var import_react2 = require("react");
66
+ var useIsomorphicLayoutEffect = typeof window === "undefined" ? import_react2.useEffect : import_react2.useLayoutEffect;
66
67
  function useControllableState({
67
68
  value,
68
69
  defaultValue,
@@ -72,16 +73,19 @@ function useControllableState({
72
73
  const isControlled = value !== void 0;
73
74
  const current = isControlled ? value : uncontrolled;
74
75
  const onChangeRef = (0, import_react2.useRef)(onChange);
75
- (0, import_react2.useEffect)(() => {
76
+ const currentRef = (0, import_react2.useRef)(current);
77
+ useIsomorphicLayoutEffect(() => {
76
78
  onChangeRef.current = onChange;
79
+ currentRef.current = current;
77
80
  });
78
- const currentRef = (0, import_react2.useRef)(current);
79
- currentRef.current = current;
80
81
  const setValue = (0, import_react2.useCallback)(
81
82
  (next) => {
82
83
  const resolved = typeof next === "function" ? next(currentRef.current) : next;
83
84
  if (Object.is(resolved, currentRef.current)) return;
84
- if (!isControlled) setUncontrolled(resolved);
85
+ if (!isControlled) {
86
+ currentRef.current = resolved;
87
+ setUncontrolled(resolved);
88
+ }
85
89
  onChangeRef.current?.(resolved);
86
90
  },
87
91
  [isControlled]
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/cn.ts","../src/use-reduced-motion.ts","../src/use-controllable-state.ts"],"sourcesContent":["export { cn, type ClassValue } from \"./cn\";\nexport { useReducedMotion } from \"./use-reduced-motion\";\nexport {\n useControllableState,\n type UseControllableStateParams,\n} from \"./use-controllable-state\";\n","import { clsx, type ClassValue } from \"clsx\";\nimport { extendTailwindMerge } from \"tailwind-merge\";\n\n/**\n * tailwind-merge only knows Tailwind's default scale. The custom theme keys\n * declared in `@irisui/tokens/tailwind.css` are registered here so that, for\n * instance, `text-h3` is seen as a font size and not as a text color.\n */\nconst twMerge = extendTailwindMerge({\n extend: {\n theme: {\n text: [\"xs\", \"sm\", \"md\", \"lg\", \"h1\", \"h2\", \"h3\"],\n \"font-weight\": [\"regular\", \"medium\", \"bold\"],\n tracking: [\"display\"],\n radius: [\"pill\", \"field\", \"check\", \"card\", \"panel\"],\n blur: [\"glass\", \"small\"],\n shadow: [\"glass\", \"rim\", \"holo\"],\n animate: [\"holo\"],\n },\n },\n});\n\n/** Merges class names, resolving conflicting Tailwind utilities (last one wins). */\nexport function cn(...inputs: ClassValue[]): string {\n return twMerge(clsx(inputs));\n}\n\nexport type { ClassValue };\n","import { useSyncExternalStore } from \"react\";\n\nconst QUERY = \"(prefers-reduced-motion: reduce)\";\n\nfunction subscribe(onChange: () => void): () => void {\n const media = window.matchMedia(QUERY);\n media.addEventListener(\"change\", onChange);\n return () => media.removeEventListener(\"change\", onChange);\n}\n\nconst getSnapshot = (): boolean => window.matchMedia(QUERY).matches;\nconst getServerSnapshot = (): boolean => false;\n\n/** `true` when the user asked the system for less motion. Always `false` on the server. */\nexport function useReducedMotion(): boolean {\n return useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);\n}\n","import { useCallback, useEffect, useRef, useState } from \"react\";\n\nexport interface UseControllableStateParams<T> {\n /** Controlled value. When defined, the component is controlled. */\n value?: T | undefined;\n /** Initial value when uncontrolled. */\n defaultValue: T;\n /** Called with the next value, in both controlled and uncontrolled modes. */\n onChange?: ((value: T) => void) | undefined;\n}\n\ntype SetStateFn<T> = (next: T | ((prev: T) => T)) => void;\n\n/**\n * State that can be either controlled (`value` + `onChange`) or uncontrolled\n * (`defaultValue`), following the Radix / shadcn convention.\n */\nexport function useControllableState<T>({\n value,\n defaultValue,\n onChange,\n}: UseControllableStateParams<T>): [T, SetStateFn<T>] {\n const [uncontrolled, setUncontrolled] = useState<T>(defaultValue);\n const isControlled = value !== undefined;\n const current = isControlled ? value : uncontrolled;\n\n const onChangeRef = useRef(onChange);\n useEffect(() => {\n onChangeRef.current = onChange;\n });\n\n const currentRef = useRef(current);\n currentRef.current = current;\n\n const setValue = useCallback<SetStateFn<T>>(\n (next) => {\n const resolved =\n typeof next === \"function\" ? (next as (prev: T) => T)(currentRef.current) : next;\n if (Object.is(resolved, currentRef.current)) return;\n if (!isControlled) setUncontrolled(resolved);\n onChangeRef.current?.(resolved);\n },\n [isControlled],\n );\n\n return [current, setValue];\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA,kBAAsC;AACtC,4BAAoC;AAOpC,IAAM,cAAU,2CAAoB;AAAA,EAClC,QAAQ;AAAA,IACN,OAAO;AAAA,MACL,MAAM,CAAC,MAAM,MAAM,MAAM,MAAM,MAAM,MAAM,IAAI;AAAA,MAC/C,eAAe,CAAC,WAAW,UAAU,MAAM;AAAA,MAC3C,UAAU,CAAC,SAAS;AAAA,MACpB,QAAQ,CAAC,QAAQ,SAAS,SAAS,QAAQ,OAAO;AAAA,MAClD,MAAM,CAAC,SAAS,OAAO;AAAA,MACvB,QAAQ,CAAC,SAAS,OAAO,MAAM;AAAA,MAC/B,SAAS,CAAC,MAAM;AAAA,IAClB;AAAA,EACF;AACF,CAAC;AAGM,SAAS,MAAM,QAA8B;AAClD,SAAO,YAAQ,kBAAK,MAAM,CAAC;AAC7B;;;ACzBA,mBAAqC;AAErC,IAAM,QAAQ;AAEd,SAAS,UAAU,UAAkC;AACnD,QAAM,QAAQ,OAAO,WAAW,KAAK;AACrC,QAAM,iBAAiB,UAAU,QAAQ;AACzC,SAAO,MAAM,MAAM,oBAAoB,UAAU,QAAQ;AAC3D;AAEA,IAAM,cAAc,MAAe,OAAO,WAAW,KAAK,EAAE;AAC5D,IAAM,oBAAoB,MAAe;AAGlC,SAAS,mBAA4B;AAC1C,aAAO,mCAAqB,WAAW,aAAa,iBAAiB;AACvE;;;AChBA,IAAAA,gBAAyD;AAiBlD,SAAS,qBAAwB;AAAA,EACtC;AAAA,EACA;AAAA,EACA;AACF,GAAsD;AACpD,QAAM,CAAC,cAAc,eAAe,QAAI,wBAAY,YAAY;AAChE,QAAM,eAAe,UAAU;AAC/B,QAAM,UAAU,eAAe,QAAQ;AAEvC,QAAM,kBAAc,sBAAO,QAAQ;AACnC,+BAAU,MAAM;AACd,gBAAY,UAAU;AAAA,EACxB,CAAC;AAED,QAAM,iBAAa,sBAAO,OAAO;AACjC,aAAW,UAAU;AAErB,QAAM,eAAW;AAAA,IACf,CAAC,SAAS;AACR,YAAM,WACJ,OAAO,SAAS,aAAc,KAAwB,WAAW,OAAO,IAAI;AAC9E,UAAI,OAAO,GAAG,UAAU,WAAW,OAAO,EAAG;AAC7C,UAAI,CAAC,aAAc,iBAAgB,QAAQ;AAC3C,kBAAY,UAAU,QAAQ;AAAA,IAChC;AAAA,IACA,CAAC,YAAY;AAAA,EACf;AAEA,SAAO,CAAC,SAAS,QAAQ;AAC3B;","names":["import_react"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/cn.ts","../src/use-reduced-motion.ts","../src/use-controllable-state.ts"],"sourcesContent":["export { cn, type ClassValue } from \"./cn\";\nexport { useReducedMotion } from \"./use-reduced-motion\";\nexport { useControllableState, type UseControllableStateParams } from \"./use-controllable-state\";\n","import { clsx, type ClassValue } from \"clsx\";\nimport { extendTailwindMerge } from \"tailwind-merge\";\n\n/**\n * tailwind-merge only knows Tailwind's default scale. The custom theme keys\n * declared in `@irisui/tokens/tailwind.css` are registered here so that, for\n * instance, `text-h3` is seen as a font size and not as a text color.\n */\nconst twMerge = extendTailwindMerge({\n extend: {\n theme: {\n text: [\"xs\", \"sm\", \"md\", \"lg\", \"h1\", \"h2\", \"h3\"],\n \"font-weight\": [\"regular\", \"medium\", \"bold\"],\n tracking: [\"display\"],\n radius: [\"pill\", \"field\", \"check\", \"card\", \"panel\"],\n blur: [\"glass\", \"small\"],\n shadow: [\"glass\", \"rim\", \"holo\"],\n animate: [\"holo\"],\n },\n },\n});\n\n/** Merges class names, resolving conflicting Tailwind utilities (last one wins). */\nexport function cn(...inputs: ClassValue[]): string {\n return twMerge(clsx(inputs));\n}\n\nexport type { ClassValue };\n","import { useSyncExternalStore } from \"react\";\n\nconst QUERY = \"(prefers-reduced-motion: reduce)\";\n\nfunction subscribe(onChange: () => void): () => void {\n const media = window.matchMedia(QUERY);\n media.addEventListener(\"change\", onChange);\n return () => media.removeEventListener(\"change\", onChange);\n}\n\nconst getSnapshot = (): boolean => window.matchMedia(QUERY).matches;\nconst getServerSnapshot = (): boolean => false;\n\n/** `true` when the user asked the system for less motion. Always `false` on the server. */\nexport function useReducedMotion(): boolean {\n return useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);\n}\n","import { useCallback, useEffect, useLayoutEffect, useRef, useState } from \"react\";\n\nconst useIsomorphicLayoutEffect = typeof window === \"undefined\" ? useEffect : useLayoutEffect;\n\nexport interface UseControllableStateParams<T> {\n /** Controlled value. When defined, the component is controlled. */\n value?: T | undefined;\n /** Initial value when uncontrolled. */\n defaultValue: T;\n /** Called with the next value, in both controlled and uncontrolled modes. */\n onChange?: ((value: T) => void) | undefined;\n}\n\ntype SetStateFn<T> = (next: T | ((prev: T) => T)) => void;\n\n/**\n * State that can be either controlled (`value` + `onChange`) or uncontrolled\n * (`defaultValue`), following the Radix / shadcn convention.\n */\nexport function useControllableState<T>({\n value,\n defaultValue,\n onChange,\n}: UseControllableStateParams<T>): [T, SetStateFn<T>] {\n const [uncontrolled, setUncontrolled] = useState<T>(defaultValue);\n const isControlled = value !== undefined;\n const current = isControlled ? value : uncontrolled;\n\n // Refs are synced after render (not during it) so `setValue` stays stable.\n const onChangeRef = useRef(onChange);\n const currentRef = useRef(current);\n useIsomorphicLayoutEffect(() => {\n onChangeRef.current = onChange;\n currentRef.current = current;\n });\n\n const setValue = useCallback<SetStateFn<T>>(\n (next) => {\n const resolved =\n typeof next === \"function\" ? (next as (prev: T) => T)(currentRef.current) : next;\n if (Object.is(resolved, currentRef.current)) return;\n if (!isControlled) {\n // Several updates in one event must chain, before the next render syncs the ref.\n currentRef.current = resolved;\n setUncontrolled(resolved);\n }\n onChangeRef.current?.(resolved);\n },\n [isControlled],\n );\n\n return [current, setValue];\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA,kBAAsC;AACtC,4BAAoC;AAOpC,IAAM,cAAU,2CAAoB;AAAA,EAClC,QAAQ;AAAA,IACN,OAAO;AAAA,MACL,MAAM,CAAC,MAAM,MAAM,MAAM,MAAM,MAAM,MAAM,IAAI;AAAA,MAC/C,eAAe,CAAC,WAAW,UAAU,MAAM;AAAA,MAC3C,UAAU,CAAC,SAAS;AAAA,MACpB,QAAQ,CAAC,QAAQ,SAAS,SAAS,QAAQ,OAAO;AAAA,MAClD,MAAM,CAAC,SAAS,OAAO;AAAA,MACvB,QAAQ,CAAC,SAAS,OAAO,MAAM;AAAA,MAC/B,SAAS,CAAC,MAAM;AAAA,IAClB;AAAA,EACF;AACF,CAAC;AAGM,SAAS,MAAM,QAA8B;AAClD,SAAO,YAAQ,kBAAK,MAAM,CAAC;AAC7B;;;ACzBA,mBAAqC;AAErC,IAAM,QAAQ;AAEd,SAAS,UAAU,UAAkC;AACnD,QAAM,QAAQ,OAAO,WAAW,KAAK;AACrC,QAAM,iBAAiB,UAAU,QAAQ;AACzC,SAAO,MAAM,MAAM,oBAAoB,UAAU,QAAQ;AAC3D;AAEA,IAAM,cAAc,MAAe,OAAO,WAAW,KAAK,EAAE;AAC5D,IAAM,oBAAoB,MAAe;AAGlC,SAAS,mBAA4B;AAC1C,aAAO,mCAAqB,WAAW,aAAa,iBAAiB;AACvE;;;AChBA,IAAAA,gBAA0E;AAE1E,IAAM,4BAA4B,OAAO,WAAW,cAAc,0BAAY;AAiBvE,SAAS,qBAAwB;AAAA,EACtC;AAAA,EACA;AAAA,EACA;AACF,GAAsD;AACpD,QAAM,CAAC,cAAc,eAAe,QAAI,wBAAY,YAAY;AAChE,QAAM,eAAe,UAAU;AAC/B,QAAM,UAAU,eAAe,QAAQ;AAGvC,QAAM,kBAAc,sBAAO,QAAQ;AACnC,QAAM,iBAAa,sBAAO,OAAO;AACjC,4BAA0B,MAAM;AAC9B,gBAAY,UAAU;AACtB,eAAW,UAAU;AAAA,EACvB,CAAC;AAED,QAAM,eAAW;AAAA,IACf,CAAC,SAAS;AACR,YAAM,WACJ,OAAO,SAAS,aAAc,KAAwB,WAAW,OAAO,IAAI;AAC9E,UAAI,OAAO,GAAG,UAAU,WAAW,OAAO,EAAG;AAC7C,UAAI,CAAC,cAAc;AAEjB,mBAAW,UAAU;AACrB,wBAAgB,QAAQ;AAAA,MAC1B;AACA,kBAAY,UAAU,QAAQ;AAAA,IAChC;AAAA,IACA,CAAC,YAAY;AAAA,EACf;AAEA,SAAO,CAAC,SAAS,QAAQ;AAC3B;","names":["import_react"]}
package/dist/index.js CHANGED
@@ -35,7 +35,8 @@ function useReducedMotion() {
35
35
  }
36
36
 
37
37
  // src/use-controllable-state.ts
38
- import { useCallback, useEffect, useRef, useState } from "react";
38
+ import { useCallback, useEffect, useLayoutEffect, useRef, useState } from "react";
39
+ var useIsomorphicLayoutEffect = typeof window === "undefined" ? useEffect : useLayoutEffect;
39
40
  function useControllableState({
40
41
  value,
41
42
  defaultValue,
@@ -45,16 +46,19 @@ function useControllableState({
45
46
  const isControlled = value !== void 0;
46
47
  const current = isControlled ? value : uncontrolled;
47
48
  const onChangeRef = useRef(onChange);
48
- useEffect(() => {
49
+ const currentRef = useRef(current);
50
+ useIsomorphicLayoutEffect(() => {
49
51
  onChangeRef.current = onChange;
52
+ currentRef.current = current;
50
53
  });
51
- const currentRef = useRef(current);
52
- currentRef.current = current;
53
54
  const setValue = useCallback(
54
55
  (next) => {
55
56
  const resolved = typeof next === "function" ? next(currentRef.current) : next;
56
57
  if (Object.is(resolved, currentRef.current)) return;
57
- if (!isControlled) setUncontrolled(resolved);
58
+ if (!isControlled) {
59
+ currentRef.current = resolved;
60
+ setUncontrolled(resolved);
61
+ }
58
62
  onChangeRef.current?.(resolved);
59
63
  },
60
64
  [isControlled]
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/cn.ts","../src/use-reduced-motion.ts","../src/use-controllable-state.ts"],"sourcesContent":["import { clsx, type ClassValue } from \"clsx\";\nimport { extendTailwindMerge } from \"tailwind-merge\";\n\n/**\n * tailwind-merge only knows Tailwind's default scale. The custom theme keys\n * declared in `@irisui/tokens/tailwind.css` are registered here so that, for\n * instance, `text-h3` is seen as a font size and not as a text color.\n */\nconst twMerge = extendTailwindMerge({\n extend: {\n theme: {\n text: [\"xs\", \"sm\", \"md\", \"lg\", \"h1\", \"h2\", \"h3\"],\n \"font-weight\": [\"regular\", \"medium\", \"bold\"],\n tracking: [\"display\"],\n radius: [\"pill\", \"field\", \"check\", \"card\", \"panel\"],\n blur: [\"glass\", \"small\"],\n shadow: [\"glass\", \"rim\", \"holo\"],\n animate: [\"holo\"],\n },\n },\n});\n\n/** Merges class names, resolving conflicting Tailwind utilities (last one wins). */\nexport function cn(...inputs: ClassValue[]): string {\n return twMerge(clsx(inputs));\n}\n\nexport type { ClassValue };\n","import { useSyncExternalStore } from \"react\";\n\nconst QUERY = \"(prefers-reduced-motion: reduce)\";\n\nfunction subscribe(onChange: () => void): () => void {\n const media = window.matchMedia(QUERY);\n media.addEventListener(\"change\", onChange);\n return () => media.removeEventListener(\"change\", onChange);\n}\n\nconst getSnapshot = (): boolean => window.matchMedia(QUERY).matches;\nconst getServerSnapshot = (): boolean => false;\n\n/** `true` when the user asked the system for less motion. Always `false` on the server. */\nexport function useReducedMotion(): boolean {\n return useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);\n}\n","import { useCallback, useEffect, useRef, useState } from \"react\";\n\nexport interface UseControllableStateParams<T> {\n /** Controlled value. When defined, the component is controlled. */\n value?: T | undefined;\n /** Initial value when uncontrolled. */\n defaultValue: T;\n /** Called with the next value, in both controlled and uncontrolled modes. */\n onChange?: ((value: T) => void) | undefined;\n}\n\ntype SetStateFn<T> = (next: T | ((prev: T) => T)) => void;\n\n/**\n * State that can be either controlled (`value` + `onChange`) or uncontrolled\n * (`defaultValue`), following the Radix / shadcn convention.\n */\nexport function useControllableState<T>({\n value,\n defaultValue,\n onChange,\n}: UseControllableStateParams<T>): [T, SetStateFn<T>] {\n const [uncontrolled, setUncontrolled] = useState<T>(defaultValue);\n const isControlled = value !== undefined;\n const current = isControlled ? value : uncontrolled;\n\n const onChangeRef = useRef(onChange);\n useEffect(() => {\n onChangeRef.current = onChange;\n });\n\n const currentRef = useRef(current);\n currentRef.current = current;\n\n const setValue = useCallback<SetStateFn<T>>(\n (next) => {\n const resolved =\n typeof next === \"function\" ? (next as (prev: T) => T)(currentRef.current) : next;\n if (Object.is(resolved, currentRef.current)) return;\n if (!isControlled) setUncontrolled(resolved);\n onChangeRef.current?.(resolved);\n },\n [isControlled],\n );\n\n return [current, setValue];\n}\n"],"mappings":";;;AAAA,SAAS,YAA6B;AACtC,SAAS,2BAA2B;AAOpC,IAAM,UAAU,oBAAoB;AAAA,EAClC,QAAQ;AAAA,IACN,OAAO;AAAA,MACL,MAAM,CAAC,MAAM,MAAM,MAAM,MAAM,MAAM,MAAM,IAAI;AAAA,MAC/C,eAAe,CAAC,WAAW,UAAU,MAAM;AAAA,MAC3C,UAAU,CAAC,SAAS;AAAA,MACpB,QAAQ,CAAC,QAAQ,SAAS,SAAS,QAAQ,OAAO;AAAA,MAClD,MAAM,CAAC,SAAS,OAAO;AAAA,MACvB,QAAQ,CAAC,SAAS,OAAO,MAAM;AAAA,MAC/B,SAAS,CAAC,MAAM;AAAA,IAClB;AAAA,EACF;AACF,CAAC;AAGM,SAAS,MAAM,QAA8B;AAClD,SAAO,QAAQ,KAAK,MAAM,CAAC;AAC7B;;;ACzBA,SAAS,4BAA4B;AAErC,IAAM,QAAQ;AAEd,SAAS,UAAU,UAAkC;AACnD,QAAM,QAAQ,OAAO,WAAW,KAAK;AACrC,QAAM,iBAAiB,UAAU,QAAQ;AACzC,SAAO,MAAM,MAAM,oBAAoB,UAAU,QAAQ;AAC3D;AAEA,IAAM,cAAc,MAAe,OAAO,WAAW,KAAK,EAAE;AAC5D,IAAM,oBAAoB,MAAe;AAGlC,SAAS,mBAA4B;AAC1C,SAAO,qBAAqB,WAAW,aAAa,iBAAiB;AACvE;;;AChBA,SAAS,aAAa,WAAW,QAAQ,gBAAgB;AAiBlD,SAAS,qBAAwB;AAAA,EACtC;AAAA,EACA;AAAA,EACA;AACF,GAAsD;AACpD,QAAM,CAAC,cAAc,eAAe,IAAI,SAAY,YAAY;AAChE,QAAM,eAAe,UAAU;AAC/B,QAAM,UAAU,eAAe,QAAQ;AAEvC,QAAM,cAAc,OAAO,QAAQ;AACnC,YAAU,MAAM;AACd,gBAAY,UAAU;AAAA,EACxB,CAAC;AAED,QAAM,aAAa,OAAO,OAAO;AACjC,aAAW,UAAU;AAErB,QAAM,WAAW;AAAA,IACf,CAAC,SAAS;AACR,YAAM,WACJ,OAAO,SAAS,aAAc,KAAwB,WAAW,OAAO,IAAI;AAC9E,UAAI,OAAO,GAAG,UAAU,WAAW,OAAO,EAAG;AAC7C,UAAI,CAAC,aAAc,iBAAgB,QAAQ;AAC3C,kBAAY,UAAU,QAAQ;AAAA,IAChC;AAAA,IACA,CAAC,YAAY;AAAA,EACf;AAEA,SAAO,CAAC,SAAS,QAAQ;AAC3B;","names":[]}
1
+ {"version":3,"sources":["../src/cn.ts","../src/use-reduced-motion.ts","../src/use-controllable-state.ts"],"sourcesContent":["import { clsx, type ClassValue } from \"clsx\";\nimport { extendTailwindMerge } from \"tailwind-merge\";\n\n/**\n * tailwind-merge only knows Tailwind's default scale. The custom theme keys\n * declared in `@irisui/tokens/tailwind.css` are registered here so that, for\n * instance, `text-h3` is seen as a font size and not as a text color.\n */\nconst twMerge = extendTailwindMerge({\n extend: {\n theme: {\n text: [\"xs\", \"sm\", \"md\", \"lg\", \"h1\", \"h2\", \"h3\"],\n \"font-weight\": [\"regular\", \"medium\", \"bold\"],\n tracking: [\"display\"],\n radius: [\"pill\", \"field\", \"check\", \"card\", \"panel\"],\n blur: [\"glass\", \"small\"],\n shadow: [\"glass\", \"rim\", \"holo\"],\n animate: [\"holo\"],\n },\n },\n});\n\n/** Merges class names, resolving conflicting Tailwind utilities (last one wins). */\nexport function cn(...inputs: ClassValue[]): string {\n return twMerge(clsx(inputs));\n}\n\nexport type { ClassValue };\n","import { useSyncExternalStore } from \"react\";\n\nconst QUERY = \"(prefers-reduced-motion: reduce)\";\n\nfunction subscribe(onChange: () => void): () => void {\n const media = window.matchMedia(QUERY);\n media.addEventListener(\"change\", onChange);\n return () => media.removeEventListener(\"change\", onChange);\n}\n\nconst getSnapshot = (): boolean => window.matchMedia(QUERY).matches;\nconst getServerSnapshot = (): boolean => false;\n\n/** `true` when the user asked the system for less motion. Always `false` on the server. */\nexport function useReducedMotion(): boolean {\n return useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);\n}\n","import { useCallback, useEffect, useLayoutEffect, useRef, useState } from \"react\";\n\nconst useIsomorphicLayoutEffect = typeof window === \"undefined\" ? useEffect : useLayoutEffect;\n\nexport interface UseControllableStateParams<T> {\n /** Controlled value. When defined, the component is controlled. */\n value?: T | undefined;\n /** Initial value when uncontrolled. */\n defaultValue: T;\n /** Called with the next value, in both controlled and uncontrolled modes. */\n onChange?: ((value: T) => void) | undefined;\n}\n\ntype SetStateFn<T> = (next: T | ((prev: T) => T)) => void;\n\n/**\n * State that can be either controlled (`value` + `onChange`) or uncontrolled\n * (`defaultValue`), following the Radix / shadcn convention.\n */\nexport function useControllableState<T>({\n value,\n defaultValue,\n onChange,\n}: UseControllableStateParams<T>): [T, SetStateFn<T>] {\n const [uncontrolled, setUncontrolled] = useState<T>(defaultValue);\n const isControlled = value !== undefined;\n const current = isControlled ? value : uncontrolled;\n\n // Refs are synced after render (not during it) so `setValue` stays stable.\n const onChangeRef = useRef(onChange);\n const currentRef = useRef(current);\n useIsomorphicLayoutEffect(() => {\n onChangeRef.current = onChange;\n currentRef.current = current;\n });\n\n const setValue = useCallback<SetStateFn<T>>(\n (next) => {\n const resolved =\n typeof next === \"function\" ? (next as (prev: T) => T)(currentRef.current) : next;\n if (Object.is(resolved, currentRef.current)) return;\n if (!isControlled) {\n // Several updates in one event must chain, before the next render syncs the ref.\n currentRef.current = resolved;\n setUncontrolled(resolved);\n }\n onChangeRef.current?.(resolved);\n },\n [isControlled],\n );\n\n return [current, setValue];\n}\n"],"mappings":";;;AAAA,SAAS,YAA6B;AACtC,SAAS,2BAA2B;AAOpC,IAAM,UAAU,oBAAoB;AAAA,EAClC,QAAQ;AAAA,IACN,OAAO;AAAA,MACL,MAAM,CAAC,MAAM,MAAM,MAAM,MAAM,MAAM,MAAM,IAAI;AAAA,MAC/C,eAAe,CAAC,WAAW,UAAU,MAAM;AAAA,MAC3C,UAAU,CAAC,SAAS;AAAA,MACpB,QAAQ,CAAC,QAAQ,SAAS,SAAS,QAAQ,OAAO;AAAA,MAClD,MAAM,CAAC,SAAS,OAAO;AAAA,MACvB,QAAQ,CAAC,SAAS,OAAO,MAAM;AAAA,MAC/B,SAAS,CAAC,MAAM;AAAA,IAClB;AAAA,EACF;AACF,CAAC;AAGM,SAAS,MAAM,QAA8B;AAClD,SAAO,QAAQ,KAAK,MAAM,CAAC;AAC7B;;;ACzBA,SAAS,4BAA4B;AAErC,IAAM,QAAQ;AAEd,SAAS,UAAU,UAAkC;AACnD,QAAM,QAAQ,OAAO,WAAW,KAAK;AACrC,QAAM,iBAAiB,UAAU,QAAQ;AACzC,SAAO,MAAM,MAAM,oBAAoB,UAAU,QAAQ;AAC3D;AAEA,IAAM,cAAc,MAAe,OAAO,WAAW,KAAK,EAAE;AAC5D,IAAM,oBAAoB,MAAe;AAGlC,SAAS,mBAA4B;AAC1C,SAAO,qBAAqB,WAAW,aAAa,iBAAiB;AACvE;;;AChBA,SAAS,aAAa,WAAW,iBAAiB,QAAQ,gBAAgB;AAE1E,IAAM,4BAA4B,OAAO,WAAW,cAAc,YAAY;AAiBvE,SAAS,qBAAwB;AAAA,EACtC;AAAA,EACA;AAAA,EACA;AACF,GAAsD;AACpD,QAAM,CAAC,cAAc,eAAe,IAAI,SAAY,YAAY;AAChE,QAAM,eAAe,UAAU;AAC/B,QAAM,UAAU,eAAe,QAAQ;AAGvC,QAAM,cAAc,OAAO,QAAQ;AACnC,QAAM,aAAa,OAAO,OAAO;AACjC,4BAA0B,MAAM;AAC9B,gBAAY,UAAU;AACtB,eAAW,UAAU;AAAA,EACvB,CAAC;AAED,QAAM,WAAW;AAAA,IACf,CAAC,SAAS;AACR,YAAM,WACJ,OAAO,SAAS,aAAc,KAAwB,WAAW,OAAO,IAAI;AAC9E,UAAI,OAAO,GAAG,UAAU,WAAW,OAAO,EAAG;AAC7C,UAAI,CAAC,cAAc;AAEjB,mBAAW,UAAU;AACrB,wBAAgB,QAAQ;AAAA,MAC1B;AACA,kBAAY,UAAU,QAAQ;AAAA,IAChC;AAAA,IACA,CAAC,YAAY;AAAA,EACf;AAEA,SAAO,CAAC,SAAS,QAAQ;AAC3B;","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@irisui/utils",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "Shared utilities and hooks for the iris-ui library.",
5
5
  "license": "MIT",
6
6
  "type": "module",