sqlite-hub 0.9.1 → 0.9.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -29,7 +29,9 @@ const DEFAULT_SETTINGS = {
29
29
  maxPageSize: 200,
30
30
  csvDelimiter: ',',
31
31
  };
32
+ const DEFAULT_DATA_PAGE_SIZE = 50;
32
33
  const DATA_PAGE_SIZES = [25, 50, 100];
34
+ const DATA_ROW_SIZE_STORAGE_KEY = 'data_row_size';
33
35
  const QUERY_HISTORY_PAGE_SIZE = 30;
34
36
  const QUERY_HISTORY_RUN_LIMIT = 8;
35
37
  const CHART_HEIGHT_PRESETS = new Set(['small', 'medium', 'large']);
@@ -46,6 +48,22 @@ let chartsLoadVersion = 0;
46
48
  let chartsDetailLoadVersion = 0;
47
49
  let mediaTaggingPreviewVersion = 0;
48
50
 
51
+ function readStoredDataPageSize(fallback = DEFAULT_DATA_PAGE_SIZE) {
52
+ try {
53
+ return normalizeDataPageSize(globalThis.localStorage?.getItem(DATA_ROW_SIZE_STORAGE_KEY), fallback);
54
+ } catch {
55
+ return fallback;
56
+ }
57
+ }
58
+
59
+ function storeDataPageSize(pageSize) {
60
+ try {
61
+ globalThis.localStorage?.setItem(DATA_ROW_SIZE_STORAGE_KEY, String(pageSize));
62
+ } catch {
63
+ // Ignore unavailable browser storage; the in-memory setting still applies.
64
+ }
65
+ }
66
+
49
67
  const state = {
50
68
  ready: false,
51
69
  route: { name: 'landing', path: '/', params: {} },
@@ -79,7 +97,7 @@ const state = {
79
97
  saving: false,
80
98
  deleting: false,
81
99
  page: 1,
82
- pageSize: 50,
100
+ pageSize: readStoredDataPageSize(),
83
101
  sortColumn: null,
84
102
  sortDirection: null,
85
103
  searchQuery: '',
@@ -124,9 +142,11 @@ const state = {
124
142
  saveError: null,
125
143
  },
126
144
  charts: {
145
+ loaded: false,
127
146
  queries: [],
128
147
  loading: false,
129
148
  error: null,
149
+ historyTab: 'recent',
130
150
  selectedHistoryId: null,
131
151
  chartHeightPreset: 'medium',
132
152
  sqlExpanded: false,
@@ -666,9 +686,11 @@ function resetQueryHistoryState({ preserveSearch = true } = {}) {
666
686
  function resetChartsState() {
667
687
  chartsLoadVersion += 1;
668
688
  chartsDetailLoadVersion += 1;
689
+ state.charts.loaded = false;
669
690
  state.charts.queries = [];
670
691
  state.charts.loading = false;
671
692
  state.charts.error = null;
693
+ state.charts.historyTab = 'recent';
672
694
  state.charts.selectedHistoryId = null;
673
695
  state.charts.chartHeightPreset = 'medium';
674
696
  state.charts.sqlExpanded = false;
@@ -689,6 +711,10 @@ function normalizeChartsHeightPreset(value) {
689
711
  return CHART_HEIGHT_PRESETS.has(normalizedValue) ? normalizedValue : 'medium';
690
712
  }
691
713
 
714
+ function normalizeChartsHistoryTab(value) {
715
+ return ['recent', 'saved'].includes(value) ? value : 'recent';
716
+ }
717
+
692
718
  function mergeQueryHistoryItemWithChartSummary(updatedItem, fallbackItem = null) {
693
719
  if (!updatedItem) {
694
720
  return updatedItem;
@@ -714,20 +740,24 @@ function syncQueryHistoryItem(updatedItem) {
714
740
  return;
715
741
  }
716
742
 
717
- state.editor.history = state.editor.history.map(entry => (entry.id === updatedItem.id ? updatedItem : entry));
743
+ const updatedItemId = String(updatedItem.id);
718
744
 
719
- if (state.editor.historyDetail?.id === updatedItem.id) {
745
+ state.editor.history = state.editor.history.map(entry => (String(entry.id) === updatedItemId ? updatedItem : entry));
746
+
747
+ if (String(state.editor.historyDetail?.id ?? '') === updatedItemId) {
720
748
  state.editor.historyDetail = updatedItem;
721
749
  }
722
750
 
723
751
  const existingChartsEntry =
724
- state.charts.queries.find(entry => entry.id === updatedItem.id) ??
725
- (state.charts.detail?.item?.id === updatedItem.id ? state.charts.detail.item : null);
752
+ state.charts.queries.find(entry => String(entry.id) === updatedItemId) ??
753
+ (String(state.charts.detail?.item?.id ?? '') === updatedItemId ? state.charts.detail.item : null);
726
754
  const mergedChartsItem = mergeQueryHistoryItemWithChartSummary(updatedItem, existingChartsEntry);
727
755
 
728
- state.charts.queries = state.charts.queries.map(entry => (entry.id === updatedItem.id ? mergedChartsItem : entry));
756
+ state.charts.queries = state.charts.queries.map(entry =>
757
+ String(entry.id) === updatedItemId ? mergedChartsItem : entry,
758
+ );
729
759
 
730
- if (state.charts.detail?.item?.id === updatedItem.id) {
760
+ if (String(state.charts.detail?.item?.id ?? '') === updatedItemId) {
731
761
  state.charts.detail = {
732
762
  ...state.charts.detail,
733
763
  item: mergeQueryHistoryItemWithChartSummary(updatedItem, state.charts.detail.item),
@@ -1169,7 +1199,56 @@ async function loadChartsDetail(historyId) {
1169
1199
  }
1170
1200
  }
1171
1201
 
1172
- async function loadCharts(version, route) {
1202
+ function getRequestedChartsHistoryId(route) {
1203
+ const requestedHistoryId = Number(route.params?.historyId ?? null);
1204
+
1205
+ return Number.isInteger(requestedHistoryId) && requestedHistoryId > 0 ? requestedHistoryId : null;
1206
+ }
1207
+
1208
+ function resolveLoadableChartsHistoryId(route) {
1209
+ const requestedHistoryId = getRequestedChartsHistoryId(route);
1210
+
1211
+ if (!requestedHistoryId) {
1212
+ return null;
1213
+ }
1214
+
1215
+ return state.charts.queries.some(item => item.id === requestedHistoryId) ? requestedHistoryId : null;
1216
+ }
1217
+
1218
+ function hasSettledChartsDetail(historyId) {
1219
+ if (state.charts.detailLoading || state.charts.resultLoading) {
1220
+ return false;
1221
+ }
1222
+
1223
+ if (historyId === null) {
1224
+ return (
1225
+ state.charts.selectedHistoryId === null &&
1226
+ !state.charts.detail &&
1227
+ !state.charts.result &&
1228
+ !state.charts.detailError &&
1229
+ !state.charts.resultError
1230
+ );
1231
+ }
1232
+
1233
+ return (
1234
+ state.charts.selectedHistoryId === historyId &&
1235
+ (state.charts.detail?.item?.id === historyId || Boolean(state.charts.detailError)) &&
1236
+ (Boolean(state.charts.result) || Boolean(state.charts.resultError))
1237
+ );
1238
+ }
1239
+
1240
+ async function loadCharts(version, route, options = {}) {
1241
+ if (!options.force && state.charts.loaded) {
1242
+ const historyId = resolveLoadableChartsHistoryId(route);
1243
+
1244
+ if (hasSettledChartsDetail(historyId)) {
1245
+ return;
1246
+ }
1247
+
1248
+ await loadChartsDetail(historyId);
1249
+ return;
1250
+ }
1251
+
1173
1252
  state.charts.loading = true;
1174
1253
  state.charts.error = null;
1175
1254
  emitChange();
@@ -1182,6 +1261,7 @@ async function loadCharts(version, route) {
1182
1261
  }
1183
1262
 
1184
1263
  state.charts.queries = response.data ?? [];
1264
+ state.charts.loaded = true;
1185
1265
  state.charts.error = null;
1186
1266
  } catch (error) {
1187
1267
  if (version !== routeLoadVersion) {
@@ -1189,6 +1269,7 @@ async function loadCharts(version, route) {
1189
1269
  }
1190
1270
 
1191
1271
  state.charts.queries = [];
1272
+ state.charts.loaded = false;
1192
1273
  state.charts.error = normalizeError(error);
1193
1274
  } finally {
1194
1275
  if (version === routeLoadVersion) {
@@ -1201,11 +1282,7 @@ async function loadCharts(version, route) {
1201
1282
  return;
1202
1283
  }
1203
1284
 
1204
- const requestedHistoryId = Number(route.params?.historyId ?? null);
1205
- const canLoadRequestedHistory =
1206
- Number.isInteger(requestedHistoryId) && state.charts.queries.some(item => item.id === requestedHistoryId);
1207
-
1208
- await loadChartsDetail(canLoadRequestedHistory ? requestedHistoryId : null);
1285
+ await loadChartsDetail(resolveLoadableChartsHistoryId(route));
1209
1286
  }
1210
1287
 
1211
1288
  async function loadOverview(version) {
@@ -1280,7 +1357,7 @@ async function resolvePendingDataBrowserRow(version) {
1280
1357
 
1281
1358
  async function loadDataTable(version) {
1282
1359
  const tableName = state.dataBrowser.selectedTable;
1283
- const pageSize = normalizeDataPageSize(state.dataBrowser.pageSize, 50);
1360
+ const pageSize = normalizeDataPageSize(state.dataBrowser.pageSize, DEFAULT_DATA_PAGE_SIZE);
1284
1361
  const page = Math.max(1, Number(state.dataBrowser.page) || 1);
1285
1362
  const sortColumn = state.dataBrowser.sortColumn;
1286
1363
  const sortDirection = normalizeSortDirection(state.dataBrowser.sortDirection);
@@ -1799,7 +1876,7 @@ function invalidateDatabaseCaches() {
1799
1876
  state.mediaTagging.tagFormValues = {};
1800
1877
  }
1801
1878
 
1802
- async function loadRouteData(route) {
1879
+ async function loadRouteData(route, options = {}) {
1803
1880
  clearRouteSlices();
1804
1881
 
1805
1882
  if (requiresActiveDatabase(route.name) && !state.connections.active) {
@@ -1808,7 +1885,7 @@ async function loadRouteData(route) {
1808
1885
  return;
1809
1886
  }
1810
1887
 
1811
- if (isMediaTaggingRouteName(route.name) && hasLoadedMediaTaggingForActiveConnection()) {
1888
+ if (!options.force && isMediaTaggingRouteName(route.name) && hasLoadedMediaTaggingForActiveConnection()) {
1812
1889
  return;
1813
1890
  }
1814
1891
 
@@ -1827,7 +1904,7 @@ async function loadRouteData(route) {
1827
1904
  await loadData(version, route);
1828
1905
  return;
1829
1906
  case 'charts':
1830
- await loadCharts(version, route);
1907
+ await loadCharts(version, route, options);
1831
1908
  return;
1832
1909
  case 'editor':
1833
1910
  case 'editorResults':
@@ -2201,6 +2278,21 @@ export function setChartsHeightPreset(preset) {
2201
2278
  emitChange();
2202
2279
  }
2203
2280
 
2281
+ export function setChartsHistoryTab(tab) {
2282
+ const nextTab = normalizeChartsHistoryTab(
2283
+ String(tab ?? '')
2284
+ .trim()
2285
+ .toLowerCase(),
2286
+ );
2287
+
2288
+ if (state.charts.historyTab === nextTab) {
2289
+ return;
2290
+ }
2291
+
2292
+ state.charts.historyTab = nextTab;
2293
+ emitChange();
2294
+ }
2295
+
2204
2296
  export function updateCurrentQueryChartDraftField(field, value) {
2205
2297
  if (state.modal?.kind !== 'chart-editor' || !state.modal.draft) {
2206
2298
  return;
@@ -2625,16 +2717,25 @@ export async function runQueryHistoryItem(historyId) {
2625
2717
  return executeCurrentQuery();
2626
2718
  }
2627
2719
 
2628
- export async function toggleQueryHistorySavedState(historyId, nextValue) {
2720
+ export async function toggleQueryHistorySavedState(historyId, nextValue, options = {}) {
2629
2721
  try {
2630
2722
  const response = await api.toggleQueryHistorySaved(historyId, nextValue);
2631
2723
  syncQueryHistoryItem(response.data);
2632
- pushToast(response.message || 'Query save state updated.', 'muted');
2633
- await refreshQueryHistoryState();
2724
+ if (options.toast !== false) {
2725
+ pushToast(response.message || 'Query save state updated.', 'muted');
2726
+ }
2727
+ if (options.notify !== false) {
2728
+ emitChange();
2729
+ }
2730
+ if (options.refresh !== false) {
2731
+ await refreshQueryHistoryState();
2732
+ }
2634
2733
  return true;
2635
2734
  } catch (error) {
2636
2735
  state.editor.historyDetailError = normalizeError(error);
2637
- emitChange();
2736
+ if (options.notify !== false) {
2737
+ emitChange();
2738
+ }
2638
2739
  return false;
2639
2740
  }
2640
2741
  }
@@ -3340,6 +3441,7 @@ export async function setDataPageSize(pageSize) {
3340
3441
  }
3341
3442
 
3342
3443
  state.dataBrowser.pageSize = normalizedPageSize;
3444
+ storeDataPageSize(normalizedPageSize);
3343
3445
  state.dataBrowser.page = 1;
3344
3446
  clearDataBrowserRowSelectionState();
3345
3447
  state.dataBrowser.saveError = null;
@@ -3735,7 +3837,7 @@ export function sortEditorResultsByColumn(columnName) {
3735
3837
  }
3736
3838
 
3737
3839
  export async function refreshCurrentRoute() {
3738
- await loadRouteData(state.route);
3840
+ await loadRouteData(state.route, { force: true });
3739
3841
  }
3740
3842
 
3741
3843
  export function showToast(message, tone = 'muted') {
@@ -20,10 +20,12 @@ function renderMissingDatabase() {
20
20
  }
21
21
 
22
22
  function renderChartsList(state) {
23
- const queries = state.charts.queries ?? [];
23
+ const activeTab = ['recent', 'saved'].includes(state.charts.historyTab) ? state.charts.historyTab : 'recent';
24
+ const allQueries = state.charts.queries ?? [];
25
+ const queries = activeTab === 'saved' ? allQueries.filter(item => item.isSaved) : allQueries;
24
26
  const selectedHistoryId = Number(state.charts.selectedHistoryId);
25
27
 
26
- if (state.charts.loading && !queries.length) {
28
+ if (state.charts.loading && !allQueries.length) {
27
29
  return `
28
30
  <div class="flex h-full items-center justify-center px-6 text-center text-on-surface-variant/45">
29
31
  <div>
@@ -34,7 +36,7 @@ function renderChartsList(state) {
34
36
  `;
35
37
  }
36
38
 
37
- if (state.charts.error && !queries.length) {
39
+ if (state.charts.error && !allQueries.length) {
38
40
  return `
39
41
  <div class="p-5">
40
42
  <div class="border border-error/30 bg-error-container/20 px-4 py-4 text-sm text-error">
@@ -45,15 +47,23 @@ function renderChartsList(state) {
45
47
  }
46
48
 
47
49
  if (!queries.length) {
50
+ const isSavedTab = activeTab === 'saved';
51
+
48
52
  return `
49
53
  <div class="flex h-full items-center justify-center px-6 text-center">
50
54
  <div>
51
- <span class="material-symbols-outlined mb-3 text-4xl text-on-surface-variant/25">query_stats</span>
55
+ <span class="material-symbols-outlined mb-3 text-4xl text-on-surface-variant/25">${
56
+ isSavedTab ? 'bookmark' : 'query_stats'
57
+ }</span>
52
58
  <p class="font-headline text-lg font-black uppercase tracking-tight text-on-surface">
53
- No Chartable Queries
59
+ ${isSavedTab ? 'No Saved Charts Queries' : 'No Chartable Queries'}
54
60
  </p>
55
61
  <p class="mt-2 max-w-xs text-sm leading-6 text-on-surface-variant/60">
56
- Run SELECT queries in the SQL Editor first. They will appear here automatically.
62
+ ${
63
+ isSavedTab
64
+ ? 'Save chartable queries from this list or from the SQL Editor to keep them here.'
65
+ : 'Run SELECT queries in the SQL Editor first. They will appear here automatically.'
66
+ }
57
67
  </p>
58
68
  </div>
59
69
  </div>
@@ -66,36 +76,67 @@ function renderChartsList(state) {
66
76
  ${queries
67
77
  .map(
68
78
  item => `
69
- <button
70
- class="w-full border px-4 py-3 text-left transition-colors ${
71
- selectedHistoryId === item.id
79
+ <article
80
+ class="group w-full border transition-colors ${
81
+ selectedHistoryId === Number(item.id)
72
82
  ? 'border-primary-container/30 bg-surface-container-high'
73
83
  : 'border-outline-variant/10 bg-surface-container-lowest hover:bg-surface-container-high'
74
84
  }"
75
- data-action="navigate"
76
- data-to="/charts/${encodeURIComponent(item.id)}"
77
- type="button"
85
+ data-charts-history-item
78
86
  >
79
- <div class="flex items-start justify-between gap-3">
80
- <div class="min-w-0 flex-1 truncate font-mono text-xs ${
81
- selectedHistoryId === item.id ? 'text-primary-container' : 'text-on-surface'
82
- }">
83
- ${escapeHtml(item.displayTitle)}
87
+ <button
88
+ class="w-full px-4 py-3 text-left transition-colors group-hover:bg-surface-container-high"
89
+ data-action="navigate"
90
+ data-history-id="${escapeHtml(item.id)}"
91
+ data-to="/charts/${encodeURIComponent(item.id)}"
92
+ type="button"
93
+ >
94
+ <div class="flex items-start justify-between gap-3">
95
+ <div class="min-w-0 flex-1 truncate font-mono text-xs ${
96
+ selectedHistoryId === Number(item.id) ? 'text-primary-container' : 'text-on-surface'
97
+ }" data-charts-history-title>
98
+ ${escapeHtml(item.displayTitle)}
99
+ </div>
100
+ <div class="flex shrink-0 flex-wrap justify-end gap-1">
101
+ <span class="inline-flex" data-charts-saved-badge ${item.isSaved ? '' : 'hidden'}>
102
+ ${renderStatusBadge('saved', 'primary')}
103
+ </span>
104
+ ${
105
+ item.chartTypes?.length
106
+ ? item.chartTypes
107
+ .map(chartType => renderStatusBadge(getQueryChartTypeLabel(chartType), 'primary'))
108
+ .join('')
109
+ : renderStatusBadge('None', 'muted')
110
+ }
111
+ </div>
84
112
  </div>
85
- <div class="flex shrink-0 flex-wrap justify-end gap-1">
86
- ${
87
- item.chartTypes?.length
88
- ? item.chartTypes
89
- .map(chartType => renderStatusBadge(getQueryChartTypeLabel(chartType), 'primary'))
90
- .join('')
91
- : renderStatusBadge('None', 'muted')
92
- }
113
+ <div class="mt-1 truncate text-[10px] uppercase tracking-[0.16em] text-on-surface-variant/45">
114
+ ${escapeHtml(item.previewSql)}
93
115
  </div>
116
+ </button>
117
+ <div class="flex items-center justify-end border-t border-outline-variant/10 px-3 py-2 transition-colors group-hover:bg-surface-container-high">
118
+ <button
119
+ aria-label="Open chart query"
120
+ class="min-h-[var(--control-height)] flex-1 self-stretch"
121
+ data-action="navigate"
122
+ data-history-id="${escapeHtml(item.id)}"
123
+ data-to="/charts/${encodeURIComponent(item.id)}"
124
+ type="button"
125
+ ></button>
126
+ <button
127
+ class="query-history-icon-button ${item.isSaved ? 'is-active' : ''}"
128
+ data-action="toggle-charts-query-history-saved"
129
+ data-history-id="${escapeHtml(item.id)}"
130
+ data-next-value="${item.isSaved ? 'false' : 'true'}"
131
+ title="${item.isSaved ? 'Remove from saved' : 'Save query'}"
132
+ type="button"
133
+ >
134
+ <span class="material-symbols-outlined text-[18px]">
135
+ ${item.isSaved ? 'bookmark' : 'bookmark_add'}
136
+ </span>
137
+ </button>
94
138
  </div>
95
- <div class="mt-1 truncate text-[10px] uppercase tracking-[0.16em] text-on-surface-variant/45">
96
- ${escapeHtml(item.previewSql)}
97
- </div>
98
- </button>
139
+ </article>
99
140
  `,
100
141
  )
101
142
  .join('')}
@@ -104,6 +145,37 @@ function renderChartsList(state) {
104
145
  `;
105
146
  }
106
147
 
148
+ function renderChartsHistoryTabs(state) {
149
+ const activeTab = ['recent', 'saved'].includes(state.charts.historyTab) ? state.charts.historyTab : 'recent';
150
+ const savedCount = (state.charts.queries ?? []).filter(item => item.isSaved).length;
151
+ const tabs = [
152
+ { id: 'recent', label: 'Recent', count: state.charts.queries?.length ?? 0 },
153
+ { id: 'saved', label: 'Saved', count: savedCount },
154
+ ];
155
+
156
+ return `
157
+ <div class="mt-4 flex items-center gap-2">
158
+ ${tabs
159
+ .map(
160
+ tab => `
161
+ <button
162
+ class="query-history-tab ${activeTab === tab.id ? 'is-active' : ''}"
163
+ data-action="set-charts-history-tab"
164
+ data-tab="${escapeHtml(tab.id)}"
165
+ type="button"
166
+ >
167
+ ${escapeHtml(tab.label)}
168
+ </button>
169
+ `,
170
+ )
171
+ .join('')}
172
+ <span class="ml-auto text-[10px] font-mono uppercase tracking-[0.16em] text-on-surface-variant/50">
173
+ <span data-charts-history-count>${escapeHtml(String(tabs.find(tab => tab.id === activeTab)?.count ?? 0))}</span>
174
+ </span>
175
+ </div>
176
+ `;
177
+ }
178
+
107
179
  function renderEmptyChartDetail() {
108
180
  return `
109
181
  <div class="flex flex-1 items-center justify-center px-8 py-10 text-center">
@@ -347,7 +419,7 @@ function renderChartCard(chart, state, analysis) {
347
419
  `;
348
420
  }
349
421
 
350
- function renderChartsDetail(state) {
422
+ export function renderChartsDetail(state) {
351
423
  const detail = state.charts.detail;
352
424
  const selectedHistoryId = state.charts.selectedHistoryId;
353
425
  const historyVisible = state.editor.historyPanelVisible !== false;
@@ -485,6 +557,7 @@ export function renderChartsView(state) {
485
557
  <h2 class="mt-2 text-[10px] font-mono uppercase tracking-[0.16em] text-on-surface-variant/55">
486
558
  Charts
487
559
  </h2>
560
+ ${renderChartsHistoryTabs(state)}
488
561
  </div>
489
562
  ${renderChartsList(state)}
490
563
  </aside>
@@ -449,7 +449,36 @@ export function renderDataRowEditorPanel(state) {
449
449
  (foreignKey.mappings ?? []).map(mapping => String(mapping.from ?? '').trim()).filter(Boolean),
450
450
  ),
451
451
  );
452
- const getColumnBadges = column => (foreignKeyColumnNames.has(column.name) ? ['FK'] : []);
452
+ const getColumnTypeBadge = column => String(column.declaredType || column.affinity || 'BLOB').trim().toUpperCase();
453
+ const getColumnNumberInputMeta = column => {
454
+ const affinity = String(column.affinity ?? '').toUpperCase();
455
+
456
+ if (affinity === 'INTEGER') {
457
+ return { inputType: 'number', numberStep: '1' };
458
+ }
459
+
460
+ if (affinity === 'NUMERIC') {
461
+ return { inputType: 'number', numberStep: 'any' };
462
+ }
463
+
464
+ return {};
465
+ };
466
+ const getColumnBadges = column => {
467
+ const badges = [{ label: getColumnTypeBadge(column), tone: 'type' }];
468
+
469
+ if (column.primaryKeyPosition > 0) {
470
+ badges.push({
471
+ label: column.primaryKeyPosition > 1 ? `PK ${column.primaryKeyPosition}` : 'PK',
472
+ tone: 'primary-key',
473
+ });
474
+ }
475
+
476
+ if (foreignKeyColumnNames.has(column.name)) {
477
+ badges.push({ label: 'FK', tone: 'foreign-key' });
478
+ }
479
+
480
+ return badges;
481
+ };
453
482
 
454
483
  const identityColumns = table.identityStrategy?.type === 'primaryKey' ? (table.identityStrategy.columns ?? []) : [];
455
484
  const editableColumns = (table.columnMeta ?? []).filter(column => {
@@ -502,6 +531,7 @@ export function renderDataRowEditorPanel(state) {
502
531
  name: column.name,
503
532
  label: column.name,
504
533
  badges: getColumnBadges(column),
534
+ ...getColumnNumberInputMeta(column),
505
535
  value: value === null || value === undefined ? '' : String(value),
506
536
  };
507
537
  }),
@@ -265,6 +265,7 @@ export function renderEditorView(state, { isResultsRoute = false } = {}) {
265
265
  error: state.editor.historyError,
266
266
  activeTab: state.editor.historyTab,
267
267
  search: state.editor.historySearchInput,
268
+ committedSearch: state.editor.historySearch,
268
269
  total: state.editor.historyTotal,
269
270
  hasMore: state.editor.historyHasMore,
270
271
  activeHistoryId: state.editor.historyActiveId,
@@ -28,6 +28,26 @@ button {
28
28
  cursor: pointer;
29
29
  }
30
30
 
31
+ button:disabled,
32
+ button[aria-disabled='true'],
33
+ input:disabled,
34
+ select:disabled,
35
+ textarea:disabled {
36
+ cursor: not-allowed;
37
+ }
38
+
39
+ :where(a, button, input, select, textarea, [tabindex]:not([tabindex='-1'])):focus-visible {
40
+ outline: 2px solid var(--color-primary-container);
41
+ outline-offset: 2px;
42
+ }
43
+
44
+ .control-shell:focus-within,
45
+ .standard-checkbox:focus-within,
46
+ .sql-highlight-shell:focus-within {
47
+ border-color: var(--primary-alpha-35);
48
+ box-shadow: var(--focus-ring-inset);
49
+ }
50
+
31
51
  .control-button {
32
52
  align-items: center;
33
53
  box-sizing: border-box;
@@ -52,22 +72,22 @@ button {
52
72
 
53
73
  input.control-input--ghost {
54
74
  appearance: none;
55
- background: transparent !important;
56
- background-color: transparent !important;
57
- background-image: none !important;
58
- border: 0 !important;
59
- border-color: transparent !important;
60
- box-shadow: none !important;
75
+ background: transparent;
76
+ background-color: transparent;
77
+ background-image: none;
78
+ border: 0;
79
+ border-color: transparent;
80
+ box-shadow: none;
61
81
  -webkit-appearance: none;
62
82
  padding: 0;
63
83
  }
64
84
 
65
85
  input.control-input--ghost:focus,
66
86
  input.control-input--ghost:not(:focus) {
67
- background: transparent !important;
68
- background-color: transparent !important;
69
- border-color: transparent !important;
70
- box-shadow: none !important;
87
+ background: transparent;
88
+ background-color: transparent;
89
+ border-color: transparent;
90
+ box-shadow: none;
71
91
  outline: none;
72
92
  }
73
93
 
@@ -105,6 +125,7 @@ img {
105
125
  }
106
126
 
107
127
  [hidden] {
128
+ /* Hidden must beat utility display classes generated by Tailwind CDN. */
108
129
  display: none !important;
109
130
  }
110
131
 
@@ -118,9 +139,6 @@ img {
118
139
 
119
140
  .font-mono {
120
141
  font-family: var(--font-family-mono);
121
- overflow: hidden;
122
- white-space: nowrap;
123
- text-overflow: ellipsis;
124
142
  }
125
143
 
126
144
  .material-symbols-outlined {
@@ -218,12 +236,14 @@ img {
218
236
  width: 100%;
219
237
  word-break: normal;
220
238
  overflow-wrap: anywhere;
239
+ font-variant-ligatures: none;
221
240
  }
222
241
 
223
242
  .query-editor-highlight {
224
243
  color: var(--color-on-surface);
225
244
  overflow: visible;
226
245
  pointer-events: none;
246
+ user-select: none;
227
247
  }
228
248
 
229
249
  .query-editor-input {
@@ -236,6 +256,14 @@ img {
236
256
  overflow: auto;
237
257
  }
238
258
 
259
+ .query-editor-input:focus-visible {
260
+ outline: none;
261
+ }
262
+
263
+ .query-editor-layer:focus-within {
264
+ box-shadow: inset 0 0 0 1px var(--primary-alpha-30);
265
+ }
266
+
239
267
  .query-editor-gutter-track {
240
268
  min-height: 100%;
241
269
  will-change: transform;
@@ -260,3 +288,19 @@ img {
260
288
  border-color: var(--color-primary-container);
261
289
  }
262
290
  }
291
+
292
+ @media (prefers-reduced-motion: reduce) {
293
+ *,
294
+ *::before,
295
+ *::after {
296
+ animation-duration: 0.001ms;
297
+ animation-iteration-count: 1;
298
+ scroll-behavior: auto;
299
+ transition-duration: 0.001ms;
300
+ }
301
+
302
+ .cursor-blink {
303
+ animation: none;
304
+ border-right-color: var(--color-primary-container);
305
+ }
306
+ }