@arqel-dev/hooks 0.8.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 @@
1
+ {"version":3,"sources":["../src/useBreakpoint.ts"],"names":[],"mappings":";;;AAWA,IAAM,OAAA,GAAoD;AAAA,EACxD,EAAE,IAAA,EAAM,KAAA,EAAO,GAAA,EAAK,IAAA,EAAK;AAAA,EACzB,EAAE,IAAA,EAAM,IAAA,EAAM,GAAA,EAAK,IAAA,EAAK;AAAA,EACxB,EAAE,IAAA,EAAM,IAAA,EAAM,GAAA,EAAK,IAAA,EAAK;AAAA,EACxB,EAAE,IAAA,EAAM,IAAA,EAAM,GAAA,EAAK,GAAA,EAAI;AAAA,EACvB,EAAE,IAAA,EAAM,IAAA,EAAM,GAAA,EAAK,CAAA;AACrB,CAAA;AAEA,SAAS,kBAAkB,KAAA,EAA2B;AACpD,EAAA,KAAA,MAAW,KAAK,OAAA,EAAS;AACvB,IAAA,IAAI,KAAA,IAAS,CAAA,CAAE,GAAA,EAAK,OAAO,CAAA,CAAE,IAAA;AAAA,EAC/B;AACA,EAAA,OAAO,IAAA;AACT;AAEO,SAAS,aAAA,GAA4B;AAC1C,EAAA,MAAM,CAAC,EAAA,EAAI,KAAK,CAAA,GAAI,SAAqB,IAAI,CAAA;AAE7C,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,IAAI,OAAO,WAAW,WAAA,EAAa;AAEnC,IAAA,MAAM,SAAS,MAAM,KAAA,CAAM,iBAAA,CAAkB,MAAA,CAAO,UAAU,CAAC,CAAA;AAC/D,IAAA,MAAA,EAAO;AACP,IAAA,MAAA,CAAO,gBAAA,CAAiB,UAAU,MAAM,CAAA;AACxC,IAAA,OAAO,MAAM,MAAA,CAAO,mBAAA,CAAoB,QAAA,EAAU,MAAM,CAAA;AAAA,EAC1D,CAAA,EAAG,EAAE,CAAA;AAEL,EAAA,OAAO,EAAA;AACT","file":"useBreakpoint.js","sourcesContent":["/**\n * `useBreakpoint` — current Tailwind v4 breakpoint via `matchMedia`.\n *\n * SSR-safe: returns `'sm'` until mount, then subscribes to media queries.\n * Defaults follow Tailwind v4 (sm 640, md 768, lg 1024, xl 1280, 2xl 1536).\n */\n\nimport { useEffect, useState } from 'react';\n\nexport type Breakpoint = 'sm' | 'md' | 'lg' | 'xl' | '2xl';\n\nconst QUERIES: Array<{ name: Breakpoint; min: number }> = [\n { name: '2xl', min: 1536 },\n { name: 'xl', min: 1280 },\n { name: 'lg', min: 1024 },\n { name: 'md', min: 768 },\n { name: 'sm', min: 0 },\n];\n\nfunction resolveBreakpoint(width: number): Breakpoint {\n for (const q of QUERIES) {\n if (width >= q.min) return q.name;\n }\n return 'sm';\n}\n\nexport function useBreakpoint(): Breakpoint {\n const [bp, setBp] = useState<Breakpoint>('sm');\n\n useEffect(() => {\n if (typeof window === 'undefined') return;\n\n const update = () => setBp(resolveBreakpoint(window.innerWidth));\n update();\n window.addEventListener('resize', update);\n return () => window.removeEventListener('resize', update);\n }, []);\n\n return bp;\n}\n"]}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * `useCanAccess` — UX-only ability check.
3
+ *
4
+ * Reads `auth.can[ability]` from Inertia shared props. When a record is
5
+ * supplied and the record itself exposes a `can: Record<string, boolean>`
6
+ * map (per-record abilities serialised server-side), that wins over the
7
+ * global map. Real enforcement happens server-side via Policies (ADR-017).
8
+ */
9
+ declare function useCanAccess(ability: string, record?: unknown): boolean;
10
+
11
+ export { useCanAccess };
@@ -0,0 +1,17 @@
1
+ import { usePage } from '@inertiajs/react';
2
+
3
+ // src/useCanAccess.ts
4
+ function useCanAccess(ability, record) {
5
+ const page = usePage();
6
+ const props = page.props;
7
+ const recordCan = record?.can;
8
+ if (recordCan && Object.hasOwn(recordCan, ability)) {
9
+ return recordCan[ability] === true;
10
+ }
11
+ const globalCan = props.auth?.can ?? {};
12
+ return Object.hasOwn(globalCan, ability) ? globalCan[ability] === true : false;
13
+ }
14
+
15
+ export { useCanAccess };
16
+ //# sourceMappingURL=useCanAccess.js.map
17
+ //# sourceMappingURL=useCanAccess.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/useCanAccess.ts"],"names":[],"mappings":";;;AAgBO,SAAS,YAAA,CAAa,SAAiB,MAAA,EAA2B;AACvE,EAAA,MAAM,OAAO,OAAA,EAAQ;AACrB,EAAA,MAAM,QAAQ,IAAA,CAAK,KAAA;AACnB,EAAA,MAAM,YAAa,MAAA,EAAmD,GAAA;AAEtE,EAAA,IAAI,SAAA,IAAa,MAAA,CAAO,MAAA,CAAO,SAAA,EAAW,OAAO,CAAA,EAAG;AAClD,IAAA,OAAO,SAAA,CAAU,OAAO,CAAA,KAAM,IAAA;AAAA,EAChC;AAEA,EAAA,MAAM,SAAA,GAAY,KAAA,CAAM,IAAA,EAAM,GAAA,IAAO,EAAC;AACtC,EAAA,OAAO,MAAA,CAAO,OAAO,SAAA,EAAW,OAAO,IAAI,SAAA,CAAU,OAAO,MAAM,IAAA,GAAO,KAAA;AAC3E","file":"useCanAccess.js","sourcesContent":["/**\n * `useCanAccess` — UX-only ability check.\n *\n * Reads `auth.can[ability]` from Inertia shared props. When a record is\n * supplied and the record itself exposes a `can: Record<string, boolean>`\n * map (per-record abilities serialised server-side), that wins over the\n * global map. Real enforcement happens server-side via Policies (ADR-017).\n */\n\nimport type { SharedProps } from '@arqel-dev/types/inertia';\nimport { usePage } from '@inertiajs/react';\n\ninterface RecordWithAbilities {\n can?: Record<string, boolean>;\n}\n\nexport function useCanAccess(ability: string, record?: unknown): boolean {\n const page = usePage();\n const props = page.props as unknown as SharedProps;\n const recordCan = (record as RecordWithAbilities | null | undefined)?.can;\n\n if (recordCan && Object.hasOwn(recordCan, ability)) {\n return recordCan[ability] === true;\n }\n\n const globalCan = props.auth?.can ?? {};\n return Object.hasOwn(globalCan, ability) ? globalCan[ability] === true : false;\n}\n"]}
@@ -0,0 +1,19 @@
1
+ import { FieldSchema } from '@arqel-dev/types/fields';
2
+
3
+ /**
4
+ * `useFieldDependencies` — debounced partial reload when source fields change.
5
+ *
6
+ * Phase 1 scope: detect changes on field values referenced by `dependsOn`
7
+ * descriptors, then `router.reload` with `only: ['fields.<dep>.options']`
8
+ * after a 300 ms debounce.
9
+ */
10
+
11
+ interface UseFieldDependenciesOptions {
12
+ fields: readonly FieldSchema[];
13
+ values: Record<string, unknown>;
14
+ debounceMs?: number;
15
+ onDependencyChange?: (fieldName: string) => void;
16
+ }
17
+ declare function useFieldDependencies(options: UseFieldDependenciesOptions): void;
18
+
19
+ export { type UseFieldDependenciesOptions, useFieldDependencies };
@@ -0,0 +1,35 @@
1
+ import { router } from '@inertiajs/react';
2
+ import { useRef, useEffect } from 'react';
3
+
4
+ // src/useFieldDependencies.ts
5
+ function useFieldDependencies(options) {
6
+ const { fields, values, debounceMs = 300, onDependencyChange } = options;
7
+ const lastValuesRef = useRef({});
8
+ const timersRef = useRef(/* @__PURE__ */ new Map());
9
+ useEffect(() => {
10
+ const timers = timersRef.current;
11
+ for (const field of fields) {
12
+ const deps = field.dependsOn;
13
+ if (!deps || deps.length === 0) continue;
14
+ const changed = deps.some((dep) => values[dep] !== lastValuesRef.current[dep]);
15
+ if (!changed) continue;
16
+ const existing = timers.get(field.name);
17
+ if (existing) clearTimeout(existing);
18
+ const handle = setTimeout(() => {
19
+ router.reload({ only: [`fields.${field.name}.options`] });
20
+ onDependencyChange?.(field.name);
21
+ timers.delete(field.name);
22
+ }, debounceMs);
23
+ timers.set(field.name, handle);
24
+ }
25
+ lastValuesRef.current = { ...values };
26
+ return () => {
27
+ for (const handle of timers.values()) clearTimeout(handle);
28
+ timers.clear();
29
+ };
30
+ }, [fields, values, debounceMs, onDependencyChange]);
31
+ }
32
+
33
+ export { useFieldDependencies };
34
+ //# sourceMappingURL=useFieldDependencies.js.map
35
+ //# sourceMappingURL=useFieldDependencies.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/useFieldDependencies.ts"],"names":[],"mappings":";;;;AAmBO,SAAS,qBAAqB,OAAA,EAA4C;AAC/E,EAAA,MAAM,EAAE,MAAA,EAAQ,MAAA,EAAQ,UAAA,GAAa,GAAA,EAAK,oBAAmB,GAAI,OAAA;AACjE,EAAA,MAAM,aAAA,GAAgB,MAAA,CAAgC,EAAE,CAAA;AACxD,EAAA,MAAM,SAAA,GAAY,MAAA,iBAAmD,IAAI,GAAA,EAAK,CAAA;AAE9E,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,MAAM,SAAS,SAAA,CAAU,OAAA;AAEzB,IAAA,KAAA,MAAW,SAAS,MAAA,EAAQ;AAC1B,MAAA,MAAM,OAAO,KAAA,CAAM,SAAA;AACnB,MAAA,IAAI,CAAC,IAAA,IAAQ,IAAA,CAAK,MAAA,KAAW,CAAA,EAAG;AAEhC,MAAA,MAAM,OAAA,GAAU,IAAA,CAAK,IAAA,CAAK,CAAC,GAAA,KAAQ,MAAA,CAAO,GAAG,CAAA,KAAM,aAAA,CAAc,OAAA,CAAQ,GAAG,CAAC,CAAA;AAC7E,MAAA,IAAI,CAAC,OAAA,EAAS;AAEd,MAAA,MAAM,QAAA,GAAW,MAAA,CAAO,GAAA,CAAI,KAAA,CAAM,IAAI,CAAA;AACtC,MAAA,IAAI,QAAA,eAAuB,QAAQ,CAAA;AAEnC,MAAA,MAAM,MAAA,GAAS,WAAW,MAAM;AAC9B,QAAA,MAAA,CAAO,MAAA,CAAO,EAAE,IAAA,EAAM,CAAC,UAAU,KAAA,CAAM,IAAI,CAAA,QAAA,CAAU,CAAA,EAAG,CAAA;AACxD,QAAA,kBAAA,GAAqB,MAAM,IAAI,CAAA;AAC/B,QAAA,MAAA,CAAO,MAAA,CAAO,MAAM,IAAI,CAAA;AAAA,MAC1B,GAAG,UAAU,CAAA;AAEb,MAAA,MAAA,CAAO,GAAA,CAAI,KAAA,CAAM,IAAA,EAAM,MAAM,CAAA;AAAA,IAC/B;AAEA,IAAA,aAAA,CAAc,OAAA,GAAU,EAAE,GAAG,MAAA,EAAO;AAEpC,IAAA,OAAO,MAAM;AACX,MAAA,KAAA,MAAW,MAAA,IAAU,MAAA,CAAO,MAAA,EAAO,eAAgB,MAAM,CAAA;AACzD,MAAA,MAAA,CAAO,KAAA,EAAM;AAAA,IACf,CAAA;AAAA,EACF,GAAG,CAAC,MAAA,EAAQ,MAAA,EAAQ,UAAA,EAAY,kBAAkB,CAAC,CAAA;AACrD","file":"useFieldDependencies.js","sourcesContent":["/**\n * `useFieldDependencies` — debounced partial reload when source fields change.\n *\n * Phase 1 scope: detect changes on field values referenced by `dependsOn`\n * descriptors, then `router.reload` with `only: ['fields.<dep>.options']`\n * after a 300 ms debounce.\n */\n\nimport type { FieldSchema } from '@arqel-dev/types/fields';\nimport { router } from '@inertiajs/react';\nimport { useEffect, useRef } from 'react';\n\nexport interface UseFieldDependenciesOptions {\n fields: readonly FieldSchema[];\n values: Record<string, unknown>;\n debounceMs?: number;\n onDependencyChange?: (fieldName: string) => void;\n}\n\nexport function useFieldDependencies(options: UseFieldDependenciesOptions): void {\n const { fields, values, debounceMs = 300, onDependencyChange } = options;\n const lastValuesRef = useRef<Record<string, unknown>>({});\n const timersRef = useRef<Map<string, ReturnType<typeof setTimeout>>>(new Map());\n\n useEffect(() => {\n const timers = timersRef.current;\n\n for (const field of fields) {\n const deps = field.dependsOn;\n if (!deps || deps.length === 0) continue;\n\n const changed = deps.some((dep) => values[dep] !== lastValuesRef.current[dep]);\n if (!changed) continue;\n\n const existing = timers.get(field.name);\n if (existing) clearTimeout(existing);\n\n const handle = setTimeout(() => {\n router.reload({ only: [`fields.${field.name}.options`] });\n onDependencyChange?.(field.name);\n timers.delete(field.name);\n }, debounceMs);\n\n timers.set(field.name, handle);\n }\n\n lastValuesRef.current = { ...values };\n\n return () => {\n for (const handle of timers.values()) clearTimeout(handle);\n timers.clear();\n };\n }, [fields, values, debounceMs, onDependencyChange]);\n}\n"]}
@@ -0,0 +1,17 @@
1
+ import { FlashPayload } from '@arqel-dev/types/inertia';
2
+
3
+ /**
4
+ * `useFlash` — read flash messages from Inertia shared props.
5
+ *
6
+ * Optional `onMessage` callback fires once per *new* message of each kind,
7
+ * allowing toast-library integrations without leaking effects elsewhere.
8
+ */
9
+
10
+ type FlashKind = 'success' | 'error' | 'info' | 'warning';
11
+ interface UseFlashOptions {
12
+ onMessage?: (kind: FlashKind, message: string) => void;
13
+ }
14
+ type UseFlashResult = FlashPayload;
15
+ declare function useFlash(options?: UseFlashOptions): UseFlashResult;
16
+
17
+ export { type FlashKind, type UseFlashOptions, type UseFlashResult, useFlash };
@@ -0,0 +1,38 @@
1
+ import { usePage } from '@inertiajs/react';
2
+ import { useRef, useEffect } from 'react';
3
+
4
+ // src/useFlash.ts
5
+ var KINDS = ["success", "error", "info", "warning"];
6
+ var EMPTY_FLASH = {
7
+ success: null,
8
+ error: null,
9
+ info: null,
10
+ warning: null
11
+ };
12
+ function useFlash(options = {}) {
13
+ const page = usePage();
14
+ const props = page.props;
15
+ const flash = props.flash ?? EMPTY_FLASH;
16
+ const seenRef = useRef({
17
+ success: null,
18
+ error: null,
19
+ info: null,
20
+ warning: null
21
+ });
22
+ const { onMessage } = options;
23
+ useEffect(() => {
24
+ if (!onMessage) return;
25
+ for (const kind of KINDS) {
26
+ const value = flash[kind];
27
+ if (value && value !== seenRef.current[kind]) {
28
+ seenRef.current[kind] = value;
29
+ onMessage(kind, value);
30
+ }
31
+ }
32
+ }, [flash, onMessage]);
33
+ return flash;
34
+ }
35
+
36
+ export { useFlash };
37
+ //# sourceMappingURL=useFlash.js.map
38
+ //# sourceMappingURL=useFlash.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/useFlash.ts"],"names":[],"mappings":";;;;AAmBA,IAAM,KAAA,GAAqB,CAAC,SAAA,EAAW,OAAA,EAAS,QAAQ,SAAS,CAAA;AAEjE,IAAM,WAAA,GAA4B;AAAA,EAChC,OAAA,EAAS,IAAA;AAAA,EACT,KAAA,EAAO,IAAA;AAAA,EACP,IAAA,EAAM,IAAA;AAAA,EACN,OAAA,EAAS;AACX,CAAA;AAEO,SAAS,QAAA,CAAS,OAAA,GAA2B,EAAC,EAAmB;AACtE,EAAA,MAAM,OAAO,OAAA,EAAQ;AACrB,EAAA,MAAM,QAAQ,IAAA,CAAK,KAAA;AACnB,EAAA,MAAM,KAAA,GAAQ,MAAM,KAAA,IAAS,WAAA;AAC7B,EAAA,MAAM,UAAU,MAAA,CAAyC;AAAA,IACvD,OAAA,EAAS,IAAA;AAAA,IACT,KAAA,EAAO,IAAA;AAAA,IACP,IAAA,EAAM,IAAA;AAAA,IACN,OAAA,EAAS;AAAA,GACV,CAAA;AAED,EAAA,MAAM,EAAE,WAAU,GAAI,OAAA;AAEtB,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,IAAI,CAAC,SAAA,EAAW;AAChB,IAAA,KAAA,MAAW,QAAQ,KAAA,EAAO;AACxB,MAAA,MAAM,KAAA,GAAQ,MAAM,IAAI,CAAA;AACxB,MAAA,IAAI,KAAA,IAAS,KAAA,KAAU,OAAA,CAAQ,OAAA,CAAQ,IAAI,CAAA,EAAG;AAC5C,QAAA,OAAA,CAAQ,OAAA,CAAQ,IAAI,CAAA,GAAI,KAAA;AACxB,QAAA,SAAA,CAAU,MAAM,KAAK,CAAA;AAAA,MACvB;AAAA,IACF;AAAA,EACF,CAAA,EAAG,CAAC,KAAA,EAAO,SAAS,CAAC,CAAA;AAErB,EAAA,OAAO,KAAA;AACT","file":"useFlash.js","sourcesContent":["/**\n * `useFlash` — read flash messages from Inertia shared props.\n *\n * Optional `onMessage` callback fires once per *new* message of each kind,\n * allowing toast-library integrations without leaking effects elsewhere.\n */\n\nimport type { FlashPayload, SharedProps } from '@arqel-dev/types/inertia';\nimport { usePage } from '@inertiajs/react';\nimport { useEffect, useRef } from 'react';\n\nexport type FlashKind = 'success' | 'error' | 'info' | 'warning';\n\nexport interface UseFlashOptions {\n onMessage?: (kind: FlashKind, message: string) => void;\n}\n\nexport type UseFlashResult = FlashPayload;\n\nconst KINDS: FlashKind[] = ['success', 'error', 'info', 'warning'];\n\nconst EMPTY_FLASH: FlashPayload = {\n success: null,\n error: null,\n info: null,\n warning: null,\n};\n\nexport function useFlash(options: UseFlashOptions = {}): UseFlashResult {\n const page = usePage();\n const props = page.props as unknown as SharedProps;\n const flash = props.flash ?? EMPTY_FLASH;\n const seenRef = useRef<Record<FlashKind, string | null>>({\n success: null,\n error: null,\n info: null,\n warning: null,\n });\n\n const { onMessage } = options;\n\n useEffect(() => {\n if (!onMessage) return;\n for (const kind of KINDS) {\n const value = flash[kind];\n if (value && value !== seenRef.current[kind]) {\n seenRef.current[kind] = value;\n onMessage(kind, value);\n }\n }\n }, [flash, onMessage]);\n\n return flash;\n}\n"]}
@@ -0,0 +1,21 @@
1
+ /**
2
+ * `useNavigation` — read panel navigation from Inertia shared props.
3
+ *
4
+ * Server emits a flat or grouped tree under `panel.navigation`; this hook
5
+ * normalises it to a `NavigationItem[]`-like shape callsites can render.
6
+ */
7
+ interface NavigationItemPayload {
8
+ label: string;
9
+ url: string;
10
+ icon?: string | null;
11
+ badge?: string | number | null;
12
+ active?: boolean;
13
+ group?: string | null;
14
+ children?: NavigationItemPayload[];
15
+ }
16
+ interface UseNavigationResult {
17
+ items: NavigationItemPayload[];
18
+ }
19
+ declare function useNavigation(): UseNavigationResult;
20
+
21
+ export { type NavigationItemPayload, type UseNavigationResult, useNavigation };
@@ -0,0 +1,14 @@
1
+ import { usePage } from '@inertiajs/react';
2
+
3
+ // src/useNavigation.ts
4
+ function useNavigation() {
5
+ const page = usePage();
6
+ const props = page.props;
7
+ const panel = props.panel;
8
+ const items = Array.isArray(panel?.navigation) ? panel.navigation : [];
9
+ return { items };
10
+ }
11
+
12
+ export { useNavigation };
13
+ //# sourceMappingURL=useNavigation.js.map
14
+ //# sourceMappingURL=useNavigation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/useNavigation.ts"],"names":[],"mappings":";;;AA4BO,SAAS,aAAA,GAAqC;AACnD,EAAA,MAAM,OAAO,OAAA,EAAQ;AACrB,EAAA,MAAM,QAAQ,IAAA,CAAK,KAAA;AACnB,EAAA,MAAM,QAAQ,KAAA,CAAM,KAAA;AACpB,EAAA,MAAM,KAAA,GAAQ,MAAM,OAAA,CAAQ,KAAA,EAAO,UAAU,CAAA,GAAI,KAAA,CAAM,aAAa,EAAC;AACrE,EAAA,OAAO,EAAE,KAAA,EAAM;AACjB","file":"useNavigation.js","sourcesContent":["/**\n * `useNavigation` — read panel navigation from Inertia shared props.\n *\n * Server emits a flat or grouped tree under `panel.navigation`; this hook\n * normalises it to a `NavigationItem[]`-like shape callsites can render.\n */\n\nimport type { SharedProps } from '@arqel-dev/types/inertia';\nimport { usePage } from '@inertiajs/react';\n\nexport interface NavigationItemPayload {\n label: string;\n url: string;\n icon?: string | null;\n badge?: string | number | null;\n active?: boolean;\n group?: string | null;\n children?: NavigationItemPayload[];\n}\n\nexport interface UseNavigationResult {\n items: NavigationItemPayload[];\n}\n\ninterface PanelWithNav {\n navigation?: NavigationItemPayload[];\n}\n\nexport function useNavigation(): UseNavigationResult {\n const page = usePage();\n const props = page.props as unknown as SharedProps;\n const panel = props.panel as (SharedProps['panel'] & PanelWithNav) | null;\n const items = Array.isArray(panel?.navigation) ? panel.navigation : [];\n return { items };\n}\n"]}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * `useArqelOptimistic` — thin wrapper over React 19's `useOptimistic`.
3
+ *
4
+ * Renamed (`useArqelOptimistic`) to avoid colliding with React's hook when
5
+ * both are imported in the same file. Reducer signature mirrors React's.
6
+ */
7
+ declare function useArqelOptimistic<TState, TAction>(state: TState, reducer: (current: TState, action: TAction) => TState): [TState, (action: TAction) => void];
8
+
9
+ export { useArqelOptimistic };
@@ -0,0 +1,10 @@
1
+ import { useOptimistic } from 'react';
2
+
3
+ // src/useOptimistic.ts
4
+ function useArqelOptimistic(state, reducer) {
5
+ return useOptimistic(state, reducer);
6
+ }
7
+
8
+ export { useArqelOptimistic };
9
+ //# sourceMappingURL=useOptimistic.js.map
10
+ //# sourceMappingURL=useOptimistic.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/useOptimistic.ts"],"names":[],"mappings":";;;AASO,SAAS,kBAAA,CACd,OACA,OAAA,EACqC;AACrC,EAAA,OAAO,aAAA,CAAc,OAAO,OAAO,CAAA;AACrC","file":"useOptimistic.js","sourcesContent":["/**\n * `useArqelOptimistic` — thin wrapper over React 19's `useOptimistic`.\n *\n * Renamed (`useArqelOptimistic`) to avoid colliding with React's hook when\n * both are imported in the same file. Reducer signature mirrors React's.\n */\n\nimport { useOptimistic } from 'react';\n\nexport function useArqelOptimistic<TState, TAction>(\n state: TState,\n reducer: (current: TState, action: TAction) => TState,\n): [TState, (action: TAction) => void] {\n return useOptimistic(state, reducer);\n}\n"]}
@@ -0,0 +1,25 @@
1
+ import { ResourceMeta } from '@arqel-dev/types/resources';
2
+
3
+ /**
4
+ * `useResource` — typed access to Inertia page props for a Resource page.
5
+ *
6
+ * Combines `usePage()` (shared props) with the optional ResourceContext
7
+ * provided by `<ResourceLayout>` so callsites can pull `records`, `actions`,
8
+ * `filters`, etc. with a single hook.
9
+ */
10
+
11
+ interface UseResourceResult<TRecord = unknown> {
12
+ /** Server-rendered Resource metadata (label, slug, fields, etc.). */
13
+ resource: ResourceMeta | null;
14
+ /** Records collection — `null` on non-index pages. */
15
+ records: TRecord[] | null;
16
+ /** Single record — `null` on index pages. */
17
+ record: TRecord | null;
18
+ /** Active filters as serialised by Table::serialize(). */
19
+ filters: Record<string, unknown>;
20
+ /** Raw page props for escape-hatch use. */
21
+ props: Record<string, unknown>;
22
+ }
23
+ declare function useResource<TRecord = unknown>(): UseResourceResult<TRecord>;
24
+
25
+ export { type UseResourceResult, useResource };
@@ -0,0 +1,20 @@
1
+ import { useResourceContext } from '@arqel-dev/react/context';
2
+ import { usePage } from '@inertiajs/react';
3
+
4
+ // src/useResource.ts
5
+ function useResource() {
6
+ const page = usePage();
7
+ const props = page.props;
8
+ const contextResource = useResourceContext();
9
+ return {
10
+ resource: props.resource ?? contextResource ?? null,
11
+ records: Array.isArray(props.records) ? props.records : null,
12
+ record: props.record ?? null,
13
+ filters: props.filters ?? {},
14
+ props
15
+ };
16
+ }
17
+
18
+ export { useResource };
19
+ //# sourceMappingURL=useResource.js.map
20
+ //# sourceMappingURL=useResource.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/useResource.ts"],"names":[],"mappings":";;;;AAgCO,SAAS,WAAA,GAA6D;AAC3E,EAAA,MAAM,OAAO,OAAA,EAAQ;AACrB,EAAA,MAAM,QAAQ,IAAA,CAAK,KAAA;AACnB,EAAA,MAAM,kBAAkB,kBAAA,EAAmB;AAE3C,EAAA,OAAO;AAAA,IACL,QAAA,EAAU,KAAA,CAAM,QAAA,IAAY,eAAA,IAAmB,IAAA;AAAA,IAC/C,SAAS,KAAA,CAAM,OAAA,CAAQ,MAAM,OAAO,CAAA,GAAK,MAAM,OAAA,GAAwB,IAAA;AAAA,IACvE,MAAA,EAAS,MAAM,MAAA,IAAkC,IAAA;AAAA,IACjD,OAAA,EAAU,KAAA,CAAM,OAAA,IAAmD,EAAC;AAAA,IACpE;AAAA,GACF;AACF","file":"useResource.js","sourcesContent":["/**\n * `useResource` — typed access to Inertia page props for a Resource page.\n *\n * Combines `usePage()` (shared props) with the optional ResourceContext\n * provided by `<ResourceLayout>` so callsites can pull `records`, `actions`,\n * `filters`, etc. with a single hook.\n */\n\nimport { useResourceContext } from '@arqel-dev/react/context';\nimport type { ResourceMeta } from '@arqel-dev/types/resources';\nimport { usePage } from '@inertiajs/react';\n\nexport interface UseResourceResult<TRecord = unknown> {\n /** Server-rendered Resource metadata (label, slug, fields, etc.). */\n resource: ResourceMeta | null;\n /** Records collection — `null` on non-index pages. */\n records: TRecord[] | null;\n /** Single record — `null` on index pages. */\n record: TRecord | null;\n /** Active filters as serialised by Table::serialize(). */\n filters: Record<string, unknown>;\n /** Raw page props for escape-hatch use. */\n props: Record<string, unknown>;\n}\n\ninterface ResourcePagePayload<TRecord> {\n resource?: ResourceMeta;\n records?: TRecord[];\n record?: TRecord;\n filters?: Record<string, unknown>;\n}\n\nexport function useResource<TRecord = unknown>(): UseResourceResult<TRecord> {\n const page = usePage();\n const props = page.props as Record<string, unknown> & ResourcePagePayload<TRecord>;\n const contextResource = useResourceContext();\n\n return {\n resource: props.resource ?? contextResource ?? null,\n records: Array.isArray(props.records) ? (props.records as TRecord[]) : null,\n record: (props.record as TRecord | undefined) ?? null,\n filters: (props.filters as Record<string, unknown> | undefined) ?? {},\n props,\n };\n}\n"]}
@@ -0,0 +1,51 @@
1
+ /**
2
+ * `useResourceUpdates` — subscribe to a Laravel Echo private channel for a
3
+ * resource (or specific record) and trigger a partial Inertia reload when an
4
+ * update event arrives.
5
+ *
6
+ * The hook is intentionally defensive: it only activates when `window.Echo` is
7
+ * present at runtime. If Reverb/Echo is not configured in the host app, the
8
+ * hook becomes a no-op so that consumers can ship pages without crashing in
9
+ * environments where realtime is disabled.
10
+ *
11
+ * Note: the `laravel-echo` package is *not* a dependency of `@arqel-dev/hooks` —
12
+ * we only declare an ambient minimal shape of `window.Echo`.
13
+ */
14
+ /**
15
+ * Minimal structural type describing the Echo surface we use. Keeping this
16
+ * narrow avoids a hard dependency on `laravel-echo` types and keeps the hook
17
+ * usable with stubs/mocks.
18
+ */
19
+ interface EchoChannelLike {
20
+ listen(event: string, callback: (payload: ResourceUpdatePayload) => void): EchoChannelLike;
21
+ }
22
+ interface EchoLike {
23
+ private(channel: string): EchoChannelLike;
24
+ leave(channel: string): void;
25
+ }
26
+ declare global {
27
+ interface Window {
28
+ Echo?: EchoLike;
29
+ }
30
+ }
31
+ /**
32
+ * Shape of the broadcast payload. We type it loosely because broadcasters can
33
+ * include arbitrary metadata, but expose convenience fields used by the
34
+ * default UI integration.
35
+ */
36
+ type ResourceUpdatePayload = Record<string, any>;
37
+ interface UseResourceUpdatesOptions {
38
+ /**
39
+ * Optional callback invoked on each received update with the raw payload.
40
+ * Useful for showing a toast like "Updated by {user}".
41
+ */
42
+ onUpdate?: (payload: ResourceUpdatePayload) => void;
43
+ /**
44
+ * Override the broadcast event name. Defaults to `.ResourceUpdated`.
45
+ * The leading `.` opts into Laravel's "use literal event name" convention.
46
+ */
47
+ event?: string;
48
+ }
49
+ declare function useResourceUpdates(resourceSlug: string, recordId?: string | number, options?: UseResourceUpdatesOptions): void;
50
+
51
+ export { type EchoChannelLike, type EchoLike, type ResourceUpdatePayload, type UseResourceUpdatesOptions, useResourceUpdates };
@@ -0,0 +1,29 @@
1
+ import { router } from '@inertiajs/react';
2
+ import { useEffect } from 'react';
3
+
4
+ // src/useResourceUpdates.ts
5
+ var DEFAULT_EVENT = ".ResourceUpdated";
6
+ function useResourceUpdates(resourceSlug, recordId, options = {}) {
7
+ const { onUpdate, event = DEFAULT_EVENT } = options;
8
+ useEffect(() => {
9
+ const echo = typeof window !== "undefined" ? window.Echo : void 0;
10
+ if (!echo) {
11
+ return;
12
+ }
13
+ const channelName = recordId !== void 0 && recordId !== null ? `arqel.${resourceSlug}.${recordId}` : `arqel.${resourceSlug}`;
14
+ const only = recordId !== void 0 && recordId !== null ? ["record"] : ["records"];
15
+ echo.private(channelName).listen(event, (payload) => {
16
+ if (onUpdate) {
17
+ onUpdate(payload);
18
+ }
19
+ router.reload({ only });
20
+ });
21
+ return () => {
22
+ window.Echo?.leave(channelName);
23
+ };
24
+ }, [resourceSlug, recordId, onUpdate, event]);
25
+ }
26
+
27
+ export { useResourceUpdates };
28
+ //# sourceMappingURL=useResourceUpdates.js.map
29
+ //# sourceMappingURL=useResourceUpdates.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/useResourceUpdates.ts"],"names":[],"mappings":";;;;AA0DA,IAAM,aAAA,GAAgB,kBAAA;AAEf,SAAS,kBAAA,CACd,YAAA,EACA,QAAA,EACA,OAAA,GAAqC,EAAC,EAChC;AACN,EAAA,MAAM,EAAE,QAAA,EAAU,KAAA,GAAQ,aAAA,EAAc,GAAI,OAAA;AAE5C,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,MAAM,IAAA,GAAO,OAAO,MAAA,KAAW,WAAA,GAAc,OAAO,IAAA,GAAO,MAAA;AAC3D,IAAA,IAAI,CAAC,IAAA,EAAM;AACT,MAAA;AAAA,IACF;AAEA,IAAA,MAAM,WAAA,GACJ,QAAA,KAAa,MAAA,IAAa,QAAA,KAAa,IAAA,GACnC,CAAA,MAAA,EAAS,YAAY,CAAA,CAAA,EAAI,QAAQ,CAAA,CAAA,GACjC,CAAA,MAAA,EAAS,YAAY,CAAA,CAAA;AAE3B,IAAA,MAAM,IAAA,GAAO,aAAa,MAAA,IAAa,QAAA,KAAa,OAAO,CAAC,QAAQ,CAAA,GAAI,CAAC,SAAS,CAAA;AAElF,IAAA,IAAA,CAAK,QAAQ,WAAW,CAAA,CAAE,MAAA,CAAO,KAAA,EAAO,CAAC,OAAA,KAAmC;AAC1E,MAAA,IAAI,QAAA,EAAU;AACZ,QAAA,QAAA,CAAS,OAAO,CAAA;AAAA,MAClB;AAGA,MAAA,MAAA,CAAO,MAAA,CAAO,EAAE,IAAA,EAAM,CAAA;AAAA,IACxB,CAAC,CAAA;AAED,IAAA,OAAO,MAAM;AACX,MAAA,MAAA,CAAO,IAAA,EAAM,MAAM,WAAW,CAAA;AAAA,IAChC,CAAA;AAAA,EACF,GAAG,CAAC,YAAA,EAAc,QAAA,EAAU,QAAA,EAAU,KAAK,CAAC,CAAA;AAC9C","file":"useResourceUpdates.js","sourcesContent":["/**\n * `useResourceUpdates` — subscribe to a Laravel Echo private channel for a\n * resource (or specific record) and trigger a partial Inertia reload when an\n * update event arrives.\n *\n * The hook is intentionally defensive: it only activates when `window.Echo` is\n * present at runtime. If Reverb/Echo is not configured in the host app, the\n * hook becomes a no-op so that consumers can ship pages without crashing in\n * environments where realtime is disabled.\n *\n * Note: the `laravel-echo` package is *not* a dependency of `@arqel-dev/hooks` —\n * we only declare an ambient minimal shape of `window.Echo`.\n */\n\nimport { router } from '@inertiajs/react';\nimport { useEffect } from 'react';\n\n/**\n * Minimal structural type describing the Echo surface we use. Keeping this\n * narrow avoids a hard dependency on `laravel-echo` types and keeps the hook\n * usable with stubs/mocks.\n */\nexport interface EchoChannelLike {\n listen(event: string, callback: (payload: ResourceUpdatePayload) => void): EchoChannelLike;\n}\n\nexport interface EchoLike {\n private(channel: string): EchoChannelLike;\n leave(channel: string): void;\n}\n\ndeclare global {\n interface Window {\n Echo?: EchoLike;\n }\n}\n\n/**\n * Shape of the broadcast payload. We type it loosely because broadcasters can\n * include arbitrary metadata, but expose convenience fields used by the\n * default UI integration.\n */\n// biome-ignore lint/suspicious/noExplicitAny: payload is intentionally open-ended (broadcast metadata is app-defined).\nexport type ResourceUpdatePayload = Record<string, any>;\n\nexport interface UseResourceUpdatesOptions {\n /**\n * Optional callback invoked on each received update with the raw payload.\n * Useful for showing a toast like \"Updated by {user}\".\n */\n onUpdate?: (payload: ResourceUpdatePayload) => void;\n /**\n * Override the broadcast event name. Defaults to `.ResourceUpdated`.\n * The leading `.` opts into Laravel's \"use literal event name\" convention.\n */\n event?: string;\n}\n\nconst DEFAULT_EVENT = '.ResourceUpdated';\n\nexport function useResourceUpdates(\n resourceSlug: string,\n recordId?: string | number,\n options: UseResourceUpdatesOptions = {},\n): void {\n const { onUpdate, event = DEFAULT_EVENT } = options;\n\n useEffect(() => {\n const echo = typeof window !== 'undefined' ? window.Echo : undefined;\n if (!echo) {\n return;\n }\n\n const channelName =\n recordId !== undefined && recordId !== null\n ? `arqel.${resourceSlug}.${recordId}`\n : `arqel.${resourceSlug}`;\n\n const only = recordId !== undefined && recordId !== null ? ['record'] : ['records'];\n\n echo.private(channelName).listen(event, (payload: ResourceUpdatePayload) => {\n if (onUpdate) {\n onUpdate(payload);\n }\n // Note: Inertia v2 reloads always preserve scroll/state by default\n // (see `ReloadOptions` = `Omit<VisitOptions, 'preserveScroll' | 'preserveState'>`).\n router.reload({ only });\n });\n\n return () => {\n window.Echo?.leave(channelName);\n };\n }, [resourceSlug, recordId, onUpdate, event]);\n}\n"]}
@@ -0,0 +1,32 @@
1
+ /**
2
+ * `useTable` — table state (sort, filters, selection).
3
+ *
4
+ * Phase 1 scope: pure local state. URL sync via Inertia `router.get`
5
+ * lands in HOOKS-004 follow-up once `<ResourceIndex>` exists.
6
+ */
7
+ type SortDirection = 'asc' | 'desc';
8
+ interface TableSort {
9
+ column: string;
10
+ direction: SortDirection;
11
+ }
12
+ interface UseTableOptions {
13
+ defaultSort?: TableSort;
14
+ defaultFilters?: Record<string, unknown>;
15
+ defaultSelection?: ReadonlyArray<string | number>;
16
+ }
17
+ interface UseTableResult {
18
+ sort: TableSort | null;
19
+ setSort: (column: string, direction?: SortDirection) => void;
20
+ clearSort: () => void;
21
+ filters: Record<string, unknown>;
22
+ setFilter: (name: string, value: unknown) => void;
23
+ clearFilters: () => void;
24
+ selectedIds: ReadonlyArray<string | number>;
25
+ toggleSelection: (id: string | number) => void;
26
+ selectAll: (ids: ReadonlyArray<string | number>) => void;
27
+ clearSelection: () => void;
28
+ isSelected: (id: string | number) => boolean;
29
+ }
30
+ declare function useTable(options?: UseTableOptions): UseTableResult;
31
+
32
+ export { type SortDirection, type TableSort, type UseTableOptions, type UseTableResult, useTable };
@@ -0,0 +1,49 @@
1
+ import { useState, useCallback } from 'react';
2
+
3
+ // src/useTable.ts
4
+ function useTable(options = {}) {
5
+ const [sort, setSortState] = useState(options.defaultSort ?? null);
6
+ const [filters, setFilters] = useState(options.defaultFilters ?? {});
7
+ const [selectedIds, setSelectedIds] = useState(
8
+ options.defaultSelection ?? []
9
+ );
10
+ const setSort = useCallback((column, direction = "asc") => {
11
+ setSortState({ column, direction });
12
+ }, []);
13
+ const clearSort = useCallback(() => setSortState(null), []);
14
+ const setFilter = useCallback((name, value) => {
15
+ setFilters((prev) => {
16
+ if (value === void 0 || value === null || value === "") {
17
+ const { [name]: _omit, ...rest } = prev;
18
+ return rest;
19
+ }
20
+ return { ...prev, [name]: value };
21
+ });
22
+ }, []);
23
+ const clearFilters = useCallback(() => setFilters({}), []);
24
+ const toggleSelection = useCallback((id) => {
25
+ setSelectedIds((prev) => prev.includes(id) ? prev.filter((x) => x !== id) : [...prev, id]);
26
+ }, []);
27
+ const selectAll = useCallback((ids) => {
28
+ setSelectedIds([...ids]);
29
+ }, []);
30
+ const clearSelection = useCallback(() => setSelectedIds([]), []);
31
+ const isSelected = useCallback((id) => selectedIds.includes(id), [selectedIds]);
32
+ return {
33
+ sort,
34
+ setSort,
35
+ clearSort,
36
+ filters,
37
+ setFilter,
38
+ clearFilters,
39
+ selectedIds,
40
+ toggleSelection,
41
+ selectAll,
42
+ clearSelection,
43
+ isSelected
44
+ };
45
+ }
46
+
47
+ export { useTable };
48
+ //# sourceMappingURL=useTable.js.map
49
+ //# sourceMappingURL=useTable.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/useTable.ts"],"names":[],"mappings":";;;AAoCO,SAAS,QAAA,CAAS,OAAA,GAA2B,EAAC,EAAmB;AACtE,EAAA,MAAM,CAAC,IAAA,EAAM,YAAY,IAAI,QAAA,CAA2B,OAAA,CAAQ,eAAe,IAAI,CAAA;AACnF,EAAA,MAAM,CAAC,SAAS,UAAU,CAAA,GAAI,SAAkC,OAAA,CAAQ,cAAA,IAAkB,EAAE,CAAA;AAC5F,EAAA,MAAM,CAAC,WAAA,EAAa,cAAc,CAAA,GAAI,QAAA;AAAA,IACpC,OAAA,CAAQ,oBAAoB;AAAC,GAC/B;AAEA,EAAA,MAAM,OAAA,GAAU,WAAA,CAAY,CAAC,MAAA,EAAgB,YAA2B,KAAA,KAAU;AAChF,IAAA,YAAA,CAAa,EAAE,MAAA,EAAQ,SAAA,EAAW,CAAA;AAAA,EACpC,CAAA,EAAG,EAAE,CAAA;AAEL,EAAA,MAAM,YAAY,WAAA,CAAY,MAAM,aAAa,IAAI,CAAA,EAAG,EAAE,CAAA;AAE1D,EAAA,MAAM,SAAA,GAAY,WAAA,CAAY,CAAC,IAAA,EAAc,KAAA,KAAmB;AAC9D,IAAA,UAAA,CAAW,CAAC,IAAA,KAAS;AACnB,MAAA,IAAI,KAAA,KAAU,MAAA,IAAa,KAAA,KAAU,IAAA,IAAQ,UAAU,EAAA,EAAI;AACzD,QAAA,MAAM,EAAE,CAAC,IAAI,GAAG,KAAA,EAAO,GAAG,MAAK,GAAI,IAAA;AACnC,QAAA,OAAO,IAAA;AAAA,MACT;AACA,MAAA,OAAO,EAAE,GAAG,IAAA,EAAM,CAAC,IAAI,GAAG,KAAA,EAAM;AAAA,IAClC,CAAC,CAAA;AAAA,EACH,CAAA,EAAG,EAAE,CAAA;AAEL,EAAA,MAAM,YAAA,GAAe,YAAY,MAAM,UAAA,CAAW,EAAE,CAAA,EAAG,EAAE,CAAA;AAEzD,EAAA,MAAM,eAAA,GAAkB,WAAA,CAAY,CAAC,EAAA,KAAwB;AAC3D,IAAA,cAAA,CAAe,CAAC,IAAA,KAAU,IAAA,CAAK,QAAA,CAAS,EAAE,IAAI,IAAA,CAAK,MAAA,CAAO,CAAC,CAAA,KAAM,MAAM,EAAE,CAAA,GAAI,CAAC,GAAG,IAAA,EAAM,EAAE,CAAE,CAAA;AAAA,EAC7F,CAAA,EAAG,EAAE,CAAA;AAEL,EAAA,MAAM,SAAA,GAAY,WAAA,CAAY,CAAC,GAAA,KAAwC;AACrE,IAAA,cAAA,CAAe,CAAC,GAAG,GAAG,CAAC,CAAA;AAAA,EACzB,CAAA,EAAG,EAAE,CAAA;AAEL,EAAA,MAAM,cAAA,GAAiB,YAAY,MAAM,cAAA,CAAe,EAAE,CAAA,EAAG,EAAE,CAAA;AAE/D,EAAA,MAAM,UAAA,GAAa,WAAA,CAAY,CAAC,EAAA,KAAwB,WAAA,CAAY,SAAS,EAAE,CAAA,EAAG,CAAC,WAAW,CAAC,CAAA;AAE/F,EAAA,OAAO;AAAA,IACL,IAAA;AAAA,IACA,OAAA;AAAA,IACA,SAAA;AAAA,IACA,OAAA;AAAA,IACA,SAAA;AAAA,IACA,YAAA;AAAA,IACA,WAAA;AAAA,IACA,eAAA;AAAA,IACA,SAAA;AAAA,IACA,cAAA;AAAA,IACA;AAAA,GACF;AACF","file":"useTable.js","sourcesContent":["/**\n * `useTable` — table state (sort, filters, selection).\n *\n * Phase 1 scope: pure local state. URL sync via Inertia `router.get`\n * lands in HOOKS-004 follow-up once `<ResourceIndex>` exists.\n */\n\nimport { useCallback, useState } from 'react';\n\nexport type SortDirection = 'asc' | 'desc';\n\nexport interface TableSort {\n column: string;\n direction: SortDirection;\n}\n\nexport interface UseTableOptions {\n defaultSort?: TableSort;\n defaultFilters?: Record<string, unknown>;\n defaultSelection?: ReadonlyArray<string | number>;\n}\n\nexport interface UseTableResult {\n sort: TableSort | null;\n setSort: (column: string, direction?: SortDirection) => void;\n clearSort: () => void;\n filters: Record<string, unknown>;\n setFilter: (name: string, value: unknown) => void;\n clearFilters: () => void;\n selectedIds: ReadonlyArray<string | number>;\n toggleSelection: (id: string | number) => void;\n selectAll: (ids: ReadonlyArray<string | number>) => void;\n clearSelection: () => void;\n isSelected: (id: string | number) => boolean;\n}\n\nexport function useTable(options: UseTableOptions = {}): UseTableResult {\n const [sort, setSortState] = useState<TableSort | null>(options.defaultSort ?? null);\n const [filters, setFilters] = useState<Record<string, unknown>>(options.defaultFilters ?? {});\n const [selectedIds, setSelectedIds] = useState<ReadonlyArray<string | number>>(\n options.defaultSelection ?? [],\n );\n\n const setSort = useCallback((column: string, direction: SortDirection = 'asc') => {\n setSortState({ column, direction });\n }, []);\n\n const clearSort = useCallback(() => setSortState(null), []);\n\n const setFilter = useCallback((name: string, value: unknown) => {\n setFilters((prev) => {\n if (value === undefined || value === null || value === '') {\n const { [name]: _omit, ...rest } = prev;\n return rest;\n }\n return { ...prev, [name]: value };\n });\n }, []);\n\n const clearFilters = useCallback(() => setFilters({}), []);\n\n const toggleSelection = useCallback((id: string | number) => {\n setSelectedIds((prev) => (prev.includes(id) ? prev.filter((x) => x !== id) : [...prev, id]));\n }, []);\n\n const selectAll = useCallback((ids: ReadonlyArray<string | number>) => {\n setSelectedIds([...ids]);\n }, []);\n\n const clearSelection = useCallback(() => setSelectedIds([]), []);\n\n const isSelected = useCallback((id: string | number) => selectedIds.includes(id), [selectedIds]);\n\n return {\n sort,\n setSort,\n clearSort,\n filters,\n setFilter,\n clearFilters,\n selectedIds,\n toggleSelection,\n selectAll,\n clearSelection,\n isSelected,\n };\n}\n"]}
package/package.json ADDED
@@ -0,0 +1,116 @@
1
+ {
2
+ "name": "@arqel-dev/hooks",
3
+ "version": "0.8.0",
4
+ "description": "Reusable React hooks for Arqel admin panels.",
5
+ "license": "MIT",
6
+ "homepage": "https://arqel.dev",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/arqel-dev/arqel.git",
10
+ "directory": "packages-js/hooks"
11
+ },
12
+ "keywords": [
13
+ "arqel",
14
+ "laravel",
15
+ "inertia",
16
+ "react",
17
+ "hooks"
18
+ ],
19
+ "type": "module",
20
+ "sideEffects": false,
21
+ "exports": {
22
+ ".": {
23
+ "types": "./dist/index.d.ts",
24
+ "import": "./dist/index.js"
25
+ },
26
+ "./useResource": {
27
+ "types": "./dist/useResource.d.ts",
28
+ "import": "./dist/useResource.js"
29
+ },
30
+ "./useArqelForm": {
31
+ "types": "./dist/useArqelForm.d.ts",
32
+ "import": "./dist/useArqelForm.js"
33
+ },
34
+ "./useCanAccess": {
35
+ "types": "./dist/useCanAccess.d.ts",
36
+ "import": "./dist/useCanAccess.js"
37
+ },
38
+ "./useFlash": {
39
+ "types": "./dist/useFlash.d.ts",
40
+ "import": "./dist/useFlash.js"
41
+ },
42
+ "./useTable": {
43
+ "types": "./dist/useTable.d.ts",
44
+ "import": "./dist/useTable.js"
45
+ },
46
+ "./useAction": {
47
+ "types": "./dist/useAction.d.ts",
48
+ "import": "./dist/useAction.js"
49
+ },
50
+ "./useFieldDependencies": {
51
+ "types": "./dist/useFieldDependencies.d.ts",
52
+ "import": "./dist/useFieldDependencies.js"
53
+ },
54
+ "./useNavigation": {
55
+ "types": "./dist/useNavigation.d.ts",
56
+ "import": "./dist/useNavigation.js"
57
+ },
58
+ "./useBreakpoint": {
59
+ "types": "./dist/useBreakpoint.d.ts",
60
+ "import": "./dist/useBreakpoint.js"
61
+ },
62
+ "./useOptimistic": {
63
+ "types": "./dist/useOptimistic.d.ts",
64
+ "import": "./dist/useOptimistic.js"
65
+ },
66
+ "./useResourceUpdates": {
67
+ "types": "./dist/useResourceUpdates.d.ts",
68
+ "import": "./dist/useResourceUpdates.js"
69
+ }
70
+ },
71
+ "files": [
72
+ "dist",
73
+ "README.md",
74
+ "SKILL.md"
75
+ ],
76
+ "dependencies": {
77
+ "@arqel-dev/types": "0.8.0"
78
+ },
79
+ "peerDependencies": {
80
+ "@inertiajs/react": "^2.0.0",
81
+ "react": "^19.0.0",
82
+ "react-dom": "^19.0.0",
83
+ "@arqel-dev/react": "0.8.0"
84
+ },
85
+ "devDependencies": {
86
+ "@inertiajs/core": "^2.0.0",
87
+ "@inertiajs/react": "^2.0.0",
88
+ "@testing-library/jest-dom": "^6.6.3",
89
+ "@testing-library/react": "^16.1.0",
90
+ "@types/react": "^19.0.0",
91
+ "@types/react-dom": "^19.0.0",
92
+ "expect-type": "^1.2.2",
93
+ "jsdom": "^25.0.1",
94
+ "react": "^19.0.0",
95
+ "react-dom": "^19.0.0",
96
+ "tsup": "^8.5.0",
97
+ "typescript": "^5.6.3",
98
+ "vitest": "^3.0.0",
99
+ "@arqel-dev/react": "0.8.0"
100
+ },
101
+ "publishConfig": {
102
+ "access": "public"
103
+ },
104
+ "engines": {
105
+ "node": ">=20.9.0"
106
+ },
107
+ "scripts": {
108
+ "build": "tsup",
109
+ "dev": "tsup --watch",
110
+ "typecheck": "tsc --noEmit",
111
+ "lint": "biome check --diagnostic-level=error src tests",
112
+ "format": "biome format --write src tests",
113
+ "test": "vitest run",
114
+ "test:watch": "vitest"
115
+ }
116
+ }