@application-kit/ui 0.0.0-stage → 0.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (199) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +41 -2
  3. package/dist/_virtual/_plugin-vue_export-helper.js +8 -0
  4. package/dist/accent-color.d.ts +2 -0
  5. package/dist/accent-color.js +15 -0
  6. package/dist/components/avatar/Avatar.js +5 -0
  7. package/dist/components/avatar/Avatar.vue.d.ts +24 -0
  8. package/dist/components/avatar/Avatar.vue_vue_type_script_setup_true_lang.js +31 -0
  9. package/dist/components/button/Button.js +5 -0
  10. package/dist/components/button/Button.vue.d.ts +130 -0
  11. package/dist/components/button/Button.vue_vue_type_script_setup_true_lang.js +104 -0
  12. package/dist/components/button/severity-to-vendor.d.ts +4 -0
  13. package/dist/components/button/severity-to-vendor.js +11 -0
  14. package/dist/components/card/Card.js +5 -0
  15. package/dist/components/card/Card.vue.d.ts +17 -0
  16. package/dist/components/card/Card.vue_vue_type_script_setup_true_lang.js +34 -0
  17. package/dist/components/confirm/ConfirmHost.js +5 -0
  18. package/dist/components/confirm/ConfirmHost.vue.d.ts +3 -0
  19. package/dist/components/confirm/ConfirmHost.vue_vue_type_script_setup_true_lang.js +13 -0
  20. package/dist/components/confirm/install-confirmation-service.d.ts +2 -0
  21. package/dist/components/confirm/install-confirmation-service.js +16 -0
  22. package/dist/components/date-range-filter/DateRangeFilter.js +7 -0
  23. package/dist/components/date-range-filter/DateRangeFilter.vue.d.ts +13 -0
  24. package/dist/components/date-range-filter/DateRangeFilter.vue_vue_type_script_setup_true_lang.js +121 -0
  25. package/dist/components/dialog/Dialog.js +5 -0
  26. package/dist/components/dialog/Dialog.vue.d.ts +56 -0
  27. package/dist/components/dialog/Dialog.vue_vue_type_script_setup_true_lang.js +71 -0
  28. package/dist/components/drawer/Drawer.js +5 -0
  29. package/dist/components/drawer/Drawer.vue.d.ts +24 -0
  30. package/dist/components/drawer/Drawer.vue_vue_type_script_setup_true_lang.js +29 -0
  31. package/dist/components/error-page/Error.js +7 -0
  32. package/dist/components/error-page/Error.vue.d.ts +62 -0
  33. package/dist/components/error-page/Error.vue_vue_type_script_setup_true_lang.js +93 -0
  34. package/dist/components/error-page/GradientBackground.js +7 -0
  35. package/dist/components/error-page/GradientBackground.vue.d.ts +17 -0
  36. package/dist/components/error-page/GradientBackground.vue_vue_type_script_setup_true_lang.js +54 -0
  37. package/dist/components/form/Checkbox.js +5 -0
  38. package/dist/components/form/Checkbox.vue.d.ts +16 -0
  39. package/dist/components/form/Checkbox.vue_vue_type_script_setup_true_lang.js +37 -0
  40. package/dist/components/form/DateField.js +5 -0
  41. package/dist/components/form/DateField.vue.d.ts +24 -0
  42. package/dist/components/form/DateField.vue_vue_type_script_setup_true_lang.js +52 -0
  43. package/dist/components/form/DateRangeField.js +5 -0
  44. package/dist/components/form/DateRangeField.vue.d.ts +32 -0
  45. package/dist/components/form/DateRangeField.vue_vue_type_script_setup_true_lang.js +48 -0
  46. package/dist/components/form/InputNumber.js +5 -0
  47. package/dist/components/form/InputNumber.vue.d.ts +17 -0
  48. package/dist/components/form/InputNumber.vue_vue_type_script_setup_true_lang.js +37 -0
  49. package/dist/components/form/InputOtp.js +5 -0
  50. package/dist/components/form/InputOtp.vue.d.ts +20 -0
  51. package/dist/components/form/InputOtp.vue_vue_type_script_setup_true_lang.js +39 -0
  52. package/dist/components/form/InputText.js +5 -0
  53. package/dist/components/form/InputText.vue.d.ts +17 -0
  54. package/dist/components/form/InputText.vue_vue_type_script_setup_true_lang.js +37 -0
  55. package/dist/components/form/MultiSelect.js +5 -0
  56. package/dist/components/form/MultiSelect.vue.d.ts +34 -0
  57. package/dist/components/form/MultiSelect.vue_vue_type_script_setup_true_lang.js +47 -0
  58. package/dist/components/form/RadioButton.js +5 -0
  59. package/dist/components/form/RadioButton.vue.d.ts +27 -0
  60. package/dist/components/form/RadioButton.vue_vue_type_script_setup_true_lang.js +37 -0
  61. package/dist/components/form/Select.js +5 -0
  62. package/dist/components/form/Select.vue.d.ts +41 -0
  63. package/dist/components/form/Select.vue_vue_type_script_setup_true_lang.js +78 -0
  64. package/dist/components/form/SelectButton.js +5 -0
  65. package/dist/components/form/SelectButton.vue.d.ts +27 -0
  66. package/dist/components/form/SelectButton.vue_vue_type_script_setup_true_lang.js +36 -0
  67. package/dist/components/form/Textarea.js +5 -0
  68. package/dist/components/form/Textarea.vue.d.ts +16 -0
  69. package/dist/components/form/Textarea.vue_vue_type_script_setup_true_lang.js +34 -0
  70. package/dist/components/form/ToggleSwitch.js +5 -0
  71. package/dist/components/form/ToggleSwitch.vue.d.ts +14 -0
  72. package/dist/components/form/ToggleSwitch.vue_vue_type_script_setup_true_lang.js +26 -0
  73. package/dist/components/form/date-format.d.ts +1 -0
  74. package/dist/components/form/date-format.js +14 -0
  75. package/dist/components/form/select-button-option.interface.d.ts +4 -0
  76. package/dist/components/icon/Icon.js +7 -0
  77. package/dist/components/icon/Icon.vue.d.ts +23 -0
  78. package/dist/components/icon/Icon.vue_vue_type_script_setup_true_lang.js +28 -0
  79. package/dist/components/icon-cell/IconCell.js +7 -0
  80. package/dist/components/icon-cell/IconCell.vue.d.ts +7 -0
  81. package/dist/components/icon-cell/IconCell.vue_vue_type_script_setup_true_lang.js +32 -0
  82. package/dist/components/layouts/EmptyLayout.js +16 -0
  83. package/dist/components/layouts/EmptyLayout.vue.d.ts +15 -0
  84. package/dist/components/message/Message.js +5 -0
  85. package/dist/components/message/Message.vue.d.ts +72 -0
  86. package/dist/components/message/Message.vue_vue_type_script_setup_true_lang.js +63 -0
  87. package/dist/components/message/severity-to-vendor.d.ts +4 -0
  88. package/dist/components/message/severity-to-vendor.js +11 -0
  89. package/dist/components/overlay-badge/OverlayBadge.js +5 -0
  90. package/dist/components/overlay-badge/OverlayBadge.vue.d.ts +16 -0
  91. package/dist/components/overlay-badge/OverlayBadge.vue_vue_type_script_setup_true_lang.js +17 -0
  92. package/dist/components/skeleton/Skeleton.js +5 -0
  93. package/dist/components/skeleton/Skeleton.vue.d.ts +7 -0
  94. package/dist/components/skeleton/Skeleton.vue_vue_type_script_setup_true_lang.js +20 -0
  95. package/dist/components/table/DataTable.js +7 -0
  96. package/dist/components/table/DataTable.vue.d.ts +61 -0
  97. package/dist/components/table/DataTable.vue_vue_type_script_setup_true_lang.js +303 -0
  98. package/dist/components/table/InfiniteDataTable.js +7 -0
  99. package/dist/components/table/InfiniteDataTable.vue.d.ts +32 -0
  100. package/dist/components/table/InfiniteDataTable.vue_vue_type_script_setup_true_lang.js +88 -0
  101. package/dist/components/table/SelectColumnFilter.js +5 -0
  102. package/dist/components/table/SelectColumnFilter.vue.d.ts +29 -0
  103. package/dist/components/table/SelectColumnFilter.vue_vue_type_script_setup_true_lang.js +58 -0
  104. package/dist/components/table/StaticTable.js +5 -0
  105. package/dist/components/table/StaticTable.vue.d.ts +39 -0
  106. package/dist/components/table/StaticTable.vue_vue_type_script_setup_true_lang.js +48 -0
  107. package/dist/components/table/TextColumn.js +5 -0
  108. package/dist/components/table/TextColumn.vue.d.ts +13 -0
  109. package/dist/components/table/TextColumn.vue_vue_type_script_setup_true_lang.js +16 -0
  110. package/dist/components/table/collect-column-filters.d.ts +4 -0
  111. package/dist/components/table/collect-column-filters.js +11 -0
  112. package/dist/components/table/column-filters-interface.d.ts +10 -0
  113. package/dist/components/table/column-schema-interface.d.ts +20 -0
  114. package/dist/components/table/get-filters.d.ts +3 -0
  115. package/dist/components/table/get-filters.js +73 -0
  116. package/dist/components/table/select-column-filter-option.interface.d.ts +8 -0
  117. package/dist/components/table/static-table-column.d.ts +1 -0
  118. package/dist/components/tabs/Tabs.js +5 -0
  119. package/dist/components/tabs/Tabs.vue.d.ts +35 -0
  120. package/dist/components/tabs/Tabs.vue_vue_type_script_setup_true_lang.js +54 -0
  121. package/dist/components/tabs/tab-item.interface.d.ts +7 -0
  122. package/dist/components/tag/Tag.js +5 -0
  123. package/dist/components/tag/Tag.vue.d.ts +44 -0
  124. package/dist/components/tag/Tag.vue_vue_type_script_setup_true_lang.js +47 -0
  125. package/dist/components/tag/severity-to-vendor.d.ts +4 -0
  126. package/dist/components/tag/severity-to-vendor.js +11 -0
  127. package/dist/components/time-lag/TimeLag.js +7 -0
  128. package/dist/components/time-lag/TimeLag.vue.d.ts +29 -0
  129. package/dist/components/time-lag/TimeLag.vue_vue_type_script_setup_true_lang.js +38 -0
  130. package/dist/components/timestamp/Timestamp.js +5 -0
  131. package/dist/components/timestamp/Timestamp.vue.d.ts +13 -0
  132. package/dist/components/timestamp/Timestamp.vue_vue_type_script_setup_true_lang.js +21 -0
  133. package/dist/components/toast/ToastHost.js +6 -0
  134. package/dist/components/toast/ToastHost.vue.d.ts +3 -0
  135. package/dist/components/toast/ToastHost.vue_vue_type_script_setup_true_lang.js +13 -0
  136. package/dist/components/toast/install-toast-service.d.ts +2 -0
  137. package/dist/components/toast/install-toast-service.js +15 -0
  138. package/dist/components/toast/severity-to-vendor.d.ts +4 -0
  139. package/dist/components/toast/severity-to-vendor.js +11 -0
  140. package/dist/components/tooltip/Tooltip.js +7 -0
  141. package/dist/components/tooltip/Tooltip.vue.d.ts +51 -0
  142. package/dist/components/tooltip/Tooltip.vue_vue_type_script_setup_true_lang.js +47 -0
  143. package/dist/components/tooltip/dismiss-tooltips.d.ts +2 -0
  144. package/dist/components/tooltip/dismiss-tooltips.js +13 -0
  145. package/dist/components/tooltip/install-tooltip-directive.d.ts +6 -0
  146. package/dist/components/tooltip/install-tooltip-directive.js +80 -0
  147. package/dist/components/tooltip/tooltip-options.interface.d.ts +10 -0
  148. package/dist/components/truncated-code/TruncatedCode.js +5 -0
  149. package/dist/components/truncated-code/TruncatedCode.vue.d.ts +9 -0
  150. package/dist/components/truncated-code/TruncatedCode.vue_vue_type_script_setup_true_lang.js +23 -0
  151. package/dist/components/ui-icon/UiIcon.js +5 -0
  152. package/dist/components/ui-icon/UiIcon.vue.d.ts +13 -0
  153. package/dist/components/ui-icon/UiIcon.vue_vue_type_script_setup_true_lang.js +22 -0
  154. package/dist/composables/use-app-toast.d.ts +12 -0
  155. package/dist/composables/use-app-toast.js +50 -0
  156. package/dist/composables/use-confirm.d.ts +12 -0
  157. package/dist/composables/use-confirm.js +49 -0
  158. package/dist/composables/use-copy-to-clipboard.d.ts +2 -0
  159. package/dist/composables/use-copy-to-clipboard.js +23 -0
  160. package/dist/icons/ui-icon-registry.d.ts +6 -0
  161. package/dist/icons/ui-icon-registry.js +4 -0
  162. package/dist/index.d.ts +66 -0
  163. package/dist/index.js +56 -0
  164. package/dist/layout/layout.d.ts +37 -0
  165. package/dist/layout/layout.js +60 -0
  166. package/dist/messages/ui-messages.d.ts +23 -0
  167. package/dist/plugin/ui-injection.d.ts +11 -0
  168. package/dist/plugin/ui-injection.js +18 -0
  169. package/dist/plugin/ui-vendor-plugin.d.ts +22 -0
  170. package/dist/plugin/ui-vendor-plugin.js +57 -0
  171. package/dist/severity.d.ts +1 -0
  172. package/dist/store/listing/cursor-listing.d.ts +25 -0
  173. package/dist/store/listing/cursor-listing.js +97 -0
  174. package/dist/store/listing/infinite-listing-store.d.ts +21 -0
  175. package/dist/store/listing/infinite-listing-store.interface.d.ts +13 -0
  176. package/dist/store/listing/infinite-listing-store.js +68 -0
  177. package/dist/store/listing/listing-store.d.ts +7 -0
  178. package/dist/store/listing/listing-store.interface.d.ts +21 -0
  179. package/dist/store/listing/listing-store.js +114 -0
  180. package/dist/style.css +158 -0
  181. package/dist/styles/_tokens.scss +96 -0
  182. package/dist/styles/_variables.scss +8 -0
  183. package/dist/testing/browser-api-stubs.d.ts +1 -0
  184. package/dist/testing/browser-api-stubs.js +38 -0
  185. package/dist/testing/index.d.ts +5 -0
  186. package/dist/testing/index.js +6 -0
  187. package/dist/testing/set-viewport-width.d.ts +1 -0
  188. package/dist/testing/set-viewport-width.js +18 -0
  189. package/dist/testing/submit-form.d.ts +1 -0
  190. package/dist/testing/submit-form.js +25 -0
  191. package/dist/testing/ui-messages.fixture.d.ts +2 -0
  192. package/dist/testing/ui-messages.fixture.js +26 -0
  193. package/dist/testing/vendor-plugins.d.ts +2 -0
  194. package/dist/testing/vendor-plugins.js +22 -0
  195. package/dist/utils/datetime.d.ts +10 -0
  196. package/dist/utils/datetime.js +53 -0
  197. package/dist/utils/truncate-string.d.ts +2 -0
  198. package/dist/utils/truncate-string.js +10 -0
  199. package/package.json +93 -3
