@reekon-tools/boldr-utils 1.8.3 → 1.8.5

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 (75) hide show
  1. package/dist/annotation/canvas/AnnotationCanvasInner.native.js +41 -10
  2. package/dist/annotation/canvas/measurementGeometry.js +8 -8
  3. package/dist/annotation/canvas/stampLayout.js +59 -54
  4. package/dist/annotation/canvas/useAnnotationCanvasState.d.ts +4 -1
  5. package/dist/annotation/canvas/useAnnotationCanvasState.js +88 -38
  6. package/dist/calculator/evaluate.js +38 -4
  7. package/dist/calculator/schema.d.ts +12 -0
  8. package/dist/calculator/schema.js +11 -5
  9. package/dist/calculator/validate.d.ts +2 -0
  10. package/dist/calculator/validate.js +43 -1
  11. package/dist/exports.d.ts +1 -0
  12. package/dist/exports.js +2 -0
  13. package/dist/theme/colors.d.ts +101 -0
  14. package/dist/theme/colors.js +101 -0
  15. package/package.json +7 -2
  16. package/dist/canvas/AnnotationCanvas.d.ts +0 -11
  17. package/dist/canvas/AnnotationCanvas.js +0 -10
  18. package/dist/canvas/AnnotationCanvas.native.d.ts +0 -8
  19. package/dist/canvas/AnnotationCanvas.native.js +0 -6
  20. package/dist/canvas/AnnotationCanvasInner.d.ts +0 -39
  21. package/dist/canvas/AnnotationCanvasInner.js +0 -219
  22. package/dist/canvas/AnnotationCanvasInner.native.d.ts +0 -35
  23. package/dist/canvas/AnnotationCanvasInner.native.js +0 -138
  24. package/dist/canvas/AnnotationCanvasSkia.d.ts +0 -27
  25. package/dist/canvas/AnnotationCanvasSkia.js +0 -20
  26. package/dist/canvas/Tool.d.ts +0 -38
  27. package/dist/canvas/Tool.js +0 -1
  28. package/dist/canvas/elements/BackgroundImageElement.d.ts +0 -9
  29. package/dist/canvas/elements/BackgroundImageElement.js +0 -37
  30. package/dist/canvas/elements/MeasurementStampElement.d.ts +0 -13
  31. package/dist/canvas/elements/MeasurementStampElement.js +0 -30
  32. package/dist/canvas/elements/ShapeElement.d.ts +0 -7
  33. package/dist/canvas/elements/ShapeElement.js +0 -62
  34. package/dist/canvas/elements/StrokeElement.d.ts +0 -7
  35. package/dist/canvas/elements/StrokeElement.js +0 -18
  36. package/dist/canvas/measurementPicker.d.ts +0 -10
  37. package/dist/canvas/measurementPicker.js +0 -1
  38. package/dist/canvas/measurementStampOverlay.d.ts +0 -11
  39. package/dist/canvas/measurementStampOverlay.js +0 -1
  40. package/dist/canvas/pointerAdapter.d.ts +0 -3
  41. package/dist/canvas/pointerAdapter.js +0 -19
  42. package/dist/canvas/stampLayout.d.ts +0 -5
  43. package/dist/canvas/stampLayout.js +0 -14
  44. package/dist/canvas/tools/measurementStampTool.d.ts +0 -9
  45. package/dist/canvas/tools/measurementStampTool.js +0 -37
  46. package/dist/canvas/tools/panTool.d.ts +0 -5
  47. package/dist/canvas/tools/panTool.js +0 -25
  48. package/dist/canvas/tools/penTool.d.ts +0 -13
  49. package/dist/canvas/tools/penTool.js +0 -68
  50. package/dist/canvas/tools/selectTool.d.ts +0 -2
  51. package/dist/canvas/tools/selectTool.js +0 -182
  52. package/dist/canvas/useAnnotationCanvasState.d.ts +0 -54
  53. package/dist/canvas/useAnnotationCanvasState.js +0 -210
  54. package/dist/canvas/viewport.d.ts +0 -16
  55. package/dist/canvas/viewport.js +0 -54
  56. package/dist/data/AnnotationDataContext.d.ts +0 -8
  57. package/dist/data/AnnotationDataContext.js +0 -11
  58. package/dist/data/AnnotationDataProvider.d.ts +0 -65
  59. package/dist/data/AnnotationDataProvider.js +0 -4
  60. package/dist/data/InMemoryAnnotationProvider.d.ts +0 -30
  61. package/dist/data/InMemoryAnnotationProvider.js +0 -197
  62. package/dist/data/canvasPersistence.d.ts +0 -3
  63. package/dist/data/canvasPersistence.js +0 -26
  64. package/dist/data/hooks/useAnnotationCanvasDoc.d.ts +0 -33
  65. package/dist/data/hooks/useAnnotationCanvasDoc.js +0 -314
  66. package/dist/data/hooks/useAnnotationDoc.d.ts +0 -7
  67. package/dist/data/hooks/useAnnotationDoc.js +0 -33
  68. package/dist/data/hooks/useAnnotationList.d.ts +0 -7
  69. package/dist/data/hooks/useAnnotationList.js +0 -26
  70. package/dist/data/hooks/useAnnotationMutations.d.ts +0 -9
  71. package/dist/data/hooks/useAnnotationMutations.js +0 -11
  72. package/dist/hooks/useParseMeasurement.d.ts +0 -4
  73. package/dist/hooks/useParseMeasurement.js +0 -14
  74. package/dist/utils/evaluateFormula.d.ts +0 -20
  75. package/dist/utils/evaluateFormula.js +0 -31
