@pixl-app/sdk 0.1.0 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +24 -0
- package/README.md +18 -3
- package/dist/api-types/device-apps.d.ts +1 -1
- package/dist/api-types/index.d.ts +3 -0
- package/dist/api-types/modules.d.ts +1 -1
- package/dist/api-types/payloads.d.ts +389 -0
- package/dist/api-types/templates.d.ts +6 -0
- package/dist/api-types/widgets.d.ts +555 -0
- package/dist/bindings.d.ts +22 -0
- package/dist/chat.d.ts +1 -1
- package/dist/client.d.ts +127 -164
- package/dist/errors.d.ts +17 -0
- package/dist/events.d.ts +41 -0
- package/dist/feed.d.ts +21 -29
- package/dist/fonts.d.ts +19 -0
- package/dist/frame.d.ts +2 -0
- package/dist/index-nb1y5rx1.js +1932 -0
- package/dist/index.d.ts +12 -3
- package/dist/index.js +38 -873
- package/dist/live-value.d.ts +18 -0
- package/dist/pixl-ui.js +278 -0
- package/dist/pixl.js +4 -4
- package/dist/spotify.d.ts +22 -0
- package/dist/stream.d.ts +30 -0
- package/dist/streamelements.d.ts +14 -0
- package/dist/tts.d.ts +18 -9
- package/dist/twitch.d.ts +68 -0
- package/dist/ui/checkbox.d.ts +27 -0
- package/dist/ui/define.d.ts +2 -0
- package/dist/ui/dialog.d.ts +15 -0
- package/dist/ui/dropdown-helpers.d.ts +48 -0
- package/dist/ui/dropdown.d.ts +65 -0
- package/dist/ui/field.d.ts +48 -0
- package/dist/ui/index.d.ts +14 -0
- package/dist/ui/index.js +1919 -0
- package/dist/ui/inputs.d.ts +29 -0
- package/dist/ui/list.d.ts +31 -0
- package/dist/ui/runtime.d.ts +18 -0
- package/dist/ui/section.d.ts +13 -0
- package/dist/ui/styles.d.ts +6 -0
- package/dist/widget.d.ts +141 -0
- package/package.json +13 -5
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export interface LiveValue<T> {
|
|
2
|
+
/** Aktueller Stand */
|
|
3
|
+
readonly value: T;
|
|
4
|
+
/** Läuft sofort mit dem aktuellen Stand, danach bei jeder Änderung. Gibt eine Funktion zum Abmelden zurück. */
|
|
5
|
+
subscribe(fn: (value: T) => void): () => void;
|
|
6
|
+
}
|
|
7
|
+
/** @internal Live-Wert, den das SDK selbst setzt */
|
|
8
|
+
export declare class WritableLiveValue<T> implements LiveValue<T> {
|
|
9
|
+
private current;
|
|
10
|
+
private readonly listeners;
|
|
11
|
+
constructor(initial: T);
|
|
12
|
+
get value(): T;
|
|
13
|
+
subscribe(fn: (value: T) => void): () => void;
|
|
14
|
+
set(value: T): void;
|
|
15
|
+
/** Alle Abonnenten entfernen (close()) */
|
|
16
|
+
clear(): void;
|
|
17
|
+
}
|
|
18
|
+
export declare function safeCall(fn: () => void): void;
|
package/dist/pixl-ui.js
ADDED
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
(()=>{function U(){if(typeof window>"u"||window.parent===window)return;let e=globalThis;if(e.__pixlFrame)return;e.__pixlFrame=!0,window.addEventListener("message",(n)=>{if(n.source!==window.parent||typeof n.data!=="object"||!n.data)return;if(n.data.type!=="pixl:theme"||typeof n.data.vars!=="object")return;let t=document.documentElement;for(let[r,s]of Object.entries(n.data.vars))if(r.startsWith("--pixl-"))t.style.setProperty(r,String(s));if(n.data.scheme)t.style.colorScheme=n.data.scheme});let i=()=>{let n=document.body,t=getComputedStyle(n),r=n.getBoundingClientRect().height+parseFloat(t.marginTop)+parseFloat(t.marginBottom);window.parent.postMessage({type:"pixl:height",height:Math.ceil(r)},"*")},o=()=>{if(typeof ResizeObserver<"u")new ResizeObserver(i).observe(document.body);i()};if(document.body)o();else document.addEventListener("DOMContentLoaded",o,{once:!0});window.addEventListener("load",i)}var L=`
|
|
2
|
+
:where(:root) { color-scheme: dark; }
|
|
3
|
+
:where(body) { font-family: var(--pixl-font, system-ui, sans-serif); color: var(--pixl-fg, #e8e8f0); }
|
|
4
|
+
:where(pixl-field, pixl-toggle, pixl-counter, pixl-range, pixl-select, pixl-text, pixl-color, pixl-file, pixl-button, pixl-list, pixl-section, pixl-controls) { display: block; }
|
|
5
|
+
:where(.pixl-btn, .pixl-input, .pixl-select, .pixl-checkbox, .pixl-card, .pixl-skeleton) { ${`
|
|
6
|
+
--bg: var(--pixl-bg, #0f0f12);
|
|
7
|
+
--surface: var(--pixl-surface, #22222c);
|
|
8
|
+
--fg: var(--pixl-fg, #e8e8f0);
|
|
9
|
+
--muted: var(--pixl-muted, #9a9aa8);
|
|
10
|
+
--line: var(--pixl-line, rgb(255 255 255 / 0.1));
|
|
11
|
+
--line-strong: var(--pixl-line-strong, rgb(255 255 255 / 0.16));
|
|
12
|
+
--accent: var(--pixl-accent, #7c6dfa);
|
|
13
|
+
--danger: var(--pixl-danger, #f05252);
|
|
14
|
+
--radius: 10px;
|
|
15
|
+
`} }
|
|
16
|
+
.pixl-btn {
|
|
17
|
+
height: 34px; padding: 0 14px; border-radius: 999px; border: 1px solid var(--line);
|
|
18
|
+
background: transparent; color: var(--fg); font: inherit; cursor: pointer;
|
|
19
|
+
display: inline-flex; align-items: center; justify-content: center; gap: 6px; white-space: nowrap;
|
|
20
|
+
}
|
|
21
|
+
.pixl-btn:disabled { opacity: 0.45; cursor: not-allowed; }
|
|
22
|
+
.pixl-btn.primary { background: var(--accent); border-color: transparent; color: white; font-weight: 600; }
|
|
23
|
+
.pixl-btn.danger { background: var(--danger); border-color: transparent; color: white; }
|
|
24
|
+
.pixl-btn.ghost { border-color: transparent; }
|
|
25
|
+
.pixl-input {
|
|
26
|
+
height: 34px; width: 100%; box-sizing: border-box; border-radius: 8px; border: 1px solid var(--line);
|
|
27
|
+
background: rgb(0 0 0 / 0.2); color: var(--fg); padding: 0 10px; font: inherit;
|
|
28
|
+
}
|
|
29
|
+
textarea.pixl-input { height: auto; min-height: 80px; padding: 8px 10px; }
|
|
30
|
+
/* Rückfall für ein <select> des Systems: Feld wie <pixl-dropdown>, die Liste bleibt die des Systems */
|
|
31
|
+
select.pixl-select {
|
|
32
|
+
appearance: none; height: 36px; width: 100%; box-sizing: border-box; border-radius: 10px;
|
|
33
|
+
border: 1px solid var(--line-strong); color: var(--fg); padding: 0 32px 0 12px; font: inherit; font-size: 14px;
|
|
34
|
+
background: rgb(0 0 0 / 0.2) no-repeat right 8px center / 16px 16px;
|
|
35
|
+
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%239a9aa8' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
|
|
36
|
+
cursor: pointer; text-overflow: ellipsis;
|
|
37
|
+
}
|
|
38
|
+
select.pixl-select:hover:not(:disabled) { border-color: color-mix(in oklch, var(--fg) 30%, transparent); }
|
|
39
|
+
select.pixl-select:focus-visible {
|
|
40
|
+
outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px color-mix(in oklch, var(--accent) 25%, transparent);
|
|
41
|
+
}
|
|
42
|
+
select.pixl-select:disabled { opacity: 0.5; cursor: not-allowed; }
|
|
43
|
+
select.pixl-select option { background: var(--surface); color: var(--fg); }
|
|
44
|
+
/* Häkchen für ein normales <input type="checkbox">, sieht aus wie <pixl-checkbox> */
|
|
45
|
+
input[type="checkbox"].pixl-checkbox {
|
|
46
|
+
appearance: none; width: 16px; height: 16px; margin: 0; flex: none; vertical-align: middle; cursor: pointer;
|
|
47
|
+
border-radius: 5px; border: 1px solid color-mix(in oklch, var(--fg) 30%, transparent); background: rgb(0 0 0 / 0.2);
|
|
48
|
+
background-position: center; background-repeat: no-repeat; background-size: 12px 12px;
|
|
49
|
+
transition: border-color .15s, box-shadow .15s;
|
|
50
|
+
}
|
|
51
|
+
input[type="checkbox"].pixl-checkbox:hover:not(:disabled) { border-color: color-mix(in oklch, var(--fg) 50%, transparent); }
|
|
52
|
+
input[type="checkbox"].pixl-checkbox:checked {
|
|
53
|
+
border-color: transparent;
|
|
54
|
+
background-color: var(--accent);
|
|
55
|
+
background-image:
|
|
56
|
+
url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='white' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M20 6 9 17l-5-5'/%3E%3C/svg%3E"),
|
|
57
|
+
linear-gradient(oklch(from var(--accent) calc(l + 0.04) calc(c * 0.92) h), var(--accent));
|
|
58
|
+
background-size: 12px 12px, 100% 100%;
|
|
59
|
+
box-shadow: inset 0 1px 0 rgb(255 255 255 / 0.22), 0 1px 2px rgb(0 0 0 / 0.35);
|
|
60
|
+
}
|
|
61
|
+
input[type="checkbox"].pixl-checkbox:disabled { opacity: 0.45; cursor: not-allowed; }
|
|
62
|
+
input[type="checkbox"].pixl-checkbox:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
|
|
63
|
+
.pixl-card { border: 1px solid var(--line); border-radius: var(--radius); padding: 12px 16px; }
|
|
64
|
+
.pixl-skeleton { display: block; border-radius: 8px; background: var(--surface); animation: pixl-pulse 2s cubic-bezier(.4, 0, .6, 1) infinite; }
|
|
65
|
+
@keyframes pixl-pulse { 50% { opacity: .5; } }
|
|
66
|
+
@media (prefers-reduced-motion: reduce) { .pixl-skeleton { animation: none; } }
|
|
67
|
+
.pixl-btn:focus-visible, .pixl-input:focus-visible { outline: 2px solid var(--pixl-accent, #7c6dfa); outline-offset: 1px; }
|
|
68
|
+
@media (max-width: 640px) { .pixl-btn, .pixl-input { height: 40px; } }
|
|
69
|
+
@media (pointer: coarse) { select.pixl-select { height: 40px; } }
|
|
70
|
+
`,w=null;function m(e,i=""){if(!w)w=new CSSStyleSheet,w.replaceSync(`
|
|
71
|
+
:host {
|
|
72
|
+
--bg: var(--pixl-bg, #0f0f12);
|
|
73
|
+
--surface: var(--pixl-surface, #22222c);
|
|
74
|
+
--fg: var(--pixl-fg, #e8e8f0);
|
|
75
|
+
--muted: var(--pixl-muted, #9a9aa8);
|
|
76
|
+
--line: var(--pixl-line, rgb(255 255 255 / 0.1));
|
|
77
|
+
--line-strong: var(--pixl-line-strong, rgb(255 255 255 / 0.16));
|
|
78
|
+
--accent: var(--pixl-accent, #7c6dfa);
|
|
79
|
+
--danger: var(--pixl-danger, #f05252);
|
|
80
|
+
--radius: 10px;
|
|
81
|
+
display: block; color: var(--fg); font-family: var(--pixl-font, system-ui, sans-serif); font-size: 14px; }
|
|
82
|
+
:host([hidden]) { display: none; }
|
|
83
|
+
* { box-sizing: border-box; }
|
|
84
|
+
|
|
85
|
+
.btn {
|
|
86
|
+
height: 34px; padding: 0 14px; border-radius: 999px; border: 1px solid var(--line);
|
|
87
|
+
background: transparent; color: var(--fg); font: inherit; font-size: 14px; cursor: pointer;
|
|
88
|
+
display: inline-flex; align-items: center; justify-content: center; gap: 6px; white-space: nowrap;
|
|
89
|
+
}
|
|
90
|
+
.btn:hover:not(:disabled) { border-color: color-mix(in oklch, var(--fg) 35%, transparent); }
|
|
91
|
+
.btn:disabled { opacity: 0.45; cursor: not-allowed; }
|
|
92
|
+
.btn.primary { background: var(--accent); border-color: transparent; color: white; font-weight: 600; }
|
|
93
|
+
.btn.danger { background: var(--danger); border-color: transparent; color: white; }
|
|
94
|
+
.btn.ghost { border-color: transparent; }
|
|
95
|
+
.btn.icon { width: 34px; padding: 0; }
|
|
96
|
+
.input {
|
|
97
|
+
height: 34px; width: 100%; box-sizing: border-box; border-radius: 8px; border: 1px solid var(--line);
|
|
98
|
+
background: rgb(0 0 0 / 0.2); color: var(--fg); padding: 0 10px; font: inherit; font-size: 14px;
|
|
99
|
+
}
|
|
100
|
+
textarea.input { height: auto; min-height: 80px; padding: 8px 10px; }
|
|
101
|
+
.btn:focus-visible, .input:focus-visible, .switch:focus-visible {
|
|
102
|
+
outline: 2px solid var(--accent); outline-offset: 1px;
|
|
103
|
+
}
|
|
104
|
+
@media (max-width: 640px) {
|
|
105
|
+
.btn { height: 40px; }
|
|
106
|
+
.btn.icon { width: 40px; }
|
|
107
|
+
.input { height: 40px; }
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
.field { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 8px 16px; padding: 12px 0; }
|
|
111
|
+
.field.wide { flex-direction: column; align-items: stretch; }
|
|
112
|
+
.label { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
|
|
113
|
+
.label-text { font-weight: 500; }
|
|
114
|
+
.desc { color: var(--muted); font-size: 12px; }
|
|
115
|
+
.control { display: flex; align-items: center; gap: 8px; min-width: 0; }
|
|
116
|
+
.field.wide .control { width: 100%; }
|
|
117
|
+
.error { color: var(--danger); font-size: 12px; width: 100%; }
|
|
118
|
+
.skeleton { display: block; border-radius: 8px; background: var(--surface); animation: pixl-pulse 2s cubic-bezier(.4, 0, .6, 1) infinite; }
|
|
119
|
+
@keyframes pixl-pulse { 50% { opacity: .5; } }
|
|
120
|
+
@media (prefers-reduced-motion: reduce) { .skeleton { animation: none; } }
|
|
121
|
+
.switch {
|
|
122
|
+
appearance: none; width: 40px; height: 22px; border-radius: 999px; border: 1px solid var(--line);
|
|
123
|
+
background: var(--surface); position: relative; cursor: pointer; padding: 0; flex: none; transition: background .2s;
|
|
124
|
+
}
|
|
125
|
+
.switch::after {
|
|
126
|
+
content: ""; position: absolute; top: 2px; left: 2px; width: 16px; height: 16px; border-radius: 50%;
|
|
127
|
+
background: var(--fg); transition: transform .2s;
|
|
128
|
+
}
|
|
129
|
+
.switch[aria-checked="true"] { background: var(--accent); border-color: transparent; }
|
|
130
|
+
.switch[aria-checked="true"]::after { transform: translateX(18px); }
|
|
131
|
+
.count { min-width: 3ch; text-align: center; font-variant-numeric: tabular-nums; font-size: 18px; font-family: ui-monospace, monospace; }
|
|
132
|
+
input[type="range"] { flex: 1; min-width: 120px; accent-color: var(--accent); }
|
|
133
|
+
.value { min-width: 3ch; text-align: right; color: var(--muted); font-variant-numeric: tabular-nums; }
|
|
134
|
+
input[type="color"] { width: 40px; height: 34px; padding: 2px; border-radius: 8px; border: 1px solid var(--line); background: transparent; cursor: pointer; flex: none; }
|
|
135
|
+
.thumb { width: 48px; height: 48px; border-radius: 8px; border: 1px solid var(--line); background: rgb(0 0 0 / 0.25); object-fit: contain; flex: none; }
|
|
136
|
+
.thumb.empty { display: grid; place-items: center; color: var(--muted); font-size: 11px; }
|
|
137
|
+
.stack { display: flex; flex-direction: column; gap: 8px; min-width: 0; flex: 1; }
|
|
138
|
+
.row { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
|
|
139
|
+
.field:not(.wide) .control pixl-dropdown { min-width: 160px; }
|
|
140
|
+
.secret-wrap { position: relative; width: 100%; }
|
|
141
|
+
.secret-wrap .input { padding-right: 40px; }
|
|
142
|
+
.secret-wrap .eye { position: absolute; top: 50%; right: 3px; transform: translateY(-50%); width: 28px; height: 28px; color: var(--muted); }
|
|
143
|
+
.secret-wrap .eye:hover:not(:disabled) { color: var(--fg); }
|
|
144
|
+
`);let o=[w];if(i){let n=new CSSStyleSheet;n.replaceSync(i),o.push(n)}e.adoptedStyleSheets=o}function V(){if(typeof document>"u"||document.getElementById("pixl-ui-styles"))return;let e=document.createElement("style");e.id="pixl-ui-styles",e.textContent=L,document.head.prepend(e)}var pe='<svg viewBox="0 0 24 24" width="12" height="12" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M20 6 9 17l-5-5"/></svg>',ue=`
|
|
145
|
+
:host { display: inline-flex; vertical-align: middle; }
|
|
146
|
+
.control {
|
|
147
|
+
display: inline-flex; align-items: center; gap: 8px; cursor: pointer; outline: none;
|
|
148
|
+
border-radius: 6px; -webkit-tap-highlight-color: transparent;
|
|
149
|
+
}
|
|
150
|
+
.box {
|
|
151
|
+
display: inline-flex; align-items: center; justify-content: center; flex: none;
|
|
152
|
+
width: 16px; height: 16px; border-radius: 5px; border: 1px solid color-mix(in oklch, var(--fg) 30%, transparent);
|
|
153
|
+
background: rgb(0 0 0 / 0.2); color: white; transition: border-color .15s, background .15s, box-shadow .15s;
|
|
154
|
+
}
|
|
155
|
+
.box svg { visibility: hidden; }
|
|
156
|
+
.control:hover .box { border-color: color-mix(in oklch, var(--fg) 50%, transparent); }
|
|
157
|
+
.control[aria-checked="true"] .box {
|
|
158
|
+
border-color: transparent;
|
|
159
|
+
background: var(--accent);
|
|
160
|
+
background-image: linear-gradient(oklch(from var(--accent) calc(l + 0.04) calc(c * 0.92) h), var(--accent));
|
|
161
|
+
box-shadow: inset 0 1px 0 rgb(255 255 255 / 0.22), 0 1px 2px rgb(0 0 0 / 0.35);
|
|
162
|
+
}
|
|
163
|
+
.control[aria-checked="true"] .box svg { visibility: visible; }
|
|
164
|
+
.control:focus-visible .box { outline: 2px solid var(--accent); outline-offset: 2px; }
|
|
165
|
+
.control[aria-disabled="true"] { cursor: not-allowed; opacity: 0.45; }
|
|
166
|
+
.text { display: inline; min-width: 0; }
|
|
167
|
+
.text.empty { display: none; }
|
|
168
|
+
`;class O extends HTMLElement{static formAssociated=!0;static observedAttributes=["checked","disabled","aria-label"];root;control;internals;formDisabled=!1;defaultChecked=null;toggledThisTask=!1;constructor(){super();this.internals=typeof this.attachInternals==="function"?this.attachInternals():null,this.root=this.attachShadow({mode:"open",delegatesFocus:!0}),m(this.root,ue),this.root.innerHTML=`<span class="control" part="control" role="checkbox" tabindex="0"><span class="box" part="box">${pe}</span><span class="text empty" part="label"><slot></slot></span></span>`,this.control=this.root.querySelector(".control"),this.root.querySelector("slot").addEventListener("slotchange",()=>this.syncText()),this.addEventListener("click",(e)=>{let i=e.composedPath()[0];if(i instanceof Element&&i!==this&&i.closest("a, button, input, select, textarea"))return;this.toggleByUser()}),this.control.addEventListener("keydown",(e)=>{if(e.key!==" ")return;if(e.preventDefault(),!e.repeat)this.toggleByUser()})}connectedCallback(){if(Object.prototype.hasOwnProperty.call(this,"checked")){let e=this.checked;delete this.checked,this.checked=e===!0}this.defaultChecked??=this.hasAttribute("checked"),this.sync(),this.syncText()}syncText(){let e=this.root.querySelector("slot");if(!e)return;let i=e.assignedNodes().some((o)=>o.nodeType===Node.ELEMENT_NODE||o.textContent?.trim());this.root.querySelector(".text").classList.toggle("empty",!i)}attributeChangedCallback(){this.sync()}get checked(){return this.hasAttribute("checked")}set checked(e){this.toggleAttribute("checked",Boolean(e))}get disabled(){return this.hasAttribute("disabled")}set disabled(e){this.toggleAttribute("disabled",Boolean(e))}get name(){return this.getAttribute("name")??""}get value(){return this.getAttribute("value")??"on"}get form(){return this.internals?.form??null}formResetCallback(){this.checked=this.defaultChecked===!0}formDisabledCallback(e){this.formDisabled=e,this.sync()}formStateRestoreCallback(e){this.checked=e!==null}get isDisabled(){return this.disabled||this.formDisabled}toggleByUser(){if(this.isDisabled||this.toggledThisTask)return;this.toggledThisTask=!0,setTimeout(()=>this.toggledThisTask=!1),this.checked=!this.checked,this.dispatchEvent(new Event("input",{bubbles:!0,composed:!0})),this.dispatchEvent(new Event("change",{bubbles:!0,composed:!0}))}sync(){if(!this.control)return;let e=this.isDisabled;if(this.control.setAttribute("aria-checked",String(this.checked)),e)this.control.setAttribute("aria-disabled","true");else this.control.removeAttribute("aria-disabled");this.control.tabIndex=e?-1:0;let i=this.textContent?.trim()?"":[...this.internals?.labels??[]].map((n)=>n.textContent?.trim()??"").filter(Boolean).join(" "),o=this.getAttribute("aria-label")??i;if(o)this.control.setAttribute("aria-label",o);else this.control.removeAttribute("aria-label");this.internals?.setFormValue(this.checked?this.value:null)}}var T=null;function Y(e){T=e}function M(){if(!T)throw Error("[pixl] pixl-ui braucht das SDK: pixl.js vor pixl-ui.js einbinden");return T.shared()}var X=null;function x(){return X??=(async()=>{let e=M(),i=e.widget();if(i)return i;return await e.ready.catch(()=>{}),e.widget()})(),X}var A=0;function k(e){if(typeof window>"u"||window.parent===window)return;window.parent.postMessage({type:"pixl:fullscreen",on:e},"*")}var J="";function Q(){if(A++>0)return;J=document.documentElement.style.overflow,document.documentElement.style.overflow="hidden",k(!0)}function Z(){if(A===0||--A>0)return;document.documentElement.style.overflow=J,k(!1)}var he=`
|
|
169
|
+
dialog {
|
|
170
|
+
border: 1px solid var(--line); border-radius: 14px; background: var(--surface); color: var(--fg);
|
|
171
|
+
padding: 0; width: min(560px, calc(100vw - 16px)); max-height: calc(100dvh - 16px); overflow: hidden;
|
|
172
|
+
}
|
|
173
|
+
:host([size="large"]) dialog { width: min(960px, calc(100vw - 16px)); }
|
|
174
|
+
dialog[open] { display: flex; flex-direction: column; }
|
|
175
|
+
dialog::backdrop { background: rgb(0 0 0 / 0.6); }
|
|
176
|
+
header { padding: 18px 20px 6px; }
|
|
177
|
+
h2 { margin: 0 0 4px; font-size: 17px; }
|
|
178
|
+
header p { margin: 0; color: var(--muted); }
|
|
179
|
+
header p:empty { display: none; }
|
|
180
|
+
.body { padding: 12px 20px 16px; overflow: auto; flex: 1; min-height: 0; }
|
|
181
|
+
footer { display: flex; justify-content: flex-end; flex-wrap: wrap; gap: 8px; padding: 12px 20px; border-top: 1px solid var(--line); background: rgb(0 0 0 / 0.15); }
|
|
182
|
+
footer.empty { display: none; }
|
|
183
|
+
@media (max-width: 640px) {
|
|
184
|
+
dialog { width: 100%; max-width: 100%; height: fit-content; max-height: 100%; margin: auto 0 0; border-radius: 14px 14px 0 0; }
|
|
185
|
+
:host([size="large"]) dialog { width: 100%; height: 100%; margin: 0; border: 0; border-radius: 0; }
|
|
186
|
+
footer { padding-bottom: calc(12px + env(safe-area-inset-bottom)); }
|
|
187
|
+
}
|
|
188
|
+
`;class P extends HTMLElement{static observedAttributes=["heading","description"];root;dialog;isOpen=!1;constructor(){super();this.root=this.attachShadow({mode:"open"}),m(this.root,he),this.root.innerHTML=`
|
|
189
|
+
<dialog part="dialog">
|
|
190
|
+
<header><h2 part="heading"></h2><p part="description"></p></header>
|
|
191
|
+
<div class="body" part="body"><slot></slot></div>
|
|
192
|
+
<footer part="actions"><slot name="actions"></slot></footer>
|
|
193
|
+
</dialog>`,this.dialog=this.root.querySelector("dialog"),this.dialog.addEventListener("close",()=>this.closed());let e=this.root.querySelector('slot[name="actions"]'),i=this.root.querySelector("footer"),o=()=>i.classList.toggle("empty",e.assignedElements().length===0);e.addEventListener("slotchange",o),o(),this.sync()}attributeChangedCallback(){this.sync()}sync(){this.root.querySelector("h2").textContent=this.getAttribute("heading")??"",this.root.querySelector("header p").textContent=this.getAttribute("description")??""}get opened(){return this.isOpen}open(){if(this.isOpen)return;this.isOpen=!0,Q(),this.dialog.showModal(),this.dispatchEvent(new Event("open"))}close(){if(this.isOpen)this.dialog.close()}closed(){if(!this.isOpen)return;this.isOpen=!1,Z(),this.dispatchEvent(new Event("close"))}disconnectedCallback(){this.close()}}var N=32,ee=10;function te(e){if(!Array.isArray(e))return[];let i=[];for(let o of e)if(typeof o==="string"||typeof o==="number")i.push({value:String(o),label:String(o)});else if(o&&typeof o==="object"&&"value"in o){let{value:n,label:t,disabled:r}=o;i.push({value:String(n),label:t===void 0||t===null?String(n):String(t),disabled:r===!0})}return i}function E(e,i,o){for(let n=i+o;n>=0&&n<e.length;n+=o)if(!e[n].disabled)return n;return-1}function S(e){return E(e,-1,1)}function ie(e){return E(e,e.length,-1)}function ne(e,i,o){if(!i||!e.length)return-1;let n=i.toLocaleLowerCase("de"),t=[...n].every((c)=>c===n[0]),r=t?n[0]:n,s=e.length,a=t?o+1:Math.max(o,0);for(let c=0;c<s;c++){let p=((a+c)%s+s)%s,l=e[p];if(!l.disabled&&l.label.toLocaleLowerCase("de").startsWith(r))return p}return-1}function oe(e,i,o,n=320){let t=o.height-e.bottom-6-8,r=e.top-6-8,s=i>t&&r>t,a=Math.max(s?r:t,42),c=Math.min(i,n,a),p=Math.min(Math.max(e.width,160),Math.max(o.width-16,0)),l=Math.min(Math.max(e.left,8),Math.max(o.width-8-p,8));return{top:s?e.top-6-c:e.bottom+6,left:l,width:p,maxHeight:Math.min(n,a),above:s}}var ge='<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m6 9 6 6 6-6"/></svg>',me='<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M20 6 9 17l-5-5"/></svg>',be=`
|
|
194
|
+
:host { display: inline-block; vertical-align: middle; width: 100%; min-width: 0; }
|
|
195
|
+
.trigger {
|
|
196
|
+
display: flex; align-items: center; gap: 8px; width: 100%; min-width: 0; height: 36px;
|
|
197
|
+
padding: 0 8px 0 12px; border-radius: 10px; border: 1px solid var(--line-strong);
|
|
198
|
+
background: rgb(0 0 0 / 0.2); color: var(--fg); font: inherit; font-size: 14px; text-align: left;
|
|
199
|
+
cursor: pointer; outline: none; transition: border-color .15s, box-shadow .15s;
|
|
200
|
+
}
|
|
201
|
+
.trigger:hover:not(:disabled) { border-color: color-mix(in oklch, var(--fg) 30%, transparent); }
|
|
202
|
+
.trigger:focus-visible {
|
|
203
|
+
border-color: var(--accent); box-shadow: 0 0 0 3px color-mix(in oklch, var(--accent) 25%, transparent);
|
|
204
|
+
}
|
|
205
|
+
.trigger[aria-expanded="true"] { border-color: var(--accent); }
|
|
206
|
+
.trigger:disabled { cursor: not-allowed; opacity: 0.5; }
|
|
207
|
+
.text { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
208
|
+
.text.empty { color: color-mix(in oklch, var(--muted) 70%, transparent); }
|
|
209
|
+
.chevron { display: flex; flex: none; color: var(--muted); }
|
|
210
|
+
.list {
|
|
211
|
+
position: fixed; inset: auto; margin: 0; z-index: 2147483000; box-sizing: border-box;
|
|
212
|
+
overflow-x: hidden; overflow-y: auto; overscroll-behavior: contain;
|
|
213
|
+
padding: 4px; border: 1px solid var(--line); border-radius: 10px;
|
|
214
|
+
background: var(--surface); color: var(--fg); font: inherit; font-size: 14px;
|
|
215
|
+
box-shadow: 0 16px 40px -12px rgb(0 0 0 / 0.7); outline: none;
|
|
216
|
+
scrollbar-width: thin; scrollbar-color: rgb(255 255 255 / 0.15) transparent;
|
|
217
|
+
}
|
|
218
|
+
.list.fallback:not(.open) { display: none; }
|
|
219
|
+
.list:popover-open, .list.fallback.open { animation: pixl-pop-in 140ms cubic-bezier(0.2, 0.9, 0.3, 1.2) both; }
|
|
220
|
+
.list.above { --pop-y: -4px; }
|
|
221
|
+
@keyframes pixl-pop-in { from { opacity: 0; transform: translateY(var(--pop-y, 4px)) scale(0.98); } }
|
|
222
|
+
@media (prefers-reduced-motion: reduce) { .list:popover-open, .list.fallback.open { animation: none; } }
|
|
223
|
+
.option {
|
|
224
|
+
display: flex; align-items: center; gap: 8px; height: ${N}px; padding: 0 8px;
|
|
225
|
+
border-radius: 6px; cursor: default; user-select: none;
|
|
226
|
+
}
|
|
227
|
+
.option[data-highlighted] { background: rgb(255 255 255 / 0.06); }
|
|
228
|
+
.option[aria-disabled="true"] { opacity: 0.45; }
|
|
229
|
+
.option .label { display: block; flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
230
|
+
.option .check { display: flex; flex: none; color: var(--accent); }
|
|
231
|
+
.option:not([aria-selected="true"]) .check { visibility: hidden; }
|
|
232
|
+
.none { padding: 6px 8px; color: var(--muted); }
|
|
233
|
+
@media (pointer: coarse) { .trigger { height: 40px; } }
|
|
234
|
+
`,f=typeof HTMLElement<"u"&&"showPopover"in HTMLElement.prototype,fe=0;class I extends HTMLElement{static formAssociated=!0;static observedAttributes=["value","placeholder","disabled","aria-label"];root;trigger;text;list;internals;uid=`pixl-dropdown-${++fe}`;propOptions=null;childOptions=[];observer=null;highlighted=-1;query="";queryTimer;formDisabled=!1;defaultValue=null;listening=!1;frame=0;lastRect="";constructor(){super();if(this.internals=typeof this.attachInternals==="function"?this.attachInternals():null,this.root=this.attachShadow({mode:"open",delegatesFocus:!0}),m(this.root,be),this.root.innerHTML=`
|
|
235
|
+
<button type="button" class="trigger" part="trigger" role="combobox" aria-haspopup="listbox" aria-expanded="false">
|
|
236
|
+
<span class="text"></span><span class="chevron">${ge}</span>
|
|
237
|
+
</button>
|
|
238
|
+
<div class="list" part="list" role="listbox" tabindex="-1"></div>`,this.trigger=this.root.querySelector("button"),this.text=this.root.querySelector(".text"),this.list=this.root.querySelector(".list"),this.list.id=`${this.uid}-list`,this.trigger.setAttribute("aria-controls",this.list.id),f)this.list.setAttribute("popover","auto"),this.trigger.popoverTargetElement=this.list,this.list.addEventListener("beforetoggle",(e)=>{if(e.newState==="open")this.beforeOpen()}),this.list.addEventListener("toggle",(e)=>{if(e.newState==="open")this.afterOpen();else this.afterClose()});else this.list.classList.add("fallback"),this.trigger.addEventListener("click",()=>this.isOpen?this.close():this.open());this.trigger.addEventListener("keydown",(e)=>this.onKeydown(e)),this.trigger.addEventListener("keyup",(e)=>e.key===" "&&e.preventDefault()),this.list.addEventListener("mousedown",(e)=>e.preventDefault()),this.list.addEventListener("click",(e)=>{let i=e.target.closest(".option");if(i)this.pick(Number(i.dataset.index))}),this.list.addEventListener("mousemove",(e)=>{let i=e.target.closest(".option");if(i&&i.getAttribute("aria-disabled")!=="true")this.highlight(Number(i.dataset.index),!1)}),this.addEventListener("click",(e)=>{if(e.composedPath()[0]===this)this.trigger.focus()})}connectedCallback(){for(let e of["options","value"])if(Object.prototype.hasOwnProperty.call(this,e)){let i=this[e];delete this[e],this[e]=i}this.defaultValue??=this.getAttribute("value"),this.readChildren(),this.observer??=new MutationObserver(()=>this.readChildren()),this.observer.observe(this,{childList:!0,subtree:!0,characterData:!0,attributes:!0,attributeFilter:["value","label","disabled","selected"]}),this.render()}disconnectedCallback(){if(this.observer?.disconnect(),f){if(this.list.matches(":popover-open"))this.list.hidePopover()}else this.list.classList.remove("open");if(this.listening)this.afterClose()}attributeChangedCallback(){this.render()}get options(){return this.propOptions??this.childOptions}set options(e){this.propOptions=e===null||e===void 0?null:te(e),this.render()}get value(){let e=this.getAttribute("value");if(e!==null)return e;if(this.hasAttribute("placeholder"))return"";let i=this.propOptions?null:this.querySelector("option[selected]");if(i)return i.value;let o=S(this.options);return o>=0?this.options[o].value:""}set value(e){this.setAttribute("value",e===null||e===void 0?"":String(e))}get disabled(){return this.hasAttribute("disabled")}set disabled(e){this.toggleAttribute("disabled",Boolean(e))}get name(){return this.getAttribute("name")??""}get selectedLabel(){return this.options.find((e)=>e.value===this.value)?.label??""}get form(){return this.internals?.form??null}open(){if(this.isOpen||this.trigger.disabled||!this.isConnected)return;if(f)this.list.showPopover();else this.beforeOpen(),this.list.classList.add("open"),this.afterOpen()}close(){if(!this.isOpen)return;if(f)this.list.hidePopover();else this.list.classList.remove("open"),this.afterClose()}get isOpen(){return f?this.list.matches(":popover-open"):this.list.classList.contains("open")}formResetCallback(){if(this.defaultValue===null)this.removeAttribute("value");else this.setAttribute("value",this.defaultValue)}formDisabledCallback(e){this.formDisabled=e,this.render()}formStateRestoreCallback(e){if(typeof e==="string")this.value=e}readChildren(){let e=[...this.querySelectorAll("option")].map((i)=>({value:i.value,label:i.label||i.textContent?.trim()||i.value,disabled:i.disabled}));if(JSON.stringify(e)===JSON.stringify(this.childOptions))return;this.childOptions=e,this.render()}render(){if(!this.trigger)return;let e=this.options,i=this.value,o=e.find((t)=>t.value===i);this.text.textContent=o?.label??this.getAttribute("placeholder")??"Auswählen",this.text.classList.toggle("empty",!o),this.trigger.disabled=this.disabled||this.formDisabled;let n=this.getAttribute("aria-label")??this.labelText();if(n)this.trigger.setAttribute("aria-label",n);else this.trigger.removeAttribute("aria-label");if(this.internals?.setFormValue(o||i?i:null),this.trigger.disabled)this.close();this.renderList()}labelText(){let e=this.internals?.labels;if(!e?.length)return"";return[...e].map((i)=>i.textContent?.trim()??"").filter(Boolean).join(" ")}renderList(){let e=this.options,i=this.value;if(!e.length){this.list.innerHTML='<div class="none">Keine Einträge</div>',this.highlight(-1,!1);return}if(this.list.replaceChildren(...e.map((o,n)=>{let t=document.createElement("div");if(t.className="option",t.id=`${this.uid}-o${n}`,t.dataset.index=String(n),t.setAttribute("role","option"),t.setAttribute("aria-selected",String(o.value===i)),o.disabled)t.setAttribute("aria-disabled","true");let r=document.createElement("span");r.className="label",r.textContent=o.label;let s=document.createElement("span");return s.className="check",s.innerHTML=me,t.append(r,s),t})),this.highlighted>=e.length)this.highlighted=-1;this.highlight(this.highlighted,!1)}beforeOpen(){let e=this.options,i=e.findIndex((o)=>o.value===this.value&&!o.disabled);this.highlighted=i>=0?i:S(e),this.renderList(),this.position()}afterOpen(){if(this.trigger.setAttribute("aria-expanded","true"),this.highlight(this.highlighted,!0),this.listening)return;if(this.listening=!0,addEventListener("resize",this.onViewportChange,{passive:!0}),!f)document.addEventListener("pointerdown",this.onOutsidePointer,!0);this.lastRect=this.rectKey(),this.frame=requestAnimationFrame(this.follow)}afterClose(){if(this.trigger.setAttribute("aria-expanded","false"),this.trigger.removeAttribute("aria-activedescendant"),this.listening=!1,cancelAnimationFrame(this.frame),removeEventListener("resize",this.onViewportChange),!f)document.removeEventListener("pointerdown",this.onOutsidePointer,!0)}onViewportChange=()=>this.position();rectKey(){let e=this.trigger.getBoundingClientRect();return`${e.top},${e.left},${e.width},${e.height}`}follow=()=>{if(!this.listening)return;let e=this.rectKey();if(e!==this.lastRect)this.lastRect=e,this.position();this.frame=requestAnimationFrame(this.follow)};onOutsidePointer=(e)=>{if(!e.composedPath().includes(this))this.close()};position(){let e=this.trigger.getBoundingClientRect(),i=Math.max(this.options.length,1)*N+ee,o=oe(e,i,{width:innerWidth,height:innerHeight}),n=this.list.style;n.top=`${o.top}px`,n.left=`${o.left}px`,n.width=`${o.width}px`,n.maxHeight=`${o.maxHeight}px`,this.list.classList.toggle("above",o.above)}highlight(e,i){this.highlighted=e;for(let n of this.list.querySelectorAll(".option"))n.toggleAttribute("data-highlighted",Number(n.dataset.index)===e);let o=e>=0?this.list.querySelector(`#${this.uid}-o${e}`):null;if(o&&this.isOpen){if(this.trigger.setAttribute("aria-activedescendant",o.id),i)o.scrollIntoView({block:"nearest"})}else this.trigger.removeAttribute("aria-activedescendant")}pick(e){let i=this.options[e];if(!i||i.disabled)return;let o=i.value!==this.value;if(this.value=i.value,this.close(),this.trigger.focus(),!o)return;this.dispatchEvent(new Event("input",{bubbles:!0,composed:!0})),this.dispatchEvent(new Event("change",{bubbles:!0,composed:!0}))}onKeydown(e){if(e.altKey||e.ctrlKey||e.metaKey){if(e.altKey&&e.key==="ArrowUp"&&this.isOpen)e.preventDefault(),this.pick(this.highlighted);return}let i=this.options,o=this.isOpen,n=(t)=>{if(t>=0)this.highlight(t,!0)};switch(e.key){case"ArrowDown":case"ArrowUp":if(e.preventDefault(),!o)return this.open();return n(E(i,this.highlighted,e.key==="ArrowDown"?1:-1));case"Home":case"PageUp":if(!o)return;return e.preventDefault(),n(S(i));case"End":case"PageDown":if(!o)return;return e.preventDefault(),n(ie(i));case"Enter":return e.preventDefault(),o?this.pick(this.highlighted):this.open();case" ":if(e.preventDefault(),this.query)return this.typeahead(" ");return o?this.pick(this.highlighted):this.open();case"Escape":if(!o)return;return e.preventDefault(),e.stopPropagation(),this.close();case"Tab":if(o)this.close();return;default:if(e.key.length===1)e.preventDefault(),this.typeahead(e.key)}}typeahead(e){clearTimeout(this.queryTimer),this.query+=e,this.queryTimer=setTimeout(()=>this.query="",500);let i=this.options,o=this.isOpen?this.highlighted:i.findIndex((t)=>t.value===this.value),n=ne(i,this.query,o);if(n<0)return;if(this.isOpen)this.highlight(n,!0);else if(i[n].value!==this.value)this.value=i[n].value,this.dispatchEvent(new Event("input",{bubbles:!0,composed:!0})),this.dispatchEvent(new Event("change",{bubbles:!0,composed:!0}))}}var xe=[100,200,300,400,500,600,700,800,900].flatMap((e)=>[`${e}`,`${e}i`]).join(",");function ve(e){return`https://fonts.bunny.net/css?family=${encodeURIComponent(e.trim())}:${xe}&display=swap`}function H(e,i="sans-serif"){let o=typeof e==="string"?e.trim():"";if(!o)return i;return`"${o.replace(/["\\]/g,"\\$&")}", ${i}`}var re=new Map;function se(e){let i=typeof e==="string"?e.trim():"";if(!i||typeof document>"u")return Promise.resolve(!1);let o=re.get(i);if(!o)o=new Promise((n)=>{let t=document.createElement("link");t.rel="stylesheet",t.href=ve(i),t.dataset.pixlFont=i,t.onload=()=>{let r=document.fonts;if(!r)return n(!0);r.load(`16px ${H(i)}`).then((s)=>n(s.length>0),()=>n(!1))},t.onerror=()=>n(!1),document.head.append(t)}),re.set(i,o);return o}function u(e,i={},...o){let n=document.createElement(e);for(let[t,r]of Object.entries(i))if(t.startsWith("aria-")||t==="role"||t==="type")n.setAttribute(t,String(r));else n[t]=r;return n.append(...o),n}function v(e,i,o,n){switch(e.type){case"boolean":{let t=u("button",{type:"button",className:"switch",role:"switch","aria-label":n.label}),r=i===!0,s=()=>t.setAttribute("aria-checked",String(r));return t.onclick=()=>{r=!r,s(),o.set(r)},s(),{el:t,update:(a)=>{r=a===!0,s()}}}case"number":{if(e.slider)return Ee(e,i,o,n);let t=e.step??1,r=u("output",{className:"count"},String(i??0)),s=typeof i==="number"?i:0,a=(d)=>{if(o.add)o.add(d);else s+=d,r.textContent=String(s),o.set(s);l()},c=u("button",{type:"button",className:"btn icon","aria-label":`${n.label} verringern`},"−"),p=u("button",{type:"button",className:"btn icon","aria-label":`${n.label} erhöhen`},"+"),l=()=>{c.disabled=e.min!==void 0&&s<=e.min,p.disabled=e.max!==void 0&&s>=e.max};return c.onclick=()=>a(-t),p.onclick=()=>a(t),l(),{el:u("div",{className:"control"},c,r,p),update:(d)=>{s=typeof d==="number"?d:0,r.textContent=String(s),l()}}}case"enum":{let t=document.createElement("pixl-dropdown");return t.setAttribute("aria-label",n.label),t.options=e.options.map((r)=>({value:r,label:r})),t.value=String(i??e.options[0]??""),t.addEventListener("change",()=>o.set(t.value)),{el:t,update:(r)=>t.value=String(r??"")}}case"string":{if(e.sensitive)return Oe(e,i,o,n);let t=e.multiline?u("textarea",{className:"input","aria-label":n.label}):u("input",{className:"input","aria-label":n.label});if(e.maxLength)t.maxLength=e.maxLength;t.value=typeof i==="string"?i:"";let r=t.value,s=()=>{if(t.value===r)return;r=t.value,o.set(t.value)};return t.onblur=s,t.onkeydown=(a)=>{if(a instanceof KeyboardEvent&&a.key==="Enter"&&!e.multiline)a.preventDefault(),s()},{el:t,update:(a)=>{if(document.activeElement===t)return;t.value=typeof a==="string"?a:"",r=t.value}}}case"color":{let t=u("input",{type:"color","aria-label":n.label}),r=u("input",{className:"input","aria-label":`${n.label} als Hex`,maxLength:9});r.style.fontFamily="ui-monospace, monospace";let s=(a)=>{let c=typeof a==="string"?a:"#ffffff";if(t.value=c.slice(0,7),document.activeElement!==r)r.value=c};return s(i),t.onchange=()=>{r.value=t.value,o.set(t.value)},r.onchange=()=>/^#[0-9a-f]{6}([0-9a-f]{2})?$/i.test(r.value)&&o.set(r.value),{el:u("div",{className:"control"},t,r),update:s}}case"file":return Se(e,i,o,n);case"font":return ke(i,o,n);case"voice":return Ae(i,o,n);case"trigger":{let t=u("button",{type:"button",className:"btn"},n.label);return t.onclick=()=>o.press?.(),{el:t,update:()=>{}}}}}var ye="Roboto,Open Sans,Inter,Montserrat,Poppins,Lato,Arimo,Roboto Condensed,Roboto Mono,Oswald,Noto Sans,DM Sans,Raleway,Nunito Sans,Playfair Display,Nunito,Manrope,Roboto Slab,Ubuntu,Plus Jakarta Sans,JetBrains Mono,Kanit,Mulish,Figtree,PT Sans,Rubik,Black Ops One,Archivo Black,Saira,Outfit,Source Sans 3,Jost,Quicksand,Source Code Pro,Inconsolata,Merriweather,Prompt,Share Tech,Fira Sans,Lora,Space Grotesk,Smooch Sans,Work Sans,Archivo,Karla,Bebas Neue,Noto Serif,Heebo,IBM Plex Sans,Press Start 2P".split(","),we=0;function ke(e,i,o){let n=`pixl-fonts-${++we}`,t=u("input",{className:"input","aria-label":o.label,placeholder:"Schrift des Widgets",autocomplete:"off",spellcheck:!1,maxLength:100});t.setAttribute("list",n);let r=u("datalist",{id:n},...ye.map((l)=>u("option",{value:l}))),s="",a="",c=(l)=>{let d=typeof l==="string"?l:"";if(s=d,document.activeElement!==t)t.value=d;if(a===d)return;if(a="",t.style.fontFamily="",!d)return;se(d).then(()=>{if(s!==d)return;a=d,t.style.fontFamily=H(d,"system-ui, sans-serif")})},p=()=>{let l=(t.value.split(",")[0]??"").replace(/\s+/g," ").replace(/[\p{Cc}"'`;{}<>\\]/gu,"").replace(/ {2,}/g," ").trim().slice(0,100).trim();if(t.value=l,l===s)return;c(l),i.set(l)};return t.onblur=p,t.onkeydown=(l)=>{if(l instanceof KeyboardEvent&&l.key==="Enter")l.preventDefault(),p()},c(e),{el:u("div",{className:"control",style:"width:100%"},t,r),update:c}}function Ee(e,i,o,n){let t=e.min??0,r=u("input",{type:"range",min:String(t),max:String(e.max??100),step:String(e.step??1),value:String(i??t),"aria-label":n.label}),s=u("span",{className:"value"},String(i??t));return r.oninput=()=>s.textContent=r.value,r.onchange=()=>o.set(Number(r.value)),{el:u("div",{className:"control"},r,s),update:(a)=>{if(document.activeElement===r)return;r.value=String(a),s.textContent=String(a)}}}function Se(e,i,o,n){let t=n.widget,r=u("img",{className:"thumb",alt:""}),s=u("input",{className:"input",placeholder:"oder https://…","aria-label":`${n.label}, Adresse`}),a=u("input",{type:"file",accept:e.accept??"",hidden:!0}),c=u("button",{type:"button",className:"btn"},"Hochladen"),p=u("button",{type:"button",className:"btn ghost"},"Entfernen"),l=typeof i==="string"?i:"",d=()=>{let h=l&&t?t.fileUrl(l):l;if(r.hidden=!h,h)r.src=h;if(p.hidden=!l,document.activeElement!==s)s.value=l.startsWith("https://")?l:""},g=(h)=>{l=h,d(),o.set(h)};return c.onclick=()=>a.click(),a.onchange=async()=>{let h=a.files?.[0];if(a.value="",!h||!t)return;c.disabled=!0,c.textContent="Lädt hoch …";try{g((await t.files.upload(h)).path)}catch(y){console.error("[pixl] Hochladen fehlgeschlagen:",y),alert("Hochladen fehlgeschlagen")}finally{c.disabled=!1,c.textContent="Hochladen"}},p.onclick=()=>g(""),s.onchange=()=>{let h=s.value.trim();if(!h||h.startsWith("https://"))g(h)},d(),{el:u("div",{className:"control",style:"align-items:flex-start;width:100%"},r,u("div",{className:"stack"},u("div",{className:"row"},c,p,a),s)),update:(h)=>{l=typeof h==="string"?h:"",d()}}}var Ce='<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M2.06 12.35a1 1 0 0 1 0-.7 10.75 10.75 0 0 1 19.88 0 1 1 0 0 1 0 .7 10.75 10.75 0 0 1-19.88 0"/><circle cx="12" cy="12" r="3"/></svg>',Le='<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M10.73 5.08A10.43 10.43 0 0 1 12 5c7 0 10 7 10 7a13.16 13.16 0 0 1-1.67 2.68"/><path d="M6.61 6.61A13.53 13.53 0 0 0 2 12s3 7 10 7a9.74 9.74 0 0 0 5.39-1.61"/><path d="M9.88 9.88a3 3 0 1 0 4.24 4.24"/><path d="m2 2 20 20"/></svg>';function Oe(e,i,o,n){let t=u("input",{type:"password",className:"input secret","aria-label":n.label,autocomplete:"off",autocapitalize:"off",spellcheck:!1});for(let d of["data-1p-ignore","data-bwignore"])t.setAttribute(d,"");if(t.setAttribute("data-lpignore","true"),t.setAttribute("data-form-type","other"),e.maxLength)t.maxLength=e.maxLength;let r=u("button",{type:"button",className:"btn ghost icon eye"}),s=u("div",{className:"secret-wrap"},t,r),a=!1,c=(d)=>{a=d,t.type=d?"text":"password",r.innerHTML=d?Le:Ce,r.setAttribute("aria-label",d?`${n.label} verbergen`:`${n.label} anzeigen`),r.setAttribute("aria-pressed",String(d))};c(!1),r.onclick=()=>c(!a),s.addEventListener("focusout",(d)=>{if(!s.contains(d.relatedTarget))c(!1)}),t.value=typeof i==="string"?i:"";let p=t.value,l=()=>{let d=t.value.trim();if(t.value=d,d===p)return;p=d,o.set(d)};return t.onblur=l,t.onkeydown=(d)=>{if(d instanceof KeyboardEvent&&d.key==="Enter")d.preventDefault(),l()},{el:s,update:(d)=>{if(document.activeElement===t)return;t.value=typeof d==="string"?d:"",p=t.value}}}var Te={ai:" (KI)",custom:" (eigene)"},ae={standard:0,ai:1,custom:2};function Ae(e,i,o){let n=document.createElement("pixl-dropdown");n.setAttribute("aria-label",o.label),n.setAttribute("disabled","");let t=u("span",{className:"desc",hidden:!0}),r=typeof e==="string"?e:"",s=new Map,a="",c=()=>{let l=[{value:"",label:a?`Stimme des Kanals (${a})`:"Stimme des Kanals"},...[...s].map(([d,g])=>({value:d,label:g}))];if(r&&!s.has(r))l.push({value:r,label:"Unbekannte Stimme"});n.options=l,n.value=r};c(),n.addEventListener("change",()=>{r=n.value,i.set(r)});let p=o.widget;if(!p)t.textContent="Stimmen gibt es nur in gehosteten Widgets";else p.voices().then((l)=>{let d=[...l.voices].sort((g,h)=>(ae[g.tier??"standard"]??3)-(ae[h.tier??"standard"]??3)||g.name.localeCompare(h.name,"de"));if(s=new Map(d.map((g)=>[g.id,`${g.name}${Te[g.tier??""]??""}`])),a=l.voices.find((g)=>g.id===l.default)?.name??"",c(),l.enabled)n.removeAttribute("disabled");else t.textContent="TTS ist für diesen Kanal ausgeschaltet.",t.hidden=!1}).catch(()=>{t.textContent="Stimmen konnten nicht geladen werden.",t.hidden=!1});if(!p)t.hidden=!1;return{el:u("div",{className:"stack"},n,t),update:(l)=>{r=typeof l==="string"?l:"",c()}}}var Me=(e)=>e.type==="string"||e.type==="file"||e.type==="font"||e.type==="voice"||e.type==="number"&&!!e.slider,Pe={"pixl-toggle":[40,22,999],"pixl-counter":[116,34,999],"pixl-button":[120,34,999],"pixl-color":[120,34,8]};class C extends HTMLElement{widget=null;root;unsubscribe=null;connected=!1;constructor(){super();this.root=this.attachShadow({mode:"open"})}get fieldName(){return this.getAttribute("field")??""}get scope(){let e=this.getAttribute("scope");if(e==="settings"||e==="controls")return e;return this.widget?.manifest?.settings?.[this.fieldName]?"settings":"controls"}get definition(){let e=this.widget?.manifest,i=this.fieldName;return(this.scope==="settings"?e?.settings?.[i]:e?.controls?.[i])??null}connectedCallback(){if(this.connected=!0,!this.widget)this.setAttribute("aria-busy","true"),this.renderPlaceholder();x().then((e)=>{if(!this.connected)return;if(this.removeAttribute("aria-busy"),this.widget=e,!e)return this.renderMessage("Nur in gehosteten Widgets verfügbar");let i=this.scope==="settings"?e.settings[this.fieldName]:e.values[this.fieldName];this.render(),this.unsubscribe=i?.subscribe((o)=>this.onValue(o))??null})}disconnectedCallback(){this.connected=!1,this.unsubscribe?.(),this.unsubscribe=null}async write(e){if(!this.widget)return!1;try{return await this.widget.batch([{...e,name:this.fieldName,scope:this.scope}]),this.showError(null),!0}catch(i){return this.showError(i instanceof Error?i.message:"Speichern fehlgeschlagen"),!1}}showError(e){let i=this.root.querySelector(".error");if(!e)return i?.remove();if(!i)i=document.createElement("p"),i.className="error",i.setAttribute("role","alert"),this.root.querySelector(".field, .list")?.append(i);i.textContent=e}renderPlaceholder(){m(this.root);let[e,i,o]=Pe[this.localName]??[96,34,8],n=document.createElement("div");n.className="field";let t=this.getAttribute("label")??this.textContent?.trim();if(this.localName!=="pixl-button"){let s=document.createElement("span");if(t)s.className="label-text",s.textContent=t;else s.className="skeleton",s.style.cssText="height:14px;width:min(160px,45%)",s.setAttribute("aria-hidden","true");n.append(s)}let r=document.createElement("span");r.className="skeleton",r.style.cssText=`width:${e}px;height:${i}px;border-radius:${o}px`,r.setAttribute("aria-hidden","true"),n.append(r),this.root.replaceChildren(n)}renderMessage(e){m(this.root),this.root.innerHTML=`<p class="desc">${e}</p>`}}class b extends C{static observedAttributes=["field","scope","label","description"];input=null;attributeChangedCallback(){if(this.widget)this.render()}render(){m(this.root);let e=this.definition;if(!e)return this.renderMessage(`Feld „${this.fieldName}“ steht nicht in pixl.json`);if(e.type==="list")return this.renderMessage(`Für Listen <pixl-list field="${this.fieldName}"> nehmen`);let i=this.getAttribute("label")??(this.textContent?.trim()||e.label),o=this.getAttribute("description")??e.description??"",n=this.scope==="settings"?this.widget?.settings[this.fieldName]?.value:this.widget?.values[this.fieldName]?.value,t=document.createElement("div");if(t.className=`field${Me(e)?" wide":""}`,this.input=v(e,n,{set:(s)=>void this.write({set:s}),add:(s)=>void this.write({add:s}),press:()=>void this.write({press:!0})},{label:i,widget:this.widget}),e.type!=="trigger"){let s=document.createElement("div");if(s.className="label",s.innerHTML='<span class="label-text"><slot></slot></span>',s.querySelector("slot").textContent=i,o){let a=document.createElement("span");a.className="desc",a.textContent=o,s.append(a)}t.append(s)}let r=document.createElement("div");r.className="control",r.append(this.input.el),t.append(r),this.root.replaceChildren(t)}onValue(e){this.input?.update(e)}}class D extends b{}class F extends b{}class R extends b{}class j extends b{}class W extends b{}class B extends b{}class z extends b{}class q extends b{}var le=`
|
|
239
|
+
.list { display: flex; flex-direction: column; gap: 10px; }
|
|
240
|
+
/* Einträge vertieft in der Karte des Felds, statt eines zweiten Rahmens */
|
|
241
|
+
ul {
|
|
242
|
+
list-style: none; margin: 0; padding: 0; border-radius: var(--radius);
|
|
243
|
+
background: rgb(0 0 0 / 0.2); box-shadow: inset 0 1px 2px rgb(0 0 0 / 0.25);
|
|
244
|
+
}
|
|
245
|
+
li.row-item { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 12px; padding: 8px 12px; }
|
|
246
|
+
li.row-item + li.row-item { border-top: 1px solid var(--line); }
|
|
247
|
+
.name { flex: 1; min-width: 6rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
248
|
+
.actions { margin-left: auto; display: flex; gap: 2px; }
|
|
249
|
+
.img { width: 36px; height: 36px; border-radius: 6px; border: 1px solid var(--line); object-fit: contain; background: rgb(0 0 0 / 0.25); flex: none; }
|
|
250
|
+
.tiles {
|
|
251
|
+
display: flex; flex-wrap: wrap; gap: 8px; padding: 12px; border-radius: var(--radius); min-height: 72px; align-items: center;
|
|
252
|
+
background: rgb(0 0 0 / 0.2); box-shadow: inset 0 1px 2px rgb(0 0 0 / 0.25);
|
|
253
|
+
}
|
|
254
|
+
.tile { border: 0; padding: 0; background: transparent; color: inherit; font: inherit; cursor: grab; touch-action: pan-y; user-select: none; border-radius: 8px; }
|
|
255
|
+
.tile.dragging { opacity: 0.35; }
|
|
256
|
+
.tile.before { box-shadow: -4px 0 0 var(--accent); }
|
|
257
|
+
.tile.after { box-shadow: 4px 0 0 var(--accent); }
|
|
258
|
+
.empty { color: var(--muted); padding: 16px; text-align: center; border-radius: var(--radius); background: rgb(0 0 0 / 0.15); }
|
|
259
|
+
.tools { display: flex; flex-wrap: wrap; gap: 8px; }
|
|
260
|
+
.form { display: flex; flex-direction: column; gap: 14px; }
|
|
261
|
+
.form label { display: flex; flex-direction: column; gap: 6px; font-weight: 500; }
|
|
262
|
+
`;class _ extends C{static observedAttributes=["field","scope","layout"];renderItem=null;items=[];dialog=null;attributeChangedCallback(){if(this.widget)this.render()}get listField(){let e=this.definition;return e?.type==="list"?e:null}onValue(e){this.items=Array.isArray(e)?e:[],this.renderItems()}renderPlaceholder(){m(this.root,le);let e='<span class="skeleton" style="width:72px;height:72px"></span>',i='<li class="row-item"><span class="skeleton" style="height:14px;width:45%;margin:11px 0"></span></li>';this.root.innerHTML=this.getAttribute("layout")==="tiles"?`<div class="tiles" aria-hidden="true">${e.repeat(3)}</div>`:`<ul aria-hidden="true">${i.repeat(3)}</ul>`}render(){if(m(this.root,le),!this.listField)return this.renderMessage(`„${this.fieldName}“ ist in pixl.json kein list-Feld`);this.root.innerHTML=`
|
|
263
|
+
<div class="list">
|
|
264
|
+
<div class="items"></div>
|
|
265
|
+
<div class="tools">
|
|
266
|
+
<button type="button" class="btn add">+ Hinzufügen</button>
|
|
267
|
+
</div>
|
|
268
|
+
</div>
|
|
269
|
+
<pixl-dialog size="large"></pixl-dialog>`,this.dialog=this.root.querySelector("pixl-dialog"),this.root.querySelector(".add").onclick=()=>this.edit(null),this.renderItems()}labelOf(e){let i=this.listField,o=i?.itemLabel?e[i.itemLabel]:null;if(typeof o==="string"&&o)return o;for(let[n,t]of Object.entries(i?.item??{}))if((t.type==="string"||t.type==="enum")&&typeof e[n]==="string"&&e[n])return e[n];return"Ohne Namen"}renderItems(){let e=this.root.querySelector(".items"),i=this.listField;if(!e||!i)return;let o=this.root.querySelector(".add");if(o)o.disabled=this.items.length>=(i.max??200);if(!this.items.length){e.innerHTML='<p class="empty">Noch keine Einträge</p>';return}if(this.getAttribute("layout")==="tiles")return this.renderTiles(e);let n=document.createElement("ul");this.items.forEach((t,r)=>{let s=document.createElement("li");s.className="row-item";let a=this.renderItem?.(t,r);if(a!==void 0&&a!==null){let p=document.createElement("div");p.className="name",p.append(a),s.append(p)}else{let p=i.itemImage?t[i.itemImage]:null;if(i.itemImage){let d=document.createElement("img");if(d.className="img",d.alt="",typeof p==="string"&&p)d.src=this.widget?.fileUrl(p)??p;s.append(d)}let l=document.createElement("span");l.className="name",l.textContent=this.labelOf(t),s.append(l)}for(let[p,l]of Object.entries(i.item).filter(([,d])=>d.type==="boolean").slice(0,3)){let d=document.createElement("button");d.type="button",d.className="switch",d.setAttribute("role","switch"),d.setAttribute("aria-checked",String(t[p]===!0)),d.setAttribute("aria-label",`${l.label}: ${this.labelOf(t)}`),d.onclick=()=>this.op({update:t.id,field:p,toggle:!0}),s.append(d)}let c=document.createElement("div");c.className="actions",c.append(this.iconButton("↑",`${this.labelOf(t)} nach oben`,r===0,()=>this.op({move:t.id,to:r-1})),this.iconButton("↓",`${this.labelOf(t)} nach unten`,r===this.items.length-1,()=>this.op({move:t.id,to:r+1})),this.iconButton("✎",`${this.labelOf(t)} bearbeiten`,!1,()=>this.edit(r)),this.iconButton("✕",`${this.labelOf(t)} löschen`,!1,()=>this.op({remove:t.id}))),s.append(c),n.append(s)}),e.replaceChildren(n)}renderTiles(e){let i=document.createElement("div");i.className="tiles",this.items.forEach((o,n)=>{let t=document.createElement("button");t.type="button",t.className="tile",t.setAttribute("aria-label",`${this.labelOf(o)} bearbeiten`);let r=this.renderItem?.(o,n);t.append(r??this.labelOf(o)),this.attachDrag(t,n,i),i.append(t)}),e.replaceChildren(i)}attachDrag(e,i,o){let n=null,t=!1,r=null;e.addEventListener("pointerdown",(a)=>{if(a.button!==0)return;n={x:a.clientX,y:a.clientY},t=!1,r=null,e.setPointerCapture(a.pointerId)}),e.addEventListener("pointermove",(a)=>{if(!n)return;if(!t&&Math.hypot(a.clientX-n.x,a.clientY-n.y)<8)return;t=!0,e.classList.add("dragging");let c=[...o.querySelectorAll(".tile")],p=null;if(c.forEach((d,g)=>{d.classList.remove("before","after");let h=d.getBoundingClientRect(),y=Math.hypot(a.clientX-(h.left+h.width/2),a.clientY-(h.top+h.height/2));if(!p||y<p.d)p={i:g,d:y,after:a.clientX>h.left+h.width/2}}),!p)return;let l=p;c[l.i].classList.add(l.after?"after":"before"),r=l.after?l.i+1:l.i});let s=(a)=>{if(!n)return;if(n=null,a)return this.renderItems();if(!t)return this.edit(i);if(r===null)return this.renderItems();let c=Math.max(0,Math.min(r>i?r-1:r,this.items.length-1));if(c===i)return this.renderItems();this.op({move:this.items[i].id,to:c})};e.addEventListener("pointerup",()=>s(!1)),e.addEventListener("pointercancel",()=>s(!0))}iconButton(e,i,o,n){let t=document.createElement("button");return t.type="button",t.className="btn ghost icon",t.textContent=e,t.disabled=o,t.setAttribute("aria-label",i),t.onclick=n,t}op(e){return this.write(e)}edit(e){let i=this.listField;if(!i)return;let o=e===null?null:this.items[e]??null,n=async(r)=>{if(o)return this.op({update:o.id,patch:r});return this.op({insert:r})},t=new CustomEvent("pixl-edit",{cancelable:!0,bubbles:!0,composed:!0,detail:{item:o,index:e,save:n}});if(!this.dispatchEvent(t))return;this.openBuiltInEditor(i,o,n)}openBuiltInEditor(e,i,o){let n=this.dialog;if(!n)return;let t={},r=document.createElement("form");r.className="form";for(let[c,p]of Object.entries(e.item)){t[c]=i?i[c]:p.default;let l=document.createElement("label");l.append(p.label);let d=v(p,t[c],{set:(g)=>t[c]=g},{label:p.label,widget:this.widget});l.append(d.el),r.append(l)}let s=document.createElement("button");s.type="button",s.className="btn",s.textContent="Abbrechen",s.slot="actions",s.onclick=()=>n.close();let a=document.createElement("button");a.type="button",a.className="btn primary",a.textContent=i?"Speichern":"Hinzufügen",a.slot="actions",a.onclick=async()=>{if(a.disabled=!0,await o(t))n.close();a.disabled=!1},r.onsubmit=(c)=>{c.preventDefault(),a.click()},n.setAttribute("heading",i?"Eintrag bearbeiten":"Neuer Eintrag"),n.replaceChildren(r,s,a),n.open()}}var Ne=`
|
|
270
|
+
h3 {
|
|
271
|
+
margin: 0 0 10px; padding: 0 4px; font-size: 12px; font-weight: 600; color: var(--muted);
|
|
272
|
+
text-transform: uppercase; letter-spacing: 0.04em;
|
|
273
|
+
}
|
|
274
|
+
.box { display: flex; flex-direction: column; gap: 10px; }
|
|
275
|
+
::slotted(*) {
|
|
276
|
+
border: 1px solid var(--line); border-radius: 12px; background: var(--surface); padding: 0 16px;
|
|
277
|
+
}
|
|
278
|
+
`;class K extends HTMLElement{static observedAttributes=["heading"];root;constructor(){super();this.root=this.attachShadow({mode:"open"}),m(this.root,Ne),this.root.innerHTML='<h3 part="heading"></h3><div class="box" part="box"><slot></slot></div>',this.sync()}attributeChangedCallback(){this.sync()}sync(){let e=this.root.querySelector("h3");e.textContent=this.getAttribute("heading")??"",e.hidden=!e.textContent}}class G extends HTMLElement{static observedAttributes=["scope"];connectedCallback(){this.build()}attributeChangedCallback(){if(this.isConnected)this.build()}async build(){let e=await x(),i=this.getAttribute("scope")==="settings"?"settings":"controls",o=(i==="settings"?e?.manifest?.settings:e?.manifest?.controls)??{},n=new Map,t=new Map,r=[];for(let[a,c]of Object.entries(o)){if(c.hidden)continue;let p=c.group??"",l=n.get(p);if(!l)l=document.createElement("pixl-section"),l.style.marginBottom="24px",n.set(p,l),t.set(p,[]),r.push(l);t.get(p).push(c.label);let d=document.createElement(c.type==="list"?"pixl-list":"pixl-field");if(d.setAttribute("field",a),d.setAttribute("scope",i),c.type==="list")d.style.padding="12px 0";l.append(d)}let s=(a)=>a.trim().toLocaleLowerCase();for(let[a,c]of n){let p=t.get(a)??[],l=p.length===1&&s(p[0])===s(a);if(a&&!l)c.setAttribute("heading",a)}this.replaceChildren(...r)}}var Ie=[["pixl-field",b],["pixl-toggle",D],["pixl-counter",F],["pixl-range",R],["pixl-select",j],["pixl-text",W],["pixl-color",B],["pixl-file",z],["pixl-button",q],["pixl-list",_],["pixl-dialog",P],["pixl-dropdown",I],["pixl-checkbox",O],["pixl-section",K],["pixl-controls",G]];function de(){if(typeof customElements>"u")return;V();for(let[e,i]of Ie)if(!customElements.get(e))customElements.define(e,i)}var ce=globalThis.Pixl;if(!ce)console.error("[pixl] pixl-ui.js braucht pixl.js, das zuerst einbinden");else Y(ce);de();U();Object.assign(globalThis,{PixlUI:{pixl:M,widget:x,fullscreen:k,createInput:v,css:L}});})();
|
package/dist/pixl.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
(()=>{function T(t,e=1){let
|
|
1
|
+
(()=>{function H(e){if(e.startsWith("twitch.")||e.startsWith("stream.")||e.startsWith("streamelements.")||e.startsWith("seventv."))return"events:read";if(e.startsWith("state."))return"states:read";if(e.startsWith("tts."))return"tts:read";if(e.startsWith("subs."))return"subs:read";if(e.startsWith("spotify."))return"spotify:read";return null}function T(e,t="123456789"){return{id:t,login:e.toLowerCase(),name:e,avatar:"https://static-cdn.jtvnw.net/user-default-pictures-uv/cdd517fe-def4-11e9-948e-784f43822e80-profile_image-300x300.png"}}var f=T("FrostByte","100000001"),h=T("Nordlicht","100000002"),y=T("SophiePlays","100000003"),b=T("JonasNRW","100000004"),W={level:2,progress:1200,goal:3000,total:4700,expiresAt:"2026-01-01T20:05:00.000Z",startedAt:"2026-01-01T20:00:00.000Z",type:"regular",shared:!1,participants:[],topContributions:[{user:f,type:"bits",total:2500},{user:h,type:"subscription",total:1500}],record:{level:5,total:18000}},_={id:"60ae958e229664e8667aea38",name:"peepoHey",provider:"7tv",animated:!0,zeroWidth:!1},w={"stream.online":{streamId:"40000000000",title:"Gemütlicher Abend mit euch",category:"Minecraft",url:"https://twitch.tv/pixl",thumbnailUrl:null},"stream.offline":{durationSec:12300},"twitch.follow":{user:f},"twitch.sub":{user:f,tier:"1",months:7,streak:4,durationMonths:1,message:"Sieben Monate schon!"},"twitch.sub_gift":{user:h,tier:"1",count:5,community:!0,months:1,recipients:[y,f],totalGifted:42},"twitch.cheer":{user:y,bits:500,message:"Cheer500 GG"},"twitch.raid":{user:h,viewers:87},"twitch.channel_point":{user:f,reward:"Hydrieren",cost:500,input:null},"twitch.channel_point_resolved":{user:f,reward:"Hydrieren",status:"fulfilled"},"twitch.power_up":{user:y,title:"Konfetti",bits:100,input:null},"twitch.watch_streak":{user:y,streak:10,channelPoints:450},"twitch.modiversary":{user:b,months:24},"twitch.shoutout_sent":{user:h,viewers:87},"twitch.poll_started":{title:"Welche Welt als Nächstes?",choices:[{title:"Nether",votes:0},{title:"End",votes:0}]},"twitch.poll_progress":{title:"Welche Welt als Nächstes?",choices:[{title:"Nether",votes:12},{title:"End",votes:9}]},"twitch.poll_ended":{title:"Welche Welt als Nächstes?",status:"completed",choices:[{title:"Nether",votes:31},{title:"End",votes:24}]},"twitch.prediction_started":{title:"Schafft er den Boss?",outcomes:[{title:"Ja",points:0},{title:"Nein",points:0}]},"twitch.prediction_progress":{title:"Schafft er den Boss?",outcomes:[{title:"Ja",points:12000},{title:"Nein",points:8000}]},"twitch.prediction_locked":{title:"Schafft er den Boss?",outcomes:[{title:"Ja",points:25000},{title:"Nein",points:19000}]},"twitch.prediction_ended":{title:"Schafft er den Boss?",status:"resolved",winner:"Ja",outcomes:[{title:"Ja",points:25000},{title:"Nein",points:19000}]},"twitch.hype_train_started":{...W,level:1,progress:400,goal:2000,total:400},"twitch.hype_train_progress":W,"twitch.hype_train_ended":{...W,expiresAt:null},"twitch.goal_started":{type:"follower",description:"Follower-Ziel",current:980,target:1000},"twitch.goal_progress":{current:995,target:1000},"twitch.goal_ended":{type:"follower",description:"Follower-Ziel",current:1003,target:1000,achieved:!0},"twitch.charity_started":{charity:"Ärzte ohne Grenzen",target:{value:1000,currency:"EUR"}},"twitch.charity_progress":{current:{value:420,currency:"EUR"},target:{value:1000,currency:"EUR"}},"twitch.charity_donated":{user:h,charity:"Ärzte ohne Grenzen",amount:{value:25,currency:"EUR"}},"twitch.charity_stopped":{charity:"Ärzte ohne Grenzen",current:{value:1240,currency:"EUR"},target:{value:1000,currency:"EUR"}},"twitch.ad_break":{durationSec:90,automatic:!0,startedAt:"2026-01-01T20:30:00.000Z"},"twitch.title_changed":{title:"Endlich das End"},"twitch.category_changed":{category:"Minecraft"},"twitch.ban":{user:h,moderator:b,reason:"Spam",permanent:!1,endsAt:"2026-01-01T20:10:00.000Z"},"twitch.unban":{user:h,moderator:b},"twitch.vip_added":{user:y},"twitch.vip_removed":{user:y},"twitch.moderator_added":{user:b},"twitch.moderator_removed":{user:b},"twitch.chat_message":{user:f,messageId:"00000000-0000-0000-0000-000000000001",text:"Hallo zusammen peepoHey",html:'Hallo zusammen <img class="pixl-emote" src="https://cdn.7tv.app/emote/60ae958e229664e8667aea38/2x.webp" alt="peepoHey" data-provider="7tv" data-emote="60ae958e229664e8667aea38">',color:"#8A5CF5",emotes:{peepoHey:_},badges:[],paint:null},"twitch.chat_cleared":{},"twitch.chat_user_cleared":{user:h},"twitch.chat_message_deleted":{user:h,messageId:"00000000-0000-0000-0000-000000000002"},"twitch.chat_settings_updated":{emoteMode:!1,followerMode:!0,followerModeDuration:10,slowMode:!0,slowModeWait:5,subscriberMode:!1,uniqueChatMode:!1},"twitch.chat_message_held":{user:h,text:"kauf billige follower",messageId:"00000000-0000-0000-0000-000000000003"},"twitch.chat_message_held_updated":{user:h,status:"denied",messageId:"00000000-0000-0000-0000-000000000003"},"twitch.shared_chat_started":{sessionId:"shared-1",participants:2},"twitch.shared_chat_updated":{sessionId:"shared-1",participants:3},"twitch.shared_chat_ended":{sessionId:"shared-1"},"twitch.guest_star_session_started":{sessionId:"guest-1"},"twitch.guest_star_session_ended":{sessionId:"guest-1"},"twitch.guest_star_guest_updated":{sessionId:"guest-1",user:y,state:"live",slotId:"1"},"twitch.guest_star_settings_updated":{slotCount:4,groupLayout:"tiled"},"seventv.emote_added":{emote:_,actor:"FrostByte"},"seventv.emote_removed":{emote:_,actor:"FrostByte"},"seventv.emote_renamed":{emote:_,oldName:"peepoHi",actor:"FrostByte"},"seventv.emote_set_changed":{setId:"01H000000000000000000000",name:"Kanal-Emotes",actor:"FrostByte"},"streamelements.tip":{name:"SophiePlays",amount:{value:5,currency:"EUR"},message:"Für den nächsten Kaffee",tipId:"tip-1"},"subs.updated":{total:312,points:340,at:"2026-01-01T20:00:00.000Z"},"spotify.now_playing":{playing:!0,progressMs:42000,at:"2026-01-01T20:00:00.000Z",track:null,addedBy:null},"tts.ready":{id:"00000000-0000-0000-0000-000000000004",voice:"Vicki",text:"SophiePlays hat 5 Euro gespendet: Für den nächsten Kaffee",source:"tip",eventId:null},"tts.skip":{id:null},"tts.clear":{}};function N(e,t=1){let i=encodeURIComponent(e.id);switch(e.provider){case"twitch":return`https://static-cdn.jtvnw.net/emoticons/v2/${i}/${e.animated?"default":"static"}/dark/${t===4?3:t}.0`;case"7tv":return`https://cdn.7tv.app/emote/${i}/${t}x.webp`;case"bttv":return`https://cdn.betterttv.net/emote/${i}/${t===4?3:t}x.webp`;case"ffz":return`https://cdn.frankerfacez.com/emote/${i}/${t}`}}var ce={"&":"&","<":"<",">":">",'"':""","'":"'"};function v(e){return e.replace(/[&<>"']/g,(t)=>ce[t])}function j(e,t){let i=v(e.name);return`<img class="pixl-emote" src="${v(N(e,t))}" alt="${i}" data-provider="${e.provider}" data-emote="${v(e.id)}">`}function ue(e,t={},i={}){let n=i.size??2,r=e.split(/(\s+)/),s=[],o=-1;for(let a of r){if(!a)continue;if(/^\s+$/.test(a)){s.push(v(a));continue}let d=Object.prototype.hasOwnProperty.call(t,a)?t[a]:void 0;if(!d){s.push(v(a)),o=-1;continue}if(d.zeroWidth&&o>=0){s.length=o+1;let m=s[o],p=m.startsWith('<span class="pixl-emote-stack">')?m.slice(31,-7):m;s[o]=`<span class="pixl-emote-stack">${p}${j(d,n)}</span>`;continue}s.push(j(d,n)),o=s.length-1}return s.join("")}function pe(e=[]){return e.map((t)=>`<img class="pixl-badge" src="${v(t.url)}" alt="${v(t.title)}" data-provider="${t.provider}">`).join("")}var he=`.pixl-emote{height:1.75em;vertical-align:middle;margin:-.25em 0}
|
|
2
2
|
.pixl-emote-stack{display:inline-grid;vertical-align:middle}
|
|
3
3
|
.pixl-emote-stack>.pixl-emote{grid-area:1/1;justify-self:center;align-self:center;margin:0}
|
|
4
|
-
.pixl-badge{height:1.1em;vertical-align:middle;margin-right:.25em}`;function _(t){switch(t.type){case"linear-gradient":case"radial-gradient":{let e=t.stops.map((s)=>`${s.color} ${Math.round(s.at*1000)/10}%`).join(", "),r=`${t.repeating?"repeating-":""}${t.type}`;return t.type==="linear-gradient"?`${r}(${t.angle}deg, ${e})`:`${r}(${t.shape}, ${e})`}case"color":return`linear-gradient(${t.color}, ${t.color})`;case"image":return`url("${t.url.replace(/"/g,"%22")}")`}}function w(t,e="inherit"){let r=t.layers.filter((n)=>n.opacity>0);if(!r.length)return`color:${e}`;let s=t.shadows.map((n)=>`drop-shadow(${n.x}px ${n.y}px ${n.blur}px ${n.color})`).join(" ");return[`color:${e}`,`background-image:${r.map(_).join(", ")}`,"background-size:cover","-webkit-background-clip:text","background-clip:text","-webkit-text-fill-color:transparent",s?`filter:${s}`:""].filter(Boolean).join(";")}class g{pixl;paints=new Map;queue=new Map;timer=null;constructor(t){this.pixl=t}css=j;emoteUrl(t,e=1){return T(t,e)}badgesHtml(t){return M(t)}paintCss(t,e){return w(t,e)}html(t,e,r){return f(t,e,r)}async emotes(){return(await this.pixl.request("/emotes")).emotes}paint(t){let e=this.paints.get(t);if(!e)e=new Promise((r)=>{this.queue.set(t,r),this.timer??=setTimeout(()=>this.flush(),20)}),this.paints.set(t,e);return e}async paintStyle(t,e="inherit"){let r=t?await this.paint(t):null;return r?w(r,e):`color:${e}`}async flush(){this.timer=null;let t=this.queue;this.queue=new Map;let e=[...t.keys()],r=new Map;try{for(let s=0;s<e.length;s+=100){let n=await this.pixl.request("/7tv/paints",{ids:e.slice(s,s+100).join(",")});for(let i of n.paints)r.set(i.id,i)}}catch(s){console.warn("[pixl] 7TV-Paints nicht geladen:",s);for(let n of e)this.paints.delete(n)}for(let[s,n]of t)n(r.get(s)??null)}}var S=[2000,5000,1e4,30000];class v{ready=!1;error=null;pixl;since;types;handler;delivered=new Set;errorListeners=new Set;buffer=null;loadSeq=0;stopped=!1;latestAt=null;retryTimer=null;retryAttempt=0;unsubscribeStatus;onEvent=(t,e)=>this.handleLive(e);constructor(t,e,r){if(e?.since===void 0)throw Error("pixl.feed: `since` fehlt");this.pixl=t,this.since=e.since instanceof Date?e.since.toISOString():String(e.since),this.types=e.types?.length?new Set(e.types):null,this.handler=r,t.on("*",this.onEvent),this.unsubscribeStatus=t.onStatus((s)=>{if(s==="connected")this.retryAttempt=0,this.load()})}onError(t){if(this.errorListeners.add(t),this.error)t(this.error);return()=>this.errorListeners.delete(t)}stop(){this.stopped=!0,this.clearRetry(),this.pixl.off("*",this.onEvent),this.unsubscribeStatus(),this.errorListeners.clear(),this.buffer=null}async load(){let t=++this.loadSeq;this.clearRetry(),this.buffer=[];let e=null;try{let s=await this.fetchHistory();if(t!==this.loadSeq||this.stopped)return;this.error=null,this.retryAttempt=0;for(let n of s)this.deliver({event:n.event,data:n.data,replay:!!(n.data?.replay||n.data?.test),id:n.id,at:n.at,history:!0})}catch(s){if(t!==this.loadSeq||this.stopped)return;e=s instanceof Error?s:Error(String(s)),this.error=e;for(let n of this.errorListeners)n(e)}let r=this.buffer??[];this.buffer=null,this.ready=!0;for(let s of r)this.deliver(s);if(e)this.scheduleRetry(e)}async fetchHistory(){let t=this.latestAt===null?null:this.latestAt-300000,e=[],r;do{let s=await this.pixl.events({since:this.since,types:this.types?[...this.types]:void 0,limit:200,cursor:r});e.push(...s.events),r=s.nextCursor??void 0;let n=s.events[s.events.length-1];if(t!==null&&n&&Date.parse(n.at)<t)break}while(r);return e.reverse()}scheduleRetry(t){let e=t.status;if(e!==void 0&&e>=400&&e<500&&e!==429&&e!==408)return;if(this.pixl.status!=="connected")return;let r=S[Math.min(this.retryAttempt,S.length-1)];this.retryAttempt+=1,this.retryTimer=setTimeout(()=>{if(this.retryTimer=null,!this.stopped&&this.pixl.status==="connected")this.load()},r+Math.random()*r*0.3)}clearRetry(){if(this.retryTimer)clearTimeout(this.retryTimer);this.retryTimer=null}handleLive(t){if(this.stopped)return;if(this.types&&!this.types.has(t.event))return;if(this.buffer){this.buffer.push(t);return}this.deliver(t)}deliver(t){if(t.id){if(this.delivered.has(t.id))return;this.delivered.add(t.id)}if(t.at){let e=Date.parse(t.at);if(!Number.isNaN(e)&&(this.latestAt===null||e>this.latestAt))this.latestAt=e}try{this.handler(t.data,t)}catch(e){console.error("[pixl] Fehler im feed()-Handler:",e)}}}class x{pixl;constructor(t){this.pixl=t}async speak(t,e={}){return await(await this.pixl._fetch("/tts",{method:"POST",json:{text:t,voice:e.voice,filters:e.filters}})).json()}async voices(){return await(await this.pixl._fetch("/tts/voices")).json()}async audio(t){return(await this.pixl._fetch(`/tts/${encodeURIComponent(t)}/audio`)).blob()}async marks(t){return(await(await this.pixl._fetch(`/tts/${encodeURIComponent(t)}/marks`)).json()).marks}player(t={}){return new k(this.pixl,this,t)}}class k{pixl;tts;options;queue=[];current=null;wordFrame=null;volume;destroyed=!1;warnedAutoplay=!1;onReady;onSkip;onClear;constructor(t,e,r){this.pixl=t;this.tts=e;this.options=r;this.volume=P(r.volume??1),this.onReady=(s,n)=>{let i=I(s,n);if(!i||n.replay&&this.options.ignoreReplays)return;if(this.current?.item.id===i.id||this.queue.some((a)=>a.id===i.id))return;this.queue.push(i);let o=this.options.maxQueue??20;if(this.queue.length>o)this.queue.splice(0,this.queue.length-o);this.next()},this.onSkip=(s)=>{let n=typeof s.id==="string"?s.id:void 0;if(!n||this.current?.item.id===n)this.skip();else this.queue=this.queue.filter((i)=>i.id!==n)},this.onClear=()=>this.clear(),t.on("tts.ready",this.onReady),t.on("tts.skip",this.onSkip),t.on("tts.clear",this.onClear)}skip(){if(!this.current)return;this.current.skipped=!0,this.current.finish()}clear(){this.queue=[],this.skip()}setVolume(t){if(this.volume=P(t),this.current?.audio)this.current.audio.volume=this.volume}get pending(){return this.queue}destroy(){this.destroyed=!0,this.pixl.off("tts.ready",this.onReady),this.pixl.off("tts.skip",this.onSkip),this.pixl.off("tts.clear",this.onClear),this.clear()}followWords(t,e,r){this.stopWords();let s=0,n=(o)=>r[o].end??r[o+1]?.start??(Number.isFinite(t.duration)?Math.round(t.duration*1000):r[o].start),i=()=>{let o=t.currentTime*1000;while(s<r.length&&r[s].start<=o){let a=s++,l={word:r[a].word,index:a,start:r[a].start,end:n(a)};y(()=>this.options.onWord?.(l,e))}this.wordFrame=s<r.length&&!t.ended?E(i):null};this.wordFrame=E(i)}stopWords(){if(this.wordFrame!==null)L(this.wordFrame);this.wordFrame=null}async next(){if(this.current||this.destroyed)return;let t=this.queue.shift();if(!t)return;let e=!1,r,s=new Promise((i)=>r=i),n={item:t,audio:null,url:null,skipped:!1,finish:()=>{if(e)return;if(e=!0,this.stopWords(),n.audio?.pause(),n.url)URL.revokeObjectURL(n.url);r()}};this.current=n;try{let[i,o]=await Promise.all([this.tts.audio(t.id),this.options.onWord?this.tts.marks(t.id).catch((a)=>{console.warn("[pixl] Zeitmarken nicht verfügbar, onWord bleibt für diesen Eintrag stumm:",a);return}):Promise.resolve(void 0)]);if(o)t.marks=o;if(!e){n.url=URL.createObjectURL(i);let a=new Audio(n.url);if(a.volume=this.volume,a.addEventListener("ended",n.finish),a.addEventListener("error",n.finish),n.audio=a,y(()=>this.options.onStart?.(t)),o?.length)this.followWords(a,t,o);await a.play().catch((l)=>{if(!this.warnedAutoplay&&l instanceof DOMException&&l.name==="NotAllowedError")this.warnedAutoplay=!0,console.warn("[pixl] Der Browser blockiert die Wiedergabe ohne Klick. In OBS und StreamElements passiert das nicht, im normalen Browser einmal auf die Seite klicken.");n.finish()})}}catch(i){console.error("[pixl] TTS-Audio konnte nicht geladen werden:",i),n.finish()}if(await s,y(()=>this.options.onEnd?.(t,{skipped:n.skipped})),this.current=null,this.queue.length&&!this.destroyed)setTimeout(()=>void this.next(),this.options.gapMs??500)}}function E(t){return typeof requestAnimationFrame==="function"?requestAnimationFrame(t):setTimeout(t,30)}function L(t){if(typeof cancelAnimationFrame==="function")cancelAnimationFrame(t);else clearTimeout(t)}function I(t,e){if(typeof t.id!=="string")return null;return{id:t.id,text:typeof t.text==="string"?t.text:"",voice:typeof t.voice==="string"?t.voice:"",source:typeof t.source==="string"?t.source:"",...typeof t.eventId==="string"?{eventId:t.eventId}:{},...t.textTruncated===!0?{textTruncated:!0}:{},replay:e.replay}}function P(t){return Number.isFinite(t)?Math.min(1,Math.max(0,t)):1}function y(t){try{t()}catch(e){console.error("[pixl] Fehler in einem TTS-Handler:",e)}}class h extends Error{status;problem;constructor(t,e,r){super(e);this.name="PixlApiError",this.status=t,this.problem=r}}var q="https://pixl.lennox.zip",C=[500,1000,2000,3000,5000,1e4],F=45000,U=15000;function R(t){if(t instanceof Date)return t.toISOString();return String(t)}class p{static defaultBaseUrl;baseUrl;key;tts;chat;handlers=new Map;stateHandlers=new Map;statusHandlers=new Set;states=new Map;_status="closed";abort=null;reconnectTimer=null;watchdog=null;attempt=0;stopped=!1;logs;everConnected=!1;lostAt=null;constructor(t){if(!t?.key)throw Error("pixl: `key` fehlt (API-Schlüssel aus den Einstellungen)");if(this.key=t.key,this.logs=t.logs!==!1,this.baseUrl=(t.baseUrl??p.defaultBaseUrl??q).replace(/\/+$/,""),this.tts=new x(this),this.chat=new g(this),t.autoConnect!==!1)queueMicrotask(()=>this.connect())}on(t,e){let r=this.handlers.get(t);if(!r)this.handlers.set(t,r=new Set);return r.add(e),this}off(t,e){return this.handlers.get(t)?.delete(e),this}state(t,e){let r=this.stateHandlers.get(t);if(!r)this.stateHandlers.set(t,r=new Set);if(r.add(e),this.states.has(t))e(this.states.get(t),t);return this}offState(t,e){return this.stateHandlers.get(t)?.delete(e),this}getState(t){return this.states.get(t)}get status(){return this._status}onStatus(t){return this.statusHandlers.add(t),t(this._status),()=>this.statusHandlers.delete(t)}setStatus(t){if(t===this._status)return;this._status=t;for(let e of this.statusHandlers)m(()=>e(t))}async request(t,e={}){let r=new URL(`${this.baseUrl}/api/v1${t}`);for(let[n,i]of Object.entries(e))if(i!==void 0&&i!=="")r.searchParams.set(n,i);let s=await fetch(r,{headers:{Authorization:`Bearer ${this.key}`}});if(!s.ok){let n=await s.json().catch(()=>null);throw new h(s.status,n?.error??`HTTP ${s.status}`)}return await s.json()}async _fetch(t,e={}){let r={Authorization:`Bearer ${this.key}`};if(e.json!==void 0)r["Content-Type"]="application/json";let s=await fetch(`${this.baseUrl}/api/v1${t}`,{method:e.method??"GET",headers:r,body:e.json!==void 0?JSON.stringify(e.json):void 0});if(!s.ok){let n=await s.json().catch(()=>null);throw new h(s.status,n?.error??`HTTP ${s.status}`,n?.problem)}return s}events(t={}){return this.request("/events",{since:t.since!==void 0?R(t.since):void 0,until:t.until!==void 0?R(t.until):void 0,types:t.types?.join(","),limit:t.limit!==void 0?String(t.limit):void 0,cursor:t.cursor})}org(){return this.request("/org")}subs(){return this.request("/subs")}nowPlaying(){return this.request("/spotify/now-playing")}static progress(t,e=Date.now()){if(!t.track)return 0;let r=t.playing?t.progressMs+(e-Date.parse(t.at)):t.progressMs;return Math.max(0,Math.min(r,t.track.durationMs))}streamElements(t,e={}){let r={};for(let[s,n]of Object.entries(e))r[s]=n===void 0?void 0:String(n);return this.request(`/streamelements/${t}`,r)}async fetchStates(){return(await this.request("/states")).states}async fetchAllEvents(t={}){let e=[],r;do{let s=await this.events({...t,limit:200,cursor:r});e.push(...s.events),r=s.nextCursor??void 0}while(r);return e.reverse()}feed(t,e){return new v(this,t,e)}connect(){if(this.abort)return;this.stopped=!1,this.run()}disconnect(){if(this.stopped=!0,this.reconnectTimer)clearTimeout(this.reconnectTimer);this.reconnectTimer=null,this.abort?.abort(),this.abort=null,this.setStatus("closed")}async run(){let t=new AbortController;this.abort=t,this.setStatus("connecting");let e=!1;try{this.watchdog=setTimeout(()=>t.abort(),U);let r=await fetch(`${this.baseUrl}/api/v1/stream`,{headers:{Authorization:`Bearer ${this.key}`,Accept:"text/event-stream"},signal:t.signal,cache:"no-store"});if(this.clearWatchdog(),r.status===401||r.status===403){e=!0;return}if(!r.ok||!r.body)throw Error(`HTTP ${r.status}`);if(this.attempt=0,this.lostAt!==null)this.log(`Live-Verbindung wieder da nach ${((Date.now()-this.lostAt)/1000).toFixed(1)} s`);this.lostAt=null,this.everConnected=!0,this.setStatus("connected"),await this.readStream(r.body,t,()=>{e=!0})}catch{}finally{if(this.clearWatchdog(),this.abort===t)this.abort=null;if(e)this.log("Live-Verbindung abgelehnt (Schlüssel ungültig oder widerrufen), kein neuer Versuch"),this.stopped=!0,this.setStatus("unauthorized");else if(!this.stopped){if(this.lostAt===null)this.lostAt=Date.now(),this.log(this.everConnected?"Live-Verbindung getrennt, verbinde neu":"Live-Verbindung nicht erreichbar, versuche es weiter");this.setStatus("disconnected"),this.scheduleReconnect()}}}log(t){if(this.logs)console.info(`[pixl] ${t}`)}scheduleReconnect(){let t=C[Math.min(this.attempt,C.length-1)];this.attempt+=1;let e=t+Math.random()*t*0.3;this.reconnectTimer=setTimeout(()=>{if(this.reconnectTimer=null,!this.stopped)this.connect()},e)}armWatchdog(t){this.clearWatchdog(),this.watchdog=setTimeout(()=>t.abort(),F)}clearWatchdog(){if(this.watchdog)clearTimeout(this.watchdog);this.watchdog=null}async readStream(t,e,r){let s=t.getReader(),n=new TextDecoder,i="",o=[];this.armWatchdog(e);for(;;){let{value:a,done:l}=await s.read();if(l)return;this.armWatchdog(e),i+=n.decode(a,{stream:!0});let u;while((u=i.indexOf(`
|
|
5
|
-
`))>=0){let
|
|
6
|
-
`);if(o=[],this.dispatch(H)==="revoked"){r(),await s.cancel().catch(()=>{});return}}}else if(c.startsWith("data:"))o.push(c.slice(5).replace(/^ /,""))}}}dispatch(t){let e;try{e=JSON.parse(t)}catch{return}if(e.event==="api_key.revoked")return"revoked";if(e.event==="state.snapshot"){let s=e.data?.states??[];for(let{key:n,value:i}of s)this.setState(n,i);return}if(e.event==="state.updated"){let{key:s,value:n}=e.data;if(typeof s==="string")this.setState(s,n);return}let r={event:e.event,data:e.data??{},replay:!!e.replay,id:e.id,at:e.at};this.emit(r)}emit(t){if(t.event==="twitch.chat_message"&&typeof t.data.text==="string"&&t.data.html===void 0)t.data.html=f(t.data.text,t.data.emotes);for(let e of this.handlers.get(t.event)??[])m(()=>e(t.data,t));for(let e of this.handlers.get("*")??[])m(()=>e(t.data,t))}setState(t,e){this.states.set(t,e);for(let r of this.stateHandlers.get(t)??[])m(()=>r(e,t))}}function m(t){try{t()}catch(e){console.error("[pixl] Fehler in einem Handler:",e)}}var A=typeof document<"u"?document.currentScript:null;if(A?.src)try{p.defaultBaseUrl=new URL(A.src).origin}catch{}Object.assign(globalThis,{Pixl:p,PixlApiError:h});})();
|
|
4
|
+
.pixl-badge{height:1.1em;vertical-align:middle;margin-right:.25em}`;function ge(e){switch(e.type){case"linear-gradient":case"radial-gradient":{let t=e.stops.map((n)=>`${n.color} ${Math.round(n.at*1000)/10}%`).join(", "),i=`${e.repeating?"repeating-":""}${e.type}`;return e.type==="linear-gradient"?`${i}(${e.angle}deg, ${t})`:`${i}(${e.shape}, ${t})`}case"color":return`linear-gradient(${e.color}, ${e.color})`;case"image":return`url("${e.url.replace(/"/g,"%22")}")`}}function q(e,t="inherit"){let i=e.layers.filter((r)=>r.opacity>0);if(!i.length)return`color:${t}`;let n=e.shadows.map((r)=>`drop-shadow(${r.x}px ${r.y}px ${r.blur}px ${r.color})`).join(" ");return[`color:${t}`,`background-image:${i.map(ge).join(", ")}`,"background-size:cover","-webkit-background-clip:text","background-clip:text","-webkit-text-fill-color:transparent",n?`filter:${n}`:""].filter(Boolean).join(";")}class I{pixl;paints=new Map;queue=new Map;timer=null;constructor(e){this.pixl=e}css=he;emoteUrl(e,t=1){return N(e,t)}badgesHtml(e){return pe(e)}paintCss(e,t){return q(e,t)}html(e,t,i){return ue(e,t,i)}async emotes(){return(await this.pixl.request("/emotes")).emotes}paint(e){let t=this.paints.get(e);if(!t)t=new Promise((i)=>{this.queue.set(e,i),this.timer??=setTimeout(()=>this.flush(),20)}),this.paints.set(e,t);return t}async paintStyle(e,t="inherit"){let i=e?await this.paint(e):null;return i?q(i,t):`color:${t}`}async flush(){this.timer=null;let e=this.queue;this.queue=new Map;let t=[...e.keys()],i=new Map;try{for(let n=0;n<t.length;n+=100){let r=await this.pixl.request("/7tv/paints",{query:{ids:t.slice(n,n+100).join(",")}});for(let s of r.paints)i.set(s.id,s)}}catch(n){console.warn("[pixl] 7TV-Paints nicht geladen:",n);for(let r of t)this.paints.delete(r)}for(let[n,r]of e)r(i.get(n)??null)}}class l extends Error{code;status;scope;problem;constructor(e,t,i={}){super(t);this.name="PixlError",this.code=e,this.status=i.status,this.scope=i.scope,this.problem=i.problem}}async function z(e){let t=await e.json().catch(()=>null),i=t?.error??`HTTP ${e.status}`,n={status:e.status,problem:t?.problem,scope:t?.scope};if(t?.code==="module_disabled")return new l("module_disabled",i,n);if(t?.code==="missing_scope"||t?.scope)return new l("missing_scope",i,n);switch(e.status){case 400:case 422:return new l("invalid",i,n);case 401:return new l("unauthorized",i,n);case 403:return new l("forbidden",i,n);case 404:return new l("not_found",i,n);case 429:return new l("rate_limited",i,n);default:return new l("server",i,n)}}class c{current;listeners=new Set;constructor(e){this.current=e}get value(){return this.current}subscribe(e){return this.listeners.add(e),u(()=>e(this.current)),()=>{this.listeners.delete(e)}}set(e){if(Object.is(e,this.current))return;this.current=e;for(let t of[...this.listeners])u(()=>t(e))}clear(){this.listeners.clear()}}function u(e){try{e()}catch(t){console.error("[pixl] Fehler in einem Handler:",t)}}var me=300000,K=[2000,5000,1e4,30000],G=5000;class L{pixl;events;handler;ready;error;readyValue=new c(!1);errorValue=new c(null);since;types;delivered=new Set;buffer=null;loadSeq=0;stopped=!1;latestAt=null;retryTimer=null;retryAttempt=0;unsubscribe=[];constructor(e,t,i,n){this.pixl=e;this.events=t;this.handler=n;if(i?.since===void 0)throw new l("invalid","pixl.events.feed: `since` fehlt");this.ready=this.readyValue,this.error=this.errorValue,this.since=i.since instanceof Date?i.since.toISOString():String(i.since),this.types=i.types?.length?new Set(i.types):null,this.unsubscribe.push(e.on("*",(r)=>this.handleLive(r))),this.unsubscribe.push(e.status.subscribe((r)=>{if(r==="connected")this.retryAttempt=0,this.load()}))}stop(){this.stopped=!0,this.clearRetry();for(let e of this.unsubscribe)e();this.buffer=null}async load(){let e=++this.loadSeq;this.clearRetry(),this.buffer=[];let t=null;try{let n=await this.fetchHistory();if(e!==this.loadSeq||this.stopped)return;this.errorValue.set(null),this.retryAttempt=0;for(let r of n)this.deliver(r)}catch(n){if(e!==this.loadSeq||this.stopped)return;t=n instanceof l?n:new l("network",String(n)),this.errorValue.set(t)}let i=this.buffer??[];this.buffer=null,this.readyValue.set(!0);for(let n of i)this.deliver(n);if(t)this.scheduleRetry(t)}async fetchHistory(){let e=this.latestAt===null?null:this.latestAt-me,t=[],i,n={since:this.since,types:this.types?[...this.types]:void 0};do{let r=await this.events.fetchPage(n,i);t.push(...r.events),i=r.nextCursor??void 0;let s=r.events[r.events.length-1];if(e!==null&&s&&Date.parse(s.at)<e)break}while(i);return t.reverse()}scheduleRetry(e){if(e.code!=="network"&&e.code!=="rate_limited"&&e.code!=="server")return;if(this.pixl.status.value!=="connected")return;let t=K[Math.min(this.retryAttempt,K.length-1)];this.retryAttempt+=1,this.retryTimer=setTimeout(()=>{if(this.retryTimer=null,!this.stopped&&this.pixl.status.value==="connected")this.load()},t+Math.random()*t*0.3)}clearRetry(){if(this.retryTimer)clearTimeout(this.retryTimer);this.retryTimer=null}handleLive(e){if(this.stopped)return;if(this.types&&!this.types.has(e.type))return;if(this.buffer){this.buffer.push(e);return}this.deliver(e)}deliver(e){if(e.id){if(this.delivered.has(e.id))return;if(this.delivered.add(e.id),this.delivered.size>G){let i=this.delivered.size-G,n=0;for(let r of this.delivered){if(n++>=i)break;this.delivered.delete(r)}}}let t=Date.parse(e.at);if(!Number.isNaN(t)&&e.source!=="test"&&(this.latestAt===null||t>this.latestAt))this.latestAt=t;u(()=>this.handler(e))}}function Z(e){return e instanceof Date?e.toISOString():String(e)}class M{pixl;constructor(e){this.pixl=e}async*list(e){let t;do{let i=await this.fetchPage(e,t);yield*i.events,t=i.nextCursor??void 0}while(t)}async all(e){let t=[];for await(let i of this.list(e))t.push(i);return t.reverse()}feed(e,t){return new L(this.pixl,this,e,t)}fetchPage(e,t){if(this.pixl.demo)return Promise.resolve({events:[],nextCursor:null});return this.pixl.request("/events",{query:{v:2,since:Z(e.since),until:e.until!==void 0?Z(e.until):void 0,types:e.types?.join(","),limit:200,cursor:t},signal:e.signal})}}var ye=[100,200,300,400,500,600,700,800,900].flatMap((e)=>[`${e}`,`${e}i`]).join(",");function fe(e){return`https://fonts.bunny.net/css?family=${encodeURIComponent(e.trim())}:${ye}&display=swap`}function x(e,t="sans-serif"){let i=typeof e==="string"?e.trim():"";if(!i)return t;return`"${i.replace(/["\\]/g,"\\$&")}", ${t}`}var Y=new Map;function P(e){let t=typeof e==="string"?e.trim():"";if(!t||typeof document>"u")return Promise.resolve(!1);let i=Y.get(t);if(!i)i=new Promise((n)=>{let r=document.createElement("link");r.rel="stylesheet",r.href=fe(t),r.dataset.pixlFont=t,r.onload=()=>{let s=document.fonts;if(!s)return n(!0);s.load(`16px ${x(t)}`).then((o)=>n(o.length>0),()=>n(!1))},r.onerror=()=>n(!1),document.head.append(r)}),Y.set(t,i);return i}function J(e){let t=new Set,i=e?.manifest;if(!e||!i)return[];let n=(r,s)=>{for(let[o,a]of Object.entries(r??{})){let d=s[o];if(a.type==="font"&&typeof d==="string")t.add(d.trim());if(a.type!=="list"||!Array.isArray(d))continue;let m=Object.entries(a.item).filter(([,p])=>p.type==="font").map(([p])=>p);if(!m.length)continue;for(let p of d)for(let S of m)if(typeof p?.[S]==="string")t.add(p[S].trim())}};return n(i.settings,e.settings),n(i.controls,e.values),t.delete(""),[...t]}function Q(){if(typeof window>"u"||window.parent===window)return;let e=globalThis;if(e.__pixlFrame)return;e.__pixlFrame=!0,window.addEventListener("message",(n)=>{if(n.source!==window.parent||typeof n.data!=="object"||!n.data)return;if(n.data.type!=="pixl:theme"||typeof n.data.vars!=="object")return;let r=document.documentElement;for(let[s,o]of Object.entries(n.data.vars))if(s.startsWith("--pixl-"))r.style.setProperty(s,String(o));if(n.data.scheme)r.style.colorScheme=n.data.scheme});let t=()=>{let n=document.body,r=getComputedStyle(n),s=n.getBoundingClientRect().height+parseFloat(r.marginTop)+parseFloat(r.marginBottom);window.parent.postMessage({type:"pixl:height",height:Math.ceil(s)},"*")},i=()=>{if(typeof ResizeObserver<"u")new ResizeObserver(t).observe(document.body);t()};if(document.body)i();else document.addEventListener("DOMContentLoaded",i,{once:!0});window.addEventListener("load",t)}class A{pixl;nowPlaying;value=new c(null);constructor(e){this.pixl=e;this.nowPlaying=this.value,e._onConnect(()=>this.load()),e._listen("spotify.now_playing",(t)=>this.value.set(t.data))}progress(e=Date.now()){let t=this.value.value;if(!t?.track)return 0;let i=t.playing?t.progressMs+(e-Date.parse(t.at)):t.progressMs;return Math.max(0,Math.min(i,t.track.durationMs))}async load(){if(this.pixl.demo){this.value.set({...w["spotify.now_playing"],at:new Date().toISOString()});return}if(!this.pixl.scopes?.includes("spotify:read"))return;try{this.value.set(await this.pixl.request("/spotify/now-playing"))}catch(e){if(e instanceof l&&e.code==="module_disabled")this.value.set(null)}}_close(){this.value.clear()}}class F{pixl;constructor(e){this.pixl=e}get(e,t={},i={}){return this.pixl.request(`/streamelements/${e}`,{query:t,signal:i.signal})}}var X=[500,1000,2000,3000,5000,1e4];class R{options;abort=null;reconnectTimer=null;watchdog=null;attempt=0;stopped=!0;everConnected=!1;lostAt=null;constructor(e){this.options=e}start(){if(this.abort)return;this.stopped=!1,this.run()}stop(){if(this.stopped=!0,this.reconnectTimer)clearTimeout(this.reconnectTimer);this.reconnectTimer=null,this.clearWatchdog(),this.abort?.abort(),this.abort=null,this.options.onStatus("closed")}async run(){let e=new AbortController;this.abort=e,this.options.onStatus("connecting");let t=!1;try{this.watchdog=setTimeout(()=>e.abort(),15000);let i=await(this.options.fetch??fetch)(this.options.url,{headers:{Authorization:`Bearer ${this.options.token()}`,Accept:"text/event-stream"},signal:e.signal,cache:"no-store"});if(this.clearWatchdog(),i.status===401||i.status===403){t=!0;return}if(!i.ok||!i.body)throw Error(`HTTP ${i.status}`);if(this.attempt=0,this.lostAt!==null)this.options.log(`Live-Verbindung wieder da nach ${((Date.now()-this.lostAt)/1000).toFixed(1)} s`);this.lostAt=null,this.everConnected=!0,await this.read(i.body,e,()=>{t=!0})}catch{}finally{if(this.clearWatchdog(),this.abort===e)this.abort=null;if(t)this.options.log("Live-Verbindung abgelehnt (Zugang ungültig oder widerrufen), kein neuer Versuch"),this.stopped=!0,this.options.onStatus("unauthorized");else if(!this.stopped){if(this.lostAt===null)this.lostAt=Date.now(),this.options.log(this.everConnected?"Live-Verbindung getrennt, verbinde neu":"Live-Verbindung nicht erreichbar, versuche es weiter");this.options.onStatus("disconnected"),this.scheduleReconnect()}}}scheduleReconnect(){let e=X[Math.min(this.attempt,X.length-1)];this.attempt+=1;let t=e+Math.random()*e*0.3;this.reconnectTimer=setTimeout(()=>{if(this.reconnectTimer=null,!this.stopped)this.start()},t)}armWatchdog(e){this.clearWatchdog(),this.watchdog=setTimeout(()=>e.abort(),45000)}clearWatchdog(){if(this.watchdog)clearTimeout(this.watchdog);this.watchdog=null}async read(e,t,i){let n=e.getReader(),r=new TextDecoder,s="",o=[];this.armWatchdog(t);for(;;){let{value:a,done:d}=await n.read();if(d)return;this.armWatchdog(t),s+=r.decode(a,{stream:!0});let m;while((m=s.indexOf(`
|
|
5
|
+
`))>=0){let p=s.slice(0,m).replace(/\r$/,"");if(s=s.slice(m+1),p===""){if(!o.length)continue;let S=o.join(`
|
|
6
|
+
`);o=[];let C;try{C=JSON.parse(S)}catch{continue}if(C.type==="access_revoked"){i(),await n.cancel().catch(()=>{});return}this.options.onMessage(C)}else if(p.startsWith("data:"))o.push(p.slice(5).replace(/^ /,""))}}}}class O{pixl;constructor(e){this.pixl=e}async speak(e,t={}){let i=await this.pixl.fetchRaw(this.base,{method:"POST",body:{text:e,voice:t.voice,filters:t.filters},signal:t.signal});if(i.status===204)throw new l("forbidden","Liest die Browserquelle vor, die Vorschau spielt es mit ab",{status:204,problem:"preview"});let n=await i.json();if(this.pixl._preview)this.pixl.sendTestEvent("tts.ready",{id:n.id,text:n.text,voice:n.voice,source:"api",eventId:null});return n}get base(){return this.pixl._preview?"/widget/preview/tts":"/tts"}voices(e={}){return this.pixl.request("/tts/voices",{signal:e.signal})}async audio(e,t={}){return(await this.pixl.fetchRaw(`${this.base}/${encodeURIComponent(e)}/audio`,{signal:t.signal})).blob()}async marks(e,t={}){return(await this.pixl.request(`${this.base}/${encodeURIComponent(e)}/marks`,{signal:t.signal})).marks}player(e={}){return new ie(this.pixl,this,e)}}class ie{pixl;tts;options;queue=[];current=null;wordFrame=null;volume;destroyed=!1;warnedAutoplay=!1;unsubscribe;constructor(e,t,i){this.pixl=e;this.tts=t;this.options=i;this.volume=te(i.volume??1);let n=(s)=>{let o=we(s);if(!o||o.replay&&this.options.ignoreReplays)return;if(this.current?.item.id===o.id||this.queue.some((d)=>d.id===o.id))return;this.queue.push(o);let a=this.options.maxQueue??20;if(this.queue.length>a)this.queue.splice(0,this.queue.length-a);this.next()},r=(s)=>{let o=s.data.id;if(!o||this.current?.item.id===o)this.skip();else this.queue=this.queue.filter((a)=>a.id!==o)};this.unsubscribe=[e._listen("tts.ready",n),e._listen("tts.skip",r),e._listen("tts.clear",()=>this.clear())]}skip(){if(!this.current)return;this.current.skipped=!0,this.current.finish()}clear(){this.queue=[],this.skip()}setVolume(e){if(this.volume=te(e),this.current?.audio)this.current.audio.volume=this.volume}get pending(){return this.queue}destroy(){this.destroyed=!0;for(let e of this.unsubscribe)e();this.clear()}followWords(e,t,i){this.stopWords();let n=0,r=(o)=>i[o].end??i[o+1]?.start??(Number.isFinite(e.duration)?Math.round(e.duration*1000):i[o].start),s=()=>{let o=e.currentTime*1000;while(n<i.length&&i[n].start<=o){let a=n++,d={word:i[a].word,index:a,start:i[a].start,end:r(a)};u(()=>this.options.onWord?.(d,t))}this.wordFrame=n<i.length&&!e.ended?ee(s):null};this.wordFrame=ee(s)}stopWords(){if(this.wordFrame!==null)ve(this.wordFrame);this.wordFrame=null}async next(){if(this.current||this.destroyed)return;let e=this.queue.shift();if(!e)return;let t=!1,i,n=new Promise((s)=>i=s),r={item:e,audio:null,url:null,skipped:!1,finish:()=>{if(t)return;if(t=!0,this.stopWords(),r.audio?.pause(),r.url)URL.revokeObjectURL(r.url);i()}};this.current=r;try{let[s,o]=await Promise.all([this.tts.audio(e.id),this.options.onWord?this.tts.marks(e.id).catch((a)=>{console.warn("[pixl] Zeitmarken nicht verfügbar, onWord bleibt für diesen Eintrag stumm:",a);return}):Promise.resolve(void 0)]);if(o)e.marks=o;if(!t){r.url=URL.createObjectURL(s);let a=new Audio(r.url);if(a.volume=this.volume,a.addEventListener("ended",r.finish),a.addEventListener("error",r.finish),r.audio=a,u(()=>this.options.onStart?.(e)),o?.length)this.followWords(a,e,o);await a.play().catch((d)=>{if(!this.warnedAutoplay&&d instanceof DOMException&&d.name==="NotAllowedError")this.warnedAutoplay=!0,console.warn("[pixl] Der Browser blockiert die Wiedergabe ohne Klick. In OBS und StreamElements passiert das nicht, im normalen Browser einmal auf die Seite klicken.");r.finish()})}}catch(s){console.error("[pixl] TTS-Audio konnte nicht geladen werden:",s),r.finish()}if(await n,u(()=>this.options.onEnd?.(e,{skipped:r.skipped})),this.current=null,this.queue.length&&!this.destroyed)setTimeout(()=>void this.next(),this.options.gapMs??500)}}function ee(e){return typeof requestAnimationFrame==="function"?requestAnimationFrame(e):setTimeout(e,30)}function ve(e){if(typeof cancelAnimationFrame==="function")cancelAnimationFrame(e);else clearTimeout(e)}function we(e){let t=e.data;if(!t.id)return null;return{id:t.id,text:t.text,voice:t.voice,source:t.source,...t.eventId?{eventId:t.eventId}:{},...t.textTruncated===!0?{textTruncated:!0}:{},replay:e.source==="replay"}}function te(e){return Number.isFinite(e)?Math.min(1,Math.max(0,e)):1}class V{pixl;subs;hypeTrain;poll;prediction;goal;charity;adBreak;values={subs:new c(null),hypeTrain:new c(null),poll:new c(null),prediction:new c(null),goal:new c(null),charity:new c(null),adBreak:new c(null)};adBreakTimer=null;constructor(e){this.pixl=e;let t=this.values;this.subs=t.subs,this.hypeTrain=t.hypeTrain,this.poll=t.poll,this.prediction=t.prediction,this.goal=t.goal,this.charity=t.charity,this.adBreak=t.adBreak,e._onConnect(()=>this.load()),e._listen("subs.updated",(i)=>t.subs.set(i.data)),e._listen("twitch.hype_train_started",(i)=>t.hypeTrain.set(i.data)),e._listen("twitch.hype_train_progress",(i)=>t.hypeTrain.set(i.data)),e._listen("twitch.hype_train_ended",()=>t.hypeTrain.set(null)),e._listen("twitch.poll_started",(i)=>t.poll.set({...i.data,status:"active"})),e._listen("twitch.poll_progress",(i)=>t.poll.set({...i.data,status:"active"})),e._listen("twitch.poll_ended",(i)=>t.poll.set(i.data)),e._listen("twitch.prediction_started",(i)=>t.prediction.set({...i.data,status:"active",winner:null})),e._listen("twitch.prediction_progress",(i)=>t.prediction.set({...i.data,status:"active",winner:null})),e._listen("twitch.prediction_locked",(i)=>t.prediction.set({...i.data,status:"locked",winner:null})),e._listen("twitch.prediction_ended",(i)=>t.prediction.set(i.data)),e._listen("twitch.goal_started",(i)=>t.goal.set({...i.data,active:!0,achieved:!1})),e._listen("twitch.goal_progress",(i)=>{let n=t.goal.value;t.goal.set({type:n?.type??"",description:n?.description??"",...i.data,active:!0,achieved:!1})}),e._listen("twitch.goal_ended",(i)=>t.goal.set({...i.data,active:!1})),e._listen("twitch.charity_started",(i)=>t.charity.set({...i.data,current:{value:0,currency:i.data.target.currency},active:!0})),e._listen("twitch.charity_progress",(i)=>t.charity.set({charity:t.charity.value?.charity??"",...i.data,active:!0})),e._listen("twitch.charity_stopped",(i)=>t.charity.set({...i.data,active:!1})),e._listen("twitch.ad_break",(i)=>{let n=i.data.startedAt??i.at,r=new Date(Date.parse(n)+i.data.durationSec*1000);if(t.adBreak.set({durationSec:i.data.durationSec,startedAt:n,endsAt:r.toISOString()}),this.adBreakTimer)clearTimeout(this.adBreakTimer);this.adBreakTimer=setTimeout(()=>t.adBreak.set(null),Math.max(0,r.getTime()-Date.now()))})}async fetchSubs(e={}){let t=await this.pixl.request("/subs",{signal:e.signal});return this.values.subs.set(t),t}async load(){if(this.pixl.demo){this.values.subs.set(w["subs.updated"]);return}if(this.pixl.scopes?.includes("subs:read"))await this.fetchSubs().catch(()=>{})}_close(){if(this.adBreakTimer)clearTimeout(this.adBreakTimer);for(let e of Object.values(this.values))e.clear()}}function be(e){return`--pixl-${e.replace(/([a-z0-9])([A-Z])/g,"$1-$2").replace(/_/g,"-").toLowerCase()}`}function ne(e){return`"${e.replace(/[\\"]/g,"\\$&").replace(/\n/g,"\\a ")}"`}function re(e){if(e.type==="list"||e.type==="trigger"||e.type==="voice")return!1;return!(e.type==="string"&&e.sensitive)}function xe(e,t,i){if(!re(e)||t===void 0||t===null)return null;switch(e.type){case"number":return typeof t==="number"&&Number.isFinite(t)?String(t):null;case"boolean":return t===!0?"1":"0";case"string":return typeof t==="string"?ne(t):null;case"font":return typeof t==="string"&&t?x(t):null;case"file":return typeof t==="string"&&t?`url(${ne(i(t))})`:null;case"color":case"enum":return typeof t==="string"&&t?t:null}}function Se(e){let t=new Map,i=(n,r)=>{for(let[s,o]of Object.entries(n??{}))if(!t.has(s)&&re(o))t.set(s,{field:o,value:r[s]})};return i(e.manifest?.controls,e.values),i(e.manifest?.settings,e.settings),t}class D{doc;fileUrl;vars=new Set;constructor(e,t){this.doc=e;this.fileUrl=t}apply(e){let t=this.doc.documentElement.style,i=new Set;for(let[n,{field:r,value:s}]of Se(e)){let o=xe(r,s,this.fileUrl);if(o===null)continue;let a=be(n);if(i.add(a),t.getPropertyValue(a)!==o)t.setProperty(a,o)}for(let n of this.vars)if(!i.has(n))t.removeProperty(n);this.vars=i}}class k{pixl;state;stateValue;fieldValues=new Map;actionHandlers=new Map;listHandlers=new Map;fields;values;settings;files={list:()=>this.pixl.request("/widget/files"),upload:(e,t)=>this.pixl.request("/widget/files",{method:"POST",query:{name:t??(e instanceof File?e.name:"datei")},raw:e}).then((i)=>({...i,url:this.fileUrl(i.path)}))};voicesCache=null;voices(){let e=Date.now();if(!this.voicesCache||e-this.voicesCache.at>30000){let t=this.pixl.request("/widget/voices");this.voicesCache={at:e,promise:t},t.catch(()=>{if(this.voicesCache?.promise===t)this.voicesCache=null})}return this.voicesCache.promise}preview;bindings;constructor(e,t,i={}){this.pixl=e;if(this.preview=i.preview===!0,this.bindings=i.bind&&typeof document<"u"?new D(document,(n)=>this.fileUrl(n)):null,t)this.bindings?.apply(t);this.stateValue=new c(t),this.state=this.stateValue,se(t),this.values=this.fieldProxy("controls"),this.settings=this.fieldProxy("settings"),this.fields=U(()=>this.stateValue.value)}fileUrl(e){if(!e.startsWith("/files/"))return e;let t=globalThis.__PIXL__?.widgetOrigin;return t?`${t.replace(/\/+$/,"")}${e}`:e}get manifest(){return this.stateValue.value?.manifest??null}get(e){let t=this.stateValue.value;return t&&e in t.values?t.values[e]:t?.settings[e]}on(e,t){let i=this.actionHandlers.get(e);if(!i)this.actionHandlers.set(e,i=new Set);return i.add(t),()=>{i.delete(t)}}onList(e,t){let i=this.listHandlers.get(e);if(!i)this.listHandlers.set(e,i=new Set);return i.add(t),()=>{i.delete(t)}}set(e,t){return this.batch([{name:e,set:t}])}add(e,t){return this.batch([{name:e,add:t}])}toggle(e){return this.batch([{name:e,toggle:!0}])}press(e){return this.batch([{name:e,press:!0}])}list(e){return{insert:(t,i)=>this.batch([{name:e,insert:t,...i===void 0?{}:{at:i}}]),update:(t,i)=>this.batch([{name:e,update:t,patch:i}]),remove:(t)=>this.batch([{name:e,remove:t}]),move:(t,i)=>this.batch([{name:e,move:t,to:i}]),replace:(t)=>this.batch([{name:e,set:t}])}}async batch(e){if(this.preview)return this.previewBatch(e);let t=await this.pixl.request("/widget",{method:"POST",body:{operations:e}});if(this.pixl.status.value!=="connected")this.applyState(t.state,t.changes)}async previewBatch(e){let t=this.stateValue.value;if(!t)return;let i=await this.pixl.request("/widget/preview",{method:"POST",body:{operations:e,settings:t.settings,values:t.values}}),n=this.stateValue.value??t,r={...n,values:{...n.values},settings:{...n.settings}};for(let s of i.changes)ae(r,s);if(i.changes.length)this.applyState(r,i.changes);for(let s of i.actions)for(let o of[...this.actionHandlers.get(s)??[]])u(o)}fromServer(e){let t=this.stateValue.value;if(!this.preview||!t)return e;return{...e,version:t.version,manifest:t.manifest,settings:oe(t,e,"settings"),values:oe(t,e,"controls")}}async _refetch(){this.applyState(this.fromServer(await this.pixl.request("/widget")),null)}_setState(e){this.applyState(this.fromServer(e),null)}_handle(e){if(e.type==="widget.action"){for(let n of[...this.actionHandlers.get(e.name)??[]])u(n);return}if(e.type==="widget.version")return;if(e.refetch){this._refetch().catch((n)=>console.warn("[pixl] Widget-Stand nicht geladen:",n));return}let t=this.stateValue.value;if(!t)return;let i={...t,values:{...t.values},settings:{...t.settings}};for(let n of e.changes)ae(i,n);this.applyState(i,e.changes)}applyState(e,t){let i=this.stateValue.value;se(e),this.stateValue.set(e),this.bindings?.apply(e);for(let[r,s]of this.fieldValues){let[o,a]=_e(r);s.set((o==="settings"?e.settings:e.values)[a])}let n=new Set;for(let r of t??[]){let s=this.listHandlers.get(r.name);if(!s?.size)continue;let o=Te(i,r),a=Pe(r,o);if(!a)continue;n.add(r.name);for(let d of[...s])u(()=>d(a))}for(let[r,s]of this.listHandlers){if(n.has(r))continue;let o=e.values[r]??e.settings[r],a=i?i.values[r]??i.settings[r]:void 0;if(!Array.isArray(o)||a===o||JSON.stringify(a)===JSON.stringify(o))continue;for(let d of[...s])u(()=>d({type:"replace",items:o}))}}fieldProxy(e){return new Proxy({},{get:(t,i)=>{if(typeof i!=="string")return;let n=`${e}:${i}`,r=this.fieldValues.get(n);if(!r){let s=this.stateValue.value;r=new c(s?(e==="settings"?s.settings:s.values)[i]:void 0),this.fieldValues.set(n,r)}return r},ownKeys:()=>{let t=this.stateValue.value;return Object.keys(t?e==="settings"?t.settings:t.values:{})},getOwnPropertyDescriptor:()=>({enumerable:!0,configurable:!0})})}}function U(e){let t=()=>{let n=e();return n?[...new Set([...Object.keys(n.values),...Object.keys(n.settings)])]:[]},i=(n)=>{let r=e();return r&&n in r.values?r.values[n]:r?.settings[n]};return new Proxy({},{get:(n,r)=>typeof r==="string"?i(r):void 0,has:(n,r)=>typeof r==="string"&&t().includes(r),ownKeys:()=>t(),getOwnPropertyDescriptor:(n,r)=>typeof r==="string"&&t().includes(r)?{value:i(r),writable:!1,enumerable:!0,configurable:!0}:void 0,set:(n,r)=>{throw TypeError(`[pixl] fields ist nur zum Lesen, ändern geht mit widget.set("${String(r)}", …)`)},deleteProperty:()=>!1})}function se(e){for(let t of J(e))P(t)}function oe(e,t,i){let n=(i==="settings"?e.manifest?.settings:e.manifest?.controls)??{},r=(i==="settings"?t.manifest?.settings:t.manifest?.controls)??{},s={...E(e,i)},o=E(t,i);for(let[a,d]of Object.entries(n))if(r[a]?.type===d.type&&a in o)s[a]=o[a];return s}function _e(e){let t=e.indexOf(":");return[e.slice(0,t),e.slice(t+1)]}function E(e,t){return t==="settings"?e.settings:e.values}function Te(e,t){if(!e)return[];let i=E(e,t.scope)[t.name];return Array.isArray(i)?i:[]}function ae(e,t){let i=E(e,t.scope);if("value"in t){i[t.name]=t.value;return}let n=Array.isArray(i[t.name])?[...i[t.name]]:[];if("inserted"in t){if(!n.some((r)=>r.id===t.inserted.id))n.splice(t.at,0,t.inserted)}else if("updated"in t){let r=n.findIndex((s)=>s.id===t.updated.id);if(r>=0)n[r]=t.updated}else if("removed"in t){let r=n.findIndex((s)=>s.id===t.removed);if(r>=0)n.splice(r,1)}else if("moved"in t){let r=n.findIndex((s)=>s.id===t.moved);if(r>=0){let[s]=n.splice(r,1);n.splice(Math.min(t.to,n.length),0,s)}}i[t.name]=n}function Pe(e,t){if("inserted"in e)return{type:"insert",item:e.inserted,at:e.at};if("updated"in e)return{type:"update",item:e.updated,previous:t.find((i)=>i.id===e.updated.id)??null};if("removed"in e)return{type:"remove",id:e.removed,previous:t.find((i)=>i.id===e.removed)??null};if("moved"in e)return{type:"move",id:e.moved,to:e.to};if(Array.isArray(e.value))return{type:"replace",items:e.value};return null}var Ee="https://pixl.lennox.zip",ke={id:"demo",name:"Demo-Kanal",login:"pixl",avatar:null},Ce=["events:read","tts:read","tts:write","subs:read","spotify:read","streamelements:read"];function B(){let e=globalThis.__PIXL__;if(e)return e;let t=typeof location<"u"?location.hash.slice(1):"";if(!t.includes("pixl-key="))return;let i=new URLSearchParams(t);return{key:i.get("pixl-key")??void 0,baseUrl:i.get("pixl-server")??void 0}}class g{static defaultBaseUrl;static sharedInstance=null;static shared(e){return g.sharedInstance??=new g(e)}static loadFont=P;static fontFamily=x;baseUrl;demo;status;channel;ready;events;twitch;spotify;tts;chat;streamElements;fields=U(()=>this.widgetInstance?.state.value??null);key;logs;widgetInstance=null;control;previewMode;fetchImpl;stream;statusValue=new c("closed");channelValue=new c(null);handlers=new Map;internal=new Map;connectHandlers=new Set;scopeList=null;warnedScopes=new Set;warnedNoWidget=!1;resolveReady;rejectReady;readySettled=!1;constructor(e={}){let t=B();if(this.demo=e.demo===!0,this.key=e.key??t?.key??"",this.control=!this.key&&t?.control===!0,this.previewMode=this.control&&t?.preview===!0,!this.key&&!this.demo&&!this.control)throw new l("unauthorized","pixl: `key` fehlt (API-Schlüssel aus „API & SDK“) oder `demo: true` setzen");if(t?.widget)this.widgetInstance=new k(this,t.widget,this.widgetOptions());if(this.logs=e.logs!==!1,g.sharedInstance??=this,this.fetchImpl=e.fetch??((...i)=>fetch(...i)),this.baseUrl=(e.baseUrl??t?.baseUrl??g.defaultBaseUrl??Ee).replace(/\/+$/,""),this.status=this.statusValue,this.channel=this.channelValue,this.ready=new Promise((i,n)=>{this.resolveReady=i,this.rejectReady=n}),this.ready.catch(()=>{}),this.events=new M(this),this.twitch=new V(this),this.spotify=new A(this),this.tts=new O(this),this.chat=new I(this),this.streamElements=new F(this),this.stream=this.demo?null:new R({url:`${this.baseUrl}/api/v1/stream?v=2`,token:()=>this.key,fetch:this.fetchImpl,onMessage:(i)=>this.handleMessage(i),onStatus:(i)=>this.handleStatus(i),log:(i)=>this.log(i)}),this.control){this.listenToDashboard(e.autoConnect!==!1);return}if(e.autoConnect!==!1)queueMicrotask(()=>this.connect())}widget(){if(!this.widgetInstance&&!this.warnedNoWidget&&this.scopeList)this.warnedNoWidget=!0,console.warn("[pixl] widget() geht nur in gehosteten Widgets, nicht mit einem normalen API-Schlüssel");return this.widgetInstance}get scopes(){return this.scopeList}on(e,t){let i=this.handlers.get(e);if(!i)this.handlers.set(e,i=new Set);return i.add(t),this.checkScope(e),()=>{i.delete(t)}}sendTestEvent(e,t={}){let i=w[e],n={type:e,data:{...i,...t},at:new Date().toISOString(),source:"test"};return this.dispatch(n),n}connect(){if(this.demo){this.handleConnected({type:"connected",protocol:2,channel:ke,scopes:Ce});return}this.stream?.start()}close(){if(g.sharedInstance===this)g.sharedInstance=null;if(this.stream?.stop(),this.demo)this.statusValue.set("closed");this.handlers.clear(),this.internal.clear(),this.connectHandlers.clear(),this.twitch._close(),this.spotify._close(),this.channelValue.clear(),this.statusValue.clear()}async request(e,t={}){return await(await this.fetchRaw(e,t)).json()}async fetchRaw(e,t={}){if(this.demo)throw new l("demo","Im Demo-Modus gibt es keinen Server");if(!this.key)throw new l("unauthorized","Noch kein Zugang (Steuer-Seite wartet auf das Dashboard)");let i=new URL(`${this.baseUrl}/api/v1${e}`);for(let[s,o]of Object.entries(t.query??{}))if(o!==void 0&&o!=="")i.searchParams.set(s,String(o));let n={Authorization:`Bearer ${this.key}`};if(t.body!==void 0)n["Content-Type"]="application/json";else if(t.raw)n["Content-Type"]=t.raw.type||"application/octet-stream";let r;try{r=await this.fetchImpl(i,{method:t.method??"GET",headers:n,body:t.raw??(t.body!==void 0?JSON.stringify(t.body):void 0),signal:t.signal})}catch(s){if(s instanceof DOMException&&s.name==="AbortError")throw s;throw new l("network","pixl ist gerade nicht erreichbar")}if(!r.ok)throw await z(r);return r}_onConnect(e){this.connectHandlers.add(e)}_listen(e,t){let i=this.internal.get(e);if(!i)this.internal.set(e,i=new Set);return i.add(t),()=>{i.delete(t)}}widgetOptions(){let e=B()?.preview===!0;return{preview:e,bind:!this.control||e}}get _preview(){return this.previewMode}_log(e){this.log(e)}handleMessage(e){if(e.type==="connected"){this.handleConnected(e);return}if(e.type==="access_revoked")return;if(e.type.startsWith("widget.")){this.handleWidgetMessage(e);return}this.dispatch(e)}handleWidgetMessage(e){if(e.type==="widget.version"){if(!this.control&&B()?.widget&&typeof location<"u")location.reload();return}this.widgetInstance?._handle(e)}listenToDashboard(e){if(typeof window>"u"||window.parent===window){this.log("Steuer-Seite außerhalb des Dashboards geöffnet, kein Zugang");return}window.addEventListener("message",(t)=>{if(t.source!==window.parent||typeof t.data!=="object"||!t.data)return;if(t.data.type==="pixl:control"&&typeof t.data.token==="string"){let i=!this.key;if(this.key=t.data.token,i&&e)this.connect()}}),Q(),window.parent.postMessage({type:"pixl:ready"},"*")}handleConnected(e){if(this.scopeList=e.scopes,this.channelValue.set(e.channel),e.widget)if(this.widgetInstance)this.widgetInstance._setState(e.widget);else this.widgetInstance=new k(this,e.widget,this.widgetOptions());this.statusValue.set("connected");for(let i of this.handlers.keys())this.checkScope(i);let t=[...this.connectHandlers].map((i)=>Promise.resolve().then(i));Promise.allSettled(t).then(()=>{if(this.readySettled)return;this.readySettled=!0,this.resolveReady()})}handleStatus(e){if(e==="connected")return;if(this.statusValue.set(e),e==="unauthorized"&&!this.readySettled)this.readySettled=!0,this.rejectReady(new l("unauthorized","Zugang ungültig oder widerrufen"))}dispatch(e){for(let t of[...this.internal.get(e.type)??[]])u(()=>t(e));for(let t of[...this.handlers.get(e.type)??[]])u(()=>t(e));for(let t of[...this.handlers.get("*")??[]])u(()=>t(e))}checkScope(e){if(!this.scopeList||e==="*")return;let t=H(e);if(!t||this.scopeList.includes(t)||this.warnedScopes.has(t))return;this.warnedScopes.add(t),console.warn(`[pixl] "${e}" kommt nie an: dem Schlüssel fehlt die Erlaubnis ${t}`)}log(e){if(this.logs)console.info(`[pixl] ${e}`)}}function le(e){if(!e)return;try{let t=new URL(e);return t.pathname.replace(/\/+$/,"").endsWith("/sdk/pixl.js")?t.origin:void 0}catch{return}}var We=typeof document<"u"?document.currentScript:null,de=le(We?.src);if(de)g.defaultBaseUrl=de;Object.assign(globalThis,{Pixl:g,PixlError:l});var Ie=globalThis.__PIXL__;if(Ie?.widget&&typeof document<"u"){let e=()=>void g.shared();if(document.readyState==="loading")document.addEventListener("DOMContentLoaded",e,{once:!0});else queueMicrotask(e)}})();
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { type SpotifyNowPlaying } from "./api-types/index.js";
|
|
2
|
+
import type { Pixl } from "./client.js";
|
|
3
|
+
import { type LiveValue } from "./live-value.js";
|
|
4
|
+
export type { SpotifyNowPlaying };
|
|
5
|
+
export declare class PixlSpotify {
|
|
6
|
+
private readonly pixl;
|
|
7
|
+
/**
|
|
8
|
+
* Was gerade läuft (Scope `spotify:read`, Modul Musik). `track` ist null, wenn nichts
|
|
9
|
+
* läuft. Der Wert selbst null, solange unbekannt oder das Modul aus ist.
|
|
10
|
+
*/
|
|
11
|
+
readonly nowPlaying: LiveValue<SpotifyNowPlaying | null>;
|
|
12
|
+
private readonly value;
|
|
13
|
+
constructor(pixl: Pixl);
|
|
14
|
+
/**
|
|
15
|
+
* Fortschritt des laufenden Songs in ms zum Zeitpunkt `now`. Rechnet ab der letzten
|
|
16
|
+
* Messung weiter, solange der Song läuft, höchstens bis zur Songlänge.
|
|
17
|
+
*/
|
|
18
|
+
progress(now?: number): number;
|
|
19
|
+
private load;
|
|
20
|
+
/** @internal */
|
|
21
|
+
_close(): void;
|
|
22
|
+
}
|
package/dist/stream.d.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { StreamMessage } from "./api-types/index.js";
|
|
2
|
+
export type ConnectionStatus = "connecting" | "connected" | "disconnected" | "unauthorized" | "closed";
|
|
3
|
+
export type StreamOptions = {
|
|
4
|
+
url: string;
|
|
5
|
+
/** Zugang, wird bei jedem Verbinden neu gelesen (Steuer-Tokens laufen ab und werden erneuert) */
|
|
6
|
+
token: () => string;
|
|
7
|
+
onMessage: (message: StreamMessage) => void;
|
|
8
|
+
onStatus: (status: ConnectionStatus) => void;
|
|
9
|
+
log: (message: string) => void;
|
|
10
|
+
/** Zum Testen austauschbar */
|
|
11
|
+
fetch?: typeof fetch;
|
|
12
|
+
};
|
|
13
|
+
export declare class LiveStream {
|
|
14
|
+
private readonly options;
|
|
15
|
+
private abort;
|
|
16
|
+
private reconnectTimer;
|
|
17
|
+
private watchdog;
|
|
18
|
+
private attempt;
|
|
19
|
+
private stopped;
|
|
20
|
+
private everConnected;
|
|
21
|
+
private lostAt;
|
|
22
|
+
constructor(options: StreamOptions);
|
|
23
|
+
start(): void;
|
|
24
|
+
stop(): void;
|
|
25
|
+
private run;
|
|
26
|
+
private scheduleReconnect;
|
|
27
|
+
private armWatchdog;
|
|
28
|
+
private clearWatchdog;
|
|
29
|
+
private read;
|
|
30
|
+
}
|