@mittwald/flow-react-components 1.6.0-next.20 → 1.6.0-next.22

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 (38) hide show
  1. package/dist/assets/component-index.json +1 -1
  2. package/dist/assets/doc-properties.json +1 -1
  3. package/dist/js/default.mjs +1 -1
  4. package/dist/js/packages/components/src/components/CodeEditor/CodeEditor.mjs +41 -12
  5. package/dist/js/packages/components/src/components/CodeEditor/CodeEditor.mjs.map +1 -1
  6. package/dist/js/packages/components/src/components/CodeEditor/hooks/useCodeEditorExtensions.mjs +29 -16
  7. package/dist/js/packages/components/src/components/CodeEditor/hooks/useCodeEditorExtensions.mjs.map +1 -1
  8. package/dist/js/packages/components/src/components/List/components/Items/Items.mjs +3 -0
  9. package/dist/js/packages/components/src/components/List/components/Items/Items.mjs.map +1 -1
  10. package/dist/js/packages/components/src/components/List/components/Items/components/Item/Item.mjs +1 -1
  11. package/dist/js/packages/components/src/components/List/components/Items/components/Item/Item.mjs.map +1 -1
  12. package/dist/js/packages/components/src/components/List/components/Items/components/Item/hooks/useGridItemProps.mjs +1 -2
  13. package/dist/js/packages/components/src/components/List/components/Items/components/Item/hooks/useGridItemProps.mjs.map +1 -1
  14. package/dist/js/packages/components/src/components/List/components/Table/Table.mjs +8 -12
  15. package/dist/js/packages/components/src/components/List/components/Table/Table.mjs.map +1 -1
  16. package/dist/js/packages/components/src/components/List/components/Table/components/Row.mjs +31 -0
  17. package/dist/js/packages/components/src/components/List/components/Table/components/Row.mjs.map +1 -0
  18. package/dist/js/packages/components/src/components/List/hooks/useLatestOnAction.mjs +21 -0
  19. package/dist/js/packages/components/src/components/List/hooks/useLatestOnAction.mjs.map +1 -0
  20. package/dist/js/packages/components/src/components/List/model/List.mjs +1 -0
  21. package/dist/js/packages/components/src/components/List/model/List.mjs.map +1 -1
  22. package/dist/js/packages/components/src/components/List/model/item/ItemView.mjs +20 -7
  23. package/dist/js/packages/components/src/components/List/model/item/ItemView.mjs.map +1 -1
  24. package/dist/js/packages/components/src/components/List/model/table/TableCell.mjs +1 -1
  25. package/dist/js/packages/components/src/components/List/model/table/TableCell.mjs.map +1 -1
  26. package/dist/js/packages/components/src/components/List/model/table/TableRow.mjs +28 -1
  27. package/dist/js/packages/components/src/components/List/model/table/TableRow.mjs.map +1 -1
  28. package/dist/js/packages/components/src/components/Markdown/Markdown.mjs +9 -5
  29. package/dist/js/packages/components/src/components/Markdown/Markdown.mjs.map +1 -1
  30. package/dist/types/components/List/components/Items/components/Item/Item.d.ts +2 -0
  31. package/dist/types/components/List/components/Items/components/Item/hooks/useGridItemProps.d.ts +1 -0
  32. package/dist/types/components/List/components/Table/components/Row.d.ts +13 -0
  33. package/dist/types/components/List/hooks/useLatestOnAction.d.ts +7 -0
  34. package/dist/types/components/List/model/item/ItemView.d.ts +34 -11
  35. package/dist/types/components/List/model/table/TableRow.d.ts +14 -0
  36. package/dist/types/components/List/model/table/types.d.ts +17 -1
  37. package/dist/types/components/List/typedList.d.ts +1 -0
  38. package/package.json +10 -10
@@ -1,8 +1,8 @@
1
1
  "use client"
2
2
  /* */
3
3
  import { createElement } from "react";
4
+ import { isShallowEqual } from "remeda";
4
5
  //#region src/components/List/model/item/ItemView.ts
