agentcss 0.25.6 → 0.27.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 (81) hide show
  1. package/01-tokens/README.md +1 -1
  2. package/01-tokens/behavior/labels.js +44 -0
  3. package/01-tokens/responsive-demo.html +1 -1
  4. package/01-tokens/tokens.core.css +1 -1
  5. package/01-tokens/tokens.css +2 -2
  6. package/02-primitives/combobox/combobox.js +26 -3
  7. package/02-primitives/date-range/date-range.js +28 -6
  8. package/02-primitives/popover/popover.js +22 -2
  9. package/02-primitives/select/select.js +23 -2
  10. package/02-primitives/timeline/demo.html +1 -1
  11. package/02-primitives/upload/upload.js +50 -17
  12. package/03-patterns/content/content.css +13 -1
  13. package/03-patterns/content/demo.html +1 -1
  14. package/03-patterns/drawer/demo.html +1 -1
  15. package/03-patterns/drawer/drawer.js +44 -6
  16. package/03-patterns/dropdown/dropdown.js +21 -1
  17. package/03-patterns/overlay/overlay.css +1 -1
  18. package/03-patterns/overlay/overlay.js +39 -2
  19. package/03-patterns/pagination/pagination.js +37 -10
  20. package/03-patterns/states/states.css +1 -1
  21. package/03-patterns/tooltip/tooltip.js +17 -2
  22. package/03-patterns/tree/tree.js +21 -1
  23. package/04-recipes/analysis-report/demo.html +1 -1
  24. package/04-recipes/app-shell/README.md +1 -1
  25. package/04-recipes/app-shell/demo.html +1 -1
  26. package/04-recipes/data-showcase/demo.html +2 -2
  27. package/04-recipes/longform/content.html +993 -78
  28. package/04-recipes/longform/longform.html +3 -3
  29. package/04-recipes/report/demo.html +3 -3
  30. package/04-recipes/table/README.md +23 -1
  31. package/04-recipes/table/demo.html +7 -7
  32. package/09-assets/bar/bar.css +1 -1
  33. package/09-assets/bar/bar.js +28 -4
  34. package/09-assets/model-viewer/model-viewer.js +45 -13
  35. package/09-assets/scientific-plot/demo.html +6 -6
  36. package/AGENTS.md +3 -1
  37. package/API.md +32 -0
  38. package/CHANGELOG.md +174 -5
  39. package/CONTRIBUTING.md +44 -3
  40. package/README.md +19 -0
  41. package/README.zh-CN.md +18 -0
  42. package/SECURITY.md +16 -4
  43. package/START-HERE.md +25 -18
  44. package/ai/START-HERE.md +40 -6
  45. package/ai/check-html.js +658 -0
  46. package/ai/cli.js +557 -67
  47. package/ai/components.json +9 -4
  48. package/ai/pdf.js +52 -10
  49. package/ai/recipes.json +86 -0
  50. package/ai/scaffold.js +195 -0
  51. package/ai/shot.js +345 -0
  52. package/ai/validate.js +65 -6
  53. package/dist/01-tokens/behavior/labels.js +11 -0
  54. package/dist/02-primitives/combobox/combobox.js +25 -3
  55. package/dist/02-primitives/date-range/date-range.js +26 -5
  56. package/dist/02-primitives/popover/popover.js +14 -1
  57. package/dist/02-primitives/select/select.js +22 -2
  58. package/dist/02-primitives/upload/upload.js +47 -17
  59. package/dist/03-patterns/content/content.css +1 -1
  60. package/dist/03-patterns/drawer/drawer.js +32 -4
  61. package/dist/03-patterns/dropdown/dropdown.js +20 -1
  62. package/dist/03-patterns/overlay/overlay.js +25 -1
  63. package/dist/03-patterns/pagination/pagination.js +34 -10
  64. package/dist/03-patterns/tooltip/tooltip.js +14 -2
  65. package/dist/03-patterns/tree/tree.js +20 -1
  66. package/dist/09-assets/bar/bar.js +23 -4
  67. package/dist/09-assets/model-viewer/model-viewer.js +38 -12
  68. package/dist/COSTS.md +20 -20
  69. package/docs/ACCESSIBILITY.md +3 -3
  70. package/docs/ARCHITECTURE.md +116 -0
  71. package/docs/L10N.md +92 -0
  72. package/docs/LAYER-BASELINE.md +2 -2
  73. package/docs/PRINT.md +3 -3
  74. package/docs/RELEASE-PROCESS.md +59 -3
  75. package/docs/STABILITY.md +1 -1
  76. package/docs/VERIFY-RELEASE.md +24 -2
  77. package/index.html +3 -3
  78. package/llms-full.txt +18 -4
  79. package/llms.txt +26 -5
  80. package/package.json +4 -2
  81. package/types/index.d.ts +218 -13
@@ -32,7 +32,38 @@ error: 'M8 1.7a6.3 6.3 0 1 0 0 12.6A6.3 6.3 0 0 0 8 1.7zM8 4.9v3.4M8 11.1v.5',
32
32
  remove: 'M4.2 4.2l7.6 7.6M11.8 4.2l-7.6 7.6'
33
33
  };
34
34
 
