@fieldnotes/core 0.50.6 → 0.50.8

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/index.d.cts CHANGED
@@ -1123,6 +1123,6 @@ declare class LaserTool implements Tool {
1123
1123
  private notifyOptionsChange;
1124
1124
  }
1125
1125
 
1126
- declare const VERSION = "0.50.6";
1126
+ declare const VERSION = "0.50.8";
1127
1127
 
1128
1128
  export { type ActiveFormats, type AlignEdge, type ArrowElement, type ArrowStrokeStyle, ArrowTool, type ArrowToolOptions, AutoSave, type AutoSaveOptions, type BackgroundOptions, type BackgroundPattern, type Binding, type Bounds, Camera, type CameraChangeInfo, type CameraOptions, type CanvasElement, type CanvasState, type Command, DEFAULT_NOTE_FONT_SIZE, type DistributeAxis, type ElementChangeMeta, ElementStore, type ElementStyle, type ElementType, type ElementUpdateEvent, EraserTool, type EraserToolOptions, type ExportImageOptions, type ExportSvgOptions, type FontSizePreset, type GridElement, type GridInfo, HandTool, type HexOrientation, HistoryStack, type HistoryStackOptions, type HtmlElement, type ImageElement, ImageTool, type ImageToolOptions, IndexedDBAdapter, type IndexedDBAdapterOptions, LaserTool, type LaserToolOptions, type Layer, LayerManager, LocalStorageAdapter, MeasureTool, type MeasureToolOptions, type Measurement, MemoryAdapter, type NoteElement, NoteTool, type NoteToolOptions, PencilTool, type PencilToolOptions, type Point, type PointerState, type RenderStatsSnapshot, type RotateDirection, SelectTool, type ShapeElement, type ShapeKind, ShapeTool, type ShapeToolOptions, type ShortcutBindings, type ShortcutOptions, type ShortcutsApi, type Size, type StorageAdapter, type StrokeElement, type StrokePoint, type TemplateElement, type TemplateRenderStyle, type TemplateShape, TemplateTool, type TemplateToolOptions, type TextElement, TextTool, type TextToolOptions, type Tool, type ToolContext, ToolManager, type ToolName, VERSION, Viewport, type ViewportOptions, boundsIntersect, createArrow, createGrid, createHtmlElement, createImage, createNote, createShape, createStroke, createTemplate, createText, drawHexPath, exportImage, exportSvg, getActiveFormats, getArrowBounds, getArrowControlPoint, getArrowMidpoint, getArrowTangentAngle, getBendFromPoint, getElementBounds, getElementStyle, getElementsBoundingBox, getHexCellsInCone, getHexCellsInLine, getHexCellsInRadius, getHexCellsInRectangle, getHexCellsInSquare, getHexDistance, isNearBezier, setFontSize, smartSnap, snapPoint, snapToHexCenter, styleToPatch, toggleBold, toggleItalic, toggleStrikethrough, toggleUnderline };
package/dist/index.d.ts CHANGED
@@ -1123,6 +1123,6 @@ declare class LaserTool implements Tool {
1123
1123
  private notifyOptionsChange;
1124
1124
  }
1125
1125
 
1126
- declare const VERSION = "0.50.6";
1126
+ declare const VERSION = "0.50.8";
1127
1127
 
