Salt la conținutul principal

Prezentare generală

Componentele front-end sunt componente React care se afișează direct în interfața Twenty. Acestea rulează într-un Web Worker izolat folosind Remote DOM — codul se execută într-un iframe cu origine opacă, într-un mediu izolat (sandboxed), însă interfața sa se redă în continuare nativ în pagină, în loc să fie limitată la acel iframe.
Componentele Front sunt încă în curs de dezvoltare activă. Codul tău se execută pe un DOM parțial, nu pe o pagină reală a browserului, astfel încât utilizările avansate pot eșua, adesea în tăcere. Vezi Limitări actuale.

Unde pot fi utilizate componentele front-end

Componentele front-end pot fi afișate în patru locații în cadrul Twenty:
  • Panou lateral — Componentele front-end care nu sunt headless se deschid în panoul lateral din dreapta. Acesta este comportamentul implicit atunci când o componentă front-end este declanșată din meniul de comenzi.
  • Widgeturi (tablouri de bord și pagini de înregistrare) — Componentele frontale pot fi încorporate ca widgeturi în machetele de pagină. La configurarea unui tablou de bord sau a machetei unei pagini de înregistrare, utilizatorii pot adăuga un widget de componentă front-end.
  • Setările aplicației — Indicat de o declarație defineSettingsMenuItem(), componenta front-end afișează pagina din spatele unuia dintre elementele meniului de setări al aplicației. O aplicație poate declara mai multe.
  • Chat AI — O funcție logică expusă ca instrument AI poate denumi o componentă front-end, care apoi redă apelurile acelui instrument direct în chat, în locul rândului de pași implicit.
O componentă frontală, de una singură, nu este accesibilă din interfața utilizatorului — trebuie să o expui. Cele patru moduri de a face asta sunt:

Exemplu de bază

Cel mai rapid mod de a vedea o componentă frontală în acțiune este să o asociezi cu un defineCommandMenuItem, astfel încât să apară ca un buton de acțiune rapidă în colțul din dreapta sus al paginii:
src/front-components/hello-world.tsx
src/command-menu-items/hello-world.command-menu-item.ts
După sincronizarea cu twenty app dev (sau după rularea o singură dată a comenzii twenty app apply), acțiunea rapidă apare în colțul din dreapta sus al paginii:
Buton de acțiune rapidă în colțul din dreapta sus
Faceți clic pe el pentru a afișa componenta inline.

Câmpuri de configurare

Plasarea unei componente front-end pe o pagină

Dincolo de comenzi, puteți încorpora o componentă front-end direct într-o pagină de înregistrare adăugând-o ca widget într-un layout de pagină. Vezi Machete de pagină pentru detalii. widgetAvailability decide ce selector de widgeturi oferă componenta în timp ce este editată configurația unei pagini. O componentă front este creată fie pentru pagini de înregistrare, unde funcționează pe înregistrarea afișată, fie pentru tablouri de bord, astfel că declară un pageLayoutType:
Omiteți widgetAvailability pentru o componentă care este deschisă numai dintr-o comandă, dintr-un element de meniu de setări sau dintr-un rând de cronologie. Nu limitează niciodată locul în care este afișată o componentă: comenzile, elementele din meniul de setări și configurațiile de pagină pe care le declară aplicația dvs. o redau indiferent de valoarea sa. Compilarea afișează un avertisment pentru o componentă vizibilă care nu declară niciuna și nu este deschisă din altă parte.

Elemente din meniul de setări

O aplicație poate adăuga propriile intrări în meniul de setări, fiecare afișând o componentă front-end. Declarați una cu defineSettingsMenuItem() într-un fișier separat, indicând către componenta care o afișează:
src/settings-menu-items/sync-settings.ts
Elementul de meniu indică către componenta front-end, nu către componenta care se declară pe sine ca intrare de setări. Prin urmare, o componentă poate sta în spatele mai multor elemente de meniu, iar un element poate fi redirecționat către o altă componentă fără a modifica vreuna dintre ele. Fiecare element este afișat ca filă pe pagina aplicației, sub Setări > Aplicații, după General, în ordinea position. Declarați câte aveți nevoie. Propriul element Generale al Twenty — actualizarea automată, URL-ul aplicației și conexiunile — are întotdeauna prioritate și nu poate fi suprascris de o aplicație; URL-ul aplicației se afișează doar atunci când aplicația are funcții declanșate prin HTTP.

Câmpuri

position este un număr zecimal, astfel încât un element poate fi plasat între două elemente existente fără renumerotare. Elementele sunt sortate după position în ordine crescătoare, iar egalitățile sunt departajate după universalIdentifier, astfel încât ordinea este aceeași în fiecare spațiu de lucru. Două elemente ale aceleiași aplicații nu pot avea aceeași poziție în același domeniu — sincronizarea refuză acest lucru, în loc să lase ordinea instalării să decidă care este afișat primul.

URL-uri ale filelor

Fila fiecărui element este identificată printr-un hash în URL-ul paginii, astfel încât să puteți crea direct un link către aceasta, de exemplu /settings/applications/\<applicationId>#sync sau /settings/app-preferences/\<applicationId>#sync. Setați slug pentru a alege acel hash; fără acesta, fila este identificată prin universalIdentifier al elementului. Adăugarea ulterioară a unui slug păstrează funcționale linkurile care folosesc universalIdentifier: acestea deschid aceeași filă. Un slug este în format kebab-case cu litere mici (litere, cifre și cratime simple, precum sync sau sync-settings) și are cel mult 64 de caractere. general și variables sunt rezervate pentru filele integrate, un slug în format UUID este respins deoarece ar putea masca linkul altei file, iar două elemente ale aceleiași aplicații nu pot partaja un slug, nici chiar în scopuri diferite. Atât buildul, cât și sincronizarea resping un slug care încalcă aceste reguli. După ce un membru conectează sau reconectează un cont în Setări > Preferințe aplicație, acesta ajunge la prima filă de element USER a aplicației, în ordinea position, sau la Generale atunci când aplicația nu declară niciun astfel de element.

Domeniul spațiului de lucru și al utilizatorului

scope specifică cine configurează un element:
  • WORKSPACE (implicit) — configurat o singură dată pentru întregul spațiu de lucru, alături de celelalte setări ale spațiului de lucru. Acestea sunt elementele afișate ca file pe pagina aplicației.
  • USER — configurat de fiecare membru pentru sine, în propriile setări de utilizator.
