@kupola/kupola 2.0.0-beta.2 → 2.0.0-beta.3

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 (35) hide show
  1. package/dist/kupola-core-datepicker.cjs.js +9 -9
  2. package/dist/kupola-core-datepicker.esm.js +9 -9
  3. package/dist/kupola-core-dialog.cjs.js +8 -8
  4. package/dist/kupola-core-dialog.esm.js +8 -8
  5. package/dist/kupola-core-drawer.cjs.js +5 -5
  6. package/dist/kupola-core-drawer.esm.js +5 -5
  7. package/dist/kupola-core-dropdown.cjs.js +4 -4
  8. package/dist/kupola-core-dropdown.esm.js +4 -4
  9. package/dist/kupola-core-i18n.cjs.js +1 -1
  10. package/dist/kupola-core-i18n.esm.js +1 -1
  11. package/dist/kupola-core-modal.cjs.js +2 -2
  12. package/dist/kupola-core-modal.esm.js +2 -2
  13. package/dist/kupola-core-numberinput.cjs.js +6 -5
  14. package/dist/kupola-core-numberinput.esm.js +6 -5
  15. package/dist/kupola-core-select.cjs.js +8 -7
  16. package/dist/kupola-core-select.esm.js +8 -7
  17. package/dist/kupola-core-table.cjs.js +1 -1
  18. package/dist/kupola-core-table.esm.js +1 -1
  19. package/dist/kupola-core-timepicker.cjs.js +3 -3
  20. package/dist/kupola-core-timepicker.esm.js +3 -3
  21. package/dist/kupola-core.cjs.js +1 -1
  22. package/dist/kupola-core.esm.js +1 -1
  23. package/package.json +1 -1
  24. package/packages/core/src/components/datepicker.js +11 -6
  25. package/packages/core/src/components/dialog.js +14 -7
  26. package/packages/core/src/components/drawer.js +5 -3
  27. package/packages/core/src/components/dropdown.js +15 -3
  28. package/packages/core/src/components/modal.js +20 -1
  29. package/packages/core/src/components/numberinput.js +12 -3
  30. package/packages/core/src/components/select.js +22 -5
  31. package/packages/core/src/components/table.js +3 -1
  32. package/packages/core/src/components/timepicker.js +2 -1
  33. package/packages/core/src/errors.js +80 -0
  34. package/packages/core/src/i18n.js +5 -0
  35. package/packages/core/src/index.js +3 -0
@@ -24,11 +24,13 @@
24
24
 
25
25
  import { html } from '../template.js';
26
26
  import { render } from '../render.js';
27
+ import { t } from '../i18n.js';
27
28
 
28
29
  /**
29
30
  * Create a Select component instance.
30
31
  *
31
32
  * @param {Object} [options]
33
+ * @param {string} [options.label] Label text (creates <label> linked to select)
32
34
  * @param {Array<{value:string, text:string}>} [options.items] Option list
33
35
  * @param {string} [options.placeholder] Placeholder text
34
36
  * @param {boolean} [options.searchable] Enable search filter
@@ -41,8 +43,9 @@ import { render } from '../render.js';
41
43
  */
