element-book 0.0.0 → 0.0.1

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 (41) hide show
  1. package/README.md +16 -2
  2. package/dist/data/element-book-entry/element-book-chapter/element-book-chapter.d.ts +10 -0
  3. package/dist/data/element-book-entry/element-book-chapter/element-book-chapter.js +12 -0
  4. package/dist/data/element-book-entry/element-book-entry-type.d.ts +8 -0
  5. package/dist/data/element-book-entry/element-book-entry-type.js +9 -0
  6. package/dist/data/element-book-entry/element-book-entry.d.ts +9 -0
  7. package/dist/data/element-book-entry/element-book-entry.js +1 -0
  8. package/dist/data/element-book-entry/element-book-page/element-book-page-example.d.ts +22 -0
  9. package/dist/data/element-book-entry/element-book-page/element-book-page-example.js +3 -0
  10. package/dist/data/element-book-entry/element-book-page/element-book-page.d.ts +10 -0
  11. package/dist/data/element-book-entry/element-book-page/element-book-page.js +26 -0
  12. package/dist/data/element-book-entry/entry-tree/entry-tree.d.ts +14 -0
  13. package/dist/data/element-book-entry/entry-tree/entry-tree.js +69 -0
  14. package/dist/data/element-book-entry/entry-tree/walk-entry-tree.d.ts +9 -0
  15. package/dist/data/element-book-entry/entry-tree/walk-entry-tree.js +101 -0
  16. package/dist/index.d.ts +4 -0
  17. package/dist/index.js +4 -0
  18. package/dist/routing/create-element-book-router.d.ts +2 -0
  19. package/dist/routing/create-element-book-router.js +6 -0
  20. package/dist/routing/element-book-routing.d.ts +5 -0
  21. package/dist/routing/element-book-routing.js +5 -0
  22. package/dist/ui/elements/common/element-book-route-link.element.d.ts +5 -0
  23. package/dist/ui/elements/common/element-book-route-link.element.js +40 -0
  24. package/dist/ui/elements/define-book-element.d.ts +2 -0
  25. package/dist/ui/elements/define-book-element.js +2 -0
  26. package/dist/ui/elements/element-book-app.element.d.ts +9 -0
  27. package/dist/ui/elements/element-book-app.element.js +94 -0
  28. package/dist/ui/elements/element-book-breadcrumbs.element.d.ts +4 -0
  29. package/dist/ui/elements/element-book-breadcrumbs.element.js +24 -0
  30. package/dist/ui/elements/element-book-nav.element.d.ts +7 -0
  31. package/dist/ui/elements/element-book-nav.element.js +89 -0
  32. package/dist/ui/elements/entry-display/element-book-entry-display.element.d.ts +6 -0
  33. package/dist/ui/elements/entry-display/element-book-entry-display.element.js +75 -0
  34. package/dist/ui/elements/entry-display/element-book-example-controls.element.d.ts +4 -0
  35. package/dist/ui/elements/entry-display/element-book-example-controls.element.js +25 -0
  36. package/dist/ui/elements/entry-display/element-book-example-viewer.element.d.ts +7 -0
  37. package/dist/ui/elements/entry-display/element-book-example-viewer.element.js +41 -0
  38. package/dist/ui/events/change-route.event.d.ts +1 -0
  39. package/dist/ui/events/change-route.event.js +2 -0
  40. package/package.json +37 -24
  41. package/LICENSE +0 -21
package/README.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # element-book
2
2
 
3
- Because Storybook is madness and uncomposable.
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).
4
4
 
