@flood-md/react 0.2.2 → 0.2.4

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.
@@ -0,0 +1,5 @@
1
+ /** Keyed by src in Avatar so each source starts with its own load lifecycle. */
2
+ export declare function AvatarArtwork({ src, fallback }: {
3
+ src?: string;
4
+ fallback: string;
5
+ }): import("react").JSX.Element;
package/dist/avatar.js ADDED
@@ -0,0 +1,8 @@
1
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState } from "react";
3
+ import { Image } from "./index.js";
4
+ /** Keyed by src in Avatar so each source starts with its own load lifecycle. */
5
+ export function AvatarArtwork({ src, fallback }) {
6
+ const [state, setState] = useState("loading");
7
+ return _jsxs(_Fragment, { children: [_jsx("span", { className: "flood-avatar__fallback", "aria-hidden": "true", children: fallback }), src && state !== "error" && _jsx(Image, { src: src, alt: "", "aria-hidden": "true", decoding: "async", className: "flood-avatar__image", "data-ready": state === "ready", onLoad: () => setState("ready"), onError: () => setState("error") })] });
8
+ }
@@ -0,0 +1,4 @@
1
+ import { type InputHTMLAttributes } from "react";
2
+ export declare const MixedCheckbox: import("react").ForwardRefExoticComponent<InputHTMLAttributes<HTMLInputElement> & {
3
+ indeterminate: boolean;
4
+ } & import("react").RefAttributes<HTMLInputElement>>;
@@ -0,0 +1,15 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { forwardRef, useEffect, useImperativeHandle, useLayoutEffect, useRef } from "react";
3
+ const useBrowserLayoutEffect = typeof window === "undefined" ? useEffect : useLayoutEffect;
4
+ export const MixedCheckbox = forwardRef(function MixedCheckbox({ indeterminate, onChange, ...props }, ref) {
5
+ const node = useRef(null), mixed = useRef(indeterminate);
6
+ useImperativeHandle(ref, () => node.current, []);
7
+ useBrowserLayoutEffect(() => { mixed.current = indeterminate; if (node.current)
8
+ node.current.indeterminate = indeterminate; });
9
+ return _jsx("input", { ...props, ref: node, type: "checkbox", className: "flood-check", "aria-checked": indeterminate ? "mixed" : props["aria-checked"], onChange: event => { const control = event.currentTarget; try {
10
+ onChange?.(event);
11
+ }
12
+ finally {
13
+ control.indeterminate = mixed.current;
14
+ } } });
15
+ });
@@ -0,0 +1,16 @@
1
+ import type { Checkbox, Fieldset, FieldsetProps, ChoiceOption } from "./index.js";
2
+ interface Props extends Omit<FieldsetProps, "children" | "defaultValue"> {
3
+ name: string;
4
+ options: readonly ChoiceOption[];
5
+ kind: "radio" | "checkbox";
6
+ selected?: readonly string[];
7
+ initial: readonly string[];
8
+ onSelectionChange?: (value: string[]) => void;
9
+ required?: boolean;
10
+ orientation: "vertical" | "horizontal";
11
+ fieldsetComponent: typeof Fieldset;
12
+ choiceComponent: typeof Checkbox;
13
+ }
14
+ /** Private selection model; native inputs own keyboard, focus, forms and validity. */
15
+ export declare const ChoiceGroup: import("react").ForwardRefExoticComponent<Props & import("react").RefAttributes<HTMLFieldSetElement>>;
16
+ export {};
@@ -0,0 +1,52 @@
1
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { forwardRef, useEffect, useLayoutEffect, useImperativeHandle, useRef, useState } from "react";
3
+ const useBrowserLayoutEffect = typeof window === "undefined" ? useEffect : useLayoutEffect;
4
+ /** Private selection model; native inputs own keyboard, focus, forms and validity. */
5
+ export const ChoiceGroup = forwardRef(function ChoiceGroup({ kind, name, options, selected, initial, onSelectionChange, required, orientation, fieldsetComponent: Group, choiceComponent: Choice, legend, ...props }, forwardedRef) {
6
+ if (new Set(options.map(option => option.value)).size !== options.length)
7
+ throw new Error("Flood choice group option values must be unique");
8
+ const root = useRef(null);
9
+ const inputs = useRef(new Map());
10
+ const [local, setLocal] = useState(() => [...new Set(initial)]);
11
+ const controlled = selected !== undefined;
12
+ const values = controlled ? selected : local;
13
+ const latest = useRef({ controlled, selected, initial });
14
+ useBrowserLayoutEffect(() => { latest.current = { controlled, selected, initial }; });
15
+ useImperativeHandle(forwardedRef, () => root.current, []);
16
+ useEffect(() => {
17
+ const fieldset = root.current;
18
+ if (!fieldset)
19
+ return;
20
+ const eventRoot = fieldset.getRootNode();
21
+ const timers = new Set();
22
+ const reset = (event) => {
23
+ if (!fieldset.form || event.target !== fieldset.form)
24
+ return;
25
+ // Wait for the whole dispatch/default action, including React's delegated
26
+ // reset handler. A microtask can run between native event listeners.
27
+ const timer = setTimeout(() => {
28
+ timers.delete(timer);
29
+ if (event.defaultPrevented || root.current !== fieldset || event.target !== fieldset.form)
30
+ return;
31
+ const current = latest.current;
32
+ const next = [...new Set(current.controlled ? current.selected : current.initial)];
33
+ if (!current.controlled)
34
+ setLocal(next);
35
+ // Controlled groups must also undo native DOM reset if the host keeps its value.
36
+ inputs.current.forEach(input => { input.checked = next.includes(input.value); });
37
+ }, 0);
38
+ timers.add(timer);
39
+ };
40
+ eventRoot.addEventListener("reset", reset, true);
41
+ return () => { eventRoot.removeEventListener("reset", reset, true); timers.forEach(clearTimeout); };
42
+ }, []);
43
+ return _jsx(Group, { ...props, ref: root, legend: required ? _jsxs(_Fragment, { children: [legend, _jsx("span", { "aria-hidden": "true", children: " *" })] }) : legend, role: kind === "radio" ? "radiogroup" : "group", "aria-required": kind === "radio" ? required || undefined : undefined, "aria-orientation": kind === "radio" ? orientation : undefined, children: _jsxs("div", { className: "flood-choice-group", "data-orientation": orientation, children: [options.length === 0 && _jsx("span", { className: "flood-hint", children: "No options available." }), options.map(option => _jsx(Choice, { ref: input => { if (input)
44
+ inputs.current.set(option.value, input);
45
+ else
46
+ inputs.current.delete(option.value); }, name: name, value: option.value, label: option.label, form: props.form, disabled: option.disabled, required: kind === "radio" ? required : undefined, checked: values.includes(option.value), onChange: event => {
47
+ const next = kind === "radio" ? [option.value] : [...new Set(event.currentTarget.checked ? [...values, option.value] : values.filter(value => value !== option.value))];
48
+ if (!controlled)
49
+ setLocal(next);
50
+ onSelectionChange?.([...next]);
51
+ } }, option.value))] }) });
52
+ });
@@ -0,0 +1,3 @@
1
+ import type { DescriptionListProps, TableProps } from "./index.js";
2
+ export declare const DescribedData: import("react").ForwardRefExoticComponent<DescriptionListProps & import("react").RefAttributes<HTMLDListElement>>;
3
+ export declare const TabularData: import("react").ForwardRefExoticComponent<TableProps & import("react").RefAttributes<HTMLTableElement>>;
@@ -0,0 +1,11 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { forwardRef } from "react";
3
+ import { ScrollArea } from "./index.js";
4
+ export const DescribedData = forwardRef(function DescribedData({ items, layout, density, dividers, className, ...props }, ref) {
5
+ if (items.some(item => !item.id.trim()) || new Set(items.map(item => item.id)).size !== items.length)
6
+ throw new Error("Flood description items require unique nonempty IDs");
7
+ return _jsx("dl", { ...props, ref: ref, className: ["flood-description-list", className].filter(Boolean).join(" "), "data-layout": layout, "data-density": density, "data-dividers": dividers, children: items.map(item => _jsxs("div", { className: "flood-description-list__item", children: [_jsx("dt", { children: item.term }), _jsx("dd", { children: item.description })] }, item.id)) });
8
+ });
9
+ export const TabularData = forwardRef(function TabularData({ caption, children, hideCaption, density, striped, scrollLabel, className, hidden, dir, lang, ...props }, ref) {
10
+ return _jsx("div", { className: "flood-table-container", hidden: hidden, dir: dir, lang: lang, children: _jsx(ScrollArea, { orientation: "horizontal", "aria-label": scrollLabel ?? caption, children: _jsxs("table", { ...props, ref: ref, hidden: hidden, dir: dir, lang: lang, className: ["flood-table", className].filter(Boolean).join(" "), "data-density": density, "data-striped": striped, children: [_jsx("caption", { className: hideCaption ? "flood-visually-hidden" : undefined, children: caption }), children] }) }) });
11
+ });
@@ -0,0 +1,12 @@
1
+ import { type HTMLAttributes } from "react";
2
+ interface Props extends Omit<HTMLAttributes<HTMLDivElement>, "title"> {
3
+ title: string;
4
+ expanded: boolean;
5
+ onExpandedChange: (open: boolean) => void;
6
+ disabled?: boolean;
7
+ controlSize: "sm" | "md" | "lg" | "xl";
8
+ headingLevel?: 2 | 3 | 4 | 5 | 6;
9
+ }
10
+ /** Shared original disclosure anatomy. The closed panel stays mounted but hidden. */
11
+ export declare const Disclosure: import("react").ForwardRefExoticComponent<Props & import("react").RefAttributes<HTMLDivElement>>;
12
+ export {};
@@ -0,0 +1,21 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { createElement, forwardRef, useEffect, useId, useLayoutEffect, useRef, useImperativeHandle } from "react";
3
+ import { Button } from "./index.js";
4
+ const useBrowserLayoutEffect = typeof window === "undefined" ? useEffect : useLayoutEffect;
5
+ /** Shared original disclosure anatomy. The closed panel stays mounted but hidden. */
6
+ export const Disclosure = forwardRef(function Disclosure({ title, expanded, onExpandedChange, disabled, controlSize, headingLevel, className, children, id: explicitId, ...props }, ref) {
7
+ const generatedId = useId(), id = explicitId ?? `flood-disclosure-${generatedId}`;
8
+ const root = useRef(null), panel = useRef(null), trigger = useRef(null);
9
+ const focusedInside = useRef(false);
10
+ useImperativeHandle(ref, () => root.current, []);
11
+ useBrowserLayoutEffect(() => {
12
+ const active = panel.current?.ownerDocument.activeElement;
13
+ if (!expanded && focusedInside.current && !disabled && (panel.current?.contains(active ?? null) || active === panel.current?.ownerDocument.body))
14
+ trigger.current?.focus();
15
+ if (!expanded)
16
+ focusedInside.current = false;
17
+ }, [expanded, disabled]);
18
+ const button = _jsxs(Button, { ref: trigger, variant: "ghost", size: controlSize, id: `${id}-trigger`, type: "button", disabled: disabled, "aria-expanded": expanded, "aria-controls": `${id}-panel`, className: "flood-disclosure__trigger", onClick: () => onExpandedChange(!expanded), children: [_jsx("span", { className: "flood-disclosure__title", children: title }), _jsx("span", { className: "flood-disclosure__indicator", "aria-hidden": "true" })] });
19
+ return _jsxs("div", { ...props, id: id, ref: root, "data-open": expanded, className: ["flood-disclosure", `flood-control--${controlSize}`, className].filter(Boolean).join(" "), children: [createElement(headingLevel ? `h${headingLevel}` : "div", { className: "flood-disclosure__heading" }, button), _jsx("div", { ref: panel, id: `${id}-panel`, className: "flood-disclosure__panel", hidden: !expanded, onFocusCapture: () => { focusedInside.current = true; }, onBlurCapture: event => { if (event.relatedTarget && !event.currentTarget.contains(event.relatedTarget))
20
+ focusedInside.current = false; }, children: children })] });
21
+ });
@@ -0,0 +1,8 @@
1
+ import type { AlertProps, ProgressProps } from "./index.js";
2
+ export declare function validateRange(value: number | undefined, min: number, max: number, allowUnknown: boolean): void;
3
+ export declare const ValueIndicator: import("react").ForwardRefExoticComponent<ProgressProps & {
4
+ kind: "progress" | "meter";
5
+ min: number;
6
+ max: number;
7
+ } & import("react").RefAttributes<HTMLDivElement>>;
8
+ export declare const FeedbackMessage: import("react").ForwardRefExoticComponent<AlertProps & import("react").RefAttributes<HTMLDivElement>>;
@@ -0,0 +1,29 @@
1
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { forwardRef, useId } from "react";
3
+ import { VisuallyHidden } from "./index.js";
4
+ export function validateRange(value, min, max, allowUnknown) {
5
+ if (!Number.isFinite(min) || !Number.isFinite(max) || max <= min || !Number.isFinite(max - min))
6
+ throw new RangeError("Flood indicators require a finite increasing range");
7
+ if (value === undefined && allowUnknown)
8
+ return;
9
+ if (value === undefined || !Number.isFinite(value) || value < min || value > max)
10
+ throw new RangeError("Flood indicator value must be finite and within its range");
11
+ }
12
+ export const ValueIndicator = forwardRef(function ValueIndicator({ kind, label, value, min, max, valueText, size, tone, hideLabel, className, ...props }, ref) {
13
+ const labelId = `flood-indicator-${useId()}`;
14
+ validateRange(value, min, max, kind === "progress");
15
+ const unknown = value === undefined;
16
+ const percent = unknown ? undefined : ((value - min) / (max - min)) * 100;
17
+ const caption = _jsxs(_Fragment, { children: [_jsx("span", { id: labelId, children: label }), valueText && _jsx("span", { className: "flood-indicator__value", "aria-hidden": "true", children: valueText })] });
18
+ return _jsxs("div", { ...props, ref: ref, className: ["flood-indicator", className].filter(Boolean).join(" "), "data-kind": kind, "data-size": size, "data-tone": tone, "data-indeterminate": unknown, children: [hideLabel ? _jsx(VisuallyHidden, { as: "div", children: caption }) : _jsx("div", { className: "flood-indicator__caption", children: caption }), _jsx("div", { className: "flood-indicator__track", role: kind === "meter" ? "meter" : "progressbar", "aria-labelledby": labelId, "aria-valuemin": min, "aria-valuemax": max, "aria-valuenow": value, "aria-valuetext": valueText, children: _jsx("span", { className: "flood-indicator__fill", style: { inlineSize: percent === undefined ? undefined : `${percent}%` } }) })] });
19
+ });
20
+ function StatusGlyph({ tone }) {
21
+ return _jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.75", strokeLinecap: "round", strokeLinejoin: "round", focusable: "false", children: tone === "success" ? _jsxs(_Fragment, { children: [_jsx("circle", { cx: "12", cy: "12", r: "9" }), _jsx("path", { d: "m8 12 3 3 5-6" })] }) :
22
+ tone === "warning" ? _jsxs(_Fragment, { children: [_jsx("path", { d: "m12 3 10 17H2L12 3Z" }), _jsx("path", { d: "M12 9v4m0 3h.01" })] }) :
23
+ tone === "error" ? _jsxs(_Fragment, { children: [_jsx("circle", { cx: "12", cy: "12", r: "9" }), _jsx("path", { d: "m9 9 6 6m0-6-6 6" })] }) :
24
+ _jsxs(_Fragment, { children: [_jsx("circle", { cx: "12", cy: "12", r: "9" }), _jsx("path", { d: "M12 11v5m0-9h.01" })] }) });
25
+ }
26
+ export const FeedbackMessage = forwardRef(function FeedbackMessage({ title, tone, icon, announcement, children, className, ...props }, ref) {
27
+ const artwork = icon === undefined ? _jsx(StatusGlyph, { tone: tone }) : icon;
28
+ return _jsxs("div", { ...props, ref: ref, className: ["flood-alert", className].filter(Boolean).join(" "), "data-tone": tone, role: announcement === "assertive" ? "alert" : announcement === "polite" ? "status" : undefined, "aria-atomic": announcement === "off" ? undefined : true, children: [artwork != null && _jsx("span", { className: "flood-alert__icon", "aria-hidden": "true", children: artwork }), _jsxs("div", { className: "flood-alert__content", children: [title && _jsx("p", { className: "flood-alert__title", children: title }), children != null && _jsx("div", { className: "flood-alert__body", children: children })] })] });
29
+ });
@@ -0,0 +1,6 @@
1
+ import type { AriaAttributes, ReactNode } from "react";
2
+ import type { FieldStatus } from "./index.js";
3
+ /** Existing invalid semantics always take precedence over a positive status. */
4
+ export declare function resolveFieldStatus(status?: FieldStatus, invalid?: boolean, ariaInvalid?: AriaAttributes["aria-invalid"]): FieldStatus | undefined;
5
+ export declare const hasFieldContent: (value: ReactNode) => boolean;
6
+ export declare function mergeDescriptions(...values: Array<string | false | undefined>): string | undefined;
@@ -0,0 +1,8 @@
1
+ /** Existing invalid semantics always take precedence over a positive status. */
2
+ export function resolveFieldStatus(status, invalid, ariaInvalid) {
3
+ return invalid || (ariaInvalid !== undefined && ariaInvalid !== false && ariaInvalid !== "false") ? "error" : status;
4
+ }
5
+ export const hasFieldContent = (value) => value !== null && value !== undefined && value !== false && value !== true && value !== "";
6
+ export function mergeDescriptions(...values) {
7
+ return [...new Set(values.filter(Boolean).join(" ").split(/\s+/).filter(Boolean))].join(" ") || undefined;
8
+ }
@@ -0,0 +1,3 @@
1
+ import { type Ref, type RefCallback } from "react";
2
+ /** A native ref adapter; does not replace handlers or clone rich child content. */
3
+ export declare function useFocusableHidden<T extends HTMLElement>(enabled: boolean, forwardedRef?: Ref<T>): RefCallback<T>;
@@ -0,0 +1,99 @@
1
+ import { useCallback, useEffect, useLayoutEffect, useRef } from "react";
2
+ const useBrowserLayoutEffect = typeof window === "undefined" ? useEffect : useLayoutEffect;
3
+ const heldAttribute = "data-flood-focus-held";
4
+ const scopes = new WeakMap();
5
+ /** Keep in-flow focus reveal stable until the native pointer gesture completes. */
6
+ function createScope(document) {
7
+ const view = document.defaultView;
8
+ const entries = new Set(), held = new Set(), pointers = new Set();
9
+ let releaseTask;
10
+ const cancelTask = () => { if (releaseTask !== undefined)
11
+ view.clearTimeout(releaseTask); releaseTask = undefined; };
12
+ const release = () => { cancelTask(); held.forEach(node => node.removeAttribute(heldAttribute)); held.clear(); };
13
+ const releaseAfterDispatch = () => {
14
+ if (pointers.size || !held.size)
15
+ return;
16
+ cancelTask();
17
+ // A task, rather than a microtask, keeps the reveal through all native click
18
+ // listeners. Pointerup also schedules this fallback when no click is fired.
19
+ releaseTask = view.setTimeout(() => { releaseTask = undefined; if (!pointers.size)
20
+ release(); }, 0);
21
+ };
22
+ const down = (event) => {
23
+ if (event.button !== 0)
24
+ return;
25
+ cancelTask();
26
+ pointers.add(event.pointerId);
27
+ entries.forEach(entry => {
28
+ if (entry.enabled() && entry.node.matches(":focus-within")) {
29
+ entry.node.setAttribute(heldAttribute, "");
30
+ held.add(entry.node);
31
+ }
32
+ });
33
+ };
34
+ const up = (event) => { pointers.delete(event.pointerId); releaseAfterDispatch(); };
35
+ const click = () => releaseAfterDispatch();
36
+ const cancel = (event) => { pointers.delete(event.pointerId); if (!pointers.size)
37
+ release(); };
38
+ const blur = () => { pointers.clear(); release(); };
39
+ document.addEventListener("pointerdown", down, true);
40
+ document.addEventListener("pointerup", up, true);
41
+ document.addEventListener("click", click, true);
42
+ document.addEventListener("pointercancel", cancel, true);
43
+ view.addEventListener("blur", blur);
44
+ const scope = { add(entry) {
45
+ entries.add(entry);
46
+ return () => {
47
+ entries.delete(entry);
48
+ held.delete(entry.node);
49
+ entry.node.removeAttribute(heldAttribute);
50
+ if (entries.size)
51
+ return;
52
+ pointers.clear();
53
+ release();
54
+ document.removeEventListener("pointerdown", down, true);
55
+ document.removeEventListener("pointerup", up, true);
56
+ document.removeEventListener("click", click, true);
57
+ document.removeEventListener("pointercancel", cancel, true);
58
+ view.removeEventListener("blur", blur);
59
+ scopes.delete(document);
60
+ };
61
+ } };
62
+ return scope;
63
+ }
64
+ /** A native ref adapter; does not replace handlers or clone rich child content. */
65
+ export function useFocusableHidden(enabled, forwardedRef) {
66
+ const node = useRef(null), active = useRef(enabled), disconnect = useRef(() => { });
67
+ const forwardedCleanup = useRef(undefined);
68
+ useBrowserLayoutEffect(() => {
69
+ active.current = enabled;
70
+ if (!enabled)
71
+ node.current?.removeAttribute(heldAttribute);
72
+ }, [enabled]);
73
+ return useCallback((element) => {
74
+ disconnect.current();
75
+ disconnect.current = () => { };
76
+ node.current = element;
77
+ if (element?.ownerDocument.defaultView) {
78
+ const document = element.ownerDocument;
79
+ let scope = scopes.get(document);
80
+ if (!scope) {
81
+ scope = createScope(document);
82
+ scopes.set(document, scope);
83
+ }
84
+ disconnect.current = scope.add({ node: element, enabled: () => active.current });
85
+ }
86
+ if (typeof forwardedRef === "function") {
87
+ const previousCleanup = forwardedCleanup.current;
88
+ forwardedCleanup.current = undefined;
89
+ previousCleanup?.();
90
+ if (element !== null || !previousCleanup) {
91
+ const cleanup = forwardedRef(element);
92
+ if (typeof cleanup === "function")
93
+ forwardedCleanup.current = cleanup;
94
+ }
95
+ }
96
+ else if (forwardedRef)
97
+ forwardedRef.current = element;
98
+ }, [forwardedRef]);
99
+ }
@@ -1,18 +1,22 @@
1
1
  # Inter font asset
