@lnsy/data-table 0.1.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/LICENSE +26 -0
- package/README.md +229 -0
- package/package.json +77 -0
- package/src/data-table.css +327 -0
- package/src/file-io.js +203 -0
- package/src/formula.js +632 -0
- package/src/index.js +20 -0
- package/src/table-component.js +1031 -0
- package/src/wikilinks.js +71 -0
- package/styles/fonts.css +111 -0
- package/styles/variables.css +151 -0
package/src/wikilinks.js
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wikilinks
|
|
3
|
+
*
|
|
4
|
+
* Renders `[[Page Name]]` references inside cell values as clickable links.
|
|
5
|
+
*
|
|
6
|
+
* - If the component has a `wikilink-base-url` attribute, links point to
|
|
7
|
+
* `${base}${encodeURIComponent(target)}`.
|
|
8
|
+
* - Otherwise clicking a link dispatches a bubbling `wikilink-navigate`
|
|
9
|
+
* CustomEvent with `{ detail: { target, cell: { row, col } } }`, which the
|
|
10
|
+
* host application can handle (routing, filtering, opening a panel…).
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const WIKILINK_RE = /\[\[([^\]|]+)(?:\|([^\]]+))?\]\]/g;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Render a cell value into a DocumentFragment.
|
|
17
|
+
* Plain text is inserted as text nodes; `[[target]]` and
|
|
18
|
+
* `[[target|label]]` become anchor elements.
|
|
19
|
+
*/
|
|
20
|
+
export function renderCellValue(text) {
|
|
21
|
+
const fragment = document.createDocumentFragment();
|
|
22
|
+
const value = text === null || text === undefined ? '' : String(text);
|
|
23
|
+
|
|
24
|
+
let lastIndex = 0;
|
|
25
|
+
WIKILINK_RE.lastIndex = 0;
|
|
26
|
+
let match;
|
|
27
|
+
|
|
28
|
+
while ((match = WIKILINK_RE.exec(value)) !== null) {
|
|
29
|
+
if (match.index > lastIndex) {
|
|
30
|
+
fragment.appendChild(
|
|
31
|
+
document.createTextNode(value.slice(lastIndex, match.index))
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
fragment.appendChild(createWikilink(match[1].trim(), match[2]?.trim()));
|
|
35
|
+
lastIndex = match.index + match[0].length;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (lastIndex < value.length) {
|
|
39
|
+
fragment.appendChild(document.createTextNode(value.slice(lastIndex)));
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return fragment;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function createWikilink(target, label) {
|
|
46
|
+
const link = document.createElement('a');
|
|
47
|
+
link.className = 'wikilink';
|
|
48
|
+
link.textContent = label || target;
|
|
49
|
+
link.dataset.target = target;
|
|
50
|
+
link.href = '#';
|
|
51
|
+
link.addEventListener('click', (event) => {
|
|
52
|
+
event.preventDefault();
|
|
53
|
+
event.stopPropagation();
|
|
54
|
+
const baseUrl = link.closest('[data-wikilink-base]')?.dataset.wikilinkBase;
|
|
55
|
+
if (baseUrl) {
|
|
56
|
+
window.open(`${baseUrl}${encodeURIComponent(target)}`, '_blank');
|
|
57
|
+
}
|
|
58
|
+
link.dispatchEvent(new CustomEvent('wikilink-navigate', {
|
|
59
|
+
bubbles: true,
|
|
60
|
+
composed: true,
|
|
61
|
+
detail: { target },
|
|
62
|
+
}));
|
|
63
|
+
});
|
|
64
|
+
return link;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** True when the raw value contains at least one wikilink. */
|
|
68
|
+
export function containsWikilink(text) {
|
|
69
|
+
WIKILINK_RE.lastIndex = 0;
|
|
70
|
+
return WIKILINK_RE.test(String(text ?? ''));
|
|
71
|
+
}
|
package/styles/fonts.css
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Theme Fonts — mirrors the @font-face declarations in
|
|
3
|
+
* aaron-rss styles/dataroom-theme.css so <data-table> renders with the
|
|
4
|
+
* same families as the source theme.
|
|
5
|
+
*
|
|
6
|
+
* - Roboto Condensed: headings and interface chrome (--font-header/--font-ui)
|
|
7
|
+
* - EB Garamond: body text (--font-body)
|
|
8
|
+
* - Atkynson Hyperlegible Mono: code and grid data (--font-mono)
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/* EB Garamond — variable font, weight range 400–800 */
|
|
12
|
+
|
|
13
|
+
@font-face {
|
|
14
|
+
font-family: 'EB Garamond';
|
|
15
|
+
font-style: normal;
|
|
16
|
+
font-weight: 400 800;
|
|
17
|
+
font-display: swap;
|
|
18
|
+
src: url('../assets/fonts/EBGaramond-VariableFont_wght.ttf') format('truetype-variations');
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
@font-face {
|
|
22
|
+
font-family: 'EB Garamond';
|
|
23
|
+
font-style: italic;
|
|
24
|
+
font-weight: 400 800;
|
|
25
|
+
font-display: swap;
|
|
26
|
+
src: url('../assets/fonts/EBGaramond-Italic-VariableFont_wght.ttf') format('truetype-variations');
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/* Roboto Condensed — variable font, weight range 100–900 */
|
|
30
|
+
|
|
31
|
+
@font-face {
|
|
32
|
+
font-family: 'Roboto Condensed';
|
|
33
|
+
font-style: normal;
|
|
34
|
+
font-weight: 100 900;
|
|
35
|
+
font-display: swap;
|
|
36
|
+
src: url('../assets/fonts/RobotoCondensed-VariableFont_wght.ttf') format('truetype-variations');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
@font-face {
|
|
40
|
+
font-family: 'Roboto Condensed';
|
|
41
|
+
font-style: italic;
|
|
42
|
+
font-weight: 100 900;
|
|
43
|
+
font-display: swap;
|
|
44
|
+
src: url('../assets/fonts/RobotoCondensed-Italic-VariableFont_wght.ttf') format('truetype-variations');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/* Atkynson Hyperlegible Mono — static cuts, weights 300/400/500/700 */
|
|
48
|
+
|
|
49
|
+
@font-face {
|
|
50
|
+
font-family: 'Atkynson Hyperlegible Mono';
|
|
51
|
+
font-style: normal;
|
|
52
|
+
font-weight: 300;
|
|
53
|
+
font-display: swap;
|
|
54
|
+
src: url('../assets/fonts/AtkinsonHyperlegibleMono/AtkynsonMonoNerdFont-Light.otf') format('opentype');
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
@font-face {
|
|
58
|
+
font-family: 'Atkynson Hyperlegible Mono';
|
|
59
|
+
font-style: italic;
|
|
60
|
+
font-weight: 300;
|
|
61
|
+
font-display: swap;
|
|
62
|
+
src: url('../assets/fonts/AtkinsonHyperlegibleMono/AtkynsonMonoNerdFont-LightItalic.otf') format('opentype');
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
@font-face {
|
|
66
|
+
font-family: 'Atkynson Hyperlegible Mono';
|
|
67
|
+
font-style: normal;
|
|
68
|
+
font-weight: 400;
|
|
69
|
+
font-display: swap;
|
|
70
|
+
src: url('../assets/fonts/AtkinsonHyperlegibleMono/AtkynsonMonoNerdFont-Regular.otf') format('opentype');
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
@font-face {
|
|
74
|
+
font-family: 'Atkynson Hyperlegible Mono';
|
|
75
|
+
font-style: italic;
|
|
76
|
+
font-weight: 400;
|
|
77
|
+
font-display: swap;
|
|
78
|
+
src: url('../assets/fonts/AtkinsonHyperlegibleMono/AtkynsonMonoNerdFont-Italic.otf') format('opentype');
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
@font-face {
|
|
82
|
+
font-family: 'Atkynson Hyperlegible Mono';
|
|
83
|
+
font-style: normal;
|
|
84
|
+
font-weight: 500;
|
|
85
|
+
font-display: swap;
|
|
86
|
+
src: url('../assets/fonts/AtkinsonHyperlegibleMono/AtkynsonMonoNerdFont-Medium.otf') format('opentype');
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
@font-face {
|
|
90
|
+
font-family: 'Atkynson Hyperlegible Mono';
|
|
91
|
+
font-style: italic;
|
|
92
|
+
font-weight: 500;
|
|
93
|
+
font-display: swap;
|
|
94
|
+
src: url('../assets/fonts/AtkinsonHyperlegibleMono/AtkynsonMonoNerdFont-MediumItalic.otf') format('opentype');
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
@font-face {
|
|
98
|
+
font-family: 'Atkynson Hyperlegible Mono';
|
|
99
|
+
font-style: normal;
|
|
100
|
+
font-weight: 700;
|
|
101
|
+
font-display: swap;
|
|
102
|
+
src: url('../assets/fonts/AtkinsonHyperlegibleMono/AtkynsonMonoNerdFont-Bold.otf') format('opentype');
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
@font-face {
|
|
106
|
+
font-family: 'Atkynson Hyperlegible Mono';
|
|
107
|
+
font-style: italic;
|
|
108
|
+
font-weight: 700;
|
|
109
|
+
font-display: swap;
|
|
110
|
+
src: url('../assets/fonts/AtkinsonHyperlegibleMono/AtkynsonMonoNerdFont-BoldItalic.otf') format('opentype');
|
|
111
|
+
}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Theme variables — mirrors aaron-rss styles/dataroom-theme.css so
|
|
3
|
+
<data-table> matches the source theme exactly: warm cream paper, warm
|
|
4
|
+
grey ink, dark-red accents, letterpress headings — with a matching dark
|
|
5
|
+
scheme via `prefers-color-scheme`.
|
|
6
|
+
|
|
7
|
+
The color, shadow and font values below are kept in sync with the
|
|
8
|
+
source theme. The "data-table extensions" section at the end defines
|
|
9
|
+
the few component-specific tokens the source theme doesn't have.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
:root {
|
|
13
|
+
color-scheme: light;
|
|
14
|
+
|
|
15
|
+
/* Colors */
|
|
16
|
+
--foreground: #1a1812;
|
|
17
|
+
--background: #f2ece2;
|
|
18
|
+
--accent: #9f1113;
|
|
19
|
+
--secondary: #6b0c0e;
|
|
20
|
+
--surface: #f2ece2;
|
|
21
|
+
--surface-alt: #d2cec0;
|
|
22
|
+
--muted: #6b6759;
|
|
23
|
+
--border: rgba(0,0,0,0.2);
|
|
24
|
+
--highlight: #9f1113;
|
|
25
|
+
--neutral-1: #6b6759;
|
|
26
|
+
--neutral-2: #8f8b7d;
|
|
27
|
+
--neutral-3: #f2ece2;
|
|
28
|
+
--foreground-dim: #000000;
|
|
29
|
+
--background-light: #ffffff;
|
|
30
|
+
--background-dark: #e2d5bf;
|
|
31
|
+
--highlight-bright: #b00002;
|
|
32
|
+
--highlight-dim: #8d2324;
|
|
33
|
+
--secondary-bright: #740305;
|
|
34
|
+
--secondary-dim: #621517;
|
|
35
|
+
--neutral-1-lighter: #878270;
|
|
36
|
+
--neutral-3-darker: #e2d5bf;
|
|
37
|
+
--accent-red: #9f1111;
|
|
38
|
+
--accent-green: #119f11;
|
|
39
|
+
--accent-blue: #11119f;
|
|
40
|
+
--accent-yellow: #9f9f11;
|
|
41
|
+
--accent-purple: #70119f;
|
|
42
|
+
--accent-cyan: #119f9f;
|
|
43
|
+
|
|
44
|
+
/* Semantic aliases — referenced directly by the component styles */
|
|
45
|
+
--color-heading: var(--foreground);
|
|
46
|
+
--color-link: var(--accent);
|
|
47
|
+
--color-link-hover: var(--secondary);
|
|
48
|
+
|
|
49
|
+
/* Shadows
|
|
50
|
+
Letterpress text effect on body text, plus bevel-style box
|
|
51
|
+
shadows for dialogs and inset-style for form inputs. */
|
|
52
|
+
--text-shadow:
|
|
53
|
+
-0.5px -0.5px 0 rgba(0, 0, 0, 0.20),
|
|
54
|
+
0.5px 0.5px 0 rgba(255, 255, 255, 0.3);
|
|
55
|
+
--box-shadow:
|
|
56
|
+
0.5px 0.5px 0 rgba(0, 0, 0, 0.40),
|
|
57
|
+
-0.5px -0.5px 0 rgba(255, 255, 255, 0.7);
|
|
58
|
+
--box-shadow-inset:
|
|
59
|
+
inset 0.5px 0.5px 1px rgba(0, 0, 0, 0.20),
|
|
60
|
+
inset -0.5px -0.5px 3px 1px rgba(255, 255, 255, 0.6);
|
|
61
|
+
|
|
62
|
+
/* Fonts */
|
|
63
|
+
--font-header: 'Roboto Condensed', sans-serif;
|
|
64
|
+
--font-ui: 'Roboto Condensed', sans-serif;
|
|
65
|
+
--font-mono: 'Atkynson Hyperlegible Mono', monospace;
|
|
66
|
+
--font-body: 'EB Garamond', serif;
|
|
67
|
+
--font-weight-h1: 400;
|
|
68
|
+
--font-weight-h2: 400;
|
|
69
|
+
--font-weight-h3: 400;
|
|
70
|
+
--font-weight-body: 400;
|
|
71
|
+
--font-size-h1: 38.4px;
|
|
72
|
+
--font-size-h2: 28.8px;
|
|
73
|
+
--font-size-h3: 21.6px;
|
|
74
|
+
--font-size-body: 21.2px;
|
|
75
|
+
|
|
76
|
+
/* ── data-table extensions ─────────────────────────────────
|
|
77
|
+
Component-specific tokens the source theme doesn't define.
|
|
78
|
+
The selection tint is the accent at low opacity, re-tinted for
|
|
79
|
+
the dark scheme's pale accent. */
|
|
80
|
+
|
|
81
|
+
--font-weight-normal: 400;
|
|
82
|
+
--font-weight-semibold: 600;
|
|
83
|
+
--font-weight-bold: 700;
|
|
84
|
+
|
|
85
|
+
--selection-bg: rgba(159, 17, 19, 0.10);
|
|
86
|
+
--selection-border: var(--accent);
|
|
87
|
+
|
|
88
|
+
--interactive-hover: rgba(0, 0, 0, 0.05);
|
|
89
|
+
--code-background: var(--background-dark);
|
|
90
|
+
|
|
91
|
+
--grid-cell-height: 1.9rem;
|
|
92
|
+
--grid-header-height: 1.9rem;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/* Dark theme — mirrors the `prefers-color-scheme: dark` block in
|
|
96
|
+
aaron-rss styles/dataroom-theme.css; fonts are identical between the
|
|
97
|
+
two themes so they are not repeated. */
|
|
98
|
+
@media (prefers-color-scheme: dark) {
|
|
99
|
+
:root {
|
|
100
|
+
color-scheme: dark;
|
|
101
|
+
|
|
102
|
+
/* Colors */
|
|
103
|
+
--foreground: #e5c7a9;
|
|
104
|
+
--background: #1b1c25;
|
|
105
|
+
--accent: #ffe6bd;
|
|
106
|
+
--secondary: #8b7963;
|
|
107
|
+
--surface: #1b1c25;
|
|
108
|
+
--surface-alt: #1b1c25;
|
|
109
|
+
--muted: #aa9d7e;
|
|
110
|
+
--border: #e5c7a9;
|
|
111
|
+
--highlight: #d9d3c9;
|
|
112
|
+
--neutral-1: #868074;
|
|
113
|
+
--neutral-2: #404148;
|
|
114
|
+
--neutral-3: #1b1c25;
|
|
115
|
+
--foreground-dim: #bc7b39;
|
|
116
|
+
--background-light: #3b3d51;
|
|
117
|
+
--background-dark: #050608;
|
|
118
|
+
--highlight-bright: #e2d5c0;
|
|
119
|
+
--highlight-dim: #d1d1d1;
|
|
120
|
+
--secondary-bright: #c4ad92;
|
|
121
|
+
--secondary-dim: #ababab;
|
|
122
|
+
--neutral-1-lighter: #9e998f;
|
|
123
|
+
--neutral-3-darker: #050608;
|
|
124
|
+
--accent-red: #d9c9c9;
|
|
125
|
+
--accent-green: #c9d9c9;
|
|
126
|
+
--accent-blue: #c9c9d9;
|
|
127
|
+
--accent-yellow: #d9d9c9;
|
|
128
|
+
--accent-purple: #d4c9d9;
|
|
129
|
+
--accent-cyan: #c9d9d9;
|
|
130
|
+
|
|
131
|
+
/* Shadows
|
|
132
|
+
Letterpress effect adapted for a dark surface: the dark
|
|
133
|
+
shadow is deepened and the light highlight is softened so
|
|
134
|
+
the engrave stays subtle instead of glowing. */
|
|
135
|
+
--text-shadow:
|
|
136
|
+
-0.5px -0.5px 0 rgba(0, 0, 0, 0.60),
|
|
137
|
+
0.5px 0.5px 0 rgba(255, 255, 255, 0.12);
|
|
138
|
+
--box-shadow:
|
|
139
|
+
0.5px 0.5px 0 rgba(0, 0, 0, 0.60),
|
|
140
|
+
-0.5px -0.5px 0 rgba(255, 255, 255, 0.12);
|
|
141
|
+
--box-shadow-inset:
|
|
142
|
+
inset 0.5px 0.5px 1px rgba(0, 0, 0, 0.50),
|
|
143
|
+
inset -0.5px -0.5px 3px 1px rgba(255, 255, 255, 0.10);
|
|
144
|
+
|
|
145
|
+
/* data-table extensions — dark values */
|
|
146
|
+
--selection-bg: rgba(255, 230, 189, 0.14);
|
|
147
|
+
|
|
148
|
+
--interactive-hover: rgba(255, 255, 255, 0.06);
|
|
149
|
+
--code-background: var(--background-dark);
|
|
150
|
+
}
|
|
151
|
+
}
|