@pictogrammers/components 0.5.22 → 0.5.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@pictogrammers/components",
3
3
  "type": "module",
4
- "version": "0.5.22",
4
+ "version": "0.5.23",
5
5
  "license": "MIT",
6
6
  "author": "Austin Andrews",
7
7
  "scripts": {
@@ -304,7 +304,39 @@ export default class PgInputPixelEditor extends HTMLElement {
304
304
  this.#updateGrid();
305
305
  }
306
306
 
307
+ /**
308
+ * Clip a cached grid so its dimensions never exceed the current
309
+ * width/height. Used when the canvas is made smaller.
310
+ * @param grid 2d grid to clip in place
311
+ */
312
+ #clipGrid(grid: number[][]) {
313
+ if (grid.length > this.height) {
314
+ grid.length = this.height;
315
+ }
316
+ for (let y = 0; y < grid.length; y++) {
317
+ if (grid[y].length > this.width) {
318
+ grid[y].length = this.width;
319
+ }
320
+ }
321
+ }
322
+
307
323
  #redraw() {
324
+ // When the canvas shrinks, clip the cached grids so stale rows/columns
325
+ // outside the new bounds are dropped (the grow case is handled below).
326
+ this.#clipGrid(this.#export);
327
+ this.#clipGrid(this.#selection);
328
+ this.#clipGrid(this.#selectionPreview);
329
+ // Drop selection pixels that now fall outside the canvas so the
330
+ // selection map stays in sync with the clipped #selection grid.
331
+ this.#selectionPixels.forEach(([x, y], key) => {
332
+ if (x >= this.width || y >= this.height) {
333
+ this.#selectionPixels.delete(key);
334
+ }
335
+ });
336
+ if (this.#selectionPixels.size === 0) {
337
+ this.$selectionPath.classList.toggle('hide', true);
338
+ }
339
+
308
340
  // Render individual pixels
309
341
  const data = this.#data.toReversed();
310
342
  const layerCount = data.length;
@@ -17,7 +17,13 @@ export default class XPgInputSelectBasic extends HTMLElement {
17
17
  { label: 'Option 1', value: 'option1' },
18
18
  { label: 'Option 2', value: 'option2' },
19
19
  { label: 'Option 3', value: 'option3' },
20
- { label: 'Option 4', value: 'option4' }
20
+ { label: 'Option 4', value: 'option4' },
21
+ { label: 'Option 5', value: 'option5' },
22
+ { label: 'Option 6', value: 'option6' },
23
+ { label: 'Option 7', value: 'option7' },
24
+ { label: 'Option 8', value: 'option8' },
25
+ { label: 'Option 9', value: 'option9' },
26
+ { label: 'Option 10', value: 'option10' }
21
27
  );
22
28
  this.$input.default = { label: 'None', value: '', disabled: true };
23
29
  //this.$input.value = this.$input.options[1].value;