Un element USER se redă ca o filă pe pagina aplicației, în Setări > Preferințe aplicație, unde doar membrul autentificat îl vede și doar atunci când rolul său acordă permisiunea Aplicații utilizator. Acea pagină nu este încă activată în fiecare spațiu de lucru: acolo unde nu este activată, un element USER este păstrat și sincronizat, dar nimic nu îl afișează.
Un element USER rulează ca membrul care îl vizualizează. Pentru a stoca ceva pentru fiecare persoană, declară variabila cu scope: 'USER': getApplicationVariable returnează valoarea membrului care vizualizează, iar componenta dvs. o salvează cu updateMyUserApplicationVariable, care scrie doar valoarea acelui membru și necesită SystemPermissionFlag.USER_APPS în rolul aplicației dvs., dacă aceasta declară unul (consultați Citirea și actualizarea variabilelor în timpul rulării). La fel ca un element WORKSPACE, un element USER înlocuiește formularul Variabile generat, așa că ar trebui să permită membrului să seteze fiecare variabilă de utilizator de care are nevoie aplicația dvs. O variabilă de spațiu de lucru conține o singură valoare partajată de toată lumea, astfel încât salvarea unui membru ar suprascrie valoarea altuia.
defineSettingsFrontComponent() este învechită în favoarea defineSettingsMenuItem(). O aplicație care îl utilizează în continuare funcționează — componenta sa este tratată ca un singur element de meniu Setări — însă poate declara doar unul, limitare pe care defineSettingsMenuItem() există pentru a o elimina.

Headless vs non-headless

Componentele front-end au două moduri de randare controlate de opțiunea isHeadless: Non-headless (implicit) — Componenta afișează o interfață vizibilă. Când este declanșată din meniul de comenzi, se deschide în panoul lateral. Acesta este comportamentul implicit când isHeadless este false sau omis. Headless (isHeadless: true) — Componenta se montează invizibil în fundal. Nu deschide panoul lateral. Componentele headless sunt concepute pentru acțiuni care execută logică și apoi se demontează — de exemplu, rularea unei sarcini asincrone, navigarea la o pagină sau afișarea unui modal de confirmare. Se potrivesc în mod natural cu componentele Command din SDK descrise mai jos.
src/front-components/sync-tracker.tsx
Deoarece componenta returnează null, Twenty omite redarea unui container pentru ea — nu apare spațiu gol în layout. Componenta are în continuare acces la toate hook-urile și la API-ul de comunicare cu gazda.

Componentele Command din SDK

Pachetul twenty-sdk oferă patru componente ajutătoare Command, concepute pentru componente front-end headless. Fiecare componentă execută o acțiune la montare, gestionează erorile afișând o notificare snackbar și demontează automat componenta front-end la final. Importați-le din twenty-sdk/front-component:
  • Command — Rulează un callback asincron prin prop-ul execute.
  • CommandLink — Navighează către o rută a aplicației. Props: to, params, queryParams, options.
  • CommandModal — Deschide un modal de confirmare. Dacă utilizatorul confirmă, execută callback-ul execute. Props: title, subtitle, execute, confirmButtonText, confirmButtonAccent.
  • CommandOpenSidePanelPage — Deschide conținut în panoul lateral. Paginile rutate folosesc aceleași props tipizate to, params și queryParams ca CommandLink, plus props opționale hash, pageTitle și resetNavigationStack. Registrul de rute al spațiului de lucru decide dacă AppPath solicitat acceptă panoul lateral. pageTitle înlocuiește titlul antetului panoului, iar resetNavigationStack șterge paginile anterioare ale panoului înainte de a deschide ruta. Instrumentele de panou dedicate folosesc propriile proprietăți specifice paginii.
Iată un exemplu complet de componentă front-end headless care folosește Command pentru a rula o acțiune din meniul de comenzi:
src/front-components/run-action.tsx
src/command-menu-items/run-action.command-menu-item.ts
Și un exemplu care folosește CommandModal pentru a cere confirmarea înainte de execuție:
src/front-components/delete-draft.tsx
Și un exemplu care folosește CommandOpenSidePanelPage pentru a deschide înregistrarea curentă în panoul lateral, pe un tab specific. hash este un id de tab al layout-ului paginii fără # de la început (layout-urile implicite folosesc id-uri precum company-tab-emails sau company-tab-timeline; layout-urile personalizate folosesc propriul id al tab-ului). Dacă id-ul nu există în layout-ul înregistrării, se deschide în schimb tab-ul implicit:
src/front-components/open-company-emails.tsx

Apelarea unei funcții logice

Componentele de front rulează în browser, într-un Web Worker sandboxat în interiorul unui iframe cu origine opacă, în timp ce funcțiile logice rulează pe server. Nu există un apel direct în același proces între cele două — în schimb, o componentă de front apelează o funcție logică prin HTTP. O funcție logică declarată cu httpRouteTriggerSettings este accesibilă prin HTTP la ruta sa. RestApiClient tratează căile care încep cu /s/ ca rute ale aplicației, le rezolvă către URL-ul de la care sunt deservite funcțiile tale și le autentifică folosind TWENTY_APP_ACCESS_TOKEN.
În Twenty Cloud, funcțiile logice declanșate prin HTTP sunt deservite pe un domeniu dedicat pentru fiecare spațiu de lucru la https://\<your-workspace-subdomain>.withtwenty.com\<path>. Pentru apelanții externi, copiază URL-ul exact din setările HTTP trigger ale funcției sau din fila Settings a aplicației.
O componentă de front headless poate efectua apelul la montare prin componenta Command, apoi se demontează automat:
src/front-components/sync-prs.tsx
Calea transmisă către RestApiClient este proprietatea httpRouteTriggerSettings.path a funcției logice, cu prefixul /s. Păstrează isAuthRequired: true; TWENTY_APP_ACCESS_TOKEN pe care Twenty îl generează pentru componenta ta autentifică cererea:
src/logic-functions/fetch-prs.logic-function.ts
TWENTY_APP_ACCESS_TOKEN este injectat automat — vezi Variabile de aplicație. Deoarece variabilele de aplicație secrete nu sunt niciodată expuse componentelor de front, păstrează cheile API și altă logică sensibilă în funcția logică, nu în componenta de front.

Apelarea API-ului REST Twenty

