@leef-l/macos-ui-kit 0.0.0-stage → 0.1.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.
Files changed (45) hide show
  1. package/README.md +215 -2
  2. package/dist/assets/Inter Font License.txt +93 -0
  3. package/dist/assets/Inter-Medium.ttf +0 -0
  4. package/dist/component.d.ts +47 -0
  5. package/dist/controls/badge.d.ts +27 -0
  6. package/dist/controls/button.d.ts +41 -0
  7. package/dist/controls/checkbox.d.ts +40 -0
  8. package/dist/controls/combobox.d.ts +105 -0
  9. package/dist/controls/datepicker.d.ts +42 -0
  10. package/dist/controls/disclosure.d.ts +40 -0
  11. package/dist/controls/numberfield.d.ts +60 -0
  12. package/dist/controls/progress.d.ts +33 -0
  13. package/dist/controls/segmented.d.ts +64 -0
  14. package/dist/controls/select.d.ts +42 -0
  15. package/dist/controls/slider.d.ts +49 -0
  16. package/dist/controls/spinner.d.ts +24 -0
  17. package/dist/controls/splitview.d.ts +76 -0
  18. package/dist/controls/switch.d.ts +47 -0
  19. package/dist/controls/table.d.ts +103 -0
  20. package/dist/controls/textarea.d.ts +49 -0
  21. package/dist/controls/textfield.d.ts +63 -0
  22. package/dist/dom.d.ts +35 -0
  23. package/dist/index.d.ts +85 -0
  24. package/dist/index.js +2053 -0
  25. package/dist/index.js.map +1 -0
  26. package/dist/layout/sidebar.d.ts +47 -0
  27. package/dist/layout/statusbar.d.ts +30 -0
  28. package/dist/layout/tabview.d.ts +74 -0
  29. package/dist/layout/titlebar.d.ts +49 -0
  30. package/dist/layout/toolbar.d.ts +54 -0
  31. package/dist/layout/traffic-lights.d.ts +37 -0
  32. package/dist/macos-ui-kit.css +2 -0
  33. package/dist/macos-ui-kit.iife.js +2 -0
  34. package/dist/macos-ui-kit.iife.js.map +1 -0
  35. package/dist/mobile/shell.d.ts +59 -0
  36. package/dist/overlay.d.ts +41 -0
  37. package/dist/overlays/dialog.d.ts +46 -0
  38. package/dist/overlays/menu.d.ts +58 -0
  39. package/dist/overlays/popover.d.ts +90 -0
  40. package/dist/overlays/sheet.d.ts +56 -0
  41. package/dist/overlays/toast.d.ts +37 -0
  42. package/dist/overlays/tooltip.d.ts +48 -0
  43. package/dist/style.css.d.ts +2 -0
  44. package/dist/theme.d.ts +100 -0
  45. package/package.json +55 -4
