@vaadin/crud 23.1.0-alpha2 → 23.1.0-beta1

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,6 +1,6 @@
1
1
  {
2
2
  "name": "@vaadin/crud",
3
- "version": "23.1.0-alpha2",
3
+ "version": "23.1.0-beta1",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -35,22 +35,22 @@
35
35
  "dependencies": {
36
36
  "@open-wc/dedupe-mixin": "^1.3.0",
37
37
  "@polymer/polymer": "^3.0.0",
38
- "@vaadin/button": "23.1.0-alpha2",
39
- "@vaadin/component-base": "23.1.0-alpha2",
40
- "@vaadin/confirm-dialog": "23.1.0-alpha2",
41
- "@vaadin/dialog": "23.1.0-alpha2",
42
- "@vaadin/form-layout": "23.1.0-alpha2",
43
- "@vaadin/grid": "23.1.0-alpha2",
44
- "@vaadin/text-field": "23.1.0-alpha2",
38
+ "@vaadin/button": "23.1.0-beta1",
39
+ "@vaadin/component-base": "23.1.0-beta1",
40
+ "@vaadin/confirm-dialog": "23.1.0-beta1",
41
+ "@vaadin/dialog": "23.1.0-beta1",
42
+ "@vaadin/form-layout": "23.1.0-beta1",
43
+ "@vaadin/grid": "23.1.0-beta1",
44
+ "@vaadin/text-field": "23.1.0-beta1",
45
45
  "@vaadin/vaadin-license-checker": "^2.1.0",
46
- "@vaadin/vaadin-lumo-styles": "23.1.0-alpha2",
47
- "@vaadin/vaadin-material-styles": "23.1.0-alpha2",
48
- "@vaadin/vaadin-themable-mixin": "23.1.0-alpha2"
46
+ "@vaadin/vaadin-lumo-styles": "23.1.0-beta1",
47
+ "@vaadin/vaadin-material-styles": "23.1.0-beta1",
48
+ "@vaadin/vaadin-themable-mixin": "23.1.0-beta1"
49
49
  },
50
50
  "devDependencies": {
51
51
  "@esm-bundle/chai": "^4.3.4",
52
52
  "@vaadin/testing-helpers": "^0.3.2",
53
- "sinon": "^9.2.1"
53
+ "sinon": "^13.0.2"
54
54
  },
55
- "gitHead": "6842dcb8b163d4512fae8d3d12a6559077a4aee6"
55
+ "gitHead": "8be43cf83102a6b9ccf309687446e590ce0164e8"
56
56
  }
@@ -15,25 +15,16 @@ registerStyles(
15
15
  min-width: 20em;
16
16
  }
17
17
 
18
- [part='content'] {
19
- display: flex;
20
- flex-direction: column;
21
- padding: 0;
22
- }
23
-
24
- [part='scroller'] {
25
- display: flex;
26
- flex-direction: column;
27
- overflow: auto;
28
- flex: auto;
29
- }
30
-
31
18
  [part='footer'] {
32
- display: flex;
33
- flex: none;
19
+ justify-content: flex-start;
34
20
  flex-direction: row-reverse;
35
21
  }
36
22
 
23
+ /* Make buttons clickable */
24
+ [part='footer'] ::slotted(:not([disabled])) {
25
+ pointer-events: all;
26
+ }
27
+
37
28
  :host([fullscreen]) {
38
29
  top: 0;
39
30
  left: 0;
@@ -52,24 +43,15 @@ registerStyles(
52
43
  flex: 1;
53
44
  }
54
45
  `,
55
- { moduleId: 'vaadin-crud-dialog-overlay-styles' }
46
+ { moduleId: 'vaadin-crud-dialog-overlay-styles' },
56
47
  );
57
48
 
58
49
  let memoizedTemplate;
59
50
 
60
- const editorTemplate = html`
61
- <div part="scroller" role="group" aria-labelledby="header">
62
- <div part="header" id="header">
63
- <slot name="header"></slot>
64
- </div>
65
- <slot name="form"></slot>
66
- </div>
67
-
68
- <div part="footer" role="toolbar">
69
- <slot name="save-button"></slot>
70
- <slot name="cancel-button"></slot>
71
- <slot name="delete-button"></slot>
72
- </div>
51
+ const footerTemplate = html`
52
+ <slot name="save-button"></slot>
53
+ <slot name="cancel-button"></slot>
54
+ <slot name="delete-button"></slot>
73
55
  `;
74
56
 
75
57
  class CrudDialogOverlay extends DialogOverlay {
@@ -80,13 +62,40 @@ class CrudDialogOverlay extends DialogOverlay {
80
62
  static get template() {
81
63
  if (!memoizedTemplate) {
82
64
  memoizedTemplate = super.template.cloneNode(true);
65
+
66
+ // Replace two header slots with a single one
67
+ const headerPart = memoizedTemplate.content.querySelector('[part="header"]');
68
+ headerPart.innerHTML = '';
69
+ const headerSlot = document.createElement('slot');
70
+ headerSlot.setAttribute('name', 'header');
71
+ headerPart.appendChild(headerSlot);
72
+
73
+ // Replace default slot with "form" named slot
83
74
  const contentPart = memoizedTemplate.content.querySelector('[part="content"]');
84
75
  const defaultSlot = contentPart.querySelector('slot:not([name])');
85
- contentPart.removeChild(defaultSlot);
86
- contentPart.appendChild(editorTemplate.content.cloneNode(true));
76
+ defaultSlot.setAttribute('name', 'form');
77
+
78
+ // Replace footer slot with button named slots
79
+ const footerPart = memoizedTemplate.content.querySelector('[part="footer"]');
80
+ footerPart.setAttribute('role', 'toolbar');
81
+ const footerSlot = footerPart.querySelector('slot');
82
+ footerPart.removeChild(footerSlot);
83
+ footerPart.appendChild(footerTemplate.content.cloneNode(true));
87
84
  }
88
85
  return memoizedTemplate;
89
86
  }
87
+
88
+ /**
89
+ * @protected
90
+ * @override
91
+ */
92
+ _headerFooterRendererChange(headerRenderer, footerRenderer, opened) {
93
+ super._headerFooterRendererChange(headerRenderer, footerRenderer, opened);
94
+
95
+ // CRUD has header and footer but does not use renderers
96
+ this.setAttribute('has-header', '');
97
+ this.setAttribute('has-footer', '');
98
+ }
90
99
  }
91
100
 
92
101
  customElements.define('vaadin-crud-dialog-overlay', CrudDialogOverlay);
@@ -36,7 +36,7 @@ class CrudEditColumn extends GridColumn {
36
36
  */
37
37
  width: {
38
38
  type: String,
39
- value: '4em'
39
+ value: '4em',
40
40
  },
41
41
 
42
42
  /**
@@ -45,11 +45,11 @@ class CrudEditColumn extends GridColumn {
45
45
  */
46
46
  flexGrow: {
47
47
  type: Number,
48
- value: 0
48
+ value: 0,
49
49
  },
50
50
 
51
51
  /** The arial-label for the edit button */
52
- ariaLabel: String
52
+ ariaLabel: String,
53
53
  };
54
54
  }
55
55
 
@@ -44,7 +44,7 @@ class CrudEdit extends Button {
44
44
  __onClick(e) {
45
45
  const tr = e.target.parentElement.assignedSlot.parentElement.parentElement;
46
46
  tr.dispatchEvent(
47
- new CustomEvent('edit', { detail: { item: tr._item, index: tr.index }, bubbles: true, composed: true })
47
+ new CustomEvent('edit', { detail: { item: tr._item, index: tr.index }, bubbles: true, composed: true }),
48
48
  );
49
49
  }
50
50
 
@@ -25,7 +25,7 @@ class CrudForm extends IncludedMixin(FormLayout) {
25
25
  * The item being edited.
26
26
  * @type {unknown}
27
27
  */
28
- item: Object
28
+ item: Object,
29
29
  };
30
30
  }
31
31
 
@@ -44,7 +44,7 @@ class CrudGrid extends IncludedMixin(Grid) {
44
44
  noHead: Boolean,
45
45
 
46
46
  /** @private */
47
- __hideEditColumn: Boolean
47
+ __hideEditColumn: Boolean,
48
48
  };
49
49
  }
