@sincpro/web-ui 1.2.0 → 1.4.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/activity/Activity.js +1 -1
- package/dist/components/activity/Messages.d.ts +4 -2
- package/dist/components/activity/Messages.js +4 -4
- package/dist/components/activity/Timeline.js +1 -1
- package/dist/components/assist/AssistMark.js +1 -1
- package/dist/components/assist/AssistSuggestion.js +1 -1
- package/dist/components/assist/ChangeReview.js +2 -2
- package/dist/components/calendar/RecordCalendar.js +4 -1
- package/dist/components/form/Sheet.js +5 -2
- package/dist/components/lines/LineItems.d.ts +1 -1
- package/dist/components/lines/LineItems.js +2 -2
- package/dist/components/listing/ListingBar.js +1 -1
- package/dist/components/listing/RecordCard.d.ts +4 -0
- package/dist/components/listing/RecordCard.js +4 -1
- package/dist/components/listing/Row.d.ts +7 -0
- package/dist/components/listing/Row.js +7 -1
- package/dist/components/listing/ViewSwitcher.d.ts +7 -2
- package/dist/components/listing/index.d.ts +1 -1
- package/dist/components/listing/set.d.ts +4 -1
- package/dist/components/record/Breadcrumb.js +5 -1
- package/dist/components/record/DirtyActions.d.ts +20 -6
- package/dist/components/record/DirtyActions.js +55 -4
- package/dist/components/record/RecordHeader.d.ts +1 -1
- package/dist/components/record/RecordHeader.js +32 -13
- package/dist/components/record/RecordSkeleton.js +1 -1
- package/dist/components/search/CriteriaBuilder.d.ts +1 -1
- package/dist/components/search/CriteriaBuilder.js +3 -3
- package/dist/components/search/FilterBuilder.d.ts +1 -18
- package/dist/components/search/FilterBuilder.js +4 -4
- package/dist/components/search/JsonEditor.js +5 -1
- package/dist/components/search/SearchBar.d.ts +1 -2
- package/dist/components/search/SearchBar.js +12 -3
- package/dist/components/search/criteria.d.ts +17 -1
- package/dist/components/search/index.d.ts +2 -1
- package/dist/components/search/index.js +1 -0
- package/dist/components/shell/Panes.d.ts +18 -0
- package/dist/components/shell/Panes.js +36 -0
- package/dist/components/shell/index.d.ts +1 -0
- package/dist/components/shell/index.js +2 -0
- package/dist/foundations/calendar.d.ts +3 -1
- package/dist/foundations/catalog.d.ts +27 -0
- package/dist/foundations/catalog.js +48 -0
- package/dist/foundations/copy.d.ts +40 -62
- package/dist/foundations/copy.es.js +10 -0
- package/dist/foundations/copy.js +25 -93
- package/dist/{primitives/field/context.d.ts → foundations/form.d.ts} +9 -0
- package/dist/{primitives/field/mode.js → foundations/form.js} +11 -0
- package/dist/foundations/grouping.d.ts +9 -3
- package/dist/foundations/index.d.ts +2 -0
- package/dist/foundations/index.js +5 -0
- package/dist/foundations/sticky.d.ts +4 -0
- package/dist/foundations/sticky.js +1 -0
- package/dist/primitives/button/Button.d.ts +12 -2
- package/dist/primitives/button/Button.js +12 -2
- package/dist/primitives/cell/CellStack.js +1 -1
- package/dist/primitives/choice/ComboBox.d.ts +1 -1
- package/dist/primitives/choice/ComboBox.js +4 -4
- package/dist/primitives/choice/RadioGroup.d.ts +1 -1
- package/dist/primitives/choice/RadioGroup.js +3 -3
- package/dist/primitives/choice/SearchField.js +1 -1
- package/dist/primitives/choice/SegmentedControl.d.ts +1 -1
- package/dist/primitives/choice/SegmentedControl.js +2 -2
- package/dist/primitives/choice/Slider.d.ts +1 -1
- package/dist/primitives/choice/Slider.js +2 -2
- package/dist/primitives/choice/Switch.d.ts +1 -1
- package/dist/primitives/choice/Switch.js +2 -2
- package/dist/primitives/date/Calendar.js +1 -1
- package/dist/primitives/date/DatePicker.d.ts +1 -1
- package/dist/primitives/date/DatePicker.js +2 -2
- package/dist/primitives/date/DateTime.d.ts +1 -1
- package/dist/primitives/date/DateTime.js +2 -2
- package/dist/primitives/field/Checks.d.ts +1 -1
- package/dist/primitives/field/Checks.js +4 -4
- package/dist/primitives/field/DropZone.d.ts +1 -1
- package/dist/primitives/field/DropZone.js +2 -2
- package/dist/primitives/field/Field.d.ts +10 -5
- package/dist/primitives/field/Field.js +10 -6
- package/dist/primitives/field/Suggest.d.ts +1 -1
- package/dist/primitives/field/Suggest.js +2 -2
- package/dist/primitives/field/index.d.ts +1 -3
- package/dist/primitives/field/index.js +4 -4
- package/dist/primitives/notice/Notice.js +3 -1
- package/dist/primitives/picker/Picker.d.ts +1 -1
- package/dist/primitives/picker/Picker.js +4 -4
- package/dist/primitives/{tag → picker}/TagsField.d.ts +2 -2
- package/dist/primitives/{tag → picker}/TagsField.js +5 -5
- package/dist/primitives/picker/index.d.ts +2 -0
- package/dist/primitives/picker/index.js +5 -1
- package/dist/primitives/table/CellCopy.js +1 -1
- package/dist/primitives/table/CellEditor.js +2 -4
- package/dist/primitives/table/DataTable.d.ts +11 -1
- package/dist/primitives/table/DataTable.js +111 -38
- package/dist/primitives/table/EditableTable.js +6 -2
- package/dist/primitives/table/PivotTable.js +0 -0
- package/dist/primitives/table/ReportTable.js +4 -3
- package/dist/primitives/table/SheetTable.js +8 -14
- package/dist/primitives/table/Table.js +2 -2
- package/dist/primitives/tabs/Tabs.d.ts +22 -4
- package/dist/primitives/tabs/Tabs.js +77 -23
- package/dist/primitives/tabs/index.d.ts +1 -1
- package/dist/primitives/tabs/index.js +1 -1
- package/dist/primitives/tag/Tags.js +4 -1
- package/dist/primitives/tag/index.d.ts +0 -2
- package/dist/primitives/tag/index.js +0 -3
- package/dist/primitives/value/Avatar.js +8 -1
- package/dist/primitives/value/{Fecha.d.ts → DateValue.d.ts} +5 -2
- package/dist/primitives/value/{Fecha.js → DateValue.js} +9 -8
- package/dist/primitives/value/Relation.d.ts +1 -1
- package/dist/primitives/value/Relation.js +2 -2
- package/dist/primitives/value/Value.js +4 -1
- package/dist/primitives/value/index.d.ts +1 -1
- package/dist/primitives/value/index.js +1 -1
- package/dist/tokens.css +42 -0
- package/dist/views/list/List.d.ts +22 -2
- package/dist/views/list/List.js +28 -6
- package/dist/views/list/ListView.d.ts +1 -1
- package/dist/views/list/ListView.js +47 -16
- package/dist/views/list/index.d.ts +2 -2
- package/dist/views/list/presentations.js +3 -1
- package/dist/views/list/types.d.ts +57 -1
- package/dist/views/list/useNarrowList.d.ts +2 -2
- package/dist/views/list/useNarrowList.js +5 -5
- package/dist/views/record/Record.js +46 -42
- package/package.json +1 -1
- package/dist/primitives/field/context.js +0 -13
- package/dist/primitives/field/mode.d.ts +0 -9
- /package/dist/primitives/field/{copy.d.ts → clipboard.d.ts} +0 -0
- /package/dist/primitives/field/{copy.js → clipboard.js} +0 -0
- /package/dist/primitives/{tag/choices.d.ts → picker/tagChoices.d.ts} +0 -0
- /package/dist/primitives/{tag/choices.js → picker/tagChoices.js} +0 -0
|
@@ -1,22 +1,5 @@
|
|
|
1
|
-
import type { CriteriaJoin } from "./criteria.js";
|
|
1
|
+
import type { BuiltFilter, CriteriaJoin } from "./criteria.js";
|
|
2
2
|
import { type ModelDefinition } from "./model.js";
|
|
3
|
-
/** Una condición, tal como la declara el motor. `operator` sale del `ops` del campo, así que
|
|
4
|
-
* lo que el constructor ofrece es exactamente lo que el modelo acepta. */
|
|
5
|
-
export interface BuiltCondition {
|
|
6
|
-
field: string;
|
|
7
|
-
operator: string;
|
|
8
|
-
value: unknown;
|
|
9
|
-
}
|
|
10
|
-
/** Lo que el filtro puede ser en su forma real: una condición, un grupo `all`/`any` de más
|
|
11
|
-
* filtros —lo que el árbol arma, a cualquier profundidad—, o un `negate` que llegó de otro
|
|
12
|
-
* lado y que el árbol no fabrica, pero que este componente sabe mostrar y editar como JSON. */
|
|
13
|
-
export type BuiltFilter = BuiltCondition | {
|
|
14
|
-
all: BuiltFilter[];
|
|
15
|
-
} | {
|
|
16
|
-
any: BuiltFilter[];
|
|
17
|
-
} | {
|
|
18
|
-
negate: BuiltFilter;
|
|
19
|
-
};
|
|
20
3
|
/** Si esto es una de las formas que el filtro reconoce — condición, o grupo `all`/`any`/
|
|
21
4
|
* `negate`. Un JSON válido que no es ninguna de éstas —`{}`, `{"foo": 1}`, un array suelto—
|
|
22
5
|
* no es un filtro que este componente sepa dibujar, y se trata como error de forma en vez de
|
|
@@ -355,13 +355,13 @@ export function WhereTree({ model, group, onChange, depth = 0, focus, }) {
|
|
|
355
355
|
? (node) => node?.scrollIntoView({ block: "nearest" })
|
|
356
356
|
: undefined, className: `flex min-w-0 flex-wrap items-center gap-2 rounded-control ${focus !== undefined && child.field === focus
|
|
357
357
|
? "-m-1 p-1 ring-1 ring-primary/60"
|
|
358
|
-
: ""}`, children: [_jsx("select", { value: child.field, onChange: (event) => setChild(index, {
|
|
358
|
+
: ""}`, children: [_jsx("select", { "aria-label": copy.builder.ruleField, value: child.field, onChange: (event) => setChild(index, {
|
|
359
359
|
...child,
|
|
360
360
|
field: event.target.value,
|
|
361
361
|
operator: model.fields[event.target.value]?.ops.includes(child.operator)
|
|
362
362
|
? child.operator
|
|
363
363
|
: (model.fields[event.target.value]?.ops[0] ?? "="),
|
|
364
|
-
}), className: `min-w-0 flex-1 basis-40 rounded-control border px-2 py-1 text-meta ${box}`, children: names.map((name) => (_jsx("option", { value: name, children: name }, name))) }), _jsx("select", { value: child.operator, onChange: (event) => setChild(index, { ...child, operator: event.target.value }), className: `min-w-0 flex-1 basis-36 rounded-control border px-2 py-1 text-meta ${box}`, children: (model.fields[child.field]?.ops ?? []).map((operator) => (_jsx("option", { value: operator, children: opLabel(operator) }, operator))) }), WITHOUT_VALUE.has(child.operator) ? (_jsx("span", { className: "flex-1 text-meta text-muted", children: copy.builder.noValue })) : WITH_RANGE.has(child.operator) ? (_jsxs("span", { className: "flex min-w-0 flex-1 basis-40 items-center gap-1", children: [_jsx("input", { type: inputType(model.fields[child.field]?.type ?? "text"), value: child.value, onChange: (event) => setChild(index, { ...child, value: event.target.value }), placeholder: copy.builder.from, className: `min-w-0 flex-1 rounded-control border px-2 py-1 text-meta ${box}` }), _jsx("span", { className: "shrink-0 text-micro text-muted", children: copy.search.and }), _jsx("input", { type: inputType(model.fields[child.field]?.type ?? "text"), value: child.upper ?? "", onChange: (event) => setChild(index, { ...child, upper: event.target.value }), placeholder: copy.builder.to, className: `min-w-0 flex-1 rounded-control border px-2 py-1 text-meta ${box}` })] })) : (_jsx("input", { type: inputType(model.fields[child.field]?.type ?? "text"), value: child.value, onChange: (event) => setChild(index, { ...child, value: event.target.value }), placeholder: copy.builder.value, className: `min-w-0 flex-1 basis-40 rounded-control border px-2 py-1 text-meta ${box}` })), _jsxs("span", { className: "ml-auto flex shrink-0 items-center gap-2", children: [_jsx("button", { type: "button", "aria-label": copy.builder.addRule, onClick: () => addConditionAfter(index), className: "text-muted hover:text-ink", children: _jsx(Plus, { className: "size-4" }) }), _jsx("button", { type: "button", "aria-label": copy.builder.addNested, onClick: () => addGroupAfter(index), className: "text-muted hover:text-ink", children: _jsx(FolderTree, { className: "size-4" }) }), _jsx("button", { type: "button", "aria-label": copy.builder.dropRule, disabled: group.children.length === 1, onClick: () => removeAt(index), className: "text-muted disabled:opacity-30", children: _jsx(Trash2, { className: "size-4" }) })] })] }, index)))] }));
|
|
364
|
+
}), className: `min-w-0 flex-1 basis-40 rounded-control border px-2 py-1 text-meta ${box}`, children: names.map((name) => (_jsx("option", { value: name, children: name }, name))) }), _jsx("select", { "aria-label": copy.builder.ruleOperator, value: child.operator, onChange: (event) => setChild(index, { ...child, operator: event.target.value }), className: `min-w-0 flex-1 basis-36 rounded-control border px-2 py-1 text-meta ${box}`, children: (model.fields[child.field]?.ops ?? []).map((operator) => (_jsx("option", { value: operator, children: opLabel(operator) }, operator))) }), WITHOUT_VALUE.has(child.operator) ? (_jsx("span", { className: "flex-1 text-meta text-muted", children: copy.builder.noValue })) : WITH_RANGE.has(child.operator) ? (_jsxs("span", { className: "flex min-w-0 flex-1 basis-40 items-center gap-1", children: [_jsx("input", { type: inputType(model.fields[child.field]?.type ?? "text"), value: child.value, onChange: (event) => setChild(index, { ...child, value: event.target.value }), placeholder: copy.builder.from, className: `min-w-0 flex-1 rounded-control border px-2 py-1 text-meta ${box}` }), _jsx("span", { className: "shrink-0 text-micro text-muted", children: copy.search.and }), _jsx("input", { type: inputType(model.fields[child.field]?.type ?? "text"), value: child.upper ?? "", onChange: (event) => setChild(index, { ...child, upper: event.target.value }), placeholder: copy.builder.to, className: `min-w-0 flex-1 rounded-control border px-2 py-1 text-meta ${box}` })] })) : (_jsx("input", { type: inputType(model.fields[child.field]?.type ?? "text"), value: child.value, onChange: (event) => setChild(index, { ...child, value: event.target.value }), placeholder: copy.builder.value, className: `min-w-0 flex-1 basis-40 rounded-control border px-2 py-1 text-meta ${box}` })), _jsxs("span", { className: "ml-auto flex shrink-0 items-center gap-2", children: [_jsx("button", { type: "button", "aria-label": copy.builder.addRule, onClick: () => addConditionAfter(index), className: "text-muted hover:text-ink", children: _jsx(Plus, { className: "size-4" }) }), _jsx("button", { type: "button", "aria-label": copy.builder.addNested, onClick: () => addGroupAfter(index), className: "text-muted hover:text-ink", children: _jsx(FolderTree, { className: "size-4" }) }), _jsx("button", { type: "button", "aria-label": copy.builder.dropRule, disabled: group.children.length === 1, onClick: () => removeAt(index), className: "text-muted disabled:opacity-30", children: _jsx(Trash2, { className: "size-4" }) })] })] }, index)))] }));
|
|
365
365
|
}
|
|
366
366
|
export function FilterBuilder({ model, initial, onApply, onCancel, }) {
|
|
367
367
|
const names = filterableNames(model);
|
|
@@ -400,11 +400,11 @@ export function FilterBuilder({ model, initial, onApply, onCancel, }) {
|
|
|
400
400
|
parsed = JSON.parse(text);
|
|
401
401
|
}
|
|
402
402
|
catch {
|
|
403
|
-
setRawError(
|
|
403
|
+
setRawError(copy.builder.invalidJson);
|
|
404
404
|
return;
|
|
405
405
|
}
|
|
406
406
|
if (!isRecognizedFilter(parsed)) {
|
|
407
|
-
setRawError(
|
|
407
|
+
setRawError(copy.builder.notFilter);
|
|
408
408
|
return;
|
|
409
409
|
}
|
|
410
410
|
setRawError(null);
|
|
@@ -8,6 +8,7 @@ import { useTheme } from "../../foundations/theme.js";
|
|
|
8
8
|
import { Button } from "../../primitives/button/index.js";
|
|
9
9
|
import CodeMirror, { EditorView, Prec } from "@uiw/react-codemirror";
|
|
10
10
|
import { Wand2 } from "lucide-react";
|
|
11
|
+
import { useId } from "react";
|
|
11
12
|
/* ====================================================================== *
|
|
12
13
|
* UN JSON QUE SE ESCRIBE A MANO, EN UN EDITOR DE VERDAD.
|
|
13
14
|
*
|
|
@@ -56,11 +57,14 @@ const SURFACE_THEME = EditorView.theme({
|
|
|
56
57
|
});
|
|
57
58
|
export function JsonEditor({ value, onChange, error, height = "16rem", label, }) {
|
|
58
59
|
const copy = useUiCopy();
|
|
60
|
+
const labelId = useId();
|
|
59
61
|
const surface = useSurface();
|
|
60
62
|
const { resolved } = useTheme();
|
|
61
63
|
/* The chrome is dark in both themes; the canvas follows the theme. */
|
|
62
64
|
const dark = surface === "chrome" || resolved === "dark";
|
|
63
|
-
return (_jsxs("div", { className: "flex min-w-0 flex-col gap-1", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsx("p", { className: "text-micro text-muted", children: label ?? copy.builder.editor }), _jsxs(Button, { role: "ghost", size: "sm", disabled: value.trim() === "", onClick: () => onChange(formatJson(value)), children: [_jsx(Wand2, { className: "size-3" }), " ", copy.builder.format] })] }), _jsx("div", { className: `overflow-hidden rounded-control border ${error == null ? "border-ink/15" : "border-danger"}`, children: _jsx(CodeMirror, { value: value, onChange: onChange, height: height, theme: dark ? "dark" : "light", extensions: [
|
|
65
|
+
return (_jsxs("div", { className: "flex min-w-0 flex-col gap-1", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsx("p", { id: labelId, className: "text-micro text-muted", children: label ?? copy.builder.editor }), _jsxs(Button, { role: "ghost", size: "sm", disabled: value.trim() === "", onClick: () => onChange(formatJson(value)), children: [_jsx(Wand2, { className: "size-3" }), " ", copy.builder.format] })] }), _jsx("div", { className: `overflow-hidden rounded-control border ${error == null ? "border-ink/15" : "border-danger"}`, children: _jsx(CodeMirror, { value: value, onChange: onChange, height: height, theme: dark ? "dark" : "light", extensions: [
|
|
66
|
+
/* The editable area is CodeMirror's own element: named here or nowhere. */
|
|
67
|
+
EditorView.contentAttributes.of({ "aria-labelledby": labelId }),
|
|
64
68
|
json(),
|
|
65
69
|
linter(jsonParseLinter()),
|
|
66
70
|
lintGutter(),
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import { type CriteriaJoin, type CriteriaPath, type SearchCriteria } from "./criteria.js";
|
|
2
|
-
import type { BuiltCondition, BuiltFilter } from "./FilterBuilder.js";
|
|
1
|
+
import { type BuiltCondition, type BuiltFilter, type CriteriaJoin, type CriteriaPath, type SearchCriteria } from "./criteria.js";
|
|
3
2
|
import type { ModelDefinition } from "./model.js";
|
|
4
3
|
import type { Surface } from "../../foundations/types.js";
|
|
5
4
|
import type { MenuGroup } from "../../primitives/menu/Menu.js";
|
|
@@ -74,6 +74,11 @@ export function SearchBar({ facets, model, criteria, onCriteriaChange, placehold
|
|
|
74
74
|
const input = useRef(null);
|
|
75
75
|
const box = useRef(null);
|
|
76
76
|
const id = useId();
|
|
77
|
+
/* The suggestions are the input's popup (the ARIA combobox pattern): the input keeps the
|
|
78
|
+
focus and says which list it controls and which option is the active one, so a screen
|
|
79
|
+
reader announces «Nombre del cliente · contiene Globex» as the option it is. */
|
|
80
|
+
const listId = useId();
|
|
81
|
+
const optionId = (at) => `${listId}-${at}`;
|
|
77
82
|
/**
|
|
78
83
|
* Los campos por los que se puede preguntar.
|
|
79
84
|
*
|
|
@@ -290,7 +295,9 @@ export function SearchBar({ facets, model, criteria, onCriteriaChange, placehold
|
|
|
290
295
|
removeLabel: copy.search.dropOp,
|
|
291
296
|
},
|
|
292
297
|
]),
|
|
293
|
-
] }), _jsx("input", { ref: input, "aria-label": copy.choice.search,
|
|
298
|
+
] }), _jsx("input", { ref: input, "aria-label": copy.choice.search, role: "combobox", "aria-autocomplete": "list", "aria-expanded": open, ...(open ? { "aria-controls": listId } : {}), ...(open && !asTags && suggestions[active] !== undefined
|
|
299
|
+
? { "aria-activedescendant": optionId(active) }
|
|
300
|
+
: {}), value: text, onChange: (e) => setText(e.target.value), onKeyDown: (e) => {
|
|
294
301
|
/* Retroceso con el campo vacío saca la ÚLTIMA instancia: el gesto
|
|
295
302
|
que ya existe en cualquier campo de fichas y el único que se
|
|
296
303
|
descubre sin que nadie lo explique. La última y no la última de
|
|
@@ -392,7 +399,7 @@ export function SearchBar({ facets, model, criteria, onCriteriaChange, placehold
|
|
|
392
399
|
/* UNA LISTA, no una tira. Se está ELIGIENDO: el ojo baja por una
|
|
393
400
|
columna y el teclado también. Doce atributos doblados en tres
|
|
394
401
|
renglones son doce cosas que barrer en zigzag para encontrar una. */
|
|
395
|
-
_jsx("div", { className: "max-h-72 overflow-y-auto p-1", children: _jsx(Tags, { stack: true, surface: "canvas", label: step.field !== undefined ? copy.search.askOp : copy.search.askAttribute, items: suggestions.map((one, at) => ({
|
|
402
|
+
_jsx("div", { id: listId, className: "max-h-72 overflow-y-auto p-1", children: _jsx(Tags, { stack: true, surface: "canvas", label: step.field !== undefined ? copy.search.askOp : copy.search.askAttribute, items: suggestions.map((one, at) => ({
|
|
396
403
|
id: one.id,
|
|
397
404
|
label: one.label,
|
|
398
405
|
...(one.kind === "op"
|
|
@@ -402,7 +409,9 @@ export function SearchBar({ facets, model, criteria, onCriteriaChange, placehold
|
|
|
402
409
|
: {}),
|
|
403
410
|
active: at === active,
|
|
404
411
|
onSelect: () => pick(one),
|
|
405
|
-
})) }) })) : (_jsx("div", {
|
|
412
|
+
})) }) })) : (_jsx("div", { id: listId, role: "listbox", "aria-label": copy.search.suggestions, className: "p-1", children: suggestions.map((one, at) => (_jsxs("div", { id: optionId(at), role: "option", "aria-selected": at === active,
|
|
413
|
+
/* The input keeps the focus: pressing an option must not take it. */
|
|
414
|
+
onMouseDown: (event) => event.preventDefault(), onClick: () => pick(one), onMouseEnter: () => setActive(at), className: `flex w-full cursor-pointer items-baseline gap-1.5 rounded-control px-2 py-1.5 text-left text-meta transition-colors duration-(--duration-instant) ${at === active ? "bg-primary/8" : "hover:bg-ink/4"}`, children: [_jsx("span", { className: "shrink-0 font-medium text-ink", children: one.label }), one.kind === "condition" || one.kind === "literal" ? (_jsxs(_Fragment, { children: [_jsx("span", { className: "shrink-0 text-muted", children: ":" }), _jsx("span", { className: "min-w-0 truncate font-mono text-micro text-ink", children: one.hint })] })) : one.kind === "field" && one.hint !== undefined ? (_jsx("span", { className: "inline-flex shrink-0 items-center rounded-pill border border-line-strong bg-ink/5 px-2 py-0.5 text-micro text-muted", children: one.hint })) : null] }, one.id))) }))] })) : null, panel && (columns.length > 0 || onOpenBuilder !== undefined) ? (_jsxs("div", { id: id, className: "absolute top-full right-0 z-30 mt-1 flex w-max max-w-full flex-wrap overflow-hidden rounded-panel border border-line-strong bg-surface shadow-float", children: [columns.map((c, i) => (_jsxs("div", { className: `flex min-w-48 flex-1 flex-col ${i === 0 ? "" : "border-t border-line sm:border-t-0 sm:border-l"}`, children: [_jsx("div", { className: "border-b border-line px-2.5 py-1.5 text-micro font-medium text-muted", children: c.title }), _jsx("div", { className: "flex flex-col gap-0.5 p-1", children: c.content ??
|
|
406
415
|
c.groups?.map((g, gi) => (_jsx(Group, { group: g, first: gi === 0, onSelect: c.onSelect }, g.label ?? gi))) })] }, c.title))), onOpenBuilder === undefined ? null : (_jsx("div", { className: "w-full border-t border-line p-1", children: _jsxs("button", { type: "button", onClick: () => {
|
|
407
416
|
onOpenBuilder();
|
|
408
417
|
setPanel(false);
|
|
@@ -1,5 +1,21 @@
|
|
|
1
|
-
import type { BuiltCondition, BuiltFilter } from "./FilterBuilder.js";
|
|
2
1
|
import type { GroupGrain, GroupLevel } from "../../foundations/grouping.js";
|
|
2
|
+
/** Una condición, tal como la declara el motor. `operator` sale del `ops` del campo, así que
|
|
3
|
+
* lo que el constructor ofrece es exactamente lo que el modelo acepta. */
|
|
4
|
+
export interface BuiltCondition {
|
|
5
|
+
field: string;
|
|
6
|
+
operator: string;
|
|
7
|
+
value: unknown;
|
|
8
|
+
}
|
|
9
|
+
/** Lo que el filtro puede ser en su forma real: una condición, un grupo `all`/`any` de más
|
|
10
|
+
* filtros —lo que el árbol arma, a cualquier profundidad—, o un `negate` que llegó de otro
|
|
11
|
+
* lado y que el árbol no fabrica, pero que este componente sabe mostrar y editar como JSON. */
|
|
12
|
+
export type BuiltFilter = BuiltCondition | {
|
|
13
|
+
all: BuiltFilter[];
|
|
14
|
+
} | {
|
|
15
|
+
any: BuiltFilter[];
|
|
16
|
+
} | {
|
|
17
|
+
negate: BuiltFilter;
|
|
18
|
+
};
|
|
3
19
|
/** Cómo se juntan las instancias de un nodo. `not` niega lo que tiene adentro. */
|
|
4
20
|
export type CriteriaJoin = "all" | "any" | "not";
|
|
5
21
|
/**
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
export { type BuiltCondition, type BuiltFilter, } from "./criteria.js";
|
|
1
2
|
export { type BuiltCriteria, type BuiltLevel, type BuiltSort, CriteriaBuilder, } from "./CriteriaBuilder.js";
|
|
2
|
-
export { blankGroup,
|
|
3
|
+
export { blankGroup, describeFilter, FilterBuilder, type TreeCondition, treeFromFilter, type TreeGroup, type TreeNode, treeToFilter, } from "./FilterBuilder.js";
|
|
3
4
|
export { formatJson, JsonEditor } from "./JsonEditor.js";
|
|
4
5
|
export { SearchBar, type SearchBarProps, type SearchFacet, } from "./SearchBar.js";
|
|
5
6
|
export { at, criteriaId, type CriteriaJoin, type CriteriaPath, filterCriteria, filtersOf, type GroupCriteria, groupCriteria, groupsOf, hasNamed, levelsOf, nextJoin, removeAt, replaceAt, type SearchCriteria, toggleNamed, whereOf, withLevels, } from "./criteria.js";
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
/* Buscar con facetas, con los recortes metidos adentro del campo. */
|
|
2
|
+
export {} from "./criteria.js";
|
|
2
3
|
export { CriteriaBuilder, } from "./CriteriaBuilder.js";
|
|
3
4
|
export { blankGroup, describeFilter, FilterBuilder, treeFromFilter, treeToFilter, } from "./FilterBuilder.js";
|
|
4
5
|
export { formatJson, JsonEditor } from "./JsonEditor.js";
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
declare const BASIS: {
|
|
3
|
+
readonly narrow: "@4xl:flex-2";
|
|
4
|
+
readonly wide: "@4xl:flex-3";
|
|
5
|
+
};
|
|
6
|
+
/** One column of the screen. `narrow` for the set you pick from, `wide` for the work. */
|
|
7
|
+
export declare function Pane({ children, size, label, }: {
|
|
8
|
+
children: ReactNode;
|
|
9
|
+
size?: keyof typeof BASIS;
|
|
10
|
+
/** What this pane is — «Extracto», «Por conciliar». Panes are regions a screen reader
|
|
11
|
+
* jumps between, so each one is named. */
|
|
12
|
+
label: string;
|
|
13
|
+
}): import("react").JSX.Element;
|
|
14
|
+
/** The panes, side by side on a desk and stacked when narrow. Fills the height it is given. */
|
|
15
|
+
export declare function Panes({ children }: {
|
|
16
|
+
children: ReactNode;
|
|
17
|
+
}): import("react").JSX.Element;
|
|
18
|
+
export {};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { HeaderSlotContext } from "./headerSlot.js";
|
|
4
|
+
import { stickyReset } from "../../foundations/sticky.js";
|
|
5
|
+
/* ==================================================================== *
|
|
6
|
+
* PANES — a screen of N sets side by side, each one with its own search.
|
|
7
|
+
*
|
|
8
|
+
* Context: Odoo's bank reconciliation is the case — the statement lines on one side, the
|
|
9
|
+
* record being matched and the journal items that could match it on the other, each with
|
|
10
|
+
* its own search, and the person going back and forth between them. A list that fills the
|
|
11
|
+
* page carries its bar up to the screen's header (`useHeaderSlot`), and there is ONE
|
|
12
|
+
* header: with two or three lists they fought over it, and each bar stuck to the page
|
|
13
|
+
* instead of to its own column.
|
|
14
|
+
*
|
|
15
|
+
* A pane is the answer: on a desk each pane SCROLLS ON ITS OWN — a long list on the left
|
|
16
|
+
* does not drag the record on the right out of sight — so what sticks inside it (a list's
|
|
17
|
+
* bar, a table's header) sticks to the pane's edge, and nothing inside goes up to the
|
|
18
|
+
* screen's header. Narrow, the panes stack and the page scrolls: a scroll inside a scroll
|
|
19
|
+
* on a phone is a list nobody can leave.
|
|
20
|
+
*
|
|
21
|
+
* It decides by ITS OWN width (it is the `@container`), not the window's: a workspace panel
|
|
22
|
+
* among four is narrow on a wide screen, and a view mounted without a container around it
|
|
23
|
+
* would never go side by side.
|
|
24
|
+
* ==================================================================== */
|
|
25
|
+
const BASIS = { narrow: "@4xl:flex-2", wide: "@4xl:flex-3" };
|
|
26
|
+
/** One column of the screen. `narrow` for the set you pick from, `wide` for the work. */
|
|
27
|
+
export function Pane({ children, size = "wide", label, }) {
|
|
28
|
+
return (_jsx(HeaderSlotContext.Provider, { value: null, children: _jsx("section", { "aria-label": label, style: stickyReset(),
|
|
29
|
+
/* Stacked it keeps its own height (the page scrolls); side by side it shares the
|
|
30
|
+
height and scrolls inside. */
|
|
31
|
+
className: `flex min-h-0 min-w-0 shrink-0 flex-col @4xl:shrink @4xl:basis-0 @4xl:overflow-y-auto ${BASIS[size]}`, children: children }) }));
|
|
32
|
+
}
|
|
33
|
+
/** The panes, side by side on a desk and stacked when narrow. Fills the height it is given. */
|
|
34
|
+
export function Panes({ children }) {
|
|
35
|
+
return (_jsx("div", { className: "@container flex h-full min-h-0 min-w-0 flex-1 flex-col", children: _jsx("div", { className: "flex min-h-0 min-w-0 flex-1 flex-col divide-line @4xl:flex-row @4xl:divide-x max-@4xl:divide-y", children: children }) }));
|
|
36
|
+
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
1
|
/* EL MARCO DE LA APLICACIÓN — lo que está arriba de toda vista. */
|
|
2
2
|
export { AppFrame } from "./AppFrame.js";
|
|
3
3
|
export { SystrayButton, TopBar, } from "./TopBar.js";
|
|
4
|
+
/* A screen of N sets side by side — the reconciliation: each pane scrolls on its own. */
|
|
5
|
+
export { Pane, Panes } from "./Panes.js";
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+
/** El grano de un nivel de fecha. Sin grano, un campo de fecha parte por
|
|
2
|
+
* instante y cada fila termina en su propio montón, que es no agrupar. */
|
|
3
|
+
export type GroupGrain = "day" | "week" | "month" | "quarter" | "year";
|
|
2
4
|
/**
|
|
3
5
|
* Una fecha recortada a la CLAVE de su grano.
|
|
4
6
|
*
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
|
+
/** What changes, at any depth. A word is replaced; a section is merged. */
|
|
3
|
+
export type CopyOverride<C> = {
|
|
4
|
+
[K in keyof C]?: C[K] extends string ? string : C[K] extends readonly unknown[] ? C[K] : C[K] extends object ? CopyOverride<C[K]> : C[K];
|
|
5
|
+
};
|
|
6
|
+
/** `base` with `over` on top: sections merge, everything else is replaced. */
|
|
7
|
+
export declare function mergeCopyOver<C extends object>(base: C, over: CopyOverride<C> | undefined): C;
|
|
8
|
+
export type CopyCatalog<C extends object> = {
|
|
9
|
+
/** The factory words: what is there before anything is installed. */
|
|
10
|
+
english: C;
|
|
11
|
+
/** Sets the session's words — English with `over` on top — and returns them. */
|
|
12
|
+
install: (over?: CopyOverride<C>) => C;
|
|
13
|
+
/** The installed words: for what is not a component. */
|
|
14
|
+
current: () => C;
|
|
15
|
+
/** English with `over` on top, without installing it. */
|
|
16
|
+
merge: (over?: CopyOverride<C>) => C;
|
|
17
|
+
/** Changes the words for its branch, on top of what encloses it. */
|
|
18
|
+
Provider: (props: {
|
|
19
|
+
copy?: CopyOverride<C>;
|
|
20
|
+
children: ReactNode;
|
|
21
|
+
}) => ReactNode;
|
|
22
|
+
/** The words of this branch: the nearest `Provider`'s, or the installed ones. */
|
|
23
|
+
useCopy: () => C;
|
|
24
|
+
};
|
|
25
|
+
export declare function createCopy<C extends object>(english: C): CopyCatalog<C>;
|
|
26
|
+
/** `{name}` → value. A missing key stays visible in the template. */
|
|
27
|
+
export declare function fillCopy(template: string, params: Record<string, string | number>): string;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { createContext, useContext, useMemo } from "react";
|
|
4
|
+
const isSection = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
5
|
+
/** `base` with `over` on top: sections merge, everything else is replaced. */
|
|
6
|
+
export function mergeCopyOver(base, over) {
|
|
7
|
+
if (over === undefined)
|
|
8
|
+
return base;
|
|
9
|
+
const out = { ...base };
|
|
10
|
+
for (const [key, value] of Object.entries(over)) {
|
|
11
|
+
if (value === undefined)
|
|
12
|
+
continue;
|
|
13
|
+
const under = out[key];
|
|
14
|
+
out[key] = isSection(under) && isSection(value) ? mergeCopyOver(under, value) : value;
|
|
15
|
+
}
|
|
16
|
+
return out;
|
|
17
|
+
}
|
|
18
|
+
export function createCopy(english) {
|
|
19
|
+
let installed = english;
|
|
20
|
+
/* `null` and not English: a context keeps the value it was created with, so with the
|
|
21
|
+
factory words inside it a component without a provider would ignore `install`. */
|
|
22
|
+
const Context = createContext(null);
|
|
23
|
+
const current = () => installed;
|
|
24
|
+
const useCopy = () => useContext(Context) ?? installed;
|
|
25
|
+
function Provider({ copy, children }) {
|
|
26
|
+
const enclosing = useCopy();
|
|
27
|
+
const merged = useMemo(() => mergeCopyOver(enclosing, copy), [enclosing, copy]);
|
|
28
|
+
return _jsx(Context.Provider, { value: merged, children: children });
|
|
29
|
+
}
|
|
30
|
+
return {
|
|
31
|
+
english,
|
|
32
|
+
install: (over) => {
|
|
33
|
+
installed = mergeCopyOver(english, over);
|
|
34
|
+
return installed;
|
|
35
|
+
},
|
|
36
|
+
current,
|
|
37
|
+
merge: (over) => mergeCopyOver(english, over),
|
|
38
|
+
Provider,
|
|
39
|
+
useCopy,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
/** `{name}` → value. A missing key stays visible in the template. */
|
|
43
|
+
export function fillCopy(template, params) {
|
|
44
|
+
return template.replace(/\{(\w+)\}/g, (whole, name) => {
|
|
45
|
+
const value = params[name];
|
|
46
|
+
return value === undefined ? whole : String(value);
|
|
47
|
+
});
|
|
48
|
+
}
|
|
@@ -1,18 +1,20 @@
|
|
|
1
|
-
import { type
|
|
1
|
+
import { type CopyOverride, fillCopy } from "./catalog.js";
|
|
2
|
+
import type { ReactNode } from "react";
|
|
2
3
|
/**
|
|
3
4
|
* Chrome copy for the design system.
|
|
4
5
|
*
|
|
5
6
|
* The handbook rule: code is English; language is chosen at render. This
|
|
6
7
|
* package must not bake product Spanish into buttons. English is the default
|
|
7
|
-
* so a story still reads; the app
|
|
8
|
-
* `UiCopyProvider
|
|
8
|
+
* so a story still reads; the app installs the session's language once
|
|
9
|
+
* (`installCopy(SPANISH_COPY)`), and `UiCopyProvider` changes a branch.
|
|
9
10
|
*
|
|
10
11
|
* Domain sentences (empty catalog, upload hint) stay out — those are the
|
|
11
12
|
* product's catalogs, passed as props.
|
|
12
13
|
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
14
|
+
* The installed words are also what is not a component reads (`currentCopy`,
|
|
15
|
+
* `currentCopyLocale`): a sentence built in a function, a month named by the
|
|
16
|
+
* calendar. A provider does not change those — it is for a branch, not for the
|
|
17
|
+
* session (see `catalog.tsx`).
|
|
16
18
|
*/
|
|
17
19
|
export type UiCopy = {
|
|
18
20
|
/** BCP-47 for `toLocaleString` and `Intl`. */
|
|
@@ -68,8 +70,13 @@ export type UiCopy = {
|
|
|
68
70
|
faces: string;
|
|
69
71
|
/** The mark of a record with changes that are not saved yet. */
|
|
70
72
|
unsaved: string;
|
|
73
|
+
/** The same mark when the record says how many fields changed. */
|
|
74
|
+
unsavedOne: string;
|
|
75
|
+
unsavedMany: string;
|
|
71
76
|
save: string;
|
|
72
77
|
saving: string;
|
|
78
|
+
/** Said, briefly, once what was saved is saved. */
|
|
79
|
+
saved: string;
|
|
73
80
|
discard: string;
|
|
74
81
|
/** Read aloud next to a stage that failed: the red fill alone says nothing to a
|
|
75
82
|
* screen reader. */
|
|
@@ -113,6 +120,8 @@ export type UiCopy = {
|
|
|
113
120
|
unset: string;
|
|
114
121
|
/** A list of checks read with nothing chosen. */
|
|
115
122
|
none: string;
|
|
123
|
+
/** Next to the label of a field whose value changed and is not saved yet. */
|
|
124
|
+
changed: string;
|
|
116
125
|
};
|
|
117
126
|
/** The choice controls: combo boxes, selects, suggestion lists. */
|
|
118
127
|
choice: {
|
|
@@ -125,6 +134,8 @@ export type UiCopy = {
|
|
|
125
134
|
};
|
|
126
135
|
search: {
|
|
127
136
|
tools: string;
|
|
137
|
+
/** The name of the list of what the bar offers for what was typed. */
|
|
138
|
+
suggestions: string;
|
|
128
139
|
/** The name of the panel button once something is set: it shows the count, and what
|
|
129
140
|
* the button SHOWS has to be inside its name (WCAG 2.5.3) — `{label}` is `tools`. */
|
|
130
141
|
toolsSetOne: string;
|
|
@@ -226,6 +237,8 @@ export type UiCopy = {
|
|
|
226
237
|
min: string;
|
|
227
238
|
max: string;
|
|
228
239
|
distinct: string;
|
|
240
|
+
/** Under a pile that holds its first page and counts them all: ask for the rest. */
|
|
241
|
+
pileMore: string;
|
|
229
242
|
};
|
|
230
243
|
menu: {
|
|
231
244
|
empty: string;
|
|
@@ -255,6 +268,8 @@ export type UiCopy = {
|
|
|
255
268
|
* record itself. */
|
|
256
269
|
peek: string;
|
|
257
270
|
open: string;
|
|
271
|
+
/** Asked again while the rows stay: the name of the discreet bar over them. */
|
|
272
|
+
refreshing: string;
|
|
258
273
|
};
|
|
259
274
|
/** La historia de un registro: eventos y mensajes. */
|
|
260
275
|
/** The panel of everything around a record: its notices, its conversation and its
|
|
@@ -365,6 +380,8 @@ export type UiCopy = {
|
|
|
365
380
|
weekOfYear: string;
|
|
366
381
|
quarter: string;
|
|
367
382
|
quarterOfYear: string;
|
|
383
|
+
/** The calendar's «go to today» button: its word and its name are the same. */
|
|
384
|
+
today: string;
|
|
368
385
|
};
|
|
369
386
|
/**
|
|
370
387
|
* CÓMO SE LLAMA UN OPERADOR para quien lo lee.
|
|
@@ -503,6 +520,9 @@ export type UiCopy = {
|
|
|
503
520
|
pilePageNote: string;
|
|
504
521
|
limitPiles: string;
|
|
505
522
|
/** El árbol de condiciones. */
|
|
523
|
+
/** The names of a rule's two choices — the field it asks about, and how it compares. */
|
|
524
|
+
ruleField: string;
|
|
525
|
+
ruleOperator: string;
|
|
506
526
|
noValue: string;
|
|
507
527
|
from: string;
|
|
508
528
|
to: string;
|
|
@@ -741,63 +761,21 @@ export type UiCopy = {
|
|
|
741
761
|
};
|
|
742
762
|
};
|
|
743
763
|
export declare const ENGLISH_COPY: UiCopy;
|
|
744
|
-
/**
|
|
745
|
-
export
|
|
764
|
+
/** What changes of web-ui's words, at any depth: `{ search: { joins: { any: "o" } } }`. */
|
|
765
|
+
export type UiCopyOverride = CopyOverride<UiCopy>;
|
|
766
|
+
/** The installed words: for formatters and sentences that are not a component. */
|
|
767
|
+
export declare const currentCopy: () => UiCopy;
|
|
746
768
|
/** BCP-47 the formatters read when they are not a component. */
|
|
747
769
|
export declare function currentCopyLocale(): string;
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
shell?: Partial<UiCopy["shell"]>;
|
|
756
|
-
field?: Partial<UiCopy["field"]>;
|
|
757
|
-
choice?: Partial<UiCopy["choice"]>;
|
|
758
|
-
search?: Partial<UiCopy["search"]>;
|
|
759
|
-
failed?: Partial<UiCopy["failed"]>;
|
|
760
|
-
table?: Partial<UiCopy["table"]>;
|
|
761
|
-
menu?: Partial<UiCopy["menu"]>;
|
|
762
|
-
list?: Partial<UiCopy["list"]>;
|
|
763
|
-
grouping?: Partial<UiCopy["grouping"]>;
|
|
764
|
-
dates?: Partial<UiCopy["dates"]>;
|
|
765
|
-
calendar?: Partial<UiCopy["calendar"]>;
|
|
766
|
-
ops?: Record<string, string>;
|
|
767
|
-
sorting?: Partial<UiCopy["sorting"]>;
|
|
768
|
-
copyable?: Partial<UiCopy["copyable"]>;
|
|
769
|
-
activity?: Partial<UiCopy["activity"]>;
|
|
770
|
-
editable?: Partial<UiCopy["editable"]>;
|
|
771
|
-
picker?: Partial<UiCopy["picker"]>;
|
|
772
|
-
cell?: Partial<UiCopy["cell"]>;
|
|
773
|
-
time?: Partial<UiCopy["time"]>;
|
|
774
|
-
dialog?: Partial<UiCopy["dialog"]>;
|
|
775
|
-
report?: Partial<UiCopy["report"]>;
|
|
776
|
-
builder?: Partial<UiCopy["builder"]>;
|
|
777
|
-
pivot?: Partial<UiCopy["pivot"]>;
|
|
778
|
-
person?: Partial<UiCopy["person"]>;
|
|
779
|
-
lines?: Partial<UiCopy["lines"]>;
|
|
780
|
-
lineItems?: Partial<UiCopy["lineItems"]>;
|
|
781
|
-
priority?: Partial<UiCopy["priority"]>;
|
|
782
|
-
board?: Partial<UiCopy["board"]>;
|
|
783
|
-
kanbanState?: Partial<UiCopy["kanbanState"]>;
|
|
784
|
-
timeline?: Partial<UiCopy["timeline"]>;
|
|
785
|
-
activityState?: Partial<UiCopy["activityState"]>;
|
|
786
|
-
relation?: Partial<UiCopy["relation"]>;
|
|
787
|
-
relationCard?: Partial<UiCopy["relationCard"]>;
|
|
788
|
-
totals?: Partial<UiCopy["totals"]>;
|
|
789
|
-
inlineEdit?: Partial<UiCopy["inlineEdit"]>;
|
|
790
|
-
theme?: Partial<UiCopy["theme"]>;
|
|
791
|
-
tree?: Partial<UiCopy["tree"]>;
|
|
792
|
-
assist?: Partial<UiCopy["assist"]>;
|
|
793
|
-
};
|
|
794
|
-
export declare function mergeCopy(over?: UiCopyOverride): UiCopy;
|
|
795
|
-
/** For tests and non-React callers that need a locale before a provider mounts. */
|
|
796
|
-
export declare function installCopy(over?: UiCopyOverride): UiCopy;
|
|
797
|
-
export declare function UiCopyProvider({ copy, children, }: {
|
|
770
|
+
/** English with `over` on top, without installing it. */
|
|
771
|
+
export declare const mergeCopy: (over?: UiCopyOverride) => UiCopy;
|
|
772
|
+
/** Sets the session's words: once, before the first draw — and the one global there is. */
|
|
773
|
+
export declare const installCopy: (over?: UiCopyOverride) => UiCopy;
|
|
774
|
+
/** Changes the words for its branch, on top of what encloses it. It never writes the
|
|
775
|
+
* global: a corner in another language does not change the rest of the screen. */
|
|
776
|
+
export declare const UiCopyProvider: (props: {
|
|
798
777
|
copy?: UiCopyOverride;
|
|
799
778
|
children: ReactNode;
|
|
800
|
-
})
|
|
801
|
-
export declare
|
|
802
|
-
|
|
803
|
-
export declare function fillCopy(template: string, params: Record<string, string | number>): string;
|
|
779
|
+
}) => ReactNode;
|
|
780
|
+
export declare const useUiCopy: () => UiCopy;
|
|
781
|
+
export { fillCopy };
|
|
@@ -72,8 +72,11 @@ export const SPANISH_COPY = {
|
|
|
72
72
|
status: "Estado",
|
|
73
73
|
faces: "Caras",
|
|
74
74
|
unsaved: "sin guardar",
|
|
75
|
+
unsavedOne: "1 cambio sin guardar",
|
|
76
|
+
unsavedMany: "{count} cambios sin guardar",
|
|
75
77
|
save: "Guardar",
|
|
76
78
|
saving: "Guardando…",
|
|
79
|
+
saved: "Guardado",
|
|
77
80
|
discard: "Descartar",
|
|
78
81
|
failed: "falló",
|
|
79
82
|
notices: "Avisos sobre este registro",
|
|
@@ -106,6 +109,7 @@ export const SPANISH_COPY = {
|
|
|
106
109
|
help: "Ayuda",
|
|
107
110
|
unset: "sin definir",
|
|
108
111
|
none: "Ninguna",
|
|
112
|
+
changed: "modificado",
|
|
109
113
|
},
|
|
110
114
|
choice: {
|
|
111
115
|
noResults: "Sin resultados",
|
|
@@ -127,6 +131,7 @@ export const SPANISH_COPY = {
|
|
|
127
131
|
},
|
|
128
132
|
search: {
|
|
129
133
|
tools: "Filtrar, agrupar y favoritos",
|
|
134
|
+
suggestions: "Sugerencias",
|
|
130
135
|
toolsSetOne: "{label}, {count} puesto",
|
|
131
136
|
toolsSetMany: "{label}, {count} puestos",
|
|
132
137
|
editChip: "Clic para editar",
|
|
@@ -199,6 +204,7 @@ export const SPANISH_COPY = {
|
|
|
199
204
|
min: "mín {value}",
|
|
200
205
|
max: "máx {value}",
|
|
201
206
|
distinct: "{value} distintos",
|
|
207
|
+
pileMore: "Ver {count} más",
|
|
202
208
|
},
|
|
203
209
|
menu: { empty: "Nada coincide." },
|
|
204
210
|
list: {
|
|
@@ -208,6 +214,7 @@ export const SPANISH_COPY = {
|
|
|
208
214
|
count: "{count} registros",
|
|
209
215
|
peek: "Vista previa",
|
|
210
216
|
open: "Abrir",
|
|
217
|
+
refreshing: "Actualizando la lista…",
|
|
211
218
|
},
|
|
212
219
|
activity: {
|
|
213
220
|
bySystem: "el sistema",
|
|
@@ -264,6 +271,7 @@ export const SPANISH_COPY = {
|
|
|
264
271
|
weekOfYear: "Semana {n} de {year}",
|
|
265
272
|
quarter: "T{n}",
|
|
266
273
|
quarterOfYear: "T{n} de {year}",
|
|
274
|
+
today: "Hoy",
|
|
267
275
|
},
|
|
268
276
|
grouping: {
|
|
269
277
|
label: "Agrupar por",
|
|
@@ -346,6 +354,8 @@ export const SPANISH_COPY = {
|
|
|
346
354
|
pilesNote: "Sobre lo que plegaron —los totales de arriba, y «count»— no sobre las filas: eso es el filtro de más arriba.",
|
|
347
355
|
pilePageNote: "Sólo el primer nivel: pedida una página, los niveles más profundos sólo contestan por los montones que sobrevivieron.",
|
|
348
356
|
limitPiles: "Limitar cuántos montones vuelven",
|
|
357
|
+
ruleField: "Campo",
|
|
358
|
+
ruleOperator: "Comparación",
|
|
349
359
|
noValue: "sin valor",
|
|
350
360
|
from: "desde…",
|
|
351
361
|
to: "hasta…",
|