efficient-shell 6.4.0 → 7.10.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/README.md +88 -22
- package/dist/Browser-VCRHLL3W.js +43 -0
- package/dist/Calculator-HFF54RNH.js +29 -0
- package/dist/ConfirmDialog-6LAVAGDE.js +1 -0
- package/dist/CurrencyConverter-SAIDT4WN.js +21 -0
- package/dist/{Documents-ADFEHEUW.js → Documents-OWPQE6D6.js} +27 -27
- package/dist/Files-UCNAQWGM.js +87 -0
- package/dist/Notepad-NNYMEU7M.js +25 -0
- package/dist/PdfViewer-PHABUM7K.js +1 -0
- package/dist/PomodoroTimer-AV3YE2WJ.js +91 -0
- package/dist/Preview-PWFREL75.js +182 -0
- package/dist/Sidebar-3IB3YIWC.js +44 -0
- package/dist/Spreadsheet-F5BXANOO.js +37 -0
- package/dist/Stock-EP2SB76N.js +16 -0
- package/dist/{Weather-BLO354GM.js → Weather-62FA6T6O.js} +35 -35
- package/dist/WorldClock-C3A6PZYM.js +32 -0
- package/dist/apps/index.js +1 -1
- package/dist/bin/ui-check.js +1 -1
- package/dist/brand.css +1 -1
- package/dist/{chunk-YX2IMURA.js → chunk-7BX3JQBP.js} +10 -10
- package/dist/{chunk-MORDXCV6.js → chunk-7HLTPMQV.js} +1 -1
- package/dist/{chunk-4JZZLUGG.js → chunk-B4PGVDB6.js} +1 -1
- package/dist/chunk-B4XGJA3N.js +1 -0
- package/dist/{chunk-7PGHRG6J.js → chunk-BQ7GR7H6.js} +11 -11
- package/dist/{chunk-R5VRLW5Z.js → chunk-BXW5WAEX.js} +1 -1
- package/dist/{chunk-SK5DL6JX.js → chunk-FTJ2G343.js} +14 -14
- package/dist/{chunk-XO2AMGVU.js → chunk-GGSG7HSD.js} +1 -1
- package/dist/{chunk-ZEIKSMFR.js → chunk-ICN533ML.js} +8 -8
- package/dist/{chunk-I534M65N.js → chunk-ITCBLL72.js} +2 -2
- package/dist/{chunk-GWDVD3VQ.js → chunk-JQRIAZDP.js} +23 -23
- package/dist/{chunk-WOEFPBQF.js → chunk-KBOYLXAT.js} +1 -1
- package/dist/chunk-KSLOSQKN.js +29 -0
- package/dist/{chunk-UUYPUKP7.js → chunk-M4TRVJTO.js} +1 -1
- package/dist/{chunk-CXJQ42XT.js → chunk-MFDZYZZX.js} +15 -15
- package/dist/chunk-NGUH7T5V.js +1 -0
- package/dist/chunk-QRCDQPJA.js +1 -0
- package/dist/{chunk-5K76RQML.js → chunk-RBKIYZ7H.js} +1 -1
- package/dist/{chunk-GJ37JCC2.js → chunk-RUHUTZN4.js} +1 -1
- package/dist/{chunk-I2H2CKID.js → chunk-SCJT2XXU.js} +1 -1
- package/dist/{chunk-QJHWLDEN.js → chunk-THM35LAN.js} +71 -71
- package/dist/{chunk-5LUN632W.js → chunk-UJL5J7GX.js} +4 -4
- package/dist/chunk-UZJ6RYMF.js +9 -0
- package/dist/chunk-W6BQVATM.js +1016 -0
- package/dist/{chunk-VR6TR575.js → chunk-XN7CAWSZ.js} +3 -3
- package/dist/{chunk-IHZL3GJU.js → chunk-Y56GRZ2Q.js} +1 -1
- package/dist/chunk-YMWCZKUN.js +12 -0
- package/dist/{chunk-Q5O5UMJY.js → chunk-Z2TTHY7G.js} +2 -2
- package/dist/{chunk-2SSRFEOZ.js → chunk-ZGUQLVY4.js} +5 -3
- package/dist/{chunk-HQLAISFX.js → chunk-ZRU5F2XT.js} +4 -4
- package/dist/file-intake/index.js +1 -1
- package/dist/i18n/index.d.ts +1 -1
- package/dist/icons/efficient-looks.js +20 -10
- package/dist/icons/efficient.d.ts +7 -5
- package/dist/icons/efficient.js +51 -48
- package/dist/icons/index.d.ts +1 -1
- package/dist/icons/index.js +1 -1
- package/dist/{index-D5bms_uA.d.ts → index-Cevszdp8.d.ts} +296 -90
- package/dist/index.d.ts +3 -3
- package/dist/index.js +632 -639
- package/dist/locales/zh-Hans.d.ts +1 -1
- package/dist/locales/zh-Hans.js +1 -1
- package/dist/map/index.js +10 -10
- package/dist/markdown/index.js +4 -4
- package/dist/page-tabs/index.js +5 -5
- package/dist/pdf/index.js +1 -1
- package/dist/{strings-fYzxZPn4.d.ts → strings-BQtq9300.d.ts} +17 -2
- package/dist/telemetry/index.js +1 -1
- package/dist/ui/index.d.ts +2 -2
- package/dist/ui/index.js +1 -1
- package/dist/ui.css +50 -54
- package/package.json +1 -1
- package/dist/Browser-USPZWDYH.js +0 -43
- package/dist/Calculator-FP5HUWCW.js +0 -29
- package/dist/ConfirmDialog-3TIHKJRT.js +0 -1
- package/dist/CurrencyConverter-XD5LYOCD.js +0 -21
- package/dist/Files-BVUVRNC5.js +0 -87
- package/dist/Notepad-WJI5ODGS.js +0 -25
- package/dist/PdfViewer-226I3LSD.js +0 -1
- package/dist/PomodoroTimer-GEMQHIPJ.js +0 -91
- package/dist/Preview-HNSEYNZL.js +0 -182
- package/dist/Sidebar-PVHZPNIY.js +0 -44
- package/dist/Spreadsheet-6Z5ACGTT.js +0 -37
- package/dist/Stock-X3LIU4ZX.js +0 -16
- package/dist/WorldClock-BAH7QFRG.js +0 -32
- package/dist/chunk-5MDQNFOG.js +0 -9
- package/dist/chunk-DBXZS7OA.js +0 -29
- package/dist/chunk-K6DKX55N.js +0 -1
- package/dist/chunk-PFKMIPZH.js +0 -1028
- package/dist/chunk-S3LGKIZ3.js +0 -12
- package/dist/chunk-SBF5A6XV.js +0 -1
package/README.md
CHANGED
|
@@ -16,7 +16,7 @@ A backend-less playground hosted on GitHub Pages. Wallpapers, themes, sticky not
|
|
|
16
16
|
|
|
17
17
|
**Shell:** `<Layout>`, `<StartMenu>`, `<Desktop>` (with sticky notes + folders), `<WindowManager>`, `<Modal>` (standard / compact / widget styles), `<PopupMenu>`, `<ConfirmDialog>`, `<GlobalSearch>` (Cmd-K), `<ShortcutHelp>`, `<NotificationBell>`, `<StatusBadge>`, `<SearchableSelect>`, frosted-glass theming.
|
|
18
18
|
|
|
19
|
-
**UI primitives:** `<Button>`, `<Input>`, `<Textarea>`, `<Select>`, `<Checkbox>`, `<Radio>` / `<RadioGroup>`, `<FormField>`, `<Label>`, `<Snippet>`, `<Kbd>`, `<Card>` / `<StatCard>`, `<Avatar>` / `<AvatarGroup>`, `<Banner>`, `<Tabs>`, `<Accordion>`, `<Tooltip>`, `<Pagination>`, `<MetricBar>`, `<BudgetBar>`, `<SettingRow>`, and dependency-free `<Sparkline>`
|
|
19
|
+
**UI primitives:** `<Button>`, `<Input>`, `<Textarea>`, `<Select>`, `<Checkbox>`, `<Radio>` / `<RadioGroup>`, `<FormField>`, `<Label>`, `<Snippet>`, `<Kbd>`, `<Card>` / `<StatCard>`, `<Avatar>` / `<AvatarGroup>`, `<Banner>`, `<Tabs>`, `<Accordion>`, `<Tooltip>`, `<Pagination>`, `<MetricBar>`, `<BudgetBar>`, `<SettingRow>`, and the dependency-free chart family (`<Sparkline>` for a tile, and `<ColumnChart>`, `<TimeSeriesChart>`, `<PieChart>` and the rest for charts that are read).
|
|
20
20
|
|
|
21
21
|
**Page templates:** ready-made screens composed from the primitives — `<DashboardTemplate>`, `<DataTablePage>`, `<FormLayoutPage>`, `<CheckoutTemplate>`, `<EmailTemplate>`, `<ChatTemplate>`, `<GalleryTemplate>`, `<AuthScreen>`, `<ErrorPage>`. `<ErrorBoundary>` catches a render crash and shows the 500 page in place of a blank screen.
|
|
22
22
|
|
|
@@ -943,9 +943,10 @@ and `defaultWidth` are then the pane's height. The default is
|
|
|
943
943
|
```
|
|
944
944
|
|
|
945
945
|
**`WidthsBar`** sets how a row's columns share its width: a bar split into
|
|
946
|
-
the columns, a handle between each pair
|
|
947
|
-
|
|
948
|
-
widths" field (efficient-admin-portal), which is
|
|
946
|
+
the columns, with a handle between each pair. Each segment of the bar is
|
|
947
|
+
where its % is typed; there are no number boxes under it. It came from the
|
|
948
|
+
campaign designer's "Column widths" field (efficient-admin-portal), which is
|
|
949
|
+
deleted once this ships.
|
|
949
950
|
|
|
950
951
|
```tsx
|
|
951
952
|
<InputNumber aria-label="Columns" value={count} onChange={n => n && setCount(n)} min={1} max={4} />
|
|
@@ -954,8 +955,41 @@ widths" field (efficient-admin-portal), which is deleted once this ships.
|
|
|
954
955
|
|
|
955
956
|
The value is `ColumnShares`: the first `count - 1` shares, whole percentages.
|
|
956
957
|
The last column is what the others leave, so the widths always add up. No
|
|
957
|
-
column goes under `COLUMN_SHARE_MIN` (10%)
|
|
958
|
-
|
|
958
|
+
column goes under `COLUMN_SHARE_MIN` (10%).
|
|
959
|
+
|
|
960
|
+
Each segment's "34%" is a field that looks like a label until it is focused.
|
|
961
|
+
Focused, it shows a focus ring and the bare number, selected. Enter or leaving
|
|
962
|
+
the field commits; Escape puts back the value from when the field was
|
|
963
|
+
entered. ArrowUp/ArrowDown step 1, Shift 10. Every column can be typed in, the
|
|
964
|
+
last one too. A typed column takes its room from the next column (the last
|
|
965
|
+
column from the one before it); when that one is at the floor, the room comes
|
|
966
|
+
from the column after it, then from the other side. A focused field in a
|
|
967
|
+
narrow segment grows to at least 40px, over the handles and inside the bar.
|
|
968
|
+
A segment narrower than 36px shows "10" rather than "10%". Each field is a
|
|
969
|
+
`spinbutton` named "Column 1", "Column 2", … (`strings.widths.column`), valued
|
|
970
|
+
in percent and described by the hint. Tab goes column 1, the handle after it,
|
|
971
|
+
column 2, …, the last column, then "Reset to equal".
|
|
972
|
+
|
|
973
|
+
A handle trades width with its neighbour and stops at the floor. Its line is
|
|
974
|
+
2px at rest and 4px on hover, keyboard focus and while dragged; the grab area
|
|
975
|
+
is 12px either way. Under 264px of its own width (a container query, not the
|
|
976
|
+
window), the bar puts "Reset to equal" under the hint, at the right end.
|
|
977
|
+
|
|
978
|
+
A host that draws its own reset and hint passes `footer={false}`: for
|
|
979
|
+
example a settings field with a reset in its heading row (shown only when
|
|
980
|
+
the value has changed) and a hint that folds away. The bar then draws
|
|
981
|
+
nothing under itself, not even a gap. The hint is still there for screen
|
|
982
|
+
readers, as visually hidden text that the group and every field are
|
|
983
|
+
described by. The host's reset calls `onChange(undefined)`, as the bar's own
|
|
984
|
+
does.
|
|
985
|
+
|
|
986
|
+
```tsx
|
|
987
|
+
<WidthsBar label="Widths" count={count} value={row.columnShares} onChange={save} footer={false} />
|
|
988
|
+
```
|
|
989
|
+
|
|
990
|
+
While a field in a narrow segment is focused and grown, a neighbour's label
|
|
991
|
+
that it covers even in part is hidden whole, so only whole numbers are ever
|
|
992
|
+
seen.
|
|
959
993
|
A row that chose nothing shows equal widths and stores nothing; "Reset to
|
|
960
994
|
equal" calls `onChange(undefined)` and never stores 34/33, because no value
|
|
961
995
|
renders as the exact `33.34% / 33.33% / 33.33%` strings saved rows carry.
|
|
@@ -977,13 +1011,33 @@ equal strings and the equal panel figures.
|
|
|
977
1011
|
each face in itself. The `label` still names, filters and fills the field.
|
|
978
1012
|
`Select` takes the same, and `renderValue(chosen)` draws its closed field.
|
|
979
1013
|
|
|
1014
|
+
**A server search that is running, or has failed.** A `SearchableSelect` fed
|
|
1015
|
+
by `onSearchChange` can say both. `loading` puts "Searching..." on a line
|
|
1016
|
+
above the options, in place of "No matches". `error` puts the caller's words
|
|
1017
|
+
there instead ("Couldn't search your catalog."), and `onRetry` adds a "Try
|
|
1018
|
+
again" button to that line; its press keeps focus in the field and the list
|
|
1019
|
+
open. Options still listed (the last answer, or a search that half failed)
|
|
1020
|
+
stay under the line and stay pickable. `loading` wins over `error`. Without
|
|
1021
|
+
them the list is what it was.
|
|
1022
|
+
|
|
1023
|
+
**A typed value only when it is picked.** `SearchableSelect`'s
|
|
1024
|
+
`allowFreeText` saves the typed text on Enter, Tab, blur and a press outside.
|
|
1025
|
+
For a field where typing is mostly searching — a PO number, a shipment —
|
|
1026
|
+
`` freeTextOption={text => `Use “${text}” as manual PO#`} `` instead draws the
|
|
1027
|
+
typed text as one more row at the end of the list, in those words, and saves
|
|
1028
|
+
it only when that row is pressed or reached with the arrow keys and taken with
|
|
1029
|
+
Enter. Every other way out drops it. The row is not drawn while nothing is
|
|
1030
|
+
typed or while the text is exactly an option's label. It turns free text on by
|
|
1031
|
+
itself, and with `multiple` it adds the text and keeps the list open.
|
|
1032
|
+
|
|
980
1033
|
**Gaps the editors found in the form kit** (efficient-easel#199, harness UI-24),
|
|
981
1034
|
now in the kit itself:
|
|
982
1035
|
|
|
983
|
-
- **`Segmented`**: the chosen
|
|
984
|
-
|
|
985
|
-
is chosen (WCAG 1.4.11).
|
|
986
|
-
|
|
1036
|
+
- **`Segmented`**: the chosen option is the solid blue chip pill `Tabs` draw
|
|
1037
|
+
(owner, 2026-10-12), 4.8:1 against the light track and 3.1:1 against the
|
|
1038
|
+
dark one, so a strip of icons shows which option is chosen (WCAG 1.4.11).
|
|
1039
|
+
An option's `title` is shown in the kit's `Tooltip`, not the native
|
|
1040
|
+
attribute. A radio option also gets it as its description.
|
|
987
1041
|
- **`Input`, `Textarea`**: `readOnly` draws the read-only ground (`bg-gray-50`,
|
|
988
1042
|
`--surface-sunken` in dark). The border and focus ring do not change.
|
|
989
1043
|
`inputClasses({ readOnly: true })` gives the same look to anything else.
|
|
@@ -1072,6 +1126,9 @@ Round 4:
|
|
|
1072
1126
|
- **`Tabs`**: a `TabItem` takes `tooltip`, a shell `Tooltip` that opens on
|
|
1073
1127
|
hover and on focus, so the arrow keys show each tab's word in turn. Keep the
|
|
1074
1128
|
word in `label` (visually hidden) for an icon-only tab.
|
|
1129
|
+
`variant="pill"` is the same strip `Segmented` draws: the active tab is solid
|
|
1130
|
+
blue with white text on the grey track, 32px tall. The tablist roles, the
|
|
1131
|
+
arrow keys and `idPrefix` stay the tabs' own.
|
|
1075
1132
|
- **`registerModalEscapeInterceptor`** is on `efficient-shell/ui`. A window or
|
|
1076
1133
|
panel a host draws itself registers there, with the element it closes, so
|
|
1077
1134
|
Escape reaches it before the window around it and a `Tooltip` outside it
|
|
@@ -1121,25 +1178,25 @@ All exports are named — `import { Modal, ... } from 'efficient-shell'`.
|
|
|
1121
1178
|
| `ProductionTimeline` | The scrubbable production bar, drawn on `TimelineTrack` inside the shared timeline card: a sentence-case heading with the PO as its subject, the window and the lead time as meta, a Play **pill** on the right whose glyph follows its state, and a footer carrying the status line and legend chips drawn with the track's own glyphs — a shipment is a diamond with a truck in it and an inspection a disc with a flask, on the rail and in the chip alike, because every kind is painted in the one accent and the glyph is the only thing left to tell them apart. The thumb only ever rests on a date a report was filed on — drag it and it snaps report to report, arrow it and it steps one report at a time — so the bar never shows an estimate dressed as a fact. Press Play and it **travels** between them: a fresh press rewinds to the first report, the thumb reads each snapshot for 700 ms and then glides to the next with the date chip counting the days it crosses, and the status line under the bar changes on arrival rather than while it is on its way. Pausing freezes it mid-glide and pressing Play again carries on from exactly there; touching the thumb ends the playback and hands it back. The walk keeps time by the clock rather than by the frame rate, so an idle or throttled pane does not stretch it. `onScrubProgress` forwards the track's in-flight fraction — `{ fromKey, toKey, t, ms }` keyed by report id, `null` at a stop — which is how a consumer's items table moves WITH the thumb instead of jumping a whole report's worth of quantities when it lands; it is optional and undefined by default, so a caller that only reads the snapshot needs no change. `onPickReport` selects a report; the optional `onOpenReport` / `onOpenMarker` make the label above the thumb a real button that opens the document behind it. `reportLabel` names a report wherever the bar states its identity — the caption over the thumb, the popover header, each dot's accessible name, the slider's value text and the status line — and defaults to its own `progress_number`, while `resetLabel` replaces the whole of the `Back to …` button: together they are what lets a portal that must not print an internal document number run the same card. A report carries an optional `preview` for its hover/focus popover — the stage row it filed, the note attached to it — and `renderReportPreview` builds one for the reports that carry none, the convenience for a caller whose list arrives straight off an API; the report's own preview wins where both exist, and the popover prints the report number and the date itself. `useProductionTimeline(opts)` owns the slider state and the derived snapshot so the caller's items table and the bar read the same state; `STAGES` / `calcOverall` / `calcReportOverall` are the stage maths it shares with that table. **`variant="rail"`** is the bar alone, for a window whose own header already says what the order is: no card, no heading, no lead-time line, no label under the active report — every report, shipment, inspection and **invoice** (kind `invoice`, a disc with a receipt) is a dot with a popover — the thumb with its date chip, `Start` and `Est. done` flanking the rail, the estimated completion as a hollow mark on the right edge while it is still ahead, and under the rail one row: Play, the return button when the thumb has been moved, and the `<report> · date · N% overall · N pc in stock` line. The rail spreads the reports (the track's `spread` axis) rather than folding two a week apart into a `×2`. `onPlayStart` fires before the thumb moves, on a fresh run and on a resume, so a window that keeps the bar in view while its table shows something else can switch the table first. |
|
|
1122
1179
|
| `MilestoneTimeline` | The static sibling, on the same `TimelineTrack` and in the same card: one dot per milestone on a date axis, no scrubber. `heading` and `subject` split the title from the record it is about (`Mould development` · **001F/1813**), and the meta line states when it started, how long it has run and how many milestones are dated. The axis **cuts** rather than squeezes: a stretch holding more than 30% of the window's time becomes a fixed notch with a break glyph saying how many days it hides, and every stretch that still keeps time gets at least 48 px. Labels are packed into **two lanes** by their **measured** width, and a run of same-kind revisions folds into one `DFM ×4` pill whose members keep their dots on the rail: hovering it magnifies that stretch until each member can carry its own label, a click pins the popover listing them, Escape closes it. A milestone carries an optional `glyph` (a `default` one that names `doc` is drawn as the signed drawing it is), a `preview` for the popover, and an `onOpen` that the popover's "Open" button fires — `onClick` selects the dot on the bar, `onOpen` opens the drawing behind it, and a card that declares only the first keeps its Open button because the footer falls back to it. Label and date are printed by the popover above the preview, so a consumer supplies neither. A milestone with **no date gets no position**, because the only position available would be a date the record does not have: it is listed beside the bar under "Not yet reached" (two rows, then a real `+N more` button) and takes no part in the range, the fill or a phase bracket. `phase` groups of 2+ get a bracket marking parallel work, the fill never runs past today, and today gets its dashed rule and tag even when it IS the right edge — the commonest case, because a programme with no finish date ends at today. **`variant="rail"`** is the bar alone, for a window whose stepper already names the stages: no card, no heading, no label under any dot — every milestone is a dot with a popover, a revision carries a two-character `caption` over its dot (`v3`; captions that would overprint collapse to `v1–v4`), `edgeCaptions` flank the rail on its own row (`{ start: 'Start', end: 'Ready' }`), nothing is cut (the `spread` axis: every stretch at least 48 px, the idle ones drawn short rather than notched), and what has not happened is one dashed **cap** at the end of a dashed tail — a real button announced as the last undated milestone, naming the rest in its popover. The stepper and the rail then point at each other: `highlightKeys` lights one soft band from the leftmost named milestone to the rightmost and every dot inside it (a single key rings one dot; the cap answers to the pending milestone's key), and `onHoverChange` reports the milestone under the pointer or the focus, `null` on leave. A `provisional` milestone — a sample ORDERED, drawn where the shipment will be — is hollow, never "where we are", and outside the fill. |
|
|
1123
1180
|
| `SettingRow` | One setting in a settings card: the name with its explanation stacked under it on the left, the control or value on the right (it never shrinks), and an optional body (`children`) that opens beneath behind its own line. Each row draws its own top line (`divider`) and padding, so rows stack in a card. One prop per slot: `label` (the name) and `value` (a control or a read-only fact). `size="compact"` is the editor-panel row. Handles a value that is a control (`controlId` labels it), a read-only fact, a value nobody knows (an em dash in the faint ink, never an empty cell), and a `quiet` row nothing can write yet. A name over a field (children, no value) draws nothing on the right. `DescriptionList` is the record-detail neighbour and stays that. |
|
|
1124
|
-
| `Button`, `Input`, `Textarea`, `Select`, `Checkbox`, `Radio`, `Switch`, `FormField`, `Label` | Form controls — controlled (`value`/`onChange`); `Button`'s `variant` is `primary` (the default), `secondary` (outlined), `secondary-danger` (outlined in red: a destructive action in a footer of outlined buttons, e.g. Delete beside Archive — the same size and shape as `secondary`, red border and text, a light red wash on hover), `ghost`, `ghost-danger` (red text, no box: a destructive action among quiet `ghost` buttons), `danger` (solid red: the one action a confirm dialog asks for) or `link`; `Input`/`Textarea` forward native props for react-hook-form. `Button` and `Switch` take `disabledReason`: persistent text beside the control wired with `aria-describedby`, never a `title` — a tooltip needs a hover a disabled control does not reliably get. `Switch` also takes `disabledReasonId`, so a panel whose controls are all dead for one reason states it once and points every control at it. A `Select` option carries an optional `description`, rendered as a second muted line **in the open list only** — the closed trigger stays one line, so one explained option cannot change the height of every field in the row. `NativeSelect` (and so `Select` on touch) ignores it: an `<option>` holds text and nothing else. An option also takes `lang` (a BCP 47 tag) when its label is written in another language than the page's — a language picker naming each language in its own words: it goes on the option in the open list, on the native `<option>` and on the closed trigger's label while chosen, so a screen reader and the font fallback treat each name as its own language (WCAG 3.1.2). On the desktop listbox, `title` joins `aria-label` / `aria-labelledby` / `aria-describedby` on the VISIBLE trigger button — it used to ride the native spread onto the `sr-only` `<select>`, where a tooltip has no pointer that can reach it and a fallback name sits on an `aria-hidden` element. `name`, `required` and the forwarded ref stay on the native control, which is what it is for. A `Checkbox` or `Radio` with both a `label` and a `description` is NAMED by the label and DESCRIBED by the description (`aria-labelledby` / `aria-describedby`); the whole row is still its `<label>`, so its words still tick it. `FormField` points its one child at the hint or error on screen with `aria-describedby`, and marks it `aria-invalid` while `error` is set. A child that already has its own `aria-describedby` (a react-hook-form binding often does) keeps it: the ids are merged, the child's own first, and each id is said once — a repeated id is a message a screen reader reads twice. |
|
|
1181
|
+
| `Button`, `Input`, `Textarea`, `Select`, `Checkbox`, `Radio`, `Switch`, `FormField`, `Label` | Form controls — controlled (`value`/`onChange`); `Button`'s `variant` is `primary` (the default), `secondary` (outlined), `secondary-danger` (outlined in red: a destructive action in a footer of outlined buttons, e.g. Delete beside Archive — the same size and shape as `secondary`, red border and text, a light red wash on hover), `ghost`, `ghost-danger` (red text, no box: a destructive action among quiet `ghost` buttons), `danger` (solid red: the one action a confirm dialog asks for) or `link`; `Button` takes `pressed` (or follows `aria-pressed`) for a toggle that is on, with `IconButton`'s accent wash and ink on `ghost` and `secondary` (`secondary` keeps its border, tinted, so it does not move; `buttonClasses({ pressed })` gives a link the same); `Input`/`Textarea` forward native props for react-hook-form. `Button` and `Switch` take `disabledReason`: persistent text beside the control wired with `aria-describedby`, never a `title` — a tooltip needs a hover a disabled control does not reliably get. `Switch` also takes `disabledReasonId`, so a panel whose controls are all dead for one reason states it once and points every control at it. A `Select` option carries an optional `description`, rendered as a second muted line **in the open list only** — the closed trigger stays one line, so one explained option cannot change the height of every field in the row. `NativeSelect` (and so `Select` on touch) ignores it: an `<option>` holds text and nothing else. An option also takes `lang` (a BCP 47 tag) when its label is written in another language than the page's — a language picker naming each language in its own words: it goes on the option in the open list, on the native `<option>` and on the closed trigger's label while chosen, so a screen reader and the font fallback treat each name as its own language (WCAG 3.1.2). On the desktop listbox, `title` joins `aria-label` / `aria-labelledby` / `aria-describedby` on the VISIBLE trigger button — it used to ride the native spread onto the `sr-only` `<select>`, where a tooltip has no pointer that can reach it and a fallback name sits on an `aria-hidden` element. `name`, `required` and the forwarded ref stay on the native control, which is what it is for. A `Checkbox` or `Radio` with both a `label` and a `description` is NAMED by the label and DESCRIBED by the description (`aria-labelledby` / `aria-describedby`); the whole row is still its `<label>`, so its words still tick it. `FormField` points its one child at the hint or error on screen with `aria-describedby`, and marks it `aria-invalid` while `error` is set. A child that already has its own `aria-describedby` (a react-hook-form binding often does) keeps it: the ids are merged, the child's own first, and each id is said once — a repeated id is a message a screen reader reads twice. |
|
|
1125
1182
|
| `buttonClasses`, `iconButtonClasses` | `Button`'s and `IconButton`'s look as a class string, for something that must navigate and so cannot be a `<button>`: `<Link href="/organisations/new" className={buttonClasses({ variant: 'secondary' })}>Add a business</Link>`. Same options and defaults as the component (`variant`, `size`, `block`, `className`), and the components render through these functions, so the two cannot drift. What the string cannot give an `<a>`: a disabled state (`disabled:` never matches an anchor — drop the `href`, add `role="link"` and `aria-disabled="true"`, or render a disabled `Button`), and the cursor (an `<a href>` gets the browser's pointer, a `<button>` the host's preflight; set it once for both). Keep its `inline-flex` — ask for `block` rather than passing a display utility. An icon-only link still needs the `aria-label` `IconButton` would have required. Client references, like every kit function: call them in a `'use client'` file. |
|
|
1126
1183
|
| `labelClasses` | `Label`'s look as a class string, for a `<label>` a host lays out itself — beside a check box, in a row with its control, `sr-only`: `<label htmlFor="x" className={labelClasses({ inline: true, className: 'flex gap-2' })}>`. `inline: true` drops `Label`'s block layout (`mb-1 block`) and keeps its size, weight and ink; without it the string is exactly what `Label` renders. A client reference: call it in a `'use client'` file. |
|
|
1127
|
-
| `Segmented` | A row of mutually exclusive options, all visible: a button group, or with `name` a real radio group that submits. Named by `label`, or by an element already on the page (`aria-labelledby`, a list row's own heading) and described by another (`aria-describedby`); an option's `title` says on hover what choosing it means. |
|
|
1184
|
+
| `Segmented` | A row of mutually exclusive options, all visible: a button group, or with `name` a real radio group that submits. It looks exactly like `Tabs variant="pill"`: the chosen option is solid blue with white text on the grey track, the others grey text. Both draw through one shared strip, so they cannot drift apart; only the roles differ (a value here, a tab panel there). `size` is `sm` / `md` / `lg` (24 / 32 / 40, the control-height ladder) or `touch` (56, as `Button size="touch"`). Icon-only: give the option its `icon` and its word as a visually hidden label (`sr-only`); the word names it and the icon is hidden from screen readers. Named by `label`, or by an element already on the page (`aria-labelledby`, a list row's own heading) and described by another (`aria-describedby`); an option's `title` says on hover what choosing it means. |
|
|
1128
1185
|
| `DisplayChips` | Which parts of a section are shown, one chip per part ("Breadcrumb", "Stats", "Intro text"): blue with an eye while shown, grey with a crossed eye while hidden, and a press flips it. The chips wrap, so a long list takes a few short lines instead of one switch row per part. Controlled: `items` (`{ id, label, shown, disabled?, disabledReason? }`) and `onChange(id, shown)`. The group is named by `label` or `aria-labelledby`. Each chip is a real toggle button (`aria-pressed`), named by its part and described by its state in words (`displayChips.shown` / `hidden`), with a hover label that says what a press does; Space and Enter are the button's own. A chip with `disabledReason` stays focusable, its press does nothing and the reason is said, as on `Button`. Not `Segmented` (one choice of several) and not `TagInput` (typing values in). |
|
|
1129
1186
|
| `RadioGroup` | One choice from a short visible list, with the group chrome each panel used to rebuild: a `role="radiogroup"` named by its own label (`aria-labelledby`, because `<label for>` cannot name a div), the `hint`/`error`/`required` wiring borrowed from `FormField`, and `orientation` for a row that wraps rather than compresses. The options are real native radios sharing one `name`, which is what gives the group its arrow keys, its disabled-skipping and its single tab stop — all from the user agent, none of it hand-rolled. `value: null` is "nothing chosen yet", not a silent first option. |
|
|
1130
1187
|
| `Snippet` | A value the user copies rather than reads — an endpoint, a tenant id, a shell command. Monospace, truncated with the whole value on its `title` (`multiline` wraps instead), and a copy button that flips to a check for two seconds and says "Copied" through an `aria-live` region. `value` is what reaches the clipboard and `children` is what is shown, so a masked key displays masked and copies whole. `symbol` prints a leading glyph that is never copied and has **no default** — a URL has no `$` in front of it. A refused clipboard toasts rather than looking like it worked. Not a read-only `Input`: a value that can only be copied is not a control and should not take a tab stop or invite an edit. `id` and `aria-describedby` land on the COPY BUTTON — the only focusable thing in the row, and a labelable one — so a wrapping `FormField` really does label it and its hint is really announced; with `hideCopyButton` they fall back to the box, because an id that resolves to nothing is not wiring. |
|
|
1131
1188
|
| `Kbd` | The badge the shortcut constants (`CMD_ENTER`, `ALT_SHIFT_E`, …) are shown in — a real `<kbd>`, two rungs, `className` layered on so a portal can tint it. `tone` says what it sits on: `neutral` (default, grey), `accent` (inside a primary or submit button: the `kbd-submit` chip, with no colour utility of its own) or `warning` (an amber button). It renders the string it is handed; which symbol a platform uses is what the constants already decide. |
|
|
1132
1189
|
| `FormErrorSummary` | The error list at the top of a failed form (WCAG 3.3.1, the GOV.UK pattern): takes focus when errors appear, each message is a link that focuses the offending control by its `FormField` id. `fieldId` is optional: an error with no control behind it — a server's `_general` / `non_field_errors`, or a field the form does not draw — is listed as plain text, not a link that goes nowhere. Renders nothing while `errors` is empty. |
|
|
1133
|
-
| `TagInput` | Multi-value field — chosen values as removable chips, typing filters the option list in the same dropdown SearchableSelect uses. `allowFreeText` admits unlisted entries; Backspace in the empty input removes the last chip. The value array stays duplicate-free by construction. |
|
|
1190
|
+
| `TagInput` | Multi-value field — chosen values as removable chips, typing filters the option list in the same dropdown SearchableSelect uses. `allowFreeText` admits unlisted entries; Backspace in the empty input removes the last chip. The value array stays duplicate-free by construction. Opt-in, per chip: `tone(value)` gives a chip `'danger'` (the status red, for a value that is chosen but wrong here) or the default `'accent'`, and `tooltip(value)` gives it the kit's `Tooltip`, shown on hover and when its × has focus. An option's `sublabel` is drawn greyed beside its label in the list (a procedure's name beside its code), and typing it finds the option. `split` (a RegExp, such as `/[\s,/]+/`) takes typed or pasted "AA, AC" as two chips: each finished piece is a chip at once, a piece that names an option (label or value, any case) takes it, and any other piece needs `allowFreeText`. |
|
|
1134
1191
|
| `DatePicker`, `TimePicker`, `DateTimePicker`, `DateRangePicker` | Date/time fields, all careful to speak LOCAL dates and wall-clock times (never `toISOString`). `TimePicker` and `DateTimePicker` wrap the platform's own inputs in the kit's field styling; `TimePicker` hands back an `HH:MM` string — a time of day names no calendar day, so it never invents a Date. **`DatePicker`** opens the kit's `Calendar` in a popover (`native` swaps in the platform's date input). Its value posts from a hidden input that takes `name` and `form`; `required` adds "Required" to the trigger's description (`datePicker.required`; a button may not carry `aria-required`) and drops Clear from the footer (`clearable` overrides either way); `invalid` paints the field, and the reader hears the error message the host links through `aria-describedby` (`FormField` does) — the trigger carries no `aria-invalid`, which a button may not either; Today is offered only when `min`/`max` allow today; `aria-*`, `data-*`, `title`, `tabIndex`, `autoFocus`, `onFocus` and `onBlur` reach the trigger, while what only a text box can use (`readOnly`, `pattern`, `autoComplete`…) applies with `native` alone. Named by a `<label htmlFor={id}>` or `aria-labelledby`, the trigger is described by the date spelled out. A browser never validates a hidden input, so `required` cannot block a submit in the popover. **`DateRangePicker`** is a from/to calendar with presets in one popover: side by side on a desktop; where that does not fit the room the box allows (a phone, a narrow shell window — measured, not a media query), the panel takes the full width between the margins and stacks, the presets a wrapping row of chips above the calendar and From/To one line each. `fullWidth` fills a filter-grid cell instead of shrink-wrapping its label. `id` (on the trigger button, for `<label htmlFor>`), `aria-labelledby` and `aria-label` name the trigger, and the chosen range is then its description ("Period, button, 1 August 2026 to 3 October 2026"). `name={{ from, to }}` posts the applied range as two hidden `YYYY-MM-DD` inputs (`''` when unset), so a GET form or a server action reads it as it read two date inputs. `required` adds "Required" to the trigger's description (`dateRange.required`) and, unless `clearable` is given, removes Clear. `min` / `max` (`YYYY-MM-DD`) bound it as they bound `DatePicker`: days outside are disabled, a preset whose range reaches outside is not offered (none at all on a range that starts today — time off, a booking — and then no Custom either), and with nothing chosen the panel opens on the first month that has a day to pick. `disabled` makes the whole control inert: a disabled trigger with the kit's disabled look, no panel, no Clear ×, and the hidden inputs disabled so the range is not posted (an open panel closes). `allowOpenEnd` lets Apply commit ONE end on its own — "from 1 August", "up to 30 June" — and turns the panel's From and To boxes into buttons that choose which end the next day sets (the start first, as before); the open end comes back as `''`. Without it Apply needs both ends, as it always has. Both pickers take `weekStartsOn` (0 Sunday, 1 Monday); without it the week starts where the provider's `locale` says. |
|
|
1135
1192
|
| `Card`, `StatCard` | Surface panel (optional header/footer; `description` puts one sentence under the title, outside the heading; `headingLevel` makes the title a heading and the card a region named by it, and an `aria-label` names the region instead when the title is not the name, such as a count; `tone="warning"` draws the border amber for a card that needs attention, and the card's words still say why; `tone="current"` marks the current card in a list with a light-blue header band, a blue title and `aria-current`) + dashboard metric tile. |
|
|
1136
1193
|
| `Avatar`, `AvatarGroup` | User avatar with initials fallback + status dot; overlapping stack with +N overflow. |
|
|
1137
1194
|
| `Banner` | Static in-flow alert (`tone`: info / success / warning / danger / neutral). `neutral` is a note that is none of the other four ("Prices exclude GST"): grey in the kit's neutral, the same wash and ink as a neutral `StatusBadge` (`--status-neutral-soft` / `--status-neutral-soft-ink`, `--status-neutral-solid` when `emphasis="solid"`), with the info glyph. `action` renders a control at the right edge — after the text, so a screen reader reaches the problem before the remedy, and before the dismiss ×, so "Update card" is never past "close this". Vertically centred against the words and `shrink-0`, so a message that wraps does not drag the button down or squeeze it — and on a narrow banner (a phone, a narrow column: the words keep at least 12rem) it drops UNDER the words rather than leave them a column one word wide. A banner that names a condition and leaves the fix three menus away is the usual shape of a notice nobody acts on. |
|
|
1138
|
-
| `Tabs`, `Accordion`, `Tooltip` | Controlled tab strip, collapsible sections, solid hover/focus tooltip. A tooltip's `side` is a preference: near an edge the bubble slides along that side to stay 8 px clear of it, and flips to the opposite side when its own has no room, keeping inside the screen and inside the shell window that owns its trigger (UI-11). It never leaves that box: a bubble wider than the room wraps, and one taller than the room is cut off at it. The bubble is drawn on `<body>`, in a `fixed` layer laid over its trigger, so a scrolling pane or an `overflow-hidden` box around the trigger cannot cut it off; it follows the trigger when that moves and closes once the trigger has scrolled out of sight. A small arrow on the edge facing the trigger points at the trigger's centre wherever the bubble ended up, kept clear of the bubble's corners; it is the bubble's own surface (`.rosh-tooltip-arrow` in `ui.css`), seamless, and hidden from assistive technology. The bubble is SOLID, not glass, in every setting: the kit's solid popup surface (`--rosh-glass-surface`, `--rosh-glass-solid-border`, `--rosh-glass-solid-shadow`), so words behind a small bubble never mix with its own. |
|
|
1195
|
+
| `Tabs`, `Accordion`, `Tooltip` | Controlled tab strip, collapsible sections, solid hover/focus tooltip. `Tabs variant="pill"` draws the same strip as `Segmented` (the active tab solid blue on the grey track); use it when each choice shows its own panel, and `Segmented` when the choice is a value. `block` gives every tab an equal share of the strip's width, in either look (the same word as `Segmented`'s `block`): the editors' settings panel uses it for its two icon-only tabs, Design (`adjust`) and Animation (`animation`), with the `underline` look, so the chosen tab is a blue icon over a blue underline. A tooltip's `side` is a preference: near an edge the bubble slides along that side to stay 8 px clear of it, and flips to the opposite side when its own has no room, keeping inside the screen and inside the shell window that owns its trigger (UI-11). It never leaves that box: a bubble wider than the room wraps, and one taller than the room is cut off at it. The bubble is drawn on `<body>`, in a `fixed` layer laid over its trigger, so a scrolling pane or an `overflow-hidden` box around the trigger cannot cut it off; it follows the trigger when that moves and closes once the trigger has scrolled out of sight. A small arrow on the edge facing the trigger points at the trigger's centre wherever the bubble ended up, kept clear of the bubble's corners; it is the bubble's own surface (`.rosh-tooltip-arrow` in `ui.css`), seamless, and hidden from assistive technology. The bubble is SOLID, not glass, in every setting: the kit's solid popup surface (`--rosh-glass-surface`, `--rosh-glass-solid-border`, `--rosh-glass-solid-shadow`), so words behind a small bubble never mix with its own. |
|
|
1139
1196
|
| `Stepper` | A linear wizard's progress strip — numbered circles, connectors, `aria-current="step"`. Controlled like Tabs; completed steps are clickable to go back (when `onChange` is wired), upcoming steps never are — moving forward belongs to the wizard's own Continue button, behind its validation. |
|
|
1140
1197
|
| `DataTable` | Read-only table for server-driven lists: it renders and reports, and never sorts or pages its own `data`. Controlled sort and pager, row selection, column groups, pinned columns, windowed rendering. `onRow` gives a row `onClick`, a `className`, any `data-*` attributes for its `<tr>` — a test's or a stylesheet's hook (`data-testid`, `data-state`) — the pointer and the keyboard coming in and out (`onMouseEnter` … `onBlur`) and `current`. `rowHref` makes each row one real link (`onRowPress` a button) with its controls still pressable — see [DataTable rows that are links](#datatable-rows-that-are-links). `stackOnNarrow` turns each row into a labelled card on a narrow container — see [DataTable on a narrow screen](#datatable-on-a-narrow-screen). |
|
|
1141
1198
|
| `Pagination` | Numbered page control (pairs with tables; complements `ListFooter`). |
|
|
1142
|
-
| `Sparkline`, `
|
|
1199
|
+
| `Sparkline`, `ScatterChart` | Dependency-free inline-SVG charts. `Sparkline` is the decorative trend for a tile (`currentColor`-themed). For bars use `ColumnChart`, for a trend over time `TimeSeriesChart`, for a donut `PieChart variant="ring"`. `ScatterChart` takes `xDomain` / `yDomain` to override the derived axis, and `xScale` / `yScale` of `'log'` for a long tail; a point outside a supplied domain is dropped and counted in the accessible label. |
|
|
1143
1200
|
| `BulkImportGrid` | Paste-or-upload bulk entry with column mapping, duplicate review and optional sum-merge. Hands resolved rows to `onImport`; owns no persistence. The whole panel takes a dropped CSV; the Upload CSV button opens the same dialog. Its words are the strings catalog's `bulkImport` section (a `description` prop wins), and its totals are written in the provider's `locale`. |
|
|
1144
1201
|
| `UndoProvider`, `UndoControls` | One undo stack per form window, covering its fields, line items and bulk imports. Register state with `useUndoable` and the shell shows the Undo/Redo pair itself, leftmost in the window footer, as soon as the form has state and the window is editing (a draft, a duplicate, Edit mode) or the form has said `useUndoCanEdit(true)` — a view that merely holds state gets nothing — it joins a footer that is there for other reasons and never creates one, and the mobile chrome (footer hidden) gets nothing. A form mounts `UndoControls` by hand only for a place the footer is not (a plain overlay, a section header); a hand mount claims the stack and the shell then mounts nothing for it, so a form shows one pair wherever it chose to put it. Binds ⌘Z / ⇧⌘Z (and Ctrl+Y) except while the caret is in a field, where the browser's own undo wins. `WindowManager` already mounts one per window, scoped with `windowId` so a keypress reaches only the frontmost window; pass `windowId` yourself for any provider you mount outside a `<Modal>`, or two open windows will step back together. Offered to anyone who may edit the record — gate with `canEdit` and/or `perms`; a reader sees no controls and records no history. The shell-level provider cannot know the record's permissions, so a read-only form nests its own `<UndoProvider canEdit={false}>` to shadow it. A provider nested inside a window puts its own pair in the footer on the same footer terms — it joins a footer that is there for other reasons, never creates one or holds it open, and shows nothing on the mobile chrome — but its `canEdit` takes the place of the editing check: the form that nested it has already said whether the record may be edited, so pass `canEdit={isEditing}` (or the lock) rather than leaving the default. |
|
|
1145
1202
|
| `Calendar` | Month grid with full keyboard navigation (arrows, Home/End, PageUp/Down) and `role="grid"` semantics. Single or range. Days of the months either side are quieter than the month's own but keep AA contrast: they are dates a person can press. The shared grid behind `DatePicker` and `DateRangePicker`. `weekStartsOn` defaults to the provider locale's first day of the week (`firstDayOfWeek`), Sunday without one. |
|
|
@@ -1545,7 +1602,7 @@ families the kit uses: **gray** (nearly all chrome: surfaces, borders, text),
|
|
|
1545
1602
|
**red** (danger and invalid), **green** (success), **amber** and **yellow**
|
|
1546
1603
|
(warning), one **emerald** and one **neutral** (a template's sparkline,
|
|
1547
1604
|
BrandMark's dark plate), plus `white` and `black`. Status badges and charts read
|
|
1548
|
-
their own tokens (`--status-*`, `--viz
|
|
1605
|
+
their own tokens (`--status-*`, `--viz-*`) rather than the palette.
|
|
1549
1606
|
|
|
1550
1607
|
```css
|
|
1551
1608
|
@theme {
|
|
@@ -1770,12 +1827,11 @@ grid (`--viz-grid`) is `--line-strong`, the same as the axis.
|
|
|
1770
1827
|
replaced: `--rosh-media-scrim` and `--rosh-media-scrim-button` (the dark wash
|
|
1771
1828
|
under text and buttons on an upload thumbnail), `--rosh-drop-ring` (the halo
|
|
1772
1829
|
while a file is dragged over a gallery), `--rosh-mark-plate-shadow` and
|
|
1773
|
-
`--rosh-mark-plate-shadow-strong` (`BrandMark`'s plates),
|
|
1774
|
-
(`SidebarLayout`'s handle without a custom accent)
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
(`--tl-*`) are documented where `ui.css` declares them.
|
|
1830
|
+
`--rosh-mark-plate-shadow-strong` (`BrandMark`'s plates), and
|
|
1831
|
+
`--rosh-resize-handle` (`SidebarLayout`'s handle without a custom accent).
|
|
1832
|
+
Every chart paints with one palette, `--viz-series-*`. The chart palette
|
|
1833
|
+
(`--viz-*`), the status tiers (`--status-*`) and the timeline (`--tl-*`) are
|
|
1834
|
+
documented where `ui.css` declares them.
|
|
1779
1835
|
|
|
1780
1836
|
### Languages, dates and numbers — `ShellStringsProvider`
|
|
1781
1837
|
|
|
@@ -3734,6 +3790,14 @@ look="pencil-square" />`). The looks are a subpath of their own, so a host that
|
|
|
3734
3790
|
draws only the default does not bundle them. A meaning without the look asked
|
|
3735
3791
|
for draws its default.
|
|
3736
3792
|
|
|
3793
|
+
**A part a host can fill: the `swatch`.** In every look of `text-color`, the
|
|
3794
|
+
letter A itself is the swatch: one closed path (the letter and its counter)
|
|
3795
|
+
marked `data-part="swatch"`. A host paints the chosen colour into it with CSS
|
|
3796
|
+
(`.my-color-button [data-part="swatch"] { fill: var(--chosen) }`). The outline
|
|
3797
|
+
look keeps its ink line round the fill, so a light colour still shows on a
|
|
3798
|
+
light toolbar; with no such rule it draws a hollow A, so "no colour" is told
|
|
3799
|
+
apart from black.
|
|
3800
|
+
|
|
3737
3801
|
| Import | Set | Draws | Needs |
|
|
3738
3802
|
|---|---|---|---|
|
|
3739
3803
|
| `efficient-shell/icons/efficient` | `efficient` | every meaning, default look | nothing |
|
|
@@ -3855,6 +3919,8 @@ itself where the kit has an answer:
|
|
|
3855
3919
|
| `icon` | an import of an icon package (`@heroicons`, `lucide-react`, `@tabler/icons`, …) | `<Icon name>` from `efficient-shell/icons` |
|
|
3856
3920
|
| `svg` | an `<svg>` element written in JSX: an icon drawn in place (one per element, however many shapes it holds; a component named `Svg` and the word in a string are not counted) | `<Icon name>` from `efficient-shell/icons`. A chart, an illustration or a mark a host supplies stays on the baseline |
|
|
3857
3921
|
| `kit-dep` | one of those packages declared in `package.json` (not `devDependencies`, and not one the shell needs from an entry the repository imports: `@headlessui/react` and `@heroicons/react` for the root entry, an icon set's own package) | remove it |
|
|
3922
|
+
| `chart` | an import of a second chart library: `recharts`, `chart.js`, `react-chartjs-2`, `d3` and every `d3-*` module (one imported only for its maths too), `@nivo/*`, `victory` and `victory-*`, `echarts`, `echarts-for-react`, `apexcharts`, `react-apexcharts`, `@visx/*`, `highcharts`, `highcharts-react-official`, `plotly.js`, `plotly.js-dist`, `react-plotly.js`, `lightweight-charts`, `@antv/g2`, `@antv/g2plot`, `chartist`, `billboard.js`, `c3`, `uplot`. A name counts with its subpaths (`chart.js/auto`); a name that only contains one (`react-d3-tree`) does not. Read after `kit` and `icon`, so `@mui/x-charts` stays a `kit` | a chart from `efficient-shell/ui`: `ChartFrame`, `TimeSeriesChart`, `ColumnChart`, `RankedBars`, `PieChart`, `FunnelChart`, `ScatterChart`, …; the one chart system (harness FT-3) |
|
|
3923
|
+
| `chart-dep` | one of those chart libraries declared in `package.json`, whether or not anything imports it (not `devDependencies`) | remove it |
|
|
3858
3924
|
|
|
3859
3925
|
Known gaps: `rgb()` / `hsl()` colours, colours in `.ts` maps, and
|
|
3860
3926
|
`createElement('button')` are not counted.
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import{peekBrowserStartUrl as e,claimBrowserStartUrl as t}from"./chunk-LWLCNQY7.js";import{AboutApp as o}from"./chunk-UZJ6RYMF.js";import{WindowTitle as r}from"./chunk-THM35LAN.js";import"./chunk-DSSLOQDW.js";import"./chunk-SCJT2XXU.js";import"./chunk-ZRU5F2XT.js";import"./chunk-5A7MWTXX.js";import"./chunk-YMWCZKUN.js";import{confirm as a}from"./chunk-KSLOSQKN.js";import"./chunk-ICN533ML.js";import"./chunk-UVBK6YSR.js";import"./chunk-NGUH7T5V.js";import"./chunk-7IQ3B77H.js";import"./chunk-FIEA5CGB.js";import"./chunk-D65T4H6J.js";import"./chunk-B4XGJA3N.js";import"./chunk-KNUR2UEG.js";import"./chunk-QRCDQPJA.js";import"./chunk-C3QOHMR6.js";import{Icon as n}from"./chunk-ZGUQLVY4.js";import{useRef as s,useEffect as l,useState as i}from"react";import{jsxs as c,jsx as m,Fragment as d}from"react/jsx-runtime";var u="react-os-shell:browser-bookmarks",h="react-os-shell:browser-homepage",p="https://en.wikipedia.org/wiki/Main_Page",g=[{label:"Wikipedia",url:"https://en.wikipedia.org/wiki/Main_Page"},{label:"DevDocs",url:"https://devdocs.io"},{label:"Example",url:"https://example.com"}],b=new Set(["ac ad ae af ag ai al am ao aq ar as at au aw ax az ba bb bd be bf bg bh bi","bj bm bn bo br bs bt bw by bz ca cc cd cf cg ch ci ck cl cm cn co cr cu cv","cw cx cy cz de dj dk dm do dz ec ee eg er es et eu fi fj fk fm fo fr ga gd","ge gf gg gh gi gl gm gn gp gq gr gs gt gu gw gy hk hm hn hr ht hu id ie il","im in io iq ir is it je jm jo jp ke kg kh ki km kn kp kr kw ky kz la lb lc","li lk lr ls lt lu lv ly ma mc md me mg mh mk ml mm mn mo mp mq mr ms mt mu","mv mw mx my mz na nc ne nf ng ni nl no np nr nu nz om pa pe pf pg ph pk pl","pm pn pr ps pt pw py qa re ro rs ru rw sa sb sc sd se sg sh si sk sl sm sn","so sr ss st su sv sx sy sz tc td tf tg th tj tk tl tm tn to tr tt tv tw tz","ua ug uk us uy uz va vc ve vg vi vn vu wf ws ye yt za zm zw","com net org edu gov mil int info biz name pro app dev xyz online site tech","store shop cloud design digital agency studio media news blog live life","world today email systems solutions services software page link space host","press wiki team group works careers finance capital energy academy"].join(" ").split(" "));function f(e){let t=e.trim();return t?/^[a-z][a-z0-9+.-]*:\/\//i.test(t)?t:function(e){const t=/^([\w-]+\.)+([a-z]{2,})(\/.*)?$/i.exec(e);return null!==t&&b.has(t[2].toLowerCase())}(t)?"https://"+t:"https://duckduckgo.com/?q="+encodeURIComponent(t):""}var k=["duckduckgo.com","developer.mozilla.org","google.com","gmail.com","youtube.com","facebook.com","instagram.com","whatsapp.com","twitter.com","x.com","github.com","gitlab.com","linkedin.com","reddit.com","pinterest.com","amazon.com","amazon.ca","amazon.co.uk","apple.com","icloud.com","microsoft.com","outlook.com","live.com","office.com","netflix.com","spotify.com","paypal.com","stripe.com","chat.openai.com","chatgpt.com","claude.ai"];function x(){if("undefined"==typeof window)return g;try{const e=localStorage.getItem(u);if(!e)return g;const t=JSON.parse(e);if(Array.isArray(t)&&t.every(e=>"string"==typeof e?.url))return t}catch{}return g}function w(){return"undefined"==typeof window?p:localStorage.getItem(h)||p}function y(){const d=s(void 0);void 0===d.current&&(d.current=e()),l(()=>{null!=d.current&&t(d.current)},[]);const p=d.current?f(d.current.url):"",[g,b]=i(w),[k,y]=i(p||g),[j,C]=i(k),[z,S]=i([k]),[R,W]=i(0),[L,U]=i(0),[E,H]=i(x),[T,D]=i(!1),[M,I]=i(null),O=s(null),A=s(null);l(()=>{try{localStorage.setItem(u,JSON.stringify(E))}catch{}},[E]);const G=e=>{const t=f(e);t&&(y(t),C(t),S(e=>{const o=e.slice(0,R+1);return o.push(t),o}),W(e=>e+1),D(!1))},K=()=>{"undefined"!=typeof window&&window.open(k,"_blank","noopener,noreferrer")},Q=()=>{const e=M?.trim();e?(H(t=>[...t,{label:e,url:k}]),I(null)):I(null)};l(()=>{if(null===M)return;O.current?.focus(),O.current?.select();const e=e=>{const t=e.target;t?.closest("[data-bookmark-popover]")||I(null)},t=setTimeout(()=>document.addEventListener("mousedown",e),0);return()=>{clearTimeout(t),document.removeEventListener("mousedown",e)}},[null!==M]);const X=e=>{H(t=>t.filter((t,o)=>o!==e))},B=R>0,F=R<z.length-1,J=E.some(e=>e.url===k),q="p-2 rounded hover:bg-gray-200 transition-colors text-gray-600 disabled:opacity-30";/* @__PURE__ */
|
|
2
|
+
return c("div",{className:"relative flex flex-col h-full bg-white",children:[
|
|
3
|
+
/* @__PURE__ */m(r,{title:`Browser - ${v(k)}`}),
|
|
4
|
+
/* @__PURE__ */m(o,{app:"browser"}),
|
|
5
|
+
/* @__PURE__ */c("div",{className:"flex items-center gap-1 px-2 py-2 border-b border-gray-200 bg-gray-50 shrink-0",children:[
|
|
6
|
+
/* @__PURE__ */m("button",{onClick:()=>{if(R>0){const e=R-1;W(e),y(z[e]),C(z[e]),D(!1)}},disabled:!B,className:q,title:"Back",children:/* @__PURE__ */m(n,{name:"chevron-left",className:"h-4 w-4",strokeWidth:2})}),
|
|
7
|
+
/* @__PURE__ */m("button",{onClick:()=>{if(R<z.length-1){const e=R+1;W(e),y(z[e]),C(z[e]),D(!1)}},disabled:!F,className:q,title:"Forward",children:/* @__PURE__ */m(n,{name:"chevron-right",className:"h-4 w-4",strokeWidth:2})}),
|
|
8
|
+
/* @__PURE__ */m("button",{onClick:()=>U(e=>e+1),className:q,title:"Refresh",children:/* @__PURE__ */m(n,{name:"refresh",className:"h-4 w-4",strokeWidth:1.8})}),
|
|
9
|
+
/* @__PURE__ */m("button",{onClick:()=>G(g),className:q,title:"Home",children:/* @__PURE__ */m(n,{name:"home",className:"h-4 w-4",strokeWidth:1.8})}),
|
|
10
|
+
/* @__PURE__ */m("form",{onSubmit:e=>{e.preventDefault(),G(j)},className:"flex-1 flex items-center mx-1",children:/* @__PURE__ */c("div",{className:"flex items-center w-full bg-white border border-gray-300 rounded focus-within:border-blue-400 focus-within:ring-1 focus-within:ring-blue-200",children:[
|
|
11
|
+
/* @__PURE__ */m("span",{className:"px-2 text-gray-400 text-xs",children:k.startsWith("https://")?"🔒":"⚠️"}),
|
|
12
|
+
/* @__PURE__ */m("input",{type:"text",value:j,onChange:e=>C(e.target.value),onFocus:e=>e.target.select(),className:"flex-1 px-1 py-1 text-sm bg-transparent outline-none font-mono",spellCheck:!1,placeholder:"Enter URL or search…"}),k!==j&&/* @__PURE__ */m("button",{type:"button",onClick:()=>C(k),className:"px-2 text-2xs text-gray-400 hover:text-gray-700",title:"Reset to current URL",children:"×"})]})}),
|
|
13
|
+
/* @__PURE__ */m("button",{"data-bookmark-popover":!0,onClick:()=>J?X(E.findIndex(e=>e.url===k)):void I(v(k)),className:q+" "+(J?"text-yellow-500":""),title:J?"Remove bookmark":"Add bookmark",children:/* @__PURE__ */m(n,{name:"star",className:"h-4 w-4",fill:J?"currentColor":"none",strokeWidth:1.5})}),
|
|
14
|
+
/* @__PURE__ */m("button",{onClick:K,className:q,title:"Open in new tab",children:/* @__PURE__ */m(n,{name:"external",className:"h-4 w-4",strokeWidth:1.5})}),
|
|
15
|
+
/* @__PURE__ */m("button",{onClick:()=>D(e=>!e),className:q,title:"Embedding help",children:/* @__PURE__ */m(n,{name:"help",className:"h-4 w-4",strokeWidth:1.5})})]}),null!==M&&/* @__PURE__ */c("div",{"data-bookmark-popover":!0,className:"absolute right-3 top-12 z-30 w-72 bg-white border border-gray-200 rounded-md shadow-lg p-3",children:[
|
|
16
|
+
/* @__PURE__ */m("div",{className:"text-2xs font-semibold uppercase tracking-wide text-gray-500 mb-1",children:"Bookmark this page"}),
|
|
17
|
+
/* @__PURE__ */m("div",{className:"text-2xs text-gray-400 truncate mb-2 font-mono",title:k,children:k}),
|
|
18
|
+
/* @__PURE__ */m("input",{ref:O,type:"text",value:M,onChange:e=>I(e.target.value),onKeyDown:e=>{"Enter"===e.key?Q():"Escape"===e.key&&I(null)},placeholder:"Name",className:"w-full text-sm px-2 h-8 border border-gray-300 rounded focus:outline-none focus:border-blue-400 focus:ring-1 focus:ring-blue-200"}),
|
|
19
|
+
/* @__PURE__ */c("div",{className:"flex justify-end gap-1 mt-2",children:[
|
|
20
|
+
/* @__PURE__ */m("button",{onClick:()=>I(null),className:"px-2 py-1 text-xs text-gray-600 hover:bg-gray-100 rounded",children:"Cancel"}),
|
|
21
|
+
/* @__PURE__ */m("button",{onClick:Q,disabled:!M.trim(),className:"px-2 py-1 text-xs bg-blue-500 text-white rounded hover:bg-blue-600 disabled:opacity-40",children:"Save"})]})]}),
|
|
22
|
+
/* @__PURE__ */c("div",{className:"flex items-center gap-0.5 px-2 py-1 border-b border-gray-200 bg-white shrink-0 overflow-x-auto",children:[0===E.length&&/* @__PURE__ */m("span",{className:"text-2xs text-gray-400 italic px-2",children:"No bookmarks yet — star the address bar to add one."}),E.map((e,t)=>/* @__PURE__ */c("button",{onClick:()=>G(e.url),onContextMenu:async o=>{o.preventDefault();await a({title:"Remove bookmark",message:`"${e.label}" will be removed from your bookmarks.`,confirmLabel:"Remove",variant:"danger"})&&X(t)},className:"flex items-center gap-2 px-2 py-0.5 rounded text-xs text-gray-700 hover:bg-gray-100 whitespace-nowrap",title:`${e.url}\n(right-click to remove)`,children:[
|
|
23
|
+
/* @__PURE__ */m("img",{src:`https://icons.duckduckgo.com/ip3/${new URL(e.url).hostname}.ico`,alt:"",className:"h-3.5 w-3.5",onError:e=>{e.target.style.visibility="hidden"}}),e.label]},t)),
|
|
24
|
+
/* @__PURE__ */m("div",{className:"flex-1"}),
|
|
25
|
+
/* @__PURE__ */m("button",{onClick:()=>{b(k);try{localStorage.setItem(h,k)}catch{}},className:"px-2 py-0.5 text-2xs text-gray-500 hover:text-gray-800",title:"Set current page as homepage",children:"Set as home"})]}),
|
|
26
|
+
/* @__PURE__ */m("div",{className:"flex-1 relative min-h-0 bg-gray-50",children:/* @__PURE__ */m(N,{url:k,iframeKey:L,iframeRef:A,openExternal:K,showHelp:T,dismissHelp:()=>D(!1)})})]})}function v(e){try{return new URL(e).hostname.replace(/^www\./,"")}catch{return e}}function N({url:e,iframeKey:t,iframeRef:o,openExternal:r,showHelp:a,dismissHelp:s}){const[u,h]=i(!1);l(()=>{h(!1)},[e]);return function(e){try{const t=new URL(e).hostname.toLowerCase();return k.some(e=>t===e||t.endsWith("."+e))}catch{return!1}}(e)&&!u?/* @__PURE__ */m("div",{className:"absolute inset-0 flex items-center justify-center bg-white p-8",children:/* @__PURE__ */c("div",{className:"max-w-md text-center",children:[
|
|
27
|
+
/* @__PURE__ */m(n,{name:"no-symbol",className:"h-14 w-14 mx-auto text-gray-300 mb-3",strokeWidth:1.2}),
|
|
28
|
+
/* @__PURE__ */c("h3",{className:"text-base font-semibold text-gray-800 mb-1",children:[v(e)," can't be embedded"]}),
|
|
29
|
+
/* @__PURE__ */c("p",{className:"text-sm text-gray-500 mb-4",children:["This site sends an ",
|
|
30
|
+
/* @__PURE__ */m("span",{className:"font-mono text-xs",children:"X-Frame-Options"})," ","or ",
|
|
31
|
+
/* @__PURE__ */m("span",{className:"font-mono text-xs",children:"Content-Security-Policy"})," header that refuses iframe embedding. The browser blocks the load before our app can do anything about it."]}),
|
|
32
|
+
/* @__PURE__ */c("div",{className:"flex flex-col items-center gap-2",children:[
|
|
33
|
+
/* @__PURE__ */c("button",{onClick:r,className:"px-4 py-2 bg-blue-500 text-white text-sm rounded hover:bg-blue-600 inline-flex items-center gap-2",children:[
|
|
34
|
+
/* @__PURE__ */m(n,{name:"external",className:"h-4 w-4",strokeWidth:1.5}),"Open in a new tab"]}),
|
|
35
|
+
/* @__PURE__ */m("button",{onClick:()=>h(!0),className:"text-xs text-gray-500 hover:text-gray-800 underline",children:"Try loading it here anyway"})]})]})}):/* @__PURE__ */c(d,{children:[
|
|
36
|
+
/* @__PURE__ */m("iframe",{ref:o,src:e,className:"absolute inset-0 w-full h-full bg-white",sandbox:"allow-scripts allow-forms allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-modals",referrerPolicy:"no-referrer-when-downgrade"},t+e),a&&/* @__PURE__ */c("div",{className:"absolute top-2 right-2 max-w-sm bg-white border border-gray-200 rounded-md shadow-lg p-3 text-xs text-gray-700 z-10",children:[
|
|
37
|
+
/* @__PURE__ */m("div",{className:"font-medium text-gray-900 mb-1",children:"Why is this page blank?"}),
|
|
38
|
+
/* @__PURE__ */c("p",{className:"mb-2",children:["Most major sites (Google, GitHub, banks, news) refuse to be embedded in an iframe via ",
|
|
39
|
+
/* @__PURE__ */m("span",{className:"font-mono",children:"X-Frame-Options"})," or Content Security Policy. There's no workaround — the browser blocks the load before our app can do anything."]}),
|
|
40
|
+
/* @__PURE__ */c("p",{children:["Hit the ",
|
|
41
|
+
/* @__PURE__ */m("span",{className:"font-medium",children:"↗"})," button to open the page in a real new tab. Sites that ",
|
|
42
|
+
/* @__PURE__ */m("em",{children:"do"})," allow embedding (Wikipedia, DevDocs, your own apps) work fine in here."]}),
|
|
43
|
+
/* @__PURE__ */m("button",{onClick:s,className:"mt-2 text-2xs text-blue-600 hover:underline",children:"Got it"})]})]})}export{y as default};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import{loadAppearance as e,WidgetSettingsModal as t}from"./chunk-GGSG7HSD.js";import{useWidgetSettings as n}from"./chunk-THM35LAN.js";import"./chunk-DSSLOQDW.js";import"./chunk-SCJT2XXU.js";import"./chunk-ZRU5F2XT.js";import"./chunk-5A7MWTXX.js";import"./chunk-YMWCZKUN.js";import"./chunk-KSLOSQKN.js";import"./chunk-ICN533ML.js";import"./chunk-UVBK6YSR.js";import"./chunk-NGUH7T5V.js";import"./chunk-7IQ3B77H.js";import"./chunk-FIEA5CGB.js";import"./chunk-D65T4H6J.js";import"./chunk-KNUR2UEG.js";import"./chunk-QRCDQPJA.js";import"./chunk-C3QOHMR6.js";import{Icon as r}from"./chunk-ZGUQLVY4.js";import{useState as l,useRef as c,useCallback as i,useEffect as a}from"react";import{jsxs as o,Fragment as s,jsx as u}from"react/jsx-runtime";var d="calc_appearance";function h(){const[h,m]=l("0"),[b,k]=l(null),[p,g]=l(null),[N,y]=l(!1),[x,f]=l(""),[v,C]=l([]),[$,j]=l(!1),[S,w]=l(()=>e(d)),[E,T]=l(!1),[U,A]=l(S),L=c(null);n(i(()=>{A({...S}),T(!0)},[S]));const W=i(e=>{f(""),m(t=>N||"0"===t?(y(!1),"."===e?"0.":e):"."===e&&t.includes(".")?t:t+e)},[N]),D=i(()=>{m("0"),k(null),g(null),y(!1)},[]),F=i((e,t,n)=>{switch(n){case"+":return e+t;case"-":return e-t;case"×":return e*t;case"÷":return 0===t?0:e/t;default:return t}},[]),G=i(e=>{const t=parseFloat(h);if(null!==b&&p&&!N){const e=F(b,t,p);m(String(e)),k(e),C(n=>[`${b} ${p} ${t} = ${e}`,...n].slice(0,20))}else k(t);g(e),y(!0)},[h,b,p,N,F]),Q=i(()=>{if(null===b||!p)return;const e=parseFloat(h),t=F(b,e,p),n=`${b} ${p} ${e} =`;C(e=>[`${n} ${t}`,...e].slice(0,20)),f(n),m(String(t)),k(null),g(null),y(!0)},[h,b,p,F]),B=i(()=>{m(e=>String(parseFloat(e)/100))},[]),H=i(()=>{m(e=>e.startsWith("-")?e.slice(1):"0"===e?e:"-"+e)},[]);a(()=>{const e=e=>{const t=e.target.tagName;"INPUT"!==t&&"TEXTAREA"!==t&&"SELECT"!==t&&(e.key>="0"&&e.key<="9"?W(e.key):"."===e.key?W("."):"+"===e.key?G("+"):"-"===e.key?G("-"):"*"===e.key?G("×"):"/"===e.key?(e.preventDefault(),G("÷")):"Enter"===e.key||"="===e.key?("Enter"===e.key&&e.target instanceof Node&&L.current?.contains(e.target)&&e.preventDefault(),Q()):"Escape"===e.key?D():"Backspace"===e.key?m(e=>e.length>1?e.slice(0,-1):"0"):"%"===e.key&&B())};return window.addEventListener("keydown",e),()=>window.removeEventListener("keydown",e)},[W,G,Q,D,B]);const M="flex items-center justify-center rounded-lg text-sm font-medium transition-colors",R=`${M} bg-white border border-gray-200 text-gray-900 hover:bg-gray-50 active:bg-gray-100`,X=`${M} bg-blue-50 border border-blue-200 text-blue-700 hover:bg-blue-100 active:bg-blue-200`,I=`${M} bg-gray-100 border border-gray-200 text-gray-600 hover:bg-gray-200 active:bg-gray-300`,K=`${M} bg-blue-600 text-white hover:bg-blue-700 active:bg-blue-800`;/* @__PURE__ */
|
|
2
|
+
return o(s,{children:[
|
|
3
|
+
/* @__PURE__ */o("div",{ref:L,className:"flex flex-col h-full",style:{opacity:S.activeOpacity/100,backdropFilter:S.activeBlur>0?`blur(${S.activeBlur}px)`:void 0},children:[
|
|
4
|
+
/* @__PURE__ */o("div",{className:"bg-slate-200 rounded-t-lg px-4 py-2 group/display relative flex flex-col justify-end min-h-[120px] border-b border-slate-300",children:[
|
|
5
|
+
/* @__PURE__ */u("div",{className:"flex flex-col justify-end flex-1 overflow-hidden",children:v.slice(0,3).reverse().map((e,t)=>/* @__PURE__ */u("div",{className:"text-2xs text-slate-400 text-right font-mono truncate",children:e},t))}),(x||p&&null!==b)&&/* @__PURE__ */u("div",{className:"text-base text-slate-600 text-right font-mono",children:x||`${b} ${p} ${N?"":h}`}),
|
|
6
|
+
/* @__PURE__ */o("div",{className:"flex items-center gap-2",children:[
|
|
7
|
+
/* @__PURE__ */u("button",{onClick:()=>{navigator.clipboard.writeText(h),j(!0),setTimeout(()=>j(!1),1200)},title:"Copy result",className:"shrink-0 text-slate-300 hover:text-slate-500 transition-colors opacity-0 group-hover/display:opacity-100",children:/* @__PURE__ */u(r,$?{name:"check",className:"h-5 w-5 text-green-500",strokeWidth:2}:{name:"copy",className:"h-5 w-5",strokeWidth:2})}),
|
|
8
|
+
/* @__PURE__ */u("div",{className:"text-right text-4xl font-mono font-bold text-slate-800 truncate flex-1",children:(e=>{const t=parseFloat(e);return isNaN(t)||e.endsWith(".")||e.endsWith(".0")||Math.abs(t)<1e15&&e.length<16?e:t.toExponential(6)})(h)})]})]}),
|
|
9
|
+
/* @__PURE__ */o("div",{className:"grid grid-cols-4 grid-rows-5 gap-2 flex-1 min-h-0 p-2",children:[
|
|
10
|
+
/* @__PURE__ */u("button",{onClick:D,className:I,children:"AC"}),
|
|
11
|
+
/* @__PURE__ */u("button",{onClick:H,className:I,children:"+/−"}),
|
|
12
|
+
/* @__PURE__ */u("button",{onClick:B,className:I,children:"%"}),
|
|
13
|
+
/* @__PURE__ */u("button",{onClick:()=>G("÷"),className:`${X} ${"÷"===p?"ring-2 ring-blue-400":""}`,children:"÷"}),
|
|
14
|
+
/* @__PURE__ */u("button",{onClick:()=>W("7"),className:R,children:"7"}),
|
|
15
|
+
/* @__PURE__ */u("button",{onClick:()=>W("8"),className:R,children:"8"}),
|
|
16
|
+
/* @__PURE__ */u("button",{onClick:()=>W("9"),className:R,children:"9"}),
|
|
17
|
+
/* @__PURE__ */u("button",{onClick:()=>G("×"),className:`${X} ${"×"===p?"ring-2 ring-blue-400":""}`,children:"×"}),
|
|
18
|
+
/* @__PURE__ */u("button",{onClick:()=>W("4"),className:R,children:"4"}),
|
|
19
|
+
/* @__PURE__ */u("button",{onClick:()=>W("5"),className:R,children:"5"}),
|
|
20
|
+
/* @__PURE__ */u("button",{onClick:()=>W("6"),className:R,children:"6"}),
|
|
21
|
+
/* @__PURE__ */u("button",{onClick:()=>G("-"),className:`${X} ${"-"===p?"ring-2 ring-blue-400":""}`,children:"−"}),
|
|
22
|
+
/* @__PURE__ */u("button",{onClick:()=>W("1"),className:R,children:"1"}),
|
|
23
|
+
/* @__PURE__ */u("button",{onClick:()=>W("2"),className:R,children:"2"}),
|
|
24
|
+
/* @__PURE__ */u("button",{onClick:()=>W("3"),className:R,children:"3"}),
|
|
25
|
+
/* @__PURE__ */u("button",{onClick:()=>G("+"),className:`${X} ${"+"===p?"ring-2 ring-blue-400":""}`,children:"+"}),
|
|
26
|
+
/* @__PURE__ */u("button",{onClick:()=>W("0"),className:`${R} col-span-2`,children:"0"}),
|
|
27
|
+
/* @__PURE__ */u("button",{onClick:()=>W("."),className:R,children:"."}),
|
|
28
|
+
/* @__PURE__ */u("button",{onClick:Q,className:K,children:"="})]})]}),
|
|
29
|
+
/* @__PURE__ */u(t,{open:E,onClose:()=>T(!1),title:"Calculator Settings",appearance:U,onAppearanceChange:A,onSave:()=>{w(U),localStorage.setItem(d,JSON.stringify(U)),T(!1)}})]})}export{h as default};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export{ConfirmProvider,confirm,confirmDestructive,prompt,useConfirm}from"./chunk-KSLOSQKN.js";import"./chunk-UVBK6YSR.js";import"./chunk-NGUH7T5V.js";import"./chunk-FIEA5CGB.js";import"./chunk-QRCDQPJA.js";import"./chunk-C3QOHMR6.js";import"./chunk-ZGUQLVY4.js";
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import{loadAppearance as e,WidgetSettingsModal as t}from"./chunk-GGSG7HSD.js";import{useWidgetSettings as r}from"./chunk-THM35LAN.js";import"./chunk-DSSLOQDW.js";import"./chunk-SCJT2XXU.js";import"./chunk-ZRU5F2XT.js";import"./chunk-5A7MWTXX.js";import"./chunk-YMWCZKUN.js";import"./chunk-KSLOSQKN.js";import"./chunk-ICN533ML.js";import"./chunk-UVBK6YSR.js";import"./chunk-NGUH7T5V.js";import"./chunk-7IQ3B77H.js";import"./chunk-FIEA5CGB.js";import"./chunk-D65T4H6J.js";import"./chunk-KNUR2UEG.js";import"./chunk-QRCDQPJA.js";import"./chunk-C3QOHMR6.js";import"./chunk-ZGUQLVY4.js";import{useState as n,useCallback as a,useEffect as s}from"react";import{jsxs as c,Fragment as o,jsx as i}from"react/jsx-runtime";var l=["USD","CNY","AUD","GBP","EUR","JPY","CAD","THB","NZD","SGD","HKD","CHF","KRW","INR","MXN","BRL"],m=[["USD","CNY"],["USD","AUD"],["GBP","USD"],["USD","JPY"]],u="currency_pairs",p="currency_appearance",d="currency_rates_cache";function h(e){try{const t=JSON.parse(localStorage.getItem(d)||"{}")[e];if(t&&Date.now()-t.timestamp<36e5)return t.rates}catch{}return null}function g(e,t){try{const r=JSON.parse(localStorage.getItem(d)||"{}");r[e]={rates:t,timestamp:Date.now()},localStorage.setItem(d,JSON.stringify(r))}catch{}}function x(){try{const e=JSON.parse(localStorage.getItem(u)||"");if(Array.isArray(e)&&e.length)return e.slice(0,4)}catch{}return m}var N={USD:"🇺🇸",CNY:"🇨🇳",AUD:"🇦🇺",GBP:"🇬🇧",JPY:"🇯🇵",CAD:"🇨🇦",EUR:"🇪🇺",THB:"🇹🇭",NZD:"🇳🇿",SGD:"🇸🇬",HKD:"🇭🇰",CHF:"🇨🇭",KRW:"🇰🇷",INR:"🇮🇳",MXN:"🇲🇽",BRL:"🇧🇷"};function y(){const[m,d]=n(x),[y,f]=n(()=>e(p)),[b,S]=n({}),[v,k]=n(!1),[D,C]=n(!1),[j,U]=n([]),[A,B]=n(y),[J,R]=n("USD"),[G,H]=n("CNY");r(a(()=>{U([...m]),B({...y}),C(!0)},[m,y])),s(()=>{const e=[...new Set(m.map(([e])=>e))];let t=!0;return async function(){k(!0);const r={};for(const t of e){const e=h(t);if(e)r[t]=e;else try{const e=await fetch(`https://open.er-api.com/v6/latest/${t}`),n=await e.json();n.rates&&(r[t]=n.rates,g(t,n.rates))}catch{}}t&&(S(r),k(!1))}(),()=>{t=!1}},[m]);/* @__PURE__ */
|
|
2
|
+
return c(o,{children:[
|
|
3
|
+
/* @__PURE__ */i("div",{style:{backgroundColor:`rgb(var(--taskbar-bg-rgb, 243 244 246) / ${y.activeOpacity/100})`,backdropFilter:y.activeBlur>0?`blur(${y.activeBlur}px)`:void 0},children:/* @__PURE__ */c("div",{className:"px-4 py-3 space-y-1",children:[v&&/* @__PURE__ */i("div",{className:"text-xs text-gray-400 text-center py-4",children:"Loading rates..."}),m.map(([e,t],r)=>{const n=b[e]?.[t];/* @__PURE__ */
|
|
4
|
+
return c("button",{onClick:()=>{const e=m.map((e,t)=>t===r?[e[1],e[0]]:e);d(e),localStorage.setItem(u,JSON.stringify(e))},className:"flex items-center justify-between px-2 py-2 border-b border-gray-200 last:border-0 w-full hover:bg-gray-100 rounded transition-colors cursor-pointer",children:[
|
|
5
|
+
/* @__PURE__ */c("div",{className:"flex items-center gap-2",children:[
|
|
6
|
+
/* @__PURE__ */i("span",{className:"text-base",children:N[e]||""}),
|
|
7
|
+
/* @__PURE__ */i("span",{className:"text-sm font-semibold text-gray-700",children:e}),
|
|
8
|
+
/* @__PURE__ */i("svg",{className:"h-3 w-3 text-gray-400",fill:"none",viewBox:"0 0 24 24",stroke:"currentColor",strokeWidth:2,children:/* @__PURE__ */i("path",{strokeLinecap:"round",strokeLinejoin:"round",d:"M7 16V4m0 0L3 8m4-4l4 4m6 0v12m0 0l4-4m-4 4l-4-4"})}),
|
|
9
|
+
/* @__PURE__ */i("span",{className:"text-base",children:N[t]||""}),
|
|
10
|
+
/* @__PURE__ */i("span",{className:"text-sm font-semibold text-gray-700",children:t})]}),
|
|
11
|
+
/* @__PURE__ */i("div",{className:"text-base font-mono font-semibold text-gray-900 tabular-nums",children:null!=n?n.toLocaleString(void 0,{minimumFractionDigits:2,maximumFractionDigits:4}):"—"})]},`${e}-${t}-${r}`)})]})}),
|
|
12
|
+
/* @__PURE__ */i(t,{open:D,onClose:()=>C(!1),title:"Currency Settings",appearance:A,onAppearanceChange:B,onSave:()=>{if(0===j.length)return;const e=j.slice(0,4);d(e),f(A),localStorage.setItem(u,JSON.stringify(e)),localStorage.setItem(p,JSON.stringify(A)),C(!1)},children:/* @__PURE__ */c("div",{children:[
|
|
13
|
+
/* @__PURE__ */c("h3",{className:"text-sm font-semibold text-gray-700 mb-2",children:["Currency Pairs ",
|
|
14
|
+
/* @__PURE__ */c("span",{className:"text-2xs font-normal text-gray-400",children:["(",j.length,"/",4,")"]})]}),
|
|
15
|
+
/* @__PURE__ */i("div",{className:"space-y-1 mb-2",children:j.map(([e,t],r)=>/* @__PURE__ */c("div",{className:"flex items-center justify-between py-1 px-2 bg-gray-50 rounded",children:[
|
|
16
|
+
/* @__PURE__ */c("span",{className:"text-sm",children:[N[e]||""," ",e," → ",N[t]||""," ",t]}),
|
|
17
|
+
/* @__PURE__ */i("button",{onClick:()=>U(e=>e.filter((e,t)=>t!==r)),className:"text-red-400 hover:text-red-600",children:"×"})]},r))}),j.length<4?/* @__PURE__ */c("div",{className:"flex items-center gap-2",children:[
|
|
18
|
+
/* @__PURE__ */i("select",{value:J,onChange:e=>R(e.target.value),className:"bg-gray-50 border border-gray-200 rounded px-2 py-1 text-sm focus:outline-none focus:ring-1 focus:ring-blue-500",children:l.map(e=>/* @__PURE__ */i("option",{children:e},e))}),
|
|
19
|
+
/* @__PURE__ */i("span",{className:"text-gray-400",children:"→"}),
|
|
20
|
+
/* @__PURE__ */i("select",{value:G,onChange:e=>H(e.target.value),className:"bg-gray-50 border border-gray-200 rounded px-2 py-1 text-sm focus:outline-none focus:ring-1 focus:ring-blue-500",children:l.map(e=>/* @__PURE__ */i("option",{children:e},e))}),
|
|
21
|
+
/* @__PURE__ */i("button",{onClick:()=>{J!==G&&j.length<4&&U(e=>[...e,[J,G]])},className:"text-sm font-medium text-blue-600 hover:text-blue-800 px-2",children:"+ Add"})]}):/* @__PURE__ */c("p",{className:"text-2xs text-gray-400 italic",children:["Max ",4," pairs — remove one to add another."]})]})})]})}export{y as default};
|
|
@@ -1,25 +1,25 @@
|
|
|
1
|
-
import{escapeHtml as e}from"./chunk-BR22X7E5.js";import{AboutApp as t}from"./chunk-
|
|
1
|
+
import{escapeHtml as e}from"./chunk-BR22X7E5.js";import{AboutApp as t}from"./chunk-UZJ6RYMF.js";import{WindowTitle as n}from"./chunk-THM35LAN.js";import"./chunk-DSSLOQDW.js";import"./chunk-SCJT2XXU.js";import{toast_default as r}from"./chunk-RUHUTZN4.js";import"./chunk-ZRU5F2XT.js";import"./chunk-5A7MWTXX.js";import"./chunk-YMWCZKUN.js";import"./chunk-KSLOSQKN.js";import{PopupMenu as o,PopupMenuLabel as l,PopupMenuItem as a,PopupMenuDivider as i}from"./chunk-ICN533ML.js";import"./chunk-UVBK6YSR.js";import"./chunk-NGUH7T5V.js";import"./chunk-7IQ3B77H.js";import"./chunk-FIEA5CGB.js";import"./chunk-D65T4H6J.js";import"./chunk-B4XGJA3N.js";import"./chunk-KNUR2UEG.js";import"./chunk-QRCDQPJA.js";import"./chunk-C3QOHMR6.js";import{Icon as s}from"./chunk-ZGUQLVY4.js";import{useState as c,useRef as d}from"react";import{jsxs as m,jsx as h}from"react/jsx-runtime";var u=/* @__PURE__ */new Set(["txt","md","markdown","csv","tsv","log","json","xml","html","htm","css","js","jsx","ts","tsx","py","rb","go","rs","java","c","h","cpp","hpp","sh","bash","yml","yaml","toml","ini","conf","env","sql"]),p=/* @__PURE__ */new Set(["docx"]),g={filename:"Untitled",kind:"docx",content:"",ext:"docx"};function x(){const[x,f]=c(g),[y,v]=c(!1),[b,k]=c(!1),[w,N]=c(!1),[C,j]=c(null),D=d(null),L=d(null),M=d(null),U=(e,t)=>{M.current?.focus(),document.execCommand(e,!1,t),N(!0)},R=e=>{for(const t of e){if(!t.type.startsWith("image/"))continue;const e=new FileReader;e.onload=()=>{M.current?.focus(),document.execCommand("insertImage",!1,String(e.result)),N(!0)},e.readAsDataURL(t)}},T=e=>{C&&(C.el.removeAttribute("width"),C.el.removeAttribute("height"),C.el.style.width=e??"",C.el.style.height=e?"auto":"",N(!0),j(null))},B=async t=>{const n=(t.name.split(".").pop()||"").toLowerCase();v(!0);try{if(u.has(n)||!p.has(n)&&t.type.startsWith("text/")){const r=await t.text(),o=e(r).replace(/\n/g,"<br>");f({filename:t.name,kind:"text",content:o,ext:n}),N(!1)}else if(p.has(n))try{const e=await(import(
|
|
2
2
|
/* @vite-ignore */
|
|
3
|
-
"mammoth")),r=await t.arrayBuffer(),o=await e.convertToHtml({arrayBuffer:r});f({filename:t.name,kind:"docx",content:o.value||"<p><em>(empty document)</em></p>",ext:n}),N(!1)}catch(e){r.error('Install the optional "mammoth" peer dep to read .docx files.')}else"doc"===n?r.error(".doc is the legacy Word binary format — convert to .docx first."):r.error(`Unsupported file type: .${n||"unknown"}`)}finally{v(!1)}},
|
|
4
|
-
return m("div",{className:"relative flex flex-col h-full",onDragOver:e=>{e.preventDefault(),b||k(!0)},onDragLeave:e=>{e.currentTarget===e.target&&k(!1)},onDrop:e=>{e.preventDefault(),k(!1);const t=Array.from(e.dataTransfer.files??[]);0!==t.length&&(t.every(e=>e.type.startsWith("image/"))?
|
|
5
|
-
/* @__PURE__ */h(n,{title:`${
|
|
3
|
+
"mammoth")),r=await t.arrayBuffer(),o=await e.convertToHtml({arrayBuffer:r});f({filename:t.name,kind:"docx",content:o.value||"<p><em>(empty document)</em></p>",ext:n}),N(!1)}catch(e){r.error('Install the optional "mammoth" peer dep to read .docx files.')}else"doc"===n?r.error(".doc is the legacy Word binary format — convert to .docx first."):r.error(`Unsupported file type: .${n||"unknown"}`)}finally{v(!1)}},S=x?.filename?(W=x.filename).length>24?`${W.slice(0,23)}…`:W:"Untitled";var W;/* @__PURE__ */
|
|
4
|
+
return m("div",{className:"relative flex flex-col h-full",onDragOver:e=>{e.preventDefault(),b||k(!0)},onDragLeave:e=>{e.currentTarget===e.target&&k(!1)},onDrop:e=>{e.preventDefault(),k(!1);const t=Array.from(e.dataTransfer.files??[]);0!==t.length&&(t.every(e=>e.type.startsWith("image/"))?R(t):B(t[0]))},children:[
|
|
5
|
+
/* @__PURE__ */h(n,{title:`${S}${w?" •":""} - Documents`}),
|
|
6
6
|
/* @__PURE__ */h(t,{app:"documents"}),
|
|
7
7
|
/* @__PURE__ */m("div",{className:"flex items-center flex-wrap gap-x-2 gap-y-1 px-3 py-2 border-b border-gray-200 bg-gray-50 shrink-0",children:[
|
|
8
|
-
/* @__PURE__ */h("input",{ref:
|
|
9
|
-
/* @__PURE__ */m("button",{onClick:()=>
|
|
8
|
+
/* @__PURE__ */h("input",{ref:D,type:"file",accept:".txt,.md,.csv,.tsv,.log,.json,.xml,.html,.htm,.css,.js,.jsx,.ts,.tsx,.py,.rb,.go,.rs,.java,.c,.h,.cpp,.hpp,.sh,.bash,.yml,.yaml,.toml,.ini,.conf,.env,.sql,.docx,text/*",onChange:e=>{const t=e.target.files?.[0];t&&B(t),D.current&&(D.current.value="")},className:"hidden"}),
|
|
9
|
+
/* @__PURE__ */m("button",{onClick:()=>D.current?.click(),className:"text-xs text-gray-700 hover:text-gray-900 px-2 py-1 rounded hover:bg-gray-200 transition-colors flex items-center gap-1",children:[
|
|
10
10
|
/* @__PURE__ */h(s,{name:"folder-open",className:"h-3.5 w-3.5",strokeWidth:1.5}),"Open"]}),
|
|
11
11
|
/* @__PURE__ */h("button",{onClick:()=>{if(!x)return;const t=M.current?.innerHTML??"";if("text"===x.kind){if(/<(b|strong|i|em|u|font|span style|ul|ol|img)|style="[^"]*text-align/i.test(t)){const n=new Blob([`<!doctype html><meta charset="utf-8"><title>${e(x.filename)}</title><body style="font-family:ui-monospace,monospace;white-space:pre-wrap">${t}`],{type:"text/html"}),r=URL.createObjectURL(n),o=document.createElement("a");o.href=r,o.download=`${x.filename.replace(/\.[^.]+$/,"")}.html`,o.click(),URL.revokeObjectURL(r)}else{const e=M.current?.innerText??"",t=new Blob([e],{type:"text/plain"}),n=URL.createObjectURL(t),r=document.createElement("a");r.href=n,r.download=x.filename,r.click(),URL.revokeObjectURL(n)}}else{const n=new Blob([`<!doctype html><meta charset="utf-8"><title>${e(x.filename)}</title>${t}`],{type:"text/html"}),r=URL.createObjectURL(n),o=document.createElement("a");o.href=r,o.download=`${x.filename.replace(/\.docx$/i,"")}.html`,o.click(),URL.revokeObjectURL(r)}},className:"text-xs text-gray-700 hover:text-gray-900 px-2 py-1 rounded hover:bg-gray-200 transition-colors",children:"Save"}),
|
|
12
|
-
/* @__PURE__ */h("span",{className:"text-
|
|
12
|
+
/* @__PURE__ */h("span",{className:"text-2xs text-gray-400 ml-1",children:"TXT · DOCX · Code"}),
|
|
13
13
|
/* @__PURE__ */h("div",{className:"h-4 w-px bg-gray-300 mx-1"}),
|
|
14
14
|
/* @__PURE__ */m("span",{className:"text-xs font-medium text-gray-700 truncate max-w-[200px]",title:x.filename,children:[x.filename,w?" •":""]}),
|
|
15
15
|
/* @__PURE__ */h("div",{className:"h-4 w-px bg-gray-300 mx-1"}),
|
|
16
16
|
/* @__PURE__ */m("div",{className:"flex items-center gap-0.5",children:[
|
|
17
|
-
/* @__PURE__ */h("button",{onMouseDown:e=>e.preventDefault(),onClick:()=>
|
|
18
|
-
/* @__PURE__ */h("button",{onMouseDown:e=>e.preventDefault(),onClick:()=>
|
|
19
|
-
/* @__PURE__ */h("button",{onMouseDown:e=>e.preventDefault(),onClick:()=>
|
|
20
|
-
/* @__PURE__ */h("button",{onMouseDown:e=>e.preventDefault(),onClick:()=>
|
|
17
|
+
/* @__PURE__ */h("button",{onMouseDown:e=>e.preventDefault(),onClick:()=>U("bold"),className:"px-2 py-1 text-xs rounded font-bold text-gray-700 hover:bg-gray-200 transition-colors",title:"Bold",children:"B"}),
|
|
18
|
+
/* @__PURE__ */h("button",{onMouseDown:e=>e.preventDefault(),onClick:()=>U("italic"),className:"px-2 py-1 text-xs rounded italic text-gray-700 hover:bg-gray-200 transition-colors",title:"Italic",children:"I"}),
|
|
19
|
+
/* @__PURE__ */h("button",{onMouseDown:e=>e.preventDefault(),onClick:()=>U("underline"),className:"px-2 py-1 text-xs rounded underline text-gray-700 hover:bg-gray-200 transition-colors",title:"Underline",children:"U"}),
|
|
20
|
+
/* @__PURE__ */h("button",{onMouseDown:e=>e.preventDefault(),onClick:()=>U("strikeThrough"),className:"px-2 py-1 text-xs rounded line-through text-gray-700 hover:bg-gray-200 transition-colors",title:"Strikethrough",children:"S"})]}),
|
|
21
21
|
/* @__PURE__ */h("div",{className:"h-4 w-px bg-gray-300 mx-1"}),
|
|
22
|
-
/* @__PURE__ */m("select",{onMouseDown:e=>e.preventDefault(),onChange:e=>{
|
|
22
|
+
/* @__PURE__ */m("select",{onMouseDown:e=>e.preventDefault(),onChange:e=>{U("fontSize",e.target.value),e.currentTarget.value=""},defaultValue:"",className:"text-xs border border-gray-300 rounded px-1 py-0.5 bg-white",children:[
|
|
23
23
|
/* @__PURE__ */h("option",{value:"",disabled:!0,children:"Size"}),
|
|
24
24
|
/* @__PURE__ */h("option",{value:"1",children:"XS"}),
|
|
25
25
|
/* @__PURE__ */h("option",{value:"2",children:"S"}),
|
|
@@ -27,26 +27,26 @@ return m("div",{className:"relative flex flex-col h-full",onDragOver:e=>{e.preve
|
|
|
27
27
|
/* @__PURE__ */h("option",{value:"4",children:"L"}),
|
|
28
28
|
/* @__PURE__ */h("option",{value:"5",children:"XL"}),
|
|
29
29
|
/* @__PURE__ */h("option",{value:"6",children:"2XL"})]}),
|
|
30
|
-
/* @__PURE__ */h("input",{type:"color",defaultValue:"#000000",onChange:e=>
|
|
30
|
+
/* @__PURE__ */h("input",{type:"color",defaultValue:"#000000",onChange:e=>U("foreColor",e.target.value),className:"w-7 h-6 border border-gray-300 rounded cursor-pointer",title:"Text color"}),
|
|
31
31
|
/* @__PURE__ */h("div",{className:"h-4 w-px bg-gray-300 mx-1"}),
|
|
32
|
-
/* @__PURE__ */h("button",{onMouseDown:e=>e.preventDefault(),onClick:()=>
|
|
33
|
-
/* @__PURE__ */h("button",{onMouseDown:e=>e.preventDefault(),onClick:()=>
|
|
32
|
+
/* @__PURE__ */h("button",{onMouseDown:e=>e.preventDefault(),onClick:()=>U("insertUnorderedList"),className:"px-2 py-1 text-xs rounded text-gray-700 hover:bg-gray-200 transition-colors",title:"Bulleted list",children:"• List"}),
|
|
33
|
+
/* @__PURE__ */h("button",{onMouseDown:e=>e.preventDefault(),onClick:()=>U("insertOrderedList"),className:"px-2 py-1 text-xs rounded text-gray-700 hover:bg-gray-200 transition-colors",title:"Numbered list",children:"1."}),
|
|
34
34
|
/* @__PURE__ */h("div",{className:"h-4 w-px bg-gray-300 mx-1"}),
|
|
35
35
|
/* @__PURE__ */m("div",{className:"flex items-center gap-0.5",children:[
|
|
36
|
-
/* @__PURE__ */h("button",{onMouseDown:e=>e.preventDefault(),onClick:()=>
|
|
37
|
-
/* @__PURE__ */h("button",{onMouseDown:e=>e.preventDefault(),onClick:()=>
|
|
38
|
-
/* @__PURE__ */h("button",{onMouseDown:e=>e.preventDefault(),onClick:()=>
|
|
39
|
-
/* @__PURE__ */h("button",{onMouseDown:e=>e.preventDefault(),onClick:()=>
|
|
36
|
+
/* @__PURE__ */h("button",{onMouseDown:e=>e.preventDefault(),onClick:()=>U("justifyLeft"),className:"px-2 py-1 rounded text-gray-700 hover:bg-gray-200 transition-colors",title:"Align left",children:/* @__PURE__ */h("svg",{className:"h-3.5 w-3.5",fill:"none",viewBox:"0 0 24 24",stroke:"currentColor",strokeWidth:1.8,children:/* @__PURE__ */h("path",{strokeLinecap:"round",d:"M3.75 6h16.5M3.75 12h9.75M3.75 18h14.25"})})}),
|
|
37
|
+
/* @__PURE__ */h("button",{onMouseDown:e=>e.preventDefault(),onClick:()=>U("justifyCenter"),className:"px-2 py-1 rounded text-gray-700 hover:bg-gray-200 transition-colors",title:"Align center",children:/* @__PURE__ */h("svg",{className:"h-3.5 w-3.5",fill:"none",viewBox:"0 0 24 24",stroke:"currentColor",strokeWidth:1.8,children:/* @__PURE__ */h("path",{strokeLinecap:"round",d:"M3.75 6h16.5M7.125 12h9.75M4.875 18h14.25"})})}),
|
|
38
|
+
/* @__PURE__ */h("button",{onMouseDown:e=>e.preventDefault(),onClick:()=>U("justifyRight"),className:"px-2 py-1 rounded text-gray-700 hover:bg-gray-200 transition-colors",title:"Align right",children:/* @__PURE__ */h("svg",{className:"h-3.5 w-3.5",fill:"none",viewBox:"0 0 24 24",stroke:"currentColor",strokeWidth:1.8,children:/* @__PURE__ */h("path",{strokeLinecap:"round",d:"M3.75 6h16.5M10.5 12h9.75M6 18h14.25"})})}),
|
|
39
|
+
/* @__PURE__ */h("button",{onMouseDown:e=>e.preventDefault(),onClick:()=>U("justifyFull"),className:"px-2 py-1 rounded text-gray-700 hover:bg-gray-200 transition-colors",title:"Justify",children:/* @__PURE__ */h("svg",{className:"h-3.5 w-3.5",fill:"none",viewBox:"0 0 24 24",stroke:"currentColor",strokeWidth:1.8,children:/* @__PURE__ */h("path",{strokeLinecap:"round",d:"M3.75 6h16.5M3.75 12h16.5M3.75 18h16.5"})})})]}),
|
|
40
40
|
/* @__PURE__ */h("div",{className:"h-4 w-px bg-gray-300 mx-1"}),
|
|
41
|
-
/* @__PURE__ */h("input",{ref:L,type:"file",accept:"image/*",multiple:!0,className:"hidden",onChange:e=>{e.target.files?.length&&
|
|
41
|
+
/* @__PURE__ */h("input",{ref:L,type:"file",accept:"image/*",multiple:!0,className:"hidden",onChange:e=>{e.target.files?.length&&R(Array.from(e.target.files)),L.current&&(L.current.value="")}}),
|
|
42
42
|
/* @__PURE__ */m("button",{onClick:()=>L.current?.click(),className:"px-2 py-1 text-xs rounded text-gray-700 hover:bg-gray-200 transition-colors flex items-center gap-1",title:"Insert image (or paste / drop one)",children:[
|
|
43
43
|
/* @__PURE__ */h(s,{name:"image",className:"h-3.5 w-3.5",strokeWidth:1.5}),"Image"]})]}),
|
|
44
|
-
/* @__PURE__ */h("div",{className:"flex-1 min-h-0 overflow-hidden",children:/* @__PURE__ */h("div",y?{className:"flex items-center justify-center h-full text-sm text-gray-400",children:"Loading…"}:{className:"h-full overflow-auto "+("docx"===x.kind?"bg-gray-100 px-6 py-8":"bg-white"),children:/* @__PURE__ */h("div",{ref:M,contentEditable:!0,suppressContentEditableWarning:!0,spellCheck:!1,onInput:()=>N(!0),onPaste:e=>{const t=Array.from(e.clipboardData?.items??[]).filter(e=>"file"===e.kind&&e.type.startsWith("image/")).map(e=>e.getAsFile()).filter(e=>!!e);t.length&&(e.preventDefault(),
|
|
44
|
+
/* @__PURE__ */h("div",{className:"flex-1 min-h-0 overflow-hidden",children:/* @__PURE__ */h("div",y?{className:"flex items-center justify-center h-full text-sm text-gray-400",children:"Loading…"}:{className:"h-full overflow-auto "+("docx"===x.kind?"bg-gray-100 px-6 py-8":"bg-white"),children:/* @__PURE__ */h("div",{ref:M,contentEditable:!0,suppressContentEditableWarning:!0,spellCheck:!1,onInput:()=>N(!0),onPaste:e=>{const t=Array.from(e.clipboardData?.items??[]).filter(e=>"file"===e.kind&&e.type.startsWith("image/")).map(e=>e.getAsFile()).filter(e=>!!e);t.length&&(e.preventDefault(),R(t))},onClick:e=>{const t=e.target;"IMG"===t.tagName&&(e.preventDefault(),j({x:e.clientX,y:e.clientY,el:t}))},dangerouslySetInnerHTML:{__html:x.content},className:"docx"===x.kind?"docs-editor mx-auto w-[8.5in] min-h-[11in] bg-white p-[1in] shadow text-sm leading-relaxed text-gray-800 outline-none focus:ring-2 focus:ring-blue-400/40":"docs-editor block w-full min-h-full p-4 font-mono text-sm leading-relaxed text-gray-800 bg-white outline-none whitespace-pre-wrap"})})}),b&&/* @__PURE__ */h("div",{className:"absolute inset-0 bg-blue-500/15 border-4 border-dashed border-blue-500 pointer-events-none flex items-center justify-center z-20",children:/* @__PURE__ */h("div",{className:"px-4 py-2 rounded-md bg-blue-600 text-white text-sm font-medium shadow-lg",children:"Drop a document to open · images insert into the page"})}),C&&/* @__PURE__ */m(o,{minWidth:150,style:{left:C.x,top:C.y},onClose:()=>j(null),children:[
|
|
45
45
|
/* @__PURE__ */h(l,{children:"Image width"}),
|
|
46
|
-
/* @__PURE__ */h(a,{onClick:()=>
|
|
47
|
-
/* @__PURE__ */h(a,{onClick:()=>
|
|
48
|
-
/* @__PURE__ */h(a,{onClick:()=>
|
|
49
|
-
/* @__PURE__ */h(a,{onClick:()=>
|
|
50
|
-
/* @__PURE__ */h(a,{onClick:()=>
|
|
46
|
+
/* @__PURE__ */h(a,{onClick:()=>T("25%"),children:"25%"}),
|
|
47
|
+
/* @__PURE__ */h(a,{onClick:()=>T("50%"),children:"50%"}),
|
|
48
|
+
/* @__PURE__ */h(a,{onClick:()=>T("75%"),children:"75%"}),
|
|
49
|
+
/* @__PURE__ */h(a,{onClick:()=>T("100%"),children:"100%"}),
|
|
50
|
+
/* @__PURE__ */h(a,{onClick:()=>T(null),children:"Original size"}),
|
|
51
51
|
/* @__PURE__ */h(i,{}),
|
|
52
|
-
/* @__PURE__ */h(a,{danger:!0,onClick:()=>{C?.el.remove(),N(!0),
|
|
52
|
+
/* @__PURE__ */h(a,{danger:!0,onClick:()=>{C?.el.remove(),N(!0),j(null)},children:"Remove image"})]})]})}export{x as default};
|