@charcoal-ui/styled 2.3.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 +765 -643
  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 +555 -393
  28. package/dist/index.modern.js.map +1 -1
  29. package/dist/index.module.js +765 -643
  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
package/src/index.ts CHANGED
@@ -1,39 +1,9 @@
1
1
  import { CSSObject, ThemedStyledInterface } from 'styled-components'
2
- import warning from 'warning'
3
- import {
4
- factory,
5
- modifiedFactory,
6
- constFactory,
7
- modifiedArgumentedFactory,
8
- variable,
9
- } from './lib'
10
- import {
11
- EffectType,
12
- CharcoalAbstractTheme as Theme,
13
- Key,
14
- } from '@charcoal-ui/theme'
15
- import {
16
- objectAssign,
17
- unreachable,
18
- ReadonlyArrayConstructor,
19
- objectKeys,
20
- isPresent,
21
- noThemeProvider,
22
- } from './util'
23
- import { columnSystem } from '@charcoal-ui/foundation'
24
- import {
25
- halfLeading,
26
- applyEffect,
27
- applyEffectToGradient,
28
- dur,
29
- gradient,
30
- GradientDirection,
31
- notDisabledSelector,
32
- disabledSelector,
33
- px,
34
- customPropertyToken,
35
- } from '@charcoal-ui/utils'
36
- export { type Modified, type ModifiedArgumented } from './lib'
2
+ import { CharcoalAbstractTheme } from '@charcoal-ui/theme'
3
+ import { ArrayOrSingle, isPresent, noThemeProvider, wrapArray } from './util'
4
+ import { Internal, toCSSObjects } from './internals'
5
+ import createO from './builders/o'
6
+ import transition from './builders/transition'
37
7
  export { default as TokenInjector } from './TokenInjector'
