clickgo 6.9.0 → 6.11.0

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 (47) hide show
  1. package/AGENTS.md +21 -0
  2. package/README.md +4 -0
  3. package/dist/app/demo.cga +0 -0
  4. package/dist/app/task.cga +0 -0
  5. package/dist/clickgo.d.ts +8 -1
  6. package/dist/index.js +1 -1
  7. package/dist/lib/command/guide.d.ts +7 -0
  8. package/dist/lib/command/native.d.ts +18 -0
  9. package/dist/lib/command/palette.d.ts +10 -0
  10. package/dist/lib/command/schema.d.ts +31 -0
  11. package/dist/lib/command.d.ts +271 -0
  12. package/dist/lib/core.d.ts +8 -1
  13. package/dist/lib/form.d.ts +7 -0
  14. package/dist/lib/native.d.ts +29 -0
  15. package/dist/lib/task.d.ts +2 -0
  16. package/doc/clickgo-rag.md +4637 -2263
  17. package/package.json +8 -3
  18. package/test/core-module-loading.mjs +0 -169
  19. package/test/desktop.mjs +0 -715
  20. package/test/dock-lifecycle.mjs +0 -280
  21. package/test/dom-style.mjs +0 -261
  22. package/test/form-launcher.mjs +0 -177
  23. package/test/form-modal-layer.mjs +0 -75
  24. package/test/form-native-regions.mjs +0 -115
  25. package/test/form-native-style.mjs +0 -80
  26. package/test/form-native.mjs +0 -68
  27. package/test/form-notify-animation.mjs +0 -102
  28. package/test/form-notify.mjs +0 -90
  29. package/test/form-viewport.mjs +0 -127
  30. package/test/iconview.mjs +0 -70
  31. package/test/jodit-lifecycle.mjs +0 -212
  32. package/test/locale-integrity.mjs +0 -189
  33. package/test/pop-max-size.mjs +0 -66
  34. package/test/rtl-archive.mjs +0 -440
  35. package/test/rtl-direction.mjs +0 -193
  36. package/test/scroll.mjs +0 -145
  37. package/test/select.mjs +0 -885
  38. package/test/sidebar-toggle.mjs +0 -75
  39. package/test/task-package-loading.mjs +0 -94
  40. package/test/task-start.mjs +0 -141
  41. package/test/task-tray.mjs +0 -331
  42. package/test/tool-assets.mjs +0 -64
  43. package/test/tool-clone.mjs +0 -42
  44. package/test/toolbox.mjs +0 -88
  45. package/test/tuieditor-locale.mjs +0 -175
  46. package/test/vflow.mjs +0 -122
  47. package/test/web-drag-mask.mjs +0 -96
