inline-style-editor 1.4.3 → 1.5.4

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "inline-style-editor",
3
- "version": "1.4.3",
3
+ "version": "1.5.4",
4
4
  "description": "Update CSS rules or add inline style to elements visualy",
5
5
  "scripts": {
6
6
  "build": "rollup --config",
@@ -38,21 +38,13 @@
38
38
  margin: auto;
39
39
  }
40
40
 
41
- .prop-name {
42
- display: flex;
43
- align-items: center;
44
-
45
- &>span {
46
- margin-left: 4px;
47
- }
41
+ .current-value {
42
+ font-size: 12px;
43
+ color: #64748b;
44
+ min-width: 40px;
45
+ text-align: right;
48
46
  }
49
47
 
50
- .delete {
51
- cursor: pointer;
52
- font-size: 20px;
53
- margin: 0 4px;
54
- margin-bottom: 2px;
55
- }
56
48
 
57
49
  .select-tab {
58
50
  display: flex;
@@ -91,21 +83,100 @@
91
83
  }
92
84
 
93
85
  .editor .prop-section {
94
- display: flex;
95
- align-items: center;
96
- margin: 5px 0;
97
- align-items: baseline;
86
+ background: #f8fafc;
87
+ border-radius: 6px;
88
+ padding: 8px;
89
+ margin: 6px 0;
90
+
91
+ &:first-child {
92
+ margin-top: 0;
93
+ }
94
+
95
+ &.slider {
96
+ .prop-header + input[type="range"] {
97
+ display: inline-block;
98
+ width: calc(100% - 50px);
99
+ vertical-align: middle;
100
+ }
101
+
102
+ .current-value {
103
+ display: inline-block;
104
+ vertical-align: middle;
105
+ }
106
+ }
98
107
  }
99
108
 
100
- .editor .prop-section>span {
101
- margin: 0 5px;
109
+ .prop-header {
110
+ display: flex;
111
+ align-items: center;
112
+ justify-content: space-between;
113
+ margin-bottom: 6px;
102
114
  }
103
115
 
104
- .editor .prop-section :first-child {
116
+ .prop-name {
117
+ display: flex;
118
+ align-items: center;
119
+ gap: 8px;
105
120
  color: #5d5d5d;
106
121
  font-weight: bold;
107
122
  }
108
123
 
124
+ .selected-label {
125
+ font-size: 12px;
126
+ color: #64748b;
127
+ font-weight: 500;
128
+ }
129
+
130
+ .close-button,
131
+ .delete-btn {
132
+ display: flex;
133
+ align-items: center;
134
+ justify-content: center;
135
+ width: 20px;
136
+ height: 20px;
137
+ padding: 0;
138
+ border: none;
139
+ cursor: pointer;
140
+ flex-shrink: 0;
141
+ transition: background 0.15s ease, color 0.15s ease, transform 0.15s ease;
142
+
143
+ svg {
144
+ width: 12px;
145
+ height: 12px;
146
+ }
147
+ }
148
+
149
+ .delete-btn {
150
+ border-radius: 4px;
151
+ background: transparent;
152
+ color: #94a3b8;
153
+
154
+ &:hover {
155
+ background: #fee2e2;
156
+ color: #dc2626;
157
+ }
158
+ }
159
+
160
+ .close-button {
161
+ position: absolute;
162
+ top: -8px;
163
+ right: -8px;
164
+ border-radius: 50%;
165
+ background: #64748b;
166
+ color: white;
167
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
168
+
169
+ &:hover {
170
+ background: #475569;
171
+ transform: scale(1.1);
172
+ }
173
+
174
+ svg {
175
+ width: 10px;
176
+ height: 10px;
177
+ }
178
+ }
179
+
109
180
  .editor .btn {
110
181
  &.active {
111
182
  background-color: #4d84bf;
@@ -125,19 +196,41 @@
125
196
  padding: 5px;
126
197
  }
127
198
 
128
- .close-button {
129
- position: absolute;
130
- top: -7px;
131
- right: -7px;
132
- background-color: #dbdbdb;
133
- color: #818181;
134
- width: 15px;
135
- height: 15px;
199
+ .icon-selector {
136
200
  display: flex;
137
- justify-content: center;
201
+ gap: 2px;
138
202
  align-items: center;
203
+ }
204
+
205
+ .icon-btn {
206
+ display: flex;
207
+ align-items: center;
208
+ justify-content: center;
209
+ width: 26px;
210
+ height: 26px;
211
+ padding: 4px;
212
+ border: 1.5px solid transparent;
213
+ border-radius: 5px;
214
+ background: transparent;
139
215
  cursor: pointer;
140
- border-radius: 3px;
216
+ color: #94a3b8;
217
+
218
+ &:hover {
219
+ background: #e2e8f0;
220
+ color: #475569;
221
+ }
222
+
223
+ &.selected {
224
+ background: white;
225
+ border-color: #3b82f6;
226
+ color: #2563eb;
227
+ box-shadow: 0 1px 3px 0 rgba(59, 130, 246, 0.3), 0 0 0 1px rgba(59, 130, 246, 0.1);
228
+ }
229
+
230
+ svg {
231
+ width: 16px;
232
+ height: 16px;
233
+ }
141
234
  }
142
235
 
143
236
  .layout_default.picker_wrapper {
@@ -56,6 +56,23 @@
56
56
  "background-color": { type: "color" },
57
57
  };
58
58
 
59
+ const propertyIcons = {
60
+ stroke: `<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"><line x1="2" y1="14" x2="14" y2="2"/></svg>`,
61
+ fill: `<svg viewBox="0 0 16 16" fill="currentColor"><rect x="2" y="2" width="12" height="12" rx="2"/></svg>`,
62
+ color: `<svg viewBox="0 0 16 16" fill="currentColor"><text x="3" y="11" font-size="10" font-weight="bold">A</text><rect x="2" y="13" width="12" height="2"/></svg>`,
63
+ "border-color": `<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><rect x="2" y="2" width="12" height="12" rx="1"/></svg>`,
64
+ "background-color": `<svg viewBox="0 0 16 16" fill="currentColor"><rect x="4" y="4" width="10" height="10" rx="1" opacity="0.5"/><rect x="2" y="2" width="10" height="10" rx="1"/></svg>`,
65
+ "border-radius": `<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M2 10 L2 2 L10 2" stroke-linecap="round"/><path d="M2 10 Q2 14 6 14 L14 14" stroke-linecap="round"/></svg>`,
66
+ "border-width": `<svg viewBox="0 0 16 16" fill="currentColor"><rect x="2" y="2" width="12" height="2"/><rect x="2" y="7" width="12" height="3"/><rect x="2" y="13" width="12" height="1"/></svg>`,
67
+ "border-style": `<svg viewBox="0 0 16 16" fill="currentColor"><rect x="2" y="2" width="12" height="1.5"/><rect x="2" y="7" width="3" height="1.5"/><rect x="7" y="7" width="3" height="1.5"/><rect x="2" y="12" width="1.5" height="1.5"/><rect x="5" y="12" width="1.5" height="1.5"/><rect x="8" y="12" width="1.5" height="1.5"/><rect x="11" y="12" width="1.5" height="1.5"/></svg>`,
68
+ "font-size": `<svg viewBox="0 0 16 16" fill="currentColor"><text x="1" y="12" font-size="12" font-weight="bold">T</text><text x="9" y="12" font-size="8" font-weight="bold">T</text></svg>`,
69
+ "font-weight": `<svg viewBox="0 0 16 16" fill="currentColor"><text x="3" y="13" font-size="14" font-weight="bold">B</text></svg>`,
70
+ "font-family": `<svg viewBox="0 0 16 16" fill="currentColor"><text x="4" y="13" font-size="14" font-style="italic" font-family="serif">F</text></svg>`,
71
+ "stroke-width": `<svg viewBox="0 0 16 16" fill="currentColor"><rect x="2" y="2" width="12" height="2"/><rect x="2" y="7" width="12" height="3"/><rect x="2" y="13" width="12" height="1"/></svg>`,
72
+ "stroke-dasharray": `<svg viewBox="0 0 16 16" fill="currentColor"><rect x="1" y="7" width="4" height="2"/><rect x="7" y="7" width="4" height="2"/><rect x="13" y="7" width="2" height="2"/></svg>`,
73
+ "stroke-linejoin": `<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2" stroke-linejoin="round"><polyline points="3,12 8,4 13,12"/></svg>`,
74
+ };
75
+
59
76
  // Props
60
77
  const props = $props();
61
78
  const getElems = props.getElems ?? null;
@@ -70,6 +87,7 @@
70
87
  if (cssRuleName === "inline") return "Selected element";
71
88
  return cssRuleName;
72
89
  });
90
+ const ignoredProps = props.ignoredProps ?? [];
73
91
 
74
92
  const typeText = "text";
75
93
  const typeBorder = "border";
@@ -154,6 +172,7 @@
154
172
  function initAndGroup() {
155
173
  const allProps = { ...cssPropByType, ...customProps };
156
174
  const _allCurrentPropDefs = pick(allProps, propByType[curType]);
175
+ ignoredProps.forEach((prop) => delete _allCurrentPropDefs[prop]);
157
176
  Object.keys(_allCurrentPropDefs).forEach((key) => {
158
177
  const propSelectType = _allCurrentPropDefs[key].type;
159
178
  let retrieveType = "number";
@@ -170,10 +189,18 @@
170
189
  } else {
171
190
  _allCurrentPropDefs[key].displayed = getComputedPropValue(currentElement, key, "raw");
172
191
  _allCurrentPropDefs[key].value = getComputedPropValue(currentElement, key, retrieveType);
192
+ // Special handling for SVG fill with url() expressions (e.g., gradients)
193
+ if (key === "fill") {
194
+ const rawFill = currentElement.getAttribute?.("fill") || _allCurrentPropDefs[key].displayed;
195
+ if (rawFill && rawFill.includes("url(")) {
196
+ _allCurrentPropDefs[key].originalUrl = rawFill;
197
+ _allCurrentPropDefs[key].value = "#00000000";
198
+ }
199
+ }
173
200
  }
174
201
  });
175
-
176
- propsByType = Object.entries(_allCurrentPropDefs)
202
+ console.log(_allCurrentPropDefs);
203
+ const _propsByType = Object.entries(_allCurrentPropDefs)
177
204
  .reduce((byType, [propName, selectorDef]) => {
178
205
  const selectorType = selectorDef.type;
179
206
  const existing = byType.find((x) => x.type === selectorType);
@@ -191,6 +218,18 @@
191
218
  if (inputTypeOrder[a.type] > inputTypeOrder[b.type]) return 1;
192
219
  return 0;
193
220
  });
221
+
222
+ // Pre-select fill over stroke if the element has a non-transparent fill
223
+ const colorGroup = _propsByType.find((g) => g.type === "color");
224
+ if (colorGroup) {
225
+ const fillIndex = colorGroup.props.indexOf("fill");
226
+ const fillValue = _allCurrentPropDefs["fill"]?.value;
227
+ if (fillIndex !== -1 && fillValue && fillValue !== "#00000000") {
228
+ colorGroup.selected = fillIndex;
229
+ }
230
+ }
231
+
232
+ propsByType = _propsByType;
194
233
  allCurrentPropDefs = _allCurrentPropDefs;
195
234
  updateHelpers();
196
235
  }
@@ -448,13 +487,48 @@
448
487
  close();
449
488
  }
450
489
  function deleteProp(propName) {
451
- if (currentRule === "inline") {
452
- currentElement.style.removeProperty(propName);
490
+ const propDef = allCurrentPropDefs[propName];
491
+ const isCustomProp = propName in customProps;
492
+
493
+ // Check if custom prop has a defaultValue
494
+ if (isCustomProp && propDef && "defaultValue" in customProps[propName]) {
495
+ const defaultValue = customProps[propName].defaultValue;
496
+ if (propDef.setter) {
497
+ propDef.setter(currentElement, defaultValue);
498
+ }
499
+ propDef.value = defaultValue;
500
+ propDef.displayed = defaultValue;
501
+ onStyleChanged(currentElement, currentRule, propName, defaultValue);
502
+ } else if (propName === "fill" && propDef?.originalUrl) {
503
+ // Restore original url() expression for SVG fill
504
+ const urlValue = propDef.originalUrl;
505
+ if (currentRule === "inline") {
506
+ currentElement.style.fill = urlValue;
507
+ } else {
508
+ currentRule.style.setProperty("fill", urlValue);
509
+ }
510
+ propDef.value = "#00000000";
511
+ propDef.displayed = urlValue;
512
+ onStyleChanged(currentElement, currentRule, "fill", urlValue);
453
513
  } else {
454
- currentRule.style.removeProperty(propName);
514
+ // Standard CSS property reset
515
+ if (currentRule === "inline") {
516
+ currentElement.style.removeProperty(propName);
517
+ } else {
518
+ currentRule.style.removeProperty(propName);
519
+ }
520
+ onStyleChanged(currentElement, currentRule, propName, null);
521
+ // Update the displayed value without rebuilding (which would reset selection)
522
+ if (propDef) {
523
+ const propSelectType = propDef.type;
524
+ let retrieveType = "number";
525
+ if (propSelectType === "color") retrieveType = "rgb";
526
+ else if (propSelectType === "select") retrieveType = "raw";
527
+ propDef.displayed = getComputedPropValue(currentElement, propName, "raw");
528
+ propDef.value = getComputedPropValue(currentElement, propName, retrieveType);
529
+ }
455
530
  }
456
- onStyleChanged(currentElement, currentRule, propName, null);
457
- initAndGroup();
531
+ updateHelpers();
458
532
  }
459
533
 
460
534
  function selectRule(ruleIndex) {
@@ -484,7 +558,11 @@
484
558
  </svg>
485
559
 
486
560
  <div class="ise" bind:this={self}>
487
- <div class="close-button" onclick={close}>x</div>
561
+ <button class="close-button" title="Close" onclick={close}>
562
+ <svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2.5">
563
+ <path d="M4 4 L12 12 M12 4 L4 12" />
564
+ </svg>
565
+ </button>
488
566
  {#if targetsToSearch.length > 1}
489
567
  <div class="select-tab">
490
568
  <b> Element </b>
@@ -501,73 +579,89 @@
501
579
  {/each}
502
580
  </div>
503
581
  {/if}
504
- <div class="select-tab">
505
- <b> Applied to: </b>
506
- {#if nbChars(getRuleNamesTransformed(allRules[selectedElemIndex])) > 30}
507
- <select onchange={(e) => selectRule(e.target.value)}>
582
+ {#if allRules[selectedElemIndex]?.length > 1}
583
+ <div class="select-tab">
584
+ <b> Applied to: </b>
585
+ {#if nbChars(getRuleNamesTransformed(allRules[selectedElemIndex])) > 30}
586
+ <select onchange={(e) => selectRule(e.target.value)}>
587
+ {#each getRuleNames(allRules[selectedElemIndex]) as ruleName, ruleIndex}
588
+ <option selected={selectedRuleIndex === ruleIndex} value={ruleIndex}
589
+ >{getCssRuleName(ruleName, clickedElement)}</option
590
+ >
591
+ {/each}
592
+ </select>
593
+ {:else}
508
594
  {#each getRuleNames(allRules[selectedElemIndex]) as ruleName, ruleIndex}
509
- <option selected={selectedRuleIndex === ruleIndex} value={ruleIndex}
510
- >{getCssRuleName(ruleName, clickedElement)}</option
595
+ <span
596
+ title={ruleName}
597
+ class:selected={selectedRuleIndex === ruleIndex}
598
+ onclick={() => {
599
+ selectRule(ruleIndex);
600
+ }}
601
+ >
602
+ {getCssRuleName(ruleName, clickedElement)}</span
511
603
  >
512
604
  {/each}
513
- </select>
514
- {:else}
515
- {#each getRuleNames(allRules[selectedElemIndex]) as ruleName, ruleIndex}
516
- <span
517
- title={ruleName}
518
- class:selected={selectedRuleIndex === ruleIndex}
519
- onclick={() => {
520
- selectRule(ruleIndex);
521
- }}
522
- >
523
- {getCssRuleName(ruleName, clickedElement)}</span
524
- >
525
- {/each}
526
- {/if}
527
- </div>
528
- <div class="select-tab">
529
- <b> Property type: </b>
530
- {#each allTypes[selectedElemIndex] || [] as type, typeIndex}
531
- <!-- Only display "custom" on "inline" rule -->
532
- {#if type !== "custom" || (currentRule === "inline" && type === "custom" && hasDisplayedCustom)}
533
- <span
534
- class:selected={selectedTypeIndex === typeIndex}
535
- onclick={() => {
536
- selectedTypeIndex = typeIndex;
537
- }}
538
- >
539
- {type === "stroke" ? "SVG paint" : capitalizeFirstLetter(type)}
540
- </span>
541
605
  {/if}
542
- {/each}
543
- </div>
606
+ </div>
607
+ {/if}
608
+ {#if (allTypes[selectedElemIndex] || []).filter((t) => t !== "custom" || (currentRule === "inline" && hasDisplayedCustom)).length > 1}
609
+ <div class="select-tab">
610
+ <b> Property type: </b>
611
+ {#each allTypes[selectedElemIndex] || [] as type, typeIndex}
612
+ <!-- Only display "custom" on "inline" rule -->
613
+ {#if type !== "custom" || (currentRule === "inline" && type === "custom" && hasDisplayedCustom)}
614
+ <span
615
+ class:selected={selectedTypeIndex === typeIndex}
616
+ onclick={() => {
617
+ selectedTypeIndex = typeIndex;
618
+ }}
619
+ >
620
+ {type === "stroke" ? "SVG paint" : capitalizeFirstLetter(type)}
621
+ </span>
622
+ {/if}
623
+ {/each}
624
+ </div>
625
+ {/if}
544
626
  {#if allTypes[selectedElemIndex]}
545
627
  <div class="editor">
546
628
  {#each propsByType as choices}
547
629
  {@const selectedName = choices.props[choices.selected]}
548
630
  <div class="prop-section {choices.type}">
549
- <div class="prop-name">
550
- {#if choices.props.length > 1}
551
- <select
552
- onchange={async (e) => {
553
- choices.selected = e.target.value;
554
- await tick();
555
- }}
556
- >
557
- {#each choices.props as propName, i}
558
- <option selected={i === choices.selected} value={i}>
559
- {#if choices.type === "color"}
560
- {capitalizeFirstLetter(propName)} color
561
- {:else}
562
- {pascalCaseToSentence(propName)}
563
- {/if}
564
- </option>
565
- {/each}
566
- </select>
567
- {:else}
568
- <span> {pascalCaseToSentence(selectedName)} </span>
569
- {/if}
570
- <span class="delete" onclick={() => deleteProp(selectedName)}>✕</span>
631
+ <div class="prop-header">
632
+ <div class="prop-name">
633
+ {#if choices.props.length > 1}
634
+ <div class="icon-selector">
635
+ {#each choices.props as propName, i}
636
+ <button
637
+ class="icon-btn"
638
+ class:selected={i === choices.selected}
639
+ title={choices.type === "color"
640
+ ? `${capitalizeFirstLetter(propName)} color`
641
+ : pascalCaseToSentence(propName)}
642
+ onclick={async () => {
643
+ choices.selected = i;
644
+ await tick();
645
+ }}
646
+ >
647
+ {@html propertyIcons[propName]}
648
+ </button>
649
+ {/each}
650
+ </div>
651
+ <span class="selected-label"
652
+ >{choices.type === "color"
653
+ ? `${capitalizeFirstLetter(selectedName)} color`
654
+ : pascalCaseToSentence(selectedName)}</span
655
+ >
656
+ {:else}
657
+ <span>{pascalCaseToSentence(selectedName)}</span>
658
+ {/if}
659
+ </div>
660
+ <button class="delete-btn" title="Reset to default" onclick={() => deleteProp(selectedName)}>
661
+ <svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2">
662
+ <path d="M4 4 L12 12 M12 4 L4 12" />
663
+ </svg>
664
+ </button>
571
665
  </div>
572
666
  {#if choices.type === "slider"}
573
667
  <input