@inglorious/ui 0.1.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 (83) hide show
  1. package/LICENSE +9 -0
  2. package/package.json +142 -0
  3. package/src/controls/button/button.stories.js +139 -0
  4. package/src/controls/button/index.js +5 -0
  5. package/src/controls/button/style.css +148 -0
  6. package/src/controls/button/template.js +74 -0
  7. package/src/controls/button-group/button-group.stories.js +108 -0
  8. package/src/controls/button-group/index.js +4 -0
  9. package/src/controls/button-group/style.css +58 -0
  10. package/src/controls/button-group/template.js +117 -0
  11. package/src/controls/checkbox/checkbox.stories.js +47 -0
  12. package/src/controls/checkbox/index.js +4 -0
  13. package/src/controls/checkbox/style.css +33 -0
  14. package/src/controls/checkbox/template.js +54 -0
  15. package/src/controls/fab/fab.stories.js +52 -0
  16. package/src/controls/fab/index.js +4 -0
  17. package/src/controls/fab/style.css +76 -0
  18. package/src/controls/fab/template.js +33 -0
  19. package/src/controls/icon-button/icon-button.stories.js +70 -0
  20. package/src/controls/icon-button/index.js +4 -0
  21. package/src/controls/icon-button/style.css +14 -0
  22. package/src/controls/icon-button/template.js +47 -0
  23. package/src/controls/input/index.js +4 -0
  24. package/src/controls/input/input.stories.js +180 -0
  25. package/src/controls/input/style.css +121 -0
  26. package/src/controls/input/template.js +111 -0
  27. package/src/controls/radio-group/index.js +4 -0
  28. package/src/controls/radio-group/radio-group.stories.js +51 -0
  29. package/src/controls/radio-group/style.css +41 -0
  30. package/src/controls/radio-group/template.js +71 -0
  31. package/src/controls/rating/index.js +4 -0
  32. package/src/controls/rating/rating.stories.js +47 -0
  33. package/src/controls/rating/style.css +29 -0
  34. package/src/controls/rating/template.js +53 -0
  35. package/src/controls/select/handlers.js +133 -0
  36. package/src/controls/select/helpers.js +91 -0
  37. package/src/controls/select/index.js +15 -0
  38. package/src/controls/select/select.stories.js +90 -0
  39. package/src/controls/select/style.css +211 -0
  40. package/src/controls/select/template.js +423 -0
  41. package/src/controls/slider/index.js +4 -0
  42. package/src/controls/slider/slider.stories.js +43 -0
  43. package/src/controls/slider/style.css +28 -0
  44. package/src/controls/slider/template.js +66 -0
  45. package/src/controls/switch/index.js +4 -0
  46. package/src/controls/switch/style.css +82 -0
  47. package/src/controls/switch/switch.stories.js +45 -0
  48. package/src/controls/switch/template.js +55 -0
  49. package/src/index.js +14 -0
  50. package/src/layout/flex/flex.stories.js +103 -0
  51. package/src/layout/flex/index.js +4 -0
  52. package/src/layout/flex/style.css +103 -0
  53. package/src/layout/flex/template.js +41 -0
  54. package/src/layout/grid/grid.stories.js +82 -0
  55. package/src/layout/grid/index.js +4 -0
  56. package/src/layout/grid/style.css +51 -0
  57. package/src/layout/grid/template.js +47 -0
  58. package/src/shared/applyElementProps.js +79 -0
  59. package/src/shared/withRenderValidation.js +19 -0
  60. package/src/stories/notifyStory.js +43 -0
  61. package/src/themes/bootstrap.css +118 -0
  62. package/src/themes/inglorious.css +106 -0
  63. package/src/themes/material.css +124 -0
  64. package/src/tokens/index.css +133 -0
  65. package/src/widgets/card/card.stories.js +135 -0
  66. package/src/widgets/card/index.js +4 -0
  67. package/src/widgets/card/style.css +53 -0
  68. package/src/widgets/card/template.js +97 -0
  69. package/types/controls/button-group.d.ts +35 -0
  70. package/types/controls/button.d.ts +23 -0
  71. package/types/controls/checkbox.d.ts +18 -0
  72. package/types/controls/fab.d.ts +19 -0
  73. package/types/controls/icon-button.d.ts +23 -0
  74. package/types/controls/input.d.ts +29 -0
  75. package/types/controls/radio-group.d.ts +25 -0
  76. package/types/controls/rating.d.ts +18 -0
  77. package/types/controls/select.d.ts +56 -0
  78. package/types/controls/slider.d.ts +21 -0
  79. package/types/controls/switch.d.ts +17 -0
  80. package/types/index.d.ts +14 -0
  81. package/types/layout/flex.d.ts +20 -0
  82. package/types/layout/grid.d.ts +19 -0
  83. package/types/widgets/card.d.ts +21 -0