Pentru a apela rute HTTP ale aplicației sau pentru a citi și scrie înregistrări Twenty dintr-un front component, folosește RestApiClient din twenty-client-sdk/rest. Trimite căile de forma /s/... către URL-ul de bază al funcțiilor spațiului tău de lucru, iar orice altă cale, inclusiv /rest/..., către TWENTY_API_URL. Acționează întotdeauna ca persoana care se uită la pagină. runAs: 'application' este o opțiune exclusivă pentru funcțiile logice: o componentă nu primește niciodată propriul token al aplicației tale, astfel că solicitarea lui aici generează o eroare. Pune activitatea care necesită propriul acces al aplicației în spatele unei funcții logice și apeleaz-o în schimb pe aceasta. options acceptă headers, query (un „record” de parametri de query-string; valorile nule sau nedefinite sunt omise) și un AbortSignal prin signal. Un obiect body care nu este de tip FormData este serializat automat în JSON. La un 401, clientul reîmprospătează o dată tokenul de acces prin gazdă și reîncearcă cererea. URL-ul de bază și tokenul sunt rezolvate din mediu în mod implicit. Transmite suprascrieri către constructor atunci când este necesar — de exemplu, în teste:
Cererile eșuate declanșează o eroare RestApiClientError care expune status, statusText, url și body analizat:

Publicarea în cutia de intrare

sendInboxMessage și ajutoarele pentru mențiuni din cutia de intrare sunt exportate și din twenty-sdk/front-component, astfel încât o componentă poate publica un mesaj fără o funcție logică intermediară. Mesajul provine din aplicația ta, iar apelul include tokenul componentei, astfel încât membrul care vizualizează pagina are nevoie de permisiunea AI, la fel ca fiecare destinatar. Pentru a afișa conversația ulterior, navighează la aceasta folosind threadId returnat de apel:
  • workspaceMemberIds acceptă ID-uri ale membrilor spațiului de lucru, nu ID-ul de utilizator returnat de useUserId().
  • O conversație se deschide numai pentru participanții săi, așadar navighează la aceasta atunci când membrul care vizualizează pagina este unul dintre destinatari.
  • Cutia de intrare este o rută a paginii principale: navigate o deschide acolo, iar openSidePanelPage nu o poate deschide în panoul lateral.

Accesarea contextului de rulare

În interiorul componentei, folosiți hook-urile SDK pentru a accesa utilizatorul curent, înregistrarea curentă și instanța componentei:
src/front-components/record-info.tsx
Hook-uri disponibile:

Metadatele obiectului

Utilizați useSelectedObjectMetadata() pentru a identifica obiectul asociat cu un widget al paginii de înregistrare, un rând de cronologie personalizat, o comandă cu domeniu de aplicare la nivel de obiect sau o componentă front-end deschisă în panoul lateral cu un obiect (dintr-o comandă sau prin openSidePanelPage cu un objectNameSingular). Aceasta permite unei integrări să selecteze șabloane sau alte configurații pentru obiectul curent fără a căuta un ID de înregistrare în toate obiectele.
Rezultatul conține id (UUID-ul metadatelor obiectului din spațiul de lucru), nameSingular și namePlural. Numele sunt nume de metadate, precum person și people, nu etichete afișate. Metadatele obiectului pot fi disponibile chiar și atunci când nu este selectată nicio înregistrare. Acestea aparțin contextului acestei componente front-end, astfel încât un panou lateral poate expune un obiect diferit de cel din pagina principală. Hook-ul se actualizează atunci când acel context se modifică și returnează null atunci când obiectul nu poate fi determinat, când nu este furnizat niciun context de obiect sau când rulează pe o gazdă mai veche care nu oferă metadate despre obiect.

Locația gazdei

Utilizați useLocation() pentru a citi ruta care găzduiește componenta front. Returnează un obiect FrontComponentLocation cu patru câmpuri șir obligatorii: pathname, search, hash și origin. De exemplu, o pagină la https://myworkspace.twenty.com/page/page-layout-id?project=abc#details furnizează /page/page-layout-id, ?project=abc, #details și, respectiv, https://myworkspace.twenty.com. hash include # de la început și este '' atunci când nu există niciun fragment. pathname, search și hash urmează contextul React Router al componentei, inclusiv propria rută a unui panou lateral rutat. origin provine din aplicația gazdă. Hook-ul se actualizează când ruta se schimbă. Gazdele mai vechi pot omite executionContext.location, astfel încât useLocation() returnează null când locația nu este disponibilă. Pentru parametrii de interogare, citiți useLocation()?.search ?? '' și analizați rezultatul cu URLSearchParams. search include ? de la început și este '' atunci când ruta nu are un șir de interogare. Rezerva oferă, de asemenea, '' când gazda nu furnizează locația.

Variabile de aplicație

Variabilele de aplicație definite în defineApplication() cu isSecret: false sunt disponibile în componentele de interfață prin utilitarul getApplicationVariable:
src/front-components/greeting.tsx
Variabilele secrete (isSecret: true) nu sunt expuse componentelor de interfață. Acestea sunt disponibile doar în funcțiile logice, care rulează pe server. Acest lucru împiedică trimiterea către browser a valorilor sensibile, cum ar fi cheile API.
getApplicationVariable returnează întotdeauna un string (sau undefined), indiferent de type‑ul declarat al variabilei. Stringul este serializat în mod consecvent în funcție de tip (valorile boolean ca "true" / "false", numerele ca stringuri zecimale, array‑urile / obiectele ca JSON), în același format folosit pentru process.env în funcțiile logice — parsează‑l tu însuți (Number(...), JSON.parse(...), === 'true'). Vezi Tipuri de variabile.

Citirea și actualizarea variabilelor în timpul rulării

getApplicationVariable returnează valorile injectate la încărcarea componentei, astfel încât o valoare scrisă ulterior apare doar la următoarea încărcare. Pentru a citi valorile stocate sau pentru a scrie una (ce face un element din meniul de setări când salvează ce a introdus utilizatorul), utilizați MetadataApiClient:
src/front-components/app-settings.tsx
Notițe:
  • Tokenul componentei tale este asociat propriei tale aplicații. updateOneApplicationVariable o vizează, astfel încât applicationId este opțional, iar specificarea unei alte aplicații este refuzată. Citirea applicationVariables ale unei alte aplicații este refuzată în același mod, iar o aplicație care aparține unui alt spațiu de lucru este citită ca fiind negăsită.
  • Cheia trebuie să fie una pe care aplicația ta o declară în defineApplication(). Scrierea unei chei care nu este declarată returnează negăsit.
  • Variabilele secrete sunt citite înapoi mascate, niciodată ca valoarea stocată. Scrierea acestora funcționează normal.
  • Citirea value al unei variabile și scrierea acesteia necesită permisiunea de setări Workspace Apps atât pentru rolul aplicației dvs., cât și pentru rolul persoanei autentificate, deoarece tokenul componentei este intersecția celor două. Adăugați SystemPermissionFlag.WORKSPACE_APPS la permissionFlagUniversalIdentifiers în rolul aplicației.
  • O variabilă de utilizator (scope: 'USER') este injectată cu valoarea membrului care vizualizează, cu excepția cazului în care este secretă. updateOneApplicationVariable o refuză: salvați-o cu updateMyUserApplicationVariable, așa cum face saveMyLanguage mai sus. Scrie numai valoarea membrului care vizualizează și refuză să numească o altă aplicație. Nu necesită nicio permisiune Workspace Apps, dar necesită permisiunea Aplicații utilizator pentru rolul membrului care vizualizează și pentru rolul aplicației dvs., dacă aplicația dvs. declară un astfel de rol: adăugați SystemPermissionFlag.USER_APPS la permissionFlagUniversalIdentifiers în rolul aplicației. Consultați Variabile de spațiu de lucru și de utilizator.

