@inkra/react 0.1.0 → 0.2.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.
Files changed (160) hide show
  1. package/README.md +104 -1
  2. package/dist/PencilIcon.js +14 -16
  3. package/dist/PencilLine.js +18 -12
  4. package/dist/PencilText.js +192 -59
  5. package/dist/alert-dialog/index.d.ts +64 -0
  6. package/dist/alert-dialog/index.js +109 -0
  7. package/dist/audio-player/index.d.ts +60 -0
  8. package/dist/audio-player/index.js +101 -0
  9. package/dist/carousel/index.d.ts +51 -0
  10. package/dist/carousel/index.js +290 -0
  11. package/dist/choice-grid/index.d.ts +38 -0
  12. package/dist/choice-grid/index.js +85 -0
  13. package/dist/color-picker/color.d.ts +15 -0
  14. package/dist/color-picker/color.js +55 -0
  15. package/dist/color-picker/index.d.ts +31 -0
  16. package/dist/color-picker/index.js +69 -0
  17. package/dist/command/index.d.ts +23 -0
  18. package/dist/command/index.js +39 -0
  19. package/dist/context-menu/index.d.ts +76 -0
  20. package/dist/context-menu/index.js +240 -0
  21. package/dist/data-table/index.d.ts +47 -0
  22. package/dist/data-table/index.js +91 -0
  23. package/dist/date-picker/index.d.ts +50 -0
  24. package/dist/date-picker/index.js +180 -0
  25. package/dist/display/ProgressRing.d.ts +6 -0
  26. package/dist/display/ProgressRing.js +146 -0
  27. package/dist/display/index.d.ts +27 -0
  28. package/dist/display/index.js +18 -2
  29. package/dist/dockable-panel/index.d.ts +41 -0
  30. package/dist/dockable-panel/index.js +253 -0
  31. package/dist/dockable-panel/model.d.ts +21 -0
  32. package/dist/dockable-panel/model.js +61 -0
  33. package/dist/drawer/index.d.ts +63 -0
  34. package/dist/drawer/index.js +86 -0
  35. package/dist/editable/index.d.ts +53 -0
  36. package/dist/editable/index.js +162 -0
  37. package/dist/empty/index.d.ts +21 -0
  38. package/dist/empty/index.js +23 -0
  39. package/dist/flip/index.d.ts +49 -0
  40. package/dist/flip/index.js +138 -0
  41. package/dist/form/EditablePencilText.js +157 -146
  42. package/dist/form/Textarea.d.ts +12 -0
  43. package/dist/form/Textarea.js +83 -4
  44. package/dist/form/index.d.ts +1 -1
  45. package/dist/heatmap/index.d.ts +59 -0
  46. package/dist/heatmap/index.js +248 -0
  47. package/dist/hotkey/index.d.ts +37 -0
  48. package/dist/hotkey/index.js +130 -0
  49. package/dist/hotkey/model.d.ts +27 -0
  50. package/dist/hotkey/model.js +97 -0
  51. package/dist/hover-card/index.d.ts +33 -0
  52. package/dist/hover-card/index.js +141 -0
  53. package/dist/image-cropper/index.d.ts +82 -0
  54. package/dist/image-cropper/index.js +261 -0
  55. package/dist/image-cropper/model.d.ts +42 -0
  56. package/dist/image-cropper/model.js +94 -0
  57. package/dist/image-viewer/index.d.ts +79 -0
  58. package/dist/image-viewer/index.js +220 -0
  59. package/dist/image-viewer/model.d.ts +9 -0
  60. package/dist/image-viewer/model.js +18 -0
  61. package/dist/index.d.ts +47 -0
  62. package/dist/index.js +47 -0
  63. package/dist/internal/cache.d.ts +1 -1
  64. package/dist/internal/cache.js +1 -1
  65. package/dist/internal/renderTokens.d.ts +10 -0
  66. package/dist/internal/renderTokens.js +13 -0
  67. package/dist/internal/surfaceRenderer.d.ts +14 -6
  68. package/dist/internal/surfaceRenderer.js +15 -12
  69. package/dist/internal/textRenderer.d.ts +1 -0
  70. package/dist/internal/textRenderer.js +33 -7
  71. package/dist/internal/usePencilSurface.js +16 -8
  72. package/dist/internal/useSelectionDrawing.js +15 -11
  73. package/dist/journey-map/MapScale.js +3 -1
  74. package/dist/journey-map/index.js +3 -1
  75. package/dist/keypad/index.d.ts +23 -0
  76. package/dist/keypad/index.js +63 -0
  77. package/dist/keypad/model.d.ts +20 -0
  78. package/dist/keypad/model.js +33 -0
  79. package/dist/layout/index.d.ts +63 -0
  80. package/dist/layout/index.js +62 -0
  81. package/dist/log/index.d.ts +45 -0
  82. package/dist/log/index.js +159 -0
  83. package/dist/log/model.d.ts +16 -0
  84. package/dist/log/model.js +13 -0
  85. package/dist/node-graph/index.d.ts +123 -0
  86. package/dist/node-graph/index.js +304 -0
  87. package/dist/number-ticker/index.d.ts +29 -0
  88. package/dist/number-ticker/index.js +132 -0
  89. package/dist/number-ticker/model.d.ts +8 -0
  90. package/dist/number-ticker/model.js +34 -0
  91. package/dist/overflow-list/index.d.ts +39 -0
  92. package/dist/overflow-list/index.js +230 -0
  93. package/dist/overflow-list/layout.d.ts +19 -0
  94. package/dist/overflow-list/layout.js +44 -0
  95. package/dist/pagination/index.d.ts +23 -0
  96. package/dist/pagination/index.js +41 -0
  97. package/dist/pagination/model.d.ts +4 -0
  98. package/dist/pagination/model.js +42 -0
  99. package/dist/pan-zoom/index.d.ts +71 -0
  100. package/dist/pan-zoom/index.js +175 -0
  101. package/dist/radar/index.d.ts +54 -0
  102. package/dist/radar/index.js +220 -0
  103. package/dist/range-slider/index.d.ts +16 -0
  104. package/dist/range-slider/index.js +22 -0
  105. package/dist/rating/index.d.ts +28 -0
  106. package/dist/rating/index.js +127 -0
  107. package/dist/resizable/index.d.ts +32 -0
  108. package/dist/resizable/index.js +50 -0
  109. package/dist/scroll-area/index.d.ts +36 -0
  110. package/dist/scroll-area/index.js +44 -0
  111. package/dist/segmented-progress/SegmentedProgress.d.ts +17 -0
  112. package/dist/segmented-progress/SegmentedProgress.js +42 -0
  113. package/dist/slider/index.js +1 -1
  114. package/dist/slot/index.d.ts +24 -0
  115. package/dist/slot/index.js +29 -0
  116. package/dist/sortable/index.d.ts +50 -0
  117. package/dist/sortable/index.js +278 -0
  118. package/dist/sortable/model.d.ts +30 -0
  119. package/dist/sortable/model.js +64 -0
  120. package/dist/sparkline/index.d.ts +19 -0
  121. package/dist/sparkline/index.js +240 -0
  122. package/dist/sparkline/model.d.ts +16 -0
  123. package/dist/sparkline/model.js +28 -0
  124. package/dist/steps/index.d.ts +16 -0
  125. package/dist/steps/index.js +37 -0
  126. package/dist/styles.css +4850 -287
  127. package/dist/tag-input/index.d.ts +69 -0
  128. package/dist/tag-input/index.js +325 -0
  129. package/dist/tag-input/model.d.ts +14 -0
  130. package/dist/tag-input/model.js +38 -0
  131. package/dist/time-picker/index.d.ts +15 -0
  132. package/dist/time-picker/index.js +168 -0
  133. package/dist/timeline/index.d.ts +14 -0
  134. package/dist/timeline/index.js +16 -0
  135. package/dist/toast/index.d.ts +27 -0
  136. package/dist/toast/index.js +36 -0
  137. package/dist/toggle-group/index.d.ts +17 -0
  138. package/dist/toggle-group/index.js +26 -0
  139. package/dist/tokens.css +10 -9
  140. package/dist/transfer/index.d.ts +64 -0
  141. package/dist/transfer/index.js +174 -0
  142. package/dist/transfer/model.d.ts +12 -0
  143. package/dist/transfer/model.js +33 -0
  144. package/dist/tree/index.d.ts +39 -0
  145. package/dist/tree/index.js +36 -0
  146. package/dist/tree-select/index.d.ts +59 -0
  147. package/dist/tree-select/index.js +142 -0
  148. package/dist/tree-select/model.d.ts +31 -0
  149. package/dist/tree-select/model.js +135 -0
  150. package/dist/typewriter/index.d.ts +21 -0
  151. package/dist/typewriter/index.js +101 -0
  152. package/dist/typewriter/model.d.ts +7 -0
  153. package/dist/typewriter/model.js +45 -0
  154. package/dist/upload/index.d.ts +59 -0
  155. package/dist/upload/index.js +230 -0
  156. package/dist/virtual-list/index.d.ts +44 -0
  157. package/dist/virtual-list/index.js +128 -0
  158. package/dist/virtual-list/model.d.ts +7 -0
  159. package/dist/virtual-list/model.js +27 -0
  160. package/package.json +22 -3
