@ankhorage/zora 27.1.1 → 27.1.3

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 (26) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +1 -1
  3. package/dist/features/graph-view/adapters/inbound/web-artifact/createGraphController.d.ts.map +1 -1
  4. package/dist/features/graph-view/adapters/inbound/web-artifact/createGraphController.js +4 -2
  5. package/dist/features/graph-view/adapters/inbound/web-artifact/createGraphController.js.map +1 -1
  6. package/dist/features/graph-view/adapters/inbound/web-artifact/fitGraphViewport.d.ts.map +1 -1
  7. package/dist/features/graph-view/adapters/inbound/web-artifact/fitGraphViewport.js +2 -1
  8. package/dist/features/graph-view/adapters/inbound/web-artifact/fitGraphViewport.js.map +1 -1
  9. package/dist/features/graph-view/adapters/inbound/web-artifact/getGraphFitPadding.d.ts +4 -0
  10. package/dist/features/graph-view/adapters/inbound/web-artifact/getGraphFitPadding.d.ts.map +1 -0
  11. package/dist/features/graph-view/adapters/inbound/web-artifact/getGraphFitPadding.js +9 -0
  12. package/dist/features/graph-view/adapters/inbound/web-artifact/getGraphFitPadding.js.map +1 -0
  13. package/package.json +2 -2
  14. package/src/features/graph-view/adapters/inbound/web-artifact/createGraphController.ts +4 -2
  15. package/src/features/graph-view/adapters/inbound/web-artifact/fitGraphViewport.ts +2 -1
  16. package/src/features/graph-view/adapters/inbound/web-artifact/getGraphFitPadding.test.ts +32 -0
  17. package/src/features/graph-view/adapters/inbound/web-artifact/getGraphFitPadding.ts +10 -0
  18. package/web-dist/chunks/{Accordion-enn0479f.js → Accordion-bfszt9f0.js} +12 -15
  19. package/web-dist/components/game/Game.js +1 -1
  20. package/web-dist/components/game-entity/GameEntity.js +1 -1
  21. package/web-dist/components/game-field/GameField.js +1 -1
  22. package/web-dist/components/game-input-zone/GameInputZone.js +1 -1
  23. package/web-dist/components/game-measurement-probe/GameMeasurementProbe.js +1 -1
  24. package/web-dist/components/game-overlay/GameOverlay.js +1 -1
  25. package/web-dist/components/graph-view/GraphView.js +13 -3
  26. package/web-dist/manifest.json +6 -6
package/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # Changelog
2
2
 
