element-book 6.0.2 → 7.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 (121) hide show
  1. package/README.md +5 -5
  2. package/dist/data/book-entry/base-book-entry.d.ts +20 -0
  3. package/dist/data/book-entry/book-element-example/book-element-example.d.ts +32 -0
  4. package/dist/data/book-entry/book-entry-type.d.ts +9 -0
  5. package/dist/data/book-entry/book-entry-type.js +9 -0
  6. package/dist/data/book-entry/book-entry.d.ts +8 -0
  7. package/dist/data/book-entry/book-entry.js +4 -0
  8. package/dist/data/book-entry/book-page/book-page-controls.d.ts +41 -0
  9. package/dist/data/book-entry/book-page/book-page-controls.js +49 -0
  10. package/dist/data/book-entry/book-page/book-page.d.ts +12 -0
  11. package/dist/data/book-entry/book-page/book-page.js +1 -0
  12. package/dist/data/book-entry/book-page/current-controls.d.ts +12 -0
  13. package/dist/data/book-entry/book-page/current-controls.js +62 -0
  14. package/dist/data/book-entry/book-page/define-book-page.d.ts +17 -0
  15. package/dist/data/book-entry/book-page/define-book-page.js +34 -0
  16. package/dist/data/book-entry/book-root.d.ts +7 -0
  17. package/dist/data/book-entry/book-root.js +1 -0
  18. package/dist/data/book-entry/url-breadcrumbs.d.ts +7 -0
  19. package/dist/data/book-entry/url-breadcrumbs.js +24 -0
  20. package/dist/data/book-entry/verify-book-entry.d.ts +6 -0
  21. package/dist/data/book-entry/verify-book-entry.js +18 -0
  22. package/dist/data/book-tree/book-tree-node.d.ts +27 -0
  23. package/dist/data/book-tree/book-tree-node.js +1 -0
  24. package/dist/data/book-tree/book-tree.d.ts +16 -0
  25. package/dist/data/book-tree/book-tree.js +148 -0
  26. package/dist/data/book-tree/search-nodes.d.ts +5 -0
  27. package/dist/data/book-tree/search-nodes.js +56 -0
  28. package/dist/data/book-tree/tree-cache.d.ts +4 -0
  29. package/dist/data/book-tree/tree-cache.js +8 -0
  30. package/dist/index.d.ts +5 -6
  31. package/dist/index.js +5 -6
  32. package/dist/routing/book-routing.d.ts +10 -0
  33. package/dist/routing/book-routing.js +18 -0
  34. package/dist/routing/create-book-router.d.ts +2 -0
  35. package/dist/routing/{create-element-book-router.js → create-book-router.js} +8 -8
  36. package/dist/ui/elements/book-breadcrumbs.element.d.ts +5 -0
  37. package/dist/ui/elements/{element-book-breadcrumbs.element.js → book-breadcrumbs.element.js} +9 -9
  38. package/dist/ui/elements/book-nav.element.d.ts +7 -0
  39. package/dist/ui/elements/book-nav.element.js +114 -0
  40. package/dist/ui/elements/common/book-error.element.d.ts +3 -0
  41. package/dist/ui/elements/common/book-error.element.js +27 -0
  42. package/dist/ui/elements/common/book-route-link.element.d.ts +5 -0
  43. package/dist/ui/elements/common/{element-book-route-link.element.js → book-route-link.element.js} +5 -5
  44. package/dist/ui/elements/define-book-element.d.ts +2 -2
  45. package/dist/ui/elements/define-book-element.js +1 -1
  46. package/dist/ui/elements/element-book-app/element-book-app-slots.d.ts +2 -2
  47. package/dist/ui/elements/element-book-app/element-book-app-slots.js +2 -2
  48. package/dist/ui/elements/element-book-app/element-book-app.element.d.ts +7 -2
  49. package/dist/ui/elements/element-book-app/element-book-app.element.js +76 -29
  50. package/dist/ui/elements/element-book-app/element-book-config.d.ts +4 -2
  51. package/dist/ui/elements/element-book-app/get-current-nodes.d.ts +3 -0
  52. package/dist/ui/elements/element-book-app/get-current-nodes.js +23 -0
  53. package/dist/ui/elements/entry-display/book-breadcrumbs-bar.element.d.ts +6 -0
  54. package/dist/ui/elements/entry-display/book-breadcrumbs-bar.element.js +65 -0
  55. package/dist/ui/elements/entry-display/book-entry-description.element.d.ts +3 -0
  56. package/dist/ui/elements/entry-display/book-entry-description.element.js +34 -0
  57. package/dist/ui/elements/entry-display/book-entry-display.element.d.ts +12 -0
  58. package/dist/ui/elements/entry-display/book-entry-display.element.js +173 -0
  59. package/dist/ui/elements/entry-display/book-page/book-page-controls.element.d.ts +17 -0
  60. package/dist/ui/elements/entry-display/book-page/book-page-controls.element.js +147 -0
  61. package/dist/ui/elements/entry-display/book-page/book-page-wrapper.element.d.ts +10 -0
  62. package/dist/ui/elements/entry-display/book-page/book-page-wrapper.element.js +92 -0
  63. package/dist/ui/elements/entry-display/element-example/book-element-example-controls.element.d.ts +7 -0
  64. package/dist/ui/elements/entry-display/element-example/book-element-example-controls.element.js +36 -0
  65. package/dist/ui/elements/entry-display/element-example/book-element-example-viewer.element.d.ts +9 -0
  66. package/dist/ui/elements/entry-display/element-example/book-element-example-viewer.element.js +57 -0
  67. package/dist/ui/elements/entry-display/element-example/book-element-example-wrapper.element.d.ts +9 -0
  68. package/dist/ui/elements/entry-display/element-example/book-element-example-wrapper.element.js +49 -0
  69. package/dist/ui/events/change-route.event.d.ts +1 -1
  70. package/dist/util/type.d.ts +40 -0
  71. package/dist/util/type.js +1 -0
  72. package/package.json +15 -11
  73. package/dist/data/element-book-entry/element-book-chapter/element-book-chapter.d.ts +0 -22
  74. package/dist/data/element-book-entry/element-book-chapter/element-book-chapter.js +0 -16
  75. package/dist/data/element-book-entry/element-book-entry-type.d.ts +0 -8
  76. package/dist/data/element-book-entry/element-book-entry-type.js +0 -9
  77. package/dist/data/element-book-entry/element-book-entry.d.ts +0 -14
  78. package/dist/data/element-book-entry/element-book-entry.js +0 -22
  79. package/dist/data/element-book-entry/element-book-page/element-book-page-controls/element-book-page-control-type.d.ts +0 -13
  80. package/dist/data/element-book-entry/element-book-page/element-book-page-controls/element-book-page-control-type.js +0 -7
  81. package/dist/data/element-book-entry/element-book-page/element-book-page-controls/element-book-page-control.d.ts +0 -13
  82. package/dist/data/element-book-entry/element-book-page/element-book-page-controls/element-book-page-control.js +0 -4
  83. package/dist/data/element-book-entry/element-book-page/element-book-page-example.d.ts +0 -22
  84. package/dist/data/element-book-entry/element-book-page/element-book-page.d.ts +0 -23
  85. package/dist/data/element-book-entry/element-book-page/element-book-page.js +0 -41
  86. package/dist/data/element-book-entry/entry-tree/entry-tree-search.d.ts +0 -8
  87. package/dist/data/element-book-entry/entry-tree/entry-tree-search.js +0 -66
  88. package/dist/data/element-book-entry/entry-tree/entry-tree.d.ts +0 -18
  89. package/dist/data/element-book-entry/entry-tree/entry-tree.js +0 -87
  90. package/dist/data/element-book-entry/entry-tree/tree-cache.d.ts +0 -5
  91. package/dist/data/element-book-entry/entry-tree/tree-cache.js +0 -8
  92. package/dist/data/element-book-entry/entry-tree/walk-entry-tree.d.ts +0 -8
  93. package/dist/data/element-book-entry/entry-tree/walk-entry-tree.js +0 -85
  94. package/dist/routing/create-element-book-router.d.ts +0 -2
  95. package/dist/routing/element-book-routing.d.ts +0 -10
  96. package/dist/routing/element-book-routing.js +0 -18
  97. package/dist/ui/elements/common/element-book-route-link.element.d.ts +0 -5
  98. package/dist/ui/elements/element-book-app/get-current-entry.d.ts +0 -4
  99. package/dist/ui/elements/element-book-app/get-current-entry.js +0 -17
  100. package/dist/ui/elements/element-book-breadcrumbs.element.d.ts +0 -5
  101. package/dist/ui/elements/element-book-nav.element.d.ts +0 -7
  102. package/dist/ui/elements/element-book-nav.element.js +0 -129
  103. package/dist/ui/elements/entry-display/element-book-entry-display.element.d.ts +0 -8
  104. package/dist/ui/elements/entry-display/element-book-entry-display.element.js +0 -279
  105. package/dist/ui/elements/entry-display/element-book-example-controls.element.d.ts +0 -4
  106. package/dist/ui/elements/entry-display/element-book-example-controls.element.js +0 -26
  107. package/dist/ui/elements/entry-display/element-book-example-viewer.element.d.ts +0 -6
  108. package/dist/ui/elements/entry-display/element-book-example-viewer.element.js +0 -43
  109. package/dist/ui/elements/entry-display/element-book-page-controls.element.d.ts +0 -11
  110. package/dist/ui/elements/entry-display/element-book-page-controls.element.js +0 -65
  111. package/dist/ui/elements/entry-display/element-book-page-examples.element.d.ts +0 -7
  112. package/dist/ui/elements/entry-display/element-book-page-examples.element.js +0 -102
  113. package/dist/ui/icons/element-16.icon.d.ts +0 -1
  114. package/dist/ui/icons/element-16.icon.js +0 -18
  115. package/dist/ui/icons/element-24.icon.d.ts +0 -1
  116. package/dist/ui/icons/element-24.icon.js +0 -18
  117. package/dist/utilities/type.d.ts +0 -3
  118. /package/dist/data/{element-book-entry/element-book-page/element-book-page-example.js → book-entry/base-book-entry.js} +0 -0
  119. /package/dist/{utilities/type.js → data/book-entry/book-element-example/book-element-example.js} +0 -0
  120. /package/dist/{utilities/search.d.ts → util/fuzzy-search.d.ts} +0 -0
  121. /package/dist/{utilities/search.js → util/fuzzy-search.js} +0 -0
