Files
webawesome/docs/assets/scripts/util/deep.js
Lea Verou bdd25571a8 Themer first slice (#857)
* Basic scaffolding

* Generate theme & palette data.js that other JS can import

* Make it possible to include page-card without links

* WIP

* Add `appearance` to details, closes #569

Except `accent` as that's a) far less useful and b) trickier due to the icon color

* Fix broken link

* WIP

* WIP

* Icons icon

* Unify styles for interactive cards

* Prevent focusing inside theme icons

* Fixes

* Action page cards

* Panel scrollables

* scrollable

* Scroll shadows

* Add renaming UI

* UI

* Move styling of heading icons to `ui.css`

* Support permalinks & CRUD

* Make clickable cards more accessible

* Style cards a little better

* Default to styles panel if theme is selected

* Update theme-icons.css

* Custom themes should be saved under Custom

* Get theme code

* Bigger title

* Fixes

* Use theme Vue app for remixing too

* Fix preview jank and make preview script more flexible

* Make radio groups scrollable

* Add affordance to button cards

* Sticky

* `<color-select>`

* Fix theme remixing

* Improve previewing logic

* Fix preview

* Move `domChange()` to separate module

`theme-picker.js` includes side-effects, which may not always be desirable everywhere we may want to import `domChange()`

* Update preview.js

* Panel animation

* Hide Save button if no changes and not saved

* Do not show blank code when no selection has been made

* Use theme slug in filename

* Remove unused component

* Better UI for editing title (and any other text)

* Tweak UI of renaming

* Better indicate default selection

* Fix preview reverting bug

* Fill out app preview with more examples

* Remove `zoom` from theme showcase (yields unexpected/painful results Safari), improve display in wider viewports

* Pending delete

* Make styles panel cards scrollable

* Fix some of the Safari issues

* Update search.css

* Update panel.css

* Select preview UI

* Fix typo

* Frame colors setting as color contrast

* Show dark mode in color mappings

* Brand color

* Swatch styling

* Fix caret icon

* Move Starting theme to the same level as other controls

* Rename typography to Fonts

* Fix bug: Swatch select should show swatches from the selected palette

* Move capitalize to shared utils

* Add utils for handling nested objects

* Icons panel

* Update code.js

* Move utils around

* Add fit and finish to sidebar panels

* Theme card: Move icons to separate data structure

* Move data to dedicated folder since we now have a lot more of it

* Add default icon families and variants to themes

* Data

* Add `deepEntries()`

* Add Duotone

* Spruce up icons preview

* Use theme's icon family in showcase

* Font cards

* Font cards

* Add `max-inline-size` to preview container

* Remove alternate preview options

* Remove theme subtitle

* Support FA kit codes

* Remove Pro badges from theme cards

* Use panagram preview for Fonts

* Consistent heading and label capitalization

* Classes for different icons-card types

* Update data.js.njk

* Variable style on icon family cards

* Fix Sharp Duotone

* Clean up FA kit code hint

* Hide non-functional Icon Library field

* Fix theme icon heights

* icon variant -> style in theme metadata

* Fix bug with icons defaults not being shown

* More convenient theme defaults

* Fix bug with non updating URL

* Fix bug

* Fix multiplying badges

* Custom docs pages

* Add Duotone icons to Mellow theme

* Fix 404

* Remove "Create" from sidebar

* Fix bug

* Move vue components to `/assets/`, move their CSS with them

* Safari/FF compatibility

* Make panels scrollable again

* Fix extra spacing

---------

Co-authored-by: lindsaym-fa <dev@lindsaym.design>
2025-05-09 17:04:06 -04:00

181 lines
4.4 KiB
JavaScript

/**
* @typedef { string | number | Symbol } Property
* @typedef { (value: any, key: Property, parent: object, path: Property[]) => any } EachCallback
*/
export function isPlainObject(obj) {
return isObject(obj, 'Object');
}
export function isObject(obj, type) {
if (!obj || typeof obj !== 'object') {
return false;
}
let proto = Object.getPrototypeOf(obj);
return proto.constructor?.name === type;
}
export function deepMerge(target, source, options = {}) {
let {
emptyValues = [undefined],
containers = ['Object', 'EventTarget'],
isContainer = value => containers.some(type => isObject(value, type)),
} = options;
if (isContainer(target) && isContainer(source)) {
for (let key in source) {
if (key in target && isContainer(target[key]) && isContainer(source[key])) {
target[key] = deepMerge(target[key], source[key], options);
} else if (!emptyValues.includes(source[key])) {
target[key] = source[key];
}
}
return target;
}
return target ?? source;
}
/**
* Iterate over a deep array, recursively for plain objects
* @param { any } obj The object to iterate over. Can be an array or a plain object, or even a primitive value.
* @param { EachCallback } callback. value is === parent[key]
* @param { object } [parentObj] The parent object of the current value Mainly used internally to facilitate recursion.
* @param { Property } [key] The key of the current value. Mainly used internally to facilitate recursion.
* @param { Property[] } [path] Any existing path (not including the key). Mainly used internally to facilitate recursion.
*/
export function deepEach(obj, callback, parentObj, key, path = []) {
if (key !== undefined) {
let ret = callback(obj, key, parentObj, path);
if (ret !== undefined) {
if (ret === false) {
// Do not descend further
return;
}
// Overwrite value
parentObj[key] = ret;
obj = ret;
}
}
let newPath = key !== undefined ? [...path, key] : path;
if (Array.isArray(obj)) {
for (let i = 0; i < obj.length; i++) {
deepEach(obj[i], callback, obj, i, newPath);
}
} else if (isPlainObject(obj)) {
for (let key in obj) {
deepEach(obj[key], callback, obj, key, newPath);
}
}
}
/**
* Get a value from a deeply nested object
* @param {*} obj
* @param {PropertyPath} path
* @returns
*/
export function deepGet(obj, path) {
if (path.length === 0) {
return obj;
}
let ret = obj;
for (let key of path) {
if (ret === undefined) {
return undefined;
}
ret = ret[key];
}
return ret;
}
/**
* Set a value in a deep object, creating object literals as needed
* @param { * } obj
* @param { Property[] } path
* @param { any } value
*/
export function deepSet(obj, path, value) {
if (path.length === 0) {
return;
}
let key = path.pop();
let ret = path.reduce((acc, property) => {
if (acc[property] === undefined) {
acc[property] = {};
}
return acc[property];
}, obj);
ret[key] = value;
}
export function deepClone(obj) {
if (!obj) {
return obj;
}
let ret = obj;
if (Array.isArray(obj)) {
ret = obj.map(item => deepClone(item));
} else if (isPlainObject(obj)) {
ret = { ...obj };
for (let key in obj) {
ret[key] = deepClone(obj[key]);
}
}
return ret;
}
/**
* Like Object.entries, but for deeply nested objects.
* For shallow objects the output is the same as Object.entries.
* @param {*} obj
* @param { object } options
* @param { EachCallback } each - If this returns false, the entry is not added to the result and the recursion is stopped.
* @param { EachCallback } filter - If this returns false, the entry is not added to the result.
* @param { EachCallback } descend - If this returns false, recursion is stopped.
* @returns {any[][]}
*/
export function deepEntries(obj, options = {}) {
let { each, filter, descend } = options;
let entries = [];
deepEach(obj, (value, key, parent, path) => {
let ret = each?.(value, key, parent, path);
if (ret !== false) {
let included = filter?.(value, key, parent, path) ?? true;
if (included) {
entries.push([...path, key, value]);
}
let descendRet = descend?.(value, key, parent, path);
if (descendRet === false) {
return false; // Stop recursion
}
}
return ret;
});
return entries;
}