@activecollab/components 2.0.398 → 2.0.400

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/dist/cjs/presentation/whiteboard/bottomDock.js +194 -0
  2. package/dist/cjs/presentation/whiteboard/bottomDock.js.map +1 -0
  3. package/dist/cjs/presentation/whiteboard/canvasElements.js +877 -0
  4. package/dist/cjs/presentation/whiteboard/canvasElements.js.map +1 -0
  5. package/dist/cjs/presentation/whiteboard/elements.js +816 -0
  6. package/dist/cjs/presentation/whiteboard/elements.js.map +1 -0
  7. package/dist/cjs/presentation/whiteboard/fileElement.js +457 -0
  8. package/dist/cjs/presentation/whiteboard/fileElement.js.map +1 -0
  9. package/dist/cjs/presentation/whiteboard/glyphs.js +67 -0
  10. package/dist/cjs/presentation/whiteboard/glyphs.js.map +1 -0
  11. package/dist/cjs/presentation/whiteboard/inspector.js +884 -0
  12. package/dist/cjs/presentation/whiteboard/inspector.js.map +1 -0
  13. package/dist/cjs/presentation/whiteboard/linkElement.js +357 -0
  14. package/dist/cjs/presentation/whiteboard/linkElement.js.map +1 -0
  15. package/dist/cjs/presentation/whiteboard/outline.js +966 -0
  16. package/dist/cjs/presentation/whiteboard/outline.js.map +1 -0
  17. package/dist/cjs/presentation/whiteboard/peopleMenu.js +516 -0
  18. package/dist/cjs/presentation/whiteboard/peopleMenu.js.map +1 -0
  19. package/dist/cjs/presentation/whiteboard/settingsMenu.js +177 -0
  20. package/dist/cjs/presentation/whiteboard/settingsMenu.js.map +1 -0
  21. package/dist/esm/presentation/whiteboard/bottomDock.d.ts +51 -0
  22. package/dist/esm/presentation/whiteboard/bottomDock.d.ts.map +1 -0
  23. package/dist/esm/presentation/whiteboard/bottomDock.js +162 -0
  24. package/dist/esm/presentation/whiteboard/bottomDock.js.map +1 -0
  25. package/dist/esm/presentation/whiteboard/canvasElements.d.ts +61 -0
  26. package/dist/esm/presentation/whiteboard/canvasElements.d.ts.map +1 -0
  27. package/dist/esm/presentation/whiteboard/canvasElements.js +746 -0
  28. package/dist/esm/presentation/whiteboard/canvasElements.js.map +1 -0
  29. package/dist/esm/presentation/whiteboard/elements.d.ts +180 -0
  30. package/dist/esm/presentation/whiteboard/elements.d.ts.map +1 -0
  31. package/dist/esm/presentation/whiteboard/elements.js +736 -0
  32. package/dist/esm/presentation/whiteboard/elements.js.map +1 -0
  33. package/dist/esm/presentation/whiteboard/fileElement.d.ts +113 -0
  34. package/dist/esm/presentation/whiteboard/fileElement.d.ts.map +1 -0
  35. package/dist/esm/presentation/whiteboard/fileElement.js +430 -0
  36. package/dist/esm/presentation/whiteboard/fileElement.js.map +1 -0
  37. package/dist/esm/presentation/whiteboard/glyphs.d.ts +32 -0
  38. package/dist/esm/presentation/whiteboard/glyphs.d.ts.map +1 -0
  39. package/dist/esm/presentation/whiteboard/glyphs.js +50 -0
  40. package/dist/esm/presentation/whiteboard/glyphs.js.map +1 -0
  41. package/dist/esm/presentation/whiteboard/inspector.d.ts +100 -0
  42. package/dist/esm/presentation/whiteboard/inspector.d.ts.map +1 -0
  43. package/dist/esm/presentation/whiteboard/inspector.js +753 -0
  44. package/dist/esm/presentation/whiteboard/inspector.js.map +1 -0
  45. package/dist/esm/presentation/whiteboard/linkElement.d.ts +97 -0
  46. package/dist/esm/presentation/whiteboard/linkElement.d.ts.map +1 -0
  47. package/dist/esm/presentation/whiteboard/linkElement.js +327 -0
  48. package/dist/esm/presentation/whiteboard/linkElement.js.map +1 -0
  49. package/dist/esm/presentation/whiteboard/outline.d.ts +89 -0
  50. package/dist/esm/presentation/whiteboard/outline.d.ts.map +1 -0
  51. package/dist/esm/presentation/whiteboard/outline.js +870 -0
  52. package/dist/esm/presentation/whiteboard/outline.js.map +1 -0
  53. package/dist/esm/presentation/whiteboard/peopleMenu.d.ts +44 -0
  54. package/dist/esm/presentation/whiteboard/peopleMenu.d.ts.map +1 -0
  55. package/dist/esm/presentation/whiteboard/peopleMenu.js +440 -0
  56. package/dist/esm/presentation/whiteboard/peopleMenu.js.map +1 -0
  57. package/dist/esm/presentation/whiteboard/settingsMenu.d.ts +13 -0
  58. package/dist/esm/presentation/whiteboard/settingsMenu.d.ts.map +1 -0
  59. package/dist/esm/presentation/whiteboard/settingsMenu.js +155 -0
  60. package/dist/esm/presentation/whiteboard/settingsMenu.js.map +1 -0
  61. package/package.json +1 -1
