@memberjunction/ng-whiteboard 5.51.1 → 5.51.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 +5 -0
- package/dist/lib/whiteboard-board.component.d.ts +11 -3
- package/dist/lib/whiteboard-board.component.d.ts.map +1 -1
- package/dist/lib/whiteboard-board.component.js +12 -3
- package/dist/lib/whiteboard-board.component.js.map +1 -1
- package/dist/lib/whiteboard-context-menu.d.ts +2 -1
- package/dist/lib/whiteboard-context-menu.d.ts.map +1 -1
- package/dist/lib/whiteboard-context-menu.js +11 -7
- package/dist/lib/whiteboard-context-menu.js.map +1 -1
- package/dist/lib/whiteboard-host.component.d.ts +72 -3
- package/dist/lib/whiteboard-host.component.d.ts.map +1 -1
- package/dist/lib/whiteboard-host.component.dom.test.d.ts +2 -0
- package/dist/lib/whiteboard-host.component.dom.test.d.ts.map +1 -0
- package/dist/lib/whiteboard-host.component.dom.test.js +187 -0
- package/dist/lib/whiteboard-host.component.dom.test.js.map +1 -0
- package/dist/lib/whiteboard-host.component.js +132 -12
- package/dist/lib/whiteboard-host.component.js.map +1 -1
- package/dist/lib/whiteboard-tool-roster.d.ts +55 -0
- package/dist/lib/whiteboard-tool-roster.d.ts.map +1 -0
- package/dist/lib/whiteboard-tool-roster.js +58 -0
- package/dist/lib/whiteboard-tool-roster.js.map +1 -0
- package/dist/lib/whiteboard-toolbar.component.d.ts +11 -4
- package/dist/lib/whiteboard-toolbar.component.d.ts.map +1 -1
- package/dist/lib/whiteboard-toolbar.component.dom.test.js +13 -0
- package/dist/lib/whiteboard-toolbar.component.dom.test.js.map +1 -1
- package/dist/lib/whiteboard-toolbar.component.js +18 -3
- package/dist/lib/whiteboard-toolbar.component.js.map +1 -1
- package/dist/public-api.d.ts +1 -0
- package/dist/public-api.d.ts.map +1 -1
- package/dist/public-api.js +2 -0
- package/dist/public-api.js.map +1 -1
- package/package.json +6 -6
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { ChangeDetectorRef, Component, EventEmitter, HostListener, Input, Output, ViewChild, inject } from '@angular/core';
|
|
1
|
+
import { ChangeDetectorRef, Component, ElementRef, EventEmitter, HostBinding, HostListener, Input, Output, ViewChild, inject } from '@angular/core';
|
|
2
2
|
import { CommonModule } from '@angular/common';
|
|
3
3
|
import { WhiteboardState } from './whiteboard-state';
|
|
4
|
+
import { ClampToolToRoster, IsToolAllowed } from './whiteboard-tool-roster';
|
|
4
5
|
import { BuildWhiteboardExportHtml, BuildWhiteboardExportHtmlAllPages, BuildWhiteboardExportSvg, BuildWhiteboardExportSvgPages } from './whiteboard-export';
|
|
5
6
|
import { ApplyWhiteboardAgentTool, WHITEBOARD_TOOL_NAMES } from './whiteboard-tools';
|
|
6
7
|
import { RealtimeWhiteboardBoardComponent } from './whiteboard-board.component';
|
|
@@ -115,6 +116,24 @@ export class RealtimeWhiteboardHostComponent {
|
|
|
115
116
|
this.BoardTitle = 'Whiteboard';
|
|
116
117
|
/** Persistence chip text (e.g. "Saved to session · v14"). */
|
|
117
118
|
this.SavedLabel = 'Saved to session';
|
|
119
|
+
/**
|
|
120
|
+
* Opt back in to DOCUMENT-WIDE keyboard shortcuts.
|
|
121
|
+
*
|
|
122
|
+
* The host binds eleven bare single-character tool shortcuts (v h p r s t m w i c e). While
|
|
123
|
+
* those listened on `document` unconditionally they fired wherever the user was on the page,
|
|
124
|
+
* which fails WCAG 2.1.4 (Character Key Shortcuts): a speech-input user saying an ordinary
|
|
125
|
+
* word, or anyone typing in a non-input control elsewhere, silently switched the board tool.
|
|
126
|
+
* They are now scoped to focus being inside this host, which satisfies the criterion's
|
|
127
|
+
* "active only on focus" branch.
|
|
128
|
+
*
|
|
129
|
+
* Scoping covers the host's ENTIRE keydown handler, not only the letters: undo/redo
|
|
130
|
+
* (Cmd/Ctrl+Z, +Y), Escape and Delete/Backspace are focus-gated as well.
|
|
131
|
+
*
|
|
132
|
+
* Set `true` only for a surface where the whiteboard is the entire page AND the 2.1.4
|
|
133
|
+
* exposure has been accepted — it is an accessibility regression, not a convenience flag.
|
|
134
|
+
*/
|
|
135
|
+
this.EnableGlobalShortcuts = false;
|
|
136
|
+
this._toolRoster = null;
|
|
118
137
|
/** Debounced (750 ms), coalesced scene-delta JSON — the live perception feed. */
|
|
119
138
|
this.SceneDelta = new EventEmitter();
|
|
120
139
|
/** The user clicked Undo on the agent-action toast (the undo itself already applied). */
|
|
@@ -167,8 +186,7 @@ export class RealtimeWhiteboardHostComponent {
|
|
|
167
186
|
this.ItemRemoving = new EventEmitter();
|
|
168
187
|
/** AFTER mirror of {@link WhiteboardState.ItemRemoved$}. */
|
|
169
188
|
this.ItemRemoved = new EventEmitter();
|
|
170
|
-
|
|
171
|
-
this.Tool = 'select';
|
|
189
|
+
this._tool = 'select';
|
|
172
190
|
this.PenColor = WHITEBOARD_PEN_COLORS[0];
|
|
173
191
|
this.PenWidth = 4;
|
|
174
192
|
this.ShapeKind = 'rect';
|
|
@@ -196,6 +214,68 @@ export class RealtimeWhiteboardHostComponent {
|
|
|
196
214
|
this.ticker = null;
|
|
197
215
|
this.mirrorSubs = [];
|
|
198
216
|
this.cdr = inject(ChangeDetectorRef);
|
|
217
|
+
// ────────────────────────────────────────────── keyboard shortcuts
|
|
218
|
+
/**
|
|
219
|
+
* The board is made focusable so its shortcuts have a focus scope to be bound to. `-1`, not
|
|
220
|
+
* `0`: the canvas has no keyboard interaction model yet (see the whiteboard keyboard-
|
|
221
|
+
* operability work), so adding a real Tab stop would put a stop in the page that a keyboard
|
|
222
|
+
* user can reach and do nothing with. Click focuses it; that is the whole contract today.
|
|
223
|
+
*/
|
|
224
|
+
this.HostTabIndex = '-1';
|
|
225
|
+
this.host = inject(ElementRef);
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Which tools this surface offers. `null` (default) is all eleven: today's rendering.
|
|
229
|
+
*
|
|
230
|
+
* The roster governs which tools are AVAILABLE, closing every door to a tool it leaves out:
|
|
231
|
+
* the toolbar button, the single-letter shortcut, and the canvas "add … here" action. It is
|
|
232
|
+
* deliberately not a content policy — it does not restrict what already exists on the board,
|
|
233
|
+
* what the agent places, or authoring on existing items (Restyle, Duplicate, paste). An empty
|
|
234
|
+
* roster narrows the palette to nothing; it does NOT make the board read-only, which is a
|
|
235
|
+
* separate input.
|
|
236
|
+
*
|
|
237
|
+
* A setter rather than `ngOnChanges` (packages/Angular/CLAUDE.md): a realtime channel's
|
|
238
|
+
* `BindSurface` assigns this property directly on a dynamically-created component, and
|
|
239
|
+
* Angular fires no `ngOnChanges` for a plain assignment, so a clamp implemented there would
|
|
240
|
+
* silently never run on the path real consumers use.
|
|
241
|
+
*/
|
|
242
|
+
set ToolRoster(value) {
|
|
243
|
+
const next = value ?? null;
|
|
244
|
+
// Identity compare, per the Angular rule: a consumer binding a freshly-built array literal
|
|
245
|
+
// re-fires this setter every change-detection pass, and re-running the clamp on every pass
|
|
246
|
+
// would fight the user for the active tool.
|
|
247
|
+
if (next === this._toolRoster) {
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
this._toolRoster = next;
|
|
251
|
+
// Re-validate the CURRENT tool through the same chokepoint every other write uses.
|
|
252
|
+
this.Tool = this._tool;
|
|
253
|
+
}
|
|
254
|
+
get ToolRoster() {
|
|
255
|
+
return this._toolRoster;
|
|
256
|
+
}
|
|
257
|
+
// tool state (host-owned; toolbar + keyboard drive it, board consumes it)
|
|
258
|
+
/**
|
|
259
|
+
* The active tool, and the ONE place the roster invariant is enforced on a write.
|
|
260
|
+
*
|
|
261
|
+
* "The active tool is always one the roster allows" is a property of every write, not of the
|
|
262
|
+
* handful of call sites that exist today (the roster setter, Escape, the key map, and the two
|
|
263
|
+
* template `Tool = $event` bindings). Guarding each of those would leave the next writer —
|
|
264
|
+
* a new shortcut, a gesture, a host API — to remember the rule. Guarding here closes them all
|
|
265
|
+
* by construction, and makes assignment order irrelevant: setting `ToolRoster` and `Tool` in
|
|
266
|
+
* either order still lands on an allowed tool.
|
|
267
|
+
*
|
|
268
|
+
* A DISALLOWED request is ignored rather than clamped: pressing Escape under `['pen','eraser']`
|
|
269
|
+
* should leave you on pen, not bounce you to the roster's fallback. The clamp is only for the
|
|
270
|
+
* case where the tool you are already holding stopped being allowed.
|
|
271
|
+
*/
|
|
272
|
+
get Tool() {
|
|
273
|
+
return this._tool;
|
|
274
|
+
}
|
|
275
|
+
set Tool(value) {
|
|
276
|
+
this._tool = IsToolAllowed(this._toolRoster, value)
|
|
277
|
+
? value
|
|
278
|
+
: ClampToolToRoster(this._tool, this._toolRoster);
|
|
199
279
|
}
|
|
200
280
|
ngOnInit() {
|
|
201
281
|
this.changedSub = this.State.Changed$.subscribe((change) => {
|
|
@@ -370,8 +450,34 @@ export class RealtimeWhiteboardHostComponent {
|
|
|
370
450
|
this.hideToast();
|
|
371
451
|
this.AgentUndo.emit();
|
|
372
452
|
}
|
|
373
|
-
|
|
453
|
+
/**
|
|
454
|
+
* Clicking the board takes focus, so the shortcuts below apply. The browser would usually do
|
|
455
|
+
* this on its own by focusing the nearest focusable ancestor, but the board's pointer
|
|
456
|
+
* pipeline is elaborate enough (drag, marquee, handles, transient shapes) that relying on
|
|
457
|
+
* that default is fragile — one `preventDefault()` added later would silently kill every
|
|
458
|
+
* shortcut. This makes it explicit.
|
|
459
|
+
*/
|
|
460
|
+
OnHostPointerDown() {
|
|
461
|
+
if (!this.hasFocusWithin()) {
|
|
462
|
+
this.host.nativeElement.focus({ preventScroll: true });
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
/** Whether focus currently sits on, or inside, this whiteboard host. */
|
|
466
|
+
hasFocusWithin() {
|
|
467
|
+
const el = this.host.nativeElement;
|
|
468
|
+
const active = document.activeElement;
|
|
469
|
+
return !!active && (active === el || el.contains(active));
|
|
470
|
+
}
|
|
374
471
|
OnKeydown(event) {
|
|
472
|
+
// WCAG 2.1.4: only live while focus is on the board. NOTE this gates the WHOLE handler,
|
|
473
|
+
// not just the single-character tool keys that 2.1.4 is about — undo/redo (Cmd/Ctrl+Z,
|
|
474
|
+
// +Y), Escape and Delete/Backspace are scoped too. That is deliberate: a board that
|
|
475
|
+
// swallows the document's Cmd+Z from anywhere on the page is its own bug, and "the board
|
|
476
|
+
// responds to keys when you are on the board" is the only model that stays predictable.
|
|
477
|
+
// See {@link EnableGlobalShortcuts} for the (discouraged) opt-out.
|
|
478
|
+
if (!this.EnableGlobalShortcuts && !this.hasFocusWithin()) {
|
|
479
|
+
return;
|
|
480
|
+
}
|
|
375
481
|
const target = event.target;
|
|
376
482
|
if (target && (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA' || target.isContentEditable)) {
|
|
377
483
|
return;
|
|
@@ -410,6 +516,8 @@ export class RealtimeWhiteboardHostComponent {
|
|
|
410
516
|
break;
|
|
411
517
|
default: {
|
|
412
518
|
const tool = RealtimeWhiteboardHostComponent.toolForKey(event.key);
|
|
519
|
+
// No roster check here: the Tool setter ignores a disallowed write, so a key for a
|
|
520
|
+
// hidden tool is a no-op by construction rather than by a remembered guard.
|
|
413
521
|
if (tool) {
|
|
414
522
|
this.Tool = tool;
|
|
415
523
|
}
|
|
@@ -546,9 +654,11 @@ export class RealtimeWhiteboardHostComponent {
|
|
|
546
654
|
} if (rf & 2) {
|
|
547
655
|
let _t;
|
|
548
656
|
i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.Board = _t.first);
|
|
549
|
-
} }, hostBindings: function RealtimeWhiteboardHostComponent_HostBindings(rf, ctx) { if (rf & 1) {
|
|
550
|
-
i0.ɵɵlistener("click", function RealtimeWhiteboardHostComponent_click_HostBindingHandler() { return ctx.CloseExport(); }, i0.ɵɵresolveDocument)("keydown", function RealtimeWhiteboardHostComponent_keydown_HostBindingHandler($event) { return ctx.OnKeydown($event); }, i0.ɵɵresolveDocument);
|
|
551
|
-
}
|
|
657
|
+
} }, hostVars: 1, hostBindings: function RealtimeWhiteboardHostComponent_HostBindings(rf, ctx) { if (rf & 1) {
|
|
658
|
+
i0.ɵɵlistener("click", function RealtimeWhiteboardHostComponent_click_HostBindingHandler() { return ctx.CloseExport(); }, i0.ɵɵresolveDocument)("pointerdown", function RealtimeWhiteboardHostComponent_pointerdown_HostBindingHandler() { return ctx.OnHostPointerDown(); })("keydown", function RealtimeWhiteboardHostComponent_keydown_HostBindingHandler($event) { return ctx.OnKeydown($event); }, i0.ɵɵresolveDocument);
|
|
659
|
+
} if (rf & 2) {
|
|
660
|
+
i0.ɵɵattribute("tabindex", ctx.HostTabIndex);
|
|
661
|
+
} }, inputs: { State: "State", AgentName: "AgentName", BoardTitle: "BoardTitle", SavedLabel: "SavedLabel", EnableGlobalShortcuts: "EnableGlobalShortcuts", ToolRoster: "ToolRoster" }, outputs: { SceneDelta: "SceneDelta", AgentUndo: "AgentUndo", FocusModeChange: "FocusModeChange", SaveToArtifactsRequested: "SaveToArtifactsRequested", WidgetSubmitting: "WidgetSubmitting", WidgetSubmitted: "WidgetSubmitted", WidgetInteraction: "WidgetInteraction", ContentApplying: "ContentApplying", ContentApplied: "ContentApplied", ItemAdding: "ItemAdding", ItemAdded: "ItemAdded", ItemUpdating: "ItemUpdating", ItemUpdated: "ItemUpdated", ItemRemoving: "ItemRemoving", ItemRemoved: "ItemRemoved" }, decls: 70, vars: 50, consts: [["board", ""], [1, "board-head"], [1, "bt"], [1, "fa-solid", "fa-chalkboard"], [1, "board-save"], [1, "fa-solid", "fa-circle-check"], [1, "board-legend"], [1, "sw", "sw--you"], [1, "sw", "sw--sage"], [1, "fa-solid", "fa-feather", "legend-feather"], [1, "head-end"], ["type", "button", 1, "head-btn", "head-btn--sees", 3, "click"], [1, "eye-pulse"], [1, "fa-regular", "fa-eye"], [3, "AgentName", "DeltaJson", "DebounceMs", "ElementCount", "LastDeltaLabel"], [1, "export-wrap"], ["type", "button", 1, "head-btn", 3, "click"], [1, "fa-solid", "fa-file-export"], [1, "export-menu"], ["type", "button", "title", "More", 1, "head-btn", "head-btn--more"], [1, "fa-solid", "fa-ellipsis"], [1, "board-wrap"], [3, "ToolChangeRequest", "WidgetSubmitting", "WidgetSubmitted", "WidgetInteraction", "ContentApplying", "ContentApplied", "State", "AgentName", "Tool", "ToolRoster", "PenColor", "PenWidth", "ShapeKind", "TextSize", "TextFamily", "TextBold", "TextColor", "AgentPresence"], [3, "ToolChange", "PenColorChange", "PenWidthChange", "ShapeKindChange", "TextSizeChange", "TextFamilyChange", "TextBoldChange", "TextColorChange", "StyleSelection", "Undo", "Redo", "ActiveTool", "ToolRoster", "PenColor", "PenWidth", "ShapeKind", "TextSize", "TextFamily", "TextBold", "TextColor", "CanUndo", "CanRedo", "AgentName"], [3, "ZoomOut", "ZoomIn", "ZoomBy", "Fit", "ToggleMinimap", "ZoomPercent", "MinimapOpen"], [1, "board-toast"], [1, "board-status"], [1, "stat"], [1, "seg-r"], [1, "stat", "ctx-live", 3, "title"], [1, "d"], [1, "ctx-live__text"], ["title", "Persists as a session artifact when the call ends", 1, "stat", "persist"], ["aria-hidden", "true", 1, "fa-solid", "fa-box-archive"], [1, "persist__long"], [1, "persist__short"], ["type", "button", 1, "export-item", 3, "click"], [1, "fa-solid", "fa-file-code"], ["type", "button", 1, "export-item"], [1, "fa-solid", "fa-bezier-curve"], ["type", "button", "title", "One SVG file per page", 1, "export-item"], [1, "fa-solid", "fa-print"], [1, "fa-solid", "fa-box-archive"], ["type", "button", "title", "One SVG file per page", 1, "export-item", 3, "click"], [1, "fa-solid", "fa-feather"], [1, "board-toast__text"], [1, "undo", 3, "click"], [1, "fa-solid", "fa-rotate-left"]], template: function RealtimeWhiteboardHostComponent_Template(rf, ctx) { if (rf & 1) {
|
|
552
662
|
const _r1 = i0.ɵɵgetCurrentView();
|
|
553
663
|
i0.ɵɵelementStart(0, "div", 1)(1, "span", 2);
|
|
554
664
|
i0.ɵɵelement(2, "i", 3);
|
|
@@ -664,9 +774,9 @@ export class RealtimeWhiteboardHostComponent {
|
|
|
664
774
|
i0.ɵɵadvance();
|
|
665
775
|
i0.ɵɵtextInterpolate1(" ", ctx.FocusMode ? "Show thread" : "Focus board", " ");
|
|
666
776
|
i0.ɵɵadvance(4);
|
|
667
|
-
i0.ɵɵproperty("State", ctx.State)("AgentName", ctx.AgentName)("Tool", ctx.Tool)("PenColor", ctx.PenColor)("PenWidth", ctx.PenWidth)("ShapeKind", ctx.ShapeKind)("TextSize", ctx.TextSize)("TextFamily", ctx.TextFamily)("TextBold", ctx.TextBold)("TextColor", ctx.TextColor)("AgentPresence", ctx.AgentPresence);
|
|
777
|
+
i0.ɵɵproperty("State", ctx.State)("AgentName", ctx.AgentName)("Tool", ctx.Tool)("ToolRoster", ctx.ToolRoster)("PenColor", ctx.PenColor)("PenWidth", ctx.PenWidth)("ShapeKind", ctx.ShapeKind)("TextSize", ctx.TextSize)("TextFamily", ctx.TextFamily)("TextBold", ctx.TextBold)("TextColor", ctx.TextColor)("AgentPresence", ctx.AgentPresence);
|
|
668
778
|
i0.ɵɵadvance(2);
|
|
669
|
-
i0.ɵɵproperty("ActiveTool", ctx.Tool)("PenColor", ctx.PenColor)("PenWidth", ctx.PenWidth)("ShapeKind", ctx.ShapeKind)("TextSize", ctx.TextSize)("TextFamily", ctx.TextFamily)("TextBold", ctx.TextBold)("TextColor", ctx.TextColor)("CanUndo", ctx.State.CanUndo)("CanRedo", ctx.State.CanRedo)("AgentName", ctx.AgentName);
|
|
779
|
+
i0.ɵɵproperty("ActiveTool", ctx.Tool)("ToolRoster", ctx.ToolRoster)("PenColor", ctx.PenColor)("PenWidth", ctx.PenWidth)("ShapeKind", ctx.ShapeKind)("TextSize", ctx.TextSize)("TextFamily", ctx.TextFamily)("TextBold", ctx.TextBold)("TextColor", ctx.TextColor)("CanUndo", ctx.State.CanUndo)("CanRedo", ctx.State.CanRedo)("AgentName", ctx.AgentName);
|
|
670
780
|
i0.ɵɵadvance();
|
|
671
781
|
i0.ɵɵproperty("ZoomPercent", board_r6.ZoomPercent)("MinimapOpen", board_r6.MinimapOpen);
|
|
672
782
|
i0.ɵɵadvance();
|
|
@@ -691,7 +801,7 @@ export class RealtimeWhiteboardHostComponent {
|
|
|
691
801
|
RealtimeWhiteboardBoardComponent,
|
|
692
802
|
RealtimeWhiteboardToolbarComponent,
|
|
693
803
|
RealtimeWhiteboardZoomComponent,
|
|
694
|
-
RealtimeWhiteboardAgentSeesPopoverComponent], styles: ["\n\n\n\n\n[_nghost-%COMP%] {\n \n\n --sage: var(--mj-color-violet-400, #a78bfa);\n --sage-bg: color-mix(in srgb, var(--mj-color-violet-400, #a78bfa) 14%, transparent);\n --sage-border: color-mix(in srgb, var(--mj-color-violet-400, #a78bfa) 45%, transparent);\n --sage-chip-bg: color-mix(in srgb, var(--mj-color-violet-500, #8b5cf6) 32%, var(--mj-bg-surface-sunken));\n\n display: flex;\n flex-direction: column;\n flex: 1;\n min-height: 0;\n height: 100%;\n \n\n container-type: inline-size;\n container-name: wb-host;\n}\n\n\n\n.board-head[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 12px;\n padding: 9px 16px;\n border-bottom: 1px solid var(--mj-border-default);\n background: var(--mj-bg-surface);\n flex-shrink: 0;\n flex-wrap: wrap;\n}\n.board-head[_ngcontent-%COMP%] .bt[_ngcontent-%COMP%] {\n font-weight: 700;\n font-size: 13.5px;\n display: flex;\n align-items: center;\n gap: 8px;\n color: var(--mj-text-primary);\n}\n.board-head[_ngcontent-%COMP%] .bt[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n color: var(--mj-brand-accent, var(--mj-brand-primary));\n font-size: 13px;\n}\n.board-save[_ngcontent-%COMP%] {\n font-size: 11px;\n color: var(--mj-text-muted);\n display: inline-flex;\n align-items: center;\n gap: 5px;\n}\n.board-save[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n color: var(--mj-status-success);\n font-size: 10px;\n}\n\n.board-legend[_ngcontent-%COMP%] {\n display: inline-flex;\n align-items: center;\n gap: 12px;\n font-size: 11px;\n color: var(--mj-text-muted);\n margin-left: 6px;\n padding: 4px 10px;\n border: 1px solid var(--mj-border-default);\n border-radius: var(--mj-radius-full, 9999px);\n background: var(--mj-bg-surface-card);\n}\n.board-legend[_ngcontent-%COMP%] .sw[_ngcontent-%COMP%] {\n width: 10px;\n height: 10px;\n border-radius: 3px;\n display: inline-block;\n vertical-align: -1px;\n margin-right: 5px;\n}\n.board-legend[_ngcontent-%COMP%] .sw--you[_ngcontent-%COMP%] {\n background: var(--mj-bg-surface-elevated);\n border: 1px solid var(--mj-border-strong);\n}\n.board-legend[_ngcontent-%COMP%] .sw--sage[_ngcontent-%COMP%] {\n background: var(--sage-bg);\n border: 1px solid var(--sage);\n}\n.legend-feather[_ngcontent-%COMP%] {\n font-size: 9px;\n color: var(--sage);\n}\n\n.head-end[_ngcontent-%COMP%] {\n margin-left: auto;\n display: flex;\n align-items: center;\n gap: 8px;\n position: relative;\n}\n\n.head-btn[_ngcontent-%COMP%] {\n display: inline-flex;\n align-items: center;\n gap: 7px;\n padding: 5px 11px;\n font-size: 11.5px;\n font-weight: 600;\n border: 1px solid var(--mj-border-default);\n border-radius: var(--mj-radius-full, 9999px);\n cursor: pointer;\n background: var(--mj-bg-surface-card);\n color: var(--mj-text-secondary);\n transition: all 140ms ease;\n font-family: inherit;\n}\n.head-btn[_ngcontent-%COMP%]:hover {\n color: var(--mj-text-primary);\n border-color: var(--mj-border-strong);\n}\n.head-btn.on[_ngcontent-%COMP%] {\n background: color-mix(in srgb, var(--mj-brand-primary) 14%, transparent);\n border-color: var(--mj-brand-primary);\n color: var(--mj-brand-accent, var(--mj-brand-primary));\n}\n.head-btn--sees[_ngcontent-%COMP%] {\n position: relative;\n}\n.head-btn--sees[_ngcontent-%COMP%] .eye-pulse[_ngcontent-%COMP%] {\n width: 6px;\n height: 6px;\n border-radius: 50%;\n background: var(--sage);\n box-shadow: 0 0 0 0 color-mix(in srgb, var(--sage) 50%, transparent);\n animation: _ngcontent-%COMP%_wb-pulse-ring 2s infinite;\n}\n.head-btn--more[_ngcontent-%COMP%] {\n padding: 5px 9px;\n}\n\n\n\n.export-wrap[_ngcontent-%COMP%] {\n position: relative;\n display: inline-flex;\n}\n.export-menu[_ngcontent-%COMP%] {\n position: absolute;\n top: calc(100% + 6px);\n right: 0;\n z-index: 30;\n min-width: 168px;\n display: flex;\n flex-direction: column;\n padding: 5px;\n border: 1px solid var(--mj-border-default);\n border-radius: var(--mj-radius-md, 8px);\n background: var(--mj-bg-surface-elevated);\n box-shadow: var(--mj-shadow-lg, 0 10px 15px -3px rgba(0, 0, 0, 0.3));\n}\n.export-item[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 9px;\n padding: 7px 10px;\n font-size: 11.5px;\n font-weight: 600;\n font-family: inherit;\n text-align: left;\n border: none;\n border-radius: var(--mj-radius-sm, 5px);\n background: transparent;\n color: var(--mj-text-secondary);\n cursor: pointer;\n white-space: nowrap;\n}\n.export-item[_ngcontent-%COMP%]:hover {\n background: var(--mj-bg-surface-hover);\n color: var(--mj-text-primary);\n}\n.export-item[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n width: 14px;\n text-align: center;\n color: var(--mj-text-muted);\n}\n.export-item[_ngcontent-%COMP%]:hover i[_ngcontent-%COMP%] {\n color: var(--mj-brand-accent, var(--mj-brand-primary));\n}\n@keyframes _ngcontent-%COMP%_wb-pulse-ring {\n 0% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--sage) 50%, transparent); }\n 70% { box-shadow: 0 0 0 6px transparent; }\n 100% { box-shadow: 0 0 0 0 transparent; }\n}\n\n\n\n.board-wrap[_ngcontent-%COMP%] {\n position: relative;\n flex: 1;\n min-height: 0;\n overflow: hidden;\n}\n\n\n\n\n\n\n.board-toast[_ngcontent-%COMP%] {\n position: absolute;\n left: 50%;\n bottom: 64px;\n transform: translateX(-50%);\n z-index: 9;\n display: flex;\n align-items: center;\n gap: 9px;\n padding: 8px 16px;\n max-width: calc(100% - 32px);\n border-radius: var(--mj-radius-full, 9999px);\n background: var(--sage-chip-bg);\n border: 1px solid var(--sage-border);\n color: var(--mj-color-violet-300, #c4b5fd);\n font-size: 12px;\n font-weight: 600;\n box-shadow: var(--mj-shadow-lg, 0 10px 15px -3px rgba(0, 0, 0, 0.3));\n animation: _ngcontent-%COMP%_wb-toast-fade 300ms ease both;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n.board-toast[_ngcontent-%COMP%] > i[_ngcontent-%COMP%] {\n flex-shrink: 0;\n}\n.board-toast__text[_ngcontent-%COMP%] {\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n.board-toast[_ngcontent-%COMP%] .undo[_ngcontent-%COMP%] {\n flex-shrink: 0;\n}\n@keyframes _ngcontent-%COMP%_wb-toast-fade {\n from { opacity: 0; transform: translate(-50%, 4px); }\n to { opacity: 1; transform: translate(-50%, 0); }\n}\n.board-toast[_ngcontent-%COMP%] .undo[_ngcontent-%COMP%] {\n color: var(--mj-text-muted);\n font-weight: 600;\n cursor: pointer;\n margin-left: 4px;\n}\n.board-toast[_ngcontent-%COMP%] .undo[_ngcontent-%COMP%]:hover {\n color: var(--mj-text-primary);\n}\n\n\n\n.board-status[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 18px;\n padding: 6px 16px;\n flex-shrink: 0;\n border-top: 1px solid var(--mj-border-default);\n background: var(--mj-bg-surface);\n font-size: 10.5px;\n color: var(--mj-text-muted);\n font-weight: 500;\n}\n.board-status[_ngcontent-%COMP%] b[_ngcontent-%COMP%] {\n color: var(--mj-text-secondary);\n font-weight: 600;\n}\n.board-status[_ngcontent-%COMP%] .seg-r[_ngcontent-%COMP%] {\n margin-left: auto;\n display: flex;\n align-items: center;\n gap: 18px;\n}\n.board-status[_ngcontent-%COMP%] .ctx-live[_ngcontent-%COMP%] {\n color: var(--mj-color-violet-300, var(--mj-text-secondary));\n display: inline-flex;\n align-items: center;\n gap: 6px;\n font-weight: 600;\n}\n.board-status[_ngcontent-%COMP%] .ctx-live[_ngcontent-%COMP%] .d[_ngcontent-%COMP%] {\n width: 6px;\n height: 6px;\n border-radius: 50%;\n background: var(--sage);\n animation: _ngcontent-%COMP%_wb-pulse-ring 2s infinite;\n}"] }); }
|
|
804
|
+
RealtimeWhiteboardAgentSeesPopoverComponent], styles: ["\n\n\n\n[_nghost-%COMP%]:focus:not(:focus-visible) {\n outline: none;\n}\n\n\n\n\n\n\n[_nghost-%COMP%] {\n \n\n --sage: var(--mj-color-violet-400, #a78bfa);\n --sage-bg: color-mix(in srgb, var(--mj-color-violet-400, #a78bfa) 14%, transparent);\n --sage-border: color-mix(in srgb, var(--mj-color-violet-400, #a78bfa) 45%, transparent);\n --sage-chip-bg: color-mix(in srgb, var(--mj-color-violet-500, #8b5cf6) 32%, var(--mj-bg-surface-sunken));\n\n display: flex;\n flex-direction: column;\n flex: 1;\n min-height: 0;\n height: 100%;\n \n\n container-type: inline-size;\n container-name: wb-host;\n}\n\n\n\n.board-head[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 12px;\n padding: 9px 16px;\n border-bottom: 1px solid var(--mj-border-default);\n background: var(--mj-bg-surface);\n flex-shrink: 0;\n flex-wrap: wrap;\n}\n.board-head[_ngcontent-%COMP%] .bt[_ngcontent-%COMP%] {\n font-weight: 700;\n font-size: 13.5px;\n display: flex;\n align-items: center;\n gap: 8px;\n color: var(--mj-text-primary);\n}\n.board-head[_ngcontent-%COMP%] .bt[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n color: var(--mj-brand-accent, var(--mj-brand-primary));\n font-size: 13px;\n}\n.board-save[_ngcontent-%COMP%] {\n font-size: 11px;\n color: var(--mj-text-muted);\n display: inline-flex;\n align-items: center;\n gap: 5px;\n}\n.board-save[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n color: var(--mj-status-success);\n font-size: 10px;\n}\n\n.board-legend[_ngcontent-%COMP%] {\n display: inline-flex;\n align-items: center;\n gap: 12px;\n font-size: 11px;\n color: var(--mj-text-muted);\n margin-left: 6px;\n padding: 4px 10px;\n border: 1px solid var(--mj-border-default);\n border-radius: var(--mj-radius-full, 9999px);\n background: var(--mj-bg-surface-card);\n}\n.board-legend[_ngcontent-%COMP%] .sw[_ngcontent-%COMP%] {\n width: 10px;\n height: 10px;\n border-radius: 3px;\n display: inline-block;\n vertical-align: -1px;\n margin-right: 5px;\n}\n.board-legend[_ngcontent-%COMP%] .sw--you[_ngcontent-%COMP%] {\n background: var(--mj-bg-surface-elevated);\n border: 1px solid var(--mj-border-strong);\n}\n.board-legend[_ngcontent-%COMP%] .sw--sage[_ngcontent-%COMP%] {\n background: var(--sage-bg);\n border: 1px solid var(--sage);\n}\n.legend-feather[_ngcontent-%COMP%] {\n font-size: 9px;\n color: var(--sage);\n}\n\n.head-end[_ngcontent-%COMP%] {\n margin-left: auto;\n display: flex;\n align-items: center;\n gap: 8px;\n position: relative;\n}\n\n.head-btn[_ngcontent-%COMP%] {\n display: inline-flex;\n align-items: center;\n gap: 7px;\n padding: 5px 11px;\n font-size: 11.5px;\n font-weight: 600;\n border: 1px solid var(--mj-border-default);\n border-radius: var(--mj-radius-full, 9999px);\n cursor: pointer;\n background: var(--mj-bg-surface-card);\n color: var(--mj-text-secondary);\n transition: all 140ms ease;\n font-family: inherit;\n}\n.head-btn[_ngcontent-%COMP%]:hover {\n color: var(--mj-text-primary);\n border-color: var(--mj-border-strong);\n}\n.head-btn.on[_ngcontent-%COMP%] {\n background: color-mix(in srgb, var(--mj-brand-primary) 14%, transparent);\n border-color: var(--mj-brand-primary);\n color: var(--mj-brand-accent, var(--mj-brand-primary));\n}\n.head-btn--sees[_ngcontent-%COMP%] {\n position: relative;\n}\n.head-btn--sees[_ngcontent-%COMP%] .eye-pulse[_ngcontent-%COMP%] {\n width: 6px;\n height: 6px;\n border-radius: 50%;\n background: var(--sage);\n box-shadow: 0 0 0 0 color-mix(in srgb, var(--sage) 50%, transparent);\n animation: _ngcontent-%COMP%_wb-pulse-ring 2s infinite;\n}\n.head-btn--more[_ngcontent-%COMP%] {\n padding: 5px 9px;\n}\n\n\n\n.export-wrap[_ngcontent-%COMP%] {\n position: relative;\n display: inline-flex;\n}\n.export-menu[_ngcontent-%COMP%] {\n position: absolute;\n top: calc(100% + 6px);\n right: 0;\n z-index: 30;\n min-width: 168px;\n display: flex;\n flex-direction: column;\n padding: 5px;\n border: 1px solid var(--mj-border-default);\n border-radius: var(--mj-radius-md, 8px);\n background: var(--mj-bg-surface-elevated);\n box-shadow: var(--mj-shadow-lg, 0 10px 15px -3px rgba(0, 0, 0, 0.3));\n}\n.export-item[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 9px;\n padding: 7px 10px;\n font-size: 11.5px;\n font-weight: 600;\n font-family: inherit;\n text-align: left;\n border: none;\n border-radius: var(--mj-radius-sm, 5px);\n background: transparent;\n color: var(--mj-text-secondary);\n cursor: pointer;\n white-space: nowrap;\n}\n.export-item[_ngcontent-%COMP%]:hover {\n background: var(--mj-bg-surface-hover);\n color: var(--mj-text-primary);\n}\n.export-item[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n width: 14px;\n text-align: center;\n color: var(--mj-text-muted);\n}\n.export-item[_ngcontent-%COMP%]:hover i[_ngcontent-%COMP%] {\n color: var(--mj-brand-accent, var(--mj-brand-primary));\n}\n@keyframes _ngcontent-%COMP%_wb-pulse-ring {\n 0% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--sage) 50%, transparent); }\n 70% { box-shadow: 0 0 0 6px transparent; }\n 100% { box-shadow: 0 0 0 0 transparent; }\n}\n\n\n\n.board-wrap[_ngcontent-%COMP%] {\n position: relative;\n flex: 1;\n min-height: 0;\n overflow: hidden;\n}\n\n\n\n\n\n\n.board-toast[_ngcontent-%COMP%] {\n position: absolute;\n left: 50%;\n bottom: 64px;\n transform: translateX(-50%);\n z-index: 9;\n display: flex;\n align-items: center;\n gap: 9px;\n padding: 8px 16px;\n max-width: calc(100% - 32px);\n border-radius: var(--mj-radius-full, 9999px);\n background: var(--sage-chip-bg);\n border: 1px solid var(--sage-border);\n color: var(--mj-color-violet-300, #c4b5fd);\n font-size: 12px;\n font-weight: 600;\n box-shadow: var(--mj-shadow-lg, 0 10px 15px -3px rgba(0, 0, 0, 0.3));\n animation: _ngcontent-%COMP%_wb-toast-fade 300ms ease both;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n.board-toast[_ngcontent-%COMP%] > i[_ngcontent-%COMP%] {\n flex-shrink: 0;\n}\n.board-toast__text[_ngcontent-%COMP%] {\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n.board-toast[_ngcontent-%COMP%] .undo[_ngcontent-%COMP%] {\n flex-shrink: 0;\n}\n@keyframes _ngcontent-%COMP%_wb-toast-fade {\n from { opacity: 0; transform: translate(-50%, 4px); }\n to { opacity: 1; transform: translate(-50%, 0); }\n}\n.board-toast[_ngcontent-%COMP%] .undo[_ngcontent-%COMP%] {\n color: var(--mj-text-muted);\n font-weight: 600;\n cursor: pointer;\n margin-left: 4px;\n}\n.board-toast[_ngcontent-%COMP%] .undo[_ngcontent-%COMP%]:hover {\n color: var(--mj-text-primary);\n}\n\n\n\n.board-status[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 18px;\n padding: 6px 16px;\n flex-shrink: 0;\n border-top: 1px solid var(--mj-border-default);\n background: var(--mj-bg-surface);\n font-size: 10.5px;\n color: var(--mj-text-muted);\n font-weight: 500;\n}\n.board-status[_ngcontent-%COMP%] b[_ngcontent-%COMP%] {\n color: var(--mj-text-secondary);\n font-weight: 600;\n}\n.board-status[_ngcontent-%COMP%] .seg-r[_ngcontent-%COMP%] {\n margin-left: auto;\n display: flex;\n align-items: center;\n gap: 18px;\n}\n.board-status[_ngcontent-%COMP%] .ctx-live[_ngcontent-%COMP%] {\n color: var(--mj-color-violet-300, var(--mj-text-secondary));\n display: inline-flex;\n align-items: center;\n gap: 6px;\n font-weight: 600;\n}\n.board-status[_ngcontent-%COMP%] .ctx-live[_ngcontent-%COMP%] .d[_ngcontent-%COMP%] {\n width: 6px;\n height: 6px;\n border-radius: 50%;\n background: var(--sage);\n animation: _ngcontent-%COMP%_wb-pulse-ring 2s infinite;\n}"] }); }
|
|
695
805
|
}
|
|
696
806
|
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(RealtimeWhiteboardHostComponent, [{
|
|
697
807
|
type: Component,
|
|
@@ -701,7 +811,7 @@ export class RealtimeWhiteboardHostComponent {
|
|
|
701
811
|
RealtimeWhiteboardToolbarComponent,
|
|
702
812
|
RealtimeWhiteboardZoomComponent,
|
|
703
813
|
RealtimeWhiteboardAgentSeesPopoverComponent
|
|
704
|
-
], template: "<!-- board header -->\n<div class=\"board-head\">\n <span class=\"bt\"><i class=\"fa-solid fa-chalkboard\"></i> {{ BoardTitle }}</span>\n <span class=\"board-save\"><i class=\"fa-solid fa-circle-check\"></i> {{ SavedLabel }}</span>\n <span class=\"board-legend\">\n <span><span class=\"sw sw--you\"></span>You</span>\n <span><span class=\"sw sw--sage\"></span>{{ AgentName }} <i class=\"fa-solid fa-feather legend-feather\"></i></span>\n </span>\n <div class=\"head-end\">\n <button type=\"button\" class=\"head-btn head-btn--sees\" [class.on]=\"SeesOpen\" (click)=\"ToggleSees($event)\">\n <span class=\"eye-pulse\"></span><i class=\"fa-regular fa-eye\"></i> What {{ AgentName }} sees\n @if (SeesOpen) {\n <mj-realtime-whiteboard-agent-sees-popover\n [AgentName]=\"AgentName\"\n [DeltaJson]=\"LastDeltaJson\"\n [DebounceMs]=\"SceneDeltaDebounceMs\"\n [ElementCount]=\"ElementCount\"\n [LastDeltaLabel]=\"LastDeltaLabel\"/>\n }\n </button>\n <span class=\"export-wrap\">\n <button type=\"button\" class=\"head-btn\" [class.on]=\"ExportOpen\" (click)=\"ToggleExport($event)\">\n <i class=\"fa-solid fa-file-export\"></i> Export\n </button>\n @if (ExportOpen) {\n <div class=\"export-menu\">\n <button type=\"button\" class=\"export-item\" (click)=\"ExportDownloadHtml()\">\n <i class=\"fa-solid fa-file-code\"></i> Download HTML\n </button>\n @if (HasMultiplePages) {\n <button type=\"button\" class=\"export-item\" (click)=\"ExportDownloadHtmlAllPages()\">\n <i class=\"fa-solid fa-file-code\"></i> Download HTML \u2014 all pages\n </button>\n }\n <button type=\"button\" class=\"export-item\" (click)=\"ExportDownloadSvg()\">\n <i class=\"fa-solid fa-bezier-curve\"></i> Download SVG\n </button>\n @if (HasMultiplePages) {\n <button type=\"button\" class=\"export-item\" title=\"One SVG file per page\"\n (click)=\"ExportDownloadSvgAllPages()\">\n <i class=\"fa-solid fa-bezier-curve\"></i> Download SVG \u2014 all pages\n </button>\n }\n <button type=\"button\" class=\"export-item\" (click)=\"ExportPrint()\">\n <i class=\"fa-solid fa-print\"></i> Print\n </button>\n <button type=\"button\" class=\"export-item\" (click)=\"ExportSaveToArtifacts()\">\n <i class=\"fa-solid fa-box-archive\"></i> Save to artifacts\n </button>\n </div>\n }\n </span>\n <button type=\"button\" class=\"head-btn\" [class.on]=\"FocusMode\" (click)=\"ToggleFocus()\">\n <i [class]=\"FocusMode ? 'fa-solid fa-compress' : 'fa-solid fa-expand'\"></i>\n {{ FocusMode ? 'Show thread' : 'Focus board' }}\n </button>\n <button type=\"button\" class=\"head-btn head-btn--more\" title=\"More\"><i class=\"fa-solid fa-ellipsis\"></i></button>\n </div>\n</div>\n\n<!-- the board -->\n<div class=\"board-wrap\">\n <mj-realtime-whiteboard #board\n [State]=\"State\"\n [AgentName]=\"AgentName\"\n [Tool]=\"Tool\"\n [PenColor]=\"PenColor\"\n [PenWidth]=\"PenWidth\"\n [ShapeKind]=\"ShapeKind\"\n [TextSize]=\"TextSize\"\n [TextFamily]=\"TextFamily\"\n [TextBold]=\"TextBold\"\n [TextColor]=\"TextColor\"\n [AgentPresence]=\"AgentPresence\"\n (ToolChangeRequest)=\"Tool = $event\"\n (WidgetSubmitting)=\"WidgetSubmitting.emit($event)\"\n (WidgetSubmitted)=\"WidgetSubmitted.emit($event)\"\n (WidgetInteraction)=\"WidgetInteraction.emit($event)\"\n (ContentApplying)=\"ContentApplying.emit($event)\"\n (ContentApplied)=\"ContentApplied.emit($event)\"/>\n\n <mj-realtime-whiteboard-toolbar\n [ActiveTool]=\"Tool\"\n [PenColor]=\"PenColor\"\n [PenWidth]=\"PenWidth\"\n [ShapeKind]=\"ShapeKind\"\n [TextSize]=\"TextSize\"\n [TextFamily]=\"TextFamily\"\n [TextBold]=\"TextBold\"\n [TextColor]=\"TextColor\"\n [CanUndo]=\"State.CanUndo\"\n [CanRedo]=\"State.CanRedo\"\n [AgentName]=\"AgentName\"\n (ToolChange)=\"Tool = $event\"\n (PenColorChange)=\"PenColor = $event\"\n (PenWidthChange)=\"PenWidth = $event\"\n (ShapeKindChange)=\"ShapeKind = $event\"\n (TextSizeChange)=\"TextSize = $event\"\n (TextFamilyChange)=\"TextFamily = $event\"\n (TextBoldChange)=\"TextBold = $event\"\n (TextColorChange)=\"TextColor = $event\"\n (StyleSelection)=\"board.ApplyTextStyleToSelection($event)\"\n (Undo)=\"State.Undo()\"\n (Redo)=\"State.Redo()\"/>\n\n <mj-realtime-whiteboard-zoom\n [ZoomPercent]=\"board.ZoomPercent\"\n [MinimapOpen]=\"board.MinimapOpen\"\n (ZoomOut)=\"board.ZoomOut()\"\n (ZoomIn)=\"board.ZoomIn()\"\n (ZoomBy)=\"board.ZoomByFactor($event)\"\n (Fit)=\"board.FitToContent()\"\n (ToggleMinimap)=\"board.ToggleMinimap()\"/>\n\n <!-- agent action toast -->\n @if (ToastText) {\n <div class=\"board-toast\">\n <i class=\"fa-solid fa-feather\"></i>\n <span class=\"board-toast__text\">{{ ToastText }}</span>\n <span class=\"undo\" (click)=\"OnToastUndo($event)\"><i class=\"fa-solid fa-rotate-left\"></i> Undo</span>\n </div>\n }\n</div>\n\n<!-- board status footer: ONE row, never wraps \u2014 items ellipsize, and the long\n persistence prose collapses to an icon chip (full text on its tooltip) when\n the host is narrow (container query on the host width). -->\n<div class=\"board-status\">\n <span class=\"stat\"><b>{{ ElementCount }}</b> elements</span>\n <span class=\"stat\">you <b>{{ UserCount }}</b> \u00B7 {{ AgentName }} <b>{{ AgentCount }}</b></span>\n <span class=\"stat\">synced <b>{{ LastDeltaLabel }}</b> \u00B7 seq <b>{{ CurrentSeq }}</b></span>\n <span class=\"seg-r\">\n <span class=\"stat ctx-live\" [title]=\"AgentName + '\u2019s live context: current'\">\n <span class=\"d\"></span><span class=\"ctx-live__text\"> {{ AgentName }}'s live context: current</span>\n </span>\n <span class=\"stat persist\" title=\"Persists as a session artifact when the call ends\">\n <i class=\"fa-solid fa-box-archive\" aria-hidden=\"true\"></i>\n <span class=\"persist__long\">persists as <b>session artifact</b> when the call ends</span>\n <span class=\"persist__short\">session artifact</span>\n </span>\n </span>\n</div>\n", styles: ["/* LIVE WHITEBOARD host chrome \u2014 board header, board wrap, agent toast, status footer.\n Ported pixel-faithfully from plans/realtime/mockups/whiteboard.html\n (.board-head, .head-btn, .board-legend, .board-toast, .board-status). */\n\n:host {\n /* agent (violet) accent \u2014 same derivation as the sibling realtime components */\n --sage: var(--mj-color-violet-400, #a78bfa);\n --sage-bg: color-mix(in srgb, var(--mj-color-violet-400, #a78bfa) 14%, transparent);\n --sage-border: color-mix(in srgb, var(--mj-color-violet-400, #a78bfa) 45%, transparent);\n --sage-chip-bg: color-mix(in srgb, var(--mj-color-violet-500, #8b5cf6) 32%, var(--mj-bg-surface-sunken));\n\n display: flex;\n flex-direction: column;\n flex: 1;\n min-height: 0;\n height: 100%;\n /* width container \u2014 drives the status footer's narrow-width collapse rules */\n container-type: inline-size;\n container-name: wb-host;\n}\n\n/* ---------- board header row ---------- */\n.board-head {\n display: flex;\n align-items: center;\n gap: 12px;\n padding: 9px 16px;\n border-bottom: 1px solid var(--mj-border-default);\n background: var(--mj-bg-surface);\n flex-shrink: 0;\n flex-wrap: wrap;\n}\n.board-head .bt {\n font-weight: 700;\n font-size: 13.5px;\n display: flex;\n align-items: center;\n gap: 8px;\n color: var(--mj-text-primary);\n}\n.board-head .bt i {\n color: var(--mj-brand-accent, var(--mj-brand-primary));\n font-size: 13px;\n}\n.board-save {\n font-size: 11px;\n color: var(--mj-text-muted);\n display: inline-flex;\n align-items: center;\n gap: 5px;\n}\n.board-save i {\n color: var(--mj-status-success);\n font-size: 10px;\n}\n\n.board-legend {\n display: inline-flex;\n align-items: center;\n gap: 12px;\n font-size: 11px;\n color: var(--mj-text-muted);\n margin-left: 6px;\n padding: 4px 10px;\n border: 1px solid var(--mj-border-default);\n border-radius: var(--mj-radius-full, 9999px);\n background: var(--mj-bg-surface-card);\n}\n.board-legend .sw {\n width: 10px;\n height: 10px;\n border-radius: 3px;\n display: inline-block;\n vertical-align: -1px;\n margin-right: 5px;\n}\n.board-legend .sw--you {\n background: var(--mj-bg-surface-elevated);\n border: 1px solid var(--mj-border-strong);\n}\n.board-legend .sw--sage {\n background: var(--sage-bg);\n border: 1px solid var(--sage);\n}\n.legend-feather {\n font-size: 9px;\n color: var(--sage);\n}\n\n.head-end {\n margin-left: auto;\n display: flex;\n align-items: center;\n gap: 8px;\n position: relative;\n}\n\n.head-btn {\n display: inline-flex;\n align-items: center;\n gap: 7px;\n padding: 5px 11px;\n font-size: 11.5px;\n font-weight: 600;\n border: 1px solid var(--mj-border-default);\n border-radius: var(--mj-radius-full, 9999px);\n cursor: pointer;\n background: var(--mj-bg-surface-card);\n color: var(--mj-text-secondary);\n transition: all 140ms ease;\n font-family: inherit;\n}\n.head-btn:hover {\n color: var(--mj-text-primary);\n border-color: var(--mj-border-strong);\n}\n.head-btn.on {\n background: color-mix(in srgb, var(--mj-brand-primary) 14%, transparent);\n border-color: var(--mj-brand-primary);\n color: var(--mj-brand-accent, var(--mj-brand-primary));\n}\n.head-btn--sees {\n position: relative;\n}\n.head-btn--sees .eye-pulse {\n width: 6px;\n height: 6px;\n border-radius: 50%;\n background: var(--sage);\n box-shadow: 0 0 0 0 color-mix(in srgb, var(--sage) 50%, transparent);\n animation: wb-pulse-ring 2s infinite;\n}\n.head-btn--more {\n padding: 5px 9px;\n}\n\n/* ---------- export menu (Download HTML / Download SVG / Print) ---------- */\n.export-wrap {\n position: relative;\n display: inline-flex;\n}\n.export-menu {\n position: absolute;\n top: calc(100% + 6px);\n right: 0;\n z-index: 30;\n min-width: 168px;\n display: flex;\n flex-direction: column;\n padding: 5px;\n border: 1px solid var(--mj-border-default);\n border-radius: var(--mj-radius-md, 8px);\n background: var(--mj-bg-surface-elevated);\n box-shadow: var(--mj-shadow-lg, 0 10px 15px -3px rgba(0, 0, 0, 0.3));\n}\n.export-item {\n display: flex;\n align-items: center;\n gap: 9px;\n padding: 7px 10px;\n font-size: 11.5px;\n font-weight: 600;\n font-family: inherit;\n text-align: left;\n border: none;\n border-radius: var(--mj-radius-sm, 5px);\n background: transparent;\n color: var(--mj-text-secondary);\n cursor: pointer;\n white-space: nowrap;\n}\n.export-item:hover {\n background: var(--mj-bg-surface-hover);\n color: var(--mj-text-primary);\n}\n.export-item i {\n width: 14px;\n text-align: center;\n color: var(--mj-text-muted);\n}\n.export-item:hover i {\n color: var(--mj-brand-accent, var(--mj-brand-primary));\n}\n@keyframes wb-pulse-ring {\n 0% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--sage) 50%, transparent); }\n 70% { box-shadow: 0 0 0 6px transparent; }\n 100% { box-shadow: 0 0 0 0 transparent; }\n}\n\n/* ---------- the board ---------- */\n.board-wrap {\n position: relative;\n flex: 1;\n min-height: 0;\n overflow: hidden;\n}\n\n/* ---------- agent action toast ----------\n Bottom-center, raised ABOVE the zoom cluster's row (cluster: bottom 14px + ~38px\n tall) so the two can never collide, and width-capped clear of the cluster at the\n board's right edge. Sits just above the status footer per the mockup. */\n.board-toast {\n position: absolute;\n left: 50%;\n bottom: 64px;\n transform: translateX(-50%);\n z-index: 9;\n display: flex;\n align-items: center;\n gap: 9px;\n padding: 8px 16px;\n max-width: calc(100% - 32px);\n border-radius: var(--mj-radius-full, 9999px);\n background: var(--sage-chip-bg);\n border: 1px solid var(--sage-border);\n color: var(--mj-color-violet-300, #c4b5fd);\n font-size: 12px;\n font-weight: 600;\n box-shadow: var(--mj-shadow-lg, 0 10px 15px -3px rgba(0, 0, 0, 0.3));\n animation: wb-toast-fade 300ms ease both;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n.board-toast > i {\n flex-shrink: 0;\n}\n.board-toast__text {\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n.board-toast .undo {\n flex-shrink: 0;\n}\n@keyframes wb-toast-fade {\n from { opacity: 0; transform: translate(-50%, 4px); }\n to { opacity: 1; transform: translate(-50%, 0); }\n}\n.board-toast .undo {\n color: var(--mj-text-muted);\n font-weight: 600;\n cursor: pointer;\n margin-left: 4px;\n}\n.board-toast .undo:hover {\n color: var(--mj-text-primary);\n}\n\n/* ---------- board status footer ---------- */\n.board-status {\n display: flex;\n align-items: center;\n gap: 18px;\n padding: 6px 16px;\n flex-shrink: 0;\n border-top: 1px solid var(--mj-border-default);\n background: var(--mj-bg-surface);\n font-size: 10.5px;\n color: var(--mj-text-muted);\n font-weight: 500;\n}\n.board-status b {\n color: var(--mj-text-secondary);\n font-weight: 600;\n}\n.board-status .seg-r {\n margin-left: auto;\n display: flex;\n align-items: center;\n gap: 18px;\n}\n.board-status .ctx-live {\n color: var(--mj-color-violet-300, var(--mj-text-secondary));\n display: inline-flex;\n align-items: center;\n gap: 6px;\n font-weight: 600;\n}\n.board-status .ctx-live .d {\n width: 6px;\n height: 6px;\n border-radius: 50%;\n background: var(--sage);\n animation: wb-pulse-ring 2s infinite;\n}\n"] }]
|
|
814
|
+
], template: "<!-- board header -->\n<div class=\"board-head\">\n <span class=\"bt\"><i class=\"fa-solid fa-chalkboard\"></i> {{ BoardTitle }}</span>\n <span class=\"board-save\"><i class=\"fa-solid fa-circle-check\"></i> {{ SavedLabel }}</span>\n <span class=\"board-legend\">\n <span><span class=\"sw sw--you\"></span>You</span>\n <span><span class=\"sw sw--sage\"></span>{{ AgentName }} <i class=\"fa-solid fa-feather legend-feather\"></i></span>\n </span>\n <div class=\"head-end\">\n <button type=\"button\" class=\"head-btn head-btn--sees\" [class.on]=\"SeesOpen\" (click)=\"ToggleSees($event)\">\n <span class=\"eye-pulse\"></span><i class=\"fa-regular fa-eye\"></i> What {{ AgentName }} sees\n @if (SeesOpen) {\n <mj-realtime-whiteboard-agent-sees-popover\n [AgentName]=\"AgentName\"\n [DeltaJson]=\"LastDeltaJson\"\n [DebounceMs]=\"SceneDeltaDebounceMs\"\n [ElementCount]=\"ElementCount\"\n [LastDeltaLabel]=\"LastDeltaLabel\"/>\n }\n </button>\n <span class=\"export-wrap\">\n <button type=\"button\" class=\"head-btn\" [class.on]=\"ExportOpen\" (click)=\"ToggleExport($event)\">\n <i class=\"fa-solid fa-file-export\"></i> Export\n </button>\n @if (ExportOpen) {\n <div class=\"export-menu\">\n <button type=\"button\" class=\"export-item\" (click)=\"ExportDownloadHtml()\">\n <i class=\"fa-solid fa-file-code\"></i> Download HTML\n </button>\n @if (HasMultiplePages) {\n <button type=\"button\" class=\"export-item\" (click)=\"ExportDownloadHtmlAllPages()\">\n <i class=\"fa-solid fa-file-code\"></i> Download HTML \u2014 all pages\n </button>\n }\n <button type=\"button\" class=\"export-item\" (click)=\"ExportDownloadSvg()\">\n <i class=\"fa-solid fa-bezier-curve\"></i> Download SVG\n </button>\n @if (HasMultiplePages) {\n <button type=\"button\" class=\"export-item\" title=\"One SVG file per page\"\n (click)=\"ExportDownloadSvgAllPages()\">\n <i class=\"fa-solid fa-bezier-curve\"></i> Download SVG \u2014 all pages\n </button>\n }\n <button type=\"button\" class=\"export-item\" (click)=\"ExportPrint()\">\n <i class=\"fa-solid fa-print\"></i> Print\n </button>\n <button type=\"button\" class=\"export-item\" (click)=\"ExportSaveToArtifacts()\">\n <i class=\"fa-solid fa-box-archive\"></i> Save to artifacts\n </button>\n </div>\n }\n </span>\n <button type=\"button\" class=\"head-btn\" [class.on]=\"FocusMode\" (click)=\"ToggleFocus()\">\n <i [class]=\"FocusMode ? 'fa-solid fa-compress' : 'fa-solid fa-expand'\"></i>\n {{ FocusMode ? 'Show thread' : 'Focus board' }}\n </button>\n <button type=\"button\" class=\"head-btn head-btn--more\" title=\"More\"><i class=\"fa-solid fa-ellipsis\"></i></button>\n </div>\n</div>\n\n<!-- the board -->\n<div class=\"board-wrap\">\n <mj-realtime-whiteboard #board\n [State]=\"State\"\n [AgentName]=\"AgentName\"\n [Tool]=\"Tool\"\n [ToolRoster]=\"ToolRoster\"\n [PenColor]=\"PenColor\"\n [PenWidth]=\"PenWidth\"\n [ShapeKind]=\"ShapeKind\"\n [TextSize]=\"TextSize\"\n [TextFamily]=\"TextFamily\"\n [TextBold]=\"TextBold\"\n [TextColor]=\"TextColor\"\n [AgentPresence]=\"AgentPresence\"\n (ToolChangeRequest)=\"Tool = $event\"\n (WidgetSubmitting)=\"WidgetSubmitting.emit($event)\"\n (WidgetSubmitted)=\"WidgetSubmitted.emit($event)\"\n (WidgetInteraction)=\"WidgetInteraction.emit($event)\"\n (ContentApplying)=\"ContentApplying.emit($event)\"\n (ContentApplied)=\"ContentApplied.emit($event)\"/>\n\n <mj-realtime-whiteboard-toolbar\n [ActiveTool]=\"Tool\"\n [ToolRoster]=\"ToolRoster\"\n [PenColor]=\"PenColor\"\n [PenWidth]=\"PenWidth\"\n [ShapeKind]=\"ShapeKind\"\n [TextSize]=\"TextSize\"\n [TextFamily]=\"TextFamily\"\n [TextBold]=\"TextBold\"\n [TextColor]=\"TextColor\"\n [CanUndo]=\"State.CanUndo\"\n [CanRedo]=\"State.CanRedo\"\n [AgentName]=\"AgentName\"\n (ToolChange)=\"Tool = $event\"\n (PenColorChange)=\"PenColor = $event\"\n (PenWidthChange)=\"PenWidth = $event\"\n (ShapeKindChange)=\"ShapeKind = $event\"\n (TextSizeChange)=\"TextSize = $event\"\n (TextFamilyChange)=\"TextFamily = $event\"\n (TextBoldChange)=\"TextBold = $event\"\n (TextColorChange)=\"TextColor = $event\"\n (StyleSelection)=\"board.ApplyTextStyleToSelection($event)\"\n (Undo)=\"State.Undo()\"\n (Redo)=\"State.Redo()\"/>\n\n <mj-realtime-whiteboard-zoom\n [ZoomPercent]=\"board.ZoomPercent\"\n [MinimapOpen]=\"board.MinimapOpen\"\n (ZoomOut)=\"board.ZoomOut()\"\n (ZoomIn)=\"board.ZoomIn()\"\n (ZoomBy)=\"board.ZoomByFactor($event)\"\n (Fit)=\"board.FitToContent()\"\n (ToggleMinimap)=\"board.ToggleMinimap()\"/>\n\n <!-- agent action toast -->\n @if (ToastText) {\n <div class=\"board-toast\">\n <i class=\"fa-solid fa-feather\"></i>\n <span class=\"board-toast__text\">{{ ToastText }}</span>\n <span class=\"undo\" (click)=\"OnToastUndo($event)\"><i class=\"fa-solid fa-rotate-left\"></i> Undo</span>\n </div>\n }\n</div>\n\n<!-- board status footer: ONE row, never wraps \u2014 items ellipsize, and the long\n persistence prose collapses to an icon chip (full text on its tooltip) when\n the host is narrow (container query on the host width). -->\n<div class=\"board-status\">\n <span class=\"stat\"><b>{{ ElementCount }}</b> elements</span>\n <span class=\"stat\">you <b>{{ UserCount }}</b> \u00B7 {{ AgentName }} <b>{{ AgentCount }}</b></span>\n <span class=\"stat\">synced <b>{{ LastDeltaLabel }}</b> \u00B7 seq <b>{{ CurrentSeq }}</b></span>\n <span class=\"seg-r\">\n <span class=\"stat ctx-live\" [title]=\"AgentName + '\u2019s live context: current'\">\n <span class=\"d\"></span><span class=\"ctx-live__text\"> {{ AgentName }}'s live context: current</span>\n </span>\n <span class=\"stat persist\" title=\"Persists as a session artifact when the call ends\">\n <i class=\"fa-solid fa-box-archive\" aria-hidden=\"true\"></i>\n <span class=\"persist__long\">persists as <b>session artifact</b> when the call ends</span>\n <span class=\"persist__short\">session artifact</span>\n </span>\n </span>\n</div>\n", styles: ["/* The host carries tabindex=\"-1\" so the board has a focus scope for its keyboard\n shortcuts (WCAG 2.1.4). Suppress the ring for the pointer path only \u2014 a keyboard\n focus, if the board ever becomes tab-reachable, must still show one (2.4.7). */\n:host:focus:not(:focus-visible) {\n outline: none;\n}\n\n/* LIVE WHITEBOARD host chrome \u2014 board header, board wrap, agent toast, status footer.\n Ported pixel-faithfully from plans/realtime/mockups/whiteboard.html\n (.board-head, .head-btn, .board-legend, .board-toast, .board-status). */\n\n:host {\n /* agent (violet) accent \u2014 same derivation as the sibling realtime components */\n --sage: var(--mj-color-violet-400, #a78bfa);\n --sage-bg: color-mix(in srgb, var(--mj-color-violet-400, #a78bfa) 14%, transparent);\n --sage-border: color-mix(in srgb, var(--mj-color-violet-400, #a78bfa) 45%, transparent);\n --sage-chip-bg: color-mix(in srgb, var(--mj-color-violet-500, #8b5cf6) 32%, var(--mj-bg-surface-sunken));\n\n display: flex;\n flex-direction: column;\n flex: 1;\n min-height: 0;\n height: 100%;\n /* width container \u2014 drives the status footer's narrow-width collapse rules */\n container-type: inline-size;\n container-name: wb-host;\n}\n\n/* ---------- board header row ---------- */\n.board-head {\n display: flex;\n align-items: center;\n gap: 12px;\n padding: 9px 16px;\n border-bottom: 1px solid var(--mj-border-default);\n background: var(--mj-bg-surface);\n flex-shrink: 0;\n flex-wrap: wrap;\n}\n.board-head .bt {\n font-weight: 700;\n font-size: 13.5px;\n display: flex;\n align-items: center;\n gap: 8px;\n color: var(--mj-text-primary);\n}\n.board-head .bt i {\n color: var(--mj-brand-accent, var(--mj-brand-primary));\n font-size: 13px;\n}\n.board-save {\n font-size: 11px;\n color: var(--mj-text-muted);\n display: inline-flex;\n align-items: center;\n gap: 5px;\n}\n.board-save i {\n color: var(--mj-status-success);\n font-size: 10px;\n}\n\n.board-legend {\n display: inline-flex;\n align-items: center;\n gap: 12px;\n font-size: 11px;\n color: var(--mj-text-muted);\n margin-left: 6px;\n padding: 4px 10px;\n border: 1px solid var(--mj-border-default);\n border-radius: var(--mj-radius-full, 9999px);\n background: var(--mj-bg-surface-card);\n}\n.board-legend .sw {\n width: 10px;\n height: 10px;\n border-radius: 3px;\n display: inline-block;\n vertical-align: -1px;\n margin-right: 5px;\n}\n.board-legend .sw--you {\n background: var(--mj-bg-surface-elevated);\n border: 1px solid var(--mj-border-strong);\n}\n.board-legend .sw--sage {\n background: var(--sage-bg);\n border: 1px solid var(--sage);\n}\n.legend-feather {\n font-size: 9px;\n color: var(--sage);\n}\n\n.head-end {\n margin-left: auto;\n display: flex;\n align-items: center;\n gap: 8px;\n position: relative;\n}\n\n.head-btn {\n display: inline-flex;\n align-items: center;\n gap: 7px;\n padding: 5px 11px;\n font-size: 11.5px;\n font-weight: 600;\n border: 1px solid var(--mj-border-default);\n border-radius: var(--mj-radius-full, 9999px);\n cursor: pointer;\n background: var(--mj-bg-surface-card);\n color: var(--mj-text-secondary);\n transition: all 140ms ease;\n font-family: inherit;\n}\n.head-btn:hover {\n color: var(--mj-text-primary);\n border-color: var(--mj-border-strong);\n}\n.head-btn.on {\n background: color-mix(in srgb, var(--mj-brand-primary) 14%, transparent);\n border-color: var(--mj-brand-primary);\n color: var(--mj-brand-accent, var(--mj-brand-primary));\n}\n.head-btn--sees {\n position: relative;\n}\n.head-btn--sees .eye-pulse {\n width: 6px;\n height: 6px;\n border-radius: 50%;\n background: var(--sage);\n box-shadow: 0 0 0 0 color-mix(in srgb, var(--sage) 50%, transparent);\n animation: wb-pulse-ring 2s infinite;\n}\n.head-btn--more {\n padding: 5px 9px;\n}\n\n/* ---------- export menu (Download HTML / Download SVG / Print) ---------- */\n.export-wrap {\n position: relative;\n display: inline-flex;\n}\n.export-menu {\n position: absolute;\n top: calc(100% + 6px);\n right: 0;\n z-index: 30;\n min-width: 168px;\n display: flex;\n flex-direction: column;\n padding: 5px;\n border: 1px solid var(--mj-border-default);\n border-radius: var(--mj-radius-md, 8px);\n background: var(--mj-bg-surface-elevated);\n box-shadow: var(--mj-shadow-lg, 0 10px 15px -3px rgba(0, 0, 0, 0.3));\n}\n.export-item {\n display: flex;\n align-items: center;\n gap: 9px;\n padding: 7px 10px;\n font-size: 11.5px;\n font-weight: 600;\n font-family: inherit;\n text-align: left;\n border: none;\n border-radius: var(--mj-radius-sm, 5px);\n background: transparent;\n color: var(--mj-text-secondary);\n cursor: pointer;\n white-space: nowrap;\n}\n.export-item:hover {\n background: var(--mj-bg-surface-hover);\n color: var(--mj-text-primary);\n}\n.export-item i {\n width: 14px;\n text-align: center;\n color: var(--mj-text-muted);\n}\n.export-item:hover i {\n color: var(--mj-brand-accent, var(--mj-brand-primary));\n}\n@keyframes wb-pulse-ring {\n 0% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--sage) 50%, transparent); }\n 70% { box-shadow: 0 0 0 6px transparent; }\n 100% { box-shadow: 0 0 0 0 transparent; }\n}\n\n/* ---------- the board ---------- */\n.board-wrap {\n position: relative;\n flex: 1;\n min-height: 0;\n overflow: hidden;\n}\n\n/* ---------- agent action toast ----------\n Bottom-center, raised ABOVE the zoom cluster's row (cluster: bottom 14px + ~38px\n tall) so the two can never collide, and width-capped clear of the cluster at the\n board's right edge. Sits just above the status footer per the mockup. */\n.board-toast {\n position: absolute;\n left: 50%;\n bottom: 64px;\n transform: translateX(-50%);\n z-index: 9;\n display: flex;\n align-items: center;\n gap: 9px;\n padding: 8px 16px;\n max-width: calc(100% - 32px);\n border-radius: var(--mj-radius-full, 9999px);\n background: var(--sage-chip-bg);\n border: 1px solid var(--sage-border);\n color: var(--mj-color-violet-300, #c4b5fd);\n font-size: 12px;\n font-weight: 600;\n box-shadow: var(--mj-shadow-lg, 0 10px 15px -3px rgba(0, 0, 0, 0.3));\n animation: wb-toast-fade 300ms ease both;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n.board-toast > i {\n flex-shrink: 0;\n}\n.board-toast__text {\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n.board-toast .undo {\n flex-shrink: 0;\n}\n@keyframes wb-toast-fade {\n from { opacity: 0; transform: translate(-50%, 4px); }\n to { opacity: 1; transform: translate(-50%, 0); }\n}\n.board-toast .undo {\n color: var(--mj-text-muted);\n font-weight: 600;\n cursor: pointer;\n margin-left: 4px;\n}\n.board-toast .undo:hover {\n color: var(--mj-text-primary);\n}\n\n/* ---------- board status footer ---------- */\n.board-status {\n display: flex;\n align-items: center;\n gap: 18px;\n padding: 6px 16px;\n flex-shrink: 0;\n border-top: 1px solid var(--mj-border-default);\n background: var(--mj-bg-surface);\n font-size: 10.5px;\n color: var(--mj-text-muted);\n font-weight: 500;\n}\n.board-status b {\n color: var(--mj-text-secondary);\n font-weight: 600;\n}\n.board-status .seg-r {\n margin-left: auto;\n display: flex;\n align-items: center;\n gap: 18px;\n}\n.board-status .ctx-live {\n color: var(--mj-color-violet-300, var(--mj-text-secondary));\n display: inline-flex;\n align-items: center;\n gap: 6px;\n font-weight: 600;\n}\n.board-status .ctx-live .d {\n width: 6px;\n height: 6px;\n border-radius: 50%;\n background: var(--sage);\n animation: wb-pulse-ring 2s infinite;\n}\n"] }]
|
|
705
815
|
}], null, { State: [{
|
|
706
816
|
type: Input,
|
|
707
817
|
args: [{ required: true }]
|
|
@@ -711,6 +821,10 @@ export class RealtimeWhiteboardHostComponent {
|
|
|
711
821
|
type: Input
|
|
712
822
|
}], SavedLabel: [{
|
|
713
823
|
type: Input
|
|
824
|
+
}], EnableGlobalShortcuts: [{
|
|
825
|
+
type: Input
|
|
826
|
+
}], ToolRoster: [{
|
|
827
|
+
type: Input
|
|
714
828
|
}], SceneDelta: [{
|
|
715
829
|
type: Output
|
|
716
830
|
}], AgentUndo: [{
|
|
@@ -750,9 +864,15 @@ export class RealtimeWhiteboardHostComponent {
|
|
|
750
864
|
}], CloseExport: [{
|
|
751
865
|
type: HostListener,
|
|
752
866
|
args: ['document:click']
|
|
867
|
+
}], HostTabIndex: [{
|
|
868
|
+
type: HostBinding,
|
|
869
|
+
args: ['attr.tabindex']
|
|
870
|
+
}], OnHostPointerDown: [{
|
|
871
|
+
type: HostListener,
|
|
872
|
+
args: ['pointerdown']
|
|
753
873
|
}], OnKeydown: [{
|
|
754
874
|
type: HostListener,
|
|
755
875
|
args: ['document:keydown', ['$event']]
|
|
756
876
|
}] }); })();
|
|
757
|
-
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(RealtimeWhiteboardHostComponent, { className: "RealtimeWhiteboardHostComponent", filePath: "src/lib/whiteboard-host.component.ts", lineNumber:
|
|
877
|
+
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(RealtimeWhiteboardHostComponent, { className: "RealtimeWhiteboardHostComponent", filePath: "src/lib/whiteboard-host.component.ts", lineNumber: 72 }); })();
|
|
758
878
|
//# sourceMappingURL=whiteboard-host.component.js.map
|