@rimelight/ui 0.0.43 → 0.0.44

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 (153) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +14 -12
  3. package/package.json +11 -14
  4. package/src/components/avatar/RLAAvatar.astro +4 -12
  5. package/src/components/badge/RLABadge.astro +2 -1
  6. package/src/components/badge/badge.theme.ts +0 -4
  7. package/src/components/banner/RLABanner.astro +1 -1
  8. package/src/components/banner/banner.theme.ts +1 -1
  9. package/src/components/breadcrumb/RLABreadcrumb.astro +2 -2
  10. package/src/components/button/RLAButton.astro +4 -1
  11. package/src/components/button/button.theme.ts +1 -5
  12. package/src/components/callout/RLACallout.astro +7 -5
  13. package/src/components/callout/callout.theme.ts +1 -5
  14. package/src/components/card/card.theme.ts +3 -3
  15. package/src/components/carousel/RLACarousel.astro +2 -2
  16. package/src/components/carousel/carousel.theme.ts +2 -2
  17. package/src/components/chart/RLAChart.astro +1 -1
  18. package/src/components/checkbox/RLACheckbox.astro +2 -1
  19. package/src/components/checkbox/checkbox.theme.ts +0 -4
  20. package/src/components/chip/RLAChip.astro +2 -1
  21. package/src/components/chip/chip.theme.ts +0 -4
  22. package/src/components/collapsible/RLACollapsible.astro +1 -1
  23. package/src/components/color-area/RLAColorArea.astro +1 -0
  24. package/src/components/color-area/color-area.theme.ts +1 -5
  25. package/src/components/color-field/RLAColorField.astro +1 -0
  26. package/src/components/color-field/color-field.theme.ts +1 -5
  27. package/src/components/color-slider/RLAColorSlider.astro +1 -0
  28. package/src/components/color-slider/color-slider.theme.ts +1 -5
  29. package/src/components/dashboard-group/RLADashboardGroup.astro +5 -7
  30. package/src/components/dashboard-group/dashboard-group.theme.ts +1 -1
  31. package/src/components/dashboard-group/dashboard-group.ts +1 -1
  32. package/src/components/dashboard-navbar/RLADashboardNavbar.astro +4 -4
  33. package/src/components/dashboard-navbar/dashboard-navbar.theme.ts +3 -3
  34. package/src/components/dashboard-navbar/dashboard-navbar.ts +1 -1
  35. package/src/components/dashboard-panel/RLADashboardPanel.astro +9 -8
  36. package/src/components/dashboard-panel/dashboard-panel.theme.ts +1 -1
  37. package/src/components/dashboard-sidebar/RLADashboardSidebar.astro +7 -4
  38. package/src/components/dashboard-sidebar/dashboard-sidebar.theme.ts +6 -5
  39. package/src/components/dashboard-sidebar-collapse/RLADashboardSidebarCollapse.astro +1 -0
  40. package/src/components/drawer/RLADrawer.astro +1 -1
  41. package/src/components/dropdown-menu/dropdown-menu.theme.ts +2 -2
  42. package/src/components/footer/RLAFooter.astro +2 -0
  43. package/src/components/footer/footer.theme.ts +0 -8
  44. package/src/components/gamepad/RLAGamepad.astro +1 -1
  45. package/src/components/head/UIHead.astro +1 -2
  46. package/src/components/header/header.theme.ts +1 -1
  47. package/src/components/icon/RLAIcon.astro +2 -5
  48. package/src/components/icon/RLSIcon.tsx +2 -2
  49. package/src/components/icon/RLVIcon.vue +18 -6
  50. package/src/components/icon/icon.theme.ts +1 -1
  51. package/src/components/image/image.theme.ts +1 -1
  52. package/src/components/input/RLAInput.astro +2 -1
  53. package/src/components/input/input.theme.ts +0 -4
  54. package/src/components/input-menu/RLAInputMenu.astro +2 -2
  55. package/src/components/input-menu/input-menu.theme.ts +1 -1
  56. package/src/components/input-rating/input-rating.theme.ts +2 -2
  57. package/src/components/kbd/RLAKbd.astro +2 -1
  58. package/src/components/kbd/kbd.theme.ts +0 -4
  59. package/src/components/keyboard/RLAKeyboard.astro +1 -1
  60. package/src/components/link/RLALink.astro +1 -0
  61. package/src/components/link/link.theme.ts +0 -4
  62. package/src/components/link-group/RLALinkGroup.astro +1 -1
  63. package/src/components/link-group/link-group.theme.ts +1 -10
  64. package/src/components/locale-selector/RLALocaleSelector.astro +4 -36
  65. package/src/components/locale-selector/locale-selector.theme.ts +3 -11
  66. package/src/components/logo/RLALogo.astro +1 -2
  67. package/src/components/modal/RLAModal.astro +1 -1
  68. package/src/components/modal/RLSModal.tsx +2 -2
  69. package/src/components/navigation-menu/RLANavigationMenu.astro +17 -21
  70. package/src/components/navigation-menu/navigation-menu.theme.ts +3 -7
  71. package/src/components/pagination/RLAPagination.astro +2 -2
  72. package/src/components/pagination/pagination.theme.ts +1 -1
  73. package/src/components/popover/RLAPopover.astro +39 -0
  74. package/src/components/progress/RLAProgress.astro +2 -0
  75. package/src/components/progress/progress.theme.ts +0 -4
  76. package/src/components/scroll-area/scroll-area.theme.ts +1 -1
  77. package/src/components/scroll-to-top/RLAScrollToTop.astro +6 -5
  78. package/src/components/scroll-to-top/RLVScrollToTop.vue +7 -7
  79. package/src/components/scroll-to-top/scroll-to-top.theme.ts +3 -7
  80. package/src/components/search/RLASearch.astro +3 -3
  81. package/src/components/select/RLASelect.astro +96 -528
  82. package/src/components/select/RLSSelect.tsx +68 -14
  83. package/src/components/select/RLVSelect.vue +57 -0
  84. package/src/components/select/select.theme.ts +11 -96
  85. package/src/components/shortcuts/RLAShortcuts.astro +9 -9
  86. package/src/components/sidebar/RLASidebar.astro +3 -3
  87. package/src/components/slideover/RLASlideover.astro +1 -1
  88. package/src/components/slider/RLASlider.astro +2 -1
  89. package/src/components/slider/slider.theme.ts +1 -5
  90. package/src/components/spinner/RLASpinner.astro +2 -1
  91. package/src/components/spinner/spinner.theme.ts +1 -5
  92. package/src/components/splitter/splitter.theme.ts +1 -1
  93. package/src/components/stepper/RLAStepper.astro +8 -8
  94. package/src/components/stepper/stepper.theme.ts +6 -7
  95. package/src/components/steps/RLASteps.astro +3 -3
  96. package/src/components/switch/RLASwitch.astro +2 -1
  97. package/src/components/switch/switch.theme.ts +0 -4
  98. package/src/components/table/table.theme.ts +1 -1
  99. package/src/components/table-of-contents/RLATableOfContents.astro +5 -5
  100. package/src/components/theme-selector/RLAThemeSelector.astro +16 -14
  101. package/src/components/theme-selector/theme-selector.theme.ts +2 -2
  102. package/src/components/toast/RLAToast.astro +6 -6
  103. package/src/components/toast/store.ts +1 -1
  104. package/src/components/tooltip/tooltip.theme.ts +1 -1
  105. package/src/icons.raw.ts +67 -0
  106. package/src/icons.ts +31 -64
  107. package/src/index.ts +19 -18
  108. package/src/preset.ts +110 -108
  109. package/src/resolver.ts +127 -15
  110. package/src/shortcuts.ts +103 -84
  111. package/src/stores.ts +22 -0
  112. package/src/types.ts +27 -9
  113. package/src/virtual.d.ts +4 -0
  114. package/src/components/editor/PageEditor.vue +0 -459
  115. package/src/components/editor/PageMention.vue +0 -87
  116. package/src/components/editor/PagePropertiesEditor.vue +0 -358
  117. package/src/components/editor/PagePropertiesRenderer.vue +0 -285
  118. package/src/components/editor/PageRenderer.vue +0 -155
  119. package/src/components/editor/PageSurround.vue +0 -130
  120. package/src/components/editor/PageVersionSelector.vue +0 -371
  121. package/src/components/editor/blocks/AddBlockModal.vue +0 -196
  122. package/src/components/editor/blocks/Block.vue +0 -142
  123. package/src/components/editor/blocks/BlockEditRenderer.vue +0 -26
  124. package/src/components/editor/blocks/BlockEditor.vue +0 -141
  125. package/src/components/editor/blocks/BlockViewRenderer.vue +0 -71
  126. package/src/components/editor/blocks/TextRenderer.vue +0 -31
  127. package/src/components/editor/blocks/editor/CalloutBlockEditor.vue +0 -53
  128. package/src/components/editor/blocks/editor/ImageBlockEditor.vue +0 -55
  129. package/src/components/editor/blocks/editor/ParagraphBlockEditor.vue +0 -31
  130. package/src/components/editor/blocks/editor/SectionBlockEditor.vue +0 -65
  131. package/src/components/editor/blocks/nodes/LinkNode.vue +0 -28
  132. package/src/components/editor/blocks/nodes/TextNode.vue +0 -22
  133. package/src/components/editor/blocks/renderer/CalloutBlockRenderer.vue +0 -65
  134. package/src/components/editor/blocks/renderer/ImageBlockRenderer.vue +0 -20
  135. package/src/components/editor/blocks/renderer/ParagraphBlockRenderer.vue +0 -21
  136. package/src/components/editor/blocks/renderer/SectionBlockRenderer.vue +0 -55
  137. package/src/components/editor/composables/injectionKeys.ts +0 -3
  138. package/src/components/editor/composables/useBlockEditor.ts +0 -261
  139. package/src/components/editor/composables/usePageEditor.ts +0 -97
  140. package/src/components/editor/modals/CreatePageModal.vue +0 -160
  141. package/src/components/editor/modals/DeletePageModal.vue +0 -116
  142. package/src/components/editor/modals/PageTreeModal.vue +0 -275
  143. package/src/components/editor/types/blocks.ts +0 -100
  144. package/src/components/editor/types/pages.ts +0 -89
  145. package/src/components/editor/types/schemas.ts +0 -36
  146. package/src/components/editor/utils/blocks.ts +0 -42
  147. package/src/components/editor/utils/editorHelpers.ts +0 -24
  148. package/src/components/editor/utils/page.ts +0 -219
  149. package/src/components/editor/utils/richTextHelpers.ts +0 -35
  150. package/src/components/editor/views/PageEditView.vue +0 -195
  151. package/src/components/editor/views/PageLiveView.vue +0 -90
  152. package/src/components/editor/views/PageReviewView.vue +0 -308
  153. package/src/components/icon/resolver.ts +0 -23
