@ccslabs/xtend 0.1.0-rc.1

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.
Files changed (664) hide show
  1. package/CHANGELOG.md +65 -0
  2. package/LICENSE +201 -0
  3. package/README.md +184 -0
  4. package/a11y/motion-contrast-policy.d.ts +32 -0
  5. package/a11y/motion-contrast-policy.js +261 -0
  6. package/a11y/runtime-a11y-contract.d.ts +44 -0
  7. package/a11y/runtime-a11y-contract.js +385 -0
  8. package/a11y/screenreader-signals.d.ts +32 -0
  9. package/a11y/screenreader-signals.js +372 -0
  10. package/api.d.ts +168 -0
  11. package/api.js +864 -0
  12. package/catalog/catalog-public-types.d.ts +66 -0
  13. package/catalog/component-catalog-coverage.d.ts +20 -0
  14. package/catalog/component-catalog-coverage.js +377 -0
  15. package/catalog/component-long-tail-migration.d.ts +18 -0
  16. package/catalog/component-long-tail-migration.js +305 -0
  17. package/catalog/component-regression-priority.d.ts +20 -0
  18. package/catalog/component-regression-priority.js +305 -0
  19. package/catalog/enterprise-component-flex-release-handoff.d.ts +32 -0
  20. package/catalog/enterprise-component-flex-release-handoff.js +437 -0
  21. package/catalog/enterprise-component-style-audit.d.ts +22 -0
  22. package/catalog/enterprise-component-style-audit.js +353 -0
  23. package/catalog/enterprise-form-control-theme-a11y.d.ts +19 -0
  24. package/catalog/enterprise-form-control-theme-a11y.js +220 -0
  25. package/catalog/enterprise-icon-control-audit.d.ts +21 -0
  26. package/catalog/enterprise-icon-control-audit.js +258 -0
  27. package/catalog/enterprise-layout-display-media-tokenization.d.ts +20 -0
  28. package/catalog/enterprise-layout-display-media-tokenization.js +237 -0
  29. package/catalog/enterprise-navigation-routing-state-hardening.d.ts +20 -0
  30. package/catalog/enterprise-navigation-routing-state-hardening.js +255 -0
  31. package/catalog/enterprise-overlay-mode-token-parity.d.ts +15 -0
  32. package/catalog/enterprise-overlay-mode-token-parity.js +178 -0
  33. package/catalog/enterprise-third-party-authoring-guide.d.ts +23 -0
  34. package/catalog/enterprise-third-party-authoring-guide.js +310 -0
  35. package/catalog/enterprise-visual-dom-snapshot-matrix.d.ts +31 -0
  36. package/catalog/enterprise-visual-dom-snapshot-matrix.js +357 -0
  37. package/catalog/epic10-existing-component-metadata.d.ts +25 -0
  38. package/catalog/epic10-existing-component-metadata.js +534 -0
  39. package/catalog/epic10-p0-component-wave.d.ts +28 -0
  40. package/catalog/epic10-p0-component-wave.js +688 -0
  41. package/catalog/epic10-platform-gates.d.ts +31 -0
  42. package/catalog/epic10-platform-gates.js +425 -0
  43. package/catalog/epic10-release-handoff.d.ts +30 -0
  44. package/catalog/epic10-release-handoff.js +195 -0
  45. package/catalog/epic11-enterprise-ux-handoff.d.ts +29 -0
  46. package/catalog/epic11-enterprise-ux-handoff.js +403 -0
  47. package/catalog/epic12-docs-adoption.d.ts +29 -0
  48. package/catalog/epic12-docs-adoption.js +183 -0
  49. package/catalog/epic12-rc0-gate-matrix.d.ts +36 -0
  50. package/catalog/epic12-rc0-gate-matrix.js +439 -0
  51. package/catalog/epic12-rc0-handoff.d.ts +30 -0
  52. package/catalog/epic12-rc0-handoff.js +385 -0
  53. package/catalog/epic13-conditional-network-evidence-ci.d.ts +35 -0
  54. package/catalog/epic13-conditional-network-evidence-ci.js +278 -0
  55. package/catalog/epic13-conditional-network-evidence.d.ts +34 -0
  56. package/catalog/epic13-conditional-network-evidence.js +280 -0
  57. package/catalog/epic13-docs-rmt-production-hardening.d.ts +39 -0
  58. package/catalog/epic13-docs-rmt-production-hardening.js +286 -0
  59. package/catalog/epic13-hydration-performance-closure.d.ts +33 -0
  60. package/catalog/epic13-hydration-performance-closure.js +234 -0
  61. package/catalog/epic13-known-residual-triage.d.ts +32 -0
  62. package/catalog/epic13-known-residual-triage.js +339 -0
  63. package/catalog/epic13-package-export-lock.d.ts +41 -0
  64. package/catalog/epic13-package-export-lock.js +604 -0
  65. package/catalog/epic13-prod-browser-csp-smoke.d.ts +35 -0
  66. package/catalog/epic13-prod-browser-csp-smoke.js +218 -0
  67. package/catalog/epic13-rc1-gate-matrix-ci-handoff.d.ts +36 -0
  68. package/catalog/epic13-rc1-gate-matrix-ci-handoff.js +418 -0
  69. package/catalog/epic13-rc1-migration-notes.d.ts +36 -0
  70. package/catalog/epic13-rc1-migration-notes.js +271 -0
  71. package/catalog/epic13-rc1-readiness.d.ts +33 -0
  72. package/catalog/epic13-rc1-readiness.js +487 -0
  73. package/catalog/epic13-release-owner-acceptance.d.ts +33 -0
  74. package/catalog/epic13-release-owner-acceptance.js +354 -0
  75. package/catalog/epic13-release-report-pack-dry-run-evidence.d.ts +36 -0
  76. package/catalog/epic13-release-report-pack-dry-run-evidence.js +253 -0
  77. package/catalog/epic13-rmt-production-readiness.d.ts +35 -0
  78. package/catalog/epic13-rmt-production-readiness.js +314 -0
  79. package/catalog/epic13-trusted-dom-boundary.d.ts +36 -0
  80. package/catalog/epic13-trusted-dom-boundary.js +230 -0
  81. package/catalog/epic13-visual-owner-artifact.d.ts +35 -0
  82. package/catalog/epic13-visual-owner-artifact.js +233 -0
  83. package/catalog/epic14-lsp-handoff.d.ts +28 -0
  84. package/catalog/epic14-lsp-handoff.js +312 -0
  85. package/catalog/epic14-rmt-tooling.d.ts +33 -0
  86. package/catalog/epic14-rmt-tooling.js +282 -0
  87. package/catalog/surface-manager-adapter-runtime.d.ts +37 -0
  88. package/catalog/surface-manager-adapter-runtime.js +203 -0
  89. package/catalog/surface-manager-browser-lab.d.ts +39 -0
  90. package/catalog/surface-manager-browser-lab.js +225 -0
  91. package/catalog/surface-manager-controller.d.ts +43 -0
  92. package/catalog/surface-manager-controller.js +290 -0
  93. package/catalog/surface-manager-layout-engines.d.ts +32 -0
  94. package/catalog/surface-manager-layout-engines.js +161 -0
  95. package/catalog/surface-manager-lazy-loading.d.ts +35 -0
  96. package/catalog/surface-manager-lazy-loading.js +173 -0
  97. package/catalog/surface-manager-materialization.d.ts +37 -0
  98. package/catalog/surface-manager-materialization.js +202 -0
  99. package/catalog/surface-manager-native-rmt-surfaces.d.ts +48 -0
  100. package/catalog/surface-manager-native-rmt-surfaces.js +325 -0
  101. package/catalog/surface-manager-overlay-bridge.d.ts +42 -0
  102. package/catalog/surface-manager-overlay-bridge.js +247 -0
  103. package/catalog/surface-manager-persistence.d.ts +37 -0
  104. package/catalog/surface-manager-persistence.js +178 -0
  105. package/catalog/surface-manager-quality-gates.d.ts +48 -0
  106. package/catalog/surface-manager-quality-gates.js +324 -0
  107. package/catalog/surface-manager-release-handoff.d.ts +47 -0
  108. package/catalog/surface-manager-release-handoff.js +274 -0
  109. package/catalog/surface-manager-remote-policy.d.ts +34 -0
  110. package/catalog/surface-manager-remote-policy.js +199 -0
  111. package/catalog/surface-manager-rmt-authoring.d.ts +44 -0
  112. package/catalog/surface-manager-rmt-authoring.js +368 -0
  113. package/catalog/surface-manager-route-lifecycle.d.ts +32 -0
  114. package/catalog/surface-manager-route-lifecycle.js +162 -0
  115. package/catalog/surface-manager-runtime-release-handoff.d.ts +36 -0
  116. package/catalog/surface-manager-runtime-release-handoff.js +245 -0
  117. package/catalog/surface-manager-side-panel-runtime.d.ts +46 -0
  118. package/catalog/surface-manager-side-panel-runtime.js +307 -0
  119. package/catalog/surface-manager-stack-policy.d.ts +32 -0
  120. package/catalog/surface-manager-stack-policy.js +169 -0
  121. package/catalog/surface-manager-window-runtime.d.ts +45 -0
  122. package/catalog/surface-manager-window-runtime.js +285 -0
  123. package/catalog/surface-manager-workbench-fixture.d.ts +50 -0
  124. package/catalog/surface-manager-workbench-fixture.js +315 -0
  125. package/catalog/type-exports-api.js +236 -0
  126. package/catalog/type-exports-builder.js +405 -0
  127. package/catalog/type-exports-catalog.js +394 -0
  128. package/catalog/type-exports-loader.js +266 -0
  129. package/catalog/type-exports-policy.js +461 -0
  130. package/catalog/type-exports-rmt.js +407 -0
  131. package/catalog/type-exports-vendor.js +365 -0
  132. package/catalog/type-exports.js +574 -0
  133. package/components/icon-packs/core.js +154 -0
  134. package/components/icon-packs/lucide.js +136 -0
  135. package/components/manifest.json +44 -0
  136. package/components/prism.d.ts +73 -0
  137. package/components/prism.js +300 -0
  138. package/components/turndown.d.ts +34 -0
  139. package/components/turndown.js +107 -0
  140. package/components/x-rmt-lifecycle-demo-build.d.ts +78 -0
  141. package/components/x-rmt-lifecycle-demo-build.js +1175 -0
  142. package/components/x-rmt-lifecycle-demo.d.ts +83 -0
  143. package/components/x-rmt-lifecycle-demo.js +1175 -0
  144. package/components/xalert.d.ts +42 -0
  145. package/components/xalert.js +538 -0
  146. package/components/xbutton.d.ts +127 -0
  147. package/components/xbutton.js +612 -0
  148. package/components/xcalendar.d.ts +39 -0
  149. package/components/xcalendar.js +338 -0
  150. package/components/xcards.d.ts +34 -0
  151. package/components/xcards.js +253 -0
  152. package/components/xcheckbox.d.ts +48 -0
  153. package/components/xcheckbox.js +448 -0
  154. package/components/xcode.d.ts +35 -0
  155. package/components/xcode.js +370 -0
  156. package/components/xdialog.d.ts +48 -0
  157. package/components/xdialog.js +763 -0
  158. package/components/xdrawer.d.ts +61 -0
  159. package/components/xdrawer.js +654 -0
  160. package/components/xfooter.d.ts +41 -0
  161. package/components/xfooter.js +351 -0
  162. package/components/xform.d.ts +43 -0
  163. package/components/xform.js +456 -0
  164. package/components/xheader.d.ts +68 -0
  165. package/components/xheader.js +1253 -0
  166. package/components/xhero.d.ts +42 -0
  167. package/components/xhero.js +475 -0
  168. package/components/xicon.d.ts +146 -0
  169. package/components/xicon.js +688 -0
  170. package/components/xinput.d.ts +37 -0
  171. package/components/xinput.js +444 -0
  172. package/components/xlightbox.d.ts +48 -0
  173. package/components/xlightbox.js +571 -0
  174. package/components/xlink.d.ts +63 -0
  175. package/components/xlink.js +565 -0
  176. package/components/xmasonry.d.ts +35 -0
  177. package/components/xmasonry.js +666 -0
  178. package/components/xmenu.d.ts +118 -0
  179. package/components/xmenu.js +1005 -0
  180. package/components/xmodal.d.ts +64 -0
  181. package/components/xmodal.js +831 -0
  182. package/components/xplayer.d.ts +57 -0
  183. package/components/xplayer.js +1748 -0
  184. package/components/xpopover.d.ts +54 -0
  185. package/components/xpopover.js +466 -0
  186. package/components/xprogress.d.ts +40 -0
  187. package/components/xprogress.js +345 -0
  188. package/components/xradio.d.ts +50 -0
  189. package/components/xradio.js +474 -0
  190. package/components/xrouter.d.ts +244 -0
  191. package/components/xrouter.js +1841 -0
  192. package/components/xsection.d.ts +34 -0
  193. package/components/xsection.js +253 -0
  194. package/components/xselect.d.ts +46 -0
  195. package/components/xselect.js +463 -0
  196. package/components/xsidepanel.d.ts +56 -0
  197. package/components/xsidepanel.js +728 -0
  198. package/components/xspinner.d.ts +38 -0
  199. package/components/xspinner.js +388 -0
  200. package/components/xstate.d.ts +137 -0
  201. package/components/xstate.js +493 -0
  202. package/components/xstatus.d.ts +41 -0
  203. package/components/xstatus.js +381 -0
  204. package/components/xsummary.d.ts +43 -0
  205. package/components/xsummary.js +293 -0
  206. package/components/xsurfacemanager-controller.d.ts +130 -0
  207. package/components/xsurfacemanager-controller.js +699 -0
  208. package/components/xsurfacemanager.d.ts +452 -0
  209. package/components/xsurfacemanager.js +3775 -0
  210. package/components/xsurfaceoverlay-bridge.d.ts +43 -0
  211. package/components/xsurfaceoverlay-bridge.js +238 -0
  212. package/components/xsurfacewindow.d.ts +50 -0
  213. package/components/xsurfacewindow.js +576 -0
  214. package/components/xtabs.d.ts +73 -0
  215. package/components/xtabs.js +611 -0
  216. package/components/xtend-public-types.d.ts +208 -0
  217. package/components/xtextarea.d.ts +46 -0
  218. package/components/xtextarea.js +451 -0
  219. package/components/xtheme.d.ts +253 -0
  220. package/components/xtheme.js +1438 -0
  221. package/components/xtoast.d.ts +39 -0
  222. package/components/xtoast.js +389 -0
  223. package/components/xtooltip.d.ts +53 -0
  224. package/components/xtooltip.js +432 -0
  225. package/components/xtype.d.ts +42 -0
  226. package/components/xtype.js +244 -0
  227. package/components/xutils.d.ts +164 -0
  228. package/components/xutils.js +496 -0
  229. package/components/xwriter.d.ts +67 -0
  230. package/components/xwriter.js +854 -0
  231. package/design-tokens/themes/enterprise-light.json +40 -0
  232. package/design-tokens/themes/xtend-signature.json +126 -0
  233. package/design-tokens/xtend-design-tokens.d.ts +95 -0
  234. package/design-tokens/xtend-design-tokens.js +395 -0
  235. package/design-tokens/xtheme-token-alias-layer.d.ts +84 -0
  236. package/design-tokens/xtheme-token-alias-layer.js +423 -0
  237. package/docs/.htaccess +51 -0
  238. package/docs/README.md +340 -0
  239. package/docs/XTend-ADR.md +221 -0
  240. package/docs/a11y-keyboard-smokes.md +62 -0
  241. package/docs/about.md +18 -0
  242. package/docs/api.md +157 -0
  243. package/docs/best-practices.md +76 -0
  244. package/docs/component-catalog-coverage.md +58 -0
  245. package/docs/component-lab.md +103 -0
  246. package/docs/component-long-tail-migration.md +41 -0
  247. package/docs/component-platform.md +159 -0
  248. package/docs/component-ux-app-authoring.md +130 -0
  249. package/docs/component-ux-authoring.md +96 -0
  250. package/docs/component-ux-gates.md +45 -0
  251. package/docs/components/x-rmt-lifecycle-demo-build.md +60 -0
  252. package/docs/components/xalert.md +81 -0
  253. package/docs/components/xbutton.md +103 -0
  254. package/docs/components/xcalendar.md +82 -0
  255. package/docs/components/xcards.md +128 -0
  256. package/docs/components/xcheckbox.md +102 -0
  257. package/docs/components/xcode.md +126 -0
  258. package/docs/components/xdialog.md +92 -0
  259. package/docs/components/xdrawer.md +84 -0
  260. package/docs/components/xfooter.md +126 -0
  261. package/docs/components/xform.md +128 -0
  262. package/docs/components/xheader.md +308 -0
  263. package/docs/components/xhero.md +142 -0
  264. package/docs/components/xicon.md +125 -0
  265. package/docs/components/xinput.md +129 -0
  266. package/docs/components/xlightbox.md +98 -0
  267. package/docs/components/xlink.md +109 -0
  268. package/docs/components/xmasonry.md +124 -0
  269. package/docs/components/xmenu.md +158 -0
  270. package/docs/components/xmodal.md +82 -0
  271. package/docs/components/xplayer.md +104 -0
  272. package/docs/components/xpopover.md +67 -0
  273. package/docs/components/xprogress.md +56 -0
  274. package/docs/components/xradio.md +103 -0
  275. package/docs/components/xrouter.md +260 -0
  276. package/docs/components/xsection.md +125 -0
  277. package/docs/components/xselect.md +105 -0
  278. package/docs/components/xsidepanel.md +30 -0
  279. package/docs/components/xspinner.md +102 -0
  280. package/docs/components/xstate.md +148 -0
  281. package/docs/components/xstatus.md +55 -0
  282. package/docs/components/xsummary.md +78 -0
  283. package/docs/components/xsurfacemanager.md +27 -0
  284. package/docs/components/xsurfacewindow.md +21 -0
  285. package/docs/components/xtabs.md +160 -0
  286. package/docs/components/xtextarea.md +98 -0
  287. package/docs/components/xtheme.md +167 -0
  288. package/docs/components/xtoast.md +62 -0
  289. package/docs/components/xtooltip.md +66 -0
  290. package/docs/components/xtype.md +82 -0
  291. package/docs/components/xutils.md +144 -0
  292. package/docs/components/xwriter.md +94 -0
  293. package/docs/components.md +117 -0
  294. package/docs/conditional-network-evidence-ci.md +38 -0
  295. package/docs/conditional-network-evidence.md +50 -0
  296. package/docs/core-migration-guide.md +110 -0
  297. package/docs/design-tokens.md +116 -0
  298. package/docs/docs-rmt-production-hardening.md +31 -0
  299. package/docs/enterprise-adoption.md +411 -0
  300. package/docs/enterprise-component-flex-release-handoff.md +129 -0
  301. package/docs/epic10-platform-gates.md +62 -0
  302. package/docs/epic10-release-handoff.md +81 -0
  303. package/docs/epic11-enterprise-ux-handoff.md +70 -0
  304. package/docs/epic12-rc0-handoff.md +61 -0
  305. package/docs/existing-component-metadata.md +67 -0
  306. package/docs/hydration-performance-closure.md +34 -0
  307. package/docs/hydration-policies.md +71 -0
  308. package/docs/index.php +1625 -0
  309. package/docs/known-residual-triage.md +22 -0
  310. package/docs/manifest-import-policy.md +79 -0
  311. package/docs/manifest.md +106 -0
  312. package/docs/menu.json +1190 -0
  313. package/docs/motion-contrast.md +67 -0
  314. package/docs/package-export-lock.md +44 -0
  315. package/docs/performance-measurements.md +106 -0
  316. package/docs/performance-regression.md +89 -0
  317. package/docs/performance.md +94 -0
  318. package/docs/previews/README.md +17 -0
  319. package/docs/prod-browser-csp-smokes.md +40 -0
  320. package/docs/public-component-types.md +79 -0
  321. package/docs/quick-start-guide.md +152 -0
  322. package/docs/rc0-adoption-guide.md +102 -0
  323. package/docs/rc0-gate-matrix.md +58 -0
  324. package/docs/rc1-gate-matrix-ci-handoff.md +56 -0
  325. package/docs/rc1-migration-notes.md +69 -0
  326. package/docs/rc1-readiness.md +46 -0
  327. package/docs/release-owner-acceptance.md +56 -0
  328. package/docs/release-report-pack-dry-run-evidence.md +39 -0
  329. package/docs/rmt-dsl-authoring-polish.md +122 -0
  330. package/docs/rmt-first-demo-app.md +77 -0
  331. package/docs/rmt-first-xtend-apps.md +105 -0
  332. package/docs/rmt-kernel-panic-recovery-incident-handoff.md +61 -0
  333. package/docs/rmt-kernel-security-hardening-migration.md +50 -0
  334. package/docs/rmt-kernel-trusted-output-authoring.md +69 -0
  335. package/docs/rmt-language-server.md +177 -0
  336. package/docs/rmt-lifecycle-demo.md +25 -0
  337. package/docs/rmt-linter.md +140 -0
  338. package/docs/rmt-production-readiness.md +63 -0
  339. package/docs/rmt-tooling-release-gates.md +77 -0
  340. package/docs/rmt-vnext-authoring.md +60 -0
  341. package/docs/rmt-vnext-cross-surface-events.md +68 -0
  342. package/docs/rmt-vnext-enterprise-mfe-handoff.md +70 -0
  343. package/docs/rmt-vnext-migration-notes.md +62 -0
  344. package/docs/rmt-vnext-release-handoff.md +69 -0
  345. package/docs/rmt-vnext-remote-surfaces.md +90 -0
  346. package/docs/rmt-vnext-surface-registry-enterprise.md +76 -0
  347. package/docs/screenreader-signals.md +56 -0
  348. package/docs/supply-chain-gates.md +100 -0
  349. package/docs/surface-manager-authoring-guide.md +94 -0
  350. package/docs/surface-manager-browser-lab.md +45 -0
  351. package/docs/surface-manager-component-lab.md +43 -0
  352. package/docs/surface-manager-controller.md +66 -0
  353. package/docs/surface-manager-layout-engines.md +32 -0
  354. package/docs/surface-manager-lazy-hydration.md +63 -0
  355. package/docs/surface-manager-migration-guide.md +94 -0
  356. package/docs/surface-manager-native-rmt-surfaces.md +38 -0
  357. package/docs/surface-manager-overlay-bridge.md +53 -0
  358. package/docs/surface-manager-persistence.md +30 -0
  359. package/docs/surface-manager-quality-gates.md +51 -0
  360. package/docs/surface-manager-release-handoff.md +68 -0
  361. package/docs/surface-manager-remote-policy.md +54 -0
  362. package/docs/surface-manager-rmt-authoring.md +86 -0
  363. package/docs/surface-manager-route-lifecycle.md +59 -0
  364. package/docs/surface-manager-runtime-release-handoff.md +69 -0
  365. package/docs/surface-manager-side-panel-runtime.md +36 -0
  366. package/docs/surface-manager-stack-policy.md +39 -0
  367. package/docs/surface-manager-window-runtime.md +47 -0
  368. package/docs/surface-manager-workbench-fixture.md +43 -0
  369. package/docs/third-party-design-authoring.md +406 -0
  370. package/docs/trusted-dom-boundary-browser-proof.md +32 -0
  371. package/docs/trusted-dom-sanitizing.md +110 -0
  372. package/docs/type-exports.md +61 -0
  373. package/docs/typescript-components.md +63 -0
  374. package/docs/utils/fabric-runtime.js +650 -0
  375. package/docs/utils/pageloader.js +2823 -0
  376. package/docs/utils/parsedown.php +298 -0
  377. package/docs/visual-browser-regression.md +83 -0
  378. package/docs/visual-owner-artifacts.md +46 -0
  379. package/docs/visual-snapshot-automation.md +87 -0
  380. package/docs/xtend-api-types.md +55 -0
  381. package/docs/xtend-builder-types.md +55 -0
  382. package/docs/xtend-catalog-types.md +44 -0
  383. package/docs/xtend-fabric-rmt-lane-mapping.md +143 -0
  384. package/docs/xtend-fabric.md +474 -0
  385. package/docs/xtend-loader-types.md +58 -0
  386. package/docs/xtend-loader.md +265 -0
  387. package/docs/xtend-policy-types.md +38 -0
  388. package/docs/xtend-rmt-types.md +39 -0
  389. package/docs/xtend-vendor-types.md +36 -0
  390. package/docs/xtendrmt-app-dsl.md +269 -0
  391. package/docs/xtendrmt-migration-guide.md +235 -0
  392. package/docs/xtendrmt-native-authoring.md +337 -0
  393. package/docs/xtendrmt-overview.md +89 -0
  394. package/docs/xtendrmt-parsedown-docs.rmt +956 -0
  395. package/docs/xtendrmt-parsedown-scheduling.md +301 -0
  396. package/docs/xtendrmt-runtime-bridge.md +155 -0
  397. package/fabric/hydration-policy.d.ts +27 -0
  398. package/fabric/hydration-policy.js +306 -0
  399. package/fabric/package.json +58 -0
  400. package/fabric/rmt-lane-mapping.d.ts +47 -0
  401. package/fabric/rmt-lane-mapping.js +504 -0
  402. package/fabric/xtend-fabric.d.ts +81 -0
  403. package/fabric/xtend-fabric.js +2669 -0
  404. package/fabric/xtend-policy-public-types.d.ts +135 -0
  405. package/package.json +8225 -0
  406. package/security/README.md +54 -0
  407. package/security/manifest-import-policy.d.ts +43 -0
  408. package/security/manifest-import-policy.js +260 -0
  409. package/security/supply-chain-gate-policy.d.ts +45 -0
  410. package/security/supply-chain-gate-policy.js +249 -0
  411. package/security/trusted-dom-policy.d.ts +36 -0
  412. package/security/trusted-dom-policy.js +316 -0
  413. package/tools/package.json +77 -0
  414. package/tools/rmt-editor/vscode/README.md +33 -0
  415. package/tools/rmt-editor/vscode/extension.d.ts +9 -0
  416. package/tools/rmt-editor/vscode/extension.js +55 -0
  417. package/tools/rmt-editor/vscode/language-configuration.json +28 -0
  418. package/tools/rmt-editor/vscode/package.json +83 -0
  419. package/tools/rmt-editor/vscode/snippets/rmt.code-snippets +243 -0
  420. package/tools/rmt-editor/vscode/syntaxes/rmt.tmLanguage.json +13 -0
  421. package/tools/rmt-editor/vscode/xtend-rmt-language-0.0.0-enterprise-readiness.vsix +0 -0
  422. package/tools/rmt-language/code-actions.d.ts +15 -0
  423. package/tools/rmt-language/code-actions.js +566 -0
  424. package/tools/rmt-language/completions.d.ts +22 -0
  425. package/tools/rmt-language/completions.js +475 -0
  426. package/tools/rmt-language/definitions.d.ts +13 -0
  427. package/tools/rmt-language/definitions.js +212 -0
  428. package/tools/rmt-language/diagnostics.d.ts +23 -0
  429. package/tools/rmt-language/diagnostics.js +486 -0
  430. package/tools/rmt-language/format-adapter.d.ts +16 -0
  431. package/tools/rmt-language/format-adapter.js +270 -0
  432. package/tools/rmt-language/hover.d.ts +12 -0
  433. package/tools/rmt-language/hover.js +326 -0
  434. package/tools/rmt-language/kernel-escalation.d.ts +122 -0
  435. package/tools/rmt-language/kernel-escalation.js +427 -0
  436. package/tools/rmt-language/kernel-panic-monitor.d.ts +176 -0
  437. package/tools/rmt-language/kernel-panic-monitor.js +674 -0
  438. package/tools/rmt-language/kernel-policy-parity.d.ts +142 -0
  439. package/tools/rmt-language/kernel-policy-parity.js +629 -0
  440. package/tools/rmt-language/kernel-recovery.d.ts +173 -0
  441. package/tools/rmt-language/kernel-recovery.js +666 -0
  442. package/tools/rmt-language/kernel-scheduler-failure.d.ts +136 -0
  443. package/tools/rmt-language/kernel-scheduler-failure.js +486 -0
  444. package/tools/rmt-language/kernel-security-regression.d.ts +154 -0
  445. package/tools/rmt-language/kernel-security-regression.js +465 -0
  446. package/tools/rmt-language/kernel-trust-authority.d.ts +120 -0
  447. package/tools/rmt-language/kernel-trust-authority.js +549 -0
  448. package/tools/rmt-language/parser.d.ts +14 -0
  449. package/tools/rmt-language/parser.js +111 -0
  450. package/tools/rmt-language/rmt-tooling-public-types.d.ts +179 -0
  451. package/tools/rmt-language/rules/boundary-policy.js +81 -0
  452. package/tools/rmt-language/rules/document-policy.js +65 -0
  453. package/tools/rmt-language/rules/index.js +29 -0
  454. package/tools/rmt-language/rules/route-policy.js +81 -0
  455. package/tools/rmt-language/rules/scheduler-policy.js +66 -0
  456. package/tools/rmt-language/rules/template-policy.js +130 -0
  457. package/tools/rmt-language/semantic-graph.d.ts +18 -0
  458. package/tools/rmt-language/semantic-graph.js +827 -0
  459. package/tools/rmt-language/snippets/README.md +17 -0
  460. package/tools/rmt-language/snippets/index.d.ts +17 -0
  461. package/tools/rmt-language/snippets/index.js +417 -0
  462. package/tools/rmt-language/snippets/rmt.code-snippets +243 -0
  463. package/tools/rmt-language/source-model.d.ts +14 -0
  464. package/tools/rmt-language/source-model.js +731 -0
  465. package/tools/rmt-language/symbols.d.ts +13 -0
  466. package/tools/rmt-language/symbols.js +183 -0
  467. package/tools/rmt-language/vnext-compatibility.d.ts +28 -0
  468. package/tools/rmt-language/vnext-compatibility.js +675 -0
  469. package/tools/rmt-language/vnext-compiler.d.ts +17 -0
  470. package/tools/rmt-language/vnext-compiler.js +716 -0
  471. package/tools/rmt-language/vnext-composition.d.ts +30 -0
  472. package/tools/rmt-language/vnext-composition.js +595 -0
  473. package/tools/rmt-language/vnext-conditions.d.ts +25 -0
  474. package/tools/rmt-language/vnext-conditions.js +474 -0
  475. package/tools/rmt-language/vnext-cross-surface-events.d.ts +30 -0
  476. package/tools/rmt-language/vnext-cross-surface-events.js +607 -0
  477. package/tools/rmt-language/vnext-degradation.d.ts +23 -0
  478. package/tools/rmt-language/vnext-degradation.js +428 -0
  479. package/tools/rmt-language/vnext-enterprise-fixtures.d.ts +28 -0
  480. package/tools/rmt-language/vnext-enterprise-fixtures.js +487 -0
  481. package/tools/rmt-language/vnext-enterprise-registry.d.ts +21 -0
  482. package/tools/rmt-language/vnext-enterprise-registry.js +495 -0
  483. package/tools/rmt-language/vnext-enterprise-release.d.ts +44 -0
  484. package/tools/rmt-language/vnext-enterprise-release.js +472 -0
  485. package/tools/rmt-language/vnext-event-governance.d.ts +29 -0
  486. package/tools/rmt-language/vnext-event-governance.js +488 -0
  487. package/tools/rmt-language/vnext-events.d.ts +44 -0
  488. package/tools/rmt-language/vnext-events.js +680 -0
  489. package/tools/rmt-language/vnext-import-resolver.d.ts +28 -0
  490. package/tools/rmt-language/vnext-import-resolver.js +642 -0
  491. package/tools/rmt-language/vnext-lifecycle.d.ts +22 -0
  492. package/tools/rmt-language/vnext-lifecycle.js +404 -0
  493. package/tools/rmt-language/vnext-parser.d.ts +21 -0
  494. package/tools/rmt-language/vnext-parser.js +1391 -0
  495. package/tools/rmt-language/vnext-regression.d.ts +25 -0
  496. package/tools/rmt-language/vnext-regression.js +394 -0
  497. package/tools/rmt-language/vnext-release.d.ts +29 -0
  498. package/tools/rmt-language/vnext-release.js +293 -0
  499. package/tools/rmt-language/vnext-remote-compatibility.d.ts +33 -0
  500. package/tools/rmt-language/vnext-remote-compatibility.js +892 -0
  501. package/tools/rmt-language/vnext-remote-compiler.d.ts +14 -0
  502. package/tools/rmt-language/vnext-remote-compiler.js +520 -0
  503. package/tools/rmt-language/vnext-remote-manifest.d.ts +33 -0
  504. package/tools/rmt-language/vnext-remote-manifest.js +538 -0
  505. package/tools/rmt-language/vnext-remote-security.d.ts +27 -0
  506. package/tools/rmt-language/vnext-remote-security.js +380 -0
  507. package/tools/rmt-language/vnext-remote-tooling.d.ts +25 -0
  508. package/tools/rmt-language/vnext-remote-tooling.js +805 -0
  509. package/tools/rmt-language/vnext-scheduler.d.ts +24 -0
  510. package/tools/rmt-language/vnext-scheduler.js +469 -0
  511. package/tools/rmt-language/vnext-security.d.ts +28 -0
  512. package/tools/rmt-language/vnext-security.js +597 -0
  513. package/tools/rmt-language/vnext-streaming.d.ts +28 -0
  514. package/tools/rmt-language/vnext-streaming.js +593 -0
  515. package/tools/rmt-language/vnext-surfaces.d.ts +24 -0
  516. package/tools/rmt-language/vnext-surfaces.js +406 -0
  517. package/tools/rmt-language/vnext-tooling.d.ts +25 -0
  518. package/tools/rmt-language/vnext-tooling.js +728 -0
  519. package/tools/rmt-language-server/protocol.d.ts +22 -0
  520. package/tools/rmt-language-server/protocol.js +352 -0
  521. package/tools/rmt-language-server/server.d.ts +15 -0
  522. package/tools/rmt-language-server/server.js +622 -0
  523. package/tools/rmt-linter/cli.d.ts +14 -0
  524. package/tools/rmt-linter/cli.js +450 -0
  525. package/tools/rmt-linter/reporter.d.ts +16 -0
  526. package/tools/rmt-linter/reporter.js +472 -0
  527. package/xtend-builder/README.md +83 -0
  528. package/xtend-builder/a11y/README.md +42 -0
  529. package/xtend-builder/a11y/component-a11y-profile.d.ts +14 -0
  530. package/xtend-builder/a11y/component-a11y-profile.js +314 -0
  531. package/xtend-builder/blueprints/README.md +105 -0
  532. package/xtend-builder/blueprints/component-blueprint.contract.d.ts +16 -0
  533. package/xtend-builder/blueprints/component-blueprint.contract.js +343 -0
  534. package/xtend-builder/builder-public-types.d.ts +234 -0
  535. package/xtend-builder/extensions/README.md +26 -0
  536. package/xtend-builder/extensions/component-extension-points.d.ts +11 -0
  537. package/xtend-builder/extensions/component-extension-points.js +277 -0
  538. package/xtend-builder/generators/README.md +149 -0
  539. package/xtend-builder/generators/component-files.d.ts +5 -0
  540. package/xtend-builder/generators/component-files.js +769 -0
  541. package/xtend-builder/generators/component-plan.d.ts +4 -0
  542. package/xtend-builder/generators/component-plan.js +104 -0
  543. package/xtend-builder/generators/registry.d.ts +6 -0
  544. package/xtend-builder/generators/registry.js +118 -0
  545. package/xtend-builder/generators/rmt-build.js +738 -0
  546. package/xtend-builder/generators/rmt-lifecycle-demo.js +922 -0
  547. package/xtend-builder/lib/cli.d.ts +9 -0
  548. package/xtend-builder/lib/cli.js +543 -0
  549. package/xtend-builder/lib/layout.d.ts +6 -0
  550. package/xtend-builder/lib/layout.js +153 -0
  551. package/xtend-builder/lib/package-resolver.js +25 -0
  552. package/xtend-builder/package.json +90 -0
  553. package/xtend-builder/performance/README.md +31 -0
  554. package/xtend-builder/performance/component-performance-profile.d.ts +11 -0
  555. package/xtend-builder/performance/component-performance-profile.js +347 -0
  556. package/xtend-builder/performance/component-ux-performance-contract.d.ts +27 -0
  557. package/xtend-builder/performance/component-ux-performance-contract.js +424 -0
  558. package/xtend-builder/preview/README.md +61 -0
  559. package/xtend-builder/preview/component-lab-ux-inspector.d.ts +20 -0
  560. package/xtend-builder/preview/component-lab-ux-inspector.js +448 -0
  561. package/xtend-builder/preview/component-lab.d.ts +14 -0
  562. package/xtend-builder/preview/component-lab.js +278 -0
  563. package/xtend-builder/preview/component-preview.d.ts +5 -0
  564. package/xtend-builder/preview/component-preview.js +160 -0
  565. package/xtend-builder/scaffold.config.d.ts +4 -0
  566. package/xtend-builder/scaffold.config.js +2056 -0
  567. package/xtend-builder/scaffold.d.ts +3 -0
  568. package/xtend-builder/scaffold.js +11 -0
  569. package/xtend-builder/templates/README.md +33 -0
  570. package/xtend-builder/templates/component/a11y.template.ts +11 -0
  571. package/xtend-builder/templates/component/component-suite.template.d.ts +2 -0
  572. package/xtend-builder/templates/component/component-suite.template.js +108 -0
  573. package/xtend-builder/templates/component/contract.template.ts +10 -0
  574. package/xtend-builder/templates/component/demo-plan.template.md +73 -0
  575. package/xtend-builder/templates/component/docs.template.md +301 -0
  576. package/xtend-builder/templates/component/fixture-data.template.ts +28 -0
  577. package/xtend-builder/templates/component/fixture.template.html +37 -0
  578. package/xtend-builder/templates/component/manifest-plan.template.json +22 -0
  579. package/xtend-builder/templates/component/performance.template.ts +13 -0
  580. package/xtend-builder/templates/component/rmt.template.ts +12 -0
  581. package/xtend-builder/templates/component/source.template.d.ts +2 -0
  582. package/xtend-builder/templates/component/source.template.js +137 -0
  583. package/xtend-builder/templates/component/source.template.ts +110 -0
  584. package/xtend-builder/templates/component/types.template.d.ts +423 -0
  585. package/xtend-builder/templates/loader.d.ts +4 -0
  586. package/xtend-builder/templates/loader.js +51 -0
  587. package/xtend-builder/templates/registry.d.ts +6 -0
  588. package/xtend-builder/templates/registry.js +119 -0
  589. package/xtend-builder/typing/README.md +130 -0
  590. package/xtend-builder/typing/component-contract-v2.d.ts +15 -0
  591. package/xtend-builder/typing/component-contract-v2.js +248 -0
  592. package/xtend-builder/typing/component-network-contract.d.ts +22 -0
  593. package/xtend-builder/typing/component-network-contract.js +478 -0
  594. package/xtend-builder/typing/component-shell-contract.d.ts +21 -0
  595. package/xtend-builder/typing/component-shell-contract.js +312 -0
  596. package/xtend-builder/typing/component-styling-contract.d.ts +22 -0
  597. package/xtend-builder/typing/component-styling-contract.js +301 -0
  598. package/xtend-builder/typing/component-types.d.ts +10 -0
  599. package/xtend-builder/typing/component-types.js +551 -0
  600. package/xtend-builder/typing/enterprise-component-flex-hardening-contract.d.ts +20 -0
  601. package/xtend-builder/typing/enterprise-component-flex-hardening-contract.js +332 -0
  602. package/xtend-builder/typing/feedback-status-ux-contract.d.ts +25 -0
  603. package/xtend-builder/typing/feedback-status-ux-contract.js +347 -0
  604. package/xtend-builder/typing/form-controls-ux-contract.d.ts +25 -0
  605. package/xtend-builder/typing/form-controls-ux-contract.js +357 -0
  606. package/xtend-builder/typing/layout-display-media-ux-contract.d.ts +25 -0
  607. package/xtend-builder/typing/layout-display-media-ux-contract.js +420 -0
  608. package/xtend-builder/typing/navigation-routing-ux-contract.d.ts +17 -0
  609. package/xtend-builder/typing/navigation-routing-ux-contract.js +297 -0
  610. package/xtend-builder/typing/overlay-interaction-ux-contract.d.ts +25 -0
  611. package/xtend-builder/typing/overlay-interaction-ux-contract.js +383 -0
  612. package/xtend-builder/typing/rmt-dsl-authoring-polish.d.ts +27 -0
  613. package/xtend-builder/typing/rmt-dsl-authoring-polish.js +419 -0
  614. package/xtend-builder/typing/rmt-shell-authoring-contract.d.ts +26 -0
  615. package/xtend-builder/typing/rmt-shell-authoring-contract.js +315 -0
  616. package/xtend-builder/utils/README.md +8 -0
  617. package/xtend-builder/utils/naming.d.ts +7 -0
  618. package/xtend-builder/utils/naming.js +36 -0
  619. package/xtend-builder/utils/validation.d.ts +5 -0
  620. package/xtend-builder/utils/validation.js +95 -0
  621. package/xtend-builder/wiring/README.md +46 -0
  622. package/xtend-builder/wiring/features.d.ts +5 -0
  623. package/xtend-builder/wiring/features.js +165 -0
  624. package/xtend-builder/wiring/hydration.d.ts +4 -0
  625. package/xtend-builder/wiring/hydration.js +44 -0
  626. package/xtend-builder/wiring/manifest.d.ts +5 -0
  627. package/xtend-builder/wiring/manifest.js +48 -0
  628. package/xtend-builder/workflows/README.md +47 -0
  629. package/xtend-builder/workflows/developer-workflow.d.ts +6 -0
  630. package/xtend-builder/workflows/developer-workflow.js +125 -0
  631. package/xtend-builder/writing/manifest-patcher.d.ts +49 -0
  632. package/xtend-builder/writing/manifest-patcher.js +391 -0
  633. package/xtend-builder/writing/write-plan.d.ts +148 -0
  634. package/xtend-builder/writing/write-plan.js +646 -0
  635. package/xtend-dev.d.ts +23 -0
  636. package/xtend-dev.js +4 -0
  637. package/xtend-loader.d.ts +201 -0
  638. package/xtend-loader.js +1704 -0
  639. package/xtend.css +402 -0
  640. package/xtendrmt/package.json +64 -0
  641. package/xtendrmt/rmt-core.d.ts +4452 -0
  642. package/xtendrmt/rmt-core.esm.js +25793 -0
  643. package/xtendrmt/rmt-first-demo-app.js +265 -0
  644. package/xtendrmt/rmt-first-demo-app.rmt +737 -0
  645. package/xtendrmt/rmt-lifecycle-demo.app.js +493 -0
  646. package/xtendrmt/rmt-lifecycle-demo.core.json +810 -0
  647. package/xtendrmt/rmt-lifecycle-demo.rmt +35 -0
  648. package/xtendrmt/rmt-lifecycle-demo.rmt-build.app.js +153 -0
  649. package/xtendrmt/rmt-lifecycle-demo.rmt-build.core.json +810 -0
  650. package/xtendrmt/rmt-lifecycle-demo.rmt-build.scaffold.json +202 -0
  651. package/xtendrmt/rmt-lifecycle-demo.scaffold.json +187 -0
  652. package/xtendrmt/rmt-manifest.json +548 -0
  653. package/xtendrmt/rmt-runtime.browser.js +26183 -0
  654. package/xtendrmt/rmt-runtime.esm.js +26214 -0
  655. package/xtendrmt/rmt-vnext-enterprise-mfe-demo.core.json +849 -0
  656. package/xtendrmt/rmt-vnext-enterprise-mfe-demo.rmt +50 -0
  657. package/xtendrmt/rmt-vnext-reference-demo.core.json +1069 -0
  658. package/xtendrmt/rmt-vnext-reference-demo.rmt +50 -0
  659. package/xtendrmt/rmt.schema.json +3145 -0
  660. package/xtendrmt/surface-workbench.js +316 -0
  661. package/xtendrmt/surface-workbench.rmt +762 -0
  662. package/xtendrmt/xtendrmt-bestcase-demo.core.json +1187 -0
  663. package/xtendrmt/xtendrmt-bestcase-demo.js +1728 -0
  664. package/xtendrmt/xtendrmt-bestcase-demo.rmt +57 -0
