@rotki/ui-library 3.0.0 → 3.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (170) hide show
  1. package/dist/components/accordions/accordion/RuiAccordion.js.map +1 -1
  2. package/dist/components/accordions/accordion/RuiAccordion.vue_vue_type_script_setup_true_lang.js +13 -2
  3. package/dist/components/accordions/accordion/RuiAccordion.vue_vue_type_script_setup_true_lang.js.map +1 -1
  4. package/dist/components/alerts/RuiAlert.js.map +1 -1
  5. package/dist/components/alerts/RuiAlert.vue.d.ts +2 -2
  6. package/dist/components/alerts/RuiAlert.vue_vue_type_script_setup_true_lang.js.map +1 -1
  7. package/dist/components/buttons/button/RuiButton.js.map +1 -1
  8. package/dist/components/buttons/button/RuiButton.vue.d.ts +7 -1
  9. package/dist/components/buttons/button/RuiButton.vue_vue_type_script_setup_true_lang.js +4 -2
  10. package/dist/components/buttons/button/RuiButton.vue_vue_type_script_setup_true_lang.js.map +1 -1
  11. package/dist/components/buttons/button/button-props.d.ts +3 -0
  12. package/dist/components/buttons/button/button-props.js +2 -1
  13. package/dist/components/buttons/button/button-props.js.map +1 -1
  14. package/dist/components/buttons/button/button-styles.d.ts +75 -0
  15. package/dist/components/buttons/button/button-styles.js +135 -4
  16. package/dist/components/buttons/button/button-styles.js.map +1 -1
  17. package/dist/components/cards/RuiCard.js.map +1 -1
  18. package/dist/components/cards/RuiCard.vue.d.ts +6 -0
  19. package/dist/components/cards/RuiCard.vue_vue_type_script_setup_true_lang.js +14 -2
  20. package/dist/components/cards/RuiCard.vue_vue_type_script_setup_true_lang.js.map +1 -1
  21. package/dist/components/chips/RuiChip.js.map +1 -1
  22. package/dist/components/chips/RuiChip.vue_vue_type_script_setup_true_lang.js +15 -1
  23. package/dist/components/chips/RuiChip.vue_vue_type_script_setup_true_lang.js.map +1 -1
  24. package/dist/components/chips/chip-props.d.ts +2 -0
  25. package/dist/components/chips/chip-props.js +3 -0
  26. package/dist/components/chips/chip-props.js.map +1 -1
  27. package/dist/components/color-picker/RuiColorInput.js.map +1 -1
  28. package/dist/components/color-picker/RuiColorInput.vue_vue_type_script_setup_true_lang.js +2 -2
  29. package/dist/components/color-picker/RuiColorInput.vue_vue_type_script_setup_true_lang.js.map +1 -1
  30. package/dist/components/date-time-picker/RuiDateTimePicker.js.map +1 -1
  31. package/dist/components/date-time-picker/RuiDateTimePicker.vue.d.ts +4 -0
  32. package/dist/components/date-time-picker/RuiDateTimePicker.vue_vue_type_script_setup_true_lang.js +6 -2
  33. package/dist/components/date-time-picker/RuiDateTimePicker.vue_vue_type_script_setup_true_lang.js.map +1 -1
  34. package/dist/components/date-time-picker/RuiDateTimePickerMenu.js.map +1 -1
  35. package/dist/components/date-time-picker/RuiDateTimePickerMenu.vue_vue_type_script_setup_true_lang.js +1 -1
  36. package/dist/components/date-time-picker/RuiDateTimePickerMenu.vue_vue_type_script_setup_true_lang.js.map +1 -1
  37. package/dist/components/date-time-picker/RuiTimezoneSelect.js.map +1 -1
  38. package/dist/components/date-time-picker/RuiTimezoneSelect.vue.d.ts +4 -0
  39. package/dist/components/date-time-picker/RuiTimezoneSelect.vue_vue_type_script_setup_true_lang.js +3 -2
  40. package/dist/components/date-time-picker/RuiTimezoneSelect.vue_vue_type_script_setup_true_lang.js.map +1 -1
  41. package/dist/components/forms/auto-complete/RuiAutoComplete.js.map +1 -1
  42. package/dist/components/forms/auto-complete/RuiAutoComplete.vue_vue_type_script_setup_true_lang.js +6 -2
  43. package/dist/components/forms/auto-complete/RuiAutoComplete.vue_vue_type_script_setup_true_lang.js.map +1 -1
  44. package/dist/components/forms/auto-complete/auto-complete-props.d.ts +4 -0
  45. package/dist/components/forms/category-picker/RuiCategoryPicker.js.map +1 -1
  46. package/dist/components/forms/category-picker/RuiCategoryPicker.vue.d.ts +4 -1
  47. package/dist/components/forms/category-picker/RuiCategoryPicker.vue_vue_type_script_setup_true_lang.js +7 -4
  48. package/dist/components/forms/category-picker/RuiCategoryPicker.vue_vue_type_script_setup_true_lang.js.map +1 -1
  49. package/dist/components/forms/check-control-styles.d.ts +27 -0
  50. package/dist/components/forms/check-control-styles.js +26 -1
  51. package/dist/components/forms/check-control-styles.js.map +1 -1
  52. package/dist/components/forms/checkbox/RuiCheckbox.js.map +1 -1
  53. package/dist/components/forms/checkbox/RuiCheckbox.vue.d.ts +6 -0
  54. package/dist/components/forms/checkbox/RuiCheckbox.vue_vue_type_script_setup_true_lang.js +14 -4
  55. package/dist/components/forms/checkbox/RuiCheckbox.vue_vue_type_script_setup_true_lang.js.map +1 -1
  56. package/dist/components/forms/checkbox/checkbox-group/RuiCheckboxGroup.js.map +1 -1
  57. package/dist/components/forms/checkbox/checkbox-group/RuiCheckboxGroup.vue.d.ts +4 -0
  58. package/dist/components/forms/checkbox/checkbox-group/RuiCheckboxGroup.vue_vue_type_script_setup_true_lang.js +7 -2
  59. package/dist/components/forms/checkbox/checkbox-group/RuiCheckboxGroup.vue_vue_type_script_setup_true_lang.js.map +1 -1
  60. package/dist/components/forms/field-defaults/RuiFieldDefaults.js.map +1 -1
  61. package/dist/components/forms/field-defaults/RuiFieldDefaults.vue.d.ts +3 -0
  62. package/dist/components/forms/field-defaults/RuiFieldDefaults.vue_vue_type_script_setup_true_lang.js +8 -2
  63. package/dist/components/forms/field-defaults/RuiFieldDefaults.vue_vue_type_script_setup_true_lang.js.map +1 -1
  64. package/dist/components/forms/file-upload/RuiFileUpload.js.map +1 -1
  65. package/dist/components/forms/file-upload/RuiFileUpload.vue.d.ts +4 -1
  66. package/dist/components/forms/file-upload/RuiFileUpload.vue_vue_type_script_setup_true_lang.js +18 -6
  67. package/dist/components/forms/file-upload/RuiFileUpload.vue_vue_type_script_setup_true_lang.js.map +1 -1
  68. package/dist/components/forms/radio-button/radio/RuiRadio.js.map +1 -1
  69. package/dist/components/forms/radio-button/radio/RuiRadio.vue.d.ts +6 -0
  70. package/dist/components/forms/radio-button/radio/RuiRadio.vue_vue_type_script_setup_true_lang.js +14 -3
  71. package/dist/components/forms/radio-button/radio/RuiRadio.vue_vue_type_script_setup_true_lang.js.map +1 -1
  72. package/dist/components/forms/radio-button/radio-group/RuiRadioGroup.js.map +1 -1
  73. package/dist/components/forms/radio-button/radio-group/RuiRadioGroup.vue.d.ts +4 -0
  74. package/dist/components/forms/radio-button/radio-group/RuiRadioGroup.vue_vue_type_script_setup_true_lang.js +7 -2
  75. package/dist/components/forms/radio-button/radio-group/RuiRadioGroup.vue_vue_type_script_setup_true_lang.js.map +1 -1
  76. package/dist/components/forms/revealable-text-field/RuiRevealableTextField.js.map +1 -1
  77. package/dist/components/forms/revealable-text-field/RuiRevealableTextField.vue_vue_type_script_setup_true_lang.js +3 -2
  78. package/dist/components/forms/revealable-text-field/RuiRevealableTextField.vue_vue_type_script_setup_true_lang.js.map +1 -1
  79. package/dist/components/forms/select/RuiMenuSelect.js.map +1 -1
  80. package/dist/components/forms/select/RuiMenuSelect.vue.d.ts +4 -0
  81. package/dist/components/forms/select/RuiMenuSelect.vue_vue_type_script_setup_true_lang.js +6 -2
  82. package/dist/components/forms/select/RuiMenuSelect.vue_vue_type_script_setup_true_lang.js.map +1 -1
  83. package/dist/components/forms/slider/RuiSlider.js.map +1 -1
  84. package/dist/components/forms/slider/RuiSlider.vue.d.ts +4 -0
  85. package/dist/components/forms/slider/RuiSlider.vue_vue_type_script_setup_true_lang.js +6 -3
  86. package/dist/components/forms/slider/RuiSlider.vue_vue_type_script_setup_true_lang.js.map +1 -1
  87. package/dist/components/forms/switch/RuiSwitch.js.map +1 -1
  88. package/dist/components/forms/switch/RuiSwitch.vue.d.ts +6 -0
  89. package/dist/components/forms/switch/RuiSwitch.vue_vue_type_script_setup_true_lang.js +25 -5
  90. package/dist/components/forms/switch/RuiSwitch.vue_vue_type_script_setup_true_lang.js.map +1 -1
  91. package/dist/components/forms/text-area/RuiTextArea.js.map +1 -1
  92. package/dist/components/forms/text-area/RuiTextArea.vue.d.ts +4 -0
  93. package/dist/components/forms/text-area/RuiTextArea.vue_vue_type_script_setup_true_lang.js +6 -4
  94. package/dist/components/forms/text-area/RuiTextArea.vue_vue_type_script_setup_true_lang.js.map +1 -1
  95. package/dist/components/forms/text-area/text-area-styles.d.ts +0 -1
  96. package/dist/components/forms/text-area/text-area-styles.js +6 -7
  97. package/dist/components/forms/text-area/text-area-styles.js.map +1 -1
  98. package/dist/components/forms/text-field/RuiTextField.js.map +1 -1
  99. package/dist/components/forms/text-field/RuiTextField.vue.d.ts +6 -2
  100. package/dist/components/forms/text-field/RuiTextField.vue_vue_type_script_setup_true_lang.js +6 -4
  101. package/dist/components/forms/text-field/RuiTextField.vue_vue_type_script_setup_true_lang.js.map +1 -1
  102. package/dist/components/forms/text-field/text-field-styles.d.ts +0 -1
  103. package/dist/components/forms/text-field/text-field-styles.js +6 -7
  104. package/dist/components/forms/text-field/text-field-styles.js.map +1 -1
  105. package/dist/components/helpers/RuiFormTextDetail.js.map +1 -1
  106. package/dist/components/helpers/RuiFormTextDetail.vue.d.ts +14 -0
  107. package/dist/components/helpers/RuiFormTextDetail.vue_vue_type_script_setup_true_lang.js +41 -11
  108. package/dist/components/helpers/RuiFormTextDetail.vue_vue_type_script_setup_true_lang.js.map +1 -1
  109. package/dist/components/loaders/RuiSkeletonBase.js.map +1 -1
  110. package/dist/components/loaders/RuiSkeletonBase.vue_vue_type_script_setup_true_lang.js.map +1 -1
  111. package/dist/components/overlays/menu/RuiMenu.js.map +1 -1
  112. package/dist/components/overlays/menu/RuiMenu.vue.d.ts +3 -0
  113. package/dist/components/overlays/menu/RuiMenu.vue_vue_type_script_setup_true_lang.js +9 -1
  114. package/dist/components/overlays/menu/RuiMenu.vue_vue_type_script_setup_true_lang.js.map +1 -1
  115. package/dist/components/tables/RuiDataTable.js.map +1 -1
  116. package/dist/components/tables/RuiDataTable.vue.d.ts +4 -0
  117. package/dist/components/tables/RuiDataTable.vue_vue_type_script_setup_true_lang.js +7 -1
  118. package/dist/components/tables/RuiDataTable.vue_vue_type_script_setup_true_lang.js.map +1 -1
  119. package/dist/components/tables/RuiExpandButton.js.map +1 -1
  120. package/dist/components/tables/RuiExpandButton.vue_vue_type_script_setup_true_lang.js.map +1 -1
  121. package/dist/components/tables/RuiTableHead.js.map +1 -1
  122. package/dist/components/tables/RuiTableHead.vue_vue_type_script_setup_true_lang.js +2 -2
  123. package/dist/components/tables/RuiTableHead.vue_vue_type_script_setup_true_lang.js.map +1 -1
  124. package/dist/components/tables/RuiTablePagination.js.map +1 -1
  125. package/dist/components/tables/RuiTablePagination.vue_vue_type_script_setup_true_lang.js +3 -3
  126. package/dist/components/tables/RuiTablePagination.vue_vue_type_script_setup_true_lang.js.map +1 -1
  127. package/dist/components/tables/data-table/RuiDataTableAppendRow.js.map +1 -1
  128. package/dist/components/tables/data-table/RuiDataTableAppendRow.vue_vue_type_script_setup_true_lang.js.map +1 -1
  129. package/dist/components/tables/data-table/RuiDataTableRow.js.map +1 -1
  130. package/dist/components/tables/data-table/RuiDataTableRow.vue_vue_type_script_setup_true_lang.js +2 -2
  131. package/dist/components/tables/data-table/RuiDataTableRow.vue_vue_type_script_setup_true_lang.js.map +1 -1
  132. package/dist/components/tables/data-table-styles.d.ts +8 -1
  133. package/dist/components/tables/data-table-styles.js +14 -5
  134. package/dist/components/tables/data-table-styles.js.map +1 -1
  135. package/dist/components/tabs/tab-items/RuiTabItems.js.map +1 -1
  136. package/dist/components/tabs/tab-items/RuiTabItems.vue_vue_type_script_setup_true_lang.js +9 -2
  137. package/dist/components/tabs/tab-items/RuiTabItems.vue_vue_type_script_setup_true_lang.js.map +1 -1
  138. package/dist/composables/clip-while-resizing.d.ts +15 -0
  139. package/dist/composables/clip-while-resizing.js +46 -0
  140. package/dist/composables/clip-while-resizing.js.map +1 -0
  141. package/dist/composables/defaults/field.d.ts +19 -3
  142. package/dist/composables/defaults/field.js +32 -6
  143. package/dist/composables/defaults/field.js.map +1 -1
  144. package/dist/composables/forms/auto-complete/keyboard-navigation.js.map +1 -1
  145. package/dist/composables/sticky-blocker.d.ts +20 -0
  146. package/dist/composables/sticky-blocker.js +62 -0
  147. package/dist/composables/sticky-blocker.js.map +1 -0
  148. package/dist/composables/tables/data-table/columns.js.map +1 -1
  149. package/dist/composables/tables/data-table/selection.js +23 -21
  150. package/dist/composables/tables/data-table/selection.js.map +1 -1
  151. package/dist/icons/icons_1.js.map +1 -1
  152. package/dist/icons/icons_2.js.map +1 -1
  153. package/dist/icons/icons_3.js.map +1 -1
  154. package/dist/icons/icons_4.js.map +1 -1
  155. package/dist/icons/icons_5.js.map +1 -1
  156. package/dist/index.d.ts +6 -2
  157. package/dist/index.js.map +1 -1
  158. package/dist/style.css +325 -19
  159. package/dist/styles/colors.css +4 -0
  160. package/dist/theme/theme.css +1 -1
  161. package/dist/theme.css +1 -1
  162. package/dist/utils/development.d.ts +6 -0
  163. package/dist/utils/development.js +17 -0
  164. package/dist/utils/development.js.map +1 -0
  165. package/dist/utils/form-text-detail.d.ts +15 -0
  166. package/dist/utils/form-text-detail.js +11 -1
  167. package/dist/utils/form-text-detail.js.map +1 -1
  168. package/dist/vite-plugin/types.d.ts +1 -1
  169. package/dist/web-types.json +232 -3
  170. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"field.js","names":[],"sources":["../../../src/composables/defaults/field.ts"],"sourcesContent":["import type { ComputedRef, InjectionKey, MaybeRef, MaybeRefOrGetter } from 'vue';\n\n/**\n * Where a field shows its label.\n *\n * - `top`: a label above a bordered field (the default).\n * - `hidden`: no visible label; the label is still announced to screen readers.\n * For search and filter fields whose placeholder and icon say what they are.\n */\nexport type LabelPlacement = 'top' | 'hidden';\n\nexport interface FieldOptions {\n labelPlacement: MaybeRef<LabelPlacement>;\n}\n\n/** The app-wide default, set through `createRui({ defaults: { field } })`. */\nexport const FieldSymbol: InjectionKey<FieldOptions> = Symbol.for('rui:field');\n\n/** A default for every field below a component, set with `provideFieldDefaults` or `RuiFieldDefaults`. */\nconst FieldScopeSymbol: InjectionKey<MaybeRefOrGetter<LabelPlacement | undefined>> = Symbol.for('rui:field-scope');\n\nexport function createFieldDefaults(options?: Partial<FieldOptions>): FieldOptions {\n return {\n labelPlacement: 'top',\n ...options,\n };\n}\n\n/** Sets the label placement for every field rendered below the calling component. */\nexport function provideFieldDefaults(labelPlacement: MaybeRefOrGetter<LabelPlacement | undefined>): void {\n provide(FieldScopeSymbol, labelPlacement);\n}\n\n/**\n * The placement a field uses: its own prop, then the nearest scope, then the\n * app default, then `top`.\n */\nexport function useLabelPlacement(placement: MaybeRefOrGetter<LabelPlacement | undefined>): ComputedRef<LabelPlacement> {\n const scope = inject(FieldScopeSymbol, undefined);\n const app = inject(FieldSymbol, undefined);\n\n return computed<LabelPlacement>(() =>\n toValue(placement)\n ?? toValue(scope)\n ?? (app ? toValue(app.labelPlacement) : undefined)\n ?? 'top',\n );\n}\n"],"mappings":";;;AAgBA,IAAa,cAA0C,OAAO,IAAI,WAAW;;AAG7E,IAAM,mBAA+E,OAAO,IAAI,iBAAiB;AAEjH,SAAgB,oBAAoB,SAA+C;CACjF,OAAO;EACL,gBAAgB;EAChB,GAAG;CACL;AACF;;AAGA,SAAgB,qBAAqB,gBAAoE;CACvG,QAAQ,kBAAkB,cAAc;AAC1C;;;;;AAMA,SAAgB,kBAAkB,WAAsF;CACtH,MAAM,QAAQ,OAAO,kBAAkB,KAAA,CAAS;CAChD,MAAM,MAAM,OAAO,aAAa,KAAA,CAAS;CAEzC,OAAO,eACL,QAAQ,SAAS,KACd,QAAQ,KAAK,MACZ,MAAM,QAAQ,IAAI,cAAc,IAAI,KAAA,MACrC,KACL;AACF"}
