@dimarborda/xlsx-editor 0.2.0 → 0.3.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/README.md +43 -232
- package/package.json +4 -2
- package/src/draw.js +12 -3
- package/src/elements.js +1 -0
- package/src/engine.d.ts +42 -2
- package/src/engine.js +132 -2
- package/src/find.js +37 -0
- package/src/grid.js +89 -0
- package/src/index.d.ts +86 -4
- package/src/style.css +154 -1
- package/src/xlsx_editor.js +1481 -51
- package/wasm/xlsx_engine.d.ts +57 -1
- package/wasm/xlsx_engine.js +180 -1
- package/wasm/xlsx_engine_bg.wasm +0 -0
- package/wasm/xlsx_engine_bg.wasm.d.ts +11 -0
package/src/find.js
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// Searching cell texts as Excel does: `*` any text, `?` one character, `~` takes either literally.
|
|
2
|
+
// No DOM here, so it is testable in Node.
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The search as a global regular expression; null for an empty search
|
|
6
|
+
* @param {string} query
|
|
7
|
+
* @param {{matchCase?: boolean, wholeCell?: boolean}} [options]
|
|
8
|
+
*/
|
|
9
|
+
export function searchPattern(query, { matchCase = false, wholeCell = false } = {}) {
|
|
10
|
+
if (!query) return null;
|
|
11
|
+
// With the `u` flag only characters with a meaning may be escaped
|
|
12
|
+
const literal = c => c.replace(/[.*+?^${}()|[\]\\/]/gu, '\\$&');
|
|
13
|
+
let source = '';
|
|
14
|
+
const chars = [...query];
|
|
15
|
+
for (let i = 0; i < chars.length; i++) {
|
|
16
|
+
const c = chars[i];
|
|
17
|
+
if (c === '~' && '*?~'.includes(chars[i + 1] ?? ' ')) source += literal(chars[++i]);
|
|
18
|
+
else if (c === '*') source += '[\\s\\S]*';
|
|
19
|
+
else if (c === '?') source += '[\\s\\S]';
|
|
20
|
+
else source += literal(c);
|
|
21
|
+
}
|
|
22
|
+
return new RegExp(wholeCell ? `^(?:${source})$` : source, matchCase ? 'gu' : 'giu');
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Whether the pattern is in a text (a global expression remembers where it stopped: not here) */
|
|
26
|
+
export function matches(pattern, text) {
|
|
27
|
+
pattern.lastIndex = 0;
|
|
28
|
+
const found = pattern.test(text);
|
|
29
|
+
pattern.lastIndex = 0;
|
|
30
|
+
return found;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** The text with what the pattern finds replaced, taken literally */
|
|
34
|
+
export function replaceMatches(pattern, text, replacement) {
|
|
35
|
+
pattern.lastIndex = 0;
|
|
36
|
+
return text.replace(pattern, found => (found === '' ? '' : replacement));
|
|
37
|
+
}
|
package/src/grid.js
CHANGED
|
@@ -12,6 +12,11 @@ export function colWidthToPx(width, mdw) {
|
|
|
12
12
|
return Math.trunc(((256 * width + Math.trunc(128 / mdw)) / 256) * mdw);
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
+
/** Pixels to the width in characters `colWidthToPx` turns back into those pixels */
|
|
16
|
+
export function pxToColWidth(px, mdw) {
|
|
17
|
+
return Math.round((px / mdw) * 256) / 256;
|
|
18
|
+
}
|
|
19
|
+
|
|
15
20
|
/**
|
|
16
21
|
* Default column width in pixels: `defaultColWidth` when the sheet sets it, otherwise
|
|
17
22
|
* `baseColWidth` characters (8 by default) plus 5 pixels of padding, rounded up to a multiple
|
|
@@ -147,3 +152,87 @@ export function sheetAxes(layout, used, mdw, defaultRowPt = 15) {
|
|
|
147
152
|
const cols = Math.min(MAX_COLS, Math.max(lastCol, used.maxCol) + 12);
|
|
148
153
|
return { rows: new Axis(rows, rowPx, rowSizes), cols: new Axis(Math.max(cols, 26), colPx, colSizes) };
|
|
149
154
|
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* What Excel's status bar says about the cells of a range: how many have something and the sum,
|
|
158
|
+
* average, smallest and largest of the numbers. Hidden rows and columns do not count, and any
|
|
159
|
+
* error leaves only the count (as Excel does). `style` is the format of the first number. `null`
|
|
160
|
+
* when the range is empty.
|
|
161
|
+
* @param {Map<number, {kind: string, style: number, number?: number}>} cells by `row * 16385 + col`
|
|
162
|
+
* @param {{first: {row: number, col: number}, last: {row: number, col: number}}} range
|
|
163
|
+
* @param {{rows?: Axis, cols?: Axis}} [axes]
|
|
164
|
+
*/
|
|
165
|
+
export function rangeStats(cells, { first, last }, { rows, cols } = {}) {
|
|
166
|
+
let count = 0;
|
|
167
|
+
let numbers = 0;
|
|
168
|
+
let sum = 0;
|
|
169
|
+
let min = Infinity;
|
|
170
|
+
let max = -Infinity;
|
|
171
|
+
let error = false;
|
|
172
|
+
let style = 0;
|
|
173
|
+
let firstAt = Infinity;
|
|
174
|
+
const visit = (cell, row, col) => {
|
|
175
|
+
if (!cell || !cell.kind || rows?.size(row) === 0 || cols?.size(col) === 0) return;
|
|
176
|
+
count++;
|
|
177
|
+
if (cell.kind === 'e') error = true;
|
|
178
|
+
if (cell.number === undefined) return;
|
|
179
|
+
numbers++;
|
|
180
|
+
sum += cell.number;
|
|
181
|
+
min = Math.min(min, cell.number);
|
|
182
|
+
max = Math.max(max, cell.number);
|
|
183
|
+
const at = row * 16_385 + col;
|
|
184
|
+
if (at < firstAt) {
|
|
185
|
+
firstAt = at;
|
|
186
|
+
style = cell.style;
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
// Whichever is smaller: the cells of the range or those of the sheet
|
|
190
|
+
if ((last.row - first.row + 1) * (last.col - first.col + 1) <= cells.size) {
|
|
191
|
+
for (let r = first.row; r <= last.row; r++) {
|
|
192
|
+
for (let c = first.col; c <= last.col; c++) visit(cells.get(r * 16_385 + c), r, c);
|
|
193
|
+
}
|
|
194
|
+
} else {
|
|
195
|
+
for (const [at, cell] of cells) {
|
|
196
|
+
const r = Math.floor(at / 16_385);
|
|
197
|
+
const c = at - r * 16_385;
|
|
198
|
+
if (r >= first.row && r <= last.row && c >= first.col && c <= last.col) visit(cell, r, c);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
if (!count) return null;
|
|
202
|
+
if (error || !numbers) return { count, numbers: error ? 0 : numbers, sum: null, average: null, min: null, max: null, style };
|
|
203
|
+
return { count, numbers, sum, average: sum / numbers, min, max, style };
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Where Ctrl+arrow lands from the line `from`, as Excel does: to the end of the block of filled
|
|
208
|
+
* cells it is in, else to the next filled cell, else to the end of the sheet
|
|
209
|
+
* @param {(line: number) => boolean} filled
|
|
210
|
+
* @param {number} from
|
|
211
|
+
* @param {1 | -1} step
|
|
212
|
+
* @param {number} limit last line of the sheet
|
|
213
|
+
*/
|
|
214
|
+
export function dataEdge(filled, from, step, limit) {
|
|
215
|
+
const inside = i => i >= 1 && i <= limit;
|
|
216
|
+
if (!inside(from + step)) return from;
|
|
217
|
+
let i = from;
|
|
218
|
+
if (filled(from) && filled(from + step)) {
|
|
219
|
+
while (inside(i + step) && filled(i + step)) i += step;
|
|
220
|
+
return i;
|
|
221
|
+
}
|
|
222
|
+
for (i = from + step; inside(i); i += step) if (filled(i)) return i;
|
|
223
|
+
return step < 0 ? 1 : limit;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* The far end of the run of numbers next to the line `from`, going by `step`; null when the next
|
|
228
|
+
* line has none. Autosum takes that run.
|
|
229
|
+
* @param {(line: number) => boolean} isNumber
|
|
230
|
+
* @param {number} from
|
|
231
|
+
* @param {1 | -1} step
|
|
232
|
+
* @param {number} limit last line of the sheet
|
|
233
|
+
*/
|
|
234
|
+
export function numberRun(isNumber, from, step, limit) {
|
|
235
|
+
let end = null;
|
|
236
|
+
for (let i = from + step; i >= 1 && i <= limit && isNumber(i); i += step) end = i;
|
|
237
|
+
return end;
|
|
238
|
+
}
|
package/src/index.d.ts
CHANGED
|
@@ -11,6 +11,8 @@ export interface XlsxEditorOptions {
|
|
|
11
11
|
tabs?: boolean;
|
|
12
12
|
/** Bar above the grid with the selected cell and its content; default true */
|
|
13
13
|
formulaBar?: boolean;
|
|
14
|
+
/** Bar below the tabs with the count, sum and average of the selection; default true */
|
|
15
|
+
statusBar?: boolean;
|
|
14
16
|
/** A viewer: cells are selected and copied but not edited; default false */
|
|
15
17
|
readOnly?: boolean;
|
|
16
18
|
/** Where `xlsx_engine_bg.wasm` is served from, when the bundler does not serve it */
|
|
@@ -30,6 +32,41 @@ export interface Selection {
|
|
|
30
32
|
active: CellPosition;
|
|
31
33
|
/** Text the active cell shows */
|
|
32
34
|
text: string;
|
|
35
|
+
/** Whole rows, whole columns or the whole sheet selected from the headers */
|
|
36
|
+
whole: 'row' | 'col' | 'all' | null;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** What the status bar says about the selection */
|
|
40
|
+
export interface SelectionStats {
|
|
41
|
+
/** Cells that have something */
|
|
42
|
+
count: number;
|
|
43
|
+
/** Of them, numbers (dates count, as serial numbers) */
|
|
44
|
+
numbers: number;
|
|
45
|
+
/** `null` without numbers, or when an error is selected */
|
|
46
|
+
sum: number | null;
|
|
47
|
+
average: number | null;
|
|
48
|
+
min: number | null;
|
|
49
|
+
max: number | null;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface FindOptions {
|
|
53
|
+
matchCase?: boolean;
|
|
54
|
+
/** Only cells whose whole text matches */
|
|
55
|
+
wholeCell?: boolean;
|
|
56
|
+
/** The active sheet (default) or every worksheet */
|
|
57
|
+
scope?: 'sheet' | 'workbook';
|
|
58
|
+
/** Look in the formula of formula cells instead of the text they show */
|
|
59
|
+
inFormulas?: boolean;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export interface FindHit {
|
|
63
|
+
sheet: number;
|
|
64
|
+
sheetName: string;
|
|
65
|
+
ref: string;
|
|
66
|
+
row: number;
|
|
67
|
+
col: number;
|
|
68
|
+
/** The text that matched */
|
|
69
|
+
text: string;
|
|
33
70
|
}
|
|
34
71
|
|
|
35
72
|
/** Cells changed from the grid (typing, deleting, pasting, cutting) or by undo and redo */
|
|
@@ -39,9 +76,9 @@ export interface ChangeDetail {
|
|
|
39
76
|
index: number;
|
|
40
77
|
/** Range changed, e.g. `B3` or `A1:C4` */
|
|
41
78
|
range: string;
|
|
42
|
-
/** Every cell of `range` with its new value (`null` when empty) */
|
|
79
|
+
/** Every cell of `range` with its new value (`null` when empty); empty above 200 000 cells */
|
|
43
80
|
cells: { ref: string; value: CellValue }[];
|
|
44
|
-
source: 'edit' | 'clear' | 'paste' | 'cut' | 'undo' | 'redo';
|
|
81
|
+
source: 'edit' | 'clear' | 'paste' | 'cut' | 'move' | 'insert' | 'delete' | 'fill' | 'replace' | 'undo' | 'redo';
|
|
45
82
|
}
|
|
46
83
|
|
|
47
84
|
export interface XlsxEditorEventMap {
|
|
@@ -49,7 +86,7 @@ export interface XlsxEditorEventMap {
|
|
|
49
86
|
sheetchange: CustomEvent<{ index: number; name: string }>;
|
|
50
87
|
selectionchange: CustomEvent<Selection>;
|
|
51
88
|
change: CustomEvent<ChangeDetail>;
|
|
52
|
-
/** A change the engine refused (e.g.
|
|
89
|
+
/** A change the engine refused (e.g. moving a row through merged cells) */
|
|
53
90
|
error: CustomEvent<{ error: Error }>;
|
|
54
91
|
}
|
|
55
92
|
|
|
@@ -65,10 +102,55 @@ export class XlsxEditor extends EventTarget {
|
|
|
65
102
|
readonly sheetIndex: number;
|
|
66
103
|
readonly sheetName: string | null;
|
|
67
104
|
readonly selection: Selection | null;
|
|
105
|
+
/** Count, sum, average, smallest and largest of the selected cells; `null` when they are empty */
|
|
106
|
+
readonly selectionStats: SelectionStats | null;
|
|
68
107
|
/** Without editing from the grid (selecting and copying still work) */
|
|
69
108
|
readOnly: boolean;
|
|
70
109
|
readonly canUndo: boolean;
|
|
71
110
|
readonly canRedo: boolean;
|
|
111
|
+
/**
|
|
112
|
+
* Moves `count` whole rows from `first` before the row `before` (numbered as they are now), as
|
|
113
|
+
* one undo step, and selects them. False when nothing changed: it stays where it is, it is
|
|
114
|
+
* read-only, or the engine refused (an `error` event says why).
|
|
115
|
+
*/
|
|
116
|
+
moveRows(first: number, count: number, before: number): boolean;
|
|
117
|
+
/** Moves whole columns, by number (see `moveRows`) */
|
|
118
|
+
moveColumns(first: number, count: number, before: number): boolean;
|
|
119
|
+
/** Inserts rows before the row `before`, as one undo step, and selects them; false if refused */
|
|
120
|
+
insertRows(before: number, count?: number): boolean;
|
|
121
|
+
/** Inserts columns, by number (see `insertRows`) */
|
|
122
|
+
insertColumns(before: number, count?: number): boolean;
|
|
123
|
+
/** Deletes rows from `first`, as one undo step; false if refused */
|
|
124
|
+
deleteRows(first: number, count?: number): boolean;
|
|
125
|
+
/** Deletes columns, by number (see `deleteRows`) */
|
|
126
|
+
deleteColumns(first: number, count?: number): boolean;
|
|
127
|
+
/** Fits columns to their content, as one undo step; false if they are empty, read-only or refused. Needs the editor: it measures the text as drawn */
|
|
128
|
+
autofitColumns(first: number, count?: number): boolean;
|
|
129
|
+
/** Fits rows to their content (empty ones go back to the automatic height), as one undo step; false if read-only or refused */
|
|
130
|
+
autofitRows(first: number, count?: number): boolean;
|
|
131
|
+
/** Hides `count` rows from `first`, as one undo step */
|
|
132
|
+
hideRows(first: number, count?: number): boolean;
|
|
133
|
+
/** Shows hidden rows among the `count` from `first` */
|
|
134
|
+
showRows(first: number, count?: number): boolean;
|
|
135
|
+
/** Hides columns, by number */
|
|
136
|
+
hideColumns(first: number, count?: number): boolean;
|
|
137
|
+
/** Shows hidden columns, by number */
|
|
138
|
+
showColumns(first: number, count?: number): boolean;
|
|
139
|
+
/**
|
|
140
|
+
* Fills `target` (`A1:A20`) from the selected cells, which must be at one of its ends, as one
|
|
141
|
+
* undo step, and selects it. False if refused.
|
|
142
|
+
*/
|
|
143
|
+
fill(target: string, options?: { series?: boolean }): boolean;
|
|
144
|
+
/** Where a text is, row by row: `*` is any text, `?` one character and `~` takes them literally */
|
|
145
|
+
find(query: string, options?: FindOptions): FindHit[];
|
|
146
|
+
/**
|
|
147
|
+
* Replaces a text in cells with text and in formulas (never in numbers or dates), as one undo
|
|
148
|
+
* step; returns how many cells changed. Formatted runs of a rich text become plain.
|
|
149
|
+
*/
|
|
150
|
+
replaceAll(query: string, replacement: string, options?: Omit<FindOptions, 'inFormulas'>): number;
|
|
151
|
+
/** Opens the search panel, with the row to replace when asked */
|
|
152
|
+
openFind(options?: { replace?: boolean }): void;
|
|
153
|
+
closeFind(): void;
|
|
72
154
|
/** Undoes the last change; false when there is nothing to undo */
|
|
73
155
|
undo(): boolean;
|
|
74
156
|
/** Redoes the last undone change */
|
|
@@ -105,7 +187,7 @@ export class XlsxEditor extends EventTarget {
|
|
|
105
187
|
addEventListener(type: string, listener: EventListenerOrEventListenerObject | null, options?: boolean | AddEventListenerOptions): void;
|
|
106
188
|
}
|
|
107
189
|
|
|
108
|
-
/** `<xlsx-editor src="…" locale zoom sheet tabs formula-bar readonly wasm-url blank quiet>` */
|
|
190
|
+
/** `<xlsx-editor src="…" locale zoom sheet tabs formula-bar status-bar readonly wasm-url blank quiet>` */
|
|
109
191
|
export class XlsxEditorElement extends HTMLElement {
|
|
110
192
|
/** The editor, once the `ready` event fired */
|
|
111
193
|
editor: XlsxEditor | null;
|
package/src/style.css
CHANGED
|
@@ -27,7 +27,8 @@ xlsx-editor {
|
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
.xlsx-editor-bar[hidden],
|
|
30
|
-
.xlsx-editor-tabs[hidden]
|
|
30
|
+
.xlsx-editor-tabs[hidden],
|
|
31
|
+
.xlsx-editor-status[hidden] {
|
|
31
32
|
display: none;
|
|
32
33
|
}
|
|
33
34
|
|
|
@@ -159,3 +160,155 @@ xlsx-editor {
|
|
|
159
160
|
opacity: 0.5;
|
|
160
161
|
cursor: default;
|
|
161
162
|
}
|
|
163
|
+
|
|
164
|
+
/* Below the tabs: what went wrong on the left, the sum and average of the selection on the right */
|
|
165
|
+
.xlsx-editor-status {
|
|
166
|
+
display: flex;
|
|
167
|
+
align-items: center;
|
|
168
|
+
gap: 1.5em;
|
|
169
|
+
min-height: 22px;
|
|
170
|
+
padding: 0 10px;
|
|
171
|
+
border-top: 1px solid var(--xlsx-border, #d1d5db);
|
|
172
|
+
background: var(--xlsx-tabs-bg, #f3f4f6);
|
|
173
|
+
font-size: 12px;
|
|
174
|
+
color: #4b5563;
|
|
175
|
+
white-space: nowrap;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
.xlsx-editor-status-message {
|
|
179
|
+
flex: 1 1 auto;
|
|
180
|
+
min-width: 0;
|
|
181
|
+
overflow: hidden;
|
|
182
|
+
text-overflow: ellipsis;
|
|
183
|
+
color: #b91c1c;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
.xlsx-editor-status-stats {
|
|
187
|
+
flex: 0 0 auto;
|
|
188
|
+
display: flex;
|
|
189
|
+
gap: 1.25em;
|
|
190
|
+
font-variant-numeric: tabular-nums;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/* Right click on cells and headers */
|
|
194
|
+
.xlsx-editor-menu {
|
|
195
|
+
position: fixed;
|
|
196
|
+
z-index: 1000;
|
|
197
|
+
display: flex;
|
|
198
|
+
flex-direction: column;
|
|
199
|
+
min-width: 14rem;
|
|
200
|
+
padding: 4px;
|
|
201
|
+
background: #ffffff;
|
|
202
|
+
border: 1px solid var(--xlsx-border, #d1d5db);
|
|
203
|
+
border-radius: var(--xlsx-radius, 6px);
|
|
204
|
+
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.18);
|
|
205
|
+
font: 13px/1.4 system-ui, -apple-system, "Segoe UI", sans-serif;
|
|
206
|
+
color: #1f2937;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
.xlsx-editor-menu[hidden] {
|
|
210
|
+
display: none;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
.xlsx-editor-menu-item {
|
|
214
|
+
display: flex;
|
|
215
|
+
justify-content: space-between;
|
|
216
|
+
gap: 2rem;
|
|
217
|
+
padding: 5px 10px;
|
|
218
|
+
border: 0;
|
|
219
|
+
border-radius: 4px;
|
|
220
|
+
background: transparent;
|
|
221
|
+
font: inherit;
|
|
222
|
+
color: inherit;
|
|
223
|
+
text-align: left;
|
|
224
|
+
white-space: nowrap;
|
|
225
|
+
cursor: pointer;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
.xlsx-editor-menu-item:hover,
|
|
229
|
+
.xlsx-editor-menu-item:focus-visible {
|
|
230
|
+
outline: none;
|
|
231
|
+
background: rgba(33, 115, 70, 0.12);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
.xlsx-editor-menu-keys {
|
|
235
|
+
color: #6b7280;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
.xlsx-editor-menu-line {
|
|
239
|
+
height: 1px;
|
|
240
|
+
margin: 4px 6px;
|
|
241
|
+
background: var(--xlsx-border, #d1d5db);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/* Search panel, over the top right corner of the grid */
|
|
245
|
+
.xlsx-editor-find {
|
|
246
|
+
position: absolute;
|
|
247
|
+
z-index: 3;
|
|
248
|
+
top: 6px;
|
|
249
|
+
right: 22px;
|
|
250
|
+
display: flex;
|
|
251
|
+
flex-direction: column;
|
|
252
|
+
gap: 6px;
|
|
253
|
+
width: min(22rem, calc(100% - 44px));
|
|
254
|
+
padding: 8px;
|
|
255
|
+
background: #ffffff;
|
|
256
|
+
border: 1px solid var(--xlsx-border, #d1d5db);
|
|
257
|
+
border-radius: var(--xlsx-radius, 6px);
|
|
258
|
+
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.18);
|
|
259
|
+
font-size: 12px;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
.xlsx-editor-find[hidden],
|
|
263
|
+
.xlsx-editor-find-row[hidden] {
|
|
264
|
+
display: none;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
.xlsx-editor-find-row {
|
|
268
|
+
display: flex;
|
|
269
|
+
align-items: center;
|
|
270
|
+
flex-wrap: wrap;
|
|
271
|
+
gap: 6px;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
.xlsx-editor-find input[type="text"] {
|
|
275
|
+
flex: 1 1 6rem;
|
|
276
|
+
min-width: 0;
|
|
277
|
+
padding: 3px 6px;
|
|
278
|
+
border: 1px solid var(--xlsx-border, #d1d5db);
|
|
279
|
+
border-radius: 4px;
|
|
280
|
+
font: inherit;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
.xlsx-editor-find input[type="text"]:focus {
|
|
284
|
+
outline: 2px solid var(--xlsx-accent, #217346);
|
|
285
|
+
outline-offset: -1px;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
.xlsx-editor-find button,
|
|
289
|
+
.xlsx-editor-find select {
|
|
290
|
+
padding: 3px 7px;
|
|
291
|
+
border: 1px solid var(--xlsx-border, #d1d5db);
|
|
292
|
+
border-radius: 4px;
|
|
293
|
+
background: #ffffff;
|
|
294
|
+
font: inherit;
|
|
295
|
+
color: inherit;
|
|
296
|
+
cursor: pointer;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
.xlsx-editor-find button:hover {
|
|
300
|
+
background: rgba(33, 115, 70, 0.12);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
.xlsx-editor-find label {
|
|
304
|
+
display: inline-flex;
|
|
305
|
+
align-items: center;
|
|
306
|
+
gap: 3px;
|
|
307
|
+
cursor: pointer;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
.xlsx-editor-find-count {
|
|
311
|
+
margin-left: auto;
|
|
312
|
+
color: #4b5563;
|
|
313
|
+
font-variant-numeric: tabular-nums;
|
|
314
|
+
}
|