@panneau/core 5.0.0-alpha.4 → 5.0.0-alpha.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.
@@ -0,0 +1,38 @@
1
+ import type { Definition, Resource } from '@panneau/protocol';
2
+ /**
3
+ * A command of the "Go to…" palette that a plugin adds (decision 0029): a link to follow or a
4
+ * function to run, found by its label, description and keywords.
5
+ */
6
+ export interface Command {
7
+ /** Unique among the commands of the palette. */
8
+ id: string;
9
+ label: string;
10
+ /** The group it is listed in. Defaults to the palette's group of commands. */
11
+ group?: string;
12
+ description?: string;
13
+ /** More words it is found by. */
14
+ keywords?: readonly string[];
15
+ /** A Lucide icon name (`"send"`). */
16
+ icon?: string;
17
+ /** A path of the app, or a URL, to go to. */
18
+ href?: string;
19
+ /** What it does, without an `href`. */
20
+ perform?: () => void;
21
+ }
22
+ /** What a plugin's commands can depend on. */
23
+ export interface CommandContext {
24
+ /** The definition for the current user, or `null` outside of the app. */
25
+ definition: Definition | null;
26
+ }
27
+ /** Commands, or a function that makes them for the current definition. */
28
+ export type CommandSource = readonly Command[] | ((context: CommandContext) => readonly Command[]);
29
+ /** The commands of every source, in order. */
30
+ export declare function getCommands(sources: readonly CommandSource[], context: CommandContext): Command[];
31
+ /**
32
+ * Whether a resource is in the global search: its `features.search`, or, without it, whether its
33
+ * list has a search (`list.search`), so one setting serves both (decision 0029).
34
+ */
35
+ export declare function isSearchable(resource: Resource): boolean;
36
+ /** The resources of the global search that the user can list. */
37
+ export declare function getSearchableResources(definition: Definition): Resource[];
38
+ //# sourceMappingURL=commands.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"commands.d.ts","sourceRoot":"","sources":["../src/commands.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAK9D;;;GAGG;AACH,MAAM,WAAW,OAAO;IACpB,gDAAgD;IAChD,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,8EAA8E;IAC9E,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,iCAAiC;IACjC,QAAQ,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC7B,qCAAqC;IACrC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,6CAA6C;IAC7C,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,uCAAuC;IACvC,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;CACxB;AAED,8CAA8C;AAC9C,MAAM,WAAW,cAAc;IAC3B,yEAAyE;IACzE,UAAU,EAAE,UAAU,GAAG,IAAI,CAAC;CACjC;AAED,0EAA0E;AAC1E,MAAM,MAAM,aAAa,GAAG,SAAS,OAAO,EAAE,GAAG,CAAC,CAAC,OAAO,EAAE,cAAc,KAAK,SAAS,OAAO,EAAE,CAAC,CAAC;AAEnG,8CAA8C;AAC9C,wBAAgB,WAAW,CAAC,OAAO,EAAE,SAAS,aAAa,EAAE,EAAE,OAAO,EAAE,cAAc,GAAG,OAAO,EAAE,CAEjG;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAExD;AAED,iEAAiE;AACjE,wBAAgB,sBAAsB,CAAC,UAAU,EAAE,UAAU,GAAG,QAAQ,EAAE,CAIzE"}
@@ -0,0 +1,22 @@
1
+ import { can } from "./definition/abilities.js";
2
+ import { isSearchableResource } from "@panneau/protocol/runtime";
3
+ //#region src/commands.ts
4
+ /** The commands of every source, in order. */
5
+ function getCommands(sources, context) {
6
+ return sources.flatMap((source) => typeof source === "function" ? source(context) : source);
7
+ }
8
+ /**
9
+ * Whether a resource is in the global search: its `features.search`, or, without it, whether its
10
+ * list has a search (`list.search`), so one setting serves both (decision 0029).
11
+ */
12
+ function isSearchable(resource) {
13
+ return isSearchableResource(resource);
14
+ }
15
+ /** The resources of the global search that the user can list. */
16
+ function getSearchableResources(definition) {
17
+ return definition.resources.filter((resource) => isSearchable(resource) && can(resource.abilities, "viewAny"));
18
+ }
19
+ //#endregion
20
+ export { getCommands, getSearchableResources, isSearchable };
21
+
22
+ //# sourceMappingURL=commands.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"commands.js","names":[],"sources":["../src/commands.ts"],"sourcesContent":["import type { Definition, Resource } from '@panneau/protocol';\nimport { isSearchableResource } from '@panneau/protocol/runtime';\n\nimport { can } from './definition/abilities';\n\n/**\n * A command of the \"Go to…\" palette that a plugin adds (decision 0029): a link to follow or a\n * function to run, found by its label, description and keywords.\n */\nexport interface Command {\n /** Unique among the commands of the palette. */\n id: string;\n label: string;\n /** The group it is listed in. Defaults to the palette's group of commands. */\n group?: string;\n description?: string;\n /** More words it is found by. */\n keywords?: readonly string[];\n /** A Lucide icon name (`\"send\"`). */\n icon?: string;\n /** A path of the app, or a URL, to go to. */\n href?: string;\n /** What it does, without an `href`. */\n perform?: () => void;\n}\n\n/** What a plugin's commands can depend on. */\nexport interface CommandContext {\n /** The definition for the current user, or `null` outside of the app. */\n definition: Definition | null;\n}\n\n/** Commands, or a function that makes them for the current definition. */\nexport type CommandSource = readonly Command[] | ((context: CommandContext) => readonly Command[]);\n\n/** The commands of every source, in order. */\nexport function getCommands(sources: readonly CommandSource[], context: CommandContext): Command[] {\n return sources.flatMap((source) => (typeof source === 'function' ? source(context) : source));\n}\n\n/**\n * Whether a resource is in the global search: its `features.search`, or, without it, whether its\n * list has a search (`list.search`), so one setting serves both (decision 0029).\n */\nexport function isSearchable(resource: Resource): boolean {\n return isSearchableResource(resource);\n}\n\n/** The resources of the global search that the user can list. */\nexport function getSearchableResources(definition: Definition): Resource[] {\n return definition.resources.filter(\n (resource) => isSearchable(resource) && can(resource.abilities, 'viewAny'),\n );\n}\n"],"mappings":";;;;AAoCA,SAAgB,YAAY,SAAmC,SAAoC;CAC/F,OAAO,QAAQ,SAAS,WAAY,OAAO,WAAW,aAAa,OAAO,OAAO,IAAI,MAAO;AAChG;;;;;AAMA,SAAgB,aAAa,UAA6B;CACtD,OAAO,qBAAqB,QAAQ;AACxC;;AAGA,SAAgB,uBAAuB,YAAoC;CACvE,OAAO,WAAW,UAAU,QACvB,aAAa,aAAa,QAAQ,KAAK,IAAI,SAAS,WAAW,SAAS,CAC7E;AACJ"}
@@ -1 +1 @@
1
- {"version":3,"file":"messages.d.ts","sourceRoot":"","sources":["../../src/form/messages.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAgBrD;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,WAAW,GAAG,MAAM,CAE9D"}
1
+ {"version":3,"file":"messages.d.ts","sourceRoot":"","sources":["../../src/form/messages.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAiBrD;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,WAAW,GAAG,MAAM,CAE9D"}
@@ -6,6 +6,7 @@ var MESSAGES = {
6
6
  pattern: () => "This value has an invalid format.",
7
7
  email: () => "This value must be an email address.",
8
8
  url: () => "This value must be a URL.",
9
+ json: () => "This value must be valid JSON: an object or an array.",
9
10
  in: () => "This value is not one of the choices.",
10
11
  accept: () => "This file type is not accepted.",
11
12
  maxSize: () => "This file is too large.",
@@ -1 +1 @@
1
- {"version":3,"file":"messages.js","names":[],"sources":["../../src/form/messages.ts"],"sourcesContent":["import type { RuleFailure } from '@panneau/protocol';\n\nconst MESSAGES: Record<string, (failure: RuleFailure) => string> = {\n required: () => 'This field is required.',\n min: (failure) => `This value must be at least ${String(failure.value)}.`,\n max: (failure) => `This value must be at most ${String(failure.value)}.`,\n pattern: () => 'This value has an invalid format.',\n email: () => 'This value must be an email address.',\n url: () => 'This value must be a URL.',\n in: () => 'This value is not one of the choices.',\n accept: () => 'This file type is not accepted.',\n maxSize: () => 'This file is too large.',\n before: () => 'This date is too late.',\n after: () => 'This date is too early.',\n};\n\n/**\n * The message of a failed rule: the rule's own `message`, or a default English message. Forms\n * take a `formatError` option to translate them.\n */\nexport function formatRuleFailure(failure: RuleFailure): string {\n return failure.message ?? MESSAGES[failure.rule]?.(failure) ?? 'This value is invalid.';\n}\n"],"mappings":";AAEA,IAAM,WAA6D;CAC/D,gBAAgB;CAChB,MAAM,YAAY,+BAA+B,OAAO,QAAQ,KAAK,EAAC;CACtE,MAAM,YAAY,8BAA8B,OAAO,QAAQ,KAAK,EAAC;CACrE,eAAe;CACf,aAAa;CACb,WAAW;CACX,UAAU;CACV,cAAc;CACd,eAAe;CACf,cAAc;CACd,aAAa;AACjB;;;;;AAMA,SAAgB,kBAAkB,SAA8B;CAC5D,OAAO,QAAQ,WAAW,SAAS,QAAQ,KAAK,GAAG,OAAO,KAAK;AACnE"}
1
+ {"version":3,"file":"messages.js","names":[],"sources":["../../src/form/messages.ts"],"sourcesContent":["import type { RuleFailure } from '@panneau/protocol';\n\nconst MESSAGES: Record<string, (failure: RuleFailure) => string> = {\n required: () => 'This field is required.',\n min: (failure) => `This value must be at least ${String(failure.value)}.`,\n max: (failure) => `This value must be at most ${String(failure.value)}.`,\n pattern: () => 'This value has an invalid format.',\n email: () => 'This value must be an email address.',\n url: () => 'This value must be a URL.',\n json: () => 'This value must be valid JSON: an object or an array.',\n in: () => 'This value is not one of the choices.',\n accept: () => 'This file type is not accepted.',\n maxSize: () => 'This file is too large.',\n before: () => 'This date is too late.',\n after: () => 'This date is too early.',\n};\n\n/**\n * The message of a failed rule: the rule's own `message`, or a default English message. Forms\n * take a `formatError` option to translate them.\n */\nexport function formatRuleFailure(failure: RuleFailure): string {\n return failure.message ?? MESSAGES[failure.rule]?.(failure) ?? 'This value is invalid.';\n}\n"],"mappings":";AAEA,IAAM,WAA6D;CAC/D,gBAAgB;CAChB,MAAM,YAAY,+BAA+B,OAAO,QAAQ,KAAK,EAAC;CACtE,MAAM,YAAY,8BAA8B,OAAO,QAAQ,KAAK,EAAC;CACrE,eAAe;CACf,aAAa;CACb,WAAW;CACX,YAAY;CACZ,UAAU;CACV,cAAc;CACd,eAAe;CACf,cAAc;CACd,aAAa;AACjB;;;;;AAMA,SAAgB,kBAAkB,SAA8B;CAC5D,OAAO,QAAQ,WAAW,SAAS,QAAQ,KAAK,GAAG,OAAO,KAAK;AACnE"}
package/dist/index.d.ts CHANGED
@@ -10,6 +10,9 @@ export { type FormStore, createLocalStore } from './form/store';
10
10
  export { type List, type ListDefaults, type ListOptions, type ListSelection, type ListState, createList, } from './list/list';
11
11
  export { type MemoryRouter, type Router, createHistoryRouter, createMemoryRouter, } from './list/router';
12
12
  export { type Messages, type MessagesLoader, type PanneauPlugin, type PluginComponent, type PluginComponentKind, type PluginRegistry, type Theme, createPluginRegistry, definePlugin, loadMessages, resolveTheme, } from './plugins';
13
+ export { type Command, type CommandContext, type CommandSource, getCommands, getSearchableResources, isSearchable, } from './commands';
14
+ export { type PreviewProps } from './preview';
15
+ export { type LeaveGuard, type LeaveGuards, LeaveGuardsProvider, type LeaveGuardsProviderProps, createLeaveGuards, useLeaveGuard, useLeaveGuards, } from './react/leaveGuards';
13
16
  export { PanneauProvider, type PanneauContextValue, type PanneauProviderProps, usePanneau, usePlugins, useResource, } from './react/provider';
14
17
  export { type UseFormFieldResult, type UseFormResult, useForm, useFormField, } from './react/useForm';
15
18
  export { type UseListResult, useList } from './react/useList';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACH,GAAG,EACH,YAAY,EACZ,mBAAmB,EACnB,iBAAiB,EACjB,iBAAiB,GACpB,MAAM,wBAAwB,CAAC;AAChC,OAAO,EACH,KAAK,QAAQ,EACb,KAAK,kBAAkB,EACvB,KAAK,YAAY,EACjB,mBAAmB,EACnB,aAAa,EACb,WAAW,GACd,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACH,gBAAgB,EAChB,KAAK,cAAc,EACnB,KAAK,YAAY,EACjB,YAAY,EACZ,WAAW,GACd,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACH,KAAK,gBAAgB,EACrB,KAAK,iBAAiB,EACtB,YAAY,EACZ,eAAe,GAClB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EACH,KAAK,IAAI,EACT,KAAK,YAAY,EACjB,KAAK,WAAW,EAChB,KAAK,SAAS,EACd,KAAK,UAAU,EACf,KAAK,gBAAgB,EACrB,KAAK,aAAa,EAClB,KAAK,YAAY,EACjB,KAAK,oBAAoB,EACzB,UAAU,EACV,gBAAgB,GACnB,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AACpD,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AACnE,OAAO,EAAE,gBAAgB,EAAE,YAAY,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAC9F,OAAO,EAAE,KAAK,SAAS,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAChE,OAAO,EACH,KAAK,IAAI,EACT,KAAK,YAAY,EACjB,KAAK,WAAW,EAChB,KAAK,aAAa,EAClB,KAAK,SAAS,EACd,UAAU,GACb,MAAM,aAAa,CAAC;AACrB,OAAO,EACH,KAAK,YAAY,EACjB,KAAK,MAAM,EACX,mBAAmB,EACnB,kBAAkB,GACrB,MAAM,eAAe,CAAC;AACvB,OAAO,EACH,KAAK,QAAQ,EACb,KAAK,cAAc,EACnB,KAAK,aAAa,EAClB,KAAK,eAAe,EACpB,KAAK,mBAAmB,EACxB,KAAK,cAAc,EACnB,KAAK,KAAK,EACV,oBAAoB,EACpB,YAAY,EACZ,YAAY,EACZ,YAAY,GACf,MAAM,WAAW,CAAC;AACnB,OAAO,EACH,eAAe,EACf,KAAK,mBAAmB,EACxB,KAAK,oBAAoB,EACzB,UAAU,EACV,UAAU,EACV,WAAW,GACd,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACH,KAAK,kBAAkB,EACvB,KAAK,aAAa,EAClB,OAAO,EACP,YAAY,GACf,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,KAAK,aAAa,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAC9D,OAAO,EAAE,KAAK,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACH,GAAG,EACH,YAAY,EACZ,mBAAmB,EACnB,iBAAiB,EACjB,iBAAiB,GACpB,MAAM,wBAAwB,CAAC;AAChC,OAAO,EACH,KAAK,QAAQ,EACb,KAAK,kBAAkB,EACvB,KAAK,YAAY,EACjB,mBAAmB,EACnB,aAAa,EACb,WAAW,GACd,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACH,gBAAgB,EAChB,KAAK,cAAc,EACnB,KAAK,YAAY,EACjB,YAAY,EACZ,WAAW,GACd,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACH,KAAK,gBAAgB,EACrB,KAAK,iBAAiB,EACtB,YAAY,EACZ,eAAe,GAClB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EACH,KAAK,IAAI,EACT,KAAK,YAAY,EACjB,KAAK,WAAW,EAChB,KAAK,SAAS,EACd,KAAK,UAAU,EACf,KAAK,gBAAgB,EACrB,KAAK,aAAa,EAClB,KAAK,YAAY,EACjB,KAAK,oBAAoB,EACzB,UAAU,EACV,gBAAgB,GACnB,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AACpD,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AACnE,OAAO,EAAE,gBAAgB,EAAE,YAAY,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAC9F,OAAO,EAAE,KAAK,SAAS,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAChE,OAAO,EACH,KAAK,IAAI,EACT,KAAK,YAAY,EACjB,KAAK,WAAW,EAChB,KAAK,aAAa,EAClB,KAAK,SAAS,EACd,UAAU,GACb,MAAM,aAAa,CAAC;AACrB,OAAO,EACH,KAAK,YAAY,EACjB,KAAK,MAAM,EACX,mBAAmB,EACnB,kBAAkB,GACrB,MAAM,eAAe,CAAC;AACvB,OAAO,EACH,KAAK,QAAQ,EACb,KAAK,cAAc,EACnB,KAAK,aAAa,EAClB,KAAK,eAAe,EACpB,KAAK,mBAAmB,EACxB,KAAK,cAAc,EACnB,KAAK,KAAK,EACV,oBAAoB,EACpB,YAAY,EACZ,YAAY,EACZ,YAAY,GACf,MAAM,WAAW,CAAC;AACnB,OAAO,EACH,KAAK,OAAO,EACZ,KAAK,cAAc,EACnB,KAAK,aAAa,EAClB,WAAW,EACX,sBAAsB,EACtB,YAAY,GACf,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,KAAK,YAAY,EAAE,MAAM,WAAW,CAAC;AAC9C,OAAO,EACH,KAAK,UAAU,EACf,KAAK,WAAW,EAChB,mBAAmB,EACnB,KAAK,wBAAwB,EAC7B,iBAAiB,EACjB,aAAa,EACb,cAAc,GACjB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACH,eAAe,EACf,KAAK,mBAAmB,EACxB,KAAK,oBAAoB,EACzB,UAAU,EACV,UAAU,EACV,WAAW,GACd,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACH,KAAK,kBAAkB,EACvB,KAAK,aAAa,EAClB,OAAO,EACP,YAAY,GACf,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,KAAK,aAAa,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAC9D,OAAO,EAAE,KAAK,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC"}
package/dist/index.js CHANGED
@@ -11,7 +11,9 @@ import { createForm, getDefaultValues } from "./form/form.js";
11
11
  import { createList } from "./list/list.js";
12
12
  import { createHistoryRouter, createMemoryRouter } from "./list/router.js";
13
13
  import { createPluginRegistry, definePlugin, loadMessages, resolveTheme } from "./plugins.js";
14
+ import { getCommands, getSearchableResources, isSearchable } from "./commands.js";
15
+ import { LeaveGuardsProvider, createLeaveGuards, useLeaveGuard, useLeaveGuards } from "./react/leaveGuards.js";
14
16
  import { PanneauProvider, usePanneau, usePlugins, useResource } from "./react/provider.js";
15
17
  import { useForm, useFormField } from "./react/useForm.js";
16
18
  import { useList } from "./react/useList.js";
17
- export { DEFAULT_PER_PAGE, PanneauProvider, can, canRunAction, createForm, createHistoryRouter, createList, createLocalStore, createMemoryRouter, createPluginRegistry, definePlugin, findResource, formatRuleFailure, getActionPlacements, getDefaultValues, getItemValues, getLayoutFieldNames, getProblem, groupActions, isActionAllowedOn, isEqual, isItem, loadMessages, partitionByAction, resolveFields, resolveForm, resolveList, resolveResource, resolveTheme, selectErrorCount, selectErrors, selectValue, selectVisible, setValue, useForm, useFormField, useList, usePanneau, usePlugins, useResource };
19
+ export { DEFAULT_PER_PAGE, LeaveGuardsProvider, PanneauProvider, can, canRunAction, createForm, createHistoryRouter, createLeaveGuards, createList, createLocalStore, createMemoryRouter, createPluginRegistry, definePlugin, findResource, formatRuleFailure, getActionPlacements, getCommands, getDefaultValues, getItemValues, getLayoutFieldNames, getProblem, getSearchableResources, groupActions, isActionAllowedOn, isEqual, isItem, isSearchable, loadMessages, partitionByAction, resolveFields, resolveForm, resolveList, resolveResource, resolveTheme, selectErrorCount, selectErrors, selectValue, selectVisible, setValue, useForm, useFormField, useLeaveGuard, useLeaveGuards, useList, usePanneau, usePlugins, useResource };
package/dist/plugins.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import type { ComponentType } from 'react';
2
2
  import type { DefinitionTheme, Theme } from '@panneau/protocol';
3
+ import type { CommandSource } from './commands';
3
4
  /** A theme of `@panneau/ui`, as plain data: what plugins register (decision 0024). */
4
5
  export type { Theme };
5
6
  /**
@@ -31,13 +32,20 @@ export interface PanneauPlugin {
31
32
  widgets?: Record<string, PluginComponent>;
32
33
  /** Layout node components, for `component` nodes of form layouts. */
33
34
  components?: Record<string, PluginComponent>;
35
+ /**
36
+ * Previews of items, by name, for resources whose `features.preview` is a `component`
37
+ * (`{ "mode": "component", "component": "page" }`). They get `PreviewProps`.
38
+ */
39
+ previews?: Record<string, PluginComponent>;
40
+ /** Commands of the "Go to…" palette, or a function that makes them for the definition. */
41
+ commands?: CommandSource;
34
42
  /** UI messages, by locale. Messages of later plugins override earlier ones. */
35
43
  locales?: Record<string, MessagesLoader>;
36
44
  /** Themes, by id, that the theme of the definition can extend. */
37
45
  themes?: Record<string, Theme>;
38
46
  }
39
47
  /** The kinds of components a plugin can provide. */
40
- export type PluginComponentKind = 'fields' | 'displays' | 'listLayouts' | 'actions' | 'pages' | 'widgets' | 'components';
48
+ export type PluginComponentKind = 'fields' | 'displays' | 'listLayouts' | 'actions' | 'pages' | 'widgets' | 'components' | 'previews';
41
49
  /** Everything the plugins provide, merged. A later plugin overrides an earlier one. */
42
50
  export interface PluginRegistry {
43
51
  /** The plugins, in order. */
@@ -49,6 +57,9 @@ export interface PluginRegistry {
49
57
  pages: ReadonlyMap<string, PluginComponent>;
50
58
  widgets: ReadonlyMap<string, PluginComponent>;
51
59
  components: ReadonlyMap<string, PluginComponent>;
60
+ previews: ReadonlyMap<string, PluginComponent>;
61
+ /** The commands of the plugins, in plugin order: `getCommands()` makes them. */
62
+ commands: readonly CommandSource[];
52
63
  /** Message loaders of each locale, in plugin order. */
53
64
  locales: ReadonlyMap<string, readonly MessagesLoader[]>;
54
65
  themes: ReadonlyMap<string, Theme>;
@@ -1 +1 @@
1
- {"version":3,"file":"plugins.d.ts","sourceRoot":"","sources":["../src/plugins.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"plugins.d.ts","sourceRoot":"","sources":["../src/plugins.ts"],"names":[],"mappings":"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"}
package/dist/plugins.js CHANGED
@@ -7,7 +7,8 @@ var COMPONENT_KINDS = [
7
7
  "actions",
8
8
  "pages",
9
9
  "widgets",
10
- "components"
10
+ "components",
11
+ "previews"
11
12
  ];
12
13
  /** Declares a plugin. It returns the plugin as is, with its type checked. */
13
14
  function definePlugin(plugin) {
@@ -32,7 +33,8 @@ function createPluginRegistry(plugins) {
32
33
  plugins: [...plugins],
33
34
  ...components,
34
35
  locales,
35
- themes: new Map(plugins.flatMap((plugin) => Object.entries(plugin.themes ?? {})))
36
+ themes: new Map(plugins.flatMap((plugin) => Object.entries(plugin.themes ?? {}))),
37
+ commands: plugins.flatMap((plugin) => plugin.commands !== void 0 ? [plugin.commands] : [])
36
38
  };
37
39
  }
38
40
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"plugins.js","names":[],"sources":["../src/plugins.ts"],"sourcesContent":["import type { ComponentType } from 'react';\n\nimport type { DefinitionTheme, Theme } from '@panneau/protocol';\nimport { mergeThemes } from '@panneau/protocol/runtime';\n\n/** A theme of `@panneau/ui`, as plain data: what plugins register (decision 0024). */\nexport type { Theme };\n\n/**\n * A component provided by a plugin. Its props are defined by the package that renders it (fields,\n * lists, app), so the registry stores it as given.\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any -- see above\nexport type PluginComponent = ComponentType<any>;\n\n/** UI messages of a locale, keyed by message id. */\nexport type Messages = Record<string, string>;\n\n/** Messages, or a function that loads them (`() => import('./lang/fr.json')`). */\nexport type MessagesLoader = Messages | (() => Promise<Messages | { default: Messages }>);\n\n/** What a plugin adds to Panneau. Every part is keyed by the name the definition uses. */\nexport interface PanneauPlugin {\n /** Unique id of the plugin. */\n id: string;\n /** Field components, by field type (`text`, `page-slug`). */\n fields?: Record<string, PluginComponent>;\n /** Components that display a value in lists, by display (`date`, `page-slug`). */\n displays?: Record<string, PluginComponent>;\n /** List layouts, by id (`table`, `cards`). */\n listLayouts?: Record<string, PluginComponent>;\n /** Handlers of `client` actions, by the action's `component`. */\n actions?: Record<string, PluginComponent>;\n /** Pages, by component name (`dashboard`). */\n pages?: Record<string, PluginComponent>;\n /** Dashboard widgets, by widget type (`stat`). */\n widgets?: Record<string, PluginComponent>;\n /** Layout node components, for `component` nodes of form layouts. */\n components?: Record<string, PluginComponent>;\n /** UI messages, by locale. Messages of later plugins override earlier ones. */\n locales?: Record<string, MessagesLoader>;\n /** Themes, by id, that the theme of the definition can extend. */\n themes?: Record<string, Theme>;\n}\n\n/** The kinds of components a plugin can provide. */\nexport type PluginComponentKind =\n 'fields' | 'displays' | 'listLayouts' | 'actions' | 'pages' | 'widgets' | 'components';\n\nconst COMPONENT_KINDS: PluginComponentKind[] = [\n 'fields',\n 'displays',\n 'listLayouts',\n 'actions',\n 'pages',\n 'widgets',\n 'components',\n];\n\n/** Everything the plugins provide, merged. A later plugin overrides an earlier one. */\nexport interface PluginRegistry {\n /** The plugins, in order. */\n plugins: readonly PanneauPlugin[];\n fields: ReadonlyMap<string, PluginComponent>;\n displays: ReadonlyMap<string, PluginComponent>;\n listLayouts: ReadonlyMap<string, PluginComponent>;\n actions: ReadonlyMap<string, PluginComponent>;\n pages: ReadonlyMap<string, PluginComponent>;\n widgets: ReadonlyMap<string, PluginComponent>;\n components: ReadonlyMap<string, PluginComponent>;\n /** Message loaders of each locale, in plugin order. */\n locales: ReadonlyMap<string, readonly MessagesLoader[]>;\n themes: ReadonlyMap<string, Theme>;\n}\n\n/** Declares a plugin. It returns the plugin as is, with its type checked. */\nexport function definePlugin(plugin: PanneauPlugin): PanneauPlugin {\n return plugin;\n}\n\n/**\n * Merges plugins into a registry. Later plugins override the components and themes of earlier\n * ones, so a project can replace a built-in field. Plugin ids must be unique.\n */\nexport function createPluginRegistry(plugins: readonly PanneauPlugin[]): PluginRegistry {\n const ids = new Set<string>();\n plugins.forEach((plugin) => {\n if (ids.has(plugin.id)) {\n throw new Error(`Two plugins have the id \\`${plugin.id}\\`.`);\n }\n ids.add(plugin.id);\n });\n const components = Object.fromEntries(\n COMPONENT_KINDS.map((kind) => [\n kind,\n new Map(plugins.flatMap((plugin) => Object.entries(plugin[kind] ?? {}))),\n ]),\n ) as Record<PluginComponentKind, Map<string, PluginComponent>>;\n const locales = new Map<string, MessagesLoader[]>();\n plugins.forEach((plugin) =>\n Object.entries(plugin.locales ?? {}).forEach(([locale, loader]) => {\n locales.set(locale, [...(locales.get(locale) ?? []), loader]);\n }),\n );\n return {\n plugins: [...plugins],\n ...components,\n locales,\n themes: new Map(plugins.flatMap((plugin) => Object.entries(plugin.themes ?? {}))),\n };\n}\n\n/**\n * Loads the messages of a locale from every plugin that provides it, merged in plugin order. A\n * regional locale (`fr-CA`) falls back to its language (`fr`) for missing messages.\n */\nexport async function loadMessages(registry: PluginRegistry, locale: string): Promise<Messages> {\n const language = locale.split('-')[0] ?? locale;\n const loaders = [\n ...(language !== locale ? (registry.locales.get(language) ?? []) : []),\n ...(registry.locales.get(locale) ?? []),\n ];\n const loaded = await Promise.all(\n loaders.map(async (loader) => {\n if (typeof loader !== 'function') {\n return loader;\n }\n const result = await loader();\n return 'default' in result && typeof result.default === 'object'\n ? (result.default as Messages)\n : (result as Messages);\n }),\n );\n return Object.assign({}, ...loaded) as Messages;\n}\n\n/**\n * The theme of the admin: the themes of plugins that the definition's theme extends, in order, then\n * the definition's own tokens, tones and variants. An id no plugin registers is skipped, with a\n * warning.\n */\nexport function resolveTheme(\n theme: DefinitionTheme | undefined,\n themes: ReadonlyMap<string, Theme>,\n): Theme {\n const { colorScheme: _colorScheme, extends: ids, ...own } = theme ?? {};\n const extended = (typeof ids === 'string' ? [ids] : (ids ?? [])).flatMap((id) => {\n const found = themes.get(id);\n if (found === undefined) {\n console.warn(`[panneau] No plugin registers the theme \"${id}\".`);\n return [];\n }\n return [found];\n });\n return mergeThemes(...extended, own);\n}\n"],"mappings":";;AAiDA,IAAM,kBAAyC;CAC3C;CACA;CACA;CACA;CACA;CACA;CACA;AAAY;;AAoBhB,SAAgB,aAAa,QAAsC;CAC/D,OAAO;AACX;;;;;AAMA,SAAgB,qBAAqB,SAAmD;CACpF,MAAM,sBAAM,IAAI,IAAY;CAC5B,QAAQ,SAAS,WAAW;EACxB,IAAI,IAAI,IAAI,OAAO,EAAE,GACjB,MAAM,IAAI,MAAM,6BAA6B,OAAO,GAAE,IAAK;EAE/D,IAAI,IAAI,OAAO,EAAE;CACrB,CAAC;CACD,MAAM,aAAa,OAAO,YACtB,gBAAgB,KAAK,SAAS,CAC1B,MACA,IAAI,IAAI,QAAQ,SAAS,WAAW,OAAO,QAAQ,OAAO,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAC3E,CACL;CACA,MAAM,0BAAU,IAAI,IAA8B;CAClD,QAAQ,SAAS,WACb,OAAO,QAAQ,OAAO,WAAW,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,QAAQ,YAAY;EAC/D,QAAQ,IAAI,QAAQ,CAAC,GAAI,QAAQ,IAAI,MAAM,KAAK,CAAA,GAAK,MAAM,CAAC;CAChE,CAAC,CACL;CACA,OAAO;EACH,SAAS,CAAC,GAAG,OAAO;EACpB,GAAG;EACH;EACA,QAAQ,IAAI,IAAI,QAAQ,SAAS,WAAW,OAAO,QAAQ,OAAO,UAAU,CAAC,CAAC,CAAC,CAAC;CACpF;AACJ;;;;;AAMA,eAAsB,aAAa,UAA0B,QAAmC;CAC5F,MAAM,WAAW,OAAO,MAAM,GAAG,CAAC,CAAC,MAAM;CACzC,MAAM,UAAU,CACZ,GAAI,aAAa,SAAU,SAAS,QAAQ,IAAI,QAAQ,KAAK,CAAA,IAAM,CAAA,GACnE,GAAI,SAAS,QAAQ,IAAI,MAAM,KAAK,CAAA,CAAG;CAE3C,MAAM,SAAS,MAAM,QAAQ,IACzB,QAAQ,IAAI,OAAO,WAAW;EAC1B,IAAI,OAAO,WAAW,YAClB,OAAO;EAEX,MAAM,SAAS,MAAM,OAAO;EAC5B,OAAO,aAAa,UAAU,OAAO,OAAO,YAAY,WACjD,OAAO,UACP;CACX,CAAC,CACL;CACA,OAAO,OAAO,OAAO,CAAC,GAAG,GAAG,MAAM;AACtC;;;;;;AAOA,SAAgB,aACZ,OACA,QACK;CACL,MAAM,EAAE,aAAa,cAAc,SAAS,KAAK,GAAG,QAAQ,SAAS,CAAC;CACtE,MAAM,YAAY,OAAO,QAAQ,WAAW,CAAC,GAAG,IAAK,OAAO,CAAA,EAAA,CAAK,SAAS,OAAO;EAC7E,MAAM,QAAQ,OAAO,IAAI,EAAE;EAC3B,IAAI,UAAU,KAAA,GAAW;GACrB,QAAQ,KAAK,4CAA4C,GAAE,GAAI;GAC/D,OAAO,CAAA;EACX;EACA,OAAO,CAAC,KAAK;CACjB,CAAC;CACD,OAAO,YAAY,GAAG,UAAU,GAAG;AACvC"}
1
+ {"version":3,"file":"plugins.js","names":[],"sources":["../src/plugins.ts"],"sourcesContent":["import type { ComponentType } from 'react';\n\nimport type { DefinitionTheme, Theme } from '@panneau/protocol';\nimport { mergeThemes } from '@panneau/protocol/runtime';\n\nimport type { CommandSource } from './commands';\n\n/** A theme of `@panneau/ui`, as plain data: what plugins register (decision 0024). */\nexport type { Theme };\n\n/**\n * A component provided by a plugin. Its props are defined by the package that renders it (fields,\n * lists, app), so the registry stores it as given.\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any -- see above\nexport type PluginComponent = ComponentType<any>;\n\n/** UI messages of a locale, keyed by message id. */\nexport type Messages = Record<string, string>;\n\n/** Messages, or a function that loads them (`() => import('./lang/fr.json')`). */\nexport type MessagesLoader = Messages | (() => Promise<Messages | { default: Messages }>);\n\n/** What a plugin adds to Panneau. Every part is keyed by the name the definition uses. */\nexport interface PanneauPlugin {\n /** Unique id of the plugin. */\n id: string;\n /** Field components, by field type (`text`, `page-slug`). */\n fields?: Record<string, PluginComponent>;\n /** Components that display a value in lists, by display (`date`, `page-slug`). */\n displays?: Record<string, PluginComponent>;\n /** List layouts, by id (`table`, `cards`). */\n listLayouts?: Record<string, PluginComponent>;\n /** Handlers of `client` actions, by the action's `component`. */\n actions?: Record<string, PluginComponent>;\n /** Pages, by component name (`dashboard`). */\n pages?: Record<string, PluginComponent>;\n /** Dashboard widgets, by widget type (`stat`). */\n widgets?: Record<string, PluginComponent>;\n /** Layout node components, for `component` nodes of form layouts. */\n components?: Record<string, PluginComponent>;\n /**\n * Previews of items, by name, for resources whose `features.preview` is a `component`\n * (`{ \"mode\": \"component\", \"component\": \"page\" }`). They get `PreviewProps`.\n */\n previews?: Record<string, PluginComponent>;\n /** Commands of the \"Go to…\" palette, or a function that makes them for the definition. */\n commands?: CommandSource;\n /** UI messages, by locale. Messages of later plugins override earlier ones. */\n locales?: Record<string, MessagesLoader>;\n /** Themes, by id, that the theme of the definition can extend. */\n themes?: Record<string, Theme>;\n}\n\n/** The kinds of components a plugin can provide. */\nexport type PluginComponentKind =\n | 'fields'\n | 'displays'\n | 'listLayouts'\n | 'actions'\n | 'pages'\n | 'widgets'\n | 'components'\n | 'previews';\n\nconst COMPONENT_KINDS: PluginComponentKind[] = [\n 'fields',\n 'displays',\n 'listLayouts',\n 'actions',\n 'pages',\n 'widgets',\n 'components',\n 'previews',\n];\n\n/** Everything the plugins provide, merged. A later plugin overrides an earlier one. */\nexport interface PluginRegistry {\n /** The plugins, in order. */\n plugins: readonly PanneauPlugin[];\n fields: ReadonlyMap<string, PluginComponent>;\n displays: ReadonlyMap<string, PluginComponent>;\n listLayouts: ReadonlyMap<string, PluginComponent>;\n actions: ReadonlyMap<string, PluginComponent>;\n pages: ReadonlyMap<string, PluginComponent>;\n widgets: ReadonlyMap<string, PluginComponent>;\n components: ReadonlyMap<string, PluginComponent>;\n previews: ReadonlyMap<string, PluginComponent>;\n /** The commands of the plugins, in plugin order: `getCommands()` makes them. */\n commands: readonly CommandSource[];\n /** Message loaders of each locale, in plugin order. */\n locales: ReadonlyMap<string, readonly MessagesLoader[]>;\n themes: ReadonlyMap<string, Theme>;\n}\n\n/** Declares a plugin. It returns the plugin as is, with its type checked. */\nexport function definePlugin(plugin: PanneauPlugin): PanneauPlugin {\n return plugin;\n}\n\n/**\n * Merges plugins into a registry. Later plugins override the components and themes of earlier\n * ones, so a project can replace a built-in field. Plugin ids must be unique.\n */\nexport function createPluginRegistry(plugins: readonly PanneauPlugin[]): PluginRegistry {\n const ids = new Set<string>();\n plugins.forEach((plugin) => {\n if (ids.has(plugin.id)) {\n throw new Error(`Two plugins have the id \\`${plugin.id}\\`.`);\n }\n ids.add(plugin.id);\n });\n const components = Object.fromEntries(\n COMPONENT_KINDS.map((kind) => [\n kind,\n new Map(plugins.flatMap((plugin) => Object.entries(plugin[kind] ?? {}))),\n ]),\n ) as Record<PluginComponentKind, Map<string, PluginComponent>>;\n const locales = new Map<string, MessagesLoader[]>();\n plugins.forEach((plugin) =>\n Object.entries(plugin.locales ?? {}).forEach(([locale, loader]) => {\n locales.set(locale, [...(locales.get(locale) ?? []), loader]);\n }),\n );\n return {\n plugins: [...plugins],\n ...components,\n locales,\n themes: new Map(plugins.flatMap((plugin) => Object.entries(plugin.themes ?? {}))),\n commands: plugins.flatMap((plugin) =>\n plugin.commands !== undefined ? [plugin.commands] : [],\n ),\n };\n}\n\n/**\n * Loads the messages of a locale from every plugin that provides it, merged in plugin order. A\n * regional locale (`fr-CA`) falls back to its language (`fr`) for missing messages.\n */\nexport async function loadMessages(registry: PluginRegistry, locale: string): Promise<Messages> {\n const language = locale.split('-')[0] ?? locale;\n const loaders = [\n ...(language !== locale ? (registry.locales.get(language) ?? []) : []),\n ...(registry.locales.get(locale) ?? []),\n ];\n const loaded = await Promise.all(\n loaders.map(async (loader) => {\n if (typeof loader !== 'function') {\n return loader;\n }\n const result = await loader();\n return 'default' in result && typeof result.default === 'object'\n ? (result.default as Messages)\n : (result as Messages);\n }),\n );\n return Object.assign({}, ...loaded) as Messages;\n}\n\n/**\n * The theme of the admin: the themes of plugins that the definition's theme extends, in order, then\n * the definition's own tokens, tones and variants. An id no plugin registers is skipped, with a\n * warning.\n */\nexport function resolveTheme(\n theme: DefinitionTheme | undefined,\n themes: ReadonlyMap<string, Theme>,\n): Theme {\n const { colorScheme: _colorScheme, extends: ids, ...own } = theme ?? {};\n const extended = (typeof ids === 'string' ? [ids] : (ids ?? [])).flatMap((id) => {\n const found = themes.get(id);\n if (found === undefined) {\n console.warn(`[panneau] No plugin registers the theme \"${id}\".`);\n return [];\n }\n return [found];\n });\n return mergeThemes(...extended, own);\n}\n"],"mappings":";;AAiEA,IAAM,kBAAyC;CAC3C;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AAAU;;AAuBd,SAAgB,aAAa,QAAsC;CAC/D,OAAO;AACX;;;;;AAMA,SAAgB,qBAAqB,SAAmD;CACpF,MAAM,sBAAM,IAAI,IAAY;CAC5B,QAAQ,SAAS,WAAW;EACxB,IAAI,IAAI,IAAI,OAAO,EAAE,GACjB,MAAM,IAAI,MAAM,6BAA6B,OAAO,GAAE,IAAK;EAE/D,IAAI,IAAI,OAAO,EAAE;CACrB,CAAC;CACD,MAAM,aAAa,OAAO,YACtB,gBAAgB,KAAK,SAAS,CAC1B,MACA,IAAI,IAAI,QAAQ,SAAS,WAAW,OAAO,QAAQ,OAAO,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAC3E,CACL;CACA,MAAM,0BAAU,IAAI,IAA8B;CAClD,QAAQ,SAAS,WACb,OAAO,QAAQ,OAAO,WAAW,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,QAAQ,YAAY;EAC/D,QAAQ,IAAI,QAAQ,CAAC,GAAI,QAAQ,IAAI,MAAM,KAAK,CAAA,GAAK,MAAM,CAAC;CAChE,CAAC,CACL;CACA,OAAO;EACH,SAAS,CAAC,GAAG,OAAO;EACpB,GAAG;EACH;EACA,QAAQ,IAAI,IAAI,QAAQ,SAAS,WAAW,OAAO,QAAQ,OAAO,UAAU,CAAC,CAAC,CAAC,CAAC;EAChF,UAAU,QAAQ,SAAS,WACvB,OAAO,aAAa,KAAA,IAAY,CAAC,OAAO,QAAQ,IAAI,CAAA,CACxD;CACJ;AACJ;;;;;AAMA,eAAsB,aAAa,UAA0B,QAAmC;CAC5F,MAAM,WAAW,OAAO,MAAM,GAAG,CAAC,CAAC,MAAM;CACzC,MAAM,UAAU,CACZ,GAAI,aAAa,SAAU,SAAS,QAAQ,IAAI,QAAQ,KAAK,CAAA,IAAM,CAAA,GACnE,GAAI,SAAS,QAAQ,IAAI,MAAM,KAAK,CAAA,CAAG;CAE3C,MAAM,SAAS,MAAM,QAAQ,IACzB,QAAQ,IAAI,OAAO,WAAW;EAC1B,IAAI,OAAO,WAAW,YAClB,OAAO;EAEX,MAAM,SAAS,MAAM,OAAO;EAC5B,OAAO,aAAa,UAAU,OAAO,OAAO,YAAY,WACjD,OAAO,UACP;CACX,CAAC,CACL;CACA,OAAO,OAAO,OAAO,CAAC,GAAG,GAAG,MAAM;AACtC;;;;;;AAOA,SAAgB,aACZ,OACA,QACK;CACL,MAAM,EAAE,aAAa,cAAc,SAAS,KAAK,GAAG,QAAQ,SAAS,CAAC;CACtE,MAAM,YAAY,OAAO,QAAQ,WAAW,CAAC,GAAG,IAAK,OAAO,CAAA,EAAA,CAAK,SAAS,OAAO;EAC7E,MAAM,QAAQ,OAAO,IAAI,EAAE;EAC3B,IAAI,UAAU,KAAA,GAAW;GACrB,QAAQ,KAAK,4CAA4C,GAAE,GAAI;GAC/D,OAAO,CAAA;EACX;EACA,OAAO,CAAC,KAAK;CACjB,CAAC;CACD,OAAO,YAAY,GAAG,UAAU,GAAG;AACvC"}
@@ -0,0 +1,14 @@
1
+ import type { Item, Resource } from '@panneau/protocol';
2
+ /**
3
+ * Props of a preview component (the `previews` of a plugin), for a resource whose
4
+ * `features.preview` is `{ "mode": "component", "component": "<name>" }`. It renders the item as
5
+ * the site would, or anything richer than its values: blocks, partials, charts.
6
+ */
7
+ export interface PreviewProps {
8
+ /** The item: as saved, or with the unsaved values of a form. */
9
+ item: Item;
10
+ resource: Resource;
11
+ /** The locale of the content to show, for localized values. */
12
+ locale: string;
13
+ }
14
+ //# sourceMappingURL=preview.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"preview.d.ts","sourceRoot":"","sources":["../src/preview.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAExD;;;;GAIG;AACH,MAAM,WAAW,YAAY;IACzB,gEAAgE;IAChE,IAAI,EAAE,IAAI,CAAC;IACX,QAAQ,EAAE,QAAQ,CAAC;IACnB,+DAA+D;IAC/D,MAAM,EAAE,MAAM,CAAC;CAClB"}
@@ -0,0 +1,34 @@
1
+ import { type ReactNode } from 'react';
2
+ /** Asks whether the user may leave: resolves `true` to leave, `false` to stay. */
3
+ export type LeaveGuard = () => Promise<boolean>;
4
+ /**
5
+ * What guards in-app navigation: forms with unsaved changes add a guard while they are dirty,
6
+ * and the router asks them before it leaves the page.
7
+ */
8
+ export interface LeaveGuards {
9
+ /** Adds a guard. Returns a function that removes it. */
10
+ add(guard: LeaveGuard): () => void;
11
+ /** Whether a guard would ask: leaving now needs a confirmation. */
12
+ isBlocking(): boolean;
13
+ /** Asks each guard in turn. Resolves `true` when every one lets the user leave. */
14
+ confirmLeave(): Promise<boolean>;
15
+ }
16
+ /** Creates the guards of an app, for `LeaveGuardsProvider`. */
17
+ export declare function createLeaveGuards(): LeaveGuards;
18
+ export interface LeaveGuardsProviderProps {
19
+ guards: LeaveGuards;
20
+ children?: ReactNode;
21
+ }
22
+ /**
23
+ * Gives the guards of the app's router to the forms below it, so that leaving a page with unsaved
24
+ * changes asks first. Without it, forms only guard closing and reloading the page.
25
+ */
26
+ export declare function LeaveGuardsProvider({ guards, children }: LeaveGuardsProviderProps): import("react").JSX.Element;
27
+ /** The guards of the closest `LeaveGuardsProvider`, or `null`. */
28
+ export declare function useLeaveGuards(): LeaveGuards | null;
29
+ /**
30
+ * Adds `guard` to the guards of the closest `LeaveGuardsProvider` while `active` is true, such
31
+ * as while a form has unsaved changes. The latest `guard` is the one asked.
32
+ */
33
+ export declare function useLeaveGuard(active: boolean, guard: LeaveGuard): void;
34
+ //# sourceMappingURL=leaveGuards.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"leaveGuards.d.ts","sourceRoot":"","sources":["../../src/react/leaveGuards.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,SAAS,EAAyC,MAAM,OAAO,CAAC;AAE9E,kFAAkF;AAClF,MAAM,MAAM,UAAU,GAAG,MAAM,OAAO,CAAC,OAAO,CAAC,CAAC;AAEhD;;;GAGG;AACH,MAAM,WAAW,WAAW;IACxB,wDAAwD;IACxD,GAAG,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,IAAI,CAAC;IACnC,mEAAmE;IACnE,UAAU,IAAI,OAAO,CAAC;IACtB,mFAAmF;IACnF,YAAY,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC;CACpC;AAED,+DAA+D;AAC/D,wBAAgB,iBAAiB,IAAI,WAAW,CAmB/C;AAID,MAAM,WAAW,wBAAwB;IACrC,MAAM,EAAE,WAAW,CAAC;IACpB,QAAQ,CAAC,EAAE,SAAS,CAAC;CACxB;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,EAAE,wBAAwB,+BAEjF;AAED,kEAAkE;AAClE,wBAAgB,cAAc,IAAI,WAAW,GAAG,IAAI,CAEnD;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,UAAU,GAAG,IAAI,CAYtE"}
@@ -0,0 +1,84 @@
1
+ import { c } from "react/compiler-runtime";
2
+ import { createContext, use, useEffect, useRef } from "react";
3
+ import { jsx } from "react/jsx-runtime";
4
+ //#region src/react/leaveGuards.tsx
5
+ /** Creates the guards of an app, for `LeaveGuardsProvider`. */
6
+ function createLeaveGuards() {
7
+ const guards = /* @__PURE__ */ new Set();
8
+ return {
9
+ add(guard) {
10
+ guards.add(guard);
11
+ return () => {
12
+ guards.delete(guard);
13
+ };
14
+ },
15
+ isBlocking: () => guards.size > 0,
16
+ async confirmLeave() {
17
+ for (const guard of [...guards]) if (!await guard()) return false;
18
+ return true;
19
+ }
20
+ };
21
+ }
22
+ var LeaveGuardsContext = createContext(null);
23
+ /**
24
+ * Gives the guards of the app's router to the forms below it, so that leaving a page with unsaved
25
+ * changes asks first. Without it, forms only guard closing and reloading the page.
26
+ */
27
+ function LeaveGuardsProvider(t0) {
28
+ const $ = c(3);
29
+ const { guards, children } = t0;
30
+ let t1;
31
+ if ($[0] !== children || $[1] !== guards) {
32
+ t1 = /* @__PURE__ */ jsx(LeaveGuardsContext, {
33
+ value: guards,
34
+ children
35
+ });
36
+ $[0] = children;
37
+ $[1] = guards;
38
+ $[2] = t1;
39
+ } else t1 = $[2];
40
+ return t1;
41
+ }
42
+ /** The guards of the closest `LeaveGuardsProvider`, or `null`. */
43
+ function useLeaveGuards() {
44
+ return use(LeaveGuardsContext);
45
+ }
46
+ /**
47
+ * Adds `guard` to the guards of the closest `LeaveGuardsProvider` while `active` is true, such
48
+ * as while a form has unsaved changes. The latest `guard` is the one asked.
49
+ */
50
+ function useLeaveGuard(active, guard) {
51
+ const $ = c(6);
52
+ const guards = useLeaveGuards();
53
+ const latest = useRef(guard);
54
+ let t0;
55
+ if ($[0] !== guard) {
56
+ t0 = () => {
57
+ latest.current = guard;
58
+ };
59
+ $[0] = guard;
60
+ $[1] = t0;
61
+ } else t0 = $[1];
62
+ useEffect(t0);
63
+ let t1;
64
+ let t2;
65
+ if ($[2] !== active || $[3] !== guards) {
66
+ t1 = () => {
67
+ if (!active || guards === null) return;
68
+ return guards.add(() => latest.current());
69
+ };
70
+ t2 = [active, guards];
71
+ $[2] = active;
72
+ $[3] = guards;
73
+ $[4] = t1;
74
+ $[5] = t2;
75
+ } else {
76
+ t1 = $[4];
77
+ t2 = $[5];
78
+ }
79
+ useEffect(t1, t2);
80
+ }
81
+ //#endregion
82
+ export { LeaveGuardsProvider, createLeaveGuards, useLeaveGuard, useLeaveGuards };
83
+
84
+ //# sourceMappingURL=leaveGuards.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"leaveGuards.js","names":["guards","children","_c"],"sources":["../../src/react/leaveGuards.tsx"],"sourcesContent":["import { type ReactNode, createContext, use, useEffect, useRef } from 'react';\n\n/** Asks whether the user may leave: resolves `true` to leave, `false` to stay. */\nexport type LeaveGuard = () => Promise<boolean>;\n\n/**\n * What guards in-app navigation: forms with unsaved changes add a guard while they are dirty,\n * and the router asks them before it leaves the page.\n */\nexport interface LeaveGuards {\n /** Adds a guard. Returns a function that removes it. */\n add(guard: LeaveGuard): () => void;\n /** Whether a guard would ask: leaving now needs a confirmation. */\n isBlocking(): boolean;\n /** Asks each guard in turn. Resolves `true` when every one lets the user leave. */\n confirmLeave(): Promise<boolean>;\n}\n\n/** Creates the guards of an app, for `LeaveGuardsProvider`. */\nexport function createLeaveGuards(): LeaveGuards {\n const guards = new Set<LeaveGuard>();\n return {\n add(guard) {\n guards.add(guard);\n return () => {\n guards.delete(guard);\n };\n },\n isBlocking: () => guards.size > 0,\n async confirmLeave() {\n for (const guard of [...guards]) {\n if (!(await guard())) {\n return false;\n }\n }\n return true;\n },\n };\n}\n\nconst LeaveGuardsContext = createContext<LeaveGuards | null>(null);\n\nexport interface LeaveGuardsProviderProps {\n guards: LeaveGuards;\n children?: ReactNode;\n}\n\n/**\n * Gives the guards of the app's router to the forms below it, so that leaving a page with unsaved\n * changes asks first. Without it, forms only guard closing and reloading the page.\n */\nexport function LeaveGuardsProvider({ guards, children }: LeaveGuardsProviderProps) {\n return <LeaveGuardsContext value={guards}>{children}</LeaveGuardsContext>;\n}\n\n/** The guards of the closest `LeaveGuardsProvider`, or `null`. */\nexport function useLeaveGuards(): LeaveGuards | null {\n return use(LeaveGuardsContext);\n}\n\n/**\n * Adds `guard` to the guards of the closest `LeaveGuardsProvider` while `active` is true, such\n * as while a form has unsaved changes. The latest `guard` is the one asked.\n */\nexport function useLeaveGuard(active: boolean, guard: LeaveGuard): void {\n const guards = useLeaveGuards();\n const latest = useRef(guard);\n useEffect(() => {\n latest.current = guard;\n });\n useEffect(() => {\n if (!active || guards === null) {\n return undefined;\n }\n return guards.add(() => latest.current());\n }, [active, guards]);\n}\n"],"mappings":";;;;;AAmBA,SAAgB,oBAAiC;CAC7C,MAAM,yBAAS,IAAI,IAAgB;CACnC,OAAO;EACH,IAAI,OAAO;GACP,OAAO,IAAI,KAAK;GAChB,aAAa;IACT,OAAO,OAAO,KAAK;GACvB;EACJ;EACA,kBAAkB,OAAO,OAAO;EAChC,MAAM,eAAe;GACjB,KAAK,MAAM,SAAS,CAAC,GAAG,MAAM,GAC1B,IAAI,CAAE,MAAM,MAAM,GACd,OAAO;GAGf,OAAO;EACX;CACJ;AACJ;AAEA,IAAM,qBAAqB,cAAkC,IAAI;;;;;AAWjE,SAAO,oBAAA,IAAA;CAAA,MAAA,IAAAE,EAAA,CAAA;CAA6B,MAAA,EAAA,QAAA,aAAA;CAA8C,IAAA;CAAA,IAAA,EAAA,OAAA,YAAA,EAAA,OAAA,QAAA;EACvE,KAAA,oBAAC,oBAAD;GAA2BF,OAAA;GAAS;EAAjB,CAAA;EAA+C,EAAA,KAAA;EAAA,EAAA,KAAA;EAAA,EAAA,KAAA;CAAA,OAAA,KAAA,EAAA;CAAA,OAAlE;AAAkE;;AAI7E,SAAgB,iBAAqC;CACjD,OAAO,IAAI,kBAAkB;AACjC;;;;;AAMA,SAAO,cAAA,QAAA,OAAA;CAAA,MAAA,IAAAE,EAAA,CAAA;CACH,MAAA,SAAe,eAAe;CAC9B,MAAA,SAAe,OAAO,KAAK;CAAE,IAAA;CAAA,IAAA,EAAA,OAAA,OAAA;EACnB,WAAA;GACN,OAAM,UAAW;EAAH;EACjB,EAAA,KAAA;EAAA,EAAA,KAAA;CAAA,OAAA,KAAA,EAAA;CAFD,UAAU,EAET;CAAC,IAAA;CAAA,IAAA;CAAA,IAAA,EAAA,OAAA,UAAA,EAAA,OAAA,QAAA;EACQ,WAAA;GACN,IAAI,CAAC,UAAU,WAAW,MAAI;GAE7B,OACM,OAAM,UAAW,OAAM,QAAS,CAAC;EAAC;EAC1C,KAAA,CAAC,QAAQ,MAAM;EAAC,EAAA,KAAA;EAAA,EAAA,KAAA;EAAA,EAAA,KAAA;EAAA,EAAA,KAAA;CAAA,OAAA;EAAA,KAAA,EAAA;EAAA,KAAA,EAAA;CAAA;CALnB,UAAU,IAKP,EAAgB;AAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@panneau/core",
3
- "version": "5.0.0-alpha.4",
3
+ "version": "5.0.0-alpha.6",
4
4
  "description": "The engine of Panneau: plugin registry, definition to forms and lists, form and list state, with React hooks. No UI.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -33,6 +33,6 @@
33
33
  "react": "^19.2.0"
34
34
  },
35
35
  "dependencies": {
36
- "@panneau/protocol": "5.0.0-alpha.4"
36
+ "@panneau/protocol": "5.0.0-alpha.6"
37
37
  }
38
38
  }