@antfu/design 0.1.0 → 0.2.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.
- package/README.md +37 -17
- package/a11y/cli.ts +6 -4
- package/composables/colorScheme.ts +1 -1
- package/package.json +25 -7
- package/skills/antfu-design/references/core-setup.md +67 -2
- package/skills/antfu-design/references/core-tokens.md +12 -16
- package/styles/base.css +6 -18
- package/styles/floating-vue.css +9 -12
- package/styles/splitpanes.css +4 -3
- package/unocss/blocklist.ts +44 -0
- package/unocss/colors.ts +15 -5
- package/unocss/index.ts +21 -0
- package/unocss/options.ts +16 -0
- package/unocss/shortcuts.ts +17 -9
- package/utils/color.ts +3 -2
- package/utils/format.ts +2 -2
- package/utils/keybinding.ts +4 -3
- package/utils/path.ts +6 -4
- package/utils/semver.ts +4 -3
- package/utils/tree.ts +2 -0
package/README.md
CHANGED
|
@@ -16,6 +16,7 @@ pnpm add @antfu/design unocss vue
|
|
|
16
16
|
```ts
|
|
17
17
|
// uno.config.ts
|
|
18
18
|
import { presetAnthonyDesign } from '@antfu/design/unocss'
|
|
19
|
+
import transformerDirectives from '@unocss/transformer-directives'
|
|
19
20
|
import { defineConfig, presetIcons, presetWebFonts, presetWind4 } from 'unocss'
|
|
20
21
|
|
|
21
22
|
export default defineConfig({
|
|
@@ -25,10 +26,29 @@ export default defineConfig({
|
|
|
25
26
|
presetIcons(),
|
|
26
27
|
presetWebFonts({ fonts: { sans: 'DM Sans', mono: 'DM Mono' } }),
|
|
27
28
|
],
|
|
28
|
-
|
|
29
|
+
// Required — the shipped styles recolor overlays with token `--at-apply`
|
|
30
|
+
// directives; this transformer expands them (and lets you reuse tokens in CSS).
|
|
31
|
+
transformers: [transformerDirectives()],
|
|
32
|
+
// The preset ships no z-index scale (stacking is the app's to own) and blocks
|
|
33
|
+
// plain `z-<number>`. Define the named layers the overlay components use here —
|
|
34
|
+
// these values are yours; tune them to fit your app's stack.
|
|
35
|
+
shortcuts: {
|
|
36
|
+
'z-nav': 'z-[30]',
|
|
37
|
+
'z-dropdown': 'z-[40]',
|
|
38
|
+
'z-tooltip': 'z-[45]',
|
|
39
|
+
'z-toast': 'z-[50]',
|
|
40
|
+
'z-modal-backdrop': 'z-[60]',
|
|
41
|
+
'z-modal-content': 'z-[70]',
|
|
42
|
+
'z-drawer-backdrop': 'z-[80]',
|
|
43
|
+
'z-drawer-content': 'z-[90]',
|
|
44
|
+
},
|
|
29
45
|
})
|
|
30
46
|
```
|
|
31
47
|
|
|
48
|
+
> Pair it with [`@unocss/eslint-plugin`](https://unocss.dev/integrations/eslint)
|
|
49
|
+
> for the feedback loop — it surfaces the blocklist hints (e.g. a plain `z-50`) in
|
|
50
|
+
> your editor and CI instead of silently dropping at build time.
|
|
51
|
+
|
|
32
52
|
```ts
|
|
33
53
|
// Components are imported by full path (no barrel) — categorized and prefixed:
|
|
34
54
|
import ActionButton from '@antfu/design/components/Action/ActionButton.vue'
|
|
@@ -38,12 +58,17 @@ import '@antfu/design/styles.css'
|
|
|
38
58
|
```
|
|
39
59
|
|
|
40
60
|
The package ships **raw `.ts` / `.vue` source** (no bundling) — your build
|
|
41
|
-
compiles it.
|
|
42
|
-
|
|
61
|
+
compiles it. No extra `content` config is needed: UnoCSS's default scan matches
|
|
62
|
+
`.vue`/`.tsx` by extension (its only default exclude is CSS, not `node_modules`),
|
|
63
|
+
so the components you import are picked up automatically. If you *do* set
|
|
64
|
+
`content.pipeline.include`, note it **replaces** the default scan rather than
|
|
65
|
+
extending it — restate the defaults too, or your own sources stop being generated.
|
|
43
66
|
|
|
44
67
|
It's a **single** preset that is **not self-contained**: it contributes only the
|
|
45
68
|
antfu design layer (theme tokens, semantic shortcuts, dynamic rules, severity).
|
|
46
|
-
You compose the base preset, icons, fonts and reset yourself
|
|
69
|
+
You compose the base preset, icons, fonts and reset yourself — and add
|
|
70
|
+
`@unocss/transformer-directives` (the shipped styles use token `--at-apply`
|
|
71
|
+
directives) plus, recommended, `@unocss/eslint-plugin` for the feedback loop.
|
|
47
72
|
|
|
48
73
|
## Exports
|
|
49
74
|
|
|
@@ -114,10 +139,18 @@ tsx node_modules/@antfu/design/a11y/cli.ts http://localhost:6006/iframe.html
|
|
|
114
139
|
| `btn-action-active` | `color-active border-active! bg-active op100!` |
|
|
115
140
|
| `btn-icon` | `w-9 h-9 rounded-full op-fade hover:op100 hover:bg-active transition flex items-center justify-center disabled:pointer-events-none disabled:op30 outline-none focus-visible:ring-2 focus-visible:ring-primary-500/40` |
|
|
116
141
|
| `btn-icon-compact` | `w-6 h-6 rounded op-fade hover:op100 hover:bg-active transition flex items-center justify-center disabled:pointer-events-none disabled:op30 outline-none focus-visible:ring-2 focus-visible:ring-primary-500/40` |
|
|
142
|
+
| `btn-icon-square` | `w-9 h-9 rounded border border-base op-fade hover:op100 hover:bg-active transition flex items-center justify-center disabled:pointer-events-none disabled:op30 outline-none focus-visible:ring-2 focus-visible:ring-primary-500/40` |
|
|
117
143
|
| `btn-primary` | `px3 py1.5 rounded flex gap-2 items-center bg-primary-500 hover:bg-primary-600 text-white transition disabled:op50 disabled:pointer-events-none outline-none focus-visible:ring-2 focus-visible:ring-primary-500/40` |
|
|
118
144
|
| `badge` | `inline-flex items-center gap-1 px-1.5 py-0.5 rounded-md text-xs font-medium leading-none` |
|
|
119
145
|
| `badge-active` | `badge bg-active color-active` |
|
|
120
146
|
| `badge-muted` | `badge bg-#8881 color-muted` |
|
|
147
|
+
| `pad-safe-t` | `pt-[env(safe-area-inset-top)]` |
|
|
148
|
+
| `pad-safe-r` | `pr-[env(safe-area-inset-right)]` |
|
|
149
|
+
| `pad-safe-b` | `pb-[env(safe-area-inset-bottom)]` |
|
|
150
|
+
| `pad-safe-l` | `pl-[env(safe-area-inset-left)]` |
|
|
151
|
+
| `pad-safe-x` | `pad-safe-l pad-safe-r` |
|
|
152
|
+
| `pad-safe-y` | `pad-safe-t pad-safe-b` |
|
|
153
|
+
| `pad-safe` | `pad-safe-x pad-safe-y` |
|
|
121
154
|
|
|
122
155
|
### Severity scale
|
|
123
156
|
|
|
@@ -137,19 +170,6 @@ tsx node_modules/@antfu/design/a11y/cli.ts http://localhost:6006/iframe.html
|
|
|
137
170
|
| `text-mini` | `text-[11px] leading-[1.45]` |
|
|
138
171
|
| `text-compact` | `text-[12px] leading-[1.5]` |
|
|
139
172
|
|
|
140
|
-
### Named z-index layers
|
|
141
|
-
|
|
142
|
-
| Token | Expands to |
|
|
143
|
-
|---|---|
|
|
144
|
-
| `z-nav` | `z-[30]` |
|
|
145
|
-
| `z-dropdown` | `z-[40]` |
|
|
146
|
-
| `z-tooltip` | `z-[45]` |
|
|
147
|
-
| `z-toast` | `z-[50]` |
|
|
148
|
-
| `z-modal-backdrop` | `z-[60]` |
|
|
149
|
-
| `z-modal-content` | `z-[70]` |
|
|
150
|
-
| `z-drawer-backdrop` | `z-[80]` |
|
|
151
|
-
| `z-drawer-content` | `z-[90]` |
|
|
152
|
-
|
|
153
173
|
### Dynamic
|
|
154
174
|
|
|
155
175
|
| Token | Expands to |
|
package/a11y/cli.ts
CHANGED
|
@@ -27,13 +27,15 @@ function parseArgs(argv: string[]): { options: ContrastScanOptions, help: boolea
|
|
|
27
27
|
|
|
28
28
|
for (let i = 0; i < argv.length; i++) {
|
|
29
29
|
const arg = argv[i]
|
|
30
|
+
if (arg == null)
|
|
31
|
+
continue
|
|
30
32
|
if (arg === '-h' || arg === '--help')
|
|
31
33
|
help = true
|
|
32
|
-
else if (arg === '--mode')
|
|
34
|
+
else if (arg === '--mode' && argv[i + 1] != null)
|
|
33
35
|
modes.push(argv[++i] as ColorMode)
|
|
34
|
-
else if (arg === '--exclude')
|
|
35
|
-
exclude.push(argv[++i])
|
|
36
|
-
else if (arg === '--key')
|
|
36
|
+
else if (arg === '--exclude' && argv[i + 1] != null)
|
|
37
|
+
exclude.push(argv[++i]!)
|
|
38
|
+
else if (arg === '--key' && argv[i + 1] != null)
|
|
37
39
|
colorSchemeStorageKey = argv[++i]
|
|
38
40
|
else if (arg === '--headed')
|
|
39
41
|
headless = false
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Opt-in color-scheme context.
|
|
3
3
|
*
|
|
4
4
|
* The package is stateless: most components flip light/dark automatically from
|
|
5
|
-
* the `html.dark` class +
|
|
5
|
+
* the `html.dark` class + the token shortcuts, and the handful that compute colors in
|
|
6
6
|
* JS (badges/labels/proportion bars) take a `colorScheme` prop. Threading that
|
|
7
7
|
* prop everywhere is tedious, so this provides an *opt-in* context: call
|
|
8
8
|
* {@link provideColorScheme} once near the app root (feeding it your own
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@antfu/design",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.2.0",
|
|
5
5
|
"description": "A customizable, composable design system for devtools-style Vue apps — a UnoCSS preset, Vue primitives, a design skill, and an a11y contrast check",
|
|
6
6
|
"author": "Anthony Fu <anthonyfu117@hotmail.com>",
|
|
7
7
|
"license": "MIT",
|
|
@@ -46,7 +46,11 @@
|
|
|
46
46
|
],
|
|
47
47
|
"peerDependencies": {
|
|
48
48
|
"@axe-core/playwright": "^4.0.0",
|
|
49
|
+
"@tanstack/vue-virtual": "^3.0.0",
|
|
50
|
+
"floating-vue": "^5.0.0",
|
|
49
51
|
"playwright": "^1.0.0",
|
|
52
|
+
"reka-ui": "^2.0.0",
|
|
53
|
+
"splitpanes": "^4.0.0",
|
|
50
54
|
"unocss": ">=66.0.0",
|
|
51
55
|
"vue": "^3.5.0"
|
|
52
56
|
},
|
|
@@ -54,34 +58,48 @@
|
|
|
54
58
|
"@axe-core/playwright": {
|
|
55
59
|
"optional": true
|
|
56
60
|
},
|
|
61
|
+
"@tanstack/vue-virtual": {
|
|
62
|
+
"optional": true
|
|
63
|
+
},
|
|
64
|
+
"floating-vue": {
|
|
65
|
+
"optional": true
|
|
66
|
+
},
|
|
57
67
|
"playwright": {
|
|
58
68
|
"optional": true
|
|
69
|
+
},
|
|
70
|
+
"reka-ui": {
|
|
71
|
+
"optional": true
|
|
72
|
+
},
|
|
73
|
+
"splitpanes": {
|
|
74
|
+
"optional": true
|
|
59
75
|
}
|
|
60
76
|
},
|
|
61
77
|
"dependencies": {
|
|
62
78
|
"@antfu/utils": "^9.3.0",
|
|
63
79
|
"@iconify-json/catppuccin": "^1.2.17",
|
|
64
|
-
"@
|
|
65
|
-
"@unocss/core": "^66.7.2",
|
|
80
|
+
"@unocss/core": ">=66.0.0",
|
|
66
81
|
"@vueuse/core": "^14.3.0",
|
|
67
|
-
"colorjs.io": "^0.6.1"
|
|
68
|
-
"floating-vue": "^5.2.2",
|
|
69
|
-
"reka-ui": "^2.10.0",
|
|
70
|
-
"splitpanes": "^4.1.2"
|
|
82
|
+
"colorjs.io": "^0.6.1"
|
|
71
83
|
},
|
|
72
84
|
"devDependencies": {
|
|
73
85
|
"@arethetypeswrong/cli": "^0.18.2",
|
|
74
86
|
"@axe-core/playwright": "^4.12.1",
|
|
75
87
|
"@storybook/vue3-vite": "^10.4.6",
|
|
88
|
+
"@tanstack/vue-virtual": "^3.13.29",
|
|
76
89
|
"@unocss/preset-icons": "^66.7.2",
|
|
77
90
|
"@unocss/preset-mini": "^66.7.2",
|
|
78
91
|
"@unocss/preset-web-fonts": "^66.7.2",
|
|
79
92
|
"@unocss/preset-wind3": "^66.7.2",
|
|
80
93
|
"@unocss/preset-wind4": "^66.7.2",
|
|
94
|
+
"@unocss/transformer-directives": "^66.7.2",
|
|
81
95
|
"@vitejs/plugin-vue": "^6.0.7",
|
|
82
96
|
"@vue/test-utils": "^2.4.11",
|
|
97
|
+
"floating-vue": "^5.2.2",
|
|
83
98
|
"happy-dom": "^20.10.6",
|
|
99
|
+
"magic-string": "^0.30.21",
|
|
84
100
|
"playwright": "^1.61.1",
|
|
101
|
+
"reka-ui": "^2.10.0",
|
|
102
|
+
"splitpanes": "^4.1.2",
|
|
85
103
|
"tsdown": "^0.22.0",
|
|
86
104
|
"tsdown-stale-guard": "^0.1.2",
|
|
87
105
|
"tsnapi": "^0.3.3",
|
|
@@ -12,6 +12,7 @@ pnpm add @antfu/design unocss
|
|
|
12
12
|
```ts
|
|
13
13
|
// uno.config.ts
|
|
14
14
|
import { presetAnthonyDesign } from '@antfu/design/unocss'
|
|
15
|
+
import transformerDirectives from '@unocss/transformer-directives'
|
|
15
16
|
import { defineConfig, presetIcons, presetWebFonts, presetWind4 } from 'unocss'
|
|
16
17
|
|
|
17
18
|
export default defineConfig({
|
|
@@ -19,13 +20,19 @@ export default defineConfig({
|
|
|
19
20
|
presetAnthonyDesign({
|
|
20
21
|
primary: '#49833E', // string | full color-scale object (default antfu green)
|
|
21
22
|
darkBackground: '#111', // near-black for dark surfaces
|
|
23
|
+
// overrides: { 'bg-base': 'bg-zinc-50 dark:bg-zinc-950' }, // retune any built-in shortcut
|
|
22
24
|
}),
|
|
23
25
|
presetWind4(), // a base preset is REQUIRED — shortcuts expand into its utilities
|
|
24
26
|
presetIcons(),
|
|
25
27
|
presetWebFonts({ fonts: { sans: 'DM Sans', mono: 'DM Mono' } }),
|
|
26
28
|
],
|
|
27
|
-
//
|
|
28
|
-
|
|
29
|
+
// REQUIRED: the shipped `@antfu/design/styles` recolor third-party overlays with
|
|
30
|
+
// token `--at-apply` directives — this transformer is what expands them.
|
|
31
|
+
transformers: [transformerDirectives()],
|
|
32
|
+
// No `content.pipeline.include` needed: UnoCSS's default scan matches `.vue`/`.tsx`
|
|
33
|
+
// by extension (its only default exclude is CSS, not `node_modules`), so imported
|
|
34
|
+
// components are picked up. If you DO set `include`, it REPLACES the default scan —
|
|
35
|
+
// restate your own sources or they stop generating.
|
|
29
36
|
})
|
|
30
37
|
```
|
|
31
38
|
|
|
@@ -33,6 +40,64 @@ export default defineConfig({
|
|
|
33
40
|
> `presetMini`). Without one, the semantic shortcuts have nothing to expand into.
|
|
34
41
|
> The design layer is **one preset** — there are no sub-presets to compose.
|
|
35
42
|
|
|
43
|
+
> **`@unocss/transformer-directives` is required**, not optional: the design
|
|
44
|
+
> system's own CSS (`base.css`, `floating-vue.css`, `splitpanes.css`) styles
|
|
45
|
+
> surfaces with token directives like `--at-apply: 'bg-base color-base'` instead of
|
|
46
|
+
> hand-duplicated hex values. Without the transformer those rules are dropped and
|
|
47
|
+
> overlays/surfaces lose their theming. It also lets *you* reuse the tokens in your
|
|
48
|
+
> own CSS (`.panel { --at-apply: 'bg-base border border-base'; }`).
|
|
49
|
+
|
|
50
|
+
## Recommended: the UnoCSS ESLint plugin
|
|
51
|
+
|
|
52
|
+
Add [`@unocss/eslint-plugin`](https://unocss.dev/integrations/eslint) so the
|
|
53
|
+
guardrails surface as you type instead of silently dropping at build time. It's
|
|
54
|
+
where the blocklist's messages show up — e.g. a plain `z-50` is flagged with the
|
|
55
|
+
"use a named layer" hint — and it keeps class order/duplication tidy. This
|
|
56
|
+
feedback loop is the intended way to work with the design system.
|
|
57
|
+
|
|
58
|
+
```js
|
|
59
|
+
// eslint.config.js
|
|
60
|
+
import unocss from '@unocss/eslint-plugin'
|
|
61
|
+
|
|
62
|
+
export default [
|
|
63
|
+
unocss(),
|
|
64
|
+
]
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## z-index layers (you own them)
|
|
68
|
+
|
|
69
|
+
Stacking is a whole-app concern, so the preset ships **no** z-index scale and
|
|
70
|
+
**blocks plain `z-<number>` / `z-[…]` utilities** (`z-auto` stays allowed) — every
|
|
71
|
+
z-index must flow through a *named* layer you define in your own UnoCSS
|
|
72
|
+
`shortcuts`. The overlay components reference these names; without them, overlays
|
|
73
|
+
have no stacking value. Define them once, alongside the preset, and tune the
|
|
74
|
+
numbers to fit your app:
|
|
75
|
+
|
|
76
|
+
```ts
|
|
77
|
+
// uno.config.ts (top-level — NOT inside the preset)
|
|
78
|
+
export default defineConfig({
|
|
79
|
+
presets: [presetAnthonyDesign(), presetWind4()],
|
|
80
|
+
transformers: [transformerDirectives()],
|
|
81
|
+
shortcuts: {
|
|
82
|
+
'z-nav': 'z-[30]',
|
|
83
|
+
'z-dropdown': 'z-[40]',
|
|
84
|
+
'z-tooltip': 'z-[45]',
|
|
85
|
+
'z-toast': 'z-[50]',
|
|
86
|
+
'z-modal-backdrop': 'z-[60]',
|
|
87
|
+
'z-modal-content': 'z-[70]',
|
|
88
|
+
'z-drawer-backdrop': 'z-[80]',
|
|
89
|
+
'z-drawer-content': 'z-[90]',
|
|
90
|
+
},
|
|
91
|
+
})
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
These are *reference* values — the only contract is the **ordering** (`z-nav` <
|
|
95
|
+
`z-dropdown` < `z-tooltip` < `z-toast` < `z-modal-backdrop` < `z-modal-content` <
|
|
96
|
+
`z-drawer-backdrop` < `z-drawer-content`) so a modal sits over a dropdown, a drawer
|
|
97
|
+
over a modal. The block lifts each utility *defined in `shortcuts`* — only plain
|
|
98
|
+
z-index written in markup is rejected. Disable the guardrail with
|
|
99
|
+
`presetAnthonyDesign({ blocklists: { plainZIndex: false } })`.
|
|
100
|
+
|
|
36
101
|
## Styles
|
|
37
102
|
|
|
38
103
|
```ts
|
|
@@ -34,10 +34,18 @@ never drift from what the shortcuts actually resolve to.
|
|
|
34
34
|
| `btn-action-active` | `color-active border-active! bg-active op100!` |
|
|
35
35
|
| `btn-icon` | `w-9 h-9 rounded-full op-fade hover:op100 hover:bg-active transition flex items-center justify-center disabled:pointer-events-none disabled:op30 outline-none focus-visible:ring-2 focus-visible:ring-primary-500/40` |
|
|
36
36
|
| `btn-icon-compact` | `w-6 h-6 rounded op-fade hover:op100 hover:bg-active transition flex items-center justify-center disabled:pointer-events-none disabled:op30 outline-none focus-visible:ring-2 focus-visible:ring-primary-500/40` |
|
|
37
|
+
| `btn-icon-square` | `w-9 h-9 rounded border border-base op-fade hover:op100 hover:bg-active transition flex items-center justify-center disabled:pointer-events-none disabled:op30 outline-none focus-visible:ring-2 focus-visible:ring-primary-500/40` |
|
|
37
38
|
| `btn-primary` | `px3 py1.5 rounded flex gap-2 items-center bg-primary-500 hover:bg-primary-600 text-white transition disabled:op50 disabled:pointer-events-none outline-none focus-visible:ring-2 focus-visible:ring-primary-500/40` |
|
|
38
39
|
| `badge` | `inline-flex items-center gap-1 px-1.5 py-0.5 rounded-md text-xs font-medium leading-none` |
|
|
39
40
|
| `badge-active` | `badge bg-active color-active` |
|
|
40
41
|
| `badge-muted` | `badge bg-#8881 color-muted` |
|
|
42
|
+
| `pad-safe-t` | `pt-[env(safe-area-inset-top)]` |
|
|
43
|
+
| `pad-safe-r` | `pr-[env(safe-area-inset-right)]` |
|
|
44
|
+
| `pad-safe-b` | `pb-[env(safe-area-inset-bottom)]` |
|
|
45
|
+
| `pad-safe-l` | `pl-[env(safe-area-inset-left)]` |
|
|
46
|
+
| `pad-safe-x` | `pad-safe-l pad-safe-r` |
|
|
47
|
+
| `pad-safe-y` | `pad-safe-t pad-safe-b` |
|
|
48
|
+
| `pad-safe` | `pad-safe-x pad-safe-y` |
|
|
41
49
|
|
|
42
50
|
### Severity scale
|
|
43
51
|
|
|
@@ -57,19 +65,6 @@ never drift from what the shortcuts actually resolve to.
|
|
|
57
65
|
| `text-mini` | `text-[11px] leading-[1.45]` |
|
|
58
66
|
| `text-compact` | `text-[12px] leading-[1.5]` |
|
|
59
67
|
|
|
60
|
-
### Named z-index layers
|
|
61
|
-
|
|
62
|
-
| Token | Expands to |
|
|
63
|
-
|---|---|
|
|
64
|
-
| `z-nav` | `z-[30]` |
|
|
65
|
-
| `z-dropdown` | `z-[40]` |
|
|
66
|
-
| `z-tooltip` | `z-[45]` |
|
|
67
|
-
| `z-toast` | `z-[50]` |
|
|
68
|
-
| `z-modal-backdrop` | `z-[60]` |
|
|
69
|
-
| `z-modal-content` | `z-[70]` |
|
|
70
|
-
| `z-drawer-backdrop` | `z-[80]` |
|
|
71
|
-
| `z-drawer-content` | `z-[90]` |
|
|
72
|
-
|
|
73
68
|
### Dynamic
|
|
74
69
|
|
|
75
70
|
| Token | Expands to |
|
|
@@ -92,9 +87,10 @@ never drift from what the shortcuts actually resolve to.
|
|
|
92
87
|
- **Severity** `color-scale-{neutral,low,medium,high,critical}` is the one ramp
|
|
93
88
|
for fresh→stale / fast→slow / small→large. Prefer the `colorize` prop on display
|
|
94
89
|
components over using these directly.
|
|
95
|
-
- **
|
|
96
|
-
|
|
97
|
-
|
|
90
|
+
- **z-index**: always a named layer (`z-nav`, `z-dropdown`, `z-modal-content`, …),
|
|
91
|
+
never plain `z-<n>` — the preset blocks plain z-index. The preset ships **no**
|
|
92
|
+
values; the app defines the named layers in its own `shortcuts`. See
|
|
93
|
+
[core-setup.md](core-setup.md#z-index-layers-you-own-them).
|
|
98
94
|
- **Theme**: `font-sans` = DM Sans, `font-mono` = DM Mono; extra sizes
|
|
99
95
|
`text-micro` / `text-mini` / `text-compact`; color ramps `primary` (default
|
|
100
96
|
antfu green), `warning`, `success`, `error`.
|
package/styles/base.css
CHANGED
|
@@ -1,31 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Root surface +
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
2
|
+
* Root surface + text, applied straight from the design tokens. The `--at-apply`
|
|
3
|
+
* directives below (and in the other style files) need
|
|
4
|
+
* `@unocss/transformer-directives` in the consuming app's UnoCSS config (see
|
|
5
|
+
* Setup) — that's what expands `bg-base` / `color-base` etc., so the surface
|
|
6
|
+
* follows light/dark with no duplicated set of CSS variables to maintain.
|
|
6
7
|
*/
|
|
7
8
|
|
|
8
9
|
:root {
|
|
9
|
-
--af-bg-base: #ffffff;
|
|
10
|
-
--af-bg-secondary: #f5f5f5;
|
|
11
|
-
--af-color-base: #262626; /* neutral-800 */
|
|
12
|
-
--af-color-muted: #525252; /* neutral-600 */
|
|
13
|
-
--af-border-base: rgba(136, 136, 136, 0.13); /* ~#8882 */
|
|
14
|
-
--af-border-mute: rgba(136, 136, 136, 0.07); /* ~#8881 */
|
|
15
|
-
--af-tooltip-bg: rgba(255, 255, 255, 0.75);
|
|
16
10
|
color-scheme: light;
|
|
17
11
|
}
|
|
18
12
|
|
|
19
13
|
html.dark {
|
|
20
|
-
--af-bg-base: #111111;
|
|
21
|
-
--af-bg-secondary: #1a1a1a;
|
|
22
|
-
--af-color-base: #e5e5e5; /* neutral-200 */
|
|
23
|
-
--af-color-muted: #a3a3a3; /* neutral-400 */
|
|
24
|
-
--af-tooltip-bg: rgba(17, 17, 17, 0.75);
|
|
25
14
|
color-scheme: dark;
|
|
26
15
|
}
|
|
27
16
|
|
|
28
17
|
html {
|
|
29
|
-
|
|
30
|
-
color: var(--af-color-base);
|
|
18
|
+
--at-apply: 'bg-base color-base';
|
|
31
19
|
}
|
package/styles/floating-vue.css
CHANGED
|
@@ -1,28 +1,25 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Recolor floating-vue tooltips/dropdowns to the design tokens
|
|
3
|
-
*
|
|
2
|
+
* Recolor floating-vue tooltips/dropdowns to the design tokens via
|
|
3
|
+
* `@unocss/transformer-directives` (see Setup): `bg-tooltip` carries the
|
|
4
|
+
* translucent surface + backdrop blur, and `color-base` / `border-base` follow
|
|
5
|
+
* light/dark automatically.
|
|
4
6
|
*/
|
|
5
7
|
|
|
6
8
|
.v-popper__popper .v-popper__inner {
|
|
7
|
-
|
|
8
|
-
color: var(--af-color-base);
|
|
9
|
-
border: 1px solid var(--af-border-base);
|
|
10
|
-
border-radius: 6px;
|
|
9
|
+
--at-apply: 'bg-tooltip color-base border border-base rounded-md text-xs px-2 py-1';
|
|
11
10
|
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12);
|
|
12
|
-
backdrop-filter: blur(8px);
|
|
13
|
-
padding: 4px 8px;
|
|
14
|
-
font-size: 12px;
|
|
15
11
|
}
|
|
16
12
|
|
|
17
13
|
.v-popper__popper .v-popper__arrow-outer {
|
|
18
|
-
|
|
14
|
+
--at-apply: 'border-base';
|
|
19
15
|
}
|
|
20
16
|
|
|
21
17
|
.v-popper__popper .v-popper__arrow-inner {
|
|
22
|
-
|
|
18
|
+
/* Matches the `bg-tooltip` fill so the arrow reads as part of the surface. */
|
|
19
|
+
--at-apply: 'border-white/75 dark:border-[#111]/75';
|
|
23
20
|
visibility: visible;
|
|
24
21
|
}
|
|
25
22
|
|
|
26
23
|
.v-popper--theme-dropdown .v-popper__inner {
|
|
27
|
-
|
|
24
|
+
--at-apply: 'p-1';
|
|
28
25
|
}
|
package/styles/splitpanes.css
CHANGED
|
@@ -33,15 +33,16 @@
|
|
|
33
33
|
pointer-events: none;
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
/* Splitter —
|
|
36
|
+
/* Splitter — divider tinted to the neutral border token (see Setup for the
|
|
37
|
+
* `@unocss/transformer-directives` requirement). */
|
|
37
38
|
.splitpanes__splitter {
|
|
38
39
|
position: relative;
|
|
39
40
|
touch-action: none;
|
|
40
|
-
|
|
41
|
+
--at-apply: 'bg-#8882';
|
|
41
42
|
transition: background-color 0.2s;
|
|
42
43
|
}
|
|
43
44
|
.splitpanes__splitter:hover {
|
|
44
|
-
|
|
45
|
+
--at-apply: 'bg-#8886';
|
|
45
46
|
}
|
|
46
47
|
.splitpanes--vertical > .splitpanes__splitter {
|
|
47
48
|
min-width: 1px;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { BlocklistRule } from '@unocss/core'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Granular toggle for the preset's blocklists. `true` (default) enables them all,
|
|
5
|
+
* `false` disables them all, or pass an object to flip individual entries. Kept
|
|
6
|
+
* open-ended so future guardrails slot in without another option.
|
|
7
|
+
*/
|
|
8
|
+
export type BlocklistsOption = boolean | {
|
|
9
|
+
/**
|
|
10
|
+
* Block plain `z-<number>` / `z-[…]` utilities so every z-index flows through a
|
|
11
|
+
* named, app-owned layer (see Setup). Default `true`.
|
|
12
|
+
*/
|
|
13
|
+
plainZIndex?: boolean
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Matches a plain z-index utility — `z-50`, `z-[70]`, `-z-10`, `z-[var(--x)]` —
|
|
18
|
+
* but **not** a named layer (`z-modal-content`, `z-nav`) nor the `z-auto` reset.
|
|
19
|
+
*
|
|
20
|
+
* The named layers are shortcuts the app defines in its own UnoCSS `shortcuts`
|
|
21
|
+
* config; their expansion to `z-[70]` happens *inside* the shortcut, which the
|
|
22
|
+
* blocklist never re-checks. So the layers keep resolving while a plain z-index
|
|
23
|
+
* written in markup fails to generate — pushing authors to semantic layers.
|
|
24
|
+
*/
|
|
25
|
+
export const RE_PLAIN_Z_INDEX = /^-?z-(?:\d+|\[[^\]]+\])$/
|
|
26
|
+
|
|
27
|
+
/** Blocklist entry banning plain z-index utilities (see {@link RE_PLAIN_Z_INDEX}). */
|
|
28
|
+
export const plainZIndexBlocklist: BlocklistRule = [
|
|
29
|
+
RE_PLAIN_Z_INDEX,
|
|
30
|
+
{
|
|
31
|
+
message: (selector: string) =>
|
|
32
|
+
`[@antfu/design] "${selector}" — plain z-index is blocked. Define a named layer in your UnoCSS \`shortcuts\` (e.g. z-modal-content: 'z-[70]') and use that, or pass \`blocklists: { plainZIndex: false }\` to opt out.`,
|
|
33
|
+
},
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
/** Resolve the preset's `blocklist` array from the `blocklists` option (default all on). */
|
|
37
|
+
export function buildBlocklist(option: BlocklistsOption = true): BlocklistRule[] {
|
|
38
|
+
if (option === false)
|
|
39
|
+
return []
|
|
40
|
+
const plainZIndex = option === true ? true : option.plainZIndex !== false
|
|
41
|
+
return [
|
|
42
|
+
...(plainZIndex ? [plainZIndexBlocklist] : []),
|
|
43
|
+
]
|
|
44
|
+
}
|
package/unocss/colors.ts
CHANGED
|
@@ -76,9 +76,20 @@ export const error: ColorRamp = {
|
|
|
76
76
|
DEFAULT: '#f63d68',
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
79
|
+
/** Each stop paired with its target OKLCH lightness (0–1). */
|
|
80
|
+
const RAMP_STEPS = [
|
|
81
|
+
[50, 0.97],
|
|
82
|
+
[100, 0.94],
|
|
83
|
+
[200, 0.87],
|
|
84
|
+
[300, 0.78],
|
|
85
|
+
[400, 0.68],
|
|
86
|
+
[500, 0.58],
|
|
87
|
+
[600, 0.50],
|
|
88
|
+
[700, 0.42],
|
|
89
|
+
[800, 0.35],
|
|
90
|
+
[900, 0.28],
|
|
91
|
+
[950, 0.18],
|
|
92
|
+
] as const
|
|
82
93
|
|
|
83
94
|
/**
|
|
84
95
|
* Generate an 11-stop color ramp (`50`..`950` + `DEFAULT`) from a single color,
|
|
@@ -97,8 +108,7 @@ export function generateColorRamp(input: string): ColorRamp {
|
|
|
97
108
|
const [, chroma, rawHue] = new Color(input).to('oklch').coords
|
|
98
109
|
const hue = Number.isFinite(rawHue) ? rawHue : 0
|
|
99
110
|
const ramp: ColorRamp = { DEFAULT: input }
|
|
100
|
-
|
|
101
|
-
const l = RAMP_LIGHTNESS[i]
|
|
111
|
+
RAMP_STEPS.forEach(([stop, l]) => {
|
|
102
112
|
// Taper chroma at the lightest/darkest stops so they don't look muddy.
|
|
103
113
|
const c = (chroma || 0) * (l > 0.9 || l < 0.25 ? 0.6 : 1)
|
|
104
114
|
ramp[stop] = new Color('oklch', [l, c, hue]).to('srgb').toGamut({ space: 'srgb' }).toString({ format: 'hex' })
|
package/unocss/index.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { Preset, UserShortcuts } from '@unocss/core'
|
|
2
2
|
import type { PresetAnthonyDesignOptions } from './options'
|
|
3
3
|
import { definePreset, mergeDeep } from '@unocss/core'
|
|
4
|
+
import { buildBlocklist } from './blocklist'
|
|
4
5
|
import { error, resolvePrimary, success, warning } from './colors'
|
|
5
6
|
import { DEFAULT_DARK_BG, DEFAULT_FONTS } from './options'
|
|
6
7
|
import { patternRules } from './patterns'
|
|
@@ -26,12 +27,19 @@ function assertOptions(options: PresetAnthonyDesignOptions): void {
|
|
|
26
27
|
* consumer composes those themselves. The semantic layer is base-agnostic, so it
|
|
27
28
|
* resolves under Wind4, Wind3 or Mini.
|
|
28
29
|
*
|
|
30
|
+
* It deliberately ships **no z-index scale** — stacking is a whole-app concern.
|
|
31
|
+
* The overlay components reference named layers (`z-modal-content`, `z-dropdown`,
|
|
32
|
+
* …); define those in your own UnoCSS `shortcuts` config (see Setup). As a
|
|
33
|
+
* guardrail the preset blocks plain `z-<number>` utilities so every z-index goes
|
|
34
|
+
* through a named layer (opt out with `blocklists: { plainZIndex: false }`).
|
|
35
|
+
*
|
|
29
36
|
* @param options - Theme + dark-surface options (see {@link PresetAnthonyDesignOptions}).
|
|
30
37
|
* @returns A single UnoCSS `Preset`.
|
|
31
38
|
*
|
|
32
39
|
* @example
|
|
33
40
|
* ```ts
|
|
34
41
|
* import { presetAnthonyDesign } from '@antfu/design/unocss'
|
|
42
|
+
* import transformerDirectives from '@unocss/transformer-directives'
|
|
35
43
|
* import { defineConfig, presetIcons, presetWebFonts, presetWind4 } from 'unocss'
|
|
36
44
|
*
|
|
37
45
|
* export default defineConfig({
|
|
@@ -41,6 +49,8 @@ function assertOptions(options: PresetAnthonyDesignOptions): void {
|
|
|
41
49
|
* presetIcons(),
|
|
42
50
|
* presetWebFonts({ fonts: { sans: 'DM Sans', mono: 'DM Mono' } }),
|
|
43
51
|
* ],
|
|
52
|
+
* // Required — expands the token `--at-apply` directives in the shipped styles.
|
|
53
|
+
* transformers: [transformerDirectives()],
|
|
44
54
|
* })
|
|
45
55
|
* ```
|
|
46
56
|
*/
|
|
@@ -71,6 +81,8 @@ export const presetAnthonyDesign = definePreset((options: PresetAnthonyDesignOpt
|
|
|
71
81
|
...buildRules(darkBackground),
|
|
72
82
|
...severityShortcuts,
|
|
73
83
|
...extend,
|
|
84
|
+
// Appended last so a same-named entry wins over everything above it.
|
|
85
|
+
...(options.overrides ? [options.overrides] : []),
|
|
74
86
|
]
|
|
75
87
|
|
|
76
88
|
return {
|
|
@@ -78,11 +90,20 @@ export const presetAnthonyDesign = definePreset((options: PresetAnthonyDesignOpt
|
|
|
78
90
|
extendTheme: theme => mergeDeep(theme as any, themeOverrides as any),
|
|
79
91
|
shortcuts,
|
|
80
92
|
rules: patternRules,
|
|
93
|
+
// Best-practice guardrails (default all on); see the `blocklists` option.
|
|
94
|
+
blocklist: buildBlocklist(options.blocklists),
|
|
81
95
|
}
|
|
82
96
|
})
|
|
83
97
|
|
|
84
98
|
export default presetAnthonyDesign
|
|
85
99
|
|
|
100
|
+
export {
|
|
101
|
+
type BlocklistsOption,
|
|
102
|
+
buildBlocklist,
|
|
103
|
+
plainZIndexBlocklist,
|
|
104
|
+
RE_PLAIN_Z_INDEX,
|
|
105
|
+
} from './blocklist'
|
|
106
|
+
|
|
86
107
|
export {
|
|
87
108
|
type ColorRamp,
|
|
88
109
|
error as errorRamp,
|
package/unocss/options.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { UserShortcuts } from '@unocss/core'
|
|
2
|
+
import type { BlocklistsOption } from './blocklist'
|
|
2
3
|
import type { ColorRamp } from './colors'
|
|
3
4
|
|
|
4
5
|
/** Default near-black used for dark surfaces. Overridable via `darkBackground`. */
|
|
@@ -28,4 +29,19 @@ export interface PresetAnthonyDesignOptions {
|
|
|
28
29
|
theme?: Record<string, any>
|
|
29
30
|
/** Extra shortcuts appended after the built-in layer (so they can override it). */
|
|
30
31
|
extendShortcuts?: UserShortcuts
|
|
32
|
+
/**
|
|
33
|
+
* Override built-in shortcuts by name. Merged in with the **highest** precedence
|
|
34
|
+
* (after the built-in layer and `extendShortcuts`), so e.g.
|
|
35
|
+
* `{ 'bg-base': 'bg-zinc-50 dark:bg-zinc-950' }` redefines what `bg-base` resolves
|
|
36
|
+
* to. Use this to retune the semantic vocabulary; use `extendShortcuts` to add new
|
|
37
|
+
* ones.
|
|
38
|
+
*/
|
|
39
|
+
overrides?: Record<string, string>
|
|
40
|
+
/**
|
|
41
|
+
* Best-practice guardrails. Defaults to `true` (all on). Currently the only
|
|
42
|
+
* entry is `plainZIndex`, which blocks plain `z-<number>` / `z-[…]` utilities so
|
|
43
|
+
* every z-index goes through a named layer the app defines (see Setup). Pass
|
|
44
|
+
* `false` to disable all, or `{ plainZIndex: false }` to opt out of one.
|
|
45
|
+
*/
|
|
46
|
+
blocklists?: BlocklistsOption
|
|
31
47
|
}
|
package/unocss/shortcuts.ts
CHANGED
|
@@ -42,6 +42,9 @@ export function buildShortcuts(db: string): (StaticShortcutMap | DynamicShortcut
|
|
|
42
42
|
'btn-action-active': 'color-active border-active! bg-active op100!',
|
|
43
43
|
'btn-icon': 'w-9 h-9 rounded-full op-fade hover:op100 hover:bg-active transition flex items-center justify-center disabled:pointer-events-none disabled:op30 outline-none focus-visible:ring-2 focus-visible:ring-primary-500/40',
|
|
44
44
|
'btn-icon-compact': 'w-6 h-6 rounded op-fade hover:op100 hover:bg-active transition flex items-center justify-center disabled:pointer-events-none disabled:op30 outline-none focus-visible:ring-2 focus-visible:ring-primary-500/40',
|
|
45
|
+
// Bordered, square counterpart to the round/borderless `btn-icon` — for
|
|
46
|
+
// toolbar-style icon buttons that read as a distinct affordance.
|
|
47
|
+
'btn-icon-square': 'w-9 h-9 rounded border border-base op-fade hover:op100 hover:bg-active transition flex items-center justify-center disabled:pointer-events-none disabled:op30 outline-none focus-visible:ring-2 focus-visible:ring-primary-500/40',
|
|
45
48
|
'btn-primary': 'px3 py1.5 rounded flex gap-2 items-center bg-primary-500 hover:bg-primary-600 text-white transition disabled:op50 disabled:pointer-events-none outline-none focus-visible:ring-2 focus-visible:ring-primary-500/40',
|
|
46
49
|
|
|
47
50
|
// ── Badges ────────────────────────────────────────────────────────
|
|
@@ -54,15 +57,20 @@ export function buildShortcuts(db: string): (StaticShortcutMap | DynamicShortcut
|
|
|
54
57
|
'text-mini': 'text-[11px] leading-[1.45]',
|
|
55
58
|
'text-compact': 'text-[12px] leading-[1.5]',
|
|
56
59
|
|
|
57
|
-
// ──
|
|
58
|
-
'
|
|
59
|
-
'
|
|
60
|
-
'
|
|
61
|
-
'
|
|
62
|
-
'
|
|
63
|
-
'
|
|
64
|
-
'
|
|
65
|
-
|
|
60
|
+
// ── Safe-area padding (notches / home indicators) ─────────────────
|
|
61
|
+
'pad-safe-t': 'pt-[env(safe-area-inset-top)]',
|
|
62
|
+
'pad-safe-r': 'pr-[env(safe-area-inset-right)]',
|
|
63
|
+
'pad-safe-b': 'pb-[env(safe-area-inset-bottom)]',
|
|
64
|
+
'pad-safe-l': 'pl-[env(safe-area-inset-left)]',
|
|
65
|
+
'pad-safe-x': 'pad-safe-l pad-safe-r',
|
|
66
|
+
'pad-safe-y': 'pad-safe-t pad-safe-b',
|
|
67
|
+
'pad-safe': 'pad-safe-x pad-safe-y',
|
|
68
|
+
|
|
69
|
+
// NOTE: the preset deliberately ships **no** z-index scale. Stacking is a
|
|
70
|
+
// whole-app concern, so the layer values are the app's to own — it defines
|
|
71
|
+
// the named layers (`z-modal-content`, `z-dropdown`, …) that the overlay
|
|
72
|
+
// components reference in its own top-level UnoCSS `shortcuts`. The preset
|
|
73
|
+
// blocks plain `z-<number>` to keep usage semantic (see `./blocklist`).
|
|
66
74
|
},
|
|
67
75
|
]
|
|
68
76
|
}
|
package/utils/color.ts
CHANGED
|
@@ -243,8 +243,9 @@ export function getPluginColor(
|
|
|
243
243
|
const hues = map === defaultBrandHues ? defaultBrandHues : { ...defaultBrandHues, ...map }
|
|
244
244
|
const bare = stripPluginPrefix(name).toLowerCase()
|
|
245
245
|
const key = Object.keys(hues).find(k => bare === k || bare.startsWith(`${k}-`) || bare.startsWith(`${k}.`))
|
|
246
|
-
|
|
247
|
-
|
|
246
|
+
const hue = key != null ? hues[key] : undefined
|
|
247
|
+
if (hue != null)
|
|
248
|
+
return getHsla(hue, opacity, dark)
|
|
248
249
|
return getHashColorFromString(name, opacity, dark)
|
|
249
250
|
}
|
|
250
251
|
|
package/utils/format.ts
CHANGED
|
@@ -47,7 +47,7 @@ export function mapSeverity(value: number, scale: SeverityScale): ColorScaleClas
|
|
|
47
47
|
if (value <= max)
|
|
48
48
|
return cls
|
|
49
49
|
}
|
|
50
|
-
return scale
|
|
50
|
+
return scale.at(-1)?.[1] ?? colorScale.neutral
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
// ── Locale ──────────────────────────────────────────────────────────────────
|
|
@@ -159,7 +159,7 @@ export function formatBytes(bytes: number, options: FormatBytesOptions = {}): [s
|
|
|
159
159
|
if (i === 0)
|
|
160
160
|
return [String(bytes), 'B']
|
|
161
161
|
const value = (bytes / base ** i).toFixed(digits).replace(/\.?0+$/, '')
|
|
162
|
-
return [value, units[i]]
|
|
162
|
+
return [value, units[i] ?? 'B']
|
|
163
163
|
}
|
|
164
164
|
|
|
165
165
|
/**
|
package/utils/keybinding.ts
CHANGED
|
@@ -78,8 +78,9 @@ export function parseChord(input: string): ParsedChord {
|
|
|
78
78
|
let key = ''
|
|
79
79
|
for (const token of tokens) {
|
|
80
80
|
const lower = token.toLowerCase()
|
|
81
|
-
|
|
82
|
-
|
|
81
|
+
const mod = MOD_ALIASES[lower]
|
|
82
|
+
if (mod != null)
|
|
83
|
+
modifiers.add(mod)
|
|
83
84
|
else
|
|
84
85
|
key = KEY_ALIASES[lower] ?? (token.length === 1 ? token.toLowerCase() : token)
|
|
85
86
|
}
|
|
@@ -177,7 +178,7 @@ const KEY_GLYPHS: Record<string, string> = {
|
|
|
177
178
|
* // → ['⌃', 'K'] on macOS, ['Ctrl', 'K'] elsewhere
|
|
178
179
|
*/
|
|
179
180
|
export function chordDisplay(chord: ParsedChord): string[] {
|
|
180
|
-
const mods = chord.modifiers.map(m => isMac ? GLYPHS_MAC[m] : LABELS[m])
|
|
181
|
+
const mods = chord.modifiers.map(m => (isMac ? GLYPHS_MAC[m] : LABELS[m]) ?? m)
|
|
181
182
|
const key = KEY_GLYPHS[chord.key] ?? (chord.key.length === 1 ? chord.key.toUpperCase() : chord.key)
|
|
182
183
|
return [...mods, key]
|
|
183
184
|
}
|
package/utils/path.ts
CHANGED
|
@@ -77,7 +77,7 @@ export interface PnpmPackageInfo {
|
|
|
77
77
|
*/
|
|
78
78
|
export function parsePnpmSegment(segment: string): PnpmPackageInfo | undefined {
|
|
79
79
|
// Strip peer-dependency suffix (`_react@18...`).
|
|
80
|
-
const base = segment.split('_')[0]
|
|
80
|
+
const base = segment.split('_')[0] ?? ''
|
|
81
81
|
const at = base.lastIndexOf('@')
|
|
82
82
|
if (at <= 0)
|
|
83
83
|
return undefined
|
|
@@ -109,7 +109,7 @@ export function parsePnpmSegment(segment: string): PnpmPackageInfo | undefined {
|
|
|
109
109
|
*/
|
|
110
110
|
export function getPnpmPackageInfoFromPath(path: string): PnpmPackageInfo | undefined {
|
|
111
111
|
const match = normalizeModulePath(path).match(/\.pnpm\/([^/]+)/)
|
|
112
|
-
if (
|
|
112
|
+
if (match?.[1] == null)
|
|
113
113
|
return undefined
|
|
114
114
|
return parsePnpmSegment(match[1])
|
|
115
115
|
}
|
|
@@ -138,8 +138,10 @@ export function getModuleNameFromPath(path: string): string | undefined {
|
|
|
138
138
|
if (idx === -1)
|
|
139
139
|
return undefined
|
|
140
140
|
const rest = normalized.slice(idx + 'node_modules/'.length)
|
|
141
|
-
const
|
|
142
|
-
|
|
141
|
+
const [first, second] = rest.split('/')
|
|
142
|
+
if (first == null)
|
|
143
|
+
return undefined
|
|
144
|
+
return first.startsWith('@') && second != null ? `${first}/${second}` : first
|
|
143
145
|
}
|
|
144
146
|
|
|
145
147
|
/**
|
package/utils/semver.ts
CHANGED
|
@@ -47,9 +47,10 @@ export function compareSemver(a: string, b: string): number {
|
|
|
47
47
|
const pb = parseVersion(b)
|
|
48
48
|
if (!pa || !pb)
|
|
49
49
|
return 0
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
50
|
+
const core = [[pa[0], pb[0]], [pa[1], pb[1]], [pa[2], pb[2]]] as const
|
|
51
|
+
for (const [x, y] of core) {
|
|
52
|
+
if (x !== y)
|
|
53
|
+
return x < y ? -1 : 1
|
|
53
54
|
}
|
|
54
55
|
// Equal core: a version without prerelease is greater than one with.
|
|
55
56
|
if (pa[3] === pb[3])
|
package/utils/tree.ts
CHANGED
|
@@ -24,6 +24,8 @@ export interface ToTreeOptions {
|
|
|
24
24
|
function flattenChain<T>(node: TreeNode<T>, separator: string): void {
|
|
25
25
|
while (node.children.length === 1 && node.item == null) {
|
|
26
26
|
const child = node.children[0]
|
|
27
|
+
if (child == null)
|
|
28
|
+
break
|
|
27
29
|
node.name = `${node.name}${separator}${child.name}`
|
|
28
30
|
node.path = child.path
|
|
29
31
|
node.item = child.item
|