@ankhorage/zora 27.1.1 → 27.1.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## 27.1.2
4
+
5
+ ### Patch Changes
6
+
7
+ - 7b77abf: Fit GraphView canvases narrower than the requested padding without clipping rendered nodes.
8
+
3
9
  ## 27.1.1
4
10
 
5
11
  ### 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: v27.1.1](././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: v27.1.2](././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":"createGraphController.d.ts","sourceRoot":"","sources":["../../../../../../src/features/graph-view/adapters/inbound/web-artifact/createGraphController.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEtC,OAAO,KAAK,EACV,mBAAmB,EACnB,mBAAmB,EAEpB,MAAM,iCAAiC,CAAC;AACzC,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,uCAAuC,CAAC;AAGhF,kGAAkG;AAClG,wBAAgB,qBAAqB,CACnC,EAAE,EAAE,IAAI,EACR,aAAa,EAAE;IAAE,OAAO,EAAE,MAAM,CAAA;CAAE,EAClC,WAAW,CAAC,EAAE,CAAC,OAAO,EAAE,mBAAmB,KAAK,IAAI;;qBAkDjC,kBAAkB;IASnC,6EAA6E;;IAI7E,8FAA8F;;IAI9F,6FAA6F;;gBAIjF,OAAO;EAuBtB"}
