@invisibleloop/pulse 0.1.21
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.
- package/.claude/commands/build-page.md +59 -0
- package/.claude/commands/new-doc-page.md +45 -0
- package/.claude/commands/verify.md +52 -0
- package/.claude/pulse-checklist.md +111 -0
- package/.claude/settings.local.json +102 -0
- package/.github/workflows/ci.yml +22 -0
- package/.github/workflows/publish.yml +41 -0
- package/.pulse/load-reports/home/1773432711417.json +22 -0
- package/CLAUDE.md +383 -0
- package/README.md +95 -0
- package/docs/.claude/pulse-checklist.md +111 -0
- package/docs/public/.pulse-ui-version +1 -0
- package/docs/public/dist/accessibility.boot-5DVTARJU.js +115 -0
- package/docs/public/dist/actions.boot-P66HKQEM.js +164 -0
- package/docs/public/dist/auth.boot-IMAJAUPH.js +140 -0
- package/docs/public/dist/caching.boot-DVR6KDE7.js +53 -0
- package/docs/public/dist/components--accordion.boot-3HVKMNWC.js +11 -0
- package/docs/public/dist/components--alert.boot-GCEXOZAC.js +6 -0
- package/docs/public/dist/components--app-badge.boot-DVT3GCHJ.js +6 -0
- package/docs/public/dist/components--avatar.boot-PSW24EVA.js +5 -0
- package/docs/public/dist/components--badge.boot-TYDY2RMK.js +7 -0
- package/docs/public/dist/components--banner.boot-EI5PZSZK.js +7 -0
- package/docs/public/dist/components--breadcrumbs.boot-SMA2E2GO.js +34 -0
- package/docs/public/dist/components--button.boot-J54BQM2E.js +23 -0
- package/docs/public/dist/components--card.boot-PZGNDIB6.js +138 -0
- package/docs/public/dist/components--carousel.boot-TP6LPFZZ.js +12 -0
- package/docs/public/dist/components--charts.boot-2EOYQWKL.js +108 -0
- package/docs/public/dist/components--checkbox.boot-DS5BSL6T.js +54 -0
- package/docs/public/dist/components--cluster.boot-HHVIBBJG.js +9 -0
- package/docs/public/dist/components--code-window.boot-2GR2DV33.js +20 -0
- package/docs/public/dist/components--container.boot-7LOOGK2K.js +5 -0
- package/docs/public/dist/components--cta.boot-FSNZ5YRT.js +11 -0
- package/docs/public/dist/components--divider.boot-3NI2C3QG.js +6 -0
- package/docs/public/dist/components--empty.boot-YX2UR3PV.js +7 -0
- package/docs/public/dist/components--feature.boot-MUD7NSUO.js +13 -0
- package/docs/public/dist/components--fieldset.boot-J7BYHMKF.js +19 -0
- package/docs/public/dist/components--fileupload.boot-NIKVTTPD.js +52 -0
- package/docs/public/dist/components--footer.boot-EYUK5FRG.js +14 -0
- package/docs/public/dist/components--grid.boot-URDQVDDR.js +59 -0
- package/docs/public/dist/components--heading.boot-BPQKU43E.js +44 -0
- package/docs/public/dist/components--hero.boot-4RAPRGAB.js +17 -0
- package/docs/public/dist/components--icons.boot-ZITNU5JP.js +68 -0
- package/docs/public/dist/components--image.boot-XEEGHQZF.js +19 -0
- package/docs/public/dist/components--input.boot-SGASZG5K.js +7 -0
- package/docs/public/dist/components--list.boot-W3XC5MHD.js +55 -0
- package/docs/public/dist/components--media.boot-5VFIETZO.js +13 -0
- package/docs/public/dist/components--modal.boot-RZUYXBN2.js +47 -0
- package/docs/public/dist/components--nav.boot-ODBOHU7O.js +33 -0
- package/docs/public/dist/components--pricing.boot-4AQ4ZVBY.js +21 -0
- package/docs/public/dist/components--progress.boot-GHAGYZOK.js +30 -0
- package/docs/public/dist/components--prose.boot-QANJL6JI.js +67 -0
- package/docs/public/dist/components--pullquote.boot-Q2WMNAZU.js +22 -0
- package/docs/public/dist/components--radio.boot-TJRDQ2OL.js +75 -0
- package/docs/public/dist/components--rating.boot-QBAN6DEL.js +38 -0
- package/docs/public/dist/components--search.boot-PXH5O5AG.js +17 -0
- package/docs/public/dist/components--section.boot-AQGIYHWW.js +12 -0
- package/docs/public/dist/components--segmented.boot-BEVTKEJO.js +33 -0
- package/docs/public/dist/components--select.boot-47X5RHOC.js +10 -0
- package/docs/public/dist/components--slider.boot-PSRRX7XL.js +47 -0
- package/docs/public/dist/components--spinner.boot-MZ5MO2OH.js +22 -0
- package/docs/public/dist/components--stack.boot-DI4NJXBF.js +9 -0
- package/docs/public/dist/components--stat.boot-QMFUWBQT.js +9 -0
- package/docs/public/dist/components--stepper.boot-34PP2NEV.js +22 -0
- package/docs/public/dist/components--table.boot-FCQGSFIQ.js +11 -0
- package/docs/public/dist/components--testimonial.boot-DWQPDKYG.js +11 -0
- package/docs/public/dist/components--textarea.boot-QVXLBOJ5.js +4 -0
- package/docs/public/dist/components--timeline.boot-26LN52P2.js +95 -0
- package/docs/public/dist/components--toggle.boot-IQQEI76S.js +29 -0
- package/docs/public/dist/components--tooltip.boot-LGHCO6NN.js +9 -0
- package/docs/public/dist/components.boot-SE6PQ4P7.js +103 -0
- package/docs/public/dist/config.boot-DTRRWUE6.js +126 -0
- package/docs/public/dist/constraints.boot-DUHDZBMC.js +71 -0
- package/docs/public/dist/deploy.boot-SLAD3NI2.js +163 -0
- package/docs/public/dist/docs-8e3d4b5c.css +1 -0
- package/docs/public/dist/extending.boot-UA3CN243.js +159 -0
- package/docs/public/dist/faq.boot-6EQAWLQR.js +43 -0
- package/docs/public/dist/getting-started.boot-TDKIFL5U.js +86 -0
- package/docs/public/dist/guard.boot-AUHAWTG4.js +80 -0
- package/docs/public/dist/home.boot-BVQXRH32.js +383 -0
- package/docs/public/dist/how-it-works.boot-LTWAKWKW.js +104 -0
- package/docs/public/dist/hydration.boot-JRM6IPJL.js +78 -0
- package/docs/public/dist/images.boot-M6ZVKTZS.js +80 -0
- package/docs/public/dist/manifest.json +94 -0
- package/docs/public/dist/meta.boot-7NXGPHR4.js +79 -0
- package/docs/public/dist/mutations.boot-F6F43UDX.js +79 -0
- package/docs/public/dist/navigation.boot-AOXWS3ZF.js +57 -0
- package/docs/public/dist/performance.boot-C3UPCOBK.js +98 -0
- package/docs/public/dist/persist.boot-WT32PQOQ.js +61 -0
- package/docs/public/dist/project-structure.boot-FB3LRVJ4.js +63 -0
- package/docs/public/dist/prompt-examples.boot-YKR4VDK4.js +31 -0
- package/docs/public/dist/pulse-ui-81a85c03.css +1 -0
- package/docs/public/dist/raw-responses.boot-M4KA5YXL.js +104 -0
- package/docs/public/dist/routing.boot-FNX5FDGH.js +70 -0
- package/docs/public/dist/runtime-B73WLANC.js +1 -0
- package/docs/public/dist/runtime-KO4BHUQ3.js +49 -0
- package/docs/public/dist/runtime-L2HNXIHW.js +59 -0
- package/docs/public/dist/runtime-QFURDKA2.js +5 -0
- package/docs/public/dist/runtime-UVPXO4IR.js +375 -0
- package/docs/public/dist/runtime-VMJA3Z4N.js +10 -0
- package/docs/public/dist/runtime-ZJ4FXT5O.js +11 -0
- package/docs/public/dist/server-api.boot-K7X3LCFB.js +219 -0
- package/docs/public/dist/server-data.boot-Y7HQYC4R.js +157 -0
- package/docs/public/dist/slash-commands.boot-V2UV7OW2.js +26 -0
- package/docs/public/dist/spec.boot-2WU7ZHCV.js +159 -0
- package/docs/public/dist/state.boot-B24GUE3R.js +73 -0
- package/docs/public/dist/store.boot-TLIB4XHH.js +150 -0
- package/docs/public/dist/streaming.boot-W2DZSMW4.js +80 -0
- package/docs/public/dist/stripe.boot-QN3C2GEL.js +164 -0
- package/docs/public/dist/supabase.boot-BG4XXLZE.js +303 -0
- package/docs/public/dist/testing.boot-6U4WKMTE.js +130 -0
- package/docs/public/dist/validation.boot-PQHYGW5B.js +100 -0
- package/docs/public/docs.css +2020 -0
- package/docs/public/menu.js +83 -0
- package/docs/public/pulse-ui.css +2739 -0
- package/docs/public/pulse-ui.js +236 -0
- package/docs/server.js +192 -0
- package/docs/src/lib/component-page.js +47 -0
- package/docs/src/lib/highlight.js +255 -0
- package/docs/src/lib/layout.js +131 -0
- package/docs/src/lib/metrics-store.js +6 -0
- package/docs/src/lib/nav.js +159 -0
- package/docs/src/lib/stats.js +81 -0
- package/docs/src/pages/accessibility.js +157 -0
- package/docs/src/pages/actions.js +191 -0
- package/docs/src/pages/auth.js +177 -0
- package/docs/src/pages/caching.js +95 -0
- package/docs/src/pages/components/accordion.js +48 -0
- package/docs/src/pages/components/alert.js +35 -0
- package/docs/src/pages/components/app-badge.js +41 -0
- package/docs/src/pages/components/avatar.js +35 -0
- package/docs/src/pages/components/badge.js +36 -0
- package/docs/src/pages/components/banner.js +45 -0
- package/docs/src/pages/components/breadcrumbs.js +94 -0
- package/docs/src/pages/components/button.js +84 -0
- package/docs/src/pages/components/card.js +225 -0
- package/docs/src/pages/components/carousel.js +72 -0
- package/docs/src/pages/components/charts.js +278 -0
- package/docs/src/pages/components/checkbox.js +129 -0
- package/docs/src/pages/components/cluster.js +47 -0
- package/docs/src/pages/components/code-window.js +57 -0
- package/docs/src/pages/components/container.js +40 -0
- package/docs/src/pages/components/cta.js +53 -0
- package/docs/src/pages/components/divider.js +37 -0
- package/docs/src/pages/components/empty.js +36 -0
- package/docs/src/pages/components/feature.js +60 -0
- package/docs/src/pages/components/fieldset.js +65 -0
- package/docs/src/pages/components/fileupload.js +127 -0
- package/docs/src/pages/components/footer.js +58 -0
- package/docs/src/pages/components/grid.js +165 -0
- package/docs/src/pages/components/heading.js +107 -0
- package/docs/src/pages/components/hero.js +65 -0
- package/docs/src/pages/components/icons.js +285 -0
- package/docs/src/pages/components/image.js +71 -0
- package/docs/src/pages/components/input.js +51 -0
- package/docs/src/pages/components/list.js +112 -0
- package/docs/src/pages/components/media.js +51 -0
- package/docs/src/pages/components/modal.js +111 -0
- package/docs/src/pages/components/nav.js +86 -0
- package/docs/src/pages/components/pricing.js +68 -0
- package/docs/src/pages/components/progress.js +102 -0
- package/docs/src/pages/components/prose.js +111 -0
- package/docs/src/pages/components/pullquote.js +71 -0
- package/docs/src/pages/components/radio.js +194 -0
- package/docs/src/pages/components/rating.js +106 -0
- package/docs/src/pages/components/search.js +61 -0
- package/docs/src/pages/components/section.js +59 -0
- package/docs/src/pages/components/segmented.js +121 -0
- package/docs/src/pages/components/select.js +45 -0
- package/docs/src/pages/components/slider.js +114 -0
- package/docs/src/pages/components/spinner.js +73 -0
- package/docs/src/pages/components/stack.js +48 -0
- package/docs/src/pages/components/stat.js +55 -0
- package/docs/src/pages/components/stepper.js +66 -0
- package/docs/src/pages/components/table.js +45 -0
- package/docs/src/pages/components/testimonial.js +49 -0
- package/docs/src/pages/components/textarea.js +31 -0
- package/docs/src/pages/components/timeline.js +227 -0
- package/docs/src/pages/components/toggle.js +84 -0
- package/docs/src/pages/components/tooltip.js +48 -0
- package/docs/src/pages/components.js +204 -0
- package/docs/src/pages/config.js +193 -0
- package/docs/src/pages/constraints.js +99 -0
- package/docs/src/pages/deploy.js +233 -0
- package/docs/src/pages/extending.js +198 -0
- package/docs/src/pages/faq.js +96 -0
- package/docs/src/pages/getting-started.js +106 -0
- package/docs/src/pages/guard.js +121 -0
- package/docs/src/pages/home.js +401 -0
- package/docs/src/pages/how-it-works.js +183 -0
- package/docs/src/pages/hydration.js +98 -0
- package/docs/src/pages/images.js +121 -0
- package/docs/src/pages/meta.js +120 -0
- package/docs/src/pages/mutations.js +106 -0
- package/docs/src/pages/navigation.js +85 -0
- package/docs/src/pages/performance.js +157 -0
- package/docs/src/pages/persist.js +88 -0
- package/docs/src/pages/project-structure.js +90 -0
- package/docs/src/pages/prompt-examples.js +186 -0
- package/docs/src/pages/raw-responses.js +124 -0
- package/docs/src/pages/routing.js +99 -0
- package/docs/src/pages/server-api.js +281 -0
- package/docs/src/pages/server-data.js +185 -0
- package/docs/src/pages/slash-commands.js +55 -0
- package/docs/src/pages/spec.js +207 -0
- package/docs/src/pages/state.js +101 -0
- package/docs/src/pages/store.js +181 -0
- package/docs/src/pages/streaming.js +108 -0
- package/docs/src/pages/stripe.js +193 -0
- package/docs/src/pages/supabase.js +323 -0
- package/docs/src/pages/testing.js +198 -0
- package/docs/src/pages/validation.js +138 -0
- package/examples/contact.js +166 -0
- package/examples/counter.js +94 -0
- package/examples/dev.server.js +91 -0
- package/examples/examples.test.js +394 -0
- package/examples/pricing.js +244 -0
- package/examples/products.js +191 -0
- package/examples/quiz.js +208 -0
- package/examples/shared.js +78 -0
- package/examples/todos.js +162 -0
- package/package.json +75 -0
- package/public/.pulse-ui-version +1 -0
- package/public/chippy-bird.css +246 -0
- package/public/examples/contact.css +119 -0
- package/public/examples/counter.css +79 -0
- package/public/examples/pricing.css +132 -0
- package/public/examples/products.css +100 -0
- package/public/examples/quiz.css +200 -0
- package/public/examples/todos.css +137 -0
- package/public/favicon.ico +0 -0
- package/public/log-dashboard.css +383 -0
- package/public/pulse-ui.css +2740 -0
- package/public/pulse-ui.js +236 -0
- package/public/pulse.css +149 -0
- package/scripts/build.js +411 -0
- package/src/agent/checklist.md +111 -0
- package/src/agent/coverage-check.js +66 -0
- package/src/agent/guide-components.md +274 -0
- package/src/agent/guide-examples.md +54 -0
- package/src/agent/guide-routing.md +36 -0
- package/src/agent/guide-server.md +258 -0
- package/src/agent/guide-spec.md +103 -0
- package/src/agent/guide-styles.md +191 -0
- package/src/agent/guide.md +979 -0
- package/src/agent/identity.md +106 -0
- package/src/agent/workflow.md +108 -0
- package/src/cli/cli.test.js +82 -0
- package/src/cli/dev.js +195 -0
- package/src/cli/discover.js +113 -0
- package/src/cli/index.js +361 -0
- package/src/cli/load-report.js +91 -0
- package/src/cli/load-runner.js +121 -0
- package/src/cli/report-server.js +723 -0
- package/src/cli/report.js +116 -0
- package/src/cli/scaffold.archive.js +1371 -0
- package/src/cli/scaffold.js +349 -0
- package/src/cli/start.js +74 -0
- package/src/html.js +19 -0
- package/src/mcp/server.js +884 -0
- package/src/mcp/validate-worker.js +110 -0
- package/src/runtime/image.js +74 -0
- package/src/runtime/image.test.js +111 -0
- package/src/runtime/index.js +621 -0
- package/src/runtime/navigate.js +146 -0
- package/src/runtime/runtime.test.js +773 -0
- package/src/runtime/ssr.js +464 -0
- package/src/runtime/ssr.test.js +421 -0
- package/src/runtime/store.js +92 -0
- package/src/runtime/toast.js +163 -0
- package/src/server/index.js +1386 -0
- package/src/server/server.test.js +1248 -0
- package/src/spec/schema.js +428 -0
- package/src/spec/schema.test.js +291 -0
- package/src/store/index.js +102 -0
- package/src/store/store.test.js +210 -0
- package/src/testing/html.js +283 -0
- package/src/testing/index.js +249 -0
- package/src/testing/testing.test.js +450 -0
- package/src/ui/accordion.js +28 -0
- package/src/ui/alert.js +43 -0
- package/src/ui/app-badge.js +48 -0
- package/src/ui/avatar.js +47 -0
- package/src/ui/badge.js +24 -0
- package/src/ui/banner.js +26 -0
- package/src/ui/breadcrumbs.js +38 -0
- package/src/ui/button.js +66 -0
- package/src/ui/card.js +34 -0
- package/src/ui/carousel.js +59 -0
- package/src/ui/charts.js +321 -0
- package/src/ui/checkbox.js +65 -0
- package/src/ui/cluster.js +44 -0
- package/src/ui/code-window.js +39 -0
- package/src/ui/container.js +24 -0
- package/src/ui/cta.js +37 -0
- package/src/ui/divider.js +29 -0
- package/src/ui/empty.js +33 -0
- package/src/ui/feature.js +33 -0
- package/src/ui/fieldset.js +37 -0
- package/src/ui/fileupload.js +89 -0
- package/src/ui/footer.js +38 -0
- package/src/ui/grid.js +36 -0
- package/src/ui/heading.js +45 -0
- package/src/ui/hero.js +37 -0
- package/src/ui/icons.js +161 -0
- package/src/ui/index.js +89 -0
- package/src/ui/input.js +74 -0
- package/src/ui/list.js +36 -0
- package/src/ui/media.js +44 -0
- package/src/ui/modal.js +80 -0
- package/src/ui/nav.js +61 -0
- package/src/ui/pricing.js +56 -0
- package/src/ui/progress.js +62 -0
- package/src/ui/prose.js +29 -0
- package/src/ui/pullquote.js +34 -0
- package/src/ui/radio.js +102 -0
- package/src/ui/rating.js +93 -0
- package/src/ui/search.js +77 -0
- package/src/ui/section.js +69 -0
- package/src/ui/segmented.js +50 -0
- package/src/ui/select.js +77 -0
- package/src/ui/slider.js +84 -0
- package/src/ui/spinner.js +34 -0
- package/src/ui/stack.js +36 -0
- package/src/ui/stat.js +52 -0
- package/src/ui/stepper.js +46 -0
- package/src/ui/switch.js +57 -0
- package/src/ui/table.js +45 -0
- package/src/ui/testimonial.js +48 -0
- package/src/ui/textarea.js +72 -0
- package/src/ui/timeline.js +72 -0
- package/src/ui/tooltip.js +28 -0
- package/src/ui/ui.test.js +1241 -0
- package/src/ui/uiimage.js +65 -0
- package/tsconfig.json +13 -0
- package/types/html.d.ts +17 -0
- package/types/image.d.ts +70 -0
- package/types/index.d.ts +7 -0
- package/types/navigate.d.ts +38 -0
- package/types/runtime.d.ts +63 -0
- package/types/schema.d.ts +243 -0
- package/types/server.d.ts +145 -0
- package/types/ssr.d.ts +110 -0
- package/types/testing.d.ts +154 -0
- package/types/ui.d.ts +704 -0
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import{a}from"./runtime-QFURDKA2.js";import{a as r,b as c,c as l,d,e,g as t,h as i,i as u}from"./runtime-L2HNXIHW.js";import{a as s,b as m}from"./runtime-B73WLANC.js";var{prev:p,next:g}=r("/accessibility"),n={route:"/accessibility",meta:{title:"Accessibility \u2014 Pulse Docs",description:"Keyboard navigation, focus management, semantic HTML, and ARIA patterns in Pulse.",styles:["/docs.css"]},state:{},view:()=>c({currentHref:"/accessibility",prev:p,next:g,content:`
|
|
2
|
+
${l("Accessibility")}
|
|
3
|
+
${d("Pulse enforces a 100 Lighthouse Accessibility score as the baseline. The foundations \u2014 skip link, focus styles, and focus management on navigation \u2014 are provided by the framework on every page. There is nothing to configure and nothing to forget.")}
|
|
4
|
+
|
|
5
|
+
${e("built-in","What the framework provides")}
|
|
6
|
+
${i(["Feature","How"],[["Skip link","Injected on every page as the first focusable element. Targets <code>#main-content</code>. Visible only on focus."],["Focus styles","<code>:focus-visible</code> outline applied globally \u2014 purple, 3px, offset 2px. Suppressed for mouse users via <code>:focus:not(:focus-visible)</code>."],["Navigation focus","After client-side navigation, focus moves to <code>#main-content</code>, <code><main></code>, or <code><h1></code> \u2014 whichever is found first. Screen reader users hear the new page heading without a full reload."]])}
|
|
7
|
+
${u("note",'The skip link targets <code>#main-content</code>. Pages use <code><main id="main-content"></code> as the content wrapper so the link resolves correctly.')}
|
|
8
|
+
|
|
9
|
+
${e("structure","Page structure")}
|
|
10
|
+
<p>Each page view is wrapped in <code><main id="main-content"></code> with a single <code><h1></code> that describes the current page. Landmark elements communicate structure to assistive technology:</p>
|
|
11
|
+
${t(a(`view: (state) => \`
|
|
12
|
+
<main id="main-content">
|
|
13
|
+
<h1>Page title</h1>
|
|
14
|
+
<!-- page content -->
|
|
15
|
+
</main>
|
|
16
|
+
\``,"js"))}
|
|
17
|
+
${i(["Element","Purpose"],[["<code><header></code>","Site header, logo, primary nav"],["<code><nav></code>","Navigation links \u2014 <code>aria-label</code> distinguishes multiple navs"],['<code><main id="main-content"></code>',"Primary page content \u2014 one per page"],["<code><aside></code>","Supplementary content (sidebars, related links)"],["<code><footer></code>","Site footer"]])}
|
|
18
|
+
|
|
19
|
+
${e("interactive","Interactive elements")}
|
|
20
|
+
<p>Actions are expressed as <code><button></code> elements, navigation as <code><a href></code> links. Both are keyboard-accessible by default. <code><div></code> and <code><span></code> elements with click handlers are not reachable by keyboard:</p>
|
|
21
|
+
${t(a(`<!-- Keyboard accessible -->
|
|
22
|
+
<button data-event="toggle">Open menu</button>
|
|
23
|
+
<a href="/about">About</a>
|
|
24
|
+
|
|
25
|
+
<!-- Not keyboard accessible \u2014 avoid -->
|
|
26
|
+
<div data-event="toggle">Open menu</div>
|
|
27
|
+
<span onclick="...">About</span>`,"html"))}
|
|
28
|
+
<p>Buttons that toggle state carry <code>aria-expanded</code> or <code>aria-pressed</code> to communicate the current state to screen readers:</p>
|
|
29
|
+
${t(a(`view: (state) => \`
|
|
30
|
+
<button data-event="toggleMenu" aria-expanded="\${state.menuOpen}">
|
|
31
|
+
Menu
|
|
32
|
+
</button>
|
|
33
|
+
\${state.menuOpen ? \`<nav>...</nav>\` : ''}
|
|
34
|
+
\``,"js"))}
|
|
35
|
+
|
|
36
|
+
${e("focus","Focus management")}
|
|
37
|
+
<p>When a modal or dialog opens, focus moves inside it. When it closes, focus returns to the element that opened it. Since Pulse updates the DOM via morphing rather than a full replacement, the triggering element stays in the DOM and receives focus back naturally.</p>
|
|
38
|
+
<p>The <code>autofocus</code> attribute on the first interactive element inside newly revealed content moves focus there after the DOM update \u2014 no JavaScript required:</p>
|
|
39
|
+
${t(a(`view: (state) => \`
|
|
40
|
+
<button data-event="openDialog">Delete item</button>
|
|
41
|
+
|
|
42
|
+
\${state.dialogOpen ? \`
|
|
43
|
+
<div role="dialog" aria-modal="true" aria-labelledby="dialog-title">
|
|
44
|
+
<h2 id="dialog-title">Confirm deletion</h2>
|
|
45
|
+
<p>This cannot be undone.</p>
|
|
46
|
+
<button autofocus data-event="confirmDelete">Delete</button>
|
|
47
|
+
<button data-event="closeDialog">Cancel</button>
|
|
48
|
+
</div>
|
|
49
|
+
\` : ''}
|
|
50
|
+
\``,"js"))}
|
|
51
|
+
|
|
52
|
+
${e("live-regions","Dynamic content announcements")}
|
|
53
|
+
<p>State changes that produce status messages \u2014 loading indicators, confirmations, validation errors \u2014 are wrapped in live regions so screen readers announce them without a page reload:</p>
|
|
54
|
+
${i(["Role","Politeness","Used for"],[['<code>role="status"</code>',"Polite \u2014 waits for the user to finish","Loading states, success messages, counts"],['<code>role="alert"</code>',"Assertive \u2014 interrupts immediately","Validation errors, destructive confirmations"]])}
|
|
55
|
+
${t(a(`view: (state) => \`
|
|
56
|
+
<form data-action="submit">
|
|
57
|
+
<!-- fields -->
|
|
58
|
+
<button type="submit" \${state.status === 'loading' ? 'disabled' : ''}>
|
|
59
|
+
\${state.status === 'loading' ? 'Saving\u2026' : 'Save'}
|
|
60
|
+
</button>
|
|
61
|
+
</form>
|
|
62
|
+
|
|
63
|
+
\${state.status === 'loading' ? \`
|
|
64
|
+
<p role="status">Saving\u2026</p>
|
|
65
|
+
\` : ''}
|
|
66
|
+
|
|
67
|
+
\${state.errors.length ? \`
|
|
68
|
+
<div role="alert">
|
|
69
|
+
\${state.errors.map(e => \`<p>\${e.message}</p>\`).join('')}
|
|
70
|
+
</div>
|
|
71
|
+
\` : ''}
|
|
72
|
+
\``,"js"))}
|
|
73
|
+
|
|
74
|
+
${e("forms","Forms")}
|
|
75
|
+
<p>Form controls are paired with <code><label></code> elements. Error messages are linked to their input via <code>aria-describedby</code> so screen readers announce them when the field receives focus:</p>
|
|
76
|
+
${t(a(`<form data-action="submit">
|
|
77
|
+
<div class="field">
|
|
78
|
+
<label for="email">Email</label>
|
|
79
|
+
<input
|
|
80
|
+
id="email"
|
|
81
|
+
name="email"
|
|
82
|
+
type="email"
|
|
83
|
+
required
|
|
84
|
+
aria-describedby="\${state.emailError ? 'email-error' : ''}"
|
|
85
|
+
>
|
|
86
|
+
\${state.emailError
|
|
87
|
+
? \`<p id="email-error" role="alert">\${state.emailError}</p>\`
|
|
88
|
+
: ''}
|
|
89
|
+
</div>
|
|
90
|
+
|
|
91
|
+
<fieldset>
|
|
92
|
+
<legend>Notification preferences</legend>
|
|
93
|
+
<label><input type="checkbox" name="email-notifs"> Email</label>
|
|
94
|
+
<label><input type="checkbox" name="sms-notifs"> SMS</label>
|
|
95
|
+
</fieldset>
|
|
96
|
+
|
|
97
|
+
<button type="submit">Submit</button>
|
|
98
|
+
</form>`,"html"))}
|
|
99
|
+
|
|
100
|
+
${e("images","Images")}
|
|
101
|
+
<p>Decorative images carry <code>alt=""</code> so screen readers skip them. Informative images have descriptive alt text. Icon-only buttons are labelled with <code>aria-label</code>, with the icon marked <code>aria-hidden="true"</code>:</p>
|
|
102
|
+
${t(a(`<!-- Informative image -->
|
|
103
|
+
<img src="/team.jpg" alt="The Pulse team at the 2025 offsite" width="800" height="450">
|
|
104
|
+
|
|
105
|
+
<!-- Decorative image -->
|
|
106
|
+
<img src="/divider.svg" alt="" width="600" height="4">
|
|
107
|
+
|
|
108
|
+
<!-- Icon-only button -->
|
|
109
|
+
<button aria-label="Close">
|
|
110
|
+
<svg aria-hidden="true" focusable="false">...</svg>
|
|
111
|
+
</button>`,"html"))}
|
|
112
|
+
|
|
113
|
+
${e("lighthouse","Lighthouse score")}
|
|
114
|
+
<p>Every page is expected to score 100 on Lighthouse Accessibility. Run <code>/pulse-report</code> after every new page or significant change. Regressions \u2014 contrast failures, missing labels, unreachable controls \u2014 are caught before they reach users.</p>
|
|
115
|
+
`})};var o=document.getElementById("pulse-root");o&&!o.dataset.pulseMounted&&(o.dataset.pulseMounted="1",s(n,o,window.__PULSE_SERVER__||{},{ssr:!0}),m(o,s));var k=n;export{k as default};
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import{a as o}from"./runtime-QFURDKA2.js";import{a as i,b as d,c,d as l,e,g as t,h as u,i as a}from"./runtime-L2HNXIHW.js";import{a as s,b as m}from"./runtime-B73WLANC.js";var{prev:p,next:f}=i("/actions"),n={route:"/actions",meta:{title:"Actions \u2014 Pulse Docs",description:"Async operations in Pulse \u2014 lifecycle, FormData, error handling.",styles:["/docs.css"]},state:{},view:()=>d({currentHref:"/actions",prev:p,next:f,content:`
|
|
2
|
+
${c("Actions")}
|
|
3
|
+
${l("Actions handle async operations with an enforced lifecycle. The order of steps \u2014 capture inputs, validate, run, succeed or fail \u2014 is fixed. Skipping validation or running async work before showing a loading state is not possible within the action structure.")}
|
|
4
|
+
|
|
5
|
+
${e("lifecycle","The action lifecycle")}
|
|
6
|
+
<p>When a form with <code>data-action</code> is submitted, Pulse runs the action through a fixed sequence of steps:</p>
|
|
7
|
+
${t(o(`onStart(state, formData)
|
|
8
|
+
\u2193 (optional) validate \u2014 checks spec.validation rules
|
|
9
|
+
run(state, serverState, formData)
|
|
10
|
+
\u2193 success \u2193 error
|
|
11
|
+
onSuccess(state, payload) onError(state, err)`,"bash"))}
|
|
12
|
+
<p>Each step triggers a view re-render, so the UI always reflects the current state \u2014 loading, validated, succeeded, or failed. The sequence cannot be reordered.</p>
|
|
13
|
+
|
|
14
|
+
${e("defining","Defining an action")}
|
|
15
|
+
${t(o(`export default {
|
|
16
|
+
route: '/contact',
|
|
17
|
+
state: {
|
|
18
|
+
status: 'idle', // 'idle' | 'loading' | 'success' | 'error'
|
|
19
|
+
errors: [],
|
|
20
|
+
},
|
|
21
|
+
validation: {
|
|
22
|
+
'fields.name': { required: true, minLength: 2 },
|
|
23
|
+
'fields.email': { required: true, format: 'email' },
|
|
24
|
+
'fields.message': { required: true, minLength: 10 },
|
|
25
|
+
},
|
|
26
|
+
actions: {
|
|
27
|
+
submit: {
|
|
28
|
+
// 1. Immediately update state \u2014 show loading indicator
|
|
29
|
+
onStart: (state, formData) => ({
|
|
30
|
+
status: 'loading',
|
|
31
|
+
errors: [],
|
|
32
|
+
// Capture form values into state before validation runs
|
|
33
|
+
fields: {
|
|
34
|
+
name: formData.get('name'),
|
|
35
|
+
email: formData.get('email'),
|
|
36
|
+
message: formData.get('message'),
|
|
37
|
+
},
|
|
38
|
+
}),
|
|
39
|
+
|
|
40
|
+
// 2. Run spec.validation before proceeding to run()
|
|
41
|
+
validate: true,
|
|
42
|
+
|
|
43
|
+
// 3. Perform the async work
|
|
44
|
+
run: async (state, serverState, formData) => {
|
|
45
|
+
const res = await fetch('/api/contact', {
|
|
46
|
+
method: 'POST',
|
|
47
|
+
headers: { 'Content-Type': 'application/json' },
|
|
48
|
+
body: JSON.stringify(Object.fromEntries(formData)),
|
|
49
|
+
})
|
|
50
|
+
if (!res.ok) throw new Error('Request failed')
|
|
51
|
+
return res.json()
|
|
52
|
+
},
|
|
53
|
+
|
|
54
|
+
// 4a. Success
|
|
55
|
+
onSuccess: (state, payload) => ({
|
|
56
|
+
status: 'success',
|
|
57
|
+
errors: [],
|
|
58
|
+
}),
|
|
59
|
+
|
|
60
|
+
// 4b. Error \u2014 payload may have validation errors
|
|
61
|
+
onError: (state, err) => ({
|
|
62
|
+
status: 'error',
|
|
63
|
+
errors: err?.validation ?? [{ message: err.message }],
|
|
64
|
+
}),
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
}`,"js"))}
|
|
68
|
+
|
|
69
|
+
${e("binding","Binding actions to forms")}
|
|
70
|
+
<p>A <code>data-action</code> attribute on a <code><form></code> element binds it to an action. When the form is submitted, Pulse creates a <code>FormData</code> object from the form's inputs and passes it through the action lifecycle:</p>
|
|
71
|
+
${t(o(`<form data-action="submit">
|
|
72
|
+
<input name="name" type="text" placeholder="Your name">
|
|
73
|
+
<input name="email" type="email" placeholder="Email">
|
|
74
|
+
<textarea name="message" placeholder="Message"></textarea>
|
|
75
|
+
<button type="submit">Send</button>
|
|
76
|
+
</form>`,"html"))}
|
|
77
|
+
${a("note","Pulse intercepts and prevents the default form submission. The action lifecycle is fully in control of what happens with the data \u2014 no manual <code>event.preventDefault()</code> needed.")}
|
|
78
|
+
|
|
79
|
+
${e("on-start","onStart")}
|
|
80
|
+
<p><code>onStart(state, formData)</code> runs synchronously as soon as the form is submitted, before any async work begins. It sets a loading state, captures form values into state so validation can check them, and clears previous errors.</p>
|
|
81
|
+
${a("warning","<code>onStart</code> runs <strong>before</strong> validation. <code>FormData</code> values are captured into state first, because the HTML re-renders (destroying the form inputs) once validation runs. All form values are captured here so validation can read them from state via dot-paths.")}
|
|
82
|
+
|
|
83
|
+
${e("validate","validate")}
|
|
84
|
+
<p>Set <code>validate: true</code> to run the spec's <a href="/validation">validation rules</a> after <code>onStart</code>. If validation fails, <code>onError</code> is called immediately \u2014 <code>run</code> is never reached. Async work cannot execute against invalid input.</p>
|
|
85
|
+
${t(o(`// Validation error structure
|
|
86
|
+
{
|
|
87
|
+
message: 'Validation failed',
|
|
88
|
+
validation: [
|
|
89
|
+
{ field: 'fields.email', rule: 'format', message: 'Must be a valid email' },
|
|
90
|
+
{ field: 'fields.name', rule: 'required', message: 'Required' },
|
|
91
|
+
]
|
|
92
|
+
}`,"js"))}
|
|
93
|
+
<p>Access the errors in <code>onError</code>:</p>
|
|
94
|
+
${t(o(`onError: (state, err) => ({
|
|
95
|
+
status: 'error',
|
|
96
|
+
errors: err?.validation ?? [{ message: err.message }],
|
|
97
|
+
})`,"js"))}
|
|
98
|
+
|
|
99
|
+
${e("run","run")}
|
|
100
|
+
<p><code>run(state, serverState, formData)</code> is where the async work happens. Throw or reject to trigger <code>onError</code>. The return value is passed to <code>onSuccess</code> as <code>payload</code>.</p>
|
|
101
|
+
${t(o(`run: async (state, serverState, formData) => {
|
|
102
|
+
const res = await fetch('/api/submit', {
|
|
103
|
+
method: 'POST',
|
|
104
|
+
body: formData,
|
|
105
|
+
})
|
|
106
|
+
if (!res.ok) {
|
|
107
|
+
const err = await res.json()
|
|
108
|
+
throw Object.assign(new Error('Server error'), err)
|
|
109
|
+
}
|
|
110
|
+
return res.json() // \u2192 onSuccess payload
|
|
111
|
+
},`,"js"))}
|
|
112
|
+
|
|
113
|
+
${e("on-success","onSuccess")}
|
|
114
|
+
<p><code>onSuccess(state, payload)</code> receives the current state and whatever <code>run</code> returned. Return a partial state update:</p>
|
|
115
|
+
${t(o(`onSuccess: (state, payload) => ({
|
|
116
|
+
status: 'success',
|
|
117
|
+
userId: payload.id,
|
|
118
|
+
})`,"js"))}
|
|
119
|
+
|
|
120
|
+
${e("on-error","onError")}
|
|
121
|
+
<p><code>onError(state, err)</code> receives the current state and the thrown error. Return a partial state update to surface the error in the view:</p>
|
|
122
|
+
${t(o(`onError: (state, err) => ({
|
|
123
|
+
status: 'error',
|
|
124
|
+
errors: err?.validation ?? [{ message: err.message }],
|
|
125
|
+
})`,"js"))}
|
|
126
|
+
|
|
127
|
+
${e("toast","Toast notifications")}
|
|
128
|
+
<p>Return <code>_toast</code> from any action hook to show a notification. It is stripped from spec state automatically \u2014 it never appears in <code>getState()</code> or the view.</p>
|
|
129
|
+
${t(o(`onSuccess: (state, payload) => ({
|
|
130
|
+
status: 'success',
|
|
131
|
+
_toast: { message: 'Saved successfully', variant: 'success' },
|
|
132
|
+
}),
|
|
133
|
+
|
|
134
|
+
onError: (state, err) => ({
|
|
135
|
+
status: 'error',
|
|
136
|
+
errors: err?.validation ?? [{ message: err.message }],
|
|
137
|
+
_toast: { message: 'Something went wrong', variant: 'error' },
|
|
138
|
+
}),`,"js"))}
|
|
139
|
+
<p><code>_toast</code> works in <code>onStart</code>, <code>onSuccess</code>, and <code>onError</code>, and also in mutations. The toast container is injected into <code>document.body</code> once and survives client-side navigations.</p>
|
|
140
|
+
${u(["Option","Type","Default",""],[["<code>message</code>","string","\u2014","Required. The notification text."],["<code>variant</code>","<code>success | error | warning | info</code>","<code>info</code>",""],["<code>duration</code>","number (ms)","<code>4000</code>","Auto-dismiss delay. <code>0</code> = sticky until dismissed."]])}
|
|
141
|
+
|
|
142
|
+
${e("store-update","Pushing to the global store")}
|
|
143
|
+
<p>Return <code>_storeUpdate</code> from <code>onSuccess</code> to push a partial update into the <a href="/store">global store</a>. Every mounted page that subscribes to the updated keys re-renders immediately \u2014 no navigation, no polling.</p>
|
|
144
|
+
${t(o(`onSuccess: (state, theme) => ({
|
|
145
|
+
saved: true,
|
|
146
|
+
_storeUpdate: { settings: { theme } }, // \u2190 merged into global store state
|
|
147
|
+
}),`,"js"))}
|
|
148
|
+
<p><code>_storeUpdate</code> is stripped from the page's own state \u2014 only the rest of the return object is merged into local state as usual. See <a href="/store">Global Store</a> for the full store API.</p>
|
|
149
|
+
|
|
150
|
+
${e("rendering-errors","Rendering errors in the view")}
|
|
151
|
+
${t(o(`view: (state) => \`
|
|
152
|
+
<form data-action="submit">
|
|
153
|
+
\${state.errors.map(e => \`
|
|
154
|
+
<p class="error">
|
|
155
|
+
\${e.field ? \`<strong>\${e.field}:</strong> \` : ''}\${e.message}
|
|
156
|
+
</p>
|
|
157
|
+
\`).join('')}
|
|
158
|
+
<!-- ... form fields ... -->
|
|
159
|
+
<button \${state.status === 'loading' ? 'disabled' : ''}>
|
|
160
|
+
\${state.status === 'loading' ? 'Sending\u2026' : 'Send'}
|
|
161
|
+
</button>
|
|
162
|
+
</form>
|
|
163
|
+
\``,"js"))}
|
|
164
|
+
`})};var r=document.getElementById("pulse-root");r&&!r.dataset.pulseMounted&&(r.dataset.pulseMounted="1",s(n,r,window.__PULSE_SERVER__||{},{ssr:!0}),m(r,s));var $=n;export{$ as default};
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import{a as o}from"./runtime-QFURDKA2.js";import{a as i,b as u,c as d,d as h,e,g as t,h as s,i as n}from"./runtime-L2HNXIHW.js";import{a as c,b as l}from"./runtime-B73WLANC.js";var{prev:p,next:A}=i("/auth"),a={route:"/auth",meta:{title:"Auth (Auth0) \u2014 Pulse Docs",description:"Integrating Auth0 OAuth authentication with Pulse using guard functions, ctx.setCookie, and raw response specs.",styles:["/docs.css"]},state:{},view:()=>u({currentHref:"/auth",prev:p,next:A,content:`
|
|
2
|
+
${d("Auth (Auth0)")}
|
|
3
|
+
${h("Pulse integrates with Auth0 \u2014 and any OAuth 2.0 provider \u2014 using plain HTTP redirects and a token exchange. No client-side auth SDK is required. Protected routes enforce access through <code>guard</code>, which runs before any data fetcher can execute.")}
|
|
4
|
+
|
|
5
|
+
${n("info","No Auth0 SDK required. The OAuth flow is plain HTTP redirects and a token exchange fetch \u2014 no client-side library needed.")}
|
|
6
|
+
|
|
7
|
+
${e("setup","Setup")}
|
|
8
|
+
<p>Register your application in Auth0 and note your credentials. Store them in environment variables \u2014 never hardcode them in specs.</p>
|
|
9
|
+
${t(o(`# .env
|
|
10
|
+
AUTH0_DOMAIN=your-tenant.auth0.com
|
|
11
|
+
AUTH0_CLIENT_ID=your_client_id
|
|
12
|
+
AUTH0_CLIENT_SECRET=your_client_secret
|
|
13
|
+
AUTH0_CALLBACK_URL=http://localhost:3000/auth/callback`,"bash"))}
|
|
14
|
+
|
|
15
|
+
${e("login","Login route")}
|
|
16
|
+
<p>The login route is a raw response spec that redirects the browser to Auth0's authorization endpoint.</p>
|
|
17
|
+
${t(o(`// src/pages/auth/login.js
|
|
18
|
+
const {
|
|
19
|
+
AUTH0_DOMAIN, AUTH0_CLIENT_ID, AUTH0_CALLBACK_URL
|
|
20
|
+
} = process.env
|
|
21
|
+
|
|
22
|
+
export default {
|
|
23
|
+
route: '/auth/login',
|
|
24
|
+
contentType: 'text/html',
|
|
25
|
+
|
|
26
|
+
render: (ctx) => {
|
|
27
|
+
const params = new URLSearchParams({
|
|
28
|
+
response_type: 'code',
|
|
29
|
+
client_id: AUTH0_CLIENT_ID,
|
|
30
|
+
redirect_uri: AUTH0_CALLBACK_URL,
|
|
31
|
+
scope: 'openid profile email',
|
|
32
|
+
state: crypto.randomUUID(),
|
|
33
|
+
})
|
|
34
|
+
ctx.setHeader('Location', \`https://\${AUTH0_DOMAIN}/authorize?\${params}\`)
|
|
35
|
+
return { redirect: \`https://\${AUTH0_DOMAIN}/authorize?\${params}\` }
|
|
36
|
+
},
|
|
37
|
+
}`,"js"))}
|
|
38
|
+
|
|
39
|
+
${e("callback","Callback route")}
|
|
40
|
+
<p>Auth0 redirects back to <code>/auth/callback</code> with a <code>code</code> query parameter. The server exchanges it for tokens, sets a session cookie, and redirects to the app.</p>
|
|
41
|
+
${t(o(`// src/pages/auth/callback.js
|
|
42
|
+
const {
|
|
43
|
+
AUTH0_DOMAIN, AUTH0_CLIENT_ID,
|
|
44
|
+
AUTH0_CLIENT_SECRET, AUTH0_CALLBACK_URL
|
|
45
|
+
} = process.env
|
|
46
|
+
|
|
47
|
+
export default {
|
|
48
|
+
route: '/auth/callback',
|
|
49
|
+
contentType: 'text/html',
|
|
50
|
+
|
|
51
|
+
server: {
|
|
52
|
+
session: async (ctx) => {
|
|
53
|
+
const { code } = ctx.query
|
|
54
|
+
if (!code) return null
|
|
55
|
+
|
|
56
|
+
// Exchange auth code for tokens
|
|
57
|
+
const res = await fetch(\`https://\${AUTH0_DOMAIN}/oauth/token\`, {
|
|
58
|
+
method: 'POST',
|
|
59
|
+
headers: { 'Content-Type': 'application/json' },
|
|
60
|
+
body: JSON.stringify({
|
|
61
|
+
grant_type: 'authorization_code',
|
|
62
|
+
client_id: AUTH0_CLIENT_ID,
|
|
63
|
+
client_secret: AUTH0_CLIENT_SECRET,
|
|
64
|
+
redirect_uri: AUTH0_CALLBACK_URL,
|
|
65
|
+
code,
|
|
66
|
+
}),
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
if (!res.ok) return null
|
|
70
|
+
const { access_token, id_token } = await res.json()
|
|
71
|
+
|
|
72
|
+
// Set a secure session cookie with the access token
|
|
73
|
+
ctx.setCookie('session', access_token, {
|
|
74
|
+
httpOnly: true,
|
|
75
|
+
secure: process.env.NODE_ENV === 'production',
|
|
76
|
+
sameSite: 'Lax',
|
|
77
|
+
maxAge: 86400, // 24 hours
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
return access_token
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
|
|
84
|
+
render: (ctx, server) => {
|
|
85
|
+
if (!server.session) return { redirect: '/auth/login' }
|
|
86
|
+
return { redirect: '/' }
|
|
87
|
+
},
|
|
88
|
+
}`,"js"))}
|
|
89
|
+
|
|
90
|
+
${e("logout","Logout route")}
|
|
91
|
+
<p>Clear the session cookie and redirect to Auth0's logout endpoint to invalidate the session there too.</p>
|
|
92
|
+
${t(o(`// src/pages/auth/logout.js
|
|
93
|
+
const { AUTH0_DOMAIN, AUTH0_CLIENT_ID } = process.env
|
|
94
|
+
|
|
95
|
+
export default {
|
|
96
|
+
route: '/auth/logout',
|
|
97
|
+
contentType: 'text/html',
|
|
98
|
+
|
|
99
|
+
render: (ctx) => {
|
|
100
|
+
// Expire the session cookie
|
|
101
|
+
ctx.setCookie('session', '', { maxAge: 0 })
|
|
102
|
+
|
|
103
|
+
const returnTo = encodeURIComponent('http://localhost:3000')
|
|
104
|
+
return { redirect: \`https://\${AUTH0_DOMAIN}/v2/logout?client_id=\${AUTH0_CLIENT_ID}&returnTo=\${returnTo}\` }
|
|
105
|
+
},
|
|
106
|
+
}`,"js"))}
|
|
107
|
+
|
|
108
|
+
${e("guard","Protecting routes")}
|
|
109
|
+
<p>Use <code>guard</code> to verify the session token before any server data is fetched. For production, verify the JWT signature locally rather than calling Auth0 on every request.</p>
|
|
110
|
+
${t(o(`// src/pages/dashboard.js
|
|
111
|
+
export default {
|
|
112
|
+
route: '/dashboard',
|
|
113
|
+
|
|
114
|
+
guard: async (ctx) => {
|
|
115
|
+
if (!ctx.cookies.session) return { redirect: '/auth/login' }
|
|
116
|
+
|
|
117
|
+
// Optional: verify JWT signature for production
|
|
118
|
+
// const user = await verifyJwt(ctx.cookies.session)
|
|
119
|
+
// if (!user) return { redirect: '/auth/login' }
|
|
120
|
+
},
|
|
121
|
+
|
|
122
|
+
server: {
|
|
123
|
+
profile: async (ctx) => fetchUserProfile(ctx.cookies.session),
|
|
124
|
+
},
|
|
125
|
+
|
|
126
|
+
state: {},
|
|
127
|
+
view: (state, server) => \`
|
|
128
|
+
<main id="main-content">
|
|
129
|
+
<h1>Welcome, \${server.profile.name}</h1>
|
|
130
|
+
</main>
|
|
131
|
+
\`,
|
|
132
|
+
}`,"js"))}
|
|
133
|
+
|
|
134
|
+
${n("info","Guard runs before server data fetchers \u2014 if the session is invalid the profile fetch never happens.")}
|
|
135
|
+
|
|
136
|
+
${e("ctx-reference","ctx reference")}
|
|
137
|
+
${s(["Method","Description"],[["<code>ctx.cookies.session</code>","Read the session cookie set during OAuth callback"],["<code>ctx.setCookie(name, value, opts)</code>","Set a response cookie \u2014 used in callback and logout routes"],["<code>ctx.setHeader(name, value)</code>","Set an arbitrary response header"]])}
|
|
138
|
+
|
|
139
|
+
${s(["setCookie option","Type","Description"],[["<code>httpOnly</code>","boolean","Prevents JS access \u2014 always use for session cookies"],["<code>secure</code>","boolean","HTTPS only \u2014 set <code>true</code> in production"],["<code>sameSite</code>",'<code>"Lax" | "Strict" | "None"</code>',"<code>Lax</code> works for most OAuth flows"],["<code>maxAge</code>","number","Lifetime in seconds \u2014 omit for session cookie"],["<code>path</code>","string","Defaults to <code>/</code>"],["<code>domain</code>","string","Scope to a domain \u2014 omit for current host"]])}
|
|
140
|
+
`})};var r=document.getElementById("pulse-root");r&&!r.dataset.pulseMounted&&(r.dataset.pulseMounted="1",c(a,r,window.__PULSE_SERVER__||{},{ssr:!0}),l(r,c));var y=a;export{y as default};
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import{a as s}from"./runtime-QFURDKA2.js";import{a as i,b as n,c as l,d as h,e,g as c,h as a,i as t}from"./runtime-L2HNXIHW.js";import{a as d,b as u}from"./runtime-B73WLANC.js";var{prev:p,next:m}=i("/caching"),r={route:"/caching",meta:{title:"Caching \u2014 Pulse Docs",description:"HTTP cache headers, in-process server data caching, and asset caching in Pulse.",styles:["/docs.css"]},state:{},view:()=>n({currentHref:"/caching",prev:p,next:m,content:`
|
|
2
|
+
${l("Caching")}
|
|
3
|
+
${h("Pulse handles asset caching automatically \u2014 production bundles are content-hashed and served with immutable cache headers. Page caching is controlled declaratively in the spec: <code>serverTtl</code> for in-process data, <code>cache</code> for HTTP headers. Nothing is cached by default unless you declare it.")}
|
|
4
|
+
|
|
5
|
+
${e("server-ttl","serverTtl \u2014 in-process data cache")}
|
|
6
|
+
<p><code>serverTtl</code> is a number of seconds to cache the result of <code>server.data()</code> in memory. Subsequent requests within the TTL window skip the async data fetch entirely and serve the cached result.</p>
|
|
7
|
+
${c(s(`export default {
|
|
8
|
+
route: '/homepage',
|
|
9
|
+
serverTtl: 60, // cache server data for 60 seconds
|
|
10
|
+
server: {
|
|
11
|
+
data: async () => ({
|
|
12
|
+
featured: await db.products.getFeatured(),
|
|
13
|
+
stats: await analytics.getGlobalStats(),
|
|
14
|
+
}),
|
|
15
|
+
},
|
|
16
|
+
}`,"js"))}
|
|
17
|
+
${a(["Value","Behaviour"],[["<code>undefined</code> (default)","No caching \u2014 <code>server.data()</code> runs on every request"],["<code>0</code>","No caching (same as undefined)"],["<code>60</code>","Cache for 60 seconds \u2014 at most one database hit per minute"],["<code>3600</code>","Cache for 1 hour"]])}
|
|
18
|
+
${t("note","The in-process cache is per-process and per-route. It is not shared across multiple server instances. Use a distributed cache (Redis, etc.) for cross-instance consistency.")}
|
|
19
|
+
${t("warning","In development, setting a long <code>serverTtl</code> can mask stale data. Set it to <code>0</code> or omit it during development, and add it when deploying to production.")}
|
|
20
|
+
|
|
21
|
+
${e("http-cache","cache \u2014 HTTP response headers")}
|
|
22
|
+
<p>The <code>cache</code> field controls the <code>Cache-Control</code> header sent with the page HTML response. This tells browsers and CDNs how to cache the response.</p>
|
|
23
|
+
${c(s(`export default {
|
|
24
|
+
route: '/blog/:slug',
|
|
25
|
+
cache: {
|
|
26
|
+
public: true, // allow CDN/proxy caching
|
|
27
|
+
maxAge: 300, // cache for 5 minutes
|
|
28
|
+
staleWhileRevalidate: 86400, // serve stale for up to 24 hours while revalidating
|
|
29
|
+
},
|
|
30
|
+
// ...
|
|
31
|
+
}`,"js"))}
|
|
32
|
+
|
|
33
|
+
${e("cache-fields","Cache field reference")}
|
|
34
|
+
${a(["Field","Type","Description"],[["<code>public</code>","<code>boolean</code>","If true, emits <code>public</code> \u2014 allows CDN/proxy caching. Default: <code>private</code>."],["<code>maxAge</code>","<code>number</code>","Seconds before the response is considered stale. Emits <code>max-age=N</code>."],["<code>staleWhileRevalidate</code>","<code>number</code>","Seconds to serve stale content while revalidating in the background. Emits <code>stale-while-revalidate=N</code>."]])}
|
|
35
|
+
${c(s(`// private page \u2014 user-specific content
|
|
36
|
+
cache: { public: false, maxAge: 0 }
|
|
37
|
+
// \u2192 Cache-Control: private, no-store
|
|
38
|
+
|
|
39
|
+
// public marketing page \u2014 cached at CDN
|
|
40
|
+
cache: { public: true, maxAge: 3600, staleWhileRevalidate: 86400 }
|
|
41
|
+
// \u2192 Cache-Control: public, max-age=3600, stale-while-revalidate=86400`,"js"))}
|
|
42
|
+
|
|
43
|
+
${e("html-default","Default HTML caching")}
|
|
44
|
+
<p>By default, Pulse sends <code>Cache-Control: no-store</code> for all HTML responses. Users always see fresh content \u2014 stale HTML is never served from browser or proxy caches unless you explicitly declare a <code>cache</code> policy in the spec.</p>
|
|
45
|
+
|
|
46
|
+
${e("asset-caching","Asset caching")}
|
|
47
|
+
<p>Static assets in <code>public/</code> receive <code>Cache-Control: max-age=3600</code> (one hour).</p>
|
|
48
|
+
<p>Production bundles in <code>public/dist/</code> receive <code>Cache-Control: public, max-age=31536000, immutable</code> (one year, immutable). This is guaranteed safe because bundle filenames include a content hash \u2014 code changes produce a new hash, and browsers fetch the updated file automatically. There is nothing to configure.</p>
|
|
49
|
+
|
|
50
|
+
${e("dev-vs-prod","Development vs production")}
|
|
51
|
+
${a(["Resource","Development","Production"],[["HTML pages","<code>no-store</code>","<code>no-store</code> (or your <code>cache</code> config)"],["Static assets (<code>/public/*</code>)","<code>max-age=3600</code>","<code>max-age=3600</code>"],["JS bundles (<code>/dist/*</code>)","N/A (source files served directly)","<code>immutable, max-age=31536000</code>"]])}
|
|
52
|
+
${t("tip","For maximum performance, combine <code>serverTtl</code> for expensive database queries with a short <code>cache.maxAge</code> and a generous <code>cache.staleWhileRevalidate</code>. Users get fast responses; data stays reasonably fresh.")}
|
|
53
|
+
`})};var o=document.getElementById("pulse-root");o&&!o.dataset.pulseMounted&&(o.dataset.pulseMounted="1",d(r,o,window.__PULSE_SERVER__||{},{ssr:!0}),u(o,d));var C=r;export{C as default};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import{a as i,b as a}from"./runtime-ZJ4FXT5O.js";import{Ka as c}from"./runtime-UVPXO4IR.js";import"./runtime-VMJA3Z4N.js";import"./runtime-QFURDKA2.js";import{a as n,h as r}from"./runtime-L2HNXIHW.js";import{a as o,b as s}from"./runtime-B73WLANC.js";var{prev:d,next:p}=n("/components/accordion"),t={route:"/components/accordion",meta:{title:"Accordion \u2014 Pulse Docs",description:"Accordion component for Pulse UI.",styles:["/pulse-ui.css","/docs.css"]},state:{},view:()=>a({currentHref:"/components/accordion",prev:d,next:p,name:"accordion",description:"Collapsible FAQ items built on native <code><details></code>/<code><summary></code> \u2014 no JavaScript required. The open/close animation is handled entirely by the browser.",content:`
|
|
2
|
+
${i(c({items:[{question:"Is there a free plan?",answer:"Yes \u2014 the free plan includes up to 3 pages and community support, with no credit card required."},{question:"Can I cancel anytime?",answer:"Absolutely. There are no contracts or lock-in periods. Cancel from your account settings at any time."},{question:"Does it work on older devices?",answer:"The app supports iOS 14+ and Android 8+, covering over 97% of active devices."}]}),`accordion({
|
|
3
|
+
items: [
|
|
4
|
+
{ question: 'Is there a free plan?', answer: 'Yes \u2014 up to 3 pages, no card required.' },
|
|
5
|
+
{ question: 'Can I cancel anytime?', answer: 'No contracts. Cancel from your account.' },
|
|
6
|
+
{ question: 'Works on older devices?', answer: 'iOS 14+ and Android 8+.' },
|
|
7
|
+
],
|
|
8
|
+
})`)}
|
|
9
|
+
|
|
10
|
+
${r(["Prop","Type","Default",""],[["<code>items</code>","array","[]","<code>{ question: string, answer: string }[]</code>"]])}
|
|
11
|
+
`})};var e=document.getElementById("pulse-root");e&&!e.dataset.pulseMounted&&(e.dataset.pulseMounted="1",o(t,e,window.__PULSE_SERVER__||{},{ssr:!0}),s(e,o));var q=t;export{q as default};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import{a as i,b as c}from"./runtime-ZJ4FXT5O.js";import{Ba as e}from"./runtime-UVPXO4IR.js";import"./runtime-VMJA3Z4N.js";import"./runtime-QFURDKA2.js";import{a as r}from"./runtime-L2HNXIHW.js";import{a as n,b as a}from"./runtime-B73WLANC.js";var{prev:s,next:d}=r("/components/alert"),o={route:"/components/alert",meta:{title:"Alert \u2014 Pulse Docs",description:"Alert component for Pulse UI.",styles:["/pulse-ui.css","/docs.css"]},state:{},view:()=>c({currentHref:"/components/alert",prev:s,next:d,name:"alert",description:'<code>error</code> and <code>warning</code> use <code>role="alert"</code> \u2014 screen readers announce them immediately. <code>info</code> and <code>success</code> use <code>role="status"</code> for a polite announcement.',content:`
|
|
2
|
+
${i(e({variant:"info",content:"Your account is pending email verification."})+e({variant:"success",title:"Saved",content:"Your changes have been saved."})+e({variant:"warning",title:"Heads up",content:"This action cannot be undone."})+e({variant:"error",title:"Failed",content:"Something went wrong. Please try again."}),`alert({ variant: 'info', content: 'Your account is pending email verification.' })
|
|
3
|
+
alert({ variant: 'success', title: 'Saved', content: 'Your changes have been saved.' })
|
|
4
|
+
alert({ variant: 'warning', title: 'Heads up', content: 'This action cannot be undone.' })
|
|
5
|
+
alert({ variant: 'error', title: 'Failed', content: escHtml(server.error) })`,{col:!0})}
|
|
6
|
+
`})};var t=document.getElementById("pulse-root");t&&!t.dataset.pulseMounted&&(t.dataset.pulseMounted="1",n(o,t,window.__PULSE_SERVER__||{},{ssr:!0}),a(t,n));var h=o;export{h as default};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import{a as n,b as d}from"./runtime-ZJ4FXT5O.js";import{Ma as t}from"./runtime-UVPXO4IR.js";import"./runtime-VMJA3Z4N.js";import"./runtime-QFURDKA2.js";import{a as r,h as a}from"./runtime-L2HNXIHW.js";import{a as o,b as s}from"./runtime-B73WLANC.js";var{prev:i,next:l}=r("/components/app-badge"),p={route:"/components/app-badge",meta:{title:"App Badge \u2014 Pulse Docs",description:"App Badge component for Pulse UI.",styles:["/pulse-ui.css","/docs.css"]},state:{},view:()=>d({currentHref:"/components/app-badge",prev:i,next:l,name:"appBadge",description:"App Store and Google Play download buttons. Designed to sit in a <code>hero()</code> actions slot, or anywhere a download CTA is needed.",content:`
|
|
2
|
+
${n('<div style="display:flex;gap:.75rem;flex-wrap:wrap">'+t({store:"apple",href:"#"})+t({store:"google",href:"#"})+"</div>",`appBadge({ store: 'apple', href: appStoreUrl })
|
|
3
|
+
appBadge({ store: 'google', href: playStoreUrl })`)}
|
|
4
|
+
|
|
5
|
+
${a(["Prop","Type","Default",""],[["<code>store</code>","string","'apple'","'apple' or 'google'"],["<code>href</code>","string","'#'","Link to the app store listing"]])}
|
|
6
|
+
`})};var e=document.getElementById("pulse-root");e&&!e.dataset.pulseMounted&&(e.dataset.pulseMounted="1",o(p,e,window.__PULSE_SERVER__||{},{ssr:!0}),s(e,o));var B=p;export{B as default};
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import{a as s,b as n}from"./runtime-ZJ4FXT5O.js";import{Da as e}from"./runtime-UVPXO4IR.js";import"./runtime-VMJA3Z4N.js";import"./runtime-QFURDKA2.js";import{a as r}from"./runtime-L2HNXIHW.js";import{a,b as i}from"./runtime-B73WLANC.js";var{prev:l,next:c}=r("/components/avatar"),o={route:"/components/avatar",meta:{title:"Avatar \u2014 Pulse Docs",description:"Avatar component for Pulse UI.",styles:["/pulse-ui.css","/docs.css"]},state:{},view:()=>n({currentHref:"/components/avatar",prev:l,next:c,name:"avatar",description:'When <code>src</code> is set, renders an <code><img></code> with <code>loading="lazy"</code>. Without <code>src</code>, shows initials derived from <code>alt</code>.',content:`
|
|
2
|
+
${s('<div style="display:flex;gap:1rem;align-items:center">'+e({alt:"Alice Smith",size:"sm"})+e({alt:"Bob Jones",size:"md"})+e({alt:"Carol White",size:"lg"})+e({alt:"Dan Brown",size:"xl"})+"</div>",`avatar({ src: user.avatarUrl, alt: user.name, size: 'md' })
|
|
3
|
+
avatar({ alt: 'Alice Smith' }) // renders initials "AS"
|
|
4
|
+
avatar({ alt: 'Alice', initials: 'A' }) // explicit initials`)}
|
|
5
|
+
`})};var t=document.getElementById("pulse-root");t&&!t.dataset.pulseMounted&&(t.dataset.pulseMounted="1",a(o,t,window.__PULSE_SERVER__||{},{ssr:!0}),i(t,a));var h=o;export{h as default};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import{a as s,b as i}from"./runtime-ZJ4FXT5O.js";import{b as e}from"./runtime-UVPXO4IR.js";import"./runtime-VMJA3Z4N.js";import"./runtime-QFURDKA2.js";import{a as n}from"./runtime-L2HNXIHW.js";import{a as t,b as o}from"./runtime-B73WLANC.js";var{prev:l,next:d}=n("/components/badge"),r={route:"/components/badge",meta:{title:"Badge \u2014 Pulse Docs",description:"Badge component for Pulse UI.",styles:["/pulse-ui.css","/docs.css"]},state:{},view:()=>i({currentHref:"/components/badge",prev:l,next:d,name:"badge",description:"Inline status and label indicator. Five semantic variants cover the most common states.",content:`
|
|
2
|
+
${s(e({label:"Default",variant:"default"})+" "+e({label:"Info",variant:"info"})+" "+e({label:"Success",variant:"success"})+" "+e({label:"Warning",variant:"warning"})+" "+e({label:"Error",variant:"error"}),`badge({ label: 'Default', variant: 'default' })
|
|
3
|
+
badge({ label: 'Info', variant: 'info' })
|
|
4
|
+
badge({ label: 'Success', variant: 'success' })
|
|
5
|
+
badge({ label: 'Warning', variant: 'warning' })
|
|
6
|
+
badge({ label: 'Error', variant: 'error' })`)}
|
|
7
|
+
`})};var a=document.getElementById("pulse-root");a&&!a.dataset.pulseMounted&&(a.dataset.pulseMounted="1",t(r,a,window.__PULSE_SERVER__||{},{ssr:!0}),o(a,t));var v=r;export{v as default};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import{a as s,b as c}from"./runtime-ZJ4FXT5O.js";import{Qa as m,Ta as t}from"./runtime-UVPXO4IR.js";import"./runtime-VMJA3Z4N.js";import"./runtime-QFURDKA2.js";import{a as r,h as a}from"./runtime-L2HNXIHW.js";import{a as e,b as i}from"./runtime-B73WLANC.js";var{prev:p,next:d}=r("/components/banner"),o={route:"/components/banner",meta:{title:"Banner \u2014 Pulse Docs",description:"Banner component for Pulse UI.",styles:["/pulse-ui.css","/docs.css"]},state:{},view:()=>c({currentHref:"/components/banner",prev:p,next:d,name:"banner",description:"Full-width announcement bar. Sits above the nav for promotions and launch announcements. The content slot accepts links and formatted text.",content:`
|
|
2
|
+
${s(m({gap:"sm",content:t({variant:"promo",content:`\u{1F389} Chippy Bird v2.0 is out \u2014 <a href="#" style="color:inherit;text-decoration:underline">see what's new</a>`})+t({variant:"info",content:'Android support is coming soon. <a href="#" style="color:inherit;text-decoration:underline">Join the waitlist</a>'})+t({variant:"warning",content:"Scheduled maintenance Sunday 2\u20134am UTC."})}),`banner({ variant: 'promo', content:
|
|
3
|
+
'\u{1F389} v2.0 is out \u2014 ' + button({ label: "See what's new", variant: 'ghost', size: 'sm', href: '/changelog' })
|
|
4
|
+
})`)}
|
|
5
|
+
|
|
6
|
+
${a(["Prop","Type","Default",""],[["<code>content</code>","string (HTML)","\u2014","Raw HTML slot"],["<code>variant</code>","string","'info'","'info' \xB7 'promo' \xB7 'warning'"]])}
|
|
7
|
+
`})};var n=document.getElementById("pulse-root");n&&!n.dataset.pulseMounted&&(n.dataset.pulseMounted="1",e(o,n,window.__PULSE_SERVER__||{},{ssr:!0}),i(n,e));var x=o;export{x as default};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import{a as r,b as d}from"./runtime-ZJ4FXT5O.js";import{pb as o}from"./runtime-UVPXO4IR.js";import"./runtime-VMJA3Z4N.js";import"./runtime-QFURDKA2.js";import{a as t,h as c}from"./runtime-L2HNXIHW.js";import{a as s,b as l}from"./runtime-B73WLANC.js";var{prev:n,next:m}=t("/components/breadcrumbs"),a={route:"/components/breadcrumbs",meta:{title:"Breadcrumbs \u2014 Pulse Docs",description:"Accessible breadcrumb navigation component for Pulse UI.",styles:["/pulse-ui.css","/docs.css"]},state:{},view:()=>d({currentHref:"/components/breadcrumbs",prev:n,next:m,name:"breadcrumbs",description:'Accessible breadcrumb navigation. The current page item renders as a <code><span></code> with <code>aria-current="page"</code>; all preceding items render as links.',content:`
|
|
2
|
+
|
|
3
|
+
<h2 class="doc-h2" id="basic">Basic</h2>
|
|
4
|
+
${r(o({items:[{label:"Home",href:"/"},{label:"Products",href:"/products"},{label:"Sneakers"}]}),`breadcrumbs({
|
|
5
|
+
items: [
|
|
6
|
+
{ label: 'Home', href: '/' },
|
|
7
|
+
{ label: 'Products', href: '/products' },
|
|
8
|
+
{ label: 'Sneakers' },
|
|
9
|
+
],
|
|
10
|
+
})`)}
|
|
11
|
+
|
|
12
|
+
<h2 class="doc-h2" id="longer">Longer trail</h2>
|
|
13
|
+
${r(o({items:[{label:"Home",href:"/"},{label:"Shop",href:"/shop"},{label:"Footwear",href:"/shop/footwear"},{label:"Sneakers",href:"/shop/footwear/sneakers"},{label:"Air Max 90"}]}),`breadcrumbs({
|
|
14
|
+
items: [
|
|
15
|
+
{ label: 'Home', href: '/' },
|
|
16
|
+
{ label: 'Shop', href: '/shop' },
|
|
17
|
+
{ label: 'Footwear', href: '/shop/footwear' },
|
|
18
|
+
{ label: 'Sneakers', href: '/shop/footwear/sneakers' },
|
|
19
|
+
{ label: 'Air Max 90' },
|
|
20
|
+
],
|
|
21
|
+
})`)}
|
|
22
|
+
|
|
23
|
+
<h2 class="doc-h2" id="separator">Custom separator</h2>
|
|
24
|
+
${r(o({separator:"\u203A",items:[{label:"Docs",href:"/docs"},{label:"Components",href:"/docs/components"},{label:"Breadcrumbs"}]}),`breadcrumbs({
|
|
25
|
+
separator: '\u203A',
|
|
26
|
+
items: [
|
|
27
|
+
{ label: 'Docs', href: '/docs' },
|
|
28
|
+
{ label: 'Components', href: '/docs/components' },
|
|
29
|
+
{ label: 'Breadcrumbs' },
|
|
30
|
+
],
|
|
31
|
+
})`)}
|
|
32
|
+
|
|
33
|
+
${c(["Prop","Type","Default",""],[["<code>items</code>","array","[]","Array of <code>{ label, href }</code>. The last item should have no <code>href</code> \u2014 it becomes the current page."],["<code>separator</code>","string","'/'","Character rendered between items"],["<code>class</code>","string","\u2014",""]])}
|
|
34
|
+
`})};var e=document.getElementById("pulse-root");e&&!e.dataset.pulseMounted&&(e.dataset.pulseMounted="1",s(a,e,window.__PULSE_SERVER__||{},{ssr:!0}),l(e,s));var S=a;export{S as default};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import{a as t,b as s}from"./runtime-ZJ4FXT5O.js";import{E as b,H as u,a as e,f as d,x as c}from"./runtime-UVPXO4IR.js";import"./runtime-VMJA3Z4N.js";import"./runtime-QFURDKA2.js";import{a as i,h as r}from"./runtime-L2HNXIHW.js";import{a as n,b as l}from"./runtime-B73WLANC.js";var{prev:m,next:p}=i("/components/button"),a={route:"/components/button",meta:{title:"Button \u2014 Pulse Docs",description:"Button component for Pulse UI.",styles:["/pulse-ui.css","/docs.css"]},state:{},view:()=>s({currentHref:"/components/button",prev:m,next:p,name:"button",description:"Renders as <code><button></code> by default. Pass <code>href</code> to get an <code><a></code> that looks identical. All four variants are shown below.",content:`
|
|
2
|
+
${t(e({label:"Primary",variant:"primary"})+e({label:"Secondary",variant:"secondary"})+e({label:"Ghost",variant:"ghost"})+e({label:"Danger",variant:"danger"}),`button({ label: 'Primary', variant: 'primary' })
|
|
3
|
+
button({ label: 'Secondary', variant: 'secondary' })
|
|
4
|
+
button({ label: 'Ghost', variant: 'ghost' })
|
|
5
|
+
button({ label: 'Danger', variant: 'danger' })`)}
|
|
6
|
+
|
|
7
|
+
${t(e({label:"Small",size:"sm"})+e({label:"Medium",size:"md"})+e({label:"Large",size:"lg"}),`button({ label: 'Small', size: 'sm' })
|
|
8
|
+
button({ label: 'Medium', size: 'md' })
|
|
9
|
+
button({ label: 'Large', size: 'lg' })`)}
|
|
10
|
+
|
|
11
|
+
${t(e({label:"Download",icon:b({size:14})})+e({label:"New item",icon:c({size:14}),variant:"secondary"})+e({label:"Continue",iconAfter:d({size:14})})+e({label:"Send",iconAfter:u({size:14}),variant:"ghost"}),`import { iconDownload, iconPlus, iconArrowRight, iconSend } from '@invisibleloop/pulse/ui'
|
|
12
|
+
|
|
13
|
+
button({ label: 'Download', icon: iconDownload({ size: 14 }) })
|
|
14
|
+
button({ label: 'New item', icon: iconPlus({ size: 14 }), variant: 'secondary' })
|
|
15
|
+
button({ label: 'Continue', iconAfter: iconArrowRight({ size: 14 }) })
|
|
16
|
+
button({ label: 'Send', iconAfter: iconSend({ size: 14 }), variant: 'ghost' })`)}
|
|
17
|
+
|
|
18
|
+
${t(e({label:"Disabled",disabled:!0})+e({label:"Link",href:"/docs"})+e({label:"Submit",type:"submit",variant:"primary"}),`button({ label: 'Disabled', disabled: true })
|
|
19
|
+
button({ label: 'Link', href: '/docs' })
|
|
20
|
+
button({ label: 'Submit', type: 'submit', variant: 'primary' })`)}
|
|
21
|
+
|
|
22
|
+
${r(["Prop","Type","Default",""],[["<code>label</code>","string","\u2014","Visible text"],["<code>variant</code>","string","primary","primary \xB7 secondary \xB7 ghost \xB7 danger"],["<code>size</code>","string","md","sm \xB7 md \xB7 lg"],["<code>href</code>","string","\u2014","Renders <code><a></code> instead of <code><button></code>"],["<code>disabled</code>","boolean","false",""],["<code>type</code>","string","button","button \xB7 submit \xB7 reset"],["<code>icon</code>","string","\u2014","SVG HTML prepended inside"],["<code>iconAfter</code>","string","\u2014","SVG HTML appended inside"],["<code>fullWidth</code>","boolean","false",""],["<code>class</code>","string","\u2014","Extra classes appended to the element"],["<code>attrs</code>","object","{}","Extra HTML attributes (button only)"]])}
|
|
23
|
+
`})};var o=document.getElementById("pulse-root");o&&!o.dataset.pulseMounted&&(o.dataset.pulseMounted="1",n(a,o,window.__PULSE_SERVER__||{},{ssr:!0}),l(o,n));var P=a;export{P as default};
|