efficient-shell 4.137.1 → 4.145.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 +77 -11
- package/dist/{Browser-ZX5OTOKK.js → Browser-24YNRTKT.js} +14 -14
- package/dist/Calculator-LER6KUQC.js +29 -0
- package/dist/{ConfirmDialog-NPKW2XHM.js → ConfirmDialog-GAYETRMG.js} +1 -1
- package/dist/{CurrencyConverter-2KOM7L6V.js → CurrencyConverter-Y35GFDFF.js} +6 -6
- package/dist/{Documents-IZTIQYPG.js → Documents-7FYX2CGT.js} +14 -14
- package/dist/{Files-B63KKXGZ.js → Files-RZOML2N6.js} +39 -39
- package/dist/{Notepad-WA77V45J.js → Notepad-VVDCBDUA.js} +13 -13
- package/dist/PdfViewer-CvLb2j_l.d.ts +40 -0
- package/dist/PdfViewer-ZCT3PYXU.js +1 -0
- package/dist/{PomodoroTimer-SDUBKXUS.js → PomodoroTimer-VAM3AJOX.js} +37 -37
- package/dist/{Preview-D4KOA4OI.js → Preview-HZ37D7QJ.js} +23 -23
- package/dist/{Sidebar-Y57LYXXI.js → Sidebar-CVF7C6BO.js} +3 -3
- package/dist/{Spreadsheet-JX5E6WH6.js → Spreadsheet-6WX3P6AB.js} +15 -15
- package/dist/{Stock-XAQ5WMUZ.js → Stock-AUWI3TUX.js} +3 -3
- package/dist/{Weather-HLAGJ5LV.js → Weather-HU75TH7T.js} +13 -13
- package/dist/{WorldClock-IRI4VCTA.js → WorldClock-T64MDRMJ.js} +5 -5
- package/dist/apps/index.d.ts +3 -23
- package/dist/apps/index.js +1 -1
- package/dist/brand.css +1 -1
- package/dist/{chunk-UZAJ5BF6.js → chunk-2RHMSFZV.js} +7 -7
- package/dist/{chunk-466CM2TJ.js → chunk-4FS742BM.js} +1 -1
- package/dist/{chunk-JN4WXTWJ.js → chunk-7DAQ3WVP.js} +42 -42
- package/dist/chunk-CVTOZCM3.js +24 -0
- package/dist/{chunk-H562R4GX.js → chunk-GDSGXDET.js} +1 -1
- package/dist/{chunk-A2OQLLYK.js → chunk-I55UMU3R.js} +1 -1
- package/dist/chunk-KKOHUQPF.js +1 -0
- package/dist/{chunk-Y7BOLDRM.js → chunk-L7TEFXIS.js} +14 -14
- package/dist/chunk-LVFMK5OQ.js +29 -0
- package/dist/{chunk-5CRAF2AD.js → chunk-MU5H2CDL.js} +2 -2
- package/dist/{chunk-FQLMX3IE.js → chunk-NBBOFQPL.js} +441 -442
- package/dist/{chunk-TAWKWSPN.js → chunk-RJ4LACFH.js} +3 -3
- package/dist/{chunk-B43AGQET.js → chunk-SLCR26NK.js} +4 -4
- package/dist/{chunk-XO43FJRM.js → chunk-TFKB2WEB.js} +5 -5
- package/dist/chunk-TR6PLN7W.js +1 -0
- package/dist/chunk-TWV42Q3K.js +1 -0
- package/dist/{chunk-VVAGSKW7.js → chunk-UB22J6EF.js} +1 -1
- package/dist/{chunk-44UO7FYU.js → chunk-V453E4KU.js} +4 -4
- package/dist/file-intake/index.js +1 -1
- package/dist/{index-DVI9DMSu.d.ts → index-NhR7frRu.d.ts} +189 -17
- package/dist/index.d.ts +2 -2
- package/dist/index.js +235 -235
- package/dist/map/index.d.ts +266 -13
- package/dist/map/index.js +22 -20
- package/dist/page-tabs/index.js +13 -13
- package/dist/pdf/index.d.ts +9 -0
- package/dist/pdf/index.js +2 -0
- package/dist/ui/index.d.ts +1 -1
- package/dist/ui/index.js +1 -1
- package/dist/ui.css +7 -0
- package/package.json +6 -1
- package/dist/Calculator-4SQGOQFQ.js +0 -29
- package/dist/PdfViewer-RBRGEKWF.js +0 -1
- package/dist/chunk-3A46NCXK.js +0 -29
- package/dist/chunk-OQDWLHZI.js +0 -1
- package/dist/chunk-OWSAMA3I.js +0 -23
- package/dist/chunk-U4GTZPXP.js +0 -1
- package/dist/chunk-VEGMDXT4.js +0 -1
package/README.md
CHANGED
|
@@ -400,6 +400,7 @@ All exports are named — `import { Modal, ... } from 'efficient-shell'`.
|
|
|
400
400
|
| `PopupMenu`, `PopupMenuItem`, `PopupMenuDivider`, `PopupMenuLabel`, `PopupSubmenu` | Right-click / context-menu primitive; `PopupSubmenu` is a row that opens a nested menu. |
|
|
401
401
|
| `ShellContextMenu` | The shell-wide right-click menu, already mounted by `Layout` (`<Layout contextMenu={false}>` turns it off). Mount it yourself only on a screen rendered outside the layout. See [Right-click menu](#right-click-menu). |
|
|
402
402
|
| `DropdownMenu` | A button and the menu it opens, right under it: actions, links (`href`, drawn by `linkAs`), radios, labels and dividers, with shared dismissal and the menu-button keyboard. `align` lines it up with the trigger's left or right edge; `side="top"` suits a trigger in a bottom action bar. See [Dropdown menus](#dropdown-menus). |
|
|
403
|
+
| `Dialog`, `Drawer` | A modal sheet that is not a shell window — centred (`Dialog`) or pinned to an edge (`Drawer`, `side` right / left / bottom) — for a till, a routed page or a form: trapped focus, a locked page, Escape and a backdrop click to close (`blocking` turns both off), a close cross when there is no `footer`. `title` names it; `description` is a line under the title that DESCRIBES it to a screen reader — without one the body does, which suits a confirm whose body is the question and not a form, whose every label would be read out. `role="alertdialog"` (Dialog) for a confirmation that interrupts. A heading or note that lives in the body — a form that loads the record its heading names — names and describes it through `aria-labelledby` / `aria-describedby`. Two open at once — the second inside the first, or beside it on `<body>` where a host portals its dialogs — each hold focus, and only the one in front moves Tab (`useFocusTrap` keeps a stack). |
|
|
403
404
|
| `ConfirmProvider`, `confirm` | Imperative `confirm({ title, body })` returning a Promise<boolean>. |
|
|
404
405
|
| `GlobalSearch` | Cmd-K command palette. Pass `providers: SearchProvider[]` to add results. Portalled to `<body>` on the overlay layer (`Z_LAYERS.overlay`), so it covers every window, pinned or not; opening it closes any open popup (`dismissPopups`). |
|
|
405
406
|
| `ShortcutHelp` | The keyboard cheatsheet shown on `?`. |
|
|
@@ -414,20 +415,20 @@ All exports are named — `import { Modal, ... } from 'efficient-shell'`.
|
|
|
414
415
|
| `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. |
|
|
415
416
|
| `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. |
|
|
416
417
|
| `SettingRow` | One setting: its name and current value on one baseline, the explanation at full width beneath in a smaller voice. The value never shrinks, so a short badge keeps its line instead of folding into a narrow column. 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. `DescriptionList` is the record-detail neighbour and stays that. |
|
|
417
|
-
| `Button`, `Input`, `Textarea`, `Select`, `Checkbox`, `Radio`, `Switch`, `FormField`, `Label` | Form controls — controlled (`value`/`onChange`); `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. 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. |
|
|
418
|
+
| `Button`, `Input`, `Textarea`, `Select`, `Checkbox`, `Radio`, `Switch`, `FormField`, `Label` | Form controls — controlled (`value`/`onChange`); `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. |
|
|
418
419
|
| `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. |
|
|
419
420
|
| `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. |
|
|
420
421
|
| `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. |
|
|
421
422
|
| `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. It renders the string it is handed; which symbol a platform uses is what the constants already decide. |
|
|
422
423
|
| `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. Renders nothing while `errors` is empty. |
|
|
423
424
|
| `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. |
|
|
424
|
-
| `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`
|
|
425
|
+
| `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. Both pickers take `weekStartsOn` (0 Sunday, 1 Monday); without it the week starts where the provider's `locale` says. |
|
|
425
426
|
| `Card`, `StatCard` | Surface panel (optional header/footer) + dashboard metric tile. |
|
|
426
427
|
| `Avatar`, `AvatarGroup` | User avatar with initials fallback + status dot; overlapping stack with +N overflow. |
|
|
427
428
|
| `Banner` | Static in-flow alert (`tone`: info / success / warning / danger). `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 whole box and `shrink-0`, so a message that wraps does not drag the button down or squeeze it. A banner that names a condition and leaves the fix three menus away is the usual shape of a notice nobody acts on. |
|
|
428
429
|
| `Tabs`, `Accordion`, `Tooltip` | Controlled tab strip, collapsible sections, frosted 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. 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`), solid and seamless under `rosh-reduce-transparency`, and hidden from assistive technology. |
|
|
429
430
|
| `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. |
|
|
430
|
-
| `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. `stackOnNarrow` turns each row into a labelled card on a narrow container — see [DataTable on a narrow screen](#datatable-on-a-narrow-screen). |
|
|
431
|
+
| `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` and any `data-*` attributes for its `<tr>` — a test's or a stylesheet's hook (`data-testid`, `data-state`). `stackOnNarrow` turns each row into a labelled card on a narrow container — see [DataTable on a narrow screen](#datatable-on-a-narrow-screen). |
|
|
431
432
|
| `Pagination` | Numbered page control (pairs with tables; complements `ListFooter`). |
|
|
432
433
|
| `Sparkline`, `LineChart`, `BarChart`, `DonutChart`, `ScatterChart` | Dependency-free inline-SVG charts (`currentColor`-themed). `LineChart` is the multi-series trend with optional scale, legend, dots and area fill. `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. |
|
|
433
434
|
| `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. |
|
|
@@ -491,11 +492,13 @@ All exports are named — `import { Modal, ... } from 'efficient-shell'`.
|
|
|
491
492
|
app without windows gives `PdfActionButton` an `openPreview` callback that
|
|
492
493
|
mounts the shell's `Preview` somewhere of its own — a tab, a route, an overlay.)
|
|
493
494
|
When the document belongs *inside* something else — a preview beside the form that
|
|
494
|
-
generates it, a document tab on a record — render `PdfViewer` directly
|
|
495
|
+
generates it, a document tab on a record, a dialog — render `PdfViewer` directly.
|
|
496
|
+
A host without the window manager takes it from **`efficient-shell/pdf`** (see
|
|
497
|
+
below); a desktop host may also take it from `efficient-shell/apps`:
|
|
495
498
|
|
|
496
499
|
```tsx
|
|
497
|
-
import {
|
|
498
|
-
|
|
500
|
+
import { Suspense } from 'react';
|
|
501
|
+
import { PdfViewer } from 'efficient-shell/pdf'; // already lazy
|
|
499
502
|
|
|
500
503
|
<div className="h-[32rem]">
|
|
501
504
|
<Suspense fallback={<p>Loading viewer…</p>}>
|
|
@@ -511,6 +514,12 @@ const PdfViewer = lazy(() => import('efficient-shell/apps').then(m => ({ default
|
|
|
511
514
|
| `fit` | `'width'` (default) fills the container's width and lets a tall page scroll — right for a viewer that owns a window. `'page'` fits the whole page, which is what a pane inside a dialog wants. Either way it is the *initial* mode: the reader can still zoom, and the Fit button re-arms it. |
|
|
512
515
|
| `onDownload` | Replaces the built-in "save `url` as `filename`". |
|
|
513
516
|
| `onEmail` | Adds an Email button to the toolbar. Omit it and there is none. |
|
|
517
|
+
| `workerSrc` | Where the host serves `pdfjs-dist/build/pdf.worker.min.mjs` (the installed version's). Omit it and the worker comes from unpkg — which a host whose Content-Security-Policy allows its own scripts only cannot load. It sets pdf.js's global `GlobalWorkerOptions.workerSrc`, so give every viewer on the page the same value. |
|
|
518
|
+
| `wasmUrl` | Where the host serves the installed `pdfjs-dist/wasm/` directory (the JBIG2 / JPEG 2000 decoders, ICC colour, and the JavaScript fallbacks pdf.js loads when WebAssembly is refused). Wins over `window.__REACT_OS_SHELL_PDF_WASM__`; omit both and the bundler-emitted decoders are used. |
|
|
519
|
+
|
|
520
|
+
Its words — the page counter, Zoom, Fit, Print, Download, the loading line and
|
|
521
|
+
the two toasts — come from the strings catalog's `pdf` section, so a host that
|
|
522
|
+
mounts `ShellStringsProvider` gets them in its reader's language.
|
|
514
523
|
|
|
515
524
|
It fills its parent and scrolls inside, so **give the parent a resolved
|
|
516
525
|
height** — `h-full` inside a flex column, or an explicit height. It brings its
|
|
@@ -533,6 +542,52 @@ The alternative this replaces is `<iframe src={objectUrl}>`, which hands the
|
|
|
533
542
|
document to the browser's own plugin — its toolbar, its thumbnail rail, its
|
|
534
543
|
idea of the zoom, none of it themeable and none of it testable.
|
|
535
544
|
|
|
545
|
+
#### The PDF reader without the window manager — `efficient-shell/pdf`
|
|
546
|
+
|
|
547
|
+
`efficient-shell/apps` is the bundled window registry: importing it puts every
|
|
548
|
+
bundled app's chunk in your build graph — react-router-dom,
|
|
549
|
+
`@tanstack/react-query` and Heroicons statically, `xlsx`, `dxf-viewer` and
|
|
550
|
+
`online-3d-viewer` behind dynamic imports. Vite never complains about an
|
|
551
|
+
optional peer it is not asked to load; **Next.js (Turbopack) and webpack resolve
|
|
552
|
+
every import while they build**, so a server-rendered app that wanted one PDF in
|
|
553
|
+
a dialog failed its build on five packages it would never load.
|
|
554
|
+
`efficient-shell/pdf` is `PdfViewer` alone: React, and pdf.js behind the lazy
|
|
555
|
+
boundary — `scripts/verify-dist.mjs` holds the built graph to exactly that.
|
|
556
|
+
|
|
557
|
+
```bash
|
|
558
|
+
npm i pdfjs-dist
|
|
559
|
+
```
|
|
560
|
+
|
|
561
|
+
```tsx
|
|
562
|
+
'use client';
|
|
563
|
+
import { Suspense } from 'react';
|
|
564
|
+
import { Dialog } from 'efficient-shell/ui';
|
|
565
|
+
import { PdfViewer } from 'efficient-shell/pdf';
|
|
566
|
+
|
|
567
|
+
<Dialog open={open} onClose={close} title="INV-000123" size="xl">
|
|
568
|
+
<div className="h-[75vh]">
|
|
569
|
+
<Suspense fallback={null}>
|
|
570
|
+
<PdfViewer
|
|
571
|
+
url="/api/invoices/123/pdf"
|
|
572
|
+
filename="INV-000123.pdf"
|
|
573
|
+
workerSrc="/vendor/pdfjs/6.3.289/pdf.worker.min.mjs"
|
|
574
|
+
wasmUrl="/vendor/pdfjs/6.3.289/wasm/"
|
|
575
|
+
/>
|
|
576
|
+
</Suspense>
|
|
577
|
+
</div>
|
|
578
|
+
</Dialog>
|
|
579
|
+
```
|
|
580
|
+
|
|
581
|
+
The host serves pdf.js's worker and, for scanned documents, its `wasm/`
|
|
582
|
+
directory, from its own origin and under the installed version (so an upgrade
|
|
583
|
+
cannot pair a cached old worker with a new main thread) — copied from
|
|
584
|
+
`node_modules/pdfjs-dist` by a vendor script, or a static route. Its CSP then
|
|
585
|
+
needs `worker-src 'self'` and nothing from a CDN. pdf.js 6 compiles nothing
|
|
586
|
+
with `eval`; a CSP without `'wasm-unsafe-eval'` makes it load the decoders'
|
|
587
|
+
JavaScript fallbacks from `wasmUrl` instead of the WebAssembly ones.
|
|
588
|
+
|
|
589
|
+
`Dialog`'s `size="xl"` is the width for reading a page in.
|
|
590
|
+
|
|
536
591
|
### UI kit without the window manager — `efficient-shell/ui`
|
|
537
592
|
|
|
538
593
|
Not every app that wants this package's components wants a desktop. A
|
|
@@ -931,7 +986,7 @@ tables read); the sample content of the starter templates (`DashboardTemplate`,
|
|
|
931
986
|
`AuthScreen` and the rest are demos to copy, not components to configure); and
|
|
932
987
|
console warnings, which are for developers.
|
|
933
988
|
|
|
934
|
-
**The full catalog** — 39 sections,
|
|
989
|
+
**The full catalog** — 39 sections, 362 keys. A function's default is shown
|
|
935
990
|
with sample arguments.
|
|
936
991
|
|
|
937
992
|
<details><summary><code>window</code> — 17 keys</summary>
|
|
@@ -1122,7 +1177,7 @@ with sample arguments.
|
|
|
1122
1177
|
|
|
1123
1178
|
</details>
|
|
1124
1179
|
|
|
1125
|
-
<details><summary><code>datePicker</code> —
|
|
1180
|
+
<details><summary><code>datePicker</code> — 5 keys</summary>
|
|
1126
1181
|
|
|
1127
1182
|
| Key | Type | English default |
|
|
1128
1183
|
|---|---|---|
|
|
@@ -1130,10 +1185,11 @@ with sample arguments.
|
|
|
1130
1185
|
| `dialog` | `string` | `Choose a date` |
|
|
1131
1186
|
| `clear` | `string` | `Clear` |
|
|
1132
1187
|
| `today` | `string` | `Today` |
|
|
1188
|
+
| `required` | `string` | `Required` |
|
|
1133
1189
|
|
|
1134
1190
|
</details>
|
|
1135
1191
|
|
|
1136
|
-
<details><summary><code>dateRange</code> —
|
|
1192
|
+
<details><summary><code>dateRange</code> — 20 keys</summary>
|
|
1137
1193
|
|
|
1138
1194
|
| Key | Type | English default |
|
|
1139
1195
|
|---|---|---|
|
|
@@ -1147,6 +1203,7 @@ with sample arguments.
|
|
|
1147
1203
|
| `fromOnly` | `(date: string) => string` | `From {date}` |
|
|
1148
1204
|
| `toOnly` | `(date: string) => string` | `To {date}` |
|
|
1149
1205
|
| `range` | `(from: string, to: string) => string` | `{from} to {to}` |
|
|
1206
|
+
| `required` | `string` | `Required` |
|
|
1150
1207
|
| `last2Weeks` | `string` | `Last 2 Weeks` |
|
|
1151
1208
|
| `lastMonth` | `string` | `Last Month` |
|
|
1152
1209
|
| `last3Months` | `string` | `Last 3 Months` |
|
|
@@ -1810,14 +1867,23 @@ copying both from `node_modules/maplibre-gl/dist`). Tiles come from
|
|
|
1810
1867
|
|
|
1811
1868
|
| Export | Notes |
|
|
1812
1869
|
|---|---|
|
|
1813
|
-
| `EntityMap` | The map. `pins` (`MapPin`: glyph or `
|
|
1870
|
+
| `EntityMap` | The map. `pins` (`MapPin`: glyph, `icon` paths or an `iconNode` React element, `badge`, `tone` classes — a border colour among them replaces the white edge — `data` for a host stylesheet's `data-*` key, `approximate`, `faded`, `stop` for a route's numbered stop, `dot` for a small context dot on a full-size target; pass a memoised array: pins are redrawn only when it changes, so the focus stays on one), `selectedKey` + `onSelect`, `focus` to fly to a pin, `routes` (`MapRoute`, dashed when an estimate, `color` any CSS colour, `attribution` a credit shown while it is drawn), `marks` (`MapMark`: a start, an end, a place on the route — named images, never pressed), `fitTo` (pins and marks), `attribution`, `initialView`, `cooperativeGestures` (`'coarse'` for touch screens). Follows `data-theme="dark"` or a `dark` class on `<html>`; resizes with its box (a shell window sends no window resize). Without WebGL it says so and its children still draw. |
|
|
1871
|
+
| `EntityMap` `groupCrowded` | Every pin a target of 24 px or more (a stop 28) and no two targets touching — WCAG 2.5.8, which axe's `target-size` measures and a bigger hit area alone does not satisfy where pins crowd (axe 4.13 even throws on a target several others cover, reporting the rule as `incomplete`). Pins that would touch are one group ("5 leads here", `noun` names them; stops only with stops, "Stops 3–4"), which zooms in until they stand apart and hands the focus to the first — as close as the map goes, it chooses the next of them. A pin that would sit under a stop, or crowd a control over the map (the zoom buttons, the attribution `<summary>`, the card's buttons), is left off until the map moves. `focus` zooms until the pin stands alone. |
|
|
1872
|
+
| `EntityMap` `card` | The chosen pin's card, beside its pin and pointing at it — above where it fits, else below, turned to a corner by the map's sides, the map moving by what is missing (`placeCard`) — never taller than the map can show beside its pin (it scrolls). On a map under `cardMinMapWidth` (480 px) it runs along the bottom edge. Closed from the keyboard, the focus goes back to its pin. |
|
|
1873
|
+
| `EntityMap` `highlightKey`, `useMapHighlight` | Rings what the list beside the map points at — its pin, the group holding it, a mark, or its own spot while it is left off. A picture: `aria-hidden`, nothing to press, and the map never moves for it. `pointAt(key)` is the row's pointer and focus handlers. |
|
|
1874
|
+
| `EntityMap` `picking` + `onPick`, `fullScreen` | The next click on the map as `{ lat, lng }` (a crosshair meanwhile). `fullScreen: { active, onChange }` draws "Full screen" / "Leave full screen" over the top-left corner; the host lays the page out for `active`, the map asks the browser to match and reports when the browser leaves it. |
|
|
1814
1875
|
| `LocationsMap` | A record's compact map over an endpoint: legend, "Locating N addresses…", a note per record with no pin, the chosen pin's card. `palette` names the categories the portal's way; `numbers` gives each pin the number the page's own list shows. |
|
|
1815
1876
|
| `MapLegend`, `SelectedCard` | The overlays, for a map that lays out its own list (the admin portal's list-wide Map view). |
|
|
1816
1877
|
| `useMapPoints(queryKey, queryFn)` | react-query, re-asked every few seconds while `pending > 0`; `mapPointsRefetchInterval` is the same rule for a hand-written query. |
|
|
1817
1878
|
| `MAP_CATEGORIES`, `pinTone`, `pinGlyph`, `pinCategory`, `categoryLabel`, `categoryTone` | The pin palette. Importer and supplier are **sky**, never blue — the themes remap `bg-blue-600` to the accent. |
|
|
1818
1879
|
| `isOnMap`, `placementNote`, `pointKey`, `locationNumbers`, `summarize`, `summaryText`, `legendCategories`, `offMapNotes` | The pure logic over `MapPoint[]`. |
|
|
1880
|
+
| `layoutPins`, `zoomApart`, `stopsLabel`, `placeCard`, `cardMaxHeight` | The pure rules behind `groupCrowded` and `card`. |
|
|
1819
1881
|
| `loadMapLibre`, `mapLibreWorkerUrl`, `foldAttributionOnce` | For a host drawing its own MapLibre layer on the same terms. |
|
|
1820
1882
|
|
|
1883
|
+
Every word the map draws on its own account — a group's name, "Full screen",
|
|
1884
|
+
the could-not-draw line, `SelectedCard`'s buttons — is the `map` section of
|
|
1885
|
+
`ShellStrings`.
|
|
1886
|
+
|
|
1821
1887
|
### Misc
|
|
1822
1888
|
|
|
1823
1889
|
| Export | Notes |
|
|
@@ -1833,7 +1899,7 @@ copying both from `node_modules/maplibre-gl/dist`). Tiles come from
|
|
|
1833
1899
|
| `GROUP_COLORS`, `GROUP_COLORS_SOLID`, `groupColors(group, emphasis?)` | The status palette, both registers, as the class strings the badges emit — for a surface that has to build its own pill (a virtualised cell, a canvas legend) and must not guess at the colors. |
|
|
1834
1900
|
| `severityOf(value, warn?, crit?)` | The `SeverityTone` (`success` \| `warning` \| `danger`) a reading earns against **inclusive** bounds; `null` when there's no reading or no usable bounds — the shell hardcodes no threshold. Backs `MetricBar`; use it to roll a `SidebarNavItem severity` up. |
|
|
1835
1901
|
| `isSeverityTone(value)` | Type guard for the three tones. Validate a backend rollup with it at the fetch boundary, where a bad token can still be reported against its payload, rather than letting it surface as a wrong pixel. |
|
|
1836
|
-
| `toast.success / .error / .info` | Toast notifications — auto-mounts container. |
|
|
1902
|
+
| `toast.success / .error / .info` | Toast notifications — auto-mounts container. Each plays the user's chime; `toast.configure({ sound: false })` keeps them quiet for a host whose screen makes sounds of its own (an alarm, a till's beep), and `{ sound: true }` on one toast still asks for it. |
|
|
1837
1903
|
| `Z_LAYERS` | The shell's z-index scale: `window` 50 (+ `windowStep` 10 per window above), `menu` 400, `pinnedWindow` 999, `overlay` 9999 (the palette, `Dialog`, `Drawer`, the shortcut sheet), `popup` 10000 (portalled listboxes, calendars, the server-status card). Popups sit above overlays so one opened inside a dialog is not drawn behind it. Apply as inline `zIndex` — an interpolated `z-[…]` class is invisible to Tailwind's scan. |
|
|
1838
1904
|
| `dismissPopups()`, `OVERLAY_OPEN_EVENT` | An overlay calls `dismissPopups()` as it opens; every open kit popup (`Select`, `SearchableSelect`, `TagInput`, `DatePicker`, `DateRangePicker`, `PopupMenu` via its `onClose`, `ServerStatusIndicator`) closes rather than floating over the overlay. The kit's own overlays already call it. A consumer's own popup listens for `OVERLAY_OPEN_EVENT` on `window` while open. |
|
|
1839
1905
|
| `Kbd` constants — `MOD`, `ALT`, `SHIFT`, `ENTER`, `ALT_SHIFT_E`, `CMD_K`, … | Symbol constants for rendering keyboard shortcuts. |
|
|
@@ -1,29 +1,29 @@
|
|
|
1
|
-
import{peekBrowserStartUrl as e,claimBrowserStartUrl as t}from"./chunk-LWLCNQY7.js";import{AboutApp as o}from"./chunk-
|
|
1
|
+
import{peekBrowserStartUrl as e,claimBrowserStartUrl as t}from"./chunk-LWLCNQY7.js";import{AboutApp as o}from"./chunk-RJ4LACFH.js";import"./chunk-TWV42Q3K.js";import{WindowTitle as r}from"./chunk-7DAQ3WVP.js";import"./chunk-V453E4KU.js";import"./chunk-UB4JIOLD.js";import"./chunk-PH3PRIFP.js";import"./chunk-2RHMSFZV.js";import{confirm as n}from"./chunk-LVFMK5OQ.js";import"./chunk-57MIENLJ.js";import"./chunk-MAIHBBWT.js";import"./chunk-TR6PLN7W.js";import"./chunk-D65T4H6J.js";import{useRef as a,useEffect as i,useState as l}from"react";import{jsxs as s,jsx as c,Fragment as m}from"react/jsx-runtime";var d="react-os-shell:browser-bookmarks",u="react-os-shell:browser-homepage",h="https://en.wikipedia.org/wiki/Main_Page",p=[{label:"Wikipedia",url:"https://en.wikipedia.org/wiki/Main_Page"},{label:"DevDocs",url:"https://devdocs.io"},{label:"Example",url:"https://example.com"}],g=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 b(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&&g.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 f(){if("undefined"==typeof window)return p;try{const e=localStorage.getItem(d);if(!e)return p;const t=JSON.parse(e);if(Array.isArray(t)&&t.every(e=>"string"==typeof e?.url))return t}catch{}return p}function x(){return"undefined"==typeof window?h:localStorage.getItem(u)||h}function w(){const m=a(void 0);void 0===m.current&&(m.current=e()),i(()=>{null!=m.current&&t(m.current)},[]);const h=m.current?b(m.current.url):"",[p,g]=l(x),[k,w]=l(h||p),[N,L]=l(k),[C,j]=l([k]),[z,M]=l(0),[W,R]=l(0),[H,B]=l(f),[S,A]=l(!1),[E,D]=l(null),I=a(null),P=a(null);i(()=>{try{localStorage.setItem(d,JSON.stringify(H))}catch{}},[H]);const T=e=>{const t=b(e);t&&(w(t),L(t),j(e=>{const o=e.slice(0,z+1);return o.push(t),o}),M(e=>e+1),A(!1))},V=()=>{"undefined"!=typeof window&&window.open(k,"_blank","noopener,noreferrer")},F=()=>{const e=E?.trim();e?(B(t=>[...t,{label:e,url:k}]),D(null)):D(null)};i(()=>{if(null===E)return;I.current?.focus(),I.current?.select();const e=e=>{const t=e.target;t?.closest("[data-bookmark-popover]")||D(null)},t=setTimeout(()=>document.addEventListener("mousedown",e),0);return()=>{clearTimeout(t),document.removeEventListener("mousedown",e)}},[null!==E]);const O=e=>{B(t=>t.filter((t,o)=>o!==e))},U=z>0,q=z<C.length-1,J=H.some(e=>e.url===k),K="p-1.5 rounded hover:bg-gray-200 transition-colors text-gray-600 disabled:opacity-30";/* @__PURE__ */
|
|
2
2
|
return s("div",{className:"relative flex flex-col h-full bg-white",children:[
|
|
3
3
|
/* @__PURE__ */c(r,{title:`Browser - ${v(k)}`}),
|
|
4
4
|
/* @__PURE__ */c(o,{app:"browser"}),
|
|
5
5
|
/* @__PURE__ */s("div",{className:"flex items-center gap-1 px-2 py-1.5 border-b border-gray-200 bg-gray-50 shrink-0",children:[
|
|
6
|
-
/* @__PURE__ */c("button",{onClick:()=>{if(z>0){const e=z-1;M(e),w(C[e]),L(C[e]),
|
|
7
|
-
/* @__PURE__ */c("button",{onClick:()=>{if(z<C.length-1){const e=z+1;M(e),w(C[e]),L(C[e]),
|
|
8
|
-
/* @__PURE__ */c("button",{onClick:()=>
|
|
9
|
-
/* @__PURE__ */c("button",{onClick:()=>
|
|
10
|
-
/* @__PURE__ */c("form",{onSubmit:e=>{e.preventDefault(),
|
|
6
|
+
/* @__PURE__ */c("button",{onClick:()=>{if(z>0){const e=z-1;M(e),w(C[e]),L(C[e]),A(!1)}},disabled:!U,className:K,title:"Back",children:/* @__PURE__ */c("svg",{className:"h-4 w-4",fill:"none",viewBox:"0 0 24 24",stroke:"currentColor",strokeWidth:2,children:/* @__PURE__ */c("path",{strokeLinecap:"round",strokeLinejoin:"round",d:"M15.75 19.5L8.25 12l7.5-7.5"})})}),
|
|
7
|
+
/* @__PURE__ */c("button",{onClick:()=>{if(z<C.length-1){const e=z+1;M(e),w(C[e]),L(C[e]),A(!1)}},disabled:!q,className:K,title:"Forward",children:/* @__PURE__ */c("svg",{className:"h-4 w-4",fill:"none",viewBox:"0 0 24 24",stroke:"currentColor",strokeWidth:2,children:/* @__PURE__ */c("path",{strokeLinecap:"round",strokeLinejoin:"round",d:"M8.25 4.5l7.5 7.5-7.5 7.5"})})}),
|
|
8
|
+
/* @__PURE__ */c("button",{onClick:()=>R(e=>e+1),className:K,title:"Refresh",children:/* @__PURE__ */c("svg",{className:"h-4 w-4",fill:"none",viewBox:"0 0 24 24",stroke:"currentColor",strokeWidth:1.8,children:/* @__PURE__ */c("path",{strokeLinecap:"round",strokeLinejoin:"round",d:"M16.023 9.348h4.992V4.356M2.985 19.644v-4.992h4.992M3.05 9.348a9 9 0 0114.85-3.36L21.015 9.348m0 5.304a9 9 0 01-14.85 3.36l-3.115-3.36"})})}),
|
|
9
|
+
/* @__PURE__ */c("button",{onClick:()=>T(p),className:K,title:"Home",children:/* @__PURE__ */c("svg",{className:"h-4 w-4",fill:"none",viewBox:"0 0 24 24",stroke:"currentColor",strokeWidth:1.8,children:/* @__PURE__ */c("path",{strokeLinecap:"round",strokeLinejoin:"round",d:"M2.25 12l8.954-8.955a1.5 1.5 0 012.122 0l8.954 8.955M4.5 9.75v10.125c0 .621.504 1.125 1.125 1.125H9.75v-4.875c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125V21h4.125c.621 0 1.125-.504 1.125-1.125V9.75M8.25 21h8.25"})})}),
|
|
10
|
+
/* @__PURE__ */c("form",{onSubmit:e=>{e.preventDefault(),T(N)},className:"flex-1 flex items-center mx-1",children:/* @__PURE__ */s("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
11
|
/* @__PURE__ */c("span",{className:"px-2 text-gray-400 text-xs",children:k.startsWith("https://")?"🔒":"⚠️"}),
|
|
12
12
|
/* @__PURE__ */c("input",{type:"text",value:N,onChange:e=>L(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!==N&&/* @__PURE__ */c("button",{type:"button",onClick:()=>L(k),className:"px-1.5 text-[10px] text-gray-400 hover:text-gray-700",title:"Reset to current URL",children:"×"})]})}),
|
|
13
|
-
/* @__PURE__ */c("button",{"data-bookmark-popover":!0,onClick:()=>
|
|
14
|
-
/* @__PURE__ */c("button",{onClick:
|
|
15
|
-
/* @__PURE__ */c("button",{onClick:()=>
|
|
13
|
+
/* @__PURE__ */c("button",{"data-bookmark-popover":!0,onClick:()=>J?O(H.findIndex(e=>e.url===k)):void D(v(k)),className:K+" "+(J?"text-yellow-500":""),title:J?"Remove bookmark":"Add bookmark",children:/* @__PURE__ */c("svg",{className:"h-4 w-4",fill:J?"currentColor":"none",viewBox:"0 0 24 24",stroke:"currentColor",strokeWidth:1.5,children:/* @__PURE__ */c("path",{strokeLinecap:"round",strokeLinejoin:"round",d:"M11.48 3.499a.562.562 0 011.04 0l2.125 5.111a.563.563 0 00.475.345l5.518.442c.499.04.701.663.321.988l-4.204 3.602a.563.563 0 00-.182.557l1.285 5.385a.562.562 0 01-.84.61l-4.725-2.885a.563.563 0 00-.586 0L6.982 20.54a.562.562 0 01-.84-.61l1.285-5.386a.562.562 0 00-.182-.557l-4.204-3.602a.563.563 0 01.321-.988l5.518-.442a.563.563 0 00.475-.345L11.48 3.5z"})})}),
|
|
14
|
+
/* @__PURE__ */c("button",{onClick:V,className:K,title:"Open in new tab",children:/* @__PURE__ */c("svg",{className:"h-4 w-4",fill:"none",viewBox:"0 0 24 24",stroke:"currentColor",strokeWidth:1.5,children:/* @__PURE__ */c("path",{strokeLinecap:"round",strokeLinejoin:"round",d:"M13.5 6H5.25A2.25 2.25 0 003 8.25v10.5A2.25 2.25 0 005.25 21h10.5A2.25 2.25 0 0018 18.75V10.5m-7.5-9L21 3m0 0v6m0-6L9.75 14.25"})})}),
|
|
15
|
+
/* @__PURE__ */c("button",{onClick:()=>A(e=>!e),className:K,title:"Embedding help",children:/* @__PURE__ */c("svg",{className:"h-4 w-4",fill:"none",viewBox:"0 0 24 24",stroke:"currentColor",strokeWidth:1.5,children:/* @__PURE__ */c("path",{strokeLinecap:"round",strokeLinejoin:"round",d:"M9.879 7.519c1.171-1.025 3.071-1.025 4.242 0 1.172 1.025 1.172 2.687 0 3.712-.203.179-.43.326-.67.442-.745.361-1.45.999-1.45 1.827v.75M21 12a9 9 0 11-18 0 9 9 0 0118 0zm-9 5.25h.008v.008H12v-.008z"})})})]}),null!==E&&/* @__PURE__ */s("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
16
|
/* @__PURE__ */c("div",{className:"text-[11px] font-semibold uppercase tracking-wide text-gray-500 mb-1",children:"Bookmark this page"}),
|
|
17
17
|
/* @__PURE__ */c("div",{className:"text-[11px] text-gray-400 truncate mb-2 font-mono",title:k,children:k}),
|
|
18
|
-
/* @__PURE__ */c("input",{ref:
|
|
18
|
+
/* @__PURE__ */c("input",{ref:I,type:"text",value:E,onChange:e=>D(e.target.value),onKeyDown:e=>{"Enter"===e.key?F():"Escape"===e.key&&D(null)},placeholder:"Name",className:"w-full text-sm px-2 py-1.5 border border-gray-300 rounded focus:outline-none focus:border-blue-400 focus:ring-1 focus:ring-blue-200"}),
|
|
19
19
|
/* @__PURE__ */s("div",{className:"flex justify-end gap-1 mt-2",children:[
|
|
20
|
-
/* @__PURE__ */c("button",{onClick:()=>
|
|
21
|
-
/* @__PURE__ */c("button",{onClick:
|
|
22
|
-
/* @__PURE__ */s("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===H.length&&/* @__PURE__ */c("span",{className:"text-[11px] text-gray-400 italic px-2",children:"No bookmarks yet — star the address bar to add one."}),H.map((e,t)=>/* @__PURE__ */s("button",{onClick:()=>
|
|
20
|
+
/* @__PURE__ */c("button",{onClick:()=>D(null),className:"px-2 py-1 text-xs text-gray-600 hover:bg-gray-100 rounded",children:"Cancel"}),
|
|
21
|
+
/* @__PURE__ */c("button",{onClick:F,disabled:!E.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__ */s("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===H.length&&/* @__PURE__ */c("span",{className:"text-[11px] text-gray-400 italic px-2",children:"No bookmarks yet — star the address bar to add one."}),H.map((e,t)=>/* @__PURE__ */s("button",{onClick:()=>T(e.url),onContextMenu:async o=>{o.preventDefault();await n({title:"Remove bookmark",message:`"${e.label}" will be removed from your bookmarks.`,confirmLabel:"Remove",variant:"danger"})&&O(t)},className:"flex items-center gap-1.5 px-2 py-0.5 rounded text-[12px] text-gray-700 hover:bg-gray-100 whitespace-nowrap",title:`${e.url}\n(right-click to remove)`,children:[
|
|
23
23
|
/* @__PURE__ */c("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
24
|
/* @__PURE__ */c("div",{className:"flex-1"}),
|
|
25
25
|
/* @__PURE__ */c("button",{onClick:()=>{g(k);try{localStorage.setItem(u,k)}catch{}},className:"px-2 py-0.5 text-[11px] text-gray-500 hover:text-gray-800",title:"Set current page as homepage",children:"Set as home"})]}),
|
|
26
|
-
/* @__PURE__ */c("div",{className:"flex-1 relative min-h-0 bg-gray-50",children:/* @__PURE__ */c(y,{url:k,iframeKey:W,iframeRef:
|
|
26
|
+
/* @__PURE__ */c("div",{className:"flex-1 relative min-h-0 bg-gray-50",children:/* @__PURE__ */c(y,{url:k,iframeKey:W,iframeRef:P,openExternal:V,showHelp:S,dismissHelp:()=>A(!1)})})]})}function v(e){try{return new URL(e).hostname.replace(/^www\./,"")}catch{return e}}function y({url:e,iframeKey:t,iframeRef:o,openExternal:r,showHelp:n,dismissHelp:a}){const[d,u]=l(!1);i(()=>{u(!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)&&!d?/* @__PURE__ */c("div",{className:"absolute inset-0 flex items-center justify-center bg-white p-8",children:/* @__PURE__ */s("div",{className:"max-w-md text-center",children:[
|
|
27
27
|
/* @__PURE__ */c("svg",{className:"h-14 w-14 mx-auto text-gray-300 mb-3",fill:"none",viewBox:"0 0 24 24",stroke:"currentColor",strokeWidth:1.2,children:/* @__PURE__ */c("path",{strokeLinecap:"round",strokeLinejoin:"round",d:"M18.364 18.364A9 9 0 005.636 5.636m12.728 12.728L5.636 5.636m12.728 12.728L21 21M5.636 5.636L3 3m9 9a9 9 0 110-18 9 9 0 010 18z"})}),
|
|
28
28
|
/* @__PURE__ */s("h3",{className:"text-base font-semibold text-gray-800 mb-1",children:[v(e)," can't be embedded"]}),
|
|
29
29
|
/* @__PURE__ */s("p",{className:"text-sm text-gray-500 mb-4",children:["This site sends an ",
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import{loadAppearance as e,WidgetSettingsModal as t}from"./chunk-GDSGXDET.js";import{useWidgetSettings as n}from"./chunk-7DAQ3WVP.js";import"./chunk-V453E4KU.js";import"./chunk-UB4JIOLD.js";import"./chunk-PH3PRIFP.js";import"./chunk-2RHMSFZV.js";import"./chunk-LVFMK5OQ.js";import"./chunk-57MIENLJ.js";import"./chunk-MAIHBBWT.js";import"./chunk-TR6PLN7W.js";import"./chunk-D65T4H6J.js";import{useState as l,useCallback as r,useEffect as i}from"react";import{jsxs as o,Fragment as a,jsx as c}from"react/jsx-runtime";var s="calc_appearance";function u(){const[u,d]=l("0"),[h,m]=l(null),[b,k]=l(null),[g,p]=l(!1),[x,v]=l(""),[y,N]=l([]),[f,C]=l(!1),[$,j]=l(()=>e(s)),[w,E]=l(!1),[L,S]=l($);n(r(()=>{S({...$}),E(!0)},[$]));const T=r(e=>{v(""),d(t=>g||"0"===t?(p(!1),"."===e?"0.":e):"."===e&&t.includes(".")?t:t+e)},[g]),B=r(()=>{d("0"),m(null),k(null),p(!1)},[]),F=r((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}},[]),W=r(e=>{const t=parseFloat(u);if(null!==h&&b&&!g){const e=F(h,t,b);d(String(e)),m(e),N(n=>[`${h} ${b} ${t} = ${e}`,...n].slice(0,20))}else m(t);k(e),p(!0)},[u,h,b,g,F]),M=r(()=>{if(null===h||!b)return;const e=parseFloat(u),t=F(h,e,b),n=`${h} ${b} ${e} =`;N(e=>[`${n} ${t}`,...e].slice(0,20)),v(n),d(String(t)),m(null),k(null),p(!0)},[u,h,b,F]),A=r(()=>{d(e=>String(parseFloat(e)/100))},[]),D=r(()=>{d(e=>e.startsWith("-")?e.slice(1):"0"===e?e:"-"+e)},[]);i(()=>{const e=e=>{const t=e.target.tagName;"INPUT"!==t&&"TEXTAREA"!==t&&"SELECT"!==t&&(e.key>="0"&&e.key<="9"?T(e.key):"."===e.key?T("."):"+"===e.key?W("+"):"-"===e.key?W("-"):"*"===e.key?W("×"):"/"===e.key?(e.preventDefault(),W("÷")):"Enter"===e.key||"="===e.key?M():"Escape"===e.key?B():"Backspace"===e.key?d(e=>e.length>1?e.slice(0,-1):"0"):"%"===e.key&&A())};return window.addEventListener("keydown",e),()=>window.removeEventListener("keydown",e)},[T,W,M,B,A]);const I="flex items-center justify-center rounded-lg text-sm font-medium transition-colors",P=`${I} bg-white border border-gray-200 text-gray-900 hover:bg-gray-50 active:bg-gray-100`,H=`${I} bg-blue-50 border border-blue-200 text-blue-700 hover:bg-blue-100 active:bg-blue-200`,V=`${I} bg-gray-100 border border-gray-200 text-gray-600 hover:bg-gray-200 active:bg-gray-300`,J=`${I} bg-blue-600 text-white hover:bg-blue-700 active:bg-blue-800`;/* @__PURE__ */
|
|
2
|
+
return o(a,{children:[
|
|
3
|
+
/* @__PURE__ */o("div",{className:"flex flex-col h-full",style:{opacity:$.activeOpacity/100,backdropFilter:$.activeBlur>0?`blur(${$.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__ */c("div",{className:"flex flex-col justify-end flex-1 overflow-hidden",children:y.slice(0,3).reverse().map((e,t)=>/* @__PURE__ */c("div",{className:"text-[11px] text-slate-400 text-right font-mono truncate",children:e},t))}),(x||b&&null!==h)&&/* @__PURE__ */c("div",{className:"text-base text-slate-600 text-right font-mono",children:x||`${h} ${b} ${g?"":u}`}),
|
|
6
|
+
/* @__PURE__ */o("div",{className:"flex items-center gap-2",children:[
|
|
7
|
+
/* @__PURE__ */c("button",{onClick:()=>{navigator.clipboard.writeText(u),C(!0),setTimeout(()=>C(!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__ */c("svg",f?{className:"h-5 w-5 text-green-500",fill:"none",viewBox:"0 0 24 24",stroke:"currentColor",strokeWidth:2,children:/* @__PURE__ */c("path",{strokeLinecap:"round",strokeLinejoin:"round",d:"M5 13l4 4L19 7"})}:{className:"h-5 w-5",fill:"none",viewBox:"0 0 24 24",stroke:"currentColor",strokeWidth:2,children:/* @__PURE__ */c("path",{strokeLinecap:"round",strokeLinejoin:"round",d:"M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"})})}),
|
|
8
|
+
/* @__PURE__ */c("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)})(u)})]})]}),
|
|
9
|
+
/* @__PURE__ */o("div",{className:"grid grid-cols-4 grid-rows-5 gap-1.5 flex-1 min-h-0 p-2",children:[
|
|
10
|
+
/* @__PURE__ */c("button",{onClick:B,className:V,children:"AC"}),
|
|
11
|
+
/* @__PURE__ */c("button",{onClick:D,className:V,children:"+/−"}),
|
|
12
|
+
/* @__PURE__ */c("button",{onClick:A,className:V,children:"%"}),
|
|
13
|
+
/* @__PURE__ */c("button",{onClick:()=>W("÷"),className:`${H} ${"÷"===b?"ring-2 ring-blue-400":""}`,children:"÷"}),
|
|
14
|
+
/* @__PURE__ */c("button",{onClick:()=>T("7"),className:P,children:"7"}),
|
|
15
|
+
/* @__PURE__ */c("button",{onClick:()=>T("8"),className:P,children:"8"}),
|
|
16
|
+
/* @__PURE__ */c("button",{onClick:()=>T("9"),className:P,children:"9"}),
|
|
17
|
+
/* @__PURE__ */c("button",{onClick:()=>W("×"),className:`${H} ${"×"===b?"ring-2 ring-blue-400":""}`,children:"×"}),
|
|
18
|
+
/* @__PURE__ */c("button",{onClick:()=>T("4"),className:P,children:"4"}),
|
|
19
|
+
/* @__PURE__ */c("button",{onClick:()=>T("5"),className:P,children:"5"}),
|
|
20
|
+
/* @__PURE__ */c("button",{onClick:()=>T("6"),className:P,children:"6"}),
|
|
21
|
+
/* @__PURE__ */c("button",{onClick:()=>W("-"),className:`${H} ${"-"===b?"ring-2 ring-blue-400":""}`,children:"−"}),
|
|
22
|
+
/* @__PURE__ */c("button",{onClick:()=>T("1"),className:P,children:"1"}),
|
|
23
|
+
/* @__PURE__ */c("button",{onClick:()=>T("2"),className:P,children:"2"}),
|
|
24
|
+
/* @__PURE__ */c("button",{onClick:()=>T("3"),className:P,children:"3"}),
|
|
25
|
+
/* @__PURE__ */c("button",{onClick:()=>W("+"),className:`${H} ${"+"===b?"ring-2 ring-blue-400":""}`,children:"+"}),
|
|
26
|
+
/* @__PURE__ */c("button",{onClick:()=>T("0"),className:`${P} col-span-2`,children:"0"}),
|
|
27
|
+
/* @__PURE__ */c("button",{onClick:()=>T("."),className:P,children:"."}),
|
|
28
|
+
/* @__PURE__ */c("button",{onClick:M,className:J,children:"="})]})]}),
|
|
29
|
+
/* @__PURE__ */c(t,{open:w,onClose:()=>E(!1),title:"Calculator Settings",appearance:L,onAppearanceChange:S,onSave:()=>{j(L),localStorage.setItem(s,JSON.stringify(L)),E(!1)}})]})}export{u as default};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export{ConfirmProvider,confirm,confirmDestructive,prompt,useConfirm}from"./chunk-
|
|
1
|
+
export{ConfirmProvider,confirm,confirmDestructive,prompt,useConfirm}from"./chunk-LVFMK5OQ.js";import"./chunk-TR6PLN7W.js";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{loadAppearance as e,WidgetSettingsModal as t}from"./chunk-
|
|
1
|
+
import{loadAppearance as e,WidgetSettingsModal as t}from"./chunk-GDSGXDET.js";import{useWidgetSettings as r}from"./chunk-7DAQ3WVP.js";import"./chunk-V453E4KU.js";import"./chunk-UB4JIOLD.js";import"./chunk-PH3PRIFP.js";import"./chunk-2RHMSFZV.js";import"./chunk-LVFMK5OQ.js";import"./chunk-57MIENLJ.js";import"./chunk-MAIHBBWT.js";import"./chunk-TR6PLN7W.js";import"./chunk-D65T4H6J.js";import{useState as a,useCallback as n,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"]],d="currency_pairs",u="currency_appearance",p="currency_rates_cache";function h(e){try{const t=JSON.parse(localStorage.getItem(p)||"{}")[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(p)||"{}");r[e]={rates:t,timestamp:Date.now()},localStorage.setItem(p,JSON.stringify(r))}catch{}}function x(){try{const e=JSON.parse(localStorage.getItem(d)||"");if(Array.isArray(e)&&e.length)return e.slice(0,4)}catch{}return m}var y={USD:"🇺🇸",CNY:"🇨🇳",AUD:"🇦🇺",GBP:"🇬🇧",JPY:"🇯🇵",CAD:"🇨🇦",EUR:"🇪🇺",THB:"🇹🇭",NZD:"🇳🇿",SGD:"🇸🇬",HKD:"🇭🇰",CHF:"🇨🇭",KRW:"🇰🇷",INR:"🇮🇳",MXN:"🇲🇽",BRL:"🇧🇷"};function f(){const[m,p]=a(x),[f,N]=a(()=>e(u)),[b,v]=a({}),[S,D]=a(!1),[k,C]=a(!1),[j,U]=a([]),[B,I]=a(f),[J,P]=a("USD"),[A,L]=a("CNY");r(n(()=>{U([...m]),I({...f}),C(!0)},[m,f])),s(()=>{const e=[...new Set(m.map(([e])=>e))];let t=!0;return async function(){D(!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}`),a=await e.json();a.rates&&(r[t]=a.rates,g(t,a.rates))}catch{}}t&&(v(r),D(!1))}(),()=>{t=!1}},[m]);/* @__PURE__ */
|
|
2
2
|
return c(o,{children:[
|
|
3
3
|
/* @__PURE__ */i("div",{style:{backgroundColor:`rgb(var(--taskbar-bg-rgb, 243 244 246) / ${f.activeOpacity/100})`,backdropFilter:f.activeBlur>0?`blur(${f.activeBlur}px)`:void 0},children:/* @__PURE__ */c("div",{className:"px-4 py-3 space-y-1",children:[S&&/* @__PURE__ */i("div",{className:"text-xs text-gray-400 text-center py-4",children:"Loading rates..."}),m.map(([e,t],r)=>{const a=b[e]?.[t];/* @__PURE__ */
|
|
4
4
|
return c("button",{onClick:()=>{const e=m.map((e,t)=>t===r?[e[1],e[0]]:e);p(e),localStorage.setItem(d,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:[
|
|
@@ -9,13 +9,13 @@ return c("button",{onClick:()=>{const e=m.map((e,t)=>t===r?[e[1],e[0]]:e);p(e),l
|
|
|
9
9
|
/* @__PURE__ */i("span",{className:"text-base",children:y[t]||""}),
|
|
10
10
|
/* @__PURE__ */i("span",{className:"text-sm font-semibold text-gray-700",children:t})]}),
|
|
11
11
|
/* @__PURE__ */i("div",{className:"text-base font-mono font-semibold text-gray-900 tabular-nums",children:null!=a?a.toLocaleString(void 0,{minimumFractionDigits:2,maximumFractionDigits:4}):"—"})]},`${e}-${t}-${r}`)})]})}),
|
|
12
|
-
/* @__PURE__ */i(t,{open:k,onClose:()=>C(!1),title:"Currency Settings",appearance:
|
|
12
|
+
/* @__PURE__ */i(t,{open:k,onClose:()=>C(!1),title:"Currency Settings",appearance:B,onAppearanceChange:I,onSave:()=>{if(0===j.length)return;const e=j.slice(0,4);p(e),N(B),localStorage.setItem(d,JSON.stringify(e)),localStorage.setItem(u,JSON.stringify(B)),C(!1)},children:/* @__PURE__ */c("div",{children:[
|
|
13
13
|
/* @__PURE__ */c("h3",{className:"text-sm font-semibold text-gray-700 mb-2",children:["Currency Pairs ",
|
|
14
14
|
/* @__PURE__ */c("span",{className:"text-[11px] font-normal text-gray-400",children:["(",j.length,"/",4,")"]})]}),
|
|
15
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
16
|
/* @__PURE__ */c("span",{className:"text-sm",children:[y[e]||""," ",e," → ",y[t]||""," ",t]}),
|
|
17
|
-
/* @__PURE__ */i("button",{onClick:()=>
|
|
18
|
-
/* @__PURE__ */i("select",{value:
|
|
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=>P(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
19
|
/* @__PURE__ */i("span",{className:"text-gray-400",children:"→"}),
|
|
20
|
-
/* @__PURE__ */i("select",{value:
|
|
21
|
-
/* @__PURE__ */i("button",{onClick:()=>{
|
|
20
|
+
/* @__PURE__ */i("select",{value:A,onChange:e=>L(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!==A&&j.length<4&&U(e=>[...e,[J,A]])},className:"text-sm font-medium text-blue-600 hover:text-blue-800 px-2",children:"+ Add"})]}):/* @__PURE__ */c("p",{className:"text-[11px] text-gray-400 italic",children:["Max ",4," pairs — remove one to add another."]})]})})]})}export{f as default};
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import{escapeHtml as e}from"./chunk-BR22X7E5.js";import{AboutApp as t}from"./chunk-RJ4LACFH.js";import{toast_default as n}from"./chunk-KKOHUQPF.js";import"./chunk-TWV42Q3K.js";import{WindowTitle as r}from"./chunk-7DAQ3WVP.js";import"./chunk-V453E4KU.js";import"./chunk-UB4JIOLD.js";import"./chunk-PH3PRIFP.js";import{PopupMenu as o,PopupMenuLabel as l,PopupMenuItem as a,PopupMenuDivider as i}from"./chunk-2RHMSFZV.js";import"./chunk-LVFMK5OQ.js";import"./chunk-57MIENLJ.js";import"./chunk-MAIHBBWT.js";import"./chunk-TR6PLN7W.js";import"./chunk-D65T4H6J.js";import{useState as s,useRef as c}from"react";import{jsxs as d,jsx as h}from"react/jsx-runtime";var m=/* @__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"]),u=/* @__PURE__ */new Set(["docx"]),p={filename:"Untitled",kind:"docx",content:"",ext:"docx"};function g(){const[g,x]=s(p),[f,y]=s(!1),[v,b]=s(!1),[k,w]=s(!1),[N,C]=s(null),L=c(null),j=c(null),D=c(null),M=(e,t)=>{D.current?.focus(),document.execCommand(e,!1,t),w(!0)},U=e=>{for(const t of e){if(!t.type.startsWith("image/"))continue;const e=new FileReader;e.onload=()=>{D.current?.focus(),document.execCommand("insertImage",!1,String(e.result)),w(!0)},e.readAsDataURL(t)}},R=e=>{N&&(N.el.removeAttribute("width"),N.el.removeAttribute("height"),N.el.style.width=e??"",N.el.style.height=e?"auto":"",w(!0),C(null))},B=async t=>{const r=(t.name.split(".").pop()||"").toLowerCase();y(!0);try{if(m.has(r)||!u.has(r)&&t.type.startsWith("text/")){const n=await t.text(),o=e(n).replace(/\n/g,"<br>");x({filename:t.name,kind:"text",content:o,ext:r}),w(!1)}else if(u.has(r))try{const e=await(import(
|
|
2
2
|
/* @vite-ignore */
|
|
3
|
-
"mammoth")),
|
|
4
|
-
return d("div",{className:"relative flex flex-col h-full",onDragOver:e=>{e.preventDefault(),v||b(!0)},onDragLeave:e=>{e.currentTarget===e.target&&b(!1)},onDrop:e=>{e.preventDefault(),b(!1);const t=Array.from(e.dataTransfer.files??[]);0!==t.length&&(t.every(e=>e.type.startsWith("image/"))?U(t):
|
|
5
|
-
/* @__PURE__ */h(
|
|
6
|
-
/* @__PURE__ */h(
|
|
3
|
+
"mammoth")),n=await t.arrayBuffer(),o=await e.convertToHtml({arrayBuffer:n});x({filename:t.name,kind:"docx",content:o.value||"<p><em>(empty document)</em></p>",ext:r}),w(!1)}catch(e){n.error('Install the optional "mammoth" peer dep to read .docx files.')}else"doc"===r?n.error(".doc is the legacy Word binary format — convert to .docx first."):n.error(`Unsupported file type: .${r||"unknown"}`)}finally{y(!1)}},W=g?.filename?(A=g.filename).length>24?`${A.slice(0,23)}…`:A:"Untitled";var A;/* @__PURE__ */
|
|
4
|
+
return d("div",{className:"relative flex flex-col h-full",onDragOver:e=>{e.preventDefault(),v||b(!0)},onDragLeave:e=>{e.currentTarget===e.target&&b(!1)},onDrop:e=>{e.preventDefault(),b(!1);const t=Array.from(e.dataTransfer.files??[]);0!==t.length&&(t.every(e=>e.type.startsWith("image/"))?U(t):B(t[0]))},children:[
|
|
5
|
+
/* @__PURE__ */h(r,{title:`${W}${k?" •":""} - Documents`}),
|
|
6
|
+
/* @__PURE__ */h(t,{app:"documents"}),
|
|
7
7
|
/* @__PURE__ */d("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__ */d("button",{onClick:()=>
|
|
8
|
+
/* @__PURE__ */h("input",{ref:L,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),L.current&&(L.current.value="")},className:"hidden"}),
|
|
9
|
+
/* @__PURE__ */d("button",{onClick:()=>L.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("svg",{className:"h-3.5 w-3.5",fill:"none",viewBox:"0 0 24 24",stroke:"currentColor",strokeWidth:1.5,children:/* @__PURE__ */h("path",{strokeLinecap:"round",strokeLinejoin:"round",d:"M3.75 9.776c.112-.017.227-.026.344-.026h15.812c.117 0 .232.009.344.026m-16.5 0a2.25 2.25 0 00-1.883 2.542l.857 6a2.25 2.25 0 002.227 1.932H19.05a2.25 2.25 0 002.227-1.932l.857-6a2.25 2.25 0 00-1.883-2.542m-16.5 0V6A2.25 2.25 0 016 3.75h3.879a1.5 1.5 0 011.06.44l2.122 2.12a1.5 1.5 0 001.06.44H18A2.25 2.25 0 0120.25 9v.776"})}),"Open"]}),
|
|
11
|
-
/* @__PURE__ */h("button",{onClick:()=>{if(!g)return;const
|
|
11
|
+
/* @__PURE__ */h("button",{onClick:()=>{if(!g)return;const t=D.current?.innerHTML??"";if("text"===g.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(g.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=`${g.filename.replace(/\.[^.]+$/,"")}.html`,o.click(),URL.revokeObjectURL(r)}else{const e=D.current?.innerText??"",t=new Blob([e],{type:"text/plain"}),n=URL.createObjectURL(t),r=document.createElement("a");r.href=n,r.download=g.filename,r.click(),URL.revokeObjectURL(n)}}else{const n=new Blob([`<!doctype html><meta charset="utf-8"><title>${e(g.filename)}</title>${t}`],{type:"text/html"}),r=URL.createObjectURL(n),o=document.createElement("a");o.href=r,o.download=`${g.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
12
|
/* @__PURE__ */h("span",{className:"text-[10px] 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__ */d("span",{className:"text-xs font-medium text-gray-700 truncate max-w-[200px]",title:g.filename,children:[g.filename,k?" •":""]}),
|
|
@@ -41,12 +41,12 @@ return d("div",{className:"relative flex flex-col h-full",onDragOver:e=>{e.preve
|
|
|
41
41
|
/* @__PURE__ */h("input",{ref:j,type:"file",accept:"image/*",multiple:!0,className:"hidden",onChange:e=>{e.target.files?.length&&U(Array.from(e.target.files)),j.current&&(j.current.value="")}}),
|
|
42
42
|
/* @__PURE__ */d("button",{onClick:()=>j.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("svg",{className:"h-3.5 w-3.5",fill:"none",viewBox:"0 0 24 24",stroke:"currentColor",strokeWidth:1.5,children:/* @__PURE__ */h("path",{strokeLinecap:"round",strokeLinejoin:"round",d:"M2.25 15.75l5.159-5.159a2.25 2.25 0 013.182 0l5.159 5.159m-1.5-1.5l1.409-1.409a2.25 2.25 0 013.182 0l2.909 2.909m-18 3.75h16.5a1.5 1.5 0 001.5-1.5V6a1.5 1.5 0 00-1.5-1.5H3.75A1.5 1.5 0 002.25 6v12a1.5 1.5 0 001.5 1.5zm10.5-11.25h.008v.008h-.008V8.25zm.375 0a.375.375 0 11-.75 0 .375.375 0 01.75 0z"})}),"Image"]})]}),
|
|
44
|
-
/* @__PURE__ */h("div",{className:"flex-1 min-h-0 overflow-hidden",children:/* @__PURE__ */h("div",f?{className:"flex items-center justify-center h-full text-sm text-gray-400",children:"Loading…"}:{className:"h-full overflow-auto "+("docx"===g.kind?"bg-gray-100 px-6 py-8":"bg-white"),children:/* @__PURE__ */h("div",{ref:
|
|
44
|
+
/* @__PURE__ */h("div",{className:"flex-1 min-h-0 overflow-hidden",children:/* @__PURE__ */h("div",f?{className:"flex items-center justify-center h-full text-sm text-gray-400",children:"Loading…"}:{className:"h-full overflow-auto "+("docx"===g.kind?"bg-gray-100 px-6 py-8":"bg-white"),children:/* @__PURE__ */h("div",{ref:D,contentEditable:!0,suppressContentEditableWarning:!0,spellCheck:!1,onInput:()=>w(!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(),U(t))},onClick:e=>{const t=e.target;"IMG"===t.tagName&&(e.preventDefault(),C({x:e.clientX,y:e.clientY,el:t}))},dangerouslySetInnerHTML:{__html:g.content},className:"docx"===g.kind?"docs-editor mx-auto w-[8.5in] min-h-[11in] bg-white p-[1in] shadow text-[14px] 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-[13px] leading-relaxed text-gray-800 bg-white outline-none whitespace-pre-wrap"})})}),v&&/* @__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"})}),N&&/* @__PURE__ */d(o,{minWidth:150,style:{left:N.x,top:N.y},onClose:()=>C(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:()=>R("25%"),children:"25%"}),
|
|
47
|
+
/* @__PURE__ */h(a,{onClick:()=>R("50%"),children:"50%"}),
|
|
48
|
+
/* @__PURE__ */h(a,{onClick:()=>R("75%"),children:"75%"}),
|
|
49
|
+
/* @__PURE__ */h(a,{onClick:()=>R("100%"),children:"100%"}),
|
|
50
|
+
/* @__PURE__ */h(a,{onClick:()=>R(null),children:"Original size"}),
|
|
51
51
|
/* @__PURE__ */h(i,{}),
|
|
52
52
|
/* @__PURE__ */h(a,{danger:!0,onClick:()=>{N?.el.remove(),w(!0),C(null)},children:"Remove image"})]})]})}export{g as default};
|