nexa-ui-kit 0.6.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 (114) hide show
  1. package/dist/NBadge.nexa +40 -0
  2. package/dist/NBottomSheet.nexa +124 -0
  3. package/dist/NButton.nexa +123 -0
  4. package/dist/NCard.nexa +74 -0
  5. package/dist/NInput.nexa +116 -0
  6. package/dist/NModal.nexa +165 -0
  7. package/dist/NSelect.nexa +169 -0
  8. package/dist/NToastContainer.nexa +86 -0
  9. package/dist/NTooltip.nexa +115 -0
  10. package/dist/components/NAlert.js +134 -0
  11. package/dist/components/NAlert.nexa +115 -0
  12. package/dist/components/NAutocomplete.js +94 -0
  13. package/dist/components/NAutocomplete.nexa +58 -0
  14. package/dist/components/NAvatar.js +75 -0
  15. package/dist/components/NAvatar.nexa +67 -0
  16. package/dist/components/NBadge.js +74 -0
  17. package/dist/components/NBadge.nexa +61 -0
  18. package/dist/components/NBottomSheet.js +149 -0
  19. package/dist/components/NBottomSheet.nexa +145 -0
  20. package/dist/components/NButton.js +284 -0
  21. package/dist/components/NButton.nexa +275 -0
  22. package/dist/components/NCard.js +117 -0
  23. package/dist/components/NCard.nexa +100 -0
  24. package/dist/components/NCheckbox.js +108 -0
  25. package/dist/components/NCheckbox.nexa +90 -0
  26. package/dist/components/NChips.js +72 -0
  27. package/dist/components/NChips.nexa +57 -0
  28. package/dist/components/NDataTable.js +252 -0
  29. package/dist/components/NDataTable.nexa +186 -0
  30. package/dist/components/NDatepicker.js +379 -0
  31. package/dist/components/NDatepicker.nexa +367 -0
  32. package/dist/components/NForm.js +132 -0
  33. package/dist/components/NForm.nexa +133 -0
  34. package/dist/components/NFormField.js +173 -0
  35. package/dist/components/NFormField.nexa +171 -0
  36. package/dist/components/NInput.js +311 -0
  37. package/dist/components/NInput.nexa +311 -0
  38. package/dist/components/NInputNumber.js +202 -0
  39. package/dist/components/NInputNumber.nexa +199 -0
  40. package/dist/components/NModal.js +221 -0
  41. package/dist/components/NModal.nexa +221 -0
  42. package/dist/components/NMultiSelect.js +156 -0
  43. package/dist/components/NMultiSelect.nexa +77 -0
  44. package/dist/components/NPaginator.js +117 -0
  45. package/dist/components/NPaginator.nexa +77 -0
  46. package/dist/components/NPassword.js +193 -0
  47. package/dist/components/NPassword.nexa +178 -0
  48. package/dist/components/NProgressBar.js +127 -0
  49. package/dist/components/NProgressBar.nexa +111 -0
  50. package/dist/components/NRadio.js +96 -0
  51. package/dist/components/NRadio.nexa +81 -0
  52. package/dist/components/NSelect.js +468 -0
  53. package/dist/components/NSelect.nexa +452 -0
  54. package/dist/components/NSkeleton.js +98 -0
  55. package/dist/components/NSkeleton.nexa +74 -0
  56. package/dist/components/NSwitch.js +92 -0
  57. package/dist/components/NSwitch.nexa +76 -0
  58. package/dist/components/NTabs.js +129 -0
  59. package/dist/components/NTabs.nexa +113 -0
  60. package/dist/components/NTag.js +108 -0
  61. package/dist/components/NTag.nexa +93 -0
  62. package/dist/components/NToastContainer.js +242 -0
  63. package/dist/components/NToastContainer.nexa +221 -0
  64. package/dist/components/NTooltip.js +163 -0
  65. package/dist/components/NTooltip.nexa +166 -0
  66. package/dist/components/NTreeMenu.js +151 -0
  67. package/dist/components/NTreeMenu.nexa +142 -0
  68. package/dist/index.d.ts +32 -0
  69. package/dist/index.js +34 -0
  70. package/dist/services/FloatingOverlay.d.ts +27 -0
  71. package/dist/services/FloatingOverlay.js +98 -0
  72. package/dist/services/FormValidation.d.ts +8 -0
  73. package/dist/services/FormValidation.js +46 -0
  74. package/dist/services/ToastService.d.ts +16 -0
  75. package/dist/services/ToastService.js +26 -0
  76. package/dist/styles/theme.d.ts +1 -0
  77. package/dist/styles/theme.js +144 -0
  78. package/package.json +32 -0
  79. package/src/components/NAlert.nexa +115 -0
  80. package/src/components/NAutocomplete.nexa +58 -0
  81. package/src/components/NAvatar.nexa +67 -0
  82. package/src/components/NBadge.nexa +61 -0
  83. package/src/components/NBottomSheet.nexa +145 -0
  84. package/src/components/NButton.nexa +275 -0
  85. package/src/components/NCard.nexa +100 -0
  86. package/src/components/NCheckbox.nexa +90 -0
  87. package/src/components/NChips.nexa +57 -0
  88. package/src/components/NDataTable.nexa +186 -0
  89. package/src/components/NDatepicker.nexa +367 -0
  90. package/src/components/NForm.nexa +133 -0
  91. package/src/components/NFormField.nexa +171 -0
  92. package/src/components/NInput.nexa +311 -0
  93. package/src/components/NInputNumber.nexa +199 -0
  94. package/src/components/NModal.nexa +221 -0
  95. package/src/components/NMultiSelect.nexa +77 -0
  96. package/src/components/NPaginator.nexa +77 -0
  97. package/src/components/NPassword.nexa +178 -0
  98. package/src/components/NProgressBar.nexa +111 -0
  99. package/src/components/NRadio.nexa +81 -0
  100. package/src/components/NSelect.nexa +452 -0
  101. package/src/components/NSkeleton.nexa +74 -0
  102. package/src/components/NSwitch.nexa +76 -0
  103. package/src/components/NTabs.nexa +113 -0
  104. package/src/components/NTag.nexa +93 -0
  105. package/src/components/NToastContainer.nexa +221 -0
  106. package/src/components/NTooltip.nexa +166 -0
  107. package/src/components/NTreeMenu.nexa +142 -0
  108. package/src/index.ts +36 -0
  109. package/src/services/FloatingOverlay.ts +133 -0
  110. package/src/services/FormValidation.ts +44 -0
  111. package/src/services/ToastService.ts +41 -0
  112. package/src/shims.d.ts +5 -0
  113. package/src/styles/theme.ts +146 -0
  114. package/src/styles/tokens.css +170 -0