1128
1128
  export { type ActiveFormats, type AlignEdge, type ArrowElement, type ArrowStrokeStyle, ArrowTool, type ArrowToolOptions, AutoSave, type AutoSaveOptions, type BackgroundOptions, type BackgroundPattern, type Binding, type Bounds, Camera, type CameraChangeInfo, type CameraOptions, type CanvasElement, type CanvasState, type Command, DEFAULT_NOTE_FONT_SIZE, type DistributeAxis, type ElementChangeMeta, ElementStore, type ElementStyle, type ElementType, type ElementUpdateEvent, EraserTool, type EraserToolOptions, type ExportImageOptions, type ExportSvgOptions, type FontSizePreset, type GridElement, type GridInfo, HandTool, type HexOrientation, HistoryStack, type HistoryStackOptions, type HtmlElement, type ImageElement, ImageTool, type ImageToolOptions, IndexedDBAdapter, type IndexedDBAdapterOptions, LaserTool, type LaserToolOptions, type Layer, LayerManager, LocalStorageAdapter, MeasureTool, type MeasureToolOptions, type Measurement, MemoryAdapter, type NoteElement, NoteTool, type NoteToolOptions, PencilTool, type PencilToolOptions, type Point, type PointerState, type RenderStatsSnapshot, type RotateDirection, SelectTool, type ShapeElement, type ShapeKind, ShapeTool, type ShapeToolOptions, type ShortcutBindings, type ShortcutOptions, type ShortcutsApi, type Size, type StorageAdapter, type StrokeElement, type StrokePoint, type TemplateElement, type TemplateRenderStyle, type TemplateShape, TemplateTool, type TemplateToolOptions, type TextElement, TextTool, type TextToolOptions, type Tool, type ToolContext, ToolManager, type ToolName, VERSION, Viewport, type ViewportOptions, boundsIntersect, createArrow, createGrid, createHtmlElement, createImage, createNote, createShape, createStroke, createTemplate, createText, drawHexPath, exportImage, exportSvg, getActiveFormats, getArrowBounds, getArrowControlPoint, getArrowMidpoint, getArrowTangentAngle, getBendFromPoint, getElementBounds, getElementStyle, getElementsBoundingBox, getHexCellsInCone, getHexCellsInLine, getHexCellsInRadius, getHexCellsInRectangle, getHexCellsInSquare, getHexDistance, isNearBezier, setFontSize, smartSnap, snapPoint, snapToHexCenter, styleToPatch, toggleBold, toggleItalic, toggleStrikethrough, toggleUnderline };
