canvu-react 0.4.6 → 0.4.7

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/dist/native.js CHANGED
@@ -1,8 +1,8 @@
1
1
  import getStroke from 'perfect-freehand';
2
- import { Group, RoundedRect, Circle, Line, vec, Path, matchFont, Text, Canvas, Rect, Oval } from '@shopify/react-native-skia';
3
- import { memo, forwardRef, useState, useRef, useCallback, useMemo, useImperativeHandle } from 'react';
2
+ import { Group, RoundedRect, Circle, Line, vec, Path, matchFont, Text, Canvas, Rect, Oval, useImage, Image } from '@shopify/react-native-skia';
3
+ import { memo, forwardRef, useState, useRef, useEffect, useCallback, useMemo, useImperativeHandle } from 'react';
4
4
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
5
- import { StyleSheet, PanResponder, View, ScrollView, Text as Text$1, Pressable } from 'react-native';
5
+ import { StyleSheet, PanResponder, View, ScrollView, Pressable, Text as Text$1 } from 'react-native';
6
6
 
7
7
  // src/scene/shape-builders.ts
8
8
 
@@ -893,6 +893,25 @@ function buildRasterImageChildrenSvg(dataUrl, intrinsic, bounds) {
893
893
  const ty = (r.height - ih * s) / 2;
894
894
  return `<g transform="translate(${tx}, ${ty}) scale(${s})"><image href="${href}" x="0" y="0" width="${iw}" height="${ih}" /></g>`;
895
895
  }
896
+ function createImageItem(id, bounds, imageRasterHref, imageIntrinsicSize) {
897
+ const r = normalizeRect(bounds);
898
+ const iw = Math.max(1e-6, imageIntrinsicSize.width);
899
+ const ih = Math.max(1e-6, imageIntrinsicSize.height);
900
+ return {
901
+ id,
902
+ x: r.x,
903
+ y: r.y,
904
+ bounds: { ...r },
905
+ toolKind: "image",
906
+ imageRasterHref,
907
+ imageIntrinsicSize: { width: iw, height: ih },
908
+ childrenSvg: buildRasterImageChildrenSvg(
909
+ imageRasterHref,
910
+ { width: iw, height: ih },
911
+ r
912
+ )
913
+ };
914
+ }
896
915
 
897
916
  // src/math/item-transform.ts
898
917
  function getItemRotationRad(item) {
@@ -1281,6 +1300,9 @@ function SvgNodeItem({ node }) {
1281
1300
  }
1282
1301
  );
1283
1302
  }
1303
+ case "image": {
1304
+ return /* @__PURE__ */ jsx(SvgImageNodeItem, { node });
1305
+ }
1284
1306
  case "g": {
1285
1307
  const transform = node.transform ? parseSvgTransform(node.transform) : void 0;
1286
1308
  return /* @__PURE__ */ jsx(Group, { transform, children: node.children.map((child, i) => /* @__PURE__ */ jsx(SvgNodeItem, { node: child }, i)) });
@@ -1292,6 +1314,23 @@ function SvgNodeItem({ node }) {
1292
1314
  return null;
1293
1315
  }
1294
1316
  }