@@ -1,64 +0,0 @@
1
- import assert from 'node:assert/strict';
2
- import { readFile } from 'node:fs/promises';
3
- import vm from 'node:vm';
4
- import { JSDOM } from 'jsdom';
5
- import ts from 'typescript';
6
-
7
- const dom = new JSDOM('<!doctype html><head></head><body></body>', { url: 'https://example.test/' });
8
- const source = await readFile(new URL('../dist/lib/tool.ts', import.meta.url), 'utf8');
9
- const tree = ts.createSourceFile('tool.ts', source, ts.ScriptTarget.ES2022, true);
10
- const names = new Set(['getHeadElement', 'loadScript', 'loadScripts', 'loadLink', 'loadLinks', 'loadAssets']);
11
- const nodes = tree.statements.filter(node =>
12
- (ts.isVariableStatement(node) && node.declarationList.declarations.some(item => item.name.getText(tree) === 'headElement')) ||
13
- (ts.isFunctionDeclaration(node) && node.body && names.has(node.name?.text)));
14
- const api = {};
15
- vm.runInNewContext(ts.transpileModule(nodes.map(node => node.getText(tree)).join('\n'), {
16
- compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2022 },
17
- }).outputText, { exports: api, document: dom.window.document });
18
- const head = dom.window.document.head;
19
- function finish(el, success = true) { el.dispatchEvent(new dom.window.Event(success ? 'load' : 'error')); }
20
- async function tick() { await new Promise(resolve => setImmediate(resolve)); }
21
-
22
- assert.equal(await api.loadAssets([]), true);
23
- assert.equal(head.children.length, 0);
24
- const urls = ['/base.js?file=theme.css', '/theme.CSS?version=1#dark', '/addon.js#theme.css', '/override.css#last'];
25
- const events = [];
26
- let complete = false;
27
- const loading = api.loadAssets(urls, { loaded: (url, state) => events.push([url, state]) }).then(value => { complete = true; return value; });
28
- const scripts = [...head.querySelectorAll('script')];
29
- const links = [...head.querySelectorAll('link')];
30
- assert.equal(scripts.length, 2);
31
- assert.equal(links.length, 2);
32
- assert.equal(head.children.length, 4, 'all JS and CSS requests start before awaiting a resource');
33
- assert.deepEqual(scripts.map(el => el.getAttribute('src')), [urls[0], urls[2]], 'query/hash values do not change the resource type');
34
- assert.deepEqual(links.map(el => el.getAttribute('href')), [urls[1], urls[3]], 'CSS insertion order and original URLs are preserved');
35
- assert.ok(scripts.every(el => el.async === false), 'unified loader uses browser ordered script execution');
36
- finish(scripts[1]); finish(links[1]); finish(links[0]);
37
- await tick();
38
- assert.equal(complete, false, 'the aggregate waits for every file, regardless of completion order');
39
- finish(scripts[0]);
40
- assert.equal(await loading, true);
41
- assert.deepEqual(events.map(([url, state]) => `${url}:${state}`).sort(), urls.map(url => `${url}:1`).sort(), 'each resource reports its own result once');
42
-
43
- head.replaceChildren();
44
- const cssFailed = api.loadAssets(['/ok.js', '/missing.css']);
45
- finish(head.querySelector('link'), false);
46
- finish(head.querySelector('script'));
47
- assert.equal(await cssFailed, false, 'CSS errors affect the aggregate result');
48
- head.replaceChildren();
49
- const jsFailed = api.loadAssets(['/missing.js', '/ok.css']);
50
- finish(head.querySelector('script'), false);
51
- finish(head.querySelector('link'));
52
- assert.equal(await jsFailed, false, 'JS errors affect the aggregate result');
53
- head.replaceChildren();
54
- const legacy = api.loadScript('/legacy.js');
55
- assert.equal(head.querySelector('script').async, true, 'existing single-script loading retains async behavior');
56
- finish(head.querySelector('script'));
57
- assert.equal(await legacy, true);
58
- head.replaceChildren();
59
- const batch = api.loadScripts(['/legacy-a.js', '/legacy-b.js']);
60
- assert.ok([...head.querySelectorAll('script')].every(el => el.async === true), 'existing script batches retain async behavior by default');
61
- for (const el of head.querySelectorAll('script')) finish(el);
62
- await batch;
63
- dom.window.close();
64
- console.log('Unified asset loading, URL classification, order, concurrency, callbacks, errors and legacy behavior passed.');
@@ -1,42 +0,0 @@
1
- // Run after TypeScript compilation: node test/tool-clone.mjs
2
- import assert from 'node:assert/strict';
3
- import { clone } from '../dist/lib/tool.js';
4
-
5
- const shared = { 'value': 1 };
6
- const source = {
7
- 'map': new Map(),
8
- 'set': new Set(),
9
- shared,
10
- 'alias': shared,
11
- 'date': new Date('2026-09-20T00:00:00.000Z'),
12
- };
13
- source.self = source;
14
- source.map.set(shared, source);
15
- source.set.add(shared);
16
- source.set.add(source);
17
-
18
- const first = clone(source);
19
- const second = clone(source);
20
-
21
- assert.notEqual(first, source);
22
- assert.notEqual(first, second);
23
- assert(first.map instanceof Map);
24
- assert(first.set instanceof Set);
25
- assert.notEqual(first.map, source.map);
26
- assert.notEqual(first.map, second.map);
27
- assert.notEqual(first.set, source.set);
28
- assert.notEqual(first.set, second.set);
29
- assert.equal(first.self, first);
30
- assert.equal(first.shared, first.alias);
31
- assert.notEqual(first.shared, shared);
32
- assert.equal(first.map.get(first.shared), first);
33
- assert(first.set.has(first.shared));
34
- assert(first.set.has(first));
35
- assert(first.date instanceof Date);
36
- assert.notEqual(first.date, source.date);
37
- assert.equal(first.date.getTime(), source.date.getTime());
38
-
39
- first.map.set('only-first', true);
40
- assert.equal(second.map.has('only-first'), false);
41
-
42
- console.log('Tool clone Map/Set, circular-reference and instance-isolation checks passed.');
package/test/toolbox.mjs DELETED
@@ -1,88 +0,0 @@
1
- // Run after TypeScript compilation: node --experimental-vm-modules test/toolbox.mjs
2
- import assert from 'node:assert/strict';
3
- import { readFile } from 'node:fs/promises';
4
- import { SourceTextModule, SyntheticModule } from 'node:vm';
5
- import { JSDOM } from 'jsdom';
6
-
7
- const dom = new JSDOM('<div id="tools"></div>');
8
- globalThis.HTMLElement = dom.window.HTMLElement;
9
- const tools = dom.window.document.getElementById('tools');
10
- class AbstractControl {
11
- constructor() {
12
- this.refs = { tools };
13
- this.emitted = [];
14
- }
15
- emit(name, value) {
16
- this.emitted.push([name, value]);
17
- }
18
- propNumber(name) {
19
- return Number(this.props[name]);
20
- }
21
- watch(name, callback, options) {
22
- if (options?.immediate) {
23
- callback();
24
- }
25
- }
26
- }
27
- const clickgo = new SyntheticModule(['control'], function() {
28
- this.setExport('control', { AbstractControl });
29
- });
30
- const mod = new SourceTextModule(await readFile(new URL('../dist/sources/control/toolbox/code.js', import.meta.url), 'utf8'));
31
- await mod.link(() => clickgo);
32
- await mod.evaluate();
33
- const Toolbox = mod.namespace.default;
34
- const toolbox = new Toolbox();
35
- toolbox.props.columns = '2';
36
- toolbox.onMounted();
37
- assert.equal(toolbox.columnsData, 2);
38
- toolbox.toggleColumns();
39
- assert.equal(toolbox.columnsData, 1);
40
- assert.deepEqual(toolbox.emitted, [['update:columns', 1]]);
41
-
42
- function button(x, y, { disabled = false, hidden = false } = {}) {
43
- const item = dom.window.document.createElement('div');
44
- item.setAttribute('data-cg-control', 'button');
45
- item.setAttribute('role', 'button');
46
- item.setAttribute('tabindex', '0');
47
- if (disabled) {
48
- item.setAttribute('data-cg-disabled', '');
49
- }
50
- Object.defineProperty(item, 'offsetHeight', { 'value': hidden ? 0 : 24 });
51
- item.getBoundingClientRect = () => ({ 'left': x, 'top': y, 'width': 24, 'height': 24 });
52
- tools.append(item);
53
- return item;
54
- }
55
- // A one-tool group precedes a two-column group; DOM index arithmetic would pick the wrong row.
56
- const browse = button(0, 0);
57
- const paint = button(0, 40);
58
- const erase = button(30, 40);
59
- button(0, 80, { 'disabled': true });
60
- button(30, 80, { 'hidden': true });
61
- const select = button(0, 120);
62
-
63
- function move(from, key, expected) {
64
- from.focus();
65
- let prevented = false;
66
- toolbox.keydown({ 'target': from, key, 'preventDefault': () => { prevented = true; } });
67
- assert.equal(prevented, true);
68
- assert.equal(dom.window.document.activeElement, expected, key);
69
- }
70
- move(browse, 'ArrowDown', paint);
71
- move(paint, 'ArrowRight', erase);
72
- move(erase, 'ArrowLeft', paint);
73
- move(paint, 'ArrowDown', select);
74
- move(select, 'Home', browse);
75
- move(browse, 'End', select);
76
- move(browse, 'ArrowUp', browse);
77
-
78
- // RTL changes the rendered physical positions; navigation must follow those positions.
79
- paint.getBoundingClientRect = () => ({ 'left': 30, 'top': 40, 'width': 24, 'height': 24 });
80
- erase.getBoundingClientRect = () => ({ 'left': 0, 'top': 40, 'width': 24, 'height': 24 });
81
- move(paint, 'ArrowLeft', erase);
82
- move(erase, 'ArrowRight', paint);
83
-
84
- const outside = dom.window.document.createElement('div');
85
- outside.setAttribute('data-cg-control', 'button');
86
- outside.setAttribute('role', 'button');
87
- toolbox.keydown({ 'target': outside, 'key': 'ArrowDown', 'preventDefault': () => assert.fail('header/outside buttons are not tools') });
88
- console.log('Toolbox column binding and keyboard navigation across groups, hidden/disabled tools and RTL passed.');
@@ -1,175 +0,0 @@
1
- import assert from 'node:assert/strict';
2
- import { readFile } from 'node:fs/promises';
3
- import vm from 'node:vm';
4
- import { setImmediate } from 'node:timers/promises';
5
- import ts from 'typescript';
6
- import { JSDOM } from 'jsdom';
7
-
8
- const source = await readFile(new URL('../dist/sources/control/tuieditor/code.ts', import.meta.url), 'utf8');
9
- const compiled = ts.transpileModule(source, {
10
- compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2022, esModuleInterop: false },
11
- }).outputText;
12
- const dom = new JSDOM('<!doctype html><html><body></body></html>');
13
- const { document } = dom.window;
14
- const requests = [];
15
- const editors = [];
16
- function deferred() {
17
- let resolve;
18
- let reject;
19
- const promise = new Promise((yes, no) => { resolve = yes; reject = no; });
20
- return { promise, resolve, reject };
21
- }
22
- class AbstractControl {
23
- get locale() { return this.testLocale; }
24
- propBoolean(name) { return this.props[name] === true; }
25
- watch(name, callback, options) {
26
- this.watches.set(name, callback);
27
- if (options?.immediate) callback();
28
- }
29
- emit() {}
30
- }
31
- class Editor {
32
- constructor(options) {
33
- this.options = options;
34
- this.i18n = { get: key => key };
35
- options.el.appendChild(document.createElement('div'));
36
- editors.push(this);
37
- }
38
- insertToolbarItem() {}
39
- }
40
- let main;
41
- const clickgo = {
42
- modules: {},
43
- getCdn: () => 'https://cdn.example',
44
- getDirname: () => 'https://app.example/clickgo',
45
- core: { getModule: () => main },
46
- control: { AbstractControl },
47
- tool: {
48
- loadScript(url) {
49
- const pending = deferred();
50
- requests.push({ url, ...pending });
51
- return pending.promise;
52
- },
53
- },
54
- dom: { createElement: name => document.createElement(name), watchStyle() {} },
55
- };
56
- // Build the actual shared module once, then evaluate controls independently as different tasks do.
57
- const coreSource = await readFile(new URL('../dist/lib/core.ts', import.meta.url), 'utf8');
58
- const tree = ts.createSourceFile('core.ts', coreSource, ts.ScriptTarget.ES2022, true);
59
- const names = new Set(['getModule', 'loadModule']);
60
- const selected = tree.statements.filter(node =>
61
- (ts.isVariableStatement(node) && node.declarationList.declarations.some(item => item.name.getText(tree) === 'modules')) ||
62
- (ts.isFunctionDeclaration(node) && node.body && names.has(node.name?.text)));
63
- const core = {};
64
- vm.runInNewContext(ts.transpileModule(selected.map(node => node.getText(tree)).join('\n'), {
65
- compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2022, esModuleInterop: false },
66
- }).outputText, {
67
- exports: core, window: { toastui: { Editor } }, clickgo,
68
- lTool: { ...clickgo.tool, loadAssets: async () => true, loadStyle() {}, urlResolve: (base, path) => new URL(path, base).href },
69
- });
70
- const shared = await core.getModule('@toast-ui/editor');
71
- main = Promise.resolve(shared);
72
- function loadControl() {
73
- const exports = {};
74
- vm.runInNewContext(compiled, { exports, require: () => clickgo });
75
- return exports.default;
76
- }
77
- let Control = loadControl();
78
- function create(locale) {
79
- const control = new Control();
80
- control.testLocale = locale;
81
- control.watches = new Map();
82
- control.element = document.createElement('div');
83
- const content = document.createElement('div');
84
- const pop = document.createElement('div');
85
- control.element.append(content, pop);
86
- control.refs = { content, pop };
87
- document.body.appendChild(control.element);
88
- return control;
89
- }
90
- await create('en').onMounted();
91
- await create('ar').onMounted();
92
- assert.equal(requests.length, 0, 'English and unsupported languages need no locale scripts');
93
- assert.equal(editors.at(-1).options.language, 'en');
94
- assert.equal(editors.at(-1).options.el.dir, 'rtl');
95
-
96
- const base = deferred();
97
- main = base.promise;
98
- const first = create('sc');
99
- Control = loadControl();
100
- const second = create('sc');
101
- const a = first.onMounted();
102
- const b = second.onMounted();
103
- assert.equal(requests.length, 0, 'locale registration waits for the main library');
104
- base.resolve(shared);
105
- await setImmediate();
106
- assert.equal(requests.length, 1, 'independent task control modules share one pending language request');
107
- assert.ok(requests[0].url.endsWith('/i18n/zh-cn.min.js'));
108
- assert.equal(editors.length, 2, 'instances wait until their locale is registered');
109
- requests[0].resolve(true);
110
- await Promise.all([a, b]);
111
- assert.equal(first.access.tuieditor.options.language, 'zh-CN');
112
- assert.equal(second.access.tuieditor.options.language, 'zh-CN');
113
- assert.notEqual(first.access.tuieditor, second.access.tuieditor);
114
- await create('sc').onMounted();
115
- assert.equal(requests.length, 1, 'ready locale scripts are reused across task control modules');
116
-
117
- Control = loadControl();
118
- await create('sc').onMounted();
119
- assert.equal(requests.length, 1, 'a later task reuses the same completed language request');
120
-
121
- first.testLocale = 'ja';
122
- first.watches.get('locale')();
123
- assert.equal(requests.length, 1, 'changing task locale does not reload an existing editor');
124
- assert.equal(first.access.tuieditor.options.language, 'zh-CN');
125
- main = Promise.resolve(shared);
126
- const japanese = create('ja');
127
- const c = japanese.onMounted();
128
- await setImmediate();
129
- assert.ok(requests.at(-1).url.endsWith('/i18n/ja-jp.min.js'));
130
- requests.at(-1).resolve(true);
131
- await c;
132
- assert.equal(japanese.access.tuieditor.options.language, 'ja-JP', 'new editors use the current task language');
133
-
134
- const french = create('fr');
135
- const d = french.onMounted();
136
- await setImmediate();
137
- requests.at(-1).resolve(false);
138
- await d;
139
- assert.equal(french.access.tuieditor.options.language, 'en', 'locale failure falls back without disabling editing');
140
- const beforeRetry = requests.length;
141
- const retry = create('fr').onMounted();
142
- await setImmediate();
143
- assert.equal(requests.length, beforeRetry + 1, 'failed locale requests can be retried');
144
- requests.at(-1).resolve(true);
145
- await retry;
146
- assert.equal(editors.at(-1).options.language, 'fr-FR');
147
-
148
- const rejected = shared.loadLanguage('de-DE');
149
- const rejectedWaiter = shared.loadLanguage('de-de');
150
- requests.at(-1).reject(new Error('external loader rejected'));
151
- assert.equal(await rejected, false);
152
- assert.equal(await rejectedWaiter, false, 'external loader errors release all waiters');
153
- const rejectedRetry = shared.loadLanguage('de-DE');
154
- requests.at(-1).resolve(true);
155
- assert.equal(await rejectedRetry, true, 'external errors do not poison the shared cache');
156
-
157
- const removed = create('tc');
158
- const e = removed.onMounted();
159
- await setImmediate();
160
- assert.ok(requests.at(-1).url.endsWith('/i18n/zh-tw.min.js'));
161
- removed.element.remove();
162
- const count = editors.length;
163
- requests.at(-1).resolve(true);
164
- await e;
165
- assert.equal(editors.length, count, 'removed hosts do not receive late editor instances');
166
-
167
- Control = loadControl();
168
- main = Promise.resolve(null);
169
- const missing = create('sc');
170
- const beforeMissing = requests.length;
171
- await missing.onMounted();
172
- assert.equal(missing.notInit, true);
173
- assert.equal(missing.isLoading, false);
174
- assert.equal(requests.length, beforeMissing, 'a missing main library triggers no locale request');
175
- console.log('Toast UI cross-task locale requests, reuse, fallback, retries and initialization language passed.');
package/test/vflow.mjs DELETED
@@ -1,122 +0,0 @@
1
- import assert from 'node:assert/strict';
2
- import { readFile } from 'node:fs/promises';
3
- import vm from 'node:vm';
4
- import { JSDOM } from 'jsdom';
5
- import ts from 'typescript';
6
- import { getClassPrototype, lang } from '../dist/lib/tool.js';
7
-
8
- const dom = new JSDOM('<!doctype html><html><body></body></html>');
9
- for (const name of ['window', 'document', 'Element', 'HTMLElement', 'SVGElement', 'Node']) {
10
- Object.defineProperty(globalThis, name, { configurable: true, value: dom.window[name] });
11
- }
12
- Object.defineProperties(HTMLElement.prototype, {
13
- offsetWidth: { configurable: true, get: () => 80 },
14
- offsetHeight: { configurable: true, get: () => 20 },
15
- });
16
- const { createApp, reactive, nextTick, h } = await import('vue');
17
- const state = reactive({ locale: 'en', direction: 'h', count: 2 });
18
- const errors = [];
19
- const watched = [];
20
- process.on('unhandledRejection', error => { errors.push(error); });
21
-
22
- // Keep the framework's computed $el access and component-root mounting order.
23
- class AbstractControl {
24
- get element() { return this.$el; }
25
- get locale() { return state.locale; }
26
- get localeDirection() { return lang.getDirection(this.locale); }
27
- nextTick() { return this.$nextTick(); }
28
- watch(...args) { return this.$watch(...args); }
29
- emit(...args) { this.$emit(...args); }
30
- propInt(name) { return parseInt(this.props[name]); }
31
- }
32
- const clickgo = {
33
- control: { AbstractControl },
34
- dom: {
35
- isRtl: el => el instanceof Element && dom.window.getComputedStyle(el).direction === 'rtl',
36
- watchStyle: (el, names, callback, immediate) => {
37
- assert.ok(el instanceof HTMLElement && el.isConnected, 'style watcher needs a mounted element');
38
- watched.push(el);
39
- if (immediate) {
40
- for (const name of names) callback(name, name === 'font' ? '13px sans-serif' : '0px');
41
- }
42
- },
43
- watchSize() {},
44
- unwatchSize() {},
45
- },
46
- };
47
- const source = await readFile(new URL('../dist/sources/control/vflow/code.ts', import.meta.url), 'utf8');
48
- const exports = {};
49
- vm.runInNewContext(ts.transpileModule(source, {
50
- compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2022 },
51
- }).outputText, { exports, require: () => clickgo });
52
- const instance = new exports.default();
53
- const prototype = getClassPrototype(instance);
54
- const layout = await readFile(new URL('../dist/sources/control/vflow/layout.html', import.meta.url), 'utf8');
55
- const Flow = {
56
- template: '<div><slot /></div>',
57
- mounted() { this.$emit('clientwidth', 200); this.$emit('clientheight', 100); },
58
- };
59
- const Vflow = {
60
- template: layout,
61
- components: { 'cg-flow': Flow },
62
- props: Object.fromEntries(Object.entries(instance.props).map(([name, value]) => [name, {
63
- default: () => structuredClone(value),
64
- }])),
65
- emits: instance.emits,
66
- data: () => Object.fromEntries(Object.entries(instance).filter(([name]) => !['props', 'emits'].includes(name))),
67
- computed: prototype.access,
68
- methods: prototype.method,
69
- created() { this.props = this.$props; },
70
- async mounted() { await this.$nextTick(); this.onMounted(); },
71
- };
72
- const host = document.createElement('div');
73
- document.body.append(host);
74
- const app = createApp({
75
- render() {
76
- return h('div', { dir: lang.getDirection(state.locale) }, [
77
- h(Vflow, { ref: 'flow', data: state.count, direction: state.direction }, {
78
- default: item => h('span', `Line ${item.row}`),
79
- }),
80
- ]);
81
- },
82
- });
83
- app.config.errorHandler = error => { errors.push(error); };
84
- const root = app.mount(host);
85
-
86
- /** --- Drain Vue render, mount and deferred item-size work. --- */
87
- async function flush() {
88
- for (let i = 0; i < 10; ++i) await nextTick();
89
- await new Promise(resolve => setImmediate(resolve));
90
- assert.deepEqual(errors, [], 'Vflow must mount/update without runtime errors');
91
- }
92
- await flush();
93
- assert.equal(watched.length, 1);
94
- assert.equal(root.$refs.flow.element, host.querySelector('[direction]'));
95
- assert.equal(root.$refs.flow.length, 160);
96
- assert.equal(root.$refs.flow.isRtl, false);
97
- assert.equal(host.querySelector('.item').style.left, '0px');
98
- state.count = 102;
99
- await flush();
100
- assert.equal(root.$refs.flow.pos.length, 102);
101
- assert.ok(host.querySelectorAll('.item').length < 102, 'only visible items are rendered');
102
- state.locale = 'ar';
103
- await flush();
104
- assert.equal(root.$refs.flow.isRtl, true);
105
- assert.equal(host.querySelector('.item').style.left, '');
106
- assert.equal(host.querySelector('.item').style.right, '0px');
107
- state.direction = 'v';
108
- await flush();
109
- assert.equal(root.$refs.flow.isRtl, false);
110
- assert.equal(root.$refs.flow.length, 2040);
111
- state.count = 0;
112
- await flush();
113
- assert.equal(root.$refs.flow.pos.length, 0);
114
- state.count = 2;
115
- state.direction = 'h';
116
- state.locale = 'en';
117
- await flush();
118
- assert.equal(root.$refs.flow.length, 160);
119
- assert.equal(host.querySelector('.item').style.left, '0px');
120
- app.unmount();
121
- await flush();
122
- console.log('Vflow real Vue mount, virtualization, data/direction updates and live RTL switching passed.');
@@ -1,96 +0,0 @@
1
- // Run: node --experimental-vm-modules test/web-drag-mask.mjs
2
- import assert from 'node:assert/strict';
3
- import { readFile } from 'node:fs/promises';
4
- import { SourceTextModule, SyntheticModule } from 'node:vm';
5
- import { JSDOM } from 'jsdom';
6
- import ts from 'typescript';
7
-
8
- const dom = new JSDOM('<!doctype html><html><head></head><body></body></html>');
9
- for (const name of ['window', 'document', 'Element', 'SVGElement', 'Node', 'HTMLElement']) {
10
- globalThis[name] = dom.window[name];
11
- }
12
- globalThis.getComputedStyle = dom.window.getComputedStyle.bind(dom.window);
13
- const { createApp, compile, reactive, nextTick } = await import('vue');
14
- const pointer = await import('@litert/pointer');
15
- const state = reactive({ move: false });
16
- const clickgo = new SyntheticModule(['control', 'dom'], function() {
17
- this.setExport('control', { AbstractControl: class {} });
18
- this.setExport('dom', { is: state });
19
- });
20
- const source = await readFile(new URL('../dist/sources/control/web/code.ts', import.meta.url), 'utf8');
21
- const web = new SourceTextModule(ts.transpileModule(source, {
22
- compilerOptions: { target: ts.ScriptTarget.ES2022, module: ts.ModuleKind.ESNext },
23
- }).outputText);
24
- await web.link(() => clickgo);
25
- await web.evaluate();
26
- const control = new web.namespace.default();
27
-
28
- // Exercise the actual launcher hooks with the installed Pointer library.
29
- const launcher = await readFile(new URL('../dist/clickgo.ts', import.meta.url), 'utf8');
30
- const hookSource = launcher.match(/ modules\.pointer\.addMoveHook\('down',[\s\S]*? modules\.pointer\.addMoveHook\('up',[\s\S]*? \}\);/)[0];
31
- const hooks = new SourceTextModule(`import { pointer, is } from 'bridge';
32
- const modules = { pointer }; const lDom = { is }; ${hookSource}`);
33
- const bridge = new SyntheticModule(['pointer', 'is'], function() {
34
- this.setExport('pointer', pointer);
35
- this.setExport('is', state);
36
- });
37
- await hooks.link(() => bridge);
38
- await hooks.evaluate();
39
-
40
- const template = await readFile(new URL('../dist/sources/control/web/layout.html', import.meta.url), 'utf8');
41
- const host = document.createElement('div');
42
- document.body.append(host);
43
- const app = createApp({
44
- render: compile(template),
45
- data: () => ({ src: 'about:blank' }),
46
- computed: { showMask: () => control.showMask },
47
- });
48
- app.mount(host);
49
- const iframe = host.querySelector('iframe');
50
- const mask = host.querySelector('.mask');
51
- assert.equal(iframe.getAttribute('src'), 'about:blank');
52
- assert.equal(mask.style.display, 'none');
53
-
54
- function event(type, x, y) {
55
- const e = new dom.window.Event(type, { bubbles: true, cancelable: true });
56
- Object.assign(e, { clientX: x, clientY: y, pointerId: 1, pointerType: 'mouse', button: 0 });
57
- return e;
58
- }
59
-
60
- for (const operation of ['move', 'resize']) {
61
- for (const finish of ['pointerup', 'pointercancel']) {
62
- let updates = 0;
63
- const handle = document.createElement('div');
64
- document.body.append(handle);
65
- handle.addEventListener('pointerdown', e => {
66
- if (operation === 'resize') {
67
- pointer.resize(e, {
68
- border: 'rb', objectLeft: 0, objectTop: 0,
69
- objectWidth: 400, objectHeight: 300,
70
- move: () => { ++updates; },
71
- });
72
- }
73
- else {
74
- pointer.move(e, { move: () => { ++updates; } });
75
- }
76
- });
77
- handle.dispatchEvent(event('pointerdown', 400, 300));
78
- await nextTick();
79
- assert.equal(state.move, true);
80
- assert.notEqual(mask.style.display, 'none');
81
- mask.dispatchEvent(event('pointermove', 350, 250));
82
- mask.dispatchEvent(event('pointermove', 320, 230));
83
- assert.equal(updates, 2);
84
- mask.dispatchEvent(event(finish, 320, 230));
85
- await nextTick();
86
- assert.equal(state.move, false);
87
- assert.equal(mask.style.display, 'none');
88
- mask.dispatchEvent(event('pointermove', 300, 200));
89
- assert.equal(updates, 2);
90
- assert.equal(host.querySelector('iframe'), iframe);
91
- handle.remove();
92
- }
93
- }
94
- app.unmount();
95
- dom.window.close();
96
- console.log('WEB drag/resize mask, pointerup/cancel cleanup and iframe preservation checks passed.');