@pgcorp/ui-kit 0.2.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/README.md +5 -5
- package/docs/public-api.md +8 -0
- package/package.json +15 -3
- package/src/components/layout/SAppShell.vue +3 -2
- package/src/components/layout/SStack.css +9 -3
- package/src/components/layout/SStack.vue +10 -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/SPageHeader.css +11 -0
- package/src/components/shared/containers/SPageHeader.vue +7 -1
- 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.css +13 -0
- package/src/components/shared/controls/SButton.vue +135 -29
- package/src/components/shared/controls/SCombobox.css +9 -0
- package/src/components/shared/controls/SCombobox.vue +322 -0
- package/src/components/shared/controls/SInputText.vue +15 -0
- 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/controls/SListbox.vue +99 -1
- package/src/components/shared/controls/SListboxOption.vue +15 -2
- package/src/components/shared/controls/SSwitch.css +48 -0
- package/src/components/shared/controls/SSwitch.vue +109 -0
- package/src/components/shared/controls/_internal/SInlineTokenSurface.vue +6 -3
- package/src/components/shared/data-display/SChip.css +59 -0
- package/src/components/shared/data-display/SChip.vue +92 -0
- 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 +150 -14
- 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 +9 -28
- 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/SBottomNav.vue +3 -0
- 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/ownedAttrs.ts +24 -2
- package/src/internal/pointerInteractionLease.ts +2 -1
|
@@ -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'
|
|
@@ -270,6 +270,9 @@ const resolveOverflowTrigger = (item: BottomNavOverflowItem): HTMLButtonElement
|
|
|
270
270
|
throw new Error(`SBottomNav: overflow trigger '${item.id}' is unavailable`);
|
|
271
271
|
}
|
|
272
272
|
const trigger = api.getElement();
|
|
273
|
+
if (!(trigger instanceof HTMLButtonElement)) {
|
|
274
|
+
throw new TypeError(`SBottomNav: overflow trigger '${item.id}' must be an action button`);
|
|
275
|
+
}
|
|
273
276
|
const expectedId = overflowTriggerId(item);
|
|
274
277
|
if (trigger.id !== expectedId) {
|
|
275
278
|
throw new Error(
|
|
@@ -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 {
|
|
@@ -126,6 +126,13 @@ export const ownedAttributeSchemas = Object.freeze({
|
|
|
126
126
|
onPointerdown: 'listener',
|
|
127
127
|
tabindex: 'string-or-number',
|
|
128
128
|
}),
|
|
129
|
+
SSwitch: defineOwnedAttributeSchema({
|
|
130
|
+
'aria-label': 'string',
|
|
131
|
+
'aria-describedby': 'string',
|
|
132
|
+
onClick: 'listener',
|
|
133
|
+
onPointerdown: 'listener',
|
|
134
|
+
tabindex: 'string-or-number',
|
|
135
|
+
}),
|
|
129
136
|
SCheckboxGroup: defineOwnedAttributeSchema({
|
|
130
137
|
'aria-describedby': 'string',
|
|
131
138
|
'data-testid': 'data',
|
|
@@ -137,6 +144,10 @@ export const ownedAttributeSchemas = Object.freeze({
|
|
|
137
144
|
SColorSelect: defineOwnedAttributeSchema({
|
|
138
145
|
'aria-label': 'string',
|
|
139
146
|
}),
|
|
147
|
+
SCombobox: defineOwnedAttributeSchema({
|
|
148
|
+
'aria-describedby': 'string',
|
|
149
|
+
'aria-label': 'string',
|
|
150
|
+
}),
|
|
140
151
|
SDragHandle: defineOwnedAttributeSchema({
|
|
141
152
|
'data-testid': 'data',
|
|
142
153
|
}),
|
|
@@ -158,7 +169,12 @@ export const ownedAttributeSchemas = Object.freeze({
|
|
|
158
169
|
title: 'string',
|
|
159
170
|
}),
|
|
160
171
|
SInputText: defineOwnedAttributeSchema({
|
|
172
|
+
'aria-activedescendant': 'string',
|
|
173
|
+
'aria-autocomplete': 'string',
|
|
174
|
+
'aria-controls': 'string',
|
|
161
175
|
'aria-describedby': 'string',
|
|
176
|
+
'aria-expanded': 'aria-boolean',
|
|
177
|
+
'aria-haspopup': 'aria-haspopup',
|
|
162
178
|
'aria-invalid': 'aria-boolean',
|
|
163
179
|
'aria-label': 'string',
|
|
164
180
|
'data-testid': 'data',
|
|
@@ -168,10 +184,12 @@ export const ownedAttributeSchemas = Object.freeze({
|
|
|
168
184
|
max: 'string-or-number',
|
|
169
185
|
min: 'string-or-number',
|
|
170
186
|
onBlur: 'listener',
|
|
187
|
+
onClick: 'listener',
|
|
171
188
|
onFocus: 'listener',
|
|
172
189
|
onKeydown: 'listener',
|
|
173
190
|
onKeyup: 'listener',
|
|
174
191
|
pattern: 'string',
|
|
192
|
+
role: 'string',
|
|
175
193
|
step: 'string-or-number',
|
|
176
194
|
title: 'string',
|
|
177
195
|
}),
|
|
@@ -237,6 +255,10 @@ export const ownedAttributeSchemas = Object.freeze({
|
|
|
237
255
|
'data-testid': 'data',
|
|
238
256
|
title: 'string',
|
|
239
257
|
}),
|
|
258
|
+
SChip: defineOwnedAttributeSchema({
|
|
259
|
+
'aria-label': 'string',
|
|
260
|
+
title: 'string',
|
|
261
|
+
}),
|
|
240
262
|
SCodeBlock: defineOwnedAttributeSchema({
|
|
241
263
|
'aria-label': 'string',
|
|
242
264
|
'data-testid': 'data',
|
|
@@ -338,7 +360,7 @@ type OwnedAttributeValue<Name extends string, Kind extends OwnedAttributeValueKi
|
|
|
338
360
|
|
|
339
361
|
export type OwnedAttributeBindings<Component extends OwnedAttributeComponent> = Readonly<Partial<{
|
|
340
362
|
[Name in OwnedAttributeName<Component>]: OwnedAttributeValue<Name, OwnedAttributeKind<Component, Name>>
|
|
341
|
-
}
|
|
363
|
+
}> & Partial<Record<`data-${string}`, string | number | boolean>>>
|
|
342
364
|
|
|
343
365
|
export interface OwnedAttrsOptions<Component extends OwnedAttributeComponent> {
|
|
344
366
|
/** Имя public-компонента для диагностики контракта. / Public component name used in contract diagnostics. */
|
|
@@ -420,7 +442,7 @@ export function useOwnedAttrs<Component extends OwnedAttributeComponent>(
|
|
|
420
442
|
`raw "${name}" запрещён; атрибут "${name}" не объявлен в exact owned-attrs schema; используйте типизированные props/tokens UI-kit или consumer layout wrapper. Raw "${name}" is forbidden and is not declared by the exact owned-attrs schema; use typed UI-kit props/tokens or a consumer layout wrapper`,
|
|
421
443
|
)
|
|
422
444
|
}
|
|
423
|
-
const kind = schema[name]
|
|
445
|
+
const kind = schema[name] ?? (name.startsWith('data-') && name.length > 5 ? 'data' : undefined)
|
|
424
446
|
if (!kind) {
|
|
425
447
|
throw attributeBoundaryError(
|
|
426
448
|
options.component,
|
|
@@ -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'
|