microui-wc 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/ISSUE_TEMPLATE/bug_report.md +40 -0
- package/.github/ISSUE_TEMPLATE/feature_request.md +33 -0
- package/.github/PULL_REQUEST_TEMPLATE.md +28 -0
- package/.github/workflows/ci.yml +42 -0
- package/.github/workflows/deploy-pages.yml +112 -0
- package/AGENTS.md +2366 -0
- package/CHANGELOG.md +47 -0
- package/CODE_OF_CONDUCT.md +59 -0
- package/CONTRIBUTING.md +156 -0
- package/LICENSE +190 -0
- package/README.md +254 -0
- package/SECURITY.md +58 -0
- package/app/.generated/routes/alerts.js +8 -0
- package/app/.generated/routes/avatars.js +8 -0
- package/app/.generated/routes/badges.js +8 -0
- package/app/.generated/routes/buttons.js +10 -0
- package/app/.generated/routes/cards.js +10 -0
- package/app/.generated/routes/checkboxes.js +9 -0
- package/app/.generated/routes/chips.js +8 -0
- package/app/.generated/routes/dropdowns.js +9 -0
- package/app/.generated/routes/home.js +7 -0
- package/app/.generated/routes/icons.js +9 -0
- package/app/.generated/routes/inputs.js +10 -0
- package/app/.generated/routes/installation.js +7 -0
- package/app/.generated/routes/layout.js +9 -0
- package/app/.generated/routes/modals.js +9 -0
- package/app/.generated/routes/navbar.js +7 -0
- package/app/.generated/routes/progress.js +9 -0
- package/app/.generated/routes/radios.js +9 -0
- package/app/.generated/routes/switches.js +9 -0
- package/app/.generated/routes/tabs.js +8 -0
- package/app/.generated/routes/toasts.js +9 -0
- package/app/index.html +67 -0
- package/app/pages/alerts.html +23 -0
- package/app/pages/avatars.html +22 -0
- package/app/pages/badges.html +22 -0
- package/app/pages/buttons.html +71 -0
- package/app/pages/cards.html +54 -0
- package/app/pages/checkboxes.html +39 -0
- package/app/pages/chips.html +23 -0
- package/app/pages/dropdowns.html +41 -0
- package/app/pages/home.html +59 -0
- package/app/pages/icons.html +29 -0
- package/app/pages/inputs.html +66 -0
- package/app/pages/installation.html +34 -0
- package/app/pages/layout.html +30 -0
- package/app/pages/modals.html +21 -0
- package/app/pages/navbar.html +22 -0
- package/app/pages/progress.html +35 -0
- package/app/pages/radios.html +40 -0
- package/app/pages/switches.html +39 -0
- package/app/pages/tabs.html +30 -0
- package/app/pages/toasts.html +22 -0
- package/app-dist/index.html +67 -0
- package/app-dist/pages/alerts.html +23 -0
- package/app-dist/pages/avatars.html +22 -0
- package/app-dist/pages/badges.html +22 -0
- package/app-dist/pages/buttons.html +71 -0
- package/app-dist/pages/cards.html +54 -0
- package/app-dist/pages/checkboxes.html +39 -0
- package/app-dist/pages/chips.html +23 -0
- package/app-dist/pages/dropdowns.html +41 -0
- package/app-dist/pages/home.html +59 -0
- package/app-dist/pages/icons.html +29 -0
- package/app-dist/pages/inputs.html +66 -0
- package/app-dist/pages/installation.html +34 -0
- package/app-dist/pages/layout.html +30 -0
- package/app-dist/pages/modals.html +21 -0
- package/app-dist/pages/navbar.html +22 -0
- package/app-dist/pages/progress.html +35 -0
- package/app-dist/pages/radios.html +40 -0
- package/app-dist/pages/switches.html +39 -0
- package/app-dist/pages/tabs.html +30 -0
- package/app-dist/pages/toasts.html +22 -0
- package/app-dist/pages.json +217 -0
- package/app-dist/routes/alerts.js +5 -0
- package/app-dist/routes/avatars.js +1 -0
- package/app-dist/routes/badges.js +1 -0
- package/app-dist/routes/buttons.js +1 -0
- package/app-dist/routes/cards.js +1 -0
- package/app-dist/routes/checkboxes.js +9 -0
- package/app-dist/routes/chips.js +4 -0
- package/app-dist/routes/chunk-019e5e2f.js +5 -0
- package/app-dist/routes/chunk-0m4j19yd.js +2 -0
- package/app-dist/routes/chunk-0tmmp5q0.js +1 -0
- package/app-dist/routes/chunk-10xn709r.js +1 -0
- package/app-dist/routes/chunk-15m2qcda.js +2 -0
- package/app-dist/routes/chunk-1bh8g23n.js +1 -0
- package/app-dist/routes/chunk-1vg0v937.js +1 -0
- package/app-dist/routes/chunk-1zvcgy3j.js +1 -0
- package/app-dist/routes/chunk-2afb0861.js +1 -0
- package/app-dist/routes/chunk-2c6ttpzt.js +5 -0
- package/app-dist/routes/chunk-3dy30fhs.js +1 -0
- package/app-dist/routes/chunk-426dnces.js +13 -0
- package/app-dist/routes/chunk-44kgxery.js +1 -0
- package/app-dist/routes/chunk-47fdnejd.js +33 -0
- package/app-dist/routes/chunk-49a6t2vq.js +1 -0
- package/app-dist/routes/chunk-4fe1rm5b.js +1 -0
- package/app-dist/routes/chunk-4ggmvkta.js +33 -0
- package/app-dist/routes/chunk-4vkz81q7.js +33 -0
- package/app-dist/routes/chunk-4w4tmj8f.js +31 -0
- package/app-dist/routes/chunk-532s62kr.js +31 -0
- package/app-dist/routes/chunk-5hm3bssy.js +33 -0
- package/app-dist/routes/chunk-5vrh24hc.js +1 -0
- package/app-dist/routes/chunk-61pcg25a.js +1 -0
- package/app-dist/routes/chunk-6nfhygvf.js +1 -0
- package/app-dist/routes/chunk-700e7je6.js +33 -0
- package/app-dist/routes/chunk-7fsn17kg.js +1 -0
- package/app-dist/routes/chunk-7k789b32.js +1 -0
- package/app-dist/routes/chunk-7r46q0ys.js +36 -0
- package/app-dist/routes/chunk-86fmc1fr.js +5 -0
- package/app-dist/routes/chunk-8qth37vw.js +1 -0
- package/app-dist/routes/chunk-924wv8n0.js +1 -0
- package/app-dist/routes/chunk-9mbhgxk9.js +1 -0
- package/app-dist/routes/chunk-a216hyd9.js +1 -0
- package/app-dist/routes/chunk-akzxykh9.js +33 -0
- package/app-dist/routes/chunk-b3dcvy8c.js +1 -0
- package/app-dist/routes/chunk-b74zahz5.js +31 -0
- package/app-dist/routes/chunk-bftj53p2.js +5 -0
- package/app-dist/routes/chunk-c01hnz3e.js +1 -0
- package/app-dist/routes/chunk-d8pvv5km.js +1 -0
- package/app-dist/routes/chunk-dev0aezr.js +2 -0
- package/app-dist/routes/chunk-dh6vnv0e.js +1 -0
- package/app-dist/routes/chunk-dn2cbpva.js +36 -0
- package/app-dist/routes/chunk-dvn0my90.js +1 -0
- package/app-dist/routes/chunk-dvq8mnve.js +36 -0
- package/app-dist/routes/chunk-e8c2gc4d.js +5 -0
- package/app-dist/routes/chunk-ejf9ak2x.js +1 -0
- package/app-dist/routes/chunk-f083m55s.js +1 -0
- package/app-dist/routes/chunk-fnrj28s1.js +31 -0
- package/app-dist/routes/chunk-fvg3yjdp.js +31 -0
- package/app-dist/routes/chunk-g7k381n1.js +1 -0
- package/app-dist/routes/chunk-h01kq2ae.js +13 -0
- package/app-dist/routes/chunk-h4dk761v.js +5 -0
- package/app-dist/routes/chunk-hmx91z2x.js +5 -0
- package/app-dist/routes/chunk-hxbg4m42.js +36 -0
- package/app-dist/routes/chunk-jbjnfp2b.js +2 -0
- package/app-dist/routes/chunk-jxtz5vv6.js +36 -0
- package/app-dist/routes/chunk-jxzcs0ey.js +36 -0
- package/app-dist/routes/chunk-kt7wwhcx.js +1 -0
- package/app-dist/routes/chunk-kzptszyc.js +33 -0
- package/app-dist/routes/chunk-mhgca4w4.js +2 -0
- package/app-dist/routes/chunk-mhswxa20.js +1 -0
- package/app-dist/routes/chunk-n8zfeex6.js +1 -0
- package/app-dist/routes/chunk-pee47b2r.js +1 -0
- package/app-dist/routes/chunk-pesmw829.js +1 -0
- package/app-dist/routes/chunk-pgc4c6f3.js +36 -0
- package/app-dist/routes/chunk-q8egegm1.js +1 -0
- package/app-dist/routes/chunk-q9mn2qyq.js +36 -0
- package/app-dist/routes/chunk-qh0rtaf3.js +5 -0
- package/app-dist/routes/chunk-qqhmk6ye.js +2 -0
- package/app-dist/routes/chunk-qrxygmf7.js +33 -0
- package/app-dist/routes/chunk-r46yzksx.js +36 -0
- package/app-dist/routes/chunk-rgpbw2w0.js +5 -0
- package/app-dist/routes/chunk-rnpzv3d8.js +2 -0
- package/app-dist/routes/chunk-s5v8cv05.js +2 -0
- package/app-dist/routes/chunk-sbwn5bpc.js +1 -0
- package/app-dist/routes/chunk-sqbg8jbt.js +33 -0
- package/app-dist/routes/chunk-sv8dqnf7.js +1 -0
- package/app-dist/routes/chunk-t67sw3za.js +1 -0
- package/app-dist/routes/chunk-tjdpqwdf.js +31 -0
- package/app-dist/routes/chunk-tq2mfghg.js +1 -0
- package/app-dist/routes/chunk-ttn10vt6.js +1 -0
- package/app-dist/routes/chunk-v2hzpjxr.js +1 -0
- package/app-dist/routes/chunk-wfjjkw9y.js +1 -0
- package/app-dist/routes/chunk-wt8cxzmf.js +31 -0
- package/app-dist/routes/chunk-x45d372k.js +5 -0
- package/app-dist/routes/chunk-y3wsazkt.js +1 -0
- package/app-dist/routes/chunk-y7pmgc7t.js +33 -0
- package/app-dist/routes/chunk-zefdt2q3.js +31 -0
- package/app-dist/routes/dropdowns.js +6 -0
- package/app-dist/routes/home.js +1 -0
- package/app-dist/routes/icons.js +1 -0
- package/app-dist/routes/inputs.js +12 -0
- package/app-dist/routes/installation.js +1 -0
- package/app-dist/routes/layout.js +1 -0
- package/app-dist/routes/modals.js +7 -0
- package/app-dist/routes/navbar.js +1 -0
- package/app-dist/routes/progress.js +1 -0
- package/app-dist/routes/radios.js +6 -0
- package/app-dist/routes/switches.js +6 -0
- package/app-dist/routes/tabs.js +1 -0
- package/app-dist/routes/toasts.js +16 -0
- package/assets/fonts/material-symbols-mini.woff2 +0 -0
- package/assets/fonts/material-symbols.woff2 +0 -0
- package/assets/fonts/roboto-400.woff2 +0 -0
- package/assets/fonts/roboto-500.woff2 +0 -0
- package/assets/fonts/roboto-700.woff2 +0 -0
- package/assets/logo-banner-400.jpg +0 -0
- package/assets/logo-banner-400.webp +0 -0
- package/assets/logo-banner-800.webp +0 -0
- package/assets/logo-banner.jpg +0 -0
- package/assets/logo-icon-64.jpg +0 -0
- package/assets/logo-icon-64.webp +0 -0
- package/assets/logo-icon.jpg +0 -0
- package/assets/logo-square.jpg +0 -0
- package/bun.lock +312 -0
- package/bunfig.toml +4 -0
- package/custom-elements.json +1916 -0
- package/demo/api/sample-data.json +38 -0
- package/demo/content/alerts.html +115 -0
- package/demo/content/avatars.html +70 -0
- package/demo/content/badges.html +65 -0
- package/demo/content/buttons.html +188 -0
- package/demo/content/callouts.html +91 -0
- package/demo/content/cards.html +121 -0
- package/demo/content/checkboxes.html +178 -0
- package/demo/content/chips.html +67 -0
- package/demo/content/codeblocks.html +101 -0
- package/demo/content/confirms.html +115 -0
- package/demo/content/datatables.html +149 -0
- package/demo/content/dividers.html +119 -0
- package/demo/content/dropdowns.html +89 -0
- package/demo/content/enterprise.html +252 -0
- package/demo/content/home.html +149 -0
- package/demo/content/icons.html +89 -0
- package/demo/content/inputs.html +135 -0
- package/demo/content/installation.html +16 -0
- package/demo/content/layout.html +136 -0
- package/demo/content/modals.html +141 -0
- package/demo/content/navbar.html +70 -0
- package/demo/content/progress.html +119 -0
- package/demo/content/radios.html +88 -0
- package/demo/content/skeletons.html +109 -0
- package/demo/content/spinners.html +96 -0
- package/demo/content/switches.html +84 -0
- package/demo/content/tables.html +124 -0
- package/demo/content/tabs.html +85 -0
- package/demo/content/toasts.html +116 -0
- package/demo/content/tooltips.html +107 -0
- package/demo/content/virtual-lists.html +233 -0
- package/demo/favicon.ico +0 -0
- package/demo/favicon.png +0 -0
- package/demo/full.html +52 -0
- package/demo/iife.html +46 -0
- package/demo/manifest.json +34 -0
- package/demo/pages/datatable-demo.html +237 -0
- package/demo/pages/prompt-ui-demo.html +218 -0
- package/demo/pages/responsive-demo.html +122 -0
- package/demo/pages/schema-form-demo.html +270 -0
- package/demo/robots.txt +6 -0
- package/demo/shell.html +712 -0
- package/demo/sw.js +387 -0
- package/dist/AGENTS.md +2366 -0
- package/dist/README.md +254 -0
- package/dist/chunks/advanced.js +174 -0
- package/dist/chunks/chunk-1nhr1wrq.js +14 -0
- package/dist/chunks/chunk-hssyjbr0.js +2 -0
- package/dist/chunks/chunk-k8etzx0z.js +2 -0
- package/dist/chunks/chunk-rr1et8fg.js +2 -0
- package/dist/chunks/chunk-sjcx4fd5.js +6 -0
- package/dist/chunks/chunk-v1c777xh.js +5 -0
- package/dist/chunks/chunk-w5k5vwjd.js +13 -0
- package/dist/chunks/core.js +10 -0
- package/dist/chunks/display.js +17 -0
- package/dist/chunks/feedback.js +15 -0
- package/dist/chunks/forms.js +48 -0
- package/dist/chunks/layout.js +9 -0
- package/dist/components/chunk-4tezav8r.js +2 -0
- package/dist/components/chunk-fqyb2pms.js +2 -0
- package/dist/components/chunk-h7cdbhxw.js +13 -0
- package/dist/components/chunk-mzd8jwrs.js +2 -0
- package/dist/components/chunk-qwmxyn8e.js +2 -0
- package/dist/components/chunk-redtk47a.js +14 -0
- package/dist/components/mu-alert.js +5 -0
- package/dist/components/mu-api-table.js +33 -0
- package/dist/components/mu-avatar.js +1 -0
- package/dist/components/mu-badge.js +1 -0
- package/dist/components/mu-bottom-nav.js +1 -0
- package/dist/components/mu-button.js +1 -0
- package/dist/components/mu-callout.js +1 -0
- package/dist/components/mu-card.js +1 -0
- package/dist/components/mu-checkbox.js +9 -0
- package/dist/components/mu-chip.js +4 -0
- package/dist/components/mu-code.js +48 -0
- package/dist/components/mu-confirm.js +10 -0
- package/dist/components/mu-container.js +1 -0
- package/dist/components/mu-datatable.js +96 -0
- package/dist/components/mu-divider.js +1 -0
- package/dist/components/mu-doc-page.js +26 -0
- package/dist/components/mu-drawer-item.js +9 -0
- package/dist/components/mu-drawer.js +1 -0
- package/dist/components/mu-dropdown.js +6 -0
- package/dist/components/mu-error-boundary.js +10 -0
- package/dist/components/mu-example.js +38 -0
- package/dist/components/mu-fetch.js +1 -0
- package/dist/components/mu-form.js +1 -0
- package/dist/components/mu-grid.js +1 -0
- package/dist/components/mu-icon.js +5 -0
- package/dist/components/mu-input.js +12 -0
- package/dist/components/mu-layout.js +1 -0
- package/dist/components/mu-lazy.js +1 -0
- package/dist/components/mu-modal.js +7 -0
- package/dist/components/mu-navbar.js +1 -0
- package/dist/components/mu-page.js +1 -0
- package/dist/components/mu-progress.js +1 -0
- package/dist/components/mu-prompt-ui.js +20 -0
- package/dist/components/mu-radio.js +6 -0
- package/dist/components/mu-repeat.js +1 -0
- package/dist/components/mu-router.js +6 -0
- package/dist/components/mu-schema-form.js +76 -0
- package/dist/components/mu-sidebar.js +1 -0
- package/dist/components/mu-skeleton.js +13 -0
- package/dist/components/mu-spinner.js +1 -0
- package/dist/components/mu-stack.js +1 -0
- package/dist/components/mu-switch.js +6 -0
- package/dist/components/mu-table.js +1 -0
- package/dist/components/mu-tabs.js +1 -0
- package/dist/components/mu-textarea.js +11 -0
- package/dist/components/mu-theme-toggle.js +5 -0
- package/dist/components/mu-toast.js +4 -0
- package/dist/components/mu-tooltip.js +10 -0
- package/dist/components/mu-virtual-list.js +33 -0
- package/dist/components.css +1 -0
- package/dist/microui.css +1 -0
- package/dist/microui.d.ts +234 -0
- package/dist/microui.esm.js +549 -0
- package/dist/microui.esm.js.map +79 -0
- package/dist/microui.min.js +549 -0
- package/dist/microui.min.js.map +79 -0
- package/dist/routes/alerts.js +1 -0
- package/dist/routes/avatars.js +1 -0
- package/dist/routes/badges.js +1 -0
- package/dist/routes/buttons.js +1 -0
- package/dist/routes/callouts.js +1 -0
- package/dist/routes/cards.js +1 -0
- package/dist/routes/checkboxes.js +9 -0
- package/dist/routes/chips.js +4 -0
- package/dist/routes/chunk-19wgcncm.js +2 -0
- package/dist/routes/chunk-1khyr3v1.js +33 -0
- package/dist/routes/chunk-4rhxe97g.js +1 -0
- package/dist/routes/chunk-5qah04bh.js +2 -0
- package/dist/routes/chunk-7gfxy70n.js +5 -0
- package/dist/routes/chunk-e86zbeta.js +1 -0
- package/dist/routes/chunk-fagt36h6.js +2 -0
- package/dist/routes/chunk-fed7zr7m.js +1 -0
- package/dist/routes/chunk-hwj7pfwp.js +1 -0
- package/dist/routes/chunk-mhvcs2f8.js +5 -0
- package/dist/routes/chunk-nv3bddmj.js +13 -0
- package/dist/routes/chunk-q3f2aeqe.js +7 -0
- package/dist/routes/chunk-qxxa8trk.js +1 -0
- package/dist/routes/chunk-rw15y9zh.js +1 -0
- package/dist/routes/chunk-sfb7x11v.js +5 -0
- package/dist/routes/chunk-swyhghrm.js +48 -0
- package/dist/routes/chunk-sxddjs2d.js +2 -0
- package/dist/routes/chunk-vby0zg5w.js +17 -0
- package/dist/routes/chunk-w6zqjqqs.js +9 -0
- package/dist/routes/chunk-z960rexd.js +38 -0
- package/dist/routes/codeblocks.js +1 -0
- package/dist/routes/confirms.js +10 -0
- package/dist/routes/datatables.js +96 -0
- package/dist/routes/dividers.js +1 -0
- package/dist/routes/dropdowns.js +6 -0
- package/dist/routes/enterprise.js +15 -0
- package/dist/routes/home.js +1 -0
- package/dist/routes/icons.js +1 -0
- package/dist/routes/inputs.js +22 -0
- package/dist/routes/installation.js +1 -0
- package/dist/routes/layout.js +1 -0
- package/dist/routes/modals.js +1 -0
- package/dist/routes/navbar.js +1 -0
- package/dist/routes/page-components.json +316 -0
- package/dist/routes/progress.js +1 -0
- package/dist/routes/radios.js +6 -0
- package/dist/routes/route-deps.json +156 -0
- package/dist/routes/shell-critical.js +1 -0
- package/dist/routes/shell-deferred.js +1 -0
- package/dist/routes/shell.js +20 -0
- package/dist/routes/skeletons.js +13 -0
- package/dist/routes/spinners.js +1 -0
- package/dist/routes/src/chunks/core.js +36 -0
- package/dist/routes/switches.js +6 -0
- package/dist/routes/tables.js +1 -0
- package/dist/routes/tabs.js +1 -0
- package/dist/routes/toasts.js +1 -0
- package/dist/routes/tooltips.js +10 -0
- package/dist/routes/virtual-lists.js +33 -0
- package/dist/styles/common.css +1 -0
- package/dist/styles/components/animations.css +1 -0
- package/dist/styles/components/avatar.css +1 -0
- package/dist/styles/components/badge.css +1 -0
- package/dist/styles/components/bottom-nav.css +1 -0
- package/dist/styles/components/button.css +1 -0
- package/dist/styles/components/card.css +1 -0
- package/dist/styles/components/checkbox.css +1 -0
- package/dist/styles/components/chip.css +1 -0
- package/dist/styles/components/datatable.css +1 -0
- package/dist/styles/components/divider.css +1 -0
- package/dist/styles/components/drawer-item.css +1 -0
- package/dist/styles/components/drawer.css +1 -0
- package/dist/styles/components/grid.css +1 -0
- package/dist/styles/components/icon.css +1 -0
- package/dist/styles/components/input.css +1 -0
- package/dist/styles/components/layout.css +1 -0
- package/dist/styles/components/navbar.css +1 -0
- package/dist/styles/components/overlays.css +1 -0
- package/dist/styles/components/progress.css +1 -0
- package/dist/styles/components/prompt-ui.css +1 -0
- package/dist/styles/components/radio.css +1 -0
- package/dist/styles/components/schema-form.css +1 -0
- package/dist/styles/components/switch.css +1 -0
- package/dist/styles/components/tabs.css +1 -0
- package/dist/styles/components/tooltip.css +1 -0
- package/dist/styles/components/virtual-list.css +1 -0
- package/dist/tokens.css +1 -0
- package/docs/api-reference.md +175 -0
- package/docs/component-schema.md +231 -0
- package/docs/components.md +269 -0
- package/docs/design-system.md +183 -0
- package/docs/getting-started.md +198 -0
- package/docs/message-protocol.md +262 -0
- package/docs/utility-classes.md +205 -0
- package/lighthouse-audit.mjs +113 -0
- package/package.json +45 -0
- package/scripts/analyze-components.js +105 -0
- package/scripts/build-app.js +193 -0
- package/scripts/build-framework.js +444 -0
- package/scripts/build-utils.js +101 -0
- package/scripts/test-isolated.js +151 -0
- package/server.js +256 -0
- package/src/chunks/advanced.js +27 -0
- package/src/chunks/core.js +61 -0
- package/src/chunks/display.js +25 -0
- package/src/chunks/feedback.js +15 -0
- package/src/chunks/forms.js +25 -0
- package/src/chunks/layout.js +27 -0
- package/src/components/mu-alert.js +96 -0
- package/src/components/mu-api-table.js +167 -0
- package/src/components/mu-avatar.js +94 -0
- package/src/components/mu-badge.js +32 -0
- package/src/components/mu-bottom-nav.js +115 -0
- package/src/components/mu-button.js +61 -0
- package/src/components/mu-callout.js +71 -0
- package/src/components/mu-card.js +36 -0
- package/src/components/mu-checkbox.js +186 -0
- package/src/components/mu-chip.js +125 -0
- package/src/components/mu-code.js +534 -0
- package/src/components/mu-confirm.js +268 -0
- package/src/components/mu-container.js +53 -0
- package/src/components/mu-datatable.js +517 -0
- package/src/components/mu-divider.js +40 -0
- package/src/components/mu-doc-page.js +100 -0
- package/src/components/mu-drawer-item.js +158 -0
- package/src/components/mu-drawer.js +305 -0
- package/src/components/mu-dropdown.js +239 -0
- package/src/components/mu-error-boundary.js +191 -0
- package/src/components/mu-example.js +335 -0
- package/src/components/mu-fetch.js +256 -0
- package/src/components/mu-form.js +133 -0
- package/src/components/mu-grid.js +63 -0
- package/src/components/mu-icon.js +211 -0
- package/src/components/mu-input.js +142 -0
- package/src/components/mu-layout.js +129 -0
- package/src/components/mu-lazy.js +94 -0
- package/src/components/mu-modal.js +160 -0
- package/src/components/mu-navbar.js +71 -0
- package/src/components/mu-page.js +77 -0
- package/src/components/mu-progress.js +54 -0
- package/src/components/mu-prompt-ui.js +382 -0
- package/src/components/mu-radio.js +200 -0
- package/src/components/mu-repeat.js +135 -0
- package/src/components/mu-router.js +169 -0
- package/src/components/mu-schema-form.js +441 -0
- package/src/components/mu-sidebar.js +81 -0
- package/src/components/mu-skeleton.js +69 -0
- package/src/components/mu-spinner.js +30 -0
- package/src/components/mu-stack.js +59 -0
- package/src/components/mu-switch.js +150 -0
- package/src/components/mu-table.js +80 -0
- package/src/components/mu-tabs.js +112 -0
- package/src/components/mu-textarea.js +96 -0
- package/src/components/mu-theme-toggle.js +52 -0
- package/src/components/mu-toast.js +151 -0
- package/src/components/mu-tooltip.js +182 -0
- package/src/components/mu-virtual-list.js +184 -0
- package/src/core/MuElement.js +562 -0
- package/src/core/agent-api.js +771 -0
- package/src/core/breakpoints.js +195 -0
- package/src/core/bus.js +378 -0
- package/src/core/component-schema.js +287 -0
- package/src/core/feature-registry.js +241 -0
- package/src/core/form-state.js +252 -0
- package/src/core/http.js +104 -0
- package/src/core/keyboard.js +105 -0
- package/src/core/layers.js +71 -0
- package/src/core/render.js +201 -0
- package/src/core/ripple.js +158 -0
- package/src/core/router.js +100 -0
- package/src/core/scheduler.js +109 -0
- package/src/core/signals.js +164 -0
- package/src/core/store.js +268 -0
- package/src/core/theme.js +68 -0
- package/src/core/transitions.js +72 -0
- package/src/core/utils.js +30 -0
- package/src/index.d.ts +234 -0
- package/src/index.js +308 -0
- package/src/styles/animations.css +252 -0
- package/src/styles/common.css +82 -0
- package/src/styles/components/animations.css +129 -0
- package/src/styles/components/avatar.css +83 -0
- package/src/styles/components/badge.css +80 -0
- package/src/styles/components/bottom-nav.css +37 -0
- package/src/styles/components/button.css +348 -0
- package/src/styles/components/card.css +138 -0
- package/src/styles/components/checkbox.css +201 -0
- package/src/styles/components/chip.css +93 -0
- package/src/styles/components/datatable.css +180 -0
- package/src/styles/components/divider.css +49 -0
- package/src/styles/components/drawer-item.css +123 -0
- package/src/styles/components/drawer.css +273 -0
- package/src/styles/components/grid.css +189 -0
- package/src/styles/components/icon.css +40 -0
- package/src/styles/components/input.css +203 -0
- package/src/styles/components/layout.css +121 -0
- package/src/styles/components/navbar.css +91 -0
- package/src/styles/components/overlays.css +329 -0
- package/src/styles/components/progress.css +79 -0
- package/src/styles/components/prompt-ui.css +286 -0
- package/src/styles/components/radio.css +17 -0
- package/src/styles/components/schema-form.css +85 -0
- package/src/styles/components/switch.css +69 -0
- package/src/styles/components/tabs.css +145 -0
- package/src/styles/components/tooltip.css +93 -0
- package/src/styles/components/virtual-list.css +36 -0
- package/src/styles/components.css +3677 -0
- package/src/styles/routes/home.css +97 -0
- package/src/styles/tokens.css +675 -0
- package/tests/agents/agent-integration.test.js +76 -0
- package/tests/benchmark.html +296 -0
- package/tests/build/scan-components.test.js +173 -0
- package/tests/components/all-components.test.js +245 -0
- package/tests/components/all-missing-components.test.js +574 -0
- package/tests/components/mu-alert.test.js +113 -0
- package/tests/components/mu-avatar.test.js +148 -0
- package/tests/components/mu-badge.test.js +92 -0
- package/tests/components/mu-button.test.js +112 -0
- package/tests/components/mu-card.test.js +89 -0
- package/tests/components/mu-checkbox.test.js +158 -0
- package/tests/components/mu-chip.test.js +118 -0
- package/tests/components/mu-container.test.js +120 -0
- package/tests/components/mu-divider.test.js +98 -0
- package/tests/components/mu-drawer-item.test.js +199 -0
- package/tests/components/mu-drawer.test.js +96 -0
- package/tests/components/mu-dropdown.test.js +125 -0
- package/tests/components/mu-form.test.js +138 -0
- package/tests/components/mu-grid.test.js +135 -0
- package/tests/components/mu-icon.test.js +110 -0
- package/tests/components/mu-input.test.js +131 -0
- package/tests/components/mu-lazy.test.js +103 -0
- package/tests/components/mu-modal.test.js +275 -0
- package/tests/components/mu-navbar.test.js +101 -0
- package/tests/components/mu-progress.test.js +115 -0
- package/tests/components/mu-radio.test.js +114 -0
- package/tests/components/mu-repeat.test.js +106 -0
- package/tests/components/mu-sidebar.test.js +126 -0
- package/tests/components/mu-skeleton.test.js +162 -0
- package/tests/components/mu-stack.test.js +143 -0
- package/tests/components/mu-switch.test.js +292 -0
- package/tests/components/mu-table.test.js +124 -0
- package/tests/components/mu-tabs.test.js +104 -0
- package/tests/components/mu-textarea.test.js +115 -0
- package/tests/components/mu-toast.test.js +321 -0
- package/tests/components/mu-tooltip.test.js +133 -0
- package/tests/components/mu-virtual-list.test.js +109 -0
- package/tests/core/MuElement.test.js +120 -0
- package/tests/core/agent-api.test.js +125 -0
- package/tests/core/all-core-modules.test.js +442 -0
- package/tests/core/bus.test.js +364 -0
- package/tests/core/component-schema.test.js +160 -0
- package/tests/core/feature-registry.test.js +198 -0
- package/tests/core/form-state.test.js +167 -0
- package/tests/core/http.test.js +119 -0
- package/tests/core/keyboard.test.js +319 -0
- package/tests/core/layers.test.js +129 -0
- package/tests/core/namespaced-stores.test.js +114 -0
- package/tests/core/render.test.js +121 -0
- package/tests/core/ripple.test.js +131 -0
- package/tests/core/router.test.js +89 -0
- package/tests/core/scheduler.test.js +121 -0
- package/tests/core/signals.test.js +128 -0
- package/tests/core/store.test.js +171 -0
- package/tests/core/transitions.test.js +82 -0
- package/tests/e2e/accessibility-harness.html +58 -0
- package/tests/e2e/accessibility.test.js +401 -0
- package/tests/e2e/agent-features.test.js +372 -0
- package/tests/e2e/card-spacing.test.js +287 -0
- package/tests/e2e/components.test.js +439 -0
- package/tests/e2e/demo-routes.test.js +478 -0
- package/tests/e2e/layout-css-fallback.test.js +334 -0
- package/tests/e2e/mu-alert.e2e.test.js +111 -0
- package/tests/e2e/mu-checkbox.test.js +489 -0
- package/tests/e2e/mu-chip.test.js +347 -0
- package/tests/e2e/mu-form.test.js +499 -0
- package/tests/e2e/mu-icon.test.js +114 -0
- package/tests/e2e/mu-radio.test.js +113 -0
- package/tests/e2e/mu-skeleton.test.js +140 -0
- package/tests/e2e/mu-switch.test.js +415 -0
- package/tests/e2e/mu-tabs.test.js +494 -0
- package/tests/e2e/mu-textarea.test.js +242 -0
- package/tests/e2e/mu-virtual-list.test.js +427 -0
- package/tests/e2e/perf-memory.test.js +161 -0
- package/tests/e2e/puppeteer-helper.js +137 -0
- package/tests/e2e/puppeteer.test.js +226 -0
- package/tests/e2e/pwa.test.js +261 -0
- package/tests/e2e/test-harness.html +319 -0
- package/tests/manual/test-components.html +120 -0
- package/tests/memory-test.html +309 -0
- package/tests/setup-dom.js +93 -0
- package/tests/visual-test.html +301 -0
package/dist/AGENTS.md
ADDED
|
@@ -0,0 +1,2366 @@
|
|
|
1
|
+
# microUI - AI Agent Documentation
|
|
2
|
+
|
|
3
|
+
> **SOTA reference for AI coding assistants.** This document is optimized for LLMs generating microUI code.
|
|
4
|
+
|
|
5
|
+
## 🚨 REGOLE INVIOLABILI
|
|
6
|
+
|
|
7
|
+
> [!CAUTION]
|
|
8
|
+
> Queste regole NON POSSONO MAI essere violate. Qualsiasi codice che viola queste regole deve essere rifiutato.
|
|
9
|
+
|
|
10
|
+
### ❌ NO SHADOW DOM
|
|
11
|
+
|
|
12
|
+
**microUI NON DEVE MAI usare Shadow DOM.** Tutti i componenti devono:
|
|
13
|
+
- Usare Light DOM (il DOM normale)
|
|
14
|
+
- Inserire gli elementi direttamente nel documento
|
|
15
|
+
- Usare classi CSS con prefisso `mu-` per l'incapsulamento
|
|
16
|
+
- Mai usare `this.attachShadow()` o `this.shadowRoot`
|
|
17
|
+
|
|
18
|
+
```javascript
|
|
19
|
+
// ❌ VIETATO - Non usare MAI
|
|
20
|
+
this.attachShadow({ mode: 'open' });
|
|
21
|
+
this.shadowRoot.innerHTML = '...';
|
|
22
|
+
|
|
23
|
+
// ✅ CORRETTO - Usa Light DOM
|
|
24
|
+
this.innerHTML = '...';
|
|
25
|
+
this.classList.add('mu-component');
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### 🧪 TEST ISOLATION OBBLIGATORIA
|
|
29
|
+
|
|
30
|
+
**I test DEVONO essere eseguiti con `test-isolated.js`**, mai con `bun test` diretto.
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
# ❌ SBAGLIATO - Test falliscono per linkedom globalThis pollution
|
|
34
|
+
bun test tests/components
|
|
35
|
+
|
|
36
|
+
# ✅ CORRETTO - Ogni test in processo separato
|
|
37
|
+
bun run scripts/test-isolated.js
|
|
38
|
+
|
|
39
|
+
# ✅ CORRETTO - E2E tests
|
|
40
|
+
bun run test:e2e
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
**Perché?** linkedom (usato per simulare DOM in unit test) inquina `globalThis` e il registry dei custom elements. Quando i test vengono eseguiti in parallelo nello stesso processo, le registrazioni entrano in conflitto.
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## 🛠️ COMPONENT DEVELOPMENT GUIDELINES
|
|
48
|
+
|
|
49
|
+
> **Linee guida obbligatorie per creare nuovi componenti microUI.** Seguire queste regole previene memory leak, crash e garantisce consistenza con il framework.
|
|
50
|
+
|
|
51
|
+
### 📋 Template Componente Completo
|
|
52
|
+
|
|
53
|
+
```javascript
|
|
54
|
+
/**
|
|
55
|
+
* mu-example.js - Description of what this component does
|
|
56
|
+
*
|
|
57
|
+
* @example
|
|
58
|
+
* <mu-example label="Test" value="123"></mu-example>
|
|
59
|
+
*/
|
|
60
|
+
|
|
61
|
+
import { MuElement, define } from '../core/MuElement.js';
|
|
62
|
+
import { escapeHTML } from '../core/utils.js'; // Per contenuti user-facing
|
|
63
|
+
|
|
64
|
+
export class MuExample extends MuElement {
|
|
65
|
+
static get observedAttributes() {
|
|
66
|
+
return ['label', 'value', 'disabled'];
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
static baseClass = 'mu-example';
|
|
70
|
+
static cssFile = 'example';
|
|
71
|
+
|
|
72
|
+
constructor() {
|
|
73
|
+
super();
|
|
74
|
+
this._internalState = null;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// ========================
|
|
78
|
+
// LIFECYCLE (OBBLIGATORIO)
|
|
79
|
+
// ========================
|
|
80
|
+
|
|
81
|
+
connectedCallback() {
|
|
82
|
+
super.connectedCallback(); // ⚠️ OBBLIGATORIO - Inizializza AbortController
|
|
83
|
+
this.render();
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
disconnectedCallback() {
|
|
87
|
+
super.disconnectedCallback(); // ⚠️ OBBLIGATORIO se usi timer/subscriptions
|
|
88
|
+
// Cleanup subscriptions manuali (es. breakpoints.subscribe)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
attributeChangedCallback(name, oldVal, newVal) {
|
|
92
|
+
if (this.isConnected && oldVal !== newVal) {
|
|
93
|
+
this.render();
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// ========================
|
|
98
|
+
// PROPERTIES
|
|
99
|
+
// ========================
|
|
100
|
+
|
|
101
|
+
get label() {
|
|
102
|
+
return this.getAttribute('label') || '';
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
get value() {
|
|
106
|
+
return this.getAttribute('value') || '';
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
get disabled() {
|
|
110
|
+
return this.hasAttribute('disabled');
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// ========================
|
|
114
|
+
// RENDER (XSS-Safe)
|
|
115
|
+
// ========================
|
|
116
|
+
|
|
117
|
+
render() {
|
|
118
|
+
// ✅ SEMPRE escapare contenuti user-facing
|
|
119
|
+
this.innerHTML = `
|
|
120
|
+
<div class="mu-example">
|
|
121
|
+
<label>${escapeHTML(this.label)}</label>
|
|
122
|
+
<input
|
|
123
|
+
type="text"
|
|
124
|
+
value="${escapeHTML(this.value)}"
|
|
125
|
+
placeholder="${escapeHTML(this.getAttribute('placeholder') || '')}"
|
|
126
|
+
${this.disabled ? 'disabled' : ''}
|
|
127
|
+
>
|
|
128
|
+
</div>
|
|
129
|
+
`;
|
|
130
|
+
this._attachEventListeners();
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// ========================
|
|
134
|
+
// EVENT LISTENERS (Memory-Safe)
|
|
135
|
+
// ========================
|
|
136
|
+
|
|
137
|
+
_attachEventListeners() {
|
|
138
|
+
const input = this.querySelector('input');
|
|
139
|
+
|
|
140
|
+
// ✅ CORRETTO: Usa this.listen() per auto-cleanup
|
|
141
|
+
this.listen(input, 'input', (e) => {
|
|
142
|
+
this.dispatchEvent(new CustomEvent('mu-input', {
|
|
143
|
+
bubbles: true,
|
|
144
|
+
detail: { value: e.target.value }
|
|
145
|
+
}));
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
// ✅ CORRETTO: this.listen() per eventi window/document
|
|
149
|
+
this.listen(window, 'resize', () => this._handleResize());
|
|
150
|
+
|
|
151
|
+
// ✅ CORRETTO: Keyboard accessibility
|
|
152
|
+
if (!this.disabled) {
|
|
153
|
+
this.setupActivation(() => this._handleClick());
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// ========================
|
|
158
|
+
// TIMER (Memory-Safe)
|
|
159
|
+
// ========================
|
|
160
|
+
|
|
161
|
+
_startPolling() {
|
|
162
|
+
// ✅ CORRETTO: Usa this.setInterval() per auto-cleanup
|
|
163
|
+
this.setInterval(() => this._poll(), 5000);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
_debounce() {
|
|
167
|
+
// ✅ CORRETTO: Usa this.setTimeout() per auto-cleanup
|
|
168
|
+
this.setTimeout(() => this._doSomething(), 300);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// ========================
|
|
172
|
+
// ERROR REPORTING (AI Agent)
|
|
173
|
+
// ========================
|
|
174
|
+
|
|
175
|
+
_validate() {
|
|
176
|
+
if (!this.label) {
|
|
177
|
+
// ✅ Segnala errori per AI agent debugging
|
|
178
|
+
this.logError('MISSING_LABEL', 'mu-example requires a label attribute');
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Registration (idempotent)
|
|
184
|
+
if (!customElements.get('mu-example')) {
|
|
185
|
+
customElements.define('mu-example', MuExample);
|
|
186
|
+
}
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
### ⚠️ ANTI-PATTERN: Cosa NON Fare Mai
|
|
190
|
+
|
|
191
|
+
```javascript
|
|
192
|
+
// ❌ VIETATO: addEventListener diretto (memory leak)
|
|
193
|
+
this.querySelector('button').addEventListener('click', handler);
|
|
194
|
+
|
|
195
|
+
// ❌ VIETATO: setTimeout/setInterval raw (memory leak)
|
|
196
|
+
setTimeout(() => this.update(), 1000);
|
|
197
|
+
setInterval(() => this.poll(), 5000);
|
|
198
|
+
|
|
199
|
+
// ❌ VIETATO: Nessun super.connectedCallback() (AbortController non inizializzato)
|
|
200
|
+
connectedCallback() {
|
|
201
|
+
this.render(); // this.listen() non funzionerà!
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// ❌ VIETATO: innerHTML senza escaping (XSS vulnerability)
|
|
205
|
+
this.innerHTML = `<div>${userInput}</div>`;
|
|
206
|
+
|
|
207
|
+
// ❌ VIETATO: removeEventListener con arrow function (non funziona mai)
|
|
208
|
+
window.removeEventListener('resize', () => this.handleResize());
|
|
209
|
+
|
|
210
|
+
// ❌ VIETATO: Temporal dead zone con setTimeout
|
|
211
|
+
const id = setTimeout(() => { this._timers.delete(id); }, delay);
|
|
212
|
+
|
|
213
|
+
// ❌ VIETATO: Shadow DOM
|
|
214
|
+
this.attachShadow({ mode: 'open' });
|
|
215
|
+
|
|
216
|
+
// ❌ VIETATO: DOM Teleportation senza reset del flag listener (Bug Jan 2026)
|
|
217
|
+
// Quando un componente sposta SE STESSO nel DOM (es. appendChild(this)),
|
|
218
|
+
// disconnectedCallback aborta i listener ma il flag impedisce ri-registrazione
|
|
219
|
+
connectedCallback() {
|
|
220
|
+
if (!this._listenerSetup) { // ⚠️ Il flag resta true dopo disconnect!
|
|
221
|
+
this._listenerSetup = true;
|
|
222
|
+
this.listen(this, 'click', handler); // Perso dopo teleportation
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
// ✅ CORRETTO: Reset del flag in disconnectedCallback
|
|
226
|
+
disconnectedCallback() {
|
|
227
|
+
super.disconnectedCallback();
|
|
228
|
+
this._listenerSetup = false; // Permette ri-registrazione dopo move
|
|
229
|
+
}
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
### ✅ PATTERN OBBLIGATORI
|
|
233
|
+
|
|
234
|
+
| Pattern | Metodo | Perché |
|
|
235
|
+
|---------|--------|--------|
|
|
236
|
+
| **Event Listeners** | `this.listen(target, type, handler)` | Auto-cleanup via AbortController |
|
|
237
|
+
| **Timers** | `this.setTimeout()` / `this.setInterval()` | Auto-cleanup su disconnectedCallback |
|
|
238
|
+
| **XSS Prevention** | `escapeHTML(userInput)` | Previene injection in innerHTML |
|
|
239
|
+
| **Keyboard A11y** | `this.setupActivation(callback)` | Gestisce Enter/Space per elementi interattivi |
|
|
240
|
+
| **AI Debugging** | `this.logError(code, message)` | Errori strutturati per AI agents |
|
|
241
|
+
| **Lifecycle** | `super.connectedCallback()` | Inizializza AbortController per this.listen() |
|
|
242
|
+
| **DOM Teleportation** | Reset flag in `disconnectedCallback()` | Previene listener orfani dopo move |
|
|
243
|
+
|
|
244
|
+
### 🚀 SOTA Patterns (Feb 2026)
|
|
245
|
+
|
|
246
|
+
Pattern moderni che dovrebbero essere usati per nuovi componenti e per migrazioni.
|
|
247
|
+
|
|
248
|
+
#### ✅ Performance CSS (COMPLETATO - v3.5.28)
|
|
249
|
+
|
|
250
|
+
Il framework applica **by default** le seguenti ottimizzazioni:
|
|
251
|
+
|
|
252
|
+
| Tecnica | File | Beneficio |
|
|
253
|
+
|---------|------|-----------|
|
|
254
|
+
| `will-change` | animations.css | GPU compositor hints per 13+ classi animazione |
|
|
255
|
+
| `CSS contain` | layout.css, components.css | Isola repaint scope per mu-layout, modals |
|
|
256
|
+
| `content-visibility: auto` | components.css | Skip rendering off-screen per mu-example |
|
|
257
|
+
| Speculation Rules API | shell.html | Prefetch route bundle predittivo |
|
|
258
|
+
| View Transitions API | shell.html | Animazioni smooth tra pagine SPA |
|
|
259
|
+
|
|
260
|
+
**Lighthouse Score**: 98/100 Performance, 100/100 Accessibility/BP/SEO
|
|
261
|
+
|
|
262
|
+
```css
|
|
263
|
+
/* Framework applica automaticamente: */
|
|
264
|
+
mu-example {
|
|
265
|
+
content-visibility: auto;
|
|
266
|
+
contain-intrinsic-size: auto 300px;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
mu-layout {
|
|
270
|
+
contain: layout style;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
.mu-spin {
|
|
274
|
+
animation: mu-spin 1s linear infinite;
|
|
275
|
+
will-change: transform;
|
|
276
|
+
}
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
#### ✅ Native `<dialog>` per Modal (COMPLETATO)
|
|
280
|
+
**mu-modal** usa ora `<dialog>` nativo con `showModal()`:
|
|
281
|
+
- Top-layer automatico (niente z-index manuali)
|
|
282
|
+
- Focus trap built-in
|
|
283
|
+
- ESC key gestito nativamente
|
|
284
|
+
- `::backdrop` per scrim animato
|
|
285
|
+
- `margin: auto` per centrare
|
|
286
|
+
|
|
287
|
+
```javascript
|
|
288
|
+
// ✅ SOTA: Native dialog
|
|
289
|
+
this.#dialog = document.createElement('dialog');
|
|
290
|
+
this.#dialog.showModal();
|
|
291
|
+
|
|
292
|
+
// ❌ VECCHIO: Position fixed + z-index + manual focus trap
|
|
293
|
+
this.style.cssText = 'position:fixed;z-index:9999;';
|
|
294
|
+
document.body.appendChild(this);
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
#### 🔜 Popover API (CANDIDATI)
|
|
298
|
+
I seguenti componenti potrebbero beneficiare della **Popover API**:
|
|
299
|
+
|
|
300
|
+
| Componente | Pattern Attuale | SOTA Migration |
|
|
301
|
+
|------------|----------------|----------------|
|
|
302
|
+
| `mu-dropdown` | Portal container + z-index | `popover` attribute |
|
|
303
|
+
| `mu-tooltip` | Position fixed + z-index | `popover` attribute |
|
|
304
|
+
|
|
305
|
+
**Benefici Popover API**:
|
|
306
|
+
- Light-dismiss automatico (click outside)
|
|
307
|
+
- Top-layer (niente z-index issues)
|
|
308
|
+
- `showPopover()` / `hidePopover()` / `togglePopover()`
|
|
309
|
+
- Keyboard accessibility built-in
|
|
310
|
+
|
|
311
|
+
```html
|
|
312
|
+
<!-- SOTA: Declarative popover -->
|
|
313
|
+
<button popovertarget="my-menu">Open</button>
|
|
314
|
+
<div id="my-menu" popover>Menu content</div>
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
### 🔒 Security Checklist
|
|
318
|
+
|
|
319
|
+
Prima di completare un componente, verifica:
|
|
320
|
+
|
|
321
|
+
- [ ] Tutti i contenuti user-facing usano `escapeHTML()`
|
|
322
|
+
- [ ] Attributi interpolati in innerHTML sono escaped
|
|
323
|
+
- [ ] Slot content controllato (developer-provided) o escaped
|
|
324
|
+
- [ ] Nessun `eval()`, `Function()`, `document.write()`
|
|
325
|
+
|
|
326
|
+
### ♿ Accessibility Checklist
|
|
327
|
+
|
|
328
|
+
- [ ] Elementi interattivi hanno `tabindex="0"`
|
|
329
|
+
- [ ] Role ARIA appropriato (`role="button"`, `role="menuitem"`, etc.)
|
|
330
|
+
- [ ] `setupActivation()` per attivazione via keyboard (Enter/Space)
|
|
331
|
+
- [ ] `aria-label` o label associata per screen reader
|
|
332
|
+
- [ ] Stati (`aria-checked`, `aria-expanded`, etc.) aggiornati dinamicamente
|
|
333
|
+
|
|
334
|
+
### 🧪 Testing Checklist
|
|
335
|
+
|
|
336
|
+
- [ ] Unit test con `bun run scripts/test-isolated.js`
|
|
337
|
+
- [ ] E2E test per interazioni browser reali
|
|
338
|
+
- [ ] Coverage ≥97%
|
|
339
|
+
- [ ] Test memory leak: componente aggiunto/rimosso dal DOM non lascia listener
|
|
340
|
+
|
|
341
|
+
---
|
|
342
|
+
|
|
343
|
+
## TL;DR - Copy-Paste Templates
|
|
344
|
+
|
|
345
|
+
### Agent-First Features (v3.5)
|
|
346
|
+
microUI is optimized for AI Agents with deep introspection, runtime feedback, enterprise-scale infrastructure, and 2026 multimodal/MCP support.
|
|
347
|
+
|
|
348
|
+
**1. Introspection (Metadata)**
|
|
349
|
+
A `custom-elements.json` manifest is included in the root. Agents can read this to understand:
|
|
350
|
+
- Component Attributes (names, types, defaults)
|
|
351
|
+
- Events and payloads
|
|
352
|
+
- CSS Shadow Parts
|
|
353
|
+
|
|
354
|
+
**2. Runtime Debugging (Agent Logger)**
|
|
355
|
+
Components validate usage and log structured errors to `window.__MICROUI_ERRORS__`.
|
|
356
|
+
```javascript
|
|
357
|
+
// Check for errors in E2E tests
|
|
358
|
+
const errors = await page.evaluate(() => window.__MICROUI_ERRORS__);
|
|
359
|
+
if (errors.length > 0) console.error('microUI Errors:', errors);
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
**3. Agent API (v3.3) - LLM-Friendly Component Access**
|
|
363
|
+
Direct API for LLM agents to introspect and interact with components:
|
|
364
|
+
|
|
365
|
+
```javascript
|
|
366
|
+
// Get all components as structured data (for agent parsing)
|
|
367
|
+
const tree = microUI.getMuComponentTree();
|
|
368
|
+
// Returns: [{ tag, id, label, description, state, actions, rect, interactive, visible }]
|
|
369
|
+
|
|
370
|
+
// Get only interactive visible components
|
|
371
|
+
const buttons = microUI.getMuComponentTree(document.body, {
|
|
372
|
+
visibleOnly: true,
|
|
373
|
+
interactiveOnly: true
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
// Describe a component in natural language
|
|
377
|
+
microUI.describeComponent('#submit-btn');
|
|
378
|
+
// Returns: 'Button "Submit". Click to activate.'
|
|
379
|
+
|
|
380
|
+
// Find components by label (fuzzy match)
|
|
381
|
+
const matches = microUI.findByLabel('save');
|
|
382
|
+
|
|
383
|
+
// Get all registered microUI components
|
|
384
|
+
const components = microUI.getRegisteredComponents();
|
|
385
|
+
// Returns: Map<tag, constructor>
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
**4. Enterprise-Scale Features (v3.4) - Large Application Infrastructure**
|
|
389
|
+
|
|
390
|
+
For building complex, multi-team applications:
|
|
391
|
+
|
|
392
|
+
```javascript
|
|
393
|
+
// === NAMESPACED STORES ===
|
|
394
|
+
// Isolated state per feature/team - no conflicts
|
|
395
|
+
const userStore = microUI.createNamespacedStore('user', { profile: null });
|
|
396
|
+
const cartStore = microUI.createNamespacedStore('cart', { items: [] });
|
|
397
|
+
|
|
398
|
+
// Access stores by namespace
|
|
399
|
+
microUI.getStore('user'); // Returns userStore
|
|
400
|
+
microUI.getAllStores(); // { user: ..., cart: ... }
|
|
401
|
+
|
|
402
|
+
// Serialize/restore entire app state
|
|
403
|
+
const snapshot = microUI.captureAppState();
|
|
404
|
+
localStorage.setItem('state', JSON.stringify(snapshot));
|
|
405
|
+
microUI.restoreAppState(JSON.parse(localStorage.getItem('state')));
|
|
406
|
+
|
|
407
|
+
// === FEATURE REGISTRY ===
|
|
408
|
+
// Organize code by feature for multi-team development
|
|
409
|
+
microUI.createFeature('user', {
|
|
410
|
+
routes: ['/profile', '/settings'],
|
|
411
|
+
store: userStore,
|
|
412
|
+
components: ['mu-user-card', 'mu-user-form'],
|
|
413
|
+
meta: { team: 'user-team' }
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
microUI.getFeatures(); // All features
|
|
417
|
+
microUI.getFeatureSummary(); // Overview with stats
|
|
418
|
+
microUI.getFeatureComponents('user'); // Components in feature
|
|
419
|
+
|
|
420
|
+
// === OBSERVABILITY ===
|
|
421
|
+
// Track state changes for debugging
|
|
422
|
+
microUI.enableObservability();
|
|
423
|
+
microUI.getStateHistory('user'); // Timeline of state changes
|
|
424
|
+
microUI.getErrors(); // All caught errors
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
```html
|
|
428
|
+
<!-- === ERROR BOUNDARIES === -->
|
|
429
|
+
<!-- Catch errors with fallback UI - one error doesn't crash everything -->
|
|
430
|
+
<mu-error-boundary fallback="<p>Something went wrong</p>">
|
|
431
|
+
<mu-complex-widget></mu-complex-widget>
|
|
432
|
+
</mu-error-boundary>
|
|
433
|
+
|
|
434
|
+
<!-- Custom error handling -->
|
|
435
|
+
<mu-error-boundary onerror="logError(event.detail.error)">
|
|
436
|
+
<mu-data-table :items="${data}"></mu-data-table>
|
|
437
|
+
</mu-error-boundary>
|
|
438
|
+
```
|
|
439
|
+
|
|
440
|
+
**5. Agent Automation (v3.5) - 2026 Multimodal & MCP Support**
|
|
441
|
+
|
|
442
|
+
Based on UI-TARS, A2UI, and Anthropic MCP research (2025-2026):
|
|
443
|
+
|
|
444
|
+
```javascript
|
|
445
|
+
// === VISUAL MARKERS (for screenshot-based agents) ===
|
|
446
|
+
// Enable overlay labels for multimodal AI agents
|
|
447
|
+
microUI.enableVisualMarkers({ style: 'badge' }); // or 'outline'
|
|
448
|
+
|
|
449
|
+
// Agent takes screenshot, sees: [B1] Save, [B2] Cancel, [I1] Email
|
|
450
|
+
// Then references elements by marker ID:
|
|
451
|
+
const saveBtn = microUI.getMarkerElement('B1');
|
|
452
|
+
saveBtn.click();
|
|
453
|
+
|
|
454
|
+
// Get all marker mappings
|
|
455
|
+
microUI.getMarkerMap();
|
|
456
|
+
// { B1: { tag: 'mu-button', label: 'Save' }, I1: { tag: 'mu-input'... } }
|
|
457
|
+
|
|
458
|
+
microUI.disableVisualMarkers(); // Clean up
|
|
459
|
+
|
|
460
|
+
// === STRUCTURED SCHEMA (for code generation) ===
|
|
461
|
+
// Get JSON Schema for type-safe code gen
|
|
462
|
+
microUI.getComponentSchema('mu-button');
|
|
463
|
+
// { properties: { variant: { enum: ['filled'...] } }, actions: ['click'] }
|
|
464
|
+
|
|
465
|
+
microUI.getAllSchemas(); // All 11 documented components
|
|
466
|
+
microUI.getSchemaQuickRef('mu-input'); // Minimal summary
|
|
467
|
+
|
|
468
|
+
// === MCP ACTIONS (standard agent-tool protocol) ===
|
|
469
|
+
// MCP-compatible action definitions (Anthropic/Google 2026 standard)
|
|
470
|
+
microUI.getMCPActions();
|
|
471
|
+
// Returns 10 standardized actions:
|
|
472
|
+
// click_button, fill_input, toggle_checkbox, select_option,
|
|
473
|
+
// open_modal, close_modal, select_tab, get_component_tree,
|
|
474
|
+
// enable_visual_markers, confirm_dialog
|
|
475
|
+
```
|
|
476
|
+
|
|
477
|
+
**Semantic Attributes (auto-inferred):**
|
|
478
|
+
```html
|
|
479
|
+
<!-- Components auto-add data-mu-action and data-mu-role -->
|
|
480
|
+
<mu-button variant="filled">Save</mu-button>
|
|
481
|
+
<!-- Becomes: data-mu-action="submit" data-mu-role="primary-action" -->
|
|
482
|
+
|
|
483
|
+
<mu-button variant="text">Cancel</mu-button>
|
|
484
|
+
<!-- Becomes: data-mu-action="cancel" data-mu-role="tertiary-action" -->
|
|
485
|
+
```
|
|
486
|
+
|
|
487
|
+
### Minimal Page Setup
|
|
488
|
+
```html
|
|
489
|
+
<!DOCTYPE html>
|
|
490
|
+
<html lang="en">
|
|
491
|
+
<head>
|
|
492
|
+
<meta charset="UTF-8">
|
|
493
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
494
|
+
<link rel="stylesheet" href="dist/microui.css">
|
|
495
|
+
</head>
|
|
496
|
+
<body>
|
|
497
|
+
<!-- Your content here -->
|
|
498
|
+
<script type="module" src="dist/microui.esm.js"></script>
|
|
499
|
+
</body>
|
|
500
|
+
</html>
|
|
501
|
+
```
|
|
502
|
+
|
|
503
|
+
### Common Patterns (Copy These)
|
|
504
|
+
|
|
505
|
+
```html
|
|
506
|
+
<!-- Form with validation -->
|
|
507
|
+
<mu-form>
|
|
508
|
+
<mu-stack gap="md">
|
|
509
|
+
<mu-input label="Email" type="email" required></mu-input>
|
|
510
|
+
<mu-input label="Password" type="password" required></mu-input>
|
|
511
|
+
<mu-button variant="filled">Submit</mu-button>
|
|
512
|
+
</mu-stack>
|
|
513
|
+
</mu-form>
|
|
514
|
+
|
|
515
|
+
<!-- Card layout -->
|
|
516
|
+
<mu-grid cols="3" gap="md">
|
|
517
|
+
<mu-card variant="elevated">
|
|
518
|
+
<h3>Title</h3>
|
|
519
|
+
<p>Content</p>
|
|
520
|
+
<mu-button variant="text">Action</mu-button>
|
|
521
|
+
</mu-card>
|
|
522
|
+
</mu-grid>
|
|
523
|
+
|
|
524
|
+
<!-- Navigation tabs -->
|
|
525
|
+
<mu-tabs active="0">
|
|
526
|
+
<mu-tab>Tab 1</mu-tab>
|
|
527
|
+
<mu-tab>Tab 2</mu-tab>
|
|
528
|
+
</mu-tabs>
|
|
529
|
+
|
|
530
|
+
<!-- Modal dialog -->
|
|
531
|
+
<mu-modal id="my-modal">
|
|
532
|
+
<h2>Modal Title</h2>
|
|
533
|
+
<p>Content</p>
|
|
534
|
+
<mu-button onclick="this.closest('mu-modal').close()">Close</mu-button>
|
|
535
|
+
</mu-modal>
|
|
536
|
+
<mu-button onclick="document.getElementById('my-modal').open()">Open Modal</mu-button>
|
|
537
|
+
|
|
538
|
+
<!-- Toast notification -->
|
|
539
|
+
<script type="module">
|
|
540
|
+
import { showToast } from './dist/microui.esm.js';
|
|
541
|
+
showToast('Success!', { variant: 'success', duration: 3000 });
|
|
542
|
+
</script>
|
|
543
|
+
```
|
|
544
|
+
|
|
545
|
+
---
|
|
546
|
+
|
|
547
|
+
## Quick Reference
|
|
548
|
+
|
|
549
|
+
### All Components (49)
|
|
550
|
+
|
|
551
|
+
| Category | Components | Common Attributes |
|
|
552
|
+
|----------|------------|-------------------|
|
|
553
|
+
| **Layout** | `mu-container`, `mu-stack`, `mu-grid`, `mu-navbar`, `mu-sidebar`, `mu-divider` | `gap="xs\|sm\|md\|lg"`, `direction="row\|column"` |
|
|
554
|
+
| **Form** | `mu-button`, `mu-input`, `mu-textarea`, `mu-checkbox`, `mu-switch`, `mu-radio`, `mu-radio-group`, `mu-dropdown`, `mu-option`, `mu-chip`, `mu-form` | `variant`, `disabled`, `required`, `value` |
|
|
555
|
+
| **Display** | `mu-card`, `mu-tabs`, `mu-tab`, `mu-table`, `mu-avatar`, `mu-badge`, `mu-progress`, `mu-skeleton`, `mu-alert`, `mu-icon` | `variant="elevated\|outlined\|filled"` |
|
|
556
|
+
| **Feedback** | `mu-modal`, `mu-toast`, `mu-toast-container`, `mu-tooltip`, `mu-spinner`, `mu-confirm` | `duration`, `position` |
|
|
557
|
+
| **Performance** | `mu-virtual-list`, `mu-lazy`, `mu-repeat` | `items`, `renderItem` |
|
|
558
|
+
| **Data** | `mu-fetch` | `url`, `auto`, `interval` |
|
|
559
|
+
| **Enterprise** | `mu-error-boundary` | `fallback` |
|
|
560
|
+
| **Utility** | `mu-theme-toggle` | - |
|
|
561
|
+
|
|
562
|
+
### Attribute Cheatsheet
|
|
563
|
+
|
|
564
|
+
```html
|
|
565
|
+
<!-- Sizes -->
|
|
566
|
+
size="xs" | size="sm" | size="md" | size="lg" | size="xl"
|
|
567
|
+
|
|
568
|
+
<!-- Gaps (for layout) -->
|
|
569
|
+
gap="xs" (4px) | gap="sm" (8px) | gap="md" (16px) | gap="lg" (24px) | gap="xl" (32px)
|
|
570
|
+
|
|
571
|
+
<!-- Button variants -->
|
|
572
|
+
variant="filled" | variant="elevated" | variant="tonal" | variant="outlined" | variant="text" | variant="danger"
|
|
573
|
+
|
|
574
|
+
<!-- Card variants -->
|
|
575
|
+
variant="elevated" | variant="outlined" | variant="filled"
|
|
576
|
+
|
|
577
|
+
<!-- Alert variants -->
|
|
578
|
+
variant="info" | variant="success" | variant="warning" | variant="error"
|
|
579
|
+
|
|
580
|
+
<!-- Direction (stack) -->
|
|
581
|
+
direction="row" | direction="column"
|
|
582
|
+
|
|
583
|
+
<!-- Alignment -->
|
|
584
|
+
align="start" | align="center" | align="end" | align="stretch"
|
|
585
|
+
justify="start" | justify="center" | justify="end" | justify="space-between"
|
|
586
|
+
```
|
|
587
|
+
|
|
588
|
+
---
|
|
589
|
+
|
|
590
|
+
## Component Details
|
|
591
|
+
|
|
592
|
+
### mu-button
|
|
593
|
+
```html
|
|
594
|
+
<mu-button variant="filled" size="md" disabled>Label</mu-button>
|
|
595
|
+
```
|
|
596
|
+
| Attribute | Values | Default |
|
|
597
|
+
|-----------|--------|---------|
|
|
598
|
+
| `variant` | `filled`, `elevated`, `tonal`, `outlined`, `text`, `danger` | `filled` |
|
|
599
|
+
| `size` | `sm`, `md`, `lg` | `md` |
|
|
600
|
+
| `disabled` | boolean | `false` |
|
|
601
|
+
|
|
602
|
+
**Events:** `click` (native)
|
|
603
|
+
|
|
604
|
+
### mu-input
|
|
605
|
+
```html
|
|
606
|
+
<mu-input label="Email" type="email" placeholder="Enter email" required></mu-input>
|
|
607
|
+
```
|
|
608
|
+
| Attribute | Values | Default |
|
|
609
|
+
|-----------|--------|---------|
|
|
610
|
+
| `type` | `text`, `email`, `password`, `number`, `tel`, `url` | `text` |
|
|
611
|
+
| `label` | string | - |
|
|
612
|
+
| `placeholder` | string | - |
|
|
613
|
+
| `value` | string | `""` |
|
|
614
|
+
| `disabled` | boolean | `false` |
|
|
615
|
+
| `required` | boolean | `false` |
|
|
616
|
+
| `variant` | `outlined`, `filled` | `outlined` |
|
|
617
|
+
|
|
618
|
+
**Events:** `mu-input` (on keystroke), `mu-change` (on blur)
|
|
619
|
+
**Accessibility:** Uses `for/id` association, `aria-label` fallback
|
|
620
|
+
|
|
621
|
+
**Accessibility:** `role="checkbox"`, `aria-checked="true|false|mixed"`, keyboard (Space/Enter)
|
|
622
|
+
|
|
623
|
+
### mu-switch
|
|
624
|
+
```html
|
|
625
|
+
<mu-switch label="Dark mode" checked></mu-switch>
|
|
626
|
+
```
|
|
627
|
+
| Attribute | Values | Default |
|
|
628
|
+
|-----------|--------|---------|
|
|
629
|
+
| `label` | string | - |
|
|
630
|
+
| `checked` | boolean | `false` |
|
|
631
|
+
| `disabled` | boolean | `false` |
|
|
632
|
+
|
|
633
|
+
**Events:** `mu-change` with `{ checked: boolean }`
|
|
634
|
+
**Accessibility:** `role="switch"`, `aria-checked`, keyboard (Space/Enter)
|
|
635
|
+
|
|
636
|
+
### mu-radio-group / mu-radio
|
|
637
|
+
```html
|
|
638
|
+
<mu-radio-group name="size" value="md">
|
|
639
|
+
<mu-radio value="sm">Small</mu-radio>
|
|
640
|
+
<mu-radio value="md">Medium</mu-radio>
|
|
641
|
+
<mu-radio value="lg">Large</mu-radio>
|
|
642
|
+
</mu-radio-group>
|
|
643
|
+
```
|
|
644
|
+
| Attribute | Values | Default |
|
|
645
|
+
|-----------|--------|---------|
|
|
646
|
+
| `name` | string (group) | - |
|
|
647
|
+
| `value` | string | - |
|
|
648
|
+
| `disabled` | boolean | `false` |
|
|
649
|
+
|
|
650
|
+
**Events:** `mu-change` with `{ value: string }` on group
|
|
651
|
+
**Accessibility:** `role="radiogroup"`, `role="radio"`, `aria-checked`
|
|
652
|
+
|
|
653
|
+
### mu-dropdown
|
|
654
|
+
```html
|
|
655
|
+
<mu-dropdown placeholder="Select option" value="1">
|
|
656
|
+
<mu-option value="1">Option 1</mu-option>
|
|
657
|
+
<mu-option value="2">Option 2</mu-option>
|
|
658
|
+
</mu-dropdown>
|
|
659
|
+
```
|
|
660
|
+
| Attribute | Values | Default |
|
|
661
|
+
|-----------|--------|---------|
|
|
662
|
+
| `placeholder` | string | - |
|
|
663
|
+
| `value` | string | - |
|
|
664
|
+
| `disabled` | boolean | `false` |
|
|
665
|
+
|
|
666
|
+
**Events:** `mu-change` with `{ value: string, label: string }`
|
|
667
|
+
**Events:** `mu-change` with `{ value: string, label: string }`
|
|
668
|
+
**Accessibility:** `role="listbox"`, `aria-expanded`, full keyboard navigation (Arrows, Enter, Escape, Space)
|
|
669
|
+
|
|
670
|
+
### mu-modal
|
|
671
|
+
```html
|
|
672
|
+
<mu-modal id="dialog">
|
|
673
|
+
<h2>Title</h2>
|
|
674
|
+
<p>Content</p>
|
|
675
|
+
</mu-modal>
|
|
676
|
+
```
|
|
677
|
+
**Methods:** `.open()`, `.close()`
|
|
678
|
+
**Events:** `mu-open`, `mu-close`
|
|
679
|
+
|
|
680
|
+
### mu-tabs / mu-tab
|
|
681
|
+
```html
|
|
682
|
+
<mu-tabs active="0" id="my-tabs">
|
|
683
|
+
<mu-tab>First</mu-tab>
|
|
684
|
+
<mu-tab>Second</mu-tab>
|
|
685
|
+
</mu-tabs>
|
|
686
|
+
```
|
|
687
|
+
**Events:** `mu-change` with `{ index: number }`
|
|
688
|
+
|
|
689
|
+
### mu-stack (Flexbox layout)
|
|
690
|
+
```html
|
|
691
|
+
<mu-stack direction="row" gap="md" align="center" justify="space-between">
|
|
692
|
+
<div>Item 1</div>
|
|
693
|
+
<div>Item 2</div>
|
|
694
|
+
</mu-stack>
|
|
695
|
+
```
|
|
696
|
+
|
|
697
|
+
### mu-grid (CSS Grid)
|
|
698
|
+
```html
|
|
699
|
+
<mu-grid cols="3" gap="md">
|
|
700
|
+
<mu-card>1</mu-card>
|
|
701
|
+
<mu-card>2</mu-card>
|
|
702
|
+
<mu-card>3</mu-card>
|
|
703
|
+
</mu-grid>
|
|
704
|
+
```
|
|
705
|
+
**Responsive Behavior:** Grids with `cols="3"`, `cols="4"`, or `cols="6"` **automatically collapse to 1 column** on mobile (< 600px).
|
|
706
|
+
|
|
707
|
+
### mu-virtual-list (10K+ items)
|
|
708
|
+
```javascript
|
|
709
|
+
const list = document.querySelector('mu-virtual-list');
|
|
710
|
+
list.items = largeArray;
|
|
711
|
+
list.itemHeight = 50;
|
|
712
|
+
list.renderItem = (item) => `<div class="item">${item.name}</div>`;
|
|
713
|
+
```
|
|
714
|
+
|
|
715
|
+
---
|
|
716
|
+
|
|
717
|
+
## Responsive Breakpoints (MD3)
|
|
718
|
+
|
|
719
|
+
microUI follows **Material Design 3 Window Size Classes** for adaptive layouts:
|
|
720
|
+
|
|
721
|
+
| Class | Width | Components Behavior |
|
|
722
|
+
|-------|-------|---------------------|
|
|
723
|
+
| **Compact** | < 600px | `mu-grid` → 1 column, `mu-bottom-nav` visible, `mu-drawer` → modal |
|
|
724
|
+
| **Medium** | 600-839px | `mu-drawer` → rail mode |
|
|
725
|
+
| **Expanded** | ≥ 840px | `mu-drawer` → expanded, `mu-bottom-nav` hidden |
|
|
726
|
+
|
|
727
|
+
### Breakpoints API
|
|
728
|
+
```javascript
|
|
729
|
+
import { breakpoints } from 'microui/core/breakpoints';
|
|
730
|
+
|
|
731
|
+
// Reactive subscriptions (for components that need to react to changes)
|
|
732
|
+
const unsubscribe = breakpoints.subscribe((size) => {
|
|
733
|
+
console.log(`Now in ${size} mode`); // 'compact', 'medium', 'expanded'
|
|
734
|
+
});
|
|
735
|
+
|
|
736
|
+
// Direct checks (live queries - always current, no caching)
|
|
737
|
+
if (breakpoints.isCompact) {
|
|
738
|
+
// Mobile-specific logic
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
if (breakpoints.isMedium) {
|
|
742
|
+
// Tablet-specific logic
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
if (breakpoints.isExpanded) {
|
|
746
|
+
// Desktop-specific logic
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
// Cleanup when done
|
|
750
|
+
unsubscribe();
|
|
751
|
+
```
|
|
752
|
+
|
|
753
|
+
### Best Practices for Responsive Agents
|
|
754
|
+
|
|
755
|
+
1. **Don't hardcode media queries** - Use the centralized `breakpoints` utility
|
|
756
|
+
2. **Use CSS for simple responsive** - `mu-grid` automatically collapses via CSS
|
|
757
|
+
3. **Subscribe for complex logic** - Components needing JS-driven adaptation should subscribe
|
|
758
|
+
4. **Clean up subscriptions** - Always unsubscribe in `disconnectedCallback()`
|
|
759
|
+
|
|
760
|
+
```javascript
|
|
761
|
+
// Example: Component that adapts to breakpoints
|
|
762
|
+
class MyComponent extends MuElement {
|
|
763
|
+
#unsubscribe = null;
|
|
764
|
+
|
|
765
|
+
connectedCallback() {
|
|
766
|
+
super.connectedCallback();
|
|
767
|
+
this.#unsubscribe = breakpoints.subscribe(() => {
|
|
768
|
+
this.#updateLayout();
|
|
769
|
+
});
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
disconnectedCallback() {
|
|
773
|
+
this.#unsubscribe?.();
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
#updateLayout() {
|
|
777
|
+
if (breakpoints.isCompact) {
|
|
778
|
+
this.classList.add('mobile');
|
|
779
|
+
} else {
|
|
780
|
+
this.classList.remove('mobile');
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
```
|
|
785
|
+
|
|
786
|
+
---
|
|
787
|
+
|
|
788
|
+
## JavaScript API
|
|
789
|
+
|
|
790
|
+
### Signals (Reactive State)
|
|
791
|
+
```javascript
|
|
792
|
+
import { signal, computed, effect } from 'microui';
|
|
793
|
+
|
|
794
|
+
const count = signal(0, 'my-counter'); // name for debugging
|
|
795
|
+
const doubled = computed(() => count() * 2);
|
|
796
|
+
|
|
797
|
+
effect(() => {
|
|
798
|
+
console.log('Count:', count()); // Auto-tracks dependencies
|
|
799
|
+
});
|
|
800
|
+
|
|
801
|
+
count.set(5); // Set value
|
|
802
|
+
count.update(n => n + 1); // Update with function
|
|
803
|
+
count.peek(); // Read without tracking
|
|
804
|
+
```
|
|
805
|
+
|
|
806
|
+
### EventBus (CrossBus)
|
|
807
|
+
```javascript
|
|
808
|
+
import { bus } from 'microui';
|
|
809
|
+
|
|
810
|
+
// Subscribe
|
|
811
|
+
const unsub = bus.on('my:event', (data) => console.log(data));
|
|
812
|
+
|
|
813
|
+
// Emit (111M ops/sec - synchronous)
|
|
814
|
+
bus.emit('my:event', { key: 'value' });
|
|
815
|
+
|
|
816
|
+
// Cleanup
|
|
817
|
+
unsub();
|
|
818
|
+
```
|
|
819
|
+
|
|
820
|
+
### Keyboard Manager (ESC Stack)
|
|
821
|
+
|
|
822
|
+
Centralized LIFO stack for handling ESC key in modal-like components. When multiple modals/dropdowns are open, ESC closes only the **topmost** one.
|
|
823
|
+
|
|
824
|
+
```javascript
|
|
825
|
+
import { keyboard } from 'microui/core/keyboard';
|
|
826
|
+
|
|
827
|
+
class MuMyModal extends MuElement {
|
|
828
|
+
#unsubEsc = null;
|
|
829
|
+
|
|
830
|
+
open() {
|
|
831
|
+
this.hidden = false;
|
|
832
|
+
// Register ESC handler - returns unsubscribe function
|
|
833
|
+
this.#unsubEsc = keyboard.pushEscapeHandler(this, () => this.close());
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
close() {
|
|
837
|
+
this.hidden = true;
|
|
838
|
+
// Always unsubscribe when closing
|
|
839
|
+
this.#unsubEsc?.();
|
|
840
|
+
this.#unsubEsc = null;
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
disconnectedCallback() {
|
|
844
|
+
super.disconnectedCallback();
|
|
845
|
+
this.#unsubEsc?.(); // Cleanup on removal
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
```
|
|
849
|
+
|
|
850
|
+
**API:**
|
|
851
|
+
| Method | Returns | Description |
|
|
852
|
+
|--------|---------|-------------|
|
|
853
|
+
| `pushEscapeHandler(element, callback)` | `() => void` | Register handler, returns unsubscribe function |
|
|
854
|
+
| `isTopmost(element)` | `boolean` | Check if element is top of stack |
|
|
855
|
+
| `stackDepth` | `number` | Current stack depth (for debugging) |
|
|
856
|
+
|
|
857
|
+
### Layer System (Z-Index)
|
|
858
|
+
|
|
859
|
+
Centralized z-index constants to avoid magic numbers. Auto-injected as CSS custom properties.
|
|
860
|
+
|
|
861
|
+
```javascript
|
|
862
|
+
import { Z_INDEX } from 'microui/core/layers';
|
|
863
|
+
|
|
864
|
+
// Use in JavaScript
|
|
865
|
+
this.style.zIndex = Z_INDEX.modal;
|
|
866
|
+
```
|
|
867
|
+
|
|
868
|
+
```css
|
|
869
|
+
/* Use in CSS (auto-injected as --z-* tokens) */
|
|
870
|
+
.my-modal { z-index: var(--z-modal); }
|
|
871
|
+
.my-tooltip { z-index: var(--z-tooltip); }
|
|
872
|
+
```
|
|
873
|
+
|
|
874
|
+
**Layer Hierarchy (lowest to highest):**
|
|
875
|
+
| Token | Value | Use For |
|
|
876
|
+
|-------|-------|---------|
|
|
877
|
+
| `base` | 1 | Default content |
|
|
878
|
+
| `sticky` | 100 | Sticky headers, FABs |
|
|
879
|
+
| `dropdown` | 1000 | Dropdowns, menus, popovers |
|
|
880
|
+
| `drawer` | 1100 | Navigation drawers |
|
|
881
|
+
| `modal` | 1200 | Modal dialogs |
|
|
882
|
+
| `toast` | 1300 | Toast notifications |
|
|
883
|
+
| `tooltip` | 1400 | Tooltips |
|
|
884
|
+
| `overlay` | 9999 | Full-screen overlays |
|
|
885
|
+
| `devtools` | 999999 | Agent dev tools (always on top) |
|
|
886
|
+
|
|
887
|
+
### Confirm Dialog (Agent-Friendly)
|
|
888
|
+
```javascript
|
|
889
|
+
import { muConfirm } from 'microui';
|
|
890
|
+
|
|
891
|
+
// Simple confirm - returns Promise<boolean>
|
|
892
|
+
const ok = await muConfirm('Delete this item?');
|
|
893
|
+
if (ok) deleteItem();
|
|
894
|
+
|
|
895
|
+
// With options
|
|
896
|
+
const confirmed = await muConfirm('Are you sure?', {
|
|
897
|
+
title: 'Confirm Delete',
|
|
898
|
+
confirmText: 'Delete',
|
|
899
|
+
cancelText: 'Cancel',
|
|
900
|
+
variant: 'danger' // 'primary' | 'danger' | 'warning'
|
|
901
|
+
});
|
|
902
|
+
```
|
|
903
|
+
|
|
904
|
+
### Data Fetcher Component
|
|
905
|
+
```html
|
|
906
|
+
<!-- Declarative data fetching with automatic states -->
|
|
907
|
+
<mu-fetch url="/api/users" id="users">
|
|
908
|
+
<template slot="loading"><mu-skeleton lines="3"></mu-skeleton></template>
|
|
909
|
+
<template slot="error"><mu-alert variant="error">${error}</mu-alert></template>
|
|
910
|
+
<template slot="empty"><p>No users found</p></template>
|
|
911
|
+
</mu-fetch>
|
|
912
|
+
|
|
913
|
+
<script type="module">
|
|
914
|
+
const fetcher = document.getElementById('users');
|
|
915
|
+
|
|
916
|
+
// Set render function for array data
|
|
917
|
+
fetcher.renderItem = (user) => `
|
|
918
|
+
<mu-card variant="outlined">
|
|
919
|
+
<strong>${user.name}</strong> - ${user.email}
|
|
920
|
+
</mu-card>
|
|
921
|
+
`;
|
|
922
|
+
|
|
923
|
+
// Programmatic access
|
|
924
|
+
fetcher.data; // Current data
|
|
925
|
+
fetcher.state; // 'idle' | 'loading' | 'success' | 'error'
|
|
926
|
+
fetcher.refetch(); // Re-fetch data
|
|
927
|
+
|
|
928
|
+
// Events
|
|
929
|
+
fetcher.addEventListener('mu-success', (e) => {
|
|
930
|
+
console.log('Data loaded:', e.detail.data);
|
|
931
|
+
});
|
|
932
|
+
</script>
|
|
933
|
+
```
|
|
934
|
+
|
|
935
|
+
### Form State Management
|
|
936
|
+
```javascript
|
|
937
|
+
import { createFormState } from 'microui';
|
|
938
|
+
|
|
939
|
+
const form = document.querySelector('mu-form');
|
|
940
|
+
const state = createFormState(form, {
|
|
941
|
+
email: { required: true, type: 'email' },
|
|
942
|
+
password: { required: true, minLength: 8 },
|
|
943
|
+
name: { maxLength: 50 }
|
|
944
|
+
});
|
|
945
|
+
|
|
946
|
+
// Check validation
|
|
947
|
+
console.log(state.isValid); // true if all fields valid
|
|
948
|
+
console.log(state.isDirty); // true if any field changed
|
|
949
|
+
|
|
950
|
+
// Per-field state
|
|
951
|
+
state.fields.email.value; // Current value
|
|
952
|
+
state.fields.email.error; // Error message or null
|
|
953
|
+
state.fields.email.touched; // true after blur
|
|
954
|
+
state.fields.email.dirty; // true if changed from initial
|
|
955
|
+
|
|
956
|
+
// Actions
|
|
957
|
+
state.validate(); // Validate all, mark touched
|
|
958
|
+
state.getValues(); // { email: '...', password: '...' }
|
|
959
|
+
state.getErrors(); // { email: null, password: 'Min 8 chars' }
|
|
960
|
+
state.reset(); // Reset to initial values
|
|
961
|
+
```
|
|
962
|
+
|
|
963
|
+
|
|
964
|
+
### Toast Notifications
|
|
965
|
+
```javascript
|
|
966
|
+
import { showToast } from 'microui';
|
|
967
|
+
|
|
968
|
+
showToast('Operation successful', {
|
|
969
|
+
variant: 'success', // 'info' | 'success' | 'warning' | 'error'
|
|
970
|
+
duration: 3000 // ms, 0 for persistent
|
|
971
|
+
});
|
|
972
|
+
```
|
|
973
|
+
|
|
974
|
+
### Theme Control
|
|
975
|
+
```javascript
|
|
976
|
+
import { Theme } from 'microui';
|
|
977
|
+
|
|
978
|
+
Theme.init(); // Auto-detect or use saved
|
|
979
|
+
Theme.set('dark'); // 'light' | 'dark' | 'system'
|
|
980
|
+
Theme.toggle(); // Switch light/dark
|
|
981
|
+
const current = Theme.get();
|
|
982
|
+
```
|
|
983
|
+
|
|
984
|
+
### View Transitions
|
|
985
|
+
```javascript
|
|
986
|
+
import { transition } from 'microui';
|
|
987
|
+
|
|
988
|
+
await transition(() => {
|
|
989
|
+
document.querySelector('#content').innerHTML = newHTML;
|
|
990
|
+
});
|
|
991
|
+
```
|
|
992
|
+
|
|
993
|
+
---
|
|
994
|
+
|
|
995
|
+
## Cache Control (PWA)
|
|
996
|
+
|
|
997
|
+
microUI includes a Service Worker with intelligent caching. Control via console:
|
|
998
|
+
|
|
999
|
+
```javascript
|
|
1000
|
+
// Clear all caches and reload
|
|
1001
|
+
await microUICache.clear();
|
|
1002
|
+
|
|
1003
|
+
// Get cache status
|
|
1004
|
+
await microUICache.status();
|
|
1005
|
+
// Returns: { version: "2.0.18", caches: { "microui-precache-2.0.18": 5 } }
|
|
1006
|
+
|
|
1007
|
+
// Force check for updates
|
|
1008
|
+
await microUICache.update();
|
|
1009
|
+
|
|
1010
|
+
// Prefetch URLs into cache
|
|
1011
|
+
await microUICache.prefetch(['/assets/image.webp', '/data.json']);
|
|
1012
|
+
```
|
|
1013
|
+
|
|
1014
|
+
---
|
|
1015
|
+
|
|
1016
|
+
## PWA Development for Agents
|
|
1017
|
+
|
|
1018
|
+
> **Complete guide for AI agents building Progressive Web Apps with microUI.**
|
|
1019
|
+
|
|
1020
|
+
### 1. Service Worker Setup
|
|
1021
|
+
|
|
1022
|
+
Copy this template to your app root as `sw.js`:
|
|
1023
|
+
|
|
1024
|
+
```javascript
|
|
1025
|
+
// sw.js - microUI Service Worker Template
|
|
1026
|
+
const VERSION = '1.0.0'; // Bump on each deploy
|
|
1027
|
+
const CACHE_PREFIX = 'myapp';
|
|
1028
|
+
const PRECACHE_NAME = `${CACHE_PREFIX}-precache-${VERSION}`;
|
|
1029
|
+
const RUNTIME_NAME = `${CACHE_PREFIX}-runtime-${VERSION}`;
|
|
1030
|
+
|
|
1031
|
+
// Critical assets (precached on install)
|
|
1032
|
+
const PRECACHE_ASSETS = [
|
|
1033
|
+
'/',
|
|
1034
|
+
'/shell.html',
|
|
1035
|
+
'/dist/microui.css',
|
|
1036
|
+
'/dist/routes/shell.js',
|
|
1037
|
+
'/dist/routes/home.js',
|
|
1038
|
+
'/favicon.png'
|
|
1039
|
+
];
|
|
1040
|
+
|
|
1041
|
+
// INSTALL: Precache critical assets
|
|
1042
|
+
self.addEventListener('install', (event) => {
|
|
1043
|
+
event.waitUntil(
|
|
1044
|
+
caches.open(PRECACHE_NAME)
|
|
1045
|
+
.then(cache => cache.addAll(PRECACHE_ASSETS))
|
|
1046
|
+
.then(() => self.skipWaiting())
|
|
1047
|
+
);
|
|
1048
|
+
});
|
|
1049
|
+
|
|
1050
|
+
// ACTIVATE: Cleanup old caches
|
|
1051
|
+
self.addEventListener('activate', (event) => {
|
|
1052
|
+
event.waitUntil(
|
|
1053
|
+
caches.keys().then(names =>
|
|
1054
|
+
Promise.all(names
|
|
1055
|
+
.filter(n => n.startsWith(CACHE_PREFIX) && n !== PRECACHE_NAME && n !== RUNTIME_NAME)
|
|
1056
|
+
.map(n => caches.delete(n))
|
|
1057
|
+
)
|
|
1058
|
+
).then(() => self.clients.claim())
|
|
1059
|
+
);
|
|
1060
|
+
});
|
|
1061
|
+
|
|
1062
|
+
// FETCH: Route requests
|
|
1063
|
+
self.addEventListener('fetch', (event) => {
|
|
1064
|
+
const { request } = event;
|
|
1065
|
+
if (request.method !== 'GET') return;
|
|
1066
|
+
|
|
1067
|
+
const url = new URL(request.url);
|
|
1068
|
+
if (url.origin !== self.location.origin) return;
|
|
1069
|
+
|
|
1070
|
+
// HTML: Network-first (always fresh)
|
|
1071
|
+
if (request.destination === 'document') {
|
|
1072
|
+
event.respondWith(networkFirst(request));
|
|
1073
|
+
return;
|
|
1074
|
+
}
|
|
1075
|
+
|
|
1076
|
+
// JS/CSS: Stale-while-revalidate (fast + fresh)
|
|
1077
|
+
event.respondWith(staleWhileRevalidate(request));
|
|
1078
|
+
});
|
|
1079
|
+
|
|
1080
|
+
async function networkFirst(request) {
|
|
1081
|
+
try {
|
|
1082
|
+
const response = await fetch(request);
|
|
1083
|
+
const cache = await caches.open(PRECACHE_NAME);
|
|
1084
|
+
cache.put(request, response.clone());
|
|
1085
|
+
return response;
|
|
1086
|
+
} catch {
|
|
1087
|
+
return caches.match(request) || caches.match('/');
|
|
1088
|
+
}
|
|
1089
|
+
}
|
|
1090
|
+
|
|
1091
|
+
async function staleWhileRevalidate(request) {
|
|
1092
|
+
const cache = await caches.open(RUNTIME_NAME);
|
|
1093
|
+
const cached = await cache.match(request);
|
|
1094
|
+
|
|
1095
|
+
const fetchPromise = fetch(request).then(response => {
|
|
1096
|
+
if (response.ok) cache.put(request, response.clone());
|
|
1097
|
+
return response;
|
|
1098
|
+
}).catch(() => null);
|
|
1099
|
+
|
|
1100
|
+
return cached || fetchPromise || caches.match('/');
|
|
1101
|
+
}
|
|
1102
|
+
```
|
|
1103
|
+
|
|
1104
|
+
### 2. Register Service Worker
|
|
1105
|
+
|
|
1106
|
+
Add to your `shell.html` (after app shell loads):
|
|
1107
|
+
|
|
1108
|
+
```html
|
|
1109
|
+
<script>
|
|
1110
|
+
if ('serviceWorker' in navigator) {
|
|
1111
|
+
navigator.serviceWorker.register('/sw.js')
|
|
1112
|
+
.then(reg => console.log('SW registered:', reg.scope))
|
|
1113
|
+
.catch(err => console.error('SW failed:', err));
|
|
1114
|
+
}
|
|
1115
|
+
</script>
|
|
1116
|
+
```
|
|
1117
|
+
|
|
1118
|
+
### 3. Caching Strategy Guide
|
|
1119
|
+
|
|
1120
|
+
| Asset Type | Strategy | Why |
|
|
1121
|
+
|------------|----------|-----|
|
|
1122
|
+
| **HTML** | Network-first | Always get fresh content |
|
|
1123
|
+
| **JS/CSS/Images** | Stale-while-revalidate | Fast load + background update |
|
|
1124
|
+
| **Fonts (woff2)** | Cache-first | Immutable, never changes |
|
|
1125
|
+
| **API calls** | Network-only | Data must be fresh |
|
|
1126
|
+
|
|
1127
|
+
### 4. App Shell + Service Worker Integration
|
|
1128
|
+
|
|
1129
|
+
```
|
|
1130
|
+
Initial Load Flow:
|
|
1131
|
+
─────────────────────────────────────────────────────────
|
|
1132
|
+
Browser → SW Installed? ─No→ Network → Cache critical assets
|
|
1133
|
+
│
|
|
1134
|
+
Yes
|
|
1135
|
+
│
|
|
1136
|
+
▼
|
|
1137
|
+
Cache → Return shell.js (instant)
|
|
1138
|
+
│
|
|
1139
|
+
▼
|
|
1140
|
+
Render App Shell (navbar, theme)
|
|
1141
|
+
│
|
|
1142
|
+
▼
|
|
1143
|
+
Lazy load route (home.js)
|
|
1144
|
+
│
|
|
1145
|
+
▼
|
|
1146
|
+
Background: Check for SW update
|
|
1147
|
+
```
|
|
1148
|
+
|
|
1149
|
+
### 5. PWA Checklist for Agents
|
|
1150
|
+
|
|
1151
|
+
```
|
|
1152
|
+
[ ] manifest.json created (name, icons, theme_color)
|
|
1153
|
+
[ ] sw.js in app root
|
|
1154
|
+
[ ] PRECACHE_ASSETS includes shell.js + CSS
|
|
1155
|
+
[ ] VERSION bumped on each deploy
|
|
1156
|
+
[ ] Meta tags: theme-color, apple-mobile-web-app-capable
|
|
1157
|
+
[ ] Favicon + PWA icons (192x192, 512x512)
|
|
1158
|
+
[ ] HTTPS enabled (required for SW)
|
|
1159
|
+
[ ] Tested offline mode
|
|
1160
|
+
```
|
|
1161
|
+
|
|
1162
|
+
### 6. Manifest Template
|
|
1163
|
+
|
|
1164
|
+
```json
|
|
1165
|
+
{
|
|
1166
|
+
"name": "My microUI App",
|
|
1167
|
+
"short_name": "MyApp",
|
|
1168
|
+
"start_url": "/",
|
|
1169
|
+
"display": "standalone",
|
|
1170
|
+
"background_color": "#FFFBFE",
|
|
1171
|
+
"theme_color": "#6750A4",
|
|
1172
|
+
"icons": [
|
|
1173
|
+
{ "src": "/icons/icon-192.png", "sizes": "192x192", "type": "image/png" },
|
|
1174
|
+
{ "src": "/icons/icon-512.png", "sizes": "512x512", "type": "image/png" }
|
|
1175
|
+
]
|
|
1176
|
+
}
|
|
1177
|
+
```
|
|
1178
|
+
|
|
1179
|
+
### 7. Agent Workflow: Creating a PWA
|
|
1180
|
+
|
|
1181
|
+
```bash
|
|
1182
|
+
# 1. Copy SW template
|
|
1183
|
+
cp demo/sw.js my-app/sw.js
|
|
1184
|
+
|
|
1185
|
+
# 2. Update PRECACHE_ASSETS with your routes
|
|
1186
|
+
# 3. Create manifest.json
|
|
1187
|
+
# 4. Add meta tags to shell.html
|
|
1188
|
+
# 5. Register SW in shell.html
|
|
1189
|
+
# 6. Build and test
|
|
1190
|
+
bun run build
|
|
1191
|
+
```
|
|
1192
|
+
|
|
1193
|
+
---
|
|
1194
|
+
|
|
1195
|
+
## Accessibility Compliance (WCAG 2.1)
|
|
1196
|
+
|
|
1197
|
+
All form components include proper ARIA attributes:
|
|
1198
|
+
|
|
1199
|
+
| Component | Role | Key Attributes |
|
|
1200
|
+
|-----------|------|----------------|
|
|
1201
|
+
| `mu-checkbox` | `checkbox` | `aria-checked="true\|false\|mixed"`, `tabindex` |
|
|
1202
|
+
| `mu-switch` | `switch` | `aria-checked`, `aria-label` |
|
|
1203
|
+
| `mu-radio` | `radio` | `aria-checked`, `tabindex` |
|
|
1204
|
+
| `mu-radio-group` | `radiogroup` | - |
|
|
1205
|
+
| `mu-dropdown` | trigger + listbox | `aria-haspopup`, `aria-expanded` |
|
|
1206
|
+
| `mu-button` | `button` | `tabindex` |
|
|
1207
|
+
| `mu-input` | - | `for/id` label association, `aria-label` fallback |
|
|
1208
|
+
|
|
1209
|
+
**Keyboard navigation:** All interactive components support `Tab`, `Space`, `Enter`.
|
|
1210
|
+
|
|
1211
|
+
---
|
|
1212
|
+
|
|
1213
|
+
## Best Practices for AI Agents
|
|
1214
|
+
|
|
1215
|
+
### DO ✅
|
|
1216
|
+
1. **Use semantic components** - `<mu-input label="Email">` not `<input placeholder="Email">`
|
|
1217
|
+
2. **Use layout components** - `<mu-stack gap="md">` not manual CSS flexbox
|
|
1218
|
+
3. **Use declarative HTML** - Attributes over JavaScript for configuration
|
|
1219
|
+
4. **Always include labels** - For accessibility compliance
|
|
1220
|
+
5. **Use `variant` attributes** - For consistent MD3 styling
|
|
1221
|
+
6. **Use `mu-virtual-list`** - For lists with 100+ items
|
|
1222
|
+
7. **Use `mu-lazy`** - For below-the-fold heavy components
|
|
1223
|
+
|
|
1224
|
+
### DON'T ❌
|
|
1225
|
+
1. **Don't use raw CSS for layout** - Use `mu-stack`, `mu-grid`, `mu-container`
|
|
1226
|
+
2. **Don't forget `variant`** - Buttons/cards need explicit variant
|
|
1227
|
+
3. **Don't manipulate Shadow DOM** - Components manage their own internals
|
|
1228
|
+
4. **Don't use inline styles for spacing** - Use `gap` attributes
|
|
1229
|
+
5. **Don't create custom form controls** - Use the provided accessible ones
|
|
1230
|
+
|
|
1231
|
+
### CSS Fallback Pattern ⚡
|
|
1232
|
+
Layout components (`mu-stack`, `mu-grid`) have **CSS fallback styles** that apply BEFORE JavaScript loads. This prevents Flash of Unstyled Content (FOUC).
|
|
1233
|
+
|
|
1234
|
+
```css
|
|
1235
|
+
/* CSS attribute selectors work immediately */
|
|
1236
|
+
mu-stack { display: flex; flex-direction: column; gap: 16px; }
|
|
1237
|
+
mu-stack[gap="lg"] { gap: 24px; }
|
|
1238
|
+
|
|
1239
|
+
mu-grid { display: grid; gap: 16px; }
|
|
1240
|
+
mu-grid[cols="3"] { grid-template-columns: repeat(3, 1fr); }
|
|
1241
|
+
```
|
|
1242
|
+
|
|
1243
|
+
**Why this matters:** HTML is parsed before JavaScript executes. Without CSS fallback, layout elements have no styling until custom element upgrade completes.
|
|
1244
|
+
|
|
1245
|
+
### Common Mistakes to Avoid
|
|
1246
|
+
```html
|
|
1247
|
+
<!-- WRONG: Missing variant -->
|
|
1248
|
+
<mu-button>Click</mu-button>
|
|
1249
|
+
|
|
1250
|
+
<!-- CORRECT: Explicit variant -->
|
|
1251
|
+
<mu-button variant="filled">Click</mu-button>
|
|
1252
|
+
|
|
1253
|
+
<!-- WRONG: Raw div layout -->
|
|
1254
|
+
<div style="display: flex; gap: 16px;">
|
|
1255
|
+
|
|
1256
|
+
<!-- CORRECT: Use mu-stack -->
|
|
1257
|
+
<mu-stack direction="row" gap="md">
|
|
1258
|
+
|
|
1259
|
+
<!-- WRONG: Placeholder only (accessibility issue) -->
|
|
1260
|
+
<mu-input placeholder="Email"></mu-input>
|
|
1261
|
+
|
|
1262
|
+
<!-- CORRECT: Use label -->
|
|
1263
|
+
<mu-input label="Email"></mu-input>
|
|
1264
|
+
```
|
|
1265
|
+
|
|
1266
|
+
### XSS Prevention Patterns ⚠️
|
|
1267
|
+
|
|
1268
|
+
microUI provides centralized utilities for safe HTML rendering. **Always use these patterns when handling dynamic content.**
|
|
1269
|
+
|
|
1270
|
+
```javascript
|
|
1271
|
+
// Import centralized utility
|
|
1272
|
+
import { escapeHTML } from 'microui';
|
|
1273
|
+
|
|
1274
|
+
// ✅ SAFE: Escape user-provided content
|
|
1275
|
+
const userName = '<script>alert("xss")</script>';
|
|
1276
|
+
element.innerHTML = `<span>${escapeHTML(userName)}</span>`;
|
|
1277
|
+
// Result: <span><script>alert("xss")</script></span>
|
|
1278
|
+
|
|
1279
|
+
// ✅ SAFE: Use textContent for plain text
|
|
1280
|
+
element.textContent = userName;
|
|
1281
|
+
|
|
1282
|
+
// ✅ SAFE: Use createElement + property assignment
|
|
1283
|
+
const img = document.createElement('img');
|
|
1284
|
+
img.src = userProvidedUrl;
|
|
1285
|
+
img.alt = userProvidedAlt; // Property, not attribute string
|
|
1286
|
+
element.appendChild(img);
|
|
1287
|
+
|
|
1288
|
+
// ❌ UNSAFE: Never use innerHTML with unescaped user data
|
|
1289
|
+
element.innerHTML = `<span>${userName}</span>`; // XSS RISK!
|
|
1290
|
+
```
|
|
1291
|
+
|
|
1292
|
+
| Pattern | When to Use |
|
|
1293
|
+
|---------|-------------|
|
|
1294
|
+
| `escapeHTML(str)` | Dynamic values in template literals with `innerHTML` |
|
|
1295
|
+
| `textContent` | Displaying plain text (no HTML needed) |
|
|
1296
|
+
| `createElement` | Creating elements with user-provided attributes |
|
|
1297
|
+
| Static templates | When content is hardcoded (safe) |
|
|
1298
|
+
|
|
1299
|
+
**Components using these patterns:** `mu-datatable`, `mu-api-table`, `mu-avatar`, `mu-fetch`
|
|
1300
|
+
|
|
1301
|
+
---
|
|
1302
|
+
|
|
1303
|
+
## File Structure
|
|
1304
|
+
```
|
|
1305
|
+
dist/
|
|
1306
|
+
├── microui.esm.js # Full ESM bundle (230 KB)
|
|
1307
|
+
├── microui.min.js # Full IIFE bundle (231 KB)
|
|
1308
|
+
├── microui.css # Combined CSS (64 KB)
|
|
1309
|
+
├── microui.d.ts # TypeScript definitions
|
|
1310
|
+
├── routes/ # v3.5 Route Bundles (SOTA)
|
|
1311
|
+
│ ├── shell.js # App Shell (Navbar, Theme, Bus) - 5 KB
|
|
1312
|
+
│ ├── home.js # Home route components
|
|
1313
|
+
│ ├── chunk-*.js # Shared dependencies (deduplicated)
|
|
1314
|
+
│ ├── route-deps.json # Auto-generated page dependencies
|
|
1315
|
+
│ └── page-components.json # Debug: components per page
|
|
1316
|
+
└── AGENTS.md # This file
|
|
1317
|
+
```
|
|
1318
|
+
|
|
1319
|
+
---
|
|
1320
|
+
|
|
1321
|
+
## Automatic Bundle Dependencies (v3.5)
|
|
1322
|
+
|
|
1323
|
+
> **Build-time static analysis** detects which components each page uses.
|
|
1324
|
+
|
|
1325
|
+
The build process scans HTML for `<mu-*>` tags and automatically:
|
|
1326
|
+
1. **Generates `route-deps.json`** - Maps pages to their required route bundles
|
|
1327
|
+
2. **Generates `page-components.json`** - Lists all mu-* components found per page
|
|
1328
|
+
|
|
1329
|
+
### How It Works
|
|
1330
|
+
|
|
1331
|
+
```bash
|
|
1332
|
+
bun run build:framework
|
|
1333
|
+
# Output:
|
|
1334
|
+
# ✅ route-deps.json (19 pages with dependencies)
|
|
1335
|
+
# ✅ page-components.json (20 pages scanned)
|
|
1336
|
+
```
|
|
1337
|
+
|
|
1338
|
+
**Generated `dist/routes/route-deps.json`:**
|
|
1339
|
+
```json
|
|
1340
|
+
{
|
|
1341
|
+
"enterprise": ["alerts", "buttons", "cards", "tabs"],
|
|
1342
|
+
"buttons": ["cards", "layout", "tabs"],
|
|
1343
|
+
"home": ["cards", "icons", "layout"]
|
|
1344
|
+
}
|
|
1345
|
+
```
|
|
1346
|
+
|
|
1347
|
+
**Runtime:** The demo loads this JSON and auto-fetches required routes before rendering:
|
|
1348
|
+
```javascript
|
|
1349
|
+
// Automatic dependency loading
|
|
1350
|
+
const routeDeps = await fetch('dist/routes/route-deps.json').then(r => r.json());
|
|
1351
|
+
async function loadRouteWithDeps(name) {
|
|
1352
|
+
await Promise.all((routeDeps[name] || []).map(dep => loadRoute(dep)));
|
|
1353
|
+
await loadRoute(name);
|
|
1354
|
+
}
|
|
1355
|
+
```
|
|
1356
|
+
|
|
1357
|
+
### Benefits
|
|
1358
|
+
|
|
1359
|
+
| Feature | Before (Manual) | After (Automatic) |
|
|
1360
|
+
|---------|----------------|-------------------|
|
|
1361
|
+
| Maintenance | Edit `routeDependencies` map | Zero - auto-detected |
|
|
1362
|
+
| New pages | Must update build config | Just add HTML |
|
|
1363
|
+
| Debugging | Guess missing deps | Check `page-components.json` |
|
|
1364
|
+
|
|
1365
|
+
### Build Utils API
|
|
1366
|
+
|
|
1367
|
+
```javascript
|
|
1368
|
+
import { scanPageComponents, componentsToRoutes } from './scripts/build-utils.js';
|
|
1369
|
+
|
|
1370
|
+
// Extract mu-* tags from HTML
|
|
1371
|
+
const components = scanPageComponents(htmlContent);
|
|
1372
|
+
// { "home": ["mu-card", "mu-icon", "mu-stack"], "buttons": [...] }
|
|
1373
|
+
|
|
1374
|
+
// Map components to route bundles
|
|
1375
|
+
const routes = componentsToRoutes(["mu-tabs", "mu-button"]);
|
|
1376
|
+
// [\"buttons\", \"tabs\"]
|
|
1377
|
+
```
|
|
1378
|
+
|
|
1379
|
+
### Auto-Versioning (v3.5.2)
|
|
1380
|
+
|
|
1381
|
+
The build script automatically increments the version cache buster in `demo/shell.html`:
|
|
1382
|
+
|
|
1383
|
+
```bash
|
|
1384
|
+
bun run build:framework
|
|
1385
|
+
# Output:
|
|
1386
|
+
# 🔄 Updating version cache buster...
|
|
1387
|
+
# ✅ Version updated to 2.0.23
|
|
1388
|
+
```
|
|
1389
|
+
|
|
1390
|
+
**How it works:**
|
|
1391
|
+
- Extracts current version from `?v=X.X.X` pattern
|
|
1392
|
+
- Increments patch version (e.g., `2.0.21` → `2.0.22`)
|
|
1393
|
+
- Replaces all occurrences in demo/shell.html
|
|
1394
|
+
|
|
1395
|
+
**Benefits:**
|
|
1396
|
+
- No more stale cache issues after component updates
|
|
1397
|
+
- Zero manual version management needed
|
|
1398
|
+
- Browser automatically downloads updated bundles
|
|
1399
|
+
|
|
1400
|
+
|
|
1401
|
+
---
|
|
1402
|
+
|
|
1403
|
+
## App Shell Architecture (v3.2)
|
|
1404
|
+
|
|
1405
|
+
For SOTA performance, use the **App Shell** pattern. Load the critical shell first, then lazily load routes.
|
|
1406
|
+
|
|
1407
|
+
```html
|
|
1408
|
+
<head>
|
|
1409
|
+
<link rel="stylesheet" href="dist/microui.css">
|
|
1410
|
+
</head>
|
|
1411
|
+
<body>
|
|
1412
|
+
<!-- Global Shell Components -->
|
|
1413
|
+
<mu-navbar>...</mu-navbar>
|
|
1414
|
+
<mu-toast-container></mu-toast-container>
|
|
1415
|
+
|
|
1416
|
+
<!-- App Router -->
|
|
1417
|
+
<mu-router base="/app-dist/pages" default="home"></mu-router>
|
|
1418
|
+
|
|
1419
|
+
<!-- Loader Script -->
|
|
1420
|
+
<script type="module">
|
|
1421
|
+
// 1. Load Shell (Navbar, Theme, Signals, Toasts)
|
|
1422
|
+
await import('./dist/routes/shell.js');
|
|
1423
|
+
|
|
1424
|
+
// 2. Initialize Router (which loads pages + their bundles)
|
|
1425
|
+
const router = document.querySelector('mu-router');
|
|
1426
|
+
|
|
1427
|
+
// Helper to manually load a route bundle if needed
|
|
1428
|
+
window.loadRoute = async (name) => {
|
|
1429
|
+
await import(`./dist/routes/${name}.js`);
|
|
1430
|
+
};
|
|
1431
|
+
</script>
|
|
1432
|
+
</body>
|
|
1433
|
+
```
|
|
1434
|
+
|
|
1435
|
+
| Bundle | Content | Size (gzip) |
|
|
1436
|
+
|--------|---------|-------------|
|
|
1437
|
+
| `shell.js` | Navbar, Theme, CrossBus, Toasts | ~5 KB |
|
|
1438
|
+
| `home.js` | Landing page components | ~2 KB |
|
|
1439
|
+
| `forms.js` | (Example) Input, Button, Checkbox | ~7 KB |
|
|
1440
|
+
|
|
1441
|
+
**Why this is SOTA:**
|
|
1442
|
+
- The user only downloads `shell.js` (small) to see the UI.
|
|
1443
|
+
- Page bundles (`home.js`) are only downloaded when navigating to that page.
|
|
1444
|
+
- Common code (CrossBus, BaseClass) is shared via `chunk-*.js` files automatically.
|
|
1445
|
+
|
|
1446
|
+
---
|
|
1447
|
+
|
|
1448
|
+
## Performance Metrics
|
|
1449
|
+
- **Bundle size:** 230 KB full / 8 KB route-based (gzipped: 75 KB / 3 KB)
|
|
1450
|
+
- **Lighthouse:** 100% Performance, 100% Accessibility, 100% SEO, 100% Best Practices
|
|
1451
|
+
- **EventBus:** 111M ops/sec (CrossBus emitSync)
|
|
1452
|
+
- **Virtual List:** Handles 10K+ items at 60fps
|
|
1453
|
+
- **First paint:** <100ms (precached assets)
|
|
1454
|
+
|
|
1455
|
+
---
|
|
1456
|
+
|
|
1457
|
+
## Performance Patterns (SOTA)
|
|
1458
|
+
|
|
1459
|
+
> **Critical patterns for 100% Lighthouse scores and excellent UX.**
|
|
1460
|
+
|
|
1461
|
+
### 1. Skeleton Loading (Perceived Performance)
|
|
1462
|
+
|
|
1463
|
+
Use `mu-skeleton` to show content placeholders while loading:
|
|
1464
|
+
|
|
1465
|
+
```html
|
|
1466
|
+
<!-- Before data loads -->
|
|
1467
|
+
<mu-card>
|
|
1468
|
+
<mu-skeleton type="text" width="60%"></mu-skeleton>
|
|
1469
|
+
<mu-skeleton type="text" lines="3"></mu-skeleton>
|
|
1470
|
+
<mu-skeleton type="button"></mu-skeleton>
|
|
1471
|
+
</mu-card>
|
|
1472
|
+
|
|
1473
|
+
<!-- Replace with real content when ready -->
|
|
1474
|
+
<mu-card>
|
|
1475
|
+
<h3>User Profile</h3>
|
|
1476
|
+
<p>Actual content...</p>
|
|
1477
|
+
<mu-button>Edit</mu-button>
|
|
1478
|
+
</mu-card>
|
|
1479
|
+
```
|
|
1480
|
+
|
|
1481
|
+
| Attribute | Values | Default |
|
|
1482
|
+
|-----------|--------|---------|
|
|
1483
|
+
| `type` | `text`, `avatar`, `button`, `image` | `text` |
|
|
1484
|
+
| `width` | CSS width | `100%` |
|
|
1485
|
+
| `lines` | Number of text lines | `1` |
|
|
1486
|
+
| `animated` | boolean | `true` |
|
|
1487
|
+
|
|
1488
|
+
### 2. Lazy Loading (Below-the-fold)
|
|
1489
|
+
|
|
1490
|
+
Use `mu-lazy` to defer loading of non-critical content:
|
|
1491
|
+
|
|
1492
|
+
```html
|
|
1493
|
+
<!-- Content loads only when scrolled into view -->
|
|
1494
|
+
<mu-lazy threshold="200px">
|
|
1495
|
+
<mu-card>
|
|
1496
|
+
<img src="heavy-image.webp">
|
|
1497
|
+
<p>This loads lazily</p>
|
|
1498
|
+
</mu-card>
|
|
1499
|
+
</mu-lazy>
|
|
1500
|
+
|
|
1501
|
+
<!-- With loading placeholder -->
|
|
1502
|
+
<mu-lazy placeholder="<mu-skeleton type='image'></mu-skeleton>">
|
|
1503
|
+
<img src="hero.webp" alt="Hero">
|
|
1504
|
+
</mu-lazy>
|
|
1505
|
+
```
|
|
1506
|
+
|
|
1507
|
+
| Attribute | Description | Default |
|
|
1508
|
+
|-----------|-------------|---------|
|
|
1509
|
+
| `threshold` | Distance from viewport to trigger load | `100px` |
|
|
1510
|
+
| `placeholder` | HTML to show while loading | (empty) |
|
|
1511
|
+
|
|
1512
|
+
### 3. Resource Hints (Preload Critical Assets)
|
|
1513
|
+
|
|
1514
|
+
Add to `<head>` for faster loading:
|
|
1515
|
+
|
|
1516
|
+
```html
|
|
1517
|
+
<head>
|
|
1518
|
+
<!-- Preconnect to external origins (fonts, CDN) -->
|
|
1519
|
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
1520
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
1521
|
+
|
|
1522
|
+
<!-- Preload critical assets (above-the-fold) -->
|
|
1523
|
+
<link rel="preload" href="/dist/microui.css" as="style">
|
|
1524
|
+
<link rel="preload" href="/dist/routes/shell.js" as="script" crossorigin>
|
|
1525
|
+
<link rel="preload" href="/assets/logo.webp" as="image" type="image/webp">
|
|
1526
|
+
|
|
1527
|
+
<!-- Prefetch likely next pages (after current page loads) -->
|
|
1528
|
+
<link rel="prefetch" href="/dist/routes/home.js">
|
|
1529
|
+
</head>
|
|
1530
|
+
```
|
|
1531
|
+
|
|
1532
|
+
| Hint | When to Use | Priority |
|
|
1533
|
+
|------|-------------|----------|
|
|
1534
|
+
| `preconnect` | External domains you'll fetch from | 🔴 High |
|
|
1535
|
+
| `preload` | Critical above-the-fold assets | 🔴 High |
|
|
1536
|
+
| `prefetch` | Next likely navigation | 🟢 Low |
|
|
1537
|
+
| `dns-prefetch` | External domains (fallback) | 🟢 Low |
|
|
1538
|
+
|
|
1539
|
+
### 4. Core Web Vitals Optimization
|
|
1540
|
+
|
|
1541
|
+
| Metric | Target | How to Achieve |
|
|
1542
|
+
|--------|--------|----------------|
|
|
1543
|
+
| **LCP** (Largest Contentful Paint) | < 2.5s | Preload hero image, inline critical CSS |
|
|
1544
|
+
| **FID** (First Input Delay) | < 100ms | Code split, defer non-critical JS |
|
|
1545
|
+
| **CLS** (Cumulative Layout Shift) | < 0.1 | Set dimensions on images, reserve space |
|
|
1546
|
+
|
|
1547
|
+
```html
|
|
1548
|
+
<!-- LCP: Preload hero image with fetchpriority -->
|
|
1549
|
+
<link rel="preload" as="image" href="/hero.webp" fetchpriority="high">
|
|
1550
|
+
|
|
1551
|
+
<!-- CLS: Always set dimensions -->
|
|
1552
|
+
<img src="photo.webp" width="800" height="600" alt="...">
|
|
1553
|
+
|
|
1554
|
+
<!-- CLS: Reserve space for dynamic content -->
|
|
1555
|
+
<div style="min-height: 300px;">
|
|
1556
|
+
<mu-lazy><mu-card>...</mu-card></mu-lazy>
|
|
1557
|
+
</div>
|
|
1558
|
+
```
|
|
1559
|
+
|
|
1560
|
+
### 5. View Transitions (Smooth Navigation)
|
|
1561
|
+
|
|
1562
|
+
Use the View Transitions API for cinematic page changes:
|
|
1563
|
+
|
|
1564
|
+
```javascript
|
|
1565
|
+
import { transition, transitionNamed } from 'microui';
|
|
1566
|
+
|
|
1567
|
+
// Basic transition
|
|
1568
|
+
await transition(() => {
|
|
1569
|
+
document.getElementById('content').innerHTML = newHTML;
|
|
1570
|
+
});
|
|
1571
|
+
|
|
1572
|
+
// Named transition with custom animation
|
|
1573
|
+
await transitionNamed(() => {
|
|
1574
|
+
router.navigate('/users');
|
|
1575
|
+
}, { name: 'page-slide' });
|
|
1576
|
+
```
|
|
1577
|
+
|
|
1578
|
+
```css
|
|
1579
|
+
/* Custom transition animation */
|
|
1580
|
+
::view-transition-old(page-slide) {
|
|
1581
|
+
animation: slide-out 0.3s ease-out;
|
|
1582
|
+
}
|
|
1583
|
+
::view-transition-new(page-slide) {
|
|
1584
|
+
animation: slide-in 0.3s ease-out;
|
|
1585
|
+
}
|
|
1586
|
+
```
|
|
1587
|
+
|
|
1588
|
+
### 6. Virtual List (Large Datasets)
|
|
1589
|
+
|
|
1590
|
+
For lists with 100+ items, use `mu-virtual-list`:
|
|
1591
|
+
|
|
1592
|
+
```javascript
|
|
1593
|
+
const list = document.querySelector('mu-virtual-list');
|
|
1594
|
+
list.items = largeArray; // 10K+ items OK
|
|
1595
|
+
list.itemHeight = 48; // Fixed height for calculation
|
|
1596
|
+
list.renderItem = (item, index) => `
|
|
1597
|
+
<div class="list-item">
|
|
1598
|
+
<span>${item.name}</span>
|
|
1599
|
+
<span>${item.email}</span>
|
|
1600
|
+
</div>
|
|
1601
|
+
`;
|
|
1602
|
+
```
|
|
1603
|
+
|
|
1604
|
+
### 7. Speculative Route Preloading
|
|
1605
|
+
|
|
1606
|
+
Preload routes on hover for instant navigation:
|
|
1607
|
+
|
|
1608
|
+
```javascript
|
|
1609
|
+
// Add to navigation links
|
|
1610
|
+
document.querySelectorAll('a[data-route]').forEach(link => {
|
|
1611
|
+
link.addEventListener('mouseenter', () => {
|
|
1612
|
+
const route = link.dataset.route;
|
|
1613
|
+
// Prefetch route bundle
|
|
1614
|
+
import(`/dist/routes/${route}.js`);
|
|
1615
|
+
});
|
|
1616
|
+
});
|
|
1617
|
+
```
|
|
1618
|
+
|
|
1619
|
+
---
|
|
1620
|
+
|
|
1621
|
+
## Error Handling Patterns
|
|
1622
|
+
|
|
1623
|
+
### 1. Runtime Error Collection
|
|
1624
|
+
|
|
1625
|
+
microUI logs all component errors to `window.__MICROUI_ERRORS__`:
|
|
1626
|
+
|
|
1627
|
+
```javascript
|
|
1628
|
+
// In E2E tests or debug console
|
|
1629
|
+
const errors = window.__MICROUI_ERRORS__ || [];
|
|
1630
|
+
errors.forEach(err => {
|
|
1631
|
+
console.error(`[${err.component}] ${err.message}`, err.details);
|
|
1632
|
+
});
|
|
1633
|
+
```
|
|
1634
|
+
|
|
1635
|
+
### 2. Async Error Handling
|
|
1636
|
+
|
|
1637
|
+
Wrap async operations with proper error handling:
|
|
1638
|
+
|
|
1639
|
+
```javascript
|
|
1640
|
+
async function loadUserData() {
|
|
1641
|
+
try {
|
|
1642
|
+
const response = await fetch('/api/users');
|
|
1643
|
+
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
|
1644
|
+
return await response.json();
|
|
1645
|
+
} catch (error) {
|
|
1646
|
+
// Show user-friendly error
|
|
1647
|
+
showToast('Failed to load data. Please retry.', { variant: 'error' });
|
|
1648
|
+
// Log for debugging
|
|
1649
|
+
console.error('loadUserData failed:', error);
|
|
1650
|
+
return null;
|
|
1651
|
+
}
|
|
1652
|
+
}
|
|
1653
|
+
```
|
|
1654
|
+
|
|
1655
|
+
### 3. Error Boundary Pattern
|
|
1656
|
+
|
|
1657
|
+
Create a wrapper to catch render errors:
|
|
1658
|
+
|
|
1659
|
+
```javascript
|
|
1660
|
+
function safeRender(container, renderFn) {
|
|
1661
|
+
try {
|
|
1662
|
+
container.innerHTML = renderFn();
|
|
1663
|
+
} catch (error) {
|
|
1664
|
+
container.innerHTML = `
|
|
1665
|
+
<mu-alert variant="error">
|
|
1666
|
+
Something went wrong. Please refresh the page.
|
|
1667
|
+
</mu-alert>
|
|
1668
|
+
`;
|
|
1669
|
+
console.error('Render error:', error);
|
|
1670
|
+
}
|
|
1671
|
+
}
|
|
1672
|
+
|
|
1673
|
+
// Usage
|
|
1674
|
+
safeRender(document.getElementById('content'), () => {
|
|
1675
|
+
return `<mu-card>${userData.name}</mu-card>`;
|
|
1676
|
+
});
|
|
1677
|
+
```
|
|
1678
|
+
|
|
1679
|
+
### 4. Network Error Handling
|
|
1680
|
+
|
|
1681
|
+
```javascript
|
|
1682
|
+
// Fetch with timeout and retry
|
|
1683
|
+
async function fetchWithRetry(url, options = {}, retries = 3) {
|
|
1684
|
+
for (let i = 0; i < retries; i++) {
|
|
1685
|
+
try {
|
|
1686
|
+
const controller = new AbortController();
|
|
1687
|
+
const timeout = setTimeout(() => controller.abort(), 5000);
|
|
1688
|
+
|
|
1689
|
+
const response = await fetch(url, {
|
|
1690
|
+
...options,
|
|
1691
|
+
signal: controller.signal
|
|
1692
|
+
});
|
|
1693
|
+
|
|
1694
|
+
clearTimeout(timeout);
|
|
1695
|
+
if (response.ok) return response;
|
|
1696
|
+
} catch (error) {
|
|
1697
|
+
if (i === retries - 1) throw error;
|
|
1698
|
+
await new Promise(r => setTimeout(r, 1000 * (i + 1)));
|
|
1699
|
+
}
|
|
1700
|
+
}
|
|
1701
|
+
}
|
|
1702
|
+
```
|
|
1703
|
+
|
|
1704
|
+
### 5. Agent Debugging Tips
|
|
1705
|
+
|
|
1706
|
+
```javascript
|
|
1707
|
+
// Enable verbose logging
|
|
1708
|
+
window.__MICROUI_DEBUG__ = true;
|
|
1709
|
+
|
|
1710
|
+
// Check component registration
|
|
1711
|
+
customElements.get('mu-button'); // Returns class or undefined
|
|
1712
|
+
|
|
1713
|
+
// Inspect component state
|
|
1714
|
+
const btn = document.querySelector('mu-button');
|
|
1715
|
+
console.log(btn.getAttribute('variant'), btn.disabled);
|
|
1716
|
+
|
|
1717
|
+
// Force component re-render
|
|
1718
|
+
btn.render?.();
|
|
1719
|
+
```
|
|
1720
|
+
|
|
1721
|
+
---
|
|
1722
|
+
|
|
1723
|
+
## Page-Based App Development (v3.2)
|
|
1724
|
+
|
|
1725
|
+
> **For AI Agents building apps with microUI.** This is the recommended architecture.
|
|
1726
|
+
|
|
1727
|
+
### Creating a New Page
|
|
1728
|
+
|
|
1729
|
+
Create a single file in `app/pages/`:
|
|
1730
|
+
|
|
1731
|
+
```html
|
|
1732
|
+
<!-- app/pages/users.html -->
|
|
1733
|
+
<mu-page route="users" title="Users">
|
|
1734
|
+
<script type="x-dependencies">
|
|
1735
|
+
mu-table
|
|
1736
|
+
mu-button
|
|
1737
|
+
mu-stack
|
|
1738
|
+
mu-tabs
|
|
1739
|
+
</script>
|
|
1740
|
+
|
|
1741
|
+
<template>
|
|
1742
|
+
<h1 class="page-title">Users</h1>
|
|
1743
|
+
<mu-table id="users-table"></mu-table>
|
|
1744
|
+
<mu-button onclick="addUser()">Add User</mu-button>
|
|
1745
|
+
</template>
|
|
1746
|
+
</mu-page>
|
|
1747
|
+
```
|
|
1748
|
+
|
|
1749
|
+
### Build & Run
|
|
1750
|
+
|
|
1751
|
+
```bash
|
|
1752
|
+
# Build everything (framework + app)
|
|
1753
|
+
bun run build
|
|
1754
|
+
|
|
1755
|
+
# Or separately:
|
|
1756
|
+
bun run build:framework # → dist/
|
|
1757
|
+
bun run build:app # → app-dist/
|
|
1758
|
+
|
|
1759
|
+
# Serve
|
|
1760
|
+
npx serve app-dist
|
|
1761
|
+
```
|
|
1762
|
+
|
|
1763
|
+
### File Structure
|
|
1764
|
+
|
|
1765
|
+
```
|
|
1766
|
+
microUI/
|
|
1767
|
+
├── src/ # Framework source
|
|
1768
|
+
│ └── components/ # 49 components
|
|
1769
|
+
│
|
|
1770
|
+
├── dist/ # Framework build
|
|
1771
|
+
│ ├── microui.esm.js # Full bundle
|
|
1772
|
+
│ ├── microui.css # CSS
|
|
1773
|
+
│ └── components/ # Per-component builds
|
|
1774
|
+
│
|
|
1775
|
+
├── app/ # App source
|
|
1776
|
+
│ ├── pages/ # One file per page
|
|
1777
|
+
│ │ ├── home.html
|
|
1778
|
+
│ │ ├── users.html
|
|
1779
|
+
│ │ └── settings.html
|
|
1780
|
+
│ └── shell.html # App shell
|
|
1781
|
+
│
|
|
1782
|
+
└── app-dist/ # App build (auto-generated)
|
|
1783
|
+
├── routes/ # Auto-bundled from x-dependencies
|
|
1784
|
+
├── pages/ # Copied HTML pages
|
|
1785
|
+
└── pages.json # Manifest
|
|
1786
|
+
```
|
|
1787
|
+
|
|
1788
|
+
### How It Works
|
|
1789
|
+
|
|
1790
|
+
1. **Agent creates** `app/pages/mypage.html` with `<mu-page>` component
|
|
1791
|
+
2. **Build extracts** dependencies from `<script type="x-dependencies">`
|
|
1792
|
+
3. **Build generates** route bundle in `app-dist/routes/mypage.js`
|
|
1793
|
+
4. **Router lazy-loads** page HTML and route bundle on navigation
|
|
1794
|
+
|
|
1795
|
+
### mu-router
|
|
1796
|
+
|
|
1797
|
+
```html
|
|
1798
|
+
<mu-router base="/app-dist/pages" default="home"></mu-router>
|
|
1799
|
+
```
|
|
1800
|
+
|
|
1801
|
+
| Attribute | Description | Default |
|
|
1802
|
+
|-----------|-------------|---------|
|
|
1803
|
+
| `base` | Path to pages directory | `/app/pages` |
|
|
1804
|
+
| `default` | Default route when no hash | `home` |
|
|
1805
|
+
|
|
1806
|
+
**Events:** `mu-route-change`, `mu-page-loaded`, `mu-page-error`
|
|
1807
|
+
|
|
1808
|
+
**Methods:** `.navigate(route)`
|
|
1809
|
+
|
|
1810
|
+
### mu-page
|
|
1811
|
+
|
|
1812
|
+
```html
|
|
1813
|
+
<mu-page route="mypage" title="My Page">
|
|
1814
|
+
<script type="x-dependencies">mu-button, mu-card</script>
|
|
1815
|
+
<template><!-- content --></template>
|
|
1816
|
+
</mu-page>
|
|
1817
|
+
```
|
|
1818
|
+
|
|
1819
|
+
| Attribute | Description |
|
|
1820
|
+
|-----------|-------------|
|
|
1821
|
+
| `route` | Route name (used in URL hash) |
|
|
1822
|
+
| `title` | Page title (set on navigation) |
|
|
1823
|
+
|
|
1824
|
+
---
|
|
1825
|
+
|
|
1826
|
+
## Agent Workflow: Adding a Page
|
|
1827
|
+
|
|
1828
|
+
```bash
|
|
1829
|
+
# 1. Create page file
|
|
1830
|
+
cat > app/pages/dashboard.html << 'EOF'
|
|
1831
|
+
<mu-page route="dashboard" title="Dashboard">
|
|
1832
|
+
<script type="x-dependencies">
|
|
1833
|
+
mu-card
|
|
1834
|
+
mu-stack
|
|
1835
|
+
mu-grid
|
|
1836
|
+
</script>
|
|
1837
|
+
<template>
|
|
1838
|
+
<h1 class="page-title">Dashboard</h1>
|
|
1839
|
+
<mu-grid cols="3" gap="md">
|
|
1840
|
+
<mu-card variant="elevated">Widget 1</mu-card>
|
|
1841
|
+
<mu-card variant="elevated">Widget 2</mu-card>
|
|
1842
|
+
<mu-card variant="elevated">Widget 3</mu-card>
|
|
1843
|
+
</mu-grid>
|
|
1844
|
+
</template>
|
|
1845
|
+
</mu-page>
|
|
1846
|
+
EOF
|
|
1847
|
+
|
|
1848
|
+
# 2. Build
|
|
1849
|
+
bun run build:app
|
|
1850
|
+
|
|
1851
|
+
# 3. Navigate to: http://localhost:5001/app-dist/#dashboard
|
|
1852
|
+
```
|
|
1853
|
+
|
|
1854
|
+
---
|
|
1855
|
+
|
|
1856
|
+
## Agent Workflow: Adding a Component
|
|
1857
|
+
|
|
1858
|
+
```bash
|
|
1859
|
+
# 1. Create component file
|
|
1860
|
+
cat > src/components/mu-counter.js << 'EOF'
|
|
1861
|
+
import { MuElement, define } from '../core/MuElement.js';
|
|
1862
|
+
|
|
1863
|
+
export class MuCounter extends MuElement {
|
|
1864
|
+
static baseClass = 'mu-counter';
|
|
1865
|
+
static observedAttributes = ['value'];
|
|
1866
|
+
|
|
1867
|
+
render() {
|
|
1868
|
+
const value = parseInt(this.attr('value', '0'));
|
|
1869
|
+
this.innerHTML = `
|
|
1870
|
+
<button class="mu-counter__dec">-</button>
|
|
1871
|
+
<span class="mu-counter__value">${value}</span>
|
|
1872
|
+
<button class="mu-counter__inc">+</button>
|
|
1873
|
+
`;
|
|
1874
|
+
this.#setupEvents();
|
|
1875
|
+
}
|
|
1876
|
+
|
|
1877
|
+
#setupEvents() {
|
|
1878
|
+
this.querySelector('.mu-counter__dec')
|
|
1879
|
+
.addEventListener('click', () => this.#change(-1));
|
|
1880
|
+
this.querySelector('.mu-counter__inc')
|
|
1881
|
+
.addEventListener('click', () => this.#change(1));
|
|
1882
|
+
}
|
|
1883
|
+
|
|
1884
|
+
#change(delta) {
|
|
1885
|
+
const current = parseInt(this.attr('value', '0'));
|
|
1886
|
+
this.setAttribute('value', current + delta);
|
|
1887
|
+
this.emit('mu-change', { value: current + delta });
|
|
1888
|
+
}
|
|
1889
|
+
}
|
|
1890
|
+
|
|
1891
|
+
define('mu-counter', MuCounter);
|
|
1892
|
+
EOF
|
|
1893
|
+
|
|
1894
|
+
# 2. Add to index.js exports (if needed)
|
|
1895
|
+
# 3. Build framework
|
|
1896
|
+
bun run build:framework
|
|
1897
|
+
```
|
|
1898
|
+
|
|
1899
|
+
---
|
|
1900
|
+
|
|
1901
|
+
## State Management for Complex Apps
|
|
1902
|
+
|
|
1903
|
+
> **For apps with shared state across components and pages.**
|
|
1904
|
+
|
|
1905
|
+
### Global Store Pattern
|
|
1906
|
+
```javascript
|
|
1907
|
+
// store/index.js - Centralized reactive state
|
|
1908
|
+
import { signal, computed, effect } from 'microui';
|
|
1909
|
+
|
|
1910
|
+
export const store = {
|
|
1911
|
+
// Core state
|
|
1912
|
+
user: signal(null, 'user'),
|
|
1913
|
+
items: signal([], 'items'),
|
|
1914
|
+
loading: signal(false, 'loading'),
|
|
1915
|
+
|
|
1916
|
+
// Computed values (auto-update when dependencies change)
|
|
1917
|
+
itemCount: computed(() => store.items().length),
|
|
1918
|
+
isAuthenticated: computed(() => store.user() !== null),
|
|
1919
|
+
|
|
1920
|
+
// Actions
|
|
1921
|
+
async fetchItems() {
|
|
1922
|
+
store.loading.set(true);
|
|
1923
|
+
try {
|
|
1924
|
+
const data = await fetch('/api/items').then(r => r.json());
|
|
1925
|
+
store.items.set(data);
|
|
1926
|
+
} finally {
|
|
1927
|
+
store.loading.set(false);
|
|
1928
|
+
}
|
|
1929
|
+
},
|
|
1930
|
+
|
|
1931
|
+
login(userData) {
|
|
1932
|
+
store.user.set(userData);
|
|
1933
|
+
},
|
|
1934
|
+
|
|
1935
|
+
logout() {
|
|
1936
|
+
store.user.set(null);
|
|
1937
|
+
}
|
|
1938
|
+
};
|
|
1939
|
+
|
|
1940
|
+
// Auto-persist to localStorage
|
|
1941
|
+
effect(() => {
|
|
1942
|
+
const user = store.user();
|
|
1943
|
+
if (user) localStorage.setItem('user', JSON.stringify(user));
|
|
1944
|
+
else localStorage.removeItem('user');
|
|
1945
|
+
});
|
|
1946
|
+
|
|
1947
|
+
// Restore on load
|
|
1948
|
+
const savedUser = localStorage.getItem('user');
|
|
1949
|
+
if (savedUser) store.user.set(JSON.parse(savedUser));
|
|
1950
|
+
```
|
|
1951
|
+
|
|
1952
|
+
### Binding State to Components
|
|
1953
|
+
```javascript
|
|
1954
|
+
import { store } from './store/index.js';
|
|
1955
|
+
import { effect } from 'microui';
|
|
1956
|
+
|
|
1957
|
+
// Auto-update UI when state changes
|
|
1958
|
+
effect(() => {
|
|
1959
|
+
const user = store.user();
|
|
1960
|
+
document.querySelector('#user-name').textContent = user?.name || 'Guest';
|
|
1961
|
+
document.querySelector('#login-btn').hidden = !!user;
|
|
1962
|
+
document.querySelector('#logout-btn').hidden = !user;
|
|
1963
|
+
});
|
|
1964
|
+
|
|
1965
|
+
// Show loading state
|
|
1966
|
+
effect(() => {
|
|
1967
|
+
document.querySelector('#spinner').hidden = !store.loading();
|
|
1968
|
+
});
|
|
1969
|
+
```
|
|
1970
|
+
|
|
1971
|
+
### State in Custom Components
|
|
1972
|
+
```javascript
|
|
1973
|
+
export class MuUserCard extends MuElement {
|
|
1974
|
+
connectedCallback() {
|
|
1975
|
+
super.connectedCallback();
|
|
1976
|
+
// Re-render when user changes
|
|
1977
|
+
this.#unsubscribe = effect(() => {
|
|
1978
|
+
this.user = store.user();
|
|
1979
|
+
this.render();
|
|
1980
|
+
});
|
|
1981
|
+
}
|
|
1982
|
+
|
|
1983
|
+
disconnectedCallback() {
|
|
1984
|
+
this.#unsubscribe?.();
|
|
1985
|
+
}
|
|
1986
|
+
}
|
|
1987
|
+
```
|
|
1988
|
+
|
|
1989
|
+
---
|
|
1990
|
+
|
|
1991
|
+
## Data-Driven Components
|
|
1992
|
+
|
|
1993
|
+
> **Components that render dynamic data from arrays/objects.**
|
|
1994
|
+
|
|
1995
|
+
### mu-repeat (Declarative Lists)
|
|
1996
|
+
```html
|
|
1997
|
+
<mu-repeat id="user-list"></mu-repeat>
|
|
1998
|
+
|
|
1999
|
+
<script type="module">
|
|
2000
|
+
const list = document.getElementById('user-list');
|
|
2001
|
+
|
|
2002
|
+
// Set data and render template
|
|
2003
|
+
list.items = await fetch('/api/users').then(r => r.json());
|
|
2004
|
+
list.renderItem = (user, index) => `
|
|
2005
|
+
<mu-card variant="outlined">
|
|
2006
|
+
<mu-stack direction="row" gap="md" align="center">
|
|
2007
|
+
<mu-avatar initials="${user.name[0]}" size="md"></mu-avatar>
|
|
2008
|
+
<mu-stack gap="xs">
|
|
2009
|
+
<strong>${user.name}</strong>
|
|
2010
|
+
<small>${user.email}</small>
|
|
2011
|
+
</mu-stack>
|
|
2012
|
+
<mu-button variant="text" data-id="${user.id}">Edit</mu-button>
|
|
2013
|
+
</mu-stack>
|
|
2014
|
+
</mu-card>
|
|
2015
|
+
`;
|
|
2016
|
+
|
|
2017
|
+
// Listen for item events
|
|
2018
|
+
list.addEventListener('click', (e) => {
|
|
2019
|
+
const btn = e.target.closest('[data-id]');
|
|
2020
|
+
if (btn) console.log('Edit user:', btn.dataset.id);
|
|
2021
|
+
});
|
|
2022
|
+
</script>
|
|
2023
|
+
```
|
|
2024
|
+
|
|
2025
|
+
### mu-table (Data Tables with Sorting)
|
|
2026
|
+
```html
|
|
2027
|
+
<mu-table id="data-table"></mu-table>
|
|
2028
|
+
|
|
2029
|
+
<script type="module">
|
|
2030
|
+
const table = document.getElementById('data-table');
|
|
2031
|
+
|
|
2032
|
+
// Configure columns
|
|
2033
|
+
table.columns = [
|
|
2034
|
+
{ key: 'name', label: 'Name', sortable: true },
|
|
2035
|
+
{ key: 'email', label: 'Email', sortable: true },
|
|
2036
|
+
{ key: 'role', label: 'Role', sortable: true },
|
|
2037
|
+
{ key: 'actions', label: '', render: (row) => `
|
|
2038
|
+
<mu-button variant="text" size="sm" data-action="edit" data-id="${row.id}">
|
|
2039
|
+
<mu-icon name="edit"></mu-icon>
|
|
2040
|
+
</mu-button>
|
|
2041
|
+
`}
|
|
2042
|
+
];
|
|
2043
|
+
|
|
2044
|
+
// Set data
|
|
2045
|
+
table.data = await fetch('/api/users').then(r => r.json());
|
|
2046
|
+
|
|
2047
|
+
// Handle actions
|
|
2048
|
+
table.addEventListener('click', (e) => {
|
|
2049
|
+
const btn = e.target.closest('[data-action]');
|
|
2050
|
+
if (btn?.dataset.action === 'edit') {
|
|
2051
|
+
console.log('Edit:', btn.dataset.id);
|
|
2052
|
+
}
|
|
2053
|
+
});
|
|
2054
|
+
</script>
|
|
2055
|
+
```
|
|
2056
|
+
|
|
2057
|
+
### mu-virtual-list (10K+ Items)
|
|
2058
|
+
```javascript
|
|
2059
|
+
const list = document.querySelector('mu-virtual-list');
|
|
2060
|
+
list.items = hugeArray; // 10,000+ items OK
|
|
2061
|
+
list.itemHeight = 56; // Fixed height for virtualization
|
|
2062
|
+
list.renderItem = (item) => `
|
|
2063
|
+
<div class="list-row">${item.name}</div>
|
|
2064
|
+
`;
|
|
2065
|
+
```
|
|
2066
|
+
|
|
2067
|
+
---
|
|
2068
|
+
|
|
2069
|
+
## Error Handling Patterns
|
|
2070
|
+
|
|
2071
|
+
> **Production-ready error handling for complex apps.**
|
|
2072
|
+
|
|
2073
|
+
### Runtime Error Detection
|
|
2074
|
+
```javascript
|
|
2075
|
+
// Check for microUI component errors
|
|
2076
|
+
const errors = window.__MICROUI_ERRORS__ || [];
|
|
2077
|
+
if (errors.length) {
|
|
2078
|
+
console.error('microUI component errors:', errors);
|
|
2079
|
+
// Report to monitoring service
|
|
2080
|
+
fetch('/api/errors', {
|
|
2081
|
+
method: 'POST',
|
|
2082
|
+
body: JSON.stringify({ errors, url: location.href })
|
|
2083
|
+
});
|
|
2084
|
+
}
|
|
2085
|
+
```
|
|
2086
|
+
|
|
2087
|
+
### Global Error Boundary
|
|
2088
|
+
```javascript
|
|
2089
|
+
// Catch all unhandled errors
|
|
2090
|
+
window.addEventListener('error', (event) => {
|
|
2091
|
+
console.error('Uncaught error:', event.error);
|
|
2092
|
+
showToast('Something went wrong', { variant: 'error' });
|
|
2093
|
+
|
|
2094
|
+
// Prevent default browser error UI
|
|
2095
|
+
event.preventDefault();
|
|
2096
|
+
});
|
|
2097
|
+
|
|
2098
|
+
// Catch unhandled promise rejections
|
|
2099
|
+
window.addEventListener('unhandledrejection', (event) => {
|
|
2100
|
+
console.error('Unhandled rejection:', event.reason);
|
|
2101
|
+
showToast('Network or async error', { variant: 'warning' });
|
|
2102
|
+
});
|
|
2103
|
+
```
|
|
2104
|
+
|
|
2105
|
+
### API Error Handling
|
|
2106
|
+
```javascript
|
|
2107
|
+
// services/api.js
|
|
2108
|
+
export async function fetchAPI(endpoint, options = {}) {
|
|
2109
|
+
try {
|
|
2110
|
+
const res = await fetch(`/api${endpoint}`, {
|
|
2111
|
+
headers: { 'Content-Type': 'application/json' },
|
|
2112
|
+
...options
|
|
2113
|
+
});
|
|
2114
|
+
|
|
2115
|
+
if (!res.ok) {
|
|
2116
|
+
const error = await res.json().catch(() => ({}));
|
|
2117
|
+
throw new Error(error.message || `HTTP ${res.status}`);
|
|
2118
|
+
}
|
|
2119
|
+
|
|
2120
|
+
return res.json();
|
|
2121
|
+
} catch (err) {
|
|
2122
|
+
showToast(err.message || 'Network error', { variant: 'error' });
|
|
2123
|
+
throw err;
|
|
2124
|
+
}
|
|
2125
|
+
}
|
|
2126
|
+
|
|
2127
|
+
// Usage
|
|
2128
|
+
import { fetchAPI } from './services/api.js';
|
|
2129
|
+
const users = await fetchAPI('/users');
|
|
2130
|
+
```
|
|
2131
|
+
|
|
2132
|
+
---
|
|
2133
|
+
|
|
2134
|
+
## Architecture for Large Apps (10+ Pages)
|
|
2135
|
+
|
|
2136
|
+
> **Recommended structure for complex multi-page applications.**
|
|
2137
|
+
|
|
2138
|
+
### Directory Structure
|
|
2139
|
+
```
|
|
2140
|
+
my-app/
|
|
2141
|
+
├── shell.html # App shell (minimal HTML)
|
|
2142
|
+
├── sw.js # Service Worker
|
|
2143
|
+
├── manifest.json # PWA manifest
|
|
2144
|
+
├── store/
|
|
2145
|
+
│ └── index.js # Global reactive state (signals)
|
|
2146
|
+
├── pages/
|
|
2147
|
+
│ ├── home.js # Lazy-loaded route modules
|
|
2148
|
+
│ ├── users.js
|
|
2149
|
+
│ ├── settings.js
|
|
2150
|
+
│ └── not-found.js
|
|
2151
|
+
├── components/
|
|
2152
|
+
│ ├── user-card.js # Reusable custom components
|
|
2153
|
+
│ ├── data-table.js
|
|
2154
|
+
│ └── nav-menu.js
|
|
2155
|
+
├── services/
|
|
2156
|
+
│ └── api.js # API client with error handling
|
|
2157
|
+
└── assets/
|
|
2158
|
+
├── logo.webp
|
|
2159
|
+
└── icons/
|
|
2160
|
+
```
|
|
2161
|
+
|
|
2162
|
+
### Route-Based Code Splitting
|
|
2163
|
+
```html
|
|
2164
|
+
<!-- shell.html - App Shell -->
|
|
2165
|
+
<body>
|
|
2166
|
+
<mu-navbar>...</mu-navbar>
|
|
2167
|
+
<main id="app"></main>
|
|
2168
|
+
<mu-toast-container></mu-toast-container>
|
|
2169
|
+
|
|
2170
|
+
<script type="module">
|
|
2171
|
+
// Route configuration
|
|
2172
|
+
const routes = {
|
|
2173
|
+
home: () => import('./pages/home.js'),
|
|
2174
|
+
users: () => import('./pages/users.js'),
|
|
2175
|
+
settings: () => import('./pages/settings.js'),
|
|
2176
|
+
default: () => import('./pages/not-found.js')
|
|
2177
|
+
};
|
|
2178
|
+
|
|
2179
|
+
async function navigate() {
|
|
2180
|
+
const hash = location.hash.slice(1) || 'home';
|
|
2181
|
+
const loader = routes[hash] || routes.default;
|
|
2182
|
+
|
|
2183
|
+
try {
|
|
2184
|
+
const module = await loader();
|
|
2185
|
+
document.getElementById('app').innerHTML = '';
|
|
2186
|
+
await module.render(document.getElementById('app'));
|
|
2187
|
+
} catch (err) {
|
|
2188
|
+
console.error('Route error:', err);
|
|
2189
|
+
document.getElementById('app').innerHTML = `
|
|
2190
|
+
<mu-alert variant="error">Failed to load page</mu-alert>
|
|
2191
|
+
`;
|
|
2192
|
+
}
|
|
2193
|
+
}
|
|
2194
|
+
|
|
2195
|
+
window.addEventListener('hashchange', navigate);
|
|
2196
|
+
navigate(); // Initial load
|
|
2197
|
+
</script>
|
|
2198
|
+
</body>
|
|
2199
|
+
```
|
|
2200
|
+
|
|
2201
|
+
### Page Module Pattern
|
|
2202
|
+
```javascript
|
|
2203
|
+
// pages/users.js
|
|
2204
|
+
import { store } from '../store/index.js';
|
|
2205
|
+
import { fetchAPI } from '../services/api.js';
|
|
2206
|
+
|
|
2207
|
+
export async function render(container) {
|
|
2208
|
+
// Show skeleton while loading
|
|
2209
|
+
container.innerHTML = `
|
|
2210
|
+
<mu-doc-page title="Users">
|
|
2211
|
+
<mu-skeleton type="text" lines="5"></mu-skeleton>
|
|
2212
|
+
</mu-doc-page>
|
|
2213
|
+
`;
|
|
2214
|
+
|
|
2215
|
+
// Fetch data
|
|
2216
|
+
const users = await fetchAPI('/users');
|
|
2217
|
+
store.users.set(users);
|
|
2218
|
+
|
|
2219
|
+
// Render content
|
|
2220
|
+
container.innerHTML = `
|
|
2221
|
+
<mu-doc-page title="Users">
|
|
2222
|
+
<mu-stack gap="md">
|
|
2223
|
+
<mu-stack direction="row" justify="space-between">
|
|
2224
|
+
<h2>All Users (${users.length})</h2>
|
|
2225
|
+
<mu-button variant="filled" id="add-user">Add User</mu-button>
|
|
2226
|
+
</mu-stack>
|
|
2227
|
+
<mu-repeat id="user-list"></mu-repeat>
|
|
2228
|
+
</mu-stack>
|
|
2229
|
+
</mu-doc-page>
|
|
2230
|
+
`;
|
|
2231
|
+
|
|
2232
|
+
// Setup repeat
|
|
2233
|
+
const list = container.querySelector('#user-list');
|
|
2234
|
+
list.items = users;
|
|
2235
|
+
list.renderItem = (user) => `
|
|
2236
|
+
<mu-card variant="outlined">
|
|
2237
|
+
<strong>${user.name}</strong> - ${user.email}
|
|
2238
|
+
</mu-card>
|
|
2239
|
+
`;
|
|
2240
|
+
}
|
|
2241
|
+
```
|
|
2242
|
+
|
|
2243
|
+
### Performance Checklist for Large Apps
|
|
2244
|
+
```
|
|
2245
|
+
[ ] Use route-based code splitting (only load needed code)
|
|
2246
|
+
[ ] Preload critical routes with <link rel="prefetch">
|
|
2247
|
+
[ ] Use mu-virtual-list for lists with 100+ items
|
|
2248
|
+
[ ] Use mu-lazy for below-the-fold content
|
|
2249
|
+
[ ] Implement skeleton loading for perceived performance
|
|
2250
|
+
[ ] Cache API responses with Service Worker
|
|
2251
|
+
[ ] Use signals for state (not prop drilling)
|
|
2252
|
+
[ ] Minimize bundle size with tree-shaking
|
|
2253
|
+
```
|
|
2254
|
+
|
|
2255
|
+
---
|
|
2256
|
+
|
|
2257
|
+
## 🚀 Development Server
|
|
2258
|
+
|
|
2259
|
+
> **CRITICAL**: Use the project's official `server.js` for development and Lighthouse testing.
|
|
2260
|
+
|
|
2261
|
+
### Quick Start
|
|
2262
|
+
```bash
|
|
2263
|
+
# Start the Lighthouse-optimized dev server
|
|
2264
|
+
bun server.js
|
|
2265
|
+
|
|
2266
|
+
# Server runs on port 5001 (auto-increments if busy)
|
|
2267
|
+
# 🚀 http://localhost:5001/
|
|
2268
|
+
```
|
|
2269
|
+
|
|
2270
|
+
### URL Routing
|
|
2271
|
+
|
|
2272
|
+
| URL | Maps To | Description |
|
|
2273
|
+
|-----|---------|-------------|
|
|
2274
|
+
| `/` | `/demo/shell.html` | Main showcase (App Shell pattern with dynamic route loading) |
|
|
2275
|
+
| `/shell.html` | `/demo/shell.html` | App Shell pattern (dynamic route loading) |
|
|
2276
|
+
| `/content/*` | `/demo/content/*` | Content fragments for shell.html |
|
|
2277
|
+
| `/sw.js` | `/demo/sw.js` | Service Worker |
|
|
2278
|
+
| `/manifest.json` | `/demo/manifest.json` | PWA manifest |
|
|
2279
|
+
| `/favicon.png` | `/demo/favicon.png` | Favicon |
|
|
2280
|
+
| `/-/health` | - | Health check endpoint |
|
|
2281
|
+
| `/-/clear-cache` | - | Clear browser cache |
|
|
2282
|
+
|
|
2283
|
+
### Server Features
|
|
2284
|
+
|
|
2285
|
+
The `server.js` is optimized for **Lighthouse 100** scores:
|
|
2286
|
+
|
|
2287
|
+
1. **Cache-Control Headers**
|
|
2288
|
+
- `1 year` for versioned assets (`/dist/`, `/assets/`)
|
|
2289
|
+
- `must-revalidate` for HTML (bf-cache friendly)
|
|
2290
|
+
- `1 hour` for other resources
|
|
2291
|
+
|
|
2292
|
+
2. **Gzip Compression**
|
|
2293
|
+
- Auto-compresses `.html`, `.css`, `.js`, `.json`, `.svg`
|
|
2294
|
+
|
|
2295
|
+
3. **Link Preload Headers**
|
|
2296
|
+
- Sends HTTP `Link` headers for critical resources
|
|
2297
|
+
|
|
2298
|
+
4. **Security**
|
|
2299
|
+
- Directory traversal protection
|
|
2300
|
+
- `X-Content-Type-Options: nosniff`
|
|
2301
|
+
|
|
2302
|
+
### Running Lighthouse Audits
|
|
2303
|
+
```bash
|
|
2304
|
+
# Run the automated Lighthouse audit script
|
|
2305
|
+
source ~/.nvm/nvm.sh && nvm use 22
|
|
2306
|
+
node lighthouse-audit.mjs
|
|
2307
|
+
|
|
2308
|
+
# Output shows scores for shell.html
|
|
2309
|
+
# Results saved to /tmp/lighthouse-shell.json
|
|
2310
|
+
```
|
|
2311
|
+
|
|
2312
|
+
### Current Lighthouse Scores (v0.1.0)
|
|
2313
|
+
|
|
2314
|
+
| Page | Performance | Accessibility | Best Practices | SEO |
|
|
2315
|
+
|------|-------------|---------------|----------------|-----|
|
|
2316
|
+
| `shell.html` | 98 | 100 | 100 | 100 |
|
|
2317
|
+
|
|
2318
|
+
### CLS Prevention Pattern
|
|
2319
|
+
|
|
2320
|
+
To prevent Cumulative Layout Shift from custom elements, pre-define dimensions:
|
|
2321
|
+
|
|
2322
|
+
```css
|
|
2323
|
+
/* In <head> style block - REQUIRED for Lighthouse Performance */
|
|
2324
|
+
mu-bottom-nav:not(:defined) {
|
|
2325
|
+
display: block;
|
|
2326
|
+
position: fixed;
|
|
2327
|
+
bottom: 0;
|
|
2328
|
+
left: 0;
|
|
2329
|
+
right: 0;
|
|
2330
|
+
height: var(--mu-bottom-nav-height, 80px);
|
|
2331
|
+
z-index: 1000;
|
|
2332
|
+
}
|
|
2333
|
+
```
|
|
2334
|
+
|
|
2335
|
+
### Srcset for Retina Images
|
|
2336
|
+
|
|
2337
|
+
Always use srcset for hero images to pass "Serves images with low resolution":
|
|
2338
|
+
|
|
2339
|
+
```html
|
|
2340
|
+
<picture>
|
|
2341
|
+
<source srcset="logo-400.webp 1x, logo-800.webp 2x" type="image/webp">
|
|
2342
|
+
<img src="logo-400.jpg" width="400" height="223" alt="Description">
|
|
2343
|
+
</picture>
|
|
2344
|
+
|
|
2345
|
+
<!-- Preload with imagesrcset -->
|
|
2346
|
+
<link rel="preload" as="image" type="image/webp"
|
|
2347
|
+
href="logo-400.webp"
|
|
2348
|
+
imagesrcset="logo-400.webp 1x, logo-800.webp 2x"
|
|
2349
|
+
fetchpriority="high">
|
|
2350
|
+
```
|
|
2351
|
+
|
|
2352
|
+
### Skip Link Anchors (Accessibility)
|
|
2353
|
+
|
|
2354
|
+
All hash links must have focusable targets:
|
|
2355
|
+
|
|
2356
|
+
```html
|
|
2357
|
+
<!-- Before each page section, add anchor with tabindex -->
|
|
2358
|
+
<div id="installation" tabindex="-1" aria-hidden="true"></div>
|
|
2359
|
+
<div id="page-installation" class="page">
|
|
2360
|
+
<!-- Page content -->
|
|
2361
|
+
</div>
|
|
2362
|
+
```
|
|
2363
|
+
|
|
2364
|
+
---
|
|
2365
|
+
|
|
2366
|
+
*Last updated: v0.1.0 - 2026-02-02*
|