Variabile de sistem

Următoarele variabile de sistem sunt întotdeauna disponibile prin process.env:

TWENTY_FUNCTIONS_URL

Twenty injectează, de asemenea, TWENTY_FUNCTIONS_URL în front components și în funcțiile logice: URL-ul de bază de la care sunt deservite funcțiile logice ale aplicației tale declanșate prin HTTP. Există deoarece acel URL nu este întotdeauna chiar serverul Twenty. În Twenty Cloud, rutele aplicației sunt deservite pe un domeniu dedicat pentru fiecare spațiu de lucru (https://\<your-workspace-subdomain>.withtwenty.com sau domeniul public principal al aplicației atunci când este configurat unul), astfel încât răspunsurile create de aplicație să ruleze pe propria lor origine, nu pe originea aplicației Twenty. Instanțele self-hosted și locale deservesc rutele aplicației sub prefixul /s chiar pe server și este posibil să nu seteze deloc variabila. Deoarece URL-ul de bază variază în funcție de spațiul de lucru și de instanță, codul tău nu îl poate hardcoda — serverul injectează valoarea corectă la runtime. Rareori ai nevoie să o citești direct. Apelează-ți rutele prin RestApiClient folosind o cale prefixată cu /s/, iar clientul îți rezolvă URL-ul: elimină prefixul /s și țintește TWENTY_FUNCTIONS_URL, folosind \<TWENTY_API_URL>/s ca rezervă atunci când variabila nu este setată. Folosește resolveUrl('/s/\<path>') pentru a obține URL-ul absolut fără a trimite o cerere, de exemplu pentru un link. Citește variabila direct doar atunci când construiești manual un URL:

API-ul de comunicare cu gazda

Componentele front-end pot declanșa navigare, ferestre modale și notificări folosind funcții din twenty-sdk/front-component: Utilizați openCommandConfirmationModal din twenty-sdk/front-component pentru casete de dialog. Aceasta acceptă opțiuni structurate pentru titlu, subtitlu și butoane și se rezolvă la 'confirm' sau 'cancel'. Twenty gestionează fereastra modală, gestionarea focalizării, închiderea și derularea paginii. Componentele front-end nu trebuie să redea direct componentele Dialog, AlertDialog, elementele dialog native sau ferestrele popover native ale browserului. Furnizarea unui container de portal local nu face ca modalitatea deținută direct de aplicație să fie acceptată. Iată un exemplu care folosește API-ul gazdei pentru a afișa un snackbar și a închide panoul lateral după finalizarea unei acțiuni:
src/front-components/archive-record.tsx

Stocare

localStorage și sessionStorage funcționează la fel ca într-o pagină normală, cu API-ul sincron standard. Cheile tale sunt limitate la instalarea aplicației tale și la utilizatorul autentificat: nicio altă aplicație nu le poate citi, iar un alt utilizator care se conectează în același browser pornește de la un spațiu de stocare gol. Valorile scrise în localStorage rămân pe dispozitiv între reîncărcări; sessionStorage durează pe durata sesiunii de browser.
src/front-components/note-draft.tsx
Twenty stochează valorile în numele aplicației tale, astfel încât o operație de scriere este aplicată local imediat și salvată în fundal. Citirile nu așteaptă niciodată gazda. Nimic nu este sincronizat: valorile nu urmează utilizatorul într-un alt browser sau pe o altă mașină, așa că folosește key-value store-ul al unei logic function pentru orice trebuie să supraviețuiască unei schimbări de dispozitiv. Scrierile sunt limitate, iar fiecare limită ia în calcul caracterele, nu octeții: cheile au cel mult 512 caractere, o singură valoare are cel mult 262.144 de caractere, iar fiecare spațiu de stocare are cel mult 1.048.576 de caractere per aplicație și utilizator. O scriere care depășește o limită aruncă o eroare QuotaExceededError, la fel ca API-ul de browser.

Lucrul cu mai multe înregistrări

Folosiți useSelectedRecordIds() pentru a gestiona mai multe înregistrări selectate. Acest lucru este util pentru operațiuni în masă:
src/front-components/bulk-export.tsx
Afișați-o cu un element de meniu de comandă restricționat la selecțiile de înregistrări:
src/command-menu-items/bulk-export.command-menu-item.ts

Selectați tot

Când utilizatorul selectează toate înregistrările dintr-o vizualizare, useSelectedRecordIds() returnează o matrice goală: selecția este definită de filtrele, căutarea și rândurile nebifate ale vizualizării și poate conține mult mai multe înregistrări decât încap în memorie. useSelectedRecordsFilter() returnează un filtru care corespunde exact înregistrărilor selectate în fiecare caz, așadar interogați folosindu-l în loc să listați ID-uri:
Pentru selecții mari, transmiteți filtrul unei funcții logice și procesați înregistrările acolo, pagină cu pagină.

Resurse publice

Componentele front-end pot accesa fișiere din directorul public/ al aplicației folosind getPublicAssetUrl:
Consultați secțiunea despre resurse publice pentru detalii.

Partajare dependențe între componentele din față

În mod implicit, fiecare componentă din față grupează propria copie a bibliotecilor pe care le importă, astfel încât o aplicație cu cinci componente expediază React de cinci ori. Declară dependențe partajate în fișierul package.json pentru a construi acele biblioteci o dată și a avea fiecare componentă a aplicației să le încarce dintr-un singur fișier cache:
package.json
Fiecare componentă importă apoi dependențele sale exact ca înainte - nimic nu modifică codul componentei tale:
src/front-components/counter.tsx
Câteva lucruri de știut:
  • Un pachet de dependențe partajate pentru fiecare aplicație. Pachetul este construit din propriile dependențe ale aplicației dvs., așa că țineți controlul deplin asupra versiunilor pe care le expediați.
  • Enumerați specificatorii exacți pe care îi importați. twenty-ui/primitives/input și twenty-ui/primitives/data-display sunt două intrări; un singur nume de pachet nu acoperă subcăile acestuia. Listarea react acoperă automat react/jsx-runtime.
  • Distribuie react-dom/client alături de react. Fiecare componentă redă prin createRoot, astfel lăsând-o înseamnă că fiecare componentă încă lipsește React DOM.
  • **Pachetul este cacheat. * Este servită sub un URL content-hash cu o geocutie mutabilă de lungă durată, astfel încât acesta este descărcat o dată și reutilizat pe toate componentele aplicației până când una dintre dependențele sale se schimbă.
  • Componente care importă niciunul dintre pachetele partajate nu îl descarcă niciodată.

Stilizare

Componentele front-end acceptă mai multe abordări de stilizare. Puteți folosi:
  • Stiluri inline — style={{ color: 'red' }}
  • Componente UI Twenty — biblioteca proprie de componente a Twenty; vezi Folosirea componentelor UI Twenty mai jos
  • Emotion — CSS-in-JS cu @emotion/react
  • Styled-components — pattern-uri styled.div
  • Tailwind CSS — clase utilitare
  • Orice bibliotecă CSS-in-JS compatibilă cu React

Folosirea componentelor UI Twenty

Twenty livrează biblioteca sa de componente ca pachetul twenty-ui. Preferați controalele și tokenurile de temă pentru a oferi componentelor frontale același aspect și aceleași interacțiuni ca în restul Twenty. Consultați componentele partajate și elementele primitive pentru exemple, proprietăți și API-uri de compunere.

Instalare

Aplicațiile noi generate cu twenty app init includ deja twenty-ui. Pentru o aplicație existentă, adăugați-l ca dependență de dezvoltare, fixată la aceeași versiune ca twenty-sdk și twenty-client-sdk. De exemplu, dacă pachetele SDK utilizează 2.51.0:
twenty-ui este inclus în momentul compilării, fie în fiecare componentă frontală, fie în pachetul de dependențe partajate al aplicației dvs. Twenty încarcă foile de stil ale componentelor și temei și aplică schema de culori activă a spațiului de lucru. Nu trebuie să imporți twenty-ui/style.css, foile de stil ale temei sau să adaugi un ThemeProvider rădăcină, așa cum ai face într-o aplicație React independentă.

Importarea componentelor

Importă din subcalea publică a familiei corespunzătoare pentru a menține dependențele concentrate: Componentele de control fundamentale sunt grupate în twenty-ui/primitives/\<family>. Presetările de design partajate și elementele de bază pentru construirea aplicației sunt grupate în twenty-ui/components/\<family>. Punctul de intrare agregat twenty-ui/components rămâne disponibil, dar importurile pe familie facilitează partajarea doar a dependențelor pe care le folosești. Folosește MainButton pentru acțiuni importante și LightButton pentru acțiuni ușoare. Folosește Button, Field, Input, Select și alte primitive din twenty-ui/primitives/input pentru controale și formulare fundamentale. Respectă părțile documentate ale fiecărui control compus, cum ar fi Field.Root, Field.Label și Field.Description. Transmiteți alignItemWithTrigger={false} către Select.Positioner. Alinierea elementului selectat cu elementul declanșator, comportamentul implicit al Base UI, necesită măsurători sincrone ale aspectului pe care sandboxul nu le poate furniza, astfel încât fereastra popup se restrânge când se redeschide. Folosește Dropdown din twenty-ui/components/navigation pentru meniuri de acțiuni, selectoare de opțiuni și panouri de formulare nonmodale. Folosește enqueueSnackbar din twenty-sdk/front-component pentru notificările gazdei. API-urile independente Dialog și AlertDialog sunt, de asemenea, exportate de Twenty UI, dar utilizarea directă în componentele front-end este interzisă. Utilizați API-ul de comunicare cu gazda pentru casete de dialog. Aceste puncte de intrare nu necesită Monaco. Punctul de intrare separat twenty-ui/components/code-editor depinde de Monaco, al cărui încărcător de scripturi nu este acceptat în sandbox-ul componentei front, astfel încât CodeEditor nu poate deveni interactiv acolo.

Pictograme

Importă pictograme individuale din twenty-ui/icon:
Fiecare pictogramă denumită este eliminată prin tree-shaking, astfel încât importarea câtorva adaugă foarte puțin la dimensiunea bundle-ului. Evitați twenty-ui/icon/provider (IconsProvider, useIcons și Icon după nume) în componentele front-end. Componentele front sunt incluse în pachet fără divizarea codului, astfel încât catalogul dinamic de pictograme al providerului (setul complet Tabler, aproximativ 2 MB minificat) ajunge în pachetul dvs. Consultați referința pictogramelor pentru exporturile disponibile și proprietățile acceptate.

Teme și tokeni de temă

Componentele Twenty UI corespund automat temei luminoase și întunecate a spațiului de lucru. Motorul de randare aplică schema de culori activă pe gazdă, iar componentele își rezolvă variabilele CSS în raport cu aceasta. Pentru a folosi aceiași tokeni de design în propriile tale stiluri inline, apelează useTheme() în corpul componentei. În componentele front, acesta returnează implicit referințe la variabile CSS, astfel încât stilurile urmează tema activă a gazdei fără un ThemeProvider local:
Aceleași referințe CSS sunt exportate ca themeCssVariables din twenty-ui/theme. Citește-le în corpul componentei: CLI-ul simulează twenty-ui în timpul extragerii manifestului, astfel încât dereferențierea constantelor de temă importate în domeniul modulului poate eșua la compilare. Șirurile de referință CSS nu sunt numere JavaScript evaluate, nici măcar pentru tokeni precum theme.icon.size.md. Nu efectua operații aritmetice asupra lor și nu le transmite API-urilor care necesită numere. Consultă Tematizare și Tokeni pentru căi semantice și nume de variabile CSS. Pentru a ramifica explicit în funcție de schema activă, citește-o cu useColorScheme() din twenty-sdk/front-component, care returnează 'light' sau 'dark'.

Limitări actuale

Componentele Front sunt în curs de dezvoltare activă. Redarea, stilizarea, gestionarea evenimentelor, măsurarea elementelor, portalurile body și spațiul de stocare al browserului funcționează bine. Orice ajunge dincolo de acestea (de exemplu, apelarea unei metode DOM pe un ref) lipsește sau este incomplet astăzi, iar majoritatea eșuează în tăcere: fără excepție și fără eroare TypeScript, deoarece scheletul este tipizat pentru întregul DOM al browserului. Dacă unul dintre aceste lucruri te blochează, deschide un tichet ca să fie prioritar.

Layout și măsurare

Elementele se pot măsura singure: gazda reflectă geometria în sandbox, astfel încât citirile sunt efectuate local, dar pot avea un decalaj de până la un cadru, iar prima citire a unui element nemăsurat anterior returnează zerouri. După scriere, recitește într-un callback requestAnimationFrame sau într-un efect. Poziționarea din getBoundingClientRect și observarea dimensiunii prin ResizeObserver funcționează ambele din aceeași oglindă, astfel încât apelurile de redimensionare rămân în urma gazdei cu aproximativ un cadru. Oricum, preferă CSS pentru layout: stylesheet-ul tău ajunge la pagina reală, astfel încât flexbox, grid, aspect-ratio, clamp() și @container se comportă normal, fără întârziere de cadru. matchMedia() evaluează width, height, orientation, -webkit-device-pixel-ratio, resolution, hover, pointer și prefers-color-scheme, în forma min-/max-, forma de interval de nivelul 4 ((width >= 600px), (400px \<= width \<= 800px)) și forma fără valoare ((width)). Lățimile, înălțimile și orientation descriu caseta widgetului dvs., nu fereastra de vizualizare a browserului, astfel încât urmează document.body.clientWidth în loc de window.innerWidth, iar em și rem sunt considerate ca având 16px. hover și pointer descriu modalitatea principală de intrare a dispozitivului utilizatorului. Orice altă condiție este considerată necunoscută, la fel cum browserele tratează caracteristicile necunoscute: elementul separat prin virgulă în care apare este evaluat la false, chiar și sub not, cu excepția cazului în care restul acelui element decide deja rezultatul, ca în not print and (prefers-reduced-motion: reduce). Acest lucru acoperă caracteristicile necunoscute și sintaxa pe care acest analizator nu o implementează în interiorul unei condiții, precum grupurile imbricate, cum ar fi ((min-width: 600px)), not în interiorul unui grup și valorile calc(). Un or între condiții face ca elementul său să fie false. Constructorii MediaQueryList și MediaQueryListEvent nu sunt expuși nici ei, astfel încât verificările instanceof față de aceștia generează un ReferenceError. Deoarece acea foaie de stil ajunge la pagina reală, o regulă @media (min-width: ...) este evaluată de gazdă în raport cu fereastra de vizualizare a browserului, în timp ce matchMedia('(min-width: ...)') răspunde pentru caseta widgetului dvs. Pentru ca CSS să urmeze aceeași casetă pe care o vede JavaScript-ul dvs., declarați propriul container-type pe un înveliș și interogați-l: o regulă @container fără unul se rezolvă în raport cu cel mai apropiat container al gazdei, nu cu widgetul dvs. Aceeași separare se aplică schemei de culori, unde o foaie de stil @media (prefers-color-scheme: dark) urmează preferința browserului și a sistemului de operare, în timp ce matchMedia('(prefers-color-scheme: dark)') urmează tema selectată în Twenty, așadar folosiți tokenurile din useTheme() pentru stilizare atunci când cele două trebuie să fie consecvente. Înălțimea care vă este furnizată este tot propria dvs. casetă și, în funcție de locul în care gazda vă montează, acea casetă poate fi dimensionată de propriul conținut. Ramificarea aspectului în funcție de min-height, max-height sau orientation se poate autoalimenta și oscila. Preferați interogările bazate pe lățime sau @container cu container-type: inline-size pentru deciziile privind aspectul.
requestAnimationFrame funcționează, cu sau fără prefixul window.. Doar Chrome rulează un cadru de animație real într-un worker; Firefox și Safari primesc un temporizator de 16 ms, așadar folosește-l pentru a amâna o recitire după o scriere, nu pentru a rula o animație.

Derulare

Atribuirea scrollTop sau scrollLeft, apelarea scrollTo() sau a aliasului său scroll(), precum și apelarea scrollIntoView() derulează elementul corespunzător de pe pagină:
  • Modificările derulării se aplică după următoarea randare, în ordinea în care le-a efectuat codul dvs. În cadrul unei randări, modificările selecției de intrare se aplică primele, astfel încât setSelectionRange() urmat de o atribuire pentru scrollLeft menține cursorul vizibil.
  • Citirile returnează valoarea pe care ați scris-o până când pagina raportează poziția reală, pe care browserul o limitează la intervalul care poate fi derulat. În containerele de la dreapta la stânga, scrollLeft este zero sau negativ, ca în browsere.
  • scrollIntoView() acceptă block și inline cu start, center, end și nearest, aliniind block pe verticală și inline pe orizontală, astfel încât modurile de scriere verticale nu sunt mapate. Derulează numai containerele de derulare proprii ale componentei tale, niciodată cadrul widgetului, pagina sau o altă componentă. focus() afișează în continuare elementul focalizat așa cum o face browserul.
  • Derularea este instantanee: behavior și opțiunea container sunt ignorate, iar scrollBy() nu este disponibil.
  • Rescrierea unei poziții pe care tocmai ai citit-o, precum element.scrollTop = element.scrollTop, nu face nimic, deci nu poate anula o derulare efectuată între timp de utilizator. Acest lucru este valabil doar după ce pagina a măsurat elementul; până atunci, scrierea este aplicată.
  • Derularea document.documentElement, document.body sau window rămâne în sandbox și nu derulează niciodată pagina; window.scrollTo(), scroll() și scrollBy() nu fac nimic.
  • Prima citire a unui container care nu a fost niciodată măsurat returnează zerouri (consultați Aspect și măsurare), astfel încât o verificare a posibilității de derulare efectuată imediat după apariția containerului poate raporta că acesta nu poate fi derulat.

Domeniu global

Funcțiile native proprii ale workerului au, de asemenea, aliasuri în window, astfel încât funcționează cu sau fără prefixul window.: fetch, setTimeout/clearTimeout, setInterval/clearInterval, queueMicrotask, console, crypto, performance, structuredClone, btoa/atob, URL/URLSearchParams, AbortController/AbortSignal, Headers/Request/Response, Blob, File, FormData și TextEncoder/TextDecoder.

Acces DOM

Un ref îți oferă un element din sandbox, nu un HTMLElement. element.classList funcționează, fiind susținut de atributul class: add, remove, toggle, replace, contains, item, length, value și iterarea se comportă toate la fel ca într-un browser, iar className rămâne sincronizat. element.dataset funcționează la fel, fiind susținut de atributele data-*; scrie numele data-* cu litere mici, deoarece sandbox-ul păstrează numele atributelor așa cum sunt scrise, în loc să le transforme în litere mici ca un browser. Portalurile body se pot extinde până la 200px dincolo de fiecare margine a componentei tale, inclusiv în afara cadrului de derulare al unui widget. Suprapunerile mai mari sunt decupate la această zonă, așa că oferă meniurilor lungi propriul container cu derulare. Navigarea cu tastatura în listele Twenty UI, precum Autocomplete și Select, afișează opțiunea evidențiată în acel container. API-urile native de nivel superior, cum ar fi showModal(), showPopover() și requestFullscreen(), sunt blocate.

Evenimente

Mouse, pointer, touch, drag, tastatură, focus, input/change/submit, scroll/wheel/contextmenu și animationend/transitionend trec către gazdă, plus câteva per element: load/error pe img, clipboard și compoziție pe input/textarea, media pe video/audio, toggle pe details/dialog. Orice altceva (onAuxClick, onSelect, onInvalid, onReset, onAnimationStart, pointer capture, onLoad de pe img) este eliminat fără avertisment. Handler-ele primesc evenimente tipizate construite din evenimentul paginii, PointerEvent, MouseEvent, KeyboardEvent, InputEvent, WheelEvent sau FocusEvent, iar acei constructori există în sandbox pentru dispatchEvent(). Fiecare eveniment redirecționat include și suprafața de evenimente sintetice a React (nativeEvent, isDefaultPrevented(), isPropagationStopped(), persist()), astfel încât codul scris pentru evenimentele React continuă să funcționeze chiar dacă sandbox-ul transmite obiectul eveniment direct handler-ului tău. Un eveniment de pagină trece atunci când elementul cu care a interacționat utilizatorul sau unul dintre strămoșii săi din componenta ta are un handler pentru acel tip de eveniment sau când este o apăsare pe care listener-ele tale document o așteaptă (vezi mai jos). Traversează o singură dată, apoi se propagă în sandbox ca pe pagină: event.target este elementul cu care a interacționat utilizatorul, currentTarget este elementul al cărui handler rulează, stopPropagation() împiedică rularea handler-elor strămoșilor, iar relatedTarget indică elementul din sandbox către sau de la care s-a mutat focusul ori indicatorul sau este null atunci când acel element se află în afara componentei tale. Ca în React, onFocus și onBlur rulează și pentru modificările focusului din interiorul elementului, iar onClickCapture și celelalte handler-e de capturare rulează în timpul fazei de capturare. Handler-ele pe care o bibliotecă le adaugă cu React.cloneElement, precum cele din spatele prop-ului render al Base UI, rulează după handler-ele proprii ale elementului, în loc să le înlocuiască, astfel încât transmiterea onClick: undefined către cloneElement nu elimină handler-ul elementului. Un handler de pe un element render al Base UI poate totuși apela event.preventBaseUIHandler() pentru a omite handler-ul propriu al Base UI. event.target.value și checked reflectă controlul după ce browserul a aplicat modificarea utilizatorului: în handler-ele input, change, click, blur și focusout și în handler-ele keyup pentru value. În handler-ele keydown, beforeinput și de indicator, acestea păstrează valoarea și starea bifată a controlului de la ultima sa modificare, indiferent ce ascultă componenta ta: un handler Enter citește textul introdus până atunci, însă un handler pentru o tastă imprimabilă nu vede încă acea tastă, așa că citește valorile în timp real dintr-o intrare controlată sau dintr-un handler onChange. Listener-ele document primesc evenimentele care trec din componenta ta, cu o excepție pentru apăsări. În timp ce componenta ta ascultă pentru pointerdown, pointerup, pointercancel, mousedown, mouseup sau click pe document, așa cum face un popup deschis pentru a se închide la apăsări în exterior, fiecare apăsare ajunge la acei listeneri: o apăsare în interiorul componentei tale este transmisă la elementul pe care utilizatorul a apăsat chiar și atunci când nimic nu o gestionează, iar o apăsare în altă parte a paginii este transmisă la document.body cu starea butonului, numărul de clicuri și tipul pointerului, dar fără coordonate sau taste modificatoare. Clicurile fără o apăsare, cum ar fi activarea prin tastatură a unui control din afara componentei tale, și apăsările pe barele de derulare ale paginii nu sunt trimise, iar o glisare tactilă din afara componentei tale ajunge doar ca pointerdown și pointercancel, astfel încât nu închide un popup deschis. Nimic altceva din restul paginii nu trece, așa că, pentru a capta scurtături cu document.addEventListener('keydown', ...), pune un onKeyDown pe un element care înfășoară componenta ta. Listener-ele window primesc doar error și unhandledrejection. Mișcările indicatorului traversează doar peste elementele care au un handler de mișcare, iar capturarea indicatorului rămâne pe pagină, motiv pentru care o glisare se oprește imediat ce indicatorul părăsește acele elemente. event.preventDefault() nu trece nici el; trimiterea formularelor, dragover/drop și clicurile pe linkuri sunt deja protejate pentru tine.

Atribute și stilizare

Fiecare element își transmite propriile proprietăți către DOM-ul gazdă (href pe a, src/alt pe img, value/placeholder/disabled pe input etc.), plus un set comun pe fiecare element: id, className, style, title, tabIndex, role, draggable și orice atribut aria-* / data-* (cu cratimă, astfel încât ariaLabel este omis). Orice în afara acestora este ignorat în tăcere, așa că exprimă starea personalizată ca data-*. CSS-ul componentelor, fie din import './styles.css', CSS-in-JS sau un element style, este injectat în head-ul paginii gazdă fără scope (unscoped). Astfel numele de clase intră în coliziune cu cele ale Twenty (prefixează-le și nu scrie niciodată un selector simplu div { ... }), iar @media se potrivește cu fereastra browserului, nu cu widgetul tău (folosește @container cu propriul tău container-type). Proprietățile style inline nu sunt afectate.

Stocare și rețea

localStorage și sessionStorage sunt furnizate de Twenty, nu de browser: componenta rulează într-un worker cu o origine opacă, astfel încât gazda stochează valorile în numele aplicației tale. Consultă secțiunea storage pentru aria lor de aplicare și limite. IndexedDB, cookie-urile, Cache API și BroadcastChannel rămân indisponibile. Pentru a păstra starea între dispozitive, apelează o logic function și folosește key-value store-ul acesteia. fetch funcționează, cu unele rezerve:
  • Apelurile către Twenty API și către rutele aplicației tale sunt proxate de gazdă, așa că preferă RestApiClient. La apelurile proxate, AbortSignal și celelalte opțiuni RequestInit sunt eliminate, iar doar corpurile de tip string și URLSearchParams sunt acceptate.
  • Alte origini părăsesc sandbox-ul cu Origin: null, astfel încât un API terț răspunde doar dacă trimite Access-Control-Allow-Origin: *. Apelează-l dintr-o logic function în schimb.
  • fetch('/rest/people') nu este niciodată asociat cu Twenty API, deoarece sandbox-ul nu are un URL de pagină față de care să rezolve o cale relativă.

Selectarea fișierului

Controalele de introducere a fișierelor furnizează obiecte File native cu metadate și conținut care poate fi citit. Utilizați text() sau arrayBuffer() pentru a citi un fișier selectat sau transmiteți-l funcției gazdă uploadFile. Deschiderea selectorului nativ necesită apelarea input.click() în decurs de o secundă de la un clic de încredere în cadrul componentei dvs. Fluxurile React și Preact create cu SDK-ul sunt verificate în Chromium. URL-urile obiect create de worker și atribuite lui img.src acceptă previzualizări. Revocați URL-urile când înlocuiți imaginea sau demontați componenta. Aplicația dvs. este responsabilă de validarea fișierelor, progresul încărcării și anulare.

Captură media

Declarați microphone, camera sau ambele în matricea requestedCapabilities transmisă către defineApplication() pentru ca aceste capabilități să apară în timpul instalării. Aprobarea se aplică întregului spațiu de lucru și necesită un membru care poate gestiona aplicații. Dacă o aplicație instalată apelează navigator.mediaDevices.getUserMedia() fără accesul necesar, Twenty întrerupe capturarea și afișează un dialog de aprobare. Aceasta acceptă și aplicații mai vechi fără declarații de capabilități. Membrii care nu pot gestiona aplicații trebuie să solicite unui administrator al spațiului de lucru să aprobe accesul. Anularea dialogului respinge solicitarea înainte de a fi solicitate permisiunile browserului sau ale sistemului de operare. Actualizările pachetului păstrează permisiunile existente ale spațiului de lucru și nu acordă automat acces suplimentar la conținut media. Aplicațiile de dezvoltare sincronizate local primesc capabilitățile declarate în manifestul lor. După aprobarea accesului, Twenty nu mai întreabă pentru fiecare captură, însă permisiunile browserului și ale sistemului de operare se aplică în continuare. Un punct portocaliu în antetul spațiului de lucru indică o captură activă. Treceți cu cursorul peste punct sau selectați-l pentru a vedea aplicațiile care folosesc microfonul sau camera și pentru a opri captura acestora. Solicitările în așteptare apar ca un punct conturat cu un control de anulare. getUserMedia() și MediaRecorder rulează prin polyfill-uri sandbox, astfel încât codul standard de înregistrare și MediaRecorder.isTypeSupported() funcționează nemodificate după aprobare. Obiectele detaliate de constrângeri sunt acceptate, dar nu sunt transmise mai departe. Gazda realizează captura folosind valorile sale implicite pentru tipurile solicitate. Aplicații diferite pot realiza capturi simultan, iar oprirea unei aplicații afectează doar propriile sesiuni. Fiecare componentă front acceptă câte o captură în așteptare sau activă la un moment dat. Stochează un Blob înregistrat cu funcția gazdă uploadFile.

Site-uri web încorporate cu acces la conținut media

Pentru a încorpora un instrument de telefonie sau video, declarați originea HTTPS a acestuia în requestedIframeMediaPermissions din aplicația dvs., cu accesul la microfon și cameră configurat separat. Instalarea aplicației acordă spațiului de lucru acces pentru aceste site-uri web. Actualizările aplicațiilor publicate păstrează permisiunile existente și nu acordă automat acces suplimentar la conținut media. Aplicațiile de dezvoltare sincronizate local primesc automat site-urile web declarate în manifestul lor. Un membru care poate gestiona aplicații poate aproba solicitări noi din fila General a aplicației instalate, în Acces media pentru site-uri web încorporate > Revizuiește accesul media. Această aprobare utilizează o confirmare nativă a browserului care afișează aplicația, originile site-urilor web și accesul solicitat la microfon sau cameră. Twenty citește site-urile web autorizate când se încarcă o filă, deci reîncărcați filele Twenty deschise după instalare sau aprobare. Solicitați funcțiile corespunzătoare în iframe:
Pentru video, utilizați allow="microphone; camera" și declarați ambele pentru origine. Twenty deleagă doar accesul media acordat aplicației dvs. pentru acea origine. Membrii trebuie să selecteze controlul Activează al elementului încorporat și să aprobe o confirmare nativă a browserului care afișează aplicația, originea site-ului web și accesul solicitat la microfon sau cameră înainte ca acesta să primească acces la conținut media. Permisiunile browserului și ale sistemului de operare se aplică în continuare. srcDoc și elementele încorporate la originea proprie a Twenty nu pot primi această delegare. Widgeturile de aspect de pagină IFRAME definite de aplicație utilizează originile autorizate ale aplicației proprietare și același flux de confirmare. Elementele încorporate activate apar în același indicator din antet ca și capturarea nativă, cu Acces la microfon activat sau Acces la cameră activat, în loc să indice că înregistrarea este activă. Indicatorul rămâne vizibil cât timp accesul este activat, chiar dacă elementul încorporat este ascuns. Dezactivează descarcă iframe-ul și îl reîncarcă cu accesul media blocat. Capturarea aplicației prin getUserMedia() continuă să raporteze capturarea efectivă și păstrează propriul control Oprește. Conținutul iframe rămâne izolat într-un sandbox. Elementele externe încorporate cu conținut media aprobate își păstrează propria origine după ce membrul activează accesul, permițând site-ului web să își utilizeze sesiunea și să solicite permisiuni media în browser. Alte iframe-uri ale componentelor front-end păstrează o origine sandbox opacă.

Alte lacune

  • Payload-uri drag-and-drop. Evenimentele de tip drag sunt declanșate, dar event.dataTransfer este undefined.
  • Built-in-uri Node. fs, path și node:crypto eșuează la build, așa că mută acea logică într-o logic function. Web Crypto înlocuiește node:crypto, iar fetch, TextEncoder și URL sunt de asemenea disponibile, vedeți domeniul global — dar nimic nu înlocuiește fs: sandboxul nu are sistem de fișiere.