material-inspired-component-library 8.0.4 → 8.1.1

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 (80) hide show
  1. package/.claude/settings.local.json +4 -1
  2. package/CLAUDE.md +53 -0
  3. package/README.md +4 -0
  4. package/components/alert/index.scss +2 -2
  5. package/components/appbar/index.scss +2 -2
  6. package/components/bottomsheet/index.scss +3 -3
  7. package/components/button/README.md +68 -10
  8. package/components/button/index.scss +99 -179
  9. package/components/button/index.ts +4 -4
  10. package/components/card/index.scss +3 -3
  11. package/components/checkbox/index.scss +2 -2
  12. package/components/datepicker/index.scss +3 -3
  13. package/components/dialog/index.scss +3 -3
  14. package/components/iconbutton/README.md +55 -9
  15. package/components/iconbutton/index.scss +109 -167
  16. package/components/iconbutton/index.ts +4 -4
  17. package/components/list/index.scss +8 -8
  18. package/components/menu/index.scss +3 -3
  19. package/components/navigationrail/README.md +2 -1
  20. package/components/navigationrail/index.scss +4 -4
  21. package/components/radio/index.scss +2 -2
  22. package/components/select/index.scss +2 -2
  23. package/components/shapes/README.md +94 -0
  24. package/components/{shape → shapes}/_paths.generated.scss +0 -6
  25. package/components/{shape → shapes}/index.scss +10 -10
  26. package/components/{shape → shapes}/master.scss +3 -3
  27. package/components/sidesheet/index.scss +3 -3
  28. package/components/slider/index.scss +5 -5
  29. package/components/snackbar/index.scss +2 -2
  30. package/components/switch/index.scss +2 -2
  31. package/components/textfield/index.scss +2 -2
  32. package/components/timepicker/index.scss +3 -3
  33. package/dist/bottomsheet.js +1 -1
  34. package/dist/button.css +1 -1
  35. package/dist/button.js +1 -1
  36. package/dist/card.css +1 -1
  37. package/dist/checkbox.js +1 -1
  38. package/dist/components/button/index.d.ts +1 -1
  39. package/dist/components/iconbutton/index.d.ts +1 -1
  40. package/dist/datepicker.js +1 -1
  41. package/dist/iconbutton.css +1 -1
  42. package/dist/iconbutton.js +1 -1
  43. package/dist/list.css +1 -1
  44. package/dist/list.js +1 -1
  45. package/dist/menu.js +1 -1
  46. package/dist/micl.css +1 -1
  47. package/dist/micl.js +1 -1
  48. package/dist/navigationrail.css +1 -1
  49. package/dist/navigationrail.js +1 -1
  50. package/dist/progressindicator.js +1 -1
  51. package/dist/shapes.css +1 -0
  52. package/dist/slider.js +1 -1
  53. package/dist/snackbar.js +1 -1
  54. package/dist/stepper.js +1 -1
  55. package/dist/textfield.js +1 -1
  56. package/dist/timepicker.js +1 -1
  57. package/docs/button.html +18 -17
  58. package/docs/iconbutton.html +12 -10
  59. package/docs/index.html +104 -89
  60. package/docs/menu.html +1 -1
  61. package/docs/micl.css +1 -1
  62. package/docs/micl.js +1 -1
  63. package/docs/shapes.css +1 -0
  64. package/docs/shapes.html +49 -40
  65. package/foundations/layout/index.scss +2 -2
  66. package/package.json +2 -1
  67. package/styles/elevation.scss +10 -7
  68. package/styles/motion.scss +12 -19
  69. package/styles/{shapes.scss → shape.scss} +6 -4
  70. package/styles/statelayer.scss +32 -45
  71. package/styles/typography.scss +4 -5
  72. package/styles.scss +1 -4
  73. package/tools/shapes/check.mjs +1 -1
  74. package/tools/shapes/generate.mjs +2 -8
  75. package/webpack.config.js +60 -5
  76. package/components/shape/README.md +0 -103
  77. package/dist/shape.css +0 -1
  78. package/docs/shape.css +0 -1
  79. /package/dist/{shape.js → shapes.js} +0 -0
  80. /package/docs/{shape.js → shapes.js} +0 -0
package/docs/shapes.html CHANGED
@@ -11,16 +11,24 @@
11
11
  <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:FILL@0..1&icon_names=content_copy,dark_mode,home&display=block">
