@pgcorp/ui-kit 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +157 -0
- package/docs/accessibility.md +27 -0
- package/docs/getting-started.md +80 -0
- package/docs/licensing.md +30 -0
- package/docs/public-api.md +39 -0
- package/docs/security.md +21 -0
- package/docs/theming.md +35 -0
- package/docs/versioning.md +23 -0
- package/index.ts +3 -0
- package/package.json +592 -0
- package/src/components/icons/_internal/generatedSputnigUiIconMarkup.ts +971 -0
- package/src/components/icons/sputnigUiIcons.ts +530 -0
- package/src/components/layout/SAppShell.vue +232 -0
- package/src/components/layout/SDockRegion.vue +200 -0
- package/src/components/layout/SResizeHandle.vue +251 -0
- package/src/components/layout/SSideMenuButton.vue +104 -0
- package/src/components/layout/SSideMenuRail.vue +47 -0
- package/src/components/layout/SStack.css +25 -0
- package/src/components/layout/SStack.vue +57 -0
- package/src/components/layout/SStatusBarShell.css +15 -0
- package/src/components/layout/SStatusBarShell.vue +26 -0
- package/src/components/layout/SWorkbenchLayout.vue +53 -0
- package/src/components/layout/SWorkbenchPanelShell.css +161 -0
- package/src/components/layout/SWorkbenchPanelShell.vue +286 -0
- package/src/components/layout/_internal/SAppBrand.css +45 -0
- package/src/components/layout/_internal/SAppBrand.vue +37 -0
- package/src/components/layout/_internal/SAppBrandContent.vue +30 -0
- package/src/components/layout/types.ts +28 -0
- package/src/components/shared/_internal/SDialogSurface.css +127 -0
- package/src/components/shared/_internal/SDialogSurface.vue +117 -0
- package/src/components/shared/_internal/SDisclosureSummary.css +92 -0
- package/src/components/shared/_internal/SDisclosureSummary.vue +121 -0
- package/src/components/shared/_internal/SListboxGroup.css +18 -0
- package/src/components/shared/_internal/SListboxGroup.vue +24 -0
- package/src/components/shared/_internal/SMenuItem.css +112 -0
- package/src/components/shared/_internal/SMenuItem.vue +213 -0
- package/src/components/shared/_internal/SMenuLayer.css +21 -0
- package/src/components/shared/_internal/SMenuLayer.vue +48 -0
- package/src/components/shared/_internal/SMenuSeparator.css +5 -0
- package/src/components/shared/_internal/SMenuSeparator.vue +13 -0
- package/src/components/shared/_internal/SToastItem.css +65 -0
- package/src/components/shared/_internal/SToastItem.vue +58 -0
- package/src/components/shared/_internal/STreeNode.vue +761 -0
- package/src/components/shared/_internal/dialogSurface.ts +105 -0
- package/src/components/shared/_internal/focusTrap.ts +40 -0
- package/src/components/shared/_internal/listboxContext.ts +16 -0
- package/src/components/shared/_internal/markerContract.ts +10 -0
- package/src/components/shared/_internal/menuContext.ts +23 -0
- package/src/components/shared/_internal/selectionContract.ts +239 -0
- package/src/components/shared/_internal/sidebarGroupContext.ts +31 -0
- package/src/components/shared/_internal/tabsContext.ts +57 -0
- package/src/components/shared/_internal/treeRuntime.ts +203 -0
- package/src/components/shared/_internal/useSelectionRoving.ts +162 -0
- package/src/components/shared/codeLanguages.ts +168 -0
- package/src/components/shared/complex/SKanbanCard.css +8 -0
- package/src/components/shared/complex/SKanbanCard.vue +76 -0
- package/src/components/shared/complex/SKanbanLane.vue +82 -0
- package/src/components/shared/complex/STree.css +24 -0
- package/src/components/shared/complex/STree.vue +288 -0
- package/src/components/shared/complex/treeAdapter.ts +210 -0
- package/src/components/shared/complex/types.ts +102 -0
- package/src/components/shared/containers/SCloudChrome.css +103 -0
- package/src/components/shared/containers/SCloudChrome.vue +87 -0
- package/src/components/shared/containers/SCloudChromeEmpty.css +10 -0
- package/src/components/shared/containers/SCloudChromeEmpty.vue +18 -0
- package/src/components/shared/containers/SCloudChromeRow.css +78 -0
- package/src/components/shared/containers/SCloudChromeRow.vue +108 -0
- package/src/components/shared/containers/SCollapsiblePanel.css +57 -0
- package/src/components/shared/containers/SCollapsiblePanel.vue +360 -0
- package/src/components/shared/containers/SDrawer.css +60 -0
- package/src/components/shared/containers/SDrawer.vue +86 -0
- package/src/components/shared/containers/SFormGrid.css +26 -0
- package/src/components/shared/containers/SFormGrid.vue +46 -0
- package/src/components/shared/containers/SFormGridItem.css +18 -0
- package/src/components/shared/containers/SFormGridItem.vue +29 -0
- package/src/components/shared/containers/SLeftSidebar.vue +74 -0
- package/src/components/shared/containers/SModal.css +27 -0
- package/src/components/shared/containers/SModal.vue +105 -0
- package/src/components/shared/containers/SPageHeader.css +66 -0
- package/src/components/shared/containers/SPageHeader.vue +45 -0
- package/src/components/shared/containers/SPanel.css +134 -0
- package/src/components/shared/containers/SPanel.vue +169 -0
- package/src/components/shared/containers/SPopover.css +35 -0
- package/src/components/shared/containers/SPopover.vue +551 -0
- package/src/components/shared/containers/SSidebarGroup.css +12 -0
- package/src/components/shared/containers/SSidebarGroup.vue +443 -0
- package/src/components/shared/containers/SSidebarSection.css +41 -0
- package/src/components/shared/containers/SSidebarSection.vue +268 -0
- package/src/components/shared/containers/disclosureSummary.ts +48 -0
- package/src/components/shared/containers/popover.ts +72 -0
- package/src/components/shared/containers/sidebar.ts +206 -0
- package/src/components/shared/controls/SButton.css +274 -0
- package/src/components/shared/controls/SButton.vue +211 -0
- package/src/components/shared/controls/SCheckbox.css +65 -0
- package/src/components/shared/controls/SCheckbox.vue +130 -0
- package/src/components/shared/controls/SCheckboxGroup.vue +134 -0
- package/src/components/shared/controls/SChoiceCards.css +29 -0
- package/src/components/shared/controls/SChoiceCards.vue +145 -0
- package/src/components/shared/controls/SColorSelect.vue +146 -0
- package/src/components/shared/controls/SComboboxTrigger.css +35 -0
- package/src/components/shared/controls/SComboboxTrigger.vue +122 -0
- package/src/components/shared/controls/SContextMenu.css +11 -0
- package/src/components/shared/controls/SContextMenu.vue +186 -0
- package/src/components/shared/controls/SContextToggleButton.vue +64 -0
- package/src/components/shared/controls/SDragHandle.vue +100 -0
- package/src/components/shared/controls/SDropdownMenu.css +5 -0
- package/src/components/shared/controls/SDropdownMenu.vue +188 -0
- package/src/components/shared/controls/SField.css +79 -0
- package/src/components/shared/controls/SField.vue +89 -0
- package/src/components/shared/controls/SFieldGroup.css +58 -0
- package/src/components/shared/controls/SFieldGroup.vue +117 -0
- package/src/components/shared/controls/SFieldLabel.css +12 -0
- package/src/components/shared/controls/SFieldLabel.vue +51 -0
- package/src/components/shared/controls/SInlineTokenEditor.css +61 -0
- package/src/components/shared/controls/SInlineTokenEditor.vue +1081 -0
- package/src/components/shared/controls/SInputText.css +66 -0
- package/src/components/shared/controls/SInputText.vue +321 -0
- package/src/components/shared/controls/SInteractiveSurface.css +72 -0
- package/src/components/shared/controls/SInteractiveSurface.vue +326 -0
- package/src/components/shared/controls/SLink.css +114 -0
- package/src/components/shared/controls/SLink.ts +85 -0
- package/src/components/shared/controls/SLink.vue +114 -0
- package/src/components/shared/controls/SListbox.css +31 -0
- package/src/components/shared/controls/SListbox.vue +388 -0
- package/src/components/shared/controls/SListboxOption.css +63 -0
- package/src/components/shared/controls/SListboxOption.vue +85 -0
- package/src/components/shared/controls/SMarker.css +19 -0
- package/src/components/shared/controls/SMarker.vue +33 -0
- package/src/components/shared/controls/SMenuSurface.css +20 -0
- package/src/components/shared/controls/SMenuSurface.vue +333 -0
- package/src/components/shared/controls/SSegmentedControl.css +38 -0
- package/src/components/shared/controls/SSegmentedControl.vue +134 -0
- package/src/components/shared/controls/SSelect.css +12 -0
- package/src/components/shared/controls/SSelect.vue +338 -0
- package/src/components/shared/controls/STextarea.css +31 -0
- package/src/components/shared/controls/STextarea.vue +316 -0
- package/src/components/shared/controls/_internal/SInlineTokenSurface.vue +224 -0
- package/src/components/shared/controls/menu.ts +178 -0
- package/src/components/shared/data-display/SActionCard.css +6 -0
- package/src/components/shared/data-display/SActionCard.vue +49 -0
- package/src/components/shared/data-display/SActionList.css +15 -0
- package/src/components/shared/data-display/SActionList.vue +34 -0
- package/src/components/shared/data-display/SActionListItem.css +114 -0
- package/src/components/shared/data-display/SActionListItem.vue +230 -0
- package/src/components/shared/data-display/SBadge.css +73 -0
- package/src/components/shared/data-display/SBadge.vue +52 -0
- package/src/components/shared/data-display/SCodeBlock.css +56 -0
- package/src/components/shared/data-display/SCodeBlock.vue +188 -0
- package/src/components/shared/data-display/SCodeEditor.css +178 -0
- package/src/components/shared/data-display/SCodeEditor.vue +274 -0
- package/src/components/shared/data-display/SCodeSearchPanel.vue +327 -0
- package/src/components/shared/data-display/SCopyField.vue +77 -0
- package/src/components/shared/data-display/SDocBlock.css +136 -0
- package/src/components/shared/data-display/SDocBlock.vue +358 -0
- package/src/components/shared/data-display/SEmptyState.css +42 -0
- package/src/components/shared/data-display/SEmptyState.vue +79 -0
- package/src/components/shared/data-display/SExpandableText.vue +314 -0
- package/src/components/shared/data-display/SJsonTree.css +6 -0
- package/src/components/shared/data-display/SJsonTree.vue +111 -0
- package/src/components/shared/data-display/SKeyValueGrid.css +37 -0
- package/src/components/shared/data-display/SKeyValueGrid.vue +277 -0
- package/src/components/shared/data-display/SLinkedSystemsList.vue +270 -0
- package/src/components/shared/data-display/SMessage.css +88 -0
- package/src/components/shared/data-display/SMessage.vue +152 -0
- package/src/components/shared/data-display/SMetricCard.css +32 -0
- package/src/components/shared/data-display/SMetricCard.vue +117 -0
- package/src/components/shared/data-display/SProgressBar.css +84 -0
- package/src/components/shared/data-display/SProgressBar.vue +114 -0
- package/src/components/shared/data-display/SProgressIndicator.css +59 -0
- package/src/components/shared/data-display/SProgressIndicator.vue +104 -0
- package/src/components/shared/data-display/SSectionHeader.css +84 -0
- package/src/components/shared/data-display/SSectionHeader.vue +58 -0
- package/src/components/shared/data-display/SStatus.css +90 -0
- package/src/components/shared/data-display/SStatus.vue +79 -0
- package/src/components/shared/data-display/STable.css +83 -0
- package/src/components/shared/data-display/STable.vue +339 -0
- package/src/components/shared/data-display/SToastContainer.css +53 -0
- package/src/components/shared/data-display/SToastContainer.vue +31 -0
- package/src/components/shared/data-display/STooltip.css +114 -0
- package/src/components/shared/data-display/STooltip.vue +550 -0
- package/src/components/shared/data-display/STooltipAnchor.css +21 -0
- package/src/components/shared/data-display/STooltipAnchor.vue +119 -0
- package/src/components/shared/data-display/SVirtualList.css +24 -0
- package/src/components/shared/data-display/SVirtualList.ts +19 -0
- package/src/components/shared/data-display/SVirtualList.vue +361 -0
- package/src/components/shared/data-display/json.ts +136 -0
- package/src/components/shared/data-display/table.ts +76 -0
- package/src/components/shared/database/SDataGrid.css +3 -0
- package/src/components/shared/database/SDataGrid.vue +464 -0
- package/src/components/shared/database/SSqlEditor.vue +363 -0
- package/src/components/shared/database/dataGrid.ts +61 -0
- package/src/components/shared/feedback/SAsyncState.css +26 -0
- package/src/components/shared/feedback/SAsyncState.vue +190 -0
- package/src/components/shared/graph/SGraphViewport.ts +1 -0
- package/src/components/shared/graph/SGraphViewport.vue +858 -0
- package/src/components/shared/graph/_internal/graphViewportContract.ts +362 -0
- package/src/components/shared/markdown.ts +169 -0
- package/src/components/shared/navigation/SBottomNav.vue +692 -0
- package/src/components/shared/navigation/SBreadcrumbs.css +26 -0
- package/src/components/shared/navigation/SBreadcrumbs.vue +155 -0
- package/src/components/shared/navigation/SCatalogNavigator.css +100 -0
- package/src/components/shared/navigation/SCatalogNavigator.vue +308 -0
- package/src/components/shared/navigation/STab.vue +363 -0
- package/src/components/shared/navigation/STabList.vue +442 -0
- package/src/components/shared/navigation/STabPanel.vue +165 -0
- package/src/components/shared/navigation/STabPanels.vue +33 -0
- package/src/components/shared/navigation/STabs.vue +617 -0
- package/src/components/shared/navigation/SWizardSteps.css +69 -0
- package/src/components/shared/navigation/SWizardSteps.vue +171 -0
- package/src/components/shared/navigation/_internal/SCatalogNavigatorGroup.vue +63 -0
- package/src/components/shared/persona/SPersonaProjectPicker.css +36 -0
- package/src/components/shared/persona/SPersonaProjectPicker.vue +361 -0
- package/src/components/shared/persona/SPersonaRunWorkbench.css +109 -0
- package/src/components/shared/persona/SPersonaRunWorkbench.vue +549 -0
- package/src/components/shared/persona/_internal/SPersonaRunConsole.css +40 -0
- package/src/components/shared/persona/_internal/SPersonaRunConsole.vue +298 -0
- package/src/components/shared/persona/_internal/inventory.ts +110 -0
- package/src/components/shared/persona/_internal/labels.ts +79 -0
- package/src/components/shared/persona/presentation.ts +371 -0
- package/src/components/shared/persona/types.ts +256 -0
- package/src/composables/uiPreferencesReset.ts +52 -0
- package/src/composables/useClipboard.ts +37 -0
- package/src/composables/useDialogOverlay.ts +85 -0
- package/src/composables/useDisclosure.ts +40 -0
- package/src/composables/useFloatingPosition.ts +245 -0
- package/src/composables/useNotifier.ts +26 -0
- package/src/composables/useSidebarPanelState.ts +610 -0
- package/src/internal/codeEditorContract.ts +110 -0
- package/src/internal/codeEditorExtensions.ts +27 -0
- package/src/internal/feedbackPresentation.ts +136 -0
- package/src/internal/fieldSurface.ts +59 -0
- package/src/internal/focusNavigation.ts +69 -0
- package/src/internal/inlineTokenEditorContract.ts +136 -0
- package/src/internal/interactiveElement.ts +33 -0
- package/src/internal/layerStack.ts +202 -0
- package/src/internal/linkTarget.ts +256 -0
- package/src/internal/ownedAttrs.ts +464 -0
- package/src/internal/passiveContentContract.ts +129 -0
- package/src/internal/pointerInteractionLease.ts +81 -0
- package/src/internal/runtimeContract.ts +194 -0
- package/src/internal/runtimeTheme.ts +159 -0
- package/src/internal/semanticSizing.ts +60 -0
- package/src/internal/useMenuLayer.ts +129 -0
- package/src/stores/useNotifierStore.ts +69 -0
- package/src/styles/reference.css +4 -0
- package/src/styles/style.css +151 -0
- package/src/styles/tokens.css +836 -0
- package/src/theme.ts +449 -0
- package/src/types/highlight-js-lib.d.ts +76 -0
- package/src/variants.ts +15 -0
package/src/theme.ts
ADDED
|
@@ -0,0 +1,449 @@
|
|
|
1
|
+
export const themeFamilies = ['classic', 'pink', 'green'] as const;
|
|
2
|
+
export type ThemeFamily = (typeof themeFamilies)[number];
|
|
3
|
+
|
|
4
|
+
export const themeModes = ['light', 'dark'] as const;
|
|
5
|
+
export type ThemeMode = (typeof themeModes)[number];
|
|
6
|
+
|
|
7
|
+
export const themePreferences = ['light', 'dark', 'pink-light', 'pink-dark', 'green-light', 'green-dark'] as const;
|
|
8
|
+
export type ThemePreference = (typeof themePreferences)[number];
|
|
9
|
+
|
|
10
|
+
export const themeIds = ['classic-light', 'classic-dark', 'pink-light', 'pink-dark', 'green-light', 'green-dark'] as const;
|
|
11
|
+
export type ThemeId = (typeof themeIds)[number];
|
|
12
|
+
|
|
13
|
+
export interface StoredThemeState {
|
|
14
|
+
themeId: ThemeId;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface ThemeVariant {
|
|
18
|
+
id: ThemeId;
|
|
19
|
+
family: ThemeFamily;
|
|
20
|
+
mode: ThemeMode;
|
|
21
|
+
dataTheme: ThemePreference;
|
|
22
|
+
browserThemeColor: string;
|
|
23
|
+
statusBarStyle: 'default' | 'black-translucent';
|
|
24
|
+
label: string;
|
|
25
|
+
familyLabel: string;
|
|
26
|
+
modeLabel: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export const defaultThemeId: ThemeId = 'classic-light';
|
|
30
|
+
|
|
31
|
+
export const themeVariants = [
|
|
32
|
+
{
|
|
33
|
+
id: 'classic-light',
|
|
34
|
+
family: 'classic',
|
|
35
|
+
mode: 'light',
|
|
36
|
+
dataTheme: 'light',
|
|
37
|
+
browserThemeColor: '#f8fafc',
|
|
38
|
+
statusBarStyle: 'default',
|
|
39
|
+
label: 'Классическая светлая',
|
|
40
|
+
familyLabel: 'Классическая',
|
|
41
|
+
modeLabel: 'Светлая',
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
id: 'classic-dark',
|
|
45
|
+
family: 'classic',
|
|
46
|
+
mode: 'dark',
|
|
47
|
+
dataTheme: 'dark',
|
|
48
|
+
browserThemeColor: '#020617',
|
|
49
|
+
statusBarStyle: 'black-translucent',
|
|
50
|
+
label: 'Классическая тёмная',
|
|
51
|
+
familyLabel: 'Классическая',
|
|
52
|
+
modeLabel: 'Тёмная',
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
id: 'pink-light',
|
|
56
|
+
family: 'pink',
|
|
57
|
+
mode: 'light',
|
|
58
|
+
dataTheme: 'pink-light',
|
|
59
|
+
browserThemeColor: '#fff6fb',
|
|
60
|
+
statusBarStyle: 'default',
|
|
61
|
+
label: 'Розовая светлая',
|
|
62
|
+
familyLabel: 'Розовая',
|
|
63
|
+
modeLabel: 'Светлая',
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
id: 'pink-dark',
|
|
67
|
+
family: 'pink',
|
|
68
|
+
mode: 'dark',
|
|
69
|
+
dataTheme: 'pink-dark',
|
|
70
|
+
browserThemeColor: '#020617',
|
|
71
|
+
statusBarStyle: 'black-translucent',
|
|
72
|
+
label: 'Розовая тёмная',
|
|
73
|
+
familyLabel: 'Розовая',
|
|
74
|
+
modeLabel: 'Тёмная',
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
id: 'green-light',
|
|
78
|
+
family: 'green',
|
|
79
|
+
mode: 'light',
|
|
80
|
+
dataTheme: 'green-light',
|
|
81
|
+
browserThemeColor: '#f5fbf6',
|
|
82
|
+
statusBarStyle: 'default',
|
|
83
|
+
label: 'Зелёная светлая',
|
|
84
|
+
familyLabel: 'Зелёная',
|
|
85
|
+
modeLabel: 'Светлая',
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
id: 'green-dark',
|
|
89
|
+
family: 'green',
|
|
90
|
+
mode: 'dark',
|
|
91
|
+
dataTheme: 'green-dark',
|
|
92
|
+
browserThemeColor: '#04120b',
|
|
93
|
+
statusBarStyle: 'black-translucent',
|
|
94
|
+
label: 'Зелёная тёмная',
|
|
95
|
+
familyLabel: 'Зелёная',
|
|
96
|
+
modeLabel: 'Тёмная',
|
|
97
|
+
},
|
|
98
|
+
] as const satisfies readonly ThemeVariant[];
|
|
99
|
+
|
|
100
|
+
const themeVariantMap = new Map<ThemeId, ThemeVariant>(
|
|
101
|
+
themeVariants.map((variant): [ThemeId, ThemeVariant] => [variant.id, variant]),
|
|
102
|
+
);
|
|
103
|
+
const themeFamilyValues = new Set<string>(themeFamilies);
|
|
104
|
+
const themeModeValues = new Set<string>(themeModes);
|
|
105
|
+
const themePreferenceValues = new Set<string>(themePreferences);
|
|
106
|
+
const themeIdValues = new Set<string>(themeIds);
|
|
107
|
+
|
|
108
|
+
const THEME_COLOR_META_NAME = 'theme-color';
|
|
109
|
+
const APPLE_STATUS_BAR_META_NAME = 'apple-mobile-web-app-status-bar-style';
|
|
110
|
+
|
|
111
|
+
/** Имена browser meta, принадлежащих theme document owner. / Browser meta names owned by the theme document contract. */
|
|
112
|
+
export const documentThemeMetaNames = Object.freeze({
|
|
113
|
+
themeColor: THEME_COLOR_META_NAME,
|
|
114
|
+
appleStatusBar: APPLE_STATUS_BAR_META_NAME,
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
export function isThemeFamily(value: unknown): value is ThemeFamily {
|
|
118
|
+
return typeof value === 'string' && themeFamilyValues.has(value);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export function isThemeMode(value: unknown): value is ThemeMode {
|
|
122
|
+
return typeof value === 'string' && themeModeValues.has(value);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export function isThemePreference(value: unknown): value is ThemePreference {
|
|
126
|
+
return typeof value === 'string' && themePreferenceValues.has(value);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export function isThemeId(value: unknown): value is ThemeId {
|
|
130
|
+
return typeof value === 'string' && themeIdValues.has(value);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export function getThemeVariant(themeId: ThemeId): ThemeVariant {
|
|
134
|
+
const variant = themeVariantMap.get(themeId);
|
|
135
|
+
if (variant === undefined) {
|
|
136
|
+
throw new Error(`Неизвестный идентификатор темы: ${themeId}`);
|
|
137
|
+
}
|
|
138
|
+
return variant;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function assertCanonicalThemeVariant(variant: ThemeVariant): ThemeVariant {
|
|
142
|
+
const canonical = getThemeVariant(variant.id);
|
|
143
|
+
const fields = [
|
|
144
|
+
'family',
|
|
145
|
+
'mode',
|
|
146
|
+
'dataTheme',
|
|
147
|
+
'browserThemeColor',
|
|
148
|
+
'statusBarStyle',
|
|
149
|
+
'label',
|
|
150
|
+
'familyLabel',
|
|
151
|
+
'modeLabel',
|
|
152
|
+
] as const satisfies readonly (keyof ThemeVariant)[];
|
|
153
|
+
const mismatched = fields.filter((field) => variant[field] !== canonical[field]);
|
|
154
|
+
if (mismatched.length > 0) {
|
|
155
|
+
throw new Error(
|
|
156
|
+
`Theme variant "${variant.id}" не совпадает с canonical theme registry: ${mismatched.join(', ')}. `
|
|
157
|
+
+ `Theme variant "${variant.id}" does not match the canonical theme registry: ${mismatched.join(', ')}.`,
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
return canonical;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function findDocumentThemeMeta(targetDocument: Document, name: string): HTMLMetaElement | null {
|
|
164
|
+
const matches = [...targetDocument.head.querySelectorAll<HTMLMetaElement>(`meta[name="${name}"]`)];
|
|
165
|
+
if (matches.length > 1) {
|
|
166
|
+
throw new Error(
|
|
167
|
+
`UI-kit theme document: для "${name}" найдено ${matches.length} meta; ожидается не более одного owner. `
|
|
168
|
+
+ `UI-kit theme document: found ${matches.length} meta elements for "${name}"; at most one owner is allowed.`,
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
const existing = matches[0];
|
|
172
|
+
if (existing === undefined) return null;
|
|
173
|
+
if (existing.tagName !== 'META') {
|
|
174
|
+
throw new TypeError(`UI-kit theme document: meta owner "${name}" имеет неверный DOM type.`);
|
|
175
|
+
}
|
|
176
|
+
return existing;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function createDocumentThemeMeta(targetDocument: Document, name: string): HTMLMetaElement {
|
|
180
|
+
const meta = targetDocument.createElement('meta');
|
|
181
|
+
meta.name = name;
|
|
182
|
+
meta.dataset.sUiKitThemeMeta = 'true';
|
|
183
|
+
targetDocument.head.appendChild(meta);
|
|
184
|
+
return meta;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
interface DocumentThemeMetaSnapshot {
|
|
188
|
+
readonly element: HTMLMetaElement | null;
|
|
189
|
+
readonly content: string | null;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
interface DocumentThemeSnapshot {
|
|
193
|
+
readonly hadDarkClass: boolean;
|
|
194
|
+
readonly dataTheme: ThemePreference | null;
|
|
195
|
+
readonly themeColor: DocumentThemeMetaSnapshot;
|
|
196
|
+
readonly appleStatusBar: DocumentThemeMetaSnapshot;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
interface DocumentThemeScopeLayer {
|
|
200
|
+
readonly themeId: ThemeId;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
interface DocumentThemeScopeRuntime {
|
|
204
|
+
readonly snapshot: DocumentThemeSnapshot;
|
|
205
|
+
readonly themeColor: HTMLMetaElement;
|
|
206
|
+
readonly appleStatusBar: HTMLMetaElement;
|
|
207
|
+
readonly layers: DocumentThemeScopeLayer[];
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const documentThemeScopeRuntimes = new WeakMap<Document, DocumentThemeScopeRuntime>();
|
|
211
|
+
|
|
212
|
+
function captureDocumentThemeMeta(targetDocument: Document, name: string): DocumentThemeMetaSnapshot {
|
|
213
|
+
const element = findDocumentThemeMeta(targetDocument, name);
|
|
214
|
+
return {
|
|
215
|
+
element,
|
|
216
|
+
content: element === null ? null : element.getAttribute('content'),
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function captureDocumentTheme(targetDocument: Document): DocumentThemeSnapshot {
|
|
221
|
+
const hadDarkClass = targetDocument.documentElement.classList.contains('dark');
|
|
222
|
+
const dataTheme = targetDocument.documentElement.getAttribute('data-theme');
|
|
223
|
+
if (dataTheme === null && hadDarkClass) {
|
|
224
|
+
throw new Error('UI-kit theme scope: class="dark" требует canonical data-theme.');
|
|
225
|
+
}
|
|
226
|
+
if (dataTheme !== null) {
|
|
227
|
+
if (!isThemePreference(dataTheme)) {
|
|
228
|
+
throw new TypeError(`UI-kit theme scope: неизвестный data-theme "${dataTheme}".`);
|
|
229
|
+
}
|
|
230
|
+
const variant = themeVariants.find((item) => item.dataTheme === dataTheme);
|
|
231
|
+
if (variant === undefined || (variant.mode === 'dark') !== hadDarkClass) {
|
|
232
|
+
throw new Error(`UI-kit theme scope: data-theme="${dataTheme}" конфликтует с class="dark".`);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
return {
|
|
236
|
+
hadDarkClass,
|
|
237
|
+
dataTheme,
|
|
238
|
+
themeColor: captureDocumentThemeMeta(targetDocument, THEME_COLOR_META_NAME),
|
|
239
|
+
appleStatusBar: captureDocumentThemeMeta(targetDocument, APPLE_STATUS_BAR_META_NAME),
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function assertDocumentThemeMetaRestorable(
|
|
244
|
+
targetDocument: Document,
|
|
245
|
+
name: string,
|
|
246
|
+
snapshot: DocumentThemeMetaSnapshot,
|
|
247
|
+
appliedElement: HTMLMetaElement,
|
|
248
|
+
): void {
|
|
249
|
+
const current = findDocumentThemeMeta(targetDocument, name);
|
|
250
|
+
const expected = snapshot.element === null ? appliedElement : snapshot.element;
|
|
251
|
+
if (current !== expected) {
|
|
252
|
+
throw new Error(`UI-kit theme scope: meta owner "${name}" был заменён до восстановления.`);
|
|
253
|
+
}
|
|
254
|
+
if (snapshot.element === null && current.dataset.sUiKitThemeMeta !== 'true') {
|
|
255
|
+
throw new Error(`UI-kit theme scope: meta owner "${name}" изменён вне canonical scope.`);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function restoreDocumentThemeMeta(
|
|
260
|
+
snapshot: DocumentThemeMetaSnapshot,
|
|
261
|
+
appliedElement: HTMLMetaElement,
|
|
262
|
+
): void {
|
|
263
|
+
if (snapshot.element === null) {
|
|
264
|
+
appliedElement.remove();
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
if (snapshot.content === null) snapshot.element.removeAttribute('content');
|
|
268
|
+
else snapshot.element.content = snapshot.content;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function activeDocumentThemeScopeLayer(runtime: DocumentThemeScopeRuntime): DocumentThemeScopeLayer {
|
|
272
|
+
const layer = runtime.layers[runtime.layers.length - 1];
|
|
273
|
+
if (layer === undefined) {
|
|
274
|
+
throw new Error('UI-kit theme scope: runtime не содержит активного owner.');
|
|
275
|
+
}
|
|
276
|
+
return layer;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
function assertActiveDocumentThemeScope(
|
|
280
|
+
targetDocument: Document,
|
|
281
|
+
runtime: DocumentThemeScopeRuntime,
|
|
282
|
+
): void {
|
|
283
|
+
const variant = getThemeVariant(activeDocumentThemeScopeLayer(runtime).themeId);
|
|
284
|
+
const root = targetDocument.documentElement;
|
|
285
|
+
if (root.getAttribute('data-theme') !== variant.dataTheme
|
|
286
|
+
|| root.classList.contains('dark') !== (variant.mode === 'dark')) {
|
|
287
|
+
throw new Error(
|
|
288
|
+
`UI-kit theme scope: active owner ${JSON.stringify(variant.id)} был изменён вне canonical scope.`,
|
|
289
|
+
);
|
|
290
|
+
}
|
|
291
|
+
assertDocumentThemeMetaRestorable(
|
|
292
|
+
targetDocument,
|
|
293
|
+
THEME_COLOR_META_NAME,
|
|
294
|
+
runtime.snapshot.themeColor,
|
|
295
|
+
runtime.themeColor,
|
|
296
|
+
);
|
|
297
|
+
assertDocumentThemeMetaRestorable(
|
|
298
|
+
targetDocument,
|
|
299
|
+
APPLE_STATUS_BAR_META_NAME,
|
|
300
|
+
runtime.snapshot.appleStatusBar,
|
|
301
|
+
runtime.appleStatusBar,
|
|
302
|
+
);
|
|
303
|
+
if (runtime.themeColor.content !== variant.browserThemeColor
|
|
304
|
+
|| runtime.appleStatusBar.content !== variant.statusBarStyle) {
|
|
305
|
+
throw new Error(
|
|
306
|
+
`UI-kit theme scope: browser meta active owner ${JSON.stringify(variant.id)} изменены вне canonical scope.`,
|
|
307
|
+
);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
function restoreDocumentThemeScope(
|
|
312
|
+
targetDocument: Document,
|
|
313
|
+
runtime: DocumentThemeScopeRuntime,
|
|
314
|
+
): void {
|
|
315
|
+
const snapshot = runtime.snapshot;
|
|
316
|
+
targetDocument.documentElement.classList.toggle('dark', snapshot.hadDarkClass);
|
|
317
|
+
if (snapshot.dataTheme === null) targetDocument.documentElement.removeAttribute('data-theme');
|
|
318
|
+
else targetDocument.documentElement.setAttribute('data-theme', snapshot.dataTheme);
|
|
319
|
+
restoreDocumentThemeMeta(snapshot.themeColor, runtime.themeColor);
|
|
320
|
+
restoreDocumentThemeMeta(snapshot.appleStatusBar, runtime.appleStatusBar);
|
|
321
|
+
documentThemeScopeRuntimes.delete(targetDocument);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* Атомарно применяет canonical theme variant к class, data-theme и browser meta одного документа.
|
|
326
|
+
* Atomically applies a canonical theme variant to one document's class, data-theme, and browser meta contract.
|
|
327
|
+
*/
|
|
328
|
+
export function applyThemeVariantToDocument(targetDocument: Document, candidate: ThemeVariant): void {
|
|
329
|
+
const variant = assertCanonicalThemeVariant(candidate);
|
|
330
|
+
const themeColorMeta = findDocumentThemeMeta(targetDocument, THEME_COLOR_META_NAME);
|
|
331
|
+
const statusBarMeta = findDocumentThemeMeta(targetDocument, APPLE_STATUS_BAR_META_NAME);
|
|
332
|
+
const resolvedThemeColorMeta = themeColorMeta === null
|
|
333
|
+
? createDocumentThemeMeta(targetDocument, THEME_COLOR_META_NAME)
|
|
334
|
+
: themeColorMeta;
|
|
335
|
+
const resolvedStatusBarMeta = statusBarMeta === null
|
|
336
|
+
? createDocumentThemeMeta(targetDocument, APPLE_STATUS_BAR_META_NAME)
|
|
337
|
+
: statusBarMeta;
|
|
338
|
+
|
|
339
|
+
targetDocument.documentElement.classList.toggle('dark', variant.mode === 'dark');
|
|
340
|
+
targetDocument.documentElement.dataset.theme = variant.dataTheme;
|
|
341
|
+
resolvedThemeColorMeta.content = variant.browserThemeColor;
|
|
342
|
+
resolvedStatusBarMeta.content = variant.statusBarStyle;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* Применяет зарегистрированный ThemeId через единственного document owner.
|
|
347
|
+
* Applies a registered ThemeId through the single document owner.
|
|
348
|
+
*/
|
|
349
|
+
export function applyThemeIdToDocument(targetDocument: Document, themeId: ThemeId): void {
|
|
350
|
+
applyThemeVariantToDocument(targetDocument, getThemeVariant(themeId));
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
/**
|
|
354
|
+
* Временно применяет canonical тему и возвращает идемпотентное точное восстановление document state.
|
|
355
|
+
* Temporarily applies a canonical theme and returns an idempotent exact document-state restore.
|
|
356
|
+
*/
|
|
357
|
+
export function applyScopedThemeIdToDocument(targetDocument: Document, themeId: ThemeId): () => void {
|
|
358
|
+
const variant = getThemeVariant(themeId);
|
|
359
|
+
let runtime = documentThemeScopeRuntimes.get(targetDocument);
|
|
360
|
+
if (runtime !== undefined) assertActiveDocumentThemeScope(targetDocument, runtime);
|
|
361
|
+
const snapshot = runtime === undefined ? captureDocumentTheme(targetDocument) : runtime.snapshot;
|
|
362
|
+
applyThemeIdToDocument(targetDocument, themeId);
|
|
363
|
+
const appliedThemeColor = findDocumentThemeMeta(targetDocument, THEME_COLOR_META_NAME);
|
|
364
|
+
const appliedAppleStatusBar = findDocumentThemeMeta(targetDocument, APPLE_STATUS_BAR_META_NAME);
|
|
365
|
+
if (appliedThemeColor === null || appliedAppleStatusBar === null) {
|
|
366
|
+
throw new Error('UI-kit theme scope: canonical applier не создал обязательные browser meta owners.');
|
|
367
|
+
}
|
|
368
|
+
if (runtime === undefined) {
|
|
369
|
+
runtime = {
|
|
370
|
+
snapshot,
|
|
371
|
+
themeColor: appliedThemeColor,
|
|
372
|
+
appleStatusBar: appliedAppleStatusBar,
|
|
373
|
+
layers: [],
|
|
374
|
+
};
|
|
375
|
+
documentThemeScopeRuntimes.set(targetDocument, runtime);
|
|
376
|
+
} else if (runtime.themeColor !== appliedThemeColor || runtime.appleStatusBar !== appliedAppleStatusBar) {
|
|
377
|
+
throw new Error('UI-kit theme scope: canonical browser meta owners были заменены при apply.');
|
|
378
|
+
}
|
|
379
|
+
const layer: DocumentThemeScopeLayer = { themeId: variant.id };
|
|
380
|
+
runtime.layers.push(layer);
|
|
381
|
+
let active = true;
|
|
382
|
+
return () => {
|
|
383
|
+
if (!active) return;
|
|
384
|
+
const currentRuntime = documentThemeScopeRuntimes.get(targetDocument);
|
|
385
|
+
if (currentRuntime !== runtime) {
|
|
386
|
+
throw new Error('UI-kit theme scope: per-document runtime был заменён до cleanup.');
|
|
387
|
+
}
|
|
388
|
+
assertActiveDocumentThemeScope(targetDocument, currentRuntime);
|
|
389
|
+
const layerIndex = currentRuntime.layers.indexOf(layer);
|
|
390
|
+
if (layerIndex < 0) {
|
|
391
|
+
throw new Error('UI-kit theme scope: active owner отсутствует в per-document runtime.');
|
|
392
|
+
}
|
|
393
|
+
currentRuntime.layers.splice(layerIndex, 1);
|
|
394
|
+
if (currentRuntime.layers.length === 0) restoreDocumentThemeScope(targetDocument, currentRuntime);
|
|
395
|
+
else applyThemeIdToDocument(targetDocument, activeDocumentThemeScopeLayer(currentRuntime).themeId);
|
|
396
|
+
active = false;
|
|
397
|
+
};
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
export function getThemeVariantsForFamily(family: ThemeFamily): ThemeVariant[] {
|
|
401
|
+
return themeVariants.filter((variant) => variant.family === family);
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
export function getThemeModesForFamily(family: ThemeFamily): ThemeMode[] {
|
|
405
|
+
return getThemeVariantsForFamily(family).map((variant) => variant.mode);
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
export function resolveThemeId(
|
|
409
|
+
family: ThemeFamily,
|
|
410
|
+
mode: ThemeMode,
|
|
411
|
+
): ThemeId | null {
|
|
412
|
+
const match = themeVariants.find((variant) => variant.family === family && variant.mode === mode);
|
|
413
|
+
return match === undefined ? null : match.id;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
export function resolveThemePreference(themeId: ThemeId): ThemePreference {
|
|
417
|
+
return getThemeVariant(themeId).dataTheme;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
export function getThemeFamilyLabel(family: ThemeFamily): string {
|
|
421
|
+
const variant = themeVariants.find((item) => item.family === family);
|
|
422
|
+
if (variant === undefined) {
|
|
423
|
+
throw new Error(`Неизвестное семейство темы: ${family}`);
|
|
424
|
+
}
|
|
425
|
+
return variant.familyLabel;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
export function getThemeModeLabel(mode: ThemeMode): string {
|
|
429
|
+
if (mode === 'light') {
|
|
430
|
+
return 'Светлая';
|
|
431
|
+
}
|
|
432
|
+
if (mode === 'dark') {
|
|
433
|
+
return 'Тёмная';
|
|
434
|
+
}
|
|
435
|
+
throw new Error(`Неизвестный режим темы: ${String(mode)}`);
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
export function resolveLegacyThemeId(value: unknown): ThemeId | null {
|
|
439
|
+
if (value === 'light') {
|
|
440
|
+
return 'classic-light';
|
|
441
|
+
}
|
|
442
|
+
if (value === 'dark') {
|
|
443
|
+
return 'classic-dark';
|
|
444
|
+
}
|
|
445
|
+
if (value === 'pink-dark') {
|
|
446
|
+
return 'pink-dark';
|
|
447
|
+
}
|
|
448
|
+
return null;
|
|
449
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
declare module 'highlight.js/lib/core' {
|
|
2
|
+
import type { HLJSApi } from 'highlight.js';
|
|
3
|
+
|
|
4
|
+
const hljs: HLJSApi;
|
|
5
|
+
export default hljs;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
declare module 'highlight.js/lib/languages/bash' {
|
|
9
|
+
import type { LanguageFn } from 'highlight.js';
|
|
10
|
+
|
|
11
|
+
const language: LanguageFn;
|
|
12
|
+
export default language;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
declare module 'highlight.js/lib/languages/dockerfile' {
|
|
16
|
+
import type { LanguageFn } from 'highlight.js';
|
|
17
|
+
|
|
18
|
+
const language: LanguageFn;
|
|
19
|
+
export default language;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
declare module 'highlight.js/lib/languages/javascript' {
|
|
23
|
+
import type { LanguageFn } from 'highlight.js';
|
|
24
|
+
|
|
25
|
+
const language: LanguageFn;
|
|
26
|
+
export default language;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
declare module 'highlight.js/lib/languages/makefile' {
|
|
30
|
+
import type { LanguageFn } from 'highlight.js';
|
|
31
|
+
|
|
32
|
+
const language: LanguageFn;
|
|
33
|
+
export default language;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
declare module 'highlight.js/lib/languages/nginx' {
|
|
37
|
+
import type { LanguageFn } from 'highlight.js';
|
|
38
|
+
|
|
39
|
+
const language: LanguageFn;
|
|
40
|
+
export default language;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
declare module 'highlight.js/lib/languages/powershell' {
|
|
44
|
+
import type { LanguageFn } from 'highlight.js';
|
|
45
|
+
|
|
46
|
+
const language: LanguageFn;
|
|
47
|
+
export default language;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
declare module 'highlight.js/lib/languages/python' {
|
|
51
|
+
import type { LanguageFn } from 'highlight.js';
|
|
52
|
+
|
|
53
|
+
const language: LanguageFn;
|
|
54
|
+
export default language;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
declare module 'highlight.js/lib/languages/typescript' {
|
|
58
|
+
import type { LanguageFn } from 'highlight.js';
|
|
59
|
+
|
|
60
|
+
const language: LanguageFn;
|
|
61
|
+
export default language;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
declare module 'highlight.js/lib/languages/xml' {
|
|
65
|
+
import type { LanguageFn } from 'highlight.js';
|
|
66
|
+
|
|
67
|
+
const language: LanguageFn;
|
|
68
|
+
export default language;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
declare module 'highlight.js/lib/languages/yaml' {
|
|
72
|
+
import type { LanguageFn } from 'highlight.js';
|
|
73
|
+
|
|
74
|
+
const language: LanguageFn;
|
|
75
|
+
export default language;
|
|
76
|
+
}
|
package/src/variants.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export const toneVariants = [
|
|
2
|
+
'primary',
|
|
3
|
+
'secondary',
|
|
4
|
+
'success',
|
|
5
|
+
'info',
|
|
6
|
+
'warn',
|
|
7
|
+
'danger',
|
|
8
|
+
'contrast',
|
|
9
|
+
'neutral',
|
|
10
|
+
] as const;
|
|
11
|
+
|
|
12
|
+
export type ToneVariant = (typeof toneVariants)[number];
|
|
13
|
+
|
|
14
|
+
export const sizeVariants = ['sm', 'md', 'lg'] as const;
|
|
15
|
+
export type SizeVariant = (typeof sizeVariants)[number];
|