react-x11 2.25.0 → 2.26.1
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/package.json +1 -1
- package/src/cocoa/fonts.js +63 -0
- package/src/components/Select.js +22 -2
- package/src/types/components.d.ts +10 -0
package/package.json
CHANGED
package/src/cocoa/fonts.js
CHANGED
|
@@ -150,6 +150,31 @@ function unpackLines(lineData = NO_GEOMETRY, runData = NO_GEOMETRY) {
|
|
|
150
150
|
return lines;
|
|
151
151
|
}
|
|
152
152
|
|
|
153
|
+
/**
|
|
154
|
+
* The one line of an empty paragraph, placed the way the native places a
|
|
155
|
+
* line with text in it: a `lineHeight` multiplies the face's natural line,
|
|
156
|
+
* and the leading is split either side of the glyphs (CSS half-leading,
|
|
157
|
+
* which ntk and `@windowkit/appkit` >= 0.15 both follow).
|
|
158
|
+
*/
|
|
159
|
+
function blankLine(metrics, lineHeight, x) {
|
|
160
|
+
const { ascent, descent } = metrics;
|
|
161
|
+
const natural = metrics.lineHeight ?? ascent + descent;
|
|
162
|
+
const multiple = lineHeight >= 0 && lineHeight < Infinity ? lineHeight : 1;
|
|
163
|
+
const height = natural * multiple;
|
|
164
|
+
return {
|
|
165
|
+
x,
|
|
166
|
+
y: 0,
|
|
167
|
+
width: 0,
|
|
168
|
+
height,
|
|
169
|
+
baseline: (height - (ascent + descent)) / 2 + ascent,
|
|
170
|
+
ascent,
|
|
171
|
+
descent,
|
|
172
|
+
start: 0,
|
|
173
|
+
end: 0,
|
|
174
|
+
runs: [],
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
|
|
153
178
|
/** Whether two spans read the same, field for field. */
|
|
154
179
|
function sameSpan(a, b) {
|
|
155
180
|
if (a === b) return true;
|
|
@@ -302,6 +327,19 @@ class CocoaTextLayout {
|
|
|
302
327
|
ctx._drawLayout(this, x, y);
|
|
303
328
|
}
|
|
304
329
|
|
|
330
|
+
/**
|
|
331
|
+
* An empty paragraph as ntk lays it out: one line with no runs, as tall as
|
|
332
|
+
* a line of the face it would have been set in. CoreText sets no line for
|
|
333
|
+
* no text, and a layout with no lines has no height and a caret of none —
|
|
334
|
+
* so an empty field's caret was its three-pixel mark padding either side
|
|
335
|
+
* of nothing, and grew to the line the moment the first letter arrived.
|
|
336
|
+
*/
|
|
337
|
+
_setBlank(line) {
|
|
338
|
+
this.lines = [line];
|
|
339
|
+
this.height = line.height;
|
|
340
|
+
this._blank = line;
|
|
341
|
+
}
|
|
342
|
+
|
|
305
343
|
/** Code point boundary nearest the point, in layout coordinates. */
|
|
306
344
|
indexAt(x, y) {
|
|
307
345
|
return this._cpOf(this._native.layoutIndexAt(this._handle, x, y));
|
|
@@ -309,6 +347,13 @@ class CocoaTextLayout {
|
|
|
309
347
|
|
|
310
348
|
/** Caret rect for a code-point index: { x, y, height }. */
|
|
311
349
|
caretPosition(cp) {
|
|
350
|
+
// the rect the native gives a caret on a line with text in it — the
|
|
351
|
+
// line's box — so typing the first letter moves the caret and nothing
|
|
352
|
+
// else
|
|
353
|
+
const blank = this._blank;
|
|
354
|
+
if (blank) {
|
|
355
|
+
return { x: blank.x, y: blank.y, height: blank.height, line: 0 };
|
|
356
|
+
}
|
|
312
357
|
return this._native.layoutCaret(this._handle, this._cuOf(cp));
|
|
313
358
|
}
|
|
314
359
|
|
|
@@ -1315,6 +1360,24 @@ export class CocoaFontManager {
|
|
|
1315
1360
|
minContent ? laid.map((span) => span.text).join('') : text,
|
|
1316
1361
|
);
|
|
1317
1362
|
layout._contextInk = contextInk;
|
|
1363
|
+
if (!text) {
|
|
1364
|
+
const first = spans[0] ?? {};
|
|
1365
|
+
const size = first.size ?? base.size ?? 14;
|
|
1366
|
+
const face = first.font ?? base.font;
|
|
1367
|
+
const metrics = (
|
|
1368
|
+
typeof face?.metrics === 'function'
|
|
1369
|
+
? face
|
|
1370
|
+
: this.match(first.family ?? base.family, {
|
|
1371
|
+
weight: first.weight ?? base.weight,
|
|
1372
|
+
style: first.style ?? base.style,
|
|
1373
|
+
})
|
|
1374
|
+
).metrics(size);
|
|
1375
|
+
const container =
|
|
1376
|
+
Number.isFinite(maxWidth) && maxWidth > 0 ? maxWidth : 0;
|
|
1377
|
+
layout._setBlank(
|
|
1378
|
+
blankLine(metrics, lineHeight, flushFor(align, direction) * container),
|
|
1379
|
+
);
|
|
1380
|
+
}
|
|
1318
1381
|
if (!minContent) {
|
|
1319
1382
|
const runs = new Map();
|
|
1320
1383
|
layout._hangRuns(sources, (i, rtl) => {
|
package/src/components/Select.js
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
import React, { useEffect, useRef, useState } from 'react';
|
|
6
6
|
import { useAppOrNull } from '../appcontext.js';
|
|
7
|
+
import { flattenStyle } from '../styles.js';
|
|
7
8
|
import { capBand, capTrim, rowRadius, useTheme } from './theme.js';
|
|
8
9
|
import { Icon } from './Icon.js';
|
|
9
10
|
import {
|
|
@@ -282,6 +283,14 @@ function Option({
|
|
|
282
283
|
* the active option with wrapping; Home/End jump to the ends; Enter/Space
|
|
283
284
|
* pick the active option (or open the menu when closed); Escape closes.
|
|
284
285
|
* The active option is scrolled into view.
|
|
286
|
+
*
|
|
287
|
+
* `labelStyle` and `chevronStyle` are style slots over the caption and the
|
|
288
|
+
* chevron, each merged over the part's own default, as `<Slider>`'s are over
|
|
289
|
+
* its parts — so a select that has to wear someone else's look, a toolbar's
|
|
290
|
+
* or a web page's, is the widget restyled, with `style` on the trigger. The
|
|
291
|
+
* caption's `fontSize` is read back: the chevron is as tall as the capitals
|
|
292
|
+
* beside it. Any slot also chooses the drawn trigger over the platform's
|
|
293
|
+
* popup bezel, whose pixels no style reaches.
|
|
285
294
|
*/
|
|
286
295
|
export function Select({
|
|
287
296
|
value,
|
|
@@ -291,11 +300,18 @@ export function Select({
|
|
|
291
300
|
placeholder = 'Select…',
|
|
292
301
|
native,
|
|
293
302
|
style,
|
|
303
|
+
labelStyle,
|
|
304
|
+
chevronStyle,
|
|
294
305
|
...boxProps
|
|
295
306
|
}) {
|
|
296
307
|
const theme = useTheme();
|
|
297
308
|
const app = useAppOrNull();
|
|
298
|
-
const
|
|
309
|
+
const styled = Boolean(labelStyle || chevronStyle);
|
|
310
|
+
const nativeControls = useNativeControls(styled ? false : native);
|
|
311
|
+
// The size the caption is set in, where its slot names one: the chevron is
|
|
312
|
+
// measured against those capitals, and an `<Icon>`'s size does not inherit.
|
|
313
|
+
const ownSize = flattenStyle(labelStyle).fontSize;
|
|
314
|
+
const captionSize = typeof ownSize === 'number' ? ownSize : theme.fontSize;
|
|
299
315
|
const [open, setOpen] = useState(false);
|
|
300
316
|
const [anchor, setAnchor] = useState(null);
|
|
301
317
|
const [focused, setFocused] = useState(false);
|
|
@@ -572,6 +588,7 @@ export function Select({
|
|
|
572
588
|
ONE_LINE,
|
|
573
589
|
nativeControls && nativeTitleStyle('regular'),
|
|
574
590
|
{ color: current ? theme.text : theme.textMuted },
|
|
591
|
+
labelStyle,
|
|
575
592
|
],
|
|
576
593
|
},
|
|
577
594
|
current ? current.label : placeholder,
|
|
@@ -593,8 +610,11 @@ export function Select({
|
|
|
593
610
|
})
|
|
594
611
|
: h(Icon, {
|
|
595
612
|
name: 'chevronDown',
|
|
596
|
-
size: capBand(
|
|
613
|
+
size: capBand(captionSize),
|
|
597
614
|
color: theme.textMuted,
|
|
615
|
+
// `display: 'none'` leaves it out, for a trigger whose arrow is
|
|
616
|
+
// drawn by something else
|
|
617
|
+
style: chevronStyle,
|
|
598
618
|
}),
|
|
599
619
|
open &&
|
|
600
620
|
anchor &&
|
|
@@ -511,6 +511,16 @@ export interface SelectProps<T = unknown>
|
|
|
511
511
|
onChange?: (ev: WidgetChangeEvent<T>) => void;
|
|
512
512
|
placeholder?: string;
|
|
513
513
|
style?: StyleProp;
|
|
514
|
+
/**
|
|
515
|
+
* The caption's style — the value, or the placeholder — over the palette's
|
|
516
|
+
* `text` (`textMuted` for the placeholder). Its `fontSize`, as a number, is
|
|
517
|
+
* the one the chevron is sized against. Any slot chooses the drawn trigger
|
|
518
|
+
* over the platform's popup bezel.
|
|
519
|
+
*/
|
|
520
|
+
labelStyle?: StyleProp;
|
|
521
|
+
/** The chevron's style, over `textMuted` at the caption's cap height.
|
|
522
|
+
* `display: 'none'` leaves it out. */
|
|
523
|
+
chevronStyle?: StyleProp;
|
|
514
524
|
}
|
|
515
525
|
export function Select<T = unknown>(props: SelectProps<T>): ReactNode;
|
|
516
526
|
|