@actualwave/codemirror-lang-embed-tailwind 0.0.0-stage → 0.1.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/completion.js +59 -0
- package/data.js +135 -0
- package/decoration.js +106 -0
- package/index.js +21 -0
- package/package.json +23 -4
- package/README.md +0 -3
package/completion.js
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
const { syntaxTree } = require('@codemirror/language');
|
|
2
|
+
const { getTaggedTemplateTagPath, templateContentRanges, docAsInput } = require('@actualwave/codemirror-lang-embed-core');
|
|
3
|
+
const { TAILWIND_CLASS_NAMES } = require('./data.js');
|
|
4
|
+
|
|
5
|
+
// Finds the innermost TemplateString node at `pos` (if any) whose tag is `tw`.
|
|
6
|
+
// Mirrors embed-core's getTaggedTemplateTagPath usage in parseMixed, but
|
|
7
|
+
// driving a CompletionSource instead of a nested parser — see TODO.md item 6:
|
|
8
|
+
// "same tag-name-matching primitive used for parseMixed, just driving
|
|
9
|
+
// completions instead of a sub-parser."
|
|
10
|
+
function findTwTemplateAt(state, pos) {
|
|
11
|
+
const tree = syntaxTree(state);
|
|
12
|
+
const node = tree.resolveInner(pos, -1);
|
|
13
|
+
for (let cur = node; cur; cur = cur.parent) {
|
|
14
|
+
if (cur.name !== 'TemplateString') continue;
|
|
15
|
+
const tagPath = getTaggedTemplateTagPath(cur, docAsInput(state.doc));
|
|
16
|
+
if (tagPath && tagPath.length === 1 && tagPath[0] === 'tw') return cur;
|
|
17
|
+
}
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function isInsideContentRanges(pos, ranges) {
|
|
22
|
+
return ranges.some((r) => pos >= r.from && pos <= r.to);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* A `CompletionSource` (see @codemirror/autocomplete) that suggests Tailwind
|
|
27
|
+
* (twrnc) class names while the cursor is inside a `` tw`...` `` tagged
|
|
28
|
+
* template's literal content, and declines (returns null) everywhere else so
|
|
29
|
+
* normal JS/TSX completions are unaffected.
|
|
30
|
+
*/
|
|
31
|
+
function tailwindCompletionSource(context) {
|
|
32
|
+
const templateNode = findTwTemplateAt(context.state, context.pos);
|
|
33
|
+
if (!templateNode) return null;
|
|
34
|
+
|
|
35
|
+
const ranges = templateContentRanges(templateNode);
|
|
36
|
+
if (!isInsideContentRanges(context.pos, ranges)) return null;
|
|
37
|
+
|
|
38
|
+
const word = context.matchBefore(/[^\s`]*/);
|
|
39
|
+
if (!word) return null;
|
|
40
|
+
if (word.from === word.to && !context.explicit) return null;
|
|
41
|
+
|
|
42
|
+
const query = word.text;
|
|
43
|
+
const from = word.from;
|
|
44
|
+
|
|
45
|
+
// Arbitrary-value bracket syntax (bg-[#123456], p-[13px]) — don't try to
|
|
46
|
+
// complete inside the brackets, just don't offer suggestions there (stretch
|
|
47
|
+
// goal per TODO.md item 6; v1 just avoids breaking on it).
|
|
48
|
+
if (query.includes('[')) return null;
|
|
49
|
+
|
|
50
|
+
const options = TAILWIND_CLASS_NAMES.filter((name) => name.startsWith(query)).map((label) => ({
|
|
51
|
+
label,
|
|
52
|
+
type: 'class',
|
|
53
|
+
}));
|
|
54
|
+
if (!options.length) return null;
|
|
55
|
+
|
|
56
|
+
return { from, options, validFor: /^[^\s`]*$/ };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
module.exports = { tailwindCompletionSource, findTwTemplateAt, isInsideContentRanges };
|
package/data.js
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
// Categorized Tailwind/twrnc utility class list, generated from small scale
|
|
2
|
+
// tables rather than hand-listed one by one — see TODO.md item 6: "Category
|
|
3
|
+
// must be data-driven per class, not prefix-derived. Tailwind overloads
|
|
4
|
+
// prefixes across categories (`text-lg` is typography, `text-red-500` is
|
|
5
|
+
// color, `text-center` is alignment/layout, all starting with `text-`)."
|
|
6
|
+
// Web-only categories (grid, filters, tables, SVG) are intentionally omitted
|
|
7
|
+
// — this targets the RN-relevant subset twrnc actually implements.
|
|
8
|
+
|
|
9
|
+
const SPACING_SCALE = [
|
|
10
|
+
'0', 'px', '0.5', '1', '1.5', '2', '2.5', '3', '3.5', '4', '5', '6', '7', '8',
|
|
11
|
+
'9', '10', '11', '12', '14', '16', '20', '24', '28', '32', '36', '40', '44',
|
|
12
|
+
'48', '52', '56', '60', '64', '72', '80', '96',
|
|
13
|
+
];
|
|
14
|
+
|
|
15
|
+
const FRACTIONS = [
|
|
16
|
+
'1/2', '1/3', '2/3', '1/4', '2/4', '3/4', '1/5', '2/5', '3/5', '4/5',
|
|
17
|
+
'1/6', '2/6', '3/6', '4/6', '5/6',
|
|
18
|
+
'1/12', '2/12', '3/12', '4/12', '5/12', '6/12', '7/12', '8/12', '9/12', '10/12', '11/12',
|
|
19
|
+
'full',
|
|
20
|
+
];
|
|
21
|
+
|
|
22
|
+
const COLOR_NAMES = [
|
|
23
|
+
'slate', 'gray', 'zinc', 'neutral', 'stone', 'red', 'orange', 'amber', 'yellow',
|
|
24
|
+
'lime', 'green', 'emerald', 'teal', 'cyan', 'sky', 'blue', 'indigo', 'violet',
|
|
25
|
+
'purple', 'fuchsia', 'pink', 'rose',
|
|
26
|
+
];
|
|
27
|
+
|
|
28
|
+
const COLOR_SHADES = ['50', '100', '200', '300', '400', '500', '600', '700', '800', '900', '950'];
|
|
29
|
+
|
|
30
|
+
const OPACITY_SCALE = ['0', '5', '10', '20', '25', '30', '40', '50', '60', '70', '75', '80', '90', '95', '100'];
|
|
31
|
+
|
|
32
|
+
const ROUNDED_SIZES = ['none', 'sm', '', 'md', 'lg', 'xl', '2xl', '3xl', 'full'];
|
|
33
|
+
const ROUNDED_CORNERS = ['t', 'r', 'b', 'l', 'tl', 'tr', 'br', 'bl'];
|
|
34
|
+
|
|
35
|
+
// class -> category, built up by the add() calls below.
|
|
36
|
+
const CLASSES = new Map();
|
|
37
|
+
|
|
38
|
+
function add(category, ...names) {
|
|
39
|
+
for (const name of names) {
|
|
40
|
+
if (name) CLASSES.set(name, category);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function addScaled(category, prefixes, scales) {
|
|
45
|
+
for (const prefix of prefixes) {
|
|
46
|
+
for (const scale of scales) {
|
|
47
|
+
add(category, `${prefix}-${scale}`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// --- Spacing: padding, margin (+ negative), gap, space-between ---
|
|
53
|
+
addScaled('spacing', ['p', 'pt', 'pr', 'pb', 'pl', 'px', 'py'], SPACING_SCALE);
|
|
54
|
+
addScaled('spacing', ['m', 'mt', 'mr', 'mb', 'ml', 'mx', 'my'], SPACING_SCALE);
|
|
55
|
+
for (const prefix of ['m', 'mt', 'mr', 'mb', 'ml', 'mx', 'my']) {
|
|
56
|
+
for (const scale of SPACING_SCALE) add('spacing', `-${prefix}-${scale}`);
|
|
57
|
+
}
|
|
58
|
+
addScaled('spacing', ['gap', 'gap-x', 'gap-y'], SPACING_SCALE);
|
|
59
|
+
addScaled('spacing', ['space-x', 'space-y'], SPACING_SCALE);
|
|
60
|
+
add('spacing', 'space-x-reverse', 'space-y-reverse');
|
|
61
|
+
|
|
62
|
+
// --- Sizing: width/height (+ min/max), fractions, full/screen/auto ---
|
|
63
|
+
const SIZING_PREFIXES = ['w', 'h', 'min-w', 'min-h', 'max-w', 'max-h'];
|
|
64
|
+
addScaled('sizing', SIZING_PREFIXES, SPACING_SCALE);
|
|
65
|
+
addScaled('sizing', SIZING_PREFIXES, FRACTIONS);
|
|
66
|
+
for (const prefix of SIZING_PREFIXES) {
|
|
67
|
+
add('sizing', `${prefix}-auto`, `${prefix}-screen`, `${prefix}-min`, `${prefix}-max`, `${prefix}-fit`);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// --- Layout: display, flexbox container/item, alignment, order ---
|
|
71
|
+
add('layout', 'flex', 'hidden', 'inline', 'inline-block', 'block');
|
|
72
|
+
add('layout', 'flex-row', 'flex-row-reverse', 'flex-col', 'flex-col-reverse');
|
|
73
|
+
add('layout', 'flex-wrap', 'flex-wrap-reverse', 'flex-nowrap');
|
|
74
|
+
add('layout', 'flex-1', 'flex-auto', 'flex-initial', 'flex-none');
|
|
75
|
+
add('layout', 'grow', 'grow-0', 'shrink', 'shrink-0');
|
|
76
|
+
addScaled('layout', ['basis'], SPACING_SCALE);
|
|
77
|
+
addScaled('layout', ['basis'], FRACTIONS);
|
|
78
|
+
add('layout', 'basis-auto');
|
|
79
|
+
add('layout', 'items-start', 'items-end', 'items-center', 'items-baseline', 'items-stretch');
|
|
80
|
+
add('layout', 'content-start', 'content-end', 'content-center', 'content-between', 'content-around', 'content-evenly');
|
|
81
|
+
add('layout', 'self-auto', 'self-start', 'self-end', 'self-center', 'self-stretch', 'self-baseline');
|
|
82
|
+
add('layout', 'justify-start', 'justify-end', 'justify-center', 'justify-between', 'justify-around', 'justify-evenly');
|
|
83
|
+
for (let i = 1; i <= 12; i++) add('layout', `order-${i}`);
|
|
84
|
+
add('layout', 'order-first', 'order-last', 'order-none');
|
|
85
|
+
|
|
86
|
+
// --- Positioning: position, inset/top/right/bottom/left, z-index ---
|
|
87
|
+
add('positioning', 'absolute', 'relative', 'static');
|
|
88
|
+
addScaled('positioning', ['inset', 'inset-x', 'inset-y', 'top', 'right', 'bottom', 'left'], SPACING_SCALE);
|
|
89
|
+
add('positioning', 'z-0', 'z-10', 'z-20', 'z-30', 'z-40', 'z-50', 'z-auto');
|
|
90
|
+
|
|
91
|
+
// --- Typography: size/weight/style/align/decoration/transform/spacing ---
|
|
92
|
+
add('typography', 'text-xs', 'text-sm', 'text-base', 'text-lg', 'text-xl', 'text-2xl', 'text-3xl', 'text-4xl', 'text-5xl', 'text-6xl');
|
|
93
|
+
add('typography', 'font-thin', 'font-extralight', 'font-light', 'font-normal', 'font-medium', 'font-semibold', 'font-bold', 'font-extrabold', 'font-black');
|
|
94
|
+
add('typography', 'italic', 'not-italic');
|
|
95
|
+
add('typography', 'text-left', 'text-center', 'text-right', 'text-justify');
|
|
96
|
+
add('typography', 'underline', 'line-through', 'no-underline');
|
|
97
|
+
add('typography', 'uppercase', 'lowercase', 'capitalize', 'normal-case');
|
|
98
|
+
add('typography', 'tracking-tighter', 'tracking-tight', 'tracking-normal', 'tracking-wide', 'tracking-wider', 'tracking-widest');
|
|
99
|
+
add('typography', 'leading-none', 'leading-tight', 'leading-snug', 'leading-normal', 'leading-relaxed', 'leading-loose');
|
|
100
|
+
addScaled('typography', ['leading'], ['3', '4', '5', '6', '7', '8', '9', '10']);
|
|
101
|
+
|
|
102
|
+
// --- Colors: background/text/border color scales (distinct strings from the
|
|
103
|
+
// typography/border families above — e.g. `text-red-500` vs `text-lg`) ---
|
|
104
|
+
for (const prefix of ['bg', 'text', 'border']) {
|
|
105
|
+
for (const color of COLOR_NAMES) {
|
|
106
|
+
for (const shade of COLOR_SHADES) add('colors', `${prefix}-${color}-${shade}`);
|
|
107
|
+
}
|
|
108
|
+
add('colors', `${prefix}-black`, `${prefix}-white`, `${prefix}-transparent`, `${prefix}-current`);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// --- Borders: width/side/style/radius (border *color* lives in "colors") ---
|
|
112
|
+
add('borders', 'border', 'border-0', 'border-2', 'border-4', 'border-8');
|
|
113
|
+
for (const side of ['t', 'r', 'b', 'l']) {
|
|
114
|
+
add('borders', `border-${side}`, `border-${side}-0`, `border-${side}-2`, `border-${side}-4`, `border-${side}-8`);
|
|
115
|
+
}
|
|
116
|
+
add('borders', 'border-solid', 'border-dashed', 'border-dotted');
|
|
117
|
+
for (const size of ROUNDED_SIZES) add('borders', size ? `rounded-${size}` : 'rounded');
|
|
118
|
+
for (const corner of ROUNDED_CORNERS) {
|
|
119
|
+
for (const size of ROUNDED_SIZES) add('borders', size ? `rounded-${corner}-${size}` : `rounded-${corner}`);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// --- Effects: opacity, shadow ---
|
|
123
|
+
for (const value of OPACITY_SCALE) add('effects', `opacity-${value}`);
|
|
124
|
+
add('effects', 'shadow', 'shadow-sm', 'shadow-md', 'shadow-lg', 'shadow-xl', 'shadow-2xl', 'shadow-none');
|
|
125
|
+
|
|
126
|
+
/** @type {ReadonlyMap<string, string>} className -> category */
|
|
127
|
+
const TAILWIND_CLASSES = CLASSES;
|
|
128
|
+
|
|
129
|
+
/** Sorted array of all known class names, for completion filtering. */
|
|
130
|
+
const TAILWIND_CLASS_NAMES = Array.from(CLASSES.keys()).sort();
|
|
131
|
+
|
|
132
|
+
/** All category names, in the order documented by TODO.md item 6. */
|
|
133
|
+
const TAILWIND_CATEGORIES = ['layout', 'spacing', 'sizing', 'typography', 'colors', 'borders', 'effects', 'positioning'];
|
|
134
|
+
|
|
135
|
+
module.exports = { TAILWIND_CLASSES, TAILWIND_CLASS_NAMES, TAILWIND_CATEGORIES };
|
package/decoration.js
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
const { syntaxTree } = require('@codemirror/language');
|
|
2
|
+
const { Decoration, ViewPlugin, EditorView } = require('@codemirror/view');
|
|
3
|
+
const { RangeSetBuilder, Prec } = require('@codemirror/state');
|
|
4
|
+
const { getTaggedTemplateTagPath, templateContentRanges, docAsInput } = require('@actualwave/codemirror-lang-embed-core');
|
|
5
|
+
const { TAILWIND_CLASSES, TAILWIND_CATEGORIES } = require('./data.js');
|
|
6
|
+
|
|
7
|
+
const TOKEN_RE = /[^\s`]+/g;
|
|
8
|
+
|
|
9
|
+
const decorationByCategory = new Map(
|
|
10
|
+
TAILWIND_CATEGORIES.map((category) => [category, Decoration.mark({ class: `cm-tw-${category}` })]),
|
|
11
|
+
);
|
|
12
|
+
|
|
13
|
+
// Collects every `tw`-tagged TemplateString's content ranges that intersect
|
|
14
|
+
// [from, to) — same tag-matching primitive embed-core uses for parseMixed and
|
|
15
|
+
// completion.js uses for the CompletionSource, just driving decoration this
|
|
16
|
+
// time (see TODO.md item 6).
|
|
17
|
+
function twContentRangesIn(state, from, to) {
|
|
18
|
+
const ranges = [];
|
|
19
|
+
syntaxTree(state).iterate({
|
|
20
|
+
from,
|
|
21
|
+
to,
|
|
22
|
+
enter(node) {
|
|
23
|
+
if (node.name !== 'TemplateString') return;
|
|
24
|
+
const tagPath = getTaggedTemplateTagPath(node.node, docAsInput(state.doc));
|
|
25
|
+
if (!tagPath || tagPath.length !== 1 || tagPath[0] !== 'tw') return;
|
|
26
|
+
for (const range of templateContentRanges(node.node)) ranges.push(range);
|
|
27
|
+
},
|
|
28
|
+
});
|
|
29
|
+
return ranges;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function buildDecorations(view) {
|
|
33
|
+
const builder = new RangeSetBuilder();
|
|
34
|
+
const seen = [];
|
|
35
|
+
for (const { from, to } of view.visibleRanges) {
|
|
36
|
+
for (const range of twContentRangesIn(view.state, from, to)) {
|
|
37
|
+
seen.push(range);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
// Visible ranges can overlap at chunk boundaries — dedupe by range identity
|
|
41
|
+
// (same node can't produce the same [from,to) twice from disjoint queries,
|
|
42
|
+
// but a template spanning multiple visible chunks could; sort+merge is
|
|
43
|
+
// unnecessary for correctness here since Decoration ranges just need
|
|
44
|
+
// ascending, non-overlapping input, and token matches within one template
|
|
45
|
+
// range never repeat).
|
|
46
|
+
seen.sort((a, b) => a.from - b.from || a.to - b.to);
|
|
47
|
+
|
|
48
|
+
const text = view.state.doc;
|
|
49
|
+
for (const { from, to } of seen) {
|
|
50
|
+
const chunk = text.sliceString(from, to);
|
|
51
|
+
TOKEN_RE.lastIndex = 0;
|
|
52
|
+
let match;
|
|
53
|
+
while ((match = TOKEN_RE.exec(chunk))) {
|
|
54
|
+
const token = match[0];
|
|
55
|
+
const category = TAILWIND_CLASSES.get(token);
|
|
56
|
+
if (!category) continue; // unknown/arbitrary-value token — leave undecorated (v1)
|
|
57
|
+
const start = from + match.index;
|
|
58
|
+
builder.add(start, start + token.length, decorationByCategory.get(category));
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return builder.finish();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Mark decorations nest by facet precedence — the higher-precedence one
|
|
65
|
+
// becomes the *inner* DOM element (see @codemirror/view's Decoration.mark
|
|
66
|
+
// doc comment). @codemirror/language's syntaxHighlighting() wraps its
|
|
67
|
+
// treeHighlighter in Prec.high(...), so it paints `.tok-string` as the
|
|
68
|
+
// innermost span around the whole `tw`-tagged template content. Without
|
|
69
|
+
// out-precedencing that, our per-category `.cm-tw-*` span ends up as the
|
|
70
|
+
// *outer* element and `.tok-string`'s own color always wins (an element's
|
|
71
|
+
// own matching rule beats an ancestor's color regardless of specificity).
|
|
72
|
+
// Prec.highest here makes ours the inner span instead, so it wins.
|
|
73
|
+
const tailwindHighlightPlugin = Prec.highest(
|
|
74
|
+
ViewPlugin.fromClass(
|
|
75
|
+
class {
|
|
76
|
+
constructor(view) {
|
|
77
|
+
this.decorations = buildDecorations(view);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
update(update) {
|
|
81
|
+
if (update.docChanged || update.viewportChanged) {
|
|
82
|
+
this.decorations = buildDecorations(update.view);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
},
|
|
86
|
+
{ decorations: (v) => v.decorations },
|
|
87
|
+
),
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
// Default category colors. Downstream (react-native-codeditor's editor.html)
|
|
91
|
+
// overrides these per active editor theme, the same way _themePackageMap
|
|
92
|
+
// remaps @uiw theme exports — see TODO.md item 6's "Category colors need to
|
|
93
|
+
// be defined per editor theme... not hardcoded" note. These are just the
|
|
94
|
+
// standalone/demo defaults so the plugin isn't inert without that wiring.
|
|
95
|
+
const tailwindHighlightTheme = EditorView.baseTheme({
|
|
96
|
+
'.cm-tw-layout': { color: '#569cd6' },
|
|
97
|
+
'.cm-tw-spacing': { color: '#9cdcfe' },
|
|
98
|
+
'.cm-tw-sizing': { color: '#4ec9b0' },
|
|
99
|
+
'.cm-tw-typography': { color: '#c586c0' },
|
|
100
|
+
'.cm-tw-colors': { color: '#ce9178' },
|
|
101
|
+
'.cm-tw-borders': { color: '#d7ba7d' },
|
|
102
|
+
'.cm-tw-effects': { color: '#b5cea8' },
|
|
103
|
+
'.cm-tw-positioning': { color: '#dcdcaa' },
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
module.exports = { tailwindHighlightPlugin, tailwindHighlightTheme };
|
package/index.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
const { tailwindCompletionSource } = require('./completion.js');
|
|
2
|
+
const { tailwindHighlightPlugin, tailwindHighlightTheme } = require('./decoration.js');
|
|
3
|
+
|
|
4
|
+
// Unlike the other embed-* packages (embed-sql/embed-graphql/embed-css/
|
|
5
|
+
// embed-sksl), Tailwind has no grammar to parse — `` tw`...` `` content is a
|
|
6
|
+
// flat space-separated list of utility class names (see TODO.md item 6: "no
|
|
7
|
+
// language structure inside `tw`...` ` to parse... this isn't a parseMixed
|
|
8
|
+
// grammar problem like the other DSLs"). So instead of `createEmbedding()`
|
|
9
|
+
// (matcher + nested Language for the tag registry), this package exports
|
|
10
|
+
// `createSupportExtension(jsLanguageSupport)`, returning plain CM extensions
|
|
11
|
+
// (a completion source + a decoration ViewPlugin) to merge into the JS/TSX
|
|
12
|
+
// language's `support` array.
|
|
13
|
+
function createSupportExtension(jsLanguageSupport) {
|
|
14
|
+
return [
|
|
15
|
+
jsLanguageSupport.language.data.of({ autocomplete: tailwindCompletionSource }),
|
|
16
|
+
tailwindHighlightPlugin,
|
|
17
|
+
tailwindHighlightTheme,
|
|
18
|
+
];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
module.exports = { createSupportExtension };
|
package/package.json
CHANGED
|
@@ -1,6 +1,25 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@actualwave/codemirror-lang-embed-tailwind",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Tailwind (twrnc) class-name completion and category-colored highlighting inside `tw` tagged templates in JS/TSX",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "https://github.com/burdiuz/codemorror-lang-modules.git",
|
|
10
|
+
"directory": "packages/embed-tailwind"
|
|
11
|
+
},
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/burdiuz/codemorror-lang-modules/issues"
|
|
14
|
+
},
|
|
15
|
+
"homepage": "https://github.com/burdiuz/codemorror-lang-modules/tree/main/packages/embed-tailwind",
|
|
16
|
+
"dependencies": {
|
|
17
|
+
"@actualwave/codemirror-lang-embed-core": "*"
|
|
18
|
+
},
|
|
19
|
+
"peerDependencies": {
|
|
20
|
+
"@codemirror/autocomplete": "^6.0.0",
|
|
21
|
+
"@codemirror/language": "^6.0.0",
|
|
22
|
+
"@codemirror/state": "^6.0.0",
|
|
23
|
+
"@codemirror/view": "^6.0.0"
|
|
24
|
+
}
|
|
25
|
+
}
|
package/README.md
DELETED