@mlola-ui/engine 1.0.7 → 1.0.9

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.
@@ -194,6 +194,11 @@
194
194
  "data-danger": [],
195
195
  "data-highlighted": []
196
196
  },
197
+ "ml-copy-button": {
198
+ "data-status": [
199
+ "error"
200
+ ]
201
+ },
197
202
  "ml-date-picker-panel": {
198
203
  "data-presets": []
199
204
  },
@@ -316,6 +321,18 @@
316
321
  "active"
317
322
  ]
318
323
  },
324
+ "ml-password-input-strength": {
325
+ "data-strength": [
326
+ "medium",
327
+ "strong",
328
+ "weak"
329
+ ]
330
+ },
331
+ "ml-password-input-toggle": {
332
+ "aria-pressed": [
333
+ "true"
334
+ ]
335
+ },
319
336
  "ml-popover": {
320
337
  "data-side": [
321
338
  "bottom",
@@ -438,6 +455,12 @@
438
455
  "sm"
439
456
  ]
440
457
  },
458
+ "ml-spinner": {
459
+ "data-size": [
460
+ "lg",
461
+ "sm"
462
+ ]
463
+ },
441
464
  "ml-status-icon": {
442
465
  "data-status": [
443
466
  "backlog",
@@ -1,4 +1,9 @@
1
1
  /* Generated by @mlola-ui/engine. React and Tailwind are not required. */
2
+ /* Where Mlola sits among a utility framework's layers, by Tailwind's names:
3
+ above its reset, so preflight never unstyles a component, whichever
4
+ stylesheet the app imports first. Without such a framework these layers
5
+ stay empty. */
6
+ @layer theme, base, mlola, components, utilities;
2
7
  @layer mlola.tokens, mlola.foundations, mlola.materials, mlola.recipes, mlola.motion, mlola.accessibility;
3
8
  @import "./tokens.css" layer(mlola.tokens);
4
9
  /* The project theme is a token source too, imported last within the same
@@ -7,9 +7,26 @@
7
7
  display: none !important;
8
8
  }
9
9
 
10
- /* Icons keep their size inside flex rows; a squeezed row shrinks text, not glyphs. */
10
+ /* Icons sit on the text's optical center and keep their size inside flex
11
+ rows; a squeezed row shrinks text, not glyphs. The same rules ship in
12
+ @mlola-ui/icons/glyphs.css for apps that use the icons without the engine. */
11
13
  :where(.mlola-glyph) {
14
+ display: inline-block;
12
15
  flex-shrink: 0;
16
+ overflow: visible;
17
+ vertical-align: -0.125em;
18
+ transform-box: fill-box;
19
+ transform-origin: center;
20
+ }
21
+
22
+ :where(.mlola-glyph[data-optical-size="small"]) {
23
+ --mlola-glyph-stroke: 2;
24
+ --mlola-glyph-optical-scale: 0.94;
25
+ }
26
+
27
+ :where(.mlola-glyph[data-optical-size="large"]) {
28
+ --mlola-glyph-stroke: 1.5;
29
+ --mlola-glyph-optical-scale: 1.03;
13
30
  }
14
31
 
15
32
  .ml-visually-hidden {
@@ -1575,15 +1592,6 @@
1575
1592
  gap: var(--ml-space-1-5);
1576
1593
  }
1577
1594
 
1578
- .ml-button-spinner {
1579
- width: .875rem;
1580
- height: .875rem;
1581
- border: 1.5px solid currentColor;
1582
- border-inline-end-color: transparent;
1583
- border-radius: 50%;
1584
- animation: ml-spin .7s linear infinite;
1585
- }
1586
-
1587
1595
  .ml-button[data-state="success"] {
1588
1596
  background: var(--ml-success);
1589
1597
  border-color: var(--ml-success);
@@ -2771,6 +2779,13 @@
2771
2779
  background: var(--ml-border-subtle);
2772
2780
  }
2773
2781
 
2782
+ /* components/copy-button */
2783
+ /* A copy the browser refused reads as a problem, not as a success. On a
2784
+ filled button the label keeps its own ink; the spoken message carries it. */
2785
+ .ml-copy-button[data-status="error"]:not([data-variant="primary"], [data-variant="danger"]) {
2786
+ color: var(--ml-danger-text);
2787
+ }
2788
+
2774
2789
  /* components/date-picker */
2775
2790
  /* Wide enough for a range in the reader's format. */
2776
2791
  .ml-date-picker {
@@ -3769,6 +3784,82 @@
3769
3784
  color: var(--ml-text-muted);
3770
3785
  }
3771
3786
 
3787
+ /* components/password-input */
3788
+ .ml-password-input {
3789
+ display: grid;
3790
+ gap: var(--ml-space-2);
3791
+ }
3792
+
3793
+ /* While the password shows, the toggle takes full ink: its state is seen,
3794
+ not only announced. */
3795
+ .ml-password-input-toggle[aria-pressed="true"] {
3796
+ color: var(--ml-text);
3797
+ }
3798
+
3799
+ .ml-password-input-strength {
3800
+ display: grid;
3801
+ gap: var(--ml-space-2);
3802
+ color: var(--ml-text-muted);
3803
+ font-size: var(--ml-type-xs);
3804
+ }
3805
+
3806
+ .ml-password-input-strength p {
3807
+ margin: 0;
3808
+ }
3809
+
3810
+ .ml-password-input-strength strong {
3811
+ color: var(--ml-text);
3812
+ font-weight: 600;
3813
+ }
3814
+
3815
+ /* Three segments light up as the password grows. They are marks on the page,
3816
+ so they take the -text roles, which clear 3:1 against it. */
3817
+ .ml-password-input-meter {
3818
+ display: grid;
3819
+ grid-template-columns: repeat(3, 1fr);
3820
+ gap: var(--ml-space-1);
3821
+ }
3822
+
3823
+ .ml-password-input-meter > span {
3824
+ height: 0.25rem;
3825
+ border-radius: var(--ml-radius-pill);
3826
+ background: var(--ml-track);
3827
+ transition: background-color var(--ml-duration-normal) var(--ml-ease-standard);
3828
+ }
3829
+
3830
+ .ml-password-input-strength[data-strength="weak"] .ml-password-input-meter > span:nth-child(1) {
3831
+ background: var(--ml-danger-text);
3832
+ }
3833
+
3834
+ .ml-password-input-strength[data-strength="medium"] .ml-password-input-meter > span:nth-child(-n + 2) {
3835
+ background: var(--ml-warning-text);
3836
+ }
3837
+
3838
+ .ml-password-input-strength[data-strength="strong"] .ml-password-input-meter > span {
3839
+ background: var(--ml-success-text);
3840
+ }
3841
+
3842
+ .ml-password-input-strength[data-strength="weak"] strong {
3843
+ color: var(--ml-danger-text);
3844
+ }
3845
+
3846
+ .ml-password-input-strength[data-strength="strong"] strong {
3847
+ color: var(--ml-success-text);
3848
+ }
3849
+
3850
+ @media (forced-colors: active) {
3851
+ .ml-password-input-meter > span {
3852
+ border: 1px solid CanvasText;
3853
+ background: Canvas;
3854
+ }
3855
+
3856
+ .ml-password-input-strength:not([data-strength="empty"]) .ml-password-input-meter > span:nth-child(1),
3857
+ .ml-password-input-strength:is([data-strength="medium"], [data-strength="strong"]) .ml-password-input-meter > span:nth-child(2),
3858
+ .ml-password-input-strength[data-strength="strong"] .ml-password-input-meter > span:nth-child(3) {
3859
+ background: CanvasText;
3860
+ }
3861
+ }
3862
+
3772
3863
  /* components/popover */
3773
3864
  .ml-popover {
3774
3865
  position: fixed;
@@ -4984,6 +5075,43 @@
4984
5075
  outline-offset: 2px;
4985
5076
  }
4986
5077
 
5078
+ /* components/spinner */
5079
+ /* A ring of the current text color with one bright arc. It is drawn from
5080
+ currentColor alone, so it matches the label beside it in a button, a
5081
+ toast or a line of text, in every theme and mode. */
5082
+ .ml-spinner {
5083
+ display: inline-block;
5084
+ flex: none;
5085
+ width: 1rem;
5086
+ height: 1rem;
5087
+ border: 2px solid color-mix(in oklab, currentColor 22%, transparent);
5088
+ border-top-color: currentColor;
5089
+ border-radius: var(--ml-radius-pill);
5090
+ vertical-align: -0.125em;
5091
+ animation: ml-spin 0.7s linear infinite;
5092
+ }
5093
+
5094
+ .ml-spinner[data-size="sm"] {
5095
+ width: 0.875rem;
5096
+ height: 0.875rem;
5097
+ border-width: 1.5px;
5098
+ }
5099
+
5100
+ .ml-spinner[data-size="lg"] {
5101
+ width: 1.5rem;
5102
+ height: 1.5rem;
5103
+ border-width: 2.5px;
5104
+ }
5105
+
5106
+ /* Under forced colors the ring is the system's text color, and the arc is
5107
+ a real border that stays visible. */
5108
+ @media (forced-colors: active) {
5109
+ .ml-spinner {
5110
+ border-color: CanvasText;
5111
+ border-top-color: Highlight;
5112
+ }
5113
+ }
5114
+
4987
5115
  /* components/status-icon */
4988
5116
  /* Color carries the stage; the glyph carries it without color too. */
4989
5117
  .ml-status-icon {
@@ -5255,9 +5383,12 @@ button.ml-stepper-target:focus-visible {
5255
5383
  background: transparent;
5256
5384
  }
5257
5385
 
5386
+ /* A header cell lines up with its column. Browsers center th by default, which
5387
+ a table's own text-align does not reach. */
5258
5388
  .ml-table th,
5259
5389
  .ml-table-header th,
5260
5390
  .ml-table-head {
5391
+ text-align: start;
5261
5392
  height: 2.5rem;
5262
5393
  padding: var(--ml-space-2) var(--ml-space-4);
5263
5394
  border-bottom: var(--ml-border-width) solid var(--ml-border-subtle);
@@ -6096,21 +6227,6 @@ button.ml-stepper-target:focus-visible {
6096
6227
  color: var(--ml-info-text);
6097
6228
  }
6098
6229
 
6099
- .ml-toast-spinner {
6100
- width: 1rem;
6101
- height: 1rem;
6102
- border: 2px solid var(--ml-track);
6103
- border-top-color: var(--ml-text);
6104
- border-radius: 50%;
6105
- animation: ml-toast-spin 0.7s linear infinite;
6106
- }
6107
-
6108
- @keyframes ml-toast-spin {
6109
- to {
6110
- transform: rotate(1turn);
6111
- }
6112
- }
6113
-
6114
6230
  .ml-toast-content {
6115
6231
  display: grid;
6116
6232
  flex: 1;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mlola-ui/engine",
3
- "version": "1.0.7",
3
+ "version": "1.0.9",
4
4
  "description": "Framework-free CSS, spring easings, and the machine-readable contract for Mlola UI",
5
5
  "type": "module",
6
6
  "sideEffects": [
package/src/color.mjs CHANGED
@@ -117,7 +117,7 @@ export function contrast(foreground, background) {
117
117
  return (Math.max(a, b) + 0.05) / (Math.min(a, b) + 0.05);
118
118
  }
119
119
 
120
- /** Serialise an OKLCH color, gamut-mapped, at stable precision. */
120
+ /** Serialize an OKLCH color, gamut-mapped, at stable precision. */
121
121
  export function formatColor(color) {
122
122
  const mapped = toGamut(color);
123
123
  const chroma = round(mapped.C, 3);
@@ -8,8 +8,6 @@
8
8
  * ledger entry remains.
9
9
  */
10
10
 
11
- export const CURRENT_VERSION = "0.3.0";
12
-
13
11
  export const DEPRECATIONS = [
14
12
  // 1.0 removed data-skin, the shadcn --background/--chart-*/--sidebar-*
15
13
  // aliases, the .ml-button-* class variants and every other duplicate
package/src/palette.mjs CHANGED
@@ -183,7 +183,7 @@ function statusFor(name, mode, neutrals) {
183
183
  * Six categorical colors for charts, apart from the status roles so a series
184
184
  * never reads as "good" or "bad" by accident. The first follows the brand's
185
185
  * hue (blue for a monochrome brand); the rest are the hues furthest from those
186
- * already taken, so neighbours never share a family. Each is solved to 3:1
186
+ * already taken, so neighbors never share a family. Each is solved to 3:1
187
187
  * against the surface (WCAG 1.4.11 for graphics).
188
188
  */
189
189
  const CHART_HUES = [255, 185, 300, 50, 10, 145];
@@ -222,7 +222,7 @@ function hoverFor(fill, foreground, neutrals) {
222
222
  return solveLightness({ L: Math.min(1, Math.max(0, fill.L + away)), C: fill.C, H: fill.H }, foreground, TARGETS.body, away < 0 ? "darker" : "lighter");
223
223
  }
224
224
 
225
- /** Every palette token for one mode, as serialised OKLCH strings. */
225
+ /** Every palette token for one mode, as serialized OKLCH strings. */
226
226
  export function derivePalette(spec, mode = "light") {
227
227
  const neutrals = mode === "dark" ? neutralsDark(spec) : neutralsLight(spec);
228
228
  const primary = primaryFor(spec, mode, neutrals);
package/src/theme.mjs CHANGED
@@ -11,7 +11,7 @@ import { renderSpecCss } from "./theme-css.mjs";
11
11
  * - `inherit` names the canonical theme whose decisions fill anything the
12
12
  * file leaves out.
13
13
  * - `scale` overrides spacing or type steps, and `extend` adds raw custom
14
- * properties, for anything the engine has not modelled yet. Both are
14
+ * properties, for anything the engine has not modeled yet. Both are
15
15
  * emitted after the derived tokens, so they win.
16
16
  *
17
17
  * The spec itself renders through the same function as every canonical theme.