@alfakit/core 0.1.8 → 0.1.9
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 -1
- package/dist/actions.js.map +1 -1
- package/dist/schema/panel.d.ts +2 -0
- package/dist/schema/panel.d.ts.map +1 -1
- package/dist/schema/panel.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# @alfakit/core
|
|
2
2
|
|
|
3
|
+
## 0.1.9
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Row actions' modal requests send the table state, so modals work on any list tab; `standalone.css` status colours pass WCAG AA; standalone components get their own action runner and form id prefix.
|
|
8
|
+
|
|
3
9
|
## 0.1.8
|
|
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;;;;;
|
|
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;UAgFK;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
|
@@ -225,7 +225,7 @@ var ActionRunner = class {
|
|
|
225
225
|
records: records !== void 0 && records.length > 0 ? records : void 0,
|
|
226
226
|
allRecords: context.allRecords ? 1 : void 0,
|
|
227
227
|
bulk: isBulk ? 1 : void 0,
|
|
228
|
-
tableQuery:
|
|
228
|
+
tableQuery: currentPageQuery(),
|
|
229
229
|
arguments: context.arguments
|
|
230
230
|
};
|
|
231
231
|
const mountedData = this.mountedData(context);
|
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 tableQuery: isBulk ? currentPageQuery() : undefined,\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":"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"}
|
|
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":"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"}
|
package/dist/schema/panel.d.ts
CHANGED
|
@@ -339,6 +339,8 @@ export interface PanelSchema {
|
|
|
339
339
|
schemaContract?: string;
|
|
340
340
|
path: string;
|
|
341
341
|
url?: string;
|
|
342
|
+
/** Where "G then D" goes when it isn't `url`: the app's `/dashboard` for a starter kit panel without a dashboard page. */
|
|
343
|
+
dashboardUrl?: string;
|
|
342
344
|
brandName: string;
|
|
343
345
|
brandLogo?: string;
|
|
344
346
|
darkModeBrandLogo?: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"panel.d.ts","names":[],"sources":["../../src/schema/panel.ts"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"panel.d.ts","names":[],"sources":["../../src/schema/panel.ts"],"mappings":"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"}
|
package/dist/schema/panel.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"panel.js","names":[],"sources":["../../src/schema/panel.ts"],"sourcesContent":["import type { ActionSchema, AnyActionSchema } from './actions'\nimport type { CommandPaletteSchema, KeyboardShortcutsSchema } from './command-palette'\nimport type { Color, IconName, RecordKey, Width } from './common'\nimport type { SchemaComponent } from './components'\nimport type { DebugSchema } from './debug'\nimport type { DevToolsSchema } from './dev-tools'\nimport type { FormBlock, InfolistBlock, LinksBlock, SchemaBlock } from './form'\nimport type { NotificationSchema } from './notifications'\nimport type { PwaSchema } from './pwa'\nimport type { ConflictsSchema, PresenceSchema, RealtimeSchema } from './realtime'\nimport type { DeferredTabBadges } from './resources'\nimport type { StandaloneComponentSchema } from './standalone'\nimport type { RelationManagerSchema, RelationManagersBlock, TableBlock } from './table'\nimport type { WidgetSchema, WidgetsBlock } from './widgets'\n\nexport interface NavigationItemSchema {\n /** Text, or sanitised HTML when `labelHtml` is true (an `Htmlable` label). */\n label: string\n /** `label` is sanitised HTML: render it as markup (use `htmlLabelToText()` for text). */\n labelHtml?: boolean\n icon?: IconName\n /**\n * Split navigation (`alfakit.navigation` is an Inertia once prop): the icon of the item\n * while active, swapped in by `resolvePanelNavigation()`.\n */\n activeIcon?: IconName\n url?: string\n openInNewTab?: boolean\n active?: boolean\n badge?: string | number\n badgeColor?: Color\n badgeTooltip?: string\n /** `Panel::deferNavigationBadges()`: the item has a badge that loads after the page. */\n deferredBadge?: boolean\n /** Set by `resolvePanelNavigation()` while a deferred badge is loading (show a placeholder). */\n badgePending?: boolean\n /** `NavigationItem::extraAttributes()`: HTML attributes for the item's link (`class` merges). */\n extraAttributes?: Record<string, string | number | boolean | null>\n children?: NavigationItemSchema[]\n}\n\nexport interface NavigationGroupSchema {\n label?: string | null\n icon?: IconName\n collapsible: boolean\n collapsed?: boolean\n items: NavigationItemSchema[]\n /** `NavigationGroup::extraSidebarAttributes()`: HTML attributes for the group in the sidebar. */\n extraSidebarAttributes?: Record<string, string | number | boolean | null>\n /** `NavigationGroup::extraTopbarAttributes()`: HTML attributes for the group in the top navigation. */\n extraTopbarAttributes?: Record<string, string | number | boolean | null>\n}\n\n/** A navigation item's badge, from `navigationState` or the deferred `alfakitNavigationBadges` prop. */\nexport interface NavigationBadgeSchema {\n badge: string | number\n badgeColor?: Color\n badgeTooltip?: string\n}\n\n/**\n * The per-request part of the panel navigation, whose structure (`navigation`) is an\n * Inertia once prop. Items are addressed by position: `group.item[.child…]` (`0.2.1`).\n */\nexport interface NavigationStateSchema {\n /** The active items (an active child's ancestors included). */\n active?: string[]\n /** Badges, unless `Panel::deferNavigationBadges()` defers them. */\n badges?: Record<string, NavigationBadgeSchema>\n}\n\n/** One `starterKit()` panel's navigation, as shared with the host application's pages. */\n/** One `starterKit()` panel's dashboard widgets on a host page (`alfakitWidgets` prop). */\nexport interface StarterKitDashboardWidgetsSchema {\n /** The panel id. */\n id: string\n /** A standalone widgets payload bound to the panel (lazy loading and polling included). */\n component: StandaloneComponentSchema\n}\n\nexport interface StarterKitNavigationSchema {\n /** The panel id. */\n id: string\n /** The panel's home URL. */\n url: string\n brandName: string\n /** Empty for tenancy panels outside a tenant (their items link to tenant routes). */\n groups: NavigationGroupSchema[]\n /** What the drop-in header components render before they load. */\n widgets?: StarterKitWidgetsSchema\n}\n\n/**\n * The `widgets` of a `starterKit()` panel's shared navigation: what `<AlfakitSearch />`,\n * `<AlfakitNotifications />`, `<AlfakitTenantSwitcher />` and `<AlfakitLanguageSwitcher />`\n * show on any host page before their code and the panel data (`endpoint`) have loaded.\n */\nexport interface StarterKitWidgetsSchema {\n /** GET: the panel data the components render with (the `alfakit` prop of the panel's pages). */\n endpoint: string\n /** Global search or the command palette (tenancy panels: only inside a tenant). */\n search?: { keyBindings: string[]; placeholder: string }\n /** `databaseNotifications()`: the bell's label and the user's unread count. */\n notifications?: { label: string; unreadCount: number }\n /** Tenancy panels: the user's tenants; `current` is null on the host's own pages. */\n tenancy?: {\n current?: TenantSchema | null\n tenants: TenantSchema[]\n registrationUrl?: string\n /** The registration page's own label (`RegisterTenant::getLabel()`). */\n registrationLabel?: string\n searchable?: true\n }\n /**\n * `<AlfakitLanguageSwitcher />`: the button's label (\"Change language\") and the current\n * language, when the panel has a language switcher with two or more languages\n * (`locales()`). The languages and the locale endpoint come with the panel data.\n */\n language?: { label: string; current: LocaleSchema }\n /** While impersonating: the banner the header components show on the host's own pages. */\n impersonation?: ImpersonationSchema\n /**\n * `AiPlugin::copilot()` (alfasix/alfakit-ai, `$panel->starterKitWidget('copilot')`): the\n * label and shortcuts of `<AlfakitCopilot />` before its code loads. Tenancy panels: only\n * inside a tenant.\n */\n copilot?: { label: string; keyBindings: string[] }\n}\n\n/** A page's secondary navigation: cluster pages or a record's pages. */\nexport interface SubNavigationSchema {\n position: 'start' | 'end' | 'top'\n groups: NavigationGroupSchema[]\n}\n\n/**\n * An entry of the user or tenant menu: a link (`url`, `method: 'post'` for POST links) or\n * an `Action` with server logic or a modal (`action`), run through the action endpoint\n * with the component `userMenu` / `tenantMenu` and the page's token.\n */\nexport interface MenuItemSchema {\n label?: string\n icon?: IconName\n url?: string\n openInNewTab?: boolean\n color?: Color\n method?: 'post'\n action?: ActionSchema\n /** Group index (nested arrays in `userMenuItems()`); a separator divides groups. */\n group?: number\n}\n\n/** Action endpoint components of the menus (`Panel::USER_MENU_COMPONENT`, …). */\nexport const USER_MENU_COMPONENT = 'userMenu'\nexport const TENANT_MENU_COMPONENT = 'tenantMenu'\n\nexport interface PanelUserSchema {\n id: RecordKey\n name: string\n email?: string | null\n avatar?: string | null\n}\n\nexport interface TenantSchema {\n id: RecordKey\n /** Text, or sanitised HTML when `nameHtml` is true (an `Htmlable` `getAlfakitName()`). */\n name: string\n /** `name` is sanitised HTML: render it as markup (use `htmlLabelToText()` for text). */\n nameHtml?: boolean\n avatar?: string | null\n url: string\n /** `HasCurrentTenantLabel`: a caption shown with the current tenant (\"Active team\"). */\n label?: string | null\n}\n\n/** The panel's design, see `Panel::theme()`. */\nexport interface PanelThemeSchema {\n name: string\n /** Value of `data-alfakit-theme`, base theme first: `\"alfakit acme\"`. */\n attribute: string\n sidebar: 'inset' | 'floating' | 'flush'\n density: 'comfortable' | 'compact'\n radius?: string | null\n font?: string | null\n /** CSS value for `--alfakit-font`. */\n fontCss?: string | null\n /** CSS value for `--alfakit-heading-font`. */\n headingFontCss?: string | null\n /** Stylesheet that loads the font, if any. */\n fontUrl?: string | null\n /** Every font stylesheet to load (body and heading fonts). */\n fontUrls?: string[]\n /** Structure and component styles; mirrored as `data-*` attributes on `<html>`. */\n design: PanelDesignSchema\n /**\n * Icon aliases replaced with `Icons::register()` (`alfakit::panels.sidebar.collapse-button`\n * → a Lucide name, image URL or sanitised SVG). The chrome falls back to its own icon.\n */\n icons?: Record<string, string> | null\n}\n\nexport interface PanelDesignSchema {\n layout: 'sidebar' | 'topbar' | 'rail'\n nav: 'soft' | 'pill' | 'bar' | 'underline'\n header: 'plain' | 'banner' | 'band' | 'compact'\n surfaces: 'bordered' | 'elevated' | 'flat' | 'ring'\n inputs: 'outline' | 'filled' | 'underline'\n buttons: 'rounded' | 'pill' | 'square'\n tables: 'lined' | 'striped' | 'minimal'\n /**\n * Below the `md` breakpoint: `drawer` (default) or the `bottom` mobile preset (bottom\n * navigation bar, tables as stacked cards, slide-over modals, 44px touch targets and\n * sticky form actions). See `Panel::mobile()`.\n */\n mobile?: 'drawer' | 'bottom'\n}\n\n/**\n * One piece of slot content: server-rendered HTML or a schema component. `for` lists the\n * components (`relationManager:<class>`, `widget:<id>`) it is scoped to: such items only\n * render in a `<Slot scope=\"…\">` of a matching component. `when` (`Panel::slot(…, when:)`)\n * limits it to renders whose slot data matches: `{ column: 'title' }` only renders in the\n * `table.header-cell` slot of the `title` column; a list value matches any of its entries.\n */\nexport type SlotItemSchema = (\n { type: 'html'; html: string } | { type: 'component'; component: SchemaComponent }\n) & { for?: string[]; when?: Record<string, SlotDataValue | SlotDataValue[]> }\n\n/** A value in a slot's render data (`<Slot data={{ column: 'title' }}>`). */\nexport type SlotDataValue = string | number | boolean | null\n\n/** A language the panel can be displayed in. */\nexport interface LocaleSchema {\n /** Laravel-style code: `en`, `pt_BR`, `zh_Hant`. */\n code: string\n /** BCP 47 tag for `Intl` and the `lang` attribute: `pt-BR`. */\n tag: string\n /** The language's own name: `Deutsch`, `العربية`. */\n label: string\n direction: 'ltr' | 'rtl'\n}\n\n/** The current request's language, formats and the panel's language list. */\nexport interface LocalizationSchema extends LocaleSchema {\n fallbackLocale: string\n /** 0 = Sunday … 6 = Saturday. */\n firstDayOfWeek: number\n /** Default ISO 4217 currency. */\n currency: string\n /** IANA timezone dates are displayed in. */\n timezone: string\n /** Whether to show a language menu. */\n switcher: boolean\n /**\n * Where panel pages offer the menu (`Panel::languageSwitcher()`): the user menu (default\n * when absent), a top bar button, both, or neither. Auth pages keep their switcher.\n */\n switcherPosition?: LanguageSwitcherPosition\n /** Languages to offer in the menu (empty when the switcher is off). */\n locales: LocaleSchema[]\n}\n\nexport type LanguageSwitcherPosition = 'user-menu' | 'topbar' | 'both' | 'hidden'\n\nexport interface PanelEndpoints {\n action: string\n modal: string\n schemaRefresh: string\n schemaOptions: string\n /** Validates one wizard step's fields (`step` = the Step component's key). */\n schemaValidate?: string\n /** Loads `deferLoading()` children and `Tab::deferBadge()` badges (`{schema}::{key}`). */\n schemaDeferred?: string\n tableCell: string\n tableReorder: string\n /** Stores a user's column order (`reorderableColumns()`). */\n tableColumns?: string\n /** `Table::deferTotals()`: the record count and summaries, loaded after the rows. */\n tableTotals?: string\n /** `Table::savedViews()`: save, share, rename, delete and pick a default view. */\n tableViews?: string\n /** `Table::spreadsheet()`: batch save (or validate) grid edits. */\n tableCells?: string\n /** Fresh schemas of lazy and polled components by token (components outside panels). */\n components?: string\n locale: string\n /** Calls `#[Expose]` methods of custom pages (`usePageMethod()`). */\n pageCall?: string\n /** Edit pages: what changed since the form was loaded (`detectConflicts()`). */\n conflicts?: string\n upload: string\n uploadDestroy: string\n /** Starts a resumable chunked upload (`{uploadChunks}/{id}` takes chunks, `/complete` finishes). */\n uploadChunks?: string\n globalSearch: string\n /** Command palette: \"create\" links and recent records (GET). */\n commandPalette?: string\n /** Stores the user's customised keyboard shortcuts (POST `{shortcuts}`). */\n shortcuts?: string\n /** `Action::undoable()`: undoes an action from its toast (POST `{undo}`). */\n undo?: string\n /** `Dashboard::customizable()`: saves or resets the user's layout (POST `{token, layout}`). */\n dashboardLayout?: string\n notifications: string\n notificationsReadAll: string\n notificationsDestroyAll: string\n logout: string\n}\n\n/** Where toasts appear (`Panel::toastPosition()`); `start`/`end` follow the reading direction. */\nexport type ToastPosition =\n 'top-start' | 'top-center' | 'top-end' | 'bottom-start' | 'bottom-center' | 'bottom-end'\n\n/** Shown while impersonating another user (`ImpersonateAction`). */\nexport interface ImpersonationSchema {\n /** \"You are signed in as …\", translated on the server. */\n label: string\n leaveLabel: string\n /** POST here to return to the real user. */\n leaveUrl: string\n}\n\n/** The `alfakit` prop shared with every page of a panel. */\n/**\n * One `errorNotifications` entry: the text to show, or `hidden` (caught, nothing shown,\n * `hiddenErrorNotification()`) or `disabled` (default handling, `disabledErrorNotification()`).\n */\nexport interface ErrorNotificationEntrySchema {\n title?: string\n body?: string | null\n icon?: IconName\n hidden?: boolean\n disabled?: boolean\n}\n\n/** `errorNotifications`, keyed by HTTP status plus `default`. */\nexport type ErrorNotificationsSchema = Record<string, ErrorNotificationEntrySchema>\n\nexport interface PanelSchema {\n id: string\n /**\n * The schema contract version the server serialised (`SchemaContract::VERSION`). Renderers\n * compare its major part with `SCHEMA_CONTRACT_VERSION`; absent on servers before 1.0.\n */\n schemaContract?: string\n path: string\n url?: string\n brandName: string\n brandLogo?: string\n darkModeBrandLogo?: string\n brandLogoHeight?: string\n favicon?: string\n /**\n * Colour custom properties without the `--alfakit-` prefix: `primary`, its readable\n * `primary-foreground` and, for palettes (`Color::Indigo`), `primary-50` … `primary-950`.\n */\n colors?: Record<string, string>\n /**\n * CSS for custom colour names (`colors(['brand' => …])`): `.alfakit-color-{name}` rules\n * that point the generic colour utilities of themes.css at `--alfakit-{name}`.\n */\n colorCss?: string\n darkMode: {\n enabled: boolean\n forced: boolean\n /** `defaultThemeMode()`; absent for `system`. */\n default?: 'light' | 'dark'\n /** `false` when `themeSwitcher(false)`: no switcher, always the default mode. */\n switcher?: false\n }\n /** `false` when `topbar(false)`: sidebar layouts render no top bar (a slim bar on phones). */\n topbar?: false\n sidebarCollapsibleOnDesktop: boolean\n sidebarFullyCollapsibleOnDesktop?: boolean\n topNavigation?: boolean\n maxContentWidth?: Width | string\n breadcrumbs: boolean\n unsavedChangesAlerts?: boolean\n /** `false` when `collapsibleNavigationGroups(false)`: groups never collapse. */\n collapsibleNavigationGroups?: false\n /** CSS lengths for the open and icon-only sidebar (`--sidebar-width`, `--sidebar-width-icon`). */\n sidebarWidth?: string\n collapsedSidebarWidth?: string\n /** Max width of `auth` layout pages (login, registration…). */\n simplePageMaxContentWidth?: Width | string\n /** `false` when `spa(false)`: every link loads a full page. */\n spa?: false\n /** URL patterns (`*` wildcard) that load with a full page request. */\n spaUrlExceptions?: string[]\n /**\n * Notifications shown instead of Inertia's error modal, keyed by HTTP status plus\n * `default`. Absent when disabled or in debug mode. A page's own `errorNotifications`\n * replaces it while that page is open.\n */\n errorNotifications?: ErrorNotificationsSchema\n theme: PanelThemeSchema\n /** Content inserted with `Panel::slot()`, keyed by slot name. */\n slots?: Record<string, SlotItemSchema[]>\n /**\n * Empty for `starterKit()` panels: their navigation is the `alfakitNavigation` page prop,\n * which every page of the host application carries (not only the panel's).\n */\n navigation: NavigationGroupSchema[]\n /**\n * The active items and badges of this request; `navigation` holds only the structure\n * (sent once). Read the navigation through `usePanel()`, which merges the two.\n */\n navigationState?: NavigationStateSchema\n /**\n * `Panel::starterKit()`: the panel runs inside a Laravel starter kit app. Its pages render\n * in the host's own layout (sidebar, header, appearance) and the host's shadcn tokens;\n * the panel's own chrome, theme attributes and colour scheme are not applied.\n */\n starterKit?: true\n user?: PanelUserSchema\n userMenu?: {\n items: MenuItemSchema[]\n profileUrl?: string\n logoutUrl: string\n /** The `profile` entry as customised in `userMenuItems()` (replaces the built-in link). */\n profile?: MenuItemSchema\n /** The customised `logout` entry, or `false` when it is hidden. */\n logout?: MenuItemSchema | false\n /**\n * `topbar` when `userMenu(position: UserMenuPosition::Topbar)`: sidebar layouts show the\n * menu in the top bar instead of the sidebar footer. Omitted for the sidebar.\n */\n position?: 'topbar'\n }\n tenancy?: {\n current?: TenantSchema | null\n tenants: TenantSchema[]\n registrationUrl?: string\n profileUrl?: string\n /** The registration page's own label (`RegisterTenant::getLabel()`); else \"New\". */\n registrationLabel?: string\n /** The profile page's own label (`EditTenantProfile::getLabel()`); else \"Settings\". */\n profileLabel?: string\n /** The customised `register` entry of `tenantMenuItems()`. */\n registration?: MenuItemSchema\n /** The customised `profile` entry of `tenantMenuItems()`. */\n profile?: MenuItemSchema\n menu?: MenuItemSchema[]\n /** `false` when `tenantSwitcher(false)`: `tenants` is empty and the menu lists no others. */\n switcher?: false\n /** `searchableTenantMenu()`, or more than 10 tenants: show a search field in the menu. */\n searchable?: boolean\n }\n databaseNotifications?: {\n pollingInterval?: string\n unreadCount: number\n /**\n * `sidebar` when `databaseNotifications(position: 'sidebar')` or `topbar(false)`: sidebar\n * layouts show the bell in the sidebar instead of the top bar. Omitted for the top bar.\n */\n position?: 'sidebar'\n }\n globalSearch?: {\n keyBindings: string[]\n placeholder: string\n /** `globalSearchDebounce()` in milliseconds; renderers default to 250. */\n debounce?: number\n /** `globalSearchFieldSuffix()`: text shown at the end of the field instead of the key hint. */\n suffix?: string\n /** `false` when `globalSearchFieldKeyBindingSuffix(false)`: no key binding hint. */\n keyBindingSuffix?: false\n /**\n * `sidebar` when `globalSearch(position: GlobalSearchPosition::Sidebar)` or\n * `topbar(false)`: sidebar layouts show the field in the sidebar header. Omitted for the top bar.\n */\n position?: 'sidebar'\n }\n endpoints: Partial<PanelEndpoints> & { logout: string }\n /** `alfakit.uploads.chunk_size` in bytes: larger files are uploaded in chunks. */\n uploadChunkSize?: number\n auth: { login?: string; register?: string; passwordReset?: string }\n flashKey: string\n /** Present while impersonating: render a banner with a way back. */\n impersonation?: ImpersonationSchema\n /**\n * `Panel::realtimeNotifications()`: the private channel to listen on with Laravel Echo;\n * with `Panel::realtime()` also the connection (`live`, `authEndpoint`, `echo`).\n */\n realtime?: RealtimeSchema\n /** Omitted for the default (`top-end`). */\n toastPosition?: ToastPosition\n /** The command palette's extras: page actions, panel commands, navigation, create links, recent records. */\n commandPalette?: CommandPaletteSchema\n /** Keyboard shortcuts (`false` with `keyboardShortcuts(false)`); absent for guests. */\n keyboardShortcuts?: KeyboardShortcutsSchema | false\n /** `Panel::pwa()`: service worker, install prompt and Web Push; absent when off. */\n pwa?: PwaSchema\n localization: LocalizationSchema\n csrf: string\n translations: Record<string, unknown>\n}\n\nexport interface BreadcrumbSchema {\n label: string\n url?: string\n}\n\nexport type ContentBlock =\n | FormBlock\n | InfolistBlock\n | SchemaBlock\n | TableBlock\n | RelationManagersBlock\n | WidgetsBlock\n | LinksBlock\n | { type: string; key: string; [key: string]: unknown }\n\nexport type PageLayout = 'app' | 'auth' | 'blank' | 'fullscreen'\n\n/** The `page` prop rendered by `Alfakit/Page`. */\nexport interface PageSchema {\n class: string\n layout: PageLayout\n title: string\n heading?: string\n subheading?: string\n breadcrumbs?: BreadcrumbSchema[]\n maxContentWidth?: Width | string\n /** `fullscreen` layout: whether to show the slim header, and where its exit link goes. */\n fullscreen?: { header: boolean; exitUrl?: string }\n /** Cluster or record sub-navigation. */\n subNavigation?: SubNavigationSchema\n headerActions?: AnyActionSchema[]\n /** `$headerActionsAlignment`; absent for `end` (the default). */\n headerActionsAlignment?: 'start' | 'center' | 'end' | 'between' | 'justify'\n /** `$extraBodyAttributes` / `getExtraBodyAttributes()`: set on `<body>` while the page is open. */\n extraBodyAttributes?: Record<string, string>\n content: ContentBlock[]\n /** Encrypted context token for internal endpoints. */\n token: string\n record?: RecordKey\n view?: { component: string; props: Record<string, unknown> }\n /**\n * An action whose modal the renderer opens once the page hydrated (`$defaultAction`,\n * `?defaultAction=`, `?tableAction=&tableActionRecord=`).\n */\n defaultAction?: DefaultActionSchema\n /**\n * Page override of the panel's `errorNotifications` (`$hasErrorNotifications`,\n * `setUpErrorNotifications()`): `false` turns them off on this page. Absent when the\n * page follows the panel.\n */\n errorNotifications?: ErrorNotificationsSchema | false\n /** Record pages with `Panel::realtime()`: who else is on the record. */\n presence?: PresenceSchema\n /** Edit pages with `detectConflicts()`: the version the form was loaded from. */\n conflicts?: ConflictsSchema\n}\n\n/** `PageSchema::defaultAction`: what to open and in which context. */\nexport interface DefaultActionSchema {\n action: ActionSchema\n /** Context component: `page`, `table`, `relationManager:…`. */\n component: string\n record?: RecordKey\n arguments?: Record<string, unknown>\n}\n\n/**\n * Fresh schemas returned by a poll, keyed by poll target: `table`, `relationManager:Class`\n * or `widget:<id or class>`.\n */\nexport type PolledComponents = Record<string, ContentBlock | WidgetSchema | RelationManagerSchema>\n\n/** Shape of `usePage().props` on an Alfakit page. */\nexport interface AlfakitPageProps {\n alfakit: PanelSchema\n page?: PageSchema\n /** Components refreshed by a poll (only present after one); merged into `page`. */\n alfakitPoll?: PolledComponents\n /**\n * Lazy widgets and relation managers (`Page::LAZY_PROP`), an Inertia deferred prop that\n * arrives right after the first response; `resolveLazyComponents()` puts them in `page`.\n */\n alfakitLazy?: PolledComponents\n /** Badges of `Tab::deferBadge()` list tabs, a deferred prop loaded after the page. */\n alfakitTabBadges?: DeferredTabBadges\n /**\n * `Panel::deferNavigationBadges()`: the navigation badges keyed by item id\n * (`navigationState`), a deferred prop loaded after the page.\n */\n alfakitNavigationBadges?: Record<string, NavigationBadgeSchema>\n alfakitAppearance?: 'light' | 'dark' | 'system'\n /** The stored sidebar state (`alfakit_sidebar_state` cookie); null when never toggled. */\n alfakitSidebarOpen?: boolean | null\n /**\n * The navigation of the application's `starterKit()` panels, shared with every Inertia\n * page (the host's own pages too) for the host sidebar. Null for guests.\n */\n alfakitNavigation?: StarterKitNavigationSchema[] | null\n /**\n * `starterKit()` panels: their dashboard widgets on the host's dashboard\n * (`starterKitDashboard()`), for `<AlfakitWidgets />`. Null on other pages, for guests\n * and users who may not open the panel.\n */\n alfakitWidgets?: StarterKitDashboardWidgetsSchema[] | null\n /**\n * The developer debug panel's data (`Page::DEBUG_PROP`): schema tree with visibility and\n * authorisation, evaluated closures and server timings. Only present while the panel's\n * debug panel is active; never in production.\n */\n alfakitDebug?: DebugSchema\n /**\n * The schema inspector's data (context identity, class links). Only present while the\n * panel's dev tools are on (`Panel::devTools()` with `app.debug`); never in production.\n */\n alfakitDevTools?: DevToolsSchema\n flash?: {\n alfakitNotifications?: NotificationSchema[]\n alfakitAction?: {\n name: string\n status: 'success' | 'halted' | 'cancelled'\n closeModal?: boolean\n deselectRecords?: boolean | null\n }\n [key: string]: unknown\n }\n errors?: Record<string, string>\n [key: string]: unknown\n}\n\nexport type { ActionSchema }\n"],"mappings":";;AAyJA,MAAa,sBAAsB;AACnC,MAAa,wBAAwB"}
|
|
1
|
+
{"version":3,"file":"panel.js","names":[],"sources":["../../src/schema/panel.ts"],"sourcesContent":["import type { ActionSchema, AnyActionSchema } from './actions'\nimport type { CommandPaletteSchema, KeyboardShortcutsSchema } from './command-palette'\nimport type { Color, IconName, RecordKey, Width } from './common'\nimport type { SchemaComponent } from './components'\nimport type { DebugSchema } from './debug'\nimport type { DevToolsSchema } from './dev-tools'\nimport type { FormBlock, InfolistBlock, LinksBlock, SchemaBlock } from './form'\nimport type { NotificationSchema } from './notifications'\nimport type { PwaSchema } from './pwa'\nimport type { ConflictsSchema, PresenceSchema, RealtimeSchema } from './realtime'\nimport type { DeferredTabBadges } from './resources'\nimport type { StandaloneComponentSchema } from './standalone'\nimport type { RelationManagerSchema, RelationManagersBlock, TableBlock } from './table'\nimport type { WidgetSchema, WidgetsBlock } from './widgets'\n\nexport interface NavigationItemSchema {\n /** Text, or sanitised HTML when `labelHtml` is true (an `Htmlable` label). */\n label: string\n /** `label` is sanitised HTML: render it as markup (use `htmlLabelToText()` for text). */\n labelHtml?: boolean\n icon?: IconName\n /**\n * Split navigation (`alfakit.navigation` is an Inertia once prop): the icon of the item\n * while active, swapped in by `resolvePanelNavigation()`.\n */\n activeIcon?: IconName\n url?: string\n openInNewTab?: boolean\n active?: boolean\n badge?: string | number\n badgeColor?: Color\n badgeTooltip?: string\n /** `Panel::deferNavigationBadges()`: the item has a badge that loads after the page. */\n deferredBadge?: boolean\n /** Set by `resolvePanelNavigation()` while a deferred badge is loading (show a placeholder). */\n badgePending?: boolean\n /** `NavigationItem::extraAttributes()`: HTML attributes for the item's link (`class` merges). */\n extraAttributes?: Record<string, string | number | boolean | null>\n children?: NavigationItemSchema[]\n}\n\nexport interface NavigationGroupSchema {\n label?: string | null\n icon?: IconName\n collapsible: boolean\n collapsed?: boolean\n items: NavigationItemSchema[]\n /** `NavigationGroup::extraSidebarAttributes()`: HTML attributes for the group in the sidebar. */\n extraSidebarAttributes?: Record<string, string | number | boolean | null>\n /** `NavigationGroup::extraTopbarAttributes()`: HTML attributes for the group in the top navigation. */\n extraTopbarAttributes?: Record<string, string | number | boolean | null>\n}\n\n/** A navigation item's badge, from `navigationState` or the deferred `alfakitNavigationBadges` prop. */\nexport interface NavigationBadgeSchema {\n badge: string | number\n badgeColor?: Color\n badgeTooltip?: string\n}\n\n/**\n * The per-request part of the panel navigation, whose structure (`navigation`) is an\n * Inertia once prop. Items are addressed by position: `group.item[.child…]` (`0.2.1`).\n */\nexport interface NavigationStateSchema {\n /** The active items (an active child's ancestors included). */\n active?: string[]\n /** Badges, unless `Panel::deferNavigationBadges()` defers them. */\n badges?: Record<string, NavigationBadgeSchema>\n}\n\n/** One `starterKit()` panel's navigation, as shared with the host application's pages. */\n/** One `starterKit()` panel's dashboard widgets on a host page (`alfakitWidgets` prop). */\nexport interface StarterKitDashboardWidgetsSchema {\n /** The panel id. */\n id: string\n /** A standalone widgets payload bound to the panel (lazy loading and polling included). */\n component: StandaloneComponentSchema\n}\n\nexport interface StarterKitNavigationSchema {\n /** The panel id. */\n id: string\n /** The panel's home URL. */\n url: string\n brandName: string\n /** Empty for tenancy panels outside a tenant (their items link to tenant routes). */\n groups: NavigationGroupSchema[]\n /** What the drop-in header components render before they load. */\n widgets?: StarterKitWidgetsSchema\n}\n\n/**\n * The `widgets` of a `starterKit()` panel's shared navigation: what `<AlfakitSearch />`,\n * `<AlfakitNotifications />`, `<AlfakitTenantSwitcher />` and `<AlfakitLanguageSwitcher />`\n * show on any host page before their code and the panel data (`endpoint`) have loaded.\n */\nexport interface StarterKitWidgetsSchema {\n /** GET: the panel data the components render with (the `alfakit` prop of the panel's pages). */\n endpoint: string\n /** Global search or the command palette (tenancy panels: only inside a tenant). */\n search?: { keyBindings: string[]; placeholder: string }\n /** `databaseNotifications()`: the bell's label and the user's unread count. */\n notifications?: { label: string; unreadCount: number }\n /** Tenancy panels: the user's tenants; `current` is null on the host's own pages. */\n tenancy?: {\n current?: TenantSchema | null\n tenants: TenantSchema[]\n registrationUrl?: string\n /** The registration page's own label (`RegisterTenant::getLabel()`). */\n registrationLabel?: string\n searchable?: true\n }\n /**\n * `<AlfakitLanguageSwitcher />`: the button's label (\"Change language\") and the current\n * language, when the panel has a language switcher with two or more languages\n * (`locales()`). The languages and the locale endpoint come with the panel data.\n */\n language?: { label: string; current: LocaleSchema }\n /** While impersonating: the banner the header components show on the host's own pages. */\n impersonation?: ImpersonationSchema\n /**\n * `AiPlugin::copilot()` (alfasix/alfakit-ai, `$panel->starterKitWidget('copilot')`): the\n * label and shortcuts of `<AlfakitCopilot />` before its code loads. Tenancy panels: only\n * inside a tenant.\n */\n copilot?: { label: string; keyBindings: string[] }\n}\n\n/** A page's secondary navigation: cluster pages or a record's pages. */\nexport interface SubNavigationSchema {\n position: 'start' | 'end' | 'top'\n groups: NavigationGroupSchema[]\n}\n\n/**\n * An entry of the user or tenant menu: a link (`url`, `method: 'post'` for POST links) or\n * an `Action` with server logic or a modal (`action`), run through the action endpoint\n * with the component `userMenu` / `tenantMenu` and the page's token.\n */\nexport interface MenuItemSchema {\n label?: string\n icon?: IconName\n url?: string\n openInNewTab?: boolean\n color?: Color\n method?: 'post'\n action?: ActionSchema\n /** Group index (nested arrays in `userMenuItems()`); a separator divides groups. */\n group?: number\n}\n\n/** Action endpoint components of the menus (`Panel::USER_MENU_COMPONENT`, …). */\nexport const USER_MENU_COMPONENT = 'userMenu'\nexport const TENANT_MENU_COMPONENT = 'tenantMenu'\n\nexport interface PanelUserSchema {\n id: RecordKey\n name: string\n email?: string | null\n avatar?: string | null\n}\n\nexport interface TenantSchema {\n id: RecordKey\n /** Text, or sanitised HTML when `nameHtml` is true (an `Htmlable` `getAlfakitName()`). */\n name: string\n /** `name` is sanitised HTML: render it as markup (use `htmlLabelToText()` for text). */\n nameHtml?: boolean\n avatar?: string | null\n url: string\n /** `HasCurrentTenantLabel`: a caption shown with the current tenant (\"Active team\"). */\n label?: string | null\n}\n\n/** The panel's design, see `Panel::theme()`. */\nexport interface PanelThemeSchema {\n name: string\n /** Value of `data-alfakit-theme`, base theme first: `\"alfakit acme\"`. */\n attribute: string\n sidebar: 'inset' | 'floating' | 'flush'\n density: 'comfortable' | 'compact'\n radius?: string | null\n font?: string | null\n /** CSS value for `--alfakit-font`. */\n fontCss?: string | null\n /** CSS value for `--alfakit-heading-font`. */\n headingFontCss?: string | null\n /** Stylesheet that loads the font, if any. */\n fontUrl?: string | null\n /** Every font stylesheet to load (body and heading fonts). */\n fontUrls?: string[]\n /** Structure and component styles; mirrored as `data-*` attributes on `<html>`. */\n design: PanelDesignSchema\n /**\n * Icon aliases replaced with `Icons::register()` (`alfakit::panels.sidebar.collapse-button`\n * → a Lucide name, image URL or sanitised SVG). The chrome falls back to its own icon.\n */\n icons?: Record<string, string> | null\n}\n\nexport interface PanelDesignSchema {\n layout: 'sidebar' | 'topbar' | 'rail'\n nav: 'soft' | 'pill' | 'bar' | 'underline'\n header: 'plain' | 'banner' | 'band' | 'compact'\n surfaces: 'bordered' | 'elevated' | 'flat' | 'ring'\n inputs: 'outline' | 'filled' | 'underline'\n buttons: 'rounded' | 'pill' | 'square'\n tables: 'lined' | 'striped' | 'minimal'\n /**\n * Below the `md` breakpoint: `drawer` (default) or the `bottom` mobile preset (bottom\n * navigation bar, tables as stacked cards, slide-over modals, 44px touch targets and\n * sticky form actions). See `Panel::mobile()`.\n */\n mobile?: 'drawer' | 'bottom'\n}\n\n/**\n * One piece of slot content: server-rendered HTML or a schema component. `for` lists the\n * components (`relationManager:<class>`, `widget:<id>`) it is scoped to: such items only\n * render in a `<Slot scope=\"…\">` of a matching component. `when` (`Panel::slot(…, when:)`)\n * limits it to renders whose slot data matches: `{ column: 'title' }` only renders in the\n * `table.header-cell` slot of the `title` column; a list value matches any of its entries.\n */\nexport type SlotItemSchema = (\n { type: 'html'; html: string } | { type: 'component'; component: SchemaComponent }\n) & { for?: string[]; when?: Record<string, SlotDataValue | SlotDataValue[]> }\n\n/** A value in a slot's render data (`<Slot data={{ column: 'title' }}>`). */\nexport type SlotDataValue = string | number | boolean | null\n\n/** A language the panel can be displayed in. */\nexport interface LocaleSchema {\n /** Laravel-style code: `en`, `pt_BR`, `zh_Hant`. */\n code: string\n /** BCP 47 tag for `Intl` and the `lang` attribute: `pt-BR`. */\n tag: string\n /** The language's own name: `Deutsch`, `العربية`. */\n label: string\n direction: 'ltr' | 'rtl'\n}\n\n/** The current request's language, formats and the panel's language list. */\nexport interface LocalizationSchema extends LocaleSchema {\n fallbackLocale: string\n /** 0 = Sunday … 6 = Saturday. */\n firstDayOfWeek: number\n /** Default ISO 4217 currency. */\n currency: string\n /** IANA timezone dates are displayed in. */\n timezone: string\n /** Whether to show a language menu. */\n switcher: boolean\n /**\n * Where panel pages offer the menu (`Panel::languageSwitcher()`): the user menu (default\n * when absent), a top bar button, both, or neither. Auth pages keep their switcher.\n */\n switcherPosition?: LanguageSwitcherPosition\n /** Languages to offer in the menu (empty when the switcher is off). */\n locales: LocaleSchema[]\n}\n\nexport type LanguageSwitcherPosition = 'user-menu' | 'topbar' | 'both' | 'hidden'\n\nexport interface PanelEndpoints {\n action: string\n modal: string\n schemaRefresh: string\n schemaOptions: string\n /** Validates one wizard step's fields (`step` = the Step component's key). */\n schemaValidate?: string\n /** Loads `deferLoading()` children and `Tab::deferBadge()` badges (`{schema}::{key}`). */\n schemaDeferred?: string\n tableCell: string\n tableReorder: string\n /** Stores a user's column order (`reorderableColumns()`). */\n tableColumns?: string\n /** `Table::deferTotals()`: the record count and summaries, loaded after the rows. */\n tableTotals?: string\n /** `Table::savedViews()`: save, share, rename, delete and pick a default view. */\n tableViews?: string\n /** `Table::spreadsheet()`: batch save (or validate) grid edits. */\n tableCells?: string\n /** Fresh schemas of lazy and polled components by token (components outside panels). */\n components?: string\n locale: string\n /** Calls `#[Expose]` methods of custom pages (`usePageMethod()`). */\n pageCall?: string\n /** Edit pages: what changed since the form was loaded (`detectConflicts()`). */\n conflicts?: string\n upload: string\n uploadDestroy: string\n /** Starts a resumable chunked upload (`{uploadChunks}/{id}` takes chunks, `/complete` finishes). */\n uploadChunks?: string\n globalSearch: string\n /** Command palette: \"create\" links and recent records (GET). */\n commandPalette?: string\n /** Stores the user's customised keyboard shortcuts (POST `{shortcuts}`). */\n shortcuts?: string\n /** `Action::undoable()`: undoes an action from its toast (POST `{undo}`). */\n undo?: string\n /** `Dashboard::customizable()`: saves or resets the user's layout (POST `{token, layout}`). */\n dashboardLayout?: string\n notifications: string\n notificationsReadAll: string\n notificationsDestroyAll: string\n logout: string\n}\n\n/** Where toasts appear (`Panel::toastPosition()`); `start`/`end` follow the reading direction. */\nexport type ToastPosition =\n 'top-start' | 'top-center' | 'top-end' | 'bottom-start' | 'bottom-center' | 'bottom-end'\n\n/** Shown while impersonating another user (`ImpersonateAction`). */\nexport interface ImpersonationSchema {\n /** \"You are signed in as …\", translated on the server. */\n label: string\n leaveLabel: string\n /** POST here to return to the real user. */\n leaveUrl: string\n}\n\n/** The `alfakit` prop shared with every page of a panel. */\n/**\n * One `errorNotifications` entry: the text to show, or `hidden` (caught, nothing shown,\n * `hiddenErrorNotification()`) or `disabled` (default handling, `disabledErrorNotification()`).\n */\nexport interface ErrorNotificationEntrySchema {\n title?: string\n body?: string | null\n icon?: IconName\n hidden?: boolean\n disabled?: boolean\n}\n\n/** `errorNotifications`, keyed by HTTP status plus `default`. */\nexport type ErrorNotificationsSchema = Record<string, ErrorNotificationEntrySchema>\n\nexport interface PanelSchema {\n id: string\n /**\n * The schema contract version the server serialised (`SchemaContract::VERSION`). Renderers\n * compare its major part with `SCHEMA_CONTRACT_VERSION`; absent on servers before 1.0.\n */\n schemaContract?: string\n path: string\n url?: string\n /** Where \"G then D\" goes when it isn't `url`: the app's `/dashboard` for a starter kit panel without a dashboard page. */\n dashboardUrl?: string\n brandName: string\n brandLogo?: string\n darkModeBrandLogo?: string\n brandLogoHeight?: string\n favicon?: string\n /**\n * Colour custom properties without the `--alfakit-` prefix: `primary`, its readable\n * `primary-foreground` and, for palettes (`Color::Indigo`), `primary-50` … `primary-950`.\n */\n colors?: Record<string, string>\n /**\n * CSS for custom colour names (`colors(['brand' => …])`): `.alfakit-color-{name}` rules\n * that point the generic colour utilities of themes.css at `--alfakit-{name}`.\n */\n colorCss?: string\n darkMode: {\n enabled: boolean\n forced: boolean\n /** `defaultThemeMode()`; absent for `system`. */\n default?: 'light' | 'dark'\n /** `false` when `themeSwitcher(false)`: no switcher, always the default mode. */\n switcher?: false\n }\n /** `false` when `topbar(false)`: sidebar layouts render no top bar (a slim bar on phones). */\n topbar?: false\n sidebarCollapsibleOnDesktop: boolean\n sidebarFullyCollapsibleOnDesktop?: boolean\n topNavigation?: boolean\n maxContentWidth?: Width | string\n breadcrumbs: boolean\n unsavedChangesAlerts?: boolean\n /** `false` when `collapsibleNavigationGroups(false)`: groups never collapse. */\n collapsibleNavigationGroups?: false\n /** CSS lengths for the open and icon-only sidebar (`--sidebar-width`, `--sidebar-width-icon`). */\n sidebarWidth?: string\n collapsedSidebarWidth?: string\n /** Max width of `auth` layout pages (login, registration…). */\n simplePageMaxContentWidth?: Width | string\n /** `false` when `spa(false)`: every link loads a full page. */\n spa?: false\n /** URL patterns (`*` wildcard) that load with a full page request. */\n spaUrlExceptions?: string[]\n /**\n * Notifications shown instead of Inertia's error modal, keyed by HTTP status plus\n * `default`. Absent when disabled or in debug mode. A page's own `errorNotifications`\n * replaces it while that page is open.\n */\n errorNotifications?: ErrorNotificationsSchema\n theme: PanelThemeSchema\n /** Content inserted with `Panel::slot()`, keyed by slot name. */\n slots?: Record<string, SlotItemSchema[]>\n /**\n * Empty for `starterKit()` panels: their navigation is the `alfakitNavigation` page prop,\n * which every page of the host application carries (not only the panel's).\n */\n navigation: NavigationGroupSchema[]\n /**\n * The active items and badges of this request; `navigation` holds only the structure\n * (sent once). Read the navigation through `usePanel()`, which merges the two.\n */\n navigationState?: NavigationStateSchema\n /**\n * `Panel::starterKit()`: the panel runs inside a Laravel starter kit app. Its pages render\n * in the host's own layout (sidebar, header, appearance) and the host's shadcn tokens;\n * the panel's own chrome, theme attributes and colour scheme are not applied.\n */\n starterKit?: true\n user?: PanelUserSchema\n userMenu?: {\n items: MenuItemSchema[]\n profileUrl?: string\n logoutUrl: string\n /** The `profile` entry as customised in `userMenuItems()` (replaces the built-in link). */\n profile?: MenuItemSchema\n /** The customised `logout` entry, or `false` when it is hidden. */\n logout?: MenuItemSchema | false\n /**\n * `topbar` when `userMenu(position: UserMenuPosition::Topbar)`: sidebar layouts show the\n * menu in the top bar instead of the sidebar footer. Omitted for the sidebar.\n */\n position?: 'topbar'\n }\n tenancy?: {\n current?: TenantSchema | null\n tenants: TenantSchema[]\n registrationUrl?: string\n profileUrl?: string\n /** The registration page's own label (`RegisterTenant::getLabel()`); else \"New\". */\n registrationLabel?: string\n /** The profile page's own label (`EditTenantProfile::getLabel()`); else \"Settings\". */\n profileLabel?: string\n /** The customised `register` entry of `tenantMenuItems()`. */\n registration?: MenuItemSchema\n /** The customised `profile` entry of `tenantMenuItems()`. */\n profile?: MenuItemSchema\n menu?: MenuItemSchema[]\n /** `false` when `tenantSwitcher(false)`: `tenants` is empty and the menu lists no others. */\n switcher?: false\n /** `searchableTenantMenu()`, or more than 10 tenants: show a search field in the menu. */\n searchable?: boolean\n }\n databaseNotifications?: {\n pollingInterval?: string\n unreadCount: number\n /**\n * `sidebar` when `databaseNotifications(position: 'sidebar')` or `topbar(false)`: sidebar\n * layouts show the bell in the sidebar instead of the top bar. Omitted for the top bar.\n */\n position?: 'sidebar'\n }\n globalSearch?: {\n keyBindings: string[]\n placeholder: string\n /** `globalSearchDebounce()` in milliseconds; renderers default to 250. */\n debounce?: number\n /** `globalSearchFieldSuffix()`: text shown at the end of the field instead of the key hint. */\n suffix?: string\n /** `false` when `globalSearchFieldKeyBindingSuffix(false)`: no key binding hint. */\n keyBindingSuffix?: false\n /**\n * `sidebar` when `globalSearch(position: GlobalSearchPosition::Sidebar)` or\n * `topbar(false)`: sidebar layouts show the field in the sidebar header. Omitted for the top bar.\n */\n position?: 'sidebar'\n }\n endpoints: Partial<PanelEndpoints> & { logout: string }\n /** `alfakit.uploads.chunk_size` in bytes: larger files are uploaded in chunks. */\n uploadChunkSize?: number\n auth: { login?: string; register?: string; passwordReset?: string }\n flashKey: string\n /** Present while impersonating: render a banner with a way back. */\n impersonation?: ImpersonationSchema\n /**\n * `Panel::realtimeNotifications()`: the private channel to listen on with Laravel Echo;\n * with `Panel::realtime()` also the connection (`live`, `authEndpoint`, `echo`).\n */\n realtime?: RealtimeSchema\n /** Omitted for the default (`top-end`). */\n toastPosition?: ToastPosition\n /** The command palette's extras: page actions, panel commands, navigation, create links, recent records. */\n commandPalette?: CommandPaletteSchema\n /** Keyboard shortcuts (`false` with `keyboardShortcuts(false)`); absent for guests. */\n keyboardShortcuts?: KeyboardShortcutsSchema | false\n /** `Panel::pwa()`: service worker, install prompt and Web Push; absent when off. */\n pwa?: PwaSchema\n localization: LocalizationSchema\n csrf: string\n translations: Record<string, unknown>\n}\n\nexport interface BreadcrumbSchema {\n label: string\n url?: string\n}\n\nexport type ContentBlock =\n | FormBlock\n | InfolistBlock\n | SchemaBlock\n | TableBlock\n | RelationManagersBlock\n | WidgetsBlock\n | LinksBlock\n | { type: string; key: string; [key: string]: unknown }\n\nexport type PageLayout = 'app' | 'auth' | 'blank' | 'fullscreen'\n\n/** The `page` prop rendered by `Alfakit/Page`. */\nexport interface PageSchema {\n class: string\n layout: PageLayout\n title: string\n heading?: string\n subheading?: string\n breadcrumbs?: BreadcrumbSchema[]\n maxContentWidth?: Width | string\n /** `fullscreen` layout: whether to show the slim header, and where its exit link goes. */\n fullscreen?: { header: boolean; exitUrl?: string }\n /** Cluster or record sub-navigation. */\n subNavigation?: SubNavigationSchema\n headerActions?: AnyActionSchema[]\n /** `$headerActionsAlignment`; absent for `end` (the default). */\n headerActionsAlignment?: 'start' | 'center' | 'end' | 'between' | 'justify'\n /** `$extraBodyAttributes` / `getExtraBodyAttributes()`: set on `<body>` while the page is open. */\n extraBodyAttributes?: Record<string, string>\n content: ContentBlock[]\n /** Encrypted context token for internal endpoints. */\n token: string\n record?: RecordKey\n view?: { component: string; props: Record<string, unknown> }\n /**\n * An action whose modal the renderer opens once the page hydrated (`$defaultAction`,\n * `?defaultAction=`, `?tableAction=&tableActionRecord=`).\n */\n defaultAction?: DefaultActionSchema\n /**\n * Page override of the panel's `errorNotifications` (`$hasErrorNotifications`,\n * `setUpErrorNotifications()`): `false` turns them off on this page. Absent when the\n * page follows the panel.\n */\n errorNotifications?: ErrorNotificationsSchema | false\n /** Record pages with `Panel::realtime()`: who else is on the record. */\n presence?: PresenceSchema\n /** Edit pages with `detectConflicts()`: the version the form was loaded from. */\n conflicts?: ConflictsSchema\n}\n\n/** `PageSchema::defaultAction`: what to open and in which context. */\nexport interface DefaultActionSchema {\n action: ActionSchema\n /** Context component: `page`, `table`, `relationManager:…`. */\n component: string\n record?: RecordKey\n arguments?: Record<string, unknown>\n}\n\n/**\n * Fresh schemas returned by a poll, keyed by poll target: `table`, `relationManager:Class`\n * or `widget:<id or class>`.\n */\nexport type PolledComponents = Record<string, ContentBlock | WidgetSchema | RelationManagerSchema>\n\n/** Shape of `usePage().props` on an Alfakit page. */\nexport interface AlfakitPageProps {\n alfakit: PanelSchema\n page?: PageSchema\n /** Components refreshed by a poll (only present after one); merged into `page`. */\n alfakitPoll?: PolledComponents\n /**\n * Lazy widgets and relation managers (`Page::LAZY_PROP`), an Inertia deferred prop that\n * arrives right after the first response; `resolveLazyComponents()` puts them in `page`.\n */\n alfakitLazy?: PolledComponents\n /** Badges of `Tab::deferBadge()` list tabs, a deferred prop loaded after the page. */\n alfakitTabBadges?: DeferredTabBadges\n /**\n * `Panel::deferNavigationBadges()`: the navigation badges keyed by item id\n * (`navigationState`), a deferred prop loaded after the page.\n */\n alfakitNavigationBadges?: Record<string, NavigationBadgeSchema>\n alfakitAppearance?: 'light' | 'dark' | 'system'\n /** The stored sidebar state (`alfakit_sidebar_state` cookie); null when never toggled. */\n alfakitSidebarOpen?: boolean | null\n /**\n * The navigation of the application's `starterKit()` panels, shared with every Inertia\n * page (the host's own pages too) for the host sidebar. Null for guests.\n */\n alfakitNavigation?: StarterKitNavigationSchema[] | null\n /**\n * `starterKit()` panels: their dashboard widgets on the host's dashboard\n * (`starterKitDashboard()`), for `<AlfakitWidgets />`. Null on other pages, for guests\n * and users who may not open the panel.\n */\n alfakitWidgets?: StarterKitDashboardWidgetsSchema[] | null\n /**\n * The developer debug panel's data (`Page::DEBUG_PROP`): schema tree with visibility and\n * authorisation, evaluated closures and server timings. Only present while the panel's\n * debug panel is active; never in production.\n */\n alfakitDebug?: DebugSchema\n /**\n * The schema inspector's data (context identity, class links). Only present while the\n * panel's dev tools are on (`Panel::devTools()` with `app.debug`); never in production.\n */\n alfakitDevTools?: DevToolsSchema\n flash?: {\n alfakitNotifications?: NotificationSchema[]\n alfakitAction?: {\n name: string\n status: 'success' | 'halted' | 'cancelled'\n closeModal?: boolean\n deselectRecords?: boolean | null\n }\n [key: string]: unknown\n }\n errors?: Record<string, string>\n [key: string]: unknown\n}\n\nexport type { ActionSchema }\n"],"mappings":";;AAyJA,MAAa,sBAAsB;AACnC,MAAa,wBAAwB"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alfakit/core",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.9",
|
|
4
4
|
"description": "Framework-agnostic schema types and state management for Alfakit, the Laravel admin panel builder by Alfa Six.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Alfa Six (https://alfasixbh.com)",
|