@underverse-ui/underverse 1.0.204 → 1.0.205

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.js CHANGED
@@ -4,19 +4,19 @@ import {
4
4
  extractImageSrcsFromHtml,
5
5
  normalizeImageUrl,
6
6
  prepareUEditorContentForSave
7
- } from "./chunk-CC3QO2YM.js";
7
+ } from "./chunk-2V6A6PGI.js";
8
8
  import {
9
9
  EmojiPicker_default
10
- } from "./chunk-CKKLFVOY.js";
10
+ } from "./chunk-Z63YNF3N.js";
11
11
  import {
12
12
  getEmojiImageUrl,
13
13
  getEmojiUnifiedCode,
14
14
  setEmojiBaseUrl
15
- } from "./chunk-ZUAIBY2Z.js";
15
+ } from "./chunk-IXEFOLUD.js";
16
16
  import "./chunk-4TYW5K4J.js";
17
17
  import {
18
18
  Modal_default
19
- } from "./chunk-QVQTWZ24.js";
19
+ } from "./chunk-7U3Y7R7U.js";
20
20
  import {
21
21
  DropdownMenuItem,
22
22
  DropdownMenuSeparator,
@@ -34,7 +34,7 @@ import {
34
34
  shadcnAnimationStyles,
35
35
  useShadCNAnimations,
36
36
  useUnderverseUIConfig
37
- } from "./chunk-KFTYWTJR.js";
37
+ } from "./chunk-MJV7ETJM.js";
38
38
  import {
39
39
  ForceInternalTranslationsProvider,
40
40
  NextIntlAdapter,
@@ -54,7 +54,7 @@ import {
54
54
  useUnderverseLocale,
55
55
  useUnderverseTranslations,
56
56
  vi_default
57
- } from "./chunk-GSBRTFP2.js";
57
+ } from "./chunk-XJR7E6QB.js";
58
58
  import {
59
59
  __export,
60
60
  __require
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
  import {
3
3
  Modal_default
4
- } from "./chunk-QVQTWZ24.js";
4
+ } from "./chunk-7U3Y7R7U.js";
5
5
  import {
6
6
  DEFAULT_TABLE_ROW_HEIGHT,
7
7
  DEFAULT_UEDITOR_IMAGE_MAX_FILE_SIZE,
@@ -21,11 +21,11 @@ import {
21
21
  sanitizeUEditorUrl,
22
22
  useDropdownMenuClose,
23
23
  useSharedEditorUiRenderState
24
- } from "./chunk-KFTYWTJR.js";
24
+ } from "./chunk-MJV7ETJM.js";
25
25
  import {
26
26
  cn,
27
27
  useSmartTranslations
28
- } from "./chunk-GSBRTFP2.js";
28
+ } from "./chunk-XJR7E6QB.js";
29
29
  import "./chunk-NSBPE2FW.js";
30
30
 
31
31
  // src/components/UEditor/menu-bar.tsx
