@bhsd/codemirror-css-color-picker 7.2.0 → 8.0.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/dist/color.d.ts +4 -6
- package/dist/color.js +84 -0
- package/dist/css.js +45 -0
- package/dist/index.d.ts +2 -5
- package/dist/index.js +173 -400
- package/dist/types.js +1 -0
- package/package.json +5 -11
package/dist/color.d.ts
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
import '
|
|
2
|
-
import type { WidgetOptions, RGB, ColorData } from './types';
|
|
1
|
+
import type { WidgetOptions, ColorData } from './types';
|
|
3
2
|
/**
|
|
4
3
|
* Parses a CSS color function call expression (including `rgb()`, `rgba()`, `hsl()`, `hsla()`)
|
|
5
4
|
* @param callExp the full text of the call expression, including function name and parentheses
|
|
6
5
|
*/
|
|
7
|
-
export declare const parseCallExpression: (callExp: string) => ColorData | false
|
|
6
|
+
export declare const parseCallExpression: (callExp: string) => ColorData | false;
|
|
8
7
|
/**
|
|
9
8
|
* Parses a hex color literal (e.g. `#ff0000`, `#f00`, `#ff000080`, `#f008`)
|
|
10
9
|
* @param colorLiteral the hex color literal text
|
|
@@ -13,9 +12,8 @@ export declare const parseColorLiteral: (colorLiteral: string) => ColorData | fa
|
|
|
13
12
|
/**
|
|
14
13
|
* Parses a named color (e.g. `red`, `blue`, `rebeccapurple`)
|
|
15
14
|
* @param colorName the named color text
|
|
16
|
-
* @param colors an object mapping color names to RGB values
|
|
17
15
|
*/
|
|
18
|
-
export declare const parseNamedColor: (colorName: string
|
|
16
|
+
export declare const parseNamedColor: (colorName: string) => ColorData | false;
|
|
19
17
|
export declare const getDelimiter: (legacy: boolean, spaced: boolean) => string;
|
|
20
18
|
export declare const alphaToString: (alpha: number, legacy: boolean, spaced: boolean) => string;
|
|
21
|
-
export declare const colorToString: ({ color, alpha, colorType, legacy, spaced }: WidgetOptions, value: string
|
|
19
|
+
export declare const colorToString: ({ color, alpha, colorType, legacy, spaced }: WidgetOptions, value: string) => string | false;
|
package/dist/color.js
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { intToHex, numToHex, rgba, hsla, colorsNamed } from '@bhsd/common';
|
|
2
|
+
const parse = (color) => {
|
|
3
|
+
const [r, g, b, alpha] = rgba(color);
|
|
4
|
+
return alpha !== undefined && {
|
|
5
|
+
color: [r, g, b],
|
|
6
|
+
alpha,
|
|
7
|
+
};
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* Parses a CSS color function call expression (including `rgb()`, `rgba()`, `hsl()`, `hsla()`)
|
|
11
|
+
* @param callExp the full text of the call expression, including function name and parentheses
|
|
12
|
+
*/
|
|
13
|
+
export const parseCallExpression = (callExp) => {
|
|
14
|
+
const color = parse(callExp);
|
|
15
|
+
return color && {
|
|
16
|
+
...color,
|
|
17
|
+
colorType: callExp.split('(', 1)[0].toLowerCase(),
|
|
18
|
+
legacy: callExp.includes(','),
|
|
19
|
+
spaced: /\s/u.test(callExp),
|
|
20
|
+
};
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* Parses a hex color literal (e.g. `#ff0000`, `#f00`, `#ff000080`, `#f008`)
|
|
24
|
+
* @param colorLiteral the hex color literal text
|
|
25
|
+
*/
|
|
26
|
+
export const parseColorLiteral = (colorLiteral) => {
|
|
27
|
+
const color = parse(colorLiteral), { length } = colorLiteral;
|
|
28
|
+
return color && {
|
|
29
|
+
...color,
|
|
30
|
+
colorType: 'hex',
|
|
31
|
+
legacy: length === 4 || length === 7,
|
|
32
|
+
spaced: false,
|
|
33
|
+
};
|
|
34
|
+
};
|
|
35
|
+
/**
|
|
36
|
+
* Parses a named color (e.g. `red`, `blue`, `rebeccapurple`)
|
|
37
|
+
* @param colorName the named color text
|
|
38
|
+
*/
|
|
39
|
+
export const parseNamedColor = (colorName) => {
|
|
40
|
+
const color = parse(colorName);
|
|
41
|
+
return color && {
|
|
42
|
+
...color,
|
|
43
|
+
colorType: 'named',
|
|
44
|
+
legacy: true,
|
|
45
|
+
spaced: false,
|
|
46
|
+
};
|
|
47
|
+
};
|
|
48
|
+
export const getDelimiter = (legacy, spaced) => legacy ? `,${spaced ? ' ' : ''}` : ' ';
|
|
49
|
+
export const alphaToString = (alpha, legacy, spaced) => alpha === 1
|
|
50
|
+
? ''
|
|
51
|
+
: (legacy ? `,${spaced ? ' ' : ''}` : ' / ')
|
|
52
|
+
+ String(alpha === 0 ? alpha : Number(alpha.toFixed(2)));
|
|
53
|
+
const hexToName = new Map(Object.entries(colorsNamed).map(([key, val]) => [`#${intToHex(val, 6)}`, key]));
|
|
54
|
+
export const colorToString = ({ color, alpha, colorType, legacy, spaced }, value) => {
|
|
55
|
+
const currentColor = rgba(value).slice(0, 3);
|
|
56
|
+
if (currentColor.every((c, i) => c === Math.round(color[i]))) {
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
const delimiter = getDelimiter(legacy, spaced);
|
|
60
|
+
switch (colorType) {
|
|
61
|
+
case 'rgba':
|
|
62
|
+
case 'rgb':
|
|
63
|
+
return `${colorType}(${currentColor.join(delimiter)}${alphaToString(alpha, legacy, spaced)})`;
|
|
64
|
+
case 'hsla':
|
|
65
|
+
case 'hsl': {
|
|
66
|
+
const [h, s, l] = hsla(value);
|
|
67
|
+
return `${colorType}(${[h, `${s}%`, `${l}%`].join(delimiter)}${alphaToString(alpha, legacy, spaced)})`;
|
|
68
|
+
}
|
|
69
|
+
case 'named':
|
|
70
|
+
if (alpha === 1) {
|
|
71
|
+
// If the color is an exact match for another named color, prefer retaining name
|
|
72
|
+
const colorName = hexToName.get(value);
|
|
73
|
+
if (colorName) {
|
|
74
|
+
return colorName;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
// fall through
|
|
78
|
+
case 'hex':
|
|
79
|
+
// hex color literal
|
|
80
|
+
return value + (alpha === 1 ? '' : numToHex(alpha));
|
|
81
|
+
default:
|
|
82
|
+
throw new Error('Unknown color type');
|
|
83
|
+
}
|
|
84
|
+
};
|
package/dist/css.js
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { NodeProp } from '@lezer/common';
|
|
2
|
+
/**
|
|
3
|
+
* Discovers colors in CSS code
|
|
4
|
+
* @implements
|
|
5
|
+
*/
|
|
6
|
+
export const discoverColorsInCSS = (tree, { from, to, name: typeName }, doc) => {
|
|
7
|
+
switch (typeName) {
|
|
8
|
+
case 'UnquotedAttributeValue':
|
|
9
|
+
case 'AttributeValue': {
|
|
10
|
+
// CSS nested in an HTML attribute value
|
|
11
|
+
const overlayTree = tree.resolveInner(from, 0).tree?.prop(NodeProp.mounted)?.tree;
|
|
12
|
+
if (overlayTree?.type.name !== 'Styles') {
|
|
13
|
+
// Skip if the attribute value is not a style attribute, or if there is no mounted tree
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
const ret = [],
|
|
17
|
+
// Account for the quotation mark in AttributeValue
|
|
18
|
+
offset = from + (typeName === 'AttributeValue' ? 1 : 0);
|
|
19
|
+
overlayTree.iterate({
|
|
20
|
+
from: 0,
|
|
21
|
+
to: overlayTree.length,
|
|
22
|
+
enter({ name, from: overlayFrom, to: overlayTo }) {
|
|
23
|
+
const widgetOptions = discoverColorsInCSS(tree, {
|
|
24
|
+
from: offset + overlayFrom,
|
|
25
|
+
to: offset + overlayTo,
|
|
26
|
+
name,
|
|
27
|
+
}, doc);
|
|
28
|
+
if (widgetOptions) {
|
|
29
|
+
if (Array.isArray(widgetOptions)) {
|
|
30
|
+
throw new Error('Unexpected nested overlays');
|
|
31
|
+
}
|
|
32
|
+
ret.push(widgetOptions);
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
});
|
|
36
|
+
return ret;
|
|
37
|
+
}
|
|
38
|
+
case 'CallExpression':
|
|
39
|
+
case 'ColorLiteral':
|
|
40
|
+
case 'ValueName':
|
|
41
|
+
return { from, to };
|
|
42
|
+
default:
|
|
43
|
+
return undefined;
|
|
44
|
+
}
|
|
45
|
+
};
|
package/dist/index.d.ts
CHANGED
|
@@ -1,14 +1,11 @@
|
|
|
1
|
-
import namedColors from 'color-name';
|
|
2
1
|
import type { Extension } from '@codemirror/state';
|
|
3
|
-
import type { DiscoverColors, WidgetOptions
|
|
4
|
-
export { namedColors };
|
|
2
|
+
import type { DiscoverColors, WidgetOptions } from './types';
|
|
5
3
|
export type { DiscoverColors, WidgetOptions };
|
|
6
4
|
export declare const wrapperClassName = "cm-css-color-picker-wrapper";
|
|
7
5
|
/**
|
|
8
6
|
* Factory function to create a color picker plugin with the given options
|
|
9
7
|
* @param discoverColors the function to discover colors in a syntax node; return `false` to skip children
|
|
10
|
-
* @param colors an optional object of color names mapping to RGB values
|
|
11
8
|
*/
|
|
12
|
-
export declare const makeColorPicker: (discoverColors: DiscoverColors
|
|
9
|
+
export declare const makeColorPicker: (discoverColors: DiscoverColors) => Extension;
|
|
13
10
|
/** Default color picker plugin for CSS and HTML */
|
|
14
11
|
export declare const colorPicker: Extension;
|
package/dist/index.js
CHANGED
|
@@ -1,410 +1,183 @@
|
|
|
1
|
-
import { EditorView, WidgetType, ViewPlugin, Decoration } from
|
|
2
|
-
import { syntaxTree } from
|
|
3
|
-
import
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
}
|
|
1
|
+
import { EditorView, WidgetType, ViewPlugin, Decoration } from '@codemirror/view';
|
|
2
|
+
import { syntaxTree } from '@codemirror/language';
|
|
3
|
+
import { intToHex } from '@bhsd/common';
|
|
4
|
+
import { discoverColorsInCSS } from './css.js';
|
|
5
|
+
import { colorToString, parseCallExpression, parseColorLiteral, parseNamedColor } from './color.js';
|
|
6
|
+
export const wrapperClassName = 'cm-css-color-picker-wrapper';
|
|
7
|
+
const pickerState = new WeakMap();
|
|
8
|
+
class ColorPickerWidget extends WidgetType {
|
|
9
|
+
/** @class */
|
|
10
|
+
constructor(state, readOnly) {
|
|
11
|
+
super();
|
|
12
|
+
this.state = state;
|
|
13
|
+
this.readonly = readOnly;
|
|
14
|
+
}
|
|
15
|
+
/** @override */
|
|
16
|
+
eq(other) {
|
|
17
|
+
return other.readonly === this.readonly
|
|
18
|
+
&& other.state.from === this.state.from
|
|
19
|
+
&& other.state.to === this.state.to
|
|
20
|
+
&& other.state.colorType === this.state.colorType
|
|
21
|
+
&& other.state.color === this.state.color
|
|
22
|
+
&& other.state.alpha === this.state.alpha
|
|
23
|
+
&& other.state.legacy === this.state.legacy
|
|
24
|
+
&& other.state.spaced === this.state.spaced;
|
|
25
|
+
}
|
|
26
|
+
/** @override */
|
|
27
|
+
toDOM() {
|
|
28
|
+
const picker = document.createElement('input');
|
|
29
|
+
picker.type = 'color';
|
|
30
|
+
picker.value = `#${this.state.color.map(c => intToHex(c)).join('')}`;
|
|
31
|
+
picker.style.opacity = String(this.state.alpha);
|
|
32
|
+
if (this.readonly || this.state.colorType === 'unknown') {
|
|
33
|
+
picker.disabled = true;
|
|
35
34
|
}
|
|
36
|
-
|
|
37
|
-
|
|
35
|
+
pickerState.set(picker, this.state);
|
|
36
|
+
const wrapper = document.createElement('span');
|
|
37
|
+
wrapper.className = wrapperClassName;
|
|
38
|
+
wrapper.append(picker);
|
|
39
|
+
return wrapper;
|
|
38
40
|
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
return { from, to };
|
|
43
|
-
default:
|
|
44
|
-
return void 0;
|
|
45
|
-
}
|
|
46
|
-
};
|
|
47
|
-
|
|
48
|
-
import rgb2 from "color-space/rgb.js";
|
|
49
|
-
import "color-space/hsl.js";
|
|
50
|
-
|
|
51
|
-
var names = {};
|
|
52
|
-
var color_parse_default = parse;
|
|
53
|
-
var baseHues = {
|
|
54
|
-
red: 0,
|
|
55
|
-
orange: 60,
|
|
56
|
-
yellow: 120,
|
|
57
|
-
green: 180,
|
|
58
|
-
blue: 240,
|
|
59
|
-
purple: 300
|
|
60
|
-
};
|
|
61
|
-
function parse(cstr) {
|
|
62
|
-
var m, parts = [], alpha = 1, space;
|
|
63
|
-
if (typeof cstr === "number") {
|
|
64
|
-
return { space: "rgb", values: [cstr >>> 16, (cstr & 65280) >>> 8, cstr & 255], alpha: 1 };
|
|
65
|
-
}
|
|
66
|
-
if (typeof cstr === "number") return { space: "rgb", values: [cstr >>> 16, (cstr & 65280) >>> 8, cstr & 255], alpha: 1 };
|
|
67
|
-
cstr = String(cstr).toLowerCase();
|
|
68
|
-
if (names[cstr]) {
|
|
69
|
-
parts = names[cstr].slice();
|
|
70
|
-
space = "rgb";
|
|
71
|
-
} else if (cstr === "transparent") {
|
|
72
|
-
alpha = 0;
|
|
73
|
-
space = "rgb";
|
|
74
|
-
parts = [0, 0, 0];
|
|
75
|
-
} else if (cstr[0] === "#") {
|
|
76
|
-
var base = cstr.slice(1);
|
|
77
|
-
var size = base.length;
|
|
78
|
-
var isShort = size <= 4;
|
|
79
|
-
alpha = 1;
|
|
80
|
-
if (isShort) {
|
|
81
|
-
parts = [
|
|
82
|
-
parseInt(base[0] + base[0], 16),
|
|
83
|
-
parseInt(base[1] + base[1], 16),
|
|
84
|
-
parseInt(base[2] + base[2], 16)
|
|
85
|
-
];
|
|
86
|
-
if (size === 4) {
|
|
87
|
-
alpha = parseInt(base[3] + base[3], 16) / 255;
|
|
88
|
-
}
|
|
89
|
-
} else {
|
|
90
|
-
parts = [
|
|
91
|
-
parseInt(base[0] + base[1], 16),
|
|
92
|
-
parseInt(base[2] + base[3], 16),
|
|
93
|
-
parseInt(base[4] + base[5], 16)
|
|
94
|
-
];
|
|
95
|
-
if (size === 8) {
|
|
96
|
-
alpha = parseInt(base[6] + base[7], 16) / 255;
|
|
97
|
-
}
|
|
41
|
+
/** @override */
|
|
42
|
+
ignoreEvent(e) {
|
|
43
|
+
return e.type !== 'change';
|
|
98
44
|
}
|
|
99
|
-
if (!parts[0]) parts[0] = 0;
|
|
100
|
-
if (!parts[1]) parts[1] = 0;
|
|
101
|
-
if (!parts[2]) parts[2] = 0;
|
|
102
|
-
space = "rgb";
|
|
103
|
-
} else if (m = /^((?:rgba?|hs[lvb]a?|hwba?|cmyk?|xy[zy]|gray|lab|lchu?v?|[ly]uv|lms|oklch|oklab|color))\s*\(([^\)]*)\)/.exec(cstr)) {
|
|
104
|
-
var name = m[1];
|
|
105
|
-
space = name.replace(/a$/, "");
|
|
106
|
-
var dims = space === "cmyk" ? 4 : space === "gray" ? 1 : 3;
|
|
107
|
-
parts = m[2].trim().split(/\s*[,\/]\s*|\s+/);
|
|
108
|
-
if (space === "color") space = parts.shift();
|
|
109
|
-
parts = parts.map(function(x, i) {
|
|
110
|
-
if (x[x.length - 1] === "%") {
|
|
111
|
-
x = parseFloat(x) / 100;
|
|
112
|
-
if (i === 3) return x;
|
|
113
|
-
if (space === "rgb") return x * 255;
|
|
114
|
-
if (space[0] === "h") return x * 100;
|
|
115
|
-
if (space[0] === "l" && !i) return x * 100;
|
|
116
|
-
if (space === "lab") return x * 125;
|
|
117
|
-
if (space === "lch") return i < 2 ? x * 150 : x * 360;
|
|
118
|
-
if (space[0] === "o" && !i) return x;
|
|
119
|
-
if (space === "oklab") return x * 0.4;
|
|
120
|
-
if (space === "oklch") return i < 2 ? x * 0.4 : x * 360;
|
|
121
|
-
return x;
|
|
122
|
-
}
|
|
123
|
-
if (space[i] === "h" || i === 2 && space[space.length - 1] === "h") {
|
|
124
|
-
if (baseHues[x] !== void 0) return baseHues[x];
|
|
125
|
-
if (x.endsWith("deg")) return parseFloat(x);
|
|
126
|
-
if (x.endsWith("turn")) return parseFloat(x) * 360;
|
|
127
|
-
if (x.endsWith("grad")) return parseFloat(x) * 360 / 400;
|
|
128
|
-
if (x.endsWith("rad")) return parseFloat(x) * 180 / Math.PI;
|
|
129
|
-
}
|
|
130
|
-
if (x === "none") return 0;
|
|
131
|
-
return parseFloat(x);
|
|
132
|
-
});
|
|
133
|
-
alpha = parts.length > dims ? parts.pop() : 1;
|
|
134
|
-
} else if (/[0-9](?:\s|\/|,)/.test(cstr)) {
|
|
135
|
-
parts = cstr.match(/([0-9]+)/g).map(function(value) {
|
|
136
|
-
return parseFloat(value);
|
|
137
|
-
});
|
|
138
|
-
space = cstr.match(/([a-z])/ig)?.join("")?.toLowerCase() || "rgb";
|
|
139
|
-
}
|
|
140
|
-
return {
|
|
141
|
-
space,
|
|
142
|
-
values: parts,
|
|
143
|
-
alpha
|
|
144
|
-
};
|
|
145
45
|
}
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
function
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
return void 0;
|
|
189
|
-
}
|
|
190
|
-
const [r, g, b, alpha] = rgba(callExp);
|
|
191
|
-
return alpha !== void 0 && {
|
|
192
|
-
colorType: fn,
|
|
193
|
-
color: [r, g, b].map(Math.round),
|
|
194
|
-
alpha,
|
|
195
|
-
legacy: callExp.includes(","),
|
|
196
|
-
spaced: /\s/u.test(callExp)
|
|
197
|
-
};
|
|
198
|
-
};
|
|
199
|
-
var parseColorLiteral = (colorLiteral) => {
|
|
200
|
-
if (!hexRegex.test(colorLiteral)) {
|
|
201
|
-
return false;
|
|
202
|
-
}
|
|
203
|
-
const [r, g, b, alpha] = rgba(colorLiteral), { length } = colorLiteral;
|
|
204
|
-
return alpha !== void 0 && {
|
|
205
|
-
colorType: "hex",
|
|
206
|
-
color: [r, g, b],
|
|
207
|
-
alpha,
|
|
208
|
-
legacy: length === 4 || length === 7,
|
|
209
|
-
spaced: false
|
|
210
|
-
};
|
|
211
|
-
};
|
|
212
|
-
var parseNamedColor = (colorName, colors) => {
|
|
213
|
-
const lcName = colorName.toLowerCase();
|
|
214
|
-
if (!colors || !Object.hasOwn(colors, lcName)) {
|
|
215
|
-
return false;
|
|
216
|
-
}
|
|
217
|
-
const color = colors[lcName];
|
|
218
|
-
return {
|
|
219
|
-
colorType: "named",
|
|
220
|
-
color,
|
|
221
|
-
alpha: 1,
|
|
222
|
-
legacy: true,
|
|
223
|
-
spaced: false
|
|
224
|
-
};
|
|
225
|
-
};
|
|
226
|
-
var getDelimiter = (legacy, spaced) => legacy ? `,${spaced ? " " : ""}` : " ";
|
|
227
|
-
var alphaToString = (alpha, legacy, spaced) => alpha === 1 ? "" : (legacy ? `,${spaced ? " " : ""}` : " / ") + String(alpha === 0 ? alpha : Number(alpha.toFixed(2)));
|
|
228
|
-
var colorToString = ({ color, alpha, colorType, legacy, spaced }, value, colors) => {
|
|
229
|
-
const currentColor = rgba(value).slice(0, 3);
|
|
230
|
-
if (currentColor.every((c, i) => c === Math.round(color[i]))) {
|
|
231
|
-
return false;
|
|
232
|
-
}
|
|
233
|
-
const delimiter = getDelimiter(legacy, spaced);
|
|
234
|
-
switch (colorType) {
|
|
235
|
-
case "rgba":
|
|
236
|
-
case "rgb":
|
|
237
|
-
return `${colorType}(${currentColor.join(delimiter)}${alphaToString(alpha, legacy, spaced)})`;
|
|
238
|
-
case "hsla":
|
|
239
|
-
case "hsl": {
|
|
240
|
-
const [h, s, l] = rgb2.hsl(currentColor.slice(0, 3));
|
|
241
|
-
return `${colorType}(${[Math.round(h), `${Math.round(s)}%`, `${Math.round(l)}%`].join(delimiter)}${alphaToString(alpha, legacy, spaced)})`;
|
|
242
|
-
}
|
|
243
|
-
case "named":
|
|
244
|
-
if (colors && alpha === 1) {
|
|
245
|
-
const colorName = Object.entries(colors).find(([, colorValues]) => colorValues.every((c, i) => c === currentColor[i]))?.[0];
|
|
246
|
-
if (colorName) {
|
|
247
|
-
return colorName;
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
// fall through
|
|
251
|
-
case "hex":
|
|
252
|
-
return value + (alpha === 1 ? "" : numToHex(alpha));
|
|
253
|
-
default:
|
|
254
|
-
throw new Error("Unknown color type");
|
|
255
|
-
}
|
|
256
|
-
};
|
|
257
|
-
|
|
258
|
-
var wrapperClassName = "cm-css-color-picker-wrapper";
|
|
259
|
-
var pickerState = /* @__PURE__ */ new WeakMap();
|
|
260
|
-
var ColorPickerWidget = class extends WidgetType {
|
|
261
|
-
/** @class */
|
|
262
|
-
constructor(state, readOnly) {
|
|
263
|
-
super();
|
|
264
|
-
this.state = state;
|
|
265
|
-
this.readonly = readOnly;
|
|
266
|
-
}
|
|
267
|
-
/** @override */
|
|
268
|
-
eq(other) {
|
|
269
|
-
return other.readonly === this.readonly && other.state.from === this.state.from && other.state.to === this.state.to && other.state.colorType === this.state.colorType && other.state.color === this.state.color && other.state.alpha === this.state.alpha && other.state.legacy === this.state.legacy && other.state.spaced === this.state.spaced;
|
|
270
|
-
}
|
|
271
|
-
/** @override */
|
|
272
|
-
toDOM() {
|
|
273
|
-
const picker = document.createElement("input");
|
|
274
|
-
picker.type = "color";
|
|
275
|
-
picker.value = `#${this.state.color.map((c) => intToHex(c)).join("")}`;
|
|
276
|
-
if (this.readonly || this.state.colorType === "unknown") {
|
|
277
|
-
picker.disabled = true;
|
|
46
|
+
/**
|
|
47
|
+
* Compute color picker decorations
|
|
48
|
+
* @param view the editor view for which to compute decorations
|
|
49
|
+
* @param discoverColors the function to discover colors in a syntax node; return `false` to skip children
|
|
50
|
+
*/
|
|
51
|
+
const colorPickersDecorations = (view, discoverColors) => {
|
|
52
|
+
const widgets = [], { state, visibleRanges } = view, tree = syntaxTree(state);
|
|
53
|
+
for (const { from, to } of visibleRanges) {
|
|
54
|
+
tree.iterate({
|
|
55
|
+
from,
|
|
56
|
+
to,
|
|
57
|
+
enter(node) {
|
|
58
|
+
const widgetOptions = discoverColors(tree, node, state.doc);
|
|
59
|
+
if (!widgetOptions) {
|
|
60
|
+
return widgetOptions;
|
|
61
|
+
}
|
|
62
|
+
for (const wo of Array.isArray(widgetOptions) ? widgetOptions : [widgetOptions]) {
|
|
63
|
+
if (wo.colorType !== 'unknown') {
|
|
64
|
+
const value = state.sliceDoc(wo.from, wo.to);
|
|
65
|
+
let data;
|
|
66
|
+
if (value.includes('(')) {
|
|
67
|
+
data = parseCallExpression(value);
|
|
68
|
+
}
|
|
69
|
+
else if (value.startsWith('#')) {
|
|
70
|
+
data = parseColorLiteral(value);
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
data = parseNamedColor(value);
|
|
74
|
+
}
|
|
75
|
+
if (!data) {
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
Object.assign(wo, data);
|
|
79
|
+
}
|
|
80
|
+
widgets.push(Decoration.widget({
|
|
81
|
+
widget: new ColorPickerWidget(wo, state.readOnly),
|
|
82
|
+
side: 1,
|
|
83
|
+
}).range(wo.from));
|
|
84
|
+
}
|
|
85
|
+
return undefined;
|
|
86
|
+
},
|
|
87
|
+
});
|
|
278
88
|
}
|
|
279
|
-
|
|
280
|
-
const wrapper = document.createElement("span");
|
|
281
|
-
wrapper.className = wrapperClassName;
|
|
282
|
-
wrapper.append(picker);
|
|
283
|
-
return wrapper;
|
|
284
|
-
}
|
|
285
|
-
/** @override */
|
|
286
|
-
ignoreEvent(e) {
|
|
287
|
-
return e.type !== "change";
|
|
288
|
-
}
|
|
289
|
-
};
|
|
290
|
-
var colorPickersDecorations = (view, discoverColors, colors) => {
|
|
291
|
-
const widgets = [], { state, visibleRanges } = view, tree = syntaxTree(state);
|
|
292
|
-
for (const { from, to } of visibleRanges) {
|
|
293
|
-
tree.iterate({
|
|
294
|
-
from,
|
|
295
|
-
to,
|
|
296
|
-
enter(node) {
|
|
297
|
-
const widgetOptions = discoverColors(tree, node, state.doc);
|
|
298
|
-
if (!widgetOptions) {
|
|
299
|
-
return widgetOptions;
|
|
300
|
-
}
|
|
301
|
-
for (const wo of Array.isArray(widgetOptions) ? widgetOptions : [widgetOptions]) {
|
|
302
|
-
if (wo.colorType !== "unknown") {
|
|
303
|
-
const value = state.sliceDoc(wo.from, wo.to);
|
|
304
|
-
let data;
|
|
305
|
-
if (value.includes("(")) {
|
|
306
|
-
data = parseCallExpression(value);
|
|
307
|
-
} else if (value.startsWith("#")) {
|
|
308
|
-
data = parseColorLiteral(value);
|
|
309
|
-
} else {
|
|
310
|
-
data = parseNamedColor(value, colors);
|
|
311
|
-
}
|
|
312
|
-
if (!data) {
|
|
313
|
-
continue;
|
|
314
|
-
}
|
|
315
|
-
Object.assign(wo, data);
|
|
316
|
-
}
|
|
317
|
-
widgets.push(
|
|
318
|
-
Decoration.widget({
|
|
319
|
-
widget: new ColorPickerWidget(wo, state.readOnly),
|
|
320
|
-
side: 1
|
|
321
|
-
}).range(wo.from)
|
|
322
|
-
);
|
|
323
|
-
}
|
|
324
|
-
return void 0;
|
|
325
|
-
}
|
|
326
|
-
});
|
|
327
|
-
}
|
|
328
|
-
return Decoration.set(widgets);
|
|
89
|
+
return Decoration.set(widgets);
|
|
329
90
|
};
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
91
|
+
const getBgImage = (fg, bg) => `linear-gradient(45deg, ${fg} 25%, transparent 25%, transparent 75%, ${fg} 75%),
|
|
92
|
+
linear-gradient(45deg, ${fg} 25%, ${bg} 25%, ${bg} 75%, ${fg} 75%)`;
|
|
93
|
+
const colorPickerTheme = EditorView.baseTheme({
|
|
94
|
+
[`.${wrapperClassName}`]: {
|
|
95
|
+
display: 'inline-block',
|
|
96
|
+
marginLeft: '0.6ch',
|
|
97
|
+
marginRight: '0.6ch',
|
|
98
|
+
height: '1em',
|
|
99
|
+
width: '1em',
|
|
100
|
+
outline: '1px solid #ddd',
|
|
101
|
+
position: 'relative',
|
|
102
|
+
transform: 'translateY(0.1em)',
|
|
103
|
+
backgroundSize: '0.4em 0.4em',
|
|
104
|
+
backgroundPosition: '0 0, 0.2em 0.2em',
|
|
105
|
+
'&>input[type="color"]': {
|
|
106
|
+
height: '100%',
|
|
107
|
+
width: '100%',
|
|
108
|
+
padding: 0,
|
|
109
|
+
appearance: 'none',
|
|
110
|
+
border: 'none',
|
|
111
|
+
borderRadius: 0,
|
|
112
|
+
position: 'absolute',
|
|
113
|
+
top: 0,
|
|
114
|
+
left: 0,
|
|
115
|
+
'&:enabled': {
|
|
116
|
+
cursor: 'pointer',
|
|
117
|
+
},
|
|
118
|
+
'&::-webkit-color-swatch-wrapper': {
|
|
119
|
+
padding: 0,
|
|
120
|
+
},
|
|
121
|
+
'&::-webkit-color-swatch': {
|
|
122
|
+
border: 'none',
|
|
123
|
+
borderRadius: 0,
|
|
124
|
+
},
|
|
125
|
+
'&::-moz-color-swatch': {
|
|
126
|
+
border: 'none',
|
|
127
|
+
borderRadius: 0,
|
|
128
|
+
},
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
[`&light .${wrapperClassName}`]: {
|
|
132
|
+
backgroundImage: getBgImage('#aaa', '#fff'),
|
|
133
|
+
},
|
|
134
|
+
[`&dark .${wrapperClassName}`]: {
|
|
135
|
+
backgroundImage: getBgImage('#888', '#000'),
|
|
136
|
+
},
|
|
362
137
|
});
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
const { readOnly } = view.state;
|
|
374
|
-
if (docChanged || viewportChanged || readOnly !== this.readOnly) {
|
|
375
|
-
this.readOnly = readOnly;
|
|
376
|
-
this.decorations = colorPickersDecorations(view, discoverColors, colors);
|
|
138
|
+
/**
|
|
139
|
+
* Factory function to create a color picker plugin with the given options
|
|
140
|
+
* @param discoverColors the function to discover colors in a syntax node; return `false` to skip children
|
|
141
|
+
*/
|
|
142
|
+
export const makeColorPicker = (discoverColors) => [
|
|
143
|
+
ViewPlugin.fromClass(class ColorPickerViewPlugin {
|
|
144
|
+
/** @class */
|
|
145
|
+
constructor(view) {
|
|
146
|
+
this.readOnly = view.state.readOnly;
|
|
147
|
+
this.decorations = colorPickersDecorations(view, discoverColors);
|
|
377
148
|
}
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
change(e, view) {
|
|
386
|
-
const target = e.target;
|
|
387
|
-
if (target.nodeName !== "INPUT" || target.type !== "color" || !target.parentElement?.classList.contains(wrapperClassName)) {
|
|
388
|
-
return false;
|
|
389
|
-
}
|
|
390
|
-
const state = pickerState.get(target), insert = colorToString(state, target.value, colors);
|
|
391
|
-
if (insert) {
|
|
392
|
-
const { from, to } = state;
|
|
393
|
-
view.dispatch({
|
|
394
|
-
changes: { from, to, insert }
|
|
395
|
-
});
|
|
396
|
-
}
|
|
397
|
-
return true;
|
|
149
|
+
/** @implements */
|
|
150
|
+
update({ docChanged, viewportChanged, view }) {
|
|
151
|
+
const { readOnly } = view.state;
|
|
152
|
+
if (docChanged || viewportChanged || readOnly !== this.readOnly) {
|
|
153
|
+
this.readOnly = readOnly;
|
|
154
|
+
this.decorations = colorPickersDecorations(view, discoverColors);
|
|
155
|
+
}
|
|
398
156
|
}
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
157
|
+
}, {
|
|
158
|
+
decorations(v) {
|
|
159
|
+
return v.decorations;
|
|
160
|
+
},
|
|
161
|
+
eventHandlers: {
|
|
162
|
+
change(e, view) {
|
|
163
|
+
const target = e.target;
|
|
164
|
+
if (target.nodeName !== 'INPUT'
|
|
165
|
+
|| target.type !== 'color'
|
|
166
|
+
|| !target.parentElement?.classList.contains(wrapperClassName)) {
|
|
167
|
+
return false;
|
|
168
|
+
}
|
|
169
|
+
const state = pickerState.get(target), insert = colorToString(state, target.value);
|
|
170
|
+
if (insert) {
|
|
171
|
+
const { from, to } = state;
|
|
172
|
+
view.dispatch({
|
|
173
|
+
changes: { from, to, insert },
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
return true;
|
|
177
|
+
},
|
|
178
|
+
},
|
|
179
|
+
}),
|
|
180
|
+
colorPickerTheme,
|
|
403
181
|
];
|
|
404
|
-
|
|
405
|
-
export
|
|
406
|
-
colorPicker,
|
|
407
|
-
makeColorPicker,
|
|
408
|
-
namedColors,
|
|
409
|
-
wrapperClassName
|
|
410
|
-
};
|
|
182
|
+
/** Default color picker plugin for CSS and HTML */
|
|
183
|
+
export const colorPicker = /* #__PURE__ */ makeColorPicker(discoverColorsInCSS);
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bhsd/codemirror-css-color-picker",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "8.0.0",
|
|
4
4
|
"description": "Enables a color picker input next to CSS colors",
|
|
5
5
|
"homepage": "https://github.com/bhsd-harry/Codemirror-CSS-color-picker#readme",
|
|
6
6
|
"bugs": {
|
|
@@ -17,8 +17,7 @@
|
|
|
17
17
|
},
|
|
18
18
|
"type": "module",
|
|
19
19
|
"files": [
|
|
20
|
-
"/dist/
|
|
21
|
-
"/dist/*.d.ts"
|
|
20
|
+
"/dist/"
|
|
22
21
|
],
|
|
23
22
|
"main": "dist/index.js",
|
|
24
23
|
"types": "dist/index.d.ts",
|
|
@@ -29,7 +28,7 @@
|
|
|
29
28
|
"lint:ts": "tsc --noEmit && eslint --cache .",
|
|
30
29
|
"lint:md": "markdownlint-cli2 '**/*.md'",
|
|
31
30
|
"lint": "npm run lint:ts && npm run lint:md",
|
|
32
|
-
"build": "tsc &&
|
|
31
|
+
"build": "tsc && eslint --no-config-lookup -c eslint.dist.mjs dist/*.js",
|
|
33
32
|
"build:test": "tsc --project test/tsconfig.json && npm test",
|
|
34
33
|
"test": "mocha"
|
|
35
34
|
},
|
|
@@ -38,21 +37,16 @@
|
|
|
38
37
|
"@codemirror/view": "^6.0.0"
|
|
39
38
|
},
|
|
40
39
|
"dependencies": {
|
|
41
|
-
"@bhsd/common": "^
|
|
42
|
-
"color-name": "~2.0.2",
|
|
43
|
-
"color-space": "^2.3.2"
|
|
40
|
+
"@bhsd/common": "^3.0.0"
|
|
44
41
|
},
|
|
45
42
|
"devDependencies": {
|
|
46
|
-
"@bhsd/code-standard": "^2.
|
|
43
|
+
"@bhsd/code-standard": "^2.7.0",
|
|
47
44
|
"@codemirror/language": "^6.12.3",
|
|
48
45
|
"@codemirror/lang-css": "^6.3.1",
|
|
49
46
|
"@codemirror/lang-html": "^6.4.11",
|
|
50
47
|
"@codemirror/state": "^6.6.0",
|
|
51
48
|
"@codemirror/view": "^6.42.1",
|
|
52
|
-
"@types/color-name": "^2.0.0",
|
|
53
|
-
"@types/color-rgba": "^2.1.3",
|
|
54
49
|
"@types/mocha": "^10.0.10",
|
|
55
|
-
"color-rgba": "^3.0.0",
|
|
56
50
|
"esbuild": "^0.28.0",
|
|
57
51
|
"eslint": "^10.4.1",
|
|
58
52
|
"markdownlint-cli2": "^0.22.1",
|