@pgcorp/ui-kit 0.3.0 → 0.3.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 (32) hide show
  1. package/docs/public-api.md +8 -0
  2. package/package.json +1 -1
  3. package/src/components/layout/SAppShell.vue +3 -2
  4. package/src/components/shared/complex/STree.vue +29 -4
  5. package/src/components/shared/complex/treeAdapter.ts +2 -1
  6. package/src/components/shared/complex/types.ts +1 -12
  7. package/src/components/shared/containers/SLeftSidebar.vue +7 -2
  8. package/src/components/shared/containers/SPopover.vue +2 -1
  9. package/src/components/shared/containers/SSidebarGroup.vue +10 -5
  10. package/src/components/shared/containers/sidebar.ts +2 -1
  11. package/src/components/shared/controls/SButton.vue +10 -5
  12. package/src/components/shared/controls/SInteractiveSurface.vue +2 -1
  13. package/src/components/shared/controls/SLink.ts +1 -17
  14. package/src/components/shared/controls/SLink.vue +26 -2
  15. package/src/components/shared/data-display/SDocBlock.vue +10 -10
  16. package/src/components/shared/data-display/SLinkedSystemsList.vue +3 -3
  17. package/src/components/shared/data-display/STable.vue +44 -12
  18. package/src/components/shared/data-display/STooltip.vue +2 -1
  19. package/src/components/shared/data-display/SVirtualList.ts +1 -16
  20. package/src/components/shared/data-display/SVirtualList.vue +18 -2
  21. package/src/components/shared/data-display/json.ts +1 -1
  22. package/src/components/shared/data-display/table.ts +1 -29
  23. package/src/components/shared/database/SDataGrid.vue +29 -8
  24. package/src/components/shared/database/SSqlEditor.vue +3 -2
  25. package/src/components/shared/database/dataGrid.ts +1 -17
  26. package/src/components/shared/navigation/STabs.vue +2 -1
  27. package/src/composables/uiPreferencesReset.ts +2 -1
  28. package/src/composables/useClipboard.ts +2 -1
  29. package/src/composables/useSidebarPanelState.ts +3 -2
  30. package/src/internal/es2020.ts +50 -0
  31. package/src/internal/inlineTokenEditorContract.ts +4 -3
  32. package/src/internal/pointerInteractionLease.ts +2 -1
@@ -18,6 +18,10 @@
18
18
 
19
19
  Компонентный contract выражается через typed props, slots, events и exposed
20
20
  methods. Внутренний DOM, CSS-классы и private modules не являются API.
21
+ Публичные source-SFC объявляют runtime props и events локально: consumer Vue
22
+ compiler регистрирует их без межфайлового filesystem type resolver.
23
+ Публикуемый production-source компилируется с `target/lib: ES2020`, совпадающим
24
+ с baseline `@vue/tsconfig/tsconfig.dom.json`, и не требует скрытых полифиллов.
21
25
 
22
26
  ## English
23
27
 
@@ -37,3 +41,7 @@ Primary groups:
37
41
 
38
42
  Component contracts are expressed through typed props, slots, events, and exposed
39
43
  methods. Internal DOM, CSS classes, and private modules are not API.
44
+ Public source SFCs declare runtime props and events locally, so the consumer Vue
45
+ compiler registers them without a cross-file filesystem type resolver.
46
+ Published production source compiles with the `target/lib: ES2020` baseline from
47
+ `@vue/tsconfig/tsconfig.dom.json` and does not require implicit polyfills.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pgcorp/ui-kit",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
4
  "description": "Typed Vue 3 design system with accessible components, semantic themes, and workbench patterns.",
5
5
  "type": "module",
6
6
  "types": "./index.ts",
@@ -1,6 +1,7 @@
1
1
  <script setup lang="ts">
2
2
  import { computed, getCurrentInstance, useSlots } from 'vue'
3
3
  import type { RouteLocationRaw } from 'vue-router'
4
+ import { hasOwn } from '../../internal/es2020'
4
5
  import { useOwnedAttrs } from '../../internal/ownedAttrs'
5
6
  import { validateNonEmptyString } from '../../internal/runtimeContract'
6
7
  import SBadge from '../shared/data-display/SBadge.vue'
