@ankhorage/zora 25.8.12 → 25.8.14

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 (35) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +1 -1
  3. package/dist/features/grid-interactions/adapters/inbound/GridInteractions.d.ts.map +1 -1
  4. package/dist/features/grid-interactions/adapters/inbound/GridInteractions.js +1 -1
  5. package/dist/features/grid-interactions/adapters/inbound/GridInteractions.js.map +1 -1
  6. package/dist/features/grid-interactions/adapters/inbound/GridInteractionsKeyboardProxy.web.d.ts.map +1 -1
  7. package/dist/features/grid-interactions/adapters/inbound/GridInteractionsKeyboardProxy.web.js +9 -2
  8. package/dist/features/grid-interactions/adapters/inbound/GridInteractionsKeyboardProxy.web.js.map +1 -1
  9. package/dist/features/matrix-grid/adapters/inbound/MatrixGrid.d.ts.map +1 -1
  10. package/dist/features/matrix-grid/adapters/inbound/MatrixGrid.js +11 -8
  11. package/dist/features/matrix-grid/adapters/inbound/MatrixGrid.js.map +1 -1
  12. package/dist/features/matrix-grid/adapters/inbound/useMatrixGridKeyboardFocus.d.ts +1 -1
  13. package/dist/features/matrix-grid/adapters/inbound/useMatrixGridKeyboardFocus.d.ts.map +1 -1
  14. package/dist/features/matrix-grid/adapters/inbound/useMatrixGridKeyboardFocus.js +25 -5
  15. package/dist/features/matrix-grid/adapters/inbound/useMatrixGridKeyboardFocus.js.map +1 -1
  16. package/dist/features/matrix-grid/application/resolveMatrixGridSelection.d.ts +1 -1
  17. package/dist/features/matrix-grid/application/resolveMatrixGridSelection.d.ts.map +1 -1
  18. package/dist/features/matrix-grid/application/resolveMatrixGridSelection.js +5 -2
  19. package/dist/features/matrix-grid/application/resolveMatrixGridSelection.js.map +1 -1
  20. package/dist/types/matrix-grid.d.ts +2 -0
  21. package/dist/types/matrix-grid.d.ts.map +1 -1
  22. package/dist/types/matrix-grid.js.map +1 -1
  23. package/package.json +2 -2
  24. package/src/features/grid-interactions/adapters/inbound/GridInteractions.test.tsx +6 -1
  25. package/src/features/grid-interactions/adapters/inbound/GridInteractions.tsx +0 -1
  26. package/src/features/grid-interactions/adapters/inbound/GridInteractionsKeyboardProxy.web.tsx +10 -1
  27. package/src/features/matrix-grid/adapters/inbound/MatrixGrid.protected-focus.test.tsx +252 -0
  28. package/src/features/matrix-grid/adapters/inbound/MatrixGrid.test.tsx +79 -0
  29. package/src/features/matrix-grid/adapters/inbound/MatrixGrid.tsx +16 -5
  30. package/src/features/matrix-grid/adapters/inbound/useMatrixGridKeyboardFocus.ts +31 -4
  31. package/src/features/matrix-grid/application/resolveMatrixGridSelection.test.ts +20 -13
  32. package/src/features/matrix-grid/application/resolveMatrixGridSelection.ts +3 -0
  33. package/src/types/matrix-grid.ts +2 -0
  34. package/web-dist/components/grid-interactions/GridInteractions.js +9 -2
  35. package/web-dist/components/matrix-grid/MatrixGrid.js +41 -16
package/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # Changelog
2
2
 
3
+ ## 25.8.14
4
+
5
+ ### Patch Changes
6
+
7
+ - 2ba10d7: Prevent protected MatrixGrid cells from receiving focus or changing selection.
8
+
9
+ ## 25.8.13
10
+
11
+ ### Patch Changes
12
+
13
+ - df79c6e: Fix the web grid interaction keyboard boundary so it is reachable through normal tab navigation in browsers.
14
+
3
15
  ## 25.8.12
4
16
 
5
17
  ### Patch Changes
package/README.md CHANGED
@@ -3,7 +3,7 @@
3
3
 
4
4
  # @ankhorage/zora
5
5
 
6
- ![license: MIT](././paradox/badges/license.svg) ![npm: v25.8.12](././paradox/badges/npm.svg) ![runtime: bun](././paradox/badges/runtime.svg) ![typescript: strict](././paradox/badges/typescript.svg) ![eslint: checked](././paradox/badges/eslint.svg) ![prettier: checked](././paradox/badges/prettier.svg) ![build: checked](././paradox/badges/build.svg) ![tests: checked](././paradox/badges/tests.svg) ![paradox: warnings](././paradox/badges/docs.svg)
6
+ ![license: MIT](././paradox/badges/license.svg) ![npm: v25.8.14](././paradox/badges/npm.svg) ![runtime: bun](././paradox/badges/runtime.svg) ![typescript: strict](././paradox/badges/typescript.svg) ![eslint: checked](././paradox/badges/eslint.svg) ![prettier: checked](././paradox/badges/prettier.svg) ![build: checked](././paradox/badges/build.svg) ![tests: checked](././paradox/badges/tests.svg) ![paradox: warnings](././paradox/badges/docs.svg)
7
7
 
8
8
  Opinionated React Native and React Native Web UI kit built on @ankhorage/surface.
9
9
 
