@memberjunction/ng-whiteboard 5.44.0 → 5.45.0

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.
@@ -89,7 +89,7 @@ export class RealtimeWhiteboardZoomComponent {
89
89
  }
90
90
  }
91
91
  static { this.ɵfac = function RealtimeWhiteboardZoomComponent_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || RealtimeWhiteboardZoomComponent)(); }; }
92
- static { this.ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: RealtimeWhiteboardZoomComponent, selectors: [["mj-realtime-whiteboard-zoom"]], inputs: { ZoomPercent: "ZoomPercent", MinimapOpen: "MinimapOpen" }, outputs: { ZoomOut: "ZoomOut", ZoomIn: "ZoomIn", ZoomBy: "ZoomBy", Fit: "Fit", ToggleMinimap: "ToggleMinimap" }, decls: 12, vars: 3, consts: [[1, "zoom-cluster"], ["type", "button", "title", "Zoom out (hold for continuous)", 3, "pointerdown", "pointerup", "pointerleave", "pointercancel", "click"], [1, "fa-solid", "fa-minus"], [1, "zoom-pct"], ["type", "button", "title", "Zoom in (hold for continuous)", 3, "pointerdown", "pointerup", "pointerleave", "pointercancel", "click"], [1, "fa-solid", "fa-plus"], [1, "zoom-sep"], ["type", "button", "title", "Fit to content", 3, "click"], [1, "fa-solid", "fa-compress"], ["type", "button", "title", "Mini-map", 3, "click"], [1, "fa-regular", "fa-map"]], template: function RealtimeWhiteboardZoomComponent_Template(rf, ctx) { if (rf & 1) {
92
+ static { this.ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: RealtimeWhiteboardZoomComponent, selectors: [["mj-realtime-whiteboard-zoom"]], inputs: { ZoomPercent: "ZoomPercent", MinimapOpen: "MinimapOpen" }, outputs: { ZoomOut: "ZoomOut", ZoomIn: "ZoomIn", ZoomBy: "ZoomBy", Fit: "Fit", ToggleMinimap: "ToggleMinimap" }, decls: 12, vars: 3, consts: [[1, "zoom-cluster"], ["type", "button", "title", "Zoom out (hold for continuous)", "aria-label", "Zoom out", 3, "pointerdown", "pointerup", "pointerleave", "pointercancel", "click"], ["aria-hidden", "true", 1, "fa-solid", "fa-minus"], [1, "zoom-pct"], ["type", "button", "title", "Zoom in (hold for continuous)", "aria-label", "Zoom in", 3, "pointerdown", "pointerup", "pointerleave", "pointercancel", "click"], ["aria-hidden", "true", 1, "fa-solid", "fa-plus"], [1, "zoom-sep"], ["type", "button", "title", "Fit to content", "aria-label", "Fit to content", 3, "click"], ["aria-hidden", "true", 1, "fa-solid", "fa-compress"], ["type", "button", "title", "Mini-map", "aria-label", "Toggle mini-map", 3, "click"], ["aria-hidden", "true", 1, "fa-regular", "fa-map"]], template: function RealtimeWhiteboardZoomComponent_Template(rf, ctx) { if (rf & 1) {
93
93
  i0.ɵɵdomElementStart(0, "div", 0)(1, "button", 1);
94
94
  i0.ɵɵdomListener("pointerdown", function RealtimeWhiteboardZoomComponent_Template_button_pointerdown_1_listener($event) { return ctx.OnZoomPressStart($event, -1); })("pointerup", function RealtimeWhiteboardZoomComponent_Template_button_pointerup_1_listener() { return ctx.OnZoomPressEnd(); })("pointerleave", function RealtimeWhiteboardZoomComponent_Template_button_pointerleave_1_listener() { return ctx.OnZoomPressEnd(); })("pointercancel", function RealtimeWhiteboardZoomComponent_Template_button_pointercancel_1_listener() { return ctx.OnZoomPressEnd(); })("click", function RealtimeWhiteboardZoomComponent_Template_button_click_1_listener() { return ctx.OnZoomClick(-1); });
95
95
  i0.ɵɵdomElement(2, "i", 2);
@@ -119,7 +119,7 @@ export class RealtimeWhiteboardZoomComponent {
119
119
  }
120
120
  (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(RealtimeWhiteboardZoomComponent, [{
121
121
  type: Component,
122
- args: [{ standalone: true, selector: 'mj-realtime-whiteboard-zoom', imports: [CommonModule], template: "<div class=\"zoom-cluster\">\n <button type=\"button\" title=\"Zoom out (hold for continuous)\"\n (pointerdown)=\"OnZoomPressStart($event, -1)\"\n (pointerup)=\"OnZoomPressEnd()\"\n (pointerleave)=\"OnZoomPressEnd()\"\n (pointercancel)=\"OnZoomPressEnd()\"\n (click)=\"OnZoomClick(-1)\"><i class=\"fa-solid fa-minus\"></i></button>\n <span class=\"zoom-pct\">{{ ZoomPercent }}%</span>\n <button type=\"button\" title=\"Zoom in (hold for continuous)\"\n (pointerdown)=\"OnZoomPressStart($event, 1)\"\n (pointerup)=\"OnZoomPressEnd()\"\n (pointerleave)=\"OnZoomPressEnd()\"\n (pointercancel)=\"OnZoomPressEnd()\"\n (click)=\"OnZoomClick(1)\"><i class=\"fa-solid fa-plus\"></i></button>\n <span class=\"zoom-sep\"></span>\n <button type=\"button\" title=\"Fit to content\" (click)=\"Fit.emit()\"><i class=\"fa-solid fa-compress\"></i></button>\n <button type=\"button\" title=\"Mini-map\" [class.on]=\"MinimapOpen\" (click)=\"ToggleMinimap.emit()\"><i class=\"fa-regular fa-map\"></i></button>\n</div>\n", styles: ["/* Bottom-right zoom cluster \u2014 ported pixel-faithfully from\n plans/realtime/mockups/whiteboard.html (.zoom-cluster). */\n\n:host {\n display: block;\n position: absolute;\n right: 14px;\n bottom: 14px;\n z-index: 10;\n}\n\n.zoom-cluster {\n display: flex;\n align-items: center;\n gap: 2px;\n background: var(--mj-bg-surface-card);\n border: 1px solid var(--mj-border-default);\n border-radius: var(--mj-radius-full, 9999px);\n box-shadow: var(--mj-shadow-xl, 0 20px 25px -5px rgba(0, 0, 0, 0.3));\n padding: 4px;\n}\n\n.zoom-cluster button {\n width: 30px;\n height: 30px;\n border-radius: 50%;\n border: 0;\n background: transparent;\n color: var(--mj-text-secondary);\n cursor: pointer;\n font-size: 12px;\n display: grid;\n place-items: center;\n font-family: inherit;\n}\n.zoom-cluster button:hover {\n background: var(--mj-bg-surface-hover);\n color: var(--mj-text-primary);\n}\n.zoom-cluster button.on {\n background: color-mix(in srgb, var(--mj-brand-primary) 14%, transparent);\n color: var(--mj-brand-accent, var(--mj-brand-primary));\n}\n\n.zoom-pct {\n font-size: 11.5px;\n font-weight: 700;\n color: var(--mj-text-secondary);\n padding: 0 7px;\n font-family: var(--mj-font-mono, ui-monospace, monospace);\n}\n\n.zoom-sep {\n width: 1px;\n height: 18px;\n background: var(--mj-border-default);\n margin: 0 3px;\n}\n"] }]
122
+ args: [{ standalone: true, selector: 'mj-realtime-whiteboard-zoom', imports: [CommonModule], template: "<div class=\"zoom-cluster\">\n <button type=\"button\" title=\"Zoom out (hold for continuous)\" aria-label=\"Zoom out\"\n (pointerdown)=\"OnZoomPressStart($event, -1)\"\n (pointerup)=\"OnZoomPressEnd()\"\n (pointerleave)=\"OnZoomPressEnd()\"\n (pointercancel)=\"OnZoomPressEnd()\"\n (click)=\"OnZoomClick(-1)\"><i class=\"fa-solid fa-minus\" aria-hidden=\"true\"></i></button>\n <span class=\"zoom-pct\">{{ ZoomPercent }}%</span>\n <button type=\"button\" title=\"Zoom in (hold for continuous)\" aria-label=\"Zoom in\"\n (pointerdown)=\"OnZoomPressStart($event, 1)\"\n (pointerup)=\"OnZoomPressEnd()\"\n (pointerleave)=\"OnZoomPressEnd()\"\n (pointercancel)=\"OnZoomPressEnd()\"\n (click)=\"OnZoomClick(1)\"><i class=\"fa-solid fa-plus\" aria-hidden=\"true\"></i></button>\n <span class=\"zoom-sep\"></span>\n <button type=\"button\" title=\"Fit to content\" aria-label=\"Fit to content\" (click)=\"Fit.emit()\"><i class=\"fa-solid fa-compress\" aria-hidden=\"true\"></i></button>\n <button type=\"button\" title=\"Mini-map\" aria-label=\"Toggle mini-map\" [class.on]=\"MinimapOpen\" (click)=\"ToggleMinimap.emit()\"><i class=\"fa-regular fa-map\" aria-hidden=\"true\"></i></button>\n</div>\n", styles: ["/* Bottom-right zoom cluster \u2014 ported pixel-faithfully from\n plans/realtime/mockups/whiteboard.html (.zoom-cluster). */\n\n:host {\n display: block;\n position: absolute;\n right: 14px;\n bottom: 14px;\n z-index: 10;\n}\n\n.zoom-cluster {\n display: flex;\n align-items: center;\n gap: 2px;\n background: var(--mj-bg-surface-card);\n border: 1px solid var(--mj-border-default);\n border-radius: var(--mj-radius-full, 9999px);\n box-shadow: var(--mj-shadow-xl, 0 20px 25px -5px rgba(0, 0, 0, 0.3));\n padding: 4px;\n}\n\n.zoom-cluster button {\n width: 30px;\n height: 30px;\n border-radius: 50%;\n border: 0;\n background: transparent;\n color: var(--mj-text-secondary);\n cursor: pointer;\n font-size: 12px;\n display: grid;\n place-items: center;\n font-family: inherit;\n}\n.zoom-cluster button:hover {\n background: var(--mj-bg-surface-hover);\n color: var(--mj-text-primary);\n}\n.zoom-cluster button.on {\n background: color-mix(in srgb, var(--mj-brand-primary) 14%, transparent);\n color: var(--mj-brand-accent, var(--mj-brand-primary));\n}\n\n.zoom-pct {\n font-size: 11.5px;\n font-weight: 700;\n color: var(--mj-text-secondary);\n padding: 0 7px;\n font-family: var(--mj-font-mono, ui-monospace, monospace);\n}\n\n.zoom-sep {\n width: 1px;\n height: 18px;\n background: var(--mj-border-default);\n margin: 0 3px;\n}\n"] }]
123
123
  }], null, { ZoomPercent: [{
124
124
  type: Input
125
125
  }], MinimapOpen: [{
@@ -1 +1 @@
1
- {"version":3,"file":"whiteboard-zoom.component.js","sourceRoot":"","sources":["../../src/lib/whiteboard-zoom.component.ts","../../src/lib/whiteboard-zoom.component.html"],"names":[],"mappings":"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","sourcesContent":["import { Component, EventEmitter, Input, OnDestroy, Output } from '@angular/core';\nimport { CommonModule } from '@angular/common';\n\n/** Which way a zoom button points. */\ntype ZoomDirection = 1 | -1;\n\n/**\n * The bottom-right zoom cluster of the live whiteboard: zoom out / percentage / zoom in,\n * fit-to-content, and the minimap toggle. Pure presentational; the board owns the\n * viewport math and the minimap rendering.\n *\n * HOLD-TO-ZOOM: a plain click on +/− still emits the existing single preset step\n * ({@link ZoomIn} / {@link ZoomOut}); holding the button down (pointerdown held past a\n * short delay) emits {@link ZoomBy} factors (~3.5%) every 50 ms for smooth continuous\n * zooming, stopping on pointerup / pointerleave / pointercancel / destroy — the timers\n * never leak. The board clamps to its usual 25%–200% limits.\n */\n@Component({\n standalone: true,\n selector: 'mj-realtime-whiteboard-zoom',\n imports: [CommonModule],\n templateUrl: './whiteboard-zoom.component.html',\n styleUrl: './whiteboard-zoom.component.css'\n})\nexport class RealtimeWhiteboardZoomComponent implements OnDestroy {\n /** How long a press must be held before continuous zooming kicks in. */\n private static readonly HoldDelayMs = 350;\n /** Continuous-zoom tick cadence while held. */\n private static readonly HoldIntervalMs = 50;\n /** Multiplicative zoom factor per held tick (~3.5%). */\n private static readonly HoldFactor = 1.035;\n\n /** Current zoom as a percentage (e.g. 90). */\n @Input() ZoomPercent = 100;\n /** Whether the minimap is currently shown (highlights the map button). */\n @Input() MinimapOpen = false;\n\n /** Single preset step out (a plain click on −). */\n @Output() ZoomOut = new EventEmitter<void>();\n /** Single preset step in (a plain click on +). */\n @Output() ZoomIn = new EventEmitter<void>();\n /**\n * Continuous (held-button) zoom tick: a multiplicative factor (>1 zooms in, <1 zooms\n * out) emitted every 50 ms while + or − is held. Wire to the board's `ZoomByFactor`.\n */\n @Output() ZoomBy = new EventEmitter<number>();\n @Output() Fit = new EventEmitter<void>();\n @Output() ToggleMinimap = new EventEmitter<void>();\n\n private holdDelayTimer: ReturnType<typeof setTimeout> | null = null;\n private holdIntervalTimer: ReturnType<typeof setInterval> | null = null;\n /** Set once continuous ticks started, so the trailing click doesn't ALSO step. */\n private suppressNextClick = false;\n\n ngOnDestroy(): void {\n this.stopHold();\n }\n\n /** Pointer down on +/−: arm the hold — after the delay, tick {@link ZoomBy} repeatedly. */\n public OnZoomPressStart(event: PointerEvent, direction: ZoomDirection): void {\n if (event.button !== 0) {\n return;\n }\n this.stopHold();\n this.suppressNextClick = false;\n this.holdDelayTimer = setTimeout(() => {\n this.holdDelayTimer = null;\n this.suppressNextClick = true;\n const factor = direction === 1\n ? RealtimeWhiteboardZoomComponent.HoldFactor\n : 1 / RealtimeWhiteboardZoomComponent.HoldFactor;\n this.ZoomBy.emit(factor); // first tick fires immediately when the hold engages\n this.holdIntervalTimer = setInterval(() => this.ZoomBy.emit(factor), RealtimeWhiteboardZoomComponent.HoldIntervalMs);\n }, RealtimeWhiteboardZoomComponent.HoldDelayMs);\n }\n\n /** Pointer up / leave / cancel: stop any pending or running continuous zoom. */\n public OnZoomPressEnd(): void {\n this.stopHold();\n }\n\n /** The click that follows the press: a single preset step unless a hold already zoomed. */\n public OnZoomClick(direction: ZoomDirection): void {\n if (this.suppressNextClick) {\n this.suppressNextClick = false;\n return;\n }\n if (direction === 1) {\n this.ZoomIn.emit();\n }\n else {\n this.ZoomOut.emit();\n }\n }\n\n /** Clear both hold timers (idempotent — safe from any stop path). */\n private stopHold(): void {\n if (this.holdDelayTimer !== null) {\n clearTimeout(this.holdDelayTimer);\n this.holdDelayTimer = null;\n }\n if (this.holdIntervalTimer !== null) {\n clearInterval(this.holdIntervalTimer);\n this.holdIntervalTimer = null;\n }\n }\n}\n","<div class=\"zoom-cluster\">\n <button type=\"button\" title=\"Zoom out (hold for continuous)\"\n (pointerdown)=\"OnZoomPressStart($event, -1)\"\n (pointerup)=\"OnZoomPressEnd()\"\n (pointerleave)=\"OnZoomPressEnd()\"\n (pointercancel)=\"OnZoomPressEnd()\"\n (click)=\"OnZoomClick(-1)\"><i class=\"fa-solid fa-minus\"></i></button>\n <span class=\"zoom-pct\">{{ ZoomPercent }}%</span>\n <button type=\"button\" title=\"Zoom in (hold for continuous)\"\n (pointerdown)=\"OnZoomPressStart($event, 1)\"\n (pointerup)=\"OnZoomPressEnd()\"\n (pointerleave)=\"OnZoomPressEnd()\"\n (pointercancel)=\"OnZoomPressEnd()\"\n (click)=\"OnZoomClick(1)\"><i class=\"fa-solid fa-plus\"></i></button>\n <span class=\"zoom-sep\"></span>\n <button type=\"button\" title=\"Fit to content\" (click)=\"Fit.emit()\"><i class=\"fa-solid fa-compress\"></i></button>\n <button type=\"button\" title=\"Mini-map\" [class.on]=\"MinimapOpen\" (click)=\"ToggleMinimap.emit()\"><i class=\"fa-regular fa-map\"></i></button>\n</div>\n"]}
1
+ {"version":3,"file":"whiteboard-zoom.component.js","sourceRoot":"","sources":["../../src/lib/whiteboard-zoom.component.ts","../../src/lib/whiteboard-zoom.component.html"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,KAAK,EAAa,MAAM,EAAE,MAAM,eAAe,CAAC;AAClF,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;;AAK/C;;;;;;;;;;GAUG;AAQH,MAAM,OAAO,+BAA+B;IAP5C;QAeE,8CAA8C;QACrC,gBAAW,GAAG,GAAG,CAAC;QAC3B,0EAA0E;QACjE,gBAAW,GAAG,KAAK,CAAC;QAE7B,mDAAmD;QACzC,YAAO,GAAG,IAAI,YAAY,EAAQ,CAAC;QAC7C,kDAAkD;QACxC,WAAM,GAAG,IAAI,YAAY,EAAQ,CAAC;QAC5C;;;WAGG;QACO,WAAM,GAAG,IAAI,YAAY,EAAU,CAAC;QACpC,QAAG,GAAG,IAAI,YAAY,EAAQ,CAAC;QAC/B,kBAAa,GAAG,IAAI,YAAY,EAAQ,CAAC;QAE3C,mBAAc,GAAyC,IAAI,CAAC;QAC5D,sBAAiB,GAA0C,IAAI,CAAC;QACxE,kFAAkF;QAC1E,sBAAiB,GAAG,KAAK,CAAC;KAsDnC;IAjFC,wEAAwE;aAChD,gBAAW,GAAG,GAAG,AAAN,CAAO;IAC1C,+CAA+C;aACvB,mBAAc,GAAG,EAAE,AAAL,CAAM;IAC5C,wDAAwD;aAChC,eAAU,GAAG,KAAK,AAAR,CAAS;IAwB3C,WAAW;QACT,IAAI,CAAC,QAAQ,EAAE,CAAC;IAClB,CAAC;IAED,2FAA2F;IACpF,gBAAgB,CAAC,KAAmB,EAAE,SAAwB;QACnE,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACvB,OAAO;QACT,CAAC;QACD,IAAI,CAAC,QAAQ,EAAE,CAAC;QAChB,IAAI,CAAC,iBAAiB,GAAG,KAAK,CAAC;QAC/B,IAAI,CAAC,cAAc,GAAG,UAAU,CAAC,GAAG,EAAE;YACpC,IAAI,CAAC,cAAc,GAAG,IAAI,CAAC;YAC3B,IAAI,CAAC,iBAAiB,GAAG,IAAI,CAAC;YAC9B,MAAM,MAAM,GAAG,SAAS,KAAK,CAAC;gBAC5B,CAAC,CAAC,+BAA+B,CAAC,UAAU;gBAC5C,CAAC,CAAC,CAAC,GAAG,+BAA+B,CAAC,UAAU,CAAC;YACnD,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,qDAAqD;YAC/E,IAAI,CAAC,iBAAiB,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,+BAA+B,CAAC,cAAc,CAAC,CAAC;QACvH,CAAC,EAAE,+BAA+B,CAAC,WAAW,CAAC,CAAC;IAClD,CAAC;IAED,gFAAgF;IACzE,cAAc;QACnB,IAAI,CAAC,QAAQ,EAAE,CAAC;IAClB,CAAC;IAED,2FAA2F;IACpF,WAAW,CAAC,SAAwB;QACzC,IAAI,IAAI,CAAC,iBAAiB,EAAE,CAAC;YAC3B,IAAI,CAAC,iBAAiB,GAAG,KAAK,CAAC;YAC/B,OAAO;QACT,CAAC;QACD,IAAI,SAAS,KAAK,CAAC,EAAE,CAAC;YACpB,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;QACrB,CAAC;aACI,CAAC;YACJ,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC;QACtB,CAAC;IACH,CAAC;IAED,qEAAqE;IAC7D,QAAQ;QACd,IAAI,IAAI,CAAC,cAAc,KAAK,IAAI,EAAE,CAAC;YACjC,YAAY,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;YAClC,IAAI,CAAC,cAAc,GAAG,IAAI,CAAC;QAC7B,CAAC;QACD,IAAI,IAAI,CAAC,iBAAiB,KAAK,IAAI,EAAE,CAAC;YACpC,aAAa,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC;YACtC,IAAI,CAAC,iBAAiB,GAAG,IAAI,CAAC;QAChC,CAAC;IACH,CAAC;gIAjFU,+BAA+B;oEAA/B,+BAA+B;YCvB1C,AADF,iCAA0B,gBAMU;YAA1B,AADA,AADA,AADA,AADA,iIAAe,8BAA0B,CAAC,CAAC,IAAC,uGAC/B,oBAAgB,IAAC,6GACd,oBAAgB,IAAC,+GAChB,oBAAgB,IAAC,+FACzB,iBAAa,CAAC,CAAC,IAAC;YAAC,0BAAoD;YAAA,oBAAS;YAC/F,kCAAuB;YAAA,YAAkB;YAAA,oBAAO;YAChD,oCAKiC;YAAzB,AADA,AADA,AADA,AADA,iIAAe,6BAAyB,CAAC,CAAC,IAAC,uGAC9B,oBAAgB,IAAC,6GACd,oBAAgB,IAAC,+GAChB,oBAAgB,IAAC,+FACzB,gBAAY,CAAC,CAAC,IAAC;YAAC,0BAAmD;YAAA,oBAAS;YAC7F,6BAA8B;YAC9B,oCAA8F;YAArB,+GAAS,cAAU,IAAC;YAAC,0BAAuD;YAAA,oBAAS;YAC9J,qCAA4H;YAA/B,gHAAS,wBAAoB,IAAC;YAAC,4BAAoD;YAClL,AADkL,oBAAS,EACrL;;YAVmB,eAAkB;YAAlB,+CAAkB;YAS2B,eAAwB;YAAxB,qCAAwB;4BDIlF,YAAY;;iFAIX,+BAA+B;cAP3C,SAAS;6BACI,IAAI,YACN,6BAA6B,WAC9B,CAAC,YAAY,CAAC;;kBAatB,KAAK;;kBAEL,KAAK;;kBAGL,MAAM;;kBAEN,MAAM;;kBAKN,MAAM;;kBACN,MAAM;;kBACN,MAAM;;kFAvBI,+BAA+B","sourcesContent":["import { Component, EventEmitter, Input, OnDestroy, Output } from '@angular/core';\nimport { CommonModule } from '@angular/common';\n\n/** Which way a zoom button points. */\ntype ZoomDirection = 1 | -1;\n\n/**\n * The bottom-right zoom cluster of the live whiteboard: zoom out / percentage / zoom in,\n * fit-to-content, and the minimap toggle. Pure presentational; the board owns the\n * viewport math and the minimap rendering.\n *\n * HOLD-TO-ZOOM: a plain click on +/− still emits the existing single preset step\n * ({@link ZoomIn} / {@link ZoomOut}); holding the button down (pointerdown held past a\n * short delay) emits {@link ZoomBy} factors (~3.5%) every 50 ms for smooth continuous\n * zooming, stopping on pointerup / pointerleave / pointercancel / destroy — the timers\n * never leak. The board clamps to its usual 25%–200% limits.\n */\n@Component({\n standalone: true,\n selector: 'mj-realtime-whiteboard-zoom',\n imports: [CommonModule],\n templateUrl: './whiteboard-zoom.component.html',\n styleUrl: './whiteboard-zoom.component.css'\n})\nexport class RealtimeWhiteboardZoomComponent implements OnDestroy {\n /** How long a press must be held before continuous zooming kicks in. */\n private static readonly HoldDelayMs = 350;\n /** Continuous-zoom tick cadence while held. */\n private static readonly HoldIntervalMs = 50;\n /** Multiplicative zoom factor per held tick (~3.5%). */\n private static readonly HoldFactor = 1.035;\n\n /** Current zoom as a percentage (e.g. 90). */\n @Input() ZoomPercent = 100;\n /** Whether the minimap is currently shown (highlights the map button). */\n @Input() MinimapOpen = false;\n\n /** Single preset step out (a plain click on −). */\n @Output() ZoomOut = new EventEmitter<void>();\n /** Single preset step in (a plain click on +). */\n @Output() ZoomIn = new EventEmitter<void>();\n /**\n * Continuous (held-button) zoom tick: a multiplicative factor (>1 zooms in, <1 zooms\n * out) emitted every 50 ms while + or − is held. Wire to the board's `ZoomByFactor`.\n */\n @Output() ZoomBy = new EventEmitter<number>();\n @Output() Fit = new EventEmitter<void>();\n @Output() ToggleMinimap = new EventEmitter<void>();\n\n private holdDelayTimer: ReturnType<typeof setTimeout> | null = null;\n private holdIntervalTimer: ReturnType<typeof setInterval> | null = null;\n /** Set once continuous ticks started, so the trailing click doesn't ALSO step. */\n private suppressNextClick = false;\n\n ngOnDestroy(): void {\n this.stopHold();\n }\n\n /** Pointer down on +/−: arm the hold — after the delay, tick {@link ZoomBy} repeatedly. */\n public OnZoomPressStart(event: PointerEvent, direction: ZoomDirection): void {\n if (event.button !== 0) {\n return;\n }\n this.stopHold();\n this.suppressNextClick = false;\n this.holdDelayTimer = setTimeout(() => {\n this.holdDelayTimer = null;\n this.suppressNextClick = true;\n const factor = direction === 1\n ? RealtimeWhiteboardZoomComponent.HoldFactor\n : 1 / RealtimeWhiteboardZoomComponent.HoldFactor;\n this.ZoomBy.emit(factor); // first tick fires immediately when the hold engages\n this.holdIntervalTimer = setInterval(() => this.ZoomBy.emit(factor), RealtimeWhiteboardZoomComponent.HoldIntervalMs);\n }, RealtimeWhiteboardZoomComponent.HoldDelayMs);\n }\n\n /** Pointer up / leave / cancel: stop any pending or running continuous zoom. */\n public OnZoomPressEnd(): void {\n this.stopHold();\n }\n\n /** The click that follows the press: a single preset step unless a hold already zoomed. */\n public OnZoomClick(direction: ZoomDirection): void {\n if (this.suppressNextClick) {\n this.suppressNextClick = false;\n return;\n }\n if (direction === 1) {\n this.ZoomIn.emit();\n }\n else {\n this.ZoomOut.emit();\n }\n }\n\n /** Clear both hold timers (idempotent — safe from any stop path). */\n private stopHold(): void {\n if (this.holdDelayTimer !== null) {\n clearTimeout(this.holdDelayTimer);\n this.holdDelayTimer = null;\n }\n if (this.holdIntervalTimer !== null) {\n clearInterval(this.holdIntervalTimer);\n this.holdIntervalTimer = null;\n }\n }\n}\n","<div class=\"zoom-cluster\">\n <button type=\"button\" title=\"Zoom out (hold for continuous)\" aria-label=\"Zoom out\"\n (pointerdown)=\"OnZoomPressStart($event, -1)\"\n (pointerup)=\"OnZoomPressEnd()\"\n (pointerleave)=\"OnZoomPressEnd()\"\n (pointercancel)=\"OnZoomPressEnd()\"\n (click)=\"OnZoomClick(-1)\"><i class=\"fa-solid fa-minus\" aria-hidden=\"true\"></i></button>\n <span class=\"zoom-pct\">{{ ZoomPercent }}%</span>\n <button type=\"button\" title=\"Zoom in (hold for continuous)\" aria-label=\"Zoom in\"\n (pointerdown)=\"OnZoomPressStart($event, 1)\"\n (pointerup)=\"OnZoomPressEnd()\"\n (pointerleave)=\"OnZoomPressEnd()\"\n (pointercancel)=\"OnZoomPressEnd()\"\n (click)=\"OnZoomClick(1)\"><i class=\"fa-solid fa-plus\" aria-hidden=\"true\"></i></button>\n <span class=\"zoom-sep\"></span>\n <button type=\"button\" title=\"Fit to content\" aria-label=\"Fit to content\" (click)=\"Fit.emit()\"><i class=\"fa-solid fa-compress\" aria-hidden=\"true\"></i></button>\n <button type=\"button\" title=\"Mini-map\" aria-label=\"Toggle mini-map\" [class.on]=\"MinimapOpen\" (click)=\"ToggleMinimap.emit()\"><i class=\"fa-regular fa-map\" aria-hidden=\"true\"></i></button>\n</div>\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@memberjunction/ng-whiteboard",
3
- "version": "5.44.0",
3
+ "version": "5.45.0",
4
4
  "description": "MemberJunction: Generic collaborative whiteboard for any Angular application — Angular-free state engine with undo/redo + change journal, agent perception deltas, a programmatic (AI-agent-ready) mutation tool API, sandboxed HTML widgets with an input bridge, markdown panels, and self-contained HTML/SVG export",
5
5
  "main": "./dist/public-api.js",
6
6
  "typings": "./dist/public-api.d.ts",
@@ -25,7 +25,7 @@
25
25
  "devDependencies": {
26
26
  "@angular/compiler": "21.1.3",
27
27
  "@angular/compiler-cli": "21.1.3",
28
- "@memberjunction/ng-test-utils": "5.44.0",
28
+ "@memberjunction/ng-test-utils": "5.45.0",
29
29
  "vitest": "^4.0.18"
30
30
  },
31
31
  "peerDependencies": {
@@ -34,10 +34,10 @@
34
34
  "@angular/platform-browser": "21.1.3"
35
35
  },
36
36
  "dependencies": {
37
- "@memberjunction/ng-ui-components": "5.44.0",
38
- "@memberjunction/global": "5.44.0",
39
- "@memberjunction/ng-code-editor": "5.44.0",
40
- "@memberjunction/ng-markdown": "5.44.0",
37
+ "@memberjunction/ng-ui-components": "5.45.0",
38
+ "@memberjunction/global": "5.45.0",
39
+ "@memberjunction/ng-code-editor": "5.45.0",
40
+ "@memberjunction/ng-markdown": "5.45.0",
41
41
  "rxjs": "^7.8.2",
42
42
  "tslib": "^2.8.1"
43
43
  },