@charcoal-ui/styled 2.4.0 → 2.5.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.
Files changed (57) hide show
  1. package/dist/builders/border.d.ts +10 -0
  2. package/dist/builders/border.d.ts.map +1 -0
  3. package/dist/builders/borderRadius.d.ts +7 -0
  4. package/dist/builders/borderRadius.d.ts.map +1 -0
  5. package/dist/builders/colors.d.ts +13 -0
  6. package/dist/builders/colors.d.ts.map +1 -0
  7. package/dist/builders/elementEffect.d.ts +7 -0
  8. package/dist/builders/elementEffect.d.ts.map +1 -0
  9. package/dist/builders/o.d.ts +115 -0
  10. package/dist/builders/o.d.ts.map +1 -0
  11. package/dist/builders/outline.d.ts +10 -0
  12. package/dist/builders/outline.d.ts.map +1 -0
  13. package/dist/builders/size.d.ts +23 -0
  14. package/dist/builders/size.d.ts.map +1 -0
  15. package/dist/builders/spacing.d.ts +15 -0
  16. package/dist/builders/spacing.d.ts.map +1 -0
  17. package/dist/builders/transition.d.ts +7 -0
  18. package/dist/builders/transition.d.ts.map +1 -0
  19. package/dist/builders/typography.d.ts +11 -0
  20. package/dist/builders/typography.d.ts.map +1 -0
  21. package/dist/{lib.d.ts → factories/lib.d.ts} +13 -14
  22. package/dist/factories/lib.d.ts.map +1 -0
  23. package/dist/index.cjs +764 -641
  24. package/dist/index.cjs.map +1 -1
  25. package/dist/index.d.ts +91 -30
  26. package/dist/index.d.ts.map +1 -1
  27. package/dist/index.modern.js +554 -391
  28. package/dist/index.modern.js.map +1 -1
  29. package/dist/index.module.js +764 -641
  30. package/dist/index.module.js.map +1 -1
  31. package/dist/index.story.d.ts +1 -0
  32. package/dist/index.story.d.ts.map +1 -1
  33. package/dist/index.test.d.ts +2 -0
  34. package/dist/index.test.d.ts.map +1 -0
  35. package/dist/internals/index.d.ts +42 -0
  36. package/dist/internals/index.d.ts.map +1 -0
  37. package/dist/util.d.ts +36 -2
  38. package/dist/util.d.ts.map +1 -1
  39. package/package.json +8 -5
  40. package/src/__snapshots__/index.test.tsx.snap +768 -0
  41. package/src/builders/border.ts +63 -0
  42. package/src/builders/borderRadius.ts +32 -0
  43. package/src/builders/colors.ts +198 -0
  44. package/src/builders/elementEffect.ts +54 -0
  45. package/src/builders/o.ts +43 -0
  46. package/src/builders/outline.ts +79 -0
  47. package/src/builders/size.ts +61 -0
  48. package/src/builders/spacing.ts +113 -0
  49. package/src/builders/transition.ts +32 -0
  50. package/src/builders/typography.ts +97 -0
  51. package/src/{lib.ts → factories/lib.ts} +30 -25
  52. package/src/index.story.tsx +2 -2
  53. package/src/index.test.tsx +24 -0
  54. package/src/index.ts +47 -696
  55. package/src/internals/index.ts +84 -0
  56. package/src/util.ts +46 -3
  57. package/dist/lib.d.ts.map +0 -1
@@ -1,5 +1,5 @@
1
+ import { filterObject, flatMapObject, customPropertyToken, applyEffect, notDisabledSelector, disabledSelector, px, dur, gradient, applyEffectToGradient, halfLeading } from '@charcoal-ui/utils';
1
2
  import warning from 'warning';
2
- import { filterObject, flatMapObject, customPropertyToken, applyEffect, dur, gradient, applyEffectToGradient, notDisabledSelector, halfLeading, px, disabledSelector } from '@charcoal-ui/utils';
3
3
  import { columnSystem } from '@charcoal-ui/foundation';
4
4
  import React, { useEffect, useState, useMemo } from 'react';
5
5
  import { createGlobalStyle, css } from 'styled-components';
@@ -35,9 +35,27 @@ const isPresent = value => value != null; // eslint-disable-next-line @typescrip
35
35
  function objectAssign(...sources) {
36
36
  return Object.assign({}, ...sources);
37
37
  }