@@ -0,0 +1,28 @@
1
+ import { Component, Part, Prop } from '@pictogrammers/element';
2
+
3
+ import PgJson from '../../jsonBoolean';
4
+
5
+ import template from './basic.html';
6
+
7
+ @Component({
8
+ selector: 'x-pg-json-boolean-basic',
9
+ template
10
+ })
11
+ export default class XPgJsonBasic extends HTMLElement {
12
+ @Part() $json: PgJson;
13
+
14
+ connectedCallback() {
15
+ this.$json.value = {
16
+ users: [{
17
+ name: 'Dipper Pines',
18
+ age: 12,
19
+ }]
20
+ }; // Array or Object
21
+ this.$json.addEventListener('change', (e: any) => {
22
+ const { parent, key, path, value } = e.detail;
23
+ if (value !== parent[key]) {
24
+ parent[key] = value;
25
+ }
26
+ });
27
+ }
28
+ }
@@ -0,0 +1,28 @@
1
+ import { Component, Part, Prop } from '@pictogrammers/element';
2
+
3
+ import PgJson from '../../jsonNumber';
4
+
5
+ import template from './basic.html';
6
+
7
+ @Component({
8
+ selector: 'x-pg-json-number-basic',
9
+ template
10
+ })
11
+ export default class XPgJsonBasic extends HTMLElement {
12
+ @Part() $json: PgJson;
13
+
14
+ connectedCallback() {
15
+ this.$json.value = {
16
+ users: [{
17
+ name: 'Dipper Pines',
18
+ age: 12,
19
+ }]
20
+ }; // Array or Object
21
+ this.$json.addEventListener('change', (e: any) => {
22
+ const { parent, key, path, value } = e.detail;
23
+ if (value !== parent[key]) {
24
+ parent[key] = value;
25
+ }
26
+ });
27
+ }
28
+ }
@@ -0,0 +1,28 @@
1
+ import { Component, Part, Prop } from '@pictogrammers/element';
2
+
3
+ import PgJson from '../../jsonString';
4
+
5
+ import template from './basic.html';
6
+
7
+ @Component({
8
+ selector: 'x-pg-json-string-basic',
9
+ template
10
+ })
11
+ export default class XPgJsonBasic extends HTMLElement {
12
+ @Part() $json: PgJson;
13
+
14
+ connectedCallback() {
15
+ this.$json.value = {
16
+ users: [{
17
+ name: 'Dipper Pines',
18
+ age: 12,
19
+ }]
20
+ }; // Array or Object
21
+ this.$json.addEventListener('change', (e: any) => {
22
+ const { parent, key, path, value } = e.detail;
23
+ if (value !== parent[key]) {
24
+ parent[key] = value;
25
+ }
26
+ });
27
+ }
28
+ }
package/pg/menu/menu.css CHANGED
@@ -12,6 +12,8 @@
12
12
  border-radius: 0.5rem;
13
13
  background-color: var(--pg-menu-background-color, #FFFFFF);
14
14
  box-shadow: var(--pg-menu-box-shadow, none);
15
+ max-height: var(--pg-menu-max-height);
16
+ overflow: var(--pg-menu-overflow);
15
17
  }
16
18
 
17
19
  [part=items].check {
@@ -0,0 +1,9 @@
1
+ <div class="example" style="position: relative;">
2
+ <pg-node part="node"></pg-node>
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>
@@ -2,6 +2,7 @@ import { Component, Part } from '@pictogrammers/element';
2
2
  import PgNode from '../../node';
3
3
 
4
4
  import template from './basic.html';
5
+ import PgNodeEditorText from 'components/pg/nodeEditorText/nodeEditorText';
5
6
 
6
7
  @Component({
7
8
  selector: 'x-pg-node-basic',
@@ -12,11 +13,12 @@ export default class XPgNodeBasic extends HTMLElement {
12
13
  @Part() $node: PgNode;
13
14
 
14
15
  connectedCallback() {
16
+ this.$node.editors.push(PgNodeEditorText);
15
17
  this.$node.fields = [{
16
18
  label: 'Name',
17
19
  value: 'foo',
18
20
  name: 'name',
19
- type: 'text',
21
+ type: 'Text',
20
22
  }];
21
23
  this.$node.addEventListener('change', this.#handleChange.bind(this));
22
24
  this.$node.addEventListener('input', this.#handleInput.bind(this));
package/pg/node/node.ts CHANGED
@@ -40,7 +40,11 @@ export default class PgNode extends HTMLElement {
40
40
  container: this.$items,
41
41
  items: this.fields,
42
42
  type: (item) => {
43
- return this.editors.find(x => x.type === item.type);
43
+ const comp = this.editors.find(x => x.type === item.type);
44
+ if (!comp) {
45
+ throw new Error(`"${item.name}" missing in editors array.`);
46
+ }
47
+ return comp;
44
48
  },
45
49
  create: ($item: any, item) => {
46
50
  this.height += $item.height;
@@ -54,6 +58,14 @@ export default class PgNode extends HTMLElement {
54
58
  this.#syncFieldHeight(item.itemKey, $item);
55
59
  this.#dispatchArg('change', item.itemKey, e.detail.value);
56
60
  });
61
+ // Editor events don't compose past this shadow root; proxy the
62
+ // Link editor's events up for pg-nodes.
63
+ $item.addEventListener('nodepulse', (e: any) => {
64
+ this.dispatchEvent(new CustomEvent('nodepulse', { detail: e.detail }));
65
+ });
66
+ $item.addEventListener('nodeselection', (e: any) => {
67
+ this.dispatchEvent(new CustomEvent('nodeselection', { detail: e.detail }));
68
+ });
57
69
  },
58
70
  });
59
71
 
@@ -67,10 +79,12 @@ export default class PgNode extends HTMLElement {
67
79
  this.height += $item.height;
68
80
  },
69
81
  connect: ($item: any, item) => {
70
- // Measured mid-setup, before render() applies the host styles, so
71
- // the adjust constant differs from the post-layout one below.
72
- const top = this.$node.getBoundingClientRect().top;
73
- this.#registerOutputPin(item.key, item.label, $item.getBoundingClientRect().top - top + 31);
82
+ let height = 0;
83
+ this.#fieldHeights.forEach((value) => {
84
+ height += value;
85
+ });
86
+ const index = Array.from(this.$outputs.children).indexOf($item);
87
+ this.#registerOutputPin(item.key, item.label, ((height + index + 2) * 16) + 5);
74
88
  },
75
89
  });
76
90
  this.#intrinsicHeight = this.height;
@@ -78,6 +92,11 @@ export default class PgNode extends HTMLElement {
78
92
  this.height = requestedHeight;
79
93
  }
80
94
  this.$node.addEventListener('pointerover', this.#handlePointerOver.bind(this));
95
+ this.$node.addEventListener('animationend', (e: AnimationEvent) => {
96
+ if (e.animationName === 'pg-node-pulse') {
97
+ this.$node.classList.remove('pulse');
98
+ }
99
+ });
81
100
  }
82
101
 
83
102
  // Editor heights are cached by field key so a change event only triggers a
@@ -262,9 +281,13 @@ export default class PgNode extends HTMLElement {
262
281
  }
263
282
  }