1
+ {"version":3,"file":"field.js","names":[],"sources":["../../../src/composables/defaults/field.ts"],"sourcesContent":["import type { FormDetails } from '@/utils/form-text-detail';\nimport { type ComputedRef, hasInjectionContext, type InjectionKey, type MaybeRef, type MaybeRefOrGetter } from 'vue';\n\n/**\n * Where a field shows its label.\n *\n * - `top`: a label above a bordered field (the default).\n * - `hidden`: no visible label; the label is still announced to screen readers.\n * For search and filter fields whose placeholder and icon say what they are.\n */\nexport type LabelPlacement = 'top' | 'hidden';\n\nexport interface FieldOptions {\n labelPlacement: MaybeRef<LabelPlacement>;\n details: MaybeRef<FormDetails>;\n}\n\n/** What a `provideFieldDefaults` or `RuiFieldDefaults` scope sets; an unset key falls through to the scope above. */\nexport interface FieldScopeOptions {\n labelPlacement?: MaybeRefOrGetter<LabelPlacement | undefined>;\n details?: MaybeRefOrGetter<FormDetails | undefined>;\n}\n\ninterface FieldScope {\n labelPlacement: () => LabelPlacement | undefined;\n details: () => FormDetails | undefined;\n}\n\n/** The app-wide default, set through `createRui({ defaults: { field } })`. */\nexport const FieldSymbol: InjectionKey<FieldOptions> = Symbol.for('rui:field');\n\n/** A default for every field below a component, set with `provideFieldDefaults` or `RuiFieldDefaults`. */\nconst FieldScopeSymbol: InjectionKey<FieldScope> = Symbol.for('rui:field-scope');\n\nexport function createFieldDefaults(options?: Partial<FieldOptions>): FieldOptions {\n return {\n labelPlacement: 'top',\n details: 'reserve',\n ...options,\n };\n}\n\nfunction isScopeOptions(\n options: FieldScopeOptions | MaybeRefOrGetter<LabelPlacement | undefined>,\n): options is FieldScopeOptions {\n return typeof options === 'object' && !isRef(options);\n}\n\n/**\n * Sets defaults for every field rendered below the calling component. A key left unset\n * keeps what an enclosing scope set. Passing a placement alone is the 3.0 form.\n */\nexport function provideFieldDefaults(options: FieldScopeOptions | MaybeRefOrGetter<LabelPlacement | undefined>): void {\n const { labelPlacement, details } = isScopeOptions(options) ? options : { labelPlacement: options, details: undefined };\n const parent = inject(FieldScopeSymbol, undefined);\n\n provide(FieldScopeSymbol, {\n labelPlacement: () => toValue(labelPlacement) ?? parent?.labelPlacement(),\n details: () => toValue(details) ?? parent?.details(),\n });\n}\n\n/**\n * The placement a field uses: its own prop, then the nearest scope, then the\n * app default, then `top`.\n */\nexport function useLabelPlacement(placement: MaybeRefOrGetter<LabelPlacement | undefined>): ComputedRef<LabelPlacement> {\n const scope = inject(FieldScopeSymbol, undefined);\n const app = inject(FieldSymbol, undefined);\n\n return computed<LabelPlacement>(() =>\n toValue(placement)\n ?? scope?.labelPlacement()\n ?? (app ? toValue(app.labelPlacement) : undefined)\n ?? 'top',\n );\n}\n\n/**\n * The details mode the scope and app give a control that sets none itself: the nearest\n * scope, then the app default. `undefined` when neither sets one. Outside a component\n * setup there is nothing to inject, so it is always `undefined` there.\n */\nexport function useInheritedDetails(): () => FormDetails | undefined {\n if (!hasInjectionContext())\n return () => undefined;\n\n const scope = inject(FieldScopeSymbol, undefined);\n const app = inject(FieldSymbol, undefined);\n\n return () => scope?.details() ?? (app ? toValue(app.details) : undefined);\n}\n"],"mappings":";;;AA6BA,IAAa,cAA0C,OAAO,IAAI,WAAW;;AAG7E,IAAM,mBAA6C,OAAO,IAAI,iBAAiB;AAE/E,SAAgB,oBAAoB,SAA+C;CACjF,OAAO;EACL,gBAAgB;EAChB,SAAS;EACT,GAAG;CACL;AACF;AAEA,SAAS,eACP,SAC8B;CAC9B,OAAO,OAAO,YAAY,YAAY,CAAC,MAAM,OAAO;AACtD;;;;;AAMA,SAAgB,qBAAqB,SAAiF;CACpH,MAAM,EAAE,gBAAgB,YAAY,eAAe,OAAO,IAAI,UAAU;EAAE,gBAAgB;EAAS,SAAS,KAAA;CAAU;CACtH,MAAM,SAAS,OAAO,kBAAkB,KAAA,CAAS;CAEjD,QAAQ,kBAAkB;EACxB,sBAAsB,QAAQ,cAAc,KAAK,QAAQ,eAAe;EACxE,eAAe,QAAQ,OAAO,KAAK,QAAQ,QAAQ;CACrD,CAAC;AACH;;;;;AAMA,SAAgB,kBAAkB,WAAsF;CACtH,MAAM,QAAQ,OAAO,kBAAkB,KAAA,CAAS;CAChD,MAAM,MAAM,OAAO,aAAa,KAAA,CAAS;CAEzC,OAAO,eACL,QAAQ,SAAS,KACd,OAAO,eAAe,MACrB,MAAM,QAAQ,IAAI,cAAc,IAAI,KAAA,MACrC,KACL;AACF;;;;;;AAOA,SAAgB,sBAAqD;CACnE,IAAI,CAAC,oBAAoB,GACvB,aAAa,KAAA;CAEf,MAAM,QAAQ,OAAO,kBAAkB,KAAA,CAAS;CAChD,MAAM,MAAM,OAAO,aAAa,KAAA,CAAS;CAEzC,aAAa,OAAO,QAAQ,MAAM,MAAM,QAAQ,IAAI,OAAO,IAAI,KAAA;AACjE"}
@@ -1 +1 @@
1
- {"version":3,"file":"keyboard-navigation.js","names":[],"sources":["../../../../src/composables/forms/auto-complete/keyboard-navigation.ts"],"sourcesContent":["import type { MaybeRefOrGetter, Ref, TemplateRef } from 'vue';\nimport { get, set } from '@vueuse/shared';\n\nexport interface UseAutoCompleteKeyboardNavigationOptions {\n /** Whether multiple items can be selected. */\n multiple: MaybeRefOrGetter<boolean>;\n /** Whether custom (free-text) values are allowed. */\n customValue: MaybeRefOrGetter<boolean>;\n /** Whether selected values are displayed as chips. */\n chips: MaybeRefOrGetter<boolean>;\n}\n\nexport interface UseAutoCompleteKeyboardNavigationDeps<TItem> {\n internalSearch: Ref<string>;\n value: Ref<TItem[]>;\n filteredOptions: Ref<TItem[]>;\n isOpen: Ref<boolean>;\n highlightedIndex: Ref<number>;\n userNavigated: Ref<boolean>;\n searchInputFocused: Ref<boolean>;\n applyHighlighted: () => void;\n clear: () => void;\n removeValue: (item: TItem) => void;\n setSearchAsValue: () => void;\n getText: (item: TItem) => string | undefined;\n updateInternalSearch: (value?: string) => void;\n activator: Ref<HTMLElement | undefined> | TemplateRef<HTMLElement>;\n}\n\nexport interface UseAutoCompleteKeyboardNavigationReturn {\n /**\n * The selected value (a chip) that Backspace or Delete would remove, -1 for none. It is only\n * drawn as selected: focus stays in the input, where no browser shortcut takes the key.\n */\n focusedValueIndex: Ref<number>;\n moveSelectedValueHighlight: (event: KeyboardEvent, next: boolean) => void;\n onEnter: (event: KeyboardEvent) => void;\n onTab: (event: KeyboardEvent) => void;\n onInputDeletePressed: (event: KeyboardEvent) => void;\n /** Drops the value selection on any key that does not act on it. */\n onInputKeydown: (event: KeyboardEvent) => void;\n setValueFocus: (index: number) => void;\n}\n\nexport function useAutoCompleteKeyboardNavigation<TItem>(\n options: UseAutoCompleteKeyboardNavigationOptions,\n deps: UseAutoCompleteKeyboardNavigationDeps<TItem>,\n): UseAutoCompleteKeyboardNavigationReturn {\n const focusedValueIndex = ref<number>(-1);\n\n function setValueFocus(index: number): void {\n set(focusedValueIndex, index);\n }\n\n function moveSelectedValueHighlight(event: KeyboardEvent, next: boolean): void {\n const multiple = toValue(options.multiple);\n const internalSearchValue = get(deps.internalSearch);\n\n if (!multiple || internalSearchValue.length > 0)\n return;\n\n event.preventDefault();\n const total = get(deps.value).length;\n\n let current = get(focusedValueIndex);\n\n if (current === -1) {\n set(focusedValueIndex, next ? 0 : total - 1);\n return;\n }\n\n const move = next ? 1 : -1;\n current += move;\n\n if (current < 0 || current >= total) {\n set(focusedValueIndex, -1);\n set(deps.searchInputFocused, true);\n }\n else {\n set(focusedValueIndex, current);\n }\n }\n\n function highlightedMatchesSearch(filteredOptions: TItem[], highlightedIndex: number, search: string): boolean {\n if (highlightedIndex < 0 || highlightedIndex >= filteredOptions.length)\n return false;\n\n const option = filteredOptions[highlightedIndex];\n if (typeof option === 'string')\n return option === search;\n\n if (option && typeof option === 'object')\n return (deps.getText(option) ?? '') === search;\n\n return false;\n }\n\n function applyCustomValue(event: KeyboardEvent, multiple: boolean): void {\n deps.setSearchAsValue();\n if (!multiple)\n set(deps.isOpen, false);\n event.preventDefault();\n }\n\n function submitClosestForm(): void {\n const activator = get(deps.activator);\n const form = activator?.closest('form');\n form?.dispatchEvent(new Event('submit'));\n }\n\n function tryApplyHighlight(event: KeyboardEvent): boolean {\n const filteredOptions = get(deps.filteredOptions);\n const highlightedIndex = get(deps.highlightedIndex);\n if (highlightedIndex <= -1 || filteredOptions.length === 0)\n return false;\n\n deps.applyHighlighted();\n event.preventDefault();\n return true;\n }\n\n function tryApplyCustom(event: KeyboardEvent, requireEmptyOptions: boolean): boolean {\n const customValue = toValue(options.customValue);\n const internalSearch = get(deps.internalSearch);\n if (!customValue || !internalSearch)\n return false;\n\n const filteredOptions = get(deps.filteredOptions);\n if (requireEmptyOptions) {\n if (filteredOptions.length > 0)\n return false;\n }\n else {\n const highlightedIndex = get(deps.highlightedIndex);\n if (highlightedMatchesSearch(filteredOptions, highlightedIndex, internalSearch))\n return false;\n }\n\n applyCustomValue(event, toValue(options.multiple));\n return true;\n }\n\n /**\n * Enter with the menu open, which takes the option the user navigated to\n * before anything the component highlighted on its own.\n */\n function handleOpenMenuEnter(event: KeyboardEvent): boolean {\n if (get(deps.userNavigated) && tryApplyHighlight(event))\n return true;\n\n // Fall back to a typed custom value when it differs from the highlight.\n if (tryApplyCustom(event, false))\n return true;\n\n // Use the (auto-)highlighted option if there is one.\n if (tryApplyHighlight(event))\n return true;\n\n // No options matched but custom values are allowed — accept the typed text.\n return tryApplyCustom(event, true);\n }\n\n function onEnter(event: KeyboardEvent): void {\n if (get(deps.isOpen)) {\n if (handleOpenMenuEnter(event))\n return;\n\n // Open with nothing to act on, so Enter is swallowed rather than submitting a surrounding form\n event.preventDefault();\n return;\n }\n\n // Nothing selected, menu closed: open menu\n if (get(deps.value).length === 0) {\n set(deps.isOpen, true);\n return event.preventDefault();\n }\n\n // Menu closed with a value selected: let Enter submit the parent form.\n submitClosestForm();\n }\n\n function onTab(event: KeyboardEvent): void {\n const isOpen = get(deps.isOpen);\n const filteredOptions = get(deps.filteredOptions);\n const highlightedIndex = get(deps.highlightedIndex);\n const multiple = toValue(options.multiple);\n\n if (isOpen && filteredOptions.length > 0 && highlightedIndex > -1 && !multiple) {\n deps.applyHighlighted();\n event.preventDefault();\n }\n }\n\n /**\n * Removes the selected value. Alt with it, where custom values are accepted, puts the value's\n * text back in the search instead of dropping it.\n *\n * @param event - the Backspace or Delete press\n * @param item - the value the highlighted chip stands for\n */\n function removeSelectedValue(event: KeyboardEvent, item: TItem): void {\n event.preventDefault();\n set(focusedValueIndex, -1);\n deps.removeValue(item);\n if (event.altKey && toValue(options.customValue))\n deps.updateInternalSearch(deps.getText(item) ?? '');\n }\n\n /** Backspace or Delete with nothing typed: steps onto the last value, or drops it outright. */\n function deleteFromEmptySearch(value: TItem[]): void {\n if (!toValue(options.multiple)) {\n deps.clear();\n return;\n }\n if (toValue(options.chips)) {\n set(focusedValueIndex, value.length - 1);\n return;\n }\n const lastItem = value.at(-1);\n if (lastItem !== undefined)\n deps.removeValue(lastItem);\n }\n\n function onInputDeletePressed(event: KeyboardEvent): void {\n const value = get(deps.value);\n const selected = value[get(focusedValueIndex)];\n if (selected !== undefined) {\n removeSelectedValue(event, selected);\n return;\n }\n\n if (!get(deps.internalSearch) && value.length > 0)\n deleteFromEmptySearch(value);\n }\n\n function onInputKeydown(event: KeyboardEvent): void {\n if (get(focusedValueIndex) === -1)\n return;\n // a modifier pressed on its own (the Alt of Alt + Backspace) keeps the selection for the key it modifies\n const keepsSelection = ['Backspace', 'Delete', 'ArrowLeft', 'ArrowRight', 'Alt', 'AltGraph', 'Control', 'Meta', 'Shift'].includes(event.key);\n if (!keepsSelection)\n set(focusedValueIndex, -1);\n }\n\n // Watch value changes to reset focused index\n watch(deps.value, () => {\n setValueFocus(-1);\n });\n\n return {\n // eslint-disable-next-line @rotki/composable-return-readonly -- written by focus.ts onInputFocused\n focusedValueIndex,\n moveSelectedValueHighlight,\n onEnter,\n onInputDeletePressed,\n onInputKeydown,\n onTab,\n setValueFocus,\n };\n}\n"],"mappings":";;;AA4CA,SAAgB,kCACd,SACA,MACyC;CACzC,MAAM,oBAAoB,IAAY,EAAE;CAExC,SAAS,cAAc,OAAqB;EAC1C,IAAI,mBAAmB,KAAK;CAC9B;CAEA,SAAS,2BAA2B,OAAsB,MAAqB;EAC7E,MAAM,WAAW,QAAQ,QAAQ,QAAQ;EACzC,MAAM,sBAAsB,IAAI,KAAK,cAAc;EAEnD,IAAI,CAAC,YAAY,oBAAoB,SAAS,GAC5C;EAEF,MAAM,eAAe;EACrB,MAAM,QAAQ,IAAI,KAAK,KAAK,CAAC,CAAC;EAE9B,IAAI,UAAU,IAAI,iBAAiB;EAEnC,IAAI,YAAY,IAAI;GAClB,IAAI,mBAAmB,OAAO,IAAI,QAAQ,CAAC;GAC3C;EACF;EAGA,WADa,OAAO,IAAI;EAGxB,IAAI,UAAU,KAAK,WAAW,OAAO;GACnC,IAAI,mBAAmB,EAAE;GACzB,IAAI,KAAK,oBAAoB,IAAI;EACnC,OAEE,IAAI,mBAAmB,OAAO;CAElC;CAEA,SAAS,yBAAyB,iBAA0B,kBAA0B,QAAyB;EAC7G,IAAI,mBAAmB,KAAK,oBAAoB,gBAAgB,QAC9D,OAAO;EAET,MAAM,SAAS,gBAAgB;EAC/B,IAAI,OAAO,WAAW,UACpB,OAAO,WAAW;EAEpB,IAAI,UAAU,OAAO,WAAW,UAC9B,QAAQ,KAAK,QAAQ,MAAM,KAAK,QAAQ;EAE1C,OAAO;CACT;CAEA,SAAS,iBAAiB,OAAsB,UAAyB;EACvE,KAAK,iBAAiB;EACtB,IAAI,CAAC,UACH,IAAI,KAAK,QAAQ,KAAK;EACxB,MAAM,eAAe;CACvB;CAEA,SAAS,oBAA0B;EAGjC,CAFkB,IAAI,KAAK,SACd,CAAA,EAAW,QAAQ,MAAM,EAAA,EAChC,cAAc,IAAI,MAAM,QAAQ,CAAC;CACzC;CAEA,SAAS,kBAAkB,OAA+B;EACxD,MAAM,kBAAkB,IAAI,KAAK,eAAe;EAEhD,IADyB,IAAI,KAAK,gBAC9B,KAAoB,MAAM,gBAAgB,WAAW,GACvD,OAAO;EAET,KAAK,iBAAiB;EACtB,MAAM,eAAe;EACrB,OAAO;CACT;CAEA,SAAS,eAAe,OAAsB,qBAAuC;EACnF,MAAM,cAAc,QAAQ,QAAQ,WAAW;EAC/C,MAAM,iBAAiB,IAAI,KAAK,cAAc;EAC9C,IAAI,CAAC,eAAe,CAAC,gBACnB,OAAO;EAET,MAAM,kBAAkB,IAAI,KAAK,eAAe;EAChD,IAAI,qBACE;OAAA,gBAAgB,SAAS,GAC3B,OAAO;EAAA,OAIT,IAAI,yBAAyB,iBADJ,IAAI,KAAK,gBACY,GAAkB,cAAc,GAC5E,OAAO;EAGX,iBAAiB,OAAO,QAAQ,QAAQ,QAAQ,CAAC;EACjD,OAAO;CACT;;;;;CAMA,SAAS,oBAAoB,OAA+B;EAC1D,IAAI,IAAI,KAAK,aAAa,KAAK,kBAAkB,KAAK,GACpD,OAAO;EAGT,IAAI,eAAe,OAAO,KAAK,GAC7B,OAAO;EAGT,IAAI,kBAAkB,KAAK,GACzB,OAAO;EAGT,OAAO,eAAe,OAAO,IAAI;CACnC;CAEA,SAAS,QAAQ,OAA4B;EAC3C,IAAI,IAAI,KAAK,MAAM,GAAG;GACpB,IAAI,oBAAoB,KAAK,GAC3B;GAGF,MAAM,eAAe;GACrB;EACF;EAGA,IAAI,IAAI,KAAK,KAAK,CAAC,CAAC,WAAW,GAAG;GAChC,IAAI,KAAK,QAAQ,IAAI;GACrB,OAAO,MAAM,eAAe;EAC9B;EAGA,kBAAkB;CACpB;CAEA,SAAS,MAAM,OAA4B;EACzC,MAAM,SAAS,IAAI,KAAK,MAAM;EAC9B,MAAM,kBAAkB,IAAI,KAAK,eAAe;EAChD,MAAM,mBAAmB,IAAI,KAAK,gBAAgB;EAClD,MAAM,WAAW,QAAQ,QAAQ,QAAQ;EAEzC,IAAI,UAAU,gBAAgB,SAAS,KAAK,mBAAmB,MAAM,CAAC,UAAU;GAC9E,KAAK,iBAAiB;GACtB,MAAM,eAAe;EACvB;CACF;;;;;;;;CASA,SAAS,oBAAoB,OAAsB,MAAmB;EACpE,MAAM,eAAe;EACrB,IAAI,mBAAmB,EAAE;EACzB,KAAK,YAAY,IAAI;EACrB,IAAI,MAAM,UAAU,QAAQ,QAAQ,WAAW,GAC7C,KAAK,qBAAqB,KAAK,QAAQ,IAAI,KAAK,EAAE;CACtD;;CAGA,SAAS,sBAAsB,OAAsB;EACnD,IAAI,CAAC,QAAQ,QAAQ,QAAQ,GAAG;GAC9B,KAAK,MAAM;GACX;EACF;EACA,IAAI,QAAQ,QAAQ,KAAK,GAAG;GAC1B,IAAI,mBAAmB,MAAM,SAAS,CAAC;GACvC;EACF;EACA,MAAM,WAAW,MAAM,GAAG,EAAE;EAC5B,IAAI,aAAa,KAAA,GACf,KAAK,YAAY,QAAQ;CAC7B;CAEA,SAAS,qBAAqB,OAA4B;EACxD,MAAM,QAAQ,IAAI,KAAK,KAAK;EAC5B,MAAM,WAAW,MAAM,IAAI,iBAAiB;EAC5C,IAAI,aAAa,KAAA,GAAW;GAC1B,oBAAoB,OAAO,QAAQ;GACnC;EACF;EAEA,IAAI,CAAC,IAAI,KAAK,cAAc,KAAK,MAAM,SAAS,GAC9C,sBAAsB,KAAK;CAC/B;CAEA,SAAS,eAAe,OAA4B;EAClD,IAAI,IAAI,iBAAiB,MAAM,IAC7B;EAGF,IAAI,CADmB;GAAC;GAAa;GAAU;GAAa;GAAc;GAAO;GAAY;GAAW;GAAQ;EAAO,CAAC,CAAC,SAAS,MAAM,GACnI,GACH,IAAI,mBAAmB,EAAE;CAC7B;CAGA,MAAM,KAAK,aAAa;EACtB,cAAc,EAAE;CAClB,CAAC;CAED,OAAO;EAEL;EACA;EACA;EACA;EACA;EACA;EACA;CACF;AACF"}
1
+ {"version":3,"file":"keyboard-navigation.js","names":[],"sources":["../../../../src/composables/forms/auto-complete/keyboard-navigation.ts"],"sourcesContent":["import type { MaybeRefOrGetter, Ref, TemplateRef } from 'vue';\nimport { get, set } from '@vueuse/shared';\n\nexport interface UseAutoCompleteKeyboardNavigationOptions {\n /** Whether multiple items can be selected. */\n multiple: MaybeRefOrGetter<boolean>;\n /** Whether custom (free-text) values are allowed. */\n customValue: MaybeRefOrGetter<boolean>;\n /** Whether selected values are displayed as chips. */\n chips: MaybeRefOrGetter<boolean>;\n}\n\nexport interface UseAutoCompleteKeyboardNavigationDeps<TItem> {\n internalSearch: Ref<string>;\n value: Ref<TItem[]>;\n filteredOptions: Ref<TItem[]>;\n isOpen: Ref<boolean>;\n highlightedIndex: Ref<number>;\n userNavigated: Ref<boolean>;\n searchInputFocused: Ref<boolean>;\n applyHighlighted: () => void;\n clear: () => void;\n removeValue: (item: TItem) => void;\n setSearchAsValue: () => void;\n getText: (item: TItem) => string | undefined;\n updateInternalSearch: (value?: string) => void;\n activator: Ref<HTMLElement | undefined> | TemplateRef<HTMLElement>;\n}\n\nexport interface UseAutoCompleteKeyboardNavigationReturn {\n /**\n * The selected value (a chip) that Backspace or Delete would remove, -1 for none. It is only\n * drawn as selected: focus stays in the input, where no browser shortcut takes the key.\n */\n focusedValueIndex: Ref<number>;\n moveSelectedValueHighlight: (event: KeyboardEvent, next: boolean) => void;\n onEnter: (event: KeyboardEvent) => void;\n onTab: (event: KeyboardEvent) => void;\n onInputDeletePressed: (event: KeyboardEvent) => void;\n /** Drops the value selection on any key that does not act on it. */\n onInputKeydown: (event: KeyboardEvent) => void;\n setValueFocus: (index: number) => void;\n}\n\nexport function useAutoCompleteKeyboardNavigation<TItem>(\n options: UseAutoCompleteKeyboardNavigationOptions,\n deps: UseAutoCompleteKeyboardNavigationDeps<TItem>,\n): UseAutoCompleteKeyboardNavigationReturn {\n const focusedValueIndex = ref<number>(-1);\n\n function setValueFocus(index: number): void {\n set(focusedValueIndex, index);\n }\n\n function moveSelectedValueHighlight(event: KeyboardEvent, next: boolean): void {\n const multiple = toValue(options.multiple);\n const internalSearchValue = get(deps.internalSearch);\n\n if (!multiple || internalSearchValue.length > 0)\n return;\n\n event.preventDefault();\n const total = get(deps.value).length;\n\n let current = get(focusedValueIndex);\n\n if (current === -1) {\n set(focusedValueIndex, next ? 0 : total - 1);\n return;\n }\n\n const move = next ? 1 : -1;\n current += move;\n\n if (current < 0 || current >= total) {\n set(focusedValueIndex, -1);\n set(deps.searchInputFocused, true);\n }\n else {\n set(focusedValueIndex, current);\n }\n }\n\n function highlightedMatchesSearch(filteredOptions: TItem[], highlightedIndex: number, search: string): boolean {\n if (highlightedIndex < 0 || highlightedIndex >= filteredOptions.length)\n return false;\n\n const option = filteredOptions[highlightedIndex];\n if (typeof option === 'string')\n return option === search;\n\n if (option && typeof option === 'object')\n return (deps.getText(option) ?? '') === search;\n\n return false;\n }\n\n function applyCustomValue(event: KeyboardEvent, multiple: boolean): void {\n deps.setSearchAsValue();\n if (!multiple)\n set(deps.isOpen, false);\n event.preventDefault();\n }\n\n function submitClosestForm(): void {\n const activator = get(deps.activator);\n const form = activator?.closest('form');\n form?.dispatchEvent(new Event('submit'));\n }\n\n function tryApplyHighlight(event: KeyboardEvent): boolean {\n const filteredOptions = get(deps.filteredOptions);\n const highlightedIndex = get(deps.highlightedIndex);\n if (highlightedIndex <= -1 || filteredOptions.length === 0)\n return false;\n\n deps.applyHighlighted();\n event.preventDefault();\n return true;\n }\n\n function tryApplyCustom(event: KeyboardEvent, requireEmptyOptions: boolean): boolean {\n const customValue = toValue(options.customValue);\n const internalSearch = get(deps.internalSearch);\n if (!customValue || !internalSearch)\n return false;\n\n const filteredOptions = get(deps.filteredOptions);\n if (requireEmptyOptions) {\n if (filteredOptions.length > 0)\n return false;\n }\n else {\n const highlightedIndex = get(deps.highlightedIndex);\n if (highlightedMatchesSearch(filteredOptions, highlightedIndex, internalSearch))\n return false;\n }\n\n applyCustomValue(event, toValue(options.multiple));\n return true;\n }\n\n /**\n * Enter with the menu open, which takes the option the user navigated to\n * before anything the component highlighted on its own.\n */\n function handleOpenMenuEnter(event: KeyboardEvent): boolean {\n if (get(deps.userNavigated) && tryApplyHighlight(event))\n return true;\n\n // Fall back to a typed custom value when it differs from the highlight.\n if (tryApplyCustom(event, false))\n return true;\n\n // Use the (auto-)highlighted option if there is one.\n if (tryApplyHighlight(event))\n return true;\n\n // No options matched but custom values are allowed, so accept the typed text.\n return tryApplyCustom(event, true);\n }\n\n function onEnter(event: KeyboardEvent): void {\n if (get(deps.isOpen)) {\n if (handleOpenMenuEnter(event))\n return;\n\n // Open with nothing to act on, so Enter is swallowed rather than submitting a surrounding form\n event.preventDefault();\n return;\n }\n\n // Nothing selected, menu closed: open menu\n if (get(deps.value).length === 0) {\n set(deps.isOpen, true);\n return event.preventDefault();\n }\n\n // Menu closed with a value selected: let Enter submit the parent form.\n submitClosestForm();\n }\n\n function onTab(event: KeyboardEvent): void {\n const isOpen = get(deps.isOpen);\n const filteredOptions = get(deps.filteredOptions);\n const highlightedIndex = get(deps.highlightedIndex);\n const multiple = toValue(options.multiple);\n\n if (isOpen && filteredOptions.length > 0 && highlightedIndex > -1 && !multiple) {\n deps.applyHighlighted();\n event.preventDefault();\n }\n }\n\n /**\n * Removes the selected value. Alt with it, where custom values are accepted, puts the value's\n * text back in the search instead of dropping it.\n *\n * @param event - the Backspace or Delete press\n * @param item - the value the highlighted chip stands for\n */\n function removeSelectedValue(event: KeyboardEvent, item: TItem): void {\n event.preventDefault();\n set(focusedValueIndex, -1);\n deps.removeValue(item);\n if (event.altKey && toValue(options.customValue))\n deps.updateInternalSearch(deps.getText(item) ?? '');\n }\n\n /** Backspace or Delete with nothing typed: steps onto the last value, or drops it outright. */\n function deleteFromEmptySearch(value: TItem[]): void {\n if (!toValue(options.multiple)) {\n deps.clear();\n return;\n }\n if (toValue(options.chips)) {\n set(focusedValueIndex, value.length - 1);\n return;\n }\n const lastItem = value.at(-1);\n if (lastItem !== undefined)\n deps.removeValue(lastItem);\n }\n\n function onInputDeletePressed(event: KeyboardEvent): void {\n const value = get(deps.value);\n const selected = value[get(focusedValueIndex)];\n if (selected !== undefined) {\n removeSelectedValue(event, selected);\n return;\n }\n\n if (!get(deps.internalSearch) && value.length > 0)\n deleteFromEmptySearch(value);\n }\n\n function onInputKeydown(event: KeyboardEvent): void {\n if (get(focusedValueIndex) === -1)\n return;\n // a modifier pressed on its own (the Alt of Alt + Backspace) keeps the selection for the key it modifies\n const keepsSelection = ['Backspace', 'Delete', 'ArrowLeft', 'ArrowRight', 'Alt', 'AltGraph', 'Control', 'Meta', 'Shift'].includes(event.key);\n if (!keepsSelection)\n set(focusedValueIndex, -1);\n }\n\n // Watch value changes to reset focused index\n watch(deps.value, () => {\n setValueFocus(-1);\n });\n\n return {\n // eslint-disable-next-line @rotki/composable-return-readonly -- written by focus.ts onInputFocused\n focusedValueIndex,\n moveSelectedValueHighlight,\n onEnter,\n onInputDeletePressed,\n onInputKeydown,\n onTab,\n setValueFocus,\n };\n}\n"],"mappings":"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"}
@@ -0,0 +1,20 @@
1
+ import type { MaybeRefOrGetter, Ref } from 'vue';
2
+ /**
3
+ * The nearest ancestor with `overflow: hidden` on either axis, which a `position: sticky` element
4
+ * sticks to instead of the page. `hidden` never scrolls, so the element can never stick. `auto` and
5
+ * `scroll` are left out: those can be real scrolling areas, like a dialog body. `clip` makes no
6
+ * scroll container, so it never blocks. A box marked `data-clipped` clips only while it animates.
7
+ *
8
+ * @param element - where the walk up the tree starts; its own overflow does not count
9
+ * @returns the blocking ancestor, or undefined when nothing blocks the element
10
+ */
11
+ export declare function findStickyBlocker(element: HTMLElement): HTMLElement | undefined;
12
+ /**
13
+ * In a development build, warns once when an ancestor keeps a sticky element from sticking to the
14
+ * page. Silent in production, where the check does not run.
15
+ *
16
+ * @param element - the `position: sticky` box, checked once it is mounted
17
+ * @param enabled - whether the element is meant to stick
18
+ * @param name - what the element is, for the message
19
+ */
20
+ export declare function useStickyBlockerWarning(element: Readonly<Ref<HTMLElement | null | undefined>>, enabled: MaybeRefOrGetter<boolean>, name: string): void;
@@ -0,0 +1,62 @@
1
+ import { get, useTimeoutFn } from "../vendor/@vueuse/shared/dist/index.js";
2
+ import { isDevelopment } from "../utils/development.js";
3
+ import { toValue, watch } from "vue";
4
+ //#region src/composables/sticky-blocker.ts
5
+ /** Long enough for an opening accordion or tab panel to settle before the check reads its overflow. */
6
+ var CHECK_DELAY_MS = 1e3;
7
+ /**
8
+ * The nearest ancestor with `overflow: hidden` on either axis, which a `position: sticky` element
9
+ * sticks to instead of the page. `hidden` never scrolls, so the element can never stick. `auto` and
10
+ * `scroll` are left out: those can be real scrolling areas, like a dialog body. `clip` makes no
11
+ * scroll container, so it never blocks. A box marked `data-clipped` clips only while it animates.
12
+ *
13
+ * @param element - where the walk up the tree starts; its own overflow does not count
14
+ * @returns the blocking ancestor, or undefined when nothing blocks the element
15
+ */
16
+ function findStickyBlocker(element) {
17
+ let parent = element.parentElement;
18
+ while (parent && parent !== document.body && parent !== document.documentElement) {
19
+ const { overflowX, overflowY } = getComputedStyle(parent);
20
+ if ((overflowX === "hidden" || overflowY === "hidden") && !parent.hasAttribute("data-clipped")) return parent;
21
+ parent = parent.parentElement;
22
+ }
23
+ }
24
+ /**
25
+ * A short, recognizable name for an element in a console message.
26
+ *
27
+ * @param element - the element to name
28
+ * @returns its tag, `data-id` and first classes
29
+ */
30
+ function describe(element) {
31
+ const id = element.dataset.id ? `[data-id="${element.dataset.id}"]` : "";
32
+ const classes = [...element.classList].slice(0, 3).map((name) => `.${name}`).join("");
33
+ return `<${element.tagName.toLowerCase()}${id}${classes}>`;
34
+ }
35
+ /**
36
+ * In a development build, warns once when an ancestor keeps a sticky element from sticking to the
37
+ * page. Silent in production, where the check does not run.
38
+ *
39
+ * @param element - the `position: sticky` box, checked once it is mounted
40
+ * @param enabled - whether the element is meant to stick
41
+ * @param name - what the element is, for the message
42
+ */
43
+ function useStickyBlockerWarning(element, enabled, name) {
44
+ if (!isDevelopment()) return;
45
+ let warned = false;
46
+ function check() {
47
+ const target = get(element);
48
+ if (warned || !toValue(enabled) || !target) return;
49
+ const blocker = findStickyBlocker(target);
50
+ if (!blocker) return;
51
+ warned = true;
52
+ console.warn(`[@rotki/ui-library] ${name} cannot stick to the page: the ancestor ${describe(blocker)} has overflow: hidden, so the bar sticks to it instead. Use overflow: clip there (it clips the same without creating a scroll container), or move the element out of it.`, blocker);
53
+ }
54
+ const { start } = useTimeoutFn(check, CHECK_DELAY_MS, { immediate: false });
55
+ watch([element, () => toValue(enabled)], ([target, on]) => {
56
+ if (target && on) start();
57
+ }, { immediate: true });
58
+ }
59
+ //#endregion
60
+ export { findStickyBlocker, useStickyBlockerWarning };
61
+
62
+ //# sourceMappingURL=sticky-blocker.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sticky-blocker.js","names":[],"sources":["../../src/composables/sticky-blocker.ts"],"sourcesContent":["import type { MaybeRefOrGetter, Ref } from 'vue';\nimport { useTimeoutFn } from '@vueuse/core';\nimport { isDevelopment } from '@/utils/development';\n\n/** Long enough for an opening accordion or tab panel to settle before the check reads its overflow. */\nconst CHECK_DELAY_MS = 1000;\n\n/**\n * The nearest ancestor with `overflow: hidden` on either axis, which a `position: sticky` element\n * sticks to instead of the page. `hidden` never scrolls, so the element can never stick. `auto` and\n * `scroll` are left out: those can be real scrolling areas, like a dialog body. `clip` makes no\n * scroll container, so it never blocks. A box marked `data-clipped` clips only while it animates.\n *\n * @param element - where the walk up the tree starts; its own overflow does not count\n * @returns the blocking ancestor, or undefined when nothing blocks the element\n */\nexport function findStickyBlocker(element: HTMLElement): HTMLElement | undefined {\n let parent = element.parentElement;\n while (parent && parent !== document.body && parent !== document.documentElement) {\n const { overflowX, overflowY } = getComputedStyle(parent);\n if ((overflowX === 'hidden' || overflowY === 'hidden') && !parent.hasAttribute('data-clipped'))\n return parent;\n parent = parent.parentElement;\n }\n return undefined;\n}\n\n/**\n * A short, recognizable name for an element in a console message.\n *\n * @param element - the element to name\n * @returns its tag, `data-id` and first classes\n */\nfunction describe(element: HTMLElement): string {\n const id = element.dataset.id ? `[data-id=\"${element.dataset.id}\"]` : '';\n const classes = [...element.classList].slice(0, 3).map(name => `.${name}`).join('');\n return `<${element.tagName.toLowerCase()}${id}${classes}>`;\n}\n\n/**\n * In a development build, warns once when an ancestor keeps a sticky element from sticking to the\n * page. Silent in production, where the check does not run.\n *\n * @param element - the `position: sticky` box, checked once it is mounted\n * @param enabled - whether the element is meant to stick\n * @param name - what the element is, for the message\n */\nexport function useStickyBlockerWarning(\n element: Readonly<Ref<HTMLElement | null | undefined>>,\n enabled: MaybeRefOrGetter<boolean>,\n name: string,\n): void {\n if (!isDevelopment())\n return;\n\n let warned = false;\n\n function check(): void {\n const target = get(element);\n if (warned || !toValue(enabled) || !target)\n return;\n const blocker = findStickyBlocker(target);\n if (!blocker)\n return;\n warned = true;\n console.warn(\n `[@rotki/ui-library] ${name} cannot stick to the page: the ancestor ${describe(blocker)} has overflow: hidden, `\n + 'so the bar sticks to it instead. Use overflow: clip there (it clips the same without creating a scroll container), '\n + 'or move the element out of it.',\n blocker,\n );\n }\n\n const { start } = useTimeoutFn(check, CHECK_DELAY_MS, { immediate: false });\n\n watch([element, () => toValue(enabled)], ([target, on]) => {\n if (target && on)\n start();\n }, { immediate: true });\n}\n"],"mappings":";;;;;AAKA,IAAM,iBAAiB;;;;;;;;;;AAWvB,SAAgB,kBAAkB,SAA+C;CAC/E,IAAI,SAAS,QAAQ;CACrB,OAAO,UAAU,WAAW,SAAS,QAAQ,WAAW,SAAS,iBAAiB;EAChF,MAAM,EAAE,WAAW,cAAc,iBAAiB,MAAM;EACxD,KAAK,cAAc,YAAY,cAAc,aAAa,CAAC,OAAO,aAAa,cAAc,GAC3F,OAAO;EACT,SAAS,OAAO;CAClB;AAEF;;;;;;;AAQA,SAAS,SAAS,SAA8B;CAC9C,MAAM,KAAK,QAAQ,QAAQ,KAAK,aAAa,QAAQ,QAAQ,GAAG,MAAM;CACtE,MAAM,UAAU,CAAC,GAAG,QAAQ,SAAS,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,KAAI,SAAQ,IAAI,MAAM,CAAC,CAAC,KAAK,EAAE;CAClF,OAAO,IAAI,QAAQ,QAAQ,YAAY,IAAI,KAAK,QAAQ;AAC1D;;;;;;;;;AAUA,SAAgB,wBACd,SACA,SACA,MACM;CACN,IAAI,CAAC,cAAc,GACjB;CAEF,IAAI,SAAS;CAEb,SAAS,QAAc;EACrB,MAAM,SAAS,IAAI,OAAO;EAC1B,IAAI,UAAU,CAAC,QAAQ,OAAO,KAAK,CAAC,QAClC;EACF,MAAM,UAAU,kBAAkB,MAAM;EACxC,IAAI,CAAC,SACH;EACF,SAAS;EACT,QAAQ,KACN,uBAAuB,KAAK,0CAA0C,SAAS,OAAO,EAAE,2KAGxF,OACF;CACF;CAEA,MAAM,EAAE,UAAU,aAAa,OAAO,gBAAgB,EAAE,WAAW,MAAM,CAAC;CAE1E,MAAM,CAAC,eAAe,QAAQ,OAAO,CAAC,IAAI,CAAC,QAAQ,QAAQ;EACzD,IAAI,UAAU,IACZ,MAAM;CACV,GAAG,EAAE,WAAW,KAAK,CAAC;AACxB"}
@@ -1 +1 @@
1
- {"version":3,"file":"columns.js","names":[],"sources":["../../../../src/composables/tables/data-table/columns.ts"],"sourcesContent":["import type { ComputedRef, MaybeRefOrGetter, Ref } from 'vue';\nimport type {\n NoneSortableTableColumn,\n TableColumn,\n TableRowKey,\n} from '@/components/tables/RuiTableHead.vue';\nimport { getAlignClass, TableAlign } from '@/components/tables/table-props';\nimport { getObjectKeys, isHeaderSlot } from '@/composables/tables/data-table/types';\n\nexport interface UseTableColumnsOptions<T extends object, IdType extends keyof T> {\n /** Column definitions for the table. When omitted, columns are inferred from row data. */\n cols: MaybeRefOrGetter<TableColumn<T>[] | undefined>;\n /** The property on each column definition used as the column identifier. */\n columnAttr: keyof TableColumn<T>;\n /** The data rows to display in the table. */\n rows: MaybeRefOrGetter<readonly T[]>;\n /** Whether row expansion is enabled. */\n expandable: ComputedRef<boolean>;\n /** The keys currently used for grouping rows. */\n groupKeys: ComputedRef<TableRowKey<T>[]>;\n /** The currently selected row identifiers. */\n selectedData: Ref<T[IdType][] | undefined>;\n /** Template slots provided to the table component. */\n slots: Record<string, any>;\n /** Pre-computes td class per column to avoid per-cell tv() calls. */\n tdResolver?: (options: { class?: string }) => string;\n /** Whether the table is currently rendered in its stacked mobile layout. */\n isMobile?: MaybeRefOrGetter<boolean>;\n}\n\nexport interface UseTableColumnsReturn<T extends object> {\n columns: ComputedRef<TableColumn<T>[]>;\n colspan: ComputedRef<number>;\n headerSlots: `header.${string}`[];\n cellValue: (row: T, key: TableColumn<T>['key']) => T[TableRowKey<T>];\n}\n\nexport function useTableColumns<T extends object, IdType extends keyof T>(\n options: UseTableColumnsOptions<T, IdType>,\n): UseTableColumnsReturn<T> {\n const { cols, columnAttr, expandable, groupKeys, isMobile, rows, selectedData, slots, tdResolver } = options;\n\n function attachTdClass(columnList: TableColumn<T>[]): TableColumn<T>[] {\n if (!tdResolver)\n return columnList;\n\n return columnList.map(col => ({\n ...col,\n tdClass: tdResolver({ class: getAlignClass(col.align) }),\n }));\n }\n\n const columns = computed<TableColumn<T>[]>(() => {\n const currentCols = toValue(cols);\n const currentRows = toValue(rows);\n const allColumns =\n currentCols ??\n getObjectKeys(currentRows[0] ?? {}).map(\n key =>\n ({\n key,\n [columnAttr]: String(key),\n }) satisfies NoneSortableTableColumn<T>,\n );\n\n const data = toValue(isMobile)\n ? allColumns.filter(column => !column.mobileHidden)\n : allColumns;\n\n const hasExpandColumn = data.some(row => row.key === 'expand');\n\n if (get(expandable) && !hasExpandColumn) {\n return attachTdClass([\n ...data,\n {\n key: 'expand' as TableRowKey<T>,\n sortable: false,\n class: 'w-16',\n cellClass: '!py-0 w-16',\n align: TableAlign.end,\n } satisfies NoneSortableTableColumn<T>,\n ]);\n }\n\n const groupByKeys = get(groupKeys);\n\n if (groupByKeys.length === 0)\n return attachTdClass(data);\n\n return attachTdClass(data.filter(column => !groupByKeys.includes(column.key as TableRowKey<T>)));\n });\n\n const colspan = computed<number>(() => {\n let columnLength = get(columns).length;\n if (get(selectedData))\n columnLength++;\n\n return columnLength;\n });\n\n // Slot keys are static for the component lifetime — no reactivity needed\n const headerSlots: `header.${string}`[] = Object.keys(slots).filter(isHeaderSlot);\n\n function cellValue(row: T, key: TableColumn<T>['key']): T[TableRowKey<T>] {\n return row[key as TableRowKey<T>];\n }\n\n return {\n columns,\n colspan,\n headerSlots,\n cellValue,\n };\n}\n"],"mappings":";;;;;AAqCA,SAAgB,gBACd,SAC0B;CAC1B,MAAM,EAAE,MAAM,YAAY,YAAY,WAAW,UAAU,MAAM,cAAc,OAAO,eAAe;CAErG,SAAS,cAAc,YAAgD;EACrE,IAAI,CAAC,YACH,OAAO;EAET,OAAO,WAAW,KAAI,SAAQ;GAC5B,GAAG;GACH,SAAS,WAAW,EAAE,OAAO,cAAc,IAAI,KAAK,EAAE,CAAC;EACzD,EAAE;CACJ;CAEA,MAAM,UAAU,eAAiC;EAC/C,MAAM,cAAc,QAAQ,IAAI;EAChC,MAAM,cAAc,QAAQ,IAAI;EAChC,MAAM,aACJ,eACA,cAAc,YAAY,MAAM,CAAC,CAAC,CAAC,CAAC,KAClC,SACG;GACC;IACC,aAAa,OAAO,GAAG;EAC1B,EACJ;EAEF,MAAM,OAAO,QAAQ,QAAQ,IACzB,WAAW,QAAO,WAAU,CAAC,OAAO,YAAY,IAChD;EAEJ,MAAM,kBAAkB,KAAK,MAAK,QAAO,IAAI,QAAQ,QAAQ;EAE7D,IAAI,IAAI,UAAU,KAAK,CAAC,iBACtB,OAAO,cAAc,CACnB,GAAG,MACH;GACE,KAAK;GACL,UAAU;GACV,OAAO;GACP,WAAW;GACX,OAAO,WAAW;EACpB,CACF,CAAC;EAGH,MAAM,cAAc,IAAI,SAAS;EAEjC,IAAI,YAAY,WAAW,GACzB,OAAO,cAAc,IAAI;EAE3B,OAAO,cAAc,KAAK,QAAO,WAAU,CAAC,YAAY,SAAS,OAAO,GAAqB,CAAC,CAAC;CACjG,CAAC;CAED,MAAM,UAAU,eAAuB;EACrC,IAAI,eAAe,IAAI,OAAO,CAAC,CAAC;EAChC,IAAI,IAAI,YAAY,GAClB;EAEF,OAAO;CACT,CAAC;CAGD,MAAM,cAAoC,OAAO,KAAK,KAAK,CAAC,CAAC,OAAO,YAAY;CAEhF,SAAS,UAAU,KAAQ,KAA+C;EACxE,OAAO,IAAI;CACb;CAEA,OAAO;EACL;EACA;EACA;EACA;CACF;AACF"}
1
+ {"version":3,"file":"columns.js","names":[],"sources":["../../../../src/composables/tables/data-table/columns.ts"],"sourcesContent":["import type { ComputedRef, MaybeRefOrGetter, Ref } from 'vue';\nimport type {\n NoneSortableTableColumn,\n TableColumn,\n TableRowKey,\n} from '@/components/tables/RuiTableHead.vue';\nimport { getAlignClass, TableAlign } from '@/components/tables/table-props';\nimport { getObjectKeys, isHeaderSlot } from '@/composables/tables/data-table/types';\n\nexport interface UseTableColumnsOptions<T extends object, IdType extends keyof T> {\n /** Column definitions for the table. When omitted, columns are inferred from row data. */\n cols: MaybeRefOrGetter<TableColumn<T>[] | undefined>;\n /** The property on each column definition used as the column identifier. */\n columnAttr: keyof TableColumn<T>;\n /** The data rows to display in the table. */\n rows: MaybeRefOrGetter<readonly T[]>;\n /** Whether row expansion is enabled. */\n expandable: ComputedRef<boolean>;\n /** The keys currently used for grouping rows. */\n groupKeys: ComputedRef<TableRowKey<T>[]>;\n /** The currently selected row identifiers. */\n selectedData: Ref<T[IdType][] | undefined>;\n /** Template slots provided to the table component. */\n slots: Record<string, any>;\n /** Pre-computes td class per column to avoid per-cell tv() calls. */\n tdResolver?: (options: { class?: string }) => string;\n /** Whether the table is currently rendered in its stacked mobile layout. */\n isMobile?: MaybeRefOrGetter<boolean>;\n}\n\nexport interface UseTableColumnsReturn<T extends object> {\n columns: ComputedRef<TableColumn<T>[]>;\n colspan: ComputedRef<number>;\n headerSlots: `header.${string}`[];\n cellValue: (row: T, key: TableColumn<T>['key']) => T[TableRowKey<T>];\n}\n\nexport function useTableColumns<T extends object, IdType extends keyof T>(\n options: UseTableColumnsOptions<T, IdType>,\n): UseTableColumnsReturn<T> {\n const { cols, columnAttr, expandable, groupKeys, isMobile, rows, selectedData, slots, tdResolver } = options;\n\n function attachTdClass(columnList: TableColumn<T>[]): TableColumn<T>[] {\n if (!tdResolver)\n return columnList;\n\n return columnList.map(col => ({\n ...col,\n tdClass: tdResolver({ class: getAlignClass(col.align) }),\n }));\n }\n\n const columns = computed<TableColumn<T>[]>(() => {\n const currentCols = toValue(cols);\n const currentRows = toValue(rows);\n const allColumns =\n currentCols ??\n getObjectKeys(currentRows[0] ?? {}).map(\n key =>\n ({\n key,\n [columnAttr]: String(key),\n }) satisfies NoneSortableTableColumn<T>,\n );\n\n const data = toValue(isMobile)\n ? allColumns.filter(column => !column.mobileHidden)\n : allColumns;\n\n const hasExpandColumn = data.some(row => row.key === 'expand');\n\n if (get(expandable) && !hasExpandColumn) {\n return attachTdClass([\n ...data,\n {\n key: 'expand' as TableRowKey<T>,\n sortable: false,\n class: 'w-16',\n cellClass: '!py-0 w-16',\n align: TableAlign.end,\n } satisfies NoneSortableTableColumn<T>,\n ]);\n }\n\n const groupByKeys = get(groupKeys);\n\n if (groupByKeys.length === 0)\n return attachTdClass(data);\n\n return attachTdClass(data.filter(column => !groupByKeys.includes(column.key as TableRowKey<T>)));\n });\n\n const colspan = computed<number>(() => {\n let columnLength = get(columns).length;\n if (get(selectedData))\n columnLength++;\n\n return columnLength;\n });\n\n // Slot keys are static for the component lifetime, so no reactivity is needed\n const headerSlots: `header.${string}`[] = Object.keys(slots).filter(isHeaderSlot);\n\n function cellValue(row: T, key: TableColumn<T>['key']): T[TableRowKey<T>] {\n return row[key as TableRowKey<T>];\n }\n\n return {\n columns,\n colspan,\n headerSlots,\n cellValue,\n };\n}\n"],"mappings":";;;;;AAqCA,SAAgB,gBACd,SAC0B;CAC1B,MAAM,EAAE,MAAM,YAAY,YAAY,WAAW,UAAU,MAAM,cAAc,OAAO,eAAe;CAErG,SAAS,cAAc,YAAgD;EACrE,IAAI,CAAC,YACH,OAAO;EAET,OAAO,WAAW,KAAI,SAAQ;GAC5B,GAAG;GACH,SAAS,WAAW,EAAE,OAAO,cAAc,IAAI,KAAK,EAAE,CAAC;EACzD,EAAE;CACJ;CAEA,MAAM,UAAU,eAAiC;EAC/C,MAAM,cAAc,QAAQ,IAAI;EAChC,MAAM,cAAc,QAAQ,IAAI;EAChC,MAAM,aACJ,eACA,cAAc,YAAY,MAAM,CAAC,CAAC,CAAC,CAAC,KAClC,SACG;GACC;IACC,aAAa,OAAO,GAAG;EAC1B,EACJ;EAEF,MAAM,OAAO,QAAQ,QAAQ,IACzB,WAAW,QAAO,WAAU,CAAC,OAAO,YAAY,IAChD;EAEJ,MAAM,kBAAkB,KAAK,MAAK,QAAO,IAAI,QAAQ,QAAQ;EAE7D,IAAI,IAAI,UAAU,KAAK,CAAC,iBACtB,OAAO,cAAc,CACnB,GAAG,MACH;GACE,KAAK;GACL,UAAU;GACV,OAAO;GACP,WAAW;GACX,OAAO,WAAW;EACpB,CACF,CAAC;EAGH,MAAM,cAAc,IAAI,SAAS;EAEjC,IAAI,YAAY,WAAW,GACzB,OAAO,cAAc,IAAI;EAE3B,OAAO,cAAc,KAAK,QAAO,WAAU,CAAC,YAAY,SAAS,OAAO,GAAqB,CAAC,CAAC;CACjG,CAAC;CAED,MAAM,UAAU,eAAuB;EACrC,IAAI,eAAe,IAAI,OAAO,CAAC,CAAC;EAChC,IAAI,IAAI,YAAY,GAClB;EAEF,OAAO;CACT,CAAC;CAGD,MAAM,cAAoC,OAAO,KAAK,KAAK,CAAC,CAAC,OAAO,YAAY;CAEhF,SAAS,UAAU,KAAQ,KAA+C;EACxE,OAAO,IAAI;CACb;CAEA,OAAO;EACL;EACA;EACA;EACA;CACF;AACF"}
@@ -68,6 +68,27 @@ function useTableSelection(options, deps) {
68
68
  else if (!checked && selected) set(internalSelectedData, selectedRows.filter((r) => r !== value));
69
69
  if (userAction) set(selectedData, get(internalSelectedData));
70
70
  }
