element-book 0.0.3 → 0.0.4

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 (29) hide show
  1. package/dist/augments/type.d.ts +3 -0
  2. package/dist/augments/type.js +1 -0
  3. package/dist/data/element-book-entry/element-book-page/element-book-page.js +2 -2
  4. package/dist/data/element-book-entry/entry-tree/entry-tree.d.ts +4 -3
  5. package/dist/data/element-book-entry/entry-tree/entry-tree.js +29 -22
  6. package/dist/data/element-book-entry/entry-tree/walk-entry-tree.js +2 -2
  7. package/dist/ui/color-theme/color-theme.d.ts +25 -0
  8. package/dist/ui/color-theme/color-theme.js +70 -0
  9. package/dist/ui/color-theme/create-color-theme.d.ts +3 -0
  10. package/dist/ui/color-theme/create-color-theme.js +42 -0
  11. package/dist/ui/elements/common/element-book-route-link.element.d.ts +1 -1
  12. package/dist/ui/elements/common/element-book-route-link.element.js +1 -0
  13. package/dist/ui/elements/define-book-element.d.ts +1 -1
  14. package/dist/ui/elements/element-book-app.element.d.ts +11 -3
  15. package/dist/ui/elements/element-book-app.element.js +48 -7
  16. package/dist/ui/elements/element-book-breadcrumbs.element.d.ts +1 -1
  17. package/dist/ui/elements/element-book-nav.element.d.ts +1 -1
  18. package/dist/ui/elements/element-book-nav.element.js +43 -6
  19. package/dist/ui/elements/entry-display/element-book-entry-display.element.d.ts +3 -3
  20. package/dist/ui/elements/entry-display/element-book-entry-display.element.js +47 -39
  21. package/dist/ui/elements/entry-display/element-book-example-controls.element.d.ts +1 -1
  22. package/dist/ui/elements/entry-display/element-book-example-viewer.element.d.ts +1 -1
  23. package/dist/ui/elements/entry-display/element-book-page-examples.element.d.ts +5 -0
  24. package/dist/ui/elements/entry-display/element-book-page-examples.element.js +46 -0
  25. package/dist/ui/icons/element-16.icon.d.ts +1 -0
  26. package/dist/ui/icons/element-16.icon.js +18 -0
  27. package/dist/ui/icons/element-24.icon.d.ts +1 -0
  28. package/dist/ui/icons/element-24.icon.js +18 -0
  29. package/package.json +1 -1
