@jfdevelops/react-layout 0.24.2 → 0.25.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 (67) hide show
  1. package/dist/create-config/create-defined-resource-layout.cjs +67 -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 +68 -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 +68 -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 +67 -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 +41 -28
  11. package/dist/create-config/create-resource-links.d.cts.map +1 -1
  12. package/dist/create-config/create-resource-links.d.mts +41 -28
  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 +187 -8
  16. package/dist/create-config/define-layout.cjs.map +1 -1
  17. package/dist/create-config/define-layout.d.cts +152 -21
  18. package/dist/create-config/define-layout.d.cts.map +1 -1
  19. package/dist/create-config/define-layout.d.mts +152 -21
  20. package/dist/create-config/define-layout.d.mts.map +1 -1
  21. package/dist/create-config/define-layout.mjs +189 -10
  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/index.d.cts +4 -1
  28. package/dist/create-config/index.d.mts +4 -1
  29. package/dist/create-config/lazy-resource-layout.cjs +111 -0
  30. package/dist/create-config/lazy-resource-layout.cjs.map +1 -0
  31. package/dist/create-config/lazy-resource-layout.d.cts +51 -0
  32. package/dist/create-config/lazy-resource-layout.d.cts.map +1 -0
  33. package/dist/create-config/lazy-resource-layout.d.mts +51 -0
  34. package/dist/create-config/lazy-resource-layout.d.mts.map +1 -0
  35. package/dist/create-config/lazy-resource-layout.mjs +111 -0
  36. package/dist/create-config/lazy-resource-layout.mjs.map +1 -0
  37. package/dist/create-config/render-state.cjs +75 -0
  38. package/dist/create-config/render-state.cjs.map +1 -0
  39. package/dist/create-config/render-state.d.cts +120 -0
  40. package/dist/create-config/render-state.d.cts.map +1 -0
  41. package/dist/create-config/render-state.d.mts +120 -0
  42. package/dist/create-config/render-state.d.mts.map +1 -0
  43. package/dist/create-config/render-state.mjs +68 -0
  44. package/dist/create-config/render-state.mjs.map +1 -0
  45. package/dist/errors/resource-state-error.cjs +29 -0
  46. package/dist/errors/resource-state-error.cjs.map +1 -0
  47. package/dist/errors/resource-state-error.d.cts +19 -0
  48. package/dist/errors/resource-state-error.d.cts.map +1 -0
  49. package/dist/errors/resource-state-error.d.mts +19 -0
  50. package/dist/errors/resource-state-error.d.mts.map +1 -0
  51. package/dist/errors/resource-state-error.mjs +27 -0
  52. package/dist/errors/resource-state-error.mjs.map +1 -0
  53. package/dist/index.cjs +5 -0
  54. package/dist/index.d.cts +8 -4
  55. package/dist/index.d.mts +8 -4
  56. package/dist/index.mjs +3 -1
  57. package/dist/props.d.cts +6 -1
  58. package/dist/props.d.cts.map +1 -1
  59. package/dist/props.d.mts +6 -1
  60. package/dist/props.d.mts.map +1 -1
  61. package/dist/resource.cjs.map +1 -1
  62. package/dist/resource.d.cts +7 -1
  63. package/dist/resource.d.cts.map +1 -1
  64. package/dist/resource.d.mts +7 -1
  65. package/dist/resource.d.mts.map +1 -1
  66. package/dist/resource.mjs.map +1 -1
  67. package/package.json +3 -3
@@ -1,6 +1,7 @@
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 { ResourceModuleLoadError, ResourceRenderError } from "../errors/resource-state-error.mjs";
4
5
  import { createIsValidResourceFn, normalizeResources, toResourceEnum } from "../resource.mjs";
5
6
  import { functionalUpdate, pick } from "../utils.mjs";
6
7
  import { capitalize } from "../utils/capitalize.mjs";
@@ -10,12 +11,31 @@ 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);
@@ -88,6 +108,15 @@ function resolveLayoutOptionDefaults(defaults, options) {
88
108
  else resolved[key] = functionalUpdate(defaultValue, options[key]);
89
109
  return resolved;
90
110
  }