@@ -0,0 +1,55 @@
1
+ /**
2
+ * @typedef {import('../../../types/controls/switch').SwitchEntity} SwitchEntity
3
+ * @typedef {import('@inglorious/web').Api} Api
4
+ * @typedef {import('@inglorious/web').TemplateResult} TemplateResult
5
+ */
6
+
7
+ import { classMap, html, ref } from "@inglorious/web"
8
+
9
+ import { applyElementProps } from "@/shared/applyElementProps.js"
10
+
11
+ /**
12
+ * Switch control implemented with a checkbox input.
13
+ *
14
+ * @param {SwitchEntity} entity
15
+ * @param {Api} api
16
+ * @returns {TemplateResult}
17
+ */
18
+ export function render(entity, api) {
19
+ const {
20
+ name = "",
21
+ label = "",
22
+ checked = false,
23
+ disabled = false,
24
+ size = "md",
25
+ ...rest
26
+ } = entity
27
+
28
+ const inputId = entity.id || name
29
+
30
+ const classes = {
31
+ "iw-switch-field": true,
32
+ [`iw-switch-${size}`]: size !== "md",
33
+ "iw-switch-disabled": disabled,
34
+ }
35
+
36
+ return html`
37
+ <label class=${classMap(classes)} for=${inputId}>
38
+ <input
39
+ id=${inputId}
40
+ class="iw-switch-input"
41
+ type="checkbox"
42
+ name=${name}
43
+ .checked=${checked}
44
+ ?disabled=${disabled}
45
+ @change=${(event) =>
46
+ api.notify(`#${entity.id}:change`, event.target.checked)}
47
+ ${ref((element) => applyElementProps(element, rest))}
48
+ />
49
+ <span class="iw-switch-track" aria-hidden="true">
50
+ <span class="iw-switch-thumb"></span>
51
+ </span>
52
+ ${label ? html`<span class="iw-switch-label">${label}</span>` : null}
53
+ </label>
54
+ `
55
+ }
package/src/index.js ADDED
@@ -0,0 +1,14 @@
1
+ export { button } from "./controls/button/index.js"
2
+ export { buttonGroup } from "./controls/button-group/index.js"
3
+ export { checkbox } from "./controls/checkbox/index.js"
4
+ export { fab } from "./controls/fab/index.js"
5
+ export { iconButton } from "./controls/icon-button/index.js"
6
+ export { input } from "./controls/input/index.js"
7
+ export { radioGroup } from "./controls/radio-group/index.js"
8
+ export { rating } from "./controls/rating/index.js"
9
+ export { select } from "./controls/select/index.js"
10
+ export { slider } from "./controls/slider/index.js"
11
+ export { switchControl } from "./controls/switch/index.js"
12
+ export { card } from "./widgets/card/index.js"
13
+ export { flex } from "./layout/flex/index.js"
14
+ export { grid } from "./layout/grid/index.js"
@@ -0,0 +1,103 @@
1
+ import { html } from "@inglorious/web"
2
+
3
+ import {
4
+ makeStoryRender,
5
+ notifyActionArgType,
6
+ } from "../../stories/notifyStory.js"
7
+ import { flex } from "."
8
+
9
+ export default {
10
+ title: "Layout/Flex",
11
+ tags: ["autodocs"],
12
+ render: makeStoryRender(flex, "story-flex"),
13
+ argTypes: {
14
+ ...notifyActionArgType,
15
+ direction: {
16
+ control: "select",
17
+ options: ["row", "column", "row-reverse", "column-reverse"],
18
+ description: "Flex main-axis direction.",
19
+ },
20
+ wrap: {
21
+ control: "select",
22
+ options: ["nowrap", "wrap", "wrap-reverse"],
23
+ description: "Controls wrapping behavior of children.",
24
+ },
25
+ justify: {
26
+ control: "select",
27
+ options: ["start", "center", "end", "between", "around", "evenly"],
28
+ description: "Main-axis distribution of children.",
29
+ },
30
+ align: {
31
+ control: "select",
32
+ options: ["stretch", "start", "center", "end", "baseline"],
33
+ description: "Cross-axis alignment of children.",
34
+ },
35
+ gap: {
36
+ control: "select",
37
+ options: ["none", "sm", "md", "lg", "xl"],
38
+ description: "Spacing between children.",
39
+ },
40
+ inline: {
41
+ control: "boolean",
42
+ description: "Uses inline-flex instead of flex.",
43
+ },
44
+ fullWidth: {
45
+ control: "boolean",
46
+ description: "Expands container width to 100%.",
47
+ },
48
+ children: {
49
+ control: "object",
50
+ description: "Array of templates/content rendered as-is.",
51
+ },
52
+ },
53
+ parameters: {
54
+ docs: {
55
+ description: {
56
+ component:
57
+ "Layout primitive for one-dimensional arrangement of template children.",
58
+ },
59
+ },
60
+ },
61
+ }
62
+
63
+ export const Default = {}
64
+ Default.args = {
65
+ direction: "row",
66
+ wrap: "nowrap",
67
+ justify: "start",
68
+ align: "stretch",
69
+ gap: "md",
70
+ inline: false,
71
+ fullWidth: false,
72
+ children: [
73
+ html`<div>First</div>`,
74
+ html`<div>Second</div>`,
75
+ html`<div>Third</div>`,
76
+ ],
77
+ }
78
+
79
+ export const Vertical = {}
80
+ Vertical.args = {
81
+ ...Default.args,
82
+ direction: "column",
83
+ children: [
84
+ html`<div>Header</div>`,
85
+ html`<div>Body</div>`,
86
+ html`<div>Footer</div>`,
87
+ ],
88
+ }
89
+
90
+ export const Wrapped = {}
91
+ Wrapped.args = {
92
+ ...Default.args,
93
+ wrap: "wrap",
94
+ gap: "lg",
95
+ children: [
96
+ html`<div>One</div>`,
97
+ html`<div>Two</div>`,
98
+ html`<div>Three</div>`,
99
+ html`<div>Four</div>`,
100
+ html`<div>Five</div>`,
101
+ html`<div>Six</div>`,
102
+ ],
103
+ }
@@ -0,0 +1,4 @@
1
+ import { withRenderValidation } from "../../shared/withRenderValidation.js"
2
+ import * as renderers from "./template.js"
3
+
4
+ export const flex = [{ ...renderers }, withRenderValidation]
@@ -0,0 +1,103 @@
1
+ .iw-flex {
2
+ display: flex;
3
+
4
+ &.iw-flex-inline {
5
+ display: inline-flex;
6
+ }
7
+
8
+ &.iw-flex-full-width {
9
+ width: 100%;
10
+ }
11
+
12
+ &.iw-flex-direction-column {
13
+ flex-direction: column;
14
+ }
15
+
16
+ &.iw-flex-direction-row {
17
+ flex-direction: row;
18
+ }
19
+
20
+ &.iw-flex-direction-row-reverse {
21
+ flex-direction: row-reverse;
22
+ }
23
+
24
+ &.iw-flex-direction-column-reverse {
25
+ flex-direction: column-reverse;
26
+ }
27
+
28
+ &.iw-flex-wrap-wrap {
29
+ flex-wrap: wrap;
30
+ }
31
+
32
+ &.iw-flex-wrap-nowrap {
33
+ flex-wrap: nowrap;
34
+ }
35
+
36
+ &.iw-flex-wrap-wrap-reverse {
37
+ flex-wrap: wrap-reverse;
38
+ }
39
+
40
+ &.iw-flex-justify-center {
41
+ justify-content: center;
42
+ }
43
+
44
+ &.iw-flex-justify-start {
45
+ justify-content: flex-start;
46
+ }
47
+
48
+ &.iw-flex-justify-end {
49
+ justify-content: flex-end;
50
+ }
51
+
52
+ &.iw-flex-justify-between {
53
+ justify-content: space-between;
54
+ }
55
+
56
+ &.iw-flex-justify-around {
57
+ justify-content: space-around;
58
+ }
59
+
60
+ &.iw-flex-justify-evenly {
61
+ justify-content: space-evenly;
62
+ }
63
+
64
+ &.iw-flex-align-start {
65
+ align-items: flex-start;
66
+ }
67
+
68
+ &.iw-flex-align-stretch {
69
+ align-items: stretch;
70
+ }
71
+
72
+ &.iw-flex-align-center {
73
+ align-items: center;
74
+ }
75
+
76
+ &.iw-flex-align-end {
77
+ align-items: flex-end;
78
+ }
79
+
80
+ &.iw-flex-align-baseline {
81
+ align-items: baseline;
82
+ }
83
+
84
+ &.iw-flex-gap-none {
85
+ gap: 0;
86
+ }
87
+
88
+ &.iw-flex-gap-sm {
89
+ gap: var(--iw-space-2);
90
+ }
91
+
92
+ &.iw-flex-gap-md {
93
+ gap: var(--iw-space-4);
94
+ }
95
+
96
+ &.iw-flex-gap-lg {
97
+ gap: var(--iw-space-6);
98
+ }
99
+
100
+ &.iw-flex-gap-xl {
101
+ gap: var(--iw-space-8);
102
+ }
103
+ }
@@ -0,0 +1,41 @@
1
+ /**
2
+ * @typedef {import('../../../types/layout/flex').FlexEntity} FlexEntity
3
+ * @typedef {import('@inglorious/web').Api} Api
4
+ * @typedef {import('@inglorious/web').TemplateResult} TemplateResult
5
+ */
6
+
7
+ import { classMap, html } from "@inglorious/web"
8
+
9
+ /**
10
+ * Flex layout component for Inglorious Web.
11
+ * Children are rendered as-is (templates/content composition).
12
+ *
13
+ * @param {FlexEntity} entity
14
+ * @param {Api} api
15
+ * @returns {TemplateResult}
16
+ */
17
+ export function render(entity) {
18
+ const {
19
+ direction = "row",
20
+ wrap = "nowrap",
21
+ justify = "start",
22
+ align = "stretch",
23
+ gap = "md",
24
+ inline = false,
25
+ fullWidth = false,
26
+ children = [],
27
+ } = entity
28
+
29
+ const classes = {
30
+ "iw-flex": true,
31
+ "iw-flex-inline": inline,
32
+ "iw-flex-full-width": fullWidth,
33
+ [`iw-flex-direction-${direction}`]: true,
34
+ [`iw-flex-wrap-${wrap}`]: true,
35
+ [`iw-flex-justify-${justify}`]: true,
36
+ [`iw-flex-align-${align}`]: true,
37
+ [`iw-flex-gap-${gap}`]: true,
38
+ }
39
+
40
+ return html`<div class=${classMap(classes)}>${children}</div>`
41
+ }
@@ -0,0 +1,82 @@
1
+ import { html } from "@inglorious/web"
2
+
3
+ import {
4
+ makeStoryRender,
5
+ notifyActionArgType,
6
+ } from "../../stories/notifyStory.js"
7
+ import { grid } from "."
8
+
9
+ export default {
10
+ title: "Layout/Grid",
11
+ tags: ["autodocs"],
12
+ render: makeStoryRender(grid, "story-grid"),
13
+ argTypes: {
14
+ ...notifyActionArgType,
15
+ columns: {
16
+ control: "number",
17
+ description: "Fixed number of columns when auto-fit is not used.",
18
+ },
19
+ minColumnWidth: {
20
+ control: "text",
21
+ description: "Enables auto-fit columns with minmax(minColumnWidth, 1fr).",
22
+ },
23
+ gap: {
24
+ control: "select",
25
+ options: ["none", "sm", "md", "lg", "xl"],
26
+ description: "Spacing between grid cells.",
27
+ },
28
+ align: {
29
+ control: "select",
30
+ options: ["stretch", "start", "center", "end"],
31
+ description: "Cross-axis alignment for cell content.",
32
+ },
33
+ justify: {
34
+ control: "select",
35
+ options: ["stretch", "start", "center", "end"],
36
+ description: "Inline-axis alignment for cell content.",
37
+ },
38
+ fullWidth: {
39
+ control: "boolean",
40
+ description: "Expands container width to 100%.",
41
+ },
42
+ children: {
43
+ control: "object",
44
+ description: "Array of templates/content rendered as-is.",
45
+ },
46
+ },
47
+ parameters: {
48
+ docs: {
49
+ description: {
50
+ component:
51
+ "Layout primitive for two-dimensional arrangement of template children.",
52
+ },
53
+ },
54
+ },
55
+ }
56
+
57
+ export const Default = {}
58
+ Default.args = {
59
+ columns: 3,
60
+ gap: "md",
61
+ align: "stretch",
62
+ justify: "stretch",
63
+ fullWidth: false,
64
+ children: [
65
+ html`<div>Cell A</div>`,
66
+ html`<div>Cell B</div>`,
67
+ html`<div>Cell C</div>`,
68
+ ],
69
+ }
70
+
71
+ export const AutoFit = {}
72
+ AutoFit.args = {
73
+ ...Default.args,
74
+ minColumnWidth: "180px",
75
+ children: [
76
+ html`<div>Product</div>`,
77
+ html`<div>Price</div>`,
78
+ html`<div>Status</div>`,
79
+ html`<div>Actions</div>`,
80
+ html`<div>Notes</div>`,
81
+ ],
82
+ }
@@ -0,0 +1,4 @@
1
+ import { withRenderValidation } from "../../shared/withRenderValidation.js"
2
+ import * as renderers from "./template.js"
3
+
4
+ export const grid = [{ ...renderers }, withRenderValidation]
@@ -0,0 +1,51 @@
1
+ .iw-grid {
2
+ display: grid;
3
+ }
4
+
5
+ .iw-grid.iw-grid-full-width {
6
+ width: 100%;
7
+ }
8
+
9
+ .iw-grid.iw-grid-gap-none {
10
+ gap: 0;
11
+ }
12
+
13
+ .iw-grid.iw-grid-gap-sm {
14
+ gap: var(--iw-space-2);
15
+ }
16
+
17
+ .iw-grid.iw-grid-gap-md {
18
+ gap: var(--iw-space-4);
19
+ }
20
+
21
+ .iw-grid.iw-grid-gap-lg {
22
+ gap: var(--iw-space-6);
23
+ }
24
+
25
+ .iw-grid.iw-grid-gap-xl {
26
+ gap: var(--iw-space-8);
27
+ }
28
+
29
+ .iw-grid.iw-grid-align-start {
30
+ align-items: start;
31
+ }
32
+
33
+ .iw-grid.iw-grid-align-center {
34
+ align-items: center;
35
+ }
36
+
37
+ .iw-grid.iw-grid-align-end {
38
+ align-items: end;
39
+ }
40
+
41
+ .iw-grid.iw-grid-justify-start {
42
+ justify-items: start;
43
+ }
44
+
45
+ .iw-grid.iw-grid-justify-center {
46
+ justify-items: center;
47
+ }
48
+
49
+ .iw-grid.iw-grid-justify-end {
50
+ justify-items: end;
51
+ }
@@ -0,0 +1,47 @@
1
+ /**
2
+ * @typedef {import('../../../types/layout/grid').GridEntity} GridEntity
3
+ * @typedef {import('@inglorious/web').Api} Api
4
+ * @typedef {import('@inglorious/web').TemplateResult} TemplateResult
5
+ */
6
+
7
+ import { classMap, html, styleMap } from "@inglorious/web"
8
+
9
+ const DEFAULT_COLUMNS = 2
10
+
11
+ /**
12
+ * Grid layout component for Inglorious Web.
13
+ * Children are rendered as-is (templates/content composition).
14
+ *
15
+ * @param {GridEntity} entity
16
+ * @param {Api} _api
17
+ * @returns {TemplateResult}
18
+ */
19
+ export function render(entity) {
20
+ const {
21
+ columns = DEFAULT_COLUMNS,
22
+ minColumnWidth,
23
+ gap = "md",
24
+ align = "stretch",
25
+ justify = "stretch",
26
+ fullWidth = false,
27
+ children = [],
28
+ } = entity
29
+
30
+ const classes = {
31
+ "iw-grid": true,
32
+ "iw-grid-full-width": fullWidth,
33
+ [`iw-grid-gap-${gap}`]: true,
34
+ [`iw-grid-align-${align}`]: align !== "stretch",
35
+ [`iw-grid-justify-${justify}`]: justify !== "stretch",
36
+ }
37
+
38
+ const styles = minColumnWidth
39
+ ? {
40
+ gridTemplateColumns: `repeat(auto-fit, minmax(${minColumnWidth}, 1fr))`,
41
+ }
42
+ : { gridTemplateColumns: `repeat(${columns}, minmax(0, 1fr))` }
43
+
44
+ return html`<div class=${classMap(classes)} style=${styleMap(styles)}>
45
+ ${children}
46
+ </div>`
47
+ }
@@ -0,0 +1,79 @@
1
+ const previousKeysByElement = new WeakMap()
2
+
3
+ function clearProp(element, key) {
4
+ if (key in element && !key.includes("-")) {
5
+ try {
6
+ element[key] = undefined
7
+ } catch {
8
+ // Ignore read-only property assignments.
9
+ }
10
+ }
11
+ element.removeAttribute(key)
12
+ }
13
+
14
+ function setProp(element, key, value) {
15
+ if (value == null) {
16
+ clearProp(element, key)
17
+ return
18
+ }
19
+
20
+ if (typeof value === "boolean") {
21
+ if (value) {
22
+ element.setAttribute(key, "")
23
+ } else {
24
+ element.removeAttribute(key)
25
+ }
26
+
27
+ if (key in element && !key.includes("-")) {
28
+ try {
29
+ element[key] = value
30
+ } catch {
31
+ // Ignore read-only property assignments.
32
+ }
33
+ }
34
+ return
35
+ }
36
+
37
+ if (key === "style" && typeof value === "object" && value !== null) {
38
+ Object.assign(element.style, value)
39
+ return
40
+ }
41
+
42
+ if (key in element && !key.includes("-")) {
43
+ try {
44
+ element[key] = value
45
+ return
46
+ } catch {
47
+ // Fallback to attribute below.
48
+ }
49
+ }
50
+
51
+ element.setAttribute(key, String(value))
52
+ }
53
+
54
+ /**
55
+ * Apply arbitrary properties/attributes to an element.
56
+ * Useful when entities include extra DOM fields beyond the typed API.
57
+ *
58
+ * @param {Element | null} element
59
+ * @param {Record<string, unknown>} props
60
+ */
61
+ export function applyElementProps(element, props) {
62
+ if (!element) return
63
+
64
+ const previousKeys = previousKeysByElement.get(element) ?? new Set()
65
+ const nextKeys = new Set(Object.keys(props))
66
+
67
+ for (const key of previousKeys) {
68
+ if (!nextKeys.has(key)) {
69
+ clearProp(element, key)
70
+ }
71
+ }
72
+
73
+ for (const [key, value] of Object.entries(props)) {
74
+ if (key === "class" || key === "className") continue
75
+ setProp(element, key, value)
76
+ }
77
+
78
+ previousKeysByElement.set(element, nextKeys)
79
+ }
@@ -0,0 +1,19 @@
1
+ export function withRenderValidation(type) {
2
+ if (typeof type.render !== "function") {
3
+ throw new TypeError(
4
+ "withRenderValidation(type): type.render must be a function",
5
+ )
6
+ }
7
+
8
+ return {
9
+ ...type,
10
+
11
+ render(entity, api) {
12
+ if (entity == null || api == null) {
13
+ throw new TypeError("render(entity, api): both arguments are required")
14
+ }
15
+
16
+ return type.render(entity, api)
17
+ },
18
+ }
19
+ }
@@ -0,0 +1,43 @@
1
+ import { augmentType } from "@inglorious/store/types"
2
+ import { createMockApi, render } from "@inglorious/web/test"
3
+
4
+ export const notifyActionArgType = {
5
+ onNotify: {
6
+ action: "api.notify",
7
+ control: false,
8
+ table: { disable: true },
9
+ },
10
+ }
11
+
12
+ export function makeStoryRender(typeDefinition, id, options = {}) {
13
+ const { resolveByEntityType = false } = options
14
+ const type = augmentType(typeDefinition)
15
+
16
+ return (args) => {
17
+ const container = document.createElement("div")
18
+ const { onNotify, ...entityArgs } = args
19
+ const entity = { id, ...entityArgs }
20
+ const api = createInstrumentedApi(entity, onNotify)
21
+
22
+ if (resolveByEntityType && entity.type) {
23
+ const getType = api.getType?.bind(api)
24
+ api.getType = (typeName) =>
25
+ typeName === entity.type ? type : getType?.(typeName)
26
+ }
27
+
28
+ render(type.render(entity, api), container)
29
+ return container
30
+ }
31
+ }
32
+
33
+ function createInstrumentedApi(entityOrState, onNotify) {
34
+ const api = createMockApi(entityOrState)
35
+ const originalNotify = api.notify.bind(api)
36
+
37
+ api.notify = (type, payload) => {
38
+ onNotify?.({ type, payload })
39
+ return originalNotify(type, payload)
40
+ }
41
+
42
+ return api
43
+ }