3
+ ## 27.1.3
4
+
5
+ ### Patch Changes
6
+
7
+ - 22a1773: Update dependencies: `@ankhorage/runtime`.
8
+
9
+ ## 27.1.2
10
+
11
+ ### Patch Changes
12
+
13
+ - 7b77abf: Fit GraphView canvases narrower than the requested padding without clipping rendered nodes.
14
+
3
15
  ## 27.1.1
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: 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.3](././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":"AAQA,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAEtD,kGAAkG;AAClG,MAAM,UAAU,qBAAqB,CACnC,EAAQ,EACR,aAAkC,EAClC,WAAoD;IAEpD,MAAM,KAAK,GAAG;QACZ,KAAK,EAAE,CAAC;QACR,QAAQ,EAAE,KAAK;QACf,GAAG,EAAE,IAAI;QACT,GAAG,EAAE,CAAC;QACN,YAAY,EAAE,CAAC;QACf,OAAO,EAAE,QAAQ;QACjB,YAAY,EAAE,CAAC;QACf,OAAO,EAAE,QAAQ;QACjB,cAAc,EAAE,IAAkC;KACnD,CAAC;IACF,MAAM,UAAU,GAAwB;QACtC,GAAG,CAAC,OAAO;YACT,IAAI,OAAO,EAAE,eAAe,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,MAAM,IAAI,WAAW,EAAE,CAAC;gBACxE,WAAW,CAAC,OAAO,CAAC,CAAC;gBACrB,OAAO;YACT,CAAC;YACD,gBAAgB,CACd,EAAE,EACF;gBACE,OAAO,EAAE,OAAO,EAAE,OAAO;gBACzB,OAAO,EAAE,OAAO,EAAE,OAAO,IAAI,aAAa,CAAC,OAAO;aACnD,EACD,KAAK,CAAC,OAAO,CACd,CAAC;QACJ,CAAC;QACD,YAAY,CAAC,OAAO;YAClB,OAAO,oBAAoB,CAAC,EAAE,EAAE,OAAO,CAAC,CAAC;QAC3C,CAAC;QACD,WAAW;YACT,OAAO,EAAE,GAAG,EAAE,EAAE,CAAC,GAAG,EAAE,EAAE,IAAI,EAAE,EAAE,CAAC,IAAI,EAAE,GAAG,KAAK,CAAC,KAAK,EAAE,CAAC;QAC1D,CAAC;QACD,YAAY;YACV,OAAO,EAAE,GAAG,EAAE,EAAE,CAAC,OAAO,EAAE,GAAG,KAAK,CAAC,KAAK,EAAE,GAAG,EAAE,EAAE,CAAC,OAAO,EAAE,GAAG,KAAK,CAAC,KAAK,EAAE,CAAC;QAC9E,CAAC;QACD,MAAM,CAAC,GAAG;YACR,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACd,CAAC;QACD,OAAO,CAAC,IAAI;YACV,eAAe,CAAC,EAAE,EAAE,IAAI,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC;QAC1C,CAAC;QACD,MAAM,CAAC,MAAM;YACX,eAAe,CAAC,EAAE,EAAE,EAAE,CAAC,IAAI,EAAE,GAAG,MAAM,CAAC,CAAC;QAC1C,CAAC;KACF,CAAC;IAEF,OAAO;QACL,UAAU;QACV,SAAS,CAAC,KAAyB;YACjC,KAAK,CAAC,QAAQ,GAAG,KAAK,CAAC,QAAQ,KAAK,cAAc,CAAC;YACnD,KAAK,CAAC,GAAG,GAAG,KAAK,CAAC,OAAO,IAAI,IAAI,CAAC;YAClC,KAAK,CAAC,GAAG,GAAG,KAAK,CAAC,OAAO,IAAI,CAAC,CAAC;YAC/B,KAAK,CAAC,YAAY,GAAG,YAAY,CAAC,KAAK,CAAC,oBAAoB,EAAE,CAAC,CAAC,CAAC;YACjE,KAAK,CAAC,OAAO,GAAG,YAAY,CAAC,KAAK,CAAC,eAAe,EAAE,QAAQ,CAAC,CAAC;YAC9D,IAAI,CAAC,KAAK,CAAC,QAAQ;gBAAE,KAAK,CAAC,KAAK,GAAG,CAAC,CAAC;YACrC,eAAe,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC;QAC7B,CAAC;QACD,6EAA6E;QAC7E,eAAe;YACb,OAAO,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC;QAC7E,CAAC;QACD,8FAA8F;QAC9F,qBAAqB;YACnB,OAAO,KAAK,CAAC,YAAY,CAAC;QAC5B,CAAC;QACD,6FAA6F;QAC7F,WAAW;YACT,KAAK,CAAC,cAAc,GAAG,qBAAqB,CAAC,EAAE,CAAC,CAAC;QACnD,CAAC;QACD,MAAM,CAAC,GAAY;YACjB,MAAM,WAAW,GAAG,EAAE,CAAC,IAAI,EAAE,GAAG,KAAK,CAAC,KAAK,CAAC;YAC5C,MAAM,KAAK,GAAG,EAAE;iBACb,KAAK,EAAE;iBACP,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC;iBAClE,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,MAAM,CAAC,UAAU,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC;iBACjE,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,IAAI,GAAG,CAAC,CAAC,CAAC;YACvD,KAAK,CAAC,YAAY,GAAG,KAAK,CAAC,MAAM;gBAC/B,CAAC,CAAC,KAAK,CAAC,YAAY,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,OAAO,EAAE,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,IAAI,CAAC,EAAE,QAAQ,CAAC;gBACzF,CAAC,CAAC,CAAC,CAAC;YACN,KAAK,CAAC,OAAO,GAAG,KAAK,CAAC,MAAM;gBAC1B,CAAC,CAAC,KAAK,CAAC,OAAO,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,OAAO,EAAE,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,IAAI,CAAC,EAAE,CAAC,CAAC;gBAC7E,CAAC,CAAC,QAAQ,CAAC;YACb,KAAK,CAAC,KAAK,GAAG,KAAK,CAAC,QAAQ;gBAC1B,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,cAAc,CAAC,EAAE,EAAE,aAAa,CAAC,OAAO,CAAC,EAAE,KAAK,CAAC,OAAO,CAAC;gBACpE,CAAC,CAAC,CAAC,CAAC;YACN,eAAe,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC;YAC3B,IAAI,GAAG,EAAE,CAAC;gBACR,KAAK,CAAC,cAAc,GAAG,IAAI,CAAC;gBAC5B,UAAU,CAAC,GAAG,EAAE,CAAC;YACnB,CAAC;iBAAM,IAAI,uBAAuB,CAAC,EAAE,EAAE,KAAK,EAAE,WAAW,CAAC;gBAAE,UAAU,CAAC,GAAG,EAAE,CAAC;QAC/E,CAAC;KACF,CAAC;AACJ,CAAC;AASD,8FAA8F;AAC9F,SAAS,qBAAqB,CAAC,EAAQ;IACrC,MAAM,QAAQ,GAAG,EAAE,KAAK,EAAE,EAAE,CAAC,KAAK,EAAE,EAAE,MAAM,EAAE,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC;IAC5D,MAAM,IAAI,GAAG,EAAE,CAAC,IAAI,EAAE,CAAC;IACvB,IAAI,CAAC,gBAAgB,CAAC,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC;IACpF,MAAM,GAAG,GAAG,EAAE,CAAC,GAAG,EAAE,CAAC;IACrB,MAAM,cAAc,GAAG,EAAE,CAAC,EAAE,QAAQ,CAAC,KAAK,GAAG,CAAC,EAAE,CAAC,EAAE,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;IACzE,OAAO;QACL,MAAM,EAAE;YACN,CAAC,EAAE,CAAC,cAAc,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,GAAG,IAAI;YACpC,CAAC,EAAE,CAAC,cAAc,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,GAAG,IAAI;SACrC;QACD,UAAU,EAAE,mBAAmB,CAAC,EAAE,EAAE,QAAQ,CAAC;KAC9C,CAAC;AACJ,CAAC;AAED,yGAAyG;AACzG,SAAS,uBAAuB,CAC9B,EAAQ,EACR,KAIC,EACD,WAAmB;IAEnB,MAAM,QAAQ,GAAG,KAAK,CAAC,cAAc,CAAC;IACtC,KAAK,CAAC,cAAc,GAAG,IAAI,CAAC;IAC5B,MAAM,IAAI,GAAG,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,WAAW,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC;IACpE,IAAI,QAAQ,KAAK,IAAI,EAAE,CAAC;QACtB,IAAI,KAAK,CAAC,QAAQ;YAAE,eAAe,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC;QAC9C,OAAO,KAAK,CAAC;IACf,CAAC;IACD,MAAM,QAAQ,GAAG,EAAE,KAAK,EAAE,EAAE,CAAC,KAAK,EAAE,EAAE,MAAM,EAAE,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC;IAC5D,IAAI,CAAC,gBAAgB,CAAC,QAAQ,CAAC;QAAE,OAAO,KAAK,CAAC;IAC9C,EAAE,CAAC,QAAQ,CAAC;QACV,IAAI;QACJ,GAAG,EAAE;YACH,CAAC,EAAE,QAAQ,CAAC,KAAK,GAAG,CAAC,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,GAAG,IAAI;YAChD,CAAC,EAAE,QAAQ,CAAC,MAAM,GAAG,CAAC,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,GAAG,IAAI;SAClD;KACF,CAAC,CAAC;IACH,MAAM,UAAU,GAAG,mBAAmB,CAAC,EAAE,EAAE,QAAQ,CAAC,CAAC;IACrD,OAAO,UAAU,KAAK,UAAU,IAAI,CAAC,QAAQ,CAAC,UAAU,KAAK,KAAK,IAAI,UAAU,KAAK,SAAS,CAAC,CAAC;AAClG,CAAC;AAED,8FAA8F;AAC9F,SAAS,mBAAmB,CAC1B,EAAQ,EACR,QAA6D;IAE7D,IAAI,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,EAAE;QAAE,OAAO,OAAO,CAAC;IACvC,IAAI,CAAC,gBAAgB,CAAC,QAAQ,CAAC;QAAE,OAAO,UAAU,CAAC;IACnD,MAAM,MAAM,GAAG,EAAE,CAAC,KAAK,EAAE,CAAC,mBAAmB,EAAE,CAAC;IAChD,IAAI,CAAC,CAAC,MAAM,CAAC,EAAE,EAAE,MAAM,CAAC,EAAE,EAAE,MAAM,CAAC,EAAE,EAAE,MAAM,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,QAAQ,CAAC;QAAE,OAAO,UAAU,CAAC;IAC5F,MAAM,gBAAgB,GAAG,MAAM,CAAC,EAAE,IAAI,CAAC,IAAI,MAAM,CAAC,EAAE,IAAI,QAAQ,CAAC,KAAK,CAAC;IACvE,MAAM,cAAc,GAAG,MAAM,CAAC,EAAE,IAAI,CAAC,IAAI,MAAM,CAAC,EAAE,IAAI,QAAQ,CAAC,MAAM,CAAC;IACtE,OAAO,gBAAgB,IAAI,cAAc,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC;AAChE,CAAC;AAED,6FAA6F;AAC7F,SAAS,gBAAgB,CAAC,QAA6D;IACrF,OAAO,CACL,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC,KAAK,CAAC;QAC/B,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC,MAAM,CAAC;QAChC,QAAQ,CAAC,KAAK,GAAG,CAAC;QAClB,QAAQ,CAAC,MAAM,GAAG,CAAC,CACpB,CAAC;AACJ,CAAC;AAED,6FAA6F;AAC7F,SAAS,oBAAoB,CAAC,EAAQ,EAAE,OAA0B;IAChE,MAAM,QAAQ,GAAG,EAAE,KAAK,EAAE,EAAE,CAAC,KAAK,EAAE,EAAE,MAAM,EAAE,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC;IAC5D,IAAI,QAAQ,CAAC,KAAK,IAAI,CAAC,IAAI,QAAQ,CAAC,MAAM,IAAI,CAAC,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IAErF,MAAM,KAAK,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,EAAE,EAAE,EAAE;QACjD,MAAM,IAAI,GAAG,EAAE,CAAC,cAAc,CAAC,EAAE,CAAC,CAAC;QACnC,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE;YAAE,OAAO,EAAE,CAAC;QAE9B,OAAO;YACL;gBACE,EAAE;gBACF,QAAQ,EAAE,IAAI,CAAC,gBAAgB,EAAE;gBACjC,IAAI,EAAE,EAAE,KAAK,EAAE,IAAI,CAAC,kBAAkB,EAAE,EAAE,MAAM,EAAE,IAAI,CAAC,mBAAmB,EAAE,EAAE;aAC/E;SACF,CAAC;IACJ,CAAC,CAAC,CAAC;IACH,OAAO,KAAK,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC;AACzD,CAAC;AAED,mGAAmG;AACnG,SAAS,eAAe,CACtB,EAAQ,EACR,KAKC;IAED,MAAM,GAAG,GAAG,KAAK,CAAC,GAAG,GAAG,KAAK,CAAC,KAAK,CAAC;IACpC,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,GAAG,CAAC,CAAC,CAAC;IACxC,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,GAAG,GAAG,KAAK,CAAC,KAAK,EAAE,KAAK,CAAC,YAAY,CAAC,CAAC,CAAC;IACvE,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;AAClB,CAAC;AAED,wFAAwF;AACxF,SAAS,YAAY,CAAC,KAAyB,EAAE,QAAgB;IAC/D,OAAO,KAAK,KAAK,SAAS,IAAI,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC;AACvF,CAAC;AAED;;;GAGG;AACH,SAAS,cAAc,CAAC,EAAQ,EAAE,OAAe;IAC/C,IAAI,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,EAAE;QAAE,OAAO,CAAC,CAAC;IACjC,MAAM,MAAM,GAAG,EAAE,CAAC,KAAK,EAAE,CAAC,WAAW,EAAE,CAAC;IACxC,MAAM,KAAK,GAAG,EAAE,CAAC,KAAK,EAAE,GAAG,CAAC,GAAG,OAAO,CAAC;IACvC,MAAM,MAAM,GAAG,EAAE,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,OAAO,CAAC;IACzC,IAAI,KAAK,IAAI,CAAC,IAAI,MAAM,IAAI,CAAC,IAAI,MAAM,CAAC,CAAC,IAAI,CAAC,IAAI,MAAM,CAAC,CAAC,IAAI,CAAC;QAAE,OAAO,CAAC,CAAC;IAC1E,OAAO,IAAI,CAAC,GAAG,CAAC,KAAK,GAAG,MAAM,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;AACvD,CAAC;AAED,+EAA+E;AAC/E,SAAS,eAAe,CAAC,EAAQ,EAAE,IAAY;IAC7C,EAAE,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,gBAAgB,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC,KAAK,EAAE,GAAG,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,EAAE,CAAC,CAAC;AACxF,CAAC","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.3",
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": {
@@ -47,7 +47,7 @@
47
47
  "@ankhorage/game": "^0.1.3",
48
48
  "@ankhorage/grid-view": "^0.5.0",
49
49
  "@ankhorage/reader": "^0.1.2",
50
- "@ankhorage/runtime": "^5.4.0",
50
+ "@ankhorage/runtime": "^5.4.1",
51
51
  "@ankhorage/surface": "^9.5.3",
52
52
  "@ankhorage/utility": "^1.10.0",
53
53
  "expo-document-picker": "~57.0.3",
@@ -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
+ }
@@ -59,8 +59,8 @@ var styles = import_react_native.StyleSheet.create({
59
59
  }
60
60
  });