42
44
  export function Select(options = {}) {
43
45
  const {
46
+ label = '',
44
47
  items = [],
45
- placeholder = 'Select...',
48
+ placeholder = null,
46
49
  searchable = false,
47
50
  clearable = false,
48
51
  multiple = false,
@@ -51,6 +54,10 @@ export function Select(options = {}) {
51
54
  onChange = null,
52
55
  } = options;
53
56
 
57
+ const _placeholder = placeholder || t('select.placeholder');
58
+ const _id = label ? `ds-select-${Math.random().toString(36).slice(2, 8)}` : '';
59
+ const _hasLabel = !!label;
60
+
54
61
  let _isOpen = false;
55
62
  let _focusIndex = -1;
56
63
  let _searchQuery = '';
@@ -158,7 +165,7 @@ export function Select(options = {}) {
158
165
  valueEl.textContent = `${_selectedValues.size} selected`;
159
166
  valueEl.classList.remove('ds-select__value--placeholder');
160
167
  } else {
161
- valueEl.textContent = placeholder;
168
+ valueEl.textContent = _placeholder;
162
169
  valueEl.classList.add('ds-select__value--placeholder');
163
170
  }
164
171
  }
@@ -230,6 +237,14 @@ export function Select(options = {}) {
230
237
  e.preventDefault();
231
238
  _setFocus(_focusIndex - 1);
232
239
  break;
240
+ case 'Home':
241
+ e.preventDefault();
242
+ _setFocus(0);
243
+ break;
244
+ case 'End':
245
+ e.preventDefault();
246
+ _setFocus(_filteredItems().length - 1);
247
+ break;
233
248
  case 'Enter':
234
249
  e.preventDefault();
235
250
  {
@@ -240,6 +255,7 @@ export function Select(options = {}) {
240
255
  }
241
256
  break;
242
257
  case 'Escape':
258
+ case 'Tab':
243
259
  e.preventDefault();
244
260
  close();
245
261
  break;
@@ -274,9 +290,10 @@ export function Select(options = {}) {
274
290
  // are serialized as static text by the render system.
275
291
 
276
292
  const tpl = html`
277
- <div class="ds-select">
278
- <div class="ds-select__trigger">
279
- <span class="ds-select__value${!displayText ? ' ds-select__value--placeholder' : ''}">${displayText || placeholder}</span>
293
+ ${_hasLabel ? html`<label class="ds-form-label" for="${_id}">${label}</label>` : ''}
294
+ <div class="ds-select" ${!label ? 'aria-label="Select"' : ''}>
295
+ <div class="ds-select__trigger" id="${_id}">
296
+ <span class="ds-select__value${!displayText ? ' ds-select__value--placeholder' : ''}">${displayText || _placeholder}</span>
280
297
  ${clearable ? html`<button class="ds-select__clear">&times;</button>` : ''}
281
298
  <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
282
299
  <polyline points="6 9 12 15 18 9"/>
@@ -8,6 +8,8 @@
8
8
  * @module Table
9
9
  */
10
10
 
11
+ import { t } from '../i18n.js';
12
+
11
13
  /**
12
14
  * @typedef {Object} TableColumn
13
15
  * @property {string} key - Column data key
@@ -382,7 +384,7 @@ export function Table(options = {}) {
382
384
  const td = document.createElement('td');
383
385
  td.colSpan = _getTotalColCount();
384
386
  td.className = 'kupola-table-empty';
385
- td.textContent = options.emptyText || 'No data';
387
+ td.textContent = options.emptyText || t('table.empty');
386
388
  tr.appendChild(td);
387
389
  tbody.appendChild(tr);
388
390
  } else {
@@ -20,6 +20,7 @@
20
20
 
21
21
  import { html } from '../template.js';
22
22
  import { render } from '../render.js';
23
+ import { t } from '../i18n.js';
23
24
 
24
25
  /**
25
26
  * Create a Timepicker component instance.
@@ -126,7 +127,7 @@ export function Timepicker(options = {}) {
126
127
  const tpl = html`
127
128
  <div class="ds-timepicker">
128
129
  <div class="ds-timepicker__input-wrap">
129
- <input class="ds-timepicker__input" type="text" readonly placeholder="Select time..." />
130
+ <input class="ds-timepicker__input" type="text" readonly placeholder="${t('timepicker.placeholder')}" />
130
131
  <span class="ds-timepicker__icon">
131
132
  <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg>
132
133
  </span>
@@ -0,0 +1,80 @@
1
+ // SPDX-License-Identifier: MIT
2
+ /**
3
+ * @kupola/core — Error boundary utility for graceful error handling.
4
+ *
5
+ * Wraps component rendering so that exceptions within a component
6
+ * do not crash the entire application. On error, a fallback element
7
+ * is rendered in place of the failed component.
8
+ *
9
+ * ```js
10
+ * import { ErrorBoundary } from '@kupola/core/errors';
11
+ *
12
+ * const view = ErrorBoundary(
13
+ * () => SomeRiskyComponent({ data: badData }),
14
+ * { fallback: (err) => html`<div class="ds-alert ds-alert--error">Error: ${err.message}</div>` }
15
+ * );
16
+ * container.appendChild(view.element);
17
+ * ```
18
+ *
19
+ * @module errors
20
+ */
21
+
22
+ import { html } from './template.js';
23
+ import { render } from './render.js';
24
+
25
+ /**
26
+ * Wrap a component factory with error handling.
27
+ *
28
+ * @param {Function} factory - Component factory function that returns { element, ... }
29
+ * @param {Object} [options]
30
+ * @param {Function} [options.fallback] - Fallback factory: (error) => TemplateResult|string
31
+ * @param {Function} [options.onError] - Error callback: (error) => void
32
+ * @returns {{ element: DocumentFragment|HTMLElement, error: Error|null }}
33
+ */
34
+ export function ErrorBoundary(factory, options = {}) {
35
+ const {
36
+ fallback = null,
37
+ onError = null,
38
+ } = options;
39
+
40
+ try {
41
+ const result = factory();
42
+ return result;
43
+ } catch (err) {
44
+ if (onError) {
45
+ try { onError(err); } catch (_e) { /* swallow callback errors */ }
46
+ }
47
+
48
+ // Log to console for debugging
49
+ console.error('[Kupola ErrorBoundary]', err);
50
+
51
+ // Create fallback element
52
+ const container = document.createDocumentFragment();
53
+ if (fallback) {
54
+ try {
55
+ const fallbackContent = typeof fallback === 'function' ? fallback(err) : fallback;
56
+ if (typeof fallbackContent === 'string') {
57
+ const el = document.createElement('div');
58
+ el.className = 'ds-error-boundary';
59
+ el.textContent = fallbackContent;
60
+ container.appendChild(el);
61
+ } else {
62
+ render(fallbackContent, container);
63
+ }
64
+ } catch (_fallbackErr) {
65
+ // If fallback also fails, show a minimal error
66
+ const el = document.createElement('div');
67
+ el.className = 'ds-error-boundary';
68
+ el.textContent = `Component error: ${err.message}`;
69
+ container.appendChild(el);
70
+ }
71
+ } else {
72
+ const el = document.createElement('div');
73
+ el.className = 'ds-error-boundary';
74
+ el.textContent = `Component error: ${err.message}`;
75
+ container.appendChild(el);
76
+ }
77
+
78
+ return { element: container, error: err };
79
+ }
80
+ }
@@ -19,6 +19,9 @@ const _messages = {
19
19
  'fileupload.drag': 'Drag files here or',
20
20
  'fileupload.browse': 'Browse',
21
21
  'empty.text': 'No data',
22
+ 'datepicker.months': 'Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec',
23
+ 'datepicker.weekdays': 'Mo,Tu,We,Th,Fr,Sa,Su',
24
+ 'timepicker.placeholder': 'Select time',
22
25
  },
23
26
  'zh-CN': {
24
27
  'modal.close': '关闭',
@@ -29,6 +32,8 @@ const _messages = {
29
32
  'pagination.of': '/',
30
33
  'select.placeholder': '请选择',
31
34
  'datepicker.placeholder': '选择日期',
35
+ 'datepicker.months': '1月,2月,3月,4月,5月,6月,7月,8月,9月,10月,11月,12月',
36
+ 'datepicker.weekdays': '一,二,三,四,五,六,日',
32
37
  'timepicker.placeholder': '选择时间',
33
38
  'fileupload.drag': '拖拽文件到此处或',
34
39
  'fileupload.browse': '浏览',
@@ -40,3 +40,6 @@ export { flushJobs, resetScheduler } from './scheduler.js';
40
40
 
41
41
  // ── i18n ─────────────────────────────────────────────────────────────────────
42
42
  export { setLocale, getLocale, t, addMessages } from './i18n.js';
43
+
44
+ // ── Error Boundary ───────────────────────────────────────────────────────────
45
+ export { ErrorBoundary } from './errors.js';