111
+ function getSynchronousRenderStateDefinition(input, fallbackProps) {
112
+ if (isLazyRenderState(input)) return;
113
+ if (isDeferredRenderState(input)) return input[deferredRenderStateSymbol];
114
+ if (typeof input === "function") return {
115
+ props: fallbackProps,
116
+ render: input
117
+ };
118
+ return typeof input === "object" && input !== null ? input : void 0;
119
+ }
91
120
  function readResourceSlug(resource) {
92
121
  return typeof resource === "string" ? resource : resource.value;
93
122
  }
@@ -129,7 +158,15 @@ function defineResourceLayoutImpl(options) {
129
158
  ...instancePropDefinitions,
130
159
  ...layoutOptionProps
131
160
  });
132
- const { composables, render, props: layoutProps } = layout;
161
+ const { composables, props: legacyLayoutProps } = layout;
162
+ const renderStates = typeof layout.render === "function" ? { default: layout.render } : layout.render;
163
+ const defaultRenderInput = renderStates.default;
164
+ const createdDefaultState = isDeferredRenderState(defaultRenderInput) || isLazyRenderState(defaultRenderInput);
165
+ const defaultRenderDefinition = getSynchronousRenderStateDefinition(defaultRenderInput, legacyLayoutProps) ?? { render: () => null };
166
+ 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");
167
+ const defaultState = defaultRenderDefinition;
168
+ const render = defaultState.render;
169
+ const layoutProps = defaultState.props ?? legacyLayoutProps;
133
170
  const customLayoutProps = layoutProps?.custom;
134
171
  const includeLayoutProps = layoutProps?.include;
135
172
  const layoutOptionValues = layoutOptionProps;
@@ -143,7 +180,11 @@ function defineResourceLayoutImpl(options) {
143
180
  layoutName: name,
144
181
  resource: layoutContext.resource
145
182
  };
146
- const resolvedComposables = composables ? resolveLayoutComposables(composables, layoutContext) : void 0;
183
+ const defaultComposables = defaultState.composables ? (create) => ({
184
+ ...composables?.(create) ?? {},
185
+ ...defaultState.composables?.(create)
186
+ }) : composables;
187
+ const resolvedComposables = defaultComposables ? resolveLayoutComposables(defaultComposables, layoutContext) : void 0;
147
188
  const mergedResolvedInProps = { ...resolvedInProps };
148
189
  for (const { props: presetPropDefinitions } of collectComposablePresetEntries(resolvedComposables)) Object.assign(mergedResolvedInProps, presetPropDefinitions);
