@tenphi/tasty 0.0.0-snapshot.37c3068 → 0.0.0-snapshot.61865f6
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/README.md +24 -9
- package/dist/pipeline/materialize.js +125 -85
- package/dist/pipeline/materialize.js.map +1 -1
- package/dist/styles/inset.d.ts +3 -1
- package/dist/styles/inset.js +48 -40
- package/dist/styles/inset.js.map +1 -1
- package/dist/styles/margin.d.ts +1 -5
- package/dist/styles/margin.js +48 -40
- package/dist/styles/margin.js.map +1 -1
- package/dist/styles/padding.d.ts +1 -5
- package/dist/styles/padding.js +48 -40
- package/dist/styles/padding.js.map +1 -1
- package/dist/styles/transition.js +26 -6
- package/dist/styles/transition.js.map +1 -1
- package/dist/tasty.d.ts +63 -63
- package/package.json +8 -3
- package/tasty.config.ts +14 -0
package/README.md
CHANGED
|
@@ -24,9 +24,10 @@ On top of that foundation, Tasty gives you a concise, CSS-like DSL with design t
|
|
|
24
24
|
## Why Tasty
|
|
25
25
|
|
|
26
26
|
- **Deterministic at any scale** — Exclusive selector generation eliminates the entire class of cascade/specificity bugs. Every state combination resolves to exactly one CSS rule per property. Refactor freely.
|
|
27
|
+
- **AI-friendly by design** — Style definitions are declarative, self-contained, and structurally consistent. AI tools can read, understand, and refactor even advanced state bindings as confidently as a human — because there's no hidden cascade logic or implicit ordering to second-guess.
|
|
27
28
|
- **DSL that feels like CSS** — Property names you already know (`padding`, `color`, `display`) with syntax sugar that removes boilerplate. Learn the DSL in minutes, not days.
|
|
28
29
|
- **Design-system native** — Color tokens (`#primary`), spacing units (`2x`), typography presets (`h1`, `t2`), border radius (`1r`), and recipes are first-class primitives, not afterthoughts.
|
|
29
|
-
- **
|
|
30
|
+
- **Near-complete modern CSS coverage** — Media queries, container queries, `@supports`, `:has()`, `@starting-style`, `@property`, `@keyframes`, etc. Some features that don't fit Tasty's component model (such as `@layer` and `!important`) are intentionally omitted, but real-world use cases are covered almost completely.
|
|
30
31
|
- **Runtime or zero-runtime — your call** — Use `tasty()` for dynamic React components with runtime injection, or `tastyStatic()` with the Babel plugin for zero-runtime CSS extraction. Same DSL, same tokens, same output.
|
|
31
32
|
- **Only generate what is used** — In runtime mode, Tasty injects CSS on demand for mounted components/variants, so your app avoids shipping style rules for UI states that are never rendered.
|
|
32
33
|
- **Runtime performance that holds at scale** — The runtime path is tested against enterprise-scale applications and tuned with multi-level caching, chunk-level style reuse, style garbage collection, and a dedicated injector.
|
|
@@ -133,9 +134,13 @@ Predefined states turn complex selector logic into single tokens. Use `@mobile`
|
|
|
133
134
|
|
|
134
135
|
This is the core idea that makes everything else possible.
|
|
135
136
|
|
|
136
|
-
Traditional CSS
|
|
137
|
+
Traditional CSS has two structural problems.
|
|
137
138
|
|
|
138
|
-
|
|
139
|
+
First, the **cascade** resolves conflicts by specificity and source order: when multiple selectors match, the one with the highest specificity wins, or — if specificity is equal — the last one in source order wins. That makes styles inherently fragile. Reordering imports, adding a media query, or composing components from different libraries can silently break styling.
|
|
140
|
+
|
|
141
|
+
Second, **authoring selectors that capture real-world state logic is fundamentally hard.** A single state like "dark mode" may depend on a root attribute, an OS preference, or both — each branch needing its own selector, proper negation of competing branches, and correct `@media` nesting. The example below shows the CSS you'd write by hand for just *one* property with *one* state. Scale that across dozens of properties, then add breakpoints and container queries, and the selector logic quickly becomes unmanageable.
|
|
142
|
+
|
|
143
|
+
Tasty solves both problems at once: **every state mapping compiles into mutually exclusive selectors.**
|
|
139
144
|
|
|
140
145
|
```tsx
|
|
141
146
|
const Text = tasty({
|
|
@@ -180,7 +185,9 @@ If `@dark` expands to `@root(schema=dark) | (!@root(schema) & @media(prefers-col
|
|
|
180
185
|
}
|
|
181
186
|
```
|
|
182
187
|
|
|
183
|
-
Every rule is guarded by the negation of
|
|
188
|
+
Every rule is guarded by the negation of higher-priority rules. No two rules can match at the same time. No specificity arithmetic. No source-order dependence. Components compose and extend without collisions.
|
|
189
|
+
|
|
190
|
+
By absorbing selector complexity, Tasty makes advanced CSS patterns practical again — nested container queries, multi-condition `@supports` gates, and combined root-state/media branches. You stay in pure CSS instead of relying on JavaScript workarounds, so the browser can optimize layout, painting, and transitions natively. Tasty doesn't limit CSS; it unlocks its full potential by removing the complexity that held teams back.
|
|
184
191
|
|
|
185
192
|
## Capabilities
|
|
186
193
|
|
|
@@ -412,11 +419,12 @@ If you choose the runtime approach, performance is usually a non-issue in practi
|
|
|
412
419
|
|
|
413
420
|
| Import | Description | Platform |
|
|
414
421
|
|--------|-------------|----------|
|
|
415
|
-
| `@tenphi/tasty` | Runtime style engine | Browser |
|
|
416
|
-
| `@tenphi/tasty/static` | Zero-runtime static styles | Browser |
|
|
417
|
-
| `@tenphi/tasty/
|
|
422
|
+
| `@tenphi/tasty` | Runtime style engine (`tasty`, hooks, `configure`) | Browser |
|
|
423
|
+
| `@tenphi/tasty/static` | Zero-runtime static styles (`tastyStatic`) | Browser |
|
|
424
|
+
| `@tenphi/tasty/core` | Lower-level internals (config, parser, pipeline, injector, style handlers) for tooling and advanced use | Browser / Node |
|
|
425
|
+
| `@tenphi/tasty/babel-plugin` | Babel plugin for zero-runtime CSS extraction | Node |
|
|
418
426
|
| `@tenphi/tasty/zero` | Programmatic extraction API | Node |
|
|
419
|
-
| `@tenphi/tasty/next` | Next.js integration | Node |
|
|
427
|
+
| `@tenphi/tasty/next` | Next.js integration wrapper | Node |
|
|
420
428
|
|
|
421
429
|
## Ecosystem
|
|
422
430
|
|
|
@@ -455,14 +463,21 @@ const tokens = theme.tasty(); // Ready-to-use Tasty tokens
|
|
|
455
463
|
|
|
456
464
|
Syntax highlighting for Tasty styles in TypeScript, TSX, JavaScript, and JSX. Highlights color tokens, custom units, state keys, presets, and style properties inside `tasty()`, `tastyStatic()`, and related APIs.
|
|
457
465
|
|
|
466
|
+
<p align="center">
|
|
467
|
+
<img src="assets/tasty-vscode-highlight.png" width="512" alt="Tasty VS Code syntax highlighting example">
|
|
468
|
+
</p>
|
|
469
|
+
|
|
458
470
|
### [Cube UI Kit](https://github.com/cube-js/cube-ui-kit)
|
|
459
471
|
|
|
460
472
|
Open-source React UI kit built on Tasty + React Aria. 100+ production components proving Tasty works at design-system scale. A reference implementation and a ready-to-use component library.
|
|
461
473
|
|
|
462
474
|
## Documentation
|
|
463
475
|
|
|
464
|
-
- **[Runtime API (tasty)](docs/tasty.md)** — Full runtime styling documentation: component creation, state mappings, sub-elements, variants, hooks,
|
|
476
|
+
- **[Runtime API (tasty)](docs/tasty.md)** — Full runtime styling documentation: component creation, state mappings, sub-elements, variants, hooks, and configuration
|
|
477
|
+
- **[Style Properties](docs/styles.md)** — Complete reference for all enhanced style properties: syntax, values, modifiers, and recommendations
|
|
465
478
|
- **[Zero Runtime (tastyStatic)](docs/tasty-static.md)** — Build-time static styling: Babel plugin setup, Next.js integration, and static style patterns
|
|
479
|
+
- **[Style Injector](docs/injector.md)** — Internal CSS injection engine: `inject()`, `injectGlobal()`, `injectRawCSS()`, `keyframes()`, deduplication, reference counting, cleanup, SSR support, and Shadow DOM
|
|
480
|
+
- **[Debug Utilities](docs/debug.md)** — Runtime CSS inspection via `tastyDebug`: CSS extraction, element inspection, cache metrics, chunk breakdown, and performance monitoring
|
|
466
481
|
|
|
467
482
|
## License
|
|
468
483
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Lru } from "../parser/lru.js";
|
|
2
|
+
import { not } from "./conditions.js";
|
|
2
3
|
|
|
3
4
|
//#region src/pipeline/materialize.ts
|
|
4
5
|
/**
|
|
@@ -68,27 +69,43 @@ function stateToCSS(state) {
|
|
|
68
69
|
case "root": return innerConditionToVariants(state.innerCondition, state.negated ?? false, "rootConditions");
|
|
69
70
|
case "parent": return parentConditionToVariants(state.innerCondition, state.negated ?? false, state.direct);
|
|
70
71
|
case "own": return innerConditionToVariants(state.innerCondition, state.negated ?? false, "ownConditions");
|
|
71
|
-
|
|
72
|
-
const
|
|
73
|
-
|
|
74
|
-
case "modifier":
|
|
75
|
-
variant.modifierConditions.push(modifierToParsed(state));
|
|
76
|
-
break;
|
|
77
|
-
case "pseudo":
|
|
78
|
-
variant.pseudoConditions.push(pseudoToParsed(state));
|
|
79
|
-
break;
|
|
80
|
-
case "container":
|
|
81
|
-
variant.containerConditions.push(containerToParsed(state));
|
|
82
|
-
break;
|
|
83
|
-
case "supports":
|
|
84
|
-
variant.supportsConditions.push(supportsToParsed(state));
|
|
85
|
-
break;
|
|
86
|
-
case "starting":
|
|
87
|
-
variant.startingStyle = !state.negated;
|
|
88
|
-
break;
|
|
89
|
-
}
|
|
72
|
+
case "modifier": {
|
|
73
|
+
const v = emptyVariant();
|
|
74
|
+
v.modifierConditions.push(modifierToParsed(state));
|
|
90
75
|
return {
|
|
91
|
-
variants: [
|
|
76
|
+
variants: [v],
|
|
77
|
+
isImpossible: false
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
case "pseudo": {
|
|
81
|
+
const v = emptyVariant();
|
|
82
|
+
v.pseudoConditions.push(pseudoToParsed(state));
|
|
83
|
+
return {
|
|
84
|
+
variants: [v],
|
|
85
|
+
isImpossible: false
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
case "container": {
|
|
89
|
+
const v = emptyVariant();
|
|
90
|
+
v.containerConditions.push(containerToParsed(state));
|
|
91
|
+
return {
|
|
92
|
+
variants: [v],
|
|
93
|
+
isImpossible: false
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
case "supports": {
|
|
97
|
+
const v = emptyVariant();
|
|
98
|
+
v.supportsConditions.push(supportsToParsed(state));
|
|
99
|
+
return {
|
|
100
|
+
variants: [v],
|
|
101
|
+
isImpossible: false
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
case "starting": {
|
|
105
|
+
const v = emptyVariant();
|
|
106
|
+
v.startingStyle = !state.negated;
|
|
107
|
+
return {
|
|
108
|
+
variants: [v],
|
|
92
109
|
isImpossible: false
|
|
93
110
|
};
|
|
94
111
|
}
|
|
@@ -227,35 +244,46 @@ function supportsToParsed(state) {
|
|
|
227
244
|
};
|
|
228
245
|
}
|
|
229
246
|
/**
|
|
247
|
+
* Collect all modifier and pseudo conditions from a variant as a flat array.
|
|
248
|
+
*/
|
|
249
|
+
function collectSelectorConditions(variant) {
|
|
250
|
+
return [...variant.modifierConditions, ...variant.pseudoConditions];
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
230
253
|
* Convert an inner condition tree into SelectorVariants.
|
|
231
254
|
* Each inner OR branch becomes a separate variant, preserving disjunction.
|
|
232
255
|
* Shared by @root() and @own().
|
|
233
256
|
*/
|
|
234
257
|
function innerConditionToVariants(innerCondition, negated, target) {
|
|
235
|
-
const innerCSS = conditionToCSS(innerCondition);
|
|
258
|
+
const innerCSS = conditionToCSS(negated ? not(innerCondition) : innerCondition);
|
|
236
259
|
if (innerCSS.isImpossible || innerCSS.variants.length === 0) return {
|
|
237
260
|
variants: [],
|
|
238
261
|
isImpossible: true
|
|
239
262
|
};
|
|
240
|
-
|
|
241
|
-
|
|
263
|
+
const variants = [];
|
|
264
|
+
for (const innerVariant of innerCSS.variants) {
|
|
265
|
+
const conditions = collectSelectorConditions(innerVariant);
|
|
266
|
+
if (conditions.length > 0) {
|
|
242
267
|
const v = emptyVariant();
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
268
|
+
v[target].push(...conditions);
|
|
269
|
+
variants.push(v);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
if (variants.length === 0) return {
|
|
273
|
+
variants: [emptyVariant()],
|
|
274
|
+
isImpossible: false
|
|
275
|
+
};
|
|
276
|
+
return {
|
|
277
|
+
variants,
|
|
253
278
|
isImpossible: false
|
|
254
279
|
};
|
|
255
280
|
}
|
|
256
281
|
/**
|
|
257
282
|
* Convert a @parent() inner condition into SelectorVariants with ParentGroups.
|
|
258
|
-
*
|
|
283
|
+
*
|
|
284
|
+
* Positive: each inner OR branch becomes a separate variant with one :is() group.
|
|
285
|
+
* Negated: !(A | B) = !A & !B — all branches become :not() groups collected
|
|
286
|
+
* into a single variant so they produce :not([a] *):not([b] *) on one element.
|
|
259
287
|
*/
|
|
260
288
|
function parentConditionToVariants(innerCondition, negated, direct) {
|
|
261
289
|
const innerCSS = conditionToCSS(innerCondition);
|
|
@@ -263,19 +291,44 @@ function parentConditionToVariants(innerCondition, negated, direct) {
|
|
|
263
291
|
variants: [],
|
|
264
292
|
isImpossible: true
|
|
265
293
|
};
|
|
266
|
-
|
|
267
|
-
|
|
294
|
+
if (negated) {
|
|
295
|
+
const v = emptyVariant();
|
|
296
|
+
for (const innerVariant of innerCSS.variants) {
|
|
297
|
+
const conditions = collectSelectorConditions(innerVariant);
|
|
298
|
+
if (conditions.length > 0) v.parentGroups.push({
|
|
299
|
+
conditions,
|
|
300
|
+
direct,
|
|
301
|
+
negated: true
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
if (v.parentGroups.length === 0) return {
|
|
305
|
+
variants: [emptyVariant()],
|
|
306
|
+
isImpossible: false
|
|
307
|
+
};
|
|
308
|
+
return {
|
|
309
|
+
variants: [v],
|
|
310
|
+
isImpossible: false
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
const variants = [];
|
|
314
|
+
for (const innerVariant of innerCSS.variants) {
|
|
315
|
+
const conditions = collectSelectorConditions(innerVariant);
|
|
316
|
+
if (conditions.length > 0) {
|
|
268
317
|
const v = emptyVariant();
|
|
269
|
-
|
|
270
|
-
conditions
|
|
318
|
+
v.parentGroups.push({
|
|
319
|
+
conditions,
|
|
271
320
|
direct,
|
|
272
|
-
negated
|
|
273
|
-
};
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
321
|
+
negated: false
|
|
322
|
+
});
|
|
323
|
+
variants.push(v);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
if (variants.length === 0) return {
|
|
327
|
+
variants: [emptyVariant()],
|
|
328
|
+
isImpossible: false
|
|
329
|
+
};
|
|
330
|
+
return {
|
|
331
|
+
variants,
|
|
279
332
|
isImpossible: false
|
|
280
333
|
};
|
|
281
334
|
}
|
|
@@ -330,36 +383,6 @@ function getSelectorConditionKey(cond) {
|
|
|
330
383
|
return "attribute" in cond ? `mod:${getModifierKey(cond)}` : `pseudo:${getPseudoKey(cond)}`;
|
|
331
384
|
}
|
|
332
385
|
/**
|
|
333
|
-
* Deduplicate modifier conditions
|
|
334
|
-
*/
|
|
335
|
-
function dedupeModifierConditions(conditions) {
|
|
336
|
-
const seen = /* @__PURE__ */ new Set();
|
|
337
|
-
const result = [];
|
|
338
|
-
for (const c of conditions) {
|
|
339
|
-
const key = getModifierKey(c);
|
|
340
|
-
if (!seen.has(key)) {
|
|
341
|
-
seen.add(key);
|
|
342
|
-
result.push(c);
|
|
343
|
-
}
|
|
344
|
-
}
|
|
345
|
-
return result;
|
|
346
|
-
}
|
|
347
|
-
/**
|
|
348
|
-
* Deduplicate pseudo conditions
|
|
349
|
-
*/
|
|
350
|
-
function dedupePseudoConditions(conditions) {
|
|
351
|
-
const seen = /* @__PURE__ */ new Set();
|
|
352
|
-
const result = [];
|
|
353
|
-
for (const c of conditions) {
|
|
354
|
-
const key = getPseudoKey(c);
|
|
355
|
-
if (!seen.has(key)) {
|
|
356
|
-
seen.add(key);
|
|
357
|
-
result.push(c);
|
|
358
|
-
}
|
|
359
|
-
}
|
|
360
|
-
return result;
|
|
361
|
-
}
|
|
362
|
-
/**
|
|
363
386
|
* Deduplicate selector conditions (modifiers or pseudos).
|
|
364
387
|
* Shared by root, parent, and own conditions.
|
|
365
388
|
*/
|
|
@@ -374,15 +397,16 @@ function dedupeSelectorConditions(conditions) {
|
|
|
374
397
|
}
|
|
375
398
|
}
|
|
376
399
|
const negatedBooleanAttrs = /* @__PURE__ */ new Set();
|
|
377
|
-
const
|
|
400
|
+
const positiveExactValuesByAttr = /* @__PURE__ */ new Map();
|
|
378
401
|
for (const c of result) {
|
|
379
402
|
if (!("attribute" in c)) continue;
|
|
380
403
|
if (c.negated && c.value === void 0) negatedBooleanAttrs.add(c.attribute);
|
|
381
|
-
|
|
382
|
-
|
|
404
|
+
const op = c.operator ?? "=";
|
|
405
|
+
if (!c.negated && c.value !== void 0 && op === "=") {
|
|
406
|
+
let values = positiveExactValuesByAttr.get(c.attribute);
|
|
383
407
|
if (!values) {
|
|
384
408
|
values = /* @__PURE__ */ new Set();
|
|
385
|
-
|
|
409
|
+
positiveExactValuesByAttr.set(c.attribute, values);
|
|
386
410
|
}
|
|
387
411
|
values.add(c.value);
|
|
388
412
|
}
|
|
@@ -390,7 +414,8 @@ function dedupeSelectorConditions(conditions) {
|
|
|
390
414
|
result = result.filter((c) => {
|
|
391
415
|
if (!("attribute" in c) || !c.negated || c.value === void 0) return true;
|
|
392
416
|
if (negatedBooleanAttrs.has(c.attribute)) return false;
|
|
393
|
-
|
|
417
|
+
if ((c.operator ?? "=") !== "=") return true;
|
|
418
|
+
const positiveValues = positiveExactValuesByAttr.get(c.attribute);
|
|
394
419
|
if (positiveValues !== void 0 && positiveValues.size === 1 && !positiveValues.has(c.value)) return false;
|
|
395
420
|
return true;
|
|
396
421
|
});
|
|
@@ -433,6 +458,21 @@ function hasSelectorConditionContradiction(conditions) {
|
|
|
433
458
|
return hasModifierContradiction(modifiers) || hasPseudoContradiction(pseudos);
|
|
434
459
|
}
|
|
435
460
|
/**
|
|
461
|
+
* Check for parent group contradiction: same target (direct + conditions)
|
|
462
|
+
* with opposite negation. E.g. :not([data-hovered] *) and :is([data-hovered] *)
|
|
463
|
+
* in the same variant is impossible.
|
|
464
|
+
*/
|
|
465
|
+
function hasParentGroupContradiction(groups) {
|
|
466
|
+
const byBaseKey = /* @__PURE__ */ new Map();
|
|
467
|
+
for (const g of groups) {
|
|
468
|
+
const baseKey = `${g.direct ? ">" : ""}(${g.conditions.map(getSelectorConditionKey).sort().join(",")})`;
|
|
469
|
+
const existing = byBaseKey.get(baseKey);
|
|
470
|
+
if (existing !== void 0 && existing !== !g.negated) return true;
|
|
471
|
+
byBaseKey.set(baseKey, !g.negated);
|
|
472
|
+
}
|
|
473
|
+
return false;
|
|
474
|
+
}
|
|
475
|
+
/**
|
|
436
476
|
* Merge two selector variants (AND operation)
|
|
437
477
|
* Deduplicates conditions and checks for contradictions
|
|
438
478
|
*/
|
|
@@ -441,11 +481,11 @@ function mergeVariants(a, b) {
|
|
|
441
481
|
if (hasMediaContradiction(mergedMedia)) return null;
|
|
442
482
|
const mergedRoots = dedupeSelectorConditions([...a.rootConditions, ...b.rootConditions]);
|
|
443
483
|
if (hasSelectorConditionContradiction(mergedRoots)) return null;
|
|
444
|
-
const mergedModifiers =
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
if (hasPseudoContradiction(mergedPseudos)) return null;
|
|
484
|
+
const mergedModifiers = dedupeSelectorConditions([...a.modifierConditions, ...b.modifierConditions]);
|
|
485
|
+
const mergedPseudos = dedupeSelectorConditions([...a.pseudoConditions, ...b.pseudoConditions]);
|
|
486
|
+
if (hasSelectorConditionContradiction([...mergedModifiers, ...mergedPseudos])) return null;
|
|
448
487
|
const mergedParentGroups = [...a.parentGroups, ...b.parentGroups];
|
|
488
|
+
if (hasParentGroupContradiction(mergedParentGroups)) return null;
|
|
449
489
|
const mergedOwn = dedupeSelectorConditions([...a.ownConditions, ...b.ownConditions]);
|
|
450
490
|
if (hasSelectorConditionContradiction(mergedOwn)) return null;
|
|
451
491
|
const mergedContainers = dedupeContainerConditions([...a.containerConditions, ...b.containerConditions]);
|
|
@@ -620,7 +660,7 @@ function getVariantKey(v) {
|
|
|
620
660
|
containerKey,
|
|
621
661
|
v.supportsConditions.map((c) => `${c.subtype}:${c.negated ? "!" : ""}${c.condition}`).sort().join("|"),
|
|
622
662
|
v.rootConditions.map(getSelectorConditionKey).sort().join("|"),
|
|
623
|
-
v.parentGroups.map(
|
|
663
|
+
v.parentGroups.map(getParentGroupKey).sort().join("|"),
|
|
624
664
|
v.startingStyle ? "1" : "0"
|
|
625
665
|
].join("###");
|
|
626
666
|
}
|