Simplify settings usage (#42)
* remove _parent from component settings and stop using augment platform theme for settings * Change files
This commit is contained in:
Родитель
f85c768914
Коммит
798ed80030
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"type": "patch",
|
||||
"comment": "remove _parent from component settings and stop using augment platform theme for settings",
|
||||
"packageName": "@uifabricshared/foundation-compose",
|
||||
"email": "jasonmo360@gmail.com",
|
||||
"commit": "7b983871659d635b808556e002c323ad2d222269",
|
||||
"date": "2019-09-26T08:15:45.595Z"
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"type": "minor",
|
||||
"comment": "remove _parent from component settings and stop using augment platform theme for settings",
|
||||
"packageName": "@uifabricshared/foundation-settings",
|
||||
"email": "jasonmo360@gmail.com",
|
||||
"commit": "7b983871659d635b808556e002c323ad2d222269",
|
||||
"date": "2019-09-26T08:15:52.516Z"
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"type": "minor",
|
||||
"comment": "remove _parent from component settings and stop using augment platform theme for settings",
|
||||
"packageName": "@uifabricshared/theming-ramp",
|
||||
"email": "jasonmo360@gmail.com",
|
||||
"commit": "7b983871659d635b808556e002c323ad2d222269",
|
||||
"date": "2019-09-26T08:15:59.166Z"
|
||||
}
|
|
@ -1,71 +1,69 @@
|
|||
import { IButtonSettings } from './Button.types';
|
||||
import { augmentPlatformTheme } from '@uifabricshared/theming-react-native';
|
||||
import { IButtonSlots } from './Button.types';
|
||||
import { IComposeSettings } from '@uifabricshared/foundation-compose';
|
||||
|
||||
export function loadButtonSettings(): void {
|
||||
const buttonSettings: { [key: string]: IButtonSettings } = {
|
||||
RNFButton: {
|
||||
root: {
|
||||
backgroundColor: 'buttonBackground',
|
||||
color: 'buttonText',
|
||||
borderColor: 'buttonBorder',
|
||||
export const settings: IComposeSettings<IButtonSlots> = [
|
||||
{
|
||||
root: {
|
||||
backgroundColor: 'buttonBackground',
|
||||
color: 'buttonText',
|
||||
borderColor: 'buttonBorder',
|
||||
borderWidth: 1,
|
||||
fontSize: 'large',
|
||||
fontWeight: 'semiBold',
|
||||
fontFamily: 'primary',
|
||||
horizontalAlign: 'center',
|
||||
verticalAlign: 'center',
|
||||
style: {
|
||||
boxSizing: 'border-box',
|
||||
borderRadius: 2,
|
||||
borderStyle: 'solid',
|
||||
borderWidth: 1,
|
||||
fontSize: 'large',
|
||||
fontWeight: 'semiBold',
|
||||
fontFamily: 'primary',
|
||||
horizontalAlign: 'center',
|
||||
verticalAlign: 'center',
|
||||
style: {
|
||||
boxSizing: 'border-box',
|
||||
borderRadius: 2,
|
||||
borderStyle: 'solid',
|
||||
borderWidth: 1,
|
||||
cursor: 'pointer',
|
||||
lineHeight: 1,
|
||||
display: 'inline-flex',
|
||||
alignItems: 'flex-start',
|
||||
flexDirection: 'row',
|
||||
alignSelf: 'flex-start',
|
||||
minHeight: 32,
|
||||
minWidth: 100,
|
||||
overflow: 'hidden',
|
||||
paddingTop: 4,
|
||||
paddingBottom: 4,
|
||||
paddingLeft: 8,
|
||||
paddingRight: 8
|
||||
cursor: 'pointer',
|
||||
lineHeight: 1,
|
||||
display: 'inline-flex',
|
||||
alignItems: 'flex-start',
|
||||
flexDirection: 'row',
|
||||
alignSelf: 'flex-start',
|
||||
minHeight: 32,
|
||||
minWidth: 100,
|
||||
overflow: 'hidden',
|
||||
paddingTop: 4,
|
||||
paddingBottom: 4,
|
||||
paddingLeft: 8,
|
||||
paddingRight: 8
|
||||
}
|
||||
},
|
||||
content: {},
|
||||
icon: {},
|
||||
_precedence: ['disabled', 'hovered', 'pressed', 'focused'],
|
||||
_overrides: {
|
||||
disabled: {
|
||||
root: {
|
||||
backgroundColor: 'buttonBackgroundDisabled',
|
||||
color: 'buttonTextDisabled',
|
||||
borderColor: 'buttonBorderDisabled'
|
||||
}
|
||||
},
|
||||
content: {},
|
||||
icon: {},
|
||||
_precedence: ['disabled', 'hovered', 'pressed', 'focused'],
|
||||
_overrides: {
|
||||
disabled: {
|
||||
root: {
|
||||
backgroundColor: 'buttonBackgroundDisabled',
|
||||
color: 'buttonTextDisabled',
|
||||
borderColor: 'buttonBorderDisabled'
|
||||
}
|
||||
},
|
||||
hovered: {
|
||||
root: {
|
||||
backgroundColor: 'buttonBackgroundHovered',
|
||||
color: 'buttonTextHovered',
|
||||
borderColor: 'buttonBorderHovered'
|
||||
}
|
||||
},
|
||||
pressed: {
|
||||
root: {
|
||||
backgroundColor: 'buttonBackgroundPressed',
|
||||
color: 'buttonTextPressed',
|
||||
borderColor: 'buttonBorderPressed'
|
||||
}
|
||||
},
|
||||
focused: {
|
||||
root: {
|
||||
borderColor: 'inputFocusBorderAlt'
|
||||
}
|
||||
hovered: {
|
||||
root: {
|
||||
backgroundColor: 'buttonBackgroundHovered',
|
||||
color: 'buttonTextHovered',
|
||||
borderColor: 'buttonBorderHovered'
|
||||
}
|
||||
},
|
||||
pressed: {
|
||||
root: {
|
||||
backgroundColor: 'buttonBackgroundPressed',
|
||||
color: 'buttonTextPressed',
|
||||
borderColor: 'buttonBorderPressed'
|
||||
}
|
||||
},
|
||||
focused: {
|
||||
root: {
|
||||
borderColor: 'inputFocusBorderAlt'
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
augmentPlatformTheme({ settings: buttonSettings });
|
||||
}
|
||||
},
|
||||
'RNFButton'
|
||||
];
|
||||
|
|
|
@ -3,7 +3,7 @@ import { IButtonSlots, IButtonRenderData, IButtonCustomizableProps, IButtonState
|
|||
import { compose, IUseComposeStyling } from '@uifabricshared/foundation-compose';
|
||||
// import { Stack } from '../Stack';
|
||||
import { Text } from '../Text/index';
|
||||
import { loadButtonSettings } from './Button.settings';
|
||||
import { settings } from './Button.settings';
|
||||
import { Stack } from '../Stack/index';
|
||||
import { textTokens, borderTokens } from '../tokens';
|
||||
import { backgroundColorTokens, foregroundColorTokens, getPaletteFromTheme } from '../tokens/ColorTokens';
|
||||
|
@ -11,11 +11,9 @@ import { ISlots, withSlots } from '@uifabricshared/foundation-composable';
|
|||
import { useAsPressable } from '../Pressable';
|
||||
import { mergeSettings } from '@uifabricshared/foundation-settings';
|
||||
|
||||
loadButtonSettings();
|
||||
|
||||
export const Button = compose<IButtonProps, IButtonSlots, IButtonState>({
|
||||
displayName: 'Button',
|
||||
settings: ['RNFButton'],
|
||||
settings,
|
||||
usePrepareProps: (userProps: IButtonCustomizableProps, useStyling: IUseComposeStyling<IButtonSlots>) => {
|
||||
const { icon, content, ...rest } = userProps;
|
||||
// attach the pressable state handlers
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import { ILinkSettings } from './Link.types';
|
||||
import { augmentPlatformTheme } from '@uifabricshared/theming-react-native';
|
||||
import { ILinkSlotProps } from './Link.types';
|
||||
import { IComposeSettings } from '@uifabricshared/foundation-compose';
|
||||
|
||||
export function loadLinkSettings(): void {
|
||||
const linkSettings: ILinkSettings = {
|
||||
export const settings: IComposeSettings<ILinkSlotProps> = [
|
||||
{
|
||||
root: {
|
||||
fontFamily: 'primary',
|
||||
fontSize: 'medium',
|
||||
|
@ -36,11 +36,6 @@ export function loadLinkSettings(): void {
|
|||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
augmentPlatformTheme({
|
||||
settings: {
|
||||
RNFLink: linkSettings
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
'RNFLink'
|
||||
];
|
||||
|
|
|
@ -4,17 +4,15 @@
|
|||
import { compose } from '@uifabricshared/foundation-compose';
|
||||
import { ILinkSlotProps, ILinkRenderData, ILinkProps, ILinkState } from './Link.types';
|
||||
import { Text } from '../Text';
|
||||
import { loadLinkSettings } from './Link.settings';
|
||||
import { settings } from './Link.settings';
|
||||
import { useLinkPrepareProps } from './Link.helpers';
|
||||
import { textTokens } from '../tokens';
|
||||
import { foregroundColorTokens } from '../tokens/ColorTokens';
|
||||
import { ISlots, withSlots } from '@uifabricshared/foundation-composable';
|
||||
|
||||
loadLinkSettings();
|
||||
|
||||
export const Link = compose<ILinkProps, ILinkSlotProps, ILinkState>({
|
||||
displayName: 'Link',
|
||||
settings: ['RNFLink'],
|
||||
settings,
|
||||
usePrepareProps: useLinkPrepareProps,
|
||||
render: (Slots: ISlots<ILinkSlotProps>, renderData: ILinkRenderData) => {
|
||||
const URL = renderData.state.URL;
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import { IStackSettings, IStackProps } from './Stack.types';
|
||||
import { IStackProps } from './Stack.types';
|
||||
import { parseGap, parsePadding } from './StackUtils';
|
||||
import { augmentPlatformTheme } from '@uifabricshared/theming-react-native';
|
||||
import { ITheme } from '@uifabricshared/theming-ramp';
|
||||
import { styleFunction } from '@uifabricshared/foundation-tokens';
|
||||
import { IStyleProp } from '@uifabricshared/foundation-settings';
|
||||
|
@ -11,34 +10,6 @@ const nameMap: { [key: string]: string } = {
|
|||
end: 'flex-end'
|
||||
};
|
||||
|
||||
export function loadStackSettings(): void {
|
||||
const settings: IStackSettings = {
|
||||
root: {
|
||||
style: {
|
||||
display: 'flex',
|
||||
flexWrap: 'nowrap',
|
||||
width: 'auto',
|
||||
overflow: 'visible',
|
||||
height: '100%'
|
||||
}
|
||||
},
|
||||
inner: {
|
||||
style: {
|
||||
display: 'flex',
|
||||
flexWrap: 'wrap',
|
||||
overflow: 'visible',
|
||||
boxSizing: 'border-box',
|
||||
maxWidth: '100vw'
|
||||
}
|
||||
}
|
||||
};
|
||||
augmentPlatformTheme({
|
||||
settings: {
|
||||
RNFStack: settings
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function _getAlignment(
|
||||
horizontal: IStackProps['horizontal'],
|
||||
horizontalAlign: IStackProps['horizontalAlign'],
|
||||
|
@ -139,10 +110,10 @@ function _buildRootStyles(tokenProps: IStackProps, theme: ITheme): IStackProps {
|
|||
}
|
||||
},
|
||||
grow &&
|
||||
!wrap && {
|
||||
flexGrow: grow === true ? 1 : grow,
|
||||
overflow: 'hidden'
|
||||
}
|
||||
!wrap && {
|
||||
flexGrow: grow === true ? 1 : grow,
|
||||
overflow: 'hidden'
|
||||
}
|
||||
]
|
||||
} as IStackProps;
|
||||
}
|
||||
|
|
|
@ -8,7 +8,29 @@ import { withSlots, ISlots, atomicUsePrepareProps } from '@uifabricshared/founda
|
|||
|
||||
export const Stack = compose<IStackProps, IStackSlotProps, object, IStackStatics>({
|
||||
displayName: 'Stack',
|
||||
settings: ['RNFStack'],
|
||||
settings: [
|
||||
{
|
||||
root: {
|
||||
style: {
|
||||
display: 'flex',
|
||||
flexWrap: 'nowrap',
|
||||
width: 'auto',
|
||||
overflow: 'visible',
|
||||
height: '100%'
|
||||
}
|
||||
},
|
||||
inner: {
|
||||
style: {
|
||||
display: 'flex',
|
||||
flexWrap: 'wrap',
|
||||
overflow: 'visible',
|
||||
boxSizing: 'border-box',
|
||||
maxWidth: '100vw'
|
||||
}
|
||||
}
|
||||
},
|
||||
'RNFStack'
|
||||
],
|
||||
statics: { Item: StackItem },
|
||||
usePrepareProps: atomicUsePrepareProps,
|
||||
slots: {
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import { ITextSettings } from './Text.types';
|
||||
import { augmentPlatformTheme } from '@uifabricshared/theming-react-native';
|
||||
import { ITextSlotProps } from './Text.types';
|
||||
import { IComposeSettings } from '@uifabricshared/foundation-compose';
|
||||
|
||||
export function loadTextSettings(): void {
|
||||
const textSettings: ITextSettings = {
|
||||
export const settings: IComposeSettings<ITextSlotProps> = [
|
||||
{
|
||||
root: {
|
||||
fontFamily: 'primary',
|
||||
fontSize: 'medium',
|
||||
|
@ -22,11 +22,6 @@ export function loadTextSettings(): void {
|
|||
}
|
||||
},
|
||||
_precedence: ['disabled']
|
||||
};
|
||||
|
||||
augmentPlatformTheme({
|
||||
settings: {
|
||||
RNFText: textSettings
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
'RNFText'
|
||||
];
|
||||
|
|
|
@ -1,13 +1,11 @@
|
|||
import { ITextProps } from './Text.types';
|
||||
import { compose } from '@uifabricshared/foundation-compose';
|
||||
import { foregroundColorTokens, textTokens } from '../tokens/index';
|
||||
import { loadTextSettings } from './Text.settings';
|
||||
|
||||
loadTextSettings();
|
||||
import { settings } from './Text.settings';
|
||||
|
||||
export const Text = compose<ITextProps>({
|
||||
displayName: 'Text',
|
||||
settings: ['RNFText'],
|
||||
settings,
|
||||
slots: {
|
||||
root: {
|
||||
slotType: 'div',
|
||||
|
|
|
@ -10,6 +10,16 @@ import { ISlotStyleFactories, IComponentTokens } from '@uifabricshared/foundatio
|
|||
*/
|
||||
export type IUseComposeStyling<TSlotProps extends ISlotProps> = (props: TSlotProps['root'], lookup?: IOverrideLookup) => TSlotProps;
|
||||
|
||||
/**
|
||||
* Array of:
|
||||
* IComponentSettings for the component
|
||||
* string - name of the entry to query in the theme
|
||||
* theme => IComponentSettings function
|
||||
*
|
||||
* These settings are layered together in order to produce the merged settings for a component
|
||||
*/
|
||||
export type IComposeSettings<TSlotProps extends ISlotProps> = ISettingsEntry<IComponentSettings<TSlotProps>, ITheme>[];
|
||||
|
||||
/**
|
||||
* Settings which dictate the behavior of useStyling, as implemented by the compose package. These are
|
||||
* separated from IComponentOptions to allow the styling portion to be used independently if so desired.
|
||||
|
@ -23,7 +33,7 @@ export interface IStylingSettings<TSlotProps extends ISlotProps> {
|
|||
/**
|
||||
* settings used to build up the style definitions
|
||||
*/
|
||||
settings?: ISettingsEntry<IComponentSettings<TSlotProps>, ITheme>[];
|
||||
settings?: IComposeSettings<TSlotProps>;
|
||||
|
||||
/**
|
||||
* The input tokens processed, built into functions, with the keys built into a map.
|
||||
|
@ -83,9 +93,7 @@ export type IComposeReturnType<
|
|||
/**
|
||||
* shorthand function for doing quick customizations of a component by appending to settings
|
||||
*/
|
||||
customize: (
|
||||
...keys: ISettingsEntry<IComponentSettings<TSlotProps>, ITheme>[]
|
||||
) => IComposeReturnType<TProps, TSlotProps, TState, TStatics>;
|
||||
customize: (...settings: IComposeSettings<TSlotProps>) => IComposeReturnType<TProps, TSlotProps, TState, TStatics>;
|
||||
|
||||
/**
|
||||
* helper function to quickly add new partial options to the base component. The primary advantage is that
|
||||
|
|
|
@ -25,7 +25,7 @@ function _getComponentCache(cacheKey: symbol, theme: ITheme): { [key: string]: I
|
|||
}
|
||||
|
||||
function _getSettingsFromTheme(theme: ITheme, name: string): IComponentSettings {
|
||||
return getSettings(theme, name).settings;
|
||||
return getSettings(theme, name);
|
||||
}
|
||||
|
||||
function _getHasToken<TSlotProps extends ISlotProps>(slots: IStylingSettings<TSlotProps>['slots']): ITargetHasToken {
|
||||
|
|
|
@ -63,45 +63,7 @@ The IComponentSettings interface is an extension of ISlotProps which is designed
|
|||
|
||||
Props interfaces have a mix of required and optional parameters. When defining settings in places such as theme definitions this is not desireable as those values will need to be filled from the actual props passed into the component. As a result `IComponentSettings<IMySlotProps>` will make the slot props into a partial object.
|
||||
|
||||
### \_parent?: string | string[]
|
||||
|
||||
When used in a theme, the `_parent` property allows inheritance from one or more previously declared settings. Settings blocks references as parents will be applied earlier in the precedence chain, meaning values in the current settings block will overwrite values coming from the parents. As an example:
|
||||
|
||||
const theme = {
|
||||
settings: {
|
||||
Obj1: {
|
||||
root: {
|
||||
val1: 'foo'
|
||||
val2: 'bar'
|
||||
}
|
||||
},
|
||||
Obj2: {
|
||||
root: {
|
||||
val2: 'baz'
|
||||
val3: 'foobar'
|
||||
}
|
||||
},
|
||||
Obj3: {
|
||||
_parent: ['Obj1', 'Obj2'],
|
||||
root: {
|
||||
val3: 'this will win'
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
When resolving Obj3 the result will be:
|
||||
|
||||
{
|
||||
_parent: ['Obj1', 'Obj2'],
|
||||
root: {
|
||||
val1: 'foo', // coming from Obj1
|
||||
val2: 'baz', // value from Obj2 overwrote value from Obj1
|
||||
val3: 'thsi will win' // value from Obj3 overwrote value from Obj2
|
||||
}
|
||||
}
|
||||
|
||||
#### \_overrides and \_precedence
|
||||
### \_overrides and \_precedence
|
||||
|
||||
The overrides define recursive IComponentSettings that will be applied to the root in the order defined by \_precedence. These will be merged one by one, supplanting values at the root level and potentially supplying additional overrides.
|
||||
|
||||
|
|
|
@ -1,100 +0,0 @@
|
|||
import { IComponentSettings, IComponentSettingsCollection } from './Settings.types';
|
||||
import { getParentSettingsChain, mergeSettings } from './Settings';
|
||||
|
||||
/**
|
||||
* Resolve any parent references for the settings, using the `lookup` collection of settings to lookup
|
||||
* parents by name.
|
||||
*
|
||||
* The process starts with the settings specified by `target`. Settings are merged from the oldest
|
||||
* parent forward, up to and including the source settings.
|
||||
*/
|
||||
function resolveSettingsParents(lookup: IComponentSettingsCollection, target: string | IComponentSettings): IComponentSettings {
|
||||
const collectedSettings = getParentSettingsChain(lookup, target);
|
||||
// merge the hierarhcy
|
||||
return mergeSettings(...collectedSettings);
|
||||
}
|
||||
|
||||
interface IProps {
|
||||
root: {
|
||||
fontFamily?: string;
|
||||
fontWeight?: 'light' | 'normal' | 'bold';
|
||||
fontSize?: number;
|
||||
};
|
||||
}
|
||||
|
||||
const settingsGrandParentName = 'settingsGrandParent';
|
||||
const settingsGrandParent: IComponentSettings<IProps> = {
|
||||
root: {
|
||||
fontFamily: 'Helvetica',
|
||||
fontWeight: 'light',
|
||||
fontSize: 12
|
||||
}
|
||||
};
|
||||
|
||||
const settingsParentName = 'settingsParent';
|
||||
const settingsParent: IComponentSettings<IProps> = {
|
||||
_parent: settingsGrandParentName,
|
||||
root: {
|
||||
fontFamily: 'Verdana',
|
||||
fontWeight: 'normal'
|
||||
}
|
||||
};
|
||||
|
||||
const settingsChildName = 'settingsChildName';
|
||||
const settingsChild: IComponentSettings<IProps> = {
|
||||
_parent: settingsParentName,
|
||||
root: {
|
||||
fontFamily: 'Arial',
|
||||
fontSize: 11
|
||||
}
|
||||
};
|
||||
|
||||
const emptySettingsName = 'emptyLayerName';
|
||||
const emptySettings: IComponentSettings<IProps> = {};
|
||||
|
||||
const collection: IComponentSettingsCollection<IComponentSettings<IProps>> = {};
|
||||
collection[settingsGrandParentName] = settingsGrandParent;
|
||||
collection[settingsParentName] = settingsParent;
|
||||
collection[settingsChildName] = settingsChild;
|
||||
collection[emptySettingsName] = emptySettings;
|
||||
|
||||
describe('Resolve settings parents tests', () => {
|
||||
test('resolveSettingsParents returns undefined when the source settings does not exist', () => {
|
||||
const flattened: IComponentSettings = resolveSettingsParents({}, 'does not exist');
|
||||
expect(flattened).toEqual(undefined);
|
||||
});
|
||||
|
||||
test('resolveSettingsParents returns undefined when the source settings is empty', () => {
|
||||
const flattened: IComponentSettings<IProps> = resolveSettingsParents(collection, emptySettingsName);
|
||||
expect(flattened).toEqual(undefined);
|
||||
});
|
||||
|
||||
test('resolveSettingsParents returns the grand-parent settings when starting with the grand-parent settings', () => {
|
||||
const flattened: IComponentSettings = resolveSettingsParents(collection, settingsGrandParentName);
|
||||
expect(flattened).toEqual(settingsGrandParent);
|
||||
});
|
||||
|
||||
test('resolveSettingsParents blends the parent and grand-parent layers when starting with the parent settings', () => {
|
||||
const flattened = resolveSettingsParents(collection, settingsParentName);
|
||||
expect(flattened).toEqual({
|
||||
_parent: settingsGrandParentName,
|
||||
root: {
|
||||
fontFamily: 'Verdana',
|
||||
fontWeight: 'normal',
|
||||
fontSize: 12
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test('resolveSettingsParents blends the child, parent and grand-parent when starting with child settings', () => {
|
||||
const flattened = resolveSettingsParents(collection, settingsChildName);
|
||||
expect(flattened).toEqual({
|
||||
_parent: settingsParentName,
|
||||
root: {
|
||||
fontFamily: 'Arial',
|
||||
fontWeight: 'normal',
|
||||
fontSize: 11
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
|
@ -96,46 +96,6 @@ export function mergeSettingsCollection<TCollection extends IComponentSettingsCo
|
|||
return immutableMergeCore(_mergeCollectionOptions, ...collections) as TCollection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Walk the chain of parents, calling the visitor function on each one
|
||||
*/
|
||||
function visitSettingsHierarchyDepthFirst(
|
||||
collection: IComponentSettingsCollection,
|
||||
target: string | IComponentSettings,
|
||||
visitor: (settings: IComponentSettings) => void
|
||||
): void {
|
||||
const isSettings = typeof target === 'object';
|
||||
if (isSettings || collection.hasOwnProperty(target as string)) {
|
||||
const settings = isSettings ? (target as IComponentSettings) : collection[target as string];
|
||||
|
||||
if (settings) {
|
||||
// visit parents first
|
||||
if (settings._parent) {
|
||||
const parents = Array.isArray(settings._parent) ? settings._parent : [settings._parent];
|
||||
for (const parent of parents) {
|
||||
visitSettingsHierarchyDepthFirst(collection, parent, visitor);
|
||||
}
|
||||
}
|
||||
|
||||
// visit this layer
|
||||
visitor(settings);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the set of settings, in the order they should be merged, to resolve the parent chain
|
||||
*
|
||||
* @param lookup - collection to use for looking up settings
|
||||
* @param target - settings entry to use as the root of the lookup chain
|
||||
*/
|
||||
export function getParentSettingsChain(lookup: IComponentSettingsCollection, target: string | IComponentSettings): IComponentSettings[] {
|
||||
// gather the entire settings hierarchy into an ordered array
|
||||
const collectedSettings: IComponentSettings[] = [];
|
||||
visitSettingsHierarchyDepthFirst(lookup, target, settings => collectedSettings.push(settings));
|
||||
return collectedSettings;
|
||||
}
|
||||
|
||||
export function getActiveOverrides(target: IComponentSettings, lookup?: IOverrideLookup): string[] {
|
||||
const hasOverride = typeof lookup === 'function' ? lookup : o => lookup[o];
|
||||
return (target && target._precedence && target._precedence.filter(o => hasOverride(o))) || [];
|
||||
|
@ -165,6 +125,6 @@ export function resolveSettingsOverrides(target: IComponentSettings, overrideLoo
|
|||
*/
|
||||
export function slotPropsFromSettings(target: IComponentSettings): ISlotProps {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const { _overrides, _parent, _precedence, ...slotProps } = target;
|
||||
const { _overrides, _precedence, ...slotProps } = target;
|
||||
return slotProps as ISlotProps;
|
||||
}
|
||||
|
|
|
@ -5,7 +5,6 @@ export interface ISlotProps<TProps extends object = object> {
|
|||
export type IPartialSlotProps<TSlotProps extends ISlotProps> = { [K in keyof TSlotProps]+?: Partial<TSlotProps[K]> };
|
||||
|
||||
export type IComponentSettings<TSlotProps extends ISlotProps = ISlotProps> = IPartialSlotProps<TSlotProps> & {
|
||||
_parent?: string | string[];
|
||||
_precedence?: string[];
|
||||
_overrides?: IComponentSettingsCollection<IComponentSettings<TSlotProps>>;
|
||||
};
|
||||
|
|
|
@ -1,80 +0,0 @@
|
|||
import { ITheme } from './Theme.types';
|
||||
import { IPalette } from './Color.types';
|
||||
import { ITypography } from './Typography.types';
|
||||
import { getSettings } from './SettingsWorker';
|
||||
|
||||
const theme: ITheme = {
|
||||
palette: {
|
||||
bodyBackground: '#ff0000'
|
||||
} as IPalette,
|
||||
typography: {
|
||||
families: {
|
||||
primary: 'Arial'
|
||||
},
|
||||
sizes: {
|
||||
medium: 14
|
||||
},
|
||||
weights: {
|
||||
medium: '500'
|
||||
}
|
||||
} as ITypography,
|
||||
spacing: { s2: '4px', s1: '8px', m: '16px', l1: '20px', l2: '32px' },
|
||||
settings: {
|
||||
base: {
|
||||
root: {
|
||||
style: {
|
||||
backgroundColor: 'bodyBackground'
|
||||
}
|
||||
}
|
||||
},
|
||||
Text: {
|
||||
_parent: 'base',
|
||||
root: {
|
||||
style: {
|
||||
textColor: '#0b1621',
|
||||
fontFamily: 'primary'
|
||||
}
|
||||
},
|
||||
_precedence: ['disabled'],
|
||||
_overrides: {
|
||||
disabled: {
|
||||
root: {
|
||||
style: {
|
||||
fontWeight: 100
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const _styleKey = 'style';
|
||||
|
||||
describe('Style tests', () => {
|
||||
test('create style for the base layer', () => {
|
||||
const style = getSettings(theme, 'base').settings.root![_styleKey];
|
||||
expect(style).toEqual({
|
||||
backgroundColor: 'bodyBackground'
|
||||
});
|
||||
});
|
||||
|
||||
test('create style for the Text layer', () => {
|
||||
const style = getSettings(theme, 'Text').settings.root![_styleKey];
|
||||
expect(style).toEqual({
|
||||
backgroundColor: 'bodyBackground',
|
||||
textColor: '#0b1621',
|
||||
fontFamily: 'primary'
|
||||
});
|
||||
});
|
||||
|
||||
test('create style for the Text layer with a disabled override', () => {
|
||||
const style = getSettings(theme, 'Text', { disabled: true }).settings.root![_styleKey];
|
||||
expect(style).toEqual({
|
||||
backgroundColor: 'bodyBackground',
|
||||
textColor: '#0b1621',
|
||||
fontFamily: 'primary',
|
||||
fontWeight: 100
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,108 +1,22 @@
|
|||
import { ITheme } from './Theme.types';
|
||||
import {
|
||||
IComponentSettings,
|
||||
IComponentSettingsCollection,
|
||||
IOverrideLookup,
|
||||
resolveSettingsOverrides,
|
||||
getParentSettingsChain,
|
||||
mergeSettings,
|
||||
getActiveOverrides
|
||||
} from '@uifabricshared/foundation-settings';
|
||||
|
||||
interface ISettingsWithKey {
|
||||
settings: IComponentSettings;
|
||||
styleKey: string;
|
||||
}
|
||||
|
||||
/** key used to store the style cache in the theme */
|
||||
const _styleCacheKey = Symbol('__styleCache');
|
||||
import { IComponentSettings } from '@uifabricshared/foundation-settings';
|
||||
|
||||
/** helper to strip out the component settings specific bits from the returned structure */
|
||||
export function returnAsSlotProps(target: IComponentSettings): IComponentSettings {
|
||||
if (target) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const { _overrides, _parent, _precedence, ...settings } = target;
|
||||
const { _overrides, _precedence, ...settings } = target;
|
||||
return settings;
|
||||
}
|
||||
return target;
|
||||
}
|
||||
|
||||
/**
|
||||
* resolve the parents and overrides of a settings that is not part of the theme against the current theme
|
||||
*/
|
||||
export function resolveSettings(theme: ITheme, target: IComponentSettings, overrides?: IOverrideLookup): IComponentSettings {
|
||||
const mergedAndFinalized = mergeSettings(...getParentSettingsChain(theme.settings, target));
|
||||
return returnAsSlotProps(resolveSettingsOverrides(mergedAndFinalized, overrides));
|
||||
}
|
||||
|
||||
/**
|
||||
* get a key to use for cache lookups for this settings entry
|
||||
*
|
||||
* @param name - name of the settings entry in the theme
|
||||
* @param precedence - ordered precedence of which overrides to apply
|
||||
* @param overrides - lookup object for testing whether an override should be applied
|
||||
*/
|
||||
export function getOverrideKey(name: string, precedence: string[], overrides: IOverrideLookup): string {
|
||||
getActiveOverrides;
|
||||
const overrideKey = (precedence && precedence.filter(val => overrides[val]).join('-')) || undefined;
|
||||
return overrideKey ? name + '-' + overrideKey : name;
|
||||
}
|
||||
|
||||
/**
|
||||
* build the settings to cache with as few extra operations as possible. If the target is a name
|
||||
* this is assumed to be the root entry with no overrides applied.
|
||||
*
|
||||
* If the target is an object then this will simply apply overrides
|
||||
*/
|
||||
function _buildCacheableThemeSettings(theme: ITheme, target: string | IComponentSettings, overrides?: IOverrideLookup): IComponentSettings {
|
||||
if (typeof target === 'string') {
|
||||
// this means we are building the base settings with no overrides applied
|
||||
const setToMerge = getParentSettingsChain(theme.settings, target);
|
||||
// apply the finalizers as part of the merge. This will recurse to overrides which means that there
|
||||
// is no need to finalize again when overrides are applied
|
||||
return mergeSettings(...setToMerge);
|
||||
}
|
||||
// this is just doing override application so do that now
|
||||
return resolveSettingsOverrides(target, overrides);
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to look the settings up in the theme, create the settings if it was not found
|
||||
*/
|
||||
function _findOrCreateSettings(
|
||||
theme: ITheme,
|
||||
target: string | IComponentSettings,
|
||||
styleKey: string,
|
||||
overrides?: IOverrideLookup
|
||||
): IComponentSettings {
|
||||
// get the style cache from the theme, ensuring it exists in the process
|
||||
let styleCache: IComponentSettingsCollection = theme[_styleCacheKey];
|
||||
if (!styleCache) {
|
||||
theme[_styleCacheKey] = styleCache = {};
|
||||
}
|
||||
|
||||
// if this entry is not in the style cache already go through the full resolution logic
|
||||
if (!styleCache[styleKey]) {
|
||||
styleCache[styleKey] = _buildCacheableThemeSettings(theme, target, overrides);
|
||||
}
|
||||
|
||||
return styleCache[styleKey];
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param theme - theme used to retrieve settings
|
||||
* @param name - name of the settings entry to retrieve
|
||||
* @param overrides - optional override lookup object to conditionally apply overrides
|
||||
*/
|
||||
export function getSettings(theme: ITheme, name: string, overrides?: IOverrideLookup): ISettingsWithKey {
|
||||
let settings = _findOrCreateSettings(theme, name, name);
|
||||
let styleKey = name;
|
||||
if (overrides && settings) {
|
||||
styleKey = getOverrideKey(name, settings._precedence || [], overrides);
|
||||
if (styleKey !== name) {
|
||||
settings = _findOrCreateSettings(theme, settings, styleKey, overrides);
|
||||
}
|
||||
}
|
||||
return { settings, styleKey };
|
||||
export function getSettings(theme: ITheme, name: string): IComponentSettings {
|
||||
return (theme.settings && theme.settings[name]) || undefined;
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче