@pictogrammers/components 0.5.14 → 0.5.17

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 (153) hide show
  1. package/package.json +1 -1
  2. package/pg/autocompleteItem/__examples__/basic/basic.html +3 -0
  3. package/pg/autocompleteItem/autocompleteItem.css +55 -0
  4. package/pg/autocompleteItem/autocompleteItem.html +1 -0
  5. package/pg/autocompleteItem/autocompleteItem.ts +65 -0
  6. package/pg/button/button.css +5 -0
  7. package/pg/buttonMenu/buttonMenu.ts +1 -1
  8. package/pg/inputCombobox/__examples__/basic/basic.ts +33 -0
  9. package/pg/inputCombobox/inputCombobox.ts +158 -0
  10. package/pg/inputCombobox/inputCombobox.ts.tmp.44252.751480cb81cc +158 -0
  11. package/pg/inputCombobox/inputCombobox.ts.tmp.44252.dcea847c40a8 +156 -0
  12. package/pg/inputNumber/inputNumber.ts +31 -13
  13. package/pg/inputPill/inputPill.html +6 -0
  14. package/pg/inputPill/inputPill.html.tmp.26852.84ac63f4c46f +6 -0
  15. package/pg/inputPill/inputPill.ts +41 -0
  16. package/pg/inputPills/__examples__/basic/basic.html +9 -0
  17. package/pg/inputPills/__examples__/basic/basic.html.tmp.26852.4771e4cad5b7 +9 -0
  18. package/pg/inputPills/__examples__/basic/basic.ts.tmp.26852.d8fe41df18bd +0 -0
  19. package/pg/inputPixelEditor/README.md +104 -69
  20. package/pg/inputPixelEditor/README.md.tmp.42596.658de5f0a6f5 +0 -0
  21. package/pg/inputPixelEditor/__examples__/basic/basic.ts +6 -5
  22. package/pg/inputPixelEditor/inputPixelEditor.ts +95 -23
  23. package/pg/inputPixelEditor/inputPixelEditor.ts.tmp.38516.96529dd82670 +1703 -0
  24. package/pg/inputPixelEditor/inputPixelEditor.ts.tmp.42596.48ecc3ad9c72 +1724 -0
  25. package/pg/inputPixelEditor/utils/bitmapToMask.ts +113 -79
  26. package/pg/inputPixelEditor/utils/getLassoPixels.ts +51 -0
  27. package/pg/inputRange/inputRange.css +7 -0
  28. package/pg/inputSelect/__examples__/basic/basic.ts +2 -2
  29. package/pg/inputSelect/inputSelect.css +1 -1
  30. package/pg/inputSelect/inputSelect.ts +62 -5
  31. package/pg/inputText/README.md +6 -6
  32. package/pg/inputText/__examples__/basic/basic.ts +1 -1
  33. package/pg/inputText/inputText.css +2 -2
  34. package/pg/inputText copy/README.md +38 -0
  35. package/pg/inputText copy/inputText.css +41 -0
  36. package/pg/inputText copy/inputText.html +1 -0
  37. package/pg/inputText copy/inputText.spec.ts +59 -0
  38. package/pg/inputText copy/inputText.ts +67 -0
  39. package/pg/inputTextarea/README.md +39 -0
  40. package/pg/inputTextarea/__examples__/basic/basic.html +9 -0
  41. package/pg/inputTextarea/__examples__/basic/basic.ts +30 -0
  42. package/pg/inputTextarea/inputTextarea.ts +121 -0
  43. package/pg/inputTextarea copy/README.md +39 -0
  44. package/pg/inputTextarea copy/inputTextarea.css +41 -0
  45. package/pg/inputTextarea copy/inputTextarea.html +1 -0
  46. package/pg/inputTextarea copy/inputTextarea.spec.ts +59 -0
  47. package/pg/inputTextarea copy/inputTextarea.ts +79 -0
  48. package/pg/inputTextareaAutocomplete/README.md +70 -0
  49. package/pg/inputTextareaAutocomplete/__examples__/basic/basic.ts +67 -0
  50. package/pg/inputTextareaAutocomplete/inputTextareaAutocomplete.ts +267 -0
  51. package/pg/node/README.md +35 -0
  52. package/pg/node/__examples__/basic/basic.ts +34 -0
  53. package/pg/node/index.ts +3 -0
  54. package/pg/node/node.css +53 -0
  55. package/pg/node/node.html +5 -0
  56. package/pg/node/node.ts +270 -0
  57. package/pg/nodeEditorLink/nodeEditorLink.css +69 -0
  58. package/pg/nodeEditorLink/nodeEditorLink.css.tmp.1408.09e961fd0dcc +69 -0
  59. package/pg/nodeEditorLink/nodeEditorLink.html +9 -0
  60. package/pg/nodeEditorNumber/nodeEditorNumber.css +59 -0
  61. package/pg/nodeEditorNumber/nodeEditorNumber.ts +57 -0
  62. package/pg/nodeEditorRange/nodeEditorRange.css +23 -0
  63. package/pg/nodeEditorRange/nodeEditorRange.ts +69 -0
  64. package/pg/nodeEditorText/__examples__/basic/basic.html +9 -0
  65. package/pg/nodeEditorText/__examples__/basic/basic.ts +30 -0
  66. package/pg/nodeEditorText/nodeEditorText.css +17 -0
  67. package/pg/nodeEditorText/nodeEditorText.html +4 -0
  68. package/pg/nodeEditorText/nodeEditorText.ts +57 -0
  69. package/pg/nodeEditorText copy/README.md +35 -0
  70. package/pg/nodeEditorText copy/nodeEditorText.css +49 -0
  71. package/pg/nodeEditorText copy/nodeEditorText.html +4 -0
  72. package/pg/nodeEditorText copy/nodeEditorText.ts +50 -0
  73. package/pg/nodeEditorTextArray/nodeEditorTextArray.css +68 -0
  74. package/pg/nodeEditorTextArray/nodeEditorTextArray.html +9 -0
  75. package/pg/nodeEditorTextArray/nodeEditorTextArray.ts +162 -0
  76. package/pg/nodeEntry/nodeEntry.css +61 -0
  77. package/pg/nodeEntry/nodeEntry.ts +164 -0
  78. package/pg/nodeEntry/nodeEntry.ts.tmp.28724.dda67762bca5 +164 -0
  79. package/pg/nodeInputText/nodeInputText.css +75 -0
  80. package/pg/nodeInputText/nodeInputText.html +4 -0
  81. package/pg/nodeInputText/nodeInputText.ts +105 -0
  82. package/pg/nodeOutput/nodeOutput.css +32 -0
  83. package/pg/nodeResize/README.md +3 -0
  84. package/pg/nodeResize/THINKING.md +81 -0
  85. package/pg/nodeResize/THINKING.md.tmp.24812.99a70ba81062 +81 -0
  86. package/pg/nodeResize/basic/basic.ts +17 -0
  87. package/pg/nodeResize/dragUtil.ts +105 -0
  88. package/pg/nodeResize/dragUtil.ts.tmp.34372.787464c73309 +75 -0
  89. package/pg/nodeResize/nodeResize.css +187 -0
  90. package/pg/nodeResize/nodeResize.html +9 -0
  91. package/pg/nodeResize/nodeResize.ts +156 -0
  92. package/pg/nodes/README.md +177 -0
  93. package/pg/nodes/__examples__/basic/basic.html +33 -0
  94. package/pg/nodes/__examples__/basic/basic.ts +716 -0
  95. package/pg/nodes/__examples__/basic/exampleNodes.ts +129 -0
  96. package/pg/nodes/__examples__/basic/exampleScripts.ts +129 -0
  97. package/pg/nodes/__examples__/basic/keystore.ts +38 -0
  98. package/pg/nodes/executeInSandbox.ts +32 -0
  99. package/pg/nodes/index.ts +3 -0
  100. package/pg/nodes/nodes.css +69 -0
  101. package/pg/nodes/nodes.html +6 -0
  102. package/pg/nodes/nodes.ts +1240 -0
  103. package/pg/nodes copy/nodes.css +41 -0
  104. package/pg/nodes copy/nodes.html +1 -0
  105. package/pg/overlayContextMenu/__examples__/filter/filter.ts +87 -0
  106. package/pg/overlayContextMenu/overlayContextMenu.css +14 -0
  107. package/pg/overlayContextMenu/overlayContextMenu.html +4 -3
  108. package/pg/overlayContextMenu/overlayContextMenu.ts +6 -2
  109. package/pg/overlayContextMenu/overlayContextMenu.ts.tmp.37088.530ff1c0a321 +0 -0
  110. package/pg/overlayMenu/overlayMenu.css +3 -1
  111. package/pg/overlayMenu/overlayMenu.ts +11 -2
  112. package/pg/overlayMenu/overlayMenu.ts.tmp.44252.9bb22fd59ede +80 -0
  113. package/pg/overlaySelectMenu/overlaySelectMenu.css +2 -0
  114. package/pg/overlaySubMenu/overlaySubMenu.css +8 -6
  115. package/pg/poco/README.md +22 -0
  116. package/pg/poco/__examples__/basic/basic.html +11 -0
  117. package/pg/poco/__examples__/basic/basic.ts +95 -0
  118. package/pg/poco/__examples__/basic/examples.ts +258 -0
  119. package/pg/poco/__examples__/basic/myFont.ts +1300 -0
  120. package/pg/poco/index.ts +3 -0
  121. package/pg/poco/mockPoco.ts +640 -0
  122. package/pg/poco/poco.css +7 -0
  123. package/pg/poco/poco.html +1 -0
  124. package/pg/poco/poco.ts +75 -0
  125. package/pg/popover/README.md +47 -0
  126. package/pg/popover/__examples__/basic/basic.html +3 -0
  127. package/pg/popover/popover.html +3 -0
  128. package/pg/popover/popover.ts +44 -0
  129. package/pg/popoverAutocomplete/__examples__/basic/basic.html +4 -0
  130. package/pg/popoverAutocomplete/__examples__/basic/basic.ts +46 -0
  131. package/pg/popoverAutocomplete/popoverAutocomplete.css +25 -0
  132. package/pg/popoverAutocomplete/popoverAutocomplete.html +1 -0
  133. package/pg/popoverAutocomplete/popoverAutocomplete.ts +134 -0
  134. package/pg/popoverAutocompleteItem/popoverAutocompleteItem.css +69 -0
  135. package/pg/popoverAutocompleteItem/popoverAutocompleteItem.ts +90 -0
  136. package/pg/table/__examples__/basic/basic.ts +1 -0
  137. package/pg/tableCellFileLocal/tableCellFileLocal.html +3 -0
  138. package/pg/tableCellFileLocal/tableCellFileLocal.html.tmp.20484.68ee2e30710e +3 -0
  139. package/pg/tableCellFileLocal/tableCellFileLocal.ts +44 -0
  140. package/pg/tableCellSelect/tableCellSelect.html +3 -0
  141. package/pg/tableCellSelect/tableCellSelect.html.tmp.41808.99c1a6f14340 +3 -0
  142. package/pg/tableCellSelect/tableCellSelect.ts +51 -0
  143. package/pg/tree/__examples__/basic/basic.ts +12 -5
  144. package/pg/tree/tree.html +1 -1
  145. package/pg/tree/tree.ts +385 -227
  146. package/pg/tree/tree.ts.tmp.38376.07d376a04f41 +343 -0
  147. package/pg/tree/tree.ts.tmp.38376.2a04599ade9f +361 -0
  148. package/pg/tree/tree.ts.tmp.38376.6cb2c663d476 +0 -0
  149. package/pg/tree/tree.ts.tmp.38376.ac5af1aff2b0 +393 -0
  150. package/pg/tree/tree.ts.tmp.38376.ac9acfc4f95f +324 -0
  151. package/pg/treeItem/treeItem.ts +536 -558
  152. package/pg/treeItem/treeItem.ts.tmp.38376.145d3121d648 +530 -0
  153. package/pg/treeItem/treeItem.ts.tmp.38376.b5224d48e5c0 +0 -0