package/src/preset.ts CHANGED
@@ -1,7 +1,8 @@
1
1
  import { presetIcons, presetTypography, presetWind4, type Preset } from "unocss"
2
- import type { ColorModeInput, PresetOptions } from "./types"
2
+ import type { ColorModeInput, UIConfig } from "./types"
3
+ import { getIconSvg } from "./icons.raw"
3
4
 
4
- export const P = {
5
+ export const PALETTES = {
5
6
  neutral: [0, 0],
6
7
  primary: [0.21, 260],
7
8
  secondary: [0.18, 70],
@@ -14,132 +15,133 @@ export const P = {
14
15
  source: [0.13, 215]
15
16
  } as const
16
17
 
17
- const S =
18
- "50:0.985:0.1:0.145:0.2,100:0.965:0.25:0.2:0.35,200:0.925:0.45:0.28:0.55,300:0.85:0.7:0.35:0.75,400:0.72:0.9:0.44:0.9,500:0.55:1:0.53:1,600:0.44:0.9:0.61:0.85,700:0.35:0.75:0.69:0.7,800:0.27:0.6:0.77:0.55,900:0.2:0.45:0.86:0.4,950:0.145:0.3:0.93:0.25"
19
- .split(",")
20
- .map((s) => s.split(":") as [string, any, any, any, any]),
21
- INTENTS: Record<string, Record<string, string>> = Object.fromEntries(
22
- "default:700:50:200:300,muted:600:100:200:300,elevated:800:100:200:400,accented:900:200:300:500,dimmed:500:50:300:400,toned:800:700:600:900,highlighted:950:200:800:950,inverted:50:950:800:700"
23
- .split(",")
24
- .map((s) => s.split(":") as [string, string, string, string, string])
25
- .map(([k, text, bg, border, ring]) => [k, { text, bg, border, ring }])
26
- ),
27
- CSS_PROPS: Record<string, string> = {
28
- text: "color",
29
- bg: "background-color",
30
- border: "border-color",
31
- divide: "border-color",
32
- ring: "--un-ring-color",
33
- outline: "outline-color"
34
- },
35
- emit = (n: string, fn: (s: string, l1: number, c1: number, l2: number, c2: number) => string) =>
36
- S.map(([s, l1, c1, l2, c2]) => ` --color-${n}-${s}: ${fn(s, l1, c1, l2, c2)};\n`).join("") +
37
- ` --${n}: var(--color-${n}-500);\n --${n}-foreground: light-dark(oklch(1 0 0), oklch(0.145 0 0));\n --${n}-accent: var(--color-${n}-600);\n`,
38
- cssVars = (custom: Record<string, ColorModeInput> = {}) =>
39
- Object.entries(P)
40
- .map(([n, [c, h]]) =>
41
- emit(
42
- n,
43
- (_, l1, c1, l2, c2) =>
44
- `light-dark(oklch(${l1} ${(c1 * c).toFixed(3)} ${h}), oklch(${l2} ${(c2 * c).toFixed(3)} ${h}))`
45
- )
46
- )
47
- .join("") +
48
- Object.entries(custom)
49
- .map(([n, e]) => {
50
- const m = (e.light || e.dark ? e : { light: e, dark: e }) as Record<
51
- string,
52
- Record<string, string>
53
- >,
54
- l = m.light?.["500"] ?? Object.values(m.light ?? {})[0] ?? "oklch(0.55 0.15 0)"
55
- return emit(
56
- n,
57
- (s) => `light-dark(${m.light?.[s] ?? l}, ${m.dark?.[s] ?? m.dark?.["500"] ?? l})`
58
- )
59
- })
60
- .join("")
18
+ const SHADES = {
19
+ "50": [0.985, 0.1, 0.145, 0.2],
20
+ "100": [0.965, 0.25, 0.2, 0.35],
21
+ "200": [0.925, 0.45, 0.28, 0.55],
22
+ "300": [0.85, 0.7, 0.35, 0.75],
23
+ "400": [0.72, 0.9, 0.44, 0.9],
24
+ "500": [0.55, 1, 0.53, 1],
25
+ "600": [0.44, 0.9, 0.61, 0.85],
26
+ "700": [0.35, 0.75, 0.69, 0.7],
27
+ "800": [0.27, 0.6, 0.77, 0.55],
28
+ "900": [0.2, 0.45, 0.86, 0.4],
29
+ "950": [0.145, 0.3, 0.93, 0.25]
30
+ } as const
31
+
32
+ const SHADE_ENTRIES = Object.entries(SHADES)
33
+
34
+ const INTENTS: Record<string, Record<string, string>> = {
35
+ default: { text: "700", bg: "50", border: "200", ring: "300" },
36
+ muted: { text: "600", bg: "100", border: "200", ring: "300" },
37
+ elevated: { text: "800", bg: "100", border: "200", ring: "400" },
38
+ accented: { text: "900", bg: "200", border: "300", ring: "500" },
39
+ dimmed: { text: "500", bg: "50", border: "300", ring: "400" },
40
+ toned: { text: "800", bg: "700", border: "600", ring: "900" },
41
+ highlighted: { text: "950", bg: "200", border: "800", ring: "950" },
42
+ inverted: { text: "50", bg: "950", border: "800", ring: "700" }
43
+ }
44
+
45
+ const createThemeShades = (name: string) => ({
46
+ DEFAULT: `var(--rl-${name}-500)`,
47
+ ...Object.fromEntries(SHADE_ENTRIES.map(([s]) => [s, `var(--rl-${name}-${s})`]))
48
+ })
49
+
50
+ const createOklchShadeGetter = ([c, h]: readonly [number, number]) => {
51
+ const oklch = (l: number, factor: number) => `oklch(${l} ${(factor * c).toFixed(3)} ${h})`
52
+ return (_: string, [l1, c1, l2, c2]: readonly [number, number, number, number]) => [
53
+ oklch(l1, c1),
54
+ oklch(l2, c2)
55
+ ]
56
+ }
57
+
58
+ const createCustomShadeGetter = (val: any) => {
59
+ const isSplit = "light" in val || "dark" in val
60
+ const light = (isSplit ? val.light : val) ?? {}
61
+ const dark = (isSplit ? val.dark : light) ?? light
62
+ const fallback = light["500"] ?? Object.values(light)[0] ?? "oklch(0.55 0.15 0)"
63
+ return (s: string) => [light[s] ?? fallback, dark[s] ?? fallback]
64
+ }
65
+
66
+ function resolveTokens(custom: Record<string, ColorModeInput> = {}) {
67
+ const themeColors: Record<string, Record<string, string>> = {}
68
+ const tokenVarLines: string[] = []
69
+
70
+ for (const [name, val] of Object.entries({ ...PALETTES, ...custom })) {
71
+ themeColors[name] = createThemeShades(name)
72
+ const getShade = Array.isArray(val) ? createOklchShadeGetter(val) : createCustomShadeGetter(val)
73
+
74
+ for (const [s, coords] of SHADE_ENTRIES) {
75
+ const [light, dark] = getShade(s, coords as any)
76
+ tokenVarLines.push(`--rl-${name}-${s}: light-dark(${light}, ${dark});`)
77
+ }
78
+ }
79
+
80
+ return { themeColors, tokenVarLines }
81
+ }
82
+
83
+ export default function rimelightUiPreset({ colors, icons, presets = {} }: UIConfig = {}): Preset {
84
+ const { themeColors, tokenVarLines } = resolveTokens(colors)
61
85
 
62
- export default function rimelightUiPreset(options: PresetOptions = {}): Preset {
63
- const allColors = Object.keys({ ...P, ...options.colors })
64
86
  return {
65
87
  name: "rimelight-ui",
66
88
  outputToCssLayers: true,
67
89
  safelist: [
68
- ...allColors.map((c) => `rl-color-${c}`),
69
- ...Object.values(options.icons ?? {}).filter(
90
+ ...Object.keys(themeColors).map((c) => `rl-color-${c}`),
91
+ ...Object.values(icons ?? {}).filter(
70
92
  (v): v is string => typeof v === "string" && v.startsWith("i-")
71
93
  )
72
94
  ],
73
95
  presets: [
74
- options.presets?.wind !== false && presetWind4(),
75
- options.presets?.typography !== false && presetTypography(),
76
- options.presets?.icons !== false && presetIcons()
96
+ presets.wind !== false && presetWind4(),
97
+ presets.typography !== false && presetTypography(),
98
+ presets.icons !== false && presetIcons({ collections: { rl: getIconSvg } }),
99
+ {
100
+ name: "rimelight-theme",
101
+ theme: {
102
+ spacing: { md: "1rem" },
103
+ colors: themeColors
104
+ }
105
+ }
77
106
  ].filter(Boolean) as Preset[],
107
+ shortcuts: [
108
+ [
109
+ /^(text|bg|ring|border)-([a-z]+)(?:\/(\d+))?$/,
110
+ ([, p, n, o]) => {
111
+ const s = INTENTS[n!]?.[p!]
112
+ if (!s) return
113
+ const base = `${p}-neutral-${s}${o ? `/${o}` : ""}`
114
+ return p === "bg" && n === "inverted" ? `${base} text-neutral-50` : base
115
+ }
116
+ ]
117
+ ],
78
118
  rules: [
79
119
  [
80
120
  /^rl-color-([\w-]+)$/,
81
- ([, c]) => ({
82
- "--rl-bg": `var(--color-${c}-500)`,
83
- "--rl-ring": `var(--color-${c}-500)`,
84
- "--rl-border": `var(--color-${c}-500)`,
85
- "--rl-bg-hover": `var(--color-${c}-600)`,
86
- "--rl-text": `var(--color-${c}-600)`
87
- })
88
- ],
89
- [
90
- /^(text|bg|ring|border|outline|divide)-([a-z]+)(?:\/(\d+))?$/,
91
- ([, p, n, o]) => {
92
- const prop = CSS_PROPS[p!],
93
- s = INTENTS[n!]?.[p === "divide" ? "border" : p === "outline" ? "ring" : p!]
94
- if (!prop || !s) return
95
- const b = `var(--color-neutral-${s})`
121
+ ([, c]) => {
122
+ const v500 = `var(--rl-${c}-500)`
123
+ const v600 = `var(--rl-${c}-600)`
96
124
  return {
97
- [prop]: o ? `color-mix(in srgb, ${b} ${o}%, transparent)` : b,
98
- ...(p === "bg" && n === "inverted" && { color: "var(--color-neutral-50)" })
125
+ "--rl-bg": v500,
126
+ "--rl-ring": v500,
127
+ "--rl-border": v500,
128
+ "--rl-bg-hover": v600,
129
+ "--rl-text": v600
99
130
  }
100
131
  }
101
- ],
102
- [
103
- /^rl-select$/,
104
- () => `
105
- select.rl-select, select.rl-select::picker(select) { appearance: base-select; }
106
- select.rl-select::picker-icon { display: none; }
107
- select.rl-select::picker(select) { border: 1px solid var(--color-neutral-200, rgba(255, 255, 255, 0.1)); border-radius: 0.5rem; background-color: var(--color-neutral-100, #18181b); color: var(--color-neutral-950, #f4f4f5); box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.5), 0 4px 6px -4px rgba(0, 0, 0, 0.5); padding: 0.25rem; margin-top: 0.25rem; }
108
- select.rl-select option { padding: 0.375rem 0.625rem; border-radius: 0.375rem; background-color: var(--color-neutral-100, #18181b); color: var(--color-neutral-950, #f4f4f5); font-size: 0.875rem; cursor: pointer; transition: background-color 0.15s ease, color 0.15s ease; }
109
- select.rl-select option:hover, select.rl-select option:focus { background-color: var(--color-neutral-200, #27272a); color: var(--color-neutral-950, #ffffff); }
110
- select.rl-select option:checked { background-color: var(--color-primary-500, #3b82f6); color: #ffffff; font-weight: 500; }
111
- `
112
132
  ]
113
133
  ],
114
- theme: {
115
- spacing: Object.fromEntries(
116
- "6xs:0.125,5xs:0.25,4xs:0.375,3xs:0.5,2xs:0.625,xs:0.75,sm:0.875,md:1,lg:1.25,xl:1.5,2xl:2,3xl:3,4xl:4,5xl:6,6xl:8"
117
- .split(",")
118
- .map((s) => s.split(":") as [string, string])
119
- .map(([k, v]) => [k, `${v}rem`])
120
- ),
121
- radius: Object.fromEntries(
122
- "xs:- 0.375,sm:- 0.25,md:- 0.125,lg:,xl:+ 0.25,2xl:+ 0.5,3xl:+ 1"
123
- .split(",")
124
- .map((s) => s.split(":") as [string, string])
125
- .map(([k, v]) => [k, v ? `calc(var(--radius) ${v}rem)` : "var(--radius)"])
126
- ),
127
- colors: Object.fromEntries(
128
- allColors.map((c) => [
129
- c,
130
- {
131
- DEFAULT: `var(--${c})`,
132
- foreground: `var(--${c}-foreground)`,
133
- accent: `var(--${c}-accent)`,
134
- ...Object.fromEntries(S.map(([s]) => [s, `var(--color-${c}-${s})`]))
135
- }
136
- ])
137
- )
138
- },
139
134
  preflights: [
140
135
  {
141
- getCSS: () =>
142
- `:root {\n color-scheme: light dark;\n --radius: 0.625rem;\n --rl-header-height: 4rem;\n --rl-container: 90rem;\n}\n:root, .dark, .light, .rl-dark, .rl-light, [data-theme="dark"], [data-theme="light"] {\n${cssVars(options.colors)}}\n.dark, [data-theme="dark"], .rl-dark { color-scheme: dark; }\n.light, [data-theme="light"], .rl-light { color-scheme: light; }\nbody { background-color: light-dark(oklch(1 0 0), var(--color-neutral-50)); color: var(--color-neutral-950); }`
136
+ getCSS: () => `:root {
137
+ color-scheme: light dark;
138
+ --rl-header-height: 4rem;
139
+ --rl-container: 90rem;
140
+ ${tokenVarLines.join("\n ")}
141
+ }
142
+ [data-theme="dark"] { color-scheme: dark; }
143
+ [data-theme="light"] { color-scheme: light; }
144
+ body { background-color: var(--rl-neutral-50); color: var(--rl-neutral-950); }`
143
145
  }
144
146
  ]
145
147
  }
package/src/resolver.ts CHANGED
@@ -1,20 +1,132 @@
1
- import { scv, cx } from "css-variants"
2
- import type { UIConfig } from "./types"
1
+ import uiConfig from "virtual:rimelight/ui"
2
+ import type { ClassValue, ThemeConfig } from "./types"
3
3
 
4
- export const uiConfig: UIConfig = import.meta.env.RIMELIGHT_UI_CONFIG ?? {}
4
+ export function cx(...args: ClassValue[]): string {
5
+ let str = ""
6
+ for (let i = 0; i < args.length; i++) {
7
+ const val = args[i]
8
+ if (!val) continue
9
+ if (typeof val === "string" || typeof val === "number") {
10
+ str += (str ? " " : "") + val
11
+ } else if (Array.isArray(val)) {
12
+ if (val.length) {
13
+ const inner = cx(...val)
14
+ if (inner) str += (str ? " " : "") + inner
15
+ }
16
+ } else if (typeof val === "object") {
17
+ for (const k in val) {
18
+ if (val[k]) str += (str ? " " : "") + k
19
+ }
20
+ }
21
+ }
22
+ return str
23
+ }
24
+
25
+ export function defineTheme<const S extends string>(name: string, themeConfig: ThemeConfig<S>) {
26
+ const custom = uiConfig?.components?.themes?.[name]
27
+ const slots = (custom?.slots ?? themeConfig.slots ?? ["root" as S]) as readonly S[]
28
+ const rootSlot = (slots.includes("root" as S) ? "root" : slots[0] || "root") as S
29
+
30
+ if (uiConfig?.unstyled) {
31
+ return (props: any = {}): Record<S, string> => {
32
+ const res = {} as Record<S, string>
33
+ const hasClass = props.class || props.className
34
+ for (let i = 0; i < slots.length; i++) {
35
+ const slot = slots[i]!
36
+ const base = slot === rootSlot && hasClass ? cx(props.class, props.className) : ""
37
+ const fn = props.ui?.[slot]
38
+ res[slot] = typeof fn === "function" ? fn(base) : cx(base, fn)
39
+ }
40
+ return res
41
+ }
42
+ }
43
+
44
+ const autoColor = (custom?.variants ?? themeConfig.variants)?.color === "auto"
45
+ const defaultVariants = { ...themeConfig.defaultVariants, ...custom?.defaultVariants }
46
+ const compoundVariants = [
47
+ ...(themeConfig.compoundVariants ?? []),
48
+ ...(custom?.compoundVariants ?? [])
49
+ ]
50
+
51
+ const compiledCV = compoundVariants.map((cv) => {
52
+ const { classNames, class: cls, ...rest } = cv
53
+ const matchers: Array<{ key: string; exp: any }> = []
54
+ for (const k in rest) {
55
+ matchers.push({ key: k, exp: rest[k] })
56
+ }
57
+ return { matchers, cls: classNames || cls }
58
+ })
59
+
60
+ const baseMap: Record<string, string> = {}
61
+ for (let i = 0; i < slots.length; i++) {
62
+ const s = slots[i]!
63
+ baseMap[s] = cx(themeConfig.base?.[s], custom?.base?.[s])
64
+ }
65
+
66
+ const variants: Record<string, Record<string, any>> = { ...themeConfig.variants }
67
+ if (custom?.variants) {
68
+ for (const k in custom.variants) variants[k] = { ...variants[k], ...custom.variants[k] }
69
+ }
70
+
71
+ const variantKeys = Object.keys(variants).filter((k) => !(k === "color" && autoColor))
5
72
 
6
- export const defineTheme = <const S extends string>(name: string, theme: any) => {
7
- const root = (theme.slots.includes("root") ? "root" : theme.slots[0]) as S,
8
- autoColor = theme.variants?.color === "auto",
9
- scvFn = scv(autoColor ? { ...theme, variants: { ...theme.variants, color: {} } } : theme),
10
- globalSlots = uiConfig.components?.themes?.[name]?.slots
11
73
  return (props: any = {}): Record<S, string> => {
12
- if (uiConfig.unstyled) return { [root]: cx(props.class, props.className) } as any
13
- const slots = scvFn(props),
14
- color = autoColor && (props.color ?? theme.defaultVariants?.color)
15
- slots[root] = cx(slots[root], color && `rl-color-${color}`, props.class, props.className)
16
- for (const [k, v] of Object.entries({ ...globalSlots, ...props.ui }))
17
- slots[k] = cx(slots[k], typeof v === "function" ? v(slots[k] || "") : v)
18
- return slots
74
+ const res = {} as Record<S, string>
75
+ for (let i = 0; i < slots.length; i++) {
76
+ res[slots[i]!] = baseMap[slots[i]!] || ""
77
+ }
78
+
79
+ const values = { ...defaultVariants, ...props }
80
+ const color = autoColor ? (props.color ?? defaultVariants.color) : undefined
81
+
82
+ for (let k = 0; k < variantKeys.length; k++) {
83
+ const key = variantKeys[k]!
84
+ const selected = values[key]
85
+ if (selected != null && variants[key]?.[selected]) {
86
+ const layer = variants[key][selected]
87
+ for (let i = 0; i < slots.length; i++) {
88
+ const s = slots[i]!
89
+ const l = layer[s]
90
+ if (l) res[s] = res[s] ? `${res[s]} ${cx(l)}` : cx(l)
91
+ }
92
+ }
93
+ }
94
+
95
+ if (compiledCV.length) {
96
+ for (let c = 0; c < compiledCV.length; c++) {
97
+ const { matchers, cls } = compiledCV[c]!
98
+ if (!cls) continue
99
+ let match = true
100
+ for (let m = 0; m < matchers.length; m++) {
101
+ const { key, exp } = matchers[m]!
102
+ const actual = values[key]
103
+ if (Array.isArray(exp) ? !exp.includes(actual) : actual !== exp) {
104
+ match = false
105
+ break
106
+ }
107
+ }
108
+ if (match) {
109
+ for (let i = 0; i < slots.length; i++) {
110
+ const s = slots[i]!
111
+ const l = cls[s]
112
+ if (l) res[s] = res[s] ? `${res[s]} ${cx(l)}` : cx(l)
113
+ }
114
+ }
115
+ }
116
+ }
117
+
118
+ for (let i = 0; i < slots.length; i++) {
119
+ const slot = slots[i]!
120
+ let slotClass = res[slot]!
121
+ if (slot === rootSlot) {
122
+ if (color) slotClass = slotClass ? `${slotClass} rl-color-${color}` : `rl-color-${color}`
123
+ const extra = cx(props.class, props.className)
124
+ if (extra) slotClass = slotClass ? `${slotClass} ${extra}` : extra
125
+ }
126
+ const override = props.ui?.[slot]
127
+ res[slot] = typeof override === "function" ? override(slotClass) : cx(slotClass, override)
128
+ }
129
+
130
+ return res
19
131
  }
20
132
  }
package/src/shortcuts.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { atom } from "nanostores"
1
+ import { atom } from "./stores"
2
2
 
3
3
  export type Handler = (e: KeyboardEvent) => void
4
4
  export interface ShortcutConfig {
@@ -23,86 +23,92 @@ export interface RegisteredShortcut {
23
23
  }
24
24
 
25
25
  interface Shortcut {
26
- key: string
27
26
  handler: Handler
28
- mods: number
29
27
  usingInput?: boolean | string | undefined
30
28
  }
31
29
 
32
30
  const isMac = typeof navigator !== "undefined" && /Mac|iP/.test(navigator.userAgent),
33
- MOD_CMD = isMac ? 2 : 1,
34
- SHIFTABLE = new Set([
35
- "arrowleft",
36
- "arrowright",
37
- "arrowup",
38
- "arrowdown",
39
- "tab",
40
- "escape",
41
- "enter",
42
- "backspace"
43
- ]),
44
- kMap = (m: string, v: any) => m.split(",").map((k) => [k, v]),
45
- MODS: Record<string, number> = Object.fromEntries([
46
- ...kMap("ctrl,control", 1),
47
- ...kMap("cmd,command,meta", MOD_CMD),
48
- ...kMap("alt,opt,option", 4),
49
- ["shift", 8]
50
- ]),
51
- KEYS: Record<string, string> = Object.fromEntries([
52
- ...kMap("ctrl,control", "Ctrl"),
53
- ...kMap("cmd,command,meta", isMac ? "⌘" : "Ctrl"),
54
- ...kMap("alt,opt,option", isMac ? "⌥" : "Alt"),
55
- ["shift", isMac ? "⇧" : "Shift"],
56
- ..."enter:↵,delete:⌦,backspace:⌫,escape:Esc,tab:⇥,space:Space,arrowup:↑,arrowdown:↓,arrowleft:←,arrowright:→"
57
- .split(",")
58
- .map((s) => s.split(":") as [string, string])
59
- ])
31
+ KEYS: Record<string, string> = {
32
+ ctrl: "Ctrl",
33
+ meta: isMac ? "" : "Ctrl",
34
+ alt: isMac ? "" : "Alt",
35
+ shift: isMac ? "" : "Shift",
36
+ enter: "",
37
+ delete: "",
38
+ backspace: "",
39
+ escape: "Esc",
40
+ tab: "",
41
+ space: "Space",
42
+ arrowup: "",
43
+ arrowdown: "↓",
44
+ arrowleft: "",
45
+ arrowright: ""
46
+ }
47
+
48
+ const MODS: Record<string, string> = {
49
+ cmd: "meta",
50
+ command: "meta",
51
+ control: "ctrl",
52
+ opt: "alt",
53
+ option: "alt"
54
+ }
55
+
56
+ export const normalizeModifier = (m: string) => MODS[m.toLowerCase()] || m.toLowerCase()
60
57
 
61
58
  export const formatShortcutKeys = (k: string): string[] =>
62
- k.split(/[_-]/).map((p) => KEYS[p.toLowerCase()] ?? (p ? p[0]!.toUpperCase() + p.slice(1) : ""))
59
+ k.split(/[_-]/).map((p) => {
60
+ const norm = normalizeModifier(p)
61
+ return KEYS[norm] ?? (p ? p[0]!.toUpperCase() + p.slice(1) : "")
62
+ })
63
63
 
64
64
  export const activeShortcutsStore = atom<RegisteredShortcut[]>([])
65
65
 
66
66
  const isUsingInput = (el: Element | null): boolean | string => {
67
- while (el?.shadowRoot?.activeElement) el = el.shadowRoot.activeElement
68
- return el && (/INPUT|TEXTAREA|SELECT/.test(el.nodeName) || (el as HTMLElement).isContentEditable)
69
- ? el.getAttribute("name") || true
67
+ const target = (el?.shadowRoot?.activeElement || el) as HTMLElement | null
68
+ return target && (/INPUT|TEXTAREA|SELECT/.test(target.tagName) || target.isContentEditable)
69
+ ? target.getAttribute("name") || true
70
70
  : false
71
71
  }
72
72
 
73
73
  const isEnabled = (s: Shortcut, input: boolean | string) =>
74
74
  s.usingInput === true || (s.usingInput ? input === s.usingInput : !input)
75
75
 
76
+ const normalizeCombo = (combo: string) => {
77
+ const parts = combo.toLowerCase().split("_")
78
+ let meta = ""
79
+ let ctrl = ""
80
+ let alt = ""
81
+ let shift = ""
82
+ for (let i = 0; i < parts.length - 1; i++) {
83
+ const m = MODS[parts[i]!] || parts[i]!
84
+ if (m === "meta") meta = "meta_"
85
+ else if (m === "ctrl") ctrl = "ctrl_"
86
+ else if (m === "alt") alt = "alt_"
87
+ else if (m === "shift") shift = "shift_"
88
+ }
89
+ return `${meta}${ctrl}${alt}${shift}${parts[parts.length - 1] || ""}`
90
+ }
91
+
76
92
  export function defineShortcuts(
77
93
  config: ShortcutsConfig,
78
94
  options: ShortcutsOptions = {}
79
95
  ): () => void {
80
96
  if (typeof window === "undefined") return () => {}
81
97
 
82
- const standard: Shortcut[] = [],
83
- chained = new Map<string, Shortcut>(),
84
- reg: RegisteredShortcut[] = []
85
- let maxChain = 2
98
+ const map = new Map<string, Shortcut>()
99
+ const reg: RegisteredShortcut[] = []
86
100
 
87
101
  for (const [k, cfg] of Object.entries(config)) {
88
102
  if (!cfg) continue
89
- const obj = typeof cfg === "object" ? cfg : null,
90
- handler = obj ? obj.handler : cfg
103
+ const obj = typeof cfg === "object" ? cfg : null
104
+ const handler = obj ? obj.handler : cfg
91
105
  if (typeof handler !== "function") continue
92
106
 
93
- const chain = k.includes("-") && k !== "-" && !k.includes("_"),
94
- parts = k.toLowerCase().split(chain ? "-" : "_")
95
- let mods = 0
96
- const nonMods = parts.filter((p) => (MODS[p] ? !(mods |= MODS[p]!) : true)),
97
- base = chain ? k.toLowerCase() : nonMods.join("_"),
98
- item: Shortcut = { key: base, handler, mods, usingInput: obj?.usingInput }
107
+ const key =
108
+ k.includes("-") && k !== "-" && !k.includes("_") ? k.toLowerCase() : normalizeCombo(k)
109
+ map.set(key, { handler, usingInput: obj?.usingInput })
99
110
 
100
- if (chain) {
101
- maxChain = Math.max(maxChain, parts.length)
102
- chained.set(base, item)
103
- } else standard.push(item)
104
-
105
- if (obj?.label || obj?.description)
111
+ if (obj?.label || obj?.description) {
106
112
  reg.push({
107
113
  id: `${obj.category || "system"}-${k}`,
108
114
  keys: formatShortcutKeys(k),
@@ -111,62 +117,75 @@ export function defineShortcuts(
111
117
  description: obj.description,
112
118
  order: obj.order
113
119
  })
120
+ }
114
121
  }
115
122
 
116
123
  const ids = new Set(reg.map((i) => i.id))
117
- if (ids.size)
124
+ if (ids.size) {
118
125
  activeShortcutsStore.set(
119
126
  activeShortcutsStore
120
127
  .get()
121
128
  .filter((i) => !ids.has(i.id))
122
129
  .concat(reg)
123
130
  )
131
+ }
124
132
 
125
- const inputs: string[] = []
133
+ const buffer: string[] = []
126
134
  let timer: ReturnType<typeof setTimeout> | undefined
127
135
 
128
136
  const onKeyDown = (e: KeyboardEvent) => {
129
- if (!e.key) return
130
- const input = isUsingInput(document.activeElement),
131
- trigger = (s: Shortcut) => {
132
- e.preventDefault()
133
- s.handler(e)
134
- inputs.length = 0
135
- }
136
-
137
- inputs.push(e.key.toLowerCase())
138
- if (inputs.length > maxChain) inputs.shift()
137
+ if (!e.key || e.isComposing) return
138
+ const key = e.key === " " ? "space" : e.key.toLowerCase()
139
+ const codeKey = e.code
140
+ ? e.code
141
+ .replace(/^Key/, "")
142
+ .replace(/^Digit/, "")
143
+ .toLowerCase()
144
+ : ""
145
+ const input = isUsingInput(typeof document !== "undefined" ? document.activeElement : null)
146
+
147
+ buffer.push(key)
148
+ if (buffer.length > 2) buffer.shift()
139
149
  clearTimeout(timer)
140
150
 
141
- if (inputs.length >= 2 && chained.size)
142
- for (let len = Math.min(inputs.length, maxChain); len >= 2; len--) {
143
- const match = chained.get(inputs.slice(-len).join("-"))
144
- if (match && isEnabled(match, input)) return trigger(match)
151
+ // Chained sequence check (e.g. g-d)
152
+ if (buffer.length === 2) {
153
+ const chainedMatch = map.get(buffer.join("-"))
154
+ if (chainedMatch && isEnabled(chainedMatch, input)) {
155
+ e.preventDefault()
156
+ chainedMatch.handler(e)
157
+ buffer.length = 0
158
+ return
145
159
  }
160
+ }
146
161
 
147
- const eMods = +e.ctrlKey | (+e.metaKey << 1) | (+e.altKey << 2) | (+e.shiftKey << 3),
148
- c = e.code.toLowerCase(),
149
- k = e.key.toLowerCase()
150
-
151
- for (const s of standard) {
152
- if ((s.mods & 7) !== (eMods & 7)) continue
153
- if (s.mods & 4 ? c !== `key${s.key}` && c !== `digit${s.key}` && c !== s.key : k !== s.key)
154
- continue
155
- if (
156
- Boolean(s.mods & 8) !== e.shiftKey &&
157
- (s.mods & 8 || /^[a-z]$/.test(s.key) || SHIFTABLE.has(s.key))
158
- )
159
- continue
160
- if (isEnabled(s, input)) return trigger(s)
162
+ // Unified event normalization
163
+ const keysToCheck = Array.from(new Set([key, codeKey].filter(Boolean)))
164
+ for (const k of keysToCheck) {
165
+ const sig = `${e.metaKey ? "meta_" : ""}${e.ctrlKey ? "ctrl_" : ""}${e.altKey ? "alt_" : ""}${e.shiftKey ? "shift_" : ""}${k}`
166
+ const primarySig =
167
+ !isMac && e.ctrlKey && !e.metaKey
168
+ ? `meta_${e.altKey ? "alt_" : ""}${e.shiftKey ? "shift_" : ""}${k}`
169
+ : sig
170
+
171
+ const match = map.get(sig) || map.get(primarySig) || map.get(k)
172
+ if (match && isEnabled(match, input)) {
173
+ e.preventDefault()
174
+ match.handler(e)
175
+ buffer.length = 0
176
+ return
177
+ }
161
178
  }
162
179
 
163
- timer = setTimeout(() => void (inputs.length = 0), options.chainDelay ?? 800)
180
+ timer = setTimeout(() => void (buffer.length = 0), options.chainDelay ?? 800)
164
181
  }
165
182
 
166
183
  window.addEventListener("keydown", onKeyDown)
167
184
  return () => {
168
185
  window.removeEventListener("keydown", onKeyDown)
169
186
  clearTimeout(timer)
170
- if (ids.size) activeShortcutsStore.set(activeShortcutsStore.get().filter((i) => !ids.has(i.id)))
187
+ if (ids.size) {
188
+ activeShortcutsStore.set(activeShortcutsStore.get().filter((i) => !ids.has(i.id)))
189
+ }
171
190
  }
172
191
  }