brand-intent 0.15.0 → 0.16.0
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/package.json
CHANGED
package/parser/brandParser.ts
CHANGED
|
@@ -250,6 +250,14 @@ const VALID_STRATEGIES = new Set<PaletteStrategy>(['monochrome', 'duotone', 'tri
|
|
|
250
250
|
const VALID_ADHERENCES = new Set<Adherence>(['strict', 'on-brand', 'flexible']);
|
|
251
251
|
const TONE_CLASSES = new Set<ToneClass>(['light', 'mid', 'dark']);
|
|
252
252
|
const FAMILY_ROLES = new Set<FamilyRole>(['core', 'accent', 'contrast']);
|
|
253
|
+
|
|
254
|
+
const DAYS_IN_MONTH = [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
|
|
255
|
+
function isMonthDay(value: string): boolean {
|
|
256
|
+
const m = value.match(/^(\d{2})-(\d{2})$/);
|
|
257
|
+
if (!m) return false;
|
|
258
|
+
const month = Number(m[1]), day = Number(m[2]);
|
|
259
|
+
return month >= 1 && month <= 12 && day >= 1 && day <= DAYS_IN_MONTH[month - 1];
|
|
260
|
+
}
|
|
253
261
|
const FAMILY_ORDERS = new Set<FamilyOrder>(['cycle', 'sequence']);
|
|
254
262
|
const FAMILY_BORROWS = new Set<FamilyBorrow>(['previous', 'next', 'both', 'none']);
|
|
255
263
|
const PAIR_KEYS = ['ground', 'figure', 'text'] as const;
|
|
@@ -395,7 +403,7 @@ export function parseBrandFile(content: string, fileName: string): ParsedBrand {
|
|
|
395
403
|
const adherenceOverridesRaw: Record<string, string> = {};
|
|
396
404
|
const colorRulesRaw: Record<string, string> = {};
|
|
397
405
|
let colorRulesDeclared = false;
|
|
398
|
-
type RawFamily = { name: string; line: number; colors: Array<{ role: string; value: string; line: number }>; pairs: Array<{ ground: string; figure: string; line: number }> };
|
|
406
|
+
type RawFamily = { name: string; line: number; from?: string; colors: Array<{ role: string; value: string; line: number }>; pairs: Array<{ ground: string; figure: string; line: number }> };
|
|
399
407
|
const familiesProps: Record<string, string> = {};
|
|
400
408
|
const familiesRaw: RawFamily[] = [];
|
|
401
409
|
let familiesDeclared = false;
|
|
@@ -653,8 +661,16 @@ export function parseBrandFile(content: string, fileName: string): ParsedBrand {
|
|
|
653
661
|
}
|
|
654
662
|
const colonIdx = trimmed.indexOf(':');
|
|
655
663
|
const key = colonIdx !== -1 ? trimmed.slice(0, colonIdx).trim() : trimmed;
|
|
664
|
+
if (key === 'from' && colonIdx !== -1) {
|
|
665
|
+
const value = trimmed.slice(colonIdx + 1).trim();
|
|
666
|
+
if (currentFamily.from !== undefined) throw new Error(`${fileName}:${lineNum}: family "${currentFamily.name}" declares from: twice`);
|
|
667
|
+
if (!isMonthDay(value)) throw new Error(`${fileName}:${lineNum}: family "${currentFamily.name}" from: must be "MM-DD" (e.g. 12-01), got "${value}"`);
|
|
668
|
+
inPairs = false;
|
|
669
|
+
currentFamily.from = value;
|
|
670
|
+
continue;
|
|
671
|
+
}
|
|
656
672
|
if (colonIdx === -1 || !FAMILY_ROLES.has(key as FamilyRole)) {
|
|
657
|
-
throw new Error(`${fileName}:${lineNum}: family "${currentFamily.name}": expected core / accent / contrast / pairs, got "${trimmed}"`);
|
|
673
|
+
throw new Error(`${fileName}:${lineNum}: family "${currentFamily.name}": expected from / core / accent / contrast / pairs, got "${trimmed}"`);
|
|
658
674
|
}
|
|
659
675
|
inPairs = false;
|
|
660
676
|
currentFamily.colors.push({ role: key, value: trimmed.slice(colonIdx + 1).trim(), line: lineNum });
|
|
@@ -1150,7 +1166,16 @@ export function parseBrandFile(content: string, fileName: string): ParsedBrand {
|
|
|
1150
1166
|
}
|
|
1151
1167
|
brandColors[c.key] = c.hex;
|
|
1152
1168
|
}
|
|
1153
|
-
families.push({ name: raw.name, colors, pairs: [] });
|
|
1169
|
+
families.push({ name: raw.name, ...(raw.from ? { from: raw.from } : {}), colors, pairs: [] });
|
|
1170
|
+
}
|
|
1171
|
+
const dated = familiesRaw.filter(f => f.from !== undefined);
|
|
1172
|
+
if (dated.length > 0 && dated.length < familiesRaw.length) {
|
|
1173
|
+
const missing = familiesRaw.filter(f => f.from === undefined).map(f => f.name).join(', ');
|
|
1174
|
+
throw new Error(`${fileName}: color-families: from: must be declared on every family or on none (missing: ${missing})`);
|
|
1175
|
+
}
|
|
1176
|
+
for (const f of dated) {
|
|
1177
|
+
const twin = dated.find(o => o !== f && o.from === f.from);
|
|
1178
|
+
if (twin) throw new Error(`${fileName}:${f.line}: families "${twin.name}" and "${f.name}" share from: ${f.from}`);
|
|
1154
1179
|
}
|
|
1155
1180
|
colorFamilies = { order, borrow, distance, shared, families };
|
|
1156
1181
|
if (borrow !== 'none' && reachCoversAll(families.length, { order, borrow, distance })) {
|
package/parser/colorSystem.ts
CHANGED
|
@@ -193,6 +193,23 @@ export function colorOrigin(
|
|
|
193
193
|
return null;
|
|
194
194
|
}
|
|
195
195
|
|
|
196
|
+
/**
|
|
197
|
+
* Family in season on `date`: the one with the latest `from:` on or before that
|
|
198
|
+
* month/day, wrapping to the year's last start before the first one. Uses the
|
|
199
|
+
* date's local calendar day. -1 when the families declare no dates.
|
|
200
|
+
*/
|
|
201
|
+
export function familyIndexForDate(families: Pick<ColorFamily, 'from'>[], date: Date): number {
|
|
202
|
+
if (families.length === 0 || families.some(f => !f.from)) return -1;
|
|
203
|
+
const today = `${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`;
|
|
204
|
+
let best = -1;
|
|
205
|
+
let latest = -1;
|
|
206
|
+
families.forEach((f, i) => {
|
|
207
|
+
if (f.from! <= today && (best === -1 || f.from! > families[best].from!)) best = i;
|
|
208
|
+
if (latest === -1 || f.from! > families[latest].from!) latest = i;
|
|
209
|
+
});
|
|
210
|
+
return best !== -1 ? best : latest;
|
|
211
|
+
}
|
|
212
|
+
|
|
196
213
|
/** Name of the family that declares `hex` (first match), for error messages. */
|
|
197
214
|
export function owningFamily(hex: string, families: ColorFamily[]): string | undefined {
|
|
198
215
|
return families.find(f => f.colors.some(c => c.hex === hex))?.name;
|
package/parser/index.ts
CHANGED
package/parser/types.ts
CHANGED
|
@@ -156,6 +156,8 @@ export interface FamilyPair extends PairCheck {
|
|
|
156
156
|
|
|
157
157
|
export interface ColorFamily {
|
|
158
158
|
name: string;
|
|
159
|
+
/** Start date `MM-DD` (`from:`). Declared on every family or on none. */
|
|
160
|
+
from?: string;
|
|
159
161
|
colors: FamilyColor[];
|
|
160
162
|
pairs: FamilyPair[];
|
|
161
163
|
}
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
{ "include": "#color-rules-entry" },
|
|
16
16
|
{ "include": "#color-families-property" },
|
|
17
17
|
{ "include": "#family-header" },
|
|
18
|
+
{ "include": "#family-from" },
|
|
18
19
|
{ "include": "#family-role" },
|
|
19
20
|
{ "include": "#family-pairs-keyword" },
|
|
20
21
|
{ "include": "#color-mode-enum" },
|
|
@@ -143,6 +144,15 @@
|
|
|
143
144
|
"5": { "name": "string.unquoted.brand-intent-brand" }
|
|
144
145
|
}
|
|
145
146
|
},
|
|
147
|
+
"family-from": {
|
|
148
|
+
"comment": "Family start date: from: MM-DD",
|
|
149
|
+
"match": "^(\\s{4,})(from)(:\\s*)(\\d{2}-\\d{2})\\s*$",
|
|
150
|
+
"captures": {
|
|
151
|
+
"2": { "name": "support.type.property-name.brand-intent-brand" },
|
|
152
|
+
"3": { "name": "punctuation.separator.key-value.brand-intent-brand" },
|
|
153
|
+
"4": { "name": "constant.numeric.brand-intent-brand" }
|
|
154
|
+
}
|
|
155
|
+
},
|
|
146
156
|
"family-pairs-keyword": {
|
|
147
157
|
"match": "^(\\s{4,})(pairs)\\s*$",
|
|
148
158
|
"captures": {
|