@ni/ok-components 1.5.2 → 1.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (28) hide show
  1. package/dist/all-components-bundle.js +904 -411
  2. package/dist/all-components-bundle.js.map +1 -1
  3. package/dist/all-components-bundle.min.js +5609 -5345
  4. package/dist/all-components-bundle.min.js.map +1 -1
  5. package/dist/custom-elements.json +393 -70
  6. package/dist/custom-elements.md +78 -16
  7. package/dist/esm/fv/all-fv.d.ts +2 -0
  8. package/dist/esm/fv/all-fv.js +2 -0
  9. package/dist/esm/fv/all-fv.js.map +1 -1
  10. package/dist/esm/fv/master-detail-list/index.d.ts +50 -0
  11. package/dist/esm/fv/master-detail-list/index.js +176 -0
  12. package/dist/esm/fv/master-detail-list/index.js.map +1 -0
  13. package/dist/esm/fv/master-detail-list/styles.d.ts +1 -0
  14. package/dist/esm/fv/master-detail-list/styles.js +68 -0
  15. package/dist/esm/fv/master-detail-list/styles.js.map +1 -0
  16. package/dist/esm/fv/master-detail-list/template.d.ts +2 -0
  17. package/dist/esm/fv/master-detail-list/template.js +36 -0
  18. package/dist/esm/fv/master-detail-list/template.js.map +1 -0
  19. package/dist/esm/fv/master-detail-list-item/index.d.ts +32 -0
  20. package/dist/esm/fv/master-detail-list-item/index.js +86 -0
  21. package/dist/esm/fv/master-detail-list-item/index.js.map +1 -0
  22. package/dist/esm/fv/master-detail-list-item/styles.d.ts +1 -0
  23. package/dist/esm/fv/master-detail-list-item/styles.js +100 -0
  24. package/dist/esm/fv/master-detail-list-item/styles.js.map +1 -0
  25. package/dist/esm/fv/master-detail-list-item/template.d.ts +2 -0
  26. package/dist/esm/fv/master-detail-list-item/template.js +28 -0
  27. package/dist/esm/fv/master-detail-list-item/template.js.map +1 -0
  28. package/package.json +3 -3
