@alfakit/core 0.1.9 → 0.1.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -0
- package/dist/actions.d.ts.map +1 -1
- package/dist/actions.js +1 -0
- package/dist/actions.js.map +1 -1
- package/dist/calendar.d.ts.map +1 -1
- package/dist/calendar.js +3 -0
- package/dist/calendar.js.map +1 -1
- package/dist/form.d.ts +6 -0
- package/dist/form.d.ts.map +1 -1
- package/dist/form.js +27 -3
- package/dist/form.js.map +1 -1
- package/dist/index.d.ts +6 -6
- package/dist/index.js +7 -7
- package/dist/page-actions.d.ts.map +1 -1
- package/dist/page-actions.js +30 -1
- package/dist/page-actions.js.map +1 -1
- package/dist/prefetch.d.ts +3 -1
- package/dist/prefetch.d.ts.map +1 -1
- package/dist/prefetch.js +37 -3
- package/dist/prefetch.js.map +1 -1
- package/dist/pwa.d.ts +12 -0
- package/dist/pwa.d.ts.map +1 -1
- package/dist/pwa.js +28 -1
- package/dist/pwa.js.map +1 -1
- package/dist/rich-editor.d.ts +11 -0
- package/dist/rich-editor.d.ts.map +1 -1
- package/dist/rich-editor.js +29 -1
- package/dist/rich-editor.js.map +1 -1
- package/dist/schema/panel.d.ts +7 -0
- package/dist/schema/panel.d.ts.map +1 -1
- package/dist/schema/panel.js.map +1 -1
- package/dist/shortcuts.d.ts +27 -0
- package/dist/shortcuts.d.ts.map +1 -1
- package/dist/shortcuts.js +53 -2
- package/dist/shortcuts.js.map +1 -1
- package/dist/starter-kit.d.ts +27 -2
- package/dist/starter-kit.d.ts.map +1 -1
- package/dist/starter-kit.js +32 -2
- package/dist/starter-kit.js.map +1 -1
- package/dist/table-layout.d.ts +6 -2
- package/dist/table-layout.d.ts.map +1 -1
- package/dist/table-layout.js +5 -3
- package/dist/table-layout.js.map +1 -1
- package/dist/table.d.ts.map +1 -1
- package/dist/table.js +5 -2
- package/dist/table.js.map +1 -1
- package/dist/undo.d.ts +2 -0
- package/dist/undo.d.ts.map +1 -1
- package/dist/undo.js +21 -1
- package/dist/undo.js.map +1 -1
- package/package.json +1 -1
- package/themes.css +4 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# @alfakit/core
|
|
2
2
|
|
|
3
|
+
## 0.1.10
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Pages restored by the browser's Back button reload when records changed since they were shown (and starter kits drop prefetched pages after a save); list-tab state on cell edits, reordering, wizard steps and field actions; single-use undo tokens; multi-key shortcut recording; starter-kit user menu links and Web Push toggle; translatable `live()` fields; deferred tables outside panels; mobile slide-over translate fix.
|
|
8
|
+
|
|
3
9
|
## 0.1.9
|
|
4
10
|
|
|
5
11
|
### Patch Changes
|
package/dist/actions.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"actions.d.ts","names":[],"sources":["../src/actions.ts"],"mappings":";;;;;;iBA6BiB;;EAEf;;EAEA;EACA,SAAS;;EAET,UAAU;;EAEV;EACA,YAAY;;;;;EAKZ;;;;;EAKA,OAAO;;iBAGQ;;EAEf;;;;;EAKA;;;qBAIW;;;;;wBAMG,mBACd,OAAO,yBACP,UAAU,yBACV,2CACC;iBAiBc;EACf,QAAQ;EACR,SAAS;EACT,OAAO;;EAEP,QAAQ;EACR;;EAEA,WAAW;;;;;EAKX;;iBAGe;;EAEf,QAAQ;;EAER;;;;;EAKA;;iBAGe;EACf;IAAa;IAAgB;;EAC7B,WAAW;;EAEX,aAAa,QAAQ,cAAc,SAAS;;;;;EAK5C,mBAAmB,eAAe;;;;;;qBAOvB;mBASkB;WARb,OAAO,MAAM;;mBAGZ;;mBAGA;EAEY,YAAA,SAAS;MAIlC,SAAS;;EAKb,uBACE,WAAW,eAAe;UAKpB;;EAOR,aAAa,WAAW,QAAQ;;;;;;EAShC,kBAAkB,QAAQ,cAAc,SAAS,eAAe,MAAM;;EAUtE,aAAa,SAAS,gBAAgB,MAAM;;;;;EAkB5C,YAAY,SAAS,gBAAgB,MAAM;;EAoBrC,QAAQ,QAAQ,cAAc,SAAS,gBAAgB;;;;;EA8DvD,SAAS,QAAQ,cAAc,SAAS,gBAAgB;;;;;EAkB9D,kBAAkB,QAAQ,cAAc,SAAS;;;;;EAqBjD,YAAY;;EAYZ,aAAa,SAAS,eAAe;EAY/B,UACJ,QAAQ,cACR,SAAS,eACT,WAAW,2BACV;UAyCK;;;;;EA0CF,WAAW,cAAc,WAAU,0BAA+B;;EAwCxE,UAAU,QAAQ,cAAc,SAAS;EAIzC,WAAW;;EAeX,QACE,QAAQ,cACR,SAAS,eACT,OAAM,yBACN,UAAS;;;;;EAmIL,YAAY,aAChB,QAAQ,cACR,SAAS,eACT,OAAM,0BACL,QAAQ;;;;;EAwCL,aACJ,QAAQ,cACR,SAAS,eACT,OAAM,0BACL;
|
|
1
|
+
{"version":3,"file":"actions.d.ts","names":[],"sources":["../src/actions.ts"],"mappings":";;;;;;iBA6BiB;;EAEf;;EAEA;EACA,SAAS;;EAET,UAAU;;EAEV;EACA,YAAY;;;;;EAKZ;;;;;EAKA,OAAO;;iBAGQ;;EAEf;;;;;EAKA;;;qBAIW;;;;;wBAMG,mBACd,OAAO,yBACP,UAAU,yBACV,2CACC;iBAiBc;EACf,QAAQ;EACR,SAAS;EACT,OAAO;;EAEP,QAAQ;EACR;;EAEA,WAAW;;;;;EAKX;;iBAGe;;EAEf,QAAQ;;EAER;;;;;EAKA;;iBAGe;EACf;IAAa;IAAgB;;EAC7B,WAAW;;EAEX,aAAa,QAAQ,cAAc,SAAS;;;;;EAK5C,mBAAmB,eAAe;;;;;;qBAOvB;mBASkB;WARb,OAAO,MAAM;;mBAGZ;;mBAGA;EAEY,YAAA,SAAS;MAIlC,SAAS;;EAKb,uBACE,WAAW,eAAe;UAKpB;;EAOR,aAAa,WAAW,QAAQ;;;;;;EAShC,kBAAkB,QAAQ,cAAc,SAAS,eAAe,MAAM;;EAUtE,aAAa,SAAS,gBAAgB,MAAM;;;;;EAkB5C,YAAY,SAAS,gBAAgB,MAAM;;EAoBrC,QAAQ,QAAQ,cAAc,SAAS,gBAAgB;;;;;EA8DvD,SAAS,QAAQ,cAAc,SAAS,gBAAgB;;;;;EAkB9D,kBAAkB,QAAQ,cAAc,SAAS;;;;;EAqBjD,YAAY;;EAYZ,aAAa,SAAS,eAAe;EAY/B,UACJ,QAAQ,cACR,SAAS,eACT,WAAW,2BACV;UAyCK;;;;;EA0CF,WAAW,cAAc,WAAU,0BAA+B;;EAwCxE,UAAU,QAAQ,cAAc,SAAS;EAIzC,WAAW;;EAeX,QACE,QAAQ,cACR,SAAS,eACT,OAAM,yBACN,UAAS;;;;;EAmIL,YAAY,aAChB,QAAQ,cACR,SAAS,eACT,OAAM,0BACL,QAAQ;;;;;EAwCL,aACJ,QAAQ,cACR,SAAS,eACT,OAAM,0BACL;UAkFK;UAWA;;;;;;;wBAqBM,iBACd,QAAQ,KAAK,2CACb,SAAS,KAAK;;YAMJ;;;;;wBAMI,kBACd,MAAM,yBACN,WAAU,mBACT;;wBAiBa,eAAe,MAAM;iBAIpB;EACf;EACA;;;;;;wBAOc,uBAAuB,uCAAuC;;wBAa9D,mBAAmB,SAAS,oBAAoB;;;;;;wBAWhD,qBAAqB,QAAQ,aAAa;;wBAqB1C,qBACd,OAAO,KAAK;;;;;wBAkBE,cACd,8CACA;;;;;wBAiBc,wBACd,OAAO,KAAK,2CACZ;EAAQ;;;;;;wBASM,kBACd,OAAO,KAAK,2CACZ;EAAQ;sBACR;;;;;;wBAcc,iBACd,OAAO,KAAK,2CACZ;EAAQ;sBACR;;wBA8Bc,kBAAkB,eAAe,kCAAkC;;;;;wBAQnE,eAAe;EAAY;GAAkB,OAAO,MAAM;;wBAmB1D,gBACd,OAAO,mBACP,QAAQ,cACR,SAAS;;wBAMK,mBACd,OAAO,mBACP,QAAQ,cACR,SAAS;;;;;wBAmBK,eACd,kBACA,SAAS,KAAK,8BACd"}
|
package/dist/actions.js
CHANGED
package/dist/actions.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"actions.js","names":[],"sources":["../src/actions.ts"],"sourcesContent":["import type { Page } from '@inertiajs/core'\nimport { revealAutofocusField } from './autofocus'\nimport {\n CONFLICT_ERROR_KEY,\n currentRecordVersion,\n markRecordSubmitted,\n RECORD_VERSION_INPUT,\n reportSaveConflict,\n} from './conflicts'\nimport { isFieldActionContext } from './field-actions'\nimport { pageFlash } from './flash'\nimport { isLeavingVisit, type FormStore } from './form'\nimport { HttpError, inertiaPost, requestJson, router } from './http'\nimport { ACTION_PATH_SEPARATOR, actionPath, PASSWORD_CONFIRMATION_INPUT } from './modal-actions'\nimport { openDatabaseNotifications } from './notifications'\nimport { submitFormById } from './page-actions'\nimport { currentPageQuery } from './page-query'\nimport type {\n ActionModalCancelledResponse,\n ActionModalSchema,\n ActionSchema,\n AnyActionSchema,\n} from './schema/actions'\nimport { isActionGroup } from './schema/actions'\nimport type { RecordKey, Size } from './schema/common'\nimport type { FieldActionResponse } from './schema/form'\nimport type { NotificationSchema } from './schema/notifications'\nimport { createStore, type Store } from './store'\n\nexport interface ActionContext {\n /** Encrypted page context token. */\n token: string\n /** `page`, `table`, `relationManager:Class`, `widget:Class`. */\n component?: string\n record?: RecordKey | null\n /** Selected record keys for bulk actions. */\n records?: RecordKey[]\n /** \"All filtered records\" bulk selection. */\n allRecords?: boolean\n arguments?: Record<string, unknown>\n /**\n * Names of the actions whose modals this action sits in, outermost first (actions in a\n * modal's footer). The server resolves the action by its path, `parent>child`.\n */\n parents?: string[]\n /**\n * Field actions (`component` = `field:{schema}::{key}`): the form the field belongs to.\n * Its state is sent with the action and the refreshed form is merged back into it.\n */\n form?: FormStore\n}\n\nexport interface ExecuteOptions {\n /** The current password, for actions with `requiresPasswordConfirmation()`. */\n password?: string\n /**\n * \"Create & create another\": run with the `another` argument and, when the server keeps\n * the modal open, reload it with a fresh form (keeping `preserveFormData` fields).\n */\n createAnother?: boolean\n}\n\n/** The argument `CreateAction` reads for \"Create & create another\". */\nexport const CREATE_ANOTHER_ARGUMENT = 'another'\n\n/**\n * The state of the fresh form after \"Create & create another\": the server's defaults,\n * plus the previous values of the fields `preserveFormData` names (all of them for `true`).\n */\nexport function createAnotherState(\n fresh: Record<string, unknown>,\n previous: Record<string, unknown>,\n preserve: boolean | string[] | undefined,\n): Record<string, unknown> {\n if (!preserve) {\n return fresh\n }\n\n const keys = preserve === true ? Object.keys(previous) : preserve\n const state = { ...fresh }\n\n for (const key of keys) {\n if (Object.prototype.hasOwnProperty.call(previous, key)) {\n state[key] = previous[key]\n }\n }\n\n return state\n}\n\nexport interface OpenModal {\n action: ActionSchema\n context: ActionContext\n modal: ActionModalSchema\n /** Current form values of the modal form. */\n errors: Record<string, string[]>\n processing: boolean\n /** Field actions: settles `runField()` with the action's result (undefined when closed). */\n resolve?: (result: unknown) => void\n /**\n * Bumped by `resetModal()` (\"Create & create another\"): the renderer focuses the form's\n * `autofocus()` field again (`focusAutofocusField()`) once the fresh form rendered.\n */\n refocus?: number\n}\n\nexport interface ActionRunnerState {\n /** Stack of open modals (nested modals are possible). */\n modals: OpenModal[]\n /** Name of the action currently loading its modal. */\n loading: string | null\n /**\n * Which run of it (component, action path and record): a table renders the same action\n * once per row, and only the clicked row's button is loading (`isActionLoading()`).\n */\n loadingKey?: string | null\n}\n\nexport interface ActionRunnerOptions {\n endpoints: { action: string; modal: string }\n onError?: (error: unknown) => void\n /** Called after a successful run so the UI can e.g. clear a table selection. */\n onSuccess?: (action: ActionSchema, context: ActionContext) => void\n /**\n * Shows notifications that arrive in JSON responses (a modal that `mountUsing()`\n * cancelled, field actions). Renderers can also set it with `setNotificationHandler()`.\n */\n onNotifications?: (notifications: NotificationSchema[]) => void\n}\n\n/**\n * Drives action buttons: plain links navigate, actions without a modal post straight to\n * the server, and actions with a modal fetch the modal schema first.\n */\nexport class ActionRunner {\n public readonly store: Store<ActionRunnerState>\n\n /** Runs in flight, so a double click or a repeated shortcut doesn't submit twice. */\n private readonly running = new Set<string>()\n\n /** The form of each open modal (registered by the renderer), by modal path. */\n private readonly modalForms = new Map<string, FormStore>()\n\n constructor(private readonly options: ActionRunnerOptions) {\n this.store = createStore<ActionRunnerState>({ modals: [], loading: null, loadingKey: null })\n }\n\n get state(): ActionRunnerState {\n return this.store.get()\n }\n\n /** Show notifications from JSON responses with this handler (e.g. the renderer's toasts). */\n setNotificationHandler(\n handler: ((notifications: NotificationSchema[]) => void) | undefined,\n ): void {\n this.options.onNotifications = handler\n }\n\n private notify(notifications: NotificationSchema[] | undefined): void {\n if (notifications && notifications.length > 0) {\n this.options.onNotifications?.(notifications)\n }\n }\n\n /** Point the runner at the current panel's endpoints (they change per tenant/panel). */\n setEndpoints(endpoints: Partial<ActionRunnerOptions['endpoints']>): void {\n Object.assign(this.options.endpoints, endpoints)\n }\n\n /**\n * The renderer registers the form of an open modal, so nested actions can send its state\n * (`$mountedActions`, `getRawData()`) and `fillData()` can write into it. Returns the\n * function that unregisters it.\n */\n registerModalForm(action: ActionSchema, context: ActionContext, form: FormStore): () => void {\n const key = actionPath(action, context)\n this.modalForms.set(key, form)\n\n return () => {\n if (this.modalForms.get(key) === form) this.modalForms.delete(key)\n }\n }\n\n /** The open modals of the actions a nested action sits in, outermost first. */\n parentModals(context: ActionContext): Array<OpenModal | undefined> {\n const parents = context.parents ?? []\n\n return parents.map((_, index) => {\n const path = parents.slice(0, index + 1).join(ACTION_PATH_SEPARATOR)\n\n return this.state.modals.find(\n (modal) =>\n modal.context.component === context.component &&\n actionPath(modal.action, modal.context) === path,\n )\n })\n }\n\n /**\n * The form state of the modals a nested action sits in, outermost first (undefined when\n * none of them has a form).\n */\n mountedData(context: ActionContext): Array<Record<string, unknown>> | undefined {\n const parents = context.parents ?? []\n\n if (parents.length === 0) {\n return undefined\n }\n\n const forms = parents.map((_, index) =>\n this.modalForms.get(parents.slice(0, index + 1).join(ACTION_PATH_SEPARATOR)),\n )\n\n // Parents without a form have nothing to send.\n if (forms.every((form) => !form)) {\n return undefined\n }\n\n return forms.map((form) => form?.state.values ?? {})\n }\n\n /** Entry point for a click on an action button. */\n async trigger(action: ActionSchema, context: ActionContext): Promise<void> {\n if (action.disabled) {\n return\n }\n\n // `openDatabaseNotifications()`: a client-side button.\n if (action.opensDatabaseNotifications) {\n openDatabaseNotifications()\n\n return\n }\n\n // `makeModalSubmitAction()`: submit the modal this button sits in, with its arguments.\n if (action.submitsModal) {\n this.submitParentModal(action, context)\n\n return\n }\n\n // `formId()`: the page form submits itself with its state, wherever the button is.\n if (submitFormById(action, context)) {\n return\n }\n\n // `postToUrl()`: a POST request to the URL (e.g. \"lock session\"), not a visit.\n if (action.url && action.postToUrl) {\n const { router } = await import('@inertiajs/core')\n router.post(action.url)\n\n return\n }\n\n if (action.url && !action.hasAction && !action.hasModal) {\n if (action.openUrlInNewTab) {\n window.open(action.url, '_blank', 'noopener')\n } else {\n const { router } = await import('@inertiajs/core')\n router.visit(action.url)\n }\n\n return\n }\n\n if (isFieldActionContext(context)) {\n await this.runField(action, context)\n\n return\n }\n\n if (action.hasModal) {\n await this.openModal(action, context)\n\n return\n }\n\n this.execute(action, context)\n }\n\n /**\n * Run a field action (opening its modal first, if it has one) and resolve with its\n * result once it ran, or with undefined when the modal is closed without running it.\n */\n async runField(action: ActionSchema, context: ActionContext): Promise<unknown> {\n if (action.disabled) {\n return undefined\n }\n\n if (action.hasModal) {\n return new Promise<unknown>((resolve) => {\n void this.openModal(action, context, resolve)\n })\n }\n\n return this.executeField(action, context)\n }\n\n /**\n * Run the action whose modal `button` sits in (a `makeModalSubmitAction()` button) with\n * that modal's form state and the button's arguments.\n */\n submitParentModal(button: ActionSchema, context: ActionContext): void {\n const parents = context.parents ?? []\n const open = this.parentModals(context).at(-1)\n\n if (!open || parents.length === 0) {\n return\n }\n\n const form = this.modalForms.get(parents.join(ACTION_PATH_SEPARATOR))\n\n this.execute(\n open.action,\n { ...open.context, arguments: { ...open.context.arguments, ...button.arguments } },\n form?.state.values ?? {},\n )\n }\n\n /**\n * Close a modal the user dismissed (cancel, Escape, clicking away, close button). With\n * `cancelParentActionsOnClose()` its parents close too.\n */\n cancelModal(name: string): void {\n const open = this.state.modals.find((modal) => modal.action.name === name)\n const cancel = open?.modal.cancelParentActionsOnClose\n\n this.closeModal(name)\n\n if (open && cancel) {\n this.closeParents(open.context, cancel)\n }\n }\n\n /** Close the modals `context` sits in: all of them, or up to and including `until`. */\n closeParents(context: ActionContext, until: true | string): void {\n const parents = [...(context.parents ?? [])].reverse()\n\n for (const parent of parents) {\n this.closeModal(parent)\n\n if (until !== true && parent === until) {\n break\n }\n }\n }\n\n async openModal(\n action: ActionSchema,\n context: ActionContext,\n resolve?: (result: unknown) => void,\n ): Promise<void> {\n const key = `modal:${runKey(action, context)}`\n\n if (this.running.has(key)) {\n resolve?.(undefined)\n\n return\n }\n\n this.running.add(key)\n this.store.update({ loading: action.name, loadingKey: runKey(action, context) })\n\n try {\n const modal = await this.fetchModal(action, context)\n\n // `mountUsing()` halted or cancelled the action: the modal stays closed.\n if (isCancelledModal(modal)) {\n this.store.update({ loading: null, loadingKey: null })\n this.notify(modal.notifications)\n resolve?.(undefined)\n\n return\n }\n\n this.store.set((state) => ({\n loading: null,\n loadingKey: null,\n modals: [\n ...state.modals,\n { action, context, modal, errors: {}, processing: false, resolve },\n ],\n }))\n } catch (error) {\n this.store.update({ loading: null, loadingKey: null })\n resolve?.(undefined)\n this.options.onError?.(error)\n } finally {\n this.running.delete(key)\n }\n }\n\n private fetchModal(\n action: ActionSchema,\n context: ActionContext,\n ): Promise<ActionModalSchema | ActionModalCancelledResponse> {\n // Bulk actions send their selection (and, for \"select all\", the page's table state), so\n // the server resolves the bulk action rather than a record action and fills `$records`.\n const records = context.records\n const isBulk = records !== undefined\n const params = {\n token: context.token,\n component: context.component,\n action: actionPath(action, context),\n record: context.record ?? undefined,\n records: records !== undefined && records.length > 0 ? records : undefined,\n allRecords: context.allRecords ? 1 : undefined,\n bulk: isBulk ? 1 : undefined,\n // The table state (active tab, filters, search) for record and bulk actions alike: a\n // record action on a non-default tab resolves its record through that tab's query.\n tableQuery: currentPageQuery(),\n arguments: context.arguments,\n }\n const mountedData = this.mountedData(context)\n\n // Field actions send the form state, and nested actions their parents' form state,\n // which needs a request body.\n if (isFieldActionContext(context)) {\n return requestJson<ActionModalSchema | ActionModalCancelledResponse>(\n this.options.endpoints.modal,\n { method: 'POST', body: { ...params, state: context.form?.state.values ?? {} } },\n )\n }\n\n return requestJson<ActionModalSchema | ActionModalCancelledResponse>(\n this.options.endpoints.modal,\n mountedData ? { method: 'POST', body: { ...params, mountedData } } : { query: params },\n )\n }\n\n /**\n * After \"Create & create another\": load the modal again so its form starts from the\n * server's defaults, keeping the `preserveFormData` fields from the values just sent.\n */\n async resetModal(name: string, previous: Record<string, unknown> = {}): Promise<void> {\n const open = this.state.modals.find((modal) => modal.action.name === name)\n\n if (!open) {\n return\n }\n\n try {\n const modal = await this.fetchModal(open.action, open.context)\n\n if (isCancelledModal(modal)) {\n this.notify(modal.notifications)\n this.closeModal(name)\n\n return\n }\n\n if (modal.form) {\n modal.form = {\n ...modal.form,\n // A wizard goes back to the step holding the `autofocus()` field.\n components: revealAutofocusField(modal.form.components),\n state: createAnotherState(modal.form.state, previous, modal.preserveFormData),\n }\n }\n\n this.store.set((state) => ({\n ...state,\n modals: state.modals.map((entry) =>\n entry === open || entry.action.name === name\n ? { ...entry, modal, errors: {}, refocus: (entry.refocus ?? 0) + 1 }\n : entry,\n ),\n }))\n } catch (error) {\n this.options.onError?.(error)\n }\n }\n\n /** Whether this action is currently being submitted for this context. */\n isRunning(action: ActionSchema, context: ActionContext): boolean {\n return this.running.has(runKey(action, context))\n }\n\n closeModal(name?: string): void {\n const modals = this.state.modals\n const remaining = name\n ? modals.filter((modal) => modal.action.name !== name)\n : modals.slice(0, -1)\n\n this.store.set((state) => ({ ...state, modals: remaining }))\n\n // A field action's caller learns that its modal closed without running.\n for (const modal of modals) {\n if (!remaining.includes(modal)) modal.resolve?.(undefined)\n }\n }\n\n /** Submit the topmost modal (or run a modal-less action) through Inertia. */\n execute(\n action: ActionSchema,\n context: ActionContext,\n data: Record<string, unknown> = {},\n options: ExecuteOptions = {},\n ): void {\n if (isFieldActionContext(context)) {\n void this.executeField(action, context, data)\n\n return\n }\n\n const key = runKey(action, context)\n\n if (this.running.has(key)) {\n return\n }\n\n this.running.add(key)\n this.setProcessing(action.name, true)\n\n // Edit pages (`detectConflicts()`): page actions send the version the form was loaded\n // from, so the server refuses to save over somebody else's changes.\n const version = context.component === 'page' ? currentRecordVersion() : null\n\n if (version !== null) markRecordSubmitted()\n\n inertiaPost(\n actionEndpoint(this.options.endpoints.action, context),\n {\n ...(version !== null ? { [RECORD_VERSION_INPUT]: version } : {}),\n token: context.token,\n component: context.component,\n action: actionPath(action, context),\n record: context.record ?? undefined,\n records: context.records,\n allRecords: context.allRecords ? 1 : undefined,\n bulk: context.records !== undefined ? 1 : undefined,\n arguments: options.createAnother\n ? { ...context.arguments, [CREATE_ANOTHER_ARGUMENT]: true }\n : context.arguments,\n tableQuery: currentPageQuery(),\n data,\n mountedData: this.mountedData(context),\n [PASSWORD_CONFIRMATION_INPUT]: options.password || undefined,\n },\n {\n onError: (errors) => {\n if (CONFLICT_ERROR_KEY in (errors as Record<string, unknown>)) reportSaveConflict()\n\n // A nested action without a modal of its own (e.g. `getValidatedData()` on its\n // parent failed): the parent's modal shows the errors.\n const hasModal = this.state.modals.some((modal) => modal.action.name === action.name)\n const parent = hasModal ? undefined : this.parentModals(context).at(-1)\n\n this.setErrors(\n parent ? parent.action.name : action.name,\n errors as Record<string, string>,\n )\n },\n onSuccess: (page) => {\n const flash = pageFlash(page as Page).alfakitAction\n\n // `fillData()`: values written into the parents' forms.\n for (const filled of flash?.parentData ?? []) {\n const parents = context.parents ?? []\n const index = parents.lastIndexOf(filled.name)\n const form =\n index >= 0\n ? this.modalForms.get(parents.slice(0, index + 1).join(ACTION_PATH_SEPARATOR))\n : undefined\n\n for (const [path, value] of Object.entries(filled.data)) {\n form?.setValue(path, value, { silent: true })\n }\n }\n\n if (\n !flash ||\n flash.status === 'cancelled' ||\n (flash.status === 'success' && flash.closeModal !== false)\n ) {\n this.closeModal(action.name)\n }\n\n // `cancelParentActions()`: the modals this nested action was opened from close too.\n if (flash?.status === 'success' && flash.cancelParentActions) {\n this.closeParents(\n context,\n typeof flash.cancelParentActions === 'string' ? flash.cancelParentActions : true,\n )\n }\n\n // `replaceMountedAction()`: the next action opens (or runs) in place of this one.\n const replacement = flash?.status === 'success' ? flash.replaceMountedAction : undefined\n\n if (replacement) {\n const next: ActionSchema = {\n type: 'Action',\n name: replacement.name,\n hasModal: replacement.hasModal || undefined,\n hasAction: true,\n }\n const nextContext: ActionContext = {\n ...context,\n arguments: replacement.arguments ?? undefined,\n }\n\n if (replacement.hasModal) {\n void this.openModal(next, nextContext)\n } else {\n this.execute(next, nextContext)\n }\n }\n\n if (flash?.status === 'success' && flash.closeModal === false && options.createAnother) {\n void this.resetModal(action.name, data)\n }\n\n if (!flash || flash.status === 'success') {\n this.options.onSuccess?.(action, context)\n }\n },\n onFinish: () => {\n this.running.delete(key)\n this.setProcessing(action.name, false)\n },\n },\n )\n }\n\n /**\n * Run an action and resolve with its JSON result (for callers that don't want an\n * Inertia visit, e.g. a custom widget).\n */\n async executeJson<T = unknown>(\n action: ActionSchema,\n context: ActionContext,\n data: Record<string, unknown> = {},\n ): Promise<T> {\n try {\n const response = await requestJson<{ result: T }>(\n actionEndpoint(this.options.endpoints.action, context),\n {\n method: 'POST',\n body: {\n token: context.token,\n component: context.component,\n action: actionPath(action, context),\n record: context.record ?? undefined,\n records: context.records,\n allRecords: context.allRecords ? 1 : undefined,\n bulk: context.records !== undefined ? 1 : undefined,\n arguments: context.arguments,\n tableQuery: currentPageQuery(),\n data,\n },\n },\n )\n\n return response.result\n } catch (error) {\n if (error instanceof HttpError && error.status === 422) {\n this.setErrors(\n action.name,\n Object.fromEntries(\n Object.entries(error.errors).map(([key, messages]) => [key, messages[0] ?? '']),\n ),\n )\n }\n\n throw error\n }\n }\n\n /**\n * Run a field action as JSON with its form's state; merge the refreshed form from the\n * response, settle the modal's `runField()` promise and close the modal.\n */\n async executeField(\n action: ActionSchema,\n context: ActionContext,\n data: Record<string, unknown> = {},\n ): Promise<unknown> {\n const key = runKey(action, context)\n\n if (this.running.has(key)) {\n return undefined\n }\n\n this.running.add(key)\n this.setProcessing(action.name, true)\n\n const form = context.form\n const sent = form?.state.values ?? {}\n\n try {\n const response = await requestJson<FieldActionResponse>(\n actionEndpoint(this.options.endpoints.action, context),\n {\n method: 'POST',\n body: {\n token: context.token,\n component: context.component,\n action: actionPath(action, context),\n record: context.record ?? undefined,\n arguments: context.arguments,\n data,\n state: sent,\n fingerprints: form?.currentFingerprints,\n },\n },\n )\n\n // A partial response that doesn't fit the tree falls back to a full refresh.\n if (response.form && form && !form.applyResponse(response.form, sent)) {\n void form.refresh()\n }\n\n this.notify(response.notifications)\n\n if (response.status === 'halted') {\n return undefined\n }\n\n if (response.status === 'success') {\n const modal = this.state.modals.find((open) => open.action.name === action.name)\n modal?.resolve?.(response.result)\n }\n\n if (response.status === 'cancelled' || response.closeModal !== false) {\n this.closeModal(action.name)\n }\n\n if (response.status === 'success') {\n this.options.onSuccess?.(action, context)\n\n if (response.redirect) {\n const { router } = await import('@inertiajs/core')\n router.visit(response.redirect)\n }\n }\n\n return response.status === 'success' ? response.result : undefined\n } catch (error) {\n if (error instanceof HttpError && error.status === 422) {\n this.setErrors(\n action.name,\n Object.fromEntries(\n Object.entries(error.errors).map(([field, messages]) => [field, messages[0] ?? '']),\n ),\n )\n } else {\n this.options.onError?.(error)\n }\n\n return undefined\n } finally {\n this.running.delete(key)\n this.setProcessing(action.name, false)\n }\n }\n\n private setProcessing(name: string, processing: boolean): void {\n this.store.set((state) => ({\n ...state,\n modals: state.modals.map((modal) =>\n modal.action.name === name\n ? { ...modal, processing, errors: processing ? {} : modal.errors }\n : modal,\n ),\n }))\n }\n\n private setErrors(name: string, errors: Record<string, string>): void {\n const normalized: Record<string, string[]> = {}\n\n for (const [key, message] of Object.entries(errors)) {\n normalized[key] = [message]\n }\n\n this.store.set((state) => ({\n ...state,\n modals: state.modals.map((modal) =>\n modal.action.name === name ? { ...modal, errors: normalized } : modal,\n ),\n }))\n }\n}\n\n/**\n * Whether an action button should bind its key bindings globally. Record actions repeat\n * on every row, so a page-wide shortcut would run them once per record; they only bind\n * when rendered outside a record (header, page and bulk actions).\n */\nexport function bindsKeyBindings(\n action: Pick<ActionSchema, 'keyBindings' | 'disabled'>,\n context: Pick<ActionContext, 'record'>,\n): boolean {\n return Boolean(action.keyBindings?.length) && !action.disabled && context.record == null\n}\n\n/** Button sizes every renderer's shadcn `Button` understands. */\nexport type ActionButtonSize = 'xs' | 'sm' | 'default' | 'lg'\n\n/**\n * The button size for an action: its own `size()` wins over the size the surrounding\n * list asks for (`md` is the default button, `xl` the largest one).\n */\nexport function resolveActionSize(\n size: Size | null | undefined,\n fallback: ActionButtonSize = 'default',\n): ActionButtonSize {\n switch (size) {\n case 'xs':\n return 'xs'\n case 'sm':\n return 'sm'\n case 'md':\n return 'default'\n case 'lg':\n case 'xl':\n return 'lg'\n default:\n return fallback\n }\n}\n\n/** The square icon-only button that matches a button size. */\nexport function iconButtonSize(size: ActionButtonSize): 'icon-xs' | 'icon-sm' | 'icon' {\n return size === 'xs' ? 'icon-xs' : size === 'sm' ? 'icon-sm' : 'icon'\n}\n\nexport interface DropdownPlacement {\n side: 'top' | 'right' | 'bottom' | 'left'\n align: 'start' | 'center' | 'end'\n}\n\n/**\n * Parse an ActionGroup `dropdownPlacement()` (`bottom-start`, `top-end`, `left`, …) into the\n * side/align pair dropdown primitives take. Defaults to `bottom` / `end`.\n */\nexport function parseDropdownPlacement(placement: string | null | undefined): DropdownPlacement {\n const [side, align] = (placement ?? '').split('-')\n const sides = ['top', 'right', 'bottom', 'left'] as const\n const aligns = ['start', 'center', 'end'] as const\n\n const knownSide = sides.find((candidate) => candidate === side)\n const resolvedAlign =\n aligns.find((candidate) => candidate === align) ?? (knownSide && !align ? 'center' : 'end')\n\n return { side: knownSide ?? 'bottom', align: resolvedAlign }\n}\n\n/** Every action inside a group, nested groups flattened. */\nexport function flattenActionGroup(actions: AnyActionSchema[]): ActionSchema[] {\n return actions.flatMap((action) =>\n isActionGroup(action) ? flattenActionGroup(action.actions) : [action],\n )\n}\n\n/**\n * Whether an open modal is hidden because a nested modal was opened from it. Nested modals\n * hide their parents unless they opt into `overlayParentActions()`; a hidden parent shows\n * again when the child closes.\n */\nexport function isModalHiddenByChild(modals: OpenModal[], index: number): boolean {\n const modal = modals[index]\n\n if (!modal) {\n return false\n }\n\n const path = actionPath(modal.action, modal.context)\n\n return modals.slice(index + 1).some((child) => {\n if (child.modal.overlayParent === true) return false\n\n const parents = child.context.parents ?? []\n\n return parents.some(\n (_, position) => parents.slice(0, position + 1).join(ACTION_PATH_SEPARATOR) === path,\n )\n })\n}\n\n/** Justify class keyword for modal footer buttons: `footerActionsAlignment`, else `alignment`. */\nexport function modalFooterAlignment(\n modal: Pick<ActionModalSchema, 'alignment' | 'footerActionsAlignment'>,\n): 'start' | 'center' | 'end' | 'between' {\n const alignment = modal.footerActionsAlignment ?? modal.alignment\n\n switch (alignment) {\n case 'start':\n case 'center':\n case 'between':\n return alignment\n default:\n return 'end'\n }\n}\n\n/**\n * The physical side a slide-over enters from: `end` (default) is the right edge in\n * left-to-right documents and the left edge in right-to-left ones.\n */\nexport function slideOverSide(\n position: 'start' | 'end' | null | undefined,\n direction: 'ltr' | 'rtl' = 'ltr',\n): 'left' | 'right' {\n const atStart = position === 'start'\n\n return atStart === (direction === 'rtl') ? 'right' : 'left'\n}\n\nfunction isCancelledModal(\n modal: ActionModalSchema | ActionModalCancelledResponse,\n): modal is ActionModalCancelledResponse {\n return (modal as Partial<ActionModalCancelledResponse>).cancelled === true\n}\n\n/**\n * Whether closing a modal has to ask first: its form changed and the modal has\n * `unsavedChangesAlert` (the action's own setting or the panel's `unsavedChangesAlerts()`).\n */\nexport function shouldConfirmModalClose(\n modal: Pick<ActionModalSchema, 'unsavedChangesAlert'>,\n form: { isDirty: boolean } | null | undefined,\n): boolean {\n return Boolean(modal.unsavedChangesAlert && form?.isDirty)\n}\n\n/**\n * Ask whether to close a modal with unsaved changes (`window.confirm`); true when it may\n * close. Without changes (or the alert) it never asks.\n */\nexport function confirmModalClose(\n modal: Pick<ActionModalSchema, 'unsavedChangesAlert'>,\n form: { isDirty: boolean } | null | undefined,\n message: string,\n): boolean {\n if (!shouldConfirmModalClose(modal, form) || typeof window === 'undefined') {\n return true\n }\n\n return window.confirm(message)\n}\n\n/**\n * Warn before leaving the page (a reload, or an Inertia visit when `message` is given)\n * while a modal form with `unsavedChangesAlert` has unsaved changes. Returns a function\n * that removes the listeners.\n */\nexport function guardModalUnload(\n modal: Pick<ActionModalSchema, 'unsavedChangesAlert'>,\n form: { isDirty: boolean } | null | undefined,\n message?: string,\n): () => void {\n if (!modal.unsavedChangesAlert || !form || typeof window === 'undefined') {\n return () => {}\n }\n\n const listener = (event: BeforeUnloadEvent) => {\n if (form.isDirty) {\n event.preventDefault()\n event.returnValue = ''\n }\n }\n\n window.addEventListener('beforeunload', listener)\n\n const removeVisitGuard = message\n ? router.on('before', (event) => {\n if (form.isDirty && isLeavingVisit(event.detail.visit) && !window.confirm(message)) {\n event.preventDefault()\n }\n })\n : () => {}\n\n return () => {\n window.removeEventListener('beforeunload', listener)\n removeVisitGuard()\n }\n}\n\n/** The context of a user or tenant menu action: the page's token and the menu's component. */\nexport function menuActionContext(token: string, menu: 'userMenu' | 'tenantMenu'): ActionContext {\n return { token, component: menu }\n}\n\n/**\n * Split menu entries into their groups (`group` index, nested arrays in\n * `userMenuItems()`), in order; renderers draw a separator between groups.\n */\nexport function groupMenuItems<T extends { group?: number }>(items: T[]): T[][] {\n const groups: T[][] = []\n let current: number | null = null\n\n for (const item of items) {\n const group = item.group ?? 0\n\n if (current === null || group !== current) {\n groups.push([])\n current = group\n }\n\n groups[groups.length - 1]!.push(item)\n }\n\n return groups\n}\n\n/** Whether this button's run (this action for this record) is fetching its modal. */\nexport function isActionLoading(\n state: ActionRunnerState,\n action: ActionSchema,\n context: ActionContext,\n): boolean {\n return state.loadingKey === runKey(action, context)\n}\n\n/** Whether this button's run (this action for this record) has a modal that is submitting. */\nexport function isActionProcessing(\n state: ActionRunnerState,\n action: ActionSchema,\n context: ActionContext,\n): boolean {\n const key = runKey(action, context)\n\n return state.modals.some(\n (modal) => modal.processing && runKey(modal.action, modal.context) === key,\n )\n}\n\nfunction runKey(action: Pick<ActionSchema, 'name'>, context: ActionContext): string {\n return [context.component ?? 'page', actionPath(action, context), context.record ?? ''].join(\n '\\u0000',\n )\n}\n\n/**\n * The action endpoint for a run. \"All records\" bulk actions carry the page's query string\n * (filters, search, list tab), so the server selects exactly the records the table shows.\n */\nexport function actionEndpoint(\n endpoint: string,\n context: Pick<ActionContext, 'allRecords'>,\n search: string = typeof window !== 'undefined' ? window.location.search : '',\n): string {\n if (!context.allRecords || search.length <= 1) {\n return endpoint\n }\n\n return endpoint + (endpoint.includes('?') ? '&' : '?') + search.replace(/^\\?/, '')\n}\n"],"mappings":";;;;;;;;;;;;;;AA+DA,MAAa,0BAA0B;;;;;AAMvC,SAAgB,mBACd,OACA,UACA,UACyB;CACzB,IAAI,CAAC,UACH,OAAO;CAGT,MAAM,OAAO,aAAa,OAAO,OAAO,KAAK,QAAQ,IAAI;CACzD,MAAM,QAAQ,EAAE,GAAG,MAAM;CAEzB,KAAK,MAAM,OAAO,MAChB,IAAI,OAAO,UAAU,eAAe,KAAK,UAAU,GAAG,GACpD,MAAM,OAAO,SAAS;CAI1B,OAAO;AACT;;;;;AA8CA,IAAa,eAAb,MAA0B;CASK;CAR7B;;CAGA,0BAA2B,IAAI,IAAY;;CAG3C,6BAA8B,IAAI,IAAuB;CAEzD,YAAY,SAA+C;EAA9B,KAAA,UAAA;EAC3B,KAAK,QAAQ,YAA+B;GAAE,QAAQ,CAAC;GAAG,SAAS;GAAM,YAAY;EAAK,CAAC;CAC7F;CAEA,IAAI,QAA2B;EAC7B,OAAO,KAAK,MAAM,IAAI;CACxB;;CAGA,uBACE,SACM;EACN,KAAK,QAAQ,kBAAkB;CACjC;CAEA,OAAe,eAAuD;EACpE,IAAI,iBAAiB,cAAc,SAAS,GAC1C,KAAK,QAAQ,kBAAkB,aAAa;CAEhD;;CAGA,aAAa,WAA4D;EACvE,OAAO,OAAO,KAAK,QAAQ,WAAW,SAAS;CACjD;;;;;;CAOA,kBAAkB,QAAsB,SAAwB,MAA6B;EAC3F,MAAM,MAAM,WAAW,QAAQ,OAAO;EACtC,KAAK,WAAW,IAAI,KAAK,IAAI;EAE7B,aAAa;GACX,IAAI,KAAK,WAAW,IAAI,GAAG,MAAM,MAAM,KAAK,WAAW,OAAO,GAAG;EACnE;CACF;;CAGA,aAAa,SAAsD;EACjE,MAAM,UAAU,QAAQ,WAAW,CAAC;EAEpC,OAAO,QAAQ,KAAK,GAAG,UAAU;GAC/B,MAAM,OAAO,QAAQ,MAAM,GAAG,QAAQ,CAAC,CAAC,CAAC,KAAA,GAA0B;GAEnE,OAAO,KAAK,MAAM,OAAO,MACtB,UACC,MAAM,QAAQ,cAAc,QAAQ,aACpC,WAAW,MAAM,QAAQ,MAAM,OAAO,MAAM,IAChD;EACF,CAAC;CACH;;;;;CAMA,YAAY,SAAoE;EAC9E,MAAM,UAAU,QAAQ,WAAW,CAAC;EAEpC,IAAI,QAAQ,WAAW,GACrB;EAGF,MAAM,QAAQ,QAAQ,KAAK,GAAG,UAC5B,KAAK,WAAW,IAAI,QAAQ,MAAM,GAAG,QAAQ,CAAC,CAAC,CAAC,KAAA,GAA0B,CAAC,CAC7E;EAGA,IAAI,MAAM,OAAO,SAAS,CAAC,IAAI,GAC7B;EAGF,OAAO,MAAM,KAAK,SAAS,MAAM,MAAM,UAAU,CAAC,CAAC;CACrD;;CAGA,MAAM,QAAQ,QAAsB,SAAuC;EACzE,IAAI,OAAO,UACT;EAIF,IAAI,OAAO,4BAA4B;GACrC,0BAA0B;GAE1B;EACF;EAGA,IAAI,OAAO,cAAc;GACvB,KAAK,kBAAkB,QAAQ,OAAO;GAEtC;EACF;EAGA,IAAI,eAAe,QAAQ,OAAO,GAChC;EAIF,IAAI,OAAO,OAAO,OAAO,WAAW;GAClC,MAAM,EAAE,WAAW,MAAM,OAAO;GAChC,OAAO,KAAK,OAAO,GAAG;GAEtB;EACF;EAEA,IAAI,OAAO,OAAO,CAAC,OAAO,aAAa,CAAC,OAAO,UAAU;GACvD,IAAI,OAAO,iBACT,OAAO,KAAK,OAAO,KAAK,UAAU,UAAU;QACvC;IACL,MAAM,EAAE,WAAW,MAAM,OAAO;IAChC,OAAO,MAAM,OAAO,GAAG;GACzB;GAEA;EACF;EAEA,IAAI,qBAAqB,OAAO,GAAG;GACjC,MAAM,KAAK,SAAS,QAAQ,OAAO;GAEnC;EACF;EAEA,IAAI,OAAO,UAAU;GACnB,MAAM,KAAK,UAAU,QAAQ,OAAO;GAEpC;EACF;EAEA,KAAK,QAAQ,QAAQ,OAAO;CAC9B;;;;;CAMA,MAAM,SAAS,QAAsB,SAA0C;EAC7E,IAAI,OAAO,UACT;EAGF,IAAI,OAAO,UACT,OAAO,IAAI,SAAkB,YAAY;GACvC,KAAU,UAAU,QAAQ,SAAS,OAAO;EAC9C,CAAC;EAGH,OAAO,KAAK,aAAa,QAAQ,OAAO;CAC1C;;;;;CAMA,kBAAkB,QAAsB,SAA8B;EACpE,MAAM,UAAU,QAAQ,WAAW,CAAC;EACpC,MAAM,OAAO,KAAK,aAAa,OAAO,CAAC,CAAC,GAAG,EAAE;EAE7C,IAAI,CAAC,QAAQ,QAAQ,WAAW,GAC9B;EAGF,MAAM,OAAO,KAAK,WAAW,IAAI,QAAQ,KAAA,GAA0B,CAAC;EAEpE,KAAK,QACH,KAAK,QACL;GAAE,GAAG,KAAK;GAAS,WAAW;IAAE,GAAG,KAAK,QAAQ;IAAW,GAAG,OAAO;GAAU;EAAE,GACjF,MAAM,MAAM,UAAU,CAAC,CACzB;CACF;;;;;CAMA,YAAY,MAAoB;EAC9B,MAAM,OAAO,KAAK,MAAM,OAAO,MAAM,UAAU,MAAM,OAAO,SAAS,IAAI;EACzE,MAAM,SAAS,MAAM,MAAM;EAE3B,KAAK,WAAW,IAAI;EAEpB,IAAI,QAAQ,QACV,KAAK,aAAa,KAAK,SAAS,MAAM;CAE1C;;CAGA,aAAa,SAAwB,OAA4B;EAC/D,MAAM,UAAU,CAAC,GAAI,QAAQ,WAAW,CAAC,CAAE,CAAC,CAAC,QAAQ;EAErD,KAAK,MAAM,UAAU,SAAS;GAC5B,KAAK,WAAW,MAAM;GAEtB,IAAI,UAAU,QAAQ,WAAW,OAC/B;EAEJ;CACF;CAEA,MAAM,UACJ,QACA,SACA,SACe;EACf,MAAM,MAAM,SAAS,OAAO,QAAQ,OAAO;EAE3C,IAAI,KAAK,QAAQ,IAAI,GAAG,GAAG;GACzB,UAAU,KAAA,CAAS;GAEnB;EACF;EAEA,KAAK,QAAQ,IAAI,GAAG;EACpB,KAAK,MAAM,OAAO;GAAE,SAAS,OAAO;GAAM,YAAY,OAAO,QAAQ,OAAO;EAAE,CAAC;EAE/E,IAAI;GACF,MAAM,QAAQ,MAAM,KAAK,WAAW,QAAQ,OAAO;GAGnD,IAAI,iBAAiB,KAAK,GAAG;IAC3B,KAAK,MAAM,OAAO;KAAE,SAAS;KAAM,YAAY;IAAK,CAAC;IACrD,KAAK,OAAO,MAAM,aAAa;IAC/B,UAAU,KAAA,CAAS;IAEnB;GACF;GAEA,KAAK,MAAM,KAAK,WAAW;IACzB,SAAS;IACT,YAAY;IACZ,QAAQ,CACN,GAAG,MAAM,QACT;KAAE;KAAQ;KAAS;KAAO,QAAQ,CAAC;KAAG,YAAY;KAAO;IAAQ,CACnE;GACF,EAAE;EACJ,SAAS,OAAO;GACd,KAAK,MAAM,OAAO;IAAE,SAAS;IAAM,YAAY;GAAK,CAAC;GACrD,UAAU,KAAA,CAAS;GACnB,KAAK,QAAQ,UAAU,KAAK;EAC9B,UAAU;GACR,KAAK,QAAQ,OAAO,GAAG;EACzB;CACF;CAEA,WACE,QACA,SAC2D;EAG3D,MAAM,UAAU,QAAQ;EACxB,MAAM,SAAS,YAAY,KAAA;EAC3B,MAAM,SAAS;GACb,OAAO,QAAQ;GACf,WAAW,QAAQ;GACnB,QAAQ,WAAW,QAAQ,OAAO;GAClC,QAAQ,QAAQ,UAAU,KAAA;GAC1B,SAAS,YAAY,KAAA,KAAa,QAAQ,SAAS,IAAI,UAAU,KAAA;GACjE,YAAY,QAAQ,aAAa,IAAI,KAAA;GACrC,MAAM,SAAS,IAAI,KAAA;GAGnB,YAAY,iBAAiB;GAC7B,WAAW,QAAQ;EACrB;EACA,MAAM,cAAc,KAAK,YAAY,OAAO;EAI5C,IAAI,qBAAqB,OAAO,GAC9B,OAAO,YACL,KAAK,QAAQ,UAAU,OACvB;GAAE,QAAQ;GAAQ,MAAM;IAAE,GAAG;IAAQ,OAAO,QAAQ,MAAM,MAAM,UAAU,CAAC;GAAE;EAAE,CACjF;EAGF,OAAO,YACL,KAAK,QAAQ,UAAU,OACvB,cAAc;GAAE,QAAQ;GAAQ,MAAM;IAAE,GAAG;IAAQ;GAAY;EAAE,IAAI,EAAE,OAAO,OAAO,CACvF;CACF;;;;;CAMA,MAAM,WAAW,MAAc,WAAoC,CAAC,GAAkB;EACpF,MAAM,OAAO,KAAK,MAAM,OAAO,MAAM,UAAU,MAAM,OAAO,SAAS,IAAI;EAEzE,IAAI,CAAC,MACH;EAGF,IAAI;GACF,MAAM,QAAQ,MAAM,KAAK,WAAW,KAAK,QAAQ,KAAK,OAAO;GAE7D,IAAI,iBAAiB,KAAK,GAAG;IAC3B,KAAK,OAAO,MAAM,aAAa;IAC/B,KAAK,WAAW,IAAI;IAEpB;GACF;GAEA,IAAI,MAAM,MACR,MAAM,OAAO;IACX,GAAG,MAAM;IAET,YAAY,qBAAqB,MAAM,KAAK,UAAU;IACtD,OAAO,mBAAmB,MAAM,KAAK,OAAO,UAAU,MAAM,gBAAgB;GAC9E;GAGF,KAAK,MAAM,KAAK,WAAW;IACzB,GAAG;IACH,QAAQ,MAAM,OAAO,KAAK,UACxB,UAAU,QAAQ,MAAM,OAAO,SAAS,OACpC;KAAE,GAAG;KAAO;KAAO,QAAQ,CAAC;KAAG,UAAU,MAAM,WAAW,KAAK;IAAE,IACjE,KACN;GACF,EAAE;EACJ,SAAS,OAAO;GACd,KAAK,QAAQ,UAAU,KAAK;EAC9B;CACF;;CAGA,UAAU,QAAsB,SAAiC;EAC/D,OAAO,KAAK,QAAQ,IAAI,OAAO,QAAQ,OAAO,CAAC;CACjD;CAEA,WAAW,MAAqB;EAC9B,MAAM,SAAS,KAAK,MAAM;EAC1B,MAAM,YAAY,OACd,OAAO,QAAQ,UAAU,MAAM,OAAO,SAAS,IAAI,IACnD,OAAO,MAAM,GAAG,EAAE;EAEtB,KAAK,MAAM,KAAK,WAAW;GAAE,GAAG;GAAO,QAAQ;EAAU,EAAE;EAG3D,KAAK,MAAM,SAAS,QAClB,IAAI,CAAC,UAAU,SAAS,KAAK,GAAG,MAAM,UAAU,KAAA,CAAS;CAE7D;;CAGA,QACE,QACA,SACA,OAAgC,CAAC,GACjC,UAA0B,CAAC,GACrB;EACN,IAAI,qBAAqB,OAAO,GAAG;GACjC,KAAU,aAAa,QAAQ,SAAS,IAAI;GAE5C;EACF;EAEA,MAAM,MAAM,OAAO,QAAQ,OAAO;EAElC,IAAI,KAAK,QAAQ,IAAI,GAAG,GACtB;EAGF,KAAK,QAAQ,IAAI,GAAG;EACpB,KAAK,cAAc,OAAO,MAAM,IAAI;EAIpC,MAAM,UAAU,QAAQ,cAAc,SAAS,qBAAqB,IAAI;EAExE,IAAI,YAAY,MAAM,oBAAoB;EAE1C,YACE,eAAe,KAAK,QAAQ,UAAU,QAAQ,OAAO,GACrD;GACE,GAAI,YAAY,OAAO,GAAG,uBAAuB,QAAQ,IAAI,CAAC;GAC9D,OAAO,QAAQ;GACf,WAAW,QAAQ;GACnB,QAAQ,WAAW,QAAQ,OAAO;GAClC,QAAQ,QAAQ,UAAU,KAAA;GAC1B,SAAS,QAAQ;GACjB,YAAY,QAAQ,aAAa,IAAI,KAAA;GACrC,MAAM,QAAQ,YAAY,KAAA,IAAY,IAAI,KAAA;GAC1C,WAAW,QAAQ,gBACf;IAAE,GAAG,QAAQ;KAAY,0BAA0B;GAAK,IACxD,QAAQ;GACZ,YAAY,iBAAiB;GAC7B;GACA,aAAa,KAAK,YAAY,OAAO;IACpC,8BAA8B,QAAQ,YAAY,KAAA;EACrD,GACA;GACE,UAAU,WAAW;IACnB,IAAA,qBAA2B,QAAoC,mBAAmB;IAKlF,MAAM,SADW,KAAK,MAAM,OAAO,MAAM,UAAU,MAAM,OAAO,SAAS,OAAO,IAC1D,IAAI,KAAA,IAAY,KAAK,aAAa,OAAO,CAAC,CAAC,GAAG,EAAE;IAEtE,KAAK,UACH,SAAS,OAAO,OAAO,OAAO,OAAO,MACrC,MACF;GACF;GACA,YAAY,SAAS;IACnB,MAAM,QAAQ,UAAU,IAAY,CAAC,CAAC;IAGtC,KAAK,MAAM,UAAU,OAAO,cAAc,CAAC,GAAG;KAC5C,MAAM,UAAU,QAAQ,WAAW,CAAC;KACpC,MAAM,QAAQ,QAAQ,YAAY,OAAO,IAAI;KAC7C,MAAM,OACJ,SAAS,IACL,KAAK,WAAW,IAAI,QAAQ,MAAM,GAAG,QAAQ,CAAC,CAAC,CAAC,KAAA,GAA0B,CAAC,IAC3E,KAAA;KAEN,KAAK,MAAM,CAAC,MAAM,UAAU,OAAO,QAAQ,OAAO,IAAI,GACpD,MAAM,SAAS,MAAM,OAAO,EAAE,QAAQ,KAAK,CAAC;IAEhD;IAEA,IACE,CAAC,SACD,MAAM,WAAW,eAChB,MAAM,WAAW,aAAa,MAAM,eAAe,OAEpD,KAAK,WAAW,OAAO,IAAI;IAI7B,IAAI,OAAO,WAAW,aAAa,MAAM,qBACvC,KAAK,aACH,SACA,OAAO,MAAM,wBAAwB,WAAW,MAAM,sBAAsB,IAC9E;IAIF,MAAM,cAAc,OAAO,WAAW,YAAY,MAAM,uBAAuB,KAAA;IAE/E,IAAI,aAAa;KACf,MAAM,OAAqB;MACzB,MAAM;MACN,MAAM,YAAY;MAClB,UAAU,YAAY,YAAY,KAAA;MAClC,WAAW;KACb;KACA,MAAM,cAA6B;MACjC,GAAG;MACH,WAAW,YAAY,aAAa,KAAA;KACtC;KAEA,IAAI,YAAY,UACd,KAAU,UAAU,MAAM,WAAW;UAErC,KAAK,QAAQ,MAAM,WAAW;IAElC;IAEA,IAAI,OAAO,WAAW,aAAa,MAAM,eAAe,SAAS,QAAQ,eACvE,KAAU,WAAW,OAAO,MAAM,IAAI;IAGxC,IAAI,CAAC,SAAS,MAAM,WAAW,WAC7B,KAAK,QAAQ,YAAY,QAAQ,OAAO;GAE5C;GACA,gBAAgB;IACd,KAAK,QAAQ,OAAO,GAAG;IACvB,KAAK,cAAc,OAAO,MAAM,KAAK;GACvC;EACF,CACF;CACF;;;;;CAMA,MAAM,YACJ,QACA,SACA,OAAgC,CAAC,GACrB;EACZ,IAAI;GAoBF,QAAO,MAnBgB,YACrB,eAAe,KAAK,QAAQ,UAAU,QAAQ,OAAO,GACrD;IACE,QAAQ;IACR,MAAM;KACJ,OAAO,QAAQ;KACf,WAAW,QAAQ;KACnB,QAAQ,WAAW,QAAQ,OAAO;KAClC,QAAQ,QAAQ,UAAU,KAAA;KAC1B,SAAS,QAAQ;KACjB,YAAY,QAAQ,aAAa,IAAI,KAAA;KACrC,MAAM,QAAQ,YAAY,KAAA,IAAY,IAAI,KAAA;KAC1C,WAAW,QAAQ;KACnB,YAAY,iBAAiB;KAC7B;IACF;GACF,CACF,EAAA,CAEgB;EAClB,SAAS,OAAO;GACd,IAAI,iBAAiB,aAAa,MAAM,WAAW,KACjD,KAAK,UACH,OAAO,MACP,OAAO,YACL,OAAO,QAAQ,MAAM,MAAM,CAAC,CAAC,KAAK,CAAC,KAAK,cAAc,CAAC,KAAK,SAAS,MAAM,EAAE,CAAC,CAChF,CACF;GAGF,MAAM;EACR;CACF;;;;;CAMA,MAAM,aACJ,QACA,SACA,OAAgC,CAAC,GACf;EAClB,MAAM,MAAM,OAAO,QAAQ,OAAO;EAElC,IAAI,KAAK,QAAQ,IAAI,GAAG,GACtB;EAGF,KAAK,QAAQ,IAAI,GAAG;EACpB,KAAK,cAAc,OAAO,MAAM,IAAI;EAEpC,MAAM,OAAO,QAAQ;EACrB,MAAM,OAAO,MAAM,MAAM,UAAU,CAAC;EAEpC,IAAI;GACF,MAAM,WAAW,MAAM,YACrB,eAAe,KAAK,QAAQ,UAAU,QAAQ,OAAO,GACrD;IACE,QAAQ;IACR,MAAM;KACJ,OAAO,QAAQ;KACf,WAAW,QAAQ;KACnB,QAAQ,WAAW,QAAQ,OAAO;KAClC,QAAQ,QAAQ,UAAU,KAAA;KAC1B,WAAW,QAAQ;KACnB;KACA,OAAO;KACP,cAAc,MAAM;IACtB;GACF,CACF;GAGA,IAAI,SAAS,QAAQ,QAAQ,CAAC,KAAK,cAAc,SAAS,MAAM,IAAI,GAClE,KAAU,QAAQ;GAGpB,KAAK,OAAO,SAAS,aAAa;GAElC,IAAI,SAAS,WAAW,UACtB;GAGF,IAAI,SAAS,WAAW,WAEtB,KADmB,MAAM,OAAO,MAAM,SAAS,KAAK,OAAO,SAAS,OAAO,IACvE,CAAC,EAAE,UAAU,SAAS,MAAM;GAGlC,IAAI,SAAS,WAAW,eAAe,SAAS,eAAe,OAC7D,KAAK,WAAW,OAAO,IAAI;GAG7B,IAAI,SAAS,WAAW,WAAW;IACjC,KAAK,QAAQ,YAAY,QAAQ,OAAO;IAExC,IAAI,SAAS,UAAU;KACrB,MAAM,EAAE,WAAW,MAAM,OAAO;KAChC,OAAO,MAAM,SAAS,QAAQ;IAChC;GACF;GAEA,OAAO,SAAS,WAAW,YAAY,SAAS,SAAS,KAAA;EAC3D,SAAS,OAAO;GACd,IAAI,iBAAiB,aAAa,MAAM,WAAW,KACjD,KAAK,UACH,OAAO,MACP,OAAO,YACL,OAAO,QAAQ,MAAM,MAAM,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,OAAO,SAAS,MAAM,EAAE,CAAC,CACpF,CACF;QAEA,KAAK,QAAQ,UAAU,KAAK;GAG9B;EACF,UAAU;GACR,KAAK,QAAQ,OAAO,GAAG;GACvB,KAAK,cAAc,OAAO,MAAM,KAAK;EACvC;CACF;CAEA,cAAsB,MAAc,YAA2B;EAC7D,KAAK,MAAM,KAAK,WAAW;GACzB,GAAG;GACH,QAAQ,MAAM,OAAO,KAAK,UACxB,MAAM,OAAO,SAAS,OAClB;IAAE,GAAG;IAAO;IAAY,QAAQ,aAAa,CAAC,IAAI,MAAM;GAAO,IAC/D,KACN;EACF,EAAE;CACJ;CAEA,UAAkB,MAAc,QAAsC;EACpE,MAAM,aAAuC,CAAC;EAE9C,KAAK,MAAM,CAAC,KAAK,YAAY,OAAO,QAAQ,MAAM,GAChD,WAAW,OAAO,CAAC,OAAO;EAG5B,KAAK,MAAM,KAAK,WAAW;GACzB,GAAG;GACH,QAAQ,MAAM,OAAO,KAAK,UACxB,MAAM,OAAO,SAAS,OAAO;IAAE,GAAG;IAAO,QAAQ;GAAW,IAAI,KAClE;EACF,EAAE;CACJ;AACF;;;;;;AAOA,SAAgB,iBACd,QACA,SACS;CACT,OAAO,QAAQ,OAAO,aAAa,MAAM,KAAK,CAAC,OAAO,YAAY,QAAQ,UAAU;AACtF;;;;;AASA,SAAgB,kBACd,MACA,WAA6B,WACX;CAClB,QAAQ,MAAR;EACE,KAAK,MACH,OAAO;EACT,KAAK,MACH,OAAO;EACT,KAAK,MACH,OAAO;EACT,KAAK;EACL,KAAK,MACH,OAAO;EACT,SACE,OAAO;CACX;AACF;;AAGA,SAAgB,eAAe,MAAwD;CACrF,OAAO,SAAS,OAAO,YAAY,SAAS,OAAO,YAAY;AACjE;;;;;AAWA,SAAgB,uBAAuB,WAAyD;CAC9F,MAAM,CAAC,MAAM,UAAU,aAAa,GAAA,CAAI,MAAM,GAAG;CACjD,MAAM,QAAQ;EAAC;EAAO;EAAS;EAAU;CAAM;CAC/C,MAAM,SAAS;EAAC;EAAS;EAAU;CAAK;CAExC,MAAM,YAAY,MAAM,MAAM,cAAc,cAAc,IAAI;CAC9D,MAAM,gBACJ,OAAO,MAAM,cAAc,cAAc,KAAK,MAAM,aAAa,CAAC,QAAQ,WAAW;CAEvF,OAAO;EAAE,MAAM,aAAa;EAAU,OAAO;CAAc;AAC7D;;AAGA,SAAgB,mBAAmB,SAA4C;CAC7E,OAAO,QAAQ,SAAS,WACtB,cAAc,MAAM,IAAI,mBAAmB,OAAO,OAAO,IAAI,CAAC,MAAM,CACtE;AACF;;;;;;AAOA,SAAgB,qBAAqB,QAAqB,OAAwB;CAChF,MAAM,QAAQ,OAAO;CAErB,IAAI,CAAC,OACH,OAAO;CAGT,MAAM,OAAO,WAAW,MAAM,QAAQ,MAAM,OAAO;CAEnD,OAAO,OAAO,MAAM,QAAQ,CAAC,CAAC,CAAC,MAAM,UAAU;EAC7C,IAAI,MAAM,MAAM,kBAAkB,MAAM,OAAO;EAE/C,MAAM,UAAU,MAAM,QAAQ,WAAW,CAAC;EAE1C,OAAO,QAAQ,MACZ,GAAG,aAAa,QAAQ,MAAM,GAAG,WAAW,CAAC,CAAC,CAAC,KAAA,GAA0B,MAAM,IAClF;CACF,CAAC;AACH;;AAGA,SAAgB,qBACd,OACwC;CACxC,MAAM,YAAY,MAAM,0BAA0B,MAAM;CAExD,QAAQ,WAAR;EACE,KAAK;EACL,KAAK;EACL,KAAK,WACH,OAAO;EACT,SACE,OAAO;CACX;AACF;;;;;AAMA,SAAgB,cACd,UACA,YAA2B,OACT;CAGlB,OAFgB,aAAa,aAET,cAAc,SAAS,UAAU;AACvD;AAEA,SAAS,iBACP,OACuC;CACvC,OAAQ,MAAgD,cAAc;AACxE;;;;;AAMA,SAAgB,wBACd,OACA,MACS;CACT,OAAO,QAAQ,MAAM,uBAAuB,MAAM,OAAO;AAC3D;;;;;AAMA,SAAgB,kBACd,OACA,MACA,SACS;CACT,IAAI,CAAC,wBAAwB,OAAO,IAAI,KAAK,OAAO,WAAW,aAC7D,OAAO;CAGT,OAAO,OAAO,QAAQ,OAAO;AAC/B;;;;;;AAOA,SAAgB,iBACd,OACA,MACA,SACY;CACZ,IAAI,CAAC,MAAM,uBAAuB,CAAC,QAAQ,OAAO,WAAW,aAC3D,aAAa,CAAC;CAGhB,MAAM,YAAY,UAA6B;EAC7C,IAAI,KAAK,SAAS;GAChB,MAAM,eAAe;GACrB,MAAM,cAAc;EACtB;CACF;CAEA,OAAO,iBAAiB,gBAAgB,QAAQ;CAEhD,MAAM,mBAAmB,UACrB,OAAO,GAAG,WAAW,UAAU;EAC7B,IAAI,KAAK,WAAW,eAAe,MAAM,OAAO,KAAK,KAAK,CAAC,OAAO,QAAQ,OAAO,GAC/E,MAAM,eAAe;CAEzB,CAAC,UACK,CAAC;CAEX,aAAa;EACX,OAAO,oBAAoB,gBAAgB,QAAQ;EACnD,iBAAiB;CACnB;AACF;;AAGA,SAAgB,kBAAkB,OAAe,MAAgD;CAC/F,OAAO;EAAE;EAAO,WAAW;CAAK;AAClC;;;;;AAMA,SAAgB,eAA6C,OAAmB;CAC9E,MAAM,SAAgB,CAAC;CACvB,IAAI,UAAyB;CAE7B,KAAK,MAAM,QAAQ,OAAO;EACxB,MAAM,QAAQ,KAAK,SAAS;EAE5B,IAAI,YAAY,QAAQ,UAAU,SAAS;GACzC,OAAO,KAAK,CAAC,CAAC;GACd,UAAU;EACZ;EAEA,OAAO,OAAO,SAAS,EAAE,CAAE,KAAK,IAAI;CACtC;CAEA,OAAO;AACT;;AAGA,SAAgB,gBACd,OACA,QACA,SACS;CACT,OAAO,MAAM,eAAe,OAAO,QAAQ,OAAO;AACpD;;AAGA,SAAgB,mBACd,OACA,QACA,SACS;CACT,MAAM,MAAM,OAAO,QAAQ,OAAO;CAElC,OAAO,MAAM,OAAO,MACjB,UAAU,MAAM,cAAc,OAAO,MAAM,QAAQ,MAAM,OAAO,MAAM,GACzE;AACF;AAEA,SAAS,OAAO,QAAoC,SAAgC;CAClF,OAAO;EAAC,QAAQ,aAAa;EAAQ,WAAW,QAAQ,OAAO;EAAG,QAAQ,UAAU;CAAE,CAAC,CAAC,KACtF,IACF;AACF;;;;;AAMA,SAAgB,eACd,UACA,SACA,SAAiB,OAAO,WAAW,cAAc,OAAO,SAAS,SAAS,IAClE;CACR,IAAI,CAAC,QAAQ,cAAc,OAAO,UAAU,GAC1C,OAAO;CAGT,OAAO,YAAY,SAAS,SAAS,GAAG,IAAI,MAAM,OAAO,OAAO,QAAQ,OAAO,EAAE;AACnF"}
|
|
1
|
+
{"version":3,"file":"actions.js","names":[],"sources":["../src/actions.ts"],"sourcesContent":["import type { Page } from '@inertiajs/core'\nimport { revealAutofocusField } from './autofocus'\nimport {\n CONFLICT_ERROR_KEY,\n currentRecordVersion,\n markRecordSubmitted,\n RECORD_VERSION_INPUT,\n reportSaveConflict,\n} from './conflicts'\nimport { isFieldActionContext } from './field-actions'\nimport { pageFlash } from './flash'\nimport { isLeavingVisit, type FormStore } from './form'\nimport { HttpError, inertiaPost, requestJson, router } from './http'\nimport { ACTION_PATH_SEPARATOR, actionPath, PASSWORD_CONFIRMATION_INPUT } from './modal-actions'\nimport { openDatabaseNotifications } from './notifications'\nimport { submitFormById } from './page-actions'\nimport { currentPageQuery } from './page-query'\nimport type {\n ActionModalCancelledResponse,\n ActionModalSchema,\n ActionSchema,\n AnyActionSchema,\n} from './schema/actions'\nimport { isActionGroup } from './schema/actions'\nimport type { RecordKey, Size } from './schema/common'\nimport type { FieldActionResponse } from './schema/form'\nimport type { NotificationSchema } from './schema/notifications'\nimport { createStore, type Store } from './store'\n\nexport interface ActionContext {\n /** Encrypted page context token. */\n token: string\n /** `page`, `table`, `relationManager:Class`, `widget:Class`. */\n component?: string\n record?: RecordKey | null\n /** Selected record keys for bulk actions. */\n records?: RecordKey[]\n /** \"All filtered records\" bulk selection. */\n allRecords?: boolean\n arguments?: Record<string, unknown>\n /**\n * Names of the actions whose modals this action sits in, outermost first (actions in a\n * modal's footer). The server resolves the action by its path, `parent>child`.\n */\n parents?: string[]\n /**\n * Field actions (`component` = `field:{schema}::{key}`): the form the field belongs to.\n * Its state is sent with the action and the refreshed form is merged back into it.\n */\n form?: FormStore\n}\n\nexport interface ExecuteOptions {\n /** The current password, for actions with `requiresPasswordConfirmation()`. */\n password?: string\n /**\n * \"Create & create another\": run with the `another` argument and, when the server keeps\n * the modal open, reload it with a fresh form (keeping `preserveFormData` fields).\n */\n createAnother?: boolean\n}\n\n/** The argument `CreateAction` reads for \"Create & create another\". */\nexport const CREATE_ANOTHER_ARGUMENT = 'another'\n\n/**\n * The state of the fresh form after \"Create & create another\": the server's defaults,\n * plus the previous values of the fields `preserveFormData` names (all of them for `true`).\n */\nexport function createAnotherState(\n fresh: Record<string, unknown>,\n previous: Record<string, unknown>,\n preserve: boolean | string[] | undefined,\n): Record<string, unknown> {\n if (!preserve) {\n return fresh\n }\n\n const keys = preserve === true ? Object.keys(previous) : preserve\n const state = { ...fresh }\n\n for (const key of keys) {\n if (Object.prototype.hasOwnProperty.call(previous, key)) {\n state[key] = previous[key]\n }\n }\n\n return state\n}\n\nexport interface OpenModal {\n action: ActionSchema\n context: ActionContext\n modal: ActionModalSchema\n /** Current form values of the modal form. */\n errors: Record<string, string[]>\n processing: boolean\n /** Field actions: settles `runField()` with the action's result (undefined when closed). */\n resolve?: (result: unknown) => void\n /**\n * Bumped by `resetModal()` (\"Create & create another\"): the renderer focuses the form's\n * `autofocus()` field again (`focusAutofocusField()`) once the fresh form rendered.\n */\n refocus?: number\n}\n\nexport interface ActionRunnerState {\n /** Stack of open modals (nested modals are possible). */\n modals: OpenModal[]\n /** Name of the action currently loading its modal. */\n loading: string | null\n /**\n * Which run of it (component, action path and record): a table renders the same action\n * once per row, and only the clicked row's button is loading (`isActionLoading()`).\n */\n loadingKey?: string | null\n}\n\nexport interface ActionRunnerOptions {\n endpoints: { action: string; modal: string }\n onError?: (error: unknown) => void\n /** Called after a successful run so the UI can e.g. clear a table selection. */\n onSuccess?: (action: ActionSchema, context: ActionContext) => void\n /**\n * Shows notifications that arrive in JSON responses (a modal that `mountUsing()`\n * cancelled, field actions). Renderers can also set it with `setNotificationHandler()`.\n */\n onNotifications?: (notifications: NotificationSchema[]) => void\n}\n\n/**\n * Drives action buttons: plain links navigate, actions without a modal post straight to\n * the server, and actions with a modal fetch the modal schema first.\n */\nexport class ActionRunner {\n public readonly store: Store<ActionRunnerState>\n\n /** Runs in flight, so a double click or a repeated shortcut doesn't submit twice. */\n private readonly running = new Set<string>()\n\n /** The form of each open modal (registered by the renderer), by modal path. */\n private readonly modalForms = new Map<string, FormStore>()\n\n constructor(private readonly options: ActionRunnerOptions) {\n this.store = createStore<ActionRunnerState>({ modals: [], loading: null, loadingKey: null })\n }\n\n get state(): ActionRunnerState {\n return this.store.get()\n }\n\n /** Show notifications from JSON responses with this handler (e.g. the renderer's toasts). */\n setNotificationHandler(\n handler: ((notifications: NotificationSchema[]) => void) | undefined,\n ): void {\n this.options.onNotifications = handler\n }\n\n private notify(notifications: NotificationSchema[] | undefined): void {\n if (notifications && notifications.length > 0) {\n this.options.onNotifications?.(notifications)\n }\n }\n\n /** Point the runner at the current panel's endpoints (they change per tenant/panel). */\n setEndpoints(endpoints: Partial<ActionRunnerOptions['endpoints']>): void {\n Object.assign(this.options.endpoints, endpoints)\n }\n\n /**\n * The renderer registers the form of an open modal, so nested actions can send its state\n * (`$mountedActions`, `getRawData()`) and `fillData()` can write into it. Returns the\n * function that unregisters it.\n */\n registerModalForm(action: ActionSchema, context: ActionContext, form: FormStore): () => void {\n const key = actionPath(action, context)\n this.modalForms.set(key, form)\n\n return () => {\n if (this.modalForms.get(key) === form) this.modalForms.delete(key)\n }\n }\n\n /** The open modals of the actions a nested action sits in, outermost first. */\n parentModals(context: ActionContext): Array<OpenModal | undefined> {\n const parents = context.parents ?? []\n\n return parents.map((_, index) => {\n const path = parents.slice(0, index + 1).join(ACTION_PATH_SEPARATOR)\n\n return this.state.modals.find(\n (modal) =>\n modal.context.component === context.component &&\n actionPath(modal.action, modal.context) === path,\n )\n })\n }\n\n /**\n * The form state of the modals a nested action sits in, outermost first (undefined when\n * none of them has a form).\n */\n mountedData(context: ActionContext): Array<Record<string, unknown>> | undefined {\n const parents = context.parents ?? []\n\n if (parents.length === 0) {\n return undefined\n }\n\n const forms = parents.map((_, index) =>\n this.modalForms.get(parents.slice(0, index + 1).join(ACTION_PATH_SEPARATOR)),\n )\n\n // Parents without a form have nothing to send.\n if (forms.every((form) => !form)) {\n return undefined\n }\n\n return forms.map((form) => form?.state.values ?? {})\n }\n\n /** Entry point for a click on an action button. */\n async trigger(action: ActionSchema, context: ActionContext): Promise<void> {\n if (action.disabled) {\n return\n }\n\n // `openDatabaseNotifications()`: a client-side button.\n if (action.opensDatabaseNotifications) {\n openDatabaseNotifications()\n\n return\n }\n\n // `makeModalSubmitAction()`: submit the modal this button sits in, with its arguments.\n if (action.submitsModal) {\n this.submitParentModal(action, context)\n\n return\n }\n\n // `formId()`: the page form submits itself with its state, wherever the button is.\n if (submitFormById(action, context)) {\n return\n }\n\n // `postToUrl()`: a POST request to the URL (e.g. \"lock session\"), not a visit.\n if (action.url && action.postToUrl) {\n const { router } = await import('@inertiajs/core')\n router.post(action.url)\n\n return\n }\n\n if (action.url && !action.hasAction && !action.hasModal) {\n if (action.openUrlInNewTab) {\n window.open(action.url, '_blank', 'noopener')\n } else {\n const { router } = await import('@inertiajs/core')\n router.visit(action.url)\n }\n\n return\n }\n\n if (isFieldActionContext(context)) {\n await this.runField(action, context)\n\n return\n }\n\n if (action.hasModal) {\n await this.openModal(action, context)\n\n return\n }\n\n this.execute(action, context)\n }\n\n /**\n * Run a field action (opening its modal first, if it has one) and resolve with its\n * result once it ran, or with undefined when the modal is closed without running it.\n */\n async runField(action: ActionSchema, context: ActionContext): Promise<unknown> {\n if (action.disabled) {\n return undefined\n }\n\n if (action.hasModal) {\n return new Promise<unknown>((resolve) => {\n void this.openModal(action, context, resolve)\n })\n }\n\n return this.executeField(action, context)\n }\n\n /**\n * Run the action whose modal `button` sits in (a `makeModalSubmitAction()` button) with\n * that modal's form state and the button's arguments.\n */\n submitParentModal(button: ActionSchema, context: ActionContext): void {\n const parents = context.parents ?? []\n const open = this.parentModals(context).at(-1)\n\n if (!open || parents.length === 0) {\n return\n }\n\n const form = this.modalForms.get(parents.join(ACTION_PATH_SEPARATOR))\n\n this.execute(\n open.action,\n { ...open.context, arguments: { ...open.context.arguments, ...button.arguments } },\n form?.state.values ?? {},\n )\n }\n\n /**\n * Close a modal the user dismissed (cancel, Escape, clicking away, close button). With\n * `cancelParentActionsOnClose()` its parents close too.\n */\n cancelModal(name: string): void {\n const open = this.state.modals.find((modal) => modal.action.name === name)\n const cancel = open?.modal.cancelParentActionsOnClose\n\n this.closeModal(name)\n\n if (open && cancel) {\n this.closeParents(open.context, cancel)\n }\n }\n\n /** Close the modals `context` sits in: all of them, or up to and including `until`. */\n closeParents(context: ActionContext, until: true | string): void {\n const parents = [...(context.parents ?? [])].reverse()\n\n for (const parent of parents) {\n this.closeModal(parent)\n\n if (until !== true && parent === until) {\n break\n }\n }\n }\n\n async openModal(\n action: ActionSchema,\n context: ActionContext,\n resolve?: (result: unknown) => void,\n ): Promise<void> {\n const key = `modal:${runKey(action, context)}`\n\n if (this.running.has(key)) {\n resolve?.(undefined)\n\n return\n }\n\n this.running.add(key)\n this.store.update({ loading: action.name, loadingKey: runKey(action, context) })\n\n try {\n const modal = await this.fetchModal(action, context)\n\n // `mountUsing()` halted or cancelled the action: the modal stays closed.\n if (isCancelledModal(modal)) {\n this.store.update({ loading: null, loadingKey: null })\n this.notify(modal.notifications)\n resolve?.(undefined)\n\n return\n }\n\n this.store.set((state) => ({\n loading: null,\n loadingKey: null,\n modals: [\n ...state.modals,\n { action, context, modal, errors: {}, processing: false, resolve },\n ],\n }))\n } catch (error) {\n this.store.update({ loading: null, loadingKey: null })\n resolve?.(undefined)\n this.options.onError?.(error)\n } finally {\n this.running.delete(key)\n }\n }\n\n private fetchModal(\n action: ActionSchema,\n context: ActionContext,\n ): Promise<ActionModalSchema | ActionModalCancelledResponse> {\n // Bulk actions send their selection (and, for \"select all\", the page's table state), so\n // the server resolves the bulk action rather than a record action and fills `$records`.\n const records = context.records\n const isBulk = records !== undefined\n const params = {\n token: context.token,\n component: context.component,\n action: actionPath(action, context),\n record: context.record ?? undefined,\n records: records !== undefined && records.length > 0 ? records : undefined,\n allRecords: context.allRecords ? 1 : undefined,\n bulk: isBulk ? 1 : undefined,\n // The table state (active tab, filters, search) for record and bulk actions alike: a\n // record action on a non-default tab resolves its record through that tab's query.\n tableQuery: currentPageQuery(),\n arguments: context.arguments,\n }\n const mountedData = this.mountedData(context)\n\n // Field actions send the form state, and nested actions their parents' form state,\n // which needs a request body.\n if (isFieldActionContext(context)) {\n return requestJson<ActionModalSchema | ActionModalCancelledResponse>(\n this.options.endpoints.modal,\n { method: 'POST', body: { ...params, state: context.form?.state.values ?? {} } },\n )\n }\n\n return requestJson<ActionModalSchema | ActionModalCancelledResponse>(\n this.options.endpoints.modal,\n mountedData ? { method: 'POST', body: { ...params, mountedData } } : { query: params },\n )\n }\n\n /**\n * After \"Create & create another\": load the modal again so its form starts from the\n * server's defaults, keeping the `preserveFormData` fields from the values just sent.\n */\n async resetModal(name: string, previous: Record<string, unknown> = {}): Promise<void> {\n const open = this.state.modals.find((modal) => modal.action.name === name)\n\n if (!open) {\n return\n }\n\n try {\n const modal = await this.fetchModal(open.action, open.context)\n\n if (isCancelledModal(modal)) {\n this.notify(modal.notifications)\n this.closeModal(name)\n\n return\n }\n\n if (modal.form) {\n modal.form = {\n ...modal.form,\n // A wizard goes back to the step holding the `autofocus()` field.\n components: revealAutofocusField(modal.form.components),\n state: createAnotherState(modal.form.state, previous, modal.preserveFormData),\n }\n }\n\n this.store.set((state) => ({\n ...state,\n modals: state.modals.map((entry) =>\n entry === open || entry.action.name === name\n ? { ...entry, modal, errors: {}, refocus: (entry.refocus ?? 0) + 1 }\n : entry,\n ),\n }))\n } catch (error) {\n this.options.onError?.(error)\n }\n }\n\n /** Whether this action is currently being submitted for this context. */\n isRunning(action: ActionSchema, context: ActionContext): boolean {\n return this.running.has(runKey(action, context))\n }\n\n closeModal(name?: string): void {\n const modals = this.state.modals\n const remaining = name\n ? modals.filter((modal) => modal.action.name !== name)\n : modals.slice(0, -1)\n\n this.store.set((state) => ({ ...state, modals: remaining }))\n\n // A field action's caller learns that its modal closed without running.\n for (const modal of modals) {\n if (!remaining.includes(modal)) modal.resolve?.(undefined)\n }\n }\n\n /** Submit the topmost modal (or run a modal-less action) through Inertia. */\n execute(\n action: ActionSchema,\n context: ActionContext,\n data: Record<string, unknown> = {},\n options: ExecuteOptions = {},\n ): void {\n if (isFieldActionContext(context)) {\n void this.executeField(action, context, data)\n\n return\n }\n\n const key = runKey(action, context)\n\n if (this.running.has(key)) {\n return\n }\n\n this.running.add(key)\n this.setProcessing(action.name, true)\n\n // Edit pages (`detectConflicts()`): page actions send the version the form was loaded\n // from, so the server refuses to save over somebody else's changes.\n const version = context.component === 'page' ? currentRecordVersion() : null\n\n if (version !== null) markRecordSubmitted()\n\n inertiaPost(\n actionEndpoint(this.options.endpoints.action, context),\n {\n ...(version !== null ? { [RECORD_VERSION_INPUT]: version } : {}),\n token: context.token,\n component: context.component,\n action: actionPath(action, context),\n record: context.record ?? undefined,\n records: context.records,\n allRecords: context.allRecords ? 1 : undefined,\n bulk: context.records !== undefined ? 1 : undefined,\n arguments: options.createAnother\n ? { ...context.arguments, [CREATE_ANOTHER_ARGUMENT]: true }\n : context.arguments,\n tableQuery: currentPageQuery(),\n data,\n mountedData: this.mountedData(context),\n [PASSWORD_CONFIRMATION_INPUT]: options.password || undefined,\n },\n {\n onError: (errors) => {\n if (CONFLICT_ERROR_KEY in (errors as Record<string, unknown>)) reportSaveConflict()\n\n // A nested action without a modal of its own (e.g. `getValidatedData()` on its\n // parent failed): the parent's modal shows the errors.\n const hasModal = this.state.modals.some((modal) => modal.action.name === action.name)\n const parent = hasModal ? undefined : this.parentModals(context).at(-1)\n\n this.setErrors(\n parent ? parent.action.name : action.name,\n errors as Record<string, string>,\n )\n },\n onSuccess: (page) => {\n const flash = pageFlash(page as Page).alfakitAction\n\n // `fillData()`: values written into the parents' forms.\n for (const filled of flash?.parentData ?? []) {\n const parents = context.parents ?? []\n const index = parents.lastIndexOf(filled.name)\n const form =\n index >= 0\n ? this.modalForms.get(parents.slice(0, index + 1).join(ACTION_PATH_SEPARATOR))\n : undefined\n\n for (const [path, value] of Object.entries(filled.data)) {\n form?.setValue(path, value, { silent: true })\n }\n }\n\n if (\n !flash ||\n flash.status === 'cancelled' ||\n (flash.status === 'success' && flash.closeModal !== false)\n ) {\n this.closeModal(action.name)\n }\n\n // `cancelParentActions()`: the modals this nested action was opened from close too.\n if (flash?.status === 'success' && flash.cancelParentActions) {\n this.closeParents(\n context,\n typeof flash.cancelParentActions === 'string' ? flash.cancelParentActions : true,\n )\n }\n\n // `replaceMountedAction()`: the next action opens (or runs) in place of this one.\n const replacement = flash?.status === 'success' ? flash.replaceMountedAction : undefined\n\n if (replacement) {\n const next: ActionSchema = {\n type: 'Action',\n name: replacement.name,\n hasModal: replacement.hasModal || undefined,\n hasAction: true,\n }\n const nextContext: ActionContext = {\n ...context,\n arguments: replacement.arguments ?? undefined,\n }\n\n if (replacement.hasModal) {\n void this.openModal(next, nextContext)\n } else {\n this.execute(next, nextContext)\n }\n }\n\n if (flash?.status === 'success' && flash.closeModal === false && options.createAnother) {\n void this.resetModal(action.name, data)\n }\n\n if (!flash || flash.status === 'success') {\n this.options.onSuccess?.(action, context)\n }\n },\n onFinish: () => {\n this.running.delete(key)\n this.setProcessing(action.name, false)\n },\n },\n )\n }\n\n /**\n * Run an action and resolve with its JSON result (for callers that don't want an\n * Inertia visit, e.g. a custom widget).\n */\n async executeJson<T = unknown>(\n action: ActionSchema,\n context: ActionContext,\n data: Record<string, unknown> = {},\n ): Promise<T> {\n try {\n const response = await requestJson<{ result: T }>(\n actionEndpoint(this.options.endpoints.action, context),\n {\n method: 'POST',\n body: {\n token: context.token,\n component: context.component,\n action: actionPath(action, context),\n record: context.record ?? undefined,\n records: context.records,\n allRecords: context.allRecords ? 1 : undefined,\n bulk: context.records !== undefined ? 1 : undefined,\n arguments: context.arguments,\n tableQuery: currentPageQuery(),\n data,\n },\n },\n )\n\n return response.result\n } catch (error) {\n if (error instanceof HttpError && error.status === 422) {\n this.setErrors(\n action.name,\n Object.fromEntries(\n Object.entries(error.errors).map(([key, messages]) => [key, messages[0] ?? '']),\n ),\n )\n }\n\n throw error\n }\n }\n\n /**\n * Run a field action as JSON with its form's state; merge the refreshed form from the\n * response, settle the modal's `runField()` promise and close the modal.\n */\n async executeField(\n action: ActionSchema,\n context: ActionContext,\n data: Record<string, unknown> = {},\n ): Promise<unknown> {\n const key = runKey(action, context)\n\n if (this.running.has(key)) {\n return undefined\n }\n\n this.running.add(key)\n this.setProcessing(action.name, true)\n\n const form = context.form\n const sent = form?.state.values ?? {}\n\n try {\n const response = await requestJson<FieldActionResponse>(\n actionEndpoint(this.options.endpoints.action, context),\n {\n method: 'POST',\n body: {\n token: context.token,\n component: context.component,\n action: actionPath(action, context),\n record: context.record ?? undefined,\n arguments: context.arguments,\n // A field action inside a record action's modal on a list tab.\n tableQuery: currentPageQuery(),\n data,\n state: sent,\n fingerprints: form?.currentFingerprints,\n },\n },\n )\n\n // A partial response that doesn't fit the tree falls back to a full refresh.\n if (response.form && form && !form.applyResponse(response.form, sent)) {\n void form.refresh()\n }\n\n this.notify(response.notifications)\n\n if (response.status === 'halted') {\n return undefined\n }\n\n if (response.status === 'success') {\n const modal = this.state.modals.find((open) => open.action.name === action.name)\n modal?.resolve?.(response.result)\n }\n\n if (response.status === 'cancelled' || response.closeModal !== false) {\n this.closeModal(action.name)\n }\n\n if (response.status === 'success') {\n this.options.onSuccess?.(action, context)\n\n if (response.redirect) {\n const { router } = await import('@inertiajs/core')\n router.visit(response.redirect)\n }\n }\n\n return response.status === 'success' ? response.result : undefined\n } catch (error) {\n if (error instanceof HttpError && error.status === 422) {\n this.setErrors(\n action.name,\n Object.fromEntries(\n Object.entries(error.errors).map(([field, messages]) => [field, messages[0] ?? '']),\n ),\n )\n } else {\n this.options.onError?.(error)\n }\n\n return undefined\n } finally {\n this.running.delete(key)\n this.setProcessing(action.name, false)\n }\n }\n\n private setProcessing(name: string, processing: boolean): void {\n this.store.set((state) => ({\n ...state,\n modals: state.modals.map((modal) =>\n modal.action.name === name\n ? { ...modal, processing, errors: processing ? {} : modal.errors }\n : modal,\n ),\n }))\n }\n\n private setErrors(name: string, errors: Record<string, string>): void {\n const normalized: Record<string, string[]> = {}\n\n for (const [key, message] of Object.entries(errors)) {\n normalized[key] = [message]\n }\n\n this.store.set((state) => ({\n ...state,\n modals: state.modals.map((modal) =>\n modal.action.name === name ? { ...modal, errors: normalized } : modal,\n ),\n }))\n }\n}\n\n/**\n * Whether an action button should bind its key bindings globally. Record actions repeat\n * on every row, so a page-wide shortcut would run them once per record; they only bind\n * when rendered outside a record (header, page and bulk actions).\n */\nexport function bindsKeyBindings(\n action: Pick<ActionSchema, 'keyBindings' | 'disabled'>,\n context: Pick<ActionContext, 'record'>,\n): boolean {\n return Boolean(action.keyBindings?.length) && !action.disabled && context.record == null\n}\n\n/** Button sizes every renderer's shadcn `Button` understands. */\nexport type ActionButtonSize = 'xs' | 'sm' | 'default' | 'lg'\n\n/**\n * The button size for an action: its own `size()` wins over the size the surrounding\n * list asks for (`md` is the default button, `xl` the largest one).\n */\nexport function resolveActionSize(\n size: Size | null | undefined,\n fallback: ActionButtonSize = 'default',\n): ActionButtonSize {\n switch (size) {\n case 'xs':\n return 'xs'\n case 'sm':\n return 'sm'\n case 'md':\n return 'default'\n case 'lg':\n case 'xl':\n return 'lg'\n default:\n return fallback\n }\n}\n\n/** The square icon-only button that matches a button size. */\nexport function iconButtonSize(size: ActionButtonSize): 'icon-xs' | 'icon-sm' | 'icon' {\n return size === 'xs' ? 'icon-xs' : size === 'sm' ? 'icon-sm' : 'icon'\n}\n\nexport interface DropdownPlacement {\n side: 'top' | 'right' | 'bottom' | 'left'\n align: 'start' | 'center' | 'end'\n}\n\n/**\n * Parse an ActionGroup `dropdownPlacement()` (`bottom-start`, `top-end`, `left`, …) into the\n * side/align pair dropdown primitives take. Defaults to `bottom` / `end`.\n */\nexport function parseDropdownPlacement(placement: string | null | undefined): DropdownPlacement {\n const [side, align] = (placement ?? '').split('-')\n const sides = ['top', 'right', 'bottom', 'left'] as const\n const aligns = ['start', 'center', 'end'] as const\n\n const knownSide = sides.find((candidate) => candidate === side)\n const resolvedAlign =\n aligns.find((candidate) => candidate === align) ?? (knownSide && !align ? 'center' : 'end')\n\n return { side: knownSide ?? 'bottom', align: resolvedAlign }\n}\n\n/** Every action inside a group, nested groups flattened. */\nexport function flattenActionGroup(actions: AnyActionSchema[]): ActionSchema[] {\n return actions.flatMap((action) =>\n isActionGroup(action) ? flattenActionGroup(action.actions) : [action],\n )\n}\n\n/**\n * Whether an open modal is hidden because a nested modal was opened from it. Nested modals\n * hide their parents unless they opt into `overlayParentActions()`; a hidden parent shows\n * again when the child closes.\n */\nexport function isModalHiddenByChild(modals: OpenModal[], index: number): boolean {\n const modal = modals[index]\n\n if (!modal) {\n return false\n }\n\n const path = actionPath(modal.action, modal.context)\n\n return modals.slice(index + 1).some((child) => {\n if (child.modal.overlayParent === true) return false\n\n const parents = child.context.parents ?? []\n\n return parents.some(\n (_, position) => parents.slice(0, position + 1).join(ACTION_PATH_SEPARATOR) === path,\n )\n })\n}\n\n/** Justify class keyword for modal footer buttons: `footerActionsAlignment`, else `alignment`. */\nexport function modalFooterAlignment(\n modal: Pick<ActionModalSchema, 'alignment' | 'footerActionsAlignment'>,\n): 'start' | 'center' | 'end' | 'between' {\n const alignment = modal.footerActionsAlignment ?? modal.alignment\n\n switch (alignment) {\n case 'start':\n case 'center':\n case 'between':\n return alignment\n default:\n return 'end'\n }\n}\n\n/**\n * The physical side a slide-over enters from: `end` (default) is the right edge in\n * left-to-right documents and the left edge in right-to-left ones.\n */\nexport function slideOverSide(\n position: 'start' | 'end' | null | undefined,\n direction: 'ltr' | 'rtl' = 'ltr',\n): 'left' | 'right' {\n const atStart = position === 'start'\n\n return atStart === (direction === 'rtl') ? 'right' : 'left'\n}\n\nfunction isCancelledModal(\n modal: ActionModalSchema | ActionModalCancelledResponse,\n): modal is ActionModalCancelledResponse {\n return (modal as Partial<ActionModalCancelledResponse>).cancelled === true\n}\n\n/**\n * Whether closing a modal has to ask first: its form changed and the modal has\n * `unsavedChangesAlert` (the action's own setting or the panel's `unsavedChangesAlerts()`).\n */\nexport function shouldConfirmModalClose(\n modal: Pick<ActionModalSchema, 'unsavedChangesAlert'>,\n form: { isDirty: boolean } | null | undefined,\n): boolean {\n return Boolean(modal.unsavedChangesAlert && form?.isDirty)\n}\n\n/**\n * Ask whether to close a modal with unsaved changes (`window.confirm`); true when it may\n * close. Without changes (or the alert) it never asks.\n */\nexport function confirmModalClose(\n modal: Pick<ActionModalSchema, 'unsavedChangesAlert'>,\n form: { isDirty: boolean } | null | undefined,\n message: string,\n): boolean {\n if (!shouldConfirmModalClose(modal, form) || typeof window === 'undefined') {\n return true\n }\n\n return window.confirm(message)\n}\n\n/**\n * Warn before leaving the page (a reload, or an Inertia visit when `message` is given)\n * while a modal form with `unsavedChangesAlert` has unsaved changes. Returns a function\n * that removes the listeners.\n */\nexport function guardModalUnload(\n modal: Pick<ActionModalSchema, 'unsavedChangesAlert'>,\n form: { isDirty: boolean } | null | undefined,\n message?: string,\n): () => void {\n if (!modal.unsavedChangesAlert || !form || typeof window === 'undefined') {\n return () => {}\n }\n\n const listener = (event: BeforeUnloadEvent) => {\n if (form.isDirty) {\n event.preventDefault()\n event.returnValue = ''\n }\n }\n\n window.addEventListener('beforeunload', listener)\n\n const removeVisitGuard = message\n ? router.on('before', (event) => {\n if (form.isDirty && isLeavingVisit(event.detail.visit) && !window.confirm(message)) {\n event.preventDefault()\n }\n })\n : () => {}\n\n return () => {\n window.removeEventListener('beforeunload', listener)\n removeVisitGuard()\n }\n}\n\n/** The context of a user or tenant menu action: the page's token and the menu's component. */\nexport function menuActionContext(token: string, menu: 'userMenu' | 'tenantMenu'): ActionContext {\n return { token, component: menu }\n}\n\n/**\n * Split menu entries into their groups (`group` index, nested arrays in\n * `userMenuItems()`), in order; renderers draw a separator between groups.\n */\nexport function groupMenuItems<T extends { group?: number }>(items: T[]): T[][] {\n const groups: T[][] = []\n let current: number | null = null\n\n for (const item of items) {\n const group = item.group ?? 0\n\n if (current === null || group !== current) {\n groups.push([])\n current = group\n }\n\n groups[groups.length - 1]!.push(item)\n }\n\n return groups\n}\n\n/** Whether this button's run (this action for this record) is fetching its modal. */\nexport function isActionLoading(\n state: ActionRunnerState,\n action: ActionSchema,\n context: ActionContext,\n): boolean {\n return state.loadingKey === runKey(action, context)\n}\n\n/** Whether this button's run (this action for this record) has a modal that is submitting. */\nexport function isActionProcessing(\n state: ActionRunnerState,\n action: ActionSchema,\n context: ActionContext,\n): boolean {\n const key = runKey(action, context)\n\n return state.modals.some(\n (modal) => modal.processing && runKey(modal.action, modal.context) === key,\n )\n}\n\nfunction runKey(action: Pick<ActionSchema, 'name'>, context: ActionContext): string {\n return [context.component ?? 'page', actionPath(action, context), context.record ?? ''].join(\n '\\u0000',\n )\n}\n\n/**\n * The action endpoint for a run. \"All records\" bulk actions carry the page's query string\n * (filters, search, list tab), so the server selects exactly the records the table shows.\n */\nexport function actionEndpoint(\n endpoint: string,\n context: Pick<ActionContext, 'allRecords'>,\n search: string = typeof window !== 'undefined' ? window.location.search : '',\n): string {\n if (!context.allRecords || search.length <= 1) {\n return endpoint\n }\n\n return endpoint + (endpoint.includes('?') ? '&' : '?') + search.replace(/^\\?/, '')\n}\n"],"mappings":"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"}
|
package/dist/calendar.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"calendar.d.ts","names":[],"sources":["../src/calendar.ts"],"mappings":";;;;;;;;;;;;;;;qBAqBa;qBACA;qBACA;;qBAGA;;qBAGA;;wBAGG,iBACd,MAAM,yBACN;;iBAYe;EACf;EACA,OAAO;EACP,KAAK;EACL;EACA;EACA,eAAe;;iBAGA;EACf,OAAO;EACP,UAAU;EACV;;iBAGe;EACf,OAAO;EACP;IAAW;;;iBAGI;EACf,OAAO;EACP,KAAK;EACL;EACA;IAAQ;MAAY;;;;iBAGL;EACf,OAAO;EACP,KAAK;;;iBAIU;EACf,QAAQ,KAAK;;EAEb;EACA,GAAG;;EAEH;EACA;;EAEA;;EAEA,MAAM,aAAa;;EAEnB,OAAO,eAAe;;EAEtB,MAAM;;;wBAgBQ,sBACd,QAAQ,KAAK,6BACb,eACA,QAAO,QAAQ,iBACd;;;;;;wBAaa,gBACd,QAAQ,sBACR,KAAK,sBACJ;;;;;
|
|
1
|
+
{"version":3,"file":"calendar.d.ts","names":[],"sources":["../src/calendar.ts"],"mappings":";;;;;;;;;;;;;;;qBAqBa;qBACA;qBACA;;qBAGA;;qBAGA;;wBAGG,iBACd,MAAM,yBACN;;iBAYe;EACf;EACA,OAAO;EACP,KAAK;EACL;EACA;EACA,eAAe;;iBAGA;EACf,OAAO;EACP,UAAU;EACV;;iBAGe;EACf,OAAO;EACP;IAAW;;;iBAGI;EACf,OAAO;EACP,KAAK;EACL;EACA;IAAQ;MAAY;;;;iBAGL;EACf,OAAO;EACP,KAAK;;;iBAIU;EACf,QAAQ,KAAK;;EAEb;EACA,GAAG;;EAEH;EACA;;EAEA;;EAEA,MAAM,aAAa;;EAEnB,OAAO,eAAe;;EAEtB,MAAM;;;wBAgBQ,sBACd,QAAQ,KAAK,6BACb,eACA,QAAO,QAAQ,iBACd;;;;;;wBAaa,gBACd,QAAQ,sBACR,KAAK,sBACJ;;;;;wBAoKa,iBACd,QAAQ,sBACR,KAAK,KAAK;;wBAkBI,wBAAwB,YAAY,gBAAgB"}
|
package/dist/calendar.js
CHANGED
|
@@ -115,6 +115,9 @@ function calendarOptions(widget, env) {
|
|
|
115
115
|
navLinkHint: (date) => t("calendar.go_to_date", { date }, "Go to :date"),
|
|
116
116
|
moreLinkHint: (count) => t("calendar.more_events_hint", { count }, "Show :count more events"),
|
|
117
117
|
closeHint: t("close", {}, "Close"),
|
|
118
|
+
viewDidMount: ({ el }) => {
|
|
119
|
+
el.closest(".fc")?.querySelectorAll(".fc-icon").forEach((icon) => icon.setAttribute("aria-hidden", "true"));
|
|
120
|
+
},
|
|
118
121
|
timeHint: t("calendar.time", {}, "Time"),
|
|
119
122
|
eventHint: t("calendar.event", {}, "Event"),
|
|
120
123
|
weekText: t("calendar.week_number_short", {}, "W"),
|
package/dist/calendar.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"calendar.js","names":[],"sources":["../src/calendar.ts"],"sourcesContent":["import type { ActionContext, ActionRunner } from './actions'\nimport type { Translator } from './i18n'\nimport type { ActionSchema } from './schema/actions'\nimport type {\n CalendarChangeResult,\n CalendarEventSchema,\n CalendarWidgetSchema,\n} from './schema/calendar'\nimport type { NotificationSchema } from './schema/notifications'\n\n/**\n * The framework-agnostic half of the calendar widget (alfasix/alfakit-calendar): every\n * renderer creates a FullCalendar `Calendar` with `calendarOptions()` and only adds the\n * card, the header actions and the lifecycle.\n *\n * FullCalendar runs in UTC mode: the server sends wall times in the calendar's timezone\n * (`2026-10-13T09:30:00`, no offset) and reads the same format back, so no timezone plugin\n * is needed and the server stays the only place that converts timezones.\n */\n\n/** The widget's JSON actions (see `CalendarWidget` in PHP). */\nexport const CALENDAR_FETCH_EVENTS_ACTION = 'calendarFetchEvents'\nexport const CALENDAR_EVENT_DROP_ACTION = 'calendarEventDrop'\nexport const CALENDAR_EVENT_RESIZE_ACTION = 'calendarEventResize'\n\n/** Dates go to the server under this argument: `{ calendar: { start, end, allDay } }`. */\nexport const CALENDAR_ARGUMENTS_KEY = 'calendar'\n\n/** The registry key of the calendar widget component. */\nexport const CALENDAR_REGISTRY_KEY = 'widget:calendar'\n\n/** A UTC-mode FullCalendar date as the wall-time string the server reads. */\nexport function calendarWallTime(\n date: Date | null | undefined,\n allDay = false,\n): string | undefined {\n if (!date || Number.isNaN(date.getTime())) {\n return undefined\n }\n\n const iso = date.toISOString()\n\n return allDay ? iso.slice(0, 10) : iso.slice(0, 19)\n}\n\n/** The subset of FullCalendar's event API the callbacks use. */\nexport interface CalendarEventApi {\n id: string\n start: Date | null\n end: Date | null\n allDay: boolean\n url: string\n extendedProps: Record<string, unknown>\n}\n\nexport interface CalendarEventChangeInfo {\n event: CalendarEventApi\n oldEvent: CalendarEventApi\n revert(): void\n}\n\nexport interface CalendarEventClickInfo {\n event: CalendarEventApi\n jsEvent: { preventDefault(): void }\n}\n\nexport interface CalendarSelectInfo {\n start: Date\n end: Date\n allDay: boolean\n view: { calendar: { unselect(): void } }\n}\n\nexport interface CalendarFetchInfo {\n start: Date\n end: Date\n}\n\n/** What the options need from the renderer. */\nexport interface CalendarEnvironment {\n runner: Pick<ActionRunner, 'trigger' | 'executeJson'>\n /** The page context token. */\n token: string\n t: Translator['t']\n /** The panel's BCP 47 locale (the widget's own `locale` wins). */\n locale: string\n direction: 'ltr' | 'rtl'\n /** The panel's first day of the week (the widget's own `firstDay` wins). */\n firstDayOfWeek: number\n /** Visit an event's URL (Inertia for internal links). */\n visit(url: string, newTab: boolean): void\n /** Show notifications sent by the server during a change. */\n notify(notifications: NotificationSchema[]): void\n /** Show a failure (a toast). */\n error(message: string): void\n}\n\nconst VIEW_LABELS: Record<string, [key: string, fallback: string]> = {\n dayGridMonth: ['calendar.month', 'Month'],\n dayGridWeek: ['calendar.week', 'Week'],\n dayGridDay: ['calendar.day', 'Day'],\n timeGridWeek: ['calendar.week', 'Week'],\n timeGridDay: ['calendar.day', 'Day'],\n listDay: ['calendar.list', 'List'],\n listWeek: ['calendar.list', 'List'],\n listMonth: ['calendar.list', 'List'],\n listYear: ['calendar.list', 'List'],\n}\n\n/** The action context of a calendar widget's actions. */\nexport function calendarActionContext(\n widget: Pick<CalendarWidgetSchema, 'id'>,\n token: string,\n extra: Partial<ActionContext> = {},\n): ActionContext {\n return { token, component: `widget:${widget.id}`, ...extra }\n}\n\nfunction jsonAction(name: string): ActionSchema {\n return { type: 'Action', name, hasAction: true }\n}\n\n/**\n * The FullCalendar options of a calendar widget: the server's `config` first, then the\n * options the widget controls (views, locale, direction, first day, translated buttons)\n * and the callbacks that fetch events and run the widget's actions.\n */\nexport function calendarOptions(\n widget: CalendarWidgetSchema,\n env: CalendarEnvironment,\n): Record<string, unknown> {\n const t = env.t\n const views = widget.views.length > 0 ? widget.views : ['dayGridMonth']\n const buttonText: Record<string, string> = {\n today: t('calendar.today', {}, 'Today'),\n month: t('calendar.month', {}, 'Month'),\n week: t('calendar.week', {}, 'Week'),\n day: t('calendar.day', {}, 'Day'),\n list: t('calendar.list', {}, 'List'),\n }\n\n for (const view of views) {\n const label = VIEW_LABELS[view]\n\n if (label) {\n buttonText[view] = t(label[0], {}, label[1])\n }\n }\n\n const change =\n (action: string) =>\n async (info: CalendarEventChangeInfo): Promise<void> => {\n const { event, oldEvent } = info\n\n try {\n const result = await env.runner.executeJson<CalendarChangeResult>(\n jsonAction(action),\n calendarActionContext(widget, env.token, {\n record: event.id || undefined,\n arguments: {\n [CALENDAR_ARGUMENTS_KEY]: {\n start: calendarWallTime(event.start, event.allDay),\n end: calendarWallTime(event.end, event.allDay),\n allDay: event.allDay,\n oldStart: calendarWallTime(oldEvent.start, oldEvent.allDay),\n oldEnd: calendarWallTime(oldEvent.end, oldEvent.allDay),\n oldAllDay: oldEvent.allDay,\n },\n },\n }),\n )\n\n env.notify(result?.notifications ?? [])\n\n if (!result?.ok) {\n info.revert()\n }\n } catch {\n info.revert()\n env.error(t('calendar.move_failed', {}, 'The change could not be saved.'))\n }\n }\n\n return {\n ...(widget.config ?? {}),\n initialView: widget.initialView ?? views[0],\n timeZone: 'UTC',\n locale: widget.locale ?? env.locale,\n direction: env.direction,\n firstDay: widget.firstDay ?? env.firstDayOfWeek,\n editable: Boolean(widget.editable),\n selectable: Boolean(widget.selectable),\n headerToolbar: {\n start: 'prev,next today',\n center: 'title',\n end: views.length > 1 ? views.join(',') : '',\n },\n buttonText,\n buttonHints: {\n prev: t('calendar.previous', {}, 'Previous'),\n next: t('calendar.next', {}, 'Next'),\n today: t('calendar.today', {}, 'Today'),\n },\n allDayText: t('calendar.all_day', {}, 'All day'),\n noEventsText: t('calendar.no_events', {}, 'No events to display'),\n moreLinkText: (count: number) => t('calendar.more_events', { count }, '+:count more'),\n // FullCalendar's own accessible texts (its English defaults otherwise: no FullCalendar\n // locale is loaded; dates still follow `locale` through Intl).\n viewHint: (buttonText: string) => t('calendar.view_hint', { view: buttonText }, ':view view'),\n navLinkHint: (date: string) => t('calendar.go_to_date', { date }, 'Go to :date'),\n moreLinkHint: (count: number) =>\n t('calendar.more_events_hint', { count }, 'Show :count more events'),\n closeHint: t('close', {}, 'Close'),\n timeHint: t('calendar.time', {}, 'Time'),\n eventHint: t('calendar.event', {}, 'Event'),\n weekText: t('calendar.week_number_short', {}, 'W'),\n weekTextLong: t('calendar.week', {}, 'Week'),\n events: (\n info: CalendarFetchInfo,\n success: (events: CalendarEventSchema[]) => void,\n failure: (error: Error) => void,\n ) => {\n env.runner\n .executeJson<{ events?: CalendarEventSchema[] }>(\n jsonAction(CALENDAR_FETCH_EVENTS_ACTION),\n calendarActionContext(widget, env.token, {\n arguments: {\n [CALENDAR_ARGUMENTS_KEY]: {\n start: calendarWallTime(info.start),\n end: calendarWallTime(info.end),\n },\n },\n }),\n )\n .then((result) => success(result?.events ?? []))\n .catch((error: unknown) => {\n failure(error instanceof Error ? error : new Error(String(error)))\n env.error(t('calendar.load_failed', {}, 'The events could not be loaded.'))\n })\n },\n eventClick: (info: CalendarEventClickInfo) => {\n const { event } = info\n\n if (event.url) {\n info.jsEvent.preventDefault()\n env.visit(event.url, event.extendedProps.openUrlInNewTab === true)\n\n return\n }\n\n if (widget.eventClickAction && event.id) {\n info.jsEvent.preventDefault()\n void env.runner.trigger(\n widget.eventClickAction,\n calendarActionContext(widget, env.token, { record: event.id }),\n )\n }\n },\n select: (info: CalendarSelectInfo) => {\n info.view.calendar.unselect()\n\n if (!widget.dateSelectAction) {\n return\n }\n\n void env.runner.trigger(\n widget.dateSelectAction,\n calendarActionContext(widget, env.token, {\n arguments: {\n [CALENDAR_ARGUMENTS_KEY]: {\n start: calendarWallTime(info.start, info.allDay),\n end: calendarWallTime(info.end, info.allDay),\n allDay: info.allDay,\n },\n },\n }),\n )\n },\n eventDrop: change(CALENDAR_EVENT_DROP_ACTION),\n eventResize: change(CALENDAR_EVENT_RESIZE_ACTION),\n }\n}\n\n/**\n * A key that changes when the calendar has to be created again (views, locale, direction,\n * interactions or extra options changed); other schema updates only refetch the events.\n */\nexport function calendarIdentity(\n widget: CalendarWidgetSchema,\n env: Pick<CalendarEnvironment, 'locale' | 'direction' | 'firstDayOfWeek'>,\n): string {\n return JSON.stringify([\n widget.id,\n widget.views,\n widget.initialView,\n widget.locale ?? env.locale,\n widget.firstDay ?? env.firstDayOfWeek,\n env.direction,\n Boolean(widget.editable),\n Boolean(widget.selectable),\n widget.eventClickAction?.name ?? null,\n widget.dateSelectAction?.name ?? null,\n widget.config ?? null,\n ])\n}\n\n/** A client-side toast for a failed calendar or tree request. */\nexport function clientErrorNotification(id: string, title: string): NotificationSchema {\n return { id: `alfakit-${id}`, title, status: 'danger', format: 'alfakit' }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"calendar.js","names":[],"sources":["../src/calendar.ts"],"sourcesContent":["import type { ActionContext, ActionRunner } from './actions'\nimport type { Translator } from './i18n'\nimport type { ActionSchema } from './schema/actions'\nimport type {\n CalendarChangeResult,\n CalendarEventSchema,\n CalendarWidgetSchema,\n} from './schema/calendar'\nimport type { NotificationSchema } from './schema/notifications'\n\n/**\n * The framework-agnostic half of the calendar widget (alfasix/alfakit-calendar): every\n * renderer creates a FullCalendar `Calendar` with `calendarOptions()` and only adds the\n * card, the header actions and the lifecycle.\n *\n * FullCalendar runs in UTC mode: the server sends wall times in the calendar's timezone\n * (`2026-10-13T09:30:00`, no offset) and reads the same format back, so no timezone plugin\n * is needed and the server stays the only place that converts timezones.\n */\n\n/** The widget's JSON actions (see `CalendarWidget` in PHP). */\nexport const CALENDAR_FETCH_EVENTS_ACTION = 'calendarFetchEvents'\nexport const CALENDAR_EVENT_DROP_ACTION = 'calendarEventDrop'\nexport const CALENDAR_EVENT_RESIZE_ACTION = 'calendarEventResize'\n\n/** Dates go to the server under this argument: `{ calendar: { start, end, allDay } }`. */\nexport const CALENDAR_ARGUMENTS_KEY = 'calendar'\n\n/** The registry key of the calendar widget component. */\nexport const CALENDAR_REGISTRY_KEY = 'widget:calendar'\n\n/** A UTC-mode FullCalendar date as the wall-time string the server reads. */\nexport function calendarWallTime(\n date: Date | null | undefined,\n allDay = false,\n): string | undefined {\n if (!date || Number.isNaN(date.getTime())) {\n return undefined\n }\n\n const iso = date.toISOString()\n\n return allDay ? iso.slice(0, 10) : iso.slice(0, 19)\n}\n\n/** The subset of FullCalendar's event API the callbacks use. */\nexport interface CalendarEventApi {\n id: string\n start: Date | null\n end: Date | null\n allDay: boolean\n url: string\n extendedProps: Record<string, unknown>\n}\n\nexport interface CalendarEventChangeInfo {\n event: CalendarEventApi\n oldEvent: CalendarEventApi\n revert(): void\n}\n\nexport interface CalendarEventClickInfo {\n event: CalendarEventApi\n jsEvent: { preventDefault(): void }\n}\n\nexport interface CalendarSelectInfo {\n start: Date\n end: Date\n allDay: boolean\n view: { calendar: { unselect(): void } }\n}\n\nexport interface CalendarFetchInfo {\n start: Date\n end: Date\n}\n\n/** What the options need from the renderer. */\nexport interface CalendarEnvironment {\n runner: Pick<ActionRunner, 'trigger' | 'executeJson'>\n /** The page context token. */\n token: string\n t: Translator['t']\n /** The panel's BCP 47 locale (the widget's own `locale` wins). */\n locale: string\n direction: 'ltr' | 'rtl'\n /** The panel's first day of the week (the widget's own `firstDay` wins). */\n firstDayOfWeek: number\n /** Visit an event's URL (Inertia for internal links). */\n visit(url: string, newTab: boolean): void\n /** Show notifications sent by the server during a change. */\n notify(notifications: NotificationSchema[]): void\n /** Show a failure (a toast). */\n error(message: string): void\n}\n\nconst VIEW_LABELS: Record<string, [key: string, fallback: string]> = {\n dayGridMonth: ['calendar.month', 'Month'],\n dayGridWeek: ['calendar.week', 'Week'],\n dayGridDay: ['calendar.day', 'Day'],\n timeGridWeek: ['calendar.week', 'Week'],\n timeGridDay: ['calendar.day', 'Day'],\n listDay: ['calendar.list', 'List'],\n listWeek: ['calendar.list', 'List'],\n listMonth: ['calendar.list', 'List'],\n listYear: ['calendar.list', 'List'],\n}\n\n/** The action context of a calendar widget's actions. */\nexport function calendarActionContext(\n widget: Pick<CalendarWidgetSchema, 'id'>,\n token: string,\n extra: Partial<ActionContext> = {},\n): ActionContext {\n return { token, component: `widget:${widget.id}`, ...extra }\n}\n\nfunction jsonAction(name: string): ActionSchema {\n return { type: 'Action', name, hasAction: true }\n}\n\n/**\n * The FullCalendar options of a calendar widget: the server's `config` first, then the\n * options the widget controls (views, locale, direction, first day, translated buttons)\n * and the callbacks that fetch events and run the widget's actions.\n */\nexport function calendarOptions(\n widget: CalendarWidgetSchema,\n env: CalendarEnvironment,\n): Record<string, unknown> {\n const t = env.t\n const views = widget.views.length > 0 ? widget.views : ['dayGridMonth']\n const buttonText: Record<string, string> = {\n today: t('calendar.today', {}, 'Today'),\n month: t('calendar.month', {}, 'Month'),\n week: t('calendar.week', {}, 'Week'),\n day: t('calendar.day', {}, 'Day'),\n list: t('calendar.list', {}, 'List'),\n }\n\n for (const view of views) {\n const label = VIEW_LABELS[view]\n\n if (label) {\n buttonText[view] = t(label[0], {}, label[1])\n }\n }\n\n const change =\n (action: string) =>\n async (info: CalendarEventChangeInfo): Promise<void> => {\n const { event, oldEvent } = info\n\n try {\n const result = await env.runner.executeJson<CalendarChangeResult>(\n jsonAction(action),\n calendarActionContext(widget, env.token, {\n record: event.id || undefined,\n arguments: {\n [CALENDAR_ARGUMENTS_KEY]: {\n start: calendarWallTime(event.start, event.allDay),\n end: calendarWallTime(event.end, event.allDay),\n allDay: event.allDay,\n oldStart: calendarWallTime(oldEvent.start, oldEvent.allDay),\n oldEnd: calendarWallTime(oldEvent.end, oldEvent.allDay),\n oldAllDay: oldEvent.allDay,\n },\n },\n }),\n )\n\n env.notify(result?.notifications ?? [])\n\n if (!result?.ok) {\n info.revert()\n }\n } catch {\n info.revert()\n env.error(t('calendar.move_failed', {}, 'The change could not be saved.'))\n }\n }\n\n return {\n ...(widget.config ?? {}),\n initialView: widget.initialView ?? views[0],\n timeZone: 'UTC',\n locale: widget.locale ?? env.locale,\n direction: env.direction,\n firstDay: widget.firstDay ?? env.firstDayOfWeek,\n editable: Boolean(widget.editable),\n selectable: Boolean(widget.selectable),\n headerToolbar: {\n start: 'prev,next today',\n center: 'title',\n end: views.length > 1 ? views.join(',') : '',\n },\n buttonText,\n buttonHints: {\n prev: t('calendar.previous', {}, 'Previous'),\n next: t('calendar.next', {}, 'Next'),\n today: t('calendar.today', {}, 'Today'),\n },\n allDayText: t('calendar.all_day', {}, 'All day'),\n noEventsText: t('calendar.no_events', {}, 'No events to display'),\n moreLinkText: (count: number) => t('calendar.more_events', { count }, '+:count more'),\n // FullCalendar's own accessible texts (its English defaults otherwise: no FullCalendar\n // locale is loaded; dates still follow `locale` through Intl).\n viewHint: (buttonText: string) => t('calendar.view_hint', { view: buttonText }, ':view view'),\n navLinkHint: (date: string) => t('calendar.go_to_date', { date }, 'Go to :date'),\n moreLinkHint: (count: number) =>\n t('calendar.more_events_hint', { count }, 'Show :count more events'),\n closeHint: t('close', {}, 'Close'),\n // The toolbar's chevrons are `role=\"img\"` without a name; the buttons around them are\n // named (`buttonHints`), so the icons are decorative.\n viewDidMount: ({ el }: { el: HTMLElement }) => {\n el.closest('.fc')\n ?.querySelectorAll('.fc-icon')\n .forEach((icon) => icon.setAttribute('aria-hidden', 'true'))\n },\n timeHint: t('calendar.time', {}, 'Time'),\n eventHint: t('calendar.event', {}, 'Event'),\n weekText: t('calendar.week_number_short', {}, 'W'),\n weekTextLong: t('calendar.week', {}, 'Week'),\n events: (\n info: CalendarFetchInfo,\n success: (events: CalendarEventSchema[]) => void,\n failure: (error: Error) => void,\n ) => {\n env.runner\n .executeJson<{ events?: CalendarEventSchema[] }>(\n jsonAction(CALENDAR_FETCH_EVENTS_ACTION),\n calendarActionContext(widget, env.token, {\n arguments: {\n [CALENDAR_ARGUMENTS_KEY]: {\n start: calendarWallTime(info.start),\n end: calendarWallTime(info.end),\n },\n },\n }),\n )\n .then((result) => success(result?.events ?? []))\n .catch((error: unknown) => {\n failure(error instanceof Error ? error : new Error(String(error)))\n env.error(t('calendar.load_failed', {}, 'The events could not be loaded.'))\n })\n },\n eventClick: (info: CalendarEventClickInfo) => {\n const { event } = info\n\n if (event.url) {\n info.jsEvent.preventDefault()\n env.visit(event.url, event.extendedProps.openUrlInNewTab === true)\n\n return\n }\n\n if (widget.eventClickAction && event.id) {\n info.jsEvent.preventDefault()\n void env.runner.trigger(\n widget.eventClickAction,\n calendarActionContext(widget, env.token, { record: event.id }),\n )\n }\n },\n select: (info: CalendarSelectInfo) => {\n info.view.calendar.unselect()\n\n if (!widget.dateSelectAction) {\n return\n }\n\n void env.runner.trigger(\n widget.dateSelectAction,\n calendarActionContext(widget, env.token, {\n arguments: {\n [CALENDAR_ARGUMENTS_KEY]: {\n start: calendarWallTime(info.start, info.allDay),\n end: calendarWallTime(info.end, info.allDay),\n allDay: info.allDay,\n },\n },\n }),\n )\n },\n eventDrop: change(CALENDAR_EVENT_DROP_ACTION),\n eventResize: change(CALENDAR_EVENT_RESIZE_ACTION),\n }\n}\n\n/**\n * A key that changes when the calendar has to be created again (views, locale, direction,\n * interactions or extra options changed); other schema updates only refetch the events.\n */\nexport function calendarIdentity(\n widget: CalendarWidgetSchema,\n env: Pick<CalendarEnvironment, 'locale' | 'direction' | 'firstDayOfWeek'>,\n): string {\n return JSON.stringify([\n widget.id,\n widget.views,\n widget.initialView,\n widget.locale ?? env.locale,\n widget.firstDay ?? env.firstDayOfWeek,\n env.direction,\n Boolean(widget.editable),\n Boolean(widget.selectable),\n widget.eventClickAction?.name ?? null,\n widget.dateSelectAction?.name ?? null,\n widget.config ?? null,\n ])\n}\n\n/** A client-side toast for a failed calendar or tree request. */\nexport function clientErrorNotification(id: string, title: string): NotificationSchema {\n return { id: `alfakit-${id}`, title, status: 'danger', format: 'alfakit' }\n}\n"],"mappings":"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"}
|
package/dist/form.d.ts
CHANGED
|
@@ -154,6 +154,12 @@ export declare class FormStore {
|
|
|
154
154
|
}): void;
|
|
155
155
|
/** Call when a field loses focus so `live(onBlur: true)` fields refresh. */
|
|
156
156
|
blur(path: string): void;
|
|
157
|
+
/**
|
|
158
|
+
* The field whose `live()` a change at `path` triggers, and the path to refresh: a
|
|
159
|
+
* translatable field's language input (`title.en`) reports its field (`title`), whose
|
|
160
|
+
* `afterStateUpdated()` gets the whole `{ code: value }` state.
|
|
161
|
+
*/
|
|
162
|
+
private liveTarget;
|
|
157
163
|
/** Replace the whole state (e.g. after the server returns a refreshed payload). */
|
|
158
164
|
fill(payload: FormPayload, options?: {
|
|
159
165
|
keepDirty?: boolean;
|
package/dist/form.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"form.d.ts","names":[],"sources":["../src/form.ts"],"mappings":";;;;;;;;
|
|
1
|
+
{"version":3,"file":"form.d.ts","names":[],"sources":["../src/form.ts"],"mappings":";;;;;;;;iBAsBiB;EACf,YAAY;EACZ,QAAQ;EACR,QAAQ;;EAER,OAAO;;EAEP;EACA;EACA,SAAS;;iBAGM;;EAEf;;EAEA;;EAEA;;EAEA;;EAEA;;EAEA;;;;;EAKA,eAAe;EACf,WAAW;;EAEX,mBAAmB,eAAe;;EAElC,eAAe;;;;;EAKf;;;wBAIc,aAAa;;wBAKb,WAAW,gBAAgB;;;;wBAO3B,cACd,YAAY,mBACZ,OAAM,gBACL;;;;;;;;wBA2Ba,gBACd,YAAY,mBACZ,cACA,SAAQ,0BACP;;;;;;wBAmEa,mBACd,OAAO,mBACP,gBAAgB,yBAChB,QAAQ;;;;;;wBAkBM,mBACd,WAAW,KAAK,2DAChB,QAAQ;;;;;wBAkBM,cAAc,KAAK,KAAK;;;;;wBAQxB,aACd,MAAM,MAAM,KAAK,mCACjB;;;;;wBAcc,YACd,MAAM,MAAM,KAAK,oEACjB,mBACA,gBAAgB,yBAChB,QAAQ;;;;;;;wBA4BM,WACd,QAAQ,SAAS,gDACjB;;;;;qBAiBW;UAyBD;WAxBM,OAAO,MAAM;mBAEZ;UAET;;;;;UAMA;;;;;;UAOA;;UAGA;EAGN,YAAA,SAAS,aACD,SAAS;MAkBf,SAAS;;;;;EAQb,SAAS;;MAKL;;MAKA;MAIA;MAIA;MAIA;MAIA;;MAKA,uBAAuB;;EAK3B,kBAAkB;;;;;;EAiBlB,gBAAgB;EAahB,UAAU,WAAW,OAAO;EAM5B,SAAS,aAAa,cAAc,WAAW,IAAI;;;;;EAQnD,SAAS,cAAc,gBAAgB;IAAW;;;EAoClD,KAAK;;;;;;UAaG;;EAmBR,KAAK,SAAS,aAAa;IAAW;;EAYtC,UAAU,QAAQ;EAUlB;EAIA,SAAS;EAIT,cAAc;MAIV;EAIJ,UAAU,eAAe;UAMjB;;;;;EAiBR,QAAQ,gBAAgB;;;;;;EAgBlB,UAAU;;;;;EAYhB,YAAY;UAYJ;UAEM;;;;;;;EAoEd,cACE,UAAU,uBACV,MAAM,yBACN,QAAO;;;;;;EAqCH,aAAa,iBAAiB,uBAAuB;;EA8CrD,cACJ,cACA,iBACC,QAAQ;IAAQ;IAAe;;EAe5B,aACJ,cACA,QAAQ,yBACP,QAAQ;;EAgBL,YACJ,cACA;IAAS;IAAiB;IAAqB;KAC/C,SAAS,cACR,QAAQ;;;;;EAqBL,WACJ,cACA,oBACA,OAAO,YACP,SAAS,cACR,QAAQ;EAgBX;;;;;;;;wBAkCc,aACd;EAAQ;EAAgB;sBACxB;EAAS;EAAa;EAAmB;;;iBAY1B;EACf;EACA,KAAK;EACL;EACA;EACA;;;;;;;;wBASc,eAAe,OAAO,cAAc;iBAuBnC;EACf;EACA,OAAO;;;wBAIO,mBACd,kBACA,eAAe;wBAKD,mBAAmB,kBAAkB;wBAIrC,iBAAiB,kBAAkB,cAAc;wBAYjD,kBAAkB,kBAAkB;;;;;YAWxC,aAAa,KAAK,eAAe;wBAiB7B,iBAAiB,GAC/B,OAAO,aAAa,IACpB,cACA,aACC,aAAa;wBAYA,mBAAmB,GAAG,OAAO,aAAa,IAAI,kBAAkB,aAAa;wBAM7E,kBAAkB,GAAG,OAAO,aAAa,IAAI,iBAAiB,aAAa"}
|
package/dist/form.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createStore } from "./store.js";
|
|
2
2
|
import { debugRecorder } from "./debug.js";
|
|
3
|
+
import { currentPageQuery } from "./page-query.js";
|
|
3
4
|
import { HttpError, requestJson } from "./http.js";
|
|
4
5
|
import { cloneState } from "./utils/clone.js";
|
|
5
6
|
import { getPath, setPath } from "./utils/path.js";
|
|
@@ -201,6 +202,7 @@ var FormStore = class {
|
|
|
201
202
|
token: this.options.token,
|
|
202
203
|
component: this.options.component,
|
|
203
204
|
record: this.options.record ?? void 0,
|
|
205
|
+
tableQuery: currentPageQuery(),
|
|
204
206
|
...this.options.params?.()
|
|
205
207
|
};
|
|
206
208
|
}
|
|
@@ -246,12 +248,34 @@ var FormStore = class {
|
|
|
246
248
|
});
|
|
247
249
|
this.editsInFlight?.add(path);
|
|
248
250
|
if (options.silent) return;
|
|
249
|
-
const
|
|
250
|
-
if (field
|
|
251
|
+
const target = this.liveTarget(path);
|
|
252
|
+
if (target?.field.live && !target.field.live.onBlur) this.scheduleRefresh(target.path, target.field.live.debounce ?? 150);
|
|
251
253
|
}
|
|
252
254
|
/** Call when a field loses focus so `live(onBlur: true)` fields refresh. */
|
|
253
255
|
blur(path) {
|
|
254
|
-
|
|
256
|
+
const target = this.liveTarget(path);
|
|
257
|
+
if (target?.field.live?.onBlur && this.state.dirty.has(path)) this.refresh(target.path);
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* The field whose `live()` a change at `path` triggers, and the path to refresh: a
|
|
261
|
+
* translatable field's language input (`title.en`) reports its field (`title`), whose
|
|
262
|
+
* `afterStateUpdated()` gets the whole `{ code: value }` state.
|
|
263
|
+
*/
|
|
264
|
+
liveTarget(path) {
|
|
265
|
+
const field = this.findField(path);
|
|
266
|
+
if (field) return {
|
|
267
|
+
field,
|
|
268
|
+
path
|
|
269
|
+
};
|
|
270
|
+
const dot = path.lastIndexOf(".");
|
|
271
|
+
if (dot <= 0) return void 0;
|
|
272
|
+
const parentPath = path.slice(0, dot);
|
|
273
|
+
const parent = this.findField(parentPath);
|
|
274
|
+
const code = path.slice(dot + 1);
|
|
275
|
+
return parent?.translatable?.locales.some((locale) => locale.code === code) ? {
|
|
276
|
+
field: parent,
|
|
277
|
+
path: parentPath
|
|
278
|
+
} : void 0;
|
|
255
279
|
}
|
|
256
280
|
/** Replace the whole state (e.g. after the server returns a refreshed payload). */
|
|
257
281
|
fill(payload, options = {}) {
|