1
+ {"version":3,"file":"createGraphController.d.ts","sourceRoot":"","sources":["../../../../../../src/features/graph-view/adapters/inbound/web-artifact/createGraphController.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEtC,OAAO,KAAK,EACV,mBAAmB,EACnB,mBAAmB,EAEpB,MAAM,iCAAiC,CAAC;AACzC,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,uCAAuC,CAAC;AAIhF,kGAAkG;AAClG,wBAAgB,qBAAqB,CACnC,EAAE,EAAE,IAAI,EACR,aAAa,EAAE;IAAE,OAAO,EAAE,MAAM,CAAA;CAAE,EAClC,WAAW,CAAC,EAAE,CAAC,OAAO,EAAE,mBAAmB,KAAK,IAAI;;qBAkDjC,kBAAkB;IASnC,6EAA6E;;IAI7E,8FAA8F;;IAI9F,6FAA6F;;gBAIjF,OAAO;EAuBtB"}
@@ -1,4 +1,5 @@
1
1
  import { fitGraphViewport } from './fitGraphViewport';
2
+ import { getGraphFitPadding } from './getGraphFitPadding';
2
3
  /*** Own viewport units and limits alongside the public controller, without exposing Cytoscape. */
3
4
  export function createGraphController(cy, fitPaddingRef, optimizeFit) {
4
5
  const state = {
@@ -189,8 +190,9 @@ function getNodeFitZoom(cy, padding) {
189
190
  if (cy.nodes().empty())
190
191
  return 1;
191
192
  const bounds = cy.nodes().boundingBox();
192
- const width = cy.width() - 2 * padding;
193
- const height = cy.height() - 2 * padding;
193
+ const fitPadding = getGraphFitPadding(cy, padding);
194
+ const width = cy.width() - 2 * fitPadding;
195
+ const height = cy.height() - 2 * fitPadding;
194
196
  if (width <= 0 || height <= 0 || bounds.w <= 0 || bounds.h <= 0)
195
197
  return 1;
196
198
  return Math.min(width / bounds.w, height / bounds.h);
@@ -1 +1 @@
1
- {"version":3,"file":"createGraphController.js","sourceRoot":"","sources":["../../../../../../src/features/graph-view/adapters/inbound/web-artifact/createGraphController.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Core } from 'cytoscape';\n\nimport type {\n GraphViewController,\n GraphViewFitOptions,\n GraphViewMeasurement,\n} from '../../../../../types/graph-view';\nimport type { GraphRuntimeUpdate } from '../../../../../types/graphViewRuntime';\nimport { fitGraphViewport } from './fitGraphViewport';\n\n/*** Own viewport units and limits alongside the public controller, without exposing Cytoscape. */\nexport function createGraphController(\n cy: Core,\n fitPaddingRef: { current: number },\n optimizeFit?: (options: GraphViewFitOptions) => void,\n) {\n const state = {\n scale: 1,\n relative: false,\n min: 0.05,\n max: 2,\n readableSize: 0,\n fitSize: Infinity,\n readableZoom: 0,\n fitZoom: Infinity,\n resizeSnapshot: null as GraphResizeSnapshot | null,\n };\n const controller: GraphViewController = {\n fit(options) {\n if (options?.optimizeSpacing && !options.nodeIds?.length && optimizeFit) {\n optimizeFit(options);\n return;\n }\n fitGraphViewport(\n cy,\n {\n nodeIds: options?.nodeIds,\n padding: options?.padding ?? fitPaddingRef.current,\n },\n state.fitZoom,\n );\n },\n measureNodes(nodeIds) {\n return measureRenderedNodes(cy, nodeIds);\n },\n getViewport() {\n return { pan: cy.pan(), zoom: cy.zoom() / state.scale };\n },\n getZoomRange() {\n return { min: cy.minZoom() / state.scale, max: cy.maxZoom() / state.scale };\n },\n setPan(pan) {\n cy.pan(pan);\n },\n setZoom(zoom) {\n setCenteredZoom(cy, zoom * state.scale);\n },\n zoomBy(factor) {\n setCenteredZoom(cy, cy.zoom() * factor);\n },\n };\n\n return {\n controller,\n configure(input: GraphRuntimeUpdate) {\n state.relative = input.zoomMode === 'fit-relative';\n state.min = input.minZoom ?? 0.05;\n state.max = input.maxZoom ?? 2;\n state.readableSize = positiveSize(input.minReadableLabelSize, 0);\n state.fitSize = positiveSize(input.maxFitLabelSize, Infinity);\n if (!state.relative) state.scale = 1;\n applyZoomLimits(cy, state);\n },\n /*** Return the native automatic-fit ceiling used by spacing optimization. */\n getFitZoomLimit() {\n return state.relative ? state.fitZoom : Math.min(state.fitZoom, state.max);\n },\n /*** Return the native label-readability threshold used before allowing background overlap. */\n getReadableZoomTarget() {\n return state.readableZoom;\n },\n /*** Capture the model point beneath the current viewport center before Cytoscape resizes. */\n beginResize() {\n state.resizeSnapshot = captureResizeSnapshot(cy);\n },\n settle(fit: boolean) {\n const logicalZoom = cy.zoom() / state.scale;\n const fonts = cy\n .nodes()\n .filter((node) => !node.isParent() && Boolean(node.style('label')))\n .map((node) => Number.parseFloat(String(node.style('font-size'))))\n .filter((size) => Number.isFinite(size) && size > 0);\n state.readableZoom = fonts.length\n ? state.readableSize / fonts.reduce((minimum, size) => Math.min(minimum, size), Infinity)\n : 0;\n state.fitZoom = fonts.length\n ? state.fitSize / fonts.reduce((maximum, size) => Math.max(maximum, size), 0)\n : Infinity;\n state.scale = state.relative\n ? Math.min(getNodeFitZoom(cy, fitPaddingRef.current), state.fitZoom)\n : 1;\n applyZoomLimits(cy, state);\n if (fit) {\n state.resizeSnapshot = null;\n controller.fit();\n } else if (settlePreservedViewport(cy, state, logicalZoom)) controller.fit();\n },\n };\n}\n\ninterface GraphResizeSnapshot {\n readonly center: { readonly x: number; readonly y: number };\n readonly nodeBounds: GraphNodeBoundsStatus;\n}\n\ntype GraphNodeBoundsStatus = 'clipped' | 'empty' | 'fit' | 'unusable';\n\n/*** Capture the model-space center and whether the prior viewport was an intact graph fit. */\nfunction captureResizeSnapshot(cy: Core): GraphResizeSnapshot | null {\n const viewport = { width: cy.width(), height: cy.height() };\n const zoom = cy.zoom();\n if (!isUsableViewport(viewport) || !Number.isFinite(zoom) || zoom <= 0) return null;\n const pan = cy.pan();\n const renderedCenter = { x: viewport.width / 2, y: viewport.height / 2 };\n return {\n center: {\n x: (renderedCenter.x - pan.x) / zoom,\n y: (renderedCenter.y - pan.y) / zoom,\n },\n nodeBounds: getNodeBoundsStatus(cy, viewport),\n };\n}\n\n/*** Preserve a manual viewport through resize, fitting only when an intact prior fit becomes invalid. */\nfunction settlePreservedViewport(\n cy: Core,\n state: {\n readonly relative: boolean;\n resizeSnapshot: GraphResizeSnapshot | null;\n readonly scale: number;\n },\n logicalZoom: number,\n): boolean {\n const snapshot = state.resizeSnapshot;\n state.resizeSnapshot = null;\n const zoom = state.relative ? logicalZoom * state.scale : cy.zoom();\n if (snapshot === null) {\n if (state.relative) setCenteredZoom(cy, zoom);\n return false;\n }\n const viewport = { width: cy.width(), height: cy.height() };\n if (!isUsableViewport(viewport)) return false;\n cy.viewport({\n zoom,\n pan: {\n x: viewport.width / 2 - snapshot.center.x * zoom,\n y: viewport.height / 2 - snapshot.center.y * zoom,\n },\n });\n const nodeBounds = getNodeBoundsStatus(cy, viewport);\n return nodeBounds === 'unusable' || (snapshot.nodeBounds === 'fit' && nodeBounds === 'clipped');\n}\n\n/*** Accept only finite non-empty node bounds that are wholly visible in a usable viewport. */\nfunction getNodeBoundsStatus(\n cy: Core,\n viewport: { readonly width: number; readonly height: number },\n): GraphNodeBoundsStatus {\n if (cy.nodes().empty()) return 'empty';\n if (!isUsableViewport(viewport)) return 'unusable';\n const bounds = cy.nodes().renderedBoundingBox();\n if (![bounds.x1, bounds.x2, bounds.y1, bounds.y2].every(Number.isFinite)) return 'unusable';\n const fitsHorizontally = bounds.x1 >= 0 && bounds.x2 <= viewport.width;\n const fitsVertically = bounds.y1 >= 0 && bounds.y2 <= viewport.height;\n return fitsHorizontally && fitsVertically ? 'fit' : 'clipped';\n}\n\n/*** Reject zero, negative, and non-finite renderer dimensions before viewport arithmetic. */\nfunction isUsableViewport(viewport: { readonly width: number; readonly height: number }): boolean {\n return (\n Number.isFinite(viewport.width) &&\n Number.isFinite(viewport.height) &&\n viewport.width > 0 &&\n viewport.height > 0\n );\n}\n\n/*** Measure rendered node hit geometry without exposing paint-only outlines or Cytoscape. */\nfunction measureRenderedNodes(cy: Core, nodeIds: readonly string[]): GraphViewMeasurement | null {\n const viewport = { width: cy.width(), height: cy.height() };\n if (viewport.width <= 0 || viewport.height <= 0 || nodeIds.length === 0) return null;\n\n const nodes = [...new Set(nodeIds)].flatMap((id) => {\n const node = cy.getElementById(id);\n if (!node.isNode()) return [];\n\n return [\n {\n id,\n position: node.renderedPosition(),\n size: { width: node.renderedOuterWidth(), height: node.renderedOuterHeight() },\n },\n ];\n });\n return nodes.length === 0 ? null : { nodes, viewport };\n}\n\n/*** Keep pointer, wheel, and programmatic zoom on the same limits in the engine's native units. */\nfunction applyZoomLimits(\n cy: Core,\n state: {\n readonly min: number;\n readonly max: number;\n readonly scale: number;\n readonly readableZoom: number;\n },\n) {\n const min = state.min * state.scale;\n cy.minZoom(Math.min(cy.minZoom(), min));\n cy.maxZoom(Math.max(min, state.max * state.scale, state.readableZoom));\n cy.minZoom(min);\n}\n\n/*** Ignore invalid optional typography targets at the public configuration boundary. */\nfunction positiveSize(value: number | undefined, fallback: number): number {\n return value !== undefined && Number.isFinite(value) && value > 0 ? value : fallback;\n}\n\n/***\n * Recompute the fit reference only after layout or resize, never on every pan/zoom event.\n * Cache the geometry-derived scale in the viewport owner; labels are included, edges excluded.\n */\nfunction getNodeFitZoom(cy: Core, padding: number): number {\n if (cy.nodes().empty()) return 1;\n const bounds = cy.nodes().boundingBox();\n const width = cy.width() - 2 * padding;\n const height = cy.height() - 2 * padding;\n if (width <= 0 || height <= 0 || bounds.w <= 0 || bounds.h <= 0) return 1;\n return Math.min(width / bounds.w, height / bounds.h);\n}\n\n/*** Zoom around the visible viewport center instead of its top-left corner. */\nfunction setCenteredZoom(cy: Core, zoom: number) {\n cy.zoom({ level: zoom, renderedPosition: { x: cy.width() / 2, y: cy.height() / 2 } });\n}\n"]}
1
+ {"version":3,"file":"createGraphController.js","sourceRoot":"","sources":["../../../../../../src/features/graph-view/adapters/inbound/web-artifact/createGraphController.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Core } from 'cytoscape';\n\nimport type {\n GraphViewController,\n GraphViewFitOptions,\n GraphViewMeasurement,\n} from '../../../../../types/graph-view';\nimport type { GraphRuntimeUpdate } from '../../../../../types/graphViewRuntime';\nimport { fitGraphViewport } from './fitGraphViewport';\nimport { getGraphFitPadding } from './getGraphFitPadding';\n\n/*** Own viewport units and limits alongside the public controller, without exposing Cytoscape. */\nexport function createGraphController(\n cy: Core,\n fitPaddingRef: { current: number },\n optimizeFit?: (options: GraphViewFitOptions) => void,\n) {\n const state = {\n scale: 1,\n relative: false,\n min: 0.05,\n max: 2,\n readableSize: 0,\n fitSize: Infinity,\n readableZoom: 0,\n fitZoom: Infinity,\n resizeSnapshot: null as GraphResizeSnapshot | null,\n };\n const controller: GraphViewController = {\n fit(options) {\n if (options?.optimizeSpacing && !options.nodeIds?.length && optimizeFit) {\n optimizeFit(options);\n return;\n }\n fitGraphViewport(\n cy,\n {\n nodeIds: options?.nodeIds,\n padding: options?.padding ?? fitPaddingRef.current,\n },\n state.fitZoom,\n );\n },\n measureNodes(nodeIds) {\n return measureRenderedNodes(cy, nodeIds);\n },\n getViewport() {\n return { pan: cy.pan(), zoom: cy.zoom() / state.scale };\n },\n getZoomRange() {\n return { min: cy.minZoom() / state.scale, max: cy.maxZoom() / state.scale };\n },\n setPan(pan) {\n cy.pan(pan);\n },\n setZoom(zoom) {\n setCenteredZoom(cy, zoom * state.scale);\n },\n zoomBy(factor) {\n setCenteredZoom(cy, cy.zoom() * factor);\n },\n };\n\n return {\n controller,\n configure(input: GraphRuntimeUpdate) {\n state.relative = input.zoomMode === 'fit-relative';\n state.min = input.minZoom ?? 0.05;\n state.max = input.maxZoom ?? 2;\n state.readableSize = positiveSize(input.minReadableLabelSize, 0);\n state.fitSize = positiveSize(input.maxFitLabelSize, Infinity);\n if (!state.relative) state.scale = 1;\n applyZoomLimits(cy, state);\n },\n /*** Return the native automatic-fit ceiling used by spacing optimization. */\n getFitZoomLimit() {\n return state.relative ? state.fitZoom : Math.min(state.fitZoom, state.max);\n },\n /*** Return the native label-readability threshold used before allowing background overlap. */\n getReadableZoomTarget() {\n return state.readableZoom;\n },\n /*** Capture the model point beneath the current viewport center before Cytoscape resizes. */\n beginResize() {\n state.resizeSnapshot = captureResizeSnapshot(cy);\n },\n settle(fit: boolean) {\n const logicalZoom = cy.zoom() / state.scale;\n const fonts = cy\n .nodes()\n .filter((node) => !node.isParent() && Boolean(node.style('label')))\n .map((node) => Number.parseFloat(String(node.style('font-size'))))\n .filter((size) => Number.isFinite(size) && size > 0);\n state.readableZoom = fonts.length\n ? state.readableSize / fonts.reduce((minimum, size) => Math.min(minimum, size), Infinity)\n : 0;\n state.fitZoom = fonts.length\n ? state.fitSize / fonts.reduce((maximum, size) => Math.max(maximum, size), 0)\n : Infinity;\n state.scale = state.relative\n ? Math.min(getNodeFitZoom(cy, fitPaddingRef.current), state.fitZoom)\n : 1;\n applyZoomLimits(cy, state);\n if (fit) {\n state.resizeSnapshot = null;\n controller.fit();\n } else if (settlePreservedViewport(cy, state, logicalZoom)) controller.fit();\n },\n };\n}\n\ninterface GraphResizeSnapshot {\n readonly center: { readonly x: number; readonly y: number };\n readonly nodeBounds: GraphNodeBoundsStatus;\n}\n\ntype GraphNodeBoundsStatus = 'clipped' | 'empty' | 'fit' | 'unusable';\n\n/*** Capture the model-space center and whether the prior viewport was an intact graph fit. */\nfunction captureResizeSnapshot(cy: Core): GraphResizeSnapshot | null {\n const viewport = { width: cy.width(), height: cy.height() };\n const zoom = cy.zoom();\n if (!isUsableViewport(viewport) || !Number.isFinite(zoom) || zoom <= 0) return null;\n const pan = cy.pan();\n const renderedCenter = { x: viewport.width / 2, y: viewport.height / 2 };\n return {\n center: {\n x: (renderedCenter.x - pan.x) / zoom,\n y: (renderedCenter.y - pan.y) / zoom,\n },\n nodeBounds: getNodeBoundsStatus(cy, viewport),\n };\n}\n\n/*** Preserve a manual viewport through resize, fitting only when an intact prior fit becomes invalid. */\nfunction settlePreservedViewport(\n cy: Core,\n state: {\n readonly relative: boolean;\n resizeSnapshot: GraphResizeSnapshot | null;\n readonly scale: number;\n },\n logicalZoom: number,\n): boolean {\n const snapshot = state.resizeSnapshot;\n state.resizeSnapshot = null;\n const zoom = state.relative ? logicalZoom * state.scale : cy.zoom();\n if (snapshot === null) {\n if (state.relative) setCenteredZoom(cy, zoom);\n return false;\n }\n const viewport = { width: cy.width(), height: cy.height() };\n if (!isUsableViewport(viewport)) return false;\n cy.viewport({\n zoom,\n pan: {\n x: viewport.width / 2 - snapshot.center.x * zoom,\n y: viewport.height / 2 - snapshot.center.y * zoom,\n },\n });\n const nodeBounds = getNodeBoundsStatus(cy, viewport);\n return nodeBounds === 'unusable' || (snapshot.nodeBounds === 'fit' && nodeBounds === 'clipped');\n}\n\n/*** Accept only finite non-empty node bounds that are wholly visible in a usable viewport. */\nfunction getNodeBoundsStatus(\n cy: Core,\n viewport: { readonly width: number; readonly height: number },\n): GraphNodeBoundsStatus {\n if (cy.nodes().empty()) return 'empty';\n if (!isUsableViewport(viewport)) return 'unusable';\n const bounds = cy.nodes().renderedBoundingBox();\n if (![bounds.x1, bounds.x2, bounds.y1, bounds.y2].every(Number.isFinite)) return 'unusable';\n const fitsHorizontally = bounds.x1 >= 0 && bounds.x2 <= viewport.width;\n const fitsVertically = bounds.y1 >= 0 && bounds.y2 <= viewport.height;\n return fitsHorizontally && fitsVertically ? 'fit' : 'clipped';\n}\n\n/*** Reject zero, negative, and non-finite renderer dimensions before viewport arithmetic. */\nfunction isUsableViewport(viewport: { readonly width: number; readonly height: number }): boolean {\n return (\n Number.isFinite(viewport.width) &&\n Number.isFinite(viewport.height) &&\n viewport.width > 0 &&\n viewport.height > 0\n );\n}\n\n/*** Measure rendered node hit geometry without exposing paint-only outlines or Cytoscape. */\nfunction measureRenderedNodes(cy: Core, nodeIds: readonly string[]): GraphViewMeasurement | null {\n const viewport = { width: cy.width(), height: cy.height() };\n if (viewport.width <= 0 || viewport.height <= 0 || nodeIds.length === 0) return null;\n\n const nodes = [...new Set(nodeIds)].flatMap((id) => {\n const node = cy.getElementById(id);\n if (!node.isNode()) return [];\n\n return [\n {\n id,\n position: node.renderedPosition(),\n size: { width: node.renderedOuterWidth(), height: node.renderedOuterHeight() },\n },\n ];\n });\n return nodes.length === 0 ? null : { nodes, viewport };\n}\n\n/*** Keep pointer, wheel, and programmatic zoom on the same limits in the engine's native units. */\nfunction applyZoomLimits(\n cy: Core,\n state: {\n readonly min: number;\n readonly max: number;\n readonly scale: number;\n readonly readableZoom: number;\n },\n) {\n const min = state.min * state.scale;\n cy.minZoom(Math.min(cy.minZoom(), min));\n cy.maxZoom(Math.max(min, state.max * state.scale, state.readableZoom));\n cy.minZoom(min);\n}\n\n/*** Ignore invalid optional typography targets at the public configuration boundary. */\nfunction positiveSize(value: number | undefined, fallback: number): number {\n return value !== undefined && Number.isFinite(value) && value > 0 ? value : fallback;\n}\n\n/***\n * Recompute the fit reference only after layout or resize, never on every pan/zoom event.\n * Cache the geometry-derived scale in the viewport owner; labels are included, edges excluded.\n */\nfunction getNodeFitZoom(cy: Core, padding: number): number {\n if (cy.nodes().empty()) return 1;\n const bounds = cy.nodes().boundingBox();\n const fitPadding = getGraphFitPadding(cy, padding);\n const width = cy.width() - 2 * fitPadding;\n const height = cy.height() - 2 * fitPadding;\n if (width <= 0 || height <= 0 || bounds.w <= 0 || bounds.h <= 0) return 1;\n return Math.min(width / bounds.w, height / bounds.h);\n}\n\n/*** Zoom around the visible viewport center instead of its top-left corner. */\nfunction setCenteredZoom(cy: Core, zoom: number) {\n cy.zoom({ level: zoom, renderedPosition: { x: cy.width() / 2, y: cy.height() / 2 } });\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"fitGraphViewport.d.ts","sourceRoot":"","sources":["../../../../../../src/features/graph-view/adapters/inbound/web-artifact/fitGraphViewport.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEtC,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,iCAAiC,CAAC;AAE3E,2FAA2F;AAC3F,wBAAgB,gBAAgB,CAC9B,EAAE,EAAE,IAAI,EACR,OAAO,GAAE,mBAAwB,EACjC,OAAO,SAAW,GACjB,OAAO,CAWT"}
1
+ {"version":3,"file":"fitGraphViewport.d.ts","sourceRoot":"","sources":["../../../../../../src/features/graph-view/adapters/inbound/web-artifact/fitGraphViewport.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEtC,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,iCAAiC,CAAC;AAG3E,2FAA2F;AAC3F,wBAAgB,gBAAgB,CAC9B,EAAE,EAAE,IAAI,EACR,OAAO,GAAE,mBAAwB,EACjC,OAAO,SAAW,GACjB,OAAO,CAWT"}
@@ -1,3 +1,4 @@
1
+ import { getGraphFitPadding } from './getGraphFitPadding';
1
2
  /*** Fit the requested visible nodes and never include edge geometry in viewport bounds. */
2
3
  export function fitGraphViewport(cy, options = {}, maxZoom = Infinity) {
3
4
  if (cy.destroyed())
@@ -5,7 +6,7 @@ export function fitGraphViewport(cy, options = {}, maxZoom = Infinity) {
5
6
  const nodes = getFitNodes(cy, options.nodeIds);
6
7
  if (nodes.empty())
7
8
  return false;
8
- cy.fit(nodes, options.padding ?? 50);
9
+ cy.fit(nodes, getGraphFitPadding(cy, options.padding ?? 50));
9
10
  if (cy.zoom() > maxZoom) {
10
11
  cy.zoom(maxZoom);
11
12
  cy.center(nodes);
@@ -1 +1 @@
1
- {"version":3,"file":"fitGraphViewport.js","sourceRoot":"","sources":["../../../../../../src/features/graph-view/adapters/inbound/web-artifact/fitGraphViewport.ts"],"names":[],"mappings":"AAIA,2FAA2F;AAC3F,MAAM,UAAU,gBAAgB,CAC9B,EAAQ,EACR,UAA+B,EAAE,EACjC,OAAO,GAAG,QAAQ;IAElB,IAAI,EAAE,CAAC,SAAS,EAAE;QAAE,OAAO,KAAK,CAAC;IACjC,MAAM,KAAK,GAAG,WAAW,CAAC,EAAE,EAAE,OAAO,CAAC,OAAO,CAAC,CAAC;IAC/C,IAAI,KAAK,CAAC,KAAK,EAAE;QAAE,OAAO,KAAK,CAAC;IAEhC,EAAE,CAAC,GAAG,CAAC,KAAK,EAAE,OAAO,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC;IACrC,IAAI,EAAE,CAAC,IAAI,EAAE,GAAG,OAAO,EAAE,CAAC;QACxB,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACjB,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IACnB,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,2FAA2F;AAC3F,SAAS,WAAW,CAAC,EAAQ,EAAE,OAAsC;IACnE,IAAI,OAAO,KAAK,SAAS,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC,KAAK,EAAE,CAAC;IACrE,MAAM,YAAY,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,CAAC;IACtC,OAAO,EAAE,CAAC,KAAK,EAAE,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,YAAY,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC;AAClE,CAAC","sourcesContent":["import type { Core } from 'cytoscape';\n\nimport type { GraphViewFitOptions } from '../../../../../types/graph-view';\n\n/*** Fit the requested visible nodes and never include edge geometry in viewport bounds. */\nexport function fitGraphViewport(\n cy: Core,\n options: GraphViewFitOptions = {},\n maxZoom = Infinity,\n): boolean {\n if (cy.destroyed()) return false;\n const nodes = getFitNodes(cy, options.nodeIds);\n if (nodes.empty()) return false;\n\n cy.fit(nodes, options.padding ?? 50);\n if (cy.zoom() > maxZoom) {\n cy.zoom(maxZoom);\n cy.center(nodes);\n }\n return true;\n}\n\n/*** Resolve an optional node-id target without ever adding edges to the fit collection. */\nfunction getFitNodes(cy: Core, nodeIds: readonly string[] | undefined) {\n if (nodeIds === undefined || nodeIds.length === 0) return cy.nodes();\n const requestedIds = new Set(nodeIds);\n return cy.nodes().filter((node) => requestedIds.has(node.id()));\n}\n"]}
1
+ {"version":3,"file":"fitGraphViewport.js","sourceRoot":"","sources":["../../../../../../src/features/graph-view/adapters/inbound/web-artifact/fitGraphViewport.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAE1D,2FAA2F;AAC3F,MAAM,UAAU,gBAAgB,CAC9B,EAAQ,EACR,UAA+B,EAAE,EACjC,OAAO,GAAG,QAAQ;IAElB,IAAI,EAAE,CAAC,SAAS,EAAE;QAAE,OAAO,KAAK,CAAC;IACjC,MAAM,KAAK,GAAG,WAAW,CAAC,EAAE,EAAE,OAAO,CAAC,OAAO,CAAC,CAAC;IAC/C,IAAI,KAAK,CAAC,KAAK,EAAE;QAAE,OAAO,KAAK,CAAC;IAEhC,EAAE,CAAC,GAAG,CAAC,KAAK,EAAE,kBAAkB,CAAC,EAAE,EAAE,OAAO,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,CAAC;IAC7D,IAAI,EAAE,CAAC,IAAI,EAAE,GAAG,OAAO,EAAE,CAAC;QACxB,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACjB,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IACnB,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,2FAA2F;AAC3F,SAAS,WAAW,CAAC,EAAQ,EAAE,OAAsC;IACnE,IAAI,OAAO,KAAK,SAAS,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC,KAAK,EAAE,CAAC;IACrE,MAAM,YAAY,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,CAAC;IACtC,OAAO,EAAE,CAAC,KAAK,EAAE,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,YAAY,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC;AAClE,CAAC","sourcesContent":["import type { Core } from 'cytoscape';\n\nimport type { GraphViewFitOptions } from '../../../../../types/graph-view';\nimport { getGraphFitPadding } from './getGraphFitPadding';\n\n/*** Fit the requested visible nodes and never include edge geometry in viewport bounds. */\nexport function fitGraphViewport(\n cy: Core,\n options: GraphViewFitOptions = {},\n maxZoom = Infinity,\n): boolean {\n if (cy.destroyed()) return false;\n const nodes = getFitNodes(cy, options.nodeIds);\n if (nodes.empty()) return false;\n\n cy.fit(nodes, getGraphFitPadding(cy, options.padding ?? 50));\n if (cy.zoom() > maxZoom) {\n cy.zoom(maxZoom);\n cy.center(nodes);\n }\n return true;\n}\n\n/*** Resolve an optional node-id target without ever adding edges to the fit collection. */\nfunction getFitNodes(cy: Core, nodeIds: readonly string[] | undefined) {\n if (nodeIds === undefined || nodeIds.length === 0) return cy.nodes();\n const requestedIds = new Set(nodeIds);\n return cy.nodes().filter((node) => requestedIds.has(node.id()));\n}\n"]}
@@ -0,0 +1,4 @@
1
+ import type { Core } from 'cytoscape';
2
+ /*** Limits requested fit padding so every usable canvas retains graph space. */
3
+ export declare function getGraphFitPadding(cy: Core, padding: number): number;
4
+ //# sourceMappingURL=getGraphFitPadding.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"getGraphFitPadding.d.ts","sourceRoot":"","sources":["../../../../../../src/features/graph-view/adapters/inbound/web-artifact/getGraphFitPadding.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEtC,gFAAgF;AAChF,wBAAgB,kBAAkB,CAAC,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAMpE"}
@@ -0,0 +1,9 @@
1
+ /*** Limits requested fit padding so every usable canvas retains graph space. */
2
+ export function getGraphFitPadding(cy, padding) {
3
+ const viewportSize = Math.min(cy.width(), cy.height());
4
+ if (!Number.isFinite(viewportSize) || viewportSize <= 0)
5
+ return 0;
6
+ const requestedPadding = Number.isFinite(padding) ? Math.max(0, padding) : 0;
7
+ return Math.min(requestedPadding, viewportSize / 4);
8
+ }
9
+ //# sourceMappingURL=getGraphFitPadding.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"getGraphFitPadding.js","sourceRoot":"","sources":["../../../../../../src/features/graph-view/adapters/inbound/web-artifact/getGraphFitPadding.ts"],"names":[],"mappings":"AAEA,gFAAgF;AAChF,MAAM,UAAU,kBAAkB,CAAC,EAAQ,EAAE,OAAe;IAC1D,MAAM,YAAY,GAAG,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,KAAK,EAAE,EAAE,EAAE,CAAC,MAAM,EAAE,CAAC,CAAC;IACvD,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,YAAY,CAAC,IAAI,YAAY,IAAI,CAAC;QAAE,OAAO,CAAC,CAAC;IAElE,MAAM,gBAAgB,GAAG,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAC7E,OAAO,IAAI,CAAC,GAAG,CAAC,gBAAgB,EAAE,YAAY,GAAG,CAAC,CAAC,CAAC;AACtD,CAAC","sourcesContent":["import type { Core } from 'cytoscape';\n\n/*** Limits requested fit padding so every usable canvas retains graph space. */\nexport function getGraphFitPadding(cy: Core, padding: number): number {\n const viewportSize = Math.min(cy.width(), cy.height());\n if (!Number.isFinite(viewportSize) || viewportSize <= 0) return 0;\n\n const requestedPadding = Number.isFinite(padding) ? Math.max(0, padding) : 0;\n return Math.min(requestedPadding, viewportSize / 4);\n}\n"]}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@ankhorage/zora",
3
3
  "type": "module",
4
- "version": "27.1.1",
4
+ "version": "27.1.2",
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": {
@@ -7,6 +7,7 @@ import type {
7
7
  } from '../../../../../types/graph-view';
8
8
  import type { GraphRuntimeUpdate } from '../../../../../types/graphViewRuntime';
9
9
  import { fitGraphViewport } from './fitGraphViewport';
10
+ import { getGraphFitPadding } from './getGraphFitPadding';
10
11
 
11
12
  /*** Own viewport units and limits alongside the public controller, without exposing Cytoscape. */
12
13
  export function createGraphController(
@@ -233,8 +234,9 @@ function positiveSize(value: number | undefined, fallback: number): number {
233
234
  function getNodeFitZoom(cy: Core, padding: number): number {
234
235
  if (cy.nodes().empty()) return 1;
235
236
  const bounds = cy.nodes().boundingBox();
236
- const width = cy.width() - 2 * padding;
237
- const height = cy.height() - 2 * padding;
237
+ const fitPadding = getGraphFitPadding(cy, padding);
238
+ const width = cy.width() - 2 * fitPadding;
239
+ const height = cy.height() - 2 * fitPadding;
238
240
  if (width <= 0 || height <= 0 || bounds.w <= 0 || bounds.h <= 0) return 1;
239
241
  return Math.min(width / bounds.w, height / bounds.h);
240
242
  }
@@ -1,6 +1,7 @@
1
1
  import type { Core } from 'cytoscape';
2
2
 
3
3
  import type { GraphViewFitOptions } from '../../../../../types/graph-view';
4
+ import { getGraphFitPadding } from './getGraphFitPadding';
4
5
 
5
6
  /*** Fit the requested visible nodes and never include edge geometry in viewport bounds. */
6
7
  export function fitGraphViewport(
@@ -12,7 +13,7 @@ export function fitGraphViewport(
12
13
  const nodes = getFitNodes(cy, options.nodeIds);
13
14
  if (nodes.empty()) return false;
14
15
 
15
- cy.fit(nodes, options.padding ?? 50);
16
+ cy.fit(nodes, getGraphFitPadding(cy, options.padding ?? 50));
16
17
  if (cy.zoom() > maxZoom) {
17
18
  cy.zoom(maxZoom);
18
19
  cy.center(nodes);
@@ -0,0 +1,32 @@
1
+ import { expect, test } from 'bun:test';
2
+ import cytoscape from 'cytoscape';
3
+
4
+ import { getGraphFitPadding } from './getGraphFitPadding';
5
+
6
+ test('keeps requested padding only while the canvas retains graph space', () => {
7
+ const cy = cytoscape({ headless: true });
8
+ cy.width = () => 800;
9
+ cy.height = () => 600;
10
+ try {
11
+ expect(getGraphFitPadding(cy, 50)).toBe(50);
12
+
13
+ cy.width = () => 38;
14
+ expect(getGraphFitPadding(cy, 50)).toBe(9.5);
15
+ } finally {
16
+ cy.destroy();
17
+ }
18
+ });
19
+
20
+ test('rejects unusable canvas dimensions and negative padding', () => {
21
+ const cy = cytoscape({ headless: true });
22
+ cy.width = () => 0;
23
+ cy.height = () => 520;
24
+ try {
25
+ expect(getGraphFitPadding(cy, 50)).toBe(0);
26
+
27
+ cy.width = () => 800;
28
+ expect(getGraphFitPadding(cy, -50)).toBe(0);
29
+ } finally {
30
+ cy.destroy();
31
+ }
32
+ });
@@ -0,0 +1,10 @@
1
+ import type { Core } from 'cytoscape';
2
+
3
+ /*** Limits requested fit padding so every usable canvas retains graph space. */
4
+ export function getGraphFitPadding(cy: Core, padding: number): number {
5
+ const viewportSize = Math.min(cy.width(), cy.height());
6
+ if (!Number.isFinite(viewportSize) || viewportSize <= 0) return 0;
7
+
8
+ const requestedPadding = Number.isFinite(padding) ? Math.max(0, padding) : 0;
9
+ return Math.min(requestedPadding, viewportSize / 4);
10
+ }
@@ -123099,6 +123099,15 @@ function findExpansionFactor(accepts) {
123099
123099
  return findMinimumAcceptedFactor(maximum / 2, maximum, accepts);
123100
123100
  }
123101
123101
 
123102
+ // src/features/graph-view/adapters/inbound/web-artifact/getGraphFitPadding.ts
123103
+ function getGraphFitPadding(cy, padding) {
123104
+ const viewportSize = Math.min(cy.width(), cy.height());
123105
+ if (!Number.isFinite(viewportSize) || viewportSize <= 0)
123106
+ return 0;
123107
+ const requestedPadding = Number.isFinite(padding) ? Math.max(0, padding) : 0;
123108
+ return Math.min(requestedPadding, viewportSize / 4);
123109
+ }
123110
+
123102
123111
  // src/features/graph-view/adapters/inbound/web-artifact/fitGraphViewport.ts
123103
123112
  function fitGraphViewport(cy, options = {}, maxZoom = Infinity) {
123104
123113
  if (cy.destroyed())
@@ -123106,7 +123115,7 @@ function fitGraphViewport(cy, options = {}, maxZoom = Infinity) {
123106
123115
  const nodes = getFitNodes(cy, options.nodeIds);
123107
123116
  if (nodes.empty())
123108
123117
  return false;
123109
- cy.fit(nodes, options.padding ?? 50);
123118
+ cy.fit(nodes, getGraphFitPadding(cy, options.padding ?? 50));
123110
123119
  if (cy.zoom() > maxZoom) {
123111
123120
  cy.zoom(maxZoom);
123112
123121
  cy.center(nodes);
@@ -123282,8 +123291,9 @@ function getNodeFitZoom(cy, padding) {
123282
123291
  if (cy.nodes().empty())
123283
123292
  return 1;
123284
123293
  const bounds = cy.nodes().boundingBox();
123285
- const width = cy.width() - 2 * padding;
123286
- const height = cy.height() - 2 * padding;
123294
+ const fitPadding = getGraphFitPadding(cy, padding);
123295
+ const width = cy.width() - 2 * fitPadding;
123296
+ const height = cy.height() - 2 * fitPadding;
123287
123297
  if (width <= 0 || height <= 0 || bounds.w <= 0 || bounds.h <= 0)
123288
123298
  return 1;
123289
123299
  return Math.min(width / bounds.w, height / bounds.h);