@@ -0,0 +1,736 @@
1
+ import _extends from "@babel/runtime/helpers/esm/extends";
2
+ import React, { useState } from "react";
3
+ import styled from "styled-components";
4
+ import { MinusGlyph, PlusGlyph, SquareGlyph } from "./glyphs";
5
+ import { Dot } from "../../components/Dot";
6
+ import { IconButton } from "../../components/IconButton";
7
+ import { ArrowRefreshIcon, CircleIcon, CircleSlashIcon, LockIcon, RoundedRectangleIcon, TextAlignCenterIcon, TextAlignJustifyIcon, TextAlignLeftIcon, TextAlignRightIcon, TextStyleBoldIcon, TextStyleItalicIcon, TextStyleStrikeIcon, TextStyleUnderlineIcon, TrashIcon, TriangleIcon } from "../../components/Icons";
8
+ import { List, ListItem } from "../../components/List";
9
+ import { Menu } from "../../components/Menu";
10
+ import { Tooltip } from "../../components/Tooltip";
11
+ import { colors } from "../../utils/colors";
12
+
13
+ /* ------------------------------------------------------------------ */
14
+ /* Canvas elements (1:1 with new-activecollab-front */
15
+ /* src/components/Whiteboards/ — constants.js, ShapeElement.jsx, */
16
+ /* PostitElement.jsx, ResizableWrapper.jsx, ResizeHandles.jsx, */
17
+ /* FormattingToolbarContainer.jsx + the two formatting toolbars) */
18
+ /* ------------------------------------------------------------------ */
19
+
20
+ /* element colors are canvas DATA (app constants), not theme — hardcoded */
21
+ export const ELEMENT_FILL = "#ffffff";
22
+ export const ELEMENT_STROKE = "#374151";
23
+ export const ELEMENT_TEXT = "#1f2937";
24
+ export const STICKY_FILL = "#fbbf24";
25
+ export const SELECTION_BLUE = "#3b82f6";
26
+
27
+ /* COLOR_PALETTE from toolbar/colorPalette.js (submenu swatch rows) */
28
+ export const COLOR_PALETTE = ["#ffffff", "#3b82f6", "#ef4444", "#10b981", "#f59e0b", "#8b5cf6", "#f97316", "#06b6d4", "#84cc16", "#ec4899", "#6b7280"];
29
+
30
+ /* Discrete option sets for the right Inspector — mirror the app's fixed
31
+ font-size ladder and the border-thickness steps offered in the toolbar. */
32
+ export const FONT_SIZE_OPTIONS = [10, 13, 16, 20, 24, 30, 36, 48, 72, 96, 144, 216, 288];
33
+ export const THICKNESS_OPTIONS = [1, 2, 3, 4, 5];
34
+ export const OPACITY_OPTIONS = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100];
35
+
36
+ /* Corner radii scaled to the board: shapes are drawn at ~100–300px, so the jump
37
+ from a crisp corner to a pill reads across 0–64px. */
38
+ export const RADIUS_OPTIONS = [0, 4, 8, 12, 16, 24, 32, 48, 64];
39
+
40
+ /* Element bodies are HTML, as the API returns them (the `body` field). The
41
+ Inspector renders them read-only; when a body is empty the field is omitted.
42
+ Mock the three cases across the canvas: a short body (shape-rounded, the
43
+ default selection), a long one that scrolls (shape-circle), and none at all
44
+ (shape-triangle / sticky). */
45
+ export const ELEMENT_BODIES = {
46
+ "shape-rounded": "<p>Marks the <strong>Kickoff</strong> phase.</p>",
47
+ "shape-circle": "<p>The <strong>discovery</strong> loop for this initiative.</p><p>We collect signals from support, sales and analytics, then cluster them into themes before committing to a direction.</p><ul><li>Owner: research</li><li>Inputs: interviews, tickets, funnel data</li><li>Output: a prioritised opportunity list</li></ul><p>See the <em>retro notes</em> and last quarter's readout for the full context and prior decisions.</p>"
48
+ };
49
+
50
+ /* Named-colour options for the Inspector fill/stroke pickers, built from the
51
+ design system's shared data colours (utils/colors). Shaped for the DS Select
52
+ (id + name + color + textColor) so it renders the standard dot-and-radio menu
53
+ used by the label dialogs. */
54
+ export const COLOR_OPTIONS = Object.values(colors).map(entry => ({
55
+ id: entry.color,
56
+ name: entry.name,
57
+ color: entry.color,
58
+ textColor: entry.color
59
+ }));
60
+
61
+ /* per-shape editable state (the app bakes opacity into rgba via
62
+ hexToRgba/parseOpacity; the mock keeps opacity separate and rebuilds
63
+ the color when rendering) */
64
+
65
+ export const defaultShapeState = type => ({
66
+ type,
67
+ fill: ELEMENT_FILL,
68
+ fillOpacity: 100,
69
+ stroke: ELEMENT_STROKE,
70
+ strokeOpacity: 100,
71
+ strokeWidth: 2,
72
+ strokeDash: undefined,
73
+ borderRadius: type === "roundedRectangle" ? 12 : 0,
74
+ fontSize: 16,
75
+ bold: false,
76
+ italic: false,
77
+ underline: false,
78
+ strikethrough: false,
79
+ textColor: ELEMENT_TEXT,
80
+ align: "left"
81
+ });
82
+
83
+ /* swatches are 6-digit hex, the color wheel returns hsl(...) */
84
+ export const withOpacity = (color, opacityPct) => {
85
+ if (opacityPct >= 100) {
86
+ return color;
87
+ }
88
+ const alpha = opacityPct / 100;
89
+ if (color.startsWith("#")) {
90
+ const r = parseInt(color.slice(1, 3), 16);
91
+ const g = parseInt(color.slice(3, 5), 16);
92
+ const b = parseInt(color.slice(5, 7), 16);
93
+ return "rgba(" + r + ", " + g + ", " + b + ", " + alpha + ")";
94
+ }
95
+ if (color.startsWith("hsl(")) {
96
+ return color.replace("hsl(", "hsla(").replace(")", ", " + alpha + ")");
97
+ }
98
+ return color;
99
+ };
100
+
101
+ /* getShapeBorder from ShapeElement.jsx */
102
+ export const shapeBorder = state => {
103
+ if (state.strokeWidth === 0) {
104
+ return "none";
105
+ }
106
+ const lineStyle = state.strokeDash === "5,5" ? "dashed" : state.strokeDash === "2,2" ? "dotted" : "solid";
107
+ return state.strokeWidth + "px " + lineStyle + " " + withOpacity(state.stroke, state.strokeOpacity);
108
+ };
109
+
110
+ /* PostitElement gradient: color → same color at ~87% alpha */
111
+ export const stickyGradient = function (color, opacity) {
112
+ if (opacity === void 0) {
113
+ opacity = 100;
114
+ }
115
+ const main = withOpacity(color, opacity);
116
+ const faded = withOpacity(color, Math.max(0, opacity - 13));
117
+ return "linear-gradient(135deg, " + main + " 0%, " + faded + " 100%)";
118
+ };
119
+ /* Grid config, aligned to the 8px design-system grid. dotSpacing drives the
120
+ dot layer; snapStep drives snapping — decoupled, so each level snaps finer
121
+ than its dots (Small: 16px dots / 8px snap; Large: 32px dots / 16px snap,
122
+ i.e. dots and half-dots). */
123
+ export const GRID_LEVELS = [{
124
+ value: "off",
125
+ label: "Off",
126
+ dotSpacing: null,
127
+ snapStep: null
128
+ }, {
129
+ value: "small",
130
+ label: "Small",
131
+ dotSpacing: 16,
132
+ snapStep: 8
133
+ }, {
134
+ value: "large",
135
+ label: "Large",
136
+ dotSpacing: 32,
137
+ snapStep: 16
138
+ }];
139
+ export const gridLevel = value => {
140
+ var _GRID_LEVELS$find;
141
+ return (_GRID_LEVELS$find = GRID_LEVELS.find(level => level.value === value)) != null ? _GRID_LEVELS$find : GRID_LEVELS[0];
142
+ };
143
+ /* default sizes from DEFAULT_SHAPE_SIZES / DEFAULT_STICKY_SIZE; positions are
144
+ board-space pixels (top-left) so dragging can snap to the pixel grid */
145
+ export const CANVAS_ELEMENTS = [{
146
+ id: "shape-circle",
147
+ kind: "shape",
148
+ initialType: "circle",
149
+ x: 320,
150
+ y: 180,
151
+ width: 80,
152
+ height: 80
153
+ }, {
154
+ id: "shape-rounded",
155
+ kind: "shape",
156
+ initialType: "roundedRectangle",
157
+ x: 560,
158
+ y: 300,
159
+ width: 100,
160
+ height: 60
161
+ }, {
162
+ id: "shape-triangle",
163
+ kind: "shape",
164
+ initialType: "triangle",
165
+ x: 820,
166
+ y: 170,
167
+ width: 80,
168
+ height: 80
169
+ }, {
170
+ id: "sticky",
171
+ kind: "sticky",
172
+ x: 430,
173
+ y: 430,
174
+ width: 120,
175
+ height: 120
176
+ }];
177
+ export const INITIAL_SHAPES = CANVAS_ELEMENTS.reduce((acc, element) => {
178
+ if (element.kind === "shape" && element.initialType) {
179
+ acc[element.id] = defaultShapeState(element.initialType);
180
+ }
181
+ return acc;
182
+ }, {});
183
+ export const INITIAL_POSITIONS = CANVAS_ELEMENTS.reduce((acc, element) => {
184
+ acc[element.id] = {
185
+ x: element.x,
186
+ y: element.y
187
+ };
188
+ return acc;
189
+ }, {});
190
+ export const snapValue = (value, step) => step ? Math.round(value / step) * step : value;
191
+
192
+ /* deselect happens on canvas-background click (layer under the bars) */
193
+ export const StyledCanvasLayer = styled.div.withConfig({
194
+ displayName: "elements__StyledCanvasLayer",
195
+ componentId: "sc-ohnju4-0"
196
+ })(["position:absolute;inset:0;&.drawing{cursor:crosshair;}"]);
197
+
198
+ /* ResizableWrapper: selected = 2px blue-500 ring @50%, hover = 4px
199
+ blue-400 ring @70% (Tailwind rings are box-shadows) */
200
+ export const StyledCanvasElement = styled.div.withConfig({
201
+ displayName: "elements__StyledCanvasElement",
202
+ componentId: "sc-ohnju4-1"
203
+ })(["position:absolute;cursor:move;&:hover:not(.selected){box-shadow:0 0 0 4px rgba(96,165,250,0.7);}&.selected{box-shadow:0 0 0 2px rgba(59,130,246,0.5);}&.is-card{cursor:grab;height:auto;&:hover:not(.selected),&.selected{box-shadow:none;}}.shape-fill{width:100%;height:100%;box-sizing:border-box;}"]);
204
+
205
+ /* PostitElement: 12px padding, shadow-lg, 1deg tilt that straightens on
206
+ hover, diagonal gradient to ~87% alpha, Comic Sans */
207
+ export const StyledSticky = styled.div.withConfig({
208
+ displayName: "elements__StyledSticky",
209
+ componentId: "sc-ohnju4-2"
210
+ })(["width:100%;height:100%;box-sizing:border-box;padding:12px;box-shadow:0 10px 15px -3px rgba(0,0,0,0.1),0 4px 6px -4px rgba(0,0,0,0.1);transform:rotate(1deg);transition:transform 200ms;font-family:\"Comic Sans MS\",cursive,system-ui,sans-serif;line-height:1.4;word-break:break-word;white-space:pre-wrap;", ":hover &{transform:rotate(0deg);}"], StyledCanvasElement);
211
+
212
+ /* ResizeHandles: 8 12px blue squares w/ 2px white border on the box,
213
+ plus the 24px rotate handle outside the bottom-left corner */
214
+ export const StyledHandles = styled.div.withConfig({
215
+ displayName: "elements__StyledHandles",
216
+ componentId: "sc-ohnju4-3"
217
+ })([".rh{position:absolute;width:12px;height:12px;box-sizing:border-box;background-color:", ";border:2px solid #ffffff;border-radius:2px;z-index:10;&:hover{background-color:#2563eb;}}.rh-nw{top:-6px;left:-6px;cursor:nw-resize;}.rh-n{top:-6px;left:50%;margin-left:-6px;cursor:n-resize;}.rh-ne{top:-6px;right:-6px;cursor:ne-resize;}.rh-w{top:50%;left:-6px;margin-top:-6px;cursor:w-resize;}.rh-e{top:50%;right:-6px;margin-top:-6px;cursor:e-resize;}.rh-sw{bottom:-6px;left:-6px;cursor:sw-resize;}.rh-s{bottom:-6px;left:50%;margin-left:-6px;cursor:s-resize;}.rh-se{bottom:-6px;right:-6px;cursor:se-resize;}.rotate-handle{position:absolute;bottom:-35px;left:-35px;width:24px;height:24px;box-sizing:border-box;display:flex;align-items:center;justify-content:center;background-color:#ffffff;border:2px solid ", ";border-radius:50%;cursor:grab;z-index:11;svg{width:16px;height:16px;}}"], SELECTION_BLUE, SELECTION_BLUE);
218
+ export const RESIZE_DIRECTIONS = ["nw", "n", "ne", "w", "e", "sw", "s", "se"];
219
+ export const ResizeHandles = () => /*#__PURE__*/React.createElement(StyledHandles, {
220
+ onMouseDown: event => event.stopPropagation()
221
+ }, RESIZE_DIRECTIONS.map(direction => /*#__PURE__*/React.createElement("span", {
222
+ key: direction,
223
+ className: "rh rh-" + direction
224
+ })), /*#__PURE__*/React.createElement("span", {
225
+ className: "rotate-handle"
226
+ }, /*#__PURE__*/React.createElement(ArrowRefreshIcon, {
227
+ fill: SELECTION_BLUE
228
+ })));
229
+
230
+ /* FormattingToolbarContainer: Paper paper-2 (bg + 8px radius + shadow,
231
+ no border), 8px padding, 4px gap, centered 10px above the element */
232
+ export const StyledFormattingToolbar = styled.div.withConfig({
233
+ displayName: "elements__StyledFormattingToolbar",
234
+ componentId: "sc-ohnju4-4"
235
+ })(["position:absolute;bottom:calc(100% + 10px);left:50%;transform:translateX(-50%);display:flex;align-items:center;gap:4px;padding:8px;border-radius:8px;background-color:var(--page-paper-main);box-shadow:var(--shadow-tertiary);white-space:nowrap;cursor:auto;z-index:20;.tb-group{position:relative;display:flex;align-items:center;gap:4px;padding-right:8px;margin-right:8px;border-right:1px solid var(--border-primary);}.tb-rel{position:relative;display:inline-flex;}.divider{width:1px;height:20px;background-color:var(--border-primary);margin:0 4px;flex-shrink:0;}"]);
236
+
237
+ /* Button variant="option" (idle) / "tertiary" (active), h-8 px-2 gap-1 */
238
+ export const StyledToolbarButton = styled.button.withConfig({
239
+ displayName: "elements__StyledToolbarButton",
240
+ componentId: "sc-ohnju4-5"
241
+ })(["display:inline-flex;align-items:center;justify-content:center;gap:4px;height:32px;box-sizing:border-box;padding:0 8px;border:0;border-radius:8px;background-color:transparent;color:var(--color-secondary);font-family:-apple-system,BlinkMacSystemFont,\"Roboto\",\"Helvetica Neue\",Arial,sans-serif;font-size:14px;cursor:pointer;&:hover{background-color:var(--color-theme-200);}&.active{background-color:var(--color-theme-300);}&.icon-only{width:32px;padding:0;}&:disabled{opacity:0.45;cursor:default;&:hover{background-color:transparent;}}svg{fill:currentColor;flex-shrink:0;}svg.i16{width:16px;height:16px;}svg.i12{width:12px;height:12px;}"]);
242
+
243
+ /* Same chrome as a toolbar button, but an <a> — used where the action is a real
244
+ navigation (Visit). `withComponent` keeps the styles and swaps the element, so
245
+ the props type becomes the anchor's. */
246
+ export const StyledToolbarLink = styled(StyledToolbarButton.withComponent("a")).withConfig({
247
+ displayName: "elements__StyledToolbarLink",
248
+ componentId: "sc-ohnju4-6"
249
+ })(["text-decoration:none;"]);
250
+
251
+ /* font-mono min-w-[24px] text-xs readout between the stepper buttons */
252
+ export const StyledFontSizeValue = styled.span.withConfig({
253
+ displayName: "elements__StyledFontSizeValue",
254
+ componentId: "sc-ohnju4-7"
255
+ })(["min-width:24px;text-align:center;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12px;color:var(--color-secondary);"]);
256
+ export const stopClick = event => event.stopPropagation();
257
+
258
+ /* Shared drop shadow for the colour swatch popover / colour wheel that the
259
+ compact toolbar swatch trigger opens. */
260
+ export const SUBMENU_SHADOW = "0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -4px rgba(0, 0, 0, 0.1)";
261
+
262
+ /* ColorWheelPopover: beside the submenu (SHAPE_POPOVER_CLASS) or below
263
+ a toolbar trigger (sticky default class) */
264
+ export const StyledColorPopover = styled.div.withConfig({
265
+ displayName: "elements__StyledColorPopover",
266
+ componentId: "sc-ohnju4-8"
267
+ })(["position:absolute;box-sizing:border-box;padding:12px;background-color:var(--page-paper-main);border:1px solid var(--border-primary);border-radius:8px;box-shadow:", ";z-index:70;cursor:auto;&.beside{top:0;left:100%;margin-left:8px;}&.below{top:40px;left:0;}"], SUBMENU_SHADOW);
268
+
269
+ /* ImprovedColorWheel: hue = angle from east (atan2), saturation = distance,
270
+ lightness 50 — the canvas wheel replicated with CSS gradients */
271
+ export const WHEEL_SIZE = 200;
272
+ export const WHEEL_RADIUS = WHEEL_SIZE / 2 - 5;
273
+ export const StyledColorWheel = styled.button.withConfig({
274
+ displayName: "elements__StyledColorWheel",
275
+ componentId: "sc-ohnju4-9"
276
+ })(["display:block;width:", "px;height:", "px;padding:0;border:0;border-radius:50%;cursor:crosshair;box-shadow:", ";background:radial-gradient( circle closest-side,#ffffff,rgba(255,255,255,0) ),conic-gradient( from 90deg,#ff0000,#ffff00,#00ff00,#00ffff,#0000ff,#ff00ff,#ff0000 );"], WHEEL_SIZE, WHEEL_SIZE, SUBMENU_SHADOW);
277
+ export const ColorWheel = _ref => {
278
+ let onColorChange = _ref.onColorChange;
279
+ return /*#__PURE__*/React.createElement(StyledColorWheel, {
280
+ onClick: event => {
281
+ const rect = event.currentTarget.getBoundingClientRect();
282
+ const dx = event.clientX - rect.left - WHEEL_SIZE / 2;
283
+ const dy = event.clientY - rect.top - WHEEL_SIZE / 2;
284
+ const distance = Math.sqrt(dx * dx + dy * dy);
285
+ if (distance > WHEEL_RADIUS) {
286
+ return;
287
+ }
288
+ let angle = Math.atan2(dy, dx);
289
+ if (angle < 0) {
290
+ angle += 2 * Math.PI;
291
+ }
292
+ const hue = Math.round(angle * 180 / Math.PI);
293
+ const saturation = Math.round(Math.min(100, distance / WHEEL_RADIUS * 100));
294
+ onColorChange("hsl(" + hue + ", " + saturation + "%, 50%)");
295
+ }
296
+ });
297
+ };
298
+
299
+ /* ColorSwatchRow.jsx: 32px swatches + a "+" that opens the color wheel */
300
+ export const StyledSwatchRow = styled.div.withConfig({
301
+ displayName: "elements__StyledSwatchRow",
302
+ componentId: "sc-ohnju4-10"
303
+ })(["display:flex;flex-wrap:wrap;gap:8px;.swatch{width:32px;height:32px;box-sizing:border-box;padding:0;border:2px solid var(--border-primary);border-radius:4px;cursor:pointer;&:hover{border-color:var(--color-theme-500);}&.selected{box-shadow:0 0 0 2px ", ";}}.swatch-add{display:flex;align-items:center;justify-content:center;background-color:var(--color-theme-200);color:var(--color-secondary);}"], SELECTION_BLUE);
304
+ export const ColorSwatchRow = _ref2 => {
305
+ let selectedColor = _ref2.selectedColor,
306
+ includeTransparent = _ref2.includeTransparent,
307
+ onSelect = _ref2.onSelect,
308
+ onAddClick = _ref2.onAddClick;
309
+ return /*#__PURE__*/React.createElement(StyledSwatchRow, null, includeTransparent ? /*#__PURE__*/React.createElement("button", {
310
+ type: "button",
311
+ className: selectedColor === "transparent" ? "swatch swatch-add selected" : "swatch swatch-add",
312
+ "aria-label": "Transparent",
313
+ onClick: () => onSelect("transparent")
314
+ }, /*#__PURE__*/React.createElement(CircleSlashIcon, {
315
+ width: 20,
316
+ height: 20,
317
+ fill: "currentColor"
318
+ })) : null, COLOR_PALETTE.map(color => /*#__PURE__*/React.createElement("button", {
319
+ key: color,
320
+ type: "button",
321
+ className: selectedColor === color ? "swatch selected" : "swatch",
322
+ style: {
323
+ backgroundColor: color
324
+ },
325
+ onClick: () => onSelect(color)
326
+ })), /*#__PURE__*/React.createElement("button", {
327
+ type: "button",
328
+ className: "swatch swatch-add",
329
+ onClick: onAddClick
330
+ }, /*#__PURE__*/React.createElement(PlusGlyph, null)));
331
+ };
332
+ export const SHAPE_TYPE_ICON = {
333
+ rectangle: /*#__PURE__*/React.createElement(SquareGlyph, null),
334
+ roundedRectangle: /*#__PURE__*/React.createElement(RoundedRectangleIcon, {
335
+ className: "i16"
336
+ }),
337
+ circle: /*#__PURE__*/React.createElement(CircleIcon, {
338
+ className: "i16"
339
+ }),
340
+ triangle: /*#__PURE__*/React.createElement(TriangleIcon, {
341
+ className: "i16"
342
+ })
343
+ };
344
+
345
+ /* The four shape types, in the order the shape-type menu lists them. */
346
+ export const SHAPE_TYPE_OPTIONS = [{
347
+ type: "rectangle",
348
+ label: "Rectangle"
349
+ }, {
350
+ type: "roundedRectangle",
351
+ label: "Rounded Rectangle"
352
+ }, {
353
+ type: "circle",
354
+ label: "Circle"
355
+ }, {
356
+ type: "triangle",
357
+ label: "Triangle"
358
+ }];
359
+
360
+ /* Stroke pattern options — now only the Inspector's Stroke group renders these
361
+ (the shape floating toolbar no longer edits stroke). Mirrors the app's
362
+ STROKE_PATTERN_OPTIONS dash enum: solid | dashed | dotted. */
363
+ export const BORDER_STYLE_OPTIONS = [{
364
+ value: undefined,
365
+ label: "Solid"
366
+ }, {
367
+ value: "5,5",
368
+ label: "Dashed"
369
+ }, {
370
+ value: "2,2",
371
+ label: "Dotted"
372
+ }];
373
+
374
+ /* ── Shared floating-toolbar controls ───────────────────────────────────────
375
+ The floating toolbars were flattened this session to match the app: compact,
376
+ collapsed controls (single-button dropdowns) with uniform 4px spacing and a
377
+ single hairline separator only before Lock / Delete. These mirror the shared
378
+ controls in the app's Inspector/Controls (ShapeTypeMenu, AlignmentMenu,
379
+ ColorPicker compact, ToolbarActions). */
380
+
381
+ /* ToolbarActions — the trailing block shared by EVERY floating toolbar:
382
+ separator → Lock → Delete, in the standard control colour (not red). Mirrors
383
+ app `toolbar/ToolbarActions.jsx`. */
384
+
385
+ export const ToolbarActions = _ref3 => {
386
+ let locked = _ref3.locked,
387
+ onToggleLock = _ref3.onToggleLock,
388
+ onDelete = _ref3.onDelete;
389
+ return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("span", {
390
+ className: "divider"
391
+ }), /*#__PURE__*/React.createElement(Tooltip, {
392
+ title: locked ? "Unlock" : "Lock"
393
+ }, /*#__PURE__*/React.createElement(StyledToolbarButton, {
394
+ className: locked ? "icon-only active" : "icon-only",
395
+ "aria-pressed": locked,
396
+ onClick: onToggleLock
397
+ }, /*#__PURE__*/React.createElement(LockIcon, {
398
+ className: "i16"
399
+ }))), /*#__PURE__*/React.createElement(Tooltip, {
400
+ title: "Delete"
401
+ }, /*#__PURE__*/React.createElement(StyledToolbarButton, {
402
+ className: "icon-only",
403
+ onClick: onDelete
404
+ }, /*#__PURE__*/React.createElement(TrashIcon, {
405
+ className: "i16"
406
+ }))));
407
+ };
408
+
409
+ /* ShapeTypeMenu — collapsed shape-type picker: one icon button showing the
410
+ current shape, opening a dropdown of the four types. Modeled on the app's
411
+ ShapeTypeMenu (DS Menu + List). */
412
+
413
+ export const ShapeTypeMenu = _ref4 => {
414
+ let value = _ref4.value,
415
+ onChange = _ref4.onChange;
416
+ const _useState = useState(false),
417
+ open = _useState[0],
418
+ setOpen = _useState[1];
419
+ return /*#__PURE__*/React.createElement(Menu, {
420
+ open: open,
421
+ onOpen: () => setOpen(true),
422
+ onClose: () => setOpen(false),
423
+ position: "bottom-start",
424
+ target: /*#__PURE__*/React.createElement(IconButton, {
425
+ type: "button",
426
+ variant: "text gray",
427
+ active: open,
428
+ "aria-label": "Shape"
429
+ }, SHAPE_TYPE_ICON[value])
430
+ }, /*#__PURE__*/React.createElement(List, {
431
+ style: {
432
+ padding: "8px 0"
433
+ },
434
+ tabIndex: -1
435
+ }, SHAPE_TYPE_OPTIONS.map(option => /*#__PURE__*/React.createElement(ListItem, {
436
+ key: option.type,
437
+ "aria-label": option.label,
438
+ onClick: () => {
439
+ onChange(option.type);
440
+ setOpen(false);
441
+ }
442
+ }, SHAPE_TYPE_ICON[option.type], option.label))));
443
+ };
444
+
445
+ /* AlignmentMenu — collapsed alignment picker (single button + dropdown), the
446
+ compact-toolbar counterpart to the Inspector's expanded four-button row.
447
+ Mirrors the app's AlignmentMenu. */
448
+
449
+ export const AlignmentMenu = _ref5 => {
450
+ var _ALIGN_OPTIONS$find;
451
+ let value = _ref5.value,
452
+ onChange = _ref5.onChange,
453
+ disabled = _ref5.disabled;
454
+ const _useState2 = useState(false),
455
+ open = _useState2[0],
456
+ setOpen = _useState2[1];
457
+ const selected = (_ALIGN_OPTIONS$find = ALIGN_OPTIONS.find(option => option.value === value)) != null ? _ALIGN_OPTIONS$find : ALIGN_OPTIONS[0];
458
+ return /*#__PURE__*/React.createElement(Menu, {
459
+ open: open,
460
+ onOpen: () => setOpen(true),
461
+ onClose: () => setOpen(false),
462
+ position: "bottom-start",
463
+ target: /*#__PURE__*/React.createElement(IconButton, {
464
+ type: "button",
465
+ variant: "text gray",
466
+ active: open,
467
+ disabled: disabled,
468
+ "aria-label": "Alignment"
469
+ }, selected.icon)
470
+ }, /*#__PURE__*/React.createElement(List, {
471
+ style: {
472
+ padding: "8px 0"
473
+ },
474
+ tabIndex: -1
475
+ }, ALIGN_OPTIONS.map(option => /*#__PURE__*/React.createElement(ListItem, {
476
+ key: option.value,
477
+ "aria-label": option.label,
478
+ onClick: () => {
479
+ onChange(option.value);
480
+ setOpen(false);
481
+ }
482
+ }, option.icon, option.label))));
483
+ };
484
+
485
+ /* Compact swatch-only colour trigger: a single dot (or a CircleSlash chip when
486
+ transparent) that opens a swatch popover. The Inspector's ColorPickerField
487
+ keeps the swatch + name; the floating toolbars use this swatch-only variant.
488
+ `includeTransparent` is passed through to the swatch row — shape fill opts
489
+ in, sticky fill and text colour do not. */
490
+
491
+ export const ToolbarColorSwatch = _ref6 => {
492
+ let value = _ref6.value,
493
+ includeTransparent = _ref6.includeTransparent,
494
+ label = _ref6.label,
495
+ onChange = _ref6.onChange;
496
+ const _useState3 = useState(false),
497
+ open = _useState3[0],
498
+ setOpen = _useState3[1];
499
+ const _useState4 = useState(false),
500
+ wheelOpen = _useState4[0],
501
+ setWheelOpen = _useState4[1];
502
+ const isTransparent = value === "transparent";
503
+ return /*#__PURE__*/React.createElement("span", {
504
+ className: "tb-rel"
505
+ }, /*#__PURE__*/React.createElement(Tooltip, {
506
+ title: label
507
+ }, /*#__PURE__*/React.createElement(StyledToolbarButton, {
508
+ className: open ? "icon-only active" : "icon-only",
509
+ "aria-label": label,
510
+ onClick: () => {
511
+ setOpen(current => !current);
512
+ setWheelOpen(false);
513
+ }
514
+ }, isTransparent ? /*#__PURE__*/React.createElement(CircleSlashIcon, {
515
+ width: 16,
516
+ height: 16,
517
+ fill: "currentColor"
518
+ }) : /*#__PURE__*/React.createElement(Dot, {
519
+ color: value,
520
+ size: 16
521
+ }))), open ? /*#__PURE__*/React.createElement(StyledColorPopover, {
522
+ className: "below"
523
+ }, /*#__PURE__*/React.createElement(ColorSwatchRow, {
524
+ selectedColor: value,
525
+ includeTransparent: includeTransparent,
526
+ onSelect: color => {
527
+ onChange(color);
528
+ setOpen(false);
529
+ },
530
+ onAddClick: () => setWheelOpen(current => !current)
531
+ }), wheelOpen ? /*#__PURE__*/React.createElement(StyledColorPopover, {
532
+ className: "beside"
533
+ }, /*#__PURE__*/React.createElement(ColorWheel, {
534
+ onColorChange: color => {
535
+ onChange(color);
536
+ setOpen(false);
537
+ }
538
+ })) : null) : null);
539
+ };
540
+
541
+ /* The text controls shared by the shape and sticky toolbars: font-size stepper,
542
+ B / I / U / S, and the collapsed alignment menu. Same set the app's sticky
543
+ and shape toolbars share (font-size picker, TextStyleButtons, AlignmentMenu).
544
+ Kept generic so both element shapes can drive it. */
545
+
546
+ export const ToolbarTextControls = _ref7 => {
547
+ let fontSize = _ref7.fontSize,
548
+ styleFlags = _ref7.styleFlags,
549
+ align = _ref7.align,
550
+ onFontSize = _ref7.onFontSize,
551
+ onToggleStyle = _ref7.onToggleStyle,
552
+ onAlign = _ref7.onAlign;
553
+ return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Tooltip, {
554
+ title: "Decrease font size"
555
+ }, /*#__PURE__*/React.createElement(StyledToolbarButton, {
556
+ className: "icon-only",
557
+ "aria-label": "Decrease font size",
558
+ onClick: () => onFontSize(Math.max(8, fontSize - 2))
559
+ }, /*#__PURE__*/React.createElement(MinusGlyph, {
560
+ className: "i16"
561
+ }))), /*#__PURE__*/React.createElement(StyledFontSizeValue, null, fontSize), /*#__PURE__*/React.createElement(Tooltip, {
562
+ title: "Increase font size"
563
+ }, /*#__PURE__*/React.createElement(StyledToolbarButton, {
564
+ className: "icon-only",
565
+ "aria-label": "Increase font size",
566
+ onClick: () => onFontSize(Math.min(96, fontSize + 2))
567
+ }, /*#__PURE__*/React.createElement(PlusGlyph, {
568
+ className: "i16"
569
+ }))), STYLE_OPTIONS.map(option => /*#__PURE__*/React.createElement(Tooltip, {
570
+ key: option.key,
571
+ title: option.label
572
+ }, /*#__PURE__*/React.createElement(StyledToolbarButton, {
573
+ className: styleFlags[option.key] ? "icon-only active" : "icon-only",
574
+ "aria-label": option.label,
575
+ "aria-pressed": styleFlags[option.key],
576
+ onClick: () => onToggleStyle(option.key)
577
+ }, option.icon))), /*#__PURE__*/React.createElement(AlignmentMenu, {
578
+ value: align,
579
+ onChange: onAlign
580
+ }));
581
+ };
582
+ /* ShapeFormattingToolbar — flat layout (was submenu-based): shape-type menu →
583
+ fill colour → the same text controls as the sticky toolbar → Lock / Delete.
584
+ Stroke is no longer edited here — it lives only in the Inspector's Stroke
585
+ group now. Mirrors the app's ShapeFormattingToolbar. */
586
+ export const ShapeFormattingToolbar = _ref8 => {
587
+ var _element$align;
588
+ let element = _ref8.element,
589
+ onUpdate = _ref8.onUpdate,
590
+ onDelete = _ref8.onDelete,
591
+ locked = _ref8.locked,
592
+ onToggleLock = _ref8.onToggleLock;
593
+ return /*#__PURE__*/React.createElement(StyledFormattingToolbar, {
594
+ onClick: stopClick,
595
+ onMouseDown: stopClick
596
+ }, /*#__PURE__*/React.createElement(ShapeTypeMenu, {
597
+ value: element.type,
598
+ onChange: type => onUpdate({
599
+ type
600
+ })
601
+ }), /*#__PURE__*/React.createElement(ToolbarColorSwatch, {
602
+ value: element.fill,
603
+ includeTransparent: true,
604
+ label: "Fill color",
605
+ onChange: fill => onUpdate({
606
+ fill
607
+ })
608
+ }), /*#__PURE__*/React.createElement(ToolbarTextControls, {
609
+ fontSize: element.fontSize,
610
+ styleFlags: element,
611
+ align: (_element$align = element.align) != null ? _element$align : "left",
612
+ onFontSize: fontSize => onUpdate({
613
+ fontSize
614
+ }),
615
+ onToggleStyle: key => onUpdate({
616
+ [key]: !element[key]
617
+ }),
618
+ onAlign: align => onUpdate({
619
+ align
620
+ })
621
+ }), /*#__PURE__*/React.createElement(ToolbarActions, {
622
+ locked: locked,
623
+ onToggleLock: onToggleLock,
624
+ onDelete: onDelete
625
+ }));
626
+ };
627
+ /* PostitFormattingToolbar — flat layout: note (fill) colour → the shared text
628
+ controls (font-size, B/I/U/S, alignment menu) → Lock / Delete. The note's
629
+ colour is its whole point, so its fill swatch does NOT offer Transparent.
630
+ Mirrors the app's PostitFormattingToolbar. */
631
+ export const PostitFormattingToolbar = _ref9 => {
632
+ let value = _ref9.value,
633
+ onChange = _ref9.onChange,
634
+ onDelete = _ref9.onDelete,
635
+ locked = _ref9.locked,
636
+ onToggleLock = _ref9.onToggleLock;
637
+ return /*#__PURE__*/React.createElement(StyledFormattingToolbar, {
638
+ onClick: stopClick,
639
+ onMouseDown: stopClick
640
+ }, /*#__PURE__*/React.createElement(ToolbarColorSwatch, {
641
+ value: value.color,
642
+ label: "Note color",
643
+ onChange: color => onChange(_extends({}, value, {
644
+ color
645
+ }))
646
+ }), /*#__PURE__*/React.createElement(ToolbarTextControls, {
647
+ fontSize: value.fontSize,
648
+ styleFlags: value,
649
+ align: value.align,
650
+ onFontSize: fontSize => onChange(_extends({}, value, {
651
+ fontSize
652
+ })),
653
+ onToggleStyle: key => onChange(_extends({}, value, {
654
+ [key]: !value[key]
655
+ })),
656
+ onAlign: align => onChange(_extends({}, value, {
657
+ align
658
+ }))
659
+ }), /*#__PURE__*/React.createElement(ToolbarActions, {
660
+ locked: locked,
661
+ onToggleLock: onToggleLock,
662
+ onDelete: onDelete
663
+ }));
664
+ };
665
+
666
+ /* shape body per current (editable) type */
667
+ export const renderShapeBody = function (def, state,
668
+ /* Effective size — the Inspector can override the element's default width /
669
+ height, so the triangle polygon is drawn from the live size, not the def. */
670
+ width, height) {
671
+ if (width === void 0) {
672
+ width = def.width;
673
+ }
674
+ if (height === void 0) {
675
+ height = def.height;
676
+ }
677
+ return state.type === "triangle" ?
678
+ /*#__PURE__*/
679
+ /* TriangleElement: SVG polygon, apex top-center */
680
+ React.createElement("svg", {
681
+ width: "100%",
682
+ height: "100%",
683
+ style: {
684
+ display: "block",
685
+ overflow: "visible"
686
+ }
687
+ }, /*#__PURE__*/React.createElement("polygon", {
688
+ points: width / 2 + ",0 " + width + "," + height + " 0," + height,
689
+ fill: withOpacity(state.fill, state.fillOpacity),
690
+ stroke: withOpacity(state.stroke, state.strokeOpacity),
691
+ strokeWidth: state.strokeWidth,
692
+ strokeDasharray: state.strokeDash
693
+ })) : /*#__PURE__*/React.createElement("div", {
694
+ className: "shape-fill",
695
+ style: {
696
+ backgroundColor: withOpacity(state.fill, state.fillOpacity),
697
+ border: shapeBorder(state),
698
+ borderRadius: state.type === "circle" ? "50%" : state.borderRadius
699
+ }
700
+ });
701
+ };
702
+ export const ALIGN_OPTIONS = [{
703
+ value: "left",
704
+ label: "Align Left",
705
+ icon: /*#__PURE__*/React.createElement(TextAlignLeftIcon, null)
706
+ }, {
707
+ value: "center",
708
+ label: "Align Center",
709
+ icon: /*#__PURE__*/React.createElement(TextAlignCenterIcon, null)
710
+ }, {
711
+ value: "right",
712
+ label: "Align Right",
713
+ icon: /*#__PURE__*/React.createElement(TextAlignRightIcon, null)
714
+ }, {
715
+ value: "justify",
716
+ label: "Justify",
717
+ icon: /*#__PURE__*/React.createElement(TextAlignJustifyIcon, null)
718
+ }];
719
+ export const STYLE_OPTIONS = [{
720
+ key: "bold",
721
+ label: "Bold",
722
+ icon: /*#__PURE__*/React.createElement(TextStyleBoldIcon, null)
723
+ }, {
724
+ key: "italic",
725
+ label: "Italic",
726
+ icon: /*#__PURE__*/React.createElement(TextStyleItalicIcon, null)
727
+ }, {
728
+ key: "underline",
729
+ label: "Underline",
730
+ icon: /*#__PURE__*/React.createElement(TextStyleUnderlineIcon, null)
731
+ }, {
732
+ key: "strikethrough",
733
+ label: "Strikethrough",
734
+ icon: /*#__PURE__*/React.createElement(TextStyleStrikeIcon, null)
735
+ }];
736
+ //# sourceMappingURL=elements.js.map