@nqmcreative/ui 0.2.3 → 0.4.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 +209 -15
- package/cli/index.mjs +328 -112
- package/dist/core/color.d.ts +24 -0
- package/dist/core/color.js +39 -0
- package/dist/core/index.d.ts +8 -1
- package/dist/core/index.js +5 -1
- package/dist/core/locale.svelte.d.ts +8 -0
- package/dist/core/locale.svelte.js +14 -0
- package/dist/core/number.d.ts +22 -0
- package/dist/core/number.js +33 -0
- package/dist/core/pin.d.ts +23 -0
- package/dist/core/pin.js +38 -0
- package/dist/core/tags.d.ts +37 -0
- package/dist/core/tags.js +51 -0
- package/dist/core/time.d.ts +30 -0
- package/dist/core/time.js +78 -0
- package/dist/core/toast.svelte.d.ts +3 -0
- package/dist/styles/matte/AccordionItem.svelte +5 -0
- package/dist/styles/matte/AccordionItem.svelte.d.ts +2 -0
- package/dist/styles/matte/Avatar.svelte +15 -0
- package/dist/styles/matte/Avatar.svelte.d.ts +3 -0
- package/dist/styles/matte/Badge.svelte +5 -0
- package/dist/styles/matte/Badge.svelte.d.ts +2 -0
- package/dist/styles/matte/Breadcrumb.svelte +11 -4
- package/dist/styles/matte/Breadcrumb.svelte.d.ts +3 -0
- package/dist/styles/matte/Button.svelte +58 -7
- package/dist/styles/matte/Button.svelte.d.ts +14 -2
- package/dist/styles/matte/Card.svelte +6 -1
- package/dist/styles/matte/Card.svelte.d.ts +2 -0
- package/dist/styles/matte/CheckboxGroup.svelte +6 -0
- package/dist/styles/matte/CheckboxGroup.svelte.d.ts +2 -0
- package/dist/styles/matte/ColorInput.svelte +110 -0
- package/dist/styles/matte/ColorInput.svelte.d.ts +16 -0
- package/dist/styles/matte/Combobox.svelte +5 -0
- package/dist/styles/matte/Combobox.svelte.d.ts +3 -0
- package/dist/styles/matte/CommandPalette.svelte +6 -0
- package/dist/styles/matte/CommandPalette.svelte.d.ts +3 -0
- package/dist/styles/matte/CurrencyInput.svelte +108 -0
- package/dist/styles/matte/CurrencyInput.svelte.d.ts +26 -0
- package/dist/styles/matte/Drawer.svelte +5 -0
- package/dist/styles/matte/Drawer.svelte.d.ts +2 -0
- package/dist/styles/matte/FileInput.svelte +194 -0
- package/dist/styles/matte/FileInput.svelte.d.ts +32 -0
- package/dist/styles/matte/Footer.svelte +6 -2
- package/dist/styles/matte/Footer.svelte.d.ts +2 -0
- package/dist/styles/matte/InputAddon.svelte +36 -0
- package/dist/styles/matte/InputAddon.svelte.d.ts +13 -0
- package/dist/styles/matte/Link.svelte +9 -0
- package/dist/styles/matte/Link.svelte.d.ts +4 -0
- package/dist/styles/matte/MenuItem.svelte +2 -1
- package/dist/styles/matte/Modal.svelte +5 -0
- package/dist/styles/matte/Modal.svelte.d.ts +2 -0
- package/dist/styles/matte/MultiSelect.svelte +7 -1
- package/dist/styles/matte/MultiSelect.svelte.d.ts +3 -0
- package/dist/styles/matte/Navbar.svelte +18 -4
- package/dist/styles/matte/Navbar.svelte.d.ts +2 -0
- package/dist/styles/matte/PinInput.svelte +137 -0
- package/dist/styles/matte/PinInput.svelte.d.ts +25 -0
- package/dist/styles/matte/PricingCard.svelte +16 -8
- package/dist/styles/matte/PricingCard.svelte.d.ts +2 -0
- package/dist/styles/matte/RadioGroup.svelte +6 -0
- package/dist/styles/matte/RadioGroup.svelte.d.ts +2 -0
- package/dist/styles/matte/SearchInput.svelte +87 -0
- package/dist/styles/matte/SearchInput.svelte.d.ts +24 -0
- package/dist/styles/matte/SegmentedControl.svelte +16 -2
- package/dist/styles/matte/SegmentedControl.svelte.d.ts +5 -0
- package/dist/styles/matte/Sidebar.svelte +2 -1
- package/dist/styles/matte/Stat.svelte +8 -1
- package/dist/styles/matte/Stat.svelte.d.ts +3 -0
- package/dist/styles/matte/Steps.svelte +7 -0
- package/dist/styles/matte/Steps.svelte.d.ts +3 -0
- package/dist/styles/matte/Table.svelte +153 -97
- package/dist/styles/matte/Table.svelte.d.ts +12 -0
- package/dist/styles/matte/Tabs.svelte +5 -0
- package/dist/styles/matte/Tabs.svelte.d.ts +3 -0
- package/dist/styles/matte/TagsInput.svelte +173 -0
- package/dist/styles/matte/TagsInput.svelte.d.ts +29 -0
- package/dist/styles/matte/TimeInput.svelte +129 -0
- package/dist/styles/matte/TimeInput.svelte.d.ts +21 -0
- package/dist/styles/matte/Toaster.svelte +3 -0
- package/dist/styles/matte/icon.d.ts +21 -0
- package/dist/styles/matte/icon.js +21 -0
- package/dist/styles/matte/index.d.ts +8 -2
- package/dist/styles/matte/index.js +8 -2
- package/dist/styles/paper/AccordionItem.svelte +5 -0
- package/dist/styles/paper/AccordionItem.svelte.d.ts +2 -0
- package/dist/styles/paper/Avatar.svelte +15 -0
- package/dist/styles/paper/Avatar.svelte.d.ts +3 -0
- package/dist/styles/paper/Badge.svelte +5 -0
- package/dist/styles/paper/Badge.svelte.d.ts +2 -0
- package/dist/styles/paper/Breadcrumb.svelte +8 -4
- package/dist/styles/paper/Breadcrumb.svelte.d.ts +3 -0
- package/dist/styles/paper/Button.svelte +58 -7
- package/dist/styles/paper/Button.svelte.d.ts +14 -2
- package/dist/styles/paper/Card.svelte +6 -1
- package/dist/styles/paper/Card.svelte.d.ts +2 -0
- package/dist/styles/paper/CheckboxGroup.svelte +6 -0
- package/dist/styles/paper/CheckboxGroup.svelte.d.ts +2 -0
- package/dist/styles/paper/ColorInput.svelte +112 -0
- package/dist/styles/paper/ColorInput.svelte.d.ts +16 -0
- package/dist/styles/paper/Combobox.svelte +5 -0
- package/dist/styles/paper/Combobox.svelte.d.ts +3 -0
- package/dist/styles/paper/CommandPalette.svelte +6 -0
- package/dist/styles/paper/CommandPalette.svelte.d.ts +3 -0
- package/dist/styles/paper/CurrencyInput.svelte +108 -0
- package/dist/styles/paper/CurrencyInput.svelte.d.ts +26 -0
- package/dist/styles/paper/Drawer.svelte +5 -0
- package/dist/styles/paper/Drawer.svelte.d.ts +2 -0
- package/dist/styles/paper/FileInput.svelte +204 -0
- package/dist/styles/paper/FileInput.svelte.d.ts +32 -0
- package/dist/styles/paper/Footer.svelte +6 -2
- package/dist/styles/paper/Footer.svelte.d.ts +2 -0
- package/dist/styles/paper/InputAddon.svelte +36 -0
- package/dist/styles/paper/InputAddon.svelte.d.ts +13 -0
- package/dist/styles/paper/Link.svelte +9 -0
- package/dist/styles/paper/Link.svelte.d.ts +4 -0
- package/dist/styles/paper/MenuItem.svelte +2 -1
- package/dist/styles/paper/Modal.svelte +5 -0
- package/dist/styles/paper/Modal.svelte.d.ts +2 -0
- package/dist/styles/paper/MultiSelect.svelte +7 -1
- package/dist/styles/paper/MultiSelect.svelte.d.ts +3 -0
- package/dist/styles/paper/Navbar.svelte +18 -4
- package/dist/styles/paper/Navbar.svelte.d.ts +2 -0
- package/dist/styles/paper/PinInput.svelte +137 -0
- package/dist/styles/paper/PinInput.svelte.d.ts +25 -0
- package/dist/styles/paper/PricingCard.svelte +23 -15
- package/dist/styles/paper/PricingCard.svelte.d.ts +2 -0
- package/dist/styles/paper/RadioGroup.svelte +6 -0
- package/dist/styles/paper/RadioGroup.svelte.d.ts +2 -0
- package/dist/styles/paper/SearchInput.svelte +92 -0
- package/dist/styles/paper/SearchInput.svelte.d.ts +24 -0
- package/dist/styles/paper/SegmentedControl.svelte +16 -2
- package/dist/styles/paper/SegmentedControl.svelte.d.ts +5 -0
- package/dist/styles/paper/Sidebar.svelte +2 -1
- package/dist/styles/paper/Stat.svelte +8 -1
- package/dist/styles/paper/Stat.svelte.d.ts +3 -0
- package/dist/styles/paper/Steps.svelte +7 -0
- package/dist/styles/paper/Steps.svelte.d.ts +3 -0
- package/dist/styles/paper/Table.svelte +153 -97
- package/dist/styles/paper/Table.svelte.d.ts +12 -0
- package/dist/styles/paper/Tabs.svelte +5 -0
- package/dist/styles/paper/Tabs.svelte.d.ts +3 -0
- package/dist/styles/paper/TagsInput.svelte +178 -0
- package/dist/styles/paper/TagsInput.svelte.d.ts +29 -0
- package/dist/styles/paper/TimeInput.svelte +155 -0
- package/dist/styles/paper/TimeInput.svelte.d.ts +21 -0
- package/dist/styles/paper/Toaster.svelte +3 -0
- package/dist/styles/paper/icon.d.ts +21 -0
- package/dist/styles/paper/icon.js +21 -0
- package/dist/styles/paper/index.d.ts +8 -2
- package/dist/styles/paper/index.js +8 -2
- package/dist/styles/sprout/AccordionItem.svelte +5 -0
- package/dist/styles/sprout/AccordionItem.svelte.d.ts +2 -0
- package/dist/styles/sprout/Avatar.svelte +15 -0
- package/dist/styles/sprout/Avatar.svelte.d.ts +3 -0
- package/dist/styles/sprout/Badge.svelte +5 -0
- package/dist/styles/sprout/Badge.svelte.d.ts +2 -0
- package/dist/styles/sprout/Breadcrumb.svelte +8 -4
- package/dist/styles/sprout/Breadcrumb.svelte.d.ts +3 -0
- package/dist/styles/sprout/Button.svelte +58 -7
- package/dist/styles/sprout/Button.svelte.d.ts +14 -2
- package/dist/styles/sprout/Card.svelte +6 -1
- package/dist/styles/sprout/Card.svelte.d.ts +2 -0
- package/dist/styles/sprout/CheckboxGroup.svelte +6 -0
- package/dist/styles/sprout/CheckboxGroup.svelte.d.ts +2 -0
- package/dist/styles/sprout/ColorInput.svelte +112 -0
- package/dist/styles/sprout/ColorInput.svelte.d.ts +16 -0
- package/dist/styles/sprout/Combobox.svelte +5 -0
- package/dist/styles/sprout/Combobox.svelte.d.ts +3 -0
- package/dist/styles/sprout/CommandPalette.svelte +6 -0
- package/dist/styles/sprout/CommandPalette.svelte.d.ts +3 -0
- package/dist/styles/sprout/CurrencyInput.svelte +108 -0
- package/dist/styles/sprout/CurrencyInput.svelte.d.ts +26 -0
- package/dist/styles/sprout/Drawer.svelte +5 -0
- package/dist/styles/sprout/Drawer.svelte.d.ts +2 -0
- package/dist/styles/sprout/FileInput.svelte +205 -0
- package/dist/styles/sprout/FileInput.svelte.d.ts +32 -0
- package/dist/styles/sprout/Footer.svelte +6 -2
- package/dist/styles/sprout/Footer.svelte.d.ts +2 -0
- package/dist/styles/sprout/InputAddon.svelte +37 -0
- package/dist/styles/sprout/InputAddon.svelte.d.ts +13 -0
- package/dist/styles/sprout/Link.svelte +9 -0
- package/dist/styles/sprout/Link.svelte.d.ts +4 -0
- package/dist/styles/sprout/MenuItem.svelte +2 -1
- package/dist/styles/sprout/Modal.svelte +5 -0
- package/dist/styles/sprout/Modal.svelte.d.ts +2 -0
- package/dist/styles/sprout/MultiSelect.svelte +7 -1
- package/dist/styles/sprout/MultiSelect.svelte.d.ts +3 -0
- package/dist/styles/sprout/Navbar.svelte +18 -4
- package/dist/styles/sprout/Navbar.svelte.d.ts +2 -0
- package/dist/styles/sprout/PinInput.svelte +138 -0
- package/dist/styles/sprout/PinInput.svelte.d.ts +25 -0
- package/dist/styles/sprout/PricingCard.svelte +23 -15
- package/dist/styles/sprout/PricingCard.svelte.d.ts +2 -0
- package/dist/styles/sprout/RadioGroup.svelte +6 -0
- package/dist/styles/sprout/RadioGroup.svelte.d.ts +2 -0
- package/dist/styles/sprout/SearchInput.svelte +92 -0
- package/dist/styles/sprout/SearchInput.svelte.d.ts +24 -0
- package/dist/styles/sprout/SegmentedControl.svelte +16 -2
- package/dist/styles/sprout/SegmentedControl.svelte.d.ts +5 -0
- package/dist/styles/sprout/Sidebar.svelte +2 -1
- package/dist/styles/sprout/Stat.svelte +8 -1
- package/dist/styles/sprout/Stat.svelte.d.ts +3 -0
- package/dist/styles/sprout/Steps.svelte +7 -0
- package/dist/styles/sprout/Steps.svelte.d.ts +3 -0
- package/dist/styles/sprout/Table.svelte +153 -97
- package/dist/styles/sprout/Table.svelte.d.ts +12 -0
- package/dist/styles/sprout/Tabs.svelte +5 -0
- package/dist/styles/sprout/Tabs.svelte.d.ts +3 -0
- package/dist/styles/sprout/TagsInput.svelte +179 -0
- package/dist/styles/sprout/TagsInput.svelte.d.ts +29 -0
- package/dist/styles/sprout/TimeInput.svelte +155 -0
- package/dist/styles/sprout/TimeInput.svelte.d.ts +21 -0
- package/dist/styles/sprout/Toaster.svelte +3 -0
- package/dist/styles/sprout/icon.d.ts +21 -0
- package/dist/styles/sprout/icon.js +21 -0
- package/dist/styles/sprout/index.d.ts +8 -2
- package/dist/styles/sprout/index.js +8 -2
- package/package.json +98 -26
- package/registry.json +143 -35
- package/dist/styles/matte/NumberInput.svelte +0 -96
- package/dist/styles/matte/NumberInput.svelte.d.ts +0 -15
- package/dist/styles/matte/PasswordInput.svelte +0 -95
- package/dist/styles/matte/PasswordInput.svelte.d.ts +0 -12
- package/dist/styles/paper/NumberInput.svelte +0 -96
- package/dist/styles/paper/NumberInput.svelte.d.ts +0 -15
- package/dist/styles/paper/PasswordInput.svelte +0 -100
- package/dist/styles/paper/PasswordInput.svelte.d.ts +0 -12
- package/dist/styles/sprout/NumberInput.svelte +0 -97
- package/dist/styles/sprout/NumberInput.svelte.d.ts +0 -15
- package/dist/styles/sprout/PasswordInput.svelte +0 -101
- package/dist/styles/sprout/PasswordInput.svelte.d.ts +0 -12
package/cli/index.mjs
CHANGED
|
@@ -3,16 +3,19 @@
|
|
|
3
3
|
* @nqmcreative/ui CLI
|
|
4
4
|
*
|
|
5
5
|
* nqm-ui init wire the design system into this project
|
|
6
|
+
* nqm-ui update rewrite that wiring after upgrading the package
|
|
6
7
|
* nqm-ui add <name...> print (or insert) the import for a component
|
|
7
8
|
* nqm-ui list [category] every component and its subpath
|
|
8
9
|
* nqm-ui info <name> what one component pulls in
|
|
9
10
|
*
|
|
10
11
|
* Node built-ins only, so `bunx @nqmcreative/ui init` needs nothing installed.
|
|
11
|
-
* Every write is idempotent:
|
|
12
|
-
* time
|
|
12
|
+
* Every write is idempotent: the lines this CLI owns are rewritten from
|
|
13
|
+
* scratch on every run, so running it twice changes nothing the second time —
|
|
14
|
+
* and running it after an upgrade picks up whatever those lines became.
|
|
13
15
|
*/
|
|
14
16
|
import { readFile, writeFile, mkdir } from 'node:fs/promises';
|
|
15
17
|
import { existsSync } from 'node:fs';
|
|
18
|
+
import { createInterface } from 'node:readline/promises';
|
|
16
19
|
import { dirname, join, relative, resolve } from 'node:path';
|
|
17
20
|
import { fileURLToPath } from 'node:url';
|
|
18
21
|
|
|
@@ -76,27 +79,56 @@ function findComponent(list, query) {
|
|
|
76
79
|
* The style to work in. There is no default on purpose: a component belongs to
|
|
77
80
|
* a style, and picking one by accident is the mistake this package exists to
|
|
78
81
|
* prevent. Asking once beats shipping the wrong look.
|
|
82
|
+
*
|
|
83
|
+
* `--style` answers it up front. Without it a terminal gets a numbered prompt;
|
|
84
|
+
* a pipe or a CI job gets the list and a non-zero exit, because there is
|
|
85
|
+
* nobody there to answer.
|
|
79
86
|
*/
|
|
80
|
-
function pickStyle(styles) {
|
|
87
|
+
async function pickStyle(styles) {
|
|
81
88
|
const index = argv.indexOf('--style');
|
|
82
89
|
const wanted = index !== -1 ? argv[index + 1] : null;
|
|
83
90
|
|
|
84
|
-
if (
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
91
|
+
if (wanted) {
|
|
92
|
+
const named = styles.find((s) => s.name === wanted);
|
|
93
|
+
if (!named) fail(`unknown style "${wanted}" — try ${styles.map((s) => s.name).join(' or ')}`);
|
|
94
|
+
return named;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
log();
|
|
98
|
+
log(' which style?');
|
|
99
|
+
log();
|
|
100
|
+
styles.forEach((style, i) => {
|
|
101
|
+
log(` ${c.cyan(`${i + 1})`)} ${c.bold(style.name.padEnd(8))} ${c.dim(style.description)}`);
|
|
102
|
+
});
|
|
103
|
+
log();
|
|
104
|
+
|
|
105
|
+
if (!process.stdin.isTTY) {
|
|
106
|
+
log(` ${c.dim(`not a terminal — pass it: ${command} --style ${styles[0].name}`)}`);
|
|
93
107
|
log();
|
|
94
108
|
process.exit(1);
|
|
95
109
|
}
|
|
96
110
|
|
|
97
|
-
const
|
|
98
|
-
|
|
99
|
-
|
|
111
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
112
|
+
try {
|
|
113
|
+
for (let attempt = 0; attempt < 3; attempt++) {
|
|
114
|
+
const answer = (await rl.question(` 1-${styles.length}, or a name [1]: `)).trim();
|
|
115
|
+
if (!answer) return styles[0];
|
|
116
|
+
|
|
117
|
+
const number = Number(answer);
|
|
118
|
+
if (Number.isInteger(number) && number >= 1 && number <= styles.length) {
|
|
119
|
+
return styles[number - 1];
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const named = styles.find((s) => s.name === answer.toLowerCase());
|
|
123
|
+
if (named) return named;
|
|
124
|
+
|
|
125
|
+
warn(`no style called "${answer}"`);
|
|
126
|
+
}
|
|
127
|
+
} finally {
|
|
128
|
+
rl.close();
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
fail(`try ${command} --style ${styles[0].name}`);
|
|
100
132
|
}
|
|
101
133
|
|
|
102
134
|
/* ------------------------------------------------------------ file edits -- */
|
|
@@ -121,6 +153,39 @@ async function write(path, content, label) {
|
|
|
121
153
|
|
|
122
154
|
/* -------------------------------------------------------------- detection -- */
|
|
123
155
|
|
|
156
|
+
/**
|
|
157
|
+
* Which kind of project this is.
|
|
158
|
+
*
|
|
159
|
+
* The library itself is plain Svelte 5 — no `$app/*`, no `$env/*`, no
|
|
160
|
+
* `@sveltejs/kit` anywhere in it — so a framework only decides where four
|
|
161
|
+
* things live:
|
|
162
|
+
*
|
|
163
|
+
* kit src/app.css src/app.html src/routes/+layout.svelte
|
|
164
|
+
* vite src/app.css index.html src/main.ts
|
|
165
|
+
* laravel resources/css/… resources/views/… the @vite directive
|
|
166
|
+
*/
|
|
167
|
+
function detectFramework(deps) {
|
|
168
|
+
if ('@sveltejs/kit' in deps) return 'kit';
|
|
169
|
+
if (existsSync(join(cwd, 'artisan'))) return 'laravel';
|
|
170
|
+
return 'vite';
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const FRAMEWORK_LABEL = {
|
|
174
|
+
kit: 'SvelteKit',
|
|
175
|
+
laravel: 'Laravel + Svelte',
|
|
176
|
+
vite: 'Svelte + Vite'
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
/** The first candidate that exists, or the first one as the place to create. */
|
|
180
|
+
function pickPath(candidates) {
|
|
181
|
+
return candidates.find((path) => existsSync(join(cwd, path))) ?? candidates[0];
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/** The one that exists, or null — for files we patch but never invent. */
|
|
185
|
+
function findPath(candidates) {
|
|
186
|
+
return candidates.find((path) => existsSync(join(cwd, path))) ?? null;
|
|
187
|
+
}
|
|
188
|
+
|
|
124
189
|
async function detectProject() {
|
|
125
190
|
const pkgPath = join(cwd, 'package.json');
|
|
126
191
|
if (!existsSync(pkgPath)) fail('no package.json here — run this inside your project');
|
|
@@ -128,17 +193,156 @@ async function detectProject() {
|
|
|
128
193
|
const pkg = JSON.parse(await readFile(pkgPath, 'utf8'));
|
|
129
194
|
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
130
195
|
|
|
131
|
-
const
|
|
196
|
+
const framework = detectFramework(deps);
|
|
197
|
+
const isKit = framework === 'kit';
|
|
132
198
|
const hasTailwind = '@tailwindcss/vite' in deps || 'tailwindcss' in deps;
|
|
133
199
|
const hasUi = '@nqmcreative/ui' in deps;
|
|
200
|
+
// Kit brings its own; everywhere else the components are .svelte files in
|
|
201
|
+
// node_modules and something has to compile them.
|
|
202
|
+
const hasCompiler = isKit || '@sveltejs/vite-plugin-svelte' in deps;
|
|
134
203
|
|
|
135
204
|
// Where the Tailwind entry CSS lives, or should.
|
|
136
|
-
const
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
205
|
+
const css = pickPath(
|
|
206
|
+
{
|
|
207
|
+
kit: ['src/app.css', 'src/routes/+layout.css', 'src/app.postcss'],
|
|
208
|
+
laravel: ['resources/css/app.css'],
|
|
209
|
+
vite: ['src/app.css', 'src/style.css', 'src/main.css']
|
|
210
|
+
}[framework]
|
|
211
|
+
);
|
|
212
|
+
|
|
213
|
+
// The document head, for the font preconnect and the no-flash theme script.
|
|
214
|
+
// Kit gets one written if it is missing; the other two are only patched,
|
|
215
|
+
// because guessing at someone's Blade layout would do more harm than a
|
|
216
|
+
// printed reminder.
|
|
217
|
+
const head = {
|
|
218
|
+
kit: 'src/app.html',
|
|
219
|
+
laravel: findPath([
|
|
220
|
+
'resources/views/app.blade.php',
|
|
221
|
+
'resources/views/layouts/app.blade.php',
|
|
222
|
+
'resources/views/components/layouts/app.blade.php',
|
|
223
|
+
'resources/views/welcome.blade.php'
|
|
224
|
+
]),
|
|
225
|
+
vite: findPath(['index.html'])
|
|
226
|
+
}[framework];
|
|
227
|
+
|
|
228
|
+
// Where the entry CSS gets imported. Laravel has nowhere: the `@vite`
|
|
229
|
+
// directive in the Blade layout links it directly.
|
|
230
|
+
const entry = {
|
|
231
|
+
kit: 'src/routes/+layout.svelte',
|
|
232
|
+
laravel: null,
|
|
233
|
+
vite: findPath(['src/main.ts', 'src/main.js'])
|
|
234
|
+
}[framework];
|
|
235
|
+
|
|
236
|
+
return {
|
|
237
|
+
pkg,
|
|
238
|
+
deps,
|
|
239
|
+
framework,
|
|
240
|
+
isKit,
|
|
241
|
+
hasTailwind,
|
|
242
|
+
hasUi,
|
|
243
|
+
hasCompiler,
|
|
244
|
+
css: join(cwd, css),
|
|
245
|
+
cssRel: css,
|
|
246
|
+
head,
|
|
247
|
+
entry
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/* ------------------------------------------------------------------ head -- */
|
|
140
252
|
|
|
141
|
-
|
|
253
|
+
/** Escapes a literal for use inside a RegExp — the markers contain `%` and `/`. */
|
|
254
|
+
const escapeRe = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
255
|
+
|
|
256
|
+
/** The two things that have to run before first paint, in document order. */
|
|
257
|
+
function headAdditions(style, html) {
|
|
258
|
+
const additions = [];
|
|
259
|
+
if (style.fonts && !html.includes('fonts.gstatic.com')) {
|
|
260
|
+
additions.push(`<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />`);
|
|
261
|
+
}
|
|
262
|
+
if (!html.includes('nqm-theme')) {
|
|
263
|
+
additions.push(
|
|
264
|
+
`<script>`,
|
|
265
|
+
`\tconst saved = localStorage.getItem('nqm-theme');`,
|
|
266
|
+
`\tif (saved === 'dark' || saved === 'light') document.documentElement.classList.add(saved);`,
|
|
267
|
+
`</script>`
|
|
268
|
+
);
|
|
269
|
+
}
|
|
270
|
+
return additions;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Puts them in the document head, wherever that is for this framework.
|
|
275
|
+
*
|
|
276
|
+
* Kit renders into a `%sveltekit.head%` placeholder; a Vite index.html and a
|
|
277
|
+
* Blade layout both have a real `</head>` to sit above. When there is no file
|
|
278
|
+
* to patch — or no marker in it — the lines are printed instead of guessed at.
|
|
279
|
+
*/
|
|
280
|
+
async function wireHead(project, style) {
|
|
281
|
+
const { framework, head } = project;
|
|
282
|
+
|
|
283
|
+
const printByHand = (additions, reason) => {
|
|
284
|
+
log();
|
|
285
|
+
warn(reason);
|
|
286
|
+
log(additions.map((line) => c.cyan(` ${line}`)).join('\n'));
|
|
287
|
+
};
|
|
288
|
+
|
|
289
|
+
if (!head) {
|
|
290
|
+
printByHand(
|
|
291
|
+
headAdditions(style, ''),
|
|
292
|
+
framework === 'laravel'
|
|
293
|
+
? 'no Blade layout found — put these inside <head> in yours:'
|
|
294
|
+
: 'no index.html found — put these inside its <head>:'
|
|
295
|
+
);
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
const htmlPath = join(cwd, head);
|
|
300
|
+
let html = await readOr(htmlPath);
|
|
301
|
+
|
|
302
|
+
if (!html && framework === 'kit') {
|
|
303
|
+
// Some `sv create` templates omit it and let SvelteKit fall back to a
|
|
304
|
+
// built-in. Write the standard one so there is somewhere to put the
|
|
305
|
+
// preconnect and the no-flash theme script.
|
|
306
|
+
html = [
|
|
307
|
+
'<!doctype html>',
|
|
308
|
+
'<html lang="en">',
|
|
309
|
+
' <head>',
|
|
310
|
+
' <meta charset="utf-8" />',
|
|
311
|
+
' <meta name="viewport" content="width=device-width, initial-scale=1" />',
|
|
312
|
+
' %sveltekit.head%',
|
|
313
|
+
' </head>',
|
|
314
|
+
' <body data-sveltekit-preload-data="hover">',
|
|
315
|
+
' <div style="display: contents">%sveltekit.body%</div>',
|
|
316
|
+
' </body>',
|
|
317
|
+
'</html>',
|
|
318
|
+
''
|
|
319
|
+
].join('\n');
|
|
320
|
+
skip('src/app.html was missing — creating it');
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
const additions = headAdditions(style, html);
|
|
324
|
+
if (additions.length === 0) {
|
|
325
|
+
skip(`${head} already wired`);
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
const marker = framework === 'kit' ? '%sveltekit.head%' : '</head>';
|
|
330
|
+
if (!html.includes(marker)) {
|
|
331
|
+
printByHand(additions, `${head} has no ${marker} — add these by hand, inside <head>:`);
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// Matched with its own indentation so the block lands at the right depth,
|
|
336
|
+
// and replaced through a function so a `$` in the lines stays literal.
|
|
337
|
+
html = html.replace(new RegExp(`([ \\t]*)${escapeRe(marker)}`), (_match, indent) => {
|
|
338
|
+
// The placeholder is itself a child of <head>; a closing tag is not, so
|
|
339
|
+
// its children sit one level deeper — in whichever unit the file uses.
|
|
340
|
+
const unit = indent.startsWith('\t') ? '\t' : ' ';
|
|
341
|
+
const inner = framework === 'kit' ? indent : `${indent}${unit}`;
|
|
342
|
+
const body = additions.map((line) => line.replace(/^\t/, unit));
|
|
343
|
+
return `${inner}${body.join(`\n${inner}`)}\n${indent}${marker}`;
|
|
344
|
+
});
|
|
345
|
+
await write(htmlPath, html, head);
|
|
142
346
|
}
|
|
143
347
|
|
|
144
348
|
/** How many `../` from the CSS file up to node_modules. */
|
|
@@ -151,12 +355,12 @@ function sourcePath(cssRel) {
|
|
|
151
355
|
|
|
152
356
|
async function init() {
|
|
153
357
|
const { styles } = await registry();
|
|
154
|
-
const style = pickStyle(styles);
|
|
358
|
+
const style = await pickStyle(styles);
|
|
155
359
|
const project = await detectProject();
|
|
156
360
|
|
|
157
361
|
log();
|
|
158
362
|
log(c.bold(` @nqmcreative/ui — ${style.title}`));
|
|
159
|
-
log(c.dim(` ${project.
|
|
363
|
+
log(c.dim(` ${FRAMEWORK_LABEL[project.framework]} project detected`));
|
|
160
364
|
log();
|
|
161
365
|
|
|
162
366
|
if (!project.hasUi) {
|
|
@@ -171,6 +375,13 @@ async function init() {
|
|
|
171
375
|
log();
|
|
172
376
|
}
|
|
173
377
|
|
|
378
|
+
if (!project.hasCompiler) {
|
|
379
|
+
// The package ships .svelte files, so the app compiles them itself.
|
|
380
|
+
warn('nothing here compiles Svelte components:');
|
|
381
|
+
log(c.cyan(' bun add -d svelte @sveltejs/vite-plugin-svelte'));
|
|
382
|
+
log();
|
|
383
|
+
}
|
|
384
|
+
|
|
174
385
|
/* --- 1. the entry CSS --- */
|
|
175
386
|
const source = sourcePath(project.cssRel);
|
|
176
387
|
const lines = [
|
|
@@ -185,110 +396,90 @@ async function init() {
|
|
|
185
396
|
|
|
186
397
|
const css = await readOr(project.css);
|
|
187
398
|
|
|
188
|
-
//
|
|
189
|
-
//
|
|
190
|
-
//
|
|
191
|
-
|
|
192
|
-
|
|
399
|
+
// The block above belongs to this package, so it is rewritten on every run
|
|
400
|
+
// rather than left alone once it is there. That is what makes an upgrade
|
|
401
|
+
// land: when a release changes which files a style imports, or where
|
|
402
|
+
// `@source` has to point, the old lines would otherwise sit here and the
|
|
403
|
+
// new components would come up unstyled.
|
|
404
|
+
//
|
|
405
|
+
// Only those lines. Everything below is the project's own CSS and is
|
|
406
|
+
// carried over untouched — including an `@import` of another package, which
|
|
407
|
+
// is why the match is anchored to this one.
|
|
408
|
+
const stale =
|
|
409
|
+
/^@import ['"]@nqmcreative\/ui\/[^'"]+\.css['"];$|^@source ['"][^'"]*@nqmcreative\/ui[^'"]*['"];$/;
|
|
410
|
+
|
|
411
|
+
const kept = css
|
|
412
|
+
.split('\n')
|
|
413
|
+
.filter((line) => !stale.test(line.trim()))
|
|
414
|
+
.join('\n')
|
|
415
|
+
.replace(/@import ['"]tailwindcss['"];\n?/, '')
|
|
416
|
+
.replace(/\/\* Tailwind v4 skips node_modules[\s\S]*?\*\/\n?/, '')
|
|
417
|
+
.replace(/\n{3,}/g, '\n\n')
|
|
418
|
+
.trim();
|
|
419
|
+
|
|
420
|
+
const body = kept ? `${lines.join('\n')}\n\n${kept}\n` : `${lines.join('\n')}\n`;
|
|
421
|
+
if (body === css) {
|
|
422
|
+
skip(`${project.cssRel} already up to date`);
|
|
193
423
|
} else {
|
|
194
|
-
// Drop whatever this package left here before — an older layout's
|
|
195
|
-
// `@nqmcreative/ui/theme.css`, another style's, and the `@source` that
|
|
196
|
-
// went with them. Keeping those would either fight the new tokens or fail
|
|
197
|
-
// to resolve, so a rewrite is cleaner than an append.
|
|
198
|
-
const stale =
|
|
199
|
-
/^@import ['"]@nqmcreative\/ui\/[^'"]+\.css['"];$|^@source ['"][^'"]*@nqmcreative\/ui[^'"]*['"];$/;
|
|
200
|
-
|
|
201
|
-
const kept = css
|
|
202
|
-
.split('\n')
|
|
203
|
-
.filter((line) => !stale.test(line.trim()))
|
|
204
|
-
.join('\n')
|
|
205
|
-
.replace(/@import ['"]tailwindcss['"];\n?/, '')
|
|
206
|
-
.replace(/\n{3,}/g, '\n\n')
|
|
207
|
-
.trim();
|
|
208
|
-
|
|
209
|
-
const body = kept ? `${lines.join('\n')}\n\n${kept}\n` : `${lines.join('\n')}\n`;
|
|
210
424
|
await write(project.css, body, project.cssRel);
|
|
211
425
|
}
|
|
212
426
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
html = [
|
|
222
|
-
'<!doctype html>',
|
|
223
|
-
'<html lang="en">',
|
|
224
|
-
' <head>',
|
|
225
|
-
' <meta charset="utf-8" />',
|
|
226
|
-
' <meta name="viewport" content="width=device-width, initial-scale=1" />',
|
|
227
|
-
' %sveltekit.head%',
|
|
228
|
-
' </head>',
|
|
229
|
-
' <body data-sveltekit-preload-data="hover">',
|
|
230
|
-
' <div style="display: contents">%sveltekit.body%</div>',
|
|
231
|
-
' </body>',
|
|
232
|
-
'</html>',
|
|
233
|
-
''
|
|
234
|
-
].join('\n');
|
|
235
|
-
skip('src/app.html was missing — creating it');
|
|
236
|
-
}
|
|
237
|
-
{
|
|
238
|
-
// Unindented — `%sveltekit.head%` already sits at its own indentation,
|
|
239
|
-
// so the join below supplies it and the first line does not double up.
|
|
240
|
-
const additions = [];
|
|
241
|
-
if (style.fonts && !html.includes('fonts.gstatic.com')) {
|
|
242
|
-
additions.push(`<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />`);
|
|
243
|
-
}
|
|
244
|
-
if (!html.includes('nqm-theme')) {
|
|
245
|
-
additions.push(
|
|
246
|
-
`<script>`,
|
|
247
|
-
`\tconst saved = localStorage.getItem('nqm-theme');`,
|
|
248
|
-
`\tif (saved === 'dark' || saved === 'light') document.documentElement.classList.add(saved);`,
|
|
249
|
-
`</script>`
|
|
250
|
-
);
|
|
251
|
-
}
|
|
252
|
-
if (additions.length === 0) {
|
|
253
|
-
skip('src/app.html already wired');
|
|
254
|
-
} else if (!html.includes('%sveltekit.head%')) {
|
|
255
|
-
warn('src/app.html has no %sveltekit.head% — add these by hand:');
|
|
256
|
-
log(additions.map((line) => `\t\t${line}`).join('\n'));
|
|
257
|
-
} else {
|
|
258
|
-
const indent = /([ \t]*)%sveltekit\.head%/.exec(html)?.[1] ?? '\t\t';
|
|
259
|
-
html = html.replace(
|
|
260
|
-
'%sveltekit.head%',
|
|
261
|
-
`${additions.join(`\n${indent}`)}\n${indent}%sveltekit.head%`
|
|
262
|
-
);
|
|
263
|
-
await write(htmlPath, html, 'src/app.html');
|
|
264
|
-
}
|
|
265
|
-
}
|
|
427
|
+
// Laravel keeps its markup outside resources/css, so automatic detection
|
|
428
|
+
// never reaches it. These are the project's own lines, not ours — printed
|
|
429
|
+
// rather than written, so removing them stays possible.
|
|
430
|
+
if (project.framework === 'laravel' && !/@source ['"]\.\.\/(js|views)/.test(body)) {
|
|
431
|
+
log();
|
|
432
|
+
warn(`Tailwind also has to see your own markup — add to ${project.cssRel}:`);
|
|
433
|
+
log(c.cyan(` @source '../js';`));
|
|
434
|
+
log(c.cyan(` @source '../views';`));
|
|
266
435
|
}
|
|
267
436
|
|
|
437
|
+
/* --- 2. the document head: font preconnect + no-flash theme --- */
|
|
438
|
+
await wireHead(project, style);
|
|
439
|
+
|
|
268
440
|
/* --- 3. remind about the vite plugin, which we will not rewrite --- */
|
|
269
441
|
const viteConfig =
|
|
270
|
-
['vite.config.ts', 'vite.config.js'
|
|
271
|
-
|
|
442
|
+
['vite.config.ts', 'vite.config.js', 'vite.config.mjs']
|
|
443
|
+
.map((f) => join(cwd, f))
|
|
444
|
+
.find((f) => existsSync(f)) ?? null;
|
|
272
445
|
const viteSource = viteConfig ? await readOr(viteConfig) : '';
|
|
273
446
|
if (!viteSource.includes('@tailwindcss/vite')) {
|
|
447
|
+
const plugins = {
|
|
448
|
+
kit: `plugins: [tailwindcss(), sveltekit()]`,
|
|
449
|
+
laravel: `plugins: [laravel({ input: ['${project.cssRel}', 'resources/js/app.js'] }), tailwindcss(), svelte()]`,
|
|
450
|
+
vite: `plugins: [tailwindcss(), svelte()]`
|
|
451
|
+
}[project.framework];
|
|
452
|
+
const after = project.framework === 'kit' ? 'sveltekit()' : 'svelte()';
|
|
453
|
+
|
|
274
454
|
log();
|
|
275
455
|
warn(
|
|
276
|
-
`add the Tailwind plugin to ${viteConfig ? relative(cwd, viteConfig) : 'your vite config'} — before
|
|
456
|
+
`add the Tailwind plugin to ${viteConfig ? relative(cwd, viteConfig) : 'your vite config'} — before ${after}:`
|
|
277
457
|
);
|
|
278
458
|
log(c.cyan(` import tailwindcss from '@tailwindcss/vite';`));
|
|
279
|
-
log(c.cyan(` plugins
|
|
459
|
+
log(c.cyan(` ${plugins}`));
|
|
280
460
|
} else {
|
|
281
461
|
skip('vite config already has the Tailwind plugin');
|
|
282
462
|
}
|
|
283
463
|
|
|
284
464
|
/* --- 4. import the CSS once --- */
|
|
285
|
-
if (project.
|
|
286
|
-
|
|
465
|
+
if (project.framework === 'laravel') {
|
|
466
|
+
// Nothing imports it: the Blade layout hands the path to Vite, which is
|
|
467
|
+
// also what makes `php artisan serve` pick up the built file.
|
|
468
|
+
const blade = project.head ? await readOr(join(cwd, project.head)) : '';
|
|
469
|
+
if (blade.includes(project.cssRel)) {
|
|
470
|
+
skip(`${project.head} already loads ${project.cssRel}`);
|
|
471
|
+
} else {
|
|
472
|
+
log();
|
|
473
|
+
warn(`load the CSS from your Blade layout, inside <head>:`);
|
|
474
|
+
log(c.cyan(` @vite(['${project.cssRel}', 'resources/js/app.js'])`));
|
|
475
|
+
}
|
|
476
|
+
} else if (project.framework === 'kit') {
|
|
477
|
+
const layoutPath = join(cwd, project.entry);
|
|
287
478
|
let layout = await readOr(layoutPath);
|
|
288
479
|
const line = `import '../app.css';`;
|
|
289
480
|
|
|
290
481
|
if (layout.includes('app.css')) {
|
|
291
|
-
skip(
|
|
482
|
+
skip(`${project.entry} already imports the CSS`);
|
|
292
483
|
} else if (!layout.trim()) {
|
|
293
484
|
await write(
|
|
294
485
|
layoutPath,
|
|
@@ -300,16 +491,37 @@ async function init() {
|
|
|
300
491
|
|
|
301
492
|
{@render children()}
|
|
302
493
|
`,
|
|
303
|
-
|
|
494
|
+
project.entry
|
|
304
495
|
);
|
|
305
496
|
} else if (/<script[^>]*>/.test(layout)) {
|
|
306
497
|
layout = layout.replace(/(<script[^>]*>\n)/, `$1\t${line}\n`);
|
|
307
|
-
await write(layoutPath, layout,
|
|
498
|
+
await write(layoutPath, layout, project.entry);
|
|
308
499
|
} else {
|
|
309
500
|
log();
|
|
310
|
-
warn(
|
|
501
|
+
warn(`import the CSS once, at the top of ${project.entry}:`);
|
|
311
502
|
log(c.cyan(` ${line}`));
|
|
312
503
|
}
|
|
504
|
+
} else {
|
|
505
|
+
// Plain Vite: the entry module is the one thing every page loads.
|
|
506
|
+
const rel = project.entry
|
|
507
|
+
? relative(dirname(join(cwd, project.entry)), project.css)
|
|
508
|
+
.split(/[\\/]/)
|
|
509
|
+
.join('/')
|
|
510
|
+
: null;
|
|
511
|
+
const line = rel ? `import '${rel.startsWith('.') ? rel : `./${rel}`}';` : null;
|
|
512
|
+
|
|
513
|
+
if (!project.entry) {
|
|
514
|
+
log();
|
|
515
|
+
warn(`import ${project.cssRel} once, at the top of your entry module:`);
|
|
516
|
+
log(c.cyan(` import './${project.cssRel.split('/').pop()}';`));
|
|
517
|
+
} else {
|
|
518
|
+
const source = await readOr(join(cwd, project.entry));
|
|
519
|
+
if (source.includes(line) || /import ['"][^'"]*app\.css['"]/.test(source)) {
|
|
520
|
+
skip(`${project.entry} already imports the CSS`);
|
|
521
|
+
} else {
|
|
522
|
+
await write(join(cwd, project.entry), `${line}\n${source}`, project.entry);
|
|
523
|
+
}
|
|
524
|
+
}
|
|
313
525
|
}
|
|
314
526
|
|
|
315
527
|
log();
|
|
@@ -321,7 +533,7 @@ async function init() {
|
|
|
321
533
|
|
|
322
534
|
async function add() {
|
|
323
535
|
const { components, styles } = await registry();
|
|
324
|
-
const style = pickStyle(styles);
|
|
536
|
+
const style = await pickStyle(styles);
|
|
325
537
|
const names = args.slice(1);
|
|
326
538
|
if (names.length === 0) fail('which component? e.g. `nqm-ui add button dialog`');
|
|
327
539
|
|
|
@@ -435,22 +647,26 @@ function help() {
|
|
|
435
647
|
log(`
|
|
436
648
|
${c.bold('@nqmcreative/ui')}
|
|
437
649
|
|
|
438
|
-
${c.cyan('init
|
|
650
|
+
${c.cyan('init')} wire one style into this project
|
|
651
|
+
${c.cyan('update')} rewrite that wiring after upgrading the package
|
|
439
652
|
${c.cyan('add <name...>')} print the import for a component
|
|
440
|
-
${c.dim('--style <s>')} which style to import from (required)
|
|
441
653
|
${c.dim('--to <file>')} …and insert it into that file instead
|
|
442
654
|
${c.cyan('list [category]')} every component in the catalogue
|
|
443
655
|
${c.cyan('info <name>')} what one component pulls in, in every style
|
|
444
656
|
|
|
657
|
+
${c.dim('--style <s>')} skip the picker: matte, paper or sprout
|
|
445
658
|
${c.dim('--dry-run')} show the writes without making them
|
|
446
659
|
|
|
447
|
-
${c.dim('Every style implements every component. Run init once per project
|
|
660
|
+
${c.dim('Every style implements every component. Run init once per project,')}
|
|
661
|
+
${c.dim('then update after each upgrade — it overwrites the lines it owns.')}
|
|
448
662
|
`);
|
|
449
663
|
}
|
|
450
664
|
|
|
451
665
|
/* ------------------------------------------------------------------ main -- */
|
|
452
666
|
|
|
453
|
-
|
|
667
|
+
// `update` is `init` under the name people reach for after an upgrade: both
|
|
668
|
+
// rewrite the lines this CLI owns and leave the rest of the file alone.
|
|
669
|
+
const commands = { init, update: init, add, list, info };
|
|
454
670
|
if (!command || flags.has('--help') || command === 'help') {
|
|
455
671
|
help();
|
|
456
672
|
} else if (commands[command]) {
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hex colours for the colour field.
|
|
3
|
+
*
|
|
4
|
+
* Only hex: it is the one notation `<input type="color">` accepts and the one
|
|
5
|
+
* every design tool copies to the clipboard. Anything else the app can convert
|
|
6
|
+
* before it binds.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* `abc`, `#ABC`, `#aabbcc` → `#aabbcc`. Returns `null` when it is not a hex
|
|
10
|
+
* colour, so a half-typed value can be left alone rather than corrected under
|
|
11
|
+
* the caret.
|
|
12
|
+
*
|
|
13
|
+
* Shorthand is expanded and alpha is dropped: the native swatch understands
|
|
14
|
+
* neither, and a value the swatch cannot show is worse than a rounded one.
|
|
15
|
+
*/
|
|
16
|
+
export declare function normaliseHex(text: string): string | null;
|
|
17
|
+
/**
|
|
18
|
+
* True when a colour is dark enough to want light text on top of it.
|
|
19
|
+
*
|
|
20
|
+
* Rec. 601 luma rather than WCAG relative luminance — the threshold is for
|
|
21
|
+
* picking one of two label colours on a swatch, not for proving a contrast
|
|
22
|
+
* ratio, and this is the version everyone's design tokens are tuned against.
|
|
23
|
+
*/
|
|
24
|
+
export declare function isDark(hex: string): boolean;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hex colours for the colour field.
|
|
3
|
+
*
|
|
4
|
+
* Only hex: it is the one notation `<input type="color">` accepts and the one
|
|
5
|
+
* every design tool copies to the clipboard. Anything else the app can convert
|
|
6
|
+
* before it binds.
|
|
7
|
+
*/
|
|
8
|
+
const HEX = /^#?([0-9a-f]{3}|[0-9a-f]{4}|[0-9a-f]{6}|[0-9a-f]{8})$/i;
|
|
9
|
+
/**
|
|
10
|
+
* `abc`, `#ABC`, `#aabbcc` → `#aabbcc`. Returns `null` when it is not a hex
|
|
11
|
+
* colour, so a half-typed value can be left alone rather than corrected under
|
|
12
|
+
* the caret.
|
|
13
|
+
*
|
|
14
|
+
* Shorthand is expanded and alpha is dropped: the native swatch understands
|
|
15
|
+
* neither, and a value the swatch cannot show is worse than a rounded one.
|
|
16
|
+
*/
|
|
17
|
+
export function normaliseHex(text) {
|
|
18
|
+
const match = HEX.exec(text.trim());
|
|
19
|
+
if (!match)
|
|
20
|
+
return null;
|
|
21
|
+
let digits = match[1].toLowerCase();
|
|
22
|
+
if (digits.length <= 4)
|
|
23
|
+
digits = [...digits].map((digit) => digit + digit).join('');
|
|
24
|
+
return `#${digits.slice(0, 6)}`;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* True when a colour is dark enough to want light text on top of it.
|
|
28
|
+
*
|
|
29
|
+
* Rec. 601 luma rather than WCAG relative luminance — the threshold is for
|
|
30
|
+
* picking one of two label colours on a swatch, not for proving a contrast
|
|
31
|
+
* ratio, and this is the version everyone's design tokens are tuned against.
|
|
32
|
+
*/
|
|
33
|
+
export function isDark(hex) {
|
|
34
|
+
const value = normaliseHex(hex);
|
|
35
|
+
if (!value)
|
|
36
|
+
return false;
|
|
37
|
+
const [r, g, b] = [1, 3, 5].map((at) => parseInt(value.slice(at, at + 2), 16));
|
|
38
|
+
return (r * 299 + g * 587 + b * 114) / 1000 < 140;
|
|
39
|
+
}
|
package/dist/core/index.d.ts
CHANGED
|
@@ -25,7 +25,14 @@ export { pageRange } from './pagination.js';
|
|
|
25
25
|
export { sortFiles, matchesAccept, formatSize } from './files.js';
|
|
26
26
|
export type { RejectedFile, FileRules } from './files.js';
|
|
27
27
|
export { passwordScore } from './password.js';
|
|
28
|
-
export { clampToStep, percentOf, stepDecimals } from './number.js';
|
|
28
|
+
export { clampToStep, formatGrouped, parseGrouped, percentOf, stepDecimals } from './number.js';
|
|
29
|
+
export type { GroupOptions } from './number.js';
|
|
30
|
+
export { caretIndex, clearFrom, fillFrom, sanitisePin } from './pin.js';
|
|
31
|
+
export type { PinMode } from './pin.js';
|
|
32
|
+
export { addTag, splitTags } from './tags.js';
|
|
33
|
+
export type { TagRules, TagRejection } from './tags.js';
|
|
34
|
+
export { normaliseHex, isDark } from './color.js';
|
|
35
|
+
export { MINUTES_IN_DAY, clampTime, formatClock, formatTime, parseTime, stepTime } from './time.js';
|
|
29
36
|
export { toISO, fromISO, today, isSameDay, addDays, addMonths, monthGrid, weekdayNames, monthLabel, isOutOfRange, formatISO, parseFormatted } from './date.js';
|
|
30
37
|
export type { DateFormat } from './date.js';
|
|
31
38
|
export { calendarKeyMove, dateHint } from './calendar.js';
|
package/dist/core/index.js
CHANGED
|
@@ -23,7 +23,11 @@ export { nextSort, sortRows, toggleKey } from './table.js';
|
|
|
23
23
|
export { pageRange } from './pagination.js';
|
|
24
24
|
export { sortFiles, matchesAccept, formatSize } from './files.js';
|
|
25
25
|
export { passwordScore } from './password.js';
|
|
26
|
-
export { clampToStep, percentOf, stepDecimals } from './number.js';
|
|
26
|
+
export { clampToStep, formatGrouped, parseGrouped, percentOf, stepDecimals } from './number.js';
|
|
27
|
+
export { caretIndex, clearFrom, fillFrom, sanitisePin } from './pin.js';
|
|
28
|
+
export { addTag, splitTags } from './tags.js';
|
|
29
|
+
export { normaliseHex, isDark } from './color.js';
|
|
30
|
+
export { MINUTES_IN_DAY, clampTime, formatClock, formatTime, parseTime, stepTime } from './time.js';
|
|
27
31
|
/* --- dates --- */
|
|
28
32
|
export { toISO, fromISO, today, isSameDay, addDays, addMonths, monthGrid, weekdayNames, monthLabel, isOutOfRange, formatISO, parseFormatted } from './date.js';
|
|
29
33
|
export { calendarKeyMove, dateHint } from './calendar.js';
|