{"_id":"@andystevensname/poem-element","_rev":"5-a743bcca5ea18ba4e0454c9d24647851","name":"@andystevensname/poem-element","dist-tags":{"latest":"1.0.3"},"versions":{"1.0.2":{"name":"@andystevensname/poem-element","version":"1.0.2","keywords":["web component","poetry","custom element","line numbering"],"author":{"name":"Andy Stevens"},"license":"MIT","_id":"@andystevensname/poem-element@1.0.2","maintainers":[{"name":"andystevensname","email":"taylorstevens@gmail.com"}],"homepage":"https://github.com/andystevensname/poem-element#readme","bugs":{"url":"https://github.com/andystevensname/poem-element/issues"},"dist":{"shasum":"eda200d98cce6fc51b11505313c21492ac9aac73","tarball":"https://registry.npmjs.org/@andystevensname/poem-element/-/poem-element-1.0.2.tgz","fileCount":6,"integrity":"sha512-17OuT6Rdb7DbeGq8uXmH/ptNgzWAfSp7VxxPEKhUandFf+SM+6vqe5Z+yTITEqsly5xu9uVhqqfZFOXbaaodSQ==","signatures":[{"sig":"MEYCIQC75kq+mc36kTFKFVqc2Ds+n00bUb8k9Nj6MhYI5UvGiQIhAJx8Cw5DdX/mCJQiN2JT4HeK3GH6ZlTQT/HSRDZl5qGV","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":29902},"main":"poem-element.js","type":"module","module":"poem-element.js","exports":{".":"./poem-element.js","./ssr":"./poem-element-ssr.js","./core":"./poem-element-core.js"},"gitHead":"b0e7e2ceb0ffa4edf03fa01600cff14276f908e5","scripts":{"serve":"npx serve ."},"_npmUser":{"name":"andystevensname","email":"taylorstevens@gmail.com"},"repository":{"url":"git+https://github.com/andystevensname/poem-element.git","type":"git"},"_npmVersion":"11.3.0","description":"A web component for displaying poetry with advanced formatting and line numbering.","directories":{},"_nodeVersion":"24.0.0","publishConfig":{"access":"public","registry":"https://registry.npmjs.org/"},"_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/poem-element_1.0.2_1775934320209_0.9481897280116993","host":"s3://npm-registry-packages-npm-production"}},"1.0.3":{"name":"@andystevensname/poem-element","publishConfig":{"access":"public","registry":"https://registry.npmjs.org/"},"version":"1.0.3","description":"A web component for displaying poetry with advanced formatting and line numbering.","main":"poem-element.js","module":"poem-element.js","type":"module","exports":{".":"./poem-element.js","./ssr":"./poem-element-ssr.js","./core":"./poem-element-core.js"},"scripts":{"serve":"npx serve ."},"keywords":["web component","poetry","custom element","line numbering"],"author":{"name":"Andy Stevens"},"license":"MIT","repository":{"type":"git","url":"git+https://github.com/andystevensname/poem-element.git"},"gitHead":"2af12cde1a06a00c5cfa917772ff64cee25b5b7d","_id":"@andystevensname/poem-element@1.0.3","bugs":{"url":"https://github.com/andystevensname/poem-element/issues"},"homepage":"https://github.com/andystevensname/poem-element#readme","_nodeVersion":"24.14.1","_npmVersion":"11.11.0","dist":{"integrity":"sha512-BlJuY+KR8QS0IRvEIF/UuUmVtSL3tQFAxABgDPi0twjIZiTcBVYvkgbAs67EY3UeOQDZqycbVNEnpEOmFzssng==","shasum":"f5168c22a62e60d875c87bfa6dbb055b82866f18","tarball":"https://registry.npmjs.org/@andystevensname/poem-element/-/poem-element-1.0.3.tgz","fileCount":6,"unpackedSize":29902,"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@andystevensname%2fpoem-element@1.0.3","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIHHOzYyu0zUSLqzMdozb0RNF2CmjdZF6RvKwh6ckhBteAiEArX5LyO9GvTpVcwesQhS21NXTm5r596ydZtUZbJTaev4="}]},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:b04258f3-8897-4c46-bdd2-b6413efad4aa"}},"directories":{},"maintainers":[{"name":"andystevensname","email":"taylorstevens@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/poem-element_1.0.3_1775934414903_0.8426350226523833"},"_hasShrinkwrap":false}},"time":{"created":"2026-04-11T19:05:20.116Z","modified":"2026-04-11T19:06:55.345Z","1.0.0":"2026-04-10T13:36:26.169Z","1.0.1":"2026-04-10T13:45:15.032Z","1.0.2":"2026-04-11T19:05:20.364Z","1.0.3":"2026-04-11T19:06:55.045Z"},"bugs":{"url":"https://github.com/andystevensname/poem-element/issues"},"author":{"name":"Andy Stevens"},"license":"MIT","homepage":"https://github.com/andystevensname/poem-element#readme","keywords":["web component","poetry","custom element","line numbering"],"repository":{"type":"git","url":"git+https://github.com/andystevensname/poem-element.git"},"description":"A web component for displaying poetry with advanced formatting and line numbering.","maintainers":[{"name":"andystevensname","email":"taylorstevens@gmail.com"}],"readme":"# poem-element\n\n## Overview\n`<poem-element>` was developed to help solve the following difficulties with displaying poems on the web.\n\n- **No semantic element**: A poem is composed of lines grouped into stanzas. Many developers wrap stanzas or lines in paragraph tags (`<p>`), which, while understandable, is semanticaly incorrect. Preformatted text (`<pre>`) is a better option, but by itself it's limited.\n- **No control over line formatting**: No single text element can offer control over individual lines.\n- **Difficult to set within a reponsive site design**: Poems in print have always been constrained by the page, and while a responsive web design provides more options to adapt to a poem than physical media, sometimes a poem needs to respond to the constraints of a site design.\n\n## Features\n### Element Options\n`<poem-element>` allows for the following per-poem options.\n\n- **Line wrap**: `<poem-element>` defaults to horizontal scrolling when the content box is constrained by a parent element. Optionally, `<poem-element>` can wrap longer lines with or without a hanging indent, and with or without a graphic glyph denoting the line wrap. \n\n- **Line numbers**: Line numbers are a booksetting convention for longer poems or academic settings. Two layout modes are available: \n  - `grid` (default) uses a CSS grid layout to display lines and line numbers.\n  -  `list` uses a browser's built-in list-item counter with a `::marker` pseudo element, and is only available with wrapped poems.\n\n### Other Features\n- **Accessibility**: `<poem-element>` uses `role=\"group\"` with a configurable `aria-label` on the poem container to help screen readers announce the poem as a whole, and `role=\"none\"` on individual lines to prevent screen readers from announcing list items. Line numbers are marked `aria-hidden`. In non-wrapping mode, the poem container is keyboard-focusable (`tabindex=\"0\"`) so users can scroll horizontally with arrow keys.\n\n- **Server-side rendering**: `<poem-element>` supports Declarative Shadow DOM for instant rendering without JavaScript. A Node.js SSR helper renders the complete HTML output when used with [Astro](https://astro.build), [11ty](https://www.11ty.dev), or any templating system.\n\n- **Print support**: `poem-element` provides a `@media print` rule that forces text wrapping and removes overflow clipping, allowing poems to render completely when printed or saved as a PDF.\n\n- **External styling**: `<poem-element>` exposes `part` attributes and CSS custom properties, allowing developers to style the component via a site's main stylesheet.\n\n## Installation\n\n```bash\nnpm install poem-element\n```\n\nOr include the script directly:\n\n```html\n<script type=\"module\" src=\"poem-element.js\"></script>\n```\n\n## Usage\n\n### Basic usage\n```html\n<poem-element>\nBuffalo Bill 's\ndefunct\n               who used to\n               ride a watersmooth-silver\n                                                              stallion\nand break onetwothreefourfive pigeonsjustlikethat\n                                                                                        Jesus\nhe was a handsome man\n                                            and what i want to know is\nhow do you like your blueeyed boy\nMister Death\n</poem-element>\n```\n\n### Line wrap\n```html\n<!-- Standard wrap -->\n<poem-element wrap>\nThis is the forest primeval. The murmuring pines and the hemlocks,\nBearded with moss, and in garments green, indistinct in the twilight,\n</poem-element>\n\n<!-- Indented wrap -->\n<poem-element wrap=\"indent\">\nShall I compare thee to a summer's day?\nThou art more lovely and more temperate:\n</poem-element>\n\n<!-- Indented wrap with continuation arrow -->\n<poem-element wrap=\"indent-arrow\">\nShall I compare thee to a summer's day?\nThou art more lovely and more temperate:\n</poem-element>\n```\n\n### Line numbers\n```html\n<!-- Numbers every 5th line (default, grid layout) -->\n<poem-element numbers>\n...\n</poem-element>\n\n<!-- Numbers every 4th line -->\n<poem-element numbers=\"4\">\n...\n</poem-element>\n\n<!-- Numbers with indented wrap (grid layout, default) -->\n<poem-element numbers wrap=\"indent\">\n...\n</poem-element>\n\n<!-- Numbers with list layout (only available with wrap) -->\n<poem-element numbers numbers-layout=\"list\" wrap=\"indent\">\n...\n</poem-element>\n\n<!-- Numbers positioned outside (in the margin) -->\n<poem-element numbers numbers-position=\"outside\" wrap=\"indent\">\n...\n</poem-element>\n\n<!-- Numbers on the right side (grid layout only) -->\n<poem-element numbers numbers-align=\"right\">\n...\n</poem-element>\n```\n\n### Accessibility\n```html\n<poem-element aria-label=\"Sonnet 18 by William Shakespeare\" numbers wrap=\"indent\">\nShall I compare thee to a summer's day?\n...\n</poem-element>\n```\n\n### FOUC prevention\n\nAdd this to your page CSS to prevent a flash of unstyled content. This will preserve the poem's whitespace while hiding its content while the component registers:\n\n```css\npoem-element:not(:defined) {\n  visibility: hidden;\n}\n```\n\n## Demo\n\nBecause the web component is served over HTTP, you'll need to run the included local server to view the demo:\n\n```bash\nnpx serve .\n```\n\nThen open `index.html` in your browser.\n\n## API Reference\n\n### Attributes\n\n| Attribute | Values | Default | Description |\n|-----------|--------|---------|-------------|\n| `wrap` | (boolean), `\"indent\"`, `\"indent-arrow\"` | No line wrap | Controls line wrapping behavior. Adding this attribute without a value forces horizontal scrolling. Adding one of the values changes the wrap behavior. |\n| `numbers` | (boolean), or a positive integer | 5 | Enables line numbering. Adding this attribute without a value creates line numbers every 5th line. A number sets the interval. |\n| `numbers-layout` | `\"grid\"`, `\"list\"` | `\"grid\"` | `grid` uses CSS Grid with DOM elements for numbers. `list` uses `display: list-item` with `::marker` for numbers (only effective with `wrap`; silently ignored without it). |\n| `numbers-position` | `\"inside\"`, `\"outside\"` | `\"inside\"` | `inside` positions numbers flush with surrounding content. `outside` hangs numbers in the left margin. |\n| `numbers-align` | `\"right\"` | left | Places line numbers to the right of the poem text. Only applies to grid layout. |\n| `aria-label` | any string | `\"poem\"` | Accessible label for the poem container. Forwarded to the inner `role=\"group\"` element. |\n\n### Properties\n\n| Property | Type | Description |\n|----------|------|-------------|\n| `text` | `string` | Get or set the poem text. Setting triggers a re-render. |\n\n### Methods\n\n| Method | Description |\n|--------|-------------|\n| `render()` | Manually re-render the component. Called automatically on attribute changes. |\n| `scheduleRender()` | Queue a render via microtask. Multiple calls are debounced into a single render. |\n\n### Events\n\n| Event | Detail | Description |\n|-------|--------|-------------|\n| `poem-rendered` | `{ lines: number, config: object }` | Fired after each render completes. Bubbles and crosses shadow DOM boundaries (`composed: true`). |\n\n## Customization\n\n### CSS `::part()` Selectors\n\nInternal elements expose `part` attributes for direct styling:\n\n```css\n/* Style the poem container (font, color, background, line-height, etc.) */\npoem-element::part(block) {\n  font-family: Georgia, serif;\n  font-size: 1.1em;\n  color: #333;\n  background: #fafafa;\n  line-height: 1.6;\n}\n\n/* Style individual poem lines */\npoem-element::part(line) {\n  color: #333;\n}\n\n/* Style line numbers (grid layout) */\npoem-element::part(line-number) {\n  color: #c00;\n}\n```\n\n### CSS Custom Properties\n\nThese properties control values that can't be styled through `::part()` — either because they're used in internal `calc()` expressions or because they target `::marker`, which isn't styleable via `::part()`.\n\n```css\npoem-element {\n  /* Layout calc values */\n  --poem-num-col: 3ch;         /* Width of the line number column */\n  --poem-num-gap: 0.5rem;      /* Gap between numbers and text */\n  --poem-text-indent: 2em;     /* Hanging indent depth for wrap=\"indent\" */\n\n  /* ::marker styling (not reachable via ::part) */\n  --poem-line-number-color: inherit;\n  --poem-line-number-font: inherit;\n  --poem-line-number-font-size: inherit;\n  --poem-line-number-font-weight: inherit;\n  --poem-line-number-line-height: inherit; /* Adjust when line number font-size differs from text */\n}\n```\n\nFor general text styling (font, color, background, line-height, etc.), use `::part()` selectors.\n\n## Server-Side Rendering\n\nThe SSR helper produces Declarative Shadow DOM output for instant rendering without JavaScript:\n\n```js\nimport { renderPoemElement } from 'poem-element/ssr';\n\nconst html = renderPoemElement(\n  `Shall I compare thee to a summer's day?\nThou art more lovely and more temperate:`,\n  {\n    numbers: true,\n    wrap: 'indent',\n    'aria-label': 'Sonnet 18',\n  }\n);\n```\n\nThe output includes a `<template shadowrootmode=\"open\">` with the fully rendered shadow DOM. The browser attaches it immediately on parse, with no JavaScript required for the initial render.\n\nWhen the client-side JS loads, it detects the existing DSD content and skips re-rendering. Dynamic attribute changes after load will trigger re-renders as normal.\n\n### Framework integration\n\n**Astro:**\n```astro\n---\nimport { renderPoemElement } from 'poem-element/ssr';\nconst html = renderPoemElement(`Shall I compare...`, { numbers: true, wrap: 'indent' });\n---\n<Fragment set:html={html} />\n<script>import 'poem-element';</script>\n```\n\n**11ty:**\n```js\nconst { renderPoemElement } = require('poem-element/ssr');\neleventyConfig.addShortcode('poem', (text, attrs) => renderPoemElement(text, attrs));\n```\n\n### Package exports\n\n```js\nimport 'poem-element';                    // Client-side custom element\nimport { renderPoemElement } from 'poem-element/ssr';   // SSR helper\nimport { parseAttributes, parseLines, transformLineText, formatLineNumber, generateCSS, STATIC_CSS, generateDynamicCSS } from 'poem-element/core'; // Shared utilities\n```","readmeFilename":"README.md"}