@@ -13,11 +13,17 @@ import { unitsForDimension } from './units.js';
13
13
  // brackets) and every runtime field value is canonical — µm for length, µm²
14
14
  // area, µm³ volume, degrees for angle, raw for dimensionless (see units.ts).
15
15
  //
16
- // EXPRESSION SEMANTICS: equations are math.js expressions evaluated over a
17
- // canonical scope. Numeric literals inside an expression are therefore
18
- // canonical too (a bare `2` multiplying a length is a plain scalar; a bare
19
- // additive length constant would be micrometers). Authors should express
20
- // length constants as input fields with a defaultValue instead of literals.
16
+ // EXPRESSION SEMANTICS: equations are math.js expressions. By default they
17
+ // evaluate over a canonical scope, which makes numeric literals canonical too
18
+ // (a bare `2` multiplying a length is a plain scalar; a bare additive length
19
+ // constant would be micrometers). Per-equation `variableUnits` / `resultUnit`
20
+ // instead evaluate in author-chosen display units: each mapped variable is
21
+ // converted OUT of canonical into its unit before entering the scope, and the
22
+ // result is converted back FROM `resultUnit` into canonical. This is what
23
+ // makes literals meaningful ("roof area in ft² / 100 = roofing squares") —
24
+ // without it, a number-kind output consuming a measurement variable silently
25
+ // receives µm-scale values. The authoring UI always sets units; the bare
26
+ // canonical form survives for pre-existing definitions.
21
27
  // ---------------------------------------------------------------------------