50
50
 
@@ -54,7 +54,7 @@ class CrudGrid extends IncludedMixin(Grid) {
54
54
 
55
55
  /** @private */
56
56
  __onItemsChange(items) {
57
- if ((!this.dataProvider || this.dataProvider == this._arrayDataProvider) && !this.include && items && items[0]) {
57
+ if ((!this.dataProvider || this.dataProvider === this._arrayDataProvider) && !this.include && items && items[0]) {
58
58
  this._configure(items[0]);
59
59
  }
60
60
  }
@@ -70,7 +70,9 @@ class CrudGrid extends IncludedMixin(Grid) {
70
70
  __toggleEditColumn() {
71
71
  const el = this.querySelector('vaadin-crud-edit-column');
72
72
  if (this.hideEditColumn) {
73
- el && this.removeChild(el);
73
+ if (el) {
74
+ this.removeChild(el);
75
+ }
74
76
  } else if (!el) {
75
77
  this.appendChild(document.createElement('vaadin-crud-edit-column'));
76
78
  }
@@ -79,7 +81,7 @@ class CrudGrid extends IncludedMixin(Grid) {
79
81
  /** @private */
80
82
  __dataProviderWrapper(params, callback) {
81
83
  this.__dataProvider(params, (items, size) => {
82
- if (this.innerHTML == '' && !this.include && items[0]) {
84
+ if (this.innerHTML === '' && !this.include && items[0]) {
83
85
  this._configure(items[0]);
84
86
  }
85
87
  callback(items, size);
@@ -91,9 +93,9 @@ class CrudGrid extends IncludedMixin(Grid) {
91
93
  * @private
92
94
  */
93
95
  _dataProviderChanged(dataProvider, oldDataProvider) {
94
- if (this._arrayDataProvider == dataProvider) {
96
+ if (this._arrayDataProvider === dataProvider) {
95
97
  super._dataProviderChanged(dataProvider, oldDataProvider);
96
- } else if (this.__dataProviderWrapper != dataProvider) {
98
+ } else if (this.__dataProviderWrapper !== dataProvider) {
97
99
  this.innerHTML = '';
98
100
  this.__dataProvider = dataProvider;
99
101
  this.dataProvider = this.__dataProviderWrapper;
@@ -195,8 +197,10 @@ class CrudGrid extends IncludedMixin(Grid) {
195
197
  textField.setAttribute('slot', 'filter');
196
198
  textField.setAttribute('focus-target', true);
197
199
  textField.style.width = '100%';
198
- this.noSort && (textField.placeholder = label);
199
- textField.addEventListener('value-changed', function (event) {
200
+ if (this.noSort) {
201
+ textField.placeholder = label;
202
+ }
203
+ textField.addEventListener('value-changed', (event) => {
200
204
  filter.value = event.detail.value;
201
205
  });
202
206
 
@@ -22,7 +22,7 @@ export const IncludedMixin = (superClass) =>
22
22
  */
23
23
  exclude: {
24
24
  value: '^_',
25
- observer: '__onExcludeChange'
25
+ observer: '__onExcludeChange',
26
26
  },
27
27
 
28
28
  /**
@@ -33,21 +33,21 @@ export const IncludedMixin = (superClass) =>
33
33
  * @type {string | !Array<string> | undefined}
34
34
  */
35
35
  include: {
36
- observer: '__onIncludeChange'
37
- }
36
+ observer: '__onIncludeChange',
37
+ },
38
38
  };
39
39
  }
40
40
 
41
41
  /** @private */
42
42
  __onExcludeChange(exclude) {
43
- if (typeof exclude == 'string') {
43
+ if (typeof exclude === 'string') {
44
44
  this.exclude = exclude ? RegExp(exclude.replace(/, */g, '|'), 'i') : undefined;
45
45
  }
46
46
  }
47
47
 
48
48
  /** @private */
49
49
  __onIncludeChange(include) {
50
- if (typeof include == 'string') {
50
+ if (typeof include === 'string') {
51
51
  this.include = include ? include.split(/, */) : undefined;
52
52
  } else if (!this._fields && Array.isArray(include)) {
53
53
  const item = {};
@@ -383,13 +383,13 @@ declare class Crud<Item> extends ControllerMixin(ElementMixin(ThemableMixin(HTML
383
383
  addEventListener<K extends keyof CrudEventMap<Item>>(
384
384
  type: K,
385
385
  listener: (this: Crud<Item>, ev: CrudEventMap<Item>[K]) => void,
386
- options?: boolean | AddEventListenerOptions
386
+ options?: boolean | AddEventListenerOptions,
387
387
  ): void;
388
388
 
389
389
  removeEventListener<K extends keyof CrudEventMap<Item>>(
390
390
  type: K,
391
391
  listener: (this: Crud<Item>, ev: CrudEventMap<Item>[K]) => void,
392
- options?: boolean | EventListenerOptions
392
+ options?: boolean | EventListenerOptions,
393
393
  ): void;
394
394
  }
395
395
 
@@ -19,12 +19,6 @@ import { MediaQueryController } from '@vaadin/component-base/src/media-query-con
19
19
  import { SlotMixin } from '@vaadin/component-base/src/slot-mixin.js';
20
20
  import { ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js';
21
21
 
22
- const HOST_PROPS = {
23
- save: [{ attr: 'disabled', prop: '__isDirty', parseProp: '__isSaveBtnDisabled' }, { prop: 'i18n.saveItem' }],
24
- cancel: [{ prop: 'i18n.cancel' }],
25
- delete: [{ attr: 'hidden', prop: '__isNew', parseProp: (prop) => prop }, { prop: 'i18n.deleteItem' }]
26
- };
27
-
28
22
  /**
29
23
  * `<vaadin-crud>` is a Web Component for [CRUD](https://en.wikipedia.org/wiki/Create,_read,_update_and_delete) operations.
30
24
  *
@@ -324,7 +318,7 @@ class Crud extends SlotMixin(ControllerMixin(ElementMixin(ThemableMixin(PolymerE
324
318
  */
325
319
  _grid: {
326
320
  type: HTMLElement,
327
- observer: '__onGridChange'
321
+ observer: '__gridChanged',
328
322
  },
329
323
 
330
324
  /**
@@ -333,7 +327,7 @@ class Crud extends SlotMixin(ControllerMixin(ElementMixin(ThemableMixin(PolymerE
333
327
  */
334
328
  _form: {
335
329
  type: HTMLElement,
336
- observer: '__onFormChange'
330
+ observer: '__formChanged',
337
331
  },
338
332
 
339
333
  /**
@@ -342,7 +336,7 @@ class Crud extends SlotMixin(ControllerMixin(ElementMixin(ThemableMixin(PolymerE
342
336
  */
343
337
  _saveButton: {
344
338
  type: HTMLElement,
345
- observer: '__onSaveButtonChange'
339
+ observer: '__saveButtonChanged',
346
340
  },
347
341
 
348
342
  /**
@@ -351,7 +345,7 @@ class Crud extends SlotMixin(ControllerMixin(ElementMixin(ThemableMixin(PolymerE
351
345
  */
352
346
  _deleteButton: {
353
347
  type: HTMLElement,
354
- observer: '__onDeleteButtonChange'
348
+ observer: '__deleteButtonChanged',
355
349
  },
356
350
 
357
351
  /**
@@ -360,7 +354,7 @@ class Crud extends SlotMixin(ControllerMixin(ElementMixin(ThemableMixin(PolymerE
360
354
  */
361
355
  _cancelButton: {
362
356
  type: HTMLElement,
363
- observer: '__onCancelButtonChange'
357
+ observer: '__cancelButtonChanged',
364
358
  },
365
359
 
366
360
  /**
@@ -368,7 +362,7 @@ class Crud extends SlotMixin(ControllerMixin(ElementMixin(ThemableMixin(PolymerE
368
362
  * @private
369
363
  */
370
364
  _headerNode: {
371
- type: HTMLElement
365
+ type: HTMLElement,
372
366
  },
373
367
 
374
368
  /**
@@ -378,7 +372,7 @@ class Crud extends SlotMixin(ControllerMixin(ElementMixin(ThemableMixin(PolymerE
378
372
  items: {
379
373
  type: Array,
380
374
  notify: true,
381
- observer: '__onItemsChange'
375
+ observer: '__itemsChanged',
382
376
  },
383
377
 
384
378
  /**
@@ -387,8 +381,8 @@ class Crud extends SlotMixin(ControllerMixin(ElementMixin(ThemableMixin(PolymerE
387
381
  */
388
382
  editedItem: {
389
383
  type: Object,
390
- observer: '__onItemChange',
391
- notify: true
384
+ observer: '__editedItemChanged',
385
+ notify: true,
392
386
  },
393
387
 
394
388
  /**
@@ -405,7 +399,7 @@ class Crud extends SlotMixin(ControllerMixin(ElementMixin(ThemableMixin(PolymerE
405
399
  type: String,
406
400
  value: '',
407
401
  reflectToAttribute: true,
408
- observer: '__onEditorPositionChange'
402
+ observer: '__editorPositionChanged',
409
403
  },
410
404
 
411
405
  /**
@@ -416,7 +410,7 @@ class Crud extends SlotMixin(ControllerMixin(ElementMixin(ThemableMixin(PolymerE
416
410
  */
417
411
  editOnClick: {
418
412
  type: Boolean,
419
- value: false
413
+ value: false,
420
414
  },
421
415
 
422
416
  /**
@@ -434,7 +428,7 @@ class Crud extends SlotMixin(ControllerMixin(ElementMixin(ThemableMixin(PolymerE
434
428
  */
435
429
  dataProvider: {
436
430
  type: Function,
437
- observer: '__onDataProviderChange'
431
+ observer: '__dataProviderChanged',
438
432
  },
439
433
 
440
434
  /**
@@ -482,7 +476,7 @@ class Crud extends SlotMixin(ControllerMixin(ElementMixin(ThemableMixin(PolymerE
482
476
  type: Boolean,
483
477
  reflectToAttribute: true,
484
478
  notify: true,
485
- observer: '__onOpenedChanged'
479
+ observer: '__editorOpenedChanged',
486
480
  },
487
481
 
488
482
  /**
@@ -492,7 +486,7 @@ class Crud extends SlotMixin(ControllerMixin(ElementMixin(ThemableMixin(PolymerE
492
486
  size: {
493
487
  type: Number,
494
488
  readOnly: true,
495
- notify: true
489
+ notify: true,
496
490
  },
497
491
 
498
492
  /**
@@ -503,7 +497,7 @@ class Crud extends SlotMixin(ControllerMixin(ElementMixin(ThemableMixin(PolymerE
503
497
  noToolbar: {
504
498
  type: Boolean,
505
499
  value: false,
506
- reflectToAttribute: true
500
+ reflectToAttribute: true,
507
501
  },
508
502
 
509
503
  /**
@@ -561,20 +555,20 @@ class Crud extends SlotMixin(ControllerMixin(ElementMixin(ThemableMixin(PolymerE
561
555
  content: 'Are you sure you want to delete this item? This action cannot be undone.',
562
556
  button: {
563
557
  confirm: 'Delete',
564
- dismiss: 'Cancel'
565
- }
558
+ dismiss: 'Cancel',
559
+ },
566
560
  },
567
561
  cancel: {
568
562
  title: 'Discard changes',
569
563
  content: 'There are unsaved changes to this item.',
570
564
  button: {
571
565
  confirm: 'Discard',
572
- dismiss: 'Cancel'
573
- }
574
- }
575
- }
566
+ dismiss: 'Cancel',
567
+ },
568
+ },
569
+ },
576
570
  };
577
- }
571
+ },
578
572
  },
579
573
 
580
574
  /** @private */
@@ -592,7 +586,7 @@ class Crud extends SlotMixin(ControllerMixin(ElementMixin(ThemableMixin(PolymerE
592
586
  */
593
587
  _fullscreen: {
594
588
  type: Boolean,
595
- observer: '__fullscreenChanged'
589
+ observer: '__fullscreenChanged',
596
590
  },
597
591
 
598
592
  /**
@@ -600,24 +594,20 @@ class Crud extends SlotMixin(ControllerMixin(ElementMixin(ThemableMixin(PolymerE
600
594
  * @protected
601
595
  */
602
596
  _fullscreenMediaQuery: {
603
- value: '(max-width: 600px), (max-height: 600px)'
604
- }
597
+ value: '(max-width: 600px), (max-height: 600px)',
598
+ },
605
599
  };
606
600
  }
607
601
 
608
602
  static get observers() {
609
603
  return [
610
- '__headerNodeChanged(_headerNode, __isNew, i18n.newItem, i18n.editItem)',
611
- '__formChanged(_form, _theme, include, exclude)',
612
- '__onI18Change(i18n, _grid)',
613
- '__onEditOnClickChange(editOnClick, _grid)',
614
- '__hostPropsChanged(' +
615
- HOST_PROPS.save.map(({ prop }) => prop).join(',') +
616
- ',' +
617
- HOST_PROPS.cancel.map(({ prop }) => prop).join(',') +
618
- ',' +
619
- HOST_PROPS.delete.map(({ prop }) => prop).join(',') +
620
- ')'
604
+ '__headerPropsChanged(_headerNode, __isNew, i18n.newItem, i18n.editItem)',
605
+ '__formPropsChanged(_form, _theme, include, exclude)',
606
+ '__i18nChanged(i18n, _grid)',
607
+ '__editOnClickChanged(editOnClick, _grid)',
608
+ '__saveButtonPropsChanged(_saveButton, i18n.saveItem, __isDirty)',
609
+ '__cancelButtonPropsChanged(_cancelButton, i18n.cancel)',
610
+ '__deleteButtonPropsChanged(_deleteButton, i18n.deleteItem, __isNew)',
621
611
  ];
622
612
  }
623
613
 
@@ -632,6 +622,11 @@ class Crud extends SlotMixin(ControllerMixin(ElementMixin(ThemableMixin(PolymerE
632
622
  }
633
623
  }
634
624
 
625
+ /** @private */
626
+ static _isValidEditorPosition(editorPosition) {
627
+ return ['bottom', 'aside'].includes(editorPosition);
628
+ }
629
+
635
630
  /** @protected */
636
631
  get slots() {
637
632
  // NOTE: order in which the toolbar buttons are listed matters.
@@ -660,7 +655,7 @@ class Crud extends SlotMixin(ControllerMixin(ElementMixin(ThemableMixin(PolymerE
660
655
  button.id = 'delete';
661
656
  button.setAttribute('theme', 'tertiary error');
662
657
  return button;
663
- }
658
+ },
664
659
  };
665
660
  }
666
661
 
@@ -674,51 +669,53 @@ class Crud extends SlotMixin(ControllerMixin(ElementMixin(ThemableMixin(PolymerE
674
669
  /** @protected */
675
670
  ready() {
676
671
  super.ready();
677
- this.__editListener = (e) => this.__onCrudGridEdit(e);
678
- this.__changeListener = (e) => this.__onFormChanges(e);
679
- this.__saveBound = this.__save.bind(this);
680
- this.__cancelBound = this.__cancel.bind(this);
681
- this.__deleteBound = this.__delete.bind(this);
682
- this.__gridSizeListener = this.__onGridSizeChanges.bind(this);
683
- this.__boundEditOnClickListener = this.__editOnClickListener.bind(this);
672
+ this.__save = this.__save.bind(this);
673
+ this.__cancel = this.__cancel.bind(this);
674
+ this.__delete = this.__delete.bind(this);
675
+ this.__onFormChange = this.__onFormChange.bind(this);
676
+ this.__onGridEdit = this.__onGridEdit.bind(this);
677
+ this.__onGridSizeChanged = this.__onGridSizeChanged.bind(this);
678
+ this.__onGridActiveItemChanged = this.__onGridActiveItemChanged.bind(this);
684
679
  this._grid = this.$.grid;
685
- this.$.dialog.$.overlay.addEventListener('vaadin-overlay-outside-click', this.__cancelBound);
686
- this.$.dialog.$.overlay.addEventListener('vaadin-overlay-escape-press', this.__cancelBound);
687
- // Initialize the default buttons
688
- this.__propagateHostAttributes();
680
+ this.$.dialog.$.overlay.addEventListener('vaadin-overlay-outside-click', this.__cancel);
681
+ this.$.dialog.$.overlay.addEventListener('vaadin-overlay-escape-press', this.__cancel);
689
682
 
690
683
  this.addController(
691
684
  new MediaQueryController(this._fullscreenMediaQuery, (matches) => {
692
685
  this._fullscreen = matches;
693
- })
686
+ }),
694
687
  );
695
688
  }
696
689
 
697
- /** @private */
690
+ /**
691
+ * @param {boolean} isDirty
692
+ * @private
693
+ */
698
694
  __isSaveBtnDisabled(isDirty) {
699
695
  // Used instead of isDirty property binding in order to enable overriding of the behavior
700
696
  // by overriding the method (i.e. from Flow component)
701
697
  return !isDirty;
702
698
  }
703
699
 
704
- /** @private */
705
- __headerNodeChanged(headerNode, isNew, newItem, editItem) {
700
+ /**
701
+ * @param {HTMLElement | undefined} headerNode
702
+ * @param {boolean} isNew
703
+ * @param {string} i18nNewItem
704
+ * @param {string} i18nEditItem
705
+ * @private
706
+ */
707
+ __headerPropsChanged(headerNode, isNew, i18nNewItem, i18nEditItem) {
706
708
  if (headerNode) {
707
- headerNode.textContent = isNew ? newItem : editItem;
709
+ headerNode.textContent = isNew ? i18nNewItem : i18nEditItem;
708
710
  }
709
711
  }
710
712
 
711
- /** @private */
712
- __formChanged(form, theme, include, exclude) {
713
- if (form) {
714
- form.include = include;
715
- form.exclude = exclude;
716
- form.setAttribute('theme', theme);
717
- }
718
- }
719
-
720
- /** @private */
721
- __onI18Change(i18n, grid) {
713
+ /**
714
+ * @param {CrudI18n} i18n
715
+ * @param {CrudGrid | Grid} grid
716
+ * @private
717
+ */
718
+ __i18nChanged(i18n, grid) {
722
719
  if (!grid) {
723
720
  return;
724
721
  }
@@ -731,7 +728,7 @@ class Crud extends SlotMixin(ControllerMixin(ElementMixin(ThemableMixin(PolymerE
731
728
  }
732
729
 
733
730
  /** @private */
734
- __onEditorPositionChange(editorPosition) {
731
+ __editorPositionChanged(editorPosition) {
735
732
  if (Crud._isValidEditorPosition(editorPosition)) {
736
733
  return;
737
734
  }
@@ -739,16 +736,11 @@ class Crud extends SlotMixin(ControllerMixin(ElementMixin(ThemableMixin(PolymerE
739
736
  }
740
737
 
741
738
  /** @private */
742
- static _isValidEditorPosition(editorPosition) {
743
- return ['bottom', 'aside'].indexOf(editorPosition) != -1;
744
- }
745
-
746
- /** @private */
747
- __onOpenedChanged(opened, old) {
748
- if (!opened && old) {
739
+ __editorOpenedChanged(opened, oldOpened) {
740
+ if (!opened && oldOpened) {
749
741
  this.__closeEditor();
750
742
  } else {
751
- this.__onFormChange(this._form);
743
+ this.__formChanged(this._form);
752
744
  }
753
745
 
754
746
  if (opened) {
@@ -849,169 +841,205 @@ class Crud extends SlotMixin(ControllerMixin(ElementMixin(ThemableMixin(PolymerE
849
841
  return;
850
842
  }
851
843
 
852
- if (slotAttributeValue == 'grid') {
844
+ if (slotAttributeValue === 'grid') {
853
845
  // Force to remove listener on previous grid first
854
- this.__onEditOnClickChange(false, this._grid);
846
+ this.__editOnClickChanged(false, this._grid);
855
847
  this._grid = node;
856
- this.__onEditOnClickChange(this.editOnClick, this._grid);
857
- } else if (slotAttributeValue == 'form') {
848
+ this.__editOnClickChanged(this.editOnClick, this._grid);
849
+ } else if (slotAttributeValue === 'form') {
858
850
  this._form = node;
859
851
  } else if (slotAttributeValue.indexOf('button') >= 0) {
860
852
  const [button] = slotAttributeValue.split('-');
861
853
  this[`_${button}Button`] = node;
862
- } else if (slotAttributeValue == 'header') {
854
+ } else if (slotAttributeValue === 'header') {
863
855
  this._headerNode = node;
864
856
  }
865
857
  });
858
+
859
+ if (this.editorOpened) {
860
+ this.__ensureChildren();
861
+ }
866
862
  }
867
863
 
868
864
  /** @private */
869
- __onCrudGridEdit(e) {
870
- e.stopPropagation();
871
- this.__confirmBeforeChangingEditedItem(e.detail.item);
865
+ __onGridEdit(event) {
866
+ event.stopPropagation();
867
+ this.__confirmBeforeChangingEditedItem(event.detail.item);
872
868
  }
873
869
 
874
870
  /** @private */
875
- __onFormChanges() {
871
+ __onFormChange() {
876
872
  this.__isDirty = true;
877
873
  }
878
874
 
879
875
  /** @private */
880
- __onGridSizeChanges() {
876
+ __onGridSizeChanged() {
881
877
  this._setSize(this._grid.size);
882
878
  }
883
879
 
884
- /** @private */
885
- __onGridChange(grid, old) {
886
- if (old) {
887
- old.removeEventListener('edit', this.__editListener);
888
- old.removeEventListener('size-changed', this.__gridSizeListener);
880
+ /**
881
+ * @param {CrudGrid | Grid} grid
882
+ * @param {CrudGrid | Grid | undefined} oldGrid
883
+ * @private
884
+ */
885
+ __gridChanged(grid, oldGrid) {
886
+ if (oldGrid) {
887
+ oldGrid.removeEventListener('edit', this.__onGridEdit);
888
+ oldGrid.removeEventListener('size-changed', this.__onGridSizeChanged);
889
889
  }
890
890
  if (this.dataProvider) {
891
- this.__onDataProviderChange(this.dataProvider);
891
+ this.__dataProviderChanged(this.dataProvider);
892
892
  }
893
893
  if (this.items) {
894
- this.__onItemsChange(this.items);
894
+ this.__itemsChanged(this.items);
895
895
  }
896
896
  if (this.editedItem) {
897
- this.__onItemChange(this.editedItem);
897
+ this.__editedItemChanged(this.editedItem);
898
898
  }
899
- grid.addEventListener('edit', this.__editListener);
900
- grid.addEventListener('size-changed', this.__gridSizeListener);
901
- this.__onGridSizeChanges();
899
+ grid.addEventListener('edit', this.__onGridEdit);
900
+ grid.addEventListener('size-changed', this.__onGridSizeChanged);
901
+ this.__onGridSizeChanged();
902
902
  }
903
903
 
904
- /** @private */
905
- __onFormChange(form, old) {
906
- if (old && old.parentElement) {
907
- old.parentElement && old.parentElement.removeChild(old);
908
- old.removeEventListener('change', this.__changeListener);
909
- old.removeEventListener('input', this.__changeListener);
904
+ /**
905
+ * @param {HTMLElement | undefined | null} form
906
+ * @param {HTMLElement | undefined | null} oldForm
907
+ * @private
908
+ */
909
+ __formChanged(form, oldForm) {
910
+ if (oldForm && oldForm.parentElement) {
911
+ oldForm.parentElement.removeChild(oldForm);
912
+ oldForm.removeEventListener('change', this.__onFormChange);
913
+ oldForm.removeEventListener('input', this.__onFormChange);
910
914
  }
911
915
  if (!form) {
912
916
  return;
913
917
  }
914
918
  if (this.items) {
915
- this.__onItemsChange(this.items);
919
+ this.__itemsChanged(this.items);
916
920
  }
917
921
  if (this.editedItem) {
918
- this.__onItemChange(this.editedItem);
922
+ this.__editedItemChanged(this.editedItem);
919
923
  }
920
- form.addEventListener('change', this.__changeListener);
921
- form.addEventListener('input', this.__changeListener);
924
+ form.addEventListener('change', this.__onFormChange);
925
+ form.addEventListener('input', this.__onFormChange);
922
926
  }
923
927
 
924
- /** @private */
925
- __onSaveButtonChange(save, old) {
926
- this.__setupSlottedButton(save, old, this.__saveBound);
927
- }
928
-
929
- /** @private */
930
- __onDeleteButtonChange(deleteButton, old) {
931
- this.__setupSlottedButton(deleteButton, old, this.__deleteBound);
928
+ /**
929
+ * @param {HTMLElement | undefined} form
930
+ * @param {string} theme
931
+ * @param {string | string[] | undefined} include
932
+ * @param {string | RegExp} exclude
933
+ * @private
934
+ */
935
+ __formPropsChanged(form, theme, include, exclude) {
936
+ if (form) {
937
+ form.include = include;
938
+ form.exclude = exclude;
939
+ form.setAttribute('theme', theme);
940
+ }
932
941
  }
933
942
 
934
- /** @private */
935
- __onCancelButtonChange(cancel, old) {
936
- this.__setupSlottedButton(cancel, old, this.__cancelBound);
943
+ /**
944
+ * @param {HTMLElement} saveButton
945
+ * @param {HTMLElement | undefined} oldSaveButton
946
+ * @private
947
+ */
948
+ __saveButtonChanged(saveButton, oldSaveButton) {
949
+ this.__setupSlottedButton(saveButton, oldSaveButton, this.__save);
937
950
  }
938
951
 
939
- /** @private */
940
- __setupSlottedButton(slottedButton, currentButton, clickListener) {
941
- if (currentButton && currentButton.parentElement) {
942
- currentButton.parentElement.removeChild(currentButton);
952
+ /**
953
+ * @param {HTMLElement | undefined} saveButton
954
+ * @param {string} i18nLabel
955
+ * @param {boolean} isDirty
956
+ * @private
957
+ */
958
+ __saveButtonPropsChanged(saveButton, i18nLabel, isDirty) {
959
+ if (saveButton) {
960
+ saveButton.toggleAttribute('disabled', this.__isSaveBtnDisabled(isDirty));
961
+ saveButton.textContent = i18nLabel;
943
962
  }
944
-
945
- slottedButton.addEventListener('click', clickListener);
946
963
  }
947
964
 
948
- /** @private */
949
- __hostPropsChanged() {
950
- this.__propagateHostAttributes();
965
+ /**
966
+ * @param {HTMLElement} deleteButton
967
+ * @param {HTMLElement | undefined} oldDeleteButton
968
+ * @private
969
+ */
970
+ __deleteButtonChanged(deleteButton, oldDeleteButton) {
971
+ this.__setupSlottedButton(deleteButton, oldDeleteButton, this.__delete);
951
972
  }
952
973
 
953
- /** @private */
954
- __propagateHostAttributes() {
955
- this.__propagateHostAttributesToButton(this._saveButton, HOST_PROPS.save);
956
- this.__propagateHostAttributesToButton(this._cancelButton, HOST_PROPS.cancel);
957
- this.__propagateHostAttributesToButton(this._deleteButton, HOST_PROPS.delete);
974
+ /**
975
+ * @param {HTMLElement | undefined} deleteButton
976
+ * @param {string} i18nLabel
977
+ * @param {boolean} isNew
978
+ * @private
979
+ */
980
+ __deleteButtonPropsChanged(deleteButton, i18nLabel, isNew) {
981
+ if (deleteButton) {
982
+ deleteButton.textContent = i18nLabel;
983
+ deleteButton.toggleAttribute('hidden', isNew);
984
+ }
958
985
  }
959
986
 
960
- /** @private */
961
- __propagateHostAttributesToButton(button, props) {
962
- // Ensure the slotted button element is present in the DOM.
963
- // This is needed because the observer runs before `ready`.
964
- if (button) {
965
- props.forEach(({ attr, prop, parseProp }) => {
966
- if (prop.indexOf('i18n') >= 0) {
967
- button.textContent = this.i18n[prop.split('.')[1]];
968
- } else {
969
- if (typeof parseProp === 'string') {
970
- this._setOrToggleAttribute(attr, this[parseProp](this[prop]), button);
971
- return;
972
- }
987
+ /**
988
+ * @param {HTMLElement} cancelButton
989
+ * @param {HTMLElement | undefined} oldCancelButton
990
+ * @private
991
+ */
992
+ __cancelButtonChanged(cancelButton, oldCancelButton) {
993
+ this.__setupSlottedButton(cancelButton, oldCancelButton, this.__cancel);
994
+ }
973
995
 
974
- this._setOrToggleAttribute(attr, parseProp(this[prop]), button);
975
- }
976
- });
996
+ /**
997
+ * @param {HTMLElement | undefined} saveButton
998
+ * @param {string} i18nLabel
999
+ * @private
1000
+ */
1001
+ __cancelButtonPropsChanged(cancelButton, i18nLabel) {
1002
+ if (cancelButton) {
1003
+ cancelButton.textContent = i18nLabel;
977
1004
  }
978
1005
  }
979
1006
 
980
- /** @private */
981
- _setOrToggleAttribute(name, value, node) {
982
- if (!name || !node) {
983
- return;
1007
+ /**
1008
+ * @param {HTMLElement} newButton
1009
+ * @param {HTMLElement | undefined | null} oldButton
1010
+ * @param {Function} clickListener
1011
+ * @private
1012
+ */
1013
+ __setupSlottedButton(newButton, oldButton, clickListener) {
1014
+ if (oldButton && oldButton.parentElement) {
1015
+ oldButton.parentElement.removeChild(oldButton);
984
1016
  }
985
1017
 
986
- if (value) {
987
- node.setAttribute(name, typeof value === 'boolean' ? '' : value);
988
- } else {
989
- node.removeAttribute(name);
990
- }
1018
+ newButton.addEventListener('click', clickListener);
991
1019
  }
992
1020
 
993
1021
  /** @private */
994
- __onDataProviderChange(dataProvider) {
1022
+ __dataProviderChanged(dataProvider) {
995
1023
  if (this._grid) {
996
1024
  this._grid.dataProvider = this.__createDataProviderProxy(dataProvider);
997
1025
  }
998
1026
  }
999
1027
 
1000
1028
  /** @private */
1001
- __onEditOnClickChange(rowToEditChange, _grid) {
1029
+ __editOnClickChanged(editOnClick, _grid) {
1002
1030
  if (!_grid) {
1003
1031
  return;
1004
1032
  }
1005
1033
 
1006
- if (rowToEditChange) {
1007
- _grid.addEventListener('active-item-changed', this.__boundEditOnClickListener);
1034
+ if (editOnClick) {
1035
+ _grid.addEventListener('active-item-changed', this.__onGridActiveItemChanged);
1008
1036
  } else {
1009
- _grid.removeEventListener('active-item-changed', this.__boundEditOnClickListener);
1037
+ _grid.removeEventListener('active-item-changed', this.__onGridActiveItemChanged);
1010
1038
  }
1011
1039
  }
1012
1040
 
1013
1041
  /** @private */
1014
- __editOnClickListener(event) {
1042
+ __onGridActiveItemChanged(event) {
1015
1043
  const item = event.detail.value;
1016
1044
  if (this.editorOpened && this.__isDirty) {
1017
1045
  this.__confirmBeforeChangingEditedItem(item);
@@ -1075,7 +1103,7 @@ class Crud extends SlotMixin(ControllerMixin(ElementMixin(ThemableMixin(PolymerE
1075
1103
  }
1076
1104
 
1077
1105
  /** @private */
1078
- __onItemsChange(items) {
1106
+ __itemsChanged(items) {
1079
1107
  if (this.items && this.items[0]) {
1080
1108
  this.__model = items[0];
1081
1109
  }
@@ -1086,7 +1114,7 @@ class Crud extends SlotMixin(ControllerMixin(ElementMixin(ThemableMixin(PolymerE
1086
1114
  }
1087
1115
 
1088
1116
  /** @private */
1089
- __onItemChange(item) {
1117
+ __editedItemChanged(item) {
1090
1118
  if (!this._form) {
1091
1119
  return;
1092
1120
  }
@@ -1097,14 +1125,16 @@ class Crud extends SlotMixin(ControllerMixin(ElementMixin(ThemableMixin(PolymerE
1097
1125
  } else {
1098
1126
  console.warn(
1099
1127
  '<vaadin-crud> Unable to autoconfigure form because the data structure is unknown. ' +
1100
- 'Either specify `include` or ensure at least one item is available beforehand.'
1128
+ 'Either specify `include` or ensure at least one item is available beforehand.',
1101
1129
  );
1102
1130
  }
1103
1131
  }
1104
1132
  this._form.item = item;
1105
1133
  this._fields.forEach((e) => {
1106
1134
  const path = e.path || e.getAttribute('path');
1107
- path && (e.value = this.get(path, item));
1135
+ if (path) {
1136
+ e.value = this.get(path, item);
1137
+ }
1108
1138
  });
1109
1139
 
1110
1140
  this.__isNew = this.__isNew || (this.items && this.items.indexOf(item) < 0);
@@ -1149,7 +1179,7 @@ class Crud extends SlotMixin(ControllerMixin(ElementMixin(ThemableMixin(PolymerE
1149
1179
  /** @private */
1150
1180
  __new(event) {
1151
1181
  // This allows listening to parent element and fire only when clicking on default or custom new-button.
1152
- if (event.composedPath().filter((e) => e.nodeType == 1 && e.hasAttribute('new-button'))[0]) {
1182
+ if (event.composedPath().filter((e) => e.nodeType === 1 && e.hasAttribute('new-button'))[0]) {
1153
1183
  this.__confirmBeforeChangingEditedItem(null, true);
1154
1184
  }
1155
1185
  }
@@ -1168,7 +1198,7 @@ class Crud extends SlotMixin(ControllerMixin(ElementMixin(ThemableMixin(PolymerE
1168
1198
  this.__isDirty = false;
1169
1199
  this.__isNew = !item;
1170
1200
  const evt = this.dispatchEvent(
1171
- new CustomEvent(this.__isNew ? 'new' : 'edit', { detail: { item: item }, cancelable: true })
1201
+ new CustomEvent(this.__isNew ? 'new' : 'edit', { detail: { item: item }, cancelable: true }),
1172
1202
  );
1173
1203
  if (evt) {
1174
1204
  this.editedItem = item || {};
@@ -1186,7 +1216,9 @@ class Crud extends SlotMixin(ControllerMixin(ElementMixin(ThemableMixin(PolymerE
1186
1216
  const item = { ...this.editedItem };
1187
1217
  this._fields.forEach((e) => {
1188
1218
  const path = e.path || e.getAttribute('path');
1189
- path && this.__set(path, e.value, item);
1219
+ if (path) {
1220
+ this.__set(path, e.value, item);
1221
+ }
1190
1222
  });
1191
1223
  const evt = this.dispatchEvent(new CustomEvent('save', { detail: { item: item }, cancelable: true }));
1192
1224
  if (evt) {
@@ -30,7 +30,7 @@ registerStyles(
30
30
  left: calc((var(--lumo-size-m) - var(--lumo-size-s)) / -2);
31
31
  }
32
32
  `,
33
- { moduleId: 'lumo-crud-grid-edit' }
33
+ { moduleId: 'lumo-crud-grid-edit' },
34
34
  );
35
35
 
36
36
  /**
@@ -42,20 +42,6 @@ const editorStyles = css`
42
42
  margin-top: 0 !important;
43
43
  }
44
44
 
45
- [part='scroller'] {
46
- padding: var(--lumo-space-l);
47
- }
48
-
49
- [part='footer'] {
50
- background-color: var(--lumo-contrast-5pct);
51
- padding: var(--lumo-space-s);
52
- }
53
-
54
- [part='footer'] ::slotted(*) {
55
- margin-left: var(--lumo-space-s);
56
- margin-right: var(--lumo-space-s);
57
- }
58
-
59
45
  :host(:not([dir='rtl'])) ::slotted([slot='delete-button']) {
60
46
  margin-right: auto;
61
47
  }
@@ -74,6 +60,10 @@ registerStyles(
74
60
  font-family: var(--lumo-font-family);
75
61
  }
76
62
 
63
+ [part='scroller'] {
64
+ padding: var(--lumo-space-l);
65
+ }
66
+
77
67
  [part='toolbar'] {
78
68
  padding: var(--lumo-space-s) var(--lumo-space-m);
79
69
  background-color: var(--lumo-contrast-5pct);
@@ -93,6 +83,16 @@ registerStyles(
93
83
  border: 0;
94
84
  }
95
85
 
86
+ [part='footer'] {
87
+ background-color: var(--lumo-contrast-5pct);
88
+ padding: var(--lumo-space-s);
89
+ }
90
+
91
+ [part='footer'] ::slotted(*) {
92
+ margin-left: var(--lumo-space-s);
93
+ margin-right: var(--lumo-space-s);
94
+ }
95
+
96
96
  [part='editor'] {
97
97
  background: var(--lumo-base-color);
98
98
  box-sizing: border-box;
@@ -121,11 +121,24 @@ registerStyles(
121
121
  vaadin-grid-cell-content {
122
122
  text-overflow: ellipsis;
123
123
  }
124
- `
124
+ `,
125
125
  ],
126
- { moduleId: 'lumo-crud' }
126
+ { moduleId: 'lumo-crud' },
127
127
  );
128
128
 
129
- registerStyles('vaadin-crud-dialog-overlay', editorStyles, {
130
- moduleId: 'lumo-crud-dialog-overlay'
131
- });
129
+ registerStyles(
130
+ 'vaadin-crud-dialog-overlay',
131
+ [
132
+ editorStyles,
133
+ css`
134
+ [part='header'] ::slotted(h3) {
135
+ margin-top: 0 !important;
136
+ margin-bottom: 0 !important;
137
+ margin-inline-start: var(--lumo-space-s);
138
+ }
139
+ `,
140
+ ],
141
+ {
142
+ moduleId: 'lumo-crud-dialog-overlay',
143
+ },
144
+ );
@@ -37,7 +37,7 @@ registerStyles(
37
37
  left: 0;
38
38
  }
39
39
  `,
40
- { moduleId: 'material-crud-grid-edit' }
40
+ { moduleId: 'material-crud-grid-edit' },
41
41
  );
42
42
 
43
43
  /**
@@ -45,19 +45,8 @@ registerStyles(
45
45
  * They are applied to both `vaadin-crud` and `vaadin-crud-dialog-overlay` components.
46
46
  */
47
47
  const editorStyles = css`
48
- [part='scroller'] {
49
- padding: 16px;
50
- background: var(--material-background-color);
51
- }
52
-
53
48
  [part='footer'] {
54
49
  background-color: var(--material-secondary-background-color);
55
- padding: 8px 4px;
56
- }
57
-
58
- [part='footer'] ::slotted(*) {
59
- margin-left: 4px;
60
- margin-right: 4px;
61
50
  }
62
51
 
63
52
  :host(:not([dir='rtl'])) ::slotted([slot='delete-button']) {
@@ -78,11 +67,25 @@ registerStyles(
78
67
  font-family: var(--material-font-family);
79
68
  }
80
69
 
70
+ [part='scroller'] {
71
+ padding: 16px;
72
+ background: var(--material-background-color);
73
+ }
74
+
81
75
  [part='toolbar'] {
82
76
  padding: 8px;
83
77
  background-color: var(--material-secondary-background-color);
84
78
  }
85
79
 
80
+ [part='footer'] {
81
+ padding: 8px 4px;
82
+ }
83
+
84
+ [part='footer'] ::slotted(*) {
85
+ margin-left: 4px;
86
+ margin-right: 4px;
87
+ }
88
+
86
89
  :host(:not([editor-position=''])) [part='editor']:not([hidden]) {
87
90
  box-shadow: var(--material-shadow-elevation-12dp);
88
91
  }
@@ -101,9 +104,9 @@ registerStyles(
101
104
  margin: 0;
102
105
  padding: 0;
103
106
  }
104
- `
107
+ `,
105
108
  ],
106
- { moduleId: 'material-crud' }
109
+ { moduleId: 'material-crud' },
107
110
  );
108
111
 
109
112
  registerStyles(
@@ -125,7 +128,16 @@ registerStyles(
125
128
  :host([closing]) {
126
129
  animation: 0.25s material-overlay-dummy-animation;
127
130
  }
128
- `
131
+
132
+ [part='header'] ::slotted(h3) {
133
+ margin-top: 0 !important;
134
+ margin-bottom: 0 !important;
135
+ }
136
+
137
+ [part='content'] {
138
+ padding: 0 16px 16px;
139
+ }
140
+ `,
129
141
  ],
130
- { moduleId: 'material-crud-dialog-overlay' }
142
+ { moduleId: 'material-crud-dialog-overlay' },
131
143
  );