@pictogrammers/components 0.5.15 → 0.5.18

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 (149) 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/inputPill/inputPill.html +6 -0
  13. package/pg/inputPill/inputPill.html.tmp.26852.84ac63f4c46f +6 -0
  14. package/pg/inputPill/inputPill.ts +41 -0
  15. package/pg/inputPills/__examples__/basic/basic.html +9 -0
  16. package/pg/inputPills/__examples__/basic/basic.html.tmp.26852.4771e4cad5b7 +9 -0
  17. package/pg/inputPills/__examples__/basic/basic.ts.tmp.26852.d8fe41df18bd +0 -0
  18. package/pg/inputPixelEditor/README.md +104 -69
  19. package/pg/inputPixelEditor/README.md.tmp.42596.658de5f0a6f5 +0 -0
  20. package/pg/inputPixelEditor/__examples__/basic/basic.ts +6 -5
  21. package/pg/inputPixelEditor/inputPixelEditor.ts +96 -24
  22. package/pg/inputPixelEditor/inputPixelEditor.ts.tmp.38516.96529dd82670 +1703 -0
  23. package/pg/inputPixelEditor/inputPixelEditor.ts.tmp.42596.48ecc3ad9c72 +1724 -0
  24. package/pg/inputPixelEditor/utils/bitmapToMask.ts +113 -79
  25. package/pg/inputPixelEditor/utils/getLassoPixels.ts +51 -0
  26. package/pg/inputSelect/inputSelect.ts +5 -1
  27. package/pg/inputText/README.md +6 -6
  28. package/pg/inputText/__examples__/basic/basic.ts +1 -1
  29. package/pg/inputText/inputText.css +2 -2
  30. package/pg/inputText copy/README.md +38 -0
  31. package/pg/inputText copy/inputText.css +41 -0
  32. package/pg/inputText copy/inputText.html +1 -0
  33. package/pg/inputText copy/inputText.spec.ts +59 -0
  34. package/pg/inputText copy/inputText.ts +67 -0
  35. package/pg/inputTextarea/README.md +39 -0
  36. package/pg/inputTextarea/__examples__/basic/basic.html +9 -0
  37. package/pg/inputTextarea/__examples__/basic/basic.ts +30 -0
  38. package/pg/inputTextarea/inputTextarea.ts +121 -0
  39. package/pg/inputTextarea copy/README.md +39 -0
  40. package/pg/inputTextarea copy/inputTextarea.css +41 -0
  41. package/pg/inputTextarea copy/inputTextarea.html +1 -0
  42. package/pg/inputTextarea copy/inputTextarea.spec.ts +59 -0
  43. package/pg/inputTextarea copy/inputTextarea.ts +79 -0
  44. package/pg/inputTextareaAutocomplete/README.md +70 -0
  45. package/pg/inputTextareaAutocomplete/__examples__/basic/basic.ts +67 -0
  46. package/pg/inputTextareaAutocomplete/inputTextareaAutocomplete.ts +267 -0
  47. package/pg/node/README.md +35 -0
  48. package/pg/node/__examples__/basic/basic.ts +34 -0
  49. package/pg/node/index.ts +3 -0
  50. package/pg/node/node.css +53 -0
  51. package/pg/node/node.html +5 -0
  52. package/pg/node/node.ts +270 -0
  53. package/pg/nodeEditorLink/nodeEditorLink.css +69 -0
  54. package/pg/nodeEditorLink/nodeEditorLink.css.tmp.1408.09e961fd0dcc +69 -0
  55. package/pg/nodeEditorLink/nodeEditorLink.html +9 -0
  56. package/pg/nodeEditorNumber/nodeEditorNumber.css +59 -0
  57. package/pg/nodeEditorNumber/nodeEditorNumber.ts +57 -0
  58. package/pg/nodeEditorRange/nodeEditorRange.css +23 -0
  59. package/pg/nodeEditorRange/nodeEditorRange.ts +69 -0
  60. package/pg/nodeEditorText/__examples__/basic/basic.html +9 -0
  61. package/pg/nodeEditorText/__examples__/basic/basic.ts +30 -0
  62. package/pg/nodeEditorText/nodeEditorText.css +17 -0
  63. package/pg/nodeEditorText/nodeEditorText.html +4 -0
  64. package/pg/nodeEditorText/nodeEditorText.ts +57 -0
  65. package/pg/nodeEditorText copy/README.md +35 -0
  66. package/pg/nodeEditorText copy/nodeEditorText.css +49 -0
  67. package/pg/nodeEditorText copy/nodeEditorText.html +4 -0
  68. package/pg/nodeEditorText copy/nodeEditorText.ts +50 -0
  69. package/pg/nodeEditorTextArray/nodeEditorTextArray.css +68 -0
  70. package/pg/nodeEditorTextArray/nodeEditorTextArray.html +9 -0
  71. package/pg/nodeEditorTextArray/nodeEditorTextArray.ts +162 -0
  72. package/pg/nodeEntry/nodeEntry.css +61 -0
  73. package/pg/nodeEntry/nodeEntry.ts +164 -0
  74. package/pg/nodeEntry/nodeEntry.ts.tmp.28724.dda67762bca5 +164 -0
  75. package/pg/nodeInputText/nodeInputText.css +75 -0
  76. package/pg/nodeInputText/nodeInputText.html +4 -0
  77. package/pg/nodeInputText/nodeInputText.ts +105 -0
  78. package/pg/nodeOutput/nodeOutput.css +32 -0
  79. package/pg/nodeResize/README.md +3 -0
  80. package/pg/nodeResize/THINKING.md +81 -0
  81. package/pg/nodeResize/THINKING.md.tmp.24812.99a70ba81062 +81 -0
  82. package/pg/nodeResize/basic/basic.ts +17 -0
  83. package/pg/nodeResize/dragUtil.ts +105 -0
  84. package/pg/nodeResize/dragUtil.ts.tmp.34372.787464c73309 +75 -0
  85. package/pg/nodeResize/nodeResize.css +187 -0
  86. package/pg/nodeResize/nodeResize.html +9 -0
  87. package/pg/nodeResize/nodeResize.ts +156 -0
  88. package/pg/nodes/README.md +177 -0
  89. package/pg/nodes/__examples__/basic/basic.html +33 -0
  90. package/pg/nodes/__examples__/basic/basic.ts +716 -0
  91. package/pg/nodes/__examples__/basic/exampleNodes.ts +129 -0
  92. package/pg/nodes/__examples__/basic/exampleScripts.ts +129 -0
  93. package/pg/nodes/__examples__/basic/keystore.ts +38 -0
  94. package/pg/nodes/executeInSandbox.ts +32 -0
  95. package/pg/nodes/index.ts +3 -0
  96. package/pg/nodes/nodes.css +69 -0
  97. package/pg/nodes/nodes.html +6 -0
  98. package/pg/nodes/nodes.ts +1240 -0
  99. package/pg/nodes copy/nodes.css +41 -0
  100. package/pg/nodes copy/nodes.html +1 -0
  101. package/pg/overlayContextMenu/__examples__/filter/filter.ts +87 -0
  102. package/pg/overlayContextMenu/overlayContextMenu.css +14 -0
  103. package/pg/overlayContextMenu/overlayContextMenu.html +4 -3
  104. package/pg/overlayContextMenu/overlayContextMenu.ts +6 -2
  105. package/pg/overlayContextMenu/overlayContextMenu.ts.tmp.37088.530ff1c0a321 +0 -0
  106. package/pg/overlayMenu/overlayMenu.css +3 -1
  107. package/pg/overlayMenu/overlayMenu.ts +11 -2
  108. package/pg/overlayMenu/overlayMenu.ts.tmp.44252.9bb22fd59ede +80 -0
  109. package/pg/overlaySelectMenu/overlaySelectMenu.css +2 -0
  110. package/pg/overlaySubMenu/overlaySubMenu.css +8 -6
  111. package/pg/poco/README.md +22 -0
  112. package/pg/poco/__examples__/basic/basic.html +11 -0
  113. package/pg/poco/__examples__/basic/basic.ts +95 -0
  114. package/pg/poco/__examples__/basic/examples.ts +258 -0
  115. package/pg/poco/__examples__/basic/myFont.ts +1300 -0
  116. package/pg/poco/index.ts +3 -0
  117. package/pg/poco/mockPoco.ts +640 -0
  118. package/pg/poco/poco.css +7 -0
  119. package/pg/poco/poco.html +1 -0
  120. package/pg/poco/poco.ts +75 -0
  121. package/pg/popover/README.md +47 -0
  122. package/pg/popover/__examples__/basic/basic.html +3 -0
  123. package/pg/popover/popover.html +3 -0
  124. package/pg/popover/popover.ts +44 -0
  125. package/pg/popoverAutocomplete/__examples__/basic/basic.html +4 -0
  126. package/pg/popoverAutocomplete/__examples__/basic/basic.ts +46 -0
  127. package/pg/popoverAutocomplete/popoverAutocomplete.css +25 -0
  128. package/pg/popoverAutocomplete/popoverAutocomplete.html +1 -0
  129. package/pg/popoverAutocomplete/popoverAutocomplete.ts +134 -0
  130. package/pg/popoverAutocompleteItem/popoverAutocompleteItem.css +69 -0
  131. package/pg/popoverAutocompleteItem/popoverAutocompleteItem.ts +90 -0
  132. package/pg/table/__examples__/basic/basic.ts +1 -0
  133. package/pg/tableCellFileLocal/tableCellFileLocal.html +3 -0
  134. package/pg/tableCellFileLocal/tableCellFileLocal.html.tmp.20484.68ee2e30710e +3 -0
  135. package/pg/tableCellFileLocal/tableCellFileLocal.ts +44 -0
  136. package/pg/tableCellSelect/tableCellSelect.html +3 -0
  137. package/pg/tableCellSelect/tableCellSelect.html.tmp.41808.99c1a6f14340 +3 -0
  138. package/pg/tableCellSelect/tableCellSelect.ts +51 -0
  139. package/pg/tree/__examples__/basic/basic.ts +12 -5
  140. package/pg/tree/tree.html +1 -1
  141. package/pg/tree/tree.ts +385 -227
  142. package/pg/tree/tree.ts.tmp.38376.07d376a04f41 +343 -0
  143. package/pg/tree/tree.ts.tmp.38376.2a04599ade9f +361 -0
  144. package/pg/tree/tree.ts.tmp.38376.6cb2c663d476 +0 -0
  145. package/pg/tree/tree.ts.tmp.38376.ac5af1aff2b0 +393 -0
  146. package/pg/tree/tree.ts.tmp.38376.ac9acfc4f95f +324 -0
  147. package/pg/treeItem/treeItem.ts +536 -558
  148. package/pg/treeItem/treeItem.ts.tmp.38376.145d3121d648 +530 -0
  149. package/pg/treeItem/treeItem.ts.tmp.38376.b5224d48e5c0 +0 -0