22
28
  /**
23
29
  * Version of the definition format itself. Bumped only on breaking schema
@@ -157,6 +157,8 @@ export declare const calculatorDefinitionSchema: z.ZodObject<{
157
157
  targetFieldId: z.ZodString;
158
158
  expression: z.ZodString;
159
159
  variableToFieldId: z.ZodRecord<z.ZodString, z.ZodString>;
160
+ variableUnits: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
161
+ resultUnit: z.ZodOptional<z.ZodString>;
160
162
  }, z.core.$strip>>;
161
163
  diagramFileId: z.ZodOptional<z.ZodNullable<z.ZodString>>;
162
164
  isPublic: z.ZodBoolean;
@@ -1,7 +1,7 @@
1
1
  import { z } from 'zod';
2
2
  import { create, all } from 'mathjs';
3
3
  import { ColumnType, DecimalTolerance, FractionalTolerance, } from '../types/firestore.js';
4
- import { CALCULATOR_SCHEMA_VERSION, equationForField, isEquationTargetKind, isNumericFieldKind, } from './schema.js';
4
+ import { CALCULATOR_SCHEMA_VERSION, equationForField, fieldDimension, isEquationTargetKind, isNumericFieldKind, } from './schema.js';
5
5
  import { CALCULATOR_UNIT_INFO, unitDimension } from './units.js';
6
6
  import { isCalculatorCategoryId } from './categories.js';
7
7
  const math = create(all);
@@ -94,6 +94,8 @@ const calculatorEquationSchema = z.object({
94
94
  targetFieldId: z.string().min(1),
95
95
  expression: z.string().min(1),
96
96
  variableToFieldId: z.record(z.string(), z.string()),
97
+ variableUnits: z.record(z.string(), calculatorUnitSchema).optional(),
98
+ resultUnit: calculatorUnitSchema.optional(),
97
99
  });
98
100
  export const calculatorDefinitionSchema = z.object({
99
101
  id: z.string().min(1),
@@ -303,6 +305,46 @@ export const validateCalculatorDefinition = (input) => {
303
305
  push('self-referencing-equation', `${path}.variableToFieldId.${variable}`, `Equation for "${eq.targetFieldId}" references its own target`);
304
306
  }
305
307
  }
308
+ // --- Unit annotations --------------------------------------------------
309
+ for (const [variable, unit] of Object.entries(eq.variableUnits ?? {})) {
310
+ const unitPath = `${path}.variableUnits.${variable}`;
311
+ const mappedId = eq.variableToFieldId[variable];
312
+ if (mappedId == null) {
313
+ push('unit-for-unmapped-variable', unitPath, `Variable ${variable} has a unit but no field mapping`, 'warning');
314
+ continue;
315
+ }
316
+ const mapped = fieldById.get(mappedId);
317
+ if (!mapped)
318
+ continue; // unknown-mapped-field already reported above
319
+ const dim = fieldDimension(mapped);
320
+ if (dim === 'none') {
321
+ push('variable-unit-on-dimensionless-field', unitPath, `Variable ${variable} maps to "${mapped.name}", which has no unit dimension`);
322
+ }
323
+ else if (unitDimension(unit) !== dim) {
324
+ push('variable-unit-dimension-mismatch', unitPath, `Unit "${unit}" is not a ${dim} unit (variable ${variable} maps to "${mapped.name}")`);
325
+ }
326
+ }
327
+ if (eq.resultUnit != null && target) {
328
+ const dim = fieldDimension(target);
329
+ if (dim === 'none') {
330
+ push('result-unit-on-number-target', `${path}.resultUnit`, `Equation target "${target.name}" is a number field and takes the result as-is; remove the result unit`);
331
+ }
332
+ else if (unitDimension(eq.resultUnit) !== dim) {
333
+ push('result-unit-dimension-mismatch', `${path}.resultUnit`, `Result unit "${eq.resultUnit}" is not a ${dim} unit (target "${target.name}")`);
334
+ }
335
+ }
336
+ // The canonical-leak trap: a number target consuming a unit-carrying
337
+ // field without a variable unit receives raw µm-scale values.
338
+ if (target?.kind === ColumnType.Number) {
339
+ for (const [variable, fieldId] of Object.entries(eq.variableToFieldId)) {
340
+ const mapped = fieldById.get(fieldId);
341
+ if (!mapped || fieldDimension(mapped) === 'none')
342
+ continue;
343
+ if (eq.variableUnits?.[variable] == null) {
344
+ push('canonical-value-into-number-target', `${path}.variableToFieldId.${variable}`, `"${target.name}" is a number field but variable ${variable} ("${mapped.name}") has no display unit — its canonical (µm-scale) value will be used. Pick a unit for ${variable}.`, 'warning');
345
+ }
346
+ }
347
+ }
306
348
  const symbols = expressionSymbols(eq.expression);
307
349
  if (!symbols.ok) {
308
350
  push('expression-invalid', `${path}.expression`, `Expression does not parse: ${symbols.error}`);
package/dist/exports.d.ts CHANGED
@@ -7,6 +7,7 @@ export * from './types/firestore.js';
7
7
  export * from './types/layout.js';
8
8
  export * from './types/annotation.js';
9
9
  export { getToleranceColor, calculateDeviationPercentage, isWithinTolerance, generateToleranceGradient, createDefaultToleranceThresholds, DEFAULT_TOLERANCE_COLORS, type ToleranceThreshold, type ToleranceConfig, } from './utils/tolerance.js';
10
+ export { GeneralColors, SecondaryColors, FormColors, TileColors, FormulaColors, YellowColors, RedColors, GreenColors, tailwindColors, } from './theme/colors.js';
10
11
  export { DEFAULT_GROUP_INDEX, isDefaultGroup, findDefaultGroup, isFormGroupCompleted, } from './utils/groups.js';
11
12
  export { ALL_EXPORT_CATEGORIES, ALL_EXPORT_MEASUREMENT_DATA_FIELDS, ALL_EXPORT_OBJECT_TYPES, DEFAULT_EXPORT_CONFIG, EXPORT_EVERYTHING_CONFIG, normalizeExportConfig, type ExportCategory, type ExportConfig, type ExportImageFormat, type ExportMeasurementDataField, type ExportMeasurementFilter, type ExportObjectType, } from './export/config.js';
12
13
  export { classifyFileForExport } from './export/objectKinds.js';
package/dist/exports.js CHANGED
@@ -11,6 +11,8 @@ export * from './types/firestore.js';
11
11
  export * from './types/layout.js';
12
12
  export * from './types/annotation.js';
13
13
  export { getToleranceColor, calculateDeviationPercentage, isWithinTolerance, generateToleranceGradient, createDefaultToleranceThresholds, DEFAULT_TOLERANCE_COLORS, } from './utils/tolerance.js';
14
+ // Shared color palette (canonical source for both apps' brand colors).
15
+ export { GeneralColors, SecondaryColors, FormColors, TileColors, FormulaColors, YellowColors, RedColors, GreenColors, tailwindColors, } from './theme/colors.js';
14
16
  export { DEFAULT_GROUP_INDEX, isDefaultGroup, findDefaultGroup, isFormGroupCompleted, } from './utils/groups.js';
15
17
  // Configurable job CSV export, shared by rock-pro-app and rock-desktop so
16
18
  // both platforms emit identical files.
@@ -0,0 +1,101 @@
1
+ export declare const GeneralColors: {
2
+ readonly darkGrey: "#1D1F21";
3
+ readonly grey1: "#2B2C2E";
4
+ readonly grey2: "#424345";
5
+ readonly grey3: "#626364";
6
+ readonly grey4: "#9FA0A2";
7
+ readonly white: "#FFFFFF";
8
+ };
9
+ export declare const SecondaryColors: {
10
+ readonly accentBlue1: "#2D3843";
11
+ readonly accentBlue2: "#2E4C6A";
12
+ readonly accentBlue3: "#4B6074";
13
+ readonly accentBlue4: "#67819A";
14
+ };
15
+ export declare const FormColors: {
16
+ readonly lightGrey1: "#BCBEC2";
17
+ readonly lightGrey2: "#E8EAED";
18
+ };
19
+ export declare const TileColors: {
20
+ readonly darkBlue: "#02274B";
21
+ readonly activeBlue: "#0066FF";
22
+ readonly lightBlue1: "#50A6FF";
23
+ readonly lightBlue2: "#89C3FF";
24
+ };
25
+ export declare const FormulaColors: {
26
+ readonly darkPurple: "#6C155F";
27
+ readonly purple: "#BD30A8";
28
+ };
29
+ export declare const YellowColors: {
30
+ readonly mainYellow: "#FFAD00";
31
+ };
32
+ export declare const RedColors: {
33
+ readonly warningRed: "#DA1100";
34
+ readonly red: "#E83D2F";
35
+ readonly mutedRed: "#DF6358";
36
+ };
37
+ export declare const GreenColors: {
38
+ readonly warningGreen: "#00E920";
39
+ readonly green: "#58A942";
40
+ readonly mutedGreen: "#8BB380";
41
+ };
42
+ /**
43
+ * The palette above shaped for Tailwind's `theme.extend.colors`. Spread it
44
+ * into the app's tailwind config (works with tailwindcss v3 and NativeWind):
45
+ *
46
+ * import { tailwindColors } from '@reekon-tools/boldr-utils/theme';
47
+ * export default {
48
+ * theme: { extend: { colors: { ...tailwindColors } } },
49
+ * };
50
+ *
51
+ * Class names follow the constant names, kebab-cased per group, e.g.
52
+ * `bg-general-dark-grey`, `text-secondary-accent-blue-1`, `bg-tile-active`,
53
+ * `border-form-light-grey-2`, `bg-formula`, `text-brand-red-muted`.
54
+ *
55
+ * The yellow/red/green groups are namespaced under `brand-*` so they don't
56
+ * shadow Tailwind's built-in `yellow`/`red`/`green` scales (extend replaces
57
+ * a color key wholesale, which would break classes like `text-red-500`).
58
+ */
59
+ export declare const tailwindColors: {
60
+ readonly general: {
61
+ readonly 'dark-grey': "#1D1F21";
62
+ readonly 'grey-1': "#2B2C2E";
63
+ readonly 'grey-2': "#424345";
64
+ readonly 'grey-3': "#626364";
65
+ readonly 'grey-4': "#9FA0A2";
66
+ readonly white: "#FFFFFF";
67
+ };
68
+ readonly secondary: {
69
+ readonly 'accent-blue-1': "#2D3843";
70
+ readonly 'accent-blue-2': "#2E4C6A";
71
+ readonly 'accent-blue-3': "#4B6074";
72
+ readonly 'accent-blue-4': "#67819A";
73
+ };
74
+ readonly form: {
75
+ readonly 'light-grey-1': "#BCBEC2";
76
+ readonly 'light-grey-2': "#E8EAED";
77
+ };
78
+ readonly tile: {
79
+ readonly dark: "#02274B";
80
+ readonly active: "#0066FF";
81
+ readonly 'light-1': "#50A6FF";
82
+ readonly 'light-2': "#89C3FF";
83
+ };
84
+ readonly formula: {
85
+ readonly DEFAULT: "#BD30A8";
86
+ readonly dark: "#6C155F";
87
+ };
88
+ readonly 'brand-yellow': {
89
+ readonly DEFAULT: "#FFAD00";
90
+ };
91
+ readonly 'brand-red': {
92
+ readonly DEFAULT: "#E83D2F";
93
+ readonly warning: "#DA1100";
94
+ readonly muted: "#DF6358";
95
+ };
96
+ readonly 'brand-green': {
97
+ readonly DEFAULT: "#58A942";
98
+ readonly warning: "#00E920";
99
+ readonly muted: "#8BB380";
100
+ };
101
+ };
@@ -0,0 +1,101 @@
1
+ export const GeneralColors = {
2
+ darkGrey: '#1D1F21',
3
+ grey1: '#2B2C2E',
4
+ grey2: '#424345',
5
+ grey3: '#626364',
6
+ grey4: '#9FA0A2',
7
+ white: '#FFFFFF',
8
+ };
9
+ export const SecondaryColors = {
10
+ accentBlue1: '#2D3843',
11
+ accentBlue2: '#2E4C6A',
12
+ accentBlue3: '#4B6074',
13
+ accentBlue4: '#67819A',
14
+ };
15
+ export const FormColors = {
16
+ lightGrey1: '#BCBEC2',
17
+ lightGrey2: '#E8EAED',
18
+ };
19
+ export const TileColors = {
20
+ darkBlue: '#02274B',
21
+ activeBlue: '#0066FF',
22
+ lightBlue1: '#50A6FF',
23
+ lightBlue2: '#89C3FF',
24
+ };
25
+ export const FormulaColors = {
26
+ darkPurple: '#6C155F',
27
+ purple: '#BD30A8',
28
+ };
29
+ export const YellowColors = {
30
+ mainYellow: '#FFAD00',
31
+ };
32
+ export const RedColors = {
33
+ warningRed: '#DA1100',
34
+ red: '#E83D2F',
35
+ mutedRed: '#DF6358',
36
+ };
37
+ export const GreenColors = {
38
+ warningGreen: '#00E920',
39
+ green: '#58A942',
40
+ mutedGreen: '#8BB380',
41
+ };
42
+ /**
43
+ * The palette above shaped for Tailwind's `theme.extend.colors`. Spread it
44
+ * into the app's tailwind config (works with tailwindcss v3 and NativeWind):
45
+ *
46
+ * import { tailwindColors } from '@reekon-tools/boldr-utils/theme';
47
+ * export default {
48
+ * theme: { extend: { colors: { ...tailwindColors } } },
49
+ * };
50
+ *
51
+ * Class names follow the constant names, kebab-cased per group, e.g.
52
+ * `bg-general-dark-grey`, `text-secondary-accent-blue-1`, `bg-tile-active`,
53
+ * `border-form-light-grey-2`, `bg-formula`, `text-brand-red-muted`.
54
+ *
55
+ * The yellow/red/green groups are namespaced under `brand-*` so they don't
56
+ * shadow Tailwind's built-in `yellow`/`red`/`green` scales (extend replaces
57
+ * a color key wholesale, which would break classes like `text-red-500`).
58
+ */
59
+ export const tailwindColors = {
60
+ general: {
61
+ 'dark-grey': GeneralColors.darkGrey,
62
+ 'grey-1': GeneralColors.grey1,
63
+ 'grey-2': GeneralColors.grey2,
64
+ 'grey-3': GeneralColors.grey3,
65
+ 'grey-4': GeneralColors.grey4,
66
+ white: GeneralColors.white,
67
+ },
68
+ secondary: {
69
+ 'accent-blue-1': SecondaryColors.accentBlue1,
70
+ 'accent-blue-2': SecondaryColors.accentBlue2,
71
+ 'accent-blue-3': SecondaryColors.accentBlue3,
72
+ 'accent-blue-4': SecondaryColors.accentBlue4,
73
+ },
74
+ form: {
75
+ 'light-grey-1': FormColors.lightGrey1,
76
+ 'light-grey-2': FormColors.lightGrey2,
77
+ },
78
+ tile: {
79
+ dark: TileColors.darkBlue,
80
+ active: TileColors.activeBlue,
81
+ 'light-1': TileColors.lightBlue1,
82
+ 'light-2': TileColors.lightBlue2,
83
+ },
84
+ formula: {
85
+ DEFAULT: FormulaColors.purple,
86
+ dark: FormulaColors.darkPurple,
87
+ },
88
+ 'brand-yellow': {
89
+ DEFAULT: YellowColors.mainYellow,
90
+ },
91
+ 'brand-red': {
92
+ DEFAULT: RedColors.red,
93
+ warning: RedColors.warningRed,
94
+ muted: RedColors.mutedRed,
95
+ },
96
+ 'brand-green': {
97
+ DEFAULT: GreenColors.green,
98
+ warning: GreenColors.warningGreen,
99
+ muted: GreenColors.mutedGreen,
100
+ },
101
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reekon-tools/boldr-utils",
3
- "version": "1.8.3",
3
+ "version": "1.8.5",
4
4
  "description": "Shared utilities for formulas and measurement conversion used in Reekon apps",
5
5
  "author": "REEKON Tools",
6
6
  "license": "MIT",
@@ -19,6 +19,11 @@
19
19
  "import": "./dist/index.js",
20
20
  "default": "./dist/index.js"
21
21
  },
