clickgo 6.6.0 → 6.7.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clickgo",
3
- "version": "6.6.0",
3
+ "version": "6.7.1",
4
4
  "description": "Background interface, software interface, mobile phone APP interface operation library.",
5
5
  "type": "module",
6
6
  "keywords": [
@@ -8,7 +8,8 @@ const sizeUnwatches = [];
8
8
 
9
9
  globalThis.document = {
10
10
  'documentElement': {
11
- 'clientWidth': 360
11
+ 'clientWidth': 360,
12
+ 'clientHeight': 800
12
13
  }
13
14
  };
14
15
 
@@ -32,6 +33,14 @@ class AbstractControl {
32
33
  return Boolean(this.props[name]);
33
34
  }
34
35
 
36
+ propNumber(name) {
37
+ return Number(this.props[name]);
38
+ }
39
+
40
+ parentByName(name) {
41
+ return this.parents?.[name] ?? null;
42
+ }
43
+
35
44
  emit() {}
36
45
  }
37
46
 
@@ -56,6 +65,9 @@ const clickgo = new SyntheticModule(['control', 'dom'], function() {
56
65
  },
57
66
  isRtl(element) {
58
67
  return element.rtl;
68
+ },
69
+ index(element) {
70
+ return element.order;
59
71
  }
60
72
  });
61
73
  });
