PageSourceSearch

https://ionicframework.com/docs/assets/js/3466006b.76b09306.js

js ionicframework.com collected 2026-09-24 15:16:16 UTC 22,332 bytes, 2 lines download raw bytes

1"use strict";(self.webpackChunkionic_docs=self.webpackChunkionic_docs||[]).push([["15127"],{44563(e,n,t){t.r(n),t.d(n,{metadata:()=>i,default:()=>p,frontMatter:()=>l,contentTitle:()=>c,toc:()=>u,assets:()=>d});var i=JSON.parse('{"id":"techniques/security","title":"Security","description":"Security for Angular, React, and Vue Apps - Ionic Framework","source":"@site/docs/techniques/security.mdx","sourceDirName":"techniques","slug":"/techniques/security","permalink":"/docs/techniques/security","draft":false,"unlisted":false,"editUrl":"https://github.com/ionic-team/ionic-docs/edit/main/docs/techniques/security.mdx","tags":[],"version":"current","frontMatter":{"title":"Security"},"sidebar":"docs","previous":{"title":"Progressive Web App (PWA)","permalink":"/docs/deployment/progressive-web-app"},"next":{"title":"Debugging","permalink":"/docs/troubleshooting/debugging"}}'),s=t(74848),a=t(28453),o=t(50773),r=t(57250);let l={title:"Security"},c,d={},u=[{value:"Sanitizing User Input",id:"sanitizing-user-input",level:2},{value:"Angular",id:"angular",level:3},{value:"React",id:"react",level:3},{value:"Vue",id:"vue",level:3},{value:"Enabling Custom HTML Parsing via <code>innerHTML</code>",id:"enabling-custom-html-parsing-via-innerhtml",level:2},{value:"Ejecting from the built-in sanitizer",id:"ejecting-from-the-built-in-sanitizer",level:2},{value:"Disabling the sanitizer via config",id:"disabling-the-sanitizer-via-config",level:3},{value:"Bypassing the sanitizer on a case-by-case basis",id:"bypassing-the-sanitizer-on-a-case-by-case-basis",level:3},{value:"Usage",id:"usage",level:4},{value:"Content Security Policies (CSP)",id:"content-security-policies-csp",level:2},{value:"Enabling CSPs",id:"enabling-csps",level:3},{value:"Ionic and CSP",id:"ionic-and-csp",level:3},{value:"Angular",id:"angular-1",level:3}];function h(e){let n={a:"a",admonition:"admonition",code:"code",h2:"h2",h3:"h3",h4:"h4",li:"li",ol:"ol",p:"p",pre:"pre",...(0,a.R)(),...e.components},{Head:t}=n;return t||function(e,n){throw Error("Expected "+(n?"component":"object")+" `"+e+"` to be defined: you likely forgot to import, pass, or provide it.")}("Head",!0),(0,s.jsxs)(s.Fragment,{children:[(0,s.jsxs)(t,{children:[(0,s.jsx)("title",{children:"Security for Angular, React, and Vue Apps - Ionic Framework"}),(0,s.jsx)("meta",{name:"description",content:"View Ionic's security info for sanitizing user input, ejecting from the built-in sanitizer, and more. Learn about app security using Angular, React, and Vue."})]}),"\n","\n",(0,s.jsx)(n.h2,{id:"sanitizing-user-input",children:"Sanitizing User Input"}),"\n",(0,s.jsxs)(n.p,{children:["For components such as ",(0,s.jsx)(n.code,{children:"ion-alert"})," developers can allow for custom or user-provided content. This content can be plain text or HTML and should be considered untrusted. As with any untrusted input, it is important to sanitize it before doing anything else with it. In particular, using things like ",(0,s.jsx)(n.code,{children:"innerHTML"})," without sanitization provides an attack vector for bad actors to input malicious content and potentially launch a ",(0,s.jsx)(n.a,{href:"https://en.wikipedia.org/wiki/Cross-site_scripting",children:"Cross Site Scripting attack (XSS)"}),"."]}),"\n",(0,s.jsx)(n.p,{children:"Ionic comes built in with a basic sanitization implementation for the components it provides. However, it is not a comprehensive solution. It is up to the developer to make sure all data that is passed is sanitized. Different frameworks have different solutions for sanitizing user input, so developers should familiarize themselves with what their specific framework offers."}),"\n",(0,s.jsxs)(n.p,{children:["For developers who are not using a framework, or for developers whose framework does not provide the sanitization methods they need, we recommend using ",(0,s.jsx)(n.a,{href:"https://www.npmjs.com/package/sanitize-html",children:"sanitize-html"}),". This package provides a simple HTML sanitizer that allows the developer to specify the exact tags and attributes that they want to allow in their application."]}),"\n",(0,s.jsx)(n.h3,{id:"angular",children:"Angular"}),"\n",(0,s.jsxs)(n.p,{children:["Angular comes built in with the ",(0,s.jsx)(n.code,{children:"DomSanitizer"})," class. This helps prevent XSS issues by ensuring that values are safe to be used in the DOM. By default, Angular will mark any values it deems unsafe. For example, the following link would be marked as unsafe by Angular because it would attempt to execute some JavaScript."]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-tsx",children:'public myUrl: string = \'javascript:alert("oh no!")\';\n\n...\n\n<a [href]="myUrl">Click Me!</a>\n'})}),"\n",(0,s.jsxs)(n.p,{children:["To learn more about the built-in protections that Angular provides, refer to the ",(0,s.jsx)(n.a,{href:"https://angular.io/guide/security",children:"Angular Security Guide"}),"."]}),"\n",(0,s.jsx)(n.h3,{id:"react",children:"React"}),"\n",(0,s.jsxs)(n.p,{children:["React DOM escapes values embedded in JSX before rendering them by convert
1ing them to strings. For example, the following would be safe as ",(0,s.jsx)(n.code,{children:"name"})," is converted to a string before being rendered:"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-jsx",children:"const name = values.name;\nconst element = <h1>Hello, {name}!</h1>;\n"})}),"\n",(0,s.jsxs)(n.p,{children:["However, this does not stop someone from injecting JavaScript into places such as the ",(0,s.jsx)(n.code,{children:"href"})," attribute of an anchor element. The following is unsafe and can potentially allow an XSS attack to occur:"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-jsx",children:"const userInput = 'javascript:alert(\"Oh no!\")';\nconst element = <a href={userInput}>Click Me!</a>;\n"})}),"\n",(0,s.jsxs)(n.p,{children:["If the developer needs to achieve more comprehensive sanitization, they can use the ",(0,s.jsx)(n.a,{href:"https://www.npmjs.com/package/sanitize-html",children:"sanitize-html"})," package."]}),"\n",(0,s.jsx)(n.h3,{id:"vue",children:"Vue"}),"\n",(0,s.jsxs)(n.p,{children:["Vue does not provide any type of sanitizing methods built in. It is recommended that developers use a package such as ",(0,s.jsx)(n.a,{href:"https://www.npmjs.com/package/sanitize-html",children:"sanitize-html"}),"."]}),"\n",(0,s.jsxs)(n.p,{children:["To learn more about the security recommendations for binding to directives such as ",(0,s.jsx)(n.code,{children:"v-html"}),", refer to the ",(0,s.jsx)(n.a,{href:"https://vuejs.org/v2/guide/syntax.html#Raw-HTML",children:"Vue Syntax Guide"}),"."]}),"\n",(0,s.jsxs)(n.h2,{id:"enabling-custom-html-parsing-via-innerhtml",children:["Enabling Custom HTML Parsing via ",(0,s.jsx)(n.code,{children:"innerHTML"})]}),"\n",(0,s.jsxs)(n.p,{children:[(0,s.jsx)(n.code,{children:"ion-alert"}),", ",(0,s.jsx)(n.code,{children:"ion-infinite-scroll-content"}),", ",(0,s.jsx)(n.code,{children:"ion-loading"}),", ",(0,s.jsx)(n.code,{children:"ion-refresher-content"}),", ",(0,s.jsx)(n.code,{children:"ion-select-option"}),", and ",(0,s.jsx)(n.code,{children:"ion-toast"})," can accept custom HTML as strings for certain properties. These strings are added to the DOM using ",(0,s.jsx)(n.code,{children:"innerHTML"})," and must be properly sanitized by the developer. This behavior is disabled by default which means values passed to the affected components will always be interpreted as plaintext. Developers can enable this custom HTML behavior by setting ",(0,s.jsx)(n.code,{children:"innerHTMLTemplatesEnabled: true"})," in the ",(0,s.jsx)(n.a,{href:"/docs/developing/config#ionicconfig",children:"IonicConfig"}),"."]}),"\n",(0,s.jsx)(n.h2,{id:"ejecting-from-the-built-in-sanitizer",children:"Ejecting from the built-in sanitizer"}),"\n",(0,s.jsxs)(n.p,{children:["For developers who wish to add complex HTML to components such as ",(0,s.jsx)(n.code,{children:"ion-toast"}),", they will need to eject from the sanitizer that is built into Ionic Framework. Developers can either disable the sanitizer across their entire app or bypass it on a case-by-case basis."]}),"\n",(0,s.jsx)(n.admonition,{type:"note",children:(0,s.jsxs)(n.p,{children:["Bypassing sanitization functionality can make your application vulnerable to ",(0,s.jsx)(n.a,{href:"https://en.wikipedia.org/wiki/Cross-site_scripting",children:"XSS attacks"}),". Please exercise extreme caution when disabling the sanitizer."]})}),"\n",(0,s.jsx)(n.h3,{id:"disabling-the-sanitizer-via-config",children:"Disabling the sanitizer via config"}),"\n",(0,s.jsxs)(n.p,{children:["Ionic Framework provides an application config option called ",(0,s.jsx)(n.code,{children:"sanitizerEnabled"})," that is set to ",(0,s.jsx)(n.code,{children:"true"})," by default. Set this value to ",(0,s.jsx)(n.code,{children:"false"})," to globally disable Ionic Framework's built in sanitizer. Please note that this does not disable any sanitizing functionality provided by other frameworks such as Angular."]}),"\n",(0,s.jsx)(n.h3,{id:"bypassing-the-sanitizer-on-a-case-by-case-basis",children:"Bypassing the sanitizer on a case-by-case basis"}),"\n",(0,s.jsxs)(n.p,{children:["Developers can also choose to eject from the sanitizer in certain scenarios. Ionic Framework provides the ",(0,s.jsx)(n.code,{children:"IonicSafeString"})," class that allows developers to do just that."]}),"\n",(0,s.jsxs)(n.admonition,{type:"note",children:[(0,s.jsxs)(n.p,{children:["In order to bypass the sanitizer and use unsanitized custom HTML in the relevant Ionic components, ",(0,s.jsx)(n.code,{children:"innerHTMLTemplatesEnabled"})," must be set to ",(0,s.jsx)(n.code,{children:"true"})," in the Ionic config."]}),(0,s.jsxs)(n.p,{children:[(0,s.jsx)(n.code,{children:"IonicSafeString"})," should not be used if ",(0,s.jsx)(n.code,{children:"innerHTMLTemplatesEnabled"})," is set to ",(0,s.jsx)(n.code,{children:"false"}),"."]}),(0,s.jsxs)(n.p,{children:["Refer to ",(0,s.jsx)(n.a,{href:"#enabling-custom-html-parsing-via-innerhtml",children:"Enabling Custom HTML Parsing"})," for more information."]})]}),"\n",(0,s.jsx)(n.h4,{id:"usage",children:"Usage"}),"\n",(0,s.jsxs)(o.A,{groupId:"framework",defaultValue:"angular",values:[{value:"angular",label:"Angular"},{value:"angular-standalone",label:"Angular (Standalone)"},{value:"javascript",label:"JavaScript"},{value:"react",label:"React"}],children:[(0,s.jsx)(r.A,{value:"angular",children:(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-tsx",children:"import { IonicSafeString, ToastController } from '@ionic/angular/lazy';\n\n...\n\nconstructor(private toastController: ToastController) {}\n\nasync presentToast() {\n  const toast = await this.toastController.create({\n      message: new IonicSafeString('<ion-button>Hello!</ion-button>'),\n      duration: 2000\n  });\n  toast.present();\n}\n\n"})})}),(0,s.jsx)(r.A,{value:"angular-standalone",children:(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-tsx",children:"import { IonicSafeString, ToastController } from '@ionic/angular';\n\n...\n\nconstructor(private toastController: ToastController) {}\n\nasync presentToast() {\n  const toast = await this.toastController.create({\n      message: new IonicSafeString('<ion-button>Hello!</ion-button>'),\n      duration: 2000\n  });\n  toast.present();\n}\n\n"})})}),(0,s.jsx)(r.A,{value:"javascript",children:(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-javascript",children:"import { IonicSafeString } from '@ionic/core';\n\n...\n\nconst async presentToast = () => {\n  const toast = document.createElement('ion-toast');\n  toast.message = new IonicSafeString('<ion-button>Hello!</ion-button>');\n  toast.duration = 2000;\n\n  document.body.appendChild(toast);\n  return toast.present();\n}\n\n"})})}),(0,s.jsx)(r.A,{value:"react",children:(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-tsx",children:"import React, { useState } from 'react';\nimport { Animation, IonButton, IonContent, IonicSafeString, IonToast } from '@ionic/react';
1\n\nexport const ToastExample: React.FC = () => {\n  const [showToast, setShowToast] = useState(false);\n\n  return (\n    <IonContent>\n      <IonButton onClick={() => setShowToast(true)} expand=\"block\">Show Toast</IonButton>\n      <IonToast\n        isOpen={showToast}\n        onDidDismiss={() => setShowToast(false)}\n        message={new IonicSafeString('<ion-button>Hello!</ion-button>')}\n        duration={2000}\n      />\n    </IonContent>\n  )\n};\n"})})})]}),"\n",(0,s.jsx)(n.h2,{id:"content-security-policies-csp",children:"Content Security Policies (CSP)"}),"\n",(0,s.jsxs)(n.p,{children:["A ",(0,s.jsx)(n.a,{href:"https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP",children:"Content Security Policy (CSP)"})," is a security mechanism that helps protect web applications against certain types of attacks, such as cross-site scripting (XSS) and data injection. It is implemented through an HTTP header that instructs the browser on which sources of content, such as scripts, stylesheets, and images, are allowed to be loaded and executed on a web page."]}),"\n",(0,s.jsx)(n.p,{children:"The main purpose of a CSP is to mitigate the risks associated with code injection attacks. By defining a policy, web developers can specify from which domains or sources the browser should allow the loading and execution of various types of content. This effectively limits the potential damage that can be caused by malicious scripts or unauthorized content."}),"\n",(0,s.jsx)(n.h3,{id:"enabling-csps",children:"Enabling CSPs"}),"\n",(0,s.jsx)(n.p,{children:"Developers can assign a CSP to their application by setting a meta tag with the policy details and the expected nonce value on script and style tags."}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-html",children:"<meta\n  http-equiv=\"Content-Security-Policy\"\n  content=\"default-src 'self'; script-src 'self' 'nonce-randomNonceGoesHere'; style-src 'self' 'nonce-randomNonceGoesHere';\"\n/>\n"})}),"\n",(0,s.jsx)(n.h3,{id:"ionic-and-csp",children:"Ionic and CSP"}),"\n",(0,s.jsx)(n.p,{children:"Ionic Framework provides a function to help developers set the nonce value used when constructing the web component stylesheets. This function should be called before any Ionic components are loaded. This is required to pass the nonce value to the web components so that they can be used in a CSP environment."}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-ts",children:"import { setNonce } from '@ionic/core/loader';\n\nsetNonce('randomNonceGoesHere');\n"})}),"\n",(0,s.jsx)(n.admonition,{type:"tip",children:(0,s.jsxs)(n.p,{children:["In Angular this can be called in the ",(0,s.jsx)(n.code,{children:"main.ts"})," file, before the application is bootstrapped."]})}),"\n",(0,s.jsxs)(n.p,{children:["For more information on how to use CSPs with Stencil web components, refer to the ",(0,s.jsx)(n.a,{href:"https://stenciljs.com/docs/csp-nonce",children:"Stencil documentation"}),"."]}),"\n",(0,s.jsx)(n.h3,{id:"angular-1",children:"Angular"}),"\n",(0,s.jsx)(n.p,{children:"Starting in Angular 16, Angular provides two options for setting the nonce value."}),"\n",(0,s.jsxs)(n.ol,{children:["\n",(0,s.jsxs)(n.li,{children:["Set the ",(0,s.jsx)(n.code,{children:"ngCspNonce"})," attribute on the root application element as ",(0,s.jsx)(n.code,{children:'<app ngCspNonce="randomNonceGoesHere"></app>'}),". Use this approach if you have access to server-side templating that can add the nonce both to the header and the ",(0,s.jsx)(n.code,{children:"index.html"})," when constructing the response."]}),"\n",(0,s.jsxs)(n.li,{children:["Provide the nonce using the ",(0,s.jsx)(n.a,{href:"https://angular.io/api/core/CSP_NONCE",children:(0,s.jsx)(n.code,{children:"CSP_NONCE"})})," injection token. Use this approach if you have access to the nonce at runtime and you want to be able to cache the ",(0,s.jsx)(n.code,{children:"index.html"}),"."]}),"\n"]}),"\n",(0,s.jsx)(n.admonition,{type:"tip",children:(0,s.jsxs)(n.p,{children:["If providing the ",(0,s.jsx)(n.code,{children:"CSP_NONCE"})," injection token, set the provider in your ",(0,s.jsx)(n.code,{children:"AppModule"})," for module projects or within the ",(0,s.jsx)(n.code,{children:"bootstrapApplication"})," for standalone projects."]})}),"\n",(0,s.jsxs)(n.p,{children:["For more information on how to use CSPs with Angular, refer to the ",(0,s.jsx)(n.a,{href:"https://angular.io/guide/security#content-security-policy",children:"Angular documentation"}),"."]})]})}function p(e={}){let{wrapper:n}={...(0,a.R)(),...e.components};return n?(0,s.jsx)(n,{...e,children:(0,s.jsx)(h,{...e})}):h(e)}},57250(e,n,t){t.d(n,{A:()=>r});var i=t(74848);t(96540);var s=t(34164),a=t(57663);function o({children:e,className:n,hidden:t}){return(0,i.jsx)("div",{role:"tabpanel",className:(0,s.A)("tabItem_Ymn6",n),hidden:t,children:e})}function r({children:e,className:n,value:t}){let{selectedValue:s,lazy:l}=(0,a.uc)(),c=t===s;return!c&&l?null:(0,i.jsx)(o,{className:n,hidden:!c,children:e})}},50773(e,n,t){t.d(n,{A:()=>h});var i=t(74848);t(96540);var s=t(34164),a=t(88287),o=t(57663),r=t(28584),l=t(19863);function c({className:e}){let{selectedValue:n,selectValue:t,tabValues:a,block:l}=(0,o.uc)(),d=[],{blockElementScrollPositionUntilNextRender:u}
1=(0,r.a_)(),h=e=>{let i=e.currentTarget,s=a[d.indexOf(i)].value;s!==n&&(u(i),t(s))},p=e=>{let n=null;switch(e.key){case"Enter":h(e);break;case"ArrowRight":{let t=d.indexOf(e.currentTarget)+1;n=d[t]??d[0];break}case"ArrowLeft":{let t=d.indexOf(e.currentTarget)-1;n=d[t]??d[d.length-1]}}n?.focus()};return(0,i.jsx)("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,s.A)("tabs",{"tabs--block":l},e),children:a.map(({value:e,label:t,attributes:a})=>(0,i.jsx)("li",{role:"tab",tabIndex:n===e?0:-1,"aria-selected":n===e,ref:e=>{d.push(e)},onKeyDown:p,onClick:h,...a,className:(0,s.A)("tabs__item","tabItem_LNqP",a?.className,{"tabs__item--active":n===e}),children:t??e},e))})}function d({children:e}){return(0,i.jsx)("div",{className:"margin-top--md",children:e})}function u({className:e,children:n}){return(0,i.jsxs)("div",{className:(0,s.A)(a.G.tabs.container,"tabs-container","tabList__CuJ"),children:[(0,i.jsx)(c,{className:e}),(0,i.jsx)(d,{children:n})]})}function h(e){let n=(0,l.A)(),t=(0,o.OC)(e);return(0,i.jsx)(o.O_,{value:t,children:(0,i.jsx)(u,{className:e.className,children:(0,o.vT)(e.children)})},String(n))}},80618(e,n,t){function i(e,n=(e,n)=>e===n){return e.filter((t,i)=>e.findIndex(e=>n(e,t))!==i)}function s(e){return Array.from(new Set(e))}function a(e,n){let t={},i=0;for(let s of e){let e=n(s,i);t[e]??=[],t[e].push(s),i+=1}return t}t.d(n,{$z:()=>a,XI:()=>i,sb:()=>s})},57663(e,n,t){t.d(n,{OC:()=>h,O_:()=>f,uc:()=>m,vT:()=>d});var i=t(74848),s=t(96540),a=t(56347),o=t(99989),r=t(96629),l=t(80618),c=t(41367);function d(e){return s.Children.toArray(e).filter(e=>"\n"!==e)}function u({value:e,tabValues:n}){return n.some(n=>n.value===e)}function h(e){let n,{defaultValue:t,queryString:i=!1,groupId:d}=e,h=function(e){let{values:n,children:t}=e;return(0,s.useMemo)(()=>{let e=n??s.Children.toArray(t).flatMap(e=>{if(!e)return[];if((0,s.isValidElement)(e)&&function(e){let{props:n}=e;return!!n&&"object"==typeof n&&"value"in n}(e))return[e];let n="string"==typeof e.type?e.type:e.type.name;throw Error(`Docusaurus error: Bad <Tabs> child <${n}>: all children of the <Tabs> component should be <TabItem>, and every <TabItem> should have a unique "value" prop.
2If you do not want to pass on a "value" prop to the direct children of <Tabs>, you can also pass an explicit <Tabs values={...}> prop.`)}).map(({props:{value:e,label:n,attributes:t,default:i}})=>({value:e,label:n,attributes:t,default:i})),i=(0,l.XI)(e,(e,n)=>e.value===n.value);if(i.length>0)throw Error(`Docusaurus error: Duplicate values "${i.map(e=>`'${e.value}'`).join(", ")}" found in <Tabs>. Every value needs to be unique.`);return e},[n,t])}(e),[p,m]=(0,s.useState)(()=>(function({defaultValue:e,tabValues:n}){if(0===n.length)throw Error("Docusaurus error: the <Tabs> component requires at least one <TabItem> children component");if(e){if(!u({value:e,tabValues:n}))throw Error(`Docusaurus error: The <Tabs> has a defaultValue "${e}" but none of its children has the corresponding value. Available values are: ${n.map(e=>e.value).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return e}let t=n.find(e=>e.default)??n[0];if(!t)throw Error("Unexpected error: 0 tabValues");return t.value})({defaultValue:t,tabValues:h})),[f,g]=function({queryString:e=!1,groupId:n}){let t=(0,a.W6)(),i=function({queryString:e=!1,groupId:n}){if("string"==typeof e)return e;if(!1===e)return null;if(!0===e&&!n)throw Error('Docusaurus error: The <Tabs> component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return n??null}({queryString:e,groupId:n});return[(0,r.aZ)(i),(0,s.useCallback)(e=>{if(!i)return;let n=new URLSearchParams(t.location.search);n.set(i,e),t.replace({...t.location,search:n.toString()})},[i,t])]}({queryString:i,groupId:d}),[b,v]=function({groupId:e}){let n=e?`docusaurus.tab.${e}`:null,[t,i]=(0,c.Dv)(n);return[t,(0,s.useCallback)(e=>{n&&i.set(e)},[n,i])]}({groupId:d}),x=u({value:n=f??b,tabValues:h})?n:null;return(0,o.A)(()=>{x&&m(x)},[x]),{selectedValue:p,selectValue:(0,s.useCallback)(e=>{if(!u({value:e,tabValues:h}))throw Error(`Can't select invalid tab value=${e}`);m(e),g(e),v(e)},[g,v,h]),tabValues:h,lazy:e.lazy??!1,block:e.block??!1}}let p=(0,s.createContext)(null);function m(){let e=s.useContext(p);if(!e)throw Error("useTabsContext() must be used within a Tabs component");return e}function f(e){return(0,i.jsx)(p.Provider,{value:e.value,children:e.children})}},28453(e,n,t){t.d(n,{R:()=>o,x:()=>r});var i=t(96540);let s={},a=i.createContext(s);function o(e){let n=i.useContext(a);return i.useMemo(function(){return"function"==typeof e?e(n):{...n,...e}},[n,e])}function r(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(s):e.components||s:o(e.components),i.createElement(a.Provider,{value:n},e.children)}}}]);

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.