22
+ "./theme": {
23
+ "types": "./dist/theme/colors.d.ts",
24
+ "import": "./dist/theme/colors.js",
25
+ "default": "./dist/theme/colors.js"
26
+ },
22
27
  "./package.json": "./package.json"
23
28
  },
24
29
  "scripts": {
@@ -30,7 +35,7 @@
30
35
  "coverage": "vitest run --coverage",
31
36
  "format": "prettier --write .",
32
37
  "check-format": "prettier --check .",
33
- "check-exports": "attw --pack . --ignore-rules=cjs-resolves-to-esm",
38
+ "check-exports": "attw --pack . --profile esm-only",
34
39
  "ci": "yarn run check-format && vitest run && yarn run build && yarn run check-exports"
35
40
  },
36
41
  "keywords": [],
@@ -1,11 +0,0 @@
1
- import { type ReactNode } from 'react';
2
- import type { AnnotationCanvasInnerProps } from './AnnotationCanvasInner.js';
3
- export type { AnnotationCanvasHandle, GestureConfig, PanTrigger, } from './AnnotationCanvasInner.js';
4
- export interface CanvasKitOpts {
5
- locateFile?: (file: string) => string;
6
- }
7
- export type AnnotationCanvasProps = AnnotationCanvasInnerProps & {
8
- fallback?: ReactNode;
9
- canvasKitOpts?: CanvasKitOpts;
10
- };
11
- export declare const AnnotationCanvas: ({ fallback, canvasKitOpts, ...rest }: AnnotationCanvasProps) => import("react/jsx-runtime").JSX.Element;
@@ -1,10 +0,0 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
2
- import { WithSkiaWeb } from '@shopify/react-native-skia/lib/module/web/index.js';
3
- // Web-only entry point: lazy-loads CanvasKit wasm via WithSkiaWeb. Imperative
4
- // API (undo/redo/zoom) is exposed through the `imperativeRef` prop rather
5
- // than React refs, since WithSkiaWeb can't forward refs through its lazy
6
- // component boundary. The native build sits in `AnnotationCanvas.native.tsx`
7
- // and skips WithSkiaWeb entirely (no wasm to load).
8
- export const AnnotationCanvas = ({ fallback, canvasKitOpts, ...rest }) => (_jsx(WithSkiaWeb, { getComponent: () => import('./AnnotationCanvasInner.js').then((m) => ({
9
- default: m.AnnotationCanvasInner,
10
- })), fallback: fallback ?? null, componentProps: rest, opts: canvasKitOpts }));
@@ -1,8 +0,0 @@
1
- import type { AnnotationCanvasInnerProps } from './AnnotationCanvasInner.native.js';
2
- export type { AnnotationCanvasHandle, } from './useAnnotationCanvasState.js';
3
- export type { GestureConfig, PanTrigger, } from './AnnotationCanvasInner.js';
4
- export type AnnotationCanvasProps = AnnotationCanvasInnerProps & {
5
- fallback?: unknown;
6
- canvasKitOpts?: unknown;
7
- };
8
- export declare const AnnotationCanvas: (props: AnnotationCanvasProps) => import("react/jsx-runtime").JSX.Element;
@@ -1,6 +0,0 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
2
- import { AnnotationCanvasInner } from './AnnotationCanvasInner.native.js';
3
- export const AnnotationCanvas = (props) => {
4
- const { fallback: _f, canvasKitOpts: _o, ...rest } = props;
5
- return _jsx(AnnotationCanvasInner, { ...rest });
6
- };
@@ -1,39 +0,0 @@
1
- import { type CSSProperties, type MutableRefObject } from 'react';
2
- import type { DecimalTolerance, FractionalTolerance, Measurement, Units } from '../types/firestore.js';
3
- import type { AnnotationCanvasState, AnnotationDocumentPatch, Selection } from '../types/annotation.js';
4
- import type { MeasurementRef } from './measurementPicker.js';
5
- import type { Tool } from './Tool.js';
6
- import { type AnnotationCanvasHandle } from './useAnnotationCanvasState.js';
7
- import { type ViewportState } from './viewport.js';
8
- import type { RenderMeasurementStamp } from './measurementStampOverlay.js';
9
- export type { AnnotationCanvasHandle };
10
- export interface AnnotationCanvasInnerProps {
11
- canvas: AnnotationCanvasState;
12
- onCommit(patch: AnnotationDocumentPatch): void;
13
- tools: Tool[];
14
- activeToolId: string;
15
- selection: Selection | null;
16
- onSelectionChange(selection: Selection | null): void;
17
- measurements?: Measurement[];
18
- fallbackUnit?: Units;
19
- fractionalTolerance?: FractionalTolerance;
20
- decimalTolerance?: DecimalTolerance;
21
- resolveImageUrl?: (storagePath: string) => Promise<string>;
22
- pickMeasurement?: () => Promise<MeasurementRef | null>;
23
- renderMeasurementStamp?: RenderMeasurementStamp;
24
- stampFontSource?: unknown;
25
- stampValueFontSize?: number;
26
- stampLabelFontSize?: number;
27
- gestures?: GestureConfig;
28
- width: number;
29
- height: number;
30
- initialViewport?: ViewportState;
31
- style?: CSSProperties;
32
- imperativeRef?: MutableRefObject<AnnotationCanvasHandle | null>;
33
- }
34
- export type PanTrigger = 'middleMouse' | 'rightMouse' | 'space';
35
- export interface GestureConfig {
36
- wheel?: 'zoom' | 'pan' | 'auto';
37
- panTriggers?: PanTrigger[];
38
- }
39
- export declare const AnnotationCanvasInner: (props: AnnotationCanvasInnerProps) => import("react/jsx-runtime").JSX.Element;
@@ -1,219 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useFont } from '@shopify/react-native-skia';
3
- import { useCallback, useEffect, useRef, } from 'react';
4
- import { AnnotationCanvasSkia } from './AnnotationCanvasSkia.js';
5
- import { useAnnotationCanvasState, } from './useAnnotationCanvasState.js';
6
- import { STAMP_TILE_SIZE } from './stampLayout.js';
7
- const DEFAULT_PAN_TRIGGERS = ['middleMouse', 'space'];
8
- export const AnnotationCanvasInner = (props) => {
9
- const { fallbackUnit, fractionalTolerance, decimalTolerance, resolveImageUrl, stampFontSource, stampValueFontSize = 14, stampLabelFontSize = 11, gestures, width, height, style, activeToolId, tools, } = props;
10
- const wheelMode = gestures?.wheel ?? 'auto';
11
- const panTriggers = gestures?.panTriggers ?? DEFAULT_PAN_TRIGGERS;
12
- const allowSpacePan = panTriggers.includes('space');
13
- const allowMiddlePan = panTriggers.includes('middleMouse');
14
- const allowRightPan = panTriggers.includes('rightMouse');
15
- const valueFont = useFont(stampFontSource, stampValueFontSize);
16
- const labelFont = useFont(stampFontSource, stampLabelFontSize);
17
- const state = useAnnotationCanvasState(props);
18
- const containerRef = useRef(null);
19
- const panGestureRef = useRef(null);
20
- const spaceDownRef = useRef(false);
21
- const activeTool = tools.find((t) => t.id === activeToolId) ?? null;
22
- const toCanvasPointer = useCallback((event) => {
23
- const rect = containerRef.current?.getBoundingClientRect();
24
- const screen = {
25
- x: event.clientX - (rect?.left ?? 0),
26
- y: event.clientY - (rect?.top ?? 0),
27
- };
28
- return {
29
- pointerId: event.pointerId,
30
- screen,
31
- world: state.ctx.viewport.screenToWorld(screen),
32
- pressure: event.pressure || undefined,
33
- shiftKey: event.shiftKey,
34
- altKey: event.altKey,
35
- metaKey: event.metaKey,
36
- ctrlKey: event.ctrlKey,
37
- };
38
- }, [state.ctx.viewport]);
39
- const isPanTriggerDown = useCallback((event) => {
40
- if (allowMiddlePan && event.button === 1)
41
- return true;
42
- if (allowRightPan && event.button === 2)
43
- return true;
44
- if (allowSpacePan && event.button === 0 && spaceDownRef.current)
45
- return true;
46
- return false;
47
- }, [allowMiddlePan, allowRightPan, allowSpacePan]);
48
- const handlePointerDown = useCallback((event) => {
49
- const rect = containerRef.current?.getBoundingClientRect();
50
- const screen = {
51
- x: event.clientX - (rect?.left ?? 0),
52
- y: event.clientY - (rect?.top ?? 0),
53
- };
54
- if (isPanTriggerDown(event)) {
55
- panGestureRef.current = { pointerId: event.pointerId, lastScreen: screen };
56
- event.currentTarget.setPointerCapture(event.pointerId);
57
- event.preventDefault();
58
- return;
59
- }
60
- event.currentTarget.setPointerCapture(event.pointerId);
61
- state.dispatchPointerDown(toCanvasPointer(event));
62
- }, [state, toCanvasPointer, isPanTriggerDown]);
63
- const handlePointerMove = useCallback((event) => {
64
- const pan = panGestureRef.current;
65
- if (pan && event.pointerId === pan.pointerId) {
66
- const rect = containerRef.current?.getBoundingClientRect();
67
- const screen = {
68
- x: event.clientX - (rect?.left ?? 0),
69
- y: event.clientY - (rect?.top ?? 0),
70
- };
71
- state.pan({
72
- x: screen.x - pan.lastScreen.x,
73
- y: screen.y - pan.lastScreen.y,
74
- });
75
- panGestureRef.current = { pointerId: pan.pointerId, lastScreen: screen };
76
- return;
77
- }
78
- state.dispatchPointerMove(toCanvasPointer(event));
79
- }, [state, toCanvasPointer]);
80
- const handlePointerUp = useCallback((event) => {
81
- const pan = panGestureRef.current;
82
- if (pan && event.pointerId === pan.pointerId) {
83
- panGestureRef.current = null;
84
- return;
85
- }
86
- state.dispatchPointerUp(toCanvasPointer(event));
87
- }, [state, toCanvasPointer]);
88
- const handlePointerCancel = useCallback(() => {
89
- panGestureRef.current = null;
90
- state.dispatchPointerCancel();
91
- }, [state]);
92
- const handleWheel = useCallback((event) => {
93
- const rect = containerRef.current?.getBoundingClientRect();
94
- const focal = {
95
- x: event.clientX - (rect?.left ?? 0),
96
- y: event.clientY - (rect?.top ?? 0),
97
- };
98
- if (event.ctrlKey || event.metaKey) {
99
- const factor = Math.exp(-event.deltaY / 200);
100
- state.zoom(focal, state.ctx.viewport.state.zoom * factor);
101
- return;
102
- }
103
- if (wheelMode === 'zoom') {
104
- const factor = Math.exp(-event.deltaY / 300);
105
- state.zoom(focal, state.ctx.viewport.state.zoom * factor);
106
- return;
107
- }
108
- if (event.shiftKey) {
109
- state.pan({ x: event.deltaY, y: event.deltaX });
110
- return;
111
- }
112
- state.pan({ x: event.deltaX, y: event.deltaY });
113
- }, [state, wheelMode]);
114
- const handleContextMenu = useCallback((event) => {
115
- if (allowRightPan)
116
- event.preventDefault();
117
- }, [allowRightPan]);
118
- useEffect(() => {
119
- const el = containerRef.current;
120
- if (!el)
121
- return;
122
- const stop = (e) => {
123
- if (e.target === el || el.contains(e.target)) {
124
- e.preventDefault();
125
- }
126
- };
127
- el.addEventListener('wheel', stop, { passive: false });
128
- return () => el.removeEventListener('wheel', stop);
129
- }, []);
130
- useEffect(() => {
131
- if (!allowSpacePan)
132
- return;
133
- const onKeyDown = (e) => {
134
- if (e.code === 'Space' && !e.repeat) {
135
- spaceDownRef.current = true;
136
- if (containerRef.current)
137
- containerRef.current.style.cursor = 'grab';
138
- }
139
- };
140
- const onKeyUp = (e) => {
141
- if (e.code === 'Space') {
142
- spaceDownRef.current = false;
143
- if (containerRef.current)
144
- containerRef.current.style.cursor = '';
145
- }
146
- };
147
- window.addEventListener('keydown', onKeyDown);
148
- window.addEventListener('keyup', onKeyUp);
149
- return () => {
150
- window.removeEventListener('keydown', onKeyDown);
151
- window.removeEventListener('keyup', onKeyUp);
152
- };
153
- }, [allowSpacePan]);
154
- const containerStyle = {
155
- position: 'relative',
156
- width,
157
- height,
158
- overflow: 'hidden',
159
- touchAction: 'none',
160
- userSelect: 'none',
161
- cursor: activeTool?.cursor ?? 'default',
162
- ...style,
163
- };
164
- const customPreview = activeTool?.renderPreview?.(state.customPreviewState, state.ctx);
165
- const { renderMeasurementStamp, selection } = props;
166
- return (_jsxs("div", { ref: containerRef, style: containerStyle, onPointerDown: handlePointerDown, onPointerMove: handlePointerMove, onPointerUp: handlePointerUp, onPointerCancel: handlePointerCancel, onWheel: handleWheel, onContextMenu: handleContextMenu, children: [AnnotationCanvasSkia({
167
- width,
168
- height,
169
- effectiveCanvas: state.effectiveCanvas,
170
- worldTransform: state.worldTransform,
171
- measurementsById: state.measurementsById,
172
- fallbackUnit,
173
- fractionalTolerance,
174
- decimalTolerance,
175
- resolveImageUrl,
176
- valueFont,
177
- labelFont,
178
- hideMeasurementStamps: !!renderMeasurementStamp,
179
- penDrawingStroke: state.penDrawingStroke,
180
- customPreview,
181
- }), renderMeasurementStamp && (_jsx("div", { style: {
182
- position: 'absolute',
183
- inset: 0,
184
- pointerEvents: 'none',
185
- }, children: state.effectiveCanvas.placedMeasurements.map((placed) => {
186
- const size = STAMP_TILE_SIZE * (placed.scale ?? 1);
187
- const cx = (placed.anchor.x - state.viewport.pan.x) * state.viewport.zoom;
188
- const cy = (placed.anchor.y - state.viewport.pan.y) * state.viewport.zoom;
189
- const isSelected = selection?.ids.includes(placed.id) ?? false;
190
- return (_jsxs("div", { style: {
191
- position: 'absolute',
192
- left: 0,
193
- top: 0,
194
- width: size,
195
- height: size,
196
- transform: `translate(${cx - size / 2}px, ${cy - size / 2}px)`,
197
- }, children: [renderMeasurementStamp({
198
- placed,
199
- measurement: state.measurementsById.get(placed.measurementId) ?? null,
200
- selected: isSelected,
201
- size,
202
- zoom: state.viewport.zoom,
203
- }), isSelected && (_jsx("div", { role: "button", "aria-label": "Remove measurement", onPointerDown: (e) => {
204
- e.stopPropagation();
205
- state.ctx.commit({
206
- ops: [{ op: 'removeMeasurement', id: placed.id }],
207
- });
208
- state.ctx.setSelection(null);
209
- }, style: {
210
- position: 'absolute',
211
- top: -10,
212
- right: -10,
213
- width: 40,
214
- height: 40,
215
- cursor: 'pointer',
216
- pointerEvents: 'auto',
217
- } }))] }, placed.id));
218
- }) }))] }));
219
- };