fontdue-js 2.19.0 → 2.19.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +10 -0
- package/dist/__tests__/collectionBundleSelection.test.d.ts +1 -0
- package/dist/__tests__/collectionBundleSelection.test.js +1634 -0
- package/dist/fontdue.css +190 -126
- package/dist/reducer.js +32 -6
- package/dist/utils.d.ts +3 -0
- package/dist/utils.js +21 -10
- package/package.json +5 -2
- package/vitest.config.ts +10 -0
package/dist/reducer.js
CHANGED
|
@@ -82,13 +82,27 @@ const collectionSkuIdWithDiscount = (state, skuId) => {
|
|
|
82
82
|
const differences = (0, _utils.collectionSkuIdsDifferences)(state, skuId);
|
|
83
83
|
const collectionId = Object.keys(differences).filter(collSkuId => differences[collSkuId] <= state.skuPrices[skuId]).reduce((smallest, key) => {
|
|
84
84
|
if (!smallest || differences[key] < differences[smallest]) return key;
|
|
85
|
+
if (differences[key] === differences[smallest]) {
|
|
86
|
+
// When differences are equal, prefer a collection that is not the
|
|
87
|
+
// SKU being selected — this ensures collection bundles are preferred
|
|
88
|
+
// over selecting the family itself at the same price
|
|
89
|
+
if (smallest === skuId && key !== skuId) return key;
|
|
90
|
+
// Among non-self candidates, prefer the one covering more unique
|
|
91
|
+
// font styles — this ensures the most comprehensive collection wins
|
|
92
|
+
if (smallest !== skuId && key !== skuId) {
|
|
93
|
+
var _state$collectionStyl, _state$collectionStyl2;
|
|
94
|
+
const smallestStyles = new Set((_state$collectionStyl = state.collectionStyleSkus[smallest]) === null || _state$collectionStyl === void 0 ? void 0 : _state$collectionStyl.fontStyleSkuIds).size;
|
|
95
|
+
const keyStyles = new Set((_state$collectionStyl2 = state.collectionStyleSkus[key]) === null || _state$collectionStyl2 === void 0 ? void 0 : _state$collectionStyl2.fontStyleSkuIds).size;
|
|
96
|
+
if (keyStyles > smallestStyles) return key;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
85
99
|
return smallest;
|
|
86
100
|
}, null);
|
|
87
101
|
if (collectionId) return [collectionId, differences[collectionId]];
|
|
88
102
|
return [null, null];
|
|
89
103
|
};
|
|
90
104
|
exports.collectionSkuIdWithDiscount = collectionSkuIdWithDiscount;
|
|
91
|
-
const selectedCollectionSkuIdContainingSkuId = (state, skuId) => Object.keys(state.selectedSkuIds).filter(id => state.selectedSkuIds[id] === true).find(selectedSkuId => (0, _utils.
|
|
105
|
+
const selectedCollectionSkuIdContainingSkuId = (state, skuId) => Object.keys(state.selectedSkuIds).filter(id => state.selectedSkuIds[id] === true).find(selectedSkuId => (0, _utils.collTransitivelyContainsSkuId)(state.collectionStyleSkus, selectedSkuId, skuId));
|
|
92
106
|
const setKeysFalse = (acc, key) => {
|
|
93
107
|
acc[key] = false;
|
|
94
108
|
return acc;
|
|
@@ -100,12 +114,24 @@ const unselectedMembersSkuIds = (state, action) => {
|
|
|
100
114
|
selected
|
|
101
115
|
} = action;
|
|
102
116
|
if (!selected) return {}; // only do this in case we are selecting something
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
117
|
+
|
|
118
|
+
// Recursively collect all descendant SKU IDs so that transitive
|
|
119
|
+
// selections (e.g., collection bundle → family → bundle) are all
|
|
120
|
+
// marked as false
|
|
121
|
+
const result = {};
|
|
122
|
+
const collectMembers = collSkuId => {
|
|
123
|
+
const members = state.collectionStyleSkus[collSkuId];
|
|
124
|
+
if (!members) return;
|
|
125
|
+
for (const id of members.fontStyleSkuIds) {
|
|
126
|
+
result[id] = false;
|
|
127
|
+
}
|
|
128
|
+
for (const id of members.childrenSkuIds) {
|
|
129
|
+
result[id] = false;
|
|
130
|
+
collectMembers(id);
|
|
131
|
+
}
|
|
108
132
|
};
|
|
133
|
+
collectMembers(skuId);
|
|
134
|
+
return result;
|
|
109
135
|
};
|
|
110
136
|
const selectSkuId = (state, action) => {
|
|
111
137
|
if (action.type !== 'SELECT_SKU_ID') return state;
|
package/dist/utils.d.ts
CHANGED
|
@@ -4,6 +4,9 @@ export declare const pluralize: (count: number, singular: string, plural?: strin
|
|
|
4
4
|
export declare function notEmpty<TValue>(value: TValue | null | undefined): value is TValue;
|
|
5
5
|
export declare function kebabToCamel(str: string): string;
|
|
6
6
|
export declare const collContainsSkuId: (coll: CollectionStyleSkus | undefined, skuIdOrStyleId: string) => boolean;
|
|
7
|
+
export declare const collTransitivelyContainsSkuId: (collectionStyleSkus: {
|
|
8
|
+
[key: string]: CollectionStyleSkus | undefined;
|
|
9
|
+
}, collSkuId: string, skuIdOrStyleId: string) => boolean;
|
|
7
10
|
interface CollectionSkuIdsDifferences {
|
|
8
11
|
[collectionSkuId: string]: number;
|
|
9
12
|
}
|
package/dist/utils.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
Object.defineProperty(exports, "__esModule", {
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
|
-
exports.compareVariableSettings = exports.collectionSkuIdsDifferences = exports.collContainsSkuId = void 0;
|
|
6
|
+
exports.compareVariableSettings = exports.collectionSkuIdsDifferences = exports.collTransitivelyContainsSkuId = exports.collContainsSkuId = void 0;
|
|
7
7
|
exports.getFeatureGlyphs = getFeatureGlyphs;
|
|
8
8
|
exports.isSelected = exports.inspect = void 0;
|
|
9
9
|
exports.kebabToCamel = kebabToCamel;
|
|
@@ -29,12 +29,25 @@ const collContainsSkuId = (coll, skuIdOrStyleId) => {
|
|
|
29
29
|
if (!coll) return false;
|
|
30
30
|
return coll.childrenSkuIds.indexOf(skuIdOrStyleId) >= 0 || coll.fontStyleSkuIds.indexOf(skuIdOrStyleId) >= 0 || coll.fontStyleIds.indexOf(skuIdOrStyleId) >= 0;
|
|
31
31
|
};
|
|
32
|
+
|
|
33
|
+
// Transitive containment check — walks the collection hierarchy to find
|
|
34
|
+
// if a SKU is contained at any depth (e.g., collection bundle → family → bundle)
|
|
32
35
|
exports.collContainsSkuId = collContainsSkuId;
|
|
36
|
+
const collTransitivelyContainsSkuId = (collectionStyleSkus, collSkuId, skuIdOrStyleId) => {
|
|
37
|
+
const coll = collectionStyleSkus[collSkuId];
|
|
38
|
+
if (!coll) return false;
|
|
39
|
+
if (collContainsSkuId(coll, skuIdOrStyleId)) return true;
|
|
40
|
+
return coll.childrenSkuIds.some(childSkuId => collTransitivelyContainsSkuId(collectionStyleSkus, childSkuId, skuIdOrStyleId));
|
|
41
|
+
};
|
|
42
|
+
exports.collTransitivelyContainsSkuId = collTransitivelyContainsSkuId;
|
|
33
43
|
const isSelected = skuIdOrStyleId => state => {
|
|
34
44
|
if (!skuIdOrStyleId) return false;
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
45
|
+
if (state.selectedSkuIds[skuIdOrStyleId] === true) return true;
|
|
46
|
+
// get the collections that contain this skuId as a fontStyle or child
|
|
47
|
+
const parentCollectionSkuIds = Object.keys(state.collectionStyleSkus).filter(collSkuId => collContainsSkuId(state.collectionStyleSkus[collSkuId], skuIdOrStyleId));
|
|
48
|
+
// Recursively check ancestors — handles transitive selection
|
|
49
|
+
// (e.g., collection bundle → family → bundle)
|
|
50
|
+
return parentCollectionSkuIds.some(parentSkuId => isSelected(parentSkuId)(state));
|
|
38
51
|
};
|
|
39
52
|
|
|
40
53
|
// find any collection ancestors that contain this skuId,
|
|
@@ -42,13 +55,11 @@ const isSelected = skuIdOrStyleId => state => {
|
|
|
42
55
|
// and return the delta between the collection price and the sum.
|
|
43
56
|
exports.isSelected = isSelected;
|
|
44
57
|
const collectionSkuIdsDifferences = (state, skuId) => Object.keys(state.collectionStyleSkus).filter(collectionSkuId => {
|
|
45
|
-
|
|
46
|
-
return skuId === collectionSkuId || collContainsSkuId(coll, skuId);
|
|
58
|
+
return skuId === collectionSkuId || collTransitivelyContainsSkuId(state.collectionStyleSkus, collectionSkuId, skuId);
|
|
47
59
|
}).reduce((acc, collectionSkuId) => {
|
|
48
|
-
const
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
acc[collectionSkuId] = state.skuPrices[collectionSkuId] - sumOfSelectedSkuIdsInColl;
|
|
60
|
+
const selectedSkuIdsInColl = Object.keys(state.selectedSkuIds).filter(selectedSkuId => state.selectedSkuIds[selectedSkuId] === true && collTransitivelyContainsSkuId(state.collectionStyleSkus, collectionSkuId, selectedSkuId));
|
|
61
|
+
const sumOfSelectedSkuIdsInColl = selectedSkuIdsInColl.map(selectedSkuId => state.skuPrices[selectedSkuId] ?? 0).reduce((acc, price) => acc + price, 0);
|
|
62
|
+
acc[collectionSkuId] = (state.skuPrices[collectionSkuId] ?? 0) - sumOfSelectedSkuIdsInColl;
|
|
52
63
|
return acc;
|
|
53
64
|
}, {});
|
|
54
65
|
exports.collectionSkuIdsDifferences = collectionSkuIdsDifferences;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fontdue-js",
|
|
3
|
-
"version": "2.19.
|
|
3
|
+
"version": "2.19.2",
|
|
4
4
|
"scripts": {
|
|
5
5
|
"build": "npm run relay && run-p build-js build-css build-ts",
|
|
6
6
|
"build-js": "babel src --out-dir dist --extensions .ts,.tsx,.js,.jsx",
|
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
"build-css": "sass --load-path=node_modules --no-source-map src/index.scss dist/fontdue.css",
|
|
9
9
|
"relay": "relay-compiler",
|
|
10
10
|
"update-unicode-data": "node src/scripts/updateUnicodeData.js",
|
|
11
|
+
"test": "vitest run",
|
|
12
|
+
"test:watch": "vitest",
|
|
11
13
|
"prepublishOnly": "npm run build"
|
|
12
14
|
},
|
|
13
15
|
"author": "Fontdue",
|
|
@@ -60,7 +62,8 @@
|
|
|
60
62
|
"react-dom": "^19.0.0",
|
|
61
63
|
"relay-compiler": "^18.0.0",
|
|
62
64
|
"sass": "^1.62",
|
|
63
|
-
"typescript": "^5.9"
|
|
65
|
+
"typescript": "^5.9",
|
|
66
|
+
"vitest": "^4.1.1"
|
|
64
67
|
},
|
|
65
68
|
"relay": {
|
|
66
69
|
"src": "./src",
|