@alfakit/core 0.1.1 → 0.1.6
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/components.css +347 -0
- package/dist/actions.d.ts +9 -0
- package/dist/actions.d.ts.map +1 -1
- package/dist/actions.js +31 -5
- 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/cms-live-preview.d.ts.map +1 -1
- package/dist/cms-live-preview.js +7 -1
- package/dist/cms-live-preview.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/file-size.js +1 -1
- package/dist/file-size.js.map +1 -1
- package/dist/form.d.ts +53 -1
- package/dist/form.d.ts.map +1 -1
- package/dist/form.js +96 -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 +12 -11
- package/dist/index.js +10 -9
- 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 +10 -2
- package/dist/page-actions.d.ts.map +1 -1
- package/dist/page-actions.js +15 -5
- 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 +4 -0
- package/dist/schema/cms.d.ts.map +1 -1
- package/dist/schema/components.d.ts +5 -0
- package/dist/schema/components.d.ts.map +1 -1
- package/dist/schema/components.js.map +1 -1
- package/dist/schema/index.d.ts +3 -3
- package/dist/schema/panel.d.ts +31 -0
- package/dist/schema/panel.d.ts.map +1 -1
- package/dist/schema/panel.js.map +1 -1
- package/dist/schema/table-grouping.d.ts +5 -1
- package/dist/schema/table-grouping.d.ts.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 +5 -3
- package/themes.css +8 -335
|
@@ -1,11 +1,14 @@
|
|
|
1
|
+
import { AiCopilotSchema } from "./schema/ai.js";
|
|
2
|
+
import { StandaloneComponentSchema } from "./schema/standalone.js";
|
|
1
3
|
import { PageSchema, PanelSchema, StarterKitNavigationSchema, StarterKitWidgetsSchema } from "./schema/panel.js";
|
|
2
4
|
//#region src/starter-kit-widgets.d.ts
|
|
3
5
|
/**
|
|
4
6
|
* The drop-in header components of starter kit mode: `<AlfakitSearch />` (the command
|
|
5
|
-
* palette and global search), `<AlfakitNotifications />` (the database notifications bell)
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
7
|
+
* palette and global search), `<AlfakitNotifications />` (the database notifications bell),
|
|
8
|
+
* `<AlfakitTenantSwitcher />` and `<AlfakitCopilot />` (the AI plugin's copilot). They render
|
|
9
|
+
* on every page of the host application, from the `widgets` of the shared
|
|
10
|
+
* `alfakitNavigation` prop; their code and the panel data (translations, endpoints, the
|
|
11
|
+
* palette, realtime) load the first time one of them renders.
|
|
9
12
|
* Shared by the three renderers.
|
|
10
13
|
*/
|
|
11
14
|
/** One starter kit panel's navigation with its header components' data. */
|
|
@@ -14,10 +17,13 @@ export interface StarterKitWidgets {
|
|
|
14
17
|
widgets: StarterKitWidgetsSchema;
|
|
15
18
|
}
|
|
16
19
|
/**
|
|
17
|
-
* The header components' data of a `starterKit()` panel
|
|
18
|
-
* `panelId
|
|
20
|
+
* The header components' data of a `starterKit()` panel, or null for guests and pages of
|
|
21
|
+
* other panels. Without `panelId`: the open AlfaKit page's panel, else the first one with
|
|
22
|
+
* any; with `part` (`'tenancy'` for the tenant switcher), the first panel that has that
|
|
23
|
+
* part when the preferred one hasn't, so a team workspace on a second panel still gets its
|
|
24
|
+
* switcher on the host's own pages.
|
|
19
25
|
*/
|
|
20
|
-
export declare function starterKitWidgets(props: unknown, panelId?: string): StarterKitWidgets | null;
|
|
26
|
+
export declare function starterKitWidgets(props: unknown, panelId?: string, part?: keyof StarterKitWidgetsSchema): StarterKitWidgets | null;
|
|
21
27
|
/** Whether the open page is one of the panel's own (it carries the `alfakit` prop). */
|
|
22
28
|
export declare function isAlfakitPage(props: unknown): boolean;
|
|
23
29
|
/** Load (once) the panel data the header components render with. */
|
|
@@ -50,5 +56,32 @@ export declare function requestStarterKitSearch(): void;
|
|
|
50
56
|
* true when it did.
|
|
51
57
|
*/
|
|
52
58
|
export declare function openRequestedStarterKitSearch(): boolean;
|
|
59
|
+
/** The copilot's shortcut id (the loaded `ai:copilot` component registers the same one). */
|
|
60
|
+
export declare const STARTER_KIT_COPILOT_SHORTCUT = "ai.copilot";
|
|
61
|
+
/**
|
|
62
|
+
* Register the copilot's key bindings (`mod+j`) before its code has loaded: pressing them
|
|
63
|
+
* (or clicking the button) records the request, and the copilot opens once it has loaded.
|
|
64
|
+
* The loaded copilot registers the same id, so it replaces this registration.
|
|
65
|
+
*/
|
|
66
|
+
export declare function registerStarterKitCopilotShortcut(keys: string[], label: string, onRequest?: () => void): () => void;
|
|
67
|
+
/** Ask for the copilot to open as soon as it has loaded. */
|
|
68
|
+
export declare function requestStarterKitCopilot(): void;
|
|
69
|
+
/** Whether the copilot was asked for before it loaded (and forget the request). */
|
|
70
|
+
export declare function takeRequestedStarterKitCopilot(): boolean;
|
|
71
|
+
/**
|
|
72
|
+
* The copilot component in the loaded panel data: the panel's own pages show it in the
|
|
73
|
+
* `topbar.end` slot, which starter kit hosts don't render. Null without the AI plugin's
|
|
74
|
+
* copilot (or when the user may not use it).
|
|
75
|
+
*/
|
|
76
|
+
export declare function starterKitCopilot(panel: PanelSchema): AiCopilotSchema | null;
|
|
77
|
+
/**
|
|
78
|
+
* The dashboard widgets of a `starterKit()` panel on the host's dashboard
|
|
79
|
+
* (`<AlfakitWidgets />`, the `alfakitWidgets` prop): a standalone widgets payload bound to
|
|
80
|
+
* the panel, the first panel's unless `panelId` is given. Null on other pages, for guests,
|
|
81
|
+
* users who may not open the panel and panels without widgets.
|
|
82
|
+
*/
|
|
83
|
+
export declare function starterKitDashboardWidgets(props: unknown, panelId?: string): StandaloneComponentSchema | null;
|
|
84
|
+
/** How many widgets a dashboard widgets payload has (placeholders before its code loads). */
|
|
85
|
+
export declare function standaloneWidgetCount(component: StandaloneComponentSchema): number;
|
|
53
86
|
//#endregion
|
|
54
87
|
//# sourceMappingURL=starter-kit-widgets.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"starter-kit-widgets.d.ts","names":[],"sources":["../src/starter-kit-widgets.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"starter-kit-widgets.d.ts","names":[],"sources":["../src/starter-kit-widgets.ts"],"mappings":";;;;;;;;;;;;;;iBAyBiB;EACf,YAAY;EACZ,SAAS;;;;;;;;;wBAUK,kBACd,gBACA,kBACA,aAAa,0BACZ;;wBAaa,cAAc;;wBAYd,oBAAoB,mBAAmB,QAAQ;;wBAqC/C,sBAAsB,mBAAmB;;wBAKzC;;;;;;;;wBAYA,uBAAuB,MAAM,aAAa,QAAQ,oBAAoB;;wBAyBtE,yBAAyB;;;;;;;wBAczB,iCACd,gBACA,eACA;;wBAgBc;;;;;wBAQA;;qBAWH;;;;;;wBASG,kCACd,gBACA,eACA;;wBAkBc;;wBAKA;;;;;;wBAaA,kBAAkB,OAAO,cAAc;;;;;;;wBAkBvC,2BACd,gBACA,mBACC;;wBAaa,sBAAsB,WAAW"}
|
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
import { requestJson } from "./http.js";
|
|
2
2
|
import { SHORTCUT_IDS, shortcuts } from "./shortcuts.js";
|
|
3
|
-
import { starterKitNavigations } from "./starter-kit.js";
|
|
3
|
+
import { preferCurrentStarterKitPanel, starterKitNavigations } from "./starter-kit.js";
|
|
4
4
|
//#region src/starter-kit-widgets.ts
|
|
5
5
|
/**
|
|
6
|
-
* The header components' data of a `starterKit()` panel
|
|
7
|
-
* `panelId
|
|
6
|
+
* The header components' data of a `starterKit()` panel, or null for guests and pages of
|
|
7
|
+
* other panels. Without `panelId`: the open AlfaKit page's panel, else the first one with
|
|
8
|
+
* any; with `part` (`'tenancy'` for the tenant switcher), the first panel that has that
|
|
9
|
+
* part when the preferred one hasn't, so a team workspace on a second panel still gets its
|
|
10
|
+
* switcher on the host's own pages.
|
|
8
11
|
*/
|
|
9
|
-
function starterKitWidgets(props, panelId) {
|
|
10
|
-
const
|
|
12
|
+
function starterKitWidgets(props, panelId, part) {
|
|
13
|
+
const entries = preferCurrentStarterKitPanel(starterKitNavigations(props), props).filter((entry) => entry.widgets !== void 0 && (panelId === void 0 || entry.id === panelId));
|
|
14
|
+
const navigation = (part !== void 0 && panelId === void 0 ? entries.find((entry) => entry.widgets?.[part] !== void 0) : void 0) ?? entries[0];
|
|
11
15
|
return navigation?.widgets ? {
|
|
12
16
|
navigation,
|
|
13
17
|
widgets: navigation.widgets
|
|
@@ -24,9 +28,10 @@ function loadStarterKitPanel(endpoint) {
|
|
|
24
28
|
let request = requests.get(endpoint);
|
|
25
29
|
if (!request) {
|
|
26
30
|
request = requestJson(endpoint).then((panel) => {
|
|
31
|
+
if (!isPanelData(panel)) throw new Error(`The starter kit endpoint ${endpoint} did not answer the panel data.`);
|
|
27
32
|
panels.set(endpoint, panel);
|
|
28
33
|
return panel;
|
|
29
|
-
}
|
|
34
|
+
}).catch((error) => {
|
|
30
35
|
requests.delete(endpoint);
|
|
31
36
|
throw error;
|
|
32
37
|
});
|
|
@@ -34,6 +39,9 @@ function loadStarterKitPanel(endpoint) {
|
|
|
34
39
|
}
|
|
35
40
|
return request;
|
|
36
41
|
}
|
|
42
|
+
function isPanelData(value) {
|
|
43
|
+
return typeof value === "object" && value !== null && typeof value.endpoints === "object" && value.endpoints !== null;
|
|
44
|
+
}
|
|
37
45
|
/** The panel data once loaded, or null (so remounts render straight away). */
|
|
38
46
|
function loadedStarterKitPanel(endpoint) {
|
|
39
47
|
return panels.get(endpoint) ?? null;
|
|
@@ -125,7 +133,63 @@ function openRequestedStarterKitSearch() {
|
|
|
125
133
|
searchRequested = false;
|
|
126
134
|
return shortcuts.trigger(SHORTCUT_IDS.palette);
|
|
127
135
|
}
|
|
136
|
+
/** The copilot's shortcut id (the loaded `ai:copilot` component registers the same one). */
|
|
137
|
+
const STARTER_KIT_COPILOT_SHORTCUT = "ai.copilot";
|
|
138
|
+
let copilotRequested = false;
|
|
139
|
+
/**
|
|
140
|
+
* Register the copilot's key bindings (`mod+j`) before its code has loaded: pressing them
|
|
141
|
+
* (or clicking the button) records the request, and the copilot opens once it has loaded.
|
|
142
|
+
* The loaded copilot registers the same id, so it replaces this registration.
|
|
143
|
+
*/
|
|
144
|
+
function registerStarterKitCopilotShortcut(keys, label, onRequest) {
|
|
145
|
+
if (keys.length === 0) return () => {};
|
|
146
|
+
return shortcuts.register({
|
|
147
|
+
id: STARTER_KIT_COPILOT_SHORTCUT,
|
|
148
|
+
keys,
|
|
149
|
+
label,
|
|
150
|
+
group: "general",
|
|
151
|
+
allowInInputs: true,
|
|
152
|
+
handler: () => {
|
|
153
|
+
requestStarterKitCopilot();
|
|
154
|
+
onRequest?.();
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
/** Ask for the copilot to open as soon as it has loaded. */
|
|
159
|
+
function requestStarterKitCopilot() {
|
|
160
|
+
copilotRequested = true;
|
|
161
|
+
}
|
|
162
|
+
/** Whether the copilot was asked for before it loaded (and forget the request). */
|
|
163
|
+
function takeRequestedStarterKitCopilot() {
|
|
164
|
+
const requested = copilotRequested;
|
|
165
|
+
copilotRequested = false;
|
|
166
|
+
return requested;
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* The copilot component in the loaded panel data: the panel's own pages show it in the
|
|
170
|
+
* `topbar.end` slot, which starter kit hosts don't render. Null without the AI plugin's
|
|
171
|
+
* copilot (or when the user may not use it).
|
|
172
|
+
*/
|
|
173
|
+
function starterKitCopilot(panel) {
|
|
174
|
+
for (const item of panel.slots?.["topbar.end"] ?? []) if (item.type === "component" && item.component.type === "ai:copilot") return item.component;
|
|
175
|
+
return null;
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* The dashboard widgets of a `starterKit()` panel on the host's dashboard
|
|
179
|
+
* (`<AlfakitWidgets />`, the `alfakitWidgets` prop): a standalone widgets payload bound to
|
|
180
|
+
* the panel, the first panel's unless `panelId` is given. Null on other pages, for guests,
|
|
181
|
+
* users who may not open the panel and panels without widgets.
|
|
182
|
+
*/
|
|
183
|
+
function starterKitDashboardWidgets(props, panelId) {
|
|
184
|
+
const value = props?.alfakitWidgets;
|
|
185
|
+
if (!Array.isArray(value)) return null;
|
|
186
|
+
return value.find((item) => panelId === void 0 || item.id === panelId)?.component ?? null;
|
|
187
|
+
}
|
|
188
|
+
/** How many widgets a dashboard widgets payload has (placeholders before its code loads). */
|
|
189
|
+
function standaloneWidgetCount(component) {
|
|
190
|
+
return component.content.reduce((total, block) => total + (block.type === "Widgets" ? block.widgets.length : 0), 0);
|
|
191
|
+
}
|
|
128
192
|
//#endregion
|
|
129
|
-
export { isAlfakitPage, loadStarterKitPanel, loadedStarterKitPanel, openRequestedStarterKitSearch, registerStarterKitSearchShortcut, requestStarterKitSearch, resetStarterKitPanels, starterKitWidgets, starterKitWidgetsPage, starterKitWidgetsPanel };
|
|
193
|
+
export { STARTER_KIT_COPILOT_SHORTCUT, isAlfakitPage, loadStarterKitPanel, loadedStarterKitPanel, openRequestedStarterKitSearch, registerStarterKitCopilotShortcut, registerStarterKitSearchShortcut, requestStarterKitCopilot, requestStarterKitSearch, resetStarterKitPanels, standaloneWidgetCount, starterKitCopilot, starterKitDashboardWidgets, starterKitWidgets, starterKitWidgetsPage, starterKitWidgetsPanel, takeRequestedStarterKitCopilot };
|
|
130
194
|
|
|
131
195
|
//# sourceMappingURL=starter-kit-widgets.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"starter-kit-widgets.js","names":[],"sources":["../src/starter-kit-widgets.ts"],"sourcesContent":["import { requestJson } from './http'\nimport type {\n PageSchema,\n PanelSchema,\n StarterKitNavigationSchema,\n StarterKitWidgetsSchema,\n} from './schema/panel'\nimport { SHORTCUT_IDS, shortcuts } from './shortcuts'\nimport { starterKitNavigations } from './starter-kit'\n\n/**\n * The drop-in header components of starter kit mode: `<AlfakitSearch />` (the command\n * palette and global search), `<AlfakitNotifications />` (the database notifications bell)\n * and `<AlfakitTenantSwitcher />`. They render on every page of the host application, from\n * the `widgets` of the shared `alfakitNavigation` prop; their code and the panel data\n * (translations, endpoints, the palette, realtime) load the first time one of them renders.\n * Shared by the three renderers.\n */\n\n/** One starter kit panel's navigation with its header components' data. */\nexport interface StarterKitWidgets {\n navigation: StarterKitNavigationSchema\n widgets: StarterKitWidgetsSchema\n}\n\n/**\n * The header components' data of a `starterKit()` panel (the first one with any unless\n * `panelId` is given), or null for guests and pages of other panels.\n */\nexport function starterKitWidgets(props: unknown, panelId?: string): StarterKitWidgets | null {\n const navigation = starterKitNavigations(props).find(\n (entry) => entry.widgets !== undefined && (panelId === undefined || entry.id === panelId),\n )\n\n return navigation?.widgets ? { navigation, widgets: navigation.widgets } : null\n}\n\n/** Whether the open page is one of the panel's own (it carries the `alfakit` prop). */\nexport function isAlfakitPage(props: unknown): boolean {\n return (props as { alfakit?: unknown } | null | undefined)?.alfakit !== undefined\n}\n\n// ---------------------------------------------------------------- Panel data\n\n// Loaded once per endpoint (the tenant-scoped one inside a tenant) for the browser session;\n// Inertia visits keep it, a full page load starts over.\nconst requests = new Map<string, Promise<PanelSchema>>()\nconst panels = new Map<string, PanelSchema>()\n\n/** Load (once) the panel data the header components render with. */\nexport function loadStarterKitPanel(endpoint: string): Promise<PanelSchema> {\n let request = requests.get(endpoint)\n\n if (!request) {\n request = requestJson<PanelSchema>(endpoint).then(\n (panel) => {\n panels.set(endpoint, panel)\n\n return panel\n },\n (error: unknown) => {\n // A failed request (offline, expired session) is retried by the next render.\n requests.delete(endpoint)\n throw error\n },\n )\n requests.set(endpoint, request)\n }\n\n return request\n}\n\n/** The panel data once loaded, or null (so remounts render straight away). */\nexport function loadedStarterKitPanel(endpoint: string): PanelSchema | null {\n return panels.get(endpoint) ?? null\n}\n\n/** @internal Forget the loaded panel data (tests). */\nexport function resetStarterKitPanels(): void {\n requests.clear()\n panels.clear()\n}\n\n/**\n * The panel the header components render with: the loaded panel data with the fresh\n * shared prop (navigation, unread count, tenants). Outside Alfakit's own chrome the host\n * owns the colour scheme and there is no shortcuts overlay, and panel commands and page\n * actions need an Alfakit page (its token and modals), so the palette lists search\n * results, navigation, \"create\" links and recent records.\n */\nexport function starterKitWidgetsPanel(base: PanelSchema, source: StarterKitWidgets): PanelSchema {\n const { navigation, widgets } = source\n const { databaseNotifications, tenancy, globalSearch, commandPalette } = base\n\n return {\n ...base,\n navigation: navigation.groups,\n darkMode: { enabled: false, forced: false, switcher: false },\n keyboardShortcuts: false,\n globalSearch: globalSearch && { ...globalSearch, position: undefined },\n commandPalette: commandPalette && { ...commandPalette, commands: [] },\n databaseNotifications: databaseNotifications && {\n ...databaseNotifications,\n unreadCount: widgets.notifications?.unreadCount ?? databaseNotifications.unreadCount,\n position: undefined,\n },\n tenancy: tenancy && {\n ...tenancy,\n current: widgets.tenancy ? (widgets.tenancy.current ?? null) : tenancy.current,\n tenants: widgets.tenancy?.tenants ?? tenancy.tenants,\n },\n }\n}\n\n/** The `page` the header components render with: none of the panel's (`usePageSchema()`). */\nexport function starterKitWidgetsPage(): PageSchema {\n return { class: '', layout: 'app', title: '', content: [], token: '' }\n}\n\n// ---------------------------------------------------------------- Search shortcut\n\nlet searchRequested = false\n\n/**\n * Register the palette's key bindings before its code has loaded: pressing them (or\n * clicking the field) records the request, and the palette opens once it has loaded. It\n * uses the palette's shortcut id, so the loaded palette replaces this registration and\n * the keys are bound once, however many search components the page renders.\n */\nexport function registerStarterKitSearchShortcut(\n keys: string[],\n label: string,\n onRequest: () => void,\n): () => void {\n return shortcuts.register({\n id: SHORTCUT_IDS.palette,\n keys,\n label,\n group: 'general',\n essential: true,\n handler: () => {\n requestStarterKitSearch()\n onRequest()\n },\n })\n}\n\n/** Ask for the palette to open as soon as it has loaded. */\nexport function requestStarterKitSearch(): void {\n searchRequested = true\n}\n\n/**\n * Open the loaded palette when it was asked for before it loaded (call once it mounted):\n * true when it did.\n */\nexport function openRequestedStarterKitSearch(): boolean {\n if (!searchRequested) return false\n\n searchRequested = false\n\n return shortcuts.trigger(SHORTCUT_IDS.palette)\n}\n"],"mappings":";;;;;;;;AA6BA,SAAgB,kBAAkB,OAAgB,SAA4C;CAC5F,MAAM,aAAa,sBAAsB,KAAK,CAAC,CAAC,MAC7C,UAAU,MAAM,YAAY,KAAA,MAAc,YAAY,KAAA,KAAa,MAAM,OAAO,QACnF;CAEA,OAAO,YAAY,UAAU;EAAE;EAAY,SAAS,WAAW;CAAQ,IAAI;AAC7E;;AAGA,SAAgB,cAAc,OAAyB;CACrD,OAAQ,OAAoD,YAAY,KAAA;AAC1E;AAMA,MAAM,2BAAW,IAAI,IAAkC;AACvD,MAAM,yBAAS,IAAI,IAAyB;;AAG5C,SAAgB,oBAAoB,UAAwC;CAC1E,IAAI,UAAU,SAAS,IAAI,QAAQ;CAEnC,IAAI,CAAC,SAAS;EACZ,UAAU,YAAyB,QAAQ,CAAC,CAAC,MAC1C,UAAU;GACT,OAAO,IAAI,UAAU,KAAK;GAE1B,OAAO;EACT,IACC,UAAmB;GAElB,SAAS,OAAO,QAAQ;GACxB,MAAM;EACR,CACF;EACA,SAAS,IAAI,UAAU,OAAO;CAChC;CAEA,OAAO;AACT;;AAGA,SAAgB,sBAAsB,UAAsC;CAC1E,OAAO,OAAO,IAAI,QAAQ,KAAK;AACjC;;AAGA,SAAgB,wBAA8B;CAC5C,SAAS,MAAM;CACf,OAAO,MAAM;AACf;;;;;;;;AASA,SAAgB,uBAAuB,MAAmB,QAAwC;CAChG,MAAM,EAAE,YAAY,YAAY;CAChC,MAAM,EAAE,uBAAuB,SAAS,cAAc,mBAAmB;CAEzE,OAAO;EACL,GAAG;EACH,YAAY,WAAW;EACvB,UAAU;GAAE,SAAS;GAAO,QAAQ;GAAO,UAAU;EAAM;EAC3D,mBAAmB;EACnB,cAAc,gBAAgB;GAAE,GAAG;GAAc,UAAU,KAAA;EAAU;EACrE,gBAAgB,kBAAkB;GAAE,GAAG;GAAgB,UAAU,CAAC;EAAE;EACpE,uBAAuB,yBAAyB;GAC9C,GAAG;GACH,aAAa,QAAQ,eAAe,eAAe,sBAAsB;GACzE,UAAU,KAAA;EACZ;EACA,SAAS,WAAW;GAClB,GAAG;GACH,SAAS,QAAQ,UAAW,QAAQ,QAAQ,WAAW,OAAQ,QAAQ;GACvE,SAAS,QAAQ,SAAS,WAAW,QAAQ;EAC/C;CACF;AACF;;AAGA,SAAgB,wBAAoC;CAClD,OAAO;EAAE,OAAO;EAAI,QAAQ;EAAO,OAAO;EAAI,SAAS,CAAC;EAAG,OAAO;CAAG;AACvE;AAIA,IAAI,kBAAkB;;;;;;;AAQtB,SAAgB,iCACd,MACA,OACA,WACY;CACZ,OAAO,UAAU,SAAS;EACxB,IAAI,aAAa;EACjB;EACA;EACA,OAAO;EACP,WAAW;EACX,eAAe;GACb,wBAAwB;GACxB,UAAU;EACZ;CACF,CAAC;AACH;;AAGA,SAAgB,0BAAgC;CAC9C,kBAAkB;AACpB;;;;;AAMA,SAAgB,gCAAyC;CACvD,IAAI,CAAC,iBAAiB,OAAO;CAE7B,kBAAkB;CAElB,OAAO,UAAU,QAAQ,aAAa,OAAO;AAC/C"}
|
|
1
|
+
{"version":3,"file":"starter-kit-widgets.js","names":[],"sources":["../src/starter-kit-widgets.ts"],"sourcesContent":["import { requestJson } from './http'\nimport type { AiCopilotSchema } from './schema/ai'\nimport type {\n PageSchema,\n PanelSchema,\n StarterKitDashboardWidgetsSchema,\n StarterKitNavigationSchema,\n StarterKitWidgetsSchema,\n} from './schema/panel'\nimport type { StandaloneComponentSchema } from './schema/standalone'\nimport type { WidgetsBlock } from './schema/widgets'\nimport { SHORTCUT_IDS, shortcuts } from './shortcuts'\nimport { preferCurrentStarterKitPanel, starterKitNavigations } from './starter-kit'\n\n/**\n * The drop-in header components of starter kit mode: `<AlfakitSearch />` (the command\n * palette and global search), `<AlfakitNotifications />` (the database notifications bell),\n * `<AlfakitTenantSwitcher />` and `<AlfakitCopilot />` (the AI plugin's copilot). They render\n * on every page of the host application, from the `widgets` of the shared\n * `alfakitNavigation` prop; their code and the panel data (translations, endpoints, the\n * palette, realtime) load the first time one of them renders.\n * Shared by the three renderers.\n */\n\n/** One starter kit panel's navigation with its header components' data. */\nexport interface StarterKitWidgets {\n navigation: StarterKitNavigationSchema\n widgets: StarterKitWidgetsSchema\n}\n\n/**\n * The header components' data of a `starterKit()` panel, or null for guests and pages of\n * other panels. Without `panelId`: the open AlfaKit page's panel, else the first one with\n * any; with `part` (`'tenancy'` for the tenant switcher), the first panel that has that\n * part when the preferred one hasn't, so a team workspace on a second panel still gets its\n * switcher on the host's own pages.\n */\nexport function starterKitWidgets(\n props: unknown,\n panelId?: string,\n part?: keyof StarterKitWidgetsSchema,\n): StarterKitWidgets | null {\n const entries = preferCurrentStarterKitPanel(starterKitNavigations(props), props).filter(\n (entry) => entry.widgets !== undefined && (panelId === undefined || entry.id === panelId),\n )\n const navigation =\n (part !== undefined && panelId === undefined\n ? entries.find((entry) => entry.widgets?.[part] !== undefined)\n : undefined) ?? entries[0]\n\n return navigation?.widgets ? { navigation, widgets: navigation.widgets } : null\n}\n\n/** Whether the open page is one of the panel's own (it carries the `alfakit` prop). */\nexport function isAlfakitPage(props: unknown): boolean {\n return (props as { alfakit?: unknown } | null | undefined)?.alfakit !== undefined\n}\n\n// ---------------------------------------------------------------- Panel data\n\n// Loaded once per endpoint (the tenant-scoped one inside a tenant) for the browser session;\n// Inertia visits keep it, a full page load starts over.\nconst requests = new Map<string, Promise<PanelSchema>>()\nconst panels = new Map<string, PanelSchema>()\n\n/** Load (once) the panel data the header components render with. */\nexport function loadStarterKitPanel(endpoint: string): Promise<PanelSchema> {\n let request = requests.get(endpoint)\n\n if (!request) {\n request = requestJson<unknown>(endpoint)\n .then((panel) => {\n // A redirect to the sign-in page (expired or ended session) answers HTML, not the\n // panel: refuse it, so the components keep their trigger instead of crashing.\n if (!isPanelData(panel)) {\n throw new Error(`The starter kit endpoint ${endpoint} did not answer the panel data.`)\n }\n\n panels.set(endpoint, panel)\n\n return panel\n })\n .catch((error: unknown) => {\n // A failed request (offline, expired session) is retried by the next render.\n requests.delete(endpoint)\n throw error\n })\n requests.set(endpoint, request)\n }\n\n return request\n}\n\nfunction isPanelData(value: unknown): value is PanelSchema {\n return (\n typeof value === 'object' &&\n value !== null &&\n typeof (value as { endpoints?: unknown }).endpoints === 'object' &&\n (value as { endpoints?: unknown }).endpoints !== null\n )\n}\n\n/** The panel data once loaded, or null (so remounts render straight away). */\nexport function loadedStarterKitPanel(endpoint: string): PanelSchema | null {\n return panels.get(endpoint) ?? null\n}\n\n/** @internal Forget the loaded panel data (tests). */\nexport function resetStarterKitPanels(): void {\n requests.clear()\n panels.clear()\n}\n\n/**\n * The panel the header components render with: the loaded panel data with the fresh\n * shared prop (navigation, unread count, tenants). Outside Alfakit's own chrome the host\n * owns the colour scheme and there is no shortcuts overlay, and panel commands and page\n * actions need an Alfakit page (its token and modals), so the palette lists search\n * results, navigation, \"create\" links and recent records.\n */\nexport function starterKitWidgetsPanel(base: PanelSchema, source: StarterKitWidgets): PanelSchema {\n const { navigation, widgets } = source\n const { databaseNotifications, tenancy, globalSearch, commandPalette } = base\n\n return {\n ...base,\n navigation: navigation.groups,\n darkMode: { enabled: false, forced: false, switcher: false },\n keyboardShortcuts: false,\n globalSearch: globalSearch && { ...globalSearch, position: undefined },\n commandPalette: commandPalette && { ...commandPalette, commands: [] },\n databaseNotifications: databaseNotifications && {\n ...databaseNotifications,\n unreadCount: widgets.notifications?.unreadCount ?? databaseNotifications.unreadCount,\n position: undefined,\n },\n tenancy: tenancy && {\n ...tenancy,\n current: widgets.tenancy ? (widgets.tenancy.current ?? null) : tenancy.current,\n tenants: widgets.tenancy?.tenants ?? tenancy.tenants,\n },\n }\n}\n\n/** The `page` the header components render with: none of the panel's (`usePageSchema()`). */\nexport function starterKitWidgetsPage(): PageSchema {\n return { class: '', layout: 'app', title: '', content: [], token: '' }\n}\n\n// ---------------------------------------------------------------- Search shortcut\n\nlet searchRequested = false\n\n/**\n * Register the palette's key bindings before its code has loaded: pressing them (or\n * clicking the field) records the request, and the palette opens once it has loaded. It\n * uses the palette's shortcut id, so the loaded palette replaces this registration and\n * the keys are bound once, however many search components the page renders.\n */\nexport function registerStarterKitSearchShortcut(\n keys: string[],\n label: string,\n onRequest: () => void,\n): () => void {\n return shortcuts.register({\n id: SHORTCUT_IDS.palette,\n keys,\n label,\n group: 'general',\n essential: true,\n handler: () => {\n requestStarterKitSearch()\n onRequest()\n },\n })\n}\n\n/** Ask for the palette to open as soon as it has loaded. */\nexport function requestStarterKitSearch(): void {\n searchRequested = true\n}\n\n/**\n * Open the loaded palette when it was asked for before it loaded (call once it mounted):\n * true when it did.\n */\nexport function openRequestedStarterKitSearch(): boolean {\n if (!searchRequested) return false\n\n searchRequested = false\n\n return shortcuts.trigger(SHORTCUT_IDS.palette)\n}\n\n// ---------------------------------------------------------------- Copilot\n\n/** The copilot's shortcut id (the loaded `ai:copilot` component registers the same one). */\nexport const STARTER_KIT_COPILOT_SHORTCUT = 'ai.copilot'\n\nlet copilotRequested = false\n\n/**\n * Register the copilot's key bindings (`mod+j`) before its code has loaded: pressing them\n * (or clicking the button) records the request, and the copilot opens once it has loaded.\n * The loaded copilot registers the same id, so it replaces this registration.\n */\nexport function registerStarterKitCopilotShortcut(\n keys: string[],\n label: string,\n onRequest?: () => void,\n): () => void {\n if (keys.length === 0) return () => {}\n\n return shortcuts.register({\n id: STARTER_KIT_COPILOT_SHORTCUT,\n keys,\n label,\n group: 'general',\n allowInInputs: true,\n handler: () => {\n requestStarterKitCopilot()\n onRequest?.()\n },\n })\n}\n\n/** Ask for the copilot to open as soon as it has loaded. */\nexport function requestStarterKitCopilot(): void {\n copilotRequested = true\n}\n\n/** Whether the copilot was asked for before it loaded (and forget the request). */\nexport function takeRequestedStarterKitCopilot(): boolean {\n const requested = copilotRequested\n\n copilotRequested = false\n\n return requested\n}\n\n/**\n * The copilot component in the loaded panel data: the panel's own pages show it in the\n * `topbar.end` slot, which starter kit hosts don't render. Null without the AI plugin's\n * copilot (or when the user may not use it).\n */\nexport function starterKitCopilot(panel: PanelSchema): AiCopilotSchema | null {\n for (const item of panel.slots?.['topbar.end'] ?? []) {\n if (item.type === 'component' && item.component.type === 'ai:copilot') {\n return item.component as unknown as AiCopilotSchema\n }\n }\n\n return null\n}\n\n// ---------------------------------------------------------------- Dashboard widgets\n\n/**\n * The dashboard widgets of a `starterKit()` panel on the host's dashboard\n * (`<AlfakitWidgets />`, the `alfakitWidgets` prop): a standalone widgets payload bound to\n * the panel, the first panel's unless `panelId` is given. Null on other pages, for guests,\n * users who may not open the panel and panels without widgets.\n */\nexport function starterKitDashboardWidgets(\n props: unknown,\n panelId?: string,\n): StandaloneComponentSchema | null {\n const value = (props as { alfakitWidgets?: unknown } | null | undefined)?.alfakitWidgets\n\n if (!Array.isArray(value)) return null\n\n const entry = (value as StarterKitDashboardWidgetsSchema[]).find(\n (item) => panelId === undefined || item.id === panelId,\n )\n\n return entry?.component ?? null\n}\n\n/** How many widgets a dashboard widgets payload has (placeholders before its code loads). */\nexport function standaloneWidgetCount(component: StandaloneComponentSchema): number {\n return component.content.reduce(\n (total, block) =>\n total + (block.type === 'Widgets' ? (block as WidgetsBlock).widgets.length : 0),\n 0,\n )\n}\n"],"mappings":"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"}
|
package/dist/starter-kit.d.ts
CHANGED
|
@@ -28,13 +28,36 @@ export interface StarterKitBreadcrumb {
|
|
|
28
28
|
*/
|
|
29
29
|
export declare function toStarterKitBreadcrumbs(breadcrumbs: BreadcrumbSchema[] | null | undefined): StarterKitBreadcrumb[];
|
|
30
30
|
/** The breadcrumbs of an Alfakit page from its Inertia props (`props.page.breadcrumbs`). */
|
|
31
|
+
/**
|
|
32
|
+
* Whether an Alfakit page renders inside the host layout (`createStarterKitPage({ layout })`).
|
|
33
|
+
* Pages with the `auth` layout (sign-in, registration, password reset, multi-factor
|
|
34
|
+
* challenge, email verification, `->login()` brought back after `starterKit()`) stand on
|
|
35
|
+
* their own, like the host's auth pages: the host layout expects a signed-in user.
|
|
36
|
+
*/
|
|
37
|
+
export declare function usesStarterKitHostLayout(props: unknown): boolean;
|
|
31
38
|
export declare function starterKitBreadcrumbsFromProps(props: unknown): StarterKitBreadcrumb[];
|
|
32
39
|
/**
|
|
33
40
|
* The shared `alfakitNavigation` prop of a page: one entry per `starterKit()` panel, or none
|
|
34
41
|
* for guests and pages of other panels.
|
|
35
42
|
*/
|
|
36
43
|
export declare function starterKitNavigations(props: unknown): StarterKitNavigationSchema[];
|
|
37
|
-
/**
|
|
44
|
+
/**
|
|
45
|
+
* The panel of the open AlfaKit page (`props.alfakit.id`), or undefined on the host's own
|
|
46
|
+
* pages. With several `starterKit()` panels, components without a `panel` follow it.
|
|
47
|
+
*/
|
|
48
|
+
export declare function currentStarterKitPanelId(props: unknown): string | undefined;
|
|
49
|
+
/**
|
|
50
|
+
* Order panel entries for components without a `panel` id: the open AlfaKit page's panel
|
|
51
|
+
* first (an `/app` page of a second `starterKit()` panel shows that panel's navigation and
|
|
52
|
+
* tenant menu), then the others in registration order.
|
|
53
|
+
*/
|
|
54
|
+
export declare function preferCurrentStarterKitPanel<T extends {
|
|
55
|
+
id: string;
|
|
56
|
+
}>(entries: T[], props: unknown): T[];
|
|
57
|
+
/**
|
|
58
|
+
* One panel's navigation, or null. Without an id: the open AlfaKit page's panel, else the
|
|
59
|
+
* first starter kit panel.
|
|
60
|
+
*/
|
|
38
61
|
export declare function starterKitNavigation(props: unknown, panelId?: string): StarterKitNavigationSchema | null;
|
|
39
62
|
/**
|
|
40
63
|
* A navigation item flattened for a starter kit sidebar: the label as text and the item's
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"starter-kit.d.ts","names":[],"sources":["../src/starter-kit.ts"],"mappings":";;;;;;;;;;;;;;qBAiCa;;wBAGG,kBACd,OAAO,KAAK;;iBAMG;EACf;EACA;;;;;;;;wBASc,wBACd,aAAa,wCACZ
|
|
1
|
+
{"version":3,"file":"starter-kit.d.ts","names":[],"sources":["../src/starter-kit.ts"],"mappings":";;;;;;;;;;;;;;qBAiCa;;wBAGG,kBACd,OAAO,KAAK;;iBAMG;EACf;EACA;;;;;;;;wBASc,wBACd,aAAa,wCACZ;;;;;;;;wBAsBa,yBAAyB;wBAMzB,+BAA+B,iBAAiB;;;;;wBAUhD,sBAAsB,iBAAiB;;;;;wBAUvC,yBAAyB;;;;;;wBAWzB,6BAA6B;EAAY;GACvD,SAAS,KACT,iBACC;;;;;wBAWa,qBACd,gBACA,mBACC;;;;;iBAcc;EACf;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,UAAU;;iBAGK;EACf;EACA;EACA;EACA,OAAO;;;wBAmBO,sBACd,QAAQ,6CACP;;;;;wBA8Ba,mBAAmB,OAAO,QAAQ;;wBAalC,0BAA0B;;;;;;;wBAU1B,yBACd,OAAO,cAAc,6BACrB,IAAI,aAAa,eAAe,iCAAiC"}
|
package/dist/starter-kit.js
CHANGED
|
@@ -40,6 +40,15 @@ function toStarterKitBreadcrumbs(breadcrumbs) {
|
|
|
40
40
|
});
|
|
41
41
|
}
|
|
42
42
|
/** The breadcrumbs of an Alfakit page from its Inertia props (`props.page.breadcrumbs`). */
|
|
43
|
+
/**
|
|
44
|
+
* Whether an Alfakit page renders inside the host layout (`createStarterKitPage({ layout })`).
|
|
45
|
+
* Pages with the `auth` layout (sign-in, registration, password reset, multi-factor
|
|
46
|
+
* challenge, email verification, `->login()` brought back after `starterKit()`) stand on
|
|
47
|
+
* their own, like the host's auth pages: the host layout expects a signed-in user.
|
|
48
|
+
*/
|
|
49
|
+
function usesStarterKitHostLayout(props) {
|
|
50
|
+
return (props?.page)?.layout !== "auth";
|
|
51
|
+
}
|
|
43
52
|
function starterKitBreadcrumbsFromProps(props) {
|
|
44
53
|
const page = props?.page;
|
|
45
54
|
return toStarterKitBreadcrumbs(page?.breadcrumbs);
|
|
@@ -52,10 +61,31 @@ function starterKitNavigations(props) {
|
|
|
52
61
|
const value = props?.alfakitNavigation;
|
|
53
62
|
return Array.isArray(value) ? value : [];
|
|
54
63
|
}
|
|
55
|
-
/**
|
|
64
|
+
/**
|
|
65
|
+
* The panel of the open AlfaKit page (`props.alfakit.id`), or undefined on the host's own
|
|
66
|
+
* pages. With several `starterKit()` panels, components without a `panel` follow it.
|
|
67
|
+
*/
|
|
68
|
+
function currentStarterKitPanelId(props) {
|
|
69
|
+
const id = props?.alfakit?.id;
|
|
70
|
+
return typeof id === "string" ? id : void 0;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Order panel entries for components without a `panel` id: the open AlfaKit page's panel
|
|
74
|
+
* first (an `/app` page of a second `starterKit()` panel shows that panel's navigation and
|
|
75
|
+
* tenant menu), then the others in registration order.
|
|
76
|
+
*/
|
|
77
|
+
function preferCurrentStarterKitPanel(entries, props) {
|
|
78
|
+
const current = currentStarterKitPanelId(props);
|
|
79
|
+
const index = current === void 0 ? -1 : entries.findIndex((entry) => entry.id === current);
|
|
80
|
+
return index <= 0 ? entries : [entries[index], ...entries.filter((_, i) => i !== index)];
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* One panel's navigation, or null. Without an id: the open AlfaKit page's panel, else the
|
|
84
|
+
* first starter kit panel.
|
|
85
|
+
*/
|
|
56
86
|
function starterKitNavigation(props, panelId) {
|
|
57
87
|
const navigations = starterKitNavigations(props);
|
|
58
|
-
return (panelId === void 0 ? navigations[0] : navigations.find((navigation) => navigation.id === panelId)) ?? null;
|
|
88
|
+
return (panelId === void 0 ? preferCurrentStarterKitPanel(navigations, props)[0] : navigations.find((navigation) => navigation.id === panelId)) ?? null;
|
|
59
89
|
}
|
|
60
90
|
function toNavItem(item, key) {
|
|
61
91
|
return {
|
|
@@ -126,6 +156,6 @@ function installStarterKitRuntime(show, t) {
|
|
|
126
156
|
return true;
|
|
127
157
|
}
|
|
128
158
|
//#endregion
|
|
129
|
-
export { STARTER_KIT_TOASTER_ID, currentStarterKitPanel, installStarterKitRuntime, isStarterKitPanel, starterKitBreadcrumbsFromProps, starterKitNavigation, starterKitNavigations, syncStarterKitPage, toStarterKitBreadcrumbs, toStarterKitNavGroups };
|
|
159
|
+
export { STARTER_KIT_TOASTER_ID, currentStarterKitPanel, currentStarterKitPanelId, installStarterKitRuntime, isStarterKitPanel, preferCurrentStarterKitPanel, starterKitBreadcrumbsFromProps, starterKitNavigation, starterKitNavigations, syncStarterKitPage, toStarterKitBreadcrumbs, toStarterKitNavGroups, usesStarterKitHostLayout };
|
|
130
160
|
|
|
131
161
|
//# sourceMappingURL=starter-kit.js.map
|
package/dist/starter-kit.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"starter-kit.js","names":[],"sources":["../src/starter-kit.ts"],"sourcesContent":["import { debugRecorder } from './debug'\nimport {\n effectiveErrorNotifications,\n installErrorNotifications,\n resolveRequestErrorNotification,\n setRequestErrorReporter,\n} from './error-notifications'\nimport { reloadOnExpiredPage } from './http'\nimport { navigationItemText } from './navigation'\nimport type { NotificationSchema } from './schema/notifications'\nimport type {\n AlfakitPageProps,\n BreadcrumbSchema,\n ErrorNotificationsSchema,\n NavigationGroupSchema,\n NavigationItemSchema,\n PanelSchema,\n StarterKitNavigationSchema,\n} from './schema/panel'\nimport { installSpaUrlExceptions } from './spa'\n\n/**\n * Starter kit mode (`Panel::starterKit()`): Alfakit pages render inside an application made\n * from Laravel's React, Vue or Svelte starter kit, in the host's own layout. These helpers are\n * shared by the three renderers; the host's sidebar reads the navigation through each\n * renderer's `useAlfakitNavigation()`.\n */\n\n/**\n * The sonner toaster Alfakit renders in starter kit mode. The host layout mounts its own\n * id-less `<Toaster />`; giving Alfakit's an id keeps each toast in one toaster even when the\n * host and Alfakit resolve the same sonner instance.\n */\nexport const STARTER_KIT_TOASTER_ID = 'alfakit'\n\n/** Whether the panel runs inside a starter kit application (`Panel::starterKit()`). */\nexport function isStarterKitPanel(\n panel: Pick<PanelSchema, 'starterKit'> | null | undefined,\n): boolean {\n return panel?.starterKit === true\n}\n\n/** A breadcrumb as the starter kits' `AppLayout` takes it (`BreadcrumbItem`). */\nexport interface StarterKitBreadcrumb {\n title: string\n href: string\n}\n\n/**\n * The page's breadcrumbs in the starter kits' shape: `{ title, href }` instead of\n * `{ label, url }`. The kits render the last crumb (the current page) as plain text and key\n * the list by `href`, so every `href` is unique: the last crumb without a URL keeps an empty\n * one, other crumbs without one (or repeating one) get a `#index` fragment.\n */\nexport function toStarterKitBreadcrumbs(\n breadcrumbs: BreadcrumbSchema[] | null | undefined,\n): StarterKitBreadcrumb[] {\n const list = breadcrumbs ?? []\n const seen = new Set<string>()\n\n return list.map((crumb, index) => {\n let href = crumb.url ?? (index === list.length - 1 ? '' : `#${index}`)\n\n if (seen.has(href)) href = `${href}#${index}`\n\n seen.add(href)\n\n return { title: crumb.label, href }\n })\n}\n\n/** The breadcrumbs of an Alfakit page from its Inertia props (`props.page.breadcrumbs`). */\nexport function starterKitBreadcrumbsFromProps(props: unknown): StarterKitBreadcrumb[] {\n const page = (props as { page?: { breadcrumbs?: BreadcrumbSchema[] } } | null | undefined)?.page\n\n return toStarterKitBreadcrumbs(page?.breadcrumbs)\n}\n\n/**\n * The shared `alfakitNavigation` prop of a page: one entry per `starterKit()` panel, or none\n * for guests and pages of other panels.\n */\nexport function starterKitNavigations(props: unknown): StarterKitNavigationSchema[] {\n const value = (props as { alfakitNavigation?: unknown } | null | undefined)?.alfakitNavigation\n\n return Array.isArray(value) ? (value as StarterKitNavigationSchema[]) : []\n}\n\n/** One panel's navigation (the first starter kit panel when no id is given), or null. */\nexport function starterKitNavigation(\n props: unknown,\n panelId?: string,\n): StarterKitNavigationSchema | null {\n const navigations = starterKitNavigations(props)\n\n return (\n (panelId === undefined\n ? navigations[0]\n : navigations.find((navigation) => navigation.id === panelId)) ?? null\n )\n}\n\n/**\n * A navigation item flattened for a starter kit sidebar: the label as text and the item's\n * nested children. `title`/`href` match the kits' `NavItem` so the host can reuse its types.\n */\nexport interface StarterKitNavItem {\n key: string\n title: string\n href: string\n icon?: string\n isActive: boolean\n openInNewTab: boolean\n badge?: string | number\n badgeColor?: string\n badgeTooltip?: string\n children: StarterKitNavItem[]\n}\n\nexport interface StarterKitNavGroup {\n key: string\n label: string | null\n icon?: string\n items: StarterKitNavItem[]\n}\n\nfunction toNavItem(item: NavigationItemSchema, key: string): StarterKitNavItem {\n return {\n key,\n title: navigationItemText(item),\n href: item.url ?? '',\n icon: item.icon ?? undefined,\n isActive: item.active === true,\n openInNewTab: item.openInNewTab === true,\n badge: item.badge ?? undefined,\n badgeColor: item.badgeColor ?? undefined,\n badgeTooltip: item.badgeTooltip ?? undefined,\n children: (item.children ?? []).map((child, index) => toNavItem(child, `${key}.${index}`)),\n }\n}\n\n/** A panel's navigation groups as plain items for the host's sidebar primitives. */\nexport function toStarterKitNavGroups(\n groups: NavigationGroupSchema[] | null | undefined,\n): StarterKitNavGroup[] {\n return (groups ?? []).map((group, index) => {\n const key = `${index}:${group.label ?? ''}`\n\n return {\n key,\n label: group.label ?? null,\n icon: group.icon ?? undefined,\n items: group.items.map((item, itemIndex) => toNavItem(item, `${key}.${itemIndex}`)),\n }\n })\n}\n\n// ---------------------------------------------------------------- Browser runtime\n\n/**\n * What the browser-wide listeners of starter kit mode read: the open Alfakit page's panel\n * and error notifications, or nothing while a page of the host application is open (its\n * errors keep Inertia's default handling).\n */\nconst runtime: {\n panel: PanelSchema | null\n errorNotifications: ErrorNotificationsSchema | false | undefined\n installed: boolean\n} = { panel: null, errorNotifications: undefined, installed: false }\n\n/**\n * Record the Alfakit page being rendered (called by each renderer's starter kit root while\n * it renders); `null` once the root unmounts, when a host page opens.\n */\nexport function syncStarterKitPage(props: Partial<AlfakitPageProps> | null): void {\n const panel = props?.alfakit ?? null\n\n runtime.panel = panel\n runtime.errorNotifications = panel ? effectiveErrorNotifications(panel, props?.page) : undefined\n\n if (typeof window !== 'undefined') {\n // Internal requests and form stores are recorded only while the page carries the debug prop.\n debugRecorder.setEnabled(props?.alfakitDebug !== undefined)\n }\n}\n\n/** The panel of the open Alfakit page in starter kit mode, or null on host pages. */\nexport function currentStarterKitPanel(): PanelSchema | null {\n return runtime.panel\n}\n\n/**\n * The listeners `createAlfakit()` installs, once per page load, for starter kit mode: reload\n * on an expired CSRF token (419), the panel's error notifications instead of Inertia's error\n * modal, a notification for failed internal requests and the panel's SPA URL exceptions.\n * Returns false when they were already installed (or on the server).\n */\nexport function installStarterKitRuntime(\n show: (notification: NotificationSchema) => void,\n t: (key: string, replacements?: Record<string, string | number>, fallback?: string) => string,\n): boolean {\n if (typeof window === 'undefined' || runtime.installed) return false\n\n runtime.installed = true\n\n reloadOnExpiredPage()\n installSpaUrlExceptions(() => runtime.panel)\n installErrorNotifications(() => ({ errorNotifications: runtime.errorNotifications }), show)\n setRequestErrorReporter((error) => {\n const notification = resolveRequestErrorNotification(error, runtime.errorNotifications, t)\n\n if (notification) show(notification)\n })\n\n return true\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AAiCA,MAAa,yBAAyB;;AAGtC,SAAgB,kBACd,OACS;CACT,OAAO,OAAO,eAAe;AAC/B;;;;;;;AAcA,SAAgB,wBACd,aACwB;CACxB,MAAM,OAAO,eAAe,CAAC;CAC7B,MAAM,uBAAO,IAAI,IAAY;CAE7B,OAAO,KAAK,KAAK,OAAO,UAAU;EAChC,IAAI,OAAO,MAAM,QAAQ,UAAU,KAAK,SAAS,IAAI,KAAK,IAAI;EAE9D,IAAI,KAAK,IAAI,IAAI,GAAG,OAAO,GAAG,KAAK,GAAG;EAEtC,KAAK,IAAI,IAAI;EAEb,OAAO;GAAE,OAAO,MAAM;GAAO;EAAK;CACpC,CAAC;AACH;;AAGA,SAAgB,+BAA+B,OAAwC;CACrF,MAAM,OAAQ,OAA8E;CAE5F,OAAO,wBAAwB,MAAM,WAAW;AAClD;;;;;AAMA,SAAgB,sBAAsB,OAA8C;CAClF,MAAM,QAAS,OAA8D;CAE7E,OAAO,MAAM,QAAQ,KAAK,IAAK,QAAyC,CAAC;AAC3E;;AAGA,SAAgB,qBACd,OACA,SACmC;CACnC,MAAM,cAAc,sBAAsB,KAAK;CAE/C,QACG,YAAY,KAAA,IACT,YAAY,KACZ,YAAY,MAAM,eAAe,WAAW,OAAO,OAAO,MAAM;AAExE;AA0BA,SAAS,UAAU,MAA4B,KAAgC;CAC7E,OAAO;EACL;EACA,OAAO,mBAAmB,IAAI;EAC9B,MAAM,KAAK,OAAO;EAClB,MAAM,KAAK,QAAQ,KAAA;EACnB,UAAU,KAAK,WAAW;EAC1B,cAAc,KAAK,iBAAiB;EACpC,OAAO,KAAK,SAAS,KAAA;EACrB,YAAY,KAAK,cAAc,KAAA;EAC/B,cAAc,KAAK,gBAAgB,KAAA;EACnC,WAAW,KAAK,YAAY,CAAC,EAAA,CAAG,KAAK,OAAO,UAAU,UAAU,OAAO,GAAG,IAAI,GAAG,OAAO,CAAC;CAC3F;AACF;;AAGA,SAAgB,sBACd,QACsB;CACtB,QAAQ,UAAU,CAAC,EAAA,CAAG,KAAK,OAAO,UAAU;EAC1C,MAAM,MAAM,GAAG,MAAM,GAAG,MAAM,SAAS;EAEvC,OAAO;GACL;GACA,OAAO,MAAM,SAAS;GACtB,MAAM,MAAM,QAAQ,KAAA;GACpB,OAAO,MAAM,MAAM,KAAK,MAAM,cAAc,UAAU,MAAM,GAAG,IAAI,GAAG,WAAW,CAAC;EACpF;CACF,CAAC;AACH;;;;;;AASA,MAAM,UAIF;CAAE,OAAO;CAAM,oBAAoB,KAAA;CAAW,WAAW;AAAM;;;;;AAMnE,SAAgB,mBAAmB,OAA+C;CAChF,MAAM,QAAQ,OAAO,WAAW;CAEhC,QAAQ,QAAQ;CAChB,QAAQ,qBAAqB,QAAQ,4BAA4B,OAAO,OAAO,IAAI,IAAI,KAAA;CAEvF,IAAI,OAAO,WAAW,aAEpB,cAAc,WAAW,OAAO,iBAAiB,KAAA,CAAS;AAE9D;;AAGA,SAAgB,yBAA6C;CAC3D,OAAO,QAAQ;AACjB;;;;;;;AAQA,SAAgB,yBACd,MACA,GACS;CACT,IAAI,OAAO,WAAW,eAAe,QAAQ,WAAW,OAAO;CAE/D,QAAQ,YAAY;CAEpB,oBAAoB;CACpB,8BAA8B,QAAQ,KAAK;CAC3C,iCAAiC,EAAE,oBAAoB,QAAQ,mBAAmB,IAAI,IAAI;CAC1F,yBAAyB,UAAU;EACjC,MAAM,eAAe,gCAAgC,OAAO,QAAQ,oBAAoB,CAAC;EAEzF,IAAI,cAAc,KAAK,YAAY;CACrC,CAAC;CAED,OAAO;AACT"}
|
|
1
|
+
{"version":3,"file":"starter-kit.js","names":[],"sources":["../src/starter-kit.ts"],"sourcesContent":["import { debugRecorder } from './debug'\nimport {\n effectiveErrorNotifications,\n installErrorNotifications,\n resolveRequestErrorNotification,\n setRequestErrorReporter,\n} from './error-notifications'\nimport { reloadOnExpiredPage } from './http'\nimport { navigationItemText } from './navigation'\nimport type { NotificationSchema } from './schema/notifications'\nimport type {\n AlfakitPageProps,\n BreadcrumbSchema,\n ErrorNotificationsSchema,\n NavigationGroupSchema,\n NavigationItemSchema,\n PanelSchema,\n StarterKitNavigationSchema,\n} from './schema/panel'\nimport { installSpaUrlExceptions } from './spa'\n\n/**\n * Starter kit mode (`Panel::starterKit()`): Alfakit pages render inside an application made\n * from Laravel's React, Vue or Svelte starter kit, in the host's own layout. These helpers are\n * shared by the three renderers; the host's sidebar reads the navigation through each\n * renderer's `useAlfakitNavigation()`.\n */\n\n/**\n * The sonner toaster Alfakit renders in starter kit mode. The host layout mounts its own\n * id-less `<Toaster />`; giving Alfakit's an id keeps each toast in one toaster even when the\n * host and Alfakit resolve the same sonner instance.\n */\nexport const STARTER_KIT_TOASTER_ID = 'alfakit'\n\n/** Whether the panel runs inside a starter kit application (`Panel::starterKit()`). */\nexport function isStarterKitPanel(\n panel: Pick<PanelSchema, 'starterKit'> | null | undefined,\n): boolean {\n return panel?.starterKit === true\n}\n\n/** A breadcrumb as the starter kits' `AppLayout` takes it (`BreadcrumbItem`). */\nexport interface StarterKitBreadcrumb {\n title: string\n href: string\n}\n\n/**\n * The page's breadcrumbs in the starter kits' shape: `{ title, href }` instead of\n * `{ label, url }`. The kits render the last crumb (the current page) as plain text and key\n * the list by `href`, so every `href` is unique: the last crumb without a URL keeps an empty\n * one, other crumbs without one (or repeating one) get a `#index` fragment.\n */\nexport function toStarterKitBreadcrumbs(\n breadcrumbs: BreadcrumbSchema[] | null | undefined,\n): StarterKitBreadcrumb[] {\n const list = breadcrumbs ?? []\n const seen = new Set<string>()\n\n return list.map((crumb, index) => {\n let href = crumb.url ?? (index === list.length - 1 ? '' : `#${index}`)\n\n if (seen.has(href)) href = `${href}#${index}`\n\n seen.add(href)\n\n return { title: crumb.label, href }\n })\n}\n\n/** The breadcrumbs of an Alfakit page from its Inertia props (`props.page.breadcrumbs`). */\n/**\n * Whether an Alfakit page renders inside the host layout (`createStarterKitPage({ layout })`).\n * Pages with the `auth` layout (sign-in, registration, password reset, multi-factor\n * challenge, email verification, `->login()` brought back after `starterKit()`) stand on\n * their own, like the host's auth pages: the host layout expects a signed-in user.\n */\nexport function usesStarterKitHostLayout(props: unknown): boolean {\n const page = (props as { page?: { layout?: string } } | null | undefined)?.page\n\n return page?.layout !== 'auth'\n}\n\nexport function starterKitBreadcrumbsFromProps(props: unknown): StarterKitBreadcrumb[] {\n const page = (props as { page?: { breadcrumbs?: BreadcrumbSchema[] } } | null | undefined)?.page\n\n return toStarterKitBreadcrumbs(page?.breadcrumbs)\n}\n\n/**\n * The shared `alfakitNavigation` prop of a page: one entry per `starterKit()` panel, or none\n * for guests and pages of other panels.\n */\nexport function starterKitNavigations(props: unknown): StarterKitNavigationSchema[] {\n const value = (props as { alfakitNavigation?: unknown } | null | undefined)?.alfakitNavigation\n\n return Array.isArray(value) ? (value as StarterKitNavigationSchema[]) : []\n}\n\n/**\n * The panel of the open AlfaKit page (`props.alfakit.id`), or undefined on the host's own\n * pages. With several `starterKit()` panels, components without a `panel` follow it.\n */\nexport function currentStarterKitPanelId(props: unknown): string | undefined {\n const id = (props as { alfakit?: { id?: unknown } } | null | undefined)?.alfakit?.id\n\n return typeof id === 'string' ? id : undefined\n}\n\n/**\n * Order panel entries for components without a `panel` id: the open AlfaKit page's panel\n * first (an `/app` page of a second `starterKit()` panel shows that panel's navigation and\n * tenant menu), then the others in registration order.\n */\nexport function preferCurrentStarterKitPanel<T extends { id: string }>(\n entries: T[],\n props: unknown,\n): T[] {\n const current = currentStarterKitPanelId(props)\n const index = current === undefined ? -1 : entries.findIndex((entry) => entry.id === current)\n\n return index <= 0 ? entries : [entries[index]!, ...entries.filter((_, i) => i !== index)]\n}\n\n/**\n * One panel's navigation, or null. Without an id: the open AlfaKit page's panel, else the\n * first starter kit panel.\n */\nexport function starterKitNavigation(\n props: unknown,\n panelId?: string,\n): StarterKitNavigationSchema | null {\n const navigations = starterKitNavigations(props)\n\n return (\n (panelId === undefined\n ? preferCurrentStarterKitPanel(navigations, props)[0]\n : navigations.find((navigation) => navigation.id === panelId)) ?? null\n )\n}\n\n/**\n * A navigation item flattened for a starter kit sidebar: the label as text and the item's\n * nested children. `title`/`href` match the kits' `NavItem` so the host can reuse its types.\n */\nexport interface StarterKitNavItem {\n key: string\n title: string\n href: string\n icon?: string\n isActive: boolean\n openInNewTab: boolean\n badge?: string | number\n badgeColor?: string\n badgeTooltip?: string\n children: StarterKitNavItem[]\n}\n\nexport interface StarterKitNavGroup {\n key: string\n label: string | null\n icon?: string\n items: StarterKitNavItem[]\n}\n\nfunction toNavItem(item: NavigationItemSchema, key: string): StarterKitNavItem {\n return {\n key,\n title: navigationItemText(item),\n href: item.url ?? '',\n icon: item.icon ?? undefined,\n isActive: item.active === true,\n openInNewTab: item.openInNewTab === true,\n badge: item.badge ?? undefined,\n badgeColor: item.badgeColor ?? undefined,\n badgeTooltip: item.badgeTooltip ?? undefined,\n children: (item.children ?? []).map((child, index) => toNavItem(child, `${key}.${index}`)),\n }\n}\n\n/** A panel's navigation groups as plain items for the host's sidebar primitives. */\nexport function toStarterKitNavGroups(\n groups: NavigationGroupSchema[] | null | undefined,\n): StarterKitNavGroup[] {\n return (groups ?? []).map((group, index) => {\n const key = `${index}:${group.label ?? ''}`\n\n return {\n key,\n label: group.label ?? null,\n icon: group.icon ?? undefined,\n items: group.items.map((item, itemIndex) => toNavItem(item, `${key}.${itemIndex}`)),\n }\n })\n}\n\n// ---------------------------------------------------------------- Browser runtime\n\n/**\n * What the browser-wide listeners of starter kit mode read: the open Alfakit page's panel\n * and error notifications, or nothing while a page of the host application is open (its\n * errors keep Inertia's default handling).\n */\nconst runtime: {\n panel: PanelSchema | null\n errorNotifications: ErrorNotificationsSchema | false | undefined\n installed: boolean\n} = { panel: null, errorNotifications: undefined, installed: false }\n\n/**\n * Record the Alfakit page being rendered (called by each renderer's starter kit root while\n * it renders); `null` once the root unmounts, when a host page opens.\n */\nexport function syncStarterKitPage(props: Partial<AlfakitPageProps> | null): void {\n const panel = props?.alfakit ?? null\n\n runtime.panel = panel\n runtime.errorNotifications = panel ? effectiveErrorNotifications(panel, props?.page) : undefined\n\n if (typeof window !== 'undefined') {\n // Internal requests and form stores are recorded only while the page carries the debug prop.\n debugRecorder.setEnabled(props?.alfakitDebug !== undefined)\n }\n}\n\n/** The panel of the open Alfakit page in starter kit mode, or null on host pages. */\nexport function currentStarterKitPanel(): PanelSchema | null {\n return runtime.panel\n}\n\n/**\n * The listeners `createAlfakit()` installs, once per page load, for starter kit mode: reload\n * on an expired CSRF token (419), the panel's error notifications instead of Inertia's error\n * modal, a notification for failed internal requests and the panel's SPA URL exceptions.\n * Returns false when they were already installed (or on the server).\n */\nexport function installStarterKitRuntime(\n show: (notification: NotificationSchema) => void,\n t: (key: string, replacements?: Record<string, string | number>, fallback?: string) => string,\n): boolean {\n if (typeof window === 'undefined' || runtime.installed) return false\n\n runtime.installed = true\n\n reloadOnExpiredPage()\n installSpaUrlExceptions(() => runtime.panel)\n installErrorNotifications(() => ({ errorNotifications: runtime.errorNotifications }), show)\n setRequestErrorReporter((error) => {\n const notification = resolveRequestErrorNotification(error, runtime.errorNotifications, t)\n\n if (notification) show(notification)\n })\n\n return true\n}\n"],"mappings":"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"}
|
package/dist/utils/debounce.d.ts
CHANGED
|
@@ -3,6 +3,8 @@ export interface Debounced<TArgs extends unknown[]> {
|
|
|
3
3
|
(...args: TArgs): void;
|
|
4
4
|
cancel: () => void;
|
|
5
5
|
flush: () => void;
|
|
6
|
+
/** A call is waiting for its timer. */
|
|
7
|
+
readonly pending: boolean;
|
|
6
8
|
}
|
|
7
9
|
export declare function debounce<TArgs extends unknown[]>(callback: (...args: TArgs) => void, wait: number): Debounced<TArgs>;
|
|
8
10
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"debounce.d.ts","names":[],"sources":["../../src/utils/debounce.ts"],"mappings":";iBAAiB,UAAU;MACrB,MAAM;EACV;EACA;;
|
|
1
|
+
{"version":3,"file":"debounce.d.ts","names":[],"sources":["../../src/utils/debounce.ts"],"mappings":";iBAAiB,UAAU;MACrB,MAAM;EACV;EACA;;WAES;;wBAGK,SAAS,yBACvB,cAAc,MAAM,gBACpB,eACC,UAAU"}
|
package/dist/utils/debounce.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"debounce.js","names":[],"sources":["../../src/utils/debounce.ts"],"sourcesContent":["export interface Debounced<TArgs extends unknown[]> {\n (...args: TArgs): void\n cancel: () => void\n flush: () => void\n}\n\nexport function debounce<TArgs extends unknown[]>(\n callback: (...args: TArgs) => void,\n wait: number,\n): Debounced<TArgs> {\n let timer: ReturnType<typeof setTimeout> | null = null\n let pending: TArgs | null = null\n\n const debounced = ((...args: TArgs) => {\n pending = args\n\n if (timer !== null) {\n clearTimeout(timer)\n }\n\n timer = setTimeout(() => {\n timer = null\n const current = pending\n pending = null\n\n if (current) {\n callback(...current)\n }\n }, wait)\n }) as Debounced<TArgs>\n\n debounced.cancel = () => {\n if (timer !== null) {\n clearTimeout(timer)\n timer = null\n }\n\n pending = null\n }\n\n debounced.flush = () => {\n if (timer !== null && pending) {\n clearTimeout(timer)\n timer = null\n const current = pending\n pending = null\n callback(...current)\n }\n }\n\n return debounced\n}\n"],"mappings":";
|
|
1
|
+
{"version":3,"file":"debounce.js","names":[],"sources":["../../src/utils/debounce.ts"],"sourcesContent":["export interface Debounced<TArgs extends unknown[]> {\n (...args: TArgs): void\n cancel: () => void\n flush: () => void\n /** A call is waiting for its timer. */\n readonly pending: boolean\n}\n\nexport function debounce<TArgs extends unknown[]>(\n callback: (...args: TArgs) => void,\n wait: number,\n): Debounced<TArgs> {\n let timer: ReturnType<typeof setTimeout> | null = null\n let pending: TArgs | null = null\n\n const debounced = ((...args: TArgs) => {\n pending = args\n\n if (timer !== null) {\n clearTimeout(timer)\n }\n\n timer = setTimeout(() => {\n timer = null\n const current = pending\n pending = null\n\n if (current) {\n callback(...current)\n }\n }, wait)\n }) as Debounced<TArgs>\n\n debounced.cancel = () => {\n if (timer !== null) {\n clearTimeout(timer)\n timer = null\n }\n\n pending = null\n }\n\n Object.defineProperty(debounced, 'pending', { get: () => timer !== null && pending !== null })\n\n debounced.flush = () => {\n if (timer !== null && pending) {\n clearTimeout(timer)\n timer = null\n const current = pending\n pending = null\n callback(...current)\n }\n }\n\n return debounced\n}\n"],"mappings":";AAQA,SAAgB,SACd,UACA,MACkB;CAClB,IAAI,QAA8C;CAClD,IAAI,UAAwB;CAE5B,MAAM,cAAc,GAAG,SAAgB;EACrC,UAAU;EAEV,IAAI,UAAU,MACZ,aAAa,KAAK;EAGpB,QAAQ,iBAAiB;GACvB,QAAQ;GACR,MAAM,UAAU;GAChB,UAAU;GAEV,IAAI,SACF,SAAS,GAAG,OAAO;EAEvB,GAAG,IAAI;CACT;CAEA,UAAU,eAAe;EACvB,IAAI,UAAU,MAAM;GAClB,aAAa,KAAK;GAClB,QAAQ;EACV;EAEA,UAAU;CACZ;CAEA,OAAO,eAAe,WAAW,WAAW,EAAE,WAAW,UAAU,QAAQ,YAAY,KAAK,CAAC;CAE7F,UAAU,cAAc;EACtB,IAAI,UAAU,QAAQ,SAAS;GAC7B,aAAa,KAAK;GAClB,QAAQ;GACR,MAAM,UAAU;GAChB,UAAU;GACV,SAAS,GAAG,OAAO;EACrB;CACF;CAEA,OAAO;AACT"}
|
package/dist/utils/path.d.ts
CHANGED
|
@@ -17,8 +17,18 @@ export declare function parentPath(path: string): string;
|
|
|
17
17
|
export declare function joinPath(...segments: PathSegment[]): string;
|
|
18
18
|
/**
|
|
19
19
|
* Replace the Repeater template placeholder with a concrete index, deeply, in any
|
|
20
|
-
* string value of the object (state paths, keys, ids).
|
|
20
|
+
* string value of the object (state paths, keys, ids). Only the first placeholder of each
|
|
21
|
+
* string is this level's: a repeater inside a builder block template has item paths like
|
|
22
|
+
* `content.__INDEX__.data.items.__INDEX__.question`, whose second placeholder is filled
|
|
23
|
+
* in when an item is added to that repeater.
|
|
21
24
|
*/
|
|
22
25
|
export declare function replacePlaceholder<T>(value: T, placeholder: string, replacement: string): T;
|
|
26
|
+
/**
|
|
27
|
+
* A Repeater or Builder template with the item index filled in (`replacePlaceholder()`),
|
|
28
|
+
* the same object for the same template and index. Items the server hasn't seen yet render
|
|
29
|
+
* from their template on every render: new objects each time made every field below them
|
|
30
|
+
* render again (and restart work such as the image editor's loading).
|
|
31
|
+
*/
|
|
32
|
+
export declare function templateForIndex<T extends object>(template: T, index: number | string): T;
|
|
23
33
|
//#endregion
|
|
24
34
|
//# sourceMappingURL=path.d.ts.map
|
package/dist/utils/path.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"path.d.ts","names":[],"sources":["../../src/utils/path.ts"],"mappings":";;;;;YAKY;wBAEI,UAAU;wBAIV,QAAQ,aAAa,iBAAiB,cAAc,WAAW,IAAI;wBAkBnE,QAAQ,iBAAiB;;;;;wBAuBzB,QAAQ,kBAAkB,QAAQ,GAAG,cAAc,iBAAiB;wBAqCpE,WAAW,kBAAkB,QAAQ,GAAG,eAAe;wBA4BvD,WAAW;wBAOX,YAAY,UAAU
|
|
1
|
+
{"version":3,"file":"path.d.ts","names":[],"sources":["../../src/utils/path.ts"],"mappings":";;;;;YAKY;wBAEI,UAAU;wBAIV,QAAQ,aAAa,iBAAiB,cAAc,WAAW,IAAI;wBAkBnE,QAAQ,iBAAiB;;;;;wBAuBzB,QAAQ,kBAAkB,QAAQ,GAAG,cAAc,iBAAiB;wBAqCpE,WAAW,kBAAkB,QAAQ,GAAG,eAAe;wBA4BvD,WAAW;wBAOX,YAAY,UAAU;;;;;;;;wBActB,mBAAmB,GAAG,OAAO,GAAG,qBAAqB,sBAAsB;;;;;;;wBAoC3E,iBAAiB,kBAAkB,UAAU,GAAG,yBAAyB"}
|
package/dist/utils/path.js
CHANGED
|
@@ -67,10 +67,16 @@ function joinPath(...segments) {
|
|
|
67
67
|
}
|
|
68
68
|
/**
|
|
69
69
|
* Replace the Repeater template placeholder with a concrete index, deeply, in any
|
|
70
|
-
* string value of the object (state paths, keys, ids).
|
|
70
|
+
* string value of the object (state paths, keys, ids). Only the first placeholder of each
|
|
71
|
+
* string is this level's: a repeater inside a builder block template has item paths like
|
|
72
|
+
* `content.__INDEX__.data.items.__INDEX__.question`, whose second placeholder is filled
|
|
73
|
+
* in when an item is added to that repeater.
|
|
71
74
|
*/
|
|
72
75
|
function replacePlaceholder(value, placeholder, replacement) {
|
|
73
|
-
if (typeof value === "string")
|
|
76
|
+
if (typeof value === "string") {
|
|
77
|
+
const at = value.indexOf(placeholder);
|
|
78
|
+
return at === -1 ? value : value.slice(0, at) + replacement + value.slice(at + placeholder.length);
|
|
79
|
+
}
|
|
74
80
|
if (Array.isArray(value)) return value.map((item) => replacePlaceholder(item, placeholder, replacement));
|
|
75
81
|
if (value !== null && typeof value === "object") {
|
|
76
82
|
const result = {};
|
|
@@ -79,7 +85,28 @@ function replacePlaceholder(value, placeholder, replacement) {
|
|
|
79
85
|
}
|
|
80
86
|
return value;
|
|
81
87
|
}
|
|
88
|
+
const templateInstances = /* @__PURE__ */ new WeakMap();
|
|
89
|
+
/**
|
|
90
|
+
* A Repeater or Builder template with the item index filled in (`replacePlaceholder()`),
|
|
91
|
+
* the same object for the same template and index. Items the server hasn't seen yet render
|
|
92
|
+
* from their template on every render: new objects each time made every field below them
|
|
93
|
+
* render again (and restart work such as the image editor's loading).
|
|
94
|
+
*/
|
|
95
|
+
function templateForIndex(template, index) {
|
|
96
|
+
let instances = templateInstances.get(template);
|
|
97
|
+
if (!instances) {
|
|
98
|
+
instances = /* @__PURE__ */ new Map();
|
|
99
|
+
templateInstances.set(template, instances);
|
|
100
|
+
}
|
|
101
|
+
const key = String(index);
|
|
102
|
+
let instance = instances.get(key);
|
|
103
|
+
if (instance === void 0) {
|
|
104
|
+
instance = replacePlaceholder(template, "__INDEX__", key);
|
|
105
|
+
instances.set(key, instance);
|
|
106
|
+
}
|
|
107
|
+
return instance;
|
|
108
|
+
}
|
|
82
109
|
//#endregion
|
|
83
|
-
export { deletePath, getPath, hasPath, joinPath, parentPath, replacePlaceholder, setPath, splitPath };
|
|
110
|
+
export { deletePath, getPath, hasPath, joinPath, parentPath, replacePlaceholder, setPath, splitPath, templateForIndex };
|
|
84
111
|
|
|
85
112
|
//# sourceMappingURL=path.js.map
|
package/dist/utils/path.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"path.js","names":[],"sources":["../../src/utils/path.ts"],"sourcesContent":["/**\n * Dot-path helpers mirroring Laravel's `data_get` / `data_set` semantics, used for form\n * state addressed by `statePath` (e.g. `comments.0.body`).\n */\n\nexport type PathSegment = string | number\n\nexport function splitPath(path: string): string[] {\n return path === '' ? [] : path.split('.')\n}\n\nexport function getPath<T = unknown>(source: unknown, path: string, fallback?: T): T {\n if (path === '') {\n return source as T\n }\n\n let current: unknown = source\n\n for (const segment of splitPath(path)) {\n if (current === null || current === undefined || typeof current !== 'object') {\n return fallback as T\n }\n\n current = (current as Record<string, unknown>)[segment]\n }\n\n return (current === undefined ? fallback : current) as T\n}\n\nexport function hasPath(source: unknown, path: string): boolean {\n let current: unknown = source\n\n for (const segment of splitPath(path)) {\n if (\n current === null ||\n current === undefined ||\n typeof current !== 'object' ||\n !(segment in (current as object))\n ) {\n return false\n }\n\n current = (current as Record<string, unknown>)[segment]\n }\n\n return true\n}\n\n/**\n * Immutably set a value, cloning each container along the path. Numeric segments on a\n * missing container create arrays.\n */\nexport function setPath<T extends object>(source: T, path: string, value: unknown): T {\n const segments = splitPath(path)\n\n if (segments.length === 0) {\n return value as T\n }\n\n const root: unknown = Array.isArray(source) ? [...source] : { ...(source ?? {}) }\n let current = root as Record<string, unknown>\n\n segments.forEach((segment, index) => {\n const isLast = index === segments.length - 1\n\n if (isLast) {\n current[segment] = value\n\n return\n }\n\n const existing = current[segment]\n const nextSegment = segments[index + 1] ?? ''\n const container =\n existing !== null && typeof existing === 'object'\n ? Array.isArray(existing)\n ? [...existing]\n : { ...existing }\n : /^\\d+$/.test(nextSegment)\n ? []\n : {}\n\n current[segment] = container\n current = container as Record<string, unknown>\n })\n\n return root as T\n}\n\nexport function deletePath<T extends object>(source: T, path: string): T {\n const segments = splitPath(path)\n const last = segments.pop()\n\n if (last === undefined) {\n return source\n }\n\n const parentPath = segments.join('.')\n const parent = getPath<Record<string, unknown> | unknown[]>(source, parentPath)\n\n if (parent === null || parent === undefined || typeof parent !== 'object') {\n return source\n }\n\n if (Array.isArray(parent)) {\n const copy = [...parent]\n copy.splice(Number(last), 1)\n\n return setPath(source, parentPath, copy)\n }\n\n const copy = { ...parent }\n delete copy[last]\n\n return setPath(source, parentPath, copy)\n}\n\nexport function parentPath(path: string): string {\n const segments = splitPath(path)\n segments.pop()\n\n return segments.join('.')\n}\n\nexport function joinPath(...segments: PathSegment[]): string {\n return segments\n .map(String)\n .filter((segment) => segment !== '')\n .join('.')\n}\n\n/**\n * Replace the Repeater template placeholder with a concrete index, deeply, in any\n * string value of the object (state paths, keys, ids).\n */\nexport function replacePlaceholder<T>(value: T, placeholder: string, replacement: string): T {\n if (typeof value === 'string') {\n
|
|
1
|
+
{"version":3,"file":"path.js","names":[],"sources":["../../src/utils/path.ts"],"sourcesContent":["/**\n * Dot-path helpers mirroring Laravel's `data_get` / `data_set` semantics, used for form\n * state addressed by `statePath` (e.g. `comments.0.body`).\n */\n\nexport type PathSegment = string | number\n\nexport function splitPath(path: string): string[] {\n return path === '' ? [] : path.split('.')\n}\n\nexport function getPath<T = unknown>(source: unknown, path: string, fallback?: T): T {\n if (path === '') {\n return source as T\n }\n\n let current: unknown = source\n\n for (const segment of splitPath(path)) {\n if (current === null || current === undefined || typeof current !== 'object') {\n return fallback as T\n }\n\n current = (current as Record<string, unknown>)[segment]\n }\n\n return (current === undefined ? fallback : current) as T\n}\n\nexport function hasPath(source: unknown, path: string): boolean {\n let current: unknown = source\n\n for (const segment of splitPath(path)) {\n if (\n current === null ||\n current === undefined ||\n typeof current !== 'object' ||\n !(segment in (current as object))\n ) {\n return false\n }\n\n current = (current as Record<string, unknown>)[segment]\n }\n\n return true\n}\n\n/**\n * Immutably set a value, cloning each container along the path. Numeric segments on a\n * missing container create arrays.\n */\nexport function setPath<T extends object>(source: T, path: string, value: unknown): T {\n const segments = splitPath(path)\n\n if (segments.length === 0) {\n return value as T\n }\n\n const root: unknown = Array.isArray(source) ? [...source] : { ...(source ?? {}) }\n let current = root as Record<string, unknown>\n\n segments.forEach((segment, index) => {\n const isLast = index === segments.length - 1\n\n if (isLast) {\n current[segment] = value\n\n return\n }\n\n const existing = current[segment]\n const nextSegment = segments[index + 1] ?? ''\n const container =\n existing !== null && typeof existing === 'object'\n ? Array.isArray(existing)\n ? [...existing]\n : { ...existing }\n : /^\\d+$/.test(nextSegment)\n ? []\n : {}\n\n current[segment] = container\n current = container as Record<string, unknown>\n })\n\n return root as T\n}\n\nexport function deletePath<T extends object>(source: T, path: string): T {\n const segments = splitPath(path)\n const last = segments.pop()\n\n if (last === undefined) {\n return source\n }\n\n const parentPath = segments.join('.')\n const parent = getPath<Record<string, unknown> | unknown[]>(source, parentPath)\n\n if (parent === null || parent === undefined || typeof parent !== 'object') {\n return source\n }\n\n if (Array.isArray(parent)) {\n const copy = [...parent]\n copy.splice(Number(last), 1)\n\n return setPath(source, parentPath, copy)\n }\n\n const copy = { ...parent }\n delete copy[last]\n\n return setPath(source, parentPath, copy)\n}\n\nexport function parentPath(path: string): string {\n const segments = splitPath(path)\n segments.pop()\n\n return segments.join('.')\n}\n\nexport function joinPath(...segments: PathSegment[]): string {\n return segments\n .map(String)\n .filter((segment) => segment !== '')\n .join('.')\n}\n\n/**\n * Replace the Repeater template placeholder with a concrete index, deeply, in any\n * string value of the object (state paths, keys, ids). Only the first placeholder of each\n * string is this level's: a repeater inside a builder block template has item paths like\n * `content.__INDEX__.data.items.__INDEX__.question`, whose second placeholder is filled\n * in when an item is added to that repeater.\n */\nexport function replacePlaceholder<T>(value: T, placeholder: string, replacement: string): T {\n if (typeof value === 'string') {\n const at = value.indexOf(placeholder)\n\n return (\n at === -1 ? value : value.slice(0, at) + replacement + value.slice(at + placeholder.length)\n ) as T\n }\n\n if (Array.isArray(value)) {\n return value.map((item) => replacePlaceholder(item, placeholder, replacement)) as T\n }\n\n if (value !== null && typeof value === 'object') {\n const result: Record<string, unknown> = {}\n\n for (const [key, nested] of Object.entries(value as Record<string, unknown>)) {\n result[key] = replacePlaceholder(nested, placeholder, replacement)\n }\n\n return result as T\n }\n\n return value\n}\n\n// Instances of a template per index, kept while the template object lives (it is replaced\n// when the server sends a new schema).\nconst templateInstances = new WeakMap<object, Map<string, unknown>>()\n\n/**\n * A Repeater or Builder template with the item index filled in (`replacePlaceholder()`),\n * the same object for the same template and index. Items the server hasn't seen yet render\n * from their template on every render: new objects each time made every field below them\n * render again (and restart work such as the image editor's loading).\n */\nexport function templateForIndex<T extends object>(template: T, index: number | string): T {\n let instances = templateInstances.get(template)\n\n if (!instances) {\n instances = new Map()\n templateInstances.set(template, instances)\n }\n\n const key = String(index)\n let instance = instances.get(key) as T | undefined\n\n if (instance === undefined) {\n instance = replacePlaceholder(template, '__INDEX__', key)\n instances.set(key, instance)\n }\n\n return instance\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alfakit/core",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.6",
|
|
4
4
|
"description": "Framework-agnostic schema types and state management for Alfakit, the Laravel admin panel builder by Alfa Six.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Alfa Six (https://alfasixbh.com)",
|
|
@@ -34,13 +34,15 @@
|
|
|
34
34
|
"import": "./dist/schema/index.js"
|
|
35
35
|
},
|
|
36
36
|
"./package.json": "./package.json",
|
|
37
|
-
"./themes.css": "./themes.css"
|
|
37
|
+
"./themes.css": "./themes.css",
|
|
38
|
+
"./components.css": "./components.css"
|
|
38
39
|
},
|
|
39
40
|
"files": [
|
|
40
41
|
"dist",
|
|
41
42
|
"README.md",
|
|
42
43
|
"CHANGELOG.md",
|
|
43
|
-
"themes.css"
|
|
44
|
+
"themes.css",
|
|
45
|
+
"components.css"
|
|
44
46
|
],
|
|
45
47
|
"dependencies": {
|
|
46
48
|
"@inertiajs/core": "^3.8.0"
|