35
- var STATE_TEXT = { pending: '待上传', done: '已完成', error: '失败' };
35
+ var DEFAULT_LABELS = {
36
+ pending: '待上传',
37
+ done: '已完成',
38
+ error: '失败',
39
+ remove: function (name) { return '移除 ' + name; },
40
+ uploading: function (pct) { return '上传中 ' + Math.round(pct) + '%'; },
41
+ countMax: function (n) { return '最多 ' + n + ' 个文件'; },
42
+ sizeMax: function (limit) { return '超过 ' + limit; },
43
+ type: '类型不支持',
44
+ folder: '请拖入文件,不支持文件夹',
45
+ none: function (reason) { return reason ? '未添加任何文件:' + reason : '未添加任何文件'; },
46
+ noDrop: '当前浏览器不支持拖放,请点「选择文件」',
47
+ noDropShort: '当前浏览器不支持拖放,请点选择文件',
48
+ summary: function (added, total, rejected) {
49
+ var s = '已添加 ' + added + ' 个文件,共 ' + total + ' 个';
50
+ if (rejected) s += ';' + rejected + ' 个未通过校验';
51
+ return s;
52
+ },
53
+ removed: function (name, rest) { return '已移除 ' + name + ',还剩 ' + rest + ' 个'; }
54
+ };
55
+
56
+ var flLabels = function (defaults, src) {
57
+ src = src || {};
58
+ var out = {};
59
+ var k;
60
+ for (k in defaults) {
61
+ if (Object.prototype.hasOwnProperty.call(defaults, k)) {
62
+ out[k] = (src[k] === undefined || src[k] === null) ? defaults[k] : src[k];
63
+ }
64
+ }
65
+ return out;
66
+ };
36
67
 
37
68
  function humanSize(n) {
38
69
  if (typeof n !== 'number' || n < 0) return '';
@@ -110,6 +141,7 @@ function Upload(root, opts) {
110
141
  var opt = opts || {};
111
142
 
112
143
  this.root = root;
144
+ this.labels = flLabels(DEFAULT_LABELS, opt && opt.labels);
113
145
  this.input = root.querySelector('.upload__input');
114
146
  this.list = root.querySelector('.upload__list');
115
147
  this.status = root.querySelector('.upload__status');
@@ -209,15 +241,15 @@ var accept = this.input.getAttribute('accept');
209
241
  for (var i = 0; i < incoming.length; i++) {
210
242
  var f = incoming[i];
211
243
  if (maxFiles && keep.length >= maxFiles) {
212
- rejected.push({ file: f, reason: 'count', text: '最多 ' + maxFiles + ' 个文件' });
244
+ rejected.push({ file: f, reason: 'count', text: this.labels.countMax(maxFiles) });
213
245
  continue;
214
246
  }
215
247
  if (this.maxSize && f.size > this.maxSize) {
216
- rejected.push({ file: f, reason: 'size', text: '超过 ' + humanSize(this.maxSize) });
248
+ rejected.push({ file: f, reason: 'size', text: this.labels.sizeMax(humanSize(this.maxSize)) });
217
249
  continue;
218
250
  }
219
251
  if (!matchesAccept(f, accept)) {
220
- rejected.push({ file: f, reason: 'type', text: '类型不支持' });
252
+ rejected.push({ file: f, reason: 'type', text: this.labels.type });
221
253
  continue;
222
254
  }
223
255
  keep.push(f);
@@ -231,7 +263,7 @@ var r = this._validate(incoming);
231
263
  if (dirCount) {
232
264
  r.rejected.push({
233
265
  file: { name: dirCount + ' 个文件夹', size: 0 },
234
- reason: 'dir', text: '请拖入文件,不支持文件夹'
266
+ reason: 'dir', text: this.labels.folder
235
267
  });
236
268
  }
237
269
 
@@ -239,7 +271,7 @@ if (!r.keep.length) {
239
271
 
240
272
  this.rejected = r.rejected;
241
273
  this.render();
242
- this.announce(r.rejected.length ? '未添加任何文件:' + r.rejected[0].text : '未添加任何文件');
274
+ this.announce(this.labels.none(r.rejected.length ? r.rejected[0].text : ''));
243
275
  flEmit(this.root, 'fl-upload', {
244
276
  files: currentFiles(this.input), added: 0, rejected: r.rejected
245
277
  });
@@ -249,9 +281,9 @@ return false;
249
281
  if (!writeFiles(this.input, r.keep)) {
250
282
 
251
283
  this.rejected = [{ file: r.keep[0], reason: 'unsupported',
252
- text: '当前浏览器不支持拖放,请点「选择文件」' }];
284
+ text: this.labels.noDrop }];
253
285
  this.render();
254
- this.announce('当前浏览器不支持拖放,请点选择文件');
286
+ this.announce(this.labels.noDropShort);
255
287
  return false;
256
288
  }
257
289
 
@@ -317,7 +349,7 @@ mark.appendChild(iconEl(state === 'done' ? 'done' : state === 'error' ? 'error'
317
349
 
318
350
  var sr = document.createElement('span');
319
351
  sr.className = 'upload__status';
320
- sr.textContent = STATE_TEXT[state] || '';
352
+ sr.textContent = this.labels[state] || '';
321
353
  mark.appendChild(sr);
322
354
 
323
355
  var name = document.createElement('span');
@@ -346,12 +378,12 @@ this.paintProgress(bar0, pct);
346
378
  var rm = document.createElement('button');
347
379
  rm.className = 'upload__remove';
348
380
  rm.type = 'button';
349
- rm.setAttribute('aria-label', '移除 ' + (file.name || ''));
381
+ rm.setAttribute('aria-label', this.labels.remove(file.name || ''));
350
382
  rm.appendChild(iconEl('remove'));
351
383
  rm.addEventListener('click', function () { self.removeAt(index); });
352
384
  li.appendChild(rm);
353
385
 
354
- if (pct != null && pct >= 0) meta.textContent = '上传中 ' + Math.round(pct) + '%';
386
+ if (pct != null && pct >= 0) meta.textContent = self.labels.uploading(pct);
355
387
  }
356
388
 
357
389
  return li;
@@ -396,7 +428,7 @@ mark.innerHTML = '';
396
428
  mark.appendChild(iconEl('done'));
397
429
  var sr = document.createElement('span');
398
430
  sr.className = 'upload__status';
399
- sr.textContent = STATE_TEXT.done;
431
+ sr.textContent = this.labels.done;
400
432
  mark.appendChild(sr);
401
433
  }
402
434
  if (meta && f) meta.textContent = humanSize(f.size);
@@ -417,7 +449,7 @@ li.insertBefore(bar, rmBtn || null);
417
449
  }
418
450
  this.paintProgress(bar, pct);
419
451
 
420
- if (meta) meta.textContent = '上传中 ' + Math.round(pct) + '%';
452
+ if (meta) meta.textContent = this.labels.uploading(pct);
421
453
  },
