@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
@@ -0,0 +1,41 @@
1
+ import { Component, Prop, Part } from '@pictogrammers/element';
2
+
3
+ import template from './inputPill.html';
4
+ import style from './inputPill.css';
5
+
6
+ @Component({
7
+ selector: 'pg-input-pill',
8
+ style,
9
+ template,
10
+ })
11
+ export default class PgInputPill extends HTMLElement {
12
+ @Prop() label: string = '';
13
+ @Prop() value: string = '';
14
+
15
+ @Part() $label: HTMLSpanElement;
16
+ @Part() $remove: HTMLButtonElement;
17
+
18
+ #initialized: boolean = false;
19
+
20
+ connectedCallback() {
21
+ // Reconnects re-invoke connectedCallback; guard against duplicate listeners.
22
+ if (this.#initialized) return;
23
+ this.#initialized = true;
24
+ this.$remove.addEventListener('click', () => {
25
+ this.dispatchEvent(
26
+ new CustomEvent('remove', {
27
+ detail: {
28
+ value: this.value,
29
+ label: this.label,
30
+ }
31
+ })
32
+ );
33
+ });
34
+ }
35
+
36
+ render(changes) {
37
+ if (changes.label) {
38
+ this.$label.textContent = this.label;
39
+ }
40
+ }
41
+ }
@@ -0,0 +1,9 @@
1
+ <div class="example">
2
+ <pg-input-pills part="input"></pg-input-pills>
3
+ <div>
4
+ <button part="add" type="button">Add Pill</button>
5
+ </div>
6
+ <div>
7
+ <code>onchange: <span part="value"></span></code>
8
+ </div>
9
+ </div>
@@ -0,0 +1,9 @@
1
+ <div class="example">
2
+ <pg-input-pills part="input"></pg-input-pills>
3
+ <div>
4
+ <button part="add" type="button">Add Pill</button>
5
+ </div>
6
+ <div>
7
+ <code>onchange: <span part="value"></span></code>
8
+ </div>
9
+ </div>
@@ -16,20 +16,23 @@ import PgInputPixelEditor, {
16
16
 
17
17
  ## Attributes
18
18
 
19
- | Attributes | Tested | Description |
20
- | ----------- | -------- | ----------- |
21
- | `name` | | Unique name in `pg-form` |
22
- | `width` | | Pixel width. Default `10` |
23
- | `height` | | Pixel height. Default `10` |
24
- | `size` | | Pixel size, minimum value `4`. Default `10` |
25
- | `gridSize` | | Grid spacing between cells. Default `1` |
19
+ | Attributes | Tested | Description |
20
+ | ------------- | -------- | ----------- |
21
+ | `width` | | Pixel width. Default `10` |
22
+ | `height` | | Pixel height. Default `10` |
23
+ | `size` | | Pixel size, minimum value `4`. Default `10` |
24
+ | `gridSize` | | Grid spacing between cells. Default `1` (changing after init does not currently re-render) |
25
+ | `transparent` | | Show checkerboard background. Default `false` |
26
+ | `placeholder` | | Placeholder text (not yet implemented) |
26
27
 
27
28
  ## Events
28
29
 
29
- | Events | Tested | Description |
30
- | ----------- | -------- | ----------- |
31
- | `change` | | `{ detail: { value }` |
32
- | `reference` | | `{ detail: { id, callback } }` |
30
+ | Events | Tested | Description |
31
+ | ------------- | -------- | ----------- |
32
+ | `change` | | `{ detail: { export } }` — debounced, fires 1 second after the last edit |
33
+ | `selectlayer` | | `{ detail: { color, index } }` — fired in cursor input mode |
34
+ | `debug` | | `{ detail: { x, y, width, height, canvas, context, baseLayer, editLayer, noEditLayer, previewLayer } }` — internal layers for debugging previews |
35
+ | `reference` | | `{ detail: { id, callback } }` (not yet implemented) |
33
36
 
34
37
  ```typescript
35
38
  this.$input.addEventListener('reference', (e: any) => {
@@ -46,59 +49,71 @@ See usage for each method below.
46
49
  | Method | Tested | Description |
47
50
  | ---------- | -------- | ----------- |
48
51
  | `getJson(options)` | - | Get JSON file. |
49
- | `getData()` | - | Get layer data. |
50
- | `setData()` | - | Set layer data. |
51
52
  | `setJson(json)` | - | Set JSON file. |
53
+ | `getData()` | - | Get layer data as paths by color. |
54
+ | `setData(data)` | - | Set layer data from paths. |
52
55
  | `reset()` | - | Reset canvas and data. |
56
+ | `clear()` | - | Clear all pixel data (deprecated, will be removed). |
57
+ | `applyTemplate(grid)` | - | Replace all data with a single-layer 2d color-index grid. |
53
58
  | `getExport()` | - | 2d array of pixel color indexes. Already calculated. |
54
- | `getExport(id)` | - | 2d array of pixel color indexes by export size. Minimal perf. |
55
- | `getExportCanvas()` | - | Get new prepopulated HTML canvas, ideal for advanced export screens. |
59
+ | `getExportPath()` | - | SVG path string(s) for the export grid. |
60
+ | `getExportLayerIndexes()` | - | Indexes of layers flagged for export. |
61
+ | `getExportCanvas(options)` | - | Get new prepopulated HTML canvas, ideal for advanced export screens. |
56
62
  | `await getExportPng(options, meta)` | - | Get png image with optional metadata. |
57
- | `undo()` | - | Undo. |
58
- | `hasUndo()` | - | Has undo |
59
- | `hasRedo()` | - | Has redo |
60
- | `redo()` | - | Redo. |
63
+ | `await getSelectionPng(options, meta)` | - | Get png of the current selection bounding box. Requires an active selection. |
64
+ | `await copyPngToClipboard(blob, data)` | - | Copy a png blob and JSON data to the clipboard. |
65
+ | `undo()` | - | Undo (partial — history is not yet recorded). |
66
+ | `hasUndo()` | - | Has undo. |
67
+ | `hasRedo()` | - | Has redo. |
68
+ | `redo()` | - | Redo (not yet implemented). |
61
69
  | `getHistory()` | - | History list. |
62
- | `inputModeSelectRectangle()` | - | Input Mode Select Rectangle |
63
- | `inputModeSelectEllipse()` | - | Input Mode Select Ellipse |
64
- | `inputModeSelectLasso()` | - | Input Mode Lasso |
65
- | `inputModeSelectMagicWand()` | - | Input Mode Magic Wand |
66
- | `inputModePixel()` | - | Input Mode Pixel |
67
- | `inputModeStamp(stamp)` | - | Input Mode Stamp |
68
- | `inputModeLine()` | - | Input Mode Line |
69
- | `inputModeRectangle()` | - | Input Mode Rectangle |
70
- | `inputModeRectangleOutline()` | - | Input Mode Rectangle Outline |
71
- | `inputModeEllipse()` | - | Input Mode Ellipse |
72
- | `inputModeEllipseOutline()` | - | Input Mode Ellipse Outline |
73
- | `selectLayer(index)` | - | Select layer. |
74
- | `getLayers()` | - | Get layer array. |
70
+ | `clearHistory()` | - | Clear undo/redo history. |
71
+ | `inputModeCursor()` | - | Input Mode Cursor click to select layer. |
72
+ | `inputModeSelectRectangle()` | - | Input Mode Select Rectangle. |
73
+ | `inputModeSelectEllipse()` | - | Input Mode Select Ellipse. |
74
+ | `inputModeSelectLasso()` | - | Input Mode Lasso. |
75
+ | `inputModeSelectMagicWand()` | - | Input Mode Magic Wand. |
76
+ | `inputModePixel()` | - | Input Mode Pixel. |
77
+ | `inputModePixelSize(size)` | - | Set pixel brush size (not yet implemented). |
78
+ | `inputModeStamp(stamp)` | - | Input Mode Stamp. Accepts a `[x, y][]` offset array; a hover preview is shown. |
79
+ | `inputModeLine()` | - | Input Mode Line. |
80
+ | `inputModeRectangle()` | - | Input Mode Rectangle. |
81
+ | `inputModeRectangleOutline()` | - | Input Mode Rectangle Outline. |
82
+ | `inputModeEllipse()` | - | Input Mode Ellipse. |
83
+ | `inputModeEllipseOutline()` | - | Input Mode Ellipse Outline. |
84
+ | `selectLayers(indexes)` | - | Select one or more layers by index array. |
85
+ | `getLayers()` | - | Get layer index array. |
75
86
  | `addLayer(option)` | - | Add layer. `name`, `type` required. |
76
- | `removeLayer(index)` | - | Remove layer. |
77
- | `flattenLayers(layerIndexes)` | - | Flatten layers. |
78
- | `getColor()` | - | Get selected color |
79
- | `getColors()` | - | Get colors. |
80
- | `getLayerColorIndexes()` | - | Get selected layer colors. |
81
- | `selectColor(index)` | - | Select color. |
82
- | `setColor(index, r, g, b, a)` | - | Set Color. |
83
- | `addColor(r, g, b, a)` | - | Add color. |
84
- | `removeColor(index)` | - | Remove color. |
85
- | `moveColor(startIndex, endIndex)` | - | Move index. |
86
- | `rotateClockwise()` | - | Rotate. |
87
- | `rotateCounterclockwise()` | - | Rotate. |
88
- | `move(x, y[, layer])` | - | Move. |
89
- | `getSelection()` | - | Get selection pixels. |
90
- | `clearSelection()` | - | Clear selection. |
91
- | `moveSelection(x, y)` | - | Move selection. |
92
- | `flipHorizontal()` | - | Flip horizontal selection. |
93
- | `flipVertical()` | - | Flip vertical selection. |
94
- | `invert()` | - | Invert layer. |
95
- | `outline()` | - | Outline layer with selected color. |
96
- | `glow()` | - | Glow layer with selected color. |
97
- | `drawPixels([[x, y, color], ...])` | - | Draw pixels. Color optional. |
98
- | `drawRectangle(x, y, width, height, isOutline)` | - | Draw rectangle |
99
- | `drawEllipse(x, y, width, height, isOutline)` | - | Draw ellipse. |
100
- | `drawLine()` | - | Draw line. |
101
- | `flush()` | - | Flush changes to history. |
87
+ | `removeLayer(index)` | - | Remove layer by index. |
88
+ | `flattenLayers(layerIndexes)` | - | Flatten layers (not yet implemented). |
89
+ | `getColor(index)` | - | Get color by index. |
90
+ | `getColors()` | - | Get all colors. |
91
+ | `getColorAt(x, y)` | - | Get the export color index at a coordinate. |
92
+ | `getLayerAt(x, y)` | - | Get the top-most layer index with a color at a coordinate, or `null`. |
93
+ | `getLayerColorIndexes(layerIndex?)` | - | Get unique color indexes on selected (or given) layer. |
94
+ | `getLayerPaths()` | - | Get SVG path strings per color per layer. |
95
+ | `selectColor(index)` | - | Set the active draw color. |
96
+ | `setColor(index, r, g, b, a)` | - | Update a color by index. |
97
+ | `addColor(r, g, b, a)` | - | Append a color. |
98
+ | `removeColor(index)` | - | Remove a color by index. |
99
+ | `moveColor(startIndex, endIndex)` | - | Move a color (not yet implemented). |
100
+ | `mergeColor(fromIndex, toIndex)` | - | Merge one color into another (not yet implemented). |
101
+ | `rotateClockwise()` | - | Rotate active layer 90° clockwise. |
102
+ | `rotateCounterclockwise()` | - | Rotate active layer 90° counter-clockwise. |
103
+ | `move(x, y)` | - | Translate active layer by x/y pixels. |
104
+ | `flipHorizontal()` | - | Flip active layer horizontally. |
105
+ | `flipVertical()` | - | Flip active layer vertically. |
106
+ | `invert()` | - | Invert active layer (2-color only). |
107
+ | `outline(include?)` | - | Draw outline around active layer with selected color. |
108
+ | `glow(include?)` | - | Draw glow (outer outline) with selected color. |
109
+ | `applyGuides()` | - | Draw guide lines onto the base layer. |
110
+ | `clearGuides()` | - | Clear guide lines (not yet implemented). |
111
+ | `drawGrid(grid, layer?)` | - | Draw a full 2d color-index grid onto a layer. |
112
+ | `drawPixel(x, y, color, layer?)` | - | Draw a single pixel. |
113
+ | `hasSelection()` | - | Returns true when pixels are selected. |
114
+ | `getSelection()` | - | Get selection pixels (not yet implemented). |
115
+ | `clearSelection()` | - | Deselect all pixels. |
116
+ | `moveSelection(x, y)` | - | Translate selection and its pixels by x/y. |
102
117
 
103
118
  ### `getJson(options)` Method
104
119
 
@@ -107,8 +122,8 @@ The `getJson` method allows getting the JSON representation of the current edito
107
122
  ```typescript
108
123
  @Part() $editor: PgInputPixelEditor;
109
124
 
110
- handleSave() {
111
- const json = await this.$editor.save({
125
+ async handleSave() {
126
+ const json = await this.$editor.getJson({
112
127
  // Include history
113
128
  history: true,
114
129
  });
@@ -118,16 +133,13 @@ handleSave() {
118
133
 
119
134
  ### `setJson(json)` Method
120
135
 
121
- The open method allows loading json for previously created images.
136
+ The `setJson` method loads a previously saved JSON file into the editor.
122
137
 
123
138
  ```typescript
124
139
  @Part() $editor: PgInputPixelEditor;
125
140
 
126
- handleOpen() {
127
- const error = await this.$editor.open(json);
128
- if (error) {
129
- throw new Error(error.message);
130
- }
141
+ async handleOpen(json) {
142
+ await this.$editor.setJson(json);
131
143
  }
132
144
  ```
133
145
 
@@ -135,6 +147,14 @@ handleOpen() {
135
147
 
136
148
  ### `change`
137
149
 
150
+ Debounced — fires 1 second after the last edit. The detail contains the cached export grid.
151
+
152
+ ```typescript
153
+ this.$input.addEventListener('change', (e: any) => {
154
+ const { export: grid } = e.detail;
155
+ });
156
+ ```
157
+
138
158
  ### `selectLayer`
139
159
 
140
160
  Using `InputMode.Cursor` individual pixels can be clicked on to select the layer. Note, selecting a layer does not automatically select it. The event must be handled.
@@ -142,10 +162,25 @@ Using `InputMode.Cursor` individual pixels can be clicked on to select the layer
142
162
  ```typescript
143
163
  this.$input.addEventListener('selectlayer', (e: any) => {
144
164
  const { color, index } = e.detail;
145
- this.selectLayers([index]);
165
+ this.$input.selectLayers([index]);
146
166
  })
147
167
  ```
148
168
 
169
+ ## Keyboard Shortcuts
170
+
171
+ The wrapper is focusable (`tabindex="0"`); shortcuts apply while the editor has focus.
172
+
173
+ | Keys | Description |
174
+ | ---- | ----------- |
175
+ | `Escape` | Clear the current selection. |
176
+ | `Delete` | Delete the pixels in the current selection. |
177
+ | `Arrow Keys` | Move the current selection by 1 pixel. |
178
+ | `Shift + Arrow Keys` | Move the current selection by 10 pixels. |
179
+ | `Ctrl + C` | Copy the selection (or the full export when nothing is selected) to the clipboard as a PNG. |
180
+ | `Ctrl + V` | Paste clipboard PNG / easel JSON (work in progress — currently logs metadata only). |
181
+ | `Ctrl + Z` | Undo. |
182
+ | `Ctrl + Shift + Z` / `Ctrl + Y` | Redo. |
183
+
149
184
  ## JSON Format
150
185
 
151
186
  - `width` - Image width.
@@ -360,7 +395,7 @@ To create a repeating 10 pixel grid with an offset `5` and limit `10`. The use c
360
395
 
361
396
  Exports by default include the entire canvas, but can be resized to a specific pixel size or grid. The `x`, `y`, `width`, and `height` are optional.
362
397
 
363
- Use `getExport(id)` with a `id` value to get the data grid. This is ideal for export previews.
398
+ Use `getExport()` to get the cached 2d color-index grid. Use `getExportCanvas(options)` with `x`, `y`, `width`, `height` to crop to a region ideal for export previews.
364
399
 
365
400
  ```json
366
401
  {
@@ -388,7 +423,7 @@ Use `getExport(id)` with a `id` value to get the data grid. This is ideal for ex
388
423
 
389
424
  The history list contains every change made to the canvas. All feature insert an entry into history. Pens and pattern tools will group as to cut down history entries.
390
425
 
391
- Note: Calling the `draw*()` commands programatically will group into a single history item. To create multiple entries call `flush()`.
426
+ Note: Calling the `draw*()` commands programmatically will group into a single history item.
392
427
 
393
428
  - Undo a change
394
429
  - Redo an undo
@@ -253,7 +253,7 @@ export default class XPgInputPixelEditorBasic extends HTMLElement {
253
253
  }
254
254
  })
255
255
  );
256
- this.$input.addLayer();
256
+ this.$input.addLayer({ name: 'layer 1', type: 'pixel' });
257
257
  });
258
258
  this.$addLayerReference.addEventListener('click', () => {
259
259
  this.$layers.data.push(
@@ -268,7 +268,7 @@ export default class XPgInputPixelEditorBasic extends HTMLElement {
268
268
  }
269
269
  })
270
270
  );
271
- this.$input.addLayer();
271
+ this.$input.addLayer({ name: 'layer 1', type: 'pixel' });
272
272
  });
273
273
  // Colors
274
274
  this.$colors.columns = [{
@@ -346,9 +346,9 @@ export default class XPgInputPixelEditorBasic extends HTMLElement {
346
346
  });
347
347
  this.$addColor.addEventListener('click', () => {
348
348
  this.$colors.data.push(createTableItem({
349
- r: 5,
350
- g: 5,
351
- b: 5,
349
+ r: 0,
350
+ g: 0,
351
+ b: 0,
352
352
  a: 1,
353
353
  selected: false,
354
354
  select: {
@@ -361,6 +361,7 @@ export default class XPgInputPixelEditorBasic extends HTMLElement {
361
361
  icon: IconTrash
362
362
  }
363
363
  }));
364
+ this.$input.addColor(0, 0, 0, 1);
364
365
  });
365
366
  }
366
367
 
@@ -9,6 +9,7 @@ import getEllipseOutlinePixels from './utils/getEllipseOutlinePixels';
9
9
  import { WHITE, Pixel } from './utils/constants';
10
10
  import getLinePixels from './utils/getLinePixels';
11
11
  import getRectanglePixels from './utils/getRectanglePixels';
12
+ import getLassoPixels from './utils/getLassoPixels';
12
13
  import getRectangleOutlinePixels from './utils/getRectangleOutlinePixels';
13
14
  import fillGrid from './utils/fillGrid';
14
15
  import iterateGrid from './utils/interateGrid';
@@ -96,6 +97,16 @@ interface Export {
96
97
  height?: number
97
98
  }
98
99
 
100
+ interface AddLayerOptions {
101
+ name: string
102
+ type: Layer['type']
103
+ exclude?: boolean
104
+ locked?: boolean
105
+ hidden?: boolean
106
+ opacity?: number
107
+ data?: any[]
108
+ }
109
+
99
110
  interface GridItem {
100
111
  value: number
101
112
  color: number
@@ -137,6 +148,11 @@ export default class PgInputPixelEditor extends HTMLElement {
137
148
  #startColor: number = -1;
138
149
  #startX: number = -1;
139
150
  #startY: number = -1;
151
+ #startClientX: number = -1;
152
+ #startClientY: number = -1;
153
+ #lassoPath: [number, number][] = [];
154
+ #lassoOutlinePixels: Set<string> = new Set();
155
+ #lassoOutlineList: { x: number; y: number }[] = [];
140
156
  #x: number = -1;
141
157
  #y: number = -1;
142
158
  #layer: number[] = [0];
@@ -338,19 +354,22 @@ export default class PgInputPixelEditor extends HTMLElement {
338
354
  #setSelectionPreview(pixels: Pixel[]) {
339
355
  // Undo previous selection
340
356
  this.#previousPreview.forEach(({x, y}) => {
357
+ if (x < 0 || x >= this.width || y < 0 || y >= this.height) { return; }
341
358
  this.#selectionPreview[y][x] = 0;
342
359
  });
360
+ this.#previousPreview = pixels;
343
361
  pixels.forEach(({ x, y }) => {
344
- this.#previousPreview = pixels;
362
+ if (x < 0 || x >= this.width || y < 0 || y >= this.height) { return; }
345
363
  this.#selectionPreview[y][x] = 1;
346
364
  });
347
365
  this.$selectionPathPreview.classList.toggle('hide', false);
348
- this.$selectionPathPreview.setAttribute('d', bitmaskToPath(this.#selectionPreview, { scale: this.size }));
366
+ this.$selectionPathPreview.setAttribute('d', bitmaskToPath(this.#selectionPreview, { scale: this.size })[0]);
349
367
  }
350
368
 
351
369
  #clearSelectionPreview() {
352
370
  this.$selectionPathPreview.classList.toggle('hide', true);
353
371
  this.#previousPreview.forEach(({x, y}) => {
372
+ if (x < 0 || x >= this.width || y < 0 || y >= this.height) { return; }
354
373
  this.#selectionPreview[y][x] = 0;
355
374
  });
356
375
  }
@@ -375,6 +394,7 @@ export default class PgInputPixelEditor extends HTMLElement {
375
394
  }
376
395
 
377
396
  #setSelectionPixel(x: number, y: number) {
397
+ if (x < 0 || x >= this.width || y < 0 || y >= this.height) { return; }
378
398
  this.#selectionPixels.set(`${x},${y}`, [x, y]);
379
399
  this.#selection[y][x] = 1;
380
400
  }
@@ -650,7 +670,6 @@ export default class PgInputPixelEditor extends HTMLElement {
650
670
  }
651
671
 
652
672
  async handleKeyDown(event: KeyboardEvent) {
653
- console.log(event.shiftKey, event.ctrlKey, event.altKey, event.key);
654
673
  this.#isShift = true;
655
674
  switch (event.key) {
656
675
  case ' ':
@@ -757,6 +776,8 @@ export default class PgInputPixelEditor extends HTMLElement {
757
776
  this.#startColor = this.#data[this.#layer[0]][newY][newX];
758
777
  this.#startX = newX;
759
778
  this.#startY = newY;
779
+ this.#startClientX = event.clientX;
780
+ this.#startClientY = event.clientY;
760
781
  this.#x = newX;
761
782
  this.#y = newY;
762
783
  const color = event.buttons === 32 ? 0 : this.#color;
@@ -771,6 +792,11 @@ export default class PgInputPixelEditor extends HTMLElement {
771
792
  }
772
793
  }));
773
794
  break;
795
+ case InputMode.SelectLasso:
796
+ this.#lassoPath = [[newX, newY]];
797
+ this.#lassoOutlinePixels = new Set();
798
+ this.#lassoOutlineList = [];
799
+ break;
774
800
  case InputMode.Pixel:
775
801
  this.#setPixel(newX, newY, color);
776
802
  break;
@@ -800,33 +826,44 @@ export default class PgInputPixelEditor extends HTMLElement {
800
826
  const totalSize = this.size + this.gridSize;
801
827
  let newX = Math.floor((event.clientX - rect.left) / totalSize);
802
828
  let newY = Math.floor((event.clientY - rect.top) / totalSize);
803
- //if (newX >= this.width) { newX = this.width - 1; }
804
- //if (newY >= this.height) { newY = this.height - 1; }
829
+ if (newX >= this.width) { newX = this.width - 1; }
830
+ if (newY >= this.height) { newY = this.height - 1; }
831
+ if (newX < 0) { newX = 0; }
832
+ if (newY < 0) { newY = 0; }
805
833
  //if (this.#startX === -1 && this.#startY === -1) {
806
834
  // return;
807
835
  //}
808
- // Single Tap
809
- if (newX === this.#startX && newY === this.#startY) {
836
+ const isDragSelect = this.#inputMode === InputMode.SelectRectangle
837
+ || this.#inputMode === InputMode.SelectEllipse
838
+ || this.#inputMode === InputMode.SelectLasso;
839
+ // Select tools moving less than 3px act as a click to deselect,
840
+ // shift keeps the current selection
841
+ if (isDragSelect
842
+ && Math.abs(event.clientX - this.#startClientX) < 3
843
+ && Math.abs(event.clientY - this.#startClientY) < 3) {
844
+ this.#clearSelectionPreview();
845
+ if (!event.shiftKey) {
846
+ this.clearSelection();
847
+ }
848
+ } else if (newX === this.#startX && newY === this.#startY) {
810
849
  switch (this.#inputMode) {
811
850
  case InputMode.SelectMagicWand:
812
851
  if (!event.shiftKey) {
813
852
  this.clearSelection();
814
853
  }
815
854
  const color = this.#data[this.#layer[0]][newY][newX];
816
- console.log(color);
817
855
  const pixels = getFloodFill(this.#data[this.#layer[0]], newX, newY, [color]);
818
856
  pixels.forEach(([x, y]) => {
819
857
  this.#setSelectionPixel(x, y);
820
858
  });
821
859
  this.$selectionPathPreview.classList.toggle('hide', true);
822
860
  this.$selectionPath.classList.toggle('hide', false);
823
- this.$selectionPath.setAttribute('d', bitmaskToPath(this.#selection, { scale: this.size }));
861
+ this.$selectionPath.setAttribute('d', bitmaskToPath(this.#selection, { scale: this.size })[0]);
824
862
  break;
825
863
  case InputMode.Pixel:
826
864
  // Same as current color
827
865
  if (this.#startColor === this.#color) {
828
866
  this.#setPixel(newX, newY, 0);
829
- this.#data[this.#layer[0]][newY][newX] = 0;
830
867
  }
831
868
  break;
832
869
  case InputMode.Stamp:
@@ -850,7 +887,7 @@ export default class PgInputPixelEditor extends HTMLElement {
850
887
  });
851
888
  this.$selectionPathPreview.classList.toggle('hide', true);
852
889
  this.$selectionPath.classList.toggle('hide', false);
853
- this.$selectionPath.setAttribute('d', bitmaskToPath(this.#selection, { scale: this.size }));
890
+ this.$selectionPath.setAttribute('d', bitmaskToPath(this.#selection, { scale: this.size })[0]);
854
891
  break;
855
892
  case InputMode.SelectEllipse:
856
893
  this.#clearSelectionPreview();
@@ -862,10 +899,19 @@ export default class PgInputPixelEditor extends HTMLElement {
862
899
  });
863
900
  this.$selectionPathPreview.classList.toggle('hide', true);
864
901
  this.$selectionPath.classList.toggle('hide', false);
865
- this.$selectionPath.setAttribute('d', bitmaskToPath(this.#selection, { scale: this.size }));
902
+ this.$selectionPath.setAttribute('d', bitmaskToPath(this.#selection, { scale: this.size })[0]);
866
903
  break;
867
904
  case InputMode.SelectLasso:
868
-
905
+ this.#clearSelectionPreview();
906
+ if (!event.shiftKey) {
907
+ this.clearSelection();
908
+ }
909
+ getLassoPixels(this.#lassoPath).forEach(({ x, y }) => {
910
+ this.#setSelectionPixel(x, y);
911
+ });
912
+ this.$selectionPathPreview.classList.toggle('hide', true);
913
+ this.$selectionPath.classList.toggle('hide', false);
914
+ this.$selectionPath.setAttribute('d', bitmaskToPath(this.#selection, { scale: this.size })[0]);
869
915
  break;
870
916
  case InputMode.Line:
871
917
  getLinePixels(this.#startX, this.#startY, newX, newY).forEach(({ x, y }) => {
@@ -953,6 +999,28 @@ export default class PgInputPixelEditor extends HTMLElement {
953
999
  case InputMode.SelectRectangle:
954
1000
  this.#setSelectionPreview(getRectanglePixels(startX, startY, lastX, lastY));
955
1001
  break;
1002
+ case InputMode.SelectEllipse:
1003
+ this.#setSelectionPreview(getEllipsePixels(startX, startY, lastX, lastY));
1004
+ break;
1005
+ case InputMode.SelectLasso: {
1006
+ const [sx, sy] = this.#lassoPath[0];
1007
+ for (const [px, py] of points) {
1008
+ const [prevX, prevY] = this.#lassoPath.at(-1)!;
1009
+ for (const { x: lx, y: ly } of getLinePixels(prevX, prevY, px, py)) {
1010
+ const key = `${lx},${ly}`;
1011
+ if (!this.#lassoOutlinePixels.has(key)) {
1012
+ this.#lassoOutlinePixels.add(key);
1013
+ this.#lassoOutlineList.push({ x: lx, y: ly });
1014
+ }
1015
+ }
1016
+ this.#lassoPath.push([px, py]);
1017
+ }
1018
+ const [cx, cy] = this.#lassoPath.at(-1)!;
1019
+ const closing = getLinePixels(cx, cy, sx, sy)
1020
+ .filter(({ x: lx, y: ly }) => !this.#lassoOutlinePixels.has(`${lx},${ly}`));
1021
+ this.#setSelectionPreview([...this.#lassoOutlineList, ...closing]);
1022
+ break;
1023
+ }
956
1024
  case InputMode.Pixel:
957
1025
  for (var point of points) {
958
1026
  this.#setPixel(point[0], point[1], color);
@@ -1100,7 +1168,7 @@ export default class PgInputPixelEditor extends HTMLElement {
1100
1168
  const cloned = cloneGrid(this.#data[this.#layer[0]]);
1101
1169
  const h = cloned.length - 1;
1102
1170
  iterateGrid(this.#data[this.#layer[0]], (x, y) => {
1103
- cloned[this.#layer[0]][y][x] = this.#data[h - y][x];
1171
+ cloned[y][x] = this.#data[this.#layer[0]][h - y][x];
1104
1172
  });
1105
1173
  this.#data[this.#layer[0]] = cloned;
1106
1174
  }
@@ -1332,7 +1400,7 @@ export default class PgInputPixelEditor extends HTMLElement {
1332
1400
  return this.#colors;
1333
1401
  }
1334
1402
 
1335
- addColor(r, g, b, a) {
1403
+ addColor(r: number, g: number, b: number, a: number) {
1336
1404
  this.#colors.push([r, g, b, a]);
1337
1405
  }
1338
1406
 
@@ -1379,7 +1447,7 @@ export default class PgInputPixelEditor extends HTMLElement {
1379
1447
  this.#layer = indexes;
1380
1448
  }
1381
1449
 
1382
- addLayer({ name, type, ...optional }) {
1450
+ addLayer({ name, type, ...optional }: AddLayerOptions) {
1383
1451
  this.#data.push(fillGrid(this.width, this.height));
1384
1452
  this.#layers.push({
1385
1453
  name,
@@ -1466,8 +1534,12 @@ export default class PgInputPixelEditor extends HTMLElement {
1466
1534
  #getLayerPaths() {
1467
1535
  return this.#data.map((layer, layerIndex) => {
1468
1536
  const colors = this.getLayerColorIndexes(layerIndex);
1469
- return colors.map((color) => {
1470
- return [color, bitmaskToPath(layer, { scale: 1, include: [color] })];
1537
+ const paths = bitmaskToPath(layer, {
1538
+ scale: 1,
1539
+ include: colors.map((color) => ([color])),
1540
+ });
1541
+ return colors.map((color, i) => {
1542
+ return [color, paths[i]];
1471
1543
  });
1472
1544
  });
1473
1545
  }
@@ -1489,8 +1561,8 @@ export default class PgInputPixelEditor extends HTMLElement {
1489
1561
  color,
1490
1562
  path: bitmaskToPath(this.#data[layerIndex], {
1491
1563
  scale: 1,
1492
- include: [color]
1493
- }),
1564
+ include: [[color]]
1565
+ })[0],
1494
1566
  };
1495
1567
  });
1496
1568
  case 'reference':
@@ -1603,8 +1675,8 @@ export default class PgInputPixelEditor extends HTMLElement {
1603
1675
  ...options,
1604
1676
  x: minX,
1605
1677
  y: minY,
1606
- width: maxX - minX,
1607
- height: maxY - minY,
1678
+ width: maxX - minX + 1,
1679
+ height: maxY - minY + 1,
1608
1680
  }, meta);
1609
1681
  }
1610
1682
 
@@ -1619,6 +1691,7 @@ export default class PgInputPixelEditor extends HTMLElement {
1619
1691
  this.#selectionPixels.forEach(([currentX, currentY]) => {
1620
1692
  const newX = currentX + x;
1621
1693
  const newY = currentY + y;
1694
+ if (newX < 0 || newX >= this.width || newY < 0 || newY >= this.height) { return; }
1622
1695
  this.#layer.forEach((layer) => {
1623
1696
  const color = this.#data[layer][currentY][currentX];
1624
1697
  const newColor = this.#data[layer][newY][newX];
@@ -1632,7 +1705,6 @@ export default class PgInputPixelEditor extends HTMLElement {
1632
1705
  });
1633
1706
  const clearSelection = diffLeftMapPixels(this.#selectionPixels, newSelection);
1634
1707
  // Move pixels
1635
- console.log(changes);
1636
1708
  changes.forEach(([layer, newX, newY, color]) => {
1637
1709
  this.#data[layer][newY][newX] = color;
1638
1710
  this.#setPixel(newX, newY, color, [layer]);
@@ -1657,7 +1729,7 @@ export default class PgInputPixelEditor extends HTMLElement {
1657
1729
  });
1658
1730
 
1659
1731
  // Update render
1660
- this.$selectionPath.setAttribute('d', bitmaskToPath(this.#selection, { scale: this.size }));
1732
+ this.$selectionPath.setAttribute('d', bitmaskToPath(this.#selection, { scale: this.size })[0]);
1661
1733
  }
1662
1734
  }
1663
1735