@7pmlabs/design-system 2.0.8 → 2.1.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.
- package/README.md +4 -4
- package/dist/design-system.css +1 -1
- package/dist/design-system.js +65 -59
- package/dist/design-system100.js +1 -1
- package/dist/design-system100.js.map +1 -1
- package/dist/design-system101.js +87 -53
- package/dist/design-system101.js.map +1 -1
- package/dist/design-system103.js +5 -13
- package/dist/design-system103.js.map +1 -1
- package/dist/design-system104.js +53 -108
- package/dist/design-system104.js.map +1 -1
- package/dist/{design-system102.js → design-system105.js} +1 -1
- package/dist/{design-system102.js.map → design-system105.js.map} +1 -1
- package/dist/design-system106.js +13 -6
- package/dist/design-system106.js.map +1 -1
- package/dist/design-system107.js +93 -190
- package/dist/design-system107.js.map +1 -1
- package/dist/design-system109.js +2 -2
- package/dist/design-system109.js.map +1 -1
- package/dist/design-system110.js +183 -484
- package/dist/design-system110.js.map +1 -1
- package/dist/design-system112.js +5 -4
- package/dist/design-system112.js.map +1 -1
- package/dist/design-system113.js +507 -7
- package/dist/design-system113.js.map +1 -1
- package/dist/design-system115.js +8 -0
- package/dist/design-system115.js.map +1 -0
- package/dist/design-system116.js +7 -6
- package/dist/design-system116.js.map +1 -1
- package/dist/design-system117.js +154 -169
- package/dist/design-system117.js.map +1 -1
- package/dist/design-system119.js +2 -2
- package/dist/design-system119.js.map +1 -1
- package/dist/design-system120.js +210 -149
- package/dist/design-system120.js.map +1 -1
- package/dist/design-system122.js +5 -4
- package/dist/design-system122.js.map +1 -1
- package/dist/design-system123.js +160 -9
- package/dist/design-system123.js.map +1 -1
- package/dist/design-system125.js +8 -0
- package/dist/design-system125.js.map +1 -0
- package/dist/design-system126.js +176 -6
- package/dist/design-system126.js.map +1 -1
- package/dist/design-system128.js +8 -0
- package/dist/design-system128.js.map +1 -0
- package/dist/design-system129.js +213 -5
- package/dist/design-system129.js.map +1 -1
- package/dist/design-system131.js +5 -90
- package/dist/design-system131.js.map +1 -1
- package/dist/design-system132.js +166 -0
- package/dist/design-system132.js.map +1 -0
- package/dist/design-system134.js +5 -42
- package/dist/design-system134.js.map +1 -1
- package/dist/design-system135.js +12 -0
- package/dist/design-system135.js.map +1 -0
- package/dist/design-system136.js +274 -5
- package/dist/design-system136.js.map +1 -1
- package/dist/design-system138.js +9 -0
- package/dist/{design-system124.js.map → design-system138.js.map} +1 -1
- package/dist/design-system139.js +16 -5
- package/dist/design-system139.js.map +1 -1
- package/dist/design-system141.js +8 -0
- package/dist/{design-system127.js.map → design-system141.js.map} +1 -1
- package/dist/design-system142.js +12 -5
- package/dist/design-system142.js.map +1 -1
- package/dist/design-system143.js +78 -83
- package/dist/design-system143.js.map +1 -1
- package/dist/design-system145.js +1 -1
- package/dist/design-system145.js.map +1 -1
- package/dist/design-system146.js +42 -9
- package/dist/design-system146.js.map +1 -1
- package/dist/design-system148.js +3 -2
- package/dist/design-system148.js.map +1 -1
- package/dist/design-system149.js +230 -18
- package/dist/design-system149.js.map +1 -1
- package/dist/design-system151.js +5 -158
- package/dist/design-system151.js.map +1 -1
- package/dist/{design-system140.js → design-system152.js} +6 -6
- package/dist/{design-system140.js.map → design-system152.js.map} +1 -1
- package/dist/design-system154.js +5 -307
- package/dist/design-system154.js.map +1 -1
- package/dist/design-system155.js +98 -0
- package/dist/design-system155.js.map +1 -0
- package/dist/design-system157.js +5 -240
- package/dist/design-system157.js.map +1 -1
- package/dist/design-system158.js +12 -0
- package/dist/design-system158.js.map +1 -0
- package/dist/design-system159.js +37 -5
- package/dist/design-system159.js.map +1 -1
- package/dist/design-system160.js +4 -189
- package/dist/design-system160.js.map +1 -1
- package/dist/design-system161.js +24 -0
- package/dist/{design-system150.js.map → design-system161.js.map} +1 -1
- package/dist/design-system162.js +2 -3
- package/dist/design-system162.js.map +1 -1
- package/dist/design-system163.js +158 -3
- package/dist/design-system163.js.map +1 -1
- package/dist/{design-system153.js → design-system165.js} +2 -2
- package/dist/{design-system153.js.map → design-system165.js.map} +1 -1
- package/dist/design-system166.js +307 -6
- package/dist/design-system166.js.map +1 -1
- package/dist/{design-system156.js → design-system168.js} +2 -2
- package/dist/{design-system156.js.map → design-system168.js.map} +1 -1
- package/dist/design-system169.js +167 -6
- package/dist/design-system169.js.map +1 -1
- package/dist/design-system171.js +8 -0
- package/dist/design-system171.js.map +1 -0
- package/dist/design-system172.js +240 -6
- package/dist/design-system172.js.map +1 -1
- package/dist/design-system174.js +8 -0
- package/dist/design-system174.js.map +1 -0
- package/dist/design-system175.js +189 -6
- package/dist/design-system175.js.map +1 -1
- package/dist/design-system177.js +8 -0
- package/dist/design-system177.js.map +1 -0
- package/dist/design-system178.js +3 -5
- package/dist/design-system178.js.map +1 -1
- package/dist/design-system179.js +58 -11
- package/dist/design-system179.js.map +1 -1
- package/dist/design-system181.js +9 -0
- package/dist/design-system181.js.map +1 -0
- package/dist/design-system182.js +56 -6
- package/dist/design-system182.js.map +1 -1
- package/dist/design-system184.js +9 -0
- package/dist/{design-system167.js.map → design-system184.js.map} +1 -1
- package/dist/design-system185.js +69 -5
- package/dist/design-system185.js.map +1 -1
- package/dist/design-system187.js +9 -0
- package/dist/{design-system170.js.map → design-system187.js.map} +1 -1
- package/dist/design-system188.js +182 -5
- package/dist/design-system188.js.map +1 -1
- package/dist/design-system190.js +9 -0
- package/dist/design-system190.js.map +1 -0
- package/dist/design-system191.js +115 -5
- package/dist/design-system191.js.map +1 -1
- package/dist/design-system193.js +8 -0
- package/dist/{design-system176.js.map → design-system193.js.map} +1 -1
- package/dist/design-system194.js +11 -5
- package/dist/design-system194.js.map +1 -1
- package/dist/design-system195.js +453 -24
- package/dist/design-system195.js.map +1 -1
- package/dist/design-system197.js +5 -4
- package/dist/design-system197.js.map +1 -1
- package/dist/design-system198.js +20 -16
- package/dist/design-system198.js.map +1 -1
- package/dist/design-system200.js +1 -1
- package/dist/design-system200.js.map +1 -1
- package/dist/design-system201.js +70 -314
- package/dist/design-system201.js.map +1 -1
- package/dist/design-system203.js +1 -1
- package/dist/design-system203.js.map +1 -1
- package/dist/design-system204.js +24 -89
- package/dist/design-system204.js.map +1 -1
- package/dist/design-system206.js +1 -1
- package/dist/design-system206.js.map +1 -1
- package/dist/design-system207.js +26 -17
- package/dist/design-system207.js.map +1 -1
- package/dist/design-system209.js +5 -3
- package/dist/design-system209.js.map +1 -1
- package/dist/design-system210.js +22 -408
- package/dist/design-system210.js.map +1 -1
- package/dist/design-system212.js +1 -1
- package/dist/design-system212.js.map +1 -1
- package/dist/design-system213.js +24 -52
- package/dist/design-system213.js.map +1 -1
- package/dist/design-system215.js +1 -1
- package/dist/design-system215.js.map +1 -1
- package/dist/design-system216.js +329 -85
- package/dist/design-system216.js.map +1 -1
- package/dist/design-system218.js +5 -108
- package/dist/design-system218.js.map +1 -1
- package/dist/design-system219.js +103 -0
- package/dist/design-system219.js.map +1 -0
- package/dist/design-system221.js +5 -106
- package/dist/design-system221.js.map +1 -1
- package/dist/design-system222.js +22 -0
- package/dist/{design-system208.js.map → design-system222.js.map} +1 -1
- package/dist/design-system223.js +4 -6
- package/dist/design-system223.js.map +1 -1
- package/dist/design-system224.js +3 -737
- package/dist/design-system224.js.map +1 -1
- package/dist/design-system225.js +422 -0
- package/dist/design-system225.js.map +1 -0
- package/dist/design-system227.js +5 -11
- package/dist/design-system227.js.map +1 -1
- package/dist/design-system228.js +51 -517
- package/dist/design-system228.js.map +1 -1
- package/dist/design-system230.js +1 -1
- package/dist/design-system230.js.map +1 -1
- package/dist/design-system231.js +88 -3
- package/dist/design-system231.js.map +1 -1
- package/dist/design-system232.js +4 -46
- package/dist/design-system232.js.map +1 -1
- package/dist/design-system233.js +108 -4
- package/dist/design-system233.js.map +1 -1
- package/dist/{design-system220.js → design-system235.js} +2 -2
- package/dist/{design-system220.js.map → design-system235.js.map} +1 -1
- package/dist/design-system236.js +106 -5
- package/dist/design-system236.js.map +1 -1
- package/dist/design-system238.js +9 -0
- package/dist/design-system238.js.map +1 -0
- package/dist/design-system239.js +737 -5
- package/dist/design-system239.js.map +1 -1
- package/dist/{design-system226.js → design-system241.js} +2 -2
- package/dist/{design-system226.js.map → design-system241.js.map} +1 -1
- package/dist/design-system242.js +3 -5
- package/dist/design-system242.js.map +1 -1
- package/dist/design-system243.js +42 -50
- package/dist/design-system243.js.map +1 -1
- package/dist/design-system244.js +1 -1
- package/dist/design-system244.js.map +1 -1
- package/dist/design-system245.js +254 -141
- package/dist/design-system245.js.map +1 -1
- package/dist/design-system247.js +1 -1
- package/dist/design-system247.js.map +1 -1
- package/dist/design-system248.js +119 -7
- package/dist/design-system248.js.map +1 -1
- package/dist/design-system250.js +8 -0
- package/dist/design-system250.js.map +1 -0
- package/dist/design-system251.js +172 -5
- package/dist/design-system251.js.map +1 -1
- package/dist/design-system253.js +8 -0
- package/dist/design-system253.js.map +1 -0
- package/dist/design-system254.js +11 -6
- package/dist/design-system254.js.map +1 -1
- package/dist/design-system255.js +525 -9
- package/dist/design-system255.js.map +1 -1
- package/dist/design-system257.js +8 -0
- package/dist/design-system257.js.map +1 -0
- package/dist/design-system258.js +112 -6
- package/dist/design-system258.js.map +1 -1
- package/dist/design-system260.js +5 -374
- package/dist/design-system260.js.map +1 -1
- package/dist/design-system261.js +57 -0
- package/dist/design-system261.js.map +1 -0
- package/dist/design-system262.js +4 -6
- package/dist/design-system262.js.map +1 -1
- package/dist/design-system263.js +173 -0
- package/dist/design-system263.js.map +1 -0
- package/dist/design-system265.js +8 -0
- package/dist/design-system265.js.map +1 -0
- package/dist/design-system266.js +10 -0
- package/dist/design-system266.js.map +1 -0
- package/dist/{design-system249.js → design-system267.js} +2 -2
- package/dist/{design-system249.js.map → design-system267.js.map} +1 -1
- package/dist/design-system269.js +8 -0
- package/dist/design-system269.js.map +1 -0
- package/dist/{design-system252.js → design-system270.js} +1 -1
- package/dist/{design-system252.js.map → design-system270.js.map} +1 -1
- package/dist/design-system272.js +9 -0
- package/dist/design-system272.js.map +1 -0
- package/dist/design-system273.js +12 -0
- package/dist/design-system273.js.map +1 -0
- package/dist/{design-system256.js → design-system274.js} +2 -2
- package/dist/{design-system256.js.map → design-system274.js.map} +1 -1
- package/dist/design-system276.js +9 -0
- package/dist/design-system276.js.map +1 -0
- package/dist/{design-system259.js → design-system277.js} +1 -1
- package/dist/{design-system259.js.map → design-system277.js.map} +1 -1
- package/dist/design-system278.js +377 -0
- package/dist/design-system278.js.map +1 -0
- package/dist/design-system280.js +9 -0
- package/dist/design-system280.js.map +1 -0
- package/dist/design-system69.js +182 -13
- package/dist/design-system69.js.map +1 -1
- package/dist/design-system71.js +8 -0
- package/dist/design-system71.js.map +1 -0
- package/dist/design-system72.js +13 -5
- package/dist/design-system72.js.map +1 -1
- package/dist/design-system73.js +677 -139
- package/dist/design-system73.js.map +1 -1
- package/dist/design-system75.js +1 -1
- package/dist/design-system75.js.map +1 -1
- package/dist/design-system76.js +152 -23
- package/dist/design-system76.js.map +1 -1
- package/dist/design-system78.js +5 -49
- package/dist/design-system78.js.map +1 -1
- package/dist/design-system79.js +32 -0
- package/dist/design-system79.js.map +1 -0
- package/dist/design-system80.js +2 -3
- package/dist/design-system80.js.map +1 -1
- package/dist/design-system81.js +38 -188
- package/dist/design-system81.js.map +1 -1
- package/dist/design-system83.js +1 -1
- package/dist/design-system83.js.map +1 -1
- package/dist/design-system84.js +199 -7
- package/dist/design-system84.js.map +1 -1
- package/dist/design-system86.js +8 -0
- package/dist/design-system86.js.map +1 -0
- package/dist/design-system87.js +7 -5
- package/dist/design-system87.js.map +1 -1
- package/dist/design-system88.js +264 -48
- package/dist/design-system88.js.map +1 -1
- package/dist/design-system90.js +1 -1
- package/dist/design-system90.js.map +1 -1
- package/dist/design-system91.js +57 -11
- package/dist/design-system91.js.map +1 -1
- package/dist/design-system93.js +8 -0
- package/dist/design-system93.js.map +1 -0
- package/dist/design-system94.js +11 -5
- package/dist/design-system94.js.map +1 -1
- package/dist/design-system95.js +92 -59
- package/dist/design-system95.js.map +1 -1
- package/dist/design-system97.js +1 -1
- package/dist/design-system97.js.map +1 -1
- package/dist/design-system98.js +56 -78
- package/dist/design-system98.js.map +1 -1
- package/dist/types/components/BContextMenu/BContextMenu.spec.d.ts +1 -0
- package/dist/types/components/BContextMenu/BContextMenu.vue.d.ts +42 -0
- package/dist/types/components/BContextMenu/index.d.ts +2 -0
- package/dist/types/components/BContextMenu/types.d.ts +23 -0
- package/dist/types/components/BInputTags/BInputTags.spec.d.ts +1 -0
- package/dist/types/components/BInputTags/BInputTags.vue.d.ts +54 -0
- package/dist/types/components/BInputTags/index.d.ts +1 -0
- package/dist/types/components/BLink/BLink.spec.d.ts +1 -0
- package/dist/types/components/BLink/BLink.vue.d.ts +100 -0
- package/dist/types/components/BLink/index.d.ts +1 -0
- package/dist/types/components/BListbox/BListbox.spec.d.ts +1 -0
- package/dist/types/components/BListbox/BListbox.vue.d.ts +52 -0
- package/dist/types/components/BListbox/index.d.ts +1 -0
- package/dist/types/components/BModal/BModal.spec.d.ts +1 -0
- package/dist/types/components/BPinInput/BPinInput.spec.d.ts +1 -0
- package/dist/types/components/BPinInput/BPinInput.vue.d.ts +43 -0
- package/dist/types/components/BPinInput/index.d.ts +1 -0
- package/dist/types/components/BProgress/BProgress.vue.d.ts +47 -2
- package/dist/types/components/BTextarea/BTextarea.spec.d.ts +1 -0
- package/dist/types/components/BTextarea/BTextarea.vue.d.ts +77 -0
- package/dist/types/components/BTextarea/index.d.ts +1 -0
- package/dist/types/components/index.d.ts +7 -1
- package/package.json +1 -1
- package/dist/design-system114.js +0 -212
- package/dist/design-system114.js.map +0 -1
- package/dist/design-system124.js +0 -277
- package/dist/design-system127.js +0 -19
- package/dist/design-system130.js +0 -15
- package/dist/design-system130.js.map +0 -1
- package/dist/design-system133.js +0 -8
- package/dist/design-system133.js.map +0 -1
- package/dist/design-system137.js +0 -236
- package/dist/design-system137.js.map +0 -1
- package/dist/design-system147.js +0 -40
- package/dist/design-system147.js.map +0 -1
- package/dist/design-system150.js +0 -7
- package/dist/design-system164.js +0 -61
- package/dist/design-system164.js.map +0 -1
- package/dist/design-system167.js +0 -59
- package/dist/design-system170.js +0 -72
- package/dist/design-system173.js +0 -185
- package/dist/design-system173.js.map +0 -1
- package/dist/design-system176.js +0 -118
- package/dist/design-system180.js +0 -465
- package/dist/design-system180.js.map +0 -1
- package/dist/design-system183.js +0 -38
- package/dist/design-system183.js.map +0 -1
- package/dist/design-system186.js +0 -91
- package/dist/design-system186.js.map +0 -1
- package/dist/design-system189.js +0 -38
- package/dist/design-system189.js.map +0 -1
- package/dist/design-system192.js +0 -31
- package/dist/design-system192.js.map +0 -1
- package/dist/design-system208.js +0 -7
- package/dist/design-system217.js +0 -7
- package/dist/design-system217.js.map +0 -1
- package/dist/design-system234.js +0 -286
- package/dist/design-system234.js.map +0 -1
- package/dist/design-system237.js +0 -122
- package/dist/design-system237.js.map +0 -1
- package/dist/design-system240.js +0 -115
- package/dist/design-system240.js.map +0 -1
- package/dist/design-system70.js +0 -699
- package/dist/design-system70.js.map +0 -1
- package/dist/design-system77.js +0 -7
- package/dist/design-system77.js.map +0 -1
- package/dist/design-system85.js +0 -276
- package/dist/design-system85.js.map +0 -1
- package/dist/design-system92.js +0 -102
- package/dist/design-system92.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"design-system226.js","names":[],"sources":["../src/components/BTable/BTable.vue"],"sourcesContent":["<script setup lang=\"ts\" generic=\"T extends Record<string, unknown> = Record<string, unknown>\">\nimport type { CSSProperties } from 'vue';\nimport { computed, nextTick, onMounted, ref, watch } from 'vue';\n\nimport type {\n BTableChangeExtra,\n BTableColumnType,\n BTableExpandable,\n BTableFilterOption,\n BTableFilterState,\n BTableFilterValue,\n BTablePaginationConfig,\n BTableRowSelection,\n BTableScrollConfig,\n BTableSize,\n BTableSortOrder,\n BTableSorterResult,\n} from './types';\n\n// ─────────────────────────────────────────────\n// Props\n// ─────────────────────────────────────────────\n\nconst {\n dataSource = [],\n columns = [],\n rowKey = 'key',\n size = 'default',\n bordered = false,\n loading = false,\n showHeader = true,\n tableLayout,\n scroll,\n pagination = false,\n rowSelection,\n expandable,\n sticky = false,\n caption,\n locale,\n rowClassName,\n onRow,\n onHeaderRow,\n components: _customComponents, \n} = defineProps<{\n /** Data array */\n dataSource?: T[];\n /** Column definitions */\n columns?: BTableColumnType<T>[];\n /** Unique row key - a field name string or extractor function */\n rowKey?: string | ((record: T) => string | number);\n /** Table size */\n size?: BTableSize;\n /** Show outer border and column borders */\n bordered?: boolean;\n /** Loading state */\n loading?: boolean;\n /** Show table header */\n showHeader?: boolean;\n /** Table layout algorithm */\n tableLayout?: 'auto' | 'fixed';\n /** Scroll config */\n scroll?: BTableScrollConfig;\n /** Pagination config; false to disable */\n pagination?: BTablePaginationConfig | false;\n /** Row selection config */\n rowSelection?: BTableRowSelection<T>;\n /** Expandable config */\n expandable?: BTableExpandable<T>;\n /** Make table header sticky */\n sticky?:\n | boolean\n | { offsetHeader?: number; offsetScroll?: number; getContainer?: () => HTMLElement };\n /** Table caption (accessibility) */\n caption?: string;\n /** Custom locale text */\n locale?: {\n emptyText?: string;\n filterConfirm?: string;\n filterReset?: string;\n selectAll?: string;\n selectInvert?: string;\n selectionAll?: string;\n sortTitle?: string;\n expand?: string;\n collapse?: string;\n };\n /** Custom row class name */\n rowClassName?: (record: T, index: number) => string;\n /** Custom row event handlers */\n onRow?: (record: T, index: number) => Record<string, unknown>;\n /** Custom header row event handlers */\n onHeaderRow?: (columns: BTableColumnType<T>[], index: number) => Record<string, unknown>;\n /** Override internal table elements */\n components?: {\n table?: unknown;\n header?: { wrapper?: unknown; row?: unknown; cell?: unknown };\n body?: { wrapper?: unknown; row?: unknown; cell?: unknown };\n };\n}>();\n\n// ─────────────────────────────────────────────\n// Emits\n// ─────────────────────────────────────────────\n\nconst emit = defineEmits<{\n (\n e: 'change',\n pagination: BTablePaginationConfig,\n filters: BTableFilterState,\n sorter: BTableSorterResult<T> | BTableSorterResult<T>[],\n extra: BTableChangeExtra<T>,\n ): void;\n (e: 'expandedRowsChange', expandedRows: (string | number)[]): void;\n (e: 'expand', expanded: boolean, record: T): void;\n}>();\n\n// ─────────────────────────────────────────────\n// Slots\n// ─────────────────────────────────────────────\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\ndefineSlots<Record<string, ((props: any) => unknown) | undefined>>();\n\n// ─────────────────────────────────────────────\n// Locale defaults\n// ─────────────────────────────────────────────\n\nconst resolvedLocale = computed(() => ({\n emptyText: locale?.emptyText ?? 'No data',\n filterConfirm: locale?.filterConfirm ?? 'OK',\n filterReset: locale?.filterReset ?? 'Reset',\n selectAll: locale?.selectAll ?? 'Select all data',\n selectInvert: locale?.selectInvert ?? 'Invert current page',\n selectionAll: locale?.selectionAll ?? 'Select all data',\n sortTitle: locale?.sortTitle ?? 'Sort',\n expand: locale?.expand ?? 'Expand row',\n collapse: locale?.collapse ?? 'Collapse row',\n}));\n\n// ─────────────────────────────────────────────\n// Row-key helper\n// ─────────────────────────────────────────────\n\nfunction getRowKey(record: T, index: number): string | number {\n if (typeof rowKey === 'function') return rowKey(record);\n const val = record[rowKey as string];\n return (val as string | number) ?? index;\n}\n\n// ─────────────────────────────────────────────\n// Nested value accessor (dot-path)\n// ─────────────────────────────────────────────\n\nfunction getValue(record: T, dataIndex?: string | string[]): unknown {\n if (!dataIndex) return undefined;\n const keys = Array.isArray(dataIndex) ? dataIndex : dataIndex.split('.');\n let cur: unknown = record;\n for (const k of keys) {\n if (cur == null || typeof cur !== 'object') return undefined;\n cur = (cur as Record<string, unknown>)[k];\n }\n return cur;\n}\n\n// ─────────────────────────────────────────────\n// Sort state\n// ─────────────────────────────────────────────\n\ninterface ColumnSortState {\n key: string | number;\n order: BTableSortOrder;\n}\n\nconst internalSortState = ref<ColumnSortState>({ key: '', order: null });\n\nfunction columnSortKey(col: BTableColumnType<T>): string | number {\n return (\n col.key ?? (Array.isArray(col.dataIndex) ? col.dataIndex.join('.') : (col.dataIndex ?? ''))\n );\n}\n\nfunction getColumnSortOrder(col: BTableColumnType<T>): BTableSortOrder {\n if (col.sortOrder !== undefined) return col.sortOrder;\n const key = columnSortKey(col);\n if (internalSortState.value.key === key) return internalSortState.value.order;\n return col.defaultSortOrder ?? null;\n}\n\nfunction cycleSortOrder(current: BTableSortOrder, directions: BTableSortOrder[]): BTableSortOrder {\n const cycle = directions.length ? directions : ['ascend' as const, 'descend' as const, null];\n const idx = cycle.indexOf(current);\n return cycle[(idx + 1) % cycle.length] ?? null;\n}\n\nfunction handleSort(col: BTableColumnType<T>) {\n if (!col.sorter) return;\n const key = columnSortKey(col);\n const current = getColumnSortOrder(col);\n const directions = col.sortDirections ?? ['ascend', 'descend', null];\n const next = cycleSortOrder(current, directions);\n\n // Controlled sort is driven by parent via col.sortOrder\n if (col.sortOrder === undefined) {\n internalSortState.value = { key, order: next };\n }\n\n const sorterResult: BTableSorterResult<T> = {\n column: col,\n order: next,\n field: col.dataIndex,\n columnKey: key,\n };\n emit('change', currentPagination.value, currentFilters.value, sorterResult, {\n currentDataSource: processedData.value,\n action: 'sort',\n });\n}\n\n// ─────────────────────────────────────────────\n// Filter state\n// ─────────────────────────────────────────────\n\nconst internalFilters = ref<BTableFilterState>({});\nconst openFilterKey = ref<string | number | null>(null);\nconst filterSearchMap = ref<Record<string | number, string>>({});\n\nfunction getColumnFilterKey(col: BTableColumnType<T>): string | number {\n return (\n col.key ?? (Array.isArray(col.dataIndex) ? col.dataIndex.join('.') : (col.dataIndex ?? ''))\n );\n}\n\nfunction getColumnFilterValue(col: BTableColumnType<T>): BTableFilterValue | null {\n if (col.filteredValue !== undefined) return col.filteredValue;\n return internalFilters.value[getColumnFilterKey(col)] ?? col.defaultFilteredValue ?? null;\n}\n\nconst currentFilters = computed<BTableFilterState>(() => {\n const result: BTableFilterState = {};\n for (const col of flatColumns.value) {\n if (col.filters || col.filteredValue !== undefined) {\n const key = getColumnFilterKey(col);\n result[key] = getColumnFilterValue(col);\n }\n }\n return result;\n});\n\nfunction toggleFilterPanel(col: BTableColumnType<T>) {\n const key = getColumnFilterKey(col);\n openFilterKey.value = openFilterKey.value === key ? null : key;\n}\n\nfunction applyFilters(col: BTableColumnType<T>, values: BTableFilterValue) {\n const key = getColumnFilterKey(col);\n if (col.filteredValue === undefined) {\n internalFilters.value = { ...internalFilters.value, [key]: values.length ? values : null };\n }\n openFilterKey.value = null;\n\n // Reset to page 1 on filter change\n if (col.filterResetToDefaultFilteredValue !== false) {\n internalPage.value = 1;\n }\n\n emit('change', currentPagination.value, currentFilters.value, buildActiveSorter(), {\n currentDataSource: processedData.value,\n action: 'filter',\n });\n}\n\nfunction resetFilter(col: BTableColumnType<T>) {\n const key = getColumnFilterKey(col);\n const defaults = col.defaultFilteredValue ?? [];\n if (col.filteredValue === undefined) {\n internalFilters.value = { ...internalFilters.value, [key]: defaults.length ? defaults : null };\n }\n openFilterKey.value = null;\n emit('change', currentPagination.value, currentFilters.value, buildActiveSorter(), {\n currentDataSource: processedData.value,\n action: 'filter',\n });\n}\n\nfunction buildActiveSorter(): BTableSorterResult<T> {\n const sortCol = flatColumns.value.find((c) => getColumnSortOrder(c) !== null);\n if (!sortCol) return { order: null };\n return {\n column: sortCol,\n order: getColumnSortOrder(sortCol),\n field: sortCol.dataIndex,\n columnKey: columnSortKey(sortCol),\n };\n}\n\nfunction getFilteredOptions(col: BTableColumnType<T>): BTableFilterOption[] {\n const search = filterSearchMap.value[getColumnFilterKey(col)] ?? '';\n if (!search) return col.filters ?? [];\n const lc = search.toLowerCase();\n const filterFn =\n typeof col.filterSearch === 'function'\n ? col.filterSearch\n : (input: string, opt: BTableFilterOption) =>\n opt.text.toLowerCase().includes(input.toLowerCase());\n return (col.filters ?? []).filter((opt) => filterFn(lc, opt));\n}\n\n// Temp filter selections while the dropdown is open\nconst tempFilterValues = ref<Record<string | number, BTableFilterValue>>({});\n\nfunction openFilter(col: BTableColumnType<T>) {\n const key = getColumnFilterKey(col);\n tempFilterValues.value[key] = [...(getColumnFilterValue(col) ?? [])];\n toggleFilterPanel(col);\n}\n\nfunction toggleTempFilter(col: BTableColumnType<T>, value: string | number | boolean) {\n const key = getColumnFilterKey(col);\n const current = tempFilterValues.value[key] ?? [];\n if (col.filterMultiple !== false) {\n const idx = current.indexOf(value);\n tempFilterValues.value[key] =\n idx === -1 ? [...current, value] : current.filter((v) => v !== value);\n } else {\n tempFilterValues.value[key] = [value];\n }\n}\n\nfunction confirmFilter(col: BTableColumnType<T>) {\n const key = getColumnFilterKey(col);\n applyFilters(col, tempFilterValues.value[key] ?? []);\n}\n\n// ─────────────────────────────────────────────\n// Pagination state\n// ─────────────────────────────────────────────\n\nconst internalPage = ref(1);\nconst internalPageSize = ref(\n typeof pagination === 'object' ? (pagination.defaultPageSize ?? pagination.pageSize ?? 10) : 10,\n);\n\nwatch(\n () => (typeof pagination === 'object' ? pagination.pageSize : undefined),\n (ps) => {\n if (ps != null) internalPageSize.value = ps;\n },\n);\n\nconst currentPage = computed(() =>\n typeof pagination === 'object' && pagination.current != null\n ? pagination.current\n : internalPage.value,\n);\n\nconst currentPageSize = computed(() =>\n typeof pagination === 'object' && pagination.pageSize != null\n ? pagination.pageSize\n : internalPageSize.value,\n);\n\nconst currentPagination = computed<BTablePaginationConfig>(() => ({\n current: currentPage.value,\n pageSize: currentPageSize.value,\n total: filteredData.value.length,\n}));\n\nfunction goToPage(page: number) {\n if (typeof pagination === 'object' && pagination.current == null) {\n internalPage.value = page;\n }\n if (typeof pagination === 'object') {\n pagination.onChange?.(page, currentPageSize.value);\n }\n emit(\n 'change',\n { ...currentPagination.value, current: page },\n currentFilters.value,\n buildActiveSorter(),\n {\n currentDataSource: processedData.value,\n action: 'paginate',\n },\n );\n}\n\nfunction changePageSize(size: number) {\n internalPageSize.value = size;\n internalPage.value = 1;\n if (typeof pagination === 'object') {\n pagination.onShowSizeChange?.(1, size);\n }\n}\n\nconst totalPages = computed(() => Math.ceil(filteredData.value.length / currentPageSize.value));\n\nconst pageSizeOptions = computed(() =>\n typeof pagination === 'object' && pagination.pageSizeOptions\n ? pagination.pageSizeOptions.map(Number)\n : [10, 20, 50, 100],\n);\n\n// ─────────────────────────────────────────────\n// Data processing pipeline\n// ─────────────────────────────────────────────\n\nconst filteredData = computed<T[]>(() => {\n let data = [...dataSource];\n\n for (const col of flatColumns.value) {\n const filterValues = getColumnFilterValue(col);\n if (!filterValues || filterValues.length === 0) continue;\n\n if (col.onFilter) {\n data = data.filter((record) =>\n (filterValues as (string | number | boolean)[]).some((v) => col.onFilter!(v, record)),\n );\n } else if (col.dataIndex) {\n data = data.filter((record) => {\n const val = getValue(record, col.dataIndex);\n return (filterValues as (string | number | boolean)[]).some(\n (v) => String(val) === String(v),\n );\n });\n }\n }\n return data;\n});\n\nconst sortedData = computed<T[]>(() => {\n const data = [...filteredData.value];\n const sortCol = flatColumns.value.find((c) => getColumnSortOrder(c) !== null);\n if (!sortCol || !sortCol.sorter) return data;\n\n const order = getColumnSortOrder(sortCol);\n if (!order) return data;\n\n if (typeof sortCol.sorter === 'function') {\n data.sort((a, b) => {\n const res = (sortCol.sorter as (a: T, b: T) => number)(a, b);\n return order === 'ascend' ? res : -res;\n });\n } else {\n // Default sort: compare string values\n data.sort((a, b) => {\n const av = String(getValue(a, sortCol.dataIndex) ?? '');\n const bv = String(getValue(b, sortCol.dataIndex) ?? '');\n return order === 'ascend' ? av.localeCompare(bv) : bv.localeCompare(av);\n });\n }\n return data;\n});\n\nconst processedData = computed<T[]>(() => {\n if (!pagination) return sortedData.value;\n const start = (currentPage.value - 1) * currentPageSize.value;\n return sortedData.value.slice(start, start + currentPageSize.value);\n});\n\n// ─────────────────────────────────────────────\n// Column processing\n// ─────────────────────────────────────────────\n\n/** Flatten leaf columns (skip group columns for data rendering) */\nconst flatColumns = computed<BTableColumnType<T>[]>(() => {\n function flatten(cols: BTableColumnType<T>[]): BTableColumnType<T>[] {\n const result: BTableColumnType<T>[] = [];\n for (const col of cols) {\n if (col.children?.length) {\n result.push(...flatten(col.children));\n } else {\n result.push(col);\n }\n }\n return result;\n }\n return flatten(columns);\n});\n\n/** Header rows for colspan/rowspan grouped headers */\ninterface HeaderCell {\n col: BTableColumnType<T>;\n colSpan: number;\n rowSpan: number;\n isLeaf: boolean;\n}\n\nconst headerRows = computed<HeaderCell[][]>(() => {\n function getDepth(col: BTableColumnType<T>): number {\n if (!col.children?.length) return 1;\n return 1 + Math.max(...col.children.map(getDepth));\n }\n\n const maxDepth = Math.max(...columns.map(getDepth), 1);\n\n function buildRows(\n cols: BTableColumnType<T>[],\n rows: HeaderCell[][],\n rowIdx: number,\n ) {\n for (const col of cols) {\n const isLeaf = !col.children?.length;\n const cell: HeaderCell = {\n col,\n colSpan: isLeaf ? 1 : flattenLeafCount(col),\n rowSpan: isLeaf ? maxDepth - rowIdx : 1,\n isLeaf,\n };\n if (!rows[rowIdx]) rows[rowIdx] = [];\n rows[rowIdx].push(cell);\n if (!isLeaf) {\n buildRows(col.children!, rows, rowIdx + 1);\n }\n }\n }\n\n function flattenLeafCount(col: BTableColumnType<T>): number {\n if (!col.children?.length) return 1;\n return col.children.reduce((s, c) => s + flattenLeafCount(c), 0);\n }\n\n const rows: HeaderCell[][] = [];\n buildRows(columns, rows, 0);\n return rows;\n});\n\n// ─────────────────────────────────────────────\n// Row selection state\n// ─────────────────────────────────────────────\n\nconst internalSelectedKeys = ref<Set<string | number>>(\n new Set(rowSelection?.defaultSelectedRowKeys ?? []),\n);\n\nconst selectedKeys = computed<Set<string | number>>(() => {\n if (rowSelection?.selectedRowKeys) return new Set(rowSelection.selectedRowKeys);\n return internalSelectedKeys.value;\n});\n\nconst isCheckbox = computed(() => rowSelection?.type !== 'radio');\n\nconst pageRowKeys = computed(() => processedData.value.map((r, i) => getRowKey(r, i)));\n\nconst selectablePageKeys = computed(() =>\n pageRowKeys.value.filter((key, i) => {\n const checkboxProps = rowSelection?.getCheckboxProps?.(processedData.value[i]);\n return !checkboxProps?.disabled;\n }),\n);\n\nconst allPageSelected = computed(\n () =>\n selectablePageKeys.value.length > 0 &&\n selectablePageKeys.value.every((k) => selectedKeys.value.has(k)),\n);\n\nconst somePageSelected = computed(\n () => !allPageSelected.value && selectablePageKeys.value.some((k) => selectedKeys.value.has(k)),\n);\n\nfunction setSelectedKeys(keys: Set<string | number>) {\n if (!rowSelection?.selectedRowKeys) {\n internalSelectedKeys.value = keys;\n }\n const keyArr = [...keys];\n const rows = dataSource.filter((r, i) => keys.has(getRowKey(r, i)));\n rowSelection?.onChange?.(keyArr, rows);\n}\n\nfunction toggleRow(record: T, index: number, event: Event) {\n const key = getRowKey(record, index);\n const keys = new Set(selectedKeys.value);\n if (isCheckbox.value) {\n if (keys.has(key)) {\n keys.delete(key);\n rowSelection?.onSelect?.(\n record,\n false,\n [...keys].map((k) => dataSource.find((r, i) => getRowKey(r, i) === k)!),\n event,\n );\n } else {\n keys.add(key);\n rowSelection?.onSelect?.(\n record,\n true,\n [...keys].map((k) => dataSource.find((r, i) => getRowKey(r, i) === k)!),\n event,\n );\n }\n } else {\n keys.clear();\n keys.add(key);\n rowSelection?.onSelect?.(record, true, [record], event);\n }\n setSelectedKeys(keys);\n}\n\n \nfunction toggleAllPage(_event: Event) {\n const keys = new Set(\n rowSelection?.preserveSelectedRowKeys ? selectedKeys.value : new Set<string | number>(),\n );\n if (allPageSelected.value) {\n selectablePageKeys.value.forEach((k) => keys.delete(k));\n rowSelection?.onSelectAll?.(false, [], processedData.value);\n } else {\n selectablePageKeys.value.forEach((k) => keys.add(k));\n rowSelection?.onSelectAll?.(\n true,\n [...keys].map((k) => dataSource.find((r, i) => getRowKey(r, i) === k)!),\n processedData.value,\n );\n }\n setSelectedKeys(keys);\n}\n\n// ─────────────────────────────────────────────\n// Expand state\n// ─────────────────────────────────────────────\n\nconst internalExpandedKeys = ref<Set<string | number>>(\n new Set(expandable?.defaultExpandedRowKeys ?? []),\n);\n\nconst expandedKeys = computed<Set<string | number>>(() => {\n if (expandable?.expandedRowKeys) return new Set(expandable.expandedRowKeys);\n return internalExpandedKeys.value;\n});\n\n \nfunction toggleExpand(record: T, index: number, _event: MouseEvent) {\n const key = getRowKey(record, index);\n const keys = new Set(expandedKeys.value);\n const isExpanded = keys.has(key);\n if (isExpanded) {\n keys.delete(key);\n } else {\n keys.add(key);\n }\n if (!expandable?.expandedRowKeys) {\n internalExpandedKeys.value = keys;\n }\n expandable?.onExpand?.(!isExpanded, record);\n expandable?.onExpandedRowsChange?.([...keys]);\n emit('expand', !isExpanded, record);\n emit('expandedRowsChange', [...keys]);\n}\n\nfunction isRowExpanded(record: T, index: number): boolean {\n return expandedKeys.value.has(getRowKey(record, index));\n}\n\nfunction isRowExpandable(record: T): boolean {\n if (expandable?.rowExpandable) return expandable.rowExpandable(record);\n const childKey = expandable?.childrenColumnName ?? 'children';\n const children = (record as Record<string, unknown>)[childKey];\n return Array.isArray(children) && children.length > 0;\n}\n\n// ─────────────────────────────────────────────\n// Cell value helper\n// ─────────────────────────────────────────────\n\nfunction getCellValue(record: T, col: BTableColumnType<T>): unknown {\n return getValue(record, col.dataIndex);\n}\n\nfunction getColumnKey(col: BTableColumnType<T>, idx: number): string | number {\n return (\n col.key ?? (Array.isArray(col.dataIndex) ? col.dataIndex.join('.') : (col.dataIndex ?? idx))\n );\n}\n\n// ─────────────────────────────────────────────\n// Sticky offset\n// ─────────────────────────────────────────────\n\nconst stickyOffsetHeader = computed(() =>\n typeof sticky === 'object' ? (sticky.offsetHeader ?? 0) : 0,\n);\n\n// ─────────────────────────────────────────────\n// Scroll wrapper ref\n// ─────────────────────────────────────────────\n\nconst scrollRef = ref<HTMLElement | null>(null);\nconst hasHorizontalScroll = ref(false);\n\nonMounted(() => {\n checkScroll();\n});\n\nwatch(\n () => columns,\n () => nextTick(checkScroll),\n);\n\nfunction checkScroll() {\n if (!scrollRef.value) return;\n hasHorizontalScroll.value = scrollRef.value.scrollWidth > scrollRef.value.clientWidth;\n}\n\n// ─────────────────────────────────────────────\n// Root class / style\n// ─────────────────────────────────────────────\n\nconst rootClasses = computed(() => [\n 'b-table',\n {\n 'b-table--bordered': bordered,\n 'b-table--loading': loading,\n 'b-table--small': size === 'small',\n 'b-table--middle': size === 'middle',\n 'b-table--sticky': sticky,\n 'b-table--fixed-header': !!scroll?.y,\n 'b-table--scroll-x': !!scroll?.x,\n 'b-table--has-selection': !!rowSelection,\n 'b-table--has-expand': !!expandable,\n 'b-table--empty': processedData.value.length === 0,\n },\n]);\n\nconst tableStyle = computed<CSSProperties>(() => {\n const s: CSSProperties = {};\n if (tableLayout) s.tableLayout = tableLayout;\n if (scroll?.x === true) s.minWidth = '100%';\n else if (scroll?.x) s.width = typeof scroll.x === 'number' ? `${scroll.x}px` : scroll.x;\n return s;\n});\n\nconst wrapperStyle = computed<CSSProperties>(() => {\n const s: CSSProperties = {};\n if (scroll?.x) {\n s.overflowX = 'auto';\n }\n if (scroll?.y) {\n s.overflowY = 'auto';\n s.maxHeight = typeof scroll.y === 'number' ? `${scroll.y}px` : scroll.y;\n }\n return s;\n});\n\nconst stickyHeaderStyle = computed<CSSProperties>(() => {\n if (!sticky) return {};\n return {\n position: 'sticky',\n top: stickyOffsetHeader.value ? `${stickyOffsetHeader.value}px` : '0',\n zIndex: 2,\n };\n});\n\n// ─────────────────────────────────────────────\n// Sort icon helper\n// ─────────────────────────────────────────────\n\nfunction getSortIcon(col: BTableColumnType<T>): 'asc' | 'desc' | 'none' {\n const order = getColumnSortOrder(col);\n if (order === 'ascend') return 'asc';\n if (order === 'descend') return 'desc';\n return 'none';\n}\n\n// ─────────────────────────────────────────────\n// Ellipsis helper\n// ─────────────────────────────────────────────\n\nfunction getEllipsis(col: BTableColumnType<T>): boolean {\n if (typeof col.ellipsis === 'boolean') return col.ellipsis;\n return !!col.ellipsis;\n}\n\n// ─────────────────────────────────────────────\n// Column width style\n// ─────────────────────────────────────────────\n\nfunction colWidthStyle(col: BTableColumnType<T>): CSSProperties {\n const s: CSSProperties = {};\n if (col.width != null) s.width = typeof col.width === 'number' ? `${col.width}px` : col.width;\n if (col.minWidth != null) s.minWidth = `${col.minWidth}px`;\n if (col.maxWidth != null) s.maxWidth = `${col.maxWidth}px`;\n if (col.align) s.textAlign = col.align;\n return s;\n}\n\n// ─────────────────────────────────────────────\n// Page jump (simple pagination)\n// ─────────────────────────────────────────────\n\nconst jumpInput = ref('');\nfunction handleJumpKeydown(e: KeyboardEvent) {\n if (e.key === 'Enter') {\n const p = parseInt(jumpInput.value, 10);\n if (!isNaN(p) && p >= 1 && p <= totalPages.value) {\n goToPage(p);\n }\n jumpInput.value = '';\n }\n}\n\n// ─────────────────────────────────────────────\n// Filter keyboard handler (close on Escape)\n// ─────────────────────────────────────────────\n\nfunction handleFilterKeydown(e: KeyboardEvent) {\n if (e.key === 'Escape') openFilterKey.value = null;\n}\n\n// ─────────────────────────────────────────────\n// Get cell style\n// ─────────────────────────────────────────────\n\nfunction getCellStyle(\n col: BTableColumnType<T>,\n record: T,\n rowIndex: number,\n): CSSProperties | undefined {\n if (!col.cellStyle) return undefined;\n if (typeof col.cellStyle === 'function') return col.cellStyle(record, rowIndex);\n return col.cellStyle;\n}\n</script>\n\n<template>\n <div\n :class=\"rootClasses\"\n role=\"region\"\n :aria-label=\"caption ?? undefined\"\n :aria-busy=\"loading ? 'true' : undefined\"\n >\n <!-- Loading overlay -->\n <div\n v-if=\"loading\"\n class=\"b-table__loading-overlay\"\n role=\"status\"\n aria-live=\"polite\"\n aria-label=\"Loading data\"\n >\n <div class=\"b-table__spin\" aria-hidden=\"true\">\n <span class=\"b-table__spin-dot\" />\n <span class=\"b-table__spin-dot\" />\n <span class=\"b-table__spin-dot\" />\n <span class=\"b-table__spin-dot\" />\n </div>\n </div>\n\n <!-- Title slot -->\n <div\n v-if=\"$slots.title\"\n class=\"b-table__title\"\n :aria-hidden=\"loading ? 'true' : undefined\"\n :style=\"loading ? { visibility: 'hidden' } : {}\"\n >\n <slot name=\"title\" />\n </div>\n\n <!-- Scroll wrapper -->\n <div\n ref=\"scrollRef\"\n class=\"b-table__scroll-wrapper\"\n :style=\"[wrapperStyle, loading ? { visibility: 'hidden' } : {}]\"\n :aria-hidden=\"loading ? 'true' : undefined\"\n :tabindex=\"scroll?.x || scroll?.y ? 0 : undefined\"\n >\n <table class=\"b-table__table\" :style=\"tableStyle\" role=\"table\">\n <!-- Caption (a11y) -->\n <caption v-if=\"caption\" class=\"b-table__caption\">\n {{\n caption\n }}\n </caption>\n\n <!-- Colgroup for widths -->\n <colgroup>\n <col\n v-if=\"rowSelection\"\n :style=\"{\n width: rowSelection.columnWidth\n ? typeof rowSelection.columnWidth === 'number'\n ? `${rowSelection.columnWidth}px`\n : rowSelection.columnWidth\n : '48px',\n }\"\n />\n <col v-if=\"expandable && expandable.showExpandColumn !== false\" style=\"width: 48px\" />\n <col\n v-for=\"(col, ci) in flatColumns\"\n :key=\"getColumnKey(col, ci)\"\n :style=\"colWidthStyle(col)\"\n />\n </colgroup>\n\n <!-- Header -->\n <thead v-if=\"showHeader\" class=\"b-table__thead\" :style=\"stickyHeaderStyle\">\n <tr\n v-for=\"(row, ri) in headerRows\"\n :key=\"ri\"\n class=\"b-table__tr b-table__tr--header\"\n v-bind=\"onHeaderRow?.(columns, ri)\"\n >\n <!-- Select-all cell (first header row only) -->\n <th\n v-if=\"rowSelection && ri === 0\"\n class=\"b-table__th b-table__th--selection\"\n :rowspan=\"headerRows.length\"\n scope=\"col\"\n :aria-label=\"resolvedLocale.selectAll\"\n >\n <span v-if=\"rowSelection.columnTitle\">{{ rowSelection.columnTitle }}</span>\n <label\n v-else-if=\"isCheckbox && !rowSelection.hideSelectAll\"\n class=\"b-table__checkbox-wrapper\"\n >\n <input\n type=\"checkbox\"\n class=\"b-table__checkbox\"\n :checked=\"allPageSelected\"\n :indeterminate=\"somePageSelected\"\n :aria-label=\"resolvedLocale.selectAll\"\n @change=\"toggleAllPage($event)\"\n />\n <span\n class=\"b-table__checkbox-inner\"\n :class=\"{\n 'b-table__checkbox-inner--indeterminate': somePageSelected,\n 'b-table__checkbox-inner--checked': allPageSelected,\n }\"\n aria-hidden=\"true\"\n />\n </label>\n </th>\n\n <!-- Expand cell (first header row only) -->\n <th\n v-if=\"expandable && expandable.showExpandColumn !== false && ri === 0\"\n class=\"b-table__th b-table__th--expand\"\n :rowspan=\"headerRows.length\"\n scope=\"col\"\n aria-hidden=\"true\"\n />\n\n <!-- Column header cells -->\n <th\n v-for=\"cell in row\"\n :key=\"getColumnKey(cell.col, 0)\"\n class=\"b-table__th\"\n :class=\"{\n 'b-table__th--sorted': getColumnSortOrder(cell.col) !== null,\n 'b-table__th--sortable': !!cell.col.sorter,\n 'b-table__th--filtered': (getColumnFilterValue(cell.col)?.length ?? 0) > 0,\n [`b-table__th--align-${cell.col.align ?? 'left'}`]: true,\n [cell.col.className ?? '']: !!cell.col.className,\n }\"\n :colspan=\"cell.colSpan > 1 ? cell.colSpan : undefined\"\n :rowspan=\"cell.rowSpan > 1 ? cell.rowSpan : undefined\"\n :style=\"colWidthStyle(cell.col)\"\n scope=\"col\"\n :aria-sort=\"\n cell.col.sorter\n ? getColumnSortOrder(cell.col) === 'ascend'\n ? 'ascending'\n : getColumnSortOrder(cell.col) === 'descend'\n ? 'descending'\n : 'none'\n : undefined\n \"\n v-bind=\"onHeaderRow?.(columns, 0)\"\n >\n <div class=\"b-table__th-inner\">\n <!-- Title -->\n <span\n class=\"b-table__col-title\"\n :class=\"{ 'b-table__col-title--rowspan': cell.rowSpan > 1 }\"\n >{{ cell.col.titleText ?? cell.col.title }}</span\n >\n\n <!-- Sorter -->\n <button\n v-if=\"cell.col.sorter && cell.isLeaf\"\n class=\"b-table__sorter\"\n type=\"button\"\n :aria-label=\"`${resolvedLocale.sortTitle}: ${cell.col.titleText ?? cell.col.title}`\"\n :data-sort=\"getSortIcon(cell.col)\"\n @click.stop=\"handleSort(cell.col)\"\n >\n <span class=\"b-table__sorter-inner\" aria-hidden=\"true\">\n <svg\n class=\"b-table__sort-up\"\n :class=\"{ active: getSortIcon(cell.col) === 'asc' }\"\n viewBox=\"0 0 1024 1024\"\n width=\"1em\"\n height=\"1em\"\n fill=\"currentColor\"\n aria-hidden=\"true\"\n >\n <path\n d=\"M858.9 689L530.5 308.2a24 24 0 0 0-36.9 0L165.1 689c-4.7 5.2-.4 13 6.5 13h496.8c6.9 0 11.2-7.8 6.5-13z\"\n />\n </svg>\n <svg\n class=\"b-table__sort-down\"\n :class=\"{ active: getSortIcon(cell.col) === 'desc' }\"\n viewBox=\"0 0 1024 1024\"\n width=\"1em\"\n height=\"1em\"\n fill=\"currentColor\"\n aria-hidden=\"true\"\n >\n <path\n d=\"M840.4 300H183.6c-19.7 0-30.7 20.8-18.5 35l328.4 380.8c9.4 10.9 27.5 10.9 37 0L858.9 335c12.2-14.2 1.2-35-18.5-35z\"\n />\n </svg>\n </span>\n </button>\n\n <!-- Filter trigger -->\n <div\n v-if=\"cell.col.filters && cell.isLeaf\"\n class=\"b-table__filter-trigger\"\n :class=\"{\n 'b-table__filter-trigger--active':\n (getColumnFilterValue(cell.col)?.length ?? 0) > 0 ||\n openFilterKey === getColumnFilterKey(cell.col),\n }\"\n >\n <button\n type=\"button\"\n class=\"b-table__filter-btn\"\n :aria-expanded=\"\n openFilterKey === getColumnFilterKey(cell.col) ? 'true' : 'false'\n \"\n :aria-label=\"`Filter ${cell.col.titleText ?? cell.col.title}`\"\n @click.stop=\"openFilter(cell.col)\"\n >\n <svg\n viewBox=\"0 0 1024 1024\"\n width=\"1em\"\n height=\"1em\"\n fill=\"currentColor\"\n aria-hidden=\"true\"\n >\n <path\n d=\"M880.1 154H143.9c-24.5 0-39.8 26.7-27.5 48L349 597.4V838c0 17.8 14.5 32 32.3 32h263.4c17.8 0 32.3-14.2 32.3-32V597.4L909.6 202c12.3-21.3-3-48-29.5-48z\"\n />\n </svg>\n </button>\n\n <!-- Filter dropdown -->\n <div\n v-if=\"openFilterKey === getColumnFilterKey(cell.col)\"\n class=\"b-table__filter-dropdown\"\n role=\"dialog\"\n :aria-label=\"`Filter ${cell.col.titleText ?? cell.col.title}`\"\n @keydown=\"handleFilterKeydown\"\n >\n <!-- Search input -->\n <div v-if=\"cell.col.filterSearch\" class=\"b-table__filter-search\">\n <input\n v-model=\"filterSearchMap[getColumnFilterKey(cell.col)]\"\n type=\"search\"\n class=\"b-table__filter-search-input\"\n placeholder=\"Search...\"\n aria-label=\"Search filter options\"\n />\n </div>\n\n <!-- Options -->\n <ul\n class=\"b-table__filter-list\"\n role=\"listbox\"\n :aria-multiselectable=\"cell.col.filterMultiple !== false ? 'true' : 'false'\"\n >\n <li\n v-for=\"opt in getFilteredOptions(cell.col)\"\n :key=\"String(opt.value)\"\n class=\"b-table__filter-item\"\n role=\"option\"\n :aria-selected=\"\n (tempFilterValues[getColumnFilterKey(cell.col)] ?? []).includes(opt.value)\n \"\n >\n <label class=\"b-table__filter-item-label\">\n <input\n :type=\"cell.col.filterMultiple !== false ? 'checkbox' : 'radio'\"\n class=\"b-table__filter-item-input\"\n :checked=\"\n (tempFilterValues[getColumnFilterKey(cell.col)] ?? []).includes(\n opt.value,\n )\n \"\n @change=\"toggleTempFilter(cell.col, opt.value)\"\n />\n <span>{{ opt.text }}</span>\n </label>\n </li>\n </ul>\n\n <!-- Actions -->\n <div class=\"b-table__filter-actions\">\n <button\n type=\"button\"\n class=\"b-table__filter-reset\"\n @click=\"resetFilter(cell.col)\"\n >\n {{ resolvedLocale.filterReset }}\n </button>\n <button\n type=\"button\"\n class=\"b-table__filter-confirm\"\n @click=\"confirmFilter(cell.col)\"\n >\n {{ resolvedLocale.filterConfirm }}\n </button>\n </div>\n </div>\n </div>\n </div>\n </th>\n </tr>\n </thead>\n\n <!-- Body -->\n <tbody class=\"b-table__tbody\">\n <!-- Empty state -->\n <tr v-if=\"processedData.length === 0\" class=\"b-table__tr--empty\">\n <td\n class=\"b-table__td b-table__td--empty\"\n :colspan=\"\n flatColumns.length +\n (rowSelection ? 1 : 0) +\n (expandable && expandable.showExpandColumn !== false ? 1 : 0)\n \"\n >\n <slot name=\"emptyText\">\n <div class=\"b-table__empty\">\n <svg\n class=\"b-table__empty-icon\"\n viewBox=\"0 0 64 41\"\n xmlns=\"http://www.w3.org/2000/svg\"\n aria-hidden=\"true\"\n >\n <g transform=\"translate(0 1)\" fill=\"none\" fill-rule=\"evenodd\">\n <ellipse cx=\"32\" cy=\"33\" rx=\"32\" ry=\"7\" fill=\"currentColor\" opacity=\".2\" />\n <g fill-rule=\"nonzero\" stroke=\"currentColor\">\n <path\n d=\"M55 12.76L44.854 1.258C44.367.474 43.656 0 42.907 0H21.093c-.749 0-1.46.474-1.947 1.257L9 12.761V22h46v-9.24z\"\n />\n <path\n d=\"M41.613 15.931c0-1.605.994-2.93 2.227-2.931H55v18.137C55 33.26 53.68 35 52.05 35h-40.1C10.32 35 9 33.259 9 31.137V13h11.16c1.233 0 2.227 1.323 2.227 2.928v.022c0 1.605 1.005 2.901 2.237 2.901h14.752c1.232 0 2.237-1.308 2.237-2.913v-.007z\"\n fill=\"currentColor\"\n opacity=\".08\"\n />\n </g>\n </g>\n </svg>\n <p class=\"b-table__empty-text\">{{ resolvedLocale.emptyText }}</p>\n </div>\n </slot>\n </td>\n </tr>\n\n <!-- Data rows -->\n <template v-for=\"(record, rowIndex) in processedData\" :key=\"getRowKey(record, rowIndex)\">\n <tr\n class=\"b-table__tr b-table__tr--data\"\n :class=\"[\n rowClassName?.(record, rowIndex),\n {\n 'b-table__tr--selected': selectedKeys.has(getRowKey(record, rowIndex)),\n 'b-table__tr--expanded': isRowExpanded(record, rowIndex),\n },\n ]\"\n v-bind=\"onRow?.(record, rowIndex)\"\n >\n <!-- Selection cell -->\n <td v-if=\"rowSelection\" class=\"b-table__td b-table__td--selection\">\n <label class=\"b-table__checkbox-wrapper\">\n <input\n :type=\"isCheckbox ? 'checkbox' : 'radio'\"\n class=\"b-table__checkbox\"\n :checked=\"selectedKeys.has(getRowKey(record, rowIndex))\"\n :disabled=\"!!rowSelection.getCheckboxProps?.(record)?.disabled\"\n :aria-label=\"`Select row ${rowIndex + 1}`\"\n v-bind=\"rowSelection.getCheckboxProps?.(record)\"\n @change=\"toggleRow(record, rowIndex, $event)\"\n />\n <span\n class=\"b-table__checkbox-inner\"\n :class=\"{\n 'b-table__checkbox-inner--checked': selectedKeys.has(\n getRowKey(record, rowIndex),\n ),\n }\"\n aria-hidden=\"true\"\n />\n </label>\n </td>\n\n <!-- Expand toggle cell -->\n <td\n v-if=\"expandable && expandable.showExpandColumn !== false\"\n class=\"b-table__td b-table__td--expand\"\n >\n <button\n v-if=\"isRowExpandable(record)\"\n type=\"button\"\n class=\"b-table__expand-btn\"\n :class=\"{ 'b-table__expand-btn--expanded': isRowExpanded(record, rowIndex) }\"\n :aria-label=\"\n isRowExpanded(record, rowIndex)\n ? resolvedLocale.collapse\n : resolvedLocale.expand\n \"\n :aria-expanded=\"isRowExpanded(record, rowIndex) ? 'true' : 'false'\"\n @click=\"toggleExpand(record, rowIndex, $event)\"\n >\n <svg\n class=\"b-table__expand-icon\"\n viewBox=\"0 0 1024 1024\"\n width=\"1em\"\n height=\"1em\"\n fill=\"currentColor\"\n aria-hidden=\"true\"\n >\n <path\n d=\"M765.7 486.8L314.9 134.7A7.97 7.97 0 0 0 302 141v77.3c0 4.9 2.3 9.6 6.1 12.6l360 281.1-360 281.1c-3.9 3-6.1 7.7-6.1 12.6V883c0 6.7 7.7 10.4 12.9 6.3l450.8-352.1a31.96 31.96 0 0 0 0-50.4z\"\n />\n </svg>\n </button>\n </td>\n\n <!-- Data cells -->\n <td\n v-for=\"(col, ci) in flatColumns\"\n :key=\"getColumnKey(col, ci)\"\n class=\"b-table__td\"\n :class=\"{\n [`b-table__td--align-${col.align ?? 'left'}`]: true,\n 'b-table__td--ellipsis': getEllipsis(col),\n [col.className ?? '']: !!col.className,\n }\"\n :style=\"[colWidthStyle(col), getCellStyle(col, record, rowIndex)]\"\n >\n <!-- Slot-based rendering (by column key) -->\n <slot\n v-if=\"$slots[String(getColumnKey(col, ci))]\"\n :name=\"String(getColumnKey(col, ci))\"\n :value=\"getCellValue(record, col)\"\n :record=\"record\"\n :index=\"rowIndex\"\n :column=\"col\"\n />\n <!-- customRender function -->\n <component\n v-else-if=\"col.customRender\"\n :is=\"\n () =>\n col.customRender!({\n value: getCellValue(record, col),\n record,\n index: rowIndex,\n column: col,\n })\n \"\n />\n <!-- Default: raw cell value -->\n <template v-else>\n <span\n v-if=\"getEllipsis(col)\"\n class=\"b-table__cell-ellipsis\"\n :title=\"\n typeof col.ellipsis === 'object' && col.ellipsis.showTitle !== false\n ? String(getCellValue(record, col) ?? '')\n : undefined\n \"\n >{{ getCellValue(record, col) }}</span\n >\n <template v-else>{{ getCellValue(record, col) }}</template>\n </template>\n </td>\n </tr>\n\n <!-- Expanded row content -->\n <tr\n v-if=\"expandable && isRowExpanded(record, rowIndex)\"\n class=\"b-table__tr b-table__tr--expanded-content\"\n :key=\"`${getRowKey(record, rowIndex)}-expanded`\"\n >\n <td\n :colspan=\"\n flatColumns.length +\n (rowSelection ? 1 : 0) +\n (expandable && expandable.showExpandColumn !== false ? 1 : 0)\n \"\n class=\"b-table__td b-table__td--expanded\"\n >\n <component\n v-if=\"expandable.expandedRowRender\"\n :is=\"\n () =>\n expandable.expandedRowRender!(\n record,\n rowIndex,\n 0,\n isRowExpanded(record, rowIndex),\n )\n \"\n />\n <slot v-else name=\"expandedRow\" :record=\"record\" :index=\"rowIndex\" />\n </td>\n </tr>\n </template>\n\n <!-- Summary slot -->\n <slot name=\"summary\" :page-data=\"processedData\" />\n </tbody>\n </table>\n </div>\n\n <!-- Footer slot -->\n <div\n v-if=\"$slots.footer\"\n class=\"b-table__footer\"\n :aria-hidden=\"loading ? 'true' : undefined\"\n :style=\"loading ? { visibility: 'hidden' } : {}\"\n >\n <slot name=\"footer\" />\n </div>\n\n <!-- Pagination -->\n <div\n v-if=\"pagination !== false && filteredData.length > 0\"\n class=\"b-table__pagination\"\n role=\"navigation\"\n aria-label=\"Table pagination\"\n :aria-hidden=\"loading ? 'true' : undefined\"\n :style=\"loading ? { visibility: 'hidden' } : {}\"\n >\n <!-- Total info -->\n <span\n v-if=\"typeof pagination === 'object' && pagination.showTotal\"\n class=\"b-table__pagination-total\"\n >\n {{\n pagination.showTotal(filteredData.length, [\n (currentPage - 1) * currentPageSize + 1,\n Math.min(currentPage * currentPageSize, filteredData.length),\n ])\n }}\n </span>\n\n <div class=\"b-table__pagination-controls\">\n <!-- Prev -->\n <button\n type=\"button\"\n class=\"b-table__page-btn\"\n :disabled=\"currentPage <= 1\"\n :aria-label=\"'Previous page'\"\n @click=\"goToPage(currentPage - 1)\"\n >\n <svg\n viewBox=\"0 0 1024 1024\"\n width=\"1em\"\n height=\"1em\"\n fill=\"currentColor\"\n aria-hidden=\"true\"\n >\n <path\n d=\"M724 218.3V141c0-6.7-7.7-10.4-12.9-6.3L260.3 486.8a31.96 31.96 0 0 0 0 50.3l450.8 352.1c5.3 4.1 12.9.4 12.9-6.3v-77.3c0-4.9-2.3-9.6-6.1-12.6l-360-281 360-281.1c3.8-3 6.1-7.7 6.1-12.6z\"\n />\n </svg>\n </button>\n\n <!-- Page numbers -->\n <template v-for=\"page in totalPages\" :key=\"page\">\n <button\n v-if=\"\n totalPages <= 7 ||\n page === 1 ||\n page === totalPages ||\n Math.abs(page - currentPage) <= 1\n \"\n type=\"button\"\n class=\"b-table__page-btn\"\n :class=\"{ 'b-table__page-btn--active': page === currentPage }\"\n :aria-label=\"`Page ${page}`\"\n :aria-current=\"page === currentPage ? 'page' : undefined\"\n @click=\"goToPage(page)\"\n >\n {{ page }}\n </button>\n <span\n v-else-if=\"page === 2 && currentPage > 4\"\n class=\"b-table__page-ellipsis\"\n aria-hidden=\"true\"\n >…</span\n >\n <span\n v-else-if=\"page === totalPages - 1 && currentPage < totalPages - 3\"\n class=\"b-table__page-ellipsis\"\n aria-hidden=\"true\"\n >…</span\n >\n </template>\n\n <!-- Next -->\n <button\n type=\"button\"\n class=\"b-table__page-btn\"\n :disabled=\"currentPage >= totalPages\"\n :aria-label=\"'Next page'\"\n @click=\"goToPage(currentPage + 1)\"\n >\n <svg\n viewBox=\"0 0 1024 1024\"\n width=\"1em\"\n height=\"1em\"\n fill=\"currentColor\"\n aria-hidden=\"true\"\n >\n <path\n d=\"M765.7 486.8L314.9 134.7A7.97 7.97 0 0 0 302 141v77.3c0 4.9 2.3 9.6 6.1 12.6l360 281.1-360 281.1c-3.9 3-6.1 7.7-6.1 12.6V883c0 6.7 7.7 10.4 12.9 6.3l450.8-352.1a31.96 31.96 0 0 0 0-50.4z\"\n />\n </svg>\n </button>\n\n <!-- Page size changer -->\n <select\n v-if=\"typeof pagination === 'object' && pagination.showSizeChanger\"\n class=\"b-table__page-size\"\n :value=\"currentPageSize\"\n :aria-label=\"'Rows per page'\"\n @change=\"changePageSize(Number(($event.target as HTMLSelectElement).value))\"\n >\n <option v-for=\"ps in pageSizeOptions\" :key=\"ps\" :value=\"ps\">{{ ps }} / page</option>\n </select>\n\n <!-- Quick jump -->\n <span\n v-if=\"typeof pagination === 'object' && pagination.showQuickJumper\"\n class=\"b-table__page-jump\"\n >\n Go to\n <input\n v-model=\"jumpInput\"\n type=\"number\"\n class=\"b-table__page-jump-input\"\n :min=\"1\"\n :max=\"totalPages\"\n aria-label=\"Go to page\"\n @keydown=\"handleJumpKeydown\"\n />\n </span>\n </div>\n </div>\n </div>\n</template>\n\n<style>\n/* ─────────────────────────────────────────────\n BTable - CSS custom properties (scoped to root)\n ───────────────────────────────────────────── */\n.b-table {\n /* Colors */\n --b-table-bg: oklch(100% 0 0);\n --b-table-color: oklch(20% 0.02 260);\n --b-table-border-color: oklch(88% 0.01 260);\n --b-table-header-bg: oklch(97% 0.005 260);\n --b-table-header-color: oklch(30% 0.02 260);\n --b-table-row-hover-bg: oklch(96% 0.01 260);\n --b-table-row-selected-bg: oklch(94% 0.03 262);\n --b-table-row-expanded-bg: oklch(98% 0.005 260);\n --b-table-empty-color: oklch(48% 0.01 260);\n --b-table-footer-bg: oklch(97% 0.005 260);\n --b-table-loading-overlay-bg: oklch(100% 0 0);\n --b-table-sticky-z-index: 2;\n\n /* Sort / filter */\n --b-table-sorter-color: oklch(65% 0.01 260);\n --b-table-sorter-active-color: oklch(54.6% 0.245 262.881);\n --b-table-filter-active-color: oklch(54.6% 0.245 262.881);\n --b-table-filter-dropdown-bg: oklch(100% 0 0);\n --b-table-filter-dropdown-shadow: 0 6px 16px oklch(0% 0 0 / 8%), 0 3px 6px oklch(0% 0 0 / 4%);\n\n /* Checkbox */\n --b-table-checkbox-size: 16px;\n --b-table-checkbox-border: 1px solid oklch(75% 0.01 260);\n --b-table-checkbox-checked-bg: oklch(54.6% 0.245 262.881);\n --b-table-checkbox-checked-border: oklch(54.6% 0.245 262.881);\n --b-table-checkbox-radius: 4px;\n\n /* Expand */\n --b-table-expand-icon-size: 17px;\n --b-table-expand-icon-color: oklch(45% 0.02 260);\n --b-table-expand-icon-hover-color: oklch(54.6% 0.245 262.881);\n\n /* Spacing */\n --b-table-cell-padding: 12px 16px;\n --b-table-cell-padding-middle: 8px 16px;\n --b-table-cell-padding-small: 4px 8px;\n --b-table-font-size: 14px;\n --b-table-border-radius: 8px;\n\n /* Pagination */\n --b-table-pagination-color: oklch(30% 0.02 260);\n --b-table-pagination-btn-size: 32px;\n --b-table-pagination-btn-radius: 6px;\n --b-table-pagination-btn-border: 1px solid oklch(88% 0.01 260);\n --b-table-pagination-btn-active-bg: oklch(54.6% 0.245 262.881);\n --b-table-pagination-btn-active-color: oklch(100% 0 0);\n --b-table-pagination-btn-hover-bg: oklch(95% 0.01 262);\n\n /* Spin */\n --b-table-spin-color: oklch(54.6% 0.245 262.881);\n --b-table-spin-size: 32px;\n\n /* Transition */\n --b-table-transition-duration: 200ms;\n\n position: relative;\n font-size: var(--b-table-font-size);\n color: var(--b-table-color);\n background: var(--b-table-bg);\n border-radius: var(--b-table-border-radius);\n line-height: 1.5715;\n box-sizing: border-box;\n}\n\n/* ── Scroll wrapper ── */\n.b-table__scroll-wrapper {\n width: 100%;\n overflow: auto;\n -webkit-overflow-scrolling: touch;\n background: var(--b-table-bg);\n}\n\n.b-table__scroll-wrapper:focus-visible {\n outline: 2px solid oklch(54.6% 0.245 262.881);\n outline-offset: -2px;\n}\n\n/* ── Table element ── */\n.b-table__table {\n width: 100%;\n border-collapse: collapse;\n table-layout: auto;\n background: var(--b-table-bg);\n}\n\n/* ── Caption ── */\n.b-table__caption {\n /* Visually hidden - accessible label is surfaced via aria-label on the region landmark */\n position: absolute;\n width: 1px;\n height: 1px;\n padding: 0;\n margin: -1px;\n overflow: hidden;\n clip: rect(0, 0, 0, 0);\n white-space: nowrap;\n border: 0;\n background: var(--b-table-bg);\n}\n\n/* ── Header ── */\n.b-table__thead {\n background: var(--b-table-header-bg);\n}\n\n/* Explicit background on every header <tr> so axe's elementsFromPoint algorithm\n can resolve the background when a rowspan-2 th's text sits on the row boundary\n and the sibling <tr> appears as a non-ancestor in the z-stack. */\n.b-table__tr--header {\n background: var(--b-table-header-bg);\n}\n\n.b-table__th {\n padding: var(--b-table-cell-padding);\n font-weight: 600;\n color: var(--b-table-header-color);\n text-align: left;\n vertical-align: top;\n white-space: nowrap;\n border-bottom: 1px solid var(--b-table-border-color);\n box-sizing: border-box;\n background: var(--b-table-header-bg);\n}\n\n.b-table--middle .b-table__th {\n padding: var(--b-table-cell-padding-middle);\n}\n\n.b-table--small .b-table__th {\n padding: var(--b-table-cell-padding-small);\n}\n\n.b-table__th--align-center {\n text-align: center;\n}\n.b-table__th--align-right {\n text-align: right;\n}\n\n.b-table__th-inner {\n display: flex;\n align-items: center;\n gap: 4px;\n width: 100%;\n background: var(--b-table-header-bg);\n}\n\n.b-table__col-title {\n flex: 1;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n background: var(--b-table-header-bg);\n}\n\n/* Rowspan headers span multiple rows; removing overflow:hidden prevents axe's\n elementsFromPoint from finding the sibling <tr> as an unresolvable overlay. */\n.b-table__col-title--rowspan {\n overflow: visible;\n}\n\n/* ── Sorter ── */\n.b-table__sorter {\n display: inline-flex;\n flex-direction: column;\n align-items: center;\n cursor: pointer;\n color: var(--b-table-sorter-color);\n background: none;\n border: none;\n padding: 0;\n gap: 0;\n flex-shrink: 0;\n transition: color var(--b-table-transition-duration);\n}\n\n.b-table__sorter:hover,\n.b-table__th--sorted .b-table__sorter {\n color: var(--b-table-sorter-active-color);\n}\n\n.b-table__sorter-inner {\n display: flex;\n flex-direction: column;\n line-height: 1;\n}\n\n.b-table__sort-up,\n.b-table__sort-down {\n display: block;\n width: 11px;\n height: 7px;\n color: var(--b-table-sorter-color);\n transition: color var(--b-table-transition-duration);\n}\n\n.b-table__sort-up.active {\n color: var(--b-table-sorter-active-color);\n}\n.b-table__sort-down.active {\n color: var(--b-table-sorter-active-color);\n}\n\n/* ── Filter ── */\n.b-table__filter-trigger {\n position: relative;\n display: inline-flex;\n flex-shrink: 0;\n}\n\n.b-table__filter-btn {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n cursor: pointer;\n color: var(--b-table-sorter-color);\n background: none;\n border: none;\n padding: 2px 4px;\n border-radius: 4px;\n transition:\n color var(--b-table-transition-duration),\n background var(--b-table-transition-duration);\n}\n\n.b-table__filter-btn:hover,\n.b-table__filter-trigger--active .b-table__filter-btn {\n color: var(--b-table-filter-active-color);\n background: oklch(94% 0.02 262);\n}\n\n.b-table__filter-dropdown {\n position: absolute;\n top: calc(100% + 4px);\n left: 0;\n z-index: 10;\n min-width: 160px;\n background: var(--b-table-filter-dropdown-bg);\n border-radius: 8px;\n box-shadow: var(--b-table-filter-dropdown-shadow);\n padding: 4px 0;\n outline: none;\n}\n\n.b-table__filter-search {\n padding: 8px 12px;\n border-bottom: 1px solid var(--b-table-border-color);\n}\n\n.b-table__filter-search-input {\n width: 100%;\n border: 1px solid var(--b-table-border-color);\n border-radius: 4px;\n padding: 4px 8px;\n font-size: 13px;\n outline: none;\n background: var(--b-table-bg);\n color: var(--b-table-color);\n box-sizing: border-box;\n}\n\n.b-table__filter-search-input:focus {\n border-color: var(--b-table-sorter-active-color);\n box-shadow: 0 0 0 2px oklch(54.6% 0.245 262.881 / 20%);\n}\n\n.b-table__filter-list {\n list-style: none;\n margin: 0;\n padding: 4px 0;\n max-height: 200px;\n overflow-y: auto;\n}\n\n.b-table__filter-item {\n padding: 0;\n}\n\n.b-table__filter-item-label {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 6px 12px;\n cursor: pointer;\n font-size: 13px;\n transition: background var(--b-table-transition-duration);\n}\n\n.b-table__filter-item-label:hover {\n background: var(--b-table-row-hover-bg);\n}\n\n.b-table__filter-item-input {\n accent-color: var(--b-table-sorter-active-color);\n}\n\n.b-table__filter-actions {\n display: flex;\n justify-content: space-between;\n padding: 8px 12px;\n border-top: 1px solid var(--b-table-border-color);\n gap: 8px;\n}\n\n.b-table__filter-reset {\n background: none;\n border: none;\n cursor: pointer;\n font-size: 13px;\n color: var(--b-table-color);\n padding: 4px 8px;\n border-radius: 4px;\n transition: background var(--b-table-transition-duration);\n}\n\n.b-table__filter-reset:hover {\n background: var(--b-table-row-hover-bg);\n}\n\n.b-table__filter-confirm {\n background: oklch(54.6% 0.245 262.881);\n color: oklch(100% 0 0);\n border: none;\n cursor: pointer;\n font-size: 13px;\n padding: 4px 12px;\n border-radius: 4px;\n transition: background var(--b-table-transition-duration);\n}\n\n.b-table__filter-confirm:hover {\n background: oklch(49% 0.24 262.881);\n}\n\n/* ── Selection cells ── */\n.b-table__th--selection,\n.b-table__td--selection {\n text-align: center;\n padding: 0 !important;\n width: 48px;\n}\n\n.b-table__checkbox-wrapper {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n cursor: pointer;\n position: relative;\n padding: 0 8px;\n height: 100%;\n}\n\n.b-table__checkbox {\n /* Visually hidden but accessible */\n position: absolute;\n opacity: 0;\n width: 0;\n height: 0;\n margin: 0;\n}\n\n.b-table__checkbox:focus-visible + .b-table__checkbox-inner {\n outline: 2px solid oklch(54.6% 0.245 262.881);\n outline-offset: 2px;\n}\n\n.b-table__checkbox-inner {\n display: inline-block;\n width: var(--b-table-checkbox-size);\n height: var(--b-table-checkbox-size);\n border: var(--b-table-checkbox-border);\n border-radius: var(--b-table-checkbox-radius);\n background: var(--b-table-bg);\n transition:\n border-color var(--b-table-transition-duration),\n background var(--b-table-transition-duration);\n position: relative;\n flex-shrink: 0;\n}\n\n.b-table__checkbox-inner--checked {\n background: var(--b-table-checkbox-checked-bg);\n border-color: var(--b-table-checkbox-checked-border);\n}\n\n/* Checkmark */\n.b-table__checkbox-inner--checked::after {\n content: '';\n position: absolute;\n top: 2px;\n left: 5px;\n width: 4px;\n height: 8px;\n border: 2px solid oklch(100% 0 0);\n border-top: none;\n border-left: none;\n transform: rotate(45deg);\n}\n\n/* Indeterminate dash */\n.b-table__checkbox-inner--indeterminate {\n background: var(--b-table-checkbox-checked-bg);\n border-color: var(--b-table-checkbox-checked-border);\n}\n\n.b-table__checkbox-inner--indeterminate::after {\n content: '';\n position: absolute;\n top: 50%;\n left: 50%;\n width: 8px;\n height: 2px;\n background: oklch(100% 0 0);\n transform: translate(-50%, -50%);\n border: none;\n}\n\n/* ── Expand toggle cell ── */\n.b-table__th--expand,\n.b-table__td--expand {\n text-align: center;\n width: 48px;\n padding: 0 !important;\n}\n\n.b-table__expand-btn {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: var(--b-table-expand-icon-size);\n height: var(--b-table-expand-icon-size);\n border: 1px solid var(--b-table-border-color);\n border-radius: 4px;\n background: var(--b-table-bg);\n cursor: pointer;\n color: var(--b-table-expand-icon-color);\n padding: 0;\n transition:\n color var(--b-table-transition-duration),\n border-color var(--b-table-transition-duration);\n}\n\n.b-table__expand-btn:hover {\n color: var(--b-table-expand-icon-hover-color);\n border-color: var(--b-table-expand-icon-hover-color);\n}\n\n.b-table__expand-icon {\n transition: transform var(--b-table-transition-duration);\n}\n\n.b-table__expand-btn--expanded .b-table__expand-icon {\n transform: rotate(90deg);\n}\n\n/* ── Body cells ── */\n.b-table__td {\n padding: var(--b-table-cell-padding);\n border-bottom: 1px solid var(--b-table-border-color);\n background: var(--b-table-bg);\n color: var(--b-table-color);\n box-sizing: border-box;\n vertical-align: middle;\n transition: background var(--b-table-transition-duration);\n}\n\n.b-table--middle .b-table__td {\n padding: var(--b-table-cell-padding-middle);\n}\n.b-table--small .b-table__td {\n padding: var(--b-table-cell-padding-small);\n}\n\n.b-table__td--align-center {\n text-align: center;\n}\n.b-table__td--align-right {\n text-align: right;\n}\n\n.b-table__td--ellipsis {\n overflow: hidden;\n}\n\n.b-table__cell-ellipsis {\n display: block;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n/* ── Row states ── */\n.b-table__tr--data:hover > .b-table__td {\n background: var(--b-table-row-hover-bg);\n}\n\n.b-table__tr--selected > .b-table__td {\n background: var(--b-table-row-selected-bg);\n}\n\n.b-table__tr--expanded-content > .b-table__td--expanded {\n background: var(--b-table-row-expanded-bg);\n}\n\n/* ── Bordered ── */\n.b-table--bordered {\n border: 1px solid var(--b-table-border-color);\n border-radius: var(--b-table-border-radius);\n overflow: hidden;\n}\n\n.b-table--bordered .b-table__th,\n.b-table--bordered .b-table__td {\n border-right: 1px solid var(--b-table-border-color);\n}\n\n.b-table--bordered .b-table__th:last-child,\n.b-table--bordered .b-table__td:last-child {\n border-right: none;\n}\n\n/* ── Empty ── */\n.b-table__td--empty {\n padding: 48px 16px;\n text-align: center;\n border-bottom: none;\n}\n\n.b-table__empty {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 8px;\n}\n\n.b-table__empty-icon {\n width: 64px;\n height: 41px;\n color: var(--b-table-empty-color);\n}\n\n.b-table__empty-text {\n margin: 0;\n color: var(--b-table-empty-color);\n font-size: 14px;\n}\n\n/* ── Title / Footer ── */\n.b-table__title,\n.b-table__footer {\n padding: 12px 16px;\n background: var(--b-table-footer-bg);\n border: 1px solid var(--b-table-border-color);\n}\n\n.b-table__title {\n border-bottom: none;\n border-radius: var(--b-table-border-radius) var(--b-table-border-radius) 0 0;\n}\n.b-table__footer {\n border-top: none;\n border-radius: 0 0 var(--b-table-border-radius) var(--b-table-border-radius);\n}\n\n/* ── Loading ── */\n.b-table__loading-overlay {\n position: absolute;\n inset: 0;\n z-index: 4;\n display: flex;\n align-items: center;\n justify-content: center;\n background: var(--b-table-loading-overlay-bg);\n border-radius: var(--b-table-border-radius);\n}\n\n.b-table__spin {\n display: inline-flex;\n gap: 4px;\n align-items: center;\n}\n\n.b-table__spin-dot {\n display: inline-block;\n width: 8px;\n height: 8px;\n border-radius: 50%;\n background: var(--b-table-spin-color);\n animation: b-table-spin-bounce 1.2s ease-in-out infinite;\n}\n\n.b-table__spin-dot:nth-child(1) {\n animation-delay: 0ms;\n}\n.b-table__spin-dot:nth-child(2) {\n animation-delay: 160ms;\n}\n.b-table__spin-dot:nth-child(3) {\n animation-delay: 320ms;\n}\n.b-table__spin-dot:nth-child(4) {\n animation-delay: 480ms;\n}\n\n@keyframes b-table-spin-bounce {\n 0%,\n 60%,\n 100% {\n transform: scale(1);\n opacity: 0.5;\n }\n 30% {\n transform: scale(1.4);\n opacity: 1;\n }\n}\n\n/* ── Pagination ── */\n.b-table__pagination {\n display: flex;\n align-items: center;\n justify-content: flex-end;\n gap: 8px;\n padding: 12px 0 4px;\n flex-wrap: wrap;\n color: var(--b-table-pagination-color);\n font-size: 14px;\n}\n\n.b-table__pagination-total {\n margin-right: auto;\n}\n\n.b-table__pagination-controls {\n display: flex;\n align-items: center;\n gap: 4px;\n flex-wrap: wrap;\n}\n\n.b-table__page-btn {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n min-width: var(--b-table-pagination-btn-size);\n height: var(--b-table-pagination-btn-size);\n padding: 0 8px;\n border: var(--b-table-pagination-btn-border);\n border-radius: var(--b-table-pagination-btn-radius);\n background: var(--b-table-bg);\n color: var(--b-table-pagination-color);\n cursor: pointer;\n font-size: 14px;\n transition:\n background var(--b-table-transition-duration),\n border-color var(--b-table-transition-duration),\n color var(--b-table-transition-duration);\n}\n\n.b-table__page-btn:hover:not(:disabled) {\n background: var(--b-table-pagination-btn-hover-bg);\n border-color: oklch(54.6% 0.245 262.881);\n color: oklch(54.6% 0.245 262.881);\n}\n\n.b-table__page-btn:disabled {\n opacity: 0.4;\n cursor: not-allowed;\n}\n\n.b-table__page-btn--active {\n background: var(--b-table-pagination-btn-active-bg);\n border-color: var(--b-table-pagination-btn-active-bg);\n color: var(--b-table-pagination-btn-active-color);\n font-weight: 600;\n}\n\n.b-table__page-ellipsis {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n min-width: var(--b-table-pagination-btn-size);\n height: var(--b-table-pagination-btn-size);\n font-size: 14px;\n color: var(--b-table-empty-color);\n letter-spacing: 2px;\n}\n\n.b-table__page-size {\n height: var(--b-table-pagination-btn-size);\n border: var(--b-table-pagination-btn-border);\n border-radius: var(--b-table-pagination-btn-radius);\n background: var(--b-table-bg);\n color: var(--b-table-pagination-color);\n padding: 0 24px 0 8px;\n font-size: 14px;\n cursor: pointer;\n outline: none;\n appearance: auto;\n}\n\n.b-table__page-jump {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n font-size: 14px;\n}\n\n.b-table__page-jump-input {\n width: 48px;\n height: var(--b-table-pagination-btn-size);\n border: var(--b-table-pagination-btn-border);\n border-radius: var(--b-table-pagination-btn-radius);\n text-align: center;\n font-size: 14px;\n padding: 0 4px;\n background: var(--b-table-bg);\n color: var(--b-table-pagination-color);\n outline: none;\n box-sizing: border-box;\n}\n\n.b-table__page-jump-input:focus {\n border-color: oklch(54.6% 0.245 262.881);\n box-shadow: 0 0 0 2px oklch(54.6% 0.245 262.881 / 20%);\n}\n\n/* ── Sticky header ── */\n.b-table--sticky .b-table__thead {\n position: sticky;\n top: 0;\n z-index: var(--b-table-sticky-z-index);\n}\n\n/* ── Dark mode ── */\n[data-prefers-color='dark'] .b-table {\n --b-table-bg: oklch(18% 0.015 260);\n --b-table-color: oklch(88% 0.01 260);\n --b-table-border-color: oklch(32% 0.02 260);\n --b-table-header-bg: oklch(22% 0.02 260);\n --b-table-header-color: oklch(85% 0.01 260);\n --b-table-row-hover-bg: oklch(25% 0.02 260);\n --b-table-row-selected-bg: oklch(26% 0.04 262);\n --b-table-row-expanded-bg: oklch(21% 0.015 260);\n --b-table-empty-color: oklch(55% 0.01 260);\n --b-table-footer-bg: oklch(22% 0.02 260);\n --b-table-loading-overlay-bg: oklch(18% 0.015 260);\n --b-table-filter-dropdown-bg: oklch(22% 0.02 260);\n --b-table-filter-dropdown-shadow: 0 6px 16px oklch(0% 0 0 / 30%), 0 3px 6px oklch(0% 0 0 / 20%);\n --b-table-checkbox-border: 1px solid oklch(48% 0.02 260);\n}\n\n@media (prefers-color-scheme: dark) {\n [data-prefers-color='system'] .b-table {\n --b-table-bg: oklch(18% 0.015 260);\n --b-table-color: oklch(88% 0.01 260);\n --b-table-border-color: oklch(32% 0.02 260);\n --b-table-header-bg: oklch(22% 0.02 260);\n --b-table-header-color: oklch(85% 0.01 260);\n --b-table-row-hover-bg: oklch(25% 0.02 260);\n --b-table-row-selected-bg: oklch(26% 0.04 262);\n --b-table-row-expanded-bg: oklch(21% 0.015 260);\n --b-table-empty-color: oklch(55% 0.01 260);\n --b-table-footer-bg: oklch(22% 0.02 260);\n --b-table-loading-overlay-bg: oklch(18% 0.015 260);\n --b-table-filter-dropdown-bg: oklch(22% 0.02 260);\n --b-table-filter-dropdown-shadow: 0 6px 16px oklch(0% 0 0 / 30%), 0 3px 6px oklch(0% 0 0 / 20%);\n --b-table-checkbox-border: 1px solid oklch(48% 0.02 260);\n }\n}\n\n/* ── Reduced motion ── */\n@media (prefers-reduced-motion: reduce) {\n .b-table {\n --b-table-transition-duration: 0ms;\n }\n\n .b-table__spin-dot {\n animation: none;\n }\n\n .b-table__expand-icon {\n transition: none;\n }\n}\n</style>\n"],"mappings":""}
|
|
1
|
+
{"version":3,"file":"design-system241.js","names":[],"sources":["../src/components/BTable/BTable.vue"],"sourcesContent":["<script setup lang=\"ts\" generic=\"T extends Record<string, unknown> = Record<string, unknown>\">\nimport type { CSSProperties } from 'vue';\nimport { computed, nextTick, onMounted, ref, watch } from 'vue';\n\nimport type {\n BTableChangeExtra,\n BTableColumnType,\n BTableExpandable,\n BTableFilterOption,\n BTableFilterState,\n BTableFilterValue,\n BTablePaginationConfig,\n BTableRowSelection,\n BTableScrollConfig,\n BTableSize,\n BTableSortOrder,\n BTableSorterResult,\n} from './types';\n\n// ─────────────────────────────────────────────\n// Props\n// ─────────────────────────────────────────────\n\nconst {\n dataSource = [],\n columns = [],\n rowKey = 'key',\n size = 'default',\n bordered = false,\n loading = false,\n showHeader = true,\n tableLayout,\n scroll,\n pagination = false,\n rowSelection,\n expandable,\n sticky = false,\n caption,\n locale,\n rowClassName,\n onRow,\n onHeaderRow,\n components: _customComponents, \n} = defineProps<{\n /** Data array */\n dataSource?: T[];\n /** Column definitions */\n columns?: BTableColumnType<T>[];\n /** Unique row key - a field name string or extractor function */\n rowKey?: string | ((record: T) => string | number);\n /** Table size */\n size?: BTableSize;\n /** Show outer border and column borders */\n bordered?: boolean;\n /** Loading state */\n loading?: boolean;\n /** Show table header */\n showHeader?: boolean;\n /** Table layout algorithm */\n tableLayout?: 'auto' | 'fixed';\n /** Scroll config */\n scroll?: BTableScrollConfig;\n /** Pagination config; false to disable */\n pagination?: BTablePaginationConfig | false;\n /** Row selection config */\n rowSelection?: BTableRowSelection<T>;\n /** Expandable config */\n expandable?: BTableExpandable<T>;\n /** Make table header sticky */\n sticky?:\n | boolean\n | { offsetHeader?: number; offsetScroll?: number; getContainer?: () => HTMLElement };\n /** Table caption (accessibility) */\n caption?: string;\n /** Custom locale text */\n locale?: {\n emptyText?: string;\n filterConfirm?: string;\n filterReset?: string;\n selectAll?: string;\n selectInvert?: string;\n selectionAll?: string;\n sortTitle?: string;\n expand?: string;\n collapse?: string;\n };\n /** Custom row class name */\n rowClassName?: (record: T, index: number) => string;\n /** Custom row event handlers */\n onRow?: (record: T, index: number) => Record<string, unknown>;\n /** Custom header row event handlers */\n onHeaderRow?: (columns: BTableColumnType<T>[], index: number) => Record<string, unknown>;\n /** Override internal table elements */\n components?: {\n table?: unknown;\n header?: { wrapper?: unknown; row?: unknown; cell?: unknown };\n body?: { wrapper?: unknown; row?: unknown; cell?: unknown };\n };\n}>();\n\n// ─────────────────────────────────────────────\n// Emits\n// ─────────────────────────────────────────────\n\nconst emit = defineEmits<{\n (\n e: 'change',\n pagination: BTablePaginationConfig,\n filters: BTableFilterState,\n sorter: BTableSorterResult<T> | BTableSorterResult<T>[],\n extra: BTableChangeExtra<T>,\n ): void;\n (e: 'expandedRowsChange', expandedRows: (string | number)[]): void;\n (e: 'expand', expanded: boolean, record: T): void;\n}>();\n\n// ─────────────────────────────────────────────\n// Slots\n// ─────────────────────────────────────────────\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\ndefineSlots<Record<string, ((props: any) => unknown) | undefined>>();\n\n// ─────────────────────────────────────────────\n// Locale defaults\n// ─────────────────────────────────────────────\n\nconst resolvedLocale = computed(() => ({\n emptyText: locale?.emptyText ?? 'No data',\n filterConfirm: locale?.filterConfirm ?? 'OK',\n filterReset: locale?.filterReset ?? 'Reset',\n selectAll: locale?.selectAll ?? 'Select all data',\n selectInvert: locale?.selectInvert ?? 'Invert current page',\n selectionAll: locale?.selectionAll ?? 'Select all data',\n sortTitle: locale?.sortTitle ?? 'Sort',\n expand: locale?.expand ?? 'Expand row',\n collapse: locale?.collapse ?? 'Collapse row',\n}));\n\n// ─────────────────────────────────────────────\n// Row-key helper\n// ─────────────────────────────────────────────\n\nfunction getRowKey(record: T, index: number): string | number {\n if (typeof rowKey === 'function') return rowKey(record);\n const val = record[rowKey as string];\n return (val as string | number) ?? index;\n}\n\n// ─────────────────────────────────────────────\n// Nested value accessor (dot-path)\n// ─────────────────────────────────────────────\n\nfunction getValue(record: T, dataIndex?: string | string[]): unknown {\n if (!dataIndex) return undefined;\n const keys = Array.isArray(dataIndex) ? dataIndex : dataIndex.split('.');\n let cur: unknown = record;\n for (const k of keys) {\n if (cur == null || typeof cur !== 'object') return undefined;\n cur = (cur as Record<string, unknown>)[k];\n }\n return cur;\n}\n\n// ─────────────────────────────────────────────\n// Sort state\n// ─────────────────────────────────────────────\n\ninterface ColumnSortState {\n key: string | number;\n order: BTableSortOrder;\n}\n\nconst internalSortState = ref<ColumnSortState>({ key: '', order: null });\n\nfunction columnSortKey(col: BTableColumnType<T>): string | number {\n return (\n col.key ?? (Array.isArray(col.dataIndex) ? col.dataIndex.join('.') : (col.dataIndex ?? ''))\n );\n}\n\nfunction getColumnSortOrder(col: BTableColumnType<T>): BTableSortOrder {\n if (col.sortOrder !== undefined) return col.sortOrder;\n const key = columnSortKey(col);\n if (internalSortState.value.key === key) return internalSortState.value.order;\n return col.defaultSortOrder ?? null;\n}\n\nfunction cycleSortOrder(current: BTableSortOrder, directions: BTableSortOrder[]): BTableSortOrder {\n const cycle = directions.length ? directions : ['ascend' as const, 'descend' as const, null];\n const idx = cycle.indexOf(current);\n return cycle[(idx + 1) % cycle.length] ?? null;\n}\n\nfunction handleSort(col: BTableColumnType<T>) {\n if (!col.sorter) return;\n const key = columnSortKey(col);\n const current = getColumnSortOrder(col);\n const directions = col.sortDirections ?? ['ascend', 'descend', null];\n const next = cycleSortOrder(current, directions);\n\n // Controlled sort is driven by parent via col.sortOrder\n if (col.sortOrder === undefined) {\n internalSortState.value = { key, order: next };\n }\n\n const sorterResult: BTableSorterResult<T> = {\n column: col,\n order: next,\n field: col.dataIndex,\n columnKey: key,\n };\n emit('change', currentPagination.value, currentFilters.value, sorterResult, {\n currentDataSource: processedData.value,\n action: 'sort',\n });\n}\n\n// ─────────────────────────────────────────────\n// Filter state\n// ─────────────────────────────────────────────\n\nconst internalFilters = ref<BTableFilterState>({});\nconst openFilterKey = ref<string | number | null>(null);\nconst filterSearchMap = ref<Record<string | number, string>>({});\n\nfunction getColumnFilterKey(col: BTableColumnType<T>): string | number {\n return (\n col.key ?? (Array.isArray(col.dataIndex) ? col.dataIndex.join('.') : (col.dataIndex ?? ''))\n );\n}\n\nfunction getColumnFilterValue(col: BTableColumnType<T>): BTableFilterValue | null {\n if (col.filteredValue !== undefined) return col.filteredValue;\n return internalFilters.value[getColumnFilterKey(col)] ?? col.defaultFilteredValue ?? null;\n}\n\nconst currentFilters = computed<BTableFilterState>(() => {\n const result: BTableFilterState = {};\n for (const col of flatColumns.value) {\n if (col.filters || col.filteredValue !== undefined) {\n const key = getColumnFilterKey(col);\n result[key] = getColumnFilterValue(col);\n }\n }\n return result;\n});\n\nfunction toggleFilterPanel(col: BTableColumnType<T>) {\n const key = getColumnFilterKey(col);\n openFilterKey.value = openFilterKey.value === key ? null : key;\n}\n\nfunction applyFilters(col: BTableColumnType<T>, values: BTableFilterValue) {\n const key = getColumnFilterKey(col);\n if (col.filteredValue === undefined) {\n internalFilters.value = { ...internalFilters.value, [key]: values.length ? values : null };\n }\n openFilterKey.value = null;\n\n // Reset to page 1 on filter change\n if (col.filterResetToDefaultFilteredValue !== false) {\n internalPage.value = 1;\n }\n\n emit('change', currentPagination.value, currentFilters.value, buildActiveSorter(), {\n currentDataSource: processedData.value,\n action: 'filter',\n });\n}\n\nfunction resetFilter(col: BTableColumnType<T>) {\n const key = getColumnFilterKey(col);\n const defaults = col.defaultFilteredValue ?? [];\n if (col.filteredValue === undefined) {\n internalFilters.value = { ...internalFilters.value, [key]: defaults.length ? defaults : null };\n }\n openFilterKey.value = null;\n emit('change', currentPagination.value, currentFilters.value, buildActiveSorter(), {\n currentDataSource: processedData.value,\n action: 'filter',\n });\n}\n\nfunction buildActiveSorter(): BTableSorterResult<T> {\n const sortCol = flatColumns.value.find((c) => getColumnSortOrder(c) !== null);\n if (!sortCol) return { order: null };\n return {\n column: sortCol,\n order: getColumnSortOrder(sortCol),\n field: sortCol.dataIndex,\n columnKey: columnSortKey(sortCol),\n };\n}\n\nfunction getFilteredOptions(col: BTableColumnType<T>): BTableFilterOption[] {\n const search = filterSearchMap.value[getColumnFilterKey(col)] ?? '';\n if (!search) return col.filters ?? [];\n const lc = search.toLowerCase();\n const filterFn =\n typeof col.filterSearch === 'function'\n ? col.filterSearch\n : (input: string, opt: BTableFilterOption) =>\n opt.text.toLowerCase().includes(input.toLowerCase());\n return (col.filters ?? []).filter((opt) => filterFn(lc, opt));\n}\n\n// Temp filter selections while the dropdown is open\nconst tempFilterValues = ref<Record<string | number, BTableFilterValue>>({});\n\nfunction openFilter(col: BTableColumnType<T>) {\n const key = getColumnFilterKey(col);\n tempFilterValues.value[key] = [...(getColumnFilterValue(col) ?? [])];\n toggleFilterPanel(col);\n}\n\nfunction toggleTempFilter(col: BTableColumnType<T>, value: string | number | boolean) {\n const key = getColumnFilterKey(col);\n const current = tempFilterValues.value[key] ?? [];\n if (col.filterMultiple !== false) {\n const idx = current.indexOf(value);\n tempFilterValues.value[key] =\n idx === -1 ? [...current, value] : current.filter((v) => v !== value);\n } else {\n tempFilterValues.value[key] = [value];\n }\n}\n\nfunction confirmFilter(col: BTableColumnType<T>) {\n const key = getColumnFilterKey(col);\n applyFilters(col, tempFilterValues.value[key] ?? []);\n}\n\n// ─────────────────────────────────────────────\n// Pagination state\n// ─────────────────────────────────────────────\n\nconst internalPage = ref(1);\nconst internalPageSize = ref(\n typeof pagination === 'object' ? (pagination.defaultPageSize ?? pagination.pageSize ?? 10) : 10,\n);\n\nwatch(\n () => (typeof pagination === 'object' ? pagination.pageSize : undefined),\n (ps) => {\n if (ps != null) internalPageSize.value = ps;\n },\n);\n\nconst currentPage = computed(() =>\n typeof pagination === 'object' && pagination.current != null\n ? pagination.current\n : internalPage.value,\n);\n\nconst currentPageSize = computed(() =>\n typeof pagination === 'object' && pagination.pageSize != null\n ? pagination.pageSize\n : internalPageSize.value,\n);\n\nconst currentPagination = computed<BTablePaginationConfig>(() => ({\n current: currentPage.value,\n pageSize: currentPageSize.value,\n total: filteredData.value.length,\n}));\n\nfunction goToPage(page: number) {\n if (typeof pagination === 'object' && pagination.current == null) {\n internalPage.value = page;\n }\n if (typeof pagination === 'object') {\n pagination.onChange?.(page, currentPageSize.value);\n }\n emit(\n 'change',\n { ...currentPagination.value, current: page },\n currentFilters.value,\n buildActiveSorter(),\n {\n currentDataSource: processedData.value,\n action: 'paginate',\n },\n );\n}\n\nfunction changePageSize(size: number) {\n internalPageSize.value = size;\n internalPage.value = 1;\n if (typeof pagination === 'object') {\n pagination.onShowSizeChange?.(1, size);\n }\n}\n\nconst totalPages = computed(() => Math.ceil(filteredData.value.length / currentPageSize.value));\n\nconst pageSizeOptions = computed(() =>\n typeof pagination === 'object' && pagination.pageSizeOptions\n ? pagination.pageSizeOptions.map(Number)\n : [10, 20, 50, 100],\n);\n\n// ─────────────────────────────────────────────\n// Data processing pipeline\n// ─────────────────────────────────────────────\n\nconst filteredData = computed<T[]>(() => {\n let data = [...dataSource];\n\n for (const col of flatColumns.value) {\n const filterValues = getColumnFilterValue(col);\n if (!filterValues || filterValues.length === 0) continue;\n\n if (col.onFilter) {\n data = data.filter((record) =>\n (filterValues as (string | number | boolean)[]).some((v) => col.onFilter!(v, record)),\n );\n } else if (col.dataIndex) {\n data = data.filter((record) => {\n const val = getValue(record, col.dataIndex);\n return (filterValues as (string | number | boolean)[]).some(\n (v) => String(val) === String(v),\n );\n });\n }\n }\n return data;\n});\n\nconst sortedData = computed<T[]>(() => {\n const data = [...filteredData.value];\n const sortCol = flatColumns.value.find((c) => getColumnSortOrder(c) !== null);\n if (!sortCol || !sortCol.sorter) return data;\n\n const order = getColumnSortOrder(sortCol);\n if (!order) return data;\n\n if (typeof sortCol.sorter === 'function') {\n data.sort((a, b) => {\n const res = (sortCol.sorter as (a: T, b: T) => number)(a, b);\n return order === 'ascend' ? res : -res;\n });\n } else {\n // Default sort: compare string values\n data.sort((a, b) => {\n const av = String(getValue(a, sortCol.dataIndex) ?? '');\n const bv = String(getValue(b, sortCol.dataIndex) ?? '');\n return order === 'ascend' ? av.localeCompare(bv) : bv.localeCompare(av);\n });\n }\n return data;\n});\n\nconst processedData = computed<T[]>(() => {\n if (!pagination) return sortedData.value;\n const start = (currentPage.value - 1) * currentPageSize.value;\n return sortedData.value.slice(start, start + currentPageSize.value);\n});\n\n// ─────────────────────────────────────────────\n// Column processing\n// ─────────────────────────────────────────────\n\n/** Flatten leaf columns (skip group columns for data rendering) */\nconst flatColumns = computed<BTableColumnType<T>[]>(() => {\n function flatten(cols: BTableColumnType<T>[]): BTableColumnType<T>[] {\n const result: BTableColumnType<T>[] = [];\n for (const col of cols) {\n if (col.children?.length) {\n result.push(...flatten(col.children));\n } else {\n result.push(col);\n }\n }\n return result;\n }\n return flatten(columns);\n});\n\n/** Header rows for colspan/rowspan grouped headers */\ninterface HeaderCell {\n col: BTableColumnType<T>;\n colSpan: number;\n rowSpan: number;\n isLeaf: boolean;\n}\n\nconst headerRows = computed<HeaderCell[][]>(() => {\n function getDepth(col: BTableColumnType<T>): number {\n if (!col.children?.length) return 1;\n return 1 + Math.max(...col.children.map(getDepth));\n }\n\n const maxDepth = Math.max(...columns.map(getDepth), 1);\n\n function buildRows(\n cols: BTableColumnType<T>[],\n rows: HeaderCell[][],\n rowIdx: number,\n ) {\n for (const col of cols) {\n const isLeaf = !col.children?.length;\n const cell: HeaderCell = {\n col,\n colSpan: isLeaf ? 1 : flattenLeafCount(col),\n rowSpan: isLeaf ? maxDepth - rowIdx : 1,\n isLeaf,\n };\n if (!rows[rowIdx]) rows[rowIdx] = [];\n rows[rowIdx].push(cell);\n if (!isLeaf) {\n buildRows(col.children!, rows, rowIdx + 1);\n }\n }\n }\n\n function flattenLeafCount(col: BTableColumnType<T>): number {\n if (!col.children?.length) return 1;\n return col.children.reduce((s, c) => s + flattenLeafCount(c), 0);\n }\n\n const rows: HeaderCell[][] = [];\n buildRows(columns, rows, 0);\n return rows;\n});\n\n// ─────────────────────────────────────────────\n// Row selection state\n// ─────────────────────────────────────────────\n\nconst internalSelectedKeys = ref<Set<string | number>>(\n new Set(rowSelection?.defaultSelectedRowKeys ?? []),\n);\n\nconst selectedKeys = computed<Set<string | number>>(() => {\n if (rowSelection?.selectedRowKeys) return new Set(rowSelection.selectedRowKeys);\n return internalSelectedKeys.value;\n});\n\nconst isCheckbox = computed(() => rowSelection?.type !== 'radio');\n\nconst pageRowKeys = computed(() => processedData.value.map((r, i) => getRowKey(r, i)));\n\nconst selectablePageKeys = computed(() =>\n pageRowKeys.value.filter((key, i) => {\n const checkboxProps = rowSelection?.getCheckboxProps?.(processedData.value[i]);\n return !checkboxProps?.disabled;\n }),\n);\n\nconst allPageSelected = computed(\n () =>\n selectablePageKeys.value.length > 0 &&\n selectablePageKeys.value.every((k) => selectedKeys.value.has(k)),\n);\n\nconst somePageSelected = computed(\n () => !allPageSelected.value && selectablePageKeys.value.some((k) => selectedKeys.value.has(k)),\n);\n\nfunction setSelectedKeys(keys: Set<string | number>) {\n if (!rowSelection?.selectedRowKeys) {\n internalSelectedKeys.value = keys;\n }\n const keyArr = [...keys];\n const rows = dataSource.filter((r, i) => keys.has(getRowKey(r, i)));\n rowSelection?.onChange?.(keyArr, rows);\n}\n\nfunction toggleRow(record: T, index: number, event: Event) {\n const key = getRowKey(record, index);\n const keys = new Set(selectedKeys.value);\n if (isCheckbox.value) {\n if (keys.has(key)) {\n keys.delete(key);\n rowSelection?.onSelect?.(\n record,\n false,\n [...keys].map((k) => dataSource.find((r, i) => getRowKey(r, i) === k)!),\n event,\n );\n } else {\n keys.add(key);\n rowSelection?.onSelect?.(\n record,\n true,\n [...keys].map((k) => dataSource.find((r, i) => getRowKey(r, i) === k)!),\n event,\n );\n }\n } else {\n keys.clear();\n keys.add(key);\n rowSelection?.onSelect?.(record, true, [record], event);\n }\n setSelectedKeys(keys);\n}\n\n \nfunction toggleAllPage(_event: Event) {\n const keys = new Set(\n rowSelection?.preserveSelectedRowKeys ? selectedKeys.value : new Set<string | number>(),\n );\n if (allPageSelected.value) {\n selectablePageKeys.value.forEach((k) => keys.delete(k));\n rowSelection?.onSelectAll?.(false, [], processedData.value);\n } else {\n selectablePageKeys.value.forEach((k) => keys.add(k));\n rowSelection?.onSelectAll?.(\n true,\n [...keys].map((k) => dataSource.find((r, i) => getRowKey(r, i) === k)!),\n processedData.value,\n );\n }\n setSelectedKeys(keys);\n}\n\n// ─────────────────────────────────────────────\n// Expand state\n// ─────────────────────────────────────────────\n\nconst internalExpandedKeys = ref<Set<string | number>>(\n new Set(expandable?.defaultExpandedRowKeys ?? []),\n);\n\nconst expandedKeys = computed<Set<string | number>>(() => {\n if (expandable?.expandedRowKeys) return new Set(expandable.expandedRowKeys);\n return internalExpandedKeys.value;\n});\n\n \nfunction toggleExpand(record: T, index: number, _event: MouseEvent) {\n const key = getRowKey(record, index);\n const keys = new Set(expandedKeys.value);\n const isExpanded = keys.has(key);\n if (isExpanded) {\n keys.delete(key);\n } else {\n keys.add(key);\n }\n if (!expandable?.expandedRowKeys) {\n internalExpandedKeys.value = keys;\n }\n expandable?.onExpand?.(!isExpanded, record);\n expandable?.onExpandedRowsChange?.([...keys]);\n emit('expand', !isExpanded, record);\n emit('expandedRowsChange', [...keys]);\n}\n\nfunction isRowExpanded(record: T, index: number): boolean {\n return expandedKeys.value.has(getRowKey(record, index));\n}\n\nfunction isRowExpandable(record: T): boolean {\n if (expandable?.rowExpandable) return expandable.rowExpandable(record);\n const childKey = expandable?.childrenColumnName ?? 'children';\n const children = (record as Record<string, unknown>)[childKey];\n return Array.isArray(children) && children.length > 0;\n}\n\n// ─────────────────────────────────────────────\n// Cell value helper\n// ─────────────────────────────────────────────\n\nfunction getCellValue(record: T, col: BTableColumnType<T>): unknown {\n return getValue(record, col.dataIndex);\n}\n\nfunction getColumnKey(col: BTableColumnType<T>, idx: number): string | number {\n return (\n col.key ?? (Array.isArray(col.dataIndex) ? col.dataIndex.join('.') : (col.dataIndex ?? idx))\n );\n}\n\n// ─────────────────────────────────────────────\n// Sticky offset\n// ─────────────────────────────────────────────\n\nconst stickyOffsetHeader = computed(() =>\n typeof sticky === 'object' ? (sticky.offsetHeader ?? 0) : 0,\n);\n\n// ─────────────────────────────────────────────\n// Scroll wrapper ref\n// ─────────────────────────────────────────────\n\nconst scrollRef = ref<HTMLElement | null>(null);\nconst hasHorizontalScroll = ref(false);\n\nonMounted(() => {\n checkScroll();\n});\n\nwatch(\n () => columns,\n () => nextTick(checkScroll),\n);\n\nfunction checkScroll() {\n if (!scrollRef.value) return;\n hasHorizontalScroll.value = scrollRef.value.scrollWidth > scrollRef.value.clientWidth;\n}\n\n// ─────────────────────────────────────────────\n// Root class / style\n// ─────────────────────────────────────────────\n\nconst rootClasses = computed(() => [\n 'b-table',\n {\n 'b-table--bordered': bordered,\n 'b-table--loading': loading,\n 'b-table--small': size === 'small',\n 'b-table--middle': size === 'middle',\n 'b-table--sticky': sticky,\n 'b-table--fixed-header': !!scroll?.y,\n 'b-table--scroll-x': !!scroll?.x,\n 'b-table--has-selection': !!rowSelection,\n 'b-table--has-expand': !!expandable,\n 'b-table--empty': processedData.value.length === 0,\n },\n]);\n\nconst tableStyle = computed<CSSProperties>(() => {\n const s: CSSProperties = {};\n if (tableLayout) s.tableLayout = tableLayout;\n if (scroll?.x === true) s.minWidth = '100%';\n else if (scroll?.x) s.width = typeof scroll.x === 'number' ? `${scroll.x}px` : scroll.x;\n return s;\n});\n\nconst wrapperStyle = computed<CSSProperties>(() => {\n const s: CSSProperties = {};\n if (scroll?.x) {\n s.overflowX = 'auto';\n }\n if (scroll?.y) {\n s.overflowY = 'auto';\n s.maxHeight = typeof scroll.y === 'number' ? `${scroll.y}px` : scroll.y;\n }\n return s;\n});\n\nconst stickyHeaderStyle = computed<CSSProperties>(() => {\n if (!sticky) return {};\n return {\n position: 'sticky',\n top: stickyOffsetHeader.value ? `${stickyOffsetHeader.value}px` : '0',\n zIndex: 2,\n };\n});\n\n// ─────────────────────────────────────────────\n// Sort icon helper\n// ─────────────────────────────────────────────\n\nfunction getSortIcon(col: BTableColumnType<T>): 'asc' | 'desc' | 'none' {\n const order = getColumnSortOrder(col);\n if (order === 'ascend') return 'asc';\n if (order === 'descend') return 'desc';\n return 'none';\n}\n\n// ─────────────────────────────────────────────\n// Ellipsis helper\n// ─────────────────────────────────────────────\n\nfunction getEllipsis(col: BTableColumnType<T>): boolean {\n if (typeof col.ellipsis === 'boolean') return col.ellipsis;\n return !!col.ellipsis;\n}\n\n// ─────────────────────────────────────────────\n// Column width style\n// ─────────────────────────────────────────────\n\nfunction colWidthStyle(col: BTableColumnType<T>): CSSProperties {\n const s: CSSProperties = {};\n if (col.width != null) s.width = typeof col.width === 'number' ? `${col.width}px` : col.width;\n if (col.minWidth != null) s.minWidth = `${col.minWidth}px`;\n if (col.maxWidth != null) s.maxWidth = `${col.maxWidth}px`;\n if (col.align) s.textAlign = col.align;\n return s;\n}\n\n// ─────────────────────────────────────────────\n// Page jump (simple pagination)\n// ─────────────────────────────────────────────\n\nconst jumpInput = ref('');\nfunction handleJumpKeydown(e: KeyboardEvent) {\n if (e.key === 'Enter') {\n const p = parseInt(jumpInput.value, 10);\n if (!isNaN(p) && p >= 1 && p <= totalPages.value) {\n goToPage(p);\n }\n jumpInput.value = '';\n }\n}\n\n// ─────────────────────────────────────────────\n// Filter keyboard handler (close on Escape)\n// ─────────────────────────────────────────────\n\nfunction handleFilterKeydown(e: KeyboardEvent) {\n if (e.key === 'Escape') openFilterKey.value = null;\n}\n\n// ─────────────────────────────────────────────\n// Get cell style\n// ─────────────────────────────────────────────\n\nfunction getCellStyle(\n col: BTableColumnType<T>,\n record: T,\n rowIndex: number,\n): CSSProperties | undefined {\n if (!col.cellStyle) return undefined;\n if (typeof col.cellStyle === 'function') return col.cellStyle(record, rowIndex);\n return col.cellStyle;\n}\n</script>\n\n<template>\n <div\n :class=\"rootClasses\"\n role=\"region\"\n :aria-label=\"caption ?? undefined\"\n :aria-busy=\"loading ? 'true' : undefined\"\n >\n <!-- Loading overlay -->\n <div\n v-if=\"loading\"\n class=\"b-table__loading-overlay\"\n role=\"status\"\n aria-live=\"polite\"\n aria-label=\"Loading data\"\n >\n <div class=\"b-table__spin\" aria-hidden=\"true\">\n <span class=\"b-table__spin-dot\" />\n <span class=\"b-table__spin-dot\" />\n <span class=\"b-table__spin-dot\" />\n <span class=\"b-table__spin-dot\" />\n </div>\n </div>\n\n <!-- Title slot -->\n <div\n v-if=\"$slots.title\"\n class=\"b-table__title\"\n :aria-hidden=\"loading ? 'true' : undefined\"\n :style=\"loading ? { visibility: 'hidden' } : {}\"\n >\n <slot name=\"title\" />\n </div>\n\n <!-- Scroll wrapper -->\n <div\n ref=\"scrollRef\"\n class=\"b-table__scroll-wrapper\"\n :style=\"[wrapperStyle, loading ? { visibility: 'hidden' } : {}]\"\n :aria-hidden=\"loading ? 'true' : undefined\"\n :tabindex=\"scroll?.x || scroll?.y ? 0 : undefined\"\n >\n <table class=\"b-table__table\" :style=\"tableStyle\" role=\"table\">\n <!-- Caption (a11y) -->\n <caption v-if=\"caption\" class=\"b-table__caption\">\n {{\n caption\n }}\n </caption>\n\n <!-- Colgroup for widths -->\n <colgroup>\n <col\n v-if=\"rowSelection\"\n :style=\"{\n width: rowSelection.columnWidth\n ? typeof rowSelection.columnWidth === 'number'\n ? `${rowSelection.columnWidth}px`\n : rowSelection.columnWidth\n : '48px',\n }\"\n />\n <col v-if=\"expandable && expandable.showExpandColumn !== false\" style=\"width: 48px\" />\n <col\n v-for=\"(col, ci) in flatColumns\"\n :key=\"getColumnKey(col, ci)\"\n :style=\"colWidthStyle(col)\"\n />\n </colgroup>\n\n <!-- Header -->\n <thead v-if=\"showHeader\" class=\"b-table__thead\" :style=\"stickyHeaderStyle\">\n <tr\n v-for=\"(row, ri) in headerRows\"\n :key=\"ri\"\n class=\"b-table__tr b-table__tr--header\"\n v-bind=\"onHeaderRow?.(columns, ri)\"\n >\n <!-- Select-all cell (first header row only) -->\n <th\n v-if=\"rowSelection && ri === 0\"\n class=\"b-table__th b-table__th--selection\"\n :rowspan=\"headerRows.length\"\n scope=\"col\"\n :aria-label=\"resolvedLocale.selectAll\"\n >\n <span v-if=\"rowSelection.columnTitle\">{{ rowSelection.columnTitle }}</span>\n <label\n v-else-if=\"isCheckbox && !rowSelection.hideSelectAll\"\n class=\"b-table__checkbox-wrapper\"\n >\n <input\n type=\"checkbox\"\n class=\"b-table__checkbox\"\n :checked=\"allPageSelected\"\n :indeterminate=\"somePageSelected\"\n :aria-label=\"resolvedLocale.selectAll\"\n @change=\"toggleAllPage($event)\"\n />\n <span\n class=\"b-table__checkbox-inner\"\n :class=\"{\n 'b-table__checkbox-inner--indeterminate': somePageSelected,\n 'b-table__checkbox-inner--checked': allPageSelected,\n }\"\n aria-hidden=\"true\"\n />\n </label>\n </th>\n\n <!-- Expand cell (first header row only) -->\n <th\n v-if=\"expandable && expandable.showExpandColumn !== false && ri === 0\"\n class=\"b-table__th b-table__th--expand\"\n :rowspan=\"headerRows.length\"\n scope=\"col\"\n aria-hidden=\"true\"\n />\n\n <!-- Column header cells -->\n <th\n v-for=\"cell in row\"\n :key=\"getColumnKey(cell.col, 0)\"\n class=\"b-table__th\"\n :class=\"{\n 'b-table__th--sorted': getColumnSortOrder(cell.col) !== null,\n 'b-table__th--sortable': !!cell.col.sorter,\n 'b-table__th--filtered': (getColumnFilterValue(cell.col)?.length ?? 0) > 0,\n [`b-table__th--align-${cell.col.align ?? 'left'}`]: true,\n [cell.col.className ?? '']: !!cell.col.className,\n }\"\n :colspan=\"cell.colSpan > 1 ? cell.colSpan : undefined\"\n :rowspan=\"cell.rowSpan > 1 ? cell.rowSpan : undefined\"\n :style=\"colWidthStyle(cell.col)\"\n scope=\"col\"\n :aria-sort=\"\n cell.col.sorter\n ? getColumnSortOrder(cell.col) === 'ascend'\n ? 'ascending'\n : getColumnSortOrder(cell.col) === 'descend'\n ? 'descending'\n : 'none'\n : undefined\n \"\n v-bind=\"onHeaderRow?.(columns, 0)\"\n >\n <div class=\"b-table__th-inner\">\n <!-- Title -->\n <span\n class=\"b-table__col-title\"\n :class=\"{ 'b-table__col-title--rowspan': cell.rowSpan > 1 }\"\n >{{ cell.col.titleText ?? cell.col.title }}</span\n >\n\n <!-- Sorter -->\n <button\n v-if=\"cell.col.sorter && cell.isLeaf\"\n class=\"b-table__sorter\"\n type=\"button\"\n :aria-label=\"`${resolvedLocale.sortTitle}: ${cell.col.titleText ?? cell.col.title}`\"\n :data-sort=\"getSortIcon(cell.col)\"\n @click.stop=\"handleSort(cell.col)\"\n >\n <span class=\"b-table__sorter-inner\" aria-hidden=\"true\">\n <svg\n class=\"b-table__sort-up\"\n :class=\"{ active: getSortIcon(cell.col) === 'asc' }\"\n viewBox=\"0 0 1024 1024\"\n width=\"1em\"\n height=\"1em\"\n fill=\"currentColor\"\n aria-hidden=\"true\"\n >\n <path\n d=\"M858.9 689L530.5 308.2a24 24 0 0 0-36.9 0L165.1 689c-4.7 5.2-.4 13 6.5 13h496.8c6.9 0 11.2-7.8 6.5-13z\"\n />\n </svg>\n <svg\n class=\"b-table__sort-down\"\n :class=\"{ active: getSortIcon(cell.col) === 'desc' }\"\n viewBox=\"0 0 1024 1024\"\n width=\"1em\"\n height=\"1em\"\n fill=\"currentColor\"\n aria-hidden=\"true\"\n >\n <path\n d=\"M840.4 300H183.6c-19.7 0-30.7 20.8-18.5 35l328.4 380.8c9.4 10.9 27.5 10.9 37 0L858.9 335c12.2-14.2 1.2-35-18.5-35z\"\n />\n </svg>\n </span>\n </button>\n\n <!-- Filter trigger -->\n <div\n v-if=\"cell.col.filters && cell.isLeaf\"\n class=\"b-table__filter-trigger\"\n :class=\"{\n 'b-table__filter-trigger--active':\n (getColumnFilterValue(cell.col)?.length ?? 0) > 0 ||\n openFilterKey === getColumnFilterKey(cell.col),\n }\"\n >\n <button\n type=\"button\"\n class=\"b-table__filter-btn\"\n :aria-expanded=\"\n openFilterKey === getColumnFilterKey(cell.col) ? 'true' : 'false'\n \"\n :aria-label=\"`Filter ${cell.col.titleText ?? cell.col.title}`\"\n @click.stop=\"openFilter(cell.col)\"\n >\n <svg\n viewBox=\"0 0 1024 1024\"\n width=\"1em\"\n height=\"1em\"\n fill=\"currentColor\"\n aria-hidden=\"true\"\n >\n <path\n d=\"M880.1 154H143.9c-24.5 0-39.8 26.7-27.5 48L349 597.4V838c0 17.8 14.5 32 32.3 32h263.4c17.8 0 32.3-14.2 32.3-32V597.4L909.6 202c12.3-21.3-3-48-29.5-48z\"\n />\n </svg>\n </button>\n\n <!-- Filter dropdown -->\n <div\n v-if=\"openFilterKey === getColumnFilterKey(cell.col)\"\n class=\"b-table__filter-dropdown\"\n role=\"dialog\"\n :aria-label=\"`Filter ${cell.col.titleText ?? cell.col.title}`\"\n @keydown=\"handleFilterKeydown\"\n >\n <!-- Search input -->\n <div v-if=\"cell.col.filterSearch\" class=\"b-table__filter-search\">\n <input\n v-model=\"filterSearchMap[getColumnFilterKey(cell.col)]\"\n type=\"search\"\n class=\"b-table__filter-search-input\"\n placeholder=\"Search...\"\n aria-label=\"Search filter options\"\n />\n </div>\n\n <!-- Options -->\n <ul\n class=\"b-table__filter-list\"\n role=\"listbox\"\n :aria-multiselectable=\"cell.col.filterMultiple !== false ? 'true' : 'false'\"\n >\n <li\n v-for=\"opt in getFilteredOptions(cell.col)\"\n :key=\"String(opt.value)\"\n class=\"b-table__filter-item\"\n role=\"option\"\n :aria-selected=\"\n (tempFilterValues[getColumnFilterKey(cell.col)] ?? []).includes(opt.value)\n \"\n >\n <label class=\"b-table__filter-item-label\">\n <input\n :type=\"cell.col.filterMultiple !== false ? 'checkbox' : 'radio'\"\n class=\"b-table__filter-item-input\"\n :checked=\"\n (tempFilterValues[getColumnFilterKey(cell.col)] ?? []).includes(\n opt.value,\n )\n \"\n @change=\"toggleTempFilter(cell.col, opt.value)\"\n />\n <span>{{ opt.text }}</span>\n </label>\n </li>\n </ul>\n\n <!-- Actions -->\n <div class=\"b-table__filter-actions\">\n <button\n type=\"button\"\n class=\"b-table__filter-reset\"\n @click=\"resetFilter(cell.col)\"\n >\n {{ resolvedLocale.filterReset }}\n </button>\n <button\n type=\"button\"\n class=\"b-table__filter-confirm\"\n @click=\"confirmFilter(cell.col)\"\n >\n {{ resolvedLocale.filterConfirm }}\n </button>\n </div>\n </div>\n </div>\n </div>\n </th>\n </tr>\n </thead>\n\n <!-- Body -->\n <tbody class=\"b-table__tbody\">\n <!-- Empty state -->\n <tr v-if=\"processedData.length === 0\" class=\"b-table__tr--empty\">\n <td\n class=\"b-table__td b-table__td--empty\"\n :colspan=\"\n flatColumns.length +\n (rowSelection ? 1 : 0) +\n (expandable && expandable.showExpandColumn !== false ? 1 : 0)\n \"\n >\n <slot name=\"emptyText\">\n <div class=\"b-table__empty\">\n <svg\n class=\"b-table__empty-icon\"\n viewBox=\"0 0 64 41\"\n xmlns=\"http://www.w3.org/2000/svg\"\n aria-hidden=\"true\"\n >\n <g transform=\"translate(0 1)\" fill=\"none\" fill-rule=\"evenodd\">\n <ellipse cx=\"32\" cy=\"33\" rx=\"32\" ry=\"7\" fill=\"currentColor\" opacity=\".2\" />\n <g fill-rule=\"nonzero\" stroke=\"currentColor\">\n <path\n d=\"M55 12.76L44.854 1.258C44.367.474 43.656 0 42.907 0H21.093c-.749 0-1.46.474-1.947 1.257L9 12.761V22h46v-9.24z\"\n />\n <path\n d=\"M41.613 15.931c0-1.605.994-2.93 2.227-2.931H55v18.137C55 33.26 53.68 35 52.05 35h-40.1C10.32 35 9 33.259 9 31.137V13h11.16c1.233 0 2.227 1.323 2.227 2.928v.022c0 1.605 1.005 2.901 2.237 2.901h14.752c1.232 0 2.237-1.308 2.237-2.913v-.007z\"\n fill=\"currentColor\"\n opacity=\".08\"\n />\n </g>\n </g>\n </svg>\n <p class=\"b-table__empty-text\">{{ resolvedLocale.emptyText }}</p>\n </div>\n </slot>\n </td>\n </tr>\n\n <!-- Data rows -->\n <template v-for=\"(record, rowIndex) in processedData\" :key=\"getRowKey(record, rowIndex)\">\n <tr\n class=\"b-table__tr b-table__tr--data\"\n :class=\"[\n rowClassName?.(record, rowIndex),\n {\n 'b-table__tr--selected': selectedKeys.has(getRowKey(record, rowIndex)),\n 'b-table__tr--expanded': isRowExpanded(record, rowIndex),\n },\n ]\"\n v-bind=\"onRow?.(record, rowIndex)\"\n >\n <!-- Selection cell -->\n <td v-if=\"rowSelection\" class=\"b-table__td b-table__td--selection\">\n <label class=\"b-table__checkbox-wrapper\">\n <input\n :type=\"isCheckbox ? 'checkbox' : 'radio'\"\n class=\"b-table__checkbox\"\n :checked=\"selectedKeys.has(getRowKey(record, rowIndex))\"\n :disabled=\"!!rowSelection.getCheckboxProps?.(record)?.disabled\"\n :aria-label=\"`Select row ${rowIndex + 1}`\"\n v-bind=\"rowSelection.getCheckboxProps?.(record)\"\n @change=\"toggleRow(record, rowIndex, $event)\"\n />\n <span\n class=\"b-table__checkbox-inner\"\n :class=\"{\n 'b-table__checkbox-inner--checked': selectedKeys.has(\n getRowKey(record, rowIndex),\n ),\n }\"\n aria-hidden=\"true\"\n />\n </label>\n </td>\n\n <!-- Expand toggle cell -->\n <td\n v-if=\"expandable && expandable.showExpandColumn !== false\"\n class=\"b-table__td b-table__td--expand\"\n >\n <button\n v-if=\"isRowExpandable(record)\"\n type=\"button\"\n class=\"b-table__expand-btn\"\n :class=\"{ 'b-table__expand-btn--expanded': isRowExpanded(record, rowIndex) }\"\n :aria-label=\"\n isRowExpanded(record, rowIndex)\n ? resolvedLocale.collapse\n : resolvedLocale.expand\n \"\n :aria-expanded=\"isRowExpanded(record, rowIndex) ? 'true' : 'false'\"\n @click=\"toggleExpand(record, rowIndex, $event)\"\n >\n <svg\n class=\"b-table__expand-icon\"\n viewBox=\"0 0 1024 1024\"\n width=\"1em\"\n height=\"1em\"\n fill=\"currentColor\"\n aria-hidden=\"true\"\n >\n <path\n d=\"M765.7 486.8L314.9 134.7A7.97 7.97 0 0 0 302 141v77.3c0 4.9 2.3 9.6 6.1 12.6l360 281.1-360 281.1c-3.9 3-6.1 7.7-6.1 12.6V883c0 6.7 7.7 10.4 12.9 6.3l450.8-352.1a31.96 31.96 0 0 0 0-50.4z\"\n />\n </svg>\n </button>\n </td>\n\n <!-- Data cells -->\n <td\n v-for=\"(col, ci) in flatColumns\"\n :key=\"getColumnKey(col, ci)\"\n class=\"b-table__td\"\n :class=\"{\n [`b-table__td--align-${col.align ?? 'left'}`]: true,\n 'b-table__td--ellipsis': getEllipsis(col),\n [col.className ?? '']: !!col.className,\n }\"\n :style=\"[colWidthStyle(col), getCellStyle(col, record, rowIndex)]\"\n >\n <!-- Slot-based rendering (by column key) -->\n <slot\n v-if=\"$slots[String(getColumnKey(col, ci))]\"\n :name=\"String(getColumnKey(col, ci))\"\n :value=\"getCellValue(record, col)\"\n :record=\"record\"\n :index=\"rowIndex\"\n :column=\"col\"\n />\n <!-- customRender function -->\n <component\n v-else-if=\"col.customRender\"\n :is=\"\n () =>\n col.customRender!({\n value: getCellValue(record, col),\n record,\n index: rowIndex,\n column: col,\n })\n \"\n />\n <!-- Default: raw cell value -->\n <template v-else>\n <span\n v-if=\"getEllipsis(col)\"\n class=\"b-table__cell-ellipsis\"\n :title=\"\n typeof col.ellipsis === 'object' && col.ellipsis.showTitle !== false\n ? String(getCellValue(record, col) ?? '')\n : undefined\n \"\n >{{ getCellValue(record, col) }}</span\n >\n <template v-else>{{ getCellValue(record, col) }}</template>\n </template>\n </td>\n </tr>\n\n <!-- Expanded row content -->\n <tr\n v-if=\"expandable && isRowExpanded(record, rowIndex)\"\n class=\"b-table__tr b-table__tr--expanded-content\"\n :key=\"`${getRowKey(record, rowIndex)}-expanded`\"\n >\n <td\n :colspan=\"\n flatColumns.length +\n (rowSelection ? 1 : 0) +\n (expandable && expandable.showExpandColumn !== false ? 1 : 0)\n \"\n class=\"b-table__td b-table__td--expanded\"\n >\n <component\n v-if=\"expandable.expandedRowRender\"\n :is=\"\n () =>\n expandable.expandedRowRender!(\n record,\n rowIndex,\n 0,\n isRowExpanded(record, rowIndex),\n )\n \"\n />\n <slot v-else name=\"expandedRow\" :record=\"record\" :index=\"rowIndex\" />\n </td>\n </tr>\n </template>\n\n <!-- Summary slot -->\n <slot name=\"summary\" :page-data=\"processedData\" />\n </tbody>\n </table>\n </div>\n\n <!-- Footer slot -->\n <div\n v-if=\"$slots.footer\"\n class=\"b-table__footer\"\n :aria-hidden=\"loading ? 'true' : undefined\"\n :style=\"loading ? { visibility: 'hidden' } : {}\"\n >\n <slot name=\"footer\" />\n </div>\n\n <!-- Pagination -->\n <div\n v-if=\"pagination !== false && filteredData.length > 0\"\n class=\"b-table__pagination\"\n role=\"navigation\"\n aria-label=\"Table pagination\"\n :aria-hidden=\"loading ? 'true' : undefined\"\n :style=\"loading ? { visibility: 'hidden' } : {}\"\n >\n <!-- Total info -->\n <span\n v-if=\"typeof pagination === 'object' && pagination.showTotal\"\n class=\"b-table__pagination-total\"\n >\n {{\n pagination.showTotal(filteredData.length, [\n (currentPage - 1) * currentPageSize + 1,\n Math.min(currentPage * currentPageSize, filteredData.length),\n ])\n }}\n </span>\n\n <div class=\"b-table__pagination-controls\">\n <!-- Prev -->\n <button\n type=\"button\"\n class=\"b-table__page-btn\"\n :disabled=\"currentPage <= 1\"\n :aria-label=\"'Previous page'\"\n @click=\"goToPage(currentPage - 1)\"\n >\n <svg\n viewBox=\"0 0 1024 1024\"\n width=\"1em\"\n height=\"1em\"\n fill=\"currentColor\"\n aria-hidden=\"true\"\n >\n <path\n d=\"M724 218.3V141c0-6.7-7.7-10.4-12.9-6.3L260.3 486.8a31.96 31.96 0 0 0 0 50.3l450.8 352.1c5.3 4.1 12.9.4 12.9-6.3v-77.3c0-4.9-2.3-9.6-6.1-12.6l-360-281 360-281.1c3.8-3 6.1-7.7 6.1-12.6z\"\n />\n </svg>\n </button>\n\n <!-- Page numbers -->\n <template v-for=\"page in totalPages\" :key=\"page\">\n <button\n v-if=\"\n totalPages <= 7 ||\n page === 1 ||\n page === totalPages ||\n Math.abs(page - currentPage) <= 1\n \"\n type=\"button\"\n class=\"b-table__page-btn\"\n :class=\"{ 'b-table__page-btn--active': page === currentPage }\"\n :aria-label=\"`Page ${page}`\"\n :aria-current=\"page === currentPage ? 'page' : undefined\"\n @click=\"goToPage(page)\"\n >\n {{ page }}\n </button>\n <span\n v-else-if=\"page === 2 && currentPage > 4\"\n class=\"b-table__page-ellipsis\"\n aria-hidden=\"true\"\n >…</span\n >\n <span\n v-else-if=\"page === totalPages - 1 && currentPage < totalPages - 3\"\n class=\"b-table__page-ellipsis\"\n aria-hidden=\"true\"\n >…</span\n >\n </template>\n\n <!-- Next -->\n <button\n type=\"button\"\n class=\"b-table__page-btn\"\n :disabled=\"currentPage >= totalPages\"\n :aria-label=\"'Next page'\"\n @click=\"goToPage(currentPage + 1)\"\n >\n <svg\n viewBox=\"0 0 1024 1024\"\n width=\"1em\"\n height=\"1em\"\n fill=\"currentColor\"\n aria-hidden=\"true\"\n >\n <path\n d=\"M765.7 486.8L314.9 134.7A7.97 7.97 0 0 0 302 141v77.3c0 4.9 2.3 9.6 6.1 12.6l360 281.1-360 281.1c-3.9 3-6.1 7.7-6.1 12.6V883c0 6.7 7.7 10.4 12.9 6.3l450.8-352.1a31.96 31.96 0 0 0 0-50.4z\"\n />\n </svg>\n </button>\n\n <!-- Page size changer -->\n <select\n v-if=\"typeof pagination === 'object' && pagination.showSizeChanger\"\n class=\"b-table__page-size\"\n :value=\"currentPageSize\"\n :aria-label=\"'Rows per page'\"\n @change=\"changePageSize(Number(($event.target as HTMLSelectElement).value))\"\n >\n <option v-for=\"ps in pageSizeOptions\" :key=\"ps\" :value=\"ps\">{{ ps }} / page</option>\n </select>\n\n <!-- Quick jump -->\n <span\n v-if=\"typeof pagination === 'object' && pagination.showQuickJumper\"\n class=\"b-table__page-jump\"\n >\n Go to\n <input\n v-model=\"jumpInput\"\n type=\"number\"\n class=\"b-table__page-jump-input\"\n :min=\"1\"\n :max=\"totalPages\"\n aria-label=\"Go to page\"\n @keydown=\"handleJumpKeydown\"\n />\n </span>\n </div>\n </div>\n </div>\n</template>\n\n<style>\n/* ─────────────────────────────────────────────\n BTable - CSS custom properties (scoped to root)\n ───────────────────────────────────────────── */\n.b-table {\n /* Colors */\n --b-table-bg: oklch(100% 0 0);\n --b-table-color: oklch(20% 0.02 260);\n --b-table-border-color: oklch(88% 0.01 260);\n --b-table-header-bg: oklch(97% 0.005 260);\n --b-table-header-color: oklch(30% 0.02 260);\n --b-table-row-hover-bg: oklch(96% 0.01 260);\n --b-table-row-selected-bg: oklch(94% 0.03 262);\n --b-table-row-expanded-bg: oklch(98% 0.005 260);\n --b-table-empty-color: oklch(48% 0.01 260);\n --b-table-footer-bg: oklch(97% 0.005 260);\n --b-table-loading-overlay-bg: oklch(100% 0 0);\n --b-table-sticky-z-index: 2;\n\n /* Sort / filter */\n --b-table-sorter-color: oklch(65% 0.01 260);\n --b-table-sorter-active-color: oklch(54.6% 0.245 262.881);\n --b-table-filter-active-color: oklch(54.6% 0.245 262.881);\n --b-table-filter-dropdown-bg: oklch(100% 0 0);\n --b-table-filter-dropdown-shadow: 0 6px 16px oklch(0% 0 0 / 8%), 0 3px 6px oklch(0% 0 0 / 4%);\n\n /* Checkbox */\n --b-table-checkbox-size: 16px;\n --b-table-checkbox-border: 1px solid oklch(75% 0.01 260);\n --b-table-checkbox-checked-bg: oklch(54.6% 0.245 262.881);\n --b-table-checkbox-checked-border: oklch(54.6% 0.245 262.881);\n --b-table-checkbox-radius: 4px;\n\n /* Expand */\n --b-table-expand-icon-size: 17px;\n --b-table-expand-icon-color: oklch(45% 0.02 260);\n --b-table-expand-icon-hover-color: oklch(54.6% 0.245 262.881);\n\n /* Spacing */\n --b-table-cell-padding: 12px 16px;\n --b-table-cell-padding-middle: 8px 16px;\n --b-table-cell-padding-small: 4px 8px;\n --b-table-font-size: 14px;\n --b-table-border-radius: 8px;\n\n /* Pagination */\n --b-table-pagination-color: oklch(30% 0.02 260);\n --b-table-pagination-btn-size: 32px;\n --b-table-pagination-btn-radius: 6px;\n --b-table-pagination-btn-border: 1px solid oklch(88% 0.01 260);\n --b-table-pagination-btn-active-bg: oklch(54.6% 0.245 262.881);\n --b-table-pagination-btn-active-color: oklch(100% 0 0);\n --b-table-pagination-btn-hover-bg: oklch(95% 0.01 262);\n\n /* Spin */\n --b-table-spin-color: oklch(54.6% 0.245 262.881);\n --b-table-spin-size: 32px;\n\n /* Transition */\n --b-table-transition-duration: 200ms;\n\n position: relative;\n font-size: var(--b-table-font-size);\n color: var(--b-table-color);\n background: var(--b-table-bg);\n border-radius: var(--b-table-border-radius);\n line-height: 1.5715;\n box-sizing: border-box;\n}\n\n/* ── Scroll wrapper ── */\n.b-table__scroll-wrapper {\n width: 100%;\n overflow: auto;\n -webkit-overflow-scrolling: touch;\n background: var(--b-table-bg);\n}\n\n.b-table__scroll-wrapper:focus-visible {\n outline: 2px solid oklch(54.6% 0.245 262.881);\n outline-offset: -2px;\n}\n\n/* ── Table element ── */\n.b-table__table {\n width: 100%;\n border-collapse: collapse;\n table-layout: auto;\n background: var(--b-table-bg);\n}\n\n/* ── Caption ── */\n.b-table__caption {\n /* Visually hidden - accessible label is surfaced via aria-label on the region landmark */\n position: absolute;\n width: 1px;\n height: 1px;\n padding: 0;\n margin: -1px;\n overflow: hidden;\n clip: rect(0, 0, 0, 0);\n white-space: nowrap;\n border: 0;\n background: var(--b-table-bg);\n}\n\n/* ── Header ── */\n.b-table__thead {\n background: var(--b-table-header-bg);\n}\n\n/* Explicit background on every header <tr> so axe's elementsFromPoint algorithm\n can resolve the background when a rowspan-2 th's text sits on the row boundary\n and the sibling <tr> appears as a non-ancestor in the z-stack. */\n.b-table__tr--header {\n background: var(--b-table-header-bg);\n}\n\n.b-table__th {\n padding: var(--b-table-cell-padding);\n font-weight: 600;\n color: var(--b-table-header-color);\n text-align: left;\n vertical-align: top;\n white-space: nowrap;\n border-bottom: 1px solid var(--b-table-border-color);\n box-sizing: border-box;\n background: var(--b-table-header-bg);\n}\n\n.b-table--middle .b-table__th {\n padding: var(--b-table-cell-padding-middle);\n}\n\n.b-table--small .b-table__th {\n padding: var(--b-table-cell-padding-small);\n}\n\n.b-table__th--align-center {\n text-align: center;\n}\n.b-table__th--align-right {\n text-align: right;\n}\n\n.b-table__th-inner {\n display: flex;\n align-items: center;\n gap: 4px;\n width: 100%;\n background: var(--b-table-header-bg);\n}\n\n.b-table__col-title {\n flex: 1;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n background: var(--b-table-header-bg);\n}\n\n/* Rowspan headers span multiple rows; removing overflow:hidden prevents axe's\n elementsFromPoint from finding the sibling <tr> as an unresolvable overlay. */\n.b-table__col-title--rowspan {\n overflow: visible;\n}\n\n/* ── Sorter ── */\n.b-table__sorter {\n display: inline-flex;\n flex-direction: column;\n align-items: center;\n cursor: pointer;\n color: var(--b-table-sorter-color);\n background: none;\n border: none;\n padding: 0;\n gap: 0;\n flex-shrink: 0;\n transition: color var(--b-table-transition-duration);\n}\n\n.b-table__sorter:hover,\n.b-table__th--sorted .b-table__sorter {\n color: var(--b-table-sorter-active-color);\n}\n\n.b-table__sorter-inner {\n display: flex;\n flex-direction: column;\n line-height: 1;\n}\n\n.b-table__sort-up,\n.b-table__sort-down {\n display: block;\n width: 11px;\n height: 7px;\n color: var(--b-table-sorter-color);\n transition: color var(--b-table-transition-duration);\n}\n\n.b-table__sort-up.active {\n color: var(--b-table-sorter-active-color);\n}\n.b-table__sort-down.active {\n color: var(--b-table-sorter-active-color);\n}\n\n/* ── Filter ── */\n.b-table__filter-trigger {\n position: relative;\n display: inline-flex;\n flex-shrink: 0;\n}\n\n.b-table__filter-btn {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n cursor: pointer;\n color: var(--b-table-sorter-color);\n background: none;\n border: none;\n padding: 2px 4px;\n border-radius: 4px;\n transition:\n color var(--b-table-transition-duration),\n background var(--b-table-transition-duration);\n}\n\n.b-table__filter-btn:hover,\n.b-table__filter-trigger--active .b-table__filter-btn {\n color: var(--b-table-filter-active-color);\n background: oklch(94% 0.02 262);\n}\n\n.b-table__filter-dropdown {\n position: absolute;\n top: calc(100% + 4px);\n left: 0;\n z-index: 10;\n min-width: 160px;\n background: var(--b-table-filter-dropdown-bg);\n border-radius: 8px;\n box-shadow: var(--b-table-filter-dropdown-shadow);\n padding: 4px 0;\n outline: none;\n}\n\n.b-table__filter-search {\n padding: 8px 12px;\n border-bottom: 1px solid var(--b-table-border-color);\n}\n\n.b-table__filter-search-input {\n width: 100%;\n border: 1px solid var(--b-table-border-color);\n border-radius: 4px;\n padding: 4px 8px;\n font-size: 13px;\n outline: none;\n background: var(--b-table-bg);\n color: var(--b-table-color);\n box-sizing: border-box;\n}\n\n.b-table__filter-search-input:focus {\n border-color: var(--b-table-sorter-active-color);\n box-shadow: 0 0 0 2px oklch(54.6% 0.245 262.881 / 20%);\n}\n\n.b-table__filter-list {\n list-style: none;\n margin: 0;\n padding: 4px 0;\n max-height: 200px;\n overflow-y: auto;\n}\n\n.b-table__filter-item {\n padding: 0;\n}\n\n.b-table__filter-item-label {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 6px 12px;\n cursor: pointer;\n font-size: 13px;\n transition: background var(--b-table-transition-duration);\n}\n\n.b-table__filter-item-label:hover {\n background: var(--b-table-row-hover-bg);\n}\n\n.b-table__filter-item-input {\n accent-color: var(--b-table-sorter-active-color);\n}\n\n.b-table__filter-actions {\n display: flex;\n justify-content: space-between;\n padding: 8px 12px;\n border-top: 1px solid var(--b-table-border-color);\n gap: 8px;\n}\n\n.b-table__filter-reset {\n background: none;\n border: none;\n cursor: pointer;\n font-size: 13px;\n color: var(--b-table-color);\n padding: 4px 8px;\n border-radius: 4px;\n transition: background var(--b-table-transition-duration);\n}\n\n.b-table__filter-reset:hover {\n background: var(--b-table-row-hover-bg);\n}\n\n.b-table__filter-confirm {\n background: oklch(54.6% 0.245 262.881);\n color: oklch(100% 0 0);\n border: none;\n cursor: pointer;\n font-size: 13px;\n padding: 4px 12px;\n border-radius: 4px;\n transition: background var(--b-table-transition-duration);\n}\n\n.b-table__filter-confirm:hover {\n background: oklch(49% 0.24 262.881);\n}\n\n/* ── Selection cells ── */\n.b-table__th--selection,\n.b-table__td--selection {\n text-align: center;\n padding: 0 !important;\n width: 48px;\n}\n\n.b-table__checkbox-wrapper {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n cursor: pointer;\n position: relative;\n padding: 0 8px;\n height: 100%;\n}\n\n.b-table__checkbox {\n /* Visually hidden but accessible */\n position: absolute;\n opacity: 0;\n width: 0;\n height: 0;\n margin: 0;\n}\n\n.b-table__checkbox:focus-visible + .b-table__checkbox-inner {\n outline: 2px solid oklch(54.6% 0.245 262.881);\n outline-offset: 2px;\n}\n\n.b-table__checkbox-inner {\n display: inline-block;\n width: var(--b-table-checkbox-size);\n height: var(--b-table-checkbox-size);\n border: var(--b-table-checkbox-border);\n border-radius: var(--b-table-checkbox-radius);\n background: var(--b-table-bg);\n transition:\n border-color var(--b-table-transition-duration),\n background var(--b-table-transition-duration);\n position: relative;\n flex-shrink: 0;\n}\n\n.b-table__checkbox-inner--checked {\n background: var(--b-table-checkbox-checked-bg);\n border-color: var(--b-table-checkbox-checked-border);\n}\n\n/* Checkmark */\n.b-table__checkbox-inner--checked::after {\n content: '';\n position: absolute;\n top: 2px;\n left: 5px;\n width: 4px;\n height: 8px;\n border: 2px solid oklch(100% 0 0);\n border-top: none;\n border-left: none;\n transform: rotate(45deg);\n}\n\n/* Indeterminate dash */\n.b-table__checkbox-inner--indeterminate {\n background: var(--b-table-checkbox-checked-bg);\n border-color: var(--b-table-checkbox-checked-border);\n}\n\n.b-table__checkbox-inner--indeterminate::after {\n content: '';\n position: absolute;\n top: 50%;\n left: 50%;\n width: 8px;\n height: 2px;\n background: oklch(100% 0 0);\n transform: translate(-50%, -50%);\n border: none;\n}\n\n/* ── Expand toggle cell ── */\n.b-table__th--expand,\n.b-table__td--expand {\n text-align: center;\n width: 48px;\n padding: 0 !important;\n}\n\n.b-table__expand-btn {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: var(--b-table-expand-icon-size);\n height: var(--b-table-expand-icon-size);\n border: 1px solid var(--b-table-border-color);\n border-radius: 4px;\n background: var(--b-table-bg);\n cursor: pointer;\n color: var(--b-table-expand-icon-color);\n padding: 0;\n transition:\n color var(--b-table-transition-duration),\n border-color var(--b-table-transition-duration);\n}\n\n.b-table__expand-btn:hover {\n color: var(--b-table-expand-icon-hover-color);\n border-color: var(--b-table-expand-icon-hover-color);\n}\n\n.b-table__expand-icon {\n transition: transform var(--b-table-transition-duration);\n}\n\n.b-table__expand-btn--expanded .b-table__expand-icon {\n transform: rotate(90deg);\n}\n\n/* ── Body cells ── */\n.b-table__td {\n padding: var(--b-table-cell-padding);\n border-bottom: 1px solid var(--b-table-border-color);\n background: var(--b-table-bg);\n color: var(--b-table-color);\n box-sizing: border-box;\n vertical-align: middle;\n transition: background var(--b-table-transition-duration);\n}\n\n.b-table--middle .b-table__td {\n padding: var(--b-table-cell-padding-middle);\n}\n.b-table--small .b-table__td {\n padding: var(--b-table-cell-padding-small);\n}\n\n.b-table__td--align-center {\n text-align: center;\n}\n.b-table__td--align-right {\n text-align: right;\n}\n\n.b-table__td--ellipsis {\n overflow: hidden;\n}\n\n.b-table__cell-ellipsis {\n display: block;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n/* ── Row states ── */\n.b-table__tr--data:hover > .b-table__td {\n background: var(--b-table-row-hover-bg);\n}\n\n.b-table__tr--selected > .b-table__td {\n background: var(--b-table-row-selected-bg);\n}\n\n.b-table__tr--expanded-content > .b-table__td--expanded {\n background: var(--b-table-row-expanded-bg);\n}\n\n/* ── Bordered ── */\n.b-table--bordered {\n border: 1px solid var(--b-table-border-color);\n border-radius: var(--b-table-border-radius);\n overflow: hidden;\n}\n\n.b-table--bordered .b-table__th,\n.b-table--bordered .b-table__td {\n border-right: 1px solid var(--b-table-border-color);\n}\n\n.b-table--bordered .b-table__th:last-child,\n.b-table--bordered .b-table__td:last-child {\n border-right: none;\n}\n\n/* ── Empty ── */\n.b-table__td--empty {\n padding: 48px 16px;\n text-align: center;\n border-bottom: none;\n}\n\n.b-table__empty {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 8px;\n}\n\n.b-table__empty-icon {\n width: 64px;\n height: 41px;\n color: var(--b-table-empty-color);\n}\n\n.b-table__empty-text {\n margin: 0;\n color: var(--b-table-empty-color);\n font-size: 14px;\n}\n\n/* ── Title / Footer ── */\n.b-table__title,\n.b-table__footer {\n padding: 12px 16px;\n background: var(--b-table-footer-bg);\n border: 1px solid var(--b-table-border-color);\n}\n\n.b-table__title {\n border-bottom: none;\n border-radius: var(--b-table-border-radius) var(--b-table-border-radius) 0 0;\n}\n.b-table__footer {\n border-top: none;\n border-radius: 0 0 var(--b-table-border-radius) var(--b-table-border-radius);\n}\n\n/* ── Loading ── */\n.b-table__loading-overlay {\n position: absolute;\n inset: 0;\n z-index: 4;\n display: flex;\n align-items: center;\n justify-content: center;\n background: var(--b-table-loading-overlay-bg);\n border-radius: var(--b-table-border-radius);\n}\n\n.b-table__spin {\n display: inline-flex;\n gap: 4px;\n align-items: center;\n}\n\n.b-table__spin-dot {\n display: inline-block;\n width: 8px;\n height: 8px;\n border-radius: 50%;\n background: var(--b-table-spin-color);\n animation: b-table-spin-bounce 1.2s ease-in-out infinite;\n}\n\n.b-table__spin-dot:nth-child(1) {\n animation-delay: 0ms;\n}\n.b-table__spin-dot:nth-child(2) {\n animation-delay: 160ms;\n}\n.b-table__spin-dot:nth-child(3) {\n animation-delay: 320ms;\n}\n.b-table__spin-dot:nth-child(4) {\n animation-delay: 480ms;\n}\n\n@keyframes b-table-spin-bounce {\n 0%,\n 60%,\n 100% {\n transform: scale(1);\n opacity: 0.5;\n }\n 30% {\n transform: scale(1.4);\n opacity: 1;\n }\n}\n\n/* ── Pagination ── */\n.b-table__pagination {\n display: flex;\n align-items: center;\n justify-content: flex-end;\n gap: 8px;\n padding: 12px 0 4px;\n flex-wrap: wrap;\n color: var(--b-table-pagination-color);\n font-size: 14px;\n}\n\n.b-table__pagination-total {\n margin-right: auto;\n}\n\n.b-table__pagination-controls {\n display: flex;\n align-items: center;\n gap: 4px;\n flex-wrap: wrap;\n}\n\n.b-table__page-btn {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n min-width: var(--b-table-pagination-btn-size);\n height: var(--b-table-pagination-btn-size);\n padding: 0 8px;\n border: var(--b-table-pagination-btn-border);\n border-radius: var(--b-table-pagination-btn-radius);\n background: var(--b-table-bg);\n color: var(--b-table-pagination-color);\n cursor: pointer;\n font-size: 14px;\n transition:\n background var(--b-table-transition-duration),\n border-color var(--b-table-transition-duration),\n color var(--b-table-transition-duration);\n}\n\n.b-table__page-btn:hover:not(:disabled) {\n background: var(--b-table-pagination-btn-hover-bg);\n border-color: oklch(54.6% 0.245 262.881);\n color: oklch(54.6% 0.245 262.881);\n}\n\n.b-table__page-btn:disabled {\n opacity: 0.4;\n cursor: not-allowed;\n}\n\n.b-table__page-btn--active {\n background: var(--b-table-pagination-btn-active-bg);\n border-color: var(--b-table-pagination-btn-active-bg);\n color: var(--b-table-pagination-btn-active-color);\n font-weight: 600;\n}\n\n.b-table__page-ellipsis {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n min-width: var(--b-table-pagination-btn-size);\n height: var(--b-table-pagination-btn-size);\n font-size: 14px;\n color: var(--b-table-empty-color);\n letter-spacing: 2px;\n}\n\n.b-table__page-size {\n height: var(--b-table-pagination-btn-size);\n border: var(--b-table-pagination-btn-border);\n border-radius: var(--b-table-pagination-btn-radius);\n background: var(--b-table-bg);\n color: var(--b-table-pagination-color);\n padding: 0 24px 0 8px;\n font-size: 14px;\n cursor: pointer;\n outline: none;\n appearance: auto;\n}\n\n.b-table__page-jump {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n font-size: 14px;\n}\n\n.b-table__page-jump-input {\n width: 48px;\n height: var(--b-table-pagination-btn-size);\n border: var(--b-table-pagination-btn-border);\n border-radius: var(--b-table-pagination-btn-radius);\n text-align: center;\n font-size: 14px;\n padding: 0 4px;\n background: var(--b-table-bg);\n color: var(--b-table-pagination-color);\n outline: none;\n box-sizing: border-box;\n}\n\n.b-table__page-jump-input:focus {\n border-color: oklch(54.6% 0.245 262.881);\n box-shadow: 0 0 0 2px oklch(54.6% 0.245 262.881 / 20%);\n}\n\n/* ── Sticky header ── */\n.b-table--sticky .b-table__thead {\n position: sticky;\n top: 0;\n z-index: var(--b-table-sticky-z-index);\n}\n\n/* ── Dark mode ── */\n[data-prefers-color='dark'] .b-table {\n --b-table-bg: oklch(18% 0.015 260);\n --b-table-color: oklch(88% 0.01 260);\n --b-table-border-color: oklch(32% 0.02 260);\n --b-table-header-bg: oklch(22% 0.02 260);\n --b-table-header-color: oklch(85% 0.01 260);\n --b-table-row-hover-bg: oklch(25% 0.02 260);\n --b-table-row-selected-bg: oklch(26% 0.04 262);\n --b-table-row-expanded-bg: oklch(21% 0.015 260);\n --b-table-empty-color: oklch(55% 0.01 260);\n --b-table-footer-bg: oklch(22% 0.02 260);\n --b-table-loading-overlay-bg: oklch(18% 0.015 260);\n --b-table-filter-dropdown-bg: oklch(22% 0.02 260);\n --b-table-filter-dropdown-shadow: 0 6px 16px oklch(0% 0 0 / 30%), 0 3px 6px oklch(0% 0 0 / 20%);\n --b-table-checkbox-border: 1px solid oklch(48% 0.02 260);\n}\n\n@media (prefers-color-scheme: dark) {\n [data-prefers-color='system'] .b-table {\n --b-table-bg: oklch(18% 0.015 260);\n --b-table-color: oklch(88% 0.01 260);\n --b-table-border-color: oklch(32% 0.02 260);\n --b-table-header-bg: oklch(22% 0.02 260);\n --b-table-header-color: oklch(85% 0.01 260);\n --b-table-row-hover-bg: oklch(25% 0.02 260);\n --b-table-row-selected-bg: oklch(26% 0.04 262);\n --b-table-row-expanded-bg: oklch(21% 0.015 260);\n --b-table-empty-color: oklch(55% 0.01 260);\n --b-table-footer-bg: oklch(22% 0.02 260);\n --b-table-loading-overlay-bg: oklch(18% 0.015 260);\n --b-table-filter-dropdown-bg: oklch(22% 0.02 260);\n --b-table-filter-dropdown-shadow: 0 6px 16px oklch(0% 0 0 / 30%), 0 3px 6px oklch(0% 0 0 / 20%);\n --b-table-checkbox-border: 1px solid oklch(48% 0.02 260);\n }\n}\n\n/* ── Reduced motion ── */\n@media (prefers-reduced-motion: reduce) {\n .b-table {\n --b-table-transition-duration: 0ms;\n }\n\n .b-table__spin-dot {\n animation: none;\n }\n\n .b-table__expand-icon {\n transition: none;\n }\n}\n</style>\n"],"mappings":""}
|
package/dist/design-system242.js
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
//#region src/components/BTimeline/BTimeline.vue
|
|
4
|
-
var t = e;
|
|
1
|
+
//#region src/components/BTabs/types.ts
|
|
2
|
+
var e = Symbol("BTabsContext");
|
|
5
3
|
//#endregion
|
|
6
|
-
export {
|
|
4
|
+
export { e as BTabsContextKey };
|
|
7
5
|
|
|
8
6
|
//# sourceMappingURL=design-system242.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"design-system242.js","names":[],"sources":["../src/components/BTimeline/BTimeline.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed, provide } from 'vue';\n\nimport type {\n BTimelineItem,\n BTimelineItemColor,\n BTimelineItemPlacement,\n BTimelineMode,\n BTimelineOrientation,\n BTimelineVariant,\n} from './types';\n\n// ─────────────────────────────────────────────\n// Props\n// ─────────────────────────────────────────────\nconst {\n mode = 'start',\n variant = 'filled',\n orientation = 'vertical',\n pending = false,\n pendingDot,\n reverse = false,\n items,\n} = defineProps<{\n /**\n * Controls which side labels appear on.\n * - `'start'` - all content on the right of the line (default)\n * - `'end'` - all content on the left of the line\n * - `'alternate'` - content alternates left/right\n * @default 'start'\n */\n mode?: BTimelineMode;\n /**\n * Dot style.\n * - `'filled'` - solid filled circle (default)\n * - `'outlined'` - hollow ring with colored border\n * @default 'filled'\n */\n variant?: BTimelineVariant;\n /**\n * Layout direction.\n * - `'vertical'` - items stacked top-to-bottom (default)\n * - `'horizontal'` - items laid out left-to-right\n * @default 'vertical'\n */\n orientation?: BTimelineOrientation;\n /**\n * Whether to show a pending (ghost) item at the bottom.\n * Pass `true` for the default spinner, or a string for custom content.\n * @default false\n */\n pending?: boolean | string;\n /**\n * Custom dot for the pending item. Overridden by the `#pendingDot` slot.\n */\n pendingDot?: string;\n /**\n * Whether to reverse the order of items (newest first).\n * @default false\n */\n reverse?: boolean;\n /**\n * Data-driven items. When provided, slot-based BTimelineItem children are\n * ignored. Use this for simple, data-only timelines.\n */\n items?: BTimelineItem[];\n}>();\n\n// ─────────────────────────────────────────────\n// Slots\n// ─────────────────────────────────────────────\ndefineSlots<{\n /**\n * Default slot: place `<BTimelineItem>` children here.\n * Ignored when `items` prop is provided.\n */\n default?(): unknown;\n /** Custom dot for the pending ghost item. */\n pendingDot?(): unknown;\n}>();\n\n// Provide mode to slot-based BTimelineItem children\nprovide('b-timeline-mode', mode);\nprovide('b-timeline-variant', variant);\n\n// ─────────────────────────────────────────────\n// Pending item helpers\n// ─────────────────────────────────────────────\nconst hasPending = computed(() => !!pending);\nconst pendingContent = computed(() => (pending !== true && pending ? pending : ''));\n\n// ─────────────────────────────────────────────\n// Reversed items (data-driven)\n// ─────────────────────────────────────────────\nconst orderedItems = computed<BTimelineItem[]>(() => {\n if (!items) return [];\n return reverse ? [...items].reverse() : items;\n});\n\n// ─────────────────────────────────────────────\n// CSS-var dot color helper\n// ─────────────────────────────────────────────\nconst PRESET_COLORS: BTimelineItemColor[] = ['blue', 'red', 'green', 'gray'];\n\nfunction isPresetColor(color?: BTimelineItemColor): boolean {\n return !color || PRESET_COLORS.includes(color as string);\n}\n\nfunction dotColorStyle(color?: BTimelineItemColor): Record<string, string> | undefined {\n if (!color || isPresetColor(color)) return undefined;\n return { '--b-timeline-item-dot-color': color };\n}\n\nfunction dotColorClass(color?: BTimelineItemColor): string {\n const c = color ?? 'blue';\n return isPresetColor(c) ? `b-timeline-item--${c}` : 'b-timeline-item--custom';\n}\n\n// ─────────────────────────────────────────────\n// Root classes\n// ─────────────────────────────────────────────\nconst rootClasses = computed(() => [\n 'b-timeline',\n `b-timeline--${mode}`,\n `b-timeline--${variant}`,\n `b-timeline--${orientation}`,\n {\n 'b-timeline--pending': hasPending.value,\n 'b-timeline--reverse': reverse,\n },\n]);\n\n// ─────────────────────────────────────────────\n// Item position helper\n// ─────────────────────────────────────────────\nfunction itemPositionClass(index: number, placement?: BTimelineItemPlacement): string {\n // Per-item placement overrides the global mode\n if (placement) {\n return placement === 'end' ? 'b-timeline-item--right' : 'b-timeline-item--left';\n }\n if (mode === 'alternate') {\n return index % 2 === 0 ? 'b-timeline-item--left' : 'b-timeline-item--right';\n }\n return mode === 'end' ? 'b-timeline-item--right' : 'b-timeline-item--left';\n}\n</script>\n\n<template>\n <ol :class=\"rootClasses\" aria-label=\"Timeline\">\n <!-- ── Data-driven items ── -->\n <template v-if=\"items && items.length\">\n <li\n v-for=\"(item, i) in orderedItems\"\n :key=\"i\"\n class=\"b-timeline-item\"\n :class=\"[\n dotColorClass(item.color),\n itemPositionClass(i, item.placement),\n { 'b-timeline-item--pending': item.loading },\n item.className,\n ]\"\n :style=\"[\n dotColorStyle(item.color),\n typeof item.style === 'string' ? item.style : item.style,\n ]\"\n >\n <!-- Label / title (opposing side) - always rendered as structural spacer; CSS hides in start mode -->\n <span class=\"b-timeline-item__label\">{{ item.title ?? '' }}</span>\n\n <!-- Line + dot -->\n <div class=\"b-timeline-item__tail\" aria-hidden=\"true\" />\n <div class=\"b-timeline-item__dot-wrapper\" aria-hidden=\"true\">\n <template v-if=\"item.icon\">\n <span class=\"b-timeline-item__dot--custom\" :data-icon=\"item.icon\" aria-hidden=\"true\" />\n </template>\n <template v-else-if=\"item.loading\">\n <span class=\"b-timeline-item__dot--pending-spinner\" />\n </template>\n <template v-else>\n <span class=\"b-timeline-item__dot\" />\n </template>\n </div>\n\n <!-- Content -->\n <div class=\"b-timeline-item__content\">{{ item.content }}</div>\n </li>\n </template>\n\n <!-- ── Slot-based items (default slot children) ── -->\n <template v-else>\n <slot />\n </template>\n\n <!-- ── Pending ghost item ── -->\n <li\n v-if=\"hasPending\"\n class=\"b-timeline-item b-timeline-item--pending\"\n :class=\"[itemPositionClass(items ? orderedItems.length : 0)]\"\n aria-label=\"Pending\"\n >\n <!-- Label spacer (structural; hidden in start mode via CSS) -->\n <span class=\"b-timeline-item__label\" />\n <div class=\"b-timeline-item__tail\" aria-hidden=\"true\" />\n <div class=\"b-timeline-item__dot-wrapper\" aria-hidden=\"true\">\n <slot name=\"pendingDot\">\n <span\n v-if=\"pendingDot\"\n class=\"b-timeline-item__dot--custom\"\n :data-icon=\"pendingDot\"\n aria-hidden=\"true\"\n />\n <span v-else class=\"b-timeline-item__dot--pending-spinner\" aria-hidden=\"true\" />\n </slot>\n </div>\n <div class=\"b-timeline-item__content\">{{ pendingContent }}</div>\n </li>\n </ol>\n</template>\n\n<style>\n/* ─────────────────────────────────────────────\n BTimeline - CSS custom properties (scoped to root)\n ───────────────────────────────────────────── */\n.b-timeline {\n /* ── Structural ── */\n --b-timeline-line-width: 2px;\n --b-timeline-line-color: oklch(90% 0.005 260);\n --b-timeline-item-padding-bottom: 20px;\n\n /* ── Dot ── */\n --b-timeline-dot-size: 10px;\n --b-timeline-dot-offset: 0px; /* fine-tune vertical alignment */\n --b-timeline-dot-border-width: 2px;\n --b-timeline-custom-dot-font-size: 20px; /* emoji / text custom dots */\n\n /* ── Blue (default) ── */\n --b-timeline-color-blue: oklch(54.6% 0.245 262.881);\n /* ── Green ── */\n --b-timeline-color-green: oklch(52% 0.17 145);\n /* ── Red ── */\n --b-timeline-color-red: oklch(50% 0.2 20);\n /* ── Gray ── */\n --b-timeline-color-gray: oklch(68% 0.01 260);\n\n /* ── Content text ── */\n --b-timeline-content-color: oklch(32% 0.02 260);\n --b-timeline-content-font-size: 14px;\n\n /* ── Label text ── */\n --b-timeline-label-color: oklch(52% 0.01 260);\n --b-timeline-label-font-size: 14px;\n\n /* ── Pending ── */\n --b-timeline-pending-line-style: dashed;\n --b-timeline-pending-dot-color: oklch(70% 0.01 260);\n\n /* ── Spinner ── */\n --b-timeline-spinner-size: 14px;\n --b-timeline-spinner-border-color: oklch(54.6% 0.245 262.881 / 20%);\n --b-timeline-spinner-accent-color: oklch(54.6% 0.245 262.881);\n --b-timeline-spinner-duration: 700ms;\n\n /* ── Transition ── */\n --b-timeline-transition-duration: 200ms;\n\n /* ── Layout ── */\n box-sizing: border-box;\n margin: 0;\n padding: 0;\n list-style: none;\n font-size: var(--b-timeline-content-font-size);\n line-height: 1.5;\n color: var(--b-timeline-content-color);\n}\n\n/* ─────────────────────────────────────────────\n Item\n ───────────────────────────────────────────── */\n.b-timeline-item {\n position: relative;\n display: flex;\n align-items: flex-start;\n padding-bottom: var(--b-timeline-item-padding-bottom);\n margin: 0;\n list-style: none;\n}\n\n/* last item - hide tail, collapse bottom padding */\n.b-timeline-item:last-child {\n padding-bottom: 0;\n}\n\n.b-timeline-item:last-child .b-timeline-item__tail {\n display: none;\n}\n\n/* ── Tail (vertical line) ── */\n.b-timeline-item__tail {\n position: absolute;\n top: calc(var(--b-timeline-dot-size) + 4px);\n left: calc(\n (var(--b-timeline-dot-size) / 2) - (var(--b-timeline-line-width) / 2)\n ); /* overridden per-mode */\n height: calc(100% - var(--b-timeline-dot-size) - 4px);\n width: var(--b-timeline-line-width);\n background: var(--b-timeline-line-color);\n transition: background var(--b-timeline-transition-duration);\n}\n\n/* ── Dot wrapper ── */\n.b-timeline-item__dot-wrapper {\n position: relative;\n flex-shrink: 0;\n width: var(--b-timeline-dot-size);\n height: var(--b-timeline-dot-size);\n margin-top: var(--b-timeline-dot-offset);\n z-index: 1;\n overflow: visible;\n}\n\n/* ── Standard dot ── */\n.b-timeline-item__dot {\n display: block;\n width: 100%;\n height: 100%;\n border-radius: 50%;\n background: var(--b-timeline-item-current-color, var(--b-timeline-color-blue));\n box-sizing: border-box;\n}\n\n/* ── Custom dot (icon / text) ── */\n.b-timeline-item__dot--custom {\n background: transparent;\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: var(--b-timeline-custom-dot-font-size);\n line-height: 1;\n width: var(--b-timeline-custom-dot-font-size);\n height: var(--b-timeline-custom-dot-font-size);\n /* shift left/up so the icon stays centred over the dot position */\n position: absolute;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n color: var(--b-timeline-item-current-color, var(--b-timeline-color-blue));\n}\n\n/* Symbol rendered via ::before so no text node exists - avoids axe non-text-char rule */\n.b-timeline-item__dot--custom::before {\n content: attr(data-icon);\n}\n\n/* ─────────────────────────────────────────────\n Preset colors - set --b-timeline-item-current-color\n ───────────────────────────────────────────── */\n.b-timeline-item--blue {\n --b-timeline-item-current-color: var(--b-timeline-color-blue);\n}\n\n.b-timeline-item--green {\n --b-timeline-item-current-color: var(--b-timeline-color-green);\n}\n\n.b-timeline-item--red {\n --b-timeline-item-current-color: var(--b-timeline-color-red);\n}\n\n.b-timeline-item--gray {\n --b-timeline-item-current-color: var(--b-timeline-color-gray);\n}\n\n/* Custom (non-preset) color: provided via inline var */\n.b-timeline-item--custom {\n --b-timeline-item-current-color: var(--b-timeline-item-dot-color);\n}\n\n/* ─────────────────────────────────────────────\n Content & Label\n ───────────────────────────────────────────── */\n.b-timeline-item__content {\n flex: 1;\n padding-left: 12px;\n color: var(--b-timeline-content-color);\n font-size: var(--b-timeline-content-font-size);\n word-break: break-word;\n}\n\n.b-timeline-item__label {\n display: none; /* hidden in start mode; shown in alternate/end */\n color: var(--b-timeline-label-color);\n font-size: var(--b-timeline-label-font-size);\n text-align: right;\n flex-shrink: 0;\n}\n\n/* ─────────────────────────────────────────────\n Mode: start (default) - content right of line\n Each item: [dot] [content]\n ───────────────────────────────────────────── */\n.b-timeline--start .b-timeline-item {\n flex-direction: row;\n}\n\n.b-timeline--start .b-timeline-item__tail {\n left: calc((var(--b-timeline-dot-size) / 2) - (var(--b-timeline-line-width) / 2));\n}\n\n.b-timeline--start .b-timeline-item__label {\n display: none;\n}\n\n/* ─────────────────────────────────────────────\n Mode: end - content left of line\n Each item: [content] [dot]\n ───────────────────────────────────────────── */\n.b-timeline--end .b-timeline-item {\n flex-direction: row-reverse;\n}\n\n.b-timeline--end .b-timeline-item__content {\n padding-left: 0;\n padding-right: 12px;\n text-align: right;\n}\n\n.b-timeline--end .b-timeline-item__tail {\n right: calc((var(--b-timeline-dot-size) / 2) - (var(--b-timeline-line-width) / 2));\n left: auto;\n}\n\n.b-timeline--end .b-timeline-item__label {\n display: none;\n}\n\n/* ─────────────────────────────────────────────\n Mode: alternate\n Each row is always: [left-half][dot][right-half]\n Left items (even): left-half = label (right-aligned)\n right-half = content (left-aligned)\n Right items (odd): left-half = content (right-aligned)\n right-half = label (left-aligned)\n We use CSS `order` to swap - never flex-direction:row-reverse,\n which would flip the padding direction and break the gap.\n ───────────────────────────────────────────── */\n\n/* Label: always rendered as a structural spacer; hidden in start mode */\n.b-timeline--alternate .b-timeline-item__label {\n display: block;\n /* Each half = 50% of the row minus half the dot width.\n box-sizing:border-box means padding is included in this width,\n so the dot centre lands exactly at 50% of the row. */\n flex: 0 0 calc(50% - var(--b-timeline-dot-size) / 2);\n box-sizing: border-box;\n font-size: var(--b-timeline-label-font-size);\n color: var(--b-timeline-label-color);\n word-break: break-word;\n}\n\n/* Content: same symmetric half-width */\n.b-timeline--alternate .b-timeline-item__content {\n flex: 0 0 calc(50% - var(--b-timeline-dot-size) / 2);\n box-sizing: border-box;\n}\n\n/* Tail: always at exactly 50% of the row, regardless of which items have labels */\n.b-timeline--alternate .b-timeline-item__tail {\n left: calc(50% - var(--b-timeline-line-width) / 2);\n transform: none;\n}\n\n/* ── Left items (even): label LEFT → dot → content RIGHT ── */\n/* DOM order: label(1) tail(abs) dot(2) content(3) - already correct, no reordering needed */\n.b-timeline--alternate .b-timeline-item--left .b-timeline-item__label {\n order: 1;\n text-align: right;\n padding-right: 12px;\n padding-left: 0;\n}\n\n.b-timeline--alternate .b-timeline-item--left .b-timeline-item__dot-wrapper {\n order: 2;\n}\n\n.b-timeline--alternate .b-timeline-item--left .b-timeline-item__content {\n order: 3;\n text-align: left;\n padding-left: 12px;\n padding-right: 0;\n}\n\n/* ── Right items (odd): content LEFT → dot → label RIGHT ── */\n/* Use `order` to pull content before dot, push label after dot */\n.b-timeline--alternate .b-timeline-item--right .b-timeline-item__content {\n order: 1;\n text-align: right;\n padding-right: 12px;\n padding-left: 0;\n}\n\n.b-timeline--alternate .b-timeline-item--right .b-timeline-item__dot-wrapper {\n order: 2;\n}\n\n.b-timeline--alternate .b-timeline-item--right .b-timeline-item__label {\n order: 3;\n text-align: left;\n padding-left: 12px;\n padding-right: 0;\n}\n\n/* ─────────────────────────────────────────────\n Variant: outlined - hollow ring dot\n ───────────────────────────────────────────── */\n.b-timeline--outlined .b-timeline-item__dot {\n background: transparent;\n border: var(--b-timeline-dot-border-width) solid\n var(--b-timeline-item-current-color, var(--b-timeline-color-blue));\n}\n\n/* ─────────────────────────────────────────────\n Orientation: horizontal\n Items laid out left-to-right; tail becomes a\n horizontal bar running to the right.\n ───────────────────────────────────────────── */\n.b-timeline--horizontal {\n display: flex;\n flex-direction: row;\n align-items: flex-start;\n overflow-x: auto;\n}\n\n.b-timeline--horizontal.b-timeline--start .b-timeline-item,\n.b-timeline--horizontal.b-timeline--end .b-timeline-item {\n flex-direction: column;\n align-items: center;\n flex: 1;\n padding-bottom: 0;\n padding-right: 0;\n min-width: 80px;\n}\n\n/* Horizontal tail: runs right from the dot */\n.b-timeline--horizontal .b-timeline-item__tail {\n top: calc(var(--b-timeline-dot-size) / 2 - var(--b-timeline-line-width) / 2);\n left: calc(var(--b-timeline-dot-size) + 4px);\n width: calc(100% - var(--b-timeline-dot-size) - 4px);\n height: var(--b-timeline-line-width);\n right: auto;\n bottom: auto;\n}\n\n/* In horizontal mode the last item still hides its tail */\n.b-timeline--horizontal .b-timeline-item:last-child .b-timeline-item__tail {\n display: none;\n}\n\n/* Dot: centred horizontally above content */\n.b-timeline--horizontal .b-timeline-item__dot-wrapper {\n margin-top: 0;\n flex-shrink: 0;\n}\n\n/* Content sits below the dot row */\n.b-timeline--horizontal .b-timeline-item__content {\n padding-left: 0;\n padding-top: 8px;\n text-align: center;\n width: 100%;\n}\n\n/* Label sits above the dot row (for start mode) */\n.b-timeline--horizontal.b-timeline--start .b-timeline-item__label {\n display: block;\n text-align: center;\n padding-bottom: 8px;\n order: 1;\n}\n\n.b-timeline--horizontal.b-timeline--start .b-timeline-item__dot-wrapper {\n order: 2;\n}\n\n.b-timeline--horizontal.b-timeline--start .b-timeline-item__content {\n order: 3;\n}\n\n/* For end mode, label goes below content */\n.b-timeline--horizontal.b-timeline--end .b-timeline-item__content {\n order: 1;\n padding-top: 8px;\n padding-right: 0;\n text-align: center;\n}\n\n.b-timeline--horizontal.b-timeline--end .b-timeline-item__dot-wrapper {\n order: 2;\n}\n\n.b-timeline--horizontal.b-timeline--end .b-timeline-item__label {\n display: block;\n text-align: center;\n padding-top: 8px;\n order: 3;\n}\n\n/* ─────────────────────────────────────────────\n Pending item\n ───────────────────────────────────────────── */\n.b-timeline-item--pending .b-timeline-item__tail {\n border-left: var(--b-timeline-line-width) var(--b-timeline-pending-line-style)\n var(--b-timeline-line-color);\n background: transparent;\n}\n\n.b-timeline--start .b-timeline-item--pending .b-timeline-item__tail {\n /* override the absolute left, matches .b-timeline--start logic */\n left: calc((var(--b-timeline-dot-size) / 2) - (var(--b-timeline-line-width) / 2));\n}\n\n/* Horizontal pending tail */\n.b-timeline--horizontal .b-timeline-item--pending .b-timeline-item__tail {\n border-left: none;\n border-top: var(--b-timeline-line-width) var(--b-timeline-pending-line-style)\n var(--b-timeline-line-color);\n background: transparent;\n height: 0;\n}\n\n/* ── Default pending spinner ── */\n.b-timeline-item__dot--pending-spinner {\n display: block;\n width: var(--b-timeline-spinner-size);\n height: var(--b-timeline-spinner-size);\n margin-left: calc((var(--b-timeline-dot-size) - var(--b-timeline-spinner-size)) / 2);\n border-radius: 50%;\n border: var(--b-timeline-dot-border-width) solid var(--b-timeline-spinner-border-color);\n border-top-color: var(--b-timeline-spinner-accent-color);\n animation: b-timeline-spin var(--b-timeline-spinner-duration) linear infinite;\n}\n\n@keyframes b-timeline-spin {\n to {\n transform: rotate(360deg);\n }\n}\n\n/* ─────────────────────────────────────────────\n Dark mode\n ───────────────────────────────────────────── */\n[data-prefers-color='dark'] .b-timeline {\n --b-timeline-line-color: oklch(32% 0.012 260);\n --b-timeline-content-color: oklch(82% 0.01 260);\n --b-timeline-label-color: oklch(60% 0.01 260);\n --b-timeline-color-gray: oklch(50% 0.01 260);\n --b-timeline-spinner-border-color: oklch(54.6% 0.245 262.881 / 15%);\n}\n\n@media (prefers-color-scheme: dark) {\n [data-prefers-color='system'] .b-timeline {\n --b-timeline-line-color: oklch(32% 0.012 260);\n --b-timeline-content-color: oklch(82% 0.01 260);\n --b-timeline-label-color: oklch(60% 0.01 260);\n --b-timeline-color-gray: oklch(50% 0.01 260);\n --b-timeline-spinner-border-color: oklch(54.6% 0.245 262.881 / 15%);\n }\n}\n\n/* ─────────────────────────────────────────────\n Reduced motion\n ───────────────────────────────────────────── */\n@media (prefers-reduced-motion: reduce) {\n .b-timeline {\n --b-timeline-transition-duration: 0ms;\n --b-timeline-spinner-duration: 0ms;\n }\n\n .b-timeline-item__dot--pending-spinner {\n animation: none;\n border-top-color: var(--b-timeline-spinner-border-color);\n opacity: 0.6;\n }\n}\n</style>\n"],"mappings":""}
|
|
1
|
+
{"version":3,"file":"design-system242.js","names":[],"sources":["../src/components/BTabs/types.ts"],"sourcesContent":["import type { ComputedRef, InjectionKey, VNode } from 'vue';\n\nexport type BTabsType = 'line' | 'card' | 'editable-card';\nexport type BTabsPlacement = 'top' | 'bottom' | 'left' | 'right';\nexport type BTabsSize = 'small' | 'middle' | 'large';\n\nexport interface BTabItem {\n /** Unique identifier for the tab. */\n key: string;\n /** Tab header text or VNode. */\n label?: string | VNode;\n /** Whether the tab is disabled. @default false */\n disabled?: boolean;\n /** Whether the close button is shown (editable-card only). @default true */\n closable?: boolean;\n /** Whether to destroy content when tab is hidden. @default false */\n destroyOnHidden?: boolean;\n /** Whether to force render content even when not active. @default false */\n forceRender?: boolean;\n /** Whether to keep this tab's component state alive when switching away. Overrides global keepAlive. */\n keepAlive?: boolean;\n}\n\n// ─────────────────────────────────────────────\n// BTabPane registration (provide/inject)\n// ─────────────────────────────────────────────\n\nexport interface BTabPaneRegistration {\n /** Unique key for this pane. */\n key: string;\n /** Tab label (text or VNode). */\n label?: string | VNode;\n /** Whether tab is disabled. */\n disabled?: boolean;\n /** Whether close button shows in editable-card mode. */\n closable?: boolean;\n /** Whether to destroy when hidden. */\n destroyOnHidden?: boolean;\n /** Whether to force render even when inactive. */\n forceRender?: boolean;\n /** Per-pane keepAlive override. */\n keepAlive?: boolean;\n /** Render function for the pane content (from BTabPane's default slot). */\n renderContent: () => VNode[];\n /** Optional render function for custom label (from BTabPane's tab slot). */\n renderLabel?: () => VNode[];\n}\n\n// ─────────────────────────────────────────────\n// Context injected from BTabs to BTabPane children\n// ─────────────────────────────────────────────\n\nexport interface BTabsContext {\n /** Currently active tab key. */\n activeKey: ComputedRef<string>;\n /** Tab type. */\n type: ComputedRef<BTabsType>;\n /** Tab size. */\n size: ComputedRef<BTabsSize>;\n /** Tab placement. */\n placement: ComputedRef<BTabsPlacement>;\n /** Global keepAlive setting. */\n keepAlive: ComputedRef<boolean>;\n /** Global destroyOnHidden setting. */\n destroyOnHidden: ComputedRef<boolean>;\n /** Register a pane. */\n register: (pane: BTabPaneRegistration) => void;\n /** Unregister a pane by key. */\n unregister: (key: string) => void;\n /** Update a registered pane's data (when props change reactively). */\n update: (key: string, pane: BTabPaneRegistration) => void;\n}\n\nexport const BTabsContextKey: InjectionKey<BTabsContext> = Symbol('BTabsContext');\n"],"mappings":";AAyEA,IAAa,IAA8C,OAAO,eAAe"}
|
package/dist/design-system243.js
CHANGED
|
@@ -1,57 +1,49 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
}, d = ["data-icon"], f = {
|
|
7
|
-
key: 1,
|
|
8
|
-
class: "b-timeline-item__dot--pending-spinner"
|
|
9
|
-
}, p = { class: "b-timeline-item__content" }, m = /* @__PURE__ */ r({
|
|
10
|
-
__name: "BTimelineItem",
|
|
1
|
+
import { BTabsContextKey as e } from "./design-system242.js";
|
|
2
|
+
import { createCommentVNode as t, defineComponent as n, inject as r, onBeforeUnmount as i, onMounted as a, useSlots as o, watch as s } from "vue";
|
|
3
|
+
//#region src/components/BTabs/BTabPane.vue?vue&type=script&setup=true&lang.ts
|
|
4
|
+
var c = /* @__PURE__ */ n({
|
|
5
|
+
__name: "BTabPane",
|
|
11
6
|
props: {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
},
|
|
18
|
-
|
|
7
|
+
tabKey: {},
|
|
8
|
+
tab: {},
|
|
9
|
+
disabled: { type: Boolean },
|
|
10
|
+
closable: { type: Boolean },
|
|
11
|
+
destroyOnHidden: { type: Boolean },
|
|
12
|
+
forceRender: { type: Boolean },
|
|
13
|
+
keepAlive: { type: Boolean }
|
|
19
14
|
},
|
|
20
|
-
setup(
|
|
21
|
-
let
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
}, null, 2))])]),
|
|
50
|
-
n("div", p, [c(e.$slots, "default")])
|
|
51
|
-
], 6));
|
|
15
|
+
setup(n) {
|
|
16
|
+
let c = n, l = o(), u = r(e, null);
|
|
17
|
+
function d() {
|
|
18
|
+
return {
|
|
19
|
+
key: c.tabKey,
|
|
20
|
+
label: c.tab,
|
|
21
|
+
disabled: c.disabled,
|
|
22
|
+
closable: c.closable,
|
|
23
|
+
destroyOnHidden: c.destroyOnHidden,
|
|
24
|
+
forceRender: c.forceRender,
|
|
25
|
+
keepAlive: c.keepAlive,
|
|
26
|
+
renderContent: () => l.default?.() ?? [],
|
|
27
|
+
renderLabel: l.tab ? () => l.tab() : void 0
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
return a(() => {
|
|
31
|
+
u?.register(d());
|
|
32
|
+
}), i(() => {
|
|
33
|
+
u?.unregister(c.tabKey);
|
|
34
|
+
}), s(() => [
|
|
35
|
+
c.tab,
|
|
36
|
+
c.disabled,
|
|
37
|
+
c.closable,
|
|
38
|
+
c.destroyOnHidden,
|
|
39
|
+
c.forceRender,
|
|
40
|
+
c.keepAlive
|
|
41
|
+
], () => {
|
|
42
|
+
u?.update(c.tabKey, d());
|
|
43
|
+
}), (e, n) => t("", !0);
|
|
52
44
|
}
|
|
53
45
|
});
|
|
54
46
|
//#endregion
|
|
55
|
-
export {
|
|
47
|
+
export { c as default };
|
|
56
48
|
|
|
57
49
|
//# sourceMappingURL=design-system243.js.map
|