mlola-ui 1.1.23 → 1.1.25

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -62,8 +62,8 @@ that its UI is Mlola:
62
62
  `npx mlola-ui mcp` is that server. It answers from the registry bundled with
63
63
  this CLI, offline: `get_design_rules`, `search_components`, `get_component`,
64
64
  `get_tokens`, `check_markup` (invented classes, wrong `data-*` values, utility
65
- classes, and hand-written colors or faded text in `style` attributes and
66
- `<style>` blocks), `add_components` and `init_project`. For Claude Code
65
+ classes, and hand-written colors, spacing off the `--ml-space-*` scale or
66
+ faded text in `style` attributes and `<style>` blocks), `add_components` and `init_project`. For Claude Code
67
67
  without init:
68
68
 
69
69
  ```sh
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mlola-ui",
3
- "version": "1.1.23",
3
+ "version": "1.1.25",
4
4
  "mcpName": "io.github.mlolahq/mlola-ui",
5
5
  "description": "Source-copy CLI that writes framework-free Mlola UI items into your project",
6
6
  "type": "module",
@@ -2,7 +2,7 @@
2
2
  "$schema": "./schema/registry-index.schema.json",
3
3
  "schemaVersion": 2,
4
4
  "name": "mlola-ui",
5
- "version": "1.1.23",
5
+ "version": "1.1.25",
6
6
  "homepage": "https://ui.mlola.com",
7
7
  "themeContract": {
8
8
  "attribute": "data-theme",
@@ -177,8 +177,8 @@
177
177
  },
178
178
  "registryDependencies": [],
179
179
  "engineDependencies": {
180
- "@mlola-ui/behavior": "^1.1.23",
181
- "@mlola-ui/icons": "^1.1.23"
180
+ "@mlola-ui/behavior": "^1.1.25",
181
+ "@mlola-ui/icons": "^1.1.25"
182
182
  },
183
183
  "options": [
184
184
  {
@@ -239,7 +239,7 @@
239
239
  },
240
240
  "registryDependencies": [],
241
241
  "engineDependencies": {
242
- "@mlola-ui/icons": "^1.1.23"
242
+ "@mlola-ui/icons": "^1.1.25"
243
243
  },
244
244
  "options": [
245
245
  {
@@ -401,7 +401,7 @@
401
401
  },
402
402
  "registryDependencies": [],
403
403
  "engineDependencies": {
404
- "@mlola-ui/icons": "^1.1.23"
404
+ "@mlola-ui/icons": "^1.1.25"
405
405
  },
406
406
  "options": [
407
407
  {
@@ -493,7 +493,7 @@
493
493
  },
494
494
  "registryDependencies": [],
495
495
  "engineDependencies": {
496
- "@mlola-ui/icons": "^1.1.23"
496
+ "@mlola-ui/icons": "^1.1.25"
497
497
  },
498
498
  "options": [],
499
499
  "variants": [],
@@ -546,7 +546,7 @@
546
546
  "spinner"
547
547
  ],
548
548
  "engineDependencies": {
549
- "@mlola-ui/motion": "^1.1.23"
549
+ "@mlola-ui/motion": "^1.1.25"
550
550
  },
551
551
  "options": [
552
552
  {
@@ -714,8 +714,8 @@
714
714
  },
715
715
  "registryDependencies": [],
716
716
  "engineDependencies": {
717
- "@mlola-ui/behavior": "^1.1.23",
718
- "@mlola-ui/icons": "^1.1.23"
717
+ "@mlola-ui/behavior": "^1.1.25",
718
+ "@mlola-ui/icons": "^1.1.25"
719
719
  },
720
720
  "options": [
721
721
  {
@@ -784,7 +784,7 @@
784
784
  },
785
785
  "registryDependencies": [],
786
786
  "engineDependencies": {
787
- "@mlola-ui/icons": "^1.1.23"
787
+ "@mlola-ui/icons": "^1.1.25"
788
788
  },
789
789
  "options": [],
790
790
  "variants": [],
@@ -839,7 +839,7 @@
839
839
  "modal"
840
840
  ],
841
841
  "engineDependencies": {
842
- "@mlola-ui/icons": "^1.1.23"
842
+ "@mlola-ui/icons": "^1.1.25"
843
843
  },
844
844
  "options": [],
845
845
  "variants": [],
@@ -903,8 +903,8 @@
903
903
  },
904
904
  "registryDependencies": [],
905
905
  "engineDependencies": {
906
- "@mlola-ui/behavior": "^1.1.23",
907
- "@mlola-ui/icons": "^1.1.23"
906
+ "@mlola-ui/behavior": "^1.1.25",
907
+ "@mlola-ui/icons": "^1.1.25"
908
908
  },
909
909
  "options": [
910
910
  {
@@ -943,7 +943,7 @@
943
943
  "path": "packages/components/_internal/floating.ts",
944
944
  "target": "{{aliases.components}}/_internal/floating.ts",
945
945
  "type": "registry:ui",
946
- "integrity": "sha256-01wdG2ctOvRfi8+uR2PAO3nANDdhp3eFdsZnyMdFJw4="
946
+ "integrity": "sha256-Yk3QfedtNE5m4oG506sYzf5CGE1yIt8gBzImSkXtMbY="
947
947
  },
948
948
  {
949
949
  "path": "packages/components/_internal/react.ts",
@@ -993,7 +993,7 @@
993
993
  "input"
994
994
  ],
995
995
  "engineDependencies": {
996
- "@mlola-ui/icons": "^1.1.23"
996
+ "@mlola-ui/icons": "^1.1.25"
997
997
  },
998
998
  "options": [],
999
999
  "variants": [],
@@ -1218,8 +1218,8 @@
1218
1218
  },
1219
1219
  "registryDependencies": [],
1220
1220
  "engineDependencies": {
1221
- "@mlola-ui/behavior": "^1.1.23",
1222
- "@mlola-ui/icons": "^1.1.23"
1221
+ "@mlola-ui/behavior": "^1.1.25",
1222
+ "@mlola-ui/icons": "^1.1.25"
1223
1223
  },
1224
1224
  "options": [
1225
1225
  {
@@ -1260,7 +1260,7 @@
1260
1260
  "path": "packages/components/_internal/floating.ts",
1261
1261
  "target": "{{aliases.components}}/_internal/floating.ts",
1262
1262
  "type": "registry:ui",
1263
- "integrity": "sha256-01wdG2ctOvRfi8+uR2PAO3nANDdhp3eFdsZnyMdFJw4="
1263
+ "integrity": "sha256-Yk3QfedtNE5m4oG506sYzf5CGE1yIt8gBzImSkXtMbY="
1264
1264
  },
1265
1265
  {
1266
1266
  "path": "packages/components/_internal/react.ts",
@@ -1370,7 +1370,7 @@
1370
1370
  },
1371
1371
  "registryDependencies": [],
1372
1372
  "engineDependencies": {
1373
- "@mlola-ui/icons": "^1.1.23"
1373
+ "@mlola-ui/icons": "^1.1.25"
1374
1374
  },
1375
1375
  "options": [],
1376
1376
  "variants": [],
@@ -1421,7 +1421,7 @@
1421
1421
  },
1422
1422
  "registryDependencies": [],
1423
1423
  "engineDependencies": {
1424
- "@mlola-ui/behavior": "^1.1.23"
1424
+ "@mlola-ui/behavior": "^1.1.25"
1425
1425
  },
1426
1426
  "options": [
1427
1427
  {
@@ -1459,7 +1459,7 @@
1459
1459
  "path": "packages/components/_internal/floating.ts",
1460
1460
  "target": "{{aliases.components}}/_internal/floating.ts",
1461
1461
  "type": "registry:ui",
1462
- "integrity": "sha256-01wdG2ctOvRfi8+uR2PAO3nANDdhp3eFdsZnyMdFJw4="
1462
+ "integrity": "sha256-Yk3QfedtNE5m4oG506sYzf5CGE1yIt8gBzImSkXtMbY="
1463
1463
  },
1464
1464
  {
1465
1465
  "path": "packages/components/_internal/react.ts",
@@ -1799,7 +1799,7 @@
1799
1799
  },
1800
1800
  "registryDependencies": [],
1801
1801
  "engineDependencies": {
1802
- "@mlola-ui/behavior": "^1.1.23"
1802
+ "@mlola-ui/behavior": "^1.1.25"
1803
1803
  },
1804
1804
  "options": [],
1805
1805
  "variants": [],
@@ -1852,8 +1852,8 @@
1852
1852
  "input"
1853
1853
  ],
1854
1854
  "engineDependencies": {
1855
- "@mlola-ui/behavior": "^1.1.23",
1856
- "@mlola-ui/icons": "^1.1.23"
1855
+ "@mlola-ui/behavior": "^1.1.25",
1856
+ "@mlola-ui/icons": "^1.1.25"
1857
1857
  },
1858
1858
  "options": [
1859
1859
  {
@@ -1883,7 +1883,7 @@
1883
1883
  "path": "packages/components/_internal/floating.ts",
1884
1884
  "target": "{{aliases.components}}/_internal/floating.ts",
1885
1885
  "type": "registry:ui",
1886
- "integrity": "sha256-01wdG2ctOvRfi8+uR2PAO3nANDdhp3eFdsZnyMdFJw4="
1886
+ "integrity": "sha256-Yk3QfedtNE5m4oG506sYzf5CGE1yIt8gBzImSkXtMbY="
1887
1887
  },
1888
1888
  {
1889
1889
  "path": "packages/components/_internal/react.ts",
@@ -1931,8 +1931,8 @@
1931
1931
  },
1932
1932
  "registryDependencies": [],
1933
1933
  "engineDependencies": {
1934
- "@mlola-ui/behavior": "^1.1.23",
1935
- "@mlola-ui/icons": "^1.1.23"
1934
+ "@mlola-ui/behavior": "^1.1.25",
1935
+ "@mlola-ui/icons": "^1.1.25"
1936
1936
  },
1937
1937
  "options": [
1938
1938
  {
@@ -1980,7 +1980,7 @@
1980
1980
  "path": "packages/components/_internal/floating.ts",
1981
1981
  "target": "{{aliases.components}}/_internal/floating.ts",
1982
1982
  "type": "registry:ui",
1983
- "integrity": "sha256-01wdG2ctOvRfi8+uR2PAO3nANDdhp3eFdsZnyMdFJw4="
1983
+ "integrity": "sha256-Yk3QfedtNE5m4oG506sYzf5CGE1yIt8gBzImSkXtMbY="
1984
1984
  },
1985
1985
  {
1986
1986
  "path": "packages/components/_internal/react.ts",
@@ -2078,7 +2078,7 @@
2078
2078
  "input"
2079
2079
  ],
2080
2080
  "engineDependencies": {
2081
- "@mlola-ui/behavior": "^1.1.23"
2081
+ "@mlola-ui/behavior": "^1.1.25"
2082
2082
  },
2083
2083
  "options": [
2084
2084
  {
@@ -2223,7 +2223,7 @@
2223
2223
  },
2224
2224
  "registryDependencies": [],
2225
2225
  "engineDependencies": {
2226
- "@mlola-ui/behavior": "^1.1.23"
2226
+ "@mlola-ui/behavior": "^1.1.25"
2227
2227
  },
2228
2228
  "options": [
2229
2229
  {
@@ -2355,7 +2355,7 @@
2355
2355
  },
2356
2356
  "registryDependencies": [],
2357
2357
  "engineDependencies": {
2358
- "@mlola-ui/icons": "^1.1.23"
2358
+ "@mlola-ui/icons": "^1.1.25"
2359
2359
  },
2360
2360
  "options": [
2361
2361
  {
@@ -2443,8 +2443,8 @@
2443
2443
  "spinner"
2444
2444
  ],
2445
2445
  "engineDependencies": {
2446
- "@mlola-ui/behavior": "^1.1.23",
2447
- "@mlola-ui/icons": "^1.1.23"
2446
+ "@mlola-ui/behavior": "^1.1.25",
2447
+ "@mlola-ui/icons": "^1.1.25"
2448
2448
  },
2449
2449
  "options": [
2450
2450
  {
@@ -2472,7 +2472,7 @@
2472
2472
  "path": "packages/components/_internal/floating.ts",
2473
2473
  "target": "{{aliases.components}}/_internal/floating.ts",
2474
2474
  "type": "registry:ui",
2475
- "integrity": "sha256-01wdG2ctOvRfi8+uR2PAO3nANDdhp3eFdsZnyMdFJw4="
2475
+ "integrity": "sha256-Yk3QfedtNE5m4oG506sYzf5CGE1yIt8gBzImSkXtMbY="
2476
2476
  },
2477
2477
  {
2478
2478
  "path": "packages/components/_internal/react.ts",
@@ -2599,7 +2599,7 @@
2599
2599
  },
2600
2600
  "registryDependencies": [],
2601
2601
  "engineDependencies": {
2602
- "@mlola-ui/behavior": "^1.1.23"
2602
+ "@mlola-ui/behavior": "^1.1.25"
2603
2603
  },
2604
2604
  "options": [
2605
2605
  {
@@ -2627,7 +2627,7 @@
2627
2627
  "path": "packages/components/_internal/floating.ts",
2628
2628
  "target": "{{aliases.components}}/_internal/floating.ts",
2629
2629
  "type": "registry:ui",
2630
- "integrity": "sha256-01wdG2ctOvRfi8+uR2PAO3nANDdhp3eFdsZnyMdFJw4="
2630
+ "integrity": "sha256-Yk3QfedtNE5m4oG506sYzf5CGE1yIt8gBzImSkXtMbY="
2631
2631
  },
2632
2632
  {
2633
2633
  "path": "packages/components/_internal/react.ts",
@@ -2676,7 +2676,7 @@
2676
2676
  },
2677
2677
  "registryDependencies": [],
2678
2678
  "engineDependencies": {
2679
- "@mlola-ui/behavior": "^1.1.23"
2679
+ "@mlola-ui/behavior": "^1.1.25"
2680
2680
  },
2681
2681
  "options": [
2682
2682
  {
@@ -2704,7 +2704,7 @@
2704
2704
  "path": "packages/components/_internal/floating.ts",
2705
2705
  "target": "{{aliases.components}}/_internal/floating.ts",
2706
2706
  "type": "registry:ui",
2707
- "integrity": "sha256-01wdG2ctOvRfi8+uR2PAO3nANDdhp3eFdsZnyMdFJw4="
2707
+ "integrity": "sha256-Yk3QfedtNE5m4oG506sYzf5CGE1yIt8gBzImSkXtMbY="
2708
2708
  },
2709
2709
  {
2710
2710
  "path": "packages/components/_internal/react.ts",
@@ -2758,7 +2758,7 @@
2758
2758
  },
2759
2759
  "registryDependencies": [],
2760
2760
  "engineDependencies": {
2761
- "@mlola-ui/icons": "^1.1.23"
2761
+ "@mlola-ui/icons": "^1.1.25"
2762
2762
  },
2763
2763
  "options": [
2764
2764
  {
@@ -2830,7 +2830,7 @@
2830
2830
  "popover"
2831
2831
  ],
2832
2832
  "engineDependencies": {
2833
- "@mlola-ui/icons": "^1.1.23"
2833
+ "@mlola-ui/icons": "^1.1.25"
2834
2834
  },
2835
2835
  "options": [
2836
2836
  {
@@ -2900,7 +2900,7 @@
2900
2900
  "popover"
2901
2901
  ],
2902
2902
  "engineDependencies": {
2903
- "@mlola-ui/icons": "^1.1.23"
2903
+ "@mlola-ui/icons": "^1.1.25"
2904
2904
  },
2905
2905
  "options": [],
2906
2906
  "variants": [],
@@ -3098,8 +3098,8 @@
3098
3098
  "input"
3099
3099
  ],
3100
3100
  "engineDependencies": {
3101
- "@mlola-ui/behavior": "^1.1.23",
3102
- "@mlola-ui/icons": "^1.1.23"
3101
+ "@mlola-ui/behavior": "^1.1.25",
3102
+ "@mlola-ui/icons": "^1.1.25"
3103
3103
  },
3104
3104
  "options": [
3105
3105
  {
@@ -3124,7 +3124,7 @@
3124
3124
  "path": "packages/components/_internal/floating.ts",
3125
3125
  "target": "{{aliases.components}}/_internal/floating.ts",
3126
3126
  "type": "registry:ui",
3127
- "integrity": "sha256-01wdG2ctOvRfi8+uR2PAO3nANDdhp3eFdsZnyMdFJw4="
3127
+ "integrity": "sha256-Yk3QfedtNE5m4oG506sYzf5CGE1yIt8gBzImSkXtMbY="
3128
3128
  },
3129
3129
  {
3130
3130
  "path": "packages/components/_internal/match.ts",
@@ -3188,7 +3188,7 @@
3188
3188
  "input"
3189
3189
  ],
3190
3190
  "engineDependencies": {
3191
- "@mlola-ui/icons": "^1.1.23"
3191
+ "@mlola-ui/icons": "^1.1.25"
3192
3192
  },
3193
3193
  "options": [],
3194
3194
  "variants": [],
@@ -3243,7 +3243,7 @@
3243
3243
  "input"
3244
3244
  ],
3245
3245
  "engineDependencies": {
3246
- "@mlola-ui/icons": "^1.1.23"
3246
+ "@mlola-ui/icons": "^1.1.25"
3247
3247
  },
3248
3248
  "options": [],
3249
3249
  "variants": [],
@@ -3306,7 +3306,7 @@
3306
3306
  "popover"
3307
3307
  ],
3308
3308
  "engineDependencies": {
3309
- "@mlola-ui/icons": "^1.1.23"
3309
+ "@mlola-ui/icons": "^1.1.25"
3310
3310
  },
3311
3311
  "options": [
3312
3312
  {
@@ -3377,7 +3377,7 @@
3377
3377
  },
3378
3378
  "registryDependencies": [],
3379
3379
  "engineDependencies": {
3380
- "@mlola-ui/icons": "^1.1.23"
3380
+ "@mlola-ui/icons": "^1.1.25"
3381
3381
  },
3382
3382
  "options": [
3383
3383
  {
@@ -3496,8 +3496,8 @@
3496
3496
  },
3497
3497
  "registryDependencies": [],
3498
3498
  "engineDependencies": {
3499
- "@mlola-ui/behavior": "^1.1.23",
3500
- "@mlola-ui/icons": "^1.1.23"
3499
+ "@mlola-ui/behavior": "^1.1.25",
3500
+ "@mlola-ui/icons": "^1.1.25"
3501
3501
  },
3502
3502
  "options": [],
3503
3503
  "variants": [],
@@ -3513,7 +3513,7 @@
3513
3513
  "path": "packages/components/_internal/floating.ts",
3514
3514
  "target": "{{aliases.components}}/_internal/floating.ts",
3515
3515
  "type": "registry:ui",
3516
- "integrity": "sha256-01wdG2ctOvRfi8+uR2PAO3nANDdhp3eFdsZnyMdFJw4="
3516
+ "integrity": "sha256-Yk3QfedtNE5m4oG506sYzf5CGE1yIt8gBzImSkXtMbY="
3517
3517
  },
3518
3518
  {
3519
3519
  "path": "packages/components/_internal/react.ts",
@@ -3561,7 +3561,7 @@
3561
3561
  },
3562
3562
  "registryDependencies": [],
3563
3563
  "engineDependencies": {
3564
- "@mlola-ui/behavior": "^1.1.23"
3564
+ "@mlola-ui/behavior": "^1.1.25"
3565
3565
  },
3566
3566
  "options": [
3567
3567
  {
@@ -3599,7 +3599,7 @@
3599
3599
  "path": "packages/components/_internal/floating.ts",
3600
3600
  "target": "{{aliases.components}}/_internal/floating.ts",
3601
3601
  "type": "registry:ui",
3602
- "integrity": "sha256-01wdG2ctOvRfi8+uR2PAO3nANDdhp3eFdsZnyMdFJw4="
3602
+ "integrity": "sha256-Yk3QfedtNE5m4oG506sYzf5CGE1yIt8gBzImSkXtMbY="
3603
3603
  },
3604
3604
  {
3605
3605
  "path": "packages/components/_internal/react.ts",
@@ -3648,8 +3648,8 @@
3648
3648
  },
3649
3649
  "registryDependencies": [],
3650
3650
  "engineDependencies": {
3651
- "@mlola-ui/behavior": "^1.1.23",
3652
- "@mlola-ui/icons": "^1.1.23"
3651
+ "@mlola-ui/behavior": "^1.1.25",
3652
+ "@mlola-ui/icons": "^1.1.25"
3653
3653
  },
3654
3654
  "options": [],
3655
3655
  "variants": [],
@@ -3665,7 +3665,7 @@
3665
3665
  "path": "packages/components/_internal/floating.ts",
3666
3666
  "target": "{{aliases.components}}/_internal/floating.ts",
3667
3667
  "type": "registry:ui",
3668
- "integrity": "sha256-01wdG2ctOvRfi8+uR2PAO3nANDdhp3eFdsZnyMdFJw4="
3668
+ "integrity": "sha256-Yk3QfedtNE5m4oG506sYzf5CGE1yIt8gBzImSkXtMbY="
3669
3669
  },
3670
3670
  {
3671
3671
  "path": "packages/components/_internal/media.ts",
@@ -3723,7 +3723,7 @@
3723
3723
  "sheet"
3724
3724
  ],
3725
3725
  "engineDependencies": {
3726
- "@mlola-ui/icons": "^1.1.23"
3726
+ "@mlola-ui/icons": "^1.1.25"
3727
3727
  },
3728
3728
  "options": [],
3729
3729
  "variants": [],
@@ -3846,7 +3846,7 @@
3846
3846
  "button"
3847
3847
  ],
3848
3848
  "engineDependencies": {
3849
- "@mlola-ui/icons": "^1.1.23"
3849
+ "@mlola-ui/icons": "^1.1.25"
3850
3850
  },
3851
3851
  "options": [
3852
3852
  {
@@ -3950,8 +3950,8 @@
3950
3950
  "input"
3951
3951
  ],
3952
3952
  "engineDependencies": {
3953
- "@mlola-ui/behavior": "^1.1.23",
3954
- "@mlola-ui/icons": "^1.1.23"
3953
+ "@mlola-ui/behavior": "^1.1.25",
3954
+ "@mlola-ui/icons": "^1.1.25"
3955
3955
  },
3956
3956
  "options": [
3957
3957
  {
@@ -19,13 +19,17 @@ export interface VirtualAnchor {
19
19
  }
20
20
 
21
21
  /**
22
- * Keep a fixed-position layer next to its anchor while open: on open, on
23
- * scroll anywhere, on resize, and whenever either element changes size.
24
- * Writes styles directly, so following a scroll never re-renders React.
22
+ * Keep a fixed-position layer next to its anchor while open. It is placed
23
+ * as it opens, then watched every frame: when the anchor moves, either one
24
+ * changes size, or the visible area changes, it is placed again. Events do
25
+ * not cover every move: a phone scrolls a field into view above its
26
+ * keyboard, and a sidebar slides, without a scroll or resize event, which
27
+ * left a list behind where its field had been. A frame with nothing changed
28
+ * reads and writes nothing more, and following never re-renders React.
25
29
  * `place` swaps the placement (a tooltip's, with its arrow's `shift`); pass
26
30
  * one defined outside the component, so it is the same on every render.
27
- * `at` is for a virtual anchor that moves without either element resizing:
28
- * a new value places the layer again.
31
+ * `at` places the layer again at once when a virtual anchor moves (the
32
+ * frame after would find it too).
29
33
  */
30
34
  export function useFloating(
31
35
  anchor: React.RefObject<Element | VirtualAnchor | null>,
@@ -63,27 +67,27 @@ export function useFloating(
63
67
  if (at.shift !== undefined) layer.style.setProperty("--ml-floating-shift", `${at.shift}px`);
64
68
  layer.setAttribute("data-side", at.side);
65
69
  };
66
- const schedule = () => {
67
- cancelAnimationFrame(frame);
68
- frame = requestAnimationFrame(update);
70
+ // What decides the placement: the anchor's box, the layer's size, the visible area, and whether it holds focus.
71
+ let last = "";
72
+ const watch = () => {
73
+ const target = anchor.current;
74
+ if (target && layer.isConnected) {
75
+ const rect = target.getBoundingClientRect();
76
+ const view = visibleArea();
77
+ const now = [rect.left, rect.top, rect.width, rect.height, layer.offsetWidth, layer.offsetHeight, view.x, view.y, view.width, view.height, layer.contains(document.activeElement)].join();
78
+ if (now !== last) {
79
+ last = now;
80
+ update();
81
+ }
82
+ }
83
+ frame = requestAnimationFrame(watch);
69
84
  };
70
85
  update();
71
- window.addEventListener("scroll", schedule, true);
72
- window.addEventListener("resize", schedule);
73
- // The keyboard coming up or going down resizes and moves the visible area, not the window.
74
- window.visualViewport?.addEventListener("resize", schedule);
75
- window.visualViewport?.addEventListener("scroll", schedule);
76
- const observer = typeof ResizeObserver === "undefined" ? null : new ResizeObserver(schedule);
77
- observer?.observe(layer);
78
- if (context) observer?.observe(context);
86
+ // Where there are no frames (some test environments), the first placement stands.
87
+ if (typeof requestAnimationFrame === "function") frame = requestAnimationFrame(watch);
79
88
  return () => {
80
89
  stopTheme();
81
- cancelAnimationFrame(frame);
82
- window.removeEventListener("scroll", schedule, true);
83
- window.removeEventListener("resize", schedule);
84
- window.visualViewport?.removeEventListener("resize", schedule);
85
- window.visualViewport?.removeEventListener("scroll", schedule);
86
- observer?.disconnect();
90
+ if (typeof cancelAnimationFrame === "function") cancelAnimationFrame(frame);
87
91
  };
88
92
  }, [anchor, floating, open, side, align, offset, place, moved]);
89
93
  }
@@ -3,7 +3,7 @@
3
3
  "packages/components/_internal/clipboard.ts": "sha256-0y5p88CTXWuOrUYlC5J4XP1VCrOwkx9jYWVHmD2LouQ=",
4
4
  "packages/components/_internal/dialog.ts": "sha256-HouQjNfiorcAUoc7DFu78QLuhNDgvUuDOxjWZYYd62E=",
5
5
  "packages/components/_internal/dom.ts": "sha256-TJp94MipbXHonH/PC7FFHu5pnAqcoxzLocksWLsTAsc=",
6
- "packages/components/_internal/floating.ts": "sha256-01wdG2ctOvRfi8+uR2PAO3nANDdhp3eFdsZnyMdFJw4=",
6
+ "packages/components/_internal/floating.ts": "sha256-Yk3QfedtNE5m4oG506sYzf5CGE1yIt8gBzImSkXtMbY=",
7
7
  "packages/components/_internal/match.ts": "sha256-3tUYob6xmD2nLerh634E3R5iPbzMeNBzdDynsDFjj28=",
8
8
  "packages/components/_internal/media.ts": "sha256-ziH0YZtu8y3X7OcDI6lYxpvGJCi9zQXq4dynG8n8U9c=",
9
9
  "packages/components/_internal/react.ts": "sha256-INfds1J2f1MLpN6kBXz0GkfBEMIiRlhv0DXX455qAZo=",
package/src/knowledge.js CHANGED
@@ -193,6 +193,30 @@ const SHARED = new Set(["data-tone", "data-variant", "data-size", "data-state",
193
193
  const UTILITY = /^(-?(m|p)[trblxy]?-\d|flex$|grid$|block$|inline|hidden$|items-|justify-|gap-|w-|h-|min-|max-|text-(xs|sm|base|lg|xl|\d|[a-z]+-\d)|font-(bold|medium|semibold|light)|bg-|border-|rounded|shadow|ring-|space-[xy]-|leading-|tracking-|opacity-|z-\d|absolute$|relative$|fixed$|sticky$|overflow-|col-span|row-span|sm:|md:|lg:|xl:|hover:|focus:|dark:)/;
194
194
  const COLOR = /#[0-9a-f]{3,8}\b|\b(?:rgba?|hsla?|oklch|oklab|lab|lch|hwb)\(/i;
195
195
 
196
+ /*
197
+ * Spacing written by hand: a padding, margin, gap or inset whose length is
198
+ * not read from the scale. The guide asks for --ml-space-*; a calc() or
199
+ * clamp() of scale steps reads a token and passes, and 0, auto and
200
+ * percentages are not measures anyone invented.
201
+ */
202
+ const SPACING_CSS = /(?:^|[;{\s])((?:padding|margin|inset)(?:-(?:top|right|bottom|left|inline|block)(?:-(?:start|end))?)?|(?:row-|column-)?gap)\s*:\s*([^;}]+)/gi;
203
+ const SPACING_JS = /(?:^|[{,\s])((?:padding|margin|inset)(?:Top|Right|Bottom|Left|Inline|Block)?(?:Start|End)?|(?:row|column)?[gG]ap)\s*:\s*("[^"]*"|'[^']*'|`[^`]*`|-?\d+(?:\.\d+)?(?=\s*[,}]))/g;
204
+ const byHand = (value) => !/var\(--ml-/.test(value) && [...value.matchAll(/-?(?:\d*\.)?\d+(?:px|rem|em)\b/g)].some((length) => parseFloat(length[0]) !== 0);
205
+
206
+ /** Each spacing property in a style string, a JSX style object or a stylesheet whose length is written by hand. */
207
+ function handSpacing(text) {
208
+ const found = new Map();
209
+ for (const [, property, value] of text.matchAll(SPACING_CSS)) if (byHand(value)) found.set(property, `${property}: ${value.trim()}`);
210
+ for (const [, property, raw] of text.matchAll(SPACING_JS)) {
211
+ // A bare number in a JSX style object is pixels.
212
+ const handWritten = /^-?\d/.test(raw) ? Number(raw) !== 0 : byHand(raw.slice(1, -1));
213
+ if (handWritten) found.set(property, `${property}: ${raw}`);
214
+ }
215
+ return [...found.values()];
216
+ }
217
+
218
+ const SPACING_FIX = "Measure with the scale: var(--ml-space-2), var(--ml-space-4)… (get_tokens spacing). A calc() or clamp() of scale steps is fine; a value invented between them is not.";
219
+
196
220
  /** Every opening tag in HTML or JSX, with its string-valued attributes. */
197
221
  function tagsOf(markup) {
198
222
  const tags = [];
@@ -322,6 +346,8 @@ export function checkMarkup(markup) {
322
346
  if (overridden.length) {
323
347
  note("error", tag, `${overridden.join(", ")} is a theme token; setting it inline overrides the theme.`, "Pick a theme, or change the theme's spec (mlola.theme.json), instead of one element's tokens.");
324
348
  }
349
+ const spacing = handSpacing(style);
350
+ if (spacing.length) note("warning", tag, `Spacing is written by hand in style (${spacing.join("; ")}).`, SPACING_FIX);
325
351
  }
326
352
  }
327
353
 
@@ -342,6 +368,8 @@ export function checkMarkup(markup) {
342
368
  if (overridden.length) {
343
369
  note("error", where, `${overridden.join(", ")} is a theme token; setting it here overrides the theme.`, "Pick a theme, or change the theme's spec (mlola.theme.json), instead of redefining its tokens.");
344
370
  }
371
+ const spacing = handSpacing(body);
372
+ if (spacing.length) note("warning", where, `Spacing is written by hand (${spacing.join("; ")}).`, SPACING_FIX);
345
373
  // Faded text: its contrast now depends on the theme behind it.
346
374
  // opacity: 0 hides; only a value between 0 and 1 fades what is still read.
347
375
  const opacity = /(?:^|;)\s*opacity\s*:\s*(0?\.\d+)\s*(?:;|$)/.exec(body);
package/src/mcp.js CHANGED
@@ -104,7 +104,7 @@ function readTools({ cwd, version }) {
104
104
  {
105
105
  name: "check_markup",
106
106
  title: "Check markup against the Mlola contract",
107
- description: "Checks HTML or JSX: classes that do not exist, variant classes, data-* values a class does not react to, utility classes, misuse of data-theme or data-mode, and in style attributes and <style> blocks hand-written colors, overridden theme tokens and text faded with opacity. Run it on markup you wrote before finishing: pass the whole page or file, <style> blocks included, not an excerpt, since it only checks what it is given.",
107
+ description: "Checks HTML or JSX: classes that do not exist, variant classes, data-* values a class does not react to, utility classes, misuse of data-theme or data-mode, and in style attributes and <style> blocks hand-written colors, spacing written by hand instead of the --ml-space-* scale, overridden theme tokens and text faded with opacity. Run it on markup you wrote before finishing: pass the whole page or file, <style> blocks included, not an excerpt, since it only checks what it is given.",
108
108
  inputSchema: { type: "object", properties: { markup: { type: "string" } }, required: ["markup"], additionalProperties: false },
109
109
  annotations: { readOnlyHint: true },
110
110
  handler: ({ markup }) => {