first commit

This commit is contained in:
Stefan Hacker
2026-04-03 09:38:48 +02:00
commit 37ad745546
47450 changed files with 3120798 additions and 0 deletions
@@ -0,0 +1,41 @@
export type ISettings = {
[key: string]: any;
};
export type ISettingsFunction = (settings: ISettings) => ISettings;
/**
* @deprecated Use ISettings.
*/
export type Settings = ISettings;
/**
* @deprecated Use ISettingsFunction.
*/
export type SettingsFunction = ISettingsFunction;
export interface ICustomizations {
settings: ISettings;
scopedSettings: {
[key: string]: ISettings;
};
inCustomizerContext?: boolean;
}
export declare class Customizations {
private static _suppressUpdates;
static reset(): void;
/** Apply global Customization settings.
* @example Customizations.applySettings(\{ theme: \{...\} \});
*/
static applySettings(settings: ISettings): void;
/** Apply Customizations to a particular named scope, like a component.
* @example Customizations.applyScopedSettings('Nav', \{ styles: () =\> \{\} \});
*/
static applyScopedSettings(scopeName: string, settings: ISettings): void;
static getSettings(properties: string[], scopeName?: string, localSettings?: ICustomizations): any;
/** Used to run some code that sets Customizations without triggering an update until the end.
* Useful for applying Customizations that don't affect anything currently rendered, or for
* applying many customizations at once.
* @param suppressUpdate - Do not raise the change event at the end, preventing all updates
*/
static applyBatchedUpdates(code: () => void, suppressUpdate?: boolean): void;
static observe(onChange: () => void): void;
static unobserve(onChange: () => void): void;
private static _raiseChange;
}
@@ -0,0 +1,82 @@
define(["require", "exports", "tslib", "../GlobalSettings"], function (require, exports, tslib_1, GlobalSettings_1) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Customizations = void 0;
var CustomizationsGlobalKey = 'customizations';
var NO_CUSTOMIZATIONS = { settings: {}, scopedSettings: {}, inCustomizerContext: false };
var _allSettings = GlobalSettings_1.GlobalSettings.getValue(CustomizationsGlobalKey, {
settings: {},
scopedSettings: {},
inCustomizerContext: false,
});
var _events = [];
var Customizations = /** @class */ (function () {
function Customizations() {
}
Customizations.reset = function () {
_allSettings.settings = {};
_allSettings.scopedSettings = {};
};
/** Apply global Customization settings.
* @example Customizations.applySettings(\{ theme: \{...\} \});
*/
Customizations.applySettings = function (settings) {
_allSettings.settings = tslib_1.__assign(tslib_1.__assign({}, _allSettings.settings), settings);
Customizations._raiseChange();
};
/** Apply Customizations to a particular named scope, like a component.
* @example Customizations.applyScopedSettings('Nav', \{ styles: () =\> \{\} \});
*/
Customizations.applyScopedSettings = function (scopeName, settings) {
_allSettings.scopedSettings[scopeName] = tslib_1.__assign(tslib_1.__assign({}, _allSettings.scopedSettings[scopeName]), settings);
Customizations._raiseChange();
};
Customizations.getSettings = function (properties, scopeName, localSettings) {
if (localSettings === void 0) { localSettings = NO_CUSTOMIZATIONS; }
var settings = {};
var localScopedSettings = (scopeName && localSettings.scopedSettings[scopeName]) || {};
var globalScopedSettings = (scopeName && _allSettings.scopedSettings[scopeName]) || {};
for (var _i = 0, properties_1 = properties; _i < properties_1.length; _i++) {
var property = properties_1[_i];
settings[property] =
localScopedSettings[property] ||
localSettings.settings[property] ||
globalScopedSettings[property] ||
_allSettings.settings[property];
}
return settings;
};
/** Used to run some code that sets Customizations without triggering an update until the end.
* Useful for applying Customizations that don't affect anything currently rendered, or for
* applying many customizations at once.
* @param suppressUpdate - Do not raise the change event at the end, preventing all updates
*/
Customizations.applyBatchedUpdates = function (code, suppressUpdate) {
Customizations._suppressUpdates = true;
try {
code();
}
catch (_a) {
/* do nothing */
}
Customizations._suppressUpdates = false;
if (!suppressUpdate) {
Customizations._raiseChange();
}
};
Customizations.observe = function (onChange) {
_events.push(onChange);
};
Customizations.unobserve = function (onChange) {
_events = _events.filter(function (cb) { return cb !== onChange; });
};
Customizations._raiseChange = function () {
if (!Customizations._suppressUpdates) {
_events.forEach(function (cb) { return cb(); });
}
};
return Customizations;
}());
exports.Customizations = Customizations;
});
//# sourceMappingURL=Customizations.js.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
export {};
@@ -0,0 +1,40 @@
define(["require", "exports", "./Customizations"], function (require, exports, Customizations_1) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
describe('Customizations', function () {
beforeEach(function () {
Customizations_1.Customizations.reset();
});
it('can set/get global settings', function () {
Customizations_1.Customizations.applySettings({ a: 'a', b: 'b' });
expect(Customizations_1.Customizations.getSettings(['a', 'b'])).toEqual({
a: 'a',
b: 'b',
});
expect(Customizations_1.Customizations.getSettings(['a'])).toEqual({
a: 'a',
});
expect(Customizations_1.Customizations.getSettings(['z'])).toEqual({});
});
it('can set/get scoped settings', function () {
Customizations_1.Customizations.applyScopedSettings('Foo', { a: 'a', b: 'b' });
expect(Customizations_1.Customizations.getSettings(['a', 'b'], 'Foo')).toEqual({
a: 'a',
b: 'b',
});
expect(Customizations_1.Customizations.getSettings(['a'], 'Bar')).toEqual({});
});
it('can fire changes', function () {
var counter = 0;
var incCounter = function () { return counter++; };
Customizations_1.Customizations.observe(incCounter);
Customizations_1.Customizations.applyScopedSettings('Foo', { a: 'a', b: 'b' });
Customizations_1.Customizations.applySettings({ a: 'a', b: 'b' });
expect(counter).toEqual(2);
Customizations_1.Customizations.unobserve(incCounter);
Customizations_1.Customizations.applySettings({ a: 'a', b: 'b' });
expect(counter).toEqual(2);
});
});
});
//# sourceMappingURL=Customizations.test.js.map
@@ -0,0 +1 @@
{"version":3,"file":"Customizations.test.js","sourceRoot":"../src/","sources":["customizations/Customizations.test.ts"],"names":[],"mappings":";;;IAEA,QAAQ,CAAC,gBAAgB,EAAE;QACzB,UAAU,CAAC;YACT,+BAAc,CAAC,KAAK,EAAE,CAAC;QACzB,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,6BAA6B,EAAE;YAChC,+BAAc,CAAC,aAAa,CAAC,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,CAAC;YAEjD,MAAM,CAAC,+BAAc,CAAC,WAAW,CAAC,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC;gBACrD,CAAC,EAAE,GAAG;gBACN,CAAC,EAAE,GAAG;aACP,CAAC,CAAC;YAEH,MAAM,CAAC,+BAAc,CAAC,WAAW,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC;gBAChD,CAAC,EAAE,GAAG;aACP,CAAC,CAAC;YAEH,MAAM,CAAC,+BAAc,CAAC,WAAW,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;QACxD,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,6BAA6B,EAAE;YAChC,+BAAc,CAAC,mBAAmB,CAAC,KAAK,EAAE,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,CAAC;YAE9D,MAAM,CAAC,+BAAc,CAAC,WAAW,CAAC,CAAC,GAAG,EAAE,GAAG,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC;gBAC5D,CAAC,EAAE,GAAG;gBACN,CAAC,EAAE,GAAG;aACP,CAAC,CAAC;YAEH,MAAM,CAAC,+BAAc,CAAC,WAAW,CAAC,CAAC,GAAG,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;QAC/D,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,kBAAkB,EAAE;YACrB,IAAI,OAAO,GAAG,CAAC,CAAC;YAChB,IAAI,UAAU,GAAG,cAAM,OAAA,OAAO,EAAE,EAAT,CAAS,CAAC;YAEjC,+BAAc,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;YAEnC,+BAAc,CAAC,mBAAmB,CAAC,KAAK,EAAE,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,CAAC;YAC9D,+BAAc,CAAC,aAAa,CAAC,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,CAAC;YAEjD,MAAM,CAAC,OAAO,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;YAC3B,+BAAc,CAAC,SAAS,CAAC,UAAU,CAAC,CAAC;YAErC,+BAAc,CAAC,aAAa,CAAC,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,CAAC;YACjD,MAAM,CAAC,OAAO,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;QAC7B,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC","sourcesContent":["import { Customizations } from './Customizations';\n\ndescribe('Customizations', () => {\n beforeEach(() => {\n Customizations.reset();\n });\n\n it('can set/get global settings', () => {\n Customizations.applySettings({ a: 'a', b: 'b' });\n\n expect(Customizations.getSettings(['a', 'b'])).toEqual({\n a: 'a',\n b: 'b',\n });\n\n expect(Customizations.getSettings(['a'])).toEqual({\n a: 'a',\n });\n\n expect(Customizations.getSettings(['z'])).toEqual({});\n });\n\n it('can set/get scoped settings', () => {\n Customizations.applyScopedSettings('Foo', { a: 'a', b: 'b' });\n\n expect(Customizations.getSettings(['a', 'b'], 'Foo')).toEqual({\n a: 'a',\n b: 'b',\n });\n\n expect(Customizations.getSettings(['a'], 'Bar')).toEqual({});\n });\n\n it('can fire changes', () => {\n let counter = 0;\n let incCounter = () => counter++;\n\n Customizations.observe(incCounter);\n\n Customizations.applyScopedSettings('Foo', { a: 'a', b: 'b' });\n Customizations.applySettings({ a: 'a', b: 'b' });\n\n expect(counter).toEqual(2);\n Customizations.unobserve(incCounter);\n\n Customizations.applySettings({ a: 'a', b: 'b' });\n expect(counter).toEqual(2);\n });\n});\n"]}
@@ -0,0 +1,25 @@
import * as React from 'react';
import type { ICustomizerProps } from './Customizer.types';
/**
* The Customizer component allows for default props to be mixed into components which
* are decorated with the customizable() decorator, or use the styled HOC. This enables
* injection scenarios like:
*
* 1. render svg icons instead of the icon font within all buttons
* 2. inject a custom theme object into a component
*
* Props are provided via the settings prop which should be one of the following:
* - A json map which contains 1 or more name/value pairs representing injectable props.
* - A function that receives the current settings and returns the new ones that apply to the scope
*
* @public
*
* @deprecated This component is deprecated for purpose of applying theme to components
* as of `@fluentui/react` version 8. Use `ThemeProvider` for applying theme instead.
*/
export declare class Customizer extends React.Component<ICustomizerProps> {
componentDidMount(): void;
componentWillUnmount(): void;
render(): React.ReactElement<{}>;
private _onCustomizationChange;
}
+50
View File
@@ -0,0 +1,50 @@
define(["require", "exports", "tslib", "react", "./Customizations", "./CustomizerContext", "./mergeCustomizations"], function (require, exports, tslib_1, React, Customizations_1, CustomizerContext_1, mergeCustomizations_1) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Customizer = void 0;
/**
* The Customizer component allows for default props to be mixed into components which
* are decorated with the customizable() decorator, or use the styled HOC. This enables
* injection scenarios like:
*
* 1. render svg icons instead of the icon font within all buttons
* 2. inject a custom theme object into a component
*
* Props are provided via the settings prop which should be one of the following:
* - A json map which contains 1 or more name/value pairs representing injectable props.
* - A function that receives the current settings and returns the new ones that apply to the scope
*
* @public
*
* @deprecated This component is deprecated for purpose of applying theme to components
* as of `@fluentui/react` version 8. Use `ThemeProvider` for applying theme instead.
*/
var Customizer = /** @class */ (function (_super) {
tslib_1.__extends(Customizer, _super);
function Customizer() {
var _this = _super !== null && _super.apply(this, arguments) || this;
_this._onCustomizationChange = function () { return _this.forceUpdate(); };
return _this;
}
Customizer.prototype.componentDidMount = function () {
Customizations_1.Customizations.observe(this._onCustomizationChange);
};
Customizer.prototype.componentWillUnmount = function () {
Customizations_1.Customizations.unobserve(this._onCustomizationChange);
};
Customizer.prototype.render = function () {
var _this = this;
var contextTransform = this.props.contextTransform;
return (React.createElement(CustomizerContext_1.CustomizerContext.Consumer, null, function (parentContext) {
var newContext = (0, mergeCustomizations_1.mergeCustomizations)(_this.props, parentContext);
if (contextTransform) {
newContext = contextTransform(newContext);
}
return React.createElement(CustomizerContext_1.CustomizerContext.Provider, { value: newContext }, _this.props.children);
}));
};
return Customizer;
}(React.Component));
exports.Customizer = Customizer;
});
//# sourceMappingURL=Customizer.js.map
@@ -0,0 +1 @@
{"version":3,"file":"Customizer.js","sourceRoot":"../src/","sources":["customizations/Customizer.tsx"],"names":[],"mappings":";;;;IAOA;;;;;;;;;;;;;;;;OAgBG;IACH;QAAgC,sCAAiC;QAAjE;;YA0BU,4BAAsB,GAAG,cAAM,OAAA,KAAI,CAAC,WAAW,EAAE,EAAlB,CAAkB,CAAC;;QAC5D,CAAC;QA1BQ,sCAAiB,GAAxB;YACE,+BAAc,CAAC,OAAO,CAAC,IAAI,CAAC,sBAAsB,CAAC,CAAC;QACtD,CAAC;QAEM,yCAAoB,GAA3B;YACE,+BAAc,CAAC,SAAS,CAAC,IAAI,CAAC,sBAAsB,CAAC,CAAC;QACxD,CAAC;QAEM,2BAAM,GAAb;YAAA,iBAeC;YAdS,IAAA,gBAAgB,GAAK,IAAI,CAAC,KAAK,iBAAf,CAAgB;YACxC,OAAO,CACL,oBAAC,qCAAiB,CAAC,QAAQ,QACxB,UAAC,aAAiC;gBACjC,IAAI,UAAU,GAAG,IAAA,yCAAmB,EAAC,KAAI,CAAC,KAAK,EAAE,aAAa,CAAC,CAAC;gBAEhE,IAAI,gBAAgB,EAAE,CAAC;oBACrB,UAAU,GAAG,gBAAgB,CAAC,UAAU,CAAC,CAAC;gBAC5C,CAAC;gBAED,OAAO,oBAAC,qCAAiB,CAAC,QAAQ,IAAC,KAAK,EAAE,UAAU,IAAG,KAAI,CAAC,KAAK,CAAC,QAAQ,CAA8B,CAAC;YAC3G,CAAC,CAC0B,CAC9B,CAAC;QACJ,CAAC;QAGH,iBAAC;IAAD,CAAC,AA3BD,CAAgC,KAAK,CAAC,SAAS,GA2B9C;IA3BY,gCAAU","sourcesContent":["import * as React from 'react';\nimport { Customizations } from './Customizations';\nimport { CustomizerContext } from './CustomizerContext';\nimport { mergeCustomizations } from './mergeCustomizations';\nimport type { ICustomizerContext } from './CustomizerContext';\nimport type { ICustomizerProps } from './Customizer.types';\n\n/**\n * The Customizer component allows for default props to be mixed into components which\n * are decorated with the customizable() decorator, or use the styled HOC. This enables\n * injection scenarios like:\n *\n * 1. render svg icons instead of the icon font within all buttons\n * 2. inject a custom theme object into a component\n *\n * Props are provided via the settings prop which should be one of the following:\n * - A json map which contains 1 or more name/value pairs representing injectable props.\n * - A function that receives the current settings and returns the new ones that apply to the scope\n *\n * @public\n *\n * @deprecated This component is deprecated for purpose of applying theme to components\n * as of `@fluentui/react` version 8. Use `ThemeProvider` for applying theme instead.\n */\nexport class Customizer extends React.Component<ICustomizerProps> {\n public componentDidMount(): void {\n Customizations.observe(this._onCustomizationChange);\n }\n\n public componentWillUnmount(): void {\n Customizations.unobserve(this._onCustomizationChange);\n }\n\n public render(): React.ReactElement<{}> {\n const { contextTransform } = this.props;\n return (\n <CustomizerContext.Consumer>\n {(parentContext: ICustomizerContext) => {\n let newContext = mergeCustomizations(this.props, parentContext);\n\n if (contextTransform) {\n newContext = contextTransform(newContext);\n }\n\n return <CustomizerContext.Provider value={newContext}>{this.props.children}</CustomizerContext.Provider>;\n }}\n </CustomizerContext.Consumer>\n );\n }\n\n private _onCustomizationChange = () => this.forceUpdate();\n}\n"]}
@@ -0,0 +1 @@
export {};
@@ -0,0 +1,151 @@
define(["require", "exports", "tslib", "react", "@testing-library/react", "./customizable", "./Customizer", "./Customizations"], function (require, exports, tslib_1, React, react_1, customizable_1, Customizer_1, Customizations_1) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var Foo = /** @class */ (function (_super) {
tslib_1.__extends(Foo, _super);
function Foo() {
return _super !== null && _super.apply(this, arguments) || this;
}
Foo.prototype.render = function () {
return React.createElement("div", null, this.props.field);
};
Foo = tslib_1.__decorate([
(0, customizable_1.customizable)('Foo', ['field'])
], Foo);
return Foo;
}(React.Component));
var Bar = /** @class */ (function (_super) {
tslib_1.__extends(Bar, _super);
function Bar() {
return _super !== null && _super.apply(this, arguments) || this;
}
Bar.prototype.render = function () {
return (React.createElement("div", null,
this.props.field,
this.props.field2,
this.props.field3));
};
Bar = tslib_1.__decorate([
(0, customizable_1.customizable)('Bar', ['field', 'field2', 'field3'])
], Bar);
return Bar;
}(React.Component));
describe('Customizer', function () {
beforeEach(function () {
Customizations_1.Customizations.reset();
});
it('can provide new defaults', function () {
(0, react_1.render)(React.createElement(Customizer_1.Customizer, { settings: { field: 'customName' } },
React.createElement(Foo, null)));
expect(react_1.screen.getByText('customName')).toBeInTheDocument();
});
it('can pass through global settings', function () {
Customizations_1.Customizations.applySettings({ field: 'globalName' });
(0, react_1.render)(React.createElement(Customizer_1.Customizer, { settings: { nonMatch: 'customName' } },
React.createElement(Foo, null)));
expect(react_1.screen.getByText('globalName')).toBeInTheDocument();
});
it('can override global settings', function () {
Customizations_1.Customizations.applySettings({ field: 'globalName' });
(0, react_1.render)(React.createElement(Customizer_1.Customizer, { settings: { field: 'customName' } },
React.createElement(Foo, null)));
expect(react_1.screen.getByText('customName')).toBeInTheDocument();
});
it('can scope settings to specific components', function () {
var scopedSettings = {
Foo: { field: 'scopedToFoo' },
Bar: { field: 'scopedToBar' },
};
(0, react_1.render)(React.createElement(Customizer_1.Customizer, { scopedSettings: scopedSettings },
React.createElement("div", null,
React.createElement(Foo, null),
React.createElement(Bar, null))));
expect(react_1.screen.getByText('scopedToFoo')).toBeInTheDocument();
expect(react_1.screen.getByText('scopedToBar')).toBeInTheDocument();
});
it('can layer global settings', function () {
(0, react_1.render)(React.createElement(Customizer_1.Customizer, { settings: { field: 'field' } },
React.createElement(Customizer_1.Customizer, { settings: { field2: 'field2' } },
React.createElement(Bar, null))));
expect(react_1.screen.getByText('fieldfield2')).toBeInTheDocument();
});
it('can layer scoped settings', function () {
Customizations_1.Customizations.applySettings({ field3: 'field3' });
(0, react_1.render)(React.createElement(Customizer_1.Customizer, { scopedSettings: { Bar: { field: 'field', field2: 'oldfield2' } } },
React.createElement(Customizer_1.Customizer, { scopedSettings: { Bar: { field2: 'field2' } } },
React.createElement(Bar, null))));
expect(react_1.screen.getByText('fieldfield2field3')).toBeInTheDocument();
});
it('can layer scoped settings with scopedSettingsFunction', function () {
Customizations_1.Customizations.applySettings({ field3: 'field3' });
(0, react_1.render)(React.createElement(Customizer_1.Customizer, { scopedSettings: { Bar: { field: 'field' } } },
React.createElement(Customizer_1.Customizer, { scopedSettings: function (scopedSettings) { return ({
Bar: tslib_1.__assign(tslib_1.__assign({}, scopedSettings.Bar), { field2: 'field2' }),
}); } },
React.createElement(Bar, null))));
expect(react_1.screen.getByText('fieldfield2field3')).toBeInTheDocument();
});
it('it allows scopedSettings to be merged when a function is passed', function () {
(0, react_1.render)(React.createElement(Customizer_1.Customizer, { scopedSettings: { Foo: { field: 'scopedToFoo' } } },
React.createElement(Customizer_1.Customizer, { scopedSettings: function (settings) { return (tslib_1.__assign(tslib_1.__assign({}, settings), { Bar: { field: 'scopedToBar' } })); } },
React.createElement("div", null,
React.createElement(Foo, null),
React.createElement(Bar, null)))));
expect(react_1.screen.getByText('scopedToFoo')).toBeInTheDocument();
expect(react_1.screen.getByText('scopedToBar')).toBeInTheDocument();
});
it('overrides previously set settings', function () {
(0, react_1.render)(React.createElement(Customizer_1.Customizer, { settings: { field: 'field1' } },
React.createElement(Customizer_1.Customizer, { settings: { field: 'field2' } },
React.createElement(Bar, null))));
expect(react_1.screen.getByText('field2')).toBeInTheDocument();
});
it('overrides the old settings when the parameter is ignored', function () {
(0, react_1.render)(React.createElement(Customizer_1.Customizer, { settings: { field: 'field1' } },
React.createElement(Customizer_1.Customizer, { settings: function (settings) { return ({ field: 'field2' }); } },
React.createElement(Bar, null))));
expect(react_1.screen.getByText('field2')).toBeInTheDocument();
});
it('can use a function to merge settings', function () {
(0, react_1.render)(React.createElement(Customizer_1.Customizer, { settings: { field: 'field1' } },
React.createElement(Customizer_1.Customizer, { settings: function (settings) { return ({ field: settings.field + 'field2' }); } },
React.createElement(Bar, null))));
expect(react_1.screen.getByText('field1field2')).toBeInTheDocument();
});
it('can suppress updates', function () {
Customizations_1.Customizations.applySettings({ field: 'globalName' });
(0, react_1.render)(React.createElement(Customizer_1.Customizer, { settings: { nonMatch: 'customName' } },
React.createElement(Bar, null)));
// verify base state
expect(react_1.screen.getByText('globalName')).toBeInTheDocument();
(0, react_1.act)(function () {
// verify it doesn't update during suppressUpdates(), and it works through errors, and it updates after
Customizations_1.Customizations.applyBatchedUpdates(function () {
Customizations_1.Customizations.applySettings({ field: 'notGlobalName' });
// it should not update inside
expect(react_1.screen.getByText('globalName')).toBeInTheDocument();
throw new Error();
});
});
// afterwards it should have updated
expect(react_1.screen.getByText('notGlobalName')).toBeInTheDocument();
(0, react_1.act)(function () {
// verify it doesn't update during suppressUpdates(), works through errors, and can suppress final update
Customizations_1.Customizations.applyBatchedUpdates(function () {
Customizations_1.Customizations.applySettings({ field: 'notUpdated' });
// it should not update inside
expect(react_1.screen.getByText('notGlobalName')).toBeInTheDocument();
throw new Error();
}, true);
});
// afterwards, it should still be on the old value
expect(react_1.screen.getByText('notGlobalName')).toBeInTheDocument();
// verify it updates after suppressUpdates()
(0, react_1.act)(function () {
Customizations_1.Customizations.applySettings({ field2: 'lastGlobalName' });
});
expect(react_1.screen.getByText('notUpdatedlastGlobalName')).toBeInTheDocument();
});
});
});
//# sourceMappingURL=Customizer.test.js.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1,48 @@
import * as React from 'react';
import type { IBaseProps } from '../BaseComponent.types';
import type { ISettings, ISettingsFunction } from './Customizations';
import type { ICustomizerContext } from './CustomizerContext';
export type ICustomizerProps = IBaseProps & Partial<{
/**
* Settings are used as general settings for the React tree below.
* Components can subscribe to receive the settings by using `customizable`.
*
* @example
* ```
* // Settings can be represented by a plain object that contains the key value pairs.
* <Customizer settings={{ color: 'red' }} />
*
* // or a function that receives the current settings and returns the new ones
* <Customizer settings={(currentSettings) => ({ ...currentSettings, color: 'red' })} />
* ```
*/
settings: ISettings | ISettingsFunction;
/**
* Scoped settings are settings that are scoped to a specific scope. The
* scope is the name that is passed to the `customizable` function when the
* the component is customized.
*
* @example
* ```
* // Scoped settings can be represented by a plain object that contains the key value pairs.
* const myScopedSettings = {
* Button: { color: 'red' };
* };
* <Customizer scopedSettings={myScopedSettings} />
*
* // or a function that receives the current settings and returns the new ones
* const myScopedSettings = {
* Button: { color: 'red' };
* };
* <Customizer scopedSettings={(currentScopedSettings) => ({ ...currentScopedSettings, ...myScopedSettings })} />
* ```
*/
scopedSettings: ISettings | ISettingsFunction;
}> & {
children?: React.ReactNode;
/**
* Optional transform function for context. Any implementations should take care to return context without
* mutating it.
*/
contextTransform?: (context: Readonly<ICustomizerContext>) => ICustomizerContext;
};
@@ -0,0 +1,5 @@
define(["require", "exports"], function (require, exports) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
});
//# sourceMappingURL=Customizer.types.js.map
@@ -0,0 +1 @@
{"version":3,"file":"Customizer.types.js","sourceRoot":"../src/","sources":["customizations/Customizer.types.tsx"],"names":[],"mappings":"","sourcesContent":["import * as React from 'react';\nimport type { IBaseProps } from '../BaseComponent.types';\nimport type { ISettings, ISettingsFunction } from './Customizations';\nimport type { ICustomizerContext } from './CustomizerContext';\n\nexport type ICustomizerProps = IBaseProps &\n Partial<{\n /**\n * Settings are used as general settings for the React tree below.\n * Components can subscribe to receive the settings by using `customizable`.\n *\n * @example\n * ```\n * // Settings can be represented by a plain object that contains the key value pairs.\n * <Customizer settings={{ color: 'red' }} />\n *\n * // or a function that receives the current settings and returns the new ones\n * <Customizer settings={(currentSettings) => ({ ...currentSettings, color: 'red' })} />\n * ```\n */\n settings: ISettings | ISettingsFunction;\n\n /**\n * Scoped settings are settings that are scoped to a specific scope. The\n * scope is the name that is passed to the `customizable` function when the\n * the component is customized.\n *\n * @example\n * ```\n * // Scoped settings can be represented by a plain object that contains the key value pairs.\n * const myScopedSettings = {\n * Button: { color: 'red' };\n * };\n * <Customizer scopedSettings={myScopedSettings} />\n *\n * // or a function that receives the current settings and returns the new ones\n * const myScopedSettings = {\n * Button: { color: 'red' };\n * };\n * <Customizer scopedSettings={(currentScopedSettings) => ({ ...currentScopedSettings, ...myScopedSettings })} />\n * ```\n */\n scopedSettings: ISettings | ISettingsFunction;\n }> & {\n children?: React.ReactNode;\n /**\n * Optional transform function for context. Any implementations should take care to return context without\n * mutating it.\n */\n contextTransform?: (context: Readonly<ICustomizerContext>) => ICustomizerContext;\n };\n"]}
@@ -0,0 +1,6 @@
import * as React from 'react';
import type { ICustomizations } from './Customizations';
export interface ICustomizerContext {
customizations: ICustomizations;
}
export declare const CustomizerContext: React.Context<ICustomizerContext>;
@@ -0,0 +1,13 @@
define(["require", "exports", "react"], function (require, exports, React) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.CustomizerContext = void 0;
exports.CustomizerContext = React.createContext({
customizations: {
inCustomizerContext: false,
settings: {},
scopedSettings: {},
},
});
});
//# sourceMappingURL=CustomizerContext.js.map
@@ -0,0 +1 @@
{"version":3,"file":"CustomizerContext.js","sourceRoot":"../src/","sources":["customizations/CustomizerContext.ts"],"names":[],"mappings":";;;;IAOa,QAAA,iBAAiB,GAAG,KAAK,CAAC,aAAa,CAAqB;QACvE,cAAc,EAAE;YACd,mBAAmB,EAAE,KAAK;YAC1B,QAAQ,EAAE,EAAE;YACZ,cAAc,EAAE,EAAE;SACnB;KACF,CAAC,CAAC","sourcesContent":["import * as React from 'react';\nimport type { ICustomizations } from './Customizations';\n\nexport interface ICustomizerContext {\n customizations: ICustomizations;\n}\n\nexport const CustomizerContext = React.createContext<ICustomizerContext>({\n customizations: {\n inCustomizerContext: false,\n settings: {},\n scopedSettings: {},\n },\n});\n"]}
@@ -0,0 +1,2 @@
import * as React from 'react';
export declare function customizable(scope: string, fields: string[], concatStyles?: boolean): <P>(ComposedComponent: React.ComponentType<P>) => any;
@@ -0,0 +1,76 @@
define(["require", "exports", "tslib", "react", "./Customizations", "../hoistStatics", "./CustomizerContext", "@fluentui/merge-styles", "../shadowDom/contexts/MergeStylesShadowRootConsumer", "../dom/getWindow", "@fluentui/react-window-provider", "../memoize"], function (require, exports, tslib_1, React, Customizations_1, hoistStatics_1, CustomizerContext_1, merge_styles_1, MergeStylesShadowRootConsumer_1, getWindow_1, react_window_provider_1, memoize_1) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.customizable = customizable;
var memoizedMakeShadowConfig = (0, memoize_1.memoizeFunction)(merge_styles_1.makeShadowConfig);
var mergeComponentStyles = (0, memoize_1.memoizeFunction)(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function (defaultStyles, componentStyles, shadowConfig) {
var _a;
var styles = (_a = componentStyles !== null && componentStyles !== void 0 ? componentStyles : defaultStyles) !== null && _a !== void 0 ? _a : {};
styles.__shadowConfig__ = shadowConfig;
return styles;
});
function customizable(scope, fields, concatStyles) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return function customizableFactory(ComposedComponent) {
var _a;
var resultClass = (_a = /** @class */ (function (_super) {
tslib_1.__extends(ComponentWithInjectedProps, _super);
function ComponentWithInjectedProps(props) {
var _this = _super.call(this, props) || this;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
_this._styleCache = {};
_this._onSettingChanged = _this._onSettingChanged.bind(_this);
return _this;
}
ComponentWithInjectedProps.prototype.componentDidMount = function () {
Customizations_1.Customizations.observe(this._onSettingChanged);
};
ComponentWithInjectedProps.prototype.componentWillUnmount = function () {
Customizations_1.Customizations.unobserve(this._onSettingChanged);
};
ComponentWithInjectedProps.prototype.render = function () {
var _this = this;
return (React.createElement(MergeStylesShadowRootConsumer_1.MergeStylesShadowRootConsumer, { stylesheetKey: scope }, function (inShadow) {
return (React.createElement(CustomizerContext_1.CustomizerContext.Consumer, null, function (context) {
var _b;
var defaultProps = Customizations_1.Customizations.getSettings(fields, scope, context.customizations);
var win = (_b = _this.context.window) !== null && _b !== void 0 ? _b : (0, getWindow_1.getWindow)();
var shadowConfig = memoizedMakeShadowConfig(scope, inShadow, win);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
var componentProps = _this.props;
// If defaultProps.styles is a function, evaluate it before calling concatStyleSets
if (defaultProps.styles && typeof defaultProps.styles === 'function') {
defaultProps.styles = defaultProps.styles(tslib_1.__assign(tslib_1.__assign({}, defaultProps), componentProps));
}
// If concatStyles is true and custom styles have been defined compute those styles
if (concatStyles && defaultProps.styles) {
if (_this._styleCache.default !== defaultProps.styles ||
_this._styleCache.component !== componentProps.styles) {
var mergedStyles = (0, merge_styles_1.concatStyleSets)(defaultProps.styles, componentProps.styles);
mergedStyles.__shadowConfig__ = shadowConfig;
_this._styleCache.default = defaultProps.styles;
_this._styleCache.component = componentProps.styles;
_this._styleCache.merged = mergedStyles;
}
return (React.createElement(ComposedComponent, tslib_1.__assign({}, defaultProps, componentProps, { styles: _this._styleCache.merged })));
}
var styles = mergeComponentStyles(defaultProps.styles, componentProps.styles, shadowConfig);
return React.createElement(ComposedComponent, tslib_1.__assign({}, defaultProps, componentProps, { styles: styles }));
}));
}));
};
ComponentWithInjectedProps.prototype._onSettingChanged = function () {
this.forceUpdate();
};
return ComponentWithInjectedProps;
}(React.Component)),
_a.displayName = 'Customized' + scope,
_a.contextType = react_window_provider_1.WindowContext,
_a);
return (0, hoistStatics_1.hoistStatics)(ComposedComponent, resultClass);
};
}
});
//# sourceMappingURL=customizable.js.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
export {};
@@ -0,0 +1,42 @@
define(["require", "exports", "tslib", "react", "react-dom/server", "./customizable", "./Customizations"], function (require, exports, tslib_1, React, server_1, customizable_1, Customizations_1) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var Foo = /** @class */ (function (_super) {
tslib_1.__extends(Foo, _super);
function Foo() {
return _super !== null && _super.apply(this, arguments) || this;
}
Foo.prototype.render = function () {
return React.createElement("div", null, this.props.field);
};
Foo = tslib_1.__decorate([
(0, customizable_1.customizable)('Foo', ['field'])
], Foo);
return Foo;
}(React.Component));
describe('customizable (server-side rendering)', function () {
beforeEach(function () {
Customizations_1.Customizations.reset();
});
it('can receive global customizations', function () {
Customizations_1.Customizations.applySettings({ field: 'globalName' });
expect((0, server_1.renderToStaticMarkup)(React.createElement(Foo, null))).toEqual('<div>globalName</div>');
});
it('can receive scoped customizations', function () {
Customizations_1.Customizations.applySettings({ field: 'globalName' });
Customizations_1.Customizations.applyScopedSettings('Foo', { field: 'scopedName' });
expect((0, server_1.renderToStaticMarkup)(React.createElement(Foo, null))).toEqual('<div>scopedName</div>');
});
it('can ignore scoped customizations that do not apply', function () {
Customizations_1.Customizations.applySettings({ field: 'globalName' });
Customizations_1.Customizations.applyScopedSettings('Bar', { field: 'scopedName' });
expect((0, server_1.renderToStaticMarkup)(React.createElement(Foo, null))).toEqual('<div>globalName</div>');
});
it('can accept props over global/scoped values', function () {
Customizations_1.Customizations.applySettings({ field: 'globalName' });
Customizations_1.Customizations.applyScopedSettings('Foo', { field: 'scopedName' });
expect((0, server_1.renderToStaticMarkup)(React.createElement(Foo, { field: "name" }))).toEqual('<div>name</div>');
});
});
});
//# sourceMappingURL=customizable.server.test.js.map
@@ -0,0 +1 @@
{"version":3,"file":"customizable.server.test.js","sourceRoot":"../src/","sources":["customizations/customizable.server.test.tsx"],"names":[],"mappings":";;;IAUA;QAAkB,+BAAuC;QAAzD;;QAIA,CAAC;QAHQ,oBAAM,GAAb;YACE,OAAO,iCAAM,IAAI,CAAC,KAAK,CAAC,KAAK,CAAO,CAAC;QACvC,CAAC;QAHG,GAAG;YADR,IAAA,2BAAY,EAAC,KAAK,EAAE,CAAC,OAAO,CAAC,CAAC;WACzB,GAAG,CAIR;QAAD,UAAC;KAAA,AAJD,CAAkB,KAAK,CAAC,SAAS,GAIhC;IAED,QAAQ,CAAC,sCAAsC,EAAE;QAC/C,UAAU,CAAC;YACT,+BAAc,CAAC,KAAK,EAAE,CAAC;QACzB,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,mCAAmC,EAAE;YACtC,+BAAc,CAAC,aAAa,CAAC,EAAE,KAAK,EAAE,YAAY,EAAE,CAAC,CAAC;YACtD,MAAM,CAAC,IAAA,6BAAoB,EAAC,oBAAC,GAAG,OAAG,CAAC,CAAC,CAAC,OAAO,CAAC,uBAAuB,CAAC,CAAC;QACzE,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,mCAAmC,EAAE;YACtC,+BAAc,CAAC,aAAa,CAAC,EAAE,KAAK,EAAE,YAAY,EAAE,CAAC,CAAC;YACtD,+BAAc,CAAC,mBAAmB,CAAC,KAAK,EAAE,EAAE,KAAK,EAAE,YAAY,EAAE,CAAC,CAAC;YACnE,MAAM,CAAC,IAAA,6BAAoB,EAAC,oBAAC,GAAG,OAAG,CAAC,CAAC,CAAC,OAAO,CAAC,uBAAuB,CAAC,CAAC;QACzE,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,oDAAoD,EAAE;YACvD,+BAAc,CAAC,aAAa,CAAC,EAAE,KAAK,EAAE,YAAY,EAAE,CAAC,CAAC;YACtD,+BAAc,CAAC,mBAAmB,CAAC,KAAK,EAAE,EAAE,KAAK,EAAE,YAAY,EAAE,CAAC,CAAC;YACnE,MAAM,CAAC,IAAA,6BAAoB,EAAC,oBAAC,GAAG,OAAG,CAAC,CAAC,CAAC,OAAO,CAAC,uBAAuB,CAAC,CAAC;QACzE,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,4CAA4C,EAAE;YAC/C,+BAAc,CAAC,aAAa,CAAC,EAAE,KAAK,EAAE,YAAY,EAAE,CAAC,CAAC;YACtD,+BAAc,CAAC,mBAAmB,CAAC,KAAK,EAAE,EAAE,KAAK,EAAE,YAAY,EAAE,CAAC,CAAC;YACnE,MAAM,CAAC,IAAA,6BAAoB,EAAC,oBAAC,GAAG,IAAC,KAAK,EAAC,MAAM,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,iBAAiB,CAAC,CAAC;QAChF,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC","sourcesContent":["/**\n * @jest-environment node\n */\nimport * as React from 'react';\nimport { renderToStaticMarkup } from 'react-dom/server';\nimport { customizable } from './customizable';\nimport { Customizations } from './Customizations';\nimport type { JSXElement } from '../jsx';\n\n@customizable('Foo', ['field'])\nclass Foo extends React.Component<{ field?: string }, {}> {\n public render(): JSXElement {\n return <div>{this.props.field}</div>;\n }\n}\n\ndescribe('customizable (server-side rendering)', () => {\n beforeEach(() => {\n Customizations.reset();\n });\n\n it('can receive global customizations', () => {\n Customizations.applySettings({ field: 'globalName' });\n expect(renderToStaticMarkup(<Foo />)).toEqual('<div>globalName</div>');\n });\n\n it('can receive scoped customizations', () => {\n Customizations.applySettings({ field: 'globalName' });\n Customizations.applyScopedSettings('Foo', { field: 'scopedName' });\n expect(renderToStaticMarkup(<Foo />)).toEqual('<div>scopedName</div>');\n });\n\n it('can ignore scoped customizations that do not apply', () => {\n Customizations.applySettings({ field: 'globalName' });\n Customizations.applyScopedSettings('Bar', { field: 'scopedName' });\n expect(renderToStaticMarkup(<Foo />)).toEqual('<div>globalName</div>');\n });\n\n it('can accept props over global/scoped values', () => {\n Customizations.applySettings({ field: 'globalName' });\n Customizations.applyScopedSettings('Foo', { field: 'scopedName' });\n expect(renderToStaticMarkup(<Foo field=\"name\" />)).toEqual('<div>name</div>');\n });\n});\n"]}
@@ -0,0 +1 @@
export {};
@@ -0,0 +1,176 @@
define(["require", "exports", "tslib", "react", "@testing-library/react", "./customizable", "./Customizations", "./Customizer"], function (require, exports, tslib_1, React, react_1, customizable_1, Customizations_1, Customizer_1) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var ConcatStyles = /** @class */ (function (_super) {
tslib_1.__extends(ConcatStyles, _super);
function ConcatStyles() {
return _super !== null && _super.apply(this, arguments) || this;
}
ConcatStyles.prototype.render = function () {
return (React.createElement("div", { "data-testid": "concat-styles",
// @ts-expect-error - for testing purposes
style: this.props.styles, "data-style": JSON.stringify(this.props.styles.root) }));
};
ConcatStyles = tslib_1.__decorate([
(0, customizable_1.customizable)('ConcatStyles', ['styles'], true)
], ConcatStyles);
return ConcatStyles;
}(React.Component));
var OverrideStyles = /** @class */ (function (_super) {
tslib_1.__extends(OverrideStyles, _super);
function OverrideStyles() {
return _super !== null && _super.apply(this, arguments) || this;
}
OverrideStyles.prototype.render = function () {
return (React.createElement("div", { "data-testid": "override-styles",
// @ts-expect-error - for testing purposes
style: this.props.styles, "data-style": JSON.stringify(this.props.styles.root) }));
};
OverrideStyles = tslib_1.__decorate([
(0, customizable_1.customizable)('OverrideStyles', ['styles'])
], OverrideStyles);
return OverrideStyles;
}(React.Component));
var StyleFunction = /** @class */ (function (_super) {
tslib_1.__extends(StyleFunction, _super);
function StyleFunction() {
return _super !== null && _super.apply(this, arguments) || this;
}
StyleFunction.prototype.render = function () {
var styles = this.props.styles({ styles: { root: {} } });
return (React.createElement("div", { "data-testid": "style-function", style: styles.root, "data-style": JSON.stringify(styles.root) }));
};
StyleFunction = tslib_1.__decorate([
(0, customizable_1.customizable)('StyleFunction', ['styles'])
], StyleFunction);
return StyleFunction;
}(React.Component));
describe('customizable', function () {
beforeEach(function () {
Customizations_1.Customizations.reset();
});
it('can concatenate global styles and component styles', function () {
var globalStyles = { color: 'red', background: 'red' };
var componentStyles = { color: 'blue' };
Customizations_1.Customizations.applySettings({ styles: { root: globalStyles } });
var rtl = (0, react_1.render)(React.createElement(Customizer_1.Customizer, null,
React.createElement(ConcatStyles, { styles: { root: componentStyles } })));
var concatStyleRoot = rtl.getByTestId('concat-styles');
var rootStyles = JSON.parse(concatStyleRoot.getAttribute('data-style'));
expect(Object.keys(concatStyleRoot.style)).toEqual(expect.arrayContaining(['root', '__shadowConfig__']));
expect(rootStyles).toEqual([globalStyles, componentStyles]);
});
it('can concatenate global styles and component styles', function () {
var globalStyles = function (_props) {
return { root: { color: 'red', background: 'green' } };
};
var componentStyles = { root: { color: 'blue' } };
Customizations_1.Customizations.applySettings({ styles: globalStyles });
var rtl = (0, react_1.render)(React.createElement(Customizer_1.Customizer, null,
React.createElement(ConcatStyles, { styles: componentStyles })));
var concatStyleRoot = rtl.getByTestId('concat-styles');
var rootStyles = JSON.parse(concatStyleRoot.getAttribute('data-style'));
expect(Object.keys(concatStyleRoot.style)).toEqual(expect.arrayContaining(['root', '__shadowConfig__']));
expect(rootStyles).toEqual([globalStyles({}).root, componentStyles.root]);
});
it('will apply component style function when no global styles are present', function () {
var componentStyles = { root: { color: 'rgb(255, 0, 0)', background: 'rgb(0, 128, 0)' } };
var componentStylesFn = function (_props) {
return componentStyles;
};
var wrapper = (0, react_1.render)(React.createElement(Customizer_1.Customizer, null,
React.createElement(StyleFunction, { styles: componentStylesFn })));
var component = wrapper.getByTestId('style-function');
var rootStyles = JSON.parse(component.getAttribute('data-style'));
expect(component).toHaveStyle(componentStyles.root);
expect(rootStyles).toEqual(componentStyles.root);
});
it('can concatenate scoped styles and component styles', function () {
var scopedStyles = { color: 'green', background: 'green' };
var componentStyles = { color: 'blue' };
Customizations_1.Customizations.applyScopedSettings('ConcatStyles', { styles: { root: scopedStyles } });
var rtl = (0, react_1.render)(React.createElement(Customizer_1.Customizer, null,
React.createElement(ConcatStyles, { styles: { root: componentStyles } })));
var concatStyleRoot = rtl.getByTestId('concat-styles');
var rootStyles = JSON.parse(concatStyleRoot.getAttribute('data-style'));
expect(Object.keys(concatStyleRoot.style)).toEqual(expect.arrayContaining(['root', '__shadowConfig__']));
expect(rootStyles).toEqual([scopedStyles, componentStyles]);
});
it('can override global styles with component styles', function () {
var globalStyles = { color: 'red', background: 'red' };
var componentStyles = { color: 'blue' };
Customizations_1.Customizations.applySettings({ styles: { root: globalStyles } });
var rtl = (0, react_1.render)(React.createElement(Customizer_1.Customizer, null,
React.createElement(OverrideStyles, { styles: { root: componentStyles } })));
var overrideStyleRoot = rtl.getByTestId('override-styles');
var rootStyles = JSON.parse(overrideStyleRoot.getAttribute('data-style'));
expect(Object.keys(overrideStyleRoot.style)).toEqual(expect.arrayContaining(['root', '__shadowConfig__']));
expect(rootStyles).toEqual(componentStyles);
});
it('can override scoped styles with component styles', function () {
var scopedStyles = { color: 'green', background: 'green' };
var componentStyles = { color: 'blue' };
Customizations_1.Customizations.applyScopedSettings('OverrideStyles', { styles: { root: scopedStyles } });
var rtl = (0, react_1.render)(React.createElement(Customizer_1.Customizer, null,
React.createElement(OverrideStyles, { styles: { root: componentStyles } })));
var overrideStyleRoot = rtl.getByTestId('override-styles');
var rootStyles = JSON.parse(overrideStyleRoot.getAttribute('data-style'));
expect(Object.keys(overrideStyleRoot.style)).toEqual(expect.arrayContaining(['root', '__shadowConfig__']));
expect(rootStyles).toEqual(componentStyles);
});
it('should not mutate styles if no change to component and global styles', function () {
var globalRootStyles = { color: 'red', background: 'red' };
var componentRootStyles = { color: 'blue' };
var componentStyles = { root: componentRootStyles };
Customizations_1.Customizations.applySettings({ styles: { root: globalRootStyles } });
var rtl = (0, react_1.render)(React.createElement(Customizer_1.Customizer, null,
React.createElement(ConcatStyles, { styles: componentStyles })));
var overrideStyleRoot = rtl.getByTestId('concat-styles');
var rootStyles = JSON.parse(overrideStyleRoot.getAttribute('data-style'));
expect(Object.keys(overrideStyleRoot.style)).toEqual(expect.arrayContaining(['root', '__shadowConfig__']));
expect(rootStyles).toEqual([globalRootStyles, componentRootStyles]);
rtl.rerender(React.createElement(Customizer_1.Customizer, null,
React.createElement(ConcatStyles, { styles: componentStyles })));
var updatedOverrideStyleRoot = rtl.getByTestId('concat-styles');
var updatedRootStyles = JSON.parse(updatedOverrideStyleRoot.getAttribute('data-style'));
expect(updatedRootStyles).toStrictEqual(rootStyles);
expect(updatedRootStyles).toEqual(rootStyles);
});
it('should not mutate styles if no change to component styles without global styles', function () {
var componentStyles = { root: { color: 'blue' } };
var rtl = (0, react_1.render)(React.createElement(Customizer_1.Customizer, null,
React.createElement(ConcatStyles, { styles: componentStyles })));
var concatStyleRoot = rtl.getByTestId('concat-styles');
var rootStyles = JSON.parse(concatStyleRoot.getAttribute('data-style'));
expect(Object.keys(concatStyleRoot.style)).toEqual(expect.arrayContaining(['root', '__shadowConfig__']));
expect(rootStyles).toEqual(componentStyles.root);
rtl.rerender(React.createElement(Customizer_1.Customizer, null,
React.createElement(ConcatStyles, { styles: componentStyles })));
var updatedConcatStyleRoot = rtl.getByTestId('concat-styles');
var updatedRootStyles = JSON.parse(updatedConcatStyleRoot.getAttribute('data-style'));
expect(updatedRootStyles).toStrictEqual(rootStyles);
expect(updatedRootStyles).toEqual(rootStyles);
});
it('should update styles if component styles changed', function () {
var globalRootStyles = { color: 'red', background: 'red' };
var componentRootStyles = { color: 'blue' };
var componentStyles = { root: componentRootStyles };
Customizations_1.Customizations.applySettings({ styles: { root: globalRootStyles } });
var rtl = (0, react_1.render)(React.createElement(Customizer_1.Customizer, null,
React.createElement(ConcatStyles, { styles: componentStyles })));
var concatStyleRoot = rtl.getByTestId('concat-styles');
var rootStyles = JSON.parse(concatStyleRoot.getAttribute('data-style'));
expect(Object.keys(concatStyleRoot.style)).toEqual(expect.arrayContaining(['root', '__shadowConfig__']));
expect(rootStyles).toEqual([globalRootStyles, componentRootStyles]);
var newComponentRootStyles = { color: 'red' };
var newComponentStyles = { root: newComponentRootStyles };
rtl.rerender(React.createElement(Customizer_1.Customizer, null,
React.createElement(ConcatStyles, { styles: newComponentStyles })));
var updatedConcatStyleRoot = rtl.getByTestId('concat-styles');
var updatedRootStyles = JSON.parse(updatedConcatStyleRoot.getAttribute('data-style'));
expect(Object.keys(updatedConcatStyleRoot.style)).toEqual(expect.arrayContaining(['root', '__shadowConfig__']));
expect(updatedRootStyles).toEqual([globalRootStyles, newComponentRootStyles]);
});
});
});
//# sourceMappingURL=customizable.test.js.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1,10 @@
import type { ICustomizerProps } from './Customizer.types';
import type { ICustomizerContext } from './CustomizerContext';
/**
* Merge props and customizations giving priority to props over context.
* NOTE: This function will always perform multiple merge operations. Use with caution.
* @param props - New settings to merge in.
* @param parentContext - Context containing current settings.
* @returns Merged customizations.
*/
export declare function mergeCustomizations(props: ICustomizerProps, parentContext: ICustomizerContext): ICustomizerContext;
@@ -0,0 +1,23 @@
define(["require", "exports", "./mergeSettings"], function (require, exports, mergeSettings_1) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.mergeCustomizations = mergeCustomizations;
/**
* Merge props and customizations giving priority to props over context.
* NOTE: This function will always perform multiple merge operations. Use with caution.
* @param props - New settings to merge in.
* @param parentContext - Context containing current settings.
* @returns Merged customizations.
*/
function mergeCustomizations(props, parentContext) {
var _a = (parentContext || {}).customizations, customizations = _a === void 0 ? { settings: {}, scopedSettings: {} } : _a;
return {
customizations: {
settings: (0, mergeSettings_1.mergeSettings)(customizations.settings, props.settings),
scopedSettings: (0, mergeSettings_1.mergeScopedSettings)(customizations.scopedSettings, props.scopedSettings),
inCustomizerContext: true,
},
};
}
});
//# sourceMappingURL=mergeCustomizations.js.map
@@ -0,0 +1 @@
{"version":3,"file":"mergeCustomizations.js","sourceRoot":"../src/","sources":["customizations/mergeCustomizations.ts"],"names":[],"mappings":";;;IAWA,kDAUC;IAjBD;;;;;;OAMG;IACH,SAAgB,mBAAmB,CAAC,KAAuB,EAAE,aAAiC;QACpF,IAAA,KAA0D,CAAA,aAAa,IAAI,EAAE,CAAA,eAAxB,EAArD,cAAc,mBAAG,EAAE,QAAQ,EAAE,EAAE,EAAE,cAAc,EAAE,EAAE,EAAE,KAAA,CAAyB;QAEtF,OAAO;YACL,cAAc,EAAE;gBACd,QAAQ,EAAE,IAAA,6BAAa,EAAC,cAAc,CAAC,QAAQ,EAAE,KAAK,CAAC,QAAQ,CAAC;gBAChE,cAAc,EAAE,IAAA,mCAAmB,EAAC,cAAc,CAAC,cAAc,EAAE,KAAK,CAAC,cAAc,CAAC;gBACxF,mBAAmB,EAAE,IAAI;aAC1B;SACF,CAAC;IACJ,CAAC","sourcesContent":["import { mergeSettings, mergeScopedSettings } from './mergeSettings';\nimport type { ICustomizerProps } from './Customizer.types';\nimport type { ICustomizerContext } from './CustomizerContext';\n\n/**\n * Merge props and customizations giving priority to props over context.\n * NOTE: This function will always perform multiple merge operations. Use with caution.\n * @param props - New settings to merge in.\n * @param parentContext - Context containing current settings.\n * @returns Merged customizations.\n */\nexport function mergeCustomizations(props: ICustomizerProps, parentContext: ICustomizerContext): ICustomizerContext {\n const { customizations = { settings: {}, scopedSettings: {} } } = parentContext || {};\n\n return {\n customizations: {\n settings: mergeSettings(customizations.settings, props.settings),\n scopedSettings: mergeScopedSettings(customizations.scopedSettings, props.scopedSettings),\n inCustomizerContext: true,\n },\n };\n}\n"]}
@@ -0,0 +1,10 @@
import type { ISettings, ISettingsFunction } from './Customizations';
/**
* Merge new and old settings, giving priority to new settings.
* New settings is optional in which case oldSettings is returned as-is.
* @param oldSettings - Old settings to fall back to.
* @param newSettings - New settings that will be merged over oldSettings.
* @returns Merged settings.
*/
export declare function mergeSettings(oldSettings?: ISettings, newSettings?: ISettings | ISettingsFunction): ISettings;
export declare function mergeScopedSettings(oldSettings?: ISettings, newSettings?: ISettings | ISettingsFunction): ISettings;
@@ -0,0 +1,42 @@
define(["require", "exports", "tslib"], function (require, exports, tslib_1) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.mergeSettings = mergeSettings;
exports.mergeScopedSettings = mergeScopedSettings;
/**
* Merge new and old settings, giving priority to new settings.
* New settings is optional in which case oldSettings is returned as-is.
* @param oldSettings - Old settings to fall back to.
* @param newSettings - New settings that will be merged over oldSettings.
* @returns Merged settings.
*/
function mergeSettings(oldSettings, newSettings) {
if (oldSettings === void 0) { oldSettings = {}; }
var mergeSettingsWith = _isSettingsFunction(newSettings) ? newSettings : _settingsMergeWith(newSettings);
return mergeSettingsWith(oldSettings);
}
function mergeScopedSettings(oldSettings, newSettings) {
if (oldSettings === void 0) { oldSettings = {}; }
var mergeSettingsWith = _isSettingsFunction(newSettings) ? newSettings : _scopedSettingsMergeWith(newSettings);
return mergeSettingsWith(oldSettings);
}
function _isSettingsFunction(settings) {
return typeof settings === 'function';
}
function _settingsMergeWith(newSettings) {
return function (settings) { return (newSettings ? tslib_1.__assign(tslib_1.__assign({}, settings), newSettings) : settings); };
}
function _scopedSettingsMergeWith(scopedSettingsFromProps) {
if (scopedSettingsFromProps === void 0) { scopedSettingsFromProps = {}; }
return function (oldScopedSettings) {
var newScopedSettings = tslib_1.__assign({}, oldScopedSettings);
for (var scopeName in scopedSettingsFromProps) {
if (scopedSettingsFromProps.hasOwnProperty(scopeName)) {
newScopedSettings[scopeName] = tslib_1.__assign(tslib_1.__assign({}, oldScopedSettings[scopeName]), scopedSettingsFromProps[scopeName]);
}
}
return newScopedSettings;
};
}
});
//# sourceMappingURL=mergeSettings.js.map
@@ -0,0 +1 @@
{"version":3,"file":"mergeSettings.js","sourceRoot":"../src/","sources":["customizations/mergeSettings.ts"],"names":[],"mappings":";;;IASA,sCAIC;IAED,kDAOC;IApBD;;;;;;OAMG;IACH,SAAgB,aAAa,CAAC,WAA2B,EAAE,WAA2C;QAAxE,4BAAA,EAAA,gBAA2B;QACvD,IAAM,iBAAiB,GAAG,mBAAmB,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,kBAAkB,CAAC,WAAW,CAAC,CAAC;QAE3G,OAAO,iBAAiB,CAAC,WAAW,CAAC,CAAC;IACxC,CAAC;IAED,SAAgB,mBAAmB,CACjC,WAA2B,EAC3B,WAA2C;QAD3C,4BAAA,EAAA,gBAA2B;QAG3B,IAAM,iBAAiB,GAAG,mBAAmB,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,wBAAwB,CAAC,WAAW,CAAC,CAAC;QAEjH,OAAO,iBAAiB,CAAC,WAAW,CAAC,CAAC;IACxC,CAAC;IAED,SAAS,mBAAmB,CAAC,QAAwC;QACnE,OAAO,OAAO,QAAQ,KAAK,UAAU,CAAC;IACxC,CAAC;IAED,SAAS,kBAAkB,CAAC,WAAoB;QAC9C,OAAO,UAAC,QAAmB,IAAK,OAAA,CAAC,WAAW,CAAC,CAAC,uCAAM,QAAQ,GAAK,WAAW,EAAG,CAAC,CAAC,QAAQ,CAAC,EAA1D,CAA0D,CAAC;IAC7F,CAAC;IAED,SAAS,wBAAwB,CAAC,uBAAuC;QAAvC,wCAAA,EAAA,4BAAuC;QACvE,OAAO,UAAC,iBAA4B;YAClC,IAAM,iBAAiB,wBAAmB,iBAAiB,CAAE,CAAC;YAE9D,KAAK,IAAI,SAAS,IAAI,uBAAuB,EAAE,CAAC;gBAC9C,IAAI,uBAAuB,CAAC,cAAc,CAAC,SAAS,CAAC,EAAE,CAAC;oBACtD,iBAAiB,CAAC,SAAS,CAAC,yCAAQ,iBAAiB,CAAC,SAAS,CAAC,GAAK,uBAAuB,CAAC,SAAS,CAAC,CAAE,CAAC;gBAC5G,CAAC;YACH,CAAC;YAED,OAAO,iBAAiB,CAAC;QAC3B,CAAC,CAAC;IACJ,CAAC","sourcesContent":["import type { ISettings, ISettingsFunction } from './Customizations';\n\n/**\n * Merge new and old settings, giving priority to new settings.\n * New settings is optional in which case oldSettings is returned as-is.\n * @param oldSettings - Old settings to fall back to.\n * @param newSettings - New settings that will be merged over oldSettings.\n * @returns Merged settings.\n */\nexport function mergeSettings(oldSettings: ISettings = {}, newSettings?: ISettings | ISettingsFunction): ISettings {\n const mergeSettingsWith = _isSettingsFunction(newSettings) ? newSettings : _settingsMergeWith(newSettings);\n\n return mergeSettingsWith(oldSettings);\n}\n\nexport function mergeScopedSettings(\n oldSettings: ISettings = {},\n newSettings?: ISettings | ISettingsFunction,\n): ISettings {\n const mergeSettingsWith = _isSettingsFunction(newSettings) ? newSettings : _scopedSettingsMergeWith(newSettings);\n\n return mergeSettingsWith(oldSettings);\n}\n\nfunction _isSettingsFunction(settings?: ISettings | ISettingsFunction): settings is ISettingsFunction {\n return typeof settings === 'function';\n}\n\nfunction _settingsMergeWith(newSettings?: object): (settings: ISettings) => ISettings {\n return (settings: ISettings) => (newSettings ? { ...settings, ...newSettings } : settings);\n}\n\nfunction _scopedSettingsMergeWith(scopedSettingsFromProps: ISettings = {}): (scopedSettings: ISettings) => ISettings {\n return (oldScopedSettings: ISettings): ISettings => {\n const newScopedSettings: ISettings = { ...oldScopedSettings };\n\n for (let scopeName in scopedSettingsFromProps) {\n if (scopedSettingsFromProps.hasOwnProperty(scopeName)) {\n newScopedSettings[scopeName] = { ...oldScopedSettings[scopeName], ...scopedSettingsFromProps[scopeName] };\n }\n }\n\n return newScopedSettings;\n };\n}\n"]}
@@ -0,0 +1,6 @@
import type { ISettings } from './Customizations';
/**
* Hook to get Customizations settings from Customizations singleton or CustomizerContext.
* It will trigger component state update on settings change observed.
*/
export declare function useCustomizationSettings(properties: string[], scopeName?: string): ISettings;
@@ -0,0 +1,31 @@
define(["require", "exports", "react", "./Customizations", "./CustomizerContext"], function (require, exports, React, Customizations_1, CustomizerContext_1) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.useCustomizationSettings = useCustomizationSettings;
/**
* Hook to get Customizations settings from Customizations singleton or CustomizerContext.
* It will trigger component state update on settings change observed.
*/
function useCustomizationSettings(properties, scopeName) {
var forceUpdate = useForceUpdate();
var customizations = React.useContext(CustomizerContext_1.CustomizerContext).customizations;
var inCustomizerContext = customizations.inCustomizerContext;
React.useEffect(function () {
if (!inCustomizerContext) {
Customizations_1.Customizations.observe(forceUpdate);
}
return function () {
if (!inCustomizerContext) {
Customizations_1.Customizations.unobserve(forceUpdate);
}
};
// eslint-disable-next-line react-hooks/exhaustive-deps -- exclude forceUpdate
}, [inCustomizerContext]);
return Customizations_1.Customizations.getSettings(properties, scopeName, customizations);
}
function useForceUpdate() {
var _a = React.useState(0), setValue = _a[1];
return function () { return setValue(function (value) { return ++value; }); };
}
});
//# sourceMappingURL=useCustomizationSettings.js.map
@@ -0,0 +1 @@
{"version":3,"file":"useCustomizationSettings.js","sourceRoot":"../src/","sources":["customizations/useCustomizationSettings.ts"],"names":[],"mappings":";;;IASA,4DAiBC;IArBD;;;OAGG;IACH,SAAgB,wBAAwB,CAAC,UAAoB,EAAE,SAAkB;QAC/E,IAAM,WAAW,GAAG,cAAc,EAAE,CAAC;QAC7B,IAAA,cAAc,GAAK,KAAK,CAAC,UAAU,CAAC,qCAAiB,CAAC,eAAxC,CAAyC;QACvD,IAAA,mBAAmB,GAAK,cAAc,oBAAnB,CAAoB;QAC/C,KAAK,CAAC,SAAS,CAAC;YACd,IAAI,CAAC,mBAAmB,EAAE,CAAC;gBACzB,+BAAc,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC;YACtC,CAAC;YACD,OAAO;gBACL,IAAI,CAAC,mBAAmB,EAAE,CAAC;oBACzB,+BAAc,CAAC,SAAS,CAAC,WAAW,CAAC,CAAC;gBACxC,CAAC;YACH,CAAC,CAAC;YACF,8EAA8E;QAChF,CAAC,EAAE,CAAC,mBAAmB,CAAC,CAAC,CAAC;QAE1B,OAAO,+BAAc,CAAC,WAAW,CAAC,UAAU,EAAE,SAAS,EAAE,cAAc,CAAC,CAAC;IAC3E,CAAC;IAED,SAAS,cAAc;QACf,IAAA,KAAe,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,EAA7B,QAAQ,QAAqB,CAAC;QACvC,OAAO,cAAM,OAAA,QAAQ,CAAC,UAAA,KAAK,IAAI,OAAA,EAAE,KAAK,EAAP,CAAO,CAAC,EAA1B,CAA0B,CAAC;IAC1C,CAAC","sourcesContent":["import * as React from 'react';\nimport { Customizations } from './Customizations';\nimport { CustomizerContext } from './CustomizerContext';\nimport type { ISettings } from './Customizations';\n\n/**\n * Hook to get Customizations settings from Customizations singleton or CustomizerContext.\n * It will trigger component state update on settings change observed.\n */\nexport function useCustomizationSettings(properties: string[], scopeName?: string): ISettings {\n const forceUpdate = useForceUpdate();\n const { customizations } = React.useContext(CustomizerContext);\n const { inCustomizerContext } = customizations;\n React.useEffect(() => {\n if (!inCustomizerContext) {\n Customizations.observe(forceUpdate);\n }\n return () => {\n if (!inCustomizerContext) {\n Customizations.unobserve(forceUpdate);\n }\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps -- exclude forceUpdate\n }, [inCustomizerContext]);\n\n return Customizations.getSettings(properties, scopeName, customizations);\n}\n\nfunction useForceUpdate() {\n const [, setValue] = React.useState(0);\n return () => setValue(value => ++value);\n}\n"]}
@@ -0,0 +1 @@
export {};
@@ -0,0 +1,101 @@
define(["require", "exports", "react", "@testing-library/react", "./Customizations", "./CustomizerContext", "./useCustomizationSettings"], function (require, exports, React, react_1, Customizations_1, CustomizerContext_1, useCustomizationSettings_1) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
describe('useCustomizatioSettings', function () {
var component;
afterEach(function () {
(0, react_1.act)(function () {
component === null || component === void 0 ? void 0 : component.unmount();
component = undefined;
});
Customizations_1.Customizations.reset();
});
it('get settings from Customizations', function () {
Customizations_1.Customizations.applySettings({ a: 'a' });
var settingsStates = [];
var TestComponent = function () {
var settings = (0, useCustomizationSettings_1.useCustomizationSettings)(['a']);
settingsStates.push(settings);
return null;
};
(0, react_1.act)(function () {
component = (0, react_1.render)(React.createElement(TestComponent, null));
});
expect(settingsStates.length).toBe(1);
expect(settingsStates[0]).toEqual({ a: 'a' });
});
it('get settings from Customizations when settings have changed', function () {
Customizations_1.Customizations.applySettings({ a: 'a' });
var settingsStates = [];
var TestComponent = function () {
var settings = (0, useCustomizationSettings_1.useCustomizationSettings)(['a']);
settingsStates.push(settings);
return null;
};
(0, react_1.act)(function () {
component = (0, react_1.render)(React.createElement(TestComponent, null));
});
(0, react_1.act)(function () {
Customizations_1.Customizations.applySettings({ a: 'aa' });
});
expect(settingsStates.length).toBe(2);
expect(settingsStates[0]).toEqual({ a: 'a' });
expect(settingsStates[1]).toEqual({ a: 'aa' });
});
it('get settings from Customizations that are not applied', function () {
var settingsStates = [];
var TestComponent = function () {
var settings = (0, useCustomizationSettings_1.useCustomizationSettings)(['a']);
settingsStates.push(settings);
return null;
};
(0, react_1.act)(function () {
component = (0, react_1.render)(React.createElement(TestComponent, null));
});
expect(settingsStates.length).toBe(1);
expect(settingsStates[0]).toEqual({ a: undefined });
});
it('get settings from CustomizerContext', function () {
var settingsStates = [];
var TestComponent = function () {
var settings = (0, useCustomizationSettings_1.useCustomizationSettings)(['theme']);
settingsStates.push(settings);
return null;
};
var newContext = { customizations: { settings: { theme: { color: 'red' } }, scopedSettings: {} } };
(0, react_1.act)(function () {
component = (0, react_1.render)(React.createElement(CustomizerContext_1.CustomizerContext.Provider, { value: newContext },
React.createElement(TestComponent, null)));
});
expect(settingsStates.length).toBe(1);
expect(settingsStates[0]).toEqual({ theme: { color: 'red' } });
var updatedContext = { customizations: { settings: { theme: { color: 'green' } }, scopedSettings: {} } };
(0, react_1.act)(function () {
component.rerender(React.createElement(CustomizerContext_1.CustomizerContext.Provider, { value: updatedContext },
React.createElement(TestComponent, null)));
});
expect(settingsStates.length).toBe(2);
expect(settingsStates[1]).toEqual({ theme: { color: 'green' } });
});
it('does not re-render if global settings update but within context', function () {
Customizations_1.Customizations.applySettings({ a: 'a' });
var settingsStates = [];
var TestComponent = function () {
var settings = (0, useCustomizationSettings_1.useCustomizationSettings)(['a']);
settingsStates.push(settings);
return null;
};
var newContext = { customizations: { settings: { a: 'aa' }, scopedSettings: {}, inCustomizerContext: true } };
(0, react_1.act)(function () {
component = (0, react_1.render)(React.createElement(CustomizerContext_1.CustomizerContext.Provider, { value: newContext },
React.createElement(TestComponent, null)));
});
(0, react_1.act)(function () {
Customizations_1.Customizations.applySettings({ a: 'aaa' });
});
expect(settingsStates.length).toBe(1);
expect(settingsStates[0]).toEqual({ a: 'aa' });
});
});
});
//# sourceMappingURL=useCustomizationSettings.test.js.map
File diff suppressed because one or more lines are too long