@node-projects/web-component-designer-visualization-addons 0.3.0 → 0.3.1
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/dist/components/BindingsEditor.js +9 -3
- package/dist/components/ClassBindingsPropertiesService.d.ts +23 -0
- package/dist/components/ClassBindingsPropertiesService.js +73 -0
- package/dist/components/ClassBindingsPropertyGridExtensionProvider.d.ts +5 -0
- package/dist/components/ClassBindingsPropertyGridExtensionProvider.js +15 -0
- package/dist/components/PropertyGridClassBindings.d.ts +17 -0
- package/dist/components/PropertyGridClassBindings.js +231 -0
- package/dist/helpers/BindingsHelper.js +3 -0
- package/dist/index-min.js +107 -38
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/services/VisualizationBindingsService.js +3 -0
- package/package.json +1 -1
- package/tests/BindingsHelper.test.ts +28 -1
- package/tests/PropertyGridClassBindings.test.ts +167 -0
package/dist/index.d.ts
CHANGED
|
@@ -27,3 +27,4 @@ export * from './services/PropertyGridDragDropService.js';
|
|
|
27
27
|
export * from './services/ScriptRefactorService.js';
|
|
28
28
|
export * from './services/SignalPropertyEditor.js';
|
|
29
29
|
export * from './services/VisualizationEventsService.js';
|
|
30
|
+
export * from "./components/ClassBindingsPropertyGridExtensionProvider.js";
|
package/dist/index.js
CHANGED
|
@@ -23,3 +23,4 @@ export * from './services/ScriptRefactorService.js';
|
|
|
23
23
|
export * from './services/SignalPropertyEditor.js';
|
|
24
24
|
export * from './services/VisualizationEventsService.js';
|
|
25
25
|
//export * from './setupVisuService.js';
|
|
26
|
+
export * from "./components/ClassBindingsPropertyGridExtensionProvider.js";
|
|
@@ -16,6 +16,7 @@ export class VisualizationBindingsService {
|
|
|
16
16
|
expression: x[1].expression,
|
|
17
17
|
expressionTwoWay: x[1].expressionTwoWay,
|
|
18
18
|
converter: x[1].converter,
|
|
19
|
+
converters: x[1].converter,
|
|
19
20
|
//type: x[1].type,
|
|
20
21
|
type: VisualizationBindingsService.type,
|
|
21
22
|
service: this,
|
|
@@ -44,6 +45,8 @@ export class VisualizationBindingsService {
|
|
|
44
45
|
}
|
|
45
46
|
bnd.target = binding.target;
|
|
46
47
|
bnd.events = binding.changedEvents;
|
|
48
|
+
//@ts-ignore
|
|
49
|
+
bnd.writeBackSignal = binding.writeBackSignal;
|
|
47
50
|
let serializedBnd = this._bindingsHelper.serializeBinding(designItem.element, binding.targetName, bnd);
|
|
48
51
|
let group = designItem.openGroup('edit_binding');
|
|
49
52
|
designItem.setAttribute(serializedBnd[0], serializedBnd[1]);
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"description": "web-component-designer addon for visualizations",
|
|
3
3
|
"name": "@node-projects/web-component-designer-visualization-addons",
|
|
4
|
-
"version": "0.3.
|
|
4
|
+
"version": "0.3.1",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
7
7
|
"author": "jochen.kuehner@gmx.de",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/** @jest-environment jsdom */
|
|
2
2
|
|
|
3
|
-
import { beforeAll, describe, expect, test } from '@jest/globals';
|
|
3
|
+
import { beforeAll, describe, expect, jest, test } from '@jest/globals';
|
|
4
4
|
import type { SpecialValueHandler } from '../src/helpers/BindingsHelper.js';
|
|
5
5
|
import type { VisualizationHandler, State } from '../src/interfaces/VisualizationHandler.js';
|
|
6
6
|
import { BindingTarget } from '@node-projects/web-component-designer/dist/elements/item/BindingTarget.js';
|
|
@@ -316,6 +316,33 @@ describe('BindingsHelper runtime', () => {
|
|
|
316
316
|
})).toEqual(['bind-prop:value', '{"signal":"source","converter":{"true":"yes"}}']);
|
|
317
317
|
});
|
|
318
318
|
|
|
319
|
+
test('binding service preserves converters and write-back settings when a class binding is renamed', async () => {
|
|
320
|
+
const { BindingMode } = await import('@node-projects/web-component-designer/dist/elements/item/BindingMode.js');
|
|
321
|
+
const { PropertiesHelper } = await import('@node-projects/web-component-designer/dist/elements/services/propertiesService/services/PropertiesHelper.js');
|
|
322
|
+
jest.unstable_mockModule('@node-projects/web-component-designer', () => ({ BindingMode, BindingTarget, PropertiesHelper }));
|
|
323
|
+
const { VisualizationBindingsService } = await import('../src/services/VisualizationBindingsService.js');
|
|
324
|
+
const helper = new BindingsHelper(new TestVisualizationHandler());
|
|
325
|
+
const service = new VisualizationBindingsService(helper);
|
|
326
|
+
const element = document.createElement('div');
|
|
327
|
+
element.setAttribute('bind-class:is-active', JSON.stringify({ signal: 'source', converter: { true: true, false: false }, writeBackSignal: 'other', inverted: true }));
|
|
328
|
+
const item: any = {
|
|
329
|
+
element,
|
|
330
|
+
openGroup: () => ({ commit() { } }),
|
|
331
|
+
setAttribute: (name: string, value: string) => element.setAttribute(name, value),
|
|
332
|
+
removeAttribute: (name: string) => element.removeAttribute(name)
|
|
333
|
+
};
|
|
334
|
+
const binding = service.getBindings(item)[0];
|
|
335
|
+
expect(binding.converters).toEqual({ true: true, false: false });
|
|
336
|
+
service.setBinding(item, { ...binding, targetName: 'is-ready' });
|
|
337
|
+
service.clearBinding(item, 'is-active', BindingTarget.class);
|
|
338
|
+
const renamed = service.getBindings(item)[0];
|
|
339
|
+
expect(renamed.targetName).toBe('is-ready');
|
|
340
|
+
expect(renamed.converter).toEqual(binding.converter);
|
|
341
|
+
expect((renamed as any).writeBackSignal).toBe('other');
|
|
342
|
+
expect(renamed.invert).toBe(true);
|
|
343
|
+
expect(element.hasAttribute('bind-class:is-active')).toBe(false);
|
|
344
|
+
});
|
|
345
|
+
|
|
319
346
|
test('coalesces bursts and skips identical primitive DOM and write-back updates', async () => {
|
|
320
347
|
const handler = new TestVisualizationHandler();
|
|
321
348
|
handler.values.set('sensor', 1);
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
/** @jest-environment jsdom */
|
|
2
|
+
import { afterEach, beforeAll, expect, jest, test } from '@jest/globals';
|
|
3
|
+
import { BindingTarget } from '@node-projects/web-component-designer/dist/elements/item/BindingTarget.js';
|
|
4
|
+
|
|
5
|
+
Object.defineProperty(window, 'matchMedia', { value: () => ({ matches: false }) });
|
|
6
|
+
CSSStyleSheet.prototype.replaceSync = function () { };
|
|
7
|
+
Object.defineProperty(ShadowRoot.prototype, 'adoptedStyleSheets', { writable: true, value: [] });
|
|
8
|
+
Object.defineProperty(globalThis, 'ResizeObserver', { value: class { observe() { } disconnect() { } unobserve() { } } });
|
|
9
|
+
Element.prototype.scrollTo = function () { };
|
|
10
|
+
HTMLDialogElement.prototype.showModal = function () { this.open = true; };
|
|
11
|
+
HTMLDialogElement.prototype.close = function () { this.open = false; this.dispatchEvent(new Event('close')); };
|
|
12
|
+
|
|
13
|
+
let EditingDocument: typeof import('@node-projects/web-component-designer/dist/elements/EditingDocument.js').EditingDocument;
|
|
14
|
+
let ServiceContainer: typeof import('@node-projects/web-component-designer/dist/elements/services/ServiceContainer.js').ServiceContainer;
|
|
15
|
+
let DefaultHtmlParserService: typeof import('@node-projects/web-component-designer/dist/elements/services/htmlParserService/DefaultHtmlParserService.js').DefaultHtmlParserService;
|
|
16
|
+
let HtmlWriterService: typeof import('@node-projects/web-component-designer/dist/elements/services/htmlWriterService/HtmlWriterService.js').HtmlWriterService;
|
|
17
|
+
let DesignItemService: typeof import('@node-projects/web-component-designer/dist/elements/services/designItemService/DesignItemService.js').DesignItemService;
|
|
18
|
+
let BindingService: typeof import('@node-projects/web-component-designer/dist/elements/services/bindingsService/BaseCustomWebcomponentBindingsService.js').BaseCustomWebcomponentBindingsService;
|
|
19
|
+
let ClassService: typeof import('../src/components/ClassBindingsPropertiesService').ClassBindingsPropertiesService;
|
|
20
|
+
let ClassGrid: typeof import('../src/components/PropertyGridClassBindings').PropertyGridClassBindings;
|
|
21
|
+
let PropertyList: typeof import('@node-projects/web-component-designer/dist/elements/widgets/propertyGrid/PropertyGridPropertyList.js').PropertyGridPropertyList;
|
|
22
|
+
let CommonPropertiesService: typeof import('@node-projects/web-component-designer/dist/elements/services/propertiesService/services/CommonPropertiesService.js').CommonPropertiesService;
|
|
23
|
+
let DefaultPropertyEditorTypesService: typeof import('@node-projects/web-component-designer/dist/elements/services/propertiesService/DefaultPropertyEditorTypesService.js').DefaultPropertyEditorTypesService;
|
|
24
|
+
|
|
25
|
+
beforeAll(async () => {
|
|
26
|
+
({ EditingDocument } = await import('@node-projects/web-component-designer/dist/elements/EditingDocument.js'));
|
|
27
|
+
({ ServiceContainer } = await import('@node-projects/web-component-designer/dist/elements/services/ServiceContainer.js'));
|
|
28
|
+
({ DefaultHtmlParserService } = await import('@node-projects/web-component-designer/dist/elements/services/htmlParserService/DefaultHtmlParserService.js'));
|
|
29
|
+
({ HtmlWriterService } = await import('@node-projects/web-component-designer/dist/elements/services/htmlWriterService/HtmlWriterService.js'));
|
|
30
|
+
({ DesignItemService } = await import('@node-projects/web-component-designer/dist/elements/services/designItemService/DesignItemService.js'));
|
|
31
|
+
({ BaseCustomWebcomponentBindingsService: BindingService } = await import('@node-projects/web-component-designer/dist/elements/services/bindingsService/BaseCustomWebcomponentBindingsService.js'));
|
|
32
|
+
({ ClassBindingsPropertiesService: ClassService } = await import('../src/components/ClassBindingsPropertiesService'));
|
|
33
|
+
({ PropertyGridClassBindings: ClassGrid } = await import('../src/components/PropertyGridClassBindings'));
|
|
34
|
+
({ PropertyGridPropertyList: PropertyList } = await import('@node-projects/web-component-designer/dist/elements/widgets/propertyGrid/PropertyGridPropertyList.js'));
|
|
35
|
+
({ CommonPropertiesService } = await import('@node-projects/web-component-designer/dist/elements/services/propertiesService/services/CommonPropertiesService.js'));
|
|
36
|
+
({ DefaultPropertyEditorTypesService } = await import('@node-projects/web-component-designer/dist/elements/services/propertiesService/DefaultPropertyEditorTypesService.js'));
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
afterEach(() => document.body.replaceChildren());
|
|
40
|
+
|
|
41
|
+
async function fixture(html = '<button class="button primary" class:is-active="[[isActive]]">Submit</button>') {
|
|
42
|
+
const services = new ServiceContainer();
|
|
43
|
+
services.register('designItemService', new DesignItemService());
|
|
44
|
+
services.register('htmlParserService', new DefaultHtmlParserService());
|
|
45
|
+
services.register('htmlWriterService', new HtmlWriterService());
|
|
46
|
+
services.register('bindingService', new BindingService());
|
|
47
|
+
services.register('propertyEditorTypesService', new DefaultPropertyEditorTypesService());
|
|
48
|
+
services.config.demoViewWidget = null;
|
|
49
|
+
const doc = new EditingDocument(services);
|
|
50
|
+
await doc.loadHtml(html);
|
|
51
|
+
const item = doc.rootDesignItem.firstChild;
|
|
52
|
+
return { services, doc, item, bindingService: services.bindingService };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
test('renames and removes class bindings as individual undoable operations', async () => {
|
|
56
|
+
const { doc, item } = await fixture();
|
|
57
|
+
const service = new ClassService();
|
|
58
|
+
const binding = service.getBindings(item)[0];
|
|
59
|
+
service.rename(item, binding, 'is-ready');
|
|
60
|
+
expect(item.hasAttribute('class:is-active')).toBe(false);
|
|
61
|
+
expect(item.getAttribute('class:is-ready')).toBe('[[isActive]]');
|
|
62
|
+
doc.instanceServiceContainer.undoService.undo();
|
|
63
|
+
expect(item.getAttribute('class:is-active')).toBe('[[isActive]]');
|
|
64
|
+
expect(item.hasAttribute('class:is-ready')).toBe(false);
|
|
65
|
+
doc.instanceServiceContainer.undoService.redo();
|
|
66
|
+
service.remove(item, service.getBindings(item)[0]);
|
|
67
|
+
expect(service.getBindings(item)).toHaveLength(0);
|
|
68
|
+
doc.instanceServiceContainer.undoService.undo();
|
|
69
|
+
expect(item.getAttribute('class:is-ready')).toBe('[[isActive]]');
|
|
70
|
+
expect(item.getAttribute('class')).toBe('button primary');
|
|
71
|
+
await doc.dispose();
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test('validates lowercase HTML-safe names and rejects duplicate targets', async () => {
|
|
75
|
+
const { doc, item } = await fixture();
|
|
76
|
+
const service = new ClassService();
|
|
77
|
+
for (const name of ['', 'myClassName', 'two classes', 'bad"name', 'bad=name', 'bad/name'])
|
|
78
|
+
expect(service.validateName(item, name)).toBeTruthy();
|
|
79
|
+
expect(service.validateName(item, 'is-active')).toContain('already');
|
|
80
|
+
expect(service.validateName(item, 'is-active', 'is-active')).toBeNull();
|
|
81
|
+
expect(service.validateName(item, 'has-error')).toBeNull();
|
|
82
|
+
await doc.dispose();
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
test('source-created bindings appear under class and open the host editor with a class target', async () => {
|
|
86
|
+
const { services, doc, item } = await fixture();
|
|
87
|
+
const openEditor = jest.fn(async () => { });
|
|
88
|
+
services.config.openBindingsEditor = openEditor;
|
|
89
|
+
const { ClassBindingsPropertyGridExtensionProvider } = await import('../src/components/ClassBindingsPropertyGridExtensionProvider');
|
|
90
|
+
services.propertyGridExtensions.push(new ClassBindingsPropertyGridExtensionProvider());
|
|
91
|
+
const list = new PropertyList(services);
|
|
92
|
+
document.body.appendChild(list);
|
|
93
|
+
list.setPropertiesService(new CommonPropertiesService());
|
|
94
|
+
await list.createElements(item);
|
|
95
|
+
list.designItemsChanged([item]);
|
|
96
|
+
const section = list.shadowRoot.querySelector('node-projects-property-grid-class-bindings') as InstanceType<typeof ClassGrid>;
|
|
97
|
+
expect(section.previousElementSibling.querySelector('.editor-control').id).toBe('class');
|
|
98
|
+
expect(section.shadowRoot.textContent).toContain('is-active');
|
|
99
|
+
(section.shadowRoot.querySelector('.name') as HTMLButtonElement).click();
|
|
100
|
+
await Promise.resolve();
|
|
101
|
+
expect(openEditor).toHaveBeenCalledWith(expect.objectContaining({ name: 'is-active', type: 'boolean' }), [item], expect.objectContaining({ targetName: 'is-active' }), BindingTarget.class);
|
|
102
|
+
await doc.dispose();
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
test('addition asks only for a class name then delegates to the configured binding editor', async () => {
|
|
106
|
+
const { services, doc, item } = await fixture();
|
|
107
|
+
const openEditor = jest.fn(async () => {});
|
|
108
|
+
services.config.openBindingsEditor = openEditor;
|
|
109
|
+
const grid = new ClassGrid();
|
|
110
|
+
document.body.appendChild(grid);
|
|
111
|
+
grid.refresh([item]);
|
|
112
|
+
grid.addBinding();
|
|
113
|
+
const form = grid.shadowRoot.querySelector('form');
|
|
114
|
+
expect(form.querySelectorAll('input')).toHaveLength(1);
|
|
115
|
+
(form.querySelector('input') as HTMLInputElement).value = 'is-busy';
|
|
116
|
+
form.dispatchEvent(new Event('submit', { cancelable: true }));
|
|
117
|
+
await Promise.resolve();
|
|
118
|
+
expect(openEditor).toHaveBeenCalledWith(expect.objectContaining({ name: 'is-busy', type: 'boolean' }), [item], undefined, BindingTarget.class);
|
|
119
|
+
expect(item.hasAttribute('class:is-busy')).toBe(false);
|
|
120
|
+
await doc.dispose();
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
test('canceling addition leaves the document unchanged and multiple selection disables addition', async () => {
|
|
124
|
+
const { services, doc, item } = await fixture();
|
|
125
|
+
services.config.openBindingsEditor = async () => {};
|
|
126
|
+
const original = await doc.serialize();
|
|
127
|
+
const grid = new ClassGrid();
|
|
128
|
+
document.body.appendChild(grid);
|
|
129
|
+
grid.refresh([item]);
|
|
130
|
+
grid.addBinding();
|
|
131
|
+
(grid.shadowRoot.querySelector('.actions button[type="button"]') as HTMLButtonElement).click();
|
|
132
|
+
expect(await doc.serialize()).toBe(original);
|
|
133
|
+
grid.refresh([item, item]);
|
|
134
|
+
expect((grid.shadowRoot.querySelector('[aria-label="Add class binding"]') as HTMLButtonElement).disabled).toBe(true);
|
|
135
|
+
expect(grid.shadowRoot.textContent).toContain('Select one element');
|
|
136
|
+
await doc.dispose();
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
test('failed renames roll back the new attribute instead of leaving two bindings', async () => {
|
|
140
|
+
const { doc, item, bindingService } = await fixture();
|
|
141
|
+
const service = new ClassService();
|
|
142
|
+
const clear = jest.spyOn(bindingService, 'clearBinding').mockReturnValue(false);
|
|
143
|
+
expect(() => service.rename(item, service.getBindings(item)[0], 'is-ready')).toThrow('previous binding');
|
|
144
|
+
expect(item.hasAttribute('class:is-ready')).toBe(false);
|
|
145
|
+
expect(item.getAttribute('class:is-active')).toBe('[[isActive]]');
|
|
146
|
+
clear.mockRestore();
|
|
147
|
+
await doc.dispose();
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
test('editing after a source refresh uses changed binding options even if the summary is unchanged', async () => {
|
|
151
|
+
const { services, doc, item, bindingService } = await fixture();
|
|
152
|
+
const binding = bindingService.getBindings(item)[0];
|
|
153
|
+
let invert = false;
|
|
154
|
+
const read = jest.spyOn(bindingService, 'getBindings').mockImplementation(() => [{ ...binding, invert }]);
|
|
155
|
+
const openEditor = jest.fn(async () => { });
|
|
156
|
+
services.config.openBindingsEditor = openEditor;
|
|
157
|
+
const grid = new ClassGrid();
|
|
158
|
+
document.body.appendChild(grid);
|
|
159
|
+
grid.refresh([item]);
|
|
160
|
+
invert = true;
|
|
161
|
+
grid.refresh([item]);
|
|
162
|
+
(grid.shadowRoot.querySelector('.name') as HTMLButtonElement).click();
|
|
163
|
+
await Promise.resolve();
|
|
164
|
+
expect(openEditor).toHaveBeenCalledWith(expect.anything(), [item], expect.objectContaining({ invert: true }), BindingTarget.class);
|
|
165
|
+
read.mockRestore();
|
|
166
|
+
await doc.dispose();
|
|
167
|
+
});
|