@@ -146,6 +158,10 @@ group.dock = {
146
158
  group.updateFloatLayout();
147
159
  assert.equal(group.floatMaxWidth, 280, 'LTR left Dock must use only the viewport space on its right');
148
160
  assert.equal(group.floatStyle['max-width'], '280px');
161
+ assert.equal(group.floatStyle.height, undefined, 'short floating panels must size to their content');
162
+ group.props.grow = true;
163
+ assert.equal(group.floatStyle.height, '300px', 'grow floating panels fill the available height');
164
+ group.props.grow = false;
149
165
 
150
166
  group.element.rtl = true;
151
167
  areaRect.left = 320;
@@ -159,14 +175,106 @@ areaRect.right = 40;
159
175
  group.updateFloatLayout();
160
176
  assert.equal(group.floatMaxWidth, 320, 'RTL right Dock must open toward the viewport space on its right');
161
177
 
178
+ group.element.rtl = false;
179
+ group.dock.getFloatBounds = () => ({ 'left': 30, 'right': 330, 'top': 60, 'bottom': 220 });
180
+ areaRect.left = 300;
181
+ areaRect.right = 340;
182
+ group.updateFloatLayout();
183
+ assert.equal(group.floatMaxWidth, 270, 'floating panels must stay inside their Form, not only the viewport');
184
+ assert.equal(group.floatMaxHeight, '160px');
185
+ assert.equal(group.floatTop, 60);
186
+ group.dock.positionData = 'left';
187
+ group.updateFloatLayout();
188
+ assert.equal(group.floatMaxWidth, 0, 'zero available width must remain zero rather than use a fallback');
189
+ assert.equal(group.floatStyle['max-width'], '0px');
190
+
191
+ const columnMod = new SourceTextModule(await readFile(new URL('../dist/sources/control/dock-column/code.js', import.meta.url), 'utf8'));
192
+ await columnMod.link(() => clickgo);
193
+ await columnMod.evaluate();
194
+ const DockColumn = columnMod.namespace.default;
195
+
196
+ document.documentElement.clientWidth = 1200;
197
+ document.documentElement.clientHeight = 900;
198
+ const multiForm = {
199
+ 'element': {
200
+ 'isConnected': true,
201
+ 'offsetWidth': 1100,
202
+ 'getBoundingClientRect': () => ({ 'left': 60, 'right': 1160, 'top': 30, 'bottom': 930 })
203
+ }
204
+ };
205
+ const multiDock = new Dock(multiForm, 600);
206
+ await multiDock.onMounted();
207
+ assert.deepEqual(multiDock.getFloatBounds(), { 'left': 60, 'right': 1160, 'top': 30, 'bottom': 900 });
208
+
209
+ const projectColumn = new DockColumn(multiForm, 600);
210
+ const mainColumn = new DockColumn(multiForm, 600);
211
+ projectColumn.parents = mainColumn.parents = { 'dock': multiDock };
212
+ projectColumn.element = { 'order': 0 };
213
+ mainColumn.element = { 'order': 1 };
214
+ projectColumn.props.width = 220;
215
+ mainColumn.props.width = 340;
216
+ // Child groups mount before their column: both columns must allocate through the same root.
217
+ const projectIndex = projectColumn.registerGroup();
218
+ const mainIndex = mainColumn.registerGroup();
219
+ assert.notEqual(projectIndex, mainIndex);
220
+ await projectColumn.onMounted();
221
+ await mainColumn.onMounted();
222
+ assert.equal(projectColumn.expandedData, true);
223
+ assert.equal(mainColumn.expandedData, true);
224
+
225
+ function resizeMultiForm(width) {
226
+ multiForm.element.offsetWidth = width;
227
+ multiDock.access.formSizeWatch.handler();
228
+ }
229
+ resizeMultiForm(850);
230
+ assert.equal(projectColumn.autoCollapsed, true, 'the auxiliary column collapses first');
231
+ assert.equal(mainColumn.autoCollapsed, false, 'keep the main column open when it still fits');
232
+ assert.equal(projectColumn.expandedPreference, true, 'automatic collapse must preserve user preference');
233
+ assert.equal(projectColumn.props.expanded, true);
234
+ assert.match(multiDock.widthComp, /var\(--dock-collapsed-width\).*340px/);
235
+
236
+ resizeMultiForm(650);
237
+ assert.equal(projectColumn.expandedData, false);
238
+ assert.equal(mainColumn.expandedData, false, 'collapse the main column too when workspace space is insufficient');
239
+ resizeMultiForm(1100);
240
+ assert.equal(projectColumn.expandedData, true);
241
+ assert.equal(mainColumn.expandedData, true);
242
+
243
+ document.documentElement.clientWidth = 600;
244
+ multiDock.access.viewportSizeWatch.handler();
245
+ assert.equal(projectColumn.expandedData, false);
246
+ assert.equal(mainColumn.expandedData, false, 'viewport clipping must also limit multiple expanded columns');
247
+ document.documentElement.clientWidth = 1200;
248
+ multiDock.access.viewportSizeWatch.handler();
249
+ assert.equal(mainColumn.expandedData, true);
250
+ projectColumn.toggle();
251
+ assert.equal(projectColumn.expandedPreference, false);
252
+ resizeMultiForm(650);
253
+ resizeMultiForm(1100);
254
+ assert.equal(projectColumn.expandedData, false, 'a column the user closed must remain closed after resizing');
255
+ assert.equal(mainColumn.expandedData, true);
256
+
257
+ multiDock.toggleFloat(projectIndex);
258
+ multiDock.toggleFloat(mainIndex);
259
+ assert.equal(multiDock.floatGroup, mainIndex, 'only one floating group may be open across columns');
260
+ mainColumn.unregisterGroup(mainIndex);
261
+ assert.equal(multiDock.floatGroup, -1);
262
+ multiDock.toggleFloat(projectIndex);
263
+ projectColumn.onUnmounted();
264
+ assert.equal(multiDock.columns.length, 1);
265
+ assert.equal(multiDock.floatGroup, -1, 'column removal closes its floating panel');
266
+ mainColumn.onUnmounted();
267
+ multiDock.onUnmounted();
268
+
162
269
  const dockStyle = await readFile(new URL('../dist/sources/control/dock/style.scss', import.meta.url), 'utf8');
163
270
  assert.match(dockStyle, /border-inline-end/, 'left Dock border must follow the inline end edge');
164
271
  assert.match(dockStyle, /border-inline-start/, 'right Dock border must follow the inline start edge');
165
- assert.match(dockStyle, /:dir\(rtl\)[\s\S]*scaleX\(-1\)/, 'Dock chevron must mirror in RTL');
272
+ const toggleStyle = await readFile(new URL('../dist/sources/control/sidebar-toggle/style.scss', import.meta.url), 'utf8');
273
+ assert.match(toggleStyle, /:dir\(rtl\)[\s\S]*scaleX\(-1\)/, 'shared sidebar chevron must mirror in RTL');
166
274
 
167
275
  const groupStyle = await readFile(new URL('../dist/sources/control/dock-group/style.scss', import.meta.url), 'utf8');
168
276
  assert.match(groupStyle, /inset-inline-start:\s*100%/, 'left Dock floating panel must open toward inline end');
169
277
  assert.match(groupStyle, /inset-inline-end:\s*100%/, 'right Dock floating panel must open toward inline start');
170
278
  assert.match(groupStyle, /max-width:\s*calc\(100vw - 40px\)/, 'floating panel must reserve the collapsed Dock width');
171
279
 
172
- console.log('Dock lifecycle, RTL layout and viewport-bound floating-panel checks passed.');
280
+ console.log('Dock lifecycle, multiple columns, preference restoration, RTL and Form/viewport bounds passed.');
@@ -140,7 +140,7 @@ for (const path of localeSources) {
140
140
  tables.push({ ...table, path });
141
141
  }
142
142
  }
