@lazlon-platform/html-editor 0.6.0 → 0.7.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.
Files changed (34) hide show
  1. package/lib/hooks/actions.ts +89 -67
  2. package/lib/hooks/batch.ts +9 -5
  3. package/lib/hooks/index.ts +7 -7
  4. package/lib/hooks/page.ts +2 -4
  5. package/lib/hooks/pointer/useMovePoint.ts +100 -0
  6. package/lib/hooks/pointer/{moveable.ts → useMoveable.ts} +30 -36
  7. package/lib/hooks/pointer/{pointer.ts → usePointer.ts} +2 -2
  8. package/lib/hooks/pointer/useResize/index.ts +31 -0
  9. package/lib/hooks/pointer/useResize/multi.ts +161 -0
  10. package/lib/hooks/pointer/useResize/multiLineNode.ts +99 -0
  11. package/lib/hooks/pointer/useResize/multiRegularNode.ts +109 -0
  12. package/lib/hooks/pointer/useResize/multiTextNode.ts +108 -0
  13. package/lib/hooks/pointer/useResize/singleRegularNode.ts +91 -0
  14. package/lib/hooks/pointer/useResize/singleTextNode.ts +115 -0
  15. package/lib/hooks/pointer/useRotation.ts +102 -0
  16. package/lib/hooks/pointer/{selector.ts → useSelector.ts} +2 -1
  17. package/lib/hooks/pointer/{snap.ts → useSnap.ts} +3 -2
  18. package/lib/hooks/{pointer/selectionFrame.ts → selectionFrame.ts} +9 -6
  19. package/lib/hooks/textMarks.ts +30 -19
  20. package/lib/model/editor.ts +18 -4
  21. package/lib/model/geometry/math.ts +8 -20
  22. package/lib/model/index.ts +9 -1
  23. package/lib/model/node/imageNode.ts +1 -1
  24. package/lib/model/node/lineNode.ts +41 -20
  25. package/lib/model/node/textNode.ts +5 -14
  26. package/lib/model/node.ts +18 -9
  27. package/lib/model/page.ts +3 -2
  28. package/lib/ui/node/EditableContent/index.tsx +6 -4
  29. package/lib/ui/node/LineContent.tsx +1 -3
  30. package/lib/ui/selection.ts +6 -5
  31. package/package.json +1 -1
  32. package/lib/hooks/pointer/movePoint.ts +0 -75
  33. package/lib/hooks/pointer/resize.ts +0 -247
  34. package/lib/hooks/pointer/rotation.ts +0 -138
@@ -4,7 +4,6 @@ import {
4
4
  GroupNode,
5
5
  type HistoryAction,
6
6
  type Node,
7
- type NodeProps,
8
7
  type Page,
9
8
  } from "../model"
10
9
  import type { Editor, NodeConstructor } from "../model/editor"
@@ -19,10 +18,17 @@ import { selectionBox } from "../ui/selection"
19
18
  import { useBatchSet } from "./batch"
20
19
  import { useEditor } from "./editor"
21
20
 
