@arqel-dev/hooks 0.11.0 → 0.14.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.
package/dist/index.js CHANGED
@@ -8,7 +8,12 @@ function useAction(action) {
8
8
  const [processing, setProcessing] = useState(false);
9
9
  const invoke = useCallback(
10
10
  (record, payload = {}) => {
11
- const url = action.url ?? `/arqel-dev/actions/${action.name}`;
11
+ if (!action.url) {
12
+ throw new Error(
13
+ `Arqel: action "${action.name}" has no url to dispatch to. A custom action with ->action() must be declared on the Resource's actions() so the server can emit its endpoint URL; otherwise give it ->url().`
14
+ );
15
+ }
16
+ const url = action.url;
12
17
  const method = action.method.toLowerCase();
13
18
  const data = record ? { record_id: record.id, ...payload } : payload;
14
19
  setProcessing(true);
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/useAction.ts","../src/useArqelForm.ts","../src/useBreakpoint.ts","../src/useCanAccess.ts","../src/useFieldDependencies.ts","../src/useFlash.ts","../src/useNavigation.ts","../src/useOptimistic.ts","../src/useResource.ts","../src/useResourceUpdates.ts","../src/useTable.ts"],"names":["useState","useEffect","router","usePage","useRef","useCallback"],"mappings":";;;;;;AAmBO,SAAS,UAAU,MAAA,EAAuC;AAC/D,EAAA,MAAM,CAAC,UAAA,EAAY,aAAa,CAAA,GAAI,SAAS,KAAK,CAAA;AAElD,EAAA,MAAM,MAAA,GAAS,WAAA;AAAA,IACb,CAAC,MAAA,EAAwC,OAAA,GAAmC,EAAC,KAAM;AACjF,MAAA,MAAM,GAAA,GAAM,MAAA,CAAO,GAAA,IAAO,CAAA,mBAAA,EAAsB,OAAO,IAAI,CAAA,CAAA;AAC3D,MAAA,MAAM,MAAA,GAAS,MAAA,CAAO,MAAA,CAAO,WAAA,EAAY;AACzC,MAAA,MAAM,IAAA,GAAO,SAAS,EAAE,SAAA,EAAW,OAAO,EAAA,EAAI,GAAG,SAAQ,GAAI,OAAA;AAE7D,MAAA,aAAA,CAAc,IAAI,CAAA;AAClB,MAAA,MAAA,CAAO,MAAM,GAAA,EAAK;AAAA,QAChB,MAAA;AAAA,QACA,IAAA;AAAA,QACA,cAAA,EAAgB,IAAA;AAAA,QAChB,QAAA,EAAU,MAAM,aAAA,CAAc,KAAK;AAAA,OACpC,CAAA;AAAA,IACH,CAAA;AAAA,IACA,CAAC,MAAA,CAAO,IAAA,EAAM,MAAA,CAAO,GAAA,EAAK,OAAO,MAAM;AAAA,GACzC;AAEA,EAAA,OAAO,EAAE,MAAA,EAAQ,UAAA,EAAY,QAAA,EAAU,CAAA,EAAE;AAC3C;ACRO,SAAS,aACd,OAAA,EACoB;AACpB,EAAA,MAAM,EAAE,MAAA,EAAQ,MAAA,GAAS,IAAA,EAAM,UAAS,GAAI,OAAA;AAE5C,EAAA,MAAM,OAAA,GAAU,QAAoB,MAAM;AACxC,IAAA,MAAM,SAAA,GAAY,MAAA;AAClB,IAAA,MAAM,OAAO,qBAAA,CAAsB,CAAC,GAAG,MAAM,GAAG,SAAS,CAAA;AACzD,IAAA,OAAO,WAAW,EAAE,GAAG,IAAA,EAAM,GAAG,UAAS,GAAI,IAAA;AAAA,EAC/C,CAAA,EAAG,CAAC,MAAA,EAAQ,MAAA,EAAQ,QAAQ,CAAC,CAAA;AAK7B,EAAA,MAAM,WAAA,GAAc,OAAA;AACpB,EAAA,MAAM,IAAA,GAAO,YAAY,OAAO,CAAA;AAEhC,EAAA,OAAO;AAAA,IACL,GAAG,IAAA;AAAA,IACH,MAAA;AAAA,IACA,cAAc,EAAC;AAAA,IACf,UAAU,MAAM,IAAA;AAAA,IAChB,eAAe,MAAM;AAAA,GACvB;AACF;AC7CA,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,GAAIA,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;ACvBO,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;ACRO,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,EAAAC,UAAU,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,QAAAC,MAAAA,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;AClCA,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,OAAOC,OAAAA,EAAQ;AACrB,EAAA,MAAM,QAAQ,IAAA,CAAK,KAAA;AACnB,EAAA,MAAM,KAAA,GAAQ,MAAM,KAAA,IAAS,WAAA;AAC7B,EAAA,MAAM,UAAUC,MAAAA,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,EAAAH,UAAU,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;ACzBO,SAAS,aAAA,GAAqC;AACnD,EAAA,MAAM,OAAOE,OAAAA,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;ACzBO,SAAS,kBAAA,CACd,OACA,OAAA,EACqC;AACrC,EAAA,OAAO,aAAA,CAAc,OAAO,OAAO,CAAA;AACrC;ACkBO,SAAS,WAAA,GAA6D;AAC3E,EAAA,MAAM,OAAOA,OAAAA,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;ACcA,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,EAAAF,UAAU,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,MAAAC,MAAAA,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;ACzDO,SAAS,QAAA,CAAS,OAAA,GAA2B,EAAC,EAAmB;AACtE,EAAA,MAAM,CAAC,IAAA,EAAM,YAAY,IAAIF,QAAAA,CAA2B,OAAA,CAAQ,eAAe,IAAI,CAAA;AACnF,EAAA,MAAM,CAAC,SAAS,UAAU,CAAA,GAAIA,SAAkC,OAAA,CAAQ,cAAA,IAAkB,EAAE,CAAA;AAC5F,EAAA,MAAM,CAAC,WAAA,EAAa,cAAc,CAAA,GAAIA,QAAAA;AAAA,IACpC,OAAA,CAAQ,oBAAoB;AAAC,GAC/B;AAEA,EAAA,MAAM,OAAA,GAAUK,WAAAA,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,YAAYA,WAAAA,CAAY,MAAM,aAAa,IAAI,CAAA,EAAG,EAAE,CAAA;AAE1D,EAAA,MAAM,SAAA,GAAYA,WAAAA,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,GAAeA,YAAY,MAAM,UAAA,CAAW,EAAE,CAAA,EAAG,EAAE,CAAA;AAEzD,EAAA,MAAM,eAAA,GAAkBA,WAAAA,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,GAAYA,WAAAA,CAAY,CAAC,GAAA,KAAwC;AACrE,IAAA,cAAA,CAAe,CAAC,GAAG,GAAG,CAAC,CAAA;AAAA,EACzB,CAAA,EAAG,EAAE,CAAA;AAEL,EAAA,MAAM,cAAA,GAAiBA,YAAY,MAAM,cAAA,CAAe,EAAE,CAAA,EAAG,EAAE,CAAA;AAE/D,EAAA,MAAM,UAAA,GAAaA,WAAAA,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":"index.js","sourcesContent":["/**\n * `useAction` — invoke an Action via Inertia router.\n *\n * Phase 1 scope: thin wrapper around `router.visit` that exposes\n * `processing` and a `progress` stub (always 0). HOOKS-004 will wire\n * real Inertia progress events.\n */\n\nimport type { ActionSchema } from '@arqel-dev/types/actions';\nimport type { FormDataConvertible } from '@inertiajs/core';\nimport { router } from '@inertiajs/react';\nimport { useCallback, useState } from 'react';\n\nexport interface UseActionResult {\n invoke: (record: { id: string | number } | null, payload?: Record<string, unknown>) => void;\n processing: boolean;\n progress: number;\n}\n\nexport function useAction(action: ActionSchema): UseActionResult {\n const [processing, setProcessing] = useState(false);\n\n const invoke = useCallback(\n (record: { id: string | number } | null, payload: Record<string, unknown> = {}) => {\n const url = action.url ?? `/arqel-dev/actions/${action.name}`;\n const method = action.method.toLowerCase() as 'get' | 'post' | 'put' | 'patch' | 'delete';\n const data = record ? { record_id: record.id, ...payload } : payload;\n\n setProcessing(true);\n router.visit(url, {\n method,\n data: data as Record<string, FormDataConvertible>,\n preserveScroll: true,\n onFinish: () => setProcessing(false),\n });\n },\n [action.name, action.url, action.method],\n );\n\n return { invoke, processing, progress: 0 };\n}\n","/**\n * `useArqelForm` — wraps Inertia `useForm` with Field-aware defaults.\n *\n * Phase 1 scope: seed initial state from `buildInitialFormState(fields, record)`,\n * proxy through Inertia's API, and stub `validate()`/`validateField()` returning\n * `true` until the Zod bridge ships in HOOKS-002 follow-up.\n */\n\nimport { buildInitialFormState } from '@arqel-dev/react/utils';\nimport type { FieldSchema } from '@arqel-dev/types/fields';\nimport type { FormDataConvertible } from '@inertiajs/core';\nimport type { InertiaFormProps } from '@inertiajs/react';\nimport { useForm } from '@inertiajs/react';\nimport { useMemo } from 'react';\n\ntype FormValues = Record<string, FormDataConvertible>;\n\nexport interface UseArqelFormOptions<TRecord> {\n fields: readonly FieldSchema[];\n record?: TRecord | null;\n defaults?: FormValues;\n}\n\nexport interface UseArqelFormExtensions {\n fields: readonly FieldSchema[];\n clientErrors: Record<string, string[]>;\n validate: () => boolean;\n validateField: (name: string) => boolean;\n}\n\nexport type UseArqelFormResult = InertiaFormProps<FormValues> & UseArqelFormExtensions;\n\nexport function useArqelForm<TRecord = Record<string, unknown>>(\n options: UseArqelFormOptions<TRecord>,\n): UseArqelFormResult {\n const { fields, record = null, defaults } = options;\n\n const initial = useMemo<FormValues>(() => {\n const recordObj = record as Record<string, unknown> | null;\n const seed = buildInitialFormState([...fields], recordObj) as FormValues;\n return defaults ? { ...seed, ...defaults } : seed;\n }, [fields, record, defaults]);\n\n // The Inertia useForm generic causes deep instantiation when the value type\n // is `Record<string, FormDataConvertible>`. The runtime call is fine; we just\n // narrow the result to InertiaFormProps<FormValues> via an unknown bridge.\n const callUseForm = useForm as unknown as (data: FormValues) => InertiaFormProps<FormValues>;\n const form = callUseForm(initial);\n\n return {\n ...form,\n fields,\n clientErrors: {},\n validate: () => true,\n validateField: () => true,\n };\n}\n","/**\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","/**\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","/**\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","/**\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","/**\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","/**\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","/**\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","/**\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","/**\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"]}
1
+ {"version":3,"sources":["../src/useAction.ts","../src/useArqelForm.ts","../src/useBreakpoint.ts","../src/useCanAccess.ts","../src/useFieldDependencies.ts","../src/useFlash.ts","../src/useNavigation.ts","../src/useOptimistic.ts","../src/useResource.ts","../src/useResourceUpdates.ts","../src/useTable.ts"],"names":["useState","useEffect","router","usePage","useRef","useCallback"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * `useAction` — invoke an Action via Inertia router.\n *\n * Phase 1 scope: thin wrapper around `router.visit` that exposes\n * `processing` and a `progress` stub (always 0). HOOKS-004 will wire\n * real Inertia progress events.\n */\n\nimport type { ActionSchema } from '@arqel-dev/types/actions';\nimport type { FormDataConvertible } from '@inertiajs/core';\nimport { router } from '@inertiajs/react';\nimport { useCallback, useState } from 'react';\n\nexport interface UseActionResult {\n invoke: (record: { id: string | number } | null, payload?: Record<string, unknown>) => void;\n processing: boolean;\n progress: number;\n}\n\nexport function useAction(action: ActionSchema): UseActionResult {\n const [processing, setProcessing] = useState(false);\n\n const invoke = useCallback(\n (record: { id: string | number } | null, payload: Record<string, unknown> = {}) => {\n // The server emits a `url` for every dispatchable action — stock\n // verbs + bulk + custom callback actions all resolve through core's\n // authorised routes (see `Action::resolveStockUrl()`, #48/#231). A\n // missing `url` means the action is a misconfiguration (e.g. a custom\n // action serialised without a resource context); we must NOT silently\n // POST to the dead `/arqel-dev/actions/{name}` route removed in #174.\n // Surface it clearly instead of masking it as a 404.\n if (!action.url) {\n throw new Error(\n `Arqel: action \"${action.name}\" has no url to dispatch to. A custom ` +\n `action with ->action() must be declared on the Resource's actions() ` +\n `so the server can emit its endpoint URL; otherwise give it ->url().`,\n );\n }\n const url = action.url;\n const method = action.method.toLowerCase() as 'get' | 'post' | 'put' | 'patch' | 'delete';\n const data = record ? { record_id: record.id, ...payload } : payload;\n\n setProcessing(true);\n router.visit(url, {\n method,\n data: data as Record<string, FormDataConvertible>,\n preserveScroll: true,\n onFinish: () => setProcessing(false),\n });\n },\n [action.name, action.url, action.method],\n );\n\n return { invoke, processing, progress: 0 };\n}\n","/**\n * `useArqelForm` — wraps Inertia `useForm` with Field-aware defaults.\n *\n * Phase 1 scope: seed initial state from `buildInitialFormState(fields, record)`,\n * proxy through Inertia's API, and stub `validate()`/`validateField()` returning\n * `true` until the Zod bridge ships in HOOKS-002 follow-up.\n */\n\nimport { buildInitialFormState } from '@arqel-dev/react/utils';\nimport type { FieldSchema } from '@arqel-dev/types/fields';\nimport type { FormDataConvertible } from '@inertiajs/core';\nimport type { InertiaFormProps } from '@inertiajs/react';\nimport { useForm } from '@inertiajs/react';\nimport { useMemo } from 'react';\n\ntype FormValues = Record<string, FormDataConvertible>;\n\nexport interface UseArqelFormOptions<TRecord> {\n fields: readonly FieldSchema[];\n record?: TRecord | null;\n defaults?: FormValues;\n}\n\nexport interface UseArqelFormExtensions {\n fields: readonly FieldSchema[];\n clientErrors: Record<string, string[]>;\n validate: () => boolean;\n validateField: (name: string) => boolean;\n}\n\nexport type UseArqelFormResult = InertiaFormProps<FormValues> & UseArqelFormExtensions;\n\nexport function useArqelForm<TRecord = Record<string, unknown>>(\n options: UseArqelFormOptions<TRecord>,\n): UseArqelFormResult {\n const { fields, record = null, defaults } = options;\n\n const initial = useMemo<FormValues>(() => {\n const recordObj = record as Record<string, unknown> | null;\n const seed = buildInitialFormState([...fields], recordObj) as FormValues;\n return defaults ? { ...seed, ...defaults } : seed;\n }, [fields, record, defaults]);\n\n // The Inertia useForm generic causes deep instantiation when the value type\n // is `Record<string, FormDataConvertible>`. The runtime call is fine; we just\n // narrow the result to InertiaFormProps<FormValues> via an unknown bridge.\n const callUseForm = useForm as unknown as (data: FormValues) => InertiaFormProps<FormValues>;\n const form = callUseForm(initial);\n\n return {\n ...form,\n fields,\n clientErrors: {},\n validate: () => true,\n validateField: () => true,\n };\n}\n","/**\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","/**\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","/**\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","/**\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","/**\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","/**\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","/**\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","/**\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","/**\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/dist/useAction.js CHANGED
@@ -6,7 +6,12 @@ function useAction(action) {
6
6
  const [processing, setProcessing] = useState(false);
7
7
  const invoke = useCallback(
8
8
  (record, payload = {}) => {
9
- const url = action.url ?? `/arqel-dev/actions/${action.name}`;
9
+ if (!action.url) {
10
+ throw new Error(
11
+ `Arqel: action "${action.name}" has no url to dispatch to. A custom action with ->action() must be declared on the Resource's actions() so the server can emit its endpoint URL; otherwise give it ->url().`
12
+ );
13
+ }
14
+ const url = action.url;
10
15
  const method = action.method.toLowerCase();
11
16
  const data = record ? { record_id: record.id, ...payload } : payload;
12
17
  setProcessing(true);
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/useAction.ts"],"names":[],"mappings":";;;;AAmBO,SAAS,UAAU,MAAA,EAAuC;AAC/D,EAAA,MAAM,CAAC,UAAA,EAAY,aAAa,CAAA,GAAI,SAAS,KAAK,CAAA;AAElD,EAAA,MAAM,MAAA,GAAS,WAAA;AAAA,IACb,CAAC,MAAA,EAAwC,OAAA,GAAmC,EAAC,KAAM;AACjF,MAAA,MAAM,GAAA,GAAM,MAAA,CAAO,GAAA,IAAO,CAAA,mBAAA,EAAsB,OAAO,IAAI,CAAA,CAAA;AAC3D,MAAA,MAAM,MAAA,GAAS,MAAA,CAAO,MAAA,CAAO,WAAA,EAAY;AACzC,MAAA,MAAM,IAAA,GAAO,SAAS,EAAE,SAAA,EAAW,OAAO,EAAA,EAAI,GAAG,SAAQ,GAAI,OAAA;AAE7D,MAAA,aAAA,CAAc,IAAI,CAAA;AAClB,MAAA,MAAA,CAAO,MAAM,GAAA,EAAK;AAAA,QAChB,MAAA;AAAA,QACA,IAAA;AAAA,QACA,cAAA,EAAgB,IAAA;AAAA,QAChB,QAAA,EAAU,MAAM,aAAA,CAAc,KAAK;AAAA,OACpC,CAAA;AAAA,IACH,CAAA;AAAA,IACA,CAAC,MAAA,CAAO,IAAA,EAAM,MAAA,CAAO,GAAA,EAAK,OAAO,MAAM;AAAA,GACzC;AAEA,EAAA,OAAO,EAAE,MAAA,EAAQ,UAAA,EAAY,QAAA,EAAU,CAAA,EAAE;AAC3C","file":"useAction.js","sourcesContent":["/**\n * `useAction` — invoke an Action via Inertia router.\n *\n * Phase 1 scope: thin wrapper around `router.visit` that exposes\n * `processing` and a `progress` stub (always 0). HOOKS-004 will wire\n * real Inertia progress events.\n */\n\nimport type { ActionSchema } from '@arqel-dev/types/actions';\nimport type { FormDataConvertible } from '@inertiajs/core';\nimport { router } from '@inertiajs/react';\nimport { useCallback, useState } from 'react';\n\nexport interface UseActionResult {\n invoke: (record: { id: string | number } | null, payload?: Record<string, unknown>) => void;\n processing: boolean;\n progress: number;\n}\n\nexport function useAction(action: ActionSchema): UseActionResult {\n const [processing, setProcessing] = useState(false);\n\n const invoke = useCallback(\n (record: { id: string | number } | null, payload: Record<string, unknown> = {}) => {\n const url = action.url ?? `/arqel-dev/actions/${action.name}`;\n const method = action.method.toLowerCase() as 'get' | 'post' | 'put' | 'patch' | 'delete';\n const data = record ? { record_id: record.id, ...payload } : payload;\n\n setProcessing(true);\n router.visit(url, {\n method,\n data: data as Record<string, FormDataConvertible>,\n preserveScroll: true,\n onFinish: () => setProcessing(false),\n });\n },\n [action.name, action.url, action.method],\n );\n\n return { invoke, processing, progress: 0 };\n}\n"]}
1
+ {"version":3,"sources":["../src/useAction.ts"],"names":[],"mappings":";;;;AAmBO,SAAS,UAAU,MAAA,EAAuC;AAC/D,EAAA,MAAM,CAAC,UAAA,EAAY,aAAa,CAAA,GAAI,SAAS,KAAK,CAAA;AAElD,EAAA,MAAM,MAAA,GAAS,WAAA;AAAA,IACb,CAAC,MAAA,EAAwC,OAAA,GAAmC,EAAC,KAAM;AAQjF,MAAA,IAAI,CAAC,OAAO,GAAA,EAAK;AACf,QAAA,MAAM,IAAI,KAAA;AAAA,UACR,CAAA,eAAA,EAAkB,OAAO,IAAI,CAAA,6KAAA;AAAA,SAG/B;AAAA,MACF;AACA,MAAA,MAAM,MAAM,MAAA,CAAO,GAAA;AACnB,MAAA,MAAM,MAAA,GAAS,MAAA,CAAO,MAAA,CAAO,WAAA,EAAY;AACzC,MAAA,MAAM,IAAA,GAAO,SAAS,EAAE,SAAA,EAAW,OAAO,EAAA,EAAI,GAAG,SAAQ,GAAI,OAAA;AAE7D,MAAA,aAAA,CAAc,IAAI,CAAA;AAClB,MAAA,MAAA,CAAO,MAAM,GAAA,EAAK;AAAA,QAChB,MAAA;AAAA,QACA,IAAA;AAAA,QACA,cAAA,EAAgB,IAAA;AAAA,QAChB,QAAA,EAAU,MAAM,aAAA,CAAc,KAAK;AAAA,OACpC,CAAA;AAAA,IACH,CAAA;AAAA,IACA,CAAC,MAAA,CAAO,IAAA,EAAM,MAAA,CAAO,GAAA,EAAK,OAAO,MAAM;AAAA,GACzC;AAEA,EAAA,OAAO,EAAE,MAAA,EAAQ,UAAA,EAAY,QAAA,EAAU,CAAA,EAAE;AAC3C","file":"useAction.js","sourcesContent":["/**\n * `useAction` — invoke an Action via Inertia router.\n *\n * Phase 1 scope: thin wrapper around `router.visit` that exposes\n * `processing` and a `progress` stub (always 0). HOOKS-004 will wire\n * real Inertia progress events.\n */\n\nimport type { ActionSchema } from '@arqel-dev/types/actions';\nimport type { FormDataConvertible } from '@inertiajs/core';\nimport { router } from '@inertiajs/react';\nimport { useCallback, useState } from 'react';\n\nexport interface UseActionResult {\n invoke: (record: { id: string | number } | null, payload?: Record<string, unknown>) => void;\n processing: boolean;\n progress: number;\n}\n\nexport function useAction(action: ActionSchema): UseActionResult {\n const [processing, setProcessing] = useState(false);\n\n const invoke = useCallback(\n (record: { id: string | number } | null, payload: Record<string, unknown> = {}) => {\n // The server emits a `url` for every dispatchable action — stock\n // verbs + bulk + custom callback actions all resolve through core's\n // authorised routes (see `Action::resolveStockUrl()`, #48/#231). A\n // missing `url` means the action is a misconfiguration (e.g. a custom\n // action serialised without a resource context); we must NOT silently\n // POST to the dead `/arqel-dev/actions/{name}` route removed in #174.\n // Surface it clearly instead of masking it as a 404.\n if (!action.url) {\n throw new Error(\n `Arqel: action \"${action.name}\" has no url to dispatch to. A custom ` +\n `action with ->action() must be declared on the Resource's actions() ` +\n `so the server can emit its endpoint URL; otherwise give it ->url().`,\n );\n }\n const url = action.url;\n const method = action.method.toLowerCase() as 'get' | 'post' | 'put' | 'patch' | 'delete';\n const data = record ? { record_id: record.id, ...payload } : payload;\n\n setProcessing(true);\n router.visit(url, {\n method,\n data: data as Record<string, FormDataConvertible>,\n preserveScroll: true,\n onFinish: () => setProcessing(false),\n });\n },\n [action.name, action.url, action.method],\n );\n\n return { invoke, processing, progress: 0 };\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arqel-dev/hooks",
3
- "version": "0.11.0",
3
+ "version": "0.14.0",
4
4
  "description": "Reusable React hooks for Arqel admin panels.",
5
5
  "license": "MIT",
6
6
  "homepage": "https://arqel.dev",
@@ -74,29 +74,29 @@
74
74
  "SKILL.md"
75
75
  ],
76
76
  "dependencies": {
77
- "@arqel-dev/types": "0.11.0"
77
+ "@arqel-dev/types": "0.14.0"
78
78
  },
79
79
  "peerDependencies": {
80
- "@inertiajs/react": "^2.0.0",
81
- "react": "^19.0.0",
82
- "react-dom": "^19.0.0",
83
- "@arqel-dev/react": "0.11.0"
80
+ "@inertiajs/react": "^3.0.0",
81
+ "react": "^19.2.0",
82
+ "react-dom": "^19.2.0",
83
+ "@arqel-dev/react": "0.14.0"
84
84
  },
85
85
  "devDependencies": {
86
- "@inertiajs/core": "^2.0.0",
87
- "@inertiajs/react": "^2.0.0",
86
+ "@inertiajs/core": "^3.0.0",
87
+ "@inertiajs/react": "^3.0.0",
88
88
  "@testing-library/jest-dom": "^6.6.3",
89
89
  "@testing-library/react": "^16.1.0",
90
90
  "@types/react": "^19.0.0",
91
91
  "@types/react-dom": "^19.0.0",
92
92
  "expect-type": "^1.2.2",
93
93
  "jsdom": "^25.0.1",
94
- "react": "^19.0.0",
95
- "react-dom": "^19.0.0",
94
+ "react": "^19.2.0",
95
+ "react-dom": "^19.2.0",
96
96
  "tsup": "^8.5.0",
97
97
  "typescript": "^5.6.3",
98
- "vitest": "^3.0.0",
99
- "@arqel-dev/react": "0.11.0"
98
+ "vitest": "^4.1.0",
99
+ "@arqel-dev/react": "0.14.0"
100
100
  },
101
101
  "publishConfig": {
102
102
  "access": "public"