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 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Customizations = void 0;
var tslib_1 = require("tslib");
var GlobalSettings_1 = require("../GlobalSettings");
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,39 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var Customizations_1 = require("./Customizations");
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":";;AAAA,mDAAkD;AAElD,QAAQ,CAAC,gBAAgB,EAAE;IACzB,UAAU,CAAC;QACT,+BAAc,CAAC,KAAK,EAAE,CAAC;IACzB,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,6BAA6B,EAAE;QAChC,+BAAc,CAAC,aAAa,CAAC,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,CAAC;QAEjD,MAAM,CAAC,+BAAc,CAAC,WAAW,CAAC,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC;YACrD,CAAC,EAAE,GAAG;YACN,CAAC,EAAE,GAAG;SACP,CAAC,CAAC;QAEH,MAAM,CAAC,+BAAc,CAAC,WAAW,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC;YAChD,CAAC,EAAE,GAAG;SACP,CAAC,CAAC;QAEH,MAAM,CAAC,+BAAc,CAAC,WAAW,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;IACxD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,6BAA6B,EAAE;QAChC,+BAAc,CAAC,mBAAmB,CAAC,KAAK,EAAE,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,CAAC;QAE9D,MAAM,CAAC,+BAAc,CAAC,WAAW,CAAC,CAAC,GAAG,EAAE,GAAG,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC;YAC5D,CAAC,EAAE,GAAG;YACN,CAAC,EAAE,GAAG;SACP,CAAC,CAAC;QAEH,MAAM,CAAC,+BAAc,CAAC,WAAW,CAAC,CAAC,GAAG,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;IAC/D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,kBAAkB,EAAE;QACrB,IAAI,OAAO,GAAG,CAAC,CAAC;QAChB,IAAI,UAAU,GAAG,cAAM,OAAA,OAAO,EAAE,EAAT,CAAS,CAAC;QAEjC,+BAAc,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;QAEnC,+BAAc,CAAC,mBAAmB,CAAC,KAAK,EAAE,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,CAAC;QAC9D,+BAAc,CAAC,aAAa,CAAC,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,CAAC;QAEjD,MAAM,CAAC,OAAO,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;QAC3B,+BAAc,CAAC,SAAS,CAAC,UAAU,CAAC,CAAC;QAErC,+BAAc,CAAC,aAAa,CAAC,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,CAAC;QACjD,MAAM,CAAC,OAAO,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;IAC7B,CAAC,CAAC,CAAC;AACL,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;
}
@@ -0,0 +1,53 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Customizer = void 0;
var tslib_1 = require("tslib");
var React = require("react");
var Customizations_1 = require("./Customizations");
var CustomizerContext_1 = require("./CustomizerContext");
var mergeCustomizations_1 = require("./mergeCustomizations");
/**
* 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":";;;;AAAA,6BAA+B;AAC/B,mDAAkD;AAClD,yDAAwD;AACxD,6DAA4D;AAI5D;;;;;;;;;;;;;;;;GAgBG;AACH;IAAgC,sCAAiC;IAAjE;;QA0BU,4BAAsB,GAAG,cAAM,OAAA,KAAI,CAAC,WAAW,EAAE,EAAlB,CAAkB,CAAC;;IAC5D,CAAC;IA1BQ,sCAAiB,GAAxB;QACE,+BAAc,CAAC,OAAO,CAAC,IAAI,CAAC,sBAAsB,CAAC,CAAC;IACtD,CAAC;IAEM,yCAAoB,GAA3B;QACE,+BAAc,CAAC,SAAS,CAAC,IAAI,CAAC,sBAAsB,CAAC,CAAC;IACxD,CAAC;IAEM,2BAAM,GAAb;QAAA,iBAeC;QAdS,IAAA,gBAAgB,GAAK,IAAI,CAAC,KAAK,iBAAf,CAAgB;QACxC,OAAO,CACL,oBAAC,qCAAiB,CAAC,QAAQ,QACxB,UAAC,aAAiC;YACjC,IAAI,UAAU,GAAG,IAAA,yCAAmB,EAAC,KAAI,CAAC,KAAK,EAAE,aAAa,CAAC,CAAC;YAEhE,IAAI,gBAAgB,EAAE,CAAC;gBACrB,UAAU,GAAG,gBAAgB,CAAC,UAAU,CAAC,CAAC;YAC5C,CAAC;YAED,OAAO,oBAAC,qCAAiB,CAAC,QAAQ,IAAC,KAAK,EAAE,UAAU,IAAG,KAAI,CAAC,KAAK,CAAC,QAAQ,CAA8B,CAAC;QAC3G,CAAC,CAC0B,CAC9B,CAAC;IACJ,CAAC;IAGH,iBAAC;AAAD,CAAC,AA3BD,CAAgC,KAAK,CAAC,SAAS,GA2B9C;AA3BY,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,156 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var tslib_1 = require("tslib");
/* eslint-disable @typescript-eslint/no-deprecated */
var React = require("react");
var react_1 = require("@testing-library/react");
var customizable_1 = require("./customizable");
var Customizer_1 = require("./Customizer");
var Customizations_1 = require("./Customizations");
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,3 @@
"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,12 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.CustomizerContext = void 0;
var React = require("react");
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":";;;AAAA,6BAA+B;AAOlB,QAAA,iBAAiB,GAAG,KAAK,CAAC,aAAa,CAAqB;IACvE,cAAc,EAAE;QACd,mBAAmB,EAAE,KAAK;QAC1B,QAAQ,EAAE,EAAE;QACZ,cAAc,EAAE,EAAE;KACnB;CACF,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,84 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.customizable = customizable;
var tslib_1 = require("tslib");
var React = require("react");
var Customizations_1 = require("./Customizations");
var hoistStatics_1 = require("../hoistStatics");
var CustomizerContext_1 = require("./CustomizerContext");
var merge_styles_1 = require("@fluentui/merge-styles");
var MergeStylesShadowRootConsumer_1 = require("../shadowDom/contexts/MergeStylesShadowRootConsumer");
var getWindow_1 = require("../dom/getWindow");
var react_window_provider_1 = require("@fluentui/react-window-provider");
var memoize_1 = require("../memoize");
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,48 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var tslib_1 = require("tslib");
/**
* @jest-environment node
*/
var React = require("react");
var server_1 = require("react-dom/server");
var customizable_1 = require("./customizable");
var Customizations_1 = require("./Customizations");
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":";;;AAAA;;GAEG;AACH,6BAA+B;AAC/B,2CAAwD;AACxD,+CAA8C;AAC9C,mDAAkD;AAIlD;IAAkB,+BAAuC;IAAzD;;IAIA,CAAC;IAHQ,oBAAM,GAAb;QACE,OAAO,iCAAM,IAAI,CAAC,KAAK,CAAC,KAAK,CAAO,CAAC;IACvC,CAAC;IAHG,GAAG;QADR,IAAA,2BAAY,EAAC,KAAK,EAAE,CAAC,OAAO,CAAC,CAAC;OACzB,GAAG,CAIR;IAAD,UAAC;CAAA,AAJD,CAAkB,KAAK,CAAC,SAAS,GAIhC;AAED,QAAQ,CAAC,sCAAsC,EAAE;IAC/C,UAAU,CAAC;QACT,+BAAc,CAAC,KAAK,EAAE,CAAC;IACzB,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,mCAAmC,EAAE;QACtC,+BAAc,CAAC,aAAa,CAAC,EAAE,KAAK,EAAE,YAAY,EAAE,CAAC,CAAC;QACtD,MAAM,CAAC,IAAA,6BAAoB,EAAC,oBAAC,GAAG,OAAG,CAAC,CAAC,CAAC,OAAO,CAAC,uBAAuB,CAAC,CAAC;IACzE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,mCAAmC,EAAE;QACtC,+BAAc,CAAC,aAAa,CAAC,EAAE,KAAK,EAAE,YAAY,EAAE,CAAC,CAAC;QACtD,+BAAc,CAAC,mBAAmB,CAAC,KAAK,EAAE,EAAE,KAAK,EAAE,YAAY,EAAE,CAAC,CAAC;QACnE,MAAM,CAAC,IAAA,6BAAoB,EAAC,oBAAC,GAAG,OAAG,CAAC,CAAC,CAAC,OAAO,CAAC,uBAAuB,CAAC,CAAC;IACzE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,oDAAoD,EAAE;QACvD,+BAAc,CAAC,aAAa,CAAC,EAAE,KAAK,EAAE,YAAY,EAAE,CAAC,CAAC;QACtD,+BAAc,CAAC,mBAAmB,CAAC,KAAK,EAAE,EAAE,KAAK,EAAE,YAAY,EAAE,CAAC,CAAC;QACnE,MAAM,CAAC,IAAA,6BAAoB,EAAC,oBAAC,GAAG,OAAG,CAAC,CAAC,CAAC,OAAO,CAAC,uBAAuB,CAAC,CAAC;IACzE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,4CAA4C,EAAE;QAC/C,+BAAc,CAAC,aAAa,CAAC,EAAE,KAAK,EAAE,YAAY,EAAE,CAAC,CAAC;QACtD,+BAAc,CAAC,mBAAmB,CAAC,KAAK,EAAE,EAAE,KAAK,EAAE,YAAY,EAAE,CAAC,CAAC;QACnE,MAAM,CAAC,IAAA,6BAAoB,EAAC,oBAAC,GAAG,IAAC,KAAK,EAAC,MAAM,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,iBAAiB,CAAC,CAAC;IAChF,CAAC,CAAC,CAAC;AACL,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,180 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var tslib_1 = require("tslib");
var React = require("react");
var react_1 = require("@testing-library/react");
var customizable_1 = require("./customizable");
var Customizations_1 = require("./Customizations");
var Customizer_1 = require("./Customizer");
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,22 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.mergeCustomizations = mergeCustomizations;
var mergeSettings_1 = require("./mergeSettings");
/**
* 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":";;AAWA,kDAUC;AArBD,iDAAqE;AAIrE;;;;;;GAMG;AACH,SAAgB,mBAAmB,CAAC,KAAuB,EAAE,aAAiC;IACpF,IAAA,KAA0D,CAAA,aAAa,IAAI,EAAE,CAAA,eAAxB,EAArD,cAAc,mBAAG,EAAE,QAAQ,EAAE,EAAE,EAAE,cAAc,EAAE,EAAE,EAAE,KAAA,CAAyB;IAEtF,OAAO;QACL,cAAc,EAAE;YACd,QAAQ,EAAE,IAAA,6BAAa,EAAC,cAAc,CAAC,QAAQ,EAAE,KAAK,CAAC,QAAQ,CAAC;YAChE,cAAc,EAAE,IAAA,mCAAmB,EAAC,cAAc,CAAC,cAAc,EAAE,KAAK,CAAC,cAAc,CAAC;YACxF,mBAAmB,EAAE,IAAI;SAC1B;KACF,CAAC;AACJ,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,41 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.mergeSettings = mergeSettings;
exports.mergeScopedSettings = mergeScopedSettings;
var tslib_1 = require("tslib");
/**
* 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":";;AASA,sCAIC;AAED,kDAOC;;AApBD;;;;;;GAMG;AACH,SAAgB,aAAa,CAAC,WAA2B,EAAE,WAA2C;IAAxE,4BAAA,EAAA,gBAA2B;IACvD,IAAM,iBAAiB,GAAG,mBAAmB,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,kBAAkB,CAAC,WAAW,CAAC,CAAC;IAE3G,OAAO,iBAAiB,CAAC,WAAW,CAAC,CAAC;AACxC,CAAC;AAED,SAAgB,mBAAmB,CACjC,WAA2B,EAC3B,WAA2C;IAD3C,4BAAA,EAAA,gBAA2B;IAG3B,IAAM,iBAAiB,GAAG,mBAAmB,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,wBAAwB,CAAC,WAAW,CAAC,CAAC;IAEjH,OAAO,iBAAiB,CAAC,WAAW,CAAC,CAAC;AACxC,CAAC;AAED,SAAS,mBAAmB,CAAC,QAAwC;IACnE,OAAO,OAAO,QAAQ,KAAK,UAAU,CAAC;AACxC,CAAC;AAED,SAAS,kBAAkB,CAAC,WAAoB;IAC9C,OAAO,UAAC,QAAmB,IAAK,OAAA,CAAC,WAAW,CAAC,CAAC,uCAAM,QAAQ,GAAK,WAAW,EAAG,CAAC,CAAC,QAAQ,CAAC,EAA1D,CAA0D,CAAC;AAC7F,CAAC;AAED,SAAS,wBAAwB,CAAC,uBAAuC;IAAvC,wCAAA,EAAA,4BAAuC;IACvE,OAAO,UAAC,iBAA4B;QAClC,IAAM,iBAAiB,wBAAmB,iBAAiB,CAAE,CAAC;QAE9D,KAAK,IAAI,SAAS,IAAI,uBAAuB,EAAE,CAAC;YAC9C,IAAI,uBAAuB,CAAC,cAAc,CAAC,SAAS,CAAC,EAAE,CAAC;gBACtD,iBAAiB,CAAC,SAAS,CAAC,yCAAQ,iBAAiB,CAAC,SAAS,CAAC,GAAK,uBAAuB,CAAC,SAAS,CAAC,CAAE,CAAC;YAC5G,CAAC;QACH,CAAC;QAED,OAAO,iBAAiB,CAAC;IAC3B,CAAC,CAAC;AACJ,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,32 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.useCustomizationSettings = useCustomizationSettings;
var React = require("react");
var Customizations_1 = require("./Customizations");
var CustomizerContext_1 = require("./CustomizerContext");
/**
* 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":";;AASA,4DAiBC;AA1BD,6BAA+B;AAC/B,mDAAkD;AAClD,yDAAwD;AAGxD;;;GAGG;AACH,SAAgB,wBAAwB,CAAC,UAAoB,EAAE,SAAkB;IAC/E,IAAM,WAAW,GAAG,cAAc,EAAE,CAAC;IAC7B,IAAA,cAAc,GAAK,KAAK,CAAC,UAAU,CAAC,qCAAiB,CAAC,eAAxC,CAAyC;IACvD,IAAA,mBAAmB,GAAK,cAAc,oBAAnB,CAAoB;IAC/C,KAAK,CAAC,SAAS,CAAC;QACd,IAAI,CAAC,mBAAmB,EAAE,CAAC;YACzB,+BAAc,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC;QACtC,CAAC;QACD,OAAO;YACL,IAAI,CAAC,mBAAmB,EAAE,CAAC;gBACzB,+BAAc,CAAC,SAAS,CAAC,WAAW,CAAC,CAAC;YACxC,CAAC;QACH,CAAC,CAAC;QACF,8EAA8E;IAChF,CAAC,EAAE,CAAC,mBAAmB,CAAC,CAAC,CAAC;IAE1B,OAAO,+BAAc,CAAC,WAAW,CAAC,UAAU,EAAE,SAAS,EAAE,cAAc,CAAC,CAAC;AAC3E,CAAC;AAED,SAAS,cAAc;IACf,IAAA,KAAe,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,EAA7B,QAAQ,QAAqB,CAAC;IACvC,OAAO,cAAM,OAAA,QAAQ,CAAC,UAAA,KAAK,IAAI,OAAA,EAAE,KAAK,EAAP,CAAO,CAAC,EAA1B,CAA0B,CAAC;AAC1C,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,104 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var React = require("react");
var react_1 = require("@testing-library/react");
var Customizations_1 = require("./Customizations");
var CustomizerContext_1 = require("./CustomizerContext");
var useCustomizationSettings_1 = require("./useCustomizationSettings");
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