@@ -0,0 +1,1748 @@
1
+ import { xstate } from './xstate.js';
2
+
3
+ class XPlayer extends HTMLElement {
4
+ static get observedAttributes() {
5
+ return ["src", "poster", "type", "media-chooser", "downloadable", "autoplay", "title", "height", "width"];
6
+ }
7
+
8
+ static get xtendComponentContract() {
9
+ return {
10
+ schema: "xtend.component.contract.v2",
11
+ tag: "x-player",
12
+ profiles: ["media", "interactive"],
13
+ maturity: "ux-ready"
14
+ };
15
+ }
16
+
17
+ static get xtendRmtMetadata() {
18
+ return {
19
+ schema: "xtend.rmt.component-contract.v1",
20
+ adapter: "xtend.component",
21
+ schedule: "media.lazy.load",
22
+ kernelBoundary: "no-rmt-kernel-import-of-xtend-types"
23
+ };
24
+ }
25
+
26
+ static get xtendScaffoldA11yProfile() {
27
+ return {
28
+ schema: "xtend.a11y.screenreader-signals.v1",
29
+ role: "region",
30
+ accessibleName: "Media Player",
31
+ focusStrategy: "controls-and-slider"
32
+ };
33
+ }
34
+
35
+ static get xtendScaffoldPerformanceProfile() {
36
+ return {
37
+ schema: "xtend.performance.component-profile.v1",
38
+ performanceProfile: "media",
39
+ budgetClass: "media-interactive",
40
+ lane: "media",
41
+ hydrationPolicy: "lazy",
42
+ criticalMeasurements: ["xtend.media.lazyLoad", "xtend.media.playback", "xtend.media.controls"],
43
+ idleOrBackgroundAllowed: true
44
+ };
45
+ }
46
+
47
+ static get xtendLayoutDisplayMediaUxProfile() {
48
+ return {
49
+ schema: "xtend.component.layout-display-media-ux-profile.v1",
50
+ componentRef: "x-player",
51
+ family: "media-player",
52
+ role: "region",
53
+ contentKind: "audio-video",
54
+ responsiveStrategy: "aspect-ratio-media-box",
55
+ lazyPolicy: "lazy-media-load",
56
+ overflowPolicy: "controls-contained",
57
+ aspectRatio: "16:9-default-or-author-width-height",
58
+ events: ["xplayer-play", "xplayer-pause", "xplayer-fullscreen", "xplayer-pip"],
59
+ commands: ["preload-media", "play-media", "pause-media", "snapshot"],
60
+ stateKey: "xplayer-state-<id>",
61
+ schedule: "media.lazy.load",
62
+ fabric: { lane: "media", a11yLane: "a11y", diagnosticsLane: "diagnostics", api: "@xtend-fabric" },
63
+ rmt: { adapter: "xtend.component", kernelBoundary: "no-rmt-kernel-import-of-xtend-types" }
64
+ };
65
+ }
66
+
67
+ constructor() {
68
+ super();
69
+ this.attachShadow({ mode: "open" });
70
+ this._autoplayUnmuted = false;
71
+ this._dialogOpen = false; // <--- NEU: Globales Dialog-Flag für Endlosschutz
72
+ this.shadowRoot.innerHTML = `
73
+ <style>
74
+ :host {
75
+ display: block;
76
+ font-family: 'Inter', 'Segoe UI', Arial, sans-serif;
77
+ --glass-bg: rgba(30, 34, 44, 0.55);
78
+ --glass-blur: 18px;
79
+ --primary: #4fc3f7;
80
+ --primary-dark: #0288d1;
81
+ --accent: #fff;
82
+ --border-radius: 18px;
83
+ --icon-size: 1.6em;
84
+ --shadow: 0 8px 32px 0 rgba(31, 38, 135, 0.18);
85
+ --focus-outline: 2px solid var(--primary);
86
+ }
87
+ .player {
88
+ background: var(--glass-bg);
89
+ border-radius: var(--border-radius);
90
+ box-shadow: var(--shadow);
91
+ overflow: hidden;
92
+ position: relative;
93
+ backdrop-filter: blur(var(--glass-blur));
94
+ border: 1.5px solid rgba(255,255,255,0.12);
95
+ transition: box-shadow 0.3s, border 0.3s;
96
+ }
97
+ .controls {
98
+ display: flex;
99
+ align-items: center;
100
+ background: var(--glass-bg);
101
+ backdrop-filter: blur(var(--glass-blur));
102
+ border-radius: var(--border-radius);
103
+ box-shadow: var(--shadow);
104
+ padding: 0.7em 1em;
105
+ color: var(--accent);
106
+ font-size: 1em;
107
+ gap: 0.5em;
108
+ position: absolute;
109
+ bottom: 1.2em;
110
+ left: 1.2em;
111
+ right: 1.2em;
112
+ z-index: 10;
113
+ opacity: 0;
114
+ pointer-events: none;
115
+ transition: opacity 0.3s, box-shadow 0.3s;
116
+ }
117
+ .controls.visible {
118
+ opacity: 1;
119
+ pointer-events: auto;
120
+ }
121
+ .controls-left {
122
+ display: flex;
123
+ align-items: center;
124
+ gap: 0.5em;
125
+ flex: none;
126
+ }
127
+ .progress {
128
+ flex: 1 1 0%;
129
+ height: 6px;
130
+ background: rgba(255,255,255,0.18);
131
+ border-radius: 3px;
132
+ margin: 0 0.7em;
133
+ position: relative;
134
+ cursor: pointer;
135
+ box-shadow: 0 1px 4px rgba(0,0,0,0.08);
136
+ min-width: 60px;
137
+ max-width: 100%;
138
+ }
139
+ .controls-stack {
140
+ display: flex;
141
+ align-items: center;
142
+ justify-content: flex-end;
143
+ min-width: unset;
144
+ max-width: unset;
145
+ width: auto;
146
+ gap: 0.5em;
147
+ flex: none;
148
+ }
149
+ .volume-container {
150
+ display: flex;
151
+ align-items: center;
152
+ position: relative;
153
+ width: 2.6em;
154
+ min-width: 2.6em;
155
+ max-width: 2.6em;
156
+ height: 2.6em;
157
+ overflow: visible;
158
+ z-index: 2;
159
+ flex-shrink: 0;
160
+ }
161
+ .volume-container.expanded {
162
+ /* Keine Breitenänderung mehr! */
163
+ }
164
+ .volume-slider {
165
+ position: absolute;
166
+ left: 50%;
167
+ bottom: 100%;
168
+ transform: translateX(-50%) scaleY(0);
169
+ transform-origin: bottom center;
170
+ width: 36px;
171
+ height: 80px;
172
+ opacity: 0;
173
+ pointer-events: none;
174
+ transition: opacity 0.2s, transform 0.25s cubic-bezier(.4,1.4,.6,1);
175
+ background: rgba(30,34,44,0.85);
176
+ border-radius: 1em;
177
+ box-shadow: 0 4px 16px rgba(0,0,0,0.18);
178
+ z-index: 20;
179
+ display: flex;
180
+ align-items: center;
181
+ justify-content: center;
182
+ padding: 0.7em 0;
183
+ }
184
+ .volume-container.expanded .volume-slider {
185
+ opacity: 1;
186
+ transform: translateX(-50%) scaleY(1);
187
+ pointer-events: auto;
188
+ }
189
+ .volume-container button {
190
+ position: relative;
191
+ z-index: 2;
192
+ }
193
+ .volume-slider {
194
+ left: 50%;
195
+ /* bleibt, damit der Fader mittig über dem Button ist */
196
+ }
197
+ @media (hover: none) and (pointer: coarse) {
198
+ .volume-slider { display: none !important; }
199
+ }
200
+ .controls-right {
201
+ display: flex;
202
+ align-items: center;
203
+ gap: 0.5em;
204
+ flex-shrink: 0;
205
+ transition: none;
206
+ }
207
+ button, .icon-btn {
208
+ background: rgba(255,255,255,0.08);
209
+ border: none;
210
+ color: var(--accent);
211
+ border-radius: 50%;
212
+ width: 2.6em;
213
+ height: 2.6em;
214
+ display: flex;
215
+ align-items: center;
216
+ justify-content: center;
217
+ cursor: pointer;
218
+ transition: background 0.2s, box-shadow 0.2s, transform 0.15s;
219
+ box-shadow: 0 2px 8px rgba(0,0,0,0.08);
220
+ outline: none;
221
+ position: relative;
222
+ }
223
+ button:focus, .icon-btn:focus {
224
+ outline: var(--focus-outline);
225
+ outline-offset: 2px;
226
+ z-index: 2;
227
+ }
228
+ button:hover, .icon-btn:hover {
229
+ background: rgba(79,195,247,0.18);
230
+ transform: scale(1.08);
231
+ }
232
+ .progress {
233
+ flex: 1;
234
+ height: 6px;
235
+ background: rgba(255,255,255,0.18);
236
+ border-radius: 3px;
237
+ margin: 0 0.7em;
238
+ position: relative;
239
+ cursor: pointer;
240
+ box-shadow: 0 1px 4px rgba(0,0,0,0.08);
241
+ }
242
+ .buffer-bar {
243
+ position: absolute;
244
+ top: 0; left: 0;
245
+ height: 100%;
246
+ background: rgba(255,255,255,0.25);
247
+ width: 0%;
248
+ z-index: 1;
249
+ border-radius: 3px;
250
+ }
251
+ .progress-bar {
252
+ position: absolute;
253
+ top: 0; left: 0;
254
+ height: 100%;
255
+ background: var(--primary);
256
+ width: 0%;
257
+ border-radius: 3px;
258
+ z-index: 2;
259
+ transition: width 0.2s linear;
260
+ }
261
+ .scrub-knob {
262
+ position: absolute;
263
+ top: 50%;
264
+ left: 0;
265
+ width: 16px;
266
+ height: 16px;
267
+ background: var(--primary);
268
+ border-radius: 50%;
269
+ transform: translate(-50%, -50%);
270
+ z-index: 3;
271
+ cursor: pointer;
272
+ box-shadow: 0 2px 8px rgba(79,195,247,0.18);
273
+ border: 2px solid #fff;
274
+ transition: transform 0.2s, box-shadow 0.2s;
275
+ }
276
+ .scrub-knob:hover, .scrub-knob.dragging {
277
+ transform: translate(-50%, -50%) scale(1.2);
278
+ box-shadow: 0 4px 16px rgba(79,195,247,0.28);
279
+ }
280
+ .overlay {
281
+ position: absolute;
282
+ top: 0; left: 0; right: 0; bottom: 0;
283
+ background: rgba(30,34,44,0.32);
284
+ color: var(--accent);
285
+ display: flex;
286
+ justify-content: center;
287
+ align-items: center;
288
+ font-size: 1.2em;
289
+ text-align: center;
290
+ z-index: 5;
291
+ opacity: 0;
292
+ pointer-events: none;
293
+ transition: opacity 0.3s;
294
+ backdrop-filter: blur(8px);
295
+ border-radius: var(--border-radius);
296
+ }
297
+ .overlay.visible {
298
+ opacity: 1;
299
+ }
300
+ .big-controls {
301
+ display: none;
302
+ position: absolute;
303
+ top: 50%;
304
+ left: 50%;
305
+ transform: translate(-50%, -50%);
306
+ z-index: 15;
307
+ gap: 1.5em;
308
+ justify-content: center;
309
+ align-items: center;
310
+ opacity: 0;
311
+ transition: opacity 0.3s, transform 0.3s;
312
+ /* pointer-events: none; entfernt, damit Buttons wieder funktionieren */
313
+ }
314
+ .big-controls-visible .big-controls {
315
+ display: flex;
316
+ opacity: 1;
317
+ transform: translate(-50%, -50%) scale(1);
318
+ }
319
+ .big-controls button {
320
+ background: rgba(255,255,255,0.10);
321
+ border: none;
322
+ color: var(--primary);
323
+ font-size: 2.5em;
324
+ width: 3.5em;
325
+ height: 3.5em;
326
+ border-radius: 50%;
327
+ cursor: pointer;
328
+ display: flex;
329
+ justify-content: center;
330
+ align-items: center;
331
+ box-shadow: 0 2px 12px rgba(0,0,0,0.10);
332
+ transition: background 0.2s, transform 0.15s;
333
+ }
334
+ .big-controls button:hover {
335
+ background: rgba(79,195,247,0.18);
336
+ color: var(--primary-dark);
337
+ transform: scale(1.08);
338
+ }
339
+ .video-title {
340
+ position: absolute;
341
+ top: 1.2em;
342
+ left: 1.2em;
343
+ color: var(--accent);
344
+ background: rgba(30,34,44,0.38);
345
+ padding: 0.4em 1.2em;
346
+ border-radius: 1em;
347
+ font-size: 1em;
348
+ z-index: 15;
349
+ display: none;
350
+ box-shadow: 0 2px 8px rgba(0,0,0,0.10);
351
+ backdrop-filter: blur(8px);
352
+ }
353
+ .video-title.visible {
354
+ display: block;
355
+ }
356
+ .hide-cursor {
357
+ cursor: none;
358
+ }
359
+ .branding {
360
+ color: var(--primary);
361
+ font-size: 1em;
362
+ opacity: 0.7;
363
+ font-weight: 600;
364
+ letter-spacing: 0.04em;
365
+ text-shadow: 0 2px 8px rgba(79,195,247,0.18);
366
+ }
367
+ input[type="range"] {
368
+ accent-color: var(--primary);
369
+ border-radius: 6px;
370
+ background: rgba(255,255,255,0.10);
371
+ height: 4px;
372
+ margin: 0 0.5em;
373
+ outline: none;
374
+ transition: background 0.2s;
375
+ }
376
+ input[type="range"]::-webkit-slider-thumb {
377
+ border-radius: 50%;
378
+ background: var(--primary);
379
+ width: 16px;
380
+ height: 16px;
381
+ box-shadow: 0 2px 8px rgba(79,195,247,0.18);
382
+ border: 2px solid #fff;
383
+ transition: transform 0.2s;
384
+ }
385
+ input[type="range"]:hover::-webkit-slider-thumb {
386
+ transform: scale(1.15);
387
+ }
388
+ .spinner {
389
+ width: 40px;
390
+ height: 40px;
391
+ border: 4px solid rgba(255,255,255,0.18);
392
+ border-top: 4px solid var(--primary);
393
+ border-radius: 50%;
394
+ animation: spin 1s linear infinite;
395
+ }
396
+ .spinner-overlay {
397
+ position: absolute;
398
+ top: 0; left: 0; right: 0; bottom: 0;
399
+ display: none;
400
+ justify-content: center;
401
+ align-items: center;
402
+ background: rgba(30,34,44,0.32);
403
+ z-index: 8; /* NEU: Unter der Steuerleiste (controls: z-index 10), über Video */
404
+ backdrop-filter: blur(8px);
405
+ border-radius: var(--border-radius);
406
+ }
407
+ .spinner-overlay.visible {
408
+ display: flex;
409
+ }
410
+ @keyframes spin {
411
+ from { transform: rotate(0deg); }
412
+ to { transform: rotate(360deg); }
413
+ }
414
+ .xplayer-context-menu {
415
+ position: fixed;
416
+ z-index: 99999;
417
+ background: var(--glass-bg);
418
+ color: var(--accent);
419
+ border-radius: var(--border-radius);
420
+ box-shadow: var(--shadow);
421
+ min-width: 180px;
422
+ max-width: 240px;
423
+ padding: 0.5em 0;
424
+ font-size: 1em;
425
+ display: none;
426
+ flex-direction: column;
427
+ animation: fadeIn 0.15s;
428
+ overflow: hidden;
429
+ backdrop-filter: blur(var(--glass-blur));
430
+ border: 1.5px solid rgba(255,255,255,0.12);
431
+ }
432
+ .xplayer-context-menu.visible {
433
+ display: flex;
434
+ }
435
+ .xplayer-context-menu button {
436
+ background: none;
437
+ border: none;
438
+ color: inherit;
439
+ text-align: left;
440
+ width: 100%;
441
+ box-sizing: border-box;
442
+ border-radius: 0;
443
+ padding: 0.7em 1.2em;
444
+ font: inherit;
445
+ cursor: pointer;
446
+ transition: background 0.15s;
447
+ margin: 0;
448
+ outline: none;
449
+ }
450
+ .xplayer-context-menu button:hover, .xplayer-context-menu button:focus {
451
+ background: rgba(79,195,247,0.10);
452
+ }
453
+ @keyframes fadeIn {
454
+ from { opacity: 0; transform: scale(0.98);}
455
+ to { opacity: 1; transform: scale(1);}
456
+ }
457
+ .xplayer-dialog-backdrop {
458
+ position: absolute;
459
+ z-index: 2147483647;
460
+ top: 0; left: 0; right: 0; bottom: 0;
461
+ width: 100%;
462
+ height: 100%;
463
+ background: rgba(30,34,44,0.32);
464
+ display: none;
465
+ align-items: center;
466
+ justify-content: center;
467
+ pointer-events: auto;
468
+ backdrop-filter: blur(8px);
469
+ border-radius: var(--border-radius);
470
+ }
471
+ .xplayer-dialog-backdrop.visible {
472
+ display: flex;
473
+ }
474
+ .xplayer-dialog {
475
+ background: var(--glass-bg);
476
+ color: var(--accent);
477
+ border-radius: var(--border-radius);
478
+ padding: 2em 2em 1.5em 2em;
479
+ min-width: 240px;
480
+ max-width: 90vw;
481
+ box-shadow: var(--shadow);
482
+ text-align: center;
483
+ position: relative;
484
+ margin: 0 auto;
485
+ word-break: break-word;
486
+ z-index: 2147483648;
487
+ backdrop-filter: blur(var(--glass-blur));
488
+ border: 1.5px solid rgba(255,255,255,0.12);
489
+ }
490
+ .xplayer-dialog button {
491
+ margin-top: 1.5em;
492
+ background: var(--primary);
493
+ color: #222;
494
+ border: none;
495
+ border-radius: 2em;
496
+ padding: 0.5em 2em;
497
+ font-size: 1em;
498
+ cursor: pointer;
499
+ transition: background 0.2s, color 0.2s;
500
+ box-shadow: 0 2px 8px rgba(79,195,247,0.10);
501
+ }
502
+ .xplayer-dialog button:hover {
503
+ background: var(--primary-dark);
504
+ color: #fff;
505
+ }
506
+ .focus-visible {
507
+ outline: var(--focus-outline);
508
+ outline-offset: 2px;
509
+ }
510
+ /* Responsive */
511
+ @media (max-width: 600px) {
512
+ .controls { left: 0.5em; right: 0.5em; bottom: 0.5em; padding: 0.5em 0.5em; }
513
+ .video-title { left: 0.5em; top: 0.5em; font-size: 0.95em; padding: 0.3em 0.7em; }
514
+ }
515
+ /* Lautstärkeleiste: Standardmäßig versteckt, fährt bei Hover über das Lautsprecher-Icon aus */
516
+ .volume-container {
517
+ display: flex;
518
+ align-items: center;
519
+ position: relative;
520
+ width: 2.6em; /* Nur Icon sichtbar */
521
+ min-width: 2.6em;
522
+ max-width: 2.6em;
523
+ height: 2.6em;
524
+ overflow: visible;
525
+ z-index: 2;
526
+ flex-shrink: 0;
527
+ }
528
+ .volume-container.expanded {
529
+ /* Keine Breitenänderung mehr! */
530
+ }
531
+ .volume-slider {
532
+ position: absolute;
533
+ left: 50%;
534
+ bottom: 100%;
535
+ transform: translateX(-50%) scaleY(0);
536
+ transform-origin: bottom center;
537
+ width: 36px;
538
+ height: 80px;
539
+ opacity: 0;
540
+ pointer-events: none;
541
+ transition: opacity 0.2s, transform 0.25s cubic-bezier(.4,1.4,.6,1);
542
+ background: rgba(30,34,44,0.85);
543
+ border-radius: 1em;
544
+ box-shadow: 0 4px 16px rgba(0,0,0,0.18);
545
+ z-index: 20;
546
+ display: flex;
547
+ align-items: center;
548
+ justify-content: center;
549
+ padding: 0.7em 0;
550
+ }
551
+ .volume-container.expanded .volume-slider {
552
+ opacity: 1;
553
+ transform: translateX(-50%) scaleY(1);
554
+ pointer-events: auto;
555
+ }
556
+ .volume-slider input[type="range"] {
557
+ writing-mode: vertical-lr;
558
+ direction: rtl;
559
+ width: 100%;
560
+ height: 70px;
561
+ margin: 0;
562
+ background: transparent;
563
+ }
564
+ @media (hover: none) and (pointer: coarse) {
565
+ .volume-slider { display: none !important; }
566
+ }
567
+ .media-container {
568
+ width: 100%;
569
+ height: 100%;
570
+ display: flex;
571
+ align-items: center;
572
+ justify-content: center;
573
+ position: relative;
574
+ }
575
+ video, audio {
576
+ width: 100%;
577
+ height: 100%;
578
+ object-fit: cover;
579
+ display: block;
580
+ border-radius: var(--border-radius);
581
+ background: #111;
582
+ }
583
+ video[poster] {
584
+ object-fit: cover;
585
+ }
586
+ @media (prefers-reduced-motion: reduce) {
587
+ .player, .controls, .overlay, .big-controls, .spinner, .xplayer-context-menu {
588
+ animation: none !important;
589
+ transition: none !important;
590
+ }
591
+ }
592
+ @media (forced-colors: active) {
593
+ .player, .controls, button, .icon-btn, .progress {
594
+ border: 1px solid CanvasText;
595
+ }
596
+ }
597
+ </style>
598
+ <div class="player" part="root" tabindex="0" role="region" aria-label="Media Player">
599
+ <div id="media-container" part="media" class="media-container"></div>
600
+ <div class="video-title" part="title" id="video-title" aria-live="polite"></div>
601
+ <div class="overlay" part="overlay" id="overlay" aria-hidden="true"></div>
602
+ <div class="spinner-overlay" part="spinner-overlay" id="spinner-overlay" aria-live="polite" aria-busy="false">
603
+ <div class="spinner" part="spinner" aria-label="Loading"></div>
604
+ </div>
605
+ <div class="big-controls" part="big-controls" id="big-controls" aria-label="Main Controls" role="group">
606
+ <button id="backward" aria-label="10 Sekunden zurück" tabindex="0" class="icon-btn">${svgIcon('backward')}</button>
607
+ <button id="big-play" aria-label="Abspielen/Pause" tabindex="0" class="icon-btn">${svgIcon('play')}</button>
608
+ <button id="forward" aria-label="10 Sekunden vor" tabindex="0" class="icon-btn">${svgIcon('forward')}</button>
609
+ </div>
610
+ <div class="controls" part="controls" role="group" aria-label="Player Controls">
611
+ <div class="controls-left">
612
+ <button id="play" aria-label="Abspielen/Pause" tabindex="0" class="icon-btn">${svgIcon('play')}</button>
613
+ <div class="time" aria-hidden="true">
614
+ <span id="current">0:00</span> / <span id="duration">0:00</span>
615
+ </div>
616
+ </div>
617
+ <div class="progress" part="progress" id="seekbar" aria-label="Position" role="slider" tabindex="0" aria-valuemin="0" aria-valuemax="100" aria-valuenow="0">
618
+ <div class="buffer-bar" id="buffer"></div>
619
+ <div class="progress-bar" id="bar"></div>
620
+ <div class="scrub-knob" id="knob" tabindex="0" aria-label="Scrub"></div>
621
+ </div>
622
+ <div class="controls-stack">
623
+ <div class="volume-container">
624
+ <button id="mute" aria-label="Stumm schalten" tabindex="0" class="icon-btn">${svgIcon('volume')}</button>
625
+ <div class="volume-slider">
626
+ <input type="range" id="volume" min="0" max="1" step="0.01" value="1" aria-label="Lautstärke" tabindex="0">
627
+ </div>
628
+ </div>
629
+ <div class="controls-right">
630
+ <button id="subtitles" aria-label="Untertitel umschalten" tabindex="0" class="icon-btn">${svgIcon('cc')}</button>
631
+ <button id="pip" aria-label="Picture-in-Picture" tabindex="0" class="icon-btn">${svgIcon('pip')}</button>
632
+ <button id="fullscreen" aria-label="Vollbild" tabindex="0" class="icon-btn">${svgIcon('fullscreen')}</button>
633
+ <div id="chooser-container" style="display:none"></div>
634
+ <div id="download-container"></div>
635
+ <div class="branding" aria-hidden="true">XPlayer</div>
636
+ </div>
637
+ </div>
638
+ </div>
639
+ <div class="xplayer-context-menu" id="xplayer-context-menu" role="menu" aria-label="Kontextmenü">
640
+ <button id="xplayer-about-btn" role="menuitem" tabindex="0">About XPlayer...</button>
641
+ </div>
642
+ <div class="xplayer-dialog-backdrop" id="xplayer-dialog-backdrop" aria-modal="true" role="dialog">
643
+ <div class="xplayer-dialog">
644
+ <div style="font-size:1.2em;margin-bottom:1em;">XPlayer V0.1 Beta</div>
645
+ <div style="margin-bottom:1em;">XPlayer is part of XTend, a free and open source web framework.</div>
646
+ <button id="xplayer-dialog-close" tabindex="0">OK</button>
647
+ </div>
648
+ </div>
649
+ </div>
650
+ `;
651
+ }
652
+
653
+ connectedCallback() {
654
+ this._loadMedia();
655
+ this._initControls();
656
+ this._toggleControlsVisibility();
657
+ this._setupChooser();
658
+ this._setupDownload();
659
+ this._updateOverlay();
660
+ this._setupKeyboardControls();
661
+ this._setupBigControls();
662
+ this._setupCursorHiding();
663
+ this._updateDimensions();
664
+
665
+ // Add the loaded class to trigger branding animation
666
+ this.classList.add("loaded");
667
+
668
+ // State Management: Eindeutige ID für diesen Player
669
+ if (!this.id) this.id = `xplayer-${Math.random().toString(36).slice(2, 10)}`;
670
+
671
+ // Initialen State setzen
672
+ xstate.set(`xplayer-state-${this.id}`, {
673
+ src: this.getAttribute("src"),
674
+ playing: false,
675
+ currentTime: 0,
676
+ volume: 1,
677
+ muted: false,
678
+ fullscreen: false
679
+ });
680
+
681
+ this._internalStateUpdate = false; // <--- NEU: Flag für interne Updates
682
+
683
+ // State-Änderungen abonnieren (z.B. externe Steuerung)
684
+ this._unsubscribeState = xstate.subscribe((key, value) => {
685
+ if (key !== `xplayer-state-${this.id}`) return; // Nur auf eigenen State reagieren!
686
+ if (this._dialogOpen) return;
687
+ if (typeof value === "object" && this._media) {
688
+ if (this._internalStateUpdate) return;
689
+
690
+ if (value.src && value.src !== this._media.src) {
691
+ this.setAttribute("src", value.src);
692
+ }
693
+ if (typeof value.playing === "boolean") {
694
+ if (value.playing !== !this._media.paused) {
695
+ if (value.playing) {
696
+ this._media.play().catch(() => {});
697
+ } else {
698
+ this._media.pause();
699
+ }
700
+ }
701
+ }
702
+ if (typeof value.currentTime === "number" && Math.abs(this._media.currentTime - value.currentTime) > 0.5) {
703
+ this._media.currentTime = value.currentTime;
704
+ }
705
+ if (typeof value.volume === "number" && this._media.volume !== value.volume) {
706
+ this._media.volume = value.volume;
707
+ }
708
+ if (typeof value.muted === "boolean" && this._media.muted !== value.muted) {
709
+ this._media.muted = value.muted;
710
+ }
711
+ if (typeof value.fullscreen === "boolean") {
712
+ const isFullscreen = this.classList.contains("fullscreen");
713
+ if (value.fullscreen && !isFullscreen) {
714
+ this.shadowRoot.querySelector(".player").requestFullscreen();
715
+ } else if (!value.fullscreen && isFullscreen) {
716
+ document.exitFullscreen();
717
+ }
718
+ }
719
+ }
720
+ });
721
+
722
+ // Fullscreen-Handling optimieren
723
+ document.addEventListener("fullscreenchange", () => {
724
+ const player = this.shadowRoot.querySelector(".player");
725
+ const isFullscreen = !!document.fullscreenElement;
726
+
727
+ // 1. Dimensionen setzen
728
+ this._updateDimensions();
729
+
730
+ // 2. Spinner/Overlay nur anzeigen, wenn Video wirklich lädt oder pausiert ist
731
+ if (this._media && !this._media.paused && !this._media.seeking && !this._media.ended) {
732
+ this._hideOverlay();
733
+ const spinner = this.shadowRoot.querySelector("#spinner-overlay");
734
+ if (spinner) spinner.classList.remove("visible");
735
+ }
736
+
737
+ // 2b. Big Controls im Fullscreen bei laufender Wiedergabe ausblenden
738
+ if (this._media && !this._media.paused) {
739
+ player.classList.remove("big-controls-visible");
740
+ }
741
+
742
+ // 3. Endlosschleife verhindern: State nur setzen, wenn sich wirklich etwas ändert
743
+ if (!this._internalFullscreenChange) {
744
+ const prevState = xstate.get(`xplayer-state-${this.id}`);
745
+ if (prevState && prevState.fullscreen !== isFullscreen) {
746
+ this._internalStateUpdate = true;
747
+ xstate.set(`xplayer-state-${this.id}`, {
748
+ ...prevState,
749
+ fullscreen: isFullscreen
750
+ });
751
+ this._internalStateUpdate = false;
752
+ }
753
+ }
754
+
755
+ // 4. CSS-Klasse setzen/entfernen
756
+ if (isFullscreen) {
757
+ this.classList.add("fullscreen");
758
+ } else {
759
+ this.classList.remove("fullscreen");
760
+ }
761
+ });
762
+
763
+ // Kontextmenü-Logik
764
+ const player = this.shadowRoot.querySelector(".player");
765
+ const contextMenu = this.shadowRoot.querySelector("#xplayer-context-menu");
766
+ const aboutBtn = this.shadowRoot.querySelector("#xplayer-about-btn");
767
+ const controls = this.shadowRoot.querySelector(".controls"); // <-- HIER hinzufügen
768
+
769
+ // Kontextmenü anzeigen
770
+ player.addEventListener("contextmenu", (e) => {
771
+ e.preventDefault();
772
+ e.stopPropagation();
773
+
774
+ contextMenu.style.display = "flex";
775
+ contextMenu.classList.add("visible");
776
+
777
+ contextMenu.style.position = "absolute";
778
+ const playerRect = player.getBoundingClientRect();
779
+ const menuRect = contextMenu.getBoundingClientRect();
780
+
781
+ let left = e.clientX - playerRect.left;
782
+ let top = e.clientY - playerRect.top;
783
+
784
+ if (left + menuRect.width > playerRect.width) {
785
+ left = playerRect.width - menuRect.width;
786
+ }
787
+ if (top + menuRect.height > playerRect.height) {
788
+ top = playerRect.height - menuRect.height;
789
+ }
790
+ if (left < 0) left = 0;
791
+ if (top < 0) top = 0;
792
+
793
+ contextMenu.style.left = left + "px";
794
+ contextMenu.style.top = top + "px";
795
+
796
+ // Schließen bei Klick außerhalb
797
+ const closeMenu = (ev) => {
798
+ if (!contextMenu.contains(ev.target)) {
799
+ contextMenu.classList.remove("visible");
800
+ contextMenu.style.display = "none";
801
+ window.removeEventListener("mousedown", closeMenu, true);
802
+ }
803
+ };
804
+ window.addEventListener("mousedown", closeMenu, true);
805
+ });
806
+
807
+ // Klicks im Kontextmenü nicht an Player weiterreichen
808
+ contextMenu.addEventListener("pointerdown", (e) => {
809
+ e.stopPropagation();
810
+ e.preventDefault();
811
+ });
812
+ // About-Dialog öffnen (XDialog API im globalen Scope)
813
+ let dialogOpen = false; // Flag für Dialog-Status (nur lokal, für setTimeout-Logik)
814
+ aboutBtn.addEventListener("pointerdown", function(e) {
815
+ e.stopPropagation();
816
+ e.preventDefault();
817
+ if (this._dialogOpen) return; // <--- Endlosschutz global
818
+ this._dialogOpen = true;
819
+ contextMenu.classList.remove("visible");
820
+ contextMenu.style.display = "none";
821
+ let tries = 0;
822
+ function getXDialog() {
823
+ return (window.XDialog && typeof window.XDialog.show === "function") ? window.XDialog :
824
+ (window.top && window.top.XDialog && typeof window.top.XDialog.show === "function") ? window.top.XDialog :
825
+ (window.parent && window.parent.XDialog && typeof window.parent.XDialog.show === "function") ? window.parent.XDialog : null;
826
+ }
827
+ function tryShowDialog() {
828
+ const XDialogAPI = getXDialog();
829
+ if (XDialogAPI) {
830
+ const dialogId = XDialogAPI.show({
831
+ title: "XPlayer V0.1 Beta",
832
+ content: "XPlayer is part of XTend, a free and open source web framework.",
833
+ actions: [{ label: "OK", primary: true, callback: () => { this._dialogOpen = false; } }]
834
+ });
835
+ // Optional: State-Subscription für Dialog schließen
836
+ const unsub = xstate.subscribe((key, value) => {
837
+ if (key === `dialog-open-${dialogId}` && value === false) {
838
+ this._dialogOpen = false;
839
+ if (typeof unsub === 'function') unsub();
840
+ }
841
+ });
842
+ } else if (tries < 10) {
843
+ tries++;
844
+ setTimeout(tryShowDialog.bind(this), 100);
845
+ } else {
846
+ // Fallback: Shadow DOM Dialog anzeigen
847
+ const dialogBackdrop = this.shadowRoot.querySelector("#xplayer-dialog-backdrop");
848
+ if (dialogBackdrop) {
849
+ dialogBackdrop.classList.add("visible");
850
+ const closeBtn = this.shadowRoot.querySelector("#xplayer-dialog-close");
851
+ if (closeBtn) {
852
+ closeBtn.focus();
853
+ closeBtn.onclick = () => {
854
+ dialogBackdrop.classList.remove("visible");
855
+ this._dialogOpen = false;
856
+ };
857
+ }
858
+ } else {
859
+ alert("XPlayer V0.1 Beta\n\nXPlayer is part of XTend, a free and open source web framework.");
860
+ this._dialogOpen = false;
861
+ }
862
+ }
863
+ }
864
+ tryShowDialog.call(this);
865
+ }.bind(this))
866
+
867
+ // --- Fix: Player-Click-Handler blockiert, wenn Kontextmenü offen ---
868
+ this._playerClickHandler = (e) => {
869
+ if (e.defaultPrevented) return;
870
+ // 1. Kontextmenü offen? Dann nie toggeln!
871
+ if (contextMenu && contextMenu.classList.contains("visible")) {
872
+ if (contextMenu.contains(e.target)) return;
873
+ return;
874
+ }
875
+ // 2. Klick auf Steuerelemente? Dann nie toggeln!
876
+ if (
877
+ controls &&
878
+ (controls.contains(e.target) ||
879
+ e.target.closest(".controls") ||
880
+ e.target.closest(".big-controls") ||
881
+ e.target.closest("#chooser-container") ||
882
+ e.target.closest("#download-container") ||
883
+ e.target.closest(".xplayer-context-menu"))
884
+ ) {
885
+ return;
886
+ }
887
+ // 3. Klick auf den Player-Hintergrund oder Video/Audio? -> Toggle Play/Pause
888
+ if (
889
+ this._media.muted &&
890
+ this.hasAttribute("autoplay") &&
891
+ !this._autoplayUnmuted &&
892
+ this._media.paused
893
+ ) {
894
+ this._media.muted = false;
895
+ this._autoplayUnmuted = true;
896
+ }
897
+ if (this._media.paused) {
898
+ this._media.play();
899
+ } else {
900
+ this._media.pause();
901
+ }
902
+ };
903
+
904
+ player.addEventListener("click", this._playerClickHandler);
905
+ }
906
+
907
+ disconnectedCallback() {
908
+ if (this._unsubscribeState) this._unsubscribeState();
909
+ }
910
+
911
+ snapshot() {
912
+ return {
913
+ schema: "xtend.component.layout-display-media-snapshot.v1",
914
+ componentRef: "x-player",
915
+ stateKey: `xplayer-state-${this.id}`,
916
+ schedule: "media.lazy.load",
917
+ src: this.getAttribute("src"),
918
+ type: this.getAttribute("type") || "video",
919
+ playing: this._media ? !this._media.paused : false,
920
+ currentTime: this._media ? this._media.currentTime : 0
921
+ };
922
+ }
923
+
924
+ attributeChangedCallback(name, oldValue, newValue) {
925
+ if (["src", "poster", "type", "media-chooser", "downloadable", "title"].includes(name)) {
926
+ this._loadMedia();
927
+ if (name === "title") {
928
+ this._updateOverlay();
929
+ }
930
+ }
931
+
932
+ if (name === "autoplay" && this._media) {
933
+ if (newValue !== null) {
934
+ this._media.play().then(() => {
935
+ this.updatePlayPauseIcon(); // Ensure play button updates on autoplay
936
+ }).catch(() => {}); // Fehler ignorieren, falls play() abgebrochen wird
937
+ } else {
938
+ this._media.pause();
939
+ }
940
+ }
941
+
942
+ if (["height", "width"].includes(name)) {
943
+ this._updateDimensions(); // Dynamically update dimensions
944
+ }
945
+ }
946
+
947
+ _setupBigControls() {
948
+ const bigPlay = this.shadowRoot.querySelector("#big-play");
949
+ const backward = this.shadowRoot.querySelector("#backward");
950
+ const forward = this.shadowRoot.querySelector("#forward");
951
+ const player = this.shadowRoot.querySelector(".player");
952
+ const media = this._media;
953
+
954
+ // Replay SVG
955
+ const svgReplay = () => `<svg width="1em" height="1em" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M12 5V2L7 6.5L12 11V8C15.31 8 18 10.69 18 14C18 17.31 15.31 20 12 20C8.69 20 6 17.31 6 14H4C4 18.42 7.58 22 12 22C16.42 22 20 18.42 20 14C20 9.58 16.42 6 12 6V5Z" fill="currentColor"/></svg>`;
956
+
957
+ function updateBigPlay() {
958
+ if (media.ended) {
959
+ bigPlay.innerHTML = svgReplay();
960
+ bigPlay.setAttribute('aria-label', 'Neustart');
961
+ bigPlay.onclick = () => {
962
+ media.currentTime = 0;
963
+ media.play();
964
+ };
965
+ } else {
966
+ bigPlay.innerHTML = media.paused ? svgIcon('play') : svgIcon('pause');
967
+ bigPlay.setAttribute('aria-label', media.paused ? 'Abspielen' : 'Pause');
968
+ bigPlay.onclick = () => {
969
+ if (media.paused) {
970
+ media.play();
971
+ } else {
972
+ media.pause();
973
+ }
974
+ };
975
+ }
976
+ }
977
+
978
+ updateBigPlay();
979
+ media.addEventListener('ended', updateBigPlay);
980
+ media.addEventListener('play', updateBigPlay);
981
+ media.addEventListener('pause', updateBigPlay);
982
+
983
+ backward.onclick = () => {
984
+ media.currentTime = Math.max(media.currentTime - 10, 0);
985
+ };
986
+ forward.onclick = () => {
987
+ media.currentTime = Math.min(media.currentTime + 10, media.duration);
988
+ };
989
+ }
990
+
991
+ _updateOverlay() {
992
+ const titleElement = this.shadowRoot.querySelector("#video-title");
993
+ const title = this.getAttribute("title") || this._getDefaultTitle();
994
+ titleElement.textContent = title;
995
+ }
996
+
997
+ _getDefaultTitle() {
998
+ const src = this.getAttribute("src");
999
+ if (!src) return "Untitled Video";
1000
+ const fileName = src.split("/").pop();
1001
+ return fileName || "Untitled Video";
1002
+ }
1003
+
1004
+ _loadMedia() {
1005
+ const container = this.shadowRoot.querySelector("#media-container");
1006
+ container.innerHTML = "";
1007
+
1008
+ const type = this.getAttribute("type") === "audio" ? "audio" : "video";
1009
+ const media = document.createElement(type);
1010
+ media.controls = false;
1011
+ media.src = this.getAttribute("src") || "";
1012
+ media.removeAttribute("title"); // Tooltip entfernen (direkt nach Erzeugung)
1013
+ if (this.hasAttribute("poster") && type === "video") media.poster = this.getAttribute("poster");
1014
+ if (this.hasAttribute("loop")) media.loop = true;
1015
+
1016
+ // Add playsinline attributes for mobile-friendliness (especially iOS)
1017
+ if (type === "video") {
1018
+ media.setAttribute("playsinline", "");
1019
+ media.setAttribute("webkit-playsinline", "");
1020
+ }
1021
+
1022
+ const spinnerOverlay = this.shadowRoot.querySelector("#spinner-overlay");
1023
+ let hasPlayed = false; // <-- Add this flag
1024
+
1025
+ const showSpinner = () => {
1026
+ const spinnerOverlay = this.shadowRoot.querySelector("#spinner-overlay");
1027
+ const overlay = this.shadowRoot.querySelector("#overlay");
1028
+ const title = this.shadowRoot.querySelector("#video-title");
1029
+ if (spinnerOverlay) spinnerOverlay.classList.add("visible");
1030
+ if (overlay) overlay.classList.remove("visible");
1031
+ if (title) title.classList.remove("visible");
1032
+ xstate.set(`xplayer-spinner-${this.id}`, true);
1033
+ };
1034
+
1035
+ const hideSpinner = () => {
1036
+ const spinnerOverlay = this.shadowRoot.querySelector("#spinner-overlay");
1037
+ if (spinnerOverlay) spinnerOverlay.classList.remove("visible");
1038
+ xstate.set(`xplayer-spinner-${this.id}`, false);
1039
+ };
1040
+
1041
+ media.addEventListener("waiting", showSpinner);
1042
+ media.addEventListener("playing", hideSpinner);
1043
+ media.addEventListener("canplay", hideSpinner);
1044
+ media.addEventListener("loadedmetadata", () => {
1045
+ media.removeAttribute("title");
1046
+ media.title = "";
1047
+ });
1048
+ // --- Workaround: title-Attribut regelmäßig entfernen und leeren ---
1049
+ if (this._removeTitleInterval) clearInterval(this._removeTitleInterval);
1050
+ this._removeTitleInterval = setInterval(() => {
1051
+ if (!media.isConnected) {
1052
+ clearInterval(this._removeTitleInterval);
1053
+ this._removeTitleInterval = null;
1054
+ } else {
1055
+ media.removeAttribute('title');
1056
+ media.title = "";
1057
+ }
1058
+ }, 500);
1059
+
1060
+ if (this.hasAttribute("autoplay")) {
1061
+ media.muted = true;
1062
+ media.autoplay = true;
1063
+ media.play().then(() => {
1064
+ this._hideOverlay();
1065
+ }).catch((err) => {
1066
+ // Only show overlay if autoplay fails
1067
+ this._showOverlay();
1068
+ hideSpinner();
1069
+ console.warn("Autoplay failed:", err.message);
1070
+ });
1071
+ }
1072
+
1073
+ media.addEventListener("play", () => {
1074
+ hasPlayed = true; // <-- Mark as played
1075
+ this.updatePlayPauseIcon();
1076
+ this._hideOverlay();
1077
+
1078
+ this._internalStateUpdate = true;
1079
+ const prev = xstate.get(`xplayer-state-${this.id}`) || {};
1080
+ if (!prev.playing) {
1081
+ xstate.set(`xplayer-state-${this.id}`, {
1082
+ ...prev,
1083
+ playing: true,
1084
+ currentTime: media.currentTime
1085
+ });
1086
+ }
1087
+ this._internalStateUpdate = false;
1088
+ });
1089
+
1090
+ media.addEventListener("pause", () => {
1091
+ if (hasPlayed) this._showOverlay();
1092
+
1093
+ this._internalStateUpdate = true;
1094
+ const prev = xstate.get(`xplayer-state-${this.id}`) || {};
1095
+ if (prev.playing) {
1096
+ xstate.set(`xplayer-state-${this.id}`, {
1097
+ ...prev,
1098
+ playing: false,
1099
+ currentTime: media.currentTime
1100
+ });
1101
+ }
1102
+ this._internalStateUpdate = false;
1103
+ });
1104
+
1105
+ media.addEventListener("seeked", () => {
1106
+ this._internalStateUpdate = true;
1107
+ const prev = xstate.get(`xplayer-state-${this.id}`) || {};
1108
+ if (Math.abs((prev.currentTime || 0) - media.currentTime) > 0.5) {
1109
+ xstate.set(`xplayer-state-${this.id}`, {
1110
+ ...prev,
1111
+ currentTime: media.currentTime
1112
+ });
1113
+ }
1114
+ this._internalStateUpdate = false;
1115
+ });
1116
+
1117
+ media.addEventListener("volumechange", () => {
1118
+ this._internalStateUpdate = true;
1119
+ const prev = xstate.get(`xplayer-state-${this.id}`) || {};
1120
+ if (prev.volume !== media.volume || prev.muted !== media.muted) {
1121
+ xstate.set(`xplayer-state-${this.id}`, {
1122
+ ...prev,
1123
+ volume: media.volume,
1124
+ muted: media.muted
1125
+ });
1126
+ }
1127
+ this._internalStateUpdate = false;
1128
+ });
1129
+
1130
+ container.appendChild(media);
1131
+ this._media = media;
1132
+ this._initControls();
1133
+ this._toggleControlsVisibility();
1134
+ this._setupChooser();
1135
+ this._setupDownload();
1136
+ // Klick auf den Videobereich toggelt Play/Pause (außer auf Controls)
1137
+ const player = this.shadowRoot.querySelector(".player");
1138
+ const controls = this.shadowRoot.querySelector(".controls");
1139
+ const contextMenu = this.shadowRoot.querySelector("#xplayer-context-menu");
1140
+ // Vorherigen Listener entfernen, falls _loadMedia() mehrfach aufgerufen wird
1141
+ if (this._playerClickHandler) {
1142
+ player.removeEventListener("click", this._playerClickHandler);
1143
+ }
1144
+ this._playerClickHandler = (e) => {
1145
+ if (e.defaultPrevented) return;
1146
+ // 1. Kontextmenü offen? Dann nie toggeln!
1147
+ if (contextMenu && contextMenu.classList.contains("visible")) {
1148
+ if (contextMenu.contains(e.target)) return;
1149
+ return;
1150
+ }
1151
+ // 2. Klick auf Steuerelemente? Dann nie toggeln!
1152
+ if (
1153
+ controls &&
1154
+ (controls.contains(e.target) ||
1155
+ e.target.closest(".controls") ||
1156
+ e.target.closest(".big-controls") ||
1157
+ e.target.closest("#chooser-container") ||
1158
+ e.target.closest("#download-container") ||
1159
+ e.target.closest(".xplayer-context-menu"))
1160
+ ) {
1161
+ return;
1162
+ }
1163
+ // 3. Klick auf den Player-Hintergrund oder Video/Audio? -> Toggle Play/Pause
1164
+ if (
1165
+ this._media.muted &&
1166
+ this.hasAttribute("autoplay") &&
1167
+ !this._autoplayUnmuted &&
1168
+ this._media.paused
1169
+ ) {
1170
+ this._media.muted = false;
1171
+ this._autoplayUnmuted = true;
1172
+ }
1173
+ if (this._media.paused) {
1174
+ this._media.play();
1175
+ } else {
1176
+ this._media.pause();
1177
+ }
1178
+ };
1179
+ player.addEventListener("click", this._playerClickHandler);
1180
+ }
1181
+
1182
+ _showOverlay() {
1183
+ // Nur anzeigen, wenn das Video wirklich pausiert ist
1184
+ if (this._media && !this._media.paused) return;
1185
+ const overlay = this.shadowRoot.querySelector("#overlay");
1186
+ const spinner = this.shadowRoot.querySelector("#spinner-overlay");
1187
+ const title = this.shadowRoot.querySelector("#video-title");
1188
+ const player = this.shadowRoot.querySelector(".player");
1189
+ if (spinner && spinner.classList.contains("visible")) {
1190
+ // Spinner hat Vorrang, kein Pause-Overlay anzeigen
1191
+ overlay.classList.remove("visible");
1192
+ title.classList.remove("visible");
1193
+ if (player) player.classList.remove("big-controls-visible");
1194
+ return;
1195
+ }
1196
+ overlay.classList.add("visible");
1197
+ title.classList.add("visible");
1198
+ if (player) player.classList.add("big-controls-visible");
1199
+ }
1200
+
1201
+ _hideOverlay() {
1202
+ const overlay = this.shadowRoot.querySelector("#overlay");
1203
+ const title = this.shadowRoot.querySelector("#video-title");
1204
+ const player = this.shadowRoot.querySelector(".player");
1205
+ overlay.classList.remove("visible");
1206
+ title.classList.remove("visible"); // Hide title with overlay
1207
+ if (player) player.classList.remove("big-controls-visible");
1208
+ }
1209
+
1210
+ updatePlayPauseIcon() {
1211
+ const play = this.shadowRoot.querySelector("#play");
1212
+ const bigPlay = this.shadowRoot.querySelector("#big-play");
1213
+ const media = this._media;
1214
+ // Replay SVG
1215
+ const svgReplay = () => `<svg width="1em" height="1em" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M12 5V2L7 6.5L12 11V8C15.31 8 18 10.69 18 14C18 17.31 15.31 20 12 20C8.69 20 6 17.31 6 14H4C4 18.42 7.58 22 12 22C16.42 22 20 18.42 20 14C20 9.58 16.42 6 12 6V5Z" fill="currentColor"/></svg>`;
1216
+
1217
+ if (media.ended) {
1218
+ // Replay für BigButton
1219
+ if (bigPlay) {
1220
+ bigPlay.innerHTML = svgReplay();
1221
+ bigPlay.setAttribute('aria-label', 'Neustart');
1222
+ bigPlay.onclick = () => {
1223
+ media.currentTime = 0;
1224
+ media.play();
1225
+ };
1226
+ }
1227
+ // Replay für kleinen Button
1228
+ if (play) {
1229
+ play.innerHTML = svgReplay();
1230
+ play.setAttribute('aria-label', 'Neustart');
1231
+ play.onclick = () => {
1232
+ media.currentTime = 0;
1233
+ media.play();
1234
+ };
1235
+ }
1236
+ } else {
1237
+ // BigButton normal
1238
+ if (bigPlay) {
1239
+ bigPlay.innerHTML = media.paused ? svgIcon('play') : svgIcon('pause');
1240
+ bigPlay.setAttribute('aria-label', media.paused ? 'Abspielen' : 'Pause');
1241
+ bigPlay.onclick = () => {
1242
+ if (media.paused) {
1243
+ media.play();
1244
+ } else {
1245
+ media.pause();
1246
+ }
1247
+ };
1248
+ }
1249
+ // Kleiner Button normal
1250
+ if (play) {
1251
+ play.innerHTML = media.paused ? svgIcon('play') : svgIcon('pause');
1252
+ play.setAttribute('aria-label', media.paused ? 'Abspielen' : 'Pause');
1253
+ play.onclick = (e) => {
1254
+ e.preventDefault();
1255
+ if (media.paused) {
1256
+ media.play();
1257
+ this.dispatchEvent(new CustomEvent("xplayer-play", { detail: { currentTime: media.currentTime } }));
1258
+ } else {
1259
+ media.pause();
1260
+ this.dispatchEvent(new CustomEvent("xplayer-pause", { detail: { currentTime: media.currentTime } }));
1261
+ }
1262
+ this.updatePlayPauseIcon();
1263
+ };
1264
+ }
1265
+ }
1266
+ }
1267
+
1268
+ _initControls() {
1269
+ const media = this._media;
1270
+ const play = this.shadowRoot.querySelector("#play");
1271
+ const mute = this.shadowRoot.querySelector("#mute");
1272
+ const volume = this.shadowRoot.querySelector("#volume");
1273
+ const volumeContainer = this.shadowRoot.querySelector('.volume-container');
1274
+ const controlsStack = this.shadowRoot.querySelector('.controls-stack');
1275
+ const fullscreen = this.shadowRoot.querySelector("#fullscreen");
1276
+ const pip = this.shadowRoot.querySelector("#pip");
1277
+ const subtitles = this.shadowRoot.querySelector("#subtitles");
1278
+ const current = this.shadowRoot.querySelector("#current");
1279
+ const duration = this.shadowRoot.querySelector("#duration");
1280
+ const bar = this.shadowRoot.querySelector("#bar");
1281
+ const seekbar = this.shadowRoot.querySelector("#seekbar");
1282
+ const buffer = this.shadowRoot.querySelector("#buffer");
1283
+ const knob = this.shadowRoot.querySelector("#knob");
1284
+ const controls = this.shadowRoot.querySelector('.controls');
1285
+
1286
+ // Update time display
1287
+ const updateTime = () => {
1288
+ current.textContent = this._format(media.currentTime);
1289
+ duration.textContent = this._format(media.duration || 0);
1290
+ const progress = (media.currentTime / media.duration) * 100;
1291
+ bar.style.width = `${progress}%`;
1292
+ knob.style.left = `${progress}%`;
1293
+ };
1294
+
1295
+ // Update buffer bar
1296
+ const updateBufferBar = () => {
1297
+ if (media.buffered.length > 0) {
1298
+ const bufferedEnd = media.buffered.end(media.buffered.length - 1); // End of the last buffered range
1299
+ const bufferWidth = (bufferedEnd / media.duration) * 100;
1300
+ buffer.style.width = `${bufferWidth}%`;
1301
+ }
1302
+ };
1303
+
1304
+ media.addEventListener("timeupdate", updateTime);
1305
+ media.addEventListener("loadedmetadata", updateTime);
1306
+ media.addEventListener("progress", updateBufferBar);
1307
+ media.addEventListener("loadedmetadata", updateBufferBar);
1308
+
1309
+ // Seek functionality
1310
+ seekbar.onclick = (e) => {
1311
+ const rect = seekbar.getBoundingClientRect();
1312
+ const seekTime = ((e.clientX - rect.left) / rect.width) * media.duration;
1313
+ media.currentTime = seekTime;
1314
+ };
1315
+ // Touch support for seekbar
1316
+ seekbar.addEventListener('touchstart', (e) => {
1317
+ if (e.touches.length === 1) {
1318
+ const touch = e.touches[0];
1319
+ const rect = seekbar.getBoundingClientRect();
1320
+ const seekTime = ((touch.clientX - rect.left) / rect.width) * media.duration;
1321
+ media.currentTime = seekTime;
1322
+ }
1323
+ });
1324
+
1325
+ // Scrub knob functionality
1326
+ let isDragging = false;
1327
+ let isTouchDragging = false;
1328
+
1329
+ knob.onmousedown = (e) => {
1330
+ isDragging = true;
1331
+ knob.classList.add("dragging");
1332
+ document.addEventListener("mousemove", onMouseMove);
1333
+ document.addEventListener("mouseup", onMouseUp);
1334
+ document.addEventListener("mouseleave", onMouseUp);
1335
+ };
1336
+ // Touch support for scrub knob
1337
+ knob.addEventListener('touchstart', (e) => {
1338
+ if (e.touches.length === 1) {
1339
+ isTouchDragging = true;
1340
+ knob.classList.add("dragging");
1341
+ document.addEventListener("touchmove", onTouchMove);
1342
+ document.addEventListener("touchend", onTouchEnd);
1343
+ document.addEventListener("touchcancel", onTouchEnd);
1344
+ }
1345
+ });
1346
+
1347
+ function onMouseMove(event) {
1348
+ if (isDragging) {
1349
+ const rect = seekbar.getBoundingClientRect();
1350
+ const offsetX = Math.min(Math.max(event.clientX - rect.left, 0), rect.width);
1351
+ const progress = (offsetX / rect.width) * 100;
1352
+ const seekTime = (offsetX / rect.width) * media.duration;
1353
+ knob.style.left = `${progress}%`;
1354
+ bar.style.width = `${progress}%`;
1355
+ media.currentTime = seekTime;
1356
+ }
1357
+ }
1358
+ function onMouseUp() {
1359
+ isDragging = false;
1360
+ knob.classList.remove("dragging");
1361
+ document.removeEventListener("mousemove", onMouseMove);
1362
+ document.removeEventListener("mouseup", onMouseUp);
1363
+ document.removeEventListener("mouseleave", onMouseUp);
1364
+ }
1365
+ function onTouchMove(event) {
1366
+ if (isTouchDragging && event.touches.length === 1) {
1367
+ const touch = event.touches[0];
1368
+ const rect = seekbar.getBoundingClientRect();
1369
+ const offsetX = Math.min(Math.max(touch.clientX - rect.left, 0), rect.width);
1370
+ const progress = (offsetX / rect.width) * 100;
1371
+ const seekTime = (offsetX / rect.width) * media.duration;
1372
+ knob.style.left = `${progress}%`;
1373
+ bar.style.width = `${progress}%`;
1374
+ media.currentTime = seekTime;
1375
+ }
1376
+ }
1377
+ function onTouchEnd() {
1378
+ isTouchDragging = false;
1379
+ knob.classList.remove("dragging");
1380
+ document.removeEventListener("touchmove", onTouchMove);
1381
+ document.removeEventListener("touchend", onTouchEnd);
1382
+ document.removeEventListener("touchcancel", onTouchEnd);
1383
+ }
1384
+
1385
+ // Fullscreen button: add touch support
1386
+ fullscreen.onclick = () => {
1387
+ const player = this.shadowRoot.querySelector(".player");
1388
+ if (!document.fullscreenElement) {
1389
+ player.requestFullscreen().then(() => {
1390
+ this.classList.add("fullscreen");
1391
+ this._updateDimensions();
1392
+ }).catch((err) => {
1393
+ console.error("Failed to enter full-screen mode:", err);
1394
+ });
1395
+ } else {
1396
+ document.exitFullscreen().then(() => {
1397
+ this.classList.remove("fullscreen");
1398
+ this._updateDimensions();
1399
+ }).catch((err) => {
1400
+ console.error("Failed to exit full-screen mode:", err);
1401
+ });
1402
+ }
1403
+ this.dispatchEvent(new CustomEvent("xplayer-fullscreen", { detail: { fullscreen: !!document.fullscreenElement } }));
1404
+ };
1405
+ fullscreen.addEventListener('touchstart', (e) => {
1406
+ e.preventDefault();
1407
+ fullscreen.onclick();
1408
+ });
1409
+ pip.onclick = async () => {
1410
+ try {
1411
+ if (document.pictureInPictureElement) {
1412
+ await document.exitPictureInPicture();
1413
+ } else {
1414
+ await media.requestPictureInPicture();
1415
+ }
1416
+ } catch (err) {
1417
+ console.warn("PIP failed:", err.message);
1418
+ }
1419
+ this.dispatchEvent(new CustomEvent("xplayer-pip", { detail: {} }));
1420
+ };
1421
+ subtitles.onclick = () => {
1422
+ // ...existing code for subtitles toggle...
1423
+ this.dispatchEvent(new CustomEvent("xplayer-caption", { detail: {} }));
1424
+ };
1425
+ // Defensive: Fallback für fehlende Controls
1426
+ if (!play || !mute || !volume || !fullscreen || !pip || !subtitles) {
1427
+ console.warn("Einige Steuerelemente fehlen im Player.");
1428
+ }
1429
+
1430
+ // Volume-Slider auf Mobilgeräten deaktivieren
1431
+ if (window.matchMedia('(hover: none) and (pointer: coarse)').matches) {
1432
+ if (volume) volume.style.display = 'none';
1433
+ }
1434
+ // Desktop: Stack-Logik für Icons rechts des Lautsprecher-Icons
1435
+ if (volumeContainer && controlsStack) {
1436
+ const volumeSlider = volumeContainer.querySelector('.volume-slider');
1437
+ let hoverCount = 0;
1438
+ let hideTimeout = null;
1439
+ function isDescendant(parent, child) {
1440
+ let node = child;
1441
+ while (node) {
1442
+ if (node === parent) return true;
1443
+ node = node.parentNode;
1444
+ }
1445
+ return false;
1446
+ }
1447
+ const show = () => {
1448
+ hoverCount++;
1449
+ clearTimeout(hideTimeout); // Nur im show(), nicht im hide()
1450
+ volumeContainer.classList.add('expanded');
1451
+ if (controls) controls.classList.add('visible');
1452
+ };
1453
+ const hide = (e) => {
1454
+ hoverCount = Math.max(0, hoverCount - 1);
1455
+ let related = e && (e.relatedTarget || e.toElement);
1456
+ if (related && (isDescendant(volumeContainer, related) || isDescendant(volumeSlider, related))) {
1457
+ return;
1458
+ }
1459
+ if (hoverCount === 0) {
1460
+ hideTimeout = setTimeout(() => {
1461
+ volumeContainer.classList.remove('expanded');
1462
+ // Slider immer ausblenden, unabhängig vom Status der Steuerleiste
1463
+ }, 800);
1464
+ }
1465
+ };
1466
+ volumeContainer.addEventListener('mouseenter', show);
1467
+ volumeContainer.addEventListener('mouseleave', hide);
1468
+ if (volumeSlider) {
1469
+ volumeSlider.addEventListener('mouseenter', show);
1470
+ volumeSlider.addEventListener('mouseleave', hide);
1471
+ // Nach Interaktion auf dem Slider: GUI und Slider nach kurzer Zeit ausblenden
1472
+ const volumeInput = volumeSlider.querySelector('input[type="range"]');
1473
+ if (volumeInput) {
1474
+ let interactionEnd = () => {
1475
+ hideTimeout = setTimeout(() => {
1476
+ volumeContainer.classList.remove('expanded');
1477
+ // Slider immer ausblenden, unabhängig vom Status der Steuerleiste
1478
+ }, 800);
1479
+ };
1480
+ volumeInput.addEventListener('mouseup', interactionEnd);
1481
+ volumeInput.addEventListener('touchend', interactionEnd);
1482
+ volumeInput.addEventListener('change', interactionEnd);
1483
+ }
1484
+ }
1485
+ volumeContainer.addEventListener('focusin', show);
1486
+ volumeContainer.addEventListener('focusout', hide);
1487
+ }
1488
+ if (controls) controls.classList.add('visible');
1489
+
1490
+ // ARIA- und Event-Verbesserungen für Controls
1491
+ play.onclick = null;
1492
+ play.addEventListener('pointerdown', (e) => {
1493
+ e.preventDefault();
1494
+ if (this._media.paused) {
1495
+ this._media.play();
1496
+ this.dispatchEvent(new CustomEvent("xplayer-play", { detail: { currentTime: this._media.currentTime } }));
1497
+ } else {
1498
+ this._media.pause();
1499
+ this.dispatchEvent(new CustomEvent("xplayer-pause", { detail: { currentTime: this._media.currentTime } }));
1500
+ }
1501
+ this.updatePlayPauseIcon();
1502
+ });
1503
+ mute.onclick = null;
1504
+ mute.addEventListener('pointerdown', (e) => {
1505
+ e.preventDefault();
1506
+ this._media.muted = !this._media.muted;
1507
+ mute.innerHTML = this._media.muted ? svgIcon('mute') : svgIcon('volume');
1508
+ this.dispatchEvent(new CustomEvent("xplayer-mute", { detail: { muted: this._media.muted } }));
1509
+ });
1510
+ if (volume) {
1511
+ // Lautstärke-Slider steuert die Media-Lautstärke
1512
+ volume.addEventListener('input', (e) => {
1513
+ if (this._media) {
1514
+ this._media.volume = parseFloat(volume.value);
1515
+ if (this._media.volume === 0) {
1516
+ this._media.muted = true;
1517
+ mute.innerHTML = svgIcon('mute');
1518
+ } else {
1519
+ this._media.muted = false;
1520
+ mute.innerHTML = svgIcon('volume');
1521
+ }
1522
+ }
1523
+ });
1524
+ // Synchronisiere Slider, wenn Lautstärke extern geändert wird
1525
+ this._media.addEventListener('volumechange', () => {
1526
+ volume.value = this._media.volume;
1527
+ mute.innerHTML = this._media.muted ? svgIcon('mute') : svgIcon('volume');
1528
+ });
1529
+ }
1530
+ // NEU: Immer synchronisieren, auch bei Tastatursteuerung etc.
1531
+ this._media.addEventListener('play', () => this.updatePlayPauseIcon());
1532
+ this._media.addEventListener('pause', () => this.updatePlayPauseIcon());
1533
+ this._media.addEventListener('ended', () => this.updatePlayPauseIcon());
1534
+ }
1535
+
1536
+ _toggleControlsVisibility() {
1537
+ const controls = this.shadowRoot.querySelector(".controls");
1538
+ const volumeContainer = this.shadowRoot.querySelector('.volume-container');
1539
+ let timeout;
1540
+
1541
+ const showControls = () => {
1542
+ controls.classList.add("visible");
1543
+ clearTimeout(timeout);
1544
+ // GUI ist sichtbar, Slider kann unabhängig gesteuert werden
1545
+ };
1546
+
1547
+ const hideControls = () => {
1548
+ controls.classList.remove("visible");
1549
+ // NEU: Immer auch den Lautstärke-Slider schließen
1550
+ if (volumeContainer) volumeContainer.classList.remove('expanded');
1551
+ };
1552
+
1553
+ this._media.addEventListener("mousemove", showControls);
1554
+ this._media.addEventListener("play", showControls);
1555
+ this._media.addEventListener("pause", () => {
1556
+ controls.classList.add("visible"); // Keep controls visible when paused
1557
+ clearTimeout(timeout); // Cancel any timeout to hide controls
1558
+ });
1559
+
1560
+ // Automatisches Ausblenden nach 3s nur wenn Video spielt
1561
+ showControls();
1562
+ this._media.addEventListener("mousemove", () => {
1563
+ if (!this._media.paused) {
1564
+ clearTimeout(timeout);
1565
+ timeout = setTimeout(hideControls, 3000);
1566
+ }
1567
+ });
1568
+ }
1569
+
1570
+ _setupChooser() {
1571
+ const container = this.shadowRoot.querySelector("#chooser-container");
1572
+ container.innerHTML = "";
1573
+ container.style.display = "none";
1574
+
1575
+ if (this.getAttribute("media-chooser") !== "true") return;
1576
+
1577
+ const select = document.createElement("select");
1578
+ const options = Array.from(this.querySelectorAll("source"));
1579
+
1580
+ options.forEach((source, idx) => {
1581
+ const opt = document.createElement("option");
1582
+ opt.value = source.getAttribute("src");
1583
+ opt.textContent = source.getAttribute("label") || `Source ${idx + 1}`;
1584
+ select.appendChild(opt);
1585
+ });
1586
+
1587
+ if (options.length > 0) {
1588
+ select.onchange = () => {
1589
+ this._media.src = select.value;
1590
+ this._media.load();
1591
+ this._media.play().catch((err) => {
1592
+ console.warn("Media play failed:", err.message);
1593
+ });
1594
+ };
1595
+ container.appendChild(select);
1596
+ container.style.display = "block";
1597
+ }
1598
+ }
1599
+
1600
+ _setupDownload() {
1601
+ const container = this.shadowRoot.querySelector("#download-container");
1602
+ container.innerHTML = "";
1603
+
1604
+ if (this.getAttribute("downloadable") === "true" && this._media?.src) {
1605
+ const link = document.createElement("a");
1606
+ link.href = this._media.src;
1607
+ link.download = "";
1608
+ link.className = "download";
1609
+ link.title = "Download";
1610
+ link.textContent = "⬇";
1611
+ container.appendChild(link);
1612
+ }
1613
+ }
1614
+
1615
+ _setupKeyboardControls() {
1616
+ const player = this.shadowRoot.querySelector(".player");
1617
+ const media = this._media;
1618
+ const volumeSlider = this.shadowRoot.querySelector("#volume");
1619
+
1620
+ player.addEventListener("keydown", (e) => {
1621
+ switch (e.key) {
1622
+ case " ":
1623
+ e.preventDefault(); // Prevent scrolling
1624
+ if (media.paused) {
1625
+ media.play();
1626
+ } else {
1627
+ media.pause();
1628
+ }
1629
+ break;
1630
+ case "ArrowRight":
1631
+ media.currentTime = Math.min(media.currentTime + 10, media.duration);
1632
+ break;
1633
+ case "ArrowLeft":
1634
+ media.currentTime = Math.max(media.currentTime - 10, 0);
1635
+ break;
1636
+ case "ArrowUp":
1637
+ e.preventDefault(); // Prevent scrolling
1638
+ media.volume = Math.min(media.volume + 0.1, 1);
1639
+ volumeSlider.value = media.volume;
1640
+ break;
1641
+ case "ArrowDown":
1642
+ e.preventDefault(); // Prevent scrolling
1643
+ media.volume = Math.max(media.volume - 0.1, 0);
1644
+ volumeSlider.value = media.volume;
1645
+ break;
1646
+ }
1647
+ });
1648
+ }
1649
+
1650
+ _setupCursorHiding() {
1651
+ const player = this.shadowRoot.querySelector(".player");
1652
+ let cursorTimeout;
1653
+
1654
+ const hideCursor = () => {
1655
+ player.classList.add("hide-cursor");
1656
+ };
1657
+
1658
+ const showCursor = () => {
1659
+ player.classList.remove("hide-cursor");
1660
+ clearTimeout(cursorTimeout);
1661
+ cursorTimeout = setTimeout(hideCursor, 3000); // Hide cursor after 3 seconds
1662
+ };
1663
+
1664
+ player.addEventListener("mousemove", () => {
1665
+ if (document.fullscreenElement) {
1666
+ showCursor();
1667
+ }
1668
+ });
1669
+
1670
+ document.addEventListener("fullscreenchange", () => {
1671
+ if (!document.fullscreenElement) {
1672
+ player.classList.remove("hide-cursor"); // Ensure cursor is visible when exiting full-screen
1673
+ clearTimeout(cursorTimeout);
1674
+ }
1675
+ });
1676
+ }
1677
+
1678
+ _format(seconds) {
1679
+ const m = Math.floor(seconds / 60);
1680
+ const s = Math.floor(seconds % 60).toString().padStart(2, "0");
1681
+ return `${m}:${s}`;
1682
+ }
1683
+
1684
+ _updateDimensions() {
1685
+ const container = this.shadowRoot.querySelector("#media-container");
1686
+ const player = this.shadowRoot.querySelector(".player");
1687
+ const media = this._media;
1688
+
1689
+ // Hole Attribute oder CSS-Variablen
1690
+ const width = this.getAttribute("width") || this.style.width || "";
1691
+ const height = this.getAttribute("height") || this.style.height || "";
1692
+
1693
+ if (this.classList.contains("fullscreen")) {
1694
+ // Im Fullscreen: immer 100%
1695
+ container.style.width = "100%";
1696
+ container.style.height = "100%";
1697
+ player.style.width = "100%";
1698
+ player.style.height = "100%";
1699
+ if (media) {
1700
+ media.style.width = "100%";
1701
+ media.style.height = "100%";
1702
+ }
1703
+ } else {
1704
+ // Außerhalb Fullscreen: feste Größe aus Attributen oder CSS übernehmen
1705
+ if (width) {
1706
+ player.style.width = width.endsWith("px") || width.endsWith("%") ? width : width + "px";
1707
+ container.style.width = player.style.width;
1708
+ if (media) media.style.width = player.style.width;
1709
+ } else {
1710
+ player.style.width = "";
1711
+ container.style.width = "";
1712
+ if (media) media.style.width = "";
1713
+ }
1714
+ if (height) {
1715
+ player.style.height = height.endsWith("px") || height.endsWith("%") ? height : height + "px";
1716
+ container.style.height = player.style.height;
1717
+ if (media) media.style.height = player.style.height;
1718
+ } else {
1719
+ player.style.height = "";
1720
+ container.style.height = "";
1721
+ if (media) media.style.height = "";
1722
+ }
1723
+ }
1724
+ }
1725
+ }
1726
+
1727
+ customElements.define("x-player", XPlayer);
1728
+
1729
+ // SVG-Icon Helper (direkt im File, keine Abhängigkeit)
1730
+ function svgIcon(name) {
1731
+ switch (name) {
1732
+ case 'play': return `<svg width="1.6em" height="1.6em" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polygon points="6 3 20 12 6 21 6 3"/></svg>`;
1733
+ case 'pause': return `<svg width="1.6em" height="1.6em" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="6" y="4" width="4" height="16"/><rect x="14" y="4" width="4" height="16"/></svg>`;
1734
+ case 'backward': return `<svg width="1.6em" height="1.6em" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polygon points="11 19 2 12 11 5 11 19"/><polygon points="22 19 13 12 22 5 22 19"/></svg>`;
1735
+ case 'forward': return `<svg width="1.6em" height="1.6em" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polygon points="13 5 22 12 13 19 13 5"/><polygon points="2 5 11 12 2 19 2 5"/></svg>`;
1736
+ case 'volume': return `<svg width="1.6em" height="1.6em" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polygon points="11 5 6 9 2 9 2 15 6 15 11 19 11 5"/><path d="M19.07 4.93a10 10 0 0 1 0 14.14"/><path d="M15.54 8.46a5 5 0 0 1 0 7.07"/></svg>`;
1737
+ case 'mute': return `<svg width="1.6em" height="1.6em" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polygon points="11 5 6 9 2 9 2 15 6 15 11 19 11 5"/><line x1="23" y1="9" x2="17" y2="15"/><line x1="17" y1="9" x2="23" y2="15"/></svg>`;
1738
+ case 'fullscreen': return `<svg width="1.6em" height="1.6em" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M8 3H5a2 2 0 0 0-2 2v3"/><path d="M16 3h3a2 2 0 0 1 2 2v3"/><path d="M8 21H5a2 2 0 0 1-2-2v-3"/><path d="M16 21h3a2 2 0 0 0 2-2v-3"/></svg>`;
1739
+ case 'cc': return `<svg width="1.6em" height="1.6em" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="4" width="20" height="16" rx="4"/><path d="M9 10a2 2 0 1 0 0 4"/><path d="M15 10a2 2 0 1 0 0 4"/></svg>`;
1740
+ case 'pip': return `<svg width="1.6em" height="1.6em" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="2"/><rect x="9" y="13" width="6" height="6" rx="1"/></svg>`;
1741
+ default: return '';
1742
+ }
1743
+ }
1744
+
1745
+ // Entferne alle title-Attribute von Steuerelementen im Shadow DOM
1746
+ Array.from(this.shadowRoot.querySelectorAll('[title]')).forEach(el => {
1747
+ el.removeAttribute('title');
1748
+ });