@@ -0,0 +1,252 @@
1
+ import { signal, computed, effect, h, hText, defineComponent, registerComponent, reloadComponent, injectStyle } from 'nexa-framework'
2
+ import NPaginator from './NPaginator.js'
3
+
4
+ const _sfc_main = defineComponent({
5
+ __scopeId: 'data-v-71b71cd6',
6
+ __hmrId: 'NDataTable_nexa',
7
+ props: { value: { type: Array, default: () => [] }, columns: { type: Array, default: () => [] }, stripedRows: { type: Boolean, default: false }, hoverRows: { type: Boolean, default: true }, size: { type: String, default: 'md' }, scrollable: { type: Boolean, default: true }, paginator: { type: Boolean, default: true }, rows: { type: Number, default: 10 }, first: { type: Number, default: 0 }, rowsPerPageOptions: { type: Array, default: () => [10, 20, 50] }, selectionMode: { type: String, default: '' }, selection: { type: null, default: null }, dataKey: { type: String, default: '' }, sortField: { type: String, default: '' }, sortOrder: { type: Number, default: 1 }, resizableColumns: { type: Boolean, default: true }, columnResizeMode: { type: String, default: 'fit' }, filterDisplay: { type: String, default: 'row' }, globalFilter: { type: String, default: '' }, globalFilterFields: { type: Array, default: null }, filters: { type: null, default: null }, lazy: { type: Boolean, default: false }, totalRecords: { type: Number, default: 0 }, emptyMessage: { type: String, default: 'No records found' }, showGridlines: { type: Boolean, default: false } },
8
+ emits: ['update:first', 'update:rows', 'update:selection', 'update:sortField', 'update:sortOrder', 'update:globalFilter', 'update:filters', 'updateFirst', 'updateRows', 'updateSelection', 'updateSortField', 'updateSortOrder', 'updateGlobalFilter', 'updateFilters', 'page', 'rowSelect', 'rowUnselect', 'sort', 'filter'],
9
+ setup(props, setupContext) {
10
+ const { emit, slots, slots: $slots } = setupContext
11
+ const internalFirst = signal(props.first || 0)
12
+ const internalRows = signal(props.rows || 10)
13
+ const internalSortField = signal(props.sortField || '')
14
+ const internalSortOrder = signal(props.sortOrder || 1)
15
+ const internalFilters = signal((props.filters && typeof props.filters === 'object') ? props.filters : { __global: props.globalFilter || '' })
16
+ const effectiveFilters = computed(() => (props.filters && typeof props.filters === 'object') ? props.filters : internalFilters.value)
17
+ const columnWidths = signal({})
18
+ const resizing = signal(null)
19
+ const internalSelection = signal(props.selection)
20
+ effect(() => { internalFirst.value = props.first || 0 })
21
+ effect(() => { internalRows.value = props.rows || 10 })
22
+ effect(() => { internalSortField.value = props.sortField || '' })
23
+ effect(() => { internalSortOrder.value = props.sortOrder || 1 })
24
+ let lastPropGlobal = props.globalFilter || ''
25
+ effect(() => {
26
+ const next = props.globalFilter || ''
27
+ if (next === lastPropGlobal) return
28
+ lastPropGlobal = next
29
+ if (props.filters && typeof props.filters === 'object') return
30
+ internalFilters.value = { ...internalFilters.value, __global: next }
31
+ })
32
+ let lastPropSelection = props.selection
33
+ effect(() => {
34
+ const next = props.selection
35
+ if (next === lastPropSelection) return
36
+ lastPropSelection = next
37
+ internalSelection.value = next
38
+ })
39
+ const normalizeColumns = computed(() => (props.columns || []).map((c, idx) => { const field = c.field || `col_${idx}`; const header = c.header ?? field; const sortable = !!c.sortable; const filterable = c.filterable !== false; const type = c.type || 'text'; const width = c.width || ''; const minWidth = c.minWidth || '120px'; const align = c.align || (type === 'number' ? 'right' : 'left'); const filterMatchMode = c.filterMatchMode || 'contains'; return { ...c, field, header, sortable, filterable, type, width, minWidth, align, filterMatchMode } }))
40
+ const getRowKey = (row, index) => (props.dataKey && row && typeof row === 'object' && props.dataKey in row) ? String(row[props.dataKey]) : String(index)
41
+ const effectiveSelection = computed(() => internalSelection.value)
42
+ const isRowSelected = (row, index) => {
43
+ if (!props.selectionMode) return false
44
+ const selection = effectiveSelection.value
45
+ const key = getRowKey(row, index)
46
+ if (props.selectionMode === 'single') {
47
+ if (!selection) return false
48
+ if (props.dataKey && typeof selection === 'object' && selection && props.dataKey in selection) return String(selection[props.dataKey]) === key
49
+ return selection === row
50
+ }
51
+ const current = Array.isArray(selection) ? selection : []
52
+ if (props.dataKey) return current.some(s => s && typeof s === 'object' && props.dataKey in s && String(s[props.dataKey]) === key)
53
+ return current.includes(row)
54
+ }
55
+ const toggleRowSelection = (row, index) => {
56
+ if (!props.selectionMode) return
57
+ const selected = isRowSelected(row, index)
58
+ if (props.selectionMode === 'single') {
59
+ const next = selected ? null : row
60
+ internalSelection.value = next
61
+ emit('update:selection', next)
62
+ emit('updateSelection', next)
63
+ emit(selected ? 'rowUnselect' : 'rowSelect', { data: row })
64
+ return
65
+ }
66
+ const current = Array.isArray(effectiveSelection.value) ? effectiveSelection.value : []
67
+ const key = getRowKey(row, index)
68
+ const next = selected
69
+ ? (props.dataKey ? current.filter(r => String(r?.[props.dataKey]) !== key) : current.filter(r => r !== row))
70
+ : [...current, row]
71
+ internalSelection.value = next
72
+ emit('update:selection', next)
73
+ emit('updateSelection', next)
74
+ emit(selected ? 'rowUnselect' : 'rowSelect', { data: row })
75
+ }
76
+ const allVisibleSelected = computed(() => {
77
+ if (props.selectionMode !== 'multiple') return false
78
+ const rows = visibleRows.value
79
+ if (rows.length === 0) return false
80
+ for (let i = 0; i < rows.length; i++) {
81
+ if (!isRowSelected(rows[i], i + (internalFirst.value || 0))) return false
82
+ }
83
+ return true
84
+ })
85
+ const toggleAllVisible = () => {
86
+ if (props.selectionMode !== 'multiple') return
87
+ const current = Array.isArray(effectiveSelection.value) ? effectiveSelection.value : []
88
+ const rows = visibleRows.value
89
+ const startIndex = internalFirst.value || 0
90
+ if (rows.length === 0) return
91
+ if (allVisibleSelected.value) {
92
+ if (!props.dataKey) {
93
+ const next = current.filter(r => !rows.includes(r))
94
+ internalSelection.value = next
95
+ emit('update:selection', next)
96
+ emit('updateSelection', next)
97
+ return
98
+ }
99
+ const keys = rows.map((r, i) => getRowKey(r, startIndex + i))
100
+ const next = current.filter(r => !keys.includes(String(r?.[props.dataKey])))
101
+ internalSelection.value = next
102
+ emit('update:selection', next)
103
+ emit('updateSelection', next)
104
+ return
105
+ }
106
+ if (!props.dataKey) {
107
+ const next = [...current, ...rows.filter(r => !current.includes(r))]
108
+ internalSelection.value = next
109
+ emit('update:selection', next)
110
+ emit('updateSelection', next)
111
+ return
112
+ }
113
+ const existing = new Set(current.map(r => String(r?.[props.dataKey])))
114
+ const next = [...current, ...rows.filter((r) => !existing.has(String(r?.[props.dataKey])))]
115
+ internalSelection.value = next
116
+ emit('update:selection', next)
117
+ emit('updateSelection', next)
118
+ }
119
+ const getHeaderContent = (col) => (col.headerTemplate && typeof col.headerTemplate === 'function') ? col.headerTemplate(col) : col.header
120
+ const getRawValue = (row, col) => { if (!row) return ''; if (col.field && typeof row === 'object' && col.field in row) return row[col.field]; return '' }
121
+ const getCellContent = (row, col, index) => { const raw = getRawValue(row, col); if (col.body && typeof col.body === 'function') return col.body(row, { index, column: col, field: col.field, value: raw }); return raw }
122
+ const matchFilter = (value, query, mode) => { const q = String(query ?? '').toLowerCase().trim(); if (!q) return true; const v = String(value ?? '').toLowerCase(); if (mode === 'equals') return v === q; if (mode === 'startsWith') return v.startsWith(q); if (mode === 'endsWith') return v.endsWith(q); return v.includes(q) }
123
+ const filteredRows = computed(() => { const rows = props.value || []; if (props.lazy) return rows; const cols = normalizeColumns.value; const filters = effectiveFilters.value || {}; const global = String(filters.__global || '').trim(); const keys = Object.keys(filters).filter(k => k !== '__global'); if (!global && keys.length === 0) return rows; const globalFields = Array.isArray(props.globalFilterFields) && props.globalFilterFields.length ? props.globalFilterFields : cols.map(c => c.field); return rows.filter((row) => { if (global) { const ok = globalFields.some((f) => matchFilter(getRawValue(row, { field: f }), global, 'contains')); if (!ok) return false } for (const c of cols) { const f = filters[c.field]; if (!f) continue; if (!matchFilter(getRawValue(row, c), f.value, f.matchMode || c.filterMatchMode || 'contains')) return false } return true }) })
124
+ const sortedRows = computed(() => { if (props.lazy) return filteredRows.value; const rows = [...filteredRows.value]; const field = internalSortField.value; if (!field) return rows; const col = normalizeColumns.value.find(c => c.field === field); const order = internalSortOrder.value === -1 ? -1 : 1; const type = col?.type || 'text'; rows.sort((a, b) => { const av = getCellContent(a, { field }, -1); const bv = getCellContent(b, { field }, -1); if (type === 'number') return (Number(av) - Number(bv)) * order; return String(av ?? '').localeCompare(String(bv ?? ''), undefined, { numeric: true, sensitivity: 'base' }) * order }); return rows })
125
+ const totalRecords = computed(() => props.lazy ? (props.totalRecords || (props.value || []).length) : sortedRows.value.length)
126
+ const visibleRows = computed(() => props.lazy ? (props.value || []) : (!props.paginator ? sortedRows.value : sortedRows.value.slice(internalFirst.value || 0, (internalFirst.value || 0) + (internalRows.value || 10))))
127
+ const setSort = (col) => {
128
+ if (!col.sortable) return
129
+ const nextField = col.field
130
+ let nextOrder = 1
131
+ if (internalSortField.value === nextField) nextOrder = internalSortOrder.value === 1 ? -1 : 1
132
+ internalSortField.value = nextField
133
+ internalSortOrder.value = nextOrder
134
+ internalFirst.value = 0
135
+ emit('update:sortField', nextField)
136
+ emit('update:sortOrder', nextOrder)
137
+ emit('updateSortField', nextField)
138
+ emit('updateSortOrder', nextOrder)
139
+ emit('sort', { sortField: nextField, sortOrder: nextOrder })
140
+ }
141
+ const onSortClick = (e) => { const field = e.currentTarget?.dataset?.field; if (!field) return; const col = normalizeColumns.value.find(c => c.field === field); if (!col) return; setSort(col) }
142
+ const setGlobal = (e) => {
143
+ const next = e.target.value
144
+ const nextFilters = { ...(effectiveFilters.value || {}), __global: next }
145
+ if (props.filters && typeof props.filters === 'object') { emit('update:filters', nextFilters); emit('updateFilters', nextFilters) } else { internalFilters.value = nextFilters }
146
+ internalFirst.value = 0
147
+ emit('update:globalFilter', next)
148
+ emit('updateGlobalFilter', next)
149
+ emit('filter', { global: next, filters: nextFilters })
150
+ }
151
+ const setColumnFilter = (field, e) => { const next = e.target.value; const current = { ...(effectiveFilters.value || {}) }; const col = normalizeColumns.value.find(c => c.field === field); if (!next) delete current[field]; else current[field] = { value: next, matchMode: col?.filterMatchMode || current[field]?.matchMode || 'contains' }; if (props.filters && typeof props.filters === 'object') { emit('update:filters', current); emit('updateFilters', current) } else { internalFilters.value = current } internalFirst.value = 0; emit('filter', { field, value: next, filters: current }) }
152
+ const onColumnFilterInput = (e) => { const field = e.target?.dataset?.field; if (!field) return; setColumnFilter(field, e) }
153
+ const onPage = ({ first, rows }) => {
154
+ internalFirst.value = first
155
+ internalRows.value = rows
156
+ emit('update:first', first)
157
+ emit('update:rows', rows)
158
+ emit('updateFirst', first)
159
+ emit('updateRows', rows)
160
+ emit('page', { first, rows })
161
+ }
162
+ const getWidth = (col) => columnWidths.value[col.field] || col.width || ''
163
+ const startResize = (e) => { if (!props.resizableColumns) return; const field = e.currentTarget?.dataset?.field; if (!field) return; e.preventDefault(); e.stopPropagation(); const th = e.currentTarget?.parentElement; const base = th?.getBoundingClientRect?.().width || parseFloat(th?.style?.width || '') || 160; resizing.value = { field, startX: e.clientX, startW: base }; const onMove = (ev) => { if (!resizing.value) return; const delta = ev.clientX - resizing.value.startX; const next = Math.max(80, resizing.value.startW + delta); columnWidths.value = { ...columnWidths.value, [resizing.value.field]: `${Math.round(next)}px` } }; const onUp = () => { document.removeEventListener('mousemove', onMove); document.removeEventListener('mouseup', onUp); resizing.value = null }; document.addEventListener('mousemove', onMove); document.addEventListener('mouseup', onUp) }
164
+ return { internalFirst, internalRows, internalSortField, internalSortOrder, internalFilters, effectiveFilters, columnWidths, resizing, internalSelection, lastPropGlobal, lastPropSelection, normalizeColumns, getRowKey, effectiveSelection, isRowSelected, toggleRowSelection, allVisibleSelected, toggleAllVisible, getHeaderContent, getRawValue, getCellContent, matchFilter, filteredRows, sortedRows, totalRecords, visibleRows, setSort, onSortClick, setGlobal, setColumnFilter, onColumnFilterInput, onPage, getWidth, startResize, NPaginator, $slots, emit }
165
+ }
166
+ })
167
+ // Injected render function
168
+ _sfc_main.render = function(ctx) {
169
+ const { internalFirst, internalRows, internalSortField, internalSortOrder, internalFilters, effectiveFilters, columnWidths, resizing, internalSelection, lastPropGlobal, lastPropSelection, normalizeColumns, getRowKey, effectiveSelection, isRowSelected, toggleRowSelection, allVisibleSelected, toggleAllVisible, getHeaderContent, getRawValue, getCellContent, matchFilter, filteredRows, sortedRows, totalRecords, visibleRows, setSort, onSortClick, setGlobal, setColumnFilter, onColumnFilterInput, onPage, getWidth, startResize, NPaginator: _ntc_NPaginator, $slots, emit, value, columns, stripedRows, hoverRows, size, scrollable, paginator, rows, first, rowsPerPageOptions, selectionMode, selection, dataKey, sortField, sortOrder, resizableColumns, columnResizeMode, filterDisplay, globalFilter, globalFilterFields, filters, lazy, emptyMessage, showGridlines, Fragment: _ntc_Fragment } = ctx
170
+ return h('div', { class: ["n-dt", [`n-dt-${size}`, showGridlines ? 'is-grid' : '', scrollable ? 'is-scroll' : '']], "data-v-71b71cd6": "" }, [
171
+ "\n ",
172
+ h('div', { class: "n-dt-toolbar", "data-v-71b71cd6": "" }, [
173
+ h('div', { class: "n-dt-global", "data-v-71b71cd6": "" }, [
174
+ h('input', { class: "n-dt-global-input", value: effectiveFilters.value.__global || '', placeholder: "Search...", onInput: setGlobal, "data-v-71b71cd6": "" })
175
+ ])
176
+ ]),
177
+ "\n ",
178
+ h('div', { class: "n-dt-wrapper", "data-v-71b71cd6": "" }, [
179
+ "\n ",
180
+ h('table', { class: ["n-dt-table", { 'is-striped': stripedRows, 'is-hover': hoverRows }], "data-v-71b71cd6": "" }, [
181
+ "\n ",
182
+ h('thead', { class: "n-dt-thead", "data-v-71b71cd6": "" }, [
183
+ "\n ",
184
+ h('tr', { class: "n-dt-head-row", "data-v-71b71cd6": "" }, [
185
+ (selectionMode) ? h('th', { class: "n-dt-th is-selection", "data-v-71b71cd6": "" }, [
186
+ (selectionMode === 'multiple') ? h('input', { class: "n-dt-selectbox", type: "checkbox", checked: allVisibleSelected.value, onClick: ($event) => { $event.stopPropagation(); (toggleAllVisible)($event) }, "data-v-71b71cd6": "" }) : null
187
+ ]) : null,
188
+ normalizeColumns.value.map((col, index) =>
189
+ h('th', { class: ["n-dt-th", [`is-${col.align}`, col.sortable ? 'is-sortable' : '']], key: col.field, style: { width: getWidth(col) || undefined, minWidth: col.minWidth }, "data-field": col.field, onClick: onSortClick, "data-v-71b71cd6": "" }, [
190
+ h('div', { class: "n-dt-th-content", "data-v-71b71cd6": "" }, [
191
+ h('span', { class: "n-dt-th-text", "data-v-71b71cd6": "" }, [
192
+ getHeaderContent(col)
193
+ ]),
194
+ (col.sortable) ? h('span', { class: ["n-dt-sort", { 'is-active': internalSortField.value === col.field }], "data-v-71b71cd6": "" }, [
195
+ (internalSortField.value !== col.field) ? h('span', { class: "n-dt-sort-icon", "data-v-71b71cd6": "" }, [
196
+ "↕"
197
+ ]) : (true) ? h('span', { class: "n-dt-sort-icon", "data-v-71b71cd6": "" }, [
198
+ internalSortOrder.value === 1 ? '↑' : '↓'
199
+ ]) : null
200
+ ]) : null
201
+ ]),
202
+ (resizableColumns) ? h('span', { class: "n-dt-resizer", "data-field": col.field, onMousedown: startResize, "data-v-71b71cd6": "" }) : null
203
+ ])
204
+ )
205
+ ]),
206
+ "\n ",
207
+ (filterDisplay === 'row') ? h('tr', { class: "n-dt-filter-row", "data-v-71b71cd6": "" }, [
208
+ (selectionMode) ? h('th', { class: "n-dt-th is-selection", "data-v-71b71cd6": "" }) : null,
209
+ normalizeColumns.value.map((col, index) =>
210
+ h('th', { class: ["n-dt-th", `is-${col.align}`], key: col.field, style: { width: getWidth(col) || undefined, minWidth: col.minWidth }, "data-v-71b71cd6": "" }, [
211
+ (col.filterable) ? h('input', { class: "n-dt-filter", "data-field": col.field, value: (effectiveFilters.value[col.field]?.value) || '', placeholder: "Filter", onInput: onColumnFilterInput, "data-v-71b71cd6": "" }) : null
212
+ ])
213
+ )
214
+ ]) : null
215
+ ]),
216
+ "\n ",
217
+ h('tbody', { class: "n-dt-tbody", "data-v-71b71cd6": "" }, [
218
+ "\n ",
219
+ visibleRows.value.map((row, i) =>
220
+ h('tr', { class: ["n-dt-row", { 'is-selected': isRowSelected(row, i + internalFirst.value) }], key: getRowKey(row, i + internalFirst.value), onClick: ($event) => { toggleRowSelection(row, i + internalFirst.value) }, "data-v-71b71cd6": "" }, [
221
+ (selectionMode) ? h('td', { class: "n-dt-td is-selection", "data-v-71b71cd6": "" }, [
222
+ h('input', { class: "n-dt-selectbox", type: "checkbox", checked: isRowSelected(row, i + internalFirst.value), onClick: ($event) => { $event.stopPropagation(); (($event) => { toggleRowSelection(row, i + internalFirst.value) })($event) }, "data-v-71b71cd6": "" })
223
+ ]) : null,
224
+ normalizeColumns.value.map((col, index) =>
225
+ h('td', { class: ["n-dt-td", `is-${col.align}`], key: col.field, style: { width: getWidth(col) || undefined, minWidth: col.minWidth }, "data-v-71b71cd6": "" }, [
226
+ getCellContent(row, col, i + internalFirst.value)
227
+ ])
228
+ )
229
+ ])
230
+ ),
231
+ "\n ",
232
+ (visibleRows.value.length === 0) ? h('tr', { class: "n-dt-empty-row", "data-v-71b71cd6": "" }, [
233
+ h('td', { class: "n-dt-empty", colspan: normalizeColumns.value.length + (selectionMode ? 1 : 0), "data-v-71b71cd6": "" }, [
234
+ emptyMessage
235
+ ])
236
+ ]) : null
237
+ ]),
238
+ "\n "
239
+ ]),
240
+ "\n "
241
+ ]),
242
+ "\n ",
243
+ (paginator) ? h(_ntc_NPaginator, { first: internalFirst.value, rows: internalRows.value, totalRecords: totalRecords.value, rowsPerPageOptions: rowsPerPageOptions, onPage: onPage, "data-v-71b71cd6": "" }) : null
244
+ ])
245
+ }
246
+ _sfc_main.__scopeId = 'data-v-71b71cd6'
247
+ _sfc_main.__hmrId = 'NDataTable_nexa'
248
+
249
+ export default _sfc_main
250
+
251
+ const __style = `.n-dt[data-v-71b71cd6]{border:1px solid var(--n-color-border);border-radius:var(--n-radius-lg);background:var(--n-color-surface);overflow:hidden;font-family:var(--n-font-sans)}.n-dt-toolbar{display:flex;align-items:center;justify-content:space-between;padding:var(--n-space-3) var(--n-space-4);border-bottom:1px solid var(--n-color-border);background:linear-gradient(180deg,rgba(255,255,255,.04),rgba(0,0,0,.08))}.n-dt-global{display:flex;align-items:center;gap:var(--n-space-2)}.n-dt-global-input{width:280px;max-width:100%;background:var(--n-color-bg);border:1px solid var(--n-color-border);border-radius:var(--n-radius-md);padding:0.55rem 0.75rem;color:var(--n-color-text);font-size:var(--n-text-sm);outline:none;box-sizing:border-box}.n-dt-global-input:focus{border-color:var(--n-color-primary);box-shadow:0 0 0 3px var(--n-color-primary-light)}.n-dt-wrapper{width:100%;overflow:auto}.n-dt-table{width:100%;border-collapse:separate;border-spacing:0;table-layout:fixed}.n-dt-thead{background:var(--n-color-surface)}.n-dt-th,.n-dt-td{padding:0.75rem 0.9rem;border-bottom:1px solid var(--n-color-border);color:var(--n-color-text);font-size:var(--n-text-sm);vertical-align:middle}.n-dt-th{position:relative;overflow:hidden;background:rgba(0,0,0,.10);color:var(--n-color-text-secondary);font-weight:var(--n-weight-semibold);user-select:none}.n-dt-th.is-sortable{cursor:pointer}.n-dt-th-content{display:flex;align-items:center;justify-content:space-between;gap:0.5rem}.n-dt-sort{display:inline-flex;align-items:center;gap:0.25rem;color:var(--n-color-text-muted)}.n-dt-sort.is-active{color:var(--n-color-primary)}.n-dt-resizer{position:absolute;right:0;top:0;bottom:0;width:8px;cursor:col-resize}.n-dt-filter-row .n-dt-th{background:rgba(0,0,0,.06)}.n-dt-filter{width:100%;max-width:100%;display:block;background:var(--n-color-bg);border:1px solid var(--n-color-border);border-radius:var(--n-radius-sm);padding:0.35rem 0.5rem;color:var(--n-color-text);font-size:var(--n-text-xs);outline:none;box-sizing:border-box}.n-dt-filter:focus{border-color:var(--n-color-primary)}.n-dt-td{background:transparent;color:var(--n-color-text)}.n-dt-row.is-selected .n-dt-td{background:rgba(59,130,246,.12)}.n-dt-table.is-striped .n-dt-row:nth-child(even) .n-dt-td{background:rgba(0,0,0,.06)}.n-dt-table.is-hover .n-dt-row:hover .n-dt-td{background:rgba(255,255,255,.06)}.n-dt-empty{text-align:center;color:var(--n-color-text-muted);padding:1.25rem}.is-left{text-align:left}.is-right{text-align:right}.is-center{text-align:center}.is-selection{width:44px;min-width:44px;max-width:44px;text-align:center}.n-dt-selectbox{width:16px;height:16px;accent-color:var(--n-color-primary)}.n-dt-check{display:inline-flex;align-items:center;justify-content:center;width:18px;height:18px;border-radius:4px;border:1px solid var(--n-color-border);color:var(--n-color-primary)}.is-grid .n-dt-td,.is-grid .n-dt-th{border-right:1px solid var(--n-color-border)}.is-grid .n-dt-td:last-child,.is-grid .n-dt-th:last-child{border-right:none}.n-dt-sm .n-dt-th,.n-dt-sm .n-dt-td{padding:0.55rem 0.7rem}.n-dt-lg .n-dt-th,.n-dt-lg .n-dt-td{padding:0.9rem 1rem}`
252
+ injectStyle('data-v-71b71cd6', __style)
@@ -0,0 +1,186 @@
1
+ <script setup>
2
+ import { signal, computed, effect } from 'nexa-framework'
3
+ import NPaginator from './NPaginator.nexa'
4
+ const props = defineProps({ value: { type: Array, default: () => [] }, columns: { type: Array, default: () => [] }, stripedRows: { type: Boolean, default: false }, hoverRows: { type: Boolean, default: true }, size: { type: String, default: 'md' }, scrollable: { type: Boolean, default: true }, paginator: { type: Boolean, default: true }, rows: { type: Number, default: 10 }, first: { type: Number, default: 0 }, rowsPerPageOptions: { type: Array, default: () => [10, 20, 50] }, selectionMode: { type: String, default: '' }, selection: { type: null, default: null }, dataKey: { type: String, default: '' }, sortField: { type: String, default: '' }, sortOrder: { type: Number, default: 1 }, resizableColumns: { type: Boolean, default: true }, columnResizeMode: { type: String, default: 'fit' }, filterDisplay: { type: String, default: 'row' }, globalFilter: { type: String, default: '' }, globalFilterFields: { type: Array, default: null }, filters: { type: null, default: null }, lazy: { type: Boolean, default: false }, totalRecords: { type: Number, default: 0 }, emptyMessage: { type: String, default: 'No records found' }, showGridlines: { type: Boolean, default: false } })
5
+ const emit = defineEmits(['update:first', 'update:rows', 'update:selection', 'update:sortField', 'update:sortOrder', 'update:globalFilter', 'update:filters', 'updateFirst', 'updateRows', 'updateSelection', 'updateSortField', 'updateSortOrder', 'updateGlobalFilter', 'updateFilters', 'page', 'rowSelect', 'rowUnselect', 'sort', 'filter'])
6
+ const internalFirst = signal(props.first || 0)
7
+ const internalRows = signal(props.rows || 10)
8
+ const internalSortField = signal(props.sortField || '')
9
+ const internalSortOrder = signal(props.sortOrder || 1)
10
+ const internalFilters = signal((props.filters && typeof props.filters === 'object') ? props.filters : { __global: props.globalFilter || '' })
11
+ const effectiveFilters = computed(() => (props.filters && typeof props.filters === 'object') ? props.filters : internalFilters.value)
12
+ const columnWidths = signal({})
13
+ const resizing = signal(null)
14
+ const internalSelection = signal(props.selection)
15
+
16
+ effect(() => { internalFirst.value = props.first || 0 })
17
+ effect(() => { internalRows.value = props.rows || 10 })
18
+ effect(() => { internalSortField.value = props.sortField || '' })
19
+ effect(() => { internalSortOrder.value = props.sortOrder || 1 })
20
+ let lastPropGlobal = props.globalFilter || ''
21
+ effect(() => {
22
+ const next = props.globalFilter || ''
23
+ if (next === lastPropGlobal) return
24
+ lastPropGlobal = next
25
+ if (props.filters && typeof props.filters === 'object') return
26
+ internalFilters.value = { ...internalFilters.value, __global: next }
27
+ })
28
+
29
+ let lastPropSelection = props.selection
30
+ effect(() => {
31
+ const next = props.selection
32
+ if (next === lastPropSelection) return
33
+ lastPropSelection = next
34
+ internalSelection.value = next
35
+ })
36
+ const normalizeColumns = computed(() => (props.columns || []).map((c, idx) => { const field = c.field || `col_${idx}`; const header = c.header ?? field; const sortable = !!c.sortable; const filterable = c.filterable !== false; const type = c.type || 'text'; const width = c.width || ''; const minWidth = c.minWidth || '120px'; const align = c.align || (type === 'number' ? 'right' : 'left'); const filterMatchMode = c.filterMatchMode || 'contains'; return { ...c, field, header, sortable, filterable, type, width, minWidth, align, filterMatchMode } }))
37
+ const getRowKey = (row, index) => (props.dataKey && row && typeof row === 'object' && props.dataKey in row) ? String(row[props.dataKey]) : String(index)
38
+ const effectiveSelection = computed(() => internalSelection.value)
39
+ const isRowSelected = (row, index) => {
40
+ if (!props.selectionMode) return false
41
+ const selection = effectiveSelection.value
42
+ const key = getRowKey(row, index)
43
+ if (props.selectionMode === 'single') {
44
+ if (!selection) return false
45
+ if (props.dataKey && typeof selection === 'object' && selection && props.dataKey in selection) return String(selection[props.dataKey]) === key
46
+ return selection === row
47
+ }
48
+ const current = Array.isArray(selection) ? selection : []
49
+ if (props.dataKey) return current.some(s => s && typeof s === 'object' && props.dataKey in s && String(s[props.dataKey]) === key)
50
+ return current.includes(row)
51
+ }
52
+ const toggleRowSelection = (row, index) => {
53
+ if (!props.selectionMode) return
54
+ const selected = isRowSelected(row, index)
55
+ if (props.selectionMode === 'single') {
56
+ const next = selected ? null : row
57
+ internalSelection.value = next
58
+ emit('update:selection', next)
59
+ emit('updateSelection', next)
60
+ emit(selected ? 'rowUnselect' : 'rowSelect', { data: row })
61
+ return
62
+ }
63
+ const current = Array.isArray(effectiveSelection.value) ? effectiveSelection.value : []
64
+ const key = getRowKey(row, index)
65
+ const next = selected
66
+ ? (props.dataKey ? current.filter(r => String(r?.[props.dataKey]) !== key) : current.filter(r => r !== row))
67
+ : [...current, row]
68
+ internalSelection.value = next
69
+ emit('update:selection', next)
70
+ emit('updateSelection', next)
71
+ emit(selected ? 'rowUnselect' : 'rowSelect', { data: row })
72
+ }
73
+
74
+ const allVisibleSelected = computed(() => {
75
+ if (props.selectionMode !== 'multiple') return false
76
+ const rows = visibleRows.value
77
+ if (rows.length === 0) return false
78
+ for (let i = 0; i < rows.length; i++) {
79
+ if (!isRowSelected(rows[i], i + (internalFirst.value || 0))) return false
80
+ }
81
+ return true
82
+ })
83
+
84
+ const toggleAllVisible = () => {
85
+ if (props.selectionMode !== 'multiple') return
86
+ const current = Array.isArray(effectiveSelection.value) ? effectiveSelection.value : []
87
+ const rows = visibleRows.value
88
+ const startIndex = internalFirst.value || 0
89
+ if (rows.length === 0) return
90
+ if (allVisibleSelected.value) {
91
+ if (!props.dataKey) {
92
+ const next = current.filter(r => !rows.includes(r))
93
+ internalSelection.value = next
94
+ emit('update:selection', next)
95
+ emit('updateSelection', next)
96
+ return
97
+ }
98
+ const keys = rows.map((r, i) => getRowKey(r, startIndex + i))
99
+ const next = current.filter(r => !keys.includes(String(r?.[props.dataKey])))
100
+ internalSelection.value = next
101
+ emit('update:selection', next)
102
+ emit('updateSelection', next)
103
+ return
104
+ }
105
+ if (!props.dataKey) {
106
+ const next = [...current, ...rows.filter(r => !current.includes(r))]
107
+ internalSelection.value = next
108
+ emit('update:selection', next)
109
+ emit('updateSelection', next)
110
+ return
111
+ }
112
+ const existing = new Set(current.map(r => String(r?.[props.dataKey])))
113
+ const next = [...current, ...rows.filter((r) => !existing.has(String(r?.[props.dataKey])))]
114
+ internalSelection.value = next
115
+ emit('update:selection', next)
116
+ emit('updateSelection', next)
117
+ }
118
+ const getHeaderContent = (col) => (col.headerTemplate && typeof col.headerTemplate === 'function') ? col.headerTemplate(col) : col.header
119
+ const getRawValue = (row, col) => { if (!row) return ''; if (col.field && typeof row === 'object' && col.field in row) return row[col.field]; return '' }
120
+ const getCellContent = (row, col, index) => { const raw = getRawValue(row, col); if (col.body && typeof col.body === 'function') return col.body(row, { index, column: col, field: col.field, value: raw }); return raw }
121
+ const matchFilter = (value, query, mode) => { const q = String(query ?? '').toLowerCase().trim(); if (!q) return true; const v = String(value ?? '').toLowerCase(); if (mode === 'equals') return v === q; if (mode === 'startsWith') return v.startsWith(q); if (mode === 'endsWith') return v.endsWith(q); return v.includes(q) }
122
+ const filteredRows = computed(() => { const rows = props.value || []; if (props.lazy) return rows; const cols = normalizeColumns.value; const filters = effectiveFilters.value || {}; const global = String(filters.__global || '').trim(); const keys = Object.keys(filters).filter(k => k !== '__global'); if (!global && keys.length === 0) return rows; const globalFields = Array.isArray(props.globalFilterFields) && props.globalFilterFields.length ? props.globalFilterFields : cols.map(c => c.field); return rows.filter((row) => { if (global) { const ok = globalFields.some((f) => matchFilter(getRawValue(row, { field: f }), global, 'contains')); if (!ok) return false } for (const c of cols) { const f = filters[c.field]; if (!f) continue; if (!matchFilter(getRawValue(row, c), f.value, f.matchMode || c.filterMatchMode || 'contains')) return false } return true }) })
123
+ const sortedRows = computed(() => { if (props.lazy) return filteredRows.value; const rows = [...filteredRows.value]; const field = internalSortField.value; if (!field) return rows; const col = normalizeColumns.value.find(c => c.field === field); const order = internalSortOrder.value === -1 ? -1 : 1; const type = col?.type || 'text'; rows.sort((a, b) => { const av = getCellContent(a, { field }, -1); const bv = getCellContent(b, { field }, -1); if (type === 'number') return (Number(av) - Number(bv)) * order; return String(av ?? '').localeCompare(String(bv ?? ''), undefined, { numeric: true, sensitivity: 'base' }) * order }); return rows })
124
+ const totalRecords = computed(() => props.lazy ? (props.totalRecords || (props.value || []).length) : sortedRows.value.length)
125
+ const visibleRows = computed(() => props.lazy ? (props.value || []) : (!props.paginator ? sortedRows.value : sortedRows.value.slice(internalFirst.value || 0, (internalFirst.value || 0) + (internalRows.value || 10))))
126
+ const setSort = (col) => {
127
+ if (!col.sortable) return
128
+ const nextField = col.field
129
+ let nextOrder = 1
130
+ if (internalSortField.value === nextField) nextOrder = internalSortOrder.value === 1 ? -1 : 1
131
+ internalSortField.value = nextField
132
+ internalSortOrder.value = nextOrder
133
+ internalFirst.value = 0
134
+ emit('update:sortField', nextField)
135
+ emit('update:sortOrder', nextOrder)
136
+ emit('updateSortField', nextField)
137
+ emit('updateSortOrder', nextOrder)
138
+ emit('sort', { sortField: nextField, sortOrder: nextOrder })
139
+ }
140
+ const onSortClick = (e) => { const field = e.currentTarget?.dataset?.field; if (!field) return; const col = normalizeColumns.value.find(c => c.field === field); if (!col) return; setSort(col) }
141
+ const setGlobal = (e) => {
142
+ const next = e.target.value
143
+ const nextFilters = { ...(effectiveFilters.value || {}), __global: next }
144
+ if (props.filters && typeof props.filters === 'object') { emit('update:filters', nextFilters); emit('updateFilters', nextFilters) } else { internalFilters.value = nextFilters }
145
+ internalFirst.value = 0
146
+ emit('update:globalFilter', next)
147
+ emit('updateGlobalFilter', next)
148
+ emit('filter', { global: next, filters: nextFilters })
149
+ }
150
+ const setColumnFilter = (field, e) => { const next = e.target.value; const current = { ...(effectiveFilters.value || {}) }; const col = normalizeColumns.value.find(c => c.field === field); if (!next) delete current[field]; else current[field] = { value: next, matchMode: col?.filterMatchMode || current[field]?.matchMode || 'contains' }; if (props.filters && typeof props.filters === 'object') { emit('update:filters', current); emit('updateFilters', current) } else { internalFilters.value = current } internalFirst.value = 0; emit('filter', { field, value: next, filters: current }) }
151
+ const onColumnFilterInput = (e) => { const field = e.target?.dataset?.field; if (!field) return; setColumnFilter(field, e) }
152
+ const onPage = ({ first, rows }) => {
153
+ internalFirst.value = first
154
+ internalRows.value = rows
155
+ emit('update:first', first)
156
+ emit('update:rows', rows)
157
+ emit('updateFirst', first)
158
+ emit('updateRows', rows)
159
+ emit('page', { first, rows })
160
+ }
161
+ const getWidth = (col) => columnWidths.value[col.field] || col.width || ''
162
+ const startResize = (e) => { if (!props.resizableColumns) return; const field = e.currentTarget?.dataset?.field; if (!field) return; e.preventDefault(); e.stopPropagation(); const th = e.currentTarget?.parentElement; const base = th?.getBoundingClientRect?.().width || parseFloat(th?.style?.width || '') || 160; resizing.value = { field, startX: e.clientX, startW: base }; const onMove = (ev) => { if (!resizing.value) return; const delta = ev.clientX - resizing.value.startX; const next = Math.max(80, resizing.value.startW + delta); columnWidths.value = { ...columnWidths.value, [resizing.value.field]: `${Math.round(next)}px` } }; const onUp = () => { document.removeEventListener('mousemove', onMove); document.removeEventListener('mouseup', onUp); resizing.value = null }; document.addEventListener('mousemove', onMove); document.addEventListener('mouseup', onUp) }
163
+ </script>
164
+
165
+ <template>
166
+ <div class="n-dt" :class="[`n-dt-${size}`, showGridlines ? 'is-grid' : '', scrollable ? 'is-scroll' : '']">
167
+ <div class="n-dt-toolbar"><div class="n-dt-global"><input class="n-dt-global-input" :value="effectiveFilters.value.__global || ''" placeholder="Search..." @input="setGlobal" /></div></div>
168
+ <div class="n-dt-wrapper">
169
+ <table class="n-dt-table" :class="{ 'is-striped': stripedRows, 'is-hover': hoverRows }">
170
+ <thead class="n-dt-thead">
171
+ <tr class="n-dt-head-row"><th v-if="selectionMode" class="n-dt-th is-selection"><input v-if="selectionMode === 'multiple'" class="n-dt-selectbox" type="checkbox" :checked="allVisibleSelected.value" @click.stop="toggleAllVisible" /></th><th v-for="col in normalizeColumns.value" :key="col.field" class="n-dt-th" :class="[`is-${col.align}`, col.sortable ? 'is-sortable' : '']" :style="{ width: getWidth(col) || undefined, minWidth: col.minWidth }" :data-field="col.field" @click="onSortClick"><div class="n-dt-th-content"><span class="n-dt-th-text">{{ getHeaderContent(col) }}</span><span v-if="col.sortable" class="n-dt-sort" :class="{ 'is-active': internalSortField.value === col.field }"><span v-if="internalSortField.value !== col.field" class="n-dt-sort-icon">↕</span><span v-else class="n-dt-sort-icon">{{ internalSortOrder.value === 1 ? '↑' : '↓' }}</span></span></div><span v-if="resizableColumns" class="n-dt-resizer" :data-field="col.field" @mousedown="startResize"></span></th></tr>
172
+ <tr v-if="filterDisplay === 'row'" class="n-dt-filter-row"><th v-if="selectionMode" class="n-dt-th is-selection"></th><th v-for="col in normalizeColumns.value" :key="col.field" class="n-dt-th" :class="`is-${col.align}`" :style="{ width: getWidth(col) || undefined, minWidth: col.minWidth }"><input v-if="col.filterable" class="n-dt-filter" :data-field="col.field" :value="(effectiveFilters.value[col.field]?.value) || ''" placeholder="Filter" @input="onColumnFilterInput" /></th></tr>
173
+ </thead>
174
+ <tbody class="n-dt-tbody">
175
+ <tr v-for="(row, i) in visibleRows.value" :key="getRowKey(row, i + internalFirst.value)" class="n-dt-row" :class="{ 'is-selected': isRowSelected(row, i + internalFirst.value) }" @click="toggleRowSelection(row, i + internalFirst.value)"><td v-if="selectionMode" class="n-dt-td is-selection"><input class="n-dt-selectbox" type="checkbox" :checked="isRowSelected(row, i + internalFirst.value)" @click.stop="toggleRowSelection(row, i + internalFirst.value)" /></td><td v-for="col in normalizeColumns.value" :key="col.field" class="n-dt-td" :class="`is-${col.align}`" :style="{ width: getWidth(col) || undefined, minWidth: col.minWidth }">{{ getCellContent(row, col, i + internalFirst.value) }}</td></tr>
176
+ <tr v-if="visibleRows.value.length === 0" class="n-dt-empty-row"><td :colspan="normalizeColumns.value.length + (selectionMode ? 1 : 0)" class="n-dt-empty">{{ emptyMessage }}</td></tr>
177
+ </tbody>
178
+ </table>
179
+ </div>
180
+ <NPaginator v-if="paginator" :first="internalFirst.value" :rows="internalRows.value" :totalRecords="totalRecords.value" :rowsPerPageOptions="rowsPerPageOptions" @page="onPage" />
181
+ </div>
182
+ </template>
183
+
184
+ <style scoped>
185
+ .n-dt{border:1px solid var(--n-color-border);border-radius:var(--n-radius-lg);background:var(--n-color-surface);overflow:hidden;font-family:var(--n-font-sans)}.n-dt-toolbar{display:flex;align-items:center;justify-content:space-between;padding:var(--n-space-3) var(--n-space-4);border-bottom:1px solid var(--n-color-border);background:linear-gradient(180deg,rgba(255,255,255,.04),rgba(0,0,0,.08))}.n-dt-global{display:flex;align-items:center;gap:var(--n-space-2)}.n-dt-global-input{width:280px;max-width:100%;background:var(--n-color-bg);border:1px solid var(--n-color-border);border-radius:var(--n-radius-md);padding:0.55rem 0.75rem;color:var(--n-color-text);font-size:var(--n-text-sm);outline:none;box-sizing:border-box}.n-dt-global-input:focus{border-color:var(--n-color-primary);box-shadow:0 0 0 3px var(--n-color-primary-light)}.n-dt-wrapper{width:100%;overflow:auto}.n-dt-table{width:100%;border-collapse:separate;border-spacing:0;table-layout:fixed}.n-dt-thead{background:var(--n-color-surface)}.n-dt-th,.n-dt-td{padding:0.75rem 0.9rem;border-bottom:1px solid var(--n-color-border);color:var(--n-color-text);font-size:var(--n-text-sm);vertical-align:middle}.n-dt-th{position:relative;overflow:hidden;background:rgba(0,0,0,.10);color:var(--n-color-text-secondary);font-weight:var(--n-weight-semibold);user-select:none}.n-dt-th.is-sortable{cursor:pointer}.n-dt-th-content{display:flex;align-items:center;justify-content:space-between;gap:0.5rem}.n-dt-sort{display:inline-flex;align-items:center;gap:0.25rem;color:var(--n-color-text-muted)}.n-dt-sort.is-active{color:var(--n-color-primary)}.n-dt-resizer{position:absolute;right:0;top:0;bottom:0;width:8px;cursor:col-resize}.n-dt-filter-row .n-dt-th{background:rgba(0,0,0,.06)}.n-dt-filter{width:100%;max-width:100%;display:block;background:var(--n-color-bg);border:1px solid var(--n-color-border);border-radius:var(--n-radius-sm);padding:0.35rem 0.5rem;color:var(--n-color-text);font-size:var(--n-text-xs);outline:none;box-sizing:border-box}.n-dt-filter:focus{border-color:var(--n-color-primary)}.n-dt-td{background:transparent;color:var(--n-color-text)}.n-dt-row.is-selected .n-dt-td{background:rgba(59,130,246,.12)}.n-dt-table.is-striped .n-dt-row:nth-child(even) .n-dt-td{background:rgba(0,0,0,.06)}.n-dt-table.is-hover .n-dt-row:hover .n-dt-td{background:rgba(255,255,255,.06)}.n-dt-empty{text-align:center;color:var(--n-color-text-muted);padding:1.25rem}.is-left{text-align:left}.is-right{text-align:right}.is-center{text-align:center}.is-selection{width:44px;min-width:44px;max-width:44px;text-align:center}.n-dt-selectbox{width:16px;height:16px;accent-color:var(--n-color-primary)}.n-dt-check{display:inline-flex;align-items:center;justify-content:center;width:18px;height:18px;border-radius:4px;border:1px solid var(--n-color-border);color:var(--n-color-primary)}.is-grid .n-dt-td,.is-grid .n-dt-th{border-right:1px solid var(--n-color-border)}.is-grid .n-dt-td:last-child,.is-grid .n-dt-th:last-child{border-right:none}.n-dt-sm .n-dt-th,.n-dt-sm .n-dt-td{padding:0.55rem 0.7rem}.n-dt-lg .n-dt-th,.n-dt-lg .n-dt-td{padding:0.9rem 1rem}
186
+ </style>