@jfdevelops/react-layout 0.22.0 → 0.23.0

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.
Files changed (119) hide show
  1. package/dist/create-config/create-resource-links.cjs +42 -22
  2. package/dist/create-config/create-resource-links.cjs.map +1 -1
  3. package/dist/create-config/create-resource-links.d.cts +25 -2
  4. package/dist/create-config/create-resource-links.d.cts.map +1 -1
  5. package/dist/create-config/create-resource-links.d.mts +25 -2
  6. package/dist/create-config/create-resource-links.d.mts.map +1 -1
  7. package/dist/create-config/create-resource-links.mjs +43 -22
  8. package/dist/create-config/create-resource-links.mjs.map +1 -1
  9. package/dist/create-config/define-layout.cjs +29 -9
  10. package/dist/create-config/define-layout.cjs.map +1 -1
  11. package/dist/create-config/define-layout.d.cts +13 -1
  12. package/dist/create-config/define-layout.d.cts.map +1 -1
  13. package/dist/create-config/define-layout.d.mts +13 -1
  14. package/dist/create-config/define-layout.d.mts.map +1 -1
  15. package/dist/create-config/define-layout.mjs +30 -10
  16. package/dist/create-config/define-layout.mjs.map +1 -1
  17. package/dist/create-config/for-paths/builder.cjs +6 -6
  18. package/dist/create-config/for-paths/builder.cjs.map +1 -1
  19. package/dist/create-config/for-paths/builder.mjs +6 -6
  20. package/dist/create-config/for-paths/builder.mjs.map +1 -1
  21. package/dist/create-config/for-resource.cjs +2 -1
  22. package/dist/create-config/for-resource.cjs.map +1 -1
  23. package/dist/create-config/for-resource.d.cts.map +1 -1
  24. package/dist/create-config/for-resource.d.mts.map +1 -1
  25. package/dist/create-config/for-resource.mjs +2 -1
  26. package/dist/create-config/for-resource.mjs.map +1 -1
  27. package/dist/create-config/for-resources/create-for-resources.cjs +15 -14
  28. package/dist/create-config/for-resources/create-for-resources.cjs.map +1 -1
  29. package/dist/create-config/for-resources/create-for-resources.mjs +15 -14
  30. package/dist/create-config/for-resources/create-for-resources.mjs.map +1 -1
  31. package/dist/create-config/index.d.cts +2 -2
  32. package/dist/create-config/index.d.mts +2 -2
  33. package/dist/errors/invalid-component.cjs +15 -7
  34. package/dist/errors/invalid-component.cjs.map +1 -1
  35. package/dist/errors/invalid-component.d.cts +106 -10
  36. package/dist/errors/invalid-component.d.cts.map +1 -1
  37. package/dist/errors/invalid-component.d.mts +106 -10
  38. package/dist/errors/invalid-component.d.mts.map +1 -1
  39. package/dist/errors/invalid-component.mjs +15 -7
  40. package/dist/errors/invalid-component.mjs.map +1 -1
  41. package/dist/errors/invalid-config.cjs +8 -3
  42. package/dist/errors/invalid-config.cjs.map +1 -1
  43. package/dist/errors/invalid-config.d.cts +96 -6
  44. package/dist/errors/invalid-config.d.cts.map +1 -1
  45. package/dist/errors/invalid-config.d.mts +96 -6
  46. package/dist/errors/invalid-config.d.mts.map +1 -1
  47. package/dist/errors/invalid-config.mjs +8 -3
  48. package/dist/errors/invalid-config.mjs.map +1 -1
  49. package/dist/errors/invalid-path.cjs +11 -5
  50. package/dist/errors/invalid-path.cjs.map +1 -1
  51. package/dist/errors/invalid-path.d.cts +100 -8
  52. package/dist/errors/invalid-path.d.cts.map +1 -1
  53. package/dist/errors/invalid-path.d.mts +100 -8
  54. package/dist/errors/invalid-path.d.mts.map +1 -1
  55. package/dist/errors/invalid-path.mjs +11 -5
  56. package/dist/errors/invalid-path.mjs.map +1 -1
  57. package/dist/errors/invalid-resource.cjs +11 -5
  58. package/dist/errors/invalid-resource.cjs.map +1 -1
  59. package/dist/errors/invalid-resource.d.cts +100 -8
  60. package/dist/errors/invalid-resource.d.cts.map +1 -1
  61. package/dist/errors/invalid-resource.d.mts +100 -8
  62. package/dist/errors/invalid-resource.d.mts.map +1 -1
  63. package/dist/errors/invalid-resource.mjs +11 -5
  64. package/dist/errors/invalid-resource.mjs.map +1 -1
  65. package/dist/errors/invalid-sub-resource.cjs +12 -5
  66. package/dist/errors/invalid-sub-resource.cjs.map +1 -1
  67. package/dist/errors/invalid-sub-resource.d.cts +103 -9
  68. package/dist/errors/invalid-sub-resource.d.cts.map +1 -1
  69. package/dist/errors/invalid-sub-resource.d.mts +103 -9
  70. package/dist/errors/invalid-sub-resource.d.mts.map +1 -1
  71. package/dist/errors/invalid-sub-resource.mjs +12 -5
  72. package/dist/errors/invalid-sub-resource.mjs.map +1 -1
  73. package/dist/errors/layout-configuration-error.cjs +17 -0
  74. package/dist/errors/layout-configuration-error.cjs.map +1 -0
  75. package/dist/errors/layout-configuration-error.d.cts +99 -0
  76. package/dist/errors/layout-configuration-error.d.cts.map +1 -0
  77. package/dist/errors/layout-configuration-error.d.mts +99 -0
  78. package/dist/errors/layout-configuration-error.d.mts.map +1 -0
  79. package/dist/errors/layout-configuration-error.mjs +17 -0
  80. package/dist/errors/layout-configuration-error.mjs.map +1 -0
  81. package/dist/errors/missing-path-variable.cjs +12 -5
  82. package/dist/errors/missing-path-variable.cjs.map +1 -1
  83. package/dist/errors/missing-path-variable.d.cts +99 -7
  84. package/dist/errors/missing-path-variable.d.cts.map +1 -1
  85. package/dist/errors/missing-path-variable.d.mts +99 -7
  86. package/dist/errors/missing-path-variable.d.mts.map +1 -1
  87. package/dist/errors/missing-path-variable.mjs +12 -5
  88. package/dist/errors/missing-path-variable.mjs.map +1 -1
  89. package/dist/errors/missing-resource-provider.cjs +12 -0
  90. package/dist/errors/missing-resource-provider.cjs.map +1 -0
  91. package/dist/errors/missing-resource-provider.d.cts +91 -0
  92. package/dist/errors/missing-resource-provider.d.cts.map +1 -0
  93. package/dist/errors/missing-resource-provider.d.mts +91 -0
  94. package/dist/errors/missing-resource-provider.d.mts.map +1 -0
  95. package/dist/errors/missing-resource-provider.mjs +12 -0
  96. package/dist/errors/missing-resource-provider.mjs.map +1 -0
  97. package/dist/errors/react-layout-error.cjs +38 -43
  98. package/dist/errors/react-layout-error.cjs.map +1 -1
  99. package/dist/errors/react-layout-error.d.cts +81 -38
  100. package/dist/errors/react-layout-error.d.cts.map +1 -1
  101. package/dist/errors/react-layout-error.d.mts +81 -38
  102. package/dist/errors/react-layout-error.d.mts.map +1 -1
  103. package/dist/errors/react-layout-error.mjs +39 -43
  104. package/dist/errors/react-layout-error.mjs.map +1 -1
  105. package/dist/index.cjs +6 -3
  106. package/dist/index.d.cts +6 -3
  107. package/dist/index.d.mts +6 -3
  108. package/dist/index.mjs +6 -4
  109. package/dist/node_modules/.pnpm/@jfdevelops_create-error@0.2.0_patch_hash_afc71b16c3f866f54416d30f467784ddc31736feb04f281a5fc4772ea422f4a9/node_modules/@jfdevelops/create-error/dist/types.d.cts +107 -0
  110. package/dist/node_modules/.pnpm/@jfdevelops_create-error@0.2.0_patch_hash_afc71b16c3f866f54416d30f467784ddc31736feb04f281a5fc4772ea422f4a9/node_modules/@jfdevelops/create-error/dist/types.d.cts.map +1 -0
  111. package/dist/node_modules/.pnpm/@jfdevelops_create-error@0.2.0_patch_hash_afc71b16c3f866f54416d30f467784ddc31736feb04f281a5fc4772ea422f4a9/node_modules/@jfdevelops/create-error/dist/types.d.mts +107 -0
  112. package/dist/node_modules/.pnpm/@jfdevelops_create-error@0.2.0_patch_hash_afc71b16c3f866f54416d30f467784ddc31736feb04f281a5fc4772ea422f4a9/node_modules/@jfdevelops/create-error/dist/types.d.mts.map +1 -0
  113. package/dist/resource.cjs +2 -1
  114. package/dist/resource.cjs.map +1 -1
  115. package/dist/resource.d.cts.map +1 -1
  116. package/dist/resource.d.mts.map +1 -1
  117. package/dist/resource.mjs +3 -1
  118. package/dist/resource.mjs.map +1 -1
  119. package/package.json +8 -6
