@reekon-tools/boldr-utils 1.15.5 → 1.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -4,7 +4,7 @@ import { normalizeRect, placementOf } from './measurementGeometry.js';
4
4
  import { arrowheadTriangle, dashIntervals, toSkiaStrokeCap, } from './strokeGeometry.js';
5
5
  import { SELECTION_PAD, textResizeGeometry, textShapeBounds, } from './textGeometry.js';
6
6
  import { visualShapeBounds } from './shapeGeometry.js';
7
- import { backgroundLayerDocRect, backgroundLayersOf, } from './backgroundLayers.js';
7
+ import { backgroundLayerDocRect, backgroundLayersOf, canEditBackgroundLayers, } from './backgroundLayers.js';
8
8
  import { BackgroundLayerElement } from './elements/BackgroundImageElement.js';
9
9
  import { PlaneElement, PLANE_CHROME_COLOR, PLANE_GRID_COLOR, PLANE_GRID_OPACITY, PLANE_GRID_WIDTH, PLANE_OUTLINE_WIDTH, } from './elements/PlaneElement.js';
10
10
  import { ShapeElement } from './elements/ShapeElement.js';
@@ -251,8 +251,13 @@ export const AnnotationCanvasSkia = ({ width, height, effectiveCanvas, worldTran
251
251
  // bottom-right handle — backgrounds scale uniformly about their
252
252
  // top-left corner (the transform origin), so a single opposite-corner
253
253
  // grab is the whole gizmo. Chrome tracks the live drag/resize
254
- // transform the same way the layer itself does above.
255
- const layer = backgroundLayersOf(effectiveCanvas.viewport).find((l) => l.id === selectedId);
254
+ // transform the same way the layer itself does above. A lone
255
+ // background is never editable (canEditBackgroundLayers), so it gets
256
+ // no chrome even when a stale selection still names it — e.g. right
257
+ // after the other layer of a pair was removed.
258
+ const layer = canEditBackgroundLayers(effectiveCanvas.viewport)
259
+ ? backgroundLayersOf(effectiveCanvas.viewport).find((l) => l.id === selectedId)
260
+ : undefined;
256
261
  if (layer) {
257
262
  const r = backgroundLayerDocRect(layer);
258
263
  const isResizing = layer.id === resizingId;
@@ -2,6 +2,7 @@ import type { AnnotationBackgroundLayer, AnnotationViewport, BackgroundLayerTran
2
2
  export declare const LEGACY_BACKGROUND_LAYER_ID = "background-legacy";
3
3
  export declare const BACKGROUND_PLACEMENT_GAP_RATIO = 0.02;
4
4
  export declare const backgroundLayersOf: (viewport: AnnotationViewport) => AnnotationBackgroundLayer[];
5
+ export declare const canEditBackgroundLayers: (viewport: AnnotationViewport) => boolean;
5
6
  export declare const backgroundLayerDocRect: (layer: AnnotationBackgroundLayer) => {
6
7
  x: number;
7
8
  y: number;
@@ -46,6 +46,14 @@ export const backgroundLayersOf = (viewport) => {
46
46
  },
47
47
  ];
48
48
  };
49
+ // Whether the background stack is user-editable at all — selectable, draggable,
50
+ // corner-scalable. A lone image IS the canvas: there is nothing to arrange it
51
+ // against, so it exposes no selection chrome and never moves; taps and drags
52
+ // on it fall through to the ordinary empty-canvas behavior (deselect / pan).
53
+ // Only once a second image exists does arranging make sense, and then every
54
+ // layer becomes editable. Shared by the select tool's hit-tests and the
55
+ // selection chrome so both platforms agree on the rule.
56
+ export const canEditBackgroundLayers = (viewport) => backgroundLayersOf(viewport).length > 1;
49
57
  // The doc-space rectangle a layer occupies: the intrinsic pixel grid under
50
58
  // the layer's translate+scale.
51
59
  export const backgroundLayerDocRect = (layer) => ({
@@ -1,4 +1,4 @@
1
- import { backgroundLayerDocRect, backgroundLayersOf, } from '../backgroundLayers.js';
1
+ import { backgroundLayerDocRect, backgroundLayersOf, canEditBackgroundLayers, } from '../backgroundLayers.js';
2
2
  import { stampTileDims } from '../stampLayout.js';
3
3
  import { placementOf, linePosOf, snapLinePos, lerp, recomputeAnchor, rectCenter, rectCornerPoint, oppositeRectCorner, hitPlacedMeasurement, } from '../measurementGeometry.js';
4
4
  import { hitShapeOutline, visualShapeBounds } from '../shapeGeometry.js';
@@ -462,7 +462,10 @@ const shapeCornerPatch = (doc, id, corner, delta) => {
462
462
  // hit-test: a tap selects one only when every stroke/shape/measurement
463
463
  // misses, and a drag translates one only when it is ALREADY selected — the
464
464
  // layout tool's two-step model, which keeps a full-bleed background from
465
- // hijacking pan/marquee gestures. ---
465
+ // hijacking pan/marquee gestures. And none of it applies to a LONE image: with
466
+ // a single background there is nothing to arrange, so it is not selectable,
467
+ // draggable, or scalable at all (canEditBackgroundLayers) — every hit-test
468
+ // below misses, and the gesture falls through to deselect / pan. ---
466
469
  // Doc-space clamp on a resized background's larger doc dimension. The floor
467
470
  // keeps a corner drag from collapsing a layer below grabbable size; the
468
471
  // ceiling keeps the doc rect (which feeds fit math and the web SVG raster
@@ -478,6 +481,8 @@ const pointInBackgroundLayer = (layer, p) => {
478
481
  // layers draw on top). Point-in-doc-rect, no padding: a background is a big
479
482
  // target already.
480
483
  const findBackgroundHit = (doc, world) => {
484
+ if (!canEditBackgroundLayers(doc.viewport))
485
+ return null;
481
486
  const layers = backgroundLayersOf(doc.viewport);
482
487
  for (let i = layers.length - 1; i >= 0; i--) {
483
488
  if (pointInBackgroundLayer(layers[i], world)) {
@@ -490,6 +495,8 @@ const findBackgroundHit = (doc, world) => {
490
495
  // paths call this only after the general hit-test misses, so annotation
491
496
  // elements always win a grab over the background beneath them.
492
497
  const findSelectedBackgroundHit = (doc, world, selectedIds) => {
498
+ if (!canEditBackgroundLayers(doc.viewport))
499
+ return null;
493
500
  const layers = backgroundLayersOf(doc.viewport);
494
501
  for (let i = layers.length - 1; i >= 0; i--) {
495
502
  const layer = layers[i];
@@ -527,7 +534,11 @@ const backgroundResizeGeometry = (layer) => {
527
534
  // endpoint handles either way.
528
535
  const findResizeHandleHit = (doc, id, world, zoom) => {
529
536
  const s = doc.shapes.find((x) => x.id === id);
530
- const layer = s ? null : backgroundLayerById(doc, id);
537
+ // A lone background is not editable at all (canEditBackgroundLayers), so
538
+ // its corner offers no handle — even to a stale selection naming it.
539
+ const layer = s || !canEditBackgroundLayers(doc.viewport)
540
+ ? null
541
+ : backgroundLayerById(doc, id);
531
542
  const geom = s
532
543
  ? textResizeGeometry(s)
533
544
  : layer
@@ -9,4 +9,5 @@ export * from './instance.js';
9
9
  export * from './runtime.js';
10
10
  export * from './display.js';
11
11
  export * from './chips.js';
12
+ export * from './preview.js';
12
13
  export { calculatorDefinitionSchema, expressionSymbols, validateCalculatorDefinition, type IssueSeverity, type ValidationIssue, type ValidationResult, } from './validate.js';
@@ -15,4 +15,5 @@ export * from './instance.js';
15
15
  export * from './runtime.js';
16
16
  export * from './display.js';
17
17
  export * from './chips.js';
18
+ export * from './preview.js';
18
19
  export { calculatorDefinitionSchema, expressionSymbols, validateCalculatorDefinition, } from './validate.js';
@@ -0,0 +1,22 @@
1
+ import { type CalculatorDefinition, type CalculatorField } from './schema.js';
2
+ import type { CalculatorInstanceFileData } from './instance.js';
3
+ /**
4
+ * The field a thumbnail headlines, per CalculatorDefinition.previewFieldId's
5
+ * tri-state: an explicit id (when it names a numeric output), null for none,
6
+ * absent for auto — the sole numeric output, if there is exactly one.
7
+ */
8
+ export declare const previewFieldFor: (definition: Pick<CalculatorDefinition, "fields" | "previewFieldId">) => CalculatorField | null;
9
+ export interface PreviewValue {
10
+ field: CalculatorField;
11
+ /** Display string in `unit` — fractional inches etc., same as the runtime. */
12
+ value: string;
13
+ /** Unit label; empty for unit-less numbers. */
14
+ unit: string;
15
+ }
16
+ /**
17
+ * The headline value for an instance, or null when there is nothing to show:
18
+ * no preview field, the field is unresolved (inputs missing), or it is in
19
+ * conflict (the runtime withholds conflicted values behind a dash — a tile
20
+ * should show nothing rather than a dash).
21
+ */
22
+ export declare const previewValueFor: (fileData: Pick<CalculatorInstanceFileData, "definition" | "entries" | "displayUnitOverrides">) => PreviewValue | null;
@@ -0,0 +1,43 @@
1
+ // Thumbnail headline for a saved calculator instance (Asana
2
+ // 1217881404454870): which output a tile shows, and its formatted value.
3
+ // Shared by boldr-pro's FilePreview and rock-desktop so both platforms pick
4
+ // the same field and print the same string. Pure — runs the same
5
+ // deriveRuntime pass the runtime and the T1R drift check use, so it needs no
6
+ // stored result (computed values are never persisted; see instance.ts).
7
+ import { isEquationTargetKind, } from './schema.js';
8
+ import { deriveRuntime } from './runtime.js';
9
+ import { displayUnitFor, formatFieldValue } from './display.js';
10
+ /** Outputs eligible to headline a thumbnail: numeric, equation-target kinds. */
11
+ const numericOutputs = (definition) => definition.fields.filter((field) => field.role === 'output' && isEquationTargetKind(field.kind));
12
+ /**
13
+ * The field a thumbnail headlines, per CalculatorDefinition.previewFieldId's
14
+ * tri-state: an explicit id (when it names a numeric output), null for none,
15
+ * absent for auto — the sole numeric output, if there is exactly one.
16
+ */
17
+ export const previewFieldFor = (definition) => {
18
+ if (definition.previewFieldId === null)
19
+ return null;
20
+ const candidates = numericOutputs(definition);
21
+ if (typeof definition.previewFieldId === 'string') {
22
+ return (candidates.find((field) => field.id === definition.previewFieldId) ?? null);
23
+ }
24
+ return candidates.length === 1 ? candidates[0] : null;
25
+ };
26
+ /**
27
+ * The headline value for an instance, or null when there is nothing to show:
28
+ * no preview field, the field is unresolved (inputs missing), or it is in
29
+ * conflict (the runtime withholds conflicted values behind a dash — a tile
30
+ * should show nothing rather than a dash).
31
+ */
32
+ export const previewValueFor = (fileData) => {
33
+ const field = previewFieldFor(fileData.definition);
34
+ if (!field)
35
+ return null;
36
+ const resolved = deriveRuntime(fileData.definition, fileData.entries ?? {})
37
+ .resolved[field.id];
38
+ if (!resolved || resolved.conflict || typeof resolved.value !== 'number') {
39
+ return null;
40
+ }
41
+ const unit = displayUnitFor(field, fileData.displayUnitOverrides ?? {});
42
+ return { field, ...formatFieldValue(field, unit, resolved.value) };
43
+ };
@@ -190,6 +190,20 @@ export interface CalculatorDefinition {
190
190
  /** Inputs + outputs in one list, discriminated by `role`. */
191
191
  fields: CalculatorField[];
192
192
  equations: CalculatorEquation[];
193
+ /**
194
+ * The output whose resolved value a saved instance's THUMBNAIL headlines
195
+ * (Asana 1217881404454870 — Area/Volume and every T1R function calculator
196
+ * are impractical to open one by one just to read the result). Tri-state:
197
+ * - absent (undefined): AUTO — the sole numeric output when the
198
+ * calculator has exactly one, nothing when it has several. This is also
199
+ * what instances frozen before the field existed resolve to, so they
200
+ * gain a headline without being re-saved.
201
+ * - null: explicitly none — the author opted a single-output calc out.
202
+ * - id: that output. Must be role 'output' and an equation-target kind;
203
+ * anything else is validated as a warning and ignored at runtime.
204
+ * Always resolve through previewFieldFor() — never read this raw.
205
+ */
206
+ previewFieldId?: string | null;
193
207
  /** Diagram file (files subcollection doc carrying the Skia canvas). */
194
208
  diagramFileId?: string | null;
195
209
  /** Public calculators are readable by everyone; rules/query key. */
@@ -208,6 +208,8 @@ export const createEmptyCalculatorDefinition = (id) => ({
208
208
  category: null,
209
209
  fields: [],
210
210
  equations: [],
211
+ // previewFieldId is deliberately ABSENT, not null: absent means "auto"
212
+ // (headline the sole numeric output), null means the author opted out.
211
213
  diagramFileId: null,
212
214
  isPublic: false,
213
215
  });
@@ -174,6 +174,7 @@ export declare const calculatorDefinitionSchema: z.ZodObject<{
174
174
  variableUnits: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
175
175
  resultUnit: z.ZodOptional<z.ZodString>;
176
176
  }, z.core.$strip>>;
177
+ previewFieldId: z.ZodOptional<z.ZodNullable<z.ZodString>>;
177
178
  diagramFileId: z.ZodOptional<z.ZodNullable<z.ZodString>>;
178
179
  isPublic: z.ZodBoolean;
179
180
  sharedUserIds: z.ZodOptional<z.ZodArray<z.ZodString>>;
@@ -127,6 +127,9 @@ export const calculatorDefinitionSchema = z.object({
127
127
  category: z.string().nullish(),
128
128
  fields: z.array(calculatorFieldSchema),
129
129
  equations: z.array(calculatorEquationSchema),
130
+ // Listed so zod's unknown-key stripping doesn't silently drop it (see the
131
+ // `category` note above). Referential checks live in the invariant pass.
132
+ previewFieldId: z.string().nullish(),
130
133
  diagramFileId: z.string().nullable().optional(),
131
134
  isPublic: z.boolean(),
132
135
  sharedUserIds: z.array(z.string()).optional(),
@@ -229,6 +232,20 @@ export const validateCalculatorDefinition = (input) => {
229
232
  }
230
233
  fieldIds.add(field.id);
231
234
  });
235
+ // --- Thumbnail headline field -------------------------------------------
236
+ // Warnings, not errors: a dangling or mis-typed previewFieldId must not brick
237
+ // a published calculator (or every instance that froze it) — the runtime
238
+ // helper (previewFieldFor) simply shows no headline for it.
239
+ if (typeof def.previewFieldId === 'string') {
240
+ const previewField = def.fields.find((f) => f.id === def.previewFieldId);
241
+ if (!previewField) {
242
+ push('preview-field-missing', 'previewFieldId', `Thumbnail result field "${def.previewFieldId}" does not exist`, 'warning');
243
+ }
244
+ else if (previewField.role !== 'output' ||
245
+ !isEquationTargetKind(previewField.kind)) {
246
+ push('preview-field-not-numeric-output', 'previewFieldId', `Thumbnail result field "${previewField.name}" must be a numeric output`, 'warning');
247
+ }
248
+ }
232
249
  const equationIds = new Set();
233
250
  def.equations.forEach((eq, i) => {
234
251
  if (equationIds.has(eq.id)) {
package/dist/exports.d.ts CHANGED
@@ -32,7 +32,7 @@ export type { MeasurementStampRenderArgs, RenderMeasurementStamp, } from './anno
32
32
  export { STAMP_TILE_SIZE, GROUP_HEADER_TILE_WIDTH, GROUP_HEADER_TILE_HEIGHT, GROUP_HEADER_COLLAPSED_WIDTH, GROUP_HEADER_COLLAPSED_HEIGHT, GROUP_HEADER_DESIGN, groupHeaderBaseDims, DEFAULT_TILE_SCALE, TILE_SCALE_MIN, TILE_SCALE_MAX, clampTileScale, resolveTileScaleFactor, resolveHeaderTileScaleFactor, resolveFileTileScaleFactor, tileDocSizeForFactor, tileScaleFromDocSize, stampTileSize, stampTileDims, isUnassociatedStamp, isFileStamp, type StampTileDims, } from './annotation/canvas/stampLayout.js';
33
33
  export { GROUP_HEADER_STYLE, describeGroupHeader, groupHeaderSignature, type GroupHeaderBadge, type GroupHeaderDescriptor, } from './annotation/canvas/groupHeaderLayout.js';
34
34
  export { createViewportApi, fitToScreen, fitRectToScreen, imageDocRect, panBy, zoomAt, DEFAULT_VIEWPORT, type ViewportApi, type ViewportState, } from './annotation/canvas/viewport.js';
35
- export { LEGACY_BACKGROUND_LAYER_ID, BACKGROUND_PLACEMENT_GAP_RATIO, backgroundLayersOf, backgroundLayerDocRect, backgroundLayersUnionRect, placeNewBackgroundLayer, } from './annotation/canvas/backgroundLayers.js';
35
+ export { LEGACY_BACKGROUND_LAYER_ID, BACKGROUND_PLACEMENT_GAP_RATIO, backgroundLayersOf, canEditBackgroundLayers, backgroundLayerDocRect, backgroundLayersUnionRect, placeNewBackgroundLayer, } from './annotation/canvas/backgroundLayers.js';
36
36
  export { createPenTool, type PenToolOptions, } from './annotation/canvas/tools/penTool.js';
37
37
  export { createSelectTool, type SelectToolOptions, } from './annotation/canvas/tools/selectTool.js';
38
38
  export { createMeasurementStampTool, type MeasurementStampToolOptions, } from './annotation/canvas/tools/measurementStampTool.js';
package/dist/exports.js CHANGED
@@ -37,7 +37,7 @@ export { InMemoryAnnotationProvider } from './annotation/data/InMemoryAnnotation
37
37
  export { STAMP_TILE_SIZE, GROUP_HEADER_TILE_WIDTH, GROUP_HEADER_TILE_HEIGHT, GROUP_HEADER_COLLAPSED_WIDTH, GROUP_HEADER_COLLAPSED_HEIGHT, GROUP_HEADER_DESIGN, groupHeaderBaseDims, DEFAULT_TILE_SCALE, TILE_SCALE_MIN, TILE_SCALE_MAX, clampTileScale, resolveTileScaleFactor, resolveHeaderTileScaleFactor, resolveFileTileScaleFactor, tileDocSizeForFactor, tileScaleFromDocSize, stampTileSize, stampTileDims, isUnassociatedStamp, isFileStamp, } from './annotation/canvas/stampLayout.js';
38
38
  export { GROUP_HEADER_STYLE, describeGroupHeader, groupHeaderSignature, } from './annotation/canvas/groupHeaderLayout.js';
39
39
  export { createViewportApi, fitToScreen, fitRectToScreen, imageDocRect, panBy, zoomAt, DEFAULT_VIEWPORT, } from './annotation/canvas/viewport.js';
40
- export { LEGACY_BACKGROUND_LAYER_ID, BACKGROUND_PLACEMENT_GAP_RATIO, backgroundLayersOf, backgroundLayerDocRect, backgroundLayersUnionRect, placeNewBackgroundLayer, } from './annotation/canvas/backgroundLayers.js';
40
+ export { LEGACY_BACKGROUND_LAYER_ID, BACKGROUND_PLACEMENT_GAP_RATIO, backgroundLayersOf, canEditBackgroundLayers, backgroundLayerDocRect, backgroundLayersUnionRect, placeNewBackgroundLayer, } from './annotation/canvas/backgroundLayers.js';
41
41
  export { createPenTool, } from './annotation/canvas/tools/penTool.js';
42
42
  export { createSelectTool, } from './annotation/canvas/tools/selectTool.js';
43
43
  export { createMeasurementStampTool, } from './annotation/canvas/tools/measurementStampTool.js';
@@ -0,0 +1,16 @@
1
+ export { evaluateFormula, createFormulaScope, createEnhancedFormulaScope, clearFormulaCache, type EnhancedMapping, type FormulaDefinition, type FormulaEvaluationOptions, } from './formulas/evaluateFormula.js';
2
+ export { calculateFormula } from './formulas/calculateFormula.js';
3
+ export { convertMicrometers } from './utils/micrometersToUnit.js';
4
+ export { parseMeasurement } from './utils/parseMeasurement.js';
5
+ export * from './types/firestore.js';
6
+ export * from './types/layout.js';
7
+ export * from './types/annotation.js';
8
+ export { getToleranceColor, getToleranceSecondaryColor, calculateDeviationPercentage, isWithinTolerance, generateToleranceGradient, createDefaultToleranceThresholds, DEFAULT_TOLERANCE_COLORS, DEFAULT_TOLERANCE_SECONDARY_COLORS, type ToleranceThreshold, type ToleranceConfig, } from './utils/tolerance.js';
9
+ export { DEFAULT_GROUP_INDEX, isDefaultGroup, findDefaultGroup, isFormGroupCompleted, } from './utils/groups.js';
10
+ 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';
11
+ export { classifyFileForExport } from './export/objectKinds.js';
12
+ export { buildJobCsv, escapeCSVField, hyperlinkCell, type BuildJobCsvInput, type ExportObjectRow, type ExportScope, } from './export/buildJobCsv.js';
13
+ export { desktopBaseUrlForFirebaseProject, desktopFileEditorUrl, type DesktopLinkContext, } from './export/desktopLinks.js';
14
+ export { numberToLetterIndex, formGroupInputLabel, listGroupMeasurementLabel, } from './utils/indexLabels.js';
15
+ export { toSelectedValues, formatSelectedValues, toStoredValue, } from './utils/selectValues.js';
16
+ export * from './calculator/index.js';
package/dist/server.js ADDED
@@ -0,0 +1,21 @@
1
+ // Server-safe entry point (package export "./server"). Node consumers —
2
+ // boldr-api, Cloud Functions, scripts — import from here instead of the
3
+ // root barrel, whose module graph statically pulls in `react` and
4
+ // `@shopify/react-native-skia`. Only modules with no React/Skia anywhere
5
+ // in their import graph may be re-exported from this file.
6
+ export { evaluateFormula, createFormulaScope, createEnhancedFormulaScope, clearFormulaCache, } from './formulas/evaluateFormula.js';
7
+ export { calculateFormula } from './formulas/calculateFormula.js';
8
+ export { convertMicrometers } from './utils/micrometersToUnit.js';
9
+ export { parseMeasurement } from './utils/parseMeasurement.js';
10
+ export * from './types/firestore.js';
11
+ export * from './types/layout.js';
12
+ export * from './types/annotation.js';
13
+ export { getToleranceColor, getToleranceSecondaryColor, calculateDeviationPercentage, isWithinTolerance, generateToleranceGradient, createDefaultToleranceThresholds, DEFAULT_TOLERANCE_COLORS, DEFAULT_TOLERANCE_SECONDARY_COLORS, } from './utils/tolerance.js';
14
+ export { DEFAULT_GROUP_INDEX, isDefaultGroup, findDefaultGroup, isFormGroupCompleted, } from './utils/groups.js';
15
+ export { ALL_EXPORT_CATEGORIES, ALL_EXPORT_MEASUREMENT_DATA_FIELDS, ALL_EXPORT_OBJECT_TYPES, DEFAULT_EXPORT_CONFIG, EXPORT_EVERYTHING_CONFIG, normalizeExportConfig, } from './export/config.js';
16
+ export { classifyFileForExport } from './export/objectKinds.js';
17
+ export { buildJobCsv, escapeCSVField, hyperlinkCell, } from './export/buildJobCsv.js';
18
+ export { desktopBaseUrlForFirebaseProject, desktopFileEditorUrl, } from './export/desktopLinks.js';
19
+ export { numberToLetterIndex, formGroupInputLabel, listGroupMeasurementLabel, } from './utils/indexLabels.js';
20
+ export { toSelectedValues, formatSelectedValues, toStoredValue, } from './utils/selectValues.js';
21
+ export * from './calculator/index.js';
@@ -67,6 +67,7 @@ export interface Project extends FirestoreDoc, Timestamps {
67
67
  state: string;
68
68
  collectionId: string | null;
69
69
  index: number | null;
70
+ bgImgUpdatedAt?: number;
70
71
  }
71
72
  export declare enum TemplateType {
72
73
  Layout = "layout",
@@ -228,6 +229,7 @@ export interface Job extends FirestoreDoc, Timestamps {
228
229
  description?: string;
229
230
  starred?: boolean;
230
231
  lastModified: Date;
232
+ bgImgUpdatedAt?: number;
231
233
  }
232
234
  export interface Section extends Timestamps {
233
235
  id: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reekon-tools/boldr-utils",
3
- "version": "1.15.5",
3
+ "version": "1.17.0",
4
4
  "description": "Shared utilities for formulas and measurement conversion used in Reekon apps",
5
5
  "author": "REEKON Tools",
6
6
  "license": "MIT",
@@ -24,6 +24,11 @@
24
24
  "import": "./dist/theme/colors.js",
25
25
  "default": "./dist/theme/colors.js"
26
26
  },
27
+ "./server": {
28
+ "types": "./dist/server.d.ts",
29
+ "import": "./dist/server.js",
30
+ "default": "./dist/server.js"
31
+ },
27
32
  "./package.json": "./package.json"
28
33
  },
29
34
  "scripts": {