{"_id":"@cujuju/solidjs-pill-date-picker","_rev":"3-9a5a1189325ac17d52aaeacf942ed93e","name":"@cujuju/solidjs-pill-date-picker","dist-tags":{"latest":"0.2.0"},"versions":{"0.1.0":{"name":"@cujuju/solidjs-pill-date-picker","version":"0.1.0","keywords":["solidjs","date-picker","expiration","dte","options","popout"],"author":{"name":"Cujuju"},"license":"MIT","_id":"@cujuju/solidjs-pill-date-picker@0.1.0","maintainers":[{"name":"cujuju","email":"cujuju@pm.me"}],"homepage":"https://github.com/Cujuju/solidjs-toolkit/tree/main/packages/pill-date-picker","bugs":{"url":"https://github.com/Cujuju/solidjs-toolkit/issues"},"dist":{"shasum":"c25dd14605cbacfab52d4a604e556d4056f277a0","tarball":"https://registry.npmjs.org/@cujuju/solidjs-pill-date-picker/-/solidjs-pill-date-picker-0.1.0.tgz","fileCount":18,"integrity":"sha512-kiD8fLlMbI3jvnw+X22oZrzMkCAjSEo2/MxB21uuVKNSDSwBo116kfucdDORzr3RuftdRthu21QbglEPbcEdPQ==","signatures":[{"sig":"MEYCIQCkcp/Dbz22ajVyxwb22K76Av0CQ4lWUMwXQ+I4liJcKwIhAOG4Zsfs4AwLZnURQBtCR9gUAUtwwO55jKml69Y7R8M4","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":228027},"main":"./dist/index.js","type":"module","_from":"file:cujuju-solidjs-pill-date-picker-0.1.0.tgz","types":"./dist/index.d.ts","module":"./dist/index.js","exports":{".":{"solid":"./src/index.ts","types":"./dist/index.d.ts","import":"./dist/index.js"},"./styles.css":"./src/styles.css"},"scripts":{"test":"vitest run","build":"vite build","test:watch":"vitest"},"_npmUser":{"name":"cujuju","email":"cujuju@pm.me"},"_resolved":"/tmp/ea647520be0ca66fd533ab2a07a7c1e1/cujuju-solidjs-pill-date-picker-0.1.0.tgz","_integrity":"sha512-kiD8fLlMbI3jvnw+X22oZrzMkCAjSEo2/MxB21uuVKNSDSwBo116kfucdDORzr3RuftdRthu21QbglEPbcEdPQ==","repository":{"url":"git+https://github.com/Cujuju/solidjs-toolkit.git","type":"git","directory":"packages/pill-date-picker"},"_npmVersion":"12.0.1","description":"Compact expiration / DTE picker for SolidJS — collapsed pill with a hover DTE tooltip, portalled pop-out ladder, caller-supplied dates, deterministic DTE math.","directories":{},"sideEffects":["**/*.css"],"_nodeVersion":"24.18.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"jsdom":"^24.0.0","vitest":"^3.2.4","vite-plugin-solid":"^2.11.12","@testing-library/jest-dom":"^6.9.1","@cujuju/solidjs-kv-tooltip":"0.2.0"},"peerDependencies":{"solid-js":">=1.7.0","@cujuju/solidjs-kv-tooltip":"0.2.0"},"_npmOperationalInternal":{"tmp":"tmp/solidjs-pill-date-picker_0.1.0_1784887085328_0.7788717471960156","host":"s3://npm-registry-packages-npm-production"}},"0.1.1":{"name":"@cujuju/solidjs-pill-date-picker","version":"0.1.1","keywords":["solidjs","date-picker","expiration","dte","options","popout"],"author":{"name":"Cujuju"},"license":"MIT","_id":"@cujuju/solidjs-pill-date-picker@0.1.1","maintainers":[{"name":"cujuju","email":"cujuju@pm.me"}],"homepage":"https://github.com/Cujuju/solidjs-toolkit/tree/main/packages/pill-date-picker","bugs":{"url":"https://github.com/Cujuju/solidjs-toolkit/issues"},"dist":{"shasum":"047ecb4e20f7c086e8d609ebd482d9da19e61487","tarball":"https://registry.npmjs.org/@cujuju/solidjs-pill-date-picker/-/solidjs-pill-date-picker-0.1.1.tgz","fileCount":18,"integrity":"sha512-Lr0jHNljejIVc1kxZ/P6uFGWV4ieiRAgAzLwy2AB+8T6Qj010jePPBI9Hex/0/PnUOD4IH2pD8fiDlHXczOpGA==","signatures":[{"sig":"MEQCIHp4yt/fyEzS6KhSggNjMI0j6cueo6YnkcT7og/YbSu0AiBtas1uPP1FIIhf9FcD6PDd+bLJqxd1oZWXsBnaq8M9ww==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":228029},"main":"./dist/index.js","type":"module","_from":"file:cujuju-solidjs-pill-date-picker-0.1.1.tgz","types":"./dist/index.d.ts","module":"./dist/index.js","exports":{".":{"solid":"./src/index.ts","types":"./dist/index.d.ts","import":"./dist/index.js"},"./styles.css":"./src/styles.css"},"scripts":{"test":"vitest run","build":"vite build","test:watch":"vitest"},"_npmUser":{"name":"cujuju","email":"cujuju@pm.me"},"_resolved":"/tmp/b8cc8dd7d7e24d9c943da11c1e5d4e06/cujuju-solidjs-pill-date-picker-0.1.1.tgz","_integrity":"sha512-Lr0jHNljejIVc1kxZ/P6uFGWV4ieiRAgAzLwy2AB+8T6Qj010jePPBI9Hex/0/PnUOD4IH2pD8fiDlHXczOpGA==","repository":{"url":"git+https://github.com/Cujuju/solidjs-toolkit.git","type":"git","directory":"packages/pill-date-picker"},"_npmVersion":"12.0.1","description":"Compact expiration / DTE picker for SolidJS — collapsed pill with a hover DTE tooltip, portalled pop-out ladder, caller-supplied dates, deterministic DTE math.","directories":{},"sideEffects":["**/*.css"],"_nodeVersion":"24.18.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"jsdom":"^24.0.0","vitest":"^3.2.4","vite-plugin-solid":"^2.11.12","@testing-library/jest-dom":"^6.9.1","@cujuju/solidjs-kv-tooltip":"^0.2.0"},"peerDependencies":{"solid-js":">=1.7.0","@cujuju/solidjs-kv-tooltip":"^0.2.0"},"_npmOperationalInternal":{"tmp":"tmp/solidjs-pill-date-picker_0.1.1_1784888182778_0.24928570549843254","host":"s3://npm-registry-packages-npm-production"}},"0.2.0":{"name":"@cujuju/solidjs-pill-date-picker","version":"0.2.0","description":"Compact expiration / DTE picker for SolidJS — collapsed pill with a hover DTE tooltip, portalled pop-out ladder, caller-supplied dates, per-row state (available / adjusted / disabled), a full row escape hatch, and deterministic DTE math.","type":"module","main":"./dist/index.js","module":"./dist/index.js","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","solid":"./src/index.ts","import":"./dist/index.js"},"./styles.css":"./src/styles.css"},"sideEffects":["**/*.css"],"license":"MIT","author":{"name":"Cujuju"},"repository":{"type":"git","url":"git+https://github.com/Cujuju/solidjs-toolkit.git","directory":"packages/pill-date-picker"},"homepage":"https://github.com/Cujuju/solidjs-toolkit/tree/main/packages/pill-date-picker","bugs":{"url":"https://github.com/Cujuju/solidjs-toolkit/issues"},"keywords":["solidjs","date-picker","expiration","dte","options","popout"],"peerDependencies":{"solid-js":">=1.7.0","@cujuju/solidjs-kv-tooltip":"^0.2.0"},"devDependencies":{"@testing-library/jest-dom":"^6.9.1","jsdom":"^24.0.0","vite-plugin-solid":"^2.11.12","vitest":"^3.2.4","@cujuju/solidjs-kv-tooltip":"^0.2.0"},"publishConfig":{"access":"public"},"scripts":{"test":"vitest run","test:watch":"vitest","build":"vite build"},"_id":"@cujuju/solidjs-pill-date-picker@0.2.0","_integrity":"sha512-BGuJg9zkgKEM1yXbVYMwj7cpaZULmB5Xyn7izES4Qysr3dFARRp89sd6kENf/QfYCKooOabM05UL4hoaTBmbVQ==","_resolved":"C:\\Users\\shawn\\AppData\\Local\\Temp\\15c0f7708776eca65dc71727a1d3b43e\\cujuju-solidjs-pill-date-picker-0.2.0.tgz","_from":"file:cujuju-solidjs-pill-date-picker-0.2.0.tgz","_nodeVersion":"25.8.2","_npmVersion":"11.12.1","dist":{"integrity":"sha512-BGuJg9zkgKEM1yXbVYMwj7cpaZULmB5Xyn7izES4Qysr3dFARRp89sd6kENf/QfYCKooOabM05UL4hoaTBmbVQ==","shasum":"0d04ebd9939682b5251b11e6c5b46fdb578dd08d","tarball":"https://registry.npmjs.org/@cujuju/solidjs-pill-date-picker/-/solidjs-pill-date-picker-0.2.0.tgz","fileCount":20,"unpackedSize":312268,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQCIs3eQagcpxgXUyQE+5kn3uOplxvlg7pQzficOL58SQgIgEW0dn2e6P7BzSSfaRNtH8Y9BWiyd4GQVcGcTTv+mS9s="}]},"_npmUser":{"name":"cujuju","email":"cujuju@pm.me"},"directories":{},"maintainers":[{"name":"cujuju","email":"cujuju@pm.me"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/solidjs-pill-date-picker_0.2.0_1784976510424_0.07481146151221929"},"_hasShrinkwrap":false}},"time":{"created":"2026-07-24T09:58:05.163Z","modified":"2026-07-25T10:48:30.801Z","0.1.0":"2026-07-24T09:58:05.458Z","0.1.1":"2026-07-24T10:16:22.912Z","0.2.0":"2026-07-25T10:48:30.593Z"},"bugs":{"url":"https://github.com/Cujuju/solidjs-toolkit/issues"},"author":{"name":"Cujuju"},"license":"MIT","homepage":"https://github.com/Cujuju/solidjs-toolkit/tree/main/packages/pill-date-picker","keywords":["solidjs","date-picker","expiration","dte","options","popout"],"repository":{"type":"git","url":"git+https://github.com/Cujuju/solidjs-toolkit.git","directory":"packages/pill-date-picker"},"description":"Compact expiration / DTE picker for SolidJS — collapsed pill with a hover DTE tooltip, portalled pop-out ladder, caller-supplied dates, per-row state (available / adjusted / disabled), a full row escape hatch, and deterministic DTE math.","maintainers":[{"name":"cujuju","email":"cujuju@pm.me"}],"readme":"# @cujuju/solidjs-pill-date-picker\n\nCompact expiration / DTE picker for SolidJS. Collapsed, it is the date alone (`Jul 17`) with\nthe DTE in a hover tooltip; expanded, it pops out a portalled ladder of every expiration with\nboth its DTE and its date.\n\nThe caller supplies the dates. The control does not fetch, validate, sort, or filter them — it\nrenders what it is given and computes the DTE itself.\n\n## Install\n\n```sh\npnpm add @cujuju/solidjs-pill-date-picker @cujuju/solidjs-kv-tooltip\n```\n\nPeer dependencies: `solid-js >= 1.7.0`, `@cujuju/solidjs-kv-tooltip`.\n\n## Usage\n\n```tsx\nimport { PillDatePicker } from '@cujuju/solidjs-pill-date-picker';\nimport '@cujuju/solidjs-pill-date-picker/styles.css';\n\nconst [expiry, setExpiry] = createSignal<string | null>('2026-07-17');\n\n<PillDatePicker\n  items={['2026-06-19', '2026-06-26', '2026-07-17', '2026-09-18']}\n  value={expiry()}\n  onChange={(item) => setExpiry(item)}\n  ariaLabel=\"Expiration\"\n/>\n```\n\n### Carrying your own payload\n\n`items` also accepts objects. Whatever else you hang off each entry comes back through\n`onChange` by reference — the ORIGINAL item, not a copy — with its types intact.\n\n```tsx\nconst chain = [\n  { date: '2026-06-19', oi: 12_400, monthly: false },\n  { date: '2026-07-17', oi: 98_100, monthly: true },\n];\n\n<PillDatePicker\n  items={chain}\n  value={expiry()}\n  onChange={(item) => {\n    setExpiry(item.date);\n    console.log(item.oi, item.monthly); // still there, still typed\n  }}\n  tooltipEntries={(item, dte) => ({\n    Expires: item.date,\n    DTE: `${dte}d`,\n    OI: item.oi.toLocaleString(),\n  })}\n/>\n```\n\n## Props\n\n| Prop | Default | Description |\n|---|---|---|\n| `items` | (required) | Ordered list of expirations. `string[]` of ISO dates, or objects with a `date` key. Rendered verbatim; assumed legitimate. |\n| `value` | — | The selected expiration, as its **key** (see `keyOf` — by default, its ISO date). Keyed by value, not by object identity, so a refetched chain does not silently deselect. |\n| `keyOf` | (the item's date) | The item's stable key. **Required when a date is not unique in your ladder** — see below. |\n| `onChange` | (required) | Fires with the ORIGINAL item. |\n| `now` | `new Date()` | The clock DTE is measured from. Inject it to make DTE deterministic (tests, replay, backtests). |\n| `size` | `'md'` | `'xs' \\| 'sm' \\| 'md'` — matches the sibling pill-number-picker's rhythm. |\n| `disabled` | `false` | |\n| `placeholder` | `'Select'` | Collapsed label when nothing is selected. |\n| `emptyMessage` | `'No expirations'` | Pop-out body when `items` is empty. |\n| `noneSelectableMessage` | `'Nothing selectable'` | Line above the rows when every row is `'disabled'`. |\n| `open` / `onOpenChange` | — | Controlled open state. Omit `open` for uncontrolled; `onOpenChange` fires either way. |\n| `popoutGap` | `4` | Gap in px between the pill and the panel. |\n| `preferPlacement` | `'bottom'` | Side to open toward when both fit. |\n| `dteRamp` | see below | Ordered urgency bands, first match wins. |\n| `formatDate` | `Jul 17` | Override the collapsed/row label — the escape hatch for locales. |\n| `tooltipEntries` | long date + DTE | Rows for the hover tooltip. Takes the whole item. |\n| `disableTooltip` | `false` | |\n| `ariaLabel`, `class` | — | Passthrough. |\n\n## `keyOf` — when one date is two contracts\n\nBy default the selection is keyed by the item's ISO date, which is correct for an ordinary ladder. It is **wrong for index options**: SPX (AM-settled) and SPXW (PM-settled) both expire on the third Friday, so a date-keyed ladder cannot tell them apart — and lights up *both* rows as selected.\n\nSupply the key and the ambiguity is gone:\n\n```tsx\nconst ladder = [\n  { id: 'SPX-2026-06-19',  date: '2026-06-19', settle: 'AM' },\n  { id: 'SPXW-2026-06-19', date: '2026-06-19', settle: 'PM' },\n];\n\n<PillDatePicker\n  items={ladder}\n  keyOf={(i) => i.id}\n  value={selectedId()}\n  onChange={(item) => setSelectedId(item.id)}\n/>\n```\n\n**The key must be stable across refetches**, which is why you supply it rather than the control deriving it. A tempting-but-broken choice is the item's **position** in the array: rebuild the ladder after a weekly expires and position 3 names a *different* expiration, so the selection silently moves to the wrong contract. That is worse than deselecting — nothing about it looks wrong. Use an id, an OCC root, `${date}:${settlement}` — anything that names the **contract**, not its slot.\n\n## Row state — `itemState` + `annotation`\n\n\"Can I pick this?\" is not a boolean in a real ladder, so a row is one of three things:\n\n| State | Pickable | Looks like |\n|---|---|---|\n| `available` | yes | an ordinary row |\n| `adjusted` | **yes** | tinted, with your `annotation` — a row with a caveat, not a lesser row |\n| `disabled` | no | dimmed, `aria-disabled`, **still visible** |\n\n```tsx\n<PillDatePicker\n  items={dates}\n  itemState={(d) => listsMyStrike(d) ? 'available'\n                  : listsAnything(d) ? 'adjusted' : 'disabled'}\n  annotation={(d) => nearestRung(d)}   // '≈ 150' · 'no puts listed'\n  …\n/>\n```\n\nWhen EVERY row is disabled the rows still render, with `noneSelectableMessage` stated once above\nthem — a user should be told that nothing is takeable, not discover it by clicking five rows. That\nis a different fact from an empty ladder, which keeps its own `emptyMessage`.\n\nA disabled row stays in the list on purpose. Filtering it out of `items` is the obvious\nalternative and it lies: the user cannot tell \"not available **to you**\" from \"does not exist\",\nand a ladder missing dates misrepresents the calendar it is supposed to be showing.\n\nDisabled is enforced, not merely styled. The row cannot be committed by clicking it, by clicking\nanything a custom row nested inside it, or by pressing Enter on it; the arrow keys **step over**\nit, and the pointer will not move the cursor onto it — the highlight must never sit somewhere\nEnter refuses to act. The one deliberate exception: opening with a selection that has *since*\nbecome disabled leaves the cursor on that row, because moving it would make Enter pick a value\nthe user never chose. The commit guard refuses it and the first arrow key leaves.\n\nThe annotation's wording is yours. This package has no vocabulary for *why* a date is what it is\n— that is domain knowledge, and a date picker inventing it would be guessing. Keep it to a few\ncharacters; the long form belongs in `tooltipEntries`.\n\n## `renderRow` — when three columns are not enough\n\nReplaces what is **inside** a row, never the row element itself. The package keeps\n`role=\"option\"`, the state attributes, the selection rail, the cursor, click-to-commit and the\ndisabled guarantee — the parts that are easy to get wrong and invisible when they are.\n\n```tsx\nrenderRow={(ctx) => (\n  <>\n    <span>{ctx.label}</span>                       {/* honours formatDate */}\n    <span>{ctx.item.kind} · {ctx.item.oi}</span>   {/* your payload */}\n    <span style={{ color: ctx.dteColor }}>{ctx.dteLabel}</span>\n  </>\n)}\n```\n\n`ctx` carries `item`, `date`, `label`, `dte`, `dteLabel`, `dteColor`, `state`, `annotation`,\n`selected`, `active`, `index`. These are the values the BUILT-IN row renders, not raw lookups —\nnotably `dteColor` is `undefined` on a `'disabled'` row, because the default row drops the urgency\nramp there and one state should not look like two different things. Call the exported\n`resolveDteColor` yourself if you want the raw ramp. Everything that can change is a **getter**, so reading it inside\nyour JSX stays live — a custom row sees the cursor move and the selection change without doing\nanything special. (Handing over a plain snapshot is the trap this avoids: `ctx.active` would be\nfrozen at first paint and no consumer would have done anything wrong.)\n\nReach for `itemState` + `annotation` first. They keep every consumer's ladder looking like the\nsame control; `renderRow` is for the row shape they cannot express.\n\n## DTE\n\nDTE is a **calendar-day** difference, not an elapsed-time division. `(expiry - now) / 86400000`\nis the obvious implementation and it is wrong twice: it drifts with the time of day (34 days at\n09:00, 33 at 23:00 — same date), and doing the arithmetic on local dates makes a DST boundary a\n23- or 25-hour day, so the floor lands one day off. Both ends are collapsed to UTC midnight\nfirst, `now` via its **local** calendar fields (a trader at 20:00 ET on the 16th is on the 16th).\n\nNegative DTEs are returned as-is. You own which dates are legitimate; clamping an expired entry\nto `0d` would hide your bug behind our formatting.\n\n## DTE colour\n\nThe urgency ramp is a **prop**, because \"urgent\" is a house opinion:\n\n```tsx\n<PillDatePicker\n  dteRamp={[\n    { maxDte: 0,   color: 'var(--danger)' },\n    { maxDte: 7,   color: 'var(--warn)' },\n    { maxDte: 30,  color: 'var(--caution)' },\n    { maxDte: Infinity, color: 'var(--muted)' },\n  ]}\n  ...\n/>\n```\n\nBounds are inclusive upper edges, consulted in order, first match wins. The default ramp resolves\nto `--pdp-dte-expiring` / `--pdp-dte-urgent` / `--pdp-dte-near` / `--pdp-dte-far`, so the common\ncase is re-themed from your stylesheet without touching the prop at all.\n\n## The pop-out\n\nThe panel is rendered through a `<Portal>` and positioned in **viewport** coordinates. That is\nnot a stylistic choice: the pill lives in a dense row, dense rows live inside `overflow: hidden`\nand `overflow-y: auto` boxes, and an in-flow expansion is clipped dead by such an ancestor. It\nrepositions on resize and on a **capturing** scroll listener — the container that scrolls the\nanchor out from under the panel is almost never `window`.\n\nPrefers to open **downward** (a list reads top-down from its trigger), flipping up when there is\nno room below, and clamping — top edge first — when there is room on neither side.\n\n## Theming\n\n```css\n:root {\n  --pdp-bg: #1e293b;\n  --pdp-border: #334155;\n  --pdp-text: #e2e8f0;\n  --pdp-text-muted: #94a3b8;\n  --pdp-radius: 6px;\n  --pdp-hover-bg: rgba(255, 255, 255, 0.06);\n\n  --pdp-popout-z: 1000;\n  --pdp-popout-bg: #1e293b;\n  --pdp-popout-border: #334155;\n  --pdp-popout-shadow: 0 6px 18px rgba(0, 0, 0, 0.45);\n  --pdp-popout-min-width: 132px;\n  --pdp-popout-max-height: 260px;\n\n  --pdp-row-hover-bg: rgba(255, 255, 255, 0.07);\n  --pdp-row-selected-bg: rgba(99, 102, 241, 0.18);\n\n  --pdp-dte-expiring: #f87171;\n  --pdp-dte-urgent: #fb923c;\n  --pdp-dte-near: #fbbf24;\n  --pdp-dte-far: #94a3b8;\n}\n```\n\n## License\n\nMIT\n","readmeFilename":"README.md"}