149
190
  const composablePresetProps = Object.fromEntries(collectComposablePresetEntries(resolvedComposables).map(({ key, props: presetPropDefinitions }) => {
@@ -160,6 +201,12 @@ function defineResourceLayoutImpl(options) {
160
201
  name: layoutContext.name,
161
202
  inProps: splitInProps
162
203
  };
204
+ const stateComponents = {};
205
+ let CreatedDefaultStateComponent;
206
+ function RenderDefaultState({ renderProps }) {
207
+ if (CreatedDefaultStateComponent) return /* @__PURE__ */ jsx(CreatedDefaultStateComponent, { ...renderProps });
208
+ return render(renderProps, mergedRenderContext);
209
+ }
163
210
  function Component(props) {
164
211
  const publishResource = useResourceLayoutShellPublisher();
165
212
  useLayoutEffect(() => {
@@ -193,26 +240,153 @@ function defineResourceLayoutImpl(options) {
193
240
  ...validatedProps,
194
241
  ...layoutRenderIncludedProps
195
242
  };
196
- return /* @__PURE__ */ jsx(ResourceProvider, {
243
+ const rendered = /* @__PURE__ */ jsx(ResourceProvider, {
197
244
  resource: layoutContext.resource,
198
245
  children: /* @__PURE__ */ jsx(LayoutComposablePresetProvider, {
199
246
  value: composablePresetProps,
200
- children: render(layoutRenderProps, mergedRenderContext)
247
+ children: /* @__PURE__ */ jsx(RenderDefaultState, { renderProps: layoutRenderProps })
201
248
  })
202
249
  });
250
+ const ErrorComponent = stateComponents.Error;
251
+ return ErrorComponent ? /* @__PURE__ */ jsx(RenderStateBoundary, {
252
+ fallback: (error, retry) => /* @__PURE__ */ jsx(RenderStateRetryContext.Provider, {
253
+ value: retry,
254
+ children: /* @__PURE__ */ jsx(ErrorComponent, {
255
+ error: toResourceStateError(String(layoutContext.resource), "default", error),
256
+ retry
257
+ })
258
+ }),
259
+ children: rendered
260
+ }) : rendered;
203
261
  }
204
262
  function createComposition(compositionOptions) {
205
263
  if (!compositionOptions.components) return {};
206
264
  return { makeComposable: createComposableLayout(compositionOptions) };
207
265
  }
208
- return Object.assign(Component, {
266
+ function createStateComponent(stateName, input) {
267
+ if (isLazyRenderState(input)) {
268
+ const loadState = input[lazyRenderStateSymbol];
269
+ function createLazyState() {
270
+ return lazy(async () => {
271
+ const stateDefinition = findDefinedRenderState(await loadState().catch((error) => {
272
+ throw new ResourceModuleLoadError(String(layoutContext.resource), error);
273
+ }));
274
+ if (stateDefinition.state !== void 0 && stateDefinition.state !== stateName) throw new LayoutConfigurationError(`Lazy render state "${stateDefinition.state}" cannot be used as "${stateName}"`);
275
+ return { default: createLoadedStateComponent(stateName, stateDefinition) };
276
+ });
277
+ }
278
+ function LazyStateComponent(props) {
279
+ const [, rerender] = useReducer((version) => version + 1, 0);
280
+ const LazyState = useRef(createLazyState());
281
+ const CurrentLazyState = LazyState.current;
282
+ const LoadingComponent = stateName === "Loading" ? void 0 : stateComponents.Loading;
283
+ const ErrorComponent = stateName === "Error" ? void 0 : stateComponents.Error;
284
+ function retry() {
285
+ LazyState.current = createLazyState();
286
+ rerender();
287
+ }
288
+ const rendered = /* @__PURE__ */ jsx(Suspense, {
289
+ fallback: LoadingComponent ? /* @__PURE__ */ jsx(LoadingComponent, {}) : null,
290
+ children: /* @__PURE__ */ jsx(CurrentLazyState, { ...props })
291
+ });
292
+ return ErrorComponent ? /* @__PURE__ */ jsx(RenderStateBoundary, {
293
+ fallback: (error, reset) => /* @__PURE__ */ jsx(RenderStateRetryContext.Provider, {
294
+ value: reset,
295
+ children: /* @__PURE__ */ jsx(ErrorComponent, {
296
+ error: toResourceStateError(String(layoutContext.resource), stateName, error),
297
+ retry: reset
298
+ })
299
+ }),
300
+ onRetry: retry,
301
+ children: rendered
302
+ }) : rendered;
303
+ }
304
+ return LazyStateComponent;
305
+ }
306
+ const definition = isDeferredRenderState(input) ? input[deferredRenderStateSymbol] : typeof input === "function" ? { render: input } : input;
307
+ if (typeof definition !== "object" || definition === null || !("render" in definition) || typeof definition.render !== "function") throw new LayoutConfigurationError(`Render state "${stateName}" must define a render function`);
308
+ const StateComponent = createLoadedStateComponent(stateName, definition);
309
+ if (!isDeferredRenderState(input)) return StateComponent;
310
+ const DeferredState = lazy(() => Promise.resolve({ default: StateComponent }));
311
+ function DeferredStateComponent(props) {
312
+ const LoadingComponent = stateName === "Loading" ? void 0 : stateComponents.Loading;
313
+ const ErrorComponent = stateName === "Error" ? void 0 : stateComponents.Error;
314
+ const rendered = /* @__PURE__ */ jsx(Suspense, {
315
+ fallback: LoadingComponent ? /* @__PURE__ */ jsx(LoadingComponent, {}) : null,
316
+ children: /* @__PURE__ */ jsx(DeferredState, { ...props })
317
+ });
318
+ return ErrorComponent ? /* @__PURE__ */ jsx(RenderStateBoundary, {
319
+ fallback: (error, retry) => /* @__PURE__ */ jsx(RenderStateRetryContext.Provider, {
320
+ value: retry,
321
+ children: /* @__PURE__ */ jsx(ErrorComponent, {
322
+ error: toResourceStateError(String(layoutContext.resource), stateName, error),
323
+ retry
324
+ })
325
+ }),
326
+ children: rendered
327
+ }) : rendered;
328
+ }
329
+ return DeferredStateComponent;
330
+ }
331
+ function createLoadedStateComponent(stateName, definition) {
332
+ const stateComposables = definition.composables ? (create) => ({
333
+ ...composables?.(create) ?? {},
334
+ ...definition.composables?.(create)
335
+ }) : composables;
336
+ const stateRender = stateName === "Error" ? (props, context) => /* @__PURE__ */ jsx(RenderStateRetryContext.Consumer, { children: (retry) => definition.render(props, {
337
+ ...context,
338
+ retry
339
+ }) }) : definition.render;
340
+ const StateComponent = defineResourceLayoutImpl({
341
+ resources,
342
+ options: inProps,
343
+ layout: {
344
+ composables: stateComposables,
345
+ props: definition.props,
346
+ render: stateRender
347
+ }
348
+ }).createResourceLayout(layoutOptions);
349
+ StateComponent.displayName = `${name}.${stateName}`;
350
+ return StateComponent;
351
+ }
352
+ if (createdDefaultState) CreatedDefaultStateComponent = createStateComponent("default", defaultRenderInput);
353
+ for (const [stateName, input] of Object.entries(renderStates)) {
354
+ if (stateName === "default") continue;
355
+ if ([
356
+ "__proto__",
357
+ "apply",
358
+ "arguments",
359
+ "bind",
360
+ "call",
361
+ "caller",
362
+ "displayName",
363
+ "length",
364
+ "makeComposable",
365
+ "name",
366
+ "props",
367
+ "prototype",
368
+ "resource"
369
+ ].includes(stateName)) throw new LayoutConfigurationError(`Render state name "${stateName}" is reserved`);
370
+ stateComponents[stateName] = createStateComponent(stateName, input);
371
+ }
372
+ const includedComponentDefinitions = Object.fromEntries(Object.keys(includeLayoutProps ?? {}).flatMap((key) => {
373
+ if (!allowsComponentPassthrough(includeLayoutProps?.[key]) || !(key in mergedResolvedInProps)) return [];
374
+ const definition = mergedResolvedInProps[key];
375
+ return [[key, isIncludedPropRequired(includeLayoutProps?.[key]) ? definition : definition.optional?.() ?? definition]];
376
+ }));
377
+ return attachComponentPropDefinitions(Object.assign(Component, {
209
378
  displayName: name,
210
379
  props: void 0,
211
380
  resource: void 0,
212
381
  ...createComposition({
213
382
  components: resolvedComposables,
214
383
  name
215
- })
384
+ }),
385
+ ...stateComponents,
386
+ [definedResourceLayoutComponentSymbol]: true
387
+ }), {
388
+ ...customLayoutProps,
389
+ ...includedComponentDefinitions
216
390
  });
217
391
  });
218
392
  const createTopLevelMakeComposable = () => {
@@ -228,7 +402,12 @@ function defineResourceLayoutImpl(options) {
228
402
  resource: resourcesEnum,
229
403
  name: createProp.string().literal(componentName)
230
404
  }) : inProps);
231
- const resolvedComposables = layout.composables ? resolveLayoutComposables(layout.composables, {
405
+ const defaultState = getSynchronousRenderStateDefinition((typeof layout.render === "function" ? { default: layout.render } : layout.render).default, layout.props);
406
+ const defaultComposables = defaultState?.composables ? (create) => ({
407
+ ...layout.composables?.(create) ?? {},
408
+ ...defaultState.composables?.(create)
409
+ }) : layout.composables;
410
+ const resolvedComposables = defaultComposables ? resolveLayoutComposables(defaultComposables, {
232
411
  resource,
233
412
  name: componentName,
234
413
  capitalize
@@ -236,7 +415,7 @@ function defineResourceLayoutImpl(options) {
236
415
  for (const { props: presetPropDefinitions } of collectComposablePresetEntries(resolvedComposables)) Object.assign(resolvedInProps, presetPropDefinitions);
237
416
  return {
238
417
  ...resolvedInProps,
239
- ...layout.props?.custom
418
+ ...(defaultState?.props ?? layout.props)?.custom
240
419
  };
241
420
  }
242
421
  const { createLayoutForResourceBuilder, forResource } = createForResource({