422
454
 
423
455
  removeAt: function (i) {
@@ -435,7 +467,7 @@ if (li && li.parentNode) li.parentNode.removeChild(li);
435
467
 
436
468
  this.rebind();
437
469
  this.syncCount();
438
- this.announce('已移除 ' + (removed.name || '文件') + ',还剩 ' + rest.length + ' 个');
470
+ this.announce(this.labels.removed(removed.name || '文件', rest.length));
439
471
  flEmit(this.root, 'fl-upload', {
440
472
  files: rest, added: 0, removed: removed, rejected: []
441
473
  });
@@ -467,9 +499,7 @@ this._announceTimer = global.setTimeout(function () { el.textContent = msg; }, 6
467
499
 
468
500
  summary: function (added, rejected) {
469
501
  var total = currentFiles(this.input).length;
470
- var s = '已添加 ' + added + ' 个文件,共 ' + total + ' 个';
471
- if (rejected) s += ';' + rejected + ' 个未通过校验';
472
- return s;
502
+ return this.labels.summary(added, total, rejected);
473
503
  },
474
504
 
475
505
  destroy: function () {
@@ -1 +1 @@
1
- .prose {max-width:var(--measure-prose);color:var(--text-primary);}.prose > * + * {margin-top:var(--sp-4);}.prose > h1, .prose > h2, .prose > h3, .prose > h4, .prose > h5, .prose > h6 {font-family:var(--sans);font-weight:500;line-height:var(--lh-tight);letter-spacing:-0.008em;margin:var(--sp-7) 0 0;}.prose > h1 {font-size:var(--fs-2xl);margin-top:0;}.prose > h2 {font-size:var(--fs-lg);padding-top:var(--sp-5);border-top:2px solid var(--accent);max-width:92%;}.prose > h3 {font-size:var(--fs-md);}.prose > h4, .prose > h5, .prose > h6 {font-size:var(--fs-base);color:var(--text-secondary);}.prose > p {margin:0;line-height:1.75;}.prose > code, .prose > li > code, .prose > td > code, .prose > th > code {font-family:var(--mono);font-size:0.88em;background:var(--surface-sunken);border:1px solid var(--border-decor);border-radius:3px;padding:0.05em 0.3em;}.prose > pre > code, .prose > pre {font-family:inherit;font-size:inherit;background:none;border:none;padding:0;border-radius:0;}.prose > ul, .prose > ol {padding-inline-start:1.5em;margin:0;}.prose > ul {list-style:disc;}.prose > ol {list-style:decimal;}.prose li + li {margin-top:var(--sp-2);}.prose li > ul, .prose li > ol {margin-top:var(--sp-2);}.prose li > ul {list-style:circle;}.prose > blockquote {margin:var(--sp-5) 0;padding:0 0 0 var(--sp-4);border-inline-start:3px solid var(--accent);}.prose > blockquote > p {color:var(--text-secondary);font-size:var(--fs-md);line-height:1.6;}.prose > blockquote > blockquote {margin:var(--sp-3) 0;}.prose > pre {margin:0;padding:var(--sp-4);background:var(--surface-sunken);border-inline-start:3px solid var(--border-decor-str);border-radius:0 var(--r-sm) var(--r-sm) 0;overflow-x:auto;font-family:var(--mono);font-size:var(--fs-xs);line-height:1.6;tab-size:4;-webkit-overflow-scrolling:touch;}.prose > pre:not([class]) {border-inline-start-color:var(--border-decor);}.prose > table {width:100%;border-collapse:collapse;table-layout:auto;font-size:var(--fs-sm);}.prose > table th, .prose > table td {text-align:start;padding:var(--sp-2) var(--sp-4) var(--sp-2) 0;border-bottom:1px solid var(--border-decor);vertical-align:top;}.prose > table th {font-weight:500;font-size:var(--fs-xs);color:var(--text-secondary);}.prose > table tbody tr:last-child td {border-bottom:1px solid var(--border-decor-str);}.prose > hr {margin:var(--sp-7) 0;border:0;border-top:1px solid var(--border-decor);height:0;}.prose > img {max-width:100%;height:auto;display:block;border-radius:var(--r-sm);}.prose a {color:var(--accent);text-decoration:underline;text-underline-offset:2px;text-decoration-thickness:1px;}@media (hover:hover) and (pointer:fine) {.prose a:hover {color:var(--text-primary);}}.prose a:focus-visible {outline:2px solid var(--accent);outline-offset:2px;}.prose > a[target="_blank"]::after {content:" ↗";font-size:0.8em;vertical-align:0.1em;}.prose > del, .prose > s, .prose > li > del, .prose > li > s {color:var(--text-tertiary);}.prose .task-list-item {list-style:none;margin-inline-start:-1.5em;}.prose .task-list-item input[type="checkbox"] {margin-inline-end:var(--sp-2);vertical-align:middle;}.prose .task-list-item input[disabled] {accent-color:var(--accent);}.prose :focus-visible {outline:2px solid var(--accent);outline-offset:2px;}.prose a:visited {color:var(--accent);text-decoration:underline;text-underline-offset:2px;}
1
+ .prose {max-width:var(--measure-prose);color:var(--text-primary);}.prose > * + * {margin-top:var(--sp-4);}.prose > h1, .prose > h2, .prose > h3, .prose > h4, .prose > h5, .prose > h6 {font-family:var(--sans);font-weight:500;line-height:var(--lh-tight);letter-spacing:-0.008em;margin:var(--sp-7) 0 0;}.prose > h1 {font-size:var(--fs-2xl);margin-top:0;}.prose > h2 {font-size:var(--fs-lg);padding-top:var(--sp-5);border-top:2px solid var(--accent);max-width:92%;}.prose > h3 {font-size:var(--fs-md);}.prose > h4, .prose > h5, .prose > h6 {font-size:var(--fs-base);color:var(--text-secondary);}.prose > p {margin:0;line-height:1.75;}.prose > code, .prose > li > code, .prose > td > code, .prose > th > code {font-family:var(--mono);font-size:0.88em;background:var(--surface-sunken);border:1px solid var(--border-decor);border-radius:3px;padding:0.05em 0.3em;}.prose > pre > code, .prose > pre {font-family:inherit;font-size:inherit;background:none;border:none;padding:0;border-radius:0;}.prose > ul, .prose > ol {padding-inline-start:1.5em;margin:0;}.prose > ul {list-style:disc;}.prose > ol {list-style:decimal;}.prose li + li {margin-top:var(--sp-2);}.prose li > ul, .prose li > ol {margin-top:var(--sp-2);}.prose li > ul {list-style:circle;}.prose > blockquote {margin:var(--sp-5) 0;padding:0 0 0 var(--sp-4);border-inline-start:3px solid var(--accent);}.prose > blockquote > p {color:var(--text-secondary);font-size:var(--fs-md);line-height:1.6;}.prose > blockquote > blockquote {margin:var(--sp-3) 0;}.prose > pre {margin:0;padding:var(--sp-4);background:var(--surface-sunken);border-inline-start:3px solid var(--border-decor-str);border-radius:0 var(--r-sm) var(--r-sm) 0;overflow-x:auto;font-family:var(--mono);font-size:var(--fs-xs);line-height:1.6;tab-size:4;-webkit-overflow-scrolling:touch;}.prose > pre:not([class]) {border-inline-start-color:var(--border-decor);}.prose > table {width:100%;border-collapse:collapse;table-layout:auto;font-size:var(--fs-sm);}.prose > table th, .prose > table td {text-align:start;padding:var(--sp-2) var(--sp-4) var(--sp-2) 0;border-bottom:1px solid var(--border-decor);vertical-align:top;}.prose > table th {font-weight:500;font-size:var(--fs-xs);color:var(--text-secondary);}.prose > table tbody tr:last-child td {border-bottom:1px solid var(--border-decor-str);}.prose > hr {margin:var(--sp-7) 0;border:0;border-top:1px solid var(--border-decor);height:0;}.prose img {max-width:100%;height:auto;display:block;border-radius:var(--r-sm);}.prose a {color:var(--accent);text-decoration:underline;text-underline-offset:2px;text-decoration-thickness:1px;}@media (hover:hover) and (pointer:fine) {.prose a:hover {color:var(--text-primary);}}.prose a:focus-visible {outline:2px solid var(--accent);outline-offset:2px;}.prose > a[target="_blank"]::after {content:" ↗";font-size:0.8em;vertical-align:0.1em;}.prose > del, .prose > s, .prose > li > del, .prose > li > s {color:var(--text-tertiary);}.prose .task-list-item {list-style:none;margin-inline-start:-1.5em;}.prose .task-list-item input[type="checkbox"] {margin-inline-end:var(--sp-2);vertical-align:middle;}.prose .task-list-item input[disabled] {accent-color:var(--accent);}.prose :focus-visible {outline:2px solid var(--accent);outline-offset:2px;}.prose a:visited {color:var(--accent);text-decoration:underline;text-underline-offset:2px;}
@@ -171,8 +171,34 @@ top: 'drawer--top',
171
171
  bottom: 'drawer--bottom'
172
172
  };
173
173
 
174
+ var SANITIZER = null;
175
+
176
+ function setSanitizer(fn) {
177
+ SANITIZER = (typeof fn === 'function') ? fn : null;
178
+ }
179
+
180
+ function sanitizeHtml(html, perCall) {
181
+ var fn = (typeof perCall === 'function') ? perCall : SANITIZER;
182
+ return fn ? String(fn(String(html))) : String(html);
183
+ }
184
+
185
+ var DEFAULT_LABELS = { dialog: '抽屉', close: '关闭' };
186
+
187
+ var flLabels = function (defaults, src) {
188
+ src = src || {};
189
+ var out = {};
190
+ var k;
191
+ for (k in defaults) {
192
+ if (Object.prototype.hasOwnProperty.call(defaults, k)) {
193
+ out[k] = (src[k] === undefined || src[k] === null) ? defaults[k] : src[k];
194
+ }
195
+ }
196
+ return out;
197
+ };
198
+
174
199
  function open(opt) {
175
200
  opt = opt || {};
201
+ var L = flLabels(DEFAULT_LABELS, opt && opt.labels);
176
202
  var place = opt.place || 'right';
177
203
  var placeCls = PLACE_CLASS[place];
178
204
  if (!placeCls) placeCls = PLACE_CLASS.right;
@@ -191,7 +217,7 @@ d.setAttribute('role', 'dialog');
191
217
  d.setAttribute('aria-modal', 'true');
192
218
  if (!d.id) d.id = 'drawer-' + (new Date()).getTime();
193
219
  if (opt.title) d.setAttribute('aria-labelledby', d.id + '-title');
194
- else d.setAttribute('aria-label', opt.label || '抽屉');
220
+ else d.setAttribute('aria-label', opt.label || L.dialog);
195
221
  d.tabIndex = -1;
196
222
 
197
223
  var head = document.createElement('div');
@@ -203,15 +229,17 @@ h.textContent = opt.title || '';
203
229
  var closeBtn = document.createElement('button');
204
230
  closeBtn.className = 'drawer__close';
205
231
  closeBtn.type = 'button';
206
- closeBtn.setAttribute('aria-label', '关闭');
232
+ closeBtn.setAttribute('aria-label', L.close);
207
233
  closeBtn.textContent = '\u00D7';
208
234
  head.appendChild(h);
209
235
  head.appendChild(closeBtn);
210
236
 
211
237
  var body = document.createElement('div');
212
238
  body.className = 'drawer__body';
239
+ if (typeof opt.content === 'string') {
213
240
 
214
- if (typeof opt.content === 'string') body.innerHTML = opt.content;
241
+ body.innerHTML = sanitizeHtml(opt.content, opt.sanitize);
242
+ }
215
243
  else if (opt.content) body.appendChild(opt.content);
216
244
 
217
245
  d.appendChild(head);
@@ -311,5 +339,5 @@ if (opt.onClose) opt.onClose();
311
339
  return { el: d, close: close };
312
340
  }
313
341
 
314
- global.Drawer = { open: open };
342
+ global.Drawer = { open: open, setSanitizer: setSanitizer };
315
343
  })(typeof window !== 'undefined' ? window : globalThis);