61
61
  // node_modules/@ankhorage/runtime/dist/RuntimeRendererConfig.js
62
+ import { jsx as _jsx } from "react/jsx-runtime";
62
63
  import React, { createContext, use } from "react";
63
- import { jsx as jsx2 } from "react/jsx-runtime";
64
64
  var EMPTY_RUNTIME_RENDERER_CONFIG = {};
65
65
  var RuntimeRendererConfigContext = createContext(EMPTY_RUNTIME_RENDERER_CONFIG);
66
66
  function composeRuntimeRendererWrapNode(innerWrapNode, outerWrapNode) {
@@ -115,10 +115,7 @@ function RuntimeRendererConfigProvider(props) {
115
115
  const { value, children } = props;
116
116
  const inheritedConfig = use(RuntimeRendererConfigContext);
117
117
  const mergedConfig = mergeRuntimeRendererConfig(value, inheritedConfig);
118
- return /* @__PURE__ */ jsx2(RuntimeRendererConfigContext, {
119
- value: mergedConfig,
120
- children
121
- });
118
+ return _jsx(RuntimeRendererConfigContext, { value: mergedConfig, children });
122
119
  }
123
120
  function mergeRecordConfig(inherited, local) {
124
121
  if (!inherited && !local)
@@ -946,18 +943,18 @@ function useLatestValue(value) {
946
943
  }
947
944
 
948
945
  // src/features/game/adapters/inbound/Game.tsx
949
- import { jsx as jsx3 } from "react/jsx-runtime";
946
+ import { jsx as jsx2 } from "react/jsx-runtime";
950
947
  var Game2 = withZoraThemeScope(GameInner);
951
948
  function GameInner(props) {
952
949
  const runtime = useGameRuntime(props);
953
950
  const [measurementRegistry] = React5.useState(createGameMeasurementRegistry);
954
951
  const runtimeContext = React5.useMemo(() => ({ ...runtime, ...measurementRegistry, interactionPolicy: props.interactionPolicy }), [measurementRegistry, props.interactionPolicy, runtime]);
955
952
  const runtimeBindingConfig = React5.useMemo(() => ({ bindingContext: createGameBindingContext(runtime.session) }), [runtime.session]);
956
- return /* @__PURE__ */ jsx3(GameRuntimeContext.Provider, {
953
+ return /* @__PURE__ */ jsx2(GameRuntimeContext.Provider, {
957
954
  value: runtimeContext,
958
- children: /* @__PURE__ */ jsx3(RuntimeRendererConfigProvider, {
955
+ children: /* @__PURE__ */ jsx2(RuntimeRendererConfigProvider, {
959
956
  value: runtimeBindingConfig,
960
- children: /* @__PURE__ */ jsx3(GameField2, {
957
+ children: /* @__PURE__ */ jsx2(GameField2, {
961
958
  ...props.aspectRatio === undefined ? {} : { aspectRatio: props.aspectRatio },
962
959
  ...props.minHeight === undefined ? {} : { minHeight: props.minHeight },
963
960
  ...props.fill === undefined ? {} : { fill: props.fill },
@@ -1215,13 +1212,13 @@ function useGameEntityMeasurement(elementRef, measurementId) {
1215
1212
  }
1216
1213
 
1217
1214
  // src/features/game/adapters/inbound/GameEntity.tsx
1218
- import { jsx as jsx4 } from "react/jsx-runtime";
1215
+ import { jsx as jsx3 } from "react/jsx-runtime";
1219
1216
  var GameEntity2 = withZoraThemeScope(GameEntityInner);
1220
1217
  function GameEntityInner(props) {
1221
1218
  const elementRef = React10.useRef(null);
1222
1219
  const animatedStyle = useGameEntityAnimatedStyle(props);
1223
1220
  useGameEntityMeasurement(elementRef, props.measurementId);
1224
- return /* @__PURE__ */ jsx4(import_react_native6.Animated.View, {
1221
+ return /* @__PURE__ */ jsx3(import_react_native6.Animated.View, {
1225
1222
  ref: elementRef,
1226
1223
  accessibilityLabel: props.accessibilityLabel,
1227
1224
  testID: props.testID,
@@ -1355,7 +1352,7 @@ function useGameInputZoneResponder(props) {
1355
1352
  }
1356
1353
 
1357
1354
  // src/features/game/adapters/inbound/GameInputZone.tsx
1358
- import { jsx as jsx5 } from "react/jsx-runtime";
1355
+ import { jsx as jsx4 } from "react/jsx-runtime";
1359
1356
  var GameInputZone2 = withZoraThemeScope(GameInputZoneInner);
1360
1357
  function GameInputZoneInner({
1361
1358
  x = 0,
@@ -1387,7 +1384,7 @@ function GameInputZoneInner({
1387
1384
  enabled: enabled && interactionPolicy !== "passive",
1388
1385
  keyboardBindings
1389
1386
  });
1390
- return /* @__PURE__ */ jsx5(import_react_native7.View, {
1387
+ return /* @__PURE__ */ jsx4(import_react_native7.View, {
1391
1388
  ...keyboardProps,
1392
1389
  ...accessibilityLabel === undefined ? {} : { accessibilityLabel },
1393
1390
  ...testID === undefined ? {} : { testID },
@@ -1514,7 +1511,7 @@ async function dispatchGameMeasurement(input) {
1514
1511
 
1515
1512
  // src/features/game/adapters/inbound/GameOverlay.tsx
1516
1513
  var import_react_native8 = __toESM(require_cjs(), 1);
1517
- import { jsx as jsx6 } from "react/jsx-runtime";
1514
+ import { jsx as jsx5 } from "react/jsx-runtime";
1518
1515
  var GameOverlay2 = withZoraThemeScope(GameOverlayInner);
1519
1516
  function GameOverlayInner({
1520
1517
  children,
@@ -1525,7 +1522,7 @@ function GameOverlayInner({
1525
1522
  interactionPolicy,
1526
1523
  testID
1527
1524
  }) {
1528
- return /* @__PURE__ */ jsx6(View2, {
1525
+ return /* @__PURE__ */ jsx5(View2, {
1529
1526
  ...accessibilityLabel === undefined ? {} : { accessibilityLabel },
1530
1527
  ...testID === undefined ? {} : { testID },
1531
1528
  pointerEvents: interactionPolicy === "passive" ? "none" : blocking ? "auto" : "box-none",
@@ -6,7 +6,7 @@ import {
6
6
  GameInputZone2,
7
7
  GameMeasurementProbe2,
8
8
  GameOverlay2
9
- } from "../../chunks/Accordion-enn0479f.js";
9
+ } from "../../chunks/Accordion-bfszt9f0.js";
10
10
  import"../../chunks/Accordion-6wrr910w.js";
11
11
  import"../../chunks/Accordion-vj1m2wck.js";
12
12
  import"../../chunks/Accordion-zj4wnm3e.js";
@@ -6,7 +6,7 @@ import {
6
6
  GameInputZone2,
7
7
  GameMeasurementProbe2,
8
8
  GameOverlay2
9
- } from "../../chunks/Accordion-enn0479f.js";
9
+ } from "../../chunks/Accordion-bfszt9f0.js";
10
10
  import"../../chunks/Accordion-6wrr910w.js";
11
11
  import"../../chunks/Accordion-vj1m2wck.js";
12
12
  import"../../chunks/Accordion-zj4wnm3e.js";
@@ -6,7 +6,7 @@ import {
6
6
  GameInputZone2,
7
7
  GameMeasurementProbe2,
8
8
  GameOverlay2
9
- } from "../../chunks/Accordion-enn0479f.js";
9
+ } from "../../chunks/Accordion-bfszt9f0.js";
10
10
  import"../../chunks/Accordion-6wrr910w.js";
11
11
  import"../../chunks/Accordion-vj1m2wck.js";
12
12
  import"../../chunks/Accordion-zj4wnm3e.js";
@@ -6,7 +6,7 @@ import {
6
6
  GameInputZone2,
7
7
  GameMeasurementProbe2,
8
8
  GameOverlay2
9
- } from "../../chunks/Accordion-enn0479f.js";
9
+ } from "../../chunks/Accordion-bfszt9f0.js";
10
10
  import"../../chunks/Accordion-6wrr910w.js";
11
11
  import"../../chunks/Accordion-vj1m2wck.js";
12
12
  import"../../chunks/Accordion-zj4wnm3e.js";
@@ -6,7 +6,7 @@ import {
6
6
  GameInputZone2,
7
7
  GameMeasurementProbe2,
8
8
  GameOverlay2
9
- } from "../../chunks/Accordion-enn0479f.js";
9
+ } from "../../chunks/Accordion-bfszt9f0.js";
10
10
  import"../../chunks/Accordion-6wrr910w.js";
11
11
  import"../../chunks/Accordion-vj1m2wck.js";
12
12
  import"../../chunks/Accordion-zj4wnm3e.js";
@@ -6,7 +6,7 @@ import {
6
6
  GameInputZone2,
7
7
  GameMeasurementProbe2,
8
8
  GameOverlay2
9
- } from "../../chunks/Accordion-enn0479f.js";
9
+ } from "../../chunks/Accordion-bfszt9f0.js";
10
10
  import"../../chunks/Accordion-6wrr910w.js";
11
11
  import"../../chunks/Accordion-vj1m2wck.js";
12
12
  import"../../chunks/Accordion-zj4wnm3e.js";
@@ -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);
@@ -1516,7 +1516,7 @@
1516
1516
  "chunks/Accordion-0x8jncra.js",
1517
1517
  "chunks/Accordion-58yzdsab.js",
1518
1518
  "chunks/Accordion-6wrr910w.js",
1519
- "chunks/Accordion-enn0479f.js",
1519
+ "chunks/Accordion-bfszt9f0.js",
1520
1520
  "chunks/Accordion-fcczvwgg.js",
1521
1521
  "chunks/Accordion-fre84yaq.js",
1522
1522
  "chunks/Accordion-nh0cs9hq.js",
@@ -1539,7 +1539,7 @@
1539
1539
  "chunks/Accordion-0x8jncra.js",
1540
1540
  "chunks/Accordion-58yzdsab.js",
1541
1541
  "chunks/Accordion-6wrr910w.js",
1542
- "chunks/Accordion-enn0479f.js",
1542
+ "chunks/Accordion-bfszt9f0.js",
1543
1543
  "chunks/Accordion-fcczvwgg.js",
1544
1544
  "chunks/Accordion-fre84yaq.js",
1545
1545
  "chunks/Accordion-nh0cs9hq.js",
@@ -1562,7 +1562,7 @@
1562
1562
  "chunks/Accordion-0x8jncra.js",
1563
1563
  "chunks/Accordion-58yzdsab.js",
1564
1564
  "chunks/Accordion-6wrr910w.js",
1565
- "chunks/Accordion-enn0479f.js",
1565
+ "chunks/Accordion-bfszt9f0.js",
1566
1566
  "chunks/Accordion-fcczvwgg.js",
1567
1567
  "chunks/Accordion-fre84yaq.js",
1568
1568
  "chunks/Accordion-nh0cs9hq.js",
@@ -1585,7 +1585,7 @@
1585
1585
  "chunks/Accordion-0x8jncra.js",
1586
1586
  "chunks/Accordion-58yzdsab.js",
1587
1587
  "chunks/Accordion-6wrr910w.js",
1588
- "chunks/Accordion-enn0479f.js",
1588
+ "chunks/Accordion-bfszt9f0.js",
1589
1589
  "chunks/Accordion-fcczvwgg.js",
1590
1590
  "chunks/Accordion-fre84yaq.js",
1591
1591
  "chunks/Accordion-nh0cs9hq.js",
@@ -1608,7 +1608,7 @@
1608
1608
  "chunks/Accordion-0x8jncra.js",
1609
1609
  "chunks/Accordion-58yzdsab.js",
1610
1610
  "chunks/Accordion-6wrr910w.js",
1611
- "chunks/Accordion-enn0479f.js",
1611
+ "chunks/Accordion-bfszt9f0.js",
1612
1612
  "chunks/Accordion-fcczvwgg.js",
1613
1613
  "chunks/Accordion-fre84yaq.js",
1614
1614
  "chunks/Accordion-nh0cs9hq.js",
@@ -1631,7 +1631,7 @@
1631
1631
  "chunks/Accordion-0x8jncra.js",
1632
1632
  "chunks/Accordion-58yzdsab.js",
1633
1633
  "chunks/Accordion-6wrr910w.js",
1634
- "chunks/Accordion-enn0479f.js",
1634
+ "chunks/Accordion-bfszt9f0.js",
1635
1635
  "chunks/Accordion-fcczvwgg.js",
1636
1636
  "chunks/Accordion-fre84yaq.js",
1637
1637
  "chunks/Accordion-nh0cs9hq.js",