143
- assert.equal(tables.length, 24, 'all 24 built-in TypeScript locale tables must be present');
143
+ assert.equal(tables.length, 26, 'all 26 built-in TypeScript locale tables must be present');
144
144
  for (const { node, path, line } of tables) {
145
145
  const tableCodes = node.properties.map(getPropertyName);
146
146
  assert.deepEqual([...tableCodes].sort(), [...codes].sort(), `${path}:${line} must contain exactly 16 locale entries`);
@@ -186,4 +186,4 @@ const demoLocaleCodes = [...demoLocaleData.matchAll(/'([^']+)'/g)].map((match) =
186
186
  assert.deepEqual(demoLocaleCodes, codes, 'demo global locale selector must expose all 16 locales in registry order');
187
187
 
188
188
  dom.window.close();
189
- console.log('Locale registry, 24 TypeScript tables, 16 task app locale JSON files and the Demo selector are complete.');
189
+ console.log(`Locale registry, ${tables.length} TypeScript tables, 16 task app locale JSON files and the Demo selector are complete.`);
@@ -14,12 +14,12 @@ const codes = [
14
14
  /** --- 包含 localeData 的全部内置控件 --- */
15
15
  const localeControls = [
16
16
  'arteditor', 'calendar', 'captcha', 'date', 'datepanel', 'daterange', 'empty', 'jodit', 'label',
17
- 'levelselect', 'map', 'monaco', 'number', 'page', 'palette', 'property', 'select', 'text', 'tuieditor',
17
+ 'levelselect', 'map', 'monaco', 'number', 'page', 'palette', 'property', 'select', 'sidebar-toggle', 'text', 'toolbox', 'tuieditor',
18
18
  ];
19
19
  /** --- 合并到 common.cgc 的多语言控件 --- */
20
20
  const commonControls = new Set([
21
21
  'calendar', 'date', 'datepanel', 'daterange', 'empty', 'label', 'levelselect', 'number', 'palette',
22
- 'select', 'text',
22
+ 'select', 'sidebar-toggle', 'text', 'toolbox',
23
23
  ]);
24
24
  /** --- 全部内置主题 --- */
25
25
  const themes = ['admin', 'cyber', 'dark', 'light', 'modern', 'modern-light', 'compact-dark', 'compact-light'];
@@ -335,7 +335,7 @@ for (const code of codes) {
335
335
  }
336
336
  }
337
337
 
338
- // --- 19 个控件的归档语言表必须与 TypeScript 源码一致 ---
338
+ // --- 所有多语言控件的归档语言表必须与 TypeScript 源码一致 ---
339
339
  const archivedControlCode = new Map();
340
340
  for (const control of localeControls) {
341
341
  const packageName = commonControls.has(control) ? 'common' : control;
@@ -365,6 +365,10 @@ const rtlCodeChecks = [
365
365
  ['dist/control/common.cgc', 'date', ['autoPosition', 'autoScroll']],
366
366
  ['dist/control/common.cgc', 'daterange', ['autoPosition', 'autoScroll']],
367
367
  ['dist/control/common.cgc', 'dock-group', ['isRtl', 'floatMaxWidth']],
368
+ ['dist/control/common.cgc', 'dock', ['updateColumns', 'getFloatBounds']],
369
+ ['dist/control/common.cgc', 'dock-column', ['registerGroup', 'expandedPreference']],
370
+ ['dist/control/common.cgc', 'toolbox', ['getBoundingClientRect', 'toggleColumns']],
371
+ ['dist/control/common.cgc', 'sidebar-toggle', ['chevronRight', 'tipLabelComp', 'update:expanded']],
368
372
  ['dist/control/table.cgc', 'table', ['localeDirection', 'getScrollLeft', 'setScrollInlineOffset']],
369
373
  ['dist/control/desc.cgc', 'desc', ['getScrollInlineOffset', 'setScrollInlineOffset']],
370
374
  ['dist/control/iconview.cgc', 'iconview', ['isRtl']],
@@ -387,8 +391,11 @@ const rtlStyleChecks = [
387
391
  ['dist/control/common.cgc', 'list/style.css', 'dist/sources/control/list/style.css', [':dir(rtl)', 'margin-inline-end']],
388
392
  ['dist/control/common.cgc', 'tab/style.css', 'dist/sources/control/tab/style.css', ['border-inline-start-width', 'border-inline-end-width']],
389
393
  ['dist/control/common.cgc', 'timeline/style.css', 'dist/sources/control/timeline/style.css', ['border-inline-start']],
390
- ['dist/control/common.cgc', 'dock/style.css', 'dist/sources/control/dock/style.css', [':dir(rtl)', 'border-inline-end']],
394
+ ['dist/control/common.cgc', 'dock/style.css', 'dist/sources/control/dock/style.css', ['border-inline-end']],
391
395
  ['dist/control/common.cgc', 'dock-group/style.css', 'dist/sources/control/dock-group/style.css', ['inset-inline-start', 'max-width:calc(100vw - 40px)']],
396
+ ['dist/control/common.cgc', 'dock-column/style.css', 'dist/sources/control/dock-column/style.css', ['border-inline-start']],
397
+ ['dist/control/common.cgc', 'toolbox/style.css', 'dist/sources/control/toolbox/style.css', ['border-inline-end']],
398
+ ['dist/control/common.cgc', 'sidebar-toggle/style.css', 'dist/sources/control/sidebar-toggle/style.css', [':dir(rtl)', 'border-bottom', '--sidebar-toggle-background']],
392
399
  ['dist/control/table.cgc', 'table/style.css', 'dist/sources/control/table/style.css', ['border-inline-end', 'inset-inline-end']],
393
400
  ['dist/control/xterm.cgc', 'xterm/style.css', 'dist/sources/control/xterm/style.css', ['direction:ltr']],
394
401
  ];
@@ -417,4 +424,4 @@ for (const theme of themes) {
417
424
  );
418
425
  }
419
426
 
420
- console.log(`Archive check passed: task.cga has 16 locales, 19 control locale tables are current, and RTL output is present in all ${themes.length} themes.`);
427
+ console.log(`Archive check passed: task.cga has 16 locales, ${localeControls.length} control locale tables are current, and RTL output is present in all ${themes.length} themes.`);
@@ -0,0 +1,75 @@
1
+ // Run after TypeScript compilation: node --experimental-vm-modules test/sidebar-toggle.mjs
2
+ import assert from 'node:assert/strict';
3
+ import { readFile } from 'node:fs/promises';
4
+ import { SourceTextModule, SyntheticModule } from 'node:vm';
5
+
6
+ class AbstractControl {
7
+ constructor() {
8
+ this.emitted = [];
9
+ this.locale = 'sc';
10
+ }
11
+ propBoolean(name) {
12
+ return [true, 'true', ''].includes(this.props[name]);
13
+ }
14
+ l(key) {
15
+ return this.localeData[this.locale][key];
16
+ }
17
+ emit(name, value) {
18
+ this.emitted.push([name, value]);
19
+ }
20
+ }
21
+
22
+ const clickgo = new SyntheticModule(['control'], function() {
23
+ this.setExport('control', { AbstractControl });
24
+ });
25
+ const mod = new SourceTextModule(await readFile(new URL('../dist/sources/control/sidebar-toggle/code.js', import.meta.url), 'utf8'));
26
+ await mod.link(() => clickgo);
27
+ await mod.evaluate();
28
+ const SidebarToggle = mod.namespace.default;
29
+ const toggle = new SidebarToggle();
30
+
31
+ for (const [position, expanded, pointsRight] of [
32
+ ['left', false, true], ['left', true, false], ['right', false, false], ['right', true, true],
33
+ ]) {
34
+ toggle.props.position = position;
35
+ toggle.props.expanded = expanded;
36
+ assert.equal(toggle.chevronRight, pointsRight, `${position}, expanded=${expanded}`);
37
+ }
38
+
39
+ toggle.props.expanded = 'false';
40
+ toggle.props.label = 'Project';
41
+ assert.equal(toggle.tipLabelComp, '展开侧栏 · Project');
42
+ toggle.props.expanded = 'true';
43
+ assert.equal(toggle.tipLabelComp, '收起侧栏 · Project');
44
+ toggle.locale = 'ar';
45
+ assert.equal(toggle.tipLabelComp, 'طي الشريط الجانبي · Project');
46
+ toggle.props.tipLabel = 'Use one tool column';
47
+ assert.equal(toggle.tipLabelComp, 'Use one tool column', 'Toolbox keeps its column-specific operation');
48
+
49
+ for (const key of ['Enter', ' ']) {
50
+ toggle.props.expanded = false;
51
+ const count = toggle.emitted.length;
52
+ let prevented = false;
53
+ let stopped = false;
54
+ const event = {
55
+ key, 'repeat': false,
56
+ 'preventDefault': () => { prevented = true; },
57
+ 'stopPropagation': () => { stopped = true; },
58
+ };
59
+ toggle.keydown(event);
60
+ assert.equal(prevented && stopped, true);
61
+ assert.deepEqual(toggle.emitted[count], ['update:expanded', true]);
62
+ assert.equal(toggle.props.expanded, false, 'the owner applies the requested state');
63
+ toggle.keydown({ ...event, 'repeat': true });
64
+ assert.equal(toggle.emitted.length, count + 1, 'holding a key must not repeatedly flip the sidebar');
65
+ }
66
+ toggle.keydown({
67
+ 'key': 'Tab',
68
+ 'preventDefault': () => assert.fail('Tab keeps native focus navigation'),
69
+ 'stopPropagation': () => assert.fail('Other keys are not consumed'),
70
+ });
71
+ toggle.props.expanded = true;
72
+ toggle.toggle();
73
+ assert.deepEqual(toggle.emitted.at(-1), ['update:expanded', false]);
74
+
75
+ console.log('Shared sidebar toggle direction, localized tips, controlled state and keyboard activation passed.');
@@ -0,0 +1,88 @@
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.');