@byndyusoft-ui/view 0.0.0-stage → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,3 +1,137 @@
1
- # Temporary Holding Version
1
+ # `@byndyusoft-ui/view`
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ ---
4
+
5
+ > Фабрика базового компонента раскладки с типизированными отступами. `createView`
6
+ > создаёт компонент под собственную сетку отступов вашего проекта.
7
+
8
+ ## Установка
9
+
10
+ ```sh
11
+ npm i @byndyusoft-ui/view
12
+ ```
13
+
14
+ ## Фабрика createView (своя сетка)
15
+
16
+ `createView` принимает конфигурацию — базовый шаг и набор именованных токенов с
17
+ множителями шага — и возвращает компонент, типизированный токенами вашей сетки.
18
+ Объект конфигурации обязательно помечайте `as const`: типы токенов выводятся из него.
19
+
20
+ ```tsx
21
+ // src/shared/ui/layout.ts
22
+ import { createView } from '@byndyusoft-ui/view';
23
+
24
+ export const View = createView({
25
+ step: { value: 8, unit: 'px' },
26
+ values: { xs: 0.25, sm: 0.5, md: 1, lg: 1.5, xl: 2, xxl: 3 }
27
+ } as const);
28
+ ```
29
+
30
+ Поддерживаются единицы `px` и `rem`; числовое значение шага должно быть положительным
31
+ целым числом. `prefix` задаёт namespace CSS-классов и по умолчанию равен `bsui`:
32
+
33
+ ```tsx
34
+ export const AppView = createView({
35
+ step: { value: 8, unit: 'px' },
36
+ prefix: 'app',
37
+ values: { sm: 0.5, md: 1 }
38
+ } as const);
39
+ ```
40
+
41
+ ```tsx
42
+ import { View } from '@/shared/ui/layout';
43
+
44
+ // padding: 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'xxl' — автокомплит и проверка типов
45
+ <View p="lg" mb="md" />;
46
+ ```
47
+
48
+ Короткие aliases доступны для всех margin- и padding-пропсов: `m`, `mt`, `mr`, `mb`,
49
+ `ml`, `mh`, `mv` и `p`, `pt`, `pr`, `pb`, `pl`, `ph`, `pv`. Полные имена продолжают
50
+ поддерживаться и принимают те же токены.
51
+
52
+ Если полная форма и её alias заданы одновременно, используется последняя определённая
53
+ запись в порядке ключей итогового props-объекта; `undefined` считается отсутствующим.
54
+ Для разных свойств действует приоритет: общий `margin`/`padding` перекрывает осевые и
55
+ сторонние props, а `marginHorizontal`/`paddingHorizontal` и вертикальные варианты
56
+ перекрывают соответствующие стороны.
57
+
58
+ ```tsx
59
+ <View margin="sm" mt="md" />; // margin применяется ко всем сторонам
60
+ <View mh="lg" ml="sm" />; // mh применяется слева и справа
61
+ ```
62
+
63
+ CSS генерируется автоматически при создании компонента: классы `bsui-p-lg`, `bsui-mv-md`
64
+ и т.п. получают готовые длины, рассчитанные из шага и множителя токена (например,
65
+ `8px * 1.5` даёт `12px`). Дробные множители поддерживаются. Шаг фиксирован для созданной
66
+ фабрики; чтобы изменить его, создайте компонент с другой конфигурацией. Inline-стили не
67
+ используются, атрибут `style` не выставляется.
68
+ Если все четыре эффективных стороны имеют одинаковый токен, используется один общий
69
+ класс (`bsui-m-md` или `bsui-p-md`); одинаковые горизонтальные/вертикальные пары
70
+ сворачиваются в `mh`/`mv` или `ph`/`pv`. Сравниваются имена токенов, не множители.
71
+
72
+ ```tsx
73
+ <View marginTop="md" marginRight="md" marginBottom="md" marginLeft="md" />
74
+ // применяет один класс bsui-m-md
75
+ ```
76
+
77
+ ### Namespace CSS-классов
78
+
79
+ Без `prefix` имена классов используют namespace `bsui`. Для нескольких сеток на странице
80
+ задайте каждой уникальный префикс, чтобы избежать пересечения селекторов. Например,
81
+ `prefix: 'app'` создаёт классы `app-p-md` и `app-flex--g-md`.
82
+
83
+ Префикс должен начинаться с латинской буквы или `_`; далее допустимы латинские буквы,
84
+ цифры, дефис и `_`.
85
+
86
+ ### SSR
87
+
88
+ В браузере стили инъецируются автоматически при импорте. Для SSR добавьте компонент
89
+ `ViewStylesInjector` в корень приложения (или в `<head>` шаблона): серверный HTML будет
90
+ содержать стили, а при гидратации дубли не создаются.
91
+
92
+ ```tsx
93
+ import { ViewStylesInjector } from '@byndyusoft-ui/view';
94
+
95
+ <html>
96
+ <head>
97
+ <ViewStylesInjector />
98
+ </head>
99
+ <body>...</body>
100
+ </html>;
101
+ ```
102
+
103
+ ### Ограничения токенов
104
+
105
+ Ключи сетки становятся частью имён CSS-классов, поэтому допустимы только латиница,
106
+ цифры, дефис и подчёркивание (`'025'`, `'s-1'`, `'token_name'`). Недопустимый ключ
107
+ вызывает ошибку при создании компонента.
108
+
109
+ ## Props
110
+
111
+ | Проп | Тип | Описание |
112
+ | --------------------------------------------------------------------------------------------------------------- | ----------------- | -------------------------------------------------- |
113
+ | `as` | `keyof ReactHTML` | Тег или компонент для рендера (по умолчанию `div`) |
114
+ | `className` | `string` | Дополнительный класс, мержится со сгенерированными |
115
+ | `margin`, `marginTop`, `marginRight`, `marginBottom`, `marginLeft`, `marginHorizontal`, `marginVertical` | токен сетки | Внешние отступы |
116
+ | `m`, `mt`, `mr`, `mb`, `ml`, `mh`, `mv` | токен сетки | Короткие aliases внешних отступов |
117
+ | `padding`, `paddingTop`, `paddingRight`, `paddingBottom`, `paddingLeft`, `paddingHorizontal`, `paddingVertical` | токен сетки | Внутренние отступы |
118
+ | `p`, `pt`, `pr`, `pb`, `pl`, `ph`, `pv` | токен сетки | Короткие aliases внутренних отступов |
119
+
120
+ Остальные нативные атрибуты (`id`, `data-*`, `aria-*`, обработчики) передаются в DOM как есть.
121
+
122
+ ## Утилиты и типы
123
+
124
+ Из точки входа пакета также доступны:
125
+
126
+ - `buildCss(config)` — генерация CSS отступов без регистрации стилей.
127
+ - `getSpacingClassName(prefix, token)` — имя класса, например `bsui-p-md`.
128
+ - `getSpacingValue(step, multiplier)` — готовая CSS-длина для шага `{ value, unit }`;
129
+ прежняя перегрузка со строковым юнитом оставлена для standalone-совместимости.
130
+ - `spacingPropPrefixes` — соответствие spacing-пропсов префиксам классов.
131
+ - `spacingUnitVariable` — прежнее имя CSS-переменной `--bsui-unit`; фабрики её не используют.
132
+ - `validateValuesKeys(grid)` — проверка допустимости имён токенов; при ошибке бросает исключение.
133
+ - `registerStyles(config, css)` и `ViewStylesInjector` — регистрация и вывод стилей, в том числе для SSR.
134
+
135
+ Публичные типы: `IViewConfig`, `TSpacingValues`, `TValuesToken`, `ISpacingProps`,
136
+ `ICreatedViewProps` и `ICreatedView`. Созданный компонент имеет `displayName = 'View'`
137
+ и сохраняет исходную конфигурацию в `View.config`. Передача DOM-ref не поддерживается.
@@ -0,0 +1,2 @@
1
+ import { FC } from 'react';
2
+ export declare const ViewStylesInjector: FC;
@@ -0,0 +1,9 @@
1
+ import React from 'react';
2
+ import { getStyleEntries } from './utilities/stylesRegistry.js';
3
+
4
+ const ViewStylesInjector = () => (React.createElement(React.Fragment, null, getStyleEntries().map(entry => (
5
+ // eslint-disable-next-line react/no-danger -- CSS генерируется библиотекой из валидированной конфигурации
6
+ React.createElement("style", { key: entry.id, id: entry.id, dangerouslySetInnerHTML: { __html: entry.css } })))));
7
+ ViewStylesInjector.displayName = 'ViewStylesInjector';
8
+
9
+ export { ViewStylesInjector };
@@ -0,0 +1,7 @@
1
+ import type { IResolvedSpacingProps, ISpacingProps } from './types';
2
+ export declare const spacingUnitVariable = "--bsui-unit";
3
+ export declare const spacingPropPrefixes: Record<keyof ISpacingProps, string>;
4
+ export declare const spacingPropAliases: Record<keyof ISpacingProps, keyof IResolvedSpacingProps>;
5
+ export declare const spacingSideProperties: Record<string, Array<string>>;
6
+ export declare const valuesTokenPattern: RegExp;
7
+ export declare const spacingPropsNames: (keyof ISpacingProps<string>)[];
@@ -0,0 +1,81 @@
1
+ const spacingUnitVariable = '--bsui-unit';
2
+ const spacingPropPrefixes = {
3
+ margin: 'm',
4
+ m: 'm',
5
+ marginTop: 'mt',
6
+ mt: 'mt',
7
+ marginRight: 'mr',
8
+ mr: 'mr',
9
+ marginBottom: 'mb',
10
+ mb: 'mb',
11
+ marginLeft: 'ml',
12
+ ml: 'ml',
13
+ marginHorizontal: 'mh',
14
+ mh: 'mh',
15
+ marginVertical: 'mv',
16
+ mv: 'mv',
17
+ padding: 'p',
18
+ p: 'p',
19
+ paddingTop: 'pt',
20
+ pt: 'pt',
21
+ paddingRight: 'pr',
22
+ pr: 'pr',
23
+ paddingBottom: 'pb',
24
+ pb: 'pb',
25
+ paddingLeft: 'pl',
26
+ pl: 'pl',
27
+ paddingHorizontal: 'ph',
28
+ ph: 'ph',
29
+ paddingVertical: 'pv',
30
+ pv: 'pv'
31
+ };
32
+ const spacingPropAliases = {
33
+ margin: 'margin',
34
+ m: 'margin',
35
+ marginTop: 'marginTop',
36
+ mt: 'marginTop',
37
+ marginRight: 'marginRight',
38
+ mr: 'marginRight',
39
+ marginBottom: 'marginBottom',
40
+ mb: 'marginBottom',
41
+ marginLeft: 'marginLeft',
42
+ ml: 'marginLeft',
43
+ marginHorizontal: 'marginHorizontal',
44
+ mh: 'marginHorizontal',
45
+ marginVertical: 'marginVertical',
46
+ mv: 'marginVertical',
47
+ padding: 'padding',
48
+ p: 'padding',
49
+ paddingTop: 'paddingTop',
50
+ pt: 'paddingTop',
51
+ paddingRight: 'paddingRight',
52
+ pr: 'paddingRight',
53
+ paddingBottom: 'paddingBottom',
54
+ pb: 'paddingBottom',
55
+ paddingLeft: 'paddingLeft',
56
+ pl: 'paddingLeft',
57
+ paddingHorizontal: 'paddingHorizontal',
58
+ ph: 'paddingHorizontal',
59
+ paddingVertical: 'paddingVertical',
60
+ pv: 'paddingVertical'
61
+ };
62
+ const spacingSideProperties = {
63
+ m: ['margin'],
64
+ mt: ['margin-top'],
65
+ mr: ['margin-right'],
66
+ mb: ['margin-bottom'],
67
+ ml: ['margin-left'],
68
+ mh: ['margin-left', 'margin-right'],
69
+ mv: ['margin-top', 'margin-bottom'],
70
+ p: ['padding'],
71
+ pt: ['padding-top'],
72
+ pr: ['padding-right'],
73
+ pb: ['padding-bottom'],
74
+ pl: ['padding-left'],
75
+ ph: ['padding-left', 'padding-right'],
76
+ pv: ['padding-top', 'padding-bottom']
77
+ };
78
+ const valuesTokenPattern = /^[A-Za-z0-9_-]+$/;
79
+ const spacingPropsNames = Object.keys(spacingPropPrefixes);
80
+
81
+ export { spacingPropAliases, spacingPropPrefixes, spacingPropsNames, spacingSideProperties, spacingUnitVariable, valuesTokenPattern };
@@ -0,0 +1,2 @@
1
+ import { ICreatedView, IViewConfig, TSpacingValues } from './types';
2
+ export declare function createView<TGrid extends TSpacingValues>(config: IViewConfig<TGrid>): ICreatedView<TGrid>;
@@ -0,0 +1,30 @@
1
+ import { createElement } from 'react';
2
+ import { buildCss } from './utilities/buildCss.js';
3
+ import { buildSpacingClassNames } from './utilities/buildSpacingClassNames.js';
4
+ import { spacingPropsNames } from './constants.js';
5
+ import { resolveSpacingProps } from './utilities/resolveSpacingProps.js';
6
+ import { registerStyles } from './utilities/stylesRegistry.js';
7
+ import { validateValuesKeys } from './utilities/validateValuesKeys.js';
8
+ import { validateViewConfig } from './utilities/validateViewConfig.js';
9
+
10
+ function createView(config) {
11
+ validateViewConfig(config);
12
+ validateValuesKeys(config.values);
13
+ registerStyles(config, buildCss(config));
14
+ const CreatedView = ({ as = 'div', className, children, ...props }) => {
15
+ const domProps = { ...props };
16
+ const resolvedSpacingProps = resolveSpacingProps(props);
17
+ spacingPropsNames.forEach(name => {
18
+ delete domProps[name];
19
+ });
20
+ const classNames = buildSpacingClassNames(resolvedSpacingProps, config.prefix);
21
+ if (className) {
22
+ classNames.push(className);
23
+ }
24
+ return createElement(as, { className: classNames.join(' ') || undefined, ...domProps }, children);
25
+ };
26
+ CreatedView.displayName = 'View';
27
+ return Object.assign(CreatedView, { config });
28
+ }
29
+
30
+ export { createView };
@@ -0,0 +1,5 @@
1
+ export { createView } from './createView';
2
+ export { ViewStylesInjector } from './ViewStylesInjector';
3
+ export { registerStyles, getStyleEntries, resetStyles, buildCss, getSpacingClassName, getSpacingValue, validateValuesKeys } from './utilities';
4
+ export { spacingUnitVariable, spacingPropPrefixes, spacingSideProperties, valuesTokenPattern, spacingPropsNames } from './constants';
5
+ export type { ISpacingStep, IViewConfig, TSpacingValues, TSpacingUnit, TValuesToken, ISpacingProps, ICreatedViewProps, ICreatedView, IViewStyleEntry as ILayoutStyleEntry } from './types';
package/dist/index.js ADDED
@@ -0,0 +1,8 @@
1
+ export { createView } from './createView.js';
2
+ export { ViewStylesInjector } from './ViewStylesInjector.js';
3
+ export { buildCss } from './utilities/buildCss.js';
4
+ export { spacingPropPrefixes, spacingPropsNames, spacingSideProperties, spacingUnitVariable, valuesTokenPattern } from './constants.js';
5
+ export { getSpacingClassName } from './utilities/getSpacingClassName.js';
6
+ export { getSpacingValue } from './utilities/getSpacingValue.js';
7
+ export { getStyleEntries, registerStyles, resetStyles } from './utilities/stylesRegistry.js';
8
+ export { validateValuesKeys } from './utilities/validateValuesKeys.js';
@@ -0,0 +1,56 @@
1
+ import { FC, HTMLAttributes, ReactHTML } from 'react';
2
+ export type TSpacingValues = Record<string, number>;
3
+ export type TSpacingUnit = 'px' | 'rem';
4
+ export interface ISpacingStep {
5
+ value: number;
6
+ unit: TSpacingUnit;
7
+ }
8
+ export interface IViewConfig<TValues extends TSpacingValues = TSpacingValues> {
9
+ step: ISpacingStep;
10
+ values: TValues;
11
+ prefix?: string;
12
+ }
13
+ export type TValuesToken<TValues extends TSpacingValues> = Extract<keyof TValues, string>;
14
+ export interface IResolvedSpacingProps<TToken extends string = string> {
15
+ margin?: TToken;
16
+ marginTop?: TToken;
17
+ marginRight?: TToken;
18
+ marginBottom?: TToken;
19
+ marginLeft?: TToken;
20
+ marginHorizontal?: TToken;
21
+ marginVertical?: TToken;
22
+ padding?: TToken;
23
+ paddingTop?: TToken;
24
+ paddingRight?: TToken;
25
+ paddingBottom?: TToken;
26
+ paddingLeft?: TToken;
27
+ paddingHorizontal?: TToken;
28
+ paddingVertical?: TToken;
29
+ }
30
+ export interface ISpacingProps<TToken extends string = string> extends IResolvedSpacingProps<TToken> {
31
+ m?: TToken;
32
+ mt?: TToken;
33
+ mr?: TToken;
34
+ mb?: TToken;
35
+ ml?: TToken;
36
+ mh?: TToken;
37
+ mv?: TToken;
38
+ p?: TToken;
39
+ pt?: TToken;
40
+ pr?: TToken;
41
+ pb?: TToken;
42
+ pl?: TToken;
43
+ ph?: TToken;
44
+ pv?: TToken;
45
+ }
46
+ export interface ICreatedViewProps<TGrid extends TSpacingValues> extends HTMLAttributes<HTMLElement>, ISpacingProps<TValuesToken<TGrid>> {
47
+ as?: keyof ReactHTML;
48
+ className?: string;
49
+ }
50
+ export interface ICreatedView<TGrid extends TSpacingValues> extends FC<ICreatedViewProps<TGrid>> {
51
+ readonly config: IViewConfig<TGrid>;
52
+ }
53
+ export interface IViewStyleEntry {
54
+ id: string;
55
+ css: string;
56
+ }
@@ -0,0 +1,2 @@
1
+ import { IViewConfig, TSpacingValues } from '../types';
2
+ export declare function buildCss<TGrid extends TSpacingValues>(config: IViewConfig<TGrid>): string;
@@ -0,0 +1,17 @@
1
+ import { spacingSideProperties } from '../constants.js';
2
+ import { getSpacingClassName } from './getSpacingClassName.js';
3
+ import { getSpacingValue } from './getSpacingValue.js';
4
+
5
+ function buildCss(config) {
6
+ const rules = [];
7
+ Object.entries(config.values).forEach(([token, multiplier]) => {
8
+ const value = getSpacingValue(config.step, multiplier);
9
+ Object.entries(spacingSideProperties).forEach(([prefix, properties]) => {
10
+ const declarations = properties.map(property => `${property}:${value}`).join(';');
11
+ rules.push(`.${getSpacingClassName(prefix, token, config.prefix)}{${declarations}}`);
12
+ });
13
+ });
14
+ return rules.join('');
15
+ }
16
+
17
+ export { buildCss };
@@ -0,0 +1,2 @@
1
+ import type { IResolvedSpacingProps } from '../types';
2
+ export declare function buildSpacingClassNames<TToken extends string>(spacingProps: IResolvedSpacingProps<TToken>, classPrefix?: string): Array<string>;
@@ -0,0 +1,58 @@
1
+ import { spacingPropPrefixes } from '../constants.js';
2
+ import { getSpacingClassName } from './getSpacingClassName.js';
3
+
4
+ function getSharedToken(...tokens) {
5
+ const [firstToken, ...remainingTokens] = tokens;
6
+ if (firstToken === undefined || remainingTokens.some(token => token !== firstToken)) {
7
+ return undefined;
8
+ }
9
+ return firstToken;
10
+ }
11
+ function buildBoxSpacingClassNames(boxName, spacingProps, classPrefix) {
12
+ const getToken = (propName) => spacingProps[propName];
13
+ const getClassName = (propName, token) => getSpacingClassName(spacingPropPrefixes[propName], token, classPrefix);
14
+ const boxPrefix = spacingPropPrefixes[boxName];
15
+ const allSidesToken = getToken(boxName);
16
+ const horizontalPropName = `${boxName}Horizontal`;
17
+ const verticalPropName = `${boxName}Vertical`;
18
+ const topPropName = `${boxName}Top`;
19
+ const rightPropName = `${boxName}Right`;
20
+ const bottomPropName = `${boxName}Bottom`;
21
+ const leftPropName = `${boxName}Left`;
22
+ const horizontalToken = getToken(horizontalPropName);
23
+ const verticalToken = getToken(verticalPropName);
24
+ const topToken = allSidesToken ?? verticalToken ?? getToken(topPropName);
25
+ const rightToken = allSidesToken ?? horizontalToken ?? getToken(rightPropName);
26
+ const bottomToken = allSidesToken ?? verticalToken ?? getToken(bottomPropName);
27
+ const leftToken = allSidesToken ?? horizontalToken ?? getToken(leftPropName);
28
+ const allSidesClassToken = getSharedToken(topToken, rightToken, bottomToken, leftToken);
29
+ if (allSidesClassToken !== undefined) {
30
+ return [getSpacingClassName(boxPrefix, allSidesClassToken, classPrefix)];
31
+ }
32
+ const horizontalPairToken = getSharedToken(leftToken, rightToken);
33
+ const verticalPairToken = getSharedToken(topToken, bottomToken);
34
+ const sideTokens = [topToken, rightToken, bottomToken, leftToken];
35
+ const sidePropNames = [topPropName, rightPropName, bottomPropName, leftPropName];
36
+ const sidePairTokens = [verticalPairToken, horizontalPairToken, verticalPairToken, horizontalPairToken];
37
+ const classNames = sideTokens.reduce((result, token, index) => {
38
+ if (token !== undefined && sidePairTokens[index] === undefined) {
39
+ result.push(getClassName(sidePropNames[index], token));
40
+ }
41
+ return result;
42
+ }, []);
43
+ if (horizontalPairToken !== undefined) {
44
+ classNames.push(getClassName(horizontalPropName, horizontalPairToken));
45
+ }
46
+ if (verticalPairToken !== undefined) {
47
+ classNames.push(getClassName(verticalPropName, verticalPairToken));
48
+ }
49
+ return classNames;
50
+ }
51
+ function buildSpacingClassNames(spacingProps, classPrefix = 'bsui') {
52
+ return [
53
+ ...buildBoxSpacingClassNames('margin', spacingProps, classPrefix),
54
+ ...buildBoxSpacingClassNames('padding', spacingProps, classPrefix)
55
+ ];
56
+ }
57
+
58
+ export { buildSpacingClassNames };
@@ -0,0 +1 @@
1
+ export declare function getSpacingClassName(spacingPrefix: string, token: string, classPrefix?: string): string;
@@ -0,0 +1,5 @@
1
+ function getSpacingClassName(spacingPrefix, token, classPrefix = 'bsui') {
2
+ return `${classPrefix}-${spacingPrefix}-${token}`;
3
+ }
4
+
5
+ export { getSpacingClassName };
@@ -0,0 +1,2 @@
1
+ import type { ISpacingStep } from '../types';
2
+ export declare function getSpacingValue(stepOrUnit: ISpacingStep | string, multiplier: number): string;
@@ -0,0 +1,11 @@
1
+ import { spacingUnitVariable } from '../constants.js';
2
+
3
+ function getSpacingValue(stepOrUnit, multiplier) {
4
+ if (typeof stepOrUnit === 'string') {
5
+ return `calc(var(${spacingUnitVariable}, ${stepOrUnit}) * ${multiplier})`;
6
+ }
7
+ const value = Number((stepOrUnit.value * multiplier).toPrecision(15));
8
+ return `${value}${stepOrUnit.unit}`;
9
+ }
10
+
11
+ export { getSpacingValue };
@@ -0,0 +1,7 @@
1
+ export { buildCss } from './buildCss';
2
+ export { buildSpacingClassNames } from './buildSpacingClassNames';
3
+ export { getSpacingClassName } from './getSpacingClassName';
4
+ export { getSpacingValue } from './getSpacingValue';
5
+ export { resolveSpacingProps } from './resolveSpacingProps';
6
+ export { registerStyles, getStyleEntries, resetStyles } from './stylesRegistry';
7
+ export { validateValuesKeys } from './validateValuesKeys';
@@ -0,0 +1,2 @@
1
+ import type { IResolvedSpacingProps, ISpacingProps } from '../types';
2
+ export declare function resolveSpacingProps<TToken extends string>(props: ISpacingProps<TToken>): IResolvedSpacingProps<TToken>;
@@ -0,0 +1,54 @@
1
+ import { spacingPropAliases } from '../constants.js';
2
+
3
+ function resolveSpacingProps(props) {
4
+ const resolvedProps = {};
5
+ Object.keys(props).forEach(propName => {
6
+ if (!Object.prototype.hasOwnProperty.call(spacingPropAliases, propName)) {
7
+ return;
8
+ }
9
+ const spacingPropName = propName;
10
+ const token = props[spacingPropName];
11
+ if (token !== undefined) {
12
+ resolvedProps[spacingPropAliases[spacingPropName]] = token;
13
+ }
14
+ });
15
+ if (resolvedProps.margin !== undefined) {
16
+ delete resolvedProps.marginTop;
17
+ delete resolvedProps.marginRight;
18
+ delete resolvedProps.marginBottom;
19
+ delete resolvedProps.marginLeft;
20
+ delete resolvedProps.marginHorizontal;
21
+ delete resolvedProps.marginVertical;
22
+ }
23
+ else {
24
+ if (resolvedProps.marginHorizontal !== undefined) {
25
+ delete resolvedProps.marginLeft;
26
+ delete resolvedProps.marginRight;
27
+ }
28
+ if (resolvedProps.marginVertical !== undefined) {
29
+ delete resolvedProps.marginTop;
30
+ delete resolvedProps.marginBottom;
31
+ }
32
+ }
33
+ if (resolvedProps.padding !== undefined) {
34
+ delete resolvedProps.paddingTop;
35
+ delete resolvedProps.paddingRight;
36
+ delete resolvedProps.paddingBottom;
37
+ delete resolvedProps.paddingLeft;
38
+ delete resolvedProps.paddingHorizontal;
39
+ delete resolvedProps.paddingVertical;
40
+ }
41
+ else {
42
+ if (resolvedProps.paddingHorizontal !== undefined) {
43
+ delete resolvedProps.paddingLeft;
44
+ delete resolvedProps.paddingRight;
45
+ }
46
+ if (resolvedProps.paddingVertical !== undefined) {
47
+ delete resolvedProps.paddingTop;
48
+ delete resolvedProps.paddingBottom;
49
+ }
50
+ }
51
+ return resolvedProps;
52
+ }
53
+
54
+ export { resolveSpacingProps };
@@ -0,0 +1,4 @@
1
+ import { IViewConfig, TSpacingValues, IViewStyleEntry } from '../types';
2
+ export declare function registerStyles<TGrid extends TSpacingValues>(config: IViewConfig<TGrid>, css: string): IViewStyleEntry;
3
+ export declare function getStyleEntries(): Array<IViewStyleEntry>;
4
+ export declare function resetStyles(): void;
@@ -0,0 +1,57 @@
1
+ const entries = new Map();
2
+ function hash(value) {
3
+ let h1 = 0xdeadbeef;
4
+ let h2 = 0x41c6ce57;
5
+ for (let index = 0; index < value.length; index++) {
6
+ const char = value.charCodeAt(index);
7
+ h1 = Math.imul(h1 ^ char, 2654435761);
8
+ h2 = Math.imul(h2 ^ char, 1597334677);
9
+ }
10
+ h1 = Math.imul(h1 ^ (h1 >>> 16), 2246822507) ^ Math.imul(h2 ^ (h2 >>> 13), 3266489909);
11
+ h2 = Math.imul(h2 ^ (h2 >>> 16), 2246822507) ^ Math.imul(h1 ^ (h1 >>> 13), 3266489909);
12
+ return (h2 >>> 0).toString(36) + (h1 >>> 0).toString(36);
13
+ }
14
+ function injectStyleElement(entry) {
15
+ if (typeof document === 'undefined') {
16
+ return;
17
+ }
18
+ if (document.getElementById(entry.id)) {
19
+ return;
20
+ }
21
+ const style = document.createElement('style');
22
+ style.id = entry.id;
23
+ style.textContent = entry.css;
24
+ document.head.appendChild(style);
25
+ }
26
+ function updateStyleElement(entry) {
27
+ if (typeof document === 'undefined') {
28
+ return;
29
+ }
30
+ const style = document.getElementById(entry.id);
31
+ if (style && style.textContent !== entry.css) {
32
+ style.textContent = entry.css;
33
+ }
34
+ }
35
+ function registerStyles(config, css) {
36
+ const key = JSON.stringify(config);
37
+ const existing = entries.get(key);
38
+ if (existing) {
39
+ if (!existing.css.includes(css)) {
40
+ existing.css += css;
41
+ updateStyleElement(existing);
42
+ }
43
+ return existing;
44
+ }
45
+ const entry = { id: `bsui-layout-${hash(key)}`, css };
46
+ entries.set(key, entry);
47
+ injectStyleElement(entry);
48
+ return entry;
49
+ }
50
+ function getStyleEntries() {
51
+ return Array.from(entries.values());
52
+ }
53
+ function resetStyles() {
54
+ entries.clear();
55
+ }
56
+
57
+ export { getStyleEntries, registerStyles, resetStyles };
@@ -0,0 +1,2 @@
1
+ import { TSpacingValues } from '../types';
2
+ export declare function validateValuesKeys(values: TSpacingValues): void;
@@ -0,0 +1,12 @@
1
+ import { valuesTokenPattern } from '../constants.js';
2
+
3
+ function validateValuesKeys(values) {
4
+ Object.keys(values).forEach(token => {
5
+ if (!valuesTokenPattern.test(token)) {
6
+ throw new Error(`@byndyusoft-ui/view: недопустимый токен сетки "${token}". ` +
7
+ 'Токен может содержать только латиницу, цифры, дефис и подчёркивание.');
8
+ }
9
+ });
10
+ }
11
+
12
+ export { validateValuesKeys };
@@ -0,0 +1,10 @@
1
+ interface IRuntimeViewConfig {
2
+ step?: {
3
+ value?: unknown;
4
+ unit?: unknown;
5
+ };
6
+ values?: unknown;
7
+ prefix?: unknown;
8
+ }
9
+ export declare function validateViewConfig(config: IRuntimeViewConfig): void;
10
+ export {};
@@ -0,0 +1,15 @@
1
+ const cssClassPrefixPattern = /^[A-Za-z_][A-Za-z0-9_-]*$/;
2
+ function validateViewConfig(config) {
3
+ const { step, prefix } = config;
4
+ if (!step || typeof step.value !== 'number' || !Number.isInteger(step.value) || step.value <= 0) {
5
+ throw new Error('@byndyusoft-ui/view: step.value должен быть положительным целым числом.');
6
+ }
7
+ if (step.unit !== 'px' && step.unit !== 'rem') {
8
+ throw new Error('@byndyusoft-ui/view: step.unit должен быть "px" или "rem".');
9
+ }
10
+ if (prefix !== undefined && (typeof prefix !== 'string' || !cssClassPrefixPattern.test(prefix))) {
11
+ throw new Error('@byndyusoft-ui/view: недопустимый prefix CSS-классов. Используйте латинскую букву или подчёркивание в начале, затем латиницу, цифры, дефис или подчёркивание.');
12
+ }
13
+ }
14
+
15
+ export { validateViewConfig };
package/package.json CHANGED
@@ -1,6 +1,48 @@
1
1
  {
2
2
  "name": "@byndyusoft-ui/view",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "1.0.0",
4
+ "description": "Byndyusoft UI View React Component",
5
+ "keywords": [
6
+ "byndyusoft",
7
+ "byndyusoft-ui",
8
+ "react",
9
+ "view"
10
+ ],
11
+ "author": "Byndyusoft Frontend Developer <frontend@byndyusoft.com>",
12
+ "homepage": "https://github.com/Byndyusoft/ui/tree/master/components/view#readme",
13
+ "license": "Apache-2.0",
14
+ "main": "dist/index.js",
15
+ "types": "dist/index.d.ts",
16
+ "files": [
17
+ "dist/**/*"
18
+ ],
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "git+https://github.com/Byndyusoft/ui.git"
22
+ },
23
+ "scripts": {
24
+ "build": "rollup --config",
25
+ "clean": "rimraf dist && rimraf .turbo && rimraf node_modules && rimraf package-lock.json",
26
+ "lint:check": "npm run eslint:check && npm run prettier:check && npm run stylelint:check",
27
+ "lint:fix": "npm run eslint:fix && npm run prettier:fix && npm run stylelint:fix",
28
+ "eslint:check": "eslint src --config ../../eslint.config.js",
29
+ "eslint:fix": "eslint src --config ../../eslint.config.js --fix",
30
+ "prettier:check": "prettier --check \"**/*.{ts,tsx,css,scss,json}\"",
31
+ "prettier:fix": "prettier --write \"**/*.{ts,tsx,css,scss,json}\"",
32
+ "stylelint:check": "stylelint '**/*.{css,scss}' --allow-empty-input",
33
+ "stylelint:fix": "stylelint '**/*.{css,scss}' --fix --allow-empty-input",
34
+ "test": "vitest run --typecheck --root ../.. --exclude '**/.delta/**' components/view/src"
35
+ },
36
+ "bugs": {
37
+ "url": "https://github.com/Byndyusoft/ui/issues"
38
+ },
39
+ "publishConfig": {
40
+ "access": "public"
41
+ },
42
+ "peerDependencies": {
43
+ "react": ">=17"
44
+ },
45
+ "dependencies": {
46
+ "classnames": "^2.5.1"
47
+ }
48
+ }