@@ -1,3 +1,4 @@
1
+ const require_layout_configuration_error = require('../errors/layout-configuration-error.cjs');
1
2
 
2
3
  //#region src/create-config/create-resource-links.ts
3
4
  function resolveResourceLinkHref(value, resource, fallback) {
@@ -12,21 +13,23 @@ function createFullResourceLinkHref(href, hash) {
12
13
  }
13
14
  function createResourceLinksFromConfig(config) {
14
15
  return Object.entries(config).map(([resource, config]) => {
15
- if (!config) throw new Error(`[createResourceLinks]: "config" is required for the ${resource} resource.`);
16
- if (typeof config !== "object") throw new Error(`[createResourceLinks]: "config" must be an object for the ${resource} resource. Received ${typeof config}`);
17
- if (!("label" in config)) throw new Error(`[createResourceLinks]: "label" is required for the ${resource} resource.`);
18
- if (typeof config.label !== "string") throw new Error(`[createResourceLinks]: "label" must be a string for the ${resource} resource. Received ${typeof config.label}`);
16
+ if (!config) throw new require_layout_configuration_error.LayoutConfigurationError(`[createResourceLinks]: "config" is required for the ${resource} resource.`);
17
+ if (typeof config !== "object") throw new require_layout_configuration_error.LayoutConfigurationError(`[createResourceLinks]: "config" must be an object for the ${resource} resource. Received ${typeof config}`);
18
+ if (!("label" in config)) throw new require_layout_configuration_error.LayoutConfigurationError(`[createResourceLinks]: "label" is required for the ${resource} resource.`);
19
+ if (typeof config.label !== "string") throw new require_layout_configuration_error.LayoutConfigurationError(`[createResourceLinks]: "label" must be a string for the ${resource} resource. Received ${typeof config.label}`);
19
20
  const linkConfig = config;
20
- if ("href" in linkConfig && linkConfig.href !== void 0 && typeof linkConfig.href !== "string" && typeof linkConfig.href !== "function") throw new Error(`[createResourceLinks]: "href" must be a string or function for the ${resource} resource. Received ${typeof linkConfig.href}`);
21
- if ("hash" in linkConfig && linkConfig.hash !== void 0 && typeof linkConfig.hash !== "string" && typeof linkConfig.hash !== "function") throw new Error(`[createResourceLinks]: "hash" must be a string or function for the ${resource} resource. Received ${typeof linkConfig.hash}`);
21
+ if ("href" in linkConfig && linkConfig.href !== void 0 && typeof linkConfig.href !== "string" && typeof linkConfig.href !== "function") throw new require_layout_configuration_error.LayoutConfigurationError(`[createResourceLinks]: "href" must be a string or function for the ${resource} resource. Received ${typeof linkConfig.href}`);
22
+ if ("hash" in linkConfig && linkConfig.hash !== void 0 && typeof linkConfig.hash !== "string" && typeof linkConfig.hash !== "function") throw new require_layout_configuration_error.LayoutConfigurationError(`[createResourceLinks]: "hash" must be a string or function for the ${resource} resource. Received ${typeof linkConfig.hash}`);
22
23
  const givenHref = resolveResourceLinkHref(linkConfig.href, resource, "/");
23
24
  const givenHash = resolveResourceLinkHref(linkConfig.hash, resource, resource);
25
+ const href = {
26
+ given: givenHref,
27
+ full: createFullResourceLinkHref(givenHref, givenHash),
28
+ ...linkConfig.hash !== void 0 && !givenHref.includes("#") ? { hash: normalizeResourceLinkHash(givenHash) } : {}
29
+ };
24
30
  return {
25
- href: {
26
- given: givenHref,
27
- full: createFullResourceLinkHref(givenHref, givenHash),
28
- ...linkConfig.hash !== void 0 && !givenHref.includes("#") ? { hash: normalizeResourceLinkHash(givenHash) } : {}
29
- },
31
+ ...linkConfig.activeIcon !== void 0 ? { activeIcon: linkConfig.activeIcon } : {},
32
+ href,
30
33
  label: config.label,
31
34
  resource,
32
35
  icon: "icon" in config ? config.icon : null
@@ -52,31 +55,48 @@ function createResourceLinkGroupId(group, index) {
52
55
  if (typeof group.id === "string" && group.id.length > 0) return group.id;
53
56
  return `group-${index}-${typeof group.label === "string" ? group.label : ""}-${Object.keys(group.links).join(",")}`;
54
57
  }
55
- function createResourceLinksWithGroups(groups) {
56
- if (!Array.isArray(groups)) throw new Error("[createResourceLinks.withGroups]: \"groups\" must be an array.");
57
- return groups.map((group, index) => {
58
- if (!group) throw new Error(`[createResourceLinks.withGroups]: group at index ${index} is required.`);
59
- if (typeof group !== "object") throw new Error(`[createResourceLinks.withGroups]: group at index ${index} must be an object. Received ${typeof group}`);
60
- if ("label" in group && group.label !== void 0 && typeof group.label !== "string") throw new Error(`[createResourceLinks.withGroups]: "label" must be a string for group at index ${index}. Received ${typeof group.label}`);
61
- if ("id" in group && group.id !== void 0 && typeof group.id !== "string") throw new Error(`[createResourceLinks.withGroups]: "id" must be a string for group at index ${index}. Received ${typeof group.id}`);
62
- if (!("links" in group)) throw new Error(`[createResourceLinks.withGroups]: "links" is required for group at index ${index}.`);
63
- if (typeof group.links !== "object" || group.links === null || Array.isArray(group.links)) throw new Error(`[createResourceLinks.withGroups]: "links" must be an object for group at index ${index}. Received ${Array.isArray(group.links) ? "array" : typeof group.links}`);
58
+ function createResourceMapFn(resources) {
59
+ const resourceKeys = new Set(resources.map((resource) => typeof resource === "string" ? resource : resource.value));
60
+ 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}".`);
62
+ return map;
63
+ };
64
+ }
65
+ function createResourceLinksWithGroups(createResourceMap, groups) {
66
+ if (!Array.isArray(groups)) throw new require_layout_configuration_error.LayoutConfigurationError("[createResourceLinks.withGroups]: \"groups\" must be an array.");
67
+ const createdGroups = groups.map((group, index) => {
68
+ if (!group) throw new require_layout_configuration_error.LayoutConfigurationError(`[createResourceLinks.withGroups]: group at index ${index} is required.`);
69
+ if (typeof group !== "object") throw new require_layout_configuration_error.LayoutConfigurationError(`[createResourceLinks.withGroups]: group at index ${index} must be an object. Received ${typeof group}`);
70
+ if ("label" in group && group.label !== void 0 && typeof group.label !== "string") throw new require_layout_configuration_error.LayoutConfigurationError(`[createResourceLinks.withGroups]: "label" must be a string for group at index ${index}. Received ${typeof group.label}`);
71
+ if ("id" in group && group.id !== void 0 && typeof group.id !== "string") throw new require_layout_configuration_error.LayoutConfigurationError(`[createResourceLinks.withGroups]: "id" must be a string for group at index ${index}. Received ${typeof group.id}`);
72
+ if (!("links" in group)) throw new require_layout_configuration_error.LayoutConfigurationError(`[createResourceLinks.withGroups]: "links" is required for group at index ${index}.`);
73
+ if (typeof group.links !== "object" || group.links === null || Array.isArray(group.links)) throw new require_layout_configuration_error.LayoutConfigurationError(`[createResourceLinks.withGroups]: "links" must be an object for group at index ${index}. Received ${Array.isArray(group.links) ? "array" : typeof group.links}`);
64
74
  return {
75
+ ...group.activeIcon !== void 0 ? { activeIcon: group.activeIcon } : {},
65
76
  id: createResourceLinkGroupId(group, index),
66
77
  label: "label" in group ? group.label ?? null : null,
67
78
  icon: "icon" in group ? group.icon : null,
68
79
  links: createResourceLinksFromConfig(group.links)
69
80
  };
70
81
  });
82
+ Object.defineProperty(createdGroups, "createResourceMap", { value: createResourceMap });
83
+ Object.defineProperty(createdGroups, "findResource", { value(resource) {
84
+ return createdGroups.find((group) => group.links.some((link) => link.resource === resource));
85
+ } });
86
+ return createdGroups;
71
87
  }
72
88
  function createResourceLinksFn(_resources) {
89
+ const createResourceMap = createResourceMapFn(_resources);
73
90
  function createResourceLinks(config) {
74
91
  return createResourceLinksFromConfig(config);
75
92
  }
76
93
  function withGroups(groups) {
77
- return createResourceLinksWithGroups(groups);
94
+ return createResourceLinksWithGroups(createResourceMap, groups);
78
95
  }
79
- return Object.assign(createResourceLinks, { withGroups });
96
+ return Object.assign(createResourceLinks, {
97
+ createResourceMap,
98
+ withGroups
99
+ });
80
100
  }
81
101
 
82
102
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"create-resource-links.cjs","names":[],"sources":["../../src/create-config/create-resource-links.ts"],"sourcesContent":["import { ReactNode } from 'react';\nimport {\n type LayoutResourceKey,\n type ResourceDefinition,\n} from '../resource';\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 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 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 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 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 id: string;\n label: string | null;\n icon: ReactNode;\n links: Array<AnyCreatedResourceLink<Resources>>;\n};\n\nexport type CreateResourceLinksWithGroupsFn<\n Resources extends ReadonlyArray<ResourceDefinition>,\n> = <const Resource extends ResourceLinkConfigResource<Resources>>(\n groups: ReadonlyArray<CreateResourceLinkGroupInput<Resources, Resource>>,\n) => Array<CreateResourceLinksWithGroups<Resources>>;\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 Error(\n `[createResourceLinks]: \"config\" is required for the ${resource} resource.`,\n );\n }\n\n if (typeof config !== 'object') {\n throw new Error(\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 Error(\n `[createResourceLinks]: \"label\" is required for the ${resource} resource.`,\n );\n }\n\n if (typeof config.label !== 'string') {\n throw new Error(\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 Error(\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 Error(\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 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 createResourceLinksWithGroups<\n Resources extends ReadonlyArray<ResourceDefinition>,\n Resource extends ResourceLinkConfigResource<Resources>,\n>(\n groups: ReadonlyArray<CreateResourceLinkGroupInput<Resources, Resource>>,\n): Array<CreateResourceLinksWithGroups<Resources>> {\n if (!Array.isArray(groups)) {\n throw new Error(\n '[createResourceLinks.withGroups]: \"groups\" must be an array.',\n );\n }\n\n return groups.map((group, index) => {\n if (!group) {\n throw new Error(\n `[createResourceLinks.withGroups]: group at index ${index} is required.`,\n );\n }\n\n if (typeof group !== 'object') {\n throw new Error(\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 Error(\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 Error(\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 Error(\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 Error(\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 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\nexport function createResourceLinksFn<\n const Resources extends ReadonlyArray<ResourceDefinition>,\n>(_resources: Resources): CreateResourceLinksFn<Resources> {\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(groups);\n }\n\n return Object.assign(createResourceLinks, {\n withGroups,\n }) as CreateResourceLinksFn<Resources>;\n}\n"],"mappings":"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"}
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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"}
@@ -10,6 +10,10 @@ interface CreateResourceLinkOptions<Resources extends ReadonlyArray<ResourceDefi
10
10
  * The label of the link. This is the text that will be displayed in the link.
11
11
  */
12
12
  label: string;
13
+ /**
14
+ * An optional icon to render when the link is active.
15
+ */
16
+ activeIcon?: ReactNode;
13
17
  /**
14
18
  * An optional icon to register with the link. This is a headless approach, meaning you have full control
15
19
  * over the icon's rendering.
@@ -41,6 +45,7 @@ type CreatedResourceHref = {
41
45
  given: string;
42
46
  };
43
47
  type CreatedResourceLinkBase<Resource extends string> = {
48
+ activeIcon?: ReactNode;
44
49
  href: CreatedResourceHref;
45
50
  label: string;
46
51
  resource: Resource;
@@ -56,6 +61,7 @@ type CreatedResourceLink<Resources extends ReadonlyArray<ResourceDefinition>, Co
56
61
  } ? CreateResourceLinkWithHash<Resource, hash> : CreatedResourceLinkBase<Resource> : never }[keyof Config];
57
62
  interface CreateResourceLinksFn<Resources extends ReadonlyArray<ResourceDefinition>> {
58
63
  <const Resource extends ResourceLinkConfigResource<Resources>>(config: CreateResourceLinkConfig<Resources, Resource>): Array<CreatedResourceLink<Resources, CreateResourceLinkConfig<Resources, Resource>>>;
64
+ createResourceMap: CreateResourceMapFn<Resources>;
59
65
  withGroups: CreateResourceLinksWithGroupsFn<Resources>;
60
66
  }
61
67
  interface CreateResourceLinkGroupOptions<Resources extends ReadonlyArray<ResourceDefinition>, Links extends object = CreateResourceLinkConfig<Resources>> {
@@ -72,6 +78,10 @@ interface CreateResourceLinkGroupOptions<Resources extends ReadonlyArray<Resourc
72
78
  * The label of the group. This is the text that will be displayed for the group.
73
79
  */
74
80
  label?: string;
81
+ /**
82
+ * An optional icon to render when the group is active.
83
+ */
84
+ activeIcon?: ReactNode;
75
85
  /**
76
86
  * An optional icon to register with the group. This is a headless approach, meaning you have full control
77
87
  * over the icon's rendering.
@@ -85,12 +95,25 @@ interface CreateResourceLinkGroupOptions<Resources extends ReadonlyArray<Resourc
85
95
  type CreateResourceLinkGroupInput<Resources extends ReadonlyArray<ResourceDefinition>, Resource extends ResourceLinkConfigResource<Resources> = ResourceLinkConfigResource<Resources>> = CreateResourceLinkGroupOptions<Resources, CreateResourceLinkConfig<Resources, Resource>>;
86
96
  type AnyCreatedResourceLink<Resources extends ReadonlyArray<ResourceDefinition>> = { [Resource in LayoutResourceKey<Resources>]: CreatedResourceLinkBase<Resource> | CreateResourceLinkWithHash<Resource, string> }[LayoutResourceKey<Resources>];
87
97
  type CreateResourceLinksWithGroups<Resources extends ReadonlyArray<ResourceDefinition>> = {
98
+ activeIcon?: ReactNode;
88
99
  id: string;
89
100
  label: string | null;
90
101
  icon: ReactNode;
91
102
  links: Array<AnyCreatedResourceLink<Resources>>;
92
103
  };
93
- type CreateResourceLinksWithGroupsFn<Resources extends ReadonlyArray<ResourceDefinition>> = <const Resource extends ResourceLinkConfigResource<Resources>>(groups: ReadonlyArray<CreateResourceLinkGroupInput<Resources, Resource>>) => Array<CreateResourceLinksWithGroups<Resources>>;
104
+ type CreateResourceMap<Resources extends ReadonlyArray<ResourceDefinition>, Value> = Partial<Record<LayoutResourceKey<Resources>, Value>>;
105
+ interface CreateResourceMapFn<Resources extends ReadonlyArray<ResourceDefinition>> {
106
+ <const Map extends CreateResourceMap<Resources, unknown>>(map: Map): Map;
107
+ }
108
+ 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>;
111
+ }
112
+ type CreateResourceLinksWithGroupsResult<Resources extends ReadonlyArray<ResourceDefinition>, Resource extends ResourceLinkConfigResource<Resources> = ResourceLinkConfigResource<Resources>> = Array<CreateResourceLinksWithGroups<Resources>> & {
113
+ createResourceMap: CreateResourceMapFn<Resources>;
114
+ findResource<const RequestedResource extends LayoutResourceKey<Resources> | Resource>(resource: RequestedResource): FindResourceGroupResult<Resources, Resource, RequestedResource>;
115
+ };
116
+ type FindResourceGroupResult<Resources extends ReadonlyArray<ResourceDefinition>, IncludedResource extends string, RequestedResource extends string> = RequestedResource extends unknown ? RequestedResource extends IncludedResource ? CreateResourceLinksWithGroups<Resources> : undefined : never;
94
117
  //#endregion
95
- export { CreateResourceLinkConfig, CreateResourceLinkGroupInput, CreateResourceLinkGroupOptions, CreateResourceLinkOptions, CreateResourceLinksFn, CreateResourceLinksWithGroups, CreateResourceLinksWithGroupsFn, CreatedResourceHref, CreatedResourceLink, CreatedResourceLinkBase, InferHashFromResourceLinkHref, ResourceAnchorLinkFn, ResourceLinkHref };
118
+ export { CreateResourceLinkConfig, CreateResourceLinkGroupInput, CreateResourceLinkGroupOptions, CreateResourceLinkOptions, CreateResourceLinksFn, CreateResourceLinksWithGroups, CreateResourceLinksWithGroupsFn, CreateResourceLinksWithGroupsResult, CreateResourceMap, CreateResourceMapFn, CreatedResourceHref, CreatedResourceLink, CreatedResourceLinkBase, FindResourceGroupResult, InferHashFromResourceLinkHref, ResourceAnchorLinkFn, ResourceLinkHref };
96
119
  //# 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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"}
@@ -10,6 +10,10 @@ interface CreateResourceLinkOptions<Resources extends ReadonlyArray<ResourceDefi
10
10
  * The label of the link. This is the text that will be displayed in the link.
11
11
  */
12
12
  label: string;
13
+ /**
14
+ * An optional icon to render when the link is active.
15
+ */
16
+ activeIcon?: ReactNode;
13
17
  /**
14
18
  * An optional icon to register with the link. This is a headless approach, meaning you have full control
15
19
  * over the icon's rendering.
@@ -41,6 +45,7 @@ type CreatedResourceHref = {
41
45
  given: string;
42
46
  };
43
47
  type CreatedResourceLinkBase<Resource extends string> = {
48
+ activeIcon?: ReactNode;
44
49
  href: CreatedResourceHref;
45
50
  label: string;
46
51
  resource: Resource;
@@ -56,6 +61,7 @@ type CreatedResourceLink<Resources extends ReadonlyArray<ResourceDefinition>, Co
56
61
  } ? CreateResourceLinkWithHash<Resource, hash> : CreatedResourceLinkBase<Resource> : never }[keyof Config];
57
62
  interface CreateResourceLinksFn<Resources extends ReadonlyArray<ResourceDefinition>> {
58
63
  <const Resource extends ResourceLinkConfigResource<Resources>>(config: CreateResourceLinkConfig<Resources, Resource>): Array<CreatedResourceLink<Resources, CreateResourceLinkConfig<Resources, Resource>>>;
64
+ createResourceMap: CreateResourceMapFn<Resources>;
59
65
  withGroups: CreateResourceLinksWithGroupsFn<Resources>;
60
66
  }
61
67
  interface CreateResourceLinkGroupOptions<Resources extends ReadonlyArray<ResourceDefinition>, Links extends object = CreateResourceLinkConfig<Resources>> {
@@ -72,6 +78,10 @@ interface CreateResourceLinkGroupOptions<Resources extends ReadonlyArray<Resourc
72
78
  * The label of the group. This is the text that will be displayed for the group.
73
79
  */
74
80
  label?: string;
81
+ /**
82
+ * An optional icon to render when the group is active.
83
+ */
84
+ activeIcon?: ReactNode;
75
85
  /**
76
86
  * An optional icon to register with the group. This is a headless approach, meaning you have full control
77
87
  * over the icon's rendering.
@@ -85,12 +95,25 @@ interface CreateResourceLinkGroupOptions<Resources extends ReadonlyArray<Resourc
85
95
  type CreateResourceLinkGroupInput<Resources extends ReadonlyArray<ResourceDefinition>, Resource extends ResourceLinkConfigResource<Resources> = ResourceLinkConfigResource<Resources>> = CreateResourceLinkGroupOptions<Resources, CreateResourceLinkConfig<Resources, Resource>>;
86
96
  type AnyCreatedResourceLink<Resources extends ReadonlyArray<ResourceDefinition>> = { [Resource in LayoutResourceKey<Resources>]: CreatedResourceLinkBase<Resource> | CreateResourceLinkWithHash<Resource, string> }[LayoutResourceKey<Resources>];
87
97
  type CreateResourceLinksWithGroups<Resources extends ReadonlyArray<ResourceDefinition>> = {
98
+ activeIcon?: ReactNode;
88
99
  id: string;
89
100
  label: string | null;
90
101
  icon: ReactNode;
91
102
  links: Array<AnyCreatedResourceLink<Resources>>;
92
103
  };
93
- type CreateResourceLinksWithGroupsFn<Resources extends ReadonlyArray<ResourceDefinition>> = <const Resource extends ResourceLinkConfigResource<Resources>>(groups: ReadonlyArray<CreateResourceLinkGroupInput<Resources, Resource>>) => Array<CreateResourceLinksWithGroups<Resources>>;
104
+ type CreateResourceMap<Resources extends ReadonlyArray<ResourceDefinition>, Value> = Partial<Record<LayoutResourceKey<Resources>, Value>>;
105
+ interface CreateResourceMapFn<Resources extends ReadonlyArray<ResourceDefinition>> {
106
+ <const Map extends CreateResourceMap<Resources, unknown>>(map: Map): Map;
107
+ }
108
+ 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>;
111
+ }
112
+ type CreateResourceLinksWithGroupsResult<Resources extends ReadonlyArray<ResourceDefinition>, Resource extends ResourceLinkConfigResource<Resources> = ResourceLinkConfigResource<Resources>> = Array<CreateResourceLinksWithGroups<Resources>> & {
113
+ createResourceMap: CreateResourceMapFn<Resources>;
114
+ findResource<const RequestedResource extends LayoutResourceKey<Resources> | Resource>(resource: RequestedResource): FindResourceGroupResult<Resources, Resource, RequestedResource>;
115
+ };
116
+ type FindResourceGroupResult<Resources extends ReadonlyArray<ResourceDefinition>, IncludedResource extends string, RequestedResource extends string> = RequestedResource extends unknown ? RequestedResource extends IncludedResource ? CreateResourceLinksWithGroups<Resources> : undefined : never;
94
117
  //#endregion
95
- export { CreateResourceLinkConfig, CreateResourceLinkGroupInput, CreateResourceLinkGroupOptions, CreateResourceLinkOptions, CreateResourceLinksFn, CreateResourceLinksWithGroups, CreateResourceLinksWithGroupsFn, CreatedResourceHref, CreatedResourceLink, CreatedResourceLinkBase, InferHashFromResourceLinkHref, ResourceAnchorLinkFn, ResourceLinkHref };
118
+ export { CreateResourceLinkConfig, CreateResourceLinkGroupInput, CreateResourceLinkGroupOptions, CreateResourceLinkOptions, CreateResourceLinksFn, CreateResourceLinksWithGroups, CreateResourceLinksWithGroupsFn, CreateResourceLinksWithGroupsResult, CreateResourceMap, CreateResourceMapFn, CreatedResourceHref, CreatedResourceLink, CreatedResourceLinkBase, FindResourceGroupResult, InferHashFromResourceLinkHref, ResourceAnchorLinkFn, ResourceLinkHref };
96
119
  //# 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,3 +1,5 @@
1
+ import { LayoutConfigurationError } from "../errors/layout-configuration-error.mjs";
2
+
1
3
  //#region src/create-config/create-resource-links.ts
2
4
  function resolveResourceLinkHref(value, resource, fallback) {
3
5
  return typeof value === "function" ? value(resource) : value ?? fallback;
@@ -11,21 +13,23 @@ function createFullResourceLinkHref(href, hash) {
11
13
  }
12
14
  function createResourceLinksFromConfig(config) {
13
15
  return Object.entries(config).map(([resource, config]) => {
14
- if (!config) throw new Error(`[createResourceLinks]: "config" is required for the ${resource} resource.`);
15
- if (typeof config !== "object") throw new Error(`[createResourceLinks]: "config" must be an object for the ${resource} resource. Received ${typeof config}`);
16
- if (!("label" in config)) throw new Error(`[createResourceLinks]: "label" is required for the ${resource} resource.`);
17
- if (typeof config.label !== "string") throw new Error(`[createResourceLinks]: "label" must be a string for the ${resource} resource. Received ${typeof config.label}`);
16
+ if (!config) throw new LayoutConfigurationError(`[createResourceLinks]: "config" is required for the ${resource} resource.`);
17
+ if (typeof config !== "object") throw new LayoutConfigurationError(`[createResourceLinks]: "config" must be an object for the ${resource} resource. Received ${typeof config}`);
18
+ if (!("label" in config)) throw new LayoutConfigurationError(`[createResourceLinks]: "label" is required for the ${resource} resource.`);
19
+ if (typeof config.label !== "string") throw new LayoutConfigurationError(`[createResourceLinks]: "label" must be a string for the ${resource} resource. Received ${typeof config.label}`);
18
20
  const linkConfig = config;
19
- if ("href" in linkConfig && linkConfig.href !== void 0 && typeof linkConfig.href !== "string" && typeof linkConfig.href !== "function") throw new Error(`[createResourceLinks]: "href" must be a string or function for the ${resource} resource. Received ${typeof linkConfig.href}`);
20
- if ("hash" in linkConfig && linkConfig.hash !== void 0 && typeof linkConfig.hash !== "string" && typeof linkConfig.hash !== "function") throw new Error(`[createResourceLinks]: "hash" must be a string or function for the ${resource} resource. Received ${typeof linkConfig.hash}`);
21
+ if ("href" in linkConfig && linkConfig.href !== void 0 && typeof linkConfig.href !== "string" && typeof linkConfig.href !== "function") throw new LayoutConfigurationError(`[createResourceLinks]: "href" must be a string or function for the ${resource} resource. Received ${typeof linkConfig.href}`);
22
+ if ("hash" in linkConfig && linkConfig.hash !== void 0 && typeof linkConfig.hash !== "string" && typeof linkConfig.hash !== "function") throw new LayoutConfigurationError(`[createResourceLinks]: "hash" must be a string or function for the ${resource} resource. Received ${typeof linkConfig.hash}`);
21
23
  const givenHref = resolveResourceLinkHref(linkConfig.href, resource, "/");
22
24
  const givenHash = resolveResourceLinkHref(linkConfig.hash, resource, resource);
25
+ const href = {
26
+ given: givenHref,
27
+ full: createFullResourceLinkHref(givenHref, givenHash),
28
+ ...linkConfig.hash !== void 0 && !givenHref.includes("#") ? { hash: normalizeResourceLinkHash(givenHash) } : {}
29
+ };
23
30
  return {
24
- href: {
25
- given: givenHref,
26
- full: createFullResourceLinkHref(givenHref, givenHash),
27
- ...linkConfig.hash !== void 0 && !givenHref.includes("#") ? { hash: normalizeResourceLinkHash(givenHash) } : {}
28
- },
31
+ ...linkConfig.activeIcon !== void 0 ? { activeIcon: linkConfig.activeIcon } : {},
32
+ href,
29
33
  label: config.label,
30
34
  resource,
31
35
  icon: "icon" in config ? config.icon : null
@@ -51,31 +55,48 @@ function createResourceLinkGroupId(group, index) {
51
55
  if (typeof group.id === "string" && group.id.length > 0) return group.id;
52
56
  return `group-${index}-${typeof group.label === "string" ? group.label : ""}-${Object.keys(group.links).join(",")}`;
53
57
  }
54
- function createResourceLinksWithGroups(groups) {
55
- if (!Array.isArray(groups)) throw new Error("[createResourceLinks.withGroups]: \"groups\" must be an array.");
56
- return groups.map((group, index) => {
57
- if (!group) throw new Error(`[createResourceLinks.withGroups]: group at index ${index} is required.`);
58
- if (typeof group !== "object") throw new Error(`[createResourceLinks.withGroups]: group at index ${index} must be an object. Received ${typeof group}`);
59
- if ("label" in group && group.label !== void 0 && typeof group.label !== "string") throw new Error(`[createResourceLinks.withGroups]: "label" must be a string for group at index ${index}. Received ${typeof group.label}`);
60
- if ("id" in group && group.id !== void 0 && typeof group.id !== "string") throw new Error(`[createResourceLinks.withGroups]: "id" must be a string for group at index ${index}. Received ${typeof group.id}`);
61
- if (!("links" in group)) throw new Error(`[createResourceLinks.withGroups]: "links" is required for group at index ${index}.`);
62
- if (typeof group.links !== "object" || group.links === null || Array.isArray(group.links)) throw new Error(`[createResourceLinks.withGroups]: "links" must be an object for group at index ${index}. Received ${Array.isArray(group.links) ? "array" : typeof group.links}`);
58
+ function createResourceMapFn(resources) {
59
+ const resourceKeys = new Set(resources.map((resource) => typeof resource === "string" ? resource : resource.value));
60
+ return function createResourceMap(map) {
61
+ for (const resource of Object.keys(map)) if (!resourceKeys.has(resource)) throw new LayoutConfigurationError(`[createResourceLinks.createResourceMap]: unknown resource "${resource}".`);
62
+ return map;
63
+ };
64
+ }
65
+ function createResourceLinksWithGroups(createResourceMap, groups) {
66
+ if (!Array.isArray(groups)) throw new LayoutConfigurationError("[createResourceLinks.withGroups]: \"groups\" must be an array.");
67
+ const createdGroups = groups.map((group, index) => {
68
+ if (!group) throw new LayoutConfigurationError(`[createResourceLinks.withGroups]: group at index ${index} is required.`);
69
+ if (typeof group !== "object") throw new LayoutConfigurationError(`[createResourceLinks.withGroups]: group at index ${index} must be an object. Received ${typeof group}`);
70
+ if ("label" in group && group.label !== void 0 && typeof group.label !== "string") throw new LayoutConfigurationError(`[createResourceLinks.withGroups]: "label" must be a string for group at index ${index}. Received ${typeof group.label}`);
71
+ if ("id" in group && group.id !== void 0 && typeof group.id !== "string") throw new LayoutConfigurationError(`[createResourceLinks.withGroups]: "id" must be a string for group at index ${index}. Received ${typeof group.id}`);
72
+ if (!("links" in group)) throw new LayoutConfigurationError(`[createResourceLinks.withGroups]: "links" is required for group at index ${index}.`);
73
+ if (typeof group.links !== "object" || group.links === null || Array.isArray(group.links)) throw new LayoutConfigurationError(`[createResourceLinks.withGroups]: "links" must be an object for group at index ${index}. Received ${Array.isArray(group.links) ? "array" : typeof group.links}`);
63
74
  return {
75
+ ...group.activeIcon !== void 0 ? { activeIcon: group.activeIcon } : {},
64
76
  id: createResourceLinkGroupId(group, index),
65
77
  label: "label" in group ? group.label ?? null : null,
66
78
  icon: "icon" in group ? group.icon : null,
67
79
  links: createResourceLinksFromConfig(group.links)
68
80
  };
69
81
  });
82
+ Object.defineProperty(createdGroups, "createResourceMap", { value: createResourceMap });
83
+ Object.defineProperty(createdGroups, "findResource", { value(resource) {
84
+ return createdGroups.find((group) => group.links.some((link) => link.resource === resource));
85
+ } });
86
+ return createdGroups;
70
87
  }
71
88
  function createResourceLinksFn(_resources) {
89
+ const createResourceMap = createResourceMapFn(_resources);
72
90
  function createResourceLinks(config) {
73
91
  return createResourceLinksFromConfig(config);
74
92
  }
75
93
  function withGroups(groups) {
76
- return createResourceLinksWithGroups(groups);
94
+ return createResourceLinksWithGroups(createResourceMap, groups);
77
95
  }
78
- return Object.assign(createResourceLinks, { withGroups });
96
+ return Object.assign(createResourceLinks, {
97
+ createResourceMap,
98
+ withGroups
99
+ });
79
100
  }
80
101
 
81
102
  //#endregion
@@ -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';\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 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 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 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 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 id: string;\n label: string | null;\n icon: ReactNode;\n links: Array<AnyCreatedResourceLink<Resources>>;\n};\n\nexport type CreateResourceLinksWithGroupsFn<\n Resources extends ReadonlyArray<ResourceDefinition>,\n> = <const Resource extends ResourceLinkConfigResource<Resources>>(\n groups: ReadonlyArray<CreateResourceLinkGroupInput<Resources, Resource>>,\n) => Array<CreateResourceLinksWithGroups<Resources>>;\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 Error(\n `[createResourceLinks]: \"config\" is required for the ${resource} resource.`,\n );\n }\n\n if (typeof config !== 'object') {\n throw new Error(\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 Error(\n `[createResourceLinks]: \"label\" is required for the ${resource} resource.`,\n );\n }\n\n if (typeof config.label !== 'string') {\n throw new Error(\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 Error(\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 Error(\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 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 createResourceLinksWithGroups<\n Resources extends ReadonlyArray<ResourceDefinition>,\n Resource extends ResourceLinkConfigResource<Resources>,\n>(\n groups: ReadonlyArray<CreateResourceLinkGroupInput<Resources, Resource>>,\n): Array<CreateResourceLinksWithGroups<Resources>> {\n if (!Array.isArray(groups)) {\n throw new Error(\n '[createResourceLinks.withGroups]: \"groups\" must be an array.',\n );\n }\n\n return groups.map((group, index) => {\n if (!group) {\n throw new Error(\n `[createResourceLinks.withGroups]: group at index ${index} is required.`,\n );\n }\n\n if (typeof group !== 'object') {\n throw new Error(\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 Error(\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 Error(\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 Error(\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 Error(\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 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\nexport function createResourceLinksFn<\n const Resources extends ReadonlyArray<ResourceDefinition>,\n>(_resources: Resources): CreateResourceLinksFn<Resources> {\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(groups);\n }\n\n return Object.assign(createResourceLinks, {\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 { 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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"}