5
- var dependenciesAreEqual = (a, b) => a === b || !!a && !!b && a.length === b.length && a.every((dependency, index) => Object.is(dependency, b[index]));
6
6
  var ItemView = class ItemView {
7
7
  list;
8
8
  textValue;
@@ -13,10 +13,12 @@ var ItemView = class ItemView {
13
13
  showTiles;
14
14
  showList;
15
15
  tileMaxWidth;
16
+ renderDependencies;
16
17
  dependencies;
17
18
  renderFn;
19
+ lastComparison;
18
20
  constructor(list, shape = {}) {
19
- const { fallback, loadingView = fallback, textValue, href, target, defaultExpanded, renderFn, showTiles, showList = true, tileMaxWidth = 230, dependencies } = shape;
21
+ const { fallback, loadingView = fallback, textValue, href, target, defaultExpanded, renderFn, showTiles, showList = true, tileMaxWidth = 230, renderDependencies, dependencies } = shape;
20
22
  this.list = list;
21
23
  this.textValue = textValue;
22
24
  this.renderFn = renderFn;
@@ -27,16 +29,27 @@ var ItemView = class ItemView {
27
29
  this.showTiles = showTiles;
28
30
  this.showList = showList;
29
31
  this.tileMaxWidth = tileMaxWidth;
32
+ this.renderDependencies = renderDependencies;
30
33
  this.dependencies = dependencies;
31
34
  }
32
35
  static fallbackRenderItemFn = (item) => createElement("pre", void 0, JSON.stringify(item));
33
36
  /**
34
- * Whether `other` would render this item identically — i.e. all render
35
- * functions taken from the consumer's `ListItem` element are still the same,
36
- * and the dependencies it declared have not changed.
37
+ * Whether `other` would render the item holding `data` identically. The
38
+ * render function counts by its `renderDependencies` where given, otherwise
39
+ * by its identity and the deprecated `dependencies`. `textValue` and `href`
40
+ * count by what they return for `data`, so inline functions do not re-render
41
+ * the item. `defaultExpanded` is read on mount only, so it does not count at
42
+ * all.
37
43
  */
38
- rendersSameAs(other) {
39
- return !!other && dependenciesAreEqual(this.dependencies, other.dependencies) && this.renderFn === other.renderFn && this.textValue === other.textValue && this.href === other.href && this.target === other.target && this.defaultExpanded === other.defaultExpanded;
44
+ rendersSameFor(other, data) {
45
+ return !!other && this.rendersSameContentAs(other) && this.textValue?.(data) === other.textValue?.(data) && this.href?.(data) === other.href?.(data) && this.target === other.target;
46
+ }
47
+ rendersSameContentAs(other) {
48
+ if (this.lastComparison?.other !== other) this.lastComparison = {
49
+ other,
50
+ result: this.renderDependencies || other.renderDependencies ? isShallowEqual(this.renderDependencies, other.renderDependencies) : this.renderFn === other.renderFn && isShallowEqual(this.dependencies, other.dependencies)
51
+ };
52
+ return this.lastComparison.result;
40
53
  }
41
54
  render(data) {
42
55
  return (this.renderFn ?? ItemView.fallbackRenderItemFn)(data, this.list);
@@ -1 +1 @@
1
- {"version":3,"file":"ItemView.mjs","names":[],"sources":["../../../../../../../../../src/components/List/model/item/ItemView.ts"],"sourcesContent":["import type {\n DependencyList,\n HTMLAttributeAnchorTarget,\n ReactElement,\n ReactNode,\n} from \"react\";\nimport { createElement } from \"react\";\nimport type { RenderItemFn } from \"@/components/List/model/item/types\";\nimport type List from \"@/components/List/model/List\";\n\nexport interface ItemViewShape<T> {\n textValue?: (data: T) => string;\n href?: (data: T) => string;\n target?: HTMLAttributeAnchorTarget;\n defaultExpanded?: (data: T) => boolean;\n renderFn?: RenderItemFn<T>;\n /** @deprecated Use loadingView instead */\n fallback?: ReactElement;\n loadingView?: ReactElement;\n showList?: boolean;\n showTiles?: boolean;\n tileMaxWidth?: number;\n /**\n * Values this item's content depends on besides its own data.\n *\n * An item re-renders when its data changes, or when one of the render\n * functions on its `List.Item` changes identity — which a function written\n * inline in JSX does on every render of its parent. A render function with a\n * stable identity (hoisted out of the component, wrapped in `useCallback`, or\n * reading from an external store) has to list what it reads here, or the item\n * keeps the values it first saw.\n */\n dependencies?: DependencyList;\n}\n\nconst dependenciesAreEqual = (a?: DependencyList, b?: DependencyList) =>\n a === b ||\n (!!a &&\n !!b &&\n a.length === b.length &&\n a.every((dependency, index) => Object.is(dependency, b[index])));\n\nexport class ItemView<T> {\n public readonly list: List<T>;\n public readonly textValue?: (data: T) => string;\n public readonly href?: (data: T) => string;\n public readonly target?: HTMLAttributeAnchorTarget;\n public readonly defaultExpanded?: (data: T) => boolean;\n public readonly loadingView?: ReactElement;\n public readonly showTiles?: boolean;\n public readonly showList?: boolean;\n public readonly tileMaxWidth: number;\n public readonly dependencies?: DependencyList;\n private readonly renderFn?: RenderItemFn<T>;\n\n public constructor(list: List<T>, shape: ItemViewShape<T> = {}) {\n const {\n fallback,\n loadingView = fallback,\n textValue,\n href,\n target,\n defaultExpanded,\n renderFn,\n showTiles,\n showList = true,\n tileMaxWidth = 230,\n dependencies,\n } = shape;\n this.list = list;\n this.textValue = textValue;\n this.renderFn = renderFn;\n this.href = href;\n this.target = target;\n this.defaultExpanded = defaultExpanded;\n this.loadingView = loadingView;\n this.showTiles = showTiles;\n this.showList = showList;\n this.tileMaxWidth = tileMaxWidth;\n this.dependencies = dependencies;\n }\n\n private static fallbackRenderItemFn: RenderItemFn<never> = (item) =>\n createElement(\"pre\", undefined, JSON.stringify(item));\n\n /**\n * Whether `other` would render this item identically — i.e. all render\n * functions taken from the consumer's `ListItem` element are still the same,\n * and the dependencies it declared have not changed.\n */\n public rendersSameAs(other?: ItemView<T>): boolean {\n return (\n !!other &&\n dependenciesAreEqual(this.dependencies, other.dependencies) &&\n this.renderFn === other.renderFn &&\n this.textValue === other.textValue &&\n this.href === other.href &&\n this.target === other.target &&\n this.defaultExpanded === other.defaultExpanded\n );\n }\n\n public render(data: T): ReactNode {\n const renderFn = (this.renderFn ??\n ItemView.fallbackRenderItemFn) as RenderItemFn<T>;\n return renderFn(data as never, this.list);\n }\n}\n"],"mappings":";;;;AAmCA,IAAM,wBAAwB,GAAoB,MAChD,MAAM,KACL,CAAC,CAAC,KACD,CAAC,CAAC,KACF,EAAE,WAAW,EAAE,UACf,EAAE,OAAO,YAAY,UAAU,OAAO,GAAG,YAAY,EAAE,MAAM,CAAC;AAElE,IAAa,WAAb,MAAa,SAAY;CACvB;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CAEA,YAAmB,MAAe,QAA0B,CAAC,GAAG;EAC9D,MAAM,EACJ,UACA,cAAc,UACd,WACA,MACA,QACA,iBACA,UACA,WACA,WAAW,MACX,eAAe,KACf,iBACE;EACJ,KAAK,OAAO;EACZ,KAAK,YAAY;EACjB,KAAK,WAAW;EAChB,KAAK,OAAO;EACZ,KAAK,SAAS;EACd,KAAK,kBAAkB;EACvB,KAAK,cAAc;EACnB,KAAK,YAAY;EACjB,KAAK,WAAW;EAChB,KAAK,eAAe;EACpB,KAAK,eAAe;CACtB;CAEA,OAAe,wBAA6C,SAC1D,cAAc,OAAO,KAAA,GAAW,KAAK,UAAU,IAAI,CAAC;;;;;;CAOtD,cAAqB,OAA8B;EACjD,OACE,CAAC,CAAC,SACF,qBAAqB,KAAK,cAAc,MAAM,YAAY,KAC1D,KAAK,aAAa,MAAM,YACxB,KAAK,cAAc,MAAM,aACzB,KAAK,SAAS,MAAM,QACpB,KAAK,WAAW,MAAM,UACtB,KAAK,oBAAoB,MAAM;CAEnC;CAEA,OAAc,MAAoB;EAGhC,QAFkB,KAAK,YACrB,SAAS,qBAAA,CACK,MAAe,KAAK,IAAI;CAC1C;AACF"}
1
+ {"version":3,"file":"ItemView.mjs","names":[],"sources":["../../../../../../../../../src/components/List/model/item/ItemView.ts"],"sourcesContent":["import type {\n DependencyList,\n HTMLAttributeAnchorTarget,\n ReactElement,\n ReactNode,\n} from \"react\";\nimport { createElement } from \"react\";\nimport type { RenderItemFn } from \"@/components/List/model/item/types\";\nimport type List from \"@/components/List/model/List\";\nimport { isShallowEqual } from \"remeda\";\n\nexport interface ItemViewShape<T> {\n /**\n * Runs on every render of the list to tell whether the item changed, so it\n * should be cheap and pure.\n */\n textValue?: (data: T) => string;\n /**\n * Runs on every render of the list to tell whether the item changed, so it\n * should be cheap and pure.\n */\n href?: (data: T) => string;\n target?: HTMLAttributeAnchorTarget;\n defaultExpanded?: (data: T) => boolean;\n renderFn?: RenderItemFn<T>;\n /** @deprecated Use loadingView instead */\n fallback?: ReactElement;\n loadingView?: ReactElement;\n showList?: boolean;\n showTiles?: boolean;\n tileMaxWidth?: number;\n /**\n * Everything the render function reads besides the item's data.\n *\n * With `renderDependencies`, an item re-renders only when its data or one of\n * these values changes. The render function's identity no longer counts, so a\n * function written inline in JSX keeps the item from re-rendering on every\n * render of its parent. List every value it reads from outside the item,\n * including state read through the `list` argument — one left out keeps the\n * item on the value it last rendered.\n *\n * Without it, an item re-renders whenever its render function changes\n * identity, which an inline function does on every render of its parent.\n */\n renderDependencies?: DependencyList;\n /**\n * @deprecated Use `renderDependencies` instead. `dependencies` only adds to\n * the render function's identity, so an inline render function still\n * re-renders the item on every render of its parent.\n */\n dependencies?: DependencyList;\n}\n\nexport class ItemView<T> {\n public readonly list: List<T>;\n public readonly textValue?: (data: T) => string;\n public readonly href?: (data: T) => string;\n public readonly target?: HTMLAttributeAnchorTarget;\n public readonly defaultExpanded?: (data: T) => boolean;\n public readonly loadingView?: ReactElement;\n public readonly showTiles?: boolean;\n public readonly showList?: boolean;\n public readonly tileMaxWidth: number;\n public readonly renderDependencies?: DependencyList;\n public readonly dependencies?: DependencyList;\n private readonly renderFn?: RenderItemFn<T>;\n private lastComparison?: { other: ItemView<T>; result: boolean };\n\n public constructor(list: List<T>, shape: ItemViewShape<T> = {}) {\n const {\n fallback,\n loadingView = fallback,\n textValue,\n href,\n target,\n defaultExpanded,\n renderFn,\n showTiles,\n showList = true,\n tileMaxWidth = 230,\n renderDependencies,\n dependencies,\n } = shape;\n this.list = list;\n this.textValue = textValue;\n this.renderFn = renderFn;\n this.href = href;\n this.target = target;\n this.defaultExpanded = defaultExpanded;\n this.loadingView = loadingView;\n this.showTiles = showTiles;\n this.showList = showList;\n this.tileMaxWidth = tileMaxWidth;\n this.renderDependencies = renderDependencies;\n this.dependencies = dependencies;\n }\n\n private static fallbackRenderItemFn: RenderItemFn<never> = (item) =>\n createElement(\"pre\", undefined, JSON.stringify(item));\n\n /**\n * Whether `other` would render the item holding `data` identically. The\n * render function counts by its `renderDependencies` where given, otherwise\n * by its identity and the deprecated `dependencies`. `textValue` and `href`\n * count by what they return for `data`, so inline functions do not re-render\n * the item. `defaultExpanded` is read on mount only, so it does not count at\n * all.\n */\n public rendersSameFor(other: ItemView<T> | undefined, data: T): boolean {\n return (\n !!other &&\n this.rendersSameContentAs(other) &&\n this.textValue?.(data) === other.textValue?.(data) &&\n this.href?.(data) === other.href?.(data) &&\n this.target === other.target\n );\n }\n\n // Every item of a render shares one item view, so the result is cached for\n // the last `other`.\n private rendersSameContentAs(other: ItemView<T>): boolean {\n if (this.lastComparison?.other !== other) {\n this.lastComparison = {\n other,\n result:\n this.renderDependencies || other.renderDependencies\n ? isShallowEqual(this.renderDependencies, other.renderDependencies)\n : this.renderFn === other.renderFn &&\n isShallowEqual(this.dependencies, other.dependencies),\n };\n }\n return this.lastComparison.result;\n }\n\n public render(data: T): ReactNode {\n const renderFn = (this.renderFn ??\n ItemView.fallbackRenderItemFn) as RenderItemFn<T>;\n return renderFn(data as never, this.list);\n }\n}\n"],"mappings":";;;;;AAqDA,IAAa,WAAb,MAAa,SAAY;CACvB;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CAEA,YAAmB,MAAe,QAA0B,CAAC,GAAG;EAC9D,MAAM,EACJ,UACA,cAAc,UACd,WACA,MACA,QACA,iBACA,UACA,WACA,WAAW,MACX,eAAe,KACf,oBACA,iBACE;EACJ,KAAK,OAAO;EACZ,KAAK,YAAY;EACjB,KAAK,WAAW;EAChB,KAAK,OAAO;EACZ,KAAK,SAAS;EACd,KAAK,kBAAkB;EACvB,KAAK,cAAc;EACnB,KAAK,YAAY;EACjB,KAAK,WAAW;EAChB,KAAK,eAAe;EACpB,KAAK,qBAAqB;EAC1B,KAAK,eAAe;CACtB;CAEA,OAAe,wBAA6C,SAC1D,cAAc,OAAO,KAAA,GAAW,KAAK,UAAU,IAAI,CAAC;;;;;;;;;CAUtD,eAAsB,OAAgC,MAAkB;EACtE,OACE,CAAC,CAAC,SACF,KAAK,qBAAqB,KAAK,KAC/B,KAAK,YAAY,IAAI,MAAM,MAAM,YAAY,IAAI,KACjD,KAAK,OAAO,IAAI,MAAM,MAAM,OAAO,IAAI,KACvC,KAAK,WAAW,MAAM;CAE1B;CAIA,qBAA6B,OAA6B;EACxD,IAAI,KAAK,gBAAgB,UAAU,OACjC,KAAK,iBAAiB;GACpB;GACA,QACE,KAAK,sBAAsB,MAAM,qBAC7B,eAAe,KAAK,oBAAoB,MAAM,kBAAkB,IAChE,KAAK,aAAa,MAAM,YACxB,eAAe,KAAK,cAAc,MAAM,YAAY;EAC5D;EAEF,OAAO,KAAK,eAAe;CAC7B;CAEA,OAAc,MAAoB;EAGhC,QAFkB,KAAK,YACrB,SAAS,qBAAA,CACK,MAAe,KAAK,IAAI;CAC1C;AACF"}
@@ -7,7 +7,7 @@ var TableCell = class {
7
7
  componentProps;
8
8
  loadingView;
9
9
  constructor(row, shape = {}) {
10
- const { renderFn, loadingView, ...rest } = shape;
10
+ const { renderFn, loadingView, children: ignoredChildren, ...rest } = shape;
11
11
  this.row = row;
12
12
  this.renderFn = renderFn;
13
13
  this.componentProps = rest;
@@ -1 +1 @@
1
- {"version":3,"file":"TableCell.mjs","names":[],"sources":["../../../../../../../../../src/components/List/model/table/TableCell.ts"],"sourcesContent":["import type {\n TableCellShape,\n TableCellSupportedComponentProps,\n} from \"@/components/List/model/table/types\";\nimport type { TableRow } from \"@/components/List/model/table/TableRow\";\nimport type { RenderItemFn } from \"@/components/List\";\nimport type { ReactElement } from \"react\";\n\nexport class TableCell<T> {\n public readonly row: TableRow<T>;\n public readonly renderFn?: RenderItemFn<T>;\n public readonly componentProps: TableCellSupportedComponentProps;\n loadingView?: ReactElement;\n\n public constructor(row: TableRow<T>, shape: TableCellShape<T> = {}) {\n const { renderFn, loadingView, ...rest } = shape;\n this.row = row;\n this.renderFn = renderFn;\n this.componentProps = rest;\n this.loadingView = loadingView;\n }\n}\n"],"mappings":";;;AAQA,IAAa,YAAb,MAA0B;CACxB;CACA;CACA;CACA;CAEA,YAAmB,KAAkB,QAA2B,CAAC,GAAG;EAClE,MAAM,EAAE,UAAU,aAAa,GAAG,SAAS;EAC3C,KAAK,MAAM;EACX,KAAK,WAAW;EAChB,KAAK,iBAAiB;EACtB,KAAK,cAAc;CACrB;AACF"}
1
+ {"version":3,"file":"TableCell.mjs","names":[],"sources":["../../../../../../../../../src/components/List/model/table/TableCell.ts"],"sourcesContent":["import type {\n TableCellShape,\n TableCellSupportedComponentProps,\n} from \"@/components/List/model/table/types\";\nimport type { TableRow } from \"@/components/List/model/table/TableRow\";\nimport type { RenderItemFn } from \"@/components/List\";\nimport type { ReactElement } from \"react\";\n\nexport class TableCell<T> {\n public readonly row: TableRow<T>;\n public readonly renderFn?: RenderItemFn<T>;\n public readonly componentProps: TableCellSupportedComponentProps;\n loadingView?: ReactElement;\n\n public constructor(row: TableRow<T>, shape: TableCellShape<T> = {}) {\n // `children` is the render function, which arrives as `renderFn`.\n const {\n renderFn,\n loadingView,\n children: ignoredChildren,\n ...rest\n } = shape as TableCellShape<T> & { children?: unknown };\n this.row = row;\n this.renderFn = renderFn;\n this.componentProps = rest;\n this.loadingView = loadingView;\n }\n}\n"],"mappings":";;;AAQA,IAAa,YAAb,MAA0B;CACxB;CACA;CACA;CACA;CAEA,YAAmB,KAAkB,QAA2B,CAAC,GAAG;EAElE,MAAM,EACJ,UACA,aACA,UAAU,iBACV,GAAG,SACD;EACJ,KAAK,MAAM;EACX,KAAK,WAAW;EAChB,KAAK,iBAAiB;EACtB,KAAK,cAAc;CACrB;AACF"}
@@ -1,16 +1,43 @@
1
1
  "use client"
2
2
  /* */
3
3
  import { TableCell } from "./TableCell.mjs";
4
+ import { isShallowEqual } from "remeda";
4
5
  //#region src/components/List/model/table/TableRow.ts
5
6
  var TableRow = class {
6
7
  tableBody;
7
8
  cells;
8
9
  componentProps;
10
+ renderDependencies;
11
+ lastComparison;
9
12
  constructor(tableBody, shape = {}) {
10
- const { cells = [], ...rest } = shape;
13
+ const { cells = [], renderDependencies, children: ignoredChildren, ...rest } = shape;
11
14
  this.tableBody = tableBody;
12
15
  this.cells = cells.map((cellShape) => new TableCell(this, cellShape));
13
16
  this.componentProps = rest;
17
+ this.renderDependencies = renderDependencies;
18
+ }
19
+ /**
20
+ * Whether `other` would render a row identically. Only rows that declare
21
+ * `renderDependencies` can tell, since the cells' render functions count by
22
+ * those instead of their identity. Rows without them always re-render. Row
23
+ * and cell props count by identity, one by one.
24
+ *
25
+ * Every row of a render shares one model, so the result is cached for the
26
+ * last `other`.
27
+ */
28
+ rendersSameAs(other) {
29
+ if (!other) return false;
30
+ if (this.lastComparison?.other !== other) this.lastComparison = {
31
+ other,
32
+ result: this.compareWith(other)
33
+ };
34
+ return this.lastComparison.result;
35
+ }
36
+ compareWith(other) {
37
+ return !!this.renderDependencies && isShallowEqual(this.renderDependencies, other.renderDependencies) && isShallowEqual(this.componentProps, other.componentProps) && this.cells.length === other.cells.length && this.cells.every((cell, index) => {
38
+ const otherCell = other.cells[index];
39
+ return !!otherCell && isShallowEqual(cell.componentProps, otherCell.componentProps);
40
+ });
14
41
  }
15
42
  };
16
43
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"TableRow.mjs","names":[],"sources":["../../../../../../../../../src/components/List/model/table/TableRow.ts"],"sourcesContent":["import type {\n TableRowShape,\n TableRowSupportedComponentProps,\n} from \"@/components/List/model/table/types\";\nimport { TableCell } from \"@/components/List/model/table/TableCell\";\nimport type { TableBody } from \"@/components/List/model/table/TableBody\";\n\nexport class TableRow<T> {\n public readonly tableBody: TableBody<T>;\n public readonly cells: TableCell<T>[];\n public readonly componentProps: TableRowSupportedComponentProps;\n\n public constructor(tableBody: TableBody<T>, shape: TableRowShape<T> = {}) {\n const { cells = [], ...rest } = shape;\n this.tableBody = tableBody;\n this.cells = cells.map((cellShape) => new TableCell(this, cellShape));\n this.componentProps = rest;\n }\n}\n"],"mappings":";;;;AAOA,IAAa,WAAb,MAAyB;CACvB;CACA;CACA;CAEA,YAAmB,WAAyB,QAA0B,CAAC,GAAG;EACxE,MAAM,EAAE,QAAQ,CAAC,GAAG,GAAG,SAAS;EAChC,KAAK,YAAY;EACjB,KAAK,QAAQ,MAAM,KAAK,cAAc,IAAI,UAAU,MAAM,SAAS,CAAC;EACpE,KAAK,iBAAiB;CACxB;AACF"}
1
+ {"version":3,"file":"TableRow.mjs","names":[],"sources":["../../../../../../../../../src/components/List/model/table/TableRow.ts"],"sourcesContent":["import type {\n TableRowShape,\n TableRowSupportedComponentProps,\n} from \"@/components/List/model/table/types\";\nimport { TableCell } from \"@/components/List/model/table/TableCell\";\nimport type { TableBody } from \"@/components/List/model/table/TableBody\";\nimport type { DependencyList } from \"react\";\nimport { isShallowEqual } from \"remeda\";\n\nexport class TableRow<T> {\n public readonly tableBody: TableBody<T>;\n public readonly cells: TableCell<T>[];\n public readonly componentProps: TableRowSupportedComponentProps;\n public readonly renderDependencies?: DependencyList;\n private lastComparison?: { other: TableRow<T>; result: boolean };\n\n public constructor(tableBody: TableBody<T>, shape: TableRowShape<T> = {}) {\n // `children` holds the cell elements, which arrive as `cells`.\n const {\n cells = [],\n renderDependencies,\n children: ignoredChildren,\n ...rest\n } = shape;\n this.tableBody = tableBody;\n this.cells = cells.map((cellShape) => new TableCell(this, cellShape));\n this.componentProps = rest;\n this.renderDependencies = renderDependencies;\n }\n\n /**\n * Whether `other` would render a row identically. Only rows that declare\n * `renderDependencies` can tell, since the cells' render functions count by\n * those instead of their identity. Rows without them always re-render. Row\n * and cell props count by identity, one by one.\n *\n * Every row of a render shares one model, so the result is cached for the\n * last `other`.\n */\n public rendersSameAs(other?: TableRow<T>): boolean {\n if (!other) {\n return false;\n }\n if (this.lastComparison?.other !== other) {\n this.lastComparison = { other, result: this.compareWith(other) };\n }\n return this.lastComparison.result;\n }\n\n private compareWith(other: TableRow<T>): boolean {\n return (\n !!this.renderDependencies &&\n isShallowEqual(this.renderDependencies, other.renderDependencies) &&\n isShallowEqual(this.componentProps, other.componentProps) &&\n this.cells.length === other.cells.length &&\n this.cells.every((cell, index) => {\n const otherCell = other.cells[index];\n return (\n !!otherCell &&\n isShallowEqual(cell.componentProps, otherCell.componentProps)\n );\n })\n );\n }\n}\n"],"mappings":";;;;;AASA,IAAa,WAAb,MAAyB;CACvB;CACA;CACA;CACA;CACA;CAEA,YAAmB,WAAyB,QAA0B,CAAC,GAAG;EAExE,MAAM,EACJ,QAAQ,CAAC,GACT,oBACA,UAAU,iBACV,GAAG,SACD;EACJ,KAAK,YAAY;EACjB,KAAK,QAAQ,MAAM,KAAK,cAAc,IAAI,UAAU,MAAM,SAAS,CAAC;EACpE,KAAK,iBAAiB;EACtB,KAAK,qBAAqB;CAC5B;;;;;;;;;;CAWA,cAAqB,OAA8B;EACjD,IAAI,CAAC,OACH,OAAO;EAET,IAAI,KAAK,gBAAgB,UAAU,OACjC,KAAK,iBAAiB;GAAE;GAAO,QAAQ,KAAK,YAAY,KAAK;EAAE;EAEjE,OAAO,KAAK,eAAe;CAC7B;CAEA,YAAoB,OAA6B;EAC/C,OACE,CAAC,CAAC,KAAK,sBACP,eAAe,KAAK,oBAAoB,MAAM,kBAAkB,KAChE,eAAe,KAAK,gBAAgB,MAAM,cAAc,KACxD,KAAK,MAAM,WAAW,MAAM,MAAM,UAClC,KAAK,MAAM,OAAO,MAAM,UAAU;GAChC,MAAM,YAAY,MAAM,MAAM;GAC9B,OACE,CAAC,CAAC,aACF,eAAe,KAAK,gBAAgB,UAAU,cAAc;EAEhE,CAAC;CAEL;AACF"}
@@ -13,7 +13,7 @@ import { getHeadingLevelWithOffset } from "./lib/getHeadingLevelWithOffset.mjs";
13
13
  import { isExternalHref } from "./lib/isExternalHref.mjs";
14
14
  import { SkeletonBlockText, SkeletonInlineContent } from "./components/SkeletonBlocks.mjs";
15
15
  import clsx from "clsx";
16
- import { Children, isValidElement, useId } from "react";
16
+ import { Children, isValidElement, useId, useMemo } from "react";
17
17
  import { jsx } from "react/jsx-runtime";
18
18
  import ReactMarkdown from "react-markdown";
19
19
  import remarkGfm from "remark-gfm";
@@ -26,7 +26,7 @@ var Markdown = (props) => {
26
26
  const { children, color = "default", className, headingOffset = 0, components: customComponents, ref, allowElement, allowedElements, disallowedElements, rehypePlugins, remarkPlugins, remarkRehypeOptions, skipHtml, unwrapDisallowed, urlTransform, ...rest } = props;
27
27
  const isSkeleton = useSkeletonMode();
28
28
  const clobberPrefix = `user-content-${useId().replace(/[^\w-]/g, "")}-`;
29
- const defaultComponents = {
29
+ const defaultComponents = useMemo(() => ({
30
30
  a: (props) => /* @__PURE__ */ jsx(Link, {
31
31
  id: props.id,
32
32
  "aria-label": props["aria-label"],
@@ -115,12 +115,16 @@ var Markdown = (props) => {
115
115
  color,
116
116
  children: /* @__PURE__ */ jsx("blockquote", { children: props.children })
117
117
  })
118
- };
118
+ }), [
119
+ color,
120
+ headingOffset,
121
+ isSkeleton
122
+ ]);
119
123
  const textContent = extractTextFromFirstChild(children);
120
- const mergedComponents = {
124
+ const mergedComponents = useMemo(() => ({
121
125
  ...defaultComponents,
122
126
  ...customComponents
123
- };
127
+ }), [defaultComponents, customComponents]);
124
128
  return /* @__PURE__ */ jsx("div", {
125
129
  className: clsx(Markdown_module_default.markdown, className),
126
130
  ...rest,
@@ -1 +1 @@
1
- {"version":3,"file":"Markdown.mjs","names":["styles"],"sources":["../../../../../../../src/components/Markdown/Markdown.tsx"],"sourcesContent":["import { CodeBlock } from \"@/components/CodeBlock\";\nimport { Heading } from \"@/components/Heading\";\nimport { InlineCode } from \"@/components/InlineCode\";\nimport { Link } from \"@/components/Link\";\nimport { Separator } from \"@/components/Separator\";\nimport { Text } from \"@/components/Text\";\nimport type { CSSProperties, FC, Ref } from \"react\";\nimport { Children, isValidElement, useId } from \"react\";\nimport type { Components, Options } from \"react-markdown\";\nimport ReactMarkdown from \"react-markdown\";\nimport styles from \"./Markdown.module.scss\";\nimport { extractTextFromFirstChild } from \"@/lib/react/remote\";\nimport type { AlphaColor, PropsWithClassName } from \"@/lib/types/props\";\nimport clsx from \"clsx\";\nimport remarkGfm from \"remark-gfm\";\nimport { getHeadingLevelWithOffset } from \"@/components/Markdown/lib/getHeadingLevelWithOffset\";\nimport { isExternalHref } from \"@/components/Markdown/lib/isExternalHref\";\nimport type { CodeEditorLanguage } from \"@/components/CodeEditor/languages\";\nimport {\n SkeletonBlockText,\n SkeletonInlineContent,\n} from \"@/components/Markdown/components/SkeletonBlocks\";\nimport { useSkeletonMode } from \"@/components/SkeletonMode/skeletonModeContext\";\n\nexport interface MarkdownProps\n extends PropsWithClassName, Omit<Options, \"components\"> {\n /** The color schema of the markdown component. @default \"default\" */\n color?: \"default\" | AlphaColor;\n /** Shifts all heading levels by the given offset. @default 0 */\n headingOffset?: number;\n /** Allows overriding markdown element renderers from outside. */\n components?: Components;\n /** @internal */\n style?: CSSProperties;\n /** A ref to the elements root. */\n ref?: Ref<HTMLDivElement>;\n}\n\n/**\n * @flr-generate all\n * @flr-ignore-props components\n */\nexport const Markdown: FC<MarkdownProps> = (props) => {\n const {\n children,\n color = \"default\",\n className,\n headingOffset = 0,\n components: customComponents,\n ref,\n allowElement,\n allowedElements,\n disallowedElements,\n rehypePlugins,\n remarkPlugins,\n remarkRehypeOptions,\n skipHtml,\n unwrapDisallowed,\n urlTransform,\n ...rest\n } = props;\n\n const isSkeleton = useSkeletonMode();\n /* Footnote ids are per document, so two instances on one page would share\n them and a back link would jump into the other one. */\n const clobberPrefix = `user-content-${useId().replace(/[^\\w-]/g, \"\")}-`;\n\n const defaultComponents: Components = {\n /* GFM footnotes link through `id` (the back link's target), `aria-label`\n and `aria-describedby`. */\n a: (props) => (\n <Link\n id={props.id}\n aria-label={props[\"aria-label\"]}\n aria-describedby={props[\"aria-describedby\"]}\n target={isExternalHref(props.href) ? \"_blank\" : undefined}\n color={color}\n href={props.href}\n >\n {props.children}\n </Link>\n ),\n p: (props) => (\n <Text elementType=\"p\" color={color}>\n {props.children}\n </Text>\n ),\n code: (props) => <InlineCode color={color}>{props.children}</InlineCode>,\n h1: (props) => (\n <Heading\n id={props.id}\n level={getHeadingLevelWithOffset(1, headingOffset)}\n color={color}\n >\n {props.children}\n </Heading>\n ),\n h2: (props) => (\n <Heading\n id={props.id}\n level={getHeadingLevelWithOffset(2, headingOffset)}\n color={color}\n >\n {props.children}\n </Heading>\n ),\n h3: (props) => (\n <Heading\n id={props.id}\n level={getHeadingLevelWithOffset(3, headingOffset)}\n color={color}\n >\n {props.children}\n </Heading>\n ),\n h4: (props) => (\n <Heading\n id={props.id}\n level={getHeadingLevelWithOffset(4, headingOffset)}\n color={color}\n >\n {props.children}\n </Heading>\n ),\n h5: (props) => (\n <Heading\n id={props.id}\n level={getHeadingLevelWithOffset(5, headingOffset)}\n color={color}\n >\n {props.children}\n </Heading>\n ),\n h6: (props) => (\n <Heading\n id={props.id}\n level={getHeadingLevelWithOffset(6, headingOffset)}\n color={color}\n >\n {props.children}\n </Heading>\n ),\n hr: () => <Separator />,\n pre: (props) => {\n const preElementContent = Children.toArray(props.children)[0];\n\n return (\n <CodeBlock\n copyable={false}\n language={\n isValidElement<{ className?: string }>(preElementContent) &&\n preElementContent.props.className\n ? (preElementContent.props.className.replace(\n \"language-\",\n \"\",\n ) as CodeEditorLanguage)\n : undefined\n }\n code={String(\n isValidElement<{ children: string }>(preElementContent)\n ? preElementContent.props.children\n : preElementContent,\n ).trim()}\n />\n );\n },\n ul: ({ node: ignoredNode, ...props }) => (\n <SkeletonBlockText color={color}>\n <ul {...props} />\n </SkeletonBlockText>\n ),\n ol: ({ node: ignoredNode, ...props }) => (\n <SkeletonBlockText color={color}>\n <ol {...props} />\n </SkeletonBlockText>\n ),\n li: ({ node: ignoredNode, children, ...props }) => (\n <li {...props}>\n <SkeletonInlineContent>{children}</SkeletonInlineContent>\n </li>\n ),\n th: ({ node: ignoredNode, children, ...props }) => (\n <th {...props}>\n <SkeletonInlineContent>{children}</SkeletonInlineContent>\n </th>\n ),\n td: ({ node: ignoredNode, children, ...props }) => (\n <td {...props}>\n <SkeletonInlineContent>{children}</SkeletonInlineContent>\n </td>\n ),\n /* A text bar resets the mode for its content, so the image reads it\n from here. A skeleton issues no request. */\n img: ({ node: ignoredNode, ...props }) =>\n isSkeleton ? null : <img {...props} />,\n blockquote: (props) => (\n <SkeletonBlockText color={color}>\n <blockquote>{props.children}</blockquote>\n </SkeletonBlockText>\n ),\n };\n\n const textContent = extractTextFromFirstChild(children);\n const mergedComponents: Components = {\n ...defaultComponents,\n ...customComponents,\n };\n\n return (\n <div className={clsx(styles.markdown, className)} {...rest} ref={ref}>\n <ReactMarkdown\n allowElement={allowElement}\n allowedElements={allowedElements}\n disallowedElements={disallowedElements}\n rehypePlugins={rehypePlugins}\n remarkPlugins={remarkPlugins ?? [remarkGfm]}\n remarkRehypeOptions={{ clobberPrefix, ...remarkRehypeOptions }}\n skipHtml={skipHtml}\n unwrapDisallowed={unwrapDisallowed}\n urlTransform={urlTransform}\n components={mergedComponents}\n >\n {textContent}\n </ReactMarkdown>\n </div>\n );\n};\n\nexport default Markdown;\n"],"mappings":"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"}
1
+ {"version":3,"file":"Markdown.mjs","names":["styles"],"sources":["../../../../../../../src/components/Markdown/Markdown.tsx"],"sourcesContent":["import { CodeBlock } from \"@/components/CodeBlock\";\nimport { Heading } from \"@/components/Heading\";\nimport { InlineCode } from \"@/components/InlineCode\";\nimport { Link } from \"@/components/Link\";\nimport { Separator } from \"@/components/Separator\";\nimport { Text } from \"@/components/Text\";\nimport type { CSSProperties, FC, Ref } from \"react\";\nimport { Children, isValidElement, useId, useMemo } from \"react\";\nimport type { Components, Options } from \"react-markdown\";\nimport ReactMarkdown from \"react-markdown\";\nimport styles from \"./Markdown.module.scss\";\nimport { extractTextFromFirstChild } from \"@/lib/react/remote\";\nimport type { AlphaColor, PropsWithClassName } from \"@/lib/types/props\";\nimport clsx from \"clsx\";\nimport remarkGfm from \"remark-gfm\";\nimport { getHeadingLevelWithOffset } from \"@/components/Markdown/lib/getHeadingLevelWithOffset\";\nimport { isExternalHref } from \"@/components/Markdown/lib/isExternalHref\";\nimport type { CodeEditorLanguage } from \"@/components/CodeEditor/languages\";\nimport {\n SkeletonBlockText,\n SkeletonInlineContent,\n} from \"@/components/Markdown/components/SkeletonBlocks\";\nimport { useSkeletonMode } from \"@/components/SkeletonMode/skeletonModeContext\";\n\nexport interface MarkdownProps\n extends PropsWithClassName, Omit<Options, \"components\"> {\n /** The color schema of the markdown component. @default \"default\" */\n color?: \"default\" | AlphaColor;\n /** Shifts all heading levels by the given offset. @default 0 */\n headingOffset?: number;\n /** Allows overriding markdown element renderers from outside. */\n components?: Components;\n /** @internal */\n style?: CSSProperties;\n /** A ref to the elements root. */\n ref?: Ref<HTMLDivElement>;\n}\n\n/**\n * @flr-generate all\n * @flr-ignore-props components\n */\nexport const Markdown: FC<MarkdownProps> = (props) => {\n const {\n children,\n color = \"default\",\n className,\n headingOffset = 0,\n components: customComponents,\n ref,\n allowElement,\n allowedElements,\n disallowedElements,\n rehypePlugins,\n remarkPlugins,\n remarkRehypeOptions,\n skipHtml,\n unwrapDisallowed,\n urlTransform,\n ...rest\n } = props;\n\n const isSkeleton = useSkeletonMode();\n /* Footnote ids are per document, so two instances on one page would share\n them and a back link would jump into the other one. */\n const clobberPrefix = `user-content-${useId().replace(/[^\\w-]/g, \"\")}-`;\n\n /* react-markdown uses the renderers as element types: new ones on every\n render would remount the whole tree. */\n const defaultComponents = useMemo<Components>(\n () => ({\n /* GFM footnotes link through `id` (the back link's target), `aria-label`\n and `aria-describedby`. */\n a: (props) => (\n <Link\n id={props.id}\n aria-label={props[\"aria-label\"]}\n aria-describedby={props[\"aria-describedby\"]}\n target={isExternalHref(props.href) ? \"_blank\" : undefined}\n color={color}\n href={props.href}\n >\n {props.children}\n </Link>\n ),\n p: (props) => (\n <Text elementType=\"p\" color={color}>\n {props.children}\n </Text>\n ),\n code: (props) => <InlineCode color={color}>{props.children}</InlineCode>,\n h1: (props) => (\n <Heading\n id={props.id}\n level={getHeadingLevelWithOffset(1, headingOffset)}\n color={color}\n >\n {props.children}\n </Heading>\n ),\n h2: (props) => (\n <Heading\n id={props.id}\n level={getHeadingLevelWithOffset(2, headingOffset)}\n color={color}\n >\n {props.children}\n </Heading>\n ),\n h3: (props) => (\n <Heading\n id={props.id}\n level={getHeadingLevelWithOffset(3, headingOffset)}\n color={color}\n >\n {props.children}\n </Heading>\n ),\n h4: (props) => (\n <Heading\n id={props.id}\n level={getHeadingLevelWithOffset(4, headingOffset)}\n color={color}\n >\n {props.children}\n </Heading>\n ),\n h5: (props) => (\n <Heading\n id={props.id}\n level={getHeadingLevelWithOffset(5, headingOffset)}\n color={color}\n >\n {props.children}\n </Heading>\n ),\n h6: (props) => (\n <Heading\n id={props.id}\n level={getHeadingLevelWithOffset(6, headingOffset)}\n color={color}\n >\n {props.children}\n </Heading>\n ),\n hr: () => <Separator />,\n pre: (props) => {\n const preElementContent = Children.toArray(props.children)[0];\n\n return (\n <CodeBlock\n copyable={false}\n language={\n isValidElement<{ className?: string }>(preElementContent) &&\n preElementContent.props.className\n ? (preElementContent.props.className.replace(\n \"language-\",\n \"\",\n ) as CodeEditorLanguage)\n : undefined\n }\n code={String(\n isValidElement<{ children: string }>(preElementContent)\n ? preElementContent.props.children\n : preElementContent,\n ).trim()}\n />\n );\n },\n ul: ({ node: ignoredNode, ...props }) => (\n <SkeletonBlockText color={color}>\n <ul {...props} />\n </SkeletonBlockText>\n ),\n ol: ({ node: ignoredNode, ...props }) => (\n <SkeletonBlockText color={color}>\n <ol {...props} />\n </SkeletonBlockText>\n ),\n li: ({ node: ignoredNode, children, ...props }) => (\n <li {...props}>\n <SkeletonInlineContent>{children}</SkeletonInlineContent>\n </li>\n ),\n th: ({ node: ignoredNode, children, ...props }) => (\n <th {...props}>\n <SkeletonInlineContent>{children}</SkeletonInlineContent>\n </th>\n ),\n td: ({ node: ignoredNode, children, ...props }) => (\n <td {...props}>\n <SkeletonInlineContent>{children}</SkeletonInlineContent>\n </td>\n ),\n /* A text bar resets the mode for its content, so the image reads it\n from here. A skeleton issues no request. */\n img: ({ node: ignoredNode, ...props }) =>\n isSkeleton ? null : <img {...props} />,\n blockquote: (props) => (\n <SkeletonBlockText color={color}>\n <blockquote>{props.children}</blockquote>\n </SkeletonBlockText>\n ),\n }),\n [color, headingOffset, isSkeleton],\n );\n\n const textContent = extractTextFromFirstChild(children);\n const mergedComponents = useMemo<Components>(\n () => ({\n ...defaultComponents,\n ...customComponents,\n }),\n [defaultComponents, customComponents],\n );\n\n return (\n <div className={clsx(styles.markdown, className)} {...rest} ref={ref}>\n <ReactMarkdown\n allowElement={allowElement}\n allowedElements={allowedElements}\n disallowedElements={disallowedElements}\n rehypePlugins={rehypePlugins}\n remarkPlugins={remarkPlugins ?? [remarkGfm]}\n remarkRehypeOptions={{ clobberPrefix, ...remarkRehypeOptions }}\n skipHtml={skipHtml}\n unwrapDisallowed={unwrapDisallowed}\n urlTransform={urlTransform}\n components={mergedComponents}\n >\n {textContent}\n </ReactMarkdown>\n </div>\n );\n};\n\nexport default Markdown;\n"],"mappings":"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"}
@@ -7,6 +7,8 @@ interface Props extends PropsWithChildren {
7
7
  triggerRef?: Ref<HTMLDivElement>;
8
8
  list: ListModel<never>;
9
9
  isTile: boolean;
10
+ /** Stable invoker of the list's latest `onAction` */
11
+ onAction?: (data: never) => void;
10
12
  }
11
13
  export declare const Item: import('react').MemoExoticComponent<(props: Props) => import('react').JSX.Element | null>;
12
14
  interface ItemContainerProps extends PropsWithChildren {
@@ -2,6 +2,7 @@ import { PropsWithChildren } from 'react';
2
2
  import { default as List } from '../../../../../model/List';
3
3
  interface P extends PropsWithChildren {
4
4
  data: never;
5
+ onAction?: (data: never) => void;
5
6
  }
6
7
  export declare const useGridItemProps: (props: P, list: List<never>) => {
7
8
  gridItemProps: {
@@ -0,0 +1,13 @@
1
+ import { Ref } from 'react';
2
+ import { Key } from 'react-aria-components';
3
+ import { default as ListModel } from '../../../model/List';
4
+ interface Props {
5
+ id: Key;
6
+ data: never;
7
+ list: ListModel<never>;
8
+ /** Stable invoker of the list's latest `onAction` */
9
+ onAction?: (data: never) => void;
10
+ triggerRef?: Ref<HTMLTableRowElement>;
11
+ }
12
+ export declare const Row: import('react').MemoExoticComponent<(props: Props) => import('react').JSX.Element | null>;
13
+ export default Row;
@@ -0,0 +1,7 @@
1
+ import { default as List } from '../model/List';
2
+ /**
3
+ * A stable invoker of the list's latest `onAction`, or `undefined` without one.
4
+ * An event handler only ever needs to be the latest one, so items and rows do
5
+ * not re-render for an inline `onAction`.
6
+ */
7
+ export declare const useLatestOnAction: (list: List<never>) => ((data: never) => void | undefined) | undefined;
@@ -2,7 +2,15 @@ import { DependencyList, HTMLAttributeAnchorTarget, ReactElement, ReactNode } fr
2
2
  import { RenderItemFn } from './types';
3
3
  import { default as List } from '../List';
4
4
  export interface ItemViewShape<T> {
5
+ /**
6
+ * Runs on every render of the list to tell whether the item changed, so it
7
+ * should be cheap and pure.
8
+ */
5
9
  textValue?: (data: T) => string;
10
+ /**
11
+ * Runs on every render of the list to tell whether the item changed, so it
12
+ * should be cheap and pure.
13
+ */
6
14
  href?: (data: T) => string;
7
15
  target?: HTMLAttributeAnchorTarget;
8
16
  defaultExpanded?: (data: T) => boolean;
@@ -14,14 +22,23 @@ export interface ItemViewShape<T> {
14
22
  showTiles?: boolean;
15
23
  tileMaxWidth?: number;
16
24
  /**
17
- * Values this item's content depends on besides its own data.
25
+ * Everything the render function reads besides the item's data.
18
26
  *
19
- * An item re-renders when its data changes, or when one of the render
20
- * functions on its `List.Item` changes identity — which a function written
21
- * inline in JSX does on every render of its parent. A render function with a
22
- * stable identity (hoisted out of the component, wrapped in `useCallback`, or
23
- * reading from an external store) has to list what it reads here, or the item
24
- * keeps the values it first saw.
27
+ * With `renderDependencies`, an item re-renders only when its data or one of
28
+ * these values changes. The render function's identity no longer counts, so a
29
+ * function written inline in JSX keeps the item from re-rendering on every
30
+ * render of its parent. List every value it reads from outside the item,
31
+ * including state read through the `list` argument — one left out keeps the
32
+ * item on the value it last rendered.
33
+ *
34
+ * Without it, an item re-renders whenever its render function changes
35
+ * identity, which an inline function does on every render of its parent.
36
+ */
37
+ renderDependencies?: DependencyList;
38
+ /**
39
+ * @deprecated Use `renderDependencies` instead. `dependencies` only adds to
40
+ * the render function's identity, so an inline render function still
41
+ * re-renders the item on every render of its parent.
25
42
  */
26
43
  dependencies?: DependencyList;
27
44
  }
@@ -35,15 +52,21 @@ export declare class ItemView<T> {
35
52
  readonly showTiles?: boolean;
36
53
  readonly showList?: boolean;
37
54
  readonly tileMaxWidth: number;
55
+ readonly renderDependencies?: DependencyList;
38
56
  readonly dependencies?: DependencyList;
39
57
  private readonly renderFn?;
58
+ private lastComparison?;
40
59
  constructor(list: List<T>, shape?: ItemViewShape<T>);
41
60
  private static fallbackRenderItemFn;
42
61
  /**
43
- * Whether `other` would render this item identically — i.e. all render
44
- * functions taken from the consumer's `ListItem` element are still the same,
45
- * and the dependencies it declared have not changed.
62
+ * Whether `other` would render the item holding `data` identically. The
63
+ * render function counts by its `renderDependencies` where given, otherwise
64
+ * by its identity and the deprecated `dependencies`. `textValue` and `href`
65
+ * count by what they return for `data`, so inline functions do not re-render
66
+ * the item. `defaultExpanded` is read on mount only, so it does not count at
67
+ * all.
46
68
  */
47
- rendersSameAs(other?: ItemView<T>): boolean;
69
+ rendersSameFor(other: ItemView<T> | undefined, data: T): boolean;
70
+ private rendersSameContentAs;
48
71
  render(data: T): ReactNode;
49
72
  }
@@ -1,9 +1,23 @@
1
1
  import { TableRowShape, TableRowSupportedComponentProps } from './types';
2
2
  import { TableCell } from './TableCell';
3
3
  import { TableBody } from './TableBody';
4
+ import { DependencyList } from 'react';
4
5
  export declare class TableRow<T> {
5
6
  readonly tableBody: TableBody<T>;
6
7
  readonly cells: TableCell<T>[];
7
8
  readonly componentProps: TableRowSupportedComponentProps;
9
+ readonly renderDependencies?: DependencyList;
10
+ private lastComparison?;
8
11
  constructor(tableBody: TableBody<T>, shape?: TableRowShape<T>);
12
+ /**
13
+ * Whether `other` would render a row identically. Only rows that declare
14
+ * `renderDependencies` can tell, since the cells' render functions count by
15
+ * those instead of their identity. Rows without them always re-render. Row
16
+ * and cell props count by identity, one by one.
17
+ *
18
+ * Every row of a render shares one model, so the result is cached for the
19
+ * last `other`.
20
+ */
21
+ rendersSameAs(other?: TableRow<T>): boolean;
22
+ private compareWith;
9
23
  }
@@ -1,4 +1,4 @@
1
- import { PropsWithChildren, ReactElement } from 'react';
1
+ import { DependencyList, PropsWithChildren, ReactElement } from 'react';
2
2
  import { TableBodyProps, TableCellProps, TableColumnProps, TableHeaderProps, TableProps, TableRowProps } from '../../../Table';
3
3
  import { RenderItemFn } from '../..';
4
4
  /** Cell */
@@ -13,6 +13,22 @@ export type TableRowSupportedComponentProps = Omit<TableRowProps, "onAction" | "
13
13
  };
14
14
  export interface TableRowShape<T> extends TableRowSupportedComponentProps, PropsWithChildren {
15
15
  cells?: TableCellShape<T>[];
16
+ /**
17
+ * Everything the cells' render functions read besides the row's data.
18
+ *
19
+ * With `renderDependencies`, a row re-renders only when its data, one of
20
+ * these values, or a cell's props change. The render functions' identity does
21
+ * not count, so functions written inline in JSX keep the row from
22
+ * re-rendering on every render of its parent. List every value they read from
23
+ * outside the row, including state read through the `list` argument — one
24
+ * left out keeps the row on the value it last rendered.
25
+ *
26
+ * Row and cell props count by identity, so an inline `style` object or
27
+ * callback on `List.TableRow` or `List.TableCell` re-renders every row.
28
+ *
29
+ * Without it, every row re-renders whenever the list does.
30
+ */
31
+ renderDependencies?: DependencyList;
16
32
  }
17
33
  /** Column */
18
34
  export type TableColumnSupportedComponentProps = TableColumnProps;
@@ -127,6 +127,7 @@ export declare const typedList: <T, TMeta = unknown>() => {
127
127
  columns?: Iterable<never> | undefined;
128
128
  disabledBehavior?: import('react-stately').DisabledBehavior | undefined;
129
129
  hasChildItems?: boolean | undefined;
130
+ renderDependencies?: import('react').DependencyList | undefined;
130
131
  }) => null;
131
132
  TableCell: (ignoredProps: Omit<import('./model/table/types').TableCellShape<T>, "renderFn"> & {
132
133
  children: import('.').RenderItemFn<T>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mittwald/flow-react-components",
3
- "version": "1.6.0-next.20",
3
+ "version": "1.6.0-next.22",
4
4
  "type": "module",
5
5
  "description": "A React implementation of Flow, mittwald’s design system",
6
6
  "homepage": "https://flow.mittwald.de",
@@ -84,9 +84,9 @@
84
84
  "@internationalized/string-compiler": "^3.4.1",
85
85
  "@lezer/common": "^1.5.2",
86
86
  "@lezer/highlight": "^1.2.3",
87
- "@mittwald/flow-icons": "^1.6.0-next.20",
87
+ "@mittwald/flow-icons": "^1.6.0-next.22",
88
88
  "@mittwald/password-tools-js": "^3.0.0",
89
- "@mittwald/react-tunnel": "^1.6.0-next.20",
89
+ "@mittwald/react-tunnel": "^1.6.0-next.22",
90
90
  "@mittwald/react-use-promise": "^4.2.2",
91
91
  "@react-aria/form": "^3.2.1",
92
92
  "@react-aria/i18n": "^3.13.1",
@@ -139,13 +139,13 @@
139
139
  "@internationalized/date": "^3.12.3",
140
140
  "@lezer/generator": "^1.8.0",
141
141
  "@lezer/lr": "^1.4.10",
142
- "@mittwald/flow-components-base": "1.6.0-next.20",
143
- "@mittwald/flow-core": "1.6.0-next.20",
144
- "@mittwald/flow-design-tokens": "1.6.0-next.20",
145
- "@mittwald/flow-icons-base": "1.6.0-next.20",
142
+ "@mittwald/flow-components-base": "1.6.0-next.22",
143
+ "@mittwald/flow-core": "1.6.0-next.22",
144
+ "@mittwald/flow-design-tokens": "1.6.0-next.22",
145
+ "@mittwald/flow-icons-base": "1.6.0-next.22",
146
146
  "@mittwald/react-use-promise": "^4.2.2",
147
147
  "@mittwald/remote-dom-react": "1.2.2-mittwald.10",
148
- "@mittwald/typescript-config": "1.6.0-next.20",
148
+ "@mittwald/typescript-config": "1.6.0-next.22",
149
149
  "@nx/storybook": "^22.7.6",
150
150
  "@storybook/addon-a11y": "^10.5.10",
151
151
  "@storybook/addon-actions": "^9.0.8",
@@ -199,7 +199,7 @@
199
199
  },
200
200
  "peerDependencies": {
201
201
  "@internationalized/date": "^3.12.2",
202
- "@mittwald/flow-icons-pro": "1.6.0-next.20",
202
+ "@mittwald/flow-icons-pro": "1.6.0-next.22",
203
203
  "@mittwald/react-use-promise": "^4.2.2",
204
204
  "next": "^16.2.3",
205
205
  "react": "^19.2.0",
@@ -223,5 +223,5 @@
223
223
  "browserslist": [
224
224
  "baseline widely available"
225
225
  ],
226
- "gitHead": "151ddc103234bdf6b7832702e594639431c27fbd"
226
+ "gitHead": "e8730db3f173506049717df08b7094f8b1c27224"
227
227
  }