@farm.js/react 0.1.0-beta.1 → 0.1.0-beta.2

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.
@@ -111,6 +111,22 @@ interface CompilerKeyedRowsBlockProps {
111
111
  events?: readonly CompilerKeyedRowEvent[];
112
112
  conditionals?: readonly CompilerKeyedRowConditional[];
113
113
  }
114
+ interface CompilerKeyedRange {
115
+ /** Number of static direct host siblings between this range and the previous range. */
116
+ before: number;
117
+ items(): Iterable<unknown> | null | undefined;
118
+ rowKey(item: unknown, index: number): React.Key;
119
+ create(item: unknown, index: number): CompilerKeyedRowElement;
120
+ bindings: readonly CompilerKeyedRowBinding[];
121
+ }
122
+ interface CompilerKeyedRangesBlockProps {
123
+ id: number;
124
+ render(): React.ReactElement;
125
+ rootRef?: React.RefCallback<Element>;
126
+ ranges: readonly CompilerKeyedRange[];
127
+ /** Number of static direct host siblings after the final keyed range. */
128
+ trailing: number;
129
+ }
114
130
  interface CompilerComponentBlockProps {
115
131
  id: number;
116
132
  render(): React.ReactNode;
@@ -120,6 +136,7 @@ interface CompilerBlockRuntime {
120
136
  HostConditional: React.ComponentType<CompilerHostConditionalBlockProps>;
121
137
  KeyedList: React.ComponentType<CompilerKeyedListBlockProps>;
122
138
  KeyedRows: React.ComponentType<CompilerKeyedRowsBlockProps>;
139
+ KeyedRanges: React.ComponentType<CompilerKeyedRangesBlockProps>;
123
140
  Component: React.ComponentType<CompilerComponentBlockProps>;
124
141
  target(id: number): React.RefCallback<Element>;
125
142
  }
@@ -143,4 +160,4 @@ interface CompiledComponentDefinition<Props> {
143
160
  */
144
161
  declare function createCompiledComponent<Props>(definition: CompiledComponentDefinition<Props>): React.ComponentType<Props>;
145
162
 
146
- export { type CompiledComponentDefinition, type CompilerAttributeBinding, type CompilerBinding, type CompilerBlockRuntime, type CompilerCell, type CompilerComponentBlockProps, type CompilerConditionalBlockBinding, type CompilerConditionalBlockProps, type CompilerHostConditionalBinding, type CompilerHostConditionalBlockProps, type CompilerHostConditionalBranch, type CompilerHostElement, type CompilerKeyedListBlockProps, type CompilerKeyedRowBinding, type CompilerKeyedRowConditional, type CompilerKeyedRowConditionalBranch, type CompilerKeyedRowElement, type CompilerKeyedRowEvent, type CompilerKeyedRowsBlockProps, type CompilerStateUpdater, type CompilerStyleBinding, type CompilerTextBinding, createCompiledComponent };
163
+ export { type CompiledComponentDefinition, type CompilerAttributeBinding, type CompilerBinding, type CompilerBlockRuntime, type CompilerCell, type CompilerComponentBlockProps, type CompilerConditionalBlockBinding, type CompilerConditionalBlockProps, type CompilerHostConditionalBinding, type CompilerHostConditionalBlockProps, type CompilerHostConditionalBranch, type CompilerHostElement, type CompilerKeyedListBlockProps, type CompilerKeyedRange, type CompilerKeyedRangesBlockProps, type CompilerKeyedRowBinding, type CompilerKeyedRowConditional, type CompilerKeyedRowConditionalBranch, type CompilerKeyedRowElement, type CompilerKeyedRowEvent, type CompilerKeyedRowsBlockProps, type CompilerStateUpdater, type CompilerStyleBinding, type CompilerTextBinding, createCompiledComponent };
@@ -900,6 +900,219 @@ function createKeyedRowsBlockComponent(owner) {
900
900
  FarmKeyedRowsBlock.displayName = "FarmCompiledKeyedRows";
901
901
  return FarmKeyedRowsBlock;
902
902
  }
903
+ function createKeyedRangesBlockComponent(owner) {
904
+ class FarmKeyedRangesBlock extends React.Component {
905
+ constructor() {
906
+ super(...arguments);
907
+ this.state = { fallback: false };
908
+ this.root = null;
909
+ this.mounted = false;
910
+ this.fallbackRequested = false;
911
+ this.fallbackVersion = 0;
912
+ this.propFallbackQueued = false;
913
+ this.currentProps = this.props;
914
+ this.rangeInstances = [];
915
+ this.staticSegments = [];
916
+ this.captureRoot = (root) => {
917
+ this.root = root;
918
+ this.currentProps.rootRef?.(root);
919
+ };
920
+ this.refresh = (afterCommit) => {
921
+ this.reconcile(afterCommit);
922
+ };
923
+ }
924
+ readRange(range) {
925
+ const source = range.items();
926
+ const items = source ? Array.from(source) : [];
927
+ const keys = items.map((item, index) => keyedRowIdentity(range.rowKey(item, index)));
928
+ if (new Set(keys).size !== keys.length) return null;
929
+ return { items, keys };
930
+ }
931
+ readRanges(props) {
932
+ const ranges = [];
933
+ for (const range of props.ranges) {
934
+ const rows = this.readRange(range);
935
+ if (!rows) return null;
936
+ ranges.push(rows);
937
+ }
938
+ return ranges;
939
+ }
940
+ adopt(props = this.currentProps) {
941
+ if (!this.root || !Number.isSafeInteger(props.trailing) || props.trailing < 0) return false;
942
+ const rowsByRange = this.readRanges(props);
943
+ if (!rowsByRange) return false;
944
+ const elements = [...this.root.children];
945
+ const staticSegments = [];
946
+ const instancesByRange = [];
947
+ let cursor = 0;
948
+ for (let rangeIndex = 0; rangeIndex < props.ranges.length; rangeIndex += 1) {
949
+ const range = props.ranges[rangeIndex];
950
+ const rows = rowsByRange[rangeIndex];
951
+ if (!Number.isSafeInteger(range.before) || range.before < 0) return false;
952
+ const staticEnd = cursor + range.before;
953
+ if (staticEnd > elements.length) return false;
954
+ staticSegments.push(elements.slice(cursor, staticEnd));
955
+ cursor = staticEnd;
956
+ const rowEnd = cursor + rows.items.length;
957
+ if (rowEnd > elements.length) return false;
958
+ const instances = /* @__PURE__ */ new Map();
959
+ for (let index = 0; index < rows.items.length; index += 1) {
960
+ const element = elements[cursor + index];
961
+ if (!matchesCompilerHostElement(element, range.create(rows.items[index], index))) {
962
+ return false;
963
+ }
964
+ instances.set(rows.keys[index], {
965
+ key: rows.keys[index],
966
+ element,
967
+ values: readKeyedRowBindingValues(range, rows.items[index], index),
968
+ item: rows.items[index],
969
+ index,
970
+ conditionalValues: /* @__PURE__ */ new Map()
971
+ });
972
+ }
973
+ instancesByRange.push(instances);
974
+ cursor = rowEnd;
975
+ }
976
+ if (cursor + props.trailing !== elements.length) return false;
977
+ staticSegments.push(elements.slice(cursor));
978
+ this.staticSegments = staticSegments;
979
+ this.rangeInstances = instancesByRange;
980
+ return true;
981
+ }
982
+ anchorAfter(rangeIndex) {
983
+ for (let next = rangeIndex + 1; next < this.rangeInstances.length; next += 1) {
984
+ const staticAnchor = this.staticSegments[next]?.[0];
985
+ if (staticAnchor) return staticAnchor;
986
+ const rowAnchor = this.rangeInstances[next].values().next().value?.element;
987
+ if (rowAnchor) return rowAnchor;
988
+ }
989
+ return this.staticSegments[this.rangeInstances.length]?.[0] || null;
990
+ }
991
+ reconcileRange(rangeIndex, rows) {
992
+ if (!this.root) return;
993
+ const range = this.currentProps.ranges[rangeIndex];
994
+ const previous = this.rangeInstances[rangeIndex];
995
+ const oldIndices = /* @__PURE__ */ new Map();
996
+ [...previous.keys()].forEach((key, index) => oldIndices.set(key, index));
997
+ const nextKeys = new Set(rows.keys);
998
+ for (const [key, instance] of previous) {
999
+ if (!nextKeys.has(key)) instance.element.remove();
1000
+ }
1001
+ const sequence = rows.keys.map((key) => oldIndices.get(key) ?? -1);
1002
+ const stablePositions = longestIncreasingSubsequencePositions(sequence);
1003
+ const nextInstances = [];
1004
+ for (let index = 0; index < rows.items.length; index += 1) {
1005
+ const key = rows.keys[index];
1006
+ const existing = previous.get(key);
1007
+ if (existing) {
1008
+ applyKeyedRowBindings(range, existing, rows.items[index], index);
1009
+ existing.item = rows.items[index];
1010
+ existing.index = index;
1011
+ nextInstances.push(existing);
1012
+ continue;
1013
+ }
1014
+ const element = createCompilerHostElement(
1015
+ this.root.ownerDocument,
1016
+ range.create(rows.items[index], index)
1017
+ );
1018
+ nextInstances.push({
1019
+ key,
1020
+ element,
1021
+ values: readKeyedRowBindingValues(range, rows.items[index], index),
1022
+ item: rows.items[index],
1023
+ index,
1024
+ conditionalValues: /* @__PURE__ */ new Map()
1025
+ });
1026
+ }
1027
+ let anchor = this.anchorAfter(rangeIndex);
1028
+ for (let index = nextInstances.length - 1; index >= 0; index -= 1) {
1029
+ const instance = nextInstances[index];
1030
+ if (sequence[index] < 0) {
1031
+ this.root.insertBefore(instance.element, anchor);
1032
+ } else if (!stablePositions.has(index) && instance.element.nextSibling !== anchor) {
1033
+ this.root.insertBefore(instance.element, anchor);
1034
+ }
1035
+ anchor = instance.element;
1036
+ }
1037
+ this.rangeInstances[rangeIndex] = new Map(
1038
+ nextInstances.map((instance) => [instance.key, instance])
1039
+ );
1040
+ }
1041
+ reconcile(afterCommit) {
1042
+ if (!this.mounted || !this.root) {
1043
+ afterCommit?.();
1044
+ return;
1045
+ }
1046
+ if (this.state.fallback) {
1047
+ this.fallbackVersion += 1;
1048
+ this.forceUpdate(afterCommit);
1049
+ return;
1050
+ }
1051
+ const rowsByRange = this.readRanges(this.currentProps);
1052
+ if (!rowsByRange || rowsByRange.length !== this.rangeInstances.length) {
1053
+ this.activateFallback(afterCommit);
1054
+ return;
1055
+ }
1056
+ const activeElement = this.root.ownerDocument.activeElement;
1057
+ const restoreFocus = Boolean(activeElement && this.root.contains(activeElement));
1058
+ for (let rangeIndex = rowsByRange.length - 1; rangeIndex >= 0; rangeIndex -= 1) {
1059
+ this.reconcileRange(rangeIndex, rowsByRange[rangeIndex]);
1060
+ }
1061
+ if (restoreFocus && activeElement?.isConnected && activeElement.ownerDocument.activeElement !== activeElement && "focus" in activeElement) {
1062
+ activeElement.focus({ preventScroll: true });
1063
+ }
1064
+ afterCommit?.();
1065
+ }
1066
+ activateFallback(afterCommit) {
1067
+ if (!this.mounted || this.state.fallback || this.fallbackRequested) {
1068
+ afterCommit?.();
1069
+ return;
1070
+ }
1071
+ this.fallbackRequested = true;
1072
+ this.setState({ fallback: true }, afterCommit);
1073
+ }
1074
+ schedulePropFallback() {
1075
+ if (this.propFallbackQueued) return;
1076
+ this.propFallbackQueued = true;
1077
+ queueMicrotask(() => {
1078
+ this.propFallbackQueued = false;
1079
+ if (this.mounted && !this.state.fallback) this.activateFallback();
1080
+ });
1081
+ }
1082
+ shouldComponentUpdate(nextProps, nextState) {
1083
+ this.currentProps = nextProps;
1084
+ if (nextState.fallback || this.state.fallback) return true;
1085
+ this.schedulePropFallback();
1086
+ return false;
1087
+ }
1088
+ componentDidMount() {
1089
+ this.mounted = true;
1090
+ this.unsubscribe = owner.subscribe(this.props.id, this.refresh);
1091
+ if (!this.adopt()) this.activateFallback();
1092
+ }
1093
+ componentWillUnmount() {
1094
+ this.mounted = false;
1095
+ this.unsubscribe?.();
1096
+ this.root = null;
1097
+ this.rangeInstances = [];
1098
+ this.staticSegments = [];
1099
+ }
1100
+ render() {
1101
+ const container = this.currentProps.render();
1102
+ if (!React.isValidElement(container) || typeof container.type !== "string") {
1103
+ throw new TypeError(
1104
+ `Compiled keyed ranges ${this.currentProps.id} must own one host container.`
1105
+ );
1106
+ }
1107
+ return React.cloneElement(container, {
1108
+ key: this.state.fallback ? `react-${this.fallbackVersion}` : "compiled",
1109
+ ref: this.captureRoot
1110
+ });
1111
+ }
1112
+ }
1113
+ FarmKeyedRangesBlock.displayName = "FarmCompiledKeyedRanges";
1114
+ return FarmKeyedRangesBlock;
1115
+ }
903
1116
  function createComponentBlockComponent(owner) {
904
1117
  class FarmComponentBlock extends React.Component {
905
1118
  constructor() {
@@ -996,6 +1209,9 @@ function createCompiledComponent(definition) {
996
1209
  KeyedRows: createKeyedRowsBlockComponent({
997
1210
  subscribe: (id, refresh) => this.subscribeToBlock(id, refresh)
998
1211
  }),
1212
+ KeyedRanges: createKeyedRangesBlockComponent({
1213
+ subscribe: (id, refresh) => this.subscribeToBlock(id, refresh)
1214
+ }),
999
1215
  Component: createComponentBlockComponent({
1000
1216
  subscribe: (id, refresh) => this.subscribeToBlock(id, refresh)
1001
1217
  }),
@@ -1153,7 +1369,17 @@ function createCompiledComponent(definition) {
1153
1369
  if (this.hasBindingError) throw this.bindingError;
1154
1370
  const currentDefinition = definitionReference.current;
1155
1371
  const element = currentDefinition.render(this.props, this.cells, this.blockRuntime);
1156
- if (!React.isValidElement(element) || typeof element.type !== "string") {
1372
+ if (!React.isValidElement(element)) {
1373
+ throw new TypeError(
1374
+ `Compiled component ${currentDefinition.displayName} must return one host element.`
1375
+ );
1376
+ }
1377
+ if (element.type === this.blockRuntime.KeyedRanges) {
1378
+ return React.cloneElement(element, {
1379
+ rootRef: this.captureRoot
1380
+ });
1381
+ }
1382
+ if (typeof element.type !== "string") {
1157
1383
  throw new TypeError(
1158
1384
  `Compiled component ${currentDefinition.displayName} must return one host element.`
1159
1385
  );