@@ -0,0 +1,3 @@
1
+ export type NestedType<SubType> = {
2
+ [prop: PropertyKey]: SubType | NestedType<SubType>;
3
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -1,5 +1,5 @@
1
1
  import { ElementBookEntryTypeEnum } from '../element-book-entry-type';
2
- import { listTitleBreadcrumbs } from '../entry-tree/entry-tree';
2
+ import { listBreadcrumbs } from '../entry-tree/entry-tree';
3
3
  export function defineElementBookPage(pageSetup) {
4
4
  if (!pageSetup.title) {
5
5
  throw new Error(`Cannot have an element-book page with an empty title.`);
@@ -8,7 +8,7 @@ export function defineElementBookPage(pageSetup) {
8
8
  type: ElementBookEntryTypeEnum.Page,
9
9
  ...pageSetup,
10
10
  };
11
- const pageBreadcrumbs = listTitleBreadcrumbs(page, true);
11
+ const pageBreadcrumbs = listBreadcrumbs(page, true);
12
12
  const exampleTitlesSet = new Set();
13
13
  pageSetup.examples.forEach((example) => {
14
14
  const failureMessage = `Failed to define example '${pageBreadcrumbs
@@ -5,10 +5,11 @@ export type EntryTreeNode<EntryType extends ElementBookEntryTypeEnum = ElementBo
5
5
  entry: Extract<ElementBookEntry, {
6
6
  type: EntryType;
7
7
  }>;
8
+ breadcrumb: string;
8
9
  children: Record<string, EntryTreeNode>;
9
10
  };
10
11
  export declare function createEmptyEntryTreeRoot(): EntryTreeNode;
12
+ export declare function titleToBreadcrumb(title: string): string;
11
13
  export declare function entriesToTree(entries: ReadonlyArray<ElementBookEntry>): EntryTreeNode<ElementBookEntryTypeEnum>;
12
- export declare function traverseToImmediateParent(entry: Readonly<ElementBookEntry>, currentTree: Readonly<EntryTreeNode>): Readonly<EntryTreeNode<ElementBookEntryTypeEnum>>;
13
- export declare function listTitleBreadcrumbs(entry: ElementBookEntry, includeSelf?: boolean): string[];
14
- export declare function findEntryByBreadcrumbs(titles: ReadonlyArray<string>, tree: Readonly<EntryTreeNode>): Readonly<EntryTreeNode>;
14
+ export declare function listBreadcrumbs(entry: ElementBookEntry, includeSelf?: boolean): string[];
15
+ export declare function findEntryByBreadcrumbs(breadcrumbs: ReadonlyArray<string>, tree: Readonly<EntryTreeNode>): Readonly<EntryTreeNode> | undefined;
@@ -1,4 +1,4 @@
1
- import { isLengthAtLeast } from '@augment-vir/common';
1
+ import { collapseWhiteSpace, isLengthAtLeast } from '@augment-vir/common';
2
2
  import { ElementBookEntryTypeEnum } from '../element-book-entry-type';
3
3
  export function doesNodeHaveEntryType(node, type) {
4
4
  return node.entry.type === type;
@@ -10,60 +10,67 @@ export function createEmptyEntryTreeRoot() {
10
10
  title: 'element book tree root',
11
11
  parent: undefined,
12
12
  },
13
+ breadcrumb: '',
13
14
  children: {},
14
15
  };
15
16
  return rootNode;
16
17
  }
18
+ export function titleToBreadcrumb(title) {
19
+ return collapseWhiteSpace(title).replaceAll(/\s/g, '-');
20
+ }
17
21
  export function entriesToTree(entries) {
18
22
  const tree = createEmptyEntryTreeRoot();
19
23
  entries.forEach((newEntry) => {
20
24
  const immediateParent = traverseToImmediateParent(newEntry, tree);
21
- if (newEntry.title in immediateParent.children) {
22
- throw new Error(`Cannot create duplicate entry titled '${newEntry.title}' in parent '${immediateParent.entry.title}'.`);
25
+ const breadcrumb = titleToBreadcrumb(newEntry.title);
26
+ if (breadcrumb in immediateParent.children) {
27
+ throw new Error(`Cannot create duplicate entry '${breadcrumb}' in parent '${immediateParent.breadcrumb}'.`);
23
28
  }
24
29
  const newNode = {
25
30
  children: {},
31
+ breadcrumb,
26
32
  entry: newEntry,
27
33
  };
28
- immediateParent.children[newEntry.title] = newNode;
34
+ immediateParent.children[breadcrumb] = newNode;
29
35
  });
30
36
  return tree;
31
37
  }
32
- export function traverseToImmediateParent(entry, currentTree) {
33
- const topDownAncestryChain = listTitleBreadcrumbs(entry)
38
+ function traverseToImmediateParent(entry, currentTree) {
39
+ const topDownAncestryChain = listBreadcrumbs(entry)
34
40
  // reverse so we get the top most ancestor first in the list
35
41
  .reverse();
36
- const immediateParentNode = topDownAncestryChain.reduce((currentParent, currentTitle) => {
37
- const nextParent = currentParent.children[currentTitle];
38
- if (!nextParent) {
39
- throw new Error(`Failed to find parent ElementBookEntry by name of '${currentTitle}' in entry '${currentParent.entry.title}'`);
42
+ const immediateParentNode = topDownAncestryChain.reduce((currentAncestor, nextBreadcrumb) => {
43
+ const nextAncestor = currentAncestor.children[nextBreadcrumb];
44
+ if (!nextAncestor) {
45
+ throw new Error(`Failed to find parent ElementBookEntry by name of '${nextBreadcrumb}' in entry '${currentAncestor.entry.title}'`);
40
46
  }
41
- return nextParent;
47
+ return nextAncestor;
42
48
  }, currentTree);
43
49
  return immediateParentNode;
44
50
  }
45
- export function listTitleBreadcrumbs(entry, includeSelf) {
51
+ export function listBreadcrumbs(entry, includeSelf) {
52
+ const entryBreadcrumb = titleToBreadcrumb(entry.title);
46
53
  if (entry.parent) {
47
54
  return [
48
- entry.parent.title,
49
- ...listTitleBreadcrumbs(entry.parent, false),
50
- ].concat(includeSelf ? [entry.title] : []);
55
+ titleToBreadcrumb(entry.parent.title),
56
+ ...listBreadcrumbs(entry.parent, false),
57
+ ].concat(includeSelf ? [entryBreadcrumb] : []);
51
58
  }
52
59
  else if (includeSelf) {
53
- return [entry.title];
60
+ return [entryBreadcrumb];
54
61
  }
55
62
  else {
56
63
  return [];
57
64
  }
58
65
  }
59
- export function findEntryByBreadcrumbs(titles, tree) {
60
- if (!isLengthAtLeast(titles, 1)) {
66
+ export function findEntryByBreadcrumbs(breadcrumbs, tree) {
67
+ if (!isLengthAtLeast(breadcrumbs, 1)) {
61
68
  return tree;
62
69
  }
63
- const nextEntryTitle = titles[0];
64
- const nextTree = tree.children[nextEntryTitle];
70
+ const nextBreadcrumb = breadcrumbs[0];
71
+ const nextTree = tree.children[nextBreadcrumb];
65
72
  if (!nextTree) {
66
- throw new Error(`Failed to find '${tree.entry.title}' > '${nextEntryTitle}'.`);
73
+ return undefined;
67
74
  }
68
- return findEntryByBreadcrumbs(titles.slice(1), nextTree);
75
+ return findEntryByBreadcrumbs(breadcrumbs.slice(1), nextTree);
69
76
  }
@@ -1,5 +1,5 @@
1
1
  import { ElementBookEntryTypeEnum } from '../element-book-entry-type';
2
- import { doesNodeHaveEntryType, listTitleBreadcrumbs } from './entry-tree';
2
+ import { doesNodeHaveEntryType, listBreadcrumbs } from './entry-tree';
3
3
  export function findFirstPageBreadcrumbs(entryTree) {
4
4
  let pageEntry;
5
5
  walkEntryTree(entryTree, (node) => {
@@ -12,7 +12,7 @@ export function findFirstPageBreadcrumbs(entryTree) {
12
12
  if (!pageEntry) {
13
13
  return [];
14
14
  }
15
- return listTitleBreadcrumbs(pageEntry.entry).concat(pageEntry.entry.title);
15
+ return listBreadcrumbs(pageEntry.entry).concat(pageEntry.breadcrumb);
16
16
  }
17
17
  /**
18
18
  * Walk the whole given tree, calling callback on each node. If callback returns the boolean false
@@ -0,0 +1,25 @@
1
+ import { CSSResult } from 'lit';
2
+ export type ColorPair = {
3
+ background: CSSResult;
4
+ foreground: CSSResult;
5
+ };
6
+ export type ColorTheme = {
7
+ nav: {
8
+ hover: ColorPair;
9
+ active: ColorPair;
10
+ selected: ColorPair;
11
+ };
12
+ accent: {
13
+ icon: CSSResult;
14
+ };
15
+ };
16
+ export declare const colorThemeCssVars: import("lit-css-vars").CssVarDefinitions<{
17
+ 'element-book-nav-hover-background-color': string;
18
+ 'element-book-nav-hover-foreground-color': string;
19
+ 'element-book-nav-active-background-color': string;
20
+ 'element-book-nav-active-foreground-color': string;
21
+ 'element-book-nav-selected-background-color': string;
22
+ 'element-book-nav-selected-foreground-color': string;
23
+ 'element-book-accent-icon-color': string;
24
+ }>;
25
+ export declare function setThemeCssVars(element: HTMLElement, theme: ColorTheme): void;
@@ -0,0 +1,70 @@
1
+ import { isRuntimeTypeOf, typedHasProperties, typedHasProperty } from '@augment-vir/common';
2
+ import { defineCssVars, setCssVarValue } from 'lit-css-vars';
3
+ export const colorThemeCssVars = defineCssVars({
4
+ 'element-book-nav-hover-background-color': 'grey',
5
+ 'element-book-nav-hover-foreground-color': 'grey',
6
+ 'element-book-nav-active-background-color': 'grey',
7
+ 'element-book-nav-active-foreground-color': 'grey',
8
+ 'element-book-nav-selected-background-color': 'grey',
9
+ 'element-book-nav-selected-foreground-color': 'grey',
10
+ 'element-book-accent-icon-color': 'grey',
11
+ });
12
+ const colorThemeCssVarMapping = {
13
+ nav: {
14
+ hover: {
15
+ background: colorThemeCssVars['element-book-nav-hover-background-color'],
16
+ foreground: colorThemeCssVars['element-book-nav-hover-foreground-color'],
17
+ },
18
+ active: {
19
+ background: colorThemeCssVars['element-book-nav-active-background-color'],
20
+ foreground: colorThemeCssVars['element-book-nav-active-foreground-color'],
21
+ },
22
+ selected: {
23
+ background: colorThemeCssVars['element-book-nav-selected-background-color'],
24
+ foreground: colorThemeCssVars['element-book-nav-selected-foreground-color'],
25
+ },
26
+ },
27
+ accent: {
28
+ icon: colorThemeCssVars['element-book-accent-icon-color'],
29
+ },
30
+ };
31
+ export function setThemeCssVars(element, theme) {
32
+ recursiveSetThemeCssVars(element, theme, colorThemeCssVarMapping);
33
+ }
34
+ function isCssResult(input) {
35
+ return typedHasProperty(input, '_$cssResult$');
36
+ }
37
+ function isCssVarDefinition(input) {
38
+ return (typedHasProperties(input, [
39
+ 'name',
40
+ 'value',
41
+ 'default',
42
+ ]) &&
43
+ isRuntimeTypeOf(input.default, 'string') &&
44
+ isCssResult(input.name) &&
45
+ isCssResult(input.value));
46
+ }
47
+ function recursiveSetThemeCssVars(element, nestedCssResult, nestedCssVars) {
48
+ Object.entries(nestedCssResult).forEach(([key, value,]) => {
49
+ const nestedCssVar = nestedCssVars[key];
50
+ if (!nestedCssVar) {
51
+ throw new Error(`no nestedCssVar at key '${key}'`);
52
+ }
53
+ if (isCssResult(value)) {
54
+ if (!isCssVarDefinition(nestedCssVar)) {
55
+ throw new Error(`got a CSS result at '${key}' but no CSS var`);
56
+ }
57
+ setCssVarValue({
58
+ forCssVar: nestedCssVar,
59
+ onElement: element,
60
+ toValue: String(value),
61
+ });
62
+ }
63
+ else {
64
+ if (isCssVarDefinition(nestedCssVar)) {
65
+ throw new Error(`got no CSS result at '${key}' but did find a CSS var`);
66
+ }
67
+ recursiveSetThemeCssVars(element, value, nestedCssVar);
68
+ }
69
+ });
70
+ }
@@ -0,0 +1,3 @@
1
+ import { ColorTheme } from './color-theme';
2
+ export declare const defaultThemeStartColor = "dodgerblue";
3
+ export declare function createTheme(startColorString?: string): ColorTheme;
@@ -0,0 +1,42 @@
1
+ import { mapObjectValues } from '@augment-vir/common';
2
+ import Color from 'colorjs.io';
3
+ import { unsafeCSS } from 'lit';
4
+ function colorsObjectToCssResult(colors) {
5
+ return mapObjectValues(colors, (key, value) => {
6
+ if (value instanceof Color) {
7
+ return unsafeCSS(value.toString({ format: 'hex' }));
8
+ }
9
+ else {
10
+ return colorsObjectToCssResult(value);
11
+ }
12
+ });
13
+ }
14
+ export const defaultThemeStartColor = 'dodgerblue';
15
+ function calculateTextColor(color) {
16
+ const onWhite = Math.abs(color.contrast('white', 'APCA'));
17
+ const onBlack = Math.abs(color.contrast('black', 'APCA'));
18
+ const textColorString = onWhite > onBlack ? 'white' : 'black';
19
+ return new Color(textColorString);
20
+ }
21
+ function createColorPair({ background, foreground, }) {
22
+ return {
23
+ background: background ?? calculateTextColor(foreground),
24
+ foreground: foreground ?? calculateTextColor(background),
25
+ };
26
+ }
27
+ export function createTheme(startColorString = defaultThemeStartColor) {
28
+ // as cast because colorjs.io's types for itself are wrong
29
+ const original = new Color(startColorString);
30
+ const colors = {
31
+ nav: {
32
+ hover: createColorPair({ background: original.clone().set({ 'hsl.l': 93 }) }),
33
+ active: createColorPair({ background: original.clone().set({ 'hsl.l': 90 }) }),
34
+ selected: createColorPair({ background: original.clone().set({ 'hsl.l': 85 }) }),
35
+ },
36
+ accent: {
37
+ icon: original.clone().set({ 'hsl.l': 40 }),
38
+ },
39
+ };
40
+ const convertedToCssResults = colorsObjectToCssResult(colors);
41
+ return convertedToCssResults;
42
+ }
@@ -2,4 +2,4 @@ import { ElementBookFullRoute, ElementBookRouter } from '../../../routing/elemen
2
2
  export declare const ElementBookRouteLink: import("element-vir").DeclarativeElementDefinition<"element-book-route-link", {
3
3
  route: Partial<ElementBookFullRoute>;
4
4
  router: ElementBookRouter | undefined;
5
- }, {}, {}, string, "anchorPadding", import("lit-html").HTMLTemplateResult>;
5
+ }, {}, {}, string, "anchorPadding", import("lit-html").HTMLTemplateResult, undefined>;
@@ -9,6 +9,7 @@ export const ElementBookRouteLink = defineElementBookElement()({
9
9
  },
10
10
  styles: ({ cssVarValues }) => css `
11
11
  a {
12
+ box-sizing: border-box;
12
13
  display: block;
13
14
  padding: ${cssVarValues.anchorPadding};
14
15
  text-decoration: inherit;
@@ -1,2 +1,2 @@
1
1
  export type BookTagName = `element-book-${string}`;
2
- export declare const defineElementBookElement: <InputsGeneric extends {}>() => <TagNameGeneric extends `element-book-${string}`, StateInitGeneric extends {}, EventsInitGeneric extends {}, HostClassKeysGeneric extends string, CssVarKeysGeneric extends string, RenderOutputGeneric extends any>(inputs: import("element-vir").DeclarativeElementInit<TagNameGeneric, InputsGeneric, StateInitGeneric, EventsInitGeneric, HostClassKeysGeneric, CssVarKeysGeneric, RenderOutputGeneric>) => import("element-vir").DeclarativeElementDefinition<TagNameGeneric, InputsGeneric, StateInitGeneric, EventsInitGeneric, HostClassKeysGeneric, CssVarKeysGeneric, RenderOutputGeneric>, defineElementBookElementNoInputs: <TagNameGeneric_1 extends `element-book-${string}`, InputsGeneric_1 extends {}, StateInitGeneric_1 extends {}, EventsInitGeneric_1 extends {}, HostClassKeysGeneric_1 extends string, CssVarKeysGeneric_1 extends string, RenderOutputGeneric_1 extends any>(inputs: import("element-vir").DeclarativeElementInit<TagNameGeneric_1, InputsGeneric_1, StateInitGeneric_1, EventsInitGeneric_1, HostClassKeysGeneric_1, CssVarKeysGeneric_1, RenderOutputGeneric_1>) => import("element-vir").DeclarativeElementDefinition<TagNameGeneric_1, InputsGeneric_1, StateInitGeneric_1, EventsInitGeneric_1, HostClassKeysGeneric_1, CssVarKeysGeneric_1, RenderOutputGeneric_1>;
2
+ export declare const defineElementBookElement: <InputsGeneric extends {}>() => <TagNameGeneric extends `element-book-${string}`, StateInitGeneric extends {}, EventsInitGeneric extends {}, HostClassKeysGeneric extends string, CssVarKeysGeneric extends string, RenderOutputGeneric extends any, InputsDefinerFunctionGeneric extends undefined>(inputs: import("element-vir").DeclarativeElementInit<TagNameGeneric, InputsGeneric, StateInitGeneric, EventsInitGeneric, HostClassKeysGeneric, CssVarKeysGeneric, RenderOutputGeneric, InputsDefinerFunctionGeneric>) => import("element-vir").DeclarativeElementDefinition<TagNameGeneric, InputsGeneric, StateInitGeneric, EventsInitGeneric, HostClassKeysGeneric, CssVarKeysGeneric, RenderOutputGeneric, undefined>, defineElementBookElementNoInputs: <TagNameGeneric_1 extends `element-book-${string}`, InputsGeneric_1 extends {}, StateInitGeneric_1 extends {}, EventsInitGeneric_1 extends {}, HostClassKeysGeneric_1 extends string, CssVarKeysGeneric_1 extends string, RenderOutputGeneric_1 extends any, InputsDefinerFunctionGeneric_1 extends undefined>(inputs: import("element-vir").DeclarativeElementInit<TagNameGeneric_1, InputsGeneric_1, StateInitGeneric_1, EventsInitGeneric_1, HostClassKeysGeneric_1, CssVarKeysGeneric_1, RenderOutputGeneric_1, InputsDefinerFunctionGeneric_1>) => import("element-vir").DeclarativeElementDefinition<TagNameGeneric_1, InputsGeneric_1, StateInitGeneric_1, EventsInitGeneric_1, HostClassKeysGeneric_1, CssVarKeysGeneric_1, RenderOutputGeneric_1, InputsDefinerFunctionGeneric_1>;
@@ -1,9 +1,17 @@
1
1
  import { ElementBookEntry } from '../../data/element-book-entry/element-book-entry';
2
+ import { ColorTheme } from '../color-theme/color-theme';
3
+ type ColorThemeState = {
4
+ original: string | undefined;
5
+ theme: ColorTheme;
6
+ };
2
7
  export declare const ElementBookApp: import("element-vir").DeclarativeElementDefinition<"element-book-app", {
3
8
  entries: ReadonlyArray<ElementBookEntry>;
4
- baseRoute?: string;
5
- defaultPath?: ReadonlyArray<string>;
9
+ baseRoute?: string | undefined;
10
+ defaultPath?: ReadonlyArray<string> | undefined;
11
+ themeColor?: string | undefined;
6
12
  }, {
7
13
  currentRoute: Readonly<Required<Readonly<import("spa-router-vir").FullRoute<import("../../routing/element-book-routing").ValidElementBookPaths, undefined, undefined>>>>;
8
14
  router: Readonly<import("spa-router-vir").SpaRouter<import("../../routing/element-book-routing").ValidElementBookPaths, undefined, undefined>> | undefined;
9
- }, {}, "", "", import("lit-html").HTMLTemplateResult>;
15
+ colors: ColorThemeState;
16
+ }, {}, "", "", import("lit-html").HTMLTemplateResult, undefined>;
17
+ export {};
@@ -3,6 +3,8 @@ import { entriesToTree, findEntryByBreadcrumbs, } from '../../data/element-book-
3
3
  import { findFirstPageBreadcrumbs } from '../../data/element-book-entry/entry-tree/walk-entry-tree';
4
4
  import { createElementBookRouter } from '../../routing/create-element-book-router';
5
5
  import { emptyElementBookFullRoute, } from '../../routing/element-book-routing';
6
+ import { setThemeCssVars } from '../color-theme/color-theme';
7
+ import { createTheme } from '../color-theme/create-color-theme';
6
8
  import { ChangeRouteEvent } from '../events/change-route.event';
7
9
  import { ElementBookNav } from './element-book-nav.element';
8
10
  import { ElementBookEntryDisplay } from './entry-display/element-book-entry-display.element';
@@ -11,6 +13,10 @@ export const ElementBookApp = defineElement()({
11
13
  stateInit: {
12
14
  currentRoute: emptyElementBookFullRoute,
13
15
  router: undefined,
16
+ colors: {
17
+ original: undefined,
18
+ theme: createTheme(undefined),
19
+ },
14
20
  },
15
21
  styles: css `
16
22
  :host {
@@ -20,17 +26,33 @@ export const ElementBookApp = defineElement()({
20
26
  font-family: sans-serif;
21
27
  }
22
28
 
29
+ .error {
30
+ color: red;
31
+ }
32
+
23
33
  .root {
24
- height: 100%;
25
- width: 100%;
34
+ max-height: 100%;
35
+ max-width: 100%;
26
36
  display: flex;
37
+ position: relative;
27
38
  }
28
39
 
29
40
  ${ElementBookEntryDisplay} {
30
41
  flex-grow: 1;
42
+ overflow-x: hidden;
43
+ overflow-y: auto;
44
+ max-height: 100%;
45
+ }
46
+
47
+ ${ElementBookNav} {
48
+ position: sticky;
49
+ overflow-x: hidden;
50
+ overflow-y: auto;
51
+ max-height: 100%;
52
+ top: 0;
31
53
  }
32
54
  `,
33
- initCallback({ updateState, state, inputs }) {
55
+ initCallback({ updateState, state, inputs, host }) {
34
56
  if (inputs.baseRoute && !state.router) {
35
57
  const router = createElementBookRouter(inputs.baseRoute);
36
58
  updateState({ router });
@@ -40,8 +62,19 @@ export const ElementBookApp = defineElement()({
40
62
  });
41
63
  });
42
64
  }
65
+ setThemeCssVars(host, state.colors.theme);
43
66
  },
44
- renderCallback: ({ state, inputs, updateState }) => {
67
+ renderCallback: ({ state, inputs, host, updateState }) => {
68
+ if (inputs.themeColor !== state.colors?.original) {
69
+ const newTheme = createTheme(inputs.themeColor);
70
+ updateState({
71
+ colors: {
72
+ original: inputs.themeColor,
73
+ theme: newTheme,
74
+ },
75
+ });
76
+ setThemeCssVars(host, newTheme);
77
+ }
45
78
  function updateRoutes(newRoute) {
46
79
  if (state.router) {
47
80
  state.router.setRoutes(newRoute);
@@ -56,7 +89,7 @@ export const ElementBookApp = defineElement()({
56
89
  }
57
90
  }
58
91
  const entriesTree = entriesToTree(inputs.entries);
59
- if (!state.currentRoute.paths.length) {
92
+ if (!findEntryByBreadcrumbs(state.currentRoute.paths, entriesTree)) {
60
93
  const firstPageBreadcrumbs = findFirstPageBreadcrumbs(entriesTree);
61
94
  const defaultPath = inputs.defaultPath ??
62
95
  (firstPageBreadcrumbs.length ? firstPageBreadcrumbs : undefined);
@@ -67,7 +100,15 @@ export const ElementBookApp = defineElement()({
67
100
  updateRoutes(newRoute);
68
101
  }
69
102
  }
70
- const currentEntry = findEntryByBreadcrumbs(state.currentRoute.paths, entriesTree).entry;
103
+ const currentNode = findEntryByBreadcrumbs(state.currentRoute.paths, entriesTree);
104
+ if (!currentNode) {
105
+ return html `
106
+ <p class="error">
107
+ Tried to self-correct for invalid path ${state.currentRoute.paths.join('/')} but
108
+ failed to do so.
109
+ </p>
110
+ `;
111
+ }
71
112
  return html `
72
113
  <div
73
114
  class="root"
@@ -85,7 +126,7 @@ export const ElementBookApp = defineElement()({
85
126
  <${ElementBookEntryDisplay}
86
127
  ${assign(ElementBookEntryDisplay, {
87
128
  currentRoute: state.currentRoute,
88
- currentEntry,
129
+ currentNode,
89
130
  })}
90
131
  ></${ElementBookEntryDisplay}>
91
132
  </div>
@@ -1,4 +1,4 @@
1
1
  import { ElementBookFullRoute } from '../../routing/element-book-routing';
2
2
  export declare const ElementBookBreadcrumbs: import("element-vir").DeclarativeElementDefinition<"element-book-breadcrumbs", {
3
3
  currentRoute: Readonly<ElementBookFullRoute>;
4
- }, {}, {}, string, string, import("lit-html").HTMLTemplateResult[]>;
4
+ }, {}, {}, string, string, import("lit-html").HTMLTemplateResult[], undefined>;
@@ -4,4 +4,4 @@ export declare const ElementBookNav: import("element-vir").DeclarativeElementDef
4
4
  tree: EntryTreeNode;
5
5
  selectedPath: ReadonlyArray<string>;
6
6
  router: ElementBookRouter | undefined;
7
- }, {}, {}, string, string, import("lit-html").HTMLTemplateResult>;
7
+ }, {}, {}, string, string, import("lit-html").HTMLTemplateResult, undefined>;
@@ -1,5 +1,9 @@
1
1
  import { areJsonEqual } from '@augment-vir/common';
2
- import { assign, classMap, css, html } from 'element-vir';
2
+ import { VirIcon } from '@electrovir/icon-element';
3
+ import { assign, classMap, css, html, renderIf } from 'element-vir';
4
+ import { ElementBookEntryTypeEnum } from '../../data/element-book-entry/element-book-entry-type';
5
+ import { colorThemeCssVars } from '../color-theme/color-theme';
6
+ import { Element16Icon } from '../icons/element-16.icon';
3
7
  import { ElementBookRouteLink } from './common/element-book-route-link.element';
4
8
  import { defineElementBookElement } from './define-book-element';
5
9
  export const ElementBookNav = defineElementBookElement()({
@@ -13,10 +17,18 @@ export const ElementBookNav = defineElementBookElement()({
13
17
  }
14
18
 
15
19
  .title-row:hover {
16
- background-color: #ccc;
20
+ background-color: ${colorThemeCssVars['element-book-nav-hover-background-color'].value};
21
+ color: ${colorThemeCssVars['element-book-nav-hover-foreground-color'].value};
22
+ }
23
+
24
+ .title-row:active {
25
+ background-color: ${colorThemeCssVars['element-book-nav-active-background-color']
26
+ .value};
27
+ color: ${colorThemeCssVars['element-book-nav-active-foreground-color'].value};
17
28
  }
18
29
 
19
30
  .title-row {
31
+ padding-right: 24px;
20
32
  display: block;
21
33
  ${ElementBookRouteLink.cssVarNames
22
34
  .anchorPadding}: 4px 24px 4px calc(calc(16px * var(--indent, 0)) + 24px);
@@ -32,8 +44,25 @@ export const ElementBookNav = defineElementBookElement()({
32
44
  margin: 0;
33
45
  }
34
46
 
35
- .selected {
36
- background-color: #ddd;
47
+ .selected,
48
+ .selected:hover {
49
+ background-color: ${colorThemeCssVars['element-book-nav-selected-background-color']
50
+ .value};
51
+ color: ${colorThemeCssVars['element-book-nav-selected-foreground-color'].value};
52
+ pointer-events: none;
53
+ }
54
+
55
+ .title-text {
56
+ white-space: nowrap;
57
+ text-overflow: ellipsis;
58
+ display: inline-flex;
59
+ gap: 8px;
60
+ align-items: center;
61
+ }
62
+
63
+ ${VirIcon} {
64
+ display: inline-flex;
65
+ color: ${colorThemeCssVars['element-book-accent-icon-color'].value};
37
66
  }
38
67
  `,
39
68
  renderCallback({ inputs }) {
@@ -56,7 +85,7 @@ function createNavigationTree({ indent, tree, rootPath, selectedPath, router, })
56
85
  return [];
57
86
  }
58
87
  return Object.values(tree.children).map((child) => {
59
- const childPath = rootPath.concat(child.entry.title);
88
+ const childPath = rootPath.concat(child.breadcrumb);
60
89
  const childTemplates = createNavigationTree({
61
90
  indent: indent + 1,
62
91
  tree: child,
@@ -64,6 +93,7 @@ function createNavigationTree({ indent, tree, rootPath, selectedPath, router, })
64
93
  selectedPath,
65
94
  router,
66
95
  });
96
+ const hasExamples = child.entry.type === ElementBookEntryTypeEnum.Page;
67
97
  return html `
68
98
  <div class="nav-tree-entry" style="--indent: ${indent};">
69
99
  <li class=${child.entry.type}>
@@ -79,7 +109,14 @@ function createNavigationTree({ indent, tree, rootPath, selectedPath, router, })
79
109
  selected: areJsonEqual(selectedPath, childPath),
80
110
  })}
81
111
  >
82
- ${child.entry.title}
112
+ <div class="title-text">
113
+ ${renderIf(hasExamples, html `
114
+ <${VirIcon}
115
+ ${assign(VirIcon, { icon: Element16Icon })}
116
+ ></${VirIcon}>
117
+ `)}
118
+ ${child.entry.title}
119
+ </div>
83
120
  </${ElementBookRouteLink}>
84
121
  </li>
85
122
  ${childTemplates}
@@ -1,6 +1,6 @@
1
- import { ElementBookEntry } from '../../../data/element-book-entry/element-book-entry';
1
+ import { EntryTreeNode } from '../../../data/element-book-entry/entry-tree/entry-tree';
2
2
  import { ElementBookFullRoute } from '../../../routing/element-book-routing';
3
3
  export declare const ElementBookEntryDisplay: import("element-vir").DeclarativeElementDefinition<"element-book-entry-display", {
4
4
  currentRoute: Readonly<ElementBookFullRoute>;
5
- currentEntry: Readonly<ElementBookEntry>;
6
- }, {}, {}, string, string, import("lit-html").HTMLTemplateResult>;
5
+ currentNode: Readonly<EntryTreeNode>;
6
+ }, {}, {}, string, string, import("lit-html").HTMLTemplateResult, undefined>;
@@ -1,10 +1,12 @@
1
- import { assign, classMap, css, html, renderIf } from 'element-vir';
1
+ import { VirIcon } from '@electrovir/icon-element';
2
+ import { assign, css, html, renderIf } from 'element-vir';
2
3
  import { ElementBookEntryTypeEnum } from '../../../data/element-book-entry/element-book-entry-type';
3
- import { listTitleBreadcrumbs } from '../../../data/element-book-entry/entry-tree/entry-tree';
4
+ import { listBreadcrumbs, } from '../../../data/element-book-entry/entry-tree/entry-tree';
5
+ import { colorThemeCssVars } from '../../color-theme/color-theme';
6
+ import { Element24Icon } from '../../icons/element-24.icon';
4
7
  import { defineElementBookElement } from '../define-book-element';
5
8
  import { ElementBookBreadcrumbs } from '../element-book-breadcrumbs.element';
6
- import { ElementBookExampleControls } from './element-book-example-controls.element';
7
- import { ElementBookExampleViewer } from './element-book-example-viewer.element';
9
+ import { ElementBookPageExamples } from './element-book-page-examples.element';
8
10
  export const ElementBookEntryDisplay = defineElementBookElement()({
9
11
  tagName: 'element-book-entry-display',
10
12
  styles: css `
@@ -14,62 +16,68 @@ export const ElementBookEntryDisplay = defineElementBookElement()({
14
16
  }
15
17
 
16
18
  .title-bar {
19
+ position: sticky;
20
+ top: 0;
17
21
  border-bottom: 1px solid #f8f8f8;
18
22
  padding: 4px 8px;
23
+ background-color: white;
19
24
  }
20
25
 
21
- .examples-wrapper {
26
+ .all-examples-wrapper {
22
27
  padding: 32px;
23
28
  display: flex;
24
- gap: 32px;
25
- flex-wrap: wrap;
29
+ flex-direction: column;
30
+ gap: 64px;
26
31
  }
27
32
 
28
- .individual-example-wrapper {
33
+ h2 {
34
+ margin: 0;
35
+ margin-bottom: 24px;
36
+ padding: 0;
29
37
  display: flex;
30
- flex-direction: column;
31
- gap: 24px;
38
+ gap: 16px;
39
+ align-items: center;
32
40
  }
33
41
 
34
- .hidden-controls {
35
- pointer-events: none;
36
- visibility: hidden;
42
+ ${VirIcon} {
43
+ color: ${colorThemeCssVars['element-book-accent-icon-color'].value};
37
44
  }
38
45
  `,
39
46
  renderCallback: ({ inputs }) => {
40
- const examples = inputs.currentEntry.type === ElementBookEntryTypeEnum.Page
41
- ? inputs.currentEntry.examples
42
- : [];
43
- const entryBreadcrumbs = listTitleBreadcrumbs(inputs.currentEntry, true);
44
- const exampleTemplates = makeExampleTemplates(examples, entryBreadcrumbs);
47
+ const descendantPages = extractAllDescendantPages(inputs.currentNode);
48
+ const entryBreadcrumbs = listBreadcrumbs(inputs.currentNode.entry, true);
49
+ const showPageTitles = inputs.currentNode.entry.type !== ElementBookEntryTypeEnum.Page;
50
+ const exampleTemplates = descendantPages.map((descendantPage) => {
51
+ return html `
52
+ <div class="page-examples">
53
+ ${renderIf(showPageTitles, html `
54
+ <h2>
55
+ <${VirIcon} ${assign(VirIcon, { icon: Element24Icon })}></${VirIcon}>
56
+ ${descendantPage.title}
57
+ </h2>
58
+ `)}
59
+ <${ElementBookPageExamples}
60
+ ${assign(ElementBookPageExamples, {
61
+ page: descendantPage,
62
+ parentBreadcrumbs: entryBreadcrumbs,
63
+ })}
64
+ ></${ElementBookPageExamples}>
65
+ </div>
66
+ `;
67
+ });
45
68
  return html `
46
69
  <div class="title-bar">
47
70
  <${ElementBookBreadcrumbs}
48
71
  ${assign(ElementBookBreadcrumbs, { currentRoute: inputs.currentRoute })}
49
72
  ></${ElementBookBreadcrumbs}>
50
73
  </div>
51
- <div class="examples-wrapper">${exampleTemplates}</div>
74
+ <div class="all-examples-wrapper">${exampleTemplates}</div>
52
75
  `;
53
76
  },
54
77
  });
55
- function makeExampleTemplates(examples, parentBreadcrumbs) {
56
- const allControlsHidden = examples.every((example) => example.hideControls);
57
- return examples.map((example) => {
58
- return html `
59
- <div class="individual-example-wrapper">
60
- ${renderIf(!allControlsHidden, html `
61
- <${ElementBookExampleControls}
62
- class=${classMap({ 'hidden-controls': !!example.hideControls })}
63
- ${assign(ElementBookExampleControls, { example })}
64
- ></${ElementBookExampleControls}>
65
- `)}
66
- <${ElementBookExampleViewer}
67
- ${assign(ElementBookExampleViewer, {
68
- example,
69
- parentBreadcrumbs,
70
- })}
71
- ></${ElementBookExampleViewer}>
72
- </div>
73
- `;
74
- });
78
+ function extractAllDescendantPages(node) {
79
+ if (node.entry.type === ElementBookEntryTypeEnum.Page) {
80
+ return [node.entry];
81
+ }
82
+ return Object.values(node.children).map(extractAllDescendantPages).flat();
75
83
  }
@@ -1,4 +1,4 @@
1
1
  import { ElementBookPageExample } from '../../../data/element-book-entry/element-book-page/element-book-page-example';
2
2
  export declare const ElementBookExampleControls: import("element-vir").DeclarativeElementDefinition<"element-book-example-controls", {
3
3
  example: ElementBookPageExample;
4
- }, {}, {}, string, string, import("lit-html").HTMLTemplateResult>;
4
+ }, {}, {}, string, string, import("lit-html").HTMLTemplateResult, undefined>;
@@ -4,4 +4,4 @@ export declare const ElementBookExampleViewer: import("element-vir").Declarative
4
4
  parentBreadcrumbs: ReadonlyArray<string>;
5
5
  }, {
6
6
  internalState: any;
7
- }, {}, string, string, string | import("lit-html").HTMLTemplateResult>;
7
+ }, {}, string, string, string | import("lit-html").HTMLTemplateResult, undefined>;
@@ -0,0 +1,5 @@
1
+ import { ElementBookPage } from '../../../data/element-book-entry/element-book-page/element-book-page';
2
+ export declare const ElementBookPageExamples: import("element-vir").DeclarativeElementDefinition<"element-book-page-examples", {
3
+ page: ElementBookPage;
4
+ parentBreadcrumbs: ReadonlyArray<string>;
5
+ }, {}, {}, string, string, import("lit-html").HTMLTemplateResult[], undefined>;
@@ -0,0 +1,46 @@
1
+ import { assign, classMap, css, html, renderIf } from 'element-vir';
2
+ import { defineElementBookElement } from '../define-book-element';
3
+ import { ElementBookExampleControls } from './element-book-example-controls.element';
4
+ import { ElementBookExampleViewer } from './element-book-example-viewer.element';
5
+ export const ElementBookPageExamples = defineElementBookElement()({
6
+ tagName: 'element-book-page-examples',
7
+ styles: css `
8
+ :host {
9
+ display: flex;
10
+ gap: 32px;
11
+ flex-wrap: wrap;
12
+ }
13
+ .individual-example-wrapper {
14
+ display: flex;
15
+ flex-direction: column;
16
+ gap: 24px;
17
+ }
18
+
19
+ .hidden-controls {
20
+ pointer-events: none;
21
+ visibility: hidden;
22
+ }
23
+ `,
24
+ renderCallback({ inputs }) {
25
+ const examples = inputs.page.examples;
26
+ const allControlsHidden = examples.every((example) => example.hideControls);
27
+ return examples.map((example) => {
28
+ return html `
29
+ <div class="individual-example-wrapper">
30
+ ${renderIf(!allControlsHidden, html `
31
+ <${ElementBookExampleControls}
32
+ class=${classMap({ 'hidden-controls': !!example.hideControls })}
33
+ ${assign(ElementBookExampleControls, { example })}
34
+ ></${ElementBookExampleControls}>
35
+ `)}
36
+ <${ElementBookExampleViewer}
37
+ ${assign(ElementBookExampleViewer, {
38
+ example,
39
+ parentBreadcrumbs: inputs.parentBreadcrumbs,
40
+ })}
41
+ ></${ElementBookExampleViewer}>
42
+ </div>
43
+ `;
44
+ });
45
+ },
46
+ });
@@ -0,0 +1 @@
1
+ export declare const Element16Icon: import("@electrovir/icon-element").IconSvg;
@@ -0,0 +1,18 @@
1
+ import { defineIconSvg } from '@electrovir/icon-element';
2
+ import { html } from 'element-vir';
3
+ export const Element16Icon = defineIconSvg({
4
+ name: 'Element16Icon',
5
+ svgTemplate: html `
6
+ <svg
7
+ xmlns="http://www.w3.org/2000/svg"
8
+ xml:space="preserve"
9
+ fill="none"
10
+ width="16"
11
+ height="16"
12
+ viewBox="0 0 16 16"
13
+ stroke-width="1px"
14
+ >
15
+ <path d="M4 5 1 8l3 3m8-6 3 3-3 3m-5 0 2-6" />
16
+ </svg>
17
+ `,
18
+ });
@@ -0,0 +1 @@
1
+ export declare const Element24Icon: import("@electrovir/icon-element").IconSvg;
@@ -0,0 +1,18 @@
1
+ import { defineIconSvg } from '@electrovir/icon-element';
2
+ import { html } from 'element-vir';
3
+ export const Element24Icon = defineIconSvg({
4
+ name: 'Element24Icon',
5
+ svgTemplate: html `
6
+ <svg
7
+ xmlns="http://www.w3.org/2000/svg"
8
+ xml:space="preserve"
9
+ viewBox="0 0 24 24"
10
+ fill="none"
11
+ width="24"
12
+ height="24"
13
+ stroke-width="1px"
14
+ >
15
+ <path d="m7 7-5 5 5 5M17 7l5 5-5 5m-6 0 2-10" />
16
+ </svg>
17
+ `,
18
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "element-book",
3
- "version": "0.0.3",
3
+ "version": "0.0.4",
4
4
  "keywords": [
5
5
  "book",
6
6
  "design system",