71
+ /**
72
+ * Checks or unchecks every selectable row from `from` to `to`, updating the selection once.
73
+ */
74
+ function selectRange(from, to, checked) {
75
+ const tableData = get(filtered);
76
+ const currentSelected = get(internalSelectedData);
77
+ const rangeIds = [];
78
+ for (let i = from; i <= to; i++) {
79
+ const currSelectedData = tableData[i];
80
+ assert(currSelectedData);
81
+ if (isRow(currSelectedData) && !isDisabledRow(currSelectedData[rowAttr])) rangeIds.push(currSelectedData[rowAttr]);
82
+ }
83
+ if (checked) {
84
+ const existing = new Set(currentSelected);
85
+ const toAdd = rangeIds.filter((id) => !existing.has(id));
86
+ set(internalSelectedData, [...currentSelected, ...toAdd]);
87
+ } else {
88
+ const toRemove = new Set(rangeIds);
89
+ set(internalSelectedData, currentSelected.filter((id) => !toRemove.has(id)));
90
+ }
91
+ }
71
92
  function onCheckboxClick(event, value, index) {
72
93
  const currentTarget = event.currentTarget;
73
94
  if (!(currentTarget instanceof HTMLElement)) return;
@@ -80,31 +101,12 @@ function useTableSelection(options, deps) {
80
101
  if (shiftKey) scheduleShiftSelect(() => {
81
102
  let lastIndex = get(lastSelectedIndex);
82
103
  if (lastIndex === -1) lastIndex = index;
83
- const tableData = get(filtered);
84
- const lastSelectedData = tableData[lastIndex];
104
+ const lastSelectedData = get(filtered)[lastIndex];
85
105
  assert(lastSelectedData);
86
106
  if (isRow(lastSelectedData)) {
87
107
  const valueToApply = isSelected(lastSelectedData[rowAttr]);
88
108
  if (lastIndex === index) onSelect(!valueToApply, value);
89
- else {
90
- const from = Math.min(lastIndex, index);
91
- const to = Math.max(lastIndex, index);
92
- const currentSelected = get(internalSelectedData);
93
- const rangeIds = [];
94
- for (let i = from; i <= to; i++) {
95
- const currSelectedData = tableData[i];
96
- assert(currSelectedData);
97
- if (isRow(currSelectedData) && !isDisabledRow(currSelectedData[rowAttr])) rangeIds.push(currSelectedData[rowAttr]);
98
- }
99
- if (valueToApply) {
100
- const existing = new Set(currentSelected);
101
- const toAdd = rangeIds.filter((id) => !existing.has(id));
102
- set(internalSelectedData, [...currentSelected, ...toAdd]);
103
- } else {
104
- const toRemove = new Set(rangeIds);
105
- set(internalSelectedData, currentSelected.filter((id) => !toRemove.has(id)));
106
- }
107
- }
109
+ else selectRange(Math.min(lastIndex, index), Math.max(lastIndex, index), valueToApply);
108
110
  set(lastSelectedIndex, index);
109
111
  set(selectedData, get(internalSelectedData));
110
112
  }
@@ -1 +1 @@
1
- {"version":3,"file":"selection.js","names":[],"sources":["../../../../src/composables/tables/data-table/selection.ts"],"sourcesContent":["import type { ComputedRef, MaybeRefOrGetter, Ref } from 'vue';\nimport { type GroupedTableRow, isRow } from '@/composables/tables/data-table/types';\nimport { useTimeoutManager } from '@/composables/timeout-manager';\nimport { assert } from '@/utils/assert';\n\nexport interface UseTableSelectionOptions<T extends object, IdType extends keyof T> {\n /** The property on each row used as the unique row identifier. */\n rowAttr: IdType;\n /** Whether selection persists across pages. */\n multiPageSelect: boolean;\n /** Rows that cannot be selected or deselected by the user. */\n disabledRows: MaybeRefOrGetter<readonly T[] | undefined>;\n}\n\nexport interface UseTableSelectionDeps<T extends object, IdType extends keyof T> {\n selectedData: Ref<T[IdType][] | undefined>;\n filtered: ComputedRef<GroupedTableRow<T>[]>;\n}\n\nexport interface UseTableSelectionReturn<T extends object, IdType extends keyof T> {\n visibleIdentifiers: ComputedRef<T[IdType][]>;\n isAllSelected: ComputedRef<boolean>;\n indeterminate: ComputedRef<boolean>;\n isSelected: (identifier: T[IdType]) => boolean;\n isDisabledRow: (rowKey: T[IdType]) => boolean;\n isSelectable: (rowKey: T[IdType]) => boolean;\n mustSelect: (rowKey: T[IdType]) => boolean;\n onToggleAll: (checked: boolean) => void;\n onSelect: (checked: boolean, value: T[IdType], userAction?: boolean) => void;\n onCheckboxClick: (event: MouseEvent, value: T[IdType], index: number) => void;\n deselectRemovedRows: () => void;\n resetCheckboxShiftState: () => void;\n}\n\nexport function useTableSelection<T extends object, IdType extends keyof T>(\n options: UseTableSelectionOptions<T, IdType>,\n deps: UseTableSelectionDeps<T, IdType>,\n): UseTableSelectionReturn<T, IdType> {\n const { rowAttr, multiPageSelect, disabledRows } = options;\n const { selectedData, filtered } = deps;\n\n const shiftClicked: Ref<boolean> = shallowRef(false);\n const lastSelectedIndex: Ref<number> = shallowRef(-1);\n const internalSelectedData: Ref<T[IdType][]> = shallowRef([]);\n const { create: scheduleShiftSelect } = useTimeoutManager();\n\n // Sync external selection model to internal state\n watch(selectedData, val => set(internalSelectedData, val ?? []), { immediate: true });\n\n const visibleIdentifiers = computed<T[IdType][]>(() =>\n get(filtered)\n .filter(isRow)\n .map(row => row[rowAttr]),\n );\n\n const selectedSet = computed<Set<T[IdType]>>(() => new Set(get(selectedData) ?? []));\n\n const disabledRowKeySet = computed<Set<T[IdType]>>(() => {\n const currentDisabledRows = toValue(disabledRows);\n if (!currentDisabledRows || !rowAttr)\n return new Set<T[IdType]>();\n return new Set<T[IdType]>(currentDisabledRows.map((row: T) => row[rowAttr]));\n });\n\n const isAllSelected = computed<boolean>(() => {\n const selectedRows = get(selectedData);\n if (!selectedRows || selectedRows.length === 0)\n return false;\n\n const selected = get(selectedSet);\n return get(visibleIdentifiers).every(id => selected.has(id));\n });\n\n const indeterminate = computed<boolean>(() => {\n const selectedRows = get(selectedData);\n if (!selectedRows)\n return false;\n\n return selectedRows.length > 0 && !get(isAllSelected);\n });\n\n function isSelected(identifier: T[IdType]): boolean {\n return get(selectedSet).has(identifier);\n }\n\n function isDisabledRow(rowKey: T[IdType]): boolean {\n return get(disabledRowKeySet).has(rowKey);\n }\n\n function isSelectable(rowKey: T[IdType]): boolean {\n return isSelected(rowKey) || !isDisabledRow(rowKey);\n }\n\n function mustSelect(rowKey: T[IdType]): boolean {\n return isSelected(rowKey) && isDisabledRow(rowKey);\n }\n\n function resetCheckboxShiftState(): void {\n set(shiftClicked, false);\n set(lastSelectedIndex, -1);\n }\n\n function onToggleAll(checked: boolean): void {\n const selectedRows = get(selectedData);\n\n if (!isDefined(selectedRows))\n return;\n\n if (!multiPageSelect) {\n if (checked)\n set(selectedData, get(visibleIdentifiers).filter(isSelectable));\n else set(selectedData, get(visibleIdentifiers).filter(mustSelect));\n }\n else {\n if (checked) {\n set(\n selectedData,\n Array.from(new Set([...selectedRows, ...get(visibleIdentifiers).filter(isSelectable)])),\n );\n }\n else {\n const visibleSet = new Set(get(visibleIdentifiers));\n const mustSelectSet = new Set(get(visibleIdentifiers).filter(mustSelect));\n set(\n selectedData,\n selectedRows.filter(rowKey => !visibleSet.has(rowKey) || mustSelectSet.has(rowKey)),\n );\n }\n }\n }\n\n function onSelect(checked: boolean, value: T[IdType], userAction: boolean = false): void {\n if (get(shiftClicked) && userAction)\n return;\n\n const selectedRows = get(internalSelectedData);\n if (!selectedRows)\n return;\n\n const selected = isSelected(value);\n\n if (checked && !selected) {\n set(internalSelectedData, [...selectedRows, value]);\n }\n else if (!checked && selected) {\n set(\n internalSelectedData,\n selectedRows.filter(r => r !== value),\n );\n }\n\n if (userAction)\n set(selectedData, get(internalSelectedData));\n }\n\n function onCheckboxClick(event: MouseEvent, value: T[IdType], index: number): void {\n const currentTarget = event.currentTarget;\n if (!(currentTarget instanceof HTMLElement))\n return;\n\n const input = currentTarget.querySelector('input');\n const target = event.target;\n const nodeName = target instanceof HTMLElement ? target.nodeName : undefined;\n\n const shiftKey = event.shiftKey;\n set(shiftClicked, shiftKey);\n\n if (input && nodeName !== 'INPUT') {\n if (shiftKey) {\n scheduleShiftSelect(() => {\n let lastIndex = get(lastSelectedIndex);\n if (lastIndex === -1)\n lastIndex = index;\n const tableData = get(filtered);\n const lastSelectedData = tableData[lastIndex];\n assert(lastSelectedData);\n\n if (isRow(lastSelectedData)) {\n const valueToApply = isSelected(lastSelectedData[rowAttr]);\n\n if (lastIndex === index) {\n onSelect(!valueToApply, value);\n }\n else {\n const from = Math.min(lastIndex, index);\n const to = Math.max(lastIndex, index);\n\n // Batch: collect all IDs in range, then update once\n const currentSelected = get(internalSelectedData);\n const rangeIds: T[IdType][] = [];\n for (let i = from; i <= to; i++) {\n const currSelectedData = tableData[i];\n assert(currSelectedData);\n if (isRow(currSelectedData) && !isDisabledRow(currSelectedData[rowAttr]))\n rangeIds.push(currSelectedData[rowAttr]);\n }\n\n if (valueToApply) {\n const existing = new Set(currentSelected);\n const toAdd = rangeIds.filter(id => !existing.has(id));\n set(internalSelectedData, [...currentSelected, ...toAdd]);\n }\n else {\n const toRemove = new Set(rangeIds);\n set(internalSelectedData, currentSelected.filter(id => !toRemove.has(id)));\n }\n }\n\n set(lastSelectedIndex, index);\n set(selectedData, get(internalSelectedData));\n }\n }, 1);\n }\n else {\n set(lastSelectedIndex, index);\n }\n }\n }\n\n function deselectRemovedRows(): void {\n const currentSelected = get(selectedData);\n if (!currentSelected)\n return;\n\n const visibleSet = new Set(get(visibleIdentifiers));\n const remaining = currentSelected.filter(key => visibleSet.has(key));\n\n if (remaining.length !== currentSelected.length)\n set(selectedData, remaining);\n }\n\n return {\n visibleIdentifiers,\n isAllSelected,\n indeterminate,\n isSelected,\n isDisabledRow,\n isSelectable,\n mustSelect,\n onToggleAll,\n onSelect,\n onCheckboxClick,\n deselectRemovedRows,\n resetCheckboxShiftState,\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"selection.js","names":[],"sources":["../../../../src/composables/tables/data-table/selection.ts"],"sourcesContent":["import type { ComputedRef, MaybeRefOrGetter, Ref } from 'vue';\nimport { type GroupedTableRow, isRow } from '@/composables/tables/data-table/types';\nimport { useTimeoutManager } from '@/composables/timeout-manager';\nimport { assert } from '@/utils/assert';\n\nexport interface UseTableSelectionOptions<T extends object, IdType extends keyof T> {\n /** The property on each row used as the unique row identifier. */\n rowAttr: IdType;\n /** Whether selection persists across pages. */\n multiPageSelect: boolean;\n /** Rows that cannot be selected or deselected by the user. */\n disabledRows: MaybeRefOrGetter<readonly T[] | undefined>;\n}\n\nexport interface UseTableSelectionDeps<T extends object, IdType extends keyof T> {\n selectedData: Ref<T[IdType][] | undefined>;\n filtered: ComputedRef<GroupedTableRow<T>[]>;\n}\n\nexport interface UseTableSelectionReturn<T extends object, IdType extends keyof T> {\n visibleIdentifiers: ComputedRef<T[IdType][]>;\n isAllSelected: ComputedRef<boolean>;\n indeterminate: ComputedRef<boolean>;\n isSelected: (identifier: T[IdType]) => boolean;\n isDisabledRow: (rowKey: T[IdType]) => boolean;\n isSelectable: (rowKey: T[IdType]) => boolean;\n mustSelect: (rowKey: T[IdType]) => boolean;\n onToggleAll: (checked: boolean) => void;\n onSelect: (checked: boolean, value: T[IdType], userAction?: boolean) => void;\n onCheckboxClick: (event: MouseEvent, value: T[IdType], index: number) => void;\n deselectRemovedRows: () => void;\n resetCheckboxShiftState: () => void;\n}\n\nexport function useTableSelection<T extends object, IdType extends keyof T>(\n options: UseTableSelectionOptions<T, IdType>,\n deps: UseTableSelectionDeps<T, IdType>,\n): UseTableSelectionReturn<T, IdType> {\n const { rowAttr, multiPageSelect, disabledRows } = options;\n const { selectedData, filtered } = deps;\n\n const shiftClicked: Ref<boolean> = shallowRef(false);\n const lastSelectedIndex: Ref<number> = shallowRef(-1);\n const internalSelectedData: Ref<T[IdType][]> = shallowRef([]);\n const { create: scheduleShiftSelect } = useTimeoutManager();\n\n // Sync external selection model to internal state\n watch(selectedData, val => set(internalSelectedData, val ?? []), { immediate: true });\n\n const visibleIdentifiers = computed<T[IdType][]>(() =>\n get(filtered)\n .filter(isRow)\n .map(row => row[rowAttr]),\n );\n\n const selectedSet = computed<Set<T[IdType]>>(() => new Set(get(selectedData) ?? []));\n\n const disabledRowKeySet = computed<Set<T[IdType]>>(() => {\n const currentDisabledRows = toValue(disabledRows);\n if (!currentDisabledRows || !rowAttr)\n return new Set<T[IdType]>();\n return new Set<T[IdType]>(currentDisabledRows.map((row: T) => row[rowAttr]));\n });\n\n const isAllSelected = computed<boolean>(() => {\n const selectedRows = get(selectedData);\n if (!selectedRows || selectedRows.length === 0)\n return false;\n\n const selected = get(selectedSet);\n return get(visibleIdentifiers).every(id => selected.has(id));\n });\n\n const indeterminate = computed<boolean>(() => {\n const selectedRows = get(selectedData);\n if (!selectedRows)\n return false;\n\n return selectedRows.length > 0 && !get(isAllSelected);\n });\n\n function isSelected(identifier: T[IdType]): boolean {\n return get(selectedSet).has(identifier);\n }\n\n function isDisabledRow(rowKey: T[IdType]): boolean {\n return get(disabledRowKeySet).has(rowKey);\n }\n\n function isSelectable(rowKey: T[IdType]): boolean {\n return isSelected(rowKey) || !isDisabledRow(rowKey);\n }\n\n function mustSelect(rowKey: T[IdType]): boolean {\n return isSelected(rowKey) && isDisabledRow(rowKey);\n }\n\n function resetCheckboxShiftState(): void {\n set(shiftClicked, false);\n set(lastSelectedIndex, -1);\n }\n\n function onToggleAll(checked: boolean): void {\n const selectedRows = get(selectedData);\n\n if (!isDefined(selectedRows))\n return;\n\n if (!multiPageSelect) {\n if (checked)\n set(selectedData, get(visibleIdentifiers).filter(isSelectable));\n else set(selectedData, get(visibleIdentifiers).filter(mustSelect));\n }\n else {\n if (checked) {\n set(\n selectedData,\n Array.from(new Set([...selectedRows, ...get(visibleIdentifiers).filter(isSelectable)])),\n );\n }\n else {\n const visibleSet = new Set(get(visibleIdentifiers));\n const mustSelectSet = new Set(get(visibleIdentifiers).filter(mustSelect));\n set(\n selectedData,\n selectedRows.filter(rowKey => !visibleSet.has(rowKey) || mustSelectSet.has(rowKey)),\n );\n }\n }\n }\n\n function onSelect(checked: boolean, value: T[IdType], userAction: boolean = false): void {\n if (get(shiftClicked) && userAction)\n return;\n\n const selectedRows = get(internalSelectedData);\n if (!selectedRows)\n return;\n\n const selected = isSelected(value);\n\n if (checked && !selected) {\n set(internalSelectedData, [...selectedRows, value]);\n }\n else if (!checked && selected) {\n set(\n internalSelectedData,\n selectedRows.filter(r => r !== value),\n );\n }\n\n if (userAction)\n set(selectedData, get(internalSelectedData));\n }\n\n /**\n * Checks or unchecks every selectable row from `from` to `to`, updating the selection once.\n */\n function selectRange(from: number, to: number, checked: boolean): void {\n const tableData = get(filtered);\n const currentSelected = get(internalSelectedData);\n const rangeIds: T[IdType][] = [];\n for (let i = from; i <= to; i++) {\n const currSelectedData = tableData[i];\n assert(currSelectedData);\n if (isRow(currSelectedData) && !isDisabledRow(currSelectedData[rowAttr]))\n rangeIds.push(currSelectedData[rowAttr]);\n }\n\n if (checked) {\n const existing = new Set(currentSelected);\n const toAdd = rangeIds.filter(id => !existing.has(id));\n set(internalSelectedData, [...currentSelected, ...toAdd]);\n }\n else {\n const toRemove = new Set(rangeIds);\n set(internalSelectedData, currentSelected.filter(id => !toRemove.has(id)));\n }\n }\n\n function onCheckboxClick(event: MouseEvent, value: T[IdType], index: number): void {\n const currentTarget = event.currentTarget;\n if (!(currentTarget instanceof HTMLElement))\n return;\n\n const input = currentTarget.querySelector('input');\n const target = event.target;\n const nodeName = target instanceof HTMLElement ? target.nodeName : undefined;\n\n const shiftKey = event.shiftKey;\n set(shiftClicked, shiftKey);\n\n if (input && nodeName !== 'INPUT') {\n if (shiftKey) {\n scheduleShiftSelect(() => {\n let lastIndex = get(lastSelectedIndex);\n if (lastIndex === -1)\n lastIndex = index;\n const lastSelectedData = get(filtered)[lastIndex];\n assert(lastSelectedData);\n\n if (isRow(lastSelectedData)) {\n const valueToApply = isSelected(lastSelectedData[rowAttr]);\n\n if (lastIndex === index)\n onSelect(!valueToApply, value);\n else\n selectRange(Math.min(lastIndex, index), Math.max(lastIndex, index), valueToApply);\n\n set(lastSelectedIndex, index);\n set(selectedData, get(internalSelectedData));\n }\n }, 1);\n }\n else {\n set(lastSelectedIndex, index);\n }\n }\n }\n\n function deselectRemovedRows(): void {\n const currentSelected = get(selectedData);\n if (!currentSelected)\n return;\n\n const visibleSet = new Set(get(visibleIdentifiers));\n const remaining = currentSelected.filter(key => visibleSet.has(key));\n\n if (remaining.length !== currentSelected.length)\n set(selectedData, remaining);\n }\n\n return {\n visibleIdentifiers,\n isAllSelected,\n indeterminate,\n isSelected,\n isDisabledRow,\n isSelectable,\n mustSelect,\n onToggleAll,\n onSelect,\n onCheckboxClick,\n deselectRemovedRows,\n resetCheckboxShiftState,\n };\n}\n"],"mappings":"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"}