5
- WIP
5
+ Currently in MVP form. Functional, but WIP.
6
+
7
+ # Installation
8
+
9
+ ```bash
10
+ npm i element-book
11
+ ```
12
+
13
+ # Terminology
14
+
15
+ - **section**:
16
+
17
+ # Why not Storybook?
18
+
19
+ 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.
@@ -0,0 +1,10 @@
1
+ import { ElementBookEntryTypeEnum } from '../element-book-entry-type';
2
+ export type ElementBookChapter = {
3
+ type: ElementBookEntryTypeEnum.Chapter;
4
+ title: string;
5
+ parent?: ElementBookChapter | undefined;
6
+ };
7
+ export declare function defineElementBookChapter({ title, parent, }: {
8
+ title: string;
9
+ parent?: ElementBookChapter['parent'];
10
+ }): ElementBookChapter;
@@ -0,0 +1,12 @@
1
+ import { ElementBookEntryTypeEnum } from '../element-book-entry-type';
2
+ export function defineElementBookChapter({ title, parent, }) {
3
+ if (!title) {
4
+ throw new Error(`Cannot have an element-book chapter with an empty title.`);
5
+ }
6
+ const chapter = {
7
+ type: ElementBookEntryTypeEnum.Chapter,
8
+ title,
9
+ parent,
10
+ };
11
+ return chapter;
12
+ }
@@ -0,0 +1,8 @@
1
+ export declare enum ElementBookEntryTypeEnum {
2
+ /** A group of pages or sub-chapters. */
3
+ Chapter = "chapter",
4
+ /** An individual book page full of element examples. */
5
+ Page = "page",
6
+ /** Tree root. Not for external use. */
7
+ Root = "root"
8
+ }
@@ -0,0 +1,9 @@
1
+ export var ElementBookEntryTypeEnum;
2
+ (function (ElementBookEntryTypeEnum) {
3
+ /** A group of pages or sub-chapters. */
4
+ ElementBookEntryTypeEnum["Chapter"] = "chapter";
5
+ /** An individual book page full of element examples. */
6
+ ElementBookEntryTypeEnum["Page"] = "page";
7
+ /** Tree root. Not for external use. */
8
+ ElementBookEntryTypeEnum["Root"] = "root";
9
+ })(ElementBookEntryTypeEnum || (ElementBookEntryTypeEnum = {}));
@@ -0,0 +1,9 @@
1
+ import { ElementBookChapter } from './element-book-chapter/element-book-chapter';
2
+ import { ElementBookEntryTypeEnum } from './element-book-entry-type';
3
+ import { ElementBookPage } from './element-book-page/element-book-page';
4
+ export type ElementBookRoot = {
5
+ type: ElementBookEntryTypeEnum.Root;
6
+ title: 'element book tree root';
7
+ parent: undefined;
8
+ };
9
+ export type ElementBookEntry = ElementBookChapter | ElementBookPage | ElementBookRoot;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,22 @@
1
+ import { PropertyInitMapBase, TypedEvent } from 'element-vir';
2
+ import { CSSResult } from 'lit';
3
+ export type ElementBookPageExample<StateInit extends PropertyInitMapBase = any> = {
4
+ title: string;
5
+ /** Initialize the state for this example. */
6
+ stateInit?: StateInit;
7
+ /** Specify which events this example should intercept (so the user can see them). */
8
+ capturedEvents?: ReadonlyArray<string | TypedEvent>;
9
+ /**
10
+ * Style the example. You can even use the :host selector to style this specific example's
11
+ * wrapper element!
12
+ */
13
+ styles?: CSSResult;
14
+ /** Set to true to hide the example controls (example title and buttons). */
15
+ hideControls?: boolean | undefined;
16
+ /** Render the example. */
17
+ render: (renderParams: {
18
+ state: StateInit;
19
+ updateState: (newState: Partial<StateInit>) => void;
20
+ }) => unknown;
21
+ };
22
+ export declare function createExample<StateInit extends PropertyInitMapBase>(example: ElementBookPageExample<StateInit>): ElementBookPageExample<StateInit>;
@@ -0,0 +1,3 @@
1
+ export function createExample(example) {
2
+ return example;
3
+ }
@@ -0,0 +1,10 @@
1
+ import { ElementBookChapter } from '../element-book-chapter/element-book-chapter';
2
+ import { ElementBookEntryTypeEnum } from '../element-book-entry-type';
3
+ import { ElementBookPageExample } from './element-book-page-example';
4
+ export type ElementBookPage = {
5
+ type: ElementBookEntryTypeEnum.Page;
6
+ title: string;
7
+ parent?: ElementBookChapter | undefined;
8
+ examples: ReadonlyArray<ElementBookPageExample>;
9
+ };
10
+ export declare function defineElementBookPage(pageSetup: Omit<ElementBookPage, 'type'>): ElementBookPage;
@@ -0,0 +1,26 @@
1
+ import { ElementBookEntryTypeEnum } from '../element-book-entry-type';
2
+ import { listTitleBreadcrumbs } from '../entry-tree/entry-tree';
3
+ export function defineElementBookPage(pageSetup) {
4
+ if (!pageSetup.title) {
5
+ throw new Error(`Cannot have an element-book page with an empty title.`);
6
+ }
7
+ const page = {
8
+ type: ElementBookEntryTypeEnum.Page,
9
+ ...pageSetup,
10
+ };
11
+ const pageBreadcrumbs = listTitleBreadcrumbs(page, true);
12
+ const exampleTitlesSet = new Set();
13
+ pageSetup.examples.forEach((example) => {
14
+ const failureMessage = `Failed to define example '${pageBreadcrumbs
15
+ .concat(example.title)
16
+ .join(' > ')}'`;
17
+ if (exampleTitlesSet.has(example.title)) {
18
+ throw new Error(`${failureMessage}: example title '${example.title}' is already being used.`);
19
+ }
20
+ else if (!example.title) {
21
+ throw new Error(`${failureMessage}: example title is missing or empty.`);
22
+ }
23
+ exampleTitlesSet.add(example.title);
24
+ });
25
+ return page;
26
+ }
@@ -0,0 +1,14 @@
1
+ import { ElementBookEntry } from '../element-book-entry';
2
+ import { ElementBookEntryTypeEnum } from '../element-book-entry-type';
3
+ export declare function doesNodeHaveEntryType<EntryType extends ElementBookEntryTypeEnum>(node: EntryTreeNode<any>, type: EntryType): node is EntryTreeNode<EntryType>;
4
+ export type EntryTreeNode<EntryType extends ElementBookEntryTypeEnum = ElementBookEntryTypeEnum> = {
5
+ entry: Extract<ElementBookEntry, {
6
+ type: EntryType;
7
+ }>;
8
+ children: Record<string, EntryTreeNode>;
9
+ };
10
+ export declare function createEmptyEntryTreeRoot(): EntryTreeNode;
11
+ 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>;
@@ -0,0 +1,69 @@
1
+ import { isLengthAtLeast } from '@augment-vir/common';
2
+ import { ElementBookEntryTypeEnum } from '../element-book-entry-type';
3
+ export function doesNodeHaveEntryType(node, type) {
4
+ return node.entry.type === type;
5
+ }
6
+ export function createEmptyEntryTreeRoot() {
7
+ const rootNode = {
8
+ entry: {
9
+ type: ElementBookEntryTypeEnum.Root,
10
+ title: 'element book tree root',
11
+ parent: undefined,
12
+ },
13
+ children: {},
14
+ };
15
+ return rootNode;
16
+ }
17
+ export function entriesToTree(entries) {
18
+ const tree = createEmptyEntryTreeRoot();
19
+ entries.forEach((newEntry) => {
20
+ 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}'.`);
23
+ }
24
+ const newNode = {
25
+ children: {},
26
+ entry: newEntry,
27
+ };
28
+ immediateParent.children[newEntry.title] = newNode;
29
+ });
30
+ return tree;
31
+ }
32
+ export function traverseToImmediateParent(entry, currentTree) {
33
+ const topDownAncestryChain = listTitleBreadcrumbs(entry)
34
+ // reverse so we get the top most ancestor first in the list
35
+ .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}'`);
40
+ }
41
+ return nextParent;
42
+ }, currentTree);
43
+ return immediateParentNode;
44
+ }
45
+ export function listTitleBreadcrumbs(entry, includeSelf) {
46
+ if (entry.parent) {
47
+ return [
48
+ entry.parent.title,
49
+ ...listTitleBreadcrumbs(entry.parent, false),
50
+ ].concat(includeSelf ? [entry.title] : []);
51
+ }
52
+ else if (includeSelf) {
53
+ return [entry.title];
54
+ }
55
+ else {
56
+ return [];
57
+ }
58
+ }
59
+ export function findEntryByBreadcrumbs(titles, tree) {
60
+ if (!isLengthAtLeast(titles, 1)) {
61
+ return tree;
62
+ }
63
+ const nextEntryTitle = titles[0];
64
+ const nextTree = tree.children[nextEntryTitle];
65
+ if (!nextTree) {
66
+ throw new Error(`Failed to find '${tree.entry.title}' > '${nextEntryTitle}'.`);
67
+ }
68
+ return findEntryByBreadcrumbs(titles.slice(1), nextTree);
69
+ }
@@ -0,0 +1,9 @@
1
+ import { MaybePromise } from '@augment-vir/common';
2
+ import { EntryTreeNode } from './entry-tree';
3
+ export declare function findFirstPageBreadcrumbs(entryTree: Readonly<EntryTreeNode>): string[];
4
+ /**
5
+ * Walk the whole given tree, calling callback on each node. If callback returns the boolean false
6
+ * exactly, then all walking will abort. If callback returns a promise, this function will return a
7
+ * promise. Otherwise, it will not return a promise.
8
+ */
9
+ export declare function walkEntryTree<CallbackReturn extends MaybePromise<void | boolean>>(startNode: Readonly<EntryTreeNode>, callback: (node: Readonly<EntryTreeNode>) => CallbackReturn): CallbackReturn;
@@ -0,0 +1,101 @@
1
+ import { ElementBookEntryTypeEnum } from '../element-book-entry-type';
2
+ import { doesNodeHaveEntryType, listTitleBreadcrumbs } from './entry-tree';
3
+ export function findFirstPageBreadcrumbs(entryTree) {
4
+ let pageEntry;
5
+ walkEntryTree(entryTree, (node) => {
6
+ if (doesNodeHaveEntryType(node, ElementBookEntryTypeEnum.Page)) {
7
+ pageEntry = node;
8
+ return false;
9
+ }
10
+ return;
11
+ });
12
+ if (!pageEntry) {
13
+ return [];
14
+ }
15
+ return listTitleBreadcrumbs(pageEntry.entry).concat(pageEntry.entry.title);
16
+ }
17
+ /**
18
+ * Walk the whole given tree, calling callback on each node. If callback returns the boolean false
19
+ * exactly, then all walking will abort. If callback returns a promise, this function will return a
20
+ * promise. Otherwise, it will not return a promise.
21
+ */
22
+ export function walkEntryTree(startNode, callback) {
23
+ // this inner function is for easier typing purposes
24
+ function innerWalk() {
25
+ const outerResult = callback(startNode);
26
+ if (outerResult === false) {
27
+ return false;
28
+ }
29
+ let abort = false;
30
+ if (outerResult instanceof Promise) {
31
+ return new Promise(async (resolve, reject) => {
32
+ try {
33
+ const outerResolvedResult = await outerResult;
34
+ if (outerResolvedResult === false) {
35
+ return;
36
+ }
37
+ await Promise.all(Object.values(startNode.children).map(async (child) => {
38
+ if (abort) {
39
+ return false;
40
+ }
41
+ const result = await walkEntryTree(child, callback);
42
+ if (result === false) {
43
+ abort = true;
44
+ return false;
45
+ }
46
+ return result;
47
+ }));
48
+ if (abort) {
49
+ return resolve(false);
50
+ }
51
+ else {
52
+ return resolve();
53
+ }
54
+ }
55
+ catch (error) {
56
+ return reject(error);
57
+ }
58
+ });
59
+ }
60
+ else {
61
+ let gotPromise = false;
62
+ const childResults = Object.values(startNode.children).map((child) => {
63
+ if (abort) {
64
+ return false;
65
+ }
66
+ const result = walkEntryTree(child, callback);
67
+ if (result === false) {
68
+ abort = true;
69
+ }
70
+ else if (result instanceof Promise) {
71
+ gotPromise = true;
72
+ }
73
+ return result;
74
+ });
75
+ if (gotPromise) {
76
+ return new Promise(async (resolve, reject) => {
77
+ try {
78
+ if (abort) {
79
+ return resolve(false);
80
+ }
81
+ const results = await Promise.all(childResults);
82
+ if (results.some((result) => result === false)) {
83
+ return resolve(false);
84
+ }
85
+ else {
86
+ return resolve();
87
+ }
88
+ }
89
+ catch (error) {
90
+ return reject(error);
91
+ }
92
+ });
93
+ }
94
+ else if (abort) {
95
+ return false;
96
+ }
97
+ return;
98
+ }
99
+ }
100
+ return innerWalk();
101
+ }
@@ -0,0 +1,4 @@
1
+ export * from './data/element-book-entry/element-book-chapter/element-book-chapter';
2
+ export * from './data/element-book-entry/element-book-page/element-book-page';
3
+ export * from './data/element-book-entry/element-book-page/element-book-page-example';
4
+ export * from './ui/elements/element-book-app.element';
package/dist/index.js ADDED
@@ -0,0 +1,4 @@
1
+ export * from './data/element-book-entry/element-book-chapter/element-book-chapter';
2
+ export * from './data/element-book-entry/element-book-page/element-book-page';
3
+ export * from './data/element-book-entry/element-book-page/element-book-page-example';
4
+ export * from './ui/elements/element-book-app.element';
@@ -0,0 +1,2 @@
1
+ import { ElementBookRouter } from './element-book-routing';
2
+ export declare function createElementBookRouter(baseRoute: string): ElementBookRouter;
@@ -0,0 +1,6 @@
1
+ import { createSpaRouter } from 'spa-router-vir';
2
+ export function createElementBookRouter(baseRoute) {
3
+ return createSpaRouter({
4
+ routeBase: baseRoute,
5
+ });
6
+ }
@@ -0,0 +1,5 @@
1
+ import { FullRoute, SpaRouter } from 'spa-router-vir';
2
+ export type ValidElementBookPaths = string[];
3
+ export type ElementBookFullRoute = Required<Readonly<FullRoute<ValidElementBookPaths, undefined, undefined>>>;
4
+ export declare const emptyElementBookFullRoute: Readonly<ElementBookFullRoute>;
5
+ export type ElementBookRouter = Readonly<SpaRouter<ValidElementBookPaths, undefined, undefined>>;
@@ -0,0 +1,5 @@
1
+ export const emptyElementBookFullRoute = {
2
+ hash: undefined,
3
+ paths: [],
4
+ search: undefined,
5
+ };
@@ -0,0 +1,5 @@
1
+ import { ElementBookFullRoute, ElementBookRouter } from '../../../routing/element-book-routing';
2
+ export declare const ElementBookRouteLink: import("element-vir").DeclarativeElementDefinition<"element-book-route-link", {
3
+ route: Partial<ElementBookFullRoute>;
4
+ router: ElementBookRouter | undefined;
5
+ }, {}, {}, string, "anchorPadding", import("lit-html").HTMLTemplateResult>;
@@ -0,0 +1,40 @@
1
+ import { css, html, listen } from 'element-vir';
2
+ import { shouldMouseEventTriggerRoutes } from 'spa-router-vir';
3
+ import { ChangeRouteEvent } from '../../events/change-route.event';
4
+ import { defineElementBookElement } from '../define-book-element';
5
+ export const ElementBookRouteLink = defineElementBookElement()({
6
+ tagName: 'element-book-route-link',
7
+ cssVars: {
8
+ anchorPadding: '',
9
+ },
10
+ styles: ({ cssVarValues }) => css `
11
+ a {
12
+ display: block;
13
+ padding: ${cssVarValues.anchorPadding};
14
+ text-decoration: inherit;
15
+ color: inherit;
16
+ height: 100%;
17
+ width: 100%;
18
+ }
19
+ `,
20
+ renderCallback: ({ inputs, dispatch }) => {
21
+ const linkUrl = inputs.router?.createRoutesUrl({
22
+ ...inputs.router?.getCurrentRawRoutes(),
23
+ ...inputs.route,
24
+ }) ?? '#';
25
+ return html `
26
+ <a
27
+ href=${linkUrl}
28
+ ${listen('click', (clickEvent) => {
29
+ if (!inputs.router || shouldMouseEventTriggerRoutes(clickEvent)) {
30
+ clickEvent.preventDefault();
31
+ window.scrollTo(0, 0);
32
+ dispatch(new ChangeRouteEvent(inputs.route));
33
+ }
34
+ })}
35
+ >
36
+ <slot></slot>
37
+ </a>
38
+ `;
39
+ },
40
+ });
@@ -0,0 +1,2 @@
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>;
@@ -0,0 +1,2 @@
1
+ import { wrapDefineElement } from 'element-vir';
2
+ export const { defineElement: defineElementBookElement, defineElementNoInputs: defineElementBookElementNoInputs, } = wrapDefineElement();
@@ -0,0 +1,9 @@
1
+ import { ElementBookEntry } from '../../data/element-book-entry/element-book-entry';
2
+ export declare const ElementBookApp: import("element-vir").DeclarativeElementDefinition<"element-book-app", {
3
+ entries: ReadonlyArray<ElementBookEntry>;
4
+ baseRoute?: string;
5
+ defaultPath?: ReadonlyArray<string>;
6
+ }, {
7
+ currentRoute: Readonly<Required<Readonly<import("spa-router-vir").FullRoute<import("../../routing/element-book-routing").ValidElementBookPaths, undefined, undefined>>>>;
8
+ router: Readonly<import("spa-router-vir").SpaRouter<import("../../routing/element-book-routing").ValidElementBookPaths, undefined, undefined>> | undefined;
9
+ }, {}, "", "", import("lit-html").HTMLTemplateResult>;
@@ -0,0 +1,94 @@
1
+ import { assign, css, defineElement, html, listen } from 'element-vir';
2
+ import { entriesToTree, findEntryByBreadcrumbs, } from '../../data/element-book-entry/entry-tree/entry-tree';
3
+ import { findFirstPageBreadcrumbs } from '../../data/element-book-entry/entry-tree/walk-entry-tree';
4
+ import { createElementBookRouter } from '../../routing/create-element-book-router';
5
+ import { emptyElementBookFullRoute, } from '../../routing/element-book-routing';
6
+ import { ChangeRouteEvent } from '../events/change-route.event';
7
+ import { ElementBookNav } from './element-book-nav.element';
8
+ import { ElementBookEntryDisplay } from './entry-display/element-book-entry-display.element';
9
+ export const ElementBookApp = defineElement()({
10
+ tagName: 'element-book-app',
11
+ stateInit: {
12
+ currentRoute: emptyElementBookFullRoute,
13
+ router: undefined,
14
+ },
15
+ styles: css `
16
+ :host {
17
+ display: block;
18
+ height: 100%;
19
+ width: 100%;
20
+ font-family: sans-serif;
21
+ }
22
+
23
+ .root {
24
+ height: 100%;
25
+ width: 100%;
26
+ display: flex;
27
+ }
28
+
29
+ ${ElementBookEntryDisplay} {
30
+ flex-grow: 1;
31
+ }
32
+ `,
33
+ initCallback({ updateState, state, inputs }) {
34
+ if (inputs.baseRoute && !state.router) {
35
+ const router = createElementBookRouter(inputs.baseRoute);
36
+ updateState({ router });
37
+ router.addRouteListener(true, (fullRoute) => {
38
+ updateState({
39
+ currentRoute: fullRoute,
40
+ });
41
+ });
42
+ }
43
+ },
44
+ renderCallback: ({ state, inputs, updateState }) => {
45
+ function updateRoutes(newRoute) {
46
+ if (state.router) {
47
+ state.router.setRoutes(newRoute);
48
+ }
49
+ else {
50
+ updateState({
51
+ currentRoute: {
52
+ ...state.currentRoute,
53
+ ...newRoute,
54
+ },
55
+ });
56
+ }
57
+ }
58
+ const entriesTree = entriesToTree(inputs.entries);
59
+ if (!state.currentRoute.paths.length) {
60
+ const firstPageBreadcrumbs = findFirstPageBreadcrumbs(entriesTree);
61
+ const defaultPath = inputs.defaultPath ??
62
+ (firstPageBreadcrumbs.length ? firstPageBreadcrumbs : undefined);
63
+ if (defaultPath && defaultPath.length) {
64
+ const newRoute = {
65
+ paths: defaultPath,
66
+ };
67
+ updateRoutes(newRoute);
68
+ }
69
+ }
70
+ const currentEntry = findEntryByBreadcrumbs(state.currentRoute.paths, entriesTree).entry;
71
+ return html `
72
+ <div
73
+ class="root"
74
+ ${listen(ChangeRouteEvent, (event) => {
75
+ updateRoutes(event.detail);
76
+ })}
77
+ >
78
+ <${ElementBookNav}
79
+ ${assign(ElementBookNav, {
80
+ tree: entriesTree,
81
+ router: state.router,
82
+ selectedPath: state.currentRoute.paths,
83
+ })}
84
+ ></${ElementBookNav}>
85
+ <${ElementBookEntryDisplay}
86
+ ${assign(ElementBookEntryDisplay, {
87
+ currentRoute: state.currentRoute,
88
+ currentEntry,
89
+ })}
90
+ ></${ElementBookEntryDisplay}>
91
+ </div>
92
+ `;
93
+ },
94
+ });
@@ -0,0 +1,4 @@
1
+ import { ElementBookFullRoute } from '../../routing/element-book-routing';
2
+ export declare const ElementBookBreadcrumbs: import("element-vir").DeclarativeElementDefinition<"element-book-breadcrumbs", {
3
+ currentRoute: Readonly<ElementBookFullRoute>;
4
+ }, {}, {}, string, string, import("lit-html").HTMLTemplateResult[]>;
@@ -0,0 +1,24 @@
1
+ import { css, html } from 'element-vir';
2
+ import { defineElementBookElement } from './define-book-element';
3
+ export const ElementBookBreadcrumbs = defineElementBookElement()({
4
+ tagName: 'element-book-breadcrumbs',
5
+ styles: css `
6
+ :host {
7
+ display: flex;
8
+ color: #999;
9
+ }
10
+ `,
11
+ renderCallback: ({ inputs }) => {
12
+ return inputs.currentRoute.paths.map((currentPath, pathIndex) => {
13
+ const isLastPath = pathIndex >= inputs.currentRoute.paths.length - 1;
14
+ const spacer = isLastPath
15
+ ? ''
16
+ : html `
17
+ &gt;
18
+ `;
19
+ return html `
20
+ ${currentPath} ${spacer}
21
+ `;
22
+ });
23
+ },
24
+ });
@@ -0,0 +1,7 @@
1
+ import { EntryTreeNode } from '../../data/element-book-entry/entry-tree/entry-tree';
2
+ import { ElementBookRouter } from '../../routing/element-book-routing';
3
+ export declare const ElementBookNav: import("element-vir").DeclarativeElementDefinition<"element-book-nav", {
4
+ tree: EntryTreeNode;
5
+ selectedPath: ReadonlyArray<string>;
6
+ router: ElementBookRouter | undefined;
7
+ }, {}, {}, string, string, import("lit-html").HTMLTemplateResult>;
@@ -0,0 +1,89 @@
1
+ import { areJsonEqual } from '@augment-vir/common';
2
+ import { assign, classMap, css, html } from 'element-vir';
3
+ import { ElementBookRouteLink } from './common/element-book-route-link.element';
4
+ import { defineElementBookElement } from './define-book-element';
5
+ export const ElementBookNav = defineElementBookElement()({
6
+ tagName: 'element-book-nav',
7
+ styles: css `
8
+ :host {
9
+ display: flex;
10
+ flex-direction: column;
11
+ padding: 16px 0;
12
+ background-color: #f8f8f8;
13
+ }
14
+
15
+ .title-row:hover {
16
+ background-color: #ccc;
17
+ }
18
+
19
+ .title-row {
20
+ display: block;
21
+ ${ElementBookRouteLink.cssVarNames
22
+ .anchorPadding}: 4px 24px 4px calc(calc(16px * var(--indent, 0)) + 24px);
23
+ }
24
+
25
+ ${ElementBookRouteLink} {
26
+ font-size: 20px;
27
+ }
28
+
29
+ ul {
30
+ list-style: none;
31
+ padding: 0;
32
+ margin: 0;
33
+ }
34
+
35
+ .selected {
36
+ background-color: #ddd;
37
+ }
38
+ `,
39
+ renderCallback({ inputs }) {
40
+ const navTree = createNavigationTree({
41
+ indent: 0,
42
+ tree: inputs.tree,
43
+ rootPath: [],
44
+ router: inputs.router,
45
+ selectedPath: inputs.selectedPath,
46
+ });
47
+ return html `
48
+ <ul>
49
+ ${navTree}
50
+ </ul>
51
+ `;
52
+ },
53
+ });
54
+ function createNavigationTree({ indent, tree, rootPath, selectedPath, router, }) {
55
+ if (!tree.children) {
56
+ return [];
57
+ }
58
+ return Object.values(tree.children).map((child) => {
59
+ const childPath = rootPath.concat(child.entry.title);
60
+ const childTemplates = createNavigationTree({
61
+ indent: indent + 1,
62
+ tree: child,
63
+ rootPath: childPath,
64
+ selectedPath,
65
+ router,
66
+ });
67
+ return html `
68
+ <div class="nav-tree-entry" style="--indent: ${indent};">
69
+ <li class=${child.entry.type}>
70
+ <${ElementBookRouteLink}
71
+ ${assign(ElementBookRouteLink, {
72
+ router: router,
73
+ route: {
74
+ paths: childPath,
75
+ },
76
+ })}
77
+ class=${classMap({
78
+ 'title-row': true,
79
+ selected: areJsonEqual(selectedPath, childPath),
80
+ })}
81
+ >
82
+ ${child.entry.title}
83
+ </${ElementBookRouteLink}>
84
+ </li>
85
+ ${childTemplates}
86
+ </div>
87
+ `;
88
+ });
89
+ }
@@ -0,0 +1,6 @@
1
+ import { ElementBookEntry } from '../../../data/element-book-entry/element-book-entry';
2
+ import { ElementBookFullRoute } from '../../../routing/element-book-routing';
3
+ export declare const ElementBookEntryDisplay: import("element-vir").DeclarativeElementDefinition<"element-book-entry-display", {
4
+ currentRoute: Readonly<ElementBookFullRoute>;
5
+ currentEntry: Readonly<ElementBookEntry>;
6
+ }, {}, {}, string, string, import("lit-html").HTMLTemplateResult>;
@@ -0,0 +1,75 @@
1
+ import { assign, classMap, css, html, renderIf } from 'element-vir';
2
+ 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 { defineElementBookElement } from '../define-book-element';
5
+ 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';
8
+ export const ElementBookEntryDisplay = defineElementBookElement()({
9
+ tagName: 'element-book-entry-display',
10
+ styles: css `
11
+ :host {
12
+ display: flex;
13
+ flex-direction: column;
14
+ }
15
+
16
+ .title-bar {
17
+ border-bottom: 1px solid #f8f8f8;
18
+ padding: 4px 8px;
19
+ }
20
+
21
+ .examples-wrapper {
22
+ padding: 32px;
23
+ display: flex;
24
+ gap: 32px;
25
+ flex-wrap: wrap;
26
+ }
27
+
28
+ .individual-example-wrapper {
29
+ display: flex;
30
+ flex-direction: column;
31
+ gap: 24px;
32
+ }
33
+
34
+ .hidden-controls {
35
+ pointer-events: none;
36
+ visibility: hidden;
37
+ }
38
+ `,
39
+ 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);
45
+ return html `
46
+ <div class="title-bar">
47
+ <${ElementBookBreadcrumbs}
48
+ ${assign(ElementBookBreadcrumbs, { currentRoute: inputs.currentRoute })}
49
+ ></${ElementBookBreadcrumbs}>
50
+ </div>
51
+ <div class="examples-wrapper">${exampleTemplates}</div>
52
+ `;
53
+ },
54
+ });
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
+ });
75
+ }
@@ -0,0 +1,4 @@
1
+ import { ElementBookPageExample } from '../../../data/element-book-entry/element-book-page/element-book-page-example';
2
+ export declare const ElementBookExampleControls: import("element-vir").DeclarativeElementDefinition<"element-book-example-controls", {
3
+ example: ElementBookPageExample;
4
+ }, {}, {}, string, string, import("lit-html").HTMLTemplateResult>;
@@ -0,0 +1,25 @@
1
+ import { css, html } from 'element-vir';
2
+ import { defineElementBookElement } from '../define-book-element';
3
+ export const ElementBookExampleControls = defineElementBookElement()({
4
+ tagName: 'element-book-example-controls',
5
+ styles: css `
6
+ :host {
7
+ display: flex;
8
+ color: #bbb;
9
+ border-bottom: 1px solid currentColor;
10
+ padding: 0 8px 4px;
11
+ }
12
+
13
+ :host(:hover) {
14
+ color: black;
15
+ }
16
+ `,
17
+ renderCallback({ inputs }) {
18
+ return html `
19
+ <span>
20
+ ${inputs.example.title}
21
+ <span></span>
22
+ </span>
23
+ `;
24
+ },
25
+ });
@@ -0,0 +1,7 @@
1
+ import { ElementBookPageExample } from '../../../data/element-book-entry/element-book-page/element-book-page-example';
2
+ export declare const ElementBookExampleViewer: import("element-vir").DeclarativeElementDefinition<"element-book-example-viewer", {
3
+ example: ElementBookPageExample;
4
+ parentBreadcrumbs: ReadonlyArray<string>;
5
+ }, {
6
+ internalState: any;
7
+ }, {}, string, string, string | import("lit-html").HTMLTemplateResult>;
@@ -0,0 +1,41 @@
1
+ import { extractErrorMessage } from '@augment-vir/common';
2
+ import { html, renderIf } from 'element-vir';
3
+ import { defineElementBookElement } from '../define-book-element';
4
+ const unsetInternalState = Symbol('unset-internal-state');
5
+ export const ElementBookExampleViewer = defineElementBookElement()({
6
+ tagName: 'element-book-example-viewer',
7
+ stateInit: {
8
+ internalState: unsetInternalState,
9
+ },
10
+ renderCallback({ state, inputs, updateState }) {
11
+ const fullExampleBreadcrumbs = inputs.parentBreadcrumbs.concat(inputs.example.title);
12
+ if (state.internalState === unsetInternalState) {
13
+ updateState({ internalState: inputs.example.stateInit });
14
+ }
15
+ try {
16
+ const output = inputs.example.render({
17
+ state: state.internalState,
18
+ updateState: (newState) => {
19
+ updateState({
20
+ internalState: {
21
+ ...state.internalState,
22
+ ...newState,
23
+ },
24
+ });
25
+ },
26
+ });
27
+ return html `
28
+ ${renderIf(!!inputs.example.styles, html `
29
+ <style>
30
+ ${inputs.example.styles}
31
+ </style>
32
+ `)}
33
+ ${output}
34
+ `;
35
+ }
36
+ catch (error) {
37
+ console.error(error);
38
+ return `${fullExampleBreadcrumbs.join(' > ')} failed: ${extractErrorMessage(error)}`;
39
+ }
40
+ },
41
+ });
@@ -0,0 +1 @@
1
+ export declare const ChangeRouteEvent: import("element-vir").DefinedTypedEvent<"change-page", Partial<Required<Readonly<import("spa-router-vir").FullRoute<import("../../routing/element-book-routing").ValidElementBookPaths, undefined, undefined>>>>>;
@@ -0,0 +1,2 @@
1
+ import { defineTypedEvent } from 'element-vir';
2
+ export const ChangeRouteEvent = defineTypedEvent()('change-page');
package/package.json CHANGED
@@ -1,6 +1,19 @@
1
1
  {
2
2
  "name": "element-book",
3
- "version": "0.0.0",
3
+ "version": "0.0.1",
4
+ "keywords": [
5
+ "book",
6
+ "design system",
7
+ "design",
8
+ "element-vir",
9
+ "element",
10
+ "es module",
11
+ "esm",
12
+ "lit-element",
13
+ "lit",
14
+ "storybook",
15
+ "system"
16
+ ],
4
17
  "homepage": "https://github.com/electrovir/element-book",
5
18
  "bugs": {
6
19
  "url": "https://github.com/electrovir/element-book/issues"
@@ -9,38 +22,38 @@
9
22
  "type": "git",
10
23
  "url": "https://github.com/electrovir/element-book"
11
24
  },
12
- "license": "MIT",
25
+ "license": "(MIT or CC0 1.0)",
13
26
  "author": {
14
27
  "name": "electrovir",
15
28
  "url": "https://github.com/electrovir"
16
29
  },
17
30
  "main": "dist/index.js",
31
+ "module": "dist/index.js",
18
32
  "types": "dist/index.d.ts",
19
33
  "scripts": {
20
- "build": "virmator frontend build",
21
- "compile": "rm -rf dist && virmator compile",
22
- "docs:update": "virmator code-in-markdown",
23
- "format": "virmator format",
24
- "preview": "virmator frontend preview",
25
- "publish": "virmator publish \"npm run compile && npm run test:all\"",
26
- "start": "npm install && virmator frontend",
27
- "test": "virmator test",
28
- "test:all": "npm run test:types && npm run test:coverage && npm run test:spelling && npm run test:format && npm run test:docs",
34
+ "compile": "tsc",
35
+ "compile:clean": "rm -rf dist && npm run compile",
36
+ "test": "virmator test-web",
29
37
  "test:coverage": "npm run test coverage",
30
- "test:docs": "virmator code-in-markdown check",
31
- "test:format": "virmator format check",
32
- "test:spelling": "virmator spellcheck",
33
- "test:types": "tsc --noEmit",
34
- "test:web": "virmator test-web"
38
+ "test:types": "tsc --noEmit"
39
+ },
40
+ "dependencies": {
41
+ "@augment-vir/common": "^13.3.0",
42
+ "element-vir": "^12.2.0",
43
+ "spa-router-vir": "^2.0.0",
44
+ "typed-event-target": "^1.2.0"
35
45
  },
36
46
  "devDependencies": {
37
- "prettier": "^2.8.3",
38
- "prettier-plugin-jsdoc": "^0.4.2",
39
- "prettier-plugin-multiline-arrays": "^1.1.3",
40
- "prettier-plugin-organize-imports": "^3.2.2",
41
- "prettier-plugin-packagejson": "^2.3.0",
42
- "prettier-plugin-sort-json": "^1.0.0",
43
- "prettier-plugin-toml": "^0.3.1",
44
- "virmator": "^5.4.0"
47
+ "@augment-vir/browser-testing": "^13.3.0",
48
+ "@open-wc/testing": "^3.1.8",
49
+ "@types/mocha": "^10.0.1",
50
+ "@web/dev-server-esbuild": "^0.4.1",
51
+ "@web/test-runner": "^0.16.1",
52
+ "@web/test-runner-commands": "^0.7.0",
53
+ "@web/test-runner-playwright": "^0.10.0",
54
+ "@web/test-runner-visual-regression": "^0.8.0",
55
+ "istanbul-smart-text-reporter": "^1.1.1",
56
+ "type-fest": "^3.9.0",
57
+ "typescript": "^5.0.4"
45
58
  }
46
59
  }
package/LICENSE DELETED
@@ -1,21 +0,0 @@
1
- MIT License
2
-
3
- Copyright (c) 2023 electrovir
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.