@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.
- package/docs/public-api.md +8 -0
- package/package.json +1 -1
- package/src/components/layout/SAppShell.vue +3 -2
- package/src/components/shared/complex/STree.vue +29 -4
- package/src/components/shared/complex/treeAdapter.ts +2 -1
- package/src/components/shared/complex/types.ts +1 -12
- package/src/components/shared/containers/SLeftSidebar.vue +7 -2
- package/src/components/shared/containers/SPopover.vue +2 -1
- package/src/components/shared/containers/SSidebarGroup.vue +10 -5
- package/src/components/shared/containers/sidebar.ts +2 -1
- package/src/components/shared/controls/SButton.vue +10 -5
- package/src/components/shared/controls/SInteractiveSurface.vue +2 -1
- package/src/components/shared/controls/SLink.ts +1 -17
- package/src/components/shared/controls/SLink.vue +26 -2
- package/src/components/shared/data-display/SDocBlock.vue +10 -10
- package/src/components/shared/data-display/SLinkedSystemsList.vue +3 -3
- package/src/components/shared/data-display/STable.vue +44 -12
- package/src/components/shared/data-display/STooltip.vue +2 -1
- package/src/components/shared/data-display/SVirtualList.ts +1 -16
- package/src/components/shared/data-display/SVirtualList.vue +18 -2
- package/src/components/shared/data-display/json.ts +1 -1
- package/src/components/shared/data-display/table.ts +1 -29
- package/src/components/shared/database/SDataGrid.vue +29 -8
- package/src/components/shared/database/SSqlEditor.vue +3 -2
- package/src/components/shared/database/dataGrid.ts +1 -17
- package/src/components/shared/navigation/STabs.vue +2 -1
- package/src/composables/uiPreferencesReset.ts +2 -1
- package/src/composables/useClipboard.ts +2 -1
- package/src/composables/useSidebarPanelState.ts +3 -2
- package/src/internal/es2020.ts +50 -0
- package/src/internal/inlineTokenEditorContract.ts +4 -3
- package/src/internal/pointerInteractionLease.ts +2 -1
package/docs/public-api.md
CHANGED
|
@@ -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,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
|
-
|
|
102
|
-
||
|
|
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
|
|
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}
|
|
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
|
|
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 {
|
|
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<
|
|
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
|
|
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
|
-
|
|
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
|
|
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 (!
|
|
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
|
|
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 (!
|
|
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
|
-
|
|
76
|
-
|
|
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
|
-
|
|
136
|
-
|
|
137
|
-
|
|
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) => !
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
-
.
|
|
92
|
-
.
|
|
93
|
-
.
|
|
94
|
-
.
|
|
95
|
-
.
|
|
91
|
+
.replace(/&/gu, '&')
|
|
92
|
+
.replace(/</gu, '<')
|
|
93
|
+
.replace(/>/gu, '>')
|
|
94
|
+
.replace(/"/gu, '"')
|
|
95
|
+
.replace(/'/gu, ''');
|
|
96
96
|
}
|
|
97
97
|
|
|
98
98
|
function escapeHtmlAttribute(value: string): string {
|
|
99
99
|
return value
|
|
100
|
-
.
|
|
101
|
-
.
|
|
102
|
-
.
|
|
103
|
-
.
|
|
104
|
-
.
|
|
100
|
+
.replace(/&/gu, '&')
|
|
101
|
+
.replace(/</gu, '<')
|
|
102
|
+
.replace(/>/gu, '>')
|
|
103
|
+
.replace(/"/gu, '"')
|
|
104
|
+
.replace(/'/gu, ''');
|
|
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
|
-
|
|
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' && !
|
|
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 (!
|
|
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.
|
|
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
|
-
|
|
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 (!
|
|
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 (
|
|
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 =
|
|
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 =
|
|
295
|
-
const hasLine =
|
|
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
|
|
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
|
|
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
|
|
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',
|
|
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
|
|
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 (
|
|
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 (!
|
|
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
|
|
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
|
|
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
|
|
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'
|