@@ -98,8 +99,8 @@ function toKebabCase(value: string): string {
98
99
  function hasExplicitProp(propName: string): boolean {
99
100
  const vnodeProps = componentInstance.vnode.props
100
101
  return vnodeProps !== null && (
101
- Object.hasOwn(vnodeProps, propName)
102
- || Object.hasOwn(vnodeProps, toKebabCase(propName))
102
+ hasOwn(vnodeProps, propName)
103
+ || hasOwn(vnodeProps, toKebabCase(propName))
103
104
  )
104
105
  }
105
106
 
@@ -32,8 +32,36 @@
32
32
  </ul>
33
33
  </template>
34
34
 
35
+ <script lang="ts">
36
+ import type {
37
+ SelectionKey,
38
+ TreeActionsLayout,
39
+ TreeDensity,
40
+ TreeExpandControl,
41
+ TreeModel,
42
+ TreeRowVariant,
43
+ TreeSelection,
44
+ TreeSelectionRail,
45
+ } from './types'
46
+
47
+ /** Публичный контракт дерева принадлежит source-SFC runtime. / The public tree contract is owned by the source-SFC runtime. */
48
+ export interface STreeProps<T extends object, K extends SelectionKey = SelectionKey> {
49
+ nodes: readonly T[]
50
+ model: TreeModel<T, K>
51
+ label: string
52
+ selection: TreeSelection<K>
53
+ expanded?: ReadonlySet<K>
54
+ density?: TreeDensity
55
+ actionsLayout?: TreeActionsLayout
56
+ expandControl?: TreeExpandControl
57
+ selectionRail?: TreeSelectionRail
58
+ rowVariant?: TreeRowVariant
59
+ }
60
+ </script>
61
+
35
62
  <script setup lang="ts" generic="T extends object, K extends SelectionKey">
36
63
  import { computed, nextTick, provide, ref, watch } from 'vue'
64
+ import { lastItem } from '../../../internal/es2020'
37
65
  import { useOwnedAttrs } from '../../../internal/ownedAttrs'
38
66
  import { assertSelectionKey, selectionKeyToken } from '../_internal/useSelectionRoving'
39
67
  import STreeNode from '../_internal/STreeNode.vue'
@@ -46,12 +74,9 @@ import {
46
74
  import { validateTreeModel } from './treeAdapter'
47
75
  import type {
48
76
  STreeNodeContentScope,
49
- STreeProps,
50
- SelectionKey,
51
77
  TreeActivationOrigin,
52
78
  TreeNodeActivationEvent,
53
79
  TreeNodeEvent,
54
- TreeSelection,
55
80
  } from './types'
56
81
 
57
82
  defineOptions({ inheritAttrs: false })
@@ -172,7 +197,7 @@ function handleTreeItemKeydown(item: RegisteredTreeItem<T, K>, event: KeyboardEv
172
197
  }
173
198
  if (event.key === 'End') {
174
199
  event.preventDefault()
175
- void focusItem(items.at(-1))
200
+ void focusItem(lastItem(items))
176
201
  return
177
202
  }
178
203
  if (event.key === 'ArrowRight' && item.hasChildren()) {
@@ -5,6 +5,7 @@ import {
5
5
  type SelectionIdentity,
6
6
  type SelectionKey,
7
7
  } from '../_internal/useSelectionRoving'
8
+ import { withErrorCause } from '../../../internal/es2020'
8
9
  import type { TreeModel } from './types'
9
10
 
10
11
  export interface FlatTreeAdapter<T extends object, K extends SelectionKey> {
@@ -130,7 +131,7 @@ function readModelValue<R>(
130
131
  try {
131
132
  return reader()
132
133
  } catch (error) {
133
- throw new TypeError(`STree: model.${field} failed at ${coordinate}`, { cause: error })
134
+ throw withErrorCause(new TypeError(`STree: model.${field} failed at ${coordinate}`), error)
134
135
  }
135
136
  }
136
137
 
@@ -51,18 +51,7 @@ export type TreeExpandControl = 'button' | 'row'
51
51
  export type TreeSelectionRail = 'hidden' | 'visible'
52
52
  export type TreeRowVariant = 'default' | 'table'
53
53
 
54
- export interface STreeProps<T extends object, K extends SelectionKey = SelectionKey> {
55
- nodes: readonly T[]
56
- model: TreeModel<T, K>
57
- label: string
58
- selection: TreeSelection<K>
59
- expanded?: ReadonlySet<K>
60
- density?: TreeDensity
61
- actionsLayout?: TreeActionsLayout
62
- expandControl?: TreeExpandControl
63
- selectionRail?: TreeSelectionRail
64
- rowVariant?: TreeRowVariant
65
- }
54
+ export type { STreeProps } from './STree.vue'
66
55
 
67
56
  export interface TreeNodeEvent<T extends object, K extends SelectionKey = SelectionKey> {
68
57
  node: T
@@ -33,7 +33,7 @@ import { type FunctionalComponent, type HTMLAttributes, type VNodeProps } from '
33
33
  import SPanel from './SPanel.vue';
34
34
  import SSidebarGroup from './SSidebarGroup.vue';
35
35
  import type { SAsyncStateContract } from '../feedback/SAsyncState.vue';
36
- import type { SidebarGroupEmits, SidebarSectionsState } from './sidebar';
36
+ import type { SidebarSectionsState } from './sidebar';
37
37
  import { useOwnedAttrs } from '../../../internal/ownedAttrs';
38
38
 
39
39
  defineOptions({ inheritAttrs: false });
@@ -48,6 +48,11 @@ export interface Props {
48
48
  scrollBehavior?: 'auto' | 'body' | 'self' | 'child';
49
49
  }
50
50
 
51
+ interface Emits {
52
+ 'update-sections': [state: SidebarSectionsState];
53
+ 'toggle-section': [key: string];
54
+ }
55
+
51
56
  withDefaults(defineProps<Props>(), {
52
57
  title: undefined,
53
58
  icon: undefined,
@@ -56,7 +61,7 @@ withDefaults(defineProps<Props>(), {
56
61
  scrollBehavior: 'child',
57
62
  });
58
63
 
59
- const emit = defineEmits<SidebarGroupEmits>();
64
+ const emit = defineEmits<Emits>();
60
65
  const ownedAttrs = useOwnedAttrs({ component: 'SLeftSidebar', owner: 'left-sidebar root' });
61
66
  </script>
62
67
 
@@ -93,6 +93,7 @@ import SButton, {
93
93
  } from '../controls/SButton.vue';
94
94
  import { useFloatingPosition, type FloatingPlacement } from '../../../composables/useFloatingPosition';
95
95
  import { findRelativeTabTarget, getFocusableElements } from '../../../internal/focusNavigation';
96
+ import { lastItem } from '../../../internal/es2020';
96
97
  import { resolveInteractiveElement } from '../../../internal/interactiveElement';
97
98
  import { registerLayer, type LayerRegistration } from '../../../internal/layerStack';
98
99
  import { useOwnedAttrs } from '../../../internal/ownedAttrs';
@@ -495,7 +496,7 @@ function handlePanelTab(event: KeyboardEvent): void {
495
496
  const focusable = getFocusableElements(panel);
496
497
  const active = document.activeElement;
497
498
  const first = focusable[0];
498
- const last = focusable.at(-1);
499
+ const last = lastItem(focusable);
499
500
  const leavesPanel = focusable.length === 0
500
501
  || (event.shiftKey ? active === panel || active === first : active === last);
501
502
  if (!leavesPanel) return;
@@ -16,6 +16,7 @@ import {
16
16
  } from 'vue';
17
17
 
18
18
  import { useOwnedAttrs } from '../../../internal/ownedAttrs';
19
+ import { hasOwn, lastItem } from '../../../internal/es2020';
19
20
  import {
20
21
  sidebarGroupContextKey,
21
22
  type RegisteredSidebarSection,
@@ -27,7 +28,6 @@ import {
27
28
  createSidebarSectionsTransition,
28
29
  sidebarSectionsStateEqual,
29
30
  type SectionState,
30
- type SidebarGroupEmits,
31
31
  type SidebarSectionReplacement,
32
32
  type SidebarSectionsState,
33
33
  } from './sidebar';
@@ -38,7 +38,12 @@ const props = defineProps<{
38
38
  sectionsState: SidebarSectionsState;
39
39
  }>();
40
40
 
41
- const emit = defineEmits<SidebarGroupEmits>();
41
+ interface Emits {
42
+ 'update-sections': [state: SidebarSectionsState];
43
+ 'toggle-section': [key: string];
44
+ }
45
+
46
+ const emit = defineEmits<Emits>();
42
47
 
43
48
  const ownedAttrs = useOwnedAttrs({ component: 'SSidebarGroup', owner: 'sidebar group root' });
44
49
  const groupRef = ref<HTMLDivElement | null>(null);
@@ -147,7 +152,7 @@ function fitOpenSectionSizes(
147
152
 
148
153
  let delta = targetSize - [...sizes.values()].reduce((sum, size) => sum + size, 0);
149
154
  if (delta > 0) {
150
- const growTarget = flexible ?? openSections.at(-1);
155
+ const growTarget = flexible ?? lastItem(openSections);
151
156
  if (growTarget) sizes.set(growTarget.key, sizes.get(growTarget.key)! + delta);
152
157
  delta = 0;
153
158
  }
@@ -200,7 +205,7 @@ function registerSection(registration: SidebarSectionRegistration): () => void {
200
205
  if (!(registration.element instanceof HTMLElement)) {
201
206
  throw new TypeError('SSidebarGroup: registered section element must be an HTMLElement');
202
207
  }
203
- if (!Object.hasOwn(currentState(), registration.key)) {
208
+ if (!hasOwn(currentState(), registration.key)) {
204
209
  throw new Error(`SSidebarGroup: registered section '${registration.key}' has no controlled state`);
205
210
  }
206
211
  if (registrations.value.some((current) => current.key === registration.key)) {
@@ -371,7 +376,7 @@ function resizeSections(topKey: string, bottomKey: string, requestedTopSize: num
371
376
  );
372
377
  const delta = requestedTopSize - currentUpperSize;
373
378
  const shrinking = delta > 0 ? [...lower] : [...upper].reverse();
374
- const growing = delta > 0 ? upper.at(-1) : lower[0];
379
+ const growing = delta > 0 ? lastItem(upper) : lower[0];
375
380
  let transferable = Math.abs(delta);
376
381
  transferable = Math.min(
377
382
  transferable,
@@ -163,7 +163,7 @@ export function createSidebarSectionsTransition(
163
163
  if (typeof replacement.key !== 'string' || replacement.key.trim().length === 0) {
164
164
  throw new TypeError(`SSidebarGroup: replacements[${index}].key must be a non-empty string`);
165
165
  }
166
- if (!Object.hasOwn(current, replacement.key)) {
166
+ if (!hasOwn(current, replacement.key)) {
167
167
  throw new Error(`SSidebarGroup: cannot replace unknown section '${replacement.key}'`);
168
168
  }
169
169
  if (replacementsByKey.has(replacement.key)) {
@@ -204,3 +204,4 @@ export function sidebarSectionsStateEqual(
204
204
  && leftSection.minSize === rightSection.minSize;
205
205
  });
206
206
  }
207
+ import { hasOwn } from '../../../internal/es2020';
@@ -72,8 +72,8 @@ import { useTreeActionRegistration } from '../_internal/treeRuntime'
72
72
  import SBadge, { type BadgeSeverity } from '../data-display/SBadge.vue'
73
73
  import SProgressIndicator, { type SProgressIndicatorSize } from '../data-display/SProgressIndicator.vue'
74
74
  import type {
75
- HrefLinkDestination,
76
- RouterLinkDestination,
75
+ LinkBrowsingTarget,
76
+ LinkRouterTarget,
77
77
  } from './SLink'
78
78
 
79
79
  defineOptions({ inheritAttrs: false })
@@ -132,9 +132,14 @@ export type SButtonActionProps = SButtonPresentationProps & {
132
132
  type?: SButtonNativeType
133
133
  }
134
134
 
135
- export type SButtonNavigationProps = SButtonPresentationProps & (
136
- | (HrefLinkDestination & { type?: never })
137
- | (RouterLinkDestination & { type?: never })
135
+ interface SButtonNavigationOptions {
136
+ target?: LinkBrowsingTarget
137
+ rel?: string
138
+ }
139
+
140
+ export type SButtonNavigationProps = SButtonPresentationProps & SButtonNavigationOptions & (
141
+ | { href: string; to?: never; type?: never }
142
+ | { href?: never; to: LinkRouterTarget; type?: never }
138
143
  )
139
144
 
140
145
  /** Strict action/navigation XOR with a shared button presentation. */
@@ -2,6 +2,7 @@
2
2
  import { computed, watchEffect, type VNodeChild } from 'vue'
3
3
  import { RouterLink, type RouteLocationRaw } from 'vue-router'
4
4
  import type { STooltipTriggerBinding } from '../data-display/STooltip.vue'
5
+ import { hasOwn } from '../../../internal/es2020'
5
6
  import { resolveLinkTarget } from '../../../internal/linkTarget'
6
7
  import { useOwnedAttrs } from '../../../internal/ownedAttrs'
7
8
  import {
@@ -195,7 +196,7 @@ const validatedTooltipTrigger = computed<STooltipTriggerBinding | undefined>(()
195
196
  }
196
197
  const record = candidate as Record<string, unknown>
197
198
  const unexpected = Object.keys(record).filter((key) => !TOOLTIP_TRIGGER_KEYS.includes(key as typeof TOOLTIP_TRIGGER_KEYS[number]))
198
- const missing = TOOLTIP_TRIGGER_KEYS.filter((key) => !Object.hasOwn(record, key))
199
+ const missing = TOOLTIP_TRIGGER_KEYS.filter((key) => !hasOwn(record, key))
199
200
  if (unexpected.length > 0 || missing.length > 0) {
200
201
  throw new Error(`SInteractiveSurface: tooltipTrigger имеет недопустимый набор ключей; missing=${missing.join(',')}; unexpected=${unexpected.join(',')}. / SInteractiveSurface: tooltipTrigger has an invalid key set; missing=${missing.join(',')}; unexpected=${unexpected.join(',')}.`)
201
202
  }
@@ -1,4 +1,3 @@
1
- import type { Component } from 'vue'
2
1
  import type {
3
2
  HistoryState,
4
3
  LocationQueryRaw,
@@ -67,19 +66,4 @@ export type RouterLinkDestination = LinkDestinationOptions & {
67
66
  /** Строгий XOR-контракт обычного destination. / Strict XOR contract for an ordinary destination. */
68
67
  export type LinkDestination = HrefLinkDestination | RouterLinkDestination
69
68
 
70
- /** Canonical presentation/state API owned by SLink. */
71
- export interface LinkPresentationProps {
72
- readonly variant?: 'default' | 'subtle' | 'brand' | 'navigation' | 'bottom-navigation' | 'document'
73
- readonly external?: boolean
74
- readonly active?: boolean
75
- readonly disabled?: boolean
76
- readonly width?: 'fit' | 'full'
77
- readonly size?: 'inherit' | 'sm' | 'md'
78
- readonly underline?: 'hover' | 'always' | 'none'
79
- readonly wrap?: 'normal' | 'anywhere'
80
- readonly contentKind?: 'text' | 'identifier'
81
- readonly leadingIcon?: Component
82
- }
83
-
84
- /** Полный discriminated public props contract SLink. / Complete discriminated public SLink props contract. */
85
- export type SLinkProps = LinkDestination & LinkPresentationProps
69
+ export type { LinkPresentationProps, Props as SLinkProps } from './SLink.vue'
@@ -1,5 +1,6 @@
1
1
  <script setup lang="ts">
2
2
  import {
3
+ type Component,
3
4
  computed,
4
5
  type VNodeChild,
5
6
  } from 'vue'
@@ -12,13 +13,36 @@ import {
12
13
  type PassiveContentContract,
13
14
  } from '../../../internal/passiveContentContract'
14
15
  import type {
15
- SLinkProps,
16
+ LinkBrowsingTarget,
17
+ LinkRouterTarget,
16
18
  } from './SLink'
17
19
 
18
20
  defineOptions({ inheritAttrs: false })
19
21
 
22
+ /** Canonical presentation/state API owned by SLink. */
23
+ export interface LinkPresentationProps {
24
+ readonly variant?: 'default' | 'subtle' | 'brand' | 'navigation' | 'bottom-navigation' | 'document'
25
+ readonly external?: boolean
26
+ readonly active?: boolean
27
+ readonly disabled?: boolean
28
+ readonly width?: 'fit' | 'full'
29
+ readonly size?: 'inherit' | 'sm' | 'md'
30
+ readonly underline?: 'hover' | 'always' | 'none'
31
+ readonly wrap?: 'normal' | 'anywhere'
32
+ readonly contentKind?: 'text' | 'identifier'
33
+ readonly leadingIcon?: Component
34
+ }
35
+
36
+ interface LinkDestinationOptions {
37
+ readonly target?: LinkBrowsingTarget
38
+ readonly rel?: string
39
+ }
40
+
20
41
  /** Exact href/to XOR plus the canonical link presentation API. */
21
- export type Props = SLinkProps
42
+ export type Props = LinkPresentationProps & LinkDestinationOptions & (
43
+ | { readonly href: string; readonly to?: never }
44
+ | { readonly href?: never; readonly to: LinkRouterTarget }
45
+ )
22
46
 
23
47
  const props = withDefaults(defineProps<Props>(), {
24
48
  href: undefined,
@@ -88,20 +88,20 @@ useInteractiveLeafRegistration({ owner: 'SDocBlock' });
88
88
 
89
89
  function escapeHighlightedCode(code: string): string {
90
90
  return code
91
- .replaceAll('&', '&amp;')
92
- .replaceAll('<', '&lt;')
93
- .replaceAll('>', '&gt;')
94
- .replaceAll('"', '&quot;')
95
- .replaceAll("'", '&#39;');
91
+ .replace(/&/gu, '&amp;')
92
+ .replace(/</gu, '&lt;')
93
+ .replace(/>/gu, '&gt;')
94
+ .replace(/"/gu, '&quot;')
95
+ .replace(/'/gu, '&#39;');
96
96
  }
97
97
 
98
98
  function escapeHtmlAttribute(value: string): string {
99
99
  return value
100
- .replaceAll('&', '&amp;')
101
- .replaceAll('<', '&lt;')
102
- .replaceAll('>', '&gt;')
103
- .replaceAll('"', '&quot;')
104
- .replaceAll("'", '&#39;');
100
+ .replace(/&/gu, '&amp;')
101
+ .replace(/</gu, '&lt;')
102
+ .replace(/>/gu, '&gt;')
103
+ .replace(/"/gu, '&quot;')
104
+ .replace(/'/gu, '&#39;');
105
105
  }
106
106
 
107
107
  const markdownRenderer = new marked.Renderer();
@@ -113,6 +113,7 @@ export interface Slots<T extends LinkedSystemListItem = LinkedSystemListItem> {
113
113
 
114
114
  <script setup lang="ts" generic="T extends LinkedSystemListItem = LinkedSystemListItem">
115
115
  import { computed, watchEffect } from 'vue'
116
+ import { withErrorCause } from '../../../internal/es2020'
116
117
  import { useOwnedAttrs } from '../../../internal/ownedAttrs'
117
118
  import {
118
119
  validateBoolean,
@@ -169,11 +170,10 @@ function validateExternalUrl(value: unknown, index: number): void {
169
170
  try {
170
171
  parsedUrl = new URL(externalUrl)
171
172
  } catch (error: unknown) {
172
- throw new TypeError(
173
+ throw withErrorCause(new TypeError(
173
174
  `SLinkedSystemsList: ${coordinate} должен быть абсолютным HTTP(S) URL или null; получено ${JSON.stringify(externalUrl)}. `
174
175
  + `/ SLinkedSystemsList: ${coordinate} must be an absolute HTTP(S) URL or null; received ${JSON.stringify(externalUrl)}.`,
175
- { cause: error },
176
- )
176
+ ), error)
177
177
  }
178
178
  if (
179
179
  (parsedUrl.protocol === 'http:' || parsedUrl.protocol === 'https:')
@@ -104,6 +104,19 @@
104
104
  </template>
105
105
 
106
106
  <script lang="ts">
107
+ import type { ScrollableViewportMaxBlockSize } from '../../../internal/semanticSizing'
108
+ import type {
109
+ STableCellState,
110
+ STableColumn,
111
+ STableColumnContent,
112
+ STableDensity,
113
+ STableMinWidth,
114
+ STableRowKey,
115
+ STableRowState,
116
+ STableSort,
117
+ STableSurface,
118
+ } from './table'
119
+
107
120
  export type {
108
121
  STableAlignment,
109
122
  STableCellState,
@@ -111,18 +124,47 @@ export type {
111
124
  STableColumnContent,
112
125
  STableDensity,
113
126
  STableMinWidth,
114
- STableProps,
115
127
  STableRowKey,
116
128
  STableRowState,
117
129
  STableSort,
118
130
  STableSortDirection,
119
131
  STableSurface,
120
132
  } from './table'
133
+
134
+ /** Публичный generic-контракт таблицы принадлежит source-SFC runtime. / The public generic table contract is owned by the source-SFC runtime. */
135
+ export interface STableProps<
136
+ Row extends object,
137
+ RowKey extends STableRowKey,
138
+ Field extends string,
139
+ > {
140
+ columns: readonly STableColumn<Row, Field>[]
141
+ data: readonly Row[]
142
+ getRowKey: (row: Row) => RowKey
143
+ loading?: boolean
144
+ loadingMessage?: string
145
+ errorMessage?: string
146
+ emptyMessage?: string
147
+ selectedRowKey?: RowKey
148
+ sort?: STableSort<Field> | null
149
+ maxHeight?: ScrollableViewportMaxBlockSize
150
+ density?: STableDensity
151
+ surface?: STableSurface
152
+ minWidth?: STableMinWidth
153
+ rowState?: (row: Row) => STableRowState
154
+ cellState?: (row: Row, column: STableColumn<Row, Field>) => STableCellState
155
+ /** Accessible name без visible owner. / Accessible name when there is no visible owner. */
156
+ ariaLabel?: string
157
+ /** DOM id visible owner, mutually exclusive with ariaLabel. / DOM id of the visible owner, mutually exclusive with ariaLabel. */
158
+ ariaLabelledby?: string
159
+ /** DOM id supplemental status/diagnostic owner. / DOM id of a supplemental status/diagnostic owner. */
160
+ ariaDescribedby?: string
161
+ }
121
162
  </script>
122
163
 
123
164
  <script setup lang="ts" generic="Row extends object = Record<string, unknown>, RowKey extends STableRowKey = STableRowKey, Field extends string = Extract<keyof Row, string>">
124
165
  import { computed, useSlots } from 'vue'
125
166
  import { ChevronDown, ChevronsUpDown, ChevronUp } from '../../icons/sputnigUiIcons'
167
+ import { hasOwn } from '../../../internal/es2020'
126
168
  import { useOwnedAttrs } from '../../../internal/ownedAttrs'
127
169
  import { useInteractiveLeafRegistration } from '../../../internal/passiveContentContract'
128
170
  import {
@@ -138,16 +180,6 @@ import {
138
180
  } from '../../../internal/runtimeContract'
139
181
  import SAsyncState from '../feedback/SAsyncState.vue'
140
182
  import SButton from '../controls/SButton.vue'
141
- import type {
142
- STableCellState,
143
- STableColumn,
144
- STableColumnContent,
145
- STableProps,
146
- STableRowKey,
147
- STableRowState,
148
- STableSort,
149
- } from './table'
150
-
151
183
  defineOptions({ inheritAttrs: false })
152
184
 
153
185
  const props = withDefaults(defineProps<STableProps<Row, RowKey, Field>>(), {
@@ -392,7 +424,7 @@ const validatedRows = computed<readonly Row[]>(() => {
392
424
  for (const row of props.data) {
393
425
  for (const column of columns) {
394
426
  const content = column.content ?? (column.value === undefined ? 'field' : 'value')
395
- if (content === 'field' && !Object.hasOwn(row, column.field)) {
427
+ if (content === 'field' && !hasOwn(row, column.field)) {
396
428
  throw new TypeError(
397
429
  `STable: column ${JSON.stringify(column.field)} не известен row; добавьте typed field или value resolver. `
398
430
  + `/ STable: column ${JSON.stringify(column.field)} is unknown to row; add a typed field or value resolver.`,
@@ -69,6 +69,7 @@ import {
69
69
  type VNodeRef,
70
70
  } from 'vue';
71
71
  import { mergeIdReferences, useOwnedAttrs } from '../../../internal/ownedAttrs';
72
+ import { hasOwn } from '../../../internal/es2020';
72
73
  import {
73
74
  resolveInteractiveElement,
74
75
  type ElementRefTarget,
@@ -202,7 +203,7 @@ function assertDenseArray(coordinate: string, value: unknown): readonly unknown[
202
203
  throw triggerContractError(`${coordinate} должен быть массивом. ${coordinate} must be an array.`);
203
204
  }
204
205
  for (let index = 0; index < value.length; index += 1) {
205
- if (!Object.hasOwn(value, index)) {
206
+ if (!hasOwn(value, index)) {
206
207
  throw triggerContractError(`${coordinate}[${index}] отсутствует в sparse array. ${coordinate}[${index}] is missing from a sparse array.`);
207
208
  }
208
209
  }
@@ -1,19 +1,4 @@
1
1
  /** Ключ элемента виртуального списка. / Virtual-list item key. */
2
2
  export type SVirtualListItemKey = string | number
3
3
 
4
- /**
5
- * Публичный контракт виртуального списка с измеряемыми строками.
6
- * Public contract for a virtual list with measured rows.
7
- */
8
- export interface SVirtualListProps<Item = unknown> {
9
- items: readonly Item[]
10
- /** Оценочная высота строки до фактического измерения ResizeObserver. / Estimated row height before ResizeObserver measurement. */
11
- itemSize: number
12
- overscan?: number
13
- height?: number | null
14
- /** Устойчивый доменный ключ; индекс массива не является допустимым fallback. / Stable domain key; an array index is not a valid fallback. */
15
- itemKey: (item: Item) => SVirtualListItemKey
16
- itemLabel: (item: Item, index: number) => string
17
- accessibleLabel: string
18
- externalScrollTarget?: HTMLElement | null
19
- }
4
+ export type { SVirtualListProps } from './SVirtualList.vue'
@@ -35,6 +35,24 @@
35
35
  </div>
36
36
  </template>
37
37
 
38
+ <script lang="ts">
39
+ import type { SVirtualListItemKey } from './SVirtualList'
40
+
41
+ /** Публичный контракт virtual list принадлежит source-SFC runtime. / The public virtual-list contract is owned by the source-SFC runtime. */
42
+ export interface SVirtualListProps<Item = unknown> {
43
+ items: readonly Item[]
44
+ /** Оценочная высота строки до фактического измерения. / Estimated row height before measurement. */
45
+ itemSize: number
46
+ overscan?: number
47
+ height?: number | null
48
+ /** Устойчивый доменный ключ; индекс не является fallback. / Stable domain key; the index is not a fallback. */
49
+ itemKey: (item: Item) => SVirtualListItemKey
50
+ itemLabel: (item: Item, index: number) => string
51
+ accessibleLabel: string
52
+ externalScrollTarget?: HTMLElement | null
53
+ }
54
+ </script>
55
+
38
56
  <script setup lang="ts" generic="T">
39
57
  import {
40
58
  computed,
@@ -47,8 +65,6 @@ import {
47
65
  } from 'vue'
48
66
  import { useOwnedAttrs } from '../../../internal/ownedAttrs'
49
67
  import { useInteractiveLeafRegistration } from '../../../internal/passiveContentContract'
50
- import type { SVirtualListItemKey, SVirtualListProps } from './SVirtualList'
51
-
52
68
  defineOptions({ inheritAttrs: false })
53
69
 
54
70
  type Alignment = 'auto' | 'start' | 'center' | 'end'
@@ -12,7 +12,7 @@ export type JsonValue = JsonScalar | JsonObject | JsonArray
12
12
 
13
13
  /** Escapes one RFC 6901 reference token. / Экранирует один reference token по RFC 6901. */
14
14
  export function escapeJsonPointerToken(token: string): string {
15
- return token.replaceAll('~', '~0').replaceAll('/', '~1')
15
+ return token.replace(/~/gu, '~0').replace(/\//gu, '~1')
16
16
  }
17
17
 
18
18
  /** Appends a reference token to an RFC 6901 JSON Pointer. / Добавляет reference token к JSON Pointer по RFC 6901. */
@@ -1,5 +1,4 @@
1
1
  import type {
2
- ScrollableViewportMaxBlockSize,
3
2
  TableColumnInlineSize,
4
3
  TableColumnMinInlineSize,
5
4
  } from '../../../internal/semanticSizing'
@@ -55,31 +54,4 @@ export type STableColumn<
55
54
  | STableSlotColumn<Field>
56
55
  | (Field extends Extract<keyof Row, string> ? STableFieldColumn<Field> : never)
57
56
 
58
- /** Публичный generic-контракт таблицы. / Public generic table contract. */
59
- export interface STableProps<
60
- Row extends object,
61
- RowKey extends STableRowKey,
62
- Field extends string,
63
- > {
64
- columns: readonly STableColumn<Row, Field>[]
65
- data: readonly Row[]
66
- getRowKey: (row: Row) => RowKey
67
- loading?: boolean
68
- loadingMessage?: string
69
- errorMessage?: string
70
- emptyMessage?: string
71
- selectedRowKey?: RowKey
72
- sort?: STableSort<Field> | null
73
- maxHeight?: ScrollableViewportMaxBlockSize
74
- density?: STableDensity
75
- surface?: STableSurface
76
- minWidth?: STableMinWidth
77
- rowState?: (row: Row) => STableRowState
78
- cellState?: (row: Row, column: STableColumn<Row, Field>) => STableCellState
79
- /** Accessible name без visible owner. / Accessible name when there is no visible owner. */
80
- ariaLabel?: string
81
- /** DOM id visible owner, mutually exclusive with ariaLabel. / DOM id of the visible owner, mutually exclusive with ariaLabel. */
82
- ariaLabelledby?: string
83
- /** DOM id supplemental status/diagnostic owner. / DOM id of a supplemental status/diagnostic owner. */
84
- ariaDescribedby?: string
85
- }
57
+ export type { STableProps } from './STable.vue'
@@ -83,6 +83,13 @@
83
83
  </template>
84
84
 
85
85
  <script lang="ts">
86
+ import type { ScrollableViewportMaxBlockSize } from '../../../internal/semanticSizing'
87
+ import type {
88
+ SDataGridColumn,
89
+ SDataGridColumnKey,
90
+ SDataGridRow,
91
+ } from './dataGrid'
92
+
86
93
  export type {
87
94
  SDataGridCell,
88
95
  SDataGridCellKind,
@@ -90,14 +97,32 @@ export type {
90
97
  SDataGridColumnKey,
91
98
  SDataGridCommitInput,
92
99
  SDataGridEditPayload,
93
- SDataGridProps,
94
100
  SDataGridRow,
95
101
  SDataGridRowState,
96
102
  } from './dataGrid'
103
+
104
+ /** Публичный контракт data grid принадлежит source-SFC runtime. / The public data-grid contract is owned by the source-SFC runtime. */
105
+ export interface SDataGridProps<Key extends SDataGridColumnKey = SDataGridColumnKey> {
106
+ columns: readonly SDataGridColumn<Key>[]
107
+ rows: readonly SDataGridRow<Key>[]
108
+ title?: string
109
+ loading?: boolean
110
+ disabled?: boolean
111
+ readOnly?: boolean
112
+ errorMessage?: string
113
+ emptyMessage?: string
114
+ selectedRowId?: string
115
+ maxHeight?: ScrollableViewportMaxBlockSize
116
+ /** Accessible name без visible title. / Accessible name when there is no visible title. */
117
+ ariaLabel?: string
118
+ /** DOM id external visible title. / DOM id of an external visible title. */
119
+ ariaLabelledby?: string
120
+ }
97
121
  </script>
98
122
 
99
123
  <script setup lang="ts" generic="ColumnKey extends string = string">
100
124
  import { computed, reactive, useId, useSlots, watch } from 'vue'
125
+ import { hasOwn } from '../../../internal/es2020'
101
126
  import { useOwnedAttrs } from '../../../internal/ownedAttrs'
102
127
  import {
103
128
  validateScrollableViewportMaxBlockSize,
@@ -121,12 +146,8 @@ import type {
121
146
  } from '../data-display/table'
122
147
  import type {
123
148
  SDataGridCell,
124
- SDataGridColumn,
125
- SDataGridColumnKey,
126
149
  SDataGridCommitInput,
127
150
  SDataGridEditPayload,
128
- SDataGridProps,
129
- SDataGridRow,
130
151
  } from './dataGrid'
131
152
 
132
153
  defineOptions({ inheritAttrs: false })
@@ -266,7 +287,7 @@ const validatedGrid = computed<ValidatedGrid<ColumnKey>>(() => {
266
287
  }
267
288
  }
268
289
  for (const [key, column] of columnByKey) {
269
- if (!Object.hasOwn(row.cells, key)) {
290
+ if (!hasOwn(row.cells, key)) {
270
291
  throw new TypeError(
271
292
  `SDataGrid: row ${JSON.stringify(rowId)} не содержит cell ${JSON.stringify(key)}. `
272
293
  + `/ SDataGrid: row ${JSON.stringify(rowId)} is missing cell ${JSON.stringify(key)}.`,
@@ -358,7 +379,7 @@ function resolveColumn(key: string): SDataGridColumn<ColumnKey> {
358
379
 
359
380
  function resolveCell(row: SDataGridRow<ColumnKey>, columnKeyValue: string): SDataGridCell {
360
381
  const columnKey = resolveColumn(columnKeyValue).key
361
- if (Object.hasOwn(row.cells, columnKey)) return row.cells[columnKey]
382
+ if (hasOwn(row.cells, columnKey)) return row.cells[columnKey]
362
383
  throw new TypeError(
363
384
  `SDataGrid: row ${JSON.stringify(row.id)} не содержит cell ${JSON.stringify(columnKey)}. `
364
385
  + `/ SDataGrid: row ${JSON.stringify(row.id)} is missing cell ${JSON.stringify(columnKey)}.`,
@@ -404,7 +425,7 @@ function editCell(row: SDataGridRow<ColumnKey>, column: SDataGridColumn<ColumnKe
404
425
  function validateCommitInput(input: unknown): SDataGridCommitInput {
405
426
  assertRecord(input, 'commit input')
406
427
  const source = validateExactString('SDataGrid', 'commit input.source', input.source, COMMIT_SOURCES)
407
- const hasValue = Object.hasOwn(input, 'value')
428
+ const hasValue = hasOwn(input, 'value')
408
429
  if (source === 'draft' && hasValue) {
409
430
  throw new TypeError(
410
431
  'SDataGrid: draft commit не должен содержать value. / SDataGrid: draft commit must not contain value.',
@@ -51,6 +51,7 @@
51
51
 
52
52
  <script setup lang="ts">
53
53
  import { computed, ref, useId, useSlots } from 'vue'
54
+ import { hasOwn } from '../../../internal/es2020'
54
55
  import { useOwnedAttrs } from '../../../internal/ownedAttrs'
55
56
  import { provideTrustedCodeEditorExtensions } from '../../../internal/codeEditorExtensions'
56
57
  import { validateCodeEditorViewport } from '../../../internal/codeEditorContract'
@@ -291,8 +292,8 @@ const validatedDiagnostics = computed<SSqlEditorDiagnostic[]>(() => {
291
292
  return props.diagnostics.map((candidate, index) => {
292
293
  const runtimeCandidate: unknown = candidate
293
294
  const record = assertPlainExactRecord(`SSqlEditor.diagnostics[${index}]`, runtimeCandidate, ['severity', 'message', 'from', 'to', 'line', 'column'])
294
- const hasOffset = Object.hasOwn(record, 'from')
295
- const hasLine = Object.hasOwn(record, 'line') || Object.hasOwn(record, 'column')
295
+ const hasOffset = hasOwn(record, 'from')
296
+ const hasLine = hasOwn(record, 'line') || hasOwn(record, 'column')
296
297
  if (hasOffset === hasLine) throw new TypeError(`SSqlEditor: diagnostics[${index}] должен задавать ровно один range: from/to или line/column. / SSqlEditor: diagnostics[${index}] must provide exactly one range: from/to or line/column.`)
297
298
  const allowedRangeKeys = hasOffset
298
299
  ? ['severity', 'message', 'from', 'to']
@@ -1,5 +1,4 @@
1
1
  import type {
2
- ScrollableViewportMaxBlockSize,
3
2
  TableColumnInlineSize,
4
3
  TableColumnMinInlineSize,
5
4
  } from '../../../internal/semanticSizing'
@@ -43,19 +42,4 @@ export type SDataGridCommitInput =
43
42
  | { source: 'draft' }
44
43
  | { source: 'value'; value: unknown }
45
44
 
46
- export interface SDataGridProps<Key extends SDataGridColumnKey = SDataGridColumnKey> {
47
- columns: readonly SDataGridColumn<Key>[]
48
- rows: readonly SDataGridRow<Key>[]
49
- title?: string
50
- loading?: boolean
51
- disabled?: boolean
52
- readOnly?: boolean
53
- errorMessage?: string
54
- emptyMessage?: string
55
- selectedRowId?: string
56
- maxHeight?: ScrollableViewportMaxBlockSize
57
- /** Accessible name без visible title. / Accessible name when there is no visible title. */
58
- ariaLabel?: string
59
- /** DOM id external visible title. / DOM id of an external visible title. */
60
- ariaLabelledby?: string
61
- }
45
+ export type { SDataGridProps } from './SDataGrid.vue'
@@ -58,6 +58,7 @@ import {
58
58
  type VNodeChild,
59
59
  } from 'vue'
60
60
  import { useOwnedAttrs } from '../../../internal/ownedAttrs'
61
+ import { lastItem } from '../../../internal/es2020'
61
62
  import {
62
63
  tabsContextKey,
63
64
  type STabListRegistrationKey,
@@ -425,7 +426,7 @@ function handleTabKeydown(key: STabRegistrationKey, event: KeyboardEvent): void
425
426
  }
426
427
  let next: TabRecord | undefined
427
428
  if (event.key === 'Home') next = enabled[0]
428
- else if (event.key === 'End') next = enabled.at(-1)
429
+ else if (event.key === 'End') next = lastItem(enabled)
429
430
  else if (event.key === 'ArrowLeft') next = enabled[(currentIndex - 1 + enabled.length) % enabled.length]
430
431
  else if (event.key === 'ArrowRight') next = enabled[(currentIndex + 1) % enabled.length]
431
432
  if (!next) return
@@ -45,8 +45,9 @@ export function createUiPreferencesResetController(): UiPreferencesResetControll
45
45
  throw errors[0];
46
46
  }
47
47
  if (errors.length > 1) {
48
- throw new AggregateError(errors, 'Не все owners UI-предпочтений смогли обработать сброс.');
48
+ throw createAggregateError(errors, 'Не все owners UI-предпочтений смогли обработать сброс.');
49
49
  }
50
50
  },
51
51
  };
52
52
  }
53
+ import { createAggregateError } from '../internal/es2020';
@@ -28,10 +28,11 @@ export function useClipboard(): ClipboardService {
28
28
  const isDomException = typeof DOMException !== 'undefined' && cause instanceof DOMException
29
29
  const error = cause instanceof Error || isDomException
30
30
  ? cause
31
- : new Error('Clipboard API rejected writeText without an Error object', { cause })
31
+ : withErrorCause(new Error('Clipboard API rejected writeText without an Error object'), cause)
32
32
  return { status: 'failed', error }
33
33
  }
34
34
  }
35
35
 
36
36
  return { copyText }
37
37
  }
38
+ import { withErrorCause } from '../internal/es2020'
@@ -1,4 +1,5 @@
1
1
  import { computed, getCurrentScope, onScopeDispose, reactive, readonly, watch } from 'vue';
2
+ import { hasOwn, lastItem } from '../internal/es2020';
2
3
 
3
4
  import {
4
5
  assertSidebarSectionsState,
@@ -284,7 +285,7 @@ function preferredFlexibleSectionKey(
284
285
  return config?.defaultIsOpen === true && config.defaultSize === null;
285
286
  });
286
287
  if (defaultFlexibleKey && state[defaultFlexibleKey]?.isOpen) return defaultFlexibleKey;
287
- return openKeys.at(-1) ?? null;
288
+ return lastItem(openKeys) ?? null;
288
289
  }
289
290
 
290
291
  /**
@@ -327,7 +328,7 @@ function decodePersistedDocument(
327
328
  if (!isPlainRecord(value)) {
328
329
  throw new SidebarPanelStateContractError('$', 'expected an object');
329
330
  }
330
- if (Object.hasOwn(value, 'version') || Object.hasOwn(value, 'sections')) {
331
+ if (hasOwn(value, 'version') || hasOwn(value, 'sections')) {
331
332
  assertExactKeys(value, ['sections', 'version'], '$');
332
333
  if (value.version !== SIDEBAR_PANEL_STATE_SCHEMA_VERSION) {
333
334
  throw new SidebarPanelStateContractError(
@@ -0,0 +1,50 @@
1
+ /** Проверяет собственное свойство без зависимости от ES2022 Object.hasOwn. / Checks an own property without the ES2022 Object.hasOwn API. */
2
+ export function hasOwn(object: object, key: PropertyKey): boolean {
3
+ return Object.prototype.hasOwnProperty.call(object, key)
4
+ }
5
+
6
+ /** Возвращает последний элемент без зависимости от ES2022 Array.at. / Returns the last item without the ES2022 Array.at API. */
7
+ export function lastItem<T>(items: readonly T[]): T | undefined {
8
+ return items.length > 0 ? items[items.length - 1] : undefined
9
+ }
10
+
11
+ /** Ошибка с ES2022-compatible cause, доступная в ES2020 runtime. / An ES2022-compatible error cause for ES2020 runtimes. */
12
+ export type ErrorWithCause<T extends Error = Error> = T & { cause: unknown }
13
+
14
+ /** Добавляет стандартный non-enumerable cause без требования ES2022 lib. / Adds the standard non-enumerable cause without requiring the ES2022 lib. */
15
+ export function withErrorCause<T extends Error>(error: T, cause: unknown): ErrorWithCause<T> {
16
+ Object.defineProperty(error, 'cause', {
17
+ configurable: true,
18
+ value: cause,
19
+ writable: true,
20
+ })
21
+ return error as ErrorWithCause<T>
22
+ }
23
+
24
+ interface AggregateErrorLike extends Error {
25
+ readonly errors: readonly unknown[]
26
+ }
27
+
28
+ interface AggregateErrorConstructorLike {
29
+ new (errors: Iterable<unknown>, message?: string): AggregateErrorLike
30
+ }
31
+
32
+ class Es2020AggregateError extends Error implements AggregateErrorLike {
33
+ readonly errors: readonly unknown[]
34
+
35
+ constructor(errors: readonly unknown[], message: string) {
36
+ super(message)
37
+ this.name = 'AggregateError'
38
+ this.errors = [...errors]
39
+ }
40
+ }
41
+
42
+ /** Создаёт native AggregateError при наличии и точный ES2020 fallback иначе. / Creates a native AggregateError when available and an exact ES2020 fallback otherwise. */
43
+ export function createAggregateError(errors: readonly unknown[], message: string): AggregateErrorLike {
44
+ const candidate: unknown = (globalThis as { AggregateError?: unknown }).AggregateError
45
+ if (typeof candidate === 'function') {
46
+ const AggregateErrorConstructor = candidate as AggregateErrorConstructorLike
47
+ return new AggregateErrorConstructor(errors, message)
48
+ }
49
+ return new Es2020AggregateError(errors, message)
50
+ }
@@ -1,4 +1,5 @@
1
1
  import { validateBoundedInteger, validateNonEmptyString } from './runtimeContract'
2
+ import { hasOwn, lastItem } from './es2020'
2
3
 
3
4
  export type InlineTokenEditorSegmentContract =
4
5
  | { readonly kind: 'text'; readonly text: string }
@@ -44,7 +45,7 @@ export function parseInlineTokenEditorSource(
44
45
  const parsed: InlineTokenEditorSegmentContract[] = []
45
46
  const tokenOccurrences = new Map<string, number>()
46
47
  for (let index = 0; index < segments.length; index += 1) {
47
- if (!Object.hasOwn(segments, index)) {
48
+ if (!hasOwn(segments, index)) {
48
49
  throw new TypeError(`SInlineTokenEditor: segments[${index}] отсутствует в sparse array. / SInlineTokenEditor: segments[${index}] is missing from a sparse array.`)
49
50
  }
50
51
  const record = assertPlainRecord(`SInlineTokenEditor.segments[${index}]`, segments[index])
@@ -54,7 +55,7 @@ export function parseInlineTokenEditorSource(
54
55
  throw new TypeError(`SInlineTokenEditor: segments[${index}].text должен быть непустой строкой. / SInlineTokenEditor: segments[${index}].text must be a non-empty string.`)
55
56
  }
56
57
  const text = record.text
57
- if (parsed.at(-1)?.kind === 'text') {
58
+ if (lastItem(parsed)?.kind === 'text') {
58
59
  throw new TypeError(`SInlineTokenEditor: segments[${index}] является смежным text segment; объедините его в источнике. / SInlineTokenEditor: segments[${index}] is an adjacent text segment; merge it at the source.`)
59
60
  }
60
61
  parsed.push({ kind: 'text', text })
@@ -122,7 +123,7 @@ export function composeInlineTokenSegments(
122
123
  for (const segment of segments) {
123
124
  if (segment.kind === 'text') {
124
125
  if (segment.text.length === 0) continue
125
- const previous = result.at(-1)
126
+ const previous = lastItem(result)
126
127
  if (previous?.kind === 'text') {
127
128
  result[result.length - 1] = { kind: 'text', text: previous.text + segment.text }
128
129
  } else {
@@ -19,7 +19,7 @@ let preservedCursor = ''
19
19
  let preservedUserSelect = ''
20
20
 
21
21
  const applyActiveInteraction = (): void => {
22
- const interaction = activeInteractions.at(-1)
22
+ const interaction = lastItem(activeInteractions)
23
23
  if (!lockedBody || !interaction) return
24
24
  lockedBody.style.cursor = interaction.cursor
25
25
  lockedBody.style.userSelect = 'none'
@@ -79,3 +79,4 @@ export function acquirePointerInteractionLease(
79
79
  },
80
80
  }
81
81
  }
82
+ import { lastItem } from './es2020'