package/README.md CHANGED
@@ -1,8 +1,8 @@
1
1
  # element-book
2
2
 
3
- An [`element-vir`](https://npmjs.com/element-vir) drop-in element for building, testing, and demonstrating a collection of elements (or a design system).
3
+ An [`element-vir`](https://npmjs.com/element-vir) drop-in element for building, testing, and demonstrating a collection of elements (or, in other words, a design system).
4
4
 
5
- Currently in MVP form. Functional, but WIP.
5
+ Currently in MVP form. Functional, but WIP and frequently changing.
6
6
 
7
7
  [Example here.](https://electrovir.github.io/element-book/)
8
8
 
@@ -14,8 +14,8 @@ npm i element-book
14
14
 
15
15
  # Terminology
16
16
 
17
- - **Chapter**: a group of sub-chapters or element examples. Chapters can be infinitely nested.
18
- - **Example**: an individual element example with independent state, styles, and title.
17
+ - **Page**: a group of pages and / or element examples. Pages can be infinitely nested.
18
+ - **Element Example**: an individual element example with independent state, styles, title, etc.
19
19
 
20
20
  # Usage
21
21
 
@@ -23,4 +23,4 @@ _todo_
23
23
 
24
24
  # Why not Storybook?
25
25
 
26
- Because Storybook is un-composable, impossible to debug, and full of behind-the-scenes \*magic\* that you can't backtrack without deeply understand the inner workings of Storybook. With `element-book`, it's all just imports that you can directly follow with the TypeScript compiler.
26
+ Because Storybook is un-composable, impossible to debug, and full of behind-the-scenes \*magic\* that you can't backtrack without already understanding the inner workings of Storybook itself. With `element-book`, it's all just imports that you can directly follow with the TypeScript compiler.
@@ -0,0 +1,20 @@
1
+ import { BookEntryTypeEnum } from './book-entry-type';
2
+ export type BaseBookEntry = {
3
+ /**
4
+ * Title for the entry. This is used to create breadcrumbs and URL paths. Each title must be
5
+ * unique within a given entry's parent.
6
+ */
7
+ title: string;
8
+ entryType: BookEntryTypeEnum;
9
+ /**
10
+ * The parent page. A value of undefined here indicates that the entry should be at the top
11
+ * level of the element book.
12
+ */
13
+ parent: unknown;
14
+ /**
15
+ * A description that will be displayed below the entry title. Each item in the array will be a
16
+ * separate paragraph.
17
+ */
18
+ descriptionParagraphs: string[];
19
+ errors: Error[];
20
+ };
@@ -0,0 +1,32 @@
1
+ import { Overwrite, RequireNonVoid } from '@augment-vir/common';
2
+ import { PropertyInitMapBase, RenderParams, TypedEvent } from 'element-vir';
3
+ import { CSSResult } from 'lit';
4
+ import { SetOptional } from 'type-fest';
5
+ import { BaseBookEntry } from '../base-book-entry';
6
+ import { BookEntryTypeEnum } from '../book-entry-type';
7
+ import { BookPage } from '../book-page/book-page';
8
+ import { BookPageControlsInitBase, ControlsToValues } from '../book-page/book-page-controls';
9
+ export type BookPageExampleRenderParams<ControlsInit extends BookPageControlsInitBase, StateInit extends PropertyInitMapBase> = Pick<RenderParams<any, any, StateInit, any, any, any>, 'state' | 'updateState'> & {
10
+ controls: ControlsToValues<ControlsInit>;
11
+ };
12
+ export type BookElementExample<ControlsInit extends BookPageControlsInitBase = {}, StateInit extends PropertyInitMapBase = {}, RenderOutput = unknown> = Overwrite<BaseBookEntry, {
13
+ parent: BookPage | undefined;
14
+ entryType: BookEntryTypeEnum.ElementExample;
15
+ } & {
16
+ /** Initialize the state for this example. */
17
+ stateInitStatic?: StateInit;
18
+ /** Specify which events this example should intercept (so the user can see them). */
19
+ showEvents?: ReadonlyArray<string | TypedEvent>;
20
+ /**
21
+ * Style the element example. You can even use the :host selector to style this specific
22
+ * example's wrapper element!
23
+ */
24
+ styles?: CSSResult;
25
+ /** Render the example. */
26
+ renderCallback: RequireNonVoid<RenderOutput, (renderParams: BookPageExampleRenderParams<ControlsInit, StateInit>) => RenderOutput, 'renderCallback is missing a return statement'>;
27
+ }>;
28
+ /**
29
+ * The properties that are necessary to initialize an element example. Missing properties will be
30
+ * filling in by the parent.
31
+ */
32
+ export type BookElementExampleInit<Controls extends BookPageControlsInitBase, StateInit extends PropertyInitMapBase, RenderOutput> = SetOptional<Omit<BookElementExample<Controls, StateInit, RenderOutput>, 'entryType' | 'parent' | 'errors'>, 'descriptionParagraphs'>;
@@ -0,0 +1,9 @@
1
+ export declare enum BookEntryTypeEnum {
2
+ /** A single element example. */
3
+ ElementExample = "element-example",
4
+ /** An individual book page with element examples and/or sub-pages. */
5
+ Page = "page",
6
+ /** Tree root. Not for external use. */
7
+ Root = "root"
8
+ }
9
+ export type AnyBookEntryType = BookEntryTypeEnum.ElementExample | BookEntryTypeEnum.Page | BookEntryTypeEnum.Root;
@@ -0,0 +1,9 @@
1
+ export var BookEntryTypeEnum;
2
+ (function (BookEntryTypeEnum) {
3
+ /** A single element example. */
4
+ BookEntryTypeEnum["ElementExample"] = "element-example";
5
+ /** An individual book page with element examples and/or sub-pages. */
6
+ BookEntryTypeEnum["Page"] = "page";
7
+ /** Tree root. Not for external use. */
8
+ BookEntryTypeEnum["Root"] = "root";
9
+ })(BookEntryTypeEnum || (BookEntryTypeEnum = {}));
@@ -0,0 +1,8 @@
1
+ import { BookElementExample } from './book-element-example/book-element-example';
2
+ import { BookEntryTypeEnum } from './book-entry-type';
3
+ import { BookPage } from './book-page/book-page';
4
+ import { BookRoot } from './book-root';
5
+ export type BookEntry = BookPage | BookRoot | BookElementExample;
6
+ export declare function isBookEntry<SpecificType extends BookEntryTypeEnum>(entry: unknown, entryType: SpecificType): entry is Extract<BookEntry, {
7
+ entryType: SpecificType;
8
+ }>;
@@ -0,0 +1,4 @@
1
+ import { typedHasProperty } from '@augment-vir/common';
2
+ export function isBookEntry(entry, entryType) {
3
+ return typedHasProperty(entry, 'entryType') && entry.entryType === entryType;
4
+ }
@@ -0,0 +1,41 @@
1
+ export type BookPageControl<ControlType extends BookPageControlTypeEnum = BookPageControlTypeEnum> = {
2
+ controlType: ControlType;
3
+ initValue: BookPageControlValueType[ControlType];
4
+ /** The name and label for the control. */
5
+ controlName: string;
6
+ } & (ControlType extends BookPageControlTypeEnum.Dropdown ? {
7
+ options: string[];
8
+ } : {});
9
+ export type BookPageControlInit<ControlType extends BookPageControlTypeEnum> = Omit<BookPageControl<ControlType>, 'controlName'>;
10
+ export declare function isControlInitType<SpecificControlType extends BookPageControlTypeEnum>(controlInit: BookPageControlInit<any>, specificType: SpecificControlType): controlInit is BookPageControlInit<SpecificControlType>;
11
+ /**
12
+ * Define a page control. This doesn't do anything fancy (in fact it only returns the input) but it
13
+ * helps immensely with type inference.
14
+ */
15
+ export declare function definePageControl<ControlType extends BookPageControlTypeEnum>(controlInit: BookPageControlInit<ControlType>): BookPageControlInit<ControlType>;
16
+ export type ControlsToValues<ControlsInit extends BookPageControlsInitBase> = {
17
+ [ControlName in keyof ControlsInit]: ControlsInit[ControlName]['initValue'];
18
+ };
19
+ export type BookPageControlsInitBase = Record<string, BookPageControlInit<BookPageControlTypeEnum>>;
20
+ export type BookPageControlsValues = ControlsToValues<BookPageControlsInitBase>;
21
+ export declare enum BookPageControlTypeEnum {
22
+ Checkbox = "checkbox",
23
+ Color = "color",
24
+ Dropdown = "dropdown",
25
+ /** Hidden controls allow any values but they aren't displayed to the user for editing. */
26
+ Hidden = "hidden",
27
+ Number = "number",
28
+ Text = "text"
29
+ }
30
+ declare const controlValueTypes: {
31
+ checkbox: boolean;
32
+ color: string;
33
+ /** The number type here indicates which index in the options are selected. */
34
+ dropdown: number;
35
+ hidden: any;
36
+ number: number;
37
+ text: string;
38
+ };
39
+ export type BookPageControlValueType = typeof controlValueTypes;
40
+ export declare function checkControls(controlsInit: BookPageControlsInitBase | undefined, pageName: string): Error[];
41
+ export {};
@@ -0,0 +1,49 @@
1
+ export function isControlInitType(controlInit, specificType) {
2
+ return controlInit.controlType === specificType;
3
+ }
4
+ /**
5
+ * Define a page control. This doesn't do anything fancy (in fact it only returns the input) but it
6
+ * helps immensely with type inference.
7
+ */
8
+ export function definePageControl(controlInit) {
9
+ return controlInit;
10
+ }
11
+ export var BookPageControlTypeEnum;
12
+ (function (BookPageControlTypeEnum) {
13
+ BookPageControlTypeEnum["Checkbox"] = "checkbox";
14
+ BookPageControlTypeEnum["Color"] = "color";
15
+ BookPageControlTypeEnum["Dropdown"] = "dropdown";
16
+ /** Hidden controls allow any values but they aren't displayed to the user for editing. */
17
+ BookPageControlTypeEnum["Hidden"] = "hidden";
18
+ BookPageControlTypeEnum["Number"] = "number";
19
+ BookPageControlTypeEnum["Text"] = "text";
20
+ })(BookPageControlTypeEnum || (BookPageControlTypeEnum = {}));
21
+ const anySymbol = Symbol('any-type');
22
+ const controlValueTypes = {
23
+ [BookPageControlTypeEnum.Checkbox]: false,
24
+ [BookPageControlTypeEnum.Color]: '',
25
+ /** The number type here indicates which index in the options are selected. */
26
+ [BookPageControlTypeEnum.Dropdown]: 0,
27
+ [BookPageControlTypeEnum.Hidden]: anySymbol,
28
+ [BookPageControlTypeEnum.Number]: 0,
29
+ [BookPageControlTypeEnum.Text]: '',
30
+ };
31
+ export function checkControls(controlsInit, pageName) {
32
+ if (!controlsInit) {
33
+ return [];
34
+ }
35
+ const errors = [];
36
+ Object.entries(controlsInit).forEach(([controlName, controlEntry,]) => {
37
+ const expectedInitDefault = controlValueTypes[controlEntry.controlType];
38
+ if (expectedInitDefault === anySymbol) {
39
+ return;
40
+ }
41
+ if (typeof expectedInitDefault !== typeof controlEntry.initValue) {
42
+ errors.push(new Error(`Control '${controlName}' in page '${pageName}' has invalid initValue '${controlEntry.initValue}': expected initValue of type ${typeof expectedInitDefault} because the control is of type ${controlEntry.controlType}.`));
43
+ }
44
+ if (!controlName) {
45
+ errors.push(new Error(`'${pageName}' cannot have an empty control name.`));
46
+ }
47
+ });
48
+ return errors;
49
+ }
@@ -0,0 +1,12 @@
1
+ import { Overwrite } from '@augment-vir/common';
2
+ import { BaseBookEntry } from '../base-book-entry';
3
+ import { BookElementExample } from '../book-element-example/book-element-example';
4
+ import { BookEntryTypeEnum } from '../book-entry-type';
5
+ import { BookPageControlsInitBase } from './book-page-controls';
6
+ export type BookPage<ParentPage extends BookPage | undefined = any, ControlsInit extends BookPageControlsInitBase = BookPageControlsInitBase> = Overwrite<BaseBookEntry, {
7
+ parent: ParentPage;
8
+ entryType: BookEntryTypeEnum.Page;
9
+ }> & {
10
+ controls: ControlsInit;
11
+ elementExamples: Record<string, BookElementExample>;
12
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,12 @@
1
+ import { BookTreeNode } from '../../book-tree/book-tree-node';
2
+ import { BookPageControlsValues } from './book-page-controls';
3
+ export type ControlsWrapper = {
4
+ children: CurrentControls;
5
+ controls: BookPageControlsValues;
6
+ };
7
+ export type CurrentControls = {
8
+ [Breadcrumb: string]: ControlsWrapper;
9
+ };
10
+ export declare function traverseCurrentControls(currentControls: CurrentControls, fullUrlBreadcrumbs: ReadonlyArray<string>): BookPageControlsValues;
11
+ export declare function createNewCurrentControls(currentControls: CurrentControls, fullUrlBreadcrumbs: ReadonlyArray<string>, newValues: BookPageControlsValues): CurrentControls;
12
+ export declare function createControlsFromTree(node: BookTreeNode): CurrentControls;
@@ -0,0 +1,62 @@
1
+ import { copyThroughJson, mapObjectValues } from '@augment-vir/common';
2
+ import { isBookTreeNode } from '../../book-tree/book-tree';
3
+ import { BookEntryTypeEnum } from '../book-entry-type';
4
+ function getOrCreateControlsWrapper(currentControls, currentBreadcrumb, newValues) {
5
+ const controlsWrapper = currentControls[currentBreadcrumb];
6
+ if (controlsWrapper) {
7
+ return controlsWrapper;
8
+ }
9
+ if (newValues) {
10
+ const newControls = {
11
+ children: {},
12
+ controls: {},
13
+ };
14
+ currentControls[currentBreadcrumb] = newControls;
15
+ return newControls;
16
+ }
17
+ return undefined;
18
+ }
19
+ export function traverseCurrentControls(currentControls, fullUrlBreadcrumbs) {
20
+ return internalTraverseCurrentControls(currentControls, fullUrlBreadcrumbs, undefined);
21
+ }
22
+ function internalTraverseCurrentControls(currentControls, fullUrlBreadcrumbs, newValues) {
23
+ const currentBreadcrumb = fullUrlBreadcrumbs[0];
24
+ if (!currentBreadcrumb) {
25
+ return {};
26
+ }
27
+ const controlsWrapper = getOrCreateControlsWrapper(currentControls, currentBreadcrumb, newValues);
28
+ if (!controlsWrapper) {
29
+ return {};
30
+ }
31
+ const nextBreadcrumbs = fullUrlBreadcrumbs.slice(1);
32
+ if (!nextBreadcrumbs.length && newValues) {
33
+ controlsWrapper.controls = newValues;
34
+ }
35
+ const controlsValues = controlsWrapper.controls;
36
+ return {
37
+ ...controlsValues,
38
+ ...internalTraverseCurrentControls(controlsWrapper.children, nextBreadcrumbs, newValues),
39
+ };
40
+ }
41
+ export function createNewCurrentControls(currentControls, fullUrlBreadcrumbs, newValues) {
42
+ const newCurrentControls = copyThroughJson(currentControls);
43
+ internalTraverseCurrentControls(newCurrentControls, fullUrlBreadcrumbs, newValues);
44
+ return newCurrentControls;
45
+ }
46
+ export function createControlsFromTree(node) {
47
+ const currentControls = mapObjectValues(node.children, (childName, child) => {
48
+ if (!isBookTreeNode(child, BookEntryTypeEnum.Page)) {
49
+ return {
50
+ children: {},
51
+ controls: {},
52
+ };
53
+ }
54
+ return {
55
+ children: createControlsFromTree(child),
56
+ controls: mapObjectValues(child.entry.controls, (name, setup) => {
57
+ return setup.initValue;
58
+ }),
59
+ };
60
+ });
61
+ return currentControls;
62
+ }
@@ -0,0 +1,17 @@
1
+ import { PropertyInitMapBase } from 'element-vir';
2
+ import { SetOptional } from 'type-fest';
3
+ import { InfiniteRecursionLimiter } from '../../../util/type';
4
+ import { BookElementExampleInit } from '../book-element-example/book-element-example';
5
+ import { BookPage } from './book-page';
6
+ import { BookPageControlsInitBase } from './book-page-controls';
7
+ export type ElementExamplesDefiner<ControlsInit extends BookPageControlsInitBase = BookPageControlsInitBase> = (params: {
8
+ defineExample: <StateInit extends PropertyInitMapBase, RenderOutput>(exampleInit: BookElementExampleInit<ControlsInit, StateInit, RenderOutput>) => void;
9
+ }) => void;
10
+ type CollapseControlsInit<ParentPage extends BookPage | undefined, CurrentControlsInit extends BookPageControlsInitBase,
11
+ /** Prevent infinite recursion TypeScript errors. */
12
+ RecursionDepth = InfiniteRecursionLimiter> = CurrentControlsInit & (RecursionDepth extends [any, ...infer RemainingDepth] ? ParentPage extends BookPage<infer GrandParentPage, infer ParentControls> ? CollapseControlsInit<GrandParentPage, ParentControls, RemainingDepth> : {} : {});
13
+ export type BookPageInit<ParentPage extends BookPage | undefined, CurrentControlsInit extends BookPageControlsInitBase> = SetOptional<Omit<BookPage<ParentPage, CurrentControlsInit>, 'entryType' | 'elementExamples' | 'errors'>, 'controls' | 'descriptionParagraphs'> & {
14
+ elementExamplesCallback?: ElementExamplesDefiner<CollapseControlsInit<ParentPage, CurrentControlsInit>> | undefined;
15
+ };
16
+ export declare function defineBookPage<const ParentPage extends BookPage | undefined, const ControlsInit extends BookPageControlsInitBase = {}>(pageInit: BookPageInit<ParentPage, ControlsInit>): BookPage<ParentPage, ControlsInit>;
17
+ export {};
@@ -0,0 +1,34 @@
1
+ import { isTruthy } from '@augment-vir/common';
2
+ import { BookEntryTypeEnum } from '../book-entry-type';
3
+ import { titleToUrlBreadcrumb } from '../url-breadcrumbs';
4
+ export function defineBookPage(pageInit) {
5
+ const page = {
6
+ ...pageInit,
7
+ entryType: BookEntryTypeEnum.Page,
8
+ elementExamples: {},
9
+ descriptionParagraphs: pageInit.descriptionParagraphs ?? [],
10
+ controls: pageInit.controls ?? {},
11
+ errors: [],
12
+ };
13
+ const alreadyTakenElementExampleNames = new Set();
14
+ if (pageInit.elementExamplesCallback) {
15
+ pageInit.elementExamplesCallback({
16
+ defineExample(elementExampleInit) {
17
+ const newExample = {
18
+ ...elementExampleInit,
19
+ entryType: BookEntryTypeEnum.ElementExample,
20
+ parent: page,
21
+ descriptionParagraphs: elementExampleInit.descriptionParagraphs ?? [],
22
+ errors: [
23
+ alreadyTakenElementExampleNames.has(elementExampleInit.title) &&
24
+ new Error(`Example title '${elementExampleInit.title}' in page '${pageInit.title}' is already taken.`),
25
+ ].filter(isTruthy),
26
+ };
27
+ alreadyTakenElementExampleNames.add(elementExampleInit.title);
28
+ page.elementExamples[titleToUrlBreadcrumb(newExample.title)] =
29
+ newExample;
30
+ },
31
+ });
32
+ }
33
+ return page;
34
+ }
@@ -0,0 +1,7 @@
1
+ import { Overwrite } from '@augment-vir/common';
2
+ import { BaseBookEntry } from './base-book-entry';
3
+ import { BookEntryTypeEnum } from './book-entry-type';
4
+ export type BookRoot = Overwrite<BaseBookEntry, {
5
+ entryType: BookEntryTypeEnum.Root;
6
+ parent: undefined;
7
+ }>;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,7 @@
1
+ import { BookEntry } from './book-entry';
2
+ export declare function listUrlBreadcrumbs(entry: BookEntry, includeSelf: boolean): string[];
3
+ export declare function titleToUrlBreadcrumb(title: string): string;
4
+ export declare function doBreadcrumbsStartWith({ searchFor, searchIn, }: {
5
+ searchIn: ReadonlyArray<string>;
6
+ searchFor: ReadonlyArray<string>;
7
+ }): boolean;
@@ -0,0 +1,24 @@
1
+ import { collapseWhiteSpace } from '@augment-vir/common';
2
+ export function listUrlBreadcrumbs(entry, includeSelf) {
3
+ const entryBreadcrumb = titleToUrlBreadcrumb(entry.title);
4
+ if (entry.parent) {
5
+ return [
6
+ ...listUrlBreadcrumbs(entry.parent, false),
7
+ titleToUrlBreadcrumb(entry.parent.title),
8
+ ].concat(includeSelf ? [entryBreadcrumb] : []);
9
+ }
10
+ else if (includeSelf) {
11
+ return [entryBreadcrumb];
12
+ }
13
+ else {
14
+ return [];
15
+ }
16
+ }
17
+ export function titleToUrlBreadcrumb(title) {
18
+ return collapseWhiteSpace(title).toLowerCase().replaceAll(/\s/g, '-');
19
+ }
20
+ export function doBreadcrumbsStartWith({ searchFor, searchIn, }) {
21
+ return searchFor.every((breadcrumb, index) => {
22
+ return searchIn[index] === breadcrumb;
23
+ });
24
+ }
@@ -0,0 +1,6 @@
1
+ import { BookEntry } from './book-entry';
2
+ export declare const bookEntryVerifiers: {
3
+ "element-example": (entry: BookEntry) => Error[];
4
+ page: (entry: BookEntry) => Error[];
5
+ root: (entry: BookEntry) => Error[];
6
+ };
@@ -0,0 +1,18 @@
1
+ import { isTruthy } from '@augment-vir/common';
2
+ import { BookEntryTypeEnum } from './book-entry-type';
3
+ import { checkControls } from './book-page/book-page-controls';
4
+ export const bookEntryVerifiers = {
5
+ [BookEntryTypeEnum.ElementExample]: () => {
6
+ // currently all element example checking happens on page definition
7
+ return [];
8
+ },
9
+ [BookEntryTypeEnum.Page]: (bookPage) => {
10
+ return [
11
+ !bookPage.title && new Error(`Cannot define an element-book page with an empty title.`),
12
+ ...checkControls(bookPage.controls, bookPage.title),
13
+ ].filter(isTruthy);
14
+ },
15
+ [BookEntryTypeEnum.Root]: () => {
16
+ return [];
17
+ },
18
+ };
@@ -0,0 +1,27 @@
1
+ import { PropertyValueType } from '@augment-vir/common';
2
+ import { BookEntry } from '../book-entry/book-entry';
3
+ import { AnyBookEntryType, BookEntryTypeEnum } from '../book-entry/book-entry-type';
4
+ export declare const isBookTreeNodeMarker = "_isBookTreeNode";
5
+ type InternalBookTreeNodeType<Entry> = {
6
+ [isBookTreeNodeMarker]: true;
7
+ entry: Entry;
8
+ /** UrlBreadcrumb is different from entry.title because it's modified to support URLs. */
9
+ urlBreadcrumb: string;
10
+ fullUrlBreadcrumbs: ReadonlyArray<string>;
11
+ children: Record<string, BookTreeNode>;
12
+ /**
13
+ * False when an entry has been added by traversing manually added parents. True when the entry
14
+ * was added as an explicit entry.
15
+ */
16
+ manuallyAdded: boolean;
17
+ };
18
+ export type BookTreeNode<EntryTypes extends BookEntryTypeEnum = AnyBookEntryType> = PropertyValueType<{
19
+ [EntryType in EntryTypes]: InternalBookTreeNodeType<Extract<BookEntry, {
20
+ entryType: EntryType;
21
+ }>>;
22
+ }> | (AnyBookEntryType extends EntryTypes ? InternalBookTreeNodeType<BookEntry> : never);
23
+ export type BookTree = Readonly<{
24
+ tree: Readonly<BookTreeNode<BookEntryTypeEnum.Root>>;
25
+ flattenedNodes: ReadonlyArray<Readonly<BookTreeNode>>;
26
+ }>;
27
+ export {};
@@ -0,0 +1 @@
1
+ export const isBookTreeNodeMarker = '_isBookTreeNode';
@@ -0,0 +1,16 @@
1
+ import { BookEntry } from '../book-entry/book-entry';
2
+ import { BookEntryTypeEnum } from '../book-entry/book-entry-type';
3
+ import { BookTree, BookTreeNode } from './book-tree-node';
4
+ export declare function doesNodeHaveEntryType<EntryType extends BookEntryTypeEnum>(node: BookTreeNode<any>, entryType: EntryType): node is BookTreeNode<EntryType>;
5
+ export declare function isBookTreeNode<SpecificType extends BookEntryTypeEnum>(input: unknown, entryType: SpecificType): input is BookTreeNode<SpecificType>;
6
+ export declare function isAnyBookTreeNode(input: unknown): input is BookTreeNode<BookEntryTypeEnum>;
7
+ export declare function createEmptyBookTreeRoot(title: string | undefined, descriptionParagraphs: ReadonlyArray<string>): BookTreeNode<BookEntryTypeEnum.Root>;
8
+ export declare function createBookTreeFromEntries({ entries, everythingTitle, everythingDescriptionParagraphs, debug, }: {
9
+ entries: ReadonlyArray<BookEntry>;
10
+ everythingTitle: string | undefined;
11
+ everythingDescriptionParagraphs: ReadonlyArray<string>;
12
+ debug: boolean;
13
+ }): BookTree;
14
+ export declare function traverseToImmediateParent(entryOrNode: Readonly<BookEntry> | BookTreeNode, currentTree: Readonly<BookTreeNode>): BookTreeNode | undefined;
15
+ export declare function getSortedNodeChildren(nodeA: Readonly<BookTreeNode>, nodeB: Readonly<BookTreeNode>): number;
16
+ export declare function flattenTree(node: Readonly<BookTreeNode>): BookTreeNode[];
@@ -0,0 +1,148 @@
1
+ import { makeWritable, typedHasProperties } from '@augment-vir/common';
2
+ import { isBookEntry } from '../book-entry/book-entry';
3
+ import { BookEntryTypeEnum } from '../book-entry/book-entry-type';
4
+ import { listUrlBreadcrumbs, titleToUrlBreadcrumb } from '../book-entry/url-breadcrumbs';
5
+ import { bookEntryVerifiers } from '../book-entry/verify-book-entry';
6
+ import { isBookTreeNodeMarker } from './book-tree-node';
7
+ import { addTreeToCache, getTreeFromCache } from './tree-cache';
8
+ export function doesNodeHaveEntryType(node, entryType) {
9
+ return node.entry.entryType === entryType;
10
+ }
11
+ export function isBookTreeNode(input, entryType) {
12
+ return !!(isAnyBookTreeNode(input) && input.entry.entryType === entryType);
13
+ }
14
+ export function isAnyBookTreeNode(input) {
15
+ return !!(typedHasProperties(input, [
16
+ isBookTreeNodeMarker,
17
+ 'entry',
18
+ ]) && input[isBookTreeNodeMarker]);
19
+ }
20
+ export function createEmptyBookTreeRoot(title, descriptionParagraphs) {
21
+ const rootNode = {
22
+ [isBookTreeNodeMarker]: true,
23
+ entry: {
24
+ entryType: BookEntryTypeEnum.Root,
25
+ title: title || 'Everything',
26
+ parent: undefined,
27
+ errors: [],
28
+ descriptionParagraphs: makeWritable(descriptionParagraphs),
29
+ },
30
+ urlBreadcrumb: '',
31
+ fullUrlBreadcrumbs: [],
32
+ children: {},
33
+ manuallyAdded: true,
34
+ };
35
+ return rootNode;
36
+ }
37
+ export function createBookTreeFromEntries({ entries, everythingTitle, everythingDescriptionParagraphs, debug, }) {
38
+ const cachedTree = getTreeFromCache(entries);
39
+ if (cachedTree) {
40
+ return cachedTree;
41
+ }
42
+ const tree = createEmptyBookTreeRoot(everythingTitle, everythingDescriptionParagraphs);
43
+ entries.forEach((newEntry) => addEntryToTree({ tree, newEntry, debug, manuallyAdded: true }));
44
+ const flattenedNodes = flattenTree(tree);
45
+ const bookTree = {
46
+ tree,
47
+ flattenedNodes,
48
+ };
49
+ addTreeToCache(entries, bookTree);
50
+ if (debug) {
51
+ console.info('element-book tree:', tree);
52
+ }
53
+ return bookTree;
54
+ }
55
+ function getOrAddImmediateParent(tree, entry, debug) {
56
+ if (!entry.parent) {
57
+ return tree;
58
+ }
59
+ const immediateParent = traverseToImmediateParent(entry, tree);
60
+ if (immediateParent) {
61
+ return immediateParent;
62
+ }
63
+ if (debug) {
64
+ console.info(`parent of ${entry.title} not found in tree; adding it now.`);
65
+ }
66
+ addEntryToTree({ tree, newEntry: entry.parent, debug, manuallyAdded: false });
67
+ const immediateParentAfterAdding = traverseToImmediateParent(entry, tree);
68
+ if (!immediateParentAfterAdding) {
69
+ throw new Error(`Failed to find node despite having just added it: ${listUrlBreadcrumbs(entry, false)}`);
70
+ }
71
+ return immediateParentAfterAdding;
72
+ }
73
+ function addEntryToTree({ tree, newEntry, debug, manuallyAdded, }) {
74
+ const errors = bookEntryVerifiers[newEntry.entryType](newEntry);
75
+ newEntry.errors.push(...errors);
76
+ const immediateParent = getOrAddImmediateParent(tree, newEntry, debug);
77
+ const newEntryUrlBreadcrumb = titleToUrlBreadcrumb(newEntry.title);
78
+ const existingChild = immediateParent.children[newEntryUrlBreadcrumb];
79
+ if (existingChild) {
80
+ // ignores multiple entries that have been added by following parent chains
81
+ if (manuallyAdded) {
82
+ if (existingChild.manuallyAdded) {
83
+ existingChild.entry.errors.push(new Error(`Cannot create duplicate '${newEntryUrlBreadcrumb}'${immediateParent.urlBreadcrumb
84
+ ? ` in parent '${immediateParent.urlBreadcrumb}'.`
85
+ : ''}`));
86
+ return;
87
+ }
88
+ existingChild.manuallyAdded = true;
89
+ }
90
+ return;
91
+ }
92
+ const newNode = {
93
+ [isBookTreeNodeMarker]: true,
94
+ children: {},
95
+ urlBreadcrumb: newEntryUrlBreadcrumb,
96
+ fullUrlBreadcrumbs: [
97
+ ...immediateParent.fullUrlBreadcrumbs,
98
+ newEntryUrlBreadcrumb,
99
+ ],
100
+ entry: newEntry,
101
+ manuallyAdded,
102
+ };
103
+ immediateParent.children[newEntryUrlBreadcrumb] = newNode;
104
+ bookEntryVerifiers;
105
+ if (isBookEntry(newEntry, BookEntryTypeEnum.Page) &&
106
+ Object.values(newEntry.elementExamples ?? {}).length) {
107
+ Object.values(newEntry.elementExamples ?? {}).forEach((elementExample) => addEntryToTree({ tree, newEntry: elementExample, debug, manuallyAdded }));
108
+ }
109
+ }
110
+ export function traverseToImmediateParent(entryOrNode, currentTree) {
111
+ const breadcrumbs = isAnyBookTreeNode(entryOrNode)
112
+ ? entryOrNode.fullUrlBreadcrumbs.slice(0, -1)
113
+ : listUrlBreadcrumbs(entryOrNode, false);
114
+ if (!breadcrumbs.length) {
115
+ return undefined;
116
+ }
117
+ const immediateParentNode = breadcrumbs.reduce((currentAncestor, nextBreadcrumb) => {
118
+ if (!currentAncestor) {
119
+ return undefined;
120
+ }
121
+ return currentAncestor.children[nextBreadcrumb];
122
+ }, currentTree);
123
+ return immediateParentNode;
124
+ }
125
+ export function getSortedNodeChildren(nodeA, nodeB) {
126
+ if (nodeA.entry.entryType !== nodeB.entry.entryType) {
127
+ if (isBookEntry(nodeA.entry, BookEntryTypeEnum.ElementExample)) {
128
+ return -1;
129
+ }
130
+ if (isBookEntry(nodeB.entry, BookEntryTypeEnum.ElementExample)) {
131
+ return 1;
132
+ }
133
+ }
134
+ return nodeA.entry.title.localeCompare(nodeB.entry.title);
135
+ }
136
+ export function flattenTree(node) {
137
+ const hasErrors = !!node.entry.errors.length;
138
+ const childNodes = hasErrors
139
+ ? []
140
+ : Object.values(node.children)
141
+ .sort(getSortedNodeChildren)
142
+ .map((child) => flattenTree(child));
143
+ const entries = [
144
+ node,
145
+ ...childNodes,
146
+ ].flat();
147
+ return entries;
148
+ }
@@ -0,0 +1,5 @@
1
+ import { BookTreeNode } from './book-tree-node';
2
+ export declare function searchFlattenedNodes({ flattenedNodes, searchQuery, }: {
3
+ flattenedNodes: ReadonlyArray<BookTreeNode>;
4
+ searchQuery: string;
5
+ }): ReadonlyArray<BookTreeNode>;