@weasel-js/labkit 1.4.1 → 1.4.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.
- package/README.md +28 -2
- package/dist/_dts/{CanvasStackContext-BIRUVVGH.d.ts → CanvasStackContext-Dde8_1my.d.ts} +1 -1
- package/dist/_dts/{PrefsForm.d-CgxUequc.d.ts → PrefsForm.d-DDHFANkc.d.ts} +1 -1
- package/dist/_dts/{frac-X7mWgd7y.d.ts → frac-B5mB8LsJ.d.ts} +33 -13
- package/dist/_dts/{index-FEa1MudK.d.ts → index-DelFfeV0.d.ts} +99 -31
- package/dist/_dts/types-B9_zrHmb.d.ts +166 -0
- package/dist/_dts/{types-ChVJJHvk.d.ts → types-BP2OCcpg.d.ts} +41 -4
- package/dist/_dts/types-x92Kfeme.d.ts +62 -0
- package/dist/_dts/{useTrialState-Cdm13fvl.d.ts → useTrialState-D6Vb3T-g.d.ts} +11 -7
- package/dist/canvas/index.d.ts +9 -8
- package/dist/canvas/index.js +1 -2
- package/dist/chrome/index.d.ts +16 -9
- package/dist/chrome/index.js +5 -5
- package/dist/{chunk-MSR7EVS6.js → chunk-2RQPGOQF.js} +6 -5
- package/dist/chunk-2RQPGOQF.js.map +1 -0
- package/dist/{chunk-LJSIUFYD.js → chunk-5TROQVQ2.js} +46 -21
- package/dist/chunk-5TROQVQ2.js.map +1 -0
- package/dist/{chunk-53AZ2O2N.js → chunk-ASWKLRKJ.js} +92 -45
- package/dist/chunk-ASWKLRKJ.js.map +1 -0
- package/dist/{chunk-BMQWLKOL.js → chunk-C42T6DJT.js} +94 -83
- package/dist/chunk-C42T6DJT.js.map +1 -0
- package/dist/chunk-E44SU6XS.js +50 -0
- package/dist/chunk-E44SU6XS.js.map +1 -0
- package/dist/{chunk-OFCBEZMU.js → chunk-FQMJUVHJ.js} +3 -3
- package/dist/{chunk-OFCBEZMU.js.map → chunk-FQMJUVHJ.js.map} +1 -1
- package/dist/{chunk-PP7ZWAMV.js → chunk-I6JCVE24.js} +3 -3
- package/dist/chunk-I6JCVE24.js.map +1 -0
- package/dist/chunk-ISCOFAUO.js +170 -0
- package/dist/chunk-ISCOFAUO.js.map +1 -0
- package/dist/chunk-MHAGC6VD.js +7320 -0
- package/dist/chunk-MHAGC6VD.js.map +1 -0
- package/dist/{chunk-7TRFMIWX.js → chunk-QAGAHJKZ.js} +4 -4
- package/dist/{chunk-7TRFMIWX.js.map → chunk-QAGAHJKZ.js.map} +1 -1
- package/dist/{chunk-D7QIPGEB.js → chunk-RS3HRQWU.js} +62 -60
- package/dist/chunk-RS3HRQWU.js.map +1 -0
- package/dist/{chunk-RLOXQZW3.js → chunk-TJ7QY3OC.js} +3 -3
- package/dist/{chunk-RLOXQZW3.js.map → chunk-TJ7QY3OC.js.map} +1 -1
- package/dist/controls/index.d.ts +5 -4
- package/dist/controls/index.js +3 -3
- package/dist/dragdrop/index.d.ts +9 -8
- package/dist/dragdrop/index.js +1 -2
- package/dist/index.d.ts +152 -57
- package/dist/index.js +315 -200
- package/dist/index.js.map +1 -1
- package/dist/layers/index.d.ts +27 -11
- package/dist/layers/index.js +2 -3
- package/dist/loupe/index.d.ts +8 -7
- package/dist/loupe/index.js +1 -2
- package/dist/passthrough/weasel-canvas.d.ts +1 -3
- package/dist/passthrough/weasel-canvas.js +1 -1
- package/dist/passthrough/weasel-canvas.js.map +1 -1
- package/dist/passthrough/weasel-ui.d.ts +171 -22
- package/dist/passthrough/weasel-ui.js +1 -2
- package/dist/primitives/index.js +3 -4
- package/dist/state/index.d.ts +6 -3
- package/dist/state/index.js +3 -2
- package/dist/state/index.js.map +1 -1
- package/dist/styles.css +21 -4
- package/dist/surface/index.js +1 -2
- package/dist/ui/layers/index.d.ts +22 -9
- package/dist/ui/layers/index.js +1 -2
- package/dist/undo/index.d.ts +6 -6
- package/package.json +7 -7
- package/src/annotations/AnnotationTargets.tsx +6 -1
- package/src/annotations/Annotations.overlay.test.tsx +2 -0
- package/src/annotations/ExportMenu.test.tsx +16 -0
- package/src/annotations/ExportMenu.tsx +0 -5
- package/src/annotations/types.ts +4 -1
- package/src/canvas/useOrbit.test.ts +7 -3
- package/src/canvas/usePanZoom.test.ts +7 -3
- package/src/chrome/ChromeRegions.stories.tsx +42 -1
- package/src/chrome/builtins.test.ts +4 -0
- package/src/chrome/builtins.tsx +18 -0
- package/src/chrome/index.ts +1 -1
- package/src/chrome/regions/SidebarRegion.tsx +7 -6
- package/src/chrome/regions/TitleBarRegion.test.tsx +64 -0
- package/src/chrome/regions/TitleBarRegion.tsx +16 -7
- package/src/chrome/regions/ToolbarRegion.test.tsx +14 -0
- package/src/chrome/regions/ToolbarRegion.tsx +2 -2
- package/src/chrome/regions/ViewportRegion.tsx +2 -2
- package/src/chrome/regions/regions.test.tsx +45 -1
- package/src/chrome/types.ts +21 -3
- package/src/config/builder.test.ts +56 -2
- package/src/config/builder.ts +73 -13
- package/src/config/index.ts +16 -1
- package/src/config/path.test.ts +109 -0
- package/src/config/path.ts +76 -0
- package/src/config/resolve.test.ts +93 -2
- package/src/config/resolve.ts +72 -29
- package/src/config/types.ts +99 -9
- package/src/config/visible.ts +6 -4
- package/src/controls/ControlPanel.stories.tsx +38 -4
- package/src/controls/ControlPanel.test.tsx +152 -0
- package/src/controls/ControlPanel.tsx +134 -40
- package/src/dragdrop/DragDropRuntime.tsx +74 -61
- package/src/dragdrop/Palette.tsx +4 -3
- package/src/dragdrop/dragDrop.test.tsx +35 -12
- package/src/index.ts +18 -2
- package/src/instrument/serializers.ts +36 -0
- package/src/instrument/types.ts +8 -7
- package/src/lab/Lab.test.tsx +36 -1
- package/src/lab/Lab.tsx +12 -3
- package/src/lab/LabContext.ts +5 -1
- package/src/lab/LabShell.less +17 -2
- package/src/lab/LabShell.test.tsx +8 -0
- package/src/lab/LabShell.tsx +3 -1
- package/src/layers/AGENTS.md +29 -7
- package/src/layers/LayerList.less +16 -0
- package/src/layers/LayerList.stories.tsx +66 -0
- package/src/layers/LayerList.test.tsx +117 -5
- package/src/layers/LayerList.tsx +192 -53
- package/src/layers/index.ts +1 -1
- package/src/passthrough/weasel-ui.ts +5 -0
- package/src/primitives/FloatingPanel.test.tsx +8 -3
- package/src/primitives/ZoomControl.less +6 -2
- package/src/primitives/ZoomControl.tsx +1 -0
- package/src/state/helpers.ts +1 -1
- package/src/state/store.test.ts +233 -1
- package/src/state/store.ts +57 -24
- package/src/state/types.ts +44 -3
- package/src/state/useTrialState.ts +1 -1
- package/src/trial/Trial.config.test.tsx +44 -0
- package/src/trial/Trial.less +4 -0
- package/src/trial/Trial.targets.test.tsx +96 -0
- package/src/trial/Trial.test.tsx +171 -14
- package/src/trial/Trial.tsx +7 -4
- package/src/trial/TrialChrome.tsx +32 -7
- package/src/trial/TrialTitleBar.tsx +7 -3
- package/src/trial/index.ts +1 -0
- package/src/trial/trialOps.test.ts +49 -1
- package/src/trial/trialOps.ts +24 -6
- package/dist/_dts/types-lg4TSCb2.d.ts +0 -164
- package/dist/_dts/weasel-canvas-FJTFi3ZZ.d.ts +0 -5041
- package/dist/chunk-53AZ2O2N.js.map +0 -1
- package/dist/chunk-BMQWLKOL.js.map +0 -1
- package/dist/chunk-D7QIPGEB.js.map +0 -1
- package/dist/chunk-DG2IFMLA.js +0 -29970
- package/dist/chunk-DG2IFMLA.js.map +0 -1
- package/dist/chunk-J6SBFZYT.js +0 -7121
- package/dist/chunk-J6SBFZYT.js.map +0 -1
- package/dist/chunk-LJSIUFYD.js.map +0 -1
- package/dist/chunk-MIT3ISW4.js +0 -100
- package/dist/chunk-MIT3ISW4.js.map +0 -1
- package/dist/chunk-MSR7EVS6.js.map +0 -1
- package/dist/chunk-PP7ZWAMV.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/surface/composeRects.ts","../src/surface/deviceRect.ts","../src/surface/SurfaceContext.ts","../src/surface/useSurfaceTile.ts","../src/surface/useTiledSurface.ts"],"names":["useCallback"],"mappings":";;;;;AAGO,SAAS,YAAA,CAAa,WAAgB,KAAA,EAAoD;AAC/F,EAAA,MAAM,GAAA,uBAAU,GAAA,EAAkB;AAClC,EAAA,KAAA,MAAW,CAAC,EAAA,EAAI,IAAI,CAAA,IAAK,KAAA,EAAO;AAC9B,IAAA,GAAA,CAAI,IAAI,EAAA,EAAI;AAAA,MACV,CAAA,EAAG,IAAA,CAAK,IAAA,GAAO,SAAA,CAAU,IAAA;AAAA,MACzB,CAAA,EAAG,IAAA,CAAK,GAAA,GAAM,SAAA,CAAU,GAAA;AAAA,MACxB,GAAG,IAAA,CAAK,KAAA;AAAA,MACR,GAAG,IAAA,CAAK;AAAA,KACT,CAAA;AAAA,EACH;AACA,EAAA,OAAO,GAAA;AACT;AAEO,SAAS,UAAA,CAAW,GAAqB,CAAA,EAAkB;AAChE,EAAA,OAAO,MAAM,MAAA,IAAa,CAAA,CAAE,CAAA,KAAM,CAAA,CAAE,KAAK,CAAA,CAAE,CAAA,KAAM,CAAA,CAAE,CAAA,IAAK,EAAE,CAAA,KAAM,CAAA,CAAE,CAAA,IAAK,CAAA,CAAE,MAAM,CAAA,CAAE,CAAA;AACnF;;;ACVO,SAAS,YAAA,CAAa,IAAA,EAAY,aAAA,EAAuB,GAAA,EAAmB;AACjF,EAAA,MAAM,OAAO,CAAC,CAAA,KAAc,KAAK,KAAA,CAAM,CAAA,GAAI,GAAG,CAAA,GAAI,GAAA;AAClD,EAAA,MAAM,CAAA,GAAI,IAAA,CAAK,IAAA,CAAK,CAAC,CAAA;AACrB,EAAA,MAAM,IAAI,IAAA,CAAK,aAAA,GAAgB,IAAA,CAAK,CAAA,GAAI,KAAK,CAAC,CAAA;AAC9C,EAAA,OAAO;AAAA,IACL,CAAA;AAAA,IACA,CAAA;AAAA,IACA,GAAG,IAAA,CAAK,IAAA,CAAK,CAAA,GAAI,IAAA,CAAK,CAAC,CAAA,GAAI,CAAA;AAAA,IAC3B,CAAA,EAAG,IAAA,CAAK,aAAA,GAAgB,IAAA,CAAK,CAAC,CAAA,GAAI;AAAA,GACpC;AACF;ACdO,IAAM,cAAA,GAAiB,cAAoC,IAAI;AAI/D,IAAM,oBAAA,GAAuB,cAAwC,IAAI;ACDzE,SAAS,gBAAA,GAA6C;AAC3D,EAAA,OAAO,WAAW,oBAAoB,CAAA;AACxC;AAGO,SAAS,kBAAA,GAA2C;AACzD,EAAA,OAAO,WAAW,cAAc,CAAA;AAClC;AAGO,SAAS,UAAA,GAA4B;AAC1C,EAAA,MAAM,OAAA,GAAU,WAAW,cAAc,CAAA;AACzC,EAAA,IAAI,CAAC,OAAA,EAAS,MAAM,IAAI,MAAM,uDAAuD,CAAA;AACrF,EAAA,OAAO,OAAA;AACT;AAWO,SAAS,UAAU,EAAA,EAAoB;AAC5C,EAAA,MAAM,KAAA,GAAQ,WAAW,cAAc,CAAA;AACvC,EAAA,OAAO,KAAA,GAAQ,CAAA,EAAG,KAAK,CAAA,CAAA,EAAI,EAAE,CAAA,CAAA,GAAK,EAAA;AACpC;AAWO,SAAS,eAAe,EAAA,EAA8C;AAC3E,EAAA,MAAM,UAAU,kBAAA,EAAmB;AACnC,EAAA,MAAM,MAAA,GAAS,UAAU,EAAE,CAAA;AAC3B,EAAA,OAAO,WAAA;AAAA,IACL,CAAC,EAAA,KAA2B;AAC1B,MAAA,OAAA,EAAS,YAAA,CAAa,QAAQ,EAAE,CAAA;AAAA,IAClC,CAAA;AAAA,IACA,CAAC,SAAS,MAAM;AAAA,GAClB;AACF;ACTO,SAAS,eAAA,CAAgB,EAAE,OAAA,EAAQ,EAA0C;AAClF,EAAA,MAAM,SAAA,GAAY,OAA2B,IAAI,CAAA;AACjD,EAAA,MAAM,KAAA,GAAQ,MAAA,iBAAO,IAAI,GAAA,EAA0B,CAAA;AACnD,EAAA,MAAM,KAAA,GAAQ,MAAA,iBAAO,IAAI,GAAA,EAAmB,CAAA;AAC5C,EAAA,MAAM,KAAA,GAAQ,MAAA,iBAAO,IAAI,GAAA,EAAa,CAAA;AACtC,EAAA,MAAM,QAAA,GAAW,MAAA,iBAAO,IAAI,GAAA,EAA0B,CAAA;AACtD,EAAA,MAAM,YAAA,GAAe,OAAO,IAAI,CAAA;AAChC,EAAA,MAAM,QAAA,GAAW,OAA8B,IAAI,CAAA;AACnD,EAAA,MAAM,OAAA,GAAU,OAAO,CAAC,CAAA;AAIxB,EAAA,MAAM,UAAA,GAAa,OAAO,OAAO,CAAA;AACjC,EAAA,UAAA,CAAW,OAAA,GAAU,OAAA;AAErB,EAAA,MAAM,OAAA,GAAUA,YAAY,MAAe;AACzC,IAAA,MAAM,KAAK,SAAA,CAAU,OAAA;AACrB,IAAA,IAAI,CAAC,IAAI,OAAO,KAAA;AAChB,IAAA,MAAM,KAAA,uBAAY,GAAA,EAAiB;AACnC,IAAA,KAAA,MAAW,CAAC,EAAA,EAAI,IAAI,CAAA,IAAK,KAAA,CAAM,OAAA,EAAS,KAAA,CAAM,GAAA,CAAI,EAAA,EAAI,IAAA,CAAK,qBAAA,EAAuB,CAAA;AAClF,IAAA,MAAM,IAAA,GAAO,YAAA,CAAa,EAAA,CAAG,qBAAA,IAAyB,KAAK,CAAA;AAC3D,IAAA,IAAI,OAAA,GAAU,IAAA,CAAK,IAAA,KAAS,KAAA,CAAM,OAAA,CAAQ,IAAA;AAC1C,IAAA,KAAA,MAAW,CAAC,EAAA,EAAI,IAAI,CAAA,IAAK,IAAA,EAAM;AAC7B,MAAA,IAAI,CAAC,WAAW,KAAA,CAAM,OAAA,CAAQ,IAAI,EAAE,CAAA,EAAG,IAAI,CAAA,EAAG,OAAA,GAAU,IAAA;AAAA,IAC1D;AACA,IAAA,KAAA,CAAM,OAAA,GAAU,IAAA;AAChB,IAAA,OAAO,OAAA;AAAA,EACT,CAAA,EAAG,EAAE,CAAA;AAEL,EAAA,MAAM,SAAA,GAAY,aAAA;AAAA,IAChB,MAAM;AACJ,MAAA,MAAM,KAAK,SAAA,CAAU,OAAA;AACrB,MAAA,IAAI,CAAC,EAAA,EAAI;AACP,QAAA,KAAA,CAAM,QAAQ,KAAA,EAAM;AACpB,QAAA;AAAA,MACF;AACA,MAAA,IAAI,aAAa,OAAA,EAAS;AACxB,QAAA,YAAA,CAAa,OAAA,GAAU,KAAA;AACvB,QAAA,IAAI,OAAA,EAAQ,EAAG,KAAA,MAAW,EAAA,IAAM,KAAA,CAAM,OAAA,CAAQ,IAAA,EAAK,EAAG,KAAA,CAAM,OAAA,CAAQ,GAAA,CAAI,EAAE,CAAA;AAAA,MAC5E;AACA,MAAA,MAAM,GAAA,GAAM,WAAW,gBAAA,IAAoB,CAAA;AAC3C,MAAA,IAAI,GAAA,KAAQ,QAAQ,OAAA,EAAS;AAC3B,QAAA,OAAA,CAAQ,OAAA,GAAU,GAAA;AAClB,QAAA,KAAA,MAAW,EAAA,IAAM,MAAM,OAAA,CAAQ,IAAA,IAAQ,KAAA,CAAM,OAAA,CAAQ,IAAI,EAAE,CAAA;AAAA,MAC7D;AACA,MAAA,IAAI,KAAA,CAAM,OAAA,CAAQ,IAAA,KAAS,CAAA,EAAG;AAC9B,MAAA,MAAM,GAAA,GAAM,GAAG,qBAAA,EAAsB;AACrC,MAAA,MAAM,KAAA,GAAsB;AAAA,QAC1B,KAAA,EAAO,IAAI,GAAA,CAAI,KAAA,CAAM,OAAO,CAAA;AAAA,QAC5B,KAAA,EAAO,IAAI,GAAA,CAAI,KAAA,CAAM,OAAO,CAAA;AAAA,QAC5B,GAAA;AAAA,QACA,MAAM,EAAE,KAAA,EAAO,IAAI,KAAA,EAAO,MAAA,EAAQ,IAAI,MAAA;AAAO,OAC/C;AACA,MAAA,UAAA,CAAW,QAAQ,KAAK,CAAA;AAExB,MAAA,KAAA,MAAW,EAAA,IAAM,MAAM,KAAA,EAAO;AAC5B,QAAA,MAAM,IAAA,GAAO,KAAA,CAAM,KAAA,CAAM,GAAA,CAAI,EAAE,CAAA;AAC/B,QAAA,IAAI,MAAM,QAAA,CAAS,OAAA,CAAQ,IAAI,EAAE,CAAA,GAAI,MAAM,KAAK,CAAA;AAAA,MAClD;AACA,MAAA,KAAA,CAAM,QAAQ,KAAA,EAAM;AAAA,IACtB,CAAA;AAAA;AAAA;AAAA,IAGA,EAAE,MAAA,EAAQ,MAAM,SAAA,CAAU,OAAA;AAAQ,GACpC;AAEA,EAAA,MAAM,QAAA,GAAWA,YAAY,MAAM;AACjC,IAAA,SAAA,CAAU,OAAA,EAAQ;AAAA,EACpB,CAAA,EAAG,CAAC,SAAS,CAAC,CAAA;AAEd,EAAA,MAAM,UAAA,GAAaA,WAAAA;AAAA,IACjB,CAAC,EAAA,KAAe;AACd,MAAA,KAAA,CAAM,OAAA,CAAQ,IAAI,EAAE,CAAA;AACpB,MAAA,QAAA,EAAS;AAAA,IACX,CAAA;AAAA,IACA,CAAC,QAAQ;AAAA,GACX;AAEA,EAAA,MAAM,aAAA,GAAgBA,YAAY,MAAM;AACtC,IAAA,KAAA,MAAW,EAAA,IAAM,MAAM,OAAA,CAAQ,IAAA,IAAQ,KAAA,CAAM,OAAA,CAAQ,IAAI,EAAE,CAAA;AAC3D,IAAA,QAAA,EAAS;AAAA,EACX,CAAA,EAAG,CAAC,QAAQ,CAAC,CAAA;AAEb,EAAA,MAAM,eAAA,GAAkBA,YAAY,MAAM;AACxC,IAAA,YAAA,CAAa,OAAA,GAAU,IAAA;AACvB,IAAA,QAAA,EAAS;AAAA,EACX,CAAA,EAAG,CAAC,QAAQ,CAAC,CAAA;AAEb,EAAA,MAAM,YAAA,GAAeA,WAAAA;AAAA,IACnB,CAAC,IAAY,EAAA,KAA2B;AACtC,MAAA,MAAM,KAAA,GAAQ,KAAA,CAAM,OAAA,CAAQ,GAAA,CAAI,EAAE,CAAA;AAClC,MAAA,IAAI,UAAU,EAAA,EAAI;AAClB,MAAA,IAAI,KAAA,EAAO,QAAA,CAAS,OAAA,EAAS,SAAA,CAAU,KAAK,CAAA;AAC5C,MAAA,IAAI,EAAA,EAAI;AACN,QAAA,KAAA,CAAM,OAAA,CAAQ,GAAA,CAAI,EAAA,EAAI,EAAE,CAAA;AACxB,QAAA,QAAA,CAAS,OAAA,EAAS,QAAQ,EAAE,CAAA;AAAA,MAC9B,CAAA,MAAO;AACL,QAAA,KAAA,CAAM,OAAA,CAAQ,OAAO,EAAE,CAAA;AACvB,QAAA,KAAA,CAAM,OAAA,CAAQ,OAAO,EAAE,CAAA;AACvB,QAAA,KAAA,CAAM,OAAA,CAAQ,OAAO,EAAE,CAAA;AAAA,MACzB;AACA,MAAA,YAAA,CAAa,OAAA,GAAU,IAAA;AACvB,MAAA,QAAA,EAAS;AAAA,IACX,CAAA;AAAA,IACA,CAAC,QAAQ;AAAA,GACX;AAEA,EAAA,MAAM,eAAA,GAAkBA,WAAAA,CAAY,CAAC,EAAA,EAAY,KAAA,KAAuB;AACtE,IAAA,QAAA,CAAS,OAAA,CAAQ,GAAA,CAAI,EAAA,EAAI,KAAK,CAAA;AAC9B,IAAA,OAAO,MAAM;AACX,MAAA,IAAI,QAAA,CAAS,QAAQ,GAAA,CAAI,EAAE,MAAM,KAAA,EAAO,QAAA,CAAS,OAAA,CAAQ,MAAA,CAAO,EAAE,CAAA;AAAA,IACpE,CAAA;AAAA,EACF,CAAA,EAAG,EAAE,CAAA;AAEL,EAAA,MAAM,eAAeA,WAAAA,CAAY,MAAM,SAAA,CAAU,OAAA,EAAS,EAAE,CAAA;AAE5D,EAAA,MAAM,YAAA,GAAeA,WAAAA;AAAA,IACnB,CAAC,EAAA,KAA2B;AAC1B,MAAA,IAAI,SAAA,CAAU,YAAY,EAAA,EAAI;AAC9B,MAAA,IAAI,UAAU,OAAA,EAAS,QAAA,CAAS,OAAA,EAAS,SAAA,CAAU,UAAU,OAAO,CAAA;AACpE,MAAA,SAAA,CAAU,OAAA,GAAU,EAAA;AACpB,MAAA,IAAI,EAAA,EAAI,QAAA,CAAS,OAAA,EAAS,OAAA,CAAQ,EAAE,CAAA;AACpC,MAAA,YAAA,CAAa,OAAA,GAAU,IAAA;AACvB,MAAA,QAAA,EAAS;AAAA,IACX,CAAA;AAAA,IACA,CAAC,QAAQ;AAAA,GACX;AAEA,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,MAAM,EAAA,GAAK,IAAI,cAAA,CAAe,MAAM;AAClC,MAAA,YAAA,CAAa,OAAA,GAAU,IAAA;AACvB,MAAA,QAAA,EAAS;AAAA,IACX,CAAC,CAAA;AACD,IAAA,QAAA,CAAS,OAAA,GAAU,EAAA;AACnB,IAAA,IAAI,SAAA,CAAU,OAAA,EAAS,EAAA,CAAG,OAAA,CAAQ,UAAU,OAAO,CAAA;AACnD,IAAA,KAAA,MAAW,MAAM,KAAA,CAAM,OAAA,CAAQ,QAAO,EAAG,EAAA,CAAG,QAAQ,EAAE,CAAA;AACtD,IAAA,OAAO,MAAM;AACX,MAAA,EAAA,CAAG,UAAA,EAAW;AACd,MAAA,QAAA,CAAS,OAAA,GAAU,IAAA;AACnB,MAAA,SAAA,CAAU,MAAA,EAAO;AAAA,IACnB,CAAA;AAAA,EACF,CAAA,EAAG,CAAC,QAAA,EAAU,SAAS,CAAC,CAAA;AAExB,EAAA,OAAO;AAAA,IACL,UAAA;AAAA,IACA,aAAA;AAAA,IACA,eAAA;AAAA,IACA,YAAA;AAAA,IACA,eAAA;AAAA,IACA,YAAA;AAAA,IACA;AAAA,GACF;AACF","file":"chunk-OFCBEZMU.js","sourcesContent":["import type { Box, Rect } from './rect';\n\n/** Every tile's box expressed against the container's origin. */\nexport function composeRects(container: Box, tiles: ReadonlyMap<string, Box>): Map<string, Rect> {\n const out = new Map<string, Rect>();\n for (const [id, tile] of tiles) {\n out.set(id, {\n x: tile.left - container.left,\n y: tile.top - container.top,\n w: tile.width,\n h: tile.height,\n });\n }\n return out;\n}\n\nexport function rectsEqual(a: Rect | undefined, b: Rect): boolean {\n return a !== undefined && a.x === b.x && a.y === b.y && a.w === b.w && a.h === b.h;\n}\n","import type { Rect } from './rect';\n\n/**\n * A DOM rect as a GL viewport rect: origin at the bottom-left, every edge snapped\n * to the device-pixel grid. Still CSS pixels, because three.js applies its own\n * pixel ratio — snapping here is what stops a tile and its neighbour rounding\n * apart and leaving a hairline column between them.\n */\nexport function toDeviceRect(rect: Rect, surfaceHeight: number, dpr: number): Rect {\n const snap = (v: number) => Math.round(v * dpr) / dpr;\n const x = snap(rect.x);\n const y = snap(surfaceHeight - rect.y - rect.h);\n return {\n x,\n y,\n w: snap(rect.x + rect.w) - x,\n h: snap(surfaceHeight - rect.y) - y,\n };\n}\n","import { createContext } from 'react';\nimport type { SurfaceHandle } from './useTiledSurface';\n\n/** Null when no surface owner is above — a lab with no shared surface at all. */\nexport const SurfaceContext = createContext<SurfaceHandle | null>(null);\n\n/** The buffer the surface's tiles paint into. Null until the owner's canvas is\n * mounted, and for an owner that keeps its surface unpainted. */\nexport const SurfaceCanvasContext = createContext<HTMLCanvasElement | null>(null);\n","import { useCallback, useContext } from 'react';\nimport { TrialIdContext } from '../state/context';\nimport { SurfaceCanvasContext, SurfaceContext } from './SurfaceContext';\nimport type { SurfaceHandle } from './useTiledSurface';\n\n/** The surface's shared buffer, or null where nothing paints one. What a tile\n * hands to `paintInto`. */\nexport function useSurfaceCanvas(): HTMLCanvasElement | null {\n return useContext(SurfaceCanvasContext);\n}\n\n/** The surface above, or null. Use this where a surface is genuinely optional. */\nexport function useSurfaceOptional(): SurfaceHandle | null {\n return useContext(SurfaceContext);\n}\n\n/** The surface above. Throws where a caller cannot work without one. */\nexport function useSurface(): SurfaceHandle {\n const surface = useContext(SurfaceContext);\n if (!surface) throw new Error('[labkit] useSurface requires a surface owner above it');\n return surface;\n}\n\n/**\n * The key `id` is registered under, and so the key it comes back on in a\n * frame's `rects`: `<trial>/<id>` inside a trial, and `id` alone outside one.\n *\n * A surface's tile namespace is flat and shared by the whole lab, but an\n * instrument names its regions once and every trial of it declares those same\n * names. Unscoped, the second trial to mount takes the first one's rect and its\n * painter, and the first is never told it moved again.\n */\nexport function useTileId(id: string): string {\n const trial = useContext(TrialIdContext);\n return trial ? `${trial}/${id}` : id;\n}\n\n/**\n * A ref callback that publishes this element's rect to the surface under `id`.\n *\n * Attach it to whichever element the surface should draw into — that is not\n * necessarily the trial's own element, since a trial may hold a drawn pane beside\n * an undrawn one, or none at all.\n *\n * The frame reports it under `useTileId(id)`, not under `id`.\n */\nexport function useSurfaceTile(id: string): (el: HTMLElement | null) => void {\n const surface = useSurfaceOptional();\n const tileId = useTileId(id);\n return useCallback(\n (el: HTMLElement | null) => {\n surface?.registerTile(tileId, el);\n },\n [surface, tileId],\n );\n}\n","import { useVisibleRaf } from '@weasel-js/core';\nimport { useCallback, useEffect, useRef } from 'react';\nimport { composeRects, rectsEqual } from './composeRects';\nimport type { Box, Rect } from './rect';\n\n/** What a surface owner is handed once per animation frame. `rects` carries every\n * tile, not only the dirty ones: a scissored draw has to know where it is drawing\n * relative to a surface that may have resized under it.\n *\n * Its keys are what `useTileId` returns — `<trial>/<id>` for a tile registered\n * inside a trial — not the bare id the tile was named with. */\nexport interface SurfaceFrame {\n dirty: ReadonlySet<string>;\n rects: ReadonlyMap<string, Rect>;\n dpr: number;\n size: { width: number; height: number };\n}\n\n/** The invalidators and the two ref callbacks that publish geometry. */\nexport interface SurfaceHandle {\n /** Mark one tile for redraw. */\n invalidate: (id: string) => void;\n /** Mark every tile — what a resize or a tile-set change means. */\n invalidateAll: () => void;\n /** Re-measure before the next frame. The escape hatch for a host that knows it\n * moved something a ResizeObserver cannot see. */\n invalidateRects: () => void;\n registerTile: (id: string, el: HTMLElement | null) => void;\n /** Subscribe a tile to the frames it is dirty on. A tile paints on its own\n * loop, so this is how the surface wakes one: a resize of the shared buffer\n * clears every tile, not only the one that moved. */\n registerPainter: (id: string, paint: TilePainter) => () => void;\n containerRef: (el: HTMLElement | null) => void;\n /** The element tile rects are measured against, and so the one a tile's own\n * chrome positions itself inside. Null before the owner attaches it. */\n getContainer: () => HTMLElement | null;\n}\n\n/** What a tile's painter is handed: where it sits on the surface now, and the\n * frame that dirtied it. */\nexport type TilePainter = (rect: Rect, frame: SurfaceFrame) => void;\n\nexport interface UseTiledSurfaceOptions {\n onFrame: (frame: SurfaceFrame) => void;\n}\n\nexport function useTiledSurface({ onFrame }: UseTiledSurfaceOptions): SurfaceHandle {\n const container = useRef<HTMLElement | null>(null);\n const tiles = useRef(new Map<string, HTMLElement>());\n const rects = useRef(new Map<string, Rect>());\n const dirty = useRef(new Set<string>());\n const painters = useRef(new Map<string, TilePainter>());\n const needsMeasure = useRef(true);\n const observer = useRef<ResizeObserver | null>(null);\n const lastDpr = useRef(0);\n\n // Held in a ref so a caller passing an inline closure does not re-create every\n // callback below on each render.\n const onFrameRef = useRef(onFrame);\n onFrameRef.current = onFrame;\n\n const measure = useCallback((): boolean => {\n const el = container.current;\n if (!el) return false;\n const boxes = new Map<string, Box>();\n for (const [id, tile] of tiles.current) boxes.set(id, tile.getBoundingClientRect());\n const next = composeRects(el.getBoundingClientRect(), boxes);\n let changed = next.size !== rects.current.size;\n for (const [id, rect] of next) {\n if (!rectsEqual(rects.current.get(id), rect)) changed = true;\n }\n rects.current = next;\n return changed;\n }, []);\n\n const frameLoop = useVisibleRaf(\n () => {\n const el = container.current;\n if (!el) {\n dirty.current.clear();\n return;\n }\n if (needsMeasure.current) {\n needsMeasure.current = false;\n if (measure()) for (const id of rects.current.keys()) dirty.current.add(id);\n }\n const dpr = globalThis.devicePixelRatio ?? 1;\n if (dpr !== lastDpr.current) {\n lastDpr.current = dpr;\n for (const id of rects.current.keys()) dirty.current.add(id);\n }\n if (dirty.current.size === 0) return;\n const box = el.getBoundingClientRect();\n const frame: SurfaceFrame = {\n dirty: new Set(dirty.current),\n rects: new Map(rects.current),\n dpr,\n size: { width: box.width, height: box.height },\n };\n onFrameRef.current(frame);\n // After the owner, which is what resized the buffer this frame.\n for (const id of frame.dirty) {\n const rect = frame.rects.get(id);\n if (rect) painters.current.get(id)?.(rect, frame);\n }\n dirty.current.clear();\n },\n // The host attaches its container through `containerRef`, which may land\n // well after this hook's first effect; the gate re-resolves it per request.\n { target: () => container.current },\n );\n\n const schedule = useCallback(() => {\n frameLoop.request();\n }, [frameLoop]);\n\n const invalidate = useCallback(\n (id: string) => {\n dirty.current.add(id);\n schedule();\n },\n [schedule],\n );\n\n const invalidateAll = useCallback(() => {\n for (const id of tiles.current.keys()) dirty.current.add(id);\n schedule();\n }, [schedule]);\n\n const invalidateRects = useCallback(() => {\n needsMeasure.current = true;\n schedule();\n }, [schedule]);\n\n const registerTile = useCallback(\n (id: string, el: HTMLElement | null) => {\n const known = tiles.current.get(id);\n if (known === el) return;\n if (known) observer.current?.unobserve(known);\n if (el) {\n tiles.current.set(id, el);\n observer.current?.observe(el);\n } else {\n tiles.current.delete(id);\n rects.current.delete(id);\n dirty.current.delete(id);\n }\n needsMeasure.current = true;\n schedule();\n },\n [schedule],\n );\n\n const registerPainter = useCallback((id: string, paint: TilePainter) => {\n painters.current.set(id, paint);\n return () => {\n if (painters.current.get(id) === paint) painters.current.delete(id);\n };\n }, []);\n\n const getContainer = useCallback(() => container.current, []);\n\n const containerRef = useCallback(\n (el: HTMLElement | null) => {\n if (container.current === el) return;\n if (container.current) observer.current?.unobserve(container.current);\n container.current = el;\n if (el) observer.current?.observe(el);\n needsMeasure.current = true;\n schedule();\n },\n [schedule],\n );\n\n useEffect(() => {\n const ro = new ResizeObserver(() => {\n needsMeasure.current = true;\n schedule();\n });\n observer.current = ro;\n if (container.current) ro.observe(container.current);\n for (const el of tiles.current.values()) ro.observe(el);\n return () => {\n ro.disconnect();\n observer.current = null;\n frameLoop.cancel();\n };\n }, [schedule, frameLoop]);\n\n return {\n invalidate,\n invalidateAll,\n invalidateRects,\n registerTile,\n registerPainter,\n containerRef,\n getContainer,\n };\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/surface/composeRects.ts","../src/surface/deviceRect.ts","../src/surface/SurfaceContext.ts","../src/surface/useSurfaceTile.ts","../src/surface/useTiledSurface.ts"],"names":["useCallback"],"mappings":"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","file":"chunk-FQMJUVHJ.js","sourcesContent":["import type { Box, Rect } from './rect';\n\n/** Every tile's box expressed against the container's origin. */\nexport function composeRects(container: Box, tiles: ReadonlyMap<string, Box>): Map<string, Rect> {\n const out = new Map<string, Rect>();\n for (const [id, tile] of tiles) {\n out.set(id, {\n x: tile.left - container.left,\n y: tile.top - container.top,\n w: tile.width,\n h: tile.height,\n });\n }\n return out;\n}\n\nexport function rectsEqual(a: Rect | undefined, b: Rect): boolean {\n return a !== undefined && a.x === b.x && a.y === b.y && a.w === b.w && a.h === b.h;\n}\n","import type { Rect } from './rect';\n\n/**\n * A DOM rect as a GL viewport rect: origin at the bottom-left, every edge snapped\n * to the device-pixel grid. Still CSS pixels, because three.js applies its own\n * pixel ratio — snapping here is what stops a tile and its neighbour rounding\n * apart and leaving a hairline column between them.\n */\nexport function toDeviceRect(rect: Rect, surfaceHeight: number, dpr: number): Rect {\n const snap = (v: number) => Math.round(v * dpr) / dpr;\n const x = snap(rect.x);\n const y = snap(surfaceHeight - rect.y - rect.h);\n return {\n x,\n y,\n w: snap(rect.x + rect.w) - x,\n h: snap(surfaceHeight - rect.y) - y,\n };\n}\n","import { createContext } from 'react';\nimport type { SurfaceHandle } from './useTiledSurface';\n\n/** Null when no surface owner is above — a lab with no shared surface at all. */\nexport const SurfaceContext = createContext<SurfaceHandle | null>(null);\n\n/** The buffer the surface's tiles paint into. Null until the owner's canvas is\n * mounted, and for an owner that keeps its surface unpainted. */\nexport const SurfaceCanvasContext = createContext<HTMLCanvasElement | null>(null);\n","import { useCallback, useContext } from 'react';\nimport { TrialIdContext } from '../state/context';\nimport { SurfaceCanvasContext, SurfaceContext } from './SurfaceContext';\nimport type { SurfaceHandle } from './useTiledSurface';\n\n/** The surface's shared buffer, or null where nothing paints one. What a tile\n * hands to `paintInto`. */\nexport function useSurfaceCanvas(): HTMLCanvasElement | null {\n return useContext(SurfaceCanvasContext);\n}\n\n/** The surface above, or null. Use this where a surface is genuinely optional. */\nexport function useSurfaceOptional(): SurfaceHandle | null {\n return useContext(SurfaceContext);\n}\n\n/** The surface above. Throws where a caller cannot work without one. */\nexport function useSurface(): SurfaceHandle {\n const surface = useContext(SurfaceContext);\n if (!surface) throw new Error('[labkit] useSurface requires a surface owner above it');\n return surface;\n}\n\n/**\n * The key `id` is registered under, and so the key it comes back on in a\n * frame's `rects`: `<trial>/<id>` inside a trial, and `id` alone outside one.\n *\n * A surface's tile namespace is flat and shared by the whole lab, but an\n * instrument names its regions once and every trial of it declares those same\n * names. Unscoped, the second trial to mount takes the first one's rect and its\n * painter, and the first is never told it moved again.\n */\nexport function useTileId(id: string): string {\n const trial = useContext(TrialIdContext);\n return trial ? `${trial}/${id}` : id;\n}\n\n/**\n * A ref callback that publishes this element's rect to the surface under `id`.\n *\n * Attach it to whichever element the surface should draw into — that is not\n * necessarily the trial's own element, since a trial may hold a drawn pane beside\n * an undrawn one, or none at all.\n *\n * The frame reports it under `useTileId(id)`, not under `id`.\n */\nexport function useSurfaceTile(id: string): (el: HTMLElement | null) => void {\n const surface = useSurfaceOptional();\n const tileId = useTileId(id);\n return useCallback(\n (el: HTMLElement | null) => {\n surface?.registerTile(tileId, el);\n },\n [surface, tileId],\n );\n}\n","import { useVisibleRaf } from '@weasel-js/core';\nimport { useCallback, useEffect, useRef } from 'react';\nimport { composeRects, rectsEqual } from './composeRects';\nimport type { Box, Rect } from './rect';\n\n/** What a surface owner is handed once per animation frame. `rects` carries every\n * tile, not only the dirty ones: a scissored draw has to know where it is drawing\n * relative to a surface that may have resized under it.\n *\n * Its keys are what `useTileId` returns — `<trial>/<id>` for a tile registered\n * inside a trial — not the bare id the tile was named with. */\nexport interface SurfaceFrame {\n dirty: ReadonlySet<string>;\n rects: ReadonlyMap<string, Rect>;\n dpr: number;\n size: { width: number; height: number };\n}\n\n/** The invalidators and the two ref callbacks that publish geometry. */\nexport interface SurfaceHandle {\n /** Mark one tile for redraw. */\n invalidate: (id: string) => void;\n /** Mark every tile — what a resize or a tile-set change means. */\n invalidateAll: () => void;\n /** Re-measure before the next frame. The escape hatch for a host that knows it\n * moved something a ResizeObserver cannot see. */\n invalidateRects: () => void;\n registerTile: (id: string, el: HTMLElement | null) => void;\n /** Subscribe a tile to the frames it is dirty on. A tile paints on its own\n * loop, so this is how the surface wakes one: a resize of the shared buffer\n * clears every tile, not only the one that moved. */\n registerPainter: (id: string, paint: TilePainter) => () => void;\n containerRef: (el: HTMLElement | null) => void;\n /** The element tile rects are measured against, and so the one a tile's own\n * chrome positions itself inside. Null before the owner attaches it. */\n getContainer: () => HTMLElement | null;\n}\n\n/** What a tile's painter is handed: where it sits on the surface now, and the\n * frame that dirtied it. */\nexport type TilePainter = (rect: Rect, frame: SurfaceFrame) => void;\n\nexport interface UseTiledSurfaceOptions {\n onFrame: (frame: SurfaceFrame) => void;\n}\n\nexport function useTiledSurface({ onFrame }: UseTiledSurfaceOptions): SurfaceHandle {\n const container = useRef<HTMLElement | null>(null);\n const tiles = useRef(new Map<string, HTMLElement>());\n const rects = useRef(new Map<string, Rect>());\n const dirty = useRef(new Set<string>());\n const painters = useRef(new Map<string, TilePainter>());\n const needsMeasure = useRef(true);\n const observer = useRef<ResizeObserver | null>(null);\n const lastDpr = useRef(0);\n\n // Held in a ref so a caller passing an inline closure does not re-create every\n // callback below on each render.\n const onFrameRef = useRef(onFrame);\n onFrameRef.current = onFrame;\n\n const measure = useCallback((): boolean => {\n const el = container.current;\n if (!el) return false;\n const boxes = new Map<string, Box>();\n for (const [id, tile] of tiles.current) boxes.set(id, tile.getBoundingClientRect());\n const next = composeRects(el.getBoundingClientRect(), boxes);\n let changed = next.size !== rects.current.size;\n for (const [id, rect] of next) {\n if (!rectsEqual(rects.current.get(id), rect)) changed = true;\n }\n rects.current = next;\n return changed;\n }, []);\n\n const frameLoop = useVisibleRaf(\n () => {\n const el = container.current;\n if (!el) {\n dirty.current.clear();\n return;\n }\n if (needsMeasure.current) {\n needsMeasure.current = false;\n if (measure()) for (const id of rects.current.keys()) dirty.current.add(id);\n }\n const dpr = globalThis.devicePixelRatio ?? 1;\n if (dpr !== lastDpr.current) {\n lastDpr.current = dpr;\n for (const id of rects.current.keys()) dirty.current.add(id);\n }\n if (dirty.current.size === 0) return;\n const box = el.getBoundingClientRect();\n const frame: SurfaceFrame = {\n dirty: new Set(dirty.current),\n rects: new Map(rects.current),\n dpr,\n size: { width: box.width, height: box.height },\n };\n onFrameRef.current(frame);\n // After the owner, which is what resized the buffer this frame.\n for (const id of frame.dirty) {\n const rect = frame.rects.get(id);\n if (rect) painters.current.get(id)?.(rect, frame);\n }\n dirty.current.clear();\n },\n // The host attaches its container through `containerRef`, which may land\n // well after this hook's first effect; the gate re-resolves it per request.\n { target: () => container.current },\n );\n\n const schedule = useCallback(() => {\n frameLoop.request();\n }, [frameLoop]);\n\n const invalidate = useCallback(\n (id: string) => {\n dirty.current.add(id);\n schedule();\n },\n [schedule],\n );\n\n const invalidateAll = useCallback(() => {\n for (const id of tiles.current.keys()) dirty.current.add(id);\n schedule();\n }, [schedule]);\n\n const invalidateRects = useCallback(() => {\n needsMeasure.current = true;\n schedule();\n }, [schedule]);\n\n const registerTile = useCallback(\n (id: string, el: HTMLElement | null) => {\n const known = tiles.current.get(id);\n if (known === el) return;\n if (known) observer.current?.unobserve(known);\n if (el) {\n tiles.current.set(id, el);\n observer.current?.observe(el);\n } else {\n tiles.current.delete(id);\n rects.current.delete(id);\n dirty.current.delete(id);\n }\n needsMeasure.current = true;\n schedule();\n },\n [schedule],\n );\n\n const registerPainter = useCallback((id: string, paint: TilePainter) => {\n painters.current.set(id, paint);\n return () => {\n if (painters.current.get(id) === paint) painters.current.delete(id);\n };\n }, []);\n\n const getContainer = useCallback(() => container.current, []);\n\n const containerRef = useCallback(\n (el: HTMLElement | null) => {\n if (container.current === el) return;\n if (container.current) observer.current?.unobserve(container.current);\n container.current = el;\n if (el) observer.current?.observe(el);\n needsMeasure.current = true;\n schedule();\n },\n [schedule],\n );\n\n useEffect(() => {\n const ro = new ResizeObserver(() => {\n needsMeasure.current = true;\n schedule();\n });\n observer.current = ro;\n if (container.current) ro.observe(container.current);\n for (const el of tiles.current.values()) ro.observe(el);\n return () => {\n ro.disconnect();\n observer.current = null;\n frameLoop.cancel();\n };\n }, [schedule, frameLoop]);\n\n return {\n invalidate,\n invalidateAll,\n invalidateRects,\n registerTile,\n registerPainter,\n containerRef,\n getContainer,\n };\n}\n"]}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { zoomAt } from './chunk-CTRKTLYZ.js';
|
|
2
2
|
import { DEFAULT_FRAME, resolveFrame, screenToWorld } from './chunk-2AYGEN57.js';
|
|
3
3
|
import { CanvasStackContext } from './chunk-CQLQPQ4P.js';
|
|
4
|
-
import { openPointerSession, useVisibleRaf } from './chunk-DG2IFMLA.js';
|
|
5
4
|
import { useRef, useCallback, useEffect, useState, useMemo } from 'react';
|
|
5
|
+
import { openPointerSession, useVisibleRaf } from '@weasel-js/core';
|
|
6
6
|
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
7
7
|
|
|
8
8
|
function useLayerScheduler({
|
|
@@ -321,5 +321,5 @@ function useOrbit({
|
|
|
321
321
|
}
|
|
322
322
|
|
|
323
323
|
export { CanvasStack, PITCH_LIMIT, clampPitch, orbitAfterDrag, orbitAfterWheel, useOrbit, usePanZoom, wrapYaw };
|
|
324
|
-
//# sourceMappingURL=chunk-
|
|
325
|
-
//# sourceMappingURL=chunk-
|
|
324
|
+
//# sourceMappingURL=chunk-I6JCVE24.js.map
|
|
325
|
+
//# sourceMappingURL=chunk-I6JCVE24.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/canvas/useLayerScheduler.ts","../src/canvas/usePanZoom.ts","../src/canvas/CanvasStack.tsx","../src/canvas/useOrbit.ts"],"names":["useRef","useEffect","DRAG_THRESHOLD","useCallback","openPointerSession"],"mappings":"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","file":"chunk-I6JCVE24.js","sourcesContent":["import { useVisibleRaf } from '@weasel-js/core';\nimport { type RefObject, useEffect, useRef } from 'react';\nimport type { ViewTransform } from '../instrument/types';\nimport { DEFAULT_FRAME, type WorldFrame } from './worldSpec';\n\n/** One layer of a canvas stack: its id, whether it is currently shown, and how\n * it paints itself. */\nexport interface CanvasLayerDescriptor {\n id: string;\n visible: boolean;\n render: (ctx: CanvasRenderingContext2D, view: ViewTransform, frame: WorldFrame) => void;\n}\n\ninterface SchedulerOptions {\n layers: CanvasLayerDescriptor[];\n view: ViewTransform;\n /** The instrument's coordinate system, resolved against `size`. */\n frame?: WorldFrame;\n canvasRefs: RefObject<Map<string, HTMLCanvasElement>>;\n size: { width: number; height: number; dpr: number };\n /** The element the stack occupies. Given one, the scheduler also stops while\n * the stack sits outside the viewport. */\n host?: RefObject<Element | null>;\n}\n\nexport function useLayerScheduler({\n layers,\n view,\n frame = DEFAULT_FRAME,\n canvasRefs,\n size,\n host,\n}: SchedulerOptions): void {\n const dirty = useRef<Set<string>>(new Set());\n const lastRenderRef = useRef<Map<string, CanvasLayerDescriptor['render']>>(new Map());\n\n useEffect(() => {\n for (const layer of layers) {\n const prev = lastRenderRef.current.get(layer.id);\n if (prev !== layer.render) dirty.current.add(layer.id);\n lastRenderRef.current.set(layer.id, layer.render);\n }\n for (const id of [...lastRenderRef.current.keys()]) {\n if (!layers.find((l) => l.id === id)) lastRenderRef.current.delete(id);\n }\n }, [layers]);\n\n // biome-ignore lint/correctness/useExhaustiveDependencies: re-mark dirty when view or size changes\n useEffect(() => {\n for (const layer of layers) dirty.current.add(layer.id);\n }, [view, frame, size, layers]);\n\n // Painting only what is dirty is not the same as doing nothing: a hidden tab\n // still commits React updates, and the effect above marks every layer dirty\n // on any view or size change. The gate is what actually stops the work.\n const frameLoop = useVisibleRaf(\n () => {\n if (dirty.current.size === 0) return;\n const map = canvasRefs.current;\n if (!map) {\n dirty.current.clear();\n return;\n }\n for (const layer of layers) {\n if (!dirty.current.has(layer.id)) continue;\n if (!layer.visible) continue;\n const canvas = map.get(layer.id);\n if (!canvas) continue;\n const ctx = canvas.getContext('2d');\n if (!ctx) continue;\n ctx.save();\n ctx.setTransform(size.dpr, 0, 0, size.dpr, 0, 0);\n ctx.clearRect(0, 0, size.width, size.height);\n layer.render(ctx, view, frame);\n ctx.restore();\n }\n dirty.current.clear();\n },\n { target: host },\n );\n\n useEffect(() => {\n if (dirty.current.size > 0) frameLoop.request();\n });\n}\n","import { openPointerSession, type PointerSession } from '@weasel-js/core';\nimport {\n type PointerEvent as ReactPointerEvent,\n useCallback,\n useEffect,\n useRef,\n type WheelEvent,\n} from 'react';\nimport type { ViewTransform } from '../instrument/types';\nimport { zoomAt } from './camera';\nimport { DEFAULT_FRAME, type WorldFrame } from './worldSpec';\n\nexport interface UsePanZoomOptions {\n view: ViewTransform;\n onViewChange: (v: ViewTransform) => void;\n /** The opening `view.zoom` always stays reachable, widening these past\n * whatever is passed here if it would otherwise exclude it. */\n minZoom?: number;\n maxZoom?: number;\n /** The instrument's coordinate system, resolved against the viewport. The\n * wheel anchors in this frame; omitting it anchors at the element's\n * top-left, which is what labkit did before a frame could be declared. */\n frame?: WorldFrame;\n /** A primary-button press released without ever crossing the drag\n * threshold. The pointer session owns the release, so the gesture's end is\n * reported here rather than left for a consumer's own `onPointerUp`. */\n onTap?: (e: PointerEvent) => void;\n}\n\nexport interface PanZoomHandlers {\n onWheel: (e: WheelEvent<HTMLElement>) => void;\n onPointerDown: (e: ReactPointerEvent<HTMLElement>) => void;\n isDragging: () => boolean;\n}\n\ninterface DragState {\n startScreenX: number;\n startScreenY: number;\n startPan: { x: number; y: number };\n moved: boolean;\n}\n\nconst DRAG_THRESHOLD = 3;\n\nfunction isPositiveFinite(n: number): boolean {\n return Number.isFinite(n) && n > 0;\n}\n\nexport function usePanZoom({\n view,\n onViewChange,\n minZoom = 0.1,\n maxZoom = 32,\n frame = DEFAULT_FRAME,\n onTap,\n}: UsePanZoomOptions): PanZoomHandlers {\n const dragRef = useRef<DragState | null>(null);\n const sessionRef = useRef<PointerSession | null>(null);\n const viewRef = useRef(view);\n viewRef.current = view;\n const onViewChangeRef = useRef(onViewChange);\n onViewChangeRef.current = onViewChange;\n const onTapRef = useRef(onTap);\n onTapRef.current = onTap;\n // Captured once, from the view the canvas opened at — not the current view,\n // which would let a prior zoom-out shrink the range and trap the opening\n // view unreachable behind it.\n const initialZoomRef = useRef(isPositiveFinite(view.zoom) ? view.zoom : null);\n\n const onWheel = useCallback(\n (e: WheelEvent<HTMLElement>) => {\n e.preventDefault();\n const rect = e.currentTarget.getBoundingClientRect();\n const initialZoom = initialZoomRef.current;\n onViewChange(\n zoomAt(\n viewRef.current,\n Math.exp(-e.deltaY * 0.001),\n { x: e.clientX - rect.left, y: e.clientY - rect.top },\n {\n frame,\n min: initialZoom == null ? minZoom : Math.min(minZoom, initialZoom),\n max: initialZoom == null ? maxZoom : Math.max(maxZoom, initialZoom),\n },\n ),\n );\n },\n [onViewChange, minZoom, maxZoom, frame],\n );\n\n const clearDrag = useCallback(() => {\n dragRef.current = null;\n sessionRef.current = null;\n }, []);\n\n const onPointerDown = useCallback(\n (e: ReactPointerEvent<HTMLElement>) => {\n if (e.button !== 0) return;\n const drag: DragState = {\n startScreenX: e.clientX,\n startScreenY: e.clientY,\n startPan: { ...viewRef.current.pan },\n moved: false,\n };\n dragRef.current = drag;\n sessionRef.current = openPointerSession(e.currentTarget, e, {\n onMove: (ev) => {\n const dx = ev.clientX - drag.startScreenX;\n const dy = ev.clientY - drag.startScreenY;\n if (!drag.moved && Math.hypot(dx, dy) < DRAG_THRESHOLD) return;\n drag.moved = true;\n onViewChangeRef.current({\n zoom: viewRef.current.zoom,\n pan: { x: drag.startPan.x + dx, y: drag.startPan.y + dy },\n });\n },\n onEnd: (ev) => {\n const tapped = !drag.moved;\n clearDrag();\n if (tapped) onTapRef.current?.(ev);\n },\n // A cancelled pan keeps whatever it had already applied: every move\n // was committed as it arrived, so there is nothing left to undo.\n onCancel: clearDrag,\n });\n },\n [clearDrag],\n );\n\n useEffect(() => () => sessionRef.current?.cancel(), []);\n\n const isDragging = useCallback(() => dragRef.current?.moved === true, []);\n\n return { onWheel, onPointerDown, isDragging };\n}\n","import { type CSSProperties, type ReactNode, useEffect, useMemo, useRef, useState } from 'react';\nimport type { Point, ViewTransform } from '../instrument/types';\nimport { CanvasStackContext } from './CanvasStackContext';\nimport { screenToWorld } from './canvasCoords';\nimport { type CanvasLayerDescriptor, useLayerScheduler } from './useLayerScheduler';\nimport { usePanZoom } from './usePanZoom';\nimport { resolveFrame, type ViewportSize, type WorldSpec } from './worldSpec';\n\n/** Props for `<CanvasStack>`. */\nexport interface CanvasStackProps {\n layers: CanvasLayerDescriptor[];\n view: ViewTransform;\n onViewChange: (v: ViewTransform) => void;\n /** The instrument's coordinate system. Omitted, world (0,0) sits at the\n * element's top-left with y running down. */\n worldSpec?: WorldSpec;\n minZoom?: number;\n maxZoom?: number;\n width?: number | string;\n height?: number | string;\n className?: string;\n /** Fired whenever the stack is measured, so a consumer can place a view that\n * only makes sense in terms of the viewport. */\n onResize?: (size: ViewportSize) => void;\n onHitTest?: (worldPos: Point) => void;\n children?: ReactNode;\n}\n\n/** Stacks one `<canvas>` per layer and drives them from a shared view, so a\n * layer that changes rarely is not redrawn with one that changes every frame.\n * Handles sizing, device pixel ratio, and pan/zoom. */\nexport function CanvasStack({\n layers,\n view,\n onViewChange,\n worldSpec,\n minZoom,\n maxZoom,\n width = '100%',\n height = '100%',\n className,\n onResize,\n onHitTest,\n children,\n}: CanvasStackProps) {\n const containerRef = useRef<HTMLDivElement | null>(null);\n const canvasMap = useRef<Map<string, HTMLCanvasElement>>(new Map());\n const [size, setSize] = useState({ width: 0, height: 0, dpr: 1 });\n\n const setCanvasRef = (id: string, el: HTMLCanvasElement | null): void => {\n if (el) canvasMap.current.set(id, el);\n else canvasMap.current.delete(id);\n };\n\n useEffect(() => {\n const el = containerRef.current;\n if (!el) return;\n const update = () => {\n const rect = el.getBoundingClientRect();\n const dpr = window.devicePixelRatio ?? 1;\n setSize((prev) => {\n if (prev.width === rect.width && prev.height === rect.height && prev.dpr === dpr) {\n return prev;\n }\n return { width: rect.width, height: rect.height, dpr };\n });\n };\n update();\n if (typeof ResizeObserver === 'undefined') return;\n const ro = new ResizeObserver(update);\n ro.observe(el);\n return () => ro.disconnect();\n }, []);\n\n const onResizeRef = useRef(onResize);\n onResizeRef.current = onResize;\n useEffect(() => {\n if (size.width === 0 && size.height === 0) return;\n onResizeRef.current?.({ width: size.width, height: size.height });\n }, [size.width, size.height]);\n\n const frame = useMemo(() => resolveFrame(worldSpec, size), [worldSpec, size]);\n\n const onTap = (e: PointerEvent) => {\n const el = containerRef.current;\n if (!onHitTest || !el) return;\n const rect = el.getBoundingClientRect();\n onHitTest(screenToWorld({ x: e.clientX - rect.left, y: e.clientY - rect.top }, view, frame));\n };\n\n const handlers = usePanZoom({ view, onViewChange, minZoom, maxZoom, frame, onTap });\n useLayerScheduler({ layers, view, frame, canvasRefs: canvasMap, size, host: containerRef });\n\n const ctxValue = useMemo(\n () => ({\n view,\n frame,\n surface: { element: containerRef, size, canvases: canvasMap, layers },\n }),\n [view, frame, size, layers],\n );\n\n const containerStyle: CSSProperties = { width, height };\n const canvasPx = {\n width: Math.max(0, Math.round(size.width * size.dpr)),\n height: Math.max(0, Math.round(size.height * size.dpr)),\n };\n const canvasCss: CSSProperties = {\n width: size.width,\n height: size.height,\n };\n\n return (\n <CanvasStackContext.Provider value={ctxValue}>\n <div\n ref={containerRef}\n className={className ? `lk-canvas-stack ${className}` : 'lk-canvas-stack'}\n style={containerStyle}\n onWheel={handlers.onWheel}\n onPointerDown={handlers.onPointerDown}\n >\n {layers.map((layer) => (\n <canvas\n key={layer.id}\n ref={(el) => setCanvasRef(layer.id, el)}\n className=\"lk-canvas-stack__canvas\"\n width={canvasPx.width}\n height={canvasPx.height}\n style={{ ...canvasCss, display: layer.visible ? 'block' : 'none' }}\n />\n ))}\n <div className=\"lk-canvas-stack__overlay\">{children}</div>\n </div>\n </CanvasStackContext.Provider>\n );\n}\n","import { openPointerSession, type PointerSession } from '@weasel-js/core';\nimport {\n type PointerEvent as ReactPointerEvent,\n useCallback,\n useEffect,\n useRef,\n type WheelEvent,\n} from 'react';\n\n/** A point in the space the instrument works in. */\nexport interface Vec3 {\n x: number;\n y: number;\n z: number;\n}\n\n/** An orbit camera as a trial view: where it looks from, and what it looks at.\n * labkit does not turn this into a matrix — the host's renderer does. */\nexport interface OrbitView {\n yaw: number;\n pitch: number;\n distance: number;\n target: Vec3;\n}\n\n/** Just short of the pole. At exactly ±PI/2 the azimuth is undefined and the\n * camera rolls, which reads as the model jumping rather than as a limit. */\nexport const PITCH_LIMIT = Math.PI / 2 - 0.01;\n\nconst YAW_PER_PX = 0.008;\nconst PITCH_PER_PX = 0.008;\nconst DISTANCE_PER_NOTCH = 0.0015;\nconst DRAG_THRESHOLD = 3;\n\nexport function clampPitch(pitch: number): number {\n return Math.min(PITCH_LIMIT, Math.max(-PITCH_LIMIT, pitch));\n}\n\nexport function wrapYaw(yaw: number): number {\n const wrapped = ((((yaw + Math.PI) % (2 * Math.PI)) + 2 * Math.PI) % (2 * Math.PI)) - Math.PI;\n return wrapped === -Math.PI ? Math.PI : wrapped;\n}\n\n/** The view a drag of (dx, dy) from `start` produces. Absolute against the drag\n * start, so it can be re-applied any number of times without compounding. */\nexport function orbitAfterDrag(start: OrbitView, dx: number, dy: number): OrbitView {\n return {\n ...start,\n yaw: wrapYaw(start.yaw + dx * YAW_PER_PX),\n pitch: clampPitch(start.pitch + dy * PITCH_PER_PX),\n };\n}\n\n/** Multiplicative, so one notch covers the same proportion of the distance\n * whether the camera is near or far. */\nexport function orbitAfterWheel(\n view: OrbitView,\n deltaY: number,\n minDistance: number,\n maxDistance: number,\n): OrbitView {\n const factor = Math.exp(deltaY * DISTANCE_PER_NOTCH);\n return {\n ...view,\n distance: Math.min(maxDistance, Math.max(minDistance, view.distance * factor)),\n };\n}\n\nexport interface UseOrbitOptions {\n view: OrbitView;\n onViewChange: (v: OrbitView) => void;\n /** Restored on double-click. Defaults to the view the hook first saw. */\n home?: OrbitView;\n minDistance?: number;\n maxDistance?: number;\n}\n\nexport interface OrbitHandlers {\n onWheel: (e: WheelEvent<HTMLElement>) => void;\n onPointerDown: (e: ReactPointerEvent<HTMLElement>) => void;\n onDoubleClick: () => void;\n isDragging: () => boolean;\n}\n\ninterface DragState {\n startX: number;\n startY: number;\n startView: OrbitView;\n moved: boolean;\n}\n\n/** Pointer gestures over an orbit view: drag to turn, wheel or pinch to dolly,\n * double-click to go home. The 3D peer of `usePanZoom`. */\nexport function useOrbit({\n view,\n onViewChange,\n home,\n minDistance = 0.1,\n maxDistance = 1000,\n}: UseOrbitOptions): OrbitHandlers {\n const dragRef = useRef<DragState | null>(null);\n const sessionRef = useRef<PointerSession | null>(null);\n const viewRef = useRef(view);\n viewRef.current = view;\n const onViewChangeRef = useRef(onViewChange);\n onViewChangeRef.current = onViewChange;\n const homeRef = useRef(home ?? view);\n if (home) homeRef.current = home;\n\n const onWheel = useCallback(\n (e: WheelEvent<HTMLElement>) => {\n e.preventDefault();\n onViewChange(orbitAfterWheel(viewRef.current, e.deltaY, minDistance, maxDistance));\n },\n [onViewChange, minDistance, maxDistance],\n );\n\n const clearDrag = useCallback(() => {\n dragRef.current = null;\n sessionRef.current = null;\n }, []);\n\n const onPointerDown = useCallback(\n (e: ReactPointerEvent<HTMLElement>) => {\n if (e.button !== 0) return;\n const drag: DragState = {\n startX: e.clientX,\n startY: e.clientY,\n startView: viewRef.current,\n moved: false,\n };\n dragRef.current = drag;\n sessionRef.current = openPointerSession(e.currentTarget, e, {\n onMove: (ev) => {\n const dx = ev.clientX - drag.startX;\n const dy = ev.clientY - drag.startY;\n if (!drag.moved && Math.hypot(dx, dy) < DRAG_THRESHOLD) return;\n drag.moved = true;\n onViewChangeRef.current(orbitAfterDrag(drag.startView, dx, dy));\n },\n onEnd: clearDrag,\n // A cancelled turn keeps where it got to: every move was committed as\n // it arrived, so there is nothing left to undo.\n onCancel: clearDrag,\n });\n },\n [clearDrag],\n );\n\n useEffect(() => () => sessionRef.current?.cancel(), []);\n\n const onDoubleClick = useCallback(() => onViewChange(homeRef.current), [onViewChange]);\n const isDragging = useCallback(() => dragRef.current?.moved === true, []);\n\n return { onWheel, onPointerDown, onDoubleClick, isDragging };\n}\n"]}
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import { r5, n4 } from './chunk-MHAGC6VD.js';
|
|
2
|
+
import { openPointerSession } from '@weasel-js/core';
|
|
3
|
+
import { useState, useRef, useId, useEffect, Fragment as Fragment$1 } from 'react';
|
|
4
|
+
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
5
|
+
|
|
6
|
+
function findNode(nodes, id) {
|
|
7
|
+
for (const node of nodes) {
|
|
8
|
+
if (node.id === id) return node;
|
|
9
|
+
const hit = node.children ? findNode(node.children, id) : null;
|
|
10
|
+
if (hit) return hit;
|
|
11
|
+
}
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
function siblingsOf(layers, parentId) {
|
|
15
|
+
if (parentId === null) return layers;
|
|
16
|
+
return findNode(layers, parentId)?.children ?? [];
|
|
17
|
+
}
|
|
18
|
+
function withChildren(nodes, parentId, next) {
|
|
19
|
+
if (parentId === null) return next;
|
|
20
|
+
return nodes.map((node) => {
|
|
21
|
+
if (node.id === parentId) return { ...node, children: next };
|
|
22
|
+
if (node.children) return { ...node, children: withChildren(node.children, parentId, next) };
|
|
23
|
+
return node;
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
function collectCollapsed(nodes, into) {
|
|
27
|
+
for (const node of nodes) {
|
|
28
|
+
if (node.defaultCollapsed) into.add(node.id);
|
|
29
|
+
if (node.children) collectCollapsed(node.children, into);
|
|
30
|
+
}
|
|
31
|
+
return into;
|
|
32
|
+
}
|
|
33
|
+
function anyNested(nodes) {
|
|
34
|
+
return nodes.some((n2) => (n2.children?.length ?? 0) > 0);
|
|
35
|
+
}
|
|
36
|
+
function LayerList({
|
|
37
|
+
layers,
|
|
38
|
+
visibility,
|
|
39
|
+
onReorder,
|
|
40
|
+
onToggle,
|
|
41
|
+
collapsedIds,
|
|
42
|
+
onCollapsedChange,
|
|
43
|
+
className
|
|
44
|
+
}) {
|
|
45
|
+
const [dragMark, setDragMark] = useState(null);
|
|
46
|
+
const [ownCollapsed, setOwnCollapsed] = useState(
|
|
47
|
+
() => collectCollapsed(layers, /* @__PURE__ */ new Set())
|
|
48
|
+
);
|
|
49
|
+
const dragRef = useRef(null);
|
|
50
|
+
const sessionRef = useRef(null);
|
|
51
|
+
const uid = useId();
|
|
52
|
+
const commitRef = useRef({ layers, onReorder });
|
|
53
|
+
commitRef.current = { layers, onReorder };
|
|
54
|
+
useEffect(() => () => sessionRef.current?.cancel(), []);
|
|
55
|
+
const collapsed = collapsedIds ? new Set(collapsedIds) : ownCollapsed;
|
|
56
|
+
const toggleCollapsed = (id) => {
|
|
57
|
+
const next = new Set(collapsed);
|
|
58
|
+
if (next.has(id)) next.delete(id);
|
|
59
|
+
else next.add(id);
|
|
60
|
+
if (!collapsedIds) setOwnCollapsed(next);
|
|
61
|
+
onCollapsedChange?.([...next]);
|
|
62
|
+
};
|
|
63
|
+
if (layers.length === 0) {
|
|
64
|
+
return /* @__PURE__ */ jsx("div", { className: className ? `lk-layer-list ${className}` : "lk-layer-list", children: /* @__PURE__ */ jsx("div", { className: "lk-layer-list__empty", children: "No layers" }) });
|
|
65
|
+
}
|
|
66
|
+
const clearDrag = () => {
|
|
67
|
+
dragRef.current = null;
|
|
68
|
+
sessionRef.current = null;
|
|
69
|
+
setDragMark(null);
|
|
70
|
+
};
|
|
71
|
+
const startDrag = (e, parentId, index) => {
|
|
72
|
+
const row = e.currentTarget.closest(".lk-layer-list__row");
|
|
73
|
+
const list = row?.parentElement;
|
|
74
|
+
const gap = list ? Number.parseFloat(getComputedStyle(list).rowGap) || 0 : 0;
|
|
75
|
+
const pitch = row ? row.getBoundingClientRect().height + gap : 0;
|
|
76
|
+
const drag = {
|
|
77
|
+
parentId,
|
|
78
|
+
fromIndex: index,
|
|
79
|
+
toIndex: index,
|
|
80
|
+
startY: e.clientY,
|
|
81
|
+
pitch: pitch > 0 ? pitch : 1
|
|
82
|
+
};
|
|
83
|
+
dragRef.current = drag;
|
|
84
|
+
setDragMark({ parentId, index });
|
|
85
|
+
sessionRef.current = openPointerSession(e.currentTarget, e, {
|
|
86
|
+
onMove: (ev) => {
|
|
87
|
+
const delta = Math.round((ev.clientY - drag.startY) / drag.pitch);
|
|
88
|
+
const siblings = siblingsOf(commitRef.current.layers, parentId);
|
|
89
|
+
const last = siblings.filter((l) => !l.alwaysOn).length - 1;
|
|
90
|
+
drag.toIndex = Math.min(last, Math.max(0, drag.fromIndex + delta));
|
|
91
|
+
setDragMark({ parentId, index: drag.toIndex });
|
|
92
|
+
},
|
|
93
|
+
onEnd: () => {
|
|
94
|
+
const { layers: tree, onReorder: commit } = commitRef.current;
|
|
95
|
+
const { fromIndex, toIndex } = drag;
|
|
96
|
+
clearDrag();
|
|
97
|
+
if (toIndex === fromIndex) return;
|
|
98
|
+
const siblings = siblingsOf(tree, parentId);
|
|
99
|
+
const rows = siblings.filter((l) => !l.alwaysOn);
|
|
100
|
+
const locked = siblings.filter((l) => l.alwaysOn);
|
|
101
|
+
const next = [...rows];
|
|
102
|
+
const [moved] = next.splice(fromIndex, 1);
|
|
103
|
+
if (moved) next.splice(toIndex, 0, moved);
|
|
104
|
+
commit(withChildren(tree, parentId, [...next, ...locked]));
|
|
105
|
+
},
|
|
106
|
+
// An interrupted drag never named a destination, so it reorders nothing.
|
|
107
|
+
onCancel: clearDrag
|
|
108
|
+
});
|
|
109
|
+
};
|
|
110
|
+
const nested = anyNested(layers);
|
|
111
|
+
function renderRow(layer, parentId, index, path, pinned) {
|
|
112
|
+
const kids = layer.children ?? [];
|
|
113
|
+
const open = kids.length > 0 && !collapsed.has(layer.id);
|
|
114
|
+
const bodyId = `${uid}${path}`;
|
|
115
|
+
const dragging = !pinned && dragMark?.parentId === parentId && dragMark.index === index;
|
|
116
|
+
const cls = ["lk-layer-list__row"];
|
|
117
|
+
if (dragging) cls.push("lk-layer-list__row--dragging");
|
|
118
|
+
if (pinned) cls.push("lk-layer-list__row--pinned");
|
|
119
|
+
return /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
120
|
+
/* @__PURE__ */ jsxs("div", { className: cls.join(" "), children: [
|
|
121
|
+
nested && (kids.length > 0 ? /* @__PURE__ */ jsx(
|
|
122
|
+
r5,
|
|
123
|
+
{
|
|
124
|
+
open,
|
|
125
|
+
onToggle: () => toggleCollapsed(layer.id),
|
|
126
|
+
label: `${layer.label} sublayers`,
|
|
127
|
+
controls: bodyId
|
|
128
|
+
}
|
|
129
|
+
) : /* @__PURE__ */ jsx("span", { className: "lk-layer-list__twisty-gap" })),
|
|
130
|
+
pinned ? /* @__PURE__ */ jsx("span", { className: "lk-layer-list__lock", role: "img", "aria-label": "Always on", children: "\u{1F512}" }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
131
|
+
/* @__PURE__ */ jsx(
|
|
132
|
+
"button",
|
|
133
|
+
{
|
|
134
|
+
type: "button",
|
|
135
|
+
className: "lk-layer-list__handle",
|
|
136
|
+
"aria-label": `Reorder ${layer.label}`,
|
|
137
|
+
onPointerDown: (e) => startDrag(e, parentId, index),
|
|
138
|
+
children: /* @__PURE__ */ jsx(n4, { size: 13 })
|
|
139
|
+
}
|
|
140
|
+
),
|
|
141
|
+
/* @__PURE__ */ jsx(
|
|
142
|
+
"input",
|
|
143
|
+
{
|
|
144
|
+
className: "lk-layer-list__check",
|
|
145
|
+
type: "checkbox",
|
|
146
|
+
checked: visibility[layer.id] !== false,
|
|
147
|
+
onChange: (e) => onToggle(layer.id, e.target.checked),
|
|
148
|
+
"aria-label": `Toggle ${layer.label}`
|
|
149
|
+
}
|
|
150
|
+
)
|
|
151
|
+
] }),
|
|
152
|
+
/* @__PURE__ */ jsx("span", { className: "lk-layer-list__label", children: layer.label })
|
|
153
|
+
] }),
|
|
154
|
+
open && /* @__PURE__ */ jsx("div", { id: bodyId, className: "lk-layer-list__children", children: renderLevel(kids, layer.id, path) })
|
|
155
|
+
] }, layer.id);
|
|
156
|
+
}
|
|
157
|
+
function renderLevel(nodes, parentId, path) {
|
|
158
|
+
const reorderable = nodes.filter((l) => !l.alwaysOn);
|
|
159
|
+
const pinned = nodes.filter((l) => l.alwaysOn);
|
|
160
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
161
|
+
reorderable.map((layer, i) => renderRow(layer, parentId, i, `${path}-${i}`, false)),
|
|
162
|
+
pinned.map((layer, i) => renderRow(layer, parentId, i, `${path}-p${i}`, true))
|
|
163
|
+
] });
|
|
164
|
+
}
|
|
165
|
+
return /* @__PURE__ */ jsx("div", { className: className ? `lk-layer-list ${className}` : "lk-layer-list", children: renderLevel(layers, null, "") });
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export { LayerList };
|
|
169
|
+
//# sourceMappingURL=chunk-ISCOFAUO.js.map
|
|
170
|
+
//# sourceMappingURL=chunk-ISCOFAUO.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/layers/LayerList.tsx"],"names":["n","Fragment","r"],"mappings":"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","file":"chunk-ISCOFAUO.js","sourcesContent":["import { openPointerSession, type PointerSession } from '@weasel-js/core';\nimport { Disclosure } from '@weasel-js/ui';\nimport {\n Fragment,\n type ReactNode,\n type PointerEvent as ReactPointerEvent,\n useEffect,\n useId,\n useRef,\n useState,\n} from 'react';\nimport type { LayerDescriptor } from '../instrument/types';\nimport { DragHandleGlyph } from '../passthrough/weasel-ui';\n\n/** A layer that may itself contain layers. A plain `LayerDescriptor` is one\n * of these with no children, so a flat list needs no change. */\nexport interface LayerTreeNode extends LayerDescriptor {\n children?: LayerTreeNode[];\n /** Render this node's children hidden on first paint. Ignored once\n * `collapsedIds` is supplied. */\n defaultCollapsed?: boolean;\n}\n\n/** Props for `<LayerList>`. */\nexport interface LayerListProps {\n layers: LayerTreeNode[];\n visibility: Record<string, boolean>;\n /** The whole tree, with the dragged node moved. Reordering is scoped to a\n * node's siblings: a drag never changes which parent a layer sits under. */\n onReorder: (newOrder: LayerTreeNode[]) => void;\n onToggle: (id: string, visible: boolean) => void;\n /** Ids whose children are hidden. Supplying it makes collapse controlled;\n * without it the list keeps its own, seeded from `defaultCollapsed`. */\n collapsedIds?: readonly string[];\n onCollapsedChange?: (ids: string[]) => void;\n className?: string;\n}\n\ninterface DragState {\n /** Id of the dragged row's parent, or null at the top level. */\n parentId: string | null;\n fromIndex: number;\n toIndex: number;\n startY: number;\n /** Row height plus row gap, measured when the drag starts. */\n pitch: number;\n}\n\n/** Which row the drag currently marks. */\ninterface DragMark {\n parentId: string | null;\n index: number;\n}\n\nfunction findNode(nodes: LayerTreeNode[], id: string): LayerTreeNode | null {\n for (const node of nodes) {\n if (node.id === id) return node;\n const hit = node.children ? findNode(node.children, id) : null;\n if (hit) return hit;\n }\n return null;\n}\n\nfunction siblingsOf(layers: LayerTreeNode[], parentId: string | null): LayerTreeNode[] {\n if (parentId === null) return layers;\n return findNode(layers, parentId)?.children ?? [];\n}\n\nfunction withChildren(\n nodes: LayerTreeNode[],\n parentId: string | null,\n next: LayerTreeNode[],\n): LayerTreeNode[] {\n if (parentId === null) return next;\n return nodes.map((node) => {\n if (node.id === parentId) return { ...node, children: next };\n if (node.children) return { ...node, children: withChildren(node.children, parentId, next) };\n return node;\n });\n}\n\nfunction collectCollapsed(nodes: LayerTreeNode[], into: Set<string>): Set<string> {\n for (const node of nodes) {\n if (node.defaultCollapsed) into.add(node.id);\n if (node.children) collectCollapsed(node.children, into);\n }\n return into;\n}\n\nfunction anyNested(nodes: LayerTreeNode[]): boolean {\n return nodes.some((n) => (n.children?.length ?? 0) > 0);\n}\n\n/** A reorderable list of layers with per-layer visibility toggles. Layers\n * marked `alwaysOn` are pinned and cannot be reordered or hidden. Layers with\n * `children` render as an expandable subtree. */\nexport function LayerList({\n layers,\n visibility,\n onReorder,\n onToggle,\n collapsedIds,\n onCollapsedChange,\n className,\n}: LayerListProps) {\n const [dragMark, setDragMark] = useState<DragMark | null>(null);\n const [ownCollapsed, setOwnCollapsed] = useState<Set<string>>(() =>\n collectCollapsed(layers, new Set()),\n );\n const dragRef = useRef<DragState | null>(null);\n const sessionRef = useRef<PointerSession | null>(null);\n const uid = useId();\n // The session's callbacks outlive the render that opened them, so the commit\n // reads its inputs here rather than from that render's closure.\n const commitRef = useRef({ layers, onReorder });\n commitRef.current = { layers, onReorder };\n\n useEffect(() => () => sessionRef.current?.cancel(), []);\n\n const collapsed = collapsedIds ? new Set(collapsedIds) : ownCollapsed;\n\n const toggleCollapsed = (id: string) => {\n const next = new Set(collapsed);\n if (next.has(id)) next.delete(id);\n else next.add(id);\n if (!collapsedIds) setOwnCollapsed(next);\n onCollapsedChange?.([...next]);\n };\n\n if (layers.length === 0) {\n return (\n <div className={className ? `lk-layer-list ${className}` : 'lk-layer-list'}>\n <div className=\"lk-layer-list__empty\">No layers</div>\n </div>\n );\n }\n\n const clearDrag = () => {\n dragRef.current = null;\n sessionRef.current = null;\n setDragMark(null);\n };\n\n const startDrag = (e: ReactPointerEvent<HTMLElement>, parentId: string | null, index: number) => {\n // Measured at grab time rather than hardcoded: a restyle that changes row\n // height or gap would otherwise silently skew every drag distance.\n const row = e.currentTarget.closest('.lk-layer-list__row');\n const list = row?.parentElement;\n const gap = list ? Number.parseFloat(getComputedStyle(list).rowGap) || 0 : 0;\n const pitch = row ? row.getBoundingClientRect().height + gap : 0;\n const drag: DragState = {\n parentId,\n fromIndex: index,\n toIndex: index,\n startY: e.clientY,\n pitch: pitch > 0 ? pitch : 1,\n };\n dragRef.current = drag;\n setDragMark({ parentId, index });\n sessionRef.current = openPointerSession(e.currentTarget, e, {\n onMove: (ev) => {\n const delta = Math.round((ev.clientY - drag.startY) / drag.pitch);\n const siblings = siblingsOf(commitRef.current.layers, parentId);\n const last = siblings.filter((l) => !l.alwaysOn).length - 1;\n drag.toIndex = Math.min(last, Math.max(0, drag.fromIndex + delta));\n setDragMark({ parentId, index: drag.toIndex });\n },\n onEnd: () => {\n const { layers: tree, onReorder: commit } = commitRef.current;\n const { fromIndex, toIndex } = drag;\n clearDrag();\n if (toIndex === fromIndex) return;\n const siblings = siblingsOf(tree, parentId);\n const rows = siblings.filter((l) => !l.alwaysOn);\n const locked = siblings.filter((l) => l.alwaysOn);\n const next = [...rows];\n const [moved] = next.splice(fromIndex, 1);\n if (moved) next.splice(toIndex, 0, moved);\n commit(withChildren(tree, parentId, [...next, ...locked]));\n },\n // An interrupted drag never named a destination, so it reorders nothing.\n onCancel: clearDrag,\n });\n };\n\n // A twisty column on every row keeps labels aligned across depths, but a\n // flat list would only be paying 20px for it.\n const nested = anyNested(layers);\n\n function renderRow(\n layer: LayerTreeNode,\n parentId: string | null,\n index: number,\n path: string,\n pinned: boolean,\n ): ReactNode {\n const kids = layer.children ?? [];\n const open = kids.length > 0 && !collapsed.has(layer.id);\n const bodyId = `${uid}${path}`;\n const dragging = !pinned && dragMark?.parentId === parentId && dragMark.index === index;\n const cls = ['lk-layer-list__row'];\n if (dragging) cls.push('lk-layer-list__row--dragging');\n if (pinned) cls.push('lk-layer-list__row--pinned');\n return (\n <Fragment key={layer.id}>\n <div className={cls.join(' ')}>\n {nested &&\n (kids.length > 0 ? (\n <Disclosure\n open={open}\n onToggle={() => toggleCollapsed(layer.id)}\n label={`${layer.label} sublayers`}\n controls={bodyId}\n />\n ) : (\n <span className=\"lk-layer-list__twisty-gap\" />\n ))}\n {pinned ? (\n <span className=\"lk-layer-list__lock\" role=\"img\" aria-label=\"Always on\">\n 🔒\n </span>\n ) : (\n <>\n <button\n type=\"button\"\n className=\"lk-layer-list__handle\"\n aria-label={`Reorder ${layer.label}`}\n onPointerDown={(e) => startDrag(e, parentId, index)}\n >\n <DragHandleGlyph size={13} />\n </button>\n <input\n className=\"lk-layer-list__check\"\n type=\"checkbox\"\n checked={visibility[layer.id] !== false}\n onChange={(e) => onToggle(layer.id, e.target.checked)}\n aria-label={`Toggle ${layer.label}`}\n />\n </>\n )}\n <span className=\"lk-layer-list__label\">{layer.label}</span>\n </div>\n {open && (\n <div id={bodyId} className=\"lk-layer-list__children\">\n {renderLevel(kids, layer.id, path)}\n </div>\n )}\n </Fragment>\n );\n }\n\n function renderLevel(nodes: LayerTreeNode[], parentId: string | null, path: string): ReactNode {\n const reorderable = nodes.filter((l) => !l.alwaysOn);\n const pinned = nodes.filter((l) => l.alwaysOn);\n return (\n <>\n {reorderable.map((layer, i) => renderRow(layer, parentId, i, `${path}-${i}`, false))}\n {pinned.map((layer, i) => renderRow(layer, parentId, i, `${path}-p${i}`, true))}\n </>\n );\n }\n\n return (\n <div className={className ? `lk-layer-list ${className}` : 'lk-layer-list'}>\n {renderLevel(layers, null, '')}\n </div>\n );\n}\n"]}
|