264
283
 
284
+ // Blue attention pulse (3 flashes); the animationend listener added in
285
+ // connectedCallback clears the class so it can run again later.
265
286
  pulse() {
266
- // todo: add css class to create a blue animated pulse.
267
- // it should also focus the node into view as it may be scrolled out of view.
268
- // it should only pulse 3 times.
287
+ this.scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'nearest' });
288
+ // Restart the animation when a pulse is already running.
289
+ this.$node.classList.remove('pulse');
290
+ void this.$node.offsetWidth;
291
+ this.$node.classList.add('pulse');
269
292
  }
270
293
  }
@@ -0,0 +1,66 @@
1
+ import { Component, Prop, Part } from '@pictogrammers/element';
2
+
3
+ import template from './nodeEditorLink.html';
4
+ import style from './nodeEditorLink.css';
5
+
6
+ @Component({
7
+ selector: 'pg-node-editor-link',
8
+ style,
9
+ template,
10
+ })
11
+ export default class PgNodeEditorLink extends HTMLElement {
12
+
13
+ static type = 'Link';
14
+
15
+ @Prop() label: string = '';
16
+ @Prop() value: number = 0;
17
+ @Prop() name: string = '';
18
+
19
+ @Part() $label: HTMLSpanElement;
20
+ @Part() $link: HTMLButtonElement;
21
+ @Part() $select: HTMLButtonElement;
22
+
23
+ connectedCallback() {
24
+ // Pulse the linked node; pg-node proxies this up to pg-nodes.
25
+ this.$link.addEventListener('click', () => {
26
+ this.dispatchEvent(new CustomEvent('nodepulse', {
27
+ detail: {
28
+ id: this.value,
29
+ }
30
+ }));
31
+ });
32
+ // Ask pg-nodes to enter selection mode; it calls back with the picked id.
33
+ this.$select.addEventListener('click', () => {
34
+ this.dispatchEvent(new CustomEvent('nodeselection', {
35
+ detail: {
36
+ value: this.value,
37
+ callback: (nodeId: number) => {
38
+ this.value = nodeId;
39
+ this.dispatchEvent(new CustomEvent('change', {
40
+ detail: {
41
+ value: nodeId,
42
+ }
43
+ }));
44
+ },
45
+ }
46
+ }));
47
+ });
48
+ }
49
+
50
+ render(changes: any) {
51
+ if (changes.label) {
52
+ this.$label.textContent = this.label;
53
+ }
54
+ if (changes.value) {
55
+ this.$link.textContent = `Node ${this.value}`;
56
+ }
57
+ }
58
+
59
+ get height() {
60
+ return 2;
61
+ }
62
+
63
+ focus() {
64
+ this.$link.focus();
65
+ }
66
+ }
@@ -0,0 +1,28 @@
1
+ <label>
2
+ <span part="label"></span>
3
+ <input type="number" part="min">
4
+ </label>
5
+ <button>
6
+ <svg viewBox="0 0 24 24">
7
+ <path d="M19,13H5V11H19V13Z" />
8
+ </svg>
9
+ </button>
10
+ <button>
11
+ <svg viewBox="0 0 24 24">
12
+ <path d="M19,13H13V19H11V13H5V11H11V5H13V11H19V13Z" />
13
+ </svg>
14
+ </button>
15
+ <label>
16
+ <span part="label"></span>
17
+ <input type="range" part="max">
18
+ </label>
19
+ <button>
20
+ <svg viewBox="0 0 24 24">
21
+ <path d="M19,13H5V11H19V13Z" />
22
+ </svg>
23
+ </button>
24
+ <button>
25
+ <svg viewBox="0 0 24 24">
26
+ <path d="M19,13H13V19H11V13H5V11H11V5H13V11H19V13Z" />
27
+ </svg>
28
+ </button>
@@ -20,22 +20,39 @@ export default class PgNodeEditorRange extends HTMLElement {
20
20
  @Prop() step: number = 1;
21
21
 
22
22
  @Part() $label: HTMLLabelElement;
23
- @Part() $input: HTMLInputElement;
23
+ @Part() $min: HTMLInputElement;
24
+ @Part() $max: HTMLInputElement;
24
25
 
25
26
  connectedCallback() {
26
- this.$input.addEventListener('change', (e: any) => {
27
+ this.$min.addEventListener('change', (e: any) => {
27
28
  e.stopPropagation();
28
29
  this.dispatchEvent(new CustomEvent('change', {
29
30
  detail: {
30
- value: Number(this.$input.value),
31
+ value: Number(this.$min.value),
31
32
  }
32
33
  }));
33
34
  });
34
- this.$input.addEventListener('input', (e: any) => {
35
+ this.$min.addEventListener('input', (e: any) => {
35
36
  e.stopPropagation();
36
37
  this.dispatchEvent(new CustomEvent('input', {
37
38
  detail: {
38
- value: Number(this.$input.value),
39
+ value: Number(this.$min.value),
40
+ }
41
+ }));
42
+ });
43
+ this.$max.addEventListener('change', (e: any) => {
44
+ e.stopPropagation();
45
+ this.dispatchEvent(new CustomEvent('change', {
46
+ detail: {
47
+ value: Number(this.$max.value),
48
+ }
49
+ }));
50
+ });
51
+ this.$max.addEventListener('input', (e: any) => {
52
+ e.stopPropagation();
53
+ this.dispatchEvent(new CustomEvent('input', {
54
+ detail: {
55
+ value: Number(this.$max.value),
39
56
  }
40
57
  }));
41
58
  });
@@ -46,16 +63,16 @@ export default class PgNodeEditorRange extends HTMLElement {
46
63
  this.$label.textContent = this.label;
47
64
  }
48
65
  if (changes.value) {
49
- this.$input.value = String(this.value);
66
+ this.$min.value = String(this.value);
50
67
  }
51
68
  if (changes.min) {
52
- this.$input.min = String(this.min);
69
+ this.$min.min = String(this.min);
53
70
  }
54
71
  if (changes.max) {
55
- this.$input.max = String(this.max);
72
+ this.$min.max = String(this.max);
56
73
  }
57
74
  if (changes.step) {
58
- this.$input.step = String(this.step);
75
+ this.$min.step = String(this.step);
59
76
  }
60
77
  }
61
78
 
@@ -64,6 +81,6 @@ export default class PgNodeEditorRange extends HTMLElement {
64
81
  }
65
82
 
66
83
  focus() {
67
- this.$input.focus();
84
+ this.$min.focus();
68
85
  }
69
86
  }
@@ -1,57 +1,58 @@
1
- import { Component, Prop, Part } from '@pictogrammers/element';
2
-
3
- import template from './nodeEditorText.html';
4
- import style from './nodeEditorText.css';
5
-
6
- @Component({
7
- selector: 'pg-node-editor-text',
8
- style,
9
- template,
10
- })
11
- export default class PgNodeEditorText extends HTMLElement {
12
-
13
- static type = 'Text';
14
-
15
- @Prop() label: string = '';
16
- @Prop() value: string = '';
17
- @Prop() name: string = '';
18
-
19
- @Part() $label: HTMLLabelElement;
20
- @Part() $input: HTMLInputElement;
21
-
22
- connectedCallback() {
23
- this.$input.addEventListener('change', (e: any) => {
24
- e.stopPropagation();
25
- this.dispatchEvent(new CustomEvent('change', {
26
- detail: {
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
- value: this.$input.value,
36
- }
37
- }));
38
- });
39
- }
40
-
41
- render(changes: any) {
42
- if (changes.label) {
43
- this.$label.textContent = this.label;
44
- }
45
- if (changes.value) {
46
- this.$input.value = this.value;
47
- }
48
- }
49
-
50
- get height() {
51
- return 2;
52
- }
53
-
54
- focus() {
55
- this.$input.focus();
56
- }
57
- }
1
+ import { Component, Prop, Part } from '@pictogrammers/element';
2
+
3
+ import PgNodeInputText from '../nodeInputText/nodeInputText';
4
+ import template from './nodeEditorText.html';
5
+ import style from './nodeEditorText.css';
6
+
7
+ @Component({
8
+ selector: 'pg-node-editor-text',
9
+ style,
10
+ template,
11
+ })
12
+ export default class PgNodeEditorText extends HTMLElement {
13
+
14
+ static type = 'Text';
15
+
16
+ @Prop() label: string = '';
17
+ @Prop() value: string = '';
18
+ @Prop() name: string = '';
19
+
20
+ @Part() $label: HTMLSpanElement;
21
+ @Part() $input: PgNodeInputText;
22
+
23
+ connectedCallback() {
24
+ this.$input.addEventListener('change', (e: any) => {
25
+ e.stopPropagation();
26
+ this.dispatchEvent(new CustomEvent('change', {
27
+ detail: {
28
+ value: e.detail.value,
29
+ }
30
+ }));
31
+ });
32
+ this.$input.addEventListener('input', (e: any) => {
33
+ e.stopPropagation();
34
+ this.dispatchEvent(new CustomEvent('input', {
35
+ detail: {
36
+ value: e.detail.value,
37
+ }
38
+ }));
39
+ });
40
+ }
41
+
42
+ render(changes: any) {
43
+ if (changes.label) {
44
+ this.$label.textContent = this.label;
45
+ }
46
+ if (changes.value) {
47
+ this.$input.value = this.value;
48
+ }
49
+ }
50
+
51
+ get height() {
52
+ return 2;
53
+ }
54
+
55
+ focus() {
56
+ this.$input.focus();
57
+ }
58
+ }
@@ -110,6 +110,12 @@ export default class PgNodeEditorTextArray extends HTMLElement {
110
110
  key: uuid(),
111
111
  value: '',
112
112
  });
113
+ // Emit updated array
114
+ this.dispatchEvent(new CustomEvent('change', {
115
+ detail: {
116
+ value: this.#inputs.map(x => x.value),
117
+ },
118
+ }));
113
119
  });
114
120
  $item.addEventListener('remove', (e: any) => {
115
121
  const { index } = e.detail;
@@ -138,9 +144,12 @@ export default class PgNodeEditorTextArray extends HTMLElement {
138
144
  key: uuid(),
139
145
  value: '',
140
146
  });
141
- // Emit updated array
147
+ // Emit updated array (#inputs is the live list; this.value is only the
148
+ // initial prop and goes stale after the first edit)
142
149
  this.dispatchEvent(new CustomEvent('change', {
143
- detail: { value: [...this.value, ''] },
150
+ detail: {
151
+ value: this.#inputs.map(x => x.value),
152
+ },
144
153
  }));
145
154
  });
