@inkra/react 0.1.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 +186 -1
- package/dist/PencilIcon.js +14 -16
- package/dist/PencilLine.js +18 -12
- package/dist/PencilText.js +192 -59
- package/dist/alert-dialog/index.d.ts +64 -0
- package/dist/alert-dialog/index.js +111 -0
- package/dist/audio-player/index.d.ts +60 -0
- package/dist/audio-player/index.js +101 -0
- package/dist/carousel/index.d.ts +51 -0
- package/dist/carousel/index.js +290 -0
- package/dist/choice-grid/index.d.ts +38 -0
- package/dist/choice-grid/index.js +85 -0
- package/dist/color-picker/color.d.ts +15 -0
- package/dist/color-picker/color.js +55 -0
- package/dist/color-picker/index.d.ts +31 -0
- package/dist/color-picker/index.js +69 -0
- package/dist/command/index.d.ts +45 -0
- package/dist/command/index.js +39 -0
- package/dist/context-menu/index.d.ts +76 -0
- package/dist/context-menu/index.js +241 -0
- package/dist/data-table/index.d.ts +47 -0
- package/dist/data-table/index.js +91 -0
- package/dist/date-picker/index.d.ts +50 -0
- package/dist/date-picker/index.js +180 -0
- package/dist/display/ProgressRing.d.ts +6 -0
- package/dist/display/ProgressRing.js +146 -0
- package/dist/display/index.d.ts +27 -0
- package/dist/display/index.js +18 -2
- package/dist/dockable-panel/index.d.ts +41 -0
- package/dist/dockable-panel/index.js +253 -0
- package/dist/dockable-panel/model.d.ts +21 -0
- package/dist/dockable-panel/model.js +61 -0
- package/dist/drawer/index.d.ts +63 -0
- package/dist/drawer/index.js +88 -0
- package/dist/dropdown-menu/index.d.ts +1 -1
- package/dist/dropdown-menu/index.js +3 -2
- package/dist/editable/index.d.ts +53 -0
- package/dist/editable/index.js +162 -0
- package/dist/empty/index.d.ts +21 -0
- package/dist/empty/index.js +23 -0
- package/dist/flip/index.d.ts +49 -0
- package/dist/flip/index.js +138 -0
- package/dist/form/EditablePencilText.js +188 -143
- package/dist/form/Input.js +1 -1
- package/dist/form/Textarea.d.ts +12 -0
- package/dist/form/Textarea.js +83 -4
- package/dist/form/index.d.ts +1 -1
- package/dist/heatmap/index.d.ts +59 -0
- package/dist/heatmap/index.js +248 -0
- package/dist/hotkey/index.d.ts +37 -0
- package/dist/hotkey/index.js +130 -0
- package/dist/hotkey/model.d.ts +27 -0
- package/dist/hotkey/model.js +97 -0
- package/dist/hover-card/index.d.ts +33 -0
- package/dist/hover-card/index.js +141 -0
- package/dist/image-cropper/index.d.ts +82 -0
- package/dist/image-cropper/index.js +261 -0
- package/dist/image-cropper/model.d.ts +42 -0
- package/dist/image-cropper/model.js +94 -0
- package/dist/image-viewer/index.d.ts +79 -0
- package/dist/image-viewer/index.js +220 -0
- package/dist/image-viewer/model.d.ts +9 -0
- package/dist/image-viewer/model.js +18 -0
- package/dist/index.d.ts +47 -0
- package/dist/index.js +47 -0
- package/dist/input-number/index.d.ts +6 -0
- package/dist/input-number/index.js +126 -15
- package/dist/input-number/model.d.ts +13 -0
- package/dist/input-number/model.js +50 -0
- package/dist/internal/cache.d.ts +1 -1
- package/dist/internal/cache.js +1 -1
- package/dist/internal/renderTokens.d.ts +10 -0
- package/dist/internal/renderTokens.js +13 -0
- package/dist/internal/surfaceRenderer.d.ts +14 -6
- package/dist/internal/surfaceRenderer.js +15 -12
- package/dist/internal/textRenderer.d.ts +1 -0
- package/dist/internal/textRenderer.js +33 -7
- package/dist/internal/usePencilSurface.js +16 -8
- package/dist/internal/useSelectionDrawing.js +15 -11
- package/dist/journey-map/MapScale.js +3 -1
- package/dist/journey-map/index.js +3 -1
- package/dist/keypad/index.d.ts +23 -0
- package/dist/keypad/index.js +63 -0
- package/dist/keypad/model.d.ts +20 -0
- package/dist/keypad/model.js +33 -0
- package/dist/layout/index.d.ts +63 -0
- package/dist/layout/index.js +62 -0
- package/dist/log/index.d.ts +45 -0
- package/dist/log/index.js +159 -0
- package/dist/log/model.d.ts +16 -0
- package/dist/log/model.js +13 -0
- package/dist/node-graph/index.d.ts +123 -0
- package/dist/node-graph/index.js +304 -0
- package/dist/number-ticker/index.d.ts +31 -0
- package/dist/number-ticker/index.js +135 -0
- package/dist/number-ticker/model.d.ts +41 -0
- package/dist/number-ticker/model.js +163 -0
- package/dist/number-ticker/precision.d.ts +7 -0
- package/dist/number-ticker/precision.js +134 -0
- package/dist/overflow-list/index.d.ts +39 -0
- package/dist/overflow-list/index.js +230 -0
- package/dist/overflow-list/layout.d.ts +19 -0
- package/dist/overflow-list/layout.js +44 -0
- package/dist/overlay/Overlay.d.ts +1 -1
- package/dist/overlay/Overlay.js +4 -2
- package/dist/overlay/Popover.d.ts +1 -1
- package/dist/overlay/Popover.js +3 -2
- package/dist/overlay/themeBridge.d.ts +12 -1
- package/dist/overlay/themeBridge.js +27 -1
- package/dist/pagination/index.d.ts +23 -0
- package/dist/pagination/index.js +41 -0
- package/dist/pagination/model.d.ts +4 -0
- package/dist/pagination/model.js +42 -0
- package/dist/pan-zoom/index.d.ts +71 -0
- package/dist/pan-zoom/index.js +175 -0
- package/dist/radar/index.d.ts +56 -0
- package/dist/radar/index.js +220 -0
- package/dist/range-slider/index.d.ts +16 -0
- package/dist/range-slider/index.js +22 -0
- package/dist/rating/index.d.ts +28 -0
- package/dist/rating/index.js +127 -0
- package/dist/resizable/index.d.ts +32 -0
- package/dist/resizable/index.js +50 -0
- package/dist/scroll-area/index.d.ts +36 -0
- package/dist/scroll-area/index.js +44 -0
- package/dist/segmented-progress/SegmentedProgress.d.ts +17 -0
- package/dist/segmented-progress/SegmentedProgress.js +42 -0
- package/dist/slider/index.js +1 -1
- package/dist/slot/index.d.ts +24 -0
- package/dist/slot/index.js +29 -0
- package/dist/sortable/index.d.ts +50 -0
- package/dist/sortable/index.js +283 -0
- package/dist/sortable/model.d.ts +30 -0
- package/dist/sortable/model.js +64 -0
- package/dist/sparkline/index.d.ts +19 -0
- package/dist/sparkline/index.js +240 -0
- package/dist/sparkline/model.d.ts +16 -0
- package/dist/sparkline/model.js +28 -0
- package/dist/steps/index.d.ts +16 -0
- package/dist/steps/index.js +37 -0
- package/dist/styles.css +4957 -269
- package/dist/tag-input/index.d.ts +69 -0
- package/dist/tag-input/index.js +325 -0
- package/dist/tag-input/model.d.ts +14 -0
- package/dist/tag-input/model.js +38 -0
- package/dist/time-picker/index.d.ts +15 -0
- package/dist/time-picker/index.js +168 -0
- package/dist/timeline/index.d.ts +14 -0
- package/dist/timeline/index.js +16 -0
- package/dist/toast/index.d.ts +27 -0
- package/dist/toast/index.js +36 -0
- package/dist/toggle-group/index.d.ts +17 -0
- package/dist/toggle-group/index.js +26 -0
- package/dist/tokens.css +10 -9
- package/dist/tooltip/Tooltip.d.ts +1 -1
- package/dist/tooltip/Tooltip.js +3 -2
- package/dist/transfer/index.d.ts +64 -0
- package/dist/transfer/index.js +174 -0
- package/dist/transfer/model.d.ts +12 -0
- package/dist/transfer/model.js +33 -0
- package/dist/tree/index.d.ts +39 -0
- package/dist/tree/index.js +36 -0
- package/dist/tree-select/index.d.ts +59 -0
- package/dist/tree-select/index.js +142 -0
- package/dist/tree-select/model.d.ts +31 -0
- package/dist/tree-select/model.js +135 -0
- package/dist/typewriter/index.d.ts +21 -0
- package/dist/typewriter/index.js +101 -0
- package/dist/typewriter/model.d.ts +7 -0
- package/dist/typewriter/model.js +45 -0
- package/dist/upload/index.d.ts +59 -0
- package/dist/upload/index.js +230 -0
- package/dist/virtual-list/index.d.ts +44 -0
- package/dist/virtual-list/index.js +129 -0
- package/dist/virtual-list/model.d.ts +7 -0
- package/dist/virtual-list/model.js +27 -0
- package/package.json +23 -3
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { useRef } from 'react';
|
|
3
3
|
import { useBrowserLayoutEffect } from '../internal/useBrowserLayoutEffect.js';
|
|
4
|
-
import { effectiveDpr } from '../internal/cache.js';
|
|
4
|
+
import { BoundedCache, canvasByteCost, canvasIsUsable, effectiveDpr } from '../internal/cache.js';
|
|
5
5
|
import { observeCanvasRecovery } from '../internal/recovery.js';
|
|
6
6
|
import { seedToNumber } from '../internal/random.js';
|
|
7
|
-
import { renderPencilText } from '../internal/textRenderer.js';
|
|
8
|
-
import {
|
|
7
|
+
import { pencilLettering, renderPencilText } from '../internal/textRenderer.js';
|
|
8
|
+
import { readRenderToken } from '../internal/renderTokens.js';
|
|
9
9
|
const MAX_EDITABLE_TEXT = 4096;
|
|
10
10
|
const MAX_EDITABLE_LINE = 256;
|
|
11
|
-
const complexScript = /[\u0590-\u08ff\u0900-\u0dff\u0e00-\u0fff\u1780-\u18af\u200c\u200d]/u;
|
|
11
|
+
const complexScript = /[\u0300-\u036f\u0590-\u08ff\u0900-\u0dff\u0e00-\u0fff\u1780-\u18af\u200c\u200d\ufe00-\ufe0f]/u;
|
|
12
12
|
function numeric(style, property) {
|
|
13
13
|
const value = Number.parseFloat(style.getPropertyValue(property));
|
|
14
14
|
return Number.isFinite(value) ? value : 0;
|
|
@@ -17,29 +17,6 @@ function editableText(control) {
|
|
|
17
17
|
const placeholder = control.value.length === 0;
|
|
18
18
|
return { text: placeholder ? control.placeholder : control.value, placeholder };
|
|
19
19
|
}
|
|
20
|
-
function visibleText(text, context, scrollLeft, viewportWidth) {
|
|
21
|
-
const characters = Array.from(text);
|
|
22
|
-
const overscan = Math.max(16, viewportWidth * 0.2);
|
|
23
|
-
const startEdge = Math.max(0, scrollLeft - overscan);
|
|
24
|
-
const endEdge = scrollLeft + viewportWidth + overscan;
|
|
25
|
-
let offset = 0;
|
|
26
|
-
let start = 0;
|
|
27
|
-
let end = characters.length;
|
|
28
|
-
for (let index = 0; index < characters.length; index += 1) {
|
|
29
|
-
const width = context.measureText(characters[index]).width;
|
|
30
|
-
if (offset + width < startEdge)
|
|
31
|
-
start = index + 1;
|
|
32
|
-
if (offset <= endEdge)
|
|
33
|
-
end = index + 1;
|
|
34
|
-
offset += width;
|
|
35
|
-
if (offset > endEdge)
|
|
36
|
-
break;
|
|
37
|
-
}
|
|
38
|
-
const visible = characters.slice(start, end).join('');
|
|
39
|
-
if (Array.from(visible).length > MAX_EDITABLE_LINE)
|
|
40
|
-
return null;
|
|
41
|
-
return { text: visible, offset: context.measureText(characters.slice(0, start).join('')).width };
|
|
42
|
-
}
|
|
43
20
|
function copyMirrorStyle(mirror, control, style) {
|
|
44
21
|
Object.assign(mirror.style, {
|
|
45
22
|
boxSizing: 'border-box',
|
|
@@ -92,7 +69,7 @@ function measureTextareaLines(control, text, style) {
|
|
|
92
69
|
const rectangle = range.getBoundingClientRect();
|
|
93
70
|
let line = lines[lines.length - 1];
|
|
94
71
|
if (!line || Math.abs(line.top - rectangle.top) > 1) {
|
|
95
|
-
line = { text: '', left: rectangle.left - mirrorBox.left, top: rectangle.top - mirrorBox.top };
|
|
72
|
+
line = { text: '', left: rectangle.left - mirrorBox.left, top: rectangle.top - mirrorBox.top, start: offset - character.length };
|
|
96
73
|
lines.push(line);
|
|
97
74
|
}
|
|
98
75
|
line.text += character;
|
|
@@ -119,22 +96,17 @@ function textStart(direction, textAlign, controlWidth, paddingStart, paddingEnd,
|
|
|
119
96
|
return controlWidth - paddingEnd - advance;
|
|
120
97
|
return paddingStart;
|
|
121
98
|
}
|
|
122
|
-
function paintEditableText(canvas, control, seed, multiline) {
|
|
99
|
+
function paintEditableText(canvas, buffer, glyphs, control, seed, multiline, composition) {
|
|
123
100
|
const width = control.clientWidth;
|
|
124
101
|
const height = control.clientHeight;
|
|
125
|
-
const
|
|
126
|
-
|
|
102
|
+
const target = canvas.getContext('2d');
|
|
103
|
+
const context = buffer.getContext('2d');
|
|
104
|
+
const dpr = effectiveDpr(2);
|
|
105
|
+
if (!target || target.isContextLost?.() || !context || context.isContextLost?.()
|
|
106
|
+
|| width < 2 || height < 2 || width * height * dpr ** 2 > 2 * 1024 * 1024)
|
|
127
107
|
return false;
|
|
128
|
-
Object.assign(canvas.style, {
|
|
129
|
-
left: `${control.offsetLeft}px`,
|
|
130
|
-
top: `${control.offsetTop}px`,
|
|
131
|
-
right: 'auto',
|
|
132
|
-
bottom: 'auto',
|
|
133
|
-
width: `${width}px`,
|
|
134
|
-
height: `${height}px`,
|
|
135
|
-
});
|
|
136
108
|
const content = editableText(control);
|
|
137
|
-
if (
|
|
109
|
+
if (content.text.length > MAX_EDITABLE_TEXT)
|
|
138
110
|
return false;
|
|
139
111
|
const style = getComputedStyle(control);
|
|
140
112
|
if (style.direction === 'rtl' || complexScript.test(content.text))
|
|
@@ -142,170 +114,243 @@ function paintEditableText(canvas, control, seed, multiline) {
|
|
|
142
114
|
const fontSize = Number.parseFloat(style.fontSize);
|
|
143
115
|
if (!Number.isFinite(fontSize) || fontSize <= 0)
|
|
144
116
|
return false;
|
|
145
|
-
const dpr = effectiveDpr(2);
|
|
146
|
-
canvas.width = Math.ceil(width * dpr);
|
|
147
|
-
canvas.height = Math.ceil(height * dpr);
|
|
148
|
-
context.setTransform(dpr, 0, 0, dpr, 0, 0);
|
|
149
|
-
context.clearRect(0, 0, width, height);
|
|
150
|
-
if (!content.text)
|
|
151
|
-
return true;
|
|
152
117
|
const family = style.fontFamily;
|
|
153
118
|
const color = content.placeholder
|
|
154
119
|
? style.getPropertyValue('--inkra-editable-placeholder-color').trim() || style.color
|
|
155
120
|
: style.getPropertyValue('--inkra-editable-text-color').trim() || style.color;
|
|
121
|
+
const settings = {
|
|
122
|
+
variation: readRenderToken(style, '--inkra-letter-variation', pencilLettering.variation, 0, 1),
|
|
123
|
+
breaks: readRenderToken(style, '--inkra-letter-breaks', pencilLettering.breaks, 0, 1),
|
|
124
|
+
outline: readRenderToken(style, '--inkra-letter-outline', pencilLettering.outline, 0, 1),
|
|
125
|
+
fill: readRenderToken(style, '--inkra-letter-fill', pencilLettering.fill, 0, 1),
|
|
126
|
+
grain: readRenderToken(style, '--inkra-letter-grain', pencilLettering.grain, 0, 1),
|
|
127
|
+
};
|
|
128
|
+
buffer.width = Math.ceil(width * dpr);
|
|
129
|
+
buffer.height = Math.ceil(height * dpr);
|
|
130
|
+
context.setTransform(dpr, 0, 0, dpr, 0, 0);
|
|
131
|
+
context.font = `400 ${fontSize}px ${family}`;
|
|
132
|
+
context.fontKerning = 'none';
|
|
133
|
+
context.fillStyle = color;
|
|
134
|
+
const metrics = context.measureText('Mg');
|
|
135
|
+
const ascent = metrics.fontBoundingBoxAscent ?? fontSize;
|
|
136
|
+
const descent = metrics.fontBoundingBoxDescent ?? fontSize * 0.25;
|
|
156
137
|
const paddingStart = numeric(style, 'padding-inline-start') || numeric(style, 'padding-left');
|
|
157
138
|
const paddingEnd = numeric(style, 'padding-inline-end') || numeric(style, 'padding-right');
|
|
158
|
-
|
|
159
|
-
if (!measure)
|
|
139
|
+
if (!multiline && content.text.length > MAX_EDITABLE_LINE)
|
|
160
140
|
return false;
|
|
161
|
-
|
|
162
|
-
measure.fontKerning = 'none';
|
|
163
|
-
if (!multiline && Array.from(content.text).length > MAX_EDITABLE_LINE)
|
|
164
|
-
return false;
|
|
165
|
-
if (!multiline && style.direction === 'rtl'
|
|
166
|
-
&& measure.measureText(content.text).width > control.clientWidth - paddingStart - paddingEnd)
|
|
167
|
-
return false;
|
|
168
|
-
const measuredLines = multiline
|
|
141
|
+
const lines = multiline
|
|
169
142
|
? measureTextareaLines(control, content.text, style)
|
|
170
|
-
: [{ text: content.text, left: 0, top: 0 }];
|
|
171
|
-
if (!
|
|
143
|
+
: [{ text: content.text, left: 0, top: 0, start: 0 }];
|
|
144
|
+
if (!lines)
|
|
172
145
|
return false;
|
|
173
146
|
const baseSeed = seedToNumber(seed);
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
const
|
|
181
|
-
?
|
|
182
|
-
:
|
|
183
|
-
if (
|
|
184
|
-
continue;
|
|
185
|
-
const sprite = renderPencilText(visible.text, {
|
|
186
|
-
size: fontSize,
|
|
187
|
-
family,
|
|
188
|
-
color,
|
|
189
|
-
seed: baseSeed + index * 131,
|
|
190
|
-
dpr,
|
|
191
|
-
cache: false,
|
|
192
|
-
});
|
|
193
|
-
const start = multiline
|
|
194
|
-
? line.left
|
|
195
|
-
: textStart(style.direction, style.textAlign, control.clientWidth, paddingStart, paddingEnd, sprite.advance);
|
|
196
|
-
const x = start + visible.offset - control.scrollLeft - sprite.padding;
|
|
197
|
-
const y = multiline
|
|
198
|
-
? line.top - control.scrollTop - sprite.padding
|
|
199
|
-
: (control.clientHeight - sprite.height) / 2;
|
|
200
|
-
if (y > height || y + sprite.height < 0)
|
|
147
|
+
const glyphAppearance = JSON.stringify([fontSize, family, color, baseSeed, dpr, settings]);
|
|
148
|
+
const composingStart = composition?.start ?? -1;
|
|
149
|
+
const composingEnd = composition
|
|
150
|
+
? Math.max(composition.start, control.value.length - (composition.value.length - composition.end))
|
|
151
|
+
: -1;
|
|
152
|
+
for (const line of lines) {
|
|
153
|
+
const baseline = multiline
|
|
154
|
+
? line.top - control.scrollTop + ascent
|
|
155
|
+
: (height - ascent - descent) / 2 + ascent;
|
|
156
|
+
if (baseline - ascent > height || baseline + descent < 0)
|
|
201
157
|
continue;
|
|
202
|
-
|
|
158
|
+
let left = (multiline ? line.left : textStart(style.direction, style.textAlign, width, paddingStart, paddingEnd, context.measureText(line.text).width)) - control.scrollLeft;
|
|
159
|
+
let offset = line.start;
|
|
160
|
+
for (const character of line.text) {
|
|
161
|
+
const advance = character === '\n' || character === '\r' ? 0
|
|
162
|
+
: character === '\t' ? numeric(style, 'tab-size') * context.measureText(' ').width
|
|
163
|
+
: context.measureText(character).width;
|
|
164
|
+
if (advance && left + advance >= 0 && left <= width) {
|
|
165
|
+
if (composition && !content.placeholder && offset >= composingStart && offset < composingEnd) {
|
|
166
|
+
// Keep preedit visibly underlined without changing the native IME session.
|
|
167
|
+
context.fillText(character, left, baseline);
|
|
168
|
+
context.fillRect(left, baseline + 2, advance, 1 / dpr);
|
|
169
|
+
}
|
|
170
|
+
else if (character.trim()) {
|
|
171
|
+
const key = glyphAppearance + character;
|
|
172
|
+
let glyph = glyphs.get(key);
|
|
173
|
+
if (!glyph || !canvasIsUsable(glyph.ink)) {
|
|
174
|
+
glyph = renderPencilText(character, {
|
|
175
|
+
size: fontSize, family, color, seed: baseSeed ^ seedToNumber(character),
|
|
176
|
+
dpr, settings, cache: false,
|
|
177
|
+
});
|
|
178
|
+
glyphs.set(key, glyph, canvasByteCost(glyph.ink));
|
|
179
|
+
}
|
|
180
|
+
context.drawImage(glyph.ink, left - glyph.padding, baseline - glyph.ascent - glyph.padding, glyph.width, glyph.height);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
left += advance;
|
|
184
|
+
offset += character.length;
|
|
185
|
+
}
|
|
203
186
|
}
|
|
204
|
-
|
|
187
|
+
// Commit only a complete frame. The visible canvas never waits empty for idle work.
|
|
188
|
+
if (canvas.width !== buffer.width)
|
|
189
|
+
canvas.width = buffer.width;
|
|
190
|
+
if (canvas.height !== buffer.height)
|
|
191
|
+
canvas.height = buffer.height;
|
|
192
|
+
target.setTransform(1, 0, 0, 1, 0, 0);
|
|
193
|
+
target.clearRect(0, 0, canvas.width, canvas.height);
|
|
194
|
+
target.drawImage(buffer, 0, 0);
|
|
195
|
+
Object.assign(canvas.style, {
|
|
196
|
+
left: `${control.offsetLeft}px`, top: `${control.offsetTop}px`,
|
|
197
|
+
right: 'auto', bottom: 'auto', width: `${width}px`, height: `${height}px`,
|
|
198
|
+
});
|
|
205
199
|
return true;
|
|
206
200
|
}
|
|
207
201
|
export function EditablePencilText({ controlRef, defaultValue, multiline = false, placeholder, seed, value, }) {
|
|
208
202
|
const canvasRef = useRef(null);
|
|
209
203
|
const scheduleRef = useRef(() => { });
|
|
210
|
-
const composingRef = useRef(
|
|
204
|
+
const composingRef = useRef(null);
|
|
211
205
|
useBrowserLayoutEffect(() => {
|
|
212
206
|
const control = controlRef.current;
|
|
213
207
|
const canvas = canvasRef.current;
|
|
214
208
|
if (!control || !canvas)
|
|
215
209
|
return undefined;
|
|
216
210
|
const root = control.closest('.inkra-field-control-hit');
|
|
217
|
-
|
|
218
|
-
|
|
211
|
+
const buffer = document.createElement('canvas');
|
|
212
|
+
// Private, bounded, per-editor glyphs. Never enter the shared text cache.
|
|
213
|
+
let glyphs = new BoundedCache(128, 4 * 1024 * 1024);
|
|
219
214
|
let disposed = false;
|
|
215
|
+
let lastAppearance = '';
|
|
216
|
+
const numericControl = control instanceof HTMLInputElement && control.type === 'number';
|
|
217
|
+
let nativeNumberEditing = false;
|
|
218
|
+
const appearanceKey = () => {
|
|
219
|
+
const style = getComputedStyle(control);
|
|
220
|
+
return JSON.stringify([
|
|
221
|
+
control.clientWidth, control.clientHeight, control.offsetLeft, control.offsetTop,
|
|
222
|
+
control.scrollLeft, control.scrollTop, control.value, control.placeholder, effectiveDpr(2),
|
|
223
|
+
composingRef.current?.start, composingRef.current?.end, composingRef.current?.value.length,
|
|
224
|
+
...['font-family', 'font-size', 'line-height', 'text-align', 'text-indent',
|
|
225
|
+
'letter-spacing', 'white-space', 'word-break', 'overflow-wrap', 'direction', 'tab-size',
|
|
226
|
+
'padding-top', 'padding-left', 'padding-right', 'padding-bottom',
|
|
227
|
+
'--inkra-letter-variation', '--inkra-letter-breaks', '--inkra-letter-outline',
|
|
228
|
+
'--inkra-letter-fill', '--inkra-letter-grain',
|
|
229
|
+
'--inkra-editable-text-color', '--inkra-editable-placeholder-color'].map(name => style.getPropertyValue(name)),
|
|
230
|
+
]);
|
|
231
|
+
};
|
|
220
232
|
const paint = () => {
|
|
221
|
-
|
|
222
|
-
|
|
233
|
+
if (disposed)
|
|
234
|
+
return;
|
|
235
|
+
// Number inputs expose a sanitized value, not incomplete drafts such as "-" or "1.".
|
|
236
|
+
if (numericControl && (nativeNumberEditing || control.validity.badInput || composingRef.current)) {
|
|
237
|
+
root?.removeAttribute('data-pencil-editable-ready');
|
|
238
|
+
lastAppearance = '';
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
const key = appearanceKey();
|
|
242
|
+
if (key === lastAppearance && root?.hasAttribute('data-pencil-editable-ready'))
|
|
223
243
|
return;
|
|
224
244
|
try {
|
|
225
|
-
|
|
226
|
-
if (ready)
|
|
245
|
+
if (paintEditableText(canvas, buffer, glyphs, control, seed, multiline, composingRef.current)) {
|
|
227
246
|
root?.setAttribute('data-pencil-editable-ready', 'true');
|
|
228
|
-
|
|
247
|
+
lastAppearance = key;
|
|
248
|
+
}
|
|
249
|
+
else {
|
|
229
250
|
root?.removeAttribute('data-pencil-editable-ready');
|
|
251
|
+
lastAppearance = '';
|
|
252
|
+
}
|
|
230
253
|
}
|
|
231
254
|
catch {
|
|
232
255
|
root?.removeAttribute('data-pencil-editable-ready');
|
|
256
|
+
lastAppearance = '';
|
|
233
257
|
}
|
|
234
258
|
};
|
|
235
|
-
|
|
236
|
-
|
|
259
|
+
scheduleRef.current = paint;
|
|
260
|
+
const beforeInput = (event) => {
|
|
261
|
+
if (!numericControl || event.defaultPrevented)
|
|
237
262
|
return;
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
});
|
|
263
|
+
nativeNumberEditing = true;
|
|
264
|
+
root?.setAttribute('data-pencil-number-editing', 'true');
|
|
265
|
+
paint();
|
|
242
266
|
};
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
267
|
+
const finishNumberEditing = () => {
|
|
268
|
+
if (!numericControl)
|
|
269
|
+
return;
|
|
270
|
+
nativeNumberEditing = false;
|
|
271
|
+
root?.removeAttribute('data-pencil-number-editing');
|
|
272
|
+
paint();
|
|
247
273
|
};
|
|
248
|
-
const
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
274
|
+
const startComposition = () => {
|
|
275
|
+
if (numericControl) {
|
|
276
|
+
nativeNumberEditing = true;
|
|
277
|
+
root?.setAttribute('data-pencil-number-editing', 'true');
|
|
278
|
+
}
|
|
279
|
+
composingRef.current = {
|
|
280
|
+
value: control.value,
|
|
281
|
+
start: control.selectionStart ?? control.value.length,
|
|
282
|
+
end: control.selectionEnd ?? control.value.length,
|
|
283
|
+
};
|
|
252
284
|
root?.setAttribute('data-composing', 'true');
|
|
253
|
-
|
|
285
|
+
paint();
|
|
254
286
|
};
|
|
255
287
|
const finishComposition = () => {
|
|
256
|
-
composingRef.current =
|
|
288
|
+
composingRef.current = null;
|
|
257
289
|
root?.removeAttribute('data-composing');
|
|
258
|
-
|
|
290
|
+
paint();
|
|
259
291
|
};
|
|
260
|
-
const
|
|
292
|
+
const refreshFonts = () => {
|
|
293
|
+
glyphs = new BoundedCache(128, 4 * 1024 * 1024);
|
|
294
|
+
lastAppearance = '';
|
|
295
|
+
paint();
|
|
296
|
+
};
|
|
297
|
+
const reset = () => queueMicrotask(() => {
|
|
298
|
+
if (disposed)
|
|
299
|
+
return;
|
|
300
|
+
composingRef.current = null;
|
|
301
|
+
root?.removeAttribute('data-composing');
|
|
302
|
+
nativeNumberEditing = false;
|
|
303
|
+
root?.removeAttribute('data-pencil-number-editing');
|
|
304
|
+
refreshFonts();
|
|
305
|
+
});
|
|
261
306
|
const form = control.form;
|
|
262
|
-
const resize = typeof ResizeObserver === 'undefined' ? null : new ResizeObserver(
|
|
307
|
+
const resize = typeof ResizeObserver === 'undefined' ? null : new ResizeObserver(paint);
|
|
263
308
|
resize?.observe(control);
|
|
264
|
-
const appearance = new MutationObserver(
|
|
265
|
-
|
|
266
|
-
attributes: true,
|
|
267
|
-
|
|
268
|
-
attributeFilter: ['data-theme', 'data-inkra-theme'],
|
|
269
|
-
});
|
|
309
|
+
const appearance = new MutationObserver(paint);
|
|
310
|
+
for (let element = control; element; element = element.parentElement) {
|
|
311
|
+
appearance.observe(element, { attributes: true, attributeFilter: ['style', 'class', 'dir', 'data-theme', 'data-inkra-theme'] });
|
|
312
|
+
}
|
|
270
313
|
const colorScheme = window.matchMedia('(prefers-color-scheme: dark)');
|
|
271
|
-
colorScheme.addEventListener('change',
|
|
272
|
-
control.addEventListener('
|
|
273
|
-
control.addEventListener('
|
|
274
|
-
control.addEventListener('
|
|
314
|
+
colorScheme.addEventListener('change', paint);
|
|
315
|
+
control.addEventListener('beforeinput', beforeInput);
|
|
316
|
+
control.addEventListener('blur', finishNumberEditing);
|
|
317
|
+
control.addEventListener('input', paint);
|
|
318
|
+
control.addEventListener('scroll', paint, { passive: true });
|
|
319
|
+
control.addEventListener('compositionstart', startComposition);
|
|
275
320
|
control.addEventListener('compositionend', finishComposition);
|
|
276
321
|
form?.addEventListener('reset', reset);
|
|
277
|
-
document.fonts?.addEventListener('loadingdone',
|
|
278
|
-
document.fonts?.addEventListener('loadingerror',
|
|
322
|
+
document.fonts?.addEventListener('loadingdone', refreshFonts);
|
|
323
|
+
document.fonts?.addEventListener('loadingerror', refreshFonts);
|
|
279
324
|
document.fonts?.ready.then(() => { if (!disposed)
|
|
280
|
-
|
|
281
|
-
const stopRecovery = observeCanvasRecovery(
|
|
282
|
-
|
|
325
|
+
refreshFonts(); });
|
|
326
|
+
const stopRecovery = observeCanvasRecovery(refreshFonts);
|
|
327
|
+
paint();
|
|
283
328
|
return () => {
|
|
284
329
|
disposed = true;
|
|
285
330
|
stopRecovery();
|
|
286
|
-
pending?.();
|
|
287
331
|
resize?.disconnect();
|
|
288
332
|
appearance.disconnect();
|
|
289
|
-
colorScheme.removeEventListener('change',
|
|
290
|
-
control.removeEventListener('
|
|
291
|
-
control.removeEventListener('
|
|
292
|
-
control.removeEventListener('
|
|
333
|
+
colorScheme.removeEventListener('change', paint);
|
|
334
|
+
control.removeEventListener('beforeinput', beforeInput);
|
|
335
|
+
control.removeEventListener('blur', finishNumberEditing);
|
|
336
|
+
control.removeEventListener('input', paint);
|
|
337
|
+
control.removeEventListener('scroll', paint);
|
|
338
|
+
control.removeEventListener('compositionstart', startComposition);
|
|
293
339
|
control.removeEventListener('compositionend', finishComposition);
|
|
294
340
|
form?.removeEventListener('reset', reset);
|
|
295
|
-
document.fonts?.removeEventListener('loadingdone',
|
|
296
|
-
document.fonts?.removeEventListener('loadingerror',
|
|
297
|
-
cancelAnimationFrame(frame);
|
|
341
|
+
document.fonts?.removeEventListener('loadingdone', refreshFonts);
|
|
342
|
+
document.fonts?.removeEventListener('loadingerror', refreshFonts);
|
|
298
343
|
root?.removeAttribute('data-pencil-editable-ready');
|
|
299
344
|
root?.removeAttribute('data-composing');
|
|
300
|
-
|
|
345
|
+
root?.removeAttribute('data-pencil-number-editing');
|
|
346
|
+
composingRef.current = null;
|
|
347
|
+
glyphs = new BoundedCache(0, 0);
|
|
348
|
+
buffer.width = buffer.height = 0;
|
|
349
|
+
canvas.width = canvas.height = 0;
|
|
301
350
|
scheduleRef.current = () => { };
|
|
302
351
|
};
|
|
303
352
|
}, [controlRef, multiline, seed]);
|
|
304
353
|
useBrowserLayoutEffect(() => {
|
|
305
|
-
if (composingRef.current)
|
|
306
|
-
return;
|
|
307
|
-
controlRef.current?.closest('.inkra-field-control-hit')
|
|
308
|
-
?.removeAttribute('data-pencil-editable-ready');
|
|
309
354
|
scheduleRef.current();
|
|
310
355
|
}, [controlRef, defaultValue, placeholder, value]);
|
|
311
356
|
return _jsx("canvas", { "aria-hidden": "true", className: "inkra-editable-text", "data-renderer": "inkra-editable-lettering", ref: canvasRef });
|
package/dist/form/Input.js
CHANGED
|
@@ -7,7 +7,7 @@ import { PencilText } from '../PencilText.js';
|
|
|
7
7
|
import { InputAction } from './InputAction.js';
|
|
8
8
|
import { useResolvedControlProps } from './controlProps.js';
|
|
9
9
|
import { EditablePencilText } from './EditablePencilText.js';
|
|
10
|
-
const editablePencilInputTypes = new Set(['text', 'search', 'email', 'url', 'tel']);
|
|
10
|
+
const editablePencilInputTypes = new Set(['text', 'search', 'email', 'url', 'tel', 'number']);
|
|
11
11
|
const clearableInputTypes = new Set(['text', 'search', 'email', 'url', 'tel', 'password', 'number']);
|
|
12
12
|
// Lucide X / LoaderCircle ISC geometry, rendered by the shared pencil engine.
|
|
13
13
|
const clearPaths = [{ d: 'M18 6 6 18' }, { d: 'm6 6 12 12' }];
|
package/dist/form/Textarea.d.ts
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
import type { TextareaHTMLAttributes } from 'react';
|
|
2
2
|
import type { FieldControlStateProps } from './types.js';
|
|
3
|
+
export interface TextareaCountDetails {
|
|
4
|
+
count: number;
|
|
5
|
+
maxLength: number;
|
|
6
|
+
remaining: number;
|
|
7
|
+
}
|
|
3
8
|
export interface TextareaProps extends TextareaHTMLAttributes<HTMLTextAreaElement>, FieldControlStateProps {
|
|
9
|
+
countMode?: 'remaining' | 'fraction' | 'progress' | 'none';
|
|
10
|
+
countDisplay?: 'auto' | 'always';
|
|
11
|
+
/** Ratio of maxLength at which the automatic counter appears. */
|
|
12
|
+
countThreshold?: number;
|
|
13
|
+
getCountLabel?: (details: TextareaCountDetails) => string;
|
|
14
|
+
resize?: 'none' | 'vertical';
|
|
15
|
+
resizeLabel?: string;
|
|
4
16
|
}
|
|
5
17
|
export declare const Textarea: import("react").ForwardRefExoticComponent<TextareaProps & import("react").RefAttributes<HTMLTextAreaElement>>;
|
package/dist/form/Textarea.js
CHANGED
|
@@ -1,14 +1,64 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { forwardRef, useId, useImperativeHandle, useRef } from 'react';
|
|
2
|
+
import { forwardRef, useEffect, useId, useImperativeHandle, useRef, useState } from 'react';
|
|
3
3
|
import { PencilSurface } from '../PencilSurface.js';
|
|
4
|
+
import { PencilText } from '../PencilText.js';
|
|
5
|
+
import { PencilIcon } from '../PencilIcon.js';
|
|
6
|
+
import { Button } from '../Button.js';
|
|
7
|
+
import { Progress } from '../display/index.js';
|
|
4
8
|
import { useResolvedControlProps } from './controlProps.js';
|
|
5
9
|
import { EditablePencilText } from './EditablePencilText.js';
|
|
6
|
-
|
|
10
|
+
const resizePaths = [{ d: 'M12 3v18M8 7l4-4 4 4M8 17l4 4 4-4' }];
|
|
11
|
+
const defaultCountLabel = ({ remaining }) => remaining < 0 ? `${-remaining} over limit` : `${remaining} remaining`;
|
|
12
|
+
export const Textarea = forwardRef(function Textarea({ 'aria-describedby': ariaDescribedBy, 'aria-errormessage': ariaErrorMessage, 'aria-invalid': ariaInvalid, className, disabled, id, invalid = false, readOnly, required, seed, size = 'md', countMode = 'remaining', countDisplay = 'auto', countThreshold = 0.8, getCountLabel = defaultCountLabel, resize = 'none', resizeLabel = 'Resize text area', ...props }, ref) {
|
|
7
13
|
const generatedId = useId();
|
|
8
14
|
const textareaRef = useRef(null);
|
|
15
|
+
const [uncontrolledCount, setUncontrolledCount] = useState(() => String(props.defaultValue ?? '').length);
|
|
16
|
+
const [height, setHeight] = useState();
|
|
17
|
+
const [measuredHeight, setMeasuredHeight] = useState();
|
|
18
|
+
const drag = useRef(null);
|
|
19
|
+
const controlled = props.value != null;
|
|
20
|
+
const count = controlled ? String(props.value).length : uncontrolledCount;
|
|
21
|
+
const maximum = props.maxLength;
|
|
22
|
+
const hasCount = countMode !== 'none' && typeof maximum === 'number' && Number.isFinite(maximum) && maximum >= 0;
|
|
23
|
+
const threshold = Number.isFinite(countThreshold) ? Math.max(0, Math.min(1, countThreshold)) : 0.8;
|
|
24
|
+
const showCount = hasCount && (countDisplay === 'always' || count >= maximum * threshold);
|
|
25
|
+
const countId = `inkra-textarea-${generatedId}-count`;
|
|
26
|
+
const controlId = id ?? `inkra-textarea-${generatedId}`;
|
|
27
|
+
const remaining = hasCount ? maximum - count : 0;
|
|
28
|
+
const countLabel = hasCount ? getCountLabel({ count, maxLength: maximum, remaining }) : '';
|
|
29
|
+
const minimumHeight = size === 'sm' ? 80 : size === 'lg' ? 112 : 96;
|
|
30
|
+
const resizeTo = (next) => setHeight(Math.max(minimumHeight, Math.min(1200, next)));
|
|
31
|
+
useEffect(() => {
|
|
32
|
+
const control = textareaRef.current;
|
|
33
|
+
if (resize !== 'vertical' || !control)
|
|
34
|
+
return;
|
|
35
|
+
const measure = () => setMeasuredHeight(control.getBoundingClientRect().height);
|
|
36
|
+
measure();
|
|
37
|
+
if (typeof ResizeObserver === 'undefined')
|
|
38
|
+
return;
|
|
39
|
+
const observer = new ResizeObserver(measure);
|
|
40
|
+
observer.observe(control);
|
|
41
|
+
return () => observer.disconnect();
|
|
42
|
+
}, [resize]);
|
|
43
|
+
useEffect(() => {
|
|
44
|
+
const control = textareaRef.current;
|
|
45
|
+
const form = control?.form;
|
|
46
|
+
let frame = 0;
|
|
47
|
+
const reset = () => {
|
|
48
|
+
cancelAnimationFrame(frame);
|
|
49
|
+
frame = requestAnimationFrame(() => setUncontrolledCount(control?.value.length ?? 0));
|
|
50
|
+
};
|
|
51
|
+
form?.addEventListener('reset', reset);
|
|
52
|
+
if (!controlled)
|
|
53
|
+
setUncontrolledCount(control?.value.length ?? 0);
|
|
54
|
+
return () => {
|
|
55
|
+
cancelAnimationFrame(frame);
|
|
56
|
+
form?.removeEventListener('reset', reset);
|
|
57
|
+
};
|
|
58
|
+
}, [controlled, props.defaultValue, props.form]);
|
|
9
59
|
useImperativeHandle(ref, () => textareaRef.current);
|
|
10
60
|
const state = useResolvedControlProps({
|
|
11
|
-
id,
|
|
61
|
+
id: controlId,
|
|
12
62
|
required,
|
|
13
63
|
disabled,
|
|
14
64
|
readOnly,
|
|
@@ -19,5 +69,34 @@ export const Textarea = forwardRef(function Textarea({ 'aria-describedby': ariaD
|
|
|
19
69
|
});
|
|
20
70
|
const resolvedSeed = seed ?? `inkra-textarea-${state.id ?? generatedId}`;
|
|
21
71
|
const classes = ['inkra-textarea', className].filter(Boolean).join(' ');
|
|
22
|
-
return (_jsx("
|
|
72
|
+
return (_jsx("div", { className: "inkra-field-control-hit inkra-field-control-hit--textarea", "data-disabled": state.disabled || undefined, "data-invalid": state.invalid || undefined, "data-readonly": state.readOnly || undefined, "data-size": size, children: _jsxs(PencilSurface, { className: "inkra-field-control inkra-field-control--textarea", seed: resolvedSeed, variant: state.invalid ? 'danger' : 'outline', children: [_jsx("textarea", { ...props, "aria-describedby": [state.describedBy, showCount && countId].filter(Boolean).join(' ') || undefined, "aria-errormessage": state.errorMessage, "aria-invalid": state.ariaInvalid, className: classes, disabled: state.disabled, id: state.id, readOnly: state.readOnly, ref: textareaRef, required: state.required, style: { ...props.style, ...(resize === 'vertical' && height !== undefined ? { height } : {}), resize: 'none' }, onInput: event => {
|
|
73
|
+
if (!controlled)
|
|
74
|
+
setUncontrolledCount(event.currentTarget.value.length);
|
|
75
|
+
props.onInput?.(event);
|
|
76
|
+
} }), _jsx(EditablePencilText, { controlRef: textareaRef, defaultValue: props.defaultValue, multiline: true, placeholder: props.placeholder, seed: `${resolvedSeed}-editable`, value: props.value }), (hasCount || resize === 'vertical') && _jsxs("div", { className: "inkra-textarea__footer", children: [hasCount && _jsxs("div", { className: "inkra-textarea__count", id: countId, "data-visible": showCount, "data-limit": remaining <= 0 ? 'reached' : 'near', "aria-hidden": !showCount || undefined, children: [countMode === 'progress' && _jsx(Progress, { size: "sm", value: maximum === 0 ? 1 : count, max: maximum === 0 ? 1 : maximum, "aria-label": countLabel, "aria-valuetext": countLabel }), _jsx(PencilText, { children: countMode === 'fraction' ? `${count} / ${maximum}` : countLabel })] }), resize === 'vertical' && _jsx(Button, { className: "inkra-textarea__resize", variant: "ghost", size: "icon", role: "slider", "aria-label": resizeLabel, title: resizeLabel, "aria-controls": state.id, "aria-orientation": "vertical", "aria-valuemin": minimumHeight, "aria-valuemax": 1200, "aria-valuenow": Math.round(measuredHeight ?? minimumHeight), disabled: state.disabled, onPointerDown: event => {
|
|
77
|
+
if (event.button !== 0)
|
|
78
|
+
return;
|
|
79
|
+
drag.current = { y: event.clientY, height: textareaRef.current?.getBoundingClientRect().height ?? minimumHeight };
|
|
80
|
+
event.currentTarget.setPointerCapture(event.pointerId);
|
|
81
|
+
}, onPointerMove: event => {
|
|
82
|
+
if (drag.current)
|
|
83
|
+
resizeTo(drag.current.height + event.clientY - drag.current.y);
|
|
84
|
+
}, onPointerUp: event => {
|
|
85
|
+
drag.current = null;
|
|
86
|
+
if (event.currentTarget.hasPointerCapture(event.pointerId))
|
|
87
|
+
event.currentTarget.releasePointerCapture(event.pointerId);
|
|
88
|
+
}, onPointerCancel: () => { drag.current = null; }, onLostPointerCapture: () => { drag.current = null; }, onKeyDown: event => {
|
|
89
|
+
const current = textareaRef.current?.getBoundingClientRect().height ?? minimumHeight;
|
|
90
|
+
if (event.key === 'ArrowUp')
|
|
91
|
+
resizeTo(current - 20);
|
|
92
|
+
else if (event.key === 'ArrowDown')
|
|
93
|
+
resizeTo(current + 20);
|
|
94
|
+
else if (event.key === 'Home')
|
|
95
|
+
resizeTo(minimumHeight);
|
|
96
|
+
else if (event.key === 'End')
|
|
97
|
+
resizeTo(1200);
|
|
98
|
+
else
|
|
99
|
+
return;
|
|
100
|
+
event.preventDefault();
|
|
101
|
+
}, children: _jsx(PencilIcon, { paths: resizePaths, size: 22 }) })] })] }) }));
|
|
23
102
|
});
|
package/dist/form/index.d.ts
CHANGED
|
@@ -3,5 +3,5 @@ export type { FieldProps } from './Field.js';
|
|
|
3
3
|
export { Input } from './Input.js';
|
|
4
4
|
export type { InputProps, InputVariant, InputTextAlign, InputValueChangeDetails, InputClassNames } from './Input.js';
|
|
5
5
|
export { Textarea } from './Textarea.js';
|
|
6
|
-
export type { TextareaProps } from './Textarea.js';
|
|
6
|
+
export type { TextareaProps, TextareaCountDetails } from './Textarea.js';
|
|
7
7
|
export type { FieldControlSize, FieldControlStateProps } from './types.js';
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import type { HTMLAttributes, ReactNode } from 'react';
|
|
2
|
+
export type HeatmapKey = string | number;
|
|
3
|
+
export type HeatmapScaleType = 'continuous' | 'quantize';
|
|
4
|
+
export interface HeatmapAxisItem {
|
|
5
|
+
id: HeatmapKey;
|
|
6
|
+
label: ReactNode;
|
|
7
|
+
ariaLabel?: string;
|
|
8
|
+
}
|
|
9
|
+
export interface HeatmapCell {
|
|
10
|
+
rowId: HeatmapKey;
|
|
11
|
+
columnId: HeatmapKey;
|
|
12
|
+
value?: number | null;
|
|
13
|
+
disabled?: boolean;
|
|
14
|
+
data?: unknown;
|
|
15
|
+
}
|
|
16
|
+
export interface HeatmapSelection {
|
|
17
|
+
rowId: HeatmapKey;
|
|
18
|
+
columnId: HeatmapKey;
|
|
19
|
+
}
|
|
20
|
+
export interface HeatmapCellContext {
|
|
21
|
+
row: HeatmapAxisItem;
|
|
22
|
+
column: HeatmapAxisItem;
|
|
23
|
+
color: string | null;
|
|
24
|
+
missing: boolean;
|
|
25
|
+
selected: boolean;
|
|
26
|
+
active: boolean;
|
|
27
|
+
}
|
|
28
|
+
export interface HeatmapDetailsContext extends HeatmapCellContext {
|
|
29
|
+
valueLabel: string;
|
|
30
|
+
}
|
|
31
|
+
export interface HeatmapProps extends Omit<HTMLAttributes<HTMLDivElement>, 'children' | 'onChange'> {
|
|
32
|
+
rows: readonly HeatmapAxisItem[];
|
|
33
|
+
columns: readonly HeatmapAxisItem[];
|
|
34
|
+
cells: readonly HeatmapCell[];
|
|
35
|
+
label: ReactNode;
|
|
36
|
+
description?: ReactNode;
|
|
37
|
+
scaleType?: HeatmapScaleType;
|
|
38
|
+
domain?: readonly [number, number];
|
|
39
|
+
colorRange?: readonly string[];
|
|
40
|
+
binCount?: number;
|
|
41
|
+
showLegend?: boolean;
|
|
42
|
+
showValues?: boolean;
|
|
43
|
+
legendLabel?: ReactNode;
|
|
44
|
+
missingLabel?: ReactNode;
|
|
45
|
+
missingValueLabel?: string;
|
|
46
|
+
selectionMode?: 'single' | 'none';
|
|
47
|
+
selectedCell?: HeatmapSelection | null;
|
|
48
|
+
defaultSelectedCell?: HeatmapSelection | null;
|
|
49
|
+
onSelectedCellChange?: (selection: HeatmapSelection | null, cell: HeatmapCell | null) => void;
|
|
50
|
+
getValueLabel?: (value: number) => string;
|
|
51
|
+
getCellAriaLabel?: (cell: HeatmapCell, context: HeatmapDetailsContext) => string;
|
|
52
|
+
renderCell?: (cell: HeatmapCell, context: HeatmapCellContext) => ReactNode;
|
|
53
|
+
renderDetails?: (cell: HeatmapCell, context: HeatmapDetailsContext) => ReactNode;
|
|
54
|
+
cellSize?: number | string;
|
|
55
|
+
rowLabelWidth?: number | string;
|
|
56
|
+
rowHeaderLabel?: string;
|
|
57
|
+
emptyMessage?: ReactNode;
|
|
58
|
+
}
|
|
59
|
+
export declare const Heatmap: import("react").ForwardRefExoticComponent<HeatmapProps & import("react").RefAttributes<HTMLDivElement>>;
|