@@ -295,6 +295,25 @@ return Array.prototype.slice.call(menu.querySelectorAll(FOCUSABLE));
295
295
  }
296
296
 
297
297
  function create(root, opt) {
298
+
299
+ var DEFAULT_LABELS = {
300
+ menu: '菜单'
301
+ };
302
+
303
+ var flLabels = function (defaults, src) {
304
+ src = src || {};
305
+ var out = {};
306
+ var k;
307
+ for (k in defaults) {
308
+ if (Object.prototype.hasOwnProperty.call(defaults, k)) {
309
+ out[k] = (src[k] === undefined || src[k] === null) ? defaults[k] : src[k];
310
+ }
311
+ }
312
+ return out;
313
+ };
314
+
315
+ var L = flLabels(DEFAULT_LABELS, opt && opt.labels);
316
+
298
317
  opt = opt || {};
299
318
  var btn = root.querySelector('[data-dd-btn]') || root.querySelector('button');
300
319
  var menu = root.querySelector('[data-dd-menu]');
@@ -305,7 +324,7 @@ var active = -1;
305
324
 
306
325
  menu.setAttribute('role', 'menu');
307
326
  if (!menu.getAttribute('aria-label') && !menu.getAttribute('aria-labelledby')) {
308
- menu.setAttribute('aria-label', opt.label || '菜单');
327
+ menu.setAttribute('aria-label', opt.label || L.menu);
309
328
  }
310
329
  Array.prototype.forEach.call(menu.querySelectorAll('li'), function (li) {
311
330
 
@@ -156,8 +156,27 @@ error: 'toast--error',
156
156
  warning: 'toast--warning'
157
157
  };
158
158
 
159
+ var DEFAULT_LABELS = {
160
+ closeToast: '关闭提示'
161
+ };
162
+
163
+ var flLabels = function (defaults, src) {
164
+ src = src || {};
165
+ var out = {};
166
+ var k;
167
+ for (k in defaults) {
168
+ if (Object.prototype.hasOwnProperty.call(defaults, k)) {
169
+ out[k] = (src[k] === undefined || src[k] === null) ? defaults[k] : src[k];
170
+ }
171
+ }
172
+ return out;
173
+ };
174
+
175
+ var TOAST_LIMIT = 4;
176
+
159
177
  function toast(opts) {
160
178
  opts = opts || {};
179
+ var L = flLabels(DEFAULT_LABELS, opts && opts.labels);
161
180
 
162
181
  var region = document.querySelector('.toast-region');
163
182
  if (!region) {
@@ -203,7 +222,7 @@ el.appendChild(timer);
203
222
  var closeBtn = document.createElement('button');
204
223
  closeBtn.className = 'toast__close';
205
224
  closeBtn.type = 'button';
206
- closeBtn.setAttribute('aria-label', '关闭提示');
225
+ closeBtn.setAttribute('aria-label', L.closeToast);
207
226
  closeBtn.textContent = '×';
208
227
  el.appendChild(closeBtn);
209
228
 
@@ -220,6 +239,11 @@ if (el.parentNode) el.parentNode.removeChild(el);
220
239
  }
221
240
 
222
241
  closeBtn.addEventListener('click', close);
242
+
243
+ var max = (opts.maxVisible === undefined) ? TOAST_LIMIT : opts.maxVisible;
244
+ while (max > 0 && region.children.length >= max) {
245
+ region.removeChild(region.firstChild);
246
+ }
223
247
  region.appendChild(el);
224
248
 
225
249
  var timerId = auto > 0 ? setTimeout(close, auto) : null;
@@ -61,12 +61,36 @@ if (total > 1) out.push(total);
61
61
  return out;
62
62
  }
63
63
 
64
- function Pagination(node) {
64
+ var DEFAULT_LABELS = {
65
+ nav: '分页',
66
+ prev: '上一页',
67
+ next: '下一页',
68
+ page: function (p, isCurrent) {
69
+ return '第 ' + p + ' 页' + (isCurrent ? '(当前页)' : '');
70
+ },
71
+ status: function (page, pages) { return '第 ' + page + ' 页 / 共 ' + pages + ' 页'; },
72
+ live: function (page, pages) { return '第 ' + page + ' 页,共 ' + pages + ' 页'; }
73
+ };
74
+
75
+ var flLabels = function (defaults, src) {
76
+ src = src || {};
77
+ var out = {};
78
+ var k;
79
+ for (k in defaults) {
80
+ if (Object.prototype.hasOwnProperty.call(defaults, k)) {
81
+ out[k] = (src[k] === undefined || src[k] === null) ? defaults[k] : src[k];
82
+ }
83
+ }
84
+ return out;
85
+ };
86
+
87
+ function Pagination(node, opts) {
65
88
  this.node = node;
89
+ this.labels = flLabels(DEFAULT_LABELS, opts && opts.labels);
66
90
  this.total = Math.max(1, parseInt(node.getAttribute('data-total') || '1', 10));
67
91
  this.size = Math.max(1, parseInt(node.getAttribute('data-page-size') || '20', 10));
68
92
  this.page = Math.max(1, parseInt(node.getAttribute('data-page') || '1', 10));
69
- this.label = node.getAttribute('aria-label') || '分页';
93
+ this.label = node.getAttribute('aria-label') || this.labels.nav;
70
94
  this.pages = Math.max(1, Math.ceil(this.total / this.size));
71
95
  this.onChange = null;
72
96
 
@@ -186,7 +210,7 @@ if (cur && cur.focus) cur.focus();
186
210
 
187
211
  Pagination.prototype.announce = function () {
188
212
  if (!this.liveNode) return;
189
- this.liveNode.textContent = '第 ' + this.page + ' 页,共 ' + this.pages + ' 页';
213
+ this.liveNode.textContent = this.labels.live(this.page, this.pages);
190
214
  };
191
215
 
192
216
  Pagination.prototype.render = function () {
@@ -194,6 +218,7 @@ if (!this.listNode) return;
194
218
  var L = this.listNode;
195
219
  while (L.firstChild) L.removeChild(L.firstChild);
196
220
  var self = this;
221
+ var T = this.labels;
197
222
 
198
223
  function addBtn(text, ariaLabel, goto, disabled) {
199
224
  var li = el('li', 'pagination__item');
@@ -208,7 +233,7 @@ L.appendChild(li);
208
233
  return b;
209
234
  }
210
235
 
211
- addBtn('‹', '上一页', 'prev', self.page <= 1);
236
+ addBtn('‹', T.prev, 'prev', self.page <= 1);
212
237
 
213
238
  var win = pageWindow(self.page, self.pages, SLOTS);
214
239
  for (var i = 0; i < win.length; i++) {
@@ -227,7 +252,7 @@ var li2 = el('li', 'pagination__item');
227
252
  var b2 = el('button', 'pagination__link', String(p));
228
253
  b2.type = 'button';
229
254
  b2.setAttribute('data-goto', String(p));
230
- b2.setAttribute('aria-label', '第 ' + p + ' 页' + (isCur ? '(当前页)' : ''));
255
+ b2.setAttribute('aria-label', T.page(p, isCur));
231
256
  if (isCur) {
232
257
  b2.setAttribute('aria-current', 'page');
233
258
  b2.setAttribute('aria-disabled', 'true');
@@ -236,20 +261,19 @@ li2.appendChild(b2);
236
261
  L.appendChild(li2);
237
262
  }
238
263
 
239
- addBtn('›', '下一页', 'next', self.page >= self.pages);
264
+ addBtn('›', T.next, 'next', self.page >= self.pages);
240
265
 
241
266
  if (this.statusNode) {
242
- this.statusNode.textContent =
243
- '第 '+ self.page + ' 页 / 共 ' + self.pages + ' 页';
267
+ this.statusNode.textContent = T.status(self.page, self.pages);
244
268
  }
245
269
  };
246
270
 
247
271
  var API = {
248
272
 
249
- create: function (node) {
273
+ create: function (node, opts) {
250
274
  if (!node) return null;
251
275
  if (node.__pg) return node.__pg;
252
- node.__pg = new Pagination(node);
276
+ node.__pg = new Pagination(node, opts);
253
277
  return node.__pg;
254
278
  },
255
279
 
@@ -99,6 +99,18 @@ if (k >= 0) shared.stack.splice(k, 1);
99
99
 
100
100
  var uid = 0;
101
101
 
102
+ var LABELS = { region: '组件提示' };
103
+
104
+ function setLabels(patch) {
105
+ if (!patch) return;
106
+ for (var k in LABELS) {
107
+ if (Object.prototype.hasOwnProperty.call(LABELS, k)
108
+ && patch[k] !== undefined && patch[k] !== null) {
109
+ LABELS[k] = patch[k];
110
+ }
111
+ }
112
+ }
113
+
102
114
  function ensureStyle() {
103
115
  if (document.getElementById('tooltip-css')) return;
104
116
 
@@ -115,7 +127,7 @@ r.setAttribute('role', 'tooltip');
115
127
  r.setAttribute('aria-live', 'polite');
116
128
  r.setAttribute('aria-atomic', 'true');
117
129
 
118
- r.setAttribute('aria-label', '组件提示');
130
+ r.setAttribute('aria-label', LABELS.region);
119
131
  r.style.cssText =
120
132
  'position:absolute;width:1px;height:1px;padding:0;margin:-1px;'+
121
133
  'overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0';
@@ -211,5 +223,5 @@ el.setAttribute('aria-describedby', cur ? (cur + ' ' + id) : id);
211
223
  el.setAttribute('data-tooltip', text);
212
224
  }
213
225
 
214
- global.Tooltip = { attach: attach };
226
+ global.Tooltip = { attach: attach, setLabels: setLabels };
215
227
  })(typeof window !== 'undefined' ? window : globalThis);
@@ -196,11 +196,30 @@ return true;
196
196
  var uid = 0;
197
197
 
198
198
  function create(root, opt) {
199
+
200
+ var DEFAULT_LABELS = {
201
+ tree: '树形视图'
202
+ };
203
+
204
+ var flLabels = function (defaults, src) {
205
+ src = src || {};
206
+ var out = {};
207
+ var k;
208
+ for (k in defaults) {
209
+ if (Object.prototype.hasOwnProperty.call(defaults, k)) {
210
+ out[k] = (src[k] === undefined || src[k] === null) ? defaults[k] : src[k];
211
+ }
212
+ }
213
+ return out;
214
+ };
215
+
216
+ var L = flLabels(DEFAULT_LABELS, opt && opt.labels);
217
+
199
218
  opt = opt || {};
200
219
  root.classList.add('tree');
201
220
  if (!root.getAttribute('role')) root.setAttribute('role', 'tree');
202
221
  if (!root.getAttribute('aria-label') && !root.getAttribute('aria-labelledby')) {
203
- root.setAttribute('aria-label', opt.label || '树形视图');
222
+ root.setAttribute('aria-label', opt.label || L.tree);
204
223
  }
205
224
 
206
225
  var items = [];
@@ -27,9 +27,30 @@ e.setAttribute(k, String(attrs[k]));
27
27
  return e;
28
28
  }
29
29
 
30
+ var DEFAULT_TEXT = {
31
+ series: function (i) { return '系列 ' + i; },
32
+ clipped: function (n, isStack, max) {
33
+ return '有 ' + n + ' 个' + (isStack ? '类目的合计' : '数据')
34
+ + '超出量程(上限 ' + max + '),已按上限截断显示。';
35
+ }
36
+ };
37
+
38
+ var flLabels = function (defaults, src) {
39
+ src = src || {};
40
+ var out = {};
41
+ var k;
42
+ for (k in defaults) {
43
+ if (Object.prototype.hasOwnProperty.call(defaults, k)) {
44
+ out[k] = (src[k] === undefined || src[k] === null) ? defaults[k] : src[k];
45
+ }
46
+ }
47
+ return out;
48
+ };
49
+
30
50
  function draw(opt) {
31
51
  opt = opt || {};
32
52
  var host = opt.host;
53
+ var T = flLabels(DEFAULT_TEXT, opt && opt.text);
33
54
  if (!host) throw new Error('bar.draw: 缺少 host');
34
55
  if (opt.max === undefined || opt.max === null) {
35
56
 
@@ -187,7 +208,7 @@ sw.className = 'bar__legend-swatch ' + SERIES_CLS[si % SERIES_STYLES] +
187
208
 
188
209
  li.appendChild(sw);
189
210
  var tx = document.createElement('span');
190
- tx.textContent = se.name || ('系列 ' + (si + 1));
211
+ tx.textContent = se.name || T.series(si + 1);
191
212
  li.appendChild(tx);
192
213
  legendEl.appendChild(li);
193
214
  });
@@ -202,9 +223,7 @@ host.appendChild(p);
202
223
  } else if (clipped.length) {
203
224
  var p2 = document.createElement('p');
204
225
  p2.className = 'bar__note';
205
- p2.textContent = '有 ' + clipped.length + ' 个' +
206
- (mode === 'stack' ? '类目的合计' : '数据') +
207
- '超出量程(上限 '+ max + '),已按上限截断显示。';
226
+ p2.textContent = T.clipped(clipped.length, mode === 'stack', max);
208
227
  host.appendChild(p2);
209
228
  }
210
229
 
@@ -62,7 +62,7 @@ this.ui = el('div', 'mv__ui');
62
62
  this.ui.appendChild(this.makeIdle());
63
63
  n.appendChild(this.ui);
64
64
 
65
- this.btn = el('button', 'mv__btn', '加载 3D 模型');
65
+ this.btn = el('button', 'mv__btn', LABELS.load);
66
66
  this.btn.type = 'button';
67
67
  this.btn.addEventListener('click', function () { self.load(); });
68
68
  this.ui.appendChild(this.btn);
@@ -70,23 +70,23 @@ this.ui.appendChild(this.btn);
70
70
  this.hint = el('p', 'mv__hint');
71
71
 
72
72
  var fname = el('code', null, this.src.split('/').pop() || '—');
73
- var strong = el('b', null, '点击后才下载');
74
- this.hint.appendChild(document.createTextNode('模型 '));
73
+ var strong = el('b', null, LABELS.hintStrong);
74
+ this.hint.appendChild(document.createTextNode(LABELS.hintBefore));
75
75
  this.hint.appendChild(fname);
76
- this.hint.appendChild(document.createTextNode(' 会'));
76
+ this.hint.appendChild(document.createTextNode(LABELS.hintBetween));
77
77
  this.hint.appendChild(strong);
78
- this.hint.appendChild(document.createTextNode(',首屏不占流量。'));
78
+ this.hint.appendChild(document.createTextNode(LABELS.hintTail));
79
79
  this.ui.appendChild(this.hint);
80
80
 
81
81
  if (!hasWebGL()) {
82
- this.fail('这台设备的浏览器不支持 WebGL,无法显示 3D 模型。');
82
+ this.fail(LABELS.noWebGL);
83
83
  this.btn.disabled = true;
84
84
  }
85
85
  };
86
86
 
87
87
  Viewer.prototype.makeIdle = function () {
88
88
  var box = el('div', 'mv__stage');
89
- box.appendChild(el('div', 'mv__ph', '3D 模型'));
89
+ box.appendChild(el('div', 'mv__ph', LABELS.placeholder));
90
90
  return box;
91
91
  };
92
92
 
@@ -95,14 +95,14 @@ if (this.timer) { clearTimeout(this.timer); this.timer = null; }
95
95
  this.state = 'error';
96
96
  this.ui.innerHTML = '';
97
97
  var s = el('div', 'mv__stage mv__stage--err');
98
- s.appendChild(el('div', 'mv__ph', '3D 模型'));
98
+ s.appendChild(el('div', 'mv__ph', LABELS.placeholder));
99
99
  var p = el('p', 'mv__err', msg);
100
100
  s.appendChild(p);
101
101
  this.ui.appendChild(s);
102
102
  if (this.btn) this.ui.appendChild(this.btn);
103
103
  if (this.btn) {
104
104
  this.btn.disabled = false;
105
- this.btn.textContent = '重试';
105
+ this.btn.textContent = LABELS.retry;
106
106
  }
107
107
  };
108
108
 
@@ -117,12 +117,12 @@ this.state = 'loading';
117
117
 
118
118
  this.ui.innerHTML = '';
119
119
  var stage = el('div', 'mv__stage');
120
- this.statusNode = el('p', 'mv__status', '正在准备…');
120
+ this.statusNode = el('p', 'mv__status', LABELS.preparing);
121
121
  stage.appendChild(this.statusNode);
122
122
  this.ui.appendChild(stage);
123
123
 
124
124
  var t0 = (window.performance && performance.now) ? performance.now() : Date.now();
125
- this.status('正在准备 3D 引擎…');
125
+ this.status(LABELS.preparingEngine);
126
126
  this.boot(null, t0, 0);
127
127
 
128
128
  var self0 = this;
@@ -205,7 +205,7 @@ xhr.open('GET', this.src, true);
205
205
  xhr.onprogress = function (e) {
206
206
  if (e.lengthComputable && e.total) {
207
207
  self1.lastBytes = e.total;
208
- self1.status('正在下载模型 ' + Math.round(e.loaded / e.total * 100) + '%');
208
+ self1.status(LABELS.downloading(Math.round(e.loaded / e.total * 100)));
209
209
  }
210
210
  };
211
211
  xhr.onload = function () {
@@ -292,6 +292,30 @@ self.fail('模型加载失败:' + msg + '。请确认 ' + self.src + ' 已部
292
292
  }
293
293
  };
294
294
 
295
+ var LABELS = {
296
+ load: '加载 3D 模型',
297
+ placeholder: '3D 模型',
298
+ retry: '重试',
299
+ preparing: '正在准备…',
300
+ preparingEngine: '正在准备 3D 引擎…',
301
+ hintBefore: '模型 ',
302
+ hintBetween: ' 会',
303
+ hintStrong: '点击后才下载',
304
+ hintTail: ',首屏不占流量。',
305
+ downloading: function (pct) { return '正在下载模型 ' + pct + '%'; },
306
+ noWebGL: '这台设备的浏览器不支持 WebGL,无法显示 3D 模型。'
307
+ };
308
+
309
+ function setLabels(patch) {
310
+ if (!patch) return;
311
+ for (var k in LABELS) {
312
+ if (Object.prototype.hasOwnProperty.call(LABELS, k)
313
+ && patch[k] !== undefined && patch[k] !== null) {
314
+ LABELS[k] = patch[k];
315
+ }
316
+ }
317
+ }
318
+
295
319
  var ModelViewer = {
296
320
 
297
321
  update: function (root) {
@@ -317,6 +341,8 @@ list[i].__mv = new Viewer(list[i]);
317
341
  },
318
342
  };
319
343
 
344
+ ModelViewer.setLabels = setLabels;
345
+
320
346
  if (typeof window !== 'undefined') window.ModelViewer = ModelViewer;
321
347
 
322
348
  if (document.readyState === 'loading') {