@jfdevelops/react-layout 0.24.3 → 1.0.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 (77) hide show
  1. package/dist/create-config/create-defined-resource-layout.cjs +72 -0
  2. package/dist/create-config/create-defined-resource-layout.cjs.map +1 -0
  3. package/dist/create-config/create-defined-resource-layout.d.cts +88 -0
  4. package/dist/create-config/create-defined-resource-layout.d.cts.map +1 -0
  5. package/dist/create-config/create-defined-resource-layout.d.mts +88 -0
  6. package/dist/create-config/create-defined-resource-layout.d.mts.map +1 -0
  7. package/dist/create-config/create-defined-resource-layout.mjs +72 -0
  8. package/dist/create-config/create-defined-resource-layout.mjs.map +1 -0
  9. package/dist/create-config/create-resource-links.cjs.map +1 -1
  10. package/dist/create-config/create-resource-links.d.cts +1 -1
  11. package/dist/create-config/create-resource-links.d.cts.map +1 -1
  12. package/dist/create-config/create-resource-links.d.mts +1 -1
  13. package/dist/create-config/create-resource-links.d.mts.map +1 -1
  14. package/dist/create-config/create-resource-links.mjs.map +1 -1
  15. package/dist/create-config/define-layout.cjs +199 -9
  16. package/dist/create-config/define-layout.cjs.map +1 -1
  17. package/dist/create-config/define-layout.d.cts +172 -29
  18. package/dist/create-config/define-layout.d.cts.map +1 -1
  19. package/dist/create-config/define-layout.d.mts +172 -29
  20. package/dist/create-config/define-layout.d.mts.map +1 -1
  21. package/dist/create-config/define-layout.mjs +202 -13
  22. package/dist/create-config/define-layout.mjs.map +1 -1
  23. package/dist/create-config/for-resources/create-for-resources.cjs +7 -0
  24. package/dist/create-config/for-resources/create-for-resources.cjs.map +1 -1
  25. package/dist/create-config/for-resources/create-for-resources.mjs +7 -0
  26. package/dist/create-config/for-resources/create-for-resources.mjs.map +1 -1
  27. package/dist/create-config/get-component.cjs +22 -1
  28. package/dist/create-config/get-component.cjs.map +1 -1
  29. package/dist/create-config/get-component.d.cts +25 -3
  30. package/dist/create-config/get-component.d.cts.map +1 -1
  31. package/dist/create-config/get-component.d.mts +25 -3
  32. package/dist/create-config/get-component.d.mts.map +1 -1
  33. package/dist/create-config/get-component.mjs +22 -1
  34. package/dist/create-config/get-component.mjs.map +1 -1
  35. package/dist/create-config/index.d.cts +5 -2
  36. package/dist/create-config/index.d.mts +5 -2
  37. package/dist/create-config/lazy-resource-layout.cjs +111 -0
  38. package/dist/create-config/lazy-resource-layout.cjs.map +1 -0
  39. package/dist/create-config/lazy-resource-layout.d.cts +51 -0
  40. package/dist/create-config/lazy-resource-layout.d.cts.map +1 -0
  41. package/dist/create-config/lazy-resource-layout.d.mts +51 -0
  42. package/dist/create-config/lazy-resource-layout.d.mts.map +1 -0
  43. package/dist/create-config/lazy-resource-layout.mjs +111 -0
  44. package/dist/create-config/lazy-resource-layout.mjs.map +1 -0
  45. package/dist/create-config/render-state.cjs +62 -0
  46. package/dist/create-config/render-state.cjs.map +1 -0
  47. package/dist/create-config/render-state.d.cts +121 -0
  48. package/dist/create-config/render-state.d.cts.map +1 -0
  49. package/dist/create-config/render-state.d.mts +121 -0
  50. package/dist/create-config/render-state.d.mts.map +1 -0
  51. package/dist/create-config/render-state.mjs +55 -0
  52. package/dist/create-config/render-state.mjs.map +1 -0
  53. package/dist/errors/resource-state-error.cjs +29 -0
  54. package/dist/errors/resource-state-error.cjs.map +1 -0
  55. package/dist/errors/resource-state-error.d.cts +19 -0
  56. package/dist/errors/resource-state-error.d.cts.map +1 -0
  57. package/dist/errors/resource-state-error.d.mts +19 -0
  58. package/dist/errors/resource-state-error.d.mts.map +1 -0
  59. package/dist/errors/resource-state-error.mjs +27 -0
  60. package/dist/errors/resource-state-error.mjs.map +1 -0
  61. package/dist/index.cjs +6 -0
  62. package/dist/index.d.cts +9 -5
  63. package/dist/index.d.mts +9 -5
  64. package/dist/index.mjs +4 -2
  65. package/dist/props.d.cts +6 -1
  66. package/dist/props.d.cts.map +1 -1
  67. package/dist/props.d.mts +6 -1
  68. package/dist/props.d.mts.map +1 -1
  69. package/dist/resource.cjs +17 -0
  70. package/dist/resource.cjs.map +1 -1
  71. package/dist/resource.d.cts +33 -1
  72. package/dist/resource.d.cts.map +1 -1
  73. package/dist/resource.d.mts +33 -1
  74. package/dist/resource.d.mts.map +1 -1
  75. package/dist/resource.mjs +17 -1
  76. package/dist/resource.mjs.map +1 -1
  77. package/package.json +3 -3
