Define isChanged() before first snapshot + emit form-changed on control changes
isChanged() compared against a fabricated '{}' baseline before the first
snapshot() call, so the first query on any non-empty form reported true.
Consumers that embed the boolean in their own baseline (settings dirty
guards) froze that false positive in. Now reports false until a baseline
exists; hasSnapshot getter exposes the state.
New: form listens (capture phase, so non-bubbling events are caught) to
the tp-* control change events and re-emits them as 'form-changed'
(detail.changed = isChanged()), scoped to its own registered controls.
Consumers can drop polling.
This commit is contained in:
1 parent
354b0ab720
commit
82adb306b6
2 files changed
+65
-3
No files matched your search
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@tp/tp-form",
|
"name": "@tp/tp-form",
|
||||||
"version": "1.2.1",
|
"version": "1.3.0",
|
||||||
"description": "",
|
"description": "",
|
||||||
"main": "tp-form.js",
|
"main": "tp-form.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
+64
-2
@@ -7,6 +7,22 @@ This program is available under Apache License Version 2.0
|
|||||||
import { LitElement, html, css } from 'lit';
|
import { LitElement, html, css } from 'lit';
|
||||||
import { clone } from '@tp/helpers/clone.js';
|
import { clone } from '@tp/helpers/clone.js';
|
||||||
|
|
||||||
|
// Events that indicate a form control's value changed. Fired either by
|
||||||
|
// tp-* controls (see the control's source for the exact name) or natively
|
||||||
|
// (input/change from wrapped native fields). Listened to in the capture
|
||||||
|
// phase so events that don't bubble (e.g. tp-color-picker's 'change') are
|
||||||
|
// caught as well.
|
||||||
|
const CHANGE_EVENTS = [
|
||||||
|
'input',
|
||||||
|
'change',
|
||||||
|
'value-changed',
|
||||||
|
'selection-changed',
|
||||||
|
'checked-changed',
|
||||||
|
'toggled',
|
||||||
|
'number-changed',
|
||||||
|
'slider-changed',
|
||||||
|
];
|
||||||
|
|
||||||
class TpForm extends LitElement {
|
class TpForm extends LitElement {
|
||||||
static get styles() {
|
static get styles() {
|
||||||
return [
|
return [
|
||||||
@@ -78,6 +94,7 @@ class TpForm extends LitElement {
|
|||||||
super();
|
super();
|
||||||
this._origValues = [];
|
this._origValues = [];
|
||||||
this._wasSetReadonly = [];
|
this._wasSetReadonly = [];
|
||||||
|
this._onControlChanged = this._onControlChanged.bind(this);
|
||||||
|
|
||||||
this._addElement = this._addElement.bind(this);
|
this._addElement = this._addElement.bind(this);
|
||||||
this._removeElement = this._removeElement.bind(this);
|
this._removeElement = this._removeElement.bind(this);
|
||||||
@@ -101,6 +118,11 @@ class TpForm extends LitElement {
|
|||||||
this.addEventListener('form-element-register', this._addElement);
|
this.addEventListener('form-element-register', this._addElement);
|
||||||
this.addEventListener('form-element-unregister', this._removeElement);
|
this.addEventListener('form-element-unregister', this._removeElement);
|
||||||
|
|
||||||
|
// Notify about control changes via `form-changed` — see CHANGE_EVENTS.
|
||||||
|
for (const name of CHANGE_EVENTS) {
|
||||||
|
this.addEventListener(name, this._onControlChanged, true);
|
||||||
|
}
|
||||||
|
|
||||||
// Holds all custom elements registered.
|
// Holds all custom elements registered.
|
||||||
this._controls = [];
|
this._controls = [];
|
||||||
}
|
}
|
||||||
@@ -109,6 +131,10 @@ class TpForm extends LitElement {
|
|||||||
super.disconnectedCallback();
|
super.disconnectedCallback();
|
||||||
this.removeEventListener('form-element-register', this._addElement);
|
this.removeEventListener('form-element-register', this._addElement);
|
||||||
this.removeEventListener('form-element-unregister', this._removeElement);
|
this.removeEventListener('form-element-unregister', this._removeElement);
|
||||||
|
|
||||||
|
for (const name of CHANGE_EVENTS) {
|
||||||
|
this.removeEventListener(name, this._onControlChanged, true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_handleSlotChange(e) {
|
_handleSlotChange(e) {
|
||||||
@@ -351,10 +377,46 @@ class TpForm extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if some form control's value was changed.
|
* True once snapshot() was called at least once. Dirty checks are only
|
||||||
|
* meaningful against a baseline.
|
||||||
|
*/
|
||||||
|
get hasSnapshot() {
|
||||||
|
return this._snapshot !== undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if some form control's value was changed since the last
|
||||||
|
* snapshot(). Before the first snapshot() there is no baseline to
|
||||||
|
* compare against, so this reports false instead of comparing against a
|
||||||
|
* fabricated empty form.
|
||||||
*/
|
*/
|
||||||
isChanged() {
|
isChanged() {
|
||||||
return JSON.stringify(this.serialize()) !== (this._snapshot || '{}');
|
if (!this.hasSnapshot) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return JSON.stringify(this.serialize()) !== this._snapshot;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* True if el is a control registered with this form (custom control or
|
||||||
|
* unwrapped native element). Scopes form-changed to the form's own
|
||||||
|
* controls when forms are nested.
|
||||||
|
*/
|
||||||
|
_isRegisteredControl(el) {
|
||||||
|
return this._controls.includes(el) || Array.from(this._nativeElements).includes(el);
|
||||||
|
}
|
||||||
|
|
||||||
|
_onControlChanged(e) {
|
||||||
|
if (!this._isRegisteredControl(e.target)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.dispatchEvent(new CustomEvent('form-changed', {
|
||||||
|
detail: { changed: this.isChanged() },
|
||||||
|
bubbles: true,
|
||||||
|
composed: true,
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in new issue
Block a user