@navecss/core 0.3.0 → 0.5.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.
@@ -0,0 +1,302 @@
1
+ import { atoms } from './chunk-CUL5PLPI.js';
2
+ import { atomClassMap } from './chunk-GZL2VW7C.js';
3
+
4
+ // src/directive/diagnostics-format.ts
5
+ function vocabulary(extend) {
6
+ const extendKeys = new Set(Object.keys(extend));
7
+ const names = [];
8
+ for (const [name, definition] of Object.entries(atoms)) {
9
+ if (definition && !extendKeys.has(name)) names.push(name);
10
+ }
11
+ for (const [name, definition] of Object.entries(extend)) {
12
+ if (definition) names.push(name);
13
+ }
14
+ return names;
15
+ }
16
+ function distance(a, b) {
17
+ const d = [];
18
+ for (let i = 0; i <= a.length; i++) d.push([i]);
19
+ for (let j = 0; j <= b.length; j++) d[0][j] = j;
20
+ for (let i = 1; i <= a.length; i++) {
21
+ for (let j = 1; j <= b.length; j++) {
22
+ const cost = a[i - 1] === b[j - 1] ? 0 : 1;
23
+ let best = Math.min(d[i - 1][j] + 1, d[i][j - 1] + 1, d[i - 1][j - 1] + cost);
24
+ if (i > 1 && j > 1 && a[i - 1] === b[j - 2] && a[i - 2] === b[j - 1]) {
25
+ best = Math.min(best, d[i - 2][j - 2] + 1);
26
+ }
27
+ d[i][j] = best;
28
+ }
29
+ }
30
+ return d[a.length][b.length];
31
+ }
32
+ function normalize(name) {
33
+ const lower = name.toLowerCase();
34
+ const withoutPrefix = lower.startsWith("nave-") ? lower.slice(5) : lower;
35
+ return withoutPrefix.replaceAll("-", "");
36
+ }
37
+ function groupByDistance(normalizedTyped, names) {
38
+ let minDistance = Infinity;
39
+ const byDistance = /* @__PURE__ */ new Map();
40
+ for (const name of names) {
41
+ const d = distance(normalizedTyped, normalize(name));
42
+ if (!byDistance.has(d)) byDistance.set(d, []);
43
+ byDistance.get(d).push(name);
44
+ if (d < minDistance) minDistance = d;
45
+ }
46
+ return { byDistance, minDistance };
47
+ }
48
+ function hasCamelCaseNote(typed, candidates, minDistance, extend) {
49
+ if (minDistance !== 0 || candidates.length !== 1) return false;
50
+ if (!typed.includes("-")) return false;
51
+ const candidate = candidates[0];
52
+ const shadow = extend[candidate];
53
+ if (shadow) return false;
54
+ return Object.hasOwn(atomClassMap, candidate);
55
+ }
56
+ function findHint(typed, extend) {
57
+ const normalizedTyped = normalize(typed);
58
+ const { byDistance, minDistance } = groupByDistance(normalizedTyped, vocabulary(extend));
59
+ if (minDistance > 2 || minDistance >= normalizedTyped.length / 2) return void 0;
60
+ const candidates = byDistance.get(minDistance) ?? [];
61
+ if (candidates.length === 0) return void 0;
62
+ return { candidates, camelCaseNote: hasCamelCaseNote(typed, candidates, minDistance, extend) };
63
+ }
64
+ function quoteList(items) {
65
+ if (items.length === 1) return `"${items[0]}"`;
66
+ return `${items.slice(0, -1).map((i) => `"${i}"`).join(", ")} or "${items.at(-1)}"`;
67
+ }
68
+ function formatUnknownAtom(name, extend) {
69
+ const first = `@nave: unknown atom "${name}".`;
70
+ const hint = findHint(name, extend);
71
+ if (hint) {
72
+ const camelCase = hint.camelCaseNote ? ` Atom names are camelCase; "${atomClassMap[hint.candidates[0]]}" is its class.` : "";
73
+ return `${first} Did you mean ${quoteList(hint.candidates)}?${camelCase}`;
74
+ }
75
+ const available = vocabulary(extend).join(", ");
76
+ return `${first} If it is an atom of your own, pass it in the extend option.
77
+ Available: ${available}`;
78
+ }
79
+ var ANSWERED_BY_STANDALONE = /* @__PURE__ */ new Set([
80
+ "@navecss/core",
81
+ "@navecss/core/atomic",
82
+ "@navecss/core/layers",
83
+ "@navecss/core/reset",
84
+ "@navecss/core/standalone",
85
+ "@navecss/tokens/css"
86
+ ]);
87
+ var STANDALONE_PATH = "node_modules/@navecss/core/dist/standalone.css";
88
+ var STANDALONE_CDN_URL = "https://cdn.jsdelivr.net/npm/@navecss/core@<version>/dist/standalone.css";
89
+ var NO_TOKENS_PATH = "node_modules/@navecss/core/dist/no-tokens.css";
90
+ var NO_TOKENS_CDN_URL = "https://cdn.jsdelivr.net/npm/@navecss/core@<version>/dist/no-tokens.css";
91
+ var BAD_PARENT_WORKAROUND = ". A directive here can be written `& { @nave ...; }` inside the group rule instead.";
92
+ function formatBadParent(diagnostic) {
93
+ const workaround = diagnostic.detail === "nested-group" ? BAD_PARENT_WORKAROUND : "";
94
+ return `@nave must be the direct child of a CSS rule selector block${workaround}`;
95
+ }
96
+ var FIXED_TEXTS = {
97
+ "bad-token": (d) => `@nave: unexpected "${d.text ?? ""}"; a directive takes only atom names (separate atom names with spaces)`,
98
+ "bare-import": (d) => {
99
+ const specifier = d.text ?? "";
100
+ const quoted = JSON.stringify(specifier);
101
+ if (ANSWERED_BY_STANDALONE.has(specifier)) {
102
+ return `@import ${quoted} names a package, so a browser cannot load it. Link Nave's self-contained stylesheet from the page instead, ahead of your own stylesheets: ${STANDALONE_PATH}, or ${STANDALONE_CDN_URL} with <version> replaced by the version you installed.`;
103
+ }
104
+ if (specifier === "@navecss/core/no-tokens") {
105
+ return `@import ${quoted} names a package, so a browser cannot load it. Link that entry's own file from the page instead, ahead of your own stylesheets: ${NO_TOKENS_PATH}, or ${NO_TOKENS_CDN_URL} with <version> replaced by the version you installed. Not the self-contained stylesheet: it holds Nave's token layer, which this entry leaves out.`;
106
+ }
107
+ return `@import ${quoted} names no file relative to the output, so a browser cannot load it. Point it at a file the page can reach, by a path that works from the output or by a URL, or bundle the stylesheet.`;
108
+ },
109
+ "has-block": () => "@nave: a directive with a {} block is not supported",
110
+ "in-keyframes": () => "@nave cannot be used inside @keyframes",
111
+ "missing-declarations": (d) => `@nave: atom "${d.name ?? ""}" is registered without a declarations object`,
112
+ "no-atom": () => "@nave: directive names no atom"
113
+ };
114
+ function formatDiagnostic(diagnostic, options = {}) {
115
+ if (diagnostic.code === "unknown-atom")
116
+ return formatUnknownAtom(diagnostic.name, options.extend ?? {});
117
+ if (diagnostic.code === "bad-parent") return formatBadParent(diagnostic);
118
+ return FIXED_TEXTS[diagnostic.code](diagnostic);
119
+ }
120
+
121
+ // src/directive/source-map.ts
122
+ var BASE64_DIGITS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
123
+ var NEWLINE_CHARS = /* @__PURE__ */ new Set(["\n", "\r", "\f"]);
124
+ function encodeVLQ(n) {
125
+ let value = n < 0 ? (-n << 1) + 1 : n << 1;
126
+ let result = "";
127
+ do {
128
+ let digit = value & 31;
129
+ value >>>= 5;
130
+ if (value > 0) digit |= 32;
131
+ result += BASE64_DIGITS[digit];
132
+ } while (value > 0);
133
+ return result;
134
+ }
135
+ var BASE64_VALUE = new Map([...BASE64_DIGITS].map((c, i) => [c, i]));
136
+ function decodeVLQSegment(mappings, start) {
137
+ let value = 0;
138
+ let shift = 0;
139
+ let i = start;
140
+ for (; ; ) {
141
+ const digit = BASE64_VALUE.get(mappings[i]);
142
+ i++;
143
+ value += (digit & 31) << shift;
144
+ if ((digit & 32) === 0) break;
145
+ shift += 5;
146
+ }
147
+ const isNegative = (value & 1) === 1;
148
+ const magnitude = value >>> 1;
149
+ return { value: isNegative ? -magnitude : magnitude, next: i };
150
+ }
151
+ var MappingsBuilder = class {
152
+ hasSegmentOnLine = false;
153
+ lines = [[]];
154
+ outputColumn = 0;
155
+ outputLine = 0;
156
+ // A lone trailing `\r`, possibly half of a CRLF pair split across two
157
+ // `advance()` calls by a removed directive: consumed by the next call's
158
+ // leading `\n`, if any, instead of double-counted.
159
+ pendingCR = false;
160
+ prevGeneratedColumn = 0;
161
+ prevSourceColumn = 0;
162
+ prevSourceIndex = 0;
163
+ prevSourceLine = 0;
164
+ /**
165
+ * A plain indexed loop, not `for...of` (which iterates by code point, so
166
+ * a surrogate pair would advance the column by one instead of two — every
167
+ * position here is in UTF-16 code units). A line break is CR, FF, LF or a
168
+ * CRLF pair, CSS Syntax 3's own "newline" (§4.2).
169
+ */
170
+ advance(text) {
171
+ let i = this.consumePendingCR(text);
172
+ while (i < text.length) {
173
+ const c = text[i];
174
+ if (c !== void 0 && NEWLINE_CHARS.has(c)) {
175
+ i = this.advancePastNewline(text, i, c);
176
+ continue;
177
+ }
178
+ this.outputColumn++;
179
+ i++;
180
+ }
181
+ }
182
+ /**
183
+ * `i` past the line break `c` (at `i`): past the whole pair for a `\r`
184
+ * immediately followed by `\n`, and `pendingCR` set instead when `\r` is
185
+ * the last character `text` has to offer.
186
+ */
187
+ advancePastNewline(text, i, c) {
188
+ let next = i + 1;
189
+ if (c === "\r" && text[next] === "\n") {
190
+ next++;
191
+ } else if (c === "\r" && next === text.length) {
192
+ this.pendingCR = true;
193
+ }
194
+ this.outputLine++;
195
+ this.outputColumn = 0;
196
+ this.lines.push([]);
197
+ this.prevGeneratedColumn = 0;
198
+ this.hasSegmentOnLine = false;
199
+ return next;
200
+ }
201
+ /**
202
+ * How far into `text` `advance` should start: `1` for a lone `\n` that is
203
+ * really the other half of a CRLF pair split across two calls; `0`
204
+ * otherwise.
205
+ */
206
+ consumePendingCR(text) {
207
+ if (text.length === 0) return 0;
208
+ const skip = this.pendingCR && text.startsWith("\n") ? 1 : 0;
209
+ this.pendingCR = false;
210
+ return skip;
211
+ }
212
+ mark(position) {
213
+ const sourceIndex = position.sourceIndex ?? 0;
214
+ const sourceLine = position.line - 1;
215
+ const generatedColumnDelta = this.hasSegmentOnLine ? this.outputColumn - this.prevGeneratedColumn : this.outputColumn;
216
+ const sourceIndexDelta = sourceIndex - this.prevSourceIndex;
217
+ const sourceLineDelta = sourceLine - this.prevSourceLine;
218
+ const sourceColumnDelta = position.column - this.prevSourceColumn;
219
+ this.lines[this.outputLine].push(
220
+ encodeVLQ(generatedColumnDelta) + encodeVLQ(sourceIndexDelta) + encodeVLQ(sourceLineDelta) + encodeVLQ(sourceColumnDelta)
221
+ );
222
+ this.prevGeneratedColumn = this.outputColumn;
223
+ this.prevSourceIndex = sourceIndex;
224
+ this.prevSourceLine = sourceLine;
225
+ this.prevSourceColumn = position.column;
226
+ this.hasSegmentOnLine = true;
227
+ }
228
+ toMappings() {
229
+ return this.lines.map((segments) => segments.join(",")).join(";");
230
+ }
231
+ };
232
+ function findCandidate(segments, position) {
233
+ const generatedLine = position.line - 1;
234
+ let low = 0;
235
+ let high = segments.length - 1;
236
+ let candidate;
237
+ while (low <= high) {
238
+ const mid = low + high >> 1;
239
+ const segment = segments[mid];
240
+ const isAtOrBefore = segment.generatedLine < generatedLine || segment.generatedLine === generatedLine && segment.generatedColumn <= position.column;
241
+ if (isAtOrBefore) {
242
+ candidate = segment;
243
+ low = mid + 1;
244
+ } else {
245
+ high = mid - 1;
246
+ }
247
+ }
248
+ return candidate?.generatedLine === generatedLine ? candidate : void 0;
249
+ }
250
+ function decodeIncomingMap(map) {
251
+ const sources = map.sources;
252
+ const segments = decodeSegments(map.mappings);
253
+ return {
254
+ sources,
255
+ sourceRoot: map.sourceRoot,
256
+ sourcesContent: map.sourcesContent,
257
+ originalPositionFor(position) {
258
+ const candidate = findCandidate(segments, position);
259
+ if (!candidate) return;
260
+ return {
261
+ source: sources[candidate.sourceIndex],
262
+ sourceIndex: candidate.sourceIndex,
263
+ line: candidate.sourceLine + 1,
264
+ column: candidate.sourceColumn
265
+ };
266
+ }
267
+ };
268
+ }
269
+ function decodeOneSegment(state, raw, generatedLine) {
270
+ const col = decodeVLQSegment(raw, 0);
271
+ state.generatedColumn += col.value;
272
+ if (col.next >= raw.length) return void 0;
273
+ const srcIdx = decodeVLQSegment(raw, col.next);
274
+ state.sourceIndex += srcIdx.value;
275
+ const srcLine = decodeVLQSegment(raw, srcIdx.next);
276
+ state.sourceLine += srcLine.value;
277
+ const srcCol = decodeVLQSegment(raw, srcLine.next);
278
+ state.sourceColumn += srcCol.value;
279
+ return {
280
+ generatedLine,
281
+ generatedColumn: state.generatedColumn,
282
+ sourceIndex: state.sourceIndex,
283
+ sourceLine: state.sourceLine,
284
+ sourceColumn: state.sourceColumn
285
+ };
286
+ }
287
+ function decodeSegments(mappings) {
288
+ const segments = [];
289
+ const state = { generatedColumn: 0, sourceIndex: 0, sourceLine: 0, sourceColumn: 0 };
290
+ const lines = mappings.split(";");
291
+ for (const [generatedLine, line] of lines.entries()) {
292
+ state.generatedColumn = 0;
293
+ if (line === "") continue;
294
+ for (const raw of line.split(",")) {
295
+ const segment = decodeOneSegment(state, raw, generatedLine);
296
+ if (segment) segments.push(segment);
297
+ }
298
+ }
299
+ return segments;
300
+ }
301
+
302
+ export { MappingsBuilder, decodeIncomingMap, formatDiagnostic };
@@ -0,0 +1,204 @@
1
+ // src/directive/expand-text-diagnostics.ts
2
+ function lineStartOffsets(text) {
3
+ const starts = [0];
4
+ let i = 0;
5
+ while (i < text.length) {
6
+ const c = text[i];
7
+ if (c === "\r") {
8
+ i++;
9
+ if (text[i] === "\n") i++;
10
+ starts.push(i);
11
+ continue;
12
+ }
13
+ if (c === "\n" || c === "\f") {
14
+ i++;
15
+ starts.push(i);
16
+ continue;
17
+ }
18
+ i++;
19
+ }
20
+ return starts;
21
+ }
22
+ function lastAtOrBefore(starts, offset) {
23
+ let low = 0;
24
+ let high = starts.length - 1;
25
+ while (low < high) {
26
+ const mid = low + high + 1 >> 1;
27
+ if (starts[mid] <= offset) low = mid;
28
+ else high = mid - 1;
29
+ }
30
+ return low;
31
+ }
32
+ function createPositionFinder(text) {
33
+ const starts = lineStartOffsets(text);
34
+ return (offset) => {
35
+ const line = lastAtOrBefore(starts, offset);
36
+ return { line: line + 1, column: offset - starts[line] };
37
+ };
38
+ }
39
+ function reportDiagnostics(diagnostics, css, options) {
40
+ const onUnknown = options.onUnknown ?? "error";
41
+ if (onUnknown === "ignore") return [];
42
+ const severity = onUnknown === "warn" ? "warning" : "error";
43
+ const positionAt = createPositionFinder(css);
44
+ return diagnostics.map((diagnostic) => {
45
+ const position = positionAt(diagnostic.offset);
46
+ return {
47
+ ...diagnostic,
48
+ severity,
49
+ ...options.from !== void 0 && { file: options.from },
50
+ line: position.line,
51
+ column: position.column + 1
52
+ };
53
+ });
54
+ }
55
+ function positionWarnings(warnings, css, file) {
56
+ const positionAt = createPositionFinder(css);
57
+ return warnings.toSorted((a, b) => a.offset - b.offset).map(({ offset, text }) => {
58
+ const position = positionAt(offset);
59
+ return {
60
+ text,
61
+ offset,
62
+ line: position.line,
63
+ column: position.column + 1,
64
+ ...file !== void 0 && { file }
65
+ };
66
+ });
67
+ }
68
+
69
+ // src/directive/bracket-match.ts
70
+ var OPENERS = /* @__PURE__ */ new Set(["(-token", "[-token", "function-token", "{-token"]);
71
+ var CLOSERS = /* @__PURE__ */ new Set([")-token", "]-token", "}-token"]);
72
+ function isMirrorCloser(openerType, closerType) {
73
+ if (closerType === ")-token") return openerType === "(-token" || openerType === "function-token";
74
+ if (closerType === "]-token") return openerType === "[-token";
75
+ return openerType === "{-token";
76
+ }
77
+ function matchBrackets(tokens) {
78
+ const closerFor = new Int32Array(tokens.length).fill(-1);
79
+ const opens = [];
80
+ for (const [i, token] of tokens.entries()) {
81
+ const { type } = token;
82
+ if (OPENERS.has(type)) {
83
+ opens.push(i);
84
+ continue;
85
+ }
86
+ if (!CLOSERS.has(type)) continue;
87
+ const openIndex = opens.at(-1);
88
+ if (openIndex === void 0 || !isMirrorCloser(tokens[openIndex].type, type)) continue;
89
+ opens.pop();
90
+ closerFor[openIndex] = i;
91
+ }
92
+ return closerFor;
93
+ }
94
+ function matchedCloseWithin(closerFor, openIndex, limit) {
95
+ const closeIndex = closerFor[openIndex];
96
+ return closeIndex !== -1 && closeIndex < limit ? closeIndex : void 0;
97
+ }
98
+
99
+ // src/directive/block-reader.ts
100
+ function scanItem(tokens, start, limit, closerFor) {
101
+ let i = start;
102
+ while (i < limit) {
103
+ const type = tokens[i].type;
104
+ if (OPENERS.has(type)) {
105
+ const closeIndex = matchedCloseWithin(closerFor, i, limit);
106
+ if (type === "{-token") {
107
+ return {
108
+ end: closeIndex === void 0 ? limit : closeIndex + 1,
109
+ consumedSemicolon: false,
110
+ trailingBlock: { openIndex: i, closeIndex }
111
+ };
112
+ }
113
+ i = closeIndex === void 0 ? limit : closeIndex + 1;
114
+ continue;
115
+ }
116
+ if (CLOSERS.has(type)) {
117
+ if (i === start) return { end: i + 1, consumedSemicolon: false };
118
+ i++;
119
+ continue;
120
+ }
121
+ if (type === "semicolon-token") return { end: i + 1, consumedSemicolon: true };
122
+ i++;
123
+ }
124
+ return { end: i, consumedSemicolon: false };
125
+ }
126
+ function scanCustomPropertyValue(tokens, start, limit, closerFor) {
127
+ let i = start;
128
+ while (i < limit) {
129
+ const type = tokens[i].type;
130
+ if (OPENERS.has(type)) {
131
+ const closeIndex = matchedCloseWithin(closerFor, i, limit);
132
+ i = closeIndex === void 0 ? limit : closeIndex + 1;
133
+ continue;
134
+ }
135
+ if (type === "semicolon-token") return i + 1;
136
+ i++;
137
+ }
138
+ return i;
139
+ }
140
+ function isColonSoonAfter(tokens, i, limit) {
141
+ let j = i;
142
+ while (j < limit && (tokens[j].type === "whitespace-token" || tokens[j].type === "comment")) j++;
143
+ return tokens[j]?.type === "colon-token";
144
+ }
145
+ function isCustomPropertyName(token) {
146
+ return token.type === "ident-token" && (token.structured?.value).startsWith("--");
147
+ }
148
+ function atKeywordName(token) {
149
+ return (token.structured?.value).toLowerCase();
150
+ }
151
+ function toDeclarationItem(start, end) {
152
+ return { kind: "declaration", start, end };
153
+ }
154
+ function preludeEndOffset(tokens, start, scan, preludeStart) {
155
+ if (scan.trailingBlock) return tokens[scan.trailingBlock.openIndex].startIndex;
156
+ const lastContentIndex = scan.consumedSemicolon ? scan.end - 2 : scan.end - 1;
157
+ return lastContentIndex >= start ? tokens[lastContentIndex].endIndex : preludeStart;
158
+ }
159
+ function toAtRuleItem(tokens, start, scan) {
160
+ const nameToken = tokens[start];
161
+ const preludeStart = tokens[start + 1]?.startIndex ?? nameToken.endIndex;
162
+ return {
163
+ kind: "at-rule",
164
+ start,
165
+ end: scan.end,
166
+ atKeyword: atKeywordName(nameToken),
167
+ preludeOffset: preludeStart,
168
+ preludeEndOffset: preludeEndOffset(tokens, start + 1, scan, preludeStart),
169
+ ...scan.trailingBlock && {
170
+ blockStart: scan.trailingBlock.openIndex + 1,
171
+ blockEnd: scan.trailingBlock.closeIndex ?? scan.end
172
+ }
173
+ };
174
+ }
175
+ function toRuleOrInvalidItem(start, scan) {
176
+ if (!scan.trailingBlock) return { kind: "invalid", start, end: scan.end };
177
+ return {
178
+ kind: "rule",
179
+ start,
180
+ end: scan.end,
181
+ blockStart: scan.trailingBlock.openIndex + 1,
182
+ blockEnd: scan.trailingBlock.closeIndex ?? scan.end
183
+ };
184
+ }
185
+ function readItem(tokens, start, limit, closerFor) {
186
+ const first = tokens[start];
187
+ if (first.type === "at-keyword-token") {
188
+ return toAtRuleItem(tokens, start, scanItem(tokens, start + 1, limit, closerFor));
189
+ }
190
+ if (first.type === "ident-token" && isColonSoonAfter(tokens, start + 1, limit)) {
191
+ if (isCustomPropertyName(first)) {
192
+ return toDeclarationItem(start, scanCustomPropertyValue(tokens, start + 1, limit, closerFor));
193
+ }
194
+ const scan = scanItem(tokens, start + 1, limit, closerFor);
195
+ if (!scan.trailingBlock) return toDeclarationItem(start, scan.end);
196
+ return toRuleOrInvalidItem(start, scan);
197
+ }
198
+ return toRuleOrInvalidItem(start, scanItem(tokens, start, limit, closerFor));
199
+ }
200
+ function isNaveAtKeyword(token) {
201
+ return token.type === "at-keyword-token" && atKeywordName(token) === "nave";
202
+ }
203
+
204
+ export { OPENERS, atKeywordName, createPositionFinder, isNaveAtKeyword, matchBrackets, positionWarnings, readItem, reportDiagnostics };
@@ -1,4 +1,5 @@
1
- import { createPositionFinder, tokenize, matchBrackets, readItem, isNaveAtKeyword } from './chunk-NFS2REPQ.js';
1
+ import { createPositionFinder, matchBrackets, readItem, isNaveAtKeyword } from './chunk-UVHGEVMV.js';
2
+ import { tokenize } from './chunk-2K5EBBF2.js';
2
3
 
3
4
  // src/directive/find-survivors.ts
4
5
  var NATURAL_STOP_CHARS = /* @__PURE__ */ new Set(["\n", ";", "}"]);