@visitwonders/assembly 0.16.1 → 0.18.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 (80) hide show
  1. package/declarations/action/button-group.d.ts +5 -8
  2. package/declarations/action/button-group.d.ts.map +1 -1
  3. package/declarations/data/pagination.d.ts +3 -5
  4. package/declarations/data/pagination.d.ts.map +1 -1
  5. package/declarations/data/table.d.ts +12 -11
  6. package/declarations/data/table.d.ts.map +1 -1
  7. package/declarations/form/calendar.d.ts +19 -12
  8. package/declarations/form/calendar.d.ts.map +1 -1
  9. package/declarations/form/checkbox-group.d.ts +2 -8
  10. package/declarations/form/checkbox-group.d.ts.map +1 -1
  11. package/declarations/form/checkbox.d.ts +4 -8
  12. package/declarations/form/checkbox.d.ts.map +1 -1
  13. package/declarations/form/combobox-field.d.ts +1 -1
  14. package/declarations/form/combobox-field.d.ts.map +1 -1
  15. package/declarations/form/combobox.d.ts +1 -1
  16. package/declarations/form/combobox.d.ts.map +1 -1
  17. package/declarations/form/date-picker-field.d.ts +4 -3
  18. package/declarations/form/date-picker-field.d.ts.map +1 -1
  19. package/declarations/form/date-picker.d.ts +11 -8
  20. package/declarations/form/date-picker.d.ts.map +1 -1
  21. package/declarations/form/date-range-picker-field.d.ts +4 -3
  22. package/declarations/form/date-range-picker-field.d.ts.map +1 -1
  23. package/declarations/form/date-range-picker.d.ts +6 -9
  24. package/declarations/form/date-range-picker.d.ts.map +1 -1
  25. package/declarations/form/input.d.ts +1 -4
  26. package/declarations/form/input.d.ts.map +1 -1
  27. package/declarations/form/money-field.d.ts +13 -3
  28. package/declarations/form/money-field.d.ts.map +1 -1
  29. package/declarations/form/multi-combobox-field.d.ts +1 -1
  30. package/declarations/form/multi-combobox-field.d.ts.map +1 -1
  31. package/declarations/form/multi-combobox.d.ts +1 -1
  32. package/declarations/form/multi-combobox.d.ts.map +1 -1
  33. package/declarations/form/radio.d.ts +3 -5
  34. package/declarations/form/radio.d.ts.map +1 -1
  35. package/declarations/form/search-input.d.ts +2 -1
  36. package/declarations/form/search-input.d.ts.map +1 -1
  37. package/declarations/form/text-field.d.ts +1 -7
  38. package/declarations/form/text-field.d.ts.map +1 -1
  39. package/declarations/form/textarea.d.ts +1 -7
  40. package/declarations/form/textarea.d.ts.map +1 -1
  41. package/declarations/form/toggle-field.d.ts +2 -4
  42. package/declarations/form/toggle-field.d.ts.map +1 -1
  43. package/declarations/form/toggle.d.ts +3 -9
  44. package/declarations/form/toggle.d.ts.map +1 -1
  45. package/declarations/overlay/drawer.d.ts +10 -9
  46. package/declarations/overlay/drawer.d.ts.map +1 -1
  47. package/declarations/overlay/modal.d.ts +10 -10
  48. package/declarations/overlay/modal.d.ts.map +1 -1
  49. package/declarations/overlay/popover.d.ts +9 -8
  50. package/declarations/overlay/popover.d.ts.map +1 -1
  51. package/declarations/overlay/tooltip.d.ts +9 -8
  52. package/declarations/overlay/tooltip.d.ts.map +1 -1
  53. package/dist/action/button-group.js +2 -25
  54. package/dist/data/pagination.js +10 -24
  55. package/dist/data/table.css +51 -3
  56. package/dist/data/table.js +14 -71
  57. package/dist/form/calendar.js +29 -57
  58. package/dist/form/checkbox-group.js +3 -23
  59. package/dist/form/checkbox.js +3 -17
  60. package/dist/form/combobox-field.js.map +1 -1
  61. package/dist/form/date-picker-field.js +1 -1
  62. package/dist/form/date-picker-field.js.map +1 -1
  63. package/dist/form/date-picker.js +10 -28
  64. package/dist/form/date-range-picker-field.js +1 -1
  65. package/dist/form/date-range-picker-field.js.map +1 -1
  66. package/dist/form/date-range-picker.js +8 -31
  67. package/dist/form/input.js +1 -6
  68. package/dist/form/money-field.js +23 -22
  69. package/dist/form/multi-combobox-field.js.map +1 -1
  70. package/dist/form/radio.js +3 -4
  71. package/dist/form/search-input.js +6 -9
  72. package/dist/form/text-field.js +3 -22
  73. package/dist/form/textarea.js +2 -17
  74. package/dist/form/toggle-field.js +1 -1
  75. package/dist/form/toggle.js +4 -18
  76. package/dist/overlay/drawer.js +6 -26
  77. package/dist/overlay/modal.js +5 -29
  78. package/dist/overlay/popover.js +15 -16
  79. package/dist/overlay/tooltip.js +14 -16
  80. package/package.json +16 -17
@@ -1,38 +1,21 @@
1
1
  import "./button-group.css"
2
2
  import Component from '@glimmer/component';
3
- import { tracked } from '@glimmer/tracking';
4
3
  import { action } from '@ember/object';
5
4
  import { on } from '@ember/modifier';
6
5
  import { fn, hash } from '@ember/helper';
7
6
  import Button from './button.js';
8
7
  import { precompileTemplate } from '@ember/template-compilation';
9
8
  import { setComponentTemplate } from '@ember/component';
10
- import { g, i, n } from 'decorator-transforms/runtime';
9
+ import { n } from 'decorator-transforms/runtime';
11
10
 
12
11
  ;
13
12
 
14
13
  class ButtonGroup extends Component {
15
- static {
16
- g(this.prototype, "internalValue", [tracked]);
17
- }
18
- #internalValue = (i(this, "internalValue"), void 0);
19
- constructor(owner, args) {
20
- super(owner, args);
21
- const defaultVal = args.defaultValue;
22
- if (this.selectionType === 'multiple') {
23
- this.internalValue = Array.isArray(defaultVal) ? [...defaultVal] : defaultVal ? [defaultVal] : [];
24
- } else {
25
- this.internalValue = Array.isArray(defaultVal) ? defaultVal[0] ?? '' : defaultVal ?? '';
26
- }
27
- }
28
14
  get selectionType() {
29
15
  return this.args.type ?? 'single';
30
16
  }
31
- get isControlled() {
32
- return this.args.value !== undefined;
33
- }
34
17
  get selectedValue() {
35
- return this.isControlled ? this.args.value ?? '' : this.internalValue;
18
+ return this.args.value;
36
19
  }
37
20
  get variant() {
38
21
  return this.args.variant ?? 'outline';
@@ -74,15 +57,9 @@ class ButtonGroup extends Component {
74
57
  } else {
75
58
  newValues = [...currentArray, value];
76
59
  }
77
- if (!this.isControlled) {
78
- this.internalValue = newValues;
79
- }
80
60
  this.args.onChange?.(newValues);
81
61
  } else {
82
62
  if (this.selectedValue === value) return;
83
- if (!this.isControlled) {
84
- this.internalValue = value;
85
- }
86
63
  this.args.onChange?.(value);
87
64
  }
88
65
  }
@@ -20,13 +20,6 @@ import { g, i } from 'decorator-transforms/runtime';
20
20
  // Component
21
21
  // ============================================================================