@@ -0,0 +1,105 @@
1
+ import { Component, Prop, Part } from '@pictogrammers/element';
2
+
3
+ import template from './nodeInputText.html';
4
+ import style from './nodeInputText.css';
5
+
6
+ @Component({
7
+ selector: 'pg-node-input-text',
8
+ style,
9
+ template,
10
+ })
11
+ export default class PgNodeInputText extends HTMLElement {
12
+
13
+ @Prop() index: number;
14
+ @Prop() value: string = '';
15
+ @Prop() name: string = '';
16
+ @Prop() removable: boolean = false;
17
+
18
+ @Part() $input: HTMLInputElement;
19
+ @Part() $remove: HTMLButtonElement;
20
+
21
+ connectedCallback() {
22
+ this.$input.addEventListener('change', (e: any) => {
23
+ e.stopPropagation();
24
+ this.dispatchEvent(new CustomEvent('change', {
25
+ detail: {
26
+ index: this.index,
27
+ value: this.$input.value,
28
+ }
29
+ }));
30
+ });
31
+ this.$input.addEventListener('input', (e: any) => {
32
+ e.stopPropagation();
33
+ this.dispatchEvent(new CustomEvent('input', {
34
+ detail: {
35
+ index: this.index,
36
+ value: this.$input.value,
37
+ }
38
+ }));
39
+ });
40
+ this.$input.addEventListener('keydown', (e: KeyboardEvent) => {
41
+ if (e.key === 'ArrowUp') {
42
+ e.preventDefault();
43
+ this.dispatchEvent(new CustomEvent('inputprevious', {
44
+ detail: {
45
+ index: this.index,
46
+ selectionIndex: this.$input.selectionDirection === 'forward'
47
+ ? this.$input.selectionStart
48
+ : this.$input.selectionEnd,
49
+ }
50
+ }));
51
+ } else if (e.key === 'ArrowDown') {
52
+ e.preventDefault();
53
+ this.dispatchEvent(new CustomEvent('inputnext', {
54
+ detail: {
55
+ index: this.index,
56
+ selectionIndex: this.$input.selectionDirection === 'forward'
57
+ ? this.$input.selectionStart
58
+ : this.$input.selectionEnd,
59
+ }
60
+ }));
61
+ } else if (e.key === 'Enter' && this.$input.value !== '') {
62
+ e.preventDefault();
63
+ this.dispatchEvent(new CustomEvent('enter', {
64
+ detail: {
65
+ index: this.index,
66
+ }
67
+ }));
68
+ }
69
+ });
70
+ this.$remove.addEventListener('click', (e: any) => {
71
+ this.dispatchEvent(new CustomEvent('remove', {
72
+ detail: {
73
+ index: this.index,
74
+ }
75
+ }));
76
+ });
77
+ }
78
+
79
+ render(changes: any) {
80
+ if (changes.value) {
81
+ this.$input.value = this.value;
82
+ }
83
+ if (changes.removable) {
84
+ this.$remove.classList.toggle('hide', !this.removable);
85
+ }
86
+ }
87
+
88
+ focus() {
89
+ this.$input.focus();
90
+ }
91
+
92
+ get selectionStart(){
93
+ return this.$input.selectionStart || 0;
94
+ }
95
+ set selectionStart(value: number) {
96
+ this.$input.selectionStart = value;
97
+ }
98
+
99
+ get selectionEnd(){
100
+ return this.$input.selectionEnd || 0;
101
+ }
102
+ set selectionEnd(value: number) {
103
+ this.$input.selectionEnd = value;
104
+ }
105
+ }
@@ -0,0 +1,32 @@
1
+ :host {
2
+ display: block;
3
+ font-family: var(--pg-font-family, system-ui);
4
+ }
5
+
6
+ label {
7
+ display: flex;
8
+ flex-direction: column;
9
+ }
10
+
11
+ [part=label] {
12
+ position: relative;
13
+ display: flex;
14
+ font-size: 0.75rem;
15
+ line-height: 1rem;
16
+ padding-right: 1.125rem;
17
+ color: rgba(255, 255, 255, 0.8);
18
+ justify-content: right;
19
+ }
20
+
21
+ [part=label]::after {
22
+ position: absolute;
23
+ content: ' ';
24
+ width: 0.25rem;
25
+ height: 0.25rem;
26
+ transform: rotate(45deg);
27
+ border-top: 2px solid rgba(255, 255, 255, 0.5);
28
+ border-right: 2px solid rgba(255, 255, 255, 0.5);
29
+ border-top-right-radius: 0.125rem;
30
+ right: 0.5rem;
31
+ top: 0.4rem;
32
+ }
@@ -0,0 +1,3 @@
1
+ # Node Resize Utility
2
+
3
+ Resize component allows an overlay for resizing.
@@ -0,0 +1,81 @@
1
+ # Preview Sub-Grid Offset Bug
2
+
3
+ ## The Problem
4
+
5
+ When `snap` fires it updates the node's x/y/width/height via `emit`. That moves the CSS
6
+ anchor. The preview CSS vars must then represent the **remaining sub-grid offset** so the
7
+ overlay tracks the cursor smoothly. If the formula is wrong, the overlay jumps by ~gridSize
8
+ pixels at each snap boundary.
9
+
10
+ ## dragUtil snap algorithm (asymmetric — the root bug)
11
+
12
+ ```
13
+ snapDx = (lastDx + half < 0)
14
+ ? Math.ceil((lastDx + half) / gridSize) // negative direction: ceil
15
+ : Math.floor((lastDx + half) / gridSize) // positive direction: floor
16
+ ```
17
+
18
+ Because of ceil vs floor the first snap threshold is **asymmetric**:
19
+ - Positive drag: first snap at +8 px (half grid unit) ← works correctly (south)
20
+ - Negative drag: first snap at -24 px (1.5 grid units) ← broken (north)
21
+
22
+ Negative example step-by-step with gridSize=16, half=8:
23
+ dy=-8: (-8+8)/16 = 0/16 = 0, ceil(0) = 0 → no snap yet
24
+ dy=-16: (-16+8)/16 = -8/16 = -0.5, ceil(-0.5) = 0 → still no snap
25
+ dy=-24: (-24+8)/16 = -16/16 = -1, ceil(-1) = -1 → FIRST snap (at 1.5 grid units!)
26
+
27
+ **Fix: use Math.floor for ALL values** — this rounds toward −∞ symmetrically:
28
+ dy=+8: floor((8+8)/16) = floor(1) = 1 → snap ✓
29
+ dy=-8: floor((-8+8)/16) = floor(0) = 0 → no snap yet
30
+ dy=-9: floor((-9+8)/16) = floor(-0.0625)= -1 → snap (≈half grid unit) ✓
31
+
32
+ ## Correct sub-grid formula
33
+
34
+ The correct sub-grid residual (pixels past the last snap) must mirror the same algorithm:
35
+
36
+ ```ts
37
+ subGrid(delta) = delta - (
38
+ delta + half < 0
39
+ ? Math.ceil((delta + half) / gridSize) * gridSize
40
+ : Math.floor((delta + half) / gridSize) * gridSize
41
+ )
42
+ ```
43
+
44
+ ## Why the existing formulas fail
45
+
46
+ `delta % gridSize` (used by previewX/Y/Width):
47
+ - delta = -20 → -20 % 16 = -4 (JS truncated modulo)
48
+ - correct: no snap at -20 (threshold is -24), so sub-grid = -20
49
+ - WRONG by 16 px
50
+
51
+ `((delta + half) % gridSize) - half` (used by previewHeight):
52
+ - delta = 9 → ((17) % 16) - 8 = -7 ✓ (correct, snap fired at +8)
53
+ - delta = -20 → ((-12) % 16) - 8 = -12 - 8 = -20 ✓ (correct, no snap yet)
54
+ - delta = -9 → ((-1) % 16) - 8 = -1 - 8 = -9
55
+ - correct: snap fires at -24, so sub-grid = -9 ✓ happens to be right
56
+
57
+ ## Why the west/north handles jump but south mostly does not
58
+
59
+ For south (hDir=1), dy is positive when the height grows (normal use). The previewHeight
60
+ formula is correct for positive dy. Negative dy (shrinking the south edge) rarely reaches
61
+ the problematic range.
62
+
63
+ For north (hDir=-1), the previewY/previewWidth/previewX use `% gridSize` which is
64
+ wrong in the range (-24, -16) for dx/dy values.
65
+
66
+ ## Critical insight about width/x coupling
67
+
68
+ For a west handle (wDir=-1), dx is the raw pointer delta:
69
+ - x sub-grid = subGrid(dx)
70
+ - width sub-grid = -subGrid(dx) ← NOT subGrid(-dx)
71
+
72
+ `subGrid` is NOT an odd function (asymmetric snap means subGrid(-dx) ≠ -subGrid(dx)).
73
+ So calling previewWidth(-dx) with an internal formula on -dx is wrong.
74
+
75
+ ## Fix
76
+
77
+ Compute `sgX = subGrid(dx)` and `sgY = subGrid(dy)` once per `move` event, then:
78
+ - delta-x = sgX (when wDir == -1)
79
+ - delta-width = wDir * sgX (when wDir != 0)
80
+ - delta-y = sgY (when hDir == -1)
81
+ - delta-height = hDir * sgY (when hDir != 0)
@@ -0,0 +1,81 @@
1
+ # Preview Sub-Grid Offset Bug
2
+
3
+ ## The Problem
4
+
5
+ When `snap` fires it updates the node's x/y/width/height via `emit`. That moves the CSS
6
+ anchor. The preview CSS vars must then represent the **remaining sub-grid offset** so the
7
+ overlay tracks the cursor smoothly. If the formula is wrong, the overlay jumps by ~gridSize
8
+ pixels at each snap boundary.
9
+
10
+ ## dragUtil snap algorithm (asymmetric — the root bug)
11
+
12
+ ```
13
+ snapDx = (lastDx + half < 0)
14
+ ? Math.ceil((lastDx + half) / gridSize) // negative direction: ceil
15
+ : Math.floor((lastDx + half) / gridSize) // positive direction: floor
16
+ ```
17
+
18
+ Because of ceil vs floor the first snap threshold is **asymmetric**:
19
+ - Positive drag: first snap at +8 px (half grid unit) ← works correctly (south)
20
+ - Negative drag: first snap at -24 px (1.5 grid units) ← broken (north)
21
+
22
+ Negative example step-by-step with gridSize=16, half=8:
23
+ dy=-8: (-8+8)/16 = 0/16 = 0, ceil(0) = 0 → no snap yet
24
+ dy=-16: (-16+8)/16 = -8/16 = -0.5, ceil(-0.5) = 0 → still no snap
25
+ dy=-24: (-24+8)/16 = -16/16 = -1, ceil(-1) = -1 → FIRST snap (at 1.5 grid units!)
26
+
27
+ **Fix: use Math.floor for ALL values** — this rounds toward −∞ symmetrically:
28
+ dy=+8: floor((8+8)/16) = floor(1) = 1 → snap ✓
29
+ dy=-8: floor((-8+8)/16) = floor(0) = 0 → no snap yet
30
+ dy=-9: floor((-9+8)/16) = floor(-0.0625)= -1 → snap (≈half grid unit) ✓
31
+
32
+ ## Correct sub-grid formula
33
+
34
+ The correct sub-grid residual (pixels past the last snap) must mirror the same algorithm:
35
+
36
+ ```ts
37
+ subGrid(delta) = delta - (
38
+ delta + half < 0
39
+ ? Math.ceil((delta + half) / gridSize) * gridSize
40
+ : Math.floor((delta + half) / gridSize) * gridSize
41
+ )
42
+ ```
43
+
44
+ ## Why the existing formulas fail
45
+
46
+ `delta % gridSize` (used by previewX/Y/Width):
47
+ - delta = -20 → -20 % 16 = -4 (JS truncated modulo)
48
+ - correct: no snap at -20 (threshold is -24), so sub-grid = -20
49
+ - WRONG by 16 px
50
+
51
+ `((delta + half) % gridSize) - half` (used by previewHeight):
52
+ - delta = 9 → ((17) % 16) - 8 = -7 ✓ (correct, snap fired at +8)
53
+ - delta = -20 → ((-12) % 16) - 8 = -12 - 8 = -20 ✓ (correct, no snap yet)
54
+ - delta = -9 → ((-1) % 16) - 8 = -1 - 8 = -9
55
+ - correct: snap fires at -24, so sub-grid = -9 ✓ happens to be right
56
+
57
+ ## Why the west/north handles jump but south mostly does not
58
+
59
+ For south (hDir=1), dy is positive when the height grows (normal use). The previewHeight
60
+ formula is correct for positive dy. Negative dy (shrinking the south edge) rarely reaches
61
+ the problematic range.
62
+
63
+ For north (hDir=-1), the previewY/previewWidth/previewX use `% gridSize` which is
64
+ wrong in the range (-24, -16) for dx/dy values.
65
+
66
+ ## Critical insight about width/x coupling
67
+
68
+ For a west handle (wDir=-1), dx is the raw pointer delta:
69
+ - x sub-grid = subGrid(dx)
70
+ - width sub-grid = -subGrid(dx) ← NOT subGrid(-dx)
71
+
72
+ `subGrid` is NOT an odd function (asymmetric snap means subGrid(-dx) ≠ -subGrid(dx)).
73
+ So calling previewWidth(-dx) with an internal formula on -dx is wrong.
74
+
75
+ ## Fix
76
+
77
+ Compute `sgX = subGrid(dx)` and `sgY = subGrid(dy)` once per `move` event, then:
78
+ - delta-x = sgX (when wDir == -1)
79
+ - delta-width = wDir * sgX (when wDir != 0)
80
+ - delta-y = sgY (when hDir == -1)
81
+ - delta-height = hDir * sgY (when hDir != 0)
@@ -0,0 +1,17 @@
1
+ import { Component, Part } from '@pictogrammers/element';
2
+ import PgNodeResize from '../nodeResize';
3
+
4
+ import template from './basic.html';
5
+
6
+ @Component({
7
+ selector: 'x-pg-node-resize-basic',
8
+ template,
9
+ })
10
+ export default class XPgNodeResizeBasic extends HTMLElement {
11
+
12
+ @Part() $resize: PgNodeResize;
13
+
14
+ connectedCallback() {
15
+
16
+ }
17
+ }
@@ -0,0 +1,105 @@
1
+ interface DragConfig {
2
+ source: HTMLElement;
3
+ gridSize: number;
4
+ start?: (dx: number, dy: number) => void;
5
+ move?: (dx: number, dy: number) => void;
6
+ snap?: (dx: number, dy: number) => void;
7
+ end?: (dx: number, dy: number, complete: boolean) => void;
8
+ }
9
+
10
+ export function drag({ source, gridSize, start, move, snap, end }: DragConfig): () => void {
11
+ let startX = 0;
12
+ let startY = 0;
13
+ let lastDx = 0;
14
+ let lastDy = 0;
15
+ let lastSnapDx = 0;
16
+ let lastSnapDy = 0;
17
+ let activePointerId: number | null = null;
18
+ const halfGridSize = gridSize / 2;
19
+
20
+ const onPointerDown = (e: PointerEvent) => {
21
+ e.preventDefault();
22
+ startX = e.clientX;
23
+ startY = e.clientY;
24
+ lastDx = 0;
25
+ lastDy = 0;
26
+ lastSnapDx = 0;
27
+ lastSnapDy = 0;
28
+ activePointerId = e.pointerId;
29
+ source.setPointerCapture(e.pointerId);
30
+ source.addEventListener('pointermove', onPointerMove);
31
+ source.addEventListener('pointerup', onPointerUp);
32
+ source.addEventListener('pointercancel', onPointerCancel);
33
+ document.addEventListener('keydown', onKeyDown);
34
+ start?.(0, 0);
35
+ };
36
+
37
+ const onPointerMove = (e: PointerEvent) => {
38
+ lastDx = e.clientX - startX;
39
+ lastDy = e.clientY - startY;
40
+ move?.(lastDx, lastDy);
41
+ const snapDx = lastDx + halfGridSize < 0
42
+ ? Math.ceil((lastDx + halfGridSize) / gridSize)
43
+ : Math.floor((lastDx + halfGridSize) / gridSize);
44
+ const snapDy = lastDy + halfGridSize < 0
45
+ ? Math.ceil((lastDy + halfGridSize) / gridSize)
46
+ : Math.floor((lastDy + halfGridSize) / gridSize);
47
+ if (snapDx !== lastSnapDx || snapDy !== lastSnapDy) {
48
+ lastSnapDx = snapDx;
49
+ lastSnapDy = snapDy;
50
+ snap?.(snapDx, snapDy);
51
+ }
52
+ };
53
+
54
+ const onPointerUp = (e: PointerEvent) => {
55
+ const dx = e.clientX + halfGridSize - startX < 0
56
+ ? Math.ceil((e.clientX + halfGridSize - startX) / gridSize)
57
+ : Math.floor((e.clientX + halfGridSize - startX) / gridSize);
58
+ const dy = e.clientY + halfGridSize - startY < 0
59
+ ? Math.ceil((e.clientY + halfGridSize - startY) / gridSize)
60
+ : Math.floor((e.clientY + halfGridSize - startY) / gridSize);
61
+ stopDrag();
62
+ end?.(dx, dy, true);
63
+ };
64
+
65
+ const onPointerCancel = () => {
66
+ const dx = lastDx < 0
67
+ ? Math.ceil((lastDx + halfGridSize) / gridSize)
68
+ : Math.floor((lastDx + halfGridSize) / gridSize);
69
+ const dy = lastDy < 0
70
+ ? Math.ceil((lastDy + halfGridSize) / gridSize)
71
+ : Math.floor((lastDy + halfGridSize) / gridSize);
72
+ stopDrag();
73
+ end?.(dx, dy, false);
74
+ };
75
+
76
+ const onKeyDown = (e: KeyboardEvent) => {
77
+ if (e.key !== 'Escape') { return; }
78
+ const dx = lastDx < 0
79
+ ? Math.ceil((lastDx + halfGridSize) / gridSize)
80
+ : Math.floor((lastDx + halfGridSize) / gridSize);
81
+ const dy = lastDy < 0
82
+ ? Math.ceil((lastDy + halfGridSize) / gridSize)
83
+ : Math.floor((lastDy + halfGridSize) / gridSize);
84
+ if (activePointerId !== null) {
85
+ source.releasePointerCapture(activePointerId);
86
+ }
87
+ stopDrag();
88
+ end?.(dx, dy, false);
89
+ };
90
+
91
+ const stopDrag = () => {
92
+ activePointerId = null;
93
+ source.removeEventListener('pointermove', onPointerMove);
94
+ source.removeEventListener('pointerup', onPointerUp);
95
+ source.removeEventListener('pointercancel', onPointerCancel);
96
+ document.removeEventListener('keydown', onKeyDown);
97
+ };
98
+
99
+ source.addEventListener('pointerdown', onPointerDown);
100
+
101
+ return () => {
102
+ source.removeEventListener('pointerdown', onPointerDown);
103
+ stopDrag();
104
+ };
105
+ }
@@ -0,0 +1,75 @@
1
+ interface DragConfig {
2
+ source: HTMLElement;
3
+ start?: (dx: number, dy: number) => void;
4
+ move?: (dx: number, dy: number) => void;
5
+ end?: (dx: number, dy: number, complete: boolean) => void;
6
+ }
7
+
8
+ export function drag({ source, start, move, end }: DragConfig): () => void {
9
+ let startX = 0;
10
+ let startY = 0;
11
+ let lastDx = 0;
12
+ let lastDy = 0;
13
+ let activePointerId: number | null = null;
14
+
15
+ const onPointerDown = (e: PointerEvent) => {
16
+ e.preventDefault();
17
+ startX = e.clientX;
18
+ startY = e.clientY;
19
+ lastDx = 0;
20
+ lastDy = 0;
21
+ activePointerId = e.pointerId;
22
+ source.setPointerCapture(e.pointerId);
23
+ source.addEventListener('pointermove', onPointerMove);
24
+ source.addEventListener('pointerup', onPointerUp);
25
+ source.addEventListener('pointercancel', onPointerCancel);
26
+ document.addEventListener('keydown', onKeyDown);
27
+ start?.(0, 0);
28
+ };
29
+
30
+ const onPointerMove = (e: PointerEvent) => {
31
+ lastDx = e.clientX - startX;
32
+ lastDy = e.clientY - startY;
33
+ move?.(lastDx, lastDy);
34
+ };
35
+
36
+ const onPointerUp = (e: PointerEvent) => {
37
+ const dx = e.clientX - startX;
38
+ const dy = e.clientY - startY;
39
+ stopDrag();
40
+ end?.(dx, dy, true);
41
+ };
42
+
43
+ const onPointerCancel = () => {
44
+ const dx = lastDx;
45
+ const dy = lastDy;
46
+ stopDrag();
47
+ end?.(dx, dy, false);
48
+ };
49
+
50
+ const onKeyDown = (e: KeyboardEvent) => {
51
+ if (e.key !== 'Escape') { return; }
52
+ const dx = lastDx;
53
+ const dy = lastDy;
54
+ if (activePointerId !== null) {
55
+ source.releasePointerCapture(activePointerId);
56
+ }
57
+ stopDrag();
58
+ end?.(dx, dy, false);
59
+ };
60
+
61
+ const stopDrag = () => {
62
+ activePointerId = null;
63
+ source.removeEventListener('pointermove', onPointerMove);
64
+ source.removeEventListener('pointerup', onPointerUp);
65
+ source.removeEventListener('pointercancel', onPointerCancel);
66
+ document.removeEventListener('keydown', onKeyDown);
67
+ };
68
+
69
+ source.addEventListener('pointerdown', onPointerDown);
70
+
71
+ return () => {
72
+ source.removeEventListener('pointerdown', onPointerDown);
73
+ stopDrag();
74
+ };
75
+ }
@@ -0,0 +1,187 @@
1
+ :host {
2
+ position: absolute;
3
+ display: grid;
4
+ width: calc(anchor-size(width) + 0.5rem + var(--node-resize-delta-width, 0px));
5
+ height: calc(anchor-size(height) + 0.5rem + var(--node-resize-delta-height, 0px));
6
+ top: calc(anchor(top) - 0.25rem + var(--node-resize-delta-y, 0px));
7
+ left: calc(anchor(left) - 0.25rem + var(--node-resize-delta-x, 0px));
8
+ border: 0;
9
+ background: transparent;
10
+ --resize-radius: 0.25rem;
11
+ grid-template-columns: 0.75rem auto 0.75rem;
12
+ grid-template-rows: 0.75rem auto 0.75rem;
13
+ --node-resize-outline: transparent;
14
+ --node-resize-hover: #248BE5;
15
+ pointer-events: none;
16
+ }
17
+
18
+ :host(.preview) {
19
+ --node-resize-outline: rgba(36, 139, 229, 0.5);
20
+ }
21
+
22
+ [part=header] {
23
+ height: 1rem;
24
+ pointer-events: all;
25
+ }
26
+
27
+ [part=northWest] {
28
+ width: 0.75rem;
29
+ height: 0.75rem;
30
+ grid-column: 1;
31
+ grid-row: 1;
32
+ padding-top: 0.25rem;
33
+ padding-left: 0.25rem;
34
+ pointer-events: all;
35
+ }
36
+ [part=northWest]::before {
37
+ content: ' ';
38
+ display: flex;
39
+ border-top-left-radius: var(--resize-radius);
40
+ border-top: 2px solid var(--node-resize-outline);
41
+ border-left: 2px solid var(--node-resize-outline);
42
+ width: 0.375rem;
43
+ height: 0.375rem;
44
+ }
45
+ [part=northWest]:hover::before {
46
+ border-color: var(--node-resize-hover);
47
+ }
48
+ [part=north] {
49
+ height: 0.5rem;
50
+ grid-column: 2;
51
+ grid-row: 1;
52
+ padding-top: 0.25rem;
53
+ pointer-events: all;
54
+ }
55
+ [part=north]::before {
56
+ content: ' ';
57
+ display: flex;
58
+ border-top: 2px solid var(--node-resize-outline);
59
+ height: 0.375rem;
60
+ }
61
+ [part=north]:hover::before {
62
+ border-color: var(--node-resize-hover);
63
+ }
64
+ [part=northEast] {
65
+ width: 0.75rem;
66
+ height: 0.75rem;
67
+ grid-column: 3;
68
+ grid-row: 1;
69
+ padding-top: 0.25rem;
70
+ padding-right: 0.25rem;
71
+ pointer-events: all;
72
+ }
73
+ [part=northEast]::before {
74
+ content: ' ';
75
+ display: flex;
76
+ border-top-right-radius: var(--resize-radius);
77
+ border-top: 2px solid var(--node-resize-outline);
78
+ border-right: 2px solid var(--node-resize-outline);
79
+ width: 0.375rem;
80
+ height: 0.375rem;
81
+ }
82
+ [part=northEast]:hover::before {
83
+ border-color: var(--node-resize-hover);
84
+ }
85
+ [part=west] {
86
+ width: 0.5rem;
87
+ grid-column: 1;
88
+ grid-row: 2;
89
+ padding-left: 0.25rem;
90
+ pointer-events: all;
91
+ }
92
+ [part=west]::before {
93
+ content: ' ';
94
+ display: flex;
95
+ border-left: 2px solid var(--node-resize-outline);
96
+ width: 0.375rem;
97
+ height: 100%;
98
+ }
99
+ [part=west]:hover::before {
100
+ border-color: var(--node-resize-hover);
101
+ }
102
+ [part=east] {
103
+ width: 0.5rem;
104
+ grid-column: 3;
105
+ grid-row: 2;
106
+ padding-right: 0.25rem;
107
+ pointer-events: all;
108
+ }
109
+ [part=east]::before {
110
+ content: ' ';
111
+ display: flex;
112
+ border-right: 2px solid var(--node-resize-outline);
113
+ width: 0.375rem;
114
+ height: 100%;
115
+ }
116
+ [part=east]:hover::before {
117
+ border-color: var(--node-resize-hover);
118
+ }
119
+ [part=southWest] {
120
+ width: 0.5rem;
121
+ height: 0.5rem;
122
+ grid-column: 1;
123
+ grid-row: 3;
124
+ padding-left: 0.25rem;
125
+ padding-bottom: 0.25rem;
126
+ pointer-events: all;
127
+ }
128
+ [part=southWest]::before {
129
+ content: ' ';
130
+ display: flex;
131
+ border-bottom-left-radius: var(--resize-radius);
132
+ border-bottom: 2px solid var(--node-resize-outline);
133
+ border-left: 2px solid var(--node-resize-outline);
134
+ width: 0.375rem;
135
+ height: 0.375rem;
136
+ }
137
+ [part=southWest]:hover::before {
138
+ border-color: var(--node-resize-hover);
139
+ }
140
+ [part=south] {
141
+ height: 0.5rem;
142
+ grid-column: 2;
143
+ grid-row: 3;
144
+ pointer-events: all;
145
+ padding-bottom: 0.25rem;
146
+ }
147
+ [part=south]::before {
148
+ content: ' ';
149
+ display: flex;
150
+ border-bottom: 2px solid var(--node-resize-outline);
151
+ height: 0.375rem;
152
+ }
153
+ [part=south]:hover::before {
154
+ border-color: var(--node-resize-hover);
155
+ }
156
+ [part=southEast] {
157
+ width: 0.5rem;
158
+ height: 0.5rem;
159
+ grid-column: 3;
160
+ grid-row: 3;
161
+ padding-right: 0.25rem;
162
+ padding-bottom: 0.25rem;
163
+ pointer-events: all;
164
+ }
165
+ [part=southEast]::before {
166
+ content: ' ';
167
+ display: flex;
168
+ border-bottom-right-radius: var(--resize-radius);
169
+ border-bottom: 2px solid var(--node-resize-outline);
170
+ border-right: 2px solid var(--node-resize-outline);
171
+ width: 0.375rem;
172
+ height: 0.375rem;
173
+ }
174
+ [part=southEast]:hover::before {
175
+ border-color: var(--node-resize-hover);
176
+ }
177
+
178
+ [part=northWest].stop::before,
179
+ [part=north].stop::before,
180
+ [part=northEast].stop::before,
181
+ [part=west].stop::before,
182
+ [part=east].stop::before,
183
+ [part=southWest].stop::before,
184
+ [part=south].stop::before,
185
+ [part=southEast].stop::before {
186
+ border-color: red;
187
+ }
@@ -0,0 +1,9 @@
1
+ <div part="header"></div>
2
+ <div part="northWest"></div>
3
+ <div part="north"></div>
4
+ <div part="northEast"></div>
5
+ <div part="west"></div>
6
+ <div part="east"></div>
7
+ <div part="southWest"></div>
8
+ <div part="south"></div>
9
+ <div part="southEast"></div>