12
12
  <link rel="stylesheet" href="themes/airblue/theme.css" id="theme-link">
13
13
  <link rel="stylesheet" href="micl.css">
14
- <link rel="stylesheet" href="shape.css">
14
+ <link rel="stylesheet" href="shapes.css">
15
15
  <link rel="stylesheet" href="docs.css">
16
16
  <style>
17
- figure {
18
- margin: 24px;
17
+ .shapes {
18
+ display: flex;
19
+ flex-direction: column;
20
+ gap: 16px;
21
+ margin-inline-end: 16px;
19
22
  }
20
- .shape-primary {
21
- --micl-shape-background-color: var(--md-sys-color-primary-container);
22
- block-size: 96px;
23
- inline-size: 96px;
23
+ .buttons {
24
+ display: flex;
25
+ align-content: flex-start;
26
+ flex-wrap: wrap;
27
+ gap: 8px;
28
+ }
29
+ .micl-shapes {
30
+ block-size: 140px;
31
+ inline-size: 140px;
24
32
  }
25
33
  </style>
26
34
  </head>
@@ -42,17 +50,20 @@
42
50
  <div class="micl-pane__column">
43
51
  <div class="micl-card-elevated">
44
52
  <div class="micl-card__content" style="display:flex">
45
- <svg class="micl-shape micl-shape-square" viewBox="-10 0 120 100" style="inline-size:120px;block-size:100px"><path /></svg>
46
- <svg class="micl-shape micl-shape-square" viewBox="-10 0 120 100" style="inline-size:120px;block-size:100px">
47
- <defs>
48
- <pattern id="photo-1" patternUnits="userSpaceOnUse" x="-10" y="0" width="120" height="100">
49
- <image href="https://upload.wikimedia.org/wikipedia/commons/thumb/c/c0/A_Brouhot_car_in_Paris%2C_1910.jpg/330px-A_Brouhot_car_in_Paris%2C_1910.jpg?utm_source=commons.wikimedia.org&utm_campaign=index&utm_content=thumbnail" width="120" height="100" preserveAspectRatio="xMidYMid slice" />
50
- </pattern>
51
- </defs>
52
- <path fill="url(#photo-1)" />
53
- </svg>
54
-
55
- <div style="display:flex;gap:8px;align-self:center;flex-wrap:wrap">
53
+ <div class="shapes">
54
+ <svg class="micl-shapes micl-shapes--square" viewBox="-10 0 120 100"><path /></svg>
55
+ <svg class="micl-shapes micl-shapes--square micl-shapes--outlined" viewBox="-10 0 120 100"><path /></svg>
56
+ <svg class="micl-shapes micl-shapes--square" viewBox="-10 0 120 100">
57
+ <defs>
58
+ <pattern id="photo-1" patternUnits="userSpaceOnUse" x="-10" y="0" width="120" height="100">
59
+ <image href="https://upload.wikimedia.org/wikipedia/commons/thumb/7/71/Sibirischer_tiger_de_edit02.jpg/330px-Sibirischer_tiger_de_edit02.jpg" width="120" height="100" preserveAspectRatio="xMidYMid slice" />
60
+ </pattern>
61
+ </defs>
62
+ <path fill="url(#photo-1)" />
63
+ </svg>
64
+ <svg class="micl-shapes micl-shapes--square micl-shapes--shadowed" viewBox="-10 0 120 100" style="fill:var(--md-sys-color-tertiary)"><path /></svg>
65
+ </div>
66
+ <div class="buttons">
56
67
  <button type="button" data-target="circle" class="micl-button-outlined-s">Circle</button>
57
68
  <button type="button" data-target="square" class="micl-button-outlined-s">Square</button>
58
69
  <button type="button" data-target="slanted" class="micl-button-outlined-s">Slanted</button>
@@ -94,14 +105,14 @@
94
105
  </div>
95
106
  </div>
96
107
  </section>
97
- <section class="micl-pane">
108
+ <section class="micl-pane" style="min-inline-size:328px">
98
109
  <div class="micl-card-filled" style="margin-top:8px">
99
110
  <div class="micl-card__headline-s">
100
111
  <h2>Code example</h2>
101
112
  <button type="button" id="copycode" class="micl-iconbutton-standard-s material-symbols-outlined" aria-label="Copy code">content_copy</button>
102
113
  </div>
103
114
  <div class="micl-card__content docs-code">
104
- <pre tabindex="-1"><code>&lt;hr class="micl-divider"&gt;</code></pre>
115
+ <pre tabindex="-1"><code>&lt;svg class="micl-shapes micl-shapes--square" viewBox="-10 0 120 100"&gt;&lt;path /&gt;&lt;/svg&gt;</code></pre>
105
116
  </div>
106
117
  </div>
107
118
  </section>
@@ -111,35 +122,33 @@
111
122
  <script src="docs.js"></script>
112
123
  <script>
113
124
  const SHAPES = [
114
- 'circle', 'square', 'slanted', 'arch', 'semicircle',
115
- 'oval', 'pill', 'triangle', 'arrow', 'fan',
116
- 'diamond', 'clamshell', 'pentagon', 'gem', 'very-sunny',
117
- 'sunny', 'cookie-4', 'cookie-6', 'cookie-7', 'cookie-9',
118
- 'cookie-12', 'clover-4', 'clover-8', 'burst', 'soft-burst',
119
- 'boom', 'soft-boom', 'flower', 'puffy', 'puffy-diamond',
125
+ 'circle', 'square', 'slanted', 'arch', 'semicircle', 'oval', 'pill', 'triangle',
126
+ 'arrow', 'fan', 'diamond', 'clamshell', 'pentagon', 'gem', 'very-sunny', 'sunny',
127
+ 'cookie-4', 'cookie-6', 'cookie-7', 'cookie-9', 'cookie-12', 'clover-4', 'clover-8',
128
+ 'burst', 'soft-burst', 'boom', 'soft-boom', 'flower', 'puffy', 'puffy-diamond',
120
129
  'ghost-ish', 'pixel-circle', 'pixel-triangle', 'bun', 'heart'
121
130
  ];
122
131
 
123
- document.querySelector('.micl-shape').addEventListener('morph', event =>
124
- {
125
- const el = event.currentTarget;
126
- el.style.setProperty('--morph-duration', `${event.detail.duration}ms`);
127
- if (event.detail.easing) {
128
- el.style.setProperty('--morph-easing', event.detail.easing);
129
- }
130
- SHAPES.forEach(shape => el.classList.remove(`micl-shape-${shape}`));
131
- el.classList.add(`micl-shape-${event.detail.shape}`);
132
+ document.querySelectorAll('.micl-shapes').forEach(el => {
133
+ el.addEventListener('morph', event => {
134
+ const target = event.currentTarget;
135
+ target.style.setProperty('--micl-shapes-morph-duration', `${event.detail.duration}ms`);
136
+ if (event.detail.easing) {
137
+ target.style.setProperty('--micl-shapes-morph-easing', event.detail.easing);
138
+ }
139
+ SHAPES.forEach(shape => target.classList.remove(`micl-shapes--${shape}`));
140
+ target.classList.add(`micl-shapes--${event.detail.shape}`);
141
+ });
132
142
  });
133
143
 
134
- document.querySelectorAll('button[data-target]').forEach(button =>
135
- {
144
+ document.querySelectorAll('button[data-target]').forEach(button => {
136
145
  button.addEventListener('click', () => {
137
- document.querySelectorAll('.micl-shape').forEach(el => {
146
+ document.querySelectorAll('.micl-shapes').forEach(el => {
138
147
  el.dispatchEvent(new CustomEvent('morph', {
139
148
  detail: {
140
- shape: button.dataset.target,
149
+ shape : button.dataset.target,
141
150
  duration: 2000,
142
- easing: 'cubic-bezier(0.34, 1.56, 0.64, 1)'
151
+ easing : 'cubic-bezier(0.34, 1.56, 0.64, 1)'
143
152
  }
144
153
  }));
145
154
  });
@@ -19,9 +19,9 @@
19
19
  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20
20
  // SOFTWARE.
21
21
 
22
- @use '../../styles/shapes';
22
+ @use '../../styles/shape';
23
23
 
24
- @include shapes.corner('medium');
24
+ @include shape.corner('medium');
25
25
 
26
26
  $md-sys-window-compact-max: 599px;
27
27
  $md-sys-window-medium-min: 600px;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "material-inspired-component-library",
3
- "version": "8.0.4",
3
+ "version": "8.1.1",
4
4
  "description": "The Material-Inspired Component Library (MICL) offers a collection of beautifully crafted components leveraging native HTML markup, designed to align with the Material Design 3 guidelines.",
5
5
  "author": "MICL Hermana <micl.hermana@proton.me> (https://github.com/henkpb/micl)",
6
6
  "license": "MIT",
@@ -31,6 +31,7 @@
31
31
  "navigation rail",
32
32
  "radio button",
33
33
  "select",
34
+ "shapes",
34
35
  "sidesheet",
35
36
  "slider",
36
37
  "snackbar",
@@ -22,9 +22,9 @@
22
22
  @use 'sass:map';
23
23
 
24
24
  //
25
- // Set $master: true (via @use ... with) to install all six levels in a single :root block —
26
- // used by the master bundle. When false (default), components opt into individual levels via
27
- // @include level($n), so per-component CSS bundles only carry the levels they actually reference.
25
+ // Set $master: true (@use 'styles/elevation' with ($master: true)) to install all six levels in
26
+ // a single body block — used by the master bundle. When false (default), components opt into
27
+ // individual levels via @include level($n).
28
28
  //
29
29
  $master: false !default;
30
30
 
@@ -54,16 +54,19 @@ $_levels: (
54
54
 
55
55
  @if $master {
56
56
  body {
57
- @each $n, $v in $_levels {
58
- --md-sys-elevation-level#{$n}: #{$v};
57
+ @each $name, $value in $_levels {
58
+ --md-sys-elevation-level#{$name}: #{$value};
59
59
  }
60
60
  }
61
61
  }
62
62
 
63
- @mixin level($n) {
63
+ @mixin level($name) {
64
64
  @if not $master {
65
+ @if not map.has-key($_levels, $name) {
66
+ @error "Unknown elevation level '#{$name}'. Available: #{map.keys($_levels)}.";
67
+ }
65
68
  body {
66
- --md-sys-elevation-level#{$n}: #{map.get($_levels, $n)};
69
+ --md-sys-elevation-level#{$name}: #{map.get($_levels, $name)};
67
70
  }
68
71
  }
69
72
  }
@@ -22,19 +22,12 @@
22
22
  @use 'sass:map';
23
23
 
24
24
  //
25
- // Set $master: true (via @use ... with) to install every duration token + path on :root —
26
- // used by the master bundle. When false (default), components opt into individual duration
27
- // tokens via @include duration('medium2'), so per-component CSS bundles only carry what they
28
- // reference via var(--md-sys-motion-duration-...).
25
+ // Set $master: true (via @use 'styles/motion' with ($master: true)) to install every duration
26
+ // token + path on :root — used by the master bundle. When false (default), components opt into
27
+ // individual duration tokens via @include duration('medium2').
29
28
  //
30
29
  $master: false !default;
31
30
 
32
- //
33
- // Project-tuned cubic-bezier approximations of M3 spring-based motion. M3 itself defines
34
- // fast/default/slow × spatial/effects motion as spring physics (damping/stiffness pairs),
35
- // but CSS transitions don't have a native spring() timing function, so these bezier curves
36
- // stand in. Y values >1 in the spatial easings produce the characteristic M3 Expressive overshoot.
37
- //
38
31
  $md-sys-motion-expressive-fast-spatial: cubic-bezier(0.42, 1.67, 0.21, 0.90);
39
32
  $md-sys-motion-expressive-fast-spatial-duration: 350ms;
40
33
  $md-sys-motion-expressive-fast-effects: cubic-bezier(0.31, 0.94, 0.34, 1.00);
@@ -110,6 +103,15 @@ $_durations: (
110
103
  extra-long4: $md-sys-motion-duration-extra-long4,
111
104
  );
112
105
 
106
+ @if $master {
107
+ :root {
108
+ @each $name, $value in $_durations {
109
+ --md-sys-motion-duration-#{$name}: #{$value};
110
+ }
111
+ --md-sys-motion-path: #{$md-sys-motion-path};
112
+ }
113
+ }
114
+
113
115
  @mixin duration($name) {
114
116
  @if not $master {
115
117
  @if not map.has-key($_durations, $name) {
@@ -120,12 +122,3 @@ $_durations: (
120
122
  }
121
123
  }
122
124
  }
123
-
124
- @if $master {
125
- :root {
126
- @each $name, $value in $_durations {
127
- --md-sys-motion-duration-#{$name}: #{$value};
128
- }
129
- --md-sys-motion-path: #{$md-sys-motion-path};
130
- }
131
- }
@@ -22,10 +22,9 @@
22
22
  @use 'sass:map';
23
23
 
24
24
  //
25
- // Set $master: true (via @use ... with) to install every corner token in a single body block —
26
- // used by the master bundle. When false (default), components opt into individual tokens via
27
- // @include corner('medium'), so per-component CSS bundles only carry the tokens they actually
28
- // reference.
25
+ // Set $master: true (via @use 'styles/shape' with ($master: true)) to install every corner token
26
+ // in a single root block — used by the master bundle. When false (default), components opt into
27
+ // individual tokens via @include corner('medium').
29
28
  //
30
29
  $master: false !default;
31
30
 
@@ -57,6 +56,9 @@ $_corners: (
57
56
 
58
57
  @mixin corner($name) {
59
58
  @if not $master {
59
+ @if not map.has-key($_corners, $name) {
60
+ @error "Unknown corner rounding '#{$name}'. Available: #{map.keys($_corners)}.";
61
+ }
60
62
  :root {
61
63
  --md-sys-shape-corner-#{$name}: #{map.get($_corners, $name)};
62
64
  }
@@ -22,11 +22,11 @@
22
22
  @use 'sass:map';
23
23
 
24
24
  //
25
- // Set $master: true (via @use ... with) to install every state-layer token, the @property
26
- // registrations, and the ripple keyframes — used by the master bundle. When false (default),
27
- // components opt into individual tokens via @include token('hover-state-layer-opacity'),
28
- // the animatable @property registrations via @include property, and the ripple keyframes via
29
- // @include keyframes so per-component CSS bundles only carry what they actually reference.
25
+ // Set $master: true (via @use 'styles/statelayer' with ($master: true)) to install every
26
+ // state-layer token, the @property registrations, and the ripple keyframes — used by the
27
+ // master bundle. When false (default), components opt into individual tokens via
28
+ // @include token('hover-state-layer-opacity'), the animatable @property registrations via
29
+ // @include property, and the ripple keyframes via @include keyframes.
30
30
  //
31
31
  $master: false !default;
32
32
 
@@ -37,35 +37,50 @@ $_tokens: (
37
37
  focus-state-layer-opacity: 10%,
38
38
  pressed-state-layer-opacity: 10%,
39
39
  dragged-state-layer-opacity: 16%,
40
- // TODO: misnamed vs M3 spec — value 38% matches DisabledLabelTextOpacity / DisabledIconOpacity
41
- // (content fade), but M3's `disabled.state-layer.opacity` is 12% (container background).
42
- // Either rename to --md-sys-state-disabled-opacity (breaking) or split into two tokens.
43
40
  disabled-state-layer-opacity: 38%,
44
41
  focus-indicator-inner-offset: -3px,
45
42
  focus-indicator-outer-offset: 2px,
46
43
  focus-indicator-thickness: 3px,
47
44
  backdrop-opacity: 32%,
48
45
  ripple-opacity-factor: 0.6,
49
- // TODO: 5000ms is well outside M3's motion scale (max DurationExtraLong4 = 1000ms;
50
- // standard ripples ~300ms). Used here as an upper-bound cap on the @keyframes spread,
51
- // not as a perceived animation duration. Consider renaming to ripple-spread-cap or
52
- // aligning with an M3 motion token.
53
46
  ripple-duration: 5000ms,
54
47
  );
55
48
 
49
+ @if $master {
50
+ :root {
51
+ @each $name, $value in $_tokens {
52
+ --md-sys-state-#{$name}: #{$value};
53
+ }
54
+ }
55
+
56
+ @property --statelayer-color {
57
+ syntax: '<color>';
58
+ initial-value: transparent;
59
+ inherits: false;
60
+ }
61
+ @property --statelayer-opacity {
62
+ syntax: '<percentage>';
63
+ initial-value: 0%;
64
+ inherits: false;
65
+ }
66
+
67
+ @keyframes micl-ripple {
68
+ from { background-size: 0%, 100%; }
69
+ to { background-size: 10000%, 100%; }
70
+ }
71
+ }
72
+
56
73
  @mixin token($name) {
57
74
  @if not $master {
75
+ @if not map.has-key($_tokens, $name) {
76
+ @error "Unknown state-layer token '#{$name}'. Available: #{map.keys($_tokens)}.";
77
+ }
58
78
  :root {
59
79
  --md-sys-state-#{$name}: #{map.get($_tokens, $name)};
60
80
  }
61
81
  }
62
82
  }
63
83
 
64
- //
65
- // CSS @property registrations for the ripple animation. Components that animate --statelayer-color
66
- // or --statelayer-opacity (typed-property interpolation) must include this mixin; otherwise the
67
- // variables fall back to untyped strings and the browser cannot transition between values.
68
- //
69
84
  @mixin property {
70
85
  @if not $master {
71
86
  @property --statelayer-color {
@@ -81,10 +96,6 @@ $_tokens: (
81
96
  }
82
97
  }
83
98
 
84
- //
85
- // Ripple keyframes only required by components that actually run the ripple background-size
86
- // animation.
87
- //
88
99
  @mixin keyframes {
89
100
  @if not $master {
90
101
  @keyframes micl-ripple {
@@ -93,27 +104,3 @@ $_tokens: (
93
104
  }
94
105
  }
95
106
  }
96
-
97
- @if $master {
98
- :root {
99
- @each $name, $value in $_tokens {
100
- --md-sys-state-#{$name}: #{$value};
101
- }
102
- }
103
-
104
- @property --statelayer-color {
105
- syntax: '<color>';
106
- initial-value: transparent;
107
- inherits: false;
108
- }
109
- @property --statelayer-opacity {
110
- syntax: '<percentage>';
111
- initial-value: 0%;
112
- inherits: false;
113
- }
114
-
115
- @keyframes micl-ripple {
116
- from { background-size: 0%, 100%; }
117
- to { background-size: 10000%, 100%; }
118
- }
119
- }
@@ -22,11 +22,10 @@
22
22
  @use 'sass:map';
23
23
 
24
24
  //
25
- // Set $master: true (via @use ... with) to install every typescale token, the @supports
26
- // narrow-tracking overrides, and the .md-sys-typescale-* utility classes — used by the
27
- // master bundle. When false (default), components opt into individual typescales via @include
28
- // scale('body-large'), so per-component CSS bundles only carry the typescale tokens they
29
- // actually reference.
25
+ // Set $master: true (@use 'styles/typography' with ($master: true)) to install every typescale
26
+ // token, the @supports narrow-tracking overrides, and the .md-sys-typescale-* utility classes —
27
+ // used by the master bundle. When false (default), components opt into individual typescales via
28
+ // @include scale('body-large').
30
29
  //
31
30
  $master: false !default;
32
31
 
package/styles.scss CHANGED
@@ -19,10 +19,7 @@
19
19
  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20
20
  // SOFTWARE.
21
21
 
22
- // Configured imports first: $master must be set on each module before any
23
- // transitive consumer (e.g. foundations/layout) loads it with defaults,
24
- // otherwise Sass errors with "module already loaded".
25
- @use 'styles/shapes' with ($master: true);
22
+ @use 'styles/shape' with ($master: true);
26
23
  @use 'styles/elevation' with ($master: true);
27
24
  @use 'styles/statelayer' with ($master: true);
28
25
  @use 'styles/typography' with ($master: true);
@@ -24,7 +24,7 @@ import path from 'node:path';
24
24
  import { generatePathsScss, TARGET_PATH } from './generate.mjs';
25
25
 
26
26
  //
27
- // CI staleness gate. Compares the on-disk components/shape/_paths.generated.scss
27
+ // CI staleness gate. Compares the on-disk components/shapes/_paths.generated.scss
28
28
  // against what the generator would produce now. Exits non-zero if they differ,
29
29
  // instructing the developer to run `npm run gen:shapes` and commit the result.
30
30
  //
@@ -25,19 +25,13 @@ import { fileURLToPath } from 'node:url';
25
25
 
26
26
  //
27
27
  // Shape-path generator. Run via `npm run gen:shapes`. Writes
28
- // components/shape/_paths.generated.scss with one map entry per shape; the
28
+ // components/shapes/_paths.generated.scss with one map entry per shape; the
29
29
  // SCSS partial then looks up the path strings without doing any math at
30
30
  // compile time.
31
31
  //
32
- // This file is a faithful port of the Sass math that previously lived in
33
- // styles/shapes.scss / components/shape/index.scss. Numeric output is
34
- // formatted to match Sass's default $number-precision: 10 with trailing
35
- // zeros stripped, so the emitted `d` strings are byte-identical to the
36
- // Sass-rendered ones.
37
- //
38
32
 
39
33
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
40
- const targetPath = path.resolve(__dirname, '../../components/shape/_paths.generated.scss');
34
+ const targetPath = path.resolve(__dirname, '../../components/shapes/_paths.generated.scss');
41
35
 
42
36
  // Sass's default precision is 10 decimal places. Match that, then strip
43
37
  // trailing zeros so e.g. 50.0000000000 → 50, 3.1400000000 → 3.14.
package/webpack.config.js CHANGED
@@ -1,4 +1,5 @@
1
1
  const path = require('path');
2
+ const fs = require('fs');
2
3
  const glob = require('glob');
3
4
  const webpack = require('webpack');
4
5
  const miniCss = require('mini-css-extract-plugin');
@@ -12,9 +13,19 @@ const scssEntries = scssFiles.reduce((entries, filePath) => {
12
13
  // dist-bundle entry that installs all shapes). Only the latter should
13
14
  // be compiled as a webpack entry.
14
15
  const normalized = filePath.replace(/\\/g, '/');
15
- if (normalized.endsWith('components/shape/index.scss')) return entries;
16
+ if (normalized.endsWith('components/shapes/index.scss')) return entries;
16
17
  const componentName = path.dirname(filePath).split('\\').pop();
17
- entries[componentName] = './' + filePath;
18
+
19
+ // Pair each component's stylesheet with its sibling TypeScript handler (if
20
+ // present) so the standalone <component>.js contains the component logic
21
+ // instead of an empty UMD stub. Foundations TS are built via tsEntries.
22
+ const sources = ['./' + filePath];
23
+ const tsSibling = path.dirname(normalized) + '/index.ts';
24
+ if (normalized.startsWith('components/') && fs.existsSync(tsSibling)) {
25
+ sources.push('./' + tsSibling);
26
+ }
27
+
28
+ entries[componentName] = sources.length > 1 ? sources : sources[0];
18
29
  return entries;
19
30
  }, {});
20
31
 
@@ -26,11 +37,14 @@ const tsEntries = glob.sync('./foundations/**/*.ts').reduce((entries, filePath)
26
37
  }, {});
27
38
 
28
39
  module.exports = [{
40
+ // Standalone per-component / per-foundation files. Built first and on its
41
+ // own (clean: true) so the micl bundle below can scope-hoist component
42
+ // modules instead of treating them as entry points.
43
+ name: 'parts',
29
44
  mode: 'production',
30
45
  entry: {
31
46
  ...scssEntries,
32
- ...tsEntries,
33
- micl: ['./styles.scss', './micl.ts']
47
+ ...tsEntries
34
48
  },
35
49
  resolve: {
36
50
  extensions: ['.ts', '.tsx', '.js']
@@ -64,11 +78,52 @@ module.exports = [{
64
78
  filename: '[name].css'
65
79
  })
66
80
  ]
81
+ }, {
82
+ // Full dist bundle. Separate compilation (after 'parts', without cleaning)
83
+ // so component modules imported by micl.ts are concatenated into micl.js.
84
+ name: 'bundle',
85
+ dependencies: ['parts'],
86
+ mode: 'production',
87
+ entry: {
88
+ micl: ['./styles.scss', './micl.ts']
89
+ },
90
+ resolve: {
91
+ extensions: ['.ts', '.tsx', '.js']
92
+ },
93
+ output: {
94
+ path: distDir,
95
+ filename: '[name].js',
96
+ clean: false,
97
+ library: {
98
+ name: 'micl',
99
+ type: 'umd'
100
+ }
101
+ },
102
+ module: {
103
+ rules: [{
104
+ test: /\.scss$/,
105
+ use : [
106
+ miniCss.loader,
107
+ 'css-loader',
108
+ 'postcss-loader',
109
+ 'sass-loader'
110
+ ]
111
+ }, {
112
+ test: /\.tsx?$/,
113
+ use: 'ts-loader',
114
+ exclude: /node_modules/
115
+ }]
116
+ },
117
+ plugins: [
118
+ new miniCss({
119
+ filename: '[name].css'
120
+ })
121
+ ]
67
122
  }, {
68
123
  mode: 'production',
69
124
  entry: {
70
125
  micl: ['./styles.scss', './micl.ts'],
71
- shape: './components/shape/master.scss'
126
+ shapes: './components/shapes/master.scss'
72
127
  },
73
128
  resolve: {
74
129
  extensions: ['.ts', '.tsx', '.js']