package/dist/index.js CHANGED
@@ -152,6 +152,17 @@ function sanitizeAttributes(el, tag) {
152
152
 
153
153
  // src/core/state-serializer.ts
154
154
  var CURRENT_VERSION = 2;
155
+ var ELEMENT_TYPES = [
156
+ "stroke",
157
+ "note",
158
+ "arrow",
159
+ "image",
160
+ "html",
161
+ "text",
162
+ "shape",
163
+ "grid",
164
+ "template"
165
+ ];
155
166
  function exportState(elements, camera, layers = [], activeLayerId) {
156
167
  const state = {
157
168
  version: CURRENT_VERSION,
@@ -177,37 +188,42 @@ function parseState(json) {
177
188
  return data;
178
189
  }
179
190
  function validateState(data) {
180
- if (!data || typeof data !== "object") {
191
+ if (!isRecord(data)) {
181
192
  throw new Error("Invalid state: expected an object");
182
193
  }
183
194
  const obj = data;
184
- if (typeof obj["version"] !== "number") {
195
+ if (!Number.isInteger(obj["version"]) || obj["version"] < 1) {
185
196
  throw new Error("Invalid state: missing or invalid version");
186
197
  }
187
- if (!obj["camera"] || typeof obj["camera"] !== "object") {
198
+ if (obj["version"] > CURRENT_VERSION) {
199
+ throw new Error(`Invalid state: unsupported version ${String(obj["version"])}`);
200
+ }
201
+ if (!isRecord(obj["camera"])) {
188
202
  throw new Error("Invalid state: missing camera");
189
203
  }
190
204
  const cam = obj["camera"];
191
- if (!cam["position"] || typeof cam["position"] !== "object") {
205
+ if (!isRecord(cam["position"])) {
192
206
  throw new Error("Invalid state: missing camera.position");
193
207
  }
194
- const pos = cam["position"];
195
- if (typeof pos["x"] !== "number" || typeof pos["y"] !== "number") {
196
- throw new Error("Invalid state: camera.position must have x and y numbers");
208
+ if (!isPoint(cam["position"])) {
209
+ throw new Error("Invalid state: camera.position must have finite x and y numbers");
197
210
  }
198
- if (typeof cam["zoom"] !== "number") {
199
- throw new Error("Invalid state: missing camera.zoom");
211
+ if (!isFiniteNumber(cam["zoom"]) || cam["zoom"] <= 0) {
212
+ throw new Error("Invalid state: camera.zoom must be a positive finite number");
200
213
  }
201
214
  if (!Array.isArray(obj["elements"])) {
202
215
  throw new Error("Invalid state: elements must be an array");
203
216
  }
204
- for (const el of obj["elements"]) {
205
- validateElement(el);
206
- migrateElement(el);
217
+ if (obj["layers"] !== void 0 && !Array.isArray(obj["layers"])) {
218
+ throw new Error("Invalid state: layers must be an array");
207
219
  }
208
- cleanBindings(obj["elements"]);
209
- const layers = obj["layers"];
210
- if (!Array.isArray(layers) || layers.length === 0) {
220
+ const elements = obj["elements"];
221
+ const hasLayers = Array.isArray(obj["layers"]) && obj["layers"].length > 0;
222
+ for (const el of elements) {
223
+ if (!isRecord(el)) throw new Error("Invalid element: expected an object");
224
+ migrateElement(el, !hasLayers);
225
+ }
226
+ if (!hasLayers) {
211
227
  obj["layers"] = [
212
228
  {
213
229
  id: "default-layer",
@@ -219,31 +235,73 @@ function validateState(data) {
219
235
  }
220
236
  ];
221
237
  }
238
+ const layers = obj["layers"];
239
+ const layerIds = /* @__PURE__ */ new Set();
240
+ for (const layer of layers) {
241
+ validateLayer(layer);
242
+ if (layerIds.has(layer.id)) throw new Error(`Invalid state: duplicate layer id "${layer.id}"`);
243
+ layerIds.add(layer.id);
244
+ }
245
+ const elementIds = /* @__PURE__ */ new Set();
246
+ for (const el of elements) {
247
+ validateElement(el);
248
+ if (elementIds.has(el.id)) throw new Error(`Invalid state: duplicate element id "${el.id}"`);
249
+ elementIds.add(el.id);
250
+ if (!layerIds.has(el.layerId)) {
251
+ throw new Error(`Invalid element "${el.id}": unknown layerId "${el.layerId}"`);
252
+ }
253
+ }
254
+ if (obj["activeLayerId"] !== void 0) {
255
+ if (typeof obj["activeLayerId"] !== "string" || !layerIds.has(obj["activeLayerId"])) {
256
+ throw new Error("Invalid state: activeLayerId must reference an existing layer");
257
+ }
258
+ }
259
+ cleanBindings(elements);
222
260
  }
223
- var VALID_TYPES = /* @__PURE__ */ new Set([
224
- "stroke",
225
- "note",
226
- "arrow",
227
- "image",
228
- "html",
229
- "text",
230
- "shape",
231
- "grid",
232
- "template"
233
- ]);
234
261
  function validateElement(el) {
235
- if (!el || typeof el !== "object") {
262
+ if (!isRecord(el)) {
236
263
  throw new Error("Invalid element: expected an object");
237
264
  }
238
- const obj = el;
239
- if (typeof obj["id"] !== "string") {
265
+ if (typeof el["id"] !== "string" || el["id"].length === 0) {
240
266
  throw new Error("Invalid element: missing id");
241
267
  }
242
- if (typeof obj["type"] !== "string" || !VALID_TYPES.has(obj["type"])) {
243
- throw new Error(`Invalid element: unknown type "${String(obj["type"])}"`);
268
+ if (!isEnum(el["type"], ELEMENT_TYPES)) {
269
+ throw new Error(`Invalid element: unknown type "${String(el["type"])}"`);
244
270
  }
245
- if (typeof obj["zIndex"] !== "number") {
246
- throw new Error("Invalid element: missing zIndex");
271
+ if (!isFiniteNumber(el["zIndex"])) {
272
+ throw new Error(`Invalid element "${el["id"]}": missing or invalid zIndex`);
273
+ }
274
+ if (!isPoint(el["position"]) || typeof el["locked"] !== "boolean" || typeof el["layerId"] !== "string" || !isOptional(el["groupId"], isString) || !isOptional(el["rotation"], isFiniteNumber)) {
275
+ throw new Error(`Invalid element "${el["id"]}": invalid base fields or geometry`);
276
+ }
277
+ const valid = validateTypeFields(el, el["type"]);
278
+ if (!valid) throw new Error(`Invalid element "${el["id"]}": malformed ${el["type"]} data`);
279
+ }
280
+ function validateTypeFields(el, type) {
281
+ switch (type) {
282
+ case "stroke":
283
+ return Array.isArray(el["points"]) && el["points"].every(isStrokePoint) && isString(el["color"]) && isFiniteNumber(el["width"]) && isFiniteNumber(el["opacity"]) && isOptionalEnum(el["blendMode"], ["multiply"]);
284
+ case "note":
285
+ return isSize(el["size"]) && isString(el["text"]) && isString(el["backgroundColor"]) && isString(el["textColor"]) && isOptional(el["fontSize"], isFiniteNumber);
286
+ case "arrow":
287
+ return isPoint(el["from"]) && isPoint(el["to"]) && isFiniteNumber(el["bend"]) && isString(el["color"]) && isFiniteNumber(el["width"]) && isOptional(el["fromBinding"], isBinding) && isOptional(el["toBinding"], isBinding) && isOptional(el["cachedControlPoint"], isPoint) && isOptional(el["label"], isString) && isOptionalEnum(el["strokeStyle"], ["solid", "dashed", "dotted"]);
288
+ case "image":
289
+ return isSize(el["size"]) && isString(el["src"]);
290
+ case "html":
291
+ return isSize(el["size"]) && isOptional(el["domId"], isString) && isOptional(el["interactive"], isBoolean) && isOptional(el["htmlType"], isString) && isOptional(el["data"], isRecord);
292
+ case "text":
293
+ return isSize(el["size"]) && isString(el["text"]) && isFiniteNumber(el["fontSize"]) && isString(el["color"]) && isEnum(el["textAlign"], ["left", "center", "right"]);
294
+ case "shape":
295
+ return isEnum(el["shape"], ["rectangle", "ellipse", "line"]) && isSize(el["size"]) && isString(el["strokeColor"]) && isFiniteNumber(el["strokeWidth"]) && isString(el["fillColor"]) && isOptional(el["flip"], isBoolean);
296
+ case "grid":
297
+ return isEnum(el["gridType"], ["square", "hex"]) && isEnum(el["hexOrientation"], ["pointy", "flat"]) && isFiniteNumber(el["cellSize"]) && isString(el["strokeColor"]) && isFiniteNumber(el["strokeWidth"]) && isFiniteNumber(el["opacity"]);
298
+ case "template":
299
+ return isEnum(el["templateShape"], ["circle", "cone", "line", "square", "rectangle"]) && isFiniteNumber(el["radius"]) && isFiniteNumber(el["angle"]) && isOptional(el["width"], isFiniteNumber) && isString(el["fillColor"]) && isString(el["strokeColor"]) && isFiniteNumber(el["strokeWidth"]) && isFiniteNumber(el["opacity"]) && isOptional(el["feetPerCell"], isFiniteNumber) && isOptional(el["radiusFeet"], isFiniteNumber) && isOptionalEnum(el["renderStyle"], ["cells", "geometric"]);
300
+ }
301
+ }
302
+ function validateLayer(layer) {
303
+ if (!isRecord(layer) || typeof layer["id"] !== "string" || layer["id"].length === 0 || typeof layer["name"] !== "string" || typeof layer["visible"] !== "boolean" || typeof layer["locked"] !== "boolean" || !isFiniteNumber(layer["order"]) || !isFiniteNumber(layer["opacity"]) || layer["opacity"] < 0 || layer["opacity"] > 1) {
304
+ throw new Error("Invalid state: malformed layer");
247
305
  }
248
306
  }
249
307
  function cleanBindings(elements) {
@@ -260,30 +318,63 @@ function cleanBindings(elements) {
260
318
  }
261
319
  }
262
320
  }
263
- function migrateElement(obj) {
264
- if (typeof obj["layerId"] !== "string") {
321
+ function migrateElement(obj, useDefaultLayer) {
322
+ if (obj["layerId"] === void 0 || useDefaultLayer && obj["layerId"] === "") {
265
323
  obj["layerId"] = "default-layer";
266
324
  }
267
- if (obj["type"] === "arrow" && typeof obj["bend"] !== "number") {
325
+ if (obj["type"] === "arrow" && obj["bend"] === void 0) {
268
326
  obj["bend"] = 0;
269
327
  }
270
328
  if (obj["type"] === "stroke" && Array.isArray(obj["points"])) {
271
329
  for (const pt of obj["points"]) {
272
- if (typeof pt["pressure"] !== "number") {
330
+ if (pt["pressure"] === void 0) {
273
331
  pt["pressure"] = 0.5;
274
332
  }
275
333
  }
276
334
  }
277
- if (obj["type"] === "shape" && typeof obj["shape"] !== "string") {
335
+ if (obj["type"] === "shape" && obj["shape"] === void 0) {
278
336
  obj["shape"] = "rectangle";
279
337
  }
280
- if (obj["type"] === "note" && typeof obj["textColor"] !== "string") {
338
+ if (obj["type"] === "note" && obj["textColor"] === void 0) {
281
339
  obj["textColor"] = "#000000";
282
340
  }
283
341
  if ((obj["type"] === "note" || obj["type"] === "text") && typeof obj["text"] === "string") {
284
342
  obj["text"] = sanitizeNoteHtml(obj["text"]);
285
343
  }
286
344
  }
345
+ function isRecord(value) {
346
+ return typeof value === "object" && value !== null && !Array.isArray(value);
347
+ }
348
+ function isFiniteNumber(value) {
349
+ return typeof value === "number" && Number.isFinite(value);
350
+ }
351
+ function isString(value) {
352
+ return typeof value === "string";
353
+ }
354
+ function isBoolean(value) {
355
+ return typeof value === "boolean";
356
+ }
357
+ function isOptional(value, validate) {
358
+ return value === void 0 || validate(value);
359
+ }
360
+ function isEnum(value, values) {
361
+ return typeof value === "string" && values.includes(value);
362
+ }
363
+ function isOptionalEnum(value, values) {
364
+ return value === void 0 || isEnum(value, values);
365
+ }
366
+ function isPoint(value) {
367
+ return isRecord(value) && isFiniteNumber(value["x"]) && isFiniteNumber(value["y"]);
368
+ }
369
+ function isSize(value) {
370
+ return isRecord(value) && isFiniteNumber(value["w"]) && isFiniteNumber(value["h"]);
371
+ }
372
+ function isStrokePoint(value) {
373
+ return isRecord(value) && isPoint(value) && isFiniteNumber(value["pressure"]);
374
+ }
375
+ function isBinding(value) {
376
+ return isRecord(value) && typeof value["elementId"] === "string";
377
+ }
287
378
 
288
379
  // src/core/storage/local-storage-adapter.ts
289
380
  var LocalStorageAdapter = class {
@@ -10637,7 +10728,7 @@ var LaserTool = class {
10637
10728
  };
10638
10729
 
10639
10730
  // src/index.ts
10640
- var VERSION = "0.50.6";
10731
+ var VERSION = "0.50.8";
10641
10732
  export {
10642
10733
  ArrowTool,
10643
10734
  AutoSave,