@fieldnotes/core 0.2.2 → 0.3.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.
package/dist/index.d.cts CHANGED
@@ -61,7 +61,15 @@ interface HtmlElement extends BaseElement {
61
61
  type: 'html';
62
62
  size: Size;
63
63
  }
64
- type CanvasElement = StrokeElement | NoteElement | ArrowElement | ImageElement | HtmlElement;
64
+ interface TextElement extends BaseElement {
65
+ type: 'text';
66
+ size: Size;
67
+ text: string;
68
+ fontSize: number;
69
+ color: string;
70
+ textAlign: 'left' | 'center' | 'right';
71
+ }
72
+ type CanvasElement = StrokeElement | NoteElement | ArrowElement | ImageElement | HtmlElement | TextElement;
65
73
  type ElementType = CanvasElement['type'];
66
74
 
67
75
  interface CanvasState {
@@ -196,7 +204,7 @@ interface Tool {
196
204
  onDeactivate?(ctx: ToolContext): void;
197
205
  renderOverlay?(ctx: CanvasRenderingContext2D): void;
198
206
  }
199
- type ToolName = 'hand' | 'select' | 'pencil' | 'eraser' | 'arrow' | 'note' | 'image';
207
+ type ToolName = 'hand' | 'select' | 'pencil' | 'eraser' | 'arrow' | 'note' | 'image' | 'text';
200
208
 
201
209
  declare class ToolManager {
202
210
  private tools;
@@ -360,7 +368,8 @@ declare class Viewport {
360
368
  destroy(): void;
361
369
  private startRenderLoop;
362
370
  private render;
363
- private startEditingNote;
371
+ private startEditingElement;
372
+ private onTextEditStop;
364
373
  private onDblClick;
365
374
  private hitTestWorld;
366
375
  private startInteracting;
@@ -397,8 +406,10 @@ declare class NoteEditor {
397
406
  private keyHandler;
398
407
  private pointerHandler;
399
408
  private pendingEditId;
409
+ private onStopCallback;
400
410
  get isEditing(): boolean;
401
411
  get editingElementId(): string | null;
412
+ setOnStop(callback: (elementId: string) => void): void;
402
413
  startEditing(node: HTMLDivElement, elementId: string, store: ElementStore): void;
403
414
  stopEditing(store: ElementStore): void;
404
415
  destroy(store: ElementStore): void;
@@ -440,11 +451,20 @@ interface HtmlInput extends BaseDefaults {
440
451
  position: Point;
441
452
  size: Size;
442
453
  }
454
+ interface TextInput extends BaseDefaults {
455
+ position: Point;
456
+ size?: Size;
457
+ text?: string;
458
+ fontSize?: number;
459
+ color?: string;
460
+ textAlign?: 'left' | 'center' | 'right';
461
+ }
443
462
  declare function createStroke(input: StrokeInput): StrokeElement;
444
463
  declare function createNote(input: NoteInput): NoteElement;
445
464
  declare function createArrow(input: ArrowInput): ArrowElement;
446
465
  declare function createImage(input: ImageInput): ImageElement;
447
466
  declare function createHtmlElement(input: HtmlInput): HtmlElement;
467
+ declare function createText(input: TextInput): TextElement;
448
468
 
449
469
  interface Rect {
450
470
  x: number;
@@ -601,6 +621,25 @@ declare class NoteTool implements Tool {
601
621
  onPointerUp(state: PointerState, ctx: ToolContext): void;
602
622
  }
603
623
 
624
+ interface TextToolOptions {
625
+ fontSize?: number;
626
+ color?: string;
627
+ textAlign?: 'left' | 'center' | 'right';
628
+ }
629
+ declare class TextTool implements Tool {
630
+ readonly name = "text";
631
+ private fontSize;
632
+ private color;
633
+ private textAlign;
634
+ constructor(options?: TextToolOptions);
635
+ setOptions(options: TextToolOptions): void;
636
+ onActivate(ctx: ToolContext): void;
637
+ onDeactivate(ctx: ToolContext): void;
638
+ onPointerDown(_state: PointerState, _ctx: ToolContext): void;
639
+ onPointerMove(_state: PointerState, _ctx: ToolContext): void;
640
+ onPointerUp(state: PointerState, ctx: ToolContext): void;
641
+ }
642
+
604
643
  interface ImageToolOptions {
605
644
  size?: Size;
606
645
  }
@@ -615,6 +654,6 @@ declare class ImageTool implements Tool {
615
654
  onPointerUp(state: PointerState, ctx: ToolContext): void;
616
655
  }
617
656
 
618
- declare const VERSION = "0.2.2";
657
+ declare const VERSION = "0.3.0";
619
658
 
620
- export { AddElementCommand, type ArrowElement, ArrowTool, type ArrowToolOptions, AutoSave, type AutoSaveOptions, Background, type BackgroundOptions, type BackgroundPattern, BatchCommand, type Bounds, Camera, type CameraOptions, type CanvasElement, type CanvasState, type Command, ElementRenderer, ElementStore, type ElementType, type ElementUpdateEvent, EraserTool, type EraserToolOptions, EventBus, HandTool, HistoryRecorder, HistoryStack, type HistoryStackOptions, type HtmlElement, type ImageElement, ImageTool, type ImageToolOptions, InputHandler, NoteEditor, type NoteElement, NoteTool, type NoteToolOptions, PencilTool, type PencilToolOptions, type Point, type PointerState, RemoveElementCommand, SelectTool, type Size, type StrokeElement, type StrokePoint, type Tool, type ToolContext, ToolManager, type ToolName, UpdateElementCommand, VERSION, Viewport, type ViewportOptions, createArrow, createHtmlElement, createId, createImage, createNote, createStroke, exportState, getArrowBounds, getArrowControlPoint, getArrowMidpoint, getArrowTangentAngle, getBendFromPoint, isNearBezier, parseState };
659
+ export { AddElementCommand, type ArrowElement, ArrowTool, type ArrowToolOptions, AutoSave, type AutoSaveOptions, Background, type BackgroundOptions, type BackgroundPattern, BatchCommand, type Bounds, Camera, type CameraOptions, type CanvasElement, type CanvasState, type Command, ElementRenderer, ElementStore, type ElementType, type ElementUpdateEvent, EraserTool, type EraserToolOptions, EventBus, HandTool, HistoryRecorder, HistoryStack, type HistoryStackOptions, type HtmlElement, type ImageElement, ImageTool, type ImageToolOptions, InputHandler, NoteEditor, type NoteElement, NoteTool, type NoteToolOptions, PencilTool, type PencilToolOptions, type Point, type PointerState, RemoveElementCommand, SelectTool, type Size, type StrokeElement, type StrokePoint, type TextElement, TextTool, type TextToolOptions, type Tool, type ToolContext, ToolManager, type ToolName, UpdateElementCommand, VERSION, Viewport, type ViewportOptions, createArrow, createHtmlElement, createId, createImage, createNote, createStroke, createText, exportState, getArrowBounds, getArrowControlPoint, getArrowMidpoint, getArrowTangentAngle, getBendFromPoint, isNearBezier, parseState };
package/dist/index.d.ts CHANGED
@@ -61,7 +61,15 @@ interface HtmlElement extends BaseElement {
61
61
  type: 'html';
62
62
  size: Size;
63
63
  }
64
- type CanvasElement = StrokeElement | NoteElement | ArrowElement | ImageElement | HtmlElement;
64
+ interface TextElement extends BaseElement {
65
+ type: 'text';
66
+ size: Size;
67
+ text: string;
68
+ fontSize: number;
69
+ color: string;
70
+ textAlign: 'left' | 'center' | 'right';
71
+ }
72
+ type CanvasElement = StrokeElement | NoteElement | ArrowElement | ImageElement | HtmlElement | TextElement;
65
73
  type ElementType = CanvasElement['type'];
66
74
 
67
75
  interface CanvasState {
@@ -196,7 +204,7 @@ interface Tool {
196
204
  onDeactivate?(ctx: ToolContext): void;
197
205
  renderOverlay?(ctx: CanvasRenderingContext2D): void;
198
206
  }
199
- type ToolName = 'hand' | 'select' | 'pencil' | 'eraser' | 'arrow' | 'note' | 'image';
207
+ type ToolName = 'hand' | 'select' | 'pencil' | 'eraser' | 'arrow' | 'note' | 'image' | 'text';
200
208
 
201
209
  declare class ToolManager {
202
210
  private tools;
@@ -360,7 +368,8 @@ declare class Viewport {
360
368
  destroy(): void;
361
369
  private startRenderLoop;
362
370
  private render;
363
- private startEditingNote;
371
+ private startEditingElement;
372
+ private onTextEditStop;
364
373
  private onDblClick;
365
374
  private hitTestWorld;
366
375
  private startInteracting;
@@ -397,8 +406,10 @@ declare class NoteEditor {
397
406
  private keyHandler;
398
407
  private pointerHandler;
399
408
  private pendingEditId;
409
+ private onStopCallback;
400
410
  get isEditing(): boolean;
401
411
  get editingElementId(): string | null;
412
+ setOnStop(callback: (elementId: string) => void): void;
402
413
  startEditing(node: HTMLDivElement, elementId: string, store: ElementStore): void;
403
414
  stopEditing(store: ElementStore): void;
404
415
  destroy(store: ElementStore): void;
@@ -440,11 +451,20 @@ interface HtmlInput extends BaseDefaults {
440
451
  position: Point;
441
452
  size: Size;
442
453
  }
454
+ interface TextInput extends BaseDefaults {
455
+ position: Point;
456
+ size?: Size;
457
+ text?: string;
458
+ fontSize?: number;
459
+ color?: string;
460
+ textAlign?: 'left' | 'center' | 'right';
461
+ }
443
462
  declare function createStroke(input: StrokeInput): StrokeElement;
444
463
  declare function createNote(input: NoteInput): NoteElement;
445
464
  declare function createArrow(input: ArrowInput): ArrowElement;
446
465
  declare function createImage(input: ImageInput): ImageElement;
447
466
  declare function createHtmlElement(input: HtmlInput): HtmlElement;
467
+ declare function createText(input: TextInput): TextElement;
448
468
 
449
469
  interface Rect {
450
470
  x: number;
@@ -601,6 +621,25 @@ declare class NoteTool implements Tool {
601
621
  onPointerUp(state: PointerState, ctx: ToolContext): void;
602
622
  }
603
623
 
624
+ interface TextToolOptions {
625
+ fontSize?: number;
626
+ color?: string;
627
+ textAlign?: 'left' | 'center' | 'right';
628
+ }
629
+ declare class TextTool implements Tool {
630
+ readonly name = "text";
631
+ private fontSize;
632
+ private color;
633
+ private textAlign;
634
+ constructor(options?: TextToolOptions);
635
+ setOptions(options: TextToolOptions): void;
636
+ onActivate(ctx: ToolContext): void;
637
+ onDeactivate(ctx: ToolContext): void;
638
+ onPointerDown(_state: PointerState, _ctx: ToolContext): void;
639
+ onPointerMove(_state: PointerState, _ctx: ToolContext): void;
640
+ onPointerUp(state: PointerState, ctx: ToolContext): void;
641
+ }
642
+
604
643
  interface ImageToolOptions {
605
644
  size?: Size;
606
645
  }
@@ -615,6 +654,6 @@ declare class ImageTool implements Tool {
615
654
  onPointerUp(state: PointerState, ctx: ToolContext): void;
616
655
  }
617
656
 
618
- declare const VERSION = "0.2.2";
657
+ declare const VERSION = "0.3.0";
619
658
 
620
- export { AddElementCommand, type ArrowElement, ArrowTool, type ArrowToolOptions, AutoSave, type AutoSaveOptions, Background, type BackgroundOptions, type BackgroundPattern, BatchCommand, type Bounds, Camera, type CameraOptions, type CanvasElement, type CanvasState, type Command, ElementRenderer, ElementStore, type ElementType, type ElementUpdateEvent, EraserTool, type EraserToolOptions, EventBus, HandTool, HistoryRecorder, HistoryStack, type HistoryStackOptions, type HtmlElement, type ImageElement, ImageTool, type ImageToolOptions, InputHandler, NoteEditor, type NoteElement, NoteTool, type NoteToolOptions, PencilTool, type PencilToolOptions, type Point, type PointerState, RemoveElementCommand, SelectTool, type Size, type StrokeElement, type StrokePoint, type Tool, type ToolContext, ToolManager, type ToolName, UpdateElementCommand, VERSION, Viewport, type ViewportOptions, createArrow, createHtmlElement, createId, createImage, createNote, createStroke, exportState, getArrowBounds, getArrowControlPoint, getArrowMidpoint, getArrowTangentAngle, getBendFromPoint, isNearBezier, parseState };
659
+ export { AddElementCommand, type ArrowElement, ArrowTool, type ArrowToolOptions, AutoSave, type AutoSaveOptions, Background, type BackgroundOptions, type BackgroundPattern, BatchCommand, type Bounds, Camera, type CameraOptions, type CanvasElement, type CanvasState, type Command, ElementRenderer, ElementStore, type ElementType, type ElementUpdateEvent, EraserTool, type EraserToolOptions, EventBus, HandTool, HistoryRecorder, HistoryStack, type HistoryStackOptions, type HtmlElement, type ImageElement, ImageTool, type ImageToolOptions, InputHandler, NoteEditor, type NoteElement, NoteTool, type NoteToolOptions, PencilTool, type PencilToolOptions, type Point, type PointerState, RemoveElementCommand, SelectTool, type Size, type StrokeElement, type StrokePoint, type TextElement, TextTool, type TextToolOptions, type Tool, type ToolContext, ToolManager, type ToolName, UpdateElementCommand, VERSION, Viewport, type ViewportOptions, createArrow, createHtmlElement, createId, createImage, createNote, createStroke, createText, exportState, getArrowBounds, getArrowControlPoint, getArrowMidpoint, getArrowTangentAngle, getBendFromPoint, isNearBezier, parseState };
package/dist/index.js CHANGED
@@ -69,7 +69,7 @@ function validateState(data) {
69
69
  migrateElement(el);
70
70
  }
71
71
  }
72
- var VALID_TYPES = /* @__PURE__ */ new Set(["stroke", "note", "arrow", "image", "html"]);
72
+ var VALID_TYPES = /* @__PURE__ */ new Set(["stroke", "note", "arrow", "image", "html", "text"]);
73
73
  function validateElement(el) {
74
74
  if (!el || typeof el !== "object") {
75
75
  throw new Error("Invalid element: expected an object");
@@ -752,7 +752,7 @@ function smoothToSegments(points) {
752
752
  }
753
753
 
754
754
  // src/elements/element-renderer.ts
755
- var DOM_ELEMENT_TYPES = /* @__PURE__ */ new Set(["note", "image", "html"]);
755
+ var DOM_ELEMENT_TYPES = /* @__PURE__ */ new Set(["note", "image", "html", "text"]);
756
756
  var ARROWHEAD_LENGTH = 12;
757
757
  var ARROWHEAD_ANGLE = Math.PI / 6;
758
758
  var ElementRenderer = class {
@@ -831,12 +831,16 @@ var NoteEditor = class {
831
831
  keyHandler = null;
832
832
  pointerHandler = null;
833
833
  pendingEditId = null;
834
+ onStopCallback = null;
834
835
  get isEditing() {
835
836
  return this.editingId !== null;
836
837
  }
837
838
  get editingElementId() {
838
839
  return this.editingId;
839
840
  }
841
+ setOnStop(callback) {
842
+ this.onStopCallback = callback;
843
+ }
840
844
  startEditing(node, elementId, store) {
841
845
  if (this.editingId === elementId) return;
842
846
  if (this.editingId) {
@@ -868,6 +872,9 @@ var NoteEditor = class {
868
872
  if (this.pointerHandler) {
869
873
  this.editingNode.removeEventListener("pointerdown", this.pointerHandler);
870
874
  }
875
+ if (this.editingId && this.onStopCallback) {
876
+ this.onStopCallback(this.editingId);
877
+ }
871
878
  this.editingId = null;
872
879
  this.editingNode = null;
873
880
  this.blurHandler = null;
@@ -1218,6 +1225,20 @@ function createHtmlElement(input) {
1218
1225
  size: input.size
1219
1226
  };
1220
1227
  }
1228
+ function createText(input) {
1229
+ return {
1230
+ id: createId("text"),
1231
+ type: "text",
1232
+ position: input.position,
1233
+ zIndex: input.zIndex ?? 0,
1234
+ locked: input.locked ?? false,
1235
+ size: input.size ?? { w: 200, h: 28 },
1236
+ text: input.text ?? "",
1237
+ fontSize: input.fontSize ?? 16,
1238
+ color: input.color ?? "#1a1a1a",
1239
+ textAlign: input.textAlign ?? "left"
1240
+ };
1241
+ }
1221
1242
 
1222
1243
  // src/canvas/viewport.ts
1223
1244
  var Viewport = class {
@@ -1229,6 +1250,7 @@ var Viewport = class {
1229
1250
  this.toolManager = new ToolManager();
1230
1251
  this.renderer = new ElementRenderer();
1231
1252
  this.noteEditor = new NoteEditor();
1253
+ this.noteEditor.setOnStop((id) => this.onTextEditStop(id));
1232
1254
  this.history = new HistoryStack();
1233
1255
  this.historyRecorder = new HistoryRecorder(this.store, this.history);
1234
1256
  this.wrapper = this.createWrapper();
@@ -1242,7 +1264,7 @@ var Viewport = class {
1242
1264
  store: this.store,
1243
1265
  requestRender: () => this.requestRender(),
1244
1266
  switchTool: (name) => this.toolManager.setTool(name, this.toolContext),
1245
- editElement: (id) => this.startEditingNote(id),
1267
+ editElement: (id) => this.startEditingElement(id),
1246
1268
  setCursor: (cursor) => {
1247
1269
  this.wrapper.style.cursor = cursor;
1248
1270
  }
@@ -1396,15 +1418,34 @@ var Viewport = class {
1396
1418
  ctx.restore();
1397
1419
  ctx.restore();
1398
1420
  }
1399
- startEditingNote(id) {
1421
+ startEditingElement(id) {
1400
1422
  const element = this.store.getById(id);
1401
- if (!element || element.type !== "note") return;
1423
+ if (!element || element.type !== "note" && element.type !== "text") return;
1402
1424
  this.render();
1403
1425
  const node = this.domNodes.get(id);
1404
1426
  if (node) {
1405
1427
  this.noteEditor.startEditing(node, id, this.store);
1406
1428
  }
1407
1429
  }
1430
+ onTextEditStop(elementId) {
1431
+ const element = this.store.getById(elementId);
1432
+ if (!element || element.type !== "text") return;
1433
+ if (!element.text || element.text.trim() === "") {
1434
+ this.historyRecorder.begin();
1435
+ this.store.remove(elementId);
1436
+ this.historyRecorder.commit();
1437
+ return;
1438
+ }
1439
+ const node = this.domNodes.get(elementId);
1440
+ if (node && "size" in element) {
1441
+ const measuredHeight = node.scrollHeight;
1442
+ if (measuredHeight !== element.size.h) {
1443
+ this.store.update(elementId, {
1444
+ size: { w: element.size.w, h: measuredHeight }
1445
+ });
1446
+ }
1447
+ }
1448
+ }
1408
1449
  onDblClick = (e) => {
1409
1450
  const el = document.elementFromPoint(e.clientX, e.clientY);
1410
1451
  const nodeEl = el?.closest("[data-element-id]");
@@ -1412,8 +1453,8 @@ var Viewport = class {
1412
1453
  const elementId = nodeEl.dataset["elementId"];
1413
1454
  if (elementId) {
1414
1455
  const element = this.store.getById(elementId);
1415
- if (element?.type === "note") {
1416
- this.startEditingNote(elementId);
1456
+ if (element?.type === "note" || element?.type === "text") {
1457
+ this.startEditingElement(elementId);
1417
1458
  return;
1418
1459
  }
1419
1460
  }
@@ -1537,7 +1578,7 @@ var Viewport = class {
1537
1578
  node.addEventListener("dblclick", (e) => {
1538
1579
  e.stopPropagation();
1539
1580
  const id = node.dataset["elementId"];
1540
- if (id) this.startEditingNote(id);
1581
+ if (id) this.startEditingElement(id);
1541
1582
  });
1542
1583
  }
1543
1584
  if (!this.noteEditor.isEditing || this.noteEditor.editingElementId !== element.id) {
@@ -1578,6 +1619,42 @@ var Viewport = class {
1578
1619
  node.appendChild(content);
1579
1620
  }
1580
1621
  }
1622
+ if (element.type === "text") {
1623
+ if (!node.dataset["initialized"]) {
1624
+ node.dataset["initialized"] = "true";
1625
+ Object.assign(node.style, {
1626
+ padding: "2px",
1627
+ fontSize: `${element.fontSize}px`,
1628
+ color: element.color,
1629
+ textAlign: element.textAlign,
1630
+ background: "none",
1631
+ border: "none",
1632
+ boxShadow: "none",
1633
+ overflow: "visible",
1634
+ cursor: "default",
1635
+ userSelect: "none",
1636
+ wordWrap: "break-word",
1637
+ whiteSpace: "pre-wrap",
1638
+ lineHeight: "1.4"
1639
+ });
1640
+ node.textContent = element.text || "";
1641
+ node.addEventListener("dblclick", (e) => {
1642
+ e.stopPropagation();
1643
+ const id = node.dataset["elementId"];
1644
+ if (id) this.startEditingElement(id);
1645
+ });
1646
+ }
1647
+ if (!this.noteEditor.isEditing || this.noteEditor.editingElementId !== element.id) {
1648
+ if (node.textContent !== element.text) {
1649
+ node.textContent = element.text || "";
1650
+ }
1651
+ Object.assign(node.style, {
1652
+ fontSize: `${element.fontSize}px`,
1653
+ color: element.color,
1654
+ textAlign: element.textAlign
1655
+ });
1656
+ }
1657
+ }
1581
1658
  }
1582
1659
  removeDomNode(id) {
1583
1660
  this.htmlContent.delete(id);
@@ -2331,6 +2408,47 @@ var NoteTool = class {
2331
2408
  }
2332
2409
  };
2333
2410
 
2411
+ // src/tools/text-tool.ts
2412
+ var TextTool = class {
2413
+ name = "text";
2414
+ fontSize;
2415
+ color;
2416
+ textAlign;
2417
+ constructor(options = {}) {
2418
+ this.fontSize = options.fontSize ?? 16;
2419
+ this.color = options.color ?? "#1a1a1a";
2420
+ this.textAlign = options.textAlign ?? "left";
2421
+ }
2422
+ setOptions(options) {
2423
+ if (options.fontSize !== void 0) this.fontSize = options.fontSize;
2424
+ if (options.color !== void 0) this.color = options.color;
2425
+ if (options.textAlign !== void 0) this.textAlign = options.textAlign;
2426
+ }
2427
+ onActivate(ctx) {
2428
+ ctx.setCursor?.("text");
2429
+ }
2430
+ onDeactivate(ctx) {
2431
+ ctx.setCursor?.("default");
2432
+ }
2433
+ onPointerDown(_state, _ctx) {
2434
+ }
2435
+ onPointerMove(_state, _ctx) {
2436
+ }
2437
+ onPointerUp(state, ctx) {
2438
+ const world = ctx.camera.screenToWorld({ x: state.x, y: state.y });
2439
+ const textEl = createText({
2440
+ position: world,
2441
+ fontSize: this.fontSize,
2442
+ color: this.color,
2443
+ textAlign: this.textAlign
2444
+ });
2445
+ ctx.store.add(textEl);
2446
+ ctx.requestRender();
2447
+ ctx.switchTool?.("select");
2448
+ ctx.editElement?.(textEl.id);
2449
+ }
2450
+ };
2451
+
2334
2452
  // src/tools/image-tool.ts
2335
2453
  var ImageTool = class {
2336
2454
  name = "image";
@@ -2362,7 +2480,7 @@ var ImageTool = class {
2362
2480
  };
2363
2481
 
2364
2482
  // src/index.ts
2365
- var VERSION = "0.2.2";
2483
+ var VERSION = "0.3.0";
2366
2484
  export {
2367
2485
  AddElementCommand,
2368
2486
  ArrowTool,
@@ -2384,6 +2502,7 @@ export {
2384
2502
  PencilTool,
2385
2503
  RemoveElementCommand,
2386
2504
  SelectTool,
2505
+ TextTool,
2387
2506
  ToolManager,
2388
2507
  UpdateElementCommand,
2389
2508
  VERSION,
@@ -2394,6 +2513,7 @@ export {
2394
2513
  createImage,
2395
2514
  createNote,
2396
2515
  createStroke,
2516
+ createText,
2397
2517
  exportState,
2398
2518
  getArrowBounds,
2399
2519
  getArrowControlPoint,