38
8
  export {
39
9
  getThemeSync,
@@ -48,635 +18,6 @@ export {
48
18
  export { defineThemeVariables } from './util'
49
19
  export * from './SetThemeScript'
50
20
 
51
- const colorProperties = ['bg', 'font'] as const
52
- type ColorProperty = typeof colorProperties[number]
53
-
54
- const spacingProperties = ['margin', 'padding'] as const
55
- const spacingDirections = [
56
- 'top',
57
- 'right',
58
- 'bottom',
59
- 'left',
60
- 'vertical',
61
- 'horizontal',
62
- 'all',
63
- ] as const
64
- type SpacingProperty = typeof spacingProperties[number]
65
- type SpacingDirection = typeof spacingDirections[number]
66
-
67
- const fixedProperties = ['width', 'height'] as const
68
- type FixedProperty = typeof fixedProperties[number]
69
-
70
- const borderDirections = ['top', 'right', 'bottom', 'left'] as const
71
- type BorderDirection = typeof borderDirections[number]
72
-
73
- const outlineType = ['focus'] as const
74
- type OutlineType = typeof outlineType[number]
75
-
76
- /**
77
- * `theme(o => [...])` の `o` の部分を構築する
78
- *
79
- * @param theme テーマオブジェクト
80
- * @param isPhantom 型推論のためだけに使う場合にランタイムコストをゼロにするフラグ
81
- */
82
- function builder<T extends Theme>(
83
- theme: {
84
- // factoryの第二引数に入れ込むものだけ明示的に型変数を展開しておくことで型の具象化を遅延する
85
- color: T['color']
86
- gradientColor: T['gradientColor']
87
- border: T['border']
88
- outline: T['outline']
89
- } & Omit<T, 'color' | 'gradientColor' | 'border' | 'outline'>,
90
- isPhantom = false
91
- ) {
92
- if (isPhantom) {
93
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
94
- return {} as never
95
- }
96
- const colors = objectKeys(theme.color)
97
- const effects = objectKeys(theme.effect)
98
-
99
- // 色
100
- const gradientColors = objectKeys(theme.gradientColor)
101
- const colorCss = createColorCss(theme)
102
- const gradientColorCss = createGradientColorCss(theme)
103
- const colorObject = constFactory(
104
- {},
105
- {
106
- bg: objectAssign(
107
- factory({}, colors, (color) =>
108
- modifiedFactory(effects, (modifiers) =>
109
- colorCss('bg', color, modifiers)
110
- )
111
- ),
112
- factory(
113
- {},
114
- gradientColors,
115
- (color) => (direction: GradientDirection) =>
116
- modifiedFactory(effects, (modifiers) =>
117
- gradientColorCss(color, modifiers, direction)
118
- )
119
- )
120
- ),
121
- font: factory({}, colors, (color) =>
122
- modifiedFactory(effects, (modifiers) =>
123
- colorCss('font', color, modifiers)
124
- )
125
- ),
126
- }
127
- )
128
-
129
- // タイポグラフィ
130
- const typographyModifiers = [
131
- // TODO
132
- 'monospace',
133
- 'bold',
134
- 'preserveHalfLeading',
135
- ] as const
136
- const typographyCss = createTypographyCss(theme)
137
- const typographyObject = factory(
138
- {},
139
- ['typography'] as const,
140
- (_) => (size: keyof T['typography']['size']) =>
141
- modifiedFactory(typographyModifiers, (modifiers) =>
142
- typographyCss(size, {
143
- preserveHalfLeading: modifiers.includes('preserveHalfLeading'),
144
- monospace: modifiers.includes('monospace'),
145
- bold: modifiers.includes('bold'),
146
- })
147
- )
148
- )
149
-
150
- // スペーシング
151
- const spacingCss = createSpacingCss(theme)
152
- const spacingObject = factory({}, spacingProperties, (spacingProperty) =>
153
- modifiedArgumentedFactory(
154
- spacingDirections,
155
- (modifiers) => spacingCss(spacingProperty, modifiers),
156
- {} as keyof T['spacing'] | 'auto' // 推論のためのメタタイプ
157
- )
158
- )
159
-
160
- // 大きさ
161
- const fixedPxCss = createFixedPxCss(theme)
162
- const fixedColumnCss = createFixedColumnCss(theme)
163
- const fixedRelativeCss = createFixedRelativeCss(theme)
164
- const fixedObject = factory({}, fixedProperties, (property) =>
165
- constFactory(
166
- {},
167
- {
168
- px: (size: keyof T['spacing'] | 'auto') => fixedPxCss(property, size),
169
- column: (span: number) => fixedColumnCss(property, span),
170
- auto: fixedRelativeCss(property, 'auto'),
171
- full: fixedRelativeCss(property, '100%'),
172
- }
173
- )
174
- )
175
-
176
- // 要素へのエフェクト (etc: 透過)
177
- const elementEffectCss = createElementEffectCss(theme)
178
- const elementEffectObject = modifiedFactory(
179
- objectKeys(theme.elementEffect),
180
- (modifiers) => elementEffectCss(modifiers)
181
- )
182
-
183
- // ボーダー
184
- const borderCss = createBorderCss(theme)
185
- const borderObject = constFactory(
186
- {},
187
- {
188
- border: factory({}, objectKeys(theme.border), (variant) =>
189
- modifiedFactory(borderDirections, (modifiers) =>
190
- borderCss(variant, modifiers)
191
- )
192
- ),
193
- }
194
- )
195
-
196
- // 角丸
197
- const borderRadiusCss = createBorderRadiusCss(theme)
198
- const borderRadiusObject = constFactory(
199
- {},
200
- {
201
- borderRadius: (radius: keyof T['borderRadius']) =>
202
- borderRadiusCss(radius),
203
- }
204
- )
205
-
206
- // アウトライン
207
- const outlineCss = createOutlineColorCss(theme)
208
- const outlineObject = constFactory(
209
- {},
210
- {
211
- outline: factory({}, objectKeys(theme.outline), (variant) =>
212
- modifiedFactory(outlineType, (modifiers) =>
213
- outlineCss(variant, modifiers)
214
- )
215
- ),
216
- }
217
- )
218
-
219
- return objectAssign(
220
- colorObject,
221
- typographyObject,
222
- spacingObject,
223
- fixedObject,
224
- elementEffectObject,
225
- borderObject,
226
- borderRadiusObject,
227
- outlineObject
228
- )
229
- }
230
-
231
- function targetProperty(target: ColorProperty) {
232
- return target === 'bg' ? 'background-color' : 'color'
233
- }
234
-
235
- function isSupportedEffect(effect: Key): effect is EffectType {
236
- return ['hover', 'press', 'disabled'].includes(effect as string)
237
- }
238
-
239
- function onEffectPseudo(effect: EffectType, css: CSSObject) {
240
- return effect === 'hover'
241
- ? { '&:hover': { [notDisabledSelector]: css } }
242
- : effect === 'press'
243
- ? { '&:active': { [notDisabledSelector]: css } }
244
- : // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
245
- effect === 'disabled'
246
- ? { [disabledSelector]: css }
247
- : unreachable(effect)
248
- }
249
-
250
- const createColorCss =
251
- <T extends Theme>(_theme: T) =>
252
- (
253
- target: ColorProperty,
254
- color: keyof T['color'],
255
- effects: readonly (keyof T['effect'])[] = []
256
- ): Internal =>
257
- internal(
258
- () => ({
259
- [targetProperty(target)]: variable(
260
- customPropertyToken(color.toString())
261
- ),
262
- ...effects.filter(isSupportedEffect).reduce<CSSObject>(
263
- (acc, effect) => ({
264
- ...acc,
265
- ...onEffectPseudo(effect, {
266
- [targetProperty(target)]: variable(
267
- customPropertyToken(color.toString(), [effect])
268
- ),
269
- }),
270
- }),
271
- {}
272
- ),
273
- }),
274
- effects.length > 0
275
- ? target === 'font'
276
- ? {
277
- colorTransition: true,
278
- }
279
- : {
280
- backgroundColorTransition: true,
281
- }
282
- : {}
283
- )
284
-
285
- // TODO: deprecate
286
- const TRANSITION_DURATION = 0.2
287
-
288
- const createGradientColorCss =
289
- <T extends Theme>(theme: T) =>
290
- (
291
- color: keyof T['gradientColor'],
292
- effects: readonly (keyof T['effect'])[] = [],
293
- direction: GradientDirection
294
- ): Internal => {
295
- const toLinearGradient = gradient(direction)
296
- return internal((context) => {
297
- const optimized = !useHalfLeadingCanceller(context)
298
- const duration = dur(TRANSITION_DURATION)
299
- if (optimized && effects.length > 0) {
300
- return {
301
- position: 'relative',
302
- zIndex: 0,
303
- overflow: 'hidden',
304
- ...effects.filter(isSupportedEffect).reduce<CSSObject>(
305
- (acc, effect) => ({
306
- ...acc,
307
- '&::before': {
308
- zIndex: -1,
309
- ...overlayElement,
310
- transition: `${duration} background-color`,
311
- },
312
- '&::after': {
313
- zIndex: -2,
314
- ...overlayElement,
315
- ...toLinearGradient(theme.gradientColor[color]),
316
- },
317
- ...onEffectPseudo(effect, {
318
- '&::before': {
319
- backgroundColor: applyEffect(
320
- null,
321
- theme.effect[effect] ?? []
322
- ),
323
- },
324
- }),
325
- }),
326
- {}
327
- ),
328
- }
329
- } else {
330
- warning(
331
- effects.length === 0,
332
- // eslint-disable-next-line max-len
333
- `'Transition' will not be applied. You can get around this by specifying 'preserveHalfLeading' or both 'padding' and 'typograpy'.`
334
- )
335
- return {
336
- ...toLinearGradient(theme.gradientColor[color]),
337
- ...effects.filter(isSupportedEffect).reduce<CSSObject>(
338
- (acc, effect) => ({
339
- ...acc,
340
- ...onEffectPseudo(effect, {
341
- ...toLinearGradient(
342
- applyEffectToGradient(theme.effect[effect] ?? [])(
343
- theme.gradientColor[color]
344
- )
345
- ),
346
- }),
347
- }),
348
- {}
349
- ),
350
- }
351
- }
352
- })
353
- }
354
-
355
- /**
356
- * @see https://developer.mozilla.org/ja/docs/Web/CSS/:focus-visible#selectively_showing_the_focus_indicator
357
- */
358
- const onFocus = (css: CSSObject) => ({
359
- [notDisabledSelector]: {
360
- '&:focus, &:active': {
361
- outline: 'none',
362
- ...css,
363
- },
364
-
365
- '&:focus:not(:focus-visible), &:active:not(:focus-visible)': {
366
- outline: 'none',
367
- },
368
-
369
- '&:focus-visible': {
370
- outline: 'none',
371
- ...css,
372
- },
373
- },
374
- })
375
-
376
- const outlineCss = (weight: number, color: string) => ({
377
- boxShadow: `0 0 0 ${px(weight)} ${color}`,
378
- })
379
-
380
- const createOutlineColorCss =
381
- <T extends Theme>(theme: T) =>
382
- (
383
- variant: keyof T['outline'],
384
- modifiers: readonly OutlineType[]
385
- ): Internal => {
386
- const weight = theme.outline[variant].weight
387
- const color = theme.outline[variant].color
388
- return internal(
389
- () =>
390
- modifiers.includes('focus')
391
- ? onFocus(outlineCss(weight, color))
392
- : { '&&': { [notDisabledSelector]: outlineCss(weight, color) } },
393
- {
394
- boxShadowTransition: true,
395
- }
396
- )
397
- }
398
-
399
- const overlayElement: CSSObject = {
400
- content: "''",
401
- display: 'block',
402
- position: 'absolute',
403
- width: '100%',
404
- height: '100%',
405
- top: 0,
406
- left: 0,
407
- }
408
-
409
- // half-leadingをキャンセルするとき && 垂直方向のpaddingが無い時
410
- // -> before/afterを入れる
411
- const useHalfLeadingCanceller = ({
412
- cancelHalfLeadingPx,
413
- hasVerticalPadding = false,
414
- }: Context) => cancelHalfLeadingPx !== undefined && !hasVerticalPadding
415
-
416
- const createTypographyCss =
417
- <T extends Theme>(theme: T) =>
418
- (
419
- size: keyof T['typography']['size'],
420
- options: {
421
- preserveHalfLeading?: boolean
422
- monospace?: boolean
423
- bold?: boolean
424
- } = {}
425
- ): Internal => {
426
- const {
427
- preserveHalfLeading = false,
428
- monospace = false,
429
- bold = false,
430
- } = options
431
- const descriptor = theme.typography.size[size]
432
- const margin = -halfLeading(descriptor)
433
-
434
- return internal(
435
- (context) => ({
436
- fontSize: px(descriptor.fontSize),
437
- lineHeight: px(descriptor.lineHeight),
438
- ...(monospace && {
439
- fontFamily: 'monospace',
440
- }),
441
- ...(bold && {
442
- fontWeight: 'bold',
443
- }),
444
- ...(useHalfLeadingCanceller(context) && {
445
- // prevent margin collapsing
446
- display: 'flow-root',
447
- // cancel half-leading with negative margin
448
- '&::before': {
449
- ...leadingCancel,
450
- marginTop: px(margin),
451
- },
452
- '&::after': {
453
- ...leadingCancel,
454
- marginBottom: px(margin),
455
- },
456
- }),
457
- }),
458
- !preserveHalfLeading
459
- ? {
460
- cancelHalfLeadingPx: margin,
461
- }
462
- : {}
463
- )
464
- }
465
-
466
- const leadingCancel: CSSObject = {
467
- display: 'block',
468
- width: 0,
469
- height: 0,
470
- content: `''`,
471
- }
472
-
473
- function spacingProperty(
474
- property: SpacingProperty,
475
- direction: 'top' | 'right' | 'bottom' | 'left'
476
- ) {
477
- return `${property}-${direction}`
478
- }
479
-
480
- const createSpacingCss =
481
- <T extends Theme>(theme: { spacing: T['spacing'] }) =>
482
- (
483
- property: SpacingProperty,
484
- modifiers: readonly [SpacingDirection, keyof T['spacing'] | 'auto'][]
485
- ): Internal => {
486
- const { top, right, bottom, left } = modifiers.reduce(
487
- (acc, [direction, size]) => {
488
- if (direction === 'all') {
489
- acc.top = size
490
- acc.right = size
491
- acc.bottom = size
492
- acc.left = size
493
- } else if (direction === 'vertical') {
494
- acc.top = size
495
- acc.bottom = size
496
- } else if (direction === 'horizontal') {
497
- acc.right = size
498
- acc.left = size
499
- } else {
500
- acc[direction] = size
501
- }
502
- return acc
503
- },
504
- {} as Partial<
505
- Record<'top' | 'right' | 'bottom' | 'left', keyof T['spacing'] | 'auto'>
506
- >
507
- )
508
-
509
- const hasVerticalPadding =
510
- property === 'padding' &&
511
- top !== undefined &&
512
- bottom !== undefined &&
513
- top !== 'auto' &&
514
- bottom !== 'auto'
515
-
516
- return internal(
517
- ({ cancelHalfLeadingPx = 0 }) => ({
518
- ...(top !== undefined && {
519
- [spacingProperty(property, 'top')]:
520
- top === 'auto'
521
- ? 'auto'
522
- : px(
523
- theme.spacing[top] +
524
- (hasVerticalPadding ? cancelHalfLeadingPx : 0)
525
- ),
526
- }),
527
- ...(bottom !== undefined && {
528
- [spacingProperty(property, 'bottom')]:
529
- bottom === 'auto'
530
- ? 'auto'
531
- : px(
532
- theme.spacing[bottom] +
533
- (hasVerticalPadding ? cancelHalfLeadingPx : 0)
534
- ),
535
- }),
536
- ...(right !== undefined && {
537
- [spacingProperty(property, 'right')]:
538
- right === 'auto' ? 'auto' : px(theme.spacing[right]),
539
- }),
540
- ...(left !== undefined && {
541
- [spacingProperty(property, 'left')]:
542
- left === 'auto' ? 'auto' : px(theme.spacing[left]),
543
- }),
544
- }),
545
- hasVerticalPadding ? { hasVerticalPadding: true } : {}
546
- )
547
- }
548
-
549
- const createFixedPxCss =
550
- <T extends Theme>(theme: T) =>
551
- (property: FixedProperty, size: keyof T['spacing'] | 'auto'): Internal =>
552
- internal(() => ({
553
- [property]: size === 'auto' ? 'auto' : px(theme.spacing[size]),
554
- }))
555
-
556
- const createFixedRelativeCss =
557
- <T extends Theme>(_theme: T) =>
558
- (property: FixedProperty, amount: '100%' | 'auto'): Internal =>
559
- internal(() => ({
560
- [property]: amount,
561
- }))
562
-
563
- const createFixedColumnCss =
564
- <T extends Theme>(theme: T) =>
565
- (property: FixedProperty, span: number): Internal =>
566
- internal(() => ({
567
- [property]: px(
568
- columnSystem(span, theme.grid.unit.column, theme.grid.unit.gutter)
569
- ),
570
- }))
571
-
572
- const createElementEffectCss =
573
- <T extends Theme, TElementEffect extends T['elementEffect']>(theme: {
574
- elementEffect: TElementEffect
575
- }) =>
576
- (effects: readonly (keyof TElementEffect)[] = []): Internal =>
577
- internal(() =>
578
- effects.filter(isSupportedEffect).reduce<CSSObject>(
579
- (acc, effect) => ({
580
- ...acc,
581
- ...onEffectPseudo(effect, {
582
- opacity:
583
- !(Array as ReadonlyArrayConstructor).isArray(
584
- theme.elementEffect[effect]
585
- ) && theme.elementEffect[effect]?.type === 'opacity'
586
- ? theme.elementEffect[effect]?.opacity
587
- : unreachable(),
588
- }),
589
- }),
590
- {}
591
- )
592
- )
593
-
594
- function borderProperty(direction: BorderDirection) {
595
- return `border-${direction}`
596
- }
597
-
598
- function borderShorthand(color: string) {
599
- return `solid 1px ${color}`
600
- }
601
-
602
- const createBorderCss =
603
- <T extends Theme>(theme: T) =>
604
- (
605
- variant: keyof T['border'],
606
- directions: readonly BorderDirection[]
607
- ): Internal => {
608
- const all = directions.length === 0
609
- const value = borderShorthand(theme.border[variant].color)
610
- return internal(() => ({
611
- ...(all
612
- ? { border: value }
613
- : directions.reduce<CSSObject>(
614
- (acc, direction) => ({
615
- ...acc,
616
- [borderProperty(direction)]: value,
617
- }),
618
- {}
619
- )),
620
- }))
621
- }
622
-
623
- const createBorderRadiusCss =
624
- <T extends Theme>(theme: T) =>
625
- (size: keyof T['borderRadius']): Internal =>
626
- internal(() => ({
627
- borderRadius: px(theme.borderRadius[size]),
628
- }))
629
-
630
- interface Context {
631
- cancelHalfLeadingPx?: number
632
- hasVerticalPadding?: boolean
633
- boxShadowTransition?: boolean
634
- colorTransition?: boolean
635
- backgroundColorTransition?: boolean
636
- }
637
-
638
- const commonSpec = (_theme: unknown): Internal => {
639
- const duration = dur(TRANSITION_DURATION)
640
- const transition = (property: string[]) => ({
641
- transition: property.map((v) => `${duration} ${v}`).join(', '),
642
- })
643
- return internal(
644
- ({
645
- colorTransition = false,
646
- backgroundColorTransition = false,
647
- boxShadowTransition = false,
648
- }) =>
649
- transition(
650
- [
651
- colorTransition ? 'color' : null,
652
- backgroundColorTransition ? 'background-color' : null,
653
- boxShadowTransition ? 'box-shadow' : null,
654
- ].filter(isPresent)
655
- )
656
- )
657
- }
658
-
659
- const internalSym: unique symbol = Symbol('internal')
660
-
661
- function internal(
662
- operation: (context: Context) => CSSObject,
663
- context: Context = {}
664
- ): Internal {
665
- return {
666
- [internalSym]: {
667
- operation,
668
- context,
669
- },
670
- }
671
- }
672
-
673
- export interface Internal {
674
- [internalSym]: {
675
- operation: (context: Context) => CSSObject
676
- context: Context
677
- }
678
- }
679
-
680
21
  type Blank = null | undefined | false
681
22
 
682
23
  const nonBlank = <T>(value: T): value is T extends Blank ? never : T =>
@@ -685,7 +26,7 @@ const nonBlank = <T>(value: T): value is T extends Blank ? never : T =>
685
26
  /**
686
27
  * `theme(o => [...])` の `theme` ユーティリティを構築する
687
28
  *
688
- * @param _styled styled-componnets の `styled` そのもの (型推論のために用いられる)
29
+ * @param _styled - DEPRECATED: styled-componnets の `styled` そのものを渡すとそれを元に型推論ができる。が、型引数を渡す方が型推論が高速になりやすい
689
30
  *
690
31
  * @example
691
32
  *
@@ -696,47 +37,57 @@ const nonBlank = <T>(value: T): value is T extends Blank ? never : T =>
696
37
  *
697
38
  * const theme = createTheme<DefaultTheme>()
698
39
  */
699
- export function createTheme<T extends Theme>(
40
+ export function createTheme<T extends CharcoalAbstractTheme>(
700
41
  _styled?: ThemedStyledInterface<T>
701
42
  ) {
702
- // `theme(o => [...])` の `o` の部分の型推論のためだけに使う意味のない変数
703
- // Tを型変数のまま渡してcreateThemeが呼ばれるまで型の具象化が行われないようにする
704
- // eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-explicit-any
705
- const _phantomBuilder = builder<T>({} as any, true)
43
+ /**
44
+ * 本当は `type Builder = ReturnType<createO<T>>` みたいな型を作って、それを o の型にしたい。
45
+ * が、styled がつくられた時点の TypeScript ではこういうジェネリクスの使い方ができなかった
46
+ * なので代わりに特に意味のない `createO` の呼び出しをやっている
47
+ */
48
+ const type_O = createO<T>(
49
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-explicit-any
50
+ {} as any,
51
+ /** DO_NOTHING_IT_IS_JUST_CALLED_FOR_TYPE_INFERENCE = */ true
52
+ )
53
+
54
+ type Builder = typeof type_O
55
+
706
56
  // ランタイムの `theme(o => [...])` のインターフェースを構築する
707
- return (
708
- // ユーザー定義
709
- spec: (
710
- o: typeof _phantomBuilder
711
- ) => Blank | Internal | (Blank | Internal)[]
712
- ): ThemeProp<T> =>
713
- ({ theme }) => {
57
+ return function theme(
58
+ specFn: (o: Builder) => ArrayOrSingle<Internal | Blank>
59
+ ): ThemeProp<T> {
60
+ // styled-components のテンプレートに埋め込める関数
61
+ return function interpolate({ theme }) {
714
62
  if (!isPresent(theme)) {
715
63
  // テーマが入っていない場合は復旧不可能なのでエラーにする
716
64
  throw noThemeProvider
717
65
  }
718
- // styled-componentsのランタイムから受け取ったthemeオブジェクトをbuilderに食わせて`o`をつくる
719
- // さらに、ユーザー定義にbuilderが構築した`o`を食わせる
720
- // (`o`を一時変数に入れてしまうと型Tの具象化が行われるので関数合成を優先する)
721
- const rawSpecDescriptor = spec(builder(theme))
722
- // ユーザー定義の配列を整形
723
- const specDescriptor = [
724
- ...(Array.isArray(rawSpecDescriptor)
725
- ? rawSpecDescriptor
726
- : [rawSpecDescriptor]),
727
- commonSpec(theme),
66
+
67
+ const internals = [
68
+ // ユーザーが定義したルール
69
+ ...wrapArray(
70
+ /**
71
+ * こう書いてはいけない
72
+ *
73
+ *
74
+ * ```ts
75
+ * const o = createO(theme)
76
+ * const declaration = spec(o)
77
+ * ```
78
+ *
79
+ * `o` を一時変数に入れてしまうと型 `T` の具象化が行われるので関数内に書く
80
+ */
81
+ specFn(/** o = */ createO(theme))
82
+ ),
83
+
84
+ // 必ず挿入される共通のルール
85
+ transition(theme),
728
86
  ].filter(nonBlank)
729
87
 
730
- // 1パス目
731
- // 全ユーザー定義を舐めて相互に影響し合う定義をチェックし、その結果(コンテキスト)を取得
732
- const context = specDescriptor.reduce<Context>(
733
- (acc, v) => ({ ...acc, ...v[internalSym].context }),
734
- {}
735
- )
736
- // 2パス目
737
- // コンテキストを見ながら最適化されたCSSを構築
738
- return specDescriptor.map((v) => v[internalSym].operation(context))
88
+ return toCSSObjects(internals)
739
89
  }
90
+ }
740
91
  }
741
92
 
742
93
  export type ThemeProp<T> = ({