2
2
 
3
- `InterVariable.woff2` is an unmodified third-party font by the Inter Project
3
+ `InterVariable.woff2` and `InterVariable-Italic.woff2` are unmodified third-party fonts by the Inter Project
4
4
  Authors, distributed under the [SIL Open Font License 1.1](LICENSE.txt).
5
- It is not original Flood artwork. Retain the license with redistributed copies.
5
+ They are not original Flood artwork. Retain the license with redistributed copies.
6
6
 
7
7
  - Official source: <https://github.com/rsms/inter/releases/tag/v4.1>
8
8
  - Archive: <https://github.com/rsms/inter/releases/download/v4.1/Inter-4.1.zip>
9
- - Archive member: `web/InterVariable.woff2`
9
+ - Normal archive member: `web/InterVariable.woff2`
10
+ - Italic archive member: `web/InterVariable-Italic.woff2`
10
11
  - Font internal version: `Version 4.001;git-9221beed3`
11
12
  - Variable axes: weight 100–900; optical size 14–32.
12
13
  - SHA-256: `693b77d4f32ee9b8bfc995589b5fad5e99adf2832738661f5402f9978429a8e3`
14
+ - Italic SHA-256: `e564f652916db6c139570fefb9524a77c4d48f30c92928de9db19b6b5c7a262a`
13
15
  - License SHA-256: `262481e844521b326f5ecd053e59b98c8b2da78c8ee1bdbb6e8174305e54935a`
14
16
 
15
17
  The full upstream font includes Cyrillic; it has not been subsetted or converted.
16
18
  The original `LICENSE.txt` is copied verbatim from the same release archive.
17
- The asset supplies normal variable weights, including the Flood 400/500 roles;
18
- italic text needs a separate face or an explicitly documented fallback.
19
+ Both assets supply weight 100–900 and optical size 14–32, including Flood's 400/500
20
+ roles. CSS registers separate normal and italic faces under Inter; native em/i
21
+ content uses the real italic asset instead of synthetic slant. Both files include
22
+ Latin and Cyrillic and retain the same unmodified upstream license.