@@ -1,7 +1,8 @@
1
1
  import { InvalidConfigError } from "../errors/invalid-config.mjs";
2
2
  import { MissingResourceProviderError } from "../errors/missing-resource-provider.mjs";
3
3
  import { LayoutConfigurationError } from "../errors/layout-configuration-error.mjs";
4
- import { createIsValidResourceFn, normalizeResources, toResourceEnum } from "../resource.mjs";
4
+ import { ResourceModuleLoadError, ResourceRenderError } from "../errors/resource-state-error.mjs";
5
+ import { createIsValidResourceFn, normalizeResourceDefinitions, normalizeResources, toResourceEnum } from "../resource.mjs";
5
6
  import { functionalUpdate, pick } from "../utils.mjs";
6
7
  import { capitalize } from "../utils/capitalize.mjs";
7
8
  import { isResourceConfigComponentKey } from "./component-keys.mjs";
@@ -10,19 +11,48 @@ import { createResourceLinksFn } from "./create-resource-links.mjs";
10
11
  import { createForResource } from "./for-resource.mjs";
11
12
  import { createForResources } from "./for-resources/create-for-resources.mjs";
12
13
  import { ResourceLayoutShellPublisherContext, useResourceLayoutShellPublisher } from "./shell-context.mjs";
14
+ import { deferredRenderStateSymbol, definedResourceLayoutComponentSymbol, findDefinedRenderState, isDeferredRenderState, isLazyRenderState, lazyRenderStateSymbol } from "./render-state.mjs";
13
15
  import { LayoutComposablePresetProvider, collectComposablePresetEntries, makeComposable, resolveLayoutComposables } from "@jfdevelops/react-layout-composables";
14
- import { createContext, useCallback, useContext, useLayoutEffect, useMemo, useRef, useState } from "react";
15
- import { createPrimitivePropBuilder, createProp, resolvePropDefinitionValues, validateProps } from "@jfdevelops/react-layout-validator";
16
+ import { Component, Suspense, createContext, lazy, useCallback, useContext, useLayoutEffect, useMemo, useReducer, useRef, useState } from "react";
17
+ import { attachComponentPropDefinitions, createPrimitivePropBuilder, createProp, resolvePropDefinitionValues, validateProps } from "@jfdevelops/react-layout-validator";
16
18
  import { Fragment, jsx } from "react/jsx-runtime";
17
19
 
18
20
  //#region src/create-config/define-layout.tsx