@@ -944,4 +944,4 @@ var MenuBar = ({
944
944
  export {
945
945
  MenuBar
946
946
  };
947
- //# sourceMappingURL=menu-bar-6U5S4PS7.js.map
947
+ //# sourceMappingURL=menu-bar-NDAP3SOR.js.map
package/dist/ueditor.cjs CHANGED
@@ -1911,12 +1911,14 @@ var init_TranslationContext = __esm({
1911
1911
  TranslationContext = React2.createContext(null);
1912
1912
  useUnderverseTranslations = (namespace) => {
1913
1913
  const context = React2.useContext(TranslationContext);
1914
- if (!context) {
1915
- return (key) => {
1916
- return getUnderverseDefaultTranslation("en", namespace, key);
1917
- };
1918
- }
1919
- return context.t(namespace);
1914
+ return React2.useMemo(() => {
1915
+ if (!context) {
1916
+ return (key) => {
1917
+ return getUnderverseDefaultTranslation("en", namespace, key);
1918
+ };
1919
+ }
1920
+ return context.t(namespace);
1921
+ }, [context, namespace]);
1920
1922
  };
1921
1923
  useUnderverseLocale = () => {
1922
1924
  const context = React2.useContext(TranslationContext);
@@ -1982,10 +1984,10 @@ function useSmartTranslations(namespace) {
1982
1984
  setEnvironmentLocale(detected);
1983
1985
  }
1984
1986
  }, [forceInternal, internalLocale, nextIntlBridge]);
1985
- if (forceInternal) {
1986
- return internalT;
1987
- }
1988
- return (key) => {
1987
+ return React4.useCallback((key) => {
1988
+ if (forceInternal) {
1989
+ return internalT(key);
1990
+ }
1989
1991
  const resolvedEnvironmentLocale = environmentLocale && environmentLocale !== internalLocale ? environmentLocale : null;
1990
1992
  const primaryLocale = nextIntlBridge?.locale ?? resolvedEnvironmentLocale ?? internalLocale;
1991
1993
  const fallbackLocale = resolvedEnvironmentLocale && resolvedEnvironmentLocale !== primaryLocale ? resolvedEnvironmentLocale : null;
@@ -2014,7 +2016,7 @@ function useSmartTranslations(namespace) {
2014
2016
  return internalValue;
2015
2017
  }
2016
2018
  return key;
2017
- };
2019
+ }, [environmentLocale, forceInternal, internalLocale, internalT, namespace, nextIntlBridge]);
2018
2020
  }
2019
2021
  var React4, import_jsx_runtime4, ForceInternalContext;
2020
2022
  var init_useSmartTranslations = __esm({
@@ -4993,6 +4995,25 @@ function resolveEventElement(target) {
4993
4995
  if (target instanceof Node) return target.parentElement;
4994
4996
  return null;
4995
4997
  }
4998
+ function isPointOverRenderedText(root, clientX, clientY) {
4999
+ const view = root.ownerDocument.defaultView;
5000
+ if (!view) return false;
5001
+ const walker = root.ownerDocument.createTreeWalker(root, view.NodeFilter.SHOW_TEXT);
5002
+ let textNode = walker.nextNode();
5003
+ while (textNode) {
5004
+ if (textNode.textContent?.length) {
5005
+ const range = root.ownerDocument.createRange();
5006
+ range.selectNodeContents(textNode);
5007
+ const textRects = Array.from(range.getClientRects());
5008
+ range.detach?.();
5009
+ if (textRects.some((rect) => clientX >= rect.left && clientX <= rect.right && clientY >= rect.top && clientY <= rect.bottom)) {
5010
+ return true;
5011
+ }
5012
+ }
5013
+ textNode = walker.nextNode();
5014
+ }
5015
+ return false;
5016
+ }
4996
5017
  function getSelectionTableCell(view) {
4997
5018
  const browserSelection = window.getSelection();
4998
5019
  const anchorElement = resolveEventElement(browserSelection?.anchorNode ?? null);
@@ -7925,12 +7946,12 @@ var init_editor_styles = __esm({
7925
7946
  });
7926
7947
 
7927
7948
  // src/components/Modal.tsx
7928
- var React28, import_react_dom4, import_lucide_react20, import_jsx_runtime35, sizeStyles, Modal, Modal_default;
7949
+ var React29, import_react_dom4, import_lucide_react20, import_jsx_runtime35, sizeStyles, Modal, Modal_default;
7929
7950
  var init_Modal = __esm({
7930
7951
  "src/components/Modal.tsx"() {
7931
7952
  "use strict";
7932
7953
  "use client";
7933
- React28 = __toESM(require("react"), 1);
7954
+ React29 = __toESM(require("react"), 1);
7934
7955
  import_react_dom4 = require("react-dom");
7935
7956
  init_cn();
7936
7957
  import_lucide_react20 = require("lucide-react");
@@ -7967,17 +7988,17 @@ var init_Modal = __esm({
7967
7988
  const gi18n = useGlobalI18n();
7968
7989
  const globalConfig = useUnderverseUIConfig();
7969
7990
  const resolvedBorderMode = borderMode ?? globalConfig.borderMode;
7970
- const [isMounted, setIsMounted] = React28.useState(false);
7971
- const [isVisible, setIsVisible] = React28.useState(false);
7972
- const [isAnimating, setIsAnimating] = React28.useState(true);
7973
- const mouseDownTarget = React28.useRef(null);
7974
- const modalContentRef = React28.useRef(null);
7975
- React28.useEffect(() => {
7991
+ const [isMounted, setIsMounted] = React29.useState(false);
7992
+ const [isVisible, setIsVisible] = React29.useState(false);
7993
+ const [isAnimating, setIsAnimating] = React29.useState(true);
7994
+ const mouseDownTarget = React29.useRef(null);
7995
+ const modalContentRef = React29.useRef(null);
7996
+ React29.useEffect(() => {
7976
7997
  setIsMounted(true);
7977
7998
  return () => setIsMounted(false);
7978
7999
  }, []);
7979
- const animationRef = React28.useRef(false);
7980
- React28.useEffect(() => {
8000
+ const animationRef = React29.useRef(false);
8001
+ React29.useEffect(() => {
7981
8002
  if (isOpen) {
7982
8003
  if (animationRef.current) return;
7983
8004
  animationRef.current = true;
@@ -7997,7 +8018,7 @@ var init_Modal = __esm({
7997
8018
  }
7998
8019
  }
7999
8020
  }, [isOpen, isVisible]);
8000
- React28.useEffect(() => {
8021
+ React29.useEffect(() => {
8001
8022
  if (!isOpen || !closeOnEsc) return;
8002
8023
  const handleEscape = (event) => {
8003
8024
  if (event.key === "Escape") {
@@ -8007,7 +8028,7 @@ var init_Modal = __esm({
8007
8028
  document.addEventListener("keydown", handleEscape);
8008
8029
  return () => document.removeEventListener("keydown", handleEscape);
8009
8030
  }, [isOpen, closeOnEsc, onClose]);
8010
- React28.useEffect(() => {
8031
+ React29.useEffect(() => {
8011
8032
  if (isOpen) {
8012
8033
  document.body.style.overflow = "hidden";
8013
8034
  } else {
@@ -20432,6 +20453,22 @@ function buildLogicalRowMetrics({
20432
20453
  };
20433
20454
  });
20434
20455
  }
20456
+ function getTableCellTextSelectionRange(editor, cellPos) {
20457
+ const cellNode = editor.state.doc.nodeAt(cellPos);
20458
+ if (!cellNode || cellNode.type.name !== "tableCell" && cellNode.type.name !== "tableHeader") {
20459
+ return null;
20460
+ }
20461
+ let from = null;
20462
+ let to = null;
20463
+ cellNode.descendants((node, relativePos) => {
20464
+ if (!node.isText || node.nodeSize === 0) return true;
20465
+ const textStart = cellPos + 1 + relativePos;
20466
+ from ?? (from = textStart);
20467
+ to = textStart + node.nodeSize;
20468
+ return true;
20469
+ });
20470
+ return from !== null && to !== null && from < to ? { from, to } : null;
20471
+ }
20435
20472
  function buildTableControlLayout(editor, surface, cell) {
20436
20473
  const row = cell.closest("tr");
20437
20474
  const table = cell.closest("table");
@@ -20537,6 +20574,9 @@ function buildTableHoverState({
20537
20574
  layout,
20538
20575
  surface
20539
20576
  }) {
20577
+ if (event.buttons !== 0) {
20578
+ return DEFAULT_TABLE_HOVER_STATE;
20579
+ }
20540
20580
  const surfaceRect = surface.getBoundingClientRect();
20541
20581
  const relativeX = event.clientX - surfaceRect.left + surface.scrollLeft;
20542
20582
  const relativeY = event.clientY - surfaceRect.top + surface.scrollTop;
@@ -20546,8 +20586,8 @@ function buildTableHoverState({
20546
20586
  const directTableMenu = targetElement?.closest?.("[data-table-control='table-menu']");
20547
20587
  const directAddColumn = targetElement?.closest?.("[data-table-control='add-column']");
20548
20588
  const directAddRow = targetElement?.closest?.("[data-table-control='add-row']");
20549
- const directRowHandleIndex = directRowHandle instanceof HTMLElement ? Number.parseInt(directRowHandle.dataset.rowHandleIndex ?? "", 10) : Number.NaN;
20550
- const directColumnHandleIndex = directColumnHandle instanceof HTMLElement ? Number.parseInt(directColumnHandle.dataset.columnHandleIndex ?? "", 10) : Number.NaN;
20589
+ const directRowHandleIndex = directRowHandle instanceof HTMLElement && relativeX <= layout.tableLeft ? Number.parseInt(directRowHandle.dataset.rowHandleIndex ?? "", 10) : Number.NaN;
20590
+ const directColumnHandleIndex = directColumnHandle instanceof HTMLElement && relativeY <= layout.tableTop ? Number.parseInt(directColumnHandle.dataset.columnHandleIndex ?? "", 10) : Number.NaN;
20551
20591
  const visibleBounds = getVisibleTableBounds(layout);
20552
20592
  const rowRailTop = layout.wrapperTop + layout.wrapperHeight;
20553
20593
  const isMouseInTable = relativeX >= layout.tableLeft && relativeX <= layout.tableLeft + layout.tableWidth && relativeY >= layout.tableTop && relativeY <= layout.tableTop + layout.tableHeight;
@@ -20866,7 +20906,8 @@ function TableRowHandles({
20866
20906
  "data-row-handle-index": rowHandle.index,
20867
20907
  style: {
20868
20908
  top: Math.max(8, rowHandle.center - 12),
20869
- left: rowHandleLeft
20909
+ left: rowHandleLeft,
20910
+ pointerEvents: visible ? "auto" : "none"
20870
20911
  },
20871
20912
  children: /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
20872
20913
  Tooltip,
@@ -20899,7 +20940,10 @@ function TableRowHandles({
20899
20940
  style: {
20900
20941
  opacity: isShown ? 1 : 0,
20901
20942
  transform: isShown ? "scale(1)" : `scale(${IDLE_HANDLE_SCALE})`,
20902
- pointerEvents: isShown ? "auto" : "none"
20943
+ // The idle marker is only a location hint. Keeping it
20944
+ // pointer-transparent prevents it from stealing a text
20945
+ // selection near the first character of a table cell.
20946
+ pointerEvents: visible ? "auto" : "none"
20903
20947
  },
20904
20948
  children: visible ? /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(import_lucide_react16.GripVertical, { className: "h-3.5 w-3.5" }) : /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("div", { className: "h-3 w-1 rounded-full bg-muted-foreground/50 hover:bg-muted-foreground" })
20905
20949
  }
@@ -20937,7 +20981,8 @@ function TableColumnHandles({
20937
20981
  "data-column-handle-index": columnHandle.index,
20938
20982
  style: {
20939
20983
  top: columnHandleTop,
20940
- left: Math.max(8, columnHandle.center - 12)
20984
+ left: Math.max(8, columnHandle.center - 12),
20985
+ pointerEvents: visible ? "auto" : "none"
20941
20986
  },
20942
20987
  children: /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
20943
20988
  Tooltip,
@@ -20970,7 +21015,7 @@ function TableColumnHandles({
20970
21015
  style: {
20971
21016
  opacity: isShown ? 1 : 0,
20972
21017
  transform: isShown ? "scale(1)" : `scale(${IDLE_HANDLE_SCALE})`,
20973
- pointerEvents: isShown ? "auto" : "none"
21018
+ pointerEvents: visible ? "auto" : "none"
20974
21019
  },
20975
21020
  children: visible ? /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(import_lucide_react16.GripHorizontal, { className: "h-3.5 w-3.5" }) : /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("div", { className: "h-1 w-3 rounded-full bg-muted-foreground/50 hover:bg-muted-foreground" })
20976
21021
  }
@@ -21218,6 +21263,7 @@ function applyTableSize(editor, snapshot, dimensions) {
21218
21263
  // src/components/UEditor/table-controls.tsx
21219
21264
  var import_jsx_runtime33 = require("react/jsx-runtime");
21220
21265
  var TABLE_MENU_TOP_OFFSET = 10;
21266
+ var AXIS_HANDLE_RADIUS = 12;
21221
21267
  function nearestIndex(centers, position) {
21222
21268
  let bestIndex = 0;
21223
21269
  let bestDistance = Number.POSITIVE_INFINITY;
@@ -21395,6 +21441,20 @@ function TableControls({ editor, containerRef, showCellInspector = true }) {
21395
21441
  const surface2 = containerRef.current;
21396
21442
  const nextLayout = surface2 ? buildTableControlLayout(editor, surface2, cell) : null;
21397
21443
  if (!nextLayout) return;
21444
+ if (isPointOverRenderedText(cell, event.clientX, event.clientY)) {
21445
+ const textSelection = getTableCellTextSelectionRange(editor, nextLayout.cellPos);
21446
+ if (textSelection) {
21447
+ event.preventDefault();
21448
+ const didSelectText = editor.commands.setTextSelection(textSelection);
21449
+ if (didSelectText) {
21450
+ editor.view.focus();
21451
+ layoutRef.current = nextLayout;
21452
+ setLayout(nextLayout);
21453
+ setHoverState(DEFAULT_TABLE_HOVER_STATE);
21454
+ return;
21455
+ }
21456
+ }
21457
+ }
21398
21458
  const didSelect = editor.commands.setCellSelection({
21399
21459
  anchorCell: nextLayout.cellPos,
21400
21460
  headCell: nextLayout.cellPos
@@ -21861,8 +21921,8 @@ function TableControls({ editor, containerRef, showCellInspector = true }) {
21861
21921
  }
21862
21922
  const menuTop = Math.max(8, layout.tableTop - TABLE_MENU_TOP_OFFSET);
21863
21923
  const menuLeft = Math.max(8, layout.tableLeft);
21864
- const rowHandleLeft = layout.tableLeft - 12;
21865
- const columnHandleTop = layout.tableTop - 12;
21924
+ const rowHandleLeft = layout.tableLeft - AXIS_HANDLE_RADIUS;
21925
+ const columnHandleTop = layout.tableTop - AXIS_HANDLE_RADIUS;
21866
21926
  return /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)(import_jsx_runtime33.Fragment, { children: [
21867
21927
  /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
21868
21928
  TableRowHandles,
@@ -23049,6 +23109,73 @@ function TableFormulaBar({ editor }) {
23049
23109
  );
23050
23110
  }
23051
23111
 
23112
+ // src/components/UEditor/use-editor-output.ts
23113
+ var React28 = __toESM(require("react"), 1);
23114
+ function normalizeDebounceMs(value) {
23115
+ return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : 0;
23116
+ }
23117
+ function useUEditorOutput({
23118
+ onChange,
23119
+ onHtmlChange,
23120
+ onJsonChange,
23121
+ outputDebounceMs
23122
+ }) {
23123
+ const callbacksRef = React28.useRef({});
23124
+ const pendingEditorRef = React28.useRef(null);
23125
+ const timeoutRef = React28.useRef(null);
23126
+ const debounceMs = normalizeDebounceMs(outputDebounceMs);
23127
+ callbacksRef.current = { onChange, onHtmlChange, onJsonChange };
23128
+ const flushOutputUpdates = React28.useCallback(() => {
23129
+ if (timeoutRef.current !== null) {
23130
+ clearTimeout(timeoutRef.current);
23131
+ timeoutRef.current = null;
23132
+ }
23133
+ const editor = pendingEditorRef.current;
23134
+ pendingEditorRef.current = null;
23135
+ if (!editor || editor.isDestroyed) return;
23136
+ const callbacks = callbacksRef.current;
23137
+ if (callbacks.onChange || callbacks.onHtmlChange) {
23138
+ const html = editor.getHTML();
23139
+ callbacks.onChange?.(html);
23140
+ callbacks.onHtmlChange?.(html);
23141
+ }
23142
+ if (callbacks.onJsonChange) {
23143
+ callbacks.onJsonChange(editor.getJSON());
23144
+ }
23145
+ }, []);
23146
+ const scheduleOutputUpdate = React28.useCallback((editor) => {
23147
+ const callbacks = callbacksRef.current;
23148
+ if (!callbacks.onChange && !callbacks.onHtmlChange && !callbacks.onJsonChange) return;
23149
+ pendingEditorRef.current = editor;
23150
+ if (timeoutRef.current !== null) {
23151
+ clearTimeout(timeoutRef.current);
23152
+ timeoutRef.current = null;
23153
+ }
23154
+ if (debounceMs === 0) {
23155
+ flushOutputUpdates();
23156
+ return;
23157
+ }
23158
+ timeoutRef.current = setTimeout(flushOutputUpdates, debounceMs);
23159
+ }, [debounceMs, flushOutputUpdates]);
23160
+ React28.useEffect(() => {
23161
+ if (!pendingEditorRef.current || timeoutRef.current === null) return;
23162
+ clearTimeout(timeoutRef.current);
23163
+ timeoutRef.current = debounceMs === 0 ? null : setTimeout(flushOutputUpdates, debounceMs);
23164
+ if (debounceMs === 0) flushOutputUpdates();
23165
+ }, [debounceMs, flushOutputUpdates]);
23166
+ React28.useEffect(() => () => {
23167
+ if (timeoutRef.current !== null) {
23168
+ clearTimeout(timeoutRef.current);
23169
+ timeoutRef.current = null;
23170
+ }
23171
+ pendingEditorRef.current = null;
23172
+ }, []);
23173
+ return {
23174
+ flushOutputUpdates,
23175
+ scheduleOutputUpdate
23176
+ };
23177
+ }
23178
+
23052
23179
  // src/components/UEditor/UEditor.tsx
23053
23180
  var import_jsx_runtime37 = require("react/jsx-runtime");
23054
23181
  var LazyMenuBar = import_react44.default.lazy(async () => {
@@ -23060,6 +23187,7 @@ var UEditor = import_react44.default.forwardRef(({
23060
23187
  onChange,
23061
23188
  onHtmlChange,
23062
23189
  onJsonChange,
23190
+ outputDebounceMs = 0,
23063
23191
  uploadImage,
23064
23192
  uploadImageForSave,
23065
23193
  uploadImageConcurrency = 3,
@@ -23105,6 +23233,12 @@ var UEditor = import_react44.default.forwardRef(({
23105
23233
  const formulaRangePickRef = (0, import_react44.useRef)(null);
23106
23234
  const formulaRangeSurfaceRef = (0, import_react44.useRef)(null);
23107
23235
  const [formulaRangeHighlight, setFormulaRangeHighlight] = import_react44.default.useState(null);
23236
+ const { flushOutputUpdates, scheduleOutputUpdate } = useUEditorOutput({
23237
+ onChange,
23238
+ onHtmlChange,
23239
+ onJsonChange,
23240
+ outputDebounceMs
23241
+ });
23108
23242
  const scheduleFormulaRecalculate = import_react44.default.useCallback((editor2, options) => {
23109
23243
  if (editor2.isDestroyed || scheduledFormulaRecalculateRef.current) return;
23110
23244
  if (!options?.force && isEditingTableFormulaText(editor2)) return;
@@ -23246,14 +23380,7 @@ var UEditor = import_react44.default.forwardRef(({
23246
23380
  pendingFormulaTextRecalculateRef.current = false;
23247
23381
  }
23248
23382
  }
23249
- if (onChange || onHtmlChange) {
23250
- const html = editor2.getHTML();
23251
- onChange?.(html);
23252
- onHtmlChange?.(html);
23253
- }
23254
- if (onJsonChange) {
23255
- onJsonChange(editor2.getJSON());
23256
- }
23383
+ scheduleOutputUpdate(editor2);
23257
23384
  },
23258
23385
  onSelectionUpdate: ({ editor: editor2 }) => {
23259
23386
  if (pendingFormulaTextRecalculateRef.current && !isEditingTableFormulaText(editor2)) {
@@ -23267,6 +23394,7 @@ var UEditor = import_react44.default.forwardRef(({
23267
23394
  const shouldRecalculate = pendingFormulaTextRecalculateRef.current;
23268
23395
  pendingFormulaTextRecalculateRef.current = false;
23269
23396
  if (shouldRecalculate) scheduleFormulaRecalculate(editor2, { force: true });
23397
+ queueMicrotask(flushOutputUpdates);
23270
23398
  }
23271
23399
  });
23272
23400
  (0, import_react44.useEffect)(() => {