drab 1.9.3
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/LICENSE.md +21 -0
- package/README.md +35 -0
- package/dist/components/Chord.svelte +208 -0
- package/dist/components/Chord.svelte.d.ts +74 -0
- package/dist/components/ContextMenu.svelte +93 -0
- package/dist/components/ContextMenu.svelte.d.ts +57 -0
- package/dist/components/CopyButton.svelte +56 -0
- package/dist/components/CopyButton.svelte.d.ts +51 -0
- package/dist/components/DataTable.svelte +128 -0
- package/dist/components/DataTable.svelte.d.ts +73 -0
- package/dist/components/Editor.svelte +366 -0
- package/dist/components/Editor.svelte.d.ts +102 -0
- package/dist/components/FullscreenButton.svelte +81 -0
- package/dist/components/FullscreenButton.svelte.d.ts +63 -0
- package/dist/components/ShareButton.svelte +66 -0
- package/dist/components/ShareButton.svelte.d.ts +57 -0
- package/dist/components/YouTube.svelte +47 -0
- package/dist/components/YouTube.svelte.d.ts +45 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +8 -0
- package/dist/util/buildDocs.d.ts +1 -0
- package/dist/util/buildDocs.js +3 -0
- package/dist/util/documentProps.d.ts +2 -0
- package/dist/util/documentProps.js +86 -0
- package/package.json +74 -0
@@ -0,0 +1,128 @@
|
|
1
|
+
<!--
|
2
|
+
@component
|
3
|
+
|
4
|
+
### DataTable
|
5
|
+
|
6
|
+
Data table to display an array of JS objects. Click a column header to sort.
|
7
|
+
|
8
|
+
@props
|
9
|
+
|
10
|
+
- `ascending` - default sort order
|
11
|
+
- `columns` - table columns, in order
|
12
|
+
- `data` - a list of objects to render in the table
|
13
|
+
- `sortBy` - column to sort by--defaults to first column
|
14
|
+
- `sortedTdClass` - currently sorted td
|
15
|
+
- `sortedThClass` - currently sorted th
|
16
|
+
- `tBodyClass` - tbody class
|
17
|
+
- `tBodyTrClass` - tbody tr class
|
18
|
+
- `tHeadClass` - thead class
|
19
|
+
- `tHeadTrClass` - thead tr class
|
20
|
+
- `tableClass` - table class
|
21
|
+
- `tableId` - table id
|
22
|
+
- `tdClass` - td class
|
23
|
+
- `thClass` - th class
|
24
|
+
|
25
|
+
@example
|
26
|
+
|
27
|
+
```svelte
|
28
|
+
<script>
|
29
|
+
import { DataTable } from "@rossrobino/components";
|
30
|
+
</script>
|
31
|
+
|
32
|
+
<DataTable
|
33
|
+
data={[
|
34
|
+
{ make: "Honda", model: "CR-V", year: 2011, awd: true },
|
35
|
+
{ make: "Volvo", model: "XC-40", year: 2024, awd: true },
|
36
|
+
{ make: "Ferrari", model: "458 Italia", year: 2015, awd: false },
|
37
|
+
{ make: "Chevrolet", model: "Silverado", year: 2022, awd: true },
|
38
|
+
{ make: "Ford", model: "Model A", year: 1931, awd: false },
|
39
|
+
]}
|
40
|
+
sortBy="year"
|
41
|
+
/>
|
42
|
+
```
|
43
|
+
-->
|
44
|
+
|
45
|
+
<script context="module"></script>
|
46
|
+
|
47
|
+
<script>import { onMount } from "svelte";
|
48
|
+
export let data;
|
49
|
+
export let columns = [];
|
50
|
+
if (!columns.length && data[0]) {
|
51
|
+
columns = Object.keys(data[0]);
|
52
|
+
}
|
53
|
+
export let sortBy = columns[0];
|
54
|
+
export let ascending = true;
|
55
|
+
export let tableClass = "";
|
56
|
+
export let tableId = "";
|
57
|
+
export let tHeadClass = "";
|
58
|
+
export let tBodyClass = "";
|
59
|
+
export let tHeadTrClass = "";
|
60
|
+
export let tBodyTrClass = "";
|
61
|
+
export let thClass = "";
|
62
|
+
export let tdClass = "";
|
63
|
+
export let sortedThClass = "";
|
64
|
+
export let sortedTdClass = "";
|
65
|
+
const sort = (column, toggleAscending = true) => {
|
66
|
+
if (column === sortBy && toggleAscending) {
|
67
|
+
ascending = !ascending;
|
68
|
+
} else {
|
69
|
+
ascending = true;
|
70
|
+
}
|
71
|
+
data.sort((a, b) => {
|
72
|
+
const aVal = a[column];
|
73
|
+
const bVal = b[column];
|
74
|
+
if (typeof aVal === "number") {
|
75
|
+
if (ascending) {
|
76
|
+
return aVal - bVal;
|
77
|
+
} else {
|
78
|
+
return bVal - aVal;
|
79
|
+
}
|
80
|
+
} else if (typeof aVal === "string") {
|
81
|
+
const collator = new Intl.Collator();
|
82
|
+
if (ascending) {
|
83
|
+
return collator.compare(aVal, bVal);
|
84
|
+
} else {
|
85
|
+
return collator.compare(bVal, aVal);
|
86
|
+
}
|
87
|
+
} else if (typeof aVal === "boolean") {
|
88
|
+
if (ascending) {
|
89
|
+
return aVal === bVal ? 0 : aVal ? -1 : 1;
|
90
|
+
} else {
|
91
|
+
return aVal === bVal ? 0 : aVal ? 1 : -1;
|
92
|
+
}
|
93
|
+
} else
|
94
|
+
return 0;
|
95
|
+
});
|
96
|
+
data = data;
|
97
|
+
sortBy = column;
|
98
|
+
};
|
99
|
+
onMount(() => {
|
100
|
+
sort(sortBy, false);
|
101
|
+
});
|
102
|
+
</script>
|
103
|
+
|
104
|
+
<table class={tableClass} id={tableId}>
|
105
|
+
<thead class={tHeadClass}>
|
106
|
+
<tr class={tHeadTrClass}>
|
107
|
+
{#each columns as column}
|
108
|
+
<th
|
109
|
+
class="{thClass} {sortBy === column ? sortedThClass : ''}"
|
110
|
+
on:click={() => sort(column)}
|
111
|
+
>
|
112
|
+
{column}
|
113
|
+
</th>
|
114
|
+
{/each}
|
115
|
+
</tr>
|
116
|
+
</thead>
|
117
|
+
<tbody class={tBodyClass}>
|
118
|
+
{#each data as row}
|
119
|
+
<tr class={tBodyTrClass}>
|
120
|
+
{#each columns as column}
|
121
|
+
<td class="{tdClass} {sortBy === column ? sortedTdClass : ''}">
|
122
|
+
{row[column]}
|
123
|
+
</td>
|
124
|
+
{/each}
|
125
|
+
</tr>
|
126
|
+
{/each}
|
127
|
+
</tbody>
|
128
|
+
</table>
|
@@ -0,0 +1,73 @@
|
|
1
|
+
import { SvelteComponent } from "svelte";
|
2
|
+
export type DataTableRow<T> = {
|
3
|
+
[K in keyof T]: T[K];
|
4
|
+
};
|
5
|
+
declare const __propDef: {
|
6
|
+
props: {
|
7
|
+
/** a list of objects to render in the table */ data: DataTableRow<any>[];
|
8
|
+
/** table columns, in order */ columns?: string[] | undefined;
|
9
|
+
/** column to sort by--defaults to first column */ sortBy?: string | undefined;
|
10
|
+
/** default sort order */ ascending?: boolean | undefined;
|
11
|
+
/** table class */ tableClass?: string | undefined;
|
12
|
+
/** table id */ tableId?: string | undefined;
|
13
|
+
/** thead class */ tHeadClass?: string | undefined;
|
14
|
+
/** tbody class */ tBodyClass?: string | undefined;
|
15
|
+
/** thead tr class */ tHeadTrClass?: string | undefined;
|
16
|
+
/** tbody tr class */ tBodyTrClass?: string | undefined;
|
17
|
+
/** th class */ thClass?: string | undefined;
|
18
|
+
/** td class */ tdClass?: string | undefined;
|
19
|
+
/** currently sorted th */ sortedThClass?: string | undefined;
|
20
|
+
/** currently sorted td */ sortedTdClass?: string | undefined;
|
21
|
+
};
|
22
|
+
events: {
|
23
|
+
[evt: string]: CustomEvent<any>;
|
24
|
+
};
|
25
|
+
slots: {};
|
26
|
+
};
|
27
|
+
export type DataTableProps = typeof __propDef.props;
|
28
|
+
export type DataTableEvents = typeof __propDef.events;
|
29
|
+
export type DataTableSlots = typeof __propDef.slots;
|
30
|
+
/**
|
31
|
+
* ### DataTable
|
32
|
+
*
|
33
|
+
* Data table to display an array of JS objects. Click a column header to sort.
|
34
|
+
*
|
35
|
+
* @props
|
36
|
+
*
|
37
|
+
* - `ascending` - default sort order
|
38
|
+
* - `columns` - table columns, in order
|
39
|
+
* - `data` - a list of objects to render in the table
|
40
|
+
* - `sortBy` - column to sort by--defaults to first column
|
41
|
+
* - `sortedTdClass` - currently sorted td
|
42
|
+
* - `sortedThClass` - currently sorted th
|
43
|
+
* - `tBodyClass` - tbody class
|
44
|
+
* - `tBodyTrClass` - tbody tr class
|
45
|
+
* - `tHeadClass` - thead class
|
46
|
+
* - `tHeadTrClass` - thead tr class
|
47
|
+
* - `tableClass` - table class
|
48
|
+
* - `tableId` - table id
|
49
|
+
* - `tdClass` - td class
|
50
|
+
* - `thClass` - th class
|
51
|
+
*
|
52
|
+
* @example
|
53
|
+
*
|
54
|
+
* ```svelte
|
55
|
+
* <script>
|
56
|
+
* import { DataTable } from "@rossrobino/components";
|
57
|
+
* </script>
|
58
|
+
*
|
59
|
+
* <DataTable
|
60
|
+
* data={[
|
61
|
+
* { make: "Honda", model: "CR-V", year: 2011, awd: true },
|
62
|
+
* { make: "Volvo", model: "XC-40", year: 2024, awd: true },
|
63
|
+
* { make: "Ferrari", model: "458 Italia", year: 2015, awd: false },
|
64
|
+
* { make: "Chevrolet", model: "Silverado", year: 2022, awd: true },
|
65
|
+
* { make: "Ford", model: "Model A", year: 1931, awd: false },
|
66
|
+
* ]}
|
67
|
+
* sortBy="year"
|
68
|
+
* />
|
69
|
+
* ```
|
70
|
+
*/
|
71
|
+
export default class DataTable extends SvelteComponent<DataTableProps, DataTableEvents, DataTableSlots> {
|
72
|
+
}
|
73
|
+
export {};
|
@@ -0,0 +1,366 @@
|
|
1
|
+
<!--
|
2
|
+
@component
|
3
|
+
|
4
|
+
### Editor
|
5
|
+
|
6
|
+
Text editor with controls to add elements and keyboard shortcuts.
|
7
|
+
|
8
|
+
@props
|
9
|
+
|
10
|
+
- `buttonClass` - `class` of all the `button` elements
|
11
|
+
- `contentElements` - an array of content elements for the controls
|
12
|
+
- `controlsClass` - `class` of the `div` that wraps the controls
|
13
|
+
- `controlsId` - `id` of the `div` that wraps the controls
|
14
|
+
- `keyPairs` - keys that will auto-close if typed, value is their closing character
|
15
|
+
- `selectionStart` - `selectionStart` value of the text area
|
16
|
+
- `textAreaClass` - `class` of the `textarea` element
|
17
|
+
- `textAreaId` - `id` of the `textarea` element
|
18
|
+
- `textAreaName` - `name` of the `textarea` element
|
19
|
+
- `textAreaPlaceholder` - `placeholder` of the `textarea` element
|
20
|
+
- `textAreaValue` - `value` of the `textarea` element
|
21
|
+
|
22
|
+
@example
|
23
|
+
|
24
|
+
```svelte
|
25
|
+
<script>
|
26
|
+
import { Editor } from "@rossrobino/components";
|
27
|
+
</script>
|
28
|
+
|
29
|
+
<Editor contentElements={[
|
30
|
+
{
|
31
|
+
name: "Bullet",
|
32
|
+
text: "- ",
|
33
|
+
display: "block",
|
34
|
+
icon: "Bullet",
|
35
|
+
},
|
36
|
+
{
|
37
|
+
name: "Asterisk",
|
38
|
+
text: "*",
|
39
|
+
display: "wrap",
|
40
|
+
icon: "Asterisk",
|
41
|
+
key: "i",
|
42
|
+
class: "italic",
|
43
|
+
},
|
44
|
+
{
|
45
|
+
name: "Anchor",
|
46
|
+
text: "[text](href)",
|
47
|
+
display: "inline",
|
48
|
+
icon: "Anchor",
|
49
|
+
key: "[",
|
50
|
+
},
|
51
|
+
]}
|
52
|
+
/>
|
53
|
+
```
|
54
|
+
-->
|
55
|
+
|
56
|
+
<script context="module"></script>
|
57
|
+
|
58
|
+
<script>export let contentElements = [];
|
59
|
+
export let textAreaValue = "";
|
60
|
+
export let textAreaPlaceholder = "";
|
61
|
+
export let textAreaClass = "";
|
62
|
+
export let textAreaId = "";
|
63
|
+
export let textAreaName = "";
|
64
|
+
export let buttonClass = "";
|
65
|
+
export let controlsClass = "";
|
66
|
+
export let controlsId = "";
|
67
|
+
export let selectionStart = 0;
|
68
|
+
export let keyPairs = {
|
69
|
+
"(": ")",
|
70
|
+
"{": "}",
|
71
|
+
"[": "]",
|
72
|
+
"<": ">",
|
73
|
+
'"': '"',
|
74
|
+
"`": "`"
|
75
|
+
};
|
76
|
+
let textArea;
|
77
|
+
contentElements.forEach((el) => {
|
78
|
+
if (el.display === "wrap")
|
79
|
+
keyPairs[el.text] = el.text;
|
80
|
+
});
|
81
|
+
let openChars = [];
|
82
|
+
const insertChar = (str, char, index) => {
|
83
|
+
return str.slice(0, index) + char + str.slice(index);
|
84
|
+
};
|
85
|
+
const removeChar = (str, index) => {
|
86
|
+
return str.slice(0, index) + str.slice(index + 1);
|
87
|
+
};
|
88
|
+
const insertText = async (el, selectionStart2, selectionEnd) => {
|
89
|
+
if (el.display === "inline") {
|
90
|
+
textAreaValue = `${textAreaValue.slice(0, selectionEnd)}${el.text}${textAreaValue.slice(selectionEnd)}`;
|
91
|
+
} else if (el.display === "wrap") {
|
92
|
+
textAreaValue = insertChar(textAreaValue, el.text, selectionStart2);
|
93
|
+
textAreaValue = insertChar(
|
94
|
+
textAreaValue,
|
95
|
+
keyPairs[el.text],
|
96
|
+
selectionEnd + el.text.length
|
97
|
+
);
|
98
|
+
if (el.text.length < 2)
|
99
|
+
openChars.push(el.text);
|
100
|
+
} else if (el.display === "block") {
|
101
|
+
const { lines, lineNumber } = getLineInfo();
|
102
|
+
if (lines[lineNumber].startsWith(el.text[0])) {
|
103
|
+
lines[lineNumber] = el.text.trim() + lines[lineNumber];
|
104
|
+
} else {
|
105
|
+
lines[lineNumber] = el.text + lines[lineNumber];
|
106
|
+
}
|
107
|
+
textAreaValue = lines.join("\n");
|
108
|
+
}
|
109
|
+
};
|
110
|
+
const setCaretPosition = async (text, selectionStart2, selectionEnd) => {
|
111
|
+
let startPos = 0;
|
112
|
+
let endPos = 0;
|
113
|
+
if (/[a-z]/i.test(text)) {
|
114
|
+
for (let i = selectionEnd; i < textAreaValue.length; i++) {
|
115
|
+
if (textAreaValue[i].match(/[a-z]/i)) {
|
116
|
+
if (!startPos) {
|
117
|
+
startPos = i;
|
118
|
+
} else {
|
119
|
+
endPos = i + 1;
|
120
|
+
}
|
121
|
+
} else if (startPos) {
|
122
|
+
break;
|
123
|
+
}
|
124
|
+
}
|
125
|
+
} else {
|
126
|
+
startPos = selectionStart2 + text.length;
|
127
|
+
endPos = selectionEnd + text.length;
|
128
|
+
}
|
129
|
+
textArea.setSelectionRange(startPos, endPos);
|
130
|
+
textArea.focus();
|
131
|
+
};
|
132
|
+
const addContent = async (el) => {
|
133
|
+
const selectionEnd = textArea.selectionEnd;
|
134
|
+
const selectionStart2 = textArea.selectionStart;
|
135
|
+
await insertText(el, selectionStart2, selectionEnd);
|
136
|
+
setCaretPosition(el.text, selectionStart2, selectionEnd);
|
137
|
+
};
|
138
|
+
const onKeyDown = async (e) => {
|
139
|
+
const resetKeys = ["ArrowUp", "ArrowDown", "Delete"];
|
140
|
+
const nextChar = textAreaValue[textArea.selectionEnd];
|
141
|
+
if (resetKeys.includes(e.key)) {
|
142
|
+
openChars = [];
|
143
|
+
} else if (e.key === "Backspace") {
|
144
|
+
const prevChar = textAreaValue[textArea.selectionStart - 1];
|
145
|
+
if (prevChar in keyPairs && nextChar === keyPairs[prevChar]) {
|
146
|
+
e.preventDefault();
|
147
|
+
const start = textArea.selectionStart - 1;
|
148
|
+
const end = textArea.selectionEnd - 1;
|
149
|
+
textAreaValue = removeChar(textAreaValue, start);
|
150
|
+
textAreaValue = removeChar(textAreaValue, end);
|
151
|
+
setTimeout(() => {
|
152
|
+
textArea.setSelectionRange(start, end);
|
153
|
+
}, 0);
|
154
|
+
openChars.pop();
|
155
|
+
}
|
156
|
+
if (prevChar === "\n" && textArea.selectionStart === textArea.selectionEnd) {
|
157
|
+
e.preventDefault();
|
158
|
+
const newPos = textArea.selectionStart - 1;
|
159
|
+
const { lineNumber } = getLineInfo();
|
160
|
+
correctFollowing(lineNumber, true);
|
161
|
+
textAreaValue = removeChar(textAreaValue, newPos);
|
162
|
+
setTimeout(async () => {
|
163
|
+
textArea.setSelectionRange(newPos, newPos);
|
164
|
+
}, 0);
|
165
|
+
}
|
166
|
+
} else if (e.key === "Tab") {
|
167
|
+
if (getCurrentBlock() % 2 !== 0) {
|
168
|
+
e.preventDefault();
|
169
|
+
await addContent({
|
170
|
+
display: "inline",
|
171
|
+
text: " ",
|
172
|
+
icon: "tab",
|
173
|
+
name: "tab"
|
174
|
+
});
|
175
|
+
}
|
176
|
+
} else if (e.key === "Enter") {
|
177
|
+
const { lines, lineNumber, columnNumber } = getLineInfo();
|
178
|
+
const currentLine = lines[lineNumber];
|
179
|
+
let repeat = getRepeat(currentLine);
|
180
|
+
const original = repeat;
|
181
|
+
const num = startsWithNumberAndPeriod(repeat);
|
182
|
+
if (num)
|
183
|
+
repeat = `${num + 1}. `;
|
184
|
+
if (repeat && original.length < columnNumber) {
|
185
|
+
e.preventDefault();
|
186
|
+
if (num)
|
187
|
+
correctFollowing(lineNumber);
|
188
|
+
await addContent({
|
189
|
+
display: "inline",
|
190
|
+
text: `
|
191
|
+
${repeat}`,
|
192
|
+
icon: "",
|
193
|
+
name: ""
|
194
|
+
});
|
195
|
+
} else if (repeat && original.length === columnNumber) {
|
196
|
+
e.preventDefault();
|
197
|
+
const selectionEnd = textArea.selectionEnd;
|
198
|
+
const newPos = selectionEnd - original.length;
|
199
|
+
for (let i = 0; i < original.length; i++) {
|
200
|
+
textAreaValue = removeChar(
|
201
|
+
textAreaValue,
|
202
|
+
textArea.selectionEnd - (i + 1)
|
203
|
+
);
|
204
|
+
}
|
205
|
+
setTimeout(async () => {
|
206
|
+
textArea.setSelectionRange(newPos, newPos);
|
207
|
+
textArea.focus();
|
208
|
+
await addContent({
|
209
|
+
display: "inline",
|
210
|
+
text: `
|
211
|
+
`,
|
212
|
+
icon: "",
|
213
|
+
name: ""
|
214
|
+
});
|
215
|
+
}, 0);
|
216
|
+
}
|
217
|
+
} else {
|
218
|
+
const nextCharIsClosing = Object.values(keyPairs).includes(nextChar);
|
219
|
+
const highlighted = textArea.selectionStart !== textArea.selectionEnd;
|
220
|
+
if (e.ctrlKey && e.key) {
|
221
|
+
const matchedEl = contentElements.find((el) => el.key === e.key);
|
222
|
+
if (matchedEl)
|
223
|
+
addContent(matchedEl);
|
224
|
+
} else if (nextCharIsClosing && (nextChar === e.key || e.key === "ArrowRight") && openChars.length && !highlighted) {
|
225
|
+
e.preventDefault();
|
226
|
+
textArea.setSelectionRange(
|
227
|
+
textArea.selectionStart + 1,
|
228
|
+
textArea.selectionEnd + 1
|
229
|
+
);
|
230
|
+
openChars.pop();
|
231
|
+
} else if (e.key in keyPairs) {
|
232
|
+
e.preventDefault();
|
233
|
+
await addContent({
|
234
|
+
display: "wrap",
|
235
|
+
text: e.key,
|
236
|
+
icon: "",
|
237
|
+
name: ""
|
238
|
+
});
|
239
|
+
openChars.push(e.key);
|
240
|
+
}
|
241
|
+
}
|
242
|
+
};
|
243
|
+
const trimSelection = () => {
|
244
|
+
if (textArea.selectionStart !== textArea.selectionEnd) {
|
245
|
+
if (textAreaValue[textArea.selectionStart] === " ") {
|
246
|
+
textArea.setSelectionRange(
|
247
|
+
textArea.selectionStart + 1,
|
248
|
+
textArea.selectionEnd
|
249
|
+
);
|
250
|
+
}
|
251
|
+
if (textAreaValue[textArea.selectionEnd - 1] === " ") {
|
252
|
+
textArea.setSelectionRange(
|
253
|
+
textArea.selectionStart,
|
254
|
+
textArea.selectionEnd - 1
|
255
|
+
);
|
256
|
+
}
|
257
|
+
}
|
258
|
+
};
|
259
|
+
const updateSelectionStart = () => {
|
260
|
+
selectionStart = textArea.selectionStart;
|
261
|
+
};
|
262
|
+
const getLineInfo = () => {
|
263
|
+
const lines = textAreaValue.split("\n");
|
264
|
+
let characterCount = 0;
|
265
|
+
for (let i = 0; i < lines.length; i++) {
|
266
|
+
characterCount++;
|
267
|
+
characterCount += lines[i].length;
|
268
|
+
if (characterCount > textArea.selectionEnd) {
|
269
|
+
return {
|
270
|
+
lines,
|
271
|
+
lineNumber: i,
|
272
|
+
columnNumber: textArea.selectionEnd - (characterCount - lines[i].length - 1)
|
273
|
+
};
|
274
|
+
}
|
275
|
+
}
|
276
|
+
return { lines, lineNumber: 0, columnNumber: 0 };
|
277
|
+
};
|
278
|
+
const getCurrentBlock = () => {
|
279
|
+
const blocks = textAreaValue.split("```");
|
280
|
+
let totalChars = 0;
|
281
|
+
for (const [i, block] of blocks.entries()) {
|
282
|
+
totalChars += block.length + 3;
|
283
|
+
if (textArea.selectionStart < totalChars) {
|
284
|
+
return i;
|
285
|
+
}
|
286
|
+
}
|
287
|
+
return 0;
|
288
|
+
};
|
289
|
+
const getRepeat = (str) => {
|
290
|
+
const blockStrings = [];
|
291
|
+
contentElements.forEach((el) => {
|
292
|
+
if (el.display === "block")
|
293
|
+
blockStrings.push(el.text);
|
294
|
+
});
|
295
|
+
for (let i = 0; i < blockStrings.length; i++) {
|
296
|
+
const repeatString = blockStrings[i];
|
297
|
+
if (str.startsWith(repeatString)) {
|
298
|
+
return repeatString;
|
299
|
+
}
|
300
|
+
}
|
301
|
+
const repeatNum = startsWithNumberAndPeriod(str);
|
302
|
+
if (repeatNum)
|
303
|
+
return `${repeatNum}. `;
|
304
|
+
return "";
|
305
|
+
};
|
306
|
+
const startsWithNumberAndPeriod = (str) => {
|
307
|
+
const result = str.match(/^(\d+)\./);
|
308
|
+
return result ? Number(result[1]) : null;
|
309
|
+
};
|
310
|
+
const correctFollowing = (currentLineNumber, decrement = false) => {
|
311
|
+
const { lines } = getLineInfo();
|
312
|
+
for (let i = currentLineNumber + 1; i < lines.length; i++) {
|
313
|
+
const num = startsWithNumberAndPeriod(lines[i]);
|
314
|
+
if (!num) {
|
315
|
+
break;
|
316
|
+
} else {
|
317
|
+
let newNum;
|
318
|
+
if (decrement) {
|
319
|
+
if (num > 1) {
|
320
|
+
newNum = num - 1;
|
321
|
+
} else {
|
322
|
+
break;
|
323
|
+
}
|
324
|
+
} else {
|
325
|
+
newNum = num + 1;
|
326
|
+
}
|
327
|
+
lines[i] = lines[i].slice(String(num).length);
|
328
|
+
lines[i] = String(newNum) + lines[i];
|
329
|
+
}
|
330
|
+
}
|
331
|
+
textAreaValue = lines.join("\n");
|
332
|
+
};
|
333
|
+
</script>
|
334
|
+
|
335
|
+
<textarea
|
336
|
+
id={textAreaId}
|
337
|
+
class={textAreaClass}
|
338
|
+
name={textAreaName}
|
339
|
+
placeholder={textAreaPlaceholder}
|
340
|
+
on:keydown={onKeyDown}
|
341
|
+
on:keyup={updateSelectionStart}
|
342
|
+
on:dblclick={trimSelection}
|
343
|
+
bind:value={textAreaValue}
|
344
|
+
bind:this={textArea}
|
345
|
+
on:click={() => {
|
346
|
+
openChars = [];
|
347
|
+
updateSelectionStart();
|
348
|
+
}}
|
349
|
+
on:input
|
350
|
+
/>
|
351
|
+
<div id={controlsId} class={controlsClass}>
|
352
|
+
{#each contentElements as el}
|
353
|
+
<button
|
354
|
+
class={el.class ? `${buttonClass} ${el.class}` : buttonClass}
|
355
|
+
on:click={() => addContent(el)}
|
356
|
+
title={el.name}
|
357
|
+
aria-label={el.name}
|
358
|
+
>
|
359
|
+
{#if typeof el.icon !== "string"}
|
360
|
+
<svelte:component this={el.icon} />
|
361
|
+
{:else}
|
362
|
+
{el.icon}
|
363
|
+
{/if}
|
364
|
+
</button>
|
365
|
+
{/each}
|
366
|
+
</div>
|
@@ -0,0 +1,102 @@
|
|
1
|
+
import { SvelteComponent } from "svelte";
|
2
|
+
/**
|
3
|
+
* - `EditorContentElement` to pass into the `contentElements` array prop
|
4
|
+
* - `contentElements` prop creates a list of button controls to insert
|
5
|
+
* text into the `TextAreaElement`
|
6
|
+
*/
|
7
|
+
export interface EditorContentElement {
|
8
|
+
/** name of element */
|
9
|
+
name: string;
|
10
|
+
/** text to add */
|
11
|
+
text: string;
|
12
|
+
/** controls how the text is added */
|
13
|
+
display: "inline" | "block" | "wrap";
|
14
|
+
/** contents of the button */
|
15
|
+
icon: string | ComponentType;
|
16
|
+
/** keyboard shortcut */
|
17
|
+
key?: string;
|
18
|
+
/** class to apply to the specific button */
|
19
|
+
class?: string;
|
20
|
+
}
|
21
|
+
import type { ComponentType } from "svelte";
|
22
|
+
declare const __propDef: {
|
23
|
+
props: {
|
24
|
+
/** an array of content elements for the controls */ contentElements?: EditorContentElement[] | undefined;
|
25
|
+
/** `value` of the `textarea` element */ textAreaValue?: string | undefined;
|
26
|
+
/** `placeholder` of the `textarea` element */ textAreaPlaceholder?: string | undefined;
|
27
|
+
/** `class` of the `textarea` element */ textAreaClass?: string | undefined;
|
28
|
+
/** `id` of the `textarea` element */ textAreaId?: string | undefined;
|
29
|
+
/** `name` of the `textarea` element */ textAreaName?: string | undefined;
|
30
|
+
/** `class` of all the `button` elements */ buttonClass?: string | undefined;
|
31
|
+
/** `class` of the `div` that wraps the controls */ controlsClass?: string | undefined;
|
32
|
+
/** `id` of the `div` that wraps the controls */ controlsId?: string | undefined;
|
33
|
+
/** `selectionStart` value of the text area */ selectionStart?: number | undefined;
|
34
|
+
/** keys that will auto-close if typed, value is their closing character */ keyPairs?: {
|
35
|
+
[key: string]: string;
|
36
|
+
} | undefined;
|
37
|
+
};
|
38
|
+
events: {
|
39
|
+
input: Event;
|
40
|
+
} & {
|
41
|
+
[evt: string]: CustomEvent<any>;
|
42
|
+
};
|
43
|
+
slots: {};
|
44
|
+
};
|
45
|
+
export type EditorProps = typeof __propDef.props;
|
46
|
+
export type EditorEvents = typeof __propDef.events;
|
47
|
+
export type EditorSlots = typeof __propDef.slots;
|
48
|
+
/**
|
49
|
+
* ### Editor
|
50
|
+
*
|
51
|
+
* Text editor with controls to add elements and keyboard shortcuts.
|
52
|
+
*
|
53
|
+
* @props
|
54
|
+
*
|
55
|
+
* - `buttonClass` - `class` of all the `button` elements
|
56
|
+
* - `contentElements` - an array of content elements for the controls
|
57
|
+
* - `controlsClass` - `class` of the `div` that wraps the controls
|
58
|
+
* - `controlsId` - `id` of the `div` that wraps the controls
|
59
|
+
* - `keyPairs` - keys that will auto-close if typed, value is their closing character
|
60
|
+
* - `selectionStart` - `selectionStart` value of the text area
|
61
|
+
* - `textAreaClass` - `class` of the `textarea` element
|
62
|
+
* - `textAreaId` - `id` of the `textarea` element
|
63
|
+
* - `textAreaName` - `name` of the `textarea` element
|
64
|
+
* - `textAreaPlaceholder` - `placeholder` of the `textarea` element
|
65
|
+
* - `textAreaValue` - `value` of the `textarea` element
|
66
|
+
*
|
67
|
+
* @example
|
68
|
+
*
|
69
|
+
* ```svelte
|
70
|
+
* <script>
|
71
|
+
* import { Editor } from "@rossrobino/components";
|
72
|
+
* </script>
|
73
|
+
*
|
74
|
+
* <Editor contentElements={[
|
75
|
+
* {
|
76
|
+
* name: "Bullet",
|
77
|
+
* text: "- ",
|
78
|
+
* display: "block",
|
79
|
+
* icon: "Bullet",
|
80
|
+
* },
|
81
|
+
* {
|
82
|
+
* name: "Asterisk",
|
83
|
+
* text: "*",
|
84
|
+
* display: "wrap",
|
85
|
+
* icon: "Asterisk",
|
86
|
+
* key: "i",
|
87
|
+
* class: "italic",
|
88
|
+
* },
|
89
|
+
* {
|
90
|
+
* name: "Anchor",
|
91
|
+
* text: "[text](href)",
|
92
|
+
* display: "inline",
|
93
|
+
* icon: "Anchor",
|
94
|
+
* key: "[",
|
95
|
+
* },
|
96
|
+
* ]}
|
97
|
+
* />
|
98
|
+
* ```
|
99
|
+
*/
|
100
|
+
export default class Editor extends SvelteComponent<EditorProps, EditorEvents, EditorSlots> {
|
101
|
+
}
|
102
|
+
export {};
|