diff --git a/changelog.md b/changelog.md index a8ef4780..df05568e 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,15 @@ # Changelog +## [v1.6.1](https://github.com/janosh/svelte-widgets/compare/v1.6.0...v1.6.1) + +> 25 August 2026 + +- Add `svelte-widgets/source-links`: a Vite plugin indexes a project's source files and exported definitions into `virtual:source-symbols`, and `create_source_links` turns inline code mentions of them into GitHub links pinned to the build commit, via `source_href` or the `link_source_mentions` attachment [`#449`](https://github.com/janosh/svelte-widgets/pull/449) +- Add 493 icons from the installed Iconify sets with new transport, maps & GIS, shapes & math and accessibility blocks [`#449`](https://github.com/janosh/svelte-widgets/pull/449) +- `tooltip`: re-entering during the close delay keeps it open, top-layer mode falls back to absolute positioning without the Popover API instead of throwing, Escape passes through when nothing is open and `on_open_change(true)` always precedes a close; `portal` no longer re-inserts nodes Svelte already removed; `draggable` no longer jumps `position: relative` nodes and moves static ones [`#449`](https://github.com/janosh/svelte-widgets/pull/449) +- `CodeEditor`: `transact` without a selection maps it through the edits instead of throwing, Shift+Tab on an unindented line keeps focus, highlight resyncs snapshot at enqueue time and unchanged DOM selections are left alone so IME composition survives [`#449`](https://github.com/janosh/svelte-widgets/pull/449) +- `Nav` submenu arrow keys work from links (wrapping, with Home/End) and Escape returns focus to the toggle; `MultiSelect` only auto-activates while open and stops Escape/Tab propagation only while open; `Dialog` backdrop clicks close without native `closedby`; global `CopyButton` instances coexist, release removed blocks and copy live content; `Toast` no longer stays paused after dismissing a focused toast; `FileDetails` highlights each file once; `DraggablePane` and `Toc` hand Escape and focus back correctly; `Popover` chains a consumer's `ontoggle`; build-time heading ids decode HTML entities [`#449`](https://github.com/janosh/svelte-widgets/pull/449) + ## [v1.6.0](https://github.com/janosh/svelte-widgets/compare/v1.5.0...v1.6.0) > 19 August 2026 diff --git a/package.json b/package.json index 1dc7b1a5..713276bb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "svelte-widgets", - "version": "1.6.0", + "version": "1.6.1", "description": "Accessible, keyboard-friendly Svelte components and attachments", "keywords": [ "accessibility", @@ -84,6 +84,17 @@ "types": "./dist/live-examples/create-highlighter.d.ts", "default": "./dist/live-examples/create-highlighter.js" }, + "./source-links": { + "types": "./dist/source-links/index.d.ts", + "default": "./dist/source-links/index.js" + }, + "./source-links/vite-plugin": { + "types": "./dist/source-links/vite-plugin.d.ts", + "default": "./dist/source-links/vite-plugin.js" + }, + "./source-links/virtual": { + "types": "./dist/source-links/virtual.d.ts" + }, "./clipboard": { "types": "./dist/clipboard.svelte.d.ts", "default": "./dist/clipboard.svelte.js" @@ -154,8 +165,8 @@ "@iconify-json/file-icons": "^1.2.2", "@iconify-json/gis": "^1.2.5", "@iconify-json/iconoir": "^1.2.11", - "@iconify-json/lucide": "^1.2.123", - "@iconify-json/material-symbols": "^1.2.88", + "@iconify-json/lucide": "^1.2.126", + "@iconify-json/material-symbols": "^1.2.89", "@iconify-json/mdi": "^1.2.3", "@iconify-json/octicon": "^1.2.32", "@iconify-json/ri": "^1.2.10", @@ -165,21 +176,21 @@ "@iconify/utils": "^3.1.4", "@playwright/test": "^1.62.1", "@sveltejs/adapter-static": "^3.0.10", - "@sveltejs/kit": "^2.70.2", + "@sveltejs/kit": "^2.70.3", "@sveltejs/package": "2.5.8", "@sveltejs/vite-plugin-svelte": "^7.3.0", - "@types/node": "^26.2.0", + "@types/node": "^26.3.0", "@typescript/native-preview": "7.0.0-dev.20260707.2", - "@vitest/coverage-v8": "4.1.10", + "@vitest/coverage-v8": "4.1.11", "@wooorm/starry-night": "^3.10.0", - "happy-dom": "^20.11.2", + "happy-dom": "^20.11.6", "katex": "^0.18.4", "mdsvex": "^0.12.8", "pagefind": "^1.5.2", - "svelte": "^5.56.8", + "svelte": "^5.56.10", "typescript": "^6.0.3", - "vite": "^8.2.1", - "vite-plus": "0.2.9" + "vite": "^8.2.2", + "vite-plus": "0.3.0" }, "peerDependencies": { "@wooorm/starry-night": "^3.0.0", diff --git a/readme.md b/readme.md index 9e3d070d..5bc1e530 100644 --- a/readme.md +++ b/readme.md @@ -157,6 +157,9 @@ import { heading_anchors } from 'svelte-widgets/heading-anchors' | `/live-examples` | mdsvex live-example transform, Vite plugin and highlighter | | `/live-examples/create-highlighter` | Lightweight custom grammar highlighter factory | | `/print` | Element printing | +| `/source-links` | Link inline code mentions of your source to GitHub | +| `/source-links/vite-plugin` | Vite plugin emitting the file/export index those links use | +| `/source-links/virtual` | Types for the plugin's `virtual:source-symbols` module | | `/storage` | Non-throwing localStorage, persisted choices and MRU lists | | `/text-search` | Text ranges, highlighting and search-jump helpers | | `/theme` | Headless light/dark/system state | @@ -212,6 +215,28 @@ Import `katex/dist/katex.min.css` once in the app so the generated markup is sty See [src/lib/live-examples/readme.md](https://github.com/janosh/svelte-widgets/blob/-/src/lib/live-examples/readme.md) for optional live-example helpers. +Docs that mention source files or exports in inline code (`` `Footer` ``, `` `make_config` ``) can link them to the GitHub line they live on, pinned to the commit the site was built from. Add the plugin to `vite.config.ts`, reference its virtual-module types from `src/app.d.ts` and attach the linker to the element that wraps your pages: + +```ts +// vite.config.ts +import source_links from 'svelte-widgets/source-links/vite-plugin' +export default { plugins: [sveltekit(), source_links()] } // indexes src/lib by default + +// src/app.d.ts +/// + +// src/site/source-links.ts +import { create_source_links } from 'svelte-widgets/source-links' +import * as source_symbols from 'virtual:source-symbols' +export const { link_source_mentions, source_href } = create_source_links(source_symbols) +``` + +```svelte +
{@render children()}
+``` + +Only exact, unambiguous names link: a file name or bare component name (`Footer`, `utils.ts`) points at the file, an exported definition (`make_config`) at its line, and names defined in several files (`index.ts`) or that aren't source (`label`) are left alone. `source_href(name)` gives the same URL for use in your own markup. + ## 🆕   Changelog [View the changelog](changelog.md). diff --git a/scripts/icons-manifest.ts b/scripts/icons-manifest.ts index 3ece766e..dbbf2d00 100644 --- a/scripts/icons-manifest.ts +++ b/scripts/icons-manifest.ts @@ -7,6 +7,7 @@ export const iconify_icons = { AlignJustify: `mdi:format-align-justify`, AlignLeft: `mdi:format-align-left`, AlignRight: `mdi:format-align-right`, + Asterisk: `mdi:asterisk`, Autorenew: `mdi:autorenew`, Backspace: `mdi:backspace-outline`, Bold: `mdi:format-bold`, @@ -20,12 +21,16 @@ export const iconify_icons = { ClipboardMinus: `mdi:clipboard-minus-outline`, ClipboardPlus: `mdi:clipboard-plus-outline`, ClipboardText: `mdi:clipboard-text-outline`, + CodeBlock: `lucide:square-code`, + CommentEdit: `mdi:comment-edit-outline`, CommentPlus: `mdi:comment-plus-outline`, ContentCut: `mdi:content-cut`, ContentDuplicate: `mdi:content-duplicate`, ContentPaste: `mdi:content-paste`, Copy: `mdi:content-copy`, CursorText: `mdi:cursor-text`, + Delete: `mdi:delete-outline`, + DeleteForever: `mdi:delete-forever-outline`, Draft: `mdi:file-document-edit-outline`, Edit: `mdi:edit`, Eraser: `mdi:eraser`, @@ -33,7 +38,11 @@ export const iconify_icons = { FindReplace: `mdi:find-replace`, Font: `mdi:format-font`, FormatClear: `mdi:format-clear`, + FormatColorFill: `mdi:format-color-fill`, FormatColorText: `mdi:format-color-text`, + FormatHeader2: `mdi:format-header-2`, + FormatHeader3: `mdi:format-header-3`, + FormatHorizontalRule: `lucide:separator-horizontal`, FormatLetterSpacing: `mdi:format-letter-spacing`, FormatLineSpacing: `mdi:format-line-spacing`, FormatParagraph: `mdi:format-paragraph`, @@ -41,6 +50,8 @@ export const iconify_icons = { FormatQuoteClose: `mdi:format-quote-close`, FormatQuoteOpen: `mdi:format-quote-open`, FormatText: `mdi:format-text`, + FormatTextdirectionLToR: `mdi:format-textdirection-l-to-r`, + FormatTextdirectionRToL: `mdi:format-textdirection-r-to-l`, FormInput: `lucide:form-input`, Heading: `mdi:format-header-1`, Highlighter: `mdi:marker`, @@ -57,16 +68,19 @@ export const iconify_icons = { ListMinus: `lucide:list-minus`, ListNumbered: `mdi:format-list-numbered`, ListPlus: `lucide:list-plus`, + ListTodo: `lucide:list-todo`, MatchWholeWord: `mdi:format-letter-matches`, Merge: `mdi:call-merge`, Minus: `mdi:minus`, Notebook: `mdi:notebook-outline`, Outdent: `mdi:format-indent-decrease`, Pen: `mdi:pen`, + PencilPlus: `mdi:pencil-plus-outline`, PencilRuler: `mdi:pencil-ruler`, Plus: `mdi:plus`, Redo: `mdi:redo`, Regex: `mdi:regex`, + RemoveFormatting: `lucide:remove-formatting`, Rename: `mdi:rename-outline`, ScanLine: `lucide:scan-line`, ScanSearch: `lucide:scan-search`, @@ -76,6 +90,7 @@ export const iconify_icons = { SelectDrag: `mdi:select-drag`, SelectionLasso: `mdi:selection-lasso`, Signature: `mdi:signature-freehand`, + Slash: `mdi:slash-forward`, Sort: `mdi:sort`, SortAsc: `lucide:sort-asc`, SortDesc: `lucide:sort-desc`, @@ -113,9 +128,13 @@ export const iconify_icons = { CloudDownload: `mdi:cloud-download-outline`, CloudLock: `mdi:cloud-lock-outline`, CloudOff: `mdi:cloud-off-outline`, + CloudOutline: `mdi:cloud-outline`, + CloudPlus: `mdi:cloud-plus-outline`, CloudSync: `mdi:cloud-sync-outline`, CloudUpload: `mdi:cloud-upload-outline`, Database: `mdi:database`, + DatabaseOff: `mdi:database-off-outline`, + Disc: `mdi:disc`, Download: `mdi:download`, Export: `mdi:export`, ExportVariant: `mdi:export-variant`, @@ -126,6 +145,7 @@ export const iconify_icons = { FileAudio: `mdi:file-music-outline`, FileBadge: `lucide:file-badge`, FileCancel: `mdi:file-cancel-outline`, + FileCertificate: `mdi:file-certificate-outline`, FileChart: `mdi:file-chart-outline`, FileCheck: `mdi:file-check-outline`, FileClock: `mdi:file-clock-outline`, @@ -155,6 +175,7 @@ export const iconify_icons = { FileOutput: `lucide:file-output`, FilePDF: `mdi:file-pdf-box`, FilePlus: `mdi:file-plus-outline`, + FilePNG: `mdi:file-png-box`, FilePowerpoint: `mdi:file-powerpoint-outline`, FileQuestion: `mdi:file-question-outline`, FileRefresh: `mdi:file-refresh-outline`, @@ -181,6 +202,7 @@ export const iconify_icons = { FolderArchive: `lucide:folder-archive`, FolderCheck: `mdi:folder-check-outline`, FolderClock: `mdi:folder-clock-outline`, + FolderCode: `lucide:folder-code`, FolderCog: `mdi:folder-cog-outline`, FolderDownload: `mdi:folder-download-outline`, FolderEdit: `mdi:folder-edit-outline`, @@ -191,6 +213,7 @@ export const iconify_icons = { FolderInfo: `material-symbols:folder-info-outline-rounded`, FolderKey: `mdi:folder-key-outline`, FolderLock: `mdi:folder-lock-outline`, + FolderMinus: `mdi:folder-minus-outline`, FolderMove: `mdi:folder-move-outline`, FolderMultiple: `mdi:folder-multiple-outline`, FolderMusic: `mdi:folder-music-outline`, @@ -206,6 +229,7 @@ export const iconify_icons = { HardDrive: `mdi:harddisk`, Import: `mdi:import`, ImportExport: `mdi:import-export`, + MemoryCard: `mdi:sd`, Paperclip: `mdi:paperclip`, Save: `mdi:content-save-outline`, SaveAll: `mdi:content-save-all-outline`, @@ -218,28 +242,40 @@ export const iconify_icons = { TrayArrowUp: `mdi:tray-arrow-up`, Upload: `mdi:upload`, USB: `mdi:usb`, + USBFlashDrive: `mdi:usb-flash-drive-outline`, // navigation Apps: `mdi:apps`, AppWindow: `lucide:app-window`, ArrowBack: `mdi:arrow-u-left-top`, + ArrowCollapse: `mdi:arrow-collapse`, ArrowCollapseAll: `mdi:arrow-collapse-all`, ArrowCollapseHorizontal: `mdi:arrow-collapse-horizontal`, ArrowCollapseVertical: `mdi:arrow-collapse-vertical`, ArrowDecision: `mdi:arrow-decision-outline`, ArrowDown: `mdi:arrow-down`, + ArrowDownBold: `mdi:arrow-down-bold-outline`, + ArrowDownCircle: `mdi:arrow-down-circle-outline`, ArrowDownLeft: `mdi:arrow-bottom-left`, ArrowDownRight: `mdi:arrow-bottom-right`, + ArrowExpand: `mdi:arrow-expand`, ArrowExpandAll: `mdi:arrow-expand-all`, ArrowExpandHorizontal: `mdi:arrow-expand-horizontal`, ArrowExpandVertical: `mdi:arrow-expand-vertical`, ArrowForward: `mdi:arrow-u-right-top`, ArrowLeft: `mdi:arrow-left`, + ArrowLeftBold: `mdi:arrow-left-bold-outline`, + ArrowLeftCircle: `mdi:arrow-left-circle-outline`, ArrowLeftRight: `mdi:arrow-left-right`, ArrowRight: `mdi:arrow-right`, + ArrowRightBold: `mdi:arrow-right-bold-outline`, + ArrowRightCircle: `mdi:arrow-right-circle-outline`, ArrowUp: `mdi:arrow-up`, + ArrowUpBold: `mdi:arrow-up-bold-outline`, + ArrowUpCircle: `mdi:arrow-up-circle-outline`, ArrowUpDown: `mdi:arrow-up-down`, ArrowUpLeft: `mdi:arrow-top-left`, ArrowUpRight: `mdi:arrow-top-right`, + Backburger: `mdi:backburger`, ChevronDoubleDown: `mdi:chevron-double-down`, ChevronDoubleLeft: `mdi:chevron-double-left`, ChevronDoubleRight: `mdi:chevron-double-right`, @@ -247,11 +283,17 @@ export const iconify_icons = { ChevronDown: `mdi:chevron-down`, ChevronLeft: `mdi:chevron-left`, ChevronRight: `mdi:chevron-right`, + ChevronsLeftRight: `lucide:chevrons-left-right`, + ChevronsRightLeft: `lucide:chevrons-right-left`, ChevronUp: `mdi:chevron-up`, ChevronUpDown: `mdi:unfold-more-vertical`, Collapse: `iconoir:collapse`, Columns: `lucide:columns`, Command: `mdi:apple-keyboard-command`, + CornerDownLeft: `lucide:corner-down-left`, + CornerDownRight: `lucide:corner-down-right`, + CornerUpLeft: `lucide:corner-up-left`, + CornerUpRight: `lucide:corner-up-right`, CursorDefault: `mdi:cursor-default-outline`, CursorPointer: `mdi:cursor-pointer`, Dashboard: `mdi:view-dashboard-outline`, @@ -270,12 +312,20 @@ export const iconify_icons = { FormDropdown: `mdi:form-dropdown`, Fullscreen: `mdi:fullscreen`, FullscreenExit: `mdi:fullscreen-exit`, + GesturePinch: `mdi:gesture-pinch`, GestureSwipe: `mdi:gesture-swipe`, GestureTap: `mdi:gesture-tap`, GripVertical: `mdi:drag-vertical`, Home: `mdi:home`, HomeOutline: `mdi:home-outline`, Keyboard: `mdi:keyboard-outline`, + KeyboardBackspace: `mdi:keyboard-backspace`, + KeyboardEsc: `mdi:keyboard-esc`, + KeyboardOff: `mdi:keyboard-off-outline`, + KeyboardReturn: `mdi:keyboard-return`, + KeyboardShift: `mdi:apple-keyboard-shift`, + KeyboardTab: `mdi:keyboard-tab`, + LayoutDashboard: `lucide:layout-dashboard`, LayoutGrid: `mdi:view-dashboard-variant`, MapPinned: `lucide:map-pinned`, Masonry: `mdi:view-quilt`, @@ -304,7 +354,11 @@ export const iconify_icons = { Pan: `mdi:pan`, PanelBottom: `mdi:page-layout-footer`, PanelLeft: `lucide:panel-left`, + PanelLeftClose: `lucide:panel-left-close`, + PanelLeftOpen: `lucide:panel-left-open`, PanelRight: `lucide:panel-right`, + PanelRightClose: `lucide:panel-right-close`, + PanelRightOpen: `lucide:panel-right-open`, PanelsTopLeft: `lucide:panels-top-left`, PanelTop: `mdi:page-layout-header`, PictureInPicture: `mdi:picture-in-picture-bottom-right`, @@ -320,9 +374,12 @@ export const iconify_icons = { Stairs: `mdi:stairs`, StepBackward: `mdi:step-backward`, StepForward: `mdi:step-forward`, + SubdirectoryArrowLeft: `mdi:subdirectory-arrow-left`, + SubdirectoryArrowRight: `mdi:subdirectory-arrow-right`, SwapHorizontal: `mdi:swap-horizontal`, SwapVertical: `mdi:swap-vertical`, Tab: `mdi:tab`, + TableOfContents: `lucide:table-of-contents`, TabMinus: `mdi:tab-minus`, TabPlus: `mdi:tab-plus`, ViewAgenda: `mdi:view-agenda-outline`, @@ -348,10 +405,14 @@ export const iconify_icons = { AlertRhombus: `mdi:alert-rhombus-outline`, BadgeInfo: `lucide:badge-info`, Ban: `mdi:cancel`, + BatteryAlert: `mdi:battery-alert-variant-outline`, + BatteryLow: `mdi:battery-low`, + BatteryOff: `mdi:battery-off-outline`, BellBadge: `mdi:bell-badge-outline`, BellCheck: `mdi:bell-check-outline`, BellMinus: `lucide:bell-minus`, BellOff: `mdi:bell-off-outline`, + BellOutline: `mdi:bell-outline`, BellPlus: `lucide:bell-plus`, BellRing: `mdi:bell-ring-outline`, Check: `mdi:check`, @@ -359,6 +420,7 @@ export const iconify_icons = { CheckBold: `mdi:check-bold`, Checkbox: `mdi:checkbox-outline`, CheckboxIndeterminate: `mdi:minus-box-outline`, + CheckboxMarked: `mdi:checkbox-marked`, CheckboxMultiple: `mdi:checkbox-multiple-outline`, CheckCircle: `mdi:check-circle`, CheckCircleOutline: `mdi:check-circle-outline`, @@ -366,13 +428,17 @@ export const iconify_icons = { Circle: `mdi:circle`, CircleDashed: `lucide:circle-dashed`, CircleDot: `lucide:circle-dot`, + CircleOutline: `mdi:circle-outline`, CircleSlice: `mdi:circle-slice-4`, CloseCircle: `mdi:close-circle-outline`, CloseOctagon: `mdi:close-octagon-outline`, + CloseThick: `mdi:close-thick`, + Construction: `lucide:construction`, Help: `mdi:help`, HelpBox: `mdi:help-box-outline`, HelpCircle: `mdi:help-circle-outline`, Hourglass: `mdi:timer-sand`, + HourglassEmpty: `mdi:timer-sand-empty`, Incomplete: `mdi:circle-half-full`, Info: `mdi:info`, InfoBox: `mdi:information-box-outline`, @@ -385,11 +451,14 @@ export const iconify_icons = { InfoSquare: `carbon:information-square`, InfoSquareFilled: `carbon:information-square-filled`, Lightbulb: `mdi:lightbulb-outline`, + LightbulbOff: `mdi:lightbulb-off-outline`, Loading: `mdi:loading`, MinusCircle: `mdi:minus-circle-outline`, NewBox: `mdi:new-box`, + OctagonPause: `lucide:octagon-pause`, Pending: `mdi:dots-horizontal-circle-outline`, PlusCircle: `mdi:plus-circle-outline`, + PlusThick: `mdi:plus-thick`, Power: `mdi:power`, Progress: `mdi:progress-clock`, ProgressAlert: `mdi:progress-alert`, @@ -406,6 +475,9 @@ export const iconify_icons = { ShieldLock: `mdi:shield-lock-outline`, Signal: `mdi:signal`, SignalOff: `mdi:signal-off`, + Siren: `lucide:siren`, + StarFourPoints: `mdi:star-four-points-outline`, + StarOff: `mdi:star-off-outline`, StarOutline: `mdi:star-outline`, ThumbDown: `mdi:thumb-down-outline`, ThumbUp: `mdi:thumb-up-outline`, @@ -428,15 +500,19 @@ export const iconify_icons = { CameraPlus: `mdi:camera-plus-outline`, CaptionsOff: `lucide:captions-off`, Cast: `mdi:cast`, + CastOff: `mdi:cast-off`, + Clapperboard: `lucide:clapperboard`, ClosedCaption: `mdi:closed-caption-outline`, Eject: `mdi:eject-outline`, Equalizer: `mdi:equalizer`, FastForward: `mdi:fast-forward`, + FastForward10: `mdi:fast-forward-10`, Film: `mdi:film`, Gallery: `mdi:image-multiple-outline`, Headphones: `mdi:headphones`, Image: `mdi:image`, ImageEdit: `mdi:image-edit-outline`, + ImageOutline: `mdi:image-outline`, ImagePlus: `mdi:image-plus-outline`, ImageSearch: `mdi:image-search-outline`, ListMusic: `lucide:list-music`, @@ -464,6 +540,7 @@ export const iconify_icons = { RepeatOnce: `mdi:repeat-once`, Replay: `mdi:replay`, Rewind: `mdi:rewind`, + Rewind10: `mdi:rewind-10`, ScreenRecord: `mdi:record-rec`, ScreenShare: `lucide:screen-share`, ScreenShareOff: `lucide:screen-share-off`, @@ -484,9 +561,11 @@ export const iconify_icons = { VolumeMute: `mdi:volume-mute`, VolumeOff: `mdi:volume-off`, VolumeOn: `mdi:volume-high`, + Waveform: `mdi:waveform`, Webcam: `mdi:webcam`, // people Account: `mdi:account-outline`, + AccountAlert: `mdi:account-alert-outline`, AccountCheck: `mdi:account-check-outline`, AccountCircle: `mdi:account-circle-outline`, AccountClock: `mdi:account-clock-outline`, @@ -505,11 +584,15 @@ export const iconify_icons = { AccountSwitch: `mdi:account-switch-outline`, AccountVoice: `mdi:account-voice`, AddressBook: `mdi:contacts-outline`, + Baby: `mdi:baby-face-outline`, BadgeAccount: `mdi:badge-account-outline`, Briefcase: `mdi:briefcase-outline`, Contact: `lucide:contact`, Hand: `lucide:hand`, + HandClap: `mdi:hand-clap`, + HandHeart: `mdi:hand-heart-outline`, Handshake: `mdi:handshake-outline`, + HandWave: `mdi:hand-wave-outline`, HelpingHand: `lucide:hand-helping`, HumanGreeting: `mdi:human-greeting`, HumanMaleFemale: `mdi:human-male-female`, @@ -535,6 +618,7 @@ export const iconify_icons = { Email: `mdi:email`, EmailCheck: `mdi:email-check-outline`, EmailOpen: `mdi:email-open-outline`, // opened envelope; closed is custom.ts Email + EmailOutline: `mdi:email-outline`, EmailPlus: `mdi:email-plus-outline`, EmailSend: `mdi:email-send-outline`, Forum: `mdi:forum-outline`, @@ -609,7 +693,9 @@ export const iconify_icons = { AngleRight: `mdi:angle-right`, ApproxEqual: `mdi:approximately-equal`, Atom: `mdi:atom`, + Bacteria: `mdi:bacteria-outline`, Binary: `lucide:binary`, + Biohazard: `mdi:biohazard`, Boxes: `lucide:boxes`, ChartArc: `mdi:chart-arc`, ChartArea: `mdi:chart-areaspline`, @@ -625,6 +711,7 @@ export const iconify_icons = { ChartGantt: `mdi:chart-gantt`, ChartHistogram: `mdi:chart-histogram`, ChartLine: `mdi:chart-line`, + ChartLineStacked: `mdi:chart-line-stacked`, ChartLineVariant: `mdi:chart-line-variant`, ChartMultiline: `mdi:chart-multiline`, ChartNetwork: `lucide:chart-network`, @@ -633,6 +720,7 @@ export const iconify_icons = { ChartSankey: `mdi:chart-sankey`, ChartScatter: `mdi:chart-scatter-plot`, ChartScatterHexbin: `mdi:chart-scatter-plot-hexbin`, + ChartSpline: `lucide:chart-spline`, ChartTimeline: `mdi:chart-timeline-variant`, ChartTree: `mdi:chart-tree`, ChartWaterfall: `mdi:chart-waterfall`, @@ -640,18 +728,22 @@ export const iconify_icons = { Counter: `mdi:counter`, Cube: `mdi:cube-outline`, CubeScan: `mdi:cube-scan`, + CurrentAC: `mdi:current-ac`, Cylinder: `mdi:cylinder`, + DatabaseAlert: `mdi:database-alert-outline`, DatabaseCheck: `mdi:database-check-outline`, DatabaseCog: `mdi:database-cog-outline`, DatabaseExport: `mdi:database-export-outline`, DatabaseEye: `mdi:database-eye-outline`, DatabaseImport: `mdi:database-import-outline`, DatabaseLock: `mdi:database-lock-outline`, + DatabaseOutline: `mdi:database-outline`, DatabasePlus: `mdi:database-plus-outline`, DatabaseRemove: `mdi:database-remove-outline`, DatabaseSearch: `mdi:database-search-outline`, DatabaseSync: `mdi:database-sync-outline`, DatabaseZap: `lucide:database-zap`, + Decimal: `mdi:decimal`, Delta: `mdi:delta`, Diffraction: `mdi:sine-wave`, Divide: `mdi:division`, @@ -659,15 +751,20 @@ export const iconify_icons = { Equal: `mdi:equal`, Flask: `mdi:flask`, FlaskOff: `mdi:flask-off`, + FlaskRoundBottom: `mdi:flask-round-bottom-outline`, Function: `mdi:function-variant`, Gas: `mdi:gas-cylinder`, + Gauge: `mdi:gauge`, Graph: `mdi:graph`, GraphOutline: `mdi:graph-outline`, GreaterThan: `mdi:greater-than`, + Grid: `mdi:grid`, + GridOff: `mdi:grid-off`, Hexagon: `mdi:hexagon-outline`, Infinite: `mdi:infinity`, // not Infinity (shadows global) Integral: `mdi:math-integral`, Kelvin: `mdi:temperature-kelvin`, + Lambda: `mdi:lambda`, LessThan: `mdi:less-than`, Liquid: `mdi:water-outline`, Magnet: `mdi:magnet`, @@ -679,11 +776,24 @@ export const iconify_icons = { MathTan: `mdi:math-tan`, Matrix: `mdi:matrix`, Memory: `mdi:memory`, + MeterElectric: `mdi:meter-electric-outline`, Microscope: `mdi:microscope`, Molecule: `mdi:molecule`, + MoleculeCO2: `mdi:molecule-co2`, Multiply: `mdi:multiplication`, NotEqual: `mdi:not-equal-variant`, + Numeric0: `mdi:numeric-0`, + Numeric1: `mdi:numeric-1`, + Numeric2: `mdi:numeric-2`, + Numeric3: `mdi:numeric-3`, + Numeric4: `mdi:numeric-4`, + Numeric5: `mdi:numeric-5`, + Numeric6: `mdi:numeric-6`, + Numeric7: `mdi:numeric-7`, + Numeric8: `mdi:numeric-8`, + Numeric9: `mdi:numeric-9`, Octagon: `mdi:octagon-outline`, + Omega: `mdi:omega`, Orbit: `mdi:orbit`, Pentagon: `mdi:pentagon-outline`, PeriodicTable: `mdi:periodic-table`, @@ -691,13 +801,19 @@ export const iconify_icons = { PlusMinus: `mdi:plus-minus-variant`, Polymer: `mdi:polymer`, Pulse: `mdi:pulse`, + Radar: `mdi:radar`, Radioactive: `mdi:radioactive`, Rhombus: `mdi:rhombus-outline`, + Rotate3D: `mdi:rotate-3d`, Rows: `mdi:table-row`, Ruler: `mdi:ruler`, + RulerDimensionLine: `lucide:ruler-dimension-line`, RulerSquareCompass: `mdi:ruler-square-compass`, Satellite: `mdi:satellite-variant`, + SawtoothWave: `mdi:sawtooth-wave`, Scale: `mdi:scale`, + ScaleBalance: `mdi:scale-balance`, + ScaleUnbalanced: `mdi:scale-unbalanced`, SetCenter: `mdi:set-center`, SetLeft: `mdi:set-left`, SetNone: `mdi:set-none`, @@ -707,7 +823,10 @@ export const iconify_icons = { Sphere: `mdi:sphere`, Square: `mdi:square-outline`, SquareRoot: `mdi:square-root`, + SquareWave: `mdi:square-wave`, + Symbol: `mdi:symbol`, Table: `mdi:table`, + TableColumn: `mdi:table-column`, TableColumnPlus: `mdi:table-column-plus-after`, TableColumnRemove: `mdi:table-column-remove`, TableColumnWidth: `mdi:table-column-width`, @@ -718,6 +837,8 @@ export const iconify_icons = { TableImport: `mdi:table-import`, TableLarge: `mdi:table-large`, TableMerge: `mdi:table-merge-cells`, + TableMultiple: `mdi:table-multiple`, + TablePivot: `mdi:table-pivot`, TablePlus: `mdi:table-plus`, TableRemove: `mdi:table-remove`, TableRowHeight: `mdi:table-row-height`, @@ -725,38 +846,57 @@ export const iconify_icons = { TableRowRemove: `mdi:table-row-remove`, TableSearch: `mdi:table-search`, TableSettings: `mdi:table-settings`, + TableSplitCell: `mdi:table-split-cell`, TableSync: `mdi:table-sync`, Tag: `mdi:tag-outline`, + TagPlus: `mdi:tag-plus-outline`, Telescope: `mdi:telescope`, TestTube: `mdi:test-tube`, + TestTubeEmpty: `mdi:test-tube-empty`, Thermometer: `mdi:thermometer`, + ThermometerHigh: `mdi:thermometer-high`, + ThermometerLow: `mdi:thermometer-low`, TrendingDown: `mdi:trending-down`, TrendingFlat: `mdi:trending-neutral`, TrendingUp: `mdi:trending-up`, + TriangleWave: `mdi:triangle-wave`, Variable: `mdi:variable`, VectorBezier: `mdi:vector-bezier`, VectorCircle: `mdi:vector-circle`, + VectorCombine: `mdi:vector-combine`, + VectorDifference: `mdi:vector-difference`, + VectorIntersection: `mdi:vector-intersection`, VectorLine: `mdi:vector-line`, VectorPoint: `mdi:vector-point`, VectorPolygon: `mdi:vector-polygon`, VectorPolyline: `mdi:vector-polyline`, VectorSquare: `mdi:vector-square`, + VectorUnion: `mdi:vector-union`, + Virus: `mdi:virus-outline`, Waves: `mdi:waves`, Weight: `mdi:weight`, + WeightKilogram: `mdi:weight-kilogram`, XRay: `mdi:radiology-box-outline`, // dev ABTesting: `mdi:ab-testing`, + Ampersand: `lucide:ampersand`, API: `mdi:api`, APIOff: `mdi:api-off`, ApplicationBrackets: `mdi:application-brackets-outline`, + ApplicationCog: `mdi:application-cog-outline`, AxisArrow: `mdi:axis-arrow`, Bash: `mdi:bash`, Benchmark: `mdi:speedometer-medium`, + Blocks: `lucide:blocks`, + BotMessageSquare: `lucide:bot-message-square`, Breakpoint: `mdi:record-circle`, Bug: `mdi:bug-outline`, BugCheck: `mdi:bug-check-outline`, + BugOff: `lucide:bug-off`, + BugPlay: `lucide:bug-play`, Cable: `lucide:cable`, Chip: `mdi:chip`, + CircuitBoard: `mdi:developer-board`, CloudBraces: `mdi:cloud-braces`, CloudPrint: `mdi:cloud-print-outline`, Cluster: `mdi:server-network`, @@ -766,39 +906,53 @@ export const iconify_icons = { CodeBrackets: `mdi:code-brackets`, CodeEqual: `mdi:code-equal`, CodeGreaterThan: `mdi:code-greater-than`, + CodeNotEqual: `mdi:code-not-equal`, CodeParentheses: `mdi:code-parentheses`, CodeString: `mdi:code-string`, CodeTags: `mdi:code-tags`, CodeTagsCheck: `mdi:code-tags-check`, + CogOff: `mdi:cog-off-outline`, CogOutline: `mdi:cog-outline`, CogRefresh: `mdi:cog-refresh-outline`, + Cogs: `mdi:cogs`, + CogSync: `mdi:cog-sync-outline`, Console: `mdi:console`, ConsoleLine: `mdi:console-line`, Container: `lucide:container`, CPU: `mdi:cpu-64-bit`, Cron: `mdi:calendar-sync-outline`, Debug: `mdi:debug-step-over`, + DNS: `mdi:dns-outline`, Docker: `simple-icons:docker`, EthernetPort: `lucide:ethernet-port`, GitBranch: `octicon:git-branch`, GitCommit: `octicon:git-commit`, GitCompare: `octicon:git-compare`, + GitGraph: `lucide:git-graph`, GitMerge: `octicon:git-merge`, GPU: `mdi:expansion-card`, IterationCcw: `lucide:iteration-ccw`, + IterationCw: `lucide:iteration-cw`, + Lan: `mdi:lan`, + LanCheck: `mdi:lan-check`, LanDisconnect: `mdi:lan-disconnect`, + LightningOff: `lucide:zap-off`, Lint: `mdi:broom`, ListTree: `lucide:list-tree`, Log: `mdi:text-box-search-outline`, Metrics: `mdi:chart-multiple`, Network: `mdi:lan-connect`, NetworkOff: `mdi:network-off-outline`, + NetworkOutline: `mdi:network-outline`, Package: `mdi:package-variant-closed`, PackageCheck: `mdi:package-check`, PackageDown: `mdi:package-down`, + PackageMinus: `mdi:package-variant-closed-minus`, PackageOpen: `lucide:package-open`, + PackagePlus: `mdi:package-variant-closed-plus`, PackageSearch: `lucide:package-search`, PackageUp: `mdi:package-up`, + PackageVariant: `mdi:package-variant`, Pipeline: `mdi:pipe`, Plugin: `mdi:toy-brick-outline`, Profiler: `mdi:chart-timeline-variant-shimmer`, @@ -810,18 +964,25 @@ export const iconify_icons = { RelationManyToMany: `mdi:relation-many-to-many`, RelationOneToMany: `mdi:relation-one-to-many`, Robot: `mdi:robot-outline`, + RobotOff: `mdi:robot-off-outline`, Rocket: `mdi:rocket-launch-outline`, Router: `mdi:router-wireless`, + RouterWirelessOff: `mdi:router-wireless-off`, + Script: `mdi:script-outline`, + ScriptText: `mdi:script-text-outline`, ServerCog: `lucide:server-cog`, ServerOff: `mdi:server-off`, ServerPlus: `mdi:server-plus`, + ServerSecurity: `mdi:server-security`, Settings: `mdi:settings`, Sitemap: `mdi:sitemap-outline`, SourceBranch: `mdi:source-branch`, SourceBranchCheck: `mdi:source-branch-check`, SourceBranchPlus: `mdi:source-branch-plus`, SourceBranchRemove: `mdi:source-branch-remove`, + SourceBranchSync: `mdi:source-branch-sync`, SourceCommit: `mdi:source-commit`, + SourceCommitLocal: `mdi:source-commit-local`, SourceFork: `mdi:source-fork`, SourceMerge: `mdi:source-merge`, SourcePull: `mdi:source-pull`, @@ -832,67 +993,110 @@ export const iconify_icons = { StepOut: `mdi:debug-step-out`, TagMultiple: `mdi:tag-multiple-outline`, Terminal: `mdi:terminal`, + TestTubes: `lucide:test-tubes`, Timeline: `mdi:timeline-outline`, Tools: `mdi:tools`, + Transfer: `mdi:transfer`, + TransitConnectionVariant: `mdi:transit-connection-variant`, VariableBox: `mdi:variable-box`, Version: `carbon:version`, Versions: `octicon:versions-16`, Webhook: `mdi:webhook`, WebhookOff: `lucide:webhook-off`, + WebRefresh: `mdi:web-refresh`, + WebSync: `mdi:web-sync`, Workflow: `octicon:workflow-16`, Wrench: `mdi:wrench-outline`, + WrenchCheck: `mdi:wrench-check-outline`, XML: `mdi:xml`, // brands: langs & tools + AmazonS3: `simple-icons:amazons3`, Anaconda: `simple-icons:anaconda`, + Android: `simple-icons:android`, Angular: `simple-icons:angular`, + ApacheSpark: `simple-icons:apachespark`, + ApolloGraphQL: `simple-icons:apollographql`, Apple: `simple-icons:apple`, + ArchLinux: `simple-icons:archlinux`, Arduino: `simple-icons:arduino`, Astro: `simple-icons:astro`, AWS: `simple-icons:amazonwebservices`, + AWSLambda: `simple-icons:awslambda`, Babel: `simple-icons:babel`, Biome: `simple-icons:biome`, Bitbucket: `simple-icons:bitbucket`, + Blender: `simple-icons:blender`, + Bootstrap: `simple-icons:bootstrap`, Bun: `simple-icons:bun`, // not `C`, which would be a single-letter export CLang: `simple-icons:c`, Claude: `simple-icons:claude`, ClaudeCode: `simple-icons:claudecode`, + ClickHouse: `simple-icons:clickhouse`, Cloudflare: `simple-icons:cloudflare`, Codecov: `simple-icons:codecov`, + CPlusPlus: `simple-icons:cplusplus`, + CSharp: `simple-icons:csharp`, CSS: `simple-icons:css3`, Curl: `simple-icons:curl`, CursorIDE: `simple-icons:cursor`, + D3: `simple-icons:d3`, Dart: `simple-icons:dart`, Dask: `simple-icons:dask`, + Databricks: `simple-icons:databricks`, + Debian: `simple-icons:debian`, DeepSeek: `simple-icons:deepseek`, Deno: `simple-icons:deno`, DigitalOcean: `simple-icons:digitalocean`, Django: `simple-icons:django`, + DotNet: `simple-icons:dotnet`, + Dropbox: `simple-icons:dropbox`, + DuckDB: `simple-icons:duckdb`, Elasticsearch: `simple-icons:elasticsearch`, Electron: `simple-icons:electron`, ESLint: `simple-icons:eslint`, + Excalidraw: `simple-icons:excalidraw`, FastAPI: `simple-icons:fastapi`, + Fedora: `simple-icons:fedora`, Figma: `simple-icons:figma`, Firebase: `simple-icons:firebase`, Flutter: `simple-icons:flutter`, + GhostCMS: `simple-icons:ghost`, Git: `simple-icons:git`, GitHub: `simple-icons:github`, GitHubActions: `simple-icons:githubactions`, GitHubCopilot: `simple-icons:githubcopilot`, GitLab: `simple-icons:gitlab`, + Godot: `simple-icons:godotengine`, GoLang: `simple-icons:go`, + Google: `simple-icons:google`, + GoogleAnalytics: `simple-icons:googleanalytics`, + GoogleBigQuery: `simple-icons:googlebigquery`, + GoogleChrome: `simple-icons:googlechrome`, GoogleCloud: `simple-icons:googlecloud`, + GoogleColab: `simple-icons:googlecolab`, + GoogleDrive: `simple-icons:googledrive`, GoogleGemini: `simple-icons:googlegemini`, + GoogleMaps: `simple-icons:googlemaps`, + GoogleSheets: `simple-icons:googlesheets`, Grafana: `simple-icons:grafana`, GraphQL: `simple-icons:graphql`, + Heroku: `simple-icons:heroku`, Homebrew: `simple-icons:homebrew`, HTML5: `simple-icons:html5`, + Hugo: `simple-icons:hugo`, + Intel: `simple-icons:intel`, + IntelliJ: `simple-icons:intellijidea`, + IOS: `simple-icons:ios`, Java: `simple-icons:openjdk`, JavaScript: `simple-icons:javascript`, Jenkins: `simple-icons:jenkins`, Jest: `simple-icons:jest`, + JetBrains: `simple-icons:jetbrains`, + Jira: `simple-icons:jira`, Julia: `simple-icons:julia`, Jupyter: `simple-icons:jupyter`, + Kafka: `simple-icons:apachekafka`, Kaggle: `simple-icons:kaggle`, Keras: `simple-icons:keras`, Kotlin: `simple-icons:kotlin`, @@ -901,25 +1105,35 @@ export const iconify_icons = { Linux: `simple-icons:linux`, Lit: `simple-icons:lit`, Lua: `simple-icons:lua`, + MacOS: `simple-icons:macos`, Markdown: `simple-icons:markdown`, Mathematica: `simple-icons:wolframmathematica`, MCP: `simple-icons:modelcontextprotocol`, Mermaid: `simple-icons:mermaid`, Microsoft: `simple-icons:microsoft`, MicrosoftAzure: `simple-icons:microsoftazure`, + MicrosoftExcel: `simple-icons:microsoftexcel`, + MicrosoftSQLServer: `simple-icons:microsoftsqlserver`, + MicrosoftTeams: `simple-icons:microsoftteams`, + MicrosoftWord: `simple-icons:microsoftword`, MistralAI: `simple-icons:mistralai`, MongoDB: `simple-icons:mongodb`, MySQL: `simple-icons:mysql`, + Neo4j: `simple-icons:neo4j`, + Neovim: `simple-icons:neovim`, Netlify: `simple-icons:netlify`, NextJs: `simple-icons:nextdotjs`, NGINX: `simple-icons:nginx`, + NixOS: `simple-icons:nixos`, NodeJs: `simple-icons:nodedotjs`, NPM: `simple-icons:npm`, NumPy: `simple-icons:numpy`, Nuxt: `simple-icons:nuxt`, + NVIDIA: `simple-icons:nvidia`, Observable: `simple-icons:observable`, Ollama: `simple-icons:ollama`, OpenAI: `simple-icons:openai`, + OpenAPI: `simple-icons:openapiinitiative`, OpenRouter: `simple-icons:openrouter`, OpenStreetMap: `simple-icons:openstreetmap`, Pandas: `simple-icons:pandas`, @@ -933,9 +1147,12 @@ export const iconify_icons = { Poetry: `simple-icons:poetry`, Polars: `simple-icons:polars`, PostgreSQL: `simple-icons:postgresql`, + Postman: `simple-icons:postman`, + PowerShell: `simple-icons:powershell`, Prettier: `simple-icons:prettier`, Prisma: `simple-icons:prisma`, Prometheus: `simple-icons:prometheus`, + PyCharm: `simple-icons:pycharm`, Pydantic: `simple-icons:pydantic`, PyPI: `simple-icons:pypi`, Pytest: `simple-icons:pytest`, @@ -944,8 +1161,10 @@ export const iconify_icons = { Quarto: `simple-icons:quarto`, Qwik: `simple-icons:qwik`, RabbitMQ: `simple-icons:rabbitmq`, + Railway: `simple-icons:railway`, RaspberryPi: `simple-icons:raspberrypi`, React: `simple-icons:react`, + RedHat: `simple-icons:redhat`, Redis: `simple-icons:redis`, Remix: `simple-icons:remix`, RLang: `simple-icons:r`, @@ -953,10 +1172,13 @@ export const iconify_icons = { Ruby: `simple-icons:ruby`, Ruff: `simple-icons:ruff`, Rust: `simple-icons:rust`, + Sass: `simple-icons:sass`, ScikitLearn: `simple-icons:scikitlearn`, SciPy: `simple-icons:scipy`, Sentry: `simple-icons:sentry`, ShadcnUI: `simple-icons:shadcnui`, + SnowflakeDB: `simple-icons:snowflake`, + SocketIO: `simple-icons:socketdotio`, SolidJS: `simple-icons:solid`, SQLAlchemy: `simple-icons:sqlalchemy`, SQLite: `simple-icons:sqlite`, @@ -973,87 +1195,136 @@ export const iconify_icons = { Turborepo: `simple-icons:turborepo`, TypeScript: `simple-icons:typescript`, Ubuntu: `simple-icons:ubuntu`, + Unity: `simple-icons:unity`, + UnrealEngine: `simple-icons:unrealengine`, UV: `simple-icons:uv`, Vercel: `simple-icons:vercel`, + Vim: `simple-icons:vim`, Vite: `simple-icons:vite`, Vitest: `simple-icons:vitest`, VSCode: `simple-icons:visualstudiocode`, Vue: `simple-icons:vuedotjs`, Wasm: `simple-icons:webassembly`, Webpack: `simple-icons:webpack`, + Windows: `simple-icons:windows`, + WordPress: `simple-icons:wordpress`, + Xcode: `simple-icons:xcode`, Yarn: `simple-icons:yarn`, + Zapier: `simple-icons:zapier`, Zig: `simple-icons:zig`, Zod: `simple-icons:zod`, // brands: social + Amazon: `simple-icons:amazon`, Anthropic: `simple-icons:anthropic`, + AppleMusic: `simple-icons:applemusic`, Behance: `simple-icons:behance`, + Bitwarden: `simple-icons:bitwarden`, Bluesky: `simple-icons:bluesky`, + Canva: `simple-icons:canva`, CodePen: `simple-icons:codepen`, DevTo: `simple-icons:devdotto`, Discord: `simple-icons:discord`, Dribbble: `simple-icons:dribbble`, Facebook: `simple-icons:facebook`, + Gmail: `simple-icons:gmail`, + GoogleCalendar: `simple-icons:googlecalendar`, + GooglePlay: `simple-icons:googleplay`, Instagram: `simple-icons:instagram`, Linear: `simple-icons:linear`, LinkedIn: `simple-icons:linkedin`, Mastodon: `simple-icons:mastodon`, Medium: `simple-icons:medium`, Meta: `simple-icons:meta`, + Netflix: `simple-icons:netflix`, Notion: `simple-icons:notion`, Obsidian: `simple-icons:obsidian`, + OnePassword: `simple-icons:1password`, + PayPal: `simple-icons:paypal`, Pinterest: `simple-icons:pinterest`, Reddit: `simple-icons:reddit`, + Shopify: `simple-icons:shopify`, SignalApp: `simple-icons:signal`, Slack: `simple-icons:slack`, Spotify: `simple-icons:spotify`, StackBlitz: `simple-icons:stackblitz`, StackOverflow: `simple-icons:stackoverflow`, + Steam: `simple-icons:steam`, + Stripe: `simple-icons:stripe`, Telegram: `simple-icons:telegram`, Threads: `simple-icons:threads`, TikTok: `simple-icons:tiktok`, Twitch: `simple-icons:twitch`, Twitter: `fa-brands:twitter`, + Vimeo: `simple-icons:vimeo`, + VLC: `simple-icons:vlcmediaplayer`, WhatsApp: `simple-icons:whatsapp`, + Wikipedia: `simple-icons:wikipedia`, X: `simple-icons:x`, // X logo; Twitter keeps the bird YouTube: `simple-icons:youtube`, // publishing & academia + ACM: `simple-icons:acm`, Article: `ri:article-line`, ArXiv: `simple-icons:arxiv`, + BioRxiv: `academicons:biorxiv`, Book: `mdi:book-open-page-variant-outline`, + BookOpen: `mdi:book-open-outline`, + BookPlus: `mdi:book-plus-outline`, + BookSearch: `mdi:book-search-outline`, Certificate: `mdi:certificate-outline`, Citation: `ri:quote-text`, + ClosedAccess: `academicons:closed-access`, Crossref: `academicons:crossref`, CvSquare: `academicons:cv-square`, + DataCite: `academicons:datacite`, + Dataverse: `simple-icons:dataverse`, + DBLP: `academicons:dblp`, DOI: `academicons:doi`, Education: `zondicons:education`, Elsevier: `simple-icons:elsevier`, Figshare: `simple-icons:figshare`, GoogleScholar: `academicons:google-scholar`, + GraduationCap: `lucide:graduation-cap`, HuggingFace: `simple-icons:huggingface`, + IEEE: `simple-icons:ieee`, Interests: `material-symbols:interests`, + InternetArchive: `simple-icons:internetarchive`, Journal: `iconoir:journal`, + JSTOR: `academicons:jstor`, Languages: `lucide:languages`, LaTeX: `simple-icons:latex`, LaTeXFile: `file-icons:latex`, Library: `mdi:bookshelf`, + MathOverflow: `academicons:mathoverflow`, + Mendeley: `simple-icons:mendeley`, + NASA: `simple-icons:nasa`, + NASAADS: `academicons:ads`, Newspaper: `mdi:newspaper`, Note: `mdi:note-outline`, OpenAccess: `simple-icons:openaccess`, + OpenData: `academicons:open-data`, OpenSource: `ri:open-source-line`, ORCID: `academicons:orcid`, + OSF: `simple-icons:osf`, Overleaf: `simple-icons:overleaf`, People: `octicon:people-16`, Presentation: `mdi:presentation`, Project: `octicon:project`, PubMed: `simple-icons:pubmed`, + PubPeer: `academicons:pubpeer`, Quote: `octicon:quote`, ResearchGate: `academicons:researchgate`, + ROR: `academicons:ror`, + Scopus: `simple-icons:scopus`, ScrollText: `lucide:scroll-text`, SearchCountry: `gis:search-country`, SemanticScholar: `academicons:semantic-scholar`, SkillLevel: `carbon:skill-level-advanced`, + Springer: `academicons:springer`, + SSRN: `simple-icons:ssrn`, Sunglasses: `mdi:sunglasses`, Typst: `simple-icons:typst`, + Wikidata: `simple-icons:wikidata`, + Wiley: `academicons:wiley`, Zenodo: `academicons:zenodo`, Zoom: `simple-icons:zoom`, Zotero: `simple-icons:zotero`, @@ -1062,6 +1333,8 @@ export const iconify_icons = { AlignEndHorizontal: `lucide:align-end-horizontal`, AlignEndVertical: `lucide:align-end-vertical`, AlignHorizontalCenter: `mdi:align-horizontal-center`, + AlignHorizontalLeft: `mdi:align-horizontal-left`, + AlignHorizontalRight: `mdi:align-horizontal-right`, AlignStartHorizontal: `lucide:align-start-horizontal`, AlignStartVertical: `lucide:align-start-vertical`, AlignTop: `mdi:align-vertical-top`, @@ -1069,50 +1342,72 @@ export const iconify_icons = { BetweenHorizontalEnd: `lucide:between-horizontal-end`, BetweenHorizontalStart: `lucide:between-horizontal-start`, Blur: `mdi:blur`, + BlurOff: `mdi:blur-off`, BorderAll: `mdi:border-all`, + BorderColor: `mdi:border-color`, BorderNone: `mdi:border-none-variant`, + BorderRadius: `mdi:border-radius`, BoxShadow: `mdi:box-shadow`, Brightness: `mdi:brightness-6`, BringForward: `mdi:arrange-bring-forward`, BringToFront: `lucide:bring-to-front`, Brush: `mdi:brush`, + BrushVariant: `mdi:brush-variant`, ColorPalette: `carbon:color-palette`, Combine: `lucide:combine`, Contrast: `mdi:contrast-circle`, Crop: `mdi:crop`, + CropRotate: `mdi:crop-rotate`, DistributeHorizontal: `mdi:align-horizontal-distribute`, DistributeVertical: `mdi:align-vertical-distribute`, + Drawing: `mdi:drawing`, + Ellipse: `mdi:ellipse-outline`, Eye: `mdi:eye`, Eyedropper: `mdi:eyedropper`, EyeOff: `mdi:eye-off`, FlipHorizontal: `mdi:flip-horizontal`, FlipVertical: `mdi:flip-vertical`, Focus: `lucide:focus`, + FormatPaint: `mdi:format-paint`, Gradient: `mdi:gradient-vertical`, GradientHorizontal: `mdi:gradient-horizontal`, Group: `mdi:group`, + ImageFilterBlackWhite: `mdi:image-filter-black-white`, ImageFilterCenterFocus: `mdi:image-filter-center-focus`, ImageFrame: `mdi:image-frame`, ImageOff: `mdi:image-off-outline`, + InvertColors: `mdi:invert-colors`, + InvertColorsOff: `mdi:invert-colors-off`, Layers: `mdi:layers-outline`, + LayersEdit: `mdi:layers-edit`, LayersOff: `mdi:layers-off-outline`, LayersPlus: `mdi:layers-plus`, + MirrorRectangle: `mdi:mirror-rectangle`, Move3D: `lucide:move-3d`, NoImage: `carbon:no-image`, Opacity: `mdi:opacity`, PaintBucket: `lucide:paint-bucket`, + PaletteSwatch: `mdi:palette-swatch-outline`, Panorama: `mdi:panorama-outline`, Proportions: `lucide:proportions`, Radius: `mdi:radius-outline`, + RectangleHorizontal: `lucide:rectangle-horizontal`, + RectangleVertical: `lucide:rectangle-vertical`, RotateLeft: `mdi:rotate-left`, RotateRight: `mdi:rotate-right`, + Scaling: `lucide:scaling`, SendBackward: `mdi:arrange-send-backward`, SendToBack: `lucide:send-to-back`, Shape: `mdi:shape-outline`, + ShapePlus: `mdi:shape-plus-outline`, Skew: `mdi:skew-less`, + Swatchbook: `lucide:swatch-book`, + TapeMeasure: `mdi:tape-measure`, Texture: `mdi:texture-box`, + Tint: `mdi:water`, Transition: `mdi:transition`, Ungroup: `mdi:ungroup`, + VectorPen: `lucide:pen-tool`, WandSparkles: `lucide:wand-sparkles`, ZoomIn: `mdi:zoom-in`, ZoomOut: `mdi:zoom-out`, @@ -1128,20 +1423,34 @@ export const iconify_icons = { SortNumeric: `mdi:sort-numeric-ascending`, SortVariant: `mdi:sort-variant`, // security + Cctv: `mdi:cctv`, + EyeOffOutline: `mdi:eye-off-outline`, + EyeOutline: `mdi:eye-outline`, Fingerprint: `mdi:fingerprint`, Firewall: `mdi:wall-fire`, Incognito: `mdi:incognito`, Key: `mdi:key`, KeyAlert: `mdi:key-alert-outline`, + KeyChain: `mdi:key-chain`, KeyChange: `mdi:key-change`, + KeyOutline: `mdi:key-outline`, KeyVariant: `mdi:key-variant`, Lock: `mdi:lock`, + LockAlert: `mdi:lock-alert-outline`, + LockCheck: `mdi:lock-check-outline`, + LockOff: `mdi:lock-off-outline`, LockOpen: `mdi:lock-open-variant-outline`, + LockOutline: `mdi:lock-outline`, + LockQuestion: `mdi:lock-question`, LockReset: `mdi:lock-reset`, Password: `mdi:form-textbox-password`, ScanFace: `lucide:scan-face`, + Security: `mdi:security`, ShieldAccount: `mdi:shield-account-outline`, + ShieldBug: `mdi:shield-bug-outline`, + ShieldHome: `mdi:shield-home-outline`, ShieldKey: `mdi:shield-key-outline`, + ShieldLockOpen: `mdi:shield-lock-open-outline`, ShieldOff: `mdi:shield-off-outline`, ShieldQuestion: `material-symbols:shield-question-outline`, ShieldRefresh: `mdi:shield-refresh-outline`, @@ -1153,29 +1462,53 @@ export const iconify_icons = { WebOff: `mdi:web-off`, // commerce BadgeDollarSign: `lucide:badge-dollar-sign`, + BadgePercent: `lucide:badge-percent`, Bank: `mdi:bank-outline`, + BankTransfer: `mdi:bank-transfer`, Barcode: `mdi:barcode`, + BarcodeScan: `mdi:barcode-scan`, + Basket: `mdi:basket-outline`, Calculator: `mdi:calculator-variant-outline`, Cart: `mdi:cart-outline`, CartCheck: `mdi:cart-check`, + CartPlus: `mdi:cart-plus`, Cash: `mdi:cash`, + Cash100: `mdi:cash-100`, CashMultiple: `mdi:cash-multiple`, + CashPlus: `mdi:cash-plus`, CashRegister: `mdi:cash-register`, + Coins: `lucide:coins`, Coupon: `mdi:ticket-percent-outline`, CreditCard: `mdi:credit-card-outline`, CreditCardCheck: `mdi:credit-card-check-outline`, + CreditCardMultiple: `mdi:credit-card-multiple-outline`, + CreditCardOff: `mdi:credit-card-off-outline`, + CreditCardRefund: `mdi:credit-card-refund-outline`, + CreditCardWireless: `mdi:credit-card-wireless-outline`, Currency: `mdi:currency-usd`, CurrencyBitcoin: `mdi:currency-btc`, + CurrencyEthereum: `mdi:currency-eth`, CurrencyEuro: `mdi:currency-eur`, CurrencyPound: `mdi:currency-gbp`, + CurrencyRuble: `mdi:currency-rub`, + CurrencyRupee: `mdi:currency-inr`, + CurrencyYen: `mdi:currency-jpy`, + CurrencyYuan: `mdi:currency-cny`, + Finance: `mdi:finance`, Gift: `mdi:gift-outline`, + GiftOpen: `mdi:gift-open-outline`, HandCoins: `lucide:hand-coins`, + Invoice: `mdi:invoice-text-outline`, + InvoiceList: `mdi:invoice-list-outline`, Percent: `mdi:percent-outline`, + PiggyBank: `mdi:piggy-bank-outline`, + PointOfSale: `mdi:point-of-sale`, QRCode: `mdi:qrcode`, QRCodeScan: `mdi:qrcode-scan`, Receipt: `mdi:receipt-text-outline`, ReceiptCheck: `mdi:receipt-text-check-outline`, Refund: `mdi:cash-refund`, + Safe: `mdi:safe`, Sale: `mdi:sale-outline`, Shopping: `mdi:shopping-outline`, ShoppingBag: `lucide:shopping-bag`, @@ -1183,17 +1516,24 @@ export const iconify_icons = { Store: `mdi:store-outline`, Storefront: `mdi:storefront-outline`, Ticket: `mdi:ticket-outline`, + TicketCheck: `lucide:ticket-check`, + TicketConfirmation: `mdi:ticket-confirmation-outline`, Tickets: `lucide:tickets`, + TruckCheck: `mdi:truck-check-outline`, TruckDelivery: `mdi:truck-delivery-outline`, + TruckFast: `mdi:truck-fast-outline`, Wallet: `mdi:wallet-outline`, WalletCards: `lucide:wallet-cards`, + WalletPlus: `mdi:wallet-plus-outline`, Warehouse: `mdi:warehouse`, // weather & nature Bee: `mdi:bee`, Bird: `mdi:bird`, + Butterfly: `mdi:butterfly-outline`, Cactus: `mdi:cactus`, Cat: `mdi:cat`, Cloudy: `mdi:weather-cloudy`, + Clover: `mdi:clover`, Dog: `mdi:dog`, Fish: `mdi:fish`, Fog: `mdi:weather-fog`, @@ -1202,42 +1542,70 @@ export const iconify_icons = { Humidity: `mdi:water-percent`, Hurricane: `mdi:weather-hurricane`, Leaf: `mdi:leaf`, + MoonFull: `mdi:moon-full`, + MoonNew: `mdi:moon-new`, Mountain: `mdi:image-filter-hdr`, Mushroom: `mdi:mushroom-outline`, + Owl: `mdi:owl`, PartlyCloudy: `mdi:weather-partly-cloudy`, + Paw: `mdi:paw-outline`, + PineTree: `mdi:pine-tree`, Pouring: `mdi:weather-pouring`, + Rabbit: `mdi:rabbit-variant-outline`, Rain: `mdi:weather-rainy`, + Rainbow: `lucide:rainbow`, Snowflake: `mdi:snowflake`, Sunset: `mdi:weather-sunset`, + Thunderstorm: `mdi:weather-lightning-rainy`, Tornado: `mdi:weather-tornado`, + Tsunami: `mdi:tsunami`, + Turtle: `mdi:turtle`, Volcano: `mdi:volcano-outline`, + WeatherHazy: `mdi:weather-hazy`, + WeatherLightning: `mdi:weather-lightning`, + WeatherNightPartlyCloudy: `mdi:weather-night-partly-cloudy`, + WeatherPartlyLightning: `mdi:weather-partly-lightning`, + WeatherPartlyRainy: `mdi:weather-partly-rainy`, + WeatherSnowy: `mdi:weather-snowy`, + Wheat: `lucide:wheat`, Wind: `mdi:weather-windy`, // activities + Badminton: `mdi:badminton`, Baseball: `mdi:baseball`, Basketball: `mdi:basketball`, Bike: `mdi:bike`, Bowling: `mdi:bowling`, + Boxing: `mdi:boxing-glove`, Campfire: `mdi:campfire`, Cards: `mdi:cards-outline`, Chess: `mdi:chess-knight`, // a peak with a summit flag; the only climbing-adjacent glyph in the installed sets Climbing: `material-symbols:mountain-flag-outline`, Coffee: `mdi:coffee-outline`, + Cricket: `mdi:cricket`, + Dance: `mdi:dance-ballroom`, Dice: `mdi:dice-multiple-outline`, Dumbbell: `mdi:dumbbell`, + Fishing: `mdi:hook`, Football: `mdi:football`, Golf: `mdi:golf`, Hiking: `mdi:hiking`, Hockey: `mdi:hockey-sticks`, Kayaking: `mdi:kayaking`, Meditation: `mdi:meditation`, + Parachute: `mdi:parachute-outline`, + Rugby: `mdi:rugby`, Run: `mdi:run`, + RunFast: `mdi:run-fast`, Skateboard: `mdi:skateboard`, Ski: `mdi:ski`, + Snowboard: `mdi:snowboard`, Soccer: `mdi:soccer`, Surfing: `mdi:surfing`, Swim: `mdi:swim`, + TableTennis: `mdi:table-tennis`, Tennis: `mdi:tennis`, + Tent: `lucide:tent`, Volleyball: `mdi:volleyball`, Walk: `mdi:walk`, WeightLifter: `mdi:weight-lifter`, @@ -1387,4 +1755,133 @@ export const iconify_icons = { WifiCog: `lucide:wifi-cog`, Window: `mdi:window-closed-variant`, WindTurbine: `mdi:wind-turbine`, + // transport + AirplaneLanding: `mdi:airplane-landing`, + AirplaneTakeoff: `mdi:airplane-takeoff`, + Ambulance: `mdi:ambulance`, + BusStop: `mdi:bus-stop`, + Caravan: `mdi:caravan`, + CarBattery: `mdi:car-battery`, + CarElectric: `mdi:car-electric-outline`, + CarEmergency: `mdi:car-emergency`, + CarKey: `mdi:car-key`, + CarSide: `mdi:car-side`, + CarWash: `mdi:car-wash`, + ChargingStation: `mdi:ev-station`, + Engine: `mdi:engine-outline`, + FireTruck: `mdi:fire-truck`, + Forklift: `mdi:forklift`, + Garage: `mdi:garage-open-variant`, + GasStation: `mdi:gas-station-outline`, + Gondola: `mdi:gondola`, + Helicopter: `mdi:helicopter`, + Highway: `mdi:highway`, + Moped: `mdi:moped`, + Motorbike: `mdi:motorbike`, + RocketShip: `mdi:rocket`, + Sailboat: `mdi:sail-boat`, + Scooter: `mdi:scooter`, + Seatbelt: `mdi:seatbelt`, + Steering: `mdi:steering`, + Submarine: `mdi:submarine`, + Subway: `mdi:subway-variant`, + Taxi: `mdi:taxi`, + Tire: `mdi:tire`, + Tractor: `mdi:tractor`, + TrafficCone: `mdi:traffic-cone`, + TrainCar: `mdi:train-car`, + Tram: `mdi:tram`, + Truck: `mdi:truck-outline`, + Tunnel: `mdi:tunnel-outline`, + UFO: `mdi:ufo-outline`, + Van: `mdi:van-utility`, + // maps & gis + Buffer: `gis:buffer`, + CadastreMap: `gis:cadastre-map`, + CompassRose: `mdi:compass-rose`, + ContourMap: `gis:contour-map`, + Crosshairs: `mdi:crosshairs`, + CrosshairsGPS: `mdi:crosshairs-gps`, + CrosshairsOff: `mdi:crosshairs-off`, + EarthArrowRight: `mdi:earth-arrow-right`, + EarthNetwork: `gis:earth-network`, + EarthPlus: `mdi:earth-plus`, + FlagFinish: `gis:flag-finish`, + FlagStart: `gis:flag-start`, + GlobeModel: `mdi:globe-model`, + Latitude: `mdi:latitude`, + LayerStack: `gis:layer-stack`, + Longitude: `mdi:longitude`, + MapCheck: `mdi:map-check-outline`, + MapLegend: `mdi:map-legend`, + MapMarkerAccount: `mdi:map-marker-account-outline`, + MapMarkerAlert: `mdi:map-marker-alert-outline`, + MapMarkerCheck: `mdi:map-marker-check-outline`, + MapMarkerCircle: `mdi:map-marker-circle`, + MapMarkerDistance: `mdi:map-marker-distance`, + MapMarkerMinus: `mdi:map-marker-minus-outline`, + MapMarkerMultiple: `mdi:map-marker-multiple-outline`, + MapMarkerOff: `mdi:map-marker-off-outline`, + MapMarkerPath: `mdi:map-marker-path`, + MapMarkerPlus: `mdi:map-marker-plus-outline`, + MapMarkerQuestion: `mdi:map-marker-question-outline`, + MapMarkerRadius: `mdi:map-marker-radius-outline`, + MapMarkerStar: `mdi:map-marker-star-outline`, + MapMinus: `mdi:map-minus`, + MapPlus: `mdi:map-plus`, + MapRoute: `gis:map-route`, + MapSearch: `mdi:map-search-outline`, + MeasureArea: `gis:measure-area`, + MeasureLine: `gis:measure-line`, + NorthArrow: `gis:north-arrow`, + POI: `gis:poi`, + PolygonPoint: `gis:polygon-pt`, + Position: `gis:position`, + ShapeFile: `gis:shape-file`, + StoryMap: `gis:story-map`, + // shapes & math + Alpha: `mdi:alpha`, + Axis: `mdi:axis`, + AxisXArrow: `mdi:axis-x-arrow`, + AxisYArrow: `mdi:axis-y-arrow`, + AxisZArrow: `mdi:axis-z-arrow`, + Beta: `mdi:beta`, + CardsClub: `mdi:cards-club-outline`, + CardsDiamond: `mdi:cards-diamond-outline`, + CardsHeart: `mdi:cards-heart-outline`, + CardsSpade: `mdi:cards-spade-outline`, + CircleDouble: `mdi:circle-double`, + Cuboid: `lucide:cuboid`, + Diameter: `lucide:diameter`, + Exponent: `mdi:exponent`, + Gamma: `mdi:gamma`, + GreaterThanOrEqual: `mdi:greater-than-or-equal`, + LessThanOrEqual: `mdi:less-than-or-equal`, + Octahedron: `mdi:octahedron`, + Pyramid: `mdi:pyramid`, + Radical: `lucide:radical`, + Rectangle: `mdi:rectangle-outline`, + RhombusSplit: `mdi:rhombus-split-outline`, + Shapes: `lucide:shapes`, + Spline: `lucide:spline`, + SquareDashed: `lucide:square-dashed`, + SquareRounded: `mdi:square-rounded-outline`, + Tangent: `lucide:tangent`, + Torus: `lucide:torus`, + // accessibility + AccessibilityNew: `material-symbols:accessibility-new`, + AccessibleForward: `material-symbols:accessible-forward`, + AudioDescription: `mdi:audio-video`, + BabyChangingTable: `mdi:human-baby-changing-table`, + Blind: `material-symbols:blind`, + ClosedCaptionDisabled: `material-symbols:closed-caption-disabled-outline`, + EarHearingLoop: `mdi:ear-hearing-loop`, + EarOff: `lucide:ear-off`, + EyePlus: `mdi:eye-plus-outline`, + FontSizeDecrease: `mdi:format-font-size-decrease`, + FontSizeIncrease: `mdi:format-font-size-increase`, + HearingDisabled: `material-symbols:hearing-disabled`, + HumanCane: `mdi:human-cane`, + HumanWhiteCane: `mdi:human-white-cane`, + PersonStanding: `lucide:person-standing`, } as const diff --git a/src/lib/CodeExample.svelte b/src/lib/CodeExample.svelte index eb8d6c5a..5adbc57f 100644 --- a/src/lib/CodeExample.svelte +++ b/src/lib/CodeExample.svelte @@ -121,6 +121,8 @@ flex-direction: column; margin: var(--code-example-margin, 1em auto); position: relative; + /* a wide example scrolls inside its own pre/preview instead of widening the page */ + min-width: 0; } div.code-example.code-above > pre { order: -1; diff --git a/src/lib/CopyButton.svelte b/src/lib/CopyButton.svelte index 478a9358..9de0e7c3 100644 --- a/src/lib/CopyButton.svelte +++ b/src/lib/CopyButton.svelte @@ -12,7 +12,7 @@ let { content = ``, - state = $bindable(`ready`), + state: copy_state = $bindable(`ready`), disabled = false, reset_ms = 2000, on_copy_success = (_content: string) => {}, @@ -44,49 +44,48 @@ } = $props() const copy_button_selector = `[data-sms-copy]` - const action_labels = $derived({ ...labels, pending: labels[state] }) + const action_labels = $derived({ ...labels, pending: labels[copy_state] }) $effect(() => { if (!global && !global_selector) return - type MountedCopyButton = Parameters[0] - const mounted_copy_buttons: { pre: HTMLElement; component: MountedCopyButton }[] = [] + let mounted_copy_buttons: { + code: Element + props: { content: string } + component: Parameters[0] + }[] = [] const apply_copy_buttons = () => { + // release buttons whose code block left the document; re-read the rest so the + // button copies what the block shows now, not what it showed when mounted + mounted_copy_buttons = mounted_copy_buttons.filter(({ code, props, component }) => { + if (code.isConnected) props.content = code.textContent ?? `` + else void unmount(component) + return code.isConnected + }) const style = `position: absolute; top: 6pt; inset-inline-end: 6pt; ${ rest.style ?? `` }` const skip_sel = skip_selector ?? as for (const code of document.querySelectorAll(global_selector ?? `pre > code`)) { const pre = code.parentElement - if (!pre) continue - const existing_copy_button = pre.querySelector(copy_button_selector) - const already_mounted = mounted_copy_buttons.some((entry) => entry.pre === pre) - // If a stale button from a previous effect pass still exists, remove it synchronously - // so this pass can mount a fresh button with updated props/callbacks. - if ( - existing_copy_button && - (!already_mounted || existing_copy_button.localName !== as) - ) { - existing_copy_button.remove() - } - if (existing_copy_button?.isConnected) continue + // Any existing copy button wins, including one from a second global instance: + // replacing it would have both instances swap buttons in an endless observer loop. + if (!pre || pre.querySelector(copy_button_selector)) continue if (skip_sel && pre.querySelector(skip_sel)) continue - const mounted_copy_button = mount(Self, { - target: pre, - props: { - content: code.textContent ?? ``, - as, - labels, - disabled, - reset_ms, - on_copy_success, - on_copy_error, - ...rest, - style, - }, + const props = $state({ + content: code.textContent ?? ``, + as, + labels, + disabled, + reset_ms, + on_copy_success, + on_copy_error, + ...rest, + style, }) - mounted_copy_buttons.push({ pre, component: mounted_copy_button }) + const component = mount(Self, { target: pre, props }) + mounted_copy_buttons.push({ code, props, component }) } } @@ -95,30 +94,26 @@ observer.observe(document.body, { childList: true, subtree: true }) return () => { observer.disconnect() - for (const { pre, component } of mounted_copy_buttons) { - // unmount() is async; remove marker node now to avoid blocking remount on next effect run. - pre.querySelector(copy_button_selector)?.remove() - void unmount(component) - } + for (const { component } of mounted_copy_buttons) void unmount(component) } }) const handle_action_state = (next_state: ActionState): void => { - if (next_state !== `pending`) state = next_state + if (next_state !== `pending`) copy_state = next_state } {#snippet copy_content({ state: action_state, disabled }: ActionButtonContent)} - {@const copy_state = action_state === `pending` ? state : action_state} - {@const { text, icon } = labels[copy_state]} - {@render copy_children?.({ state: copy_state, icon, text, disabled })} + {@const shown_state = action_state === `pending` ? copy_state : action_state} + {@const { text, icon } = labels[shown_state]} + {@render copy_children?.({ state: shown_state, icon, text, disabled })} {/snippet} {#if !(global || global_selector)} navigator.clipboard.writeText(content)} - {state} + state={copy_state} disabled={disabled || !content} {reset_ms} {as} diff --git a/src/lib/Dialog.svelte b/src/lib/Dialog.svelte index f9b4f3d4..5b34d322 100644 --- a/src/lib/Dialog.svelte +++ b/src/lib/Dialog.svelte @@ -34,9 +34,7 @@ const effective_closedby = $derived( closedby ?? (close_on_escape ? (close_on_backdrop ? `any` : `closerequest`) : `none`), ) - const custom_backdrop_dismiss = $derived( - closedby === undefined && close_on_backdrop && !close_on_escape, - ) + const dismiss_on_backdrop = $derived(closedby ? closedby === `any` : close_on_backdrop) let focus_origin: HTMLElement | SVGElement | null = null let pending_close_via: DialogCloseVia | null = null let backdrop_press_started = false @@ -76,9 +74,15 @@ backdrop_press_started = event.isPrimary && is_dialog_backdrop_event(surface, event) } const track_backdrop_release = (event: MouseEvent) => { - if (backdrop_press_started && is_dialog_backdrop_event(surface, event)) { - if (custom_backdrop_dismiss) close(`pointer`) - else if (effective_closedby === `any`) pending_close_via = `pointer` + if ( + backdrop_press_started && + dismiss_on_backdrop && + is_dialog_backdrop_event(surface, event) + ) { + // Browsers without `closedby` support leave the surface open; those with it have + // already light-dismissed it and only need the reason recorded before `close` fires. + if (surface?.open) close(`pointer`) + else pending_close_via = `pointer` } backdrop_press_started = false } diff --git a/src/lib/DraggablePane.svelte b/src/lib/DraggablePane.svelte index 216d5760..4b065f20 100644 --- a/src/lib/DraggablePane.svelte +++ b/src/lib/DraggablePane.svelte @@ -110,6 +110,8 @@ resize_edges.includes(edge) ? `${resize_gutter_px}px` : null const close_pane = (via: CloseVia) => { + // display: none drops focus on the body; hand it back to the toggle instead + if (pane?.contains(document.activeElement)) toggle_btn?.focus() open = false on_close?.({ via }) } diff --git a/src/lib/FileDetails.svelte b/src/lib/FileDetails.svelte index 6b8503a5..a2c75a95 100644 --- a/src/lib/FileDetails.svelte +++ b/src/lib/FileDetails.svelte @@ -1,5 +1,5 @@ diff --git a/src/lib/MultiSelect.svelte b/src/lib/MultiSelect.svelte index 4deafb2e..99a59f5a 100644 --- a/src/lib/MultiSelect.svelte +++ b/src/lib/MultiSelect.svelte @@ -968,7 +968,9 @@ (!is_user_message_active && (current_option === undefined || is_disabled(current_option))) || (filter_changed && !option_changed) - if (autoActiveFirstOption && should_auto_activate) { + // only while open: a collapsed combobox with an active option would select it on + // Enter instead of reopening, and point aria-activedescendant at a hidden row + if (autoActiveFirstOption && open && should_auto_activate) { const first_enabled_idx = rendered_options.findIndex( (candidate) => !is_disabled(candidate), ) @@ -1435,7 +1437,9 @@ highlighted_idx = null if (event.key === `Escape` || event.key === `Tab`) { - event.stopPropagation() + // a closed dropdown has nothing to dismiss, so the key belongs to the enclosing + // dialog/pane + if (open) event.stopPropagation() close_and_clear(event) } else if (event.key === `Enter`) { event.stopPropagation() diff --git a/src/lib/Nav.svelte b/src/lib/Nav.svelte index c6c2fa0e..eff70a21 100644 --- a/src/lib/Nav.svelte +++ b/src/lib/Nav.svelte @@ -10,7 +10,7 @@ import { click_outside, focus_trap, tooltip } from './attachments/index' import Icon from './Icon.svelte' import type { NavRoute, NavRouteObject } from './types' - import { chain_handlers } from './utils' + import { chain_handlers, step_focus } from './utils' type NavLinkRouteObject = NavRouteObject & { href: string } @@ -71,9 +71,9 @@ let is_open = $state(false) let hovered_dropdown = $state(null) let pinned_dropdown = $state(null) - let focused_item_index = $state(-1) let is_touch_device = $state(false) - let viewport_width = $state(Infinity) + // Start from the real width on the client so hydration doesn't flash the desktop nav on phones + let viewport_width = $state(globalThis.innerWidth ?? Infinity) let is_mobile = $derived(viewport_width <= breakpoint) let hide_timeout: ReturnType | null = null // `$props.id()` survives hydration; a random uuid would mismatch aria-controls @@ -107,7 +107,6 @@ is_open = false hovered_dropdown = null pinned_dropdown = null - focused_item_index = -1 } // Query the submenu links / toggle button of the dropdown for a given route href @@ -124,7 +123,6 @@ const is_opening = pinned_dropdown !== href pinned_dropdown = is_opening ? href : null hovered_dropdown = is_opening ? href : null - focused_item_index = is_opening && focus_first ? 0 : -1 if (is_opening && focus_first) { setTimeout(() => dropdown_links(href)[0]?.focus(), 0) } @@ -149,44 +147,27 @@ if (event.key === `Escape`) close_menus() } - function handle_dropdown_keydown( - event: KeyboardEvent, - href: string, - sub_routes: string[], - ) { - const { key } = event - - if (key === `Enter` || key === ` `) { - event.preventDefault() - toggle_dropdown(href, true) - return - } - - const is_dropdown_open = hovered_dropdown === href || pinned_dropdown === href - // Arrow key navigation within open dropdown - if (is_dropdown_open && (key === `ArrowDown` || key === `ArrowUp`)) { - event.preventDefault() - const direction = key === `ArrowDown` ? 1 : -1 - focused_item_index = Math.max( - 0, - Math.min(sub_routes.length - 1, focused_item_index + direction), - ) - dropdown_links(href)[focused_item_index]?.focus() - } + const is_dropdown_open = (href: string) => + hovered_dropdown === href || pinned_dropdown === href - // Open dropdown with ArrowDown when closed - if (!is_dropdown_open && key === `ArrowDown`) { - event.preventDefault() - toggle_dropdown(href, true) - } + function handle_toggle_keydown(event: KeyboardEvent, href: string) { + const { key } = event + const opens = + key === `Enter` || key === ` ` || (key === `ArrowDown` && !is_dropdown_open(href)) + if (!opens) return + event.preventDefault() + toggle_dropdown(href, true) } - function handle_dropdown_item_keydown(event: KeyboardEvent, href: string) { + // On the whole dropdown, so arrows keep working after focus has moved from the toggle + // onto a link and Escape hands focus back to the toggle from anywhere inside. + function handle_dropdown_keydown(event: KeyboardEvent, href: string) { + if (!is_dropdown_open(href)) return if (event.key === `Escape`) { event.preventDefault() close_menus() dropdown_toggle(href)?.focus() - } + } else step_focus(event, [...dropdown_links(href)]) } function is_current(path: string | undefined) { @@ -266,12 +247,6 @@ if (target instanceof Element && target.closest(`[data-dropdown-toggle]`)) return open_dropdown(href) } - const dropdown_item_keydown_handler = (parent_href: string) => - chain_handlers( - (event: KeyboardEvent) => handle_dropdown_item_keydown(event, parent_href), - link_props?.onkeydown, - ) - function get_external_attrs(route: NavRouteObject) { if (!route.external) return {} return { target: `_blank`, rel: `noopener noreferrer` } @@ -367,13 +342,15 @@ (route) => route !== parsed_route.href, )} {@const is_pinned = pinned_dropdown === parsed_route.href} - {@const is_dropdown_open = hovered_dropdown === parsed_route.href || is_pinned} + {@const dropdown_open = is_dropdown_open(parsed_route.href)}
open_dropdown(parsed_route.href, true)} onmouseleave={() => schedule_hide(parsed_route.href, is_pinned)} + onkeydown={(event: KeyboardEvent) => + handle_dropdown_keydown(event, parsed_route.href)} onfocusin={dropdown_focusin_handler(parsed_route.href)} onfocusout={(event: FocusEvent) => { if ( @@ -414,21 +391,21 @@ {/if}
open_dropdown(parsed_route.href, true)} @@ -464,7 +441,6 @@ aria-current={is_current(child_href)} style={`${child_formatted.style}; ${link_props?.style ?? ``}`} onclick={link_click_handler({ href: child_href })} - onkeydown={dropdown_item_keydown_handler(parsed_route.href)} {@attach child_tooltip} > {@html child_formatted.label} @@ -689,16 +665,20 @@ inset-inline-start: 1rem; flex-direction: column; justify-content: space-around; + /* 1.4rem bars inside a ~2.4rem hit area: a finger-sized target without moving the bars */ width: 1.4rem; height: 1.4rem; + box-sizing: content-box; + padding: 0.5rem; + margin: -0.5rem; background: transparent; - padding: 0; z-index: var(--nav-toggle-btn-z-index, 10); } .burger span { width: 100%; height: 0.18rem; - background-color: var(--text); + /* hosts that don't define --text still get visible bars */ + background-color: var(--text, currentColor); border-radius: 8pt; transition: opacity 0.2s linear, @@ -732,10 +712,16 @@ visibility 0.3s ease; z-index: var(--nav-mobile-z-index, 2); flex-direction: column; + /* one scrollable column: with several submenus expanded the menu outgrows a phone + screen, and wrapping would spill entries into a second column off the panel */ + flex-wrap: nowrap; align-items: stretch; justify-content: start; gap: 0.2em; max-width: 90vw; + max-height: calc(100dvh - 4rem); + overflow-y: auto; + overscroll-behavior: contain; border-radius: 6pt; } nav.mobile .menu.open { diff --git a/src/lib/Popover.svelte b/src/lib/Popover.svelte index bc6f3fab..7cb03521 100644 --- a/src/lib/Popover.svelte +++ b/src/lib/Popover.svelte @@ -230,7 +230,7 @@ {role} aria-label={rest[`aria-label`] ?? (rest[`aria-labelledby`] ? undefined : `Popover`)} class={[`popover`, rest.class]} - ontoggle={handle_native_toggle} + ontoggle={chain_handlers(handle_native_toggle, rest.ontoggle)} {@attach show_native_popover} {@attach float({ anchor, placement, align, offset, padding, match_width, strategy })} {@attach native_dismiss diff --git a/src/lib/SettingsSearch.svelte b/src/lib/SettingsSearch.svelte index 1f3a7662..fd34be46 100644 --- a/src/lib/SettingsSearch.svelte +++ b/src/lib/SettingsSearch.svelte @@ -1,7 +1,6 @@