22
22
  class Pagination extends Component {
23
- static {
24
- g(this.prototype, "internalCurrentPage", [tracked], function () {
25
- return 1;
26
- });
27
- }
28
- #internalCurrentPage = (i(this, "internalCurrentPage"), void 0); // Uncontrolled-mode state. When the consumer passes `@currentPage`, this
29
- // value is ignored; otherwise it tracks page locally.
30
23
  // Tracked snapshot of the last seen totalPages so the refresh-clamp modifier
31
24
  // can detect a shrink-under-the-user without scheduling its own runloop.
32
25
  previousTotalPages = null;
@@ -86,15 +79,10 @@ class Pagination extends Component {
86
79
  return this.args.showJumpTo ?? false;
87
80
  }
88
81
  // ============================================================================
89
- // Mode inference (mirrors Table's isControlledSelection / isControlledSort).
90
- // Note: unlike Table, @onChange fires in BOTH modes — Pagination models a
91
- // navigation event. See spec §Behaviour > Callback contract.
82
+ // Current page (the consumer owns it)
92
83
  // ============================================================================
93
- get isControlled() {
94
- return this.args.currentPage !== undefined;
95
- }
96
84
  get currentPage() {
97
- return this.args.currentPage ?? this.internalCurrentPage;
85
+ return this.args.currentPage;
98
86
  }
99
87
  // ============================================================================
100
88
  // Derived counts
@@ -182,14 +170,11 @@ class Pagination extends Component {
182
170
  // ============================================================================
183
171
  // Navigation
184
172
  // ============================================================================
185
- // Single update path. Fires @onChange in both modes the callback models a
186
- // navigation event regardless of whether the parent stores currentPage.
187
- // Spec §Behaviour > Callback contract.
173
+ // Single navigation path. Clamps to [1, totalPages] and fires @onChange with
174
+ // the result; the consumer advances @currentPage. The component never writes
175
+ // its own page. Spec §Behaviour > Callback contract.
188
176
  goToPage(page) {
189
177
  const clamped = Math.max(1, Math.min(this.totalPages, page));
190
- if (!this.isControlled) {
191
- this.internalCurrentPage = clamped;
192
- }
193
178
  this.args.onChange(clamped);
194
179
  }
195
180
  handlePageClick = page => {
@@ -253,10 +238,11 @@ class Pagination extends Component {
253
238
  // assumed to be valid.
254
239
  if (previous === null) return;
255
240
  // Only clamp on shrink-past-current. Growth never re-fires.
256
- // The mutation is deferred to a microtask: the modifier reads
257
- // `currentPage` (which in uncontrolled mode is `internalCurrentPage`)
258
- // and `goToPage` writes it. Glimmer rejects same-computation read+write
259
- // on a tracked field, so we punt the write to the next microtask.
241
+ // @onChange is deferred to a microtask: the modifier reads `currentPage`
242
+ // (i.e. @currentPage), and firing @onChange drives the consumer to update
243
+ // that same arg. Glimmer rejects mutating a tracked value read in the
244
+ // same computation, so we punt the fire to the next microtask. Tests
245
+ // flush it with `await settled()`.
260
246
  if (totalPages < previous && currentPage > totalPages) {
261
247
  void Promise.resolve().then(() => this.goToPage(totalPages));
262
248
  }
@@ -9,6 +9,19 @@
9
9
  font-size: var(--font-size-md);
10
10
  color: var(--color-text);
11
11
  background-color: var(--color-bg-surface);
12
+
13
+ /* Edge inset: how far the first/last column sit from the table's edges.
14
+ Driven by @paddingInline; defaults to today's 16px. Only the outer side
15
+ of the edge columns reads it, so inter-column gutters never change. */
16
+ --_table-edge-inset: var(--spacing-inset-md);
17
+ }
18
+
19
+ .table_e4aed93fe[data-padding-inline="sm"] {
20
+ --_table-edge-inset: var(--spacing-inset-sm);
21
+ }
22
+
23
+ .table_e4aed93fe[data-padding-inline="lg"] {
24
+ --_table-edge-inset: var(--spacing-inset-lg);
12
25
  }
13
26
 
14
27
  .table_e4aed93fe:focus {
@@ -53,7 +66,7 @@
53
66
  }
54
67
 
55
68
  .header-cell_e4aed93fe {
56
- padding: var(--spacing-3) var(--spacing-4);
69
+ padding: var(--spacing-inset-sm) var(--spacing-inset-md);
57
70
  font-weight: var(--font-weight-medium);
58
71
  color: var(--color-text-secondary);
59
72
  text-align: left;
@@ -98,7 +111,7 @@
98
111
  justify-content: space-between;
99
112
  gap: var(--spacing-1);
100
113
  width: 100%;
101
- padding: var(--spacing-3) var(--spacing-4);
114
+ padding: var(--spacing-inset-sm) var(--spacing-inset-md);
102
115
  border: none;
103
116
  background: none;
104
117
  font: inherit;
@@ -194,7 +207,7 @@
194
207
  ============================================================================ */
195
208
 
196
209
  .cell_e4aed93fe {
197
- padding: var(--spacing-3) var(--spacing-4);
210
+ padding: var(--spacing-inset-sm) var(--spacing-inset-md);
198
211
  vertical-align: middle;
199
212
  }
200
213
 
@@ -215,6 +228,41 @@
215
228
  padding-right: 0;
216
229
  }
217
230
 
231
+ /* ============================================================================
232
+ Edge Inset (@paddingInline)
233
+ ============================================================================
234
+ The inset lands on the outer side of the first and last columns only, so
235
+ inter-column gutters and row-background bleed stay put. It composes with
236
+ @isCompact: density owns the inner padding, this owns the outer edge.
237
+
238
+ A sortable header cell is zeroed and its padding lives on the sort button.
239
+ So on a sortable edge column the inset lands on the button, not the zeroed
240
+ cell. Otherwise its label would not line up with the body cells below. */
241
+
242
+ .table_e4aed93fe .header-row_e4aed93fe > .header-cell_e4aed93fe:first-child:not([data-sortable="true"]),
243
+ .table_e4aed93fe .row_e4aed93fe > .cell_e4aed93fe:first-child {
244
+ padding-inline-start: var(--_table-edge-inset);
245
+ }
246
+
247
+ .table_e4aed93fe .header-row_e4aed93fe > .header-cell_e4aed93fe:last-child:not([data-sortable="true"]),
248
+ .table_e4aed93fe .row_e4aed93fe > .cell_e4aed93fe:last-child {
249
+ padding-inline-end: var(--_table-edge-inset);
250
+ }
251
+
252
+ .table_e4aed93fe
253
+ .header-row_e4aed93fe
254
+ > .header-cell_e4aed93fe:first-child[data-sortable="true"]
255
+ .sort-button_e4aed93fe {
256
+ padding-inline-start: var(--_table-edge-inset);
257
+ }
258
+
259
+ .table_e4aed93fe
260
+ .header-row_e4aed93fe
261
+ > .header-cell_e4aed93fe:last-child[data-sortable="true"]
262
+ .sort-button_e4aed93fe {
263
+ padding-inline-end: var(--_table-edge-inset);
264
+ }
265
+
218
266
  /* ============================================================================
219
267
  Bordered Variant
220
268
  ============================================================================ */
@@ -54,18 +54,6 @@ class Table extends Component {
54
54
  });
55
55
  }
56
56
  #focusedRowIndex = (i(this, "focusedRowIndex"), void 0);
57
- static {
58
- g(this.prototype, "internalSortState", [tracked], function () {
59
- return null;
60
- });
61
- }
62
- #internalSortState = (i(this, "internalSortState"), void 0);
63
- static {
64
- g(this.prototype, "internalSelectedIds", [tracked], function () {
65
- return [];
66
- });
67
- }
68
- #internalSelectedIds = (i(this, "internalSelectedIds"), void 0);
69
57
  static {
70
58
  g(this.prototype, "lastSelectedIndex", [tracked], function () {
71
59
  return null;
@@ -92,60 +80,23 @@ class Table extends Component {
92
80
  get visibleColumns() {
93
81
  return this.args.columns.filter(col => !col.hidden);
94
82
  }
95
- get isControlledSelection() {
96
- return this.args.selectedIds !== undefined;
83
+ // Edge inset on the spacing-inset scale. Always resolves to a value so the
84
+ // attribute is present on every render; `md` is today's default.
85
+ get paddingInline() {
86
+ return this.args.paddingInline ?? 'md';
97
87
  }
98
88
  get selectedIds() {
99
- return this.isControlledSelection ? this.args.selectedIds ?? [] : this.internalSelectedIds;
100
- }
101
- get isControlledSort() {
102
- return this.args.sortBy !== undefined || this.args.onSortChange !== undefined;
89
+ return this.args.selectedIds ?? [];
103
90
  }
104
91
  get sortState() {
105
- return this.isControlledSort ? this.args.sortBy ?? null : this.internalSortState;
106
- }
107
- get sortedData() {
108
- const data = this.args.data ?? [];
109
- const sort = this.sortState;
110
- if (!sort) return data;
111
- const column = this.args.columns.find(col => col.id === sort.columnId);
112
- if (!column) return data;
113
- const sorted = [...data];
114
- sorted.sort((a, b) => {
115
- // Use custom sort function if provided
116
- if (column.sortFn) {
117
- const result = column.sortFn(a, b);
118
- return sort.direction === 'desc' ? -result : result;
119
- }
120
- // Default sort using accessor
121
- const aVal = this.getCellValue(a, column);
122
- const bVal = this.getCellValue(b, column);
123
- let result = 0;
124
- if (aVal === bVal) {
125
- result = 0;
126
- } else if (aVal === null || aVal === undefined) {
127
- result = 1;
128
- } else if (bVal === null || bVal === undefined) {
129
- result = -1;
130
- } else if (typeof aVal === 'string' && typeof bVal === 'string') {
131
- result = aVal.localeCompare(bVal);
132
- } else if (typeof aVal === 'number' && typeof bVal === 'number') {
133
- result = aVal - bVal;
134
- } else {
135
- // Handle other types by converting to string safely
136
- const aStr = stringifyValue(aVal);
137
- const bStr = stringifyValue(bVal);
138
- result = aStr.localeCompare(bStr);
139
- }
140
- return sort.direction === 'desc' ? -result : result;
141
- });
142
- return sorted;
92
+ return this.args.sortBy ?? null;
143
93
  }
94
+ // The table renders @data in the order it is given and never sorts it. The
95
+ // consumer owns the sort: @sortBy drives the header indicator and
96
+ // @onSortChange reports a click; the consumer reorders the data.
97
+ // See docs/decisions/0003-sorting-model.md.
144
98
  get displayData() {
145
- if (this.args.isLoading) {
146
- return [];
147
- }
148
- return this.sortedData;
99
+ return this.args.isLoading ? [] : this.args.data ?? [];
149
100
  }
150
101
  get isEmpty() {
151
102
  return !this.args.isLoading && this.displayData.length === 0;
@@ -229,18 +180,10 @@ class Table extends Component {
229
180
  return 'none';
230
181
  };
231
182
  updateSelection(newIds) {
232
- if (this.isControlledSelection) {
233
- this.args.onSelectionChange?.(newIds);
234
- } else {
235
- this.internalSelectedIds = newIds;
236
- }
183
+ this.args.onSelectionChange?.(newIds);
237
184
  }
238
185
  updateSort(newSort) {
239
- if (this.isControlledSort) {
240
- this.args.onSortChange?.(newSort);
241
- } else {
242
- this.internalSortState = newSort;
243
- }
186
+ this.args.onSortChange?.(newSort);
244
187
  }
245
188
  moveFocus(direction) {
246
189
  const rows = this.displayData;
@@ -440,7 +383,7 @@ class Table extends Component {
440
383
  // Template
441
384
  // ============================================================================
442
385
  static {
443
- setComponentTemplate(precompileTemplate("<table class=\"table_e4aed93fe\" data-sticky-header={{if @hasStickyHeader \"true\"}} data-striped={{if @isStriped \"true\"}} data-bordered={{if @isBordered \"true\"}} data-compact={{if @isCompact \"true\"}} data-loading={{if @isLoading \"true\"}} data-test-table tabindex=\"0\" {{this.setupRef}} {{!-- template-lint-disable no-pointer-down-event-binding --}} {{on \"mousedown\" this.handleMouseDown}} {{on \"keydown\" this.handleKeyDown}} {{on \"focus\" this.handleFocus}} {{on \"blur\" this.handleBlur}} ...attributes>\n {{#if @caption}}\n <caption class=\"caption_e4aed93fe\" data-test-table-caption>\n {{@caption}}\n </caption>\n {{/if}}\n\n <thead class=\"thead_e4aed93fe\" data-test-table-header>\n <tr class=\"header-row_e4aed93fe\">\n {{#if @isSelectable}}\n <th class=\"header-cell_e4aed93fe checkbox-cell_e4aed93fe\" data-test-table-header-checkbox>\n <Checkbox @isChecked={{this.allRowsSelected}} @isIndeterminate={{this.someRowsSelected}} @isLabelHidden={{true}} @label=\"Select all rows\" @size=\"sm\" data-table-checkbox {{on \"change\" this.handleSelectAll}} />\n </th>\n {{/if}}\n\n {{#each this.visibleColumns as |column|}}\n <th class=\"header-cell_e4aed93fe\" data-align={{column.align}} data-sortable={{if (this.isColumnSortable column) \"true\"}} data-sorted={{if (this.getColumnSortDirection column) \"true\"}} data-sort-direction={{this.getColumnSortDirection column}} aria-sort={{this.getAriaSortValue column}} style={{if column.width (concat \"width:\" column.width \";\")}} data-test-table-header-cell data-test-column-id={{column.id}}>\n {{#if (this.isColumnSortable column)}}\n <button type=\"button\" class=\"sort-button_e4aed93fe\" {{on \"click\" (fn this.handleSort column)}} data-test-table-sort-button>\n <span class=\"header-text_e4aed93fe\">{{column.header}}</span>\n <span class=\"sort-icon_e4aed93fe\" aria-hidden=\"true\">\n {{#if (this.getColumnSortDirection column)}}\n {{#if (eq (this.getColumnSortDirection column) \"asc\")}}\n <svg viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"icon_e4aed93fe\">\n <path d=\"M4 10l4-4 4 4\" />\n </svg>\n {{else}}\n <svg viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"icon_e4aed93fe\">\n <path d=\"M4 6l4 4 4-4\" />\n </svg>\n {{/if}}\n {{else}}\n <svg viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"icon_e4aed93fe icon-unsorted_e4aed93fe\">\n <path d=\"M4 6l4-3 4 3M4 10l4 3 4-3\" />\n </svg>\n {{/if}}\n </span>\n </button>\n {{else}}\n <span class=\"header-text_e4aed93fe\">{{column.header}}</span>\n {{/if}}\n </th>\n {{/each}}\n </tr>\n </thead>\n\n <tbody class=\"tbody_e4aed93fe\" data-test-table-body>\n {{#if @isLoading}}\n {{#if (has-block \"loading\")}}\n {{yield to=\"loading\"}}\n {{else}}\n {{!-- template-lint-disable no-unused-block-params --}}\n {{#each (repeat this.loadingRowCount)}}\n <tr class=\"row_e4aed93fe skeleton-row_e4aed93fe\" data-test-table-skeleton-row>\n {{#if @isSelectable}}\n <td class=\"cell_e4aed93fe checkbox-cell_e4aed93fe\">\n <Skeleton @width={{16}} @height={{16}} @radius=\"sm\" />\n </td>\n {{/if}}\n {{#each this.visibleColumns as |column|}}\n <td class=\"cell_e4aed93fe\" data-align={{column.align}}>\n <Skeleton @width=\"70%\" @height={{16}} />\n </td>\n {{/each}}\n </tr>\n {{/each}}\n {{/if}}\n {{else if this.isEmpty}}\n {{#if (has-block \"empty\")}}\n <tr class=\"empty-row_e4aed93fe\">\n <td colspan={{if @isSelectable (add (len this.visibleColumns) 1) (len this.visibleColumns)}}>\n {{yield to=\"empty\"}}\n </td>\n </tr>\n {{else}}\n <tr class=\"empty-row_e4aed93fe\" data-test-table-empty>\n <td class=\"empty-cell_e4aed93fe\" colspan={{if @isSelectable (add (len this.visibleColumns) 1) (len this.visibleColumns)}}>\n {{#if @emptyMessage}}\n {{@emptyMessage}}\n {{else}}\n No data available\n {{/if}}\n </td>\n </tr>\n {{/if}}\n {{else}}\n {{#each this.displayData as |row rowIndex|}}\n {{!-- template-lint-disable no-invalid-interactive --}}\n <tr id={{this.getRowElementId row}} class=\"row_e4aed93fe\" data-selected={{if (this.isRowSelected row) \"true\" \"false\"}} data-focused={{if (this.isRowFocused rowIndex) \"true\" \"false\"}} data-disabled={{if (@isRowDisabled row) \"true\" \"false\"}} data-clickable={{if @onRowClick \"true\"}} aria-selected={{if @isSelectable (if (this.isRowSelected row) \"true\" \"false\")}} data-test-table-row data-test-row-id={{this.getRowId row}} {{on \"click\" (fn this.handleRowClick row)}}>\n {{#if @isSelectable}}\n <td class=\"cell_e4aed93fe checkbox-cell_e4aed93fe\" data-test-table-row-checkbox>\n <Checkbox @isChecked={{this.isRowSelected row}} @isDisabled={{@isRowDisabled row}} @isLabelHidden={{true}} @label=\"Select row\" @size=\"sm\" data-table-checkbox {{on \"click\" (fn this.handleRowSelect row)}} />\n </td>\n {{/if}}\n\n {{#each this.visibleColumns as |column|}}\n <td class=\"cell_e4aed93fe\" data-align={{column.align}} data-test-table-cell data-test-column-id={{column.id}}>\n {{#if (has-block \"cell\")}}\n {{yield (this.getCellContext row column rowIndex) to=\"cell\"}}\n {{else}}\n {{this.formatCellValue row column}}\n {{/if}}\n </td>\n {{/each}}\n </tr>\n {{/each}}\n {{/if}}\n </tbody>\n</table>", {
386
+ setComponentTemplate(precompileTemplate("<table class=\"table_e4aed93fe\" data-sticky-header={{if @hasStickyHeader \"true\"}} data-striped={{if @isStriped \"true\"}} data-bordered={{if @isBordered \"true\"}} data-compact={{if @isCompact \"true\"}} data-padding-inline={{this.paddingInline}} data-loading={{if @isLoading \"true\"}} data-test-table tabindex=\"0\" {{this.setupRef}} {{!-- template-lint-disable no-pointer-down-event-binding --}} {{on \"mousedown\" this.handleMouseDown}} {{on \"keydown\" this.handleKeyDown}} {{on \"focus\" this.handleFocus}} {{on \"blur\" this.handleBlur}} ...attributes>\n {{#if @caption}}\n <caption class=\"caption_e4aed93fe\" data-test-table-caption>\n {{@caption}}\n </caption>\n {{/if}}\n\n <thead class=\"thead_e4aed93fe\" data-test-table-header>\n <tr class=\"header-row_e4aed93fe\">\n {{#if @isSelectable}}\n <th class=\"header-cell_e4aed93fe checkbox-cell_e4aed93fe\" data-test-table-header-checkbox>\n <Checkbox @isChecked={{this.allRowsSelected}} @isIndeterminate={{this.someRowsSelected}} @isLabelHidden={{true}} @label=\"Select all rows\" @size=\"sm\" data-table-checkbox {{on \"change\" this.handleSelectAll}} />\n </th>\n {{/if}}\n\n {{#each this.visibleColumns as |column|}}\n <th class=\"header-cell_e4aed93fe\" data-align={{column.align}} data-sortable={{if (this.isColumnSortable column) \"true\"}} data-sorted={{if (this.getColumnSortDirection column) \"true\"}} data-sort-direction={{this.getColumnSortDirection column}} aria-sort={{this.getAriaSortValue column}} style={{if column.width (concat \"width:\" column.width \";\")}} data-test-table-header-cell data-test-column-id={{column.id}}>\n {{#if (this.isColumnSortable column)}}\n <button type=\"button\" class=\"sort-button_e4aed93fe\" {{on \"click\" (fn this.handleSort column)}} data-test-table-sort-button>\n <span class=\"header-text_e4aed93fe\">{{column.header}}</span>\n <span class=\"sort-icon_e4aed93fe\" aria-hidden=\"true\">\n {{#if (this.getColumnSortDirection column)}}\n {{#if (eq (this.getColumnSortDirection column) \"asc\")}}\n <svg viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"icon_e4aed93fe\">\n <path d=\"M4 10l4-4 4 4\" />\n </svg>\n {{else}}\n <svg viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"icon_e4aed93fe\">\n <path d=\"M4 6l4 4 4-4\" />\n </svg>\n {{/if}}\n {{else}}\n <svg viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"icon_e4aed93fe icon-unsorted_e4aed93fe\">\n <path d=\"M4 6l4-3 4 3M4 10l4 3 4-3\" />\n </svg>\n {{/if}}\n </span>\n </button>\n {{else}}\n <span class=\"header-text_e4aed93fe\">{{column.header}}</span>\n {{/if}}\n </th>\n {{/each}}\n </tr>\n </thead>\n\n <tbody class=\"tbody_e4aed93fe\" data-test-table-body>\n {{#if @isLoading}}\n {{#if (has-block \"loading\")}}\n {{yield to=\"loading\"}}\n {{else}}\n {{!-- template-lint-disable no-unused-block-params --}}\n {{#each (repeat this.loadingRowCount)}}\n <tr class=\"row_e4aed93fe skeleton-row_e4aed93fe\" data-test-table-skeleton-row>\n {{#if @isSelectable}}\n <td class=\"cell_e4aed93fe checkbox-cell_e4aed93fe\">\n <Skeleton @width={{16}} @height={{16}} @radius=\"sm\" />\n </td>\n {{/if}}\n {{#each this.visibleColumns as |column|}}\n <td class=\"cell_e4aed93fe\" data-align={{column.align}}>\n <Skeleton @width=\"70%\" @height={{16}} />\n </td>\n {{/each}}\n </tr>\n {{/each}}\n {{/if}}\n {{else if this.isEmpty}}\n {{#if (has-block \"empty\")}}\n <tr class=\"empty-row_e4aed93fe\">\n <td colspan={{if @isSelectable (add (len this.visibleColumns) 1) (len this.visibleColumns)}}>\n {{yield to=\"empty\"}}\n </td>\n </tr>\n {{else}}\n <tr class=\"empty-row_e4aed93fe\" data-test-table-empty>\n <td class=\"empty-cell_e4aed93fe\" colspan={{if @isSelectable (add (len this.visibleColumns) 1) (len this.visibleColumns)}}>\n {{#if @emptyMessage}}\n {{@emptyMessage}}\n {{else}}\n No data available\n {{/if}}\n </td>\n </tr>\n {{/if}}\n {{else}}\n {{#each this.displayData as |row rowIndex|}}\n {{!-- template-lint-disable no-invalid-interactive --}}\n <tr id={{this.getRowElementId row}} class=\"row_e4aed93fe\" data-selected={{if (this.isRowSelected row) \"true\" \"false\"}} data-focused={{if (this.isRowFocused rowIndex) \"true\" \"false\"}} data-disabled={{if (@isRowDisabled row) \"true\" \"false\"}} data-clickable={{if @onRowClick \"true\"}} aria-selected={{if @isSelectable (if (this.isRowSelected row) \"true\" \"false\")}} data-test-table-row data-test-row-id={{this.getRowId row}} {{on \"click\" (fn this.handleRowClick row)}}>\n {{#if @isSelectable}}\n <td class=\"cell_e4aed93fe checkbox-cell_e4aed93fe\" data-test-table-row-checkbox>\n <Checkbox @isChecked={{this.isRowSelected row}} @isDisabled={{@isRowDisabled row}} @isLabelHidden={{true}} @label=\"Select row\" @size=\"sm\" data-table-checkbox {{on \"click\" (fn this.handleRowSelect row)}} />\n </td>\n {{/if}}\n\n {{#each this.visibleColumns as |column|}}\n <td class=\"cell_e4aed93fe\" data-align={{column.align}} data-test-table-cell data-test-column-id={{column.id}}>\n {{#if (has-block \"cell\")}}\n {{yield (this.getCellContext row column rowIndex) to=\"cell\"}}\n {{else}}\n {{this.formatCellValue row column}}\n {{/if}}\n </td>\n {{/each}}\n </tr>\n {{/each}}\n {{/if}}\n </tbody>\n</table>", {
444
387
  strictMode: true,
445
388
  scope: () => ({
446
389
  on,
@@ -18,35 +18,26 @@ import { g, i } from 'decorator-transforms/runtime';
18
18
  // ============================================================================
19
19
  class Calendar extends Component {
20
20
  static {
21
- g(this.prototype, "internalMonth", [tracked], function () {
21
+ g(this.prototype, "_navigationMonth", [tracked], function () {
22
22
  return new Date();
23
23
  });
24
24
  }
25
- #internalMonth = (i(this, "internalMonth"), void 0);
25
+ #_navigationMonth = (i(this, "_navigationMonth"), void 0);
26
+ /**
27
+ * The month the calendar is currently showing. This is the calendar's own
28
+ * navigation mechanics, not the consumer's domain state: the user pages
29
+ * through months to find a date. It seeds to the current month and the prev
30
+ * and next buttons (and keyboard month paging) drive it. When the consumer
31
+ * passes `@month`, that override wins and this field is bypassed.
32
+ */
26
33
  static {
27
34
  g(this.prototype, "focusedDate", [tracked], function () {
28
35
  return null;
29
36
  });
30
37
  }
31
- #focusedDate = (i(this, "focusedDate"), void 0);
32
- static {
33
- g(this.prototype, "internalValue", [tracked], function () {
34
- return null;
35
- });
36
- }
37
- #internalValue = (i(this, "internalValue"), void 0);
38
- static {
39
- g(this.prototype, "internalMultiValue", [tracked], function () {
40
- return [];
41
- });
42
- }
43
- #internalMultiValue = (i(this, "internalMultiValue"), void 0);
44
- static {
45
- g(this.prototype, "internalRangeValue", [tracked], function () {
46
- return null;
47
- });
48
- }
49
- #internalRangeValue = (i(this, "internalRangeValue"), void 0);
38
+ #focusedDate = (i(this, "focusedDate"), void 0); // Ephemeral UI mechanics the calendar owns: which day has keyboard focus,
39
+ // which day is hovered during a range drag, and the first-click anchor of an
40
+ // in-progress range before the second click lands.
50
41
  static {
51
42
  g(this.prototype, "rangeSelectionStart", [tracked], function () {
52
43
  return null;
@@ -78,46 +69,32 @@ class Calendar extends Component {
78
69
  get showToday() {
79
70
  return this.args.showToday ?? true;
80
71
  }
81
- /** Current displayed month (controlled or uncontrolled) */
72
+ /**
73
+ * The month on screen. The `@month` override wins when present; otherwise the
74
+ * calendar's own navigation state drives. Writing it (via the prev and next
75
+ * buttons or keyboard month paging) updates the navigation state and reports
76
+ * the new month up. The override is left untouched — when it is in play the
77
+ * consumer updates it in response to `onMonthChange`, exactly as it owns the
78
+ * selection.
79
+ */
82
80
  get currentMonth() {
83
- if (this.args.month !== undefined) {
84
- return this.args.month;
85
- }
86
- if (this.args.defaultMonth !== undefined && !this.hasInternalMonthChanged) {
87
- return this.args.defaultMonth;
88
- }
89
- return this.internalMonth;
81
+ return this.args.month ?? this._navigationMonth;
90
82
  }
91
- hasInternalMonthChanged = false;
92
- hasInternalValueChanged = false;
93
83
  set currentMonth(value) {
94
- this.hasInternalMonthChanged = true;
95
- this.internalMonth = value;
84
+ this._navigationMonth = value;
96
85
  this.args.onMonthChange?.(value);
97
86
  }
98
- /** Selected value for single mode (controlled or uncontrolled) */
87
+ /** Selected value for single mode, owned by the consumer via `@value`. */
99
88
  get selectedValue() {
100
- if (this.args.value !== undefined) {
101
- return this.args.value;
102
- }
103
- if (this.args.defaultValue !== undefined && !this.hasInternalValueChanged) {
104
- return this.args.defaultValue;
105
- }
106
- return this.internalValue;
89
+ return this.args.value ?? null;
107
90
  }
108
- /** Selected values for multiple mode */
91
+ /** Selected values for multiple mode, owned by the consumer via `@multiValue`. */
109
92
  get selectedMultiValue() {
110
- if (this.args.multiValue !== undefined) {
111
- return this.args.multiValue;
112
- }
113
- return this.internalMultiValue;
93
+ return this.args.multiValue ?? [];
114
94
  }
115
- /** Selected range for range mode */
95
+ /** Selected range for range mode, owned by the consumer via `@rangeValue`. */
116
96
  get selectedRange() {
117
- if (this.args.rangeValue !== undefined) {
118
- return this.args.rangeValue;
119
- }
120
- return this.internalRangeValue;
97
+ return this.args.rangeValue ?? null;
121
98
  }
122
99
  /** Weekday labels starting from weekStartsOn */
123
100
  get weekdayLabels() {
@@ -278,8 +255,6 @@ class Calendar extends Component {
278
255
  selectDate = date => {
279
256
  if (this.isDateDisabled(date)) return;
280
257
  if (this.mode === 'single') {
281
- this.hasInternalValueChanged = true;
282
- this.internalValue = date;
283
258
  this.args.onChange?.(date);
284
259
  } else if (this.mode === 'multiple') {
285
260
  const current = [...this.selectedMultiValue];
@@ -291,13 +266,11 @@ class Calendar extends Component {
291
266
  } else {
292
267
  current.push(date);
293
268
  }
294
- this.internalMultiValue = current;
295
269
  this.args.onMultiChange?.(current);
296
270
  } else if (this.mode === 'range') {
297
271
  if (!this.rangeSelectionStart) {
298
- // First click: set range start and clear any existing range
272
+ // First click: set range start (ephemeral anchor) and clear the range.
299
273
  this.rangeSelectionStart = date;
300
- this.internalRangeValue = null;
301
274
  this.args.onRangeChange?.(null);
302
275
  } else {
303
276
  // Second click: complete range
@@ -312,7 +285,6 @@ class Calendar extends Component {
312
285
  };
313
286
  this.rangeSelectionStart = null;
314
287
  this.hoveredDate = null;
315
- this.internalRangeValue = range;
316
288
  this.args.onRangeChange?.(range);
317
289
  }
318
290
  }
@@ -1,6 +1,5 @@
1
1
  import "./checkbox-group.css"
2
2
  import Component from '@glimmer/component';
3
- import { tracked } from '@glimmer/tracking';
4
3
  import { action } from '@ember/object';
5
4
  import { on } from '@ember/modifier';
6
5
  import { hash } from '@ember/helper';
@@ -9,25 +8,14 @@ import Checkbox from './checkbox.js';
9
8
  import ErrorMessage from './error-message.js';
10
9
  import { precompileTemplate } from '@ember/template-compilation';
11
10
  import { setComponentTemplate } from '@ember/component';
12
- import { g, i, n } from 'decorator-transforms/runtime';
11
+ import { n } from 'decorator-transforms/runtime';
13
12
 
14
13
  ;
15
14
 
16
15
  class CheckboxGroup extends Component {
17
- static {
18
- g(this.prototype, "internalValues", [tracked]);
19
- }
20
- #internalValues = (i(this, "internalValues"), void 0);
21
- constructor(owner, args) {
22
- super(owner, args);
23
- this.internalValues = [...(args.defaultValue ?? [])];
24
- }
25
16
  guid = guidFor(this);
26
- get isControlled() {
27
- return this.args.value !== undefined;
28
- }
29
17
  get selectedValues() {
30
- return this.isControlled ? this.args.value ?? [] : this.internalValues;
18
+ return this.args.value;
31
19
  }
32
20
  get orientation() {
33
21
  return this.args.orientation ?? 'vertical';
@@ -48,15 +36,7 @@ class CheckboxGroup extends Component {
48
36
  return this.selectedValues.includes(value);
49
37
  };
50
38
  handleCheckboxChange(value, isChecked) {
51
- let newValues;
52
- if (isChecked) {
53
- newValues = [...this.selectedValues, value];
54
- } else {
55
- newValues = this.selectedValues.filter(v => v !== value);
56
- }
57
- if (!this.isControlled) {
58
- this.internalValues = newValues;
59
- }
39
+ const newValues = isChecked ? [...this.selectedValues, value] : this.selectedValues.filter(v => v !== value);
60
40
  this.args.onChange?.(newValues);
61
41
  }
62
42
  static {
@@ -1,32 +1,20 @@
1
1
  import "./checkbox.css"
2
2
  import Component from '@glimmer/component';
3
- import { tracked } from '@glimmer/tracking';
4
3
  import { action } from '@ember/object';
5
4
  import { on } from '@ember/modifier';
6
5
  import { modifier } from 'ember-modifier';
7
6
  import { precompileTemplate } from '@ember/template-compilation';
8
7
  import { setComponentTemplate } from '@ember/component';
9
- import { g, i, n } from 'decorator-transforms/runtime';
8
+ import { n } from 'decorator-transforms/runtime';
10
9
 
11
10
  ;
12
11
 
13
12
  class Checkbox extends Component {
14
- static {
15
- g(this.prototype, "internalChecked", [tracked]);
16
- }
17
- #internalChecked = (i(this, "internalChecked"), void 0);
18
- constructor(owner, args) {
19
- super(owner, args);
20
- this.internalChecked = args.defaultChecked ?? false;
21
- }
22
13
  get size() {
23
14
  return this.args.size ?? 'md';
24
15
  }
25
- get isControlled() {
26
- return this.args.isChecked !== undefined;
27
- }
28
16
  get checked() {
29
- return this.isControlled ? this.args.isChecked ?? false : this.internalChecked;
17
+ return this.args.isChecked;
30
18
  }
31
19
  get ariaChecked() {
32
20
  if (this.args.isIndeterminate) {
@@ -49,9 +37,7 @@ class Checkbox extends Component {
49
37
  });
50
38
  handleChange(event) {
51
39
  const target = event.target;
52
- if (!this.isControlled) {
53
- this.internalChecked = target.checked;
54
- }
40
+ this.args.onChange?.(target.checked, event);
55
41
  }
56
42
  static {
57
43
  n(this.prototype, "handleChange", [action]);
@@ -1 +1 @@
1
- {"version":3,"file":"combobox-field.js","sources":["../../src/form/combobox-field.gts"],"sourcesContent":["import Component from '@glimmer/component';\n\nimport Control from './control.gts';\nimport ComboBox from './combobox.gts';\nimport type { ComboBoxItems, ComboBoxOption } from './combobox-shared.ts';\n\nexport type LabelVisibility = 'visible' | 'hidden';\n\nexport interface ComboBoxFieldSignature {\n Element: HTMLDivElement;\n Args: {\n /** Label text (required). */\n label: string;\n\n /** Available options. Flat or pre-grouped. */\n items: ComboBoxItems;\n\n /** Current selected value (controlled). */\n value?: string | null;\n\n /** Fires when the selection changes. */\n onChange?: (value: string | null, option: ComboBoxOption | null) => void;\n\n /** Async search (passed through to ComboBox). */\n onSearch?: (query: string) => Promise<ComboBoxItems>;\n\n /** Debounce for `onSearch` in ms. */\n searchDebounceMs?: number;\n\n /** Force the loading state. */\n isLoading?: boolean;\n\n /** Called when an `onSearch` promise rejects. */\n onSearchError?: (error: unknown) => void;\n\n /** Loading row text. */\n loadingText?: string;\n\n /** Input placeholder. */\n placeholder?: string;\n\n /** Empty state text. */\n noResultsText?: string;\n\n /** Allow creating new options from a non-matching query. */\n isCreatable?: boolean;\n\n /** Fires when the user activates the create row. */\n onCreate?: (query: string) => void;\n\n /** Build the create-row label. */\n createLabel?: (query: string) => string;\n\n /** Help text displayed below the control. */\n helpText?: string;\n\n /** Error message (also sets invalid state). */\n error?: string;\n\n /** Field is required. */\n isRequired?: boolean;\n\n /** Field is disabled. */\n isDisabled?: boolean;\n\n /** Show the clear button when a value is set. */\n isClearable?: boolean;\n\n /** Info tooltip text shown next to the label. */\n labelInfo?: string;\n\n /** Optional indicator - shows \"optional\" or custom text next to the label. */\n optionalIndicator?: boolean | string;\n\n /** Label visibility. */\n labelVisibility?: LabelVisibility;\n\n /** Input name for forms. */\n name?: string;\n\n /** Blur event handler. */\n onBlur?: (event: FocusEvent) => void;\n\n /** Focus event handler. */\n onFocus?: (event: FocusEvent) => void;\n\n /** Fires when the dropdown opens. */\n onOpen?: () => void;\n\n /** Fires when the dropdown closes. */\n onClose?: () => void;\n };\n Blocks: {\n /** Rich tooltip content shown next to the label. */\n info: [];\n };\n}\n\nexport default class ComboBoxField extends Component<ComboBoxFieldSignature> {\n get isInvalid(): boolean {\n return !!this.args.error;\n }\n\n get isLabelHidden(): boolean {\n return this.args.labelVisibility === 'hidden';\n }\n\n /** Build aria-describedby from the control's help text and error IDs. */\n getAriaDescribedBy = (controlId: string): string | undefined => {\n const parts: string[] = [];\n if (this.args.helpText) {\n parts.push(`${controlId}-help-text`);\n }\n if (this.args.error) {\n parts.push(`${controlId}-error-message`);\n }\n return parts.length > 0 ? parts.join(' ') : undefined;\n };\n\n <template>\n <Control\n @isInvalid={{this.isInvalid}}\n @isDisabled={{@isDisabled}}\n @isRequired={{@isRequired}}\n @labelInfo={{@labelInfo}}\n @optionalIndicator={{@optionalIndicator}}\n ...attributes\n as |ctrl|\n >\n {{#if (has-block \"info\")}}\n <ctrl.Label @isVisuallyHidden={{this.isLabelHidden}}>\n <:default>{{@label}}</:default>\n <:info>{{yield to=\"info\"}}</:info>\n </ctrl.Label>\n {{else}}\n <ctrl.Label @isVisuallyHidden={{this.isLabelHidden}}>\n {{@label}}\n </ctrl.Label>\n {{/if}}\n\n <ComboBox\n @id={{ctrl.id}}\n @name={{@name}}\n @items={{@items}}\n @value={{@value}}\n @placeholder={{@placeholder}}\n @noResultsText={{@noResultsText}}\n @onSearch={{@onSearch}}\n @searchDebounceMs={{@searchDebounceMs}}\n @isLoading={{@isLoading}}\n @onSearchError={{@onSearchError}}\n @loadingText={{@loadingText}}\n @isCreatable={{@isCreatable}}\n @onCreate={{@onCreate}}\n @createLabel={{@createLabel}}\n @isDisabled={{@isDisabled}}\n @isInvalid={{this.isInvalid}}\n @isRequired={{@isRequired}}\n @isClearable={{@isClearable}}\n @aria-describedby={{this.getAriaDescribedBy ctrl.id}}\n @onChange={{@onChange}}\n @onBlur={{@onBlur}}\n @onFocus={{@onFocus}}\n @onOpen={{@onOpen}}\n @onClose={{@onClose}}\n data-test-combobox-field\n />\n\n {{#if @helpText}}\n <ctrl.HelpText>{{@helpText}}</ctrl.HelpText>\n {{/if}}\n\n {{#if @error}}\n <ctrl.ErrorMessage>{{@error}}</ctrl.ErrorMessage>\n {{/if}}\n </Control>\n </template>\n}\n"],"names":["ComboBoxField","Component","isInvalid","args","error","isLabelHidden","labelVisibility","getAriaDescribedBy","controlId","parts","helpText","push","length","join","undefined","setComponentTemplate","precompileTemplate","strictMode","scope","Control","ComboBox"],"mappings":";;;;;;AAkGe,MAAMA,sBAAsBC,SAAA,CAAU;EACnD,IAAIC,SAAAA,GAAqB;AACvB,IAAA,OAAO,CAAC,CAAC,IAAI,CAACC,IAAI,CAACC,KAAK;AAC1B,EAAA;EAEA,IAAIC,aAAAA,GAAyB;AAC3B,IAAA,OAAO,IAAI,CAACF,IAAI,CAACG,eAAe,KAAK,QAAA;AACvC,EAAA;AAEA;EACAC,qBAAsBC,SAAiB,IAAqB;IAC1D,MAAMC,KAAa,GAAK,EAAE;AAC1B,IAAA,IAAI,IAAI,CAACN,IAAI,CAACO,QAAQ,EAAE;AACtBD,MAAAA,KAAA,CAAME,IAAI,CAAC,CAAA,EAAGH,SAAA,YAAqB,CAAA;AACrC,IAAA;AACA,IAAA,IAAI,IAAI,CAACL,IAAI,CAACC,KAAK,EAAE;AACnBK,MAAAA,KAAA,CAAME,IAAI,CAAC,CAAA,EAAGH,SAAA,gBAAyB,CAAA;AACzC,IAAA;AACA,IAAA,OAAOC,MAAMG,MAAM,GAAG,IAAIH,KAAA,CAAMI,IAAI,CAAC,GAAA,CAAA,GAAOC,SAAA;EAC9C,CAAA;AAEA,EAAA;IAAAC,oBAAA,CAAAC,kBAAA,CAAA,81CAAA,EAyDA;MAAAC,UAAA,EAAA,IAAA;AAAAC,MAAAA,KAAA,EAAAA,OAAA;QAAAC,OAAA;AAAAC,QAAAA;AAAA,OAAA;KAAU,CAAA,EAAV,IAAW,CAAA;AAAD;AACZ;;;;"}
1
+ {"version":3,"file":"combobox-field.js","sources":["../../src/form/combobox-field.gts"],"sourcesContent":["import Component from '@glimmer/component';\n\nimport Control from './control.gts';\nimport ComboBox from './combobox.gts';\nimport type { ComboBoxItems, ComboBoxOption } from './combobox-shared.ts';\n\nexport type LabelVisibility = 'visible' | 'hidden';\n\nexport interface ComboBoxFieldSignature {\n Element: HTMLDivElement;\n Args: {\n /** Label text (required). */\n label: string;\n\n /** Available options. Flat or pre-grouped. */\n items: ComboBoxItems;\n\n /** The selected value. The consumer owns it; pair with `onChange`. */\n value?: string | null;\n\n /** Fires when the selection changes. */\n onChange?: (value: string | null, option: ComboBoxOption | null) => void;\n\n /** Async search (passed through to ComboBox). */\n onSearch?: (query: string) => Promise<ComboBoxItems>;\n\n /** Debounce for `onSearch` in ms. */\n searchDebounceMs?: number;\n\n /** Force the loading state. */\n isLoading?: boolean;\n\n /** Called when an `onSearch` promise rejects. */\n onSearchError?: (error: unknown) => void;\n\n /** Loading row text. */\n loadingText?: string;\n\n /** Input placeholder. */\n placeholder?: string;\n\n /** Empty state text. */\n noResultsText?: string;\n\n /** Allow creating new options from a non-matching query. */\n isCreatable?: boolean;\n\n /** Fires when the user activates the create row. */\n onCreate?: (query: string) => void;\n\n /** Build the create-row label. */\n createLabel?: (query: string) => string;\n\n /** Help text displayed below the control. */\n helpText?: string;\n\n /** Error message (also sets invalid state). */\n error?: string;\n\n /** Field is required. */\n isRequired?: boolean;\n\n /** Field is disabled. */\n isDisabled?: boolean;\n\n /** Show the clear button when a value is set. */\n isClearable?: boolean;\n\n /** Info tooltip text shown next to the label. */\n labelInfo?: string;\n\n /** Optional indicator - shows \"optional\" or custom text next to the label. */\n optionalIndicator?: boolean | string;\n\n /** Label visibility. */\n labelVisibility?: LabelVisibility;\n\n /** Input name for forms. */\n name?: string;\n\n /** Blur event handler. */\n onBlur?: (event: FocusEvent) => void;\n\n /** Focus event handler. */\n onFocus?: (event: FocusEvent) => void;\n\n /** Fires when the dropdown opens. */\n onOpen?: () => void;\n\n /** Fires when the dropdown closes. */\n onClose?: () => void;\n };\n Blocks: {\n /** Rich tooltip content shown next to the label. */\n info: [];\n };\n}\n\nexport default class ComboBoxField extends Component<ComboBoxFieldSignature> {\n get isInvalid(): boolean {\n return !!this.args.error;\n }\n\n get isLabelHidden(): boolean {\n return this.args.labelVisibility === 'hidden';\n }\n\n /** Build aria-describedby from the control's help text and error IDs. */\n getAriaDescribedBy = (controlId: string): string | undefined => {\n const parts: string[] = [];\n if (this.args.helpText) {\n parts.push(`${controlId}-help-text`);\n }\n if (this.args.error) {\n parts.push(`${controlId}-error-message`);\n }\n return parts.length > 0 ? parts.join(' ') : undefined;\n };\n\n <template>\n <Control\n @isInvalid={{this.isInvalid}}\n @isDisabled={{@isDisabled}}\n @isRequired={{@isRequired}}\n @labelInfo={{@labelInfo}}\n @optionalIndicator={{@optionalIndicator}}\n ...attributes\n as |ctrl|\n >\n {{#if (has-block \"info\")}}\n <ctrl.Label @isVisuallyHidden={{this.isLabelHidden}}>\n <:default>{{@label}}</:default>\n <:info>{{yield to=\"info\"}}</:info>\n </ctrl.Label>\n {{else}}\n <ctrl.Label @isVisuallyHidden={{this.isLabelHidden}}>\n {{@label}}\n </ctrl.Label>\n {{/if}}\n\n <ComboBox\n @id={{ctrl.id}}\n @name={{@name}}\n @items={{@items}}\n @value={{@value}}\n @placeholder={{@placeholder}}\n @noResultsText={{@noResultsText}}\n @onSearch={{@onSearch}}\n @searchDebounceMs={{@searchDebounceMs}}\n @isLoading={{@isLoading}}\n @onSearchError={{@onSearchError}}\n @loadingText={{@loadingText}}\n @isCreatable={{@isCreatable}}\n @onCreate={{@onCreate}}\n @createLabel={{@createLabel}}\n @isDisabled={{@isDisabled}}\n @isInvalid={{this.isInvalid}}\n @isRequired={{@isRequired}}\n @isClearable={{@isClearable}}\n @aria-describedby={{this.getAriaDescribedBy ctrl.id}}\n @onChange={{@onChange}}\n @onBlur={{@onBlur}}\n @onFocus={{@onFocus}}\n @onOpen={{@onOpen}}\n @onClose={{@onClose}}\n data-test-combobox-field\n />\n\n {{#if @helpText}}\n <ctrl.HelpText>{{@helpText}}</ctrl.HelpText>\n {{/if}}\n\n {{#if @error}}\n <ctrl.ErrorMessage>{{@error}}</ctrl.ErrorMessage>\n {{/if}}\n </Control>\n </template>\n}\n"],"names":["ComboBoxField","Component","isInvalid","args","error","isLabelHidden","labelVisibility","getAriaDescribedBy","controlId","parts","helpText","push","length","join","undefined","setComponentTemplate","precompileTemplate","strictMode","scope","Control","ComboBox"],"mappings":";;;;;;AAkGe,MAAMA,sBAAsBC,SAAA,CAAU;EACnD,IAAIC,SAAAA,GAAqB;AACvB,IAAA,OAAO,CAAC,CAAC,IAAI,CAACC,IAAI,CAACC,KAAK;AAC1B,EAAA;EAEA,IAAIC,aAAAA,GAAyB;AAC3B,IAAA,OAAO,IAAI,CAACF,IAAI,CAACG,eAAe,KAAK,QAAA;AACvC,EAAA;AAEA;EACAC,qBAAsBC,SAAiB,IAAqB;IAC1D,MAAMC,KAAa,GAAK,EAAE;AAC1B,IAAA,IAAI,IAAI,CAACN,IAAI,CAACO,QAAQ,EAAE;AACtBD,MAAAA,KAAA,CAAME,IAAI,CAAC,CAAA,EAAGH,SAAA,YAAqB,CAAA;AACrC,IAAA;AACA,IAAA,IAAI,IAAI,CAACL,IAAI,CAACC,KAAK,EAAE;AACnBK,MAAAA,KAAA,CAAME,IAAI,CAAC,CAAA,EAAGH,SAAA,gBAAyB,CAAA;AACzC,IAAA;AACA,IAAA,OAAOC,MAAMG,MAAM,GAAG,IAAIH,KAAA,CAAMI,IAAI,CAAC,GAAA,CAAA,GAAOC,SAAA;EAC9C,CAAA;AAEA,EAAA;IAAAC,oBAAA,CAAAC,kBAAA,CAAA,81CAAA,EAyDA;MAAAC,UAAA,EAAA,IAAA;AAAAC,MAAAA,KAAA,EAAAA,OAAA;QAAAC,OAAA;AAAAC,QAAAA;AAAA,OAAA;KAAU,CAAA,EAAV,IAAW,CAAA;AAAD;AACZ;;;;"}
@@ -23,7 +23,7 @@ class DatePickerField extends Component {
23
23
  return parts.length > 0 ? parts.join(' ') : undefined;
24
24
  };
25
25
  static {
26
- setComponentTemplate(precompileTemplate("<Control @isInvalid={{this.isInvalid}} @isDisabled={{@isDisabled}} @isRequired={{@isRequired}} @isReadOnly={{@isReadOnly}} @labelInfo={{@labelInfo}} @optionalIndicator={{@optionalIndicator}} ...attributes as |ctrl|>\n {{#if (has-block \"info\")}}\n <ctrl.Label @isVisuallyHidden={{this.isLabelHidden}}>\n <:default>{{@label}}</:default>\n <:info>{{yield to=\"info\"}}</:info>\n </ctrl.Label>\n {{else}}\n <ctrl.Label @isVisuallyHidden={{this.isLabelHidden}}>\n {{@label}}\n </ctrl.Label>\n {{/if}}\n\n <DatePicker @id={{ctrl.id}} @name={{@name}} @value={{@value}} @defaultValue={{@defaultValue}} @placeholder={{@placeholder}} @format={{@format}} @minDate={{@minDate}} @maxDate={{@maxDate}} @disabledDates={{@disabledDates}} @disabledDaysOfWeek={{@disabledDaysOfWeek}} @weekStartsOn={{@weekStartsOn}} @showOutsideDays={{@showOutsideDays}} @showToday={{@showToday}} @allowTextInput={{@allowTextInput}} @closeOnSelect={{@closeOnSelect}} @showIcon={{@showIcon}} @clearable={{@clearable}} @isDisabled={{@isDisabled}} @isInvalid={{this.isInvalid}} @isRequired={{@isRequired}} @isReadOnly={{@isReadOnly}} @aria-describedby={{this.getAriaDescribedBy ctrl.id}} @onChange={{@onChange}} @onBlur={{@onBlur}} @onFocus={{@onFocus}} @onOpenChange={{@onOpenChange}} data-test-date-picker-field />\n\n {{#if @helpText}}\n <ctrl.HelpText>{{@helpText}}</ctrl.HelpText>\n {{/if}}\n\n {{#if @error}}\n <ctrl.ErrorMessage>{{@error}}</ctrl.ErrorMessage>\n {{/if}}\n</Control>", {
26
+ setComponentTemplate(precompileTemplate("<Control @isInvalid={{this.isInvalid}} @isDisabled={{@isDisabled}} @isRequired={{@isRequired}} @isReadOnly={{@isReadOnly}} @labelInfo={{@labelInfo}} @optionalIndicator={{@optionalIndicator}} ...attributes as |ctrl|>\n {{#if (has-block \"info\")}}\n <ctrl.Label @isVisuallyHidden={{this.isLabelHidden}}>\n <:default>{{@label}}</:default>\n <:info>{{yield to=\"info\"}}</:info>\n </ctrl.Label>\n {{else}}\n <ctrl.Label @isVisuallyHidden={{this.isLabelHidden}}>\n {{@label}}\n </ctrl.Label>\n {{/if}}\n\n <DatePicker @id={{ctrl.id}} @name={{@name}} @value={{@value}} @placeholder={{@placeholder}} @format={{@format}} @minDate={{@minDate}} @maxDate={{@maxDate}} @disabledDates={{@disabledDates}} @disabledDaysOfWeek={{@disabledDaysOfWeek}} @weekStartsOn={{@weekStartsOn}} @showOutsideDays={{@showOutsideDays}} @showToday={{@showToday}} @allowTextInput={{@allowTextInput}} @closeOnSelect={{@closeOnSelect}} @showIcon={{@showIcon}} @clearable={{@clearable}} @isDisabled={{@isDisabled}} @isInvalid={{this.isInvalid}} @isRequired={{@isRequired}} @isReadOnly={{@isReadOnly}} @aria-describedby={{this.getAriaDescribedBy ctrl.id}} @onChange={{@onChange}} @onBlur={{@onBlur}} @onFocus={{@onFocus}} @onOpenChange={{@onOpenChange}} data-test-date-picker-field />\n\n {{#if @helpText}}\n <ctrl.HelpText>{{@helpText}}</ctrl.HelpText>\n {{/if}}\n\n {{#if @error}}\n <ctrl.ErrorMessage>{{@error}}</ctrl.ErrorMessage>\n {{/if}}\n</Control>", {
27
27
  strictMode: true,
28
28
  scope: () => ({
29
29
  Control,