package/README.md CHANGED
@@ -1,3 +1,216 @@
1
- # Temporary Holding Version
1
+ # @leef-l/macos-ui-kit
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ macOS-style UI components in dependency-free TypeScript. No React, no Vue, no
4
+ virtual DOM — just `document.createElement` wrappers over two-layer design
5
+ tokens, so one import gives you the whole design system.
6
+
7
+ - **30 components** — 17 controls, 6 layout, 6 overlays, 1 mobile shell
8
+ - **Zero runtime dependencies**
9
+ - **Light DOM only** — no Shadow DOM, so native window dragging and screen
10
+ readers both work
11
+ - **Runtime theming** — four built-in themes plus custom token overrides
12
+ - **Density scaling** — one `--mac-scale` factor resizes the whole interface
13
+
14
+ ## Scope: frontend only
15
+
16
+ This package is **UI components and nothing else**. It contains no backend, no
17
+ server, no data layer and no build-time code generation.
18
+
19
+ - **No Go, no Wails runtime.** `package.json` declares no `dependencies` and no
20
+ `peerDependencies`; the built bundle has no imports at all. Nothing in `src/`
21
+ reads a `_wails` global, an IPC bridge, or a native API.
22
+ - **No framework.** Just DOM APIs. It runs in any browser, in an Electron or
23
+ Tauri webview, in a Wails window, or in a plain HTML page opened from disk.
24
+ - **No network calls.** Components never fetch anything. What you render is what
25
+ you pass in.
26
+
27
+ Wails v3 is a *target*, not a requirement: the drag regions and the mobile shell
28
+ were tuned for a frameless desktop window, and that is where the kit looks best.
29
+ In a normal browser those behaviours simply do nothing instead of failing.
30
+
31
+ Where the kit does touch host behaviour, it is through standard web APIs only:
32
+
33
+ | Behaviour | How it works | Outside a desktop shell |
34
+ |---|---|---|
35
+ | Window dragging | `--wails-draggable` is a CSS custom property on the title bar | Ignored; nothing to drag |
36
+ | Double-click to zoom | Listens for `dblclick` | Does nothing |
37
+ | Mobile detection | `matchMedia("(pointer: coarse)")` + a width query | Works everywhere |
38
+ | Sidebar / overlays | Plain DOM + CSS | Works everywhere |
39
+
40
+ The Go application in this repository is a separate thing: a demo host that
41
+ happens to use these components. You do not need it, and nothing in this package
42
+ imports from it.
43
+
44
+ ---
45
+
46
+ ## Install
47
+
48
+ ### From npm (after the first publish)
49
+
50
+ ```sh
51
+ npm install @leef-l/macos-ui-kit
52
+ ```
53
+
54
+ ### Straight from a GitHub release — no npm account, no publish needed
55
+
56
+ ```sh
57
+ npm install https://github.com/leef-l/macos-ui-kit/releases/download/v0.1.0/leef-l-macos-ui-kit-0.1.0.tgz
58
+ ```
59
+
60
+ This is a normal npm install: it lands in `node_modules` with the same files,
61
+ the same types and the same exports. Use it while the package is not on the
62
+ registry yet, or to pin an exact build.
63
+
64
+ ### From a clone
65
+
66
+ ```sh
67
+ git clone https://github.com/leef-l/macos-ui-kit
68
+ cd macos-ui-kit/packages/macos-ui-kit
69
+ npm install
70
+ npm run build
71
+
72
+ # in your project
73
+ npm install /absolute/path/to/packages/macos-ui-kit
74
+ ```
75
+
76
+ ### No build tooling at all
77
+
78
+ Download `dist/macos-ui-kit.iife.js` and `dist/macos-ui-kit.css` from a release
79
+ (or open the files in `packages/macos-ui-kit/dist/` after building) and use the
80
+ global `MacUI`:
81
+
82
+ ```html
83
+ <link rel="stylesheet" href="./macos-ui-kit.css" />
84
+ <script src="./macos-ui-kit.iife.js"></script>
85
+ <script>
86
+ const button = new MacUI.Button({label: "Hello", variant: "primary"});
87
+ document.body.appendChild(button.element);
88
+ </script>
89
+ ```
90
+
91
+ The IIFE build needs the `assets/` folder next to the stylesheet, because the
92
+ stylesheet references the Inter font by relative path.
93
+
94
+ ---
95
+
96
+ ## Use
97
+
98
+ ```ts
99
+ import {Button, TextField, showToast} from "@leef-l/macos-ui-kit";
100
+ // The stylesheet is separate: the JS bundle contains no CSS.
101
+ import "@leef-l/macos-ui-kit/style.css";
102
+
103
+ const field = new TextField({placeholder: "Your name"});
104
+ document.body.appendChild(field.element);
105
+
106
+ const button = new Button({
107
+ label: "Greet",
108
+ variant: "primary",
109
+ onClick: () => showToast(`Hello ${field.value}`),
110
+ });
111
+ document.body.appendChild(button.element);
112
+ ```
113
+
114
+ Every component exposes `element`, which is the node you mount. Construct it,
115
+ append `element`, and keep the instance for the methods.
116
+
117
+ ### Whole kit or a single component
118
+
119
+ Both are the same import — take only what you use:
120
+
121
+ ```ts
122
+ // Whole kit, when you want everything
123
+ import * as ui from "@leef-l/macos-ui-kit";
124
+
125
+ // One component, when you want one
126
+ import {Button} from "@leef-l/macos-ui-kit";
127
+ ```
128
+
129
+ There is one entry point, and the ESM build is tree-shakeable: a bundler drops
130
+ every component you do not reference, provided the package's `sideEffects` field
131
+ is respected (Vite, Rollup, webpack and esbuild all do).
132
+
133
+ **Honest caveat:** the JavaScript is tree-shaken, the **stylesheet is not**. CSS
134
+ ships as a single 63 KB file and every component's rules are in it. Splitting it
135
+ per component is not worth the extra requests for a desktop window, but if you
136
+ are shipping to a slow network and use three components, know that you still
137
+ download the rules for thirty.
138
+
139
+ ### Theming
140
+
141
+ ```ts
142
+ import {themeManager, setDensity} from "@leef-l/macos-ui-kit";
143
+
144
+ themeManager.apply("macos-dark"); // macos-dark | macos-light | hc-dark | hc-light
145
+ setDensity("compact"); // compact | comfortable | touch
146
+ ```
147
+
148
+ Or override tokens in your own stylesheet — components read semantic tokens
149
+ only, so one declaration restyles every component:
150
+
151
+ ```css
152
+ :root {
153
+ --mac-accent: #7c5cff;
154
+ --mac-radius-m: 10px;
155
+ }
156
+ ```
157
+
158
+ ---
159
+
160
+ ## API convention
161
+
162
+ Three rules, applied to all 30 components, so a control you have never used
163
+ behaves the way you would guess:
164
+
165
+ | Concern | Rule | Examples |
166
+ |---|---|---|
167
+ | **Read state** | property getter | `field.value`, `toggle.checked`, `sheet.isOpen` |
168
+ | **Write state** | `setX()`, always silent | `setValue()`, `setChecked()`, `setSelected()` |
169
+ | **Dismissible overlays** | `open(anchor?)` / `close()` / `isOpen` | `dialog.open()`, `popover.open(btn)` |
170
+
171
+ `setX()` never fires the change callback — only a user interaction does. That
172
+ makes programmatic writes safe to call from a render path.
173
+
174
+ `Toast` is the one exception: it is a notification, not an overlay, so it uses
175
+ `show()` / `hide()`.
176
+
177
+ ---
178
+
179
+ ## Components
180
+
181
+ | Group | Components |
182
+ |---|---|
183
+ | Controls | `Button` `TextField` `TextArea` `NumberField` `Slider` `Progress` `Spinner` `Badge` `Switch` `Checkbox` `SegmentedControl` `Select` `ComboBox` `DatePicker` `Disclosure` `Table` `SplitView` |
184
+ | Layout | `TitleBar` `TrafficLights` `Sidebar` `StatusBar` `Toolbar` `TabView` |
185
+ | Overlays | `Menu` `Tooltip` `Dialog` `Popover` `Sheet` `Toast` |
186
+ | Mobile | `MobileShell` |
187
+
188
+ Every component also exposes `element`, `destroy()` (releases listeners and any
189
+ observer), and `focus()` where focusing makes sense.
190
+
191
+ ---
192
+
193
+ ## Files
194
+
195
+ ```
196
+ dist/index.js ESM bundle (import in a bundler)
197
+ dist/macos-ui-kit.iife.js IIFE, global MacUI (<script> tag)
198
+ dist/macos-ui-kit.css one stylesheet
199
+ dist/index.d.ts type declarations
200
+ dist/assets/ Inter font + its OFL licence
201
+ ```
202
+
203
+ ## Fonts
204
+
205
+ Apple's SF Pro cannot be redistributed, so the kit ships **Inter** (SIL Open
206
+ Font License 1.1, included in `dist/assets`) as an SF-alike fallback. The font
207
+ stack puts the real system font first on macOS, so Apple users see SF Pro and
208
+ everyone else sees near-identical glyphs.
209
+
210
+ The stylesheet references the font as `./assets/Inter-Medium.ttf`. If your
211
+ bundler relocates CSS assets, copy `dist/assets/` alongside the stylesheet — or
212
+ drop the `@font-face` block and provide your own.
213
+
214
+ ## Licence
215
+
216
+ MIT. Bundled Inter typeface: SIL Open Font License 1.1.
@@ -0,0 +1,93 @@
1
+ Copyright 2020 The Inter Project Authors (https://github.com/rsms/inter)
2
+
3
+ This Font Software is licensed under the SIL Open Font License, Version 1.1.
4
+ This license is copied below, and is also available with a FAQ at:
5
+ http://scripts.sil.org/OFL
6
+
7
+
8
+ -----------------------------------------------------------
9
+ SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
10
+ -----------------------------------------------------------
11
+
12
+ PREAMBLE
13
+ The goals of the Open Font License (OFL) are to stimulate worldwide
14
+ development of collaborative font projects, to support the font creation
15
+ efforts of academic and linguistic communities, and to provide a free and
16
+ open framework in which fonts may be shared and improved in partnership
17
+ with others.
18
+
19
+ The OFL allows the licensed fonts to be used, studied, modified and
20
+ redistributed freely as long as they are not sold by themselves. The
21
+ fonts, including any derivative works, can be bundled, embedded,
22
+ redistributed and/or sold with any software provided that any reserved
23
+ names are not used by derivative works. The fonts and derivatives,
24
+ however, cannot be released under any other type of license. The
25
+ requirement for fonts to remain under this license does not apply
26
+ to any document created using the fonts or their derivatives.
27
+
28
+ DEFINITIONS
29
+ "Font Software" refers to the set of files released by the Copyright
30
+ Holder(s) under this license and clearly marked as such. This may
31
+ include source files, build scripts and documentation.
32
+
33
+ "Reserved Font Name" refers to any names specified as such after the
34
+ copyright statement(s).
35
+
36
+ "Original Version" refers to the collection of Font Software components as
37
+ distributed by the Copyright Holder(s).
38
+
39
+ "Modified Version" refers to any derivative made by adding to, deleting,
40
+ or substituting -- in part or in whole -- any of the components of the
41
+ Original Version, by changing formats or by porting the Font Software to a
42
+ new environment.
43
+
44
+ "Author" refers to any designer, engineer, programmer, technical
45
+ writer or other person who contributed to the Font Software.
46
+
47
+ PERMISSION & CONDITIONS
48
+ Permission is hereby granted, free of charge, to any person obtaining
49
+ a copy of the Font Software, to use, study, copy, merge, embed, modify,
50
+ redistribute, and sell modified and unmodified copies of the Font
51
+ Software, subject to the following conditions:
52
+
53
+ 1) Neither the Font Software nor any of its individual components,
54
+ in Original or Modified Versions, may be sold by itself.
55
+
56
+ 2) Original or Modified Versions of the Font Software may be bundled,
57
+ redistributed and/or sold with any software, provided that each copy
58
+ contains the above copyright notice and this license. These can be
59
+ included either as stand-alone text files, human-readable headers or
60
+ in the appropriate machine-readable metadata fields within text or
61
+ binary files as long as those fields can be easily viewed by the user.
62
+
63
+ 3) No Modified Version of the Font Software may use the Reserved Font
64
+ Name(s) unless explicit written permission is granted by the corresponding
65
+ Copyright Holder. This restriction only applies to the primary font name as
66
+ presented to the users.
67
+
68
+ 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
69
+ Software shall not be used to promote, endorse or advertise any
70
+ Modified Version, except to acknowledge the contribution(s) of the
71
+ Copyright Holder(s) and the Author(s) or with their explicit written
72
+ permission.
73
+
74
+ 5) The Font Software, modified or unmodified, in part or in whole,
75
+ must be distributed entirely under this license, and must not be
76
+ distributed under any other license. The requirement for fonts to
77
+ remain under this license does not apply to any document created
78
+ using the Font Software.
79
+
80
+ TERMINATION
81
+ This license becomes null and void if any of the above conditions are
82
+ not met.
83
+
84
+ DISCLAIMER
85
+ THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
86
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
87
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
88
+ OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
89
+ COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
90
+ INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
91
+ DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
92
+ FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
93
+ OTHER DEALINGS IN THE FONT SOFTWARE.
Binary file
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Base class for every macOS UI Kit component.
3
+ *
4
+ * It owns two things that raw DOM code gets wrong most often:
5
+ *
6
+ * 1. **Listener lifetime.** Vanilla UI leaks because listeners outlive the
7
+ * elements they touch. `listen` records every binding so `destroy()` can
8
+ * remove all of them in one pass.
9
+ * 2. **A stable root element.** Subclasses build their markup in the
10
+ * constructor; callers only ever see `element`, which keeps the internal DOM
11
+ * structure free to change.
12
+ *
13
+ * Deliberately light DOM: shadow roots would retarget `event.target`, and
14
+ * Wails' window-drag detection reads exactly that property
15
+ * (`internal/runtime/.../drag.ts` uses `event.target`, never `composedPath`).
16
+ */
17
+ export declare abstract class Component<T extends HTMLElement = HTMLElement> {
18
+ /** The root element. Subclasses fill this in before the constructor returns. */
19
+ protected abstract readonly root: T;
20
+ private readonly cleanups;
21
+ private destroyed;
22
+ /** The component's root element, for mounting into the page. */
23
+ get element(): T;
24
+ /** True once `destroy()` has run. */
25
+ get isDestroyed(): boolean;
26
+ /**
27
+ * Add an event listener whose removal `destroy()` handles.
28
+ *
29
+ * Prefer this over `addEventListener` everywhere: a component that is torn
30
+ * down mid-session (a closed dialog, a re-rendered list) must not leave
31
+ * handlers pointing at detached nodes.
32
+ */
33
+ protected listen<K extends keyof HTMLElementEventMap>(target: T | HTMLElement | Document | Window, type: K, handler: (event: HTMLElementEventMap[K]) => void, options?: AddEventListenerOptions): void;
34
+ protected listen(target: T | HTMLElement | Document | Window, type: string, handler: EventListenerOrEventListenerObject, options?: AddEventListenerOptions): void;
35
+ /** Register an arbitrary teardown step (timers, observers, subscriptions). */
36
+ protected onDestroy(cleanup: () => void): void;
37
+ /** Reflect a boolean state as a class on the root, skipping no-op writes. */
38
+ protected toggleClass(name: string, on: boolean): void;
39
+ /** Set an attribute only when its value actually changes. */
40
+ protected setAttr(name: string, value: string): void;
41
+ /** Mount the component into a parent, replacing that parent's content. */
42
+ mount(parent?: Element): this;
43
+ /** Remove the component from the document and release everything it holds. */
44
+ remove(): void;
45
+ /** Tear down listeners and timers. Safe to call more than once. */
46
+ destroy(): void;
47
+ }
@@ -0,0 +1,27 @@
1
+ import { Component } from "../component";
2
+ import "./badge.css";
3
+ export type BadgeVariant = "default" | "accent" | "success" | "warning" | "danger";
4
+ export interface BadgeOptions {
5
+ /** Badge text. */
6
+ text: string;
7
+ /** Colour treatment. Default `default` (neutral grey). */
8
+ variant?: BadgeVariant;
9
+ }
10
+ /**
11
+ * A small pill used to tag rows, counts and states.
12
+ *
13
+ * Renders inline, so a badge dropped inside a sidebar row or a paragraph sits
14
+ * on the text baseline rather than breaking that row's layout. The text is
15
+ * written with `textContent` only — a badge is the component most likely to
16
+ * receive a server-supplied string, so it never goes anywhere near HTML.
17
+ */
18
+ export declare class Badge extends Component<HTMLElement> {
19
+ protected readonly root: HTMLElement;
20
+ private text;
21
+ private variant;
22
+ constructor(options: BadgeOptions);
23
+ /** Replace the badge text. */
24
+ setText(text: string): void;
25
+ /** Swap the colour treatment. */
26
+ setVariant(variant: BadgeVariant): void;
27
+ }
@@ -0,0 +1,41 @@
1
+ import { Component } from "../component";
2
+ import { type Child } from "../dom";
3
+ import "./button.css";
4
+ export type ButtonVariant = "default" | "primary" | "destructive" | "plain";
5
+ export type ButtonSize = "small" | "default" | "large";
6
+ export interface ButtonOptions {
7
+ /** Visible label. Omit for an icon-only button (then set `ariaLabel`). */
8
+ label?: Child;
9
+ /** Leading SVG path data, drawn at the control's icon size. */
10
+ icon?: string;
11
+ variant?: ButtonVariant;
12
+ size?: ButtonSize;
13
+ /** Accessible name; required when there is no visible label. */
14
+ ariaLabel?: string;
15
+ /** Stretch to the container's width. */
16
+ block?: boolean;
17
+ disabled?: boolean;
18
+ /** Native `type`, so a button inside a form does not submit by accident. */
19
+ type?: "button" | "submit" | "reset";
20
+ /** Fires on activation (click, Enter, Space — the browser handles the rest). */
21
+ onClick?: (event: MouseEvent) => void;
22
+ }
23
+ /**
24
+ * A native `<button>` with macOS styling.
25
+ *
26
+ * Built on the real element rather than a `div[role=button]` so keyboard
27
+ * activation, disabled handling and focus order come from the browser.
28
+ */
29
+ export declare class Button extends Component<HTMLButtonElement> {
30
+ protected readonly root: HTMLButtonElement;
31
+ private readonly labelNode;
32
+ constructor(options?: ButtonOptions);
33
+ /** Replace the visible label text. */
34
+ setLabel(text: string): void;
35
+ /** Enable or disable the button. */
36
+ setDisabled(disabled: boolean): void;
37
+ /** Toggle the primary (blue) treatment at runtime. */
38
+ setPrimary(primary: boolean): void;
39
+ /** Give the caller a way to move focus programmatically. */
40
+ focus(): void;
41
+ }
@@ -0,0 +1,40 @@
1
+ import { Component } from "../component";
2
+ import { type Child } from "../dom";
3
+ import "./checkbox.css";
4
+ export interface CheckboxOptions {
5
+ /** Initial ticked state. Default false. */
6
+ checked?: boolean;
7
+ /** Show the dash instead of the tick; it takes precedence over `checked`. */
8
+ indeterminate?: boolean;
9
+ disabled?: boolean;
10
+ /** Text rendered next to the box; it also names the control for assistive tech. */
11
+ label?: Child;
12
+ /** Fires with the new state after a user toggle; setters stay silent. */
13
+ onChange?: (checked: boolean) => void;
14
+ }
15
+ /**
16
+ * A styled checkbox built around a real `<input type="checkbox">`.
17
+ *
18
+ * The input stays in the document — clipped to a pixel, not `display: none` —
19
+ * so it keeps its place in the tab order, its focus ring and its click target.
20
+ * The drawn box is pure decoration: `:checked` and `:indeterminate` in CSS
21
+ * follow the input, and one native `change` listener mirrors the result to the
22
+ * caller. Space, screen readers and form submission therefore stay native;
23
+ * nothing in this component toggles state behind the browser's back.
24
+ */
25
+ export declare class Checkbox extends Component<HTMLElement> {
26
+ protected readonly root: HTMLLabelElement;
27
+ private readonly input;
28
+ private readonly onChange?;
29
+ constructor(options?: CheckboxOptions);
30
+ /** The current ticked state. */
31
+ get checked(): boolean;
32
+ /** Set the ticked state; a write that changes nothing is skipped, and `onChange` stays silent. */
33
+ setChecked(checked: boolean): void;
34
+ /** Show the mixed state (a dash instead of the tick). */
35
+ setIndeterminate(indeterminate: boolean): void;
36
+ /** Enable or disable the checkbox, using the native attribute. */
37
+ setDisabled(disabled: boolean): void;
38
+ /** Move keyboard focus onto the native input. */
39
+ focus(): void;
40
+ }
@@ -0,0 +1,105 @@
1
+ import { Component } from "../component";
2
+ import "./combobox.css";
3
+ /** One suggestion in the list. */
4
+ export interface ComboBoxOption {
5
+ label: string;
6
+ value: string;
7
+ }
8
+ export interface ComboBoxOptions {
9
+ /** Full suggestion list; typing filters it. */
10
+ options: ComboBoxOption[];
11
+ /** Initially selected value; its label fills the field. */
12
+ value?: string;
13
+ placeholder?: string;
14
+ /**
15
+ * Custom rendering for a suggestion row. Returning a `Node` lets the list
16
+ * hold live UI; returning a string renders as text. The list keeps its own
17
+ * ARIA wiring around whatever is returned.
18
+ */
19
+ renderOption?: (option: ComboBoxOption) => Node | string;
20
+ /** Fires when a suggestion is committed (click, Enter or Tab). */
21
+ onChange?: (value: string) => void;
22
+ /** Fires on every keystroke with the raw text. */
23
+ onInput?: (text: string) => void;
24
+ }
25
+ /**
26
+ * A macOS combo box: a text field with an editable suggestion list.
27
+ *
28
+ * The list is absolutely positioned inside the component's own box rather than
29
+ * appended to `document.body` like the overlay components. A combo box's
30
+ * suggestions are conceptually part of the field, so staying in the container
31
+ * avoids portal teardown, scroll re-positioning and a `z-index` war with the
32
+ * dialogs above it. The trade-off is that an ancestor with `overflow: hidden`
33
+ * clips the list — acceptable for a control that rarely sits at the edge of a
34
+ * clipped card.
35
+ *
36
+ * Implements the ARIA combobox pattern: the input owns `role="combobox"`,
37
+ * `aria-expanded` and `aria-activedescendant`; the list is a `role="listbox"`
38
+ * of `role="option"` rows. Focus never leaves the input, so typing continues
39
+ * while the arrow keys move the highlight.
40
+ */
41
+ export declare class ComboBox extends Component<HTMLDivElement> {
42
+ protected readonly root: HTMLDivElement;
43
+ private readonly input;
44
+ private readonly list;
45
+ private readonly onChange?;
46
+ private readonly onInput?;
47
+ private readonly renderOption?;
48
+ private readonly listId;
49
+ private options;
50
+ /** Options currently drawn, i.e. after filtering. */
51
+ private filtered;
52
+ /** Option rows, index-aligned with `filtered`. */
53
+ private rows;
54
+ /** Highlighted row index; -1 means nothing is highlighted. */
55
+ private active;
56
+ /**
57
+ * Value of the committed option, or null once the text is free-form.
58
+ * Kept separately from `input.value`, because the field shows a label while
59
+ * the caller may have handed us a machine value (an id, a slug).
60
+ */
61
+ private selectedValue;
62
+ constructor(options: ComboBoxOptions);
63
+ /**
64
+ * Show every option.
65
+ *
66
+ * Opening from a click or a focus must not filter by whatever text is
67
+ * already in the field: a field showing "张三" would otherwise open to a
68
+ * single row, which reads as a broken control. Typing is the only thing that
69
+ * narrows the list, and that path filters explicitly.
70
+ */
71
+ private openWithFullList;
72
+ /**
73
+ * The committed value.
74
+ *
75
+ * The highlight wins (an arrowed-onto row is what the user means), then the
76
+ * value behind the current label, and only then the raw text. Going through
77
+ * `selectedValue` is what keeps a field showing "Apricot" answering with its
78
+ * machine value "b".
79
+ */
80
+ get value(): string;
81
+ /**
82
+ * Set the text and value programmatically.
83
+ *
84
+ * A value that matches an option shows that option's label, so the field
85
+ * never displays a bare id where a name belongs. Silent, like every setter.
86
+ */
87
+ setValue(value: string): void;
88
+ /** Replace the suggestion list; the current text is kept and re-filtered. */
89
+ setOptions(options: ComboBoxOption[]): void;
90
+ /** Move keyboard focus into the field and show the full list. */
91
+ focus(): void;
92
+ private optionFor;
93
+ /** Rebuild the visible rows for the given text. */
94
+ private filter;
95
+ private open;
96
+ private close;
97
+ /** Move the highlight by `step` rows, wrapping at both ends. */
98
+ private move;
99
+ /** Mirror `active` into the DOM and the input's ARIA wiring. */
100
+ private syncActive;
101
+ private onKeyDown;
102
+ private onListClick;
103
+ /** Accept an option: fill the field, close the list, report the value. */
104
+ private commit;
105
+ }
@@ -0,0 +1,42 @@
1
+ import { Component } from "../component";
2
+ import "./datepicker.css";
3
+ export interface DatePickerOptions {
4
+ /** Initial date as `YYYY-MM-DD`. */
5
+ value?: string;
6
+ /** Earliest selectable date, inclusive, as `YYYY-MM-DD`. */
7
+ min?: string;
8
+ /** Latest selectable date, inclusive, as `YYYY-MM-DD`. */
9
+ max?: string;
10
+ disabled?: boolean;
11
+ /** Optional label rendered above the control and linked through `for`/`id`. */
12
+ label?: string;
13
+ /** Fires with `YYYY-MM-DD` (or "" when cleared) once the value is committed. */
14
+ onChange?: (value: string) => void;
15
+ }
16
+ /**
17
+ * A macOS date picker wrapping a native `<input type="date">`.
18
+ *
19
+ * The native control is kept, not reimplemented: it brings the platform
20
+ * calendar popup, keyboard editing of each date segment, locale-correct
21
+ * formatting, `min`/`max` enforcement and the iOS wheel picker. A hand-built
22
+ * calendar would have to re-earn all of that and would still not match the OS.
23
+ *
24
+ * What this component adds is purely visual — the macOS field chrome and a
25
+ * trailing calendar icon that is `pointer-events: none`, so the click falls
26
+ * through to the native picker indicator underneath.
27
+ */
28
+ export declare class DatePicker extends Component<HTMLDivElement> {
29
+ protected readonly root: HTMLDivElement;
30
+ private readonly input;
31
+ private readonly labelNode;
32
+ private readonly onChange?;
33
+ constructor(options?: DatePickerOptions);
34
+ /** The current date as `YYYY-MM-DD`, or "" when empty. */
35
+ get value(): string;
36
+ /** Set the date; an empty string clears it. Silent, like every setter. */
37
+ setValue(value: string): void;
38
+ /** Enable or disable the field, using the native attribute. */
39
+ setDisabled(disabled: boolean): void;
40
+ /** Move keyboard focus into the field. */
41
+ focus(): void;
42
+ }
@@ -0,0 +1,40 @@
1
+ import { Component } from "../component";
2
+ import { type Child } from "../dom";
3
+ import "./disclosure.css";
4
+ export interface DisclosureOptions {
5
+ /** Section title shown in the header. */
6
+ title: string;
7
+ /** Collapsible content. */
8
+ content?: Child;
9
+ /** Start expanded. Defaults to false, the macOS convention. */
10
+ open?: boolean;
11
+ /** Optional trailing summary, visible whether open or closed. */
12
+ meta?: string;
13
+ disabled?: boolean;
14
+ onToggle?: (open: boolean) => void;
15
+ }
16
+ /**
17
+ * A collapsible section.
18
+ *
19
+ * Uses `aria-expanded` on the header button and `hidden` on the region, which is
20
+ * the pattern screen readers understand for a disclosure widget: the button
21
+ * announces whether the section is open, and the content is genuinely removed
22
+ * from the accessibility tree when closed rather than merely scrolled away.
23
+ */
24
+ export declare class Disclosure extends Component<HTMLDivElement> {
25
+ protected readonly root: HTMLDivElement;
26
+ private readonly header;
27
+ private readonly content;
28
+ private readonly metaNode;
29
+ private readonly onToggle?;
30
+ private open;
31
+ constructor(options: DisclosureOptions);
32
+ /** Expand or collapse the section. */
33
+ setOpen(open: boolean, notify?: boolean): void;
34
+ /** Whether the section is currently expanded. */
35
+ get isOpen(): boolean;
36
+ /** Update the trailing summary text. */
37
+ setMeta(meta: string): void;
38
+ /** The region content is appended to after construction. */
39
+ get contentElement(): HTMLDivElement;
40
+ }