inline-style-editor 1.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/.vscode/launch.json +14 -0
- package/README.md +45 -0
- package/dist/inline-style-editor.css +1 -0
- package/dist/inline-style-editor.js +8 -0
- package/dist/inline-style-editor.mjs +5626 -0
- package/docs/index.html +353 -0
- package/docs/inline-style-editor.css +1 -0
- package/docs/inline-style-editor.js +8 -0
- package/package.json +38 -0
- package/rollup.config.js +40 -0
- package/src/assets/index.scss +2 -0
- package/src/assets/style.scss +144 -0
- package/src/components/ColorPicker.svelte +52 -0
- package/src/components/StyleEditor.svelte +379 -0
- package/src/index.js +7 -0
- package/src/util/boxesContour.js +82 -0
- package/src/util/fonts.js +20 -0
- package/src/util/path.js +47 -0
- package/stats.html +4034 -0
- package/stats.json +1 -0
package/package.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "inline-style-editor",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Update CSS rules or add inline style to elements visualy",
|
|
5
|
+
"scripts": {
|
|
6
|
+
"build": "rollup --config; cp dist/inline-style-editor.js docs/inline-style-editor.js; cp dist/inline-style-editor.css docs/inline-style-editor.css",
|
|
7
|
+
"dev": "rollup --config --sourcemap --watch"
|
|
8
|
+
},
|
|
9
|
+
"author": "Quentin Pinçon",
|
|
10
|
+
"license": "ISC",
|
|
11
|
+
"sideEffects": false,
|
|
12
|
+
"svelte": "src/index.js",
|
|
13
|
+
"module": "dist/inline-style-editor.mjs",
|
|
14
|
+
"main": "dist/inline-style-editor.js",
|
|
15
|
+
"style": "dist/inline-style-editor.css",
|
|
16
|
+
"devDependencies": {
|
|
17
|
+
"@rollup/plugin-commonjs": "^22.0.2",
|
|
18
|
+
"@rollup/plugin-node-resolve": "^13.3.0",
|
|
19
|
+
"rollup-plugin-analyzer": "^4.0.0",
|
|
20
|
+
"rollup-plugin-filesize": "^9.1.2",
|
|
21
|
+
"rollup-plugin-import-css": "^3.0.3",
|
|
22
|
+
"rollup-plugin-sass": "^1.2.13",
|
|
23
|
+
"rollup-plugin-scss": "^3.0.0",
|
|
24
|
+
"rollup-plugin-svelte": "^7.1.0",
|
|
25
|
+
"rollup-plugin-terser": "^7.0.2",
|
|
26
|
+
"rollup-plugin-visualizer": "^5.8.0",
|
|
27
|
+
"sass": "^1.54.4"
|
|
28
|
+
},
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"d3-contour": "^4.0.0",
|
|
31
|
+
"d3-geo": "^3.0.1",
|
|
32
|
+
"lodash.debounce": "^4.0.8",
|
|
33
|
+
"lodash.pick": "^4.4.0",
|
|
34
|
+
"parse-svg-path": "^0.1.2",
|
|
35
|
+
"svelte": "^3.49.0",
|
|
36
|
+
"vanilla-picker": "^2.12.1"
|
|
37
|
+
}
|
|
38
|
+
}
|
package/rollup.config.js
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import svelte from 'rollup-plugin-svelte';
|
|
2
|
+
import resolve from '@rollup/plugin-node-resolve';
|
|
3
|
+
import filesize from "rollup-plugin-filesize";
|
|
4
|
+
import { terser } from 'rollup-plugin-terser';
|
|
5
|
+
import commonjs from '@rollup/plugin-commonjs';
|
|
6
|
+
import scss from 'rollup-plugin-scss';
|
|
7
|
+
import { visualizer } from "rollup-plugin-visualizer";
|
|
8
|
+
import pkg from "./package.json";
|
|
9
|
+
|
|
10
|
+
const name = pkg.name
|
|
11
|
+
.replace(/^\w/, m => m.toUpperCase())
|
|
12
|
+
.replace(/-\w/g, m => m[1].toUpperCase());
|
|
13
|
+
|
|
14
|
+
export default [
|
|
15
|
+
{
|
|
16
|
+
input: 'src/index.js',
|
|
17
|
+
output: [
|
|
18
|
+
{
|
|
19
|
+
file: pkg.module,
|
|
20
|
+
format: 'es',
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
file: pkg.main,
|
|
24
|
+
format: 'umd',
|
|
25
|
+
name: name,
|
|
26
|
+
plugins: [terser()],
|
|
27
|
+
}],
|
|
28
|
+
plugins: [
|
|
29
|
+
svelte(),
|
|
30
|
+
resolve(),
|
|
31
|
+
commonjs(),
|
|
32
|
+
scss({
|
|
33
|
+
output: pkg.style,
|
|
34
|
+
outputStyle: 'compressed'
|
|
35
|
+
}),
|
|
36
|
+
filesize(),
|
|
37
|
+
visualizer()
|
|
38
|
+
]
|
|
39
|
+
},
|
|
40
|
+
]
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
.layout_default.picker_wrapper {
|
|
2
|
+
background: none;
|
|
3
|
+
box-shadow: none;
|
|
4
|
+
width: 17em;
|
|
5
|
+
|
|
6
|
+
.picker_done {
|
|
7
|
+
display: none;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
.picker_slider,
|
|
11
|
+
.picker_selector {
|
|
12
|
+
padding: 0.5em;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
.picker_editor input {
|
|
16
|
+
font-size: 0.7rem;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.ise {
|
|
21
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
|
|
22
|
+
font-size: 14px;
|
|
23
|
+
z-index: 9999999;
|
|
24
|
+
position: absolute;
|
|
25
|
+
background-color: #edf2f7;
|
|
26
|
+
max-width: 300px;
|
|
27
|
+
border-radius: 5px;
|
|
28
|
+
box-shadow: 0px 8px 17px 2px #0000007c;
|
|
29
|
+
select {
|
|
30
|
+
appearance: none;
|
|
31
|
+
-webkit-appearance: none;
|
|
32
|
+
-moz-appearance: none;
|
|
33
|
+
font-size: inherit;
|
|
34
|
+
font-family: inherit;
|
|
35
|
+
padding: 0.5rem;
|
|
36
|
+
color: #212121;
|
|
37
|
+
background: white;
|
|
38
|
+
border: 1px solid #d8dae1;
|
|
39
|
+
border-radius: 5px;
|
|
40
|
+
box-shadow: none;
|
|
41
|
+
max-width: 100%;
|
|
42
|
+
display: inline-block;
|
|
43
|
+
}
|
|
44
|
+
select:not([multiple]) {
|
|
45
|
+
background-image: linear-gradient(45deg, transparent 49%, #212121 51%),
|
|
46
|
+
linear-gradient(135deg, #212121 51%, transparent 49%);
|
|
47
|
+
background-position: calc(100% - 15px), calc(100% - 10px);
|
|
48
|
+
background-size: 5px 5px, 5px 5px;
|
|
49
|
+
background-repeat: no-repeat;
|
|
50
|
+
padding: 5px 25px 5px 5px;
|
|
51
|
+
}
|
|
52
|
+
input[type="range"] {
|
|
53
|
+
width: 100%;
|
|
54
|
+
}
|
|
55
|
+
.select-tab {
|
|
56
|
+
display: flex;
|
|
57
|
+
align-items: center;
|
|
58
|
+
background-color: #edf2f7;
|
|
59
|
+
padding: 5px 0 5px 0;
|
|
60
|
+
margin: 0 10px 0 10px;
|
|
61
|
+
border-bottom: 1px solid #dee2e6;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
.select-tab > span {
|
|
65
|
+
text-overflow: ellipsis;
|
|
66
|
+
overflow: hidden;
|
|
67
|
+
white-space: nowrap;
|
|
68
|
+
min-width: 50px;
|
|
69
|
+
padding: 3px;
|
|
70
|
+
text-align: center;
|
|
71
|
+
color: #718096;
|
|
72
|
+
cursor: pointer;
|
|
73
|
+
}
|
|
74
|
+
.select-tab > b {
|
|
75
|
+
margin-right: 5px;
|
|
76
|
+
color: #5d5d5d;
|
|
77
|
+
}
|
|
78
|
+
.select-tab span.selected {
|
|
79
|
+
border-radius: 2px;
|
|
80
|
+
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, .1), 0 1px 2px 0 rgba(0, 0, 0, .06);
|
|
81
|
+
color: #0069d9;
|
|
82
|
+
background-color: white;
|
|
83
|
+
}
|
|
84
|
+
.editor {
|
|
85
|
+
padding: 5px;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
.editor .prop-section {
|
|
89
|
+
display: flex;
|
|
90
|
+
align-items: center;
|
|
91
|
+
margin: 5px 0;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
.editor .prop-section>span {
|
|
95
|
+
margin: 0 5px;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
.editor .prop-section :first-child {
|
|
99
|
+
color: #5d5d5d;
|
|
100
|
+
font-weight: bold;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
.editor .btn {
|
|
104
|
+
&.active {
|
|
105
|
+
background-color: #4d84bf;
|
|
106
|
+
}
|
|
107
|
+
margin: 0 5px;
|
|
108
|
+
background-color: #3333330d;
|
|
109
|
+
border-radius: 4px;
|
|
110
|
+
border: 1px solid #d8dae1;
|
|
111
|
+
color: #333333;
|
|
112
|
+
cursor: pointer;
|
|
113
|
+
display: inline-block;
|
|
114
|
+
padding: 5px;
|
|
115
|
+
}
|
|
116
|
+
.close-button {
|
|
117
|
+
position: absolute;
|
|
118
|
+
top: -7px;
|
|
119
|
+
right: -7px;
|
|
120
|
+
background-color: #dbdbdb;
|
|
121
|
+
color: #818181;
|
|
122
|
+
width: 15px;
|
|
123
|
+
height: 15px;
|
|
124
|
+
display: flex;
|
|
125
|
+
justify-content: center;
|
|
126
|
+
align-items: center;
|
|
127
|
+
cursor: pointer;
|
|
128
|
+
border-radius: 3px;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
.ise-helper-wrapper {
|
|
134
|
+
z-index: 9999998;
|
|
135
|
+
position: absolute;
|
|
136
|
+
top: 0;
|
|
137
|
+
left: 0;
|
|
138
|
+
pointer-events: none;
|
|
139
|
+
.overlay-over {
|
|
140
|
+
fill: #000000A0;
|
|
141
|
+
clip-path: url(#overlay-clip);
|
|
142
|
+
pointer-events: painted;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
import Picker from 'vanilla-picker/csp';
|
|
3
|
+
|
|
4
|
+
import { onMount, onDestroy } from 'svelte';
|
|
5
|
+
|
|
6
|
+
export let value = "#AAAAAAFF";
|
|
7
|
+
export let options = {};
|
|
8
|
+
export let onChange = () => {};
|
|
9
|
+
let self;
|
|
10
|
+
let pickerElem;
|
|
11
|
+
|
|
12
|
+
$: if (pickerElem) {
|
|
13
|
+
pickerElem.setColor(value);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function setColor(rgbaString) {
|
|
17
|
+
pickerElem.setColor(rgbaString);
|
|
18
|
+
}
|
|
19
|
+
function setValue(val) {
|
|
20
|
+
if (val === value) return;
|
|
21
|
+
onChange(val, value)
|
|
22
|
+
value = val;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function _onChange(color) {
|
|
26
|
+
setValue(color.rgbaString);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
onMount( () => {
|
|
30
|
+
init(options);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
onDestroy( () => {
|
|
34
|
+
pickerElem.destroy();
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
function init(opts) {
|
|
38
|
+
if (!self) return;
|
|
39
|
+
if (pickerElem) pickerElem.destroy();
|
|
40
|
+
opts.onChange = _onChange;
|
|
41
|
+
pickerElem = new Picker({
|
|
42
|
+
parent: self,
|
|
43
|
+
color: value,
|
|
44
|
+
popup: false,
|
|
45
|
+
...opts
|
|
46
|
+
});
|
|
47
|
+
pickerElem.show();
|
|
48
|
+
pickerElem.openHandler();
|
|
49
|
+
}
|
|
50
|
+
</script>
|
|
51
|
+
|
|
52
|
+
<div bind:this={self} ></div>
|
|
@@ -0,0 +1,379 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
import '../assets/index.scss';
|
|
3
|
+
import { onMount, onDestroy } from "svelte";
|
|
4
|
+
|
|
5
|
+
import pick from 'lodash.pick';
|
|
6
|
+
import debounce from 'lodash.debounce';
|
|
7
|
+
import { computeContours } from '../util/boxesContour';
|
|
8
|
+
import ColorPicker from './ColorPicker.svelte';
|
|
9
|
+
import { getFonts } from '../util/fonts';
|
|
10
|
+
const strokeElements = [ "altGlyph", "circle", "ellipse", "line", "path", "polygon", "polyline", "rect", "text", "textPath", "tref", "tspan",];
|
|
11
|
+
|
|
12
|
+
const borderProps = ["border-radius", "border-width", "border-color", "border-style"];
|
|
13
|
+
const backgroundProps = ["background-color"];
|
|
14
|
+
const fontProps = ["font-family", "font-size", "font-weight", "color"];
|
|
15
|
+
const pathProps = ["stroke-width", "stroke", "stroke-dasharray", "fill"];
|
|
16
|
+
const cssPropByType = {
|
|
17
|
+
"border-radius": {type: "slider", min: 0, max: 30, suffix: 'px'},
|
|
18
|
+
"border-width": {type: "slider", min: 0, max: 30, suffix: 'px'},
|
|
19
|
+
"border-style": {type: 'select', choices: () => ["none", "dotted", "dashed", "solid", "double", "groove", "ridge", "inset", "outset",]},
|
|
20
|
+
"border-color": {type: "color"},
|
|
21
|
+
"font-family": { type: 'select', choices: getFontFamilies},
|
|
22
|
+
"font-size": {type: "slider", min: 0, max: 30, suffix: 'px'},
|
|
23
|
+
"font-weight": {type: "slider", min: 0, max: 500},
|
|
24
|
+
"color": {type: "color"},
|
|
25
|
+
"stroke-width": {type: "slider", min: 0, max: 30, suffix: 'px'},
|
|
26
|
+
'stroke': {type: "color"},
|
|
27
|
+
'fill': {type: "color"},
|
|
28
|
+
"stroke-dasharray": {type: "slider", min: 0, max: 30, suffix: 'px'},
|
|
29
|
+
"background-color": {type: "color"},
|
|
30
|
+
};
|
|
31
|
+
const typeText = "text";
|
|
32
|
+
const typeBorder = "border";
|
|
33
|
+
const typeStroke = "stroke";
|
|
34
|
+
const typeBackground = "background";
|
|
35
|
+
const propByType = {
|
|
36
|
+
[typeText]: fontProps,
|
|
37
|
+
[typeBorder]: borderProps,
|
|
38
|
+
[typeStroke]: pathProps,
|
|
39
|
+
[typeBackground]: backgroundProps,
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export let getAdditionalElems = () => [];
|
|
43
|
+
export let listenOnClick = false;
|
|
44
|
+
let elementToListen = null;
|
|
45
|
+
let positionAnchor;
|
|
46
|
+
let self;
|
|
47
|
+
let helperElemWrapper;
|
|
48
|
+
let pathWithHoles = '';
|
|
49
|
+
let pageDimensions = { width: 0, height: 0 };
|
|
50
|
+
let targetsToSearch = [];
|
|
51
|
+
let allRules = []; // list of list of CSS rules, for every target element
|
|
52
|
+
let allTypes = []; // list of list of types (e.g color, border), for every target element
|
|
53
|
+
let selectedElemIndex = 0;
|
|
54
|
+
let selectedRuleIndex = 0;
|
|
55
|
+
let selectedTypeIndex = 0;
|
|
56
|
+
let propsByType; // propType -> {[props], selected}
|
|
57
|
+
let allCurrentPropDefs = {}; // propName => selectorDef
|
|
58
|
+
let bringableToFront = []; // null = not bringable, true = bringable, false = was bringed
|
|
59
|
+
$: {
|
|
60
|
+
if (elementToListen !== null) {
|
|
61
|
+
init();
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
$: currentElement = targetsToSearch[selectedElemIndex];
|
|
65
|
+
$: currentRule = allRules[selectedElemIndex]?.[selectedRuleIndex];
|
|
66
|
+
// $: curType = allTypes[selectedElemIndex]?.[selectedTypeIndex];
|
|
67
|
+
let curType;
|
|
68
|
+
$: {
|
|
69
|
+
if (allTypes[selectedElemIndex]?.[selectedTypeIndex] !== curType) {
|
|
70
|
+
curType = allTypes[selectedElemIndex]?.[selectedTypeIndex];
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
$: {
|
|
74
|
+
if (curType && currentRule){
|
|
75
|
+
const _allCurrentPropDefs = pick(cssPropByType, propByType[curType]);
|
|
76
|
+
Object.keys(_allCurrentPropDefs).forEach(key => {
|
|
77
|
+
_allCurrentPropDefs[key].displayed = getComputedPropValue(currentElement, key, 'raw');
|
|
78
|
+
const propSelectType = _allCurrentPropDefs[key].type;
|
|
79
|
+
let retrieveType = 'number';
|
|
80
|
+
if (propSelectType === 'color') retrieveType = 'rgb';
|
|
81
|
+
else if (propSelectType === 'select') retrieveType = 'raw';
|
|
82
|
+
_allCurrentPropDefs[key].value = getComputedPropValue(currentElement, key, retrieveType);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
propsByType = Object.entries(_allCurrentPropDefs).reduce((byType, [propName, selectorDef]) => {
|
|
86
|
+
const selectorType = selectorDef.type;
|
|
87
|
+
if (!(selectorType in byType)) byType[selectorType] = {selected: 0, props: [propName]};
|
|
88
|
+
else byType[selectorType].props.push(propName);
|
|
89
|
+
return byType;
|
|
90
|
+
}, {});
|
|
91
|
+
allCurrentPropDefs = _allCurrentPropDefs;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
onMount(() => {
|
|
96
|
+
close();
|
|
97
|
+
elementToListen = self.parentNode
|
|
98
|
+
document.body.appendChild(self);
|
|
99
|
+
document.body.appendChild(helperElemWrapper);
|
|
100
|
+
document.body.appendChild(positionAnchor);
|
|
101
|
+
udpatePageDimensions();
|
|
102
|
+
window.addEventListener('resize', udpatePageDimensions);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
onDestroy(() => {
|
|
106
|
+
window.removeEventListener('resize', udpatePageDimensions);
|
|
107
|
+
if(listenOnClick) elementToListen.removeEventListener("click", getTargetsAndRules);
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
function getFontFamilies() {
|
|
111
|
+
return getFonts();
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function getRuleNames(rules) {
|
|
115
|
+
if (!rules) return [];
|
|
116
|
+
return rules.map((rule, i) => {
|
|
117
|
+
if (rule === 'inline') return 'inline';
|
|
118
|
+
const cssSelector = rule.selectorText;
|
|
119
|
+
const title = rule.parentStyleSheet.title || `${i}`;
|
|
120
|
+
return `${title}: ${cssSelector}`;
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
let warningDisplayed = new Set();
|
|
125
|
+
function getMatchedCSSRules(elems) {
|
|
126
|
+
const sheets = document.styleSheets;
|
|
127
|
+
return elems.reduce((matchedRulesByElem, el) => {
|
|
128
|
+
const matchedRules = ['inline'];
|
|
129
|
+
for (let i in sheets) {
|
|
130
|
+
try {
|
|
131
|
+
const rules = sheets[i].cssRules;
|
|
132
|
+
for (let r in rules) {
|
|
133
|
+
const selectorText = rules[r].selectorText;
|
|
134
|
+
if (!selectorText || rules[r].selectorText.length > 50)
|
|
135
|
+
continue; // skip selectors too long
|
|
136
|
+
if (selectorText.split(',').some(selector => selector === '*'))
|
|
137
|
+
continue; // skip * selector
|
|
138
|
+
if (el.matches(selectorText)) {
|
|
139
|
+
matchedRules.push(rules[r]);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
} catch(err) {
|
|
143
|
+
if (!warningDisplayed.has(i)) {
|
|
144
|
+
console.log('Style editor: Not able to access', sheets[i].ownerNode, 'sheet. Try CORS loading the sheet if you want to edit it.');
|
|
145
|
+
warningDisplayed.add(i);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
matchedRulesByElem.push(matchedRules);
|
|
150
|
+
return matchedRulesByElem;
|
|
151
|
+
}, []);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function getEditableTypes(elems) {
|
|
155
|
+
return elems.reduce((typesByElem, elem) => {
|
|
156
|
+
const types = [];
|
|
157
|
+
if (elem.firstChild && elem.firstChild.nodeType === 3) { // Node.TEXT_NODE
|
|
158
|
+
types.push(typeText);
|
|
159
|
+
}
|
|
160
|
+
const elemTagName = elem.tagName.toLowerCase();
|
|
161
|
+
let bringable = false;
|
|
162
|
+
if (strokeElements.includes(elemTagName)) {
|
|
163
|
+
types.push(typeStroke);
|
|
164
|
+
const parentTag = elem.parentElement.tagName.toLowerCase();
|
|
165
|
+
if (parentTag === 'g' && elem.previousElementSibling && elem.previousElementSibling.tagName.toLowerCase() == elemTagName) {
|
|
166
|
+
bringable = true;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
else {
|
|
170
|
+
types.push(typeBorder);
|
|
171
|
+
types.push(typeBackground);
|
|
172
|
+
}
|
|
173
|
+
if (bringable) bringableToFront.push(true);
|
|
174
|
+
else bringableToFront.push(null);
|
|
175
|
+
typesByElem.push(types)
|
|
176
|
+
return typesByElem;
|
|
177
|
+
}, []);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function init() {
|
|
181
|
+
if(listenOnClick) elementToListen.addEventListener("click", _open);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function _open(e) {
|
|
185
|
+
open(e.target, e.pageX, e.pageY);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
export function open(el, x, y) {
|
|
189
|
+
if (el.classList.contains('overlay-over')) return overlayClicked();
|
|
190
|
+
else if (self.contains(el)) return;
|
|
191
|
+
selectedElemIndex = 0;
|
|
192
|
+
selectedRuleIndex = 0;
|
|
193
|
+
selectedTypeIndex = 0;
|
|
194
|
+
bringableToFront = [];
|
|
195
|
+
allTypes = [];
|
|
196
|
+
allRules = [];
|
|
197
|
+
targetsToSearch = [el, ...getAdditionalElems(el)];
|
|
198
|
+
allTypes = getEditableTypes(targetsToSearch);
|
|
199
|
+
allRules = getMatchedCSSRules(targetsToSearch);
|
|
200
|
+
if (x && y) show(x, y);
|
|
201
|
+
else {
|
|
202
|
+
const rect = getBoundingBoxInfos(el, 15);
|
|
203
|
+
show(rect.left, rect.top);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
export function close() {
|
|
208
|
+
self.style.display = "none";
|
|
209
|
+
helperElemWrapper.style.display = "none";
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function overlayClicked() {
|
|
213
|
+
close();
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function show(x, y) {
|
|
217
|
+
x = (x + 260 > pageDimensions.width) ? pageDimensions.width - 300 : x + 10;
|
|
218
|
+
y = (y + 410 > pageDimensions.height) ? pageDimensions.height - 450 : y + 10;
|
|
219
|
+
self.style.left = x + "px";
|
|
220
|
+
self.style.top = y + "px";
|
|
221
|
+
helperElemWrapper.style.display = "block";
|
|
222
|
+
self.style.display = "block";
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function updateHelpers() {
|
|
226
|
+
if (!currentRule) return;
|
|
227
|
+
let matching;
|
|
228
|
+
if (currentRule === 'inline') matching = [currentElement];
|
|
229
|
+
else {
|
|
230
|
+
const selector = currentRule.selectorText.replace(/(:hover)|:focus/g, '');
|
|
231
|
+
matching = Array.from(document.querySelectorAll(selector));
|
|
232
|
+
}
|
|
233
|
+
const boundingBoxes = matching.map(el => getBoundingBoxInfos(el, 10));
|
|
234
|
+
pathWithHoles = computeContours(boundingBoxes, pageDimensions);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function getBoundingBoxInfos(el, padding = 0) {
|
|
238
|
+
const rect = el.getBoundingClientRect();
|
|
239
|
+
return {
|
|
240
|
+
left: rect.left + window.scrollX - padding,
|
|
241
|
+
top: rect.top + window.scrollY - padding,
|
|
242
|
+
width: rect.width + padding * 2,
|
|
243
|
+
height: rect.height + padding * 2,
|
|
244
|
+
right: rect.left + window.scrollX + rect.width + padding,
|
|
245
|
+
bottom: rect.top + window.scrollY + rect.height + padding,
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function _updateCssRule(cssPropName, val, suffix) {
|
|
250
|
+
const finalValue = suffix ? val + suffix : val;
|
|
251
|
+
if (currentRule === 'inline') {
|
|
252
|
+
const style = currentElement.style; // do not trigger reactivity on currentElement
|
|
253
|
+
style[cssPropName] = finalValue;
|
|
254
|
+
}
|
|
255
|
+
else currentRule.style.setProperty(cssPropName, finalValue);
|
|
256
|
+
allCurrentPropDefs[cssPropName].value = val;
|
|
257
|
+
allCurrentPropDefs[cssPropName].displayed = finalValue;
|
|
258
|
+
updateHelpers();
|
|
259
|
+
}
|
|
260
|
+
const updateCssRule = debounce(_updateCssRule, 100);
|
|
261
|
+
|
|
262
|
+
function udpatePageDimensions(){
|
|
263
|
+
pageDimensions = {width: document.body.scrollWidth, height: document.body.scrollHeight};
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function cssRgbToHex(rgbStr) {
|
|
267
|
+
const m = rgbStr.match(/[0-9\.]+/g).map(i => parseFloat(i));
|
|
268
|
+
if (m.length === 3) m.push(1);
|
|
269
|
+
return m.reduce((hexStr, cur, i) => {
|
|
270
|
+
if (i === 3) hexStr += Math.round(cur * 255).toString(16).padStart(2, '0');
|
|
271
|
+
else hexStr += cur.toString(16).padStart(2, '0');
|
|
272
|
+
return hexStr;
|
|
273
|
+
}, '#');
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// type one of: "number", "rgb", "font", "raw"
|
|
277
|
+
function parsePropvalue(value, type="number") {
|
|
278
|
+
if (type=="raw") return value;
|
|
279
|
+
if (type == "number" && /[0-9]+(px)|(em)|(rem)/.test(value)) return parseInt(value);
|
|
280
|
+
if (type == "rgb" && (value.includes('rgb') || value[0] == "#")) return cssRgbToHex(value);
|
|
281
|
+
return value
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function getComputedPropValue(el, cssProp, type="number") {
|
|
285
|
+
let currentStyleValue = currentRule?.style?.[cssProp];
|
|
286
|
+
if (!currentStyleValue) {
|
|
287
|
+
const computed = getComputedStyle(el);
|
|
288
|
+
currentStyleValue = computed[cssProp];
|
|
289
|
+
}
|
|
290
|
+
return parsePropvalue(currentStyleValue, type);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
function bringToFront() {
|
|
294
|
+
bringableToFront[selectedElemIndex] = false;
|
|
295
|
+
currentElement.parentNode.appendChild(currentElement);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
</script>
|
|
299
|
+
|
|
300
|
+
<div bind:this={positionAnchor} style="position: absolute;"> </div>
|
|
301
|
+
<svg bind:this={helperElemWrapper} class="ise-helper-wrapper"
|
|
302
|
+
version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
|
|
303
|
+
width={pageDimensions.width} height={pageDimensions.height}
|
|
304
|
+
on:click={overlayClicked}>
|
|
305
|
+
<clipPath id="overlay-clip" clip-rule="evenodd">
|
|
306
|
+
<path d={pathWithHoles} />
|
|
307
|
+
</clipPath>
|
|
308
|
+
<rect y="0" x="0" height="100%" width="100%" class="overlay-over" />
|
|
309
|
+
</svg>
|
|
310
|
+
|
|
311
|
+
<div class="ise" bind:this={self}>
|
|
312
|
+
<div class="close-button" on:click={close}>x</div>
|
|
313
|
+
{#if targetsToSearch.length > 1}
|
|
314
|
+
<div class="select-tab">
|
|
315
|
+
<b> Elem </b>
|
|
316
|
+
{#each targetsToSearch as target, elemIndex}
|
|
317
|
+
<span class:selected={selectedElemIndex === elemIndex} on:click={() => {selectedElemIndex = elemIndex; selectedRuleIndex = 0;}}>
|
|
318
|
+
Elem {elemIndex}
|
|
319
|
+
</span>
|
|
320
|
+
{/each}
|
|
321
|
+
</div>
|
|
322
|
+
{/if}
|
|
323
|
+
<div class="select-tab">
|
|
324
|
+
<b> Rule: </b>
|
|
325
|
+
{#each getRuleNames(allRules[selectedElemIndex]) as ruleName, ruleIndex}
|
|
326
|
+
<span title={ruleName}
|
|
327
|
+
class:selected="{selectedRuleIndex === ruleIndex}"
|
|
328
|
+
on:click="{() => {selectedRuleIndex = ruleIndex;}}"
|
|
329
|
+
> {ruleName}</span>
|
|
330
|
+
{/each}
|
|
331
|
+
</div>
|
|
332
|
+
<div class="select-tab">
|
|
333
|
+
<b> Property type: </b>
|
|
334
|
+
{#each allTypes[selectedElemIndex] || [] as type, typeIndex}
|
|
335
|
+
<span class:selected="{selectedTypeIndex === typeIndex}" on:click="{() => {selectedTypeIndex = typeIndex;}}"> {type} </span>
|
|
336
|
+
{/each}
|
|
337
|
+
</div>
|
|
338
|
+
{#if allTypes[selectedElemIndex]}
|
|
339
|
+
<div class="editor">
|
|
340
|
+
{#each Object.entries(propsByType) as [propType, choices]}
|
|
341
|
+
{@const selectedName = choices.props[choices.selected]}
|
|
342
|
+
<div class="prop-section">
|
|
343
|
+
{#if choices.props.length > 1}
|
|
344
|
+
<div> <select on:change="{(e) => choices.selected = e.target.value}">
|
|
345
|
+
{#each choices.props as propName, i}
|
|
346
|
+
<option selected={i === choices.selected} value="{i}"> {propName} </option>
|
|
347
|
+
{/each}
|
|
348
|
+
</select> </div>
|
|
349
|
+
{:else}
|
|
350
|
+
<span> { selectedName } </span>
|
|
351
|
+
{/if}
|
|
352
|
+
{#if propType === 'slider'}
|
|
353
|
+
<input type=range value={allCurrentPropDefs[selectedName].value}
|
|
354
|
+
min={allCurrentPropDefs[selectedName].min}
|
|
355
|
+
max={allCurrentPropDefs[selectedName].max}
|
|
356
|
+
on:change={(e) => updateCssRule(selectedName, e.target.value, allCurrentPropDefs[selectedName].suffix, e.target)}/>
|
|
357
|
+
<span class="current-value"> { allCurrentPropDefs[selectedName].displayed } </span>
|
|
358
|
+
{:else if propType == 'select'}
|
|
359
|
+
<select on:change={(e) => updateCssRule(selectedName, e.target.value)}>
|
|
360
|
+
{#each allCurrentPropDefs[selectedName].choices() as choice}
|
|
361
|
+
<option selected={choice == allCurrentPropDefs[selectedName].value || null}> {choice} </option>
|
|
362
|
+
{/each}
|
|
363
|
+
</select>
|
|
364
|
+
{:else if propType == 'color'}
|
|
365
|
+
<ColorPicker
|
|
366
|
+
value={allCurrentPropDefs[selectedName].value}
|
|
367
|
+
onChange={(color => updateCssRule(selectedName, color))}
|
|
368
|
+
/>
|
|
369
|
+
{/if}
|
|
370
|
+
</div>
|
|
371
|
+
{/each}
|
|
372
|
+
{#if currentRule === 'inline' && bringableToFront[selectedElemIndex] !== null}
|
|
373
|
+
<div class="btn" class:active="{bringableToFront[selectedElemIndex] === true}" on:click="{bringToFront}">
|
|
374
|
+
Bring to front
|
|
375
|
+
</div>
|
|
376
|
+
{/if}
|
|
377
|
+
</div>
|
|
378
|
+
{/if}
|
|
379
|
+
</div>
|