@@ -0,0 +1,23 @@
1
+ export interface UiMessagesInterface {
2
+ readonly dateRangeFilter: {
3
+ readonly clear: string;
4
+ readonly lastMonth: string;
5
+ readonly lastSixMonths: string;
6
+ readonly lastWeek: string;
7
+ readonly placeholder: string;
8
+ };
9
+ readonly errorPage: {
10
+ readonly copied: string;
11
+ readonly copy: string;
12
+ readonly goToDashboard: string;
13
+ readonly supportIdTitle: string;
14
+ };
15
+ readonly table: {
16
+ readonly empty: string;
17
+ readonly error: string;
18
+ readonly loading: string;
19
+ readonly loadingMore: string;
20
+ readonly searchPlaceholder: string;
21
+ readonly selectFilterPlaceholder: string;
22
+ };
23
+ }
@@ -0,0 +1,11 @@
1
+ import type { ComputedRef, InjectionKey, MaybeRefOrGetter } from 'vue';
2
+ import type { IconName } from '../icons/ui-icon-registry';
3
+ import type { UiMessagesInterface } from '../messages/ui-messages';
4
+ export type UiDescribeRequestError = (error: unknown, fallbackMessage: string) => string;
5
+ export type UiIconClass = (name: IconName) => string;
6
+ export declare const UI_MESSAGES: InjectionKey<MaybeRefOrGetter<UiMessagesInterface>>;
7
+ export declare const UI_ICON_CLASS: InjectionKey<UiIconClass>;
8
+ export declare const UI_DESCRIBE_REQUEST_ERROR: InjectionKey<UiDescribeRequestError>;
9
+ export declare const useUiMessages: () => ComputedRef<UiMessagesInterface>;
10
+ export declare const useUiIconClass: () => UiIconClass;
11
+ export declare const useUiDescribeRequestError: () => UiDescribeRequestError;
@@ -0,0 +1,18 @@
1
+ import { computed, inject, toValue } from "vue";
2
+ //#region src/plugin/ui-injection.ts
3
+ var UI_MESSAGES = Symbol("ui-messages");
4
+ var UI_ICON_CLASS = Symbol("ui-icon-class");
5
+ var UI_DESCRIBE_REQUEST_ERROR = Symbol("ui-describe-request-error");
6
+ var injectRequired = (key, option) => {
7
+ const value = inject(key, void 0);
8
+ if (value === void 0) throw new Error(`@application-kit/ui: нет опции «${option}» — установите UiVendorPlugin с ней`);
9
+ return value;
10
+ };
11
+ var useUiMessages = () => {
12
+ const source = injectRequired(UI_MESSAGES, "messages");
13
+ return computed(() => toValue(source));
14
+ };
15
+ var useUiIconClass = () => injectRequired(UI_ICON_CLASS, "iconClass");
16
+ var useUiDescribeRequestError = () => injectRequired(UI_DESCRIBE_REQUEST_ERROR, "describeRequestError");
17
+ //#endregion
18
+ export { UI_DESCRIBE_REQUEST_ERROR, UI_ICON_CLASS, UI_MESSAGES, useUiDescribeRequestError, useUiIconClass, useUiMessages };
@@ -0,0 +1,22 @@
1
+ import type { App, MaybeRefOrGetter, Ref } from 'vue';
2
+ import type { UiMessagesInterface } from '../messages/ui-messages';
3
+ import type { UiDescribeRequestError, UiIconClass } from './ui-injection';
4
+ export interface UiLocaleOptionsInterface {
5
+ readonly overrides: Readonly<Record<string, Partial<UiVendorLocale>>>;
6
+ readonly reference: UiLocaleReference;
7
+ }
8
+ export type UiLocaleReference = (app: App) => Ref<string> | undefined;
9
+ export type UiVendorLocale = NonNullable<App['config']['globalProperties']['$primevue']['config']['locale']>;
10
+ export interface UiVendorPluginOptionsInterface {
11
+ readonly darkModeSelector: string;
12
+ readonly describeRequestError: UiDescribeRequestError;
13
+ readonly iconClass: UiIconClass;
14
+ readonly locale?: UiLocaleOptionsInterface;
15
+ readonly messages: MaybeRefOrGetter<UiMessagesInterface>;
16
+ }
17
+ export declare class UiVendorPlugin {
18
+ private readonly options;
19
+ constructor(options: UiVendorPluginOptionsInterface);
20
+ private static followLocale;
21
+ install(vue: App): void;
22
+ }
@@ -0,0 +1,57 @@
1
+ import { UI_DESCRIBE_REQUEST_ERROR, UI_ICON_CLASS, UI_MESSAGES } from "./ui-injection.js";
2
+ import { installToastService } from "../components/toast/install-toast-service.js";
3
+ import { installTooltipDirective } from "../components/tooltip/install-tooltip-directive.js";
4
+ import { watch } from "vue";
5
+ import ConfirmationService from "openvue/confirmationservice";
6
+ import Aura from "@openvue/themes/aura";
7
+ import UiVendor from "openvue/config";
8
+ //#region src/plugin/ui-vendor-plugin.ts
9
+ var REQUIRED_OPTIONS = [
10
+ "darkModeSelector",
11
+ "describeRequestError",
12
+ "iconClass",
13
+ "messages"
14
+ ];
15
+ /**
16
+ * Bootstrap-регистрация UI-вендора: тема, toast- и confirm-сервисы, tooltip-директива, а также
17
+ * всё, что пакет получает от приложения: тексты, классы иконок, описание ошибки запроса.
18
+ * Вендор виден по имени только здесь и в обёртках пакета (`AGENTS.md` приложения, «изоляция вендора»).
19
+ * @example
20
+ * new UiVendorPlugin({ darkModeSelector: 'dark-theme', describeRequestError, iconClass, messages })
21
+ */
22
+ var UiVendorPlugin = class UiVendorPlugin {
23
+ options;
24
+ constructor(options) {
25
+ for (const name of REQUIRED_OPTIONS) if (options[name] === void 0) throw new TypeError(`@application-kit/ui: не передана обязательная опция «${name}»`);
26
+ this.options = options;
27
+ }
28
+ static followLocale(vue, locale) {
29
+ const localeReference = locale.reference(vue);
30
+ const vendorConfig = vue.config.globalProperties.$primevue.config;
31
+ if (!localeReference || !vendorConfig.locale) return;
32
+ const baseLocale = { ...vendorConfig.locale };
33
+ const applyVendorLocale = (code) => {
34
+ vendorConfig.locale = {
35
+ ...baseLocale,
36
+ ...locale.overrides[code]
37
+ };
38
+ };
39
+ applyVendorLocale(localeReference.value);
40
+ watch(localeReference, applyVendorLocale, { flush: "sync" });
41
+ }
42
+ install(vue) {
43
+ vue.provide(UI_MESSAGES, this.options.messages);
44
+ vue.provide(UI_ICON_CLASS, this.options.iconClass);
45
+ vue.provide(UI_DESCRIBE_REQUEST_ERROR, this.options.describeRequestError);
46
+ vue.use(UiVendor, { theme: {
47
+ options: { darkModeSelector: `.${this.options.darkModeSelector}` },
48
+ preset: Aura
49
+ } });
50
+ if (this.options.locale) UiVendorPlugin.followLocale(vue, this.options.locale);
51
+ installToastService(vue);
52
+ installTooltipDirective(vue);
53
+ vue.use(ConfirmationService);
54
+ }
55
+ };
56
+ //#endregion
57
+ export { UiVendorPlugin };
@@ -0,0 +1 @@
1
+ export type Severity = 'contrast' | 'danger' | 'info' | 'secondary' | 'success' | 'warn';
@@ -0,0 +1,25 @@
1
+ import type { CursorInterface, FilterInterface, PaginatorInterface, SortingInterface } from '@application-kit/typeorm/listing';
2
+ import type { Ref } from 'vue';
3
+ export interface CursorListingInterface<T> {
4
+ buildPaginator(isGoNext: boolean): PaginatorInterface;
5
+ isLatestRequest(id: number): boolean;
6
+ nextRequestId(): number;
7
+ resetPagination(): void;
8
+ setFilters(newFilters: FilterInterface[]): void;
9
+ setLimit(newLimit: number | string): void;
10
+ setSorting(newSorting: SortingInterface[]): void;
11
+ state: ListingStateInterface<T>;
12
+ }
13
+ export interface ListingStateInterface<T> {
14
+ currentCursor: Ref<null | string>;
15
+ currentPage: Ref<number>;
16
+ cursor: Ref<CursorInterface>;
17
+ filters: Ref<FilterInterface[]>;
18
+ limit: Ref<number>;
19
+ loading: Ref<boolean>;
20
+ records: Ref<T[]>;
21
+ sorting: Ref<SortingInterface[]>;
22
+ totalRecords: Ref<number>;
23
+ }
24
+ export declare const DEFAULT_LISTING_LIMIT = 20;
25
+ export declare const createCursorListing: <T>(limitValue: number) => CursorListingInterface<T>;
@@ -0,0 +1,97 @@
1
+ import { ref } from "vue";
2
+ import { SortOrder } from "@application-kit/typeorm/listing";
3
+ //#region src/store/listing/cursor-listing.ts
4
+ var DEFAULT_LISTING_LIMIT = 20;
5
+ var defaultCurrentPage = 0;
6
+ var defaultTotalRecords = 0;
7
+ var minLimit = 1;
8
+ var createListingState = (limitValue) => {
9
+ const filters = ref([]);
10
+ const records = ref([]);
11
+ const sorting = ref([{
12
+ field: "id",
13
+ order: SortOrder.DESC
14
+ }]);
15
+ return {
16
+ currentCursor: ref(null),
17
+ currentPage: ref(defaultCurrentPage),
18
+ cursor: ref({
19
+ next: null,
20
+ prev: null
21
+ }),
22
+ filters,
23
+ limit: ref(limitValue),
24
+ loading: ref(false),
25
+ records,
26
+ sorting,
27
+ totalRecords: ref(defaultTotalRecords)
28
+ };
29
+ };
30
+ var isSameCollection = (left, right) => JSON.stringify([...left]) === JSON.stringify(right);
31
+ /**
32
+ * Builds the cursor-pagination core shared by every listing store.
33
+ *
34
+ * Курсорная механика одна на все листинги: страничный стор и бесконечный скролл блоков
35
+ * отличаются только тем, что делают с ответом, а не тем, как его просят.
36
+ * Поэтому состояние, сборка пагинатора, порядковый номер запроса и сброс живут здесь,
37
+ * а не копируются во второй стор.
38
+ *
39
+ * **Порядковый номер запроса.** Ответы приходят не в том порядке, в котором их просили.
40
+ * Без счётчика результат медленного УСТАРЕВШЕГО запроса затирает свежий — например, догрузка
41
+ * страницы, начатая до поиска, перезаписывает найденную строку своим списком, и поиск выглядит
42
+ * как «ничего не нашлось» (MR !99).
43
+ *
44
+ * `resetPagination` возвращает листинг на свежую первую страницу: повторный `getRecords(0)` иначе
45
+ * пойдёт по уже полученному `cursor.next` и отдаст следующую страницу вместо первой.
46
+ * @param limitValue - Размер страницы.
47
+ * @returns Состояние листинга и примитивы над ним.
48
+ * @example
49
+ * const listing = createCursorListing<BlockDto>(100)
50
+ */
51
+ var createCursorListing = (limitValue) => {
52
+ const state = createListingState(limitValue);
53
+ let lastRequest = 0;
54
+ const dropCursors = () => {
55
+ state.cursor.value.next = null;
56
+ state.cursor.value.prev = null;
57
+ state.currentCursor.value = null;
58
+ };
59
+ return {
60
+ buildPaginator: (isGoNext) => ({
61
+ limit: state.limit.value,
62
+ pageCursor: (isGoNext ? state.cursor.value.next : state.cursor.value.prev) ?? state.currentCursor.value
63
+ }),
64
+ isLatestRequest: (id) => id === lastRequest,
65
+ nextRequestId: () => {
66
+ lastRequest += 1;
67
+ return lastRequest;
68
+ },
69
+ resetPagination: () => {
70
+ state.currentPage.value = defaultCurrentPage;
71
+ state.currentCursor.value = null;
72
+ state.cursor.value = {
73
+ next: null,
74
+ prev: null
75
+ };
76
+ },
77
+ setFilters: (newFilters) => {
78
+ if (isSameCollection(state.filters.value, newFilters)) return;
79
+ state.filters.value = newFilters;
80
+ dropCursors();
81
+ },
82
+ setLimit: (newLimit) => {
83
+ const normalizedLimit = Number(newLimit);
84
+ if (!Number.isFinite(normalizedLimit) || normalizedLimit < minLimit || normalizedLimit === state.limit.value) return;
85
+ state.limit.value = normalizedLimit;
86
+ dropCursors();
87
+ },
88
+ setSorting: (newSorting) => {
89
+ if (isSameCollection(state.sorting.value, newSorting)) return;
90
+ state.sorting.value = newSorting;
91
+ dropCursors();
92
+ },
93
+ state
94
+ };
95
+ };
96
+ //#endregion
97
+ export { DEFAULT_LISTING_LIMIT, createCursorListing };
@@ -0,0 +1,21 @@
1
+ import type { FilterInterface, ListingRepositoryInterface, SortingInterface } from '@application-kit/typeorm/listing';
2
+ import type { _ExtractActionsFromSetupStore, _ExtractGettersFromSetupStore, _ExtractStateFromSetupStore, Store } from 'pinia';
3
+ import type { ComputedRef, Ref } from 'vue';
4
+ import type { InfiniteListingStoreInterface } from './infinite-listing-store.interface';
5
+ export interface InfiniteListingInterface<T> {
6
+ isLimitReached: ComputedRef<boolean>;
7
+ loadedChunks: ComputedRef<number>;
8
+ loading: Ref<boolean>;
9
+ loadMore(): Promise<void>;
10
+ maxRecords: number;
11
+ records: Ref<T[]>;
12
+ reload(): Promise<void>;
13
+ setFilters(newFilters: FilterInterface[]): void;
14
+ setSorting(newSorting: SortingInterface[]): void;
15
+ }
16
+ export interface InfiniteListingOptionsInterface {
17
+ readonly chunkSize: number;
18
+ readonly maxChunks: number;
19
+ }
20
+ export type InfiniteListingStoreType<T, S extends string> = Store<S, _ExtractStateFromSetupStore<InfiniteListingStoreInterface<T>>, _ExtractGettersFromSetupStore<InfiniteListingStoreInterface<T>>, _ExtractActionsFromSetupStore<InfiniteListingStoreInterface<T>>>;
21
+ export declare const createInfiniteListing: <T>(getRepository: () => ListingRepositoryInterface<T>, options: InfiniteListingOptionsInterface) => InfiniteListingInterface<T>;
@@ -0,0 +1,13 @@
1
+ import type { FilterInterface, SortingInterface } from '@application-kit/typeorm/listing';
2
+ import type { ComputedRef, Ref } from 'vue';
3
+ export interface InfiniteListingStoreInterface<T> {
4
+ isLimitReached: Readonly<ComputedRef<boolean>>;
5
+ loadedChunks: Readonly<ComputedRef<number>>;
6
+ loading: Readonly<Ref<boolean>>;
7
+ loadMore(): Promise<void>;
8
+ maxRecords: number;
9
+ records: Readonly<Ref<T[]>>;
10
+ reload(): Promise<void>;
11
+ setFilters(filters: FilterInterface[]): void;
12
+ setSorting(sorting: SortingInterface[]): void;
13
+ }
@@ -0,0 +1,68 @@
1
+ import { createCursorListing } from "./cursor-listing.js";
2
+ import { computed, ref } from "vue";
3
+ //#region src/store/listing/infinite-listing-store.ts
4
+ /**
5
+ * Cursor listing that appends chunks while scrolling and stops at a hard chunk limit.
6
+ *
7
+ * Отличие от страничного стора ровно одно: ответ ДОБАВЛЯЕТСЯ к строкам, а не замещает их.
8
+ * Всё остальное — состояние, курсоры, порядковый номер запроса — общее (`cursor-listing.ts`).
9
+ *
10
+ * **Предел жёсткий, а не «пока не надоест»**. При окне хранения в несколько суток
11
+ * в таблице блоков миллионы строк, и долистать до нужной нельзя в принципе; поэтому после
12
+ * `maxChunks` порций подгрузка выключается, а интерфейс объясняет, что искать надо поиском.
13
+ * Побочная выгода — в памяти и в DOM не больше `chunkSize × maxChunks` строк, без выгрузки
14
+ * и обратной подгрузки, то есть без целого класса состояний.
15
+ *
16
+ * `totalRecords` не используется вовсе: счётчика страниц нет, считать нечего.
17
+ * @param getRepository - Доступ к репозиторию листинга.
18
+ * @param options - Настройки окна.
19
+ * @param options.chunkSize - Сколько строк в одной порции.
20
+ * @param options.maxChunks - Сколько порций можно набрать до предела.
21
+ * @returns Состояние и действия бесконечного листинга.
22
+ * @example
23
+ * const listing = createInfiniteListing(getRepository, { chunkSize: 100, maxChunks: 10 })
24
+ */
25
+ var createInfiniteListing = (getRepository, options) => {
26
+ const listing = createCursorListing(options.chunkSize);
27
+ const { cursor, filters, loading, records, sorting } = listing.state;
28
+ const chunks = ref(0);
29
+ const loadedChunks = computed(() => chunks.value);
30
+ const isLimitReached = computed(() => chunks.value >= options.maxChunks);
31
+ const isExhausted = () => chunks.value > 0 && cursor.value.next === null;
32
+ const fetchChunk = async (isFirst) => {
33
+ const paginator = listing.buildPaginator(true);
34
+ const request = listing.nextRequestId();
35
+ loading.value = true;
36
+ const data = await getRepository().list(paginator, sorting.value, filters.value);
37
+ if (!listing.isLatestRequest(request)) return;
38
+ loading.value = false;
39
+ records.value = isFirst ? data.data : [...records.value, ...data.data];
40
+ cursor.value = data.cursor;
41
+ chunks.value += 1;
42
+ };
43
+ return {
44
+ isLimitReached,
45
+ loadedChunks,
46
+ loading,
47
+ loadMore: async () => {
48
+ if (loading.value || isLimitReached.value || isExhausted()) return;
49
+ await fetchChunk(false);
50
+ },
51
+ maxRecords: options.chunkSize * options.maxChunks,
52
+ records,
53
+ reload: async () => {
54
+ listing.resetPagination();
55
+ records.value = [];
56
+ chunks.value = 0;
57
+ await fetchChunk(true);
58
+ },
59
+ setFilters: (newFilters) => {
60
+ listing.setFilters(newFilters);
61
+ },
62
+ setSorting: (newSorting) => {
63
+ listing.setSorting(newSorting);
64
+ }
65
+ };
66
+ };
67
+ //#endregion
68
+ export { createInfiniteListing };
@@ -0,0 +1,7 @@
1
+ import type { ListingRepositoryInterface } from '@application-kit/typeorm/listing';
2
+ import type { _ExtractActionsFromSetupStore, _ExtractGettersFromSetupStore, _ExtractStateFromSetupStore, Store } from 'pinia';
3
+ import type { ListingStoreInterface } from './listing-store.interface';
4
+ export type ListingStoreType<T, S extends string> = Store<S, _ExtractStateFromSetupStore<ListingStoreInterface<T>>, _ExtractGettersFromSetupStore<ListingStoreInterface<T>>, _ExtractActionsFromSetupStore<ListingStoreInterface<T>>>;
5
+ export declare const useListingStore: <T, S extends string>(getRepository: () => ListingRepositoryInterface<T>, storeName: S, options?: {
6
+ readonly limit?: number;
7
+ }) => ListingStoreType<T, S>;
@@ -0,0 +1,21 @@
1
+ import type { CursorInterface, FilterInterface, SortingInterface } from '@application-kit/typeorm/listing';
2
+ import type { Ref } from 'vue';
3
+ export interface ListingStoreInterface<T> {
4
+ consumeServerRender(): boolean;
5
+ currentCursor: Readonly<Ref<null | string>>;
6
+ currentPage: Readonly<Ref<number>>;
7
+ cursor: Readonly<Ref<CursorInterface>>;
8
+ error: Readonly<Ref<unknown>>;
9
+ getRecords(page: number): Promise<void>;
10
+ isServerRendered: Readonly<Ref<boolean>>;
11
+ limit: Readonly<Ref<number>>;
12
+ loading: Readonly<Ref<boolean>>;
13
+ markServerRendered(): void;
14
+ records: Readonly<Ref<T[]>>;
15
+ refresh(): Promise<void>;
16
+ resetPagination(): void;
17
+ setFilters(filters: FilterInterface[]): void;
18
+ setLimit(limit: number | string): void;
19
+ setSorting(sorting: SortingInterface[]): void;
20
+ totalRecords: Readonly<Ref<number>>;
21
+ }
@@ -0,0 +1,114 @@
1
+ import { createCursorListing } from "./cursor-listing.js";
2
+ import { ref } from "vue";
3
+ import { defineStore } from "pinia";
4
+ //#region src/store/listing/listing-store.ts
5
+ var createRequestRunner = (listing, error) => {
6
+ const { loading } = listing.state;
7
+ const settle = (request, apply) => {
8
+ if (!listing.isLatestRequest(request)) return;
9
+ loading.value = false;
10
+ apply();
11
+ };
12
+ return async (fetchPage, applyPage) => {
13
+ const request = listing.nextRequestId();
14
+ loading.value = true;
15
+ error.value = null;
16
+ try {
17
+ const data = await fetchPage();
18
+ settle(request, () => {
19
+ applyPage(data);
20
+ });
21
+ } catch (error_) {
22
+ settle(request, () => {
23
+ error.value = error_;
24
+ });
25
+ }
26
+ };
27
+ };
28
+ /**
29
+ * Page-by-page listing store: one page in memory, navigation through the cursor both ways.
30
+ *
31
+ * Курсорная механика (состояние, пагинатор, порядковый номер запроса, сброс) живёт в
32
+ * `cursor-listing.ts` и делится с бесконечным скроллом блоков. Здесь остаётся
33
+ * только то, чем страничный режим отличается: ответ ЗАМЕЩАЕТ строки, а не добавляет их.
34
+ * @param getRepository - Доступ к репозиторию листинга.
35
+ * @param storeName - Имя pinia-стора.
36
+ * @param options - Настройки стора.
37
+ * @param options.limit - Размер страницы, если он отличается от дефолтного.
38
+ * @returns Pinia-стор листинга.
39
+ * @example
40
+ * const listing = useListingStore<Block, 'blocks'>(getRepository, 'blocks')
41
+ */
42
+ var useListingStore = (getRepository, storeName, options) => defineStore(storeName, () => {
43
+ const listing = createCursorListing(options?.limit ?? 20);
44
+ const { currentCursor, currentPage, cursor, filters, limit, loading, records, sorting, totalRecords } = listing.state;
45
+ const error = ref(null);
46
+ const isServerRendered = ref(false);
47
+ let shownPageCursor = null;
48
+ const applyListingResult = (data, isGoNext, pageCursor) => {
49
+ shownPageCursor = pageCursor;
50
+ records.value = data.data;
51
+ totalRecords.value = data.totalRecords;
52
+ currentCursor.value = isGoNext ? cursor.value.next ?? data.cursor.next : cursor.value.prev ?? data.cursor.prev;
53
+ cursor.value = data.cursor;
54
+ };
55
+ const runRequest = createRequestRunner(listing, error);
56
+ const getRecords = async (page) => {
57
+ const isGoNext = page >= currentPage.value;
58
+ const paginator = listing.buildPaginator(isGoNext);
59
+ currentPage.value = page;
60
+ await runRequest(async () => getRepository().list(paginator, sorting.value, filters.value), (data) => {
61
+ applyListingResult(data, isGoNext, paginator.pageCursor);
62
+ });
63
+ };
64
+ const refresh = async () => {
65
+ const pageCursor = shownPageCursor;
66
+ await runRequest(async () => getRepository().list({
67
+ limit: limit.value,
68
+ pageCursor
69
+ }, sorting.value, filters.value), (data) => {
70
+ records.value = data.data;
71
+ totalRecords.value = data.totalRecords;
72
+ cursor.value = data.cursor;
73
+ });
74
+ };
75
+ return {
76
+ consumeServerRender: () => {
77
+ const wasServerRendered = isServerRendered.value;
78
+ isServerRendered.value = false;
79
+ return wasServerRendered;
80
+ },
81
+ currentCursor,
82
+ currentPage,
83
+ cursor,
84
+ error,
85
+ getRecords,
86
+ isServerRendered,
87
+ limit,
88
+ loading,
89
+ markServerRendered: () => {
90
+ isServerRendered.value = true;
91
+ },
92
+ records,
93
+ refresh,
94
+ resetPagination: () => {
95
+ shownPageCursor = null;
96
+ listing.resetPagination();
97
+ },
98
+ setFilters: (newFilters) => {
99
+ shownPageCursor = null;
100
+ listing.setFilters(newFilters);
101
+ },
102
+ setLimit: (newLimit) => {
103
+ shownPageCursor = null;
104
+ listing.setLimit(newLimit);
105
+ },
106
+ setSorting: (newSorting) => {
107
+ shownPageCursor = null;
108
+ listing.setSorting(newSorting);
109
+ },
110
+ totalRecords
111
+ };
112
+ })();
113
+ //#endregion
114
+ export { useListingStore };
package/dist/style.css ADDED
@@ -0,0 +1,158 @@
1
+ @charset "UTF-8";
2
+ .date-range-filter[data-v-30d77bcb] {
3
+ display: flex;
4
+ margin-bottom: 0.75rem;
5
+ }
6
+ .date-range-filter-input[data-v-30d77bcb] {
7
+ /* Определённая ширина (не width:100%): применяется и в блочном контексте, и внутри flex-тулбара
8
+ (Addresses), где width:100% схлопывался бы до контента и max-width визуально не срабатывал. */
9
+ width: 15rem;
10
+ max-width: 100%;
11
+ }
12
+ .date-range-filter-input[data-v-30d77bcb] input {
13
+ width: 100%;
14
+ }
15
+ .date-range-filter-presets[data-v-30d77bcb] {
16
+ display: flex;
17
+ flex-wrap: wrap;
18
+ gap: 0.25rem;
19
+ justify-content: center;
20
+ padding: 0.5rem;
21
+ }
22
+
23
+ /* Было absolute bottom-0 w-screen прежней utility-библиотеки — значения перенесены 1:1. */
24
+ .gradient-background[data-v-7d3d1707] {
25
+ position: absolute;
26
+ bottom: 0;
27
+ width: 100vw;
28
+ }
29
+ /* Значения перенесены 1:1 из снесённой utility-библиотеки (шкала: 1 = 0.25rem, 2 = 0.5rem,
30
+ 4 = 1.5rem, 5 = 2rem; lg = min-width 992px; w-6 = 50%). */
31
+ .error-overlay[data-v-6de1c932] {
32
+ position: fixed;
33
+ top: 0;
34
+ left: 0;
35
+ width: 100%;
36
+ height: 100%;
37
+ color: var(--ui-primary-color);
38
+ background-color: var(--ui-primary-contrast-color);
39
+ opacity: 0.7;
40
+ }
41
+ .error-viewport[data-v-6de1c932] {
42
+ position: relative;
43
+ display: flex;
44
+ align-items: center;
45
+ justify-content: center;
46
+ }
47
+ .error-column[data-v-6de1c932] {
48
+ display: flex;
49
+ align-items: center;
50
+ justify-content: center;
51
+ width: 100%;
52
+ height: 100vh;
53
+ }
54
+ @media screen and (width >= 992px) {
55
+ .error-column[data-v-6de1c932] {
56
+ width: 50%;
57
+ }
58
+ }
59
+ .error-panel[data-v-6de1c932] {
60
+ z-index: 5;
61
+ width: 50%;
62
+ }
63
+ .error-code[data-v-6de1c932] {
64
+ padding: 0.25rem 0.5rem;
65
+ font-size: 1.125rem;
66
+ color: light-dark(var(--ui-surface-0), var(--ui-surface-900));
67
+ border-radius: 0.375rem;
68
+ }
69
+ .error-title[data-v-6de1c932] {
70
+ margin: 1.5rem 0;
71
+ font-size: 3rem;
72
+ font-weight: 300;
73
+ }
74
+ .error-divider[data-v-6de1c932] {
75
+ width: 10rem;
76
+ }
77
+ .error-message[data-v-6de1c932] {
78
+ margin-top: 1.5rem;
79
+ font-size: 1.125rem;
80
+ color: var(--ui-text-color);
81
+ }
82
+ .error-action[data-v-6de1c932] {
83
+ margin-top: 2rem;
84
+ border: none;
85
+ }
86
+ .error-support[data-v-6de1c932] {
87
+ display: flex;
88
+ flex-wrap: wrap;
89
+ gap: 0.5rem;
90
+ align-items: center;
91
+ margin-top: 1.5rem;
92
+ }
93
+ .error-support-label[data-v-6de1c932] {
94
+ width: 100%;
95
+ font-size: 0.875rem;
96
+ color: var(--ui-text-muted-color);
97
+ }
98
+
99
+ /* Идентификатор моноширинным и с переносом: это 32 hex-символа, и на узком экране он обязан
100
+ переноситься, а не расталкивать панель. */
101
+ .error-support-id[data-v-6de1c932] {
102
+ font-family: monospace;
103
+ font-size: 1rem;
104
+ color: var(--ui-text-color);
105
+ word-break: break-all;
106
+ }[class*=icon--32][data-v-c70111ef] {
107
+ width: 32px;
108
+ height: 32px;
109
+ }.tooltip__text[data-v-405629ae] {
110
+ width: fit-content;
111
+ padding: 0.75rem;
112
+ margin: 0.25rem;
113
+ white-space: nowrap;
114
+ }.icon-cell[data-v-444fb937] {
115
+ display: inline-block;
116
+ vertical-align: middle;
117
+ }.empty-layout__corner[data-v-8ec8eae8] {
118
+ position: fixed;
119
+ top: 1rem;
120
+ right: 1rem;
121
+ z-index: 100;
122
+ }.data-table-frame__error[data-v-dac8d846] {
123
+ margin-bottom: 0.75rem;
124
+ }
125
+ .data-table__body-cell[data-v-dac8d846] {
126
+ padding: 0.75rem;
127
+ vertical-align: middle;
128
+ }
129
+ .data-table__header-cell[data-v-dac8d846] {
130
+ padding: 0.75rem;
131
+ vertical-align: middle;
132
+ }
133
+ .data-table__filter-row-item[data-v-dac8d846] {
134
+ padding: 0.75rem 1.25rem;
135
+ }
136
+ .data-table__paginator[data-v-dac8d846] {
137
+ padding: 1rem 0 1rem 1rem;
138
+ }
139
+ .data-table__paginator-row-per-page-input[data-v-dac8d846] {
140
+ padding: 1rem 0 1rem 1rem;
141
+ }
142
+ .data-table__paginator-row-per-page-item[data-v-dac8d846] {
143
+ padding: 0.75rem 1.25rem;
144
+ }.infinite-data-table__more[data-v-2c01be04] {
145
+ padding: 0.75rem;
146
+ color: var(--ui-text-muted-color);
147
+ text-align: center;
148
+ }
149
+ .infinite-data-table__sentinel[data-v-2c01be04] {
150
+ height: 1px;
151
+ }.time-lag[data-v-6caca029] {
152
+ color: green;
153
+ }
154
+ .time-lag--alert[data-v-6caca029] {
155
+ color: red;
156
+ }.p-toast.p-toast-top-right, .p-toast.p-toast-top-left, .p-toast.p-toast-top-center {
157
+ top: 85px;
158
+ }/*$vite$:1*/