@@ -0,0 +1,176 @@
1
+ import { __decorate } from "tslib";
2
+ import { attr, observable } from '@ni/fast-element';
3
+ import { DesignSystem, FoundationElement } from '@ni/fast-foundation';
4
+ import { uniqueId } from '@ni/fast-web-utilities';
5
+ import { diacriticInsensitiveStringNormalizer } from '@ni/nimble-components/dist/esm/utilities/models/string-normalizers';
6
+ import { FvMasterDetailListItem, fvMasterDetailListItemTag } from '../master-detail-list-item';
7
+ import { styles } from './styles';
8
+ import { template } from './template';
9
+ /**
10
+ * A filterable master-detail list with keyboard selection.
11
+ */
12
+ export class FvMasterDetailList extends FoundationElement {
13
+ constructor() {
14
+ super(...arguments);
15
+ this.placeholder = 'Filter items...';
16
+ /** @internal */
17
+ this.filterText = '';
18
+ /** @internal */
19
+ this.activeItemId = null;
20
+ /** @internal */
21
+ this.items = [];
22
+ /** @internal */
23
+ this.inputId = uniqueId('ok-fv-master-detail-list-input');
24
+ /** @internal */
25
+ this.listboxId = uniqueId('ok-fv-master-detail-list-listbox');
26
+ this.filterInput = null;
27
+ this.selectedItem = null;
28
+ }
29
+ /** @internal */
30
+ connectedCallback() {
31
+ super.connectedCallback();
32
+ queueMicrotask(() => {
33
+ this.refreshItems();
34
+ });
35
+ }
36
+ /** @internal */
37
+ filterTextChanged() {
38
+ if (this.$fastController.isConnected) {
39
+ this.applyFilter(true);
40
+ }
41
+ }
42
+ /** @internal */
43
+ captureFilterInput(input) {
44
+ this.filterInput = input;
45
+ }
46
+ /** @internal */
47
+ get hasVisibleItems() {
48
+ return this.visibleItems.length > 0;
49
+ }
50
+ /** @internal */
51
+ handleFilterInput(event) {
52
+ const input = event.target;
53
+ this.filterText = input.value;
54
+ }
55
+ /** @internal */
56
+ handleFilterKeydown(event) {
57
+ if (event.key !== 'ArrowDown' && event.key !== 'ArrowUp') {
58
+ return true;
59
+ }
60
+ const visibleItems = this.visibleItems;
61
+ if (visibleItems.length === 0) {
62
+ event.preventDefault();
63
+ return false;
64
+ }
65
+ const currentIndex = visibleItems.findIndex(item => item.selected);
66
+ let startingIndex = currentIndex;
67
+ if (startingIndex < 0) {
68
+ startingIndex = event.key === 'ArrowDown' ? -1 : visibleItems.length;
69
+ }
70
+ const nextIndex = event.key === 'ArrowDown'
71
+ ? Math.min(startingIndex + 1, visibleItems.length - 1)
72
+ : Math.max(startingIndex - 1, 0);
73
+ this.setSelectedItem(visibleItems[nextIndex] ?? null, true);
74
+ visibleItems[nextIndex]?.scrollIntoView({ block: 'nearest' });
75
+ event.preventDefault();
76
+ return false;
77
+ }
78
+ /** @internal */
79
+ handleItemsSlotChange() {
80
+ this.refreshItems();
81
+ }
82
+ /** @internal */
83
+ handleItemsClick(event) {
84
+ const item = event.composedPath().find(pathItem => pathItem instanceof HTMLElement && pathItem.localName === fvMasterDetailListItemTag);
85
+ if (!item || item.hidden) {
86
+ return;
87
+ }
88
+ this.setSelectedItem(item, true);
89
+ this.filterInput?.focus();
90
+ }
91
+ get visibleItems() {
92
+ return this.items.filter(item => !item.hidden);
93
+ }
94
+ refreshItems() {
95
+ const slot = this.shadowRoot?.querySelector('slot');
96
+ const assignedItems = slot?.assignedElements({ flatten: true }).filter(element => element instanceof HTMLElement && element.localName === fvMasterDetailListItemTag);
97
+ this.items = assignedItems ?? [];
98
+ for (const item of this.items) {
99
+ if (item.id.length === 0) {
100
+ item.id = uniqueId('ok-fv-master-detail-list-item');
101
+ }
102
+ }
103
+ this.applyFilter(false);
104
+ }
105
+ applyFilter(emitSelectionChange) {
106
+ const normalizedFilter = diacriticInsensitiveStringNormalizer(this.filterText.trim());
107
+ let firstVisibleItem = null;
108
+ for (const item of this.items) {
109
+ const searchableText = diacriticInsensitiveStringNormalizer(`${item.titleText} ${item.subtitle} ${item.value}`.trim());
110
+ const matches = normalizedFilter.length === 0 || searchableText.includes(normalizedFilter);
111
+ item.hidden = !matches;
112
+ if (matches && firstVisibleItem === null) {
113
+ firstVisibleItem = item;
114
+ }
115
+ }
116
+ const visibleSelectedItems = this.visibleItems.filter(item => item.selected);
117
+ const preferredSelectedItem = visibleSelectedItems[0] ?? null;
118
+ for (const extraSelectedItem of visibleSelectedItems.slice(1)) {
119
+ extraSelectedItem.selected = false;
120
+ }
121
+ if (preferredSelectedItem) {
122
+ this.setSelectedItem(preferredSelectedItem, emitSelectionChange && preferredSelectedItem !== this.selectedItem);
123
+ return;
124
+ }
125
+ this.setSelectedItem(firstVisibleItem, emitSelectionChange && firstVisibleItem !== this.selectedItem);
126
+ }
127
+ setSelectedItem(nextItem, emitChange) {
128
+ if (this.selectedItem === nextItem) {
129
+ return;
130
+ }
131
+ if (this.selectedItem) {
132
+ this.selectedItem.selected = false;
133
+ }
134
+ this.selectedItem = nextItem;
135
+ if (this.selectedItem) {
136
+ this.selectedItem.selected = true;
137
+ this.activeItemId = this.selectedItem.id;
138
+ }
139
+ else {
140
+ this.activeItemId = null;
141
+ }
142
+ if (emitChange) {
143
+ this.dispatchEvent(new CustomEvent('change', {
144
+ bubbles: true,
145
+ composed: true,
146
+ detail: {
147
+ item: this.selectedItem,
148
+ value: this.selectedItem?.value || this.selectedItem?.titleText || null
149
+ }
150
+ }));
151
+ }
152
+ }
153
+ }
154
+ __decorate([
155
+ attr
156
+ ], FvMasterDetailList.prototype, "placeholder", void 0);
157
+ __decorate([
158
+ observable
159
+ ], FvMasterDetailList.prototype, "filterText", void 0);
160
+ __decorate([
161
+ observable
162
+ ], FvMasterDetailList.prototype, "activeItemId", void 0);
163
+ __decorate([
164
+ observable
165
+ ], FvMasterDetailList.prototype, "items", void 0);
166
+ const okFvMasterDetailList = FvMasterDetailList.compose({
167
+ baseName: 'fv-master-detail-list',
168
+ template,
169
+ styles,
170
+ shadowOptions: {
171
+ delegatesFocus: true
172
+ }
173
+ });
174
+ DesignSystem.getOrCreate().withPrefix('ok').register(okFvMasterDetailList());
175
+ export const fvMasterDetailListTag = 'ok-fv-master-detail-list';
176
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/fv/master-detail-list/index.ts"],"names":[],"mappings":";AAAA,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACtE,OAAO,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAClD,OAAO,EAAE,oCAAoC,EAAE,MAAM,oEAAoE,CAAC;AAC1H,OAAO,EAAE,sBAAsB,EAAE,yBAAyB,EAAE,MAAM,4BAA4B,CAAC;AAC/F,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAClC,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAatC;;GAEG;AACH,MAAM,OAAO,kBAAmB,SAAQ,iBAAiB;IAAzD;;QAEW,gBAAW,GAAG,iBAAiB,CAAC;QAEvC,gBAAgB;QAET,eAAU,GAAG,EAAE,CAAC;QAEvB,gBAAgB;QAET,iBAAY,GAAkB,IAAI,CAAC;QAE1C,gBAAgB;QAET,UAAK,GAA6B,EAAE,CAAC;QAE5C,gBAAgB;QACA,YAAO,GAAG,QAAQ,CAAC,gCAAgC,CAAC,CAAC;QAErE,gBAAgB;QACA,cAAS,GAAG,QAAQ,CAAC,kCAAkC,CAAC,CAAC;QAEjE,gBAAW,GAA4B,IAAI,CAAC;QAE5C,iBAAY,GAAkC,IAAI,CAAC;IAuK/D,CAAC;IArKG,gBAAgB;IACA,iBAAiB;QAC7B,KAAK,CAAC,iBAAiB,EAAE,CAAC;QAC1B,cAAc,CAAC,GAAG,EAAE;YAChB,IAAI,CAAC,YAAY,EAAE,CAAC;QACxB,CAAC,CAAC,CAAC;IACP,CAAC;IAED,gBAAgB;IACT,iBAAiB;QACpB,IAAI,IAAI,CAAC,eAAe,CAAC,WAAW,EAAE,CAAC;YACnC,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC;QAC3B,CAAC;IACL,CAAC;IAED,gBAAgB;IACT,kBAAkB,CAAC,KAA8B;QACpD,IAAI,CAAC,WAAW,GAAG,KAAK,CAAC;IAC7B,CAAC;IAED,gBAAgB;IAChB,IAAW,eAAe;QACtB,OAAO,IAAI,CAAC,YAAY,CAAC,MAAM,GAAG,CAAC,CAAC;IACxC,CAAC;IAED,gBAAgB;IACT,iBAAiB,CAAC,KAAY;QACjC,MAAM,KAAK,GAAG,KAAK,CAAC,MAA0B,CAAC;QAC/C,IAAI,CAAC,UAAU,GAAG,KAAK,CAAC,KAAK,CAAC;IAClC,CAAC;IAED,gBAAgB;IACT,mBAAmB,CAAC,KAAoB;QAC3C,IAAI,KAAK,CAAC,GAAG,KAAK,WAAW,IAAI,KAAK,CAAC,GAAG,KAAK,SAAS,EAAE,CAAC;YACvD,OAAO,IAAI,CAAC;QAChB,CAAC;QAED,MAAM,YAAY,GAAG,IAAI,CAAC,YAAY,CAAC;QACvC,IAAI,YAAY,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC5B,KAAK,CAAC,cAAc,EAAE,CAAC;YACvB,OAAO,KAAK,CAAC;QACjB,CAAC;QAED,MAAM,YAAY,GAAG,YAAY,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACnE,IAAI,aAAa,GAAG,YAAY,CAAC;QAEjC,IAAI,aAAa,GAAG,CAAC,EAAE,CAAC;YACpB,aAAa,GAAG,KAAK,CAAC,GAAG,KAAK,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,MAAM,CAAC;QACzE,CAAC;QAED,MAAM,SAAS,GAAG,KAAK,CAAC,GAAG,KAAK,WAAW;YACvC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,aAAa,GAAG,CAAC,EAAE,YAAY,CAAC,MAAM,GAAG,CAAC,CAAC;YACtD,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,aAAa,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;QAErC,IAAI,CAAC,eAAe,CAAC,YAAY,CAAC,SAAS,CAAC,IAAI,IAAI,EAAE,IAAI,CAAC,CAAC;QAC5D,YAAY,CAAC,SAAS,CAAC,EAAE,cAAc,CAAC,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC,CAAC;QAC9D,KAAK,CAAC,cAAc,EAAE,CAAC;QACvB,OAAO,KAAK,CAAC;IACjB,CAAC;IAED,gBAAgB;IACT,qBAAqB;QACxB,IAAI,CAAC,YAAY,EAAE,CAAC;IACxB,CAAC;IAED,gBAAgB;IACT,gBAAgB,CAAC,KAAY;QAChC,MAAM,IAAI,GAAG,KAAK,CAAC,YAAY,EAAE,CAAC,IAAI,CAClC,QAAQ,CAAC,EAAE,CAAC,QAAQ,YAAY,WAAW,IAAI,QAAQ,CAAC,SAAS,KAAK,yBAAyB,CAC5D,CAAC;QAExC,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;YACvB,OAAO;QACX,CAAC;QAED,IAAI,CAAC,eAAe,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;QACjC,IAAI,CAAC,WAAW,EAAE,KAAK,EAAE,CAAC;IAC9B,CAAC;IAED,IAAY,YAAY;QACpB,OAAO,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACnD,CAAC;IAEO,YAAY;QAChB,MAAM,IAAI,GAAG,IAAI,CAAC,UAAU,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;QACpD,MAAM,aAAa,GAAG,IAAI,EAAE,gBAAgB,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,MAAM,CAClE,OAAO,CAAC,EAAE,CAAC,OAAO,YAAY,WAAW,IAAI,OAAO,CAAC,SAAS,KAAK,yBAAyB,CACvD,CAAC;QAE1C,IAAI,CAAC,KAAK,GAAG,aAAa,IAAI,EAAE,CAAC;QACjC,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;YAC5B,IAAI,IAAI,CAAC,EAAE,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBACvB,IAAI,CAAC,EAAE,GAAG,QAAQ,CAAC,+BAA+B,CAAC,CAAC;YACxD,CAAC;QACL,CAAC;QAED,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;IAC5B,CAAC;IAEO,WAAW,CAAC,mBAA4B;QAC5C,MAAM,gBAAgB,GAAG,oCAAoC,CAAC,IAAI,CAAC,UAAU,CAAC,IAAI,EAAE,CAAC,CAAC;QACtF,IAAI,gBAAgB,GAAkC,IAAI,CAAC;QAE3D,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;YAC5B,MAAM,cAAc,GAAG,oCAAoC,CACvD,GAAG,IAAI,CAAC,SAAS,IAAI,IAAI,CAAC,QAAQ,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC,IAAI,EAAE,CAC5D,CAAC;YACF,MAAM,OAAO,GAAG,gBAAgB,CAAC,MAAM,KAAK,CAAC,IAAI,cAAc,CAAC,QAAQ,CAAC,gBAAgB,CAAC,CAAC;YAC3F,IAAI,CAAC,MAAM,GAAG,CAAC,OAAO,CAAC;YAEvB,IAAI,OAAO,IAAI,gBAAgB,KAAK,IAAI,EAAE,CAAC;gBACvC,gBAAgB,GAAG,IAAI,CAAC;YAC5B,CAAC;QACL,CAAC;QAED,MAAM,oBAAoB,GAAG,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QAC7E,MAAM,qBAAqB,GAAG,oBAAoB,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC;QAE9D,KAAK,MAAM,iBAAiB,IAAI,oBAAoB,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;YAC5D,iBAAiB,CAAC,QAAQ,GAAG,KAAK,CAAC;QACvC,CAAC;QAED,IAAI,qBAAqB,EAAE,CAAC;YACxB,IAAI,CAAC,eAAe,CAChB,qBAAqB,EACrB,mBAAmB,IAAI,qBAAqB,KAAK,IAAI,CAAC,YAAY,CACrE,CAAC;YACF,OAAO;QACX,CAAC;QAED,IAAI,CAAC,eAAe,CAChB,gBAAgB,EAChB,mBAAmB,IAAI,gBAAgB,KAAK,IAAI,CAAC,YAAY,CAChE,CAAC;IACN,CAAC;IAEO,eAAe,CAAC,QAAuC,EAAE,UAAmB;QAChF,IAAI,IAAI,CAAC,YAAY,KAAK,QAAQ,EAAE,CAAC;YACjC,OAAO;QACX,CAAC;QAED,IAAI,IAAI,CAAC,YAAY,EAAE,CAAC;YACpB,IAAI,CAAC,YAAY,CAAC,QAAQ,GAAG,KAAK,CAAC;QACvC,CAAC;QAED,IAAI,CAAC,YAAY,GAAG,QAAQ,CAAC;QAE7B,IAAI,IAAI,CAAC,YAAY,EAAE,CAAC;YACpB,IAAI,CAAC,YAAY,CAAC,QAAQ,GAAG,IAAI,CAAC;YAClC,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC,YAAY,CAAC,EAAE,CAAC;QAC7C,CAAC;aAAM,CAAC;YACJ,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC;QAC7B,CAAC;QAED,IAAI,UAAU,EAAE,CAAC;YACb,IAAI,CAAC,aAAa,CAAC,IAAI,WAAW,CAAiC,QAAQ,EAAE;gBACzE,OAAO,EAAE,IAAI;gBACb,QAAQ,EAAE,IAAI;gBACd,MAAM,EAAE;oBACJ,IAAI,EAAE,IAAI,CAAC,YAAY;oBACvB,KAAK,EAAE,IAAI,CAAC,YAAY,EAAE,KAAK,IAAI,IAAI,CAAC,YAAY,EAAE,SAAS,IAAI,IAAI;iBAC1E;aACJ,CAAC,CAAC,CAAC;QACR,CAAC;IACL,CAAC;CACJ;AA7LU;IADN,IAAI;uDACkC;AAIhC;IADN,UAAU;sDACY;AAIhB;IADN,UAAU;wDAC+B;AAInC;IADN,UAAU;iDACiC;AAmLhD,MAAM,oBAAoB,GAAG,kBAAkB,CAAC,OAAO,CAAC;IACpD,QAAQ,EAAE,uBAAuB;IACjC,QAAQ;IACR,MAAM;IACN,aAAa,EAAE;QACX,cAAc,EAAE,IAAI;KACvB;CACJ,CAAC,CAAC;AAEH,YAAY,CAAC,WAAW,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,oBAAoB,EAAE,CAAC,CAAC;AAC7E,MAAM,CAAC,MAAM,qBAAqB,GAAG,0BAA0B,CAAC","sourcesContent":["import { attr, observable } from '@ni/fast-element';\nimport { DesignSystem, FoundationElement } from '@ni/fast-foundation';\nimport { uniqueId } from '@ni/fast-web-utilities';\nimport { diacriticInsensitiveStringNormalizer } from '@ni/nimble-components/dist/esm/utilities/models/string-normalizers';\nimport { FvMasterDetailListItem, fvMasterDetailListItemTag } from '../master-detail-list-item';\nimport { styles } from './styles';\nimport { template } from './template';\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'ok-fv-master-detail-list': FvMasterDetailList;\n }\n}\n\nexport interface FvMasterDetailListChangeDetail {\n item: FvMasterDetailListItem | null;\n value: string | null;\n}\n\n/**\n * A filterable master-detail list with keyboard selection.\n */\nexport class FvMasterDetailList extends FoundationElement {\n @attr\n public placeholder = 'Filter items...';\n\n /** @internal */\n @observable\n public filterText = '';\n\n /** @internal */\n @observable\n public activeItemId: string | null = null;\n\n /** @internal */\n @observable\n public items: FvMasterDetailListItem[] = [];\n\n /** @internal */\n public readonly inputId = uniqueId('ok-fv-master-detail-list-input');\n\n /** @internal */\n public readonly listboxId = uniqueId('ok-fv-master-detail-list-listbox');\n\n private filterInput: HTMLInputElement | null = null;\n\n private selectedItem: FvMasterDetailListItem | null = null;\n\n /** @internal */\n public override connectedCallback(): void {\n super.connectedCallback();\n queueMicrotask(() => {\n this.refreshItems();\n });\n }\n\n /** @internal */\n public filterTextChanged(): void {\n if (this.$fastController.isConnected) {\n this.applyFilter(true);\n }\n }\n\n /** @internal */\n public captureFilterInput(input: HTMLInputElement | null): void {\n this.filterInput = input;\n }\n\n /** @internal */\n public get hasVisibleItems(): boolean {\n return this.visibleItems.length > 0;\n }\n\n /** @internal */\n public handleFilterInput(event: Event): void {\n const input = event.target as HTMLInputElement;\n this.filterText = input.value;\n }\n\n /** @internal */\n public handleFilterKeydown(event: KeyboardEvent): boolean {\n if (event.key !== 'ArrowDown' && event.key !== 'ArrowUp') {\n return true;\n }\n\n const visibleItems = this.visibleItems;\n if (visibleItems.length === 0) {\n event.preventDefault();\n return false;\n }\n\n const currentIndex = visibleItems.findIndex(item => item.selected);\n let startingIndex = currentIndex;\n\n if (startingIndex < 0) {\n startingIndex = event.key === 'ArrowDown' ? -1 : visibleItems.length;\n }\n\n const nextIndex = event.key === 'ArrowDown'\n ? Math.min(startingIndex + 1, visibleItems.length - 1)\n : Math.max(startingIndex - 1, 0);\n\n this.setSelectedItem(visibleItems[nextIndex] ?? null, true);\n visibleItems[nextIndex]?.scrollIntoView({ block: 'nearest' });\n event.preventDefault();\n return false;\n }\n\n /** @internal */\n public handleItemsSlotChange(): void {\n this.refreshItems();\n }\n\n /** @internal */\n public handleItemsClick(event: Event): void {\n const item = event.composedPath().find(\n pathItem => pathItem instanceof HTMLElement && pathItem.localName === fvMasterDetailListItemTag\n ) as FvMasterDetailListItem | undefined;\n\n if (!item || item.hidden) {\n return;\n }\n\n this.setSelectedItem(item, true);\n this.filterInput?.focus();\n }\n\n private get visibleItems(): FvMasterDetailListItem[] {\n return this.items.filter(item => !item.hidden);\n }\n\n private refreshItems(): void {\n const slot = this.shadowRoot?.querySelector('slot');\n const assignedItems = slot?.assignedElements({ flatten: true }).filter(\n element => element instanceof HTMLElement && element.localName === fvMasterDetailListItemTag\n ) as FvMasterDetailListItem[] | undefined;\n\n this.items = assignedItems ?? [];\n for (const item of this.items) {\n if (item.id.length === 0) {\n item.id = uniqueId('ok-fv-master-detail-list-item');\n }\n }\n\n this.applyFilter(false);\n }\n\n private applyFilter(emitSelectionChange: boolean): void {\n const normalizedFilter = diacriticInsensitiveStringNormalizer(this.filterText.trim());\n let firstVisibleItem: FvMasterDetailListItem | null = null;\n\n for (const item of this.items) {\n const searchableText = diacriticInsensitiveStringNormalizer(\n `${item.titleText} ${item.subtitle} ${item.value}`.trim()\n );\n const matches = normalizedFilter.length === 0 || searchableText.includes(normalizedFilter);\n item.hidden = !matches;\n\n if (matches && firstVisibleItem === null) {\n firstVisibleItem = item;\n }\n }\n\n const visibleSelectedItems = this.visibleItems.filter(item => item.selected);\n const preferredSelectedItem = visibleSelectedItems[0] ?? null;\n\n for (const extraSelectedItem of visibleSelectedItems.slice(1)) {\n extraSelectedItem.selected = false;\n }\n\n if (preferredSelectedItem) {\n this.setSelectedItem(\n preferredSelectedItem,\n emitSelectionChange && preferredSelectedItem !== this.selectedItem\n );\n return;\n }\n\n this.setSelectedItem(\n firstVisibleItem,\n emitSelectionChange && firstVisibleItem !== this.selectedItem\n );\n }\n\n private setSelectedItem(nextItem: FvMasterDetailListItem | null, emitChange: boolean): void {\n if (this.selectedItem === nextItem) {\n return;\n }\n\n if (this.selectedItem) {\n this.selectedItem.selected = false;\n }\n\n this.selectedItem = nextItem;\n\n if (this.selectedItem) {\n this.selectedItem.selected = true;\n this.activeItemId = this.selectedItem.id;\n } else {\n this.activeItemId = null;\n }\n\n if (emitChange) {\n this.dispatchEvent(new CustomEvent<FvMasterDetailListChangeDetail>('change', {\n bubbles: true,\n composed: true,\n detail: {\n item: this.selectedItem,\n value: this.selectedItem?.value || this.selectedItem?.titleText || null\n }\n }));\n }\n }\n}\n\nconst okFvMasterDetailList = FvMasterDetailList.compose({\n baseName: 'fv-master-detail-list',\n template,\n styles,\n shadowOptions: {\n delegatesFocus: true\n }\n});\n\nDesignSystem.getOrCreate().withPrefix('ok').register(okFvMasterDetailList());\nexport const fvMasterDetailListTag = 'ok-fv-master-detail-list';"]}
@@ -0,0 +1 @@
1
+ export declare const styles: import("@ni/fast-element").ElementStyles;
@@ -0,0 +1,68 @@
1
+ import { css } from '@ni/fast-element';
2
+ import { bodyFont, bodyFontColor, borderHoverColor, borderRgbPartialColor, borderWidth, placeholderFontColor, smallDelay, standardPadding } from '@ni/nimble-components/dist/esm/theme-provider/design-tokens';
3
+ import { display } from '../../utilities/style/display';
4
+ export const styles = css `
5
+ @layer base, hover, focusVisible;
6
+
7
+ @layer base {
8
+ ${display('block')}
9
+
10
+ :host {
11
+ color: ${bodyFontColor};
12
+ font: ${bodyFont};
13
+ }
14
+
15
+ .master-detail-list {
16
+ display: flex;
17
+ flex-direction: column;
18
+ min-block-size: 0;
19
+ }
20
+
21
+ .filter-row {
22
+ padding: ${standardPadding};
23
+ padding-block-end: 10px;
24
+ }
25
+
26
+ .filter-input {
27
+ -webkit-appearance: none;
28
+ appearance: none;
29
+ inline-size: 100%;
30
+ padding: 10px 2px 12px;
31
+ font: inherit;
32
+ color: inherit;
33
+ border: none;
34
+ border-bottom: ${borderWidth} solid rgba(${borderRgbPartialColor}, 0.35);
35
+ background: transparent;
36
+ outline: none;
37
+ transition: border-color ${smallDelay} ease-in-out;
38
+ }
39
+
40
+ .filter-input::placeholder {
41
+ color: ${placeholderFontColor};
42
+ }
43
+
44
+ .items {
45
+ display: block;
46
+ min-block-size: 0;
47
+ overflow: auto;
48
+ }
49
+
50
+ .empty-state {
51
+ padding: 18px;
52
+ color: ${placeholderFontColor};
53
+ }
54
+ }
55
+
56
+ @layer hover {
57
+ .filter-input:hover {
58
+ border-bottom-color: ${borderHoverColor};
59
+ }
60
+ }
61
+
62
+ @layer focusVisible {
63
+ .filter-input:focus-visible {
64
+ border-bottom-color: ${borderHoverColor};
65
+ }
66
+ }
67
+ `;
68
+ //# sourceMappingURL=styles.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"styles.js","sourceRoot":"","sources":["../../../../src/fv/master-detail-list/styles.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,kBAAkB,CAAC;AACvC,OAAO,EACH,QAAQ,EACR,aAAa,EACb,gBAAgB,EAChB,qBAAqB,EACrB,WAAW,EACX,oBAAoB,EACpB,UAAU,EACV,eAAe,EAClB,MAAM,6DAA6D,CAAC;AACrE,OAAO,EAAE,OAAO,EAAE,MAAM,+BAA+B,CAAC;AAExD,MAAM,CAAC,MAAM,MAAM,GAAG,GAAG,CAAA;;;;UAIf,OAAO,CAAC,OAAO,CAAC;;;qBAGL,aAAa;oBACd,QAAQ;;;;;;;;;;uBAUL,eAAe;;;;;;;;;;;;6BAYT,WAAW,eAAe,qBAAqB;;;uCAGrC,UAAU;;;;qBAI5B,oBAAoB;;;;;;;;;;;qBAWpB,oBAAoB;;;;;;mCAMN,gBAAgB;;;;;;mCAMhB,gBAAgB;;;CAGlD,CAAC","sourcesContent":["import { css } from '@ni/fast-element';\nimport {\n bodyFont,\n bodyFontColor,\n borderHoverColor,\n borderRgbPartialColor,\n borderWidth,\n placeholderFontColor,\n smallDelay,\n standardPadding\n} from '@ni/nimble-components/dist/esm/theme-provider/design-tokens';\nimport { display } from '../../utilities/style/display';\n\nexport const styles = css`\n @layer base, hover, focusVisible;\n\n @layer base {\n ${display('block')}\n\n :host {\n color: ${bodyFontColor};\n font: ${bodyFont};\n }\n\n .master-detail-list {\n display: flex;\n flex-direction: column;\n min-block-size: 0;\n }\n\n .filter-row {\n padding: ${standardPadding};\n padding-block-end: 10px;\n }\n\n .filter-input {\n -webkit-appearance: none;\n appearance: none;\n inline-size: 100%;\n padding: 10px 2px 12px;\n font: inherit;\n color: inherit;\n border: none;\n border-bottom: ${borderWidth} solid rgba(${borderRgbPartialColor}, 0.35);\n background: transparent;\n outline: none;\n transition: border-color ${smallDelay} ease-in-out;\n }\n\n .filter-input::placeholder {\n color: ${placeholderFontColor};\n }\n\n .items {\n display: block;\n min-block-size: 0;\n overflow: auto;\n }\n\n .empty-state {\n padding: 18px;\n color: ${placeholderFontColor};\n }\n }\n\n @layer hover {\n .filter-input:hover {\n border-bottom-color: ${borderHoverColor};\n }\n }\n\n @layer focusVisible {\n .filter-input:focus-visible {\n border-bottom-color: ${borderHoverColor};\n }\n }\n`;"]}
@@ -0,0 +1,2 @@
1
+ import type { FvMasterDetailList } from '.';
2
+ export declare const template: import("@ni/fast-element").ViewTemplate<FvMasterDetailList, any>;
@@ -0,0 +1,36 @@
1
+ import { html, ref, when } from '@ni/fast-element';
2
+ export const template = html `
3
+ <div class="master-detail-list">
4
+ <div class="filter-row">
5
+ <input
6
+ id="${x => x.inputId}"
7
+ class="filter-input"
8
+ type="text"
9
+ placeholder="${x => x.placeholder}"
10
+ :value="${x => x.filterText}"
11
+ aria-controls="${x => x.listboxId}"
12
+ aria-activedescendant="${x => x.activeItemId}"
13
+ aria-label="${x => x.placeholder}"
14
+ role="combobox"
15
+ aria-haspopup="listbox"
16
+ aria-autocomplete="list"
17
+ aria-expanded="true"
18
+ ${ref('captureFilterInput')}
19
+ @input="${(x, c) => x.handleFilterInput(c.event)}"
20
+ @keydown="${(x, c) => x.handleFilterKeydown(c.event)}"
21
+ />
22
+ </div>
23
+ <div
24
+ id="${x => x.listboxId}"
25
+ class="items"
26
+ role="listbox"
27
+ @click="${(x, c) => x.handleItemsClick(c.event)}"
28
+ >
29
+ <slot @slotchange="${x => x.handleItemsSlotChange()}"></slot>
30
+ ${when(x => !x.hasVisibleItems, html `
31
+ <div class="empty-state">No matches</div>
32
+ `)}
33
+ </div>
34
+ </div>
35
+ `;
36
+ //# sourceMappingURL=template.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"template.js","sourceRoot":"","sources":["../../../../src/fv/master-detail-list/template.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAGnD,MAAM,CAAC,MAAM,QAAQ,GAAG,IAAI,CAAoB;;;;sBAI1B,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO;;;+BAGL,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,WAAW;0BACvB,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,UAAU;iCACV,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS;yCACR,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,YAAY;8BAC9B,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,WAAW;;;;;kBAK9B,GAAG,CAAC,oBAAoB,CAAC;0BACjB,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,iBAAiB,CAAC,CAAC,CAAC,KAAK,CAAC;4BACpC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,mBAAmB,CAAC,CAAC,CAAC,KAAsB,CAAC;;;;kBAInE,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS;;;sBAGZ,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,gBAAgB,CAAC,CAAC,CAAC,KAAK,CAAC;;iCAE1B,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,qBAAqB,EAAE;cACjD,IAAI,CACF,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,eAAe,EACvB,IAAI,CAAoB;;iBAEvB,CACJ;;;CAGZ,CAAC","sourcesContent":["import { html, ref, when } from '@ni/fast-element';\nimport type { FvMasterDetailList } from '.';\n\nexport const template = html<FvMasterDetailList>`\n <div class=\"master-detail-list\">\n <div class=\"filter-row\">\n <input\n id=\"${x => x.inputId}\"\n class=\"filter-input\"\n type=\"text\"\n placeholder=\"${x => x.placeholder}\"\n :value=\"${x => x.filterText}\"\n aria-controls=\"${x => x.listboxId}\"\n aria-activedescendant=\"${x => x.activeItemId}\"\n aria-label=\"${x => x.placeholder}\"\n role=\"combobox\"\n aria-haspopup=\"listbox\"\n aria-autocomplete=\"list\"\n aria-expanded=\"true\"\n ${ref('captureFilterInput')}\n @input=\"${(x, c) => x.handleFilterInput(c.event)}\"\n @keydown=\"${(x, c) => x.handleFilterKeydown(c.event as KeyboardEvent)}\"\n />\n </div>\n <div\n id=\"${x => x.listboxId}\"\n class=\"items\"\n role=\"listbox\"\n @click=\"${(x, c) => x.handleItemsClick(c.event)}\"\n >\n <slot @slotchange=\"${x => x.handleItemsSlotChange()}\"></slot>\n ${when(\n x => !x.hasVisibleItems,\n html<FvMasterDetailList>`\n <div class=\"empty-state\">No matches</div>\n `\n )}\n </div>\n </div>\n`;"]}
@@ -0,0 +1,32 @@
1
+ import { FoundationElement } from '@ni/fast-foundation';
2
+ declare global {
3
+ interface HTMLElementTagNameMap {
4
+ 'ok-fv-master-detail-list-item': FvMasterDetailListItem;
5
+ }
6
+ }
7
+ /**
8
+ * A selectable master-detail list item with title, subtitle, and optional status content.
9
+ */
10
+ export declare class FvMasterDetailListItem extends FoundationElement {
11
+ titleText: string;
12
+ subtitle: string;
13
+ value: string;
14
+ compact: boolean;
15
+ selected: boolean;
16
+ statusColor: string;
17
+ statusLabel: string;
18
+ /** @internal */
19
+ hasStatusSlotContent: boolean;
20
+ /** @internal */
21
+ connectedCallback(): void;
22
+ /** @internal */
23
+ selectedChanged(): void;
24
+ /** @internal */
25
+ handleStatusSlotChange(event: Event): void;
26
+ /** @internal */
27
+ get hasStatus(): boolean;
28
+ /** @internal */
29
+ get fallbackStatusLabel(): string | null;
30
+ private syncSelectionAria;
31
+ }
32
+ export declare const fvMasterDetailListItemTag = "ok-fv-master-detail-list-item";
@@ -0,0 +1,86 @@
1
+ import { __decorate } from "tslib";
2
+ import { attr, observable } from '@ni/fast-element';
3
+ import { DesignSystem, FoundationElement } from '@ni/fast-foundation';
4
+ import { styles } from './styles';
5
+ import { template } from './template';
6
+ /**
7
+ * A selectable master-detail list item with title, subtitle, and optional status content.
8
+ */
9
+ export class FvMasterDetailListItem extends FoundationElement {
10
+ constructor() {
11
+ super(...arguments);
12
+ this.titleText = '';
13
+ this.subtitle = '';
14
+ this.value = '';
15
+ this.compact = false;
16
+ this.selected = false;
17
+ this.statusColor = '';
18
+ this.statusLabel = '';
19
+ /** @internal */
20
+ this.hasStatusSlotContent = false;
21
+ }
22
+ /** @internal */
23
+ connectedCallback() {
24
+ super.connectedCallback();
25
+ this.setAttribute('role', 'option');
26
+ this.syncSelectionAria();
27
+ }
28
+ /** @internal */
29
+ selectedChanged() {
30
+ if (this.$fastController.isConnected) {
31
+ this.syncSelectionAria();
32
+ }
33
+ }
34
+ /** @internal */
35
+ handleStatusSlotChange(event) {
36
+ const slot = event.target;
37
+ this.hasStatusSlotContent = slot.assignedNodes({ flatten: true }).length > 0;
38
+ }
39
+ /** @internal */
40
+ get hasStatus() {
41
+ return this.hasStatusSlotContent || this.statusColor.trim().length > 0;
42
+ }
43
+ /** @internal */
44
+ get fallbackStatusLabel() {
45
+ if (this.hasStatusSlotContent || this.statusColor.trim().length === 0) {
46
+ return null;
47
+ }
48
+ const normalizedStatusLabel = this.statusLabel.trim();
49
+ return normalizedStatusLabel.length > 0 ? normalizedStatusLabel : null;
50
+ }
51
+ syncSelectionAria() {
52
+ this.setAttribute('aria-selected', String(this.selected));
53
+ }
54
+ }
55
+ __decorate([
56
+ attr({ attribute: 'title-text' })
57
+ ], FvMasterDetailListItem.prototype, "titleText", void 0);
58
+ __decorate([
59
+ attr
60
+ ], FvMasterDetailListItem.prototype, "subtitle", void 0);
61
+ __decorate([
62
+ attr
63
+ ], FvMasterDetailListItem.prototype, "value", void 0);
64
+ __decorate([
65
+ attr({ mode: 'boolean' })
66
+ ], FvMasterDetailListItem.prototype, "compact", void 0);
67
+ __decorate([
68
+ attr({ mode: 'boolean' })
69
+ ], FvMasterDetailListItem.prototype, "selected", void 0);
70
+ __decorate([
71
+ attr({ attribute: 'status-color' })
72
+ ], FvMasterDetailListItem.prototype, "statusColor", void 0);
73
+ __decorate([
74
+ attr({ attribute: 'status-label' })
75
+ ], FvMasterDetailListItem.prototype, "statusLabel", void 0);
76
+ __decorate([
77
+ observable
78
+ ], FvMasterDetailListItem.prototype, "hasStatusSlotContent", void 0);
79
+ const okFvMasterDetailListItem = FvMasterDetailListItem.compose({
80
+ baseName: 'fv-master-detail-list-item',
81
+ template,
82
+ styles
83
+ });
84
+ DesignSystem.getOrCreate().withPrefix('ok').register(okFvMasterDetailListItem());
85
+ export const fvMasterDetailListItemTag = 'ok-fv-master-detail-list-item';
86
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/fv/master-detail-list-item/index.ts"],"names":[],"mappings":"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","sourcesContent":["import { attr, observable } from '@ni/fast-element';\nimport { DesignSystem, FoundationElement } from '@ni/fast-foundation';\nimport { styles } from './styles';\nimport { template } from './template';\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'ok-fv-master-detail-list-item': FvMasterDetailListItem;\n }\n}\n\n/**\n * A selectable master-detail list item with title, subtitle, and optional status content.\n */\nexport class FvMasterDetailListItem extends FoundationElement {\n @attr({ attribute: 'title-text' })\n public titleText = '';\n\n @attr\n public subtitle = '';\n\n @attr\n public value = '';\n\n @attr({ mode: 'boolean' })\n public compact = false;\n\n @attr({ mode: 'boolean' })\n public selected = false;\n\n @attr({ attribute: 'status-color' })\n public statusColor = '';\n\n @attr({ attribute: 'status-label' })\n public statusLabel = '';\n\n /** @internal */\n @observable\n public hasStatusSlotContent = false;\n\n /** @internal */\n public override connectedCallback(): void {\n super.connectedCallback();\n\n this.setAttribute('role', 'option');\n this.syncSelectionAria();\n }\n\n /** @internal */\n public selectedChanged(): void {\n if (this.$fastController.isConnected) {\n this.syncSelectionAria();\n }\n }\n\n /** @internal */\n public handleStatusSlotChange(event: Event): void {\n const slot = event.target as HTMLSlotElement;\n this.hasStatusSlotContent = slot.assignedNodes({ flatten: true }).length > 0;\n }\n\n /** @internal */\n public get hasStatus(): boolean {\n return this.hasStatusSlotContent || this.statusColor.trim().length > 0;\n }\n\n /** @internal */\n public get fallbackStatusLabel(): string | null {\n if (this.hasStatusSlotContent || this.statusColor.trim().length === 0) {\n return null;\n }\n\n const normalizedStatusLabel = this.statusLabel.trim();\n return normalizedStatusLabel.length > 0 ? normalizedStatusLabel : null;\n }\n\n private syncSelectionAria(): void {\n this.setAttribute('aria-selected', String(this.selected));\n }\n}\n\nconst okFvMasterDetailListItem = FvMasterDetailListItem.compose({\n baseName: 'fv-master-detail-list-item',\n template,\n styles\n});\n\nDesignSystem.getOrCreate().withPrefix('ok').register(okFvMasterDetailListItem());\nexport const fvMasterDetailListItemTag = 'ok-fv-master-detail-list-item';"]}
@@ -0,0 +1 @@
1
+ export declare const styles: import("@ni/fast-element").ElementStyles;
@@ -0,0 +1,100 @@
1
+ import { css } from '@ni/fast-element';
2
+ import { bodyFont, bodyFontColor, borderHoverColor, borderWidth, fillHoverColor, fillSelectedColor, iconColor, placeholderFontColor, standardPadding } from '@ni/nimble-components/dist/esm/theme-provider/design-tokens';
3
+ import { display } from '../../utilities/style/display';
4
+ export const styles = css `
5
+ @layer base, hover, selected;
6
+
7
+ @layer base {
8
+ ${display('block')}
9
+
10
+ :host {
11
+ position: relative;
12
+ color: ${bodyFontColor};
13
+ cursor: pointer;
14
+ user-select: none;
15
+ }
16
+
17
+ :host([hidden]) {
18
+ display: none;
19
+ }
20
+
21
+ .item {
22
+ display: grid;
23
+ grid-template-columns: minmax(0, 1fr) auto;
24
+ align-items: center;
25
+ gap: ${standardPadding};
26
+ min-block-size: 76px;
27
+ padding: 14px 18px 14px 22px;
28
+ background: transparent;
29
+ }
30
+
31
+ :host([compact]) .item {
32
+ min-block-size: 56px;
33
+ padding: 10px 18px 10px 22px;
34
+ }
35
+
36
+ .text {
37
+ min-inline-size: 0;
38
+ }
39
+
40
+ .title {
41
+ overflow: hidden;
42
+ text-overflow: ellipsis;
43
+ white-space: nowrap;
44
+ font: ${bodyFont};
45
+ font-weight: 600;
46
+ color: ${bodyFontColor};
47
+ }
48
+
49
+ .subtitle {
50
+ overflow: hidden;
51
+ text-overflow: ellipsis;
52
+ white-space: nowrap;
53
+ margin-block-start: 4px;
54
+ font: ${bodyFont};
55
+ color: ${placeholderFontColor};
56
+ }
57
+
58
+ :host([compact]) .subtitle {
59
+ margin-block-start: 2px;
60
+ }
61
+
62
+ .status {
63
+ display: inline-flex;
64
+ align-items: center;
65
+ justify-content: center;
66
+ inline-size: 16px;
67
+ block-size: 16px;
68
+ flex: 0 0 auto;
69
+ }
70
+
71
+ .status-icon {
72
+ inline-size: 10px;
73
+ block-size: 10px;
74
+ color: currentColor;
75
+ ${iconColor.cssCustomProperty}: currentColor;
76
+ }
77
+ }
78
+
79
+ @layer hover {
80
+ :host(:hover) .item {
81
+ background: ${fillHoverColor};
82
+ }
83
+ }
84
+
85
+ @layer selected {
86
+ :host([selected]) .item {
87
+ background: ${fillSelectedColor};
88
+ }
89
+
90
+ :host([selected])::before {
91
+ content: '';
92
+ position: absolute;
93
+ inset-block: 0;
94
+ inset-inline-start: 0;
95
+ inline-size: calc(${borderWidth} * 4);
96
+ background: ${borderHoverColor};
97
+ }
98
+ }
99
+ `;
100
+ //# sourceMappingURL=styles.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"styles.js","sourceRoot":"","sources":["../../../../src/fv/master-detail-list-item/styles.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,kBAAkB,CAAC;AACvC,OAAO,EACH,QAAQ,EACR,aAAa,EACb,gBAAgB,EAChB,WAAW,EACX,cAAc,EACd,iBAAiB,EACjB,SAAS,EACT,oBAAoB,EACpB,eAAe,EAClB,MAAM,6DAA6D,CAAC;AACrE,OAAO,EAAE,OAAO,EAAE,MAAM,+BAA+B,CAAC;AAExD,MAAM,CAAC,MAAM,MAAM,GAAG,GAAG,CAAA;;;;UAIf,OAAO,CAAC,OAAO,CAAC;;;;qBAIL,aAAa;;;;;;;;;;;;;mBAaf,eAAe;;;;;;;;;;;;;;;;;;;oBAmBd,QAAQ;;qBAEP,aAAa;;;;;;;;oBAQd,QAAQ;qBACP,oBAAoB;;;;;;;;;;;;;;;;;;;;cAoB3B,SAAS,CAAC,iBAAiB;;;;;;0BAMf,cAAc;;;;;;0BAMd,iBAAiB;;;;;;;;gCAQX,WAAW;0BACjB,gBAAgB;;;CAGzC,CAAC","sourcesContent":["import { css } from '@ni/fast-element';\nimport {\n bodyFont,\n bodyFontColor,\n borderHoverColor,\n borderWidth,\n fillHoverColor,\n fillSelectedColor,\n iconColor,\n placeholderFontColor,\n standardPadding\n} from '@ni/nimble-components/dist/esm/theme-provider/design-tokens';\nimport { display } from '../../utilities/style/display';\n\nexport const styles = css`\n @layer base, hover, selected;\n\n @layer base {\n ${display('block')}\n\n :host {\n position: relative;\n color: ${bodyFontColor};\n cursor: pointer;\n user-select: none;\n }\n\n :host([hidden]) {\n display: none;\n }\n\n .item {\n display: grid;\n grid-template-columns: minmax(0, 1fr) auto;\n align-items: center;\n gap: ${standardPadding};\n min-block-size: 76px;\n padding: 14px 18px 14px 22px;\n background: transparent;\n }\n\n :host([compact]) .item {\n min-block-size: 56px;\n padding: 10px 18px 10px 22px;\n }\n\n .text {\n min-inline-size: 0;\n }\n\n .title {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n font: ${bodyFont};\n font-weight: 600;\n color: ${bodyFontColor};\n }\n\n .subtitle {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n margin-block-start: 4px;\n font: ${bodyFont};\n color: ${placeholderFontColor};\n }\n\n :host([compact]) .subtitle {\n margin-block-start: 2px;\n }\n\n .status {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n inline-size: 16px;\n block-size: 16px;\n flex: 0 0 auto;\n }\n\n .status-icon {\n inline-size: 10px;\n block-size: 10px;\n color: currentColor;\n ${iconColor.cssCustomProperty}: currentColor;\n }\n }\n\n @layer hover {\n :host(:hover) .item {\n background: ${fillHoverColor};\n }\n }\n\n @layer selected {\n :host([selected]) .item {\n background: ${fillSelectedColor};\n }\n\n :host([selected])::before {\n content: '';\n position: absolute;\n inset-block: 0;\n inset-inline-start: 0;\n inline-size: calc(${borderWidth} * 4);\n background: ${borderHoverColor};\n }\n }\n`;"]}
@@ -0,0 +1,2 @@
1
+ import type { FvMasterDetailListItem } from '.';
2
+ export declare const template: import("@ni/fast-element").ViewTemplate<FvMasterDetailListItem, any>;
@@ -0,0 +1,28 @@
1
+ import { html, when } from '@ni/fast-element';
2
+ import { iconCircleFilledTag } from '@ni/nimble-components/dist/esm/icons/circle-filled';
3
+ export const template = html `
4
+ <div class="item" part="item">
5
+ <div class="text" part="text">
6
+ <div class="title" part="title">${x => x.titleText}</div>
7
+ <div class="subtitle" part="subtitle">${x => x.subtitle}</div>
8
+ </div>
9
+ <div class="status" part="status" ?hidden="${x => !x.hasStatus}">
10
+ <slot
11
+ name="status"
12
+ @slotchange="${(x, c) => x.handleStatusSlotChange(c.event)}"
13
+ >
14
+ ${when(x => x.statusColor.trim().length > 0, html `
15
+ <${iconCircleFilledTag}
16
+ class="status-icon"
17
+ part="status-icon"
18
+ role="${x => (x.fallbackStatusLabel ? 'img' : null)}"
19
+ aria-label="${x => x.fallbackStatusLabel}"
20
+ aria-hidden="${x => (x.fallbackStatusLabel === null ? 'true' : null)}"
21
+ style="${x => `color: ${x.statusColor};`}"
22
+ ></${iconCircleFilledTag}>
23
+ `)}
24
+ </slot>
25
+ </div>
26
+ </div>
27
+ `;
28
+ //# sourceMappingURL=template.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"template.js","sourceRoot":"","sources":["../../../../src/fv/master-detail-list-item/template.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EAAE,mBAAmB,EAAE,MAAM,oDAAoD,CAAC;AAGzF,MAAM,CAAC,MAAM,QAAQ,GAAG,IAAI,CAAwB;;;8CAGN,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS;oDACV,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ;;qDAEd,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,SAAS;;;+BAGvC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,sBAAsB,CAAC,CAAC,CAAC,KAAK,CAAC;;kBAExD,IAAI,CACF,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,WAAW,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,EACpC,IAAI,CAAwB;2BACrB,mBAAmB;;;oCAGV,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,mBAAmB,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC;0CACrC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,mBAAmB;2CACzB,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,mBAAmB,KAAK,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC;qCAC3D,CAAC,CAAC,EAAE,CAAC,UAAU,CAAC,CAAC,WAAW,GAAG;6BACvC,mBAAmB;qBAC3B,CACJ;;;;CAIhB,CAAC","sourcesContent":["import { html, when } from '@ni/fast-element';\nimport { iconCircleFilledTag } from '@ni/nimble-components/dist/esm/icons/circle-filled';\nimport type { FvMasterDetailListItem } from '.';\n\nexport const template = html<FvMasterDetailListItem>`\n <div class=\"item\" part=\"item\">\n <div class=\"text\" part=\"text\">\n <div class=\"title\" part=\"title\">${x => x.titleText}</div>\n <div class=\"subtitle\" part=\"subtitle\">${x => x.subtitle}</div>\n </div>\n <div class=\"status\" part=\"status\" ?hidden=\"${x => !x.hasStatus}\">\n <slot\n name=\"status\"\n @slotchange=\"${(x, c) => x.handleStatusSlotChange(c.event)}\"\n >\n ${when(\n x => x.statusColor.trim().length > 0,\n html<FvMasterDetailListItem>`\n <${iconCircleFilledTag}\n class=\"status-icon\"\n part=\"status-icon\"\n role=\"${x => (x.fallbackStatusLabel ? 'img' : null)}\"\n aria-label=\"${x => x.fallbackStatusLabel}\"\n aria-hidden=\"${x => (x.fallbackStatusLabel === null ? 'true' : null)}\"\n style=\"${x => `color: ${x.statusColor};`}\"\n ></${iconCircleFilledTag}>\n `\n )}\n </slot>\n </div>\n </div>\n`;"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ni/ok-components",
3
- "version": "1.5.2",
3
+ "version": "1.6.1",
4
4
  "description": "NI Ok Components",
5
5
  "scripts": {
6
6
  "build": "npm run build:components && npm run build:cem",
@@ -42,8 +42,8 @@
42
42
  "@ni/fast-element": "^10.1.1",
43
43
  "@ni/fast-foundation": "^10.2.3",
44
44
  "@ni/fast-web-utilities": "^10.0.0",
45
- "@ni/nimble-components": "^35.11.0",
46
- "@ni/spright-components": "^6.21.0",
45
+ "@ni/nimble-components": "^35.12.0",
46
+ "@ni/spright-components": "^6.21.1",
47
47
  "tslib": "^2.2.0"
48
48
  },
49
49
  "devDependencies": {