@@ -1 +1 @@
1
- {"version":3,"file":"GridInteractions.d.ts","sourceRoot":"","sources":["../../../../../src/features/grid-interactions/adapters/inbound/GridInteractions.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAG1B,OAAO,KAAK,EAEV,qBAAqB,EACtB,MAAM,qCAAqC,CAAC;AAO7C,gGAAgG;AAChG,wBAAgB,gBAAgB,CAAC,EAC/B,QAAQ,EACR,iBAAiB,EACjB,YAAY,EACZ,MAAM,EACN,GAAG,KAAK,EACT,EAAE,qBAAqB,qBA0EvB"}
1
+ {"version":3,"file":"GridInteractions.d.ts","sourceRoot":"","sources":["../../../../../src/features/grid-interactions/adapters/inbound/GridInteractions.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAG1B,OAAO,KAAK,EAEV,qBAAqB,EACtB,MAAM,qCAAqC,CAAC;AAO7C,gGAAgG;AAChG,wBAAgB,gBAAgB,CAAC,EAC/B,QAAQ,EACR,iBAAiB,EACjB,YAAY,EACZ,MAAM,EACN,GAAG,KAAK,EACT,EAAE,qBAAqB,qBAyEvB"}
@@ -34,7 +34,7 @@ export function GridInteractions({ children, interactionPolicy, resizeHandle, te
34
34
  pointerQueue.enqueue(surface, (origin) => handlePointer(resolveGridInteractionPointer(eventSnapshot, origin, modifierSnapshot)));
35
35
  };
36
36
  const enabled = interactionPolicy !== 'passive';
37
- return (<View accessible accessibilityLabel="Grid interaction surface" accessibilityRole="adjustable" ref={surfaceRef} testID={testID} onResponderGrant={(event) => {
37
+ return (<View accessible accessibilityLabel="Grid interaction surface" ref={surfaceRef} testID={testID} onResponderGrant={(event) => {
38
38
  scrollLock.acquire();
39
39
  withPointer(event, (pointer) => controller.begin(pointer, resizeHandle));
40
40
  }} onResponderMove={(event) => withPointer(event, controller.move)} onResponderRelease={(event) => {
@@ -1 +1 @@
1
- {"version":3,"file":"GridInteractions.js","sourceRoot":"","sources":["../../../../../src/features/grid-interactions/adapters/inbound/GridInteractions.tsx"],"names":[],"mappings":"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","sourcesContent":["import React from 'react';\nimport { View } from 'react-native';\n\nimport type {\n GridInteractionsController,\n GridInteractionsProps,\n} from '../../../../types/grid-interactions';\nimport { createGridInteractionsController } from '../../application/createGridInteractionsController';\nimport { createGridInteractionsPointerQueue } from './createGridInteractionsPointerQueue';\nimport { GridInteractionsKeyboardProxy } from './GridInteractionsKeyboardProxy';\nimport { resolveGridInteractionPointer } from './resolveGridInteractionPointer';\nimport { useGridInteractionScrollLock } from './useGridInteractionScrollLock';\n\n/*** Adapts native responder and keyboard events to the controlled grid interaction boundary. */\nexport function GridInteractions({\n children,\n interactionPolicy,\n resizeHandle,\n testID,\n ...props\n}: GridInteractionsProps) {\n const controllerProps = React.useMemo(\n () => ({ ...props, interactionPolicy }),\n [interactionPolicy, props],\n );\n const [controller] = React.useState<GridInteractionsController>(() =>\n createGridInteractionsController(controllerProps),\n );\n const surfaceRef = React.useRef<View>(null);\n const spaceKeyRef = React.useRef(false);\n const [pointerQueue] = React.useState(() => createGridInteractionsPointerQueue());\n const scrollLock = useGridInteractionScrollLock();\n React.useLayoutEffect(() => controller.update(controllerProps), [controller, controllerProps]);\n React.useLayoutEffect(() => {\n pointerQueue.activate();\n return () => {\n spaceKeyRef.current = false;\n pointerQueue.dispose();\n controller.cancel();\n };\n }, [controller, pointerQueue]);\n const setSpaceKey = React.useCallback((spaceKey: boolean) => {\n spaceKeyRef.current = spaceKey;\n }, []);\n const withPointer = (\n event: Parameters<typeof resolveGridInteractionPointer>[0],\n handlePointer: (pointer: ReturnType<typeof resolveGridInteractionPointer>) => void,\n ) => {\n const surface = surfaceRef.current;\n if (!surface) return;\n const eventSnapshot = { nativeEvent: { ...event.nativeEvent } };\n const modifierSnapshot = { spaceKey: spaceKeyRef.current };\n pointerQueue.enqueue(surface, (origin) =>\n handlePointer(resolveGridInteractionPointer(eventSnapshot, origin, modifierSnapshot)),\n );\n };\n const enabled = interactionPolicy !== 'passive';\n\n return (\n <View\n accessible\n accessibilityLabel=\"Grid interaction surface\"\n accessibilityRole=\"adjustable\"\n ref={surfaceRef}\n testID={testID}\n onResponderGrant={(event) => {\n scrollLock.acquire();\n withPointer(event, (pointer) => controller.begin(pointer, resizeHandle));\n }}\n onResponderMove={(event) => withPointer(event, controller.move)}\n onResponderRelease={(event) => {\n scrollLock.release();\n withPointer(event, controller.end);\n }}\n onResponderTerminate={() => {\n scrollLock.release();\n setSpaceKey(false);\n pointerQueue.cancel();\n controller.cancel();\n }}\n onResponderTerminationRequest={() => false}\n onStartShouldSetResponder={() => enabled}\n >\n <GridInteractionsKeyboardProxy\n enabled={enabled}\n onKeyDown={(key, keyPointer) => {\n return controller.keyDown(key, keyPointer);\n }}\n onSpaceKeyChange={setSpaceKey}\n >\n {children}\n </GridInteractionsKeyboardProxy>\n </View>\n );\n}\n"]}
1
+ {"version":3,"file":"GridInteractions.js","sourceRoot":"","sources":["../../../../../src/features/grid-interactions/adapters/inbound/GridInteractions.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,IAAI,EAAE,MAAM,cAAc,CAAC;AAMpC,OAAO,EAAE,gCAAgC,EAAE,MAAM,oDAAoD,CAAC;AACtG,OAAO,EAAE,kCAAkC,EAAE,MAAM,sCAAsC,CAAC;AAC1F,OAAO,EAAE,6BAA6B,EAAE,MAAM,iCAAiC,CAAC;AAChF,OAAO,EAAE,6BAA6B,EAAE,MAAM,iCAAiC,CAAC;AAChF,OAAO,EAAE,4BAA4B,EAAE,MAAM,gCAAgC,CAAC;AAE9E,gGAAgG;AAChG,MAAM,UAAU,gBAAgB,CAAC,EAC/B,QAAQ,EACR,iBAAiB,EACjB,YAAY,EACZ,MAAM,EACN,GAAG,KAAK,EACc;IACtB,MAAM,eAAe,GAAG,KAAK,CAAC,OAAO,CACnC,GAAG,EAAE,CAAC,CAAC,EAAE,GAAG,KAAK,EAAE,iBAAiB,EAAE,CAAC,EACvC,CAAC,iBAAiB,EAAE,KAAK,CAAC,CAC3B,CAAC;IACF,MAAM,CAAC,UAAU,CAAC,GAAG,KAAK,CAAC,QAAQ,CAA6B,GAAG,EAAE,CACnE,gCAAgC,CAAC,eAAe,CAAC,CAClD,CAAC;IACF,MAAM,UAAU,GAAG,KAAK,CAAC,MAAM,CAAO,IAAI,CAAC,CAAC;IAC5C,MAAM,WAAW,GAAG,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IACxC,MAAM,CAAC,YAAY,CAAC,GAAG,KAAK,CAAC,QAAQ,CAAC,GAAG,EAAE,CAAC,kCAAkC,EAAE,CAAC,CAAC;IAClF,MAAM,UAAU,GAAG,4BAA4B,EAAE,CAAC;IAClD,KAAK,CAAC,eAAe,CAAC,GAAG,EAAE,CAAC,UAAU,CAAC,MAAM,CAAC,eAAe,CAAC,EAAE,CAAC,UAAU,EAAE,eAAe,CAAC,CAAC,CAAC;IAC/F,KAAK,CAAC,eAAe,CAAC,GAAG,EAAE;QACzB,YAAY,CAAC,QAAQ,EAAE,CAAC;QACxB,OAAO,GAAG,EAAE;YACV,WAAW,CAAC,OAAO,GAAG,KAAK,CAAC;YAC5B,YAAY,CAAC,OAAO,EAAE,CAAC;YACvB,UAAU,CAAC,MAAM,EAAE,CAAC;QACtB,CAAC,CAAC;IACJ,CAAC,EAAE,CAAC,UAAU,EAAE,YAAY,CAAC,CAAC,CAAC;IAC/B,MAAM,WAAW,GAAG,KAAK,CAAC,WAAW,CAAC,CAAC,QAAiB,EAAE,EAAE;QAC1D,WAAW,CAAC,OAAO,GAAG,QAAQ,CAAC;IACjC,CAAC,EAAE,EAAE,CAAC,CAAC;IACP,MAAM,WAAW,GAAG,CAClB,KAA0D,EAC1D,aAAkF,EAClF,EAAE;QACF,MAAM,OAAO,GAAG,UAAU,CAAC,OAAO,CAAC;QACnC,IAAI,CAAC,OAAO;YAAE,OAAO;QACrB,MAAM,aAAa,GAAG,EAAE,WAAW,EAAE,EAAE,GAAG,KAAK,CAAC,WAAW,EAAE,EAAE,CAAC;QAChE,MAAM,gBAAgB,GAAG,EAAE,QAAQ,EAAE,WAAW,CAAC,OAAO,EAAE,CAAC;QAC3D,YAAY,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC,MAAM,EAAE,EAAE,CACvC,aAAa,CAAC,6BAA6B,CAAC,aAAa,EAAE,MAAM,EAAE,gBAAgB,CAAC,CAAC,CACtF,CAAC;IACJ,CAAC,CAAC;IACF,MAAM,OAAO,GAAG,iBAAiB,KAAK,SAAS,CAAC;IAEhD,OAAO,CACL,CAAC,IAAI,CACH,UAAU,CACV,kBAAkB,CAAC,0BAA0B,CAC7C,GAAG,CAAC,CAAC,UAAU,CAAC,CAChB,MAAM,CAAC,CAAC,MAAM,CAAC,CACf,gBAAgB,CAAC,CAAC,CAAC,KAAK,EAAE,EAAE;YAC1B,UAAU,CAAC,OAAO,EAAE,CAAC;YACrB,WAAW,CAAC,KAAK,EAAE,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,KAAK,CAAC,OAAO,EAAE,YAAY,CAAC,CAAC,CAAC;QAC3E,CAAC,CAAC,CACF,eAAe,CAAC,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,WAAW,CAAC,KAAK,EAAE,UAAU,CAAC,IAAI,CAAC,CAAC,CAChE,kBAAkB,CAAC,CAAC,CAAC,KAAK,EAAE,EAAE;YAC5B,UAAU,CAAC,OAAO,EAAE,CAAC;YACrB,WAAW,CAAC,KAAK,EAAE,UAAU,CAAC,GAAG,CAAC,CAAC;QACrC,CAAC,CAAC,CACF,oBAAoB,CAAC,CAAC,GAAG,EAAE;YACzB,UAAU,CAAC,OAAO,EAAE,CAAC;YACrB,WAAW,CAAC,KAAK,CAAC,CAAC;YACnB,YAAY,CAAC,MAAM,EAAE,CAAC;YACtB,UAAU,CAAC,MAAM,EAAE,CAAC;QACtB,CAAC,CAAC,CACF,6BAA6B,CAAC,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,CAC3C,yBAAyB,CAAC,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,CAEzC;MAAA,CAAC,6BAA6B,CAC5B,OAAO,CAAC,CAAC,OAAO,CAAC,CACjB,SAAS,CAAC,CAAC,CAAC,GAAG,EAAE,UAAU,EAAE,EAAE;YAC7B,OAAO,UAAU,CAAC,OAAO,CAAC,GAAG,EAAE,UAAU,CAAC,CAAC;QAC7C,CAAC,CAAC,CACF,gBAAgB,CAAC,CAAC,WAAW,CAAC,CAE9B;QAAA,CAAC,QAAQ,CACX;MAAA,EAAE,6BAA6B,CACjC;IAAA,EAAE,IAAI,CAAC,CACR,CAAC;AACJ,CAAC","sourcesContent":["import React from 'react';\nimport { View } from 'react-native';\n\nimport type {\n GridInteractionsController,\n GridInteractionsProps,\n} from '../../../../types/grid-interactions';\nimport { createGridInteractionsController } from '../../application/createGridInteractionsController';\nimport { createGridInteractionsPointerQueue } from './createGridInteractionsPointerQueue';\nimport { GridInteractionsKeyboardProxy } from './GridInteractionsKeyboardProxy';\nimport { resolveGridInteractionPointer } from './resolveGridInteractionPointer';\nimport { useGridInteractionScrollLock } from './useGridInteractionScrollLock';\n\n/*** Adapts native responder and keyboard events to the controlled grid interaction boundary. */\nexport function GridInteractions({\n children,\n interactionPolicy,\n resizeHandle,\n testID,\n ...props\n}: GridInteractionsProps) {\n const controllerProps = React.useMemo(\n () => ({ ...props, interactionPolicy }),\n [interactionPolicy, props],\n );\n const [controller] = React.useState<GridInteractionsController>(() =>\n createGridInteractionsController(controllerProps),\n );\n const surfaceRef = React.useRef<View>(null);\n const spaceKeyRef = React.useRef(false);\n const [pointerQueue] = React.useState(() => createGridInteractionsPointerQueue());\n const scrollLock = useGridInteractionScrollLock();\n React.useLayoutEffect(() => controller.update(controllerProps), [controller, controllerProps]);\n React.useLayoutEffect(() => {\n pointerQueue.activate();\n return () => {\n spaceKeyRef.current = false;\n pointerQueue.dispose();\n controller.cancel();\n };\n }, [controller, pointerQueue]);\n const setSpaceKey = React.useCallback((spaceKey: boolean) => {\n spaceKeyRef.current = spaceKey;\n }, []);\n const withPointer = (\n event: Parameters<typeof resolveGridInteractionPointer>[0],\n handlePointer: (pointer: ReturnType<typeof resolveGridInteractionPointer>) => void,\n ) => {\n const surface = surfaceRef.current;\n if (!surface) return;\n const eventSnapshot = { nativeEvent: { ...event.nativeEvent } };\n const modifierSnapshot = { spaceKey: spaceKeyRef.current };\n pointerQueue.enqueue(surface, (origin) =>\n handlePointer(resolveGridInteractionPointer(eventSnapshot, origin, modifierSnapshot)),\n );\n };\n const enabled = interactionPolicy !== 'passive';\n\n return (\n <View\n accessible\n accessibilityLabel=\"Grid interaction surface\"\n ref={surfaceRef}\n testID={testID}\n onResponderGrant={(event) => {\n scrollLock.acquire();\n withPointer(event, (pointer) => controller.begin(pointer, resizeHandle));\n }}\n onResponderMove={(event) => withPointer(event, controller.move)}\n onResponderRelease={(event) => {\n scrollLock.release();\n withPointer(event, controller.end);\n }}\n onResponderTerminate={() => {\n scrollLock.release();\n setSpaceKey(false);\n pointerQueue.cancel();\n controller.cancel();\n }}\n onResponderTerminationRequest={() => false}\n onStartShouldSetResponder={() => enabled}\n >\n <GridInteractionsKeyboardProxy\n enabled={enabled}\n onKeyDown={(key, keyPointer) => {\n return controller.keyDown(key, keyPointer);\n }}\n onSpaceKeyChange={setSpaceKey}\n >\n {children}\n </GridInteractionsKeyboardProxy>\n </View>\n );\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"GridInteractionsKeyboardProxy.web.d.ts","sourceRoot":"","sources":["../../../../../src/features/grid-interactions/adapters/inbound/GridInteractionsKeyboardProxy.web.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,qCAAqC,CAAC;AAElF,oGAAoG;AACpG,wBAAgB,6BAA6B,CAAC,EAC5C,QAAQ,EACR,OAAO,EACP,SAAS,EACT,gBAAgB,GACjB,EAAE,kCAAkC,qBAmCpC;AAED,UAAU,kCAAkC;IAC1C,QAAQ,CAAC,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IACnC,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,SAAS,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,sBAAsB,KAAK,OAAO,CAAC;IAC9E,QAAQ,CAAC,gBAAgB,EAAE,CAAC,QAAQ,EAAE,OAAO,KAAK,IAAI,CAAC;CACxD"}
1
+ {"version":3,"file":"GridInteractionsKeyboardProxy.web.d.ts","sourceRoot":"","sources":["../../../../../src/features/grid-interactions/adapters/inbound/GridInteractionsKeyboardProxy.web.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,qCAAqC,CAAC;AAElF,oGAAoG;AACpG,wBAAgB,6BAA6B,CAAC,EAC5C,QAAQ,EACR,OAAO,EACP,SAAS,EACT,gBAAgB,GACjB,EAAE,kCAAkC,qBAoCpC;AAUD,UAAU,kCAAkC;IAC1C,QAAQ,CAAC,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IACnC,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,SAAS,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,sBAAsB,KAAK,OAAO,CAAC;IAC9E,QAAQ,CAAC,gBAAgB,EAAE,CAAC,QAAQ,EAAE,OAAO,KAAK,IAAI,CAAC;CACxD"}
@@ -6,7 +6,7 @@ export function GridInteractionsKeyboardProxy({ children, enabled, onKeyDown, on
6
6
  onSpaceKeyChange(false);
7
7
  }, [enabled, onSpaceKeyChange]);
8
8
  React.useEffect(() => () => onSpaceKeyChange(false), [onSpaceKeyChange]);
9
- return (<div aria-label="Grid interaction keyboard controls" onBlur={() => onSpaceKeyChange(false)} onKeyDown={(event) => {
9
+ return (<div aria-label="Grid interaction keyboard controls" role="group" onBlur={() => onSpaceKeyChange(false)} onKeyDown={(event) => {
10
10
  if (!enabled)
11
11
  return;
12
12
  if (isSpaceKey(event))
@@ -24,10 +24,17 @@ export function GridInteractionsKeyboardProxy({ children, enabled, onKeyDown, on
24
24
  }} onKeyUp={(event) => {
25
25
  if (isSpaceKey(event))
26
26
  onSpaceKeyChange(false);
27
- }} style={{ display: 'contents' }} tabIndex={enabled ? 0 : -1}>
27
+ }} style={keyboardBoundaryStyle} tabIndex={enabled ? 0 : -1}>
28
28
  {children}
29
29
  </div>);
30
30
  }
31
+ const keyboardBoundaryStyle = {
32
+ alignSelf: 'stretch',
33
+ display: 'flex',
34
+ flex: 1,
35
+ minHeight: 0,
36
+ minWidth: 0,
37
+ };
31
38
  /*** Determines whether a web keyboard event represents the Space modifier. */
32
39
  function isSpaceKey(event) {
33
40
  return event.code === 'Space' || event.key === ' ';
@@ -1 +1 @@
1
- {"version":3,"file":"GridInteractionsKeyboardProxy.web.js","sourceRoot":"","sources":["../../../../../src/features/grid-interactions/adapters/inbound/GridInteractionsKeyboardProxy.web.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAI1B,oGAAoG;AACpG,MAAM,UAAU,6BAA6B,CAAC,EAC5C,QAAQ,EACR,OAAO,EACP,SAAS,EACT,gBAAgB,GACmB;IACnC,KAAK,CAAC,SAAS,CAAC,GAAG,EAAE;QACnB,IAAI,CAAC,OAAO;YAAE,gBAAgB,CAAC,KAAK,CAAC,CAAC;IACxC,CAAC,EAAE,CAAC,OAAO,EAAE,gBAAgB,CAAC,CAAC,CAAC;IAChC,KAAK,CAAC,SAAS,CAAC,GAAG,EAAE,CAAC,GAAG,EAAE,CAAC,gBAAgB,CAAC,KAAK,CAAC,EAAE,CAAC,gBAAgB,CAAC,CAAC,CAAC;IAEzE,OAAO,CACL,CAAC,GAAG,CACF,UAAU,CAAC,oCAAoC,CAC/C,MAAM,CAAC,CAAC,GAAG,EAAE,CAAC,gBAAgB,CAAC,KAAK,CAAC,CAAC,CACtC,SAAS,CAAC,CAAC,CAAC,KAAK,EAAE,EAAE;YACnB,IAAI,CAAC,OAAO;gBAAE,OAAO;YACrB,IAAI,UAAU,CAAC,KAAK,CAAC;gBAAE,gBAAgB,CAAC,IAAI,CAAC,CAAC;YAC9C,IACE,SAAS,CAAC,KAAK,CAAC,GAAG,EAAE;gBACnB,MAAM,EAAE,KAAK,CAAC,MAAM;gBACpB,OAAO,EAAE,KAAK,CAAC,OAAO;gBACtB,OAAO,EAAE,KAAK,CAAC,OAAO;gBACtB,QAAQ,EAAE,KAAK,CAAC,QAAQ;gBACxB,CAAC,EAAE,CAAC;gBACJ,CAAC,EAAE,CAAC;aACL,CAAC,EACF,CAAC;gBACD,KAAK,CAAC,cAAc,EAAE,CAAC;YACzB,CAAC;QACH,CAAC,CAAC,CACF,OAAO,CAAC,CAAC,CAAC,KAAK,EAAE,EAAE;YACjB,IAAI,UAAU,CAAC,KAAK,CAAC;gBAAE,gBAAgB,CAAC,KAAK,CAAC,CAAC;QACjD,CAAC,CAAC,CACF,KAAK,CAAC,CAAC,EAAE,OAAO,EAAE,UAAU,EAAE,CAAC,CAC/B,QAAQ,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAE3B;MAAA,CAAC,QAAQ,CACX;IAAA,EAAE,GAAG,CAAC,CACP,CAAC;AACJ,CAAC;AASD,8EAA8E;AAC9E,SAAS,UAAU,CAAC,KAA0C;IAC5D,OAAO,KAAK,CAAC,IAAI,KAAK,OAAO,IAAI,KAAK,CAAC,GAAG,KAAK,GAAG,CAAC;AACrD,CAAC","sourcesContent":["import React from 'react';\n\nimport type { GridInteractionPointer } from '../../../../types/grid-interactions';\n\n/*** Adapts web keyboard movement and resize commands without changing native responder behavior. */\nexport function GridInteractionsKeyboardProxy({\n children,\n enabled,\n onKeyDown,\n onSpaceKeyChange,\n}: GridInteractionsKeyboardProxyProps) {\n React.useEffect(() => {\n if (!enabled) onSpaceKeyChange(false);\n }, [enabled, onSpaceKeyChange]);\n React.useEffect(() => () => onSpaceKeyChange(false), [onSpaceKeyChange]);\n\n return (\n <div\n aria-label=\"Grid interaction keyboard controls\"\n onBlur={() => onSpaceKeyChange(false)}\n onKeyDown={(event) => {\n if (!enabled) return;\n if (isSpaceKey(event)) onSpaceKeyChange(true);\n if (\n onKeyDown(event.key, {\n altKey: event.altKey,\n ctrlKey: event.ctrlKey,\n metaKey: event.metaKey,\n shiftKey: event.shiftKey,\n x: 0,\n y: 0,\n })\n ) {\n event.preventDefault();\n }\n }}\n onKeyUp={(event) => {\n if (isSpaceKey(event)) onSpaceKeyChange(false);\n }}\n style={{ display: 'contents' }}\n tabIndex={enabled ? 0 : -1}\n >\n {children}\n </div>\n );\n}\n\ninterface GridInteractionsKeyboardProxyProps {\n readonly children: React.ReactNode;\n readonly enabled: boolean;\n readonly onKeyDown: (key: string, pointer: GridInteractionPointer) => boolean;\n readonly onSpaceKeyChange: (spaceKey: boolean) => void;\n}\n\n/*** Determines whether a web keyboard event represents the Space modifier. */\nfunction isSpaceKey(event: React.KeyboardEvent<HTMLDivElement>): boolean {\n return event.code === 'Space' || event.key === ' ';\n}\n"]}
1
+ {"version":3,"file":"GridInteractionsKeyboardProxy.web.js","sourceRoot":"","sources":["../../../../../src/features/grid-interactions/adapters/inbound/GridInteractionsKeyboardProxy.web.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAI1B,oGAAoG;AACpG,MAAM,UAAU,6BAA6B,CAAC,EAC5C,QAAQ,EACR,OAAO,EACP,SAAS,EACT,gBAAgB,GACmB;IACnC,KAAK,CAAC,SAAS,CAAC,GAAG,EAAE;QACnB,IAAI,CAAC,OAAO;YAAE,gBAAgB,CAAC,KAAK,CAAC,CAAC;IACxC,CAAC,EAAE,CAAC,OAAO,EAAE,gBAAgB,CAAC,CAAC,CAAC;IAChC,KAAK,CAAC,SAAS,CAAC,GAAG,EAAE,CAAC,GAAG,EAAE,CAAC,gBAAgB,CAAC,KAAK,CAAC,EAAE,CAAC,gBAAgB,CAAC,CAAC,CAAC;IAEzE,OAAO,CACL,CAAC,GAAG,CACF,UAAU,CAAC,oCAAoC,CAC/C,IAAI,CAAC,OAAO,CACZ,MAAM,CAAC,CAAC,GAAG,EAAE,CAAC,gBAAgB,CAAC,KAAK,CAAC,CAAC,CACtC,SAAS,CAAC,CAAC,CAAC,KAAK,EAAE,EAAE;YACnB,IAAI,CAAC,OAAO;gBAAE,OAAO;YACrB,IAAI,UAAU,CAAC,KAAK,CAAC;gBAAE,gBAAgB,CAAC,IAAI,CAAC,CAAC;YAC9C,IACE,SAAS,CAAC,KAAK,CAAC,GAAG,EAAE;gBACnB,MAAM,EAAE,KAAK,CAAC,MAAM;gBACpB,OAAO,EAAE,KAAK,CAAC,OAAO;gBACtB,OAAO,EAAE,KAAK,CAAC,OAAO;gBACtB,QAAQ,EAAE,KAAK,CAAC,QAAQ;gBACxB,CAAC,EAAE,CAAC;gBACJ,CAAC,EAAE,CAAC;aACL,CAAC,EACF,CAAC;gBACD,KAAK,CAAC,cAAc,EAAE,CAAC;YACzB,CAAC;QACH,CAAC,CAAC,CACF,OAAO,CAAC,CAAC,CAAC,KAAK,EAAE,EAAE;YACjB,IAAI,UAAU,CAAC,KAAK,CAAC;gBAAE,gBAAgB,CAAC,KAAK,CAAC,CAAC;QACjD,CAAC,CAAC,CACF,KAAK,CAAC,CAAC,qBAAqB,CAAC,CAC7B,QAAQ,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAE3B;MAAA,CAAC,QAAQ,CACX;IAAA,EAAE,GAAG,CAAC,CACP,CAAC;AACJ,CAAC;AAED,MAAM,qBAAqB,GAAG;IAC5B,SAAS,EAAE,SAAS;IACpB,OAAO,EAAE,MAAM;IACf,IAAI,EAAE,CAAC;IACP,SAAS,EAAE,CAAC;IACZ,QAAQ,EAAE,CAAC;CACH,CAAC;AASX,8EAA8E;AAC9E,SAAS,UAAU,CAAC,KAA0C;IAC5D,OAAO,KAAK,CAAC,IAAI,KAAK,OAAO,IAAI,KAAK,CAAC,GAAG,KAAK,GAAG,CAAC;AACrD,CAAC","sourcesContent":["import React from 'react';\n\nimport type { GridInteractionPointer } from '../../../../types/grid-interactions';\n\n/*** Adapts web keyboard movement and resize commands without changing native responder behavior. */\nexport function GridInteractionsKeyboardProxy({\n children,\n enabled,\n onKeyDown,\n onSpaceKeyChange,\n}: GridInteractionsKeyboardProxyProps) {\n React.useEffect(() => {\n if (!enabled) onSpaceKeyChange(false);\n }, [enabled, onSpaceKeyChange]);\n React.useEffect(() => () => onSpaceKeyChange(false), [onSpaceKeyChange]);\n\n return (\n <div\n aria-label=\"Grid interaction keyboard controls\"\n role=\"group\"\n onBlur={() => onSpaceKeyChange(false)}\n onKeyDown={(event) => {\n if (!enabled) return;\n if (isSpaceKey(event)) onSpaceKeyChange(true);\n if (\n onKeyDown(event.key, {\n altKey: event.altKey,\n ctrlKey: event.ctrlKey,\n metaKey: event.metaKey,\n shiftKey: event.shiftKey,\n x: 0,\n y: 0,\n })\n ) {\n event.preventDefault();\n }\n }}\n onKeyUp={(event) => {\n if (isSpaceKey(event)) onSpaceKeyChange(false);\n }}\n style={keyboardBoundaryStyle}\n tabIndex={enabled ? 0 : -1}\n >\n {children}\n </div>\n );\n}\n\nconst keyboardBoundaryStyle = {\n alignSelf: 'stretch',\n display: 'flex',\n flex: 1,\n minHeight: 0,\n minWidth: 0,\n} as const;\n\ninterface GridInteractionsKeyboardProxyProps {\n readonly children: React.ReactNode;\n readonly enabled: boolean;\n readonly onKeyDown: (key: string, pointer: GridInteractionPointer) => boolean;\n readonly onSpaceKeyChange: (spaceKey: boolean) => void;\n}\n\n/*** Determines whether a web keyboard event represents the Space modifier. */\nfunction isSpaceKey(event: React.KeyboardEvent<HTMLDivElement>): boolean {\n return event.code === 'Space' || event.key === ' ';\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"MatrixGrid.d.ts","sourceRoot":"","sources":["../../../../../src/features/matrix-grid/adapters/inbound/MatrixGrid.tsx"],"names":[],"mappings":"AAOA,OAAO,KAAK,MAAM,OAAO,CAAC;AAG1B,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AA4NrE,gGAAgG;AAChG,eAAO,MAAM,UAAU,uDAAsC,CAAC"}
1
+ {"version":3,"file":"MatrixGrid.d.ts","sourceRoot":"","sources":["../../../../../src/features/matrix-grid/adapters/inbound/MatrixGrid.tsx"],"names":[],"mappings":"AAOA,OAAO,KAAK,MAAM,OAAO,CAAC;AAG1B,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AAuOrE,gGAAgG;AAChG,eAAO,MAAM,UAAU,uDAAsC,CAAC"}
@@ -12,7 +12,7 @@ import { resolveMatrixGridSelection } from '../../application/resolveMatrixGridS
12
12
  import { MatrixGridKeyboardProxy } from './MatrixGridKeyboardProxy';
13
13
  import { useMatrixGridKeyboardFocus } from './useMatrixGridKeyboardFocus';
14
14
  /*** Compose the canonical GridView around released sparse matrix placement and culling geometry. */
15
- function MatrixGridInner({ accessibilityLabel = 'Matrix grid', cells, height, interactionPolicy, layout, onSelectionChange, onViewportChange, overscanPixels = 160, readOnly = false, renderCell, selectedCellIds = [], selectionMode = 'single', testID, viewport: controlledViewport, width, zoomX = 1, zoomY = 1, }) {
15
+ function MatrixGridInner({ accessibilityLabel = 'Matrix grid', cells, height, interactionPolicy, layout, onSelectionChange, onViewportChange, overscanPixels = 160, protectedCellIds = [], readOnly = false, renderCell, selectedCellIds = [], selectionMode = 'single', testID, viewport: controlledViewport, width, zoomX = 1, zoomY = 1, }) {
16
16
  const { theme } = useZoraTheme();
17
17
  const contentSize = React.useMemo(() => resolveMatrixGridContentSize(layout), [layout]);
18
18
  const initialViewport = React.useMemo(() => ({ height, offsetX: 0, offsetY: 0, pixelsPerUnitX: zoomX, pixelsPerUnitY: zoomY, width }), [height, width, zoomX, zoomY]);
@@ -22,18 +22,20 @@ function MatrixGridInner({ accessibilityLabel = 'Matrix grid', cells, height, in
22
22
  const cellPlacements = React.useMemo(() => getVisibleMatrixCells(layout, cells, viewport, overscanPixels), [cells, layout, overscanPixels, viewport]);
23
23
  const [visibleIds, setVisibleIds] = React.useState([]);
24
24
  const anchorId = React.useRef(null);
25
+ const protectedCellIdSet = React.useMemo(() => new Set(protectedCellIds), [protectedCellIds]);
26
+ const isNavigable = React.useCallback((id) => !protectedCellIdSet.has(id), [protectedCellIdSet]);
25
27
  const handleViewportChange = React.useCallback((proposal) => {
26
28
  if (controlledViewport === undefined)
27
29
  setUncontrolledViewport(proposal);
28
30
  onViewportChange?.(proposal);
29
31
  }, [controlledViewport, onViewportChange]);
30
32
  const activateCell = React.useCallback((cell, event) => {
31
- if (interactionPolicy === 'passive' || readOnly)
33
+ if (interactionPolicy === 'passive' || readOnly || protectedCellIdSet.has(cell.id))
32
34
  return;
33
35
  const intent = isRangeSelectionEvent(event)
34
36
  ? 'range'
35
37
  : resolveSelectionEventIntent(event, 'pointer');
36
- onSelectionChange?.(resolveMatrixGridSelection(sparseIndex.entries, selectedCellIds, cell.id, anchorId.current, intent, selectionMode));
38
+ onSelectionChange?.(resolveMatrixGridSelection(sparseIndex.entries, selectedCellIds, cell.id, anchorId.current, intent, selectionMode, protectedCellIdSet));
37
39
  if (intent !== 'range')
38
40
  anchorId.current = cell.id;
39
41
  }, [
@@ -43,6 +45,7 @@ function MatrixGridInner({ accessibilityLabel = 'Matrix grid', cells, height, in
43
45
  selectedCellIds,
44
46
  selectionMode,
45
47
  sparseIndex.entries,
48
+ protectedCellIdSet,
46
49
  ]);
47
50
  const handleKeyboardNavigation = React.useCallback((targetId, originId, shiftKey) => {
48
51
  anchorId.current ??= originId;
@@ -52,7 +55,7 @@ function MatrixGridInner({ accessibilityLabel = 'Matrix grid', cells, height, in
52
55
  if (cell)
53
56
  activateCell(getMatrixCellPlacement(layout, cell), { shiftKey: true });
54
57
  }, [activateCell, layout, sparseIndex]);
55
- const keyboard = useMatrixGridKeyboardFocus(sparseIndex, interactionPolicy === 'passive', visibleIds, handleKeyboardNavigation);
58
+ const keyboard = useMatrixGridKeyboardFocus(sparseIndex, interactionPolicy === 'passive', visibleIds, isNavigable, handleKeyboardNavigation);
56
59
  const focusedPlacement = React.useMemo(() => {
57
60
  const focusedCell = keyboard.focusedId === null ? undefined : sparseIndex.cellById.get(keyboard.focusedId);
58
61
  return focusedCell ? getMatrixCellPlacement(layout, focusedCell) : undefined;
@@ -66,12 +69,12 @@ function MatrixGridInner({ accessibilityLabel = 'Matrix grid', cells, height, in
66
69
  const cell = cellById.get(item.id);
67
70
  if (!cell)
68
71
  return null;
69
- return renderMatrixCell(cell, selectedCellIds, theme.semantics.neutral.divider, theme.colors.primary, renderCell, activateCell, keyboard, interactionPolicy === 'passive', readOnly, testID);
72
+ return renderMatrixCell(cell, selectedCellIds, theme.semantics.neutral.divider, theme.colors.primary, renderCell, activateCell, keyboard, interactionPolicy === 'passive', protectedCellIdSet.has(cell.id), readOnly, testID);
70
73
  }} testID={testID} viewport={viewport} width={width} onViewportChange={handleViewportChange}/>
71
74
  </View>);
72
75
  }
73
76
  /*** Render one positioned sparse cell with native/web accessible selection semantics. */
74
- function renderMatrixCell(cell, selectedCellIds, dividerColor, selectedColor, renderCell, activateCell, keyboard, passive, readOnly, testID) {
77
+ function renderMatrixCell(cell, selectedCellIds, dividerColor, selectedColor, renderCell, activateCell, keyboard, passive, protectedCell, readOnly, testID) {
75
78
  const selected = selectedCellIds.includes(cell.id);
76
79
  const canInteract = keyboard.tabStopId === cell.id;
77
80
  return (<MatrixGridKeyboardProxy onKeyDown={(event) => {
@@ -79,12 +82,12 @@ function renderMatrixCell(cell, selectedCellIds, dividerColor, selectedColor, re
79
82
  return true;
80
83
  if (!['Enter', ' '].includes(event.key))
81
84
  return false;
82
- if (passive || readOnly)
85
+ if (passive || protectedCell || readOnly)
83
86
  return false;
84
87
  activateCell(cell, event);
85
88
  return true;
86
89
  }}>
87
- <NativePressable key={cell.id} accessibilityActions={[{ name: 'activate' }]} accessibilityLabel={`Cell ${cell.id}`} accessibilityRole="button" accessibilityState={{ disabled: passive, selected }} disabled={passive} focusable={Platform.OS !== 'web' || canInteract} style={[
90
+ <NativePressable key={cell.id} accessibilityActions={[{ name: 'activate' }]} accessibilityLabel={`Cell ${cell.id}`} accessibilityRole="button" accessibilityState={{ disabled: passive || protectedCell, selected }} disabled={passive || protectedCell} focusable={!protectedCell && (Platform.OS !== 'web' || canInteract)} style={[
88
91
  styles.cell,
89
92
  {
90
93
  borderColor: selected || keyboard.focusedId === cell.id ? selectedColor : dividerColor,
@@ -1 +1 @@
1
- {"version":3,"file":"MatrixGrid.js","sourceRoot":"","sources":["../../../../../src/features/matrix-grid/adapters/inbound/MatrixGrid.tsx"],"names":[],"mappings":"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","sourcesContent":["import {\n getMatrixCellPlacement,\n getVisibleMatrixCells,\n type GridMatrixCellPlacement,\n type GridViewport,\n} from '@ankhorage/grid-view';\nimport { isRecord } from '@ankhorage/utility/object';\nimport React from 'react';\nimport { Platform, Pressable as NativePressable, StyleSheet, View } from 'react-native';\n\nimport type { MatrixGridProps } from '../../../../types/matrix-grid';\nimport { GridView } from '../../../grid-view/public';\nimport { resolveSelectionEventIntent } from '../../../selection/public';\nimport { withZoraThemeScope } from '../../../theme/adapters/inbound/withZoraThemeScope';\nimport { useZoraTheme } from '../../../theme/composition/useZoraTheme';\nimport { createMatrixGridSparseIndex } from '../../application/createMatrixGridSparseIndex';\nimport { resolveMatrixGridContentSize } from '../../application/resolveMatrixGridContentSize';\nimport { resolveMatrixGridSelection } from '../../application/resolveMatrixGridSelection';\nimport { MatrixGridKeyboardProxy } from './MatrixGridKeyboardProxy';\nimport { useMatrixGridKeyboardFocus } from './useMatrixGridKeyboardFocus';\n\n/*** Compose the canonical GridView around released sparse matrix placement and culling geometry. */\nfunction MatrixGridInner({\n accessibilityLabel = 'Matrix grid',\n cells,\n height,\n interactionPolicy,\n layout,\n onSelectionChange,\n onViewportChange,\n overscanPixels = 160,\n readOnly = false,\n renderCell,\n selectedCellIds = [],\n selectionMode = 'single',\n testID,\n viewport: controlledViewport,\n width,\n zoomX = 1,\n zoomY = 1,\n}: MatrixGridProps) {\n const { theme } = useZoraTheme();\n const contentSize = React.useMemo(() => resolveMatrixGridContentSize(layout), [layout]);\n const initialViewport = React.useMemo<GridViewport>(\n () => ({ height, offsetX: 0, offsetY: 0, pixelsPerUnitX: zoomX, pixelsPerUnitY: zoomY, width }),\n [height, width, zoomX, zoomY],\n );\n const [uncontrolledViewport, setUncontrolledViewport] = React.useState(initialViewport);\n const viewport = controlledViewport ?? uncontrolledViewport;\n const sparseIndex = React.useMemo(\n () => createMatrixGridSparseIndex(layout, cells),\n [cells, layout],\n );\n const cellPlacements = React.useMemo(\n () => getVisibleMatrixCells(layout, cells, viewport, overscanPixels),\n [cells, layout, overscanPixels, viewport],\n );\n const [visibleIds, setVisibleIds] = React.useState<readonly string[]>([]);\n const anchorId = React.useRef<string | null>(null);\n\n const handleViewportChange = React.useCallback(\n (proposal: GridViewport) => {\n if (controlledViewport === undefined) setUncontrolledViewport(proposal);\n onViewportChange?.(proposal);\n },\n [controlledViewport, onViewportChange],\n );\n\n const activateCell = React.useCallback(\n (cell: GridMatrixCellPlacement, event: unknown) => {\n if (interactionPolicy === 'passive' || readOnly) return;\n const intent = isRangeSelectionEvent(event)\n ? 'range'\n : resolveSelectionEventIntent(event, 'pointer');\n onSelectionChange?.(\n resolveMatrixGridSelection(\n sparseIndex.entries,\n selectedCellIds,\n cell.id,\n anchorId.current,\n intent,\n selectionMode,\n ),\n );\n if (intent !== 'range') anchorId.current = cell.id;\n },\n [\n interactionPolicy,\n onSelectionChange,\n readOnly,\n selectedCellIds,\n selectionMode,\n sparseIndex.entries,\n ],\n );\n const handleKeyboardNavigation = React.useCallback(\n (targetId: string, originId: string, shiftKey: boolean) => {\n anchorId.current ??= originId;\n if (!shiftKey) return;\n const cell = sparseIndex.cellById.get(targetId);\n if (cell) activateCell(getMatrixCellPlacement(layout, cell), { shiftKey: true });\n },\n [activateCell, layout, sparseIndex],\n );\n const keyboard = useMatrixGridKeyboardFocus(\n sparseIndex,\n interactionPolicy === 'passive',\n visibleIds,\n handleKeyboardNavigation,\n );\n const focusedPlacement = React.useMemo(() => {\n const focusedCell =\n keyboard.focusedId === null ? undefined : sparseIndex.cellById.get(keyboard.focusedId);\n return focusedCell ? getMatrixCellPlacement(layout, focusedCell) : undefined;\n }, [keyboard.focusedId, layout, sparseIndex.cellById]);\n const gridItems = React.useMemo(\n () =>\n focusedPlacement && !cellPlacements.some((cell) => cell.id === focusedPlacement.id)\n ? [...cellPlacements, focusedPlacement]\n : cellPlacements,\n [cellPlacements, focusedPlacement],\n );\n const cellById = React.useMemo(\n () => new Map(gridItems.map((cell) => [cell.id, cell])),\n [gridItems],\n );\n\n return (\n <View accessibilityLabel={accessibilityLabel}>\n <GridView\n contentHeight={contentSize.height}\n contentWidth={contentSize.width}\n height={height}\n interactionPolicy={interactionPolicy}\n items={gridItems}\n focusedItemId={keyboard.focusedId ?? undefined}\n overscanPixels={0}\n onVisibleItemIdsChange={setVisibleIds}\n renderItem={(item) => {\n const cell = cellById.get(item.id);\n if (!cell) return null;\n return renderMatrixCell(\n cell,\n selectedCellIds,\n theme.semantics.neutral.divider,\n theme.colors.primary,\n renderCell,\n activateCell,\n keyboard,\n interactionPolicy === 'passive',\n readOnly,\n testID,\n );\n }}\n testID={testID}\n viewport={viewport}\n width={width}\n onViewportChange={handleViewportChange}\n />\n </View>\n );\n}\n\n/*** Render one positioned sparse cell with native/web accessible selection semantics. */\nfunction renderMatrixCell(\n cell: GridMatrixCellPlacement,\n selectedCellIds: readonly string[],\n dividerColor: string,\n selectedColor: string,\n renderCell: MatrixGridProps['renderCell'],\n activateCell: (cell: GridMatrixCellPlacement, event: unknown) => void,\n keyboard: ReturnType<typeof useMatrixGridKeyboardFocus>,\n passive: boolean,\n readOnly: boolean,\n testID: string | undefined,\n) {\n const selected = selectedCellIds.includes(cell.id);\n const canInteract = keyboard.tabStopId === cell.id;\n return (\n <MatrixGridKeyboardProxy\n onKeyDown={(event) => {\n if (keyboard.onKeyDown(cell.id, event.key, event.shiftKey)) return true;\n if (!['Enter', ' '].includes(event.key)) return false;\n if (passive || readOnly) return false;\n activateCell(cell, event);\n return true;\n }}\n >\n <NativePressable\n key={cell.id}\n accessibilityActions={[{ name: 'activate' }]}\n accessibilityLabel={`Cell ${cell.id}`}\n accessibilityRole=\"button\"\n accessibilityState={{ disabled: passive, selected }}\n disabled={passive}\n focusable={Platform.OS !== 'web' || canInteract}\n style={[\n styles.cell,\n {\n borderColor: selected || keyboard.focusedId === cell.id ? selectedColor : dividerColor,\n height: '100%',\n width: '100%',\n },\n ]}\n testID={testID === undefined ? undefined : `${testID}-cell-${cell.id}`}\n onAccessibilityAction={() => activateCell(cell, {})}\n onFocus={() => keyboard.onFocus(cell.id)}\n onPress={(event) => activateCell(cell, event)}\n ref={(node) => keyboard.registerCell(cell.id, node)}\n >\n {renderCell(cell)}\n </NativePressable>\n </MatrixGridKeyboardProxy>\n );\n}\n\n/*** Recognize range intent without changing the shared replace-or-toggle selection contract. */\nfunction isRangeSelectionEvent(event: unknown): boolean {\n if (!isRecord(event)) return false;\n if (event.shiftKey === true) return true;\n return (\n (isRecord(event.nativeEvent) && event.nativeEvent.shiftKey === true) ||\n (isRecord(event.originalEvent) && event.originalEvent.shiftKey === true)\n );\n}\n\nconst styles = StyleSheet.create({\n cell: { borderWidth: 1, height: '100%', overflow: 'hidden', width: '100%' },\n});\n\n/*** A code-first generic sparse matrix grid; formulas and domain vocabulary remain external. */\nexport const MatrixGrid = withZoraThemeScope(MatrixGridInner);\n"]}
1
+ {"version":3,"file":"MatrixGrid.js","sourceRoot":"","sources":["../../../../../src/features/matrix-grid/adapters/inbound/MatrixGrid.tsx"],"names":[],"mappings":"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","sourcesContent":["import {\n getMatrixCellPlacement,\n getVisibleMatrixCells,\n type GridMatrixCellPlacement,\n type GridViewport,\n} from '@ankhorage/grid-view';\nimport { isRecord } from '@ankhorage/utility/object';\nimport React from 'react';\nimport { Platform, Pressable as NativePressable, StyleSheet, View } from 'react-native';\n\nimport type { MatrixGridProps } from '../../../../types/matrix-grid';\nimport { GridView } from '../../../grid-view/public';\nimport { resolveSelectionEventIntent } from '../../../selection/public';\nimport { withZoraThemeScope } from '../../../theme/adapters/inbound/withZoraThemeScope';\nimport { useZoraTheme } from '../../../theme/composition/useZoraTheme';\nimport { createMatrixGridSparseIndex } from '../../application/createMatrixGridSparseIndex';\nimport { resolveMatrixGridContentSize } from '../../application/resolveMatrixGridContentSize';\nimport { resolveMatrixGridSelection } from '../../application/resolveMatrixGridSelection';\nimport { MatrixGridKeyboardProxy } from './MatrixGridKeyboardProxy';\nimport { useMatrixGridKeyboardFocus } from './useMatrixGridKeyboardFocus';\n\n/*** Compose the canonical GridView around released sparse matrix placement and culling geometry. */\nfunction MatrixGridInner({\n accessibilityLabel = 'Matrix grid',\n cells,\n height,\n interactionPolicy,\n layout,\n onSelectionChange,\n onViewportChange,\n overscanPixels = 160,\n protectedCellIds = [],\n readOnly = false,\n renderCell,\n selectedCellIds = [],\n selectionMode = 'single',\n testID,\n viewport: controlledViewport,\n width,\n zoomX = 1,\n zoomY = 1,\n}: MatrixGridProps) {\n const { theme } = useZoraTheme();\n const contentSize = React.useMemo(() => resolveMatrixGridContentSize(layout), [layout]);\n const initialViewport = React.useMemo<GridViewport>(\n () => ({ height, offsetX: 0, offsetY: 0, pixelsPerUnitX: zoomX, pixelsPerUnitY: zoomY, width }),\n [height, width, zoomX, zoomY],\n );\n const [uncontrolledViewport, setUncontrolledViewport] = React.useState(initialViewport);\n const viewport = controlledViewport ?? uncontrolledViewport;\n const sparseIndex = React.useMemo(\n () => createMatrixGridSparseIndex(layout, cells),\n [cells, layout],\n );\n const cellPlacements = React.useMemo(\n () => getVisibleMatrixCells(layout, cells, viewport, overscanPixels),\n [cells, layout, overscanPixels, viewport],\n );\n const [visibleIds, setVisibleIds] = React.useState<readonly string[]>([]);\n const anchorId = React.useRef<string | null>(null);\n const protectedCellIdSet = React.useMemo(() => new Set(protectedCellIds), [protectedCellIds]);\n const isNavigable = React.useCallback(\n (id: string) => !protectedCellIdSet.has(id),\n [protectedCellIdSet],\n );\n\n const handleViewportChange = React.useCallback(\n (proposal: GridViewport) => {\n if (controlledViewport === undefined) setUncontrolledViewport(proposal);\n onViewportChange?.(proposal);\n },\n [controlledViewport, onViewportChange],\n );\n\n const activateCell = React.useCallback(\n (cell: GridMatrixCellPlacement, event: unknown) => {\n if (interactionPolicy === 'passive' || readOnly || protectedCellIdSet.has(cell.id)) return;\n const intent = isRangeSelectionEvent(event)\n ? 'range'\n : resolveSelectionEventIntent(event, 'pointer');\n onSelectionChange?.(\n resolveMatrixGridSelection(\n sparseIndex.entries,\n selectedCellIds,\n cell.id,\n anchorId.current,\n intent,\n selectionMode,\n protectedCellIdSet,\n ),\n );\n if (intent !== 'range') anchorId.current = cell.id;\n },\n [\n interactionPolicy,\n onSelectionChange,\n readOnly,\n selectedCellIds,\n selectionMode,\n sparseIndex.entries,\n protectedCellIdSet,\n ],\n );\n const handleKeyboardNavigation = React.useCallback(\n (targetId: string, originId: string, shiftKey: boolean) => {\n anchorId.current ??= originId;\n if (!shiftKey) return;\n const cell = sparseIndex.cellById.get(targetId);\n if (cell) activateCell(getMatrixCellPlacement(layout, cell), { shiftKey: true });\n },\n [activateCell, layout, sparseIndex],\n );\n const keyboard = useMatrixGridKeyboardFocus(\n sparseIndex,\n interactionPolicy === 'passive',\n visibleIds,\n isNavigable,\n handleKeyboardNavigation,\n );\n const focusedPlacement = React.useMemo(() => {\n const focusedCell =\n keyboard.focusedId === null ? undefined : sparseIndex.cellById.get(keyboard.focusedId);\n return focusedCell ? getMatrixCellPlacement(layout, focusedCell) : undefined;\n }, [keyboard.focusedId, layout, sparseIndex.cellById]);\n const gridItems = React.useMemo(\n () =>\n focusedPlacement && !cellPlacements.some((cell) => cell.id === focusedPlacement.id)\n ? [...cellPlacements, focusedPlacement]\n : cellPlacements,\n [cellPlacements, focusedPlacement],\n );\n const cellById = React.useMemo(\n () => new Map(gridItems.map((cell) => [cell.id, cell])),\n [gridItems],\n );\n\n return (\n <View accessibilityLabel={accessibilityLabel}>\n <GridView\n contentHeight={contentSize.height}\n contentWidth={contentSize.width}\n height={height}\n interactionPolicy={interactionPolicy}\n items={gridItems}\n focusedItemId={keyboard.focusedId ?? undefined}\n overscanPixels={0}\n onVisibleItemIdsChange={setVisibleIds}\n renderItem={(item) => {\n const cell = cellById.get(item.id);\n if (!cell) return null;\n return renderMatrixCell(\n cell,\n selectedCellIds,\n theme.semantics.neutral.divider,\n theme.colors.primary,\n renderCell,\n activateCell,\n keyboard,\n interactionPolicy === 'passive',\n protectedCellIdSet.has(cell.id),\n readOnly,\n testID,\n );\n }}\n testID={testID}\n viewport={viewport}\n width={width}\n onViewportChange={handleViewportChange}\n />\n </View>\n );\n}\n\n/*** Render one positioned sparse cell with native/web accessible selection semantics. */\nfunction renderMatrixCell(\n cell: GridMatrixCellPlacement,\n selectedCellIds: readonly string[],\n dividerColor: string,\n selectedColor: string,\n renderCell: MatrixGridProps['renderCell'],\n activateCell: (cell: GridMatrixCellPlacement, event: unknown) => void,\n keyboard: ReturnType<typeof useMatrixGridKeyboardFocus>,\n passive: boolean,\n protectedCell: boolean,\n readOnly: boolean,\n testID: string | undefined,\n) {\n const selected = selectedCellIds.includes(cell.id);\n const canInteract = keyboard.tabStopId === cell.id;\n return (\n <MatrixGridKeyboardProxy\n onKeyDown={(event) => {\n if (keyboard.onKeyDown(cell.id, event.key, event.shiftKey)) return true;\n if (!['Enter', ' '].includes(event.key)) return false;\n if (passive || protectedCell || readOnly) return false;\n activateCell(cell, event);\n return true;\n }}\n >\n <NativePressable\n key={cell.id}\n accessibilityActions={[{ name: 'activate' }]}\n accessibilityLabel={`Cell ${cell.id}`}\n accessibilityRole=\"button\"\n accessibilityState={{ disabled: passive || protectedCell, selected }}\n disabled={passive || protectedCell}\n focusable={!protectedCell && (Platform.OS !== 'web' || canInteract)}\n style={[\n styles.cell,\n {\n borderColor: selected || keyboard.focusedId === cell.id ? selectedColor : dividerColor,\n height: '100%',\n width: '100%',\n },\n ]}\n testID={testID === undefined ? undefined : `${testID}-cell-${cell.id}`}\n onAccessibilityAction={() => activateCell(cell, {})}\n onFocus={() => keyboard.onFocus(cell.id)}\n onPress={(event) => activateCell(cell, event)}\n ref={(node) => keyboard.registerCell(cell.id, node)}\n >\n {renderCell(cell)}\n </NativePressable>\n </MatrixGridKeyboardProxy>\n );\n}\n\n/*** Recognize range intent without changing the shared replace-or-toggle selection contract. */\nfunction isRangeSelectionEvent(event: unknown): boolean {\n if (!isRecord(event)) return false;\n if (event.shiftKey === true) return true;\n return (\n (isRecord(event.nativeEvent) && event.nativeEvent.shiftKey === true) ||\n (isRecord(event.originalEvent) && event.originalEvent.shiftKey === true)\n );\n}\n\nconst styles = StyleSheet.create({\n cell: { borderWidth: 1, height: '100%', overflow: 'hidden', width: '100%' },\n});\n\n/*** A code-first generic sparse matrix grid; formulas and domain vocabulary remain external. */\nexport const MatrixGrid = withZoraThemeScope(MatrixGridInner);\n"]}
@@ -1,6 +1,6 @@
1
1
  import type { MatrixGridSparseIndex } from '../../application/createMatrixGridSparseIndex';
2
2
  /*** Keep one roving focus target and restore it after GridView virtualizes a sparse cell. */
3
- export declare function useMatrixGridKeyboardFocus(index: MatrixGridSparseIndex, disabled: boolean, visibleIds: readonly string[], onNavigate: (targetId: string, originId: string, shiftKey: boolean) => void): {
3
+ export declare function useMatrixGridKeyboardFocus(index: MatrixGridSparseIndex, disabled: boolean, visibleIds: readonly string[], isNavigable: (id: string) => boolean, onNavigate: (targetId: string, originId: string, shiftKey: boolean) => void): {
4
4
  focusedId: string | null;
5
5
  onFocus: (id: string) => void;
6
6
  onKeyDown: (id: string, key: string, shiftKey: boolean) => boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"useMatrixGridKeyboardFocus.d.ts","sourceRoot":"","sources":["../../../../../src/features/matrix-grid/adapters/inbound/useMatrixGridKeyboardFocus.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,+CAA+C,CAAC;AAG3F,6FAA6F;AAC7F,wBAAgB,0BAA0B,CACxC,KAAK,EAAE,qBAAqB,EAC5B,QAAQ,EAAE,OAAO,EACjB,UAAU,EAAE,SAAS,MAAM,EAAE,EAC7B,UAAU,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,KAAK,IAAI;;kBAYpC,MAAM;oBAmBtC,MAAM,OAAO,MAAM,YAAY,OAAO,KAAG,OAAO;uBAbX,MAAM,QAAQ,aAAa,GAAG,IAAI;;EAqC/E;AAED,UAAU,aAAa;IACrB,KAAK,EAAE,MAAM,IAAI,CAAC;CACnB"}
1
+ {"version":3,"file":"useMatrixGridKeyboardFocus.d.ts","sourceRoot":"","sources":["../../../../../src/features/matrix-grid/adapters/inbound/useMatrixGridKeyboardFocus.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,+CAA+C,CAAC;AAG3F,6FAA6F;AAC7F,wBAAgB,0BAA0B,CACxC,KAAK,EAAE,qBAAqB,EAC5B,QAAQ,EAAE,OAAO,EACjB,UAAU,EAAE,SAAS,MAAM,EAAE,EAC7B,WAAW,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,EACpC,UAAU,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,KAAK,IAAI;;kBAsBpC,MAAM;oBAmBtC,MAAM,OAAO,MAAM,YAAY,OAAO,KAAG,OAAO;uBAbX,MAAM,QAAQ,aAAa,GAAG,IAAI;;EAqC/E;AAkBD,UAAU,aAAa;IACrB,KAAK,EAAE,MAAM,IAAI,CAAC;CACnB"}
@@ -1,15 +1,24 @@
1
1
  import React from 'react';
2
2
  import { getMatrixGridNextFocusId } from '../../application/getMatrixGridNextFocusId';
3
3
  /*** Keep one roving focus target and restore it after GridView virtualizes a sparse cell. */
4
- export function useMatrixGridKeyboardFocus(index, disabled, visibleIds, onNavigate) {
4
+ export function useMatrixGridKeyboardFocus(index, disabled, visibleIds, isNavigable, onNavigate) {
5
5
  const [focusedId, setFocusedId] = React.useState(null);
6
6
  const focusedRef = React.useRef(null);
7
7
  const pendingFocusRef = React.useRef(null);
8
8
  const cellRefs = React.useRef(new Map());
9
9
  const visibleIdSet = React.useMemo(() => new Set(visibleIds), [visibleIds]);
10
- const tabStopId = focusedId !== null && visibleIdSet.has(focusedId)
10
+ const tabStopId = focusedId !== null && visibleIdSet.has(focusedId) && isNavigable(focusedId)
11
11
  ? focusedId
12
- : visibleIds.find((id) => index.positionById.has(id));
12
+ : visibleIds.find((id) => index.positionById.has(id) && isNavigable(id));
13
+ React.useEffect(() => {
14
+ if (focusedRef.current !== null && !isNavigable(focusedRef.current)) {
15
+ focusedRef.current = null;
16
+ setFocusedId(null);
17
+ }
18
+ if (pendingFocusRef.current !== null && !isNavigable(pendingFocusRef.current)) {
19
+ pendingFocusRef.current = null;
20
+ }
21
+ }, [isNavigable]);
13
22
  const onFocus = React.useCallback((id) => {
14
23
  pendingFocusRef.current = null;
15
24
  focusedRef.current = id;
@@ -31,7 +40,7 @@ export function useMatrixGridKeyboardFocus(index, disabled, visibleIds, onNaviga
31
40
  return false;
32
41
  }
33
42
  const originId = focusedRef.current ?? id;
34
- const nextId = getMatrixGridNextFocusId(index, originId, key);
43
+ const nextId = resolveNextNavigableId(index, originId, key, isNavigable);
35
44
  if (nextId === null || nextId === originId)
36
45
  return true;
37
46
  focusedRef.current = nextId;
@@ -44,7 +53,18 @@ export function useMatrixGridKeyboardFocus(index, disabled, visibleIds, onNaviga
44
53
  mounted.focus();
45
54
  }
46
55
  return true;
47
- }, [disabled, index, onNavigate]);
56
+ }, [disabled, index, isNavigable, onNavigate]);
48
57
  return { focusedId, onFocus, onKeyDown, registerCell, tabStopId };
49
58
  }
59
+ /*** Skip protected sparse cells while retaining the nearest deterministic directional navigation. */
60
+ function resolveNextNavigableId(index, originId, key, isNavigable) {
61
+ let candidate = getMatrixGridNextFocusId(index, originId, key);
62
+ while (candidate !== null && candidate !== originId && !isNavigable(candidate)) {
63
+ const nextCandidate = getMatrixGridNextFocusId(index, candidate, key);
64
+ if (nextCandidate === candidate)
65
+ return originId;
66
+ candidate = nextCandidate;
67
+ }
68
+ return candidate;
69
+ }
50
70
  //# sourceMappingURL=useMatrixGridKeyboardFocus.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"useMatrixGridKeyboardFocus.js","sourceRoot":"","sources":["../../../../../src/features/matrix-grid/adapters/inbound/useMatrixGridKeyboardFocus.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAG1B,OAAO,EAAE,wBAAwB,EAAE,MAAM,4CAA4C,CAAC;AAEtF,6FAA6F;AAC7F,MAAM,UAAU,0BAA0B,CACxC,KAA4B,EAC5B,QAAiB,EACjB,UAA6B,EAC7B,UAA2E;IAE3E,MAAM,CAAC,SAAS,EAAE,YAAY,CAAC,GAAG,KAAK,CAAC,QAAQ,CAAgB,IAAI,CAAC,CAAC;IACtE,MAAM,UAAU,GAAG,KAAK,CAAC,MAAM,CAAgB,IAAI,CAAC,CAAC;IACrD,MAAM,eAAe,GAAG,KAAK,CAAC,MAAM,CAAgB,IAAI,CAAC,CAAC;IAC1D,MAAM,QAAQ,GAAG,KAAK,CAAC,MAAM,CAAC,IAAI,GAAG,EAAyB,CAAC,CAAC;IAChE,MAAM,YAAY,GAAG,KAAK,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,IAAI,GAAG,CAAC,UAAU,CAAC,EAAE,CAAC,UAAU,CAAC,CAAC,CAAC;IAC5E,MAAM,SAAS,GACb,SAAS,KAAK,IAAI,IAAI,YAAY,CAAC,GAAG,CAAC,SAAS,CAAC;QAC/C,CAAC,CAAC,SAAS;QACX,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,KAAK,CAAC,YAAY,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;IAE1D,MAAM,OAAO,GAAG,KAAK,CAAC,WAAW,CAAC,CAAC,EAAU,EAAE,EAAE;QAC/C,eAAe,CAAC,OAAO,GAAG,IAAI,CAAC;QAC/B,UAAU,CAAC,OAAO,GAAG,EAAE,CAAC;QACxB,YAAY,CAAC,EAAE,CAAC,CAAC;IACnB,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,MAAM,YAAY,GAAG,KAAK,CAAC,WAAW,CAAC,CAAC,EAAU,EAAE,IAA0B,EAAE,EAAE;QAChF,IAAI,IAAI,KAAK,IAAI,EAAE,CAAC;YAClB,QAAQ,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;YAC5B,OAAO;QACT,CAAC;QACD,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC;QAC/B,IAAI,eAAe,CAAC,OAAO,KAAK,EAAE,EAAE,CAAC;YACnC,eAAe,CAAC,OAAO,GAAG,IAAI,CAAC;YAC/B,IAAI,CAAC,KAAK,EAAE,CAAC;QACf,CAAC;IACH,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,MAAM,SAAS,GAAG,KAAK,CAAC,WAAW,CACjC,CAAC,EAAU,EAAE,GAAW,EAAE,QAAiB,EAAW,EAAE;QACtD,IAAI,QAAQ,IAAI,CAAC,CAAC,WAAW,EAAE,YAAY,EAAE,SAAS,EAAE,WAAW,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;YACnF,OAAO,KAAK,CAAC;QACf,CAAC;QACD,MAAM,QAAQ,GAAG,UAAU,CAAC,OAAO,IAAI,EAAE,CAAC;QAC1C,MAAM,MAAM,GAAG,wBAAwB,CAAC,KAAK,EAAE,QAAQ,EAAE,GAAG,CAAC,CAAC;QAC9D,IAAI,MAAM,KAAK,IAAI,IAAI,MAAM,KAAK,QAAQ;YAAE,OAAO,IAAI,CAAC;QAExD,UAAU,CAAC,OAAO,GAAG,MAAM,CAAC;QAC5B,eAAe,CAAC,OAAO,GAAG,MAAM,CAAC;QACjC,YAAY,CAAC,MAAM,CAAC,CAAC;QACrB,UAAU,CAAC,MAAM,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAC;QAEvC,MAAM,OAAO,GAAG,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QAC7C,IAAI,OAAO,EAAE,CAAC;YACZ,eAAe,CAAC,OAAO,GAAG,IAAI,CAAC;YAC/B,OAAO,CAAC,KAAK,EAAE,CAAC;QAClB,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC,EACD,CAAC,QAAQ,EAAE,KAAK,EAAE,UAAU,CAAC,CAC9B,CAAC;IAEF,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,SAAS,EAAE,CAAC;AACpE,CAAC","sourcesContent":["import React from 'react';\n\nimport type { MatrixGridSparseIndex } from '../../application/createMatrixGridSparseIndex';\nimport { getMatrixGridNextFocusId } from '../../application/getMatrixGridNextFocusId';\n\n/*** Keep one roving focus target and restore it after GridView virtualizes a sparse cell. */\nexport function useMatrixGridKeyboardFocus(\n index: MatrixGridSparseIndex,\n disabled: boolean,\n visibleIds: readonly string[],\n onNavigate: (targetId: string, originId: string, shiftKey: boolean) => void,\n) {\n const [focusedId, setFocusedId] = React.useState<string | null>(null);\n const focusedRef = React.useRef<string | null>(null);\n const pendingFocusRef = React.useRef<string | null>(null);\n const cellRefs = React.useRef(new Map<string, FocusableCell>());\n const visibleIdSet = React.useMemo(() => new Set(visibleIds), [visibleIds]);\n const tabStopId =\n focusedId !== null && visibleIdSet.has(focusedId)\n ? focusedId\n : visibleIds.find((id) => index.positionById.has(id));\n\n const onFocus = React.useCallback((id: string) => {\n pendingFocusRef.current = null;\n focusedRef.current = id;\n setFocusedId(id);\n }, []);\n\n const registerCell = React.useCallback((id: string, node: FocusableCell | null) => {\n if (node === null) {\n cellRefs.current.delete(id);\n return;\n }\n cellRefs.current.set(id, node);\n if (pendingFocusRef.current === id) {\n pendingFocusRef.current = null;\n node.focus();\n }\n }, []);\n\n const onKeyDown = React.useCallback(\n (id: string, key: string, shiftKey: boolean): boolean => {\n if (disabled || !['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown'].includes(key)) {\n return false;\n }\n const originId = focusedRef.current ?? id;\n const nextId = getMatrixGridNextFocusId(index, originId, key);\n if (nextId === null || nextId === originId) return true;\n\n focusedRef.current = nextId;\n pendingFocusRef.current = nextId;\n setFocusedId(nextId);\n onNavigate(nextId, originId, shiftKey);\n\n const mounted = cellRefs.current.get(nextId);\n if (mounted) {\n pendingFocusRef.current = null;\n mounted.focus();\n }\n return true;\n },\n [disabled, index, onNavigate],\n );\n\n return { focusedId, onFocus, onKeyDown, registerCell, tabStopId };\n}\n\ninterface FocusableCell {\n focus: () => void;\n}\n"]}
1
+ {"version":3,"file":"useMatrixGridKeyboardFocus.js","sourceRoot":"","sources":["../../../../../src/features/matrix-grid/adapters/inbound/useMatrixGridKeyboardFocus.ts"],"names":[],"mappings":"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","sourcesContent":["import React from 'react';\n\nimport type { MatrixGridSparseIndex } from '../../application/createMatrixGridSparseIndex';\nimport { getMatrixGridNextFocusId } from '../../application/getMatrixGridNextFocusId';\n\n/*** Keep one roving focus target and restore it after GridView virtualizes a sparse cell. */\nexport function useMatrixGridKeyboardFocus(\n index: MatrixGridSparseIndex,\n disabled: boolean,\n visibleIds: readonly string[],\n isNavigable: (id: string) => boolean,\n onNavigate: (targetId: string, originId: string, shiftKey: boolean) => void,\n) {\n const [focusedId, setFocusedId] = React.useState<string | null>(null);\n const focusedRef = React.useRef<string | null>(null);\n const pendingFocusRef = React.useRef<string | null>(null);\n const cellRefs = React.useRef(new Map<string, FocusableCell>());\n const visibleIdSet = React.useMemo(() => new Set(visibleIds), [visibleIds]);\n const tabStopId =\n focusedId !== null && visibleIdSet.has(focusedId) && isNavigable(focusedId)\n ? focusedId\n : visibleIds.find((id) => index.positionById.has(id) && isNavigable(id));\n\n React.useEffect(() => {\n if (focusedRef.current !== null && !isNavigable(focusedRef.current)) {\n focusedRef.current = null;\n setFocusedId(null);\n }\n if (pendingFocusRef.current !== null && !isNavigable(pendingFocusRef.current)) {\n pendingFocusRef.current = null;\n }\n }, [isNavigable]);\n\n const onFocus = React.useCallback((id: string) => {\n pendingFocusRef.current = null;\n focusedRef.current = id;\n setFocusedId(id);\n }, []);\n\n const registerCell = React.useCallback((id: string, node: FocusableCell | null) => {\n if (node === null) {\n cellRefs.current.delete(id);\n return;\n }\n cellRefs.current.set(id, node);\n if (pendingFocusRef.current === id) {\n pendingFocusRef.current = null;\n node.focus();\n }\n }, []);\n\n const onKeyDown = React.useCallback(\n (id: string, key: string, shiftKey: boolean): boolean => {\n if (disabled || !['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown'].includes(key)) {\n return false;\n }\n const originId = focusedRef.current ?? id;\n const nextId = resolveNextNavigableId(index, originId, key, isNavigable);\n if (nextId === null || nextId === originId) return true;\n\n focusedRef.current = nextId;\n pendingFocusRef.current = nextId;\n setFocusedId(nextId);\n onNavigate(nextId, originId, shiftKey);\n\n const mounted = cellRefs.current.get(nextId);\n if (mounted) {\n pendingFocusRef.current = null;\n mounted.focus();\n }\n return true;\n },\n [disabled, index, isNavigable, onNavigate],\n );\n\n return { focusedId, onFocus, onKeyDown, registerCell, tabStopId };\n}\n\n/*** Skip protected sparse cells while retaining the nearest deterministic directional navigation. */\nfunction resolveNextNavigableId(\n index: MatrixGridSparseIndex,\n originId: string,\n key: string,\n isNavigable: (id: string) => boolean,\n): string | null {\n let candidate = getMatrixGridNextFocusId(index, originId, key);\n while (candidate !== null && candidate !== originId && !isNavigable(candidate)) {\n const nextCandidate = getMatrixGridNextFocusId(index, candidate, key);\n if (nextCandidate === candidate) return originId;\n candidate = nextCandidate;\n }\n return candidate;\n}\n\ninterface FocusableCell {\n focus: () => void;\n}\n"]}
@@ -2,7 +2,7 @@ import { type SelectionIntent } from '@ankhorage/utility/selection';
2
2
  import type { SelectionMode } from '../../../types/selection';
3
3
  type MatrixGridSelectionIntent = SelectionIntent | 'range';
4
4
  /*** Apply ZORA's canonical selection intent while retaining the configured selection mode. */
5
- export declare function resolveMatrixGridSelection(cells: readonly MatrixGridSelectionCell[], selectedIds: readonly string[], cellId: string, anchorId: string | null, intent: MatrixGridSelectionIntent, mode: SelectionMode): readonly string[];
5
+ export declare function resolveMatrixGridSelection(cells: readonly MatrixGridSelectionCell[], selectedIds: readonly string[], cellId: string, anchorId: string | null, intent: MatrixGridSelectionIntent, mode: SelectionMode, protectedCellIds: ReadonlySet<string>): readonly string[];
6
6
  type MatrixGridSelectionCell = Readonly<{
7
7
  columnIndex: number;
8
8
  id: string;
@@ -1 +1 @@
1
- {"version":3,"file":"resolveMatrixGridSelection.d.ts","sourceRoot":"","sources":["../../../../src/features/matrix-grid/application/resolveMatrixGridSelection.ts"],"names":[],"mappings":"AAAA,OAAO,EAAwB,KAAK,eAAe,EAAE,MAAM,8BAA8B,CAAC;AAE1F,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAE9D,KAAK,yBAAyB,GAAG,eAAe,GAAG,OAAO,CAAC;AAE3D,8FAA8F;AAC9F,wBAAgB,0BAA0B,CACxC,KAAK,EAAE,SAAS,uBAAuB,EAAE,EACzC,WAAW,EAAE,SAAS,MAAM,EAAE,EAC9B,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,GAAG,IAAI,EACvB,MAAM,EAAE,yBAAyB,EACjC,IAAI,EAAE,aAAa,GAClB,SAAS,MAAM,EAAE,CAiBnB;AAED,KAAK,uBAAuB,GAAG,QAAQ,CAAC;IACtC,WAAW,EAAE,MAAM,CAAC;IACpB,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,MAAM,CAAC;CAClB,CAAC,CAAC"}
1
+ {"version":3,"file":"resolveMatrixGridSelection.d.ts","sourceRoot":"","sources":["../../../../src/features/matrix-grid/application/resolveMatrixGridSelection.ts"],"names":[],"mappings":"AAAA,OAAO,EAAwB,KAAK,eAAe,EAAE,MAAM,8BAA8B,CAAC;AAE1F,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAE9D,KAAK,yBAAyB,GAAG,eAAe,GAAG,OAAO,CAAC;AAE3D,8FAA8F;AAC9F,wBAAgB,0BAA0B,CACxC,KAAK,EAAE,SAAS,uBAAuB,EAAE,EACzC,WAAW,EAAE,SAAS,MAAM,EAAE,EAC9B,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,GAAG,IAAI,EACvB,MAAM,EAAE,yBAAyB,EACjC,IAAI,EAAE,aAAa,EACnB,gBAAgB,EAAE,WAAW,CAAC,MAAM,CAAC,GACpC,SAAS,MAAM,EAAE,CAmBnB;AAED,KAAK,uBAAuB,GAAG,QAAQ,CAAC;IACtC,WAAW,EAAE,MAAM,CAAC;IACpB,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,MAAM,CAAC;CAClB,CAAC,CAAC"}
@@ -1,6 +1,8 @@
1
1
  import { applySelectionIntent } from '@ankhorage/utility/selection';
2
2
  /*** Apply ZORA's canonical selection intent while retaining the configured selection mode. */
3
- export function resolveMatrixGridSelection(cells, selectedIds, cellId, anchorId, intent, mode) {
3
+ export function resolveMatrixGridSelection(cells, selectedIds, cellId, anchorId, intent, mode, protectedCellIds) {
4
+ if (protectedCellIds.has(cellId))
5
+ return selectedIds;
4
6
  if (mode === 'single')
5
7
  return applySelectionIntent(selectedIds, cellId, 'replace');
6
8
  if (intent !== 'range')
@@ -10,7 +12,8 @@ export function resolveMatrixGridSelection(cells, selectedIds, cellId, anchorId,
10
12
  if (!anchor || !target)
11
13
  return applySelectionIntent(selectedIds, cellId, 'replace');
12
14
  return cells
13
- .filter((cell) => cell.rowIndex >= Math.min(anchor.rowIndex, target.rowIndex) &&
15
+ .filter((cell) => !protectedCellIds.has(cell.id) &&
16
+ cell.rowIndex >= Math.min(anchor.rowIndex, target.rowIndex) &&
14
17
  cell.rowIndex <= Math.max(anchor.rowIndex, target.rowIndex) &&
15
18
  cell.columnIndex >= Math.min(anchor.columnIndex, target.columnIndex) &&
16
19
  cell.columnIndex <= Math.max(anchor.columnIndex, target.columnIndex))
@@ -1 +1 @@
1
- {"version":3,"file":"resolveMatrixGridSelection.js","sourceRoot":"","sources":["../../../../src/features/matrix-grid/application/resolveMatrixGridSelection.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAwB,MAAM,8BAA8B,CAAC;AAM1F,8FAA8F;AAC9F,MAAM,UAAU,0BAA0B,CACxC,KAAyC,EACzC,WAA8B,EAC9B,MAAc,EACd,QAAuB,EACvB,MAAiC,EACjC,IAAmB;IAEnB,IAAI,IAAI,KAAK,QAAQ;QAAE,OAAO,oBAAoB,CAAC,WAAW,EAAE,MAAM,EAAE,SAAS,CAAC,CAAC;IACnF,IAAI,MAAM,KAAK,OAAO;QAAE,OAAO,oBAAoB,CAAC,WAAW,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;IAEjF,MAAM,MAAM,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,EAAE,KAAK,QAAQ,CAAC,CAAC;IAC1D,MAAM,MAAM,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,EAAE,KAAK,MAAM,CAAC,CAAC;IACxD,IAAI,CAAC,MAAM,IAAI,CAAC,MAAM;QAAE,OAAO,oBAAoB,CAAC,WAAW,EAAE,MAAM,EAAE,SAAS,CAAC,CAAC;IAEpF,OAAO,KAAK;SACT,MAAM,CACL,CAAC,IAAI,EAAE,EAAE,CACP,IAAI,CAAC,QAAQ,IAAI,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,QAAQ,EAAE,MAAM,CAAC,QAAQ,CAAC;QAC3D,IAAI,CAAC,QAAQ,IAAI,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,QAAQ,EAAE,MAAM,CAAC,QAAQ,CAAC;QAC3D,IAAI,CAAC,WAAW,IAAI,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,WAAW,EAAE,MAAM,CAAC,WAAW,CAAC;QACpE,IAAI,CAAC,WAAW,IAAI,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,WAAW,EAAE,MAAM,CAAC,WAAW,CAAC,CACvE;SACA,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;AAC5B,CAAC","sourcesContent":["import { applySelectionIntent, type SelectionIntent } from '@ankhorage/utility/selection';\n\nimport type { SelectionMode } from '../../../types/selection';\n\ntype MatrixGridSelectionIntent = SelectionIntent | 'range';\n\n/*** Apply ZORA's canonical selection intent while retaining the configured selection mode. */\nexport function resolveMatrixGridSelection(\n cells: readonly MatrixGridSelectionCell[],\n selectedIds: readonly string[],\n cellId: string,\n anchorId: string | null,\n intent: MatrixGridSelectionIntent,\n mode: SelectionMode,\n): readonly string[] {\n if (mode === 'single') return applySelectionIntent(selectedIds, cellId, 'replace');\n if (intent !== 'range') return applySelectionIntent(selectedIds, cellId, intent);\n\n const anchor = cells.find((cell) => cell.id === anchorId);\n const target = cells.find((cell) => cell.id === cellId);\n if (!anchor || !target) return applySelectionIntent(selectedIds, cellId, 'replace');\n\n return cells\n .filter(\n (cell) =>\n cell.rowIndex >= Math.min(anchor.rowIndex, target.rowIndex) &&\n cell.rowIndex <= Math.max(anchor.rowIndex, target.rowIndex) &&\n cell.columnIndex >= Math.min(anchor.columnIndex, target.columnIndex) &&\n cell.columnIndex <= Math.max(anchor.columnIndex, target.columnIndex),\n )\n .map((cell) => cell.id);\n}\n\ntype MatrixGridSelectionCell = Readonly<{\n columnIndex: number;\n id: string;\n rowIndex: number;\n}>;\n"]}
1
+ {"version":3,"file":"resolveMatrixGridSelection.js","sourceRoot":"","sources":["../../../../src/features/matrix-grid/application/resolveMatrixGridSelection.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAwB,MAAM,8BAA8B,CAAC;AAM1F,8FAA8F;AAC9F,MAAM,UAAU,0BAA0B,CACxC,KAAyC,EACzC,WAA8B,EAC9B,MAAc,EACd,QAAuB,EACvB,MAAiC,EACjC,IAAmB,EACnB,gBAAqC;IAErC,IAAI,gBAAgB,CAAC,GAAG,CAAC,MAAM,CAAC;QAAE,OAAO,WAAW,CAAC;IACrD,IAAI,IAAI,KAAK,QAAQ;QAAE,OAAO,oBAAoB,CAAC,WAAW,EAAE,MAAM,EAAE,SAAS,CAAC,CAAC;IACnF,IAAI,MAAM,KAAK,OAAO;QAAE,OAAO,oBAAoB,CAAC,WAAW,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;IAEjF,MAAM,MAAM,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,EAAE,KAAK,QAAQ,CAAC,CAAC;IAC1D,MAAM,MAAM,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,EAAE,KAAK,MAAM,CAAC,CAAC;IACxD,IAAI,CAAC,MAAM,IAAI,CAAC,MAAM;QAAE,OAAO,oBAAoB,CAAC,WAAW,EAAE,MAAM,EAAE,SAAS,CAAC,CAAC;IAEpF,OAAO,KAAK;SACT,MAAM,CACL,CAAC,IAAI,EAAE,EAAE,CACP,CAAC,gBAAgB,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;QAC9B,IAAI,CAAC,QAAQ,IAAI,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,QAAQ,EAAE,MAAM,CAAC,QAAQ,CAAC;QAC3D,IAAI,CAAC,QAAQ,IAAI,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,QAAQ,EAAE,MAAM,CAAC,QAAQ,CAAC;QAC3D,IAAI,CAAC,WAAW,IAAI,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,WAAW,EAAE,MAAM,CAAC,WAAW,CAAC;QACpE,IAAI,CAAC,WAAW,IAAI,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,WAAW,EAAE,MAAM,CAAC,WAAW,CAAC,CACvE;SACA,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;AAC5B,CAAC","sourcesContent":["import { applySelectionIntent, type SelectionIntent } from '@ankhorage/utility/selection';\n\nimport type { SelectionMode } from '../../../types/selection';\n\ntype MatrixGridSelectionIntent = SelectionIntent | 'range';\n\n/*** Apply ZORA's canonical selection intent while retaining the configured selection mode. */\nexport function resolveMatrixGridSelection(\n cells: readonly MatrixGridSelectionCell[],\n selectedIds: readonly string[],\n cellId: string,\n anchorId: string | null,\n intent: MatrixGridSelectionIntent,\n mode: SelectionMode,\n protectedCellIds: ReadonlySet<string>,\n): readonly string[] {\n if (protectedCellIds.has(cellId)) return selectedIds;\n if (mode === 'single') return applySelectionIntent(selectedIds, cellId, 'replace');\n if (intent !== 'range') return applySelectionIntent(selectedIds, cellId, intent);\n\n const anchor = cells.find((cell) => cell.id === anchorId);\n const target = cells.find((cell) => cell.id === cellId);\n if (!anchor || !target) return applySelectionIntent(selectedIds, cellId, 'replace');\n\n return cells\n .filter(\n (cell) =>\n !protectedCellIds.has(cell.id) &&\n cell.rowIndex >= Math.min(anchor.rowIndex, target.rowIndex) &&\n cell.rowIndex <= Math.max(anchor.rowIndex, target.rowIndex) &&\n cell.columnIndex >= Math.min(anchor.columnIndex, target.columnIndex) &&\n cell.columnIndex <= Math.max(anchor.columnIndex, target.columnIndex),\n )\n .map((cell) => cell.id);\n}\n\ntype MatrixGridSelectionCell = Readonly<{\n columnIndex: number;\n id: string;\n rowIndex: number;\n}>;\n"]}
@@ -15,6 +15,8 @@ export interface MatrixGridProps extends ZoraBaseProps {
15
15
  readonly overscanPixels?: number;
16
16
  /** Retains focus navigation while suppressing cell-selection mutations. */
17
17
  readonly readOnly?: boolean;
18
+ /** Cells that remain visible but cannot receive focus or change the controlled selection. */
19
+ readonly protectedCellIds?: readonly string[];
18
20
  readonly selectedCellIds?: readonly string[];
19
21
  readonly selectionMode?: SelectionMode;
20
22
  readonly onSelectionChange?: (ids: readonly string[]) => void;
@@ -1 +1 @@
1
- {"version":3,"file":"matrix-grid.d.ts","sourceRoot":"","sources":["../../src/types/matrix-grid.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,cAAc,EACd,uBAAuB,EACvB,gBAAgB,EAChB,YAAY,EACb,MAAM,sBAAsB,CAAC;AAC9B,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,QAAQ,CAAC;AAC5C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAEjD,mGAAmG;AACnG,MAAM,WAAW,eAAgB,SAAQ,aAAa;IACpD,QAAQ,CAAC,MAAM,EAAE,gBAAgB,CAAC;IAClC,QAAQ,CAAC,KAAK,EAAE,SAAS,cAAc,EAAE,CAAC;IAC1C,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,wFAAwF;IACxF,QAAQ,CAAC,QAAQ,CAAC,EAAE,YAAY,CAAC;IACjC,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;IACjC,2EAA2E;IAC3E,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;IAC5B,QAAQ,CAAC,eAAe,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC7C,QAAQ,CAAC,aAAa,CAAC,EAAE,aAAa,CAAC;IACvC,QAAQ,CAAC,iBAAiB,CAAC,EAAE,CAAC,GAAG,EAAE,SAAS,MAAM,EAAE,KAAK,IAAI,CAAC;IAC9D,QAAQ,CAAC,gBAAgB,CAAC,EAAE,CAAC,QAAQ,EAAE,YAAY,KAAK,IAAI,CAAC;IAC7D,QAAQ,CAAC,UAAU,EAAE,CAAC,IAAI,EAAE,uBAAuB,KAAK,KAAK,CAAC,SAAS,CAAC;IACxE,QAAQ,CAAC,kBAAkB,CAAC,EAAE,MAAM,CAAC;CACtC"}
1
+ {"version":3,"file":"matrix-grid.d.ts","sourceRoot":"","sources":["../../src/types/matrix-grid.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,cAAc,EACd,uBAAuB,EACvB,gBAAgB,EAChB,YAAY,EACb,MAAM,sBAAsB,CAAC;AAC9B,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,QAAQ,CAAC;AAC5C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAEjD,mGAAmG;AACnG,MAAM,WAAW,eAAgB,SAAQ,aAAa;IACpD,QAAQ,CAAC,MAAM,EAAE,gBAAgB,CAAC;IAClC,QAAQ,CAAC,KAAK,EAAE,SAAS,cAAc,EAAE,CAAC;IAC1C,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,wFAAwF;IACxF,QAAQ,CAAC,QAAQ,CAAC,EAAE,YAAY,CAAC;IACjC,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;IACjC,2EAA2E;IAC3E,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;IAC5B,6FAA6F;IAC7F,QAAQ,CAAC,gBAAgB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC9C,QAAQ,CAAC,eAAe,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC7C,QAAQ,CAAC,aAAa,CAAC,EAAE,aAAa,CAAC;IACvC,QAAQ,CAAC,iBAAiB,CAAC,EAAE,CAAC,GAAG,EAAE,SAAS,MAAM,EAAE,KAAK,IAAI,CAAC;IAC9D,QAAQ,CAAC,gBAAgB,CAAC,EAAE,CAAC,QAAQ,EAAE,YAAY,KAAK,IAAI,CAAC;IAC7D,QAAQ,CAAC,UAAU,EAAE,CAAC,IAAI,EAAE,uBAAuB,KAAK,KAAK,CAAC,SAAS,CAAC;IACxE,QAAQ,CAAC,kBAAkB,CAAC,EAAE,MAAM,CAAC;CACtC"}
@@ -1 +1 @@
1
- {"version":3,"file":"matrix-grid.js","sourceRoot":"","sources":["../../src/types/matrix-grid.ts"],"names":[],"mappings":"","sourcesContent":["import type {\n GridMatrixCell,\n GridMatrixCellPlacement,\n GridMatrixLayout,\n GridViewport,\n} from '@ankhorage/grid-view';\nimport type React from 'react';\n\nimport type { ZoraBaseProps } from './base';\nimport type { SelectionMode } from './selection';\n\n/** Code-first virtualized sparse-matrix presentation over the published grid-view geometry API. */\nexport interface MatrixGridProps extends ZoraBaseProps {\n readonly layout: GridMatrixLayout;\n readonly cells: readonly GridMatrixCell[];\n readonly width: number;\n readonly height: number;\n /** A supplied viewport is controlled by the caller, including independent axis zoom. */\n readonly viewport?: GridViewport;\n readonly zoomX?: number;\n readonly zoomY?: number;\n readonly overscanPixels?: number;\n /** Retains focus navigation while suppressing cell-selection mutations. */\n readonly readOnly?: boolean;\n readonly selectedCellIds?: readonly string[];\n readonly selectionMode?: SelectionMode;\n readonly onSelectionChange?: (ids: readonly string[]) => void;\n readonly onViewportChange?: (viewport: GridViewport) => void;\n readonly renderCell: (cell: GridMatrixCellPlacement) => React.ReactNode;\n readonly accessibilityLabel?: string;\n}\n"]}
1
+ {"version":3,"file":"matrix-grid.js","sourceRoot":"","sources":["../../src/types/matrix-grid.ts"],"names":[],"mappings":"","sourcesContent":["import type {\n GridMatrixCell,\n GridMatrixCellPlacement,\n GridMatrixLayout,\n GridViewport,\n} from '@ankhorage/grid-view';\nimport type React from 'react';\n\nimport type { ZoraBaseProps } from './base';\nimport type { SelectionMode } from './selection';\n\n/** Code-first virtualized sparse-matrix presentation over the published grid-view geometry API. */\nexport interface MatrixGridProps extends ZoraBaseProps {\n readonly layout: GridMatrixLayout;\n readonly cells: readonly GridMatrixCell[];\n readonly width: number;\n readonly height: number;\n /** A supplied viewport is controlled by the caller, including independent axis zoom. */\n readonly viewport?: GridViewport;\n readonly zoomX?: number;\n readonly zoomY?: number;\n readonly overscanPixels?: number;\n /** Retains focus navigation while suppressing cell-selection mutations. */\n readonly readOnly?: boolean;\n /** Cells that remain visible but cannot receive focus or change the controlled selection. */\n readonly protectedCellIds?: readonly string[];\n readonly selectedCellIds?: readonly string[];\n readonly selectionMode?: SelectionMode;\n readonly onSelectionChange?: (ids: readonly string[]) => void;\n readonly onViewportChange?: (viewport: GridViewport) => void;\n readonly renderCell: (cell: GridMatrixCellPlacement) => React.ReactNode;\n readonly accessibilityLabel?: string;\n}\n"]}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@ankhorage/zora",
3
3
  "type": "module",
4
- "version": "25.8.12",
4
+ "version": "25.8.14",
5
5
  "description": "Opinionated React Native and React Native Web UI kit built on @ankhorage/surface.",
6
6
  "homepage": "https://github.com/ankhorage/zora#readme",
7
7
  "bugs": {
@@ -129,7 +129,7 @@
129
129
  "format": "ankhorage-prettier --write .",
130
130
  "format:check": "ankhorage-prettier --check .",
131
131
  "prepack": "bun run build",
132
- "test": "bun run build && bun test src examples/grid-workspaces/time --isolate",
132
+ "test": "bun run build && bun test src examples/grid-workspaces/time examples/grid-workspaces/plugins --isolate",
133
133
  "typecheck": "bun x tsc --noEmit -p tsconfig.json && bun x tsc --noEmit -p tsconfig.scripts.json",
134
134
  "version-packages": "ankhorage-changeset version"
135
135
  },
@@ -66,7 +66,12 @@ test('keeps a mounted RNW keyboard boundary current across parent rerenders', as
66
66
  const keyboard = host.querySelector<HTMLElement>(
67
67
  '[aria-label="Grid interaction keyboard controls"]',
68
68
  );
69
- if (!keyboard) throw new Error('Missing keyboard interaction proxy');
69
+ const surface = host.querySelector<HTMLElement>('[data-testid="grid-interactions"]');
70
+ if (!keyboard || !surface) throw new Error('Missing web grid interaction boundary');
71
+ expect(keyboard.getAttribute('role')).toBe('group');
72
+ expect(keyboard.getAttribute('tabindex')).toBe('0');
73
+ expect(keyboard.style.display).toBe('flex');
74
+ expect(surface.getAttribute('role')).toBeNull();
70
75
  const resize = new browser.KeyboardEvent('keydown', {
71
76
  altKey: true,
72
77
  bubbles: true,
@@ -60,7 +60,6 @@ export function GridInteractions({
60
60
  <View
61
61
  accessible
62
62
  accessibilityLabel="Grid interaction surface"
63
- accessibilityRole="adjustable"
64
63
  ref={surfaceRef}
65
64
  testID={testID}
66
65
  onResponderGrant={(event) => {
@@ -17,6 +17,7 @@ export function GridInteractionsKeyboardProxy({
17
17
  return (
18
18
  <div
19
19
  aria-label="Grid interaction keyboard controls"
20
+ role="group"
20
21
  onBlur={() => onSpaceKeyChange(false)}
21
22
  onKeyDown={(event) => {
22
23
  if (!enabled) return;
@@ -37,7 +38,7 @@ export function GridInteractionsKeyboardProxy({
37
38
  onKeyUp={(event) => {
38
39
  if (isSpaceKey(event)) onSpaceKeyChange(false);
39
40
  }}
40
- style={{ display: 'contents' }}
41
+ style={keyboardBoundaryStyle}
41
42
  tabIndex={enabled ? 0 : -1}
42
43
  >
43
44
  {children}
@@ -45,6 +46,14 @@ export function GridInteractionsKeyboardProxy({
45
46
  );
46
47
  }
47
48
 
49
+ const keyboardBoundaryStyle = {
50
+ alignSelf: 'stretch',
51
+ display: 'flex',
52
+ flex: 1,
53
+ minHeight: 0,
54
+ minWidth: 0,
55
+ } as const;
56
+
48
57
  interface GridInteractionsKeyboardProxyProps {
49
58
  readonly children: React.ReactNode;
50
59
  readonly enabled: boolean;