1317
+ function SvgImageNodeItem({
1318
+ node
1319
+ }) {
1320
+ const image = useImage(node.href);
1321
+ if (!image) return null;
1322
+ return /* @__PURE__ */ jsx(
1323
+ Image,
1324
+ {
1325
+ image,
1326
+ x: toNum(node.x),
1327
+ y: toNum(node.y),
1328
+ width: toNum(node.width),
1329
+ height: toNum(node.height),
1330
+ fit: "contain"
1331
+ }
1332
+ );
1333
+ }
1295
1334
  function polygonPointsToPath(points) {
1296
1335
  const nums = points.split(/[\s,]+/).filter(Boolean).map(Number);
1297
1336
  if (nums.length < 4) return "";
@@ -2194,146 +2233,353 @@ function NativeSceneRenderer({
2194
2233
  if (width <= 0 || height <= 0) return null;
2195
2234
  return /* @__PURE__ */ jsx(Canvas, { style: { width, height }, children: /* @__PURE__ */ jsx(Group, { transform: cameraTransform, children: visible.map((item) => /* @__PURE__ */ jsx(MemoShape, { item }, item.id)) }) });
2196
2235
  }
2236
+ var DEFAULT_NATIVE_OVERFLOW_TOOL_IDS = [
2237
+ "rect",
2238
+ "ellipse",
2239
+ "architectural-cloud",
2240
+ "line",
2241
+ "marker",
2242
+ "laser",
2243
+ "image"
2244
+ ];
2197
2245
  var DEFAULT_NATIVE_VECTOR_TOOLS = [
2198
- { id: "hand", label: "Hand", shortLabel: "H" },
2199
- { id: "select", label: "Select", shortLabel: "V" },
2200
- { id: "draw", label: "Draw", shortLabel: "D" },
2201
- { id: "marker", label: "Marker", shortLabel: "M" },
2202
- { id: "eraser", label: "Eraser", shortLabel: "E" },
2203
- { id: "text", label: "Text", shortLabel: "T" },
2204
- { id: "note", label: "Note", shortLabel: "N" },
2205
- { id: "rect", label: "Rectangle", shortLabel: "R" },
2206
- { id: "ellipse", label: "Ellipse", shortLabel: "O" },
2207
- { id: "architectural-cloud", label: "Cloud", shortLabel: "C" },
2208
- { id: "line", label: "Line", shortLabel: "L" },
2209
- { id: "arrow", label: "Arrow", shortLabel: "A" }
2246
+ { id: "hand", label: "Hand", shortcutHint: "H", shortLabel: "H" },
2247
+ { id: "select", label: "Select", shortcutHint: "V", shortLabel: "V" },
2248
+ { id: "rect", label: "Rectangle", shortcutHint: "R", shortLabel: "R" },
2249
+ { id: "ellipse", label: "Ellipse", shortcutHint: "O", shortLabel: "O" },
2250
+ {
2251
+ id: "architectural-cloud",
2252
+ label: "Nuvem arquitetural",
2253
+ tooltipLabel: "Architectural cloud",
2254
+ shortcutHint: "C",
2255
+ shortLabel: "C"
2256
+ },
2257
+ { id: "line", label: "Line", shortcutHint: "L", shortLabel: "L" },
2258
+ { id: "arrow", label: "Arrow", shortcutHint: "A", shortLabel: "A" },
2259
+ {
2260
+ id: "draw",
2261
+ label: "Desenhar",
2262
+ tooltipLabel: "Draw",
2263
+ shortcutHint: "D",
2264
+ shortLabel: "D"
2265
+ },
2266
+ {
2267
+ id: "marker",
2268
+ label: "Realce",
2269
+ tooltipLabel: "Highlighter",
2270
+ shortcutHint: "M",
2271
+ shortLabel: "M"
2272
+ },
2273
+ { id: "laser", label: "Laser", shortcutHint: "K", shortLabel: "K" },
2274
+ {
2275
+ id: "eraser",
2276
+ label: "Borracha",
2277
+ tooltipLabel: "Eraser",
2278
+ shortcutHint: "E",
2279
+ shortLabel: "E"
2280
+ },
2281
+ { id: "text", label: "Text", shortcutHint: "T", shortLabel: "T" },
2282
+ { id: "image", label: "File", shortcutHint: "I", shortLabel: "I" }
2210
2283
  ];
2284
+ function splitToolbarTools(tools, overflowToolIds) {
2285
+ const overflowIds = new Set(overflowToolIds);
2286
+ if (overflowIds.size === 0) return { primary: tools, overflow: [] };
2287
+ return {
2288
+ primary: tools.filter((tool) => !overflowIds.has(tool.id)),
2289
+ overflow: tools.filter((tool) => overflowIds.has(tool.id))
2290
+ };
2291
+ }
2292
+ function getPromotedOverflowTool(input) {
2293
+ const selectedOverflowTool = input.overflowTools.find((tool) => tool.id === input.selectedId) ?? null;
2294
+ const fallbackTool = input.overflowTools.find((tool) => tool.id === input.initialPromotedId) ?? input.overflowTools[0] ?? null;
2295
+ const promotedTool = selectedOverflowTool ?? fallbackTool;
2296
+ if (!promotedTool) {
2297
+ return { promotedTool: null, remainingOverflowTools: input.overflowTools };
2298
+ }
2299
+ return {
2300
+ promotedTool,
2301
+ remainingOverflowTools: input.overflowTools.filter(
2302
+ (tool) => tool.id !== promotedTool.id
2303
+ )
2304
+ };
2305
+ }
2306
+ function tooltipTextForTool(tool) {
2307
+ const name = tool.tooltipLabel ?? tool.label;
2308
+ return tool.shortcutHint ? `${name} - ${tool.shortcutHint}` : name;
2309
+ }
2211
2310
  function NativeVectorToolbar({
2212
2311
  value,
2213
2312
  onChange,
2214
2313
  tools = DEFAULT_NATIVE_VECTOR_TOOLS,
2314
+ overflowToolIds = DEFAULT_NATIVE_OVERFLOW_TOOL_IDS,
2315
+ overflowMenuAccessibilityLabel = "More tools",
2215
2316
  disabled = false,
2216
2317
  disabledToolIds = [],
2318
+ showToolLockToggle = true,
2319
+ toolLocked = false,
2320
+ onToolLockedChange,
2321
+ density = "compact",
2217
2322
  accessibilityLabel = "Canvas tools",
2218
2323
  style,
2219
2324
  contentContainerStyle,
2325
+ overflowPanelStyle,
2220
2326
  toolButtonStyle,
2221
2327
  activeToolButtonStyle,
2222
2328
  toolLabelStyle,
2223
2329
  activeToolLabelStyle,
2224
2330
  renderToolIcon,
2331
+ renderToolLockIcon,
2332
+ renderOverflowIcon,
2333
+ renderOverflowChevronIcon,
2225
2334
  renderToolButton
2226
2335
  }) {
2336
+ const [overflowOpen, setOverflowOpen] = useState(false);
2227
2337
  const disabledIds = useMemo(() => new Set(disabledToolIds), [disabledToolIds]);
2228
- return /* @__PURE__ */ jsx(View, { accessibilityLabel, style: [styles.shell, style], children: /* @__PURE__ */ jsx(
2229
- ScrollView,
2338
+ const { primary: primaryTools, overflow: overflowTools } = useMemo(
2339
+ () => splitToolbarTools(tools, overflowToolIds),
2340
+ [tools, overflowToolIds]
2341
+ );
2342
+ const { promotedTool, remainingOverflowTools } = useMemo(
2343
+ () => getPromotedOverflowTool({
2344
+ overflowTools,
2345
+ selectedId: value,
2346
+ initialPromotedId: overflowToolIds[0] ?? null
2347
+ }),
2348
+ [overflowTools, overflowToolIds, value]
2349
+ );
2350
+ const activeOverflowTool = overflowTools.find((tool) => tool.id === value) ?? null;
2351
+ const toolbarTools = promotedTool !== null ? [...primaryTools, promotedTool] : primaryTools;
2352
+ const showOverflowMenu = remainingOverflowTools.length > 0;
2353
+ const toolLockDisabled = disabled || !onToolLockedChange;
2354
+ const toggleToolLock = () => onToolLockedChange?.(!toolLocked);
2355
+ const toggleOverflow = () => setOverflowOpen((open) => !open);
2356
+ const overflowRenderInput = {
2357
+ open: overflowOpen,
2358
+ activeTool: activeOverflowTool,
2359
+ disabled,
2360
+ foregroundColor: "#18181b",
2361
+ onToggle: toggleOverflow
2362
+ };
2363
+ return /* @__PURE__ */ jsxs(
2364
+ View,
2230
2365
  {
2231
- horizontal: true,
2232
- showsHorizontalScrollIndicator: false,
2233
- contentContainerStyle: [styles.content, contentContainerStyle],
2234
- children: tools.map((tool) => {
2235
- const selected = tool.id === value;
2236
- const toolDisabled = disabled || tool.disabled || disabledIds.has(tool.id);
2237
- const foregroundColor = selected ? "#fafaf9" : "#18181b";
2238
- const onSelect = () => {
2239
- if (!toolDisabled) {
2240
- onChange(tool.id);
2241
- }
2242
- };
2243
- const input = {
2244
- tool,
2245
- selected,
2246
- disabled: toolDisabled,
2247
- foregroundColor,
2248
- onSelect
2249
- };
2250
- if (renderToolButton) {
2251
- return /* @__PURE__ */ jsx(View, { children: renderToolButton(input) }, tool.id);
2252
- }
2253
- const icon = renderToolIcon?.(input) ?? /* @__PURE__ */ jsx(
2254
- Text$1,
2366
+ accessibilityLabel,
2367
+ style: [styles.shell, style],
2368
+ pointerEvents: "box-none",
2369
+ children: [
2370
+ /* @__PURE__ */ jsxs(
2371
+ ScrollView,
2255
2372
  {
2256
- style: [
2257
- styles.shortLabel,
2258
- { color: foregroundColor },
2259
- toolLabelStyle,
2260
- selected ? activeToolLabelStyle : void 0
2261
- ],
2262
- children: tool.shortLabel ?? tool.label.slice(0, 1).toUpperCase()
2263
- }
2264
- );
2265
- return /* @__PURE__ */ jsxs(
2266
- Pressable,
2267
- {
2268
- accessibilityLabel: tool.accessibilityLabel ?? tool.label,
2269
- accessibilityRole: "button",
2270
- accessibilityState: { selected, disabled: toolDisabled },
2271
- disabled: toolDisabled,
2272
- onPress: onSelect,
2273
- style: ({ pressed }) => [
2274
- styles.toolButton,
2275
- toolButtonStyle,
2276
- selected ? styles.activeToolButton : void 0,
2277
- selected ? activeToolButtonStyle : void 0,
2278
- pressed && !toolDisabled ? styles.pressedToolButton : void 0,
2279
- toolDisabled ? styles.disabledToolButton : void 0
2373
+ horizontal: true,
2374
+ showsHorizontalScrollIndicator: false,
2375
+ contentContainerStyle: [
2376
+ styles.content,
2377
+ density === "comfortable" ? styles.comfortableContent : void 0,
2378
+ contentContainerStyle
2280
2379
  ],
2281
2380
  children: [
2282
- /* @__PURE__ */ jsx(View, { style: styles.iconSlot, children: icon }),
2283
- /* @__PURE__ */ jsx(
2284
- Text$1,
2381
+ showToolLockToggle ? /* @__PURE__ */ jsxs(Fragment, { children: [
2382
+ /* @__PURE__ */ jsx(
2383
+ Pressable,
2384
+ {
2385
+ accessibilityLabel: toolLocked ? "Unlock tool" : "Lock tool",
2386
+ accessibilityRole: "button",
2387
+ accessibilityState: {
2388
+ checked: toolLocked,
2389
+ disabled: toolLockDisabled
2390
+ },
2391
+ disabled: toolLockDisabled,
2392
+ onPress: toggleToolLock,
2393
+ style: ({ pressed }) => [
2394
+ styles.toolButton,
2395
+ density === "comfortable" ? styles.comfortableToolButton : void 0,
2396
+ toolLocked ? styles.activeToolButton : void 0,
2397
+ pressed && !toolLockDisabled ? styles.pressedToolButton : void 0,
2398
+ toolLockDisabled ? styles.disabledToolButton : void 0
2399
+ ],
2400
+ children: renderToolLockIcon?.({
2401
+ locked: toolLocked,
2402
+ disabled: toolLockDisabled,
2403
+ foregroundColor: "#18181b",
2404
+ onToggle: toggleToolLock
2405
+ }) ?? /* @__PURE__ */ jsx(Text$1, { style: styles.lockGlyph, children: toolLocked ? "L" : "U" })
2406
+ }
2407
+ ),
2408
+ /* @__PURE__ */ jsx(View, { style: styles.toolLockDivider })
2409
+ ] }) : null,
2410
+ toolbarTools.map(
2411
+ (tool) => renderNativeToolButton({
2412
+ tool,
2413
+ value,
2414
+ onChange,
2415
+ disabled,
2416
+ disabledIds,
2417
+ density,
2418
+ toolButtonStyle,
2419
+ activeToolButtonStyle,
2420
+ toolLabelStyle,
2421
+ activeToolLabelStyle,
2422
+ renderToolIcon,
2423
+ renderToolButton
2424
+ })
2425
+ ),
2426
+ showOverflowMenu ? /* @__PURE__ */ jsx(View, { style: styles.overflowSpacer }) : null,
2427
+ showOverflowMenu ? /* @__PURE__ */ jsxs(
2428
+ Pressable,
2285
2429
  {
2286
- numberOfLines: 1,
2287
- style: [
2288
- styles.toolLabel,
2289
- { color: foregroundColor },
2290
- toolLabelStyle,
2291
- selected ? activeToolLabelStyle : void 0
2430
+ accessibilityLabel: activeOverflowTool ? `${overflowMenuAccessibilityLabel}: ${activeOverflowTool.accessibilityLabel ?? activeOverflowTool.label}` : overflowMenuAccessibilityLabel,
2431
+ accessibilityRole: "button",
2432
+ accessibilityState: {
2433
+ expanded: overflowOpen,
2434
+ selected: overflowOpen || activeOverflowTool !== null,
2435
+ disabled
2436
+ },
2437
+ disabled,
2438
+ onPress: toggleOverflow,
2439
+ style: ({ pressed }) => [
2440
+ styles.overflowTrigger,
2441
+ overflowOpen || activeOverflowTool ? styles.activeToolButton : void 0,
2442
+ pressed && !disabled ? styles.pressedToolButton : void 0,
2443
+ disabled ? styles.disabledToolButton : void 0
2292
2444
  ],
2293
- children: tool.label
2445
+ children: [
2446
+ /* @__PURE__ */ jsx(View, { style: styles.iconSlot, children: activeOverflowTool ? renderToolIcon?.({
2447
+ tool: activeOverflowTool,
2448
+ selected: true,
2449
+ disabled,
2450
+ foregroundColor: "#18181b",
2451
+ onSelect: () => onChange(activeOverflowTool.id)
2452
+ }) ?? renderNativeToolFallback(activeOverflowTool, "#18181b") : renderOverflowIcon?.(overflowRenderInput) ?? /* @__PURE__ */ jsx(Text$1, { style: styles.shapesGlyph, children: "S" }) }),
2453
+ renderOverflowChevronIcon?.(overflowRenderInput) ?? /* @__PURE__ */ jsx(Text$1, { style: styles.chevronGlyph, children: overflowOpen ? "^" : "v" })
2454
+ ]
2294
2455
  }
2295
- )
2456
+ ) : null
2296
2457
  ]
2297
- },
2298
- tool.id
2299
- );
2300
- })
2458
+ }
2459
+ ),
2460
+ overflowOpen && showOverflowMenu ? /* @__PURE__ */ jsx(View, { style: [styles.overflowPanel, overflowPanelStyle], children: remainingOverflowTools.map(
2461
+ (tool) => renderNativeToolButton({
2462
+ tool,
2463
+ value,
2464
+ onChange: (toolId) => {
2465
+ setOverflowOpen(false);
2466
+ onChange(toolId);
2467
+ },
2468
+ disabled,
2469
+ disabledIds,
2470
+ density: "compact",
2471
+ toolButtonStyle: styles.overflowToolButton,
2472
+ activeToolButtonStyle,
2473
+ toolLabelStyle,
2474
+ activeToolLabelStyle,
2475
+ renderToolIcon,
2476
+ renderToolButton
2477
+ })
2478
+ ) }) : null
2479
+ ]
2480
+ }
2481
+ );
2482
+ }
2483
+ function renderNativeToolButton(input) {
2484
+ const selected = input.tool.id === input.value;
2485
+ const toolDisabled = input.disabled || input.tool.disabled || input.disabledIds.has(input.tool.id);
2486
+ const foregroundColor = "#18181b";
2487
+ const onSelect = () => {
2488
+ if (!toolDisabled) {
2489
+ input.onChange(input.tool.id);
2301
2490
  }
2302
- ) });
2491
+ };
2492
+ const renderInput = {
2493
+ tool: input.tool,
2494
+ selected,
2495
+ disabled: toolDisabled,
2496
+ foregroundColor,
2497
+ onSelect
2498
+ };
2499
+ if (input.renderToolButton) {
2500
+ return /* @__PURE__ */ jsx(View, { children: input.renderToolButton(renderInput) }, input.tool.id);
2501
+ }
2502
+ const icon = input.renderToolIcon?.(renderInput) ?? renderNativeToolFallback(input.tool, foregroundColor, input.toolLabelStyle);
2503
+ return /* @__PURE__ */ jsxs(
2504
+ Pressable,
2505
+ {
2506
+ accessibilityLabel: input.tool.accessibilityLabel ?? tooltipTextForTool(input.tool),
2507
+ accessibilityRole: "button",
2508
+ accessibilityState: { selected, disabled: toolDisabled },
2509
+ disabled: toolDisabled,
2510
+ onPress: onSelect,
2511
+ style: ({ pressed }) => [
2512
+ styles.toolButton,
2513
+ input.density === "comfortable" ? styles.comfortableToolButton : void 0,
2514
+ input.toolButtonStyle,
2515
+ selected ? styles.activeToolButton : void 0,
2516
+ selected ? input.activeToolButtonStyle : void 0,
2517
+ pressed && !toolDisabled ? styles.pressedToolButton : void 0,
2518
+ toolDisabled ? styles.disabledToolButton : void 0
2519
+ ],
2520
+ children: [
2521
+ /* @__PURE__ */ jsx(View, { style: styles.iconSlot, children: icon }),
2522
+ input.density === "comfortable" ? /* @__PURE__ */ jsx(
2523
+ Text$1,
2524
+ {
2525
+ numberOfLines: 1,
2526
+ style: [
2527
+ styles.toolLabel,
2528
+ { color: foregroundColor },
2529
+ input.toolLabelStyle,
2530
+ selected ? input.activeToolLabelStyle : void 0
2531
+ ],
2532
+ children: input.tool.label
2533
+ }
2534
+ ) : null
2535
+ ]
2536
+ },
2537
+ input.tool.id
2538
+ );
2539
+ }
2540
+ function renderNativeToolFallback(tool, foregroundColor, toolLabelStyle) {
2541
+ return /* @__PURE__ */ jsx(Text$1, { style: [styles.shortLabel, { color: foregroundColor }, toolLabelStyle], children: tool.shortLabel ?? tool.label.slice(0, 1).toUpperCase() });
2303
2542
  }
2304
2543
  var styles = StyleSheet.create({
2305
2544
  shell: {
2306
- borderRadius: 10,
2545
+ borderRadius: 8,
2307
2546
  borderWidth: StyleSheet.hairlineWidth,
2308
- borderColor: "rgba(24, 24, 27, 0.14)",
2309
- backgroundColor: "rgba(255, 255, 255, 0.96)",
2547
+ borderColor: "rgba(0, 0, 0, 0.12)",
2548
+ backgroundColor: "rgba(255, 255, 255, 0.95)",
2310
2549
  shadowColor: "#18181b",
2311
- shadowOpacity: 0.12,
2312
- shadowRadius: 16,
2313
- shadowOffset: { width: 0, height: 8 },
2314
- elevation: 6
2550
+ shadowOpacity: 0.08,
2551
+ shadowRadius: 3,
2552
+ shadowOffset: { width: 0, height: 1 },
2553
+ elevation: 3
2315
2554
  },
2316
2555
  content: {
2317
2556
  alignItems: "center",
2318
- gap: 6,
2319
- paddingHorizontal: 6,
2557
+ gap: 4,
2558
+ paddingHorizontal: 8,
2320
2559
  paddingVertical: 6
2321
2560
  },
2561
+ comfortableContent: {
2562
+ gap: 6
2563
+ },
2322
2564
  toolButton: {
2323
- minWidth: 58,
2324
- height: 52,
2565
+ minWidth: 40,
2566
+ height: 40,
2325
2567
  alignItems: "center",
2326
2568
  justifyContent: "center",
2327
- gap: 3,
2328
- borderRadius: 8,
2569
+ gap: 2,
2570
+ borderRadius: 6,
2329
2571
  paddingHorizontal: 8,
2330
2572
  borderWidth: StyleSheet.hairlineWidth,
2331
2573
  borderColor: "transparent",
2332
2574
  backgroundColor: "transparent"
2333
2575
  },
2576
+ comfortableToolButton: {
2577
+ minWidth: 56,
2578
+ height: 48
2579
+ },
2334
2580
  activeToolButton: {
2335
- borderColor: "rgba(24, 24, 27, 0.24)",
2336
- backgroundColor: "#18181b"
2581
+ borderColor: "rgba(24, 24, 27, 0.28)",
2582
+ backgroundColor: "rgba(24, 24, 27, 0.1)"
2337
2583
  },
2338
2584
  pressedToolButton: {
2339
2585
  backgroundColor: "rgba(24, 24, 27, 0.08)"
@@ -2357,6 +2603,76 @@ var styles = StyleSheet.create({
2357
2603
  fontSize: 10,
2358
2604
  fontWeight: "600",
2359
2605
  lineHeight: 12
2606
+ },
2607
+ toolLockDivider: {
2608
+ width: StyleSheet.hairlineWidth,
2609
+ alignSelf: "stretch",
2610
+ backgroundColor: "rgba(24, 24, 27, 0.14)",
2611
+ marginHorizontal: 6,
2612
+ marginVertical: 4
2613
+ },
2614
+ lockGlyph: {
2615
+ color: "#18181b",
2616
+ fontSize: 13,
2617
+ fontWeight: "700",
2618
+ lineHeight: 16
2619
+ },
2620
+ overflowSpacer: {
2621
+ minWidth: 8,
2622
+ flexGrow: 1
2623
+ },
2624
+ overflowTrigger: {
2625
+ minWidth: 48,
2626
+ height: 40,
2627
+ flexDirection: "row",
2628
+ alignItems: "center",
2629
+ justifyContent: "center",
2630
+ gap: 4,
2631
+ borderRadius: 6,
2632
+ paddingHorizontal: 8,
2633
+ borderWidth: StyleSheet.hairlineWidth,
2634
+ borderColor: "transparent",
2635
+ backgroundColor: "transparent"
2636
+ },
2637
+ overflowPanel: {
2638
+ position: "absolute",
2639
+ right: 8,
2640
+ bottom: 52,
2641
+ width: 148,
2642
+ flexDirection: "row",
2643
+ flexWrap: "wrap",
2644
+ gap: 6,
2645
+ padding: 10,
2646
+ borderRadius: 10,
2647
+ borderWidth: StyleSheet.hairlineWidth,
2648
+ borderColor: "rgba(0, 0, 0, 0.1)",
2649
+ backgroundColor: "rgba(255, 255, 255, 0.98)",
2650
+ shadowColor: "#18181b",
2651
+ shadowOpacity: 0.12,
2652
+ shadowRadius: 24,
2653
+ shadowOffset: { width: 0, height: -4 },
2654
+ elevation: 8
2655
+ },
2656
+ overflowToolButton: {
2657
+ width: 40,
2658
+ minWidth: 40,
2659
+ height: 40,
2660
+ paddingHorizontal: 0,
2661
+ borderRadius: 8,
2662
+ backgroundColor: "rgba(24, 24, 27, 0.05)"
2663
+ },
2664
+ shapesGlyph: {
2665
+ color: "#18181b",
2666
+ fontSize: 17,
2667
+ fontWeight: "700",
2668
+ lineHeight: 20
2669
+ },
2670
+ chevronGlyph: {
2671
+ color: "#18181b",
2672
+ fontSize: 13,
2673
+ fontWeight: "700",
2674
+ lineHeight: 16,
2675
+ opacity: 0.75
2360
2676
  }
2361
2677
  });
2362
2678
 
@@ -2608,9 +2924,11 @@ var NativeVectorViewport = forwardRef(function NativeVectorViewport2({
2608
2924
  items,
2609
2925
  selectedIds = [],
2610
2926
  toolId = "hand",
2927
+ toolLocked = false,
2611
2928
  interactive = false,
2612
2929
  onSelectionChange,
2613
2930
  onItemsChange,
2931
+ onToolChangeRequest,
2614
2932
  onCameraChange,
2615
2933
  toolbar
2616
2934
  }, ref) {
@@ -2618,6 +2936,10 @@ var NativeVectorViewport = forwardRef(function NativeVectorViewport2({
2618
2936
  const cameraRef = useRef(null);
2619
2937
  const toolIdRef = useRef(toolId);
2620
2938
  toolIdRef.current = toolId;
2939
+ const toolLockedRef = useRef(toolLocked);
2940
+ toolLockedRef.current = toolLocked;
2941
+ const onToolChangeRequestRef = useRef(onToolChangeRequest);
2942
+ onToolChangeRequestRef.current = onToolChangeRequest;
2621
2943
  const onCameraChangeRef = useRef(onCameraChange);
2622
2944
  onCameraChangeRef.current = onCameraChange;
2623
2945
  const onItemsChangeRef = useRef(onItemsChange);
@@ -2633,8 +2955,23 @@ var NativeVectorViewport = forwardRef(function NativeVectorViewport2({
2633
2955
  null
2634
2956
  );
2635
2957
  const [eraserTrail, setEraserTrail] = useState([]);
2958
+ const [laserTrail, setLaserTrail] = useState([]);
2959
+ const laserClearTimerRef = useRef(null);
2960
+ useEffect(
2961
+ () => () => {
2962
+ if (laserClearTimerRef.current) {
2963
+ clearTimeout(laserClearTimerRef.current);
2964
+ }
2965
+ },
2966
+ []
2967
+ );
2636
2968
  const [eraserPreviewIds, setEraserPreviewIds] = useState([]);
2637
2969
  const eraserPreviewIdSetRef = useRef(/* @__PURE__ */ new Set());
2970
+ const requestSelectToolAfterUse = useCallback(() => {
2971
+ if (!toolLockedRef.current) {
2972
+ onToolChangeRequestRef.current?.("select");
2973
+ }
2974
+ }, []);
2638
2975
  if (!cameraRef.current) {
2639
2976
  cameraRef.current = new Camera2D({ minZoom: 0.05, maxZoom: 32 });
2640
2977
  }
@@ -2729,12 +3066,19 @@ var NativeVectorViewport = forwardRef(function NativeVectorViewport2({
2729
3066
  }
2730
3067
  return;
2731
3068
  }
2732
- if (tool === "draw" || tool === "marker") {
3069
+ if (tool === "draw" || tool === "marker" || tool === "laser") {
2733
3070
  dragStateRef.current = {
2734
3071
  kind: "draw",
2735
3072
  tool,
2736
3073
  points: [{ x: worldX, y: worldY }]
2737
3074
  };
3075
+ if (tool === "laser") {
3076
+ if (laserClearTimerRef.current) {
3077
+ clearTimeout(laserClearTimerRef.current);
3078
+ laserClearTimerRef.current = null;
3079
+ }
3080
+ setLaserTrail([{ x: worldX, y: worldY }]);
3081
+ }
2738
3082
  return;
2739
3083
  }
2740
3084
  if (tool === "eraser") {
@@ -2835,11 +3179,15 @@ var NativeVectorViewport = forwardRef(function NativeVectorViewport2({
2835
3179
  if (Math.hypot(dx, dy) > 0.5 / cam.zoom) {
2836
3180
  pts.push({ x: worldX, y: worldY });
2837
3181
  }
2838
- setPlacementPreview({
2839
- kind: "stroke",
2840
- tool: st.tool,
2841
- points: [...pts]
2842
- });
3182
+ if (st.tool === "laser") {
3183
+ setLaserTrail([...pts]);
3184
+ } else {
3185
+ setPlacementPreview({
3186
+ kind: "stroke",
3187
+ tool: st.tool,
3188
+ points: [...pts]
3189
+ });
3190
+ }
2843
3191
  return;
2844
3192
  }
2845
3193
  if (st.kind === "move") {
@@ -2907,6 +3255,17 @@ var NativeVectorViewport = forwardRef(function NativeVectorViewport2({
2907
3255
  if (st.kind === "draw") {
2908
3256
  dragStateRef.current = { kind: "idle" };
2909
3257
  setPlacementPreview(null);
3258
+ if (st.tool === "laser") {
3259
+ if (laserClearTimerRef.current) {
3260
+ clearTimeout(laserClearTimerRef.current);
3261
+ }
3262
+ laserClearTimerRef.current = setTimeout(() => {
3263
+ setLaserTrail([]);
3264
+ laserClearTimerRef.current = null;
3265
+ }, 650);
3266
+ requestSelectToolAfterUse();
3267
+ return;
3268
+ }
2910
3269
  if (st.points.length < 1) return;
2911
3270
  const change = onItemsChangeRef.current;
2912
3271
  if (!change) return;
@@ -2915,6 +3274,7 @@ var NativeVectorViewport = forwardRef(function NativeVectorViewport2({
2915
3274
  if (item) {
2916
3275
  change([...itemsRef.current, item]);
2917
3276
  }
3277
+ requestSelectToolAfterUse();
2918
3278
  return;
2919
3279
  }
2920
3280
  if (st.kind === "move") {
@@ -2952,6 +3312,7 @@ var NativeVectorViewport = forwardRef(function NativeVectorViewport2({
2952
3312
  setEraserPreviewIds([]);
2953
3313
  setEraserTrail([]);
2954
3314
  dragStateRef.current = { kind: "idle" };
3315
+ requestSelectToolAfterUse();
2955
3316
  return;
2956
3317
  }
2957
3318
  if (st.kind === "place") {
@@ -2989,21 +3350,25 @@ var NativeVectorViewport = forwardRef(function NativeVectorViewport2({
2989
3350
  if (st.tool === "rect") {
2990
3351
  change([...itemsRef.current, createRectangleItem(id, raw)]);
2991
3352
  onSelectionChangeRef.current?.([id]);
3353
+ requestSelectToolAfterUse();
2992
3354
  return;
2993
3355
  }
2994
3356
  if (st.tool === "ellipse") {
2995
3357
  change([...itemsRef.current, createEllipseItem(id, raw)]);
2996
3358
  onSelectionChangeRef.current?.([id]);
3359
+ requestSelectToolAfterUse();
2997
3360
  return;
2998
3361
  }
2999
3362
  if (st.tool === "architectural-cloud") {
3000
3363
  change([...itemsRef.current, createArchitecturalCloudItem(id, raw)]);
3001
3364
  onSelectionChangeRef.current?.([id]);
3365
+ requestSelectToolAfterUse();
3002
3366
  return;
3003
3367
  }
3004
3368
  const line = lineEndpointsToLocal(br, lineStart, lineEnd);
3005
3369
  change([...itemsRef.current, createLineItem(id, br, line, st.tool)]);
3006
3370
  onSelectionChangeRef.current?.([id]);
3371
+ requestSelectToolAfterUse();
3007
3372
  return;
3008
3373
  }
3009
3374
  if (st.kind === "tap") {
@@ -3029,6 +3394,7 @@ var NativeVectorViewport = forwardRef(function NativeVectorViewport2({
3029
3394
  );
3030
3395
  change([...itemsRef.current, item]);
3031
3396
  onSelectionChangeRef.current?.([id]);
3397
+ requestSelectToolAfterUse();
3032
3398
  }
3033
3399
  if (st.tool === "note") {
3034
3400
  const id = createShapeId();
@@ -3047,6 +3413,7 @@ var NativeVectorViewport = forwardRef(function NativeVectorViewport2({
3047
3413
  };
3048
3414
  change([...itemsRef.current, note]);
3049
3415
  onSelectionChangeRef.current?.([id]);
3416
+ requestSelectToolAfterUse();
3050
3417
  }
3051
3418
  return;
3052
3419
  }
@@ -3057,9 +3424,10 @@ var NativeVectorViewport = forwardRef(function NativeVectorViewport2({
3057
3424
  lastPanPoint.current = null;
3058
3425
  dragStateRef.current = { kind: "idle" };
3059
3426
  setPlacementPreview(null);
3427
+ setLaserTrail([]);
3060
3428
  }
3061
3429
  }),
3062
- [screenToWorld, requestRender, interactive]
3430
+ [screenToWorld, requestRender, requestSelectToolAfterUse, interactive]
3063
3431
  );
3064
3432
  useImperativeHandle(
3065
3433
  ref,
@@ -3107,6 +3475,7 @@ var NativeVectorViewport = forwardRef(function NativeVectorViewport2({
3107
3475
  selectedItems,
3108
3476
  showResizeHandles,
3109
3477
  placementPreview,
3478
+ laserTrail,
3110
3479
  eraserTrail,
3111
3480
  eraserPreviewItems: items.filter(
3112
3481
  (it) => eraserPreviewIds.includes(it.id)
@@ -3134,6 +3503,6 @@ var NativeVectorViewport = forwardRef(function NativeVectorViewport2({
3134
3503
  );
3135
3504
  });
3136
3505
 
3137
- export { DEFAULT_NATIVE_VECTOR_TOOLS, NativeInteractionOverlay, NativeSceneRenderer, NativeShapeRenderer, NativeVectorToolbar, NativeVectorViewport, createFreehandStrokeItem, createShapeId, parseSvgFragment };
3506
+ export { DEFAULT_NATIVE_OVERFLOW_TOOL_IDS, DEFAULT_NATIVE_VECTOR_TOOLS, NativeInteractionOverlay, NativeSceneRenderer, NativeShapeRenderer, NativeVectorToolbar, NativeVectorViewport, createFreehandStrokeItem, createImageItem, createShapeId, parseSvgFragment };
3138
3507
  //# sourceMappingURL=native.js.map
3139
3508
  //# sourceMappingURL=native.js.map