element-book 0.0.13 → 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.
@@ -1,7 +1,12 @@
1
1
  import { ElementBookEntryTypeEnum } from '../element-book-entry-type';
2
2
  export function defineElementBookChapter(chapterSetup) {
3
3
  if (!chapterSetup.title) {
4
- throw new Error(`Cannot have an element-book chapter with an empty title.`);
4
+ /**
5
+ * We don't want the Error type to actually be a part of this function's return type, cause
6
+ * users shouldn't actually return errors, but we still want to pass errors to element-book
7
+ * so element-book can handle them.
8
+ */
9
+ return new Error(`Cannot have an element-book chapter with an empty title.`);
5
10
  }
6
11
  const chapter = {
7
12
  type: ElementBookEntryTypeEnum.Chapter,
@@ -1,8 +1,10 @@
1
+ import { combineErrors } from '@augment-vir/common';
1
2
  import { ElementBookEntryTypeEnum } from '../element-book-entry-type';
2
3
  import { listBreadcrumbs } from '../entry-tree/entry-tree';
3
4
  export function defineElementBookPage(pageSetup) {
5
+ const errors = [];
4
6
  if (!pageSetup.title) {
5
- throw new Error(`Cannot have an element-book page with an empty title.`);
7
+ errors.push(new Error(`Cannot have an element-book page with an empty title.`));
6
8
  }
7
9
  const page = {
8
10
  type: ElementBookEntryTypeEnum.Page,
@@ -15,12 +17,20 @@ export function defineElementBookPage(pageSetup) {
15
17
  .concat(example.title)
16
18
  .join(' > ')}'`;
17
19
  if (exampleTitlesSet.has(example.title)) {
18
- throw new Error(`${failureMessage}: example title '${example.title}' is already being used.`);
20
+ errors.push(Error(`${failureMessage}: title '${example.title}' is already being used.`));
19
21
  }
20
22
  else if (!example.title) {
21
- throw new Error(`${failureMessage}: example title is missing or empty.`);
23
+ errors.push(Error(`${failureMessage}: example title is missing or empty.`));
22
24
  }
23
25
  exampleTitlesSet.add(example.title);
24
26
  });
27
+ if (errors.length) {
28
+ /**
29
+ * We don't want the Error type to actually be a part of this function's return type, cause
30
+ * users shouldn't actually return errors, but we still want to pass errors to element-book
31
+ * so element-book can handle them.
32
+ */
33
+ return combineErrors(errors);
34
+ }
25
35
  return page;
26
36
  }
@@ -31,10 +31,17 @@ export function titleToBreadcrumb(title) {
31
31
  export function entriesToTree(entries, everythingTitle) {
32
32
  const tree = createEmptyEntryTreeRoot(everythingTitle);
33
33
  entries.forEach((newEntry) => {
34
+ /**
35
+ * The type for newEntry does not include Error but if errors occur during entry definition
36
+ * they will be replaced with errors.
37
+ */
38
+ if (newEntry instanceof Error) {
39
+ throw newEntry;
40
+ }
34
41
  const immediateParent = traverseToImmediateParent(newEntry, tree);
35
42
  const breadcrumb = titleToBreadcrumb(newEntry.title);
36
43
  if (breadcrumb in immediateParent.children) {
37
- throw new Error(`Cannot create duplicate entry '${breadcrumb}' in parent '${immediateParent.breadcrumb}'.`);
44
+ throw new Error(`Cannot create duplicate entry '${breadcrumb}'${immediateParent.breadcrumb ? ` in parent '${immediateParent.breadcrumb}'.` : ''}`);
38
45
  }
39
46
  const newNode = {
40
47
  [markerKeyName]: true,
@@ -1,2 +1,2 @@
1
1
  import { ElementBookRouter } from './element-book-routing';
2
- export declare function createElementBookRouter(baseRoute: string): ElementBookRouter;
2
+ export declare function createElementBookRouter(baseRoute: string | undefined): ElementBookRouter;
@@ -9,5 +9,7 @@ export declare const ElementBookApp: import("element-vir").DeclarativeElementDef
9
9
  currentRoute: Readonly<Required<Readonly<import("spa-router-vir").FullRoute<import("../../../routing/element-book-routing").ValidElementBookPaths, undefined, undefined>>>>;
10
10
  router: Readonly<import("spa-router-vir").SpaRouter<import("../../../routing/element-book-routing").ValidElementBookPaths, undefined, undefined>> | undefined;
11
11
  colors: ColorThemeState;
12
- }, {}, "", "", import("lit-html").HTMLTemplateResult>;
12
+ }, {
13
+ pathUpdate: import("element-vir").DefinedTypedEventNameDefinition<readonly string[]>;
14
+ }, "", "", import("lit-html").HTMLTemplateResult>;
13
15
  export {};
@@ -1,6 +1,6 @@
1
1
  import { areJsonEqual, extractErrorMessage, getOrSetFromMap } from '@augment-vir/common';
2
- import { assign, css, defineElement, html, listen } from 'element-vir';
3
- import { entriesToTree, findEntryByBreadcrumbs, listBreadcrumbs, } from '../../../data/element-book-entry/entry-tree/entry-tree';
2
+ import { assign, css, defineElement, defineElementEvent, html, listen } from 'element-vir';
3
+ import { entriesToTree, findEntryByBreadcrumbs, } from '../../../data/element-book-entry/entry-tree/entry-tree';
4
4
  import { createElementBookRouter } from '../../../routing/create-element-book-router';
5
5
  import { ElementBookMainRoute, defaultElementBookFullRoute, } from '../../../routing/element-book-routing';
6
6
  import { colorThemeCssVars, setThemeCssVars } from '../../color-theme/color-theme';
@@ -11,6 +11,9 @@ import { ElementBookEntryDisplay } from '../entry-display/element-book-entry-dis
11
11
  const treeCache = new Map();
12
12
  export const ElementBookApp = defineElement()({
13
13
  tagName: 'element-book-app',
14
+ events: {
15
+ pathUpdate: defineElementEvent(),
16
+ },
14
17
  stateInit: {
15
18
  currentRoute: defaultElementBookFullRoute,
16
19
  router: undefined,
@@ -56,32 +59,8 @@ export const ElementBookApp = defineElement()({
56
59
  top: 0;
57
60
  }
58
61
  `,
59
- initCallback({ updateState, state, inputs, host }) {
60
- if (inputs.baseUrl && !state.router) {
61
- const router = createElementBookRouter(inputs.baseUrl);
62
- updateState({ router });
63
- router.addRouteListener(true, (fullRoute) => {
64
- updateState({
65
- currentRoute: fullRoute,
66
- });
67
- });
68
- }
69
- },
70
- renderCallback: ({ state, inputs, host, updateState }) => {
62
+ renderCallback: ({ state, inputs, host, updateState, dispatch, events }) => {
71
63
  try {
72
- const inputThemeConfig = {
73
- themeColor: inputs.themeColor,
74
- };
75
- if (!areJsonEqual(inputThemeConfig, state.colors?.config)) {
76
- const newTheme = createTheme(inputThemeConfig);
77
- updateState({
78
- colors: {
79
- config: inputThemeConfig,
80
- theme: newTheme,
81
- },
82
- });
83
- setThemeCssVars(host, newTheme);
84
- }
85
64
  function updateRoutes(newRoute) {
86
65
  if (state.router) {
87
66
  state.router.setRoutes(newRoute);
@@ -94,29 +73,45 @@ export const ElementBookApp = defineElement()({
94
73
  },
95
74
  });
96
75
  }
76
+ dispatch(new events.pathUpdate(newRoute.paths ?? []));
77
+ }
78
+ if (inputs.internalRouterConfig?.useInternalRouter && !state.router) {
79
+ const router = createElementBookRouter(inputs.internalRouterConfig.basePath);
80
+ updateState({ router });
81
+ router.addRouteListener(true, (fullRoute) => {
82
+ updateState({
83
+ currentRoute: fullRoute,
84
+ });
85
+ });
86
+ }
87
+ else if (!inputs.internalRouterConfig?.useInternalRouter && state.router) {
88
+ state.router.removeAllRouteListeners();
89
+ }
90
+ const inputThemeConfig = {
91
+ themeColor: inputs.themeColor,
92
+ };
93
+ if (!areJsonEqual(inputThemeConfig, state.colors?.config)) {
94
+ const newTheme = createTheme(inputThemeConfig);
95
+ updateState({
96
+ colors: {
97
+ config: inputThemeConfig,
98
+ theme: newTheme,
99
+ },
100
+ });
101
+ setThemeCssVars(host, newTheme);
97
102
  }
98
103
  const entriesTree = getOrSetFromMap(treeCache, inputs.entries, () => entriesToTree(inputs.entries, inputs.everythingTitle));
99
- const initialNode = findEntryByBreadcrumbs(state.currentRoute.paths.slice(1), entriesTree);
100
- if (!initialNode) {
101
- const firstChild = Object.values(entriesTree.children)[0];
102
- if (!firstChild) {
103
- throw new Error(`No entries exist.`);
104
- }
105
- const firstEntryBreadcrumbs = listBreadcrumbs(firstChild.entry, true);
106
- const defaultPath = inputs.defaultPath ??
107
- (firstEntryBreadcrumbs.length ? firstEntryBreadcrumbs : undefined);
108
- if (defaultPath && defaultPath.length) {
109
- const newRoute = {
110
- paths: [
111
- ElementBookMainRoute.Book,
112
- ...defaultPath,
113
- ],
114
- };
115
- updateRoutes(newRoute);
116
- }
104
+ const entryTreeNodeByInitialPath = findEntryByBreadcrumbs(state.currentRoute.paths.slice(1), entriesTree);
105
+ if (!entryTreeNodeByInitialPath) {
106
+ const newRoute = {
107
+ paths: [
108
+ ElementBookMainRoute.Book,
109
+ ],
110
+ };
111
+ updateRoutes(newRoute);
117
112
  }
118
- const currentNode = findEntryByBreadcrumbs(state.currentRoute.paths.slice(1), entriesTree);
119
- if (!currentNode) {
113
+ const currentEntryTreeNode = findEntryByBreadcrumbs(state.currentRoute.paths.slice(1), entriesTree);
114
+ if (!currentEntryTreeNode) {
120
115
  throw new Error(`Tried to self-correct for invalid path ${state.currentRoute.paths.join('/')}
121
116
  but failed to do so.`);
122
117
  }
@@ -146,7 +141,7 @@ export const ElementBookApp = defineElement()({
146
141
  <${ElementBookEntryDisplay}
147
142
  ${assign(ElementBookEntryDisplay, {
148
143
  currentRoute: state.currentRoute,
149
- currentNode,
144
+ currentNode: currentEntryTreeNode,
150
145
  router: state.router,
151
146
  })}
152
147
  ></${ElementBookEntryDisplay}>
@@ -154,6 +149,7 @@ export const ElementBookApp = defineElement()({
154
149
  `;
155
150
  }
156
151
  catch (error) {
152
+ console.error(error);
157
153
  return html `
158
154
  <p class="error">${extractErrorMessage(error)}</p>
159
155
  `;
@@ -1,20 +1,36 @@
1
1
  import { PartialAndUndefined } from '@augment-vir/common';
2
+ import { RequireExactlyOne } from 'type-fest';
2
3
  import { ElementBookEntry } from '../../../data/element-book-entry/element-book-entry';
3
4
  export type ElementBookConfig = {
4
5
  /** All element-book entries in order. */
5
6
  entries: ReadonlyArray<ElementBookEntry>;
6
7
  } & PartialAndUndefined<OptionalConfig>;
7
8
  type OptionalConfig = {
8
- /**
9
- * Path to this page, used for routing. For example, if this page is hosted at
10
- * www.example.org/my-page then this value should be `my-page`.
11
- */
12
- baseUrl: string;
13
- /** Starting /book/ path. */
14
- defaultPath: ReadonlyArray<string>;
15
- /** Color from which to base all element-book colors from. */
9
+ /** The base theme color from which all other element-book colors will be generated from. */
16
10
  themeColor: string;
17
11
  /** The title to use for the "Everything" nav link. */
18
12
  everythingTitle: string;
19
- };
13
+ } & RequireExactlyOne<{
14
+ /**
15
+ * Set this internal router config if element-book is intended to be the current website's
16
+ * entire web app. Meaning, you're not embedded element-book within another website.
17
+ *
18
+ * In this case, element-book will create its own internal router for managing the URL. In other
19
+ * cases, when element-book is embedded in another website, use the elementBookRoutePaths input
20
+ * property instead.
21
+ */
22
+ internalRouterConfig: {
23
+ useInternalRouter: true;
24
+ /**
25
+ * Path to this page, used for routing. For example, if this page is hosted at
26
+ * www.example.org/my-page then this value should be `my-page`.
27
+ */
28
+ basePath?: string;
29
+ };
30
+ /**
31
+ * Current route paths for element-book to handle. These are intended to come from a router
32
+ * that's external to the element-book app itself.
33
+ */
34
+ elementBookRoutePaths: ReadonlyArray<string>;
35
+ }>;
20
36
  export {};
@@ -59,6 +59,7 @@ export const ElementBookNav = defineElementBookElement()({
59
59
  display: inline-flex;
60
60
  gap: 8px;
61
61
  align-items: center;
62
+ font-size: 0.75em;
62
63
  }
63
64
 
64
65
  ${VirIcon} {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "element-book",
3
- "version": "0.0.13",
3
+ "version": "1.0.0",
4
4
  "keywords": [
5
5
  "book",
6
6
  "design system",
@@ -41,9 +41,9 @@
41
41
  "@augment-vir/common": "^13.4.0",
42
42
  "@electrovir/icon-element": "^0.0.2",
43
43
  "colorjs.io": "^0.4.3",
44
- "element-vir": "^12.4.4",
44
+ "element-vir": "^12.4.6",
45
45
  "lit-css-vars": "^2.0.2",
46
- "spa-router-vir": "^2.0.3",
46
+ "spa-router-vir": "^2.1.0",
47
47
  "typed-event-target": "^1.2.0"
48
48
  },
49
49
  "devDependencies": {