@alfakit/core 0.1.2 → 0.1.7
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 +12 -0
- package/dist/actions.d.ts.map +1 -1
- package/dist/actions.js +6 -0
- package/dist/actions.js.map +1 -1
- package/dist/advanced-fields.js +2 -2
- package/dist/advanced-fields.js.map +1 -1
- package/dist/assets.d.ts.map +1 -1
- package/dist/assets.js +2 -1
- package/dist/assets.js.map +1 -1
- package/dist/csp.d.ts +11 -0
- package/dist/csp.d.ts.map +1 -0
- package/dist/csp.js +16 -0
- package/dist/csp.js.map +1 -0
- package/dist/form.d.ts +20 -0
- package/dist/form.d.ts.map +1 -1
- package/dist/form.js +49 -4
- package/dist/form.js.map +1 -1
- package/dist/icons.d.ts +6 -0
- package/dist/icons.d.ts.map +1 -1
- package/dist/icons.js +9 -1
- package/dist/icons.js.map +1 -1
- package/dist/image-editor.d.ts +2 -2
- package/dist/image-editor.js +2 -2
- package/dist/image-files.d.ts +8 -0
- package/dist/image-files.d.ts.map +1 -1
- package/dist/image-files.js +11 -1
- package/dist/image-files.js.map +1 -1
- package/dist/index.d.ts +10 -9
- package/dist/index.js +8 -7
- package/dist/kanban.d.ts.map +1 -1
- package/dist/kanban.js +1 -1
- package/dist/kanban.js.map +1 -1
- package/dist/page-actions.d.ts +2 -2
- package/dist/page-actions.d.ts.map +1 -1
- package/dist/page-actions.js +4 -4
- package/dist/page-actions.js.map +1 -1
- package/dist/schema/advanced-fields.d.ts +5 -0
- package/dist/schema/advanced-fields.d.ts.map +1 -1
- package/dist/schema/cms.d.ts +2 -0
- package/dist/schema/cms.d.ts.map +1 -1
- package/dist/schema/index.d.ts +3 -3
- package/dist/schema/panel.d.ts +25 -0
- package/dist/schema/panel.d.ts.map +1 -1
- package/dist/schema/panel.js.map +1 -1
- package/dist/standalone.d.ts +10 -3
- package/dist/standalone.d.ts.map +1 -1
- package/dist/standalone.js +12 -2
- package/dist/standalone.js.map +1 -1
- package/dist/starter-kit-widgets.d.ts +40 -7
- package/dist/starter-kit-widgets.d.ts.map +1 -1
- package/dist/starter-kit-widgets.js +71 -7
- package/dist/starter-kit-widgets.js.map +1 -1
- package/dist/starter-kit.d.ts +24 -1
- package/dist/starter-kit.d.ts.map +1 -1
- package/dist/starter-kit.js +33 -3
- package/dist/starter-kit.js.map +1 -1
- package/dist/utils/debounce.d.ts +2 -0
- package/dist/utils/debounce.d.ts.map +1 -1
- package/dist/utils/debounce.js +1 -0
- package/dist/utils/debounce.js.map +1 -1
- package/dist/utils/path.d.ts +11 -1
- package/dist/utils/path.d.ts.map +1 -1
- package/dist/utils/path.js +30 -3
- package/dist/utils/path.js.map +1 -1
- package/package.json +1 -1
- package/themes.css +6 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @alfakit/core
|
|
2
2
|
|
|
3
|
+
## 0.1.7
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Released with AlfaKit 0.1.7 to keep the npm and PHP versions the same (no frontend changes).
|
|
8
|
+
|
|
9
|
+
## 0.1.6
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- Released with AlfaKit 0.1.6 (the npm and PHP versions now match). Fixes from the full verification in React, Vue and Svelte: starter-kit copilot, dashboard widgets and discovered components, per-item form errors, stable template schemas in new blocks, CSP nonces, WCAG AA badge contrast, plural kanban counts and more (see the AlfaKit changelog for 0.1.6).
|
|
14
|
+
|
|
3
15
|
## 0.1.2
|
|
4
16
|
|
|
5
17
|
### 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;;;;;EAwCF,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
|
@@ -215,11 +215,17 @@ var ActionRunner = class {
|
|
|
215
215
|
}
|
|
216
216
|
}
|
|
217
217
|
fetchModal(action, context) {
|
|
218
|
+
const records = context.records;
|
|
219
|
+
const isBulk = records !== void 0;
|
|
218
220
|
const params = {
|
|
219
221
|
token: context.token,
|
|
220
222
|
component: context.component,
|
|
221
223
|
action: actionPath(action, context),
|
|
222
224
|
record: context.record ?? void 0,
|
|
225
|
+
records: records !== void 0 && records.length > 0 ? records : void 0,
|
|
226
|
+
allRecords: context.allRecords ? 1 : void 0,
|
|
227
|
+
bulk: isBulk ? 1 : void 0,
|
|
228
|
+
tableQuery: isBulk ? currentPageQuery() : void 0,
|
|
223
229
|
arguments: context.arguments
|
|
224
230
|
};
|
|
225
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 const params = {\n token: context.token,\n component: context.component,\n action: actionPath(action, context),\n record: context.record ?? 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 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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"}
|
package/dist/advanced-fields.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { tableQueryKey } from "./table.js";
|
|
2
2
|
import { cloneState } from "./utils/clone.js";
|
|
3
|
-
import {
|
|
3
|
+
import { templateForIndex } from "./utils/path.js";
|
|
4
4
|
//#region src/advanced-fields.ts
|
|
5
5
|
/** The Builder items in a state value, keeping only well-formed `{type, data}` items. */
|
|
6
6
|
function builderItems(value) {
|
|
@@ -58,7 +58,7 @@ function builderItemSchema(component, items, index) {
|
|
|
58
58
|
const server = component.items[index];
|
|
59
59
|
if (server && server.type === type) return server.schema;
|
|
60
60
|
const block = component.blocks.find((candidate) => candidate.name === type);
|
|
61
|
-
return block ?
|
|
61
|
+
return block ? templateForIndex(block.schema, index) : [];
|
|
62
62
|
}
|
|
63
63
|
/** Label of the item at `index`: the server's when it describes the same block. */
|
|
64
64
|
function builderItemLabel(component, items, index) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"advanced-fields.js","names":[],"sources":["../src/advanced-fields.ts"],"sourcesContent":["import type { FormStore, FormStoreOptions } from './form'\nimport type {\n BuilderBlockSchema,\n BuilderItemState,\n BuilderSchema,\n SliderBehavior,\n SliderSchema,\n} from './schema/advanced-fields'\nimport type { FileUploadSchema, SchemaComponent, UploadedFile } from './schema/components'\nimport type { AnyPaginationSchema } from './schema/table'\nimport type { TableQuery } from './table'\nimport { tableQueryKey } from './table'\nimport { cloneState } from './utils/clone'\nimport { replacePlaceholder } from './utils/path'\n\n// ---------------------------------------------------------------- Builder\n\n/** The Builder items in a state value, keeping only well-formed `{type, data}` items. */\nexport function builderItems(value: unknown): BuilderItemState[] {\n if (!Array.isArray(value)) return []\n\n return value.filter(\n (item): item is BuilderItemState =>\n typeof item === 'object' &&\n item !== null &&\n typeof (item as BuilderItemState).type === 'string',\n )\n}\n\n/** Insert a fresh item of `block` at `index` (the end by default). */\nexport function insertBuilderItem(\n items: BuilderItemState[],\n block: BuilderBlockSchema,\n index: number = items.length,\n): BuilderItemState[] {\n const copy = [...items]\n copy.splice(Math.max(0, Math.min(index, items.length)), 0, cloneState(block.state))\n\n return copy\n}\n\n/** How many items of each block type the builder holds. */\nexport function builderBlockCounts(items: BuilderItemState[]): Record<string, number> {\n const counts: Record<string, number> = {}\n\n for (const item of items) counts[item.type] = (counts[item.type] ?? 0) + 1\n\n return counts\n}\n\n/**\n * Blocks whose label (or group heading) contains `query` (case-insensitive), for\n * `Builder::searchable()`.\n */\nexport function filterBuilderBlocks<T extends Pick<BuilderBlockSchema, 'label' | 'group'>>(\n blocks: T[],\n query: string,\n): T[] {\n const needle = query.trim().toLocaleLowerCase()\n\n return needle === ''\n ? blocks\n : blocks.filter(\n (block) =>\n block.label.toLocaleLowerCase().includes(needle) ||\n (block.group ?? '').toLocaleLowerCase().includes(needle),\n )\n}\n\nexport interface BuilderBlockGroup<T> {\n /** The heading; null for ungrouped blocks (listed first, without a heading). */\n group: string | null\n blocks: T[]\n}\n\n/**\n * Picker blocks under their headings (`Builder::blocks(['Group' => [...]])`): ungrouped\n * blocks first, then each group in the order it first appears. Empty groups are dropped.\n */\nexport function groupBuilderBlocks<T extends Pick<BuilderBlockSchema, 'group'>>(\n blocks: T[],\n): BuilderBlockGroup<T>[] {\n const groups = new Map<string | null, T[]>([[null, []]])\n\n for (const block of blocks) {\n const group = block.group ?? null\n\n if (!groups.has(group)) groups.set(group, [])\n groups.get(group)!.push(block)\n }\n\n return [...groups.entries()]\n .filter(([, items]) => items.length > 0)\n .map(([group, items]) => ({ group, blocks: items }))\n}\n\n/** Whether another item of `block` may be added (its own `maxItems` and the builder's). */\nexport function canAddBuilderBlock(\n component: Pick<BuilderSchema, 'maxItems'>,\n block: Pick<BuilderBlockSchema, 'name' | 'maxItems'>,\n items: BuilderItemState[],\n): boolean {\n // `null` (sent by PHP for \"no limit\") and `undefined` both mean unlimited.\n if (component.maxItems != null && items.length >= component.maxItems) return false\n\n return block.maxItems == null || (builderBlockCounts(items)[block.name] ?? 0) < block.maxItems\n}\n\n/**\n * The schema to render for the item at `index`: the server's per-item schema when it\n * describes the same block, else the block template with the index filled in (items the\n * browser just added, cloned or moved before the server has seen them).\n */\nexport function builderItemSchema(\n component: Pick<BuilderSchema, 'items' | 'blocks'>,\n items: BuilderItemState[],\n index: number,\n): SchemaComponent[] {\n const type = items[index]?.type\n const server = component.items[index]\n\n if (server && server.type === type) {\n return server.schema\n }\n\n const block = component.blocks.find((candidate) => candidate.name === type)\n\n return block ? replacePlaceholder(block.schema, '__INDEX__', String(index)) : []\n}\n\n/** Label of the item at `index`: the server's when it describes the same block. */\nexport function builderItemLabel(\n component: Pick<BuilderSchema, 'items' | 'blocks'>,\n items: BuilderItemState[],\n index: number,\n): string | null {\n const type = items[index]?.type\n const server = component.items[index]\n\n if (server && server.type === type && server.label) return server.label\n\n return component.blocks.find((block) => block.name === type)?.label ?? type ?? null\n}\n\n/** Where an option of the block picker sits (`getBoundingClientRect()` is enough). */\nexport interface BuilderPickerRect {\n top: number\n left: number\n}\n\n/**\n * Keyboard movement in the block picker grid: the index of the option to focus after\n * `key` on option `current`, or null when the key moves nowhere. Left/right follow the\n * reading direction (`rtl`), up/down go to the closest option of the row above/below\n * (rows may be ragged: groups start new rows), Home/End jump to the first/last option.\n */\nexport function builderPickerMove(\n key: string,\n current: number,\n rects: BuilderPickerRect[],\n rtl = false,\n): number | null {\n const count = rects.length\n\n if (count === 0) return null\n if (key === 'Home') return 0\n if (key === 'End') return count - 1\n\n const forward = rtl ? 'ArrowLeft' : 'ArrowRight'\n const backward = rtl ? 'ArrowRight' : 'ArrowLeft'\n\n if (key === forward) return current < count - 1 ? current + 1 : null\n if (key === backward) return current > 0 ? current - 1 : null\n if (key !== 'ArrowDown' && key !== 'ArrowUp') return null\n\n const here = rects[current]\n\n if (!here) return null\n\n const below = key === 'ArrowDown'\n let rowTop: number | null = null\n\n // The nearest row in that direction (options on one row share their top, ±2px).\n rects.forEach((rect) => {\n const ahead = below ? rect.top > here.top + 2 : rect.top < here.top - 2\n\n if (ahead && (rowTop === null || (below ? rect.top < rowTop : rect.top > rowTop))) {\n rowTop = rect.top\n }\n })\n\n if (rowTop === null) return null\n\n let best: number | null = null\n\n rects.forEach((rect, index) => {\n if (Math.abs(rect.top - (rowTop as number)) > 2) return\n\n const candidate = rects[best ?? -1]\n\n if (\n best === null ||\n !candidate ||\n Math.abs(rect.left - here.left) < Math.abs(candidate.left - here.left)\n ) {\n best = index\n }\n })\n\n return best\n}\n\n// ---------------------------------------------------------------- ToggleButtons\n\n/** The next state after a toggle button for `option` was pressed. */\nexport function toggleButtonsNext(\n current: unknown,\n option: string,\n multiple: boolean,\n): string | string[] | null {\n if (multiple) {\n const values = Array.isArray(current) ? current.map(String) : []\n\n return values.includes(option)\n ? values.filter((value) => value !== option)\n : [...values, option]\n }\n\n return toggleButtonsSelected(current, false).includes(option) ? null : option\n}\n\n/** The option values a toggle buttons state selects. */\nexport function toggleButtonsSelected(current: unknown, multiple: boolean): string[] {\n if (multiple) return Array.isArray(current) ? current.map(String) : []\n if (current === null || current === undefined || current === '') return []\n if (typeof current === 'boolean') return [current ? '1' : '0']\n\n return [String(current)]\n}\n\n// ---------------------------------------------------------------- Slider\n\nfunction roundTo(value: number, step: number, min: number): number {\n const decimals = (String(step).split('.')[1] ?? '').length\n\n return Number((Math.round((value - min) / step) * step + min).toFixed(decimals))\n}\n\n/** The slider's thumb values: always numbers within bounds, sorted for ranges. */\nexport function sliderValues(\n value: unknown,\n schema: Pick<SliderSchema, 'min' | 'max' | 'multiple'>,\n): number[] {\n const clamp = (input: unknown, fallback: number): number => {\n const number = typeof input === 'number' ? input : Number(input)\n\n return Number.isFinite(number) && input !== null && input !== ''\n ? Math.min(schema.max, Math.max(schema.min, number))\n : fallback\n }\n\n if (schema.multiple) {\n const list = Array.isArray(value) && value.length > 0 ? value : [schema.min, schema.max]\n\n return list.map((item) => clamp(item, schema.min)).sort((a, b) => a - b)\n }\n\n return [clamp(value, schema.min)]\n}\n\n/** The state to store for the given thumb values. */\nexport function sliderState(\n values: number[],\n schema: Pick<SliderSchema, 'multiple'>,\n): number | number[] | null {\n return schema.multiple ? [...values].sort((a, b) => a - b) : (values[0] ?? null)\n}\n\nexport interface SliderPip {\n value: number\n /** Position from the start of the track, 0–100. */\n percent: number\n /** Whether the pip shows a label (at most 11 are labelled by default). */\n labelled: boolean\n /** A small label (`pipsFilter()` returned 2, or `PipsMode::Steps` between range points). */\n small?: boolean\n}\n\n/**\n * Tick marks along the track: the server's `pipList` (modes, `steppedPips()`,\n * `pipsFilter()`), or every `pipsDensity` units / every step for schemas without one.\n */\nexport function sliderPips(\n schema: Pick<SliderSchema, 'min' | 'max' | 'step' | 'pipsDensity' | 'pipList'>,\n): SliderPip[] {\n if (schema.pipList) {\n return schema.pipList.map((pip) => ({\n value: pip.value,\n percent: pip.percent,\n labelled: pip.type !== 0,\n ...(pip.type === 2 ? { small: true } : {}),\n }))\n }\n\n const range = schema.max - schema.min\n\n if (!(range > 0)) return []\n\n let interval = schema.pipsDensity && schema.pipsDensity > 0 ? schema.pipsDensity : schema.step\n\n // Never draw more than 100 ticks.\n while (range / interval > 100) interval *= 2\n\n const values: number[] = []\n\n for (let value = schema.min; value < schema.max + interval / 1e6; value += interval) {\n values.push(roundTo(Math.min(value, schema.max), schema.step, schema.min))\n }\n\n if (values[values.length - 1] !== schema.max) values.push(schema.max)\n\n const labelEvery = Math.max(1, Math.ceil((values.length - 1) / 10))\n\n return values.map((value, index) => ({\n value,\n percent: ((value - schema.min) / range) * 100,\n labelled: index % labelEvery === 0 || index === values.length - 1,\n }))\n}\n\ntype SliderTrack = Pick<SliderSchema, 'min' | 'max' | 'step' | 'nonLinearPoints'>\n\ninterface SliderSection {\n fromPercent: number\n toPercent: number\n fromValue: number\n toValue: number\n step: number\n}\n\n/** The sections of the track between its ends and `nonLinearPoints`. */\nfunction sliderSections(schema: SliderTrack): SliderSection[] {\n const points = [\n { percent: 0, value: schema.min, step: schema.step },\n ...(schema.nonLinearPoints ?? []),\n { percent: 100, value: schema.max },\n ]\n const sections: SliderSection[] = []\n let step = schema.step\n\n for (let index = 0; index < points.length - 1; index++) {\n const from = points[index]!\n const to = points[index + 1]!\n step = from.step ?? step\n sections.push({\n fromPercent: from.percent,\n toPercent: to.percent,\n fromValue: from.value,\n toValue: to.value,\n step,\n })\n }\n\n return sections\n}\n\n/** Where a value sits on the track, 0–100 (non-linear tracks included). */\nexport function sliderPercent(value: number, schema: SliderTrack): number {\n for (const section of sliderSections(schema)) {\n if (value <= section.toValue || section.toPercent >= 100) {\n if (section.toValue === section.fromValue) return section.fromPercent\n\n const ratio = Math.min(\n 1,\n Math.max(0, (value - section.fromValue) / (section.toValue - section.fromValue)),\n )\n\n return section.fromPercent + ratio * (section.toPercent - section.fromPercent)\n }\n }\n\n return 100\n}\n\n/** The value at a position on the track (0–100), snapped to its section's step by default. */\nexport function sliderValueAt(percent: number, schema: SliderTrack, snap = true): number {\n for (const section of sliderSections(schema)) {\n if (percent <= section.toPercent || section.toPercent >= 100) {\n const span = section.toPercent - section.fromPercent\n const ratio =\n span === 0 ? 0 : Math.min(1, Math.max(0, (percent - section.fromPercent) / span))\n const value = section.fromValue + ratio * (section.toValue - section.fromValue)\n\n return snap && section.step > 0\n ? Math.min(section.toValue, roundTo(value, section.step, section.fromValue))\n : value\n }\n }\n\n return schema.max\n}\n\n/** Round a value to the step of its section of the track. */\nexport function sliderSnap(value: number, schema: SliderTrack): number {\n return sliderValueAt(sliderPercent(value, schema), schema)\n}\n\n/** The lowest and highest value a thumb can take (the track minus `rangePadding()`). */\nexport function sliderBounds(\n schema: Pick<SliderSchema, 'min' | 'max' | 'rangePadding'>,\n): [number, number] {\n const [start, end] = schema.rangePadding ?? [0, 0]\n\n return [schema.min + start, schema.max - end]\n}\n\n/** Whether thumb `index` shows its value (`tooltips()`, or `tooltips([true, false])`). */\nexport function sliderShowsTooltip(schema: Pick<SliderSchema, 'tooltips'>, index: number): boolean {\n return Array.isArray(schema.tooltips) ? schema.tooltips[index] === true : Boolean(schema.tooltips)\n}\n\n/** Whether any thumb shows its value (the slider leaves room above the track). */\nexport function sliderHasTooltips(schema: Pick<SliderSchema, 'tooltips'>): boolean {\n return Array.isArray(schema.tooltips) ? schema.tooltips.includes(true) : Boolean(schema.tooltips)\n}\n\n/**\n * Filled sections of the track for `fillTrack([false, true, false])`, as positions (0–100)\n * from the minimum. Null when `fillTrack` is not an array: the primitive's range is used.\n */\nexport function sliderFillSections(\n values: number[],\n schema: Pick<SliderSchema, 'fillTrack'> & SliderTrack,\n): Array<{ start: number; end: number }> | null {\n if (!Array.isArray(schema.fillTrack)) return null\n\n const stops = [0, ...values.map((value) => sliderPercent(value, schema)), 100]\n\n return schema.fillTrack.flatMap((filled, index) => {\n const start = stops[index]\n const end = stops[index + 1]\n\n return filled && start !== undefined && end !== undefined && end > start ? [{ start, end }] : []\n })\n}\n\n/** Inline position of a fill section: from the minimum's edge of the track. */\nexport function sliderFillStyle(\n section: { start: number; end: number },\n options: { vertical: boolean; topToBottom?: boolean; direction: 'ltr' | 'rtl' },\n): Record<string, string> {\n const size = `${section.end - section.start}%`\n const offset = `${section.start}%`\n\n if (options.vertical) {\n return options.topToBottom ? { top: offset, height: size } : { bottom: offset, height: size }\n }\n\n return options.direction === 'rtl'\n ? { right: offset, width: size }\n : { left: offset, width: size }\n}\n\n/** The pointer behaviours of a slider (`behavior()`); `tap` by default. */\nexport function sliderBehavior(schema: Pick<SliderSchema, 'behavior'>): Set<SliderBehavior> {\n return new Set(schema.behavior ?? ['tap'])\n}\n\n/** The slider's direction: `rtl()` when set, else the panel's. */\nexport function sliderDirection(\n schema: Pick<SliderSchema, 'rtl'>,\n panel: 'ltr' | 'rtl',\n): 'ltr' | 'rtl' {\n return schema.rtl === undefined || schema.rtl === null ? panel : schema.rtl ? 'rtl' : 'ltr'\n}\n\n/**\n * What the slider primitive works with: values directly, or positions (0–100) on a\n * non-linear track. `smooth` (`Behavior::SmoothSteps` while dragging) drops the step.\n */\nexport function sliderPrimitive(\n schema: SliderTrack,\n smooth = false,\n): { min: number; max: number; step: number; nonLinear: boolean } {\n if (schema.nonLinearPoints && schema.nonLinearPoints.length > 0) {\n return { min: 0, max: 100, step: smooth ? 0.01 : 0.1, nonLinear: true }\n }\n\n return {\n min: schema.min,\n max: schema.max,\n step: smooth ? (schema.max - schema.min) / 1000 || schema.step : schema.step,\n nonLinear: false,\n }\n}\n\n/** Thumb values as the primitive's values. */\nexport function sliderToPrimitive(values: number[], schema: SliderTrack): number[] {\n return sliderPrimitive(schema).nonLinear\n ? values.map((value) => sliderPercent(value, schema))\n : values\n}\n\n/** The primitive's values as thumb values (snapped unless dragging smoothly). */\nexport function sliderFromPrimitive(values: number[], schema: SliderTrack, snap = true): number[] {\n return sliderPrimitive(schema).nonLinear\n ? values.map((position) => sliderValueAt(position, schema, snap))\n : values\n}\n\ntype SliderRules = SliderTrack &\n Pick<SliderSchema, 'rangePadding' | 'behavior' | 'minDifference' | 'multiple'>\n\n/**\n * The thumb values to store after the primitive moved from `previous` to `next` (both\n * sorted): kept inside `rangePadding()`; with `fixed` every thumb moves by the same\n * amount; without `unconstrained` a thumb stops at its neighbours (`minDifference()` apart)\n * instead of passing them.\n */\nexport function sliderNextValues(\n previous: number[],\n next: number[],\n schema: SliderRules,\n): number[] {\n const [low, high] = sliderBounds(schema)\n const behavior = sliderBehavior(schema)\n const clamp = (value: number) => Math.min(high, Math.max(low, value))\n\n if (next.length !== previous.length || previous.length < 2) {\n return next.map(clamp)\n }\n\n const moved = sliderMovedThumb(previous, next)\n\n if (!moved) return next.map(clamp)\n\n const [index, value] = moved\n\n if (behavior.has('fixed')) {\n return sliderShift(previous, value - previous[index]!, schema)\n }\n\n if (behavior.has('unconstrained')) {\n return [...next].map(clamp).sort((a, b) => a - b)\n }\n\n const gap = schema.minDifference ?? 0\n const lower = index > 0 ? previous[index - 1]! + gap : low\n const upper = index < previous.length - 1 ? previous[index + 1]! - gap : high\n const result = [...previous]\n result[index] = Math.min(Math.min(high, upper), Math.max(Math.max(low, lower), value))\n\n return result\n}\n\n/**\n * Which thumb of `previous` moved, and where to, when exactly one value changed (the\n * primitive may have re-sorted the thumbs after one passed another).\n */\nfunction sliderMovedThumb(previous: number[], next: number[]): [number, number] | null {\n const remaining = [...next]\n const removed: number[] = []\n\n for (const [index, value] of previous.entries()) {\n const found = remaining.indexOf(value)\n\n if (found === -1) removed.push(index)\n else remaining.splice(found, 1)\n }\n\n if (removed.length !== 1 || remaining.length !== 1) return null\n\n return [removed[0]!, remaining[0]!]\n}\n\n/**\n * Move every thumb by `delta` (in values), keeping their distances and stopping at the\n * ends of the track (minus `rangePadding()`).\n */\nexport function sliderShift(values: number[], delta: number, schema: SliderRules): number[] {\n if (values.length === 0) return values\n\n const [low, high] = sliderBounds(schema)\n const first = Math.min(...values)\n const last = Math.max(...values)\n const allowed = Math.min(high - last, Math.max(low - first, delta))\n\n return values.map((value) => value + allowed)\n}\n\n/**\n * `Behavior::Drag`: follow a pointer that grabbed the filled range between the thumbs and\n * move them together, until the pointer is released. Positions are measured on `track`\n * in the slider's direction (bottom to top when vertical).\n */\nexport function startSliderRangeDrag(\n event: PointerEvent,\n options: {\n track: HTMLElement\n values: number[]\n schema: SliderRules\n direction: 'ltr' | 'rtl'\n vertical: boolean\n /** `vertical(isTopToBottom: true)`: the minimum is at the top. */\n topToBottom?: boolean\n onChange: (values: number[]) => void\n onEnd?: () => void\n },\n): void {\n const { track, values, schema, direction, vertical } = options\n const rect = track.getBoundingClientRect()\n const size = vertical ? rect.height : rect.width\n\n if (!(size > 0) || values.length < 2) return\n\n event.preventDefault()\n event.stopPropagation()\n\n const start = vertical ? event.clientY : event.clientX\n const percents = values.map((value) => sliderPercent(value, schema))\n\n const move = (moveEvent: PointerEvent) => {\n const current = vertical ? moveEvent.clientY : moveEvent.clientX\n let delta = ((current - start) / size) * 100\n\n if (vertical ? !options.topToBottom : direction === 'rtl') delta = -delta\n\n const [low, high] = sliderBounds(schema).map((bound) => sliderPercent(bound, schema)) as [\n number,\n number,\n ]\n const allowed = Math.min(\n high - Math.max(...percents),\n Math.max(low - Math.min(...percents), delta),\n )\n const moved = percents.map((percent) => sliderValueAt(percent + allowed, schema))\n // Snapping each thumb separately could change their distance: keep the first one's.\n options.onChange(\n sliderPrimitive(schema).nonLinear\n ? moved\n : sliderShift(values, moved[0]! - values[0]!, schema),\n )\n }\n\n const end = () => {\n window.removeEventListener('pointermove', move)\n window.removeEventListener('pointerup', end)\n window.removeEventListener('pointercancel', end)\n options.onEnd?.()\n }\n\n window.addEventListener('pointermove', move)\n window.addEventListener('pointerup', end)\n window.addEventListener('pointercancel', end)\n}\n\n/**\n * Whether a pointer press on the slider should be swallowed before the primitive sees it:\n * a press on the track when `tap` (and `snap`) are off. Thumbs always respond.\n */\nexport function sliderBlocksTap(\n target: EventTarget | null,\n schema: Pick<SliderSchema, 'behavior'>,\n): boolean {\n const behavior = sliderBehavior(schema)\n\n if (behavior.has('tap') || behavior.has('snap')) return false\n\n return !(target instanceof Element && target.closest('[role=\"slider\"]'))\n}\n\n// ---------------------------------------------------------------- CodeEditor\n\nexport interface TextEdit {\n value: string\n selectionStart: number\n selectionEnd: number\n}\n\n/**\n * Indent (or with `outdent`, unindent) the lines touched by the selection. A collapsed\n * selection without `outdent` inserts spaces at the caret.\n */\nexport function indentCode(\n value: string,\n selectionStart: number,\n selectionEnd: number,\n tabSize: number,\n outdent = false,\n): TextEdit {\n const indent = ' '.repeat(Math.max(1, tabSize))\n\n if (!outdent && selectionStart === selectionEnd) {\n return {\n value: value.slice(0, selectionStart) + indent + value.slice(selectionEnd),\n selectionStart: selectionStart + indent.length,\n selectionEnd: selectionStart + indent.length,\n }\n }\n\n const lineStart = value.lastIndexOf('\\n', selectionStart - 1) + 1\n const block = value.slice(lineStart, selectionEnd)\n const lines = block.split('\\n')\n let firstDelta = 0\n let totalDelta = 0\n\n const next = lines.map((line, index) => {\n if (outdent) {\n const removable = line.match(new RegExp(`^ {1,${indent.length}}|^\\\\t`))?.[0].length ?? 0\n\n if (index === 0) firstDelta = -removable\n totalDelta -= removable\n\n return line.slice(removable)\n }\n\n if (index === 0) firstDelta = indent.length\n totalDelta += indent.length\n\n return indent + line\n })\n\n return {\n value: value.slice(0, lineStart) + next.join('\\n') + value.slice(selectionEnd),\n selectionStart: Math.max(lineStart, selectionStart + firstDelta),\n selectionEnd: Math.max(lineStart, selectionEnd + totalDelta),\n }\n}\n\n/** Keep the indentation of the current line when Enter is pressed. */\nexport function newlineWithIndent(\n value: string,\n selectionStart: number,\n selectionEnd: number,\n): TextEdit {\n const lineStart = value.lastIndexOf('\\n', selectionStart - 1) + 1\n const indent = value.slice(lineStart, selectionStart).match(/^[ \\t]*/)?.[0] ?? ''\n const insert = `\\n${indent}`\n\n return {\n value: value.slice(0, selectionStart) + insert + value.slice(selectionEnd),\n selectionStart: selectionStart + insert.length,\n selectionEnd: selectionStart + insert.length,\n }\n}\n\n// ---------------------------------------------------------------- ModalTableSelect\n\n/** Query parameters for the picker table, prefixed with the table state identifier. */\nexport function modalTableQuery(identifier: string, query: TableQuery): Record<string, unknown> {\n const params: Record<string, unknown> = {}\n\n for (const [name, value] of Object.entries(query) as [keyof TableQuery, unknown][]) {\n if (value === null || value === undefined || value === '') continue\n\n params[tableQueryKey(identifier, name)] = value\n }\n\n return params\n}\n\n/**\n * The selection after the record `key` was toggled in the picker. Single selects keep\n * one key; multiple selects stop at `maxItems`.\n */\nexport function toggleModalTableKey(\n selected: string[],\n key: string,\n multiple: boolean,\n maxItems?: number,\n): string[] {\n if (selected.includes(key)) {\n return selected.filter((candidate) => candidate !== key)\n }\n\n if (!multiple) return [key]\n\n if (maxItems != null && selected.length >= maxItems) return selected\n\n return [...selected, key]\n}\n\n/**\n * The store options of a picker's filters form. Searchable selects among the filters load\n * their options through the owning form's endpoint, addressed by the ModalTableSelect's\n * `path` (`pickerFilters`), so the server resolves the picker table (and its tenant-scoped\n * relationships) the same way it does for the table itself.\n */\nexport function pickerFilterFormOptions(owner: FormStore, path: string): FormStoreOptions {\n return {\n token: owner.token,\n component: owner.component,\n record: owner.record,\n optionsUrl: owner.optionsUrl,\n params: () => ({\n ...owner.requestContext(),\n pickerFilters: path,\n ownerState: owner.state.values,\n }),\n }\n}\n\n// ---------------------------------------------------------------- FileUpload\n\n/**\n * The stored or temporary paths in a FileUpload state value. The server always holds a\n * list (a single field's is at most one long) and accepts one path too, so a value from\n * either side normalises the same way.\n */\nexport function fileUploadPaths(value: unknown): string[] {\n if (Array.isArray(value)) {\n return value.filter((path): path is string => typeof path === 'string' && path !== '')\n }\n\n return typeof value === 'string' && value !== '' ? [value] : []\n}\n\n/** The picker's `accept`: the accepted types plus the extensions of `mimeTypeMap()`. */\nexport function fileUploadAccept(\n schema: Pick<FileUploadSchema, 'acceptedFileTypes' | 'mimeTypeMap'>,\n): string | undefined {\n if (!schema.acceptedFileTypes || schema.acceptedFileTypes.length === 0) return undefined\n\n const mapped = Object.entries(schema.mimeTypeMap ?? {})\n .filter(([, type]) => schema.acceptedFileTypes!.includes(type))\n .map(([extension]) => `.${extension}`)\n\n return [...new Set([...schema.acceptedFileTypes, ...mapped])].join(',')\n}\n\n/** A `'2:1'` / `'16/9'` ratio as a CSS `aspect-ratio` value. */\nexport function cssAspectRatio(ratio: string | null | undefined): string | undefined {\n const [width, height] = (ratio ?? '').split(/[:/]/).map((part) => Number(part.trim()))\n\n return width && height && width > 0 && height > 0 ? `${width} / ${height}` : undefined\n}\n\n/**\n * The field's paths after an upload finished: the new key after the existing files, or\n * before them with `appendFiles(false)`, keeping at most `maxFiles`.\n */\nexport function insertUploadedFile(\n paths: string[],\n key: string,\n schema: Pick<FileUploadSchema, 'appendFiles'>,\n maxFiles: number,\n): string[] {\n return schema.appendFiles === false\n ? [key, ...paths].slice(0, maxFiles)\n : [...paths, key].slice(-maxFiles)\n}\n\n/** Where a file's \"open\" and \"download\" buttons link to. */\nexport function uploadedFileLinks(file: Pick<UploadedFile, 'url' | 'openUrl' | 'downloadUrl'>): {\n open?: string\n download?: string\n} {\n return { open: file.openUrl ?? file.url, download: file.downloadUrl ?? file.url }\n}\n\n/**\n * A queue that runs at most `limit` tasks at once, in the order they were added\n * (`maxParallelUploads()`).\n */\nexport function concurrencyLimiter(limit: number): <T>(task: () => Promise<T>) => Promise<T> {\n const max = Math.max(1, Math.floor(limit) || 1)\n const waiting: Array<() => void> = []\n let running = 0\n\n const release = () => {\n running--\n waiting.shift()?.()\n }\n\n return <T>(task: () => Promise<T>): Promise<T> =>\n new Promise<T>((resolve, reject) => {\n const start = () => {\n running++\n task().then(resolve, reject).finally(release)\n }\n\n if (running < max) start()\n else waiting.push(start)\n })\n}\n\n/** The selected record keys in a ModalTableSelect state value. */\nexport function modalTableSelected(value: unknown, multiple: boolean): string[] {\n if (multiple) return Array.isArray(value) ? value.map(String) : []\n\n return value === null || value === undefined || value === '' ? [] : [String(value)]\n}\n\nexport interface ModalTablePaging {\n page: number\n hasPrevious: boolean\n hasNext: boolean\n from: number\n to: number\n total?: number\n}\n\n/** Page-by-page navigation for the picker table, whatever the table's pagination mode. */\nexport function modalTablePaging(\n pagination: AnyPaginationSchema | null | undefined,\n): ModalTablePaging | null {\n if (!pagination) return null\n\n const page = 'currentPage' in pagination ? pagination.currentPage : 1\n const hasNext =\n 'lastPage' in pagination\n ? page < pagination.lastPage\n : 'currentPage' in pagination && 'hasMorePages' in pagination && pagination.hasMorePages\n\n return {\n page,\n hasPrevious: 'currentPage' in pagination && page > 1,\n hasNext: Boolean(hasNext),\n from: pagination.from,\n to: pagination.to,\n total: 'total' in pagination ? pagination.total : undefined,\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"advanced-fields.js","names":[],"sources":["../src/advanced-fields.ts"],"sourcesContent":["import type { FormStore, FormStoreOptions } from './form'\nimport type {\n BuilderBlockSchema,\n BuilderItemState,\n BuilderSchema,\n SliderBehavior,\n SliderSchema,\n} from './schema/advanced-fields'\nimport type { FileUploadSchema, SchemaComponent, UploadedFile } from './schema/components'\nimport type { AnyPaginationSchema } from './schema/table'\nimport type { TableQuery } from './table'\nimport { tableQueryKey } from './table'\nimport { cloneState } from './utils/clone'\nimport { templateForIndex } from './utils/path'\n\n// ---------------------------------------------------------------- Builder\n\n/** The Builder items in a state value, keeping only well-formed `{type, data}` items. */\nexport function builderItems(value: unknown): BuilderItemState[] {\n if (!Array.isArray(value)) return []\n\n return value.filter(\n (item): item is BuilderItemState =>\n typeof item === 'object' &&\n item !== null &&\n typeof (item as BuilderItemState).type === 'string',\n )\n}\n\n/** Insert a fresh item of `block` at `index` (the end by default). */\nexport function insertBuilderItem(\n items: BuilderItemState[],\n block: BuilderBlockSchema,\n index: number = items.length,\n): BuilderItemState[] {\n const copy = [...items]\n copy.splice(Math.max(0, Math.min(index, items.length)), 0, cloneState(block.state))\n\n return copy\n}\n\n/** How many items of each block type the builder holds. */\nexport function builderBlockCounts(items: BuilderItemState[]): Record<string, number> {\n const counts: Record<string, number> = {}\n\n for (const item of items) counts[item.type] = (counts[item.type] ?? 0) + 1\n\n return counts\n}\n\n/**\n * Blocks whose label (or group heading) contains `query` (case-insensitive), for\n * `Builder::searchable()`.\n */\nexport function filterBuilderBlocks<T extends Pick<BuilderBlockSchema, 'label' | 'group'>>(\n blocks: T[],\n query: string,\n): T[] {\n const needle = query.trim().toLocaleLowerCase()\n\n return needle === ''\n ? blocks\n : blocks.filter(\n (block) =>\n block.label.toLocaleLowerCase().includes(needle) ||\n (block.group ?? '').toLocaleLowerCase().includes(needle),\n )\n}\n\nexport interface BuilderBlockGroup<T> {\n /** The heading; null for ungrouped blocks (listed first, without a heading). */\n group: string | null\n blocks: T[]\n}\n\n/**\n * Picker blocks under their headings (`Builder::blocks(['Group' => [...]])`): ungrouped\n * blocks first, then each group in the order it first appears. Empty groups are dropped.\n */\nexport function groupBuilderBlocks<T extends Pick<BuilderBlockSchema, 'group'>>(\n blocks: T[],\n): BuilderBlockGroup<T>[] {\n const groups = new Map<string | null, T[]>([[null, []]])\n\n for (const block of blocks) {\n const group = block.group ?? null\n\n if (!groups.has(group)) groups.set(group, [])\n groups.get(group)!.push(block)\n }\n\n return [...groups.entries()]\n .filter(([, items]) => items.length > 0)\n .map(([group, items]) => ({ group, blocks: items }))\n}\n\n/** Whether another item of `block` may be added (its own `maxItems` and the builder's). */\nexport function canAddBuilderBlock(\n component: Pick<BuilderSchema, 'maxItems'>,\n block: Pick<BuilderBlockSchema, 'name' | 'maxItems'>,\n items: BuilderItemState[],\n): boolean {\n // `null` (sent by PHP for \"no limit\") and `undefined` both mean unlimited.\n if (component.maxItems != null && items.length >= component.maxItems) return false\n\n return block.maxItems == null || (builderBlockCounts(items)[block.name] ?? 0) < block.maxItems\n}\n\n/**\n * The schema to render for the item at `index`: the server's per-item schema when it\n * describes the same block, else the block template with the index filled in (items the\n * browser just added, cloned or moved before the server has seen them).\n */\nexport function builderItemSchema(\n component: Pick<BuilderSchema, 'items' | 'blocks'>,\n items: BuilderItemState[],\n index: number,\n): SchemaComponent[] {\n const type = items[index]?.type\n const server = component.items[index]\n\n if (server && server.type === type) {\n return server.schema\n }\n\n const block = component.blocks.find((candidate) => candidate.name === type)\n\n return block ? templateForIndex(block.schema, index) : []\n}\n\n/** Label of the item at `index`: the server's when it describes the same block. */\nexport function builderItemLabel(\n component: Pick<BuilderSchema, 'items' | 'blocks'>,\n items: BuilderItemState[],\n index: number,\n): string | null {\n const type = items[index]?.type\n const server = component.items[index]\n\n if (server && server.type === type && server.label) return server.label\n\n return component.blocks.find((block) => block.name === type)?.label ?? type ?? null\n}\n\n/** Where an option of the block picker sits (`getBoundingClientRect()` is enough). */\nexport interface BuilderPickerRect {\n top: number\n left: number\n}\n\n/**\n * Keyboard movement in the block picker grid: the index of the option to focus after\n * `key` on option `current`, or null when the key moves nowhere. Left/right follow the\n * reading direction (`rtl`), up/down go to the closest option of the row above/below\n * (rows may be ragged: groups start new rows), Home/End jump to the first/last option.\n */\nexport function builderPickerMove(\n key: string,\n current: number,\n rects: BuilderPickerRect[],\n rtl = false,\n): number | null {\n const count = rects.length\n\n if (count === 0) return null\n if (key === 'Home') return 0\n if (key === 'End') return count - 1\n\n const forward = rtl ? 'ArrowLeft' : 'ArrowRight'\n const backward = rtl ? 'ArrowRight' : 'ArrowLeft'\n\n if (key === forward) return current < count - 1 ? current + 1 : null\n if (key === backward) return current > 0 ? current - 1 : null\n if (key !== 'ArrowDown' && key !== 'ArrowUp') return null\n\n const here = rects[current]\n\n if (!here) return null\n\n const below = key === 'ArrowDown'\n let rowTop: number | null = null\n\n // The nearest row in that direction (options on one row share their top, ±2px).\n rects.forEach((rect) => {\n const ahead = below ? rect.top > here.top + 2 : rect.top < here.top - 2\n\n if (ahead && (rowTop === null || (below ? rect.top < rowTop : rect.top > rowTop))) {\n rowTop = rect.top\n }\n })\n\n if (rowTop === null) return null\n\n let best: number | null = null\n\n rects.forEach((rect, index) => {\n if (Math.abs(rect.top - (rowTop as number)) > 2) return\n\n const candidate = rects[best ?? -1]\n\n if (\n best === null ||\n !candidate ||\n Math.abs(rect.left - here.left) < Math.abs(candidate.left - here.left)\n ) {\n best = index\n }\n })\n\n return best\n}\n\n// ---------------------------------------------------------------- ToggleButtons\n\n/** The next state after a toggle button for `option` was pressed. */\nexport function toggleButtonsNext(\n current: unknown,\n option: string,\n multiple: boolean,\n): string | string[] | null {\n if (multiple) {\n const values = Array.isArray(current) ? current.map(String) : []\n\n return values.includes(option)\n ? values.filter((value) => value !== option)\n : [...values, option]\n }\n\n return toggleButtonsSelected(current, false).includes(option) ? null : option\n}\n\n/** The option values a toggle buttons state selects. */\nexport function toggleButtonsSelected(current: unknown, multiple: boolean): string[] {\n if (multiple) return Array.isArray(current) ? current.map(String) : []\n if (current === null || current === undefined || current === '') return []\n if (typeof current === 'boolean') return [current ? '1' : '0']\n\n return [String(current)]\n}\n\n// ---------------------------------------------------------------- Slider\n\nfunction roundTo(value: number, step: number, min: number): number {\n const decimals = (String(step).split('.')[1] ?? '').length\n\n return Number((Math.round((value - min) / step) * step + min).toFixed(decimals))\n}\n\n/** The slider's thumb values: always numbers within bounds, sorted for ranges. */\nexport function sliderValues(\n value: unknown,\n schema: Pick<SliderSchema, 'min' | 'max' | 'multiple'>,\n): number[] {\n const clamp = (input: unknown, fallback: number): number => {\n const number = typeof input === 'number' ? input : Number(input)\n\n return Number.isFinite(number) && input !== null && input !== ''\n ? Math.min(schema.max, Math.max(schema.min, number))\n : fallback\n }\n\n if (schema.multiple) {\n const list = Array.isArray(value) && value.length > 0 ? value : [schema.min, schema.max]\n\n return list.map((item) => clamp(item, schema.min)).sort((a, b) => a - b)\n }\n\n return [clamp(value, schema.min)]\n}\n\n/** The state to store for the given thumb values. */\nexport function sliderState(\n values: number[],\n schema: Pick<SliderSchema, 'multiple'>,\n): number | number[] | null {\n return schema.multiple ? [...values].sort((a, b) => a - b) : (values[0] ?? null)\n}\n\nexport interface SliderPip {\n value: number\n /** Position from the start of the track, 0–100. */\n percent: number\n /** Whether the pip shows a label (at most 11 are labelled by default). */\n labelled: boolean\n /** A small label (`pipsFilter()` returned 2, or `PipsMode::Steps` between range points). */\n small?: boolean\n}\n\n/**\n * Tick marks along the track: the server's `pipList` (modes, `steppedPips()`,\n * `pipsFilter()`), or every `pipsDensity` units / every step for schemas without one.\n */\nexport function sliderPips(\n schema: Pick<SliderSchema, 'min' | 'max' | 'step' | 'pipsDensity' | 'pipList'>,\n): SliderPip[] {\n if (schema.pipList) {\n return schema.pipList.map((pip) => ({\n value: pip.value,\n percent: pip.percent,\n labelled: pip.type !== 0,\n ...(pip.type === 2 ? { small: true } : {}),\n }))\n }\n\n const range = schema.max - schema.min\n\n if (!(range > 0)) return []\n\n let interval = schema.pipsDensity && schema.pipsDensity > 0 ? schema.pipsDensity : schema.step\n\n // Never draw more than 100 ticks.\n while (range / interval > 100) interval *= 2\n\n const values: number[] = []\n\n for (let value = schema.min; value < schema.max + interval / 1e6; value += interval) {\n values.push(roundTo(Math.min(value, schema.max), schema.step, schema.min))\n }\n\n if (values[values.length - 1] !== schema.max) values.push(schema.max)\n\n const labelEvery = Math.max(1, Math.ceil((values.length - 1) / 10))\n\n return values.map((value, index) => ({\n value,\n percent: ((value - schema.min) / range) * 100,\n labelled: index % labelEvery === 0 || index === values.length - 1,\n }))\n}\n\ntype SliderTrack = Pick<SliderSchema, 'min' | 'max' | 'step' | 'nonLinearPoints'>\n\ninterface SliderSection {\n fromPercent: number\n toPercent: number\n fromValue: number\n toValue: number\n step: number\n}\n\n/** The sections of the track between its ends and `nonLinearPoints`. */\nfunction sliderSections(schema: SliderTrack): SliderSection[] {\n const points = [\n { percent: 0, value: schema.min, step: schema.step },\n ...(schema.nonLinearPoints ?? []),\n { percent: 100, value: schema.max },\n ]\n const sections: SliderSection[] = []\n let step = schema.step\n\n for (let index = 0; index < points.length - 1; index++) {\n const from = points[index]!\n const to = points[index + 1]!\n step = from.step ?? step\n sections.push({\n fromPercent: from.percent,\n toPercent: to.percent,\n fromValue: from.value,\n toValue: to.value,\n step,\n })\n }\n\n return sections\n}\n\n/** Where a value sits on the track, 0–100 (non-linear tracks included). */\nexport function sliderPercent(value: number, schema: SliderTrack): number {\n for (const section of sliderSections(schema)) {\n if (value <= section.toValue || section.toPercent >= 100) {\n if (section.toValue === section.fromValue) return section.fromPercent\n\n const ratio = Math.min(\n 1,\n Math.max(0, (value - section.fromValue) / (section.toValue - section.fromValue)),\n )\n\n return section.fromPercent + ratio * (section.toPercent - section.fromPercent)\n }\n }\n\n return 100\n}\n\n/** The value at a position on the track (0–100), snapped to its section's step by default. */\nexport function sliderValueAt(percent: number, schema: SliderTrack, snap = true): number {\n for (const section of sliderSections(schema)) {\n if (percent <= section.toPercent || section.toPercent >= 100) {\n const span = section.toPercent - section.fromPercent\n const ratio =\n span === 0 ? 0 : Math.min(1, Math.max(0, (percent - section.fromPercent) / span))\n const value = section.fromValue + ratio * (section.toValue - section.fromValue)\n\n return snap && section.step > 0\n ? Math.min(section.toValue, roundTo(value, section.step, section.fromValue))\n : value\n }\n }\n\n return schema.max\n}\n\n/** Round a value to the step of its section of the track. */\nexport function sliderSnap(value: number, schema: SliderTrack): number {\n return sliderValueAt(sliderPercent(value, schema), schema)\n}\n\n/** The lowest and highest value a thumb can take (the track minus `rangePadding()`). */\nexport function sliderBounds(\n schema: Pick<SliderSchema, 'min' | 'max' | 'rangePadding'>,\n): [number, number] {\n const [start, end] = schema.rangePadding ?? [0, 0]\n\n return [schema.min + start, schema.max - end]\n}\n\n/** Whether thumb `index` shows its value (`tooltips()`, or `tooltips([true, false])`). */\nexport function sliderShowsTooltip(schema: Pick<SliderSchema, 'tooltips'>, index: number): boolean {\n return Array.isArray(schema.tooltips) ? schema.tooltips[index] === true : Boolean(schema.tooltips)\n}\n\n/** Whether any thumb shows its value (the slider leaves room above the track). */\nexport function sliderHasTooltips(schema: Pick<SliderSchema, 'tooltips'>): boolean {\n return Array.isArray(schema.tooltips) ? schema.tooltips.includes(true) : Boolean(schema.tooltips)\n}\n\n/**\n * Filled sections of the track for `fillTrack([false, true, false])`, as positions (0–100)\n * from the minimum. Null when `fillTrack` is not an array: the primitive's range is used.\n */\nexport function sliderFillSections(\n values: number[],\n schema: Pick<SliderSchema, 'fillTrack'> & SliderTrack,\n): Array<{ start: number; end: number }> | null {\n if (!Array.isArray(schema.fillTrack)) return null\n\n const stops = [0, ...values.map((value) => sliderPercent(value, schema)), 100]\n\n return schema.fillTrack.flatMap((filled, index) => {\n const start = stops[index]\n const end = stops[index + 1]\n\n return filled && start !== undefined && end !== undefined && end > start ? [{ start, end }] : []\n })\n}\n\n/** Inline position of a fill section: from the minimum's edge of the track. */\nexport function sliderFillStyle(\n section: { start: number; end: number },\n options: { vertical: boolean; topToBottom?: boolean; direction: 'ltr' | 'rtl' },\n): Record<string, string> {\n const size = `${section.end - section.start}%`\n const offset = `${section.start}%`\n\n if (options.vertical) {\n return options.topToBottom ? { top: offset, height: size } : { bottom: offset, height: size }\n }\n\n return options.direction === 'rtl'\n ? { right: offset, width: size }\n : { left: offset, width: size }\n}\n\n/** The pointer behaviours of a slider (`behavior()`); `tap` by default. */\nexport function sliderBehavior(schema: Pick<SliderSchema, 'behavior'>): Set<SliderBehavior> {\n return new Set(schema.behavior ?? ['tap'])\n}\n\n/** The slider's direction: `rtl()` when set, else the panel's. */\nexport function sliderDirection(\n schema: Pick<SliderSchema, 'rtl'>,\n panel: 'ltr' | 'rtl',\n): 'ltr' | 'rtl' {\n return schema.rtl === undefined || schema.rtl === null ? panel : schema.rtl ? 'rtl' : 'ltr'\n}\n\n/**\n * What the slider primitive works with: values directly, or positions (0–100) on a\n * non-linear track. `smooth` (`Behavior::SmoothSteps` while dragging) drops the step.\n */\nexport function sliderPrimitive(\n schema: SliderTrack,\n smooth = false,\n): { min: number; max: number; step: number; nonLinear: boolean } {\n if (schema.nonLinearPoints && schema.nonLinearPoints.length > 0) {\n return { min: 0, max: 100, step: smooth ? 0.01 : 0.1, nonLinear: true }\n }\n\n return {\n min: schema.min,\n max: schema.max,\n step: smooth ? (schema.max - schema.min) / 1000 || schema.step : schema.step,\n nonLinear: false,\n }\n}\n\n/** Thumb values as the primitive's values. */\nexport function sliderToPrimitive(values: number[], schema: SliderTrack): number[] {\n return sliderPrimitive(schema).nonLinear\n ? values.map((value) => sliderPercent(value, schema))\n : values\n}\n\n/** The primitive's values as thumb values (snapped unless dragging smoothly). */\nexport function sliderFromPrimitive(values: number[], schema: SliderTrack, snap = true): number[] {\n return sliderPrimitive(schema).nonLinear\n ? values.map((position) => sliderValueAt(position, schema, snap))\n : values\n}\n\ntype SliderRules = SliderTrack &\n Pick<SliderSchema, 'rangePadding' | 'behavior' | 'minDifference' | 'multiple'>\n\n/**\n * The thumb values to store after the primitive moved from `previous` to `next` (both\n * sorted): kept inside `rangePadding()`; with `fixed` every thumb moves by the same\n * amount; without `unconstrained` a thumb stops at its neighbours (`minDifference()` apart)\n * instead of passing them.\n */\nexport function sliderNextValues(\n previous: number[],\n next: number[],\n schema: SliderRules,\n): number[] {\n const [low, high] = sliderBounds(schema)\n const behavior = sliderBehavior(schema)\n const clamp = (value: number) => Math.min(high, Math.max(low, value))\n\n if (next.length !== previous.length || previous.length < 2) {\n return next.map(clamp)\n }\n\n const moved = sliderMovedThumb(previous, next)\n\n if (!moved) return next.map(clamp)\n\n const [index, value] = moved\n\n if (behavior.has('fixed')) {\n return sliderShift(previous, value - previous[index]!, schema)\n }\n\n if (behavior.has('unconstrained')) {\n return [...next].map(clamp).sort((a, b) => a - b)\n }\n\n const gap = schema.minDifference ?? 0\n const lower = index > 0 ? previous[index - 1]! + gap : low\n const upper = index < previous.length - 1 ? previous[index + 1]! - gap : high\n const result = [...previous]\n result[index] = Math.min(Math.min(high, upper), Math.max(Math.max(low, lower), value))\n\n return result\n}\n\n/**\n * Which thumb of `previous` moved, and where to, when exactly one value changed (the\n * primitive may have re-sorted the thumbs after one passed another).\n */\nfunction sliderMovedThumb(previous: number[], next: number[]): [number, number] | null {\n const remaining = [...next]\n const removed: number[] = []\n\n for (const [index, value] of previous.entries()) {\n const found = remaining.indexOf(value)\n\n if (found === -1) removed.push(index)\n else remaining.splice(found, 1)\n }\n\n if (removed.length !== 1 || remaining.length !== 1) return null\n\n return [removed[0]!, remaining[0]!]\n}\n\n/**\n * Move every thumb by `delta` (in values), keeping their distances and stopping at the\n * ends of the track (minus `rangePadding()`).\n */\nexport function sliderShift(values: number[], delta: number, schema: SliderRules): number[] {\n if (values.length === 0) return values\n\n const [low, high] = sliderBounds(schema)\n const first = Math.min(...values)\n const last = Math.max(...values)\n const allowed = Math.min(high - last, Math.max(low - first, delta))\n\n return values.map((value) => value + allowed)\n}\n\n/**\n * `Behavior::Drag`: follow a pointer that grabbed the filled range between the thumbs and\n * move them together, until the pointer is released. Positions are measured on `track`\n * in the slider's direction (bottom to top when vertical).\n */\nexport function startSliderRangeDrag(\n event: PointerEvent,\n options: {\n track: HTMLElement\n values: number[]\n schema: SliderRules\n direction: 'ltr' | 'rtl'\n vertical: boolean\n /** `vertical(isTopToBottom: true)`: the minimum is at the top. */\n topToBottom?: boolean\n onChange: (values: number[]) => void\n onEnd?: () => void\n },\n): void {\n const { track, values, schema, direction, vertical } = options\n const rect = track.getBoundingClientRect()\n const size = vertical ? rect.height : rect.width\n\n if (!(size > 0) || values.length < 2) return\n\n event.preventDefault()\n event.stopPropagation()\n\n const start = vertical ? event.clientY : event.clientX\n const percents = values.map((value) => sliderPercent(value, schema))\n\n const move = (moveEvent: PointerEvent) => {\n const current = vertical ? moveEvent.clientY : moveEvent.clientX\n let delta = ((current - start) / size) * 100\n\n if (vertical ? !options.topToBottom : direction === 'rtl') delta = -delta\n\n const [low, high] = sliderBounds(schema).map((bound) => sliderPercent(bound, schema)) as [\n number,\n number,\n ]\n const allowed = Math.min(\n high - Math.max(...percents),\n Math.max(low - Math.min(...percents), delta),\n )\n const moved = percents.map((percent) => sliderValueAt(percent + allowed, schema))\n // Snapping each thumb separately could change their distance: keep the first one's.\n options.onChange(\n sliderPrimitive(schema).nonLinear\n ? moved\n : sliderShift(values, moved[0]! - values[0]!, schema),\n )\n }\n\n const end = () => {\n window.removeEventListener('pointermove', move)\n window.removeEventListener('pointerup', end)\n window.removeEventListener('pointercancel', end)\n options.onEnd?.()\n }\n\n window.addEventListener('pointermove', move)\n window.addEventListener('pointerup', end)\n window.addEventListener('pointercancel', end)\n}\n\n/**\n * Whether a pointer press on the slider should be swallowed before the primitive sees it:\n * a press on the track when `tap` (and `snap`) are off. Thumbs always respond.\n */\nexport function sliderBlocksTap(\n target: EventTarget | null,\n schema: Pick<SliderSchema, 'behavior'>,\n): boolean {\n const behavior = sliderBehavior(schema)\n\n if (behavior.has('tap') || behavior.has('snap')) return false\n\n return !(target instanceof Element && target.closest('[role=\"slider\"]'))\n}\n\n// ---------------------------------------------------------------- CodeEditor\n\nexport interface TextEdit {\n value: string\n selectionStart: number\n selectionEnd: number\n}\n\n/**\n * Indent (or with `outdent`, unindent) the lines touched by the selection. A collapsed\n * selection without `outdent` inserts spaces at the caret.\n */\nexport function indentCode(\n value: string,\n selectionStart: number,\n selectionEnd: number,\n tabSize: number,\n outdent = false,\n): TextEdit {\n const indent = ' '.repeat(Math.max(1, tabSize))\n\n if (!outdent && selectionStart === selectionEnd) {\n return {\n value: value.slice(0, selectionStart) + indent + value.slice(selectionEnd),\n selectionStart: selectionStart + indent.length,\n selectionEnd: selectionStart + indent.length,\n }\n }\n\n const lineStart = value.lastIndexOf('\\n', selectionStart - 1) + 1\n const block = value.slice(lineStart, selectionEnd)\n const lines = block.split('\\n')\n let firstDelta = 0\n let totalDelta = 0\n\n const next = lines.map((line, index) => {\n if (outdent) {\n const removable = line.match(new RegExp(`^ {1,${indent.length}}|^\\\\t`))?.[0].length ?? 0\n\n if (index === 0) firstDelta = -removable\n totalDelta -= removable\n\n return line.slice(removable)\n }\n\n if (index === 0) firstDelta = indent.length\n totalDelta += indent.length\n\n return indent + line\n })\n\n return {\n value: value.slice(0, lineStart) + next.join('\\n') + value.slice(selectionEnd),\n selectionStart: Math.max(lineStart, selectionStart + firstDelta),\n selectionEnd: Math.max(lineStart, selectionEnd + totalDelta),\n }\n}\n\n/** Keep the indentation of the current line when Enter is pressed. */\nexport function newlineWithIndent(\n value: string,\n selectionStart: number,\n selectionEnd: number,\n): TextEdit {\n const lineStart = value.lastIndexOf('\\n', selectionStart - 1) + 1\n const indent = value.slice(lineStart, selectionStart).match(/^[ \\t]*/)?.[0] ?? ''\n const insert = `\\n${indent}`\n\n return {\n value: value.slice(0, selectionStart) + insert + value.slice(selectionEnd),\n selectionStart: selectionStart + insert.length,\n selectionEnd: selectionStart + insert.length,\n }\n}\n\n// ---------------------------------------------------------------- ModalTableSelect\n\n/** Query parameters for the picker table, prefixed with the table state identifier. */\nexport function modalTableQuery(identifier: string, query: TableQuery): Record<string, unknown> {\n const params: Record<string, unknown> = {}\n\n for (const [name, value] of Object.entries(query) as [keyof TableQuery, unknown][]) {\n if (value === null || value === undefined || value === '') continue\n\n params[tableQueryKey(identifier, name)] = value\n }\n\n return params\n}\n\n/**\n * The selection after the record `key` was toggled in the picker. Single selects keep\n * one key; multiple selects stop at `maxItems`.\n */\nexport function toggleModalTableKey(\n selected: string[],\n key: string,\n multiple: boolean,\n maxItems?: number,\n): string[] {\n if (selected.includes(key)) {\n return selected.filter((candidate) => candidate !== key)\n }\n\n if (!multiple) return [key]\n\n if (maxItems != null && selected.length >= maxItems) return selected\n\n return [...selected, key]\n}\n\n/**\n * The store options of a picker's filters form. Searchable selects among the filters load\n * their options through the owning form's endpoint, addressed by the ModalTableSelect's\n * `path` (`pickerFilters`), so the server resolves the picker table (and its tenant-scoped\n * relationships) the same way it does for the table itself.\n */\nexport function pickerFilterFormOptions(owner: FormStore, path: string): FormStoreOptions {\n return {\n token: owner.token,\n component: owner.component,\n record: owner.record,\n optionsUrl: owner.optionsUrl,\n params: () => ({\n ...owner.requestContext(),\n pickerFilters: path,\n ownerState: owner.state.values,\n }),\n }\n}\n\n// ---------------------------------------------------------------- FileUpload\n\n/**\n * The stored or temporary paths in a FileUpload state value. The server always holds a\n * list (a single field's is at most one long) and accepts one path too, so a value from\n * either side normalises the same way.\n */\nexport function fileUploadPaths(value: unknown): string[] {\n if (Array.isArray(value)) {\n return value.filter((path): path is string => typeof path === 'string' && path !== '')\n }\n\n return typeof value === 'string' && value !== '' ? [value] : []\n}\n\n/** The picker's `accept`: the accepted types plus the extensions of `mimeTypeMap()`. */\nexport function fileUploadAccept(\n schema: Pick<FileUploadSchema, 'acceptedFileTypes' | 'mimeTypeMap'>,\n): string | undefined {\n if (!schema.acceptedFileTypes || schema.acceptedFileTypes.length === 0) return undefined\n\n const mapped = Object.entries(schema.mimeTypeMap ?? {})\n .filter(([, type]) => schema.acceptedFileTypes!.includes(type))\n .map(([extension]) => `.${extension}`)\n\n return [...new Set([...schema.acceptedFileTypes, ...mapped])].join(',')\n}\n\n/** A `'2:1'` / `'16/9'` ratio as a CSS `aspect-ratio` value. */\nexport function cssAspectRatio(ratio: string | null | undefined): string | undefined {\n const [width, height] = (ratio ?? '').split(/[:/]/).map((part) => Number(part.trim()))\n\n return width && height && width > 0 && height > 0 ? `${width} / ${height}` : undefined\n}\n\n/**\n * The field's paths after an upload finished: the new key after the existing files, or\n * before them with `appendFiles(false)`, keeping at most `maxFiles`.\n */\nexport function insertUploadedFile(\n paths: string[],\n key: string,\n schema: Pick<FileUploadSchema, 'appendFiles'>,\n maxFiles: number,\n): string[] {\n return schema.appendFiles === false\n ? [key, ...paths].slice(0, maxFiles)\n : [...paths, key].slice(-maxFiles)\n}\n\n/** Where a file's \"open\" and \"download\" buttons link to. */\nexport function uploadedFileLinks(file: Pick<UploadedFile, 'url' | 'openUrl' | 'downloadUrl'>): {\n open?: string\n download?: string\n} {\n return { open: file.openUrl ?? file.url, download: file.downloadUrl ?? file.url }\n}\n\n/**\n * A queue that runs at most `limit` tasks at once, in the order they were added\n * (`maxParallelUploads()`).\n */\nexport function concurrencyLimiter(limit: number): <T>(task: () => Promise<T>) => Promise<T> {\n const max = Math.max(1, Math.floor(limit) || 1)\n const waiting: Array<() => void> = []\n let running = 0\n\n const release = () => {\n running--\n waiting.shift()?.()\n }\n\n return <T>(task: () => Promise<T>): Promise<T> =>\n new Promise<T>((resolve, reject) => {\n const start = () => {\n running++\n task().then(resolve, reject).finally(release)\n }\n\n if (running < max) start()\n else waiting.push(start)\n })\n}\n\n/** The selected record keys in a ModalTableSelect state value. */\nexport function modalTableSelected(value: unknown, multiple: boolean): string[] {\n if (multiple) return Array.isArray(value) ? value.map(String) : []\n\n return value === null || value === undefined || value === '' ? [] : [String(value)]\n}\n\nexport interface ModalTablePaging {\n page: number\n hasPrevious: boolean\n hasNext: boolean\n from: number\n to: number\n total?: number\n}\n\n/** Page-by-page navigation for the picker table, whatever the table's pagination mode. */\nexport function modalTablePaging(\n pagination: AnyPaginationSchema | null | undefined,\n): ModalTablePaging | null {\n if (!pagination) return null\n\n const page = 'currentPage' in pagination ? pagination.currentPage : 1\n const hasNext =\n 'lastPage' in pagination\n ? page < pagination.lastPage\n : 'currentPage' in pagination && 'hasMorePages' in pagination && pagination.hasMorePages\n\n return {\n page,\n hasPrevious: 'currentPage' in pagination && page > 1,\n hasNext: Boolean(hasNext),\n from: pagination.from,\n to: pagination.to,\n total: 'total' in pagination ? pagination.total : undefined,\n }\n}\n"],"mappings":"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"}
|
package/dist/assets.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"assets.d.ts","names":[],"sources":["../src/assets.ts"],"mappings":";;;;;;
|
|
1
|
+
{"version":3,"file":"assets.d.ts","names":[],"sources":["../src/assets.ts"],"mappings":";;;;;;QAOQ;YACI;;IAER,cAAc;;;;;;;wBAUF,UAAU,kCAAkC;;;;;wBAQ5C,WACd,gCACA;EAAW;IACV;;;;wBA+Da,iBAAiB;wBACjB,cAAc,aAAa,cAAc"}
|
package/dist/assets.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { documentNonce } from "./csp.js";
|
|
1
2
|
//#region src/assets.ts
|
|
2
3
|
const pending = /* @__PURE__ */ new Map();
|
|
3
4
|
/**
|
|
@@ -35,7 +36,7 @@ function load(url, kind) {
|
|
|
35
36
|
if (kind === "module") element.type = "module";
|
|
36
37
|
}
|
|
37
38
|
element.setAttribute("data-alfakit-asset", "on-request");
|
|
38
|
-
const nonce =
|
|
39
|
+
const nonce = documentNonce();
|
|
39
40
|
if (nonce) element.nonce = nonce;
|
|
40
41
|
element.addEventListener("load", () => resolve(), { once: true });
|
|
41
42
|
element.addEventListener("error", () => {
|
package/dist/assets.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"assets.js","names":[],"sources":["../src/assets.ts"],"sourcesContent":["/**\n * Plugin assets on the client: stylesheets and scripts registered with\n * `->loadedOnRequest()` (loaded by the component that needs them) and the data registered\n * with `Assets::registerScriptData()`.\n */\n\ndeclare global {\n interface Window {\n /** Data from `Assets::registerScriptData()`. */\n alfakitData?: Record<string, unknown>\n }\n}\n\nconst pending = new Map<string, Promise<void>>()\n\n/**\n * Add a stylesheet to `<head>` once (`Assets::getStyleHref('id', 'acme/blog')`, passed in\n * the component's props). Resolves when it has loaded; repeated calls share the request.\n */\nexport function loadStyle(href: string | null | undefined): Promise<void> {\n return load(href, 'style')\n}\n\n/**\n * Add a script to `<head>` once (`Assets::getScriptSrc('id', 'acme/blog')`). Resolves when\n * it has run; pass `module: true` for an ES module.\n */\nexport function loadScript(\n src: string | null | undefined,\n options: { module?: boolean } = {},\n): Promise<void> {\n return load(src, options.module ? 'module' : 'script')\n}\n\nfunction load(url: string | null | undefined, kind: 'style' | 'script' | 'module'): Promise<void> {\n if (!url || typeof document === 'undefined') return Promise.resolve()\n\n const key = `${kind === 'style' ? 'style' : 'script'}:${url}`\n const existing = pending.get(key)\n\n if (existing) return existing\n\n const selector =\n kind === 'style'\n ? `link[rel=\"stylesheet\"][href=\"${CSS.escape(url)}\"]`\n : `script[src=\"${CSS.escape(url)}\"]`\n\n if (document.head.querySelector(selector)) {\n const ready = Promise.resolve()\n pending.set(key, ready)\n\n return ready\n }\n\n const promise = new Promise<void>((resolve, reject) => {\n const element =\n kind === 'style' ? document.createElement('link') : document.createElement('script')\n\n if (element instanceof HTMLLinkElement) {\n element.rel = 'stylesheet'\n element.href = url\n } else {\n element.src = url\n if (kind === 'module') element.type = 'module'\n }\n\n element.setAttribute('data-alfakit-asset', 'on-request')\n\n const nonce =
|
|
1
|
+
{"version":3,"file":"assets.js","names":[],"sources":["../src/assets.ts"],"sourcesContent":["import { documentNonce } from './csp'\n/**\n * Plugin assets on the client: stylesheets and scripts registered with\n * `->loadedOnRequest()` (loaded by the component that needs them) and the data registered\n * with `Assets::registerScriptData()`.\n */\n\ndeclare global {\n interface Window {\n /** Data from `Assets::registerScriptData()`. */\n alfakitData?: Record<string, unknown>\n }\n}\n\nconst pending = new Map<string, Promise<void>>()\n\n/**\n * Add a stylesheet to `<head>` once (`Assets::getStyleHref('id', 'acme/blog')`, passed in\n * the component's props). Resolves when it has loaded; repeated calls share the request.\n */\nexport function loadStyle(href: string | null | undefined): Promise<void> {\n return load(href, 'style')\n}\n\n/**\n * Add a script to `<head>` once (`Assets::getScriptSrc('id', 'acme/blog')`). Resolves when\n * it has run; pass `module: true` for an ES module.\n */\nexport function loadScript(\n src: string | null | undefined,\n options: { module?: boolean } = {},\n): Promise<void> {\n return load(src, options.module ? 'module' : 'script')\n}\n\nfunction load(url: string | null | undefined, kind: 'style' | 'script' | 'module'): Promise<void> {\n if (!url || typeof document === 'undefined') return Promise.resolve()\n\n const key = `${kind === 'style' ? 'style' : 'script'}:${url}`\n const existing = pending.get(key)\n\n if (existing) return existing\n\n const selector =\n kind === 'style'\n ? `link[rel=\"stylesheet\"][href=\"${CSS.escape(url)}\"]`\n : `script[src=\"${CSS.escape(url)}\"]`\n\n if (document.head.querySelector(selector)) {\n const ready = Promise.resolve()\n pending.set(key, ready)\n\n return ready\n }\n\n const promise = new Promise<void>((resolve, reject) => {\n const element =\n kind === 'style' ? document.createElement('link') : document.createElement('script')\n\n if (element instanceof HTMLLinkElement) {\n element.rel = 'stylesheet'\n element.href = url\n } else {\n element.src = url\n if (kind === 'module') element.type = 'module'\n }\n\n element.setAttribute('data-alfakit-asset', 'on-request')\n\n const nonce = documentNonce()\n if (nonce) element.nonce = nonce\n\n element.addEventListener('load', () => resolve(), { once: true })\n element.addEventListener(\n 'error',\n () => {\n pending.delete(key)\n element.remove()\n reject(new Error(`Could not load ${url}`))\n },\n { once: true },\n )\n\n document.head.appendChild(element)\n })\n\n pending.set(key, promise)\n\n return promise\n}\n\n/**\n * Data registered with `Assets::registerScriptData()`: everything, or one key.\n */\nexport function getScriptData(): Record<string, unknown>\nexport function getScriptData<T = unknown>(key: string): T | undefined\nexport function getScriptData(key?: string): unknown {\n const data = typeof window === 'undefined' ? {} : (window.alfakitData ?? {})\n\n return key === undefined ? data : data[key]\n}\n"],"mappings":";;AAcA,MAAM,0BAAU,IAAI,IAA2B;;;;;AAM/C,SAAgB,UAAU,MAAgD;CACxE,OAAO,KAAK,MAAM,OAAO;AAC3B;;;;;AAMA,SAAgB,WACd,KACA,UAAgC,CAAC,GAClB;CACf,OAAO,KAAK,KAAK,QAAQ,SAAS,WAAW,QAAQ;AACvD;AAEA,SAAS,KAAK,KAAgC,MAAoD;CAChG,IAAI,CAAC,OAAO,OAAO,aAAa,aAAa,OAAO,QAAQ,QAAQ;CAEpE,MAAM,MAAM,GAAG,SAAS,UAAU,UAAU,SAAS,GAAG;CACxD,MAAM,WAAW,QAAQ,IAAI,GAAG;CAEhC,IAAI,UAAU,OAAO;CAErB,MAAM,WACJ,SAAS,UACL,gCAAgC,IAAI,OAAO,GAAG,EAAE,MAChD,eAAe,IAAI,OAAO,GAAG,EAAE;CAErC,IAAI,SAAS,KAAK,cAAc,QAAQ,GAAG;EACzC,MAAM,QAAQ,QAAQ,QAAQ;EAC9B,QAAQ,IAAI,KAAK,KAAK;EAEtB,OAAO;CACT;CAEA,MAAM,UAAU,IAAI,SAAe,SAAS,WAAW;EACrD,MAAM,UACJ,SAAS,UAAU,SAAS,cAAc,MAAM,IAAI,SAAS,cAAc,QAAQ;EAErF,IAAI,mBAAmB,iBAAiB;GACtC,QAAQ,MAAM;GACd,QAAQ,OAAO;EACjB,OAAO;GACL,QAAQ,MAAM;GACd,IAAI,SAAS,UAAU,QAAQ,OAAO;EACxC;EAEA,QAAQ,aAAa,sBAAsB,YAAY;EAEvD,MAAM,QAAQ,cAAc;EAC5B,IAAI,OAAO,QAAQ,QAAQ;EAE3B,QAAQ,iBAAiB,cAAc,QAAQ,GAAG,EAAE,MAAM,KAAK,CAAC;EAChE,QAAQ,iBACN,eACM;GACJ,QAAQ,OAAO,GAAG;GAClB,QAAQ,OAAO;GACf,uBAAO,IAAI,MAAM,kBAAkB,KAAK,CAAC;EAC3C,GACA,EAAE,MAAM,KAAK,CACf;EAEA,SAAS,KAAK,YAAY,OAAO;CACnC,CAAC;CAED,QAAQ,IAAI,KAAK,OAAO;CAExB,OAAO;AACT;AAOA,SAAgB,cAAc,KAAuB;CACnD,MAAM,OAAO,OAAO,WAAW,cAAc,CAAC,IAAK,OAAO,eAAe,CAAC;CAE1E,OAAO,QAAQ,KAAA,IAAY,OAAO,KAAK;AACzC"}
|
package/dist/csp.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
//#region src/csp.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* The page's Content Security Policy nonce, read from the server-rendered tags that carry it
|
|
4
|
+
* (the root view's inline theme `<script>`/`<style>`, Vite's tags). Elements AlfaKit and
|
|
5
|
+
* Inertia add at runtime (the progress bar's `<style>`, on-request assets) take it, so a
|
|
6
|
+
* nonce-based policy (docs: advanced/security § Content Security Policy) allows them.
|
|
7
|
+
* `undefined` on the server and on pages without a nonce.
|
|
8
|
+
*/
|
|
9
|
+
export declare function documentNonce(): string | undefined;
|
|
10
|
+
//#endregion
|
|
11
|
+
//# sourceMappingURL=csp.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"csp.d.ts","names":[],"sources":["../src/csp.ts"],"mappings":";;;;;;;;wBAOgB"}
|
package/dist/csp.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
//#region src/csp.ts
|
|
2
|
+
/**
|
|
3
|
+
* The page's Content Security Policy nonce, read from the server-rendered tags that carry it
|
|
4
|
+
* (the root view's inline theme `<script>`/`<style>`, Vite's tags). Elements AlfaKit and
|
|
5
|
+
* Inertia add at runtime (the progress bar's `<style>`, on-request assets) take it, so a
|
|
6
|
+
* nonce-based policy (docs: advanced/security § Content Security Policy) allows them.
|
|
7
|
+
* `undefined` on the server and on pages without a nonce.
|
|
8
|
+
*/
|
|
9
|
+
function documentNonce() {
|
|
10
|
+
if (typeof document === "undefined") return void 0;
|
|
11
|
+
return document.querySelector("script[nonce], style[nonce]")?.nonce || void 0;
|
|
12
|
+
}
|
|
13
|
+
//#endregion
|
|
14
|
+
export { documentNonce };
|
|
15
|
+
|
|
16
|
+
//# sourceMappingURL=csp.js.map
|
package/dist/csp.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"csp.js","names":[],"sources":["../src/csp.ts"],"sourcesContent":["/**\n * The page's Content Security Policy nonce, read from the server-rendered tags that carry it\n * (the root view's inline theme `<script>`/`<style>`, Vite's tags). Elements AlfaKit and\n * Inertia add at runtime (the progress bar's `<style>`, on-request assets) take it, so a\n * nonce-based policy (docs: advanced/security § Content Security Policy) allows them.\n * `undefined` on the server and on pages without a nonce.\n */\nexport function documentNonce(): string | undefined {\n if (typeof document === 'undefined') return undefined\n\n return document.querySelector<HTMLElement>('script[nonce], style[nonce]')?.nonce || undefined\n}\n"],"mappings":";;;;;;;;AAOA,SAAgB,gBAAoC;CAClD,IAAI,OAAO,aAAa,aAAa,OAAO,KAAA;CAE5C,OAAO,SAAS,cAA2B,6BAA6B,CAAC,EAAE,SAAS,KAAA;AACtF"}
|