bitboss-ui 3.0.0-beta.8 → 3.0.0-beta.9
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.
- package/dist/ai/BbTable.md +391 -26
- package/dist/ai/changelog.json +8 -2
- package/dist/ai/components.json +37 -5
- package/dist/ai/guides/component-picker.md +2 -2
- package/dist/ai/guides/migration/components/bb-table.md +54 -14
- package/dist/ai/source/BbTable.md +1612 -155
- package/dist/components/BbTable/BbTable.vue.d.ts +3 -2
- package/dist/components/BbTable/BbTable.vue_vue_type_script_setup_true_lang.js +829 -408
- package/dist/components/BbTable/types.d.ts +135 -66
- package/dist/components/BbTable/utils.d.ts +53 -1
- package/dist/components/BbTable/utils.js +43 -20
- package/dist/i18n/locales/cs.json +4 -1
- package/dist/i18n/locales/da.json +4 -1
- package/dist/i18n/locales/de.json +4 -1
- package/dist/i18n/locales/el.json +4 -1
- package/dist/i18n/locales/en.json +4 -1
- package/dist/i18n/locales/es.json +4 -1
- package/dist/i18n/locales/fi.json +4 -1
- package/dist/i18n/locales/fr.json +4 -1
- package/dist/i18n/locales/hu.json +4 -1
- package/dist/i18n/locales/it.json +4 -1
- package/dist/i18n/locales/ja.json +4 -1
- package/dist/i18n/locales/ko.json +4 -1
- package/dist/i18n/locales/nb.json +4 -1
- package/dist/i18n/locales/nl.json +4 -1
- package/dist/i18n/locales/pl.json +4 -1
- package/dist/i18n/locales/pt.json +4 -1
- package/dist/i18n/locales/ro.json +4 -1
- package/dist/i18n/locales/ru.json +4 -1
- package/dist/i18n/locales/sv.json +4 -1
- package/dist/i18n/locales/tr.json +4 -1
- package/dist/i18n/locales/uk.json +4 -1
- package/dist/i18n/locales/zh-cn.json +4 -1
- package/dist/llms-full.txt +447 -42
- package/dist/llms-medium.txt +2 -2
- package/dist/locale-blueprint.json +4 -1
- package/dist/styles.css +1 -1
- package/package.json +1 -1
package/dist/ai/BbTable.md
CHANGED
|
@@ -25,8 +25,8 @@ keyboard navigation, so your markup stays a column definition plus a few slots.
|
|
|
25
25
|
domain objects straight to `items` and set `item-value` for stable row
|
|
26
26
|
identity — never pre-map rows into `{ label, value }` shapes.
|
|
27
27
|
|
|
28
|
-
The table **reports** state (sort, selection, page, expansion,
|
|
29
|
-
through v-models; it never
|
|
28
|
+
The table **reports** state (sort, column order, selection, page, expansion,
|
|
29
|
+
highlight) through v-models; it never sorts, slices, or fetches on its own. You apply
|
|
30
30
|
that state to your data source (client-side or via `dependencies`-driven
|
|
31
31
|
refetch). This keeps it equally at home in a client-only screen and a
|
|
32
32
|
server-paginated one.
|
|
@@ -711,8 +711,10 @@ scriptable from anywhere via `useBbTableContext`
|
|
|
711
711
|
### Sorting
|
|
712
712
|
|
|
713
713
|
Mark columns `sortable` and bind `v-model:sort` — an ordered
|
|
714
|
-
`BbTableSortEntry[]` (`[columnKey, 'asc' | 'desc']`).
|
|
715
|
-
|
|
714
|
+
`BbTableSortEntry[]` (`[columnKey, 'asc' | 'desc']`). A click anywhere on a
|
|
715
|
+
sortable header (its whitespace included — AG Grid parity; the built-in
|
|
716
|
+
button is the keyboard/AT control) cycles the column unsorted → asc → desc →
|
|
717
|
+
removed; a direction flip updates the entry in
|
|
716
718
|
place, so the column keeps its priority in a multi-column sort and other
|
|
717
719
|
entries are never touched. The array order **is** the priority: first entry
|
|
718
720
|
sorts first. The table only reports the model; **apply it to your data**
|
|
@@ -812,6 +814,217 @@ field only seeds the header's `aria-sort` when no live model entry exists — it
|
|
|
812
814
|
does not move the visual indicator; to start pre-sorted, seed `v-model:sort`
|
|
813
815
|
instead (as the example does).
|
|
814
816
|
|
|
817
|
+
### Column order
|
|
818
|
+
|
|
819
|
+
`columns` declares what a column **is**; `v-model:order` is the order
|
|
820
|
+
it **renders** in — an array of column keys, the same split as `sortable` +
|
|
821
|
+
`v-model:sort`. Two things write it:
|
|
822
|
+
|
|
823
|
+
- **Your own control** — a column panel, a settings drawer, a persisted
|
|
824
|
+
preference. Bind the model and write the array; the table follows. No flag
|
|
825
|
+
needed for this.
|
|
826
|
+
- **The user, on the table** — with `reorderable`, every data header
|
|
827
|
+
is a grab surface: drag it and the column moves live under the pointer (AG
|
|
828
|
+
Grid style, a chip with the label follows), release to commit. The same
|
|
829
|
+
header row is also one keyboard tab stop (the AG Grid grammar): ←/→ move
|
|
830
|
+
focus between headers, **Shift+←/→** move the column one slot
|
|
831
|
+
(Shift+Home/End to the ends), Enter sorts a sortable header.
|
|
832
|
+
Each committed move writes the model once and is announced.
|
|
833
|
+
|
|
834
|
+
The model's contract is what makes a column panel trivial:
|
|
835
|
+
|
|
836
|
+
- Keys listed first render first; declared columns the array omits follow, in
|
|
837
|
+
declaration order (a column added to `columns` later renders last).
|
|
838
|
+
- Keys that match no declared column render nothing but are **preserved in
|
|
839
|
+
place**. So hide a column by filtering it out of `columns` — never out of
|
|
840
|
+
the order — and it comes back exactly where it was.
|
|
841
|
+
- A user move relocates **exactly one key**; everything else, hidden keys
|
|
842
|
+
included, keeps its relative order. The first move on a partial (or empty)
|
|
843
|
+
model emits the completed array — persist that.
|
|
844
|
+
- A drop that changes nothing emits nothing; unbound, the table keeps the
|
|
845
|
+
order as per-mount state like `sort`.
|
|
846
|
+
|
|
847
|
+
**A column panel and the header drag sharing one order**
|
|
848
|
+
|
|
849
|
+
```vue
|
|
850
|
+
<template>
|
|
851
|
+
<div class="flex max-w-2xl flex-col gap-3">
|
|
852
|
+
<!-- ONE order, two writers: the panel's buttons and the table's own
|
|
853
|
+
header drag / keyboard handles both write `v-model:order`.
|
|
854
|
+
Hiding a column only filters `columns` — its key stays in the order,
|
|
855
|
+
so it comes back where it was. -->
|
|
856
|
+
<ul
|
|
857
|
+
aria-label="Columns"
|
|
858
|
+
class="flex flex-wrap gap-2 text-xs text-[color:var(--bb-text-muted)]"
|
|
859
|
+
>
|
|
860
|
+
<li
|
|
861
|
+
v-for="(column, index) in panelColumns"
|
|
862
|
+
:key="column.key"
|
|
863
|
+
class="flex items-center gap-1 rounded-[var(--bb-radius-sm)] border border-[color:var(--bb-border)] py-0.5 pl-2 pr-1"
|
|
864
|
+
>
|
|
865
|
+
<BbCheckbox
|
|
866
|
+
:label="column.label"
|
|
867
|
+
:model-value="!hidden.has(column.key)"
|
|
868
|
+
@update:model-value="toggleVisible(column.key)"
|
|
869
|
+
/>
|
|
870
|
+
<BbButton
|
|
871
|
+
:aria-label="`Move ${column.label} left`"
|
|
872
|
+
:disabled="index === 0"
|
|
873
|
+
icon="lucide:chevron-left"
|
|
874
|
+
size="xs"
|
|
875
|
+
variant="ghost"
|
|
876
|
+
@click="move(column.key, -1)"
|
|
877
|
+
/>
|
|
878
|
+
<BbButton
|
|
879
|
+
:aria-label="`Move ${column.label} right`"
|
|
880
|
+
:disabled="index === panelColumns.length - 1"
|
|
881
|
+
icon="lucide:chevron-right"
|
|
882
|
+
size="xs"
|
|
883
|
+
variant="ghost"
|
|
884
|
+
@click="move(column.key, 1)"
|
|
885
|
+
/>
|
|
886
|
+
</li>
|
|
887
|
+
</ul>
|
|
888
|
+
<div
|
|
889
|
+
class="overflow-hidden rounded-[var(--bb-radius)] border border-[color:var(--bb-border)]"
|
|
890
|
+
>
|
|
891
|
+
<BbTable
|
|
892
|
+
v-model:order="order"
|
|
893
|
+
caption="Invoices"
|
|
894
|
+
:columns="visibleColumns"
|
|
895
|
+
compact
|
|
896
|
+
item-value="id"
|
|
897
|
+
:items="invoices"
|
|
898
|
+
reorderable
|
|
899
|
+
/>
|
|
900
|
+
</div>
|
|
901
|
+
<p class="text-xs text-[color:var(--bb-text-muted)]">
|
|
902
|
+
Order model: <code>{{ JSON.stringify(order) }}</code>
|
|
903
|
+
</p>
|
|
904
|
+
</div>
|
|
905
|
+
</template>
|
|
906
|
+
<script setup lang="ts">
|
|
907
|
+
import { computed, ref } from 'vue';
|
|
908
|
+
import { BbButton, BbCheckbox, BbTable } from 'bitboss-ui';
|
|
909
|
+
import type { BbTableColumn } from 'bitboss-ui';
|
|
910
|
+
|
|
911
|
+
type Invoice = {
|
|
912
|
+
id: string;
|
|
913
|
+
number: string;
|
|
914
|
+
client: string;
|
|
915
|
+
issued: string;
|
|
916
|
+
amount: number;
|
|
917
|
+
};
|
|
918
|
+
|
|
919
|
+
const euro = new Intl.NumberFormat('en-IE', {
|
|
920
|
+
style: 'currency',
|
|
921
|
+
currency: 'EUR',
|
|
922
|
+
});
|
|
923
|
+
|
|
924
|
+
// The declaration: what a column IS. Never reordered.
|
|
925
|
+
const columns: Array<BbTableColumn<Invoice>> = [
|
|
926
|
+
{ key: 'number', label: 'Invoice', width: 110 },
|
|
927
|
+
{ key: 'client', label: 'Client' },
|
|
928
|
+
{ key: 'issued', label: 'Issued', width: 110 },
|
|
929
|
+
{
|
|
930
|
+
key: 'amount',
|
|
931
|
+
label: 'Amount',
|
|
932
|
+
align: 'right',
|
|
933
|
+
width: 110,
|
|
934
|
+
formatter: euro.format,
|
|
935
|
+
},
|
|
936
|
+
];
|
|
937
|
+
|
|
938
|
+
const invoices: Invoice[] = [
|
|
939
|
+
{
|
|
940
|
+
id: '1',
|
|
941
|
+
number: 'INV-1041',
|
|
942
|
+
client: 'Northwind Traders',
|
|
943
|
+
issued: '2026-08-02',
|
|
944
|
+
amount: 4200,
|
|
945
|
+
},
|
|
946
|
+
{
|
|
947
|
+
id: '2',
|
|
948
|
+
number: 'INV-1042',
|
|
949
|
+
client: 'Globex',
|
|
950
|
+
issued: '2026-08-05',
|
|
951
|
+
amount: 980,
|
|
952
|
+
},
|
|
953
|
+
{
|
|
954
|
+
id: '3',
|
|
955
|
+
number: 'INV-1043',
|
|
956
|
+
client: 'Initech',
|
|
957
|
+
issued: '2026-08-11',
|
|
958
|
+
amount: 15600,
|
|
959
|
+
},
|
|
960
|
+
{
|
|
961
|
+
id: '4',
|
|
962
|
+
number: 'INV-1044',
|
|
963
|
+
client: 'Umbrella',
|
|
964
|
+
issued: '2026-08-19',
|
|
965
|
+
amount: 2750,
|
|
966
|
+
},
|
|
967
|
+
];
|
|
968
|
+
|
|
969
|
+
// The state: render order (keys). Persist THIS. Seed it with the full key
|
|
970
|
+
// list — it also completes itself on the first move.
|
|
971
|
+
const order = ref<string[]>(columns.map((column) => column.key));
|
|
972
|
+
const hidden = ref(new Set<string>());
|
|
973
|
+
|
|
974
|
+
// Visibility is just filtering the declaration; the hidden key keeps its
|
|
975
|
+
// slot in `order`.
|
|
976
|
+
const visibleColumns = computed(() =>
|
|
977
|
+
columns.filter((column) => !hidden.value.has(column.key))
|
|
978
|
+
);
|
|
979
|
+
|
|
980
|
+
// The panel lists every declared column in the order the model has them.
|
|
981
|
+
const panelColumns = computed(() =>
|
|
982
|
+
[...columns].sort(
|
|
983
|
+
(a, b) => order.value.indexOf(a.key) - order.value.indexOf(b.key)
|
|
984
|
+
)
|
|
985
|
+
);
|
|
986
|
+
|
|
987
|
+
const toggleVisible = (key: string) => {
|
|
988
|
+
const next = new Set(hidden.value);
|
|
989
|
+
if (next.has(key)) next.delete(key);
|
|
990
|
+
else next.add(key);
|
|
991
|
+
hidden.value = next;
|
|
992
|
+
};
|
|
993
|
+
|
|
994
|
+
const move = (key: string, delta: 1 | -1) => {
|
|
995
|
+
const next = [...order.value];
|
|
996
|
+
const from = next.indexOf(key);
|
|
997
|
+
const to = from + delta;
|
|
998
|
+
if (from < 0 || to < 0 || to >= next.length) return;
|
|
999
|
+
next.splice(from, 1);
|
|
1000
|
+
next.splice(to, 0, key);
|
|
1001
|
+
order.value = next;
|
|
1002
|
+
};
|
|
1003
|
+
</script>
|
|
1004
|
+
```
|
|
1005
|
+
|
|
1006
|
+
**Pinned slots are positions, not columns.** `fixed-columns` indexes rendered
|
|
1007
|
+
slots like a spreadsheet's freeze panes (a selection column is slot 0). With
|
|
1008
|
+
column order the slot holds: whichever column lands in it is pinned, and
|
|
1009
|
+
dragging the identity column out of its pinned slot (slot 1 when
|
|
1010
|
+
`selectable`, slot 0 otherwise) unpins it (AG Grid pins columns; BbTable
|
|
1011
|
+
pins positions). Keep the reorder + pin combination for slots that
|
|
1012
|
+
never move — `select`, `actions`, or a first column you also lock by
|
|
1013
|
+
normalizing the model in your `@update:order` handler.
|
|
1014
|
+
|
|
1015
|
+
**Nested tables.** An inheriting child aligns to the parent's rails by
|
|
1016
|
+
**position**, so a reordered parent re-pairs the child's columns unless the
|
|
1017
|
+
child follows. Give both tables the same `v-model:order` when they
|
|
1018
|
+
share keys (and the child declares no `snap`) and they stay paired by key.
|
|
1019
|
+
`snap` points are positional in the parent's rendered order: reorder the
|
|
1020
|
+
parent and a snap-mapped child follows its slots, but never reorder (or share
|
|
1021
|
+
the order with) the snap-mapped child itself — dev builds warn.
|
|
1022
|
+
|
|
1023
|
+
Apply the model synchronously in your handler and persist afterwards: an
|
|
1024
|
+
`await` before the write moves the cell in a later flush, after the table
|
|
1025
|
+
has re-focused the handle, and keyboard focus is usually lost (whether it
|
|
1026
|
+
survives depends on which cell Vue's keyed patch detaches).
|
|
1027
|
+
|
|
815
1028
|
### Selection and bulk actions
|
|
816
1029
|
|
|
817
1030
|
Add `selectable` for multi-select (`multiple` is the default) bound with
|
|
@@ -1311,6 +1524,99 @@ fixed first track).
|
|
|
1311
1524
|
The actions-column toggle remains valid — prefer it when expansion reveals
|
|
1312
1525
|
_detail about the row_ rather than _children of it_.
|
|
1313
1526
|
|
|
1527
|
+
### Column resize
|
|
1528
|
+
|
|
1529
|
+
`resizable` puts a handle on the trailing edge of every data header:
|
|
1530
|
+
drag it and the column follows live, release to commit; double-click it to go
|
|
1531
|
+
back to the declared width. From the keyboard, **Alt+←/→** resizes the
|
|
1532
|
+
focused header in 10px steps (the header row is a tab stop whenever columns
|
|
1533
|
+
are resizable or reorderable).
|
|
1534
|
+
|
|
1535
|
+
There is no width model on purpose. `width` on the column definition stays
|
|
1536
|
+
the single declared truth, and a user resize is an **override** the table
|
|
1537
|
+
keeps until that column's `width` changes:
|
|
1538
|
+
|
|
1539
|
+
- Each commit fires `@resize:column="(key, width) => …"` **once** with the new width
|
|
1540
|
+
in px — or `null` after a double-click reset.
|
|
1541
|
+
- To persist, write the reported width back into your column definition (a
|
|
1542
|
+
`computed` over your persisted widths). The definition wins whenever it
|
|
1543
|
+
changes, so a programmatic `width` change always takes effect.
|
|
1544
|
+
- Unbound, the override lives for the mount, like an unbound `sort`.
|
|
1545
|
+
- A resized width is always px; `width` still accepts `'20%'` / `'12rem'`
|
|
1546
|
+
for what you declare yourself.
|
|
1547
|
+
|
|
1548
|
+
Headers never wrap and never floor their column: squeezed, a header label
|
|
1549
|
+
ellipsizes and the sort arrow stays; at the narrowest only the arrow (and
|
|
1550
|
+
the handle) survive, and the column is still resizable. The data cells of a
|
|
1551
|
+
resized column clip with an ellipsis instead of spilling into the neighbour
|
|
1552
|
+
(a squeezed track cannot wrap a single word); every other cell keeps
|
|
1553
|
+
wrapping. While a resize runs the whole page shows the resize cursor.
|
|
1554
|
+
|
|
1555
|
+
A resized column becomes an **exact** track; the other columns keep doing
|
|
1556
|
+
what they did — unfrozen ones share the surplus, and a table whose columns
|
|
1557
|
+
are all declared with a `width` keeps sharing it among the un-resized ones —
|
|
1558
|
+
so a filling table stays filled and nothing collapses. **Shift+drag** moves
|
|
1559
|
+
the border instead: the right neighbour absorbs the delta, the total stays
|
|
1560
|
+
put, and both columns are reported. A table that inherits its widths from a
|
|
1561
|
+
parent (`inherit-column-widths`) renders no handles — its tracks are the
|
|
1562
|
+
parent's.
|
|
1563
|
+
|
|
1564
|
+
**Resizable columns reporting widths back into the definition**
|
|
1565
|
+
|
|
1566
|
+
```vue
|
|
1567
|
+
<template>
|
|
1568
|
+
<div class="flex max-w-2xl flex-col gap-1.5">
|
|
1569
|
+
<div
|
|
1570
|
+
class="overflow-hidden rounded-[var(--bb-radius)] border border-[color:var(--bb-border)]"
|
|
1571
|
+
>
|
|
1572
|
+
<BbTable
|
|
1573
|
+
caption="Invoices"
|
|
1574
|
+
:columns="columns"
|
|
1575
|
+
compact
|
|
1576
|
+
item-value="id"
|
|
1577
|
+
:items="invoices"
|
|
1578
|
+
resizable
|
|
1579
|
+
@resize="onResize"
|
|
1580
|
+
/>
|
|
1581
|
+
</div>
|
|
1582
|
+
<p class="text-xs text-[color:var(--bb-text-muted)]">
|
|
1583
|
+
Widths: <code>{{ JSON.stringify(widths) }}</code>
|
|
1584
|
+
</p>
|
|
1585
|
+
</div>
|
|
1586
|
+
</template>
|
|
1587
|
+
<script setup lang="ts">
|
|
1588
|
+
import { computed, ref } from 'vue';
|
|
1589
|
+
import { BbTable } from 'bitboss-ui';
|
|
1590
|
+
import type { BbTableColumn } from 'bitboss-ui';
|
|
1591
|
+
|
|
1592
|
+
type Invoice = { id: string; number: string; client: string; amount: number };
|
|
1593
|
+
|
|
1594
|
+
const base: Array<BbTableColumn<Invoice>> = [
|
|
1595
|
+
{ key: 'number', label: 'Invoice' },
|
|
1596
|
+
{ key: 'client', label: 'Client' },
|
|
1597
|
+
{ key: 'amount', label: 'Amount', align: 'right' },
|
|
1598
|
+
];
|
|
1599
|
+
// Persist THIS (localStorage, a user preference…): key → px.
|
|
1600
|
+
const widths = ref<Record<string, number>>({});
|
|
1601
|
+
// The definition carries the persisted widths; the table treats them as the
|
|
1602
|
+
// declared truth, so nothing jumps after a reload.
|
|
1603
|
+
const columns = computed(() =>
|
|
1604
|
+
base.map((column) => ({ ...column, width: widths.value[column.key] }))
|
|
1605
|
+
);
|
|
1606
|
+
const onResize = (key: string, width: number | null) => {
|
|
1607
|
+
const next = { ...widths.value };
|
|
1608
|
+
if (width === null) delete next[key];
|
|
1609
|
+
else next[key] = width;
|
|
1610
|
+
widths.value = next;
|
|
1611
|
+
};
|
|
1612
|
+
const invoices: Invoice[] = [
|
|
1613
|
+
{ id: '1', number: 'INV-1041', client: 'Northwind Traders', amount: 4200 },
|
|
1614
|
+
{ id: '2', number: 'INV-1042', client: 'Globex', amount: 980 },
|
|
1615
|
+
{ id: '3', number: 'INV-1043', client: 'Initech', amount: 15600 },
|
|
1616
|
+
];
|
|
1617
|
+
</script>
|
|
1618
|
+
```
|
|
1619
|
+
|
|
1314
1620
|
### Nested tables that align with the parent
|
|
1315
1621
|
|
|
1316
1622
|
A `BbTable` rendered inside another's `#expand` slot is independent by
|
|
@@ -1528,11 +1834,42 @@ clicks are ignored on purpose: those inside interactive elements (links,
|
|
|
1528
1834
|
buttons, inputs, labels keep their own semantics) and those ending a text
|
|
1529
1835
|
selection (dragging to copy a cell must not mutate state).
|
|
1530
1836
|
|
|
1837
|
+
**Highlight is opt-in, and the model's value is the switch.** Bind nothing and
|
|
1838
|
+
the mechanic is entirely off — a row click emits `click:row` and does nothing
|
|
1839
|
+
else: no class, no `aria-current`, no state. That matters because the table has
|
|
1840
|
+
nowhere to put a highlight it invents; a table that highlighted itself would
|
|
1841
|
+
drive a cursor the app can neither read nor clear.
|
|
1842
|
+
|
|
1843
|
+
The switch reads the value, not the binding: **`undefined` means "never
|
|
1844
|
+
initialised" and keeps highlight off; `null` means "initialised and currently
|
|
1845
|
+
empty" and turns it on.** So initialise the model:
|
|
1846
|
+
|
|
1847
|
+
```ts
|
|
1848
|
+
const highlighted = ref(null); // ✅ connected
|
|
1849
|
+
const highlighted = ref(); // ❌ undefined — highlight stays off (warns in dev)
|
|
1850
|
+
```
|
|
1851
|
+
|
|
1852
|
+
A value seeded through the shared context counts exactly the same, and binds
|
|
1853
|
+
two ways just like the prop:
|
|
1854
|
+
|
|
1855
|
+
```ts
|
|
1856
|
+
const highlighted = ref(null);
|
|
1857
|
+
useBbTableContext('invoices-table', { highlighted });
|
|
1858
|
+
```
|
|
1859
|
+
|
|
1860
|
+
`click:row` is never gated — it fires on every row click whether or not
|
|
1861
|
+
highlight is connected, so listening for clicks costs you nothing. Calling
|
|
1862
|
+
`toggleHighlighted` from a cell slot on an unconnected table does nothing and
|
|
1863
|
+
warns once in dev.
|
|
1864
|
+
|
|
1531
1865
|
Highlight ships with a **default cursor**: the row gets `aria-current="true"`
|
|
1532
1866
|
and the `bb-table-data__row--highlighted` class, and the shipped stylesheet
|
|
1533
|
-
paints that class with a
|
|
1534
|
-
distinct from hover and from a plain focus ring, and readable
|
|
1535
|
-
and dark.
|
|
1867
|
+
paints that class with a quiet neutral fill plus a left accent bar in your
|
|
1868
|
+
brand primary — distinct from hover and from a plain focus ring, and readable
|
|
1869
|
+
in both light and dark. The fill is `--bb-muted` (the shared "selected/active"
|
|
1870
|
+
stop on the neutral surface ladder, one step above the `--bb-surface-hover`
|
|
1871
|
+
used for row hover) and steps to `--bb-pressed` while hovered, so it never
|
|
1872
|
+
depends on what your `--bb-primary` happens to be — only the accent bar does. Screen-reader users get the `aria-current` announcement either way;
|
|
1536
1873
|
sighted users now get a visible cursor out of the box too, including once
|
|
1537
1874
|
`keyboard-navigation` is on and the roving tabindex needs to be seen. Override
|
|
1538
1875
|
it with `row-class` (or equivalent visible styling) whenever the default
|
|
@@ -2145,6 +2482,9 @@ leave the screen. Collapse instead of scroll:
|
|
|
2145
2482
|
out. Removing the column removes its grid track. Do **not** hide cells with
|
|
2146
2483
|
`thClass`/`tdClass` + `display: none`: the table is a grid, a hidden cell
|
|
2147
2484
|
leaves its track in place and every following cell shifts one track over.
|
|
2485
|
+
A dropped key keeps its slot in `order` (the write-back never drops
|
|
2486
|
+
unknown keys), so the column returns where it was — never filter the
|
|
2487
|
+
order array.
|
|
2148
2488
|
- **Stack what survives into the primary cell.** The identity slot renders the
|
|
2149
2489
|
name plus the hidden facts under it, at `--bb-text-muted`, so nothing is
|
|
2150
2490
|
actually lost.
|
|
@@ -2174,20 +2514,23 @@ root `.bb-table__table[role=table]` owns the column tracks and every row group
|
|
|
2174
2514
|
and row is a subgrid of it. Style it through the classes and the roles — never
|
|
2175
2515
|
through element names, which match nothing:
|
|
2176
2516
|
|
|
2177
|
-
| Region
|
|
2178
|
-
|
|
|
2179
|
-
| grid root
|
|
2180
|
-
| header group
|
|
2181
|
-
| header row
|
|
2182
|
-
| header cell
|
|
2183
|
-
| body group
|
|
2184
|
-
| data row
|
|
2185
|
-
| data cell
|
|
2186
|
-
| actions cell
|
|
2187
|
-
| expand row
|
|
2188
|
-
| empty state
|
|
2189
|
-
| footer group
|
|
2190
|
-
|
|
|
2517
|
+
| Region | Class | Role |
|
|
2518
|
+
| -------------- | ------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------- |
|
|
2519
|
+
| grid root | `.bb-table__table` | `table` |
|
|
2520
|
+
| header group | `.bb-table__head` | `rowgroup` |
|
|
2521
|
+
| header row | `.bb-table-header-row` | `row` |
|
|
2522
|
+
| header cell | `.bb-table-header` (+ `--select`, `--actions`) | `columnheader` |
|
|
2523
|
+
| body group | `.bb-table__body` | `rowgroup` |
|
|
2524
|
+
| data row | `.bb-table-data__row` (+ `--highlighted`) | `row` |
|
|
2525
|
+
| data cell | `.bb-table-data__cell` (+ `--select`) | `cell` |
|
|
2526
|
+
| actions cell | `.bb-table__cell--actions` | `cell` |
|
|
2527
|
+
| expand row | `.bb-table-expand__row` > `.bb-table-expand__cell` | `row` > `cell` |
|
|
2528
|
+
| empty state | `.bb-table-no-data__row` > `.bb-table-no-data__cell` | `row` > `cell` |
|
|
2529
|
+
| footer group | `.bb-table__foot` (`#tfoot` content) | `rowgroup` only when `#tfoot` is provided |
|
|
2530
|
+
| focused header | `.bb-table-header[data-column-key]:focus-visible` (`reorderable` only; inset ring, the header row is one roving tab stop) | `columnheader` |
|
|
2531
|
+
| dragged column | `.bb-table-header--dragging` on the header; `.bb-table__head--dragging` on the head group during a drag | — |
|
|
2532
|
+
| drag chip | `.bb-table-column-ghost` (a `popover="manual"` in the top layer; paints `--bb-panel`, not the table's `--bg`) | — (`aria-hidden`) |
|
|
2533
|
+
| caption | `.bb-table-caption` (before the root, `aria-labelledby`) | — |
|
|
2191
2534
|
|
|
2192
2535
|
`[role='row']`, `[role='cell']` and `[role='columnheader']` are supported
|
|
2193
2536
|
hooks, not incidental markup — use them when you mean "every row kind" or
|
|
@@ -2295,6 +2638,8 @@ reader.
|
|
|
2295
2638
|
- `BbPagination` — shared page ref, or zero-wiring via `id` + `table-id`.
|
|
2296
2639
|
- `useBbTableContext` (`./composables/useBbTableContext.md`) — bulk-action
|
|
2297
2640
|
toolbars, detail panels, "reset sort" buttons living outside the table.
|
|
2641
|
+
(Column order is not mirrored there: a column panel needs `columns` too,
|
|
2642
|
+
so it lives where `v-model:order` is bound.)
|
|
2298
2643
|
- `BbButton` — per-row links (`href`/`to`) in cells or `#actions`;
|
|
2299
2644
|
`BbDropdown` for an overflow menu of row actions.
|
|
2300
2645
|
- `BbBadge` / `BbAvatar` — status and identity cells via slots.
|
|
@@ -2317,8 +2662,13 @@ reader.
|
|
|
2317
2662
|
localized `common.loadingText`, so an unset value still announces.
|
|
2318
2663
|
- Row selection labels default to the localized "select" word plus every
|
|
2319
2664
|
cell's content. `accessible-label` overrides that per row — it is called
|
|
2320
|
-
`(columns, item)` with the mapped cells
|
|
2321
|
-
|
|
2665
|
+
`(columns, item)` with the mapped cells (in **render** order — look them up
|
|
2666
|
+
by `key`, never by position) and the raw record, and whatever it returns
|
|
2667
|
+
becomes the checkbox's label.
|
|
2668
|
+
- `fixed-columns` pins **slots**, not columns: under `order` the
|
|
2669
|
+
column that lands in a pinned slot is the pinned one (see Column order).
|
|
2670
|
+
- A column panel that holds a partial `order` receives the completed
|
|
2671
|
+
array on the user's first move — its persisted list grows to every key.
|
|
2322
2672
|
- Enabling select-all does not re-emit an empty `modelValue` (see the
|
|
2323
2673
|
select-all section) — in all-mode, read `select-all` +
|
|
2324
2674
|
`unselected-items`, not `modelValue`.
|
|
@@ -2358,6 +2708,15 @@ reader.
|
|
|
2358
2708
|
identity rather than object reference.
|
|
2359
2709
|
- Sortable headers set `aria-sort` and render real buttons; custom header
|
|
2360
2710
|
slots should keep a button calling `toggleSort`.
|
|
2711
|
+
- With `reorderable` the header row becomes one roving tab stop
|
|
2712
|
+
(the AG Grid grammar): ←/→ move focus between headers, Home/End to the
|
|
2713
|
+
ends, **Shift+←/→** move the focused column one slot (Shift+Home/End to
|
|
2714
|
+
the ends), Enter/Space sorts a sortable header — every move committed
|
|
2715
|
+
immediately and announced through a polite status region ("X moved to
|
|
2716
|
+
position 2 of 5"); a one-line sr-only instruction describes each header.
|
|
2717
|
+
The sort button leaves the tab order (the header is the target; it still
|
|
2718
|
+
works for the mouse). NVDA/JAWS browse mode intercepts arrows on a focused
|
|
2719
|
+
element — the same focus-mode step as row keyboard navigation.
|
|
2361
2720
|
- When you paginate, pass `page` / `per-page` / `total-items` so the table
|
|
2362
2721
|
announces the full result count (`aria-rowcount`) and each row's absolute
|
|
2363
2722
|
index (`aria-rowindex`, expansion-aware), not just the current page.
|
|
@@ -2377,6 +2736,7 @@ reader.
|
|
|
2377
2736
|
- `v-model:expandedItems` — type: `any[] \| undefined`
|
|
2378
2737
|
- `v-model:highlighted` — type: `any`
|
|
2379
2738
|
- `v-model` — type: `any`
|
|
2739
|
+
- `v-model:order` — type: `string[] \| undefined`
|
|
2380
2740
|
- `v-model:selectAll` — type: `boolean \| undefined`
|
|
2381
2741
|
- `v-model:sort` — type: `BbTableSortEntry[] \| undefined`
|
|
2382
2742
|
- `v-model:unselectedItems` — type: `any[] \| undefined`
|
|
@@ -2385,7 +2745,7 @@ reader.
|
|
|
2385
2745
|
|
|
2386
2746
|
| Prop | Type | Default | Required | Description |
|
|
2387
2747
|
| --- | --- | --- | --- | --- |
|
|
2388
|
-
| `accessibleLabel` | `((columns: MappedCell[], item: any) => string) \| undefined` | | | Function that accepts the columns and the current item as arguments and returns a label to be used for accessibility purposes. |
|
|
2748
|
+
| `accessibleLabel` | `((columns: MappedCell[], item: any) => string) \| undefined` | | | Function that accepts the columns and the current item as arguments and returns a label to be used for accessibility purposes. The cells arrive in RENDER order (they follow `order`, so the label reads like the row) — look a cell up by `key`… |
|
|
2389
2749
|
| `actions` | `boolean \| undefined` | | | Displays the actions column. |
|
|
2390
2750
|
| `actionsText` | `string \| undefined` | | | Label used in the header of the actions column. |
|
|
2391
2751
|
| `align` | `"left" \| "center" \| "right" \| undefined` | `"left"` | | Text alignment of the columns. |
|
|
@@ -2400,10 +2760,10 @@ reader.
|
|
|
2400
2760
|
| `enforceCoherence` | `boolean \| undefined` | | | After every load (initial, `dependencies` refetch or a change of the `items` array) prunes the row-keyed models of values whose row is no longer in the result set: `modelValue`, `unselected-items`, `highlighted` and `expanded-items`. Values… |
|
|
2401
2761
|
| `expandedItems` | `any[] \| undefined` | `[]` | | Used by `v-model:expandedItems`. Array of the currently expanded items — toggled by the row expand control and by external writes (mirrors the `highlighted`/`sort` v-model surfaces). Drives the `#expand` slot rows and their `aria-expanded`/… |
|
|
2402
2762
|
| `fixed` | `boolean \| undefined` | | | Splits the width equally among the columns that declare no `width` (each undeclared column becomes a `minmax(0, 1fr)` track) making each column take up the same amount of space. |
|
|
2403
|
-
| `fixedColumns` | `(number \| { index: number; position: "left" \| "right"; })[] \| undefined` | `[]` | | Definition for which column should be fixed. It can be an array of index of the column to fix on the left side of the table or an array of objects indicating the index and the position `left` or `right` where to affix the columns. |
|
|
2763
|
+
| `fixedColumns` | `(number \| { index: number; position: "left" \| "right"; })[] \| undefined` | `[]` | | Definition for which column should be fixed. It can be an array of index of the column to fix on the left side of the table or an array of objects indicating the index and the position `left` or `right` where to affix the columns. Indices a… |
|
|
2404
2764
|
| `fixedHeaders` | `boolean \| undefined` | | | Boolean that sets the headers as sticky to the top of the table. |
|
|
2405
2765
|
| `headerRowClass` | `Classes \| undefined` | | | Defines the classes to be passed to the header row. |
|
|
2406
|
-
| `highlighted` | `any` | `undefined` | | Used by `v-model:highlighted`. A parallel, UI-intent state to selection — typically "the row whose details are open". A single item value (or `null`) — highlight is single by design.
|
|
2766
|
+
| `highlighted` | `any` | `undefined` | | Used by `v-model:highlighted`. A parallel, UI-intent state to selection — typically "the row whose details are open". A single item value (or `null`) — highlight is single by design. **Opt-in, and `undefined` is the opt-out.** Leave it unbo… |
|
|
2407
2767
|
| `id` | `string \| undefined` | | | Stable id for this table's width context. When omitted a unique id is generated. Nested tables use the nearest ancestor id to inherit widths. |
|
|
2408
2768
|
| `inheritColumnWidths` | `string \| boolean \| undefined` | | | Opt a nested table into inheriting its column widths from an ancestor table's matching tracks (accounting for `select`/`actions` columns). This also makes the parent's tracks authoritative, so it is **off by default** — a nested table rende… |
|
|
2409
2769
|
| `interactiveWhileLoading` | `boolean \| undefined` | | | Keeps the table interactive while it is loading. The header and the rows are `inert` while loading by default — a refetch is about to replace the rows, so editing, selecting or sorting them races the incoming data. This escape hatch exists … |
|
|
@@ -2418,9 +2778,12 @@ reader.
|
|
|
2418
2778
|
| `multiple` | `boolean \| undefined` | `true` | | Allows the selection of multiple items. |
|
|
2419
2779
|
| `name` | `string \| undefined` | | | Defines the name of the input. |
|
|
2420
2780
|
| `noDataText` | `string \| undefined` | | | String displayed when there are no items to display. |
|
|
2781
|
+
| `order` | `string[] \| undefined` | `[]` | | Used by `v-model:order`. Ordered array of column keys — the render order of the data columns. Keys listed here render first, in this order; declared columns it omits follow, in declaration order (so a column added to `columns` later renders… |
|
|
2421
2782
|
| `page` | `string \| number \| undefined` | | | Current page number starting from 1, used for accessibility purposes. Also flows into the shared table context, so a `BbPagination` paired through `id` reads it as the current page. Numeric strings are coerced (server pagination fields ofte… |
|
|
2422
2783
|
| `perPage` | `string \| number \| undefined` | | | Number of items per page, used for accessibility purposes. Also flows into the shared table context read by a `BbPagination` paired through `id`. When omitted, an array `items` table infers it from `items.length`; an explicit value (or a co… |
|
|
2423
2784
|
| `readonly` | `boolean \| undefined` | | | Sets the input in a readonly state. |
|
|
2785
|
+
| `reorderable` | `boolean \| undefined` | | | Lets the user reorder the data columns: drag a header cell (AG Grid style — the column moves live under the pointer and a chip with its label follows) or, from the keyboard, use each header's reorder handle (ArrowLeft / ArrowRight move the … |
|
|
2786
|
+
| `resizable` | `boolean \| undefined` | | | Lets the user resize the data columns of a root table: drag the handle on a header's trailing edge (the column follows live, the width commits on release), or press Alt + ArrowLeft / ArrowRight on the focused header; double-click the handle… |
|
|
2424
2787
|
| `rowClass` | `RowClasses<Item> \| undefined` | | | Defines the classes to be passed to each data row. Can also be a function of the row's item for dynamic values. |
|
|
2425
2788
|
| `selectable` | `boolean \| ((item: Item) => boolean) \| undefined` | | | Defines whether the table is selectable. Can be a global boolean that affects all rows or a function that accepts an item and returns a boolean that only affects that item. |
|
|
2426
2789
|
| `selectAll` | `boolean \| undefined` | `false` | | Defines whether the "Select all" checkbox is checked. |
|
|
@@ -2440,9 +2803,11 @@ reader.
|
|
|
2440
2803
|
- `dblclick:row` — `(e: "dblclick:row", event: MouseEvent, item: any): void`
|
|
2441
2804
|
- `item:selected` — `(e: "item:selected", value: any): void`
|
|
2442
2805
|
- `item:unselected` — `(e: "item:unselected", value: any): void`
|
|
2806
|
+
- `resize:column` — `(e: "resize:column", key: string, width: number \| null): void` — A column was resized by the user (`resizable`): the new width in px, or `null` when the handle was double-clicked to reset the column to its declared width.
|
|
2443
2807
|
- `update:expandedItems` — `(e: "update:expandedItems", value: any[]): void`
|
|
2444
2808
|
- `update:highlighted` — `(e: "update:highlighted", value: any): void`
|
|
2445
2809
|
- `update:modelValue` — `(e: "update:modelValue", value: any): void`
|
|
2810
|
+
- `update:order` — `(e: "update:order", value: string[]): void`
|
|
2446
2811
|
- `update:selectAll` — `(e: "update:selectAll", value: boolean): void`
|
|
2447
2812
|
- `update:sort` — `(e: "update:sort", value: BbTableSortEntry[]): void`
|
|
2448
2813
|
- `update:unselectedItems` — `(e: "update:unselectedItems", value: any[]): void`
|
package/dist/ai/changelog.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 1,
|
|
3
3
|
"library": "bitboss-ui",
|
|
4
|
-
"version": "3.0.0-beta.
|
|
4
|
+
"version": "3.0.0-beta.9",
|
|
5
5
|
"upgrade": "v2-to-v3",
|
|
6
6
|
"guide": "ai/guides/migration/v2-to-v3.md",
|
|
7
7
|
"summary": {
|
|
8
8
|
"renames": 9,
|
|
9
|
-
"behaviourBreaks":
|
|
9
|
+
"behaviourBreaks": 44
|
|
10
10
|
},
|
|
11
11
|
"breaking": [
|
|
12
12
|
{
|
|
@@ -292,6 +292,12 @@
|
|
|
292
292
|
"ruling": "grid substrate, plans/BBTABLE-GRID.md",
|
|
293
293
|
"guide": "components/bb-table.md § Do not override; guide § Narrow screens"
|
|
294
294
|
},
|
|
295
|
+
{
|
|
296
|
+
"kind": "behaviour",
|
|
297
|
+
"description": "BbTable highlight is opt-in: with highlighted unbound (or bound as undefined) clicking a row no longer applies bb-table-data__row--highlighted / aria-current — the table used to drive defineModel's local fallback, an unreadable state the app could not clear. Bind ref(null), not ref(); a context seed counts. click:row is unaffected",
|
|
298
|
+
"ruling": "owner 2026-08-27",
|
|
299
|
+
"guide": "components/bb-table.md § Highlight is opt-in"
|
|
300
|
+
},
|
|
295
301
|
{
|
|
296
302
|
"kind": "behaviour",
|
|
297
303
|
"description": "BbTable renders a CSS grid of divs with ARIA roles, not a <table>: every element selector under .bb-table (table/thead/tbody/tfoot/caption/tr/th/td/colgroup/col) matches nothing. Every .bb-table* class survives; .bb-table__table/__head/__body/__foot + [role=row]/[role=columnheader]/[role=cell] are the blessed translation (no data-* hooks, no shim classes)",
|