21
+ const RenderStateRetryContext = createContext(void 0);
22
+ var RenderStateBoundary = class extends Component {
23
+ state = {};
24
+ static getDerivedStateFromError(error) {
25
+ return { error };
26
+ }
27
+ retry = () => {
28
+ this.props.onRetry?.();
29
+ this.setState({ error: void 0 });
30
+ };
31
+ render() {
32
+ if (this.state.error !== void 0) return this.props.fallback(this.state.error, this.retry);
33
+ return this.props.children;
34
+ }
35
+ };
36
+ function toResourceStateError(resource, state, error) {
37
+ return error instanceof ResourceModuleLoadError || error instanceof ResourceRenderError ? error : new ResourceRenderError(resource, state, error);
38
+ }
19
39
  function createResourceSelection(definitions) {
20
40
  const selection = (() => definitions);
21
41
  selection.isResource = createIsValidResourceFn(definitions);
22
42
  return selection;
23
43
  }
44
+ /**
45
+ * Creates the callable resource accessor shared by global and bound layout
46
+ * definitions.
47
+ *
48
+ * @param resources - Resource definitions to expose and normalize.
49
+ * @returns A callable normalized accessor with raw, selection, and guard APIs.
50
+ * @internal
51
+ */
24
52
  function createLayoutResourcesAccessor(resources) {
25
- const accessor = (() => resources);
53
+ const normalizedResources = normalizeResourceDefinitions(resources);
54
+ const accessor = ((select) => select ? select(normalizedResources) : normalizedResources);
55
+ accessor.raw = ((select) => select ? select(resources) : resources);
26
56
  accessor.isResource = createIsValidResourceFn(resources);
27
57
  accessor.omit = ((...keys) => createResourceSelection(resources.filter((resource) => !keys.includes(readResourceSlug(resource)))));
28
58
  accessor.pick = ((...keys) => createResourceSelection(resources.filter((resource) => keys.includes(readResourceSlug(resource)))));
@@ -88,6 +118,15 @@ function resolveLayoutOptionDefaults(defaults, options) {
88
118
  else resolved[key] = functionalUpdate(defaultValue, options[key]);
89
119
  return resolved;
90
120
  }
121
+ function getSynchronousRenderStateDefinition(input, fallbackProps) {
122
+ if (isLazyRenderState(input)) return;
123
+ if (isDeferredRenderState(input)) return input[deferredRenderStateSymbol];
124
+ if (typeof input === "function") return {
125
+ props: fallbackProps,
126
+ render: input
127
+ };
128
+ return typeof input === "object" && input !== null ? input : void 0;
129
+ }
91
130
  function readResourceSlug(resource) {
92
131
  return typeof resource === "string" ? resource : resource.value;
93
132
  }
@@ -129,7 +168,15 @@ function defineResourceLayoutImpl(options) {
129
168
  ...instancePropDefinitions,
130
169
  ...layoutOptionProps
131
170
  });
132
- const { composables, render, props: layoutProps } = layout;
171
+ const { composables, props: legacyLayoutProps } = layout;
172
+ const renderStates = typeof layout.render === "function" ? { default: layout.render } : layout.render;
173
+ const defaultRenderInput = renderStates.default;
174
+ const createdDefaultState = isDeferredRenderState(defaultRenderInput) || isLazyRenderState(defaultRenderInput);
175
+ const defaultRenderDefinition = getSynchronousRenderStateDefinition(defaultRenderInput, legacyLayoutProps) ?? { render: () => null };
176
+ if (typeof defaultRenderDefinition !== "object" || defaultRenderDefinition === null || !("render" in defaultRenderDefinition) || typeof defaultRenderDefinition.render !== "function") throw new LayoutConfigurationError("The \"default\" render state must define a render function");
177
+ const defaultState = defaultRenderDefinition;
178
+ const render = defaultState.render;
179
+ const layoutProps = defaultState.props ?? legacyLayoutProps;
133
180
  const customLayoutProps = layoutProps?.custom;