@@ -1,24 +1,110 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { forwardRef, useEffect, useImperativeHandle, useRef, useState } from 'react';
2
+ import { forwardRef, useImperativeHandle, useRef, useState } from 'react';
3
3
  import { useBrowserLayoutEffect } from './internal/useBrowserLayoutEffect.js';
4
4
  import { 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';
7
+ import { pencilLettering, renderPencilText } from './internal/textRenderer.js';
8
+ import { readRenderToken } from './internal/renderTokens.js';
8
9
  import { queueTextPaint } from './internal/textQueue.js';
9
10
  function textSeed(text) {
10
11
  return Array.from(text).reduce((seed, character) => Math.imul(seed ^ character.codePointAt(0), 16777619), 71) >>> 0;
11
12
  }
12
- function paintText(element, source, canvas, text, seed) {
13
- const box = element.getBoundingClientRect();
14
- if (!box.width || !box.height || text.length > 4096 || !source.firstChild)
15
- return false;
13
+ function pixelValue(value) {
14
+ const parsed = Number.parseFloat(value);
15
+ return Number.isFinite(parsed) ? parsed : 0;
16
+ }
17
+ function localBorderBox(element, style = getComputedStyle(element)) {
18
+ const horizontalExtras = pixelValue(style.paddingLeft) + pixelValue(style.paddingRight)
19
+ + pixelValue(style.borderLeftWidth) + pixelValue(style.borderRightWidth);
20
+ const verticalExtras = pixelValue(style.paddingTop) + pixelValue(style.paddingBottom)
21
+ + pixelValue(style.borderTopWidth) + pixelValue(style.borderBottomWidth);
22
+ const computedWidth = pixelValue(style.width);
23
+ const computedHeight = pixelValue(style.height);
24
+ return {
25
+ width: computedWidth > 0
26
+ ? computedWidth + (style.boxSizing === 'border-box' ? 0 : horizontalExtras)
27
+ : element.offsetWidth,
28
+ height: computedHeight > 0
29
+ ? computedHeight + (style.boxSizing === 'border-box' ? 0 : verticalExtras)
30
+ : element.offsetHeight,
31
+ };
32
+ }
33
+ function copyComputedStyle(source, target) {
34
+ for (const property of source) {
35
+ target.setProperty(property, source.getPropertyValue(property), source.getPropertyPriority(property));
36
+ }
37
+ }
38
+ function measureLocalTextLines(element, liveSource, text, width, height) {
39
+ if (!width || !height || !text)
40
+ return [];
41
+ const computed = getComputedStyle(element);
42
+ const sourceComputed = getComputedStyle(liveSource);
43
+ const probe = document.createElement(element.tagName.toLowerCase());
44
+ const source = document.createElement('span');
45
+ copyComputedStyle(computed, probe.style);
46
+ copyComputedStyle(sourceComputed, source.style);
47
+ probe.setAttribute('aria-hidden', 'true');
48
+ probe.inert = true;
49
+ Object.assign(probe.style, {
50
+ boxSizing: 'border-box',
51
+ contain: 'layout style paint',
52
+ height: `${height}px`,
53
+ left: '-100000px',
54
+ margin: '0',
55
+ maxHeight: `${height}px`,
56
+ maxWidth: `${width}px`,
57
+ minHeight: `${height}px`,
58
+ minWidth: `${width}px`,
59
+ overflow: 'visible',
60
+ pointerEvents: 'none',
61
+ position: 'fixed',
62
+ rotate: 'none',
63
+ scale: 'none',
64
+ top: '0',
65
+ transform: 'none',
66
+ translate: 'none',
67
+ visibility: 'hidden',
68
+ width: `${width}px`,
69
+ });
70
+ source.textContent = text;
71
+ probe.append(source);
72
+ document.body.append(probe);
73
+ try {
74
+ const box = probe.getBoundingClientRect();
75
+ const range = document.createRange();
76
+ const lines = [];
77
+ let offset = 0;
78
+ for (const character of text) {
79
+ range.setStart(source.firstChild, offset);
80
+ offset += character.length;
81
+ range.setEnd(source.firstChild, offset);
82
+ const rectangle = range.getBoundingClientRect();
83
+ const left = rectangle.left - box.left;
84
+ const top = rectangle.top - box.top;
85
+ let line = lines[lines.length - 1];
86
+ if (!line || Math.abs(line.top - top) > 1) {
87
+ line = { text: '', left, top };
88
+ lines.push(line);
89
+ }
90
+ line.text += character;
91
+ }
92
+ return lines;
93
+ }
94
+ finally {
95
+ probe.remove();
96
+ }
97
+ }
98
+ function paintText(element, source, canvas, text, seed, glyphSeed) {
16
99
  const style = getComputedStyle(element);
100
+ const { width: localWidth, height: localHeight } = localBorderBox(element, style);
101
+ if (!localWidth || !localHeight || text.length > 4096 || !source.firstChild)
102
+ return false;
17
103
  const size = Number.parseFloat(style.fontSize);
18
104
  const context = canvas.getContext('2d');
19
105
  const padding = 5;
20
- const width = box.width + padding * 2;
21
- const height = box.height + padding * 2;
106
+ const width = localWidth + padding * 2;
107
+ const height = localHeight + padding * 2;
22
108
  const dpr = effectiveDpr(2);
23
109
  if (width * height * dpr ** 2 > 2 * 1024 * 1024 || !context || context.isContextLost?.())
24
110
  return false;
@@ -36,21 +122,7 @@ function paintText(element, source, canvas, text, seed) {
36
122
  context.font = `400 ${size}px ${style.fontFamily}`;
37
123
  context.fontKerning = 'none';
38
124
  const ascent = context.measureText(text).fontBoundingBoxAscent ?? size;
39
- const range = document.createRange();
40
- const lines = [];
41
- let offset = 0;
42
- for (const character of text) {
43
- range.setStart(source.firstChild, offset);
44
- offset += character.length;
45
- range.setEnd(source.firstChild, offset);
46
- const rectangle = range.getBoundingClientRect();
47
- let line = lines[lines.length - 1];
48
- if (!line || Math.abs(line.top - rectangle.top) > 1) {
49
- line = { text: '', left: rectangle.left, top: rectangle.top };
50
- lines.push(line);
51
- }
52
- line.text += character;
53
- }
125
+ const lines = measureLocalTextLines(element, source, text, localWidth, localHeight);
54
126
  for (const line of lines) {
55
127
  const lineText = line.text.trimEnd();
56
128
  if (!lineText)
@@ -58,28 +130,37 @@ function paintText(element, source, canvas, text, seed) {
58
130
  const sprite = renderPencilText(lineText, {
59
131
  size,
60
132
  seed: seedToNumber(seed) ^ textSeed(line.text),
133
+ glyphSeed,
61
134
  color: style.color,
62
135
  family: style.fontFamily,
63
136
  dpr,
137
+ settings: {
138
+ variation: readRenderToken(style, '--inkra-letter-variation', pencilLettering.variation, 0, 1),
139
+ breaks: readRenderToken(style, '--inkra-letter-breaks', pencilLettering.breaks, 0, 1),
140
+ outline: readRenderToken(style, '--inkra-letter-outline', pencilLettering.outline, 0, 1),
141
+ fill: readRenderToken(style, '--inkra-letter-fill', pencilLettering.fill, 0, 1),
142
+ grain: readRenderToken(style, '--inkra-letter-grain', pencilLettering.grain, 0, 1),
143
+ },
64
144
  });
65
145
  if (sprite.ink.getContext('2d')?.isContextLost?.())
66
146
  return false;
67
- context.drawImage(sprite.ink, padding + line.left - box.left - sprite.padding, padding + line.top - box.top + ascent - sprite.ascent - sprite.padding, sprite.width, sprite.height);
147
+ context.drawImage(sprite.ink, padding + line.left - sprite.padding, padding + line.top + ascent - sprite.ascent - sprite.padding, sprite.width, sprite.height);
68
148
  }
69
149
  canvas.dataset.lines = String(lines.length);
70
150
  element.dataset.pencilReady = 'true';
71
151
  return true;
72
152
  }
73
153
  const PencilTextImplementation = forwardRef(function PencilText({ as: Element = 'span', children, seed, ellipsis = false, className = '', ...props }, forwardedRef) {
74
- const [text, setText] = useState(children);
154
+ const [fittedText, setText] = useState(children);
155
+ const text = ellipsis ? fittedText : children;
75
156
  const elementRef = useRef(null);
76
157
  const sourceRef = useRef(null);
77
158
  const canvasRef = useRef(null);
78
159
  const resolvedSeed = seed ?? children;
160
+ const glyphSeed = seedToNumber(seed ?? 'inkra-latin-lettering-v1');
79
161
  useImperativeHandle(forwardedRef, () => elementRef.current);
80
162
  useBrowserLayoutEffect(() => {
81
163
  if (!ellipsis) {
82
- setText(children);
83
164
  return undefined;
84
165
  }
85
166
  const node = elementRef.current;
@@ -125,7 +206,7 @@ const PencilTextImplementation = forwardRef(function PencilText({ as: Element =
125
206
  resize?.disconnect();
126
207
  };
127
208
  }, [children, ellipsis]);
128
- useEffect(() => {
209
+ useBrowserLayoutEffect(() => {
129
210
  const node = elementRef.current;
130
211
  const source = sourceRef.current;
131
212
  const canvas = canvasRef.current;
@@ -133,51 +214,104 @@ const PencilTextImplementation = forwardRef(function PencilText({ as: Element =
133
214
  return undefined;
134
215
  delete node.dataset.pencilReady;
135
216
  let disposed = false;
136
- let visible = typeof IntersectionObserver === 'undefined';
217
+ const inViewport = () => {
218
+ const box = node.getBoundingClientRect();
219
+ if (!box.width || !box.height || !node.isConnected)
220
+ return false;
221
+ let left = Math.max(0, box.left);
222
+ let top = Math.max(0, box.top);
223
+ let right = Math.min(window.innerWidth, box.right);
224
+ let bottom = Math.min(window.innerHeight, box.bottom);
225
+ for (let parent = node.parentElement; parent && left < right && top < bottom; parent = parent.parentElement) {
226
+ const style = getComputedStyle(parent);
227
+ if (style.overflowX === 'visible' && style.overflowY === 'visible')
228
+ continue;
229
+ const clip = parent.getBoundingClientRect();
230
+ if (style.overflowX !== 'visible') {
231
+ left = Math.max(left, clip.left);
232
+ right = Math.min(right, clip.right);
233
+ }
234
+ if (style.overflowY !== 'visible') {
235
+ top = Math.max(top, clip.top);
236
+ bottom = Math.min(bottom, clip.bottom);
237
+ }
238
+ }
239
+ return left < right && top < bottom;
240
+ };
241
+ let visible = inViewport() || typeof IntersectionObserver === 'undefined';
137
242
  let pending = null;
138
243
  let dirty = true;
244
+ let lastAppearance = '';
245
+ const appearanceKey = () => {
246
+ const style = getComputedStyle(node);
247
+ const localBox = localBorderBox(node, style);
248
+ return JSON.stringify([
249
+ localBox.width, localBox.height, effectiveDpr(2),
250
+ style.fontSize && style.fontFamily
251
+ ? document.fonts?.check(`400 ${style.fontSize} ${style.fontFamily}`, text) : false,
252
+ ...['color', 'font-family', 'font-size', 'line-height', 'text-align', 'text-indent',
253
+ 'letter-spacing', 'white-space', 'word-break', 'overflow-wrap', 'direction',
254
+ 'padding-top', 'padding-left', 'padding-right', 'padding-bottom',
255
+ '--inkra-letter-variation', '--inkra-letter-breaks', '--inkra-letter-outline',
256
+ '--inkra-letter-fill', '--inkra-letter-grain'].map(name => style.getPropertyValue(name)),
257
+ ]);
258
+ };
259
+ const paint = () => {
260
+ pending = null;
261
+ if (!disposed && visible && dirty) {
262
+ try {
263
+ if (paintText(node, source, canvas, text, resolvedSeed, glyphSeed))
264
+ lastAppearance = appearanceKey();
265
+ }
266
+ catch {
267
+ delete node.dataset.pencilReady;
268
+ }
269
+ dirty = false;
270
+ }
271
+ };
139
272
  const schedule = () => {
140
- if (!visible || !dirty || pending || disposed)
273
+ if (!visible || !dirty || disposed)
141
274
  return;
142
- pending = queueTextPaint(() => {
275
+ // Visible text is enhanced before paint; only nearby text waits for idle time.
276
+ if (inViewport()) {
277
+ pending?.();
143
278
  pending = null;
144
- if (!disposed && visible && dirty) {
145
- try {
146
- paintText(node, source, canvas, text, resolvedSeed);
147
- }
148
- catch {
149
- delete node.dataset.pencilReady;
150
- }
151
- dirty = false;
152
- }
153
- });
279
+ paint();
280
+ }
281
+ else if (!pending) {
282
+ pending = queueTextPaint(paint);
283
+ }
154
284
  };
155
285
  const markDirty = () => {
156
286
  dirty = true;
157
287
  delete node.dataset.pencilReady;
158
288
  schedule();
159
289
  };
160
- const resize = typeof ResizeObserver === 'undefined' ? null : new ResizeObserver(markDirty);
290
+ // Scroll locks and pointer-event changes must not hide already-painted text.
291
+ const refreshAppearance = () => {
292
+ if (disposed || !node.isConnected)
293
+ return;
294
+ if (appearanceKey() !== lastAppearance)
295
+ markDirty();
296
+ };
297
+ const resize = typeof ResizeObserver === 'undefined' ? null : new ResizeObserver(refreshAppearance);
161
298
  resize?.observe(node);
162
- const appearance = new MutationObserver(markDirty);
163
- appearance.observe(document.documentElement, {
164
- attributes: true,
165
- subtree: true,
166
- attributeFilter: ['data-theme', 'data-inkra-theme'],
167
- });
168
299
  const colorScheme = window.matchMedia('(prefers-color-scheme: dark)');
169
- colorScheme.addEventListener('change', markDirty);
300
+ const inheritedStyle = new MutationObserver(refreshAppearance);
301
+ for (let element = node; element; element = element.parentElement) {
302
+ inheritedStyle.observe(element, { attributes: true, attributeFilter: ['style', 'class', 'dir', 'data-theme', 'data-inkra-theme'] });
303
+ }
304
+ colorScheme.addEventListener('change', refreshAppearance);
170
305
  const intersection = typeof IntersectionObserver === 'undefined' ? null : new IntersectionObserver(entries => {
171
306
  visible = entries[entries.length - 1]?.isIntersecting ?? false;
172
307
  schedule();
173
- }, { rootMargin: '80px' });
308
+ }, { rootMargin: '240px' });
174
309
  intersection?.observe(node);
175
310
  const fonts = document.fonts;
176
- fonts?.addEventListener('loading', markDirty);
177
- fonts?.addEventListener('loadingdone', markDirty);
178
- fonts?.addEventListener('loadingerror', markDirty);
311
+ fonts?.addEventListener('loadingdone', refreshAppearance);
312
+ fonts?.addEventListener('loadingerror', refreshAppearance);
179
313
  fonts?.ready.then(() => { if (!disposed)
180
- markDirty(); });
314
+ refreshAppearance(); });
181
315
  const stopRecovery = observeCanvasRecovery(() => {
182
316
  pending?.();
183
317
  pending = null;
@@ -190,13 +324,12 @@ const PencilTextImplementation = forwardRef(function PencilText({ as: Element =
190
324
  pending?.();
191
325
  resize?.disconnect();
192
326
  intersection?.disconnect();
193
- appearance.disconnect();
194
- colorScheme.removeEventListener('change', markDirty);
195
- fonts?.removeEventListener('loading', markDirty);
196
- fonts?.removeEventListener('loadingdone', markDirty);
197
- fonts?.removeEventListener('loadingerror', markDirty);
327
+ inheritedStyle.disconnect();
328
+ colorScheme.removeEventListener('change', refreshAppearance);
329
+ fonts?.removeEventListener('loadingdone', refreshAppearance);
330
+ fonts?.removeEventListener('loadingerror', refreshAppearance);
198
331
  };
199
- }, [text, resolvedSeed]);
332
+ }, [text, resolvedSeed, glyphSeed]);
200
333
  const classes = ['inkra-pencil-text', ellipsis && 'inkra-pencil-text--ellipsis', className]
201
334
  .filter(Boolean).join(' ');
202
335
  const title = props.title ?? (ellipsis ? children : undefined);
@@ -0,0 +1,64 @@
1
+ import * as DialogPrimitive from '@radix-ui/react-dialog';
2
+ import type { ComponentPropsWithoutRef, ReactNode } from 'react';
3
+ import type { ButtonProps } from '../Button.js';
4
+ export interface AlertDialogRootProps extends Omit<DialogPrimitive.DialogProps, 'open' | 'defaultOpen' | 'onOpenChange' | 'modal'> {
5
+ defaultOpen?: boolean;
6
+ onOpenChange?: (open: boolean) => void;
7
+ open?: boolean;
8
+ pending?: boolean;
9
+ }
10
+ export declare function AlertDialogRoot({ defaultOpen, onOpenChange, open, pending, ...props }: AlertDialogRootProps): import("react").JSX.Element;
11
+ export type AlertDialogTriggerProps = DialogPrimitive.DialogTriggerProps;
12
+ export declare const AlertDialogTrigger: import("react").ForwardRefExoticComponent<DialogPrimitive.DialogTriggerProps & import("react").RefAttributes<HTMLButtonElement>>;
13
+ export type AlertDialogPortalProps = DialogPrimitive.DialogPortalProps;
14
+ export declare function AlertDialogPortal({ children, ...props }: AlertDialogPortalProps): import("react").JSX.Element;
15
+ export type AlertDialogBackdropProps = DialogPrimitive.DialogOverlayProps;
16
+ export declare const AlertDialogBackdrop: import("react").ForwardRefExoticComponent<DialogPrimitive.DialogOverlayProps & import("react").RefAttributes<HTMLDivElement>>;
17
+ export interface AlertDialogContentProps extends DialogPrimitive.DialogContentProps {
18
+ seed?: string | number;
19
+ }
20
+ export declare const AlertDialogContent: import("react").ForwardRefExoticComponent<AlertDialogContentProps & import("react").RefAttributes<HTMLDivElement>>;
21
+ export interface AlertDialogTitleProps extends Omit<ComponentPropsWithoutRef<typeof DialogPrimitive.Title>, 'children'> {
22
+ children: string;
23
+ seed?: string | number;
24
+ }
25
+ export declare const AlertDialogTitle: import("react").ForwardRefExoticComponent<AlertDialogTitleProps & import("react").RefAttributes<HTMLHeadingElement>>;
26
+ export interface AlertDialogDescriptionProps extends Omit<ComponentPropsWithoutRef<typeof DialogPrimitive.Description>, 'children'> {
27
+ children: string;
28
+ seed?: string | number;
29
+ }
30
+ export declare const AlertDialogDescription: import("react").ForwardRefExoticComponent<AlertDialogDescriptionProps & import("react").RefAttributes<HTMLParagraphElement>>;
31
+ export interface AlertDialogHeaderProps extends ComponentPropsWithoutRef<'div'> {
32
+ divided?: boolean;
33
+ }
34
+ export declare const AlertDialogHeader: import("react").ForwardRefExoticComponent<AlertDialogHeaderProps & import("react").RefAttributes<HTMLDivElement>>;
35
+ export type AlertDialogBodyProps = ComponentPropsWithoutRef<'div'>;
36
+ export declare const AlertDialogBody: import("react").ForwardRefExoticComponent<Omit<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "ref"> & import("react").RefAttributes<HTMLDivElement>>;
37
+ export interface AlertDialogFooterProps extends ComponentPropsWithoutRef<'div'> {
38
+ align?: 'start' | 'center' | 'end';
39
+ divided?: boolean;
40
+ }
41
+ export declare const AlertDialogFooter: import("react").ForwardRefExoticComponent<AlertDialogFooterProps & import("react").RefAttributes<HTMLDivElement>>;
42
+ export interface AlertDialogCancelProps extends Omit<ButtonProps, 'type'> {
43
+ children?: ReactNode;
44
+ }
45
+ export declare const AlertDialogCancel: import("react").ForwardRefExoticComponent<AlertDialogCancelProps & import("react").RefAttributes<HTMLButtonElement>>;
46
+ export interface AlertDialogActionProps extends Omit<ButtonProps, 'type'> {
47
+ children?: ReactNode;
48
+ closeOnAction?: boolean;
49
+ }
50
+ export declare const AlertDialogAction: import("react").ForwardRefExoticComponent<AlertDialogActionProps & import("react").RefAttributes<HTMLButtonElement>>;
51
+ export declare const AlertDialog: Readonly<{
52
+ Root: typeof AlertDialogRoot;
53
+ Trigger: import("react").ForwardRefExoticComponent<DialogPrimitive.DialogTriggerProps & import("react").RefAttributes<HTMLButtonElement>>;
54
+ Portal: typeof AlertDialogPortal;
55
+ Backdrop: import("react").ForwardRefExoticComponent<DialogPrimitive.DialogOverlayProps & import("react").RefAttributes<HTMLDivElement>>;
56
+ Content: import("react").ForwardRefExoticComponent<AlertDialogContentProps & import("react").RefAttributes<HTMLDivElement>>;
57
+ Header: import("react").ForwardRefExoticComponent<AlertDialogHeaderProps & import("react").RefAttributes<HTMLDivElement>>;
58
+ Body: import("react").ForwardRefExoticComponent<Omit<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "ref"> & import("react").RefAttributes<HTMLDivElement>>;
59
+ Footer: import("react").ForwardRefExoticComponent<AlertDialogFooterProps & import("react").RefAttributes<HTMLDivElement>>;
60
+ Title: import("react").ForwardRefExoticComponent<AlertDialogTitleProps & import("react").RefAttributes<HTMLHeadingElement>>;
61
+ Description: import("react").ForwardRefExoticComponent<AlertDialogDescriptionProps & import("react").RefAttributes<HTMLParagraphElement>>;
62
+ Cancel: import("react").ForwardRefExoticComponent<AlertDialogCancelProps & import("react").RefAttributes<HTMLButtonElement>>;
63
+ Action: import("react").ForwardRefExoticComponent<AlertDialogActionProps & import("react").RefAttributes<HTMLButtonElement>>;
64
+ }>;
@@ -0,0 +1,109 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import * as DialogPrimitive from '@radix-ui/react-dialog';
3
+ import { createContext, forwardRef, useContext, useId, useRef, useState, } from 'react';
4
+ import { Button } from '../Button.js';
5
+ import { PencilLine } from '../PencilLine.js';
6
+ import { PencilSurface } from '../PencilSurface.js';
7
+ import { PencilText } from '../PencilText.js';
8
+ import { PortalThemeBridge, PortalThemeProvider, usePortalTriggerRef, } from '../overlay/themeBridge.js';
9
+ const classes = (...values) => values.filter(Boolean).join(' ');
10
+ function assignRef(ref, value) {
11
+ if (typeof ref === 'function')
12
+ ref(value);
13
+ else if (ref)
14
+ ref.current = value;
15
+ }
16
+ const AlertDialogContext = createContext(null);
17
+ export function AlertDialogRoot({ defaultOpen = false, onOpenChange, open, pending = false, ...props }) {
18
+ const [uncontrolledOpen, setUncontrolledOpen] = useState(defaultOpen);
19
+ const cancelRef = useRef(null);
20
+ const controlled = open !== undefined;
21
+ const currentOpen = controlled ? open : uncontrolledOpen;
22
+ function handleOpenChange(nextOpen) {
23
+ if (!nextOpen && pending)
24
+ return;
25
+ if (!controlled)
26
+ setUncontrolledOpen(nextOpen);
27
+ onOpenChange?.(nextOpen);
28
+ }
29
+ return (_jsx(PortalThemeProvider, { children: _jsx(AlertDialogContext.Provider, { value: { cancelRef, pending }, children: _jsx(DialogPrimitive.Root, { ...props, modal: true, open: currentOpen, onOpenChange: handleOpenChange }) }) }));
30
+ }
31
+ export const AlertDialogTrigger = forwardRef(function AlertDialogTrigger(props, forwardedRef) {
32
+ return _jsx(DialogPrimitive.Trigger, { ...props, ref: usePortalTriggerRef(forwardedRef) });
33
+ });
34
+ export function AlertDialogPortal({ children, ...props }) {
35
+ return (_jsx(DialogPrimitive.Portal, { ...props, children: _jsx(PortalThemeBridge, { children: children }) }));
36
+ }
37
+ export const AlertDialogBackdrop = forwardRef(function AlertDialogBackdrop({ className, ...props }, ref) {
38
+ return (_jsx(DialogPrimitive.Overlay, { ...props, className: classes('inkra-alert-dialog__backdrop', className), ref: ref }));
39
+ });
40
+ export const AlertDialogContent = forwardRef(function AlertDialogContent({ children, className, onEscapeKeyDown, onInteractOutside, onOpenAutoFocus, onPointerDownOutside, seed, ...props }, forwardedRef) {
41
+ const context = useContext(AlertDialogContext);
42
+ const contentRef = useRef(null);
43
+ const id = useId();
44
+ return (_jsx(DialogPrimitive.Content, { ...props, role: "alertdialog", "aria-busy": context?.pending || undefined, className: classes('inkra-alert-dialog__content', className), "data-pending": context?.pending || undefined, ref: node => {
45
+ contentRef.current = node;
46
+ assignRef(forwardedRef, node);
47
+ }, onEscapeKeyDown: event => {
48
+ onEscapeKeyDown?.(event);
49
+ if (context?.pending)
50
+ event.preventDefault();
51
+ }, onInteractOutside: event => {
52
+ onInteractOutside?.(event);
53
+ event.preventDefault();
54
+ }, onPointerDownOutside: event => {
55
+ onPointerDownOutside?.(event);
56
+ event.preventDefault();
57
+ }, onOpenAutoFocus: event => {
58
+ onOpenAutoFocus?.(event);
59
+ if (event.defaultPrevented)
60
+ return;
61
+ event.preventDefault();
62
+ queueMicrotask(() => (context?.cancelRef.current ?? contentRef.current)?.focus());
63
+ }, children: _jsx(PencilSurface, { className: "inkra-alert-dialog__surface", radius: 8, seed: seed ?? `inkra-alert-dialog-${id}`, variant: "paper", children: _jsx("div", { className: "inkra-alert-dialog__layout", children: children }) }) }));
64
+ });
65
+ export const AlertDialogTitle = forwardRef(function AlertDialogTitle({ children, className, seed, ...props }, ref) {
66
+ const id = useId();
67
+ return (_jsx(DialogPrimitive.Title, { ...props, className: classes('inkra-alert-dialog__title', className), ref: ref, children: _jsx(PencilText, { seed: seed ?? `inkra-alert-dialog-title-${id}`, children: children }) }));
68
+ });
69
+ export const AlertDialogDescription = forwardRef(function AlertDialogDescription({ children, className, seed, ...props }, ref) {
70
+ const id = useId();
71
+ return (_jsx(DialogPrimitive.Description, { ...props, className: classes('inkra-alert-dialog__description', className), ref: ref, children: _jsx(PencilText, { seed: seed ?? `inkra-alert-dialog-description-${id}`, children: children }) }));
72
+ });
73
+ export const AlertDialogHeader = forwardRef(function AlertDialogHeader({ children, className, divided = true, ...props }, ref) {
74
+ const id = useId();
75
+ return (_jsxs("div", { ...props, className: classes('inkra-alert-dialog__header', className), "data-divided": divided || undefined, ref: ref, children: [_jsx("div", { className: "inkra-alert-dialog__heading", children: children }), divided ? _jsx(PencilLine, { className: "inkra-alert-dialog__divider", seed: `alert-header-${id}` }) : null] }));
76
+ });
77
+ export const AlertDialogBody = forwardRef(function AlertDialogBody({ className, ...props }, ref) {
78
+ return _jsx("div", { ...props, className: classes('inkra-alert-dialog__body', className), ref: ref });
79
+ });
80
+ export const AlertDialogFooter = forwardRef(function AlertDialogFooter({ align = 'end', children, className, divided = true, ...props }, ref) {
81
+ const id = useId();
82
+ return (_jsxs("div", { ...props, className: classes('inkra-alert-dialog__footer', className), "data-align": align, "data-divided": divided || undefined, ref: ref, children: [divided ? _jsx(PencilLine, { className: "inkra-alert-dialog__divider", seed: `alert-footer-${id}` }) : null, children] }));
83
+ });
84
+ export const AlertDialogCancel = forwardRef(function AlertDialogCancel({ disabled, variant = 'secondary', ...props }, forwardedRef) {
85
+ const context = useContext(AlertDialogContext);
86
+ return (_jsx(DialogPrimitive.Close, { asChild: true, children: _jsx(Button, { ...props, disabled: disabled || context?.pending, ref: node => {
87
+ assignRef(forwardedRef, node);
88
+ assignRef(context?.cancelRef, node);
89
+ }, type: "button", variant: variant }) }));
90
+ });
91
+ export const AlertDialogAction = forwardRef(function AlertDialogAction({ closeOnAction = true, disabled, loading, variant = 'destructive', ...props }, ref) {
92
+ const pending = useContext(AlertDialogContext)?.pending ?? false;
93
+ const button = (_jsx(Button, { ...props, "aria-busy": pending || undefined, disabled: disabled || pending, loading: loading || pending, ref: ref, type: "button", variant: variant }));
94
+ return closeOnAction ? _jsx(DialogPrimitive.Close, { asChild: true, children: button }) : button;
95
+ });
96
+ export const AlertDialog = Object.freeze({
97
+ Root: AlertDialogRoot,
98
+ Trigger: AlertDialogTrigger,
99
+ Portal: AlertDialogPortal,
100
+ Backdrop: AlertDialogBackdrop,
101
+ Content: AlertDialogContent,
102
+ Header: AlertDialogHeader,
103
+ Body: AlertDialogBody,
104
+ Footer: AlertDialogFooter,
105
+ Title: AlertDialogTitle,
106
+ Description: AlertDialogDescription,
107
+ Cancel: AlertDialogCancel,
108
+ Action: AlertDialogAction,
109
+ });
@@ -0,0 +1,60 @@
1
+ import type { AudioHTMLAttributes, HTMLAttributes, ReactNode } from 'react';
2
+ export type AudioPlayerStatus = 'idle' | 'loading' | 'ready' | 'playing' | 'paused' | 'buffering' | 'ended' | 'blocked' | 'error';
3
+ export interface AudioPlayerState {
4
+ status: AudioPlayerStatus;
5
+ currentTime: number;
6
+ duration: number;
7
+ buffered: number;
8
+ volume: number;
9
+ muted: boolean;
10
+ playbackRate: number;
11
+ canPlay: boolean;
12
+ error: MediaError | null;
13
+ }
14
+ export interface AudioPlayerControls {
15
+ audio: HTMLAudioElement | null;
16
+ state: AudioPlayerState;
17
+ play: () => Promise<void>;
18
+ pause: () => void;
19
+ togglePlay: () => Promise<void>;
20
+ seek: (time: number) => void;
21
+ setVolume: (volume: number) => void;
22
+ setMuted: (muted: boolean) => void;
23
+ setPlaybackRate: (rate: number) => void;
24
+ focus: () => void;
25
+ }
26
+ export interface AudioPlayerLabels {
27
+ play?: string;
28
+ pause?: string;
29
+ mute?: string;
30
+ unmute?: string;
31
+ seek?: string;
32
+ volume?: string;
33
+ rate?: string;
34
+ loading?: string;
35
+ blocked?: string;
36
+ error?: string;
37
+ time?: (current: number, duration: number) => string;
38
+ }
39
+ export interface AudioPlayerProps extends Omit<HTMLAttributes<HTMLDivElement>, 'onError' | 'onChange' | 'onVolumeChange'> {
40
+ src?: string;
41
+ preload?: AudioHTMLAttributes<HTMLAudioElement>['preload'];
42
+ autoPlay?: boolean;
43
+ crossOrigin?: AudioHTMLAttributes<HTMLAudioElement>['crossOrigin'];
44
+ defaultVolume?: number;
45
+ volume?: number;
46
+ defaultMuted?: boolean;
47
+ muted?: boolean;
48
+ defaultPlaybackRate?: number;
49
+ playbackRate?: number;
50
+ onVolumeChange?: (volume: number) => void;
51
+ onMutedChange?: (muted: boolean) => void;
52
+ onPlaybackRateChange?: (rate: number) => void;
53
+ onTimeChange?: (currentTime: number) => void;
54
+ onStateChange?: (state: AudioPlayerState) => void;
55
+ onError?: (error: MediaError | DOMException | null) => void;
56
+ labels?: AudioPlayerLabels;
57
+ renderControls?: (controls: AudioPlayerControls) => ReactNode;
58
+ audioProps?: Omit<AudioHTMLAttributes<HTMLAudioElement>, 'src' | 'controls' | 'autoPlay' | 'preload' | 'volume' | 'muted' | 'playbackRate' | 'onError'>;
59
+ }
60
+ export declare const AudioPlayer: import("react").ForwardRefExoticComponent<AudioPlayerProps & import("react").RefAttributes<AudioPlayerControls>>;