@jfdevelops/react-layout 0.23.0 → 0.24.1

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.
@@ -1,4 +1,5 @@
1
1
  const require_layout_configuration_error = require('../errors/layout-configuration-error.cjs');
2
+ const require_resource = require('../resource.cjs');
2
3
 
3
4
  //#region src/create-config/create-resource-links.ts
4
5
  function resolveResourceLinkHref(value, resource, fallback) {
@@ -55,15 +56,46 @@ function createResourceLinkGroupId(group, index) {
55
56
  if (typeof group.id === "string" && group.id.length > 0) return group.id;
56
57
  return `group-${index}-${typeof group.label === "string" ? group.label : ""}-${Object.keys(group.links).join(",")}`;
57
58
  }
59
+ function readResourceValue(resource) {
60
+ return typeof resource === "string" ? resource : resource.value;
61
+ }
62
+ function createResourceSelection(definitions) {
63
+ const selection = (() => definitions);
64
+ selection.isResource = require_resource.createIsValidResourceFn(definitions);
65
+ return selection;
66
+ }
67
+ function createRenderResourcesAccessor(resources, current) {
68
+ const accessor = Object.assign(() => resources, { current });
69
+ accessor.isResource = require_resource.createIsValidResourceFn(resources);
70
+ accessor.omit = ((...keys) => createResourceSelection(resources.filter((resource) => !keys.includes(readResourceValue(resource)))));
71
+ accessor.pick = ((...keys) => createResourceSelection(resources.filter((resource) => keys.includes(readResourceValue(resource)))));
72
+ return accessor;
73
+ }
58
74
  function createResourceMapFn(resources) {
75
+ const helperNames = new Set(["get", "isValidResource"]);
59
76
  const resourceKeys = new Set(resources.map((resource) => typeof resource === "string" ? resource : resource.value));
60
77
  return function createResourceMap(map) {
61
- for (const resource of Object.keys(map)) if (!resourceKeys.has(resource)) throw new require_layout_configuration_error.LayoutConfigurationError(`[createResourceLinks.createResourceMap]: unknown resource "${resource}".`);
78
+ const configuredResources = Object.keys(map);
79
+ for (const resource of configuredResources) {
80
+ if (!resourceKeys.has(resource)) throw new require_layout_configuration_error.LayoutConfigurationError(`[createResourceLinks.createResourceMap]: unknown resource "${resource}".`);
81
+ if (helperNames.has(resource)) throw new require_layout_configuration_error.LayoutConfigurationError(`[createResourceLinks.createResourceMap]: resource "${resource}" conflicts with a map helper.`);
82
+ }
83
+ const configuredResourceKeys = new Set(configuredResources);
84
+ const resourceValues = new Map(configuredResources.map((resource) => [resource, map[resource]]));
85
+ Object.defineProperties(map, {
86
+ get: { value(resource) {
87
+ return resourceValues.get(resource);
88
+ } },
89
+ isValidResource: { value(value) {
90
+ return typeof value === "string" && configuredResourceKeys.has(value);
91
+ } }
92
+ });
62
93
  return map;
63
94
  };
64
95
  }
65
- function createResourceLinksWithGroups(createResourceMap, groups) {
96
+ function createResourceLinksWithGroups(resources, createResourceMap, groups) {
66
97
  if (!Array.isArray(groups)) throw new require_layout_configuration_error.LayoutConfigurationError("[createResourceLinks.withGroups]: \"groups\" must be an array.");
98
+ if (groups.length === 0) throw new require_layout_configuration_error.LayoutConfigurationError("[createResourceLinks.withGroups]: \"groups\" must include at least one group.");
67
99
  const createdGroups = groups.map((group, index) => {
68
100
  if (!group) throw new require_layout_configuration_error.LayoutConfigurationError(`[createResourceLinks.withGroups]: group at index ${index} is required.`);
69
101
  if (typeof group !== "object") throw new require_layout_configuration_error.LayoutConfigurationError(`[createResourceLinks.withGroups]: group at index ${index} must be an object. Received ${typeof group}`);
@@ -79,22 +111,95 @@ function createResourceLinksWithGroups(createResourceMap, groups) {
79
111
  links: createResourceLinksFromConfig(group.links)
80
112
  };
81
113
  });
82
- Object.defineProperty(createdGroups, "createResourceMap", { value: createResourceMap });
114
+ function resolveGroups(resource) {
115
+ if (resource === void 0) throw new require_layout_configuration_error.LayoutConfigurationError("[createResourceLinks.withGroups]: \"resource\" is required.");
116
+ const currentResources = createRenderResourcesAccessor(resources, resource);
117
+ return createdGroups.map((group) => ({
118
+ ...group,
119
+ icon: typeof group.icon === "function" ? group.icon(currentResources) : group.icon,
120
+ links: group.links.map((link) => ({
121
+ ...link,
122
+ icon: typeof link.icon === "function" ? link.icon(currentResources) : link.icon
123
+ }))
124
+ }));
125
+ }
126
+ const callableGroups = new Proxy(resolveGroups, {
127
+ get(target, property, receiver) {
128
+ if (property === "createResourceMap") return createResourceMap;
129
+ if (property === Symbol.iterator) return createdGroups[Symbol.iterator].bind(createdGroups);
130
+ if (property === "toJSON") return () => createdGroups;
131
+ if (property in createdGroups) {
132
+ const value = Reflect.get(createdGroups, property);
133
+ return typeof value === "function" ? value.bind(createdGroups) : value;
134
+ }
135
+ return Reflect.get(target, property, receiver);
136
+ },
137
+ getOwnPropertyDescriptor(target, property) {
138
+ return Reflect.getOwnPropertyDescriptor(createdGroups, property) ?? Reflect.getOwnPropertyDescriptor(target, property);
139
+ },
140
+ getPrototypeOf() {
141
+ return Array.prototype;
142
+ },
143
+ ownKeys(target) {
144
+ return [...new Set([...Reflect.ownKeys(target), ...Reflect.ownKeys(createdGroups)])];
145
+ },
146
+ set(target, property, value, receiver) {
147
+ if (property in createdGroups || /^\d+$/.test(String(property))) return Reflect.set(createdGroups, property, value);
148
+ return Reflect.set(target, property, value, receiver);
149
+ }
150
+ });
83
151
  Object.defineProperty(createdGroups, "findResource", { value(resource) {
84
152
  return createdGroups.find((group) => group.links.some((link) => link.resource === resource));
85
153
  } });
86
- return createdGroups;
154
+ return callableGroups;
155
+ }
156
+ function createDefineIcon() {
157
+ function defineIcon(render) {
158
+ return function createIcon(Icon) {
159
+ return function renderIcon(resource) {
160
+ return render({
161
+ resource,
162
+ Icon
163
+ });
164
+ };
165
+ };
166
+ }
167
+ defineIcon.input = function defineIconInput(defineInput) {
168
+ const resourceMarker = {};
169
+ const componentMarker = {};
170
+ defineInput({
171
+ resource() {
172
+ return resourceMarker;
173
+ },
174
+ icon() {
175
+ return componentMarker;
176
+ }
177
+ });
178
+ return { render(render) {
179
+ return function createIcon(input) {
180
+ return function renderIcon(resource) {
181
+ return render({
182
+ resource,
183
+ input
184
+ });
185
+ };
186
+ };
187
+ } };
188
+ };
189
+ return defineIcon;
87
190
  }
88
191
  function createResourceLinksFn(_resources) {
89
192
  const createResourceMap = createResourceMapFn(_resources);
193
+ const defineIcon = createDefineIcon();
90
194
  function createResourceLinks(config) {
91
195
  return createResourceLinksFromConfig(config);
92
196
  }
93
197
  function withGroups(groups) {
94
- return createResourceLinksWithGroups(createResourceMap, groups);
198
+ return createResourceLinksWithGroups(_resources, createResourceMap, groups);
95
199
  }
96
200
  return Object.assign(createResourceLinks, {
97
201
  createResourceMap,
202
+ defineIcon,
98
203
  withGroups
99
204
  });
100
205
  }
@@ -1 +1 @@
1
- {"version":3,"file":"create-resource-links.cjs","names":["LayoutConfigurationError"],"sources":["../../src/create-config/create-resource-links.ts"],"sourcesContent":["import { ReactNode } from 'react';\nimport {\n type LayoutResourceKey,\n type ResourceDefinition,\n} from '../resource';\nimport { LayoutConfigurationError } from '../errors';\n\nexport type ResourceAnchorLinkFn<Resource extends string> = (\n resource: Resource,\n) => string;\n\nexport type ResourceLinkHref<Resource extends string> =\n | string\n | ResourceAnchorLinkFn<Resource>;\n\nexport type ResourceLinkConfigResource<\n Resources extends ReadonlyArray<ResourceDefinition>,\n> = LayoutResourceKey<Resources> | (string & {});\nexport interface CreateResourceLinkOptions<\n Resources extends ReadonlyArray<ResourceDefinition>,\n Resource extends ResourceLinkConfigResource<Resources>,\n> {\n /**\n * The label of the link. This is the text that will be displayed in the link.\n */\n label: string;\n /**\n * An optional icon to render when the link is active.\n */\n activeIcon?: ReactNode;\n /**\n * An optional icon to register with the link. This is a headless approach, meaning you have full control\n * over the icon's rendering.\n */\n icon?: ReactNode;\n /**\n * The href of the link. By default, the href will be `\"/\"`. While you **don't** need to include a hash,\n * if you do, it will override the `hash` property (if provided).\n */\n href?: ResourceLinkHref<Resource>;\n /**\n * The hash of the link. By default, the hash will be the resource name. If you need to customize it, you can provide\n * a string or a function that returns a string.\n *\n * @remarks A `\"#\"` is not required. The generated href will include it automatically.\n */\n hash?: ResourceLinkHref<Resource>;\n}\n\nexport type CreateResourceLinkConfig<\n Resources extends ReadonlyArray<ResourceDefinition>,\n Resource extends ResourceLinkConfigResource<Resources> = ResourceLinkConfigResource<Resources>,\n> = {\n [Key in Resource]?: CreateResourceLinkOptions<\n Resources,\n Key\n >;\n};\n\nexport type InferHashFromResourceLinkHref<\n Resource extends string,\n Hash extends ResourceLinkHref<Resource>,\n> = Hash extends string\n ? Hash\n : Hash extends ResourceAnchorLinkFn<Resource>\n ? ReturnType<Hash>\n : never;\n\nexport type CreatedResourceHref = {\n /**\n * The full generated href with the `hash` if provided.\n */\n full: string;\n /**\n * The given href. This is the same value as the `href` property of the config.\n */\n given: string;\n};\n\nexport type CreatedResourceLinkBase<Resource extends string> = {\n activeIcon?: ReactNode;\n href: CreatedResourceHref;\n label: string;\n resource: Resource;\n icon: ReactNode;\n};\nexport type CreateResourceLinkWithHash<\n Resource extends string,\n Hash extends ResourceLinkHref<Resource>,\n> = Omit<CreatedResourceLinkBase<Resource>, 'href'> & {\n href: CreatedResourceHref & {\n hash: InferHashFromResourceLinkHref<Resource, Hash>;\n };\n};\n\nexport type CreatedResourceLink<\n Resources extends ReadonlyArray<ResourceDefinition>,\n Config,\n> = {\n [Resource in keyof Config]-?: Resource extends ResourceLinkConfigResource<Resources>\n ? Config[Resource] extends {\n hash: infer hash extends ResourceLinkHref<Resource>;\n }\n ? CreateResourceLinkWithHash<Resource, hash>\n : CreatedResourceLinkBase<Resource>\n : never;\n}[keyof Config];\n\nexport interface CreateResourceLinksFn<\n Resources extends ReadonlyArray<ResourceDefinition>,\n> {\n <const Resource extends ResourceLinkConfigResource<Resources>>(\n config: CreateResourceLinkConfig<Resources, Resource>,\n ): Array<\n CreatedResourceLink<\n Resources,\n CreateResourceLinkConfig<Resources, Resource>\n >\n >;\n createResourceMap: CreateResourceMapFn<Resources>;\n withGroups: CreateResourceLinksWithGroupsFn<Resources>;\n}\n\nexport interface CreateResourceLinkGroupOptions<\n Resources extends ReadonlyArray<ResourceDefinition>,\n Links extends object =\n CreateResourceLinkConfig<Resources>,\n> {\n /**\n * An optional, caller-provided identity for the group. Provide this when you\n * combine groups from more than one `withGroups()` call into one list (e.g.\n * a module-owned nav section alongside a feature-owned one) — without it,\n * the id is derived from the group's `label` and its link resources, which\n * is enough to keep groups from a single call apart but can't distinguish\n * two independently-defined groups that happen to look identical.\n */\n id?: string;\n /**\n * The label of the group. This is the text that will be displayed for the group.\n */\n label?: string;\n /**\n * An optional icon to render when the group is active.\n */\n activeIcon?: ReactNode;\n /**\n * An optional icon to register with the group. This is a headless approach, meaning you have full control\n * over the icon's rendering.\n */\n icon?: ReactNode;\n /**\n * The resource links in this group.\n */\n links: Links;\n}\n\nexport type CreateResourceLinkGroupInput<\n Resources extends ReadonlyArray<ResourceDefinition>,\n Resource extends ResourceLinkConfigResource<Resources> = ResourceLinkConfigResource<Resources>,\n> = CreateResourceLinkGroupOptions<\n Resources,\n CreateResourceLinkConfig<Resources, Resource>\n>;\n\nexport type AnyCreatedResourceLink<\n Resources extends ReadonlyArray<ResourceDefinition>,\n> = {\n [Resource in LayoutResourceKey<Resources>]:\n | CreatedResourceLinkBase<Resource>\n | CreateResourceLinkWithHash<Resource, string>;\n}[LayoutResourceKey<Resources>];\n\nexport type CreateResourceLinksWithGroups<\n Resources extends ReadonlyArray<ResourceDefinition>,\n> = {\n activeIcon?: ReactNode;\n id: string;\n label: string | null;\n icon: ReactNode;\n links: Array<AnyCreatedResourceLink<Resources>>;\n};\n\nexport type CreateResourceMap<\n Resources extends ReadonlyArray<ResourceDefinition>,\n Value,\n> = Partial<Record<LayoutResourceKey<Resources>, Value>>;\n\nexport interface CreateResourceMapFn<\n Resources extends ReadonlyArray<ResourceDefinition>,\n> {\n <const Map extends CreateResourceMap<Resources, unknown>>(map: Map): Map;\n}\n\nexport interface CreateResourceLinksWithGroupsFn<\n Resources extends ReadonlyArray<ResourceDefinition>,\n> {\n (groups: readonly []): CreateResourceLinksWithGroupsResult<Resources, never>;\n <const Resource extends ResourceLinkConfigResource<Resources>>(\n groups: ReadonlyArray<CreateResourceLinkGroupInput<Resources, Resource>>,\n ): CreateResourceLinksWithGroupsResult<Resources, Resource>;\n}\n\nexport type CreateResourceLinksWithGroupsResult<\n Resources extends ReadonlyArray<ResourceDefinition>,\n Resource extends ResourceLinkConfigResource<Resources> = ResourceLinkConfigResource<Resources>,\n> = Array<CreateResourceLinksWithGroups<Resources>> & {\n createResourceMap: CreateResourceMapFn<Resources>;\n findResource<\n const RequestedResource extends LayoutResourceKey<Resources> | Resource,\n >(\n resource: RequestedResource,\n ): FindResourceGroupResult<Resources, Resource, RequestedResource>;\n};\n\nexport type FindResourceGroupResult<\n Resources extends ReadonlyArray<ResourceDefinition>,\n IncludedResource extends string,\n RequestedResource extends string,\n> = RequestedResource extends unknown\n ? RequestedResource extends IncludedResource\n ? CreateResourceLinksWithGroups<Resources>\n : undefined\n : never;\n\nfunction resolveResourceLinkHref<Resource extends string>(\n value: ResourceLinkHref<Resource> | undefined,\n resource: Resource,\n fallback: string,\n) {\n return typeof value === 'function' ? value(resource) : (value ?? fallback);\n}\n\nfunction normalizeResourceLinkHash(hash: string) {\n return hash.replace(/^#+/, '');\n}\n\nfunction createFullResourceLinkHref(href: string, hash: string) {\n if (href.includes('#')) {\n return href;\n }\n\n return `${href}#${normalizeResourceLinkHash(hash)}`;\n}\n\nfunction createResourceLinksFromConfig<\n Resources extends ReadonlyArray<ResourceDefinition>,\n Resource extends ResourceLinkConfigResource<Resources>,\n>(config: CreateResourceLinkConfig<Resources, Resource>) {\n return Object.entries(config).map(([resource, config]) => {\n if (!config) {\n throw new LayoutConfigurationError(\n `[createResourceLinks]: \"config\" is required for the ${resource} resource.`,\n );\n }\n\n if (typeof config !== 'object') {\n throw new LayoutConfigurationError(\n `[createResourceLinks]: \"config\" must be an object for the ${resource} resource. Received ${typeof config}`,\n );\n }\n\n if (!('label' in config)) {\n throw new LayoutConfigurationError(\n `[createResourceLinks]: \"label\" is required for the ${resource} resource.`,\n );\n }\n\n if (typeof config.label !== 'string') {\n throw new LayoutConfigurationError(\n `[createResourceLinks]: \"label\" must be a string for the ${resource} resource. Received ${typeof config.label}`,\n );\n }\n\n const linkConfig = config as CreateResourceLinkOptions<\n Resources,\n typeof resource\n >;\n\n if (\n 'href' in linkConfig &&\n linkConfig.href !== undefined &&\n typeof linkConfig.href !== 'string' &&\n typeof linkConfig.href !== 'function'\n ) {\n throw new LayoutConfigurationError(\n `[createResourceLinks]: \"href\" must be a string or function for the ${resource} resource. Received ${typeof linkConfig.href}`,\n );\n }\n\n if (\n 'hash' in linkConfig &&\n linkConfig.hash !== undefined &&\n typeof linkConfig.hash !== 'string' &&\n typeof linkConfig.hash !== 'function'\n ) {\n throw new LayoutConfigurationError(\n `[createResourceLinks]: \"hash\" must be a string or function for the ${resource} resource. Received ${typeof linkConfig.hash}`,\n );\n }\n\n const givenHref = resolveResourceLinkHref(linkConfig.href, resource, '/');\n const givenHash = resolveResourceLinkHref(\n linkConfig.hash,\n resource,\n resource,\n );\n const href = {\n given: givenHref,\n full: createFullResourceLinkHref(givenHref, givenHash),\n ...(linkConfig.hash !== undefined && !givenHref.includes('#')\n ? { hash: normalizeResourceLinkHash(givenHash) }\n : {}),\n };\n\n return {\n ...(linkConfig.activeIcon !== undefined\n ? { activeIcon: linkConfig.activeIcon }\n : {}),\n href,\n label: config.label,\n resource,\n icon: 'icon' in config ? config.icon : null,\n };\n });\n}\n\n/**\n * Deterministic, not random: a random id (the previous implementation used\n * `crypto.randomUUID()` / `Math.random()`) would make `withGroups()` unsafe to\n * call from module scope — some runtimes (e.g. Cloudflare Workers) disallow\n * generating random values outside a request handler, and config arrays like\n * this are routinely defined as top-level constants.\n *\n * Falls back to the group's own shape — its `label` and its link resource\n * keys — rather than just the array index, so groups from *separate*\n * `withGroups()` calls that get concatenated into one list (a module-owned nav\n * section next to a feature-owned one, say) don't collide just because they're\n * both first in their own call. Two groups that are genuinely identical in\n * shape will still collide; pass an explicit `id` on the group to rule that\n * out entirely.\n */\nfunction createResourceLinkGroupId(\n group: { id?: unknown; label?: unknown; links: object },\n index: number,\n) {\n if (typeof group.id === 'string' && group.id.length > 0) {\n return group.id;\n }\n\n const label = typeof group.label === 'string' ? group.label : '';\n const linkKeys = Object.keys(group.links).join(',');\n return `group-${index}-${label}-${linkKeys}`;\n}\n\nfunction createResourceMapFn<\n Resources extends ReadonlyArray<ResourceDefinition>,\n>(resources: Resources): CreateResourceMapFn<Resources> {\n const resourceKeys = new Set(\n resources.map((resource) =>\n typeof resource === 'string' ? resource : resource.value,\n ),\n );\n\n return function createResourceMap<\n const Map extends CreateResourceMap<Resources, unknown>,\n >(map: Map) {\n for (const resource of Object.keys(map)) {\n if (!resourceKeys.has(resource)) {\n throw new LayoutConfigurationError(\n `[createResourceLinks.createResourceMap]: unknown resource \"${resource}\".`,\n );\n }\n }\n\n return map;\n };\n}\n\nfunction createResourceLinksWithGroups<\n Resources extends ReadonlyArray<ResourceDefinition>,\n Resource extends ResourceLinkConfigResource<Resources>,\n>(\n createResourceMap: CreateResourceMapFn<Resources>,\n groups: ReadonlyArray<CreateResourceLinkGroupInput<Resources, Resource>>,\n): Array<CreateResourceLinksWithGroups<Resources>> {\n if (!Array.isArray(groups)) {\n throw new LayoutConfigurationError(\n '[createResourceLinks.withGroups]: \"groups\" must be an array.',\n );\n }\n\n const createdGroups = groups.map((group, index) => {\n if (!group) {\n throw new LayoutConfigurationError(\n `[createResourceLinks.withGroups]: group at index ${index} is required.`,\n );\n }\n\n if (typeof group !== 'object') {\n throw new LayoutConfigurationError(\n `[createResourceLinks.withGroups]: group at index ${index} must be an object. Received ${typeof group}`,\n );\n }\n\n if (\n 'label' in group &&\n group.label !== undefined &&\n typeof group.label !== 'string'\n ) {\n throw new LayoutConfigurationError(\n `[createResourceLinks.withGroups]: \"label\" must be a string for group at index ${index}. Received ${typeof group.label}`,\n );\n }\n\n if ('id' in group && group.id !== undefined && typeof group.id !== 'string') {\n throw new LayoutConfigurationError(\n `[createResourceLinks.withGroups]: \"id\" must be a string for group at index ${index}. Received ${typeof group.id}`,\n );\n }\n\n if (!('links' in group)) {\n throw new LayoutConfigurationError(\n `[createResourceLinks.withGroups]: \"links\" is required for group at index ${index}.`,\n );\n }\n\n if (\n typeof group.links !== 'object' ||\n group.links === null ||\n Array.isArray(group.links)\n ) {\n throw new LayoutConfigurationError(\n `[createResourceLinks.withGroups]: \"links\" must be an object for group at index ${index}. Received ${Array.isArray(group.links) ? 'array' : typeof group.links}`,\n );\n }\n\n return {\n ...(group.activeIcon !== undefined\n ? { activeIcon: group.activeIcon }\n : {}),\n id: createResourceLinkGroupId(group, index),\n label: 'label' in group ? (group.label ?? null) : null,\n icon: 'icon' in group ? group.icon : null,\n links: createResourceLinksFromConfig(group.links),\n };\n }) as Array<CreateResourceLinksWithGroups<Resources>>;\n\n Object.defineProperty(createdGroups, 'createResourceMap', {\n value: createResourceMap,\n });\n Object.defineProperty(createdGroups, 'findResource', {\n value(resource: ResourceLinkConfigResource<Resources>) {\n return createdGroups.find((group) =>\n group.links.some((link) => link.resource === resource),\n );\n },\n });\n\n return createdGroups as CreateResourceLinksWithGroupsResult<\n Resources,\n Resource\n >;\n}\n\nexport function createResourceLinksFn<\n const Resources extends ReadonlyArray<ResourceDefinition>,\n>(_resources: Resources): CreateResourceLinksFn<Resources> {\n const createResourceMap = createResourceMapFn(_resources);\n\n function createResourceLinks<\n const Resource extends ResourceLinkConfigResource<Resources>,\n >(config: CreateResourceLinkConfig<Resources, Resource>) {\n return createResourceLinksFromConfig(config);\n }\n\n function withGroups<\n const Resource extends ResourceLinkConfigResource<Resources>,\n >(\n groups: ReadonlyArray<CreateResourceLinkGroupInput<Resources, Resource>>,\n ) {\n return createResourceLinksWithGroups(createResourceMap, groups);\n }\n\n return Object.assign(createResourceLinks, {\n createResourceMap,\n withGroups,\n }) as CreateResourceLinksFn<Resources>;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"create-resource-links.cjs","names":["LayoutConfigurationError","createIsValidResourceFn"],"sources":["../../src/create-config/create-resource-links.ts"],"sourcesContent":["import type { ElementType, ReactNode } from 'react';\nimport type { NonEmptyReadonlyArray } from '@jfdevelops/react-layout-validator';\nimport {\n createIsValidResourceFn,\n type LayoutResourceKey,\n type ResourceDefinition,\n} from '../resource';\nimport { LayoutConfigurationError } from '../errors';\nimport type {\n LayoutRenderResourcesAccessor,\n ResourceSelection,\n} from './define-layout';\n\nexport type ResourceAnchorLinkFn<Resource extends string> = (\n resource: Resource,\n) => string;\n\nexport type ResourceLinkHref<Resource extends string> =\n | string\n | ResourceAnchorLinkFn<Resource>;\n\nexport type ResourceLinkConfigResource<\n Resources extends ReadonlyArray<ResourceDefinition>,\n> = LayoutResourceKey<Resources> | (string & {});\n\nexport type ResourceIconFn<Resource = any> = (resource: Resource) => ReactNode;\n\nexport type ResourceIcon<Resource = any> = ReactNode | ResourceIconFn<Resource>;\n\ndeclare const resourceIconInputMarker: unique symbol;\ndeclare const resourceIconComponentInputMarker: unique symbol;\n\ntype ResourceIconInputMarker<Value> = {\n readonly [resourceIconInputMarker]: Value;\n};\n\ntype ResourceIconComponentInputMarker<Icon extends ElementType> = {\n readonly [resourceIconComponentInputMarker]: Icon;\n};\n\nexport type ResolveResourceIconInput<Definition> =\n Definition extends ResourceIconComponentInputMarker<infer Icon>\n ? Icon\n : Definition extends ResourceIconInputMarker<infer Value>\n ? Value\n : Definition extends ReadonlyArray<unknown>\n ? { [Index in keyof Definition]: ResolveResourceIconInput<Definition[Index]> }\n : Definition extends object\n ? { [Key in keyof Definition]: ResolveResourceIconInput<Definition[Key]> }\n : Definition;\n\nexport interface DefineResourceIconInputHelpers<\n Resources extends ReadonlyArray<ResourceDefinition>,\n> {\n resource(): ResourceIconInputMarker<LayoutResourceKey<Resources>>;\n icon<Icon extends ElementType = ElementType>(): ResourceIconComponentInputMarker<Icon>;\n}\n\nexport interface DefineResourceIconInputResult<\n Resources extends ReadonlyArray<ResourceDefinition>,\n Definition,\n> {\n render(\n render: (options: {\n resource: LayoutRenderResourcesAccessor<Resources>;\n input: ResolveResourceIconInput<Definition>;\n }) => ReactNode,\n ): (\n input: ResolveResourceIconInput<Definition>,\n ) => ResourceIconFn<LayoutRenderResourcesAccessor<Resources>>;\n}\n\nexport interface DefineResourceIconFn<\n Resources extends ReadonlyArray<ResourceDefinition>,\n> {\n <Icon extends ElementType = ElementType>(\n render: (options: {\n resource: LayoutRenderResourcesAccessor<Resources>;\n Icon: Icon;\n }) => ReactNode,\n ): (Icon: Icon) => ResourceIconFn<LayoutRenderResourcesAccessor<Resources>>;\n input<Definition>(\n defineInput: (\n helpers: DefineResourceIconInputHelpers<Resources>,\n ) => Definition,\n ): DefineResourceIconInputResult<Resources, Definition>;\n}\nexport interface CreateResourceLinkOptions<\n Resources extends ReadonlyArray<ResourceDefinition>,\n Resource extends ResourceLinkConfigResource<Resources>,\n> {\n /**\n * The label of the link. This is the text that will be displayed in the link.\n */\n label: string;\n /**\n * An optional icon to render when the link is active.\n */\n activeIcon?: ReactNode;\n /**\n * An optional icon to register with the link. This is a headless approach, meaning you have full control\n * over the icon's rendering.\n */\n icon?: ResourceIcon<LayoutRenderResourcesAccessor<Resources>>;\n /**\n * The href of the link. By default, the href will be `\"/\"`. While you **don't** need to include a hash,\n * if you do, it will override the `hash` property (if provided).\n */\n href?: ResourceLinkHref<Resource>;\n /**\n * The hash of the link. By default, the hash will be the resource name. If you need to customize it, you can provide\n * a string or a function that returns a string.\n *\n * @remarks A `\"#\"` is not required. The generated href will include it automatically.\n */\n hash?: ResourceLinkHref<Resource>;\n}\n\nexport type CreateResourceLinkConfig<\n Resources extends ReadonlyArray<ResourceDefinition>,\n Resource extends ResourceLinkConfigResource<Resources> = ResourceLinkConfigResource<Resources>,\n> = {\n [Key in Resource]?: CreateResourceLinkOptions<\n Resources,\n Key\n >;\n};\n\nexport type InferHashFromResourceLinkHref<\n Resource extends string,\n Hash extends ResourceLinkHref<Resource>,\n> = Hash extends string\n ? Hash\n : Hash extends ResourceAnchorLinkFn<Resource>\n ? ReturnType<Hash>\n : never;\n\nexport type CreatedResourceHref = {\n /**\n * The full generated href with the `hash` if provided.\n */\n full: string;\n /**\n * The given href. This is the same value as the `href` property of the config.\n */\n given: string;\n};\n\nexport type CreatedResourceLinkBase<Resource extends string> = {\n activeIcon?: ReactNode;\n href: CreatedResourceHref;\n label: string;\n resource: Resource;\n icon: ResourceIcon;\n};\nexport type CreateResourceLinkWithHash<\n Resource extends string,\n Hash extends ResourceLinkHref<Resource>,\n> = Omit<CreatedResourceLinkBase<Resource>, 'href'> & {\n href: CreatedResourceHref & {\n hash: InferHashFromResourceLinkHref<Resource, Hash>;\n };\n};\n\nexport type CreatedResourceLink<\n Resources extends ReadonlyArray<ResourceDefinition>,\n Config,\n> = {\n [Resource in keyof Config]-?: Resource extends ResourceLinkConfigResource<Resources>\n ? Config[Resource] extends {\n hash: infer hash extends ResourceLinkHref<Resource>;\n }\n ? CreateResourceLinkWithHash<Resource, hash>\n : CreatedResourceLinkBase<Resource>\n : never;\n}[keyof Config];\n\nexport interface CreateResourceLinksFn<\n Resources extends ReadonlyArray<ResourceDefinition>,\n> {\n <const Resource extends ResourceLinkConfigResource<Resources>>(\n config: CreateResourceLinkConfig<Resources, Resource>,\n ): Array<\n CreatedResourceLink<\n Resources,\n CreateResourceLinkConfig<Resources, Resource>\n >\n >;\n createResourceMap: CreateResourceMapFn<Resources>;\n defineIcon: DefineResourceIconFn<Resources>;\n withGroups: CreateResourceLinksWithGroupsFn<Resources>;\n}\n\nexport interface CreateResourceLinkGroupOptions<\n Resources extends ReadonlyArray<ResourceDefinition>,\n Links extends object =\n CreateResourceLinkConfig<Resources>,\n> {\n /**\n * An optional, caller-provided identity for the group. Provide this when you\n * combine groups from more than one `withGroups()` call into one list (e.g.\n * a module-owned nav section alongside a feature-owned one) — without it,\n * the id is derived from the group's `label` and its link resources, which\n * is enough to keep groups from a single call apart but can't distinguish\n * two independently-defined groups that happen to look identical.\n */\n id?: string;\n /**\n * The label of the group. This is the text that will be displayed for the group.\n */\n label?: string;\n /**\n * An optional icon to render when the group is active.\n */\n activeIcon?: ReactNode;\n /**\n * An optional icon to register with the group. This is a headless approach, meaning you have full control\n * over the icon's rendering.\n */\n icon?: ResourceIcon<LayoutRenderResourcesAccessor<Resources>>;\n /**\n * The resource links in this group.\n */\n links: Links;\n}\n\nexport type CreateResourceLinkGroupInput<\n Resources extends ReadonlyArray<ResourceDefinition>,\n Resource extends ResourceLinkConfigResource<Resources> = ResourceLinkConfigResource<Resources>,\n> = CreateResourceLinkGroupOptions<\n Resources,\n CreateResourceLinkConfig<Resources, Resource>\n>;\n\nexport type AnyCreatedResourceLink<\n Resources extends ReadonlyArray<ResourceDefinition>,\n> = {\n [Resource in LayoutResourceKey<Resources>]:\n | CreatedResourceLinkBase<Resource>\n | CreateResourceLinkWithHash<Resource, string>;\n}[LayoutResourceKey<Resources>];\n\nexport type CreateResourceLinksWithGroups<\n Resources extends ReadonlyArray<ResourceDefinition>,\n> = {\n activeIcon?: ReactNode;\n id: string;\n label: string | null;\n icon: ResourceIcon<LayoutRenderResourcesAccessor<Resources>>;\n links: Array<AnyCreatedResourceLink<Resources>>;\n};\n\nexport type ResolvedResourceLinksWithGroups<\n Resources extends ReadonlyArray<ResourceDefinition>,\n> = Omit<CreateResourceLinksWithGroups<Resources>, 'icon' | 'links'> & {\n icon: ReactNode;\n links: Array<\n Omit<AnyCreatedResourceLink<Resources>, 'icon'> & { icon: ReactNode }\n >;\n};\n\nexport type CreateResourceMap<\n Resources extends ReadonlyArray<ResourceDefinition>,\n Value,\n> = Partial<Record<LayoutResourceKey<Resources>, Value>>;\n\nexport type ValidateResource<\n Resource extends string,\n Map extends object,\n> = Resource extends Extract<keyof Map, string> ? Resource : never;\n\nexport type CreatedResourceMap<Map extends object> = Map & {\n isValidResource(value: unknown): value is Extract<keyof Map, string>;\n get<const Resource extends string>(\n resource: ValidateResource<Resource, Map>,\n ): Map[typeof resource];\n};\n\nexport interface CreateResourceMapFn<\n Resources extends ReadonlyArray<ResourceDefinition>,\n> {\n /**\n * Creates a resource map for this layout.\n *\n * @param map Values keyed by this layout's resources. An unknown resource,\n * or a key that collides with a map helper, is rejected.\n */\n <const Map extends CreateResourceMap<Resources, unknown>>(\n map: Map,\n ): CreatedResourceMap<Map>;\n}\n\nexport interface CreateResourceLinksWithGroupsFn<\n Resources extends ReadonlyArray<ResourceDefinition>,\n> {\n /**\n * Groups resource links into sections.\n *\n * @param groups The sections to create. At least one is required. The link\n * keys in those sections are the resources `findResource` can resolve.\n */\n <const Resource extends ResourceLinkConfigResource<Resources>>(\n groups: NonEmptyReadonlyArray<\n CreateResourceLinkGroupInput<Resources, Resource>\n >,\n ): CreateResourceLinksWithGroupsResult<Resources, Resource>;\n}\n\nexport type CreateResourceLinksWithGroupsResult<\n Resources extends ReadonlyArray<ResourceDefinition>,\n Resource extends ResourceLinkConfigResource<Resources> = ResourceLinkConfigResource<Resources>,\n> = Array<CreateResourceLinksWithGroups<Resources>> & {\n /**\n * Resolves each group's icon and its link icons for the active resource.\n *\n * Functional icons receive the resources accessor for `resource`. Read the\n * groups value directly when you do not need to resolve icons.\n *\n * @param resource The active resource key.\n */\n (\n resource: LayoutResourceKey<Resources>,\n ): Array<ResolvedResourceLinksWithGroups<Resources>>;\n /**\n * Creates a resource map for this layout. Same helper as\n * `createResourceLinks.createResourceMap`.\n *\n * @param map Values keyed by this layout's resources. An unknown resource,\n * or a key that collides with a map helper, is rejected.\n */\n createResourceMap: CreateResourceMapFn<Resources>;\n /**\n * Returns the group whose links include `resource`.\n *\n * The result is `undefined` when that resource was not configured in any\n * group. A resource outside this layout is rejected by the type.\n *\n * @param resource The resource whose group should be returned.\n */\n findResource<\n const RequestedResource extends LayoutResourceKey<Resources> | Resource,\n >(\n resource: RequestedResource,\n ): FindResourceGroupResult<Resources, Resource, RequestedResource>;\n};\n\nexport type FindResourceGroupResult<\n Resources extends ReadonlyArray<ResourceDefinition>,\n IncludedResource extends string,\n RequestedResource extends string,\n> = RequestedResource extends unknown\n ? RequestedResource extends IncludedResource\n ? CreateResourceLinksWithGroups<Resources>\n : undefined\n : never;\n\nfunction resolveResourceLinkHref<Resource extends string>(\n value: ResourceLinkHref<Resource> | undefined,\n resource: Resource,\n fallback: string,\n) {\n return typeof value === 'function' ? value(resource) : (value ?? fallback);\n}\n\nfunction normalizeResourceLinkHash(hash: string) {\n return hash.replace(/^#+/, '');\n}\n\nfunction createFullResourceLinkHref(href: string, hash: string) {\n if (href.includes('#')) {\n return href;\n }\n\n return `${href}#${normalizeResourceLinkHash(hash)}`;\n}\n\nfunction createResourceLinksFromConfig<\n Resources extends ReadonlyArray<ResourceDefinition>,\n Resource extends ResourceLinkConfigResource<Resources>,\n>(config: CreateResourceLinkConfig<Resources, Resource>) {\n return Object.entries(config).map(([resource, config]) => {\n if (!config) {\n throw new LayoutConfigurationError(\n `[createResourceLinks]: \"config\" is required for the ${resource} resource.`,\n );\n }\n\n if (typeof config !== 'object') {\n throw new LayoutConfigurationError(\n `[createResourceLinks]: \"config\" must be an object for the ${resource} resource. Received ${typeof config}`,\n );\n }\n\n if (!('label' in config)) {\n throw new LayoutConfigurationError(\n `[createResourceLinks]: \"label\" is required for the ${resource} resource.`,\n );\n }\n\n if (typeof config.label !== 'string') {\n throw new LayoutConfigurationError(\n `[createResourceLinks]: \"label\" must be a string for the ${resource} resource. Received ${typeof config.label}`,\n );\n }\n\n const linkConfig = config as CreateResourceLinkOptions<\n Resources,\n typeof resource\n >;\n\n if (\n 'href' in linkConfig &&\n linkConfig.href !== undefined &&\n typeof linkConfig.href !== 'string' &&\n typeof linkConfig.href !== 'function'\n ) {\n throw new LayoutConfigurationError(\n `[createResourceLinks]: \"href\" must be a string or function for the ${resource} resource. Received ${typeof linkConfig.href}`,\n );\n }\n\n if (\n 'hash' in linkConfig &&\n linkConfig.hash !== undefined &&\n typeof linkConfig.hash !== 'string' &&\n typeof linkConfig.hash !== 'function'\n ) {\n throw new LayoutConfigurationError(\n `[createResourceLinks]: \"hash\" must be a string or function for the ${resource} resource. Received ${typeof linkConfig.hash}`,\n );\n }\n\n const givenHref = resolveResourceLinkHref(linkConfig.href, resource, '/');\n const givenHash = resolveResourceLinkHref(\n linkConfig.hash,\n resource,\n resource,\n );\n const href = {\n given: givenHref,\n full: createFullResourceLinkHref(givenHref, givenHash),\n ...(linkConfig.hash !== undefined && !givenHref.includes('#')\n ? { hash: normalizeResourceLinkHash(givenHash) }\n : {}),\n };\n\n return {\n ...(linkConfig.activeIcon !== undefined\n ? { activeIcon: linkConfig.activeIcon }\n : {}),\n href,\n label: config.label,\n resource,\n icon: 'icon' in config ? config.icon : null,\n };\n });\n}\n\n/**\n * Deterministic, not random: a random id (the previous implementation used\n * `crypto.randomUUID()` / `Math.random()`) would make `withGroups()` unsafe to\n * call from module scope — some runtimes (e.g. Cloudflare Workers) disallow\n * generating random values outside a request handler, and config arrays like\n * this are routinely defined as top-level constants.\n *\n * Falls back to the group's own shape — its `label` and its link resource\n * keys — rather than just the array index, so groups from *separate*\n * `withGroups()` calls that get concatenated into one list (a module-owned nav\n * section next to a feature-owned one, say) don't collide just because they're\n * both first in their own call. Two groups that are genuinely identical in\n * shape will still collide; pass an explicit `id` on the group to rule that\n * out entirely.\n */\nfunction createResourceLinkGroupId(\n group: { id?: unknown; label?: unknown; links: object },\n index: number,\n) {\n if (typeof group.id === 'string' && group.id.length > 0) {\n return group.id;\n }\n\n const label = typeof group.label === 'string' ? group.label : '';\n const linkKeys = Object.keys(group.links).join(',');\n return `group-${index}-${label}-${linkKeys}`;\n}\n\nfunction readResourceValue(resource: ResourceDefinition) {\n return typeof resource === 'string' ? resource : resource.value;\n}\n\nfunction createResourceSelection(\n definitions: ReadonlyArray<ResourceDefinition>,\n): ResourceSelection<ReadonlyArray<ResourceDefinition>> {\n const selection = (() => definitions) as ResourceSelection<\n ReadonlyArray<ResourceDefinition>\n >;\n\n selection.isResource = createIsValidResourceFn(definitions);\n\n return selection;\n}\n\nfunction createRenderResourcesAccessor<\n Resources extends ReadonlyArray<ResourceDefinition>,\n>(resources: Resources, current: LayoutResourceKey<Resources>) {\n const accessor = Object.assign(() => resources, {\n current,\n }) as LayoutRenderResourcesAccessor<Resources>;\n\n accessor.isResource = createIsValidResourceFn(resources);\n accessor.omit = ((...keys: string[]) =>\n createResourceSelection(\n resources.filter((resource) => !keys.includes(readResourceValue(resource))),\n )) as LayoutRenderResourcesAccessor<Resources>['omit'];\n accessor.pick = ((...keys: string[]) =>\n createResourceSelection(\n resources.filter((resource) => keys.includes(readResourceValue(resource))),\n )) as LayoutRenderResourcesAccessor<Resources>['pick'];\n\n return accessor;\n}\n\nfunction createResourceMapFn<\n Resources extends ReadonlyArray<ResourceDefinition>,\n>(resources: Resources): CreateResourceMapFn<Resources> {\n const helperNames = new Set(['get', 'isValidResource']);\n const resourceKeys = new Set(\n resources.map((resource) =>\n typeof resource === 'string' ? resource : resource.value,\n ),\n );\n\n return function createResourceMap<\n const Map extends CreateResourceMap<Resources, unknown>,\n >(map: Map) {\n const configuredResources = Object.keys(map);\n\n for (const resource of configuredResources) {\n if (!resourceKeys.has(resource)) {\n throw new LayoutConfigurationError(\n `[createResourceLinks.createResourceMap]: unknown resource \"${resource}\".`,\n );\n }\n\n if (helperNames.has(resource)) {\n throw new LayoutConfigurationError(\n `[createResourceLinks.createResourceMap]: resource \"${resource}\" conflicts with a map helper.`,\n );\n }\n }\n\n const configuredResourceKeys = new Set(configuredResources);\n const resourceValues = new Map(\n configuredResources.map((resource) => [\n resource,\n (map as Record<string, unknown>)[resource],\n ]),\n );\n\n Object.defineProperties(map, {\n get: {\n value(resource: string) {\n return resourceValues.get(resource);\n },\n },\n isValidResource: {\n value(value: unknown): value is Extract<keyof Map, string> {\n return (\n typeof value === 'string' && configuredResourceKeys.has(value)\n );\n },\n },\n });\n\n return map as CreatedResourceMap<Map>;\n };\n}\n\nfunction createResourceLinksWithGroups<\n Resources extends ReadonlyArray<ResourceDefinition>,\n Resource extends ResourceLinkConfigResource<Resources>,\n>(\n resources: Resources,\n createResourceMap: CreateResourceMapFn<Resources>,\n groups: ReadonlyArray<CreateResourceLinkGroupInput<Resources, Resource>>,\n): CreateResourceLinksWithGroupsResult<Resources, Resource> {\n if (!Array.isArray(groups)) {\n throw new LayoutConfigurationError(\n '[createResourceLinks.withGroups]: \"groups\" must be an array.',\n );\n }\n\n if (groups.length === 0) {\n throw new LayoutConfigurationError(\n '[createResourceLinks.withGroups]: \"groups\" must include at least one group.',\n );\n }\n\n const createdGroups = groups.map((group, index) => {\n if (!group) {\n throw new LayoutConfigurationError(\n `[createResourceLinks.withGroups]: group at index ${index} is required.`,\n );\n }\n\n if (typeof group !== 'object') {\n throw new LayoutConfigurationError(\n `[createResourceLinks.withGroups]: group at index ${index} must be an object. Received ${typeof group}`,\n );\n }\n\n if (\n 'label' in group &&\n group.label !== undefined &&\n typeof group.label !== 'string'\n ) {\n throw new LayoutConfigurationError(\n `[createResourceLinks.withGroups]: \"label\" must be a string for group at index ${index}. Received ${typeof group.label}`,\n );\n }\n\n if ('id' in group && group.id !== undefined && typeof group.id !== 'string') {\n throw new LayoutConfigurationError(\n `[createResourceLinks.withGroups]: \"id\" must be a string for group at index ${index}. Received ${typeof group.id}`,\n );\n }\n\n if (!('links' in group)) {\n throw new LayoutConfigurationError(\n `[createResourceLinks.withGroups]: \"links\" is required for group at index ${index}.`,\n );\n }\n\n if (\n typeof group.links !== 'object' ||\n group.links === null ||\n Array.isArray(group.links)\n ) {\n throw new LayoutConfigurationError(\n `[createResourceLinks.withGroups]: \"links\" must be an object for group at index ${index}. Received ${Array.isArray(group.links) ? 'array' : typeof group.links}`,\n );\n }\n\n return {\n ...(group.activeIcon !== undefined\n ? { activeIcon: group.activeIcon }\n : {}),\n id: createResourceLinkGroupId(group, index),\n label: 'label' in group ? (group.label ?? null) : null,\n icon: 'icon' in group ? group.icon : null,\n links: createResourceLinksFromConfig(group.links),\n };\n }) as Array<CreateResourceLinksWithGroups<Resources>>;\n\n function resolveGroups(resource?: LayoutResourceKey<Resources>) {\n if (resource === undefined) {\n throw new LayoutConfigurationError(\n '[createResourceLinks.withGroups]: \"resource\" is required.',\n );\n }\n\n const currentResources = createRenderResourcesAccessor(resources, resource);\n\n return createdGroups.map((group) => ({\n ...group,\n icon:\n typeof group.icon === 'function'\n ? group.icon(currentResources)\n : group.icon,\n links: group.links.map((link) => ({\n ...link,\n icon:\n typeof link.icon === 'function'\n ? link.icon(currentResources)\n : link.icon,\n })),\n }));\n }\n\n const callableGroups = new Proxy(resolveGroups, {\n get(target, property, receiver) {\n if (property === 'createResourceMap') {\n return createResourceMap;\n }\n\n if (property === Symbol.iterator) {\n return createdGroups[Symbol.iterator].bind(createdGroups);\n }\n\n if (property === 'toJSON') {\n return () => createdGroups;\n }\n\n if (property in createdGroups) {\n const value = Reflect.get(createdGroups, property);\n return typeof value === 'function' ? value.bind(createdGroups) : value;\n }\n\n return Reflect.get(target, property, receiver);\n },\n getOwnPropertyDescriptor(target, property) {\n return (\n Reflect.getOwnPropertyDescriptor(createdGroups, property) ??\n Reflect.getOwnPropertyDescriptor(target, property)\n );\n },\n getPrototypeOf() {\n return Array.prototype;\n },\n ownKeys(target) {\n return [\n ...new Set([\n ...Reflect.ownKeys(target),\n ...Reflect.ownKeys(createdGroups),\n ]),\n ];\n },\n set(target, property, value, receiver) {\n if (property in createdGroups || /^\\d+$/.test(String(property))) {\n return Reflect.set(createdGroups, property, value);\n }\n\n return Reflect.set(target, property, value, receiver);\n },\n });\n Object.defineProperty(createdGroups, 'findResource', {\n value(resource: ResourceLinkConfigResource<Resources>) {\n return createdGroups.find((group) =>\n group.links.some((link) => link.resource === resource),\n );\n },\n });\n\n return callableGroups as CreateResourceLinksWithGroupsResult<\n Resources,\n Resource\n >;\n}\n\nfunction createDefineIcon<\n Resources extends ReadonlyArray<ResourceDefinition>,\n>(): DefineResourceIconFn<Resources> {\n function defineIcon<Icon extends ElementType = ElementType>(\n render: (options: {\n resource: LayoutRenderResourcesAccessor<Resources>;\n Icon: Icon;\n }) => ReactNode,\n ) {\n return function createIcon(Icon: Icon) {\n return function renderIcon(\n resource: LayoutRenderResourcesAccessor<Resources>,\n ) {\n return render({ resource, Icon });\n };\n };\n }\n\n defineIcon.input = function defineIconInput<Definition>(\n defineInput: (\n helpers: DefineResourceIconInputHelpers<Resources>,\n ) => Definition,\n ): DefineResourceIconInputResult<Resources, Definition> {\n const resourceMarker = {} as ResourceIconInputMarker<never>;\n const componentMarker = {} as ResourceIconComponentInputMarker<ElementType>;\n\n defineInput({\n resource() {\n return resourceMarker;\n },\n icon<Icon extends ElementType = ElementType>() {\n return componentMarker as ResourceIconComponentInputMarker<Icon>;\n },\n });\n\n return {\n render(\n render: (options: {\n resource: LayoutRenderResourcesAccessor<Resources>;\n input: ResolveResourceIconInput<Definition>;\n }) => ReactNode,\n ) {\n return function createIcon(\n input: ResolveResourceIconInput<Definition>,\n ) {\n return function renderIcon(\n resource: LayoutRenderResourcesAccessor<Resources>,\n ) {\n return render({ resource, input });\n };\n };\n },\n };\n };\n\n return defineIcon;\n}\n\nexport function createResourceLinksFn<\n const Resources extends ReadonlyArray<ResourceDefinition>,\n>(_resources: Resources): CreateResourceLinksFn<Resources> {\n const createResourceMap = createResourceMapFn(_resources);\n const defineIcon = createDefineIcon<Resources>();\n\n function createResourceLinks<\n const Resource extends ResourceLinkConfigResource<Resources>,\n >(config: CreateResourceLinkConfig<Resources, Resource>) {\n return createResourceLinksFromConfig(config);\n }\n\n function withGroups<\n const Resource extends ResourceLinkConfigResource<Resources>,\n >(\n groups: NonEmptyReadonlyArray<\n CreateResourceLinkGroupInput<Resources, Resource>\n >,\n ) {\n return createResourceLinksWithGroups(_resources, createResourceMap, groups);\n }\n\n return Object.assign(createResourceLinks, {\n createResourceMap,\n defineIcon,\n withGroups,\n }) as CreateResourceLinksFn<Resources>;\n}\n"],"mappings":"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"}
@@ -1,10 +1,40 @@
1
1
  import { LayoutResourceKey, ResourceDefinition } from "../resource.cjs";
2
- import { ReactNode } from "react";
2
+ import { LayoutRenderResourcesAccessor } from "./define-layout.cjs";
3
+ import { ElementType, ReactNode } from "react";
4
+ import { NonEmptyReadonlyArray } from "@jfdevelops/react-layout-validator";
3
5
 
4
6
  //#region src/create-config/create-resource-links.d.ts
5
7
  type ResourceAnchorLinkFn<Resource extends string> = (resource: Resource) => string;
6
8
  type ResourceLinkHref<Resource extends string> = string | ResourceAnchorLinkFn<Resource>;
7
9
  type ResourceLinkConfigResource<Resources extends ReadonlyArray<ResourceDefinition>> = LayoutResourceKey<Resources> | (string & {});
10
+ type ResourceIconFn<Resource = any> = (resource: Resource) => ReactNode;
11
+ type ResourceIcon<Resource = any> = ReactNode | ResourceIconFn<Resource>;
12
+ declare const resourceIconInputMarker: unique symbol;
13
+ declare const resourceIconComponentInputMarker: unique symbol;
14
+ type ResourceIconInputMarker<Value> = {
15
+ readonly [resourceIconInputMarker]: Value;
16
+ };
17
+ type ResourceIconComponentInputMarker<Icon extends ElementType> = {
18
+ readonly [resourceIconComponentInputMarker]: Icon;
19
+ };
20
+ type ResolveResourceIconInput<Definition> = Definition extends ResourceIconComponentInputMarker<infer Icon> ? Icon : Definition extends ResourceIconInputMarker<infer Value> ? Value : Definition extends ReadonlyArray<unknown> ? { [Index in keyof Definition]: ResolveResourceIconInput<Definition[Index]> } : Definition extends object ? { [Key in keyof Definition]: ResolveResourceIconInput<Definition[Key]> } : Definition;
21
+ interface DefineResourceIconInputHelpers<Resources extends ReadonlyArray<ResourceDefinition>> {
22
+ resource(): ResourceIconInputMarker<LayoutResourceKey<Resources>>;
23
+ icon<Icon extends ElementType = ElementType>(): ResourceIconComponentInputMarker<Icon>;
24
+ }
25
+ interface DefineResourceIconInputResult<Resources extends ReadonlyArray<ResourceDefinition>, Definition> {
26
+ render(render: (options: {
27
+ resource: LayoutRenderResourcesAccessor<Resources>;
28
+ input: ResolveResourceIconInput<Definition>;
29
+ }) => ReactNode): (input: ResolveResourceIconInput<Definition>) => ResourceIconFn<LayoutRenderResourcesAccessor<Resources>>;
30
+ }
31
+ interface DefineResourceIconFn<Resources extends ReadonlyArray<ResourceDefinition>> {
32
+ <Icon extends ElementType = ElementType>(render: (options: {
33
+ resource: LayoutRenderResourcesAccessor<Resources>;
34
+ Icon: Icon;
35
+ }) => ReactNode): (Icon: Icon) => ResourceIconFn<LayoutRenderResourcesAccessor<Resources>>;
36
+ input<Definition>(defineInput: (helpers: DefineResourceIconInputHelpers<Resources>) => Definition): DefineResourceIconInputResult<Resources, Definition>;
37
+ }
8
38
  interface CreateResourceLinkOptions<Resources extends ReadonlyArray<ResourceDefinition>, Resource extends ResourceLinkConfigResource<Resources>> {
9
39
  /**
10
40
  * The label of the link. This is the text that will be displayed in the link.
@@ -18,7 +48,7 @@ interface CreateResourceLinkOptions<Resources extends ReadonlyArray<ResourceDefi
18
48
  * An optional icon to register with the link. This is a headless approach, meaning you have full control
19
49
  * over the icon's rendering.
20
50
  */
21
- icon?: ReactNode;
51
+ icon?: ResourceIcon<LayoutRenderResourcesAccessor<Resources>>;
22
52
  /**
23
53
  * The href of the link. By default, the href will be `"/"`. While you **don't** need to include a hash,
24
54
  * if you do, it will override the `hash` property (if provided).
@@ -49,7 +79,7 @@ type CreatedResourceLinkBase<Resource extends string> = {
49
79
  href: CreatedResourceHref;
50
80
  label: string;
51
81
  resource: Resource;
52
- icon: ReactNode;
82
+ icon: ResourceIcon;
53
83
  };
54
84
  type CreateResourceLinkWithHash<Resource extends string, Hash extends ResourceLinkHref<Resource>> = Omit<CreatedResourceLinkBase<Resource>, 'href'> & {
55
85
  href: CreatedResourceHref & {
@@ -62,6 +92,7 @@ type CreatedResourceLink<Resources extends ReadonlyArray<ResourceDefinition>, Co
62
92
  interface CreateResourceLinksFn<Resources extends ReadonlyArray<ResourceDefinition>> {
63
93
  <const Resource extends ResourceLinkConfigResource<Resources>>(config: CreateResourceLinkConfig<Resources, Resource>): Array<CreatedResourceLink<Resources, CreateResourceLinkConfig<Resources, Resource>>>;
64
94
  createResourceMap: CreateResourceMapFn<Resources>;
95
+ defineIcon: DefineResourceIconFn<Resources>;
65
96
  withGroups: CreateResourceLinksWithGroupsFn<Resources>;
66
97
  }
67
98
  interface CreateResourceLinkGroupOptions<Resources extends ReadonlyArray<ResourceDefinition>, Links extends object = CreateResourceLinkConfig<Resources>> {
@@ -86,7 +117,7 @@ interface CreateResourceLinkGroupOptions<Resources extends ReadonlyArray<Resourc
86
117
  * An optional icon to register with the group. This is a headless approach, meaning you have full control
87
118
  * over the icon's rendering.
88
119
  */
89
- icon?: ReactNode;
120
+ icon?: ResourceIcon<LayoutRenderResourcesAccessor<Resources>>;
90
121
  /**
91
122
  * The resource links in this group.
92
123
  */
@@ -98,22 +129,68 @@ type CreateResourceLinksWithGroups<Resources extends ReadonlyArray<ResourceDefin
98
129
  activeIcon?: ReactNode;
99
130
  id: string;
100
131
  label: string | null;
101
- icon: ReactNode;
132
+ icon: ResourceIcon<LayoutRenderResourcesAccessor<Resources>>;
102
133
  links: Array<AnyCreatedResourceLink<Resources>>;
103
134
  };
135
+ type ResolvedResourceLinksWithGroups<Resources extends ReadonlyArray<ResourceDefinition>> = Omit<CreateResourceLinksWithGroups<Resources>, 'icon' | 'links'> & {
136
+ icon: ReactNode;
137
+ links: Array<Omit<AnyCreatedResourceLink<Resources>, 'icon'> & {
138
+ icon: ReactNode;
139
+ }>;
140
+ };
104
141
  type CreateResourceMap<Resources extends ReadonlyArray<ResourceDefinition>, Value> = Partial<Record<LayoutResourceKey<Resources>, Value>>;
142
+ type ValidateResource<Resource extends string, Map extends object> = Resource extends Extract<keyof Map, string> ? Resource : never;
143
+ type CreatedResourceMap<Map extends object> = Map & {
144
+ isValidResource(value: unknown): value is Extract<keyof Map, string>;
145
+ get<const Resource extends string>(resource: ValidateResource<Resource, Map>): Map[typeof resource];
146
+ };
105
147
  interface CreateResourceMapFn<Resources extends ReadonlyArray<ResourceDefinition>> {
106
- <const Map extends CreateResourceMap<Resources, unknown>>(map: Map): Map;
148
+ /**
149
+ * Creates a resource map for this layout.
150
+ *
151
+ * @param map Values keyed by this layout's resources. An unknown resource,
152
+ * or a key that collides with a map helper, is rejected.
153
+ */
154
+ <const Map extends CreateResourceMap<Resources, unknown>>(map: Map): CreatedResourceMap<Map>;
107
155
  }
108
156
  interface CreateResourceLinksWithGroupsFn<Resources extends ReadonlyArray<ResourceDefinition>> {
109
- (groups: readonly []): CreateResourceLinksWithGroupsResult<Resources, never>;
110
- <const Resource extends ResourceLinkConfigResource<Resources>>(groups: ReadonlyArray<CreateResourceLinkGroupInput<Resources, Resource>>): CreateResourceLinksWithGroupsResult<Resources, Resource>;
157
+ /**
158
+ * Groups resource links into sections.
159
+ *
160
+ * @param groups The sections to create. At least one is required. The link
161
+ * keys in those sections are the resources `findResource` can resolve.
162
+ */
163
+ <const Resource extends ResourceLinkConfigResource<Resources>>(groups: NonEmptyReadonlyArray<CreateResourceLinkGroupInput<Resources, Resource>>): CreateResourceLinksWithGroupsResult<Resources, Resource>;
111
164
  }
112
165
  type CreateResourceLinksWithGroupsResult<Resources extends ReadonlyArray<ResourceDefinition>, Resource extends ResourceLinkConfigResource<Resources> = ResourceLinkConfigResource<Resources>> = Array<CreateResourceLinksWithGroups<Resources>> & {
166
+ /**
167
+ * Resolves each group's icon and its link icons for the active resource.
168
+ *
169
+ * Functional icons receive the resources accessor for `resource`. Read the
170
+ * groups value directly when you do not need to resolve icons.
171
+ *
172
+ * @param resource The active resource key.
173
+ */
174
+ (resource: LayoutResourceKey<Resources>): Array<ResolvedResourceLinksWithGroups<Resources>>;
175
+ /**
176
+ * Creates a resource map for this layout. Same helper as
177
+ * `createResourceLinks.createResourceMap`.
178
+ *
179
+ * @param map Values keyed by this layout's resources. An unknown resource,
180
+ * or a key that collides with a map helper, is rejected.
181
+ */
113
182
  createResourceMap: CreateResourceMapFn<Resources>;
183
+ /**
184
+ * Returns the group whose links include `resource`.
185
+ *
186
+ * The result is `undefined` when that resource was not configured in any
187
+ * group. A resource outside this layout is rejected by the type.
188
+ *
189
+ * @param resource The resource whose group should be returned.
190
+ */
114
191
  findResource<const RequestedResource extends LayoutResourceKey<Resources> | Resource>(resource: RequestedResource): FindResourceGroupResult<Resources, Resource, RequestedResource>;
115
192
  };
116
193
  type FindResourceGroupResult<Resources extends ReadonlyArray<ResourceDefinition>, IncludedResource extends string, RequestedResource extends string> = RequestedResource extends unknown ? RequestedResource extends IncludedResource ? CreateResourceLinksWithGroups<Resources> : undefined : never;
117
194
  //#endregion
118
- export { CreateResourceLinkConfig, CreateResourceLinkGroupInput, CreateResourceLinkGroupOptions, CreateResourceLinkOptions, CreateResourceLinksFn, CreateResourceLinksWithGroups, CreateResourceLinksWithGroupsFn, CreateResourceLinksWithGroupsResult, CreateResourceMap, CreateResourceMapFn, CreatedResourceHref, CreatedResourceLink, CreatedResourceLinkBase, FindResourceGroupResult, InferHashFromResourceLinkHref, ResourceAnchorLinkFn, ResourceLinkHref };
195
+ export { CreateResourceLinkConfig, CreateResourceLinkGroupInput, CreateResourceLinkGroupOptions, CreateResourceLinkOptions, CreateResourceLinksFn, CreateResourceLinksWithGroups, CreateResourceLinksWithGroupsFn, CreateResourceLinksWithGroupsResult, CreateResourceMap, CreateResourceMapFn, CreatedResourceHref, CreatedResourceLink, CreatedResourceLinkBase, CreatedResourceMap, DefineResourceIconFn, DefineResourceIconInputHelpers, DefineResourceIconInputResult, FindResourceGroupResult, InferHashFromResourceLinkHref, ResolveResourceIconInput, ResolvedResourceLinksWithGroups, ResourceAnchorLinkFn, ResourceIcon, ResourceIconFn, ResourceLinkHref, ValidateResource };
119
196
  //# sourceMappingURL=create-resource-links.d.cts.map
@@ -1 +1 @@
1
- {"version":3,"file":"create-resource-links.d.cts","names":[],"sources":["../../src/create-config/create-resource-links.ts"],"mappings":"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"}
1
+ {"version":3,"file":"create-resource-links.d.cts","names":[],"sources":["../../src/create-config/create-resource-links.ts"],"mappings":"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"}
@@ -1,10 +1,40 @@
1
1
  import { LayoutResourceKey, ResourceDefinition } from "../resource.mjs";
2
- import { ReactNode } from "react";
2
+ import { LayoutRenderResourcesAccessor } from "./define-layout.mjs";
3
+ import { ElementType, ReactNode } from "react";
4
+ import { NonEmptyReadonlyArray } from "@jfdevelops/react-layout-validator";
3
5
 
4
6
  //#region src/create-config/create-resource-links.d.ts
5
7
  type ResourceAnchorLinkFn<Resource extends string> = (resource: Resource) => string;
6
8
  type ResourceLinkHref<Resource extends string> = string | ResourceAnchorLinkFn<Resource>;
7
9
  type ResourceLinkConfigResource<Resources extends ReadonlyArray<ResourceDefinition>> = LayoutResourceKey<Resources> | (string & {});
10
+ type ResourceIconFn<Resource = any> = (resource: Resource) => ReactNode;
11
+ type ResourceIcon<Resource = any> = ReactNode | ResourceIconFn<Resource>;
12
+ declare const resourceIconInputMarker: unique symbol;
13
+ declare const resourceIconComponentInputMarker: unique symbol;
14
+ type ResourceIconInputMarker<Value> = {
15
+ readonly [resourceIconInputMarker]: Value;
16
+ };
17
+ type ResourceIconComponentInputMarker<Icon extends ElementType> = {
18
+ readonly [resourceIconComponentInputMarker]: Icon;
19
+ };
20
+ type ResolveResourceIconInput<Definition> = Definition extends ResourceIconComponentInputMarker<infer Icon> ? Icon : Definition extends ResourceIconInputMarker<infer Value> ? Value : Definition extends ReadonlyArray<unknown> ? { [Index in keyof Definition]: ResolveResourceIconInput<Definition[Index]> } : Definition extends object ? { [Key in keyof Definition]: ResolveResourceIconInput<Definition[Key]> } : Definition;
21
+ interface DefineResourceIconInputHelpers<Resources extends ReadonlyArray<ResourceDefinition>> {
22
+ resource(): ResourceIconInputMarker<LayoutResourceKey<Resources>>;
23
+ icon<Icon extends ElementType = ElementType>(): ResourceIconComponentInputMarker<Icon>;
24
+ }
25
+ interface DefineResourceIconInputResult<Resources extends ReadonlyArray<ResourceDefinition>, Definition> {
26
+ render(render: (options: {
27
+ resource: LayoutRenderResourcesAccessor<Resources>;
28
+ input: ResolveResourceIconInput<Definition>;
29
+ }) => ReactNode): (input: ResolveResourceIconInput<Definition>) => ResourceIconFn<LayoutRenderResourcesAccessor<Resources>>;
30
+ }
31
+ interface DefineResourceIconFn<Resources extends ReadonlyArray<ResourceDefinition>> {
32
+ <Icon extends ElementType = ElementType>(render: (options: {
33
+ resource: LayoutRenderResourcesAccessor<Resources>;
34
+ Icon: Icon;
35
+ }) => ReactNode): (Icon: Icon) => ResourceIconFn<LayoutRenderResourcesAccessor<Resources>>;
36
+ input<Definition>(defineInput: (helpers: DefineResourceIconInputHelpers<Resources>) => Definition): DefineResourceIconInputResult<Resources, Definition>;
37
+ }
8
38
  interface CreateResourceLinkOptions<Resources extends ReadonlyArray<ResourceDefinition>, Resource extends ResourceLinkConfigResource<Resources>> {
9
39
  /**
10
40
  * The label of the link. This is the text that will be displayed in the link.
@@ -18,7 +48,7 @@ interface CreateResourceLinkOptions<Resources extends ReadonlyArray<ResourceDefi
18
48
  * An optional icon to register with the link. This is a headless approach, meaning you have full control
19
49
  * over the icon's rendering.
20
50
  */
21
- icon?: ReactNode;
51
+ icon?: ResourceIcon<LayoutRenderResourcesAccessor<Resources>>;
22
52
  /**
23
53
  * The href of the link. By default, the href will be `"/"`. While you **don't** need to include a hash,
24
54
  * if you do, it will override the `hash` property (if provided).
@@ -49,7 +79,7 @@ type CreatedResourceLinkBase<Resource extends string> = {
49
79
  href: CreatedResourceHref;
50
80
  label: string;
51
81
  resource: Resource;
52
- icon: ReactNode;
82
+ icon: ResourceIcon;
53
83
  };
54
84
  type CreateResourceLinkWithHash<Resource extends string, Hash extends ResourceLinkHref<Resource>> = Omit<CreatedResourceLinkBase<Resource>, 'href'> & {
55
85
  href: CreatedResourceHref & {
@@ -62,6 +92,7 @@ type CreatedResourceLink<Resources extends ReadonlyArray<ResourceDefinition>, Co
62
92
  interface CreateResourceLinksFn<Resources extends ReadonlyArray<ResourceDefinition>> {
63
93
  <const Resource extends ResourceLinkConfigResource<Resources>>(config: CreateResourceLinkConfig<Resources, Resource>): Array<CreatedResourceLink<Resources, CreateResourceLinkConfig<Resources, Resource>>>;
64
94
  createResourceMap: CreateResourceMapFn<Resources>;
95
+ defineIcon: DefineResourceIconFn<Resources>;
65
96
  withGroups: CreateResourceLinksWithGroupsFn<Resources>;
66
97
  }
67
98
  interface CreateResourceLinkGroupOptions<Resources extends ReadonlyArray<ResourceDefinition>, Links extends object = CreateResourceLinkConfig<Resources>> {
@@ -86,7 +117,7 @@ interface CreateResourceLinkGroupOptions<Resources extends ReadonlyArray<Resourc
86
117
  * An optional icon to register with the group. This is a headless approach, meaning you have full control
87
118
  * over the icon's rendering.
88
119
  */
89
- icon?: ReactNode;
120
+ icon?: ResourceIcon<LayoutRenderResourcesAccessor<Resources>>;
90
121
  /**
91
122
  * The resource links in this group.
92
123
  */
@@ -98,22 +129,68 @@ type CreateResourceLinksWithGroups<Resources extends ReadonlyArray<ResourceDefin
98
129
  activeIcon?: ReactNode;
99
130
  id: string;
100
131
  label: string | null;
101
- icon: ReactNode;
132
+ icon: ResourceIcon<LayoutRenderResourcesAccessor<Resources>>;
102
133
  links: Array<AnyCreatedResourceLink<Resources>>;
103
134
  };
135
+ type ResolvedResourceLinksWithGroups<Resources extends ReadonlyArray<ResourceDefinition>> = Omit<CreateResourceLinksWithGroups<Resources>, 'icon' | 'links'> & {
136
+ icon: ReactNode;
137
+ links: Array<Omit<AnyCreatedResourceLink<Resources>, 'icon'> & {
138
+ icon: ReactNode;
139
+ }>;
140
+ };
104
141
  type CreateResourceMap<Resources extends ReadonlyArray<ResourceDefinition>, Value> = Partial<Record<LayoutResourceKey<Resources>, Value>>;
142
+ type ValidateResource<Resource extends string, Map extends object> = Resource extends Extract<keyof Map, string> ? Resource : never;
143
+ type CreatedResourceMap<Map extends object> = Map & {
144
+ isValidResource(value: unknown): value is Extract<keyof Map, string>;
145
+ get<const Resource extends string>(resource: ValidateResource<Resource, Map>): Map[typeof resource];
146
+ };
105
147
  interface CreateResourceMapFn<Resources extends ReadonlyArray<ResourceDefinition>> {
106
- <const Map extends CreateResourceMap<Resources, unknown>>(map: Map): Map;
148
+ /**
149
+ * Creates a resource map for this layout.
150
+ *
151
+ * @param map Values keyed by this layout's resources. An unknown resource,
152
+ * or a key that collides with a map helper, is rejected.
153
+ */
154
+ <const Map extends CreateResourceMap<Resources, unknown>>(map: Map): CreatedResourceMap<Map>;
107
155
  }
108
156
  interface CreateResourceLinksWithGroupsFn<Resources extends ReadonlyArray<ResourceDefinition>> {
109
- (groups: readonly []): CreateResourceLinksWithGroupsResult<Resources, never>;
110
- <const Resource extends ResourceLinkConfigResource<Resources>>(groups: ReadonlyArray<CreateResourceLinkGroupInput<Resources, Resource>>): CreateResourceLinksWithGroupsResult<Resources, Resource>;
157
+ /**
158
+ * Groups resource links into sections.
159
+ *
160
+ * @param groups The sections to create. At least one is required. The link
161
+ * keys in those sections are the resources `findResource` can resolve.
162
+ */
163
+ <const Resource extends ResourceLinkConfigResource<Resources>>(groups: NonEmptyReadonlyArray<CreateResourceLinkGroupInput<Resources, Resource>>): CreateResourceLinksWithGroupsResult<Resources, Resource>;
111
164
  }
112
165
  type CreateResourceLinksWithGroupsResult<Resources extends ReadonlyArray<ResourceDefinition>, Resource extends ResourceLinkConfigResource<Resources> = ResourceLinkConfigResource<Resources>> = Array<CreateResourceLinksWithGroups<Resources>> & {
166
+ /**
167
+ * Resolves each group's icon and its link icons for the active resource.
168
+ *
169
+ * Functional icons receive the resources accessor for `resource`. Read the
170
+ * groups value directly when you do not need to resolve icons.
171
+ *
172
+ * @param resource The active resource key.
173
+ */
174
+ (resource: LayoutResourceKey<Resources>): Array<ResolvedResourceLinksWithGroups<Resources>>;
175
+ /**
176
+ * Creates a resource map for this layout. Same helper as
177
+ * `createResourceLinks.createResourceMap`.
178
+ *
179
+ * @param map Values keyed by this layout's resources. An unknown resource,
180
+ * or a key that collides with a map helper, is rejected.
181
+ */
113
182
  createResourceMap: CreateResourceMapFn<Resources>;
183
+ /**
184
+ * Returns the group whose links include `resource`.
185
+ *
186
+ * The result is `undefined` when that resource was not configured in any
187
+ * group. A resource outside this layout is rejected by the type.
188
+ *
189
+ * @param resource The resource whose group should be returned.
190
+ */
114
191
  findResource<const RequestedResource extends LayoutResourceKey<Resources> | Resource>(resource: RequestedResource): FindResourceGroupResult<Resources, Resource, RequestedResource>;
115
192
  };
116
193
  type FindResourceGroupResult<Resources extends ReadonlyArray<ResourceDefinition>, IncludedResource extends string, RequestedResource extends string> = RequestedResource extends unknown ? RequestedResource extends IncludedResource ? CreateResourceLinksWithGroups<Resources> : undefined : never;
117
194
  //#endregion
118
- export { CreateResourceLinkConfig, CreateResourceLinkGroupInput, CreateResourceLinkGroupOptions, CreateResourceLinkOptions, CreateResourceLinksFn, CreateResourceLinksWithGroups, CreateResourceLinksWithGroupsFn, CreateResourceLinksWithGroupsResult, CreateResourceMap, CreateResourceMapFn, CreatedResourceHref, CreatedResourceLink, CreatedResourceLinkBase, FindResourceGroupResult, InferHashFromResourceLinkHref, ResourceAnchorLinkFn, ResourceLinkHref };
195
+ export { CreateResourceLinkConfig, CreateResourceLinkGroupInput, CreateResourceLinkGroupOptions, CreateResourceLinkOptions, CreateResourceLinksFn, CreateResourceLinksWithGroups, CreateResourceLinksWithGroupsFn, CreateResourceLinksWithGroupsResult, CreateResourceMap, CreateResourceMapFn, CreatedResourceHref, CreatedResourceLink, CreatedResourceLinkBase, CreatedResourceMap, DefineResourceIconFn, DefineResourceIconInputHelpers, DefineResourceIconInputResult, FindResourceGroupResult, InferHashFromResourceLinkHref, ResolveResourceIconInput, ResolvedResourceLinksWithGroups, ResourceAnchorLinkFn, ResourceIcon, ResourceIconFn, ResourceLinkHref, ValidateResource };
119
196
  //# sourceMappingURL=create-resource-links.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"create-resource-links.d.mts","names":[],"sources":["../../src/create-config/create-resource-links.ts"],"mappings":"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"}
1
+ {"version":3,"file":"create-resource-links.d.mts","names":[],"sources":["../../src/create-config/create-resource-links.ts"],"mappings":"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"}
@@ -1,4 +1,5 @@
1
1
  import { LayoutConfigurationError } from "../errors/layout-configuration-error.mjs";
2
+ import { createIsValidResourceFn } from "../resource.mjs";
2
3
 
3
4
  //#region src/create-config/create-resource-links.ts
4
5
  function resolveResourceLinkHref(value, resource, fallback) {
@@ -55,15 +56,46 @@ function createResourceLinkGroupId(group, index) {
55
56
  if (typeof group.id === "string" && group.id.length > 0) return group.id;
56
57
  return `group-${index}-${typeof group.label === "string" ? group.label : ""}-${Object.keys(group.links).join(",")}`;
57
58
  }
59
+ function readResourceValue(resource) {
60
+ return typeof resource === "string" ? resource : resource.value;
61
+ }
62
+ function createResourceSelection(definitions) {
63
+ const selection = (() => definitions);
64
+ selection.isResource = createIsValidResourceFn(definitions);
65
+ return selection;
66
+ }
67
+ function createRenderResourcesAccessor(resources, current) {
68
+ const accessor = Object.assign(() => resources, { current });
69
+ accessor.isResource = createIsValidResourceFn(resources);
70
+ accessor.omit = ((...keys) => createResourceSelection(resources.filter((resource) => !keys.includes(readResourceValue(resource)))));
71
+ accessor.pick = ((...keys) => createResourceSelection(resources.filter((resource) => keys.includes(readResourceValue(resource)))));
72
+ return accessor;
73
+ }
58
74
  function createResourceMapFn(resources) {
75
+ const helperNames = new Set(["get", "isValidResource"]);
59
76
  const resourceKeys = new Set(resources.map((resource) => typeof resource === "string" ? resource : resource.value));
60
77
  return function createResourceMap(map) {
61
- for (const resource of Object.keys(map)) if (!resourceKeys.has(resource)) throw new LayoutConfigurationError(`[createResourceLinks.createResourceMap]: unknown resource "${resource}".`);
78
+ const configuredResources = Object.keys(map);
79
+ for (const resource of configuredResources) {
80
+ if (!resourceKeys.has(resource)) throw new LayoutConfigurationError(`[createResourceLinks.createResourceMap]: unknown resource "${resource}".`);
81
+ if (helperNames.has(resource)) throw new LayoutConfigurationError(`[createResourceLinks.createResourceMap]: resource "${resource}" conflicts with a map helper.`);
82
+ }
83
+ const configuredResourceKeys = new Set(configuredResources);
84
+ const resourceValues = new Map(configuredResources.map((resource) => [resource, map[resource]]));
85
+ Object.defineProperties(map, {
86
+ get: { value(resource) {
87
+ return resourceValues.get(resource);
88
+ } },
89
+ isValidResource: { value(value) {
90
+ return typeof value === "string" && configuredResourceKeys.has(value);
91
+ } }
92
+ });
62
93
  return map;
63
94
  };
64
95
  }
65
- function createResourceLinksWithGroups(createResourceMap, groups) {
96
+ function createResourceLinksWithGroups(resources, createResourceMap, groups) {
66
97
  if (!Array.isArray(groups)) throw new LayoutConfigurationError("[createResourceLinks.withGroups]: \"groups\" must be an array.");
98
+ if (groups.length === 0) throw new LayoutConfigurationError("[createResourceLinks.withGroups]: \"groups\" must include at least one group.");
67
99
  const createdGroups = groups.map((group, index) => {
68
100
  if (!group) throw new LayoutConfigurationError(`[createResourceLinks.withGroups]: group at index ${index} is required.`);
69
101
  if (typeof group !== "object") throw new LayoutConfigurationError(`[createResourceLinks.withGroups]: group at index ${index} must be an object. Received ${typeof group}`);
@@ -79,22 +111,95 @@ function createResourceLinksWithGroups(createResourceMap, groups) {
79
111
  links: createResourceLinksFromConfig(group.links)
80
112
  };
81
113
  });
82
- Object.defineProperty(createdGroups, "createResourceMap", { value: createResourceMap });
114
+ function resolveGroups(resource) {
115
+ if (resource === void 0) throw new LayoutConfigurationError("[createResourceLinks.withGroups]: \"resource\" is required.");
116
+ const currentResources = createRenderResourcesAccessor(resources, resource);
117
+ return createdGroups.map((group) => ({
118
+ ...group,
119
+ icon: typeof group.icon === "function" ? group.icon(currentResources) : group.icon,
120
+ links: group.links.map((link) => ({
121
+ ...link,
122
+ icon: typeof link.icon === "function" ? link.icon(currentResources) : link.icon
123
+ }))
124
+ }));
125
+ }
126
+ const callableGroups = new Proxy(resolveGroups, {
127
+ get(target, property, receiver) {
128
+ if (property === "createResourceMap") return createResourceMap;
129
+ if (property === Symbol.iterator) return createdGroups[Symbol.iterator].bind(createdGroups);
130
+ if (property === "toJSON") return () => createdGroups;
131
+ if (property in createdGroups) {
132
+ const value = Reflect.get(createdGroups, property);
133
+ return typeof value === "function" ? value.bind(createdGroups) : value;
134
+ }
135
+ return Reflect.get(target, property, receiver);
136
+ },
137
+ getOwnPropertyDescriptor(target, property) {
138
+ return Reflect.getOwnPropertyDescriptor(createdGroups, property) ?? Reflect.getOwnPropertyDescriptor(target, property);
139
+ },
140
+ getPrototypeOf() {
141
+ return Array.prototype;
142
+ },
143
+ ownKeys(target) {
144
+ return [...new Set([...Reflect.ownKeys(target), ...Reflect.ownKeys(createdGroups)])];
145
+ },
146
+ set(target, property, value, receiver) {
147
+ if (property in createdGroups || /^\d+$/.test(String(property))) return Reflect.set(createdGroups, property, value);
148
+ return Reflect.set(target, property, value, receiver);
149
+ }
150
+ });
83
151
  Object.defineProperty(createdGroups, "findResource", { value(resource) {
84
152
  return createdGroups.find((group) => group.links.some((link) => link.resource === resource));
85
153
  } });
86
- return createdGroups;
154
+ return callableGroups;
155
+ }
156
+ function createDefineIcon() {
157
+ function defineIcon(render) {
158
+ return function createIcon(Icon) {
159
+ return function renderIcon(resource) {
160
+ return render({
161
+ resource,
162
+ Icon
163
+ });
164
+ };
165
+ };
166
+ }
167
+ defineIcon.input = function defineIconInput(defineInput) {
168
+ const resourceMarker = {};
169
+ const componentMarker = {};
170
+ defineInput({
171
+ resource() {
172
+ return resourceMarker;
173
+ },
174
+ icon() {
175
+ return componentMarker;
176
+ }
177
+ });
178
+ return { render(render) {
179
+ return function createIcon(input) {
180
+ return function renderIcon(resource) {
181
+ return render({
182
+ resource,
183
+ input
184
+ });
185
+ };
186
+ };
187
+ } };
188
+ };
189
+ return defineIcon;
87
190
  }
88
191
  function createResourceLinksFn(_resources) {
89
192
  const createResourceMap = createResourceMapFn(_resources);
193
+ const defineIcon = createDefineIcon();
90
194
  function createResourceLinks(config) {
91
195
  return createResourceLinksFromConfig(config);
92
196
  }
93
197
  function withGroups(groups) {
94
- return createResourceLinksWithGroups(createResourceMap, groups);
198
+ return createResourceLinksWithGroups(_resources, createResourceMap, groups);
95
199
  }
96
200
  return Object.assign(createResourceLinks, {
97
201
  createResourceMap,
202
+ defineIcon,
98
203
  withGroups
99
204
  });
100
205
  }
@@ -1 +1 @@
1
- {"version":3,"file":"create-resource-links.mjs","names":[],"sources":["../../src/create-config/create-resource-links.ts"],"sourcesContent":["import { ReactNode } from 'react';\nimport {\n type LayoutResourceKey,\n type ResourceDefinition,\n} from '../resource';\nimport { LayoutConfigurationError } from '../errors';\n\nexport type ResourceAnchorLinkFn<Resource extends string> = (\n resource: Resource,\n) => string;\n\nexport type ResourceLinkHref<Resource extends string> =\n | string\n | ResourceAnchorLinkFn<Resource>;\n\nexport type ResourceLinkConfigResource<\n Resources extends ReadonlyArray<ResourceDefinition>,\n> = LayoutResourceKey<Resources> | (string & {});\nexport interface CreateResourceLinkOptions<\n Resources extends ReadonlyArray<ResourceDefinition>,\n Resource extends ResourceLinkConfigResource<Resources>,\n> {\n /**\n * The label of the link. This is the text that will be displayed in the link.\n */\n label: string;\n /**\n * An optional icon to render when the link is active.\n */\n activeIcon?: ReactNode;\n /**\n * An optional icon to register with the link. This is a headless approach, meaning you have full control\n * over the icon's rendering.\n */\n icon?: ReactNode;\n /**\n * The href of the link. By default, the href will be `\"/\"`. While you **don't** need to include a hash,\n * if you do, it will override the `hash` property (if provided).\n */\n href?: ResourceLinkHref<Resource>;\n /**\n * The hash of the link. By default, the hash will be the resource name. If you need to customize it, you can provide\n * a string or a function that returns a string.\n *\n * @remarks A `\"#\"` is not required. The generated href will include it automatically.\n */\n hash?: ResourceLinkHref<Resource>;\n}\n\nexport type CreateResourceLinkConfig<\n Resources extends ReadonlyArray<ResourceDefinition>,\n Resource extends ResourceLinkConfigResource<Resources> = ResourceLinkConfigResource<Resources>,\n> = {\n [Key in Resource]?: CreateResourceLinkOptions<\n Resources,\n Key\n >;\n};\n\nexport type InferHashFromResourceLinkHref<\n Resource extends string,\n Hash extends ResourceLinkHref<Resource>,\n> = Hash extends string\n ? Hash\n : Hash extends ResourceAnchorLinkFn<Resource>\n ? ReturnType<Hash>\n : never;\n\nexport type CreatedResourceHref = {\n /**\n * The full generated href with the `hash` if provided.\n */\n full: string;\n /**\n * The given href. This is the same value as the `href` property of the config.\n */\n given: string;\n};\n\nexport type CreatedResourceLinkBase<Resource extends string> = {\n activeIcon?: ReactNode;\n href: CreatedResourceHref;\n label: string;\n resource: Resource;\n icon: ReactNode;\n};\nexport type CreateResourceLinkWithHash<\n Resource extends string,\n Hash extends ResourceLinkHref<Resource>,\n> = Omit<CreatedResourceLinkBase<Resource>, 'href'> & {\n href: CreatedResourceHref & {\n hash: InferHashFromResourceLinkHref<Resource, Hash>;\n };\n};\n\nexport type CreatedResourceLink<\n Resources extends ReadonlyArray<ResourceDefinition>,\n Config,\n> = {\n [Resource in keyof Config]-?: Resource extends ResourceLinkConfigResource<Resources>\n ? Config[Resource] extends {\n hash: infer hash extends ResourceLinkHref<Resource>;\n }\n ? CreateResourceLinkWithHash<Resource, hash>\n : CreatedResourceLinkBase<Resource>\n : never;\n}[keyof Config];\n\nexport interface CreateResourceLinksFn<\n Resources extends ReadonlyArray<ResourceDefinition>,\n> {\n <const Resource extends ResourceLinkConfigResource<Resources>>(\n config: CreateResourceLinkConfig<Resources, Resource>,\n ): Array<\n CreatedResourceLink<\n Resources,\n CreateResourceLinkConfig<Resources, Resource>\n >\n >;\n createResourceMap: CreateResourceMapFn<Resources>;\n withGroups: CreateResourceLinksWithGroupsFn<Resources>;\n}\n\nexport interface CreateResourceLinkGroupOptions<\n Resources extends ReadonlyArray<ResourceDefinition>,\n Links extends object =\n CreateResourceLinkConfig<Resources>,\n> {\n /**\n * An optional, caller-provided identity for the group. Provide this when you\n * combine groups from more than one `withGroups()` call into one list (e.g.\n * a module-owned nav section alongside a feature-owned one) — without it,\n * the id is derived from the group's `label` and its link resources, which\n * is enough to keep groups from a single call apart but can't distinguish\n * two independently-defined groups that happen to look identical.\n */\n id?: string;\n /**\n * The label of the group. This is the text that will be displayed for the group.\n */\n label?: string;\n /**\n * An optional icon to render when the group is active.\n */\n activeIcon?: ReactNode;\n /**\n * An optional icon to register with the group. This is a headless approach, meaning you have full control\n * over the icon's rendering.\n */\n icon?: ReactNode;\n /**\n * The resource links in this group.\n */\n links: Links;\n}\n\nexport type CreateResourceLinkGroupInput<\n Resources extends ReadonlyArray<ResourceDefinition>,\n Resource extends ResourceLinkConfigResource<Resources> = ResourceLinkConfigResource<Resources>,\n> = CreateResourceLinkGroupOptions<\n Resources,\n CreateResourceLinkConfig<Resources, Resource>\n>;\n\nexport type AnyCreatedResourceLink<\n Resources extends ReadonlyArray<ResourceDefinition>,\n> = {\n [Resource in LayoutResourceKey<Resources>]:\n | CreatedResourceLinkBase<Resource>\n | CreateResourceLinkWithHash<Resource, string>;\n}[LayoutResourceKey<Resources>];\n\nexport type CreateResourceLinksWithGroups<\n Resources extends ReadonlyArray<ResourceDefinition>,\n> = {\n activeIcon?: ReactNode;\n id: string;\n label: string | null;\n icon: ReactNode;\n links: Array<AnyCreatedResourceLink<Resources>>;\n};\n\nexport type CreateResourceMap<\n Resources extends ReadonlyArray<ResourceDefinition>,\n Value,\n> = Partial<Record<LayoutResourceKey<Resources>, Value>>;\n\nexport interface CreateResourceMapFn<\n Resources extends ReadonlyArray<ResourceDefinition>,\n> {\n <const Map extends CreateResourceMap<Resources, unknown>>(map: Map): Map;\n}\n\nexport interface CreateResourceLinksWithGroupsFn<\n Resources extends ReadonlyArray<ResourceDefinition>,\n> {\n (groups: readonly []): CreateResourceLinksWithGroupsResult<Resources, never>;\n <const Resource extends ResourceLinkConfigResource<Resources>>(\n groups: ReadonlyArray<CreateResourceLinkGroupInput<Resources, Resource>>,\n ): CreateResourceLinksWithGroupsResult<Resources, Resource>;\n}\n\nexport type CreateResourceLinksWithGroupsResult<\n Resources extends ReadonlyArray<ResourceDefinition>,\n Resource extends ResourceLinkConfigResource<Resources> = ResourceLinkConfigResource<Resources>,\n> = Array<CreateResourceLinksWithGroups<Resources>> & {\n createResourceMap: CreateResourceMapFn<Resources>;\n findResource<\n const RequestedResource extends LayoutResourceKey<Resources> | Resource,\n >(\n resource: RequestedResource,\n ): FindResourceGroupResult<Resources, Resource, RequestedResource>;\n};\n\nexport type FindResourceGroupResult<\n Resources extends ReadonlyArray<ResourceDefinition>,\n IncludedResource extends string,\n RequestedResource extends string,\n> = RequestedResource extends unknown\n ? RequestedResource extends IncludedResource\n ? CreateResourceLinksWithGroups<Resources>\n : undefined\n : never;\n\nfunction resolveResourceLinkHref<Resource extends string>(\n value: ResourceLinkHref<Resource> | undefined,\n resource: Resource,\n fallback: string,\n) {\n return typeof value === 'function' ? value(resource) : (value ?? fallback);\n}\n\nfunction normalizeResourceLinkHash(hash: string) {\n return hash.replace(/^#+/, '');\n}\n\nfunction createFullResourceLinkHref(href: string, hash: string) {\n if (href.includes('#')) {\n return href;\n }\n\n return `${href}#${normalizeResourceLinkHash(hash)}`;\n}\n\nfunction createResourceLinksFromConfig<\n Resources extends ReadonlyArray<ResourceDefinition>,\n Resource extends ResourceLinkConfigResource<Resources>,\n>(config: CreateResourceLinkConfig<Resources, Resource>) {\n return Object.entries(config).map(([resource, config]) => {\n if (!config) {\n throw new LayoutConfigurationError(\n `[createResourceLinks]: \"config\" is required for the ${resource} resource.`,\n );\n }\n\n if (typeof config !== 'object') {\n throw new LayoutConfigurationError(\n `[createResourceLinks]: \"config\" must be an object for the ${resource} resource. Received ${typeof config}`,\n );\n }\n\n if (!('label' in config)) {\n throw new LayoutConfigurationError(\n `[createResourceLinks]: \"label\" is required for the ${resource} resource.`,\n );\n }\n\n if (typeof config.label !== 'string') {\n throw new LayoutConfigurationError(\n `[createResourceLinks]: \"label\" must be a string for the ${resource} resource. Received ${typeof config.label}`,\n );\n }\n\n const linkConfig = config as CreateResourceLinkOptions<\n Resources,\n typeof resource\n >;\n\n if (\n 'href' in linkConfig &&\n linkConfig.href !== undefined &&\n typeof linkConfig.href !== 'string' &&\n typeof linkConfig.href !== 'function'\n ) {\n throw new LayoutConfigurationError(\n `[createResourceLinks]: \"href\" must be a string or function for the ${resource} resource. Received ${typeof linkConfig.href}`,\n );\n }\n\n if (\n 'hash' in linkConfig &&\n linkConfig.hash !== undefined &&\n typeof linkConfig.hash !== 'string' &&\n typeof linkConfig.hash !== 'function'\n ) {\n throw new LayoutConfigurationError(\n `[createResourceLinks]: \"hash\" must be a string or function for the ${resource} resource. Received ${typeof linkConfig.hash}`,\n );\n }\n\n const givenHref = resolveResourceLinkHref(linkConfig.href, resource, '/');\n const givenHash = resolveResourceLinkHref(\n linkConfig.hash,\n resource,\n resource,\n );\n const href = {\n given: givenHref,\n full: createFullResourceLinkHref(givenHref, givenHash),\n ...(linkConfig.hash !== undefined && !givenHref.includes('#')\n ? { hash: normalizeResourceLinkHash(givenHash) }\n : {}),\n };\n\n return {\n ...(linkConfig.activeIcon !== undefined\n ? { activeIcon: linkConfig.activeIcon }\n : {}),\n href,\n label: config.label,\n resource,\n icon: 'icon' in config ? config.icon : null,\n };\n });\n}\n\n/**\n * Deterministic, not random: a random id (the previous implementation used\n * `crypto.randomUUID()` / `Math.random()`) would make `withGroups()` unsafe to\n * call from module scope — some runtimes (e.g. Cloudflare Workers) disallow\n * generating random values outside a request handler, and config arrays like\n * this are routinely defined as top-level constants.\n *\n * Falls back to the group's own shape — its `label` and its link resource\n * keys — rather than just the array index, so groups from *separate*\n * `withGroups()` calls that get concatenated into one list (a module-owned nav\n * section next to a feature-owned one, say) don't collide just because they're\n * both first in their own call. Two groups that are genuinely identical in\n * shape will still collide; pass an explicit `id` on the group to rule that\n * out entirely.\n */\nfunction createResourceLinkGroupId(\n group: { id?: unknown; label?: unknown; links: object },\n index: number,\n) {\n if (typeof group.id === 'string' && group.id.length > 0) {\n return group.id;\n }\n\n const label = typeof group.label === 'string' ? group.label : '';\n const linkKeys = Object.keys(group.links).join(',');\n return `group-${index}-${label}-${linkKeys}`;\n}\n\nfunction createResourceMapFn<\n Resources extends ReadonlyArray<ResourceDefinition>,\n>(resources: Resources): CreateResourceMapFn<Resources> {\n const resourceKeys = new Set(\n resources.map((resource) =>\n typeof resource === 'string' ? resource : resource.value,\n ),\n );\n\n return function createResourceMap<\n const Map extends CreateResourceMap<Resources, unknown>,\n >(map: Map) {\n for (const resource of Object.keys(map)) {\n if (!resourceKeys.has(resource)) {\n throw new LayoutConfigurationError(\n `[createResourceLinks.createResourceMap]: unknown resource \"${resource}\".`,\n );\n }\n }\n\n return map;\n };\n}\n\nfunction createResourceLinksWithGroups<\n Resources extends ReadonlyArray<ResourceDefinition>,\n Resource extends ResourceLinkConfigResource<Resources>,\n>(\n createResourceMap: CreateResourceMapFn<Resources>,\n groups: ReadonlyArray<CreateResourceLinkGroupInput<Resources, Resource>>,\n): Array<CreateResourceLinksWithGroups<Resources>> {\n if (!Array.isArray(groups)) {\n throw new LayoutConfigurationError(\n '[createResourceLinks.withGroups]: \"groups\" must be an array.',\n );\n }\n\n const createdGroups = groups.map((group, index) => {\n if (!group) {\n throw new LayoutConfigurationError(\n `[createResourceLinks.withGroups]: group at index ${index} is required.`,\n );\n }\n\n if (typeof group !== 'object') {\n throw new LayoutConfigurationError(\n `[createResourceLinks.withGroups]: group at index ${index} must be an object. Received ${typeof group}`,\n );\n }\n\n if (\n 'label' in group &&\n group.label !== undefined &&\n typeof group.label !== 'string'\n ) {\n throw new LayoutConfigurationError(\n `[createResourceLinks.withGroups]: \"label\" must be a string for group at index ${index}. Received ${typeof group.label}`,\n );\n }\n\n if ('id' in group && group.id !== undefined && typeof group.id !== 'string') {\n throw new LayoutConfigurationError(\n `[createResourceLinks.withGroups]: \"id\" must be a string for group at index ${index}. Received ${typeof group.id}`,\n );\n }\n\n if (!('links' in group)) {\n throw new LayoutConfigurationError(\n `[createResourceLinks.withGroups]: \"links\" is required for group at index ${index}.`,\n );\n }\n\n if (\n typeof group.links !== 'object' ||\n group.links === null ||\n Array.isArray(group.links)\n ) {\n throw new LayoutConfigurationError(\n `[createResourceLinks.withGroups]: \"links\" must be an object for group at index ${index}. Received ${Array.isArray(group.links) ? 'array' : typeof group.links}`,\n );\n }\n\n return {\n ...(group.activeIcon !== undefined\n ? { activeIcon: group.activeIcon }\n : {}),\n id: createResourceLinkGroupId(group, index),\n label: 'label' in group ? (group.label ?? null) : null,\n icon: 'icon' in group ? group.icon : null,\n links: createResourceLinksFromConfig(group.links),\n };\n }) as Array<CreateResourceLinksWithGroups<Resources>>;\n\n Object.defineProperty(createdGroups, 'createResourceMap', {\n value: createResourceMap,\n });\n Object.defineProperty(createdGroups, 'findResource', {\n value(resource: ResourceLinkConfigResource<Resources>) {\n return createdGroups.find((group) =>\n group.links.some((link) => link.resource === resource),\n );\n },\n });\n\n return createdGroups as CreateResourceLinksWithGroupsResult<\n Resources,\n Resource\n >;\n}\n\nexport function createResourceLinksFn<\n const Resources extends ReadonlyArray<ResourceDefinition>,\n>(_resources: Resources): CreateResourceLinksFn<Resources> {\n const createResourceMap = createResourceMapFn(_resources);\n\n function createResourceLinks<\n const Resource extends ResourceLinkConfigResource<Resources>,\n >(config: CreateResourceLinkConfig<Resources, Resource>) {\n return createResourceLinksFromConfig(config);\n }\n\n function withGroups<\n const Resource extends ResourceLinkConfigResource<Resources>,\n >(\n groups: ReadonlyArray<CreateResourceLinkGroupInput<Resources, Resource>>,\n ) {\n return createResourceLinksWithGroups(createResourceMap, groups);\n }\n\n return Object.assign(createResourceLinks, {\n createResourceMap,\n withGroups,\n }) as CreateResourceLinksFn<Resources>;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"create-resource-links.mjs","names":[],"sources":["../../src/create-config/create-resource-links.ts"],"sourcesContent":["import type { ElementType, ReactNode } from 'react';\nimport type { NonEmptyReadonlyArray } from '@jfdevelops/react-layout-validator';\nimport {\n createIsValidResourceFn,\n type LayoutResourceKey,\n type ResourceDefinition,\n} from '../resource';\nimport { LayoutConfigurationError } from '../errors';\nimport type {\n LayoutRenderResourcesAccessor,\n ResourceSelection,\n} from './define-layout';\n\nexport type ResourceAnchorLinkFn<Resource extends string> = (\n resource: Resource,\n) => string;\n\nexport type ResourceLinkHref<Resource extends string> =\n | string\n | ResourceAnchorLinkFn<Resource>;\n\nexport type ResourceLinkConfigResource<\n Resources extends ReadonlyArray<ResourceDefinition>,\n> = LayoutResourceKey<Resources> | (string & {});\n\nexport type ResourceIconFn<Resource = any> = (resource: Resource) => ReactNode;\n\nexport type ResourceIcon<Resource = any> = ReactNode | ResourceIconFn<Resource>;\n\ndeclare const resourceIconInputMarker: unique symbol;\ndeclare const resourceIconComponentInputMarker: unique symbol;\n\ntype ResourceIconInputMarker<Value> = {\n readonly [resourceIconInputMarker]: Value;\n};\n\ntype ResourceIconComponentInputMarker<Icon extends ElementType> = {\n readonly [resourceIconComponentInputMarker]: Icon;\n};\n\nexport type ResolveResourceIconInput<Definition> =\n Definition extends ResourceIconComponentInputMarker<infer Icon>\n ? Icon\n : Definition extends ResourceIconInputMarker<infer Value>\n ? Value\n : Definition extends ReadonlyArray<unknown>\n ? { [Index in keyof Definition]: ResolveResourceIconInput<Definition[Index]> }\n : Definition extends object\n ? { [Key in keyof Definition]: ResolveResourceIconInput<Definition[Key]> }\n : Definition;\n\nexport interface DefineResourceIconInputHelpers<\n Resources extends ReadonlyArray<ResourceDefinition>,\n> {\n resource(): ResourceIconInputMarker<LayoutResourceKey<Resources>>;\n icon<Icon extends ElementType = ElementType>(): ResourceIconComponentInputMarker<Icon>;\n}\n\nexport interface DefineResourceIconInputResult<\n Resources extends ReadonlyArray<ResourceDefinition>,\n Definition,\n> {\n render(\n render: (options: {\n resource: LayoutRenderResourcesAccessor<Resources>;\n input: ResolveResourceIconInput<Definition>;\n }) => ReactNode,\n ): (\n input: ResolveResourceIconInput<Definition>,\n ) => ResourceIconFn<LayoutRenderResourcesAccessor<Resources>>;\n}\n\nexport interface DefineResourceIconFn<\n Resources extends ReadonlyArray<ResourceDefinition>,\n> {\n <Icon extends ElementType = ElementType>(\n render: (options: {\n resource: LayoutRenderResourcesAccessor<Resources>;\n Icon: Icon;\n }) => ReactNode,\n ): (Icon: Icon) => ResourceIconFn<LayoutRenderResourcesAccessor<Resources>>;\n input<Definition>(\n defineInput: (\n helpers: DefineResourceIconInputHelpers<Resources>,\n ) => Definition,\n ): DefineResourceIconInputResult<Resources, Definition>;\n}\nexport interface CreateResourceLinkOptions<\n Resources extends ReadonlyArray<ResourceDefinition>,\n Resource extends ResourceLinkConfigResource<Resources>,\n> {\n /**\n * The label of the link. This is the text that will be displayed in the link.\n */\n label: string;\n /**\n * An optional icon to render when the link is active.\n */\n activeIcon?: ReactNode;\n /**\n * An optional icon to register with the link. This is a headless approach, meaning you have full control\n * over the icon's rendering.\n */\n icon?: ResourceIcon<LayoutRenderResourcesAccessor<Resources>>;\n /**\n * The href of the link. By default, the href will be `\"/\"`. While you **don't** need to include a hash,\n * if you do, it will override the `hash` property (if provided).\n */\n href?: ResourceLinkHref<Resource>;\n /**\n * The hash of the link. By default, the hash will be the resource name. If you need to customize it, you can provide\n * a string or a function that returns a string.\n *\n * @remarks A `\"#\"` is not required. The generated href will include it automatically.\n */\n hash?: ResourceLinkHref<Resource>;\n}\n\nexport type CreateResourceLinkConfig<\n Resources extends ReadonlyArray<ResourceDefinition>,\n Resource extends ResourceLinkConfigResource<Resources> = ResourceLinkConfigResource<Resources>,\n> = {\n [Key in Resource]?: CreateResourceLinkOptions<\n Resources,\n Key\n >;\n};\n\nexport type InferHashFromResourceLinkHref<\n Resource extends string,\n Hash extends ResourceLinkHref<Resource>,\n> = Hash extends string\n ? Hash\n : Hash extends ResourceAnchorLinkFn<Resource>\n ? ReturnType<Hash>\n : never;\n\nexport type CreatedResourceHref = {\n /**\n * The full generated href with the `hash` if provided.\n */\n full: string;\n /**\n * The given href. This is the same value as the `href` property of the config.\n */\n given: string;\n};\n\nexport type CreatedResourceLinkBase<Resource extends string> = {\n activeIcon?: ReactNode;\n href: CreatedResourceHref;\n label: string;\n resource: Resource;\n icon: ResourceIcon;\n};\nexport type CreateResourceLinkWithHash<\n Resource extends string,\n Hash extends ResourceLinkHref<Resource>,\n> = Omit<CreatedResourceLinkBase<Resource>, 'href'> & {\n href: CreatedResourceHref & {\n hash: InferHashFromResourceLinkHref<Resource, Hash>;\n };\n};\n\nexport type CreatedResourceLink<\n Resources extends ReadonlyArray<ResourceDefinition>,\n Config,\n> = {\n [Resource in keyof Config]-?: Resource extends ResourceLinkConfigResource<Resources>\n ? Config[Resource] extends {\n hash: infer hash extends ResourceLinkHref<Resource>;\n }\n ? CreateResourceLinkWithHash<Resource, hash>\n : CreatedResourceLinkBase<Resource>\n : never;\n}[keyof Config];\n\nexport interface CreateResourceLinksFn<\n Resources extends ReadonlyArray<ResourceDefinition>,\n> {\n <const Resource extends ResourceLinkConfigResource<Resources>>(\n config: CreateResourceLinkConfig<Resources, Resource>,\n ): Array<\n CreatedResourceLink<\n Resources,\n CreateResourceLinkConfig<Resources, Resource>\n >\n >;\n createResourceMap: CreateResourceMapFn<Resources>;\n defineIcon: DefineResourceIconFn<Resources>;\n withGroups: CreateResourceLinksWithGroupsFn<Resources>;\n}\n\nexport interface CreateResourceLinkGroupOptions<\n Resources extends ReadonlyArray<ResourceDefinition>,\n Links extends object =\n CreateResourceLinkConfig<Resources>,\n> {\n /**\n * An optional, caller-provided identity for the group. Provide this when you\n * combine groups from more than one `withGroups()` call into one list (e.g.\n * a module-owned nav section alongside a feature-owned one) — without it,\n * the id is derived from the group's `label` and its link resources, which\n * is enough to keep groups from a single call apart but can't distinguish\n * two independently-defined groups that happen to look identical.\n */\n id?: string;\n /**\n * The label of the group. This is the text that will be displayed for the group.\n */\n label?: string;\n /**\n * An optional icon to render when the group is active.\n */\n activeIcon?: ReactNode;\n /**\n * An optional icon to register with the group. This is a headless approach, meaning you have full control\n * over the icon's rendering.\n */\n icon?: ResourceIcon<LayoutRenderResourcesAccessor<Resources>>;\n /**\n * The resource links in this group.\n */\n links: Links;\n}\n\nexport type CreateResourceLinkGroupInput<\n Resources extends ReadonlyArray<ResourceDefinition>,\n Resource extends ResourceLinkConfigResource<Resources> = ResourceLinkConfigResource<Resources>,\n> = CreateResourceLinkGroupOptions<\n Resources,\n CreateResourceLinkConfig<Resources, Resource>\n>;\n\nexport type AnyCreatedResourceLink<\n Resources extends ReadonlyArray<ResourceDefinition>,\n> = {\n [Resource in LayoutResourceKey<Resources>]:\n | CreatedResourceLinkBase<Resource>\n | CreateResourceLinkWithHash<Resource, string>;\n}[LayoutResourceKey<Resources>];\n\nexport type CreateResourceLinksWithGroups<\n Resources extends ReadonlyArray<ResourceDefinition>,\n> = {\n activeIcon?: ReactNode;\n id: string;\n label: string | null;\n icon: ResourceIcon<LayoutRenderResourcesAccessor<Resources>>;\n links: Array<AnyCreatedResourceLink<Resources>>;\n};\n\nexport type ResolvedResourceLinksWithGroups<\n Resources extends ReadonlyArray<ResourceDefinition>,\n> = Omit<CreateResourceLinksWithGroups<Resources>, 'icon' | 'links'> & {\n icon: ReactNode;\n links: Array<\n Omit<AnyCreatedResourceLink<Resources>, 'icon'> & { icon: ReactNode }\n >;\n};\n\nexport type CreateResourceMap<\n Resources extends ReadonlyArray<ResourceDefinition>,\n Value,\n> = Partial<Record<LayoutResourceKey<Resources>, Value>>;\n\nexport type ValidateResource<\n Resource extends string,\n Map extends object,\n> = Resource extends Extract<keyof Map, string> ? Resource : never;\n\nexport type CreatedResourceMap<Map extends object> = Map & {\n isValidResource(value: unknown): value is Extract<keyof Map, string>;\n get<const Resource extends string>(\n resource: ValidateResource<Resource, Map>,\n ): Map[typeof resource];\n};\n\nexport interface CreateResourceMapFn<\n Resources extends ReadonlyArray<ResourceDefinition>,\n> {\n /**\n * Creates a resource map for this layout.\n *\n * @param map Values keyed by this layout's resources. An unknown resource,\n * or a key that collides with a map helper, is rejected.\n */\n <const Map extends CreateResourceMap<Resources, unknown>>(\n map: Map,\n ): CreatedResourceMap<Map>;\n}\n\nexport interface CreateResourceLinksWithGroupsFn<\n Resources extends ReadonlyArray<ResourceDefinition>,\n> {\n /**\n * Groups resource links into sections.\n *\n * @param groups The sections to create. At least one is required. The link\n * keys in those sections are the resources `findResource` can resolve.\n */\n <const Resource extends ResourceLinkConfigResource<Resources>>(\n groups: NonEmptyReadonlyArray<\n CreateResourceLinkGroupInput<Resources, Resource>\n >,\n ): CreateResourceLinksWithGroupsResult<Resources, Resource>;\n}\n\nexport type CreateResourceLinksWithGroupsResult<\n Resources extends ReadonlyArray<ResourceDefinition>,\n Resource extends ResourceLinkConfigResource<Resources> = ResourceLinkConfigResource<Resources>,\n> = Array<CreateResourceLinksWithGroups<Resources>> & {\n /**\n * Resolves each group's icon and its link icons for the active resource.\n *\n * Functional icons receive the resources accessor for `resource`. Read the\n * groups value directly when you do not need to resolve icons.\n *\n * @param resource The active resource key.\n */\n (\n resource: LayoutResourceKey<Resources>,\n ): Array<ResolvedResourceLinksWithGroups<Resources>>;\n /**\n * Creates a resource map for this layout. Same helper as\n * `createResourceLinks.createResourceMap`.\n *\n * @param map Values keyed by this layout's resources. An unknown resource,\n * or a key that collides with a map helper, is rejected.\n */\n createResourceMap: CreateResourceMapFn<Resources>;\n /**\n * Returns the group whose links include `resource`.\n *\n * The result is `undefined` when that resource was not configured in any\n * group. A resource outside this layout is rejected by the type.\n *\n * @param resource The resource whose group should be returned.\n */\n findResource<\n const RequestedResource extends LayoutResourceKey<Resources> | Resource,\n >(\n resource: RequestedResource,\n ): FindResourceGroupResult<Resources, Resource, RequestedResource>;\n};\n\nexport type FindResourceGroupResult<\n Resources extends ReadonlyArray<ResourceDefinition>,\n IncludedResource extends string,\n RequestedResource extends string,\n> = RequestedResource extends unknown\n ? RequestedResource extends IncludedResource\n ? CreateResourceLinksWithGroups<Resources>\n : undefined\n : never;\n\nfunction resolveResourceLinkHref<Resource extends string>(\n value: ResourceLinkHref<Resource> | undefined,\n resource: Resource,\n fallback: string,\n) {\n return typeof value === 'function' ? value(resource) : (value ?? fallback);\n}\n\nfunction normalizeResourceLinkHash(hash: string) {\n return hash.replace(/^#+/, '');\n}\n\nfunction createFullResourceLinkHref(href: string, hash: string) {\n if (href.includes('#')) {\n return href;\n }\n\n return `${href}#${normalizeResourceLinkHash(hash)}`;\n}\n\nfunction createResourceLinksFromConfig<\n Resources extends ReadonlyArray<ResourceDefinition>,\n Resource extends ResourceLinkConfigResource<Resources>,\n>(config: CreateResourceLinkConfig<Resources, Resource>) {\n return Object.entries(config).map(([resource, config]) => {\n if (!config) {\n throw new LayoutConfigurationError(\n `[createResourceLinks]: \"config\" is required for the ${resource} resource.`,\n );\n }\n\n if (typeof config !== 'object') {\n throw new LayoutConfigurationError(\n `[createResourceLinks]: \"config\" must be an object for the ${resource} resource. Received ${typeof config}`,\n );\n }\n\n if (!('label' in config)) {\n throw new LayoutConfigurationError(\n `[createResourceLinks]: \"label\" is required for the ${resource} resource.`,\n );\n }\n\n if (typeof config.label !== 'string') {\n throw new LayoutConfigurationError(\n `[createResourceLinks]: \"label\" must be a string for the ${resource} resource. Received ${typeof config.label}`,\n );\n }\n\n const linkConfig = config as CreateResourceLinkOptions<\n Resources,\n typeof resource\n >;\n\n if (\n 'href' in linkConfig &&\n linkConfig.href !== undefined &&\n typeof linkConfig.href !== 'string' &&\n typeof linkConfig.href !== 'function'\n ) {\n throw new LayoutConfigurationError(\n `[createResourceLinks]: \"href\" must be a string or function for the ${resource} resource. Received ${typeof linkConfig.href}`,\n );\n }\n\n if (\n 'hash' in linkConfig &&\n linkConfig.hash !== undefined &&\n typeof linkConfig.hash !== 'string' &&\n typeof linkConfig.hash !== 'function'\n ) {\n throw new LayoutConfigurationError(\n `[createResourceLinks]: \"hash\" must be a string or function for the ${resource} resource. Received ${typeof linkConfig.hash}`,\n );\n }\n\n const givenHref = resolveResourceLinkHref(linkConfig.href, resource, '/');\n const givenHash = resolveResourceLinkHref(\n linkConfig.hash,\n resource,\n resource,\n );\n const href = {\n given: givenHref,\n full: createFullResourceLinkHref(givenHref, givenHash),\n ...(linkConfig.hash !== undefined && !givenHref.includes('#')\n ? { hash: normalizeResourceLinkHash(givenHash) }\n : {}),\n };\n\n return {\n ...(linkConfig.activeIcon !== undefined\n ? { activeIcon: linkConfig.activeIcon }\n : {}),\n href,\n label: config.label,\n resource,\n icon: 'icon' in config ? config.icon : null,\n };\n });\n}\n\n/**\n * Deterministic, not random: a random id (the previous implementation used\n * `crypto.randomUUID()` / `Math.random()`) would make `withGroups()` unsafe to\n * call from module scope — some runtimes (e.g. Cloudflare Workers) disallow\n * generating random values outside a request handler, and config arrays like\n * this are routinely defined as top-level constants.\n *\n * Falls back to the group's own shape — its `label` and its link resource\n * keys — rather than just the array index, so groups from *separate*\n * `withGroups()` calls that get concatenated into one list (a module-owned nav\n * section next to a feature-owned one, say) don't collide just because they're\n * both first in their own call. Two groups that are genuinely identical in\n * shape will still collide; pass an explicit `id` on the group to rule that\n * out entirely.\n */\nfunction createResourceLinkGroupId(\n group: { id?: unknown; label?: unknown; links: object },\n index: number,\n) {\n if (typeof group.id === 'string' && group.id.length > 0) {\n return group.id;\n }\n\n const label = typeof group.label === 'string' ? group.label : '';\n const linkKeys = Object.keys(group.links).join(',');\n return `group-${index}-${label}-${linkKeys}`;\n}\n\nfunction readResourceValue(resource: ResourceDefinition) {\n return typeof resource === 'string' ? resource : resource.value;\n}\n\nfunction createResourceSelection(\n definitions: ReadonlyArray<ResourceDefinition>,\n): ResourceSelection<ReadonlyArray<ResourceDefinition>> {\n const selection = (() => definitions) as ResourceSelection<\n ReadonlyArray<ResourceDefinition>\n >;\n\n selection.isResource = createIsValidResourceFn(definitions);\n\n return selection;\n}\n\nfunction createRenderResourcesAccessor<\n Resources extends ReadonlyArray<ResourceDefinition>,\n>(resources: Resources, current: LayoutResourceKey<Resources>) {\n const accessor = Object.assign(() => resources, {\n current,\n }) as LayoutRenderResourcesAccessor<Resources>;\n\n accessor.isResource = createIsValidResourceFn(resources);\n accessor.omit = ((...keys: string[]) =>\n createResourceSelection(\n resources.filter((resource) => !keys.includes(readResourceValue(resource))),\n )) as LayoutRenderResourcesAccessor<Resources>['omit'];\n accessor.pick = ((...keys: string[]) =>\n createResourceSelection(\n resources.filter((resource) => keys.includes(readResourceValue(resource))),\n )) as LayoutRenderResourcesAccessor<Resources>['pick'];\n\n return accessor;\n}\n\nfunction createResourceMapFn<\n Resources extends ReadonlyArray<ResourceDefinition>,\n>(resources: Resources): CreateResourceMapFn<Resources> {\n const helperNames = new Set(['get', 'isValidResource']);\n const resourceKeys = new Set(\n resources.map((resource) =>\n typeof resource === 'string' ? resource : resource.value,\n ),\n );\n\n return function createResourceMap<\n const Map extends CreateResourceMap<Resources, unknown>,\n >(map: Map) {\n const configuredResources = Object.keys(map);\n\n for (const resource of configuredResources) {\n if (!resourceKeys.has(resource)) {\n throw new LayoutConfigurationError(\n `[createResourceLinks.createResourceMap]: unknown resource \"${resource}\".`,\n );\n }\n\n if (helperNames.has(resource)) {\n throw new LayoutConfigurationError(\n `[createResourceLinks.createResourceMap]: resource \"${resource}\" conflicts with a map helper.`,\n );\n }\n }\n\n const configuredResourceKeys = new Set(configuredResources);\n const resourceValues = new Map(\n configuredResources.map((resource) => [\n resource,\n (map as Record<string, unknown>)[resource],\n ]),\n );\n\n Object.defineProperties(map, {\n get: {\n value(resource: string) {\n return resourceValues.get(resource);\n },\n },\n isValidResource: {\n value(value: unknown): value is Extract<keyof Map, string> {\n return (\n typeof value === 'string' && configuredResourceKeys.has(value)\n );\n },\n },\n });\n\n return map as CreatedResourceMap<Map>;\n };\n}\n\nfunction createResourceLinksWithGroups<\n Resources extends ReadonlyArray<ResourceDefinition>,\n Resource extends ResourceLinkConfigResource<Resources>,\n>(\n resources: Resources,\n createResourceMap: CreateResourceMapFn<Resources>,\n groups: ReadonlyArray<CreateResourceLinkGroupInput<Resources, Resource>>,\n): CreateResourceLinksWithGroupsResult<Resources, Resource> {\n if (!Array.isArray(groups)) {\n throw new LayoutConfigurationError(\n '[createResourceLinks.withGroups]: \"groups\" must be an array.',\n );\n }\n\n if (groups.length === 0) {\n throw new LayoutConfigurationError(\n '[createResourceLinks.withGroups]: \"groups\" must include at least one group.',\n );\n }\n\n const createdGroups = groups.map((group, index) => {\n if (!group) {\n throw new LayoutConfigurationError(\n `[createResourceLinks.withGroups]: group at index ${index} is required.`,\n );\n }\n\n if (typeof group !== 'object') {\n throw new LayoutConfigurationError(\n `[createResourceLinks.withGroups]: group at index ${index} must be an object. Received ${typeof group}`,\n );\n }\n\n if (\n 'label' in group &&\n group.label !== undefined &&\n typeof group.label !== 'string'\n ) {\n throw new LayoutConfigurationError(\n `[createResourceLinks.withGroups]: \"label\" must be a string for group at index ${index}. Received ${typeof group.label}`,\n );\n }\n\n if ('id' in group && group.id !== undefined && typeof group.id !== 'string') {\n throw new LayoutConfigurationError(\n `[createResourceLinks.withGroups]: \"id\" must be a string for group at index ${index}. Received ${typeof group.id}`,\n );\n }\n\n if (!('links' in group)) {\n throw new LayoutConfigurationError(\n `[createResourceLinks.withGroups]: \"links\" is required for group at index ${index}.`,\n );\n }\n\n if (\n typeof group.links !== 'object' ||\n group.links === null ||\n Array.isArray(group.links)\n ) {\n throw new LayoutConfigurationError(\n `[createResourceLinks.withGroups]: \"links\" must be an object for group at index ${index}. Received ${Array.isArray(group.links) ? 'array' : typeof group.links}`,\n );\n }\n\n return {\n ...(group.activeIcon !== undefined\n ? { activeIcon: group.activeIcon }\n : {}),\n id: createResourceLinkGroupId(group, index),\n label: 'label' in group ? (group.label ?? null) : null,\n icon: 'icon' in group ? group.icon : null,\n links: createResourceLinksFromConfig(group.links),\n };\n }) as Array<CreateResourceLinksWithGroups<Resources>>;\n\n function resolveGroups(resource?: LayoutResourceKey<Resources>) {\n if (resource === undefined) {\n throw new LayoutConfigurationError(\n '[createResourceLinks.withGroups]: \"resource\" is required.',\n );\n }\n\n const currentResources = createRenderResourcesAccessor(resources, resource);\n\n return createdGroups.map((group) => ({\n ...group,\n icon:\n typeof group.icon === 'function'\n ? group.icon(currentResources)\n : group.icon,\n links: group.links.map((link) => ({\n ...link,\n icon:\n typeof link.icon === 'function'\n ? link.icon(currentResources)\n : link.icon,\n })),\n }));\n }\n\n const callableGroups = new Proxy(resolveGroups, {\n get(target, property, receiver) {\n if (property === 'createResourceMap') {\n return createResourceMap;\n }\n\n if (property === Symbol.iterator) {\n return createdGroups[Symbol.iterator].bind(createdGroups);\n }\n\n if (property === 'toJSON') {\n return () => createdGroups;\n }\n\n if (property in createdGroups) {\n const value = Reflect.get(createdGroups, property);\n return typeof value === 'function' ? value.bind(createdGroups) : value;\n }\n\n return Reflect.get(target, property, receiver);\n },\n getOwnPropertyDescriptor(target, property) {\n return (\n Reflect.getOwnPropertyDescriptor(createdGroups, property) ??\n Reflect.getOwnPropertyDescriptor(target, property)\n );\n },\n getPrototypeOf() {\n return Array.prototype;\n },\n ownKeys(target) {\n return [\n ...new Set([\n ...Reflect.ownKeys(target),\n ...Reflect.ownKeys(createdGroups),\n ]),\n ];\n },\n set(target, property, value, receiver) {\n if (property in createdGroups || /^\\d+$/.test(String(property))) {\n return Reflect.set(createdGroups, property, value);\n }\n\n return Reflect.set(target, property, value, receiver);\n },\n });\n Object.defineProperty(createdGroups, 'findResource', {\n value(resource: ResourceLinkConfigResource<Resources>) {\n return createdGroups.find((group) =>\n group.links.some((link) => link.resource === resource),\n );\n },\n });\n\n return callableGroups as CreateResourceLinksWithGroupsResult<\n Resources,\n Resource\n >;\n}\n\nfunction createDefineIcon<\n Resources extends ReadonlyArray<ResourceDefinition>,\n>(): DefineResourceIconFn<Resources> {\n function defineIcon<Icon extends ElementType = ElementType>(\n render: (options: {\n resource: LayoutRenderResourcesAccessor<Resources>;\n Icon: Icon;\n }) => ReactNode,\n ) {\n return function createIcon(Icon: Icon) {\n return function renderIcon(\n resource: LayoutRenderResourcesAccessor<Resources>,\n ) {\n return render({ resource, Icon });\n };\n };\n }\n\n defineIcon.input = function defineIconInput<Definition>(\n defineInput: (\n helpers: DefineResourceIconInputHelpers<Resources>,\n ) => Definition,\n ): DefineResourceIconInputResult<Resources, Definition> {\n const resourceMarker = {} as ResourceIconInputMarker<never>;\n const componentMarker = {} as ResourceIconComponentInputMarker<ElementType>;\n\n defineInput({\n resource() {\n return resourceMarker;\n },\n icon<Icon extends ElementType = ElementType>() {\n return componentMarker as ResourceIconComponentInputMarker<Icon>;\n },\n });\n\n return {\n render(\n render: (options: {\n resource: LayoutRenderResourcesAccessor<Resources>;\n input: ResolveResourceIconInput<Definition>;\n }) => ReactNode,\n ) {\n return function createIcon(\n input: ResolveResourceIconInput<Definition>,\n ) {\n return function renderIcon(\n resource: LayoutRenderResourcesAccessor<Resources>,\n ) {\n return render({ resource, input });\n };\n };\n },\n };\n };\n\n return defineIcon;\n}\n\nexport function createResourceLinksFn<\n const Resources extends ReadonlyArray<ResourceDefinition>,\n>(_resources: Resources): CreateResourceLinksFn<Resources> {\n const createResourceMap = createResourceMapFn(_resources);\n const defineIcon = createDefineIcon<Resources>();\n\n function createResourceLinks<\n const Resource extends ResourceLinkConfigResource<Resources>,\n >(config: CreateResourceLinkConfig<Resources, Resource>) {\n return createResourceLinksFromConfig(config);\n }\n\n function withGroups<\n const Resource extends ResourceLinkConfigResource<Resources>,\n >(\n groups: NonEmptyReadonlyArray<\n CreateResourceLinkGroupInput<Resources, Resource>\n >,\n ) {\n return createResourceLinksWithGroups(_resources, createResourceMap, groups);\n }\n\n return Object.assign(createResourceLinks, {\n createResourceMap,\n defineIcon,\n withGroups,\n }) as CreateResourceLinksFn<Resources>;\n}\n"],"mappings":"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"}
@@ -5,6 +5,6 @@ import { CreateGetComponentForPath, GetComponentForPath, GetComponentForPathApi,
5
5
  import { ForPaths, ForPathsRenderContext, RenderedPathComponent, RenderedPathComponentHOF, TargetPathBuilder } from "./for-paths/builder.cjs";
6
6
  import { IsSubResourceKey, IsSubResourceKeyOptions, IsSubResourceKeyResult } from "./is-sub-resource-key.cjs";
7
7
  import { ComponentTypes, CreateResourceConfigFn, CreatedResourceConfig, GetComponent, GetComponentAtBound, GetComponentForResource, GetComponentForResourceOptions, GetComponentOptions, GetComponentOptionsForResource, ResourceConfigPath, ResourceFromGetComponentBound, SubResourceFromGetComponentBound, ValidateForResourceBound } from "./get-component.cjs";
8
- import { CreateResourceLinkConfig, CreateResourceLinkGroupInput, CreateResourceLinkGroupOptions, CreateResourceLinkOptions, CreateResourceLinksFn, CreateResourceLinksWithGroups, CreateResourceLinksWithGroupsFn, CreateResourceLinksWithGroupsResult, CreateResourceMap, CreateResourceMapFn, CreatedResourceHref, CreatedResourceLink, CreatedResourceLinkBase, FindResourceGroupResult, InferHashFromResourceLinkHref, ResourceAnchorLinkFn, ResourceLinkHref } from "./create-resource-links.cjs";
8
+ import { CreateResourceLinkConfig, CreateResourceLinkGroupInput, CreateResourceLinkGroupOptions, CreateResourceLinkOptions, CreateResourceLinksFn, CreateResourceLinksWithGroups, CreateResourceLinksWithGroupsFn, CreateResourceLinksWithGroupsResult, CreateResourceMap, CreateResourceMapFn, CreatedResourceHref, CreatedResourceLink, CreatedResourceLinkBase, CreatedResourceMap, DefineResourceIconFn, DefineResourceIconInputHelpers, DefineResourceIconInputResult, FindResourceGroupResult, InferHashFromResourceLinkHref, ResolveResourceIconInput, ResolvedResourceLinksWithGroups, ResourceAnchorLinkFn, ResourceIcon, ResourceIconFn, ResourceLinkHref, ValidateResource } from "./create-resource-links.cjs";
9
9
  import { CreateResourceLayoutFn, CreateResourceLayoutMakeComposableOptions, CreateResourceLayoutWithLayoutOptions, DefineResourceLayout, DefineResourceLayoutFn, DefineResourceLayoutForResources, DefineResourceLayoutForResourcesFactory, DefineResourceLayoutWithLayoutFn, DefineResourcesFn, DefinedResourceLayoutWithShell, LayoutRenderResourcesAccessor, LayoutResourcesAccessor, ResourceLayoutProvider, ResourceLayoutProviderProps, ResourceLayoutShellComponent, ResourceLayoutShellProps, ResourceSelection, ShellLayoutDefinition, ShellRenderContext, defineResourceLayout } from "./define-layout.cjs";
10
10
  import { ResourceLayoutShellPublisher } from "./shell-context.cjs";
@@ -5,6 +5,6 @@ import { CreateGetComponentForPath, GetComponentForPath, GetComponentForPathApi,
5
5
  import { ForPaths, ForPathsRenderContext, RenderedPathComponent, RenderedPathComponentHOF, TargetPathBuilder } from "./for-paths/builder.mjs";
6
6
  import { IsSubResourceKey, IsSubResourceKeyOptions, IsSubResourceKeyResult } from "./is-sub-resource-key.mjs";
7
7
  import { ComponentTypes, CreateResourceConfigFn, CreatedResourceConfig, GetComponent, GetComponentAtBound, GetComponentForResource, GetComponentForResourceOptions, GetComponentOptions, GetComponentOptionsForResource, ResourceConfigPath, ResourceFromGetComponentBound, SubResourceFromGetComponentBound, ValidateForResourceBound } from "./get-component.mjs";
8
- import { CreateResourceLinkConfig, CreateResourceLinkGroupInput, CreateResourceLinkGroupOptions, CreateResourceLinkOptions, CreateResourceLinksFn, CreateResourceLinksWithGroups, CreateResourceLinksWithGroupsFn, CreateResourceLinksWithGroupsResult, CreateResourceMap, CreateResourceMapFn, CreatedResourceHref, CreatedResourceLink, CreatedResourceLinkBase, FindResourceGroupResult, InferHashFromResourceLinkHref, ResourceAnchorLinkFn, ResourceLinkHref } from "./create-resource-links.mjs";
8
+ import { CreateResourceLinkConfig, CreateResourceLinkGroupInput, CreateResourceLinkGroupOptions, CreateResourceLinkOptions, CreateResourceLinksFn, CreateResourceLinksWithGroups, CreateResourceLinksWithGroupsFn, CreateResourceLinksWithGroupsResult, CreateResourceMap, CreateResourceMapFn, CreatedResourceHref, CreatedResourceLink, CreatedResourceLinkBase, CreatedResourceMap, DefineResourceIconFn, DefineResourceIconInputHelpers, DefineResourceIconInputResult, FindResourceGroupResult, InferHashFromResourceLinkHref, ResolveResourceIconInput, ResolvedResourceLinksWithGroups, ResourceAnchorLinkFn, ResourceIcon, ResourceIconFn, ResourceLinkHref, ValidateResource } from "./create-resource-links.mjs";
9
9
  import { CreateResourceLayoutFn, CreateResourceLayoutMakeComposableOptions, CreateResourceLayoutWithLayoutOptions, DefineResourceLayout, DefineResourceLayoutFn, DefineResourceLayoutForResources, DefineResourceLayoutForResourcesFactory, DefineResourceLayoutWithLayoutFn, DefineResourcesFn, DefinedResourceLayoutWithShell, LayoutRenderResourcesAccessor, LayoutResourcesAccessor, ResourceLayoutProvider, ResourceLayoutProviderProps, ResourceLayoutShellComponent, ResourceLayoutShellProps, ResourceSelection, ShellLayoutDefinition, ShellRenderContext, defineResourceLayout } from "./define-layout.mjs";
10
10
  import { ResourceLayoutShellPublisher } from "./shell-context.mjs";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jfdevelops/react-layout",
3
- "version": "0.23.0",
3
+ "version": "0.24.1",
4
4
  "description": "A React library for creating layout components.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -47,6 +47,110 @@ does not follow the general pattern.
47
47
  Render links using the generated `resource` value instead of reconstructing it
48
48
  from labels.
49
49
 
50
+ ### Define reusable resource icons
51
+
52
+ The `icon` property accepts either a plain React node or a function. Use a
53
+ plain node when the icon does not depend on the current resource:
54
+
55
+ ```tsx
56
+ const navigation = createResourceLinks.withGroups([
57
+ {
58
+ links: {
59
+ dashboard: {
60
+ label: 'Dashboard',
61
+ icon: <DashboardIcon />,
62
+ },
63
+ },
64
+ },
65
+ ]);
66
+ ```
67
+
68
+ Use `defineIcon` when multiple link definitions share the same render logic.
69
+ Without custom input, the returned function accepts the icon component
70
+ directly. Its render callback receives the full render-scoped resources
71
+ accessor:
72
+
73
+ ```tsx
74
+ import type { LucideIcon } from 'lucide-react';
75
+
76
+ const defineDashboardIcon = createResourceLinks.defineIcon<LucideIcon>(
77
+ ({ resource, Icon }) => (
78
+ <Icon
79
+ fill={resource.current === 'dashboard' ? 'currentColor' : 'none'}
80
+ />
81
+ ),
82
+ );
83
+
84
+ const navigation = createResourceLinks.withGroups([
85
+ {
86
+ links: {
87
+ dashboard: {
88
+ label: 'Dashboard',
89
+ icon: defineDashboardIcon(DashboardIcon),
90
+ },
91
+ },
92
+ },
93
+ ]);
94
+ ```
95
+
96
+ Use `.input(...).render(...)` when the same renderer needs caller-provided
97
+ values. The `.input` callback declares the input shape: `resource()` represents
98
+ a valid resource key and `icon<IconType>()` represents an icon component. The
99
+ render callback receives the full resources accessor and the inferred input
100
+ object. The icon component is available as
101
+ `input.Icon`; it is intentionally not duplicated as a top-level render value.
102
+
103
+ ```tsx
104
+ import type { LucideIcon } from 'lucide-react';
105
+
106
+ const defineNavigationIcon = createResourceLinks.defineIcon
107
+ .input(({ resource, icon }) => ({
108
+ resource: resource(),
109
+ Icon: icon<LucideIcon>(),
110
+ }))
111
+ .render(({ resource, input }) => {
112
+ const Icon = input.Icon;
113
+
114
+ return (
115
+ <Icon
116
+ fill={
117
+ resource.current === input.resource ? 'currentColor' : 'none'
118
+ }
119
+ />
120
+ );
121
+ });
122
+
123
+ const navigation = createResourceLinks.withGroups([
124
+ {
125
+ links: {
126
+ dashboard: {
127
+ label: 'Dashboard',
128
+ icon: defineNavigationIcon({
129
+ resource: 'dashboard',
130
+ Icon: DashboardIcon,
131
+ }),
132
+ },
133
+ appointments: {
134
+ label: 'Appointments',
135
+ icon: defineNavigationIcon({
136
+ resource: 'appointments',
137
+ Icon: AppointmentsIcon,
138
+ }),
139
+ },
140
+ },
141
+ },
142
+ ]);
143
+ ```
144
+
145
+ Resolve functional icons by calling the grouped collection with the current
146
+ resource key. The collection remains array-like when unresolved, so array
147
+ methods such as `filter` and `map` are still available directly.
148
+
149
+ ```tsx
150
+ const resolvedNavigation = navigation(currentResource);
151
+ const visibleGroups = navigation.filter(Boolean);
152
+ ```
153
+
50
154
  ## Create the resource configuration
51
155
 
52
156
  Create a typed configuration that maps each routeable resource to its normal