@unofficialbox/box-open-elements 0.11.0 → 0.13.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/dist/components/feedback/alert.d.ts +2 -0
- package/dist/components/feedback/alert.js +41 -8
- package/dist/components/feedback/indicator.d.ts +34 -0
- package/dist/components/feedback/indicator.js +167 -0
- package/dist/components/feedback/nudge.js +0 -1
- package/dist/components/feedback/toast.d.ts +4 -5
- package/dist/components/feedback/toast.js +19 -46
- package/dist/components/feedback/tone.d.ts +18 -0
- package/dist/components/feedback/tone.js +23 -0
- package/dist/components/forms/calendar.d.ts +25 -0
- package/dist/components/forms/calendar.js +94 -2
- package/dist/components/forms/tile-group.d.ts +58 -0
- package/dist/components/forms/tile-group.js +280 -0
- package/dist/components/output/code-block.d.ts +49 -0
- package/dist/components/output/code-block.js +236 -0
- package/dist/components/output/formatted-date.d.ts +38 -0
- package/dist/components/output/formatted-date.js +90 -0
- package/dist/components/output/formatted-file-size.d.ts +31 -0
- package/dist/components/output/formatted-file-size.js +61 -0
- package/dist/components/output/formatted-number.d.ts +37 -0
- package/dist/components/output/formatted-number.js +106 -0
- package/dist/components/output/formatted-value.d.ts +44 -0
- package/dist/components/output/formatted-value.js +100 -0
- package/dist/components/output/index.d.ts +6 -0
- package/dist/components/output/index.js +9 -0
- package/dist/components/output/relative-time.d.ts +41 -0
- package/dist/components/output/relative-time.js +82 -0
- package/dist/element-maps.d.ts +17 -1
- package/dist/entries/code-block.d.ts +1 -0
- package/dist/entries/code-block.js +1 -0
- package/dist/entries/comment-thread.d.ts +1 -0
- package/dist/entries/comment-thread.js +1 -0
- package/dist/entries/formatted-date.d.ts +1 -0
- package/dist/entries/formatted-date.js +1 -0
- package/dist/entries/formatted-file-size.d.ts +1 -0
- package/dist/entries/formatted-file-size.js +1 -0
- package/dist/entries/formatted-number.d.ts +1 -0
- package/dist/entries/formatted-number.js +1 -0
- package/dist/entries/indicator.d.ts +1 -0
- package/dist/entries/indicator.js +1 -0
- package/dist/entries/relative-time.d.ts +1 -0
- package/dist/entries/relative-time.js +1 -0
- package/dist/entries/tile-group.d.ts +1 -0
- package/dist/entries/tile-group.js +1 -0
- package/dist/foundations/format/format.d.ts +82 -0
- package/dist/foundations/format/format.js +122 -0
- package/dist/foundations/format/index.d.ts +1 -0
- package/dist/foundations/format/index.js +3 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +12 -0
- package/dist/patterns/comments/comment-thread.d.ts +79 -0
- package/dist/patterns/comments/comment-thread.js +561 -0
- package/dist/patterns/comments/index.d.ts +2 -0
- package/dist/patterns/comments/index.js +5 -0
- package/dist/patterns/comments/types.d.ts +39 -0
- package/dist/patterns/comments/types.js +8 -0
- package/dist/patterns/preview/annotation-thread.d.ts +67 -48
- package/dist/patterns/preview/annotation-thread.js +96 -440
- package/package.json +1 -1
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { BaseElement } from "../../core/index.js";
|
|
2
|
+
import { resolveLocale } from "../../foundations/format/index.js";
|
|
3
|
+
const valueStyles = `
|
|
4
|
+
:host {
|
|
5
|
+
display: inline;
|
|
6
|
+
color: inherit;
|
|
7
|
+
font: inherit;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
:host([hidden]) {
|
|
11
|
+
display: none;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/* Numbers and dates are scanned in columns far more often than they are read
|
|
15
|
+
in prose — a modified date beside a file name, a size in a table. Tabular
|
|
16
|
+
figures keep those columns aligned without the host having to ask. */
|
|
17
|
+
:host([tabular]) [part="value"] {
|
|
18
|
+
font-variant-numeric: tabular-nums;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
[part="value"] {
|
|
22
|
+
white-space: nowrap;
|
|
23
|
+
}
|
|
24
|
+
`;
|
|
25
|
+
/**
|
|
26
|
+
* Shared behaviour for the read-only `box-formatted-*` renderers.
|
|
27
|
+
*
|
|
28
|
+
* These components render a typed value and nothing else: no interaction, no
|
|
29
|
+
* state, no events. What they do share is the awkward part — resolving a
|
|
30
|
+
* locale, deciding what a broken value looks like, and keeping a
|
|
31
|
+
* machine-readable form beside the human one.
|
|
32
|
+
*
|
|
33
|
+
* **A value that cannot be formatted hides the element.** Not `Invalid Date`,
|
|
34
|
+
* not `NaN`, not the raw string echoed back. A malformed value is a host bug,
|
|
35
|
+
* and rendering its wreckage puts the bug in front of a reader who can do
|
|
36
|
+
* nothing about it. The host is left with an empty inline box, which is what a
|
|
37
|
+
* missing value should look like anyway.
|
|
38
|
+
*/
|
|
39
|
+
export class FormattedValue extends BaseElement {
|
|
40
|
+
valueEl;
|
|
41
|
+
/**
|
|
42
|
+
* Whether to render into `<time>` rather than `<span>`.
|
|
43
|
+
*
|
|
44
|
+
* Only the instant-valued renderers should; `<time>` on a file size would be
|
|
45
|
+
* a lie to anything reading the document semantically.
|
|
46
|
+
*/
|
|
47
|
+
usesTimeElement() {
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
/** Extra styles a subclass wants appended. */
|
|
51
|
+
valueStyles() {
|
|
52
|
+
return valueStyles;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* The locale to format in. Absent means the host's own — deliberately
|
|
56
|
+
* `undefined` rather than a hardcoded tag.
|
|
57
|
+
*/
|
|
58
|
+
get locale() {
|
|
59
|
+
return resolveLocale(this.getAttribute("locale"));
|
|
60
|
+
}
|
|
61
|
+
set locale(value) {
|
|
62
|
+
if (!value) {
|
|
63
|
+
this.removeAttribute("locale");
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
this.setAttribute("locale", value);
|
|
67
|
+
}
|
|
68
|
+
renderTemplate() {
|
|
69
|
+
if (!this.shadowRoot) {
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
const tag = this.usesTimeElement() ? "time" : "span";
|
|
73
|
+
this.shadowRoot.innerHTML = `<style>${this.valueStyles()}</style><${tag} part="value"></${tag}>`;
|
|
74
|
+
this.valueEl = this.shadowRoot.querySelector('[part="value"]');
|
|
75
|
+
}
|
|
76
|
+
setupListeners() {
|
|
77
|
+
// Output only: nothing to listen to.
|
|
78
|
+
}
|
|
79
|
+
update() {
|
|
80
|
+
if (!this.valueEl) {
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
const result = this.formatted();
|
|
84
|
+
// toggleAttribute rather than `.hidden = `, so a host that set `hidden`
|
|
85
|
+
// itself is not fought with on every update.
|
|
86
|
+
this.toggleAttribute("hidden", result === null);
|
|
87
|
+
if (!result) {
|
|
88
|
+
this.valueEl.textContent = "";
|
|
89
|
+
this.valueEl.removeAttribute("datetime");
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
this.valueEl.textContent = result.text;
|
|
93
|
+
if (result.machine) {
|
|
94
|
+
this.valueEl.setAttribute("datetime", result.machine);
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
this.valueEl.removeAttribute("datetime");
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// Read-only renderers for typed values: dates, relative durations, numbers and
|
|
2
|
+
// file sizes, all locale-aware and all hiding themselves when handed something
|
|
3
|
+
// they cannot format.
|
|
4
|
+
export * from "./formatted-value.js";
|
|
5
|
+
export * from "./code-block.js";
|
|
6
|
+
export * from "./formatted-date.js";
|
|
7
|
+
export * from "./relative-time.js";
|
|
8
|
+
export * from "./formatted-number.js";
|
|
9
|
+
export * from "./formatted-file-size.js";
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { FormattedValue } from "./formatted-value.js";
|
|
2
|
+
/**
|
|
3
|
+
* How long ago something happened, in the reader's locale.
|
|
4
|
+
*
|
|
5
|
+
* ```html
|
|
6
|
+
* <box-relative-time value="2026-08-24T09:00:00Z"></box-relative-time>
|
|
7
|
+
* ```
|
|
8
|
+
*
|
|
9
|
+
* Renders into `<time datetime="…">` carrying the exact instant, because
|
|
10
|
+
* "4 days ago" is an approximation and the precise value is what a reader
|
|
11
|
+
* reaches for when the approximation is not enough. `box-due-badge` makes the
|
|
12
|
+
* same trade for the same reason.
|
|
13
|
+
*
|
|
14
|
+
* `reference-time` pins what "now" means. It exists for the same reason
|
|
15
|
+
* `box-due-badge` has one: a component whose output depends on the wall clock
|
|
16
|
+
* cannot be tested or screenshotted deterministically, and a host rendering a
|
|
17
|
+
* list wants every row measured against one instant rather than each against
|
|
18
|
+
* the moment it happened to render.
|
|
19
|
+
*/
|
|
20
|
+
export declare class RelativeTime extends FormattedValue {
|
|
21
|
+
static readonly tagName: string;
|
|
22
|
+
static get observedAttributes(): string[];
|
|
23
|
+
protected usesTimeElement(): boolean;
|
|
24
|
+
/** The instant being described, as ISO 8601 or epoch milliseconds. */
|
|
25
|
+
get value(): string;
|
|
26
|
+
set value(next: string);
|
|
27
|
+
/** What "now" means. Absent uses the current time. */
|
|
28
|
+
get referenceTime(): string;
|
|
29
|
+
set referenceTime(next: string);
|
|
30
|
+
/**
|
|
31
|
+
* `auto` lets Intl say "yesterday" where the language has a word for it;
|
|
32
|
+
* `always` forces "1 day ago". Defaults to `auto`, because the idiomatic
|
|
33
|
+
* form is what a reader expects.
|
|
34
|
+
*/
|
|
35
|
+
get numeric(): string;
|
|
36
|
+
set numeric(next: string);
|
|
37
|
+
protected formatted(): {
|
|
38
|
+
machine: string;
|
|
39
|
+
text: string;
|
|
40
|
+
} | null;
|
|
41
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { FormattedValue } from "./formatted-value.js";
|
|
2
|
+
import { parseInstant, resolveRelativeParts } from "../../foundations/format/index.js";
|
|
3
|
+
const DEFAULT_TAG_NAME = "box-relative-time";
|
|
4
|
+
const NUMERIC_MODES = new Set(["always", "auto"]);
|
|
5
|
+
/**
|
|
6
|
+
* How long ago something happened, in the reader's locale.
|
|
7
|
+
*
|
|
8
|
+
* ```html
|
|
9
|
+
* <box-relative-time value="2026-08-24T09:00:00Z"></box-relative-time>
|
|
10
|
+
* ```
|
|
11
|
+
*
|
|
12
|
+
* Renders into `<time datetime="…">` carrying the exact instant, because
|
|
13
|
+
* "4 days ago" is an approximation and the precise value is what a reader
|
|
14
|
+
* reaches for when the approximation is not enough. `box-due-badge` makes the
|
|
15
|
+
* same trade for the same reason.
|
|
16
|
+
*
|
|
17
|
+
* `reference-time` pins what "now" means. It exists for the same reason
|
|
18
|
+
* `box-due-badge` has one: a component whose output depends on the wall clock
|
|
19
|
+
* cannot be tested or screenshotted deterministically, and a host rendering a
|
|
20
|
+
* list wants every row measured against one instant rather than each against
|
|
21
|
+
* the moment it happened to render.
|
|
22
|
+
*/
|
|
23
|
+
export class RelativeTime extends FormattedValue {
|
|
24
|
+
static tagName = DEFAULT_TAG_NAME;
|
|
25
|
+
static get observedAttributes() {
|
|
26
|
+
return ["locale", "numeric", "reference-time", "value"];
|
|
27
|
+
}
|
|
28
|
+
usesTimeElement() {
|
|
29
|
+
return true;
|
|
30
|
+
}
|
|
31
|
+
/** The instant being described, as ISO 8601 or epoch milliseconds. */
|
|
32
|
+
get value() {
|
|
33
|
+
return this.getAttribute("value") ?? "";
|
|
34
|
+
}
|
|
35
|
+
set value(next) {
|
|
36
|
+
this.setAttribute("value", next);
|
|
37
|
+
}
|
|
38
|
+
/** What "now" means. Absent uses the current time. */
|
|
39
|
+
get referenceTime() {
|
|
40
|
+
return this.getAttribute("reference-time") ?? "";
|
|
41
|
+
}
|
|
42
|
+
set referenceTime(next) {
|
|
43
|
+
if (!next) {
|
|
44
|
+
this.removeAttribute("reference-time");
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
this.setAttribute("reference-time", next);
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* `auto` lets Intl say "yesterday" where the language has a word for it;
|
|
51
|
+
* `always` forces "1 day ago". Defaults to `auto`, because the idiomatic
|
|
52
|
+
* form is what a reader expects.
|
|
53
|
+
*/
|
|
54
|
+
get numeric() {
|
|
55
|
+
const raw = this.getAttribute("numeric");
|
|
56
|
+
return raw && NUMERIC_MODES.has(raw) ? raw : "auto";
|
|
57
|
+
}
|
|
58
|
+
set numeric(next) {
|
|
59
|
+
this.setAttribute("numeric", next);
|
|
60
|
+
}
|
|
61
|
+
formatted() {
|
|
62
|
+
const instant = parseInstant(this.value);
|
|
63
|
+
if (!instant) {
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
// An unparseable reference falls back to now rather than hiding the
|
|
67
|
+
// element: the instant being described is still valid, and a broken
|
|
68
|
+
// reference should not erase it.
|
|
69
|
+
const reference = parseInstant(this.referenceTime) ?? new Date();
|
|
70
|
+
const { unit, value } = resolveRelativeParts(instant, reference);
|
|
71
|
+
try {
|
|
72
|
+
const formatter = new Intl.RelativeTimeFormat(this.locale, {
|
|
73
|
+
numeric: this.numeric,
|
|
74
|
+
});
|
|
75
|
+
return { machine: instant.toISOString(), text: formatter.format(value, unit) };
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
return { machine: instant.toISOString(), text: instant.toISOString() };
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
RelativeTime.register();
|
package/dist/element-maps.d.ts
CHANGED
|
@@ -36,6 +36,14 @@ import type { CollaboratorAvatars } from "./patterns/share/collaborator-avatars.
|
|
|
36
36
|
import type { ColorPicker } from "./components/forms/color-picker.js";
|
|
37
37
|
import type { Combobox } from "./components/forms/combobox.js";
|
|
38
38
|
import type { CommandPalette } from "./components/overlays/command-palette.js";
|
|
39
|
+
import type { CommentThread } from "./patterns/comments/comment-thread.js";
|
|
40
|
+
import type { CodeBlock } from "./components/output/code-block.js";
|
|
41
|
+
import type { FormattedDate } from "./components/output/formatted-date.js";
|
|
42
|
+
import type { Indicator } from "./components/feedback/indicator.js";
|
|
43
|
+
import type { TileGroup } from "./components/forms/tile-group.js";
|
|
44
|
+
import type { FormattedFileSize } from "./components/output/formatted-file-size.js";
|
|
45
|
+
import type { FormattedNumber } from "./components/output/formatted-number.js";
|
|
46
|
+
import type { RelativeTime } from "./components/output/relative-time.js";
|
|
39
47
|
import type { CompareView } from "./patterns/diff/compare-view.js";
|
|
40
48
|
import type { ContactDatalistItem } from "./components/identity/contact-datalist-item.js";
|
|
41
49
|
import type { ContentExplorer } from "./patterns/content-explorer/content-explorer.js";
|
|
@@ -178,6 +186,14 @@ declare global {
|
|
|
178
186
|
"box-color-picker": ColorPicker;
|
|
179
187
|
"box-combobox": Combobox;
|
|
180
188
|
"box-command-palette": CommandPalette;
|
|
189
|
+
"box-comment-thread": CommentThread;
|
|
190
|
+
"box-code-block": CodeBlock;
|
|
191
|
+
"box-formatted-date": FormattedDate;
|
|
192
|
+
"box-indicator": Indicator;
|
|
193
|
+
"box-tile-group": TileGroup;
|
|
194
|
+
"box-formatted-file-size": FormattedFileSize;
|
|
195
|
+
"box-formatted-number": FormattedNumber;
|
|
196
|
+
"box-relative-time": RelativeTime;
|
|
181
197
|
"box-compare-view": CompareView;
|
|
182
198
|
"box-contact-datalist-item": ContactDatalistItem;
|
|
183
199
|
"box-content-explorer": ContentExplorer;
|
|
@@ -291,4 +307,4 @@ declare global {
|
|
|
291
307
|
}
|
|
292
308
|
}
|
|
293
309
|
/** Every element tag the library registers, for host-side narrowing. */
|
|
294
|
-
export type BoxElementTagName = "box-access-stats" | "box-accordion" | "box-activity-density" | "box-agent-chat" | "box-alert" | "box-annotation-inspector" | "box-annotation-thread" | "box-annotation-toolbar" | "box-app-shell" | "box-audit-log" | "box-avatar" | "box-badge" | "box-badgeable" | "box-bar-chart" | "box-breadcrumb" | "box-bulk-action-bar" | "box-button" | "box-button-group" | "box-calendar" | "box-card" | "box-carousel" | "box-category-selector" | "box-chart-panel" | "box-checkbox" | "box-checkbox-group" | "box-chip" | "box-collaborator-avatars" | "box-color-picker" | "box-combobox" | "box-command-palette" | "box-compare-view" | "box-contact-datalist-item" | "box-content-explorer" | "box-content-picker" | "box-content-sidebar" | "box-content-uploader" | "box-context-menu" | "box-datalist-item" | "box-date-field" | "box-dialog" | "box-diff-viewer" | "box-divider" | "box-grid" | "box-donut-chart" | "box-draggable-list" | "box-drawer" | "box-drop-zone" | "box-dropdown" | "box-dual-listbox" | "box-due-badge" | "box-empty-state" | "box-error-mask" | "box-explorer-action-menu" | "box-explorer-breadcrumbs" | "box-explorer-items" | "box-explorer-list" | "box-explorer-table" | "box-explorer-toolbar" | "box-fieldset" | "box-file-request-builder" | "box-filter-bar" | "box-form-wizard" | "box-governance-panel" | "box-grid-view" | "box-guide-tooltip" | "box-help-text" | "box-icon-button" | "box-illustration" | "box-invite-collaborators-modal" | "box-item-details-panel" | "box-item-form" | "box-line-chart" | "box-lineage-graph" | "box-link-button" | "box-menu" | "box-menu-item" | "box-metadata-filter-builder" | "box-metadata-inspector" | "box-metric-card" | "box-multi-select" | "box-nav-sidebar" | "box-notification-bell" | "box-notification-inbox" | "box-nudge" | "box-number-input" | "box-pagination" | "box-permission-matrix" | "box-persona" | "box-pill-cloud" | "box-pill-selector-dropdown" | "box-popover" | "box-presence" | "box-preview-element" | "box-preview-header" | "box-progress-bar" | "box-progress-ring" | "box-progress-steps" | "box-provenance-strip" | "box-radio-group" | "box-range-slider" | "box-rating" | "box-review-queue-item" | "box-rich-text-input" | "box-run-trace" | "box-saved-view-picker" | "box-search-field" | "box-search-results-header" | "box-section" | "box-segmented-control" | "box-select" | "box-share-panel" | "box-shortcuts-overlay" | "box-sidebar-toggle-button" | "box-signature-ceremony" | "box-skeleton" | "box-slider" | "box-spin-button" | "box-spinner" | "box-split-view" | "box-path" | "box-switch" | "box-table" | "box-tabs" | "box-tag-input" | "box-task-assignment-panel" | "box-text-area" | "box-text-field" | "box-thumbnail-card" | "box-time-field" | "box-timeline" | "box-toast" | "box-tooltip" | "box-tree" | "box-tree-grid" | "box-unified-share-modal" | "box-version-graph" | "box-version-list" | "box-wizard-summary" | "box-work-queue" | "box-workload-board";
|
|
310
|
+
export type BoxElementTagName = "box-access-stats" | "box-accordion" | "box-activity-density" | "box-agent-chat" | "box-alert" | "box-annotation-inspector" | "box-annotation-thread" | "box-annotation-toolbar" | "box-app-shell" | "box-audit-log" | "box-avatar" | "box-badge" | "box-badgeable" | "box-bar-chart" | "box-breadcrumb" | "box-bulk-action-bar" | "box-button" | "box-button-group" | "box-calendar" | "box-card" | "box-carousel" | "box-category-selector" | "box-chart-panel" | "box-checkbox" | "box-checkbox-group" | "box-chip" | "box-collaborator-avatars" | "box-color-picker" | "box-combobox" | "box-command-palette" | "box-comment-thread" | "box-code-block" | "box-formatted-date" | "box-indicator" | "box-tile-group" | "box-formatted-file-size" | "box-formatted-number" | "box-relative-time" | "box-compare-view" | "box-contact-datalist-item" | "box-content-explorer" | "box-content-picker" | "box-content-sidebar" | "box-content-uploader" | "box-context-menu" | "box-datalist-item" | "box-date-field" | "box-dialog" | "box-diff-viewer" | "box-divider" | "box-grid" | "box-donut-chart" | "box-draggable-list" | "box-drawer" | "box-drop-zone" | "box-dropdown" | "box-dual-listbox" | "box-due-badge" | "box-empty-state" | "box-error-mask" | "box-explorer-action-menu" | "box-explorer-breadcrumbs" | "box-explorer-items" | "box-explorer-list" | "box-explorer-table" | "box-explorer-toolbar" | "box-fieldset" | "box-file-request-builder" | "box-filter-bar" | "box-form-wizard" | "box-governance-panel" | "box-grid-view" | "box-guide-tooltip" | "box-help-text" | "box-icon-button" | "box-illustration" | "box-invite-collaborators-modal" | "box-item-details-panel" | "box-item-form" | "box-line-chart" | "box-lineage-graph" | "box-link-button" | "box-menu" | "box-menu-item" | "box-metadata-filter-builder" | "box-metadata-inspector" | "box-metric-card" | "box-multi-select" | "box-nav-sidebar" | "box-notification-bell" | "box-notification-inbox" | "box-nudge" | "box-number-input" | "box-pagination" | "box-permission-matrix" | "box-persona" | "box-pill-cloud" | "box-pill-selector-dropdown" | "box-popover" | "box-presence" | "box-preview-element" | "box-preview-header" | "box-progress-bar" | "box-progress-ring" | "box-progress-steps" | "box-provenance-strip" | "box-radio-group" | "box-range-slider" | "box-rating" | "box-review-queue-item" | "box-rich-text-input" | "box-run-trace" | "box-saved-view-picker" | "box-search-field" | "box-search-results-header" | "box-section" | "box-segmented-control" | "box-select" | "box-share-panel" | "box-shortcuts-overlay" | "box-sidebar-toggle-button" | "box-signature-ceremony" | "box-skeleton" | "box-slider" | "box-spin-button" | "box-spinner" | "box-split-view" | "box-path" | "box-switch" | "box-table" | "box-tabs" | "box-tag-input" | "box-task-assignment-panel" | "box-text-area" | "box-text-field" | "box-thumbnail-card" | "box-time-field" | "box-timeline" | "box-toast" | "box-tooltip" | "box-tree" | "box-tree-grid" | "box-unified-share-modal" | "box-version-graph" | "box-version-list" | "box-wizard-summary" | "box-work-queue" | "box-workload-board";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "../components/output/code-block.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "../components/output/code-block.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "../patterns/comments/index.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "../patterns/comments/index.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "../components/output/formatted-date.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "../components/output/formatted-date.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "../components/output/formatted-file-size.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "../components/output/formatted-file-size.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "../components/output/formatted-number.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "../components/output/formatted-number.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "../components/feedback/indicator.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "../components/feedback/indicator.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "../components/output/relative-time.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "../components/output/relative-time.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "../components/forms/tile-group.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "../components/forms/tile-group.js";
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Typed-value formatting, as a foundation.
|
|
3
|
+
*
|
|
4
|
+
* Every read surface in a content platform renders the same handful of value
|
|
5
|
+
* types — a modified date, a file size, a count, a duration since something
|
|
6
|
+
* happened. Before this module each consumer reinvented that, which meant
|
|
7
|
+
* locale handling, `<time datetime>` semantics and invalid-input behaviour were
|
|
8
|
+
* reinvented with it, differently each time.
|
|
9
|
+
*
|
|
10
|
+
* The rules here are deliberately shared rather than per-component:
|
|
11
|
+
*
|
|
12
|
+
* - **A locale is never guessed.** An absent `locale` means "use the host's",
|
|
13
|
+
* which is `undefined` to `Intl` — not a hardcoded `en-US`. Substituting one
|
|
14
|
+
* would silently render American dates to a German reader.
|
|
15
|
+
* - **Invalid input renders nothing.** Not `Invalid Date`, not `NaN`, not the
|
|
16
|
+
* raw string. A malformed value is a host bug, and showing its wreckage to a
|
|
17
|
+
* reader helps nobody; the element collapses and the host sees an empty slot.
|
|
18
|
+
*/
|
|
19
|
+
/**
|
|
20
|
+
* The locale to format in.
|
|
21
|
+
*
|
|
22
|
+
* `undefined` is meaningful: it tells `Intl` to use the runtime's own locale.
|
|
23
|
+
* An empty or absent attribute must therefore become `undefined` rather than
|
|
24
|
+
* any particular language tag.
|
|
25
|
+
*/
|
|
26
|
+
export declare const resolveLocale: (value: string | null | undefined) => string | undefined;
|
|
27
|
+
/**
|
|
28
|
+
* Parse an instant from author input.
|
|
29
|
+
*
|
|
30
|
+
* Accepts an ISO 8601 string or epoch milliseconds, because hosts have both:
|
|
31
|
+
* a JSON API hands over strings, a `Date.now()` arithmetic result hands over
|
|
32
|
+
* numbers. Anything unparseable is `null` rather than an Invalid Date, so
|
|
33
|
+
* callers test one thing instead of remembering `Number.isNaN(d.getTime())`.
|
|
34
|
+
*/
|
|
35
|
+
export declare const parseInstant: (value: string | number | null | undefined) => Date | null;
|
|
36
|
+
/** Parse a finite number from author input, or `null`. */
|
|
37
|
+
export declare const parseNumber: (value: string | number | null | undefined) => number | null;
|
|
38
|
+
/** Date and time presets, mirroring `Intl.DateTimeFormat`'s own vocabulary. */
|
|
39
|
+
export type BoeDateStyle = "full" | "long" | "medium" | "short";
|
|
40
|
+
/** Narrow an author-supplied style, or `undefined` to omit it entirely. */
|
|
41
|
+
export declare const resolveDateStyle: (value: string | null | undefined) => BoeDateStyle | undefined;
|
|
42
|
+
/** A relative duration, in the largest unit that yields a whole number ≥ 1. */
|
|
43
|
+
export interface BoeRelativeParts {
|
|
44
|
+
unit: Intl.RelativeTimeFormatUnit;
|
|
45
|
+
value: number;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Express the distance from `reference` to `instant` in one unit.
|
|
49
|
+
*
|
|
50
|
+
* The sign follows `Intl.RelativeTimeFormat`: negative is the past. The largest
|
|
51
|
+
* unit that yields a magnitude of at least one is chosen, so a four-day-old
|
|
52
|
+
* file reads "4 days ago" rather than "96 hours ago"; months and years are the
|
|
53
|
+
* usual approximations, which is what a reader wants from a relative label and
|
|
54
|
+
* why an exact timestamp stays available alongside it.
|
|
55
|
+
*/
|
|
56
|
+
export declare const resolveRelativeParts: (instant: Date, reference: Date) => BoeRelativeParts;
|
|
57
|
+
/**
|
|
58
|
+
* Byte units.
|
|
59
|
+
*
|
|
60
|
+
* `binary` is powers of 1024 with IEC names (KiB, MiB); `decimal` is powers of
|
|
61
|
+
* 1000 with SI names (kB, MB). Box's own UI reports decimal, which is why it is
|
|
62
|
+
* the default — a file the product calls 1.2 MB should not become 1.1 MiB here.
|
|
63
|
+
*/
|
|
64
|
+
export type BoeByteUnits = "decimal" | "binary";
|
|
65
|
+
/** Narrow author-supplied byte units, falling back to `decimal`. */
|
|
66
|
+
export declare const resolveByteUnits: (value: string | null | undefined) => BoeByteUnits;
|
|
67
|
+
/** A file size split into the number to format and the unit to append. */
|
|
68
|
+
export interface BoeFileSizeParts {
|
|
69
|
+
suffix: string;
|
|
70
|
+
value: number;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Reduce a byte count to a readable magnitude and its unit.
|
|
74
|
+
*
|
|
75
|
+
* The number is returned unformatted so the caller can run it through
|
|
76
|
+
* `Intl.NumberFormat` in the reader's locale — a size rendered as "1,5 MB" for
|
|
77
|
+
* a German reader is the whole point of not doing this with `toFixed`.
|
|
78
|
+
*
|
|
79
|
+
* Bytes themselves are never fractional: "1.5 B" is nonsense, so the base unit
|
|
80
|
+
* is reported whole.
|
|
81
|
+
*/
|
|
82
|
+
export declare const resolveFileSizeParts: (bytes: number, units?: BoeByteUnits) => BoeFileSizeParts;
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Typed-value formatting, as a foundation.
|
|
3
|
+
*
|
|
4
|
+
* Every read surface in a content platform renders the same handful of value
|
|
5
|
+
* types — a modified date, a file size, a count, a duration since something
|
|
6
|
+
* happened. Before this module each consumer reinvented that, which meant
|
|
7
|
+
* locale handling, `<time datetime>` semantics and invalid-input behaviour were
|
|
8
|
+
* reinvented with it, differently each time.
|
|
9
|
+
*
|
|
10
|
+
* The rules here are deliberately shared rather than per-component:
|
|
11
|
+
*
|
|
12
|
+
* - **A locale is never guessed.** An absent `locale` means "use the host's",
|
|
13
|
+
* which is `undefined` to `Intl` — not a hardcoded `en-US`. Substituting one
|
|
14
|
+
* would silently render American dates to a German reader.
|
|
15
|
+
* - **Invalid input renders nothing.** Not `Invalid Date`, not `NaN`, not the
|
|
16
|
+
* raw string. A malformed value is a host bug, and showing its wreckage to a
|
|
17
|
+
* reader helps nobody; the element collapses and the host sees an empty slot.
|
|
18
|
+
*/
|
|
19
|
+
/**
|
|
20
|
+
* The locale to format in.
|
|
21
|
+
*
|
|
22
|
+
* `undefined` is meaningful: it tells `Intl` to use the runtime's own locale.
|
|
23
|
+
* An empty or absent attribute must therefore become `undefined` rather than
|
|
24
|
+
* any particular language tag.
|
|
25
|
+
*/
|
|
26
|
+
export const resolveLocale = (value) => {
|
|
27
|
+
const trimmed = value?.trim();
|
|
28
|
+
return trimmed ? trimmed : undefined;
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* Parse an instant from author input.
|
|
32
|
+
*
|
|
33
|
+
* Accepts an ISO 8601 string or epoch milliseconds, because hosts have both:
|
|
34
|
+
* a JSON API hands over strings, a `Date.now()` arithmetic result hands over
|
|
35
|
+
* numbers. Anything unparseable is `null` rather than an Invalid Date, so
|
|
36
|
+
* callers test one thing instead of remembering `Number.isNaN(d.getTime())`.
|
|
37
|
+
*/
|
|
38
|
+
export const parseInstant = (value) => {
|
|
39
|
+
if (value === null || value === undefined || value === "") {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
// A bare number, or a numeric string, is epoch milliseconds. Checking the
|
|
43
|
+
// string form first matters: `new Date("1700000000000")` is not the same
|
|
44
|
+
// instant as `new Date(1700000000000)`.
|
|
45
|
+
const numeric = typeof value === "number" ? value : Number(value);
|
|
46
|
+
const date = Number.isFinite(numeric) ? new Date(numeric) : new Date(String(value));
|
|
47
|
+
return Number.isNaN(date.getTime()) ? null : date;
|
|
48
|
+
};
|
|
49
|
+
/** Parse a finite number from author input, or `null`. */
|
|
50
|
+
export const parseNumber = (value) => {
|
|
51
|
+
if (value === null || value === undefined || value === "") {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
const parsed = typeof value === "number" ? value : Number(value);
|
|
55
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
56
|
+
};
|
|
57
|
+
const DATE_STYLES = new Set(["full", "long", "medium", "short"]);
|
|
58
|
+
/** Narrow an author-supplied style, or `undefined` to omit it entirely. */
|
|
59
|
+
export const resolveDateStyle = (value) => DATE_STYLES.has(value) ? value : undefined;
|
|
60
|
+
/**
|
|
61
|
+
* The units a relative time can be expressed in, largest first.
|
|
62
|
+
*
|
|
63
|
+
* Seconds are the floor: "0 minutes ago" reads as broken, and a surface that
|
|
64
|
+
* needs sub-second precision is not one a human is reading.
|
|
65
|
+
*/
|
|
66
|
+
const RELATIVE_UNITS = [
|
|
67
|
+
{ unit: "year", ms: 365 * 24 * 60 * 60 * 1000 },
|
|
68
|
+
{ unit: "month", ms: 30 * 24 * 60 * 60 * 1000 },
|
|
69
|
+
{ unit: "week", ms: 7 * 24 * 60 * 60 * 1000 },
|
|
70
|
+
{ unit: "day", ms: 24 * 60 * 60 * 1000 },
|
|
71
|
+
{ unit: "hour", ms: 60 * 60 * 1000 },
|
|
72
|
+
{ unit: "minute", ms: 60 * 1000 },
|
|
73
|
+
{ unit: "second", ms: 1000 },
|
|
74
|
+
];
|
|
75
|
+
/**
|
|
76
|
+
* Express the distance from `reference` to `instant` in one unit.
|
|
77
|
+
*
|
|
78
|
+
* The sign follows `Intl.RelativeTimeFormat`: negative is the past. The largest
|
|
79
|
+
* unit that yields a magnitude of at least one is chosen, so a four-day-old
|
|
80
|
+
* file reads "4 days ago" rather than "96 hours ago"; months and years are the
|
|
81
|
+
* usual approximations, which is what a reader wants from a relative label and
|
|
82
|
+
* why an exact timestamp stays available alongside it.
|
|
83
|
+
*/
|
|
84
|
+
export const resolveRelativeParts = (instant, reference) => {
|
|
85
|
+
const delta = instant.getTime() - reference.getTime();
|
|
86
|
+
const magnitude = Math.abs(delta);
|
|
87
|
+
for (const { unit, ms } of RELATIVE_UNITS) {
|
|
88
|
+
if (magnitude >= ms) {
|
|
89
|
+
// Truncate rather than round: 47 hours is "1 day ago", not "2 days ago".
|
|
90
|
+
// Rounding would let a label claim a boundary the instant has not crossed.
|
|
91
|
+
return { unit, value: Math.trunc(delta / ms) };
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return { unit: "second", value: 0 };
|
|
95
|
+
};
|
|
96
|
+
const BYTE_UNITS = new Set(["decimal", "binary"]);
|
|
97
|
+
/** Narrow author-supplied byte units, falling back to `decimal`. */
|
|
98
|
+
export const resolveByteUnits = (value) => BYTE_UNITS.has(value) ? value : "decimal";
|
|
99
|
+
const DECIMAL_SUFFIXES = ["B", "kB", "MB", "GB", "TB", "PB"];
|
|
100
|
+
const BINARY_SUFFIXES = ["B", "KiB", "MiB", "GiB", "TiB", "PiB"];
|
|
101
|
+
/**
|
|
102
|
+
* Reduce a byte count to a readable magnitude and its unit.
|
|
103
|
+
*
|
|
104
|
+
* The number is returned unformatted so the caller can run it through
|
|
105
|
+
* `Intl.NumberFormat` in the reader's locale — a size rendered as "1,5 MB" for
|
|
106
|
+
* a German reader is the whole point of not doing this with `toFixed`.
|
|
107
|
+
*
|
|
108
|
+
* Bytes themselves are never fractional: "1.5 B" is nonsense, so the base unit
|
|
109
|
+
* is reported whole.
|
|
110
|
+
*/
|
|
111
|
+
export const resolveFileSizeParts = (bytes, units = "decimal") => {
|
|
112
|
+
const base = units === "binary" ? 1024 : 1000;
|
|
113
|
+
const suffixes = units === "binary" ? BINARY_SUFFIXES : DECIMAL_SUFFIXES;
|
|
114
|
+
const magnitude = Math.abs(bytes);
|
|
115
|
+
if (magnitude < base) {
|
|
116
|
+
return { suffix: suffixes[0], value: Math.trunc(bytes) };
|
|
117
|
+
}
|
|
118
|
+
// Clamped so a petabyte-plus value keeps the largest name we have rather than
|
|
119
|
+
// running off the end of the table.
|
|
120
|
+
const exponent = Math.min(Math.floor(Math.log(magnitude) / Math.log(base)), suffixes.length - 1);
|
|
121
|
+
return { suffix: suffixes[exponent], value: bytes / base ** exponent };
|
|
122
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./format.js";
|
package/dist/index.d.ts
CHANGED
|
@@ -9,6 +9,7 @@ export { DESIGN_PROFILE_CHANGE_EVENT, DESIGN_PROFILE_VARIABLES, DEFAULT_DESIGN_P
|
|
|
9
9
|
export type { ApplyDesignProfileOptions, DesignProfileChangeDetail, DesignProfileController, DesignProfileControllerOptions, DesignProfileDefinition, DesignProfileDensity, DesignProfileElevation, DesignProfileMotion, DesignProfileRadius, DesignProfileSpacing, DesignProfileTypography, } from "./foundations/profiles/index.js";
|
|
10
10
|
export { boxIconography, boxIconographyAliases } from "./foundations/icons/index.js";
|
|
11
11
|
export * from "./foundations/layout/index.js";
|
|
12
|
+
export * from "./foundations/format/index.js";
|
|
12
13
|
export { anchorFloating, parsePlacement, resolvePosition, trackAnchor, } from "./foundations/overlay/index.js";
|
|
13
14
|
export type { OverlayAlign, OverlayPlacement, OverlaySide, PositionOptions, PositionResult, } from "./foundations/overlay/index.js";
|
|
14
15
|
export * from "./components/actions/button.js";
|
|
@@ -37,6 +38,7 @@ export * from "./components/feedback/due-types.js";
|
|
|
37
38
|
export * from "./components/feedback/empty-state.js";
|
|
38
39
|
export * from "./components/feedback/error-mask.js";
|
|
39
40
|
export * from "./components/feedback/help-text.js";
|
|
41
|
+
export * from "./components/feedback/indicator.js";
|
|
40
42
|
export * from "./components/feedback/nudge.js";
|
|
41
43
|
export * from "./components/feedback/progress-bar.js";
|
|
42
44
|
export * from "./components/feedback/progress-ring.js";
|
|
@@ -66,6 +68,7 @@ export * from "./components/forms/slider.js";
|
|
|
66
68
|
export * from "./components/forms/spin-button.js";
|
|
67
69
|
export * from "./components/forms/switch.js";
|
|
68
70
|
export * from "./components/forms/tag-input.js";
|
|
71
|
+
export * from "./components/forms/tile-group.js";
|
|
69
72
|
export * from "./components/forms/time-field.js";
|
|
70
73
|
export * from "./components/forms/category-selector.js";
|
|
71
74
|
export * from "./components/forms/checkbox-group.js";
|
|
@@ -80,6 +83,12 @@ export * from "./components/forms/search-field.js";
|
|
|
80
83
|
export * from "./components/forms/select.js";
|
|
81
84
|
export * from "./components/forms/text-area.js";
|
|
82
85
|
export * from "./components/forms/text-field.js";
|
|
86
|
+
export * from "./components/output/formatted-value.js";
|
|
87
|
+
export * from "./components/output/code-block.js";
|
|
88
|
+
export * from "./components/output/formatted-date.js";
|
|
89
|
+
export * from "./components/output/relative-time.js";
|
|
90
|
+
export * from "./components/output/formatted-number.js";
|
|
91
|
+
export * from "./components/output/formatted-file-size.js";
|
|
83
92
|
export * from "./components/layout/app-shell.js";
|
|
84
93
|
export * from "./components/layout/divider.js";
|
|
85
94
|
export * from "./components/layout/grid.js";
|
|
@@ -110,6 +119,7 @@ export * from "./patterns/versions/index.js";
|
|
|
110
119
|
export * from "./patterns/lineage/index.js";
|
|
111
120
|
export * from "./patterns/agent-chat/index.js";
|
|
112
121
|
export * from "./patterns/audit/index.js";
|
|
122
|
+
export * from "./patterns/comments/index.js";
|
|
113
123
|
export * from "./patterns/notifications/index.js";
|
|
114
124
|
export * from "./patterns/search/index.js";
|
|
115
125
|
export * from "./patterns/item/index.js";
|