{"_id":"@amritk/lynx-dialogs","_rev":"4-9ac870451ce0c6f3789872e810e00aec","name":"@amritk/lynx-dialogs","dist-tags":{"latest":"0.2.2"},"versions":{"0.1.0":{"name":"@amritk/lynx-dialogs","version":"0.1.0","keywords":["lynx","date-picker","action-sheet","dialog","native-module","typescript","mjst"],"author":{"name":"amritk"},"license":"MIT","_id":"@amritk/lynx-dialogs@0.1.0","maintainers":[{"name":"amritk","email":"amrit+spam@hockey-community.com"}],"homepage":"https://github.com/amritk/mini/tree/main/packages/lynx-dialogs#readme","bugs":{"url":"https://github.com/amritk/mini/issues"},"dist":{"shasum":"1af629f39a9e33ac61c61181a005a860a8aacc0f","tarball":"https://registry.npmjs.org/@amritk/lynx-dialogs/-/lynx-dialogs-0.1.0.tgz","fileCount":33,"integrity":"sha512-EGzaYLZ8q75NSkAJ9m5XSCMw9FkYGWjhHemFy7YlaE/Odtx49mGchEz+sbKx0qpvAkW+Fe+l4iV+L3EdE268KA==","signatures":[{"sig":"MEUCIQCNA5BI3WXVOBUz8LeCREen5TvxiSAYglahExMxCXJtPgIgM9AOi1ZifR9hLUh5OUw/YcDryRl+Fii6PXUaYcvUnsc=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":136556},"type":"module","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","default":"./dist/index.js","development":"./src/index.ts"},"./testing":{"types":"./dist/testing/index.d.ts","import":"./dist/testing/index.js","default":"./dist/testing/index.js","development":"./src/testing/index.ts"},"./package.json":"./package.json","./lynx.lib.json":"./lynx.lib.json"},"gitHead":"aea9072d4a679858c41a7d4eaf1f90f0d153798f","scripts":{"test":"NODE_ENV=production vitest run --root ../.. packages/lynx-dialogs/","build":"tsgo -p tsconfig.build.json && tsc-alias -p tsconfig.build.json -f && node ../../scripts/strip-comments.mjs","types:check":"tsgo -p . --noEmit"},"_npmUser":{"name":"amritk","email":"amrit+spam@hockey-community.com"},"repository":{"url":"git+https://github.com/amritk/mini.git","type":"git","directory":"packages/lynx-dialogs"},"_npmVersion":"11.16.0","description":"Native date pickers and action sheets for Lynx: an Android and iOS native module, and a promise-shaped facade that reaches it from the main thread.","directories":{},"sideEffects":false,"_nodeVersion":"26.3.0","dependencies":{"@amritk/mini-lynx-native":"workspace:*"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"typescript":"^5"},"peerDependencies":{"typescript":"^5"},"peerDependenciesMeta":{"typescript":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/lynx-dialogs_0.1.0_1785895735953_0.3268205310496035","host":"s3://npm-registry-packages-npm-production"}},"0.2.0":{"name":"@amritk/lynx-dialogs","version":"0.2.0","keywords":["lynx","date-picker","action-sheet","dialog","native-module","typescript","mjst"],"author":{"name":"amritk"},"license":"MIT","_id":"@amritk/lynx-dialogs@0.2.0","maintainers":[{"name":"amritk","email":"amrit+spam@hockey-community.com"}],"homepage":"https://github.com/amritk/mini/tree/main/packages/lynx-dialogs#readme","bugs":{"url":"https://github.com/amritk/mini/issues"},"dist":{"shasum":"e97dff1ecbef789176af60e78155db36539ba76b","tarball":"https://registry.npmjs.org/@amritk/lynx-dialogs/-/lynx-dialogs-0.2.0.tgz","fileCount":54,"integrity":"sha512-fXtX7XMJihoxh5bTMchrwU6PqtYgW0uOVo2HSH1nbK0UKR5Lna5Vq+Acxn8Tgd/+Y2TFgtyN6Nv/fJ42yC7w1A==","signatures":[{"sig":"MEYCIQCs1nn4OJmN7gKD3YoGDVjRUW1mMf1tThC5UVJzVjQkLwIhAMzMNOVqn4xsGRLJME6WwX56jG+e9dhCUtJSI99QtS5p","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@amritk%2flynx-dialogs@0.2.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":175374},"type":"module","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","default":"./dist/index.js"},"./testing":{"types":"./dist/testing/index.d.ts","import":"./dist/testing/index.js","default":"./dist/testing/index.js"},"./package.json":"./package.json","./lynx.lib.json":"./lynx.lib.json"},"gitHead":"9c6b6bc96beba6a5f106f5e1bf5d7d0ab3b762c1","scripts":{"test":"NODE_ENV=production vitest run --root ../.. packages/lynx-dialogs/","build":"tsgo -p tsconfig.build.json && tsc-alias -p tsconfig.build.json -f && node ../../scripts/strip-comments.mjs","types:check":"tsgo -p . --noEmit"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:79adbabe-f165-4db6-849b-49c669d783fd"}},"repository":{"url":"git+https://github.com/amritk/mini.git","type":"git","directory":"packages/lynx-dialogs"},"_npmVersion":"12.0.2","description":"Native date pickers and action sheets for Lynx: an Android and iOS native module, and a promise-shaped facade that reaches it from the main thread.","directories":{},"sideEffects":false,"_nodeVersion":"24.18.0","dependencies":{"@amritk/mini-lynx-native":"0.2.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"typescript":"^5"},"peerDependencies":{"typescript":"^5"},"peerDependenciesMeta":{"typescript":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/lynx-dialogs_0.2.0_1785905545904_0.15121862195929192","host":"s3://npm-registry-packages-npm-production"}},"0.2.1":{"name":"@amritk/lynx-dialogs","version":"0.2.1","keywords":["lynx","date-picker","action-sheet","dialog","native-module","typescript","mjst"],"author":{"name":"amritk"},"license":"MIT","_id":"@amritk/lynx-dialogs@0.2.1","maintainers":[{"name":"amritk","email":"amrit+spam@hockey-community.com"}],"homepage":"https://github.com/amritk/mini/tree/main/packages/lynx-dialogs#readme","bugs":{"url":"https://github.com/amritk/mini/issues"},"dist":{"shasum":"23da25702e81f7f5ffa659fbbeb8b4d19eaeb917","tarball":"https://registry.npmjs.org/@amritk/lynx-dialogs/-/lynx-dialogs-0.2.1.tgz","fileCount":54,"integrity":"sha512-is0oVZZxll8ZQ4FM8+1h/f3NeFG5onfjwIfTEXjZzAebtH3pKJagYLL1aydQDHS0vgL71OoRziJUbleR2Rj8JA==","signatures":[{"sig":"MEYCIQDIB7fAnR790IHEH6nS3b5n79O1U3f9jY9j0uYlYwBsyQIhALuZtVuswPSiLoyEpNLAWSo9/pDDAbS8tMbm3iejchR3","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@amritk%2flynx-dialogs@0.2.1","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":175440},"type":"module","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","default":"./dist/index.js"},"./testing":{"types":"./dist/testing/index.d.ts","import":"./dist/testing/index.js","default":"./dist/testing/index.js"},"./package.json":"./package.json","./lynx.lib.json":"./lynx.lib.json"},"gitHead":"9c78706160a9e76fc5a6e31ef33a1b7c5fdfeb03","scripts":{"test":"NODE_ENV=production vitest run --root ../.. packages/lynx-dialogs/","build":"tsgo -p tsconfig.build.json && tsc-alias -p tsconfig.build.json -f && node ../../scripts/strip-comments.mjs","types:check":"tsgo -p . --noEmit","prepublishOnly":"node ../../scripts/check-publishable.mjs"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:79adbabe-f165-4db6-849b-49c669d783fd"}},"repository":{"url":"git+https://github.com/amritk/mini.git","type":"git","directory":"packages/lynx-dialogs"},"_npmVersion":"12.0.2","description":"Native date pickers and action sheets for Lynx: an Android and iOS native module, and a promise-shaped facade that reaches it from the main thread.","directories":{},"sideEffects":false,"_nodeVersion":"24.18.0","dependencies":{"@amritk/mini-lynx-native":"0.2.1"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"typescript":"^5"},"peerDependencies":{"typescript":"^5"},"peerDependenciesMeta":{"typescript":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/lynx-dialogs_0.2.1_1785949011558_0.11305692091327546","host":"s3://npm-registry-packages-npm-production"}},"0.2.2":{"name":"@amritk/lynx-dialogs","version":"0.2.2","description":"Native date pickers and action sheets for Lynx: an Android and iOS native module, and a promise-shaped facade that reaches it from the main thread.","type":"module","license":"MIT","author":{"name":"amritk"},"sideEffects":false,"keywords":["lynx","date-picker","action-sheet","dialog","native-module","typescript","mjst"],"repository":{"type":"git","url":"git+https://github.com/amritk/mini.git","directory":"packages/lynx-dialogs"},"homepage":"https://github.com/amritk/mini/tree/main/packages/lynx-dialogs#readme","bugs":{"url":"https://github.com/amritk/mini/issues"},"publishConfig":{"access":"public"},"scripts":{"build":"tsgo -p tsconfig.build.json && tsc-alias -p tsconfig.build.json -f && node ../../scripts/strip-comments.mjs","prepublishOnly":"node ../../scripts/check-publishable.mjs","types:check":"tsgo -p . --noEmit","test":"NODE_ENV=production vitest run --root ../.. packages/lynx-dialogs/"},"exports":{"./package.json":"./package.json","./lynx.lib.json":"./lynx.lib.json",".":{"types":"./dist/index.d.ts","import":"./dist/index.js","default":"./dist/index.js"},"./testing":{"types":"./dist/testing/index.d.ts","import":"./dist/testing/index.js","default":"./dist/testing/index.js"}},"dependencies":{"@amritk/mini-lynx-native":"0.2.2"},"peerDependencies":{"typescript":"^5"},"peerDependenciesMeta":{"typescript":{"optional":true}},"devDependencies":{"typescript":"^5"},"gitHead":"c2516a9f0a935e9e8e1eda316d651ac48e0ccc71","_id":"@amritk/lynx-dialogs@0.2.2","_nodeVersion":"24.19.0","_npmVersion":"12.0.2","dist":{"integrity":"sha512-13EjAfO3WVTPGaj42C8L874ca5zqAT6sMuKc5g85aeVkdWFFm13XoqI/sTH6zIjP8jrdAt0QW8FvYq6s/SnrDw==","shasum":"a37814960ccd336c56fdd1dfcd78b820ab233a79","tarball":"https://registry.npmjs.org/@amritk/lynx-dialogs/-/lynx-dialogs-0.2.2.tgz","fileCount":54,"unpackedSize":175440,"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@amritk%2flynx-dialogs@0.2.2","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQD1bx+QuJ3yhRsn6VncUqlgEVd062JCzeRBBBfrTS6xDQIgNNzz6NfvT/5dXh5aZypuEmeFmHH33Op+kfFSanhy29M="}]},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:79adbabe-f165-4db6-849b-49c669d783fd"}},"directories":{},"maintainers":[{"name":"amritk","email":"amrit+spam@hockey-community.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/lynx-dialogs_0.2.2_1787351375371_0.2106219180972353"},"_hasShrinkwrap":false}},"time":{"created":"2026-08-05T02:08:55.748Z","modified":"2026-08-21T22:29:35.830Z","0.1.0":"2026-08-05T02:08:56.077Z","0.2.0":"2026-08-05T04:52:26.042Z","0.2.1":"2026-08-05T16:56:51.689Z","0.2.2":"2026-08-21T22:29:35.502Z"},"bugs":{"url":"https://github.com/amritk/mini/issues"},"author":{"name":"amritk"},"license":"MIT","homepage":"https://github.com/amritk/mini/tree/main/packages/lynx-dialogs#readme","keywords":["lynx","date-picker","action-sheet","dialog","native-module","typescript","mjst"],"repository":{"type":"git","url":"git+https://github.com/amritk/mini.git","directory":"packages/lynx-dialogs"},"description":"Native date pickers and action sheets for Lynx: an Android and iOS native module, and a promise-shaped facade that reaches it from the main thread.","maintainers":[{"name":"amritk","email":"amrit+spam@hockey-community.com"}],"readme":"# @amritk/lynx-dialogs\n\n**The platform's own date picker, action sheet and alert, for Lynx.** An Android\nnative module, an iOS native module, and a promise-shaped facade that reaches\nthem from a main-thread [`@amritk/mini-lynx`](../mini-lynx) tree.\n\n```ts\nimport { presentActionSheet, presentAlert, presentDatePicker } from '@amritk/lynx-dialogs'\n\nconst date = await presentDatePicker({ mode: 'date', maximum: Date.now() })\nif (date.ok) console.log(new Date(date.value))\n\nconst choice = await presentActionSheet({\n  title: 'Photo',\n  actions: [{ label: 'Replace' }, { label: 'Delete', destructive: true }],\n})\nif (choice.ok) apply(choice.index)\n\nconst confirm = await presentAlert({\n  title: 'Delete this photo?',\n  message: 'This cannot be undone.',\n  buttons: [{ label: 'Cancel', style: 'cancel' }, { label: 'Delete', style: 'destructive' }],\n})\nif (confirm.ok && confirm.index === 1) remove()\n```\n\n## Why this exists\n\nLynx ships no picker element and no modal module, and the two things published\nagainst the gap do not fill it:\n\n- [`@lynx-js/lynx-ui-sheet`](https://www.npmjs.com/package/@lynx-js/lynx-ui-sheet)\n  draws a sheet out of ReactLynx elements. It is a good component and it is not\n  a native sheet — no system dismissal gestures, no iPad popover, and it is\n  ReactLynx-only.\n- [`@sigx/lynx-datetime-picker`](https://www.npmjs.com/package/@sigx/lynx-datetime-picker)\n  *is* a real `UIDatePicker`/`DatePickerDialog`, bolted to another framework's\n  bridge — it depends on `@sigx/lynx-core` and cannot be reached from here.\n\nNothing native exists for action sheets or alerts at all. Lynx's official answer\nis \"write native code and send it into your Lynx code\", so that is what this is.\n\nThe parts that make these controls feel right are the parts you cannot draw: the\nwheel's deceleration and haptics, the calendar's locale and first-day-of-week,\nthe popover an iPad expects instead of a bottom sheet, the system dismissal\ngestures, and every accessibility behaviour a screen reader user relies on.\n\n## Install\n\n```sh\nbun add @amritk/lynx-dialogs\n```\n\n`@amritk/mini-lynx-native` comes with it — it is the wire every call travels.\n\n## JavaScript setup\n\nOne line, in your **background** chunk:\n\n```ts\nimport { installNativeBridge } from '@amritk/mini-lynx-native/background'\n\ninstallNativeBridge()\n```\n\nWithout it every call queues forever and nothing says why. `NativeModules` is a\nbackground-thread global and a `mini-lynx` tree renders on the main thread; the\nbridge is what crosses between them.\n\n## Host app setup\n\nThe native side links itself — `lynx.lib.json` declares the Android and iOS\nsources and Lynx's autolinking picks them up.\n\n**There is nothing else to do.** No permission, no `Info.plist` key, no manifest\nentry: presenting a dialog is not a capability either platform gates. That is\nthe one way this package is simpler than\n[`@amritk/lynx-notifications`](../lynx-notifications) and\n[`@amritk/lynx-location`](../lynx-location), both of which need host-app\ndeclarations before they will work at all.\n\nIf your build does not run Lynx's annotation processor, register by hand:\n\n```kotlin\nLynxEnv.inst().registerModule(\"MiniLynxDialogsModule\", MiniLynxDialogsModule::class.java)\n```\n\n```objc\n[config registerModule:MiniLynxDialogsModule.class];\n```\n\n## The surface\n\n```ts\npresentDatePicker(options?: DatePickerOptions): Promise<DatePickerResult>\npresentActionSheet(options: ActionSheetOptions): Promise<ActionSheetResult>\npresentAlert(options: AlertOptions): Promise<AlertResult>\ndismissActiveDialog(): Promise<void>\nareDialogsAvailable(): Promise<boolean>\n```\n\n### Which one\n\nAn **alert** interrupts: centred, about what the user just did, a question with\na small number of answers. An **action sheet** offers: from the edge, about what\nthe user is pointing at, a list of what can be done to it. The practical test is\nthe count — three buttons is Android's hard cap for an alert, so anything longer\nis a sheet.\n\nEvery outcome is a discriminated union, never a rejection:\n\n```ts\ntype DatePickerResult =\n  | { ok: true; value: number }                                        // epoch milliseconds\n  | { ok: false; reason: 'dismissed' | 'busy' | 'unavailable'; message: string }\n\ntype ActionSheetResult =\n  | { ok: true; index: number }                                        // a position in `actions`\n  | { ok: false; reason: 'dismissed' | 'busy' | 'unavailable'; message: string }\n\ntype AlertResult =\n  | { ok: true; index: number }                                        // a position in `buttons`\n  | { ok: false; reason: 'dismissed' | 'busy' | 'unavailable'; message: string }\n```\n\nCancelling is the single most likely thing a user does with a dialog, so it is a\nbranch rather than an exception. `reason` is `'dismissed'` for a cancel button, a\ntap outside, an Android back gesture and a swipe down on the iOS sheet alike.\n\n## What differs between the platforms\n\nEverything here is a real platform limit rather than a shortcut, and each is\ndocumented at the option it affects.\n\n| | iOS | Android |\n| --- | --- | --- |\n| `mode: 'datetime'` | one `UIDatePicker` | **two dialogs in sequence** — there has never been a combined widget |\n| `minimum` / `maximum` | honoured in `date` and `datetime` | same — `TimePickerDialog` has no bounds |\n| `minuteInterval` | `UIDatePicker.minuteInterval` | **ignored** — no equivalent exists |\n| `use24HourClock` | a locale substitution, since `UIDatePicker` has no switch | passed straight to `TimePickerDialog` |\n| `anchor` | positions the iPad popover | ignored — a dialog is centred by the window manager |\n| default cancel label | localised by the system on the picker; English on the sheet | localised by the system |\n| alert buttons | would stack any number; capped at three to match | **exactly three slots** — the API caps at three |\n| alert dismissal | none — an alert has no gesture out, only its buttons | back gesture and tap outside both cancel |\n\nCancelling **either** step of an Android `datetime` cancels the whole thing. A\nhalf-answered datetime is not a value worth handing back.\n\n`AlertButtons` is a one-to-three tuple rather than an array, so the Android cap\nis a type error instead of a button that quietly goes missing on half the\ndevices. At most one button may carry `style: 'cancel'` — `UIAlertController`\nraises on a second, and the native side demotes extras rather than crashing.\n\n## One dialog at a time\n\nA presentation made while another is on screen resolves\n`{ ok: false, reason: 'busy' }` and changes nothing. This is a rule the package\nenforces rather than a limit it ran into — the alternatives are worse than they\nsound. iOS refuses to present a second modal over an unfinished one and logs a\nwarning nobody reads, so the promise would simply never settle; Android stacks\ndialogs into something the user has to dismiss twice.\n\nIn practice it fires when a double tap sends two presentations, and ignoring it\nis usually right.\n\n## Dismissing on navigation\n\nA dialog belongs to the platform's window, not to the Lynx view, so nothing\ntears it down when the component that opened it goes away:\n\n```tsx\nimport { onCleanup } from '@amritk/mini-lynx'\n\nonCleanup(() => void dismissActiveDialog())\n```\n\nThe presentation's own promise then settles as `{ ok: false, reason: 'dismissed' }`.\n\n## Testing without a device\n\n`@amritk/lynx-dialogs/testing` exports the native module's contract as something\na test can drive:\n\n```ts\nimport { createFakeDialogs } from '@amritk/lynx-dialogs/testing'\nimport { MODULE } from '@amritk/lynx-dialogs'\n\nconst dialogs = createFakeDialogs()\ninstallNativeBridge({ peer, emitter, modules: { [MODULE]: dialogs.module } })\n\nconst pending = presentDatePicker({ mode: 'date' })\ndialogs.confirmDate(Date.now())      // or dialogs.cancel(), which is the branch worth testing\n```\n\n`chooseAction(index)` answers a sheet, `chooseButton(index)` answers an alert,\nand `presented()` says what is on screen.\n\nIt reproduces the platforms rather than smoothing them over: a second\npresentation is refused, a disabled row cannot be chosen, a value outside the\npicker's bounds cannot be confirmed, and an alert with no buttons is refused\nrather than presented.\n\n## No signals here\n\nThe surface is promises, deliberately. A second edge onto the signal engine is\nhow a consumer ends up with two reactive graphs that cannot see each other's\nwrites, and wiring a promise into whichever graph you already have is one line.\nIt also means this works unchanged from ReactLynx, Vue Lynx, or plain\nbackground-thread code.\n\n## What is verified, and what is not\n\nThe facade and the fake run here. `bun run check:android` compiles the Kotlin\nagainst the real Lynx AAR and `src/native-contract.test.ts` pins the method\nsurfaces against each other. `pod lib lint` compiles the Objective-C against the\nreal iOS SDK, but it is no longer part of CI — run it by hand on a Mac.\n\n**None of that is a device.** See [`AGENTS.md`](./AGENTS.md) for exactly what\neach check does and does not cover.\n\n## License\n\nMIT\n","readmeFilename":"README.md"}