38
- function objectKeys(obj) {
38
+ /**
39
+ * Object.keys の返り値の型を厳しめにしてくれるやつ。
40
+ *
41
+ * ジェネリクスは基本的に明示して使うことを推奨。
42
+ *
43
+ * このライブラリでは Theme オブジェクトのジェネリクスを引き回すケースが多く、
44
+ * ジェネリクスを省略するといつのまにか keys の返り値が `string | number | symbol` になりがちなので
45
+ *
46
+ * @param obj - キーを取りたいオブジェクト。ジェネリクスを省略したとき `never[]` のような使えない型が返って欲しい
47
+ */
48
+
49
+ function keyof(obj) {
39
50
  return Object.keys(obj);
40
51
  }
52
+ /**
53
+ * 配列じゃなかったら配列にする
54
+ */
55
+
56
+ function wrapArray(value) {
57
+ return Array.isArray(value) ? value : [value];
58
+ }
41
59
  const noThemeProvider = new Error('`theme` is invalid. `<ThemeProvider>` is not likely mounted.');
42
60
  /**
43
61
  * 子孫要素で使われるカラーテーマの CSS Variables を上書きする
@@ -67,6 +85,80 @@ function defineThemeVariables(colorParams, effectParams) {
67
85
  return flatMapObject(colors, (colorKey, color) => [[customPropertyToken(colorKey), color], ...effects.map(([effectKey, effect]) => [customPropertyToken(colorKey, [effectKey]), applyEffect(color, [effect])])]);
68
86
  };
69
87
  }
88
+ function isSupportedEffect(effect) {
89
+ return ['hover', 'press', 'disabled'].includes(effect);
90
+ }
91
+ const variable = value => `var(${value})`;
92
+ function onEffectPseudo(effect, css) {
93
+ return effect === 'hover' ? {
94
+ '&:hover': {
95
+ [notDisabledSelector]: css
96
+ }
97
+ } : effect === 'press' ? {
98
+ '&:active': {
99
+ [notDisabledSelector]: css
100
+ }
101
+ } : // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
102
+ effect === 'disabled' ? {
103
+ [disabledSelector]: css
104
+ } : unreachable(effect);
105
+ }
106
+
107
+ /**
108
+ * 絶対にこれを export してはいけない
109
+ *
110
+ * さもないと `o.bg[internalSym]` みたいな叩き方が可能になってしまう(補完にも意図せず出てしまう)
111
+ */
112
+ const internalSym = Symbol('internal');
113
+ /**
114
+ * CSSObject に変換可能なオブジェクトを作成する
115
+ *
116
+ * 実際に CSSObject に変換するには外部から `__DO_NOT_USE_GET_INTERNAL__` を使わなければならない
117
+ *
118
+ * これ以降メソッドチェーンが続いてもいいし、続かなくても良い
119
+ */
120
+
121
+ function createInternal({
122
+ toCSS,
123
+ context = {}
124
+ }) {
125
+ return {
126
+ [internalSym]: {
127
+ toCSS,
128
+ context
129
+ }
130
+ };
131
+ }
132
+
133
+ function __DO_NOT_USE_ACCESS_PRIVATE_PROPERTY__(internal) {
134
+ return internal[internalSym];
135
+ } // half-leadingをキャンセルするとき && 垂直方向のpaddingが無い時
136
+ // -> before/afterを入れる
137
+
138
+
139
+ const shouldCancelHalfLeading = ({
140
+ cancelHalfLeadingPx,
141
+ hasVerticalPadding: _hasVerticalPadding = false
142
+ }) => cancelHalfLeadingPx !== undefined && !_hasVerticalPadding;
143
+ /**
144
+ * 個別の Internal( o.〇〇 の返り値 )が提出した context の中身を1つの context にまとめる
145
+ */
146
+
147
+ function getContext(internals) {
148
+ return internals.reduce((context, internal) => _extends({}, context, __DO_NOT_USE_ACCESS_PRIVATE_PROPERTY__(internal).context), {});
149
+ }
150
+ /**
151
+ * 全ユーザー定義からコンテキスト生成し、styled-components 向けに CSSObject を構築
152
+ */
153
+
154
+ function toCSSObjects(internals) {
155
+ // 1パス目
156
+ // 全ユーザー定義を舐めて相互に影響し合う定義をチェックし、その結果(コンテキスト)を取得
157
+ const context = getContext(internals); // 2パス目
158
+ // コンテキストを見ながら最適化されたCSSを構築
159
+
160
+ return internals.map(v => __DO_NOT_USE_ACCESS_PRIVATE_PROPERTY__(v).toCSS(context));
161
+ }
70
162
 
71
163
  /**
72
164
  * 配列で指定したプロパティを動的に生やす
@@ -84,7 +176,7 @@ function defineThemeVariables(colorParams, effectParams) {
84
176
  * console.log(o.red) //=> #ff0000
85
177
  */
86
178
 
87
- const factory = (source, member, chain) => Object.defineProperties(source, Object.fromEntries(member.map(key => [key, {
179
+ const defineProperties = (source, member, chain) => Object.defineProperties(source, Object.fromEntries(member.map(key => [key, {
88
180
  get: () => chain(key),
89
181
  enumerable: true,
90
182
  configurable: true
@@ -98,14 +190,14 @@ const factory = (source, member, chain) => Object.defineProperties(source, Objec
98
190
  *
99
191
  * @example
100
192
  *
101
- * const o = argumentedFactory({}, ['red', 'blue'],
193
+ * const o = defineMethods({}, ['red', 'blue'],
102
194
  * (color, alpha: number) => hex(color, alpha)
103
195
  * )
104
196
  *
105
197
  * console.log(o.red(0.5)) //=> #ff000077
106
198
  */
107
199
 
108
- const argumentedFactory = (source, member, chain) => Object.defineProperties(source, Object.fromEntries(member.map(key => [key, {
200
+ const defineMethods = (source, member, chain) => Object.defineProperties(source, Object.fromEntries(member.map(key => [key, {
109
201
  value: (...args) => chain(key, ...args),
110
202
  enumerable: true,
111
203
  configurable: true
@@ -118,7 +210,7 @@ const argumentedFactory = (source, member, chain) => Object.defineProperties(sou
118
210
  *
119
211
  * @example
120
212
  *
121
- * const o = constFactory({}, {
213
+ * const o = defineConstantProperties({}, {
122
214
  * red: '#f00',
123
215
  * blue: '#00f',
124
216
  * })
@@ -126,7 +218,7 @@ const argumentedFactory = (source, member, chain) => Object.defineProperties(sou
126
218
  * console.log(o.red) //=> #f00
127
219
  */
128
220
 
129
- const constFactory = (source, def) => factory(source, Object.keys(def), key => def[key]);
221
+ const defineConstantProperties = (source, def) => defineProperties(source, Object.keys(def), key => def[key]);
130
222
  /**
131
223
  * 配列で指定したモディファイア(プロパティ)をチェーン可能な再帰オブジェクトを動的に生やす
132
224
  *
@@ -135,16 +227,16 @@ const constFactory = (source, def) => factory(source, Object.keys(def), key => d
135
227
  *
136
228
  * @example
137
229
  *
138
- * const o = modifiedArgumentedFactory(['red', 'blue'],
230
+ * const o = definePropertyChains(['red', 'blue'],
139
231
  * modifiers => modifiers.map(color => hex(color)).join(',')
140
232
  * )
141
233
  *
142
234
  * console.log(o.red.blue) => #f00,#00f
143
235
  */
144
236
 
145
- const modifiedFactory = (modifiers, source) => function recursiveModifiedFactory(applied) {
237
+ const definePropertyChains = (modifiers, source) => function definePropertiesRecursively(applied) {
146
238
  const notApplied = modifiers.filter(v => !applied.includes(v));
147
- return factory(source(applied), notApplied, modifier => notApplied.length === 0 ? unreachable() : recursiveModifiedFactory([...applied, modifier]));
239
+ return defineProperties(source(applied), notApplied, modifier => notApplied.length === 0 ? unreachable() : definePropertiesRecursively([...applied, modifier]));
148
240
  }([]);
149
241
  /**
150
242
  * 配列で指定したモディファイア(メソッド)をチェーン可能な再帰オブジェクトを動的に生やす
@@ -155,18 +247,424 @@ const modifiedFactory = (modifiers, source) => function recursiveModifiedFactory
155
247
  *
156
248
  * @example
157
249
  *
158
- * const o = modifiedArgumentedFactory(['red', 'blue'],
250
+ * const o = defineMethodChains(['red', 'blue'],
159
251
  * modifiers => modifiers.map(([color, alpha]) => hex(color, alpha)).join(',')
160
252
  * , {} as [number])
161
253
  *
162
- * console.log(o.red(0.5).blue(1)) => #ff000077,#0000ffff
254
+ * console.log(o.red(0.5).blue(1)) => #ff000077,#0000ffff
255
+ */
256
+
257
+ const defineMethodChains = (modifiers, source, ..._inferPhantom) => function defineMethodsRecursively(applied) {
258
+ const notApplied = modifiers.filter(v => !applied.map(([w]) => w).includes(v));
259
+ return defineMethods(source(applied), notApplied, (modifier, ...args) => notApplied.length === 0 ? unreachable() : defineMethodsRecursively([...applied, [modifier, ...args]]));
260
+ }([]);
261
+
262
+ const borderDirections = ['top', 'right', 'bottom', 'left'];
263
+
264
+ function borderProperty(direction) {
265
+ return `border-${direction}`;
266
+ }
267
+
268
+ function borderShorthand(color) {
269
+ return `solid 1px ${color}`;
270
+ }
271
+
272
+ const createBorderCss = theme => (variant, directions) => {
273
+ const all = directions.length === 0;
274
+ const value = borderShorthand(theme.border[variant].color);
275
+ return createInternal({
276
+ toCSS() {
277
+ return _extends({}, all ? {
278
+ border: value
279
+ } : directions.reduce((acc, direction) => _extends({}, acc, {
280
+ [borderProperty(direction)]: value
281
+ }), {}));
282
+ }
283
+
284
+ });
285
+ };
286
+ function border(theme) {
287
+ const borderTypes = keyof(theme.border);
288
+ const borderCss = createBorderCss(theme);
289
+ const borderObject = defineConstantProperties({}, {
290
+ border: defineProperties({}, borderTypes, variant => definePropertyChains(borderDirections, modifiers => borderCss(variant, modifiers)))
291
+ });
292
+ return borderObject;
293
+ }
294
+
295
+ const createBorderRadiusCss = theme => size => {
296
+ return createInternal({
297
+ toCSS() {
298
+ return {
299
+ borderRadius: px(theme.borderRadius[size])
300
+ };
301
+ }
302
+
303
+ });
304
+ };
305
+ function borderRadius(theme) {
306
+ // 角丸
307
+ const borderRadiusCss = createBorderRadiusCss(theme);
308
+ const borderRadiusObject = defineConstantProperties({}, {
309
+ borderRadius: radius => borderRadiusCss(radius)
310
+ });
311
+ return borderRadiusObject;
312
+ }
313
+
314
+ const TRANSITION_DURATION = 0.2;
315
+ /**
316
+ * context の状態を元に transition を追加する。必ず一番最後に呼ぶ
317
+ */
318
+
319
+ function transition(_theme) {
320
+ const duration = dur(TRANSITION_DURATION);
321
+
322
+ const transition = property => ({
323
+ transition: property.map(v => `${duration} ${v}`).join(', ')
324
+ });
325
+
326
+ function toCSS({
327
+ colorTransition = false,
328
+ backgroundColorTransition = false,
329
+ boxShadowTransition = false
330
+ }) {
331
+ return transition([colorTransition ? 'color' : null, backgroundColorTransition ? 'background-color' : null, boxShadowTransition ? 'box-shadow' : null].filter(isPresent));
332
+ }
333
+
334
+ return createInternal({
335
+ toCSS
336
+ });
337
+ }
338
+
339
+ function targetProperty(target) {
340
+ return target === 'bg' ? 'background-color' : 'color';
341
+ }
342
+
343
+ const createColorCss = _theme => (target, color, effects = []) => {
344
+ function toCSS() {
345
+ return _extends({
346
+ [targetProperty(target)]: variable(customPropertyToken(color.toString()))
347
+ }, effects.filter(isSupportedEffect).reduce((acc, effect) => _extends({}, acc, onEffectPseudo(effect, {
348
+ [targetProperty(target)]: variable(customPropertyToken(color.toString(), [effect]))
349
+ })), {}));
350
+ }
351
+
352
+ return createInternal({
353
+ toCSS,
354
+ context: effects.length > 0 ? target === 'font' ? {
355
+ colorTransition: true
356
+ } : {
357
+ backgroundColorTransition: true
358
+ } : {}
359
+ });
360
+ };
361
+ const createGradientColorCss = theme => (color, effects = [], direction) => {
362
+ const toLinearGradient = gradient(direction);
363
+
364
+ function toCSS(context) {
365
+ const optimized = !shouldCancelHalfLeading(context);
366
+ const duration = dur(TRANSITION_DURATION);
367
+
368
+ if (optimized && effects.length > 0) {
369
+ return _extends({
370
+ position: 'relative',
371
+ zIndex: 0,
372
+ overflow: 'hidden'
373
+ }, effects.filter(isSupportedEffect).reduce((acc, effect) => {
374
+ var _theme$effect$effect;
375
+
376
+ return _extends({}, acc, {
377
+ '&::before': _extends({
378
+ zIndex: -1
379
+ }, overlayElement, {
380
+ transition: `${duration} background-color`
381
+ }),
382
+ '&::after': _extends({
383
+ zIndex: -2
384
+ }, overlayElement, toLinearGradient(theme.gradientColor[color]))
385
+ }, onEffectPseudo(effect, {
386
+ '&::before': {
387
+ backgroundColor: applyEffect(null, (_theme$effect$effect = theme.effect[effect]) != null ? _theme$effect$effect : [])
388
+ }
389
+ }));
390
+ }, {}));
391
+ }
392
+
393
+ warning(effects.length === 0, // eslint-disable-next-line max-len
394
+ `'Transition' will not be applied. You can get around this by specifying 'preserveHalfLeading' or both 'padding' and 'typograpy'.`);
395
+ return _extends({}, toLinearGradient(theme.gradientColor[color]), effects.filter(isSupportedEffect).reduce((acc, effect) => {
396
+ var _theme$effect$effect2;
397
+
398
+ return _extends({}, acc, onEffectPseudo(effect, _extends({}, toLinearGradient(applyEffectToGradient((_theme$effect$effect2 = theme.effect[effect]) != null ? _theme$effect$effect2 : [])(theme.gradientColor[color])))));
399
+ }, {}));
400
+ }
401
+
402
+ return createInternal({
403
+ toCSS
404
+ });
405
+ };
406
+ const overlayElement = {
407
+ content: "''",
408
+ display: 'block',
409
+ position: 'absolute',
410
+ width: '100%',
411
+ height: '100%',
412
+ top: 0,
413
+ left: 0
414
+ };
415
+ function colors(theme) {
416
+ const colors = keyof(theme.color);
417
+ const effects = keyof(theme.effect); // 色
418
+
419
+ const gradientColors = keyof(theme.gradientColor);
420
+ const colorCss = createColorCss();
421
+ const gradientColorCss = createGradientColorCss(theme);
422
+ const colorObject = defineConstantProperties({}, {
423
+ bg: objectAssign(defineProperties({}, colors, color => definePropertyChains(effects, modifiers => colorCss('bg', color, modifiers))), defineProperties({}, gradientColors, color => direction => definePropertyChains(effects, modifiers => gradientColorCss(color, modifiers, direction)))),
424
+ font: defineProperties({}, colors, color => definePropertyChains(effects, modifiers => colorCss('font', color, modifiers)))
425
+ });
426
+ return colorObject;
427
+ }
428
+
429
+ const createElementEffectCss = theme => (effects = []) => createInternal({
430
+ toCSS() {
431
+ return effects.filter(isSupportedEffect).reduce((acc, effect) => {
432
+ var _theme$elementEffect$, _theme$elementEffect$2;
433
+
434
+ return _extends({}, acc, onEffectPseudo(effect, {
435
+ opacity: !Array.isArray(theme.elementEffect[effect]) && ((_theme$elementEffect$ = theme.elementEffect[effect]) == null ? void 0 : _theme$elementEffect$.type) === 'opacity' ? (_theme$elementEffect$2 = theme.elementEffect[effect]) == null ? void 0 : _theme$elementEffect$2.opacity : unreachable()
436
+ }));
437
+ }, {});
438
+ }
439
+
440
+ });
441
+ function elementEffect(theme) {
442
+ const effectTypes = keyof(theme.elementEffect); // 要素へのエフェクト (etc: 透過)
443
+
444
+ const elementEffectCss = createElementEffectCss(theme);
445
+ const elementEffectObject = definePropertyChains(effectTypes, modifiers => elementEffectCss(modifiers));
446
+ return elementEffectObject;
447
+ }
448
+
449
+ const outlineType = ['focus'];
450
+
451
+ const outlineCss = (weight, color) => ({
452
+ boxShadow: `0 0 0 ${px(weight)} ${color}`
453
+ });
454
+
455
+ const createOutlineColorCss = theme => (variant, modifiers) => {
456
+ const weight = theme.outline[variant].weight;
457
+ const color = theme.outline[variant].color;
458
+ return createInternal({
459
+ toCSS() {
460
+ return modifiers.includes('focus') ? onFocus(outlineCss(weight, color)) : {
461
+ '&&': {
462
+ [notDisabledSelector]: outlineCss(weight, color)
463
+ }
464
+ };
465
+ },
466
+
467
+ context: {
468
+ boxShadowTransition: true
469
+ }
470
+ });
471
+ };
472
+ /**
473
+ * @see https://developer.mozilla.org/ja/docs/Web/CSS/:focus-visible#selectively_showing_the_focus_indicator
474
+ */
475
+
476
+ const onFocus = css => ({
477
+ [notDisabledSelector]: {
478
+ '&:focus, &:active': _extends({
479
+ outline: 'none'
480
+ }, css),
481
+ '&:focus:not(:focus-visible), &:active:not(:focus-visible)': {
482
+ outline: 'none'
483
+ },
484
+ '&:focus-visible': _extends({
485
+ outline: 'none'
486
+ }, css)
487
+ }
488
+ });
489
+
490
+ function outline(theme) {
491
+ const outlineCss = createOutlineColorCss(theme);
492
+ const outlineObject = defineConstantProperties({}, {
493
+ outline: defineProperties({}, keyof(theme.outline), variant => definePropertyChains(outlineType, modifiers => outlineCss(variant, modifiers)))
494
+ });
495
+ return outlineObject;
496
+ }
497
+
498
+ const fixedProperties = ['width', 'height'];
499
+ const createFixedPxCss = theme => (property, size) => createInternal({
500
+ toCSS() {
501
+ return {
502
+ [property]: size === 'auto' ? 'auto' : px(theme.spacing[size])
503
+ };
504
+ }
505
+
506
+ });
507
+ const createFixedRelativeCss = _theme => (property, amount) => createInternal({
508
+ toCSS() {
509
+ return {
510
+ [property]: amount
511
+ };
512
+ }
513
+
514
+ });
515
+ const createFixedColumnCss = theme => (property, span) => createInternal({
516
+ toCSS() {
517
+ return {
518
+ [property]: px(columnSystem(span, theme.grid.unit.column, theme.grid.unit.gutter))
519
+ };
520
+ }
521
+
522
+ });
523
+ function size(theme) {
524
+ const fixedPxCss = createFixedPxCss(theme);
525
+ const fixedColumnCss = createFixedColumnCss(theme);
526
+ const fixedRelativeCss = createFixedRelativeCss();
527
+ const fixedObject = defineProperties({}, fixedProperties, property => defineConstantProperties({}, {
528
+ px: size => fixedPxCss(property, size),
529
+ column: span => fixedColumnCss(property, span),
530
+ auto: fixedRelativeCss(property, 'auto'),
531
+ full: fixedRelativeCss(property, '100%')
532
+ }));
533
+ return fixedObject;
534
+ }
535
+
536
+ const spacingProperties = ['margin', 'padding'];
537
+ const spacingDirections = ['top', 'right', 'bottom', 'left', 'vertical', 'horizontal', 'all'];
538
+
539
+ function spacingProperty(property, direction) {
540
+ return `${property}-${direction}`;
541
+ }
542
+
543
+ const createSpacingCss = theme => (property, modifiers) => {
544
+ const {
545
+ top,
546
+ right,
547
+ bottom,
548
+ left
549
+ } = modifiers.reduce((acc, [direction, size]) => {
550
+ if (direction === 'all') {
551
+ acc.top = size;
552
+ acc.right = size;
553
+ acc.bottom = size;
554
+ acc.left = size;
555
+ } else if (direction === 'vertical') {
556
+ acc.top = size;
557
+ acc.bottom = size;
558
+ } else if (direction === 'horizontal') {
559
+ acc.right = size;
560
+ acc.left = size;
561
+ } else {
562
+ acc[direction] = size;
563
+ }
564
+
565
+ return acc;
566
+ }, {});
567
+ const hasVerticalPadding = property === 'padding' && top !== undefined && bottom !== undefined && top !== 'auto' && bottom !== 'auto';
568
+
569
+ function toCSS({
570
+ cancelHalfLeadingPx = 0
571
+ }) {
572
+ return _extends({}, top !== undefined && {
573
+ [spacingProperty(property, 'top')]: top === 'auto' ? 'auto' : px(theme.spacing[top] + (hasVerticalPadding ? cancelHalfLeadingPx : 0))
574
+ }, bottom !== undefined && {
575
+ [spacingProperty(property, 'bottom')]: bottom === 'auto' ? 'auto' : px(theme.spacing[bottom] + (hasVerticalPadding ? cancelHalfLeadingPx : 0))
576
+ }, right !== undefined && {
577
+ [spacingProperty(property, 'right')]: right === 'auto' ? 'auto' : px(theme.spacing[right])
578
+ }, left !== undefined && {
579
+ [spacingProperty(property, 'left')]: left === 'auto' ? 'auto' : px(theme.spacing[left])
580
+ });
581
+ }
582
+
583
+ return createInternal({
584
+ toCSS,
585
+ context: hasVerticalPadding ? {
586
+ hasVerticalPadding: true
587
+ } : {}
588
+ });
589
+ };
590
+ function spacing(theme) {
591
+ const spacingCss = createSpacingCss(theme);
592
+ const spacingObject = defineProperties({}, spacingProperties, spacingProperty => defineMethodChains(spacingDirections, modifiers => spacingCss(spacingProperty, modifiers), {} // 推論のためのメタタイプ
593
+ ));
594
+ return spacingObject;
595
+ }
596
+
597
+ const createTypographyCss = theme => (size, options = {}) => {
598
+ const {
599
+ preserveHalfLeading = false,
600
+ monospace = false,
601
+ bold = false
602
+ } = options;
603
+ const descriptor = theme.typography.size[size];
604
+ const margin = -halfLeading(descriptor);
605
+
606
+ function toCSS(context) {
607
+ return _extends({
608
+ fontSize: px(descriptor.fontSize),
609
+ lineHeight: px(descriptor.lineHeight)
610
+ }, monospace && {
611
+ fontFamily: 'monospace'
612
+ }, bold && {
613
+ fontWeight: 'bold'
614
+ }, shouldCancelHalfLeading(context) && {
615
+ // prevent margin collapsing
616
+ display: 'flow-root',
617
+ // cancel half-leading with negative margin
618
+ '&::before': _extends({}, leadingCancel, {
619
+ marginTop: px(margin)
620
+ }),
621
+ '&::after': _extends({}, leadingCancel, {
622
+ marginBottom: px(margin)
623
+ })
624
+ });
625
+ }
626
+
627
+ return createInternal({
628
+ toCSS,
629
+ context: !preserveHalfLeading ? {
630
+ cancelHalfLeadingPx: margin
631
+ } : {}
632
+ });
633
+ };
634
+ const leadingCancel = {
635
+ display: 'block',
636
+ width: 0,
637
+ height: 0,
638
+ content: `''`
639
+ }; // タイポグラフィ
640
+
641
+ const typographyModifiers = [// TODO
642
+ 'monospace', 'bold', 'preserveHalfLeading'];
643
+ function typography(theme) {
644
+ const typographyCss = createTypographyCss(theme);
645
+ const typographyObject = defineProperties({}, ['typography'], _ => size => definePropertyChains(typographyModifiers, modifiers => typographyCss(size, {
646
+ preserveHalfLeading: modifiers.includes('preserveHalfLeading'),
647
+ monospace: modifiers.includes('monospace'),
648
+ bold: modifiers.includes('bold')
649
+ })));
650
+ return typographyObject;
651
+ }
652
+
653
+ /**
654
+ * `theme(o => [...])` の `o` の部分を構築する
655
+ *
656
+ * @param theme テーマオブジェクト
657
+ * @param DO_NOTHING_IT_IS_JUST_CALLED_FOR_TYPE_INFERENCE 型推論のためだけに使う場合にランタイムコストをゼロにするフラグ
163
658
  */
164
659
 
165
- const modifiedArgumentedFactory = (modifiers, source, ..._inferPhantom) => function recursiveModifiedFactory(applied) {
166
- const notApplied = modifiers.filter(v => !applied.map(([w]) => w).includes(v));
167
- return argumentedFactory(source(applied), notApplied, (modifier, ...args) => notApplied.length === 0 ? unreachable() : recursiveModifiedFactory([...applied, [modifier, ...args]]));
168
- }([]);
169
- const variable = value => `var(${value})`;
660
+ function createO(theme, DO_NOTHING_IT_IS_JUST_CALLED_FOR_TYPE_INFERENCE = false) {
661
+ if (DO_NOTHING_IT_IS_JUST_CALLED_FOR_TYPE_INFERENCE) {
662
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
663
+ return {};
664
+ }
665
+
666
+ return objectAssign(colors(theme), typography(theme), spacing(theme), size(theme), elementEffect(theme), border(theme), borderRadius(theme), outline(theme));
667
+ }
170
668
 
171
669
  let _ = t => t,
172
670
  _t,
@@ -427,358 +925,11 @@ const defaultProps = {
427
925
  };
428
926
  SetThemeScript.defaultProps = defaultProps;
429
927
 
430
- const spacingProperties = ['margin', 'padding'];
431
- const spacingDirections = ['top', 'right', 'bottom', 'left', 'vertical', 'horizontal', 'all'];
432
- const fixedProperties = ['width', 'height'];
433
- const borderDirections = ['top', 'right', 'bottom', 'left'];
434
- const outlineType = ['focus'];
435
- /**
436
- * `theme(o => [...])` の `o` の部分を構築する
437
- *
438
- * @param theme テーマオブジェクト
439
- * @param isPhantom 型推論のためだけに使う場合にランタイムコストをゼロにするフラグ
440
- */
441
-
442
- function builder(theme, isPhantom = false) {
443
- if (isPhantom) {
444
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
445
- return {};
446
- }
447
-
448
- const colors = objectKeys(theme.color);
449
- const effects = objectKeys(theme.effect); // 色
450
-
451
- const gradientColors = objectKeys(theme.gradientColor);
452
- const colorCss = createColorCss();
453
- const gradientColorCss = createGradientColorCss(theme);
454
- const colorObject = constFactory({}, {
455
- bg: objectAssign(factory({}, colors, color => modifiedFactory(effects, modifiers => colorCss('bg', color, modifiers))), factory({}, gradientColors, color => direction => modifiedFactory(effects, modifiers => gradientColorCss(color, modifiers, direction)))),
456
- font: factory({}, colors, color => modifiedFactory(effects, modifiers => colorCss('font', color, modifiers)))
457
- }); // タイポグラフィ
458
-
459
- const typographyModifiers = [// TODO
460
- 'monospace', 'bold', 'preserveHalfLeading'];
461
- const typographyCss = createTypographyCss(theme);
462
- const typographyObject = factory({}, ['typography'], _ => size => modifiedFactory(typographyModifiers, modifiers => typographyCss(size, {
463
- preserveHalfLeading: modifiers.includes('preserveHalfLeading'),
464
- monospace: modifiers.includes('monospace'),
465
- bold: modifiers.includes('bold')
466
- }))); // スペーシング
467
-
468
- const spacingCss = createSpacingCss(theme);
469
- const spacingObject = factory({}, spacingProperties, spacingProperty => modifiedArgumentedFactory(spacingDirections, modifiers => spacingCss(spacingProperty, modifiers), {} // 推論のためのメタタイプ
470
- )); // 大きさ
471
-
472
- const fixedPxCss = createFixedPxCss(theme);
473
- const fixedColumnCss = createFixedColumnCss(theme);
474
- const fixedRelativeCss = createFixedRelativeCss();
475
- const fixedObject = factory({}, fixedProperties, property => constFactory({}, {
476
- px: size => fixedPxCss(property, size),
477
- column: span => fixedColumnCss(property, span),
478
- auto: fixedRelativeCss(property, 'auto'),
479
- full: fixedRelativeCss(property, '100%')
480
- })); // 要素へのエフェクト (etc: 透過)
481
-
482
- const elementEffectCss = createElementEffectCss(theme);
483
- const elementEffectObject = modifiedFactory(objectKeys(theme.elementEffect), modifiers => elementEffectCss(modifiers)); // ボーダー
484
-
485
- const borderCss = createBorderCss(theme);
486
- const borderObject = constFactory({}, {
487
- border: factory({}, objectKeys(theme.border), variant => modifiedFactory(borderDirections, modifiers => borderCss(variant, modifiers)))
488
- }); // 角丸
489
-
490
- const borderRadiusCss = createBorderRadiusCss(theme);
491
- const borderRadiusObject = constFactory({}, {
492
- borderRadius: radius => borderRadiusCss(radius)
493
- }); // アウトライン
494
-
495
- const outlineCss = createOutlineColorCss(theme);
496
- const outlineObject = constFactory({}, {
497
- outline: factory({}, objectKeys(theme.outline), variant => modifiedFactory(outlineType, modifiers => outlineCss(variant, modifiers)))
498
- });
499
- return objectAssign(colorObject, typographyObject, spacingObject, fixedObject, elementEffectObject, borderObject, borderRadiusObject, outlineObject);
500
- }
501
-
502
- function targetProperty(target) {
503
- return target === 'bg' ? 'background-color' : 'color';
504
- }
505
-
506
- function isSupportedEffect(effect) {
507
- return ['hover', 'press', 'disabled'].includes(effect);
508
- }
509
-
510
- function onEffectPseudo(effect, css) {
511
- return effect === 'hover' ? {
512
- '&:hover': {
513
- [notDisabledSelector]: css
514
- }
515
- } : effect === 'press' ? {
516
- '&:active': {
517
- [notDisabledSelector]: css
518
- }
519
- } : // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
520
- effect === 'disabled' ? {
521
- [disabledSelector]: css
522
- } : unreachable(effect);
523
- }
524
-
525
- const createColorCss = _theme => (target, color, effects = []) => internal(() => _extends({
526
- [targetProperty(target)]: variable(customPropertyToken(color.toString()))
527
- }, effects.filter(isSupportedEffect).reduce((acc, effect) => _extends({}, acc, onEffectPseudo(effect, {
528
- [targetProperty(target)]: variable(customPropertyToken(color.toString(), [effect]))
529
- })), {})), effects.length > 0 ? target === 'font' ? {
530
- colorTransition: true
531
- } : {
532
- backgroundColorTransition: true
533
- } : {}); // TODO: deprecate
534
-
535
-
536
- const TRANSITION_DURATION = 0.2;
537
-
538
- const createGradientColorCss = theme => (color, effects = [], direction) => {
539
- const toLinearGradient = gradient(direction);
540
- return internal(context => {
541
- const optimized = !useHalfLeadingCanceller(context);
542
- const duration = dur(TRANSITION_DURATION);
543
-
544
- if (optimized && effects.length > 0) {
545
- return _extends({
546
- position: 'relative',
547
- zIndex: 0,
548
- overflow: 'hidden'
549
- }, effects.filter(isSupportedEffect).reduce((acc, effect) => {
550
- var _theme$effect$effect;
551
-
552
- return _extends({}, acc, {
553
- '&::before': _extends({
554
- zIndex: -1
555
- }, overlayElement, {
556
- transition: `${duration} background-color`
557
- }),
558
- '&::after': _extends({
559
- zIndex: -2
560
- }, overlayElement, toLinearGradient(theme.gradientColor[color]))
561
- }, onEffectPseudo(effect, {
562
- '&::before': {
563
- backgroundColor: applyEffect(null, (_theme$effect$effect = theme.effect[effect]) != null ? _theme$effect$effect : [])
564
- }
565
- }));
566
- }, {}));
567
- } else {
568
- warning(effects.length === 0, // eslint-disable-next-line max-len
569
- `'Transition' will not be applied. You can get around this by specifying 'preserveHalfLeading' or both 'padding' and 'typograpy'.`);
570
- return _extends({}, toLinearGradient(theme.gradientColor[color]), effects.filter(isSupportedEffect).reduce((acc, effect) => {
571
- var _theme$effect$effect2;
572
-
573
- return _extends({}, acc, onEffectPseudo(effect, _extends({}, toLinearGradient(applyEffectToGradient((_theme$effect$effect2 = theme.effect[effect]) != null ? _theme$effect$effect2 : [])(theme.gradientColor[color])))));
574
- }, {}));
575
- }
576
- });
577
- };
578
- /**
579
- * @see https://developer.mozilla.org/ja/docs/Web/CSS/:focus-visible#selectively_showing_the_focus_indicator
580
- */
581
-
582
-
583
- const onFocus = css => ({
584
- [notDisabledSelector]: {
585
- '&:focus, &:active': _extends({
586
- outline: 'none'
587
- }, css),
588
- '&:focus:not(:focus-visible), &:active:not(:focus-visible)': {
589
- outline: 'none'
590
- },
591
- '&:focus-visible': _extends({
592
- outline: 'none'
593
- }, css)
594
- }
595
- });
596
-
597
- const outlineCss = (weight, color) => ({
598
- boxShadow: `0 0 0 ${px(weight)} ${color}`
599
- });
600
-
601
- const createOutlineColorCss = theme => (variant, modifiers) => {
602
- const weight = theme.outline[variant].weight;
603
- const color = theme.outline[variant].color;
604
- return internal(() => modifiers.includes('focus') ? onFocus(outlineCss(weight, color)) : {
605
- '&&': {
606
- [notDisabledSelector]: outlineCss(weight, color)
607
- }
608
- }, {
609
- boxShadowTransition: true
610
- });
611
- };
612
-
613
- const overlayElement = {
614
- content: "''",
615
- display: 'block',
616
- position: 'absolute',
617
- width: '100%',
618
- height: '100%',
619
- top: 0,
620
- left: 0
621
- }; // half-leadingをキャンセルするとき && 垂直方向のpaddingが無い時
622
- // -> before/afterを入れる
623
-
624
- const useHalfLeadingCanceller = ({
625
- cancelHalfLeadingPx,
626
- hasVerticalPadding: _hasVerticalPadding = false
627
- }) => cancelHalfLeadingPx !== undefined && !_hasVerticalPadding;
628
-
629
- const createTypographyCss = theme => (size, options = {}) => {
630
- const {
631
- preserveHalfLeading = false,
632
- monospace = false,
633
- bold = false
634
- } = options;
635
- const descriptor = theme.typography.size[size];
636
- const margin = -halfLeading(descriptor);
637
- return internal(context => _extends({
638
- fontSize: px(descriptor.fontSize),
639
- lineHeight: px(descriptor.lineHeight)
640
- }, monospace && {
641
- fontFamily: 'monospace'
642
- }, bold && {
643
- fontWeight: 'bold'
644
- }, useHalfLeadingCanceller(context) && {
645
- // prevent margin collapsing
646
- display: 'flow-root',
647
- // cancel half-leading with negative margin
648
- '&::before': _extends({}, leadingCancel, {
649
- marginTop: px(margin)
650
- }),
651
- '&::after': _extends({}, leadingCancel, {
652
- marginBottom: px(margin)
653
- })
654
- }), !preserveHalfLeading ? {
655
- cancelHalfLeadingPx: margin
656
- } : {});
657
- };
658
-
659
- const leadingCancel = {
660
- display: 'block',
661
- width: 0,
662
- height: 0,
663
- content: `''`
664
- };
665
-
666
- function spacingProperty(property, direction) {
667
- return `${property}-${direction}`;
668
- }
669
-
670
- const createSpacingCss = theme => (property, modifiers) => {
671
- const {
672
- top,
673
- right,
674
- bottom,
675
- left
676
- } = modifiers.reduce((acc, [direction, size]) => {
677
- if (direction === 'all') {
678
- acc.top = size;
679
- acc.right = size;
680
- acc.bottom = size;
681
- acc.left = size;
682
- } else if (direction === 'vertical') {
683
- acc.top = size;
684
- acc.bottom = size;
685
- } else if (direction === 'horizontal') {
686
- acc.right = size;
687
- acc.left = size;
688
- } else {
689
- acc[direction] = size;
690
- }
691
-
692
- return acc;
693
- }, {});
694
- const hasVerticalPadding = property === 'padding' && top !== undefined && bottom !== undefined && top !== 'auto' && bottom !== 'auto';
695
- return internal(({
696
- cancelHalfLeadingPx: _cancelHalfLeadingPx = 0
697
- }) => _extends({}, top !== undefined && {
698
- [spacingProperty(property, 'top')]: top === 'auto' ? 'auto' : px(theme.spacing[top] + (hasVerticalPadding ? _cancelHalfLeadingPx : 0))
699
- }, bottom !== undefined && {
700
- [spacingProperty(property, 'bottom')]: bottom === 'auto' ? 'auto' : px(theme.spacing[bottom] + (hasVerticalPadding ? _cancelHalfLeadingPx : 0))
701
- }, right !== undefined && {
702
- [spacingProperty(property, 'right')]: right === 'auto' ? 'auto' : px(theme.spacing[right])
703
- }, left !== undefined && {
704
- [spacingProperty(property, 'left')]: left === 'auto' ? 'auto' : px(theme.spacing[left])
705
- }), hasVerticalPadding ? {
706
- hasVerticalPadding: true
707
- } : {});
708
- };
709
-
710
- const createFixedPxCss = theme => (property, size) => internal(() => ({
711
- [property]: size === 'auto' ? 'auto' : px(theme.spacing[size])
712
- }));
713
-
714
- const createFixedRelativeCss = _theme => (property, amount) => internal(() => ({
715
- [property]: amount
716
- }));
717
-
718
- const createFixedColumnCss = theme => (property, span) => internal(() => ({
719
- [property]: px(columnSystem(span, theme.grid.unit.column, theme.grid.unit.gutter))
720
- }));
721
-
722
- const createElementEffectCss = theme => (effects = []) => internal(() => effects.filter(isSupportedEffect).reduce((acc, effect) => {
723
- var _theme$elementEffect$, _theme$elementEffect$2;
724
-
725
- return _extends({}, acc, onEffectPseudo(effect, {
726
- opacity: !Array.isArray(theme.elementEffect[effect]) && ((_theme$elementEffect$ = theme.elementEffect[effect]) == null ? void 0 : _theme$elementEffect$.type) === 'opacity' ? (_theme$elementEffect$2 = theme.elementEffect[effect]) == null ? void 0 : _theme$elementEffect$2.opacity : unreachable()
727
- }));
728
- }, {}));
729
-
730
- function borderProperty(direction) {
731
- return `border-${direction}`;
732
- }
733
-
734
- function borderShorthand(color) {
735
- return `solid 1px ${color}`;
736
- }
737
-
738
- const createBorderCss = theme => (variant, directions) => {
739
- const all = directions.length === 0;
740
- const value = borderShorthand(theme.border[variant].color);
741
- return internal(() => _extends({}, all ? {
742
- border: value
743
- } : directions.reduce((acc, direction) => _extends({}, acc, {
744
- [borderProperty(direction)]: value
745
- }), {})));
746
- };
747
-
748
- const createBorderRadiusCss = theme => size => internal(() => ({
749
- borderRadius: px(theme.borderRadius[size])
750
- }));
751
-
752
- const commonSpec = _theme => {
753
- const duration = dur(TRANSITION_DURATION);
754
-
755
- const transition = property => ({
756
- transition: property.map(v => `${duration} ${v}`).join(', ')
757
- });
758
-
759
- return internal(({
760
- colorTransition: _colorTransition = false,
761
- backgroundColorTransition: _backgroundColorTransition = false,
762
- boxShadowTransition: _boxShadowTransition = false
763
- }) => transition([_colorTransition ? 'color' : null, _backgroundColorTransition ? 'background-color' : null, _boxShadowTransition ? 'box-shadow' : null].filter(isPresent)));
764
- };
765
-
766
- const internalSym = Symbol('internal');
767
-
768
- function internal(operation, context = {}) {
769
- return {
770
- [internalSym]: {
771
- operation,
772
- context
773
- }
774
- };
775
- }
776
-
777
928
  const nonBlank = value => isPresent(value) && value !== false;
778
929
  /**
779
930
  * `theme(o => [...])` の `theme` ユーティリティを構築する
780
931
  *
781
- * @param _styled styled-componnets の `styled` そのもの (型推論のために用いられる)
932
+ * @param _styled - DEPRECATED: styled-componnets の `styled` そのものを渡すとそれを元に型推論ができる。が、型引数を渡す方が型推論が高速になりやすい
782
933
  *
783
934
  * @example
784
935
  *
@@ -792,33 +943,45 @@ const nonBlank = value => isPresent(value) && value !== false;
792
943
 
793
944
 
794
945
  function createTheme(_styled) {
795
- // `theme(o => [...])` の `o` の部分の型推論のためだけに使う意味のない変数
796
- // Tを型変数のまま渡してcreateThemeが呼ばれるまで型の具象化が行われないようにする
797
- // eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-explicit-any
798
- builder({}, true); // ランタイムの `theme(o => [...])` のインターフェースを構築する
799
-
800
-
801
- return ( // ユーザー定義
802
- spec) => ({
803
- theme
804
- }) => {
805
- if (!isPresent(theme)) {
806
- // テーマが入っていない場合は復旧不可能なのでエラーにする
807
- throw noThemeProvider;
808
- } // styled-componentsのランタイムから受け取ったthemeオブジェクトをbuilderに食わせて`o`をつくる
809
- // さらに、ユーザー定義にbuilderが構築した`o`を食わせる
810
- // (`o`を一時変数に入れてしまうと型Tの具象化が行われるので関数合成を優先する)
811
-
812
-
813
- const rawSpecDescriptor = spec(builder(theme)); // ユーザー定義の配列を整形
814
-
815
- const specDescriptor = [...(Array.isArray(rawSpecDescriptor) ? rawSpecDescriptor : [rawSpecDescriptor]), commonSpec()].filter(nonBlank); // 1パス目
816
- // 全ユーザー定義を舐めて相互に影響し合う定義をチェックし、その結果(コンテキスト)を取得
817
-
818
- const context = specDescriptor.reduce((acc, v) => _extends({}, acc, v[internalSym].context), {}); // 2パス目
819
- // コンテキストを見ながら最適化されたCSSを構築
946
+ /**
947
+ * 本当は `type Builder = ReturnType<createO<T>>` みたいな型を作って、それを o の型にしたい。
948
+ * が、styled がつくられた時点の TypeScript ではこういうジェネリクスの使い方ができなかった
949
+ * なので代わりに特に意味のない `createO` の呼び出しをやっている
950
+ */
951
+ createO( // eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-explicit-any
952
+ {},
953
+ /** DO_NOTHING_IT_IS_JUST_CALLED_FOR_TYPE_INFERENCE = */
954
+ true); // ランタイムの `theme(o => [...])` のインターフェースを構築する
955
+
956
+ return function theme(specFn) {
957
+ // styled-components のテンプレートに埋め込める関数
958
+ return function interpolate({
959
+ theme
960
+ }) {
961
+ if (!isPresent(theme)) {
962
+ // テーマが入っていない場合は復旧不可能なのでエラーにする
963
+ throw noThemeProvider;
964
+ }
820
965
 
821
- return specDescriptor.map(v => v[internalSym].operation(context));
966
+ const internals = [// ユーザーが定義したルール
967
+ ...wrapArray(
968
+ /**
969
+ * こう書いてはいけない
970
+ *
971
+ * ❌
972
+ * ```ts
973
+ * const o = createO(theme)
974
+ * const declaration = spec(o)
975
+ * ```
976
+ *
977
+ * `o` を一時変数に入れてしまうと型 `T` の具象化が行われるので関数内に書く
978
+ */
979
+ specFn(
980
+ /** o = */
981
+ createO(theme))), // 必ず挿入される共通のルール
982
+ transition()].filter(nonBlank);
983
+ return toCSSObjects(internals);
984
+ };
822
985
  };
823
986
  }
824
987