134
181
  const includeLayoutProps = layoutProps?.include;
135
182
  const layoutOptionValues = layoutOptionProps;
@@ -143,7 +190,11 @@ function defineResourceLayoutImpl(options) {
143
190
  layoutName: name,
144
191
  resource: layoutContext.resource
145
192
  };
146
- const resolvedComposables = composables ? resolveLayoutComposables(composables, layoutContext) : void 0;
193
+ const defaultComposables = defaultState.composables ? (create) => ({
194
+ ...composables?.(create) ?? {},
195
+ ...defaultState.composables?.(create)
196
+ }) : composables;
197
+ const resolvedComposables = defaultComposables ? resolveLayoutComposables(defaultComposables, layoutContext) : void 0;
147
198
  const mergedResolvedInProps = { ...resolvedInProps };
148
199
  for (const { props: presetPropDefinitions } of collectComposablePresetEntries(resolvedComposables)) Object.assign(mergedResolvedInProps, presetPropDefinitions);
149
200
  const composablePresetProps = Object.fromEntries(collectComposablePresetEntries(resolvedComposables).map(({ key, props: presetPropDefinitions }) => {
@@ -160,6 +211,12 @@ function defineResourceLayoutImpl(options) {
160
211
  name: layoutContext.name,
161
212
  inProps: splitInProps
162
213
  };
214
+ const stateComponents = {};
215
+ let CreatedDefaultStateComponent;
216
+ function RenderDefaultState({ renderProps }) {
217
+ if (CreatedDefaultStateComponent) return /* @__PURE__ */ jsx(CreatedDefaultStateComponent, { ...renderProps });
218
+ return render(renderProps, mergedRenderContext);
219
+ }
163
220
  function Component(props) {
164
221
  const publishResource = useResourceLayoutShellPublisher();
165
222
  useLayoutEffect(() => {
@@ -193,26 +250,153 @@ function defineResourceLayoutImpl(options) {
193
250
  ...validatedProps,
194
251
  ...layoutRenderIncludedProps
195
252
  };
196
- return /* @__PURE__ */ jsx(ResourceProvider, {
253
+ const rendered = /* @__PURE__ */ jsx(ResourceProvider, {
197
254
  resource: layoutContext.resource,
198
255
  children: /* @__PURE__ */ jsx(LayoutComposablePresetProvider, {
199
256
  value: composablePresetProps,
200
- children: render(layoutRenderProps, mergedRenderContext)
257
+ children: /* @__PURE__ */ jsx(RenderDefaultState, { renderProps: layoutRenderProps })
201
258
  })
202
259
  });
260
+ const ErrorComponent = stateComponents.Error;
261
+ return ErrorComponent ? /* @__PURE__ */ jsx(RenderStateBoundary, {
262
+ fallback: (error, retry) => /* @__PURE__ */ jsx(RenderStateRetryContext.Provider, {
263
+ value: retry,
264
+ children: /* @__PURE__ */ jsx(ErrorComponent, {
265
+ error: toResourceStateError(String(layoutContext.resource), "default", error),
266
+ retry
267
+ })
268
+ }),
269
+ children: rendered
270
+ }) : rendered;
203
271
  }
204
272
  function createComposition(compositionOptions) {
205
273
  if (!compositionOptions.components) return {};
206
274
  return { makeComposable: createComposableLayout(compositionOptions) };
207
275
  }
208
- return Object.assign(Component, {
276
+ function createStateComponent(stateName, input) {
277
+ if (isLazyRenderState(input)) {
278
+ const loadState = input[lazyRenderStateSymbol];
279
+ function createLazyState() {
280
+ return lazy(async () => {
281
+ const stateDefinition = findDefinedRenderState(await loadState().catch((error) => {
282
+ throw new ResourceModuleLoadError(String(layoutContext.resource), error);
283
+ }));
284
+ if (stateDefinition.state !== void 0 && stateDefinition.state !== stateName) throw new LayoutConfigurationError(`Lazy render state "${stateDefinition.state}" cannot be used as "${stateName}"`);
285
+ return { default: createLoadedStateComponent(stateName, stateDefinition) };
286
+ });
287
+ }
288
+ function LazyStateComponent(props) {
289
+ const [, rerender] = useReducer((version) => version + 1, 0);
290
+ const LazyState = useRef(createLazyState());
291
+ const CurrentLazyState = LazyState.current;
292
+ const LoadingComponent = stateName === "Loading" ? void 0 : stateComponents.Loading;
293
+ const ErrorComponent = stateName === "Error" ? void 0 : stateComponents.Error;
294
+ function retry() {
295
+ LazyState.current = createLazyState();
296
+ rerender();
297
+ }
298
+ const rendered = /* @__PURE__ */ jsx(Suspense, {
299
+ fallback: LoadingComponent ? /* @__PURE__ */ jsx(LoadingComponent, {}) : null,
300
+ children: /* @__PURE__ */ jsx(CurrentLazyState, { ...props })
301
+ });
302
+ return ErrorComponent ? /* @__PURE__ */ jsx(RenderStateBoundary, {
303
+ fallback: (error, reset) => /* @__PURE__ */ jsx(RenderStateRetryContext.Provider, {
304
+ value: reset,
305
+ children: /* @__PURE__ */ jsx(ErrorComponent, {
306
+ error: toResourceStateError(String(layoutContext.resource), stateName, error),
307
+ retry: reset
308
+ })
309
+ }),
310
+ onRetry: retry,
311
+ children: rendered
312
+ }) : rendered;
313
+ }
314
+ return LazyStateComponent;
315
+ }
316
+ const definition = isDeferredRenderState(input) ? input[deferredRenderStateSymbol] : typeof input === "function" ? { render: input } : input;
317
+ if (typeof definition !== "object" || definition === null || !("render" in definition) || typeof definition.render !== "function") throw new LayoutConfigurationError(`Render state "${stateName}" must define a render function`);
318
+ const StateComponent = createLoadedStateComponent(stateName, definition);
319
+ if (!isDeferredRenderState(input)) return StateComponent;
320
+ const DeferredState = lazy(() => Promise.resolve({ default: StateComponent }));
321
+ function DeferredStateComponent(props) {
322
+ const LoadingComponent = stateName === "Loading" ? void 0 : stateComponents.Loading;
323
+ const ErrorComponent = stateName === "Error" ? void 0 : stateComponents.Error;
324
+ const rendered = /* @__PURE__ */ jsx(Suspense, {
325
+ fallback: LoadingComponent ? /* @__PURE__ */ jsx(LoadingComponent, {}) : null,
326
+ children: /* @__PURE__ */ jsx(DeferredState, { ...props })
327
+ });
328
+ return ErrorComponent ? /* @__PURE__ */ jsx(RenderStateBoundary, {
329
+ fallback: (error, retry) => /* @__PURE__ */ jsx(RenderStateRetryContext.Provider, {
330
+ value: retry,
331
+ children: /* @__PURE__ */ jsx(ErrorComponent, {
332
+ error: toResourceStateError(String(layoutContext.resource), stateName, error),
333
+ retry
334
+ })
335
+ }),
336
+ children: rendered
337
+ }) : rendered;
338
+ }
339
+ return DeferredStateComponent;
340
+ }
341
+ function createLoadedStateComponent(stateName, definition) {
342
+ const stateComposables = definition.composables ? (create) => ({
343
+ ...composables?.(create) ?? {},
344
+ ...definition.composables?.(create)
345
+ }) : composables;
346
+ const stateRender = stateName === "Error" ? (props, context) => /* @__PURE__ */ jsx(RenderStateRetryContext.Consumer, { children: (retry) => definition.render(props, {
347
+ ...context,
348
+ retry
349
+ }) }) : definition.render;
350
+ const StateComponent = defineResourceLayoutImpl({
351
+ resources,
352
+ options: inProps,
353
+ layout: {
354
+ composables: stateComposables,
355
+ props: definition.props,
356
+ render: stateRender
357
+ }
358
+ }).createResourceLayout(layoutOptions);
359
+ StateComponent.displayName = `${name}.${stateName}`;
360
+ return StateComponent;
361
+ }
362
+ if (createdDefaultState) CreatedDefaultStateComponent = createStateComponent("default", defaultRenderInput);
363
+ for (const [stateName, input] of Object.entries(renderStates)) {
364
+ if (stateName === "default") continue;
365
+ if ([
366
+ "__proto__",
367
+ "apply",
368
+ "arguments",
369
+ "bind",
370
+ "call",
371
+ "caller",
372
+ "displayName",
373
+ "length",
374
+ "makeComposable",
375
+ "name",
376
+ "props",
377
+ "prototype",
378
+ "resource"
379
+ ].includes(stateName)) throw new LayoutConfigurationError(`Render state name "${stateName}" is reserved`);
380
+ stateComponents[stateName] = createStateComponent(stateName, input);
381
+ }
382
+ const includedComponentDefinitions = Object.fromEntries(Object.keys(includeLayoutProps ?? {}).flatMap((key) => {
383
+ if (!allowsComponentPassthrough(includeLayoutProps?.[key]) || !(key in mergedResolvedInProps)) return [];
384
+ const definition = mergedResolvedInProps[key];
385
+ return [[key, isIncludedPropRequired(includeLayoutProps?.[key]) ? definition : definition.optional?.() ?? definition]];
386
+ }));
387
+ return attachComponentPropDefinitions(Object.assign(Component, {
209
388
  displayName: name,
210
389
  props: void 0,
211
390
  resource: void 0,
212
391
  ...createComposition({
213
392
  components: resolvedComposables,
214
393
  name
215
- })
394
+ }),
395
+ ...stateComponents,
396
+ [definedResourceLayoutComponentSymbol]: true
397
+ }), {
398
+ ...customLayoutProps,
399
+ ...includedComponentDefinitions
216
400
  });
217
401
  });
218
402
  const createTopLevelMakeComposable = () => {
@@ -228,7 +412,12 @@ function defineResourceLayoutImpl(options) {
228
412
  resource: resourcesEnum,
229
413
  name: createProp.string().literal(componentName)
230
414
  }) : inProps);
231
- const resolvedComposables = layout.composables ? resolveLayoutComposables(layout.composables, {
415
+ const defaultState = getSynchronousRenderStateDefinition((typeof layout.render === "function" ? { default: layout.render } : layout.render).default, layout.props);
416
+ const defaultComposables = defaultState?.composables ? (create) => ({
417
+ ...layout.composables?.(create) ?? {},
418
+ ...defaultState.composables?.(create)
419
+ }) : layout.composables;
420
+ const resolvedComposables = defaultComposables ? resolveLayoutComposables(defaultComposables, {
232
421
  resource,
233
422
  name: componentName,
234
423
  capitalize
@@ -236,7 +425,7 @@ function defineResourceLayoutImpl(options) {
236
425
  for (const { props: presetPropDefinitions } of collectComposablePresetEntries(resolvedComposables)) Object.assign(resolvedInProps, presetPropDefinitions);
237
426
  return {
238
427
  ...resolvedInProps,
239
- ...layout.props?.custom
428
+ ...(defaultState?.props ?? layout.props)?.custom
240
429
  };
241
430
  }
242
431
  const { createLayoutForResourceBuilder, forResource } = createForResource({
@@ -445,5 +634,5 @@ const defineResourceLayout = Object.assign(defineResourceLayoutImpl, {
445
634
  });
446
635
 
447
636
  //#endregion
448
- export { defineResourceLayout };
637
+ export { createLayoutResourcesAccessor, defineResourceLayout };
449
638
  //# sourceMappingURL=define-layout.mjs.map