@scalewing/react 1.5.0 → 1.6.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/CHANGELOG.md +42 -0
- package/README.md +3 -1
- package/dist/components/ActionMenu.js +3 -1
- package/dist/components/Dialog.d.ts +11 -1
- package/dist/components/Dialog.js +12 -28
- package/dist/components/Tooltip.js +6 -2
- package/dist/components/dialog-close-requests.d.ts +38 -0
- package/dist/components/dialog-close-requests.js +141 -0
- package/dist/css/css-data.js +3 -2
- package/dist/styles.css +3 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,47 @@
|
|
|
1
1
|
# @scalewing/react
|
|
2
2
|
|
|
3
|
+
## 1.6.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 22ae9c2: `TableCell numeric` and `TableCell align="end"` cells now line up at the end of
|
|
8
|
+
the cell again. The base `.sw-table th, .sw-table td { text-align: start }` rule
|
|
9
|
+
was more specific than `.sw-table-numeric` and `.sw-table-end`, so those cells
|
|
10
|
+
had been aligned to the start since the table styles were added. The alignment
|
|
11
|
+
rules are now `.sw-table .sw-table-numeric` and `.sw-table .sw-table-end`. No
|
|
12
|
+
prop changes.
|
|
13
|
+
|
|
14
|
+
## 1.6.0
|
|
15
|
+
|
|
16
|
+
### Minor Changes
|
|
17
|
+
|
|
18
|
+
- a42f7dd: `Dialog` is now fully controlled by `open`. Escape, a platform close request,
|
|
19
|
+
a backdrop press and a `<form method="dialog">` submit (the form's `method` or
|
|
20
|
+
the submitter's `formmethod`) are each prevented and ask `onClose()`; none of
|
|
21
|
+
them closes the native `<dialog>` itself. A dialog whose consumer keeps `open`
|
|
22
|
+
true (for example while a form is saving) stays shown, including on a repeated
|
|
23
|
+
Escape in Chromium. A consumer `onKeyDown`, `onCancel`, `onSubmit` or
|
|
24
|
+
`onPointerDown` that prevents the event vetoes that request (`onPointerDown`
|
|
25
|
+
used to be dropped). Escape in a search field that holds text clears the field
|
|
26
|
+
first, and a descendant's own `cancel` (a dismissed file picker) is not a close
|
|
27
|
+
request. A backdrop press counts only on the
|
|
28
|
+
dialog element itself, so a nested dialog's backdrop or an overflowing popover
|
|
29
|
+
does not close the outer dialog. See `docs/requests/teisoro-dialog-close.md`.
|
|
30
|
+
|
|
31
|
+
`Tooltip` now prevents the Escape it uses while it is visible, so one Escape
|
|
32
|
+
hides a tooltip inside a `Dialog` without also closing the dialog.
|
|
33
|
+
|
|
34
|
+
`ActionMenu` returns focus to its trigger before running a selected command,
|
|
35
|
+
as Escape already did, so a `Dialog` opened from a command returns focus to
|
|
36
|
+
the trigger when it closes (`docs/requests/teisoro-action-menu.md`).
|
|
37
|
+
|
|
38
|
+
Behavior change: `Dialog` no longer calls `onClose` when `open` turns false
|
|
39
|
+
(it used to echo the native `close` event). Migration: if you ran cleanup in
|
|
40
|
+
`onClose` after setting `open` to false yourself, run it where you set `open`
|
|
41
|
+
to false.
|
|
42
|
+
|
|
43
|
+
No prop or type is added or removed.
|
|
44
|
+
|
|
3
45
|
## 1.5.0
|
|
4
46
|
|
|
5
47
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -48,7 +48,9 @@ Import the CSS once at the application entry. Do not copy it into your source tr
|
|
|
48
48
|
|
|
49
49
|
`Field` supports optional `description`, `error`, and `required` on one native input, select, or textarea child. It preserves an existing `aria-describedby`, appends a stable message ID, and replaces the hint with an alert when an error is supplied. `error` sets `aria-invalid` and token-owned invalid styling. The consumer owns validation and localized messages.
|
|
50
50
|
|
|
51
|
-
`ActionMenu` opens independent commands from a labelled button. Provide localized command labels and callbacks; use `disabled` for unavailable commands and `destructive` for a dangerous command's presentation. Escape
|
|
51
|
+
`ActionMenu` opens independent commands from a labelled button. Provide localized command labels and callbacks; use `disabled` for unavailable commands and `destructive` for a dangerous command's presentation. Escape and choosing a command both return focus to the trigger (before the command runs, so a `Dialog` it opens hands focus back to the trigger on close), and outside interaction dismisses the menu.
|
|
52
|
+
|
|
53
|
+
`Dialog` is a modal `<dialog>` controlled by `open`. Escape, a backdrop press, a platform close request, and a `<form method="dialog">` submit (or a submitter with `formmethod="dialog"`) are each prevented and call `onClose`; none of them closes the dialog itself. A consumer `onKeyDown`, `onCancel`, `onSubmit` or `onPointerDown` that prevents the event vetoes that request. Escape in a search field that holds text clears the field first, and a descendant's own `cancel` (a dismissed file picker) is not a close request. Set `open` to false to close it, or keep it true (for example while a form is saving) and it stays shown. `onClose` is not called when `open` turns false, and it is required: a dialog that must not be dismissed passes a callback that keeps `open` true.
|
|
52
54
|
|
|
53
55
|
`Switch` is a controlled on/off input. It keeps native checkbox keyboard behavior and exposes switch semantics. Provide the current `checked` value and update it in `onCheckedChange`.
|
|
54
56
|
|
|
@@ -130,7 +130,9 @@ export function ActionMenu({ label, items, trigger, disabled = false, }) {
|
|
|
130
130
|
function select(item) {
|
|
131
131
|
if (!openRef.current || !enabled || item.disabled)
|
|
132
132
|
return;
|
|
133
|
-
|
|
133
|
+
// Focus leaves the hidden item first, so a dialog the command opens
|
|
134
|
+
// records the trigger as its opener and returns focus there on close.
|
|
135
|
+
close(true);
|
|
134
136
|
item.onSelect();
|
|
135
137
|
}
|
|
136
138
|
return (_jsxs("div", { className: "sw-action-menu", ref: rootRef, children: [_jsx("button", { "aria-controls": open ? menuId : undefined, "aria-expanded": open, "aria-haspopup": "menu", "aria-label": label, className: "sw-action-menu-trigger", disabled: !enabled, onClick: () => (open ? close(true) : openMenu()), onKeyDown: onTriggerKeyDown, ref: triggerRef, type: "button", children: trigger ?? label }), _jsx("div", { "aria-label": label, className: "sw-action-menu-list", hidden: !open, id: menuId, onKeyDown: onMenuKeyDown, ref: menuRef, role: "menu", children: items.map((item, index) => (_jsxs("button", { className: cx('sw-action-menu-item', item.destructive && 'sw-action-menu-item-danger'), disabled: item.disabled, onClick: () => select(item), onFocus: () => setFocusIndex(index), onKeyDown: (event) => {
|
|
@@ -5,10 +5,20 @@ import { type DialogHTMLAttributes, type ReactNode } from 'react';
|
|
|
5
5
|
*/
|
|
6
6
|
export type DialogSize = 'md' | 'lg';
|
|
7
7
|
export type DialogProps = Omit<DialogHTMLAttributes<HTMLDialogElement>, 'onClose' | 'open' | 'title' | 'children'> & {
|
|
8
|
+
/** Whether the dialog is shown. The element follows it and nothing else. */
|
|
8
9
|
open: boolean;
|
|
10
|
+
/**
|
|
11
|
+
* Asked on every close request: Escape, a backdrop press, a platform close
|
|
12
|
+
* request, or a `<form method="dialog">` submit. Each is prevented, so the
|
|
13
|
+
* element does not close itself. Set `open` to false to close; keep it true
|
|
14
|
+
* (for example while saving) and the dialog stays shown. It is not called
|
|
15
|
+
* when `open` turns false. A consumer `onKeyDown`, `onCancel`, `onSubmit`
|
|
16
|
+
* or `onPointerDown` that prevents the event vetoes that request. It is required: a dialog
|
|
17
|
+
* that must not be dismissed passes a callback that keeps `open` true.
|
|
18
|
+
*/
|
|
9
19
|
onClose: () => void;
|
|
10
20
|
size?: DialogSize;
|
|
11
21
|
title: string;
|
|
12
22
|
children: ReactNode;
|
|
13
23
|
};
|
|
14
|
-
export declare function Dialog({ children, className, onClose, open, size, title, ...rest }: DialogProps): import("react").JSX.Element;
|
|
24
|
+
export declare function Dialog({ children, className, onCancel, onClose, onKeyDown, onPointerDown, onSubmit, open, size, title, ...rest }: DialogProps): import("react").JSX.Element;
|
|
@@ -1,36 +1,20 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
-
import {
|
|
3
|
+
import { useId } from 'react';
|
|
4
4
|
import { cx } from '../class-names.js';
|
|
5
5
|
import { spacingClassNames } from '../spacing-classes.js';
|
|
6
|
+
import { useDialogCloseRequests } from './dialog-close-requests.js';
|
|
6
7
|
import { Stack } from './Stack.js';
|
|
7
8
|
import { Text } from './Text.js';
|
|
8
|
-
export function Dialog({ children, className, onClose, open, size = 'md', title, ...rest }) {
|
|
9
|
-
const nodeRef = useRef(null);
|
|
9
|
+
export function Dialog({ children, className, onCancel, onClose, onKeyDown, onPointerDown, onSubmit, open, size = 'md', title, ...rest }) {
|
|
10
10
|
const titleId = useId();
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
return;
|
|
21
|
-
}
|
|
22
|
-
if (node.open) {
|
|
23
|
-
node.close();
|
|
24
|
-
}
|
|
25
|
-
}, [open]);
|
|
26
|
-
return (_jsx("dialog", { ...rest, ref: nodeRef, "aria-labelledby": titleId, className: cx('sw-dialog', size === 'lg' && 'sw-dialog-lg', ...spacingClassNames({ padding: 5 }), className), onClose: onClose, onPointerDown: (event) => {
|
|
27
|
-
const rect = event.currentTarget.getBoundingClientRect();
|
|
28
|
-
const inside = event.clientX >= rect.left &&
|
|
29
|
-
event.clientX <= rect.right &&
|
|
30
|
-
event.clientY >= rect.top &&
|
|
31
|
-
event.clientY <= rect.bottom;
|
|
32
|
-
if (!inside) {
|
|
33
|
-
onClose();
|
|
34
|
-
}
|
|
35
|
-
}, children: _jsxs(Stack, { gap: 4, children: [_jsx(Text, { id: titleId, variant: "title", children: title }), children] }) }));
|
|
11
|
+
const { nodeRef, handlers } = useDialogCloseRequests({
|
|
12
|
+
open,
|
|
13
|
+
onClose,
|
|
14
|
+
onCancel,
|
|
15
|
+
onKeyDown,
|
|
16
|
+
onPointerDown,
|
|
17
|
+
onSubmit,
|
|
18
|
+
});
|
|
19
|
+
return (_jsx("dialog", { ...rest, ref: nodeRef, "aria-labelledby": titleId, className: cx('sw-dialog', size === 'lg' && 'sw-dialog-lg', ...spacingClassNames({ padding: 5 }), className), ...handlers, children: _jsxs(Stack, { gap: 4, children: [_jsx(Text, { id: titleId, variant: "title", children: title }), children] }) }));
|
|
36
20
|
}
|
|
@@ -25,8 +25,12 @@ export function Tooltip({ content, trigger }) {
|
|
|
25
25
|
if (!event.currentTarget.contains(event.relatedTarget))
|
|
26
26
|
setOpen(false);
|
|
27
27
|
}, onFocusCapture: () => setOpen(true), onKeyDownCapture: (event) => {
|
|
28
|
-
|
|
29
|
-
|
|
28
|
+
// Escape hides a visible tooltip and nothing else, so a surrounding
|
|
29
|
+
// Dialog does not also close. A hidden tooltip leaves Escape alone.
|
|
30
|
+
if (event.key !== 'Escape' || !open)
|
|
31
|
+
return;
|
|
32
|
+
event.preventDefault();
|
|
33
|
+
setOpen(false);
|
|
30
34
|
}, onPointerEnter: (event) => {
|
|
31
35
|
if (event.pointerType === 'mouse' || event.pointerType === 'pen')
|
|
32
36
|
setOpen(true);
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { type FormEvent, type FormEventHandler, type KeyboardEvent, type KeyboardEventHandler, type PointerEvent, type PointerEventHandler, type ReactEventHandler, type SyntheticEvent } from 'react';
|
|
2
|
+
type CloseRequestOptions = {
|
|
3
|
+
open: boolean;
|
|
4
|
+
onClose: () => void;
|
|
5
|
+
onCancel?: ReactEventHandler<HTMLDialogElement>;
|
|
6
|
+
onKeyDown?: KeyboardEventHandler<HTMLDialogElement>;
|
|
7
|
+
onPointerDown?: PointerEventHandler<HTMLDialogElement>;
|
|
8
|
+
onSubmit?: FormEventHandler<HTMLDialogElement>;
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* Keeps a native modal `<dialog>` controlled by `open`.
|
|
12
|
+
*
|
|
13
|
+
* Every close request (Escape, a platform close request such as Android back,
|
|
14
|
+
* a backdrop press, a `<form method="dialog">` submit) is prevented and
|
|
15
|
+
* becomes one `onClose()` call. The caller answers by setting `open` to
|
|
16
|
+
* false, or keeps it true (for example while a form is saving) and the
|
|
17
|
+
* dialog stays shown without being closed and shown again.
|
|
18
|
+
*
|
|
19
|
+
* Escape is taken at `keydown`, before the browser turns it into a close
|
|
20
|
+
* request: Chromium only lets a page cancel one close request per user
|
|
21
|
+
* activation, so a second Escape would otherwise close the element even
|
|
22
|
+
* though its `cancel` event was prevented. Two closes cannot be prevented: a
|
|
23
|
+
* repeated platform close request whose `cancel` is not cancelable, and an
|
|
24
|
+
* Escape that never reaches the dialog (focus fell to the body). Those are
|
|
25
|
+
* reconciled with `open` after the caller's update, which shows the dialog
|
|
26
|
+
* again only if `open` is still true.
|
|
27
|
+
*/
|
|
28
|
+
export declare function useDialogCloseRequests({ open, onClose, onCancel, onKeyDown, onPointerDown, onSubmit, }: CloseRequestOptions): {
|
|
29
|
+
nodeRef: import("react").RefObject<HTMLDialogElement | null>;
|
|
30
|
+
handlers: {
|
|
31
|
+
onCancel: (event: SyntheticEvent<HTMLDialogElement>) => void;
|
|
32
|
+
onClose: (event: SyntheticEvent<HTMLDialogElement>) => void;
|
|
33
|
+
onKeyDown: (event: KeyboardEvent<HTMLDialogElement>) => void;
|
|
34
|
+
onPointerDown: (event: PointerEvent<HTMLDialogElement>) => void;
|
|
35
|
+
onSubmit: (event: FormEvent<HTMLDialogElement>) => void;
|
|
36
|
+
};
|
|
37
|
+
};
|
|
38
|
+
export {};
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { useEffect, useRef, useState, } from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* A press on the backdrop: the target is the dialog element itself (not a
|
|
4
|
+
* child, a nested dialog's backdrop, or a popover that overflows the box)
|
|
5
|
+
* and the point lies outside the dialog's box.
|
|
6
|
+
*/
|
|
7
|
+
function isBackdropPress(event) {
|
|
8
|
+
if (event.target !== event.currentTarget)
|
|
9
|
+
return false;
|
|
10
|
+
const rect = event.currentTarget.getBoundingClientRect();
|
|
11
|
+
return (event.clientX < rect.left ||
|
|
12
|
+
event.clientX > rect.right ||
|
|
13
|
+
event.clientY < rect.top ||
|
|
14
|
+
event.clientY > rect.bottom);
|
|
15
|
+
}
|
|
16
|
+
/** Escape typed into an input method (Safari reports it as keyCode 229). */
|
|
17
|
+
function isComposing(event) {
|
|
18
|
+
return event.nativeEvent.isComposing || event.keyCode === 229;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Escape in a search field that holds text clears the field (the browser's
|
|
22
|
+
* own default action) and does not close the dialog.
|
|
23
|
+
*/
|
|
24
|
+
function clearsSearchField(event) {
|
|
25
|
+
const target = event.target;
|
|
26
|
+
return (target instanceof HTMLInputElement &&
|
|
27
|
+
target.type === 'search' &&
|
|
28
|
+
target.value !== '');
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* A submit that the browser would turn into closing this dialog: the
|
|
32
|
+
* submitter's `formmethod`, or else the form's `method`, is `dialog`, and the
|
|
33
|
+
* form belongs to this dialog rather than to a nested one.
|
|
34
|
+
*/
|
|
35
|
+
function isDialogSubmit(event) {
|
|
36
|
+
const form = event.target;
|
|
37
|
+
if (!(form instanceof HTMLFormElement))
|
|
38
|
+
return false;
|
|
39
|
+
if (form.closest('dialog') !== event.currentTarget)
|
|
40
|
+
return false;
|
|
41
|
+
const submitter = event.nativeEvent.submitter;
|
|
42
|
+
const method = submitter?.getAttribute('formmethod') ?? form.getAttribute('method');
|
|
43
|
+
return method?.trim().toLowerCase() === 'dialog';
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Keeps a native modal `<dialog>` controlled by `open`.
|
|
47
|
+
*
|
|
48
|
+
* Every close request (Escape, a platform close request such as Android back,
|
|
49
|
+
* a backdrop press, a `<form method="dialog">` submit) is prevented and
|
|
50
|
+
* becomes one `onClose()` call. The caller answers by setting `open` to
|
|
51
|
+
* false, or keeps it true (for example while a form is saving) and the
|
|
52
|
+
* dialog stays shown without being closed and shown again.
|
|
53
|
+
*
|
|
54
|
+
* Escape is taken at `keydown`, before the browser turns it into a close
|
|
55
|
+
* request: Chromium only lets a page cancel one close request per user
|
|
56
|
+
* activation, so a second Escape would otherwise close the element even
|
|
57
|
+
* though its `cancel` event was prevented. Two closes cannot be prevented: a
|
|
58
|
+
* repeated platform close request whose `cancel` is not cancelable, and an
|
|
59
|
+
* Escape that never reaches the dialog (focus fell to the body). Those are
|
|
60
|
+
* reconciled with `open` after the caller's update, which shows the dialog
|
|
61
|
+
* again only if `open` is still true.
|
|
62
|
+
*/
|
|
63
|
+
export function useDialogCloseRequests({ open, onClose, onCancel, onKeyDown, onPointerDown, onSubmit, }) {
|
|
64
|
+
const nodeRef = useRef(null);
|
|
65
|
+
const openRef = useRef(open);
|
|
66
|
+
// The last `cancel` could not be prevented, so its `close` follows and
|
|
67
|
+
// `onClose` has already been asked.
|
|
68
|
+
const forcedCloseRef = useRef(false);
|
|
69
|
+
const [browserCloses, setBrowserCloses] = useState(0);
|
|
70
|
+
useEffect(() => {
|
|
71
|
+
openRef.current = open;
|
|
72
|
+
const node = nodeRef.current;
|
|
73
|
+
if (!node)
|
|
74
|
+
return;
|
|
75
|
+
if (open && !node.open)
|
|
76
|
+
node.showModal();
|
|
77
|
+
if (!open && node.open)
|
|
78
|
+
node.close();
|
|
79
|
+
}, [open, browserCloses]);
|
|
80
|
+
function handleKeyDown(event) {
|
|
81
|
+
onKeyDown?.(event);
|
|
82
|
+
// A nested control (a menu, a listbox, a tooltip, an inner dialog) or the
|
|
83
|
+
// consumer that used Escape prevents it.
|
|
84
|
+
if (event.key !== 'Escape' ||
|
|
85
|
+
event.defaultPrevented ||
|
|
86
|
+
isComposing(event) ||
|
|
87
|
+
clearsSearchField(event)) {
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
event.preventDefault();
|
|
91
|
+
onClose();
|
|
92
|
+
}
|
|
93
|
+
function handleCancel(event) {
|
|
94
|
+
onCancel?.(event);
|
|
95
|
+
// A descendant's own `cancel` (a file picker dismissed from an
|
|
96
|
+
// `<input type="file">` bubbles one) is not a close request for the dialog.
|
|
97
|
+
if (event.target !== event.currentTarget)
|
|
98
|
+
return;
|
|
99
|
+
const vetoed = event.defaultPrevented;
|
|
100
|
+
event.preventDefault();
|
|
101
|
+
if (!event.cancelable)
|
|
102
|
+
forcedCloseRef.current = true;
|
|
103
|
+
if (!vetoed)
|
|
104
|
+
onClose();
|
|
105
|
+
}
|
|
106
|
+
function handleSubmit(event) {
|
|
107
|
+
onSubmit?.(event);
|
|
108
|
+
if (event.defaultPrevented || !isDialogSubmit(event))
|
|
109
|
+
return;
|
|
110
|
+
event.preventDefault();
|
|
111
|
+
onClose();
|
|
112
|
+
}
|
|
113
|
+
function handleClose(event) {
|
|
114
|
+
// A nested dialog's `close` is not this dialog's.
|
|
115
|
+
if (event.target !== event.currentTarget)
|
|
116
|
+
return;
|
|
117
|
+
const forced = forcedCloseRef.current;
|
|
118
|
+
forcedCloseRef.current = false;
|
|
119
|
+
// Closed by the `open` prop, or a stale event after it opened again.
|
|
120
|
+
if (!openRef.current || nodeRef.current?.open)
|
|
121
|
+
return;
|
|
122
|
+
if (!forced)
|
|
123
|
+
onClose();
|
|
124
|
+
setBrowserCloses((count) => count + 1);
|
|
125
|
+
}
|
|
126
|
+
function handlePointerDown(event) {
|
|
127
|
+
onPointerDown?.(event);
|
|
128
|
+
if (!event.defaultPrevented && isBackdropPress(event))
|
|
129
|
+
onClose();
|
|
130
|
+
}
|
|
131
|
+
return {
|
|
132
|
+
nodeRef,
|
|
133
|
+
handlers: {
|
|
134
|
+
onCancel: handleCancel,
|
|
135
|
+
onClose: handleClose,
|
|
136
|
+
onKeyDown: handleKeyDown,
|
|
137
|
+
onPointerDown: handlePointerDown,
|
|
138
|
+
onSubmit: handleSubmit,
|
|
139
|
+
},
|
|
140
|
+
};
|
|
141
|
+
}
|
package/dist/css/css-data.js
CHANGED
|
@@ -166,11 +166,12 @@ export function cssDataClasses() {
|
|
|
166
166
|
line-height: ${caption.lineHeight}px;
|
|
167
167
|
}
|
|
168
168
|
|
|
169
|
-
.sw-table-
|
|
169
|
+
/* Descendant form outranks .sw-table th / .sw-table td, which set text-align: start. */
|
|
170
|
+
.sw-table .sw-table-end {
|
|
170
171
|
text-align: end;
|
|
171
172
|
}
|
|
172
173
|
|
|
173
|
-
.sw-table-numeric {
|
|
174
|
+
.sw-table .sw-table-numeric {
|
|
174
175
|
font-variant-numeric: tabular-nums;
|
|
175
176
|
text-align: end;
|
|
176
177
|
}
|
package/dist/styles.css
CHANGED
|
@@ -1187,11 +1187,12 @@ body {
|
|
|
1187
1187
|
line-height: 18px;
|
|
1188
1188
|
}
|
|
1189
1189
|
|
|
1190
|
-
.sw-table-
|
|
1190
|
+
/* Descendant form outranks .sw-table th / .sw-table td, which set text-align: start. */
|
|
1191
|
+
.sw-table .sw-table-end {
|
|
1191
1192
|
text-align: end;
|
|
1192
1193
|
}
|
|
1193
1194
|
|
|
1194
|
-
.sw-table-numeric {
|
|
1195
|
+
.sw-table .sw-table-numeric {
|
|
1195
1196
|
font-variant-numeric: tabular-nums;
|
|
1196
1197
|
text-align: end;
|
|
1197
1198
|
}
|