22
- export function clone(
21
+ function getSelection(editor: Editor) {
22
+ return editor.selection
23
+ .values()
24
+ .toArray()
25
+ .filter((n) => !n.locked)
26
+ }
27
+
28
+ export function clone<N extends Node>(
23
29
  editor: Editor,
24
- node: Node,
25
- props?: ((props: NodeProps) => Partial<NodeProps>) | Partial<NodeProps>,
30
+ node: N,
31
+ props?: ((props: N["props"]) => Partial<N["props"]>) | Partial<N["props"]>,
26
32
  ) {
27
33
  const copy = node.props
28
34
  const newProps = typeof props === "function" ? props(copy) : props
@@ -48,7 +54,8 @@ export function useAddNodeAction(page?: Page) {
48
54
  ) {
49
55
  const [firstPage] = editor.pages.values()
50
56
  const targetPage = page ?? firstPage
51
- if (!targetPage) return
57
+ if (!targetPage) throw Error("missing Page")
58
+
52
59
  const { width, height, nodes } = targetPage
53
60
 
54
61
  const node = new NodeClass(targetPage, {
@@ -76,7 +83,7 @@ export function useMoveAction() {
76
83
 
77
84
  return function move(props: { x?: number; y?: number }) {
78
85
  const { x, y } = props
79
- const selection = editor.selection.values().toArray()
86
+ const selection = getSelection(editor)
80
87
  if (x) batchSet(selection, (n) => ({ x: n.x + x }))
81
88
  if (y) batchSet(selection, (n) => ({ y: n.y + y }))
82
89
  }
@@ -86,17 +93,18 @@ export function useTrashAction() {
86
93
  const editor = useEditor()
87
94
 
88
95
  return function trash() {
89
- const { selection, selectionPage: page } = editor
90
- const array = selection.values().toArray()
91
- if (selection.size === 0 || !page) return
96
+ const page = editor.selectionPage
97
+ const selection = getSelection(editor)
98
+ if (selection.length === 0 || !page) return
92
99
 
93
100
  editor.history.push({
94
- redo: ["delete-node", [page.id, array.map((node) => node.id)]],
95
- undo: ["add-node", [page.id, array.map((n) => editor.serializeNode(n))]],
101
+ redo: ["delete-node", [page.id, selection.map((node) => node.id)]],
102
+ undo: ["add-node", [page.id, selection.map((n) => editor.serializeNode(n))]],
96
103
  })
97
104
 
105
+ const current = editor.selection
98
106
  editor.selection = new Set()
99
- page.nodes = new Map(page.nodes.entries().filter(([, node]) => !selection.has(node)))
107
+ page.nodes = new Map(page.nodes.entries().filter(([, node]) => !current.has(node)))
100
108
  }
101
109
  }
102
110
 
@@ -104,9 +112,11 @@ export function useGroupAction() {
104
112
  const editor = useEditor()
105
113
 
106
114
  function group() {
107
- const { selection, selectionPage: page } = editor
115
+ const page = editor.selectionPage
116
+ const selection = getSelection(editor)
117
+ if (selection.length === 0 || !page) return
118
+
108
119
  const { width, height, x, y } = boxBounds(selectionBox(selection))
109
- if (selection.size === 0 || !page) return
110
120
 
111
121
  const group = new GroupNode(page, {
112
122
  id: editor.id(),
@@ -128,11 +138,12 @@ export function useGroupAction() {
128
138
  .toArray(),
129
139
  )
130
140
 
141
+ const current = editor.selection
131
142
  editor.selection = new Set()
132
143
  page.nodes = new Map([
133
144
  ...page.nodes
134
145
  .values()
135
- .filter((n) => !selection.has(n))
146
+ .filter((n) => !current.has(n))
136
147
  .map((n) => [n.id, n] as const),
137
148
  [group.id, group],
138
149
  ])
@@ -174,6 +185,8 @@ export function useGroupAction() {
174
185
  }
175
186
 
176
187
  function ungroup(group: GroupNode) {
188
+ if (group.locked) return
189
+
177
190
  const page = group.page
178
191
  const groupCenter = rectCenter(group)
179
192
  const nodes = group.nodes
@@ -231,30 +244,24 @@ export function useDuplicateAction() {
231
244
  const editor = useEditor()
232
245
 
233
246
  return function duplicate(props?: { page?: Page; nodes?: Set<Node>; offset?: number }) {
234
- const selection = props?.nodes ?? editor.selection
235
247
  const offset = props?.offset ?? 10
236
248
  const page = props?.page ?? editor.selectionPage
237
- if (selection.size === 0 || !page) return
238
-
239
- const nodes = selection
240
- .values()
241
- .map((node) =>
242
- clone(editor, node, (n) => ({
243
- x: n.x + offset,
244
- y: n.y + offset,
245
- })),
246
- )
247
- .toArray()
249
+ const nodes = Array.from(props?.nodes ?? getSelection(editor))
250
+ if (nodes.length === 0 || !page) return
251
+
252
+ const dup = nodes.map((node) =>
253
+ clone(editor, node, (n) => ({
254
+ x: n.x + offset,
255
+ y: n.y + offset,
256
+ })),
257
+ )
248
258
 
249
- page.nodes = new Map([
250
- ...page.nodes,
251
- ...nodes.map((node) => [node.id, node] as const),
252
- ])
259
+ page.nodes = new Map([...page.nodes, ...dup.map((node) => [node.id, node] as const)])
253
260
 
254
- editor.selection = new Set(nodes)
261
+ editor.selection = new Set(dup)
255
262
  editor.history.push({
256
- redo: ["add-node", [page.id, nodes.map((n) => editor.serializeNode(n))]],
257
- undo: ["delete-node", [page.id, nodes.map((node) => node.id)]],
263
+ redo: ["add-node", [page.id, dup.map((n) => editor.serializeNode(n))]],
264
+ undo: ["delete-node", [page.id, dup.map((node) => node.id)]],
258
265
  })
259
266
  }
260
267
  }
@@ -264,26 +271,15 @@ export function useToggleLockAction() {
264
271
 
265
272
  return function toggleLock() {
266
273
  const selection = editor.selection.values().toArray()
267
- const isLocked = selection.some((n) => n.locked)
268
- for (const node of selection) {
269
- node.locked = !isLocked
270
- }
271
- }
272
- }
274
+ const nextLocked = selection.some((n) => !n.locked)
273
275
 
274
- export function useAlignAction() {
275
- type AlignProps = { x: number } | { y: number }
276
-
277
- const editor = useEditor()
278
-
279
- function align(fn: (node: Node) => { prev: AlignProps; next: AlignProps }) {
280
276
  const undo: HistoryAction[] = []
281
277
  const redo: HistoryAction[] = []
282
278
 
283
- for (const node of editor.selection) {
284
- const { prev, next } = fn(node)
285
- undo.push(["set-node-props", [node.id, prev]])
286
- redo.push(["set-node-props", [node.id, next]])
279
+ for (const node of selection) {
280
+ undo.push(["set-node-props", [node.id, { locked: node.locked }]])
281
+ redo.push(["set-node-props", [node.id, { locked: nextLocked }]])
282
+ node.locked = nextLocked
287
283
  }
288
284
 
289
285
  editor.history.push({
@@ -291,6 +287,24 @@ export function useAlignAction() {
291
287
  redo: ["batch", redo],
292
288
  })
293
289
  }
290
+ }
291
+
292
+ export function useAlignAction() {
293
+ type AlignProps = { x: number } | { y: number }
294
+
295
+ const editor = useEditor()
296
+
297
+ function align(set: (node: Node) => { prev: AlignProps; next: AlignProps }) {
298
+ editor.pushHistory(
299
+ getSelection(editor).map((node) => {
300
+ const { prev, next } = set(node)
301
+ return {
302
+ undo: ["set-node-props", [node.id, prev]],
303
+ redo: ["set-node-props", [node.id, next]],
304
+ }
305
+ }),
306
+ )
307
+ }
294
308
 
295
309
  function halign(getX: (node: Node) => number) {
296
310
  align((node) => {
@@ -324,20 +338,22 @@ export function useDistributeAction() {
324
338
  const editor = useEditor()
325
339
 
326
340
  function distribute(pos: "x" | "y", size: "width" | "height") {
341
+ const selection = getSelection(editor)
342
+ if (selection.length === 0) return
343
+
327
344
  const rect = boxBounds(selectionBox(editor.selection))
328
- const array = editor.selection.values().toArray()
329
345
 
330
- const undo: HistoryAction[] = array.map((node) => [
346
+ const undo: HistoryAction[] = selection.map((node) => [
331
347
  "set-node-props",
332
348
  [node.id, { [pos]: node[pos] }],
333
349
  ])
334
350
 
335
- const total = array.reduce((sum, it) => sum + it[size], 0)
336
- const gap = (rect[size] - total) / (array.length - 1)
351
+ const total = selection.reduce((sum, it) => sum + it[size], 0)
352
+ const gap = (rect[size] - total) / (selection.length - 1)
337
353
 
338
354
  let cursor = rect[pos]
339
355
 
340
- const redo: HistoryAction[] = array
356
+ const redo: HistoryAction[] = selection
341
357
  .toSorted((a, b) => a[pos] - b[pos])
342
358
  .map((node) => {
343
359
  node[pos] = cursor
@@ -402,15 +418,17 @@ export function useStackOrderAction() {
402
418
  }
403
419
 
404
420
  const canBringBackward = useComputed(() => {
405
- const { selection, selectionPage: page } = editor
406
- if (selection.size !== 0 || !page) return false
421
+ const page = editor.selectionPage
422
+ const selection = getSelection(editor)
423
+ if (selection.length === 0 || !page) return false
407
424
  const nodes = page.nodes.values().toArray()
408
425
  return selection.values().some((n) => n !== nodes.at(0))
409
426
  })
410
427
 
411
428
  const canBringForward = useComputed(() => {
412
- const { selection, selectionPage: page } = editor
413
- if (selection.size !== 0 || !page) return false
429
+ const page = editor.selectionPage
430
+ const selection = getSelection(editor)
431
+ if (selection.length === 0 || !page) return false
414
432
  const nodes = page.nodes.values().toArray()
415
433
  return selection.values().some((n) => n !== nodes.at(-1))
416
434
  })
@@ -419,17 +437,20 @@ export function useStackOrderAction() {
419
437
  canBringBackward,
420
438
  canBringForward,
421
439
  bringBackward() {
422
- const { selection, selectionPage: page } = editor
423
- if (selection.size === 0 || !page) return
424
- order(page, moveBackward(page.nodes.values().toArray(), selection))
440
+ const page = editor.selectionPage
441
+ const selection = getSelection(editor)
442
+ if (selection.length === 0 || !page) return
443
+ order(page, moveBackward(page.nodes.values().toArray(), new Set(selection)))
425
444
  },
426
445
  bringForward() {
427
- const { selection, selectionPage: page } = editor
428
- if (selection.size === 0 || !page) return
429
- order(page, moveForward(page.nodes.values().toArray(), selection))
446
+ const page = editor.selectionPage
447
+ const selection = getSelection(editor)
448
+ if (selection.length === 0 || !page) return
449
+ order(page, moveForward(page.nodes.values().toArray(), new Set(selection)))
430
450
  },
431
451
  bringToBack() {
432
- const { selection, selectionPage: page } = editor
452
+ const page = editor.selectionPage
453
+ const selection = new Set(getSelection(editor))
433
454
  if (selection.size === 0 || !page) return
434
455
  order(page, [
435
456
  ...selection,
@@ -437,7 +458,8 @@ export function useStackOrderAction() {
437
458
  ])
438
459
  },
439
460
  bringToFront() {
440
- const { selection, selectionPage: page } = editor
461
+ const page = editor.selectionPage
462
+ const selection = new Set(getSelection(editor))
441
463
  if (selection.size === 0 || !page) return
442
464
  order(page, [
443
465
  ...page.nodes.values().filter((node) => !selection.has(node)),
@@ -18,10 +18,12 @@ export function useNodeField<N extends Node, K extends keyof N>(
18
18
  key: K,
19
19
  fallback: N[K],
20
20
  ) {
21
- const values = Array.from(nodes).map((node) => node[key])
22
21
  return useComputed<N[K]>({
23
22
  equals: isEqual,
24
- fn: () => reduce(values, fallback),
23
+ fn: () => {
24
+ const values = Array.from(nodes).map((node) => node[key])
25
+ return reduce(values, fallback)
26
+ },
25
27
  })
26
28
  }
27
29
 
@@ -92,12 +94,10 @@ export function useBatchSet() {
92
94
  nodes: Iterable<N>,
93
95
  set: Props<N> | ((n: N) => Props<N>),
94
96
  ) {
95
- let push = false
96
97
  const prev: HistoryAction[] = []
97
98
  const next: HistoryAction[] = []
98
99
 
99
100
  for (const node of nodes) {
100
- push = true
101
101
  const nextValues = typeof set === "function" ? set(node) : set
102
102
  const keys = Object.keys(nextValues) as WritableKeys<N>[]
103
103
  const prevValues = Object.fromEntries(keys.map((key) => [key, node[key]]))
@@ -106,8 +106,12 @@ export function useBatchSet() {
106
106
  Object.assign(node, nextValues)
107
107
  }
108
108
 
109
- if (push) {
109
+ if (next.length > 1) {
110
110
  history.push({ undo: ["batch", prev], redo: ["batch", next] })
111
+ } else if (next.length > 0) {
112
+ const [undo] = prev
113
+ const [redo] = next
114
+ history.push({ undo, redo })
111
115
  }
112
116
  }
113
117
  }
@@ -12,13 +12,13 @@ export {
12
12
  } from "./actions"
13
13
  export { useBatchSet, useNodeField, useNodeFieldBatch } from "./batch"
14
14
  export { EditorContext, PageContext, useEditor, usePage } from "./editor"
15
- export { useMoveable } from "./pointer/moveable"
16
- export { useMovePoint } from "./pointer/movePoint"
17
- export { usePointer } from "./pointer/pointer"
18
- export { useResize } from "./pointer/resize"
19
- export { useRotation } from "./pointer/rotation"
20
- export { useSelectionFrame } from "./pointer/selectionFrame"
21
- export { useSelector } from "./pointer/selector"
15
+ export { useMoveable } from "./pointer/useMoveable"
16
+ export { useMovePoint } from "./pointer/useMovePoint"
17
+ export { usePointer } from "./pointer/usePointer"
18
+ export { useResize } from "./pointer/useResize"
19
+ export { useRotation } from "./pointer/useRotation"
20
+ export { useSelectionFrame } from "./selectionFrame"
21
+ export { useSelector } from "./pointer/useSelector"
22
22
  export { useTextMarks } from "./textMarks"
23
23
  export { useEditPage } from "./page"
24
24
  export { useVisualPositionBatch } from "./node"
package/lib/hooks/page.ts CHANGED
@@ -1,10 +1,8 @@
1
1
  import { useRef } from "react"
2
- import type { Page } from "../model"
3
- import { useEditor } from "./editor"
4
2
  import { useStore } from "react-bolt"
3
+ import type { Page } from "../model"
5
4
 
6
5
  export function useEditPage<Key extends keyof Page>(page: Page, key: Key) {
7
- const editor = useEditor()
8
6
  const init = useRef(page[key])
9
7
  const value = useStore(page, key)
10
8
 
@@ -14,7 +12,7 @@ export function useEditPage<Key extends keyof Page>(page: Page, key: Key) {
14
12
  page[key] = value
15
13
  },
16
14
  onChangeEnd(value: Page[Key]) {
17
- editor.history.push({
15
+ page.editor.history.push({
18
16
  redo: ["set-page-props", [page.id, { [key]: value }]],
19
17
  undo: ["set-page-props", [page.id, { [key]: init.current }]],
20
18
  })
@@ -0,0 +1,100 @@
1
+ import { useRef } from "react"
2
+ import type { LineNode } from "../../model"
3
+ import {
4
+ deg,
5
+ floatNorm,
6
+ point,
7
+ pointSubtract,
8
+ rect,
9
+ rectCenter,
10
+ rotatePoint,
11
+ type Point,
12
+ } from "../../model/geometry/math"
13
+ import { useEditor } from "../editor"
14
+ import { cursorPosition, usePointer } from "./usePointer"
15
+ import { useSnap } from "./useSnap"
16
+
17
+ export function useMovePoint(props: { lineNode: LineNode; pointIndex: number }) {
18
+ const { lineNode, pointIndex } = props
19
+ const editor = useEditor()
20
+ const page = lineNode.page
21
+ const snap = useSnap(page)
22
+
23
+ const state = useRef({
24
+ cursorOffset: point(),
25
+ baseVertices: [] as Point[],
26
+ basePoints: [] as Point[],
27
+ baseX: 0,
28
+ baseY: 0,
29
+ })
30
+
31
+ function unrotatedPagePoints(vertices: Point[]): Point[] {
32
+ const rotation = lineNode.rotation
33
+ const origin = point()
34
+ const unrotatedBounds = rect(
35
+ ...vertices.map((p) => rotatePoint(p, origin, deg(-rotation))),
36
+ )
37
+ const center = rotatePoint(rectCenter(unrotatedBounds), origin, rotation)
38
+ return vertices.map((p) => rotatePoint(p, center, deg(-rotation)))
39
+ }
40
+
41
+ function setLinePoints(pagePoints: Point[]) {
42
+ const bounds = point(rect(...pagePoints))
43
+ lineNode.x = floatNorm(bounds.x)
44
+ lineNode.y = floatNorm(bounds.y)
45
+ lineNode.points = pagePoints.map((p) => pointSubtract(p, bounds))
46
+ }
47
+
48
+ function redoProps() {
49
+ const { x, y, points } = lineNode
50
+ return { x, y, points }
51
+ }
52
+
53
+ function undoProps() {
54
+ const { baseX: x, baseY: y, basePoints: points } = state.current
55
+ return { x, y, points }
56
+ }
57
+
58
+ return usePointer({
59
+ onDown(event) {
60
+ if (lineNode.locked) return false
61
+
62
+ event.preventDefault()
63
+ event.stopPropagation()
64
+
65
+ state.current = {
66
+ baseVertices: lineNode.vertices,
67
+ basePoints: lineNode.points,
68
+ baseX: lineNode.x,
69
+ baseY: lineNode.y,
70
+ cursorOffset: pointSubtract(
71
+ cursorPosition(event, page),
72
+ lineNode.vertices[pointIndex],
73
+ ),
74
+ }
75
+ },
76
+ onMove(event) {
77
+ const { cursorOffset, baseVertices } = state.current
78
+ const cursor = pointSubtract(cursorPosition(event, page), cursorOffset)
79
+ const result = point(snap(!event.shiftKey, rect(cursor)))
80
+ const vertices = baseVertices.map((p, i) => (i === pointIndex ? result : p))
81
+
82
+ setLinePoints(unrotatedPagePoints(vertices))
83
+
84
+ editor.action = { action: "move", payload: result }
85
+ },
86
+ onCancel() {
87
+ editor.action = {}
88
+ page.snapLines = []
89
+ },
90
+ onEnd() {
91
+ editor.action = {}
92
+ page.snapLines = []
93
+
94
+ editor.pushHistory({
95
+ redo: ["set-node-props", [lineNode.id, redoProps()]],
96
+ undo: ["set-node-props", [lineNode.id, undoProps()]],
97
+ })
98
+ },
99
+ })
100
+ }
@@ -1,4 +1,5 @@
1
1
  import { useRef } from "react"
2
+ import { LineNode } from "../../model"
2
3
  import {
3
4
  accessibleLine,
4
5
  box,
@@ -10,13 +11,11 @@ import {
10
11
  pointSubtract,
11
12
  rect,
12
13
  } from "../../model/geometry/math"
13
- import type { HistoryEntry } from "../../model/history"
14
14
  import type { Node } from "../../model/node"
15
15
  import { selectionBox } from "../../ui/selection"
16
16
  import { useEditor, usePage } from "../editor"
17
- import { cursorPosition, usePointer } from "./pointer"
18
- import { useSnap } from "./snap"
19
- import { LineNode } from "@lazlon/html-editor/model"
17
+ import { cursorPosition, usePointer } from "./usePointer"
18
+ import { useSnap } from "./useSnap"
20
19
 
21
20
  export function useMoveable() {
22
21
  const editor = useEditor()
@@ -24,11 +23,11 @@ export function useMoveable() {
24
23
  const snap = useSnap(page)
25
24
 
26
25
  const state = useRef({
27
- initialSelectionRect: rect(),
26
+ baseRect: rect(),
28
27
  cursorOffset: point(),
29
28
  nodes: Array<{
30
29
  node: Node
31
- startingPoint: Point
30
+ basePosition: Point
32
31
  selectionOffset: Point
33
32
  }>(),
34
33
  })
@@ -64,25 +63,25 @@ export function useMoveable() {
64
63
  }
65
64
 
66
65
  if (editor.selection.size === 0) return false
66
+ if (!isCursorInSelection(cursor)) return false
67
67
 
68
- const initialSelectionBox = selectionBox(editor.selection)
69
- const initialSelectionRect = boxBounds(initialSelectionBox)
70
-
71
- if (isCursorInSelection(cursor)) {
72
- state.current = {
73
- initialSelectionRect,
74
- cursorOffset: pointSubtract(cursor, initialSelectionRect),
75
- nodes: editor.selection
76
- .values()
77
- .map((node) => ({
78
- node,
79
- startingPoint: point(node),
80
- selectionOffset: pointSubtract(point(node), initialSelectionRect),
81
- }))
82
- .toArray(),
83
- }
84
- } else {
85
- return false
68
+ for (const node of editor.selection) {
69
+ if (node.locked) return false
70
+ }
71
+
72
+ const baseRect = boxBounds(selectionBox(editor.selection))
73
+
74
+ state.current = {
75
+ baseRect,
76
+ cursorOffset: pointSubtract(cursor, baseRect),
77
+ nodes: editor.selection
78
+ .values()
79
+ .map((node) => ({
80
+ node,
81
+ basePosition: point(node),
82
+ selectionOffset: pointSubtract(point(node), baseRect),
83
+ }))
84
+ .toArray(),
86
85
  }
87
86
  },
88
87
 
@@ -95,8 +94,8 @@ export function useMoveable() {
95
94
  rect({
96
95
  x: target.x,
97
96
  y: target.y,
98
- width: state.current.initialSelectionRect.width,
99
- height: state.current.initialSelectionRect.height,
97
+ width: state.current.baseRect.width,
98
+ height: state.current.baseRect.height,
100
99
  }),
101
100
  )
102
101
 
@@ -117,17 +116,12 @@ export function useMoveable() {
117
116
  editor.action = {}
118
117
  page.snapLines = []
119
118
 
120
- const entries: HistoryEntry[] = state.current.nodes.map(
121
- ({ node, startingPoint: start }) => ({
122
- redo: ["set-node-props", [node.id, { x: node.x, y: node.y }]],
123
- undo: ["set-node-props", [node.id, { x: start.x, y: start.y }]],
124
- }),
119
+ editor.pushHistory(
120
+ state.current.nodes.map(({ node, basePosition }) => ({
121
+ redo: ["set-node-props", [node.id, point(node)]],
122
+ undo: ["set-node-props", [node.id, basePosition]],
123
+ })),
125
124
  )
126
-
127
- editor.history.push({
128
- redo: ["batch", entries.map((e) => e.redo)],
129
- undo: ["batch", entries.map((e) => e.undo)],
130
- })
131
125
  },
132
126
  })
133
127
  }
@@ -17,8 +17,8 @@ export function cursorPosition(
17
17
  const { x, y } = page.ref!.getBoundingClientRect()
18
18
 
19
19
  return {
20
- x: floatNorm(event.clientX / zoom - x),
21
- y: floatNorm(event.clientY / zoom - y),
20
+ x: floatNorm((event.clientX - x) / zoom),
21
+ y: floatNorm((event.clientY - y) / zoom),
22
22
  }
23
23
  }
24
24
 
@@ -0,0 +1,31 @@
1
+ import { TextNode } from "@lazlon/html-editor/model"
2
+ import { useComputed, useStore } from "react-bolt"
3
+ import type { Corner, Edge } from "../../../model/geometry/math"
4
+ import { useEditor } from "../../editor"
5
+ import { useMultiResize } from "./multi"
6
+ import { useSingleRegularNodeResize } from "./singleRegularNode"
7
+ import { useSingleTextNodeResize } from "./singleTextNode"
8
+ import type { usePointer } from "../usePointer"
9
+
10
+ export function useResize(direction: Edge | Corner): ReturnType<typeof usePointer> {
11
+ const editor = useEditor()
12
+ const selection = useStore(editor, "selection")
13
+ const [node, ...nodes] = selection
14
+ const singleText = useSingleTextNodeResize(direction)
15
+ const single = useSingleRegularNodeResize(direction)
16
+ const multi = useMultiResize(direction)
17
+ const isLocked = useComputed(() => selection.values().some((node) => node.locked))
18
+
19
+ if (isLocked) {
20
+ return {
21
+ onPointerDown() {},
22
+ isMoving: () => false,
23
+ }
24
+ }
25
+
26
+ return nodes.length === 0
27
+ ? node instanceof TextNode && direction !== "w" && direction !== "e"
28
+ ? singleText
29
+ : single
30
+ : multi
31
+ }