@@ -13,7 +13,7 @@ interface Options {
13
13
  scale?: number;
14
14
  offsetX?: number;
15
15
  offsetY?: number;
16
- include?: number[];
16
+ include?: number[][];
17
17
  }
18
18
 
19
19
  export function toIndex(x: number, y: number, width: number) {
@@ -21,10 +21,10 @@ export function toIndex(x: number, y: number, width: number) {
21
21
  }
22
22
 
23
23
  /**
24
- * Convert a 2d array to a SVG path.
24
+ * Convert a 2d array to SVG paths.
25
25
  * @param data
26
26
  * @param options
27
- * @returns path string
27
+ * @returns array of path strings, one per include grouping
28
28
  */
29
29
  export default function bitmaskToPath(data: number[] | number[][], options: Options = {}) {
30
30
 
@@ -34,10 +34,10 @@ export default function bitmaskToPath(data: number[] | number[][], options: Opti
34
34
  scale = 1,
35
35
  offsetX = 0,
36
36
  offsetY = 0,
37
- include = [1];
37
+ include: number[][] = [[1]];
38
38
 
39
39
  if (options.width) {
40
- bitmask = data as number[]; // already flat
40
+ bitmask = data as number[];
41
41
  width = options.width;
42
42
  height = bitmask.length / width;
43
43
  if (height % 1 !== 0) {
@@ -67,28 +67,38 @@ export default function bitmaskToPath(data: number[] | number[][], options: Opti
67
67
  include = options.include;
68
68
  }
69
69
 
70
- // Naively copy into a new bitmask with a border of 1 to make sampling easier (no out of bounds checks)
70
+ const groupCount = include.length;
71
71
  const newWidth = width + 2;
72
72
  const newHeight = height + 2;
73
- const bm = Array(newWidth * newHeight).fill(0);
74
73
 
75
- // BM is just shifted over (1, 1) for the padding
76
74
  function BMXYToIndex(x: number, y: number) {
77
75
  return (y + 1) * newWidth + (x + 1);
78
76
  }
79
77
 
78
+ // Build value → group bitmask lookup
79
+ const valueToGroupBits = new Map<number, number>();
80
+ for (let g = 0; g < groupCount; ++g) {
81
+ for (const val of include[g]) {
82
+ valueToGroupBits.set(val, (valueToGroupBits.get(val) ?? 0) | (1 << g));
83
+ }
84
+ }
85
+
86
+ // Single pass: build padded cellMask where each cell stores a bitfield of group membership
87
+ const cellMask = new Int32Array(newWidth * newHeight);
80
88
  for (let y = 0; y < height; ++y) {
81
89
  for (let x = 0; x < width; ++x) {
82
- bm[BMXYToIndex(x, y)] = include.includes(bitmask[toIndex(x, y, width)]) ? 1 : 0;
90
+ const bits = valueToGroupBits.get(bitmask[toIndex(x, y, width)]) ?? 0;
91
+ if (bits) {
92
+ cellMask[BMXYToIndex(x, y)] = bits;
93
+ }
83
94
  }
84
95
  }
85
96
 
86
- // Edges data structure has [x, y, nextEdge, group]
97
+ // Allocate per-group edge structures upfront
87
98
  const edgeXCount = width * (height + 1);
88
99
  const edgeYCount = (width + 1) * height;
89
100
  const edgeCount = edgeXCount + edgeYCount;
90
101
 
91
- const edges = Array(edgeCount).fill(0).map(() => ({ x: 0, y: 0, next: undefined })) as Edge[];
92
102
  function EdgeXIndex(x: number, y: number) {
93
103
  return y * width + x;
94
104
  }
@@ -96,121 +106,145 @@ export default function bitmaskToPath(data: number[] | number[][], options: Opti
96
106
  return edgeXCount + y * (width + 1) + x;
97
107
  }
98
108
 
99
- const groups = new Set<Edge>();
109
+ const allEdges: Edge[][] = [];
110
+ const allContours: Set<Edge>[] = [];
111
+ for (let g = 0; g < groupCount; ++g) {
112
+ allEdges.push(Array(edgeCount).fill(0).map(() => ({ x: 0, y: 0, next: undefined })) as Edge[]);
113
+ allContours.push(new Set<Edge>());
114
+ }
100
115
 
101
- function SetEdge(edge: Edge, x: number, y: number) {
116
+ // Helper: check group membership via cellMask bit
117
+ function isSet(x: number, y: number, bit: number) {
118
+ return (cellMask[BMXYToIndex(x, y)] & bit) !== 0;
119
+ }
120
+
121
+ function SetEdge(contours: Set<Edge>, edge: Edge, x: number, y: number) {
102
122
  edge.x = x;
103
123
  edge.y = y;
104
- groups.add(edge);
124
+ contours.add(edge);
105
125
  }
106
126
 
107
- function UnionGroup(edge: Edge) {
127
+ function UnionGroup(contours: Set<Edge>, edge: Edge) {
108
128
  for (var itr = edge.next; itr !== undefined && itr !== edge; itr = itr.next) {
109
- groups.delete(itr);
129
+ contours.delete(itr);
110
130
  }
111
131
  if (itr !== undefined) {
112
- groups.add(edge);
132
+ contours.add(edge);
113
133
  }
114
134
  }
115
135
 
136
+ // Single pass edge detection for all groups
116
137
  for (let y = 0; y < height; ++y) {
117
138
  for (let x = 0; x < width; ++x) {
118
- if (bm[BMXYToIndex(x, y)] === 1) {
119
- const left = bm[BMXYToIndex(x - 1, y)];
120
- if (left == 0) {
139
+ const myMask = cellMask[BMXYToIndex(x, y)];
140
+ if (myMask === 0) continue;
141
+
142
+ for (let g = 0; g < groupCount; ++g) {
143
+ const groupBit = 1 << g;
144
+ if ((myMask & groupBit) === 0) continue;
145
+
146
+ const edges = allEdges[g];
147
+ const contours = allContours[g];
148
+
149
+ if (!isSet(x - 1, y, groupBit)) {
121
150
  const edge = edges[EdgeYIndex(x, y)];
122
- SetEdge(edge, x, y + 1);
123
- if (bm[BMXYToIndex(x - 1, y - 1)]) {
151
+ SetEdge(contours, edge, x, y + 1);
152
+ if (isSet(x - 1, y - 1, groupBit)) {
124
153
  edge.next = edges[EdgeXIndex(x - 1, y)];
125
- } else if (bm[BMXYToIndex(x, y - 1)]) {
154
+ } else if (isSet(x, y - 1, groupBit)) {
126
155
  edge.next = edges[EdgeYIndex(x, y - 1)];
127
156
  } else {
128
157
  edge.next = edges[EdgeXIndex(x, y)];
129
158
  }
130
- UnionGroup(edge);
159
+ UnionGroup(contours, edge);
131
160
  }
132
- const right = bm[BMXYToIndex(x + 1, y)];
133
- if (right === 0) {
161
+ if (!isSet(x + 1, y, groupBit)) {
134
162
  const edge = edges[EdgeYIndex(x + 1, y)];
135
- SetEdge(edge, x + 1, y);
136
- if (bm[BMXYToIndex(x + 1, y + 1)]) {
163
+ SetEdge(contours, edge, x + 1, y);
164
+ if (isSet(x + 1, y + 1, groupBit)) {
137
165
  edge.next = edges[EdgeXIndex(x + 1, y + 1)];
138
- } else if (bm[BMXYToIndex(x, y + 1)]) {
166
+ } else if (isSet(x, y + 1, groupBit)) {
139
167
  edge.next = edges[EdgeYIndex(x + 1, y + 1)];
140
168
  } else {
141
169
  edge.next = edges[EdgeXIndex(x, y + 1)];
142
170
  }
143
- UnionGroup(edge);
171
+ UnionGroup(contours, edge);
144
172
  }
145
- const top = bm[BMXYToIndex(x, y - 1)];
146
- if (top === 0) {
147
- const edge: Edge = edges[EdgeXIndex(x, y)];
148
- SetEdge(edge, x, y);
149
- if (bm[BMXYToIndex(x + 1, y - 1)]) {
173
+ if (!isSet(x, y - 1, groupBit)) {
174
+ const edge = edges[EdgeXIndex(x, y)];
175
+ SetEdge(contours, edge, x, y);
176
+ if (isSet(x + 1, y - 1, groupBit)) {
150
177
  edge.next = edges[EdgeYIndex(x + 1, y - 1)];
151
- } else if (bm[BMXYToIndex(x + 1, y)]) {
178
+ } else if (isSet(x + 1, y, groupBit)) {
152
179
  edge.next = edges[EdgeXIndex(x + 1, y)];
153
180
  } else {
154
181
  edge.next = edges[EdgeYIndex(x + 1, y)];
155
182
  }
156
- UnionGroup(edge);
183
+ UnionGroup(contours, edge);
157
184
  }
158
- const bottom = bm[BMXYToIndex(x, y + 1)];
159
- if (bottom === 0) {
185
+ if (!isSet(x, y + 1, groupBit)) {
160
186
  const edge = edges[EdgeXIndex(x, y + 1)];
161
- SetEdge(edge, x + 1, y + 1);
162
- if (bm[BMXYToIndex(x - 1, y + 1)]) {
187
+ SetEdge(contours, edge, x + 1, y + 1);
188
+ if (isSet(x - 1, y + 1, groupBit)) {
163
189
  edge.next = edges[EdgeYIndex(x, y + 1)];
164
- } else if (bm[BMXYToIndex(x - 1, y)]) {
190
+ } else if (isSet(x - 1, y, groupBit)) {
165
191
  edge.next = edges[EdgeXIndex(x - 1, y + 1)];
166
192
  } else {
167
193
  edge.next = edges[EdgeYIndex(x, y)];
168
194
  }
169
- UnionGroup(edge);
195
+ UnionGroup(contours, edge);
170
196
  }
171
197
  }
172
198
  }
173
199
  }
174
200
 
175
- for (const edge of groups) {
176
- let itr = edge;
177
- do {
178
- if (itr.next) {
179
- itr.next.type = itr.x == itr?.next?.x ? 'V' : 'H';
180
- itr = itr.next;
181
- }
182
- } while (itr !== edge);
183
- }
184
-
185
- // Compress sequences of H and V
186
- for (let edge of groups) {
187
- let itr = edge;
188
- do {
189
- if (itr.type != itr.next?.type) {
190
- while (itr.next?.type == itr.next?.next?.type) {
191
- if (itr.next === edge) {
192
- groups.delete(edge);
193
- edge = itr.next.next as Edge;
194
- groups.add(edge); // Note this will cause it to iterate over this group again, meh.
201
+ // Per-group post-processing: type assignment, compression, path building
202
+ const paths: string[] = [];
203
+
204
+ for (let g = 0; g < groupCount; ++g) {
205
+ const contours = allContours[g];
206
+
207
+ for (const edge of contours) {
208
+ let itr = edge;
209
+ do {
210
+ if (itr.next) {
211
+ itr.next.type = itr.x == itr?.next?.x ? 'V' : 'H';
212
+ itr = itr.next;
213
+ }
214
+ } while (itr !== edge);
215
+ }
216
+
217
+ for (let edge of contours) {
218
+ let itr = edge;
219
+ do {
220
+ if (itr.type != itr.next?.type) {
221
+ while (itr.next?.type == itr.next?.next?.type) {
222
+ if (itr.next === edge) {
223
+ contours.delete(edge);
224
+ edge = itr.next.next as Edge;
225
+ contours.add(edge);
226
+ }
227
+ itr.next = itr.next?.next;
195
228
  }
196
- itr.next = itr.next?.next;
229
+ }
230
+ itr = itr.next as Edge;
231
+ } while (itr !== edge);
232
+ }
233
+
234
+ let path = '';
235
+ for (const edge of contours) {
236
+ path += `M${edge.x * scale},${edge.y * scale}`;
237
+ for (var itr = edge.next; itr != edge; itr = itr?.next) {
238
+ if (itr?.type == 'H') {
239
+ path += `H${(itr?.x * scale) + offsetX}`;
240
+ } else if (itr?.type == 'V') {
241
+ path += `V${(itr?.y * scale) + offsetY}`;
197
242
  }
198
243
  }
199
- itr = itr.next as Edge;
200
- } while (itr !== edge);
201
- }
202
-
203
- let path = '';
204
- for (const edge of groups) {
205
- path += `M${edge.x * scale},${edge.y * scale}`;
206
- for (var itr = edge.next; itr != edge; itr = itr?.next) {
207
- if (itr?.type == 'H') {
208
- path += `H${(itr?.x * scale) + offsetX}`;
209
- } else if (itr?.type == 'V') {
210
- path += `V${(itr?.y * scale) + offsetY}`;
211
- }
244
+ path += 'Z';
212
245
  }
213
- path += 'Z';
246
+ paths.push(path);
214
247
  }
215
- return path;
248
+
249
+ return paths;
216
250
  }
@@ -0,0 +1,51 @@
1
+ import getLinePixels from './getLinePixels';
2
+ import { Pixel } from './constants';
3
+
4
+ /**
5
+ * Returns all pixels inside (and on the border of) a freehand lasso path.
6
+ *
7
+ * The path is a sequence of grid-cell coordinates visited during the drag.
8
+ * Consecutive points are connected with Bresenham lines to form the outline,
9
+ * and the interior is filled with an even-odd scanline rule.
10
+ */
11
+ export default function getLassoPixels(path: [number, number][]): Pixel[] {
12
+ if (path.length < 3) { return []; }
13
+
14
+ const result = new Map<string, Pixel>();
15
+ const n = path.length;
16
+
17
+ // Outline: Bresenham lines between consecutive path points, closing back to start
18
+ for (let i = 0; i < n; i++) {
19
+ const [x0, y0] = path[i];
20
+ const [x1, y1] = path[(i + 1) % n];
21
+ for (const { x, y } of getLinePixels(x0, y0, x1, y1)) {
22
+ result.set(`${x},${y}`, { x, y });
23
+ }
24
+ }
25
+
26
+ // Scanline interior fill — test at row + 0.5 to avoid vertex ambiguities
27
+ const minY = Math.min(...path.map(([, y]) => y));
28
+ const maxY = Math.max(...path.map(([, y]) => y));
29
+
30
+ for (let row = minY; row <= maxY; row++) {
31
+ const intersections: number[] = [];
32
+ for (let i = 0; i < n; i++) {
33
+ const [x0, y0] = path[i];
34
+ const [x1, y1] = path[(i + 1) % n];
35
+ if ((y0 <= row && y1 > row) || (y1 <= row && y0 > row)) {
36
+ const t = (row + 0.5 - y0) / (y1 - y0);
37
+ intersections.push(x0 + t * (x1 - x0));
38
+ }
39
+ }
40
+ intersections.sort((a, b) => a - b);
41
+ for (let i = 0; i + 1 < intersections.length; i += 2) {
42
+ const xStart = Math.ceil(intersections[i]);
43
+ const xEnd = Math.floor(intersections[i + 1]);
44
+ for (let x = xStart; x <= xEnd; x++) {
45
+ result.set(`${x},${row}`, { x, y: row });
46
+ }
47
+ }
48
+ }
49
+
50
+ return Array.from(result.values());
51
+ }
@@ -40,6 +40,10 @@ export default class PgInputSelect extends HTMLElement {
40
40
 
41
41
  #menuOpen = false;
42
42
  async #handleClick() {
43
+ if (this.options.length === 0) {
44
+ // No options
45
+ return;
46
+ }
43
47
  if (this.#menuOpen) { return; }
44
48
  this.#menuOpen = true;
45
49
  const result = await PgOverlaySelectMenu.open({
@@ -48,7 +52,7 @@ export default class PgInputSelect extends HTMLElement {
48
52
  value: this.options.find(x => x.value === this.value) ?? null,
49
53
  items: this.options,
50
54
  });
51
- if (result !== undefined) {
55
+ if (result && result.value !== null) {
52
56
  this.dispatchEvent(new CustomEvent('change', {
53
57
  detail: {
54
58
  value: result.value,
@@ -30,9 +30,9 @@ import { PgInputText } from '@pictogrammers/components/pgInputText';
30
30
 
31
31
  | CSS Variables | Default | Description |
32
32
  | ------------------- | --------- | ----------- |
33
- | `--pg-button-padding-inline` | `0.5rem` | Padding inline. |
34
- | `--pg-button-padding-inline-start` | `0.5rem` | Padding inline start. |
35
- | `--pg-button-padding-inline-end` | `0.5rem` | Padding inline end. |
36
- | `--pg-button-padding-block` | `0.25rem` | Padding block. |
37
- | `--pg-button-padding-block-start` | `0.25rem` | Padding block start. |
38
- | `--pg-button-padding-block-end` | `0.25rem` | Padding block end. |
33
+ | `--pg-input-text-padding-inline` | `0.5rem` | Padding inline. |
34
+ | `--pg-input-text-padding-inline-start` | `0.5rem` | Padding inline start. |
35
+ | `--pg-input-text-padding-inline-end` | `0.5rem` | Padding inline end. |
36
+ | `--pg-input-text-padding-block` | `0.25rem` | Padding block. |
37
+ | `--pg-input-text-padding-block-start` | `0.25rem` | Padding block start. |
38
+ | `--pg-input-text-padding-block-end` | `0.25rem` | Padding block end. |
@@ -5,7 +5,7 @@ import template from './basic.html';
5
5
 
6
6
  @Component({
7
7
  selector: 'x-pg-input-text-basic',
8
- template
8
+ template,
9
9
  })
10
10
  export default class XPgInputTextBasic extends HTMLElement {
11
11
 
@@ -5,7 +5,7 @@
5
5
 
6
6
  [part=input] {
7
7
  border: 1px solid var(--pg-input-text-border-color, #453C4F);
8
- border-radius: 0.125rem;
8
+ border-radius: var(--pg-input-border-radius, 0.125rem);
9
9
  padding-inline-start: var(--pg-input-text-padding-inline-start, var(--pg-input-text-padding-inline, 0.5rem));
10
10
  padding-inline-end: var(--pg-input-text-padding-inline-end, var(--pg-input-text-padding-inline, 0.5rem));
11
11
  padding-block-start: var(--pg-input-text-padding-block-start, var(--pg-input-text-padding-block, 0.25rem));
@@ -26,7 +26,7 @@
26
26
  - var(--pg-input-text-padding-inline-end, var(--pg-input-text-padding-inline, 0.5rem))
27
27
  - 2px
28
28
  );
29
- background-color: transparent;
29
+ background-color: #FFF;
30
30
  }
31
31
 
32
32
  [part=input]:read-only {
@@ -0,0 +1,38 @@
1
+ # `<pg-input-text>`
2
+
3
+ The `pg-input-text` component creates an input that accepts text input.
4
+
5
+ ```typescript
6
+ import '@pictogrammers/components/pgInputText';
7
+ import { PgInputText } from '@pictogrammers/components/pgInputText';
8
+ ```
9
+
10
+ ```html
11
+ <pg-input-text value="50"></pg-input-text>
12
+ ```
13
+
14
+ ## Attributes
15
+
16
+ | Attributes | Tested | Description |
17
+ | ----------- | -------- | ----------- |
18
+ | name | | Unique name in `pg-form` |
19
+ | value | | Field value |
20
+ | placeholder | | Placeholder text |
21
+
22
+ ## Events
23
+
24
+ | Events | Tested | Description |
25
+ | ---------- | -------- | ----------- |
26
+ | change | | `{ detail: { value }` |
27
+ | input | | `{ detail: { value }` |
28
+
29
+ ## CSS Variables
30
+
31
+ | CSS Variables | Default | Description |
32
+ | ------------------- | --------- | ----------- |
33
+ | `--pg-button-padding-inline` | `0.5rem` | Padding inline. |
34
+ | `--pg-button-padding-inline-start` | `0.5rem` | Padding inline start. |
35
+ | `--pg-button-padding-inline-end` | `0.5rem` | Padding inline end. |
36
+ | `--pg-button-padding-block` | `0.25rem` | Padding block. |
37
+ | `--pg-button-padding-block-start` | `0.25rem` | Padding block start. |
38
+ | `--pg-button-padding-block-end` | `0.25rem` | Padding block end. |
@@ -0,0 +1,41 @@
1
+ :host {
2
+ display: block;
3
+ font-family: var(--pg-font-family, system-ui);
4
+ }
5
+
6
+ [part=input] {
7
+ border: 1px solid var(--pg-input-text-border-color, #453C4F);
8
+ border-radius: 0.125rem;
9
+ padding-inline-start: var(--pg-input-text-padding-inline-start, var(--pg-input-text-padding-inline, 0.5rem));
10
+ padding-inline-end: var(--pg-input-text-padding-inline-end, var(--pg-input-text-padding-inline, 0.5rem));
11
+ padding-block-start: var(--pg-input-text-padding-block-start, var(--pg-input-text-padding-block, 0.25rem));
12
+ padding-block-end: var(--pg-input-text-padding-block-start, var(--pg-input-text-padding-block, 0.25rem));
13
+ font-family: var(--pg-input-text-font-family, var(--pg-font-family));
14
+ font-size: 1rem;
15
+ outline: none;
16
+ field-sizing: content;
17
+ min-width: calc(
18
+ 100%
19
+ - var(--pg-input-text-padding-inline-start, var(--pg-input-text-padding-inline, 0.5rem))
20
+ - var(--pg-input-text-padding-inline-end, var(--pg-input-text-padding-inline, 0.5rem))
21
+ - 2px
22
+ );
23
+ max-width: calc(
24
+ var(--pg-input-text-max-width, 100%)
25
+ - var(--pg-input-text-padding-inline-start, var(--pg-input-text-padding-inline, 0.5rem))
26
+ - var(--pg-input-text-padding-inline-end, var(--pg-input-text-padding-inline, 0.5rem))
27
+ - 2px
28
+ );
29
+ background-color: transparent;
30
+ }
31
+
32
+ [part=input]:read-only {
33
+ border-color: transparent;
34
+ }
35
+
36
+ [part=input]:active {
37
+ box-shadow: 0 0 0 3px var(--pg-input-text-active-glow, rgb(79, 143, 249, 0.6));
38
+ }
39
+ [part=input]:focus {
40
+ box-shadow: 0 0 0 3px var(--pg-input-text-focus-glow, rgb(79, 143, 249, 0.5));
41
+ }
@@ -0,0 +1 @@
1
+ <input part="input" type="text" />
@@ -0,0 +1,59 @@
1
+ import { selectComponent, getProps } from '@pictogrammers/element';
2
+
3
+ import './inputText';
4
+ import PgInputText from './inputText';
5
+
6
+ const PG_INPUT_TEXT = 'pg-input-text';
7
+
8
+ describe('pg-input-text', () => {
9
+
10
+ beforeEach(() => {
11
+ var c = document.createElement(PG_INPUT_TEXT);
12
+ document.body.appendChild(c);
13
+ });
14
+
15
+ afterEach(() => {
16
+ while (document.body.firstChild) {
17
+ document.body.removeChild(document.body.firstChild);
18
+ }
19
+ });
20
+
21
+ it('should be registered', () => {
22
+ expect(customElements.get(PG_INPUT_TEXT)).toBeDefined();
23
+ });
24
+
25
+ it('should only expose known props', () => {
26
+ const props = getProps(PG_INPUT_TEXT);
27
+ expect(props.length).toBe(3);
28
+ expect(props).toContain('name');
29
+ expect(props).toContain('value');
30
+ expect(props).toContain('placeholder');
31
+ });
32
+
33
+ it('should default value to empty', () => {
34
+ const component = selectComponent<PgInputText>(PG_INPUT_TEXT);
35
+ const { $input } = component;
36
+ expect($input.value).toEqual('');
37
+ });
38
+
39
+ it('should set value to "Hello World!"', () => {
40
+ const component = selectComponent<PgInputText>(PG_INPUT_TEXT);
41
+ component.value = 'Hello World!';
42
+ const { $input } = component;
43
+ expect($input.value).toEqual('Hello World!');
44
+ });
45
+
46
+ it('should default placeholder to empty', () => {
47
+ const component = selectComponent<PgInputText>(PG_INPUT_TEXT);
48
+ const { $input } = component;
49
+ expect($input.placeholder).toEqual('');
50
+ });
51
+
52
+ it('should set placeholder to "Hello World!"', () => {
53
+ const component = selectComponent<PgInputText>(PG_INPUT_TEXT);
54
+ component.placeholder = 'Hello World!';
55
+ const { $input } = component;
56
+ expect($input.placeholder).toEqual('Hello World!');
57
+ });
58
+
59
+ });
@@ -0,0 +1,67 @@
1
+ import { Component, Prop, Part } from '@pictogrammers/element';
2
+
3
+ import template from './inputText.html';
4
+ import style from './inputText.css';
5
+
6
+ @Component({
7
+ selector: 'pg-input-text',
8
+ style,
9
+ template
10
+ })
11
+ export default class PgInputText extends HTMLElement {
12
+ @Prop() name: string = '';
13
+ @Prop() value: string = '';
14
+ @Prop() placeholder: string = '';
15
+ @Prop() readOnly: boolean = false;
16
+
17
+ @Part() $input: HTMLInputElement;
18
+
19
+ connectedCallback() {
20
+ this.$input.addEventListener('input', this.handleInput.bind(this));
21
+ this.$input.addEventListener('change', this.handleChange.bind(this));
22
+ }
23
+
24
+ focus() {
25
+ this.$input.focus();
26
+ }
27
+
28
+ skipValue = false;
29
+
30
+ render(changes) {
31
+ if (changes.value && !this.skipValue) {
32
+ this.$input.value = this.value;
33
+ }
34
+ if (changes.placeholder) {
35
+ this.$input.placeholder = this.placeholder;
36
+ }
37
+ if (changes.readOnly) {
38
+ this.$input.readOnly = this.readOnly;
39
+ }
40
+ this.skipValue = false;
41
+ }
42
+
43
+ handleInput(e) {
44
+ e.stopPropagation();
45
+ this.skipValue = true;
46
+ this.value = e.target.value;
47
+ this.dispatchEvent(
48
+ new CustomEvent('input', {
49
+ detail: {
50
+ value: e.target.value,
51
+ name: e.name
52
+ }
53
+ })
54
+ );
55
+ }
56
+
57
+ handleChange(e) {
58
+ this.dispatchEvent(
59
+ new CustomEvent('change', {
60
+ detail: {
61
+ value: e.target.value,
62
+ name: e.name
63
+ }
64
+ })
65
+ );
66
+ }
67
+ }
@@ -0,0 +1,39 @@
1
+ # `<pg-input-textarea>`
2
+
3
+ The `pg-input-textarea` component creates an input that accepts multiline text. Additionally it can resize.
4
+
5
+ ```typescript
6
+ import '@pictogrammers/components/pgInputTextarea';
7
+ import { PgInputTextarea } from '@pictogrammers/components/pgInputTextarea';
8
+ ```
9
+
10
+ ```html
11
+ <pg-input-textarea value="50"></pg-input-textarea>
12
+ ```
13
+
14
+ ## Attributes
15
+
16
+ | Attributes | Tested | Description |
17
+ | ----------- | -------- | ----------- |
18
+ | name | | Unique name in `pg-form` |
19
+ | value | | Field value |
20
+ | placeholder | | Placeholder text |
21
+
22
+ ## Events
23
+
24
+ | Events | Tested | Description |
25
+ | ---------- | -------- | ----------- |
26
+ | change | | `{ detail: { value }` |
27
+ | input | | `{ detail: { value }` |
28
+ | caret | | `{ detail: { column, row }}` |
29
+
30
+ ## CSS Variables
31
+
32
+ | CSS Variables | Default | Description |
33
+ | ------------------- | --------- | ----------- |
34
+ | `--pg-input-textarea-padding-inline` | `0.5rem` | Padding inline. |
35
+ | `--pg-input-textarea-padding-inline-start` | `0.5rem` | Padding inline start. |
36
+ | `--pg-input-textarea-padding-inline-end` | `0.5rem` | Padding inline end. |
37
+ | `--pg-input-textarea-padding-block` | `0.25rem` | Padding block. |
38
+ | `--pg-input-textarea-padding-block-start` | `0.25rem` | Padding block start. |
39
+ | `--pg-input-textarea-padding-block-end` | `0.25rem` | Padding block end. |
@@ -0,0 +1,9 @@
1
+ <div class="example">
2
+ <pg-input-textarea part="input" value="Hello World!"></pg-input-textarea>
3
+ <div>
4
+ <code>onchange: <span part="value1"></span></code>
5
+ </div>
6
+ <div>
7
+ <code>oninput: <span part="value2"></span></code>
8
+ </div>
9
+ </div>