146
155
  }
@@ -152,7 +161,7 @@ export default class PgNodeEditorTextArray extends HTMLElement {
152
161
  }
153
162
 
154
163
  get height() {
155
- return this.#inputs.length + 1;
164
+ return (this.#inputs.length || 1) + 1;
156
165
  }
157
166
 
158
167
  focus() {
@@ -39,6 +39,8 @@ import '@pictogrammers/components/pgNodes';
39
39
 
40
40
  ### Editors
41
41
 
42
+ Nodes are made of input fields controlled by editor components. Each editor has a `static type = 'Text';` definition unique to the script.
43
+
42
44
  ```typescript
43
45
  this.$script.editors.push(PgNodeEditorText);
44
46
  ```
@@ -58,7 +58,7 @@ export default class XPgNodesBasic extends HTMLElement {
58
58
  this.$script.nodes.push({
59
59
  name: 'stateHas',
60
60
  label: 'Has',
61
- width: 6,
61
+ width: 10,
62
62
  args: [{
63
63
  key: 'key',
64
64
  label: 'Key',
@@ -117,8 +117,13 @@ export default class XPgNodesBasic extends HTMLElement {
117
117
  }, {
118
118
  name: 'stateSet',
119
119
  label: 'Set',
120
- width: 6,
120
+ width: 10,
121
121
  args: [{
122
+ key: 'key',
123
+ label: 'Key',
124
+ editor: 'Text',
125
+ value: '',
126
+ }, {
122
127
  key: 'value',
123
128
  label: 'Value',
124
129
  editor: 'Text',
@@ -142,6 +147,11 @@ export default class XPgNodesBasic extends HTMLElement {
142
147
  label: 'Equals',
143
148
  width: 10,
144
149
  args: [{
150
+ key: 'key',
151
+ label: 'Key',
152
+ editor: 'Text',
153
+ value: '',
154
+ }, {
145
155
  key: 'value',
146
156
  label: 'Value',
147
157
  editor: 'Text',
@@ -170,6 +180,11 @@ export default class XPgNodesBasic extends HTMLElement {
170
180
  label: 'Less Than',
171
181
  width: 10,
172
182
  args: [{
183
+ key: 'key',
184
+ label: 'Key',
185
+ editor: 'Text',
186
+ value: '',
187
+ }, {
173
188
  key: 'value',
174
189
  label: 'Value',
175
190
  editor: 'Number',
@@ -198,6 +213,11 @@ export default class XPgNodesBasic extends HTMLElement {
198
213
  label: 'Less Than or Equal',
199
214
  width: 10,
200
215
  args: [{
216
+ key: 'key',
217
+ label: 'Key',
218
+ editor: 'Text',
219
+ value: '',
220
+ }, {
201
221
  key: 'value',
202
222
  label: 'Value',
203
223
  editor: 'Number',
@@ -226,6 +246,11 @@ export default class XPgNodesBasic extends HTMLElement {
226
246
  label: 'Greater Than',
227
247
  width: 10,
228
248
  args: [{
249
+ key: 'key',
250
+ label: 'Key',
251
+ editor: 'Text',
252
+ value: '',
253
+ }, {
229
254
  key: 'value',
230
255
  label: 'Value',
231
256
  editor: 'Number',
@@ -254,6 +279,11 @@ export default class XPgNodesBasic extends HTMLElement {
254
279
  label: 'Greater Than or Equal',
255
280
  width: 10,
256
281
  args: [{
282
+ key: 'key',
283
+ label: 'Key',
284
+ editor: 'Text',
285
+ value: '',
286
+ }, {
257
287
  key: 'value',
258
288
  label: 'Value',
259
289
  editor: 'Number',
@@ -352,8 +382,13 @@ export default class XPgNodesBasic extends HTMLElement {
352
382
  }, {
353
383
  name: 'isTrue',
354
384
  label: 'Is True',
355
- width: 4,
356
- args: [],
385
+ width: 10,
386
+ args: [{
387
+ key: 'key',
388
+ label: 'Key',
389
+ editor: 'Text',
390
+ value: '',
391
+ }],
357
392
  nodes: [{
358
393
  key: 't',
359
394
  label: 'True',
@@ -375,8 +410,13 @@ export default class XPgNodesBasic extends HTMLElement {
375
410
  }, {
376
411
  name: 'isFalse',
377
412
  label: 'Is False',
378
- width: 4,
379
- args: [],
413
+ width: 10,
414
+ args: [{
415
+ key: 'key',
416
+ label: 'Key',
417
+ editor: 'Text',
418
+ value: '',
419
+ }],
380
420
  nodes: [{
381
421
  key: 't',
382
422
  label: 'True',
@@ -556,10 +596,11 @@ export default class XPgNodesBasic extends HTMLElement {
556
596
  });
557
597
  this.$script.items.push({
558
598
  id: 2,
559
- x: 30,
599
+ x: 28,
560
600
  y: 2,
561
601
  node: 'stateSet',
562
602
  args: {
603
+ key: 'health',
563
604
  value: '10',
564
605
  },
565
606
  nodes: {
@@ -568,10 +609,11 @@ export default class XPgNodesBasic extends HTMLElement {
568
609
  });
569
610
  this.$script.items.push({
570
611
  id: 3,
571
- x: 30,
612
+ x: 28,
572
613
  y: 10,
573
614
  node: 'stateSet',
574
615
  args: {
616
+ key: 'health',
575
617
  value: '20',
576
618
  },
577
619
  nodes: {
@@ -580,7 +622,7 @@ export default class XPgNodesBasic extends HTMLElement {
580
622
  });
581
623
  this.$script.items.push({
582
624
  id: 4,
583
- x: 44,
625
+ x: 40,
584
626
  y: 8,
585
627
  node: 'log',
586
628
  args: {
@@ -15,4 +15,13 @@
15
15
  overflow: visible;
16
16
  position-visibility: always;
17
17
  position-anchor: auto;
18
+ position-try-fallbacks: --pg-menu-up, --pg-menu-over;
19
+ }
20
+
21
+ @position-try --pg-menu-up {
22
+ max-height: 10rem;
23
+ }
24
+
25
+ @position-try --pg-menu-over {
26
+ max-height: 5rem;
18
27
  }
@@ -51,6 +51,24 @@ export default class PgOverlayMenu extends PgOverlay {
51
51
  if (!this.preventFocus) {
52
52
  this.$menu.focus(index);
53
53
  }
54
+ // Overlay
55
+ const observer = new IntersectionObserver((entries) => {
56
+ entries.forEach(entry => {
57
+ console.log(entry);
58
+ // entry.isIntersecting will be false if it is entirely outside the viewport
59
+ if (!entry.isIntersecting) {
60
+ console.log('Element is completely outside the viewport');
61
+ } else {
62
+ console.log('Element is at least partially inside the viewport');
63
+ }
64
+ });
65
+ }, {
66
+ trackVisibility: true,
67
+ root: null, // Defaults to the browser viewport
68
+ threshold: 0 // Triggers as soon as even 1 pixel enters or leaves
69
+ });
70
+
71
+ observer.observe(this.$overlay);
54
72
  }
55
73
 
56
74
  #toggle(e: ToggleEvent) {
@@ -2,7 +2,7 @@
2
2
  display: contents;
3
3
  }
4
4
 
5
- [part="overlay"] {
5
+ [part=overlay] {
6
6
  margin: 0;
7
7
  padding: 0;
8
8
  border: 0;
@@ -17,3 +17,7 @@
17
17
  position-visibility: always;
18
18
  position-anchor: auto;
19
19
  }
20
+
21
+ [part=overlay].no-offset {
22
+ margin: 0;
23
+ }
@@ -77,6 +77,24 @@ export default class PgOverlaySelectMenu extends PgOverlay {
77
77
  this.$overlay.style.setProperty('--pg-overlay-menu-_y', `${y}px`);
78
78
  // Focus
79
79
  this.$menu.focus(index);
80
+ // Overlay
81
+ const observer = new IntersectionObserver((entries) => {
82
+ entries.forEach(entry => {
83
+ console.log(entry.intersectionRatio);
84
+ //this.$overlay.classList.toggle('no-offset', entry.intersectionRatio < 1);
85
+ // entry.isIntersecting will be false if it is entirely outside the viewport
86
+ if (!entry.isIntersecting) {
87
+ console.log('Element is completely outside the viewport');
88
+ } else {
89
+ console.log('Element is at least partially inside the viewport');
90
+ }
91
+ });
92
+ }, {
93
+ root: null, // Defaults to the browser viewport
94
+ threshold: 1 // Triggers as soon as even 1 pixel enters or leaves
95
+ });
96
+
97
+ observer.observe(this.$overlay);
80
98
  }
81
99
 
82
100
  #toggle(e: ToggleEvent) {
@@ -99,4 +117,4 @@ export default class PgOverlaySelectMenu extends PgOverlay {
99
117
  this.close({ value: null });
100
118
  this.source?.focus();
101
119
  }
102
- }
120
+ }
package/pg/tree/README.md CHANGED
@@ -22,6 +22,7 @@ Each item in the `items` array follows this structure:
22
22
  {
23
23
  label: string;
24
24
  icon: { path: string };
25
+ disableRename: boolean;
25
26
  isFolder: boolean; // true enables the drop-onto zone and .items CSS class
26
27
  expanded?: boolean; // folders only
27
28
  items?: any[]; // child items (folders only)
@@ -22,14 +22,15 @@ function createFolder(label, expanded = true, items: any[] = []) {
22
22
  isFolder: true,
23
23
  actions: [{
24
24
  type: PgTreeButtonIcon,
25
+ label: 'Show/Hide',
25
26
  icon: IconEye,
26
27
  enabled: true
27
28
  },
28
29
  {
29
30
  type: PgTreeButtonIcon,
30
- label: 'Delete',
31
+ label: 'Lock/Unlock',
31
32
  icon: IconUnlock,
32
- enabled: false
33
+ enabled: true
33
34
  }],
34
35
  items
35
36
  };
@@ -43,12 +44,13 @@ function createItem(label) {
43
44
  label,
44
45
  actions: [{
45
46
  type: PgTreeButtonIcon,
47
+ label: 'Show/Hide',
46
48
  icon: IconEye,
47
49
  enabled: true
48
50
  },
49
51
  {
50
52
  type: PgTreeButtonIcon,
51
- label: 'Delete',
53
+ label: 'Lock/Unlock',
52
54
  icon: IconUnlock,
53
55
  enabled: false
54
56
  }]
@@ -89,10 +91,8 @@ export default class XPgTreeBasic extends HTMLElement {
89
91
  action.enabled = true;
90
92
  }
91
93
  });
92
- this.$tree.addEventListener('move', (e: any) => {
93
- this.#selectedItems.forEach((item: any) => {
94
- item.move(e.detail.item, e.detail.position);
95
- });
94
+ this.$tree.addEventListener('move', (_e: any) => {
95
+ // Tree handles all data manipulation internally; this is a notification only
96
96
  });
97
97
  this.$tree.addEventListener('rename', (e: any) => {
98
98
  const { item, label } = e.detail;
@@ -19,6 +19,7 @@ export default class PgTreeItem extends HTMLElement {
19
19
  @Prop() label: string = '';
20
20
  @Prop() selected: boolean = false;
21
21
  @Prop() expanded: boolean = false;
22
+ @Prop() disableRename: boolean = false;
22
23
  @Prop() isFolder: boolean = false;
23
24
  @Prop() icon: { path: string } = { path: noIcon };
24
25
  @Prop() actions: any[] = [];
@@ -179,7 +180,7 @@ export default class PgTreeItem extends HTMLElement {
179
180
  }
180
181
 
181
182
  #handleKeyDownLabel(e: KeyboardEvent) {
182
- if (e.key === 'Enter') {
183
+ if (e.key === 'Enter' && !this.disableRename) {
183
184
  this.#enableRename();
184
185
  e.preventDefault();
185
186
  } else if (e.key === 'ArrowUp') {
@@ -270,6 +271,7 @@ export default class PgTreeItem extends HTMLElement {
270
271
  #ignoreNextClick = false;
271
272
  #handleDoubleClick(e: MouseEvent) {
272
273
  if (e.ctrlKey || e.shiftKey) return;
274
+ if (this.disableRename) return;
273
275
  this.#enableRename();
274
276
  e.preventDefault();
275
277
  }