diff --git a/.env.example b/.env.example index 49db91b8..5ad19038 100644 --- a/.env.example +++ b/.env.example @@ -3,7 +3,7 @@ APP_ENV=production APP_KEY= APP_DEBUG=false APP_URL=http://localhost -APP_VERSION=7.0.10 +APP_VERSION=7.0.11 # 리버스 프록시(AWS ALB, CloudFront, Cloudflare, nginx, ngrok) 뒤에서 HTTPS 를 인식하려면 # 신뢰할 프록시를 지정합니다. 미설정 시 아무 프록시도 신뢰하지 않습니다(기존 동작). @@ -143,6 +143,16 @@ G7_UPDATE_PENDING_PATH= # 상태·수동 복구: php artisan ext-static:status / 관리자 > 환경설정 > 일반 「초기 화면 정적 파일」 # G7_STATIC_CACHE=true +# SEO 봇 캐시 상한 (config/core.php seo_cache_limits). 봇 판정은 User-Agent 뿐이라 위장이 +# 가능하고, 캐시 키에 쿼리가 들어가므로 값만 바꾼 반복 요청이 매번 렌더·저장을 유발한다. +# 렌더 예산을 넘긴 요청은 오류가 아니라 일반 SPA 응답(X-SEO-Cache: BYPASS)을 받는다. +# G7_SEO_CACHE_MAX_QUERY_PARAMS=10 +# G7_SEO_CACHE_MAX_QUERY_LENGTH=512 +# G7_SEO_CACHE_MAX_VARIANTS_PER_PATH=50 +# G7_SEO_CACHE_MAX_ENTRIES=20000 +# G7_SEO_RENDER_MISSES_PER_MINUTE=60 +# G7_SEO_STATS_RECORDS_PER_MINUTE=300 + # 코어 업데이트 경로 목록 (선택). 재정의 시 **전체 목록을 다시 적는다** — 부분값은 기본 항목을 # 통째로 대체한다 (예: excludes 에서 build/ext 가 빠지면 --prune 이 정적 게시본을 지운다). # 기본값은 config/app.php 의 update 절과 같다. diff --git a/.env.testing.example b/.env.testing.example index dea1ab52..b5706dca 100644 --- a/.env.testing.example +++ b/.env.testing.example @@ -3,7 +3,7 @@ APP_ENV=testing APP_KEY= APP_DEBUG=false APP_URL=http://localhost -APP_VERSION=7.0.10 +APP_VERSION=7.0.11 # 리버스 프록시(AWS ALB, CloudFront, Cloudflare, nginx, ngrok) 뒤에서 HTTPS 를 인식하려면 # 신뢰할 프록시를 지정합니다. 미설정 시 아무 프록시도 신뢰하지 않습니다(기존 동작). diff --git a/AGENTS.md b/AGENTS.md index 8d6901f5..2cf633b7 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -42,7 +42,7 @@ | [service-provider.md](docs/backend/service-provider.md) | 서비스 프로바이더 안전성 | DB 접근 전 .env 파일 존재 확인 필수 | | [service-repository.md](docs/backend/service-repository.md) | Service-Repository 패턴 | RepositoryInterface 주입 필수 (구체 클래스 직접 주입 금지) | | [settings-multilingual-enrichment.md](docs/backend/settings-multilingual-enrichment.md) | Settings 카탈로그 다국어 자동 보강 | settings JSON 의 다국어 카탈로그 라벨(_cached_name 등)은 카탈로그 빌드 시점에 보강 | -| [static-asset-publishing.md](docs/backend/static-asset-publishing.md) | 부트스트랩 리소스 정적 게시 (Static Asset Publishing) | 게시물: public/build/ext/{cache_version}/ — 수명주기 이벤트와 운영자 cu... | +| [static-asset-publishing.md](docs/backend/static-asset-publishing.md) | 부트스트랩 리소스 정적 게시 (Static Asset Publishing) | 게시물: public/build/ext/{cache_version}/ — 수명주기 이벤트·자산 URL ... | | [translatable-seeders.md](docs/backend/translatable-seeders.md) | 다국어 시더 인터페이스 (Translatable Seeders) | 다국어 JSON 컬럼(name 등)을 시드하는 확장 entity 시더는 TranslatableSeede... | | [user-overrides.md](docs/backend/user-overrides.md) | 사용자 수정 보존 (HasUserOverrides Trait) | 모델에 `use HasUserOverrides;` + `protected array $trackable... | | [validation.md](docs/backend/validation.md) | 검증 (Validation) | 필수: FormRequest에서 검증 (Service에 검증 로직 배치 금지) | @@ -149,7 +149,7 @@ | 대상 | 진입점 | 문서/엔드포인트 | |------|--------|----------------| -| 코어 | [docs/backend/api/README.md](docs/backend/api/README.md) | 36 / 325 | +| 코어 | [docs/backend/api/README.md](docs/backend/api/README.md) | 36 / 328 | ### 확장 API 레퍼런스 (14개 확장, 자동 스캔) @@ -198,7 +198,7 @@ | `sirsoft-verification_nhnkcp` | 플러그인 | [AGENTS.md](plugins/_bundled/sirsoft-verification_nhnkcp/AGENTS.md) | [docs/](plugins/_bundled/sirsoft-verification_nhnkcp/docs/README.md) | 훅 0 · 라우트 2 · 모델 2 · 레이아웃 1 | | `gnuboard7-hello_admin_template` | 템플릿 | [AGENTS.md](templates/_bundled/gnuboard7-hello_admin_template/AGENTS.md) | [docs/](templates/_bundled/gnuboard7-hello_admin_template/docs/README.md) | 훅 0 · 라우트 1 · 모델 0 · 레이아웃 8 | | `gnuboard7-hello_user_template` | 템플릿 | [AGENTS.md](templates/_bundled/gnuboard7-hello_user_template/AGENTS.md) | [docs/](templates/_bundled/gnuboard7-hello_user_template/docs/README.md) | 훅 0 · 라우트 1 · 모델 0 · 레이아웃 8 | -| `sirsoft-admin_basic` | 템플릿 | [AGENTS.md](templates/_bundled/sirsoft-admin_basic/AGENTS.md) | [docs/](templates/_bundled/sirsoft-admin_basic/docs/README.md) | 훅 0 · 라우트 29 · 모델 0 · 레이아웃 145 | +| `sirsoft-admin_basic` | 템플릿 | [AGENTS.md](templates/_bundled/sirsoft-admin_basic/AGENTS.md) | [docs/](templates/_bundled/sirsoft-admin_basic/docs/README.md) | 훅 0 · 라우트 29 · 모델 0 · 레이아웃 146 | | `sirsoft-basic` | 템플릿 | [AGENTS.md](templates/_bundled/sirsoft-basic/AGENTS.md) | [docs/](templates/_bundled/sirsoft-basic/docs/README.md) | 훅 0 · 라우트 40 · 모델 0 · 레이아웃 166 | @@ -278,6 +278,57 @@ | `"item"`, `"index"` | `"item_var"`, `"index_var"` | | iteration 내 if 순서 무시 | if가 iteration보다 먼저 평가됨 | +### 위젯 값 형태 ↔ apply 경로 + +편집기 위젯 중 **값이 스칼라가 아닌 것**(`image` → `{url,size,repeat,position}` 객체)을 값 슬롯이 하나뿐인 apply 경로에 연결하면, 객체가 그대로 `props[key]` 에 저장되어 소비 컴포넌트가 `[object Object]` 를 URL 로 받는다. 이 결함은 예외도 콘솔 오류도 서버 로그도 남기지 않는다 — 깨진 이미지 요청은 SPA catch-all 때문에 404 조차 아니라 **200(HTML)** 이고, 편집기 위젯의 미리보기는 정상이라 조작 중에는 이상이 보이지 않는다. 화면의 엑박이 유일한 증상이다. + +| ❌ 금지 | ✅ 올바른 사용 | +|--------|---------------| +| `image` 위젯을 `classToken`·`cssVar` 에 연결하거나 `apply` 를 생략 | 값이 스칼라로 축약되는 경로만 — `propValue`(맨 url 문자열) 또는 `backgroundImage` 를 포함한 `styleProp` 묶음(4속성 분해) | +| 축약 판정을 **값 형태 sniffing** 으로 게이트 | `widget === 'image'` 게이트 — `isImageValueObject` 는 4키 중 **하나만** 있어도 참이라 `props.tooltip = {position:'left'}` 같은 정당한 객체 prop 을 이미지로 오인해 삭제한다 | +| 축약 분기를 writer 마다 복붙 | 공용 헬퍼 `scalarizeImageValue` 단일 지점 — 이번 결함의 원인이 정확히 "방어가 `applyStyleProp` 안에만 있었다" 이다 | +| 쓰기만 축약하고 읽기는 그대로 | `propValue` 역해석이 저장 문자열을 `{url}` 로 되감는다. **표현식 문자열도 감싼다** — 감싸지 않으면 빈 피커로 보이고 업로드 1클릭에 그 표현식이 소리 없이 소실된다 | +| 저장되지 않는 컨트롤을 `disabled` 로 남김 | 단일 값 슬롯이면 표시모드 버튼을 **컨테이너째 미렌더** — `disabled` 는 *일시적* 비활성의 시각 언어라 "URL 을 넣으면 살아나겠지" 라는 거짓 정보를 준다 | +| 저장되지 않는 `size` 를 미리보기에 반영 | 단일 슬롯 미리보기는 `contain` 고정 — 실제 표시 방식은 소비 컴포넌트의 클래스가 정하므로 편집기가 흉내내면 거짓 미리보기다 | +| 코어 엔진의 느슨한 판정식을 **백필**에 이식 | 백필·런타임 방어는 **엄격 판정식**(키 집합 ⊆ 4키 **AND** `url` 키 존재). 느슨한 판정식은 레이아웃 전수에서 2,219건을 매치하고 그 대부분이 정상 props 다(`{className,name,size}` 674건 · `{name,size}` 552건) — 엄격 판정식의 매치는 0건이었다 | +| 백필 순회 범위를 **노드 키 allowlist** 로 정의 | `props` 키 진입 시 모드 ON / `style` 키 진입 시 OFF 인 **모드 플래그 전역 재귀** — 실측상 `props` 안에 컴포넌트 노드가 1,150건 살아 allowlist 는 원리상 완결 불가다 | +| 두 방어선(런타임 `Img` / 백필)의 판정 강도를 따로 정함 | 완전히 같은 엄격도 — 어긋나면 한쪽만 통과하는 값이 생긴다 | + +> 상세: [editor-spec.md](docs/extension/editor-spec.md) "controls — 재사용 스타일 컨트롤" +> 정적 검사가 두 축을 함께 본다 — editor-spec 선언과 코어 엔진의 축약 분기 실존. 선언 축만 보면 코어 분기가 삭제돼도 통과하는데 결함은 부활한다. 기설치본 보정은 DB 데이터 상태라 정적 검사 대상이 아니며, 업그레이드 스텝의 회귀 테스트가 그 축을 잠근다 + +### 편집기 컨트롤의 데이터 연결 값 보호 + +레이아웃의 prop 자리에는 `{{_global.settings?.general?.site_logo_url}}` 같은 **표현식 문자열**이 저장돼 있을 수 있다. 위젯은 그 값을 해석하지 못해 **빈 컨트롤**로 보이고, 조작하는 순간 그 연결이 사라진다 — 값 하나가 아니라 **환경설정과의 연결**이 끊기고, 원문이 화면 어디에도 남지 않아 되돌릴 수단조차 없다. 예외도 콘솔 오류도 남지 않는다. + +| ❌ 금지 | ✅ 올바른 사용 | +|--------|---------------| +| 바인딩 판정·배지·잠금을 위젯마다 구현 | `ControlRenderer` 의 **공용 게이트 한 곳** — 새 위젯을 등록해도 자동 적용된다 | +| 해제 경로 없이 잠그기만 | 「직접 지정으로 바꾸기」로 **명시적으로만** 연다 | +| 「직접 지정으로 바꾸기」를 편도로 두기 | 「되돌리기」 동반 — 해제 직후엔 취소로, 값을 이미 넣은 뒤엔 원문 복구로 동작한다 | +| 파괴적 조작 표면 중 일부만 잠그기 | 그 위젯의 **전 표면** — 업로드·제거·목록 선택뿐 아니라 **관리 모달 진입**까지. 같은 동작이 두 곳에 렌더되면 하나만 잠근 것은 판단이 아니라 누락이다 | +| 위젯이 자체 처리를 가지면서 공용 게이트도 통과 | 둘 중 하나 — 자체 처리 위젯은 제외 목록에 등재하고, 그 위젯이 **원문 표시·해제·복구 셋을 모두** 제공하는지 확인한다 | +| 자체 분기가 공용 해제 경로를 막음 | 해제된 뒤에는 위젯이 평소대로 편집 가능해야 한다 | +| 해석 못 하는 값을 위젯이 흉내내 표시 | 원문 배지로 대체 — 흉내내면 거짓 컨트롤이다 | + +> 상세: [editor-spec.md](docs/extension/editor-spec.md) "데이터 연결 값 보호" + +### 상속·주입 노드의 편집 표면 + +저장 마스킹(`stripInheritedFromLayoutContent`)은 상속(base)·주입(extension) 출처 노드를 **정상 저장에서도 항상 폐기**한다. 그래서 그 노드를 편집할 수 있게 열어 두면 편집분이 오류도 경고도 없이 사라진다 — 저장은 200 으로 성공하고 `history.clear()` 로 undo 도 불가능하다. + +| ❌ 금지 | ✅ 올바른 사용 | +|--------|---------------| +| 잠금 판정에 편집 모드 예외를 둠 (`editMode !== 'route' && isNodeLocked(...)`) | 출처 잠금이 `data_bound` 보다 **항상 우선** — "route 는 종전 동작을 한 줄도 바꾸지 않는다" 는 계약이 아니라 보수성 선언이었고, 그 보수성이 곧 결함이었다 | +| `data_bound` 의 **의미**를 좁혀 해결 | 의미는 그대로 두고(= 편집 가능, 텍스트만 잠금) **어느 노드가 그렇게 분류되는가**만 좁힌다 — 형제 경로(DnD·오버레이)가 그 계약에 실제로 의존한다 | +| 게이트 조건을 표면마다 복사 | 단일 판정 헬퍼 `isEditableLockKind` / `resolveDndDenial` 만 호출 — 한 곳만 빠져도 같은 소실 결함이 재발한다 | +| ⓘ 메뉴와 드래그 핸들만 막고 끝냄 | 인라인 편집(더블클릭)·복제·키보드 `Delete`·잘라내기까지 전 표면 — 키보드 경로는 ⓘ 메뉴를 거치지 않아 무방비였다 | +| 드래그 거부를 `zone === null` 에 기댄 간접 방어로 | commit 직전 최종 가드 — stale 슬롯이나 유효 zone 이 들어오면 그대로 이동 commit 된다 | +| 거부된 드래그가 `activeDragPath` 를 남김 | 거부 시 즉시 비운다 — 남으면 DragOverlay 가 잡힌 노드를 따라다녀 "옮길 수 있다" 는 거짓 어포던스를 준다 | +| 조상 산출 구현을 파일마다 복제 | `collectAncestors` 단일 출처 — 잠금 판정의 입력이 갈리면 "핸들은 있는데 드래그는 거부" 같은 어긋남이 조용히 생긴다 | + +차단된 노드에는 「🔒 공통 레이아웃 편집」·「🔒 확장 편집」 진입 어포던스가 대신 뜬다. 상속 노드의 「데이터 영역」 라벨은 사라지지만 후자가 행동 가능한 정보이므로 순증이다. + ### 컴포넌트 Props | 금지 | 올바른 사용 | @@ -476,6 +527,24 @@ catch-all shadow 는 보호처럼 보인다는 점이 위험하다. 가려진 > 상세: [validation.md](docs/backend/validation.md), [service-repository.md](docs/backend/service-repository.md), [frontend/security.md](docs/frontend/security.md) +### 서버가 조건에 따라 다른 형태의 200 을 돌려주는 엔드포인트 + +같은 엔드포인트가 설정·상태에 따라 **다른 형태의 2xx** 를 낸다면, 프론트는 형태를 판별한 뒤에 읽어야 한다. 한 형태만 가정하면 다른 형태에서 필드 접근이 그 자리에서 던지고, 그 원문이 오류 박스에 영문으로 노출된다. 서버는 정상 응답했으므로 **서버 로그에는 흔적이 없다** — 깨진 것은 클라이언트뿐이다. + +| ❌ 금지 | ✅ 올바른 사용 | +|--------|---------------| +| 응답 타입을 한 형태로 고정 선언하고 `response.data.user.*` 를 바로 읽기 | 판별 유니온으로 두 형태를 표현 (`LoginResult` = `{status:'authenticated', user}` \| `{status:'two_factor_required', challenge}`) | +| 저장 지점에 형태 가드 없이 `setToken(response.data.token)` | 비어 있지 않은 문자열만 저장 — `localStorage` 는 무엇을 넣든 문자열로 바꾸므로 `undefined` 가 `"undefined"`(truthy)로 남아 이후 모든 요청이 `Bearer undefined` 로 나가 401 이 된다 | +| 대체 형태 분기를 사용자 경로에만 두고 관리자 경로는 그대로 | 관리자 경로가 먼저 500 이 되면 설정을 되돌릴 수단까지 사라진다 — 두 경로 동시 적용 | +| `onSuccess` 후속 액션에 조건 없이 성공 처리를 나열 | 대체 형태에서 실행되면 안 되는 액션마다 `if:"{{!response.대체형태플래그}}"` | +| `onSuccess`·시퀀스 안에서 방금 저장한 상태(`_global.*`/`_local.*`)를 형제 액션의 `if`·값으로 재독 | 그 시점 컨텍스트는 아직 갱신 전이다 — `{{response.*}}` 만 읽는다 (`onSuccess` 결과는 `handleSequence` 의 상태 동기화 대상이 아니다) | +| 서버가 제공하는 기능의 프론트 화면 부재를 "미사용" 으로 간주 | 토글을 켠 사이트에서만 드러나는 미구현이다 — 서버 토글 ↔ 화면 존재를 전수 대조 | + +착수 전 전수조사 축은 **"서버가 대체 형태 2xx 를 내는 엔드포인트 ↔ 프론트 처리 여부"** 다. 그리고 **"서버 토글 ON 시 프론트 화면 존재 여부"** 를 함께 본다 — 2단계 인증은 도입 후 여러 버전 동안 입력 화면이 없었고, 그 토글을 켠 사이트에서만 전원 로그인 불가로 나타났다(공개 #133). + +> 상세: [auth-system.md "2단계 인증 로그인"](docs/frontend/auth-system.md) +> 정적 검사로는 잡히지 않는다 — 응답 변종은 서버 분기의 의미 판정이므로 코드 리뷰에서 확인한다. + ### 제3자 라이브러리는 쓰기 경로를 지정받는다 제3자 라이브러리는 캐시·임시파일 경로를 설정하지 않으면 **자기 설치 폴더**(vendor 안)나 시스템 temp 에 쓴다. 표준 Laravel 배포는 웹서버에 `storage/` 와 `bootstrap/cache` 만 쓰기 권한을 주므로 그 쓰기는 실패하는데, 실패가 예외가 아니라 PHP 경고라 Laravel `HandleExceptions` 가 `ErrorException` 으로 승격시켜 요청이 500 이 된다. 해시당 1회만 기록하는 라이브러리라면 캐시가 영영 생기지 않아 **매 요청이 같은 실패를 반복**한다 — 개발 머신에서는 vendor 가 쓰기 가능해 한 번 성공하고 끝나므로 재현되지 않는다 (공개 #125). @@ -495,6 +564,25 @@ catch-all shadow 는 보호처럼 보인다는 점이 위험하다. 가려진 > 상세: [storage-driver.md](docs/extension/storage-driver.md) "제3자 라이브러리에 절대 경로를 넘길 때", [service-repository.md](docs/backend/service-repository.md) "서비스가 제3자 라이브러리를 붙일 때" +### 공개 자산 직접 URL 은 운영자 선언으로만 열린다 + +저장된 파일의 주소를 직접 URL(CDN)로 내보내면 서버 스트리밍 경로가 통째로 건너뛰어진다. 그 경로에 게이트가 있으면 게이트가 사라지고, 대상 저장소가 실제로는 비공개면 발급된 주소가 403 이 된다. 둘 다 예외도 로그도 남기지 않는다 — 그 이미지들만 조용히 깨지거나, 막아야 할 파일이 조용히 열린다. + +| ❌ 금지 | ✅ 올바른 사용 | +|--------|---------------| +| `filesystems.disks.{disk}.url` 이 설정돼 있다는 이유로 그 디스크를 공개로 간주해 직접 URL 발급 | 운영자가 명시 선언한 `core.storage.public_asset_disk` 와 **행 disk 가 일치할 때만** 직접 URL. `url` 설정 유무는 "URL 을 만들 수 있는가" 이지 "익명 읽기가 되는가" 가 아니다 | +| 권한·게이트(비밀글·소유권·발행 상태·본인인증·다운로드 카운트)가 걸린 첨부 경로를 직접 URL 로 전환 | 게이트가 없는 완전 공개 자산 카테고리에만 배선. 게이트가 있는 경로의 응답 `url` 칸은 그 게이트를 통과하는 서빙 URL 로 채운다 | +| 공개 판정을 `PublicAssetDisk::resolve()` 밖에서 사본으로 재작성 | 판정은 그 단일 지점 경유. `''`·`'none'`·config 에 없는 고아 디스크가 등가 비교만으로 통과하는 것을 막는다 | +| 행 disk 로 `withDisk()` 를 무검증 호출 | 존재 확인 후 폴백 — 공개 자산 디스크가 플러그인 등록 디스크일 수 있고, 그 플러그인 비활성화 시 무인증 공개 서빙 라우트가 **500** 이 된다 | +| 직접 URL 로 전환하면서 "행 삭제 = 접근 차단" 전제를 그대로 둠 | 직접 URL 은 행과 무관하게 파일을 가리킨다 — 회수 수단(파일 삭제)과 그 한계를 문서에 남긴다 | +| 서버가 스스로 발급하는 자산 주소(프록시 서빙 URL)를 절대 URL 로 만듦 | 사이트 상대 경로 — 절대면 저장 규칙(외부 URL 차단)이 자기 주소를 외부로 차단해 image 위젯의 업로드 → 저장이 422 가 되고(배경은 `style` 이라 스캔되지 않아 로고에서만 드러났다), 저장된 레이아웃이 발급 시점 도메인·스킴에 묶인다 | +| 저장 규칙의 "외부" 판정을 스킴 접두 문자열로만 두고 자기 주소를 예외 없이 차단 | 사이트 자기 host(`app.url`)·선언된 공개 자산 디스크 host 는 외부가 아니다 — `SiteAssetHosts` 단일 판정(정규화 후 host 등가 비교). 접두 비교는 `host.evil.com`·`host@evil.com` 을 통과시키고, 요청 `Host` 헤더는 위조 가능해 근거가 아니다 | + +이 결함군의 유일한 증상은 화면이다: 직접 URL 이 403 을 돌려주면 그 이미지들만 깨지고, 게이트가 우회되면 정상 200 이 나간다. 서버 로그에는 어느 쪽도 흔적이 없다. + +> 상세: [storage-driver.md](docs/extension/storage-driver.md) "공개 자산 전용 디스크 분리" +> 이 규칙은 정적 검사로 판정할 수 없다 — "이 경로에 게이트가 있는가"·"행 disk 와 설정이 같은 축인가" 는 의미 판정이므로, 회귀 테스트(특히 비공개 S3 행 + 공개 URL 설정 조합에서 서빙 라우트가 나오는 케이스)가 잠근다 + ### 직접 전송로는 자격증명을 스스로 싣는다 레이아웃의 `globalHeaders` 는 **데이터소스(DataSourceManager)와 `apiCall` 핸들러(ActionDispatcher)** 에만 적용된다. 코어 ApiClient(`G7Core.api.*`)를 직접 부르거나 `fetch` 를 쓰는 경로는 그 배선을 타지 않아 `Authorization` 과 `Accept-Language` 만 실린다. 게이트된 엔드포인트를 그렇게 부르면 서버는 정당한 사용자를 거부하는데, 화면은 버튼·썸네일을 이미 내준 뒤라 **예외도 콘솔 오류도 없이 그 자리만 비는 것**이 유일한 증상이다. @@ -642,7 +730,7 @@ TLS 가 앞단에서 종단되고 앱에는 HTTP 로 전달되는 구성(AWS ALB | 버전 키·서명 키를 기본 TTL 로 `put()` | `PERSISTENT_TTL_SECONDS`(10년) 명시 — `forever()` 는 `CacheInterface` 밖(공개 표면 변경), `put(…, 0)` 은 forget | | 서명 스코프를 렌더 템플릿만으로 나눔 | `{템플릿}@{호스트명}` — 다중 서버 공유 캐시에서 서버 간 mtime 차이로 요청마다 재게시가 왕복한다 | | `config:cache` / `route:cache` / `event:cache` / `optimize` 를 헬퍼 밖에서 `Artisan::call` | `ConfigCacheHelper::rebuild()` / `RouteCacheHelper::rebuild()` (내부가 `withPreservedContainer`) — 이 명령들은 새 Application 을 부팅하며 전역 `Container` 를 일회용 앱으로 바꿔 놓아, 그 뒤 등록되는 `app()->terminating()` 재게시 예약이 종료되지 않는 앱에 걸려 사라진다 | -| 코어 업데이트 흐름에서 현재 프로세스의 버전·update 목록을 `config('app.version')`·`config('app.update.*')` 로 판독 | spawn 자식은 부모가 비우지 않은 이전 버전 config 캐시로 부팅한다 — 버전은 `CoreVersionChecker::getCoreVersion()`(env 우선), update 목록은 캐시 부팅이면 `CoreUpdateService::freshDiskUpdateConfig()`, 부모는 spawn 직전 `ConfigCacheHelper::clear()` | +| 코어 업데이트 흐름에서 현재 프로세스의 버전·update 목록을 `config('app.version')`·`config('app.update.*')` 로 판독 | spawn 자식은 부모가 비우지 않은 이전 버전 config 캐시로 부팅한다 — 버전은 `CoreVersionChecker::getCoreVersion()`(업데이트 트리 안에서만 env 우선), update 목록은 캐시 부팅이면 `CoreUpdateService::freshDiskUpdateConfig()`, 부모는 spawn 직전 `ConfigCacheHelper::clear()` + `PackageManifestCacheHelper::clear()` | 이 결함군은 예외도 로그도 남기지 않는다 — 게시본이 정상 200 으로 옛 내용을 내보내는 것, 또는 매일 전체 재생성이 일어나는 것이 유일한 증상이다. 재게시 누락의 안전망은 관리자 > 환경설정 > 일반 「초기 화면 정적 파일」의 [지금 다시 만들기](`POST /api/admin/settings/static-cache/republish`)이며, 상태 판정은 `ExtensionStaticCacheService::statusReport()` 한 곳이 CLI·API·화면에 공급한다. @@ -664,6 +752,27 @@ TLS 가 앞단에서 종단되고 앱에는 HTTP 로 전달되는 구성(AWS ALB > 상세: [module-settings.md](docs/extension/module-settings.md) "카탈로그 병합 설정의 공개 응답" +### vendor 를 교체한 뒤 새 PHP 프로세스를 띄우기 전에는 패키지 매니페스트를 비운다 + +Laravel 은 `bootstrap/cache/packages.php` 가 있으면 stale 여부를 검사하지 않고 그대로 읽어 provider 를 `new` 한다. 그래서 vendor 를 바꾼 뒤 그 파일을 남겨 두면 다음에 부팅하는 프로세스가 새 vendor 에 없는 클래스를 찾다 부팅 단계에서 죽고, 예외는 부팅 전이라 앱 로그에 남지 않는다. + +| 금지 | 올바른 사용 | +|------|------------| +| vendor 교체 뒤 `proc_open`·`config:cache`·`route:cache` 등 새 부팅을 `bootstrap/cache/{packages,services}.php` 정리 없이 실행 | 부팅 직전 `PackageManifestCacheHelper::clear()`(재생성까지 필요하면 `rebuild()`) — `ConfigCacheHelper::clear()` 와 짝으로 | +| 정리 로직을 호출부마다 `@unlink` 로 복제 | 헬퍼 단일 지점 — `clearAllCaches()` 도 같은 헬퍼를 쓴다 | +| 자식 프로세스 보호를 부모 코드에만 두기 | 부모는 이미 배포된 옛 코드일 수 있다 — 새 버전의 `bootstrap/app.php` 가 `G7_UPDATE_IN_PROGRESS` 를 보고 스스로 비운다(App\ 클래스 미참조·실패 무시) | +| 코어 버전 판정에서 프로세스 env `APP_VERSION` 을 무조건 우선 | env 우선은 `CoreUpdateContext::isInProgress()` 인 프로세스 트리 안에서만 — 업데이트 전에 뜬 `artisan serve`·큐 워커는 옛 값을 물고 있다 | +| 업데이트 커맨드 argv 판정을 SAPI 게이트 없이 두기 | argv 는 명령줄 SAPI(`cli`·`phpdbg`)에서만 읽는다 — CGI/FPM 은 `register_argc_argv=On` 이면 `$_SERVER['argv']` 를 쿼리스트링에서 채워(`?x+core:update`) 비인증 웹 요청이 업데이트 트리로 판정되고, 자가 치유가 요청마다 매니페스트를 지운다. env 플래그 채널은 웹에서 주입할 수 없으므로 그대로 둔다 | +| 매니페스트 삭제 실패를 `@unlink` 로 삼키기 | `clear()` 가 지우지 못한 경로를 돌려주고 spawn 직전 호출부가 업그레이드 로그에 경고로 남긴다 — 권한·소유권 불일치면 자식도 같은 이유로 실패해 증상은 제보와 같고, 이 경고가 원인을 가리키는 유일한 흔적이다 | +| 업데이트 트리 판정을 지점마다 다시 작성 | `App\Support\CoreUpdateContext` 단일 SSoT — `CoreServiceProvider::isCoreUpdateInProgress()` 도 위임이다. `bootstrap/app.php` 의 복제본은 부팅 전이라 불가피한 예외이며 주석으로 상호 참조한다 | +| 자동 비활성화 로그의 `core_version` 을 `config('app.version')` 으로 적기 | 판정과 같은 `CoreVersionChecker::getCoreVersion()` — 로그와 판정 근거가 갈리면 운영자가 원인을 특정할 수 없다 | +| "이미 있으니 건너뛴다" 분기(인스톨러 vendor 재사용)가 산출물의 출처를 보지 않음 | `installed.json` 의 `dev`/`dev-package-names` 로 출처를 보고 경고 카드·로그를 남기며, 재사용 경로에서도 컴파일 캐시를 정리한다 | +| 코어 업데이트를 마치고 상주 워커에 신호를 보내지 않음 | Step 11·핸드오프·단독 재개 사후 단계에서 `signalQueueRestart()` | + +이 결함군은 예외도 로그도 남기지 않는다 — 자식 프로세스가 부팅 단계에서 죽어 부모가 핸드오프로 멈추는 것, 또는 업데이트 직후 확장이 `incompatible_core` 로 꺼지는 것이 유일한 증상이다. 후자는 관리자 템플릿이 대상이면 복구 UI 자체에 도달할 수 없어 자가 회복 경로가 없다. + +> 상세: [core-update-system.md](docs/backend/core-update-system.md) "spawn 전 캐시 정리 계약(3계층)" · [extension-update-system.md](docs/extension/extension-update-system.md) "판정의 단일 출처와 이 플래그가 게이트하는 것" + ### 설정 주입과 `.env` 우선 관리자 환경설정(`storage/app/settings/*.json`)은 `SettingsServiceProvider` 를 거쳐 `config()` 에 주입되어 `.env` 유래 값을 덮는다. `.env` 를 배포 기준값으로 관리하는 설치(컨테이너·IaC·다중 서버)를 위해 그 소유권을 **키 단위**로 되돌리는 옵트인 스위치(`G7_ENV_PRIORITY`)가 있고, 판정은 `App\Support\EnvPriority` 가 단독으로 소유한다. @@ -727,6 +836,19 @@ TLS 가 앞단에서 종단되고 앱에는 HTTP 로 전달되는 구성(AWS ALB > 상세: [pagination.md](docs/backend/pagination.md) +### 입력 크기에 비례해 커지는 메모리는 PHP 기본 한계 안에서 잰다 + +브라우저에서 잘 돌던 알고리즘·상수를 PHP 로 옮길 때 시간 상한(O(n·m) 가드)만 함께 오고 **메모리 상한은 오지 않는다.** PHP 배열은 원소당 수십 바이트라 (줄 수)² 표는 2,350줄에서 약 150MB 이고, 운영 서버의 기본 `memory_limit` 은 128M 이다. 개발 머신(512M)에서는 통과하고 서버에서만 500 이 되며, 예외는 `FatalError` 한 줄뿐이라 어느 요청의 어떤 입력이었는지 로그에 남지 않는다. + +| ❌ 금지 | ✅ 올바른 사용 | +|--------|---------------| +| 카운트·길이만 쓰는 LCS/DP 에 전체 표 + backtrack | 두 행(또는 한 행) DP 로 길이만 구한다 — 추가 = 새 줄 − LCS, 삭제 = 옛 줄 − LCS 라 숫자가 같다 | +| 브라우저 구현의 임계값(`DIFF_MAX_LINES` 등)을 그대로 이식하고 "가드가 있다" 고 간주 | 그 임계에서의 PHP 메모리를 실측하고 128M 아래인지 확인 — 임계가 시간 축만 막는 경우가 있다 | +| "인접 버전 비교는 변경 영역이 작다" 는 가정으로 최악 경로를 비워 둠 | 변경 영역은 양끝이 동시에 바뀌면 파일 전체다 — 편집기가 `comment` 키를 떼어내는 첫 저장이 정확히 그 형태 | +| 메모리 회귀 테스트를 "통과했다" 로만 잠금 | `memory_get_peak_usage()` 증가량 상한을 단언하고, 수정 전 값(146MB)을 테스트 메시지에 남긴다 | + +> 상세: [service-repository.md "입력 크기에 비례하는 메모리"](docs/backend/service-repository.md) + ### 검색 인덱스 재생성(리인덱싱) | ❌ 금지 | ✅ 올바른 사용 | @@ -982,6 +1104,7 @@ Added/Changed/Fixed 내 항목이 10개를 초과하면 `####` 서브 헤딩으 - `## [버전] - YYYY-MM-DD` 헤더 필수 - `### Added` / `### Changed` / `### Fixed` / `### Removed` 카테고리 사용 - 최신 버전이 파일 상단 +- 한 버전 섹션 안에 같은 카테고리 헤딩은 한 번만 — 브랜치마다 섹션 머리에 자기 `### Fixed` 블록을 얹고 병합이 양쪽을 이어 붙이면 같은 제목이 두 번 남는다. 항목은 기존 카테고리 블록 끝에 추가하고, 병합 뒤 두 번째 블록이 보이면 그 항목을 첫 블록에 합치고 헤딩만 지운다(항목 삭제 금지). 같은 버전 헤더 중복·허용 밖 카테고리도 같은 결함군이며 정적 검사가 working 버전 섹션에서 차단한다. 공개 배포된 과거 섹션은 소급 수정하지 않는다 --- @@ -1414,6 +1537,8 @@ lazy 번들(편집기/devtools)이 코어 런타임(DynamicRenderer·엔진 싱 - 확장 에셋 절대경로는 `getBuiltAssetAbsolutePaths()`(=`getModulePath()`/`getPluginPath()`) 만 쓴다. `base_path("modules"|"plugins")` 직접 조립은 `_bundled` 경로 오해석 → 빈 번들. - concat 루프는 확장별 try/catch — 실패 확장만 skip 하고 나머지 병합을 지속한다. - 번들 파일명에 확장 캐시 버전을 포함(`{type}.{version}.{js,css}`). 조합 변경 시 version bump → 새 파일명 → 자동 재생성. 구파일 GC 는 `ext-bundles:cleanup` + `{module,plugin,template}:cache-clear` 가 담당한다. prod 은 version-in-path 디스크 캐시, 비프로덕션은 매 요청 concat. +- 프로덕션은 캐시 파일 존재를 **빌드보다 먼저** 확인한다. 캐시 키는 `(type, kind, version)` 만으로 계산되는데 빌드를 앞세우면 캐시 적중에도 매 요청 활성 확장 열거·파일 읽기가 일어나고, 원본이 소실되면 멀쩡한 캐시를 두고 503 이 된다. 캐시 미스는 같은 키의 잠금으로 1회 빌드에 수렴하고 잠금 뒤 캐시를 재확인하며, 잠금 대기 초과·저장소 장애는 실패가 아니라 각자 빌드로 폴백한다. +- 병합 결과가 비어도 선언 산출물이 전부 존재하거나 선언이 0이면 0바이트 캐시 파일을 만들어 정적 게시까지 간다. 만들지 않으면 그 구성의 자산 URL 이 API 로 폴백해 방문자의 모든 페이지 로드가 PHP 를 거친다. 캐시하지 않는 것은 산출물 소실(503 판정 보존)·병합 단계에서 건너뛴 확장이 있는 결과(굳지 않도록 매 요청 재시도)·디스크 쓰기 실패뿐이다. ### 빌드 명령어 (Artisan) diff --git a/CHANGELOG.md b/CHANGELOG.md index f393be09..2aaa9852 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,57 @@ 형식은 [Keep a Changelog](https://keepachangelog.com/ko/1.1.0/)를 따르며, [Semantic Versioning](https://semver.org/lang/ko/)을 준수합니다. +## [7.0.11] - 2026-09-09 + +### Added + +- 2단계 인증을 켠 사이트의 로그인 화면에 인증번호 입력 단계가 추가되었습니다. 비밀번호를 확인하면 같은 카드 안에서 인증번호 입력으로 넘어가고, 「인증번호 다시 받기」로 새 번호를 받거나 「처음부터」로 되돌아갈 수 있습니다. 관리자 로그인 화면도 같은 흐름으로 동작합니다. (#133 @keidichoi-gif 님께서 제보해주셨습니다.) + +### Changed + +- 인증번호를 보내지 못해 로그인을 마칠 수 없을 때의 응답이 「인증에 실패했습니다」(401)에서 「인증번호를 보내지 못했습니다」(503)로 바뀌었습니다. 자격 증명은 올바른데도 로그인 실패로 안내되어 사용자는 비밀번호를 의심하며 같은 시도를 반복하고, 운영자는 메일 설정이 깨진 사실을 알 방법이 없었습니다. (#133 @keidichoi-gif 님께서 제보해주셨습니다.) +- 설치 마법사가 이미 준비된 `vendor/` 에 개발용 패키지가 섞여 있으면 설치 환경 확인 단계와 설치 로그에서 이를 알리고 정리 명령을 안내합니다. 설치는 그대로 진행되며, 종전에는 아무 표시 없이 그대로 사용해 이후 코어 업데이트 중단의 원인이 되었습니다. +- 설치 안내서의 Composer 설치 명령이 운영용 구성(`--no-dev`)으로 바뀌었고, `vendor/` 가 없으면 설치 마법사가 자동 설치하므로 이 단계를 생략해도 된다는 안내가 추가되었습니다. +- 설치 중 Composer 단계가 실패했을 때 안내하는 수동 명령도 운영용 구성(`--no-dev`)으로 바뀌었습니다. 종전 안내를 그대로 따르면 개발용 패키지가 섞인 상태로 설치되어 이후 코어 업데이트가 중단될 수 있었습니다. +- 코어 업데이트가 운영 `vendor/` 에 개발용 패키지가 있는지 확인해 로그에 남깁니다. 의존성 변경이 없어 재설치를 건너뛰는 경우에는 개발용 패키지가 그대로 남는다는 경고와 정리 명령을 함께 안내합니다. +- 코어 업데이트가 끝나면 실행 중인 큐 워커에 재시작 신호를 보내, 워커를 손수 재시작하지 않아도 새 코드가 반영됩니다. +- 「공개 자산 스토리지」를 지정하면 레이아웃 배경 이미지가 그 저장소에 저장됩니다. 이미 올라간 파일은 옮기지 않으며 각자 저장된 위치에서 그대로 서비스됩니다. 기존 첨부 저장소와 공개 자산 저장소를 같은 곳으로 지정한 경우에는 설정을 켜는 순간 이전에 올린 이미지도 저장소 주소로 바뀝니다. 자세한 사항은 관리자 > 환경설정 > 드라이버의 안내를 참고하세요. +- PHP 의존성 패키지를 최신 안정 버전으로 갱신했습니다 (Laravel 12.69, Reverb 1.11, Scout 11.6, Monolog 3.11, Symfony 7.4.18 등). 각 패키지의 보안 수정과 오류 수정이 함께 반영되며, Composer 를 실행할 수 없는 환경을 위한 코어와 이커머스 모듈의 `vendor-bundle.zip` 도 같은 구성으로 다시 만들었습니다. + +### Fixed + +- 2단계 인증을 켠 사이트에서 로그인이 되지 않던 문제를 수정했습니다. 로그인 화면에 영문 오류(`Cannot read properties of undefined`)가 표시되고 그 뒤로는 진행할 방법이 없었으며, 관리자 로그인은 서버 오류(500)가 되어 설정을 되돌릴 수단까지 사라졌습니다. 이제 인증번호 입력 단계가 표시되고 관리자도 같은 흐름으로 로그인할 수 있습니다. (#133 @keidichoi-gif 님께서 제보해주셨습니다.) +- 로그인 응답을 잘못 해석해 사용할 수 없는 인증 정보가 브라우저에 저장되던 문제를 수정했습니다. 그 뒤로는 화면을 새로 열 때마다 「세션이 만료되었습니다」 안내와 함께 로그인 화면으로 되돌아갔습니다. +- 본인인증 화면에서 로그인용 인증 요청을 처리할 수 있어, 그 요청으로는 다시 로그인할 수 없게 되던 문제를 수정했습니다. 로그인용 인증 요청은 이제 로그인 화면에서만 처리됩니다. +- 로그인 시도가 많아 잠시 차단될 때 영문 안내(`Too Many Attempts.`)가 그대로 표시되던 문제를 수정했습니다. 이제 다시 시도할 수 있는 시각과 함께 사이트 언어로 안내합니다. +- 로그인 중 네트워크가 끊겼을 때 영문 원문(`Network Error`)이 표시되던 문제를 수정했습니다. +- 로그인 시도 초과로 계정이 잠겼을 때 해제 시각이 화면에 표시되지 않던 문제를 수정했습니다. 언제 다시 시도할 수 있는지 알 수 없어 계속 눌러 보게 되었습니다. +- 안내 문구 안에 날짜·시각 서식을 넣으면 그 값만 비어 보이던 문제를 수정했습니다. +- 확장 스크립트·스타일을 합쳐 주는 공개 주소가 이미 만들어 둔 파일이 있어도 요청마다 다시 합치던 문제를 수정했습니다. 같은 주소를 반복해서 부르면 활성 확장 수에 비례하는 파일 읽기와 처리가 매번 일어나 서버 부하로 이어질 수 있었습니다. 이제 만들어 둔 파일이 있으면 그것을 바로 내보내고, 처음 만드는 순간에만 한 번 합칩니다. 합친 결과가 비어 있는 경우(스타일이 없는 확장만 설치된 기본 구성)도 파일로 두어 웹서버가 직접 내보냅니다. (KISA 측에서 제보해주셨습니다 — KVE-2026-2191) +- 검색엔진 봇에게 대신 그려 주는 페이지가 주소의 물음표 뒤 값만 바꿔 계속 요청하면 매번 새로 그려지고 그 결과가 무한정 저장되던 문제를 수정했습니다. 봇으로 위장한 요청이 서버 부하와 저장 공간 증가로 이어질 수 있었습니다. 이제 한 IP 가 분당 일정 횟수를 넘겨 새 페이지를 요청하면 그 초과분에는 일반 페이지를 주고, 저장 개수에도 상한을 둡니다. 상한값은 서버 설정으로 바꿀 수 있습니다. +- 관리자 SEO 통계와 `seo:stats` 명령이 항상 0 으로 표시되던 문제를 수정했습니다. 캐시 적중·미적중이 기록되지 않고 있었습니다. +- 확장의 스크립트·스타일 파일을 읽지 못하는 상태가 되면 그 확장의 자산이 빠진 결과가 저장되어, 파일 문제가 풀린 뒤에도 확장을 다시 설치하거나 설정을 바꿀 때까지 계속 빠진 채로 남던 문제를 수정했습니다. 이제 읽지 못한 확장이 있으면 그 결과를 저장하지 않아 원인이 사라지는 즉시 정상으로 돌아오며, 어느 확장을 읽지 못했는지 서버 기록에 남습니다. +- 개발용 패키지가 포함된 상태로 설치된 사이트에서 코어 업데이트가 업그레이드 스텝 단계에서 「Class ... not found」 오류와 「수동 재개」 안내로 멈추던 문제를 수정했습니다. 이전 설치본이 만든 패키지 목록 캐시가 새 `vendor/` 로 교체된 뒤에도 남아 있던 것이 원인이며, 이제 업데이트는 스텝 실행 전에 그 캐시를 함께 비웁니다. 비우지 못한 파일이 있으면 업데이트 로그에 남겨 권한 문제를 바로 확인할 수 있습니다. (sir.kr 커뮤니티에서 제보해주신 내용입니다.) +- 이미 배포된 이전 버전(7.0.9·7.0.10)에서 업데이트를 시작하는 경우에도 같은 중단이 나지 않도록, 새 버전의 스텝 프로세스가 시작할 때 이전 패키지 목록 캐시를 스스로 정리합니다. +- `php artisan serve` 나 큐 워커처럼 오래 떠 있는 프로세스가 기동 시점의 버전 값을 계속 쓰면서, 업데이트 직후 확장이 「코어 버전 미달」로 잘못 비활성화되던 문제를 수정했습니다. 업데이트 진행 중이 아닐 때는 프로세스 환경값이 아니라 설정의 버전을 기준으로 판정합니다. +- 코어 업데이트가 마지막 정리 단계에서 「Target class [...] does not exist」 오류로 실패하고 백업으로 되돌아가던 문제를 수정했습니다. 설정 캐시를 다시 만드는 과정이 내부적으로 띄우는 일회용 애플리케이션이 그 뒤의 모든 작업까지 넘겨받은 채로 남아 있던 것이 원인입니다. +- 업데이트 진행 중 판정이 명령줄에서 실행될 때만 커맨드 이름을 참고하도록 좁혔습니다. 일부 PHP 설정(`register_argc_argv` 활성)에서는 웹 주소의 쿼리만으로 업데이트가 진행 중인 것처럼 보이게 할 수 있었습니다. +- 레이아웃 편집기에서 올린 배경 이미지의 주소가 「공개 자산 스토리지」 설정과 무관하게 항상 사이트 서버를 거쳐 전달되던 문제를 수정했습니다. 파일은 설정한 저장소에 정상 저장되지만 방문자 요청은 매번 사이트 서버가 저장소에서 받아 다시 내보내고 있어, CDN 을 쓰도록 설정해도 그 효과를 볼 수 없었습니다. 이제 「공개 자산 스토리지」를 지정하면 그 저장소에 올라간 배경 이미지가 저장소 주소로 직접 전달됩니다. 설정하지 않은 경우와 설정 이전에 올린 이미지는 종전과 똑같이 동작합니다. (#134 @lyg-kaban 님께서 제보해주셨습니다.) +- 확장이 카테고리별로 다른 저장소를 쓰도록 설정했을 때, 확장 개발용 파일 주소·경로 조회가 그 설정을 반영하지 않고 언제나 기본 저장소를 보던 문제를 수정했습니다. +- 화면 편집기에서 로고처럼 이미지 주소를 직접 받는 항목에 이미지를 지정하면 그림이 깨져 보이던 문제를 수정했습니다. 지정한 주소 대신 알 수 없는 값이 저장된 것이 원인이며, 편집기 화면에서는 미리보기가 정상으로 보여 저장 전에는 알아챌 수 없었습니다. 이제 주소가 그대로 저장되고, 이미 잘못 저장된 사이트도 업데이트하면 자동으로 정정됩니다. 업데이트 전에도 화면은 정상 표시됩니다. (#135 @lyg-kaban 님께서 제보해주셨습니다.) +- 화면 편집기의 「탭 표시 게시판 수」처럼 숫자를 받는 항목이 「지원하지 않는 컨트롤」로 표시되어 편집할 수 없던 문제를 수정했습니다. 이제 숫자 입력칸이 나타납니다. +- 화면 편집기에서 공통 레이아웃이나 확장이 넣은 요소를 고쳐도 저장되지 않던 문제를 수정했습니다. 편집은 되는데 저장 후 되돌아가 있었고 실행 취소로도 되살릴 수 없었습니다. 이제 그런 요소는 편집 대신 「공통 레이아웃 편집」·「확장 편집」 진입 안내가 표시되며, 옮기기·복제·삭제도 같은 기준으로 막힙니다. +- 화면 편집기가 열리자마자 빈 화면이 되는 경우가 있던 문제를 수정했습니다. 편집기가 잠깐 보였다가 사라졌고, 새로고침에 따라 나타났다 사라졌다 했습니다. +- 화면 편집기에서 환경설정과 연결된 항목(예: 헤더 로고)을 무심코 조작하면 그 연결이 소리 없이 끊기던 문제를 수정했습니다. 이제 그런 항목은 연결된 값이라는 안내와 원래 값이 표시되고, 「직접 지정으로 바꾸기」를 눌러야 편집할 수 있으며 「되돌리기」로 언제든 원래 연결로 복구할 수 있습니다. +- `vendor-bundle:build-all --check` 가 설치되지 않은 번들 확장을 「소스 경로 없음」으로 건너뛰어, 그 확장의 vendor 번들이 갱신되지 않은 상태가 점검을 통과하던 문제를 수정했습니다. 이제 설치 여부와 무관하게 배포 원본 기준으로 판정합니다. +- 레이아웃 편집기의 헤더 「로고 이미지」에서 파일을 올린 뒤 저장하면 「HTTPS 프로토콜 URL은 허용되지 않습니다」로 거부되던 문제를 수정했습니다. 업로드한 이미지의 주소가 사이트 전체 주소 형태로 발급되어 레이아웃 저장의 외부 주소 차단에 걸리고 있었으며, 배경 이미지는 검사 대상이 아니라 증상이 없었습니다. 이제 업로드 주소는 사이트 상대 경로로 발급되고, 사이트 자신의 주소와 「공개 자산 스토리지」로 지정한 저장소의 주소는 외부로 취급하지 않습니다. +- 큰 공통 레이아웃(예: 사용자 화면 공통 레이아웃)을 레이아웃 편집기에서 처음 저장할 때 「저장 중 네트워크 오류가 발생했습니다 / Server Error」로 끝나던 문제를 수정했습니다. 버전 이력의 변경량(+N/-N 줄)을 계산하는 과정이 레이아웃 크기의 제곱에 비례하는 메모리를 써서 PHP 기본 메모리 한도(128MB)인 서버에서 실패했습니다. 이제 메모리가 레이아웃 크기에 비례하며 표시되는 변경량 숫자는 그대로입니다. +- 레이아웃 편집기의 「확장 편집」 모드에서 저장하면 그 확장이 화면에 끼워 넣던 요소(예: 헤더의 통화 선택기)가 사라지던 문제를 수정했습니다. 내용을 바꾸지 않고 저장해도 일어났고 오류나 안내가 없어 사이트에서 그 요소가 없어진 뒤에야 알 수 있었습니다. 이제 끼워 넣은 요소가 그대로 보존되며, 되돌릴 자리를 확인할 수 없는 요소가 있으면 저장을 막고 그 사실을 안내합니다. 이미 사라진 사이트는 「버전 기록」에서 직전 버전을 복원하면 됩니다. +- 레이아웃 편집기에서 저장이나 복원을 두 번 이상 한 뒤 「초기화」나 충돌 안내의 「최신 불러오기」를 누르면 최신이 아닌 옛 내용이 올라오던 문제를 수정했습니다. 그 화면을 다시 저장하면 최신 내용을 옛 내용이 덮을 수 있었고, 충돌 안내 뒤에는 새로고침 전까지 저장이 계속 거부됐습니다. +- 레이아웃 편집기의 충돌 안내가 「최신 버전: -1」 로 표시되던 문제를 수정했습니다. 이제 실제 최신 버전 번호가 표시됩니다. +- 레이아웃·레이아웃 확장의 버전 기록에 저장자가 항상 「알 수 없음」으로 표시되던 문제를 수정했습니다. 이제 저장한 관리자가 기록됩니다. +- 버전 기록에서 복원한 직후, 복원 전 화면을 열어 둔 다른 관리자가 저장하면 복원 결과가 충돌 안내 없이 덮이던 문제를 수정했습니다. 이제 복원도 저장과 같이 충돌 검사의 기준 번호를 올립니다. + ## [7.0.10] - 2026-09-06 ### Added diff --git a/INSTALL.md b/INSTALL.md index aedfdf78..6c8f5f10 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -148,12 +148,16 @@ git clone https://github.com/gnuboard/g7.git cd g7 ``` -### 3단계: Composer 의존성 설치 +### 3단계: Composer 의존성 설치 (선택) ```bash -composer install +composer install --no-dev --optimize-autoloader ``` +> 이 단계는 생략해도 됩니다 — `vendor/` 가 없으면 설치 마법사가 운영용 구성(`--no-dev`)으로 자동 설치합니다. +> +> 개발용 패키지가 포함되는 `composer install`(옵션 없음)의 결과를 운영 사이트에 그대로 쓰지 마세요. 이미 그렇게 설치했다면 위 명령을 한 번 더 실행하면 개발용 패키지만 제거됩니다. + ### 4단계: 환경 설정 파일 생성 ```bash @@ -200,15 +204,17 @@ https://github.com/gnuboard/g7 다운로드한 ZIP 파일을 원하는 위치에 압축 해제합니다. -### 4단계: Composer 의존성 설치 +### 4단계: Composer 의존성 설치 (선택) -터미널에서 압축 해제된 디렉토리로 이동한 후 실행합니다. +터미널에서 압축 해제된 디렉토리로 이동한 후 실행합니다. 이 단계는 생략해도 됩니다 — `vendor/` 가 없으면 설치 마법사가 운영용 구성(`--no-dev`)으로 자동 설치합니다. ```bash cd g7-버전명 -composer install +composer install --no-dev --optimize-autoloader ``` +> 개발용 패키지가 포함되는 `composer install`(옵션 없음)의 결과를 운영 사이트에 그대로 쓰지 마세요. 이미 그렇게 설치했다면 위 명령을 한 번 더 실행하면 개발용 패키지만 제거됩니다. + ### 5단계: 환경 설정 파일 생성 ```bash @@ -289,7 +295,7 @@ unzip g7-release.zip # 압축 해제 결과 확인 — 루트 디렉토리가 g7이 아니면 이름 변경 ls -la -# (필요 시) mv g7-7.0.10 g7 +# (필요 시) mv g7-7.0.11 g7 # ZIP 파일 정리 (선택) rm g7-release.zip @@ -343,13 +349,23 @@ http://도메인/install |------|------| | 0. 환영 | 언어 선택 및 `storage` 권한 검증 — 권한 부족 시 인스톨러가 상대경로 기반 명령을 자동 안내 | | 1. 라이선스 | 동의 | -| 2. 요구사항 | PHP 8.2+ 및 필수 확장 확인 | +| 2. 요구사항 | PHP 8.2+ 및 필수 확장 확인 + 권장 항목(OPcache · Composer 의존성 구성) 안내 | | 3. 환경 설정 | DB 정보 + 관리자 계정 + **Vendor 설치 방식** 선택 | | 4. 확장 선택 | 템플릿/모듈/플러그인 선택 (의존성 자동 해결) | | 5. 설치 실행 | "설치 시작" 버튼 클릭 후 진행 상황 모니터링 | `.env` 파일 생성과 `storage` / `bootstrap/cache` / `public/build` 쓰기 권한 부여는 인스톨러가 환경(소유자 일치 여부 등)에 맞춰 Step 0 및 이후 단계에서 자동 안내하므로, 사전 SSH 작업으로 수행할 필요 없습니다. +**Step 2 — Composer 의존성 구성 (선택 항목):** + +이미 `vendor/` 가 준비되어 있으면 설치 마법사는 그것을 그대로 씁니다. 이때 그 `vendor/` 가 개발용 패키지까지 포함한 설치(`composer install`, 옵션 없음)인지 확인해 알려 줍니다. + +- **운영용 구성 (개발용 패키지 없음)**: 권장 상태입니다. 조치가 필요 없습니다. +- **개발용 패키지 N개 포함**: 설치는 그대로 진행됩니다. 운영에 사용할 서버라면 설치를 마친 뒤 프로젝트 루트에서 `composer install --no-dev --optimize-autoloader` 를 한 번 실행하세요. 그대로 두면 이후 코어 업데이트가 `vendor/` 를 교체할 때 이전 패키지 목록 캐시와 어긋나 업데이트가 중단될 수 있습니다. +- **vendor 없음**: 설치 마법사가 운영용 구성으로 자동 설치하므로 조치가 필요 없습니다. + +이 항목은 설치를 차단하지 않습니다. + **Step 3 — Vendor 설치 방식 선택:** - **자동 (권장)**: 환경을 자동 감지하여 번들 모드로 폴백합니다. diff --git a/README.ko.md b/README.ko.md index 28e085cf..62759593 100644 --- a/README.ko.md +++ b/README.ko.md @@ -6,7 +6,7 @@ A modern, extensible CMS platform built with Laravel + React

- Version + Version PHP Laravel React @@ -369,6 +369,8 @@ cp .env.example .env # 3. 브라우저에서 /install 접속 → 설치 마법사 진행 ``` +> Composer 의존성은 설치 마법사가 운영용 구성(`--no-dev`)으로 자동 설치합니다. 직접 설치하려면 `composer install --no-dev --optimize-autoloader` 를 사용하세요. + > 상세 설치 가이드는 [INSTALL.md](INSTALL.md)를 참조하세요. --- @@ -521,6 +523,7 @@ cp .env.example .env abc101 hwaryeon1234 koojunho + keidichoi-gif kitrio yks118 movielee2020 diff --git a/README.md b/README.md index e6ded2ed..1b4483b4 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ The next generation of Gnuboard — Korea's most widely used open-source CMS

- Version + Version PHP Laravel React @@ -369,8 +369,11 @@ Every verification point — signup, password reset, sensitive operations, the m git clone https://github.com/gnuboard/g7.git cd g7 -# 2. Install PHP dependencies -composer install +# 2. (Optional) Install PHP dependencies — you can skip this step: +# the setup wizard installs them automatically with the production +# configuration when vendor/ is absent. Do not use a plain composer +# install on a production site; it pulls in development packages. +composer install --no-dev --optimize-autoloader # 3. Copy the environment file cp .env.example .env @@ -535,6 +538,7 @@ Thanks to everyone who reported an issue or suggested a feature that shipped — abc101 hwaryeon1234 koojunho + keidichoi-gif kitrio yks118 movielee2020 diff --git a/app/Console/Commands/Core/Concerns/BundledExtensionUpdatePrompt.php b/app/Console/Commands/Core/Concerns/BundledExtensionUpdatePrompt.php index 768695de..a9ee600d 100644 --- a/app/Console/Commands/Core/Concerns/BundledExtensionUpdatePrompt.php +++ b/app/Console/Commands/Core/Concerns/BundledExtensionUpdatePrompt.php @@ -330,6 +330,10 @@ trait BundledExtensionUpdatePrompt // ENV 합집합 (G7_UPDATE_IN_PROGRESS 등 핵심 플래그 자식에 전달) $env = array_merge(getenv(), $_ENV); + // audit:allow spawn-after-vendor-swap-clears-package-manifest 이 spawn 은 vendor 를 바꾸지 않고, + // 두 호출처가 모두 clearAllCaches() 뒤다 — CoreUpdateCommand Step 11(패키지 매니페스트 재생성) + // 다음의 Step 12, ExecuteUpgradeStepsCommand 단독 실행의 캐시 정리 다음 단계. 따라서 자식이 + // 읽는 매니페스트는 이미 현재 vendor 기준으로 다시 만들어진 것이다. $process = proc_open($commandLine, $descriptors, $pipes, base_path(), $env); if (! is_resource($process)) { return null; diff --git a/app/Console/Commands/Core/CoreUpdateCommand.php b/app/Console/Commands/Core/CoreUpdateCommand.php index 9dca2f67..47565d05 100644 --- a/app/Console/Commands/Core/CoreUpdateCommand.php +++ b/app/Console/Commands/Core/CoreUpdateCommand.php @@ -16,7 +16,9 @@ use App\Extension\Traits\ClearsTemplateCaches; use App\Extension\Vendor\Exceptions\VendorInstallException; use App\Extension\Vendor\VendorMode; use App\Services\CoreUpdateService; +use App\Support\ComposerInstallInfo; use App\Support\ConfigCacheHelper; +use App\Support\PackageManifestCacheHelper; use App\Support\RouteCacheHelper; use Illuminate\Console\Command; use Illuminate\Support\Facades\Log; @@ -320,6 +322,22 @@ class CoreUpdateCommand extends Command $composerSkipped = $vendorMode !== VendorMode::Bundled && $service->isComposerUnchangedForCore($pendingPath); + // 운영 vendor 에 개발용(require-dev) 패키지가 섞여 있는지 알린다. 그대로 두면 + // 이전 설치본이 만든 패키지 매니페스트가 새 vendor 와 어긋나 이후 부팅이 깨지고, + // 그 사실은 오류 메시지 어디에도 "dev 설치본" 으로 드러나지 않는다. + $devPackages = ComposerInstallInfo::devPackageNames(base_path('vendor')); + if ($devPackages !== []) { + $devCount = count($devPackages); + $devSample = implode(', ', array_slice($devPackages, 0, 5)).($devCount > 5 ? ' …' : ''); + if ($composerSkipped) { + $this->warn("운영 vendor 에 개발용 패키지 {$devCount}개 감지 — composer.json/lock 변경이 없어 vendor 를 재설치하지 않으므로 그대로 남습니다. 업데이트 후 'composer install --no-dev --optimize-autoloader' 실행을 권장합니다."); + $log("운영 vendor 개발용 패키지 {$devCount}개 잔존 (composer 스킵): {$devSample}"); + } else { + $this->line("운영 vendor 에 개발용 패키지 {$devCount}개 감지 — 이번 업데이트가 --no-dev vendor 로 교체합니다."); + $log("운영 vendor 개발용 패키지 {$devCount}개 감지 — --no-dev vendor 로 교체: {$devSample}"); + } + } + if ($composerSkipped) { $bar->setMessage('Composer 의존성 변경 없음 — 스킵'); $bar->advance(); @@ -511,6 +529,11 @@ class CoreUpdateCommand extends Command $service->updateVersionInEnv($toVersion); $service->clearAllCaches(); + // 상주 큐 워커는 부팅이 한 번뿐이라 옛 코드를 물고 있다 — 캐시가 새 코드 기준으로 + // 정리된 직후 재시작 신호를 보낸다. + $service->signalQueueRestart(); + $log('큐 워커 재시작 신호 전송 (queue:restart)'); + // 코어 업데이트 후 프론트엔드가 새 lang/routes/layout 자원으로 fetch 하도록 // `ext.cache_version` bump. 코어 lang JSON 변경이 프론트엔드 캐시에 // 반영되지 않는 회귀를 차단한다 (core-frontend-i18n-infrastructure 계획서). @@ -636,6 +659,9 @@ class CoreUpdateCommand extends Command try { $service->updateVersionInEnv($toVersion); $service->clearAllCaches(); + // 핸드오프로 멈춰도 파일은 이미 toVersion 이므로 워커는 새 코드로 재기동해야 한다. + $service->signalQueueRestart(); + $log('큐 워커 재시작 신호 전송 (queue:restart)'); // 핸드오프 cleanup 에서도 프론트엔드 캐시 버전 bump — 사용자가 resume 명령 // (execute-upgrade-steps) 을 실행하기 전이라도 이미 toVersion 으로 반영된 // 코어 lang/routes/layout 자원이 프론트엔드 캐시 stale 로 가려지지 않도록. @@ -887,6 +913,24 @@ class CoreUpdateCommand extends Command // Step 11 의 `ConfigCacheHelper::rebuild()` 가 모든 파일이 안착한 뒤 다시 만든다. ConfigCacheHelper::clear(); + // 같은 이유로 패키지 매니페스트(bootstrap/cache/packages.php · services.php)도 비운다. + // vendor 는 Step 6/8 에서 이미 교체됐지만 두 파일은 Step 11 clearAllCaches 까지 이전 + // 설치본의 것이 남고, Laravel 은 packages.php 가 "없을 때만" 다시 만든다. 이전 설치본이 + // dev composer(require-dev 전이 의존성 laravel/mcp 의 McpServiceProvider 포함)로 깔렸다면 + // 자식은 새 vendor 에 없는 provider 를 new 하다 부팅 단계에서 죽는다 (7.0.9→7.0.10 실사례). + // 부모 메모리의 매니페스트는 영향받지 않고, Step 11 이 package:discover 로 다시 만든다. + // + // 지우지 못한 파일(권한·소유권 불일치)은 로그에 남긴다. 그 상태면 자식의 자가 치유도 같은 + // 권한으로 실패하므로 증상은 이전 설치본 provider 의 「Class not found」 그대로이고, 이 기록이 + // 권한이 원인이라는 유일한 흔적이다. + $remainingManifests = PackageManifestCacheHelper::clear(); + if ($remainingManifests !== []) { + $manifestWarning = 'spawn 직전 패키지 매니페스트 삭제 실패 — 자식이 이전 설치본의 provider 목록으로 부팅할 수 있습니다 (권한·소유권 확인): ' + .implode(', ', $remainingManifests); + $log($manifestWarning); + $this->warn($manifestWarning); + } + $process = proc_open($commandLine, $descriptors, $pipes, base_path(), $env); if (! is_resource($process)) { return $this->failSpawnWithMode( diff --git a/app/Console/Commands/Core/ExecuteUpgradeStepsCommand.php b/app/Console/Commands/Core/ExecuteUpgradeStepsCommand.php index 69140be7..480f71d7 100644 --- a/app/Console/Commands/Core/ExecuteUpgradeStepsCommand.php +++ b/app/Console/Commands/Core/ExecuteUpgradeStepsCommand.php @@ -261,6 +261,10 @@ class ExecuteUpgradeStepsCommand extends Command if (! $this->option('skip-cache-clear') && ! $isSpawnChild && ! $stepsOnly) { $this->info('캐시 정리 (config/route/view/services/packages)'); $service->clearAllCaches(); + // 상주 큐 워커는 부팅이 한 번뿐이라 옛 코드를 물고 있다 — 캐시 정리 직후 재시작 신호. + // spawn 자식·steps-only 는 이 블록을 스킵하고 부모가 보낸다. + $service->signalQueueRestart(); + $this->info('큐 워커 재시작 신호 전송 (queue:restart)'); // clearAllCaches() 는 config:clear 만 하므로, 단독 실행 흐름에서는 config 캐시가 // 비활성 상태로 남는다. 모든 upgrade step + 번들 확장 업데이트가 config 소스를 // 변경했을 수 있으니, 캐시 정리 세트의 마지막에 config 캐시를 재생성한다. diff --git a/app/Console/Commands/PlaywrightSeedTwoFactor.php b/app/Console/Commands/PlaywrightSeedTwoFactor.php new file mode 100644 index 00000000..e5060f90 --- /dev/null +++ b/app/Console/Commands/PlaywrightSeedTwoFactor.php @@ -0,0 +1,242 @@ + --code=135790 + */ +class PlaywrightSeedTwoFactor extends Command +{ + protected $signature = 'playwright:seed-two-factor + {--ensure-user= : 이 접미사로 Active 테스트 사용자를 생성/갱신하고 이메일을 출력한다} + {--password= : --ensure-user 가 설정할 비밀번호 (기본값 Passw0rd!2fa)} + {--admin : --ensure-user 계정에 admin 역할을 부여한다} + {--plant= : 이 challenge 에 알려진 인증 코드의 해시를 심는다 (challenge UUID)} + {--code=135790 : --plant 가 심을 인증 코드} + {--gc-hours=6 : 이 시간(시)보다 오래된 playwright 2FA 테스트 계정을 정리. 0 이면 정리 안 함} + {--purge-users : 나이와 무관하게 playwright 2FA 테스트 계정을 전부 제거 (실측 종료 직후 호출)}'; + + protected $description = 'Playwright E2E 용 2단계 인증 픽스처 (테스트 계정 준비 / 인증 코드 심기)'; + + /** 테스트 전용 계정 이메일 접두사 */ + private const TEST_EMAIL_PREFIX = 'playwright_2fa_'; + + public function handle(): int + { + // ① CLI 한정 — production 웹 요청에서 절대 도달 불가 + if (php_sapi_name() !== 'cli') { + $this->error('CLI 전용 커맨드입니다. (현재 SAPI: '.php_sapi_name().')'); + + return self::FAILURE; + } + + // ② 명시 옵트인 — 환경변수 없이는 production 호출 실수 차단. + // 여기의 `env()` 는 `.env` 유래 값이 아니라 호출자가 그 자리에서 넘기는 프로세스 + // 환경변수이므로 config:cache 의 영향을 받지 않는다. + if (env('G7_PLAYWRIGHT_BYPASS') !== '1') { + $this->error('G7_PLAYWRIGHT_BYPASS=1 환경변수가 필요합니다. (예: PowerShell — $env:G7_PLAYWRIGHT_BYPASS=\'1\')'); + + return self::FAILURE; + } + + // ③ APP_DEBUG 강제 — production + debug=false 환경에서도 픽스처 조작 허용 + Config::set('app.debug', true); + + $gcHours = (int) $this->option('gc-hours'); + if ($gcHours > 0) { + $this->pruneStaleTestUsers($gcHours); + } + + $did = false; + + // 알려진 비밀번호를 가진 계정(관리자 포함)을 실측 뒤에 남기지 않는다 — + // 나이 기준 정리(gc-hours)는 그 사이의 창을 닫지 못한다. + if ($this->option('purge-users')) { + $this->purgeTestUsers(); + $did = true; + } + + if ($suffix = $this->option('ensure-user')) { + $this->ensureUser((string) $suffix); + $did = true; + } + + if ($challengeId = $this->option('plant')) { + if (! $this->plantCode((string) $challengeId, (string) $this->option('code'))) { + return self::FAILURE; + } + $did = true; + } + + if (! $did) { + $this->error('--ensure-user · --plant · --purge-users 중 하나는 지정해야 합니다.'); + + return self::FAILURE; + } + + return self::SUCCESS; + } + + /** + * 알려진 비밀번호를 가진 Active 테스트 사용자를 만들거나 갱신하고 이메일을 출력합니다. + * + * @param string $suffix 계정 구분 접미사 (예: user / nonadmin) + */ + private function ensureUser(string $suffix): void + { + $email = self::TEST_EMAIL_PREFIX.$suffix.'@example.test'; + $password = (string) ($this->option('password') ?: 'Passw0rd!2fa'); + + $user = User::where('email', $email)->first(); + + if ($user === null) { + $user = User::factory()->create([ + 'email' => $email, + 'password' => Hash::make($password), + 'status' => UserStatus::Active->value, + ]); + } else { + // 이전 실행이 남긴 계정을 재사용한다 — 매 실행마다 계정이 늘면 회원 목록이 + // 테스트 잔재로 뒤덮인다. 잠금·실패 카운트도 함께 초기화해 앞선 spec 의 + // 잠금 상태가 다음 실행으로 새지 않게 한다. + $user->forceFill([ + 'password' => Hash::make($password), + 'status' => UserStatus::Active->value, + 'locked_until' => null, + 'failed_login_attempts' => 0, + ])->save(); + } + + if ($this->option('admin')) { + $adminRole = Role::firstOrCreate( + ['identifier' => 'admin'], + [ + 'name' => ['ko' => '관리자', 'en' => 'Admin'], + 'description' => ['ko' => '시스템 관리자', 'en' => 'System Admin'], + 'extension_type' => ExtensionOwnerType::Core, + 'extension_identifier' => 'core', + 'type' => 'admin', + 'is_active' => true, + ] + ); + + if (! $user->roles()->where('roles.id', $adminRole->id)->exists()) { + $user->roles()->attach($adminRole->id, ['assigned_at' => now(), 'assigned_by' => null]); + } + } else { + // 관리자 거절 경로를 재현하려면 관리자 역할이 없어야 한다 — 재사용 계정에 + // 앞선 실행의 admin 역할이 남아 있으면 403 을 측정할 수 없다. + $user->roles()->detach(); + } + + $this->line($email); + } + + /** + * challenge 에 알려진 인증 코드의 해시를 심습니다. + * + * @param string $challengeId challenge UUID + * @param string $code 심을 인증 코드 + * @return bool 성공 여부 + */ + private function plantCode(string $challengeId, string $code): bool + { + $log = IdentityVerificationLog::find($challengeId); + + if ($log === null) { + $this->error("challenge 를 찾을 수 없습니다: {$challengeId}"); + + return false; + } + + $metadata = $log->metadata ?? []; + $metadata['code_hash'] = Hash::make($code); + + $log->metadata = $metadata; + $log->save(); + + $this->line($code); + + return true; + } + + /** + * playwright 2FA 테스트 계정을 나이와 무관하게 전부 제거합니다. + * + * 이 계정들은 알려진 비밀번호를 갖고, `--admin` 으로 만든 것은 관리자 역할까지 갖는다. + * 실측이 끝난 뒤에도 남아 있으면 그 자체가 열린 문이므로 즉시 지운다. + */ + private function purgeTestUsers(): void + { + $removed = 0; + + User::where('email', 'like', self::TEST_EMAIL_PREFIX.'%') + ->chunkById(100, function ($chunk) use (&$removed) { + foreach ($chunk as $user) { + $user->tokens()->delete(); + $user->roles()->detach(); + $user->delete(); + $removed++; + } + }); + + $this->info("[purge] playwright 2FA 테스트 계정 제거: {$removed}건"); + } + + /** + * 임계 시간보다 오래된 playwright 2FA 테스트 계정을 정리합니다. + * + * chunkById(키셋 순회) 필수 — 콜백이 순회 대상 행을 삭제하므로 OFFSET 기반 + * chunk()/each() 는 줄어든 결과 집합만큼 커서가 밀려 일부를 건너뛴다. + * + * @param int $hours 이 시간보다 오래된 계정만 정리 + */ + private function pruneStaleTestUsers(int $hours): void + { + $threshold = now()->subHours($hours); + $removed = 0; + + User::where('email', 'like', self::TEST_EMAIL_PREFIX.'%') + ->where('created_at', '<', $threshold) + ->chunkById(100, function ($chunk) use (&$removed) { + foreach ($chunk as $user) { + $user->tokens()->delete(); + $user->roles()->detach(); + $user->delete(); + $removed++; + } + }); + + if ($removed > 0) { + $this->info("[gc] playwright 2FA 테스트 계정 정리: {$removed}건"); + } + } +} diff --git a/app/Console/Commands/Vendor/Concerns/RunsVendorBundleAction.php b/app/Console/Commands/Vendor/Concerns/RunsVendorBundleAction.php index 6b17bdb2..0fa1796f 100644 --- a/app/Console/Commands/Vendor/Concerns/RunsVendorBundleAction.php +++ b/app/Console/Commands/Vendor/Concerns/RunsVendorBundleAction.php @@ -3,8 +3,8 @@ namespace App\Console\Commands\Vendor\Concerns; use App\Extension\Vendor\Exceptions\VendorInstallException; -use App\Extension\Vendor\VendorBundleResult; use App\Extension\Vendor\VendorBundler; +use App\Extension\Vendor\VendorBundleResult; use App\Extension\Vendor\VendorIntegrityChecker; use Illuminate\Support\Facades\File; @@ -210,14 +210,17 @@ trait RunsVendorBundleAction $sourcePath = $this->resolveSourcePath($target); $outputPath = $this->resolveOutputPath($target); - if (! is_dir($sourcePath)) { - $this->warn("- $label: 소스 경로 없음 ($sourcePath)"); + // 판정 기준 파일은 빌드(VendorBundler::build / isStale)와 같은 규칙 — 출력(_bundled) 우선, + // 없으면 활성 디렉토리 폴백. 활성 디렉토리만 보면 미설치 확장을 "소스 경로 없음" 으로 + // 보고해 _bundled 의 stale 번들이 --check 를 통과한다. + $composerJsonPath = $this->resolveCheckFile($sourcePath, $outputPath, 'composer.json'); + if ($composerJsonPath === null) { + if (! is_dir($sourcePath) && ! is_dir($outputPath)) { + $this->warn("- $label: 경로 없음 ($outputPath)"); - return false; - } + return false; + } - $composerJsonPath = $sourcePath.DIRECTORY_SEPARATOR.'composer.json'; - if (! is_file($composerJsonPath)) { $this->line("- $label: SKIPPED (composer.json 없음)"); return false; @@ -240,6 +243,24 @@ trait RunsVendorBundleAction return $stale; } + /** + * --check 가 읽을 composer 파일 경로 — 출력(_bundled) 우선, 없으면 활성 디렉토리 폴백. + * + * VendorBundler::resolveHashTarget() 과 같은 규칙. 판정 파일이 빌드·해시 기준과 + * 다르면 --check 결과가 실제 stale 여부와 어긋난다. + */ + private function resolveCheckFile(string $sourcePath, string $outputPath, string $filename): ?string + { + $outputFile = $outputPath.DIRECTORY_SEPARATOR.$filename; + if (is_file($outputFile)) { + return $outputFile; + } + + $sourceFile = $sourcePath.DIRECTORY_SEPARATOR.$filename; + + return is_file($sourceFile) ? $sourceFile : null; + } + /** * composer.json 에 외부 패키지 의존성이 있는지 확인합니다. * diff --git a/app/Contracts/Repositories/LayoutExtensionVersionRepositoryInterface.php b/app/Contracts/Repositories/LayoutExtensionVersionRepositoryInterface.php index 1800a6b0..e2554a5d 100644 --- a/app/Contracts/Repositories/LayoutExtensionVersionRepositoryInterface.php +++ b/app/Contracts/Repositories/LayoutExtensionVersionRepositoryInterface.php @@ -14,9 +14,10 @@ interface LayoutExtensionVersionRepositoryInterface * @param int $extensionId 레이아웃 확장 ID * @param array $oldContent 이전 콘텐츠 * @param array|null $newContent 새 콘텐츠 (null이면 현재 확장 content 사용) + * @param int|null $createdBy 저장자 ID (null 이면 현재 인증 사용자) * @return TemplateLayoutExtensionVersion 생성된 버전 모델 */ - public function saveVersion(int $extensionId, array $oldContent, ?array $newContent = null): TemplateLayoutExtensionVersion; + public function saveVersion(int $extensionId, array $oldContent, ?array $newContent = null, ?int $createdBy = null): TemplateLayoutExtensionVersion; /** * 특정 확장의 모든 버전 조회 (최신순) diff --git a/app/Contracts/Repositories/LayoutVersionRepositoryInterface.php b/app/Contracts/Repositories/LayoutVersionRepositoryInterface.php index 0c3d7e47..7842a24d 100644 --- a/app/Contracts/Repositories/LayoutVersionRepositoryInterface.php +++ b/app/Contracts/Repositories/LayoutVersionRepositoryInterface.php @@ -14,9 +14,10 @@ interface LayoutVersionRepositoryInterface * @param int $layoutId 레이아웃 ID * @param array $oldContent 이전 콘텐츠 * @param array|null $newContent 새 콘텐츠 (null이면 현재 레이아웃 content 사용) + * @param int|null $createdBy 저장자 ID (null 이면 현재 인증 사용자) * @return TemplateLayoutVersion 생성된 버전 모델 */ - public function saveVersion(int $layoutId, array $oldContent, ?array $newContent = null): TemplateLayoutVersion; + public function saveVersion(int $layoutId, array $oldContent, ?array $newContent = null, ?int $createdBy = null): TemplateLayoutVersion; /** * 특정 레이아웃의 모든 버전 조회 (최신순) diff --git a/app/Exceptions/Auth/TwoFactorDeliveryFailedException.php b/app/Exceptions/Auth/TwoFactorDeliveryFailedException.php new file mode 100644 index 00000000..e973a070 --- /dev/null +++ b/app/Exceptions/Auth/TwoFactorDeliveryFailedException.php @@ -0,0 +1,25 @@ +getStorage()->getBasePath($category); + return $this->getStorageFor($category)->getBasePath($category); } /** * 파일의 공개 URL 반환 * - * public disk인 경우 직접 URL을 반환하고, - * private disk인 경우 null을 반환합니다 (별도 API 엔드포인트 사용). + * 카테고리에 배선된 디스크(getStorageDiskFor)가 직접 URL 을 지원하면 그 URL 을, + * 아니면 null 을 반환합니다 (별도 API 엔드포인트 사용). 기본 디스크를 보면 + * 공개 자산 디스크로 옮긴 카테고리가 항상 null 을 받습니다. * * @param string $category 카테고리 * @param string $path 파일 경로 - * @return string|null 파일 URL (private disk인 경우 null) + * @return string|null 파일 URL (직접 URL 불가 디스크인 경우 null) */ public function getStorageUrl(string $category, string $path): ?string { - return $this->getStorage()->url($category, $path); + return $this->getStorageFor($category)->url($category, $path); } } diff --git a/app/Extension/AbstractPlugin.php b/app/Extension/AbstractPlugin.php index 236cc313..e11f3e09 100644 --- a/app/Extension/AbstractPlugin.php +++ b/app/Extension/AbstractPlugin.php @@ -10,6 +10,7 @@ use App\Contracts\Extension\UpgradeStepInterface; use App\Extension\Cache\PluginCacheDriver; use App\Extension\Storage\PluginStorageDriver; use App\Extension\Traits\ReportsLifecycleFailure; +use App\Support\PublicAssetDisk; use Illuminate\Database\Seeder; use ReflectionClass; @@ -1313,15 +1314,7 @@ abstract class AbstractPlugin implements CacheableExtensionInterface, PluginInte */ protected function resolvePublicAssetDisk(?string $override = null): ?string { - $disk = ($override !== null && $override !== '') - ? $override - : (string) config('core.storage.public_asset_disk', ''); - - if ($disk === '' || $disk === 'none' || config("filesystems.disks.{$disk}") === null) { - return null; - } - - return $disk; + return PublicAssetDisk::resolve($override); } /** @@ -1359,26 +1352,30 @@ abstract class AbstractPlugin implements CacheableExtensionInterface, PluginInte /** * 카테고리별 스토리지 기본 경로 반환 * + * 카테고리가 다른 디스크로 배선돼 있으면(getStorageDiskFor 오버라이드) 그 디스크 + * 기준 경로를 돌려줍니다. 기본 디스크를 보면 배선한 카테고리의 경로가 어긋납니다. + * * @param string $category 카테고리 (settings, data, temp) * @return string 전체 파일 시스템 경로 */ public function getStorageBasePath(string $category): string { - return $this->getStorage()->getBasePath($category); + return $this->getStorageFor($category)->getBasePath($category); } /** * 파일의 공개 URL 반환 * - * public disk인 경우 직접 URL을 반환하고, - * private disk인 경우 null을 반환합니다 (별도 API 엔드포인트 사용). + * 카테고리에 배선된 디스크(getStorageDiskFor)가 직접 URL 을 지원하면 그 URL 을, + * 아니면 null 을 반환합니다 (별도 API 엔드포인트 사용). 기본 디스크를 보면 + * 공개 자산 디스크로 옮긴 카테고리가 항상 null 을 받습니다. * * @param string $category 카테고리 * @param string $path 파일 경로 - * @return string|null 파일 URL (private disk인 경우 null) + * @return string|null 파일 URL (직접 URL 불가 디스크인 경우 null) */ public function getStorageUrl(string $category, string $path): ?string { - return $this->getStorage()->url($category, $path); + return $this->getStorageFor($category)->url($category, $path); } } diff --git a/app/Extension/CoreVersionChecker.php b/app/Extension/CoreVersionChecker.php index e7b679d1..d3726ab2 100644 --- a/app/Extension/CoreVersionChecker.php +++ b/app/Extension/CoreVersionChecker.php @@ -5,6 +5,7 @@ namespace App\Extension; use App\Contracts\Extension\CacheInterface; use App\Exceptions\CoreVersionMismatchException; use App\Extension\Cache\CoreCacheDriver; +use App\Support\CoreUpdateContext; use Composer\Semver\Semver; use Exception; use Illuminate\Support\Facades\Log; @@ -31,10 +32,11 @@ class CoreVersionChecker * 현재 설치된 그누보드7 코어 버전 반환 * * 반환 우선순위: - * 1. 환경변수 `APP_VERSION` (getenv / $_ENV / $_SERVER) + * 1. 코어 업데이트 프로세스 트리 안(`CoreUpdateContext::isInProgress()`)이면 + * 환경변수 `APP_VERSION` (getenv / $_ENV / $_SERVER) * 2. `config('app.version')` * - * 왜 env 를 우선 읽는가: + * 왜 업데이트 트리 안에서만 env 를 우선 읽는가: * 코어 업그레이드 중 `core:update` 는 `core:execute-upgrade-steps` / 각 업그레이드 스텝의 * inline 스크립트를 `proc_open` 으로 spawn 한다. 디스크 `.env` 의 `APP_VERSION` 은 * `updateVersionInEnv()` 가 최종 단계(Step 11)에서 기록하므로, spawn 이 부팅되는 @@ -44,23 +46,28 @@ class CoreVersionChecker * env 오버라이드가 반영되지 않는 회귀가 있었다 (확장이 `>= 신버전` 요구 시 * `validateAndDeactivateIncompatibleExtensions` 가 전 확장을 자동 비활성화). * - * env 를 우선 읽는 본 구현은 config cache 유무와 무관하게 spawn 이 전달한 버전을 그대로 - * 신뢰한다. 일반 요청 경로에서는 `APP_VERSION` 이 `.env` 에 기록된 값 그대로이므로 - * 동작에 차이가 없다. + * 왜 트리 밖에서는 env 를 읽지 않는가: + * `php artisan serve` · 큐 워커 · Horizon 처럼 오래 사는 프로세스는 기동 시점의 + * `APP_VERSION` 을 프로세스 환경 테이블에 물고 있다. 업데이트가 끝나 `.env` 와 config 가 + * 새 버전이 되어도 그 프로세스만 옛 버전으로 판정해, 새 코어를 요구하는 확장을 + * `incompatible_core` 로 자동 비활성화한다 (관리자 템플릿이 꺼지면 복구 UI 에도 도달할 수 + * 없다 — 2026-09-07 실측). 업데이트 트리 밖에서는 config 가 유일한 근거다. * - * 규정 예외: "env() 는 config 파일에서만 사용" 규칙의 본문 예외. 정당성은 버전 판정이 - * config cache 우회를 요구하기 때문이다. + * 규정 예외: "env() 는 config 파일에서만 사용" 규칙의 본문 예외. 정당성은 업데이트 중 + * 버전 판정이 config cache 우회를 요구하기 때문이다. * * @return string 코어 버전 문자열 (예: "7.0.0-beta.4") */ public static function getCoreVersion(): string { - $envVersion = $_ENV['APP_VERSION'] ?? $_SERVER['APP_VERSION'] ?? getenv('APP_VERSION'); - if (is_string($envVersion) && $envVersion !== '') { - return $envVersion; + if (CoreUpdateContext::isInProgress()) { + $envVersion = $_ENV['APP_VERSION'] ?? $_SERVER['APP_VERSION'] ?? getenv('APP_VERSION'); + if (is_string($envVersion) && $envVersion !== '') { + return $envVersion; + } } - return config('app.version'); + return (string) config('app.version'); } /** diff --git a/app/Http/Controllers/Api/Admin/AuthController.php b/app/Http/Controllers/Api/Admin/AuthController.php index f6bdc66a..4979f348 100644 --- a/app/Http/Controllers/Api/Admin/AuthController.php +++ b/app/Http/Controllers/Api/Admin/AuthController.php @@ -3,9 +3,13 @@ namespace App\Http\Controllers\Api\Admin; use App\Exceptions\Auth\AccountLockedException; +use App\Exceptions\Auth\TwoFactorDeliveryFailedException; use App\Http\Controllers\Api\Base\AdminBaseController; +use App\Http\Controllers\Concerns\BuildsAuthFailureResponses; use App\Http\Requests\Auth\AuthenticatedRequest; use App\Http\Requests\Auth\LoginRequest; +use App\Http\Requests\Auth\TwoFactorChallengeRequest; +use App\Http\Requests\Auth\TwoFactorResendRequest; use App\Http\Resources\UserResource; use App\Services\AuthService; use Illuminate\Http\JsonResponse; @@ -13,10 +17,21 @@ use Illuminate\Validation\ValidationException; class AuthController extends AdminBaseController { + use BuildsAuthFailureResponses; + public function __construct( private AuthService $authService ) { - parent::__construct(); + // 부모 생성자 호출하지 않음 - 인증 미들웨어를 수동으로 설정 + // parent::__construct(); + + // 2단계 인증 확인·재발송은 아직 토큰이 없는 상태에서 호출된다 — 주체는 challenge 가 + // 식별하며, 관리자 여부는 코드 확인에 성공한 뒤 서비스가 돌려준 사용자로 판정한다. + $this->middleware(['auth:sanctum', 'admin'])->except([ + 'login', + 'verifyTwoFactor', + 'resendTwoFactor', + ]); } /** @@ -33,10 +48,21 @@ class AuthController extends AdminBaseController $request->validated()['password'] ); + // 2단계 인증이 켜져 있으면 아직 토큰도 사용자도 없다 — 관리자 판정은 코드 확인 + // 뒤로 미룬다. 이 분기가 없으면 $data['user'] 가 없어 500 이 되고, 관리자까지 + // 로그인할 수 없어 설정을 되돌릴 수단이 사라진다. + if ($data['two_factor_required'] ?? false) { + return $this->success('auth.two_factor_required', $data); + } + $user = $data['user']; // 관리자 권한 확인 if (! $user->isAdmin()) { + // 이 시점에는 이미 토큰과 web 세션이 발급되어 있다 — 거절하면서 남겨 두면 + // 관리자가 아닌 사용자가 응답만 403 을 받을 뿐 세션은 그대로 유효해진다. + $this->authService->revokeIssuedSession($user, $data['token']); + return $this->forbidden('auth.admin_required'); } @@ -45,22 +71,92 @@ class AuthController extends AdminBaseController return $this->success('auth.admin_login_success', $data); } catch (AccountLockedException $e) { - // 영구 잠금(무한대 설정)은 해제 시각·잔여 시간이 없다 — null 그대로 노출. - return $this->error( - $e->isPermanent() ? 'auth.account_locked_permanently' : 'auth.account_locked', - 423, - [ - 'locked_until' => $e->lockedUntil?->toIso8601String(), - 'retry_after_seconds' => $e->remainingMinutes === null ? null : $e->remainingMinutes * 60, - 'permanent' => $e->isPermanent(), - ], - ['minutes' => $e->remainingMinutes] - ); + return $this->lockedResponse($e); + } catch (TwoFactorDeliveryFailedException $e) { + return $this->deliveryFailedResponse(); } catch (ValidationException $e) { return $this->unauthorized('auth.login_failed'); } } + /** + * 관리자의 2단계 인증 코드를 확인하고 로그인을 완료합니다. + * + * 비밀번호 확인 단계(`login`)는 토큰 대신 challenge 를 돌려주며, 이 엔드포인트가 + * 코드 확인에 성공해야 비로소 토큰이 발급됩니다. + * + * @param TwoFactorChallengeRequest $request challenge 확인 요청 + * @return JsonResponse 로그인 결과와 관리자 정보, 토큰을 포함한 JSON 응답 + */ + public function verifyTwoFactor(TwoFactorChallengeRequest $request): JsonResponse + { + $validated = $request->validated(); + + try { + $data = $this->authService->completeTwoFactor( + $validated['challenge_id'], + ['code' => $validated['code']] + ); + + $user = $data['user']; + + if (! $user->isAdmin()) { + // completeTwoFactor() 는 코드 확인에 성공한 시점에 토큰을 발급한다. + // 관리자 판정으로 거절하면서 그 발급분을 회수하지 않으면 관리자가 아닌 + // 사용자가 유효한 세션을 손에 쥔 채 응답만 403 을 받는다. + $this->authService->revokeIssuedSession($user, $data['token']); + + return $this->forbidden('auth.admin_required'); + } + + $data['user'] = new UserResource($user); + + return $this->success('auth.admin_login_success', $data); + } catch (AccountLockedException $e) { + // 세션을 여는 지점이므로 `login` 과 같은 423 계약을 따른다. + return $this->lockedResponse($e); + } catch (TwoFactorDeliveryFailedException $e) { + return $this->deliveryFailedResponse(); + } catch (ValidationException $e) { + return $this->unauthorized('auth.two_factor_failed'); + } + } + + /** + * 관리자의 2단계 인증 코드를 재발송합니다. + * + * 기존 challenge 는 취소되고 새 challenge 가 발행되므로, 앞서 받은 인증번호는 + * 더 이상 통하지 않습니다. + * + * @param TwoFactorResendRequest $request challenge 재발송 요청 + * @return JsonResponse 새 challenge 정보를 포함한 JSON 응답 + */ + public function resendTwoFactor(TwoFactorResendRequest $request): JsonResponse + { + try { + $resolvedUser = null; + + $data = $this->authService->resendTwoFactorChallenge( + $request->validated()['challenge_id'], + $resolvedUser + ); + + // 이 단계는 토큰을 발급하지 않으므로 회수할 것이 없다 — 다만 완료할 수 없는 + // 상대에게 새 인증번호를 계속 보내지는 않는다. + if ($resolvedUser === null || ! $resolvedUser->isAdmin()) { + return $this->forbidden('auth.admin_required'); + } + + return $this->success('auth.two_factor_required', $data); + } catch (AccountLockedException $e) { + return $this->lockedResponse($e); + } catch (TwoFactorDeliveryFailedException $e) { + return $this->deliveryFailedResponse(); + } catch (ValidationException $e) { + return $this->validationError($e->errors(), 'auth.two_factor_invalid_challenge'); + } + } + /** * 관리자를 로그아웃시킵니다. * @@ -113,16 +209,7 @@ class AuthController extends AdminBaseController } catch (AccountLockedException $e) { // 재발급도 세션을 여는 지점이다 — 사용자 경로와 같은 423 계약을 따른다. // 이 catch 가 없으면 잠긴 계정의 재발급 시도가 500 으로 새어 나간다. - return $this->error( - $e->isPermanent() ? 'auth.account_locked_permanently' : 'auth.account_locked', - 423, - [ - 'locked_until' => $e->lockedUntil?->toIso8601String(), - 'retry_after_seconds' => $e->remainingMinutes === null ? null : $e->remainingMinutes * 60, - 'permanent' => $e->isPermanent(), - ], - ['minutes' => $e->remainingMinutes] - ); + return $this->lockedResponse($e); } catch (ValidationException $e) { return $this->unauthorized('auth.unauthenticated'); } diff --git a/app/Http/Controllers/Api/Auth/AuthController.php b/app/Http/Controllers/Api/Auth/AuthController.php index d66a9007..d5085011 100644 --- a/app/Http/Controllers/Api/Auth/AuthController.php +++ b/app/Http/Controllers/Api/Auth/AuthController.php @@ -3,13 +3,16 @@ namespace App\Http\Controllers\Api\Auth; use App\Exceptions\Auth\AccountLockedException; +use App\Exceptions\Auth\TwoFactorDeliveryFailedException; use App\Http\Controllers\Api\Base\AuthBaseController; +use App\Http\Controllers\Concerns\BuildsAuthFailureResponses; use App\Http\Requests\Auth\AuthenticatedRequest; use App\Http\Requests\Auth\ForgotPasswordRequest; use App\Http\Requests\Auth\LoginRequest; use App\Http\Requests\Auth\RegisterRequest; use App\Http\Requests\Auth\ResetPasswordRequest; use App\Http\Requests\Auth\TwoFactorChallengeRequest; +use App\Http\Requests\Auth\TwoFactorResendRequest; use App\Http\Requests\Auth\ValidateResetTokenRequest; use App\Http\Resources\UserResource; use App\Services\AuthService; @@ -18,6 +21,8 @@ use Illuminate\Validation\ValidationException; class AuthController extends AuthBaseController { + use BuildsAuthFailureResponses; + public function __construct( private AuthService $authService ) { @@ -27,8 +32,9 @@ class AuthController extends AuthBaseController // 공개 인증 엔드포인트를 제외한 나머지에만 인증 미들웨어 적용 $this->middleware('auth:sanctum')->except([ 'login', - // 2단계 인증 확인은 아직 토큰이 없는 상태에서 호출된다 — 주체는 challenge 가 식별한다 + // 2단계 인증 확인·재발송은 아직 토큰이 없는 상태에서 호출된다 — 주체는 challenge 가 식별한다 'verifyTwoFactor', + 'resendTwoFactor', 'register', 'forgotPassword', 'resetPassword', @@ -61,6 +67,8 @@ class AuthController extends AuthBaseController return $this->success('auth.login_success', $data); } catch (AccountLockedException $e) { return $this->lockedResponse($e); + } catch (TwoFactorDeliveryFailedException $e) { + return $this->deliveryFailedResponse(); } catch (ValidationException $e) { return $this->unauthorized('auth.login_failed'); } @@ -92,32 +100,37 @@ class AuthController extends AuthBaseController // 세션을 여는 지점이므로 `login` 과 같은 423 계약을 따른다 — 화면은 두 경로를 // 구분하지 않으므로 한쪽만 다른 모양이면 잠금 안내가 깨진다. return $this->lockedResponse($e); + } catch (TwoFactorDeliveryFailedException $e) { + return $this->deliveryFailedResponse(); } catch (ValidationException $e) { return $this->unauthorized('auth.two_factor_failed'); } } /** - * 계정 잠금 응답(423)을 구성합니다. + * 2단계 인증 코드를 재발송합니다. * - * 세션을 발급하는 모든 엔드포인트가 같은 페이로드를 돌려주도록 단일 지점에서 만든다. + * 기존 challenge 는 취소되고 새 challenge 가 발행되므로, 앞서 받은 인증번호는 + * 더 이상 통하지 않습니다. * - * @param AccountLockedException $e 잠금 예외 - * @return JsonResponse 423 응답 + * @param TwoFactorResendRequest $request challenge 재발송 요청 + * @return JsonResponse 새 challenge 정보를 포함한 JSON 응답 */ - private function lockedResponse(AccountLockedException $e): JsonResponse + public function resendTwoFactor(TwoFactorResendRequest $request): JsonResponse { - // 영구 잠금(무한대 설정)은 해제 시각·잔여 시간이 없다 — null 그대로 노출. - return $this->error( - $e->isPermanent() ? 'auth.account_locked_permanently' : 'auth.account_locked', - 423, - [ - 'locked_until' => $e->lockedUntil?->toIso8601String(), - 'retry_after_seconds' => $e->remainingMinutes === null ? null : $e->remainingMinutes * 60, - 'permanent' => $e->isPermanent(), - ], - ['minutes' => $e->remainingMinutes] - ); + try { + $data = $this->authService->resendTwoFactorChallenge( + $request->validated()['challenge_id'] + ); + + return $this->success('auth.two_factor_required', $data); + } catch (AccountLockedException $e) { + return $this->lockedResponse($e); + } catch (TwoFactorDeliveryFailedException $e) { + return $this->deliveryFailedResponse(); + } catch (ValidationException $e) { + return $this->validationError($e->errors(), 'auth.two_factor_invalid_challenge'); + } } /** diff --git a/app/Http/Controllers/Api/Identity/IdentityVerificationController.php b/app/Http/Controllers/Api/Identity/IdentityVerificationController.php index 89fc494e..573f6336 100644 --- a/app/Http/Controllers/Api/Identity/IdentityVerificationController.php +++ b/app/Http/Controllers/Api/Identity/IdentityVerificationController.php @@ -3,6 +3,7 @@ namespace App\Http\Controllers\Api\Identity; use App\Enums\IdentityOriginType; +use App\Enums\IdentityVerificationPurpose; use App\Extension\IdentityVerification\IdentityVerificationManager; use App\Http\Controllers\Api\Base\PublicBaseController; use App\Http\Requests\Identity\CancelChallengeRequest; @@ -96,6 +97,15 @@ class IdentityVerificationController extends PublicBaseController */ public function verify(VerifyChallengeRequest $request, IdentityVerificationLog $challenge): JsonResponse { + // 로그인 challenge 는 이 공개 경로로 다루지 않는다. 여기서 검증·취소되면 + // 바로 뒤의 `auth/login/two-factor` 가 INVALID_STATE 로 거절해, 그 challenge 로는 + // 영영 로그인할 수 없게 된다(자기 DoS). 로그인 전용 엔드포인트만 사용한다. + if ($challenge->purpose === IdentityVerificationPurpose::Login->value) { + return $this->error('identity.errors.purpose_not_allowed', 403, [ + 'failure_code' => 'PURPOSE_NOT_ALLOWED', + ]); + } + $result = $this->service->verify( challengeId: $challenge->id, input: $request->validated(), @@ -142,6 +152,15 @@ class IdentityVerificationController extends PublicBaseController */ public function cancel(CancelChallengeRequest $request, IdentityVerificationLog $challenge): JsonResponse { + // 로그인 challenge 는 이 공개 경로로 다루지 않는다. 여기서 검증·취소되면 + // 바로 뒤의 `auth/login/two-factor` 가 INVALID_STATE 로 거절해, 그 challenge 로는 + // 영영 로그인할 수 없게 된다(자기 DoS). 로그인 전용 엔드포인트만 사용한다. + if ($challenge->purpose === IdentityVerificationPurpose::Login->value) { + return $this->error('identity.errors.purpose_not_allowed', 403, [ + 'failure_code' => 'PURPOSE_NOT_ALLOWED', + ]); + } + $ok = $this->service->cancel($challenge->id); if (! $ok) { diff --git a/app/Http/Controllers/Api/Public/PublicLayoutController.php b/app/Http/Controllers/Api/Public/PublicLayoutController.php index cecd2d5e..ca651cb5 100644 --- a/app/Http/Controllers/Api/Public/PublicLayoutController.php +++ b/app/Http/Controllers/Api/Public/PublicLayoutController.php @@ -63,20 +63,18 @@ class PublicLayoutController extends PublicBaseController } try { - // 캐시 버전을 키에 포함하여 모듈/플러그인 변경 시 캐시 무효화. + // 서버 캐시 키는 **서버 현재** 확장 캐시 버전으로만 조립한다. 클라이언트 `?v=` 는 브라우저 + // HTTP 캐시 우회용 좌표일 뿐 서버 키의 근거가 아니다. // - // 서버 캐시 키에는 **정수 버전만** 쓴다(소수 nonce 제거). 레이아웃 편집기는 같은 세션의 - // 저장·버전 복원 직후 브라우저 HTTP 캐시를 우회하려고 `?v={cacheVersion}.{nonce}` 형식으로 - // 요청한다(클라이언트 cache-bust nonce). 그런데 `serve` 가 이 문자열을 그대로 캐시 키에 - // 쓰면 키가 `...v{cacheVersion}.{nonce}.meta` 가 되는데, 저장 경로 - // `LayoutService::clearPublicServingCache` 는 `(int) ext.cache_version` 으로 nonce 없는 - // 키만 forget 하므로 키 형식이 어긋나 무효화가 빗나간다(저장/복원 후 편집기 캔버스만 - // stale). nonce 는 브라우저 HTTP 캐시 우회용(URL·ETag 차이로 이미 달성)이고, 서버 캐시 키 - // 정합은 정수 버전이 SSoT 다. `(int)` 캐스팅은 PHP 가 소수점에서 절단해 정수부만 남긴다. - // `?v` 생략 시 현재 버전으로 폴백 — 리터럴 0 폴백은 워밍/무효화 어느 경로에도 - // 걸리지 않는 `.v0` 영구 사각 키를 만든다 (#588). - $rawVersion = request()->query('v'); - $cacheVersion = $rawVersion !== null ? (int) $rawVersion : self::getExtensionCacheVersion(); + // 종전엔 `?v` 의 정수부를 키에 썼다(#588 — nonce 제거). 그런데 레이아웃 편집기는 부팅 + // 시점 `window.G7Config.cache_version` 에 nonce 만 붙여 계속 요청하고, 저장·복원은 + // `ext.cache_version` 을 `time()` 으로 올리며 `clearPublicServingCache` 는 **현재** 버전 + // 키만 지운다. 그래서 두 번째 bump 부터 부팅 버전 키가 영영 지워지지 않아 초기화·복원· + // 409 「최신 불러오기」가 옛 content 를 받았고(실측: 초기화 직후 lock 4 응답, DB 는 lock 7), + // 그 화면을 다시 저장하면 옛 내용이 최신을 덮을 수 있었다. 서버 버전으로 키를 고정하면 + // 무효화(현재 버전 키 forget)와 굽기(현재 버전 키 remember)가 같은 키를 본다. `?v` 가 어떤 + // 값이든 결과는 같고, 이전 버전 키는 bump 로 자연 이탈한다(TTL 만료). + $cacheVersion = self::getExtensionCacheVersion(); // 편집기 출처 메타 옵션 // - 옵션이 truthy 면 각 노드에 `__source` 메타를 부여한 응답을 반환 diff --git a/app/Http/Controllers/Concerns/BuildsAuthFailureResponses.php b/app/Http/Controllers/Concerns/BuildsAuthFailureResponses.php new file mode 100644 index 00000000..f5588287 --- /dev/null +++ b/app/Http/Controllers/Concerns/BuildsAuthFailureResponses.php @@ -0,0 +1,53 @@ +error( + $e->isPermanent() ? 'auth.account_locked_permanently' : 'auth.account_locked', + 423, + [ + 'locked_until' => $e->lockedUntil?->toIso8601String(), + 'retry_after_seconds' => $e->remainingMinutes === null ? null : $e->remainingMinutes * 60, + 'permanent' => $e->isPermanent(), + ], + ['minutes' => $e->remainingMinutes] + ); + } + + /** + * 인증번호 발송 실패 응답(503)을 구성합니다. + * + * 자격 증명은 올바르므로 401 로 답하지 않는다 — 사용자는 비밀번호를 의심하며 같은 + * 실패를 반복하고, 운영자는 메일 설정이 깨진 사실을 알 방법이 없다. + * + * @return JsonResponse 503 응답 + */ + protected function deliveryFailedResponse(): JsonResponse + { + return $this->error('auth.two_factor_delivery_failed', 503); + } +} diff --git a/app/Http/Requests/Auth/TwoFactorResendRequest.php b/app/Http/Requests/Auth/TwoFactorResendRequest.php new file mode 100644 index 00000000..8ba3f3cf --- /dev/null +++ b/app/Http/Requests/Auth/TwoFactorResendRequest.php @@ -0,0 +1,54 @@ + 검증 규칙 + */ + public function rules(): array + { + $rules = [ + 'challenge_id' => ['required', 'string', 'uuid'], + ]; + + // 확장이 자체 2단계 수단을 붙일 때 재발송 입력을 추가할 수 있도록 개방한다 + return HookManager::applyFilters('core.auth.two_factor_resend_validation_rules', $rules, $this); + } + + /** + * 검증 실패 메시지를 반환합니다. + * + * @return array 검증 메시지 + */ + public function messages(): array + { + return [ + 'challenge_id.required' => __('validation.auth.two_factor.challenge_required'), + 'challenge_id.uuid' => __('validation.auth.two_factor.challenge_invalid'), + ]; + } +} diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index 6e264c4b..ed93514f 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -9,6 +9,7 @@ use App\Contracts\Notifications\ChannelReadinessCheckerInterface; use App\Extension\HookManager; use App\Extension\ModuleManager; use App\Extension\PluginManager; +use App\Helpers\ResponseHelper; use App\Http\View\Composers\TemplateComposer; use App\Http\View\Composers\UserTemplateComposer; use App\Notifications\NotificationChannelManager; @@ -126,7 +127,18 @@ class AppServiceProvider extends ServiceProvider $maxPerMinute = 60; } - return Limit::perMinute($maxPerMinute)->by($request->ip()); + // 기본 응답은 영문 "Too Many Attempts." 이다 — 로그인 화면은 이 문구를 + // 그대로 노출하므로 다국어 키로 갈아끼운다. + return Limit::perMinute($maxPerMinute) + ->by($request->ip()) + ->response(function (Request $request, array $headers) { + return ResponseHelper::error( + 'auth.too_many_attempts', + 429, + null, + ['seconds' => (int) ($headers['Retry-After'] ?? 60)] + )->withHeaders($headers); + }); }); } diff --git a/app/Providers/CoreServiceProvider.php b/app/Providers/CoreServiceProvider.php index 5254d16a..f6943070 100644 --- a/app/Providers/CoreServiceProvider.php +++ b/app/Providers/CoreServiceProvider.php @@ -97,6 +97,7 @@ use App\Services\LayoutExtensionService; use App\Services\TemplateLayoutAttachmentService; use App\Services\TemplateService; use App\Services\UniqueIdService; +use App\Support\CoreUpdateContext; use App\Support\ExtensionSettingsMirror; use App\Support\PrivilegedDatabaseAccounts; use Illuminate\Support\Facades\Config; @@ -562,7 +563,7 @@ class CoreServiceProvider extends ServiceProvider 'type' => $type, 'identifier' => $identifier, 'required_version' => $requiredVersion, - 'core_version' => config('app.version'), + 'core_version' => CoreVersionChecker::getCoreVersion(), ]); } } @@ -626,6 +627,21 @@ class CoreServiceProvider extends ServiceProvider $cache->put($cacheKey, $recovered, CoreVersionChecker::getCacheTtl()); } + /** + * 현재 프로세스가 코어 업데이트 중인지 판정합니다. + * + * 판정은 `App\Support\CoreUpdateContext::isInProgress()` 가 단독으로 소유한다 — 같은 + * 플래그가 확장 자동 비활성화 스킵 · 코어 버전의 env 우선 판독 · `bootstrap/app.php` 의 + * 패키지 매니페스트 자가 치유를 함께 게이트하므로, 판정이 갈라지면 한 경로만 조용히 + * 다르게 동작한다. 이 메서드는 기존 호출처(각 Manager)를 위한 위임으로 남는다. + * + * @return bool 업데이트 트리 안이면 true (version-based 자동 비활성화 스킵) + */ + public static function isCoreUpdateInProgress(): bool + { + return CoreUpdateContext::isInProgress(); + } + /** * 호환되지 않는 템플릿을 자동 비활성화합니다. * @@ -634,30 +650,6 @@ class CoreServiceProvider extends ServiceProvider * * @param TemplateManager $templateManager 템플릿 매니저 */ - /** - * 현재 프로세스가 코어 업데이트 중인지 판정합니다. - * - * 판정 조건 (OR): - * 1. 환경변수 `G7_UPDATE_IN_PROGRESS=1` — 부모 CoreUpdateCommand 가 시작 시 설정, - * spawn 자식에도 `$env` 로 전파 - * 2. artisan command 이름이 `core:update` / `core:execute-upgrade-steps` — 1 이 전파되지 - * 않은 극단 상황 대비 보조 판정 - * - * 둘 중 하나라도 true 면 업데이트 컨텍스트로 간주하여 version-based 자동 비활성화 스킵. - */ - public static function isCoreUpdateInProgress(): bool - { - $envFlag = $_ENV['G7_UPDATE_IN_PROGRESS'] ?? $_SERVER['G7_UPDATE_IN_PROGRESS'] ?? getenv('G7_UPDATE_IN_PROGRESS'); - if ($envFlag === '1' || $envFlag === 1 || $envFlag === true) { - return true; - } - - $argv = $_SERVER['argv'] ?? []; - $command = $argv[1] ?? ''; - - return in_array($command, ['core:update', 'core:execute-upgrade-steps'], true); - } - protected function validateAndDeactivateIncompatibleTemplates(TemplateManager $templateManager): void { // 업데이트 중 자동 비활성화 스킵 (validateAndDeactivateIncompatibleExtensions 와 동일 사유) @@ -702,7 +694,7 @@ class CoreServiceProvider extends ServiceProvider 'type' => 'templates', 'identifier' => $identifier, 'required_version' => $requiredVersion, - 'core_version' => config('app.version'), + 'core_version' => CoreVersionChecker::getCoreVersion(), ]); } } @@ -730,7 +722,7 @@ class CoreServiceProvider extends ServiceProvider $alerts = $cache->get('ext.compatibility_alerts', []); $alerts[$type] = [ 'deactivated' => $deactivated, - 'core_version' => config('app.version'), + 'core_version' => CoreVersionChecker::getCoreVersion(), 'timestamp' => now()->toIso8601String(), ]; $cache->put('ext.compatibility_alerts', $alerts, 86400); // 24시간 diff --git a/app/Repositories/Concerns/CalculatesJsonContentDiff.php b/app/Repositories/Concerns/CalculatesJsonContentDiff.php index 00156852..4475b592 100644 --- a/app/Repositories/Concerns/CalculatesJsonContentDiff.php +++ b/app/Repositories/Concerns/CalculatesJsonContentDiff.php @@ -135,10 +135,17 @@ trait CalculatesJsonContentDiff /** * 라인 배열 두 개의 LCS diff — 추가/삭제 라인 "수"를 반환한다. * - * 공통 prefix/suffix 를 먼저 트리밍해 LCS DP 입력을 변경 영역으로 축소한다(큰 - * content 의 작은 변경도 빠르게 처리). 라인 원문은 누적하지 않고 카운트만 세므로 - * 메모리도 절약된다(저장 대상은 카운트뿐). 프론트 computeLineDiff 와 동일 전략이라 - * added/removed 카운트가 일치한다. + * 공통 prefix/suffix 를 먼저 트리밍해 LCS 입력을 변경 영역으로 축소한다(큰 content 의 + * 작은 변경도 빠르게 처리). 카운트는 LCS **길이**만으로 결정된다 — 추가 = 새 줄 수 − LCS, + * 삭제 = 옛 줄 수 − LCS — 이므로 표 전체를 만들어 되짚을 필요가 없고, 두 행만 쓰는 + * 길이 계산으로 메모리를 변경 영역 줄 수에 비례하게 묶는다. 프론트 computeLineDiff 는 + * 실제 diff 줄을 그려야 해서 전체 표를 쓰지만 같은 LCS 규칙이므로 added/removed 카운트가 + * 일치한다. + * + * 트리밍은 양끝이 동시에 바뀌면 무력하다 — 편집기는 저장 시 `comment` 키를 떼어내므로 + * 큰 공통 레이아웃의 첫 편집기 저장은 변경 영역이 파일 전체(2,000줄 이상)가 된다. + * 종전의 (줄 수)² PHP 배열은 그 경우 약 150MB 를 써서 PHP 기본 memory_limit(128M) + * 서버에서 저장이 500 으로 끝났다(개발 머신은 512M 이라 드러나지 않았다). * * @param array $a 이전 라인 * @param array $b 새 라인 @@ -172,43 +179,50 @@ trait CalculatesJsonContentDiff $nb = count($midB); // 안전 가드 — 변경 영역이 과대하면 LCS 를 생략하고 라인 집합 차집합으로 근사한다. - // 프론트 lineDiff.ts 의 DIFF_MAX_LINES(4000) 와 동일 임계. 인접 버전 비교는 변경 - // 영역이 작아 이 경로를 타지 않으며, 비정상적으로 큰 변경에서만 O(n·m) DP 를 회피한다. + // 프론트 lineDiff.ts 의 DIFF_MAX_LINES(4000) 와 동일 임계. 시간 O(n·m) 의 상한이며, + // 아래 길이 계산은 메모리가 두 행뿐이라 이 임계 안에서는 memory_limit 과 무관하다. if ($na > self::DIFF_MAX_LINES || $nb > self::DIFF_MAX_LINES) { return $this->approximateLineCounts($midA, $midB); } - // LCS DP - $dp = array_fill(0, $na + 1, array_fill(0, $nb + 1, 0)); + $lcs = $this->lcsLength($midA, $midB); + + return [$nb - $lcs, $na - $lcs]; + } + + /** + * 두 라인 배열의 LCS(최장 공통 부분수열) 길이 — 두 행만 쓰는 DP. + * + * 전체 표(종전 `$dp[$i][$j]`)와 같은 점화식(`a[i] === b[j] ? next[j+1] + 1 : max(next[j], + * cur[j+1])`)을 행 단위로 굴려 마지막 행의 첫 칸만 남긴다. 되짚기(backtrack)가 필요한 + * 프론트 diff 뷰와 달리 여기서는 길이만 쓰므로 카운트가 종전과 정확히 같다. + * + * @param array $a 이전 라인 (0 부터 연속 인덱스) + * @param array $b 새 라인 (0 부터 연속 인덱스) + * @return int LCS 길이 + */ + private function lcsLength(array $a, array $b): int + { + $na = count($a); + $nb = count($b); + + if ($na === 0 || $nb === 0) { + return 0; + } + + $next = array_fill(0, $nb + 1, 0); for ($i = $na - 1; $i >= 0; $i--) { + $cur = array_fill(0, $nb + 1, 0); + $line = $a[$i]; for ($j = $nb - 1; $j >= 0; $j--) { - $dp[$i][$j] = $midA[$i] === $midB[$j] - ? $dp[$i + 1][$j + 1] + 1 - : max($dp[$i + 1][$j], $dp[$i][$j + 1]); + $cur[$j] = $line === $b[$j] + ? $next[$j + 1] + 1 + : max($next[$j], $cur[$j + 1]); } + $next = $cur; } - // backtrack — 삭제/추가 라인 수만 카운트 - $added = 0; - $removed = 0; - $i = 0; - $j = 0; - while ($i < $na && $j < $nb) { - if ($midA[$i] === $midB[$j]) { - $i++; - $j++; - } elseif ($dp[$i + 1][$j] >= $dp[$i][$j + 1]) { - $removed++; - $i++; - } else { - $added++; - $j++; - } - } - $removed += $na - $i; - $added += $nb - $j; - - return [$added, $removed]; + return $next[0]; } /** diff --git a/app/Repositories/LayoutExtensionVersionRepository.php b/app/Repositories/LayoutExtensionVersionRepository.php index 04f82095..7c81988d 100644 --- a/app/Repositories/LayoutExtensionVersionRepository.php +++ b/app/Repositories/LayoutExtensionVersionRepository.php @@ -8,6 +8,7 @@ use App\Models\TemplateLayoutExtensionVersion; use App\Repositories\Concerns\CalculatesJsonContentDiff; use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\ModelNotFoundException; +use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\DB; class LayoutExtensionVersionRepository implements LayoutExtensionVersionRepositoryInterface @@ -28,9 +29,10 @@ class LayoutExtensionVersionRepository implements LayoutExtensionVersionReposito * @param int $extensionId 레이아웃 확장 ID * @param array $content 저장할 content 스냅샷 * @param array|null $previousContent 직전 버전 content (변경 요약 기준). null 이면 변경 요약 0 + * @param int|null $createdBy 저장자 ID (null 이면 현재 인증 사용자) * @return TemplateLayoutExtensionVersion 생성된 버전 */ - public function saveVersion(int $extensionId, array $content, ?array $previousContent = null): TemplateLayoutExtensionVersion + public function saveVersion(int $extensionId, array $content, ?array $previousContent = null, ?int $createdBy = null): TemplateLayoutExtensionVersion { $nextVersion = $this->getNextVersion($extensionId); @@ -44,6 +46,8 @@ class LayoutExtensionVersionRepository implements LayoutExtensionVersionReposito 'version' => $nextVersion, 'content' => $content, 'changes_summary' => $changesSummary, + // 저장자 — 버전 목록의 「저장자」 표시 근거. 종전엔 기록하지 않아 항상 「알 수 없음」이었다. + 'created_by' => $createdBy ?? Auth::id(), ]); } @@ -165,9 +169,10 @@ class LayoutExtensionVersionRepository implements LayoutExtensionVersionReposito $extension = LayoutExtension::findOrFail($extensionId); $currentContent = $extension->content; - // 3. 확장을 복원할 content로 업데이트 + // 3. 확장을 복원할 content로 업데이트 — lock_version 도 올린다(레이아웃 본체와 동형). $extension->update([ 'content' => $versionToRestore->content, + 'lock_version' => ((int) ($extension->lock_version ?? 0)) + 1, ]); // 4. 복원 결과를 새 버전으로 저장 — content 는 복원된 내용, changes_summary 는 복원 diff --git a/app/Repositories/LayoutVersionRepository.php b/app/Repositories/LayoutVersionRepository.php index cb1ffa3d..5983e8a6 100644 --- a/app/Repositories/LayoutVersionRepository.php +++ b/app/Repositories/LayoutVersionRepository.php @@ -8,6 +8,7 @@ use App\Models\TemplateLayoutVersion; use App\Repositories\Concerns\CalculatesJsonContentDiff; use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\ModelNotFoundException; +use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\DB; class LayoutVersionRepository implements LayoutVersionRepositoryInterface @@ -28,9 +29,10 @@ class LayoutVersionRepository implements LayoutVersionRepositoryInterface * @param int $layoutId 레이아웃 ID * @param array $content 저장할 content 스냅샷 (이 버전이 담는 내용) * @param array|null $previousContent 직전 버전 content (변경 요약 기준). null 이면 변경 요약 0 + * @param int|null $createdBy 저장자 ID (null 이면 현재 인증 사용자) * @return TemplateLayoutVersion 생성된 버전 */ - public function saveVersion(int $layoutId, array $content, ?array $previousContent = null): TemplateLayoutVersion + public function saveVersion(int $layoutId, array $content, ?array $previousContent = null, ?int $createdBy = null): TemplateLayoutVersion { $nextVersion = $this->getNextVersion($layoutId); @@ -44,6 +46,8 @@ class LayoutVersionRepository implements LayoutVersionRepositoryInterface 'version' => $nextVersion, 'content' => $content, 'changes_summary' => $changesSummary, + // 저장자 — 버전 목록의 「저장자」 표시 근거. 종전엔 기록하지 않아 항상 「알 수 없음」이었다. + 'created_by' => $createdBy ?? Auth::id(), ]); } @@ -109,9 +113,11 @@ class LayoutVersionRepository implements LayoutVersionRepositoryInterface $layout = TemplateLayout::findOrFail($layoutId); $currentContent = $layout->content; - // 3. 레이아웃을 복원할 content로 업데이트 + // 3. 레이아웃을 복원할 content로 업데이트 — lock_version 도 올린다. 복원 직전 화면을 열어 둔 + // 다른 편집기가 옛 lock 으로 저장하면 409 가 나야 복원 결과가 조용히 덮이지 않는다. $layout->update([ 'content' => $versionToRestore->content, + 'lock_version' => ((int) ($layout->lock_version ?? 0)) + 1, ]); // 4. 복원 결과를 새 버전으로 저장 — content 는 복원된 내용(versionToRestore), diff --git a/app/Rules/NoExternalUrls.php b/app/Rules/NoExternalUrls.php index 891da8ce..802df1f3 100644 --- a/app/Rules/NoExternalUrls.php +++ b/app/Rules/NoExternalUrls.php @@ -2,6 +2,7 @@ namespace App\Rules; +use App\Support\SiteAssetHosts; use Closure; use Illuminate\Contracts\Validation\ValidationRule; @@ -20,9 +21,22 @@ use Illuminate\Contracts\Validation\ValidationRule; * 차단합니다. 반면 state/computed 는 데이터 값이며, 실제 위험은 그 값이 바인딩되는 * sink(컴포넌트 prop = img src 등)에서 발생하고 그 sink 는 이미 여기서 검사됩니다 — * 예시/안내용 URL 을 담는 정당한 용례를 깨지 않기 위해 데이터 계층은 재차단하지 않습니다. + * + * 외부의 기준: 사이트 자기 host(`app.url`)와 운영자가 선언한 공개 자산 디스크의 host + * (`SiteAssetHosts`)는 외부가 아닙니다. 레이아웃 첨부 API 가 스스로 발급하는 주소(프록시 + * 서빙 URL·직접 URL)가 그 host 를 쓰므로, 여기서 차단하면 image 위젯으로 올린 파일이 + * 업로드는 되고 저장은 422 가 됩니다. 판정은 정규화 뒤 host 등가 비교이며 protocol-relative + * 와 http/https 밖의 스킴은 host 가 같아도 종전대로 차단합니다. */ class NoExternalUrls implements ValidationRule { + /** + * 사이트 자산 host 목록 (검증 1회당 1회 해석 후 캐시) + * + * @var array|null + */ + private ?array $siteAssetHosts = null; + /** * 차단할 위험 URI 스킴 목록 */ @@ -203,6 +217,12 @@ class NoExternalUrls implements ValidationRule */ private function checkForDangerousUrl(string $value, string $path, Closure $fail): void { + // 사이트 자기 host·선언된 공개 자산 디스크 host 의 http(s) 절대 URL 은 외부가 아니다. + // 위험 스킴·protocol-relative 는 이 판정의 대상이 아니므로 아래 검사가 그대로 적용된다. + if (SiteAssetHosts::isSiteAssetUrl($value, $this->siteAssetHosts())) { + return; + } + $lowerValue = strtolower(trim($value)); foreach (self::DANGEROUS_SCHEMES as $scheme) { @@ -225,6 +245,16 @@ class NoExternalUrls implements ValidationRule } } + /** + * 사이트 자산 host 목록을 반환합니다 (검증 1회당 1회 해석 후 캐시). + * + * @return array 소문자 host 목록 + */ + private function siteAssetHosts(): array + { + return $this->siteAssetHosts ??= SiteAssetHosts::hosts(); + } + /** * 스킴별 에러 메시지 출력 */ diff --git a/app/Seo/SeoCacheBounds.php b/app/Seo/SeoCacheBounds.php new file mode 100644 index 00000000..9eca5642 --- /dev/null +++ b/app/Seo/SeoCacheBounds.php @@ -0,0 +1,187 @@ + $query 요청 쿼리 파라미터 + * @return array|null 정규화된 파라미터 (상한 초과 시 null) + */ + public static function normalizeQuery(array $query): ?array + { + foreach (self::SYSTEM_QUERY_PARAMS as $param) { + unset($query[$param]); + } + + if (count($query) > self::limit('max_query_params', 10)) { + return null; + } + + ksort($query); + + if (strlen(http_build_query($query)) > self::limit('max_query_length', 512)) { + return null; + } + + return $query; + } + + /** + * 이 IP 가 미스 렌더를 더 수행할 수 있는지 판정합니다. + * + * @param string $ip 요청 IP + * @return bool 렌더 허용 여부 + */ + public static function renderAllowed(string $ip): bool + { + return ! RateLimiter::tooManyAttempts( + 'seo-render:'.$ip, + self::limit('render_misses_per_minute', 60) + ); + } + + /** + * 미스 렌더 1건을 예산에서 차감합니다. + * + * @param string $ip 요청 IP + */ + public static function recordRender(string $ip): void + { + RateLimiter::hit('seo-render:'.$ip, 60); + } + + /** + * 차감한 미스 렌더 1건을 예산에 되돌립니다. + * + * 렌더러가 "그릴 게 없음"(null)으로 돌아온 요청 — 미라우트 404, SEO 비활성 화면 — 은 + * 캐시에 남지 않아 올 때마다 다시 예산을 쓴다. 봇은 예전에 있던 죽은 주소를 오래 다시 + * 긁으므로, 그 요청까지 세면 정상 페이지의 예산이 죽은 주소에 소진된다. 렌더 도중 예외는 + * 비용을 이미 치른 것이라 되돌리지 않는다. + * + * @param string $ip 요청 IP + */ + public static function refundRender(string $ip): void + { + RateLimiter::decrement('seo-render:'.$ip, 60); + } + + /** + * 이 IP 의 요청을 통계로 기록할 수 있는지 판정합니다. + * + * 통계 테이블이 새로운 증식 축이 되지 않도록 기록 자체에도 상한을 둔다. + * + * @param string $ip 요청 IP + * @return bool 기록 허용 여부 + */ + public static function statsAllowed(string $ip): bool + { + return ! RateLimiter::tooManyAttempts( + 'seo-stats:'.$ip, + self::limit('stats_records_per_minute', 300) + ); + } + + /** + * 통계 기록 1건을 예산에서 차감합니다. + * + * @param string $ip 요청 IP + */ + public static function recordStat(string $ip): void + { + RateLimiter::hit('seo-stats:'.$ip, 60); + } + + /** + * 새 URL 을 캐시에 저장할 수 있는지 판정합니다. + * + * 이미 인덱스에 있는 키의 **갱신**은 이 판정을 거치지 않는다(호출측 책임) — 저장 규모가 + * 늘지 않기 때문이다. + * + * 경로당 변종은 **언어별로** 센다. 인덱스 항목은 url|locale 별이라 경로만 보고 합산하면 + * 언어 수만큼 실효 상한이 줄어, 다국어 사이트의 목록 뒤쪽 페이지가 언어마다 캐시에서 빠진다. + * + * @param array> $index 현재 캐시 인덱스 + * @param string $url 저장하려는 URL (경로 + 정규화 쿼리) + * @param string $locale 저장하려는 로케일 + * @return bool 저장 허용 여부 + */ + public static function canStore(array $index, string $url, string $locale): bool + { + if (count($index) >= self::limit('max_entries', 20000)) { + return false; + } + + $path = self::pathOf($url); + $variants = 0; + + foreach ($index as $entry) { + if (($entry['locale'] ?? null) !== $locale) { + continue; + } + + if (self::pathOf((string) ($entry['url'] ?? '')) === $path) { + $variants++; + } + } + + return $variants < self::limit('max_variants_per_path', 50); + } + + /** + * URL 에서 경로 부분만 잘라냅니다. + * + * @param string $url 캐시 URL (`/path?a=1` 형태) + * @return string 경로 + */ + private static function pathOf(string $url): string + { + $path = parse_url($url, PHP_URL_PATH); + + return is_string($path) ? $path : $url; + } +} diff --git a/app/Seo/SeoCacheManager.php b/app/Seo/SeoCacheManager.php index 0df68d37..c117baaf 100644 --- a/app/Seo/SeoCacheManager.php +++ b/app/Seo/SeoCacheManager.php @@ -18,20 +18,57 @@ class SeoCacheManager implements SeoCacheManagerInterface */ private const INDEX_KEY = 'seo.cached_urls'; + /** + * 저장 상한에서 인덱스를 정리한 시각을 남기는 표식 키 + */ + private const PRUNE_MARK_KEY = 'seo.index_pruned_at'; + + /** + * 저장 상한에서 인덱스 정리를 다시 시도하기까지의 최소 간격 (초) + */ + private const PRUNE_INTERVAL_SECONDS = 60; + public function __construct(private readonly CacheInterface $cache) {} /** * {@inheritdoc} */ public function get(string $url, string $locale): ?string + { + return $this->getEntry($url, $locale)['html'] ?? null; + } + + /** + * 캐시 항목(HTML + 레이아웃명)을 조회합니다. + * + * 페이지는 레이아웃명과 함께 저장된다 — 캐시 적중 경로는 렌더러를 거치지 않아 요청 + * 속성에 레이아웃명이 없고, 통계를 화면별로 귀속하려면 항목이 그것을 알아야 한다. + * 이전 버전이 문자열로만 저장한 항목은 레이아웃명 없이 그대로 읽힌다 — 배포 직후 + * 살아 있는 캐시를 버리지 않는다. + * + * @param string $url URL + * @param string $locale 로케일 + * @return array{html: string, layout: string|null}|null 캐시 항목 (없으면 null) + */ + public function getEntry(string $url, string $locale): ?array { if (! $this->isEnabled()) { return null; } - $key = $this->buildKey($url, $locale); + $value = $this->cache->get($this->buildKey($url, $locale)); - return $this->cache->get($key); + if (is_string($value)) { + return ['html' => $value, 'layout' => null]; + } + + if (is_array($value) && is_string($value['html'] ?? null)) { + $layout = $value['layout'] ?? null; + + return ['html' => $value['html'], 'layout' => is_string($layout) ? $layout : null]; + } + + return null; } /** @@ -39,17 +76,7 @@ class SeoCacheManager implements SeoCacheManagerInterface */ public function put(string $url, string $locale, string $html): void { - if (! $this->isEnabled()) { - return; - } - - $key = $this->buildKey($url, $locale); - $ttl = $this->getCacheTtl(); - - $this->cache->put($key, $html, $ttl); - - // URL 인덱스 업데이트 - $this->addToIndex($url, $locale, $key); + $this->storePage($url, $locale, $html, null); } /** @@ -148,26 +175,6 @@ class SeoCacheManager implements SeoCacheManagerInterface return SeoCacheSettings::pageCacheTtl(); } - /** - * URL 인덱스에 항목을 추가합니다. - * - * @param string $url URL - * @param string $locale 로케일 - * @param string $key 캐시 키 - */ - private function addToIndex(string $url, string $locale, string $key): void - { - $index = $this->getIndex(); - $index[$key] = [ - 'url' => $url, - 'locale' => $locale, - 'key' => $key, - 'cached_at' => now()->toIso8601String(), - ]; - - $this->cache->put(self::INDEX_KEY, $index, 86400 * 30); // 30일 - } - /** * 캐시 인덱스를 조회합니다. */ @@ -177,9 +184,34 @@ class SeoCacheManager implements SeoCacheManagerInterface } /** - * 유효한 캐시만 남겨 인덱스를 재구성합니다. + * 저장 상한에서 인덱스 정리를 시도해도 되는지 판정하고, 시도한다면 표식을 남깁니다. + * + * 정리는 인덱스 전체를 훑는다(항목마다 캐시 조회). 살아 있는 항목만으로 상한에 닿은 + * 경로는 저장 시도마다 그 스캔을 되풀이하게 되고, 그 빈도는 봇 미스 렌더 예산만큼이다 + * — 정리해도 자리가 나지 않는 상태에서 비용만 곱해진다. 그래서 간격으로 묶는다. + * + * @return bool 정리를 수행해도 되면 true */ - private function rebuildIndex(): void + private function shouldAttemptPrune(): bool + { + if ($this->cache->has(self::PRUNE_MARK_KEY)) { + return false; + } + + $this->cache->put(self::PRUNE_MARK_KEY, true, self::PRUNE_INTERVAL_SECONDS); + + return true; + } + + /** + * 유효한 캐시만 남겨 인덱스를 재구성하고 그 결과를 반환합니다. + * + * 페이지는 TTL 로 사라지지만 인덱스 항목은 남는다 — 이 메서드가 그 차이를 메우는 + * 유일한 지점이므로, 인덱스를 근거로 판정하는 쪽(저장 상한)은 판정 전에 여기를 거친다. + * + * @return array> 정리된 인덱스 + */ + private function rebuildIndex(): array { $index = $this->getIndex(); $validIndex = []; @@ -191,6 +223,8 @@ class SeoCacheManager implements SeoCacheManagerInterface } $this->cache->put(self::INDEX_KEY, $validIndex, 86400 * 30); + + return $validIndex; } /** @@ -210,32 +244,80 @@ class SeoCacheManager implements SeoCacheManagerInterface /** * 캐시 저장 시 레이아웃 정보를 함께 저장합니다. * + * 인덱스는 단일 캐시 항목에 전체 변종 배열을 담고 저장마다 통째로 다시 쓴다. 항목 수에 + * 상한이 없으면 쿼리만 바꾼 반복 요청이 그 배열을 무한히 키운다 — 그래서 **새 URL** 은 + * 경로당 변종 수와 전체 항목 수 상한 안에서만 저장한다. 이미 인덱스에 있는 키의 갱신은 + * 저장 규모를 늘리지 않으므로 상한과 무관하게 쓴다. + * * @param string $url URL * @param string $locale 로케일 * @param string $html HTML * @param string $layoutName 레이아웃명 */ public function putWithLayout(string $url, string $locale, string $html, string $layoutName): void + { + $this->storePage($url, $locale, $html, $layoutName); + } + + /** + * 페이지와 인덱스 항목을 저장합니다 (`put`/`putWithLayout` 공통 경로). + * + * 두 공개 메서드는 같은 자원(페이지 캐시 + 인덱스)을 쓰므로 저장 규모 상한도 같아야 + * 한다 — 한쪽에만 두면 다른 쪽이 우회로가 되고, 인터페이스는 확장에 열려 있어 + * "지금 호출부가 없다" 는 방어가 되지 않는다. + * + * @param string $url URL (경로 + 정규화 쿼리) + * @param string $locale 로케일 + * @param string $html 저장할 HTML + * @param string|null $layoutName 레이아웃명 (없으면 인덱스에 기록하지 않음) + */ + private function storePage(string $url, string $locale, string $html, ?string $layoutName): void { if (! $this->isEnabled()) { return; } $key = $this->buildKey($url, $locale); - $ttl = $this->getCacheTtl(); - - $this->cache->put($key, $html, $ttl); - - // 레이아웃 정보 포함하여 인덱스 업데이트 $index = $this->getIndex(); - $index[$key] = [ + + if (! isset($index[$key])) { + // 상한이 세는 인덱스에는 **페이지가 이미 만료된** 항목이 섞인다 — 인덱스는 + // 페이지보다 훨씬 오래 살고(30일 vs 기본 2시간) 저장마다 수명이 갱신되며 + // 스스로 줄지 않는다. 여기서 한 번 정리하지 않으면 상한이 "지금 저장된 양"이 + // 아니라 "과거에 저장한 적이 있는 양"을 재게 되어, 한 번 닿은 경로는 실제 + // 캐시가 비어도 영영 저장이 막힌다(상한이 아니라 일방향 래치가 된다). + if (! SeoCacheBounds::canStore($index, $url, $locale) && $this->shouldAttemptPrune()) { + $index = $this->rebuildIndex(); + } + + if (! SeoCacheBounds::canStore($index, $url, $locale)) { + Log::debug('[SEO] 캐시 저장 상한에 도달해 저장하지 않습니다', [ + 'url' => $url, + 'locale' => $locale, + 'entries' => count($index), + ]); + + return; + } + } + + // 레이아웃명을 페이지와 함께 둔다 — 적중 경로가 통계를 화면별로 귀속할 유일한 출처다. + $this->cache->put($key, ['html' => $html, 'layout' => $layoutName], $this->getCacheTtl()); + + $entry = [ 'url' => $url, 'locale' => $locale, 'key' => $key, - 'layout' => $layoutName, - 'cached_at' => now()->toIso8601String(), ]; + if ($layoutName !== null) { + $entry['layout'] = $layoutName; + } + + $entry['cached_at'] = now()->toIso8601String(); + + $index[$key] = $entry; + $this->cache->put(self::INDEX_KEY, $index, 86400 * 30); } } diff --git a/app/Seo/SeoMiddleware.php b/app/Seo/SeoMiddleware.php index 0a203c38..6f92ba8c 100644 --- a/app/Seo/SeoMiddleware.php +++ b/app/Seo/SeoMiddleware.php @@ -15,10 +15,15 @@ class SeoMiddleware private readonly BotDetector $botDetector, private readonly SeoCacheManagerInterface $cacheManager, private readonly SeoRendererInterface $renderer, + private readonly SeoCacheStatsService $statsService, ) {} /** * 검색 봇 요청 시 SEO HTML을 반환합니다. + * + * @param Request $request HTTP 요청 + * @param Closure $next 다음 미들웨어 + * @return Response SEO HTML(HIT/MISS) 또는 SPA 폴백(BYPASS 포함) */ public function handle(Request $request, Closure $next): Response { @@ -56,18 +61,44 @@ class SeoMiddleware $request->attributes->set('seo_default_locale', $defaultLocale); app()->setLocale($locale); - // 캐시 키용 URL 생성 (경로 + 쿼리 파라미터, locale 제외) - $cacheUrl = $this->buildCacheUrl($request); + // 캐시 키용 쿼리 정규화 — 정규화할 수 없을 만큼 큰 쿼리는 색인 대상이 아니다. + // 그런 URL 까지 렌더·저장하면 물음표 뒤 값만 바꾼 반복 요청이 무한한 미스가 된다. + $normalizedQuery = SeoCacheBounds::normalizeQuery($request->query()); - // 캐시 확인 - $cachedHtml = $this->cacheManager->get($cacheUrl, $locale); - if ($cachedHtml !== null) { - return response($cachedHtml, 200, [ + if ($normalizedQuery === null) { + return $this->bypass($request, $next); + } + + $ip = (string) $request->ip(); + + // 캐시 키용 URL 생성 (경로 + 정규화된 쿼리) + $cacheUrl = $this->buildCacheUrl($request, $normalizedQuery); + + // 캐시 확인 — 적중은 비용이 없으므로 렌더 예산과 무관하게 서빙한다 + $entry = $this->readEntry($cacheUrl, $locale); + if ($entry !== null) { + $this->recordStat($ip, fn () => $this->statsService->recordHit( + $cacheUrl, + $locale, + $entry['layout'] ?: null + )); + + return response($entry['html'], 200, [ 'Content-Type' => 'text/html; charset=utf-8', 'X-SEO-Cache' => 'HIT', ]); } + // 미스 렌더는 IP 당 분당 예산 안에서만 — 초과분은 오류가 아니라 SPA 를 받는다. + // 봇에게 429 를 주면 그 URL 이 색인에서 빠지므로 차단이 곧 손해가 된다. + if (! SeoCacheBounds::renderAllowed($ip)) { + return $this->bypass($request, $next); + } + + SeoCacheBounds::recordRender($ip); + + $startedAt = microtime(true); + // 렌더링 try { $html = $this->renderer->render($request); @@ -103,6 +134,10 @@ class SeoMiddleware // 렌더링 실패 시 SPA fallback if ($html === null) { + // "그릴 게 없음" 은 비용을 치르지 않았다 — 되돌리지 않으면 캐시에 남지 않는 죽은 + // 주소 재크롤이 올 때마다 정상 페이지의 예산을 태운다. + SeoCacheBounds::refundRender($ip); + return $next($request); } @@ -110,6 +145,14 @@ class SeoMiddleware $layoutName = $request->attributes->get('seo_layout_name', ''); $this->cacheManager->putWithLayout($cacheUrl, $locale, $html, $layoutName); + $this->recordStat($ip, fn () => $this->statsService->recordMiss( + $cacheUrl, + $locale, + $layoutName ?: null, + null, + (int) round((microtime(true) - $startedAt) * 1000) + )); + return response($html, 200, [ 'Content-Type' => 'text/html; charset=utf-8', 'X-SEO-Cache' => 'MISS', @@ -117,30 +160,84 @@ class SeoMiddleware } /** - * 캐시 키용 URL을 생성합니다. + * 캐시 항목(HTML + 레이아웃명)을 읽습니다. * - * 경로 + 쿼리 파라미터를 포함하되, locale 파라미터는 제외합니다. - * 쿼리 파라미터를 키 순서로 정렬하여 동일 파라미터 조합이 같은 캐시 키를 생성하도록 합니다. + * 적중 경로는 렌더러를 거치지 않아 요청 속성에 레이아웃명이 없다 — 통계를 화면별로 + * 귀속하려면 캐시 항목이 그것을 알아야 한다. 인터페이스(`get`)는 HTML 만 돌려주므로 + * 코어 매니저일 때만 항목 전체를 읽고, 다른 구현이 바인딩된 경우에는 레이아웃명 없이 + * HTML 만 쓴다(그 통계는 레이아웃 미상으로 귀속된다). + * + * @param string $cacheUrl 캐시 키용 URL + * @param string $locale 로케일 + * @return array{html: string, layout: string|null}|null 캐시 항목 (없으면 null) + */ + private function readEntry(string $cacheUrl, string $locale): ?array + { + if ($this->cacheManager instanceof SeoCacheManager) { + return $this->cacheManager->getEntry($cacheUrl, $locale); + } + + $html = $this->cacheManager->get($cacheUrl, $locale); + + return $html === null ? null : ['html' => $html, 'layout' => null]; + } + + /** + * 캐시·렌더를 건너뛰고 SPA 응답을 돌려줍니다. + * + * 상한 초과는 오류가 아니라 "이 요청은 봇 렌더 대상이 아니다" 라는 판정이다. 헤더는 + * 운영 진단의 유일한 통로다 — 응답 본문만으로는 일반 SPA 폴백과 구분되지 않는다. * * @param Request $request HTTP 요청 + * @param Closure $next 다음 미들웨어 + * @return Response SPA 응답 + */ + private function bypass(Request $request, Closure $next): Response + { + $response = $next($request); + $response->headers->set('X-SEO-Cache', 'BYPASS'); + + return $response; + } + + /** + * 통계 기록을 IP 당 상한 안에서만 수행합니다. + * + * 기록 자체에 상한이 없으면 통계 테이블이 새로운 증식 축이 된다. + * + * @param string $ip 요청 IP + * @param callable $record 기록 동작 + */ + private function recordStat(string $ip, callable $record): void + { + if (! SeoCacheBounds::statsAllowed($ip)) { + return; + } + + SeoCacheBounds::recordStat($ip); + $record(); + } + + /** + * 캐시 키용 URL을 생성합니다. + * + * 경로 + 정규화된 쿼리 파라미터로 구성한다. 정규화는 시스템 파라미터(`locale`, + * `_escaped_fragment_`)를 제거하고 키 순서로 정렬하므로, 같은 조합은 순서와 무관하게 + * 같은 키가 되고 봇 렌더 표식만 다른 두 URL 이 두 벌로 저장되지 않는다. + * + * @param Request $request HTTP 요청 + * @param array $normalizedQuery 정규화된 쿼리 파라미터 * @return string 캐시 키용 URL */ - private function buildCacheUrl(Request $request): string + private function buildCacheUrl(Request $request, array $normalizedQuery): string { $path = $request->getPathInfo(); - // locale을 제외한 쿼리 파라미터 추출 - $query = $request->query(); - unset($query['locale']); - - if (empty($query)) { + if ($normalizedQuery === []) { return $path; } - // 키 순서 정렬 (동일 파라미터 조합 → 동일 캐시 키 보장) - ksort($query); - - return $path.'?'.http_build_query($query); + return $path.'?'.http_build_query($normalizedQuery); } /** diff --git a/app/Services/AuthService.php b/app/Services/AuthService.php index dea93863..c57dea68 100644 --- a/app/Services/AuthService.php +++ b/app/Services/AuthService.php @@ -2,6 +2,7 @@ namespace App\Services; +use App\Contracts\Repositories\IdentityVerificationLogRepositoryInterface; use App\Contracts\Repositories\PasswordResetTokenRepositoryInterface; use App\Contracts\Repositories\RoleRepositoryInterface; use App\Contracts\Repositories\UserConsentRepositoryInterface; @@ -11,6 +12,7 @@ use App\Enums\IdentityVerificationPurpose; use App\Enums\IdentityVerificationStatus; use App\Enums\UserStatus; use App\Exceptions\Auth\AccountLockedException; +use App\Exceptions\Auth\TwoFactorDeliveryFailedException; use App\Extension\HookManager; use App\Models\User; use Carbon\Carbon; @@ -30,6 +32,7 @@ class AuthService private UserConsentRepositoryInterface $userConsentRepository, private PasswordResetTokenRepositoryInterface $passwordResetTokenRepository, private IdentityPolicyService $policyService, + private IdentityVerificationLogRepositoryInterface $identityLogRepository, ) {} /** @@ -228,9 +231,9 @@ class AuthService 'provider_id' => $challenge->providerId, ]); - throw ValidationException::withMessages([ - 'email' => [__('auth.two_factor_delivery_failed')], - ]); + // 자격 증명은 올바르다 — 401 로 뭉개면 사용자는 비밀번호를 의심하며 같은 실패를 + // 반복하고, 운영자는 메일 설정이 깨진 사실을 알 방법이 없다. + throw new TwoFactorDeliveryFailedException; } HookManager::doAction('core.auth.two_factor_requested', $user, [ @@ -298,6 +301,83 @@ class AuthService return $this->issueLoginSession($user, (string) $user->email); } + /** + * 2단계 인증 코드를 재발송합니다. + * + * 아직 사용되지 않은 challenge 만 재발송 대상입니다. 기존 challenge 를 취소하고 새로 + * 발행하므로, 재발송 이후에는 앞서 받은 인증번호가 통하지 않습니다 — 재발송을 남겨 두면 + * 유효한 코드가 여러 개 동시에 살아 있어 대입 시도의 표적이 넓어집니다. + * + * 해석된 사용자는 응답 페이로드에 실리지 않고 out 파라미터로만 올린다 — 관리자 경로가 + * 그 사용자로 등급을 판정해야 하지만, 사용자 경로가 그대로 내보내면 모델이 응답에 새는다. + * + * @param string $challengeId 비밀번호 확인 단계가 돌려준 challenge UUID + * @param User|null $resolvedUser (out) challenge 가 식별한 사용자 + * @return array{two_factor_required: bool, challenge_id: string, provider_id: string, expires_at: mixed} 새 challenge 정보 + * + * @throws ValidationException challenge 가 재발송 대상이 아닐 때 + * @throws AccountLockedException 계정이 잠겨 있을 때 + * @throws TwoFactorDeliveryFailedException 인증번호를 보내지 못했을 때 + */ + public function resendTwoFactorChallenge(string $challengeId, ?User &$resolvedUser = null): array + { + $log = $this->identityLogRepository->findById($challengeId); + + // 존재하지 않음 / 다른 용도 / 이미 검증·취소·실패·만료 — 전부 같은 응답으로 답한다. + // 사유를 구분해 주면 challenge id 를 넣어 보며 상태를 캐낼 수 있다. + $resendable = [ + IdentityVerificationStatus::Requested->value, + IdentityVerificationStatus::Sent->value, + ]; + + if ($log === null + || $log->purpose !== IdentityVerificationPurpose::Login->value + || ! in_array($log->status->value, $resendable, true) + || ($log->expires_at !== null && $log->expires_at->isPast()) + ) { + throw ValidationException::withMessages([ + 'challenge_id' => [__('auth.two_factor_invalid_challenge')], + ]); + } + + $user = $log->user_id === null ? null : $this->userRepository->findById((int) $log->user_id); + + if (! $user || $user->status !== UserStatus::Active->value) { + throw ValidationException::withMessages([ + 'challenge_id' => [__('auth.two_factor_invalid_challenge')], + ]); + } + + // challenge 발급 이후에 잠겼을 수 있다 — 재발송도 잠긴 계정에는 코드를 보내지 않는다. + $this->assertNotLocked($user); + + $resolvedUser = $user; + + app(IdentityVerificationService::class)->cancel($log->id); + + return $this->startTwoFactorChallenge($user, (string) $user->email); + } + + /** + * 이미 발급된 세션(토큰 + web 세션)을 되돌립니다. + * + * 2단계 인증 완료는 코드 확인 시점에 토큰을 발급하므로, 그 뒤에 권한 검사로 거절하는 + * 경로는 발급분을 반드시 회수해야 합니다. `logout()` 은 `currentAccessToken()` 에 + * 의존해 이 시점(요청 자체는 미인증)에는 아무 일도 하지 않습니다. + * + * @param User $user 대상 사용자 + * @param string $plainTextToken 방금 발급한 평문 토큰 ({id}|{token} 형식) + */ + public function revokeIssuedSession(User $user, string $plainTextToken): void + { + PersonalAccessToken::findToken($plainTextToken)?->delete(); + + // completeTwoFactor() 의 Auth::login() 이 연 세션도 함께 닫는다. + if (request()->hasSession() && request()->session()->isStarted() && Auth::guard('web')->check()) { + Auth::guard('web')->logout(); + } + } + /** * 토큰을 발급하고 로그인 완료 훅을 실행합니다. * diff --git a/app/Services/CoreUpdateService.php b/app/Services/CoreUpdateService.php index 76d1d7d9..05db4473 100644 --- a/app/Services/CoreUpdateService.php +++ b/app/Services/CoreUpdateService.php @@ -23,6 +23,7 @@ use App\Extension\Vendor\VendorInstallContext; use App\Extension\Vendor\VendorInstallResult; use App\Extension\Vendor\VendorMode; use App\Extension\Vendor\VendorResolver; +use App\Support\PackageManifestCacheHelper; use Database\Seeders\IdentityMessageDefinitionSeeder; use Database\Seeders\IdentityPolicySeeder; use Database\Seeders\NotificationDefinitionSeeder; @@ -1835,6 +1836,20 @@ class CoreUpdateService } File::put($envPath, $content); + + // 프로세스 환경도 함께 갱신한다. + // + // 부모가 config 캐시 **없이** 부팅했다면 Dotenv 가 `.env` 를 읽어 `$_ENV['APP_VERSION']` 에 + // 이전 버전을 채워 두었고, Laravel 의 env 저장소는 불변(immutable)이라 뒤이어 부팅하는 + // 프로세스가 `.env` 를 다시 읽어도 그 값을 덮지 않는다. 그래서 Step 11 의 `config:cache` 가 + // 굽는 `bootstrap/cache/config.php` 에 **이전 버전**이 박제되고, 이후 모든 웹 요청이 옛 버전으로 + // 판정한다 — 새 코어를 요구하는 확장이 `incompatible_core` 로 꺼지는 경로다 + // (2026-09-07 실측: `config:clear` 후 7.0.10 → 7.0.11 업데이트가 config 캐시에 7.0.10 을 구웠다). + // + // 캐시 부팅에서는 Dotenv 가 아예 돌지 않아 이 값이 비어 있을 수 있으므로 세 채널 모두 세운다. + $_ENV['APP_VERSION'] = $version; + $_SERVER['APP_VERSION'] = $version; + putenv('APP_VERSION='.$version); } /** @@ -2074,7 +2089,8 @@ class CoreUpdateService * 모든 캐시를 초기화하고 패키지 목록을 재생성합니다. * * vendor 교체 후 bootstrap/cache의 컴파일 캐시가 stale 상태일 수 있으므로 - * services.php/packages.php 삭제 후 package:discover로 재생성합니다. + * services.php/packages.php 삭제 후 package:discover로 재생성합니다 + * (`PackageManifestCacheHelper::rebuild()` — spawn 직전 선정리와 같은 삭제 로직을 공유). * 이는 composer install의 post-autoload-dump 후속 작업(clearCompiled + package:discover)을 재현합니다. */ public function clearAllCaches(): void @@ -2085,14 +2101,10 @@ class CoreUpdateService Artisan::call('route:clear'); Artisan::call('view:clear'); - // 2. 컴파일 캐시 삭제 (composer postAutoloadDump → clearCompiled 재현) - // services.php, packages.php가 교체 전 vendor를 참조할 수 있음 - $app = app(); - @unlink($app->getCachedServicesPath()); - @unlink($app->getCachedPackagesPath()); - - // 3. 현재 vendor 기반으로 packages.php 재생성 - Artisan::call('package:discover'); + // 2~3. 컴파일 캐시 삭제 후 현재 vendor 기반으로 재생성 + // (composer postAutoloadDump → clearCompiled + package:discover 재현). + // services.php, packages.php 가 교체 전 vendor 를 참조할 수 있다. + PackageManifestCacheHelper::rebuild(); // 4. 확장 오토로드 재생성 (코어 업데이트로 _bundled 변경 가능) Artisan::call('extension:update-autoload'); @@ -2108,6 +2120,29 @@ class CoreUpdateService } } + /** + * 상주 중인 큐 워커에 정상 종료 후 재시작 신호를 보냅니다. + * + * 큐 워커는 부팅이 한 번뿐이라 코어 코드·config·확장 목록을 기동 시점 상태로 물고 있다. + * 코어 업데이트가 파일을 전부 교체해도 워커는 옛 코드로 잡을 계속 처리하며, 그 사실이 + * 오류로 드러나지 않는다 — 운영자가 워커를 손수 재시작할 때까지 조용히 어긋난 채 돈다. + * + * 캐시가 새 코드 기준으로 정리된 뒤(`clearAllCaches()` 직후) 호출해야 워커가 새 캐시로 + * 재기동한다. 신호 전송 실패는 업데이트 결과를 되돌리지 않는다 (경고 로깅 후 계속). + * + * @return void + */ + public function signalQueueRestart(): void + { + try { + Artisan::call('queue:restart'); + } catch (\Throwable $e) { + Log::channel('upgrade')->warning('queue:restart 실행 실패 (업데이트는 계속 진행)', [ + 'error' => $e->getMessage(), + ]); + } + } + /** * 코어 업그레이드 컨텍스트의 번들 확장 업데이트 감지. * diff --git a/app/Services/ExtensionBundleService.php b/app/Services/ExtensionBundleService.php index ec7b51ce..09af7245 100644 --- a/app/Services/ExtensionBundleService.php +++ b/app/Services/ExtensionBundleService.php @@ -9,6 +9,8 @@ use App\Extension\Traits\ClearsTemplateCaches; use App\Http\View\Composers\TemplateComposer; use App\Support\AssetCssUrlRewriter; use App\Support\AssetUrl; +use Illuminate\Contracts\Cache\LockTimeoutException; +use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\Log; /** @@ -49,6 +51,21 @@ class ExtensionBundleService */ private const TEMP_BUNDLE_STALE_SECONDS = 600; + /** + * 캐시 미스 빌드를 (type, kind, version) 단위로 수렴시키는 잠금 키 접두사. + */ + private const BUILD_LOCK_PREFIX = 'ext-bundles.build.'; + + /** + * 빌드 잠금의 보유 상한 (초). 좀비 잠금 방지용이며 실제 빌드는 밀리초 단위다. + */ + private const BUILD_LOCK_TTL_SECONDS = 30; + + /** + * 다른 프로세스의 빌드를 기다리는 상한 (초). 초과하면 잠금 없이 각자 빌드한다. + */ + private const BUILD_LOCK_WAIT_SECONDS = 5; + /** * 서비스 주입 * @@ -137,39 +154,7 @@ class ExtensionBundleService */ public function buildJsBundle(string $type): string { - $ordered = $this->getOrderedGlobalAssetPaths($type); - $isProduction = app()->environment('production'); - $segments = []; - - foreach ($ordered as $identifier => $paths) { - if (empty($paths['jsAbsPath'])) { - continue; - } - - try { - $content = @file_get_contents($paths['jsAbsPath']); - - if ($content === false) { - Log::warning('확장 JS 번들 병합: 파일 읽기 실패, 해당 확장 skip', [ - 'type' => $type, - 'identifier' => $identifier, - 'path' => $paths['jsAbsPath'], - ]); - - continue; - } - - $segments[] = $this->processJsSourceMap($content, $type, $identifier, $isProduction); - } catch (\Throwable $e) { - Log::warning('확장 JS 번들 병합 중 오류, 해당 확장 skip', [ - 'type' => $type, - 'identifier' => $identifier, - 'error' => $e->getMessage(), - ]); - } - } - - return implode("\n;\n", $segments); + return $this->mergeBundle($type, 'js')['content']; } /** @@ -186,12 +171,265 @@ class ExtensionBundleService * @return string 병합된 CSS (활성 global 에셋이 없으면 빈 문자열) */ public function buildCssBundle(string $type): string + { + return $this->mergeBundle($type, 'css')['content']; + } + + /** + * 캐시된 번들 파일의 절대 경로를 반환합니다(없으면 build → 원자적 write). + * + * 파일명에 version 을 포함(`{type}.{version}.{js|css}`)하므로 활성 조합이 + * 바뀌어 version 이 bump 되면 새 파일명으로 자연 무효화된다. 프로덕션에서만 + * 디스크 캐시하며, 비프로덕션(dev/watch)에서는 캐시하지 않고 매 요청 build 해 + * rebuild 를 즉시 반영한다. + * + * 프로덕션에서 **캐시 존재 확인이 빌드보다 먼저** 온다. 캐시 키는 인자만으로 + * 계산되므로 빌드가 필요 없는데, 빌드를 앞세우면 캐시가 있어도 요청마다 활성 확장을 + * 열거하고 산출물을 전부 읽는다. 응답은 정상 200 이라 그 반복은 타이밍 말고는 드러나지 + * 않고, 원본이 사라지는 순간에는 멀쩡한 캐시를 두고 빈 경로가 반환되어 503 이 된다. + * + * @param string $type 'module' | 'plugin' + * @param string $kind 'js' | 'css' + * @param int $version 확장 캐시 버전(ClearsTemplateCaches::getExtensionCacheVersion) + * @return string 캐시(또는 방금 build 한) 파일의 절대 경로. 캐시할 수 없으면 빈 문자열. + */ + public function getBundleFilePath(string $type, string $kind, int $version): string + { + $relativeName = $this->bundleFileName($type, $kind, $version); + + // 디스크 캐시는 **최적화**다 — 쓰기 실패가 공개 엔드포인트의 500 이 되면 안 된다. + // `ext-bundles` 디스크는 `throw => true` 라 권한 문제(uid 독점 0700 등)에서 + // `UnableToWriteFile` 이 그대로 올라오고, 그러면 모든 확장의 프론트엔드 JS/CSS 가 + // 통째로 나가지 못한다. 병합 결과는 이미 메모리에 있으므로 그것을 그대로 응답하면 + // 화면은 정상이다 (커밋 63a30ab29 의 AbstractCacheDriver fail-soft 와 같은 원칙). + try { + $storage = $this->bundleStorage(); + + // 비프로덕션은 캐시하지 않고 임시 파일로 매번 build → rebuild 즉시 반영 + if (! app()->environment('production')) { + $content = $this->buildBundleContent($type, $kind); + + // 병합할 에셋이 하나도 없으면 파일을 만들지 않는다(호출측이 빈 문자열로 판단). + if ($content === '') { + return ''; + } + + return $this->writeAtomically($storage, $relativeName, $content, cache: false); + } + + // 프로덕션: 동일 version 캐시가 있으면 빌드 없이 그대로 사용 + if ($storage->exists('', $relativeName)) { + return $storage->getBasePath('').'/'.$relativeName; + } + + return $this->buildAndCacheOnce($storage, $type, $kind, $version, $relativeName); + } catch (\Throwable $e) { + Log::warning('확장 번들 디스크 캐시 실패 — 메모리 병합 결과로 서빙합니다', [ + 'type' => $type, + 'kind' => $kind, + 'version' => $version, + 'error' => $e->getMessage(), + ]); + + return ''; + } + } + + /** + * 캐시 미스에서 한 번만 병합해 캐시 파일을 만들고 그 절대 경로를 반환합니다. + * + * 같은 (type, kind, version) 의 동시 요청은 잠금으로 하나에 수렴시킨다 — 버전 교체 + * 직후에는 캐시가 없는 상태로 요청이 몰리고, 각자 병합하면 그 비용이 워커 수만큼 곱해진다. + * 잠금은 **최적화**이므로 대기 초과·저장소 장애는 실패로 바꾸지 않고 각자 빌드로 폴백한다 + * (정적 게시 잠금 `ext-static.publish.{v}` 와 같은 저장소·같은 규율이며 키가 달라 자기 + * 교착이 없다). + * + * 병합 결과가 비어 있어도 선언한 산출물이 **전부 존재하면**(또는 선언이 0이면) 0바이트 + * 캐시 파일을 만든다. 그래야 정적 게시 대상이 되어 방문자가 웹서버에서 직접 받는다 — + * 만들지 않으면 그 구성의 모든 페이지 로드가 PHP 를 거친다. 캐시하지 않는 것은 둘이다 — + * 선언한 산출물이 **소실**된 경우(호출측의 503 판정을 그대로 유지한다)와 병합 단계에서 + * 확장을 **건너뛴** 경우(그 상태가 굳지 않도록 매 요청 재시도에 맡긴다). + * + * @param CoreStorageDriver $storage 번들 디스크 스토리지 + * @param string $type 'module' | 'plugin' + * @param string $kind 'js' | 'css' + * @param int $version 확장 캐시 버전 + * @param string $relativeName 캐시 파일명 + * @return string 캐시 파일의 절대 경로 (캐시하지 않았으면 빈 문자열) + */ + private function buildAndCacheOnce( + CoreStorageDriver $storage, + string $type, + string $kind, + int $version, + string $relativeName + ): string { + $lock = null; + $acquired = false; + + try { + $lock = Cache::lock(self::BUILD_LOCK_PREFIX."{$type}.{$kind}.{$version}", self::BUILD_LOCK_TTL_SECONDS); + $acquired = (bool) $lock->block(self::BUILD_LOCK_WAIT_SECONDS); + } catch (LockTimeoutException $e) { + // 대기 초과는 정상적인 경합이다 — 흔적만 남기고 각자 빌드한다. + Log::debug('확장 번들 빌드 잠금 대기 초과 — 잠금 없이 병합합니다', [ + 'type' => $type, + 'kind' => $kind, + 'version' => $version, + ]); + } catch (\Throwable $e) { + // 저장소가 잠금을 제공하지 못한다(드라이버 미지원, 캐시 디렉토리 권한 등). + // 번들 서빙 자체를 막지는 않으므로 사유만 남기고 계속한다. + Log::warning('확장 번들 빌드 잠금 획득 불가 — 잠금 없이 병합합니다', [ + 'type' => $type, + 'kind' => $kind, + 'version' => $version, + 'store' => config('cache.default'), + 'error' => $e->getMessage(), + ]); + } + + try { + // 기다리는 동안 다른 프로세스가 완성했을 수 있다 — 병합 전에 다시 본다. + if ($storage->exists('', $relativeName)) { + return $storage->getBasePath('').'/'.$relativeName; + } + + ['content' => $content, 'skipped' => $skipped] = $this->mergeBundle($type, $kind); + + // 건너뛴 확장이 있으면 캐시하지 않는다 — 파일은 존재·판독 가능한데 읽기·치환이 + // 실패한 상태가 캐시로 굳으면 버전 bump 전까지 그 확장 자산이 사라진 채 고정된다. + // 캐시 없이 돌아가면 호출측이 매 요청 다시 병합하므로 원인이 사라지는 순간 회복한다. + // 출하 기본 로그 수준이 error 라 warning 은 기록되지 않는다 — 이 통지가 유일한 흔적이다. + if ($skipped !== []) { + Log::error('확장 번들 캐시 보류 — 병합 단계에서 건너뛴 확장이 있어 캐시하지 않습니다', [ + 'type' => $type, + 'kind' => $kind, + 'version' => $version, + 'skipped' => $skipped, + ]); + + return ''; + } + + // 비었는데 선언한 산출물이 소실이면 캐시하지 않는다 — 배포 중 dist 가 잠깐 빈 + // 장애가 0바이트 캐시로 굳어 정상(빈 200)으로 위장되면 안 된다. + if ($content === '' && $this->findMissingDeclaredAssets($type, $kind) !== []) { + return ''; + } + + return $this->writeAtomically($storage, $relativeName, $content, cache: true); + } finally { + if ($acquired && $lock !== null) { + try { + $lock->release(); + } catch (\Throwable $e) { + // 해제 실패는 TTL 이 정리한다 — 서빙에는 영향이 없다. + } + } + } + } + + /** + * 번들을 서빙할 때 쓸 병합 결과를 반환합니다 (디스크 캐시 실패 시 메모리 폴백용). + * + * @param string $type 'module' | 'plugin' + * @param string $kind 'js' | 'css' + * @return string 병합 결과 (없으면 빈 문자열) + */ + public function buildBundleContent(string $type, string $kind): string + { + return $this->mergeBundle($type, $kind)['content']; + } + + /** + * 병합 결과와 함께 **건너뛴 확장**을 돌려줍니다 (캐시 판정용). + * + * 캐시할지는 결과 문자열만으로 판정할 수 없다 — 파일은 존재·판독 가능한데 읽기나 치환이 + * 실패해 건너뛴 확장은 결과에서 조용히 빠질 뿐이다. 그 상태가 캐시로 굳으면 버전 bump + * 전까지 그 확장 자산이 사라진 채 고정되므로, 캐시 경로는 건너뜀 여부를 함께 받는다. + * + * @param string $type 'module' | 'plugin' + * @param string $kind 'js' | 'css' + * @return array{content: string, skipped: list} 병합 결과와 건너뛴 확장 식별자 + */ + private function mergeBundle(string $type, string $kind): array + { + $merged = $kind === 'css' ? $this->mergeCss($type) : $this->mergeJs($type); + + return [ + 'content' => implode($kind === 'css' ? "\n" : "\n;\n", $merged['segments']), + 'skipped' => $merged['skipped'], + ]; + } + + /** + * JS 세그먼트를 priority 순으로 모읍니다. + * + * 확장별 fine-grained try/catch — 읽기 실패·처리 예외는 그 확장만 건너뛰고(`skipped` + * 에 기록) 나머지 병합을 지속한다. 한 확장의 실패가 번들 전체를 붕괴시키지 않는다. + * + * @param string $type 'module' | 'plugin' + * @return array{segments: list, skipped: list} 세그먼트와 건너뛴 확장 식별자 + */ + private function mergeJs(string $type): array + { + $ordered = $this->getOrderedGlobalAssetPaths($type); + $isProduction = app()->environment('production'); + $segments = []; + $skipped = []; + + foreach ($ordered as $identifier => $paths) { + if (empty($paths['jsAbsPath'])) { + continue; + } + + try { + $content = $this->readAssetSource($paths['jsAbsPath']); + + if ($content === false) { + Log::warning('확장 JS 번들 병합: 파일 읽기 실패, 해당 확장 skip', [ + 'type' => $type, + 'identifier' => $identifier, + 'path' => $paths['jsAbsPath'], + ]); + $skipped[] = (string) $identifier; + + continue; + } + + $segments[] = $this->processJsSourceMap($content, $type, $identifier, $isProduction); + } catch (\Throwable $e) { + Log::warning('확장 JS 번들 병합 중 오류, 해당 확장 skip', [ + 'type' => $type, + 'identifier' => $identifier, + 'error' => $e->getMessage(), + ]); + $skipped[] = (string) $identifier; + } + } + + return ['segments' => $segments, 'skipped' => $skipped]; + } + + /** + * CSS 세그먼트를 priority 순으로 모읍니다. + * + * CSS 안의 상대 `url(...)`·`@import` 참조는 그 확장의 절대 자산 URL 로 치환한다 — + * 병합본의 주소는 어느 확장의 dist 디렉토리도 아니라 상대 해석이 반드시 어긋나기 때문이다. + * 치환은 개별 자산 서빙(ServesRewritableCssAssets)과 같은 규칙(AssetCssUrlRewriter)을 쓴다. + * + * @param string $type 'module' | 'plugin' + * @return array{segments: list, skipped: list} 세그먼트와 건너뛴 확장 식별자 + */ + private function mergeCss(string $type): array { $ordered = $this->getOrderedGlobalAssetPaths($type); $isProduction = app()->environment('production'); $typeSegment = $type === 'plugin' ? 'plugins' : 'modules'; $version = $this->getCurrentVersion(); $segments = []; + $skipped = []; foreach ($ordered as $identifier => $paths) { if (empty($paths['cssAbsPath'])) { @@ -199,7 +437,7 @@ class ExtensionBundleService } try { - $content = @file_get_contents($paths['cssAbsPath']); + $content = $this->readAssetSource($paths['cssAbsPath']); if ($content === false) { Log::warning('확장 CSS 번들 병합: 파일 읽기 실패, 해당 확장 skip', [ @@ -207,6 +445,7 @@ class ExtensionBundleService 'identifier' => $identifier, 'path' => $paths['cssAbsPath'], ]); + $skipped[] = (string) $identifier; continue; } @@ -237,81 +476,26 @@ class ExtensionBundleService 'identifier' => $identifier, 'error' => $e->getMessage(), ]); + $skipped[] = (string) $identifier; } } - return implode("\n", $segments); + return ['segments' => $segments, 'skipped' => $skipped]; } /** - * 캐시된 번들 파일의 절대 경로를 반환합니다(없으면 build → 원자적 write). + * 확장 자산 원본을 읽습니다. * - * 파일명에 version 을 포함(`{type}.{version}.{js|css}`)하므로 활성 조합이 - * 바뀌어 version 이 bump 되면 새 파일명으로 자연 무효화된다. 프로덕션에서만 - * 디스크 캐시하며, 비프로덕션(dev/watch)에서는 캐시하지 않고 매 요청 build 해 - * rebuild 를 즉시 반영한다. + * 실패는 `false` 로 돌아오고 호출측이 그 확장을 건너뛴다. 별도 메서드인 이유는 + * "존재·판독 가능한데 읽기가 실패하는" 상태를 테스트가 재현할 수 있어야 하기 때문이다 — + * 그 상태가 캐시로 굳는 것이 이 서비스가 막아야 할 결함이다. * - * @param string $type 'module' | 'plugin' - * @param string $kind 'js' | 'css' - * @param int $version 확장 캐시 버전(ClearsTemplateCaches::getExtensionCacheVersion) - * @return string 캐시(또는 방금 build 한) 파일의 절대 경로. 병합 결과가 빈 문자열이면 빈 문자열. + * @param string $path 절대 경로 + * @return string|false 파일 내용 (실패 시 false) */ - public function getBundleFilePath(string $type, string $kind, int $version): string + protected function readAssetSource(string $path): string|false { - $content = $kind === 'css' - ? $this->buildCssBundle($type) - : $this->buildJsBundle($type); - - // 병합할 에셋이 하나도 없으면 파일을 만들지 않는다(호출측이 빈 문자열로 판단). - if ($content === '') { - return ''; - } - - $relativeName = $this->bundleFileName($type, $kind, $version); - - // 디스크 캐시는 **최적화**다 — 쓰기 실패가 공개 엔드포인트의 500 이 되면 안 된다. - // `ext-bundles` 디스크는 `throw => true` 라 권한 문제(uid 독점 0700 등)에서 - // `UnableToWriteFile` 이 그대로 올라오고, 그러면 모든 확장의 프론트엔드 JS/CSS 가 - // 통째로 나가지 못한다. 병합 결과는 이미 메모리에 있으므로 그것을 그대로 응답하면 - // 화면은 정상이다 (커밋 63a30ab29 의 AbstractCacheDriver fail-soft 와 같은 원칙). - try { - $storage = $this->bundleStorage(); - - // 비프로덕션은 캐시하지 않고 임시 파일로 매번 build → rebuild 즉시 반영 - if (! app()->environment('production')) { - return $this->writeAtomically($storage, $relativeName, $content, cache: false); - } - - // 프로덕션: 동일 version 캐시가 있으면 그대로 사용 - if ($storage->exists('', $relativeName)) { - return $storage->getBasePath('').'/'.$relativeName; - } - - return $this->writeAtomically($storage, $relativeName, $content, cache: true); - } catch (\Throwable $e) { - Log::warning('확장 번들 디스크 캐시 실패 — 메모리 병합 결과로 서빙합니다', [ - 'type' => $type, - 'kind' => $kind, - 'version' => $version, - 'error' => $e->getMessage(), - ]); - - return ''; - } - } - - /** - * 번들을 서빙할 때 쓸 병합 결과를 반환합니다 (디스크 캐시 실패 시 메모리 폴백용). - * - * @param string $type 'module' | 'plugin' - * @param string $kind 'js' | 'css' - * @return string 병합 결과 (없으면 빈 문자열) - */ - public function buildBundleContent(string $type, string $kind): string - { - return $kind === 'css' - ? $this->buildCssBundle($type) - : $this->buildJsBundle($type); + return @file_get_contents($path); } /** diff --git a/app/Services/LayoutExtensionService.php b/app/Services/LayoutExtensionService.php index 3efc2c53..2240d78d 100644 --- a/app/Services/LayoutExtensionService.php +++ b/app/Services/LayoutExtensionService.php @@ -292,13 +292,19 @@ class LayoutExtensionService * @param array $components 주입된 컴포넌트 배열 * @param int $extensionId 확장 PK * @param LayoutExtension|null $extension 출처 라벨 부여용 확장 모델 + * @param int|null $injectionIndex overlay `injections[]` 순번 — 편집기가 저장 시 노드를 원래 injection 으로 되돌리는 열쇠. extension_point 주입은 null * @return array 메타가 부여된 컴포넌트 배열 * * @since engine-v1.50.0 */ - private function markExtensionSource(array $components, int $extensionId, ?LayoutExtension $extension = null): array + private function markExtensionSource(array $components, int $extensionId, ?LayoutExtension $extension = null, ?int $injectionIndex = null): array { - return $this->applySourceMetaRecursively($components, $this->buildExtensionSourceMeta($extension, $extensionId)); + $meta = $this->buildExtensionSourceMeta($extension, $extensionId); + if ($injectionIndex !== null) { + $meta['injectionIndex'] = $injectionIndex; + } + + return $this->applySourceMetaRecursively($components, $meta); } /** @@ -747,7 +753,11 @@ class LayoutExtensionService 'injection_count' => count($injections), ]); - foreach ($injections as $injection) { + // injection 순번을 함께 순회한다 — 편집기 확장 편집 모드가 호스트 병합 트리에서 이 확장의 + // 노드를 추출해 `injections[].components` 로 되돌릴 때 어느 injection 인지 알 수 있어야 + // 한다. 순번이 메타에 없으면 재조립이 모든 노드를 버려 저장본의 injections 가 통째로 + // 비워진다(무변경 저장으로도 발생, 예외·경고 없음). + foreach ($injections as $injectionIndex => $injection) { $targetId = $injection['target_id'] ?? null; $position = $injection['position'] ?? 'append_child'; @@ -785,7 +795,12 @@ class LayoutExtensionService // 편집 모드 출처 메타 부여 — 주입 노드와 그 자식 모두에 extension 메타 // @since engine-v1.50.0 if ($withSourceMeta) { - $components = $this->markExtensionSource($components, $overlay->id, $overlay); + $components = $this->markExtensionSource( + $components, + $overlay->id, + $overlay, + is_int($injectionIndex) ? $injectionIndex : null + ); } $injected = $this->injectAtTarget( diff --git a/app/Services/TemplateLayoutAttachmentService.php b/app/Services/TemplateLayoutAttachmentService.php index 5630f0bd..b17e2104 100644 --- a/app/Services/TemplateLayoutAttachmentService.php +++ b/app/Services/TemplateLayoutAttachmentService.php @@ -8,6 +8,7 @@ use App\Contracts\Repositories\TemplateRepositoryInterface; use App\Extension\HookManager; use App\Models\TemplateLayoutAttachment; use App\Support\ImageResizer; +use App\Support\PublicAssetDisk; use Illuminate\Database\Eloquent\Collection; use Illuminate\Http\UploadedFile; use Illuminate\Support\Facades\Auth; @@ -64,7 +65,10 @@ class TemplateLayoutAttachmentService $storedFilename = Str::uuid().'.'.$file->getClientOriginalExtension(); $relativePath = "{$templateIdentifier}/".date('Y/m/d')."/{$storedFilename}"; - $disk = config('attachment.disk', 'attachments'); + // 운영자가 공개 자산 디스크를 선언했으면 그쪽에 저장한다 (방문자 요청이 CDN 을 탄다). + // 미선언이면 기존 첨부 디스크 그대로. 행이 자기 disk 를 기록하므로 나중에 설정이 + // 바뀌어도 각 행은 자기 저장 위치를 기억한다. + $disk = PublicAssetDisk::resolve() ?? config('attachment.disk', 'attachments'); // 환경설정 > 업로드의 최대 가로/세로·품질 적용 (코어 설정이 모든 업로드 경로에 동일 적용). // 임시 파일을 제자리에서 줄이므로 아래의 저장·크기 기록이 모두 축소본을 본다. @@ -138,8 +142,7 @@ class TemplateLayoutAttachmentService public function delete(TemplateLayoutAttachment $attachment): bool { // 1. 스토리지 파일 실삭제 (명시적 — CASCADE 미의존) - $this->storage - ->withDisk($attachment->disk) + $this->storageForRow($attachment->disk) ->delete(self::STORAGE_CATEGORY, $attachment->path); // 2. DB 행 삭제 @@ -149,16 +152,54 @@ class TemplateLayoutAttachmentService /** * 첨부 파일의 공개 접근 URL을 생성합니다. * - * 첨부 파일은 비공개 `attachments` 디스크에 저장되어 직접 공개 URL 이 없다 - * (`StorageInterface::url()` 은 public 디스크 전용). 발행된 배경 이미지는 일반 - * 사이트 방문자에게도 로드되어야 하므로, 인증 불필요한 공개 서빙 라우트 - * (`PublicTemplateController::serveFile`)의 URL 을 돌려준다. 라우트는 첨부 id 로 - * 키되며 서빙 시 첨부가 해당 템플릿 소속인지 검증한다. + * 운영자가 공개 자산 디스크(`core.storage.public_asset_disk`)를 선언했고 행 disk 가 + * 그 값과 일치할 때만 직접 URL(CDN)을 돌려준다. 그 외에는 인증 불필요한 공개 서빙 + * 라우트(`PublicTemplateController::serveFile`)의 URL 을 돌려주며, 라우트는 첨부 id 로 + * 키되고 서빙 시 첨부가 해당 템플릿 소속인지 검증한다. + * + * 디스크의 `url` 설정 유무로 판정하지 않는다 — 그 설정은 "URL 문자열을 만들 수 + * 있는가" 일 뿐 "익명 읽기가 되는가" 가 아니어서, 비공개 버킷에 공개 URL 이 + * 설정된 조합에서는 발급된 직접 URL 이 403 이 된다. * * @param TemplateLayoutAttachment $attachment 첨부 파일 - * @return string 공개 서빙 URL + * @return string 직접 URL 또는 공개 서빙 URL */ public function resolveUrl(TemplateLayoutAttachment $attachment): string + { + return $this->resolveDirectUrl($attachment) ?? $this->proxyUrl($attachment); + } + + /** + * 행 disk 가 공개 자산 디스크일 때만 직접 URL 해석을 시도합니다. + * + * @param TemplateLayoutAttachment $attachment 첨부 파일 + * @return string|null 직접 URL (대상이 아니거나 훅이 차단하면 null) + */ + private function resolveDirectUrl(TemplateLayoutAttachment $attachment): ?string + { + if (! PublicAssetDisk::isCurrent($attachment->disk)) { + return null; + } + + // 게이트를 통과한 disk 는 PublicAssetDisk::resolve() 가 존재를 이미 확인했다. + return $this->storage + ->withDisk($attachment->disk) + ->url(self::STORAGE_CATEGORY, $attachment->path); + } + + /** + * 공개 서빙 라우트(프록시) URL 을 생성합니다. + * + * 사이트 상대 경로(`/api/templates/...`)로 발급한다. 절대 URL 로 발급하면 ① 저장 + * 게이트(`NoExternalUrls`)가 서버 자신이 발급한 주소를 외부로 차단해 image 위젯의 + * 업로드 → 저장이 422 로 끝나고(배경은 style 로 들어가 스캔되지 않아 드러나지 않았다), + * ② 저장된 레이아웃이 발급 시점의 도메인·스킴에 묶여 주소가 바뀌면 그 이미지가 전부 + * 깨진다. 직접 URL(CDN)은 저장소가 정하는 절대 주소이므로 이 규칙의 대상이 아니다. + * + * @param TemplateLayoutAttachment $attachment 첨부 파일 + * @return string 공개 서빙 URL (사이트 상대 경로) + */ + private function proxyUrl(TemplateLayoutAttachment $attachment): string { $template = $attachment->template; $identifier = $template?->identifier ?? (string) $attachment->template_id; @@ -166,7 +207,27 @@ class TemplateLayoutAttachmentService return route('api.public.templates.layout-attachment-file', [ 'identifier' => $identifier, 'attachment' => $attachment->id, - ]); + ], false); + } + + /** + * 행에 기록된 disk 기준 스토리지를 반환합니다 (고아 disk 방어). + * + * 공개 자산 디스크는 플러그인이 등록한 디스크일 수 있고, 그 플러그인이 비활성화되면 + * 해당 disk 가 config 에서 사라진다. 미등록 disk 로 withDisk 를 만들면 이후 + * response/delete 가 InvalidArgumentException 을 던져 **무인증 공개 서빙 라우트가 + * 500** 이 되므로, 주입 스토리지로 폴백해 404 로 끝나게 한다. + * + * @param string|null $disk 행의 disk 컬럼 값 + * @return StorageInterface 행 disk 의 스토리지 (고아면 주입 스토리지) + */ + private function storageForRow(?string $disk): StorageInterface + { + if ($disk === null || $disk === '' || config("filesystems.disks.{$disk}") === null) { + return $this->storage; + } + + return $this->storage->withDisk($disk); } /** @@ -190,7 +251,7 @@ class TemplateLayoutAttachmentService // 행 disk 기준 인라인 스트림 (존재 검사는 response() 내부에서 수행). // 로컬 절대 경로 조립(getBasePath)은 S3 등 원격 디스크 행에서 성립하지 않는다 (#99). - $response = $this->storage->withDisk($attachment->disk)->response( + $response = $this->storageForRow($attachment->disk)->response( self::STORAGE_CATEGORY, $attachment->path, $attachment->original_name ?? basename($attachment->path), diff --git a/app/Support/ComposerInstallInfo.php b/app/Support/ComposerInstallInfo.php new file mode 100644 index 00000000..5b9bdb7c --- /dev/null +++ b/app/Support/ComposerInstallInfo.php @@ -0,0 +1,93 @@ +} dev 가 null 이면 판정 불가 + * (파일 부재 · JSON 오류 · Composer 1 형식) + */ + public static function inspect(string $vendorPath): array + { + $unknown = ['dev' => null, 'packages' => []]; + + $path = rtrim($vendorPath, '/\\').'/'.self::INSTALLED_JSON; + if (! is_file($path)) { + return $unknown; + } + + $raw = @file_get_contents($path); + if ($raw === false || $raw === '') { + return $unknown; + } + + $decoded = json_decode($raw, true); + + // Composer 1 은 최상위가 패키지 배열이라 dev 정보 자체가 없다 — 판정 불가로 둔다. + if (! is_array($decoded) || ! array_key_exists('packages', $decoded)) { + return $unknown; + } + + $names = []; + if (isset($decoded['dev-package-names']) && is_array($decoded['dev-package-names'])) { + foreach ($decoded['dev-package-names'] as $name) { + if (is_string($name) && trim($name) !== '') { + $names[] = $name; + } + } + } + + $devFlag = $decoded['dev'] ?? null; + + return [ + 'dev' => $devFlag === true || $names !== [], + 'packages' => $names, + ]; + } + + /** + * vendor 가 개발용 설치인지 반환합니다. + * + * @param string $vendorPath vendor 디렉토리 절대 경로 + * @return bool|null 판정 불가 시 null + */ + public static function isDevInstall(string $vendorPath): ?bool + { + return self::inspect($vendorPath)['dev']; + } + + /** + * vendor 에 설치된 개발용(require-dev) 패키지 이름 목록을 반환합니다. + * + * @param string $vendorPath vendor 디렉토리 절대 경로 + * @return array 개발용 패키지가 없거나 판정 불가면 빈 배열 + */ + public static function devPackageNames(string $vendorPath): array + { + return self::inspect($vendorPath)['packages']; + } +} diff --git a/app/Support/ConfigCacheHelper.php b/app/Support/ConfigCacheHelper.php index d4754f07..e52c0233 100644 --- a/app/Support/ConfigCacheHelper.php +++ b/app/Support/ConfigCacheHelper.php @@ -4,6 +4,7 @@ namespace App\Support; use Illuminate\Container\Container; use Illuminate\Support\Facades\Artisan; +use Illuminate\Support\Facades\Facade; use Illuminate\Support\Facades\File; use Illuminate\Support\Facades\Log; @@ -62,26 +63,47 @@ class ConfigCacheHelper } /** - * 콜백 실행 뒤 전역 컨테이너 인스턴스를 원래 앱으로 되돌립니다. + * 콜백 실행 뒤 전역 컨테이너 인스턴스와 파사드 애플리케이션을 원래 앱으로 되돌립니다. * - * `config:cache` 는 신선한 설정을 얻기 위해 **새 Application 을 부팅**하는데, `Application` 생성자가 - * `Container::setInstance()` 를 호출하므로 그 순간부터 `app()` 헬퍼가 실행 중인 앱이 아니라 그 - * 일회용 앱을 가리킨다(파사드는 별도 참조라 그대로다). 같은 프로세스에서 그 뒤에 등록되는 - * `app()->terminating()` 콜백은 종료되지 않는 앱에 걸려 **영원히 실행되지 않는다** — 설정 저장 - * 뒤의 확장 캐시 버전 bump 가 예약한 정적 재게시가 그렇게 조용히 사라졌다(#651 F5 실측: 버전은 - * 올랐는데 게시는 다음 렌더의 자가 치유까지 미뤄짐). 예외도 로그도 없고, 자가 치유가 한 렌더 - * 뒤에 덮어 주므로 "한 박자 늦게 반영" 으로만 나타난다. + * `config:cache` 는 신선한 설정을 얻기 위해 **새 Application 을 부팅**하는데, 그 부팅이 전역 상태를 + * 두 군데 바꾼다. + * + * 1. `Application` 생성자의 `Container::setInstance()` — 그 순간부터 `app()` 헬퍼가 실행 중인 앱이 + * 아니라 일회용 앱을 가리킨다. 같은 프로세스에서 그 뒤에 등록되는 `app()->terminating()` 콜백은 + * 종료되지 않는 앱에 걸려 **영원히 실행되지 않는다** — 설정 저장 뒤의 확장 캐시 버전 bump 가 + * 예약한 정적 재게시가 그렇게 조용히 사라졌다(#651 F5 실측: 버전은 올랐는데 게시는 다음 렌더의 + * 자가 치유까지 미뤄짐). + * 2. `registerBaseBindings()` 의 `Facade::clearResolvedInstances()` + `Facade::setFacadeApplication()` + * — 그 순간부터 **모든 파사드**(`Artisan`·`Log`·`DB`·`Cache` …)가 일회용 앱에서 새 인스턴스를 + * 해석한다. 컨테이너만 되돌리면 이 축이 남는다. + * + * 2번이 코어 업데이트에서 드러난 형태: Step 11 의 `ConfigCacheHelper::rebuild()` 뒤에 오는 + * `RouteCacheHelper::rebuild()` 의 `Artisan::call('route:clear')` 가 일회용 앱에서 **새 콘솔 Kernel** 을 + * 해석하고, 그 Kernel 은 콘솔 Application 을 처음부터 구성하며 등록된 커맨드를 전부 resolve 한다. + * 부팅 시점 vendor 가 개발용이었다면 그 목록에 `command.tinker` 같은 require-dev 커맨드가 들어 + * 있는데, Step 8 이 vendor 를 `--no-dev` 로 이미 교체했으므로 그 클래스가 없어 + * `Target class [command.tinker] does not exist.` 로 업데이트 전체가 실패·롤백한다 + * (2026-09-07 7.0.9 → 7.0.11 실측). 프로세스에 이미 등록된 provider 목록은 vendor 교체로 갱신되지 + * 않으므로, 방어는 "교체 뒤에 콘솔 Application 을 다시 구성하지 않는 것" 이다. * * @param callable $callback 전역 인스턴스를 바꿔 놓을 수 있는 작업 */ public static function withPreservedContainer(callable $callback): void { $app = Container::getInstance(); + $facadeApp = Facade::getFacadeApplication(); try { $callback(); } finally { Container::setInstance($app); + + // 일회용 앱이 남긴 파사드 해석 결과를 버리고 원래 앱으로 되돌린다. + // 순서 주의: clear 를 먼저 해야 일회용 앱에서 해석된 인스턴스가 캐시에 남지 않는다. + Facade::clearResolvedInstances(); + if ($facadeApp !== null) { + Facade::setFacadeApplication($facadeApp); + } } } diff --git a/app/Support/CoreUpdateContext.php b/app/Support/CoreUpdateContext.php new file mode 100644 index 00000000..9ce03b17 --- /dev/null +++ b/app/Support/CoreUpdateContext.php @@ -0,0 +1,79 @@ + 삭제하지 못하고 남은 파일의 절대 경로. 전부 지웠거나 원래 없었으면 빈 배열 + */ + public static function clear(): array + { + $app = app(); + $remaining = []; + + foreach ([$app->getCachedServicesPath(), $app->getCachedPackagesPath()] as $path) { + if (! is_file($path)) { + continue; + } + + if (! @unlink($path)) { + clearstatcache(true, $path); + if (is_file($path)) { + $remaining[] = $path; + } + } + } + + clearstatcache(); + + return $remaining; + } + + /** + * 패키지 매니페스트 캐시를 비우고 현재 vendor 기준으로 다시 만듭니다. + * + * `package:discover` 는 새 Application 을 부팅하지 않으므로(현재 앱의 `PackageManifest` 를 + * 그대로 쓴다) `ConfigCacheHelper::withPreservedContainer()` 로 감쌀 필요가 없다. + * + * @return void + */ + public static function rebuild(): void + { + self::clear(); + + Artisan::call('package:discover'); + } +} diff --git a/app/Support/PublicAssetDisk.php b/app/Support/PublicAssetDisk.php new file mode 100644 index 00000000..8193a679 --- /dev/null +++ b/app/Support/PublicAssetDisk.php @@ -0,0 +1,55 @@ + 코어 전역 설정(core.storage.public_asset_disk). + * 미설정('')/'none'/config 에 존재하지 않는 디스크(고아 플러그인 디스크)는 null 로 + * 해석되어 호출측이 기존 디스크(스트리밍)로 폴백합니다. + * + * @param string|null $override 확장 개별 설정값 (''/null 이면 코어 전역 설정 사용) + * @return string|null 사용할 디스크 이름 (스트리밍 유지면 null) + */ + public static function resolve(?string $override = null): ?string + { + $disk = ($override !== null && $override !== '') + ? $override + : (string) config('core.storage.public_asset_disk', ''); + + if ($disk === '' || $disk === 'none' || config("filesystems.disks.{$disk}") === null) { + return null; + } + + return $disk; + } + + /** + * 주어진 disk 가 "지금" 유효한 공개 자산 디스크인지 판정합니다. + * + * 직접 URL 발급 여부의 단일 판정점입니다. 반드시 resolve() 를 경유하므로 + * ''/'none'/고아 디스크가 등가 비교만으로 통과하지 않습니다. + * + * @param string|null $disk 행에 기록된 disk 값 + * @return bool 공개 자산 디스크와 일치하면 true + */ + public static function isCurrent(?string $disk): bool + { + return $disk !== null && $disk !== '' && $disk === self::resolve(); + } +} diff --git a/app/Support/SiteAssetHosts.php b/app/Support/SiteAssetHosts.php new file mode 100644 index 00000000..cd8d3562 --- /dev/null +++ b/app/Support/SiteAssetHosts.php @@ -0,0 +1,115 @@ + 소문자 host 목록 (중복 제거, 해석 불가면 빈 배열) + */ + public static function hosts(): array + { + $hosts = []; + + $appHost = TrustedScriptHosts::hostOf((string) config('app.url', '')); + if ($appHost !== null) { + $hosts[] = $appHost; + } + + $disk = PublicAssetDisk::resolve(); + if ($disk !== null) { + $diskHost = self::publicAssetDiskHost($disk); + if ($diskHost !== null && ! in_array($diskHost, $hosts, true)) { + $hosts[] = $diskHost; + } + } + + return $hosts; + } + + /** + * http/https 절대 URL 이고 그 host 가 사이트 자산 host 이면 true 를 돌려줍니다. + * + * 경로만 있는 값(`/api/...`)은 host 가 없어 이 판정의 대상이 아니고(호출측이 경로 + * 규칙으로 다룹니다), protocol-relative(`//host/...`)와 http/https 밖의 스킴은 host 가 + * 같아도 false 입니다 — 그 축은 종전 판정을 그대로 둡니다. + * + * @param string $url 검사 대상 URL + * @param array|null $hosts host 목록 (미지정 시 self::hosts()) + * @return bool 사이트 자산 host 의 http(s) 절대 URL 이면 true + */ + public static function isSiteAssetUrl(string $url, ?array $hosts = null): bool + { + $normalized = TrustedScriptHosts::normalizeForOriginCheck(trim($url)); + + if (preg_match('#^https?://#i', $normalized) !== 1) { + return false; + } + + $host = TrustedScriptHosts::hostOf($normalized); + + if ($host === null) { + return false; + } + + $hosts ??= self::hosts(); + + return in_array($host, $hosts, true); + } + + /** + * 공개 자산 디스크의 기준 URL host 를 해석합니다. + * + * 첨부 API 가 직접 URL 을 만드는 것과 같은 경로(`CoreStorageDriver::url()` — 디스크 url + * 설정과 `core.storage.filter_url` 훅을 모두 거친다)로 빈 경로의 URL 을 만들어 host 만 + * 취합니다. 설정값을 직접 읽으면 훅이 바꾼 host 를 놓칩니다. + * + * @param string $disk PublicAssetDisk::resolve() 가 돌려준 디스크 + * @return string|null 소문자 host (직접 URL 을 만들 수 없는 디스크면 null) + */ + private static function publicAssetDiskHost(string $disk): ?string + { + try { + // StorageInterface 는 컨텍스트 바인딩만 있어 컨테이너로 못 받는다 (ExtensionBundleService 와 같은 선례). + $base = (new CoreStorageDriver($disk))->url('', ''); + } catch (\Throwable $e) { + Log::warning('SiteAssetHosts: 공개 자산 디스크 기준 URL 해석 실패 - '.$e->getMessage(), ['disk' => $disk]); + + return null; + } + + return is_string($base) ? TrustedScriptHosts::hostOf($base) : null; + } +} diff --git a/bootstrap/app.php b/bootstrap/app.php index b25bd6be..9f05c9f9 100644 --- a/bootstrap/app.php +++ b/bootstrap/app.php @@ -214,4 +214,39 @@ $app = Application::configure(basePath: dirname(__DIR__)) }); })->create(); +/* +| Core Update: Stale Package Manifest Self-Heal — 코어 업데이트 트리 안에서 부팅하는 프로세스 +| (core:update 부모, G7_UPDATE_IN_PROGRESS=1 을 물려받은 spawn 자식, 그 안의 config:cache/route:cache +| 일회용 앱)는 bootstrap/cache/packages.php · services.php 를 비우고 부팅한다. Laravel 은 두 파일이 +| 없을 때만 vendor/composer/installed.json 에서 다시 만들므로, vendor 교체 뒤 남은 이전 설치본의 +| 목록(dev composer 의 require-dev 전이 provider 등)으로 부팅하다 "Class ... not found" 로 죽는 일을 +| 막는다. 7.0.11 미만 부모는 spawn 직전에 비우지 않으므로 그 부모 아래에서 도는 신버전 자식의 +| 유일한 방어다 (7.0.9/7.0.10 → 7.0.11). 7.0.11+ 부모는 이미 비우므로 여기서는 no-op. +| 규칙: App\ 클래스를 참조하지 않는다 (부팅 전이라 오토로드를 신뢰할 수 없고, 자가 치유가 자기 +| 실패로 부팅을 막아서는 안 된다). 판정은 App\Support\CoreUpdateContext::isInProgress() 와 동일 — +| 조건을 바꾸면 양쪽을 함께 고친다. +| argv 채널은 명령줄 SAPI 에서만 읽는다. CGI/FPM 은 register_argc_argv=On 이면 $_SERVER['argv'] 를 +| 쿼리스트링을 '+' 로 쪼갠 값으로 채우므로(`GET /?x+core:update` → argv[1] === 'core:update'), 이 게이트가 +| 없으면 비인증 웹 요청이 요청마다 매니페스트를 지우고 다시 만들게 한다. env 플래그 채널은 웹 요청으로 +| 주입할 수 없어 그대로 두며, 웹 요청 안에서 시작하는 업데이트 흐름은 그 플래그를 프로세스 안에서 세운다. +*/ +$g7UpdateFlag = $_ENV['G7_UPDATE_IN_PROGRESS'] ?? $_SERVER['G7_UPDATE_IN_PROGRESS'] ?? getenv('G7_UPDATE_IN_PROGRESS'); +$g7UpdateArgv = in_array(PHP_SAPI, ['cli', 'phpdbg'], true) ? ($_SERVER['argv'][1] ?? '') : ''; + +if ($g7UpdateFlag === '1' || $g7UpdateFlag === 1 || $g7UpdateFlag === true + || in_array($g7UpdateArgv, ['core:update', 'core:execute-upgrade-steps'], true)) { + try { + foreach ([$app->getCachedPackagesPath(), $app->getCachedServicesPath()] as $g7ManifestPath) { + if (is_file($g7ManifestPath)) { + @unlink($g7ManifestPath); // Windows 핸들 점유 시 실패 가능 — 무시(종전 동작) + } + } + clearstatcache(); + } catch (Throwable) { + // 자가 치유 실패는 부팅을 막지 않는다. + } +} + +unset($g7UpdateFlag, $g7UpdateArgv, $g7ManifestPath); + return $app; diff --git a/composer.lock b/composer.lock index f1e64c9f..67b648cc 100644 --- a/composer.lock +++ b/composer.lock @@ -62,16 +62,16 @@ }, { "name": "aws/aws-sdk-php", - "version": "3.386.1", + "version": "3.394.9", "source": { "type": "git", "url": "https://github.com/aws/aws-sdk-php.git", - "reference": "e36bc0e97e82d68acc92b9e06f5dc544913d819a" + "reference": "c5e7a247fa6aeac7355d5be88f38873480789c71" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/e36bc0e97e82d68acc92b9e06f5dc544913d819a", - "reference": "e36bc0e97e82d68acc92b9e06f5dc544913d819a", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/c5e7a247fa6aeac7355d5be88f38873480789c71", + "reference": "c5e7a247fa6aeac7355d5be88f38873480789c71", "shasum": "" }, "require": { @@ -79,9 +79,9 @@ "ext-json": "*", "ext-pcre": "*", "ext-simplexml": "*", - "guzzlehttp/guzzle": "^7.4.5", - "guzzlehttp/promises": "^2.0", - "guzzlehttp/psr7": "^2.4.5", + "guzzlehttp/guzzle": "^7.8.2 || ^8.0", + "guzzlehttp/promises": "^2.0.3 || ^3.0", + "guzzlehttp/psr7": "^2.6.3 || ^3.0", "mtdowling/jmespath.php": "^2.9.1", "php": ">=8.1", "psr/http-message": "^1.0 || ^2.0", @@ -153,9 +153,9 @@ "support": { "forum": "https://github.com/aws/aws-sdk-php/discussions", "issues": "https://github.com/aws/aws-sdk-php/issues", - "source": "https://github.com/aws/aws-sdk-php/tree/3.386.1" + "source": "https://github.com/aws/aws-sdk-php/tree/3.394.9" }, - "time": "2026-06-23T01:24:07+00:00" + "time": "2026-09-04T18:07:00+00:00" }, { "name": "brick/math", @@ -219,16 +219,16 @@ }, { "name": "carbonphp/carbon-doctrine-types", - "version": "3.2.0", + "version": "3.2.1", "source": { "type": "git", "url": "https://github.com/CarbonPHP/carbon-doctrine-types.git", - "reference": "18ba5ddfec8976260ead6e866180bd5d2f71aa1d" + "reference": "5fa5eacafd9ef47c8c6ab9143fc901ba0194d3dc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/CarbonPHP/carbon-doctrine-types/zipball/18ba5ddfec8976260ead6e866180bd5d2f71aa1d", - "reference": "18ba5ddfec8976260ead6e866180bd5d2f71aa1d", + "url": "https://api.github.com/repos/CarbonPHP/carbon-doctrine-types/zipball/5fa5eacafd9ef47c8c6ab9143fc901ba0194d3dc", + "reference": "5fa5eacafd9ef47c8c6ab9143fc901ba0194d3dc", "shasum": "" }, "require": { @@ -243,6 +243,11 @@ "phpunit/phpunit": "^10.3" }, "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, "autoload": { "psr-4": { "Carbon\\Doctrine\\": "src/Carbon/Doctrine/" @@ -268,7 +273,7 @@ ], "support": { "issues": "https://github.com/CarbonPHP/carbon-doctrine-types/issues", - "source": "https://github.com/CarbonPHP/carbon-doctrine-types/tree/3.2.0" + "source": "https://github.com/CarbonPHP/carbon-doctrine-types/tree/3.2.1" }, "funding": [ { @@ -284,7 +289,7 @@ "type": "tidelift" } ], - "time": "2024-02-09T16:56:22+00:00" + "time": "2026-09-06T14:11:38+00:00" }, { "name": "clue/redis-protocol", @@ -418,16 +423,16 @@ }, { "name": "composer/ca-bundle", - "version": "1.5.12", + "version": "1.5.14", "source": { "type": "git", "url": "https://github.com/composer/ca-bundle.git", - "reference": "00a2f4201641d5c53f7fc0195e6c8d9fcc321a78" + "reference": "0c8abba0634f637bd78c4e451981da368d403463" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/ca-bundle/zipball/00a2f4201641d5c53f7fc0195e6c8d9fcc321a78", - "reference": "00a2f4201641d5c53f7fc0195e6c8d9fcc321a78", + "url": "https://api.github.com/repos/composer/ca-bundle/zipball/0c8abba0634f637bd78c4e451981da368d403463", + "reference": "0c8abba0634f637bd78c4e451981da368d403463", "shasum": "" }, "require": { @@ -474,7 +479,7 @@ "support": { "irc": "irc://irc.freenode.org/composer", "issues": "https://github.com/composer/ca-bundle/issues", - "source": "https://github.com/composer/ca-bundle/tree/1.5.12" + "source": "https://github.com/composer/ca-bundle/tree/1.5.14" }, "funding": [ { @@ -486,7 +491,7 @@ "type": "github" } ], - "time": "2026-05-19T11:26:22+00:00" + "time": "2026-08-21T14:57:29+00:00" }, { "name": "composer/semver", @@ -1058,16 +1063,16 @@ }, { "name": "geoip2/geoip2", - "version": "v3.3.0", + "version": "v3.4.0", "source": { "type": "git", "url": "https://github.com/maxmind/GeoIP2-php.git", - "reference": "49fceddd694295e76e970a32848e03bb19e56b42" + "reference": "257a350e5e0cae57ac95417f32bb0aba285b2c0d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/maxmind/GeoIP2-php/zipball/49fceddd694295e76e970a32848e03bb19e56b42", - "reference": "49fceddd694295e76e970a32848e03bb19e56b42", + "url": "https://api.github.com/repos/maxmind/GeoIP2-php/zipball/257a350e5e0cae57ac95417f32bb0aba285b2c0d", + "reference": "257a350e5e0cae57ac95417f32bb0aba285b2c0d", "shasum": "" }, "require": { @@ -1096,10 +1101,10 @@ { "name": "Gregory J. Oschwald", "email": "goschwald@maxmind.com", - "homepage": "https://www.maxmind.com/" + "homepage": "https://www.maxmind.com/en/home" } ], - "description": "MaxMind GeoIP2 PHP API", + "description": "MaxMind GeoIP PHP API", "homepage": "https://github.com/maxmind/GeoIP2-php", "keywords": [ "IP", @@ -1110,30 +1115,30 @@ ], "support": { "issues": "https://github.com/maxmind/GeoIP2-php/issues", - "source": "https://github.com/maxmind/GeoIP2-php/tree/v3.3.0" + "source": "https://github.com/maxmind/GeoIP2-php/tree/v3.4.0" }, - "time": "2025-11-20T18:50:15+00:00" + "time": "2026-07-16T22:11:20+00:00" }, { "name": "graham-campbell/result-type", - "version": "v1.1.4", + "version": "v1.2.0", "source": { "type": "git", "url": "https://github.com/GrahamCampbell/Result-Type.git", - "reference": "e01f4a821471308ba86aa202fed6698b6b695e3b" + "reference": "adccca3324eece92ca35463648c12b9e6293c05b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/GrahamCampbell/Result-Type/zipball/e01f4a821471308ba86aa202fed6698b6b695e3b", - "reference": "e01f4a821471308ba86aa202fed6698b6b695e3b", + "url": "https://api.github.com/repos/GrahamCampbell/Result-Type/zipball/adccca3324eece92ca35463648c12b9e6293c05b", + "reference": "adccca3324eece92ca35463648c12b9e6293c05b", "shasum": "" }, "require": { "php": "^7.2.5 || ^8.0", - "phpoption/phpoption": "^1.9.5" + "phpoption/phpoption": "^1.10" }, "require-dev": { - "phpunit/phpunit": "^8.5.41 || ^9.6.22 || ^10.5.45 || ^11.5.7" + "phpunit/phpunit": "^8.5.52 || ^9.6.34 || ^10.5.63 || ^11.5.55 || ^12.5.14" }, "type": "library", "autoload": { @@ -1162,7 +1167,7 @@ ], "support": { "issues": "https://github.com/GrahamCampbell/Result-Type/issues", - "source": "https://github.com/GrahamCampbell/Result-Type/tree/v1.1.4" + "source": "https://github.com/GrahamCampbell/Result-Type/tree/v1.2.0" }, "funding": [ { @@ -1174,7 +1179,7 @@ "type": "tidelift" } ], - "time": "2025-12-27T19:43:20+00:00" + "time": "2026-08-24T09:06:52+00:00" }, { "name": "guzzlehttp/guzzle", @@ -1509,16 +1514,16 @@ }, { "name": "guzzlehttp/uri-template", - "version": "v1.0.8", + "version": "v1.0.11", "source": { "type": "git", "url": "https://github.com/guzzle/uri-template.git", - "reference": "9c19128923b05a5d7355e5d2318d7808b7e33bbd" + "reference": "d0058dccf4299d70c3d9da3378b8908b32780368" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/uri-template/zipball/9c19128923b05a5d7355e5d2318d7808b7e33bbd", - "reference": "9c19128923b05a5d7355e5d2318d7808b7e33bbd", + "url": "https://api.github.com/repos/guzzle/uri-template/zipball/d0058dccf4299d70c3d9da3378b8908b32780368", + "reference": "d0058dccf4299d70c3d9da3378b8908b32780368", "shasum": "" }, "require": { @@ -1575,7 +1580,7 @@ ], "support": { "issues": "https://github.com/guzzle/uri-template/issues", - "source": "https://github.com/guzzle/uri-template/tree/v1.0.8" + "source": "https://github.com/guzzle/uri-template/tree/v1.0.11" }, "funding": [ { @@ -1591,20 +1596,20 @@ "type": "tidelift" } ], - "time": "2026-06-23T13:02:23+00:00" + "time": "2026-08-24T09:15:32+00:00" }, { "name": "jaybizzle/crawler-detect", - "version": "v1.4.0", + "version": "v1.4.1", "source": { "type": "git", "url": "https://github.com/JayBizzle/Crawler-Detect.git", - "reference": "20d7bd4a6ee68444693fe75bc5c6f12f65ad512d" + "reference": "770e00e5bcd35054871a6f790f380123cc3b8b0c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/JayBizzle/Crawler-Detect/zipball/20d7bd4a6ee68444693fe75bc5c6f12f65ad512d", - "reference": "20d7bd4a6ee68444693fe75bc5c6f12f65ad512d", + "url": "https://api.github.com/repos/JayBizzle/Crawler-Detect/zipball/770e00e5bcd35054871a6f790f380123cc3b8b0c", + "reference": "770e00e5bcd35054871a6f790f380123cc3b8b0c", "shasum": "" }, "require": { @@ -1641,22 +1646,22 @@ ], "support": { "issues": "https://github.com/JayBizzle/Crawler-Detect/issues", - "source": "https://github.com/JayBizzle/Crawler-Detect/tree/v1.4.0" + "source": "https://github.com/JayBizzle/Crawler-Detect/tree/v1.4.1" }, - "time": "2026-06-11T18:29:41+00:00" + "time": "2026-07-10T14:15:38+00:00" }, { "name": "laravel/framework", - "version": "v12.62.0", + "version": "v12.69.1", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "f7e61eb1e0e06a38996802b769bce9127aec227c" + "reference": "0c07b0b1f88af44d8558ffadf66900a860f93c23" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/f7e61eb1e0e06a38996802b769bce9127aec227c", - "reference": "f7e61eb1e0e06a38996802b769bce9127aec227c", + "url": "https://api.github.com/repos/laravel/framework/zipball/0c07b0b1f88af44d8558ffadf66900a860f93c23", + "reference": "0c07b0b1f88af44d8558ffadf66900a860f93c23", "shasum": "" }, "require": { @@ -1865,20 +1870,20 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2026-06-09T13:50:13+00:00" + "time": "2026-09-01T21:34:37+00:00" }, { "name": "laravel/prompts", - "version": "v0.3.21", + "version": "v0.3.24", "source": { "type": "git", "url": "https://github.com/laravel/prompts.git", - "reference": "7753c65c281c2550c7c183f14e18062073b7d821" + "reference": "5d3cdef29e93ca3b62b1871359db3078cd99908b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/prompts/zipball/7753c65c281c2550c7c183f14e18062073b7d821", - "reference": "7753c65c281c2550c7c183f14e18062073b7d821", + "url": "https://api.github.com/repos/laravel/prompts/zipball/5d3cdef29e93ca3b62b1871359db3078cd99908b", + "reference": "5d3cdef29e93ca3b62b1871359db3078cd99908b", "shasum": "" }, "require": { @@ -1922,22 +1927,22 @@ "description": "Add beautiful and user-friendly forms to your command-line applications.", "support": { "issues": "https://github.com/laravel/prompts/issues", - "source": "https://github.com/laravel/prompts/tree/v0.3.21" + "source": "https://github.com/laravel/prompts/tree/v0.3.24" }, - "time": "2026-06-26T00:11:25+00:00" + "time": "2026-08-20T12:55:36+00:00" }, { "name": "laravel/reverb", - "version": "v1.10.2", + "version": "v1.11.1", "source": { "type": "git", "url": "https://github.com/laravel/reverb.git", - "reference": "43a5c0a99b1aaba33dc32f97fcf51f182dd8c8ac" + "reference": "52ce5fd88cd1d7eacfdcf6b91cea4704cc546f27" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/reverb/zipball/43a5c0a99b1aaba33dc32f97fcf51f182dd8c8ac", - "reference": "43a5c0a99b1aaba33dc32f97fcf51f182dd8c8ac", + "url": "https://api.github.com/repos/laravel/reverb/zipball/52ce5fd88cd1d7eacfdcf6b91cea4704cc546f27", + "reference": "52ce5fd88cd1d7eacfdcf6b91cea4704cc546f27", "shasum": "" }, "require": { @@ -2001,22 +2006,22 @@ ], "support": { "issues": "https://github.com/laravel/reverb/issues", - "source": "https://github.com/laravel/reverb/tree/v1.10.2" + "source": "https://github.com/laravel/reverb/tree/v1.11.1" }, - "time": "2026-05-10T15:47:52+00:00" + "time": "2026-08-06T14:48:58+00:00" }, { "name": "laravel/sanctum", - "version": "v4.3.2", + "version": "v4.3.3", "source": { "type": "git", "url": "https://github.com/laravel/sanctum.git", - "reference": "2a9bccc18e9907808e0018dd15fa643937886b1e" + "reference": "fee27a573d1a013af3721d86153a65e0b11927e6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/sanctum/zipball/2a9bccc18e9907808e0018dd15fa643937886b1e", - "reference": "2a9bccc18e9907808e0018dd15fa643937886b1e", + "url": "https://api.github.com/repos/laravel/sanctum/zipball/fee27a573d1a013af3721d86153a65e0b11927e6", + "reference": "fee27a573d1a013af3721d86153a65e0b11927e6", "shasum": "" }, "require": { @@ -2066,20 +2071,20 @@ "issues": "https://github.com/laravel/sanctum/issues", "source": "https://github.com/laravel/sanctum" }, - "time": "2026-04-30T11:46:25+00:00" + "time": "2026-06-23T18:26:55+00:00" }, { "name": "laravel/scout", - "version": "v11.3.0", + "version": "v11.6.1", "source": { "type": "git", "url": "https://github.com/laravel/scout.git", - "reference": "7d0903e083dee7c3d295a27917c82496c5c41970" + "reference": "860f8246d5712fe6e18907119def44d2633b4622" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/scout/zipball/7d0903e083dee7c3d295a27917c82496c5c41970", - "reference": "7d0903e083dee7c3d295a27917c82496c5c41970", + "url": "https://api.github.com/repos/laravel/scout/zipball/860f8246d5712fe6e18907119def44d2633b4622", + "reference": "860f8246d5712fe6e18907119def44d2633b4622", "shasum": "" }, "require": { @@ -2107,6 +2112,7 @@ }, "suggest": { "algolia/algoliasearch-client-php": "Required to use the Algolia engine (^3.2).", + "laravel/ai": "Required to generate embeddings for semantic and hybrid search.", "meilisearch/meilisearch-php": "Required to use the Meilisearch engine (^1.0).", "typesense/typesense-php": "Required to use the Typesense engine (^4.9)." }, @@ -2146,20 +2152,20 @@ "issues": "https://github.com/laravel/scout/issues", "source": "https://github.com/laravel/scout" }, - "time": "2026-06-16T15:48:22+00:00" + "time": "2026-08-25T20:32:21+00:00" }, { "name": "laravel/serializable-closure", - "version": "v2.0.13", + "version": "v2.0.16", "source": { "type": "git", "url": "https://github.com/laravel/serializable-closure.git", - "reference": "b566ee0dd251f3c4078bed003a7ce015f5ea6dce" + "reference": "7cfc24e4fa2cca045fb8dd2a797a2b2b13b655ed" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/b566ee0dd251f3c4078bed003a7ce015f5ea6dce", - "reference": "b566ee0dd251f3c4078bed003a7ce015f5ea6dce", + "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/7cfc24e4fa2cca045fb8dd2a797a2b2b13b655ed", + "reference": "7cfc24e4fa2cca045fb8dd2a797a2b2b13b655ed", "shasum": "" }, "require": { @@ -2207,7 +2213,7 @@ "issues": "https://github.com/laravel/serializable-closure/issues", "source": "https://github.com/laravel/serializable-closure" }, - "time": "2026-04-16T14:03:50+00:00" + "time": "2026-08-18T20:28:54+00:00" }, { "name": "laravel/tinker", @@ -2277,16 +2283,16 @@ }, { "name": "league/commonmark", - "version": "2.10.0", + "version": "2.10.1", "source": { "type": "git", "url": "https://github.com/thephpleague/commonmark.git", - "reference": "d2d1aa8b35e072966c89bc0c66cf926e56767dc4" + "reference": "9d489ab67a02960fd8ffe624d93f751daf95439e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/d2d1aa8b35e072966c89bc0c66cf926e56767dc4", - "reference": "d2d1aa8b35e072966c89bc0c66cf926e56767dc4", + "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/9d489ab67a02960fd8ffe624d93f751daf95439e", + "reference": "9d489ab67a02960fd8ffe624d93f751daf95439e", "shasum": "" }, "require": { @@ -2380,7 +2386,7 @@ "type": "tidelift" } ], - "time": "2026-08-11T16:06:25+00:00" + "time": "2026-09-07T13:44:26+00:00" }, { "name": "league/config", @@ -2466,16 +2472,16 @@ }, { "name": "league/flysystem", - "version": "3.35.1", + "version": "3.36.0", "source": { "type": "git", "url": "https://github.com/thephpleague/flysystem.git", - "reference": "f23af6c5aafd958a7593029a271d77baf5ed793c" + "reference": "f7fb152932f30072d573510cbd4dd657d6475b25" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/f23af6c5aafd958a7593029a271d77baf5ed793c", - "reference": "f23af6c5aafd958a7593029a271d77baf5ed793c", + "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/f7fb152932f30072d573510cbd4dd657d6475b25", + "reference": "f7fb152932f30072d573510cbd4dd657d6475b25", "shasum": "" }, "require": { @@ -2543,22 +2549,22 @@ ], "support": { "issues": "https://github.com/thephpleague/flysystem/issues", - "source": "https://github.com/thephpleague/flysystem/tree/3.35.1" + "source": "https://github.com/thephpleague/flysystem/tree/3.36.0" }, - "time": "2026-06-25T06:52:23+00:00" + "time": "2026-09-02T08:00:27+00:00" }, { "name": "league/flysystem-aws-s3-v3", - "version": "3.35.2", + "version": "3.35.3", "source": { "type": "git", "url": "https://github.com/thephpleague/flysystem-aws-s3-v3.git", - "reference": "8475ef9adfc6498b85469e2abec6fe3118cd08c4" + "reference": "b03780cb97585ee7e48977f40ed599b33b751634" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem-aws-s3-v3/zipball/8475ef9adfc6498b85469e2abec6fe3118cd08c4", - "reference": "8475ef9adfc6498b85469e2abec6fe3118cd08c4", + "url": "https://api.github.com/repos/thephpleague/flysystem-aws-s3-v3/zipball/b03780cb97585ee7e48977f40ed599b33b751634", + "reference": "b03780cb97585ee7e48977f40ed599b33b751634", "shasum": "" }, "require": { @@ -2598,22 +2604,22 @@ "storage" ], "support": { - "source": "https://github.com/thephpleague/flysystem-aws-s3-v3/tree/3.35.2" + "source": "https://github.com/thephpleague/flysystem-aws-s3-v3/tree/3.35.3" }, - "time": "2026-07-01T23:25:49+00:00" + "time": "2026-08-08T16:19:23+00:00" }, { "name": "league/flysystem-local", - "version": "3.31.0", + "version": "3.35.3", "source": { "type": "git", "url": "https://github.com/thephpleague/flysystem-local.git", - "reference": "2f669db18a4c20c755c2bb7d3a7b0b2340488079" + "reference": "a099b24dce160f3b2239043d13d47c4a1a214ea4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem-local/zipball/2f669db18a4c20c755c2bb7d3a7b0b2340488079", - "reference": "2f669db18a4c20c755c2bb7d3a7b0b2340488079", + "url": "https://api.github.com/repos/thephpleague/flysystem-local/zipball/a099b24dce160f3b2239043d13d47c4a1a214ea4", + "reference": "a099b24dce160f3b2239043d13d47c4a1a214ea4", "shasum": "" }, "require": { @@ -2647,22 +2653,22 @@ "local" ], "support": { - "source": "https://github.com/thephpleague/flysystem-local/tree/3.31.0" + "source": "https://github.com/thephpleague/flysystem-local/tree/3.35.3" }, - "time": "2026-01-23T15:30:45+00:00" + "time": "2026-08-12T13:29:21+00:00" }, { "name": "league/mime-type-detection", - "version": "1.16.0", + "version": "1.17.0", "source": { "type": "git", "url": "https://github.com/thephpleague/mime-type-detection.git", - "reference": "2d6702ff215bf922936ccc1ad31007edc76451b9" + "reference": "f5f47eff7c48ed1003069a2ca67f316fb4021c76" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/mime-type-detection/zipball/2d6702ff215bf922936ccc1ad31007edc76451b9", - "reference": "2d6702ff215bf922936ccc1ad31007edc76451b9", + "url": "https://api.github.com/repos/thephpleague/mime-type-detection/zipball/f5f47eff7c48ed1003069a2ca67f316fb4021c76", + "reference": "f5f47eff7c48ed1003069a2ca67f316fb4021c76", "shasum": "" }, "require": { @@ -2672,7 +2678,7 @@ "require-dev": { "friendsofphp/php-cs-fixer": "^3.2", "phpstan/phpstan": "^0.12.68", - "phpunit/phpunit": "^8.5.8 || ^9.3 || ^10.0" + "phpunit/phpunit": "^8.5.8 || ^9.3 || ^10.0 || ^11.0 || ^12.0" }, "type": "library", "autoload": { @@ -2693,7 +2699,7 @@ "description": "Mime-type detection for Flysystem", "support": { "issues": "https://github.com/thephpleague/mime-type-detection/issues", - "source": "https://github.com/thephpleague/mime-type-detection/tree/1.16.0" + "source": "https://github.com/thephpleague/mime-type-detection/tree/1.17.0" }, "funding": [ { @@ -2705,7 +2711,7 @@ "type": "tidelift" } ], - "time": "2024-09-21T08:32:55+00:00" + "time": "2026-07-09T11:49:27+00:00" }, { "name": "league/uri", @@ -3006,16 +3012,16 @@ }, { "name": "monolog/monolog", - "version": "3.10.0", + "version": "3.11.0", "source": { "type": "git", "url": "https://github.com/Seldaek/monolog.git", - "reference": "b321dd6749f0bf7189444158a3ce785cc16d69b0" + "reference": "147f303310f06334f03f409e49d7ad1e275ff05a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Seldaek/monolog/zipball/b321dd6749f0bf7189444158a3ce785cc16d69b0", - "reference": "b321dd6749f0bf7189444158a3ce785cc16d69b0", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/147f303310f06334f03f409e49d7ad1e275ff05a", + "reference": "147f303310f06334f03f409e49d7ad1e275ff05a", "shasum": "" }, "require": { @@ -3093,7 +3099,7 @@ ], "support": { "issues": "https://github.com/Seldaek/monolog/issues", - "source": "https://github.com/Seldaek/monolog/tree/3.10.0" + "source": "https://github.com/Seldaek/monolog/tree/3.11.0" }, "funding": [ { @@ -3105,20 +3111,20 @@ "type": "tidelift" } ], - "time": "2026-01-02T08:56:05+00:00" + "time": "2026-09-02T12:39:56+00:00" }, { "name": "mtdowling/jmespath.php", - "version": "2.9.1", + "version": "2.9.2", "source": { "type": "git", "url": "https://github.com/jmespath/jmespath.php.git", - "reference": "9c208ba27ae7d90853c288b3795d6702eb251d34" + "reference": "2157c5e50e813ec6a96c1eed3be7f64a20fb32a8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/jmespath/jmespath.php/zipball/9c208ba27ae7d90853c288b3795d6702eb251d34", - "reference": "9c208ba27ae7d90853c288b3795d6702eb251d34", + "url": "https://api.github.com/repos/jmespath/jmespath.php/zipball/2157c5e50e813ec6a96c1eed3be7f64a20fb32a8", + "reference": "2157c5e50e813ec6a96c1eed3be7f64a20fb32a8", "shasum": "" }, "require": { @@ -3169,22 +3175,22 @@ ], "support": { "issues": "https://github.com/jmespath/jmespath.php/issues", - "source": "https://github.com/jmespath/jmespath.php/tree/2.9.1" + "source": "https://github.com/jmespath/jmespath.php/tree/2.9.2" }, - "time": "2026-06-11T10:43:56+00:00" + "time": "2026-07-06T18:56:19+00:00" }, { "name": "nesbot/carbon", - "version": "3.13.0", + "version": "3.13.2", "source": { "type": "git", "url": "https://github.com/CarbonPHP/carbon.git", - "reference": "40f6618f052df16b545f626fbf9a878e6497d16a" + "reference": "a1c54919f5fff9800cd03c32bd01defd5a4061cb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/CarbonPHP/carbon/zipball/40f6618f052df16b545f626fbf9a878e6497d16a", - "reference": "40f6618f052df16b545f626fbf9a878e6497d16a", + "url": "https://api.github.com/repos/CarbonPHP/carbon/zipball/a1c54919f5fff9800cd03c32bd01defd5a4061cb", + "reference": "a1c54919f5fff9800cd03c32bd01defd5a4061cb", "shasum": "" }, "require": { @@ -3276,7 +3282,7 @@ "type": "tidelift" } ], - "time": "2026-06-18T13:49:15+00:00" + "time": "2026-08-08T11:40:35+00:00" }, { "name": "nette/schema", @@ -3438,20 +3444,19 @@ }, { "name": "nikic/php-parser", - "version": "v5.7.0", + "version": "v5.8.0", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "dca41cd15c2ac9d055ad70dbfd011130757d1f82" + "reference": "044a6a392ff8ad0d61f14370a5fbbd0a0107152f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/dca41cd15c2ac9d055ad70dbfd011130757d1f82", - "reference": "dca41cd15c2ac9d055ad70dbfd011130757d1f82", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/044a6a392ff8ad0d61f14370a5fbbd0a0107152f", + "reference": "044a6a392ff8ad0d61f14370a5fbbd0a0107152f", "shasum": "" }, "require": { - "ext-ctype": "*", "ext-json": "*", "ext-tokenizer": "*", "php": ">=7.4" @@ -3490,9 +3495,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v5.7.0" + "source": "https://github.com/nikic/PHP-Parser/tree/v5.8.0" }, - "time": "2025-12-06T11:56:16+00:00" + "time": "2026-07-04T14:30:18+00:00" }, { "name": "nunomaduro/termwind", @@ -3583,16 +3588,16 @@ }, { "name": "phpoption/phpoption", - "version": "1.9.5", + "version": "1.10.0", "source": { "type": "git", "url": "https://github.com/schmittjoh/php-option.git", - "reference": "75365b91986c2405cf5e1e012c5595cd487a98be" + "reference": "67b192b6a42ec03944b972d6e633ddec78ad2c6d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/75365b91986c2405cf5e1e012c5595cd487a98be", - "reference": "75365b91986c2405cf5e1e012c5595cd487a98be", + "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/67b192b6a42ec03944b972d6e633ddec78ad2c6d", + "reference": "67b192b6a42ec03944b972d6e633ddec78ad2c6d", "shasum": "" }, "require": { @@ -3600,7 +3605,7 @@ }, "require-dev": { "bamarni/composer-bin-plugin": "^1.8.2", - "phpunit/phpunit": "^8.5.44 || ^9.6.25 || ^10.5.53 || ^11.5.34" + "phpunit/phpunit": "^8.5.54 || ^9.6.36 || ^10.5.64 || ^11.5.56 || ^12.5.33" }, "type": "library", "extra": { @@ -3642,7 +3647,7 @@ ], "support": { "issues": "https://github.com/schmittjoh/php-option/issues", - "source": "https://github.com/schmittjoh/php-option/tree/1.9.5" + "source": "https://github.com/schmittjoh/php-option/tree/1.10.0" }, "funding": [ { @@ -3654,7 +3659,7 @@ "type": "tidelift" } ], - "time": "2025-12-27T19:41:33+00:00" + "time": "2026-08-24T00:54:40+00:00" }, { "name": "predis/predis", @@ -4132,16 +4137,16 @@ }, { "name": "psy/psysh", - "version": "v0.12.23", + "version": "v0.12.24", "source": { "type": "git", "url": "https://github.com/bobthecow/psysh.git", - "reference": "4dcc0f08047d52bbde475eda481146fd8e27e1a4" + "reference": "ca0fdcf8a7617afa3adfdf1b5fef573dffb69ca1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/bobthecow/psysh/zipball/4dcc0f08047d52bbde475eda481146fd8e27e1a4", - "reference": "4dcc0f08047d52bbde475eda481146fd8e27e1a4", + "url": "https://api.github.com/repos/bobthecow/psysh/zipball/ca0fdcf8a7617afa3adfdf1b5fef573dffb69ca1", + "reference": "ca0fdcf8a7617afa3adfdf1b5fef573dffb69ca1", "shasum": "" }, "require": { @@ -4205,30 +4210,33 @@ ], "support": { "issues": "https://github.com/bobthecow/psysh/issues", - "source": "https://github.com/bobthecow/psysh/tree/v0.12.23" + "source": "https://github.com/bobthecow/psysh/tree/v0.12.24" }, - "time": "2026-05-23T13:41:31+00:00" + "time": "2026-06-29T15:41:09+00:00" }, { "name": "pusher/pusher-php-server", - "version": "7.2.8", + "version": "7.3.0", "source": { "type": "git", "url": "https://github.com/pusher/pusher-http-php.git", - "reference": "4aa139ed2a2a805cd265449b691198beee1309d2" + "reference": "058d8464246118110a341fc2e6e70c7c8b6a7f2c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/pusher/pusher-http-php/zipball/4aa139ed2a2a805cd265449b691198beee1309d2", - "reference": "4aa139ed2a2a805cd265449b691198beee1309d2", + "url": "https://api.github.com/repos/pusher/pusher-http-php/zipball/058d8464246118110a341fc2e6e70c7c8b6a7f2c", + "reference": "058d8464246118110a341fc2e6e70c7c8b6a7f2c", "shasum": "" }, "require": { "ext-curl": "*", "ext-json": "*", - "guzzlehttp/guzzle": "^7.2", + "guzzlehttp/guzzle": "^7.8.2 || ^8.0", + "guzzlehttp/promises": "^2.0.3 || ^3.0", + "guzzlehttp/psr7": "^2.6.3 || ^3.0", "php": "^7.3|^8.0", - "psr/log": "^1.0|^2.0|^3.0" + "psr/http-client": "^1.0", + "psr/log": "^1.0 || ^2.0 || ^3.0" }, "require-dev": { "overtrue/phplint": "^2.3", @@ -4265,9 +4273,9 @@ ], "support": { "issues": "https://github.com/pusher/pusher-http-php/issues", - "source": "https://github.com/pusher/pusher-http-php/tree/7.2.8" + "source": "https://github.com/pusher/pusher-http-php/tree/7.3.0" }, - "time": "2026-05-18T13:11:36+00:00" + "time": "2026-08-07T12:47:11+00:00" }, { "name": "ralouphie/getallheaders", @@ -5136,16 +5144,16 @@ }, { "name": "symfony/console", - "version": "v7.4.14", + "version": "v7.4.18", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "92f58bc4bf97a92ed1b9f367f0cd44f20bde0e87" + "reference": "23d6f88a29f6d0eac45bd77d70307adf83ba7ab0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/92f58bc4bf97a92ed1b9f367f0cd44f20bde0e87", - "reference": "92f58bc4bf97a92ed1b9f367f0cd44f20bde0e87", + "url": "https://api.github.com/repos/symfony/console/zipball/23d6f88a29f6d0eac45bd77d70307adf83ba7ab0", + "reference": "23d6f88a29f6d0eac45bd77d70307adf83ba7ab0", "shasum": "" }, "require": { @@ -5210,7 +5218,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v7.4.14" + "source": "https://github.com/symfony/console/tree/v7.4.18" }, "funding": [ { @@ -5230,20 +5238,20 @@ "type": "tidelift" } ], - "time": "2026-06-16T11:50:14+00:00" + "time": "2026-08-25T14:18:37+00:00" }, { "name": "symfony/css-selector", - "version": "v7.4.9", + "version": "v7.4.18", "source": { "type": "git", "url": "https://github.com/symfony/css-selector.git", - "reference": "b75663ed96cf4756e28e3105476f220f92886cc4" + "reference": "fecf40067fc8d8880ea87b8ac227600b9aadd1d0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/css-selector/zipball/b75663ed96cf4756e28e3105476f220f92886cc4", - "reference": "b75663ed96cf4756e28e3105476f220f92886cc4", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/fecf40067fc8d8880ea87b8ac227600b9aadd1d0", + "reference": "fecf40067fc8d8880ea87b8ac227600b9aadd1d0", "shasum": "" }, "require": { @@ -5279,7 +5287,7 @@ "description": "Converts CSS selectors to XPath expressions", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/css-selector/tree/v7.4.9" + "source": "https://github.com/symfony/css-selector/tree/v7.4.18" }, "funding": [ { @@ -5299,7 +5307,7 @@ "type": "tidelift" } ], - "time": "2026-04-18T13:18:21+00:00" + "time": "2026-08-23T10:03:40+00:00" }, { "name": "symfony/deprecation-contracts", @@ -5374,16 +5382,16 @@ }, { "name": "symfony/error-handler", - "version": "v7.4.14", + "version": "v7.4.17", "source": { "type": "git", "url": "https://github.com/symfony/error-handler.git", - "reference": "4e1a093b481f323e6e326451f9760c3868430673" + "reference": "8373921e231e190a88e2ad526951bbaa791576fa" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/error-handler/zipball/4e1a093b481f323e6e326451f9760c3868430673", - "reference": "4e1a093b481f323e6e326451f9760c3868430673", + "url": "https://api.github.com/repos/symfony/error-handler/zipball/8373921e231e190a88e2ad526951bbaa791576fa", + "reference": "8373921e231e190a88e2ad526951bbaa791576fa", "shasum": "" }, "require": { @@ -5432,7 +5440,7 @@ "description": "Provides tools to manage errors and ease debugging PHP code", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/error-handler/tree/v7.4.14" + "source": "https://github.com/symfony/error-handler/tree/v7.4.17" }, "funding": [ { @@ -5452,20 +5460,20 @@ "type": "tidelift" } ], - "time": "2026-06-05T06:22:21+00:00" + "time": "2026-08-21T17:40:08+00:00" }, { "name": "symfony/event-dispatcher", - "version": "v7.4.14", + "version": "v7.4.17", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "51fe3d170227be8d1772214b82ae506e15ed78ff" + "reference": "d269974ee93c61d03620ffee358355bfdb471d66" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/51fe3d170227be8d1772214b82ae506e15ed78ff", - "reference": "51fe3d170227be8d1772214b82ae506e15ed78ff", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/d269974ee93c61d03620ffee358355bfdb471d66", + "reference": "d269974ee93c61d03620ffee358355bfdb471d66", "shasum": "" }, "require": { @@ -5517,7 +5525,7 @@ "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/event-dispatcher/tree/v7.4.14" + "source": "https://github.com/symfony/event-dispatcher/tree/v7.4.17" }, "funding": [ { @@ -5537,20 +5545,20 @@ "type": "tidelift" } ], - "time": "2026-06-06T11:10:32+00:00" + "time": "2026-08-21T17:40:08+00:00" }, { "name": "symfony/event-dispatcher-contracts", - "version": "v3.7.0", + "version": "v3.7.1", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher-contracts.git", - "reference": "ccba7060602b7fed0b03c85bf025257f76d9ef32" + "reference": "c7de7a00ffb67842132da02ea92988a39ccd9f4e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/ccba7060602b7fed0b03c85bf025257f76d9ef32", - "reference": "ccba7060602b7fed0b03c85bf025257f76d9ef32", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/c7de7a00ffb67842132da02ea92988a39ccd9f4e", + "reference": "c7de7a00ffb67842132da02ea92988a39ccd9f4e", "shasum": "" }, "require": { @@ -5597,7 +5605,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.7.0" + "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.7.1" }, "funding": [ { @@ -5617,20 +5625,20 @@ "type": "tidelift" } ], - "time": "2026-01-05T13:30:16+00:00" + "time": "2026-06-05T06:23:12+00:00" }, { "name": "symfony/filesystem", - "version": "v7.4.11", + "version": "v7.4.18", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "d721ea61b4a5fba8c5b6e7c1feda19efea144b50" + "reference": "90d412aa5277c6819db39e7605aa46b1019e3232" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/d721ea61b4a5fba8c5b6e7c1feda19efea144b50", - "reference": "d721ea61b4a5fba8c5b6e7c1feda19efea144b50", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/90d412aa5277c6819db39e7605aa46b1019e3232", + "reference": "90d412aa5277c6819db39e7605aa46b1019e3232", "shasum": "" }, "require": { @@ -5667,7 +5675,7 @@ "description": "Provides basic utilities for the filesystem", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/filesystem/tree/v7.4.11" + "source": "https://github.com/symfony/filesystem/tree/v7.4.18" }, "funding": [ { @@ -5687,20 +5695,20 @@ "type": "tidelift" } ], - "time": "2026-05-11T16:38:44+00:00" + "time": "2026-08-23T10:03:40+00:00" }, { "name": "symfony/finder", - "version": "v7.4.14", + "version": "v7.4.17", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "13b38720174286f55d1761152b575a8d1436fc25" + "reference": "5ce28827081f6d1f0c32eaf3882750f19cb5bbe6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/13b38720174286f55d1761152b575a8d1436fc25", - "reference": "13b38720174286f55d1761152b575a8d1436fc25", + "url": "https://api.github.com/repos/symfony/finder/zipball/5ce28827081f6d1f0c32eaf3882750f19cb5bbe6", + "reference": "5ce28827081f6d1f0c32eaf3882750f19cb5bbe6", "shasum": "" }, "require": { @@ -5735,7 +5743,7 @@ "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v7.4.14" + "source": "https://github.com/symfony/finder/tree/v7.4.17" }, "funding": [ { @@ -5755,20 +5763,20 @@ "type": "tidelift" } ], - "time": "2026-06-27T08:31:18+00:00" + "time": "2026-08-21T12:09:28+00:00" }, { "name": "symfony/http-client", - "version": "v7.4.14", + "version": "v7.4.18", "source": { "type": "git", "url": "https://github.com/symfony/http-client.git", - "reference": "f6bc6b5a54ff5afac4725cacec9bf2f52eb15920" + "reference": "68d81f78d127984ff2e741f0e0d9cb1078f8f3ea" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-client/zipball/f6bc6b5a54ff5afac4725cacec9bf2f52eb15920", - "reference": "f6bc6b5a54ff5afac4725cacec9bf2f52eb15920", + "url": "https://api.github.com/repos/symfony/http-client/zipball/68d81f78d127984ff2e741f0e0d9cb1078f8f3ea", + "reference": "68d81f78d127984ff2e741f0e0d9cb1078f8f3ea", "shasum": "" }, "require": { @@ -5836,7 +5844,7 @@ "http" ], "support": { - "source": "https://github.com/symfony/http-client/tree/v7.4.14" + "source": "https://github.com/symfony/http-client/tree/v7.4.18" }, "funding": [ { @@ -5856,20 +5864,20 @@ "type": "tidelift" } ], - "time": "2026-06-16T11:50:14+00:00" + "time": "2026-08-30T13:49:59+00:00" }, { "name": "symfony/http-client-contracts", - "version": "v3.7.0", + "version": "v3.7.3", "source": { "type": "git", "url": "https://github.com/symfony/http-client-contracts.git", - "reference": "4a2d00c37651c0bdc2b9e1c773487a8bf4edb12d" + "reference": "35be0019e2c2c9fba80f9dc033290a5240f7b44f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/4a2d00c37651c0bdc2b9e1c773487a8bf4edb12d", - "reference": "4a2d00c37651c0bdc2b9e1c773487a8bf4edb12d", + "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/35be0019e2c2c9fba80f9dc033290a5240f7b44f", + "reference": "35be0019e2c2c9fba80f9dc033290a5240f7b44f", "shasum": "" }, "require": { @@ -5918,7 +5926,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/http-client-contracts/tree/v3.7.0" + "source": "https://github.com/symfony/http-client-contracts/tree/v3.7.3" }, "funding": [ { @@ -5938,20 +5946,20 @@ "type": "tidelift" } ], - "time": "2026-03-06T13:17:50+00:00" + "time": "2026-08-04T08:41:16+00:00" }, { "name": "symfony/http-foundation", - "version": "v7.4.14", + "version": "v7.4.18", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "06db5ae1552177bf8572f8908839f12e3c06aed3" + "reference": "d070b716a32fbe3bf04204db0f58ace73b86d133" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/06db5ae1552177bf8572f8908839f12e3c06aed3", - "reference": "06db5ae1552177bf8572f8908839f12e3c06aed3", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/d070b716a32fbe3bf04204db0f58ace73b86d133", + "reference": "d070b716a32fbe3bf04204db0f58ace73b86d133", "shasum": "" }, "require": { @@ -6000,7 +6008,7 @@ "description": "Defines an object-oriented layer for the HTTP specification", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-foundation/tree/v7.4.14" + "source": "https://github.com/symfony/http-foundation/tree/v7.4.18" }, "funding": [ { @@ -6020,20 +6028,20 @@ "type": "tidelift" } ], - "time": "2026-06-11T07:31:44+00:00" + "time": "2026-08-30T20:10:52+00:00" }, { "name": "symfony/http-kernel", - "version": "v7.4.14", + "version": "v7.4.18", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "e99af79b1e776646eda0e1c23b7b45c184ff99be" + "reference": "275d2d2d24530f2a0eaf17704a3a93860a036351" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/e99af79b1e776646eda0e1c23b7b45c184ff99be", - "reference": "e99af79b1e776646eda0e1c23b7b45c184ff99be", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/275d2d2d24530f2a0eaf17704a3a93860a036351", + "reference": "275d2d2d24530f2a0eaf17704a3a93860a036351", "shasum": "" }, "require": { @@ -6091,7 +6099,7 @@ "symfony/validator": "^6.4|^7.0|^8.0", "symfony/var-dumper": "^6.4|^7.0|^8.0", "symfony/var-exporter": "^6.4|^7.0|^8.0", - "twig/twig": "^3.12" + "twig/twig": "^3.12|^4.0" }, "type": "library", "autoload": { @@ -6119,7 +6127,7 @@ "description": "Provides a structured process for converting a Request into a Response", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-kernel/tree/v7.4.14" + "source": "https://github.com/symfony/http-kernel/tree/v7.4.18" }, "funding": [ { @@ -6139,20 +6147,20 @@ "type": "tidelift" } ], - "time": "2026-06-27T09:14:35+00:00" + "time": "2026-08-30T21:24:29+00:00" }, { "name": "symfony/mailer", - "version": "v7.4.14", + "version": "v7.4.17", "source": { "type": "git", "url": "https://github.com/symfony/mailer.git", - "reference": "f88ce03ae73e3edb5c176ce1f337709996e88495" + "reference": "b17c9bf3a551d5f635638a3b6c05f06c4dc87584" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mailer/zipball/f88ce03ae73e3edb5c176ce1f337709996e88495", - "reference": "f88ce03ae73e3edb5c176ce1f337709996e88495", + "url": "https://api.github.com/repos/symfony/mailer/zipball/b17c9bf3a551d5f635638a3b6c05f06c4dc87584", + "reference": "b17c9bf3a551d5f635638a3b6c05f06c4dc87584", "shasum": "" }, "require": { @@ -6203,7 +6211,7 @@ "description": "Helps sending emails", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/mailer/tree/v7.4.14" + "source": "https://github.com/symfony/mailer/tree/v7.4.17" }, "funding": [ { @@ -6223,20 +6231,20 @@ "type": "tidelift" } ], - "time": "2026-06-13T08:51:35+00:00" + "time": "2026-08-21T17:40:08+00:00" }, { "name": "symfony/mailgun-mailer", - "version": "v7.4.14", + "version": "v7.4.18", "source": { "type": "git", "url": "https://github.com/symfony/mailgun-mailer.git", - "reference": "361c9b3dcf7bdfbcbc5b3d7837f837d9ffd96679" + "reference": "9dd193391d38e21703455a5b41fbe8b836bbac9c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mailgun-mailer/zipball/361c9b3dcf7bdfbcbc5b3d7837f837d9ffd96679", - "reference": "361c9b3dcf7bdfbcbc5b3d7837f837d9ffd96679", + "url": "https://api.github.com/repos/symfony/mailgun-mailer/zipball/9dd193391d38e21703455a5b41fbe8b836bbac9c", + "reference": "9dd193391d38e21703455a5b41fbe8b836bbac9c", "shasum": "" }, "require": { @@ -6276,7 +6284,7 @@ "description": "Symfony Mailgun Mailer Bridge", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/mailgun-mailer/tree/v7.4.14" + "source": "https://github.com/symfony/mailgun-mailer/tree/v7.4.18" }, "funding": [ { @@ -6296,20 +6304,20 @@ "type": "tidelift" } ], - "time": "2026-06-08T20:24:16+00:00" + "time": "2026-08-30T00:47:26+00:00" }, { "name": "symfony/mime", - "version": "v7.4.13", + "version": "v7.4.18", "source": { "type": "git", "url": "https://github.com/symfony/mime.git", - "reference": "a845722765c4f6b2ce88beaf4f4479975b186770" + "reference": "bf328d82105831db3e409195db0540ff57f27c80" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mime/zipball/a845722765c4f6b2ce88beaf4f4479975b186770", - "reference": "a845722765c4f6b2ce88beaf4f4479975b186770", + "url": "https://api.github.com/repos/symfony/mime/zipball/bf328d82105831db3e409195db0540ff57f27c80", + "reference": "bf328d82105831db3e409195db0540ff57f27c80", "shasum": "" }, "require": { @@ -6333,7 +6341,7 @@ "symfony/process": "^6.4|^7.0|^8.0", "symfony/property-access": "^6.4|^7.0|^8.0", "symfony/property-info": "^6.4|^7.0|^8.0", - "symfony/serializer": "^6.4.3|^7.0.3|^8.0" + "symfony/serializer": "^6.4.44|^7.4.17|^8.1.5" }, "type": "library", "autoload": { @@ -6365,7 +6373,7 @@ "mime-type" ], "support": { - "source": "https://github.com/symfony/mime/tree/v7.4.13" + "source": "https://github.com/symfony/mime/tree/v7.4.18" }, "funding": [ { @@ -6385,7 +6393,7 @@ "type": "tidelift" } ], - "time": "2026-05-23T16:22:37+00:00" + "time": "2026-08-22T09:04:42+00:00" }, { "name": "symfony/polyfill-ctype", @@ -6472,16 +6480,16 @@ }, { "name": "symfony/polyfill-intl-grapheme", - "version": "v1.38.1", + "version": "v1.41.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-grapheme.git", - "reference": "e9247d281d694a5120554d9afaf54e070e88a603" + "reference": "bb899c1db0aa8127dc3afe8cda4a67eb24915f8d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/e9247d281d694a5120554d9afaf54e070e88a603", - "reference": "e9247d281d694a5120554d9afaf54e070e88a603", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/bb899c1db0aa8127dc3afe8cda4a67eb24915f8d", + "reference": "bb899c1db0aa8127dc3afe8cda4a67eb24915f8d", "shasum": "" }, "require": { @@ -6530,7 +6538,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.38.1" + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.41.0" }, "funding": [ { @@ -6550,20 +6558,20 @@ "type": "tidelift" } ], - "time": "2026-05-26T05:58:03+00:00" + "time": "2026-07-28T08:25:59+00:00" }, { "name": "symfony/polyfill-intl-idn", - "version": "v1.38.1", + "version": "v1.42.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-idn.git", - "reference": "dc21118016c039a66235cf93d96b435ffb282412" + "reference": "51b5ff5ba85452b31ec6f55490b08148612339d9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/dc21118016c039a66235cf93d96b435ffb282412", - "reference": "dc21118016c039a66235cf93d96b435ffb282412", + "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/51b5ff5ba85452b31ec6f55490b08148612339d9", + "reference": "51b5ff5ba85452b31ec6f55490b08148612339d9", "shasum": "" }, "require": { @@ -6617,7 +6625,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.38.1" + "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.42.0" }, "funding": [ { @@ -6637,20 +6645,20 @@ "type": "tidelift" } ], - "time": "2026-05-25T15:22:23+00:00" + "time": "2026-08-24T10:51:20+00:00" }, { "name": "symfony/polyfill-intl-normalizer", - "version": "v1.38.0", + "version": "v1.42.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-normalizer.git", - "reference": "2d446c214bdbe5b71bde5011b060a05fece3ae6b" + "reference": "aa20edea75bd9c48cfecc8360922e5a6e5c44502" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/2d446c214bdbe5b71bde5011b060a05fece3ae6b", - "reference": "2d446c214bdbe5b71bde5011b060a05fece3ae6b", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/aa20edea75bd9c48cfecc8360922e5a6e5c44502", + "reference": "aa20edea75bd9c48cfecc8360922e5a6e5c44502", "shasum": "" }, "require": { @@ -6702,7 +6710,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.38.0" + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.42.0" }, "funding": [ { @@ -6722,7 +6730,7 @@ "type": "tidelift" } ], - "time": "2026-05-25T13:48:31+00:00" + "time": "2026-08-07T06:33:24+00:00" }, { "name": "symfony/polyfill-mbstring", @@ -6895,16 +6903,16 @@ }, { "name": "symfony/polyfill-php83", - "version": "v1.38.2", + "version": "v1.41.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php83.git", - "reference": "796a26abb75ce49f3a84433cd81bf1009d73d5f8" + "reference": "5ea99087fb99c273a9b9236ed4c31e78b16103c6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/796a26abb75ce49f3a84433cd81bf1009d73d5f8", - "reference": "796a26abb75ce49f3a84433cd81bf1009d73d5f8", + "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/5ea99087fb99c273a9b9236ed4c31e78b16103c6", + "reference": "5ea99087fb99c273a9b9236ed4c31e78b16103c6", "shasum": "" }, "require": { @@ -6951,7 +6959,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php83/tree/v1.38.2" + "source": "https://github.com/symfony/polyfill-php83/tree/v1.41.0" }, "funding": [ { @@ -6971,7 +6979,7 @@ "type": "tidelift" } ], - "time": "2026-05-27T06:51:48+00:00" + "time": "2026-07-01T12:47:55+00:00" }, { "name": "symfony/polyfill-php84", @@ -7055,16 +7063,16 @@ }, { "name": "symfony/polyfill-php85", - "version": "v1.38.1", + "version": "v1.41.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php85.git", - "reference": "ba2ba04f3352cfa2dcbbcb90aee13ed967f505b1" + "reference": "255fab485aaa1006ed411040c42aecd7b5302d7a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php85/zipball/ba2ba04f3352cfa2dcbbcb90aee13ed967f505b1", - "reference": "ba2ba04f3352cfa2dcbbcb90aee13ed967f505b1", + "url": "https://api.github.com/repos/symfony/polyfill-php85/zipball/255fab485aaa1006ed411040c42aecd7b5302d7a", + "reference": "255fab485aaa1006ed411040c42aecd7b5302d7a", "shasum": "" }, "require": { @@ -7111,7 +7119,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php85/tree/v1.38.1" + "source": "https://github.com/symfony/polyfill-php85/tree/v1.41.0" }, "funding": [ { @@ -7131,7 +7139,7 @@ "type": "tidelift" } ], - "time": "2026-05-26T02:25:22+00:00" + "time": "2026-07-01T12:47:55+00:00" }, { "name": "symfony/polyfill-uuid", @@ -7218,16 +7226,16 @@ }, { "name": "symfony/process", - "version": "v7.4.13", + "version": "v7.4.18", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "f5804be144caceb570f6747519999636b664f24c" + "reference": "058d17fc284cce14efb2385783b55014a461b176" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/f5804be144caceb570f6747519999636b664f24c", - "reference": "f5804be144caceb570f6747519999636b664f24c", + "url": "https://api.github.com/repos/symfony/process/zipball/058d17fc284cce14efb2385783b55014a461b176", + "reference": "058d17fc284cce14efb2385783b55014a461b176", "shasum": "" }, "require": { @@ -7259,7 +7267,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v7.4.13" + "source": "https://github.com/symfony/process/tree/v7.4.18" }, "funding": [ { @@ -7279,20 +7287,20 @@ "type": "tidelift" } ], - "time": "2026-05-23T16:05:06+00:00" + "time": "2026-08-21T17:40:08+00:00" }, { "name": "symfony/routing", - "version": "v7.4.13", + "version": "v7.4.18", "source": { "type": "git", "url": "https://github.com/symfony/routing.git", - "reference": "3a162171bb008e5e0f15dce6581373a4c0e8390d" + "reference": "ddd558991e98f693ae6bf5063cc1b0362c6bbec3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/routing/zipball/3a162171bb008e5e0f15dce6581373a4c0e8390d", - "reference": "3a162171bb008e5e0f15dce6581373a4c0e8390d", + "url": "https://api.github.com/repos/symfony/routing/zipball/ddd558991e98f693ae6bf5063cc1b0362c6bbec3", + "reference": "ddd558991e98f693ae6bf5063cc1b0362c6bbec3", "shasum": "" }, "require": { @@ -7344,7 +7352,7 @@ "url" ], "support": { - "source": "https://github.com/symfony/routing/tree/v7.4.13" + "source": "https://github.com/symfony/routing/tree/v7.4.18" }, "funding": [ { @@ -7364,20 +7372,20 @@ "type": "tidelift" } ], - "time": "2026-05-24T11:20:33+00:00" + "time": "2026-08-17T13:12:36+00:00" }, { "name": "symfony/service-contracts", - "version": "v3.7.0", + "version": "v3.7.3", "source": { "type": "git", "url": "https://github.com/symfony/service-contracts.git", - "reference": "d25d82433a80eba6aa0e6c24b61d7370d99e444a" + "reference": "15e6a07ec2a2c75ceb1b21dd98105ee8456d2257" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/d25d82433a80eba6aa0e6c24b61d7370d99e444a", - "reference": "d25d82433a80eba6aa0e6c24b61d7370d99e444a", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/15e6a07ec2a2c75ceb1b21dd98105ee8456d2257", + "reference": "15e6a07ec2a2c75ceb1b21dd98105ee8456d2257", "shasum": "" }, "require": { @@ -7431,7 +7439,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/service-contracts/tree/v3.7.0" + "source": "https://github.com/symfony/service-contracts/tree/v3.7.3" }, "funding": [ { @@ -7451,20 +7459,20 @@ "type": "tidelift" } ], - "time": "2026-03-28T09:44:51+00:00" + "time": "2026-07-27T15:39:01+00:00" }, { "name": "symfony/string", - "version": "v7.4.13", + "version": "v7.4.15", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "961683010db3b27ec6ebcd7308e6e1ee8fa7ffde" + "reference": "e394af32256bf9e7bf80849d95e589167c10097b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/961683010db3b27ec6ebcd7308e6e1ee8fa7ffde", - "reference": "961683010db3b27ec6ebcd7308e6e1ee8fa7ffde", + "url": "https://api.github.com/repos/symfony/string/zipball/e394af32256bf9e7bf80849d95e589167c10097b", + "reference": "e394af32256bf9e7bf80849d95e589167c10097b", "shasum": "" }, "require": { @@ -7522,7 +7530,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v7.4.13" + "source": "https://github.com/symfony/string/tree/v7.4.15" }, "funding": [ { @@ -7542,20 +7550,20 @@ "type": "tidelift" } ], - "time": "2026-05-23T15:23:29+00:00" + "time": "2026-07-28T07:33:02+00:00" }, { "name": "symfony/translation", - "version": "v7.4.14", + "version": "v7.4.17", "source": { "type": "git", "url": "https://github.com/symfony/translation.git", - "reference": "a1af4dacb24eb7ef4f1ca71b94da8ddbce572281" + "reference": "2ee1e4a3b32a528a642babe041ff7c440b213b4b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation/zipball/a1af4dacb24eb7ef4f1ca71b94da8ddbce572281", - "reference": "a1af4dacb24eb7ef4f1ca71b94da8ddbce572281", + "url": "https://api.github.com/repos/symfony/translation/zipball/2ee1e4a3b32a528a642babe041ff7c440b213b4b", + "reference": "2ee1e4a3b32a528a642babe041ff7c440b213b4b", "shasum": "" }, "require": { @@ -7622,7 +7630,7 @@ "description": "Provides tools to internationalize your application", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/translation/tree/v7.4.14" + "source": "https://github.com/symfony/translation/tree/v7.4.17" }, "funding": [ { @@ -7642,20 +7650,20 @@ "type": "tidelift" } ], - "time": "2026-06-06T09:33:19+00:00" + "time": "2026-08-21T17:40:08+00:00" }, { "name": "symfony/translation-contracts", - "version": "v3.7.0", + "version": "v3.7.1", "source": { "type": "git", "url": "https://github.com/symfony/translation-contracts.git", - "reference": "0ab302977a952b42fd51475c4ebac81f8da0a95d" + "reference": "ccb206b98faccc511ebae8e5fad50f2dc0b30621" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/0ab302977a952b42fd51475c4ebac81f8da0a95d", - "reference": "0ab302977a952b42fd51475c4ebac81f8da0a95d", + "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/ccb206b98faccc511ebae8e5fad50f2dc0b30621", + "reference": "ccb206b98faccc511ebae8e5fad50f2dc0b30621", "shasum": "" }, "require": { @@ -7704,7 +7712,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/translation-contracts/tree/v3.7.0" + "source": "https://github.com/symfony/translation-contracts/tree/v3.7.1" }, "funding": [ { @@ -7724,20 +7732,20 @@ "type": "tidelift" } ], - "time": "2026-01-05T13:30:16+00:00" + "time": "2026-06-05T06:23:12+00:00" }, { "name": "symfony/uid", - "version": "v7.4.9", + "version": "v7.4.17", "source": { "type": "git", "url": "https://github.com/symfony/uid.git", - "reference": "2676b524340abcfe4d6151ec698463cebafee439" + "reference": "69d732355a139c6f8881337d28515aa01f12b8be" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/uid/zipball/2676b524340abcfe4d6151ec698463cebafee439", - "reference": "2676b524340abcfe4d6151ec698463cebafee439", + "url": "https://api.github.com/repos/symfony/uid/zipball/69d732355a139c6f8881337d28515aa01f12b8be", + "reference": "69d732355a139c6f8881337d28515aa01f12b8be", "shasum": "" }, "require": { @@ -7782,7 +7790,7 @@ "uuid" ], "support": { - "source": "https://github.com/symfony/uid/tree/v7.4.9" + "source": "https://github.com/symfony/uid/tree/v7.4.17" }, "funding": [ { @@ -7802,20 +7810,20 @@ "type": "tidelift" } ], - "time": "2026-04-30T15:19:22+00:00" + "time": "2026-08-11T07:38:58+00:00" }, { "name": "symfony/var-dumper", - "version": "v7.4.14", + "version": "v7.4.18", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "9a3a56a4a1e65a5cb4f8d13801fe8ab0a170e358" + "reference": "e088da50b813f32473a76871616cbb8fa54653a8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/9a3a56a4a1e65a5cb4f8d13801fe8ab0a170e358", - "reference": "9a3a56a4a1e65a5cb4f8d13801fe8ab0a170e358", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/e088da50b813f32473a76871616cbb8fa54653a8", + "reference": "e088da50b813f32473a76871616cbb8fa54653a8", "shasum": "" }, "require": { @@ -7831,7 +7839,7 @@ "symfony/http-kernel": "^6.4|^7.0|^8.0", "symfony/process": "^6.4|^7.0|^8.0", "symfony/uid": "^6.4|^7.0|^8.0", - "twig/twig": "^3.12" + "twig/twig": "^3.12|^4.0" }, "bin": [ "Resources/bin/var-dump-server" @@ -7869,7 +7877,7 @@ "dump" ], "support": { - "source": "https://github.com/symfony/var-dumper/tree/v7.4.14" + "source": "https://github.com/symfony/var-dumper/tree/v7.4.18" }, "funding": [ { @@ -7889,7 +7897,7 @@ "type": "tidelift" } ], - "time": "2026-06-08T20:24:16+00:00" + "time": "2026-08-30T20:10:52+00:00" }, { "name": "tijsverkoyen/css-to-inline-styles", @@ -7948,23 +7956,23 @@ }, { "name": "vlucas/phpdotenv", - "version": "v5.6.3", + "version": "v5.7.0", "source": { "type": "git", "url": "https://github.com/vlucas/phpdotenv.git", - "reference": "955e7815d677a3eaa7075231212f2110983adecc" + "reference": "301c07936b16d88628b126b01d082ba153cf4c40" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/955e7815d677a3eaa7075231212f2110983adecc", - "reference": "955e7815d677a3eaa7075231212f2110983adecc", + "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/301c07936b16d88628b126b01d082ba153cf4c40", + "reference": "301c07936b16d88628b126b01d082ba153cf4c40", "shasum": "" }, "require": { "ext-pcre": "*", - "graham-campbell/result-type": "^1.1.4", + "graham-campbell/result-type": "^1.2", "php": "^7.2.5 || ^8.0", - "phpoption/phpoption": "^1.9.5", + "phpoption/phpoption": "^1.10", "symfony/polyfill-ctype": "^1.26", "symfony/polyfill-mbstring": "^1.26", "symfony/polyfill-php80": "^1.26" @@ -8008,7 +8016,7 @@ "homepage": "https://github.com/vlucas" } ], - "description": "Loads environment variables from `.env` to `getenv()`, `$_ENV` and `$_SERVER` automagically.", + "description": "Loads environment variables from `.env` to `$_ENV` and `$_SERVER` automagically, and optionally to `getenv()`.", "keywords": [ "dotenv", "env", @@ -8016,7 +8024,7 @@ ], "support": { "issues": "https://github.com/vlucas/phpdotenv/issues", - "source": "https://github.com/vlucas/phpdotenv/tree/v5.6.3" + "source": "https://github.com/vlucas/phpdotenv/tree/v5.7.0" }, "funding": [ { @@ -8028,7 +8036,7 @@ "type": "tidelift" } ], - "time": "2025-12-27T19:49:13+00:00" + "time": "2026-08-24T18:07:49+00:00" }, { "name": "voku/portable-ascii", @@ -8242,19 +8250,21 @@ }, { "name": "hamcrest/hamcrest-php", - "version": "v2.1.1", + "version": "v3.0.0", "source": { "type": "git", "url": "https://github.com/hamcrest/hamcrest-php.git", - "reference": "f8b1c0173b22fa6ec77a81fe63e5b01eba7e6487" + "reference": "b61cd040da1a4925bc90a51c074f5297e7c0fa52" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/hamcrest/hamcrest-php/zipball/f8b1c0173b22fa6ec77a81fe63e5b01eba7e6487", - "reference": "f8b1c0173b22fa6ec77a81fe63e5b01eba7e6487", + "url": "https://api.github.com/repos/hamcrest/hamcrest-php/zipball/b61cd040da1a4925bc90a51c074f5297e7c0fa52", + "reference": "b61cd040da1a4925bc90a51c074f5297e7c0fa52", "shasum": "" }, "require": { + "ext-ctype": "*", + "ext-dom": "*", "php": "^7.4|^8.0" }, "replace": { @@ -8263,13 +8273,15 @@ "kodova/hamcrest-php": "*" }, "require-dev": { + "phpstan/phpstan": "^2.1", + "phpstan/phpstan-phpunit": "^2.0", "phpunit/php-file-iterator": "^1.4 || ^2.0 || ^3.0", "phpunit/phpunit": "^4.8.36 || ^5.7 || ^6.5 || ^7.0 || ^8.0 || ^9.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.1-dev" + "dev-master": "3.0-dev" } }, "autoload": { @@ -8287,9 +8299,9 @@ ], "support": { "issues": "https://github.com/hamcrest/hamcrest-php/issues", - "source": "https://github.com/hamcrest/hamcrest-php/tree/v2.1.1" + "source": "https://github.com/hamcrest/hamcrest-php/tree/v3.0.0" }, - "time": "2025-04-30T06:54:44+00:00" + "time": "2026-03-17T11:56:53+00:00" }, { "name": "laravel/boost", @@ -8512,16 +8524,16 @@ }, { "name": "laravel/pint", - "version": "v1.29.3", + "version": "v1.31.0", "source": { "type": "git", "url": "https://github.com/laravel/pint.git", - "reference": "da1d1111a6aa2e082d2a388b194afe1ba0a05d14" + "reference": "0730363418ad06a7b267d8f2a4f87eed0ab7f610" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/pint/zipball/da1d1111a6aa2e082d2a388b194afe1ba0a05d14", - "reference": "da1d1111a6aa2e082d2a388b194afe1ba0a05d14", + "url": "https://api.github.com/repos/laravel/pint/zipball/0730363418ad06a7b267d8f2a4f87eed0ab7f610", + "reference": "0730363418ad06a7b267d8f2a4f87eed0ab7f610", "shasum": "" }, "require": { @@ -8529,17 +8541,19 @@ "ext-mbstring": "*", "ext-tokenizer": "*", "ext-xml": "*", - "php": "^8.2.0" + "php": "^8.3.0" }, "require-dev": { - "friendsofphp/php-cs-fixer": "^3.95.8", - "illuminate/view": "^12.62.0", - "larastan/larastan": "^3.10.0", - "laravel-zero/framework": "^12.1.0", + "composer/semver": "^3.4.4", + "friendsofphp/php-cs-fixer": "^3.95.24", + "illuminate/view": "^13.30.1", + "larastan/larastan": "^3.11.0", + "laravel-zero/framework": "^13.0.0", "laravel/agent-detector": "^2.0.2", - "mockery/mockery": "^1.6.12", + "laravel/prompts": "^0.3.24", + "mockery/mockery": "^1.6.15", "nunomaduro/termwind": "^2.4.0", - "pestphp/pest": "^3.8.6" + "pestphp/pest": "^4.7.8" }, "bin": [ "builds/pint" @@ -8576,7 +8590,7 @@ "issues": "https://github.com/laravel/pint/issues", "source": "https://github.com/laravel/pint" }, - "time": "2026-06-16T15:34:04+00:00" + "time": "2026-09-07T15:03:30+00:00" }, { "name": "laravel/roster", @@ -8641,16 +8655,16 @@ }, { "name": "laravel/sail", - "version": "v1.63.0", + "version": "v1.67.0", "source": { "type": "git", "url": "https://github.com/laravel/sail.git", - "reference": "51bbce3f803c1d386cabbb44e618c955a12ff5fc" + "reference": "639e03ac12cf23def171770bcab05758045b2642" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/sail/zipball/51bbce3f803c1d386cabbb44e618c955a12ff5fc", - "reference": "51bbce3f803c1d386cabbb44e618c955a12ff5fc", + "url": "https://api.github.com/repos/laravel/sail/zipball/639e03ac12cf23def171770bcab05758045b2642", + "reference": "639e03ac12cf23def171770bcab05758045b2642", "shasum": "" }, "require": { @@ -8700,33 +8714,32 @@ "issues": "https://github.com/laravel/sail/issues", "source": "https://github.com/laravel/sail" }, - "time": "2026-06-18T08:54:14+00:00" + "time": "2026-08-12T13:55:56+00:00" }, { "name": "mockery/mockery", - "version": "1.6.12", + "version": "1.6.15", "source": { "type": "git", "url": "https://github.com/mockery/mockery.git", - "reference": "1f4efdd7d3beafe9807b08156dfcb176d18f1699" + "reference": "967a801bd188989a5669bd280f252d51c0fdc9ee" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/mockery/mockery/zipball/1f4efdd7d3beafe9807b08156dfcb176d18f1699", - "reference": "1f4efdd7d3beafe9807b08156dfcb176d18f1699", + "url": "https://api.github.com/repos/mockery/mockery/zipball/967a801bd188989a5669bd280f252d51c0fdc9ee", + "reference": "967a801bd188989a5669bd280f252d51c0fdc9ee", "shasum": "" }, "require": { - "hamcrest/hamcrest-php": "^2.0.1", - "lib-pcre": ">=7.0", + "hamcrest/hamcrest-php": "^2.0 || ^3.0", "php": ">=7.3" }, "conflict": { "phpunit/phpunit": "<8.0" }, "require-dev": { - "phpunit/phpunit": "^8.5 || ^9.6.17", - "symplify/easy-coding-standard": "^12.1.14" + "phpunit/phpunit": "^9.6.36", + "symplify/easy-coding-standard": "^13.2.17" }, "type": "library", "autoload": { @@ -8783,24 +8796,24 @@ "security": "https://github.com/mockery/mockery/security/advisories", "source": "https://github.com/mockery/mockery" }, - "time": "2024-05-16T03:13:13+00:00" + "time": "2026-08-19T19:37:52+00:00" }, { "name": "myclabs/deep-copy", - "version": "1.13.4", + "version": "1.14.0", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a" + "reference": "8680aa248f8e07bc8fb43f56f0f5fc77a0c96aae" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/07d290f0c47959fd5eed98c95ee5602db07e0b6a", - "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/8680aa248f8e07bc8fb43f56f0f5fc77a0c96aae", + "reference": "8680aa248f8e07bc8fb43f56f0f5fc77a0c96aae", "shasum": "" }, "require": { - "php": "^7.1 || ^8.0" + "php": "^8.0" }, "conflict": { "doctrine/collections": "<1.6.8", @@ -8835,35 +8848,35 @@ ], "support": { "issues": "https://github.com/myclabs/DeepCopy/issues", - "source": "https://github.com/myclabs/DeepCopy/tree/1.13.4" + "source": "https://github.com/myclabs/DeepCopy/tree/1.14.0" }, "funding": [ { - "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", - "type": "tidelift" + "url": "https://github.com/mnapoli", + "type": "github" } ], - "time": "2025-08-01T08:46:24+00:00" + "time": "2026-08-11T10:17:44+00:00" }, { "name": "nunomaduro/collision", - "version": "v8.9.4", + "version": "v8.9.5", "source": { "type": "git", "url": "https://github.com/nunomaduro/collision.git", - "reference": "716af8f95a470e9094cfca09ed897b023be191a5" + "reference": "fb53eacd509a1d303858e2d20cfebf2d630254ec" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nunomaduro/collision/zipball/716af8f95a470e9094cfca09ed897b023be191a5", - "reference": "716af8f95a470e9094cfca09ed897b023be191a5", + "url": "https://api.github.com/repos/nunomaduro/collision/zipball/fb53eacd509a1d303858e2d20cfebf2d630254ec", + "reference": "fb53eacd509a1d303858e2d20cfebf2d630254ec", "shasum": "" }, "require": { "filp/whoops": "^2.18.4", "nunomaduro/termwind": "^2.4.0", "php": "^8.2.0", - "symfony/console": "^7.4.8 || ^8.0.8" + "symfony/console": "^7.4.14 || ^8.1.1" }, "conflict": { "laravel/framework": "<11.48.0 || >=14.0.0", @@ -8871,12 +8884,12 @@ }, "require-dev": { "brianium/paratest": "^7.8.5", - "larastan/larastan": "^3.9.6", - "laravel/framework": "^11.48.0 || ^12.56.0 || ^13.5.0", - "laravel/pint": "^1.29.1", - "orchestra/testbench-core": "^9.12.0 || ^10.12.1 || ^11.2.1", - "pestphp/pest": "^3.8.5 || ^4.4.3 || ^5.0.0", - "sebastian/environment": "^7.2.1 || ^8.0.4 || ^9.3.0" + "larastan/larastan": "^3.10.0", + "laravel/framework": "^11.48.0 || ^12.56.0 || ^13.20.0", + "laravel/pint": "^1.29.3", + "orchestra/testbench-core": "^9.12.0 || ^10.12.1 || ^11.3.5", + "pestphp/pest": "^3.8.5 || ^4.7.5 || ^5.0.0", + "sebastian/environment": "^7.2.1 || ^8.1.2 || ^9.3.2" }, "type": "library", "extra": { @@ -8939,7 +8952,7 @@ "type": "patreon" } ], - "time": "2026-04-21T14:04:20+00:00" + "time": "2026-07-15T19:09:14+00:00" }, { "name": "phar-io/manifest", @@ -9408,24 +9421,24 @@ }, { "name": "phpunit/phpunit", - "version": "11.5.55", + "version": "11.5.56", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "adc7262fccc12de2b30f12a8aa0b33775d814f00" + "reference": "5f83edffa6967c3db468d48a695ec7bcb02e9256" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/adc7262fccc12de2b30f12a8aa0b33775d814f00", - "reference": "adc7262fccc12de2b30f12a8aa0b33775d814f00", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/5f83edffa6967c3db468d48a695ec7bcb02e9256", + "reference": "5f83edffa6967c3db468d48a695ec7bcb02e9256", "shasum": "" }, "require": { "ext-dom": "*", + "ext-filter": "*", "ext-json": "*", "ext-libxml": "*", "ext-mbstring": "*", - "ext-xml": "*", "ext-xmlwriter": "*", "myclabs/deep-copy": "^1.13.4", "phar-io/manifest": "^2.0.4", @@ -9490,31 +9503,15 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/11.5.55" + "source": "https://github.com/sebastianbergmann/phpunit/tree/11.5.56" }, "funding": [ { - "url": "https://phpunit.de/sponsors.html", - "type": "custom" - }, - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - }, - { - "url": "https://liberapay.com/sebastianbergmann", - "type": "liberapay" - }, - { - "url": "https://thanks.dev/u/gh/sebastianbergmann", - "type": "thanks_dev" - }, - { - "url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit", - "type": "tidelift" + "url": "https://phpunit.de/sponsoring.html", + "type": "other" } ], - "time": "2026-02-18T12:37:06+00:00" + "time": "2026-07-06T14:52:39+00:00" }, { "name": "sebastian/cli-parser", @@ -10556,16 +10553,16 @@ }, { "name": "symfony/yaml", - "version": "v7.4.14", + "version": "v7.4.18", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "f8f328665ace2370d1e10645b807ba1646dc7dcc" + "reference": "4cef939e55b8a21c5780418de0d98ab00d0736e1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/f8f328665ace2370d1e10645b807ba1646dc7dcc", - "reference": "f8f328665ace2370d1e10645b807ba1646dc7dcc", + "url": "https://api.github.com/repos/symfony/yaml/zipball/4cef939e55b8a21c5780418de0d98ab00d0736e1", + "reference": "4cef939e55b8a21c5780418de0d98ab00d0736e1", "shasum": "" }, "require": { @@ -10608,7 +10605,7 @@ "description": "Loads and dumps YAML files", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/yaml/tree/v7.4.14" + "source": "https://github.com/symfony/yaml/tree/v7.4.18" }, "funding": [ { @@ -10628,7 +10625,7 @@ "type": "tidelift" } ], - "time": "2026-06-08T20:24:16+00:00" + "time": "2026-08-30T00:47:26+00:00" }, { "name": "theseer/tokenizer", diff --git a/config/app.php b/config/app.php index 6903550e..ce066c68 100644 --- a/config/app.php +++ b/config/app.php @@ -231,7 +231,7 @@ return [ | */ - 'version' => env('APP_VERSION', '7.0.10'), + 'version' => env('APP_VERSION', '7.0.11'), /* |-------------------------------------------------------------------------- diff --git a/config/core.php b/config/core.php index 53288a11..ef252a4c 100644 --- a/config/core.php +++ b/config/core.php @@ -120,6 +120,42 @@ return [ 'enabled' => env('G7_STATIC_CACHE', true), ], + /* + |-------------------------------------------------------------------------- + | SEO 봇 캐시 상한 + |-------------------------------------------------------------------------- + | 봇 판정은 User-Agent 문자열뿐이라 위장이 가능하고, 캐시 키에 쿼리가 들어가므로 + | 물음표 뒤 값만 바꾸면 매 요청이 미스가 됩니다. 미스 1건은 레이아웃 병합·표현식 + | 평가·자기 API 루프백 호출을 유발하고 그 결과가 캐시에 쌓입니다. + | + | 아래 값이 그 증식을 막는 상한입니다. 렌더 예산을 넘긴 요청은 오류가 아니라 + | 일반 SPA 응답을 받습니다(봇에게 오류를 주면 색인에서 URL 이 빠집니다). + | + | IP 단위 상한(render_misses_per_minute, stats_records_per_minute)은 요청 IP 를 + | 기준으로 셉니다. 리버스 프록시·CDN 뒤에 두면서 TRUSTED_PROXIES 를 지정하지 않으면 + | 모든 요청이 프록시 IP 하나로 보여 사이트 전체가 한 예산을 나눠 쓰게 되고, 정상 + | 검색엔진 봇도 예산 초과 시점부터 SPA 를 받습니다. docs/backend/reverse-proxy.md 참조. + */ + 'seo_cache_limits' => [ + // 캐시 키에 허용하는 쿼리 파라미터 수 (초과 → 캐시·렌더 안 함) + 'max_query_params' => (int) env('G7_SEO_CACHE_MAX_QUERY_PARAMS', 10), + + // 정규화된 쿼리 문자열 길이 상한 (바이트) + 'max_query_length' => (int) env('G7_SEO_CACHE_MAX_QUERY_LENGTH', 512), + + // 같은 경로·언어에 대해 저장하는 쿼리 변종 수 상한 (언어별로 따로 센다) + 'max_variants_per_path' => (int) env('G7_SEO_CACHE_MAX_VARIANTS_PER_PATH', 50), + + // 캐시 인덱스 전체 항목 수 상한 + 'max_entries' => (int) env('G7_SEO_CACHE_MAX_ENTRIES', 20000), + + // IP 당 분당 미스 렌더 수 (초과 → SPA + X-SEO-Cache: BYPASS) + 'render_misses_per_minute' => (int) env('G7_SEO_RENDER_MISSES_PER_MINUTE', 60), + + // IP 당 분당 통계 기록 수 (통계 테이블이 새 증식 축이 되지 않도록) + 'stats_records_per_minute' => (int) env('G7_SEO_STATS_RECORDS_PER_MINUTE', 300), + ], + /* |-------------------------------------------------------------------------- | 아웃바운드 프록시 연결 테스트 diff --git a/database/migrations/2026_09_08_000001_modify_url_in_seo_cache_stats_table.php b/database/migrations/2026_09_08_000001_modify_url_in_seo_cache_stats_table.php new file mode 100644 index 00000000..959ac081 --- /dev/null +++ b/database/migrations/2026_09_08_000001_modify_url_in_seo_cache_stats_table.php @@ -0,0 +1,44 @@ +string('url', 768)->comment('요청 URL (경로 + 정규화 쿼리)')->change(); + }); + } + + /** + * url 컬럼을 원래 길이(255)로 되돌립니다. + * + * 되돌리기 전에 255자를 넘는 행을 지운다 — 엄격 모드에서는 잘리는 값이 하나라도 있으면 + * ALTER 자체가 실패한다. 통계 행은 파생 데이터라 손실이 복구 대상이 아니다. + * + * @return void + */ + public function down(): void + { + DB::table('seo_cache_stats')->whereRaw('CHAR_LENGTH(url) > 255')->delete(); + + Schema::table('seo_cache_stats', function (Blueprint $table) { + $table->string('url', 255)->comment('요청 URL')->change(); + }); + } +}; diff --git a/docs/SECURITY.md b/docs/SECURITY.md index 0194ce92..713057c8 100644 --- a/docs/SECURITY.md +++ b/docs/SECURITY.md @@ -225,7 +225,17 @@ G7은 레이아웃 JSON 보안을 위해 **10개의 Custom Validation Rule**을 **추가 차단**: `//`로 시작하는 프로토콜 상대 URL -**검증 범위**: `components[]` → `props`, `actions` 내 모든 문자열 값을 재귀적으로 스캔 +**외부의 기준**: 사이트 자기 host(`app.url`)와 운영자가 「공개 자산 스토리지」로 선언한 디스크의 host 는 +외부가 아닙니다. 레이아웃 첨부 API 가 스스로 발급하는 주소(공개 서빙 URL·직접 URL)가 그 host 를 +쓰므로, 여기서 차단하면 image 위젯으로 올린 파일이 업로드는 되고 저장은 422 가 됩니다. 판정은 +접두 문자열 비교가 아니라 브라우저와 같은 정규화 뒤의 host 등가 비교입니다(`App\Support\SiteAssetHosts`). +`host.evil.com`·`host@evil.com`·`evil.com\@host` 같은 흉내 host 는 통과하지 않으며, 요청의 `Host` 헤더는 +위조 가능하므로 근거로 쓰지 않습니다. 프로토콜 상대 URL 과 http/https 밖의 스킴은 host 가 같아도 +차단됩니다. + +**검증 범위**: `components[]` → `props`, `actions`, `lifecycle`, `onComponentEvent`, `slots`, `component_layout`, +`responsive` 와 최상위 `init_actions`/`modals`/`named_actions`/`errorHandling` 내 모든 문자열 값을 재귀적으로 스캔. +`style` 은 스캔 대상이 아닙니다 — 배경 이미지(`backgroundImage`)가 절대 URL 이어도 통과하는 이유입니다. ### 5. ValidParentLayout diff --git a/docs/backend/api/README.md b/docs/backend/api/README.md index 67a5623a..a3087ec2 100644 --- a/docs/backend/api/README.md +++ b/docs/backend/api/README.md @@ -219,14 +219,14 @@ CSS 가 아닌 자산은 바이트 그대로 서빙됩니다. 정적 게시본(` ## 코어 API 레퍼런스 -- **문서 수**: 36 · **엔드포인트 수**: 325 +- **문서 수**: 36 · **엔드포인트 수**: 328 | 문서 | 도메인 | 엔드포인트 | | --- | --- | --- | | [activity-logs.md](activity-logs.md) | `activity-logs` | 3 | | [attachment.md](attachment.md) | `attachment` | 1 | | [attachments.md](attachments.md) | `attachments` | 4 | -| [auth.md](auth.md) | `auth` | 15 | +| [auth.md](auth.md) | `auth` | 18 | | [avatar.md](avatar.md) | `avatar` | 2 | | [broadcasting.md](broadcasting.md) | `broadcasting` | 1 | | [changelog.md](changelog.md) | `changelog` | 1 | diff --git a/docs/backend/api/auth.md b/docs/backend/api/auth.md index f945840b..0f7da50e 100644 --- a/docs/backend/api/auth.md +++ b/docs/backend/api/auth.md @@ -339,6 +339,8 @@ HTTP/1.1 200 | 403 | Forbidden | 자격 증명은 맞지만 관리자 역할이 아닌 경우 (`auth.admin_required`) | | 422 | Unprocessable Entity | 요청 파라미터가 검증 규칙을 위반한 경우 (`error.errors` 에 필드별 메시지) | | 423 | Locked | 로그인 실패 누적으로 계정이 잠긴 경우 (`auth.account_locked` — `error.locked_until`, `error.retry_after_seconds` 포함) | +| 429 | Too Many Requests | `throttle:auth-login` 초과 (`auth.too_many_attempts`) | +| 503 | Service Unavailable | 2단계 인증이 켜져 있고 인증번호를 보내지 못한 경우 (`auth.two_factor_delivery_failed`) | @@ -346,6 +348,169 @@ HTTP/1.1 200 관리자 로그인. `email`/`password` 검증 후 `AuthService::login()` 이 인증하고, 인증 사용자가 `isAdmin()` 이 아니면 `403 auth.admin_required` 로 거부한다. 성공 시 `data.token`(Sanctum Bearer) 과 `data.user`(UserResource) 를 반환한다. 계정 잠금 시 `AccountLockedException`, 자격 불일치 시 `422` 검증 오류를 반환한다. 이후 모든 관리자 API 호출은 이 토큰을 `Authorization: Bearer` 헤더로 실어야 한다. +**403 거부는 이미 발급된 세션을 회수한다.** `AuthService::login()` 은 관리자 판정보다 먼저 토큰과 web 세션을 발급하므로, 거부하면서 그대로 두면 관리자가 아닌 사용자가 응답만 `403` 을 받을 뿐 유효한 세션을 손에 쥔다. 거부 경로는 `AuthService::revokeIssuedSession()` 으로 그 발급분을 되돌린다. + +**2단계 인증이 켜져 있는 경우**: 사용자 로그인과 동일하게 `200` + `message: auth.two_factor_required` 와 `two_factor_required` / `challenge_id` / `provider_id` / `expires_at` 을 반환한다(필드 정의는 `POST /api/auth/login` 의 같은 절 참조). 이 단계에서는 아직 사용자도 토큰도 없으므로 **관리자 판정을 하지 않는다** — 판정은 `POST /api/auth/admin/login/two-factor` 가 코드 확인에 성공한 뒤에 수행한다. + + +### POST /api/auth/admin/login/two-factor + +- **라우트명**: `api.auth.admin.login.two-factor` +- **컨트롤러**: `App\Http\Controllers\Api\Admin\AuthController@verifyTwoFactor` +- **인증/권한**: 공개 (인증 불필요 — 주체는 challenge 가 식별한다) + +**요청 파라미터** + +| 이름 | 위치 | 타입 | 필수 | 허용값 | 용도 | +| --- | --- | --- | --- | --- | --- | +| challenge_id | body | string | 예 | uuid | 관리자 로그인 응답이 돌려준 challenge 식별자 | +| code | body | string | 예 | min 4, max 16 | 사용자가 받은 인증 코드 | + +> 이 엔드포인트는 확장이 파라미터를 추가할 수 있습니다 (`core.auth.two_factor_validation_rules`). + +**요청 예시** + +```http +POST /api/auth/admin/login/two-factor HTTP/1.1 +Host: api.example.com +Accept: application/json +Content-Type: application/json + +{ + "challenge_id": "9f1c2f2e-0b3a-4f0a-9a1e-5c1b7f9d2c40", + "code": "135790" +} +``` + +**응답 필드** (`data` 내부) + +_단건 응답: `POST /api/auth/admin/login` 의 성공 페이로드와 동일하다._ + +| 필드 | 타입 | 실측 예시값 | 용도/설명 | +| --- | --- | --- | --- | +| user | object | `{"uuid":"a234c2b1-…","is_admin":true, …}` | 로그인한 관리자 정보 (`UserResource`) | +| token | string | `75\|WgPUplvLGTv8YIj4507uIR6dEOHTXyNUed…` | 발급된 Sanctum 접근 토큰 평문 | +| token_type | string | `Bearer` | 토큰 타입 (항상 `Bearer`) | + +**응답 예시** + +```http +HTTP/1.1 200 +``` + +```json +{ + "success": true, + "message": "관리자 로그인이 성공했습니다.", + "data": { + "user": { + "uuid": "a234c2b1-cde8-437f-b28b-23323be2b98d", + "name": "API 문서 샘플 사용자", + "email": "apidoc-sample-user@example.com", + "status": "active", + "is_admin": true, + "is_owner": true + }, + "token": "{MASKED}", + "token_type": "Bearer" + } +} +``` + +> `user` 객체는 지면 절약을 위해 축약했습니다. 실제로는 `UserResource` 필드 전수가 내려옵니다. + +**에러 응답** + +| 상태코드 | 의미 | 발생 조건 | +| --- | --- | --- | +| 401 | Unauthorized | 코드가 틀렸거나(`auth.two_factor_failed`), challenge 의 `purpose` 가 `login` 이 아니거나, 확인된 사용자가 없거나 `active` 상태가 아닌 경우 | +| 403 | Forbidden | 코드 확인은 통과했으나 관리자 역할이 아닌 경우 (`auth.admin_required`) | +| 422 | Unprocessable Entity | `challenge_id`/`code` 형식 위반 | +| 423 | Locked | 로그인 실패 누적으로 계정이 잠긴 경우. 응답 형태는 `POST /api/auth/login` 의 423 과 동일 | +| 429 | Too Many Requests | `throttle:auth-login` 초과 (`auth.too_many_attempts`) | + + + +**설명** + +관리자 로그인의 인증번호 확인 단계. 사용자 경로(`POST /api/auth/login/two-factor`)와 같은 규칙으로 코드를 확인하고, **확인에 성공한 뒤에** 관리자 여부를 판정한다. + +`AuthService::completeTwoFactor()` 는 코드 확인에 성공한 시점에 토큰을 발급하므로, 관리자 판정으로 `403` 을 돌려줄 때는 반드시 그 발급분을 회수한다(`revokeIssuedSession()`). 회수하지 않으면 관리자가 아닌 사용자가 응답만 `403` 을 받을 뿐 유효한 세션을 손에 쥔다. + + +### POST /api/auth/admin/login/two-factor/resend + +- **라우트명**: `api.auth.admin.login.two-factor.resend` +- **컨트롤러**: `App\Http\Controllers\Api\Admin\AuthController@resendTwoFactor` +- **인증/권한**: 공개 (인증 불필요 — 주체는 challenge 가 식별한다) + +**요청 파라미터** + +| 이름 | 위치 | 타입 | 필수 | 허용값 | 용도 | +| --- | --- | --- | --- | --- | --- | +| challenge_id | body | string | 예 | uuid | 관리자 로그인 응답이 돌려준 challenge 식별자 | + +**요청 예시** + +```http +POST /api/auth/admin/login/two-factor/resend HTTP/1.1 +Host: api.example.com +Accept: application/json +Content-Type: application/json + +{ + "challenge_id": "9f1c2f2e-0b3a-4f0a-9a1e-5c1b7f9d2c40" +} +``` + +**응답 필드** (`data` 내부) + +_`POST /api/auth/login/two-factor/resend` 와 동일한 형태다._ + +| 필드 | 타입 | 실측 예시값 | 용도/설명 | +| --- | --- | --- | --- | +| two_factor_required | boolean | `true` | 항상 `true` | +| challenge_id | string(uuid) | `9f1c2f2e-0b3a-…` | **새** challenge 식별자 (이전 값은 취소됨) | +| provider_id | string | `g7:core.mail` | 코드를 발송한 본인인증 프로바이더 | +| expires_at | string(ISO8601)\|null | `2026-09-07T14:03:00+09:00` | 새 challenge 만료 시각 | + +**응답 예시** + +```http +HTTP/1.1 200 +``` + +```json +{ + "success": true, + "message": "인증번호를 보냈습니다. 받은 번호를 입력해 로그인을 완료해주세요.", + "data": { + "two_factor_required": true, + "challenge_id": "9f1c2f2e-0b3a-4f0a-9a1e-5c1b7f9d2c40", + "provider_id": "g7:core.mail", + "expires_at": "2026-09-07T14:03:00+09:00" + } +} +``` + +**에러 응답** + +| 상태코드 | 의미 | 발생 조건 | +| --- | --- | --- | +| 403 | Forbidden | challenge 가 식별한 사용자가 관리자 역할이 아닌 경우 (`auth.admin_required`) | +| 422 | Unprocessable Entity | 재발송 대상이 아닌 challenge (`auth.two_factor_invalid_challenge`) — 사유는 구분하지 않는다 | +| 423 | Locked | challenge 를 받은 뒤 계정이 잠긴 경우 | +| 429 | Too Many Requests | `throttle:auth-login` 초과 (`auth.too_many_attempts`) | +| 503 | Service Unavailable | 새 인증번호를 보내지 못한 경우 (`auth.two_factor_delivery_failed`) | + + + +**설명** + +관리자 로그인의 인증번호 재발송. 규칙은 `POST /api/auth/login/two-factor/resend` 와 동일하다 — 기존 challenge 를 취소하고 새로 발행하므로 앞서 받은 인증번호는 통하지 않는다. + +이 단계는 토큰을 발급하지 않으므로 회수할 것이 없다. 다만 완료할 수 없는 상대에게 새 인증번호를 계속 보내지는 않으므로, challenge 가 식별한 사용자가 관리자가 아니면 `403 auth.admin_required` 로 거부한다. 발급 자체를 막는 것이 아니라 **재발송만** 막는 것이며, 비밀번호 확인 단계(`POST /api/auth/admin/login`)는 종전대로 관리자 판정 없이 challenge 를 돌려준다. + ### POST /api/auth/forgot-password @@ -488,6 +653,8 @@ HTTP/1.1 200 | 401 | Unauthenticated | 이메일/비밀번호가 일치하지 않거나 계정 상태가 활성이 아닌 경우 (`auth.login_failed`) | | 422 | Unprocessable Entity | 요청 파라미터가 검증 규칙을 위반한 경우 (`error.errors` 에 필드별 메시지) | | 423 | Locked | 로그인 실패 누적으로 계정이 잠긴 경우 (`auth.account_locked` — `error.locked_until`, `error.retry_after_seconds` 포함) | +| 429 | Too Many Requests | `throttle:auth-login` 초과 (`auth.too_many_attempts` — `Retry-After` 헤더 동반) | +| 503 | Service Unavailable | 2단계 인증이 켜져 있고 인증번호를 보내지 못한 경우 (`auth.two_factor_delivery_failed`) | @@ -504,7 +671,9 @@ HTTP/1.1 200 | `provider_id` | string | 코드를 발송한 본인인증 프로바이더 | | `expires_at` | string(ISO8601)\|null | challenge 만료 시각 | -이 응답에는 `data.token` 과 `data.user` 가 없다. 토큰 존재 여부로 로그인 완료를 판정하는 클라이언트는 그대로 동작한다. +이 응답에는 `data.token` 과 `data.user` 가 없다. **클라이언트는 `two_factor_required` 를 먼저 판정해야 한다** — 응답 형태가 하나라고 가정하고 `data.user.*` 를 읽으면 그 자리에서 예외가 나고, `data.token` 을 그대로 저장하면 `"undefined"` 문자열이 남아 이후 모든 요청이 `401` 로 튕긴다(공개 #133). 코어 클라이언트(`AuthManager.login()`)는 `LoginResult` 판별 유니온으로 두 형태를 구분해 돌려준다. + +**인증번호를 보내지 못한 경우**: 자격 증명은 올바르지만 코드를 전달할 수단이 없으므로 로그인을 완료할 수 없다. 이때는 `401`(자격 증명 오류)이 아니라 `503 auth.two_factor_delivery_failed` 를 반환한다 — `401` 로 뭉뚱그리면 사용자는 비밀번호를 의심하며 같은 실패를 반복하고, 운영자는 메일 설정이 깨진 사실을 알 방법이 없다. 발송에 실패해도 2단계 인증을 건너뛰고 로그인시키지는 않는다. ### POST /api/auth/login/two-factor @@ -590,7 +759,7 @@ HTTP/1.1 200 | 401 | Unauthorized | 코드가 틀렸거나(`auth.two_factor_failed`), challenge 의 `purpose` 가 `login` 이 아니거나, 확인된 사용자가 없거나 `active` 상태가 아닌 경우. **세 사유를 같은 응답으로 뭉뚱그린다** — 구분해 내보내면 challenge 유효성 탐색에 쓰인다 | | 422 | Unprocessable Entity | `challenge_id`/`code` 형식 위반 | | 423 | Locked | 로그인 실패 누적으로 계정이 잠긴 경우. 응답 형태는 `POST /api/auth/login` 의 423 과 동일하다 (`auth.account_locked` / 무기한이면 `auth.account_locked_permanently` — `errors.locked_until`, `errors.retry_after_seconds`, `errors.permanent`) | -| 429 | Too Many Requests | `throttle:auth-login` 초과 (로그인과 같은 제한을 공유하므로 코드 대입 시도도 함께 억제된다) | +| 429 | Too Many Requests | `throttle:auth-login` 초과 (로그인과 같은 제한을 공유하므로 코드 대입 시도도 함께 억제된다 — `auth.too_many_attempts`) | @@ -603,6 +772,79 @@ challenge 의 `purpose` 가 `login` 인지 먼저 대조한다 — 대조하지 **계정 잠금은 이 단계에서 다시 검사한다.** 세션을 여는 것은 비밀번호 단계가 아니라 이 엔드포인트이므로, challenge 를 받은 뒤 잠긴 계정은 여기서 `423` 으로 차단된다. 잠기기 전에 발급받은 challenge 를 잠긴 뒤에 완료하는 것만으로 잠금을 우회할 수 없다. 차단은 로그인 완료 훅(`core.auth.after_login`)보다 앞서므로 실패 횟수·잠금 해제 시각도 초기화되지 않는다. +### POST /api/auth/login/two-factor/resend + +- **라우트명**: `api.auth.login.two-factor.resend` +- **컨트롤러**: `App\Http\Controllers\Api\Auth\AuthController@resendTwoFactor` +- **인증/권한**: 공개 (인증 불필요) + +**요청 파라미터** + +| 이름 | 위치 | 타입 | 필수 | 허용값 | 용도 | +| --- | --- | --- | --- | --- | --- | +| challenge_id | body | string | 예 | uuid | 로그인 응답이 돌려준 challenge 식별자 | + +**요청 예시** + +```http +POST /api/auth/login/two-factor/resend HTTP/1.1 +Host: api.example.com +Accept: application/json +Content-Type: application/json + +{ + "challenge_id": "9f1c2f2e-0b3a-4f0a-9a1e-5c1b7f9d2c40" +} +``` + +**응답 필드** (`data` 내부) + +_단건 응답: `data` 객체의 필드 (`AuthService::resendTwoFactorChallenge()` 가 새로 발행한 challenge — `POST /api/auth/login` 의 2단계 인증 응답과 같은 형태)._ + +| 필드 | 타입 | 실측 예시값 | 용도/설명 | +| --- | --- | --- | --- | +| two_factor_required | boolean | `true` | 항상 `true` — 여전히 추가 확인 단계임을 나타낸다 | +| challenge_id | string(uuid) | `9f1c2f2e-0b3a-…` | **새** challenge 식별자. 이전 값은 취소되었으므로 반드시 교체해야 한다 | +| provider_id | string | `g7:core.mail` | 코드를 발송한 본인인증 프로바이더 | +| expires_at | string(ISO8601)\|null | `2026-09-07T14:03:00+09:00` | 새 challenge 만료 시각 | + +**응답 예시** + +```http +HTTP/1.1 200 +``` + +```json +{ + "success": true, + "message": "인증번호를 보냈습니다. 받은 번호를 입력해 로그인을 완료해주세요.", + "data": { + "two_factor_required": true, + "challenge_id": "9f1c2f2e-0b3a-4f0a-9a1e-5c1b7f9d2c40", + "provider_id": "g7:core.mail", + "expires_at": "2026-09-07T14:03:00+09:00" + } +} +``` + +**에러 응답** + +| 상태코드 | 의미 | 발생 조건 | +| --- | --- | --- | +| 422 | Unprocessable Entity | `challenge_id` 형식 위반, 또는 재발송 대상이 아닌 challenge (`auth.two_factor_invalid_challenge`) — 존재하지 않음 / `purpose` 가 `login` 이 아님 / 이미 검증·취소·실패 / 만료 / 대상 사용자가 없거나 `active` 가 아님. **사유를 구분하지 않는다** — 구분해 내보내면 challenge 유효성 탐색에 쓰인다 | +| 423 | Locked | challenge 를 받은 뒤 계정이 잠긴 경우. 응답 형태는 `POST /api/auth/login` 의 423 과 동일하다 | +| 429 | Too Many Requests | `throttle:auth-login` 초과 (`auth.too_many_attempts`) | +| 503 | Service Unavailable | 새 인증번호를 보내지 못한 경우 (`auth.two_factor_delivery_failed`) | + + + +**설명** + +인증번호를 받지 못했을 때 새 코드를 발행한다. 서버는 **기존 challenge 를 취소하고 새로 발행**하므로, 앞서 받은 인증번호는 더 이상 통하지 않는다 — 유효한 코드를 여러 개 동시에 살려 두면 대입 시도의 표적이 넓어진다. 클라이언트는 응답의 `challenge_id` 로 반드시 교체하고 입력란을 비워야 한다. + +계정 잠금은 여기서도 다시 검사한다. challenge 를 받은 뒤 잠긴 계정에는 새 코드를 보내지 않는다. 로그인과 같은 요청 제한(`throttle:auth-login`)이 걸린다. + + ### POST /api/auth/logout - **라우트명**: `api.auth.logout` diff --git a/docs/backend/api/core-update.md b/docs/backend/api/core-update.md index 625f23bf..3d9092fc 100644 --- a/docs/backend/api/core-update.md +++ b/docs/backend/api/core-update.md @@ -100,7 +100,7 @@ _단건 응답: `data` 객체의 필드._ | 필드 | 타입 | 실측 예시값 | 용도/설명 | | --- | --- | --- | --- | | update_available | boolean | `false` | 새 버전 존재 여부 (최신 릴리스 버전이 현재 버전보다 높으면 `true`) | -| current_version | string | `7.0.3` | 현재 설치된 코어 버전 (`config('app.version')`) | +| current_version | string | `7.0.3` | 현재 설치된 코어 버전 (프로세스 환경값이 아니라 설정의 버전 — `config('app.version')`) | | latest_version | string | `7.0.3` | GitHub 릴리스에서 조회한 최신 코어 버전 (조회 값이 없으면 현재 버전과 동일) | | github_url | string | `https://github.com/gnuboard/g7` | 업데이트 조회 대상 GitHub 저장소 URL (`config('app.update.github_url')`) | diff --git a/docs/backend/api/identity.md b/docs/backend/api/identity.md index bace9eeb..a19a02e2 100644 --- a/docs/backend/api/identity.md +++ b/docs/backend/api/identity.md @@ -2518,13 +2518,13 @@ HTTP/1.1 200 | 상태코드 | 의미 | 발생 조건 | | --- | --- | --- | | 401 | Unauthenticated | Bearer 토큰을 보냈으나 만료/무효인 경우 (비회원은 헤더 생략 가능) | -| 403 | Forbidden | 요구 권한(`core.identity.cancel`)이 없는 경우, 또는 scope=self 가드가 본인 challenge 가 아니라고 판정한 경우 | +| 403 | Forbidden | 요구 권한(`core.identity.cancel`)이 없는 경우, scope=self 가드가 본인 challenge 가 아니라고 판정한 경우, 또는 challenge 의 `purpose` 가 `login` 인 경우 (`identity.errors.purpose_not_allowed` — `errors.failure_code = PURPOSE_NOT_ALLOWED`) | | 404 | Not Found | path 의 challenge 를 찾을 수 없거나 취소 처리에 실패한 경우 (`유효하지 않은 인증 요청입니다.`) | | 422 | Unprocessable Entity | 요청 파라미터가 검증 규칙을 위반한 경우 (`error.errors` 에 필드별 메시지) | -**설명** 진행 중인 challenge 를 취소합니다. `auth:sanctum` + `core.identity.cancel` 권한이 필요합니다. 라우트 모델 바인딩 + PermissionMiddleware 의 scope=self 가드로 로그인 사용자는 본인 challenge 만 취소할 수 있으며, 비로그인 게스트는 guest 역할 권한으로 진입합니다(모달 취소 시 audit trail 정합용). `IdentityVerificationService::cancel` 이 처리하고 대상 challenge 가 없으면 404 를 반환합니다. 사용자가 인증 모달을 닫을 때 서버 상태를 cancelled 로 남겨 이력 정합성을 맞추는 데 사용합니다. +**설명** 진행 중인 challenge 를 취소합니다. **로그인 2단계 인증 challenge(`purpose = login`)는 이 경로로 취소할 수 없습니다** — 취소되면 그 challenge 로는 더 이상 로그인을 마칠 수 없게 되어, 사용자가 자기 로그인을 스스로 막는 상태가 됩니다(자기 DoS). 로그인 흐름은 `POST /api/auth/login/two-factor` · `.../resend` 만 사용합니다. `auth:sanctum` + `core.identity.cancel` 권한이 필요합니다. 라우트 모델 바인딩 + PermissionMiddleware 의 scope=self 가드로 로그인 사용자는 본인 challenge 만 취소할 수 있으며, 비로그인 게스트는 guest 역할 권한으로 진입합니다(모달 취소 시 audit trail 정합용). `IdentityVerificationService::cancel` 이 처리하고 대상 challenge 가 없으면 404 를 반환합니다. 사용자가 인증 모달을 닫을 때 서버 상태를 cancelled 로 남겨 이력 정합성을 맞추는 데 사용합니다. ### POST /api/identity/challenges/{challenge}/verify @@ -2593,13 +2593,13 @@ HTTP/1.1 200 | 상태코드 | 의미 | 발생 조건 | | --- | --- | --- | | 401 | Unauthenticated | Bearer 토큰을 보냈으나 만료/무효인 경우 (비회원은 헤더 생략 가능) | -| 403 | Forbidden | 요구 권한(`core.identity.verify`)이 없는 경우, 또는 scope=self 가드가 본인 challenge 가 아니라고 판정한 경우 | +| 403 | Forbidden | 요구 권한(`core.identity.verify`)이 없는 경우, scope=self 가드가 본인 challenge 가 아니라고 판정한 경우, 또는 challenge 의 `purpose` 가 `login` 인 경우 (`identity.errors.purpose_not_allowed` — `errors.failure_code = PURPOSE_NOT_ALLOWED`) | | 404 | Not Found | path 파라미터에 해당하는 challenge 가 없는 경우 | | 422 | Unprocessable Entity | 검증 실패 — 응답 `error` 에 `failure_code`(예: `INVALID_CODE`/`EXPIRED`/`MAX_ATTEMPTS`) 와 서버 기준 `attempts`/`max_attempts` 를 함께 반환. 메시지는 `identity.errors.*` (`인증 코드가 올바르지 않습니다.` / `인증 시간이 만료되었습니다. 다시 시도해주세요.` / `시도 횟수를 초과했습니다. 다시 요청해주세요.` 등) | -**설명** challenge 를 검증(인증 완료) 합니다. `auth:sanctum` + `core.identity.verify` 권한이 필요합니다. 라우트 모델 바인딩 + PermissionMiddleware 의 scope=self 가드로 로그인 사용자는 본인 challenge 만 검증하며, 비로그인 게스트는 guest 역할 권한으로 진입합니다(Mode B 가입 흐름). `code`(text_code 흐름) 또는 `token`(link/redirect 흐름) 을 전달하고 `IdentityVerificationService::verify` 가 처리합니다. 실패 시 422 로 `failure_code` 와 서버 기준 `attempts`/`max_attempts` 를 함께 내려 클라이언트의 "남은 시도 횟수" UI 를 서버와 동기화하며, 성공 시 후속 민감 작업에 제출할 `verification_token` 을 반환합니다. 확장은 `core.identity.verify_validation_rules` 필터 훅으로 파라미터를 추가할 수 있습니다. +**설명** challenge 를 검증(인증 완료) 합니다. **로그인 2단계 인증 challenge(`purpose = login`)는 이 경로로 검증할 수 없습니다** — 여기서 검증되면 바로 뒤의 `POST /api/auth/login/two-factor` 가 「이미 처리된 요청」으로 거절해 그 challenge 로는 영영 로그인할 수 없게 됩니다. 게이트는 상태를 전혀 바꾸지 않으므로, 거부된 뒤에도 로그인 경로로 정상 완료할 수 있습니다. `auth:sanctum` + `core.identity.verify` 권한이 필요합니다. 라우트 모델 바인딩 + PermissionMiddleware 의 scope=self 가드로 로그인 사용자는 본인 challenge 만 검증하며, 비로그인 게스트는 guest 역할 권한으로 진입합니다(Mode B 가입 흐름). `code`(text_code 흐름) 또는 `token`(link/redirect 흐름) 을 전달하고 `IdentityVerificationService::verify` 가 처리합니다. 실패 시 422 로 `failure_code` 와 서버 기준 `attempts`/`max_attempts` 를 함께 내려 클라이언트의 "남은 시도 횟수" UI 를 서버와 동기화하며, 성공 시 후속 민감 작업에 제출할 `verification_token` 을 반환합니다. 확장은 `core.identity.verify_validation_rules` 필터 훅으로 파라미터를 추가할 수 있습니다. ### GET /api/identity/policies/resolve diff --git a/docs/backend/api/layouts.md b/docs/backend/api/layouts.md index 6bc7e2bc..0c3b9e81 100644 --- a/docs/backend/api/layouts.md +++ b/docs/backend/api/layouts.md @@ -345,7 +345,7 @@ _단건 응답: `data` 는 병합된 레이아웃 JSON 객체입니다 (상속 | pageConfig / schema | object | `{}` | 플러그인 설정 레이아웃 전용 (설정 UI 안내/스키마) | | lock_version | integer | `3` | 낙관적 잠금 버전. `with_source_meta=1` 일 때만 부착 (편집기 저장 시 `expected_lock_version` 으로 되돌려 보냄) | | __editor | object | `{"original": { ... }}` | 자식 레이아웃의 저장 원본 content. `with_source_meta=1` 일 때만 부착 (편집기 전용) | -| __source (각 노드 내부) | object | `{"kind":"base","layout":"_user_base"}` | 각 컴포넌트/데이터소스 노드의 출처 메타 (`base` / `extension` / `partial` / `route`). `with_source_meta=1` 일 때만 부착 | +| __source (각 노드 내부) | object | `{"kind":"base","layout":"_user_base"}` | 각 컴포넌트/데이터소스 노드의 출처 메타 (`base` / `extension` / `partial` / `route`). `with_source_meta=1` 일 때만 부착. `extension` 출처가 overlay 주입이면 `injectionIndex`(그 확장 content 의 `injections[]` 순번)를 함께 실어 편집기가 저장 시 원래 injection 으로 되돌린다 | 응답 헤더: `ETag`(본문 md5), `Cache-Control: public, max-age=3600`, `Vary: Accept-Encoding, Accept-Language`. 클라이언트 `If-None-Match` 가 일치하면 본문 없이 `304 Not Modified` 를 반환합니다. @@ -394,6 +394,6 @@ _단건 응답: `data` 는 병합된 레이아웃 JSON 객체입니다 (상속 -**설명** 활성 템플릿의 병합된 레이아웃 JSON을 프론트엔드에 서빙합니다. 템플릿이 존재하고 활성 상태여야 하며, 상속 병합·확장 적용을 마친 결과를 ETag·Cache-Control 헤더와 함께 반환하고 미변경 시 304로 응답합니다. 레이아웃의 `permissions`에 따라 접근을 제한하고(비회원 401, 권한 부족 403), 컴포넌트 단위 권한 필터링을 사용자별로 적용합니다. 쿼리 `v`(정수 캐시 버전)로 캐시를 구분하며, `with_source_meta=1`은 `core.templates.layouts.edit` 권한이 있어야 노드별 출처 메타(편집기 전용)를 포함해 반환합니다. +**설명** 활성 템플릿의 병합된 레이아웃 JSON을 프론트엔드에 서빙합니다. 템플릿이 존재하고 활성 상태여야 하며, 상속 병합·확장 적용을 마친 결과를 ETag·Cache-Control 헤더와 함께 반환하고 미변경 시 304로 응답합니다. 레이아웃의 `permissions`에 따라 접근을 제한하고(비회원 401, 권한 부족 403), 컴포넌트 단위 권한 필터링을 사용자별로 적용합니다. 쿼리 `v` 는 브라우저 HTTP 캐시 우회용 좌표이며(레이아웃 편집기는 `{cache_version}.{nonce}` 형식으로 보냄), 서버 캐시 키는 요청의 `v` 와 무관하게 서버 현재 확장 캐시 버전으로만 만들어져 저장·복원 직후의 재요청이 항상 최신 내용을 받습니다. `with_source_meta=1`은 `core.templates.layouts.edit` 권한이 있어야 노드별 출처 메타(편집기 전용)를 포함해 반환합니다. diff --git a/docs/backend/api/templates.md b/docs/backend/api/templates.md index e99804b0..7aff6c82 100644 --- a/docs/backend/api/templates.md +++ b/docs/backend/api/templates.md @@ -94667,7 +94667,7 @@ _목록 응답: `data` 는 첨부 항목 배열입니다 (페이지네이션 없 | original_name | string | `hero-bg.png` | 업로드 당시 원본 파일명 | | mime_type | string | `image/png` | 파일 MIME 타입 | | size | integer | `204800` | 파일 크기 (바이트) | -| url | string | `/storage/template-layout-attachments/sirsoft-basic/hero-bg.png` | 첨부 파일 접근 URL | +| url | string | `/api/templates/sirsoft-basic/layout-attachments/1/file` | 첨부 파일 접근 URL. 기본은 공개 서빙 라우트(프록시)의 **사이트 상대 경로**(`/api/...`)이고, 관리자 환경설정의 공개 자산 스토리지를 켠 뒤 그 디스크에 올라간 첨부만 직접 URL(CDN 절대 주소)로 내려온다. 어느 형태든 레이아웃 저장의 외부 URL 차단 규칙을 통과한다(사이트 자기 host 와 선언된 공개 자산 디스크 host 는 외부가 아니다) | | created_at | string | `2026-07-14T10:00:00+09:00` | 업로드 일시 (ISO 8601) | **응답 예시** @@ -94748,7 +94748,7 @@ _단건 응답: `data` 객체의 필드._ | original_name | string | `hero-bg.png` | 업로드된 원본 파일명 | | mime_type | string | `image/png` | 파일 MIME 타입 | | size | integer | `204800` | 파일 크기 (바이트) | -| url | string | `/storage/template-layout-attachments/sirsoft-basic/hero-bg.png` | 업로드된 파일의 접근 URL (편집기 ImagePickerControl 이 사용) | +| url | string | `/api/templates/sirsoft-basic/layout-attachments/1/file` | 업로드된 파일의 접근 URL (편집기 ImagePickerControl 이 값으로 그대로 쓴다). 기본은 공개 서빙 라우트(프록시)의 **사이트 상대 경로**(`/api/...`)이고, 공개 자산 스토리지를 켜 그 디스크에 저장된 경우에만 직접 URL(CDN 절대 주소)이다. 어느 형태든 레이아웃 저장의 외부 URL 차단 규칙을 통과한다 — 절대 프록시 URL 이던 시절 로고처럼 props 에 들어가는 값은 저장 시 422 였다 | **응답 예시** @@ -94762,7 +94762,7 @@ _단건 응답: `data` 객체의 필드._ "original_name": "hero-bg.png", "mime_type": "image/png", "size": 204800, - "url": "/storage/template-layout-attachments/sirsoft-basic/hero-bg.png" + "url": "/api/templates/sirsoft-basic/layout-attachments/1/file" } } ``` diff --git a/docs/backend/core-update-system.md b/docs/backend/core-update-system.md index f3f50191..a1274fd6 100644 --- a/docs/backend/core-update-system.md +++ b/docs/backend/core-update-system.md @@ -9,7 +9,8 @@ 2. 마이그레이션 = 스키마 변경만, 데이터 백필/변환 = 업그레이드 스텝 (역할 분리 필수) 3. 업데이트 실행 흐름: 11단계 (감지 → 다운로드 → 백업 → 적용 → 마이그레이션 → 동기화 → 업그레이드 → 마무리) 4. 롤백: CoreBackupHelper로 백업 생성, 실패 시 자동 복원 -5. 부트스트랩 호환성 검증: .env의 APP_VERSION < 확장 g7_version 시 자동 비활성화 (1시간 캐시) +5. 부트스트랩 호환성 검증: 코어 버전 < 확장 g7_version 시 자동 비활성화 (1시간 캐시) +6. vendor 를 교체한 뒤 새 프로세스를 띄우기 전에는 패키지 매니페스트를 비운다 (3계층 — 부모 선정리 / 자식 자가 치유 / 버전 판독 범위) ``` --- @@ -72,6 +73,7 @@ ```text 1. CoreVersionChecker::getCoreVersion() → config('app.version') 읽기 + (코어 업데이트 트리 안에서만 env APP_VERSION 우선 — CoreUpdateContext::isInProgress()) 2. CoreUpdateService::checkForUpdates() → GitHub API로 최신 릴리스 조회 3. version_compare(current, latest) → 업데이트 가용 여부 판단 4. 원격 CHANGELOG 캐시 → storage/app/temp/core_remote_changelog.md @@ -168,6 +170,9 @@ v접두사 자동 감지 (resolveGithubArchiveUrl): - composer.json + composer.lock 의 MD5 비교 (_pending vs base_path) - 동일 → "composer 의존성 변경 없음 — 스킵" (Step 6 + Step 8 모두 스킵) - 변경됨 → composer install --no-dev --optimize-autoloader --no-interaction --no-scripts + - 운영 vendor 의 개발용(require-dev) 패키지 감지 → 로그 기록 + · 재설치 분기: "--no-dev vendor 로 교체합니다" (정보) + · 스킵 분기: "그대로 남습니다 … composer install --no-dev 실행 권장" (경고) 3. Bundled 모드 (신규, 공유 호스팅 대응): - _pending/vendor-bundle.zip 무결성 검증 (SHA256) @@ -265,10 +270,26 @@ v접두사 자동 감지 (resolveGithubArchiveUrl): > **단독 실행 안전성 (beta.6 이후)**: `core:execute-upgrade-steps` 는 HANDOFF 안내 또는 수동 복구 목적으로 운영자가 직접 호출되는 경로가 있다. 단독 실행 시 자식은 기본값으로 부모 Step 9 (`runMigrations` + `reloadCoreConfigAndResync`), Step 11 (`updateVersionInEnv` + `clearAllCaches`), Step 12 (번들 확장 일괄 업데이트) 를 자체적으로 수행해 단일 명령으로 업그레이드를 완결한다. 부모 `CoreUpdateCommand::spawnUpgradeStepsProcess()` 는 자식 명령 라인에 `--skip-migrations`, `--skip-resync`, `--skip-version-env`, `--skip-cache-clear`, `--skip-bundled-updates` 5개를 무조건 추가해 중복 회피한다 — 부모가 자식 종료 후 동일 단계를 직접 수행하기 때문이다. -> **spawn 자식은 이전 버전의 config 캐시로 부팅한다**: 부모는 Step 10(spawn) 전에 config 캐시를 비우지 않는다 — `clearAllCaches()` 는 Step 11 이다. 그래서 이전 버전 설치본에 `bootstrap/cache/config.php` 가 있으면(설치 마법사·설정 저장·확장 업데이트가 만든다) 자식은 그 캐시로 부팅하고, 자식의 `config('app.version')` 은 부모가 env 로 넘긴 `APP_VERSION={toVersion}` 이 아니라 캐시에 박힌 fromVersion 이다. 업데이트 흐름 안에서 "지금 프로세스의 코어 버전" 을 판정하는 코드는 `config('app.version')` 을 직접 읽지 않고 `CoreVersionChecker::getCoreVersion()`(env 우선, config 폴백)을 쓴다. `runUpgradeSteps()` 의 stale 메모리 가드가 config 만 읽던 시절에는 정상 spawn 자식을 stale 부모로 오판해 스텝이 0건인 릴리즈에서도 핸드오프로 중단됐다(7.0.9→7.0.10). 부모 in-process fallback 에서는 env 가 `.env` 의 fromVersion 이므로 가드는 그대로 발동한다. +> **spawn 자식은 이전 버전의 config 캐시로 부팅한다**: 7.0.9 이하 부모는 Step 10(spawn) 전에 config 캐시를 비우지 않았다 — `clearAllCaches()` 는 Step 11 이다. 그래서 이전 버전 설치본에 `bootstrap/cache/config.php` 가 있으면(설치 마법사·설정 저장·확장 업데이트가 만든다) 자식은 그 캐시로 부팅하고, 자식의 `config('app.version')` 은 부모가 env 로 넘긴 `APP_VERSION={toVersion}` 이 아니라 캐시에 박힌 fromVersion 이다. 업데이트 흐름 안에서 "지금 프로세스의 코어 버전" 을 판정하는 코드는 `config('app.version')` 을 직접 읽지 않고 `CoreVersionChecker::getCoreVersion()`(env 우선, config 폴백)을 쓴다. `runUpgradeSteps()` 의 stale 메모리 가드가 config 만 읽던 시절에는 정상 spawn 자식을 stale 부모로 오판해 스텝이 0건인 릴리즈에서도 핸드오프로 중단됐다(7.0.9→7.0.10). 부모 in-process fallback 에서는 env 가 `.env` 의 fromVersion 이므로 가드는 그대로 발동한다. > > 같은 이유로 자식이 `config('app.update.*')` 로 읽는 목록(쓰기 권한 디렉토리 등)도 캐시에 박힌 옛 목록이다 — 신버전이 항목을 추가해도 자식에게 보이지 않는다. 방어는 두 겹이다: ① 부모(7.0.10+)는 `spawnUpgradeStepsProcess()` 가 `proc_open` 직전에 `ConfigCacheHelper::clear()` 로 캐시를 비워 자식이 디스크 config + `.env` + spawn env 로 부팅하게 한다(캐시는 Step 11 이 다시 만든다). ② 자식(7.0.10+)은 이전 버전 부모가 캐시를 남겨 둔 경우를 위해, 캐시 파일이 있으면 `CoreUpdateService::freshDiskUpdateConfig()` 로 디스크의 `config/app.php` 를 직접 읽는다 — 캐시 부팅에서는 `.env` 도 로드되지 않으므로 그 안에서 `.env` 를 먼저 불변 로드한다(프로세스 env 의 `APP_VERSION` 은 덮어쓰지 않는다). +#### spawn 전 캐시 정리 계약 (3계층) + +config 캐시와 같은 문제가 **패키지 매니페스트**(`bootstrap/cache/packages.php` · `services.php`)에도 있고, 이쪽은 결과가 더 무겁다. Laravel 의 `PackageManifest` 는 `packages.php` 가 있으면 stale 여부를 검사하지 않고 그대로 읽고, `ProviderRepository` 가 거기 등재된 eager provider 를 `new` 한다. Step 6/8 이 vendor 를 `--no-dev` 로 교체해도 두 파일은 Step 11 까지 이전 설치본의 것이 남으므로, 이전 설치본이 `composer install`(옵션 없음)로 깔린 개발용 설치였다면 자식은 새 vendor 에 없는 provider 를 찾다 **부팅 단계에서** 죽는다. 예외는 앱 로그가 열리기 전이라 남지 않고, 부모에게는 자식의 비정상 종료로만 보인다 (7.0.9 → 7.0.10 실사례). + +| 계층 | 위치 | 막는 실패 | 잠그는 테스트 | +|------|------|-----------|--------------| +| ① 부모 선정리 | `CoreUpdateCommand::spawnUpgradeStepsProcess()` 가 `proc_open` 직전 `PackageManifestCacheHelper::clear()`. 지우지 못한 파일이 있으면 그 경로를 업그레이드 로그에 경고로 남긴다 — 권한·소유권 불일치면 자식의 계층 ② 도 같은 이유로 실패해 증상은 제보와 같은 「Class not found」 인데, 이 경고가 원인을 가리키는 유일한 흔적이다 | 7.0.11+ 부모가 띄우는 자식의 부팅 실패 | `CoreUpdateCommandStalePackageManifestTest` | +| ② 자식 자가 치유 | `bootstrap/app.php` 가 `G7_UPDATE_IN_PROGRESS=1`(또는 명령줄 SAPI 에서의 업데이트 argv)이면 두 파일을 스스로 삭제 | **이미 배포된** 7.0.9·7.0.10 부모 아래에서 도는 신버전 자식 — 그 부모 코드는 고칠 수 없다 | 같은 테스트 (플래그 유·무 대조군 포함) | +| ③ 버전 판독 범위 | `CoreVersionChecker::getCoreVersion()` 의 env 우선은 `CoreUpdateContext::isInProgress()` 트리 안에서만 | 업데이트 **전에** 뜬 `php artisan serve`·큐 워커가 옛 `APP_VERSION` 을 물고 확장을 `incompatible_core` 로 끄는 것 | `CoreVersionCheckerEnvPriorityTest` · `CoreUpdateContextTest` | + +계층 ②는 `config:cache`/`route:cache` 가 만드는 in-process 일회용 앱에도 발동한다 — 그 부팅도 `bootstrap/app.php` 를 다시 require 하고 플래그를 상속하기 때문이다. 웹 요청·`queue:work`·운영자 셸은 플래그가 없어 no-op 이다. + +argv 채널은 명령줄 SAPI(`cli`·`phpdbg`)에서만 읽는다. CGI/FPM 은 `register_argc_argv=On` 이면 `$_SERVER['argv']` 를 쿼리스트링을 `+` 로 쪼갠 값으로 채우므로(`GET /?x+core:update` → `argv[1] === 'core:update'`), 그 게이트가 없으면 비인증 웹 요청이 요청마다 매니페스트를 지우고 다시 만들게 된다. env 플래그 채널은 웹 요청으로 주입할 수 없어 그대로 두며, 웹 요청 안에서 시작되는 업데이트 흐름은 그 플래그를 프로세스 안에서 세워 판정된다. + +계층 ③의 판정은 `App\Support\CoreUpdateContext` 가 단독으로 소유하고 `CoreServiceProvider::isCoreUpdateInProgress()` 가 그리로 위임한다. 자동 비활성화 로그의 `core_version` 도 같은 게터를 쓴다 — 로그가 `config('app.version')` 을 적고 판정은 env 로 하면 운영자가 보는 근거와 실제 판정이 어긋난다. + #### 재실행 안내의 권한 분기 (핸드오프 catch) spawn 자식이 실패(`proc_open` 미지원 · 비정상 종료 · silent skip)하고 `spawn_failure_mode=abort`(기본값) 이면, 파일·버전은 이미 `toVersion` 으로 반영되지만 업그레이드 스텝이 미실행 상태로 남아 운영자에게 `core:execute-upgrade-steps` 재실행을 안내한다. 이때 **sudo(root) 로 `core:update` 를 실행한 경우**, 안내받은 명령을 root 로 그대로 재실행하면 스텝이 만드는 파일·캐시가 root 소유로 생성되어 이후 웹서버(php-fpm www-data 등) 요청이 그 경로에 쓰기 실패한다. @@ -288,15 +309,20 @@ spawn 자식이 실패(`proc_open` 미지원 · 비정상 종료 · silent skip) ```text 1. .env의 APP_VERSION 갱신 -2. 캐시 클리어: config, cache, route, view +2. 캐시 클리어: config, cache, route, view (spawn 직전에도 config + 패키지 매니페스트 선정리) 3. bootstrap/cache 파일 삭제 (services.php, packages.php) 4. php artisan package:discover 재실행 5. php artisan extension:update-autoload (코어 업데이트로 _bundled 변경 가능) 6. _pending 격리 디렉토리(core_{ts}) 루트째 삭제 7. 성공 시 백업 삭제 (이후 `hotfix:rollback-stale-files` 는 대상이 없다) 8. 유지보수 모드 해제 +9. 큐 워커 재시작 신호 (queue:restart) ``` +> 3~4 는 `PackageManifestCacheHelper::rebuild()` 한 호출이다 — spawn 직전 선정리(계층 ①)와 같은 삭제 로직을 공유한다. +> +> 9 는 상주 큐 워커가 부팅 시점의 코어 코드·config 를 계속 쓰는 것을 막는다. 워커는 옛 코드로도 잡을 정상 처리하므로 오류가 나지 않고, 운영자가 손수 재시작할 때까지 조용히 어긋난 채 돈다. 핸드오프 cleanup 과 `core:execute-upgrade-steps` 단독 실행의 사후 단계도 같은 신호를 보낸다. 롤백 catch 는 제외다 — 백업으로 되돌린 옛 코드가 다시 도는 자리라 재기동시킬 이유가 없다. + ### Step 12: _bundled 확장 일괄 업데이트 프롬프트 (인터랙티브) > Trait: `App\Console\Commands\Core\Concerns\BundledExtensionUpdatePrompt` @@ -586,6 +612,8 @@ public function withCurrentStep(string $stepVersion): self // 불변 복제 ```php CoreVersionChecker::getCoreVersion() // → config('app.version') + // (코어 업데이트 트리 안에서만 env APP_VERSION 우선 + // — CoreUpdateContext::isInProgress()) ``` ### 버전 갱신 (Step 11) @@ -710,7 +738,8 @@ config('app.version') = env('APP_VERSION', 'config/app.php 기본값') |--------|---------|------| | `runUpgradeSteps()` | `(string $from, string $to, ?Closure $onStep): void` | upgrades/ 자동 발견 + 실행 | | `updateVersionInEnv()` | `(string $version): void` | .env의 APP_VERSION 갱신 | -| `clearAllCaches()` | `(): void` | config/cache/route/view 클리어 + package:discover | +| `clearAllCaches()` | `(): void` | config/cache/route/view 클리어 + 패키지 매니페스트 재생성(`PackageManifestCacheHelper::rebuild()`) | +| `signalQueueRestart()` | `(): void` | 상주 큐 워커에 재시작 신호 (실패는 경고만 — 업데이트를 되돌리지 않는다) | ### 유지보수 모드 diff --git a/docs/backend/seo-system.md b/docs/backend/seo-system.md index 7011dd3a..67e62329 100644 --- a/docs/backend/seo-system.md +++ b/docs/backend/seo-system.md @@ -105,6 +105,10 @@ Request → web.php catch-all → SeoMiddleware (봇 감지) - 봇 감지: `BotDetector` 4-레이어 체인 (아래 "봇 감지 구조" 섹션 참조) - 렌더링 실패 시: SPA fallback (기존 응답 통과) +- 캐시 키: 경로 + **정규화된** 쿼리(`locale`·`_escaped_fragment_` 제외, 키 순서 정렬, 개수·길이 상한) +- 미스 렌더는 IP 당 분당 상한 안에서만 — 초과분은 SPA + `X-SEO-Cache: BYPASS`(오류가 아니다). 렌더러가 "그릴 게 없음"(null)으로 돌아온 요청은 차감을 되돌린다 +- 저장 상한: 경로·언어당 쿼리 변종 수 · 캐시 인덱스 전체 항목 수 +- 캐시 HIT/MISS 를 통계에 기록 (IP 당 분당 상한). HIT 의 레이아웃명은 캐시 항목에 함께 저장된 값으로 귀속한다 ## 봇 감지 구조 @@ -655,7 +659,7 @@ php artisan seo:warmup # SEO 캐시 워밍업 php artisan seo:warmup --layout=shop/show # 특정 레이아웃만 php artisan seo:clear # 전체 SEO 캐시 삭제 php artisan seo:clear --layout=home # 특정 레이아웃만 -php artisan seo:stats # 캐시 통계 출력 +php artisan seo:stats # 캐시 통계 출력 (미들웨어가 HIT/MISS 를 기록 — IP 당 분당 상한) php artisan seo:generate-sitemap # Sitemap 생성 (큐 디스패치, mode=auto) php artisan seo:generate-sitemap --sync # Sitemap 동기 생성 php artisan seo:generate-sitemap --rebuild # 전체 재생성 (mode=full 상당) @@ -755,6 +759,57 @@ sitemap/_tmp/ 생성 중 임시 디렉토리 (커밋 시 정리) | twitter_default_card | string | "summary_large_image" | twitter:card 기본 (summary/summary_large_image/app/player) | | twitter_default_site | string | "" | twitter:site 핸들 (예: @gnuboard). 비면 출력 생략 | +## 캐시 상한 (config/core.php `seo_cache_limits`) + +봇 판정은 User-Agent 문자열뿐이라 위장이 가능하고, 캐시 키에 쿼리가 들어가므로 물음표 뒤 값만 바꾼 반복 요청이 매번 미스가 됩니다. 미스 1건은 레이아웃 병합 · 표현식 평가 · 자기 API 루프백 호출을 유발하고 그 결과가 캐시에 쌓이므로, 요청 하나가 워커 여러 개를 묶고 저장소를 계속 키울 수 있습니다. + +아래 상한이 그 증식을 막습니다. 관리자 화면에는 노출하지 않고 서버 설정으로만 조정합니다. + +| 키 | env | 기본값 | 설명 | +|----|-----|-------|------| +| max_query_params | `G7_SEO_CACHE_MAX_QUERY_PARAMS` | 10 | 캐시 키에 허용하는 쿼리 파라미터 수. 초과 시 캐시·렌더 안 함 | +| max_query_length | `G7_SEO_CACHE_MAX_QUERY_LENGTH` | 512 | 정규화된 쿼리 문자열 길이 상한(바이트) | +| max_variants_per_path | `G7_SEO_CACHE_MAX_VARIANTS_PER_PATH` | 50 | 같은 경로·언어에 저장하는 쿼리 변종 수 (언어별로 따로 센다) | +| max_entries | `G7_SEO_CACHE_MAX_ENTRIES` | 20000 | 캐시 인덱스 전체 항목 수 | +| render_misses_per_minute | `G7_SEO_RENDER_MISSES_PER_MINUTE` | 60 | IP 당 분당 미스 렌더 수. 렌더러가 그릴 게 없다고 돌아온 요청(미라우트 404·SEO 비활성 화면)은 세지 않는다 | +| stats_records_per_minute | `G7_SEO_STATS_RECORDS_PER_MINUTE` | 300 | IP 당 분당 통계 기록 수 | + +상한을 넘긴 요청은 **차단되지 않고** 일반 SPA 응답을 받습니다. 봇에게 오류를 돌려주면 그 URL 이 색인에서 빠지므로 차단이 곧 손해입니다. 판정 결과는 응답 헤더 `X-SEO-Cache`(`HIT`/`MISS`/`BYPASS`)로 드러나며, 그것이 운영 진단의 통로입니다. + +이미 캐시에 있는 키의 **갱신**은 저장 규모를 늘리지 않으므로 상한과 무관하게 수행됩니다. + +### IP 단위 상한은 프록시 신뢰 설정에 의존합니다 + +`render_misses_per_minute` 와 `stats_records_per_minute` 는 요청 IP 를 기준으로 셉니다. TLS 가 앞단에서 종단되는 구성(리버스 프록시, CDN, 로드밸런서)에서 신뢰할 프록시를 지정하지 않으면 모든 요청의 IP 가 프록시 IP 하나로 보이므로, 그 분당 예산을 사이트 전체 봇 트래픽이 함께 쓰게 됩니다. 예산을 넘긴 시점부터 정상 검색엔진 봇도 SEO HTML 대신 SPA 를 받아 색인 품질이 떨어지고, 응답은 200 이라 서버 로그에 흔적이 남지 않습니다. + +프록시 뒤에 두는 설치본은 `TRUSTED_PROXIES` 를 반드시 지정합니다 — 설정 방법과 진단은 [리버스 프록시 환경](reverse-proxy.md)에 있습니다. 진단이 어려우면 응답 헤더 `X-SEO-Cache` 가 `BYPASS` 로 몰리는지를 먼저 봅니다. + +### 경로당 변종 상한은 언어별로 셉니다 + +캐시 인덱스 항목은 URL 과 로케일의 조합마다 하나입니다. 경로만 보고 변종을 합산하면 언어 수만큼 실효 상한이 줄어, 언어가 셋인 사이트는 한 경로에 언어당 16개만 저장되고 목록 17페이지부터는 봇이 올 때마다 새로 그리되 저장하지 않게 됩니다. 그래서 상한은 경로와 언어의 조합 단위로 판정합니다. + +### 그릴 게 없는 요청은 렌더 예산을 쓰지 않습니다 + +렌더 예산은 새로 그리기 직전에 1을 차감합니다. 그런데 미라우트 주소(404)나 SEO 를 끈 화면은 렌더러가 "그릴 게 없음"으로 바로 돌아오고 캐시에도 남지 않아, 올 때마다 다시 차감됩니다. 봇은 예전에 있던 죽은 주소를 오래 다시 긁으므로 그 요청까지 세면 정상 페이지의 예산이 죽은 주소에 소진됩니다. 렌더러가 null 을 돌려주면 그 차감을 되돌리고, 렌더 도중 예외는 비용을 이미 치른 것이라 되돌리지 않습니다. + +### 캐시 항목은 레이아웃명을 함께 담습니다 + +페이지 캐시 값은 HTML 과 레이아웃명의 쌍입니다. 캐시 적중(HIT) 경로는 렌더러를 거치지 않아 요청 속성에 레이아웃명이 없으므로, 통계를 화면별로 귀속하려면 캐시 항목이 그것을 알아야 합니다. `SeoCacheManagerInterface::get()` 은 종전대로 HTML 만 돌려주고, 코어 구현 `SeoCacheManager::getEntry()` 가 쌍 전체를 돌려줍니다. 미들웨어는 코어 구현일 때만 쌍을 읽고, 다른 구현이 바인딩된 경우에는 레이아웃명 없이 기록합니다. 이전 버전이 문자열로만 저장한 항목은 레이아웃명 없이 그대로 읽힙니다 — 배포 직후 살아 있는 캐시를 버리지 않습니다. + +통계 테이블(`seo_cache_stats`)의 `url` 컬럼은 캐시 키 URL 상한(경로 + 정규화 쿼리 최대 512바이트)을 담도록 768자입니다. 컬럼이 짧으면 긴 주소의 기록이 엄격 모드에서 실패하고, 통계 서비스는 그 예외를 삼키므로 흔적이 남지 않습니다. + +### 저장 상한이 세는 것은 살아 있는 항목입니다 + +캐시 인덱스 항목은 페이지보다 오래 삽니다(기본 30일 vs 2시간). 그래서 저장 상한에 닿으면 **먼저 만료된 항목을 인덱스에서 걷어내고 다시 판정**합니다. 그러지 않으면 상한이 "지금 저장된 양"이 아니라 "과거에 저장한 적이 있는 양"을 재게 되어, 한 번 상한에 닿은 경로는 실제 캐시가 비어 있어도 다시는 저장되지 않습니다. + +이 정리는 인덱스 전체를 훑으므로 **최소 60초 간격**으로만 수행합니다. 살아 있는 항목만으로 상한에 닿은 경우에는 정리해도 자리가 나지 않는데, 그 상태에서 저장 시도마다 훑으면 비용만 반복되기 때문입니다. 따라서 항목이 만료된 뒤 저장이 다시 열리기까지 최대 그 간격만큼 늦어질 수 있습니다. + +### 상한은 봇 요청만이 아니라 모든 저장 경로에 걸립니다 + +저장 상한은 `SeoCacheManagerInterface` 의 저장 메서드(`put`·`putWithLayout`)에 공통으로 걸립니다. 봇 요청의 미스 렌더뿐 아니라, 게시글·상품을 저장할 때 도는 단건 재생성(`SeoCacheRegenerator`)도 같은 판정을 거칩니다 — 한쪽만 상한 밖이면 그쪽이 증식 우회로가 되기 때문입니다. + +상한에 닿아 저장하지 않은 경우 재생성 호출은 **예외를 던지지 않고** 흔적을 `Log::debug` 로만 남깁니다. 콘텐츠 페이지는 서로 다른 경로라 경로당 변종 상한(`max_variants_per_path`)과는 무관하고, 전체 항목 상한(`max_entries`)은 만료 항목을 걷어낸 뒤 판정하므로 실제로는 페이지 TTL 안에 살아 있는 항목만 셉니다. 그래도 대규모 사이트에서 이 상한에 닿는다면 저장이 조용히 넘어가므로, `seo:stats` 의 미스 비율이 지속적으로 오르는지를 신호로 삼고 `G7_SEO_CACHE_MAX_ENTRIES` 를 올립니다. + ## SEO Config 동적 확장 시스템 SEO 엔진은 컴포넌트 지식을 갖지 않습니다. 모든 컴포넌트→HTML 매핑, 렌더 모드, 셀프 클로징 태그, 외부 스타일시트는 `seo-config.json`으로 제공됩니다. @@ -1653,9 +1708,10 @@ SeoCacheManager는 URL + locale 기반 캐시 키(`md5($cacheUrl.'|'.$locale)`) SeoMiddleware의 `buildCacheUrl()`이 캐시 키용 URL을 구성합니다: -- **경로 + 쿼리 파라미터 포함**: `/shop/products?page=2&sort=price` → 페이지별 독립 캐시 -- **`locale` 파라미터 제외**: locale은 캐시 키의 두 번째 차원(`$locale`)으로 별도 관리 +- **경로 + 정규화된 쿼리 파라미터**: `/shop/products?page=2&sort=price` → 페이지별 독립 캐시 +- **시스템 파라미터 제외**: `locale` 은 캐시 키의 두 번째 차원(`$locale`)으로 별도 관리하고, 봇 렌더 표식인 `_escaped_fragment_` 는 내용에 영향이 없어 키에서 뺍니다(남기면 같은 페이지가 두 벌 저장됩니다) - **쿼리 파라미터 정렬**: `ksort()` — 동일 파라미터 조합 = 동일 캐시 키 보장 +- **상한 초과 시 캐시 불가**: 파라미터 수·길이가 상한을 넘으면 색인 대상이 아니라고 보고 캐시도 렌더도 하지 않습니다(위 "캐시 상한" 절) ## SEO 변수 시스템 diff --git a/docs/backend/service-provider.md b/docs/backend/service-provider.md index 36f22326..5dd39d15 100644 --- a/docs/backend/service-provider.md +++ b/docs/backend/service-provider.md @@ -197,6 +197,9 @@ class TemplateService ``` 인스톨러 단계 1: composer install ├─ .env 없음 → ModuleRouteServiceProvider 스킵 ✅ +├─ vendor 재사용 분기 (vendor/autoload.php + composer.lock 존재) +│ ├─ 개발용 패키지 감지 → 경고 카드·설치 로그 (설치는 계속) +│ └─ 이전 환경의 컴파일 캐시 정리 (packages/services/config) └─ package:discover 정상 완료 인스톨러 단계 2: .env 생성 diff --git a/docs/backend/service-repository.md b/docs/backend/service-repository.md index f0ebd7d1..d7974366 100644 --- a/docs/backend/service-repository.md +++ b/docs/backend/service-repository.md @@ -1679,6 +1679,15 @@ $data['depth'] = ($parent->depth ?? 0) + 1; --- +## 입력 크기에 비례하는 메모리 + +브라우저에서 잘 돌던 알고리즘을 PHP 로 옮길 때 시간 상한만 함께 오고 메모리 상한은 오지 않는다. PHP 배열은 원소당 수십 바이트라 (줄 수)² 크기 표는 2,350줄에서 약 150MB 이고, 운영 서버의 PHP 기본 `memory_limit` 은 128M 이다. 개발 머신에서는 통과하고 서버에서만 500 이 되며, 남는 것은 `Allowed memory size … exhausted` 한 줄뿐이다. + +- 카운트·길이만 쓰는 LCS/DP 는 두 행 DP 로 길이만 구한다. 추가 = 새 줄 − LCS, 삭제 = 옛 줄 − LCS 이므로 전체 표를 되짚어 얻는 숫자와 같다. 실제 줄을 그려야 하는 쪽(브라우저 diff 뷰)만 전체 표를 쓴다. +- 브라우저 구현의 임계값을 옮겨 왔다면 그 임계에서의 PHP 메모리를 실측한다. 시간만 막는 임계가 있다. +- "인접 버전 비교는 변경 영역이 작다" 같은 가정으로 최악 경로를 비워 두지 않는다. 앞뒤 공통 부분 트리밍은 양끝이 동시에 바뀌면 무력하고, 편집기가 `comment` 키를 떼어내는 첫 저장이 정확히 그 형태다. +- 메모리 회귀 테스트는 `memory_get_peak_usage()` 증가량의 상한을 단언한다. 잠금 대상은 코어 `CalculatesJsonContentDiff`(레이아웃·레이아웃 확장 버전 변경량)이며 `tests/Unit/Repositories/Concerns/CalculatesJsonContentDiffTest.php` 가 상한과 참조 구현 동치를 함께 고정한다. + ## 관련 문서 - [컨트롤러 계층 구조](controllers.md) - Controller에서 Service 사용 diff --git a/docs/backend/static-asset-publishing.md b/docs/backend/static-asset-publishing.md index e6894493..b80718dc 100644 --- a/docs/backend/static-asset-publishing.md +++ b/docs/backend/static-asset-publishing.md @@ -33,7 +33,7 @@ public/build/ext/{cache_version}/ │ ├── routes.json ← 병합 결과 + {"success":true,...} 봉투 │ └── assets/{dist 이하 경로} ← dist/** 사본 (*.map 제외, 허용 확장자만) └── bundles/ - ├── modules.js / modules.css ← 확장 병합 번들 사본 + ├── modules.js / modules.css ← 확장 병합 번들 사본 (빈 번들도 0바이트로 게시) └── plugins.js / plugins.css ``` @@ -176,7 +176,7 @@ public/build/ext/{cache_version}/ 같은 엔드포인트가 대시보드의 「초기 화면 파일 생성 실패」 알림에도 [다시 만들기] 버튼으로 붙는다 — 운영자가 결함을 처음 만나는 곳에서 복구가 끝나도록. -715파일·40MB 복사와 번들 재병합이 **웹 요청 안에서** 돈다. 서버는 게시 락 TTL(300초)만큼 실행 시간을 확보하고 클라이언트가 끊어도 게시를 끝내지만, FPM `request_terminate_timeout`·nginx `fastcgi_read_timeout` 이 그보다 짧은 서버에서는 응답이 먼저 끊길 수 있다. 그 경우에도 게시는 계속되므로 카드를 다시 열어 결과를 확인한다. +715파일·40MB 복사와 번들 재병합이 **웹 요청 안에서** 돈다(번들은 캐시가 있으면 재병합하지 않는다). 서버는 게시 락 TTL(300초)만큼 실행 시간을 확보하고 클라이언트가 끊어도 게시를 끝내지만, FPM `request_terminate_timeout`·nginx `fastcgi_read_timeout` 이 그보다 짧은 서버에서는 응답이 먼저 끊길 수 있다. 그 경우에도 게시는 계속되므로 카드를 다시 열어 결과를 확인한다. 이 통로가 있으므로 kill-switch 를 관리자 UI 로 두지 않는 방침(§5)은 그대로다 — 복구는 화면에서, 끄기는 서버에서. diff --git a/docs/extension/changelog-rules.md b/docs/extension/changelog-rules.md index 1c48d9b1..388f8523 100644 --- a/docs/extension/changelog-rules.md +++ b/docs/extension/changelog-rules.md @@ -6,7 +6,7 @@ ```text 1. 확장/코어 버전 업 시 CHANGELOG.md에 변경사항 기록 필수 (미기록 시 버전 업 불가) -2. 형식: Keep a Changelog 표준 (## [버전] - 날짜 / ### 카테고리 / - 항목) +2. 형식: Keep a Changelog 표준 (## [버전] - 날짜 / ### 카테고리 / - 항목) — 한 버전 안에 같은 카테고리 헤딩은 한 번만 3. 허용 카테고리: Added, Changed, Deprecated, Removed, Fixed, Security 4. 확장 위치: 각 확장의 루트 디렉토리 (modules/_bundled/vendor-module/CHANGELOG.md) 5. 코어 위치: 프로젝트 루트 /CHANGELOG.md (코어 버전 변경 시 필수) @@ -75,6 +75,18 @@ composer test-smoke-ci - 변경사항 설명 ``` +### 한 버전 섹션 안의 카테고리는 한 번만 + +브랜치마다 버전 섹션 머리에 자기 `### Fixed` 블록을 새로 얹으면, 병합이 양쪽을 그대로 이어 붙여 한 버전 안에 같은 카테고리 헤딩이 두 번 남는다. 오류도 경고도 없고, 렌더된 문서에서 같은 제목이 두 번 보이는 것이 유일한 증상이다. + +- 새 항목은 **이미 있는 카테고리 블록 끝에** 추가한다 (같은 카테고리 헤딩을 새로 만들지 않는다) +- 병합 뒤 두 번째 블록이 보이면 그 항목을 첫 블록에 합치고 **헤딩만** 지운다 (항목 삭제 금지) +- 같은 버전 헤더(`## [1.2.1]`)도 한 번만 — 뒤 섹션의 내용을 앞 섹션에 합친다 +- 카테고리 헤딩은 §2 의 6종만 쓴다. 기능 단위 분류는 `####` 서브 헤딩으로 둔다 +- 이미 공개 배포된 과거 버전 섹션은 소급 수정하지 않는다 + +번들 확장은 정적 검사가 working 버전 섹션(+`[Unreleased]`)에서 이 조건을 차단한다. 제3자 확장에는 강제하지 않는다. + --- ## 2. 카테고리 정의 diff --git a/docs/extension/editor-spec.md b/docs/extension/editor-spec.md index 4e4a5b3a..23f22514 100644 --- a/docs/extension/editor-spec.md +++ b/docs/extension/editor-spec.md @@ -134,15 +134,31 @@ php artisan {module|plugin|template}:update {id} --force ### controls — 재사용 스타일 컨트롤 -위젯 종류(`widget`)·친화 라벨(`label`)·적용 방식(`apply`)·대상(`target`)·선택지(`options`)를 한 곳에 선언하고 `componentCapabilities.styleControls`(스타일 탭) 또는 `propControls`(속성 탭)가 키로 참조한다. `apply` 타입은 `classToken`(클래스 토큰 교체) / `styleProp`(인라인 스타일 속성) / `cssVar` / `propValue`(임의 `node.props[propKey]` 기입 — 속성 탭의 기본 적용 방식). 색·크기 등 라이브러리 대응 컨트롤은 프리셋 토큰 + 자유값(tokenTemplate)을 함께 선언한다. 상/우/하/좌 여백처럼 측마다 다른 값이 공존하는 속성은 다값을 1급으로 다룬다(일괄/개별 2모드, 단일 토큰 교체 금지). +위젯 종류(`widget`)·친화 라벨(`label`)·적용 방식(`apply`)·대상(`target`)·선택지(`options`)를 한 곳에 선언하고 `componentCapabilities.styleControls`(스타일 탭) 또는 `propControls`(속성 탭)가 키로 참조한다. `apply` 타입은 `classToken`(클래스 토큰 교체) / `styleProp`(인라인 스타일 속성) / `cssVar` / `propValue`(임의 `node.props[propKey]` 기입 — 속성 탭의 기본 적용 방식) / `nodeKey`(노드 **최상위** 구조키 `node[nodeKey]` 기입 — `dataKey` 처럼 런타임 엔진이 props 가 아니라 노드 자신에서 읽는 키. `props`·`children`·`type` 등 예약 구조키는 지정해도 무시되고, 디바이스(breakpoint) 축은 무시하며 항상 최상위에 쓴다 — 런타임 responsive 병합은 `props`/`children`/`text`/`if`/`iteration` 5키만 보므로 분기에 쓰면 영원히 읽히지 않는다). 색·크기 등 라이브러리 대응 컨트롤은 프리셋 토큰 + 자유값(tokenTemplate)을 함께 선언한다. 상/우/하/좌 여백처럼 측마다 다른 값이 공존하는 속성은 다값을 1급으로 다룬다(일괄/개별 2모드, 단일 토큰 교체 금지). **`propControls` 전용 위젯 종류**: - `icon-picker` — 아이콘 prop(예 `iconName`/`triggerIcon`)을 카탈로그 그리드에서 검색·선택한다. **카탈로그는 템플릿 소유**(라이브러리 종속) — Font Awesome 등 아이콘셋을 쓰는 템플릿이 `G7Core.layoutEditor.registerWidget('icon-picker', ...)` 로 자기 위젯을 등록한다(코어는 위젯명만 디스패치, 아이콘 라이브러리 토큰 0). 프리뷰 폴백은 `preview.html`(코어 비해석) → `preview.className` → raw value 순. 카탈로그 미공급 시 자유 텍스트 입력으로 디그레이드. - `options-list` — `Select`/`RadioGroup` 등의 정적 `options` 배열(value/label 행)을 추가/삭제/이동 편집한다. 값이 `{{바인딩}}` 이면 "바인딩됨(코드 편집)" 디그레이드(덮어쓰기 차단). 데이터소스 바인딩(dataProps `options`)과 직교 공존(위 "선택지 직교 완화"). - `array-items` / `array-group` / `array-cell-tree` / `number-list` — 정적 배열 prop 편집(탭/차트 데이터/카드 컬럼 등). `nodeEditor` 슬롯 또는 propControl 로 결선한다(아래 "데이터 정의 빌트인" 참조). +- `number` — 숫자를 기대하는 prop(예 `maxVisibleBoards`)을 편집한다. 위젯이 `number` 타입을 직접 내보내므로 `"5"` 같은 문자열이 저장되지 않는다. 빈 입력은 prop 삭제, `0` 은 유효값이라 삭제되지 않으며, 숫자로 해석할 수 없는 입력은 방출하지 않아 저장본을 보존한다(조용한 변조 금지). `min`/`max`/`step` 은 선언만 하고 **클램프하지 않는다** — 사용자가 넣은 값은 그대로 보존한다. 값이 `{{바인딩}}` 문자열일 때의 보호는 위젯이 아니라 아래 「데이터 연결 값 보호」 공용 게이트가 맡는다 — 숫자 입력칸이 그 값을 빈칸으로 표시해 다음 blur 에 소실시키는 것을 그 게이트가 차단한다. - `i18n-text` vs `text` — **표시 텍스트**(사용자가 화면에서 읽는 라벨/메시지/플레이스홀더)는 `i18n-text` 를 쓴다. `i18n-text` 는 평문 입력 시 `createCustomKey` 로 다국어 키를 자동 생성해 `$t:custom.*` 로 치환하고(미리보기에 raw 키 미노출), 🌐 펼침으로 ko/en/ja 동시 편집을 제공하며, `{{바인딩}}` 값이면 읽기전용 디그레이드한다(공통 위젯 `I18nTextField`). **비-표시값**(URL/id/수치/색/통화코드/separator 등)만 `text` 를 쓴다. 표시 텍스트에 `text` 를 쓰면 raw `$t:` 노출·다국어 누락 회귀가 발생한다(정적 검사가 배열 라벨 필드의 `text` 사용을 차단). +**데이터 연결 값 보호 — 모든 위젯 공통**: +prop 자리에 `{{...}}` 표현식이나 설정 참조가 저장돼 있으면, 그 값을 편집하는 컨트롤은 **원문 배지**로 디그레이드된다. 위젯은 그 문자열을 해석하지 못해 빈 컨트롤로 보이고, 그 상태에서 조작하면 표현식이 그대로 덮여 **환경설정과의 연결이 끊기기** 때문이다. 이 판정은 개별 위젯이 아니라 컨트롤 렌더러 한 곳에서 이뤄지므로 **새 위젯을 등록해도 자동 적용**된다 — 위젯 안에 같은 판정을 다시 넣지 않는다(넣으면 해제 경로까지 막혀 「직접 지정으로 바꾸기」가 무반응이 된다). + +보호 상태에서는 그 컨트롤의 파괴적 조작이 함께 잠긴다. 값을 바꾸려면 「직접 지정으로 바꾸기」를 눌러 명시적으로 열고, 「되돌리기」로 원래 연결값을 복구할 수 있다(해제 직후에는 취소, 값을 넣은 뒤에는 복구로 동작). + +예외는 표현식 자체를 다루도록 설계된 위젯뿐이다 — `text`·`i18n-text` 는 표현식을 데이터 칩으로 **분해해 보여 주는 것**이 그 위젯의 기능이고, `image`·`core-id` 는 같은 보호를 위젯 안에서 더 세밀하게(갤러리·업로드·관리 모달 진입까지) 제공한다. 새 위젯을 이 예외에 넣으려면 **원문 표시·해제·복구 셋을 모두** 갖춰야 한다. + +**`image` 위젯의 값 형태 — apply 경로에 따라 축약된다**: + +`image` 위젯은 배경 이미지용으로 설계되어 `{url, size, repeat, position}` **객체**를 내보낸다. 그런데 값 슬롯이 하나뿐인 apply 경로(`propValue` / `cssVar` / 단일 `styleProp`)는 그 객체를 담을 수 없다 — 엔진이 `url` 만 남겨 스칼라로 축약한다(컴포넌트 prop 은 맨 문자열, CSS 값 문맥은 `url(...)` 래핑). `url` 이 없거나 비어 있으면 그 자리를 삭제해 컴포넌트의 폴백이 살아난다. + +축약을 전제로 위젯 표면도 달라진다 — 단일 값 슬롯 컨트롤에서는 표시모드(채움/맞춤/타일) 버튼이 **렌더되지 않는다**(`size`/`repeat`/`position` 을 저장할 자리가 없어 눌러도 저장되지 않는 죽은 컨트롤이다). 4속성을 모두 저장하려면 `styleProp` 의 `props` 배열에 `backgroundImage`·`backgroundSize`·`backgroundRepeat`·`backgroundPosition` 을 함께 선언한다. + +`image` 를 `classToken` 이나 `cssVar` 에 연결하거나 `apply` 를 생략하면 값이 문자열로 축약될 경로가 없어 `[object Object]` 가 저장된다 — 정적 검사가 그 선언을 차단한다. + ### componentCapabilities — 컴포넌트별 편집 역량 컴포넌트별로 `propControls`(속성 탭 컨트롤 화이트리스트 — `controls.json` 의 `apply.type==="propValue"` 컨트롤 참조, 비-스타일 prop 편집) / `dataProps`(데이터 연결 선언 — 아래) / `styleControls`(스타일 탭 컨트롤 화이트리스트) / `advanced`(고급 속성) / `events`(액션 편집 이벤트 — 미선언 시 동작 탭 숨김) / `flexEditor`(`container`/`item`/`auto` — 정렬 박스 편집 역할) / `visibilityCondition`(표시조건 탭 허용 여부) / `nodeEditor`·`canvasOverlay`(구조 에디터 일반 슬롯 `{kind,params}` — 종류별 고정 키 금지, kind 핸들러 레지스트리 디스패치)을 선언한다. @@ -172,6 +188,8 @@ php artisan {module|plugin|template}:update {id} --force **코어 제공 속성(요소 ID) + opt-out**: 코어는 모든 draggable 컴포넌트의 [속성] 탭 최상단에 "요소 ID" 컨트롤을 일괄 제공한다(값 = 표준 `node.props.id`, 강제 DOM 주입 없음 — 컴포넌트 passthrough 책임). 기존 `elemId`(→id) 같은 템플릿 propControl 은 코어로 이전(중복 선언 금지 — 코어 우선). 인라인 루트가 없는 컴포넌트(서드파티 모달/Portal)는 capability 에 `"coreProps": false` 로 opt-out 한다. `"coreProps": ["id"]` 처럼 부분집합 선언도 가능(미선언 = 코어 기본 전체). 코어 id 컨트롤은 `{{바인딩}}` 값이면 "바인딩됨(코드 편집)" 디그레이드, HTML 안전 문자만 허용(한글·공백 자동 제거). 컴포넌트 측 id passthrough 규약은 `docs/frontend/components-types.md` "요소 id 패스스루" 참조. +**전용 UI 를 가진 코어 속성은 `coreProps` 로 다시 선언하지 않는다**: `isolatedState`·`isolatedScopeId` 는 편집기가 별도 전용 컨트롤로 제공하며 그것이 값의 단일 출처다. 이 두 키를 `coreProps` 배열에 적어도 [속성] 탭의 일반 컨트롤 목록에는 나타나지 않는다 — 같은 값을 두 경로가 쓰면 값 형태가 갈리기 때문이다(전용 UI 는 켜짐 상태를 객체로 쓰는데 일반 토글 위젯은 `true` 를 낸다). 선언 자체는 오류가 아니고 조용히 무시되므로, 격리 설정을 노출하려면 그 전용 컨트롤을 쓴다. + ### nesting — 중첩 규칙 `draggable`(드래그/팔레트 배치 가능한 컴포넌트 이름) + `containers`(컨테이너별 `accepts` 자식 허용 목록). 병합 시 `draggable` 은 union, `containers` 는 key 병합. `accepts: []` 는 명시적 자식 거부, 엔트리 없는 컴포넌트는 어떤 자식도 받지 않는다(폴백 없음). diff --git a/docs/extension/extension-update-system.md b/docs/extension/extension-update-system.md index b7f04c1c..a39f9bdc 100644 --- a/docs/extension/extension-update-system.md +++ b/docs/extension/extension-update-system.md @@ -238,6 +238,20 @@ $process = proc_open($cmd, $descriptors, $pipes, $cwd, $env); `isCoreUpdateInProgress()` 는 env 외에 `$argv[1]` 이 `core:update` / `core:execute-upgrade-steps` 인 경우도 true 판정 — env 전파 실패 극단 상황 방어용. 하지만 `php -r '...'` 로 기동되는 spawn (inline 스크립트) 은 argv 판정이 되지 않으므로 env 전파가 유일한 수단입니다. +argv 판정은 명령줄 SAPI(`cli`·`phpdbg`)에서만 유효합니다. 웹 요청의 `$_SERVER['argv']` 는 `register_argc_argv` 설정에 따라 쿼리스트링에서 채워지므로 신뢰하지 않으며, 웹 요청 안에서 업데이트 흐름을 시작하는 코드는 env 플래그를 프로세스 안에서 세워 판정을 받습니다. + +#### 판정의 단일 출처와 이 플래그가 게이트하는 것 + +`CoreServiceProvider::isCoreUpdateInProgress()` 는 `App\Support\CoreUpdateContext::isInProgress()` 위임입니다. 같은 플래그가 서로 다른 계층에서 셋을 게이트하므로 판정이 갈라지면 그중 한 경로만 조용히 다르게 동작합니다. + +| 게이트 대상 | 위치 | 플래그가 없으면 | +|-------------|------|-----------------| +| 확장·템플릿 자동 비활성화 스킵 | `CoreServiceProvider::validateAndDeactivate*` | 일시적 버전 불일치로 활성 확장이 꺼진다 | +| 코어 버전의 env `APP_VERSION` 우선 판독 | `CoreVersionChecker::getCoreVersion()` | 캐시된 config 의 fromVersion 으로 판정한다 (반대로, 트리 밖에서 env 를 우선하면 상주 프로세스가 옛 버전을 물고 확장을 끈다) | +| 패키지 매니페스트 자가 치유 | `bootstrap/app.php` | 이전 설치본의 `packages.php` 로 부팅하다 "Class ... not found" 로 죽는다 | + +`bootstrap/app.php` 의 자가 치유 블록은 부팅 전이라 `App\` 클래스를 참조할 수 없어 같은 판정을 순수 PHP 로 복제합니다 — 조건을 바꾸면 양쪽을 함께 고쳐야 합니다. + ### 동적 엔티티 보존 (Permission / Role / Menu) 모듈·플러그인이 런타임에 동적으로 생성한 Permission/Role/Menu 는 정적 정의(`getPermissions()`, `getRoles()`, `getAdminMenus()`)에 포함되지 않으므로, 업데이트 시 `cleanupStaleModuleEntries()` / `cleanupStalePluginEntries()` 가 stale 로 오판하지 않도록 모듈 측에서 아래 hook 을 override 해 현재 식별자 전체를 반환한다. diff --git a/docs/extension/module-assets.md b/docs/extension/module-assets.md index 87c44b9a..08f4ffb1 100644 --- a/docs/extension/module-assets.md +++ b/docs/extension/module-assets.md @@ -520,6 +520,7 @@ GET /api/plugins/bundle.css?v={version} | CSS url() | 상대 `url()`·`@import` 참조는 그 확장의 절대 자산 URL 로 **치환**해 병합. 병합본의 주소는 어느 확장의 dist 디렉토리도 아니라 상대 해석이 반드시 어긋난다 | (계약 테스트) | | 디스크 캐시 fail-soft | 캐시 쓰기 실패는 **500 이 아니다** — 메모리 병합 결과를 그대로 200 으로 서빙 | (계약 테스트) | | 빈 번들 판정 | 선언한 산출물이 소실·판독 불가면 **503**, 존재하되 비었으면 빈 200 (선언 0 도 빈 200) | (계약 테스트) | +| 캐시 우선 | 프로덕션은 `(type, kind, version)` 캐시 파일 존재를 **빌드보다 먼저** 확인한다. 캐시 미스는 같은 키의 잠금으로 1회 빌드에 수렴(잠금 실패는 각자 빌드). 병합 단계에서 건너뛴 확장이 있는 결과는 캐시하지 않는다 | (계약 테스트) | ### 병합 CSS 의 상대 참조 @@ -538,7 +539,7 @@ GET /api/plugins/bundle.css?v={version} | 상태 | 판정 | 응답 | |---|---|---| | 에셋을 선언한 활성 확장이 0개 | 정상 | 빈 200 | -| 선언은 있고 그 산출물이 **전부 존재**하되 비어 있음 | 정상 (스타일이 비어 있는 확장) | 빈 200 | +| 선언은 있고 그 산출물이 **전부 존재**하되 비어 있음 | 정상 (스타일이 비어 있는 확장) | 0바이트 캐시 파일 + 정적 게시, 빈 200 | | 선언한 산출물이 **소실·판독 불가** | 장애 (배포 중 `dist` 가 잠깐 빔, 경로 어긋남) | **503** + `Log::error`(소실 경로 목록) | 장애를 정상으로 흘리면 프론트는 404 도 오류도 받지 못한 채 한참 뒤 "Unknown action handler" 로 죽는다 — 그 시점에는 원인이 번들이라는 사실이 화면에도 로그에도 남아 있지 않다. 반대로 정상을 장애로 잡으면 스타일 소스가 자리표시 주석뿐인 확장만 설치된 기본 구성이 통째로 503 이 되어 **사용자 화면마다 실패 안내가 뜬다.** 판정은 **kind 별**이다(js 만 선언한 확장이 있는 상태에서 css 번들이 비는 것은 정상). @@ -550,7 +551,9 @@ GET /api/plugins/bundle.css?v={version} 0바이트 산출물은 정당한 상태다. 스타일 규칙이 아직 없는 확장이 CSS 를 선언하는 것은 어긋남이 아니며, 그 상태를 배포 장애로 등치하면 정상 사이트가 서비스 불능으로 보고된다. -빈 번들은 정적 게시 대상이 아니다(게시할 사본이 없다). 그 결과 `AssetUrl::extensionBundle()` 이 정적 URL 대신 API URL 을 방출하므로 브라우저에는 정적 404 폴백이 생기지 않고, 그 API 가 위 표대로 빈 200 을 낸다. +선언 산출물이 전부 존재하는 빈 번들은 **0바이트 파일로 캐시·정적 게시**되어 브라우저가 웹서버에서 직접 받는다. 게시하지 않으면 `AssetUrl::extensionBundle()` 이 API URL 을 방출해 그 구성의 **모든 페이지 로드**가 PHP 를 거치고, 그 요청마다 컨트롤러가 활성 확장 열거를 세 번 반복한다(경로 조회 → 재빌드 → 소실 판정). 오류도 로그도 남지 않아 드러나지 않는 경로다. + +캐시하지 않는 것은 셋뿐이다 — 선언 산출물이 **소실**된 경우(503 판정을 그대로 유지해야 한다), 병합 단계에서 확장을 **건너뛴** 경우(파일은 있는데 읽기·치환이 실패한 상태가 캐시로 굳으면 버전 bump 전까지 그 확장 자산이 사라진 채 고정되므로, 종전처럼 매 요청 재시도에 맡긴다), 그리고 디스크 쓰기 자체가 실패한 경우다. 두 판정은 모듈·플러그인 컨트롤러가 **공유하는 단일 지점**(`ServesExtensionBundles::bundleResponse()`)에 둔다. 각자 구현하면 한쪽만 고쳐진 채 다른 쪽이 옛 동작으로 남는다. @@ -569,10 +572,20 @@ php artisan template:cache-clear # 전체 번들 파일 정리 포함 프로덕션은 version-in-path 디스크 캐시, 비프로덕션(dev/watch)은 캐시 없이 매 요청 concat(rebuild 즉시 반영). `_bundled` 수정 후에는 `{type}:update {id} --force` 로 활성 반영 후 version bump 로 번들이 재생성된다. +캐시 적중 시에는 원본 에셋을 읽지 않으므로 원본 교체는 반드시 version bump 로 반영한다 — `{type}:update {id} --force` 가 그 bump 를 수행한다. `dist` 를 손으로 덮어쓰고 bump 를 건너뛰면 같은 version 의 캐시가 계속 서빙된다. + > 개별 에셋 서빙 라우트(`/api/{type}/assets/...`, `*.map` 포함)는 소스맵·static 참조를 위해 존치한다. > 다만 `*.map` 의 **실제 서빙은 `local` 환경에서만** 허용된다 — 소스맵에는 원본 코드 전문이 > 담기므로 운영에서는 확장자 화이트리스트가 차단한다. 상세: [template-security.md](template-security.md) "소스맵 (`map`) — 로컬 개발 환경 전용". +### 요청 제한을 두지 않는 이유 + +공개 번들 라우트(`/api/{modules,plugins}/bundle.{js,css}`)에는 애플리케이션 수준의 요청 제한을 붙이지 않는다. + +- 캐시 우선 순서를 갖춘 뒤로 이 엔드포인트의 응답 비용은 **프레임워크 부팅**이 지배한다. 부팅을 마친 뒤에야 판정할 수 있는 앱 throttle 은 그 비용을 이미 치른 다음이라 방어 효과가 제한적이다. +- 정적 자산 성격의 공개 API 는 요청 제한을 두지 않는 것이 이 저장소의 기존 관행이다(공개 컨트롤러 계층 전반). +- 반복 요청을 실제로 줄이는 것은 앞단 캐시다. 응답이 `Cache-Control: immutable, max-age=31536000, public` 을 내보내므로, 리버스 프록시나 CDN 이 그 헤더를 존중하도록 `/api/*/bundle*` 을 캐시 대상에 넣으면 앱까지 도달하는 요청 자체가 사라진다. 파일명에 캐시 버전이 들어 있어 stale 위험도 없다. + ### 전송 압축 (gzip) 번들 JS/CSS 는 `fileResponse()`(= `response()->file()` → `BinaryFileResponse`)로 서빙되며, `GzipEncodeResponse` 미들웨어가 gzip 압축을 적용한다. `BinaryFileResponse` 는 `getContent()` 가 `false` 를 반환하므로, 미들웨어는 파일 경로(`getFile()->getPathname()`)에서 본문을 읽어 압축한 뒤 헤더(Content-Type/ETag/Cache-Control)를 승계한 일반 `Response` 로 치환한다. diff --git a/docs/extension/storage-driver.md b/docs/extension/storage-driver.md index 8e4ef2d8..fcdca739 100644 --- a/docs/extension/storage-driver.md +++ b/docs/extension/storage-driver.md @@ -1581,8 +1581,9 @@ class AttachmentServiceTest extends TestCase ## 공개 자산 전용 디스크 분리 (직접 URL/CDN 서빙) -완전 공개 자산(상품/카테고리/리뷰/에디터 이미지 등)을 S3+CDN 등 원격 디스크에서 -직접 URL 로 서빙하는 옵트인 기능입니다. 미설정 시 기존 PHP 스트리밍이 100% 보존됩니다. +완전 공개 자산(상품/카테고리/리뷰/에디터 이미지, 레이아웃 배경 이미지 등)을 S3+CDN 등 +원격 디스크에서 직접 URL 로 서빙하는 옵트인 기능입니다. 미설정 시 기존 PHP 스트리밍이 +100% 보존됩니다. ### 설정 사슬 @@ -1608,6 +1609,20 @@ class AttachmentServiceTest extends TestCase - `none` 은 스트리밍 유지(확장 개별 설정에서는 전역이 CDN 이어도 강제 스트리밍)입니다. - 플러그인 비활성화로 디스크가 config 에서 사라지면(고아 디스크) 자동으로 스트리밍 폴백합니다 — 저장값은 보존되므로 재활성화 시 되살아납니다. +- 코어의 **레이아웃 첨부**(레이아웃 편집기에서 올리는 배경 이미지)도 이 배선 대상입니다. + 이 카테고리의 서빙 라우트는 무인증 공개이고 검사가 템플릿 소속 확인 하나뿐이라 + "완전 공개 자산" 에 해당합니다. 설정을 켠 뒤 업로드한 파일이 공개 자산 디스크에 + 저장되고, 그 행의 disk 가 지금 설정된 공개 자산 디스크와 **일치할 때만** 직접 URL 이 + 발급됩니다. 디스크에 공개 URL(`url`) 설정이 있다는 사실만으로는 직접 URL 을 발급하지 + 않습니다 — 그 설정은 "URL 문자열을 만들 수 있는가" 일 뿐 "익명 읽기가 되는가" 가 + 아니어서, 비공개 버킷에 공개 URL 을 적어 둔 구성에서는 발급된 주소가 403 이 됩니다. +- 발급된 주소는 **레이아웃 저장의 외부 URL 차단 규칙을 통과해야** 합니다. 편집기 image 위젯은 + 그 주소를 props 에 그대로 넣고, 헤더 「로고 이미지」처럼 값 슬롯이 하나뿐인 컨트롤은 + `style` 이 아니라 `props` 에 쓰므로 규칙의 스캔 대상입니다. 그래서 공개 서빙(프록시) URL 은 + 사이트 상대 경로(`/api/templates/.../file`)로 발급하고, 직접 URL 의 host(선언된 공개 자산 + 디스크)와 사이트 자기 host 는 규칙이 외부로 보지 않습니다(`App\Support\SiteAssetHosts`). + 프록시 URL 을 절대 형태로 발급하면 그 저장이 422 로 거부될 뿐 아니라, 저장된 레이아웃이 + 발급 시점의 도메인·스킴에 묶여 주소가 바뀌면 그 이미지가 전부 깨집니다. ### 혼재 운용 @@ -1632,6 +1647,15 @@ class AttachmentServiceTest extends TestCase | 객체가 익명 읽기 가능 (버킷 정책 `s3:GetObject` 공개 또는 CDN 공개 배포) | 화면의 그 이미지들이 전부 깨짐 (S3 는 `403 AccessDenied` 를 XML 로 응답) | | 공개 URL base(`S3 URL`)가 그 객체를 가리킴 | `url()` 이 null → 스트리밍 폴백(기능은 정상, CDN 이점만 없음) | +### 켜기 전에 알아야 할 것 + +| 항목 | 내용 | +| --- | --- | +| 이미 올라간 파일에도 소급 적용될 수 있다 | 기존 첨부 디스크와 공개 자산 디스크가 **같은 디스크**(예: 둘 다 S3)라면, 설정을 켜는 순간 켜기 이전에 올라간 첨부까지 직접 URL 로 바뀝니다. 행에 기록된 disk 값이 같아 신·구를 구분할 수단이 없기 때문입니다. 두 디스크를 다르게 두면(예: 첨부는 로컬, 공개 자산은 S3) 켠 뒤 올린 파일만 전환됩니다 | +| 저장된 레이아웃에 주소가 그대로 남는다 | 설정을 켠 동안 배경 이미지를 지정해 저장하면 그 직접 URL 문자열이 레이아웃에 고정됩니다. 이후 설정을 끄거나 버킷을 비공개로 바꿔도 그 문자열은 자동으로 되돌아가지 않습니다(서버가 개입할 지점이 없습니다). 되돌리려면 편집기에서 이미지를 다시 선택해 저장해야 합니다 | +| 첨부를 지워도 직접 URL 은 살아 있을 수 있다 | 직접 URL 은 첨부 기록과 무관하게 저장소의 파일을 가리킵니다. 스트리밍 방식에서는 기록을 지우면 그 주소가 곧바로 404 가 되었지만, 직접 URL 은 파일이 남아 있는 한 계속 열립니다. 접근을 확실히 끊으려면 저장소에서 파일 자체를 지워야 합니다 | +| 확장 훅에 새로운 조합이 흘러간다 | `core.storage.filter_url` 훅을 `scope` 로 분기하는 확장은 이번부터 `scope='core'`, `identifier=null`, `category='template-layout-attachments'` 조합을 받습니다. 지금까지 코어에서 이 훅으로 URL 을 만드는 호출부가 없었으므로 이 조합은 처음 등장합니다 | + 관리자 화면의 썸네일은 교차 출처 공개 URL 을 `` 로 직접 사용하므로 CORS 설정은 필요하지 않습니다. 다만 그 URL 을 자바스크립트로 읽는 커스텀 확장을 만든다면 그때는 버킷/CDN 에 CORS 규칙이 필요합니다. @@ -1651,6 +1675,13 @@ class AttachmentServiceTest extends TestCase 직접 URL 은 서버 스트리밍 경로의 권한 검사를 우회하므로, 완전 공개 자산 카테고리에만 공개 자산 디스크를 적용합니다. +**알려진 한계** — 상품 리뷰 이미지는 공개 자산으로 배선되어 있습니다. 리뷰를 노출에서 +숨김으로 바꾸면 목록·상세 응답에서 이미지가 사라지지만, 공개 자산 디스크를 켠 상태에서 +이미 발급된 직접 URL 을 알고 있는 사람은 그 주소로 파일에 계속 접근할 수 있습니다. +직접 URL 은 저장소의 파일을 직접 가리키므로 응답에서 감추는 것만으로는 회수되지 않습니다. +숨김을 접근 차단으로 쓰려면 그 카테고리에 공개 자산 디스크를 적용하지 않거나, 저장소에서 +파일 자체를 지워야 합니다. + --- ## 관련 문서 diff --git a/docs/extension/template-security.md b/docs/extension/template-security.md index 6afe8f6d..b13572a3 100644 --- a/docs/extension/template-security.md +++ b/docs/extension/template-security.md @@ -37,7 +37,7 @@ Custom Rule: ComponentExists | JSON 구조 유효성 | 올바른 JSON 형식인지 검증 | | 최대 중첩 깊이 | 10단계 제한 | | 엔드포인트 화이트리스트 | `/api/(admin\|auth\|public)/` 패턴만 허용 | -| 외부 URL 금지 | 외부 도메인 URL 차단 | +| 외부 URL 금지 | 외부 도메인 URL 차단 (사이트 자기 host 와 「공개 자산 스토리지」로 선언한 디스크의 host 는 외부가 아니다) | | 컴포넌트 존재 여부 | components.json 기준으로 검증 | --- diff --git a/docs/frontend/actions-handlers-ui.md b/docs/frontend/actions-handlers-ui.md index 7ca56f08..bb0f335b 100644 --- a/docs/frontend/actions-handlers-ui.md +++ b/docs/frontend/actions-handlers-ui.md @@ -20,6 +20,8 @@ 12. [callExternal](#callexternal) 13. [실전 예시](#실전-예시) +> `login` 절에 `loginTwoFactor` · `loginTwoFactorResend` 가 함께 설명되어 있습니다. + --- ## login / logout @@ -61,6 +63,110 @@ | `admin` | 관리자 인증 | | `user` | 사용자 인증 | +### login 의 반환값 — 2단계 인증이 켜진 사이트 + +서버는 보안 환경설정에 따라 **두 가지 형태의 200** 을 돌려줍니다. `onSuccess` 는 두 형태 모두에서 +실행되므로, 후속 액션은 `response.two_factor_required` 로 분기해야 합니다. + +| 필드 | 타입 | 설명 | +|------|------|------| +| `user` | object \| null | 로그인한 사용자. 인증번호 확인이 남았으면 `null` | +| `two_factor_required` | boolean | `true` 면 아직 로그인이 끝나지 않았습니다 | +| `challenge_id` | string | 인증번호 확인에 그대로 전달할 식별자 | +| `provider_id` | string | 인증번호를 보낸 프로바이더 | +| `expires_at` | string \| null | 인증 요청 만료 시각 (ISO8601) | + +분기를 두지 않으면 인증이 끝나기 전에 홈으로 이동하거나, 빈 사용자 정보가 상태에 실립니다. + +```json +{ + "handler": "login", + "target": "user", + "params": { "body": { "email": "{{form.email}}", "password": "{{form.password}}" } }, + "onSuccess": [ + { + "handler": "setState", + "if": "{{response.two_factor_required}}", + "params": { + "target": "global", + "twoFactor": { + "required": true, + "challenge_id": "{{response.challenge_id}}", + "expires_at": "{{response.expires_at}}", + "code": "" + } + } + }, + { + "handler": "navigate", + "if": "{{!response.two_factor_required}}", + "params": { "path": "/" } + } + ] +} +``` + +같은 시퀀스·`onSuccess` 안에서는 방금 저장한 상태(`_global.twoFactor.*`)를 다시 읽지 않습니다 — +그 자리의 상태는 아직 갱신 전이므로 `{{response.*}}` 만 사용합니다. + +### loginTwoFactor + +인증번호를 확인해 로그인을 완료합니다. 성공 시 토큰이 발급되고 `{ user }` 를 돌려줍니다. + +```json +{ + "handler": "loginTwoFactor", + "target": "user", + "params": { + "body": { + "challenge_id": "{{_global.twoFactor?.challenge_id}}", + "code": "{{_global.twoFactor?.code}}" + } + }, + "onSuccess": [ + { "handler": "setState", "params": { "target": "global", "currentUser": "{{response.user}}", "twoFactor": null } }, + { "handler": "navigate", "params": { "path": "/" } } + ], + "onError": [ + { "handler": "setState", "params": { "target": "global", "twoFactor.error": "{{error.message}}" } } + ] +} +``` + +`params.body` 의 `challenge_id` 와 `code` 는 필수입니다. `target` 은 `login` 과 같은 값을 씁니다 +(`user` / `admin`) — 관리자 대상은 관리자 전용 엔드포인트를 호출하고, 관리자가 아니면 `403` 이 됩니다. + +### loginTwoFactorResend + +인증번호를 다시 보냅니다. 서버가 **기존 인증 요청을 취소하고 새로 발행**하므로, 반환된 +`challenge_id` 로 반드시 교체하고 입력란을 비워야 합니다 — 앞서 받은 번호는 더 이상 통하지 않습니다. + +```json +{ + "handler": "loginTwoFactorResend", + "target": "user", + "params": { "body": { "challenge_id": "{{_global.twoFactor?.challenge_id}}" } }, + "onSuccess": [ + { + "handler": "setState", + "params": { + "target": "global", + "twoFactor": { + "required": true, + "challenge_id": "{{response.challenge_id}}", + "expires_at": "{{response.expires_at}}", + "code": "", + "error": null, + "resent": true + } + } + } + ] +} +``` + +반환값은 `{ two_factor_required, challenge_id, provider_id, expires_at }` 입니다. + ### logout 로그아웃하고 토큰을 삭제합니다. diff --git a/docs/frontend/actions-handlers.md b/docs/frontend/actions-handlers.md index fb178a25..e7210490 100644 --- a/docs/frontend/actions-handlers.md +++ b/docs/frontend/actions-handlers.md @@ -56,6 +56,9 @@ ### UI 인터랙션 핸들러 → [상세 문서](actions-handlers-ui.md) 14. [login / logout](actions-handlers-ui.md#login--logout) - 인증 + - [login 의 반환값 — 2단계 인증이 켜진 사이트](actions-handlers-ui.md#login-의-반환값--2단계-인증이-켜진-사이트) + - [loginTwoFactor](actions-handlers-ui.md#logintwofactor) - 인증번호 확인 + - [loginTwoFactorResend](actions-handlers-ui.md#logintwofactorresend) - 인증번호 다시 받기 15. [openModal / closeModal](actions-handlers-ui.md#openmodal--closemodal) - 모달 16. [showAlert / toast](actions-handlers-ui.md#showalert--toast) - 알림 17. [confirm (액션 속성)](actions-handlers-ui.md#confirm-액션-속성) - 실행 전 확인 대화상자 diff --git a/docs/frontend/auth-system.md b/docs/frontend/auth-system.md index 4cea9ad1..f4d6fa09 100644 --- a/docs/frontend/auth-system.md +++ b/docs/frontend/auth-system.md @@ -98,14 +98,15 @@ private getAuthType(route: Route, pathname: string): AuthType { ## API 엔드포인트 -| 구분 | 로그인 | 사용자 정보 | 로그아웃 | 토큰 갱신 | -|------|--------|------------|---------|----------| -| **관리자** | `/auth/login` | `/admin/auth/user` | `/admin/auth/logout` | `/admin/auth/refresh` | -| **일반사용자** | `/auth/login` | `/user/auth/user` | `/user/auth/logout` | `/user/auth/refresh` | +| 구분 | 로그인 | 인증번호 확인 | 인증번호 재발송 | 사용자 정보 | 로그아웃 | 토큰 갱신 | +|------|--------|--------------|----------------|------------|---------|----------| +| **관리자** | `/auth/admin/login` | `/auth/admin/login/two-factor` | `/auth/admin/login/two-factor/resend` | `/admin/auth/user` | `/admin/auth/logout` | `/admin/auth/refresh` | +| **일반사용자** | `/auth/login` | `/auth/login/two-factor` | `/auth/login/two-factor/resend` | `/user/auth/user` | `/user/auth/logout` | `/user/auth/refresh` | **공통**: -- 로그인 엔드포인트는 관리자/일반사용자 동일 (`/auth/login`) +- 로그인 엔드포인트는 관리자/일반사용자가 다릅니다 (`AuthConfig.loginEndpoint`) +- `updateConfig({ loginEndpoint })` 로 템플릿이 재정의한 값이 그대로 사용됩니다 - 인증 후 작업은 각각 분리된 엔드포인트 사용 - API 인증은 Bearer 토큰 전용 (세션 기반 인증 미사용) - 401 응답 시 서버가 세션 쿠키 만료 헤더를 자동 전송 (잔존 쿠키 정리) @@ -161,11 +162,12 @@ user.language 확인 UI 즉시 업데이트 ``` -**구현 위치**: `AuthManager.login()` +**구현 위치**: `AuthManager.establishSession()` — 일반 로그인과 2단계 인증 완료가 같은 후처리를 +공유합니다. 갈라지면 한쪽 경로에서만 로케일 전환이나 이벤트 발행이 빠집니다. ```typescript // 로케일 변경 감지 및 처리 -const userLanguage = response.data.user.language; +const userLanguage = user.language; const currentLocale = localStorage.getItem('g7_locale'); const localeChanged = userLanguage && userLanguage !== currentLocale; @@ -187,6 +189,51 @@ if (localeChanged && window.__templateApp) { --- +## 2단계 인증 로그인 (engine-v1.65.0+) + +보안 환경설정의 「2단계 인증」이 켜져 있으면 서버는 비밀번호가 맞아도 토큰을 발급하지 않고 +인증 요청(challenge)만 돌려줍니다. 즉, **로그인 응답은 두 가지 형태의 200** 입니다. + +```typescript +export type LoginResult = + | { status: 'authenticated'; user: AuthUser } + | { status: 'two_factor_required'; challenge: TwoFactorChallenge }; +``` + +`AuthManager.login()` 은 이 판별 유니온을 돌려줍니다. 한 형태만 가정하면 challenge 응답에서 +`data.user.*` 접근이 예외가 되고, `data.token`(undefined)을 저장하면 `"undefined"` 문자열이 남아 +이후 모든 요청이 401 로 튕깁니다. + +| 메서드 | 하는 일 | +|--------|---------| +| `login(type, credentials, options?)` | 비밀번호 확인. `LoginResult` 반환 | +| `completeTwoFactor(type, { challengeId, code }, options?)` | 인증번호 확인 → 토큰 발급 → `AuthUser` 반환 | +| `resendTwoFactor(type, { challengeId }, options?)` | 인증번호 재발송 → **새** `TwoFactorChallenge` 반환 | + +레이아웃에서는 액션 핸들러 `login` / `loginTwoFactor` / `loginTwoFactorResend` 로 사용합니다 +(→ [actions-handlers-ui.md](actions-handlers-ui.md)). + +### 레이아웃 작성 규칙 + +- 1단계 블록과 2단계 블록의 `if` 는 **상보적**이어야 합니다. 두 블록이 동시에 보이면 인증번호 + 단계에서 이메일·비밀번호가 함께 노출됩니다. +- 제출 시퀀스의 `login` 과 `loginTwoFactor` 도 상호배타 `if` 를 갖습니다. `if` 가 빠지면 인증번호 + 단계에서 Enter 를 누를 때 새 challenge 가 발급되어 흐름이 깨집니다. `if` 는 시퀀스 시작 시점 + 스냅샷으로 평가되므로 한 번의 제출에 정확히 하나만 실행됩니다. +- **같은 시퀀스·`onSuccess` 안에서 방금 저장한 상태를 다시 읽지 않습니다.** 그 자리의 상태는 아직 + 갱신 전이므로 `{{response.*}}` 만 사용합니다. +- 인증번호 입력은 자동바인딩이 아니라 `value` + `onChange` 로 상태가 값을 소유해야 합니다 — + 재발송 시 입력값을 비워야 하기 때문입니다. +- 인증 단계 상태는 화면을 떠나도 남으므로, 로그인 화면 진입 시 `init_actions` 에서 초기화합니다. + +### 재발송의 계약 + +서버는 기존 challenge 를 **취소하고 새로 발행**합니다. 유효한 코드를 여러 개 동시에 살려 두면 +대입 시도의 표적이 넓어지기 때문입니다. 따라서 클라이언트는 응답의 `challenge_id` 로 반드시 +교체하고 입력란을 비워야 합니다. + +--- + ## 사용 예시 ### routes.json 설정 diff --git a/docs/frontend/identity-verification-ui.md b/docs/frontend/identity-verification-ui.md index e5beb01b..5f2c0709 100644 --- a/docs/frontend/identity-verification-ui.md +++ b/docs/frontend/identity-verification-ui.md @@ -316,6 +316,17 @@ IDV 모달 파셜 (`_identity_challenge_modal.json`) 및 동일 패턴을 따르 - code Input 의 `actions[]` 에 `event:"onChange"` + `handler:"setState"` + `target:"global"` 항목이 존재할 것 - 재전송 setState (resendCooldown=30) 의 params 에 `identityChallenge.code: ""` 가 포함될 것 +로그인 2단계 인증 화면도 같은 규칙을 따르며, 회귀 테스트는 +`templates/_bundled/{template}/__tests__/layouts/{admin-,}login-two-factor-step.test.tsx` 가 담당합니다. + +### 5. 로그인 challenge 는 이 화면으로 처리하지 않는다 + +`purpose = login` challenge 는 로그인 전용 엔드포인트(`POST /api/auth/login/two-factor`, +`.../resend`)만 사용합니다. 공개 본인인증 엔드포인트(`POST /api/identity/challenges/{id}/verify`, +`.../cancel`)는 이 목적을 `403 PURPOSE_NOT_ALLOWED` 로 거부합니다 — 여기서 검증·취소되면 그 +challenge 로는 더 이상 로그인을 마칠 수 없게 되고(자기 DoS), 이 화면은 로그인 흐름을 모르므로 +되돌릴 방법도 없기 때문입니다. + ## 관련 문서 - [identity-guard-interceptor.md](identity-guard-interceptor.md) — 코어 인터셉터 API 레퍼런스 diff --git a/docs/frontend/security.md b/docs/frontend/security.md index 72d666e4..859992de 100644 --- a/docs/frontend/security.md +++ b/docs/frontend/security.md @@ -55,7 +55,7 @@ | JSON 구조 | ValidLayoutStructure | 필수 필드, 깊이 10단계 제한, 타입 검증 | | 컴포넌트 | ComponentExists | components.json 매니페스트 대조 | | API 엔드포인트 | WhitelistedEndpoint | `/api/(admin\|auth\|public)/` 패턴만 허용 | -| 외부 URL | NoExternalUrls | http, data, javascript 등 7개 위험 스킴 차단 | +| 외부 URL | NoExternalUrls | http, data, javascript 등 7개 위험 스킴 차단. 사이트 자기 host·선언된 공개 자산 디스크 host 의 http(s) 절대 URL 은 외부가 아니다(서버가 발급하는 첨부 주소) | | 상속 | ValidParentLayout | 순환 참조 방지, 상속 깊이 10 제한 | | 슬롯 | ValidSlotStructure | 부모에서 정의된 슬롯만 허용 | | 데이터소스 | ValidDataSourceMerge | 상속 체인 ID 고유성 | diff --git a/docs/requirements.md b/docs/requirements.md index 83e6aa08..2d577991 100644 --- a/docs/requirements.md +++ b/docs/requirements.md @@ -174,6 +174,11 @@ sudo chmod -R 755 storage bootstrap/cache vendor modules plugins templates publi 두 도구 모두 필요하지 않다.** 소스에서 직접 빌드하거나 개발 환경을 구성할 때, 또는 Composer 설치 방식을 선택할 때만 필요하다. +Composer 로 직접 설치한다면 `composer install --no-dev --optimize-autoloader` 를 쓴다. +옵션 없는 `composer install` 은 개발용 패키지까지 설치하며, 그 상태로 운영하면 +이후 코어 업데이트가 vendor 를 교체할 때 이전 패키지 목록 캐시와 어긋나 부팅이 깨진다. +개발용 패키지가 섞여 있으면 설치 마법사의 「설치 환경 확인」 단계가 경고한다. + --- ## 2. 데이터베이스 diff --git a/docs/testing/e2e-testing.md b/docs/testing/e2e-testing.md index bf1b404a..44ed0554 100644 --- a/docs/testing/e2e-testing.md +++ b/docs/testing/e2e-testing.md @@ -132,6 +132,7 @@ UA 를 실제 브라우저 값으로 고정해도 그 검증은 그대로 동작 | 공유 상태 | 같은 관리자 설정 화면을 건드리는 spec 이 병렬로 돌면 서로의 저장 상태를 덮어써 실패할 수 있다. 실행 옵션에 맡기지 않고 그 `describe` 에 `test.describe.configure({ mode: 'serial' })` 를 둔다 | | 워커 수 | 관리자 SPA 는 번들이 크고 레이아웃을 여러 번 받아온다. 개발 머신에서 2워커 이상이면 `page.waitForLoadState` 가 30초를 넘겨 **비결정적으로** 실패한다(실측: 같은 스위트가 회차마다 다른 5~7건 실패, 테스트당 8초 → 25초). 판정은 `--workers=1` 결과로 한다 | | 편집기 spec 만 몰아 실행할 때 | 레이아웃 편집기 spec 만 골라 돌리면 워커 전부가 동시에 편집기 페이지를 연다 — 전체 스위트에서는 가벼운 spec 이 섞여 그 집중이 생기지 않는다. 실측: 편집기 7파일 27건을 7워커로 돌리면 `g7le-preview-frame` 대기가 전부 30초 타임아웃(27/27 실패), 같은 코드로 2워커는 26/27 통과. 편집기만 선택 실행할 때는 `--workers=2` 이하로 둔다 | +| 봇 렌더 spec 의 IP 예산 | 검색봇 화면의 미스 렌더는 **IP 당 분당 상한**(`G7_SEO_RENDER_MISSES_PER_MINUTE`, 기본 60)을 쓴다. 자동 검사는 전부 같은 IP 에서 나가므로 봇 경로 spec 을 많이 몰아 돌리면 예산을 넘긴 요청이 SEO HTML 대신 SPA(`X-SEO-Cache: BYPASS`)를 받아 본문 단언이 실패한다. 오류가 아니라 200 이라 원인이 드러나지 않으므로, 봇 spec 이 간헐 실패하면 응답 헤더가 `BYPASS` 인지 먼저 본다 (실측: `seo-bot-rendering` 12건 + `page-og-image` 2건은 기본값에서 여유가 있다) | ### 브라우저 범위 @@ -168,6 +169,7 @@ Firefox/WebKit 프로젝트를 상시 스위트에 넣지 않은 이유: |---|---|---|---| | 코어 권한/역할/유저/Sanctum 토큰 | 코어 | `app/Console/Commands/PlaywrightIssueToken.php` | `php artisan playwright:issue-token --permissions=core.xxx` (권한 경계 검증은 `--no-admin-role` 추가 — §5.1) | | 편집기 저장 spec 대상 시드 화면 | 코어 | `app/Console/Commands/PlaywrightSeedLayout.php` | `php artisan playwright:seed-layout [--remove]` (globalSetup/globalTeardown 자동 호출) | +| 로그인 2단계 인증 계정·인증번호 | 코어 | `app/Console/Commands/PlaywrightSeedTwoFactor.php` | `php artisan playwright:seed-two-factor --ensure-user=… \| --plant=` (§4.2) | | 모듈 권한 (`sirsoft-ecommerce.*`) | 모듈 | 코어 커맨드의 `--permissions=` 임의 식별자 | 동일 (Permission::firstOrCreate 자동 생성) | | 모듈 도메인 데이터 (상품/주문) | 모듈 | `modules/_bundled/{id}/src/Console/Commands/PlaywrightSeed{id}.php` | `php artisan playwright:seed-{id}` | | 플러그인 도메인 데이터 (결제 키) | 플러그인 | `plugins/_bundled/{id}/src/Console/Commands/PlaywrightSeed{id}.php` | 동일 | @@ -175,6 +177,50 @@ Firefox/WebKit 프로젝트를 상시 스위트에 넣지 않은 이유: **핵심 원칙**: 코어는 모듈 도메인을 모른다. 모듈 도메인 시드를 코어에 두면 의존 역전. +### 4.2 로그인 2단계 인증 픽스처 + +2단계 인증은 **사이트 설정과 메일 발송**에 의존하므로 브라우저만으로는 재현할 수 없다. +`tests/Playwright/fixtures/two-factor.ts` 가 세 가지를 가역적으로 준비한다. + +| 준비 항목 | 방법 | 원복 | +|---|---|---| +| 보안 설정 `security.two_factor_auth` | 관리자 토큰으로 `/api/admin/settings` GET → POST (탭 전체를 되돌려 보낸다) | 읽어 둔 탭 전체 값을 그대로 되쓴다 | +| 메일 발송 | 받아서 버리는 로컬 SMTP 싱크를 띄우고 `mail.json` 의 `host`/`port`/`encryption` 을 줄 단위 치환 | 백업 파일에서 바이트 그대로 복원 | +| 인증번호 | `php artisan playwright:seed-two-factor --plant= --code=135790` | 없음 (challenge 는 일회성) | +| 테스트 계정 | `--ensure-user=` 로 생성 | `--purge-users` 로 종료 시 즉시 제거 | + +**메일을 `log` 메일러로 돌리지 않는 이유**: `log` 는 이 제품의 설정 스키마에 없다. 저장 검증이 +등록된 메일 드라이버(`smtp`·`mailgun`·`ses`)만 허용하고, 설정 파일을 직접 고쳐도 드라이버 해석 +단계에서 `smtp` 로 되돌아간다(실측 확인). 그래서 `tests/Playwright/fixtures/smtp-sink.ts` 가 +Node 기본 `net` 만으로 최소 SMTP 서버를 127.0.0.1 에 잠깐 띄우고 그쪽으로 돌린다 — +인증도 TLS 도 요구하지 않으며 받은 메일은 어디에도 남기지 않는다. + +**메일 설정만 파일을 직접 다루는 이유**: 원래 값(빈 SMTP 호스트)은 저장 검증(`smtp` 이면 host +필수)을 통과하지 못해 **API 로는 되돌릴 수 없다**. 보안 설정은 API 로, 메일 설정은 파일 +백업·복원으로 왕복한다. + +`playwright:seed-two-factor` 계약: + +| 옵션 | 하는 일 | +|---|---| +| `--ensure-user=<접미사>` | `playwright_2fa_<접미사>@example.test` 계정을 알려진 비밀번호로 생성/갱신하고 이메일을 출력. 잠금·실패 카운트도 초기화한다 | +| `--admin` | 위 계정에 admin 역할 부여 (없으면 기존 역할을 떼어 관리자 거부 경로를 재현할 수 있게 한다) | +| `--password=` | `--ensure-user` 가 설정할 비밀번호 (기본 `Passw0rd!2fa`) | +| `--plant=` | 그 challenge 에 알려진 인증번호의 해시를 심고 코드를 출력 | +| `--code=` | `--plant` 가 심을 인증번호 (기본 `135790`) | +| `--gc-hours=` | 이 시간보다 오래된 테스트 계정 정리 (기본 6, `0` 이면 정리 안 함) | +| `--purge-users` | 나이와 무관하게 테스트 계정을 전부 제거 — 실측 종료 직후 호출한다. 이 계정들은 알려진 비밀번호를 갖고 관리자용은 관리자 역할까지 가지므로, 나이 기준 정리를 기다리는 사이가 그대로 열린 문이 된다 | + +가드는 `playwright:issue-token` 과 동형이다 — CLI 한정 + `G7_PLAYWRIGHT_BYPASS=1` + `APP_DEBUG` 강제. + +인증번호는 해시로만 저장되어 되읽을 수 없다. 메일함을 실제로 여는 대신 알려진 값을 심는 이유이며, +방식은 PHPUnit `TwoFactorAuthTest::issuedCode()` 와 같다 — 검증 대상은 코드 생성이 아니라 +로그인 흐름(코드 확인 전 토큰 미발급 / 확인 후 발급)이다. + +**원복 실패를 삼키지 않는다.** 되돌리지 못한 채 끝나면 사이트가 2단계 인증이 켜진 상태로 남아 +이후 **모든 로그인이 막힌다**. 그래서 이 spec 은 `test.describe.configure({ mode: 'serial' })` +로 한 워커에서만 실행하고, `afterAll` 의 원복 실패는 그대로 던진다. + ### 4.1 저장(PUT)하는 spec 은 제품 화면을 대상으로 두지 않는다 레이아웃 편집기 spec 이 저장까지 수행하면 그 편집 결과는 **그대로 영속된다**. 대상이 제품 화면 diff --git a/lang-packs/_bundled/g7-core-ja/CHANGELOG.md b/lang-packs/_bundled/g7-core-ja/CHANGELOG.md index 9d1c4d46..73aae299 100644 --- a/lang-packs/_bundled/g7-core-ja/CHANGELOG.md +++ b/lang-packs/_bundled/g7-core-ja/CHANGELOG.md @@ -4,6 +4,16 @@ 형식은 [Keep a Changelog](https://keepachangelog.com/ko/1.1.0/)를 따르며, [Semantic Versioning](https://semver.org/lang/ko/)을 준수합니다. +## [1.0.10] - 2026-09-09 + +### Added + +- 로그인 시도가 많아 잠시 차단될 때의 안내와, 본인인증 화면에서 로그인용 인증 요청을 처리할 수 없다는 안내의 일본어 번역을 추가했습니다. +- 레이아웃 편집기 [화면 동작] 탭의 「인증번호 확인」·「인증번호 다시 받기」 항목 이름의 일본어 번역을 추가했습니다. +- 레이아웃 편집기에서 환경설정과 연결된 항목에 표시되는 안내와 「직접 지정으로 바꾸기」·「데이터 연결로 되돌리기」 버튼의 일본어 번역을 추가했습니다. +- 레이아웃 편집기 숫자 입력 항목의 입력 자리 표시 문구의 일본어 번역을 추가했습니다. +- 레이아웃 편집기 「확장 편집」 저장이 끼워 넣은 요소를 되돌릴 수 없어 막혔을 때 표시되는 안내의 일본어 번역을 추가했습니다. + ## [1.0.9] - 2026-09-06 ### Added diff --git a/lang-packs/_bundled/g7-core-ja/backend/ja/auth.php b/lang-packs/_bundled/g7-core-ja/backend/ja/auth.php index 5b5c0096..d00372e7 100644 --- a/lang-packs/_bundled/g7-core-ja/backend/ja/auth.php +++ b/lang-packs/_bundled/g7-core-ja/backend/ja/auth.php @@ -36,6 +36,7 @@ return [ 'account_pending_verification' => '本人認証が完了していないアカウントです。メール認証を完了してください。', 'account_locked' => 'ログイン試行回数の超過によりアカウントがロックされました。:minutes分後に再度お試しください。', 'account_locked_permanently' => 'ログイン試行回数の超過によりアカウントがロックされました。管理者にお問い合わせください。', + 'too_many_attempts' => 'リクエストが多すぎます。:seconds秒後にもう一度お試しください。', 'account_unlocked' => 'アカウントのロックを解除しました。', 'reset_token_invalid' => '無効なパスワードリセットトークンです。', 'reset_token_expired' => 'パスワードリセットトークンが有効期限切れです。再度リクエストしてください。', diff --git a/lang-packs/_bundled/g7-core-ja/backend/ja/identity.php b/lang-packs/_bundled/g7-core-ja/backend/ja/identity.php index 3d1546a3..0ab1abb1 100644 --- a/lang-packs/_bundled/g7-core-ja/backend/ja/identity.php +++ b/lang-packs/_bundled/g7-core-ja/backend/ja/identity.php @@ -30,6 +30,7 @@ return [ 'admin_policy_has_no_default' => '管理者が直接作成したポリシーには宣言デフォルト値がありません。', 'reset_field_failed' => '宣言デフォルト値の復元に失敗しました。フィールドが有効であることを確認してください。', 'cannot_delete_system_policy' => 'システムが宣言したポリシーは削除できません。管理者が直接作成したポリシーのみ削除できます。', + 'purpose_not_allowed' => 'この認証リクエストはこの画面では処理できません。', ], 'messages' => [ 'challenge_requested' => '本人認証コードを送信しました。', diff --git a/lang-packs/_bundled/g7-core-ja/frontend/partial/layout_editor.json b/lang-packs/_bundled/g7-core-ja/frontend/partial/layout_editor.json index 983cae01..fbaa23b6 100644 --- a/lang-packs/_bundled/g7-core-ja/frontend/partial/layout_editor.json +++ b/lang-packs/_bundled/g7-core-ja/frontend/partial/layout_editor.json @@ -299,6 +299,8 @@ "blocked_inactive_extension": "無効化された拡張コンポーネントが新たに追加されているため保存できません", "network_error_title": "保存中にネットワークエラーが発生しました", "guard_no_document": "保存するレイアウトドキュメントが読み込まれていません", + "guard_extension_reassembly_title": "拡張の内容を元の位置に戻せないため保存しませんでした", + "guard_extension_reassembly": "この拡張が差し込んだ要素 {count} 件が、どの位置から来たのか確認できません。このまま保存するとその要素が消えるため、保存を止めました。ページを再読み込みしてからもう一度お試しください。", "concurrent": { "title": "別のユーザーが先に保存しました", "message": "このレイアウトは別の管理者が先に保存しました。最新版を読み込みますか?", @@ -322,6 +324,9 @@ "mode_tile": "タイル", "upload_failed": "画像アップロードに失敗しました" }, + "number": { + "placeholder": "数値を入力" + }, "dimension": { "placeholder": "例: 320px, 50%, 24rem, auto" }, @@ -335,6 +340,11 @@ "right": "右", "bottom": "下", "left": "左" + }, + "bound_value": { + "notice": "データ連携の値です(自動で入ります)", + "replace": "直接指定に変更", + "restore": "データ連携に戻す" } }, "recipe": { @@ -1280,6 +1290,14 @@ "label": "ログイン処理", "param_body": "ログイン情報" }, + "login_two_factor": { + "label": "認証番号の確認", + "param_body": "認証情報" + }, + "login_two_factor_resend": { + "label": "認証番号の再送信", + "param_body": "認証リクエスト情報" + }, "logout": { "label": "ログアウト処理", "param_target": "移動先" diff --git a/lang-packs/_bundled/g7-core-ja/language-pack.json b/lang-packs/_bundled/g7-core-ja/language-pack.json index eddb5bd1..fa5cb272 100644 --- a/lang-packs/_bundled/g7-core-ja/language-pack.json +++ b/lang-packs/_bundled/g7-core-ja/language-pack.json @@ -12,7 +12,7 @@ "en": "G7 core Japanese language pack (bundled)", "ja": "G7 コア 日本語 言語パック(バンドル)" }, - "version": "1.0.9", + "version": "1.0.10", "license": "MIT", "scope": "core", "target_identifier": null, diff --git a/lang-packs/_bundled/g7-template-sirsoft-admin_basic-ja/CHANGELOG.md b/lang-packs/_bundled/g7-template-sirsoft-admin_basic-ja/CHANGELOG.md index e70e3066..abcf6c04 100644 --- a/lang-packs/_bundled/g7-template-sirsoft-admin_basic-ja/CHANGELOG.md +++ b/lang-packs/_bundled/g7-template-sirsoft-admin_basic-ja/CHANGELOG.md @@ -4,6 +4,13 @@ 형식은 [Keep a Changelog](https://keepachangelog.com/ko/1.1.0/)를 따르며, [Semantic Versioning](https://semver.org/lang/ko/)을 준수합니다. +## [1.0.9] - 2026-09-09 + +### Added + +- 관리자 로그인 화면의 2단계 인증(인증번호 입력·다시 받기·처음부터·유효 시각)과 계정 잠금 해제 시각 안내의 일본어 번역을 추가했습니다. +- 환경설정 > 드라이버 「공개 자산 스토리지」 설명에 추가된 문장(레이아웃 배경 이미지 포함, 켠 뒤 올린 파일만 직접 주소로 발급)의 일본어 번역을 갱신했습니다. + ## [1.0.8] - 2026-09-06 ### Added diff --git a/lang-packs/_bundled/g7-template-sirsoft-admin_basic-ja/frontend/partial/admin.json b/lang-packs/_bundled/g7-template-sirsoft-admin_basic-ja/frontend/partial/admin.json index 465de995..35d33011 100644 --- a/lang-packs/_bundled/g7-template-sirsoft-admin_basic-ja/frontend/partial/admin.json +++ b/lang-packs/_bundled/g7-template-sirsoft-admin_basic-ja/frontend/partial/admin.json @@ -1697,7 +1697,7 @@ }, "public_asset": { "title": "公開アセットストレージ", - "desc": "完全公開アセット(商品・カテゴリ・レビュー・エディタ画像など)を直接URLで配信するディスクを設定します。使用しない場合は既存のストリーミング方式で動作します。", + "desc": "完全公開アセット(商品・カテゴリ・レビュー・エディタ画像、レイアウト背景画像など)を直接URLで配信するディスクを設定します。使用しない場合は既存のストリーミング方式で動作します。有効化後にアップロードしたファイルのみ直接URLが発行され、そのアドレスは保存されたレイアウトにそのまま残ります。", "disk": "ディスク", "s3_help": "S3を選択する場合は、上のファイルストレージカードのS3 URL(CDNドメイン)設定が必要です。未設定の場合はストリーミングで動作します。" }, diff --git a/lang-packs/_bundled/g7-template-sirsoft-admin_basic-ja/frontend/partial/auth.json b/lang-packs/_bundled/g7-template-sirsoft-admin_basic-ja/frontend/partial/auth.json index dcb06c03..8be0ae77 100644 --- a/lang-packs/_bundled/g7-template-sirsoft-admin_basic-ja/frontend/partial/auth.json +++ b/lang-packs/_bundled/g7-template-sirsoft-admin_basic-ja/frontend/partial/auth.json @@ -8,6 +8,19 @@ "processing": "処理中...", "remember": "ログイン状態を保持する", "forgot": "パスワードをお忘れですか?", + "two_factor": { + "sent": "認証番号を送信しました。受け取った番号を入力してログインを完了してください。", + "resent": "認証番号を再送信しました。新しく受け取った番号を入力してください。", + "code_label": "認証番号", + "code_placeholder": "受け取った認証番号を入力してください", + "valid_until": "有効期限 {{until}} まで", + "verify": "認証番号を確認", + "verifying": "確認中...", + "resend": "認証番号を再送信", + "restart": "最初から" + }, + "locked_until": "解除予定: {{until}}", + "locked_permanent": "管理者にお問い合わせのうえロックを解除してください。", "error": { "email_required": "メールアドレスを入力してください。", "email_invalid": "正しいメールアドレスを入力してください。", diff --git a/lang-packs/_bundled/g7-template-sirsoft-admin_basic-ja/language-pack.json b/lang-packs/_bundled/g7-template-sirsoft-admin_basic-ja/language-pack.json index 7c0c015d..5b1242c7 100644 --- a/lang-packs/_bundled/g7-template-sirsoft-admin_basic-ja/language-pack.json +++ b/lang-packs/_bundled/g7-template-sirsoft-admin_basic-ja/language-pack.json @@ -12,7 +12,7 @@ "en": "G7 template (sirsoft-admin_basic) Japanese language pack (bundled)", "ja": "G7 テンプレート (sirsoft-admin_basic) 日本語 言語パック(バンドル)" }, - "version": "1.0.8", + "version": "1.0.9", "license": "MIT", "scope": "template", "target_identifier": "sirsoft-admin_basic", diff --git a/lang-packs/_bundled/g7-template-sirsoft-basic-ja/CHANGELOG.md b/lang-packs/_bundled/g7-template-sirsoft-basic-ja/CHANGELOG.md index b8efdd12..bbb8281c 100644 --- a/lang-packs/_bundled/g7-template-sirsoft-basic-ja/CHANGELOG.md +++ b/lang-packs/_bundled/g7-template-sirsoft-basic-ja/CHANGELOG.md @@ -4,6 +4,12 @@ 형식은 [Keep a Changelog](https://keepachangelog.com/ko/1.1.0/)를 따르며, [Semantic Versioning](https://semver.org/lang/ko/)을 준수합니다. +## [1.1.3] - 2026-09-09 + +### Added + +- 로그인 화면의 2단계 인증(인증번호 입력·다시 받기·처음부터·유효 시각)과 계정 잠금 해제 시각 안내의 일본어 번역을 추가했습니다. + ## [1.1.2] - 2026-08-24 ### Fixed diff --git a/lang-packs/_bundled/g7-template-sirsoft-basic-ja/frontend/partial/auth.json b/lang-packs/_bundled/g7-template-sirsoft-basic-ja/frontend/partial/auth.json index 26f6ab6b..c04571ef 100644 --- a/lang-packs/_bundled/g7-template-sirsoft-basic-ja/frontend/partial/auth.json +++ b/lang-packs/_bundled/g7-template-sirsoft-basic-ja/frontend/partial/auth.json @@ -141,6 +141,19 @@ "minTypes": "{{count}}種類以上の組み合わせ (大文字/小文字、数字、特殊文字)" } }, + "two_factor": { + "sent": "認証番号を送信しました。受け取った番号を入力してログインを完了してください。", + "resent": "認証番号を再送信しました。新しく受け取った番号を入力してください。", + "code_label": "認証番号", + "code_placeholder": "受け取った認証番号を入力してください", + "valid_until": "有効期限 {{until}} まで", + "verify": "認証番号を確認", + "verifying": "確認中...", + "resend": "認証番号を再送信", + "restart": "最初から" + }, + "locked_until": "解除予定: {{until}}", + "locked_permanent": "管理者にお問い合わせのうえロックを解除してください。", "session_expired_toast": "セッションが期限切れになりました。もう一度ログインしてください。", "guest_checkout": { "divider": "または", diff --git a/lang-packs/_bundled/g7-template-sirsoft-basic-ja/language-pack.json b/lang-packs/_bundled/g7-template-sirsoft-basic-ja/language-pack.json index 40396a18..73469da6 100644 --- a/lang-packs/_bundled/g7-template-sirsoft-basic-ja/language-pack.json +++ b/lang-packs/_bundled/g7-template-sirsoft-basic-ja/language-pack.json @@ -12,7 +12,7 @@ "en": "G7 template (sirsoft-basic) Japanese language pack (bundled)", "ja": "G7 テンプレート (sirsoft-basic) 日本語 言語パック(バンドル)" }, - "version": "1.1.2", + "version": "1.1.3", "license": "MIT", "scope": "template", "target_identifier": "sirsoft-basic", diff --git a/lang/en/auth.php b/lang/en/auth.php index 3e782708..214e1472 100644 --- a/lang/en/auth.php +++ b/lang/en/auth.php @@ -38,6 +38,7 @@ return [ 'account_pending_verification' => 'Identity verification is not complete. Please complete the email verification.', 'account_locked' => 'Too many failed login attempts. Your account is locked for :minutes minute(s).', 'account_locked_permanently' => 'Too many failed login attempts. Your account has been locked. Please contact an administrator.', + 'too_many_attempts' => 'Too many requests. Please try again in :seconds second(s).', 'account_unlocked' => 'The account lock has been released.', // Password reset diff --git a/lang/en/identity.php b/lang/en/identity.php index 46cbf1d3..6bf65d03 100644 --- a/lang/en/identity.php +++ b/lang/en/identity.php @@ -31,6 +31,7 @@ return [ 'admin_policy_has_no_default' => 'Admin-created policies do not have a declared default.', 'reset_field_failed' => 'Failed to reset the field to its declared default. Check if the field is valid.', 'cannot_delete_system_policy' => 'System-declared policies cannot be deleted. Only administrator-created policies can be deleted.', + 'purpose_not_allowed' => 'This verification request cannot be handled on this screen.', ], 'messages' => [ diff --git a/lang/ko/auth.php b/lang/ko/auth.php index fe18839f..d871ef60 100644 --- a/lang/ko/auth.php +++ b/lang/ko/auth.php @@ -38,6 +38,7 @@ return [ 'account_pending_verification' => '본인인증이 완료되지 않은 계정입니다. 이메일 인증을 완료해주세요.', 'account_locked' => '로그인 시도 횟수 초과로 계정이 잠겼습니다. :minutes분 후 다시 시도해주세요.', 'account_locked_permanently' => '로그인 시도 횟수 초과로 계정이 잠겼습니다. 관리자에게 문의해주세요.', + 'too_many_attempts' => '요청이 너무 잦습니다. :seconds초 후에 다시 시도해주세요.', 'account_unlocked' => '계정 잠금이 해제되었습니다.', // 비밀번호 재설정 diff --git a/lang/ko/identity.php b/lang/ko/identity.php index 1f1d8b47..24d25ec6 100644 --- a/lang/ko/identity.php +++ b/lang/ko/identity.php @@ -31,6 +31,7 @@ return [ 'admin_policy_has_no_default' => '관리자가 직접 생성한 정책에는 선언 기본값이 없습니다.', 'reset_field_failed' => '선언 기본값 복원에 실패했습니다. 필드가 유효한지 확인하세요.', 'cannot_delete_system_policy' => '시스템이 선언한 정책은 삭제할 수 없습니다. 관리자가 직접 생성한 정책만 삭제할 수 있습니다.', + 'purpose_not_allowed' => '이 인증 요청은 이 화면에서 처리할 수 없습니다.', ], 'messages' => [ diff --git a/lang/partial/en/layout_editor.json b/lang/partial/en/layout_editor.json index e85c12b6..e9857188 100644 --- a/lang/partial/en/layout_editor.json +++ b/lang/partial/en/layout_editor.json @@ -320,6 +320,8 @@ "blocked_inactive_extension": "Cannot save — newly added components belong to a deactivated extension", "network_error_title": "Network error while saving", "guard_no_document": "No layout document loaded — nothing to save", + "guard_extension_reassembly_title": "Not saved — extension content could not be mapped back", + "guard_extension_reassembly": "{count} element(s) injected by this extension could not be traced to their original slot. Saving would drop them, so the save was blocked. Reload the page and try again.", "concurrent": { "title": "Another user saved first", "message": "This layout was saved by another administrator. Load the latest version?", @@ -343,6 +345,9 @@ "mode_tile": "Tile", "upload_failed": "Image upload failed" }, + "number": { + "placeholder": "Enter a number" + }, "dimension": { "placeholder": "e.g. 320px, 50%, 24rem, auto" }, @@ -356,6 +361,11 @@ "right": "Right", "bottom": "Bottom", "left": "Left" + }, + "bound_value": { + "notice": "Bound to data (filled automatically)", + "replace": "Replace with a fixed value", + "restore": "Restore the data binding" } }, "recipe": { @@ -1301,6 +1311,14 @@ "label": "Handle login", "param_body": "Login info" }, + "login_two_factor": { + "label": "Verify code", + "param_body": "Verification info" + }, + "login_two_factor_resend": { + "label": "Resend code", + "param_body": "Challenge info" + }, "logout": { "label": "Handle logout", "param_target": "Destination" diff --git a/lang/partial/ko/layout_editor.json b/lang/partial/ko/layout_editor.json index ee8b22ec..e063ae6e 100644 --- a/lang/partial/ko/layout_editor.json +++ b/lang/partial/ko/layout_editor.json @@ -320,6 +320,8 @@ "blocked_inactive_extension": "비활성화된 확장 컴포넌트가 새로 추가되어 저장할 수 없습니다", "network_error_title": "저장 중 네트워크 오류가 발생했습니다", "guard_no_document": "저장할 레이아웃 문서가 로드되지 않았습니다", + "guard_extension_reassembly_title": "확장 내용을 되돌릴 수 없어 저장하지 않았습니다", + "guard_extension_reassembly": "이 확장이 끼워 넣은 요소 {count}개가 어느 자리에서 왔는지 확인되지 않습니다. 그대로 저장하면 그 요소가 사라지므로 저장을 막았습니다. 페이지를 새로고침한 뒤 다시 시도해 주세요.", "concurrent": { "title": "다른 사용자가 먼저 저장했습니다", "message": "이 레이아웃은 다른 관리자가 먼저 저장했습니다. 최신 버전을 불러오시겠습니까?", @@ -343,6 +345,9 @@ "mode_tile": "타일", "upload_failed": "이미지 업로드에 실패했습니다" }, + "number": { + "placeholder": "숫자 입력" + }, "dimension": { "placeholder": "예: 320px, 50%, 24rem, auto" }, @@ -356,6 +361,11 @@ "right": "오른쪽", "bottom": "아래", "left": "왼쪽" + }, + "bound_value": { + "notice": "데이터 연결 값입니다(자동으로 채워집니다)", + "replace": "직접 지정으로 바꾸기", + "restore": "데이터 연결로 되돌리기" } }, "recipe": { @@ -1301,6 +1311,14 @@ "label": "로그인 처리", "param_body": "로그인 정보" }, + "login_two_factor": { + "label": "인증번호 확인", + "param_body": "인증 정보" + }, + "login_two_factor_resend": { + "label": "인증번호 다시 받기", + "param_body": "인증 요청 정보" + }, "logout": { "label": "로그아웃 처리", "param_target": "이동할 곳" diff --git a/modules/_bundled/gnuboard7-hello_module/CHANGELOG.md b/modules/_bundled/gnuboard7-hello_module/CHANGELOG.md index 2a3ad8b4..7d843c27 100644 --- a/modules/_bundled/gnuboard7-hello_module/CHANGELOG.md +++ b/modules/_bundled/gnuboard7-hello_module/CHANGELOG.md @@ -19,6 +19,7 @@ ### Changed - 확장 문서(README · AGENTS.md · docs/README.md)의 제목에 「그누보드7」과 확장 유형을 함께 표기했습니다. 제목만 보고도 그누보드7의 어떤 종류 확장인지 알 수 있습니다. +- Composer 잠금 파일을 확장 정보와 다시 맞췄습니다. 설치·업데이트 과정에서 잠금 파일이 오래되었다는 경고가 나오지 않습니다. ## [0.1.1] - 2026-08-10 diff --git a/modules/_bundled/gnuboard7-hello_module/composer.lock b/modules/_bundled/gnuboard7-hello_module/composer.lock index c4664d23..94b9f3af 100644 --- a/modules/_bundled/gnuboard7-hello_module/composer.lock +++ b/modules/_bundled/gnuboard7-hello_module/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "4eddbf75b302677f7e516f8a10d236dc", + "content-hash": "71bfa007f76af57a7cc3d98cc663b171", "packages": [], "packages-dev": [], "aliases": [], @@ -16,5 +16,5 @@ "php": "^8.2" }, "platform-dev": {}, - "plugin-api-version": "2.6.0" + "plugin-api-version": "2.9.0" } diff --git a/modules/_bundled/sirsoft-board/CHANGELOG.md b/modules/_bundled/sirsoft-board/CHANGELOG.md index 7855cda1..c962ea53 100644 --- a/modules/_bundled/sirsoft-board/CHANGELOG.md +++ b/modules/_bundled/sirsoft-board/CHANGELOG.md @@ -4,6 +4,16 @@ 형식은 [Keep a Changelog](https://keepachangelog.com/ko/1.1.0/)를 따르며, [Semantic Versioning](https://semver.org/lang/ko/)을 준수합니다. +## [1.1.2] - 2026-09-09 + +### Changed + +- Composer 잠금 파일을 최신 Composer 형식으로 갱신했습니다. + +### Fixed + +- 첨부파일을 올린 뒤 돌려주는 응답의 파일 주소 칸이 늘 비어 있던 문제를 수정했습니다. 이 칸을 읽어 연동하던 외부 도구는 주소를 얻지 못했습니다. 이제 게시판의 첨부 다운로드 주소가 채워지며, 이 주소는 비밀글·삭제글 확인을 그대로 거치므로 볼 수 없는 첨부가 열리지는 않습니다. + ## [1.1.1] - 2026-09-06 ### Added diff --git a/modules/_bundled/sirsoft-board/README.md b/modules/_bundled/sirsoft-board/README.md index 6569eaec..f40d2592 100644 --- a/modules/_bundled/sirsoft-board/README.md +++ b/modules/_bundled/sirsoft-board/README.md @@ -5,7 +5,7 @@

- version 1.1.1 + version 1.1.2 type 모듈 그누보드7 >=7.0.10 license MIT diff --git a/modules/_bundled/sirsoft-board/composer.json b/modules/_bundled/sirsoft-board/composer.json index f2c4f06a..e143c37f 100644 --- a/modules/_bundled/sirsoft-board/composer.json +++ b/modules/_bundled/sirsoft-board/composer.json @@ -2,7 +2,7 @@ "name": "modules/sirsoft-board", "description": "Board module for Gnuboard7", "type": "library", - "version": "1.1.1", + "version": "1.1.2", "license": "MIT", "autoload": { "psr-4": { diff --git a/modules/_bundled/sirsoft-board/composer.lock b/modules/_bundled/sirsoft-board/composer.lock index 817a71f8..201bd324 100644 --- a/modules/_bundled/sirsoft-board/composer.lock +++ b/modules/_bundled/sirsoft-board/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "f3ccc886d4904110ff4788fd5bc9e15d", + "content-hash": "dd055a1a5b6c59b9e30d2eaeb74b3416", "packages": [], "packages-dev": [], "aliases": [], @@ -16,5 +16,5 @@ "php": "^8.2" }, "platform-dev": {}, - "plugin-api-version": "2.6.0" + "plugin-api-version": "2.9.0" } diff --git a/modules/_bundled/sirsoft-board/module.json b/modules/_bundled/sirsoft-board/module.json index 2385af7c..54449f3a 100644 --- a/modules/_bundled/sirsoft-board/module.json +++ b/modules/_bundled/sirsoft-board/module.json @@ -5,7 +5,7 @@ "ko": "게시판", "en": "Board" }, - "version": "1.1.1", + "version": "1.1.2", "license": "MIT", "description": { "ko": "게시판 관리를 위한 모듈", diff --git a/modules/_bundled/sirsoft-board/package-lock.json b/modules/_bundled/sirsoft-board/package-lock.json index fa177ce9..53b07fe9 100644 --- a/modules/_bundled/sirsoft-board/package-lock.json +++ b/modules/_bundled/sirsoft-board/package-lock.json @@ -1,12 +1,12 @@ { "name": "@g7/sirsoft-board", - "version": "1.1.1", + "version": "1.1.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@g7/sirsoft-board", - "version": "1.1.1", + "version": "1.1.2", "devDependencies": { "jsdom": "^27.4.0", "typescript": "^5.3.3", diff --git a/modules/_bundled/sirsoft-board/package.json b/modules/_bundled/sirsoft-board/package.json index 3ac608c6..da338bba 100644 --- a/modules/_bundled/sirsoft-board/package.json +++ b/modules/_bundled/sirsoft-board/package.json @@ -1,6 +1,6 @@ { "name": "@g7/sirsoft-board", - "version": "1.1.1", + "version": "1.1.2", "description": "그누보드7 게시판 모듈 프론트엔드 에셋", "private": true, "type": "module", diff --git a/modules/_bundled/sirsoft-board/src/Services/AttachmentService.php b/modules/_bundled/sirsoft-board/src/Services/AttachmentService.php index 9e335886..5a816d90 100644 --- a/modules/_bundled/sirsoft-board/src/Services/AttachmentService.php +++ b/modules/_bundled/sirsoft-board/src/Services/AttachmentService.php @@ -792,6 +792,10 @@ class AttachmentService /** * 첨부파일 URL 조회 * + * 게시판 첨부는 비밀글·삭제글 게이트가 걸린 서빙 경로를 통해서만 내보낸다. + * 직접 URL(CDN)로 바꾸면 그 게이트가 통째로 우회되므로, 게이트가 살아 있는 + * 다운로드 서빙 URL 을 돌려준다. + * * @param string $slug 게시판 식별자 * @param int $id 첨부파일 ID * @return string|null 파일 URL 또는 없을 경우 null @@ -800,11 +804,7 @@ class AttachmentService { $attachment = $this->repository->findById($slug, $id); - if (! $attachment) { - return null; - } - - return $this->storage->url('attachments', $attachment->path); + return $attachment?->download_url; } /** diff --git a/modules/_bundled/sirsoft-board/tests/Unit/AttachmentServiceTest.php b/modules/_bundled/sirsoft-board/tests/Unit/AttachmentServiceTest.php index 89d4fa12..3cb320b6 100644 --- a/modules/_bundled/sirsoft-board/tests/Unit/AttachmentServiceTest.php +++ b/modules/_bundled/sirsoft-board/tests/Unit/AttachmentServiceTest.php @@ -823,29 +823,54 @@ class AttachmentServiceTest extends ModuleTestCase HookManager::clearAction('sirsoft-board.attachment.after_download'); } + /** + * 업로드 응답의 url 칸이 게이트가 살아 있는 서빙 URL 이어야 합니다. + * + * 이전에는 비공개 디스크에서 항상 null 이 나가 응답의 url 칸이 늘 비어 있었다. + * 게시판 첨부는 비밀글·삭제글 게이트가 걸려 있으므로 직접 URL 로 바꾸지 않고, + * 그 게이트를 통과하는 다운로드 서빙 URL 로 채운다. + * + * @effects board_upload_response_url_uses_gated_route, download_url_falls_back_to_api_path_when_direct_unavailable + */ #[Test] - public function test_get_url_returns_url(): void + public function test_get_url_returns_gated_serving_url(): void { // Arrange $attachment = new Attachment([ - 'id' => 1, 'path' => 'notice/2025/01/21/test.jpg', ]); + $attachment->hash = 'abc123def456'; + $attachment->setRelation('board', new Board(['slug' => 'notice'])); $this->repository->shouldReceive('findById') ->once() ->with('notice', 1) ->andReturn($attachment); - $this->storage->shouldReceive('url') - ->once() - ->with('attachments', 'notice/2025/01/21/test.jpg') - ->andReturn('https://example.com/storage/modules/sirsoft-board/attachments/notice/2025/01/21/test.jpg'); + // 직접 URL 은 시도조차 하지 않는다 (게이트 우회 차단) + $this->storage->shouldNotReceive('url'); // Act $result = $this->service->getUrl('notice', 1); // Assert - $this->assertEquals('https://example.com/storage/modules/sirsoft-board/attachments/notice/2025/01/21/test.jpg', $result); + $this->assertSame( + '/api/modules/sirsoft-board/boards/notice/attachment/abc123def456', + $result + ); + } + + /** + * 첨부가 없으면 null 이어야 합니다 (기존 계약 유지). + */ + #[Test] + public function test_get_url_returns_null_when_attachment_missing(): void + { + $this->repository->shouldReceive('findById') + ->once() + ->with('notice', 99) + ->andReturnNull(); + + $this->assertNull($this->service->getUrl('notice', 99)); } } diff --git a/modules/_bundled/sirsoft-ecommerce/CHANGELOG.md b/modules/_bundled/sirsoft-ecommerce/CHANGELOG.md index 050eb456..aadbbb21 100644 --- a/modules/_bundled/sirsoft-ecommerce/CHANGELOG.md +++ b/modules/_bundled/sirsoft-ecommerce/CHANGELOG.md @@ -10,11 +10,16 @@ - 상품 상세에서 옵션을 고른 뒤 「바로 구매」·「장바구니 담기」가 동작하지 않던 문제가 코어 7.0.10 과 함께 해소됩니다. 화면에는 고른 옵션이 목록에 담긴 것으로 보이는데 실제 요청에는 아무 옵션도 실리지 않아, 「바로 구매」는 오류로 끝나고 「장바구니 담기」는 「옵션을 선택해주세요」 안내만 반복됐습니다. 원인은 코어 화면 엔진에 있었으며 이 모듈의 코드는 바뀌지 않았습니다. - `sudo` 로 코어를 업데이트할 때 업그레이드 단계가 저장하는 설정 파일이 root 소유로 남을 수 있던 문제를 수정했습니다. 저장 뒤 폴더 소유자와 같게 맞춥니다. +- 장바구니에서 수량을 바꾸면 배송 불가 안내와 주문 차단이 풀리던 문제를 수정했습니다. 선택한 배송 국가로 보낼 수 없는 상품이 담겨 있으면 주문이 막혀야 하는데, 수량을 한 번만 조정해도 그 잠금이 사라져 그대로 주문할 수 있었습니다. 수량 변경 응답이 장바구니 조회 응답보다 적은 정보를 담고 있었던 것이며, 이제 두 응답이 같은 내용을 돌려줍니다. +- 비회원 주문의 결제 취소 기록이 주문번호만으로 실행되던 문제를 수정했습니다. 종전에는 로그인하지 않은 누구든 주문번호만 알면 다른 사람의 비회원 주문을 결제 취소 상태로 바꿀 수 있었습니다. 이제 비회원 주문 조회에 쓰는 것과 같은 본인 확인 절차를 거친 경우에만 처리되며, 그 밖의 요청은 주문을 찾을 수 없다는 응답으로 차단됩니다. 회원 주문은 종전과 같이 본인만 가능합니다. 비회원이 결제창에서 결제를 취소한 경우에는 주문 직후 발급된 본인 확인 정보로 종전처럼 취소 이력이 남습니다. (KISA 측에서 제보해주셨습니다 — KVE-2026-2041) +- 상세설명을 편집기(HTML)로 작성한 상품을 등록하거나 수정할 때 저장이 실패하던 문제를 수정했습니다. 상품 설명의 보안 정화에 쓰는 구성요소가 모듈 설치 폴더 안에 자기 캐시 파일을 만들려 했기 때문에, 보안상 모듈 폴더에 쓰기를 막아 둔 서버에서는 저장이 항상 오류로 끝났고 다시 시도해도 같은 결과였습니다. 이제 이 캐시는 `storage` 폴더 아래에 만들어지며, 그 위치마저 쓸 수 없는 경우에는 캐시 없이 정화만 수행해 저장이 실패하지 않습니다(설명은 종전과 똑같이 정화됩니다). (#125 @lyg-kaban 님께서 제보해주셨습니다.) ### Changed - 입금 기한 만료 주문 자동취소가 카드 등 결제창 결제까지 대상에 포함합니다. 종전에는 무통장입금·가상계좌만 정리되어, 결제창까지 갔으나 결제가 끝나지 않은 주문은 정리하는 주체가 없어 계속 남았습니다. 이제 주문 후 24시간이 지나면 함께 취소되며 미리 차감된 마일리지도 돌아옵니다. 결제가 이미 완료된 주문과 입금 대기 중인 가상계좌 주문은 대상이 아니며, 주문설정의 「만료 주문 자동취소」를 끄면 종전처럼 동작합니다. - 확장 문서(README · AGENTS.md · docs/README.md)의 제목에 「그누보드7」과 확장 유형을 함께 표기했습니다. 제목만 보고도 그누보드7의 어떤 종류 확장인지 알 수 있습니다. +- 코어 최소 요구 버전을 7.0.10 으로 상향 +- Composer 잠금 파일을 확장 정보와 다시 맞추고, Composer 를 실행할 수 없는 환경을 위한 vendor 번들을 같은 구성으로 다시 만들었습니다. 포함된 패키지 버전은 바뀌지 않았습니다. ### Added @@ -23,16 +28,6 @@ - 문서의 제품 표기를 「그누보드7」로 통일했습니다. - 환경설정 > 주문 설정 > 주문 자동취소 에 「결제 미완료 주문 만료 기준(분)」 입력이 추가되었습니다. 결제창까지 갔으나 결제가 끝나지 않은 주문을 몇 분 뒤에 정리할지 정하며, 0 으로 두면 그 부류는 정리하지 않습니다(기본 1440분 = 24시간). 종전에는 값만 있고 화면에서 조작할 수 없었습니다. -### Fixed - -- 장바구니에서 수량을 바꾸면 배송 불가 안내와 주문 차단이 풀리던 문제를 수정했습니다. 선택한 배송 국가로 보낼 수 없는 상품이 담겨 있으면 주문이 막혀야 하는데, 수량을 한 번만 조정해도 그 잠금이 사라져 그대로 주문할 수 있었습니다. 수량 변경 응답이 장바구니 조회 응답보다 적은 정보를 담고 있었던 것이며, 이제 두 응답이 같은 내용을 돌려줍니다. -- 비회원 주문의 결제 취소 기록이 주문번호만으로 실행되던 문제를 수정했습니다. 종전에는 로그인하지 않은 누구든 주문번호만 알면 다른 사람의 비회원 주문을 결제 취소 상태로 바꿀 수 있었습니다. 이제 비회원 주문 조회에 쓰는 것과 같은 본인 확인 절차를 거친 경우에만 처리되며, 그 밖의 요청은 주문을 찾을 수 없다는 응답으로 차단됩니다. 회원 주문은 종전과 같이 본인만 가능합니다. 비회원이 결제창에서 결제를 취소한 경우에는 주문 직후 발급된 본인 확인 정보로 종전처럼 취소 이력이 남습니다. (KISA 측에서 제보해주셨습니다 — KVE-2026-2041) -- 상세설명을 편집기(HTML)로 작성한 상품을 등록하거나 수정할 때 저장이 실패하던 문제를 수정했습니다. 상품 설명의 보안 정화에 쓰는 구성요소가 모듈 설치 폴더 안에 자기 캐시 파일을 만들려 했기 때문에, 보안상 모듈 폴더에 쓰기를 막아 둔 서버에서는 저장이 항상 오류로 끝났고 다시 시도해도 같은 결과였습니다. 이제 이 캐시는 `storage` 폴더 아래에 만들어지며, 그 위치마저 쓸 수 없는 경우에는 캐시 없이 정화만 수행해 저장이 실패하지 않습니다(설명은 종전과 똑같이 정화됩니다). (#125 @lyg-kaban 님께서 제보해주셨습니다.) - -### Changed - -- 코어 최소 요구 버전을 7.0.10 으로 상향 - ## [1.2.0] - 2026-08-24 ### Added diff --git a/modules/_bundled/sirsoft-ecommerce/composer.lock b/modules/_bundled/sirsoft-ecommerce/composer.lock index 0e444193..63510c8c 100644 --- a/modules/_bundled/sirsoft-ecommerce/composer.lock +++ b/modules/_bundled/sirsoft-ecommerce/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "226f06cb690d9b15f95314738c32f711", + "content-hash": "b0e08190b712afcc602a24f54098f5a8", "packages": [ { "name": "ezyang/htmlpurifier", @@ -78,5 +78,5 @@ "php": "^8.2" }, "platform-dev": {}, - "plugin-api-version": "2.6.0" + "plugin-api-version": "2.9.0" } diff --git a/modules/_bundled/sirsoft-ecommerce/docs/data-model.md b/modules/_bundled/sirsoft-ecommerce/docs/data-model.md index 09a5c71c..e79551d2 100644 --- a/modules/_bundled/sirsoft-ecommerce/docs/data-model.md +++ b/modules/_bundled/sirsoft-ecommerce/docs/data-model.md @@ -71,6 +71,13 @@ 관계없이 같은 방식으로 자산 URL 을 내도록 묶습니다. 표에 모델처럼 잡힌 것은 수집기가 클래스 파일 단위로 세기 때문이며, 테이블이 `(규약)` 인 것이 그 표식입니다. +이 규약은 **권한 검사가 걸린 자산에는 배선하지 않는 것이 원칙**입니다 — 직접 URL 은 서버 +스트리밍 경로의 검사를 우회합니다. 예외가 하나 있습니다: `ProductReviewImage` 는 이 규약을 +씁니다. 리뷰 노출 상태는 리소스 층에서 걸러지므로 숨김 리뷰의 이미지는 응답에 실리지 +않지만, 공개 자산 디스크를 켠 상태에서 **이미 발급된 직접 URL 을 아는 쪽은 노출→숨김 전환 +이후에도 그 파일에 접근할 수 있습니다.** 노출 상태를 접근 차단 수단으로 써야 하는 운용이라면 +그 카테고리에 공개 자산 디스크를 적용하지 않아야 합니다. + **`HasUserOverrides` 를 쓰는 셋**(`ClaimReason` · `ShippingCarrier` · `ShippingType`)은 시더가 기본값을 넣지만 운영자가 고칠 수 있는 테이블입니다. 시더를 다시 돌려도 운영자 수정분은 보존되므로, 이 셋의 기본 데이터를 바꿀 때는 시더만 고쳐서는 기설치본에 반영되지 않습니다. diff --git a/modules/_bundled/sirsoft-ecommerce/src/Models/Concerns/HasDirectAssetUrl.php b/modules/_bundled/sirsoft-ecommerce/src/Models/Concerns/HasDirectAssetUrl.php index 4e3a06f8..8d7e6bbf 100644 --- a/modules/_bundled/sirsoft-ecommerce/src/Models/Concerns/HasDirectAssetUrl.php +++ b/modules/_bundled/sirsoft-ecommerce/src/Models/Concerns/HasDirectAssetUrl.php @@ -16,6 +16,13 @@ use Modules\Sirsoft\Ecommerce\Support\AssetStorage; * 이미지 외의 완전 공개 자산 모델도 재사용할 수 있습니다. 단, 권한 검사가 걸린 * 첨부파일(비밀글/회원 전용 게시판 등)에는 배선하지 않습니다 — 직접 URL 은 * 서버 스트리밍 경로의 권한 검사를 우회합니다. + * + * 알려진 예외와 그 한계 — `ProductReviewImage` 는 이 trait 을 씁니다. 리뷰 노출 상태는 + * 리소스 층에서 걸러지므로 HIDDEN 리뷰의 이미지는 응답에 실리지 않지만, 공개 자산 + * 디스크를 켠 상태에서 **이미 발급된 직접 URL 을 알고 있는 쪽은 VISIBLE→HIDDEN 전환 + * 이후에도 그 파일에 접근할 수 있습니다.** 직접 URL 은 저장소의 파일을 직접 가리키므로 + * 응답에서 감추는 것으로는 회수되지 않습니다. 노출 상태를 접근 차단 수단으로 써야 하는 + * 운용이라면 그 카테고리에 공개 자산 디스크를 적용하지 않아야 합니다. */ trait HasDirectAssetUrl { diff --git a/modules/_bundled/sirsoft-ecommerce/vendor-bundle.json b/modules/_bundled/sirsoft-ecommerce/vendor-bundle.json index 711fcd44..7d9c6db2 100644 --- a/modules/_bundled/sirsoft-ecommerce/vendor-bundle.json +++ b/modules/_bundled/sirsoft-ecommerce/vendor-bundle.json @@ -1,15 +1,15 @@ { "schema_version": "1.0", - "generated_at": "2026-08-28T01:27:59+00:00", + "generated_at": "2026-09-08T10:13:14+00:00", "generator": "g7 vendor-bundle:build", "target": "module:sirsoft-ecommerce", "composer_json_sha256": "4f224863e21777f5ef474a7fcfb7d5f66132551b4c58220a660bd3acb573dfdb", - "composer_lock_sha256": "876ca9c2273a33baff878d25050a567018a946412db053930a7f548c4add595d", - "zip_sha256": "5f954f1597fc2a39bb601af441e3af3768e303b835c9047f0ade71cb56aec66c", - "zip_size": 435549, + "composer_lock_sha256": "66ab3fbbe582562f86af06406f45164a2e3aa6790f5136dce9fd4f73944126a3", + "zip_sha256": "713f98578a866fada6782d11f8c80ff13037f1faf0edaaa55f004ddc4e80d1b9", + "zip_size": 435546, "package_count": 1, "php_requirement": "^8.2", - "g7_version": "7.0.10", + "g7_version": "7.0.11", "packages": [ { "name": "ezyang/htmlpurifier", diff --git a/modules/_bundled/sirsoft-ecommerce/vendor-bundle.zip b/modules/_bundled/sirsoft-ecommerce/vendor-bundle.zip index a933076c..3305e4f1 100644 Binary files a/modules/_bundled/sirsoft-ecommerce/vendor-bundle.zip and b/modules/_bundled/sirsoft-ecommerce/vendor-bundle.zip differ diff --git a/modules/_bundled/sirsoft-page/CHANGELOG.md b/modules/_bundled/sirsoft-page/CHANGELOG.md index e2f12ec8..c6622855 100644 --- a/modules/_bundled/sirsoft-page/CHANGELOG.md +++ b/modules/_bundled/sirsoft-page/CHANGELOG.md @@ -4,6 +4,16 @@ 형식은 [Keep a Changelog](https://keepachangelog.com/ko/1.1.0/)를 따르며, [Semantic Versioning](https://semver.org/lang/ko/)을 준수합니다. +## [1.1.2] - 2026-09-09 + +### Changed + +- Composer 잠금 파일을 최신 Composer 형식으로 갱신했습니다. + +### Removed + +- 사용되지 않던 첨부파일 주소 조회 기능을 제거했습니다. 화면·API 어디에서도 호출되지 않았고 항상 빈 값을 돌려주던 기능이라 실제 동작 변화는 없습니다. + ## [1.1.1] - 2026-09-06 ### Added diff --git a/modules/_bundled/sirsoft-page/README.md b/modules/_bundled/sirsoft-page/README.md index 7ccbed3e..8fb3e05c 100644 --- a/modules/_bundled/sirsoft-page/README.md +++ b/modules/_bundled/sirsoft-page/README.md @@ -5,7 +5,7 @@

- version 1.1.1 + version 1.1.2 type 모듈 그누보드7 >=7.0.10 license MIT diff --git a/modules/_bundled/sirsoft-page/composer.json b/modules/_bundled/sirsoft-page/composer.json index 133f79e8..df6069c8 100644 --- a/modules/_bundled/sirsoft-page/composer.json +++ b/modules/_bundled/sirsoft-page/composer.json @@ -2,7 +2,7 @@ "name": "modules/sirsoft-page", "description": "Page module for Gnuboard7", "type": "library", - "version": "1.1.1", + "version": "1.1.2", "license": "MIT", "autoload": { "psr-4": { diff --git a/modules/_bundled/sirsoft-page/composer.lock b/modules/_bundled/sirsoft-page/composer.lock index b54bdee8..b72f9432 100644 --- a/modules/_bundled/sirsoft-page/composer.lock +++ b/modules/_bundled/sirsoft-page/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "b5b9d14252427e57c4bc8c36e891c5de", + "content-hash": "024fea001a7af7fa7570756507a49ec2", "packages": [], "packages-dev": [], "aliases": [], @@ -16,5 +16,5 @@ "php": "^8.2" }, "platform-dev": {}, - "plugin-api-version": "2.6.0" + "plugin-api-version": "2.9.0" } diff --git a/modules/_bundled/sirsoft-page/module.json b/modules/_bundled/sirsoft-page/module.json index d5c43683..5b828dad 100644 --- a/modules/_bundled/sirsoft-page/module.json +++ b/modules/_bundled/sirsoft-page/module.json @@ -5,7 +5,7 @@ "ko": "페이지", "en": "Page" }, - "version": "1.1.1", + "version": "1.1.2", "license": "MIT", "description": { "ko": "정적 페이지(정보/정책/안내) 관리 모듈", diff --git a/modules/_bundled/sirsoft-page/package-lock.json b/modules/_bundled/sirsoft-page/package-lock.json index e20deeb6..115fdf16 100644 --- a/modules/_bundled/sirsoft-page/package-lock.json +++ b/modules/_bundled/sirsoft-page/package-lock.json @@ -1,12 +1,12 @@ { "name": "@g7/sirsoft-page", - "version": "1.1.1", + "version": "1.1.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@g7/sirsoft-page", - "version": "1.1.1", + "version": "1.1.2", "devDependencies": { "jsdom": "^27.4.0", "typescript": "^5.3.3", diff --git a/modules/_bundled/sirsoft-page/package.json b/modules/_bundled/sirsoft-page/package.json index 3047c5fa..617a3609 100644 --- a/modules/_bundled/sirsoft-page/package.json +++ b/modules/_bundled/sirsoft-page/package.json @@ -1,6 +1,6 @@ { "name": "@g7/sirsoft-page", - "version": "1.1.1", + "version": "1.1.2", "description": "그누보드7 페이지 모듈 프론트엔드 에셋", "private": true, "type": "module", diff --git a/modules/_bundled/sirsoft-page/src/Services/PageAttachmentService.php b/modules/_bundled/sirsoft-page/src/Services/PageAttachmentService.php index c9cbd4f8..48edeb03 100644 --- a/modules/_bundled/sirsoft-page/src/Services/PageAttachmentService.php +++ b/modules/_bundled/sirsoft-page/src/Services/PageAttachmentService.php @@ -470,17 +470,6 @@ class PageAttachmentService ); } - /** - * 파일 URL을 반환합니다. - * - * @param PageAttachment $attachment 첨부파일 모델 - * @return string|null 파일 URL 또는 null - */ - public function getUrl(PageAttachment $attachment): ?string - { - return $this->storage->url('attachments', $attachment->path); - } - /** * 첨부파일 삭제 권한을 확인합니다. * diff --git a/plugins/_bundled/gnuboard7-hello_plugin/CHANGELOG.md b/plugins/_bundled/gnuboard7-hello_plugin/CHANGELOG.md index fbfba113..a5dd14c7 100644 --- a/plugins/_bundled/gnuboard7-hello_plugin/CHANGELOG.md +++ b/plugins/_bundled/gnuboard7-hello_plugin/CHANGELOG.md @@ -15,6 +15,7 @@ ### Changed - 확장 문서(README · AGENTS.md · docs/README.md)의 제목에 「그누보드7」과 확장 유형을 함께 표기했습니다. 제목만 보고도 그누보드7의 어떤 종류 확장인지 알 수 있습니다. +- Composer 잠금 파일을 확장 정보와 다시 맞췄습니다. 설치·업데이트 과정에서 잠금 파일이 오래되었다는 경고가 나오지 않습니다. ## [0.1.1] - 2026-08-17 diff --git a/plugins/_bundled/gnuboard7-hello_plugin/composer.lock b/plugins/_bundled/gnuboard7-hello_plugin/composer.lock index d1264f95..92c5a061 100644 --- a/plugins/_bundled/gnuboard7-hello_plugin/composer.lock +++ b/plugins/_bundled/gnuboard7-hello_plugin/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "cb11c22109cd1b70301cfffe7f82b973", + "content-hash": "dc78377c56e6cbc3a436d27fcfe9e258", "packages": [], "packages-dev": [], "aliases": [], @@ -16,5 +16,5 @@ "php": "^8.2" }, "platform-dev": {}, - "plugin-api-version": "2.6.0" + "plugin-api-version": "2.9.0" } diff --git a/plugins/_bundled/sirsoft-ckeditor5/CHANGELOG.md b/plugins/_bundled/sirsoft-ckeditor5/CHANGELOG.md index 78e383ea..23a548d7 100644 --- a/plugins/_bundled/sirsoft-ckeditor5/CHANGELOG.md +++ b/plugins/_bundled/sirsoft-ckeditor5/CHANGELOG.md @@ -25,6 +25,7 @@ ### Changed - 확장 문서(README · AGENTS.md · docs/README.md)의 제목에 「그누보드7」과 확장 유형을 함께 표기했습니다. 제목만 보고도 그누보드7의 어떤 종류 확장인지 알 수 있습니다. +- Composer 잠금 파일을 확장 정보와 다시 맞췄습니다. 설치·업데이트 과정에서 잠금 파일이 오래되었다는 경고가 나오지 않습니다. ## [1.0.2] - 2026-08-19 diff --git a/plugins/_bundled/sirsoft-ckeditor5/composer.lock b/plugins/_bundled/sirsoft-ckeditor5/composer.lock index d849a1f2..39250ca7 100644 --- a/plugins/_bundled/sirsoft-ckeditor5/composer.lock +++ b/plugins/_bundled/sirsoft-ckeditor5/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "dd62f80e956b1dd28c3f4f4e94c48774", + "content-hash": "0f4b4f5edf9a00794889ba89418f8b5d", "packages": [], "packages-dev": [], "aliases": [], @@ -16,5 +16,5 @@ "php": "^8.2" }, "platform-dev": {}, - "plugin-api-version": "2.6.0" + "plugin-api-version": "2.9.0" } diff --git a/plugins/_bundled/sirsoft-daum_postcode/CHANGELOG.md b/plugins/_bundled/sirsoft-daum_postcode/CHANGELOG.md index 49e18dd8..ed0def57 100644 --- a/plugins/_bundled/sirsoft-daum_postcode/CHANGELOG.md +++ b/plugins/_bundled/sirsoft-daum_postcode/CHANGELOG.md @@ -20,6 +20,7 @@ ### Changed - 확장 문서(README · AGENTS.md · docs/README.md)의 제목에 「그누보드7」과 확장 유형을 함께 표기했습니다. 제목만 보고도 그누보드7의 어떤 종류 확장인지 알 수 있습니다. +- Composer 잠금 파일을 확장 정보와 다시 맞췄습니다. 설치·업데이트 과정에서 잠금 파일이 오래되었다는 경고가 나오지 않습니다. ## [1.0.2] - 2026-08-19 diff --git a/plugins/_bundled/sirsoft-daum_postcode/composer.lock b/plugins/_bundled/sirsoft-daum_postcode/composer.lock index 916bb328..44a39797 100644 --- a/plugins/_bundled/sirsoft-daum_postcode/composer.lock +++ b/plugins/_bundled/sirsoft-daum_postcode/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "2cf3389f6c00f3e542e3f7f4e7d556bc", + "content-hash": "1a8ad4ef4e86f6dca9d3c8f143ee89b2", "packages": [], "packages-dev": [], "aliases": [], @@ -16,5 +16,5 @@ "php": "^8.2" }, "platform-dev": {}, - "plugin-api-version": "2.6.0" + "plugin-api-version": "2.9.0" } diff --git a/plugins/_bundled/sirsoft-gdpr/CHANGELOG.md b/plugins/_bundled/sirsoft-gdpr/CHANGELOG.md index eaaecda0..808a9cf9 100644 --- a/plugins/_bundled/sirsoft-gdpr/CHANGELOG.md +++ b/plugins/_bundled/sirsoft-gdpr/CHANGELOG.md @@ -32,6 +32,7 @@ ### Changed - 확장 문서(README · AGENTS.md · docs/README.md)의 제목에 「그누보드7」과 확장 유형을 함께 표기했습니다. 제목만 보고도 그누보드7의 어떤 종류 확장인지 알 수 있습니다. +- Composer 잠금 파일을 확장 정보와 다시 맞췄습니다. 설치·업데이트 과정에서 잠금 파일이 오래되었다는 경고가 나오지 않습니다. ## [1.0.3] - 2026-08-19 diff --git a/plugins/_bundled/sirsoft-gdpr/composer.lock b/plugins/_bundled/sirsoft-gdpr/composer.lock index 58d078a6..ef3dcde2 100644 --- a/plugins/_bundled/sirsoft-gdpr/composer.lock +++ b/plugins/_bundled/sirsoft-gdpr/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "ed69cfb7d799ccb19ce6420c9d3bc5b9", + "content-hash": "3327277d126e144e258ef68cf5e4352e", "packages": [], "packages-dev": [], "aliases": [], @@ -17,5 +17,5 @@ "ext-json": "*" }, "platform-dev": {}, - "plugin-api-version": "2.6.0" + "plugin-api-version": "2.9.0" } diff --git a/plugins/_bundled/sirsoft-gdpr/tests/Playwright/specs/admin/necessary-allowlist-settings.spec.ts b/plugins/_bundled/sirsoft-gdpr/tests/Playwright/specs/admin/necessary-allowlist-settings.spec.ts index d7b8e0eb..e7916e76 100644 --- a/plugins/_bundled/sirsoft-gdpr/tests/Playwright/specs/admin/necessary-allowlist-settings.spec.ts +++ b/plugins/_bundled/sirsoft-gdpr/tests/Playwright/specs/admin/necessary-allowlist-settings.spec.ts @@ -18,7 +18,7 @@ * 3. 저장된 목록이 인라인 페이로드에 실려 브라우저에 도달한다 (+ 잠금 집합 동반) * 4. 형식 위반 항목은 422 로 거부되고 그 카드에 에러가 붙는다 * - * 종료 시 추가한 항목을 되돌린다 — PO 와 브라우저를 공유하므로 원상 복구는 의무다. + * 종료 시 추가한 항목을 되돌린다 — 사람이 쓰는 브라우저를 공유하므로 원상 복구는 의무다. */ import { test, expect, authenticatePage } from '../../fixtures/gdpr-auth'; import type { Page } from '@playwright/test'; diff --git a/plugins/_bundled/sirsoft-marketing/CHANGELOG.md b/plugins/_bundled/sirsoft-marketing/CHANGELOG.md index adf85f08..5332572c 100644 --- a/plugins/_bundled/sirsoft-marketing/CHANGELOG.md +++ b/plugins/_bundled/sirsoft-marketing/CHANGELOG.md @@ -15,6 +15,7 @@ ### Changed - 확장 문서(README · AGENTS.md · docs/README.md)의 제목에 「그누보드7」과 확장 유형을 함께 표기했습니다. 제목만 보고도 그누보드7의 어떤 종류 확장인지 알 수 있습니다. +- Composer 잠금 파일을 확장 정보와 다시 맞췄습니다. 설치·업데이트 과정에서 잠금 파일이 오래되었다는 경고가 나오지 않습니다. ## [1.0.3] - 2026-08-22 diff --git a/plugins/_bundled/sirsoft-marketing/composer.lock b/plugins/_bundled/sirsoft-marketing/composer.lock index b1122f5c..505f9e2c 100644 --- a/plugins/_bundled/sirsoft-marketing/composer.lock +++ b/plugins/_bundled/sirsoft-marketing/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "b2b87b2f71ae1ead87a7304067243689", + "content-hash": "c90b712b291a2f1b249b8525880f280f", "packages": [], "packages-dev": [], "aliases": [], @@ -16,5 +16,5 @@ "php": "^8.2" }, "platform-dev": {}, - "plugin-api-version": "2.6.0" + "plugin-api-version": "2.9.0" } diff --git a/plugins/_bundled/sirsoft-message_bizppurio/CHANGELOG.md b/plugins/_bundled/sirsoft-message_bizppurio/CHANGELOG.md index aa54eab1..b3666d6f 100644 --- a/plugins/_bundled/sirsoft-message_bizppurio/CHANGELOG.md +++ b/plugins/_bundled/sirsoft-message_bizppurio/CHANGELOG.md @@ -15,6 +15,7 @@ ### Changed - 확장 문서(README · AGENTS.md · docs/README.md)의 제목에 「그누보드7」과 확장 유형을 함께 표기했습니다. 제목만 보고도 그누보드7의 어떤 종류 확장인지 알 수 있습니다. +- Composer 잠금 파일을 새로 추가했습니다. 다른 번들 확장과 같은 방식으로 의존성 상태가 고정됩니다. ## [1.0.0] - 2026-08-24 diff --git a/plugins/_bundled/sirsoft-message_bizppurio/composer.lock b/plugins/_bundled/sirsoft-message_bizppurio/composer.lock new file mode 100644 index 00000000..9b4f15eb --- /dev/null +++ b/plugins/_bundled/sirsoft-message_bizppurio/composer.lock @@ -0,0 +1,21 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "103d4ddd41142225a88cfbadee4aca1f", + "packages": [], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": {}, + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": "^8.2", + "ext-json": "*" + }, + "platform-dev": {}, + "plugin-api-version": "2.9.0" +} diff --git a/plugins/_bundled/sirsoft-pay_kginicis/CHANGELOG.md b/plugins/_bundled/sirsoft-pay_kginicis/CHANGELOG.md index b90f08c8..feb7be2c 100644 --- a/plugins/_bundled/sirsoft-pay_kginicis/CHANGELOG.md +++ b/plugins/_bundled/sirsoft-pay_kginicis/CHANGELOG.md @@ -21,6 +21,7 @@ ### Changed - 확장 문서(README · AGENTS.md · docs/README.md)의 제목에 「그누보드7」과 확장 유형을 함께 표기했습니다. 제목만 보고도 그누보드7의 어떤 종류 확장인지 알 수 있습니다. +- Composer 잠금 파일을 확장 정보와 다시 맞췄습니다. 설치·업데이트 과정에서 잠금 파일이 오래되었다는 경고가 나오지 않습니다. ### Fixed diff --git a/plugins/_bundled/sirsoft-pay_kginicis/composer.lock b/plugins/_bundled/sirsoft-pay_kginicis/composer.lock index 02be1cb8..fc4da071 100644 --- a/plugins/_bundled/sirsoft-pay_kginicis/composer.lock +++ b/plugins/_bundled/sirsoft-pay_kginicis/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "33f95805cb42a29997a78d260d49592d", + "content-hash": "90bb21c817fbec07b681b62e4127c40d", "packages": [], "packages-dev": [], "aliases": [], @@ -16,5 +16,5 @@ "php": "^8.2" }, "platform-dev": {}, - "plugin-api-version": "2.6.0" + "plugin-api-version": "2.9.0" } diff --git a/plugins/_bundled/sirsoft-pay_nhnkcp/CHANGELOG.md b/plugins/_bundled/sirsoft-pay_nhnkcp/CHANGELOG.md index f21aa0ac..b87ee493 100644 --- a/plugins/_bundled/sirsoft-pay_nhnkcp/CHANGELOG.md +++ b/plugins/_bundled/sirsoft-pay_nhnkcp/CHANGELOG.md @@ -28,6 +28,7 @@ ### Changed - 확장 문서(README · AGENTS.md · docs/README.md)의 제목에 「그누보드7」과 확장 유형을 함께 표기했습니다. 제목만 보고도 그누보드7의 어떤 종류 확장인지 알 수 있습니다. +- Composer 잠금 파일을 확장 정보와 다시 맞췄습니다. 설치·업데이트 과정에서 잠금 파일이 오래되었다는 경고가 나오지 않습니다. ## [1.0.3] - 2026-08-22 diff --git a/plugins/_bundled/sirsoft-pay_nhnkcp/composer.lock b/plugins/_bundled/sirsoft-pay_nhnkcp/composer.lock index 8ba2f579..1b1e6be2 100644 --- a/plugins/_bundled/sirsoft-pay_nhnkcp/composer.lock +++ b/plugins/_bundled/sirsoft-pay_nhnkcp/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "88988d859f2433db8b07b8700f481c2d", + "content-hash": "40ed20fff4e899e55521bf44236ee61a", "packages": [], "packages-dev": [], "aliases": [], @@ -16,5 +16,5 @@ "php": "^8.2" }, "platform-dev": {}, - "plugin-api-version": "2.6.0" + "plugin-api-version": "2.9.0" } diff --git a/plugins/_bundled/sirsoft-pay_nicepayments/CHANGELOG.md b/plugins/_bundled/sirsoft-pay_nicepayments/CHANGELOG.md index 5f629c6d..f545e360 100644 --- a/plugins/_bundled/sirsoft-pay_nicepayments/CHANGELOG.md +++ b/plugins/_bundled/sirsoft-pay_nicepayments/CHANGELOG.md @@ -25,6 +25,7 @@ ### Changed - 확장 문서(README · AGENTS.md · docs/README.md)의 제목에 「그누보드7」과 확장 유형을 함께 표기했습니다. 제목만 보고도 그누보드7의 어떤 종류 확장인지 알 수 있습니다. +- Composer 잠금 파일을 확장 정보와 다시 맞췄습니다. 설치·업데이트 과정에서 잠금 파일이 오래되었다는 경고가 나오지 않습니다. ## [1.0.2] - 2026-08-19 diff --git a/plugins/_bundled/sirsoft-pay_nicepayments/composer.lock b/plugins/_bundled/sirsoft-pay_nicepayments/composer.lock index c29a16db..1354b3e2 100644 --- a/plugins/_bundled/sirsoft-pay_nicepayments/composer.lock +++ b/plugins/_bundled/sirsoft-pay_nicepayments/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "1fce8e0d8388bbc1df8b2d62fdc98b7e", + "content-hash": "da2660ccc648f031a25e299592f5fb55", "packages": [], "packages-dev": [], "aliases": [], @@ -16,5 +16,5 @@ "php": "^8.2" }, "platform-dev": {}, - "plugin-api-version": "2.6.0" + "plugin-api-version": "2.9.0" } diff --git a/plugins/_bundled/sirsoft-tosspayments/CHANGELOG.md b/plugins/_bundled/sirsoft-tosspayments/CHANGELOG.md index dcb3e8b4..d30aa7ac 100644 --- a/plugins/_bundled/sirsoft-tosspayments/CHANGELOG.md +++ b/plugins/_bundled/sirsoft-tosspayments/CHANGELOG.md @@ -26,6 +26,7 @@ ### Changed - 확장 문서(README · AGENTS.md · docs/README.md)의 제목에 「그누보드7」과 확장 유형을 함께 표기했습니다. 제목만 보고도 그누보드7의 어떤 종류 확장인지 알 수 있습니다. +- Composer 잠금 파일을 확장 정보와 다시 맞췄습니다. 설치·업데이트 과정에서 잠금 파일이 오래되었다는 경고가 나오지 않습니다. ## [1.0.2] - 2026-08-19 diff --git a/plugins/_bundled/sirsoft-tosspayments/composer.lock b/plugins/_bundled/sirsoft-tosspayments/composer.lock index 254377c1..d4948b9d 100644 --- a/plugins/_bundled/sirsoft-tosspayments/composer.lock +++ b/plugins/_bundled/sirsoft-tosspayments/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "6754a7ed2d766f6ab8147eb4506789cb", + "content-hash": "9260e25a1dfa51b52ab9da9cff55bce5", "packages": [], "packages-dev": [], "aliases": [], @@ -16,5 +16,5 @@ "php": "^8.2" }, "platform-dev": {}, - "plugin-api-version": "2.6.0" + "plugin-api-version": "2.9.0" } diff --git a/plugins/_bundled/sirsoft-verification_kginicis/CHANGELOG.md b/plugins/_bundled/sirsoft-verification_kginicis/CHANGELOG.md index 11cebb08..b02bc172 100644 --- a/plugins/_bundled/sirsoft-verification_kginicis/CHANGELOG.md +++ b/plugins/_bundled/sirsoft-verification_kginicis/CHANGELOG.md @@ -15,6 +15,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ### Changed - 확장 문서(README · AGENTS.md · docs/README.md)의 제목에 「그누보드7」과 확장 유형을 함께 표기했습니다. 제목만 보고도 그누보드7의 어떤 종류 확장인지 알 수 있습니다. +- Composer 잠금 파일을 확장 정보와 다시 맞췄습니다. 설치·업데이트 과정에서 잠금 파일이 오래되었다는 경고가 나오지 않습니다. ## [1.0.4] - 2026-08-22 diff --git a/plugins/_bundled/sirsoft-verification_kginicis/composer.lock b/plugins/_bundled/sirsoft-verification_kginicis/composer.lock index 3395a6e7..fe58d34c 100644 --- a/plugins/_bundled/sirsoft-verification_kginicis/composer.lock +++ b/plugins/_bundled/sirsoft-verification_kginicis/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "692c2c26341fb56857aaac63a2b59910", + "content-hash": "40744923a1c241a42ea17e09d952a2c3", "packages": [], "packages-dev": [], "aliases": [], @@ -16,5 +16,5 @@ "php": "^8.2" }, "platform-dev": {}, - "plugin-api-version": "2.6.0" + "plugin-api-version": "2.9.0" } diff --git a/plugins/_bundled/sirsoft-verification_nhnkcp/CHANGELOG.md b/plugins/_bundled/sirsoft-verification_nhnkcp/CHANGELOG.md index 2ac76f86..6aa5eb47 100644 --- a/plugins/_bundled/sirsoft-verification_nhnkcp/CHANGELOG.md +++ b/plugins/_bundled/sirsoft-verification_nhnkcp/CHANGELOG.md @@ -15,6 +15,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ### Changed - 확장 문서(README · AGENTS.md · docs/README.md)의 제목에 「그누보드7」과 확장 유형을 함께 표기했습니다. 제목만 보고도 그누보드7의 어떤 종류 확장인지 알 수 있습니다. +- Composer 잠금 파일을 새로 추가했습니다. 다른 번들 확장과 같은 방식으로 의존성 상태가 고정됩니다. ## [1.0.1] - 2026-08-19 diff --git a/plugins/_bundled/sirsoft-verification_nhnkcp/composer.lock b/plugins/_bundled/sirsoft-verification_nhnkcp/composer.lock new file mode 100644 index 00000000..b191e5fd --- /dev/null +++ b/plugins/_bundled/sirsoft-verification_nhnkcp/composer.lock @@ -0,0 +1,21 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "bf7a2e9cc2c7101b900fb987e77a41f5", + "packages": [], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": {}, + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": "^8.2", + "ext-openssl": "*" + }, + "platform-dev": {}, + "plugin-api-version": "2.9.0" +} diff --git a/public/build/core/dev-dashboard.css b/public/build/core/dev-dashboard.css index efe1ec67..34129338 100644 --- a/public/build/core/dev-dashboard.css +++ b/public/build/core/dev-dashboard.css @@ -1 +1 @@ -@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-space-y-reverse:0;--tw-border-style:solid;--tw-gradient-position:initial;--tw-gradient-from:#0000;--tw-gradient-via:#0000;--tw-gradient-to:#0000;--tw-gradient-stops:initial;--tw-gradient-via-stops:initial;--tw-gradient-from-position:0%;--tw-gradient-via-position:50%;--tw-gradient-to-position:100%;--tw-leading:initial;--tw-font-weight:initial;--tw-tracking:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-backdrop-blur:initial;--tw-backdrop-brightness:initial;--tw-backdrop-contrast:initial;--tw-backdrop-grayscale:initial;--tw-backdrop-hue-rotate:initial;--tw-backdrop-invert:initial;--tw-backdrop-opacity:initial;--tw-backdrop-saturate:initial;--tw-backdrop-sepia:initial;--tw-duration:initial;--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-content:""}}}@layer theme{:root,:host{--font-sans:-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";--font-mono:ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;--color-red-300:oklch(80.8% .114 19.571);--color-red-400:oklch(70.4% .191 22.216);--color-red-500:oklch(63.7% .237 25.331);--color-red-600:oklch(57.7% .245 27.325);--color-red-700:oklch(50.5% .213 27.518);--color-red-900:oklch(39.6% .141 25.723);--color-orange-400:oklch(75% .183 55.934);--color-orange-500:oklch(70.5% .213 47.604);--color-orange-600:oklch(64.6% .222 41.116);--color-orange-700:oklch(55.3% .195 38.402);--color-amber-400:oklch(82.8% .189 84.429);--color-amber-600:oklch(66.6% .179 58.318);--color-amber-700:oklch(55.5% .163 48.998);--color-amber-800:oklch(47.3% .137 46.201);--color-yellow-400:oklch(85.2% .199 91.936);--color-lime-600:oklch(64.8% .2 131.684);--color-lime-700:oklch(53.2% .157 131.589);--color-green-400:oklch(79.2% .209 151.711);--color-emerald-300:oklch(84.5% .143 164.978);--color-emerald-400:oklch(76.5% .177 163.223);--color-emerald-500:oklch(69.6% .17 162.48);--color-emerald-600:oklch(59.6% .145 163.225);--color-emerald-700:oklch(50.8% .118 165.612);--color-emerald-900:oklch(37.8% .077 168.94);--color-teal-500:oklch(70.4% .14 182.503);--color-teal-600:oklch(60% .118 184.704);--color-teal-700:oklch(51.1% .096 186.391);--color-cyan-300:oklch(86.5% .127 207.078);--color-cyan-400:oklch(78.9% .154 211.53);--color-cyan-500:oklch(71.5% .143 215.221);--color-cyan-600:oklch(60.9% .126 221.723);--color-cyan-700:oklch(52% .105 223.128);--color-cyan-800:oklch(45% .085 224.283);--color-sky-600:oklch(58.8% .158 241.966);--color-sky-700:oklch(50% .134 242.749);--color-sky-800:oklch(44.3% .11 240.79);--color-blue-200:oklch(88.2% .059 254.128);--color-blue-300:oklch(80.9% .105 251.813);--color-blue-400:oklch(70.7% .165 254.624);--color-blue-500:oklch(62.3% .214 259.815);--color-blue-600:oklch(54.6% .245 262.881);--color-blue-700:oklch(48.8% .243 264.376);--color-blue-800:oklch(42.4% .199 265.638);--color-indigo-600:oklch(51.1% .262 276.966);--color-indigo-700:oklch(45.7% .24 277.023);--color-violet-600:oklch(54.1% .281 293.009);--color-violet-700:oklch(49.1% .27 292.581);--color-purple-200:oklch(90.2% .063 306.703);--color-purple-300:oklch(82.7% .119 306.383);--color-purple-400:oklch(71.4% .203 305.504);--color-purple-500:oklch(62.7% .265 303.9);--color-purple-600:oklch(55.8% .288 302.321);--color-purple-700:oklch(49.6% .265 301.924);--color-fuchsia-600:oklch(59.1% .293 322.896);--color-fuchsia-700:oklch(51.8% .253 323.949);--color-fuchsia-800:oklch(45.2% .211 324.591);--color-pink-500:oklch(65.6% .241 354.308);--color-slate-50:oklch(98.4% .003 247.858);--color-slate-100:oklch(96.8% .007 247.896);--color-slate-200:oklch(92.9% .013 255.508);--color-slate-300:oklch(86.9% .022 252.894);--color-slate-400:oklch(70.4% .04 256.788);--color-slate-500:oklch(55.4% .046 257.417);--color-slate-600:oklch(44.6% .043 257.281);--color-slate-700:oklch(37.2% .044 257.287);--color-slate-800:oklch(27.9% .041 260.031);--color-slate-900:oklch(20.8% .042 265.755);--color-gray-200:oklch(92.8% .006 264.531);--color-gray-300:oklch(87.2% .01 258.338);--color-gray-400:oklch(70.7% .022 261.325);--color-gray-500:oklch(55.1% .027 264.364);--color-gray-600:oklch(44.6% .03 256.802);--color-gray-700:oklch(37.3% .034 259.733);--color-gray-900:oklch(21% .034 264.665);--color-white:#fff;--spacing:.25rem;--container-6xl:72rem;--text-xs:.75rem;--text-xs--line-height:calc(1 / .75);--text-sm:.875rem;--text-sm--line-height:calc(1.25 / .875);--text-base:1rem;--text-base--line-height: 1.5 ;--text-lg:1.125rem;--text-lg--line-height:calc(1.75 / 1.125);--text-2xl:1.5rem;--text-2xl--line-height:calc(2 / 1.5);--text-3xl:1.875rem;--text-3xl--line-height: 1.2 ;--text-4xl:2.25rem;--text-4xl--line-height:calc(2.5 / 2.25);--font-weight-light:300;--font-weight-normal:400;--font-weight-medium:500;--font-weight-semibold:600;--font-weight-bold:700;--tracking-wider:.05em;--leading-relaxed:1.625;--radius-lg:.5rem;--radius-xl:.75rem;--radius-2xl:1rem;--blur-lg:16px;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4, 0, .2, 1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring:where(:not(iframe)){outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab,red,red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.sr-only{clip-path:inset(50%);white-space:nowrap;border-width:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}.absolute{position:absolute}.relative{position:relative}.inset-0{inset:0}.top-0\.5{top:calc(var(--spacing) * .5)}.left-0\.5{left:calc(var(--spacing) * .5)}.z-10{z-index:10}.mx-auto{margin-inline:auto}.mt-0\.5{margin-top:calc(var(--spacing) * .5)}.mt-1{margin-top:var(--spacing)}.mt-1\.5{margin-top:calc(var(--spacing) * 1.5)}.mt-2{margin-top:calc(var(--spacing) * 2)}.mt-3{margin-top:calc(var(--spacing) * 3)}.mr-4{margin-right:calc(var(--spacing) * 4)}.mb-2{margin-bottom:calc(var(--spacing) * 2)}.mb-3{margin-bottom:calc(var(--spacing) * 3)}.mb-4{margin-bottom:calc(var(--spacing) * 4)}.mb-6{margin-bottom:calc(var(--spacing) * 6)}.mb-10{margin-bottom:calc(var(--spacing) * 10)}.ml-0\.5{margin-left:calc(var(--spacing) * .5)}.ml-auto{margin-left:auto}.block{display:block}.flex{display:flex}.grid{display:grid}.hidden{display:none}.inline-flex{display:inline-flex}.h-4{height:calc(var(--spacing) * 4)}.h-5{height:calc(var(--spacing) * 5)}.h-6{height:calc(var(--spacing) * 6)}.h-7{height:calc(var(--spacing) * 7)}.h-16{height:calc(var(--spacing) * 16)}.max-h-48{max-height:calc(var(--spacing) * 48)}.max-h-96{max-height:calc(var(--spacing) * 96)}.min-h-\[42px\]{min-height:42px}.min-h-screen{min-height:100vh}.w-4{width:calc(var(--spacing) * 4)}.w-5{width:calc(var(--spacing) * 5)}.w-9{width:calc(var(--spacing) * 9)}.w-11{width:calc(var(--spacing) * 11)}.w-14{width:calc(var(--spacing) * 14)}.w-16{width:calc(var(--spacing) * 16)}.w-full{width:100%}.max-w-6xl{max-width:var(--container-6xl)}.min-w-\[120px\]{min-width:120px}.flex-1{flex:1}.shrink-0{flex-shrink:0}.rotate-180{rotate:180deg}.transform{transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.cursor-not-allowed{cursor:not-allowed}.cursor-pointer{cursor:pointer}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-center{align-items:center}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.gap-1{gap:var(--spacing)}.gap-1\.5{gap:calc(var(--spacing) * 1.5)}.gap-2{gap:calc(var(--spacing) * 2)}.gap-3{gap:calc(var(--spacing) * 3)}.gap-4{gap:calc(var(--spacing) * 4)}.gap-8{gap:calc(var(--spacing) * 8)}:where(.space-y-1>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(var(--spacing) * var(--tw-space-y-reverse));margin-block-end:calc(var(--spacing) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-2>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 2) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 2) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-3>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 3) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 3) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-4>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 4) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 4) * calc(1 - var(--tw-space-y-reverse)))}.gap-x-3{column-gap:calc(var(--spacing) * 3)}.gap-y-1{row-gap:var(--spacing)}.overflow-hidden{overflow:hidden}.overflow-y-auto{overflow-y:auto}.rounded{border-radius:.25rem}.rounded-2xl{border-radius:var(--radius-2xl)}.rounded-full{border-radius:3.40282e38px}.rounded-lg{border-radius:var(--radius-lg)}.rounded-xl{border-radius:var(--radius-xl)}.border{border-style:var(--tw-border-style);border-width:1px}.border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.border-emerald-700\/50{border-color:#00795680}@supports (color:color-mix(in lab,red,red)){.border-emerald-700\/50{border-color:color-mix(in oklab,var(--color-emerald-700) 50%,transparent)}}.border-red-700\/50{border-color:#bf000f80}@supports (color:color-mix(in lab,red,red)){.border-red-700\/50{border-color:color-mix(in oklab,var(--color-red-700) 50%,transparent)}}.border-slate-500{border-color:var(--color-slate-500)}.border-slate-600{border-color:var(--color-slate-600)}.border-slate-700{border-color:var(--color-slate-700)}.border-slate-700\/20{border-color:#31415833}@supports (color:color-mix(in lab,red,red)){.border-slate-700\/20{border-color:color-mix(in oklab,var(--color-slate-700) 20%,transparent)}}.border-slate-700\/30{border-color:#3141584d}@supports (color:color-mix(in lab,red,red)){.border-slate-700\/30{border-color:color-mix(in oklab,var(--color-slate-700) 30%,transparent)}}.border-slate-700\/40{border-color:#31415866}@supports (color:color-mix(in lab,red,red)){.border-slate-700\/40{border-color:color-mix(in oklab,var(--color-slate-700) 40%,transparent)}}.border-slate-700\/50{border-color:#31415880}@supports (color:color-mix(in lab,red,red)){.border-slate-700\/50{border-color:color-mix(in oklab,var(--color-slate-700) 50%,transparent)}}.bg-amber-600{background-color:var(--color-amber-600)}.bg-amber-700{background-color:var(--color-amber-700)}.bg-blue-600{background-color:var(--color-blue-600)}.bg-cyan-600{background-color:var(--color-cyan-600)}.bg-cyan-700{background-color:var(--color-cyan-700)}.bg-emerald-600{background-color:var(--color-emerald-600)}.bg-emerald-600\/30{background-color:#0097674d}@supports (color:color-mix(in lab,red,red)){.bg-emerald-600\/30{background-color:color-mix(in oklab,var(--color-emerald-600) 30%,transparent)}}.bg-emerald-900\/30{background-color:#004e3b4d}@supports (color:color-mix(in lab,red,red)){.bg-emerald-900\/30{background-color:color-mix(in oklab,var(--color-emerald-900) 30%,transparent)}}.bg-fuchsia-600{background-color:var(--color-fuchsia-600)}.bg-fuchsia-700{background-color:var(--color-fuchsia-700)}.bg-gray-200{background-color:var(--color-gray-200)}.bg-indigo-600{background-color:var(--color-indigo-600)}.bg-lime-600{background-color:var(--color-lime-600)}.bg-orange-600{background-color:var(--color-orange-600)}.bg-purple-600{background-color:var(--color-purple-600)}.bg-purple-600\/30{background-color:#9810fa4d}@supports (color:color-mix(in lab,red,red)){.bg-purple-600\/30{background-color:color-mix(in oklab,var(--color-purple-600) 30%,transparent)}}.bg-red-600{background-color:var(--color-red-600)}.bg-red-900\/30{background-color:#82181a4d}@supports (color:color-mix(in lab,red,red)){.bg-red-900\/30{background-color:color-mix(in oklab,var(--color-red-900) 30%,transparent)}}.bg-sky-600{background-color:var(--color-sky-600)}.bg-sky-700{background-color:var(--color-sky-700)}.bg-slate-600{background-color:var(--color-slate-600)}.bg-slate-700{background-color:var(--color-slate-700)}.bg-slate-800{background-color:var(--color-slate-800)}.bg-slate-800\/30{background-color:#1d293d4d}@supports (color:color-mix(in lab,red,red)){.bg-slate-800\/30{background-color:color-mix(in oklab,var(--color-slate-800) 30%,transparent)}}.bg-slate-800\/50{background-color:#1d293d80}@supports (color:color-mix(in lab,red,red)){.bg-slate-800\/50{background-color:color-mix(in oklab,var(--color-slate-800) 50%,transparent)}}.bg-slate-800\/80{background-color:#1d293dcc}@supports (color:color-mix(in lab,red,red)){.bg-slate-800\/80{background-color:color-mix(in oklab,var(--color-slate-800) 80%,transparent)}}.bg-slate-900{background-color:var(--color-slate-900)}.bg-slate-900\/30{background-color:#0f172b4d}@supports (color:color-mix(in lab,red,red)){.bg-slate-900\/30{background-color:color-mix(in oklab,var(--color-slate-900) 30%,transparent)}}.bg-slate-900\/40{background-color:#0f172b66}@supports (color:color-mix(in lab,red,red)){.bg-slate-900\/40{background-color:color-mix(in oklab,var(--color-slate-900) 40%,transparent)}}.bg-slate-900\/50{background-color:#0f172b80}@supports (color:color-mix(in lab,red,red)){.bg-slate-900\/50{background-color:color-mix(in oklab,var(--color-slate-900) 50%,transparent)}}.bg-teal-600{background-color:var(--color-teal-600)}.bg-transparent{background-color:#0000}.bg-violet-600{background-color:var(--color-violet-600)}.bg-white{background-color:var(--color-white)}.bg-gradient-to-br{--tw-gradient-position:to bottom right in oklab;background-image:linear-gradient(var(--tw-gradient-stops))}.from-blue-500{--tw-gradient-from:var(--color-blue-500);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-blue-500\/0{--tw-gradient-from:#0000}@supports (color:color-mix(in lab,red,red)){.from-blue-500\/0{--tw-gradient-from:color-mix(in oklab, var(--color-blue-500) 0%, transparent)}}.from-blue-500\/0{--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-emerald-500\/0{--tw-gradient-from:#0000}@supports (color:color-mix(in lab,red,red)){.from-emerald-500\/0{--tw-gradient-from:color-mix(in oklab, var(--color-emerald-500) 0%, transparent)}}.from-emerald-500\/0{--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-purple-500\/0{--tw-gradient-from:#0000}@supports (color:color-mix(in lab,red,red)){.from-purple-500\/0{--tw-gradient-from:color-mix(in oklab, var(--color-purple-500) 0%, transparent)}}.from-purple-500\/0{--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-red-500\/0{--tw-gradient-from:#0000}@supports (color:color-mix(in lab,red,red)){.from-red-500\/0{--tw-gradient-from:color-mix(in oklab, var(--color-red-500) 0%, transparent)}}.from-red-500\/0{--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-slate-800\/50{--tw-gradient-from:#1d293d80}@supports (color:color-mix(in lab,red,red)){.from-slate-800\/50{--tw-gradient-from:color-mix(in oklab, var(--color-slate-800) 50%, transparent)}}.from-slate-800\/50{--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.to-cyan-400{--tw-gradient-to:var(--color-cyan-400);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.to-cyan-500\/0{--tw-gradient-to:#0000}@supports (color:color-mix(in lab,red,red)){.to-cyan-500\/0{--tw-gradient-to:color-mix(in oklab, var(--color-cyan-500) 0%, transparent)}}.to-cyan-500\/0{--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.to-orange-500\/0{--tw-gradient-to:#0000}@supports (color:color-mix(in lab,red,red)){.to-orange-500\/0{--tw-gradient-to:color-mix(in oklab, var(--color-orange-500) 0%, transparent)}}.to-orange-500\/0{--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.to-pink-500\/0{--tw-gradient-to:#0000}@supports (color:color-mix(in lab,red,red)){.to-pink-500\/0{--tw-gradient-to:color-mix(in oklab, var(--color-pink-500) 0%, transparent)}}.to-pink-500\/0{--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.to-slate-800\/30{--tw-gradient-to:#1d293d4d}@supports (color:color-mix(in lab,red,red)){.to-slate-800\/30{--tw-gradient-to:color-mix(in oklab, var(--color-slate-800) 30%, transparent)}}.to-slate-800\/30{--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.to-teal-500\/0{--tw-gradient-to:#0000}@supports (color:color-mix(in lab,red,red)){.to-teal-500\/0{--tw-gradient-to:color-mix(in oklab, var(--color-teal-500) 0%, transparent)}}.to-teal-500\/0{--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.p-2{padding:calc(var(--spacing) * 2)}.p-3{padding:calc(var(--spacing) * 3)}.p-4{padding:calc(var(--spacing) * 4)}.p-5{padding:calc(var(--spacing) * 5)}.px-1\.5{padding-inline:calc(var(--spacing) * 1.5)}.px-2{padding-inline:calc(var(--spacing) * 2)}.px-3{padding-inline:calc(var(--spacing) * 3)}.px-4{padding-inline:calc(var(--spacing) * 4)}.px-6{padding-inline:calc(var(--spacing) * 6)}.py-0\.5{padding-block:calc(var(--spacing) * .5)}.py-1{padding-block:var(--spacing)}.py-1\.5{padding-block:calc(var(--spacing) * 1.5)}.py-2{padding-block:calc(var(--spacing) * 2)}.py-4{padding-block:calc(var(--spacing) * 4)}.pt-2{padding-top:calc(var(--spacing) * 2)}.pt-3{padding-top:calc(var(--spacing) * 3)}.pb-4{padding-bottom:calc(var(--spacing) * 4)}.text-center{text-align:center}.text-left{text-align:left}.font-mono{font-family:var(--font-mono)}.text-2xl{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height))}.text-3xl{font-size:var(--text-3xl);line-height:var(--tw-leading,var(--text-3xl--line-height))}.text-4xl{font-size:var(--text-4xl);line-height:var(--tw-leading,var(--text-4xl--line-height))}.text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.text-\[10px\]{font-size:10px}.text-\[11px\]{font-size:11px}.leading-relaxed{--tw-leading:var(--leading-relaxed);line-height:var(--leading-relaxed)}.font-bold{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.font-light{--tw-font-weight:var(--font-weight-light);font-weight:var(--font-weight-light)}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.font-normal{--tw-font-weight:var(--font-weight-normal);font-weight:var(--font-weight-normal)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.tracking-wider{--tw-tracking:var(--tracking-wider);letter-spacing:var(--tracking-wider)}.whitespace-pre-wrap{white-space:pre-wrap}.text-amber-400{color:var(--color-amber-400)}.text-blue-400{color:var(--color-blue-400)}.text-cyan-300{color:var(--color-cyan-300)}.text-cyan-400{color:var(--color-cyan-400)}.text-emerald-300{color:var(--color-emerald-300)}.text-emerald-400{color:var(--color-emerald-400)}.text-emerald-400\/60{color:#00d29499}@supports (color:color-mix(in lab,red,red)){.text-emerald-400\/60{color:color-mix(in oklab,var(--color-emerald-400) 60%,transparent)}}.text-gray-500{color:var(--color-gray-500)}.text-gray-900{color:var(--color-gray-900)}.text-green-400{color:var(--color-green-400)}.text-orange-400{color:var(--color-orange-400)}.text-purple-300{color:var(--color-purple-300)}.text-purple-400{color:var(--color-purple-400)}.text-purple-500{color:var(--color-purple-500)}.text-red-300{color:var(--color-red-300)}.text-red-400{color:var(--color-red-400)}.text-slate-50{color:var(--color-slate-50)}.text-slate-100{color:var(--color-slate-100)}.text-slate-200{color:var(--color-slate-200)}.text-slate-300{color:var(--color-slate-300)}.text-slate-400{color:var(--color-slate-400)}.text-slate-500{color:var(--color-slate-500)}.text-white{color:var(--color-white)}.text-yellow-400{color:var(--color-yellow-400)}.uppercase{text-transform:uppercase}.placeholder-slate-500::placeholder{color:var(--color-slate-500)}.opacity-50{opacity:.5}.opacity-60{opacity:.6}.shadow-xl{--tw-shadow:0 20px 25px -5px var(--tw-shadow-color,#0000001a), 0 8px 10px -6px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.backdrop-blur-lg{--tw-backdrop-blur:blur(var(--blur-lg));-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.transition{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-all{transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-transform{transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.duration-200{--tw-duration:.2s;transition-duration:.2s}.duration-300{--tw-duration:.3s;transition-duration:.3s}.outline-none{--tw-outline-style:none;outline-style:none}@media(hover:hover){.group-hover\:from-blue-500\/10:is(:where(.group):hover *){--tw-gradient-from:#3080ff1a}@supports (color:color-mix(in lab,red,red)){.group-hover\:from-blue-500\/10:is(:where(.group):hover *){--tw-gradient-from:color-mix(in oklab, var(--color-blue-500) 10%, transparent)}}.group-hover\:from-blue-500\/10:is(:where(.group):hover *){--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.group-hover\:from-purple-500\/10:is(:where(.group):hover *){--tw-gradient-from:#ac4bff1a}@supports (color:color-mix(in lab,red,red)){.group-hover\:from-purple-500\/10:is(:where(.group):hover *){--tw-gradient-from:color-mix(in oklab, var(--color-purple-500) 10%, transparent)}}.group-hover\:from-purple-500\/10:is(:where(.group):hover *){--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.group-hover\:from-red-500\/5:is(:where(.group):hover *){--tw-gradient-from:#fb2c360d}@supports (color:color-mix(in lab,red,red)){.group-hover\:from-red-500\/5:is(:where(.group):hover *){--tw-gradient-from:color-mix(in oklab, var(--color-red-500) 5%, transparent)}}.group-hover\:from-red-500\/5:is(:where(.group):hover *){--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.group-hover\:to-cyan-500\/5:is(:where(.group):hover *){--tw-gradient-to:#00b7d70d}@supports (color:color-mix(in lab,red,red)){.group-hover\:to-cyan-500\/5:is(:where(.group):hover *){--tw-gradient-to:color-mix(in oklab, var(--color-cyan-500) 5%, transparent)}}.group-hover\:to-cyan-500\/5:is(:where(.group):hover *){--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.group-hover\:to-orange-500\/5:is(:where(.group):hover *){--tw-gradient-to:#fe6e000d}@supports (color:color-mix(in lab,red,red)){.group-hover\:to-orange-500\/5:is(:where(.group):hover *){--tw-gradient-to:color-mix(in oklab, var(--color-orange-500) 5%, transparent)}}.group-hover\:to-orange-500\/5:is(:where(.group):hover *){--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.group-hover\:to-pink-500\/5:is(:where(.group):hover *){--tw-gradient-to:#f6339a0d}@supports (color:color-mix(in lab,red,red)){.group-hover\:to-pink-500\/5:is(:where(.group):hover *){--tw-gradient-to:color-mix(in oklab, var(--color-pink-500) 5%, transparent)}}.group-hover\:to-pink-500\/5:is(:where(.group):hover *){--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.group-hover\:text-blue-200:is(:where(.group):hover *){color:var(--color-blue-200)}.group-hover\:text-purple-200:is(:where(.group):hover *){color:var(--color-purple-200)}}.peer-checked\:translate-x-4:is(:where(.peer):checked~*){--tw-translate-x:calc(var(--spacing) * 4);translate:var(--tw-translate-x) var(--tw-translate-y)}.peer-checked\:bg-blue-600:is(:where(.peer):checked~*){background-color:var(--color-blue-600)}.peer-checked\:bg-red-600:is(:where(.peer):checked~*){background-color:var(--color-red-600)}.peer-focus\:ring-4:is(:where(.peer):focus~*){--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(4px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.peer-focus\:ring-blue-300:is(:where(.peer):focus~*){--tw-ring-color:var(--color-blue-300)}.peer-focus\:outline-none:is(:where(.peer):focus~*){--tw-outline-style:none;outline-style:none}.after\:absolute:after{content:var(--tw-content);position:absolute}.after\:top-\[2px\]:after{content:var(--tw-content);top:2px}.after\:left-\[2px\]:after{content:var(--tw-content);left:2px}.after\:h-4:after{content:var(--tw-content);height:calc(var(--spacing) * 4)}.after\:h-5:after{content:var(--tw-content);height:calc(var(--spacing) * 5)}.after\:h-6:after{content:var(--tw-content);height:calc(var(--spacing) * 6)}.after\:w-4:after{content:var(--tw-content);width:calc(var(--spacing) * 4)}.after\:w-5:after{content:var(--tw-content);width:calc(var(--spacing) * 5)}.after\:w-6:after{content:var(--tw-content);width:calc(var(--spacing) * 6)}.after\:rounded-full:after{content:var(--tw-content);border-radius:3.40282e38px}.after\:border:after{content:var(--tw-content);border-style:var(--tw-border-style);border-width:1px}.after\:border-gray-300:after{content:var(--tw-content);border-color:var(--color-gray-300)}.after\:bg-white:after{content:var(--tw-content);background-color:var(--color-white)}.after\:transition-all:after{content:var(--tw-content);transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.after\:content-\[\'\'\]:after{--tw-content:"";content:var(--tw-content)}.peer-checked\:after\:translate-x-full:is(:where(.peer):checked~*):after{content:var(--tw-content);--tw-translate-x:100%;translate:var(--tw-translate-x) var(--tw-translate-y)}.peer-checked\:after\:border-white:is(:where(.peer):checked~*):after{content:var(--tw-content);border-color:var(--color-white)}.focus-within\:border-emerald-500:focus-within{border-color:var(--color-emerald-500)}.focus-within\:border-purple-500:focus-within{border-color:var(--color-purple-500)}@media(hover:hover){.hover\:bg-amber-700:hover{background-color:var(--color-amber-700)}.hover\:bg-amber-800:hover{background-color:var(--color-amber-800)}.hover\:bg-blue-700:hover{background-color:var(--color-blue-700)}.hover\:bg-cyan-700:hover{background-color:var(--color-cyan-700)}.hover\:bg-cyan-800:hover{background-color:var(--color-cyan-800)}.hover\:bg-emerald-700:hover{background-color:var(--color-emerald-700)}.hover\:bg-emerald-900\/30:hover{background-color:#004e3b4d}@supports (color:color-mix(in lab,red,red)){.hover\:bg-emerald-900\/30:hover{background-color:color-mix(in oklab,var(--color-emerald-900) 30%,transparent)}}.hover\:bg-fuchsia-700:hover{background-color:var(--color-fuchsia-700)}.hover\:bg-fuchsia-800:hover{background-color:var(--color-fuchsia-800)}.hover\:bg-indigo-700:hover{background-color:var(--color-indigo-700)}.hover\:bg-lime-700:hover{background-color:var(--color-lime-700)}.hover\:bg-orange-700:hover{background-color:var(--color-orange-700)}.hover\:bg-purple-700:hover{background-color:var(--color-purple-700)}.hover\:bg-red-700:hover{background-color:var(--color-red-700)}.hover\:bg-sky-700:hover{background-color:var(--color-sky-700)}.hover\:bg-sky-800:hover{background-color:var(--color-sky-800)}.hover\:bg-slate-700:hover{background-color:var(--color-slate-700)}.hover\:bg-slate-700\/20:hover{background-color:#31415833}@supports (color:color-mix(in lab,red,red)){.hover\:bg-slate-700\/20:hover{background-color:color-mix(in oklab,var(--color-slate-700) 20%,transparent)}}.hover\:bg-slate-700\/50:hover{background-color:#31415880}@supports (color:color-mix(in lab,red,red)){.hover\:bg-slate-700\/50:hover{background-color:color-mix(in oklab,var(--color-slate-700) 50%,transparent)}}.hover\:bg-teal-700:hover{background-color:var(--color-teal-700)}.hover\:bg-violet-700:hover{background-color:var(--color-violet-700)}.hover\:text-red-400:hover{color:var(--color-red-400)}.hover\:text-white:hover{color:var(--color-white)}}.focus\:border-emerald-500:focus{border-color:var(--color-emerald-500)}.focus\:ring-purple-500:focus{--tw-ring-color:var(--color-purple-500)}@media(min-width:48rem){.md\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}}@media(min-width:64rem){.lg\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}}@media(prefers-color-scheme:dark){.dark\:bg-gray-700{background-color:var(--color-gray-700)}.dark\:text-gray-400{color:var(--color-gray-400)}.dark\:text-white{color:var(--color-white)}.dark\:peer-checked\:bg-blue-500:is(:where(.peer):checked~*){background-color:var(--color-blue-500)}.dark\:peer-focus\:ring-blue-800:is(:where(.peer):focus~*){--tw-ring-color:var(--color-blue-800)}.dark\:after\:border-gray-600:after{content:var(--tw-content);border-color:var(--color-gray-600)}}}@property --tw-rotate-x{syntax:"*";inherits:false}@property --tw-rotate-y{syntax:"*";inherits:false}@property --tw-rotate-z{syntax:"*";inherits:false}@property --tw-skew-x{syntax:"*";inherits:false}@property --tw-skew-y{syntax:"*";inherits:false}@property --tw-space-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-gradient-position{syntax:"*";inherits:false}@property --tw-gradient-from{syntax:"";inherits:false;initial-value:#0000}@property --tw-gradient-via{syntax:"";inherits:false;initial-value:#0000}@property --tw-gradient-to{syntax:"";inherits:false;initial-value:#0000}@property --tw-gradient-stops{syntax:"*";inherits:false}@property --tw-gradient-via-stops{syntax:"*";inherits:false}@property --tw-gradient-from-position{syntax:"";inherits:false;initial-value:0%}@property --tw-gradient-via-position{syntax:"";inherits:false;initial-value:50%}@property --tw-gradient-to-position{syntax:"";inherits:false;initial-value:100%}@property --tw-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-tracking{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-backdrop-blur{syntax:"*";inherits:false}@property --tw-backdrop-brightness{syntax:"*";inherits:false}@property --tw-backdrop-contrast{syntax:"*";inherits:false}@property --tw-backdrop-grayscale{syntax:"*";inherits:false}@property --tw-backdrop-hue-rotate{syntax:"*";inherits:false}@property --tw-backdrop-invert{syntax:"*";inherits:false}@property --tw-backdrop-opacity{syntax:"*";inherits:false}@property --tw-backdrop-saturate{syntax:"*";inherits:false}@property --tw-backdrop-sepia{syntax:"*";inherits:false}@property --tw-duration{syntax:"*";inherits:false}@property --tw-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-z{syntax:"*";inherits:false;initial-value:0}@property --tw-content{syntax:"*";inherits:false;initial-value:""} +/*! tailwindcss v4.1.16 | MIT License | https://tailwindcss.com */@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-space-y-reverse:0;--tw-border-style:solid;--tw-gradient-position:initial;--tw-gradient-from:#0000;--tw-gradient-via:#0000;--tw-gradient-to:#0000;--tw-gradient-stops:initial;--tw-gradient-via-stops:initial;--tw-gradient-from-position:0%;--tw-gradient-via-position:50%;--tw-gradient-to-position:100%;--tw-leading:initial;--tw-font-weight:initial;--tw-tracking:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-backdrop-blur:initial;--tw-backdrop-brightness:initial;--tw-backdrop-contrast:initial;--tw-backdrop-grayscale:initial;--tw-backdrop-hue-rotate:initial;--tw-backdrop-invert:initial;--tw-backdrop-opacity:initial;--tw-backdrop-saturate:initial;--tw-backdrop-sepia:initial;--tw-duration:initial;--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-content:""}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--color-red-300:oklch(80.8% .114 19.571);--color-red-400:oklch(70.4% .191 22.216);--color-red-500:oklch(63.7% .237 25.331);--color-red-600:oklch(57.7% .245 27.325);--color-red-700:oklch(50.5% .213 27.518);--color-red-900:oklch(39.6% .141 25.723);--color-orange-400:oklch(75% .183 55.934);--color-orange-500:oklch(70.5% .213 47.604);--color-orange-600:oklch(64.6% .222 41.116);--color-orange-700:oklch(55.3% .195 38.402);--color-amber-400:oklch(82.8% .189 84.429);--color-amber-600:oklch(66.6% .179 58.318);--color-amber-700:oklch(55.5% .163 48.998);--color-amber-800:oklch(47.3% .137 46.201);--color-yellow-400:oklch(85.2% .199 91.936);--color-lime-600:oklch(64.8% .2 131.684);--color-lime-700:oklch(53.2% .157 131.589);--color-green-400:oklch(79.2% .209 151.711);--color-emerald-300:oklch(84.5% .143 164.978);--color-emerald-400:oklch(76.5% .177 163.223);--color-emerald-500:oklch(69.6% .17 162.48);--color-emerald-600:oklch(59.6% .145 163.225);--color-emerald-700:oklch(50.8% .118 165.612);--color-emerald-900:oklch(37.8% .077 168.94);--color-teal-500:oklch(70.4% .14 182.503);--color-teal-600:oklch(60% .118 184.704);--color-teal-700:oklch(51.1% .096 186.391);--color-cyan-300:oklch(86.5% .127 207.078);--color-cyan-400:oklch(78.9% .154 211.53);--color-cyan-500:oklch(71.5% .143 215.221);--color-cyan-600:oklch(60.9% .126 221.723);--color-cyan-700:oklch(52% .105 223.128);--color-cyan-800:oklch(45% .085 224.283);--color-sky-600:oklch(58.8% .158 241.966);--color-sky-700:oklch(50% .134 242.749);--color-sky-800:oklch(44.3% .11 240.79);--color-blue-200:oklch(88.2% .059 254.128);--color-blue-300:oklch(80.9% .105 251.813);--color-blue-400:oklch(70.7% .165 254.624);--color-blue-500:oklch(62.3% .214 259.815);--color-blue-600:oklch(54.6% .245 262.881);--color-blue-700:oklch(48.8% .243 264.376);--color-blue-800:oklch(42.4% .199 265.638);--color-indigo-600:oklch(51.1% .262 276.966);--color-indigo-700:oklch(45.7% .24 277.023);--color-violet-600:oklch(54.1% .281 293.009);--color-violet-700:oklch(49.1% .27 292.581);--color-purple-200:oklch(90.2% .063 306.703);--color-purple-300:oklch(82.7% .119 306.383);--color-purple-400:oklch(71.4% .203 305.504);--color-purple-500:oklch(62.7% .265 303.9);--color-purple-600:oklch(55.8% .288 302.321);--color-purple-700:oklch(49.6% .265 301.924);--color-fuchsia-600:oklch(59.1% .293 322.896);--color-fuchsia-700:oklch(51.8% .253 323.949);--color-fuchsia-800:oklch(45.2% .211 324.591);--color-pink-500:oklch(65.6% .241 354.308);--color-slate-50:oklch(98.4% .003 247.858);--color-slate-100:oklch(96.8% .007 247.896);--color-slate-200:oklch(92.9% .013 255.508);--color-slate-300:oklch(86.9% .022 252.894);--color-slate-400:oklch(70.4% .04 256.788);--color-slate-500:oklch(55.4% .046 257.417);--color-slate-600:oklch(44.6% .043 257.281);--color-slate-700:oklch(37.2% .044 257.287);--color-slate-800:oklch(27.9% .041 260.031);--color-slate-900:oklch(20.8% .042 265.755);--color-gray-200:oklch(92.8% .006 264.531);--color-gray-300:oklch(87.2% .01 258.338);--color-gray-400:oklch(70.7% .022 261.325);--color-gray-500:oklch(55.1% .027 264.364);--color-gray-600:oklch(44.6% .03 256.802);--color-gray-700:oklch(37.3% .034 259.733);--color-gray-900:oklch(21% .034 264.665);--color-white:#fff;--spacing:.25rem;--container-6xl:72rem;--text-xs:.75rem;--text-xs--line-height:calc(1/.75);--text-sm:.875rem;--text-sm--line-height:calc(1.25/.875);--text-base:1rem;--text-base--line-height: 1.5 ;--text-lg:1.125rem;--text-lg--line-height:calc(1.75/1.125);--text-2xl:1.5rem;--text-2xl--line-height:calc(2/1.5);--text-3xl:1.875rem;--text-3xl--line-height: 1.2 ;--text-4xl:2.25rem;--text-4xl--line-height:calc(2.5/2.25);--font-weight-light:300;--font-weight-normal:400;--font-weight-medium:500;--font-weight-semibold:600;--font-weight-bold:700;--tracking-wider:.05em;--leading-relaxed:1.625;--radius-lg:.5rem;--radius-xl:.75rem;--radius-2xl:1rem;--blur-lg:16px;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4,0,.2,1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab,red,red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.sr-only{clip-path:inset(50%);white-space:nowrap;border-width:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}.absolute{position:absolute}.relative{position:relative}.inset-0{inset:calc(var(--spacing)*0)}.top-0\.5{top:calc(var(--spacing)*.5)}.left-0\.5{left:calc(var(--spacing)*.5)}.z-10{z-index:10}.mx-auto{margin-inline:auto}.mt-0\.5{margin-top:calc(var(--spacing)*.5)}.mt-1{margin-top:calc(var(--spacing)*1)}.mt-1\.5{margin-top:calc(var(--spacing)*1.5)}.mt-2{margin-top:calc(var(--spacing)*2)}.mt-3{margin-top:calc(var(--spacing)*3)}.mr-4{margin-right:calc(var(--spacing)*4)}.mb-2{margin-bottom:calc(var(--spacing)*2)}.mb-3{margin-bottom:calc(var(--spacing)*3)}.mb-4{margin-bottom:calc(var(--spacing)*4)}.mb-6{margin-bottom:calc(var(--spacing)*6)}.mb-10{margin-bottom:calc(var(--spacing)*10)}.ml-0\.5{margin-left:calc(var(--spacing)*.5)}.ml-auto{margin-left:auto}.block{display:block}.flex{display:flex}.grid{display:grid}.hidden{display:none}.inline-flex{display:inline-flex}.h-4{height:calc(var(--spacing)*4)}.h-5{height:calc(var(--spacing)*5)}.h-6{height:calc(var(--spacing)*6)}.h-7{height:calc(var(--spacing)*7)}.h-16{height:calc(var(--spacing)*16)}.max-h-48{max-height:calc(var(--spacing)*48)}.max-h-96{max-height:calc(var(--spacing)*96)}.min-h-\[42px\]{min-height:42px}.min-h-screen{min-height:100vh}.w-4{width:calc(var(--spacing)*4)}.w-5{width:calc(var(--spacing)*5)}.w-9{width:calc(var(--spacing)*9)}.w-11{width:calc(var(--spacing)*11)}.w-14{width:calc(var(--spacing)*14)}.w-16{width:calc(var(--spacing)*16)}.w-full{width:100%}.max-w-6xl{max-width:var(--container-6xl)}.min-w-\[120px\]{min-width:120px}.flex-1{flex:1}.shrink-0{flex-shrink:0}.rotate-180{rotate:180deg}.transform{transform:var(--tw-rotate-x,)var(--tw-rotate-y,)var(--tw-rotate-z,)var(--tw-skew-x,)var(--tw-skew-y,)}.cursor-not-allowed{cursor:not-allowed}.cursor-pointer{cursor:pointer}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-center{align-items:center}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.gap-1{gap:calc(var(--spacing)*1)}.gap-1\.5{gap:calc(var(--spacing)*1.5)}.gap-2{gap:calc(var(--spacing)*2)}.gap-3{gap:calc(var(--spacing)*3)}.gap-4{gap:calc(var(--spacing)*4)}.gap-8{gap:calc(var(--spacing)*8)}:where(.space-y-1>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*1)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*1)*calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-2>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*2)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*2)*calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-3>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*3)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*3)*calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-4>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*4)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*4)*calc(1 - var(--tw-space-y-reverse)))}.gap-x-3{column-gap:calc(var(--spacing)*3)}.gap-y-1{row-gap:calc(var(--spacing)*1)}.overflow-hidden{overflow:hidden}.overflow-y-auto{overflow-y:auto}.rounded{border-radius:.25rem}.rounded-2xl{border-radius:var(--radius-2xl)}.rounded-full{border-radius:3.40282e38px}.rounded-lg{border-radius:var(--radius-lg)}.rounded-xl{border-radius:var(--radius-xl)}.border{border-style:var(--tw-border-style);border-width:1px}.border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.border-emerald-700\/50{border-color:#00795680}@supports (color:color-mix(in lab,red,red)){.border-emerald-700\/50{border-color:color-mix(in oklab,var(--color-emerald-700)50%,transparent)}}.border-red-700\/50{border-color:#bf000f80}@supports (color:color-mix(in lab,red,red)){.border-red-700\/50{border-color:color-mix(in oklab,var(--color-red-700)50%,transparent)}}.border-slate-500{border-color:var(--color-slate-500)}.border-slate-600{border-color:var(--color-slate-600)}.border-slate-700{border-color:var(--color-slate-700)}.border-slate-700\/20{border-color:#31415833}@supports (color:color-mix(in lab,red,red)){.border-slate-700\/20{border-color:color-mix(in oklab,var(--color-slate-700)20%,transparent)}}.border-slate-700\/30{border-color:#3141584d}@supports (color:color-mix(in lab,red,red)){.border-slate-700\/30{border-color:color-mix(in oklab,var(--color-slate-700)30%,transparent)}}.border-slate-700\/40{border-color:#31415866}@supports (color:color-mix(in lab,red,red)){.border-slate-700\/40{border-color:color-mix(in oklab,var(--color-slate-700)40%,transparent)}}.border-slate-700\/50{border-color:#31415880}@supports (color:color-mix(in lab,red,red)){.border-slate-700\/50{border-color:color-mix(in oklab,var(--color-slate-700)50%,transparent)}}.bg-amber-600{background-color:var(--color-amber-600)}.bg-amber-700{background-color:var(--color-amber-700)}.bg-blue-600{background-color:var(--color-blue-600)}.bg-cyan-600{background-color:var(--color-cyan-600)}.bg-cyan-700{background-color:var(--color-cyan-700)}.bg-emerald-600{background-color:var(--color-emerald-600)}.bg-emerald-600\/30{background-color:#0097674d}@supports (color:color-mix(in lab,red,red)){.bg-emerald-600\/30{background-color:color-mix(in oklab,var(--color-emerald-600)30%,transparent)}}.bg-emerald-900\/30{background-color:#004e3b4d}@supports (color:color-mix(in lab,red,red)){.bg-emerald-900\/30{background-color:color-mix(in oklab,var(--color-emerald-900)30%,transparent)}}.bg-fuchsia-600{background-color:var(--color-fuchsia-600)}.bg-fuchsia-700{background-color:var(--color-fuchsia-700)}.bg-gray-200{background-color:var(--color-gray-200)}.bg-indigo-600{background-color:var(--color-indigo-600)}.bg-lime-600{background-color:var(--color-lime-600)}.bg-orange-600{background-color:var(--color-orange-600)}.bg-purple-600{background-color:var(--color-purple-600)}.bg-purple-600\/30{background-color:#9810fa4d}@supports (color:color-mix(in lab,red,red)){.bg-purple-600\/30{background-color:color-mix(in oklab,var(--color-purple-600)30%,transparent)}}.bg-red-600{background-color:var(--color-red-600)}.bg-red-900\/30{background-color:#82181a4d}@supports (color:color-mix(in lab,red,red)){.bg-red-900\/30{background-color:color-mix(in oklab,var(--color-red-900)30%,transparent)}}.bg-sky-600{background-color:var(--color-sky-600)}.bg-sky-700{background-color:var(--color-sky-700)}.bg-slate-600{background-color:var(--color-slate-600)}.bg-slate-700{background-color:var(--color-slate-700)}.bg-slate-800{background-color:var(--color-slate-800)}.bg-slate-800\/30{background-color:#1d293d4d}@supports (color:color-mix(in lab,red,red)){.bg-slate-800\/30{background-color:color-mix(in oklab,var(--color-slate-800)30%,transparent)}}.bg-slate-800\/50{background-color:#1d293d80}@supports (color:color-mix(in lab,red,red)){.bg-slate-800\/50{background-color:color-mix(in oklab,var(--color-slate-800)50%,transparent)}}.bg-slate-800\/80{background-color:#1d293dcc}@supports (color:color-mix(in lab,red,red)){.bg-slate-800\/80{background-color:color-mix(in oklab,var(--color-slate-800)80%,transparent)}}.bg-slate-900{background-color:var(--color-slate-900)}.bg-slate-900\/30{background-color:#0f172b4d}@supports (color:color-mix(in lab,red,red)){.bg-slate-900\/30{background-color:color-mix(in oklab,var(--color-slate-900)30%,transparent)}}.bg-slate-900\/40{background-color:#0f172b66}@supports (color:color-mix(in lab,red,red)){.bg-slate-900\/40{background-color:color-mix(in oklab,var(--color-slate-900)40%,transparent)}}.bg-slate-900\/50{background-color:#0f172b80}@supports (color:color-mix(in lab,red,red)){.bg-slate-900\/50{background-color:color-mix(in oklab,var(--color-slate-900)50%,transparent)}}.bg-teal-600{background-color:var(--color-teal-600)}.bg-transparent{background-color:#0000}.bg-violet-600{background-color:var(--color-violet-600)}.bg-white{background-color:var(--color-white)}.bg-gradient-to-br{--tw-gradient-position:to bottom right in oklab;background-image:linear-gradient(var(--tw-gradient-stops))}.from-blue-500{--tw-gradient-from:var(--color-blue-500);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.from-blue-500\/0{--tw-gradient-from:#0000}@supports (color:color-mix(in lab,red,red)){.from-blue-500\/0{--tw-gradient-from:color-mix(in oklab,var(--color-blue-500)0%,transparent)}}.from-blue-500\/0{--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.from-emerald-500\/0{--tw-gradient-from:#0000}@supports (color:color-mix(in lab,red,red)){.from-emerald-500\/0{--tw-gradient-from:color-mix(in oklab,var(--color-emerald-500)0%,transparent)}}.from-emerald-500\/0{--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.from-purple-500\/0{--tw-gradient-from:#0000}@supports (color:color-mix(in lab,red,red)){.from-purple-500\/0{--tw-gradient-from:color-mix(in oklab,var(--color-purple-500)0%,transparent)}}.from-purple-500\/0{--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.from-red-500\/0{--tw-gradient-from:#0000}@supports (color:color-mix(in lab,red,red)){.from-red-500\/0{--tw-gradient-from:color-mix(in oklab,var(--color-red-500)0%,transparent)}}.from-red-500\/0{--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.from-slate-800\/50{--tw-gradient-from:#1d293d80}@supports (color:color-mix(in lab,red,red)){.from-slate-800\/50{--tw-gradient-from:color-mix(in oklab,var(--color-slate-800)50%,transparent)}}.from-slate-800\/50{--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.to-cyan-400{--tw-gradient-to:var(--color-cyan-400);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.to-cyan-500\/0{--tw-gradient-to:#0000}@supports (color:color-mix(in lab,red,red)){.to-cyan-500\/0{--tw-gradient-to:color-mix(in oklab,var(--color-cyan-500)0%,transparent)}}.to-cyan-500\/0{--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.to-orange-500\/0{--tw-gradient-to:#0000}@supports (color:color-mix(in lab,red,red)){.to-orange-500\/0{--tw-gradient-to:color-mix(in oklab,var(--color-orange-500)0%,transparent)}}.to-orange-500\/0{--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.to-pink-500\/0{--tw-gradient-to:#0000}@supports (color:color-mix(in lab,red,red)){.to-pink-500\/0{--tw-gradient-to:color-mix(in oklab,var(--color-pink-500)0%,transparent)}}.to-pink-500\/0{--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.to-slate-800\/30{--tw-gradient-to:#1d293d4d}@supports (color:color-mix(in lab,red,red)){.to-slate-800\/30{--tw-gradient-to:color-mix(in oklab,var(--color-slate-800)30%,transparent)}}.to-slate-800\/30{--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.to-teal-500\/0{--tw-gradient-to:#0000}@supports (color:color-mix(in lab,red,red)){.to-teal-500\/0{--tw-gradient-to:color-mix(in oklab,var(--color-teal-500)0%,transparent)}}.to-teal-500\/0{--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.p-2{padding:calc(var(--spacing)*2)}.p-3{padding:calc(var(--spacing)*3)}.p-4{padding:calc(var(--spacing)*4)}.p-5{padding:calc(var(--spacing)*5)}.px-1\.5{padding-inline:calc(var(--spacing)*1.5)}.px-2{padding-inline:calc(var(--spacing)*2)}.px-3{padding-inline:calc(var(--spacing)*3)}.px-4{padding-inline:calc(var(--spacing)*4)}.px-6{padding-inline:calc(var(--spacing)*6)}.py-0\.5{padding-block:calc(var(--spacing)*.5)}.py-1{padding-block:calc(var(--spacing)*1)}.py-1\.5{padding-block:calc(var(--spacing)*1.5)}.py-2{padding-block:calc(var(--spacing)*2)}.py-4{padding-block:calc(var(--spacing)*4)}.pt-2{padding-top:calc(var(--spacing)*2)}.pt-3{padding-top:calc(var(--spacing)*3)}.pb-4{padding-bottom:calc(var(--spacing)*4)}.text-center{text-align:center}.text-left{text-align:left}.font-mono{font-family:var(--font-mono)}.text-2xl{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height))}.text-3xl{font-size:var(--text-3xl);line-height:var(--tw-leading,var(--text-3xl--line-height))}.text-4xl{font-size:var(--text-4xl);line-height:var(--tw-leading,var(--text-4xl--line-height))}.text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.text-\[10px\]{font-size:10px}.text-\[11px\]{font-size:11px}.leading-relaxed{--tw-leading:var(--leading-relaxed);line-height:var(--leading-relaxed)}.font-bold{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.font-light{--tw-font-weight:var(--font-weight-light);font-weight:var(--font-weight-light)}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.font-normal{--tw-font-weight:var(--font-weight-normal);font-weight:var(--font-weight-normal)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.tracking-wider{--tw-tracking:var(--tracking-wider);letter-spacing:var(--tracking-wider)}.whitespace-pre-wrap{white-space:pre-wrap}.text-amber-400{color:var(--color-amber-400)}.text-blue-400{color:var(--color-blue-400)}.text-cyan-300{color:var(--color-cyan-300)}.text-cyan-400{color:var(--color-cyan-400)}.text-emerald-300{color:var(--color-emerald-300)}.text-emerald-400{color:var(--color-emerald-400)}.text-emerald-400\/60{color:#00d29499}@supports (color:color-mix(in lab,red,red)){.text-emerald-400\/60{color:color-mix(in oklab,var(--color-emerald-400)60%,transparent)}}.text-gray-500{color:var(--color-gray-500)}.text-gray-900{color:var(--color-gray-900)}.text-green-400{color:var(--color-green-400)}.text-orange-400{color:var(--color-orange-400)}.text-purple-300{color:var(--color-purple-300)}.text-purple-400{color:var(--color-purple-400)}.text-purple-500{color:var(--color-purple-500)}.text-red-300{color:var(--color-red-300)}.text-red-400{color:var(--color-red-400)}.text-slate-50{color:var(--color-slate-50)}.text-slate-100{color:var(--color-slate-100)}.text-slate-200{color:var(--color-slate-200)}.text-slate-300{color:var(--color-slate-300)}.text-slate-400{color:var(--color-slate-400)}.text-slate-500{color:var(--color-slate-500)}.text-white{color:var(--color-white)}.text-yellow-400{color:var(--color-yellow-400)}.uppercase{text-transform:uppercase}.placeholder-slate-500::placeholder{color:var(--color-slate-500)}.opacity-50{opacity:.5}.opacity-60{opacity:.6}.shadow-xl{--tw-shadow:0 20px 25px -5px var(--tw-shadow-color,#0000001a),0 8px 10px -6px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.backdrop-blur-lg{--tw-backdrop-blur:blur(var(--blur-lg));-webkit-backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,)}.transition{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-all{transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-transform{transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.duration-200{--tw-duration:.2s;transition-duration:.2s}.duration-300{--tw-duration:.3s;transition-duration:.3s}.outline-none{--tw-outline-style:none;outline-style:none}@media(hover:hover){.group-hover\:from-blue-500\/10:is(:where(.group):hover *){--tw-gradient-from:#3080ff1a}@supports (color:color-mix(in lab,red,red)){.group-hover\:from-blue-500\/10:is(:where(.group):hover *){--tw-gradient-from:color-mix(in oklab,var(--color-blue-500)10%,transparent)}}.group-hover\:from-blue-500\/10:is(:where(.group):hover *){--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.group-hover\:from-purple-500\/10:is(:where(.group):hover *){--tw-gradient-from:#ac4bff1a}@supports (color:color-mix(in lab,red,red)){.group-hover\:from-purple-500\/10:is(:where(.group):hover *){--tw-gradient-from:color-mix(in oklab,var(--color-purple-500)10%,transparent)}}.group-hover\:from-purple-500\/10:is(:where(.group):hover *){--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.group-hover\:from-red-500\/5:is(:where(.group):hover *){--tw-gradient-from:#fb2c360d}@supports (color:color-mix(in lab,red,red)){.group-hover\:from-red-500\/5:is(:where(.group):hover *){--tw-gradient-from:color-mix(in oklab,var(--color-red-500)5%,transparent)}}.group-hover\:from-red-500\/5:is(:where(.group):hover *){--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.group-hover\:to-cyan-500\/5:is(:where(.group):hover *){--tw-gradient-to:#00b7d70d}@supports (color:color-mix(in lab,red,red)){.group-hover\:to-cyan-500\/5:is(:where(.group):hover *){--tw-gradient-to:color-mix(in oklab,var(--color-cyan-500)5%,transparent)}}.group-hover\:to-cyan-500\/5:is(:where(.group):hover *){--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.group-hover\:to-orange-500\/5:is(:where(.group):hover *){--tw-gradient-to:#fe6e000d}@supports (color:color-mix(in lab,red,red)){.group-hover\:to-orange-500\/5:is(:where(.group):hover *){--tw-gradient-to:color-mix(in oklab,var(--color-orange-500)5%,transparent)}}.group-hover\:to-orange-500\/5:is(:where(.group):hover *){--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.group-hover\:to-pink-500\/5:is(:where(.group):hover *){--tw-gradient-to:#f6339a0d}@supports (color:color-mix(in lab,red,red)){.group-hover\:to-pink-500\/5:is(:where(.group):hover *){--tw-gradient-to:color-mix(in oklab,var(--color-pink-500)5%,transparent)}}.group-hover\:to-pink-500\/5:is(:where(.group):hover *){--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.group-hover\:text-blue-200:is(:where(.group):hover *){color:var(--color-blue-200)}.group-hover\:text-purple-200:is(:where(.group):hover *){color:var(--color-purple-200)}}.peer-checked\:translate-x-4:is(:where(.peer):checked~*){--tw-translate-x:calc(var(--spacing)*4);translate:var(--tw-translate-x)var(--tw-translate-y)}.peer-checked\:bg-blue-600:is(:where(.peer):checked~*){background-color:var(--color-blue-600)}.peer-checked\:bg-red-600:is(:where(.peer):checked~*){background-color:var(--color-red-600)}.peer-focus\:ring-4:is(:where(.peer):focus~*){--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(4px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.peer-focus\:ring-blue-300:is(:where(.peer):focus~*){--tw-ring-color:var(--color-blue-300)}.peer-focus\:outline-none:is(:where(.peer):focus~*){--tw-outline-style:none;outline-style:none}.after\:absolute:after{content:var(--tw-content);position:absolute}.after\:top-\[2px\]:after{content:var(--tw-content);top:2px}.after\:left-\[2px\]:after{content:var(--tw-content);left:2px}.after\:h-4:after{content:var(--tw-content);height:calc(var(--spacing)*4)}.after\:h-5:after{content:var(--tw-content);height:calc(var(--spacing)*5)}.after\:h-6:after{content:var(--tw-content);height:calc(var(--spacing)*6)}.after\:w-4:after{content:var(--tw-content);width:calc(var(--spacing)*4)}.after\:w-5:after{content:var(--tw-content);width:calc(var(--spacing)*5)}.after\:w-6:after{content:var(--tw-content);width:calc(var(--spacing)*6)}.after\:rounded-full:after{content:var(--tw-content);border-radius:3.40282e38px}.after\:border:after{content:var(--tw-content);border-style:var(--tw-border-style);border-width:1px}.after\:border-gray-300:after{content:var(--tw-content);border-color:var(--color-gray-300)}.after\:bg-white:after{content:var(--tw-content);background-color:var(--color-white)}.after\:transition-all:after{content:var(--tw-content);transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.after\:content-\[\'\'\]:after{--tw-content:"";content:var(--tw-content)}.peer-checked\:after\:translate-x-full:is(:where(.peer):checked~*):after{content:var(--tw-content);--tw-translate-x:100%;translate:var(--tw-translate-x)var(--tw-translate-y)}.peer-checked\:after\:border-white:is(:where(.peer):checked~*):after{content:var(--tw-content);border-color:var(--color-white)}.focus-within\:border-emerald-500:focus-within{border-color:var(--color-emerald-500)}.focus-within\:border-purple-500:focus-within{border-color:var(--color-purple-500)}@media(hover:hover){.hover\:bg-amber-700:hover{background-color:var(--color-amber-700)}.hover\:bg-amber-800:hover{background-color:var(--color-amber-800)}.hover\:bg-blue-700:hover{background-color:var(--color-blue-700)}.hover\:bg-cyan-700:hover{background-color:var(--color-cyan-700)}.hover\:bg-cyan-800:hover{background-color:var(--color-cyan-800)}.hover\:bg-emerald-700:hover{background-color:var(--color-emerald-700)}.hover\:bg-emerald-900\/30:hover{background-color:#004e3b4d}@supports (color:color-mix(in lab,red,red)){.hover\:bg-emerald-900\/30:hover{background-color:color-mix(in oklab,var(--color-emerald-900)30%,transparent)}}.hover\:bg-fuchsia-700:hover{background-color:var(--color-fuchsia-700)}.hover\:bg-fuchsia-800:hover{background-color:var(--color-fuchsia-800)}.hover\:bg-indigo-700:hover{background-color:var(--color-indigo-700)}.hover\:bg-lime-700:hover{background-color:var(--color-lime-700)}.hover\:bg-orange-700:hover{background-color:var(--color-orange-700)}.hover\:bg-purple-700:hover{background-color:var(--color-purple-700)}.hover\:bg-red-700:hover{background-color:var(--color-red-700)}.hover\:bg-sky-700:hover{background-color:var(--color-sky-700)}.hover\:bg-sky-800:hover{background-color:var(--color-sky-800)}.hover\:bg-slate-700:hover{background-color:var(--color-slate-700)}.hover\:bg-slate-700\/20:hover{background-color:#31415833}@supports (color:color-mix(in lab,red,red)){.hover\:bg-slate-700\/20:hover{background-color:color-mix(in oklab,var(--color-slate-700)20%,transparent)}}.hover\:bg-slate-700\/50:hover{background-color:#31415880}@supports (color:color-mix(in lab,red,red)){.hover\:bg-slate-700\/50:hover{background-color:color-mix(in oklab,var(--color-slate-700)50%,transparent)}}.hover\:bg-teal-700:hover{background-color:var(--color-teal-700)}.hover\:bg-violet-700:hover{background-color:var(--color-violet-700)}.hover\:text-red-400:hover{color:var(--color-red-400)}.hover\:text-white:hover{color:var(--color-white)}}.focus\:border-emerald-500:focus{border-color:var(--color-emerald-500)}.focus\:ring-purple-500:focus{--tw-ring-color:var(--color-purple-500)}@media(min-width:48rem){.md\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}}@media(min-width:64rem){.lg\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}}@media(prefers-color-scheme:dark){.dark\:bg-gray-700{background-color:var(--color-gray-700)}.dark\:text-gray-400{color:var(--color-gray-400)}.dark\:text-white{color:var(--color-white)}.dark\:peer-checked\:bg-blue-500:is(:where(.peer):checked~*){background-color:var(--color-blue-500)}.dark\:peer-focus\:ring-blue-800:is(:where(.peer):focus~*){--tw-ring-color:var(--color-blue-800)}.dark\:after\:border-gray-600:after{content:var(--tw-content);border-color:var(--color-gray-600)}}}@property --tw-rotate-x{syntax:"*";inherits:false}@property --tw-rotate-y{syntax:"*";inherits:false}@property --tw-rotate-z{syntax:"*";inherits:false}@property --tw-skew-x{syntax:"*";inherits:false}@property --tw-skew-y{syntax:"*";inherits:false}@property --tw-space-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-gradient-position{syntax:"*";inherits:false}@property --tw-gradient-from{syntax:"";inherits:false;initial-value:#0000}@property --tw-gradient-via{syntax:"";inherits:false;initial-value:#0000}@property --tw-gradient-to{syntax:"";inherits:false;initial-value:#0000}@property --tw-gradient-stops{syntax:"*";inherits:false}@property --tw-gradient-via-stops{syntax:"*";inherits:false}@property --tw-gradient-from-position{syntax:"";inherits:false;initial-value:0%}@property --tw-gradient-via-position{syntax:"";inherits:false;initial-value:50%}@property --tw-gradient-to-position{syntax:"";inherits:false;initial-value:100%}@property --tw-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-tracking{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-backdrop-blur{syntax:"*";inherits:false}@property --tw-backdrop-brightness{syntax:"*";inherits:false}@property --tw-backdrop-contrast{syntax:"*";inherits:false}@property --tw-backdrop-grayscale{syntax:"*";inherits:false}@property --tw-backdrop-hue-rotate{syntax:"*";inherits:false}@property --tw-backdrop-invert{syntax:"*";inherits:false}@property --tw-backdrop-opacity{syntax:"*";inherits:false}@property --tw-backdrop-saturate{syntax:"*";inherits:false}@property --tw-backdrop-sepia{syntax:"*";inherits:false}@property --tw-duration{syntax:"*";inherits:false}@property --tw-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-z{syntax:"*";inherits:false;initial-value:0}@property --tw-content{syntax:"*";inherits:false;initial-value:""} diff --git a/public/build/core/layout-editor.min.js b/public/build/core/layout-editor.min.js index 24ce0ef6..7f7f3822 100644 --- a/public/build/core/layout-editor.min.js +++ b/public/build/core/layout-editor.min.js @@ -1,13 +1,13 @@ -var OM=Object.defineProperty;var BM=(dn,i,u)=>i in dn?OM(dn,i,{enumerable:!0,configurable:!0,writable:!0,value:u}):dn[i]=u;var Ni=(dn,i,u)=>BM(dn,typeof i!="symbol"?i+"":i,u);(function(dn,i,u,Cn){"use strict";function Lt(){const e=typeof window<"u"?window.G7Core?.__runtime:void 0;if(!e)throw new Error("[layout-editor] window.G7Core.__runtime 이 없습니다. 코어 번들(template-engine.min.js)이 편집기 번들보다 먼저 로드되어야 합니다.");return e}const Mi=Lt();Mi.TranslationProvider;const tt=Mi.useTranslation;Mi.TranslationReactContext;function rt(){return typeof window>"u"?null:window.__g7Devtools??null}function Il(e,t){const n=rt();if(!n?.trackEditorState)return;const o={templateIdentifier:e.templateIdentifier,editMode:e.editMode,selectedRoutePath:e.selectedRoute?.path??null,selectedLayoutName:e.selectedRoute?.layoutName??null,routeTreeSize:e.routeTree.length,isRouteTreeCollapsed:e.isRouteTreeCollapsed,locale:e.locale,previewDevice:e.previewDevice,previewZoom:e.previewZoom,lastAction:t,timestamp:Date.now()};try{n.trackEditorState(o)}catch{}}function Zh(){const e=rt();if(e?.clearEditorStateData)try{e.clearEditorStateData()}catch{}}function Qh(e){const t=rt();if(t?.trackEditorSampleMatch)try{t.trackEditorSampleMatch(e)}catch{}}function Rh(){const e=rt();if(e?.clearEditorSampleData)try{e.clearEditorSampleData()}catch{}}function ey(e){const t=rt();if(t?.trackEditorHistoryEntry)try{t.trackEditorHistoryEntry(e)}catch{}}function ty(){const e=rt();if(e?.clearEditorHistoryData)try{e.clearEditorHistoryData()}catch{}}function Al(e){const t=rt();if(t?.trackEditorSelection)try{t.trackEditorSelection(e)}catch{}}function ny(){const e=rt();if(e?.clearEditorSelectionData)try{e.clearEditorSelectionData()}catch{}}function dr(e){const t=rt();if(t?.trackEditorPropertyPatch)try{t.trackEditorPropertyPatch(e)}catch{}}function $n(e){const t=rt();if(t?.trackEditorDnd)try{t.trackEditorDnd(e)}catch{}}function oy(){const e=rt();if(e?.clearEditorDndData)try{e.clearEditorDndData()}catch{}}function st(e){const t=rt();if(t?.trackEditorDocument)try{t.trackEditorDocument(e)}catch{}}function ry(){const e=rt();if(e?.clearEditorDocumentData)try{e.clearEditorDocumentData()}catch{}}function iy(e){const t=rt();if(t?.trackEditorSpecMerge)try{t.trackEditorSpecMerge(e)}catch{}}function sy(){const e=rt();if(e?.clearEditorSpecMergeData)try{e.clearEditorSpecMergeData()}catch{}}function Li(e){let t=5381;for(let n=0;n>>0;return t}function Et(e){const t=rt();if(t?.trackEditorI18n)try{t.trackEditorI18n(e)}catch{}}function ay(){const e=rt();if(e?.clearEditorI18nData)try{e.clearEditorI18nData()}catch{}}function Tl(e){const t=rt();if(t?.trackPageState)try{t.trackPageState(e)}catch{}}function ly(){const e=rt();if(e?.clearPageStateData)try{e.clearPageStateData()}catch{}}const Dl="g7le.routeTree.collapsed";function dy(){if(typeof window>"u")return!1;try{return window.localStorage?.getItem(Dl)==="1"}catch{return!1}}function cy(e){if(!(typeof window>"u"))try{window.localStorage?.setItem(Dl,e?"1":"0")}catch{}}function uy(e,t){return{templateIdentifier:e,routeTree:[],routesError:null,selectedRoute:null,isRouteTreeCollapsed:dy(),editMode:"route",returnRoute:null,returnEditMode:null,locale:t,previewDevice:"desktop",previewZoom:1,previewCustomWidth:1024,previewColorScheme:"light",history:{canUndo:!1,canRedo:!1},isPaletteOpen:!1,availableStates:[],activeStateId:null,layoutVersions:{},extensionVersions:{}}}function py(e,t){switch(t.type){case"SET_ROUTE_TREE":return{...e,routeTree:t.tree,routesError:null};case"SET_ROUTES_ERROR":return{...e,routesError:t.error};case"SELECT_ROUTE":return{...e,editMode:"route",selectedRoute:t.route,returnRoute:null,returnEditMode:null};case"TOGGLE_ROUTE_TREE":return{...e,isRouteTreeCollapsed:!e.isRouteTreeCollapsed};case"SET_LOCALE":return{...e,locale:t.locale};case"ENTER_BASE_EDIT":return{...e,editMode:"base",returnRoute:t.returnRoute!==void 0?t.returnRoute:e.selectedRoute,selectedRoute:{path:`__base__/${t.layoutName}`,layoutName:t.layoutName}};case"EXIT_BASE_EDIT":return{...e,editMode:"route",selectedRoute:e.returnRoute,returnRoute:null};case"ENTER_MODAL_EDIT":return{...e,editMode:"modal",returnRoute:t.returnRoute!==void 0?t.returnRoute:e.selectedRoute,selectedRoute:{path:`__modal__/${t.modalId}`,layoutName:t.hostLayout}};case"EXIT_MODAL_EDIT":return{...e,editMode:"route",selectedRoute:e.returnRoute,returnRoute:null};case"ENTER_EXTENSION_EDIT":return{...e,editMode:"extension",returnRoute:t.returnRoute!==void 0?t.returnRoute:e.selectedRoute,selectedRoute:{path:`__extension__/${t.extensionId}`,layoutName:t.extensionHost??null}};case"SET_RETURN_ROUTE":return e.editMode==="route"||e.returnRoute!==null?e:{...e,returnRoute:t.route};case"EXIT_EXTENSION_EDIT":return{...e,editMode:"route",selectedRoute:e.returnRoute,returnRoute:null};case"ENTER_ITERATION_ITEM_EDIT":return{...e,editMode:"iteration_item",returnRoute:t.returnRoute!==void 0?t.returnRoute:e.selectedRoute,returnEditMode:e.editMode,selectedRoute:{path:`__iteration__/${t.sourcePath}`,layoutName:t.hostLayout??e.selectedRoute?.layoutName??null}};case"EXIT_ITERATION_ITEM_EDIT":return{...e,editMode:e.returnEditMode??"route",selectedRoute:e.returnRoute,returnRoute:null,returnEditMode:null};case"SET_PREVIEW_DEVICE":return{...e,previewDevice:t.device};case"SET_PREVIEW_ZOOM":return{...e,previewZoom:t.zoom};case"SET_PREVIEW_CUSTOM_WIDTH":return{...e,previewCustomWidth:t.width};case"SET_PREVIEW_COLOR_SCHEME":return{...e,previewColorScheme:t.scheme};case"TOGGLE_PALETTE":return{...e,isPaletteOpen:!e.isPaletteOpen};case"SET_PALETTE_OPEN":return{...e,isPaletteOpen:t.open};case"SET_AVAILABLE_STATES":return{...e,availableStates:t.states,activeStateId:t.activeStateId};case"SET_ACTIVE_STATE":return{...e,activeStateId:t.activeStateId};case"SET_LAYOUT_VERSIONS":return{...e,layoutVersions:t.versions};case"SET_LAYOUT_VERSION":return{...e,layoutVersions:{...e.layoutVersions,[t.layoutName]:t.version}};case"SET_EXTENSION_VERSIONS":return{...e,extensionVersions:t.versions};case"SET_EXTENSION_VERSION":return{...e,extensionVersions:{...e.extensionVersions,[t.extensionId]:t.version}};default:return e}}function fy(e,t){const n=py(e,t);return((n.selectedRoute?.path??null)!==(e.selectedRoute?.path??null)||n.editMode!==e.editMode)&&(n.availableStates.length>0||n.activeStateId!==null)?{...n,availableStates:[],activeStateId:null}:n}const zl=u.createContext(null);function gy(e,t){const n=fy(e,t);return Il(n,t.type),n}function hy({templateIdentifier:e,initialLocale:t,children:n}){const[o,r]=u.useReducer(gy,void 0,()=>{const a=uy(e,t);return Il(a,void 0),a});u.useEffect(()=>{cy(o.isRouteTreeCollapsed)},[o.isRouteTreeCollapsed]);const s=u.useMemo(()=>({state:o,dispatch:r}),[o]);return u.createElement(zl.Provider,{value:s},n)}function Qe(){const e=u.useContext(zl);if(!e)throw new Error("useLayoutEditor must be used within LayoutEditorProvider");return e}const Nl=u.createContext(null);function yy({value:e,children:t}){return u.createElement(Nl.Provider,{value:e},t)}function Ft(){return u.useContext(Nl)}const Pi=u.createContext(null);let by=1;function my({children:e,t}){const[n,o]=u.useState([]),r=u.useRef([]),[s,a]=u.useState({}),l=u.useCallback(y=>{const _=y.id??`editor-modal-${by++}`,w={id:_,content:y.content,ariaLabel:y.ariaLabel??"",closeOnBackdrop:y.closeOnBackdrop??!0,closeOnEscape:y.closeOnEscape??!0,width:y.width??"auto",height:y.height??"auto",maxHeightRatio:y.maxHeightRatio??.85,draggable:y.draggable??!1,avoidRect:y.avoidRect,onClose:y.onClose};return o(k=>{const $=[...k.filter(S=>S.id!==_),w];return r.current=$,$}),_},[]),d=u.useCallback(y=>{o(_=>{const w=_.find(x=>x.id===y),k=_.filter(x=>x.id!==y);return r.current=k,w?.onClose&&queueMicrotask(()=>w.onClose?.()),k})},[]),c=u.useCallback(()=>{const y=r.current[r.current.length-1];y&&d(y.id)},[d]),p=u.useCallback(()=>{o(y=>{for(let _=y.length-1;_>=0;_--){const w=y[_];w?.onClose&&queueMicrotask(()=>w.onClose?.())}return r.current=[],[]})},[]),f=u.useCallback((y,_)=>{a(w=>{const k=r.current.find($=>$.id===y),x=_??k?.ariaLabel??"";return{...w,[y]:x}})},[]),b=u.useCallback(y=>{a(_=>{if(!(y in _))return _;const w={..._};return delete w[y],w})},[]),g=u.useCallback(y=>{b(y),d(y)},[b,d]),m=u.useCallback(()=>{a({}),p()},[p]),h=u.useMemo(()=>Object.keys(s),[s]),v=u.useMemo(()=>({open:l,close:g,closeTop:c,closeAll:m,stack:n,minimize:f,restore:b,minimizedStack:h}),[l,g,c,m,n,f,b,h]);return u.createElement(Pi.Provider,{value:v},e,u.createElement(_y,{minimized:s,onRestore:b,t,key:"__min_bar"}))}function bo(){const e=u.useContext(Pi);if(!e)throw new Error("useEditorModal must be used within EditorModalProvider");return e}function vy(){const e=u.useContext(Pi);if(!e)return null;const{stack:t,close:n,minimizedStack:o}=e,r=u.useMemo(()=>new Set(o),[o]);return u.useEffect(()=>{if(t.length===0)return;const s=a=>{if(a.key!=="Escape"&&a.key!=="Esc")return;const l=t.filter(c=>!r.has(c.id)),d=l[l.length-1];d?.closeOnEscape&&(a.preventDefault(),a.stopPropagation(),n(d.id))};return window.addEventListener("keydown",s,!0),()=>window.removeEventListener("keydown",s,!0)},[t,n,r]),t.length===0?null:i.jsx(i.Fragment,{children:t.map((s,a)=>i.jsx(wy,{entry:s,depth:a,hidden:r.has(s.id),onClose:()=>n(s.id)},s.id))})}function _y({minimized:e,onRestore:t,t:n}){const o=n??(s=>s),r=Object.keys(e);return r.length===0?null:i.jsx("div",{className:"g7le-minimized-bar","data-testid":"g7le-minimized-bar",style:{position:"fixed",left:16,bottom:16,display:"flex",gap:8,zIndex:9300},children:r.map(s=>i.jsxs("button",{type:"button","data-testid":`g7le-minimized-${s}`,onClick:()=>t(s),style:{display:"flex",alignItems:"center",gap:6,padding:"8px 12px",background:"#1e293b",color:"#fff",border:"none",borderRadius:8,boxShadow:"0 8px 24px rgba(15, 23, 42, 0.3)",cursor:"pointer",fontSize:13},children:[i.jsx("span",{"aria-hidden":"true",children:"🗗"}),i.jsx("span",{children:e[s]||o("layout_editor.modal.minimized_default")}),i.jsx("span",{style:{opacity:.7},children:o("layout_editor.modal.restore")})]},s))})}function xy(e,t,n){if(!e||typeof window>"u")return null;const o=window.innerWidth,r=window.innerHeight,s=16,a=e.left,l=o-(e.left+e.width),d=t+s;let c;if(l>=a&&l>=d)c=e.left+e.width+s;else if(a>=d)c=Math.max(s,e.left-s-t);else return null;c=Math.min(Math.max(s,c),Math.max(s,o-t-s));let p=e.top;return p=Math.min(Math.max(s,p),Math.max(s,r-n-s)),{left:c,top:p}}const Ml="clamp(16px, 6vh, 56px)";function wy({entry:e,depth:t,hidden:n,onClose:o}){const r=9100+t*10,s=e.maxHeightRatio>0?`${e.maxHeightRatio*100}vh`:void 0,a=e.draggable?s:`calc(100vh - (${Ml}) * 2)`,[l,d]=u.useState(null),c=u.useRef(null),p=u.useRef(null);u.useEffect(()=>{if(!e.draggable)return;const v=c.current;if(!v)return;const y=v.getBoundingClientRect(),_=xy(e.avoidRect,y.width,y.height);_&&d(_)},[e.id,e.draggable]);const f=u.useCallback(v=>{if(!e.draggable)return;const y=v.target;if(!y.closest("[data-modal-drag-handle]")||y.closest("button, a, input, textarea, select"))return;const w=c.current,k=w?.getBoundingClientRect(),x=l?.left??k?.left??0,$=l?.top??k?.top??0;p.current={startX:v.clientX,startY:v.clientY,baseLeft:x,baseTop:$};const S=T=>{const E=p.current;if(!E)return;const A=window.innerWidth,N=window.innerHeight,D=w?.offsetWidth??0,B=w?.offsetHeight??0;let M=E.baseLeft+(T.clientX-E.startX),O=E.baseTop+(T.clientY-E.startY);M=Math.min(Math.max(0,M),Math.max(0,A-D)),O=Math.min(Math.max(0,O),Math.max(0,N-B)),d({left:M,top:O})},C=()=>{p.current=null,window.removeEventListener("pointermove",S),window.removeEventListener("pointerup",C)};window.addEventListener("pointermove",S),window.addEventListener("pointerup",C)},[e.draggable,l]),b=v=>{e.closeOnBackdrop&&v.target===v.currentTarget&&o()},g=e.draggable&&l!==null,m=e.draggable?{position:"fixed",inset:0,background:"transparent",pointerEvents:"none",zIndex:r,...n?{display:"none"}:{}}:{position:"fixed",inset:0,background:"rgba(15, 23, 42, 0.45)",display:n?"none":"flex",alignItems:"flex-start",justifyContent:"center",paddingTop:Ml,boxSizing:"border-box",zIndex:r},h={background:"#fff",borderRadius:10,boxShadow:"0 20px 60px rgba(15, 23, 42, 0.25)",width:e.width,height:e.height,maxHeight:a,maxWidth:"92vw",overflow:"hidden",display:"flex",flexDirection:"column",...e.draggable?{position:"absolute",pointerEvents:"auto",left:g?l.left:"50%",top:g?l.top:"50%",transform:g?void 0:"translate(-50%, -50%)"}:{}};return i.jsx("div",{className:"g7le-modal-backdrop",role:"presentation","data-testid":`g7le-modal-backdrop-${e.id}`,"data-draggable":e.draggable?"true":"false",onClick:b,style:m,children:i.jsx("div",{ref:c,className:"g7le-modal",role:"dialog","aria-modal":"true","aria-label":e.ariaLabel||void 0,"data-testid":`g7le-modal-${e.id}`,"data-positioned":g?"true":"false",onClick:v=>v.stopPropagation(),onPointerDown:e.draggable?f:void 0,style:h,children:e.content})})}function ht({baseStyle:e,hoverStyle:t,disabled:n,style:o,children:r,...s}){const[a,l]=u.useState(!1),d={outline:"none",...e,...o,...a&&!n?t:{}};return i.jsx("button",{type:"button",disabled:n,onMouseEnter:()=>l(!0),onMouseLeave:()=>l(!1),style:d,...s,children:r})}function ky({templateIdentifier:e,templateName:t,templateVersion:n,templateList:o,onSwitch:r}){const{t:s}=tt(),[a,l]=u.useState(!1),[d,c]=u.useState(!1),p=u.useRef(null);u.useEffect(()=>{if(!a)return;const v=_=>{p.current&&!p.current.contains(_.target)&&l(!1)},y=_=>{_.key==="Escape"&&l(!1)};return document.addEventListener("mousedown",v),document.addEventListener("keydown",y),()=>{document.removeEventListener("mousedown",v),document.removeEventListener("keydown",y)}},[a]);const f=o??[],b=f.some(v=>v.identifier!==e),g=!!r&&b,m=t||e,h=v=>{if(v===e){l(!1);return}l(!1),r?.(v)};return i.jsxs("div",{ref:p,className:"g7le-toolbar__template-switcher",style:{position:"relative",display:"inline-flex"},children:[i.jsxs("button",{type:"button",className:"g7le-toolbar__template","data-testid":"g7le-toolbar-template-label","data-can-switch":g?"true":"false","aria-haspopup":g?"listbox":void 0,"aria-expanded":g?a:void 0,disabled:!g,onClick:()=>g&&l(v=>!v),onMouseEnter:()=>c(!0),onMouseLeave:()=>c(!1),title:g?s("layout_editor.chrome.toolbar.switch_template"):m,style:{display:"inline-flex",alignItems:"center",gap:6,padding:"4px 8px",fontSize:13,fontWeight:600,color:"#0f172a",letterSpacing:.1,borderRadius:6,borderWidth:1,borderStyle:"solid",borderColor:g&&(d||a)?"#cbd5e1":"transparent",backgroundColor:g&&(d||a)?"#f1f5f9":"transparent",cursor:g?"pointer":"default",outline:"none",transition:"background-color 120ms, border-color 120ms"},children:[i.jsx("span",{className:"g7le-toolbar__template-name","data-testid":"g7le-toolbar-template-name",children:m}),n&&i.jsxs("span",{className:"g7le-toolbar__template-version","data-testid":"g7le-toolbar-template-version",style:{fontSize:11,fontWeight:500,color:"#64748b",padding:"1px 6px",borderRadius:999,background:"#f1f5f9",border:"1px solid #e2e8f0"},children:["v",n]}),g&&i.jsx("span",{className:"g7le-toolbar__template-caret","aria-hidden":"true",style:{fontSize:9,color:"#94a3b8",marginLeft:1},children:"▼"})]}),g&&a&&i.jsx("div",{className:"g7le-toolbar__template-menu","data-testid":"g7le-toolbar-template-menu",role:"listbox",style:{position:"absolute",top:"calc(100% + 4px)",left:0,minWidth:240,maxWidth:360,maxHeight:360,overflowY:"auto",background:"#ffffff",border:"1px solid #e2e8f0",borderRadius:8,boxShadow:"0 8px 24px rgba(15, 23, 42, 0.14)",zIndex:30,padding:4},children:f.map(v=>{const y=v.identifier===e;return i.jsxs("button",{type:"button",role:"option","aria-selected":y,"aria-current":y?"true":void 0,className:"g7le-toolbar__template-menu-item","data-testid":"g7le-toolbar-template-menu-item","data-identifier":v.identifier,"data-current":y?"true":"false",onClick:()=>h(v.identifier),style:{display:"flex",alignItems:"center",justifyContent:"space-between",gap:8,width:"100%",padding:"8px 10px",fontSize:13,color:"#1e293b",background:y?"#eff6ff":"transparent",border:"none",borderRadius:6,cursor:y?"default":"pointer",textAlign:"left",outline:"none"},onMouseEnter:_=>{y||(_.currentTarget.style.background="#f1f5f9")},onMouseLeave:_=>{_.currentTarget.style.background=y?"#eff6ff":"transparent"},children:[i.jsxs("span",{style:{display:"flex",alignItems:"center",gap:8,minWidth:0},children:[i.jsx("span",{"aria-hidden":"true",style:{width:12,flex:"0 0 auto",color:"#2563eb",fontSize:12,textAlign:"center"},children:y?"✓":""}),i.jsxs("span",{style:{display:"flex",flexDirection:"column",gap:2,minWidth:0},children:[i.jsx("span",{style:{fontWeight:600,color:y?"#1d4ed8":"#1e293b",overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap"},children:v.name||v.identifier}),i.jsx("span",{style:{fontSize:11,color:"#94a3b8",overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap"},children:v.identifier})]})]}),i.jsxs("span",{style:{display:"inline-flex",alignItems:"center",gap:6,flex:"0 0 auto"},children:[i.jsx("span",{style:{fontSize:10,fontWeight:600,color:v.type==="admin"?"#7c3aed":"#0891b2",textTransform:"uppercase",letterSpacing:.4},children:v.type}),v.version&&i.jsxs("span",{style:{fontSize:11,color:"#64748b"},children:["v",v.version]})]})]},v.identifier)})})]})}function Sy(e={}){const{onSave:t,isDirty:n=!1,onExit:o,onEditCode:r,onManageImages:s,onManageTranslations:a,onPageSettings:l,onReset:d,onShowShortcuts:c,onPreview:p,onShowVersions:f,templateName:b,templateVersion:g,templateList:m,onSwitchTemplate:h,onToggleCustomAssets:v,customAssetsDisabled:y=!1,onManageCustomAssets:_}=e,[w,k]=u.useState(!1),[x,$]=u.useState(!1),{t:S}=tt(),{state:C,dispatch:T}=Qe(),[E,A]=u.useState(null);u.useEffect(()=>{const K=()=>{const Z=window.__g7LayoutEditorHistory;A(Z?{undo:Z.undo,redo:Z.redo,canUndo:!!Z.canUndo,canRedo:!!Z.canRedo}:null)};K();const X=window.setInterval(K,250);return()=>window.clearInterval(X)},[]);const N=()=>{T({type:"TOGGLE_ROUTE_TREE"})},D=()=>C.editMode==="base"?S("layout_editor.chrome.toolbar.mode_badge.base"):C.editMode==="modal"?S("layout_editor.chrome.toolbar.mode_badge.modal"):C.editMode==="extension"?S("layout_editor.chrome.toolbar.mode_badge.extension"):C.editMode==="iteration_item"?S("layout_editor.chrome.toolbar.mode_badge.iteration_item"):null,B=()=>{C.editMode==="base"?T({type:"EXIT_BASE_EDIT"}):C.editMode==="modal"?T({type:"EXIT_MODAL_EDIT"}):C.editMode==="extension"?T({type:"EXIT_EXTENSION_EDIT"}):C.editMode==="iteration_item"&&T({type:"EXIT_ITERATION_ITEM_EDIT"})},M=D(),O=C.editMode!=="route",z={display:"inline-flex",alignItems:"center",gap:6,padding:"6px 12px",fontSize:13,fontWeight:500,borderRadius:6,borderWidth:1,borderStyle:"solid",borderColor:"transparent",backgroundColor:"transparent",color:"#475569",cursor:"pointer",transition:"background-color 120ms, color 120ms, border-color 120ms"},H={...z,backgroundColor:"#2563eb",color:"#ffffff",borderColor:"#2563eb",fontWeight:600},P={backgroundColor:"#fef3c7",borderColor:"#f59e0b",color:"#92400e",fontWeight:600},I={...z,borderColor:"#e2e8f0",backgroundColor:"#ffffff",color:"#0f172a"},L={backgroundColor:"#f1f5f9",color:"#0f172a",borderColor:"#cbd5e1"},F={backgroundColor:"#1d4ed8",borderColor:"#1d4ed8"},Y={backgroundColor:"#f8fafc",borderColor:"#cbd5e1"};return i.jsxs("div",{className:"g7le-toolbar","data-testid":"g7le-toolbar",role:"toolbar",style:{display:"flex",alignItems:"center",gap:6,padding:"8px 12px",borderBottom:"1px solid #e2e8f0",background:"#ffffff",boxShadow:"0 1px 2px rgba(15, 23, 42, 0.04)"},children:[i.jsx("style",{children:"@keyframes g7le-spin { to { transform: rotate(360deg); } }.g7le-toolbar > * { flex-shrink: 0; white-space: nowrap; }"}),i.jsx("button",{type:"button",className:"g7le-toolbar__route-tree-toggle","data-testid":"g7le-toolbar-toggle-route-tree","aria-label":S("layout_editor.chrome.toolbar.toggle_route_tree"),title:S("layout_editor.chrome.toolbar.toggle_route_tree"),onClick:N,style:{...z,outline:"none",padding:"6px 10px",fontSize:16,lineHeight:1,color:"#64748b"},children:"☰"}),i.jsx(ky,{templateIdentifier:C.templateIdentifier,templateName:b,templateVersion:g,templateList:m,onSwitch:h}),M&&i.jsx("span",{className:"g7le-toolbar__mode-badge","data-testid":"g7le-toolbar-mode-badge","data-mode":C.editMode,style:{padding:"2px 8px",borderRadius:999,background:"#fef3c7",color:"#92400e",fontSize:11,fontWeight:600,border:"1px solid #fde68a"},children:M}),i.jsx("div",{style:{flex:1}}),i.jsxs(i.Fragment,{children:[i.jsx(ht,{"data-testid":"g7le-toolbar-undo","aria-label":S("layout_editor.chrome.toolbar.undo"),title:S("layout_editor.chrome.toolbar.undo"),disabled:!E?.canUndo,onClick:()=>E?.undo?.(),baseStyle:z,hoverStyle:L,style:{opacity:E?.canUndo?1:.4},children:"↶"}),i.jsx(ht,{"data-testid":"g7le-toolbar-redo","aria-label":S("layout_editor.chrome.toolbar.redo"),title:S("layout_editor.chrome.toolbar.redo"),disabled:!E?.canRedo,onClick:()=>E?.redo?.(),baseStyle:z,hoverStyle:L,style:{opacity:E?.canRedo?1:.4},children:"↷"}),i.jsxs(ht,{"data-testid":"g7le-toolbar-add-element","data-open":C.isPaletteOpen?"true":"false",disabled:!C.selectedRoute,onClick:()=>T({type:"TOGGLE_PALETTE"}),"aria-pressed":C.isPaletteOpen,baseStyle:z,hoverStyle:L,style:C.isPaletteOpen?{backgroundColor:"#eff6ff",color:"#1d4ed8",borderColor:"#93c5fd"}:void 0,children:["+ ",S("layout_editor.chrome.toolbar.add_element")]}),i.jsx(ht,{"data-testid":"g7le-toolbar-preview","data-previewing":x?"true":"false",disabled:!p||!C.selectedRoute?.layoutName||x,onClick:()=>{const K=p?.();K&&typeof K.then=="function"&&($(!0),K.finally(()=>$(!1)))},baseStyle:z,hoverStyle:L,style:{opacity:x?.7:1},children:x?i.jsxs(i.Fragment,{children:[i.jsx("span",{"data-testid":"g7le-toolbar-preview-spinner","aria-hidden":"true",style:{display:"inline-block",width:12,height:12,border:"2px solid rgba(71,85,105,0.35)",borderTopColor:"#475569",borderRadius:"50%",animation:"g7le-spin 600ms linear infinite"}}),S("layout_editor.chrome.toolbar.previewing")]}):i.jsxs(i.Fragment,{children:["👁 ",S("layout_editor.chrome.toolbar.preview")]})}),i.jsxs(ht,{"data-testid":"g7le-toolbar-versions",disabled:!f||!(C.selectedRoute?.layoutName||C.selectedRoute?.path?.startsWith("__extension__/")),onClick:()=>f?.(),"aria-label":S("layout_editor.chrome.toolbar.version_history"),title:S("layout_editor.chrome.toolbar.version_history"),baseStyle:z,hoverStyle:L,children:["🕘 ",S("layout_editor.chrome.toolbar.version_history")]}),i.jsxs(ht,{"data-testid":"g7le-toolbar-edit-code",disabled:!r,onClick:()=>r?.(),baseStyle:z,hoverStyle:L,children:["⟨/⟩ ",S("layout_editor.chrome.toolbar.edit_code")]}),i.jsxs(ht,{"data-testid":"g7le-toolbar-images",disabled:!s||!C.selectedRoute,onClick:()=>s?.(),baseStyle:z,hoverStyle:L,children:["🖼 ",S("layout_editor.chrome.toolbar.images")]}),i.jsxs(ht,{"data-testid":"g7le-toolbar-translations",disabled:!a||!C.selectedRoute,onClick:()=>a?.(),baseStyle:z,hoverStyle:L,children:["🌐 ",S("layout_editor.chrome.toolbar.translations")]}),i.jsxs(ht,{"data-testid":"g7le-toolbar-custom-assets",disabled:!_,onClick:()=>_?.(),title:S("layout_editor.chrome.toolbar.custom_assets_hint"),baseStyle:z,hoverStyle:L,children:["🎨 ",S("layout_editor.chrome.toolbar.custom_assets")]}),i.jsxs(ht,{"data-testid":"g7le-toolbar-toggle-custom-assets","data-custom-disabled":y?"true":"false",disabled:!v,onClick:()=>v?.(),"aria-pressed":y,title:S(y?"layout_editor.chrome.toolbar.custom_assets_on_hint":"layout_editor.chrome.toolbar.custom_assets_off_hint"),baseStyle:z,hoverStyle:L,style:y?{...z,...P}:void 0,children:[y?"👁":"🚫"," ",S(y?"layout_editor.chrome.toolbar.custom_assets_on":"layout_editor.chrome.toolbar.custom_assets_off")]}),i.jsxs(ht,{"data-testid":"g7le-toolbar-page-settings",disabled:!l||!C.selectedRoute||C.editMode==="extension"||C.editMode==="iteration_item",onClick:()=>l?.(),baseStyle:z,hoverStyle:L,children:["⚙ ",S("layout_editor.chrome.toolbar.page_settings")]}),i.jsxs(ht,{"data-testid":"g7le-toolbar-reset",disabled:!d||!n,onClick:()=>d?.(),"aria-label":S("layout_editor.chrome.toolbar.reset"),title:S("layout_editor.chrome.toolbar.reset"),baseStyle:z,hoverStyle:L,style:{opacity:n?1:.4},children:["↺ ",S("layout_editor.chrome.toolbar.reset")]}),i.jsx(ht,{"data-testid":"g7le-toolbar-save","data-dirty":n?"true":"false","data-saving":w?"true":"false",disabled:!t||w,onClick:()=>{const K=t?.();K&&typeof K.then=="function"&&(k(!0),K.finally(()=>k(!1)))},baseStyle:H,hoverStyle:F,style:{opacity:w?.7:n?1:.7},children:w?i.jsxs(i.Fragment,{children:[i.jsx("span",{"data-testid":"g7le-toolbar-save-spinner","aria-hidden":"true",className:"g7le-toolbar__spinner",style:{display:"inline-block",width:12,height:12,border:"2px solid rgba(255,255,255,0.4)",borderTopColor:"#ffffff",borderRadius:"50%",animation:"g7le-spin 600ms linear infinite"}}),S("layout_editor.chrome.toolbar.saving")]}):i.jsxs(i.Fragment,{children:["💾 ",S("layout_editor.chrome.toolbar.save")]})}),i.jsx(ht,{"data-testid":"g7le-toolbar-shortcuts",disabled:!c,onClick:()=>c?.(),"aria-label":S("layout_editor.shortcuts.title"),title:S("layout_editor.shortcuts.title"),baseStyle:z,hoverStyle:L,children:"⌨"}),i.jsxs(ht,{"data-testid":"g7le-toolbar-exit",disabled:!o,onClick:()=>o?.(),baseStyle:I,hoverStyle:Y,children:["✕ ",S("layout_editor.chrome.toolbar.exit")]})]}),O&&i.jsxs(ht,{"data-testid":"g7le-toolbar-exit-alt-mode",onClick:B,baseStyle:I,hoverStyle:Y,children:["← ",C.editMode==="base"&&S("layout_editor.chrome.toolbar.exit_base_edit"),C.editMode==="modal"&&S("layout_editor.chrome.toolbar.exit_modal_edit"),C.editMode==="extension"&&S("layout_editor.chrome.toolbar.exit_extension_edit"),C.editMode==="iteration_item"&&S("layout_editor.chrome.toolbar.exit_iteration_item_edit")]})]})}function cr(){if(typeof window>"u")return null;try{return window.localStorage?.getItem("auth_token")??null}catch{return null}}function Ne(e){const t={Accept:"application/json",...e??{}},n=cr();return n&&(t.Authorization=`Bearer ${n}`),t}function Oi(e){return encodeURIComponent(e)}async function Ll(e,t){const n=`/api/admin/templates/${Oi(e)}/layout-attachments?layout_name=${encodeURIComponent(t)}`;try{const o=await fetch(n,{method:"GET",headers:Ne(),credentials:"same-origin"}),r=await o.json().catch(()=>null);return!o.ok||!r?.success?{ok:!1,status:o.status,message:Bi(r,o.status)}:{ok:!0,data:Array.isArray(r?.data)?r.data:[]}}catch(o){return{ok:!1,status:0,message:o instanceof Error?o.message:"network error"}}}async function Pl(e,t,n){const o=`/api/admin/templates/${Oi(e)}/layout-attachments`,r=new FormData;r.append("file",n),r.append("layout_name",t);try{const s=await fetch(o,{method:"POST",headers:Ne(),body:r,credentials:"same-origin"}),a=await s.json().catch(()=>null);return!s.ok||!a?.success||!a?.data?.url?{ok:!1,status:s.status,message:Bi(a,s.status)}:{ok:!0,data:a.data}}catch(s){return{ok:!1,status:0,message:s instanceof Error?s.message:"network error"}}}async function Ol(e){const t=`/api/admin/templates/layout-attachments/${Oi(String(e))}`;try{const n=await fetch(t,{method:"DELETE",headers:Ne(),credentials:"same-origin"}),o=await n.json().catch(()=>null);return!n.ok||o&&o.success===!1?{ok:!1,status:n.status,message:Bi(o,n.status)}:{ok:!0,data:!0}}catch(n){return{ok:!1,status:0,message:n instanceof Error?n.message:"network error"}}}function Bi(e,t){if(e&&typeof e=="object"&&"message"in e){const n=e.message;if(typeof n=="string"&&n.length>0)return n}return`HTTP ${t}`}function Cy(e){return e<1024?`${e} B`:e<1024*1024?`${Math.round(e/1024)} KB`:`${(e/(1024*1024)).toFixed(1)} MB`}function $y(e){if(!e)return"";const t=e.match(/^(\d{4})-(\d{2})-(\d{2})/);return t?`${t[2]}-${t[3]}`:""}function Bl({templateIdentifier:e,layoutName:t,t:n,onSelect:o,onChanged:r,onClose:s}){const[a,l]=u.useState([]),[d,c]=u.useState("idle"),[p,f]=u.useState(!1),[b,g]=u.useState(null),m=u.useRef(null),h=u.useCallback(async()=>{c("loading"),g(null);const _=await Ll(e,t);_.ok?(l(_.data),c("idle")):(g(_.message),c("error"))},[e,t]);u.useEffect(()=>{h()},[h]);const v=async _=>{const w=_.target.files?.[0];if(!w)return;f(!0),g(null);const k=await Pl(e,t,w);k.ok?(l(x=>[k.data,...x.filter($=>$.id!==k.data.id)]),r?.()):g(k.message),f(!1),m.current&&(m.current.value="")},y=async _=>{if(!(typeof window<"u"&&typeof window.confirm=="function"?window.confirm(n("layout_editor.attachment_manager.delete_confirm")):!0))return;const k=await Ol(_.id);k.ok?(l(x=>x.filter($=>$.id!==_.id)),r?.()):g(k.message)};return i.jsxs("div",{className:"g7le-attachment-manager","data-testid":"g7le-attachment-manager",style:Ey,children:[i.jsxs("div",{"data-modal-drag-handle":!0,style:Iy,children:[i.jsx("span",{style:{fontSize:14,fontWeight:700},"data-testid":"g7le-attachment-manager-title",children:n("layout_editor.attachment_manager.title")}),i.jsx("button",{type:"button","aria-label":"close",onClick:s,style:Ay,"data-testid":"g7le-attachment-manager-close",children:"✕"})]}),i.jsx("div",{style:Ty,children:i.jsxs("label",{style:Dy,"data-testid":"g7le-attachment-upload",children:[p?"…":`⬆ ${n("layout_editor.attachment_manager.upload")}`,i.jsx("input",{ref:m,type:"file",accept:"image/*",onChange:v,disabled:p,style:{display:"none"},"data-testid":"g7le-attachment-upload-input"})]})}),b&&i.jsx("div",{style:zy,"data-testid":"g7le-attachment-manager-error",children:b}),i.jsx("div",{style:Ny,children:d==="loading"?i.jsx("div",{style:Wl,"data-testid":"g7le-attachment-manager-loading",children:n("layout_editor.attachment_manager.loading")}):a.length===0&&d!=="error"?i.jsx("div",{style:Wl,"data-testid":"g7le-attachment-manager-empty",children:n("layout_editor.attachment_manager.empty")}):i.jsx("div",{style:My,"data-testid":"g7le-attachment-grid",children:a.map(_=>i.jsxs("div",{style:Ly,"data-testid":`g7le-attachment-card-${_.id}`,children:[i.jsx("div",{style:{...Py,backgroundImage:`url(${_.url})`}}),i.jsx("div",{style:Oy,title:_.original_name,children:_.original_name}),i.jsxs("div",{style:By,children:[Cy(_.size),_.created_at?` · ${$y(_.created_at)}`:""]}),i.jsxs("div",{style:Wy,children:[o&&i.jsx("button",{type:"button",onClick:()=>o(_.url),style:jy,"data-testid":`g7le-attachment-use-${_.id}`,children:n("layout_editor.attachment_manager.use_as_bg")}),i.jsx("button",{type:"button",onClick:()=>y(_),style:Hy,"data-testid":`g7le-attachment-delete-${_.id}`,children:"🗑"})]})]},_.id))})}),i.jsx("div",{style:Fy,children:i.jsx("button",{type:"button",onClick:s,style:Ky,"data-testid":"g7le-attachment-manager-footer-close",children:n("layout_editor.attachment_manager.close")})})]})}const Ey={display:"flex",flexDirection:"column",minHeight:0,flex:1},Iy={display:"flex",alignItems:"center",justifyContent:"space-between",padding:"12px 16px",borderBottom:"1px solid #e2e8f0"},Ay={border:"none",background:"transparent",fontSize:16,color:"#94a3b8",cursor:"pointer"},Ty={padding:"10px 16px",borderBottom:"1px solid #f1f5f9",display:"flex",gap:8},Dy={padding:"6px 12px",fontSize:13,border:"1px solid #2563eb",borderRadius:6,background:"#fff",color:"#2563eb",cursor:"pointer",display:"inline-flex",alignItems:"center"},zy={padding:"8px 16px",fontSize:12,color:"#b91c1c",background:"#fef2f2"},Ny={padding:16,overflow:"auto",flex:1,minHeight:120},Wl={fontSize:13,color:"#94a3b8",textAlign:"center",padding:"32px 0"},My={display:"grid",gridTemplateColumns:"repeat(auto-fill, minmax(140px, 1fr))",gap:12},Ly={display:"flex",flexDirection:"column",gap:4,border:"1px solid #e2e8f0",borderRadius:8,padding:8},Py={width:"100%",height:88,borderRadius:6,border:"1px solid #cbd5e1",backgroundSize:"cover",backgroundPosition:"center",backgroundColor:"#f8fafc"},Oy={fontSize:12,fontWeight:600,color:"#0f172a",whiteSpace:"nowrap",overflow:"hidden",textOverflow:"ellipsis"},By={fontSize:11,color:"#64748b"},Wy={display:"flex",gap:6,marginTop:2},jy={flex:1,padding:"4px 6px",fontSize:11,border:"1px solid #2563eb",borderRadius:6,background:"#2563eb",color:"#fff",cursor:"pointer"},Hy={padding:"4px 8px",fontSize:12,border:"1px solid #fecaca",borderRadius:6,background:"#fff",color:"#dc2626",cursor:"pointer"},Fy={padding:"10px 16px",borderTop:"1px solid #e2e8f0",display:"flex",justifyContent:"flex-end"},Ky={padding:"6px 14px",fontSize:13,border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",color:"#475569",cursor:"pointer"};function ur(e){return encodeURIComponent(e)}async function Vy(e,t,n){const o=new URLSearchParams;t&&o.set("layout_name",t);const r=o.toString(),s=`/api/admin/templates/${ur(e)}/custom-translations`+(r?`?${r}`:"");try{const a=await fetch(s,{method:"GET",headers:Ne(),credentials:"same-origin"}),l=await a.json().catch(()=>null);return!a.ok||!l?.success?{ok:!1,status:a.status,message:pr(l,a.status)}:{ok:!0,data:Array.isArray(l?.data)?l.data:[]}}catch(a){return{ok:!1,status:0,message:a instanceof Error?a.message:"network error"}}}async function Uy(e,t,n,o){const r=`/api/admin/templates/${ur(e)}/custom-translations/${t}`;try{const s=await fetch(r,{method:"PUT",headers:Ne({"Content-Type":"application/json"}),body:JSON.stringify({values:n,expected_lock_version:o}),credentials:"same-origin"}),a=await s.json().catch(()=>null);return!s.ok||!a?.success||!a?.data?{ok:!1,status:s.status,message:pr(a,s.status)}:{ok:!0,data:a.data}}catch(s){return{ok:!1,status:0,message:s instanceof Error?s.message:"network error"}}}async function qy(e,t){const n=`/api/admin/templates/${ur(e)}/custom-translations/${t}`;try{const o=await fetch(n,{method:"DELETE",headers:Ne(),credentials:"same-origin"}),r=await o.json().catch(()=>null);return!o.ok||r&&r.success===!1?{ok:!1,status:o.status,message:pr(r,o.status)}:{ok:!0,data:!0}}catch(o){return{ok:!1,status:0,message:o instanceof Error?o.message:"network error"}}}async function Gy(e,t){const n=`/api/admin/templates/${ur(e)}/custom-translations`;try{const o=await fetch(n,{method:"DELETE",headers:Ne({"Content-Type":"application/json"}),body:JSON.stringify({ids:t}),credentials:"same-origin"}),r=await o.json().catch(()=>null);return!o.ok||!r?.success?{ok:!1,status:o.status,message:pr(r,o.status)}:{ok:!0,data:{deleted:typeof r?.data?.deleted=="number"?r.data.deleted:t.length}}}catch(o){return{ok:!1,status:0,message:o instanceof Error?o.message:"network error"}}}const jl=/\$t:(custom\.[A-Za-z0-9_\-]+\.\d+)/g;function Yy(e){const t=new Set,n=o=>{if(typeof o=="string"){if(o.includes("$t:custom.")){let r;for(jl.lastIndex=0;(r=jl.exec(o))!==null;)t.add(r[1])}return}if(Array.isArray(o)){for(const r of o)n(r);return}if(o&&typeof o=="object")for(const r of Object.values(o))n(r)};return n(e),t}function pr(e,t){if(e&&typeof e=="object"&&"message"in e){const n=e.message;if(typeof n=="string"&&n.length>0)return n}return`HTTP ${t}`}const Pt=Lt().TranslationEngine,mo=Lt().createLogger,Wi=mo("EditorSpecLoader"),Xy=["controls","componentCapabilities","actionRecipes","conditionRecipes","pageSettings","initActionRecipes","errorRecipes","computedRecipes","loadingComponents"],Jy=["actionRecipes","initActionRecipes","errorRecipes","computedRecipes","loadingComponents"];function Hl(e){if(typeof window>"u")return[];const n=window.G7Config?.[e];if(!Array.isArray(n))return[];const o=[];for(const r of n){const s=r?.identifier;typeof s=="string"&&s.length>0&&o.push(s)}return o}async function Zy(e,t,n,o){const s=`${n}/${e==="template"?"templates":e==="module"?"modules":"plugins"}/${encodeURIComponent(t)}/editor-spec`;try{const a=await o(s);if(!a.ok)return a.status!==404&&Wi.warn(`fetchOneSpec: HTTP ${a.status}`,s),null;const l=await a.json();return!l?.success||!l.data?null:l.data.spec??null}catch(a){return Wi.warn("fetchOneSpec: fetch failed",s,a),null}}function Qy(e){return Jy.includes(e)}function Ry(e,t){return e==null||typeof e!="object"?e:{...e,__source:t}}function eb(e,t,n,o){const r=t[n];if(!r||typeof r!="object")return;const s=e[n]??{};if(o&&Qy(n)){const a={};for(const[l,d]of Object.entries(r))a[l]=Ry(d,o);e[n]={...s,...a};return}e[n]={...s,...r}}function tb(e,t){if(!t.nesting)return;const n=e.nesting??{},o=new Set(n.draggable??[]);for(const r of t.nesting.draggable??[])o.add(r);n.draggable=Array.from(o),n.containers={...n.containers??{},...t.nesting.containers??{}},e.nesting=n}function nb(e,t){if(!t.componentPalette)return;const n=e.componentPalette??{},o=[...n.groups??[],...t.componentPalette.groups??[]];n.groups=o,n.entries={...n.entries??{},...t.componentPalette.entries??{}},e.componentPalette=n}function ob(e,t){if(!t.states)return;const n=e.states??{},o=[...n.groups??[],...t.states.groups??[]];n.groups=o,e.states=n}function rb(e,t){if(!Array.isArray(t.stateLabels)||t.stateLabels.length===0)return;const n=[...e.stateLabels??[],...t.stateLabels],o=new Map;for(const r of n)!r||typeof r.key!="string"||typeof r.scope!="string"||o.set(`${r.scope}:${r.key}`,r);e.stateLabels=Array.from(o.values())}function ib(e,t){if(!t.actionChipCandidates)return;const n=e.actionChipCandidates??{};for(const o of["response","error","payload"]){const r=t.actionChipCandidates[o];!Array.isArray(r)||r.length===0||(n[o]=[...n[o]??[],...r])}e.actionChipCandidates=n}function sb(e,t){if(!t.sampleData)return;const n=e.sampleData??{};n.byDataSourceId={...n.byDataSourceId??{},...t.sampleData.byDataSourceId??{}},n.byEndpointPattern={...n.byEndpointPattern??{},...t.sampleData.byEndpointPattern??{}},e.sampleData=n}function Fl(e,t,n){for(const o of Xy)eb(e,t,o,n);tb(e,t),nb(e,t),ob(e,t),rb(e,t),ib(e,t),sb(e,t),t.version&&(e.version=t.version)}async function ab(e){const t=e.apiBaseUrl??"/api",n=e.fetcher??(typeof fetch<"u"?fetch:void 0);if(!n)return Wi.warn("loadEditorSpecBundle: fetch unavailable, returning null spec"),{spec:null,sampleGlobalSources:[]};const o=e.activeModuleIdentifiers??Hl("activeModules"),r=e.activePluginIdentifiers??Hl("activePlugins"),s=[...o.map(p=>({kind:"module",id:p})),...r.map(p=>({kind:"plugin",id:p})),{kind:"template",id:e.templateIdentifier}],a=await Promise.all(s.map(p=>Zy(p.kind,p.id,t,n)));let l=null;const d=[],c={};if(e.coreSeed&&(l={},Fl(l,e.coreSeed,{kind:"core"})),s.forEach((p,f)=>{const b=a[f];if(!b)return;b.sampleGlobal&&typeof b.sampleGlobal=="object"&&d.push({id:p.id,kind:p.kind,sampleGlobal:b.sampleGlobal});const g=b.sampleData?.byDataSourceId;if(g&&typeof g=="object"){const m=p.kind==="template"?"template":`${p.kind}:${p.id}`;c[m]={...c[m]??{},...g}}l===null&&(l={}),Fl(l,b,{kind:p.kind,id:p.id})}),l!==null&&Object.keys(c).length>0){const p=l;p.sampleData={...p.sampleData??{},bySource:c}}return{spec:l,sampleGlobalSources:d}}function ji(e,t){return e?.componentCapabilities?.[t]??null}function Ct(e,t){return e?.controls?.[t]??null}function lb(e){return e?.initActionRecipes??{}}function db(e){return e?.computedRecipes??{}}function cb(e){return e?.errorRecipes??{}}function ub(e){const t=e?.loadingComponents;return!t||typeof t!="object"?[]:Object.values(t).filter(n=>n!=null&&typeof n=="object"&&typeof n.name=="string")}function pb(e,t,n){const o=e?.stateLabels;if(!Array.isArray(o))return null;for(const r of o)if(r?.scope===t&&r?.key===n)return typeof r.label_key=="string"?r.label_key:null;return null}function Hi(e){return e!==null&&typeof e=="object"&&!Array.isArray(e)}function fb(e){return e===null?"null":typeof e=="string"?e.length>40?`${e.slice(0,40)}…`:e:typeof e=="number"||typeof e=="boolean"?String(e):""}function gb(e){const t=e.find(n=>Hi(n));if(Hi(t))return Object.keys(t).slice(0,8)}function hb(e){return Object.keys(e).slice(0,8)}function yb(e){const t=Object.entries(e);return t.length===0?!1:t.some(([,n])=>n===null||typeof n!="object")}function Fi(e,t,n,o,r){if(Array.isArray(e)){r(t,"array",`[${e.length}]`,gb(e));return}if(Hi(e)){if(t&&yb(e)){const s=hb(e);r(t,"object",`{${s.length}}`,s)}if(n>=o)return;for(const[s,a]of Object.entries(e)){const l=t?`${t}.${s}`:s;Fi(a,l,n+1,o,r)}return}r(t,"scalar",fb(e))}function bb(e,t,n){const o=[];let r=e;for(const s of t){if(!r||(r=(Array.isArray(r.children)?r.children:[])[s],!r))break;const l=r.iteration;if(!(!l||l.source===void 0)&&(typeof l.index_var=="string"&&l.index_var.length>0&&o.push({name:l.index_var,kind:"index"}),typeof l.item_var=="string"&&l.item_var.length>0)){const d=typeof l.source=="string"?l.source:"",c=d&&n?.get(Kl(d))||[];o.push({name:l.item_var,kind:"item",itemFields:c})}}return o}function Kl(e){return e.replace(/^\{\{|\}\}$/g,"").trim()}function mb(e){const t=new Map;for(const n of e)n.shape==="array"&&n.itemFields&&n.itemFields.length>0&&t.set(Kl(n.expression),n.itemFields);return t}function Ki(e){const t=e.maxDepth??4,n=[];for(const o of e.dataSources??[])!o||typeof o.id!="string"||o.id.length===0||Fi(o.sample,"",0,t,(r,s,a,l)=>{const d=r?`{{${o.id}.${r}}}`:`{{${o.id}}}`;n.push({expression:d,source:"data_source",sourceId:o.id,path:r,shape:s,groupLabelKey:o.labelKey?.startsWith("$t:")?o.labelKey.slice(3):o.labelKey,preview:a,itemFields:l})});for(const o of e.states??[])!o||typeof o.scope!="string"||Fi(o.tree,"",0,t,(r,s,a,l)=>{if(!r)return;const d=pb(e.spec,o.scope,r)??void 0;n.push({expression:`{{${o.scope}.${r}}}`,source:o.scope,sourceId:o.scope,path:r,shape:s,labelKey:d?.startsWith("$t:")?d.slice(3):d,preview:a,itemFields:l})});for(const o of e.iterationVars??[])if(!(!o||typeof o.name!="string"||o.name.length===0)){if(o.kind==="index"){n.push({expression:`{{${o.name}}}`,source:"iteration",sourceId:o.name,path:"",shape:"scalar",preview:"0"});continue}for(const r of o.itemFields??[])n.push({expression:`{{${o.name}.${r}}}`,source:"iteration",sourceId:o.name,path:r,shape:"scalar",preview:""})}return n}const vb=["$localized"];function _b(e){const t=e.trim(),n=t.indexOf("(");if(n===-1||!t.endsWith(")"))return{fn:null,inner:t};const o=t.slice(0,n).trim();if(!vb.includes(o))return{fn:null,inner:t};const r=t.slice(n+1,-1);let s=0,a=null;for(let l=0;ln.shape===t)}function qi(e,t){const n=t.trim().toLowerCase();return n===""?e:e.filter(o=>[o.resolvedLabel??"",o.sourceId,o.path,o.preview].join(" ").toLowerCase().includes(n))}function Vl(e,t,n){const o={response:[{path:"data",labelKey:"layout_editor.action_chip.response_data",shape:"object"},{path:"message",labelKey:"layout_editor.action_chip.response_message",shape:"scalar"}],error:[{path:"status",labelKey:"layout_editor.action_chip.error_status",shape:"scalar"},{path:"message",labelKey:"layout_editor.action_chip.error_message",shape:"scalar"},{path:"errors",labelKey:"layout_editor.action_chip.error_errors",shape:"object"},{path:"data",labelKey:"layout_editor.action_chip.error_data",shape:"object"},{path:"statusText",labelKey:"layout_editor.action_chip.error_status_text",shape:"scalar"}],payload:[{path:"data",labelKey:"layout_editor.action_chip.payload_data",shape:"object"}]},r=e==="response"?"response":e==="error"?"error":"message",s=new Set,a=[];for(const d of o[e])s.has(d.path)||(s.add(d.path),a.push(d));const l=n?.[e];if(Array.isArray(l))for(const d of l)!d||typeof d.path!="string"||typeof d.labelKey!="string"||s.has(d.path)||(s.add(d.path),a.push({path:d.path,labelKey:d.labelKey,shape:d.shape??"scalar"}));return a.map(d=>({expression:`{{${r}.${d.path}}}`,source:"_local",sourceId:r,path:d.path,shape:d.shape,labelKey:d.labelKey,preview:t(d.labelKey)}))}const Gi=/\{\{(?:[^}]|\}(?!\}))*\}\}/g;function In(e){const t=[];if(typeof e!="string"||e.length===0)return t;let n=0,o=0;const r=new RegExp(Gi.source,"g");let s;for(;(s=r.exec(e))!==null;){const a=s.index,l=a+s[0].length;a>n&&t.push({kind:"literal",raw:e.slice(n,a),start:n,end:a}),t.push({kind:"binding",raw:s[0],start:a,end:l,parsed:Kt(s[0]),bindingIndex:o++}),n=l}return nt.kind==="binding")}function Yi(e){return typeof e!="string"?!1:new RegExp(Gi.source).test(e)}function wb(e){const t=new Set(e.map(o=>o.trim()));let n=0;for(;t.has(`p${n}`);)n+=1;return`p${n}`}function Ul(e,t,n){const o=typeof e=="string"?e:"",r=Math.max(0,Math.min(t,o.length)),s=o.slice(0,r),a=o.slice(r),l=s.length>0&&!/\s$/.test(s)?" ":"",d=a.length>0&&!/^\s/.test(a)?" ":"";return`${s}${l}{${n}}${d}${a}`}function Xi(e,t){const n=typeof e=="string"?e:"";return An(n).includes(t)?n:n.length===0?`{${t}}`:/\s$/.test(n)?`${n}{${t}}`:`${n} {${t}}`}function ql(e){return fr(e).map(t=>({bindingIndex:t.bindingIndex??0,expression:t.raw,parsed:t.parsed??null,isComplex:(t.parsed??null)===null}))}function gr(e){return typeof e!="string"?"":e.replace(new RegExp(Gi.source,"g")," ").replace(/\s+/g," ").trim()}const kb=/\$t:([a-zA-Z0-9._-]+)/g,Gl="custom.";function Ji(e,t){return typeof e!="string"||e.length===0?typeof e=="string"?e:"":t?In(e).map(n=>n.kind!=="literal"?n.raw:n.raw.replace(kb,(o,r)=>{if(r.startsWith(Gl))return o;const s=t(r);return s&&s!==r?s:o})).join(""):e}const Kn="(?:(?!\\|[a-zA-Z0-9_]+=).)*",Sb=new RegExp(`^\\s*\\$t:([a-zA-Z0-9._-]+)((?:\\|[a-zA-Z0-9_]+=${Kn})*)`),Cb=new RegExp(`\\|([a-zA-Z0-9_]+)=(${Kn})`,"g");function Vt(e){if(typeof e!="string")return"";const t=Kt(e);if(t)return t.path||t.sourceId;const n=e.trim().replace(/^\{\{\s*/,"").replace(/\s*\}\}$/,"");let o=-1;for(let l=0;l0){const l=n.slice(0,o).trim(),d=Kt(`{{${l}}}`);if(d)return d.path||d.sourceId;const c=l.replace(/\?\./g,".").replace(/\s*\?\?.*$/,"").trim();if(/^[A-Za-z_$][\w$.]*$/.test(c)){const p=c.split(".").filter(Boolean);return p.length>1&&p.slice(1).join(".")||c}}const r=n.replace(/\s+/g," ").trim(),s=r.replace(/\s*(\?\?|\|\|)\s*(\[\s*\]|\{\s*\}|'[^']*'|"[^"]*"|\d+|null|undefined)\s*$/,"").trim(),a=/\.([A-Za-z_$][\w$]*)\s*$/.exec(s);return a?a[1]:r}const Yl=/\$(?:core|module|plugin)_settings:[a-zA-Z0-9._:-]+/g;function Xl(e){return typeof e=="string"&&new RegExp(Yl.source).test(e)}function $b(e){return typeof e!="string"?"":e.replace(/^\$(?:core|module|plugin)_settings:/,"").split(/[.:]/).filter(Boolean).pop()||e}function Jl(e){const t=[];for(const n of In(e)){if(n.kind==="binding"){t.push({kind:"chip",label:Vt(n.raw)});continue}let o=0;const r=n.raw,s=new RegExp(Yl.source,"g");let a;for(;(a=s.exec(r))!==null;)a.index>o&&t.push({kind:"text",label:r.slice(o,a.index)}),t.push({kind:"chip",label:$b(a[0])}),o=a.index+a[0].length;o{const m=`p${o.length}`;return o.push(g),r[m]=Vt(g),`{${m}}`};let a=e,l="";const d=Sb.exec(e);if(d&&!d[1].startsWith(Gl)){const g=d[1],m=d[2]??"",h=e.slice(d[0].length),v=new Map,y=new RegExp(Cb.source,"g");let _;for(;(_=y.exec(m))!==null;)_[2]&&_[2].includes("{{")&&v.set(_[1],_[2]);const w=t?t(g):"";if(w&&w!==g&&!w.startsWith("$t:"))l=w.replace(/\{\{?([a-zA-Z0-9_]+)\}?\}/g,(k,x)=>{const $=v.get(x);return $?s($):k});else return n;a=h}const c=Ji(a,t),p=In(c).map(g=>g.kind==="binding"?s(g.raw):g.raw).join(""),f=`${l}${p}`;return/\$t:[a-zA-Z0-9._-]+/.test(f)||o.length===0?n:{chipValue:f,bindings:o,paramLabels:r,keyifiable:!0}}function Zl(e,t){return t.bindings.length?e+t.bindings.map((n,o)=>`|p${o}=${n}`).join(""):e}function Ql(e,t){const n=fr(t).map(r=>r.raw);if(n.length===0)return e;const o=n.map((r,s)=>`|p${s}=${r}`).join("");return`${e}${o}`}function Rl(e){if(typeof e!="string")return"";const t=In(e);let n=0;return t.map(o=>o.kind==="binding"?`{p${n++}}`:o.raw).join("")}const ed=new RegExp(`^\\s*\\$t:([a-zA-Z0-9._-]+)((?:\\|[a-zA-Z0-9_]+=${Kn})+)\\s*$`),Ib=new RegExp(`\\|([a-zA-Z0-9_]+)=(${Kn})`,"g");function vt(e){if(typeof e!="string")return null;const t=ed.exec(e);if(!t)return null;const n=t[1],o=t[2],r=[],s=new RegExp(Ib.source,"g");let a;for(;(a=s.exec(o))!==null;){const l=a[1],d=a[2];r.push({name:l,expression:d,parsed:Kt(d)})}return{key:n,params:r}}function td(e){return typeof e=="string"&&ed.test(e)}function An(e){if(typeof e!="string")return[];const t=[],n=/\{\{?(p\d+)\}?\}/g;let o;for(;(o=n.exec(e))!==null;)t.push(o[1]);return t.sort()}function Ab(e,t,n){if(typeof e!="string")return e;const o=new RegExp(`(\\|${t}=)${Kn}`);return o.test(e)?e.replace(o,`$1${n}`):e}function vo(e,t){if(typeof e!="string")return e;const n=new RegExp(`\\|${t}=${Kn}`);return e.replace(n,"")}function nd(e,t){return typeof e!="string"?e:e.replace(new RegExp(`\\s*\\{\\{?${t}\\}?\\}\\s*`,"g")," ").replace(/\s+/g," ").trim()}function Tb(e,t){const n=s=>fr(typeof s=="string"?s:"").map(a=>a.raw.replace(/\s+/g,"")).sort(),o=n(e),r=n(t);return o.length!==r.length?!1:o.every((s,a)=>s===r[a])}function od(e){return e.__source?.kind==="base"}function rd(e){return e.__source?.kind==="partial"}function Db(e){return e.__source?.kind==="route"}function hr(e,t){return e.__source?.kind!=="extension"?!1:t==null?!0:e.__source.extensionId!==t}function id(e){return e.type==="extension_point"}function Qi(e=[]){for(const t of e)if(t.iteration&&t.iteration.source!==void 0)return!0;return!1}function zb(e,t=[]){for(const n of t)if(n.iteration&&n.iteration.source!==void 0)return!0;return e.iteration&&e.iteration.source!==void 0?!0:sd(e)}function sd(e){if(typeof e.text=="string"&&ad(e.text)&&!Nb(e.text))return!0;if(e.props&&typeof e.props=="object"){for(const t of Object.values(e.props))if(typeof t=="string"&&ad(t))return!0}return!1}function ad(e){return/\{\{[\s\S]*?\}\}/.test(e)}function Nb(e){const t=vt(e);return t!==null&&t.key.startsWith("custom.")}function Mb(e,t){return e??t??null}function Ri(e,t,n){if(id(e))return!0;switch(t){case"route":return od(e)||rd(e)||hr(e);case"base":return hr(e);case"modal":return hr(e);case"extension":return hr(e,n)?!0:od(e)||Db(e)||rd(e);default:return!1}}function et(e){return typeof e=="object"&&e!==null&&typeof e.responsive=="string"}function yr(e,t){return et(e)?et(t)&&e.responsive===t.responsive:!et(t)&&e===t}function at(e){const t=[];let n=!1;for(const o of e){if(et(o)){t.push("responsive",o.responsive),n=!0;continue}n?t.push("children",String(o)):(t.push(String(o)),n=!0)}return t.join(".")}function Qt(e){return Array.isArray(e.children)?e.children:[]}function br(e,t){const n=e.responsive?.[t];return n&&Array.isArray(n.children)?n.children:[]}function Be(e,t){if(!e)return null;if(t.length===0)return e;let n=e,o=Qt(e);for(const r of t){if(et(r)){o=br(n,r.responsive);continue}if(r<0||r>=o.length||(n=o[r],!n))return null;o=Qt(n)}return n}function Lb(e,t,n){if(!e||t.length===0)return t;let o=e,r=Qt(e),s;for(let a=0;a=2?t[t.length-2]:void 0;if(r!==void 0&&et(r)){const a=Be(e,t.slice(0,-2));return a?{owner:a,children:br(a,r.responsive),branchKey:r.responsive,index:o}:null}const s=Be(e,t.slice(0,-1));return s?{owner:s,children:Qt(s),index:o}:null}function es(e,t){if(e.branchKey!==void 0){const n={...e.owner.responsive??{}};n[e.branchKey]={...n[e.branchKey]??{},children:t},e.owner.responsive=n}else e.owner.children=t}function Rt(e){const t={...e};t.props&&typeof t.props=="object"&&(t.props={...t.props});const n=Qt(e);if(n.length>0&&(t.children=n.map(Rt)),e.responsive&&typeof e.responsive=="object"){const o={};for(const r of Object.keys(e.responsive)){const s=e.responsive[r];if(!s||typeof s!="object"){o[r]=s;continue}const a={...s};Array.isArray(s.children)&&(a.children=s.children.map(Rt)),a.props&&typeof a.props=="object"&&(a.props={...a.props}),o[r]=a}t.responsive=o}return t}function ts(e,t,n,o){const r=Rt(e),s=t.length>0?t[t.length-1]:void 0;if(s!==void 0&&et(s)){const p=Be(r,t.slice(0,-1));if(!p)return r;const f=br(p,s.responsive),b=Math.max(0,Math.min(n,f.length)),g=[...f];return g.splice(b,0,o),es({owner:p,branchKey:s.responsive},g),r}const a=Be(r,t);if(!a)return r;const l=Qt(a),d=Math.max(0,Math.min(n,l.length)),c=[...l];return c.splice(d,0,o),a.children=c,r}function ns(e,t){if(t.length===0)return e;const n=Rt(e),o=ld(n,t);if(!o||o.index<0||o.index>=o.children.length)return n;const r=[...o.children];return r.splice(o.index,1),es(o,r),n}function Pb(e,t,n,o){if(t.length===0||!n||et(t[t.length-1]))return e;const r=Rt(e),s=Be(r,t);if(!s)return r;const a=s.responsive&&typeof s.responsive=="object"?s.responsive[n]:void 0;if(a&&Array.isArray(a.children))return r;const l=o&&s.responsive&&typeof s.responsive=="object"?s.responsive[o]:void 0,c=(l&&Array.isArray(l.children)?l.children:Qt(s)).map(Rt),p={...s.responsive??{}};return p[n]={...p[n]??{},children:c},s.responsive=p,r}function Ob(e,t,n){if(t.length===0||!n||et(t[t.length-1]))return e;const o=Rt(e),r=Be(o,t);if(!r||!r.responsive||typeof r.responsive!="object")return o;const s=r.responsive[n];if(!s||!Array.isArray(s.children))return o;const a={...r.responsive},l={...a[n]};return delete l.children,Object.keys(l).length===0?delete a[n]:a[n]=l,r.responsive=Object.keys(a).length>0?a:void 0,o}function Bb(e,t,n,o){if(t.length===0)return e;const r=Be(e,t);if(!r||Wb(t,n))return e;const s=Rt(r),a=ns(e,t),l=dd(n,t);let d=o;const c=t.slice(0,-1);if(cd(c,n)){const p=t[t.length-1];(typeof p=="number"?p:0)o){const s=[...e];return s[n]=r-1,s}return e}function cd(e,t){if(e.length!==t.length)return!1;for(let n=0;ne.length||cd(e,t)}function os(e){const t={...e};if(t.props&&typeof t.props=="object"&&(t.props={...t.props}),typeof t.id=="string"&&t.id.length>0){const o=typeof t.name=="string"?t.name:"node";t.id=`${o}_${jb()}`}const n=Qt(e);return n.length>0&&(t.children=n.map(os)),t}function Ot(e,t,n){if(t.length===0)return n(e);const o=Rt(e),r=Be(o,t);if(!r)return o;const s=n(r),a=ld(o,t);if(!a||a.index<0||a.index>=a.children.length)return o;const l=[...a.children];return l[a.index]=s,es(a,l),o}function jb(){return Math.floor(Math.random()*2821109907456).toString(36).padStart(8,"0")}function Vn(e){if(!Array.isArray(e)||e.length===0)return[];const t=[];for(const n of e){const o=Hb(n);o.length>0&&t.push(...o)}return t}function Hb(e){if(!e||typeof e!="object")return[];const t=e.__source,n=e._fromBase===!0,o=t?.kind;if(o==="extension"||o==="partial")return[];if(o==="base"||n){const s=Array.isArray(e.children)?e.children:[];return Vn(s)}const r={...e};return delete r.__source,delete r._fromBase,delete r.__injectedProps,Array.isArray(r.children)&&(r.children=Vn(r.children)),[r]}function _o(e){if(!e||typeof e!="object")return e;const t=e.__editor,n=t&&typeof t=="object"&&!Array.isArray(t)?t.original:void 0,o=n&&typeof n=="object"&&!Array.isArray(n)?n:void 0,r=Array.isArray(e.components)?e.components:[],s=r.length>0,a=Vn(r),l=o?{...o}:{...e};if(typeof l.extends=="string"&&l.extends.length>0){delete l.components;const c=l.slots&&typeof l.slots=="object"&&!Array.isArray(l.slots)?l.slots:{},p=Object.keys(c);if(!s&&p.length>0){const f={};for(const[b,g]of Object.entries(c))f[b]=Array.isArray(g)?Vn(g):g;l.slots=f}else if(p.length===1)l.slots={[p[0]]:a};else if(p.length===0)l.slots={content:a};else{const f={};for(const[b,g]of Object.entries(c))f[b]=Array.isArray(g)?Vn(g):g;l.slots=f}}else l.components=a,delete l.slots;return delete l.lock_version,delete l.__editor,l}const ud="extension",Un="extensionless";function Fb(){const e=globalThis?.G7Config;if(globalThis?.__g7AssetUrlMode===Un)return Un;const n=e?.assetUrlMode;return n===Un||n===ud?n:e?.settings?.general?.asset_url_mode===Un?Un:ud}function Kb(){return Fb()===Un}function en(e,t,n,o){const r=e.replace(/\/+$/,""),s=t.replace(/^\.+/,""),a=Kb()?r:`${r}.${s}`,l=[];return o&&l.push(o.replace(/^[?&]+/,"")),n!=null&&n!==""&&l.push(`v=${n}`),l.length>0?`${a}?${l.join("&")}`:a}const pd=/^\s*\$t:([a-zA-Z0-9._-]+)\s*$/,mr="custom.",Vb=/\{\{.*?\}\}/;function Ub(e){return!!(!e||e.iteration&&typeof e.iteration=="object")}function fd(e,t){const n=e?.text,o=typeof n=="string"?n:"";if(Ub(e)||typeof n!="string")return{sourceState:"plain_text",editable:!1,customKey:null,displayValue:o};const r=vt(o);if(r&&r.key.startsWith(mr)){const a=t?t(r.key):r.key;return{sourceState:"custom_key",editable:!0,customKey:r.key,displayValue:a,isParamKey:!0}}const s=pd.exec(o);if(s){const a=s[1];if(a.startsWith(mr)){const d=t?t(a):a;return{sourceState:"custom_key",editable:!0,customKey:a,displayValue:d}}const l=t?t(a):"";return{sourceState:"plain_text",editable:!0,customKey:null,displayValue:l&&l!==a?l:""}}if(Vb.test(o)){const a=gr(o),l=pd.exec(a);if(l&&l[1].startsWith(mr)){const f=l[1],b=t?t(f):f;return{sourceState:"custom_key",editable:!0,customKey:f,displayValue:b}}const d=Ji(o,t),c=gr(d),p=/\$t:[a-zA-Z0-9._-]+/.test(c);if(c.length>0){const f=Zi(o,t);return{sourceState:"plain_with_binding",editable:!0,customKey:null,displayValue:p?"":c,chipValue:f.keyifiable?f.chipValue:void 0,chipParamLabels:f.keyifiable?f.paramLabels:void 0}}return{sourceState:"binding_expression",editable:!1,customKey:null,displayValue:o}}return{sourceState:"plain_text",editable:!0,customKey:null,displayValue:o}}function qb(){const{state:e}=Qe(),t=Ft(),n=e.templateIdentifier,o=e.locale,r=e.selectedRoute?.layoutName??null,s=u.useCallback(p=>{try{const b=Pt.getInstance().translate(p,{templateId:n,locale:o});return b&&b!==p?b:""}catch{return""}},[n,o]),a=u.useCallback(p=>fd(p,s),[s]),l=u.useCallback(p=>{Et({op:"inline_edit_cancel",componentPath:p?at(p):null,timestamp:Date.now()})},[]),d=u.useCallback(async(p,f)=>{if(!t)return{kind:"error",message:"no document context"};const g={children:t.document?.raw?.components??[]},m=Be(g,p),h=fd(m,s);if(!h.editable)return Et({op:"inline_edit_blocked_binding",sourceState:h.sourceState,componentPath:at(p),timestamp:Date.now()}),{kind:"blocked"};if(h.customKey){if(f===h.displayValue)return{kind:"noop",translationKey:h.customKey};xo(n,o,h.customKey,f);const S=await vr(n,h.customKey,o,f);return S.kind==="error"?{kind:"error",message:S.message}:(await tn(n,o),Et({op:"inline_edit_update_value",sourceState:"custom_key",componentPath:at(p),translationKey:h.customKey,toLocale:o,valueLength:f.length,valueHash:Li(f),timestamp:Date.now()}),{kind:"updated",translationKey:h.customKey})}const v=typeof m?.text=="string"?m.text:"",y=h.sourceState==="plain_with_binding",_=Ji(v,s),w=y?Rl(_):f,k=S=>y?Ql(S,v):S;t.patchLayout(S=>Ot({children:S},p,E=>({...E,text:f})).children??[]);const x=await qn(n,r,o,w);if(x.kind==="error"||!x.resource)return{kind:"error",message:x.message};const $=x.resource.translation_key;return xo(n,o,$,w),t.patchLayout(S=>Ot({children:S},p,E=>({...E,text:k(`$t:${$}`)})).children??[]),await tn(n,o),Et({op:"inline_edit_create_key",sourceState:h.sourceState,componentPath:at(p),translationKey:$,toLocale:o,valueLength:f.length,valueHash:Li(f),timestamp:Date.now()}),{kind:"created",translationKey:$,id:x.resource.id}},[t,n,o,r,s]),c=u.useCallback(async(p,f)=>{if(!t)return{kind:"error",message:"no document context"};const g={children:t.document?.raw?.components??[]},m=Be(g,p),h=typeof m?.text=="string"?m.text:"",v=vt(h);if(v&&v.key.startsWith(mr))return _r(n,v.key,o,f),xo(n,o,v.key,f),t.markDirty?.(),{kind:"updated",translationKey:v.key};const y=Zi(h,s),_=gr(f.replace(/\{\{?p\d+\}?\}/g," ")).replace(/\s+/g," ").trim(),w=await qn(n,r,o,_);if(w.kind==="error"||!w.resource)return{kind:"error",message:w.message};const k=w.resource.translation_key,x=An(f),$=w.resource.values??{},S={[o]:f};for(const C of Object.keys($)){if(C===o)continue;let T=$[C]??"";for(const E of x)T=Xi(T,E);S[C]=T}return wo(n,k,S),xo(n,o,k,f),t.patchLayout(C=>Ot({children:C},p,A=>({...A,text:Zl(`$t:${k}`,y)})).children??[]),t.markDirty?.(),await tn(n,o),xo(n,o,k,f),Et({op:"inline_edit_create_key",sourceState:"plain_with_binding",componentPath:at(p),translationKey:k,toLocale:o,valueLength:f.length,valueHash:Li(f),timestamp:Date.now()}),{kind:"created",translationKey:k}},[t,n,o,r]);return{classify:a,commit:d,trackCancel:l,keyifyChipValue:c}}async function qn(e,t,n,o){const r=`/api/admin/templates/${encodeURIComponent(e)}/custom-translations`;try{const s=await fetch(r,{method:"POST",credentials:"same-origin",headers:Ne({"Content-Type":"application/json"}),body:JSON.stringify({layout_name:t,locale:n,value:o})}),a=await s.json().catch(()=>null);if(!s.ok)return{kind:"error",message:a?.message??`HTTP ${s.status}`};const l=a?.data;return!l||typeof l.translation_key!="string"?{kind:"error",message:"invalid response"}:{kind:"ok",resource:l}}catch(s){return{kind:"error",message:s instanceof Error?s.message:"network error"}}}async function vr(e,t,n,o){const r=await cn(e,t);if(!r)return{kind:"error",message:"custom key not found"};const s=`/api/admin/templates/${encodeURIComponent(e)}/custom-translations/${r.id}`,a={...r.values??{},[n]:o};try{const l=await fetch(s,{method:"PUT",credentials:"same-origin",headers:Ne({"Content-Type":"application/json"}),body:JSON.stringify({values:a,expected_lock_version:r.lock_version})}),d=await l.json().catch(()=>null);return l.status===409?{kind:"error",message:"concurrent_modification"}:l.ok?{kind:"ok"}:{kind:"error",message:d?.message??`HTTP ${l.status}`}}catch(l){return{kind:"error",message:l instanceof Error?l.message:"network error"}}}async function cn(e,t){const n=`/api/admin/templates/${encodeURIComponent(e)}/custom-translations`;try{const o=await fetch(n,{method:"GET",credentials:"same-origin",headers:Ne()});if(!o.ok)return null;const s=(await o.json().catch(()=>null))?.data;return Array.isArray(s)?s.find(a=>a.translation_key===t)??null:null}catch{return null}}function xo(e,t,n,o){try{Pt.getInstance().setTranslationValue(e,t,n,o)}catch{}if(typeof window<"u")try{window.dispatchEvent(new CustomEvent(kt,{detail:{templateIdentifier:e,locale:t}}))}catch{}}const kt="g7le:translations-refreshed";async function tn(e,t){if(typeof window>"u")return;const n=t??window.__g7EditorContentLocale;if(n){try{const o=Pt.getInstance();try{const r=await fetch(en(`/api/templates/${encodeURIComponent(e)}/config`,"json"),{headers:{Accept:"application/json"},credentials:"same-origin"}).then(a=>a.ok?a.json():null),s=r?.data?.cache_version??r?.cache_version;if(typeof s=="number"&&s>0){o.setCacheVersion(s);const a=window;a.G7Config&&(a.G7Config.cache_version=s)}}catch{}await o.loadTranslations(e,n,"/api",!0)}catch{}try{window.dispatchEvent(new CustomEvent(kt,{detail:{templateIdentifier:e,locale:n}}))}catch{}}}const nn=new Map;function _r(e,t,n,o){const r=nn.get(t)??{};r[n]=o,nn.set(t,r);try{Pt.getInstance().setTranslationValue(e,n,t,o)}catch{}if(typeof window<"u")try{window.dispatchEvent(new CustomEvent(kt,{detail:{templateIdentifier:e,locale:n}}))}catch{}}function wo(e,t,n){for(const[o,r]of Object.entries(n))_r(e,t,o,r)}function It(e,t){return nn.get(e)?.[t]}function Gb(e){const t=nn.get(e);return t?{...t}:void 0}function Yb(){return nn.size>0}function gd(e,t){for(const[n,o]of nn.entries())for(const[r,s]of Object.entries(o))try{e.setTranslationValue(t,r,n,s)}catch{}}async function Xb(e){if(nn.size===0)return{ok:0,failed:[]};const t=Array.from(nn.entries());let n=0;const o=[];for(const[r,s]of t)try{const a=await cn(e,r);if(!a){o.push(r);continue}const l={...a.values??{},...s},d=`/api/admin/templates/${encodeURIComponent(e)}/custom-translations/${a.id}`;if(!(await fetch(d,{method:"PUT",credentials:"same-origin",headers:Ne({"Content-Type":"application/json"}),body:JSON.stringify({values:l,expected_lock_version:a.lock_version})})).ok){o.push(r);continue}n+=1}catch{o.push(r)}for(const[r]of t)o.includes(r)||nn.delete(r);try{await tn(e)}catch{}return{ok:n,failed:o}}function Gn(e,t){const n=`layout_editor.locale.${e}`,o=t(n);return o&&o!==n?o:e.toUpperCase()}function hd(){if(typeof window>"u")return[];const t=window.G7Config?.appConfig?.supportedLocales;return Array.isArray(t)?t.filter(n=>typeof n=="string"&&n.length>0):[]}function Jb({locales:e}={}){const{t}=tt(),{state:n,dispatch:o}=Qe();u.useEffect(()=>{window.__g7EditorContentLocale=n.locale},[n.locale]);const r=e??hd(),s=r.includes(n.locale)?r:[n.locale,...r];if(s.length<=1)return i.jsxs("span",{className:"g7le-locale-switcher g7le-locale-switcher--single","data-testid":"g7le-locale-switcher-single",style:{fontSize:11,color:"#475569",padding:"0 4px"},children:[t("layout_editor.chrome.toolbar.locale_switcher_label"),": ",Gn(n.locale,t)]});const a=l=>{if(l===n.locale)return;const d=n.locale;o({type:"SET_LOCALE",locale:l}),Et({op:"locale_switch",fromLocale:d,toLocale:l,timestamp:Date.now()})};return i.jsxs("div",{className:"g7le-locale-switcher","data-testid":"g7le-locale-switcher",role:"group","aria-label":t("layout_editor.chrome.toolbar.locale_switcher_label"),style:{display:"inline-flex",alignItems:"center",gap:4},children:[i.jsx("span",{style:{fontSize:11,color:"#475569"},children:t("layout_editor.chrome.toolbar.locale_switcher_label")}),i.jsx("div",{style:{display:"inline-flex",gap:2},children:s.map(l=>{const d=l===n.locale;return i.jsx("button",{type:"button","data-testid":`g7le-locale-${l}`,"aria-pressed":d,onClick:()=>a(l),style:{padding:"2px 8px",fontSize:12,cursor:"pointer",background:d?"#0f172a":"#f8fafc",color:d?"#fff":"#0f172a",border:"1px solid #cbd5e1",borderRadius:4},children:Gn(l,t)},l)})})]})}const Zb=/\{\{?(p\d+)\}?\}/g;function Qb(e){const t=[];if(typeof e!="string"||e.length===0)return t;const n=new RegExp(Zb.source,"g");let o=0,r;for(;(r=n.exec(e))!==null;){const s=r.index,a=s+r[0].length;s>o&&t.push({kind:"text",raw:e.slice(o,s),start:o,end:s}),t.push({kind:"chip",raw:r[0],paramName:r[1],start:s,end:a}),o=a}return o{n.push({kind:"text",text:a}),r=!0};for(const a of t)a.kind==="text"?(o=a.raw,s(o),o=""):(r||s(""),n.push({kind:"chip",paramName:a.paramName}),r=!1);return r||s(""),n}function em(e,t,n,o){const r=[];return e.forEach((s,a)=>{if(s.kind==="chip"){if(s.paramName===t)return;r.push(`{${s.paramName}}`)}else{const l=s.text??"";if(a===n){const d=Math.max(0,Math.min(o,l.length));r.push(l.slice(0,d),`{${t}}`,l.slice(d))}else r.push(l)}}),r.join("")}function xr({value:e,onChange:t,t:n,paramLabels:o,onRequestInsert:r,onRemoveChip:s,testIdSuffix:a="pc",disabled:l=!1,hideInsertButton:d=!1,caretRef:c}){const p=u.useMemo(()=>Rb(e),[e]),f=u.useRef(null),[b,g]=u.useState(null),[m,h]=u.useState(null),[v,y]=u.useState(null),_=u.useMemo(()=>p.map(O=>O.kind==="chip"?`c:${O.paramName}`:"t").join("|"),[p]),w=u.useRef(null),k=u.useRef([]),x=u.useRef(!1);u.useLayoutEffect(()=>{const O=x.current;x.current=!1,!(!O&&w.current===_)&&(w.current=_,p.forEach((z,H)=>{if(z.kind!=="text")return;const P=k.current[H];P&&P.textContent!==(z.text??"")&&(P.textContent=z.text??"")}))},[_,p,e]);const $=()=>{const O=f.current;if(!O)return e;const z=[];for(const H of Array.from(O.querySelectorAll("[data-seg]")))H.dataset.seg==="chip"?z.push(`{${H.dataset.param??""}}`):z.push(H.textContent??"");return z.join("")},S=()=>{l||t($())},C=u.useRef(null),T=u.useRef(!1),E=(O,z)=>{if(!l){O.preventDefault(),O.stopPropagation(),C.current={param:z,pointerId:O.pointerId},T.current=!1,g(z);try{O.currentTarget.setPointerCapture(O.pointerId)}catch{}}},A=O=>{const z=C.current;if(!z||O.pointerId!==z.pointerId)return;T.current=!0;const H=B(O.clientX,O.clientY);H&&(h({slotIdx:H.slotIdx,offset:H.offset}),y(nm(f.current,H.slotIdx,H.offset)))},N=(O,z)=>{if(O.stopPropagation(),T.current){T.current=!1;return}s?.(z)},D=O=>{const z=C.current;if(!z||O.pointerId!==z.pointerId)return;try{O.currentTarget.releasePointerCapture(O.pointerId)}catch{}C.current=null,T.current&&typeof setTimeout=="function"&&setTimeout(()=>{T.current=!1},0);const H=B(O.clientX,O.clientY);if(g(null),h(null),y(null),!H)return;const P=f.current;if(!P)return;const L=Array.from(P.querySelectorAll("[data-seg]")).map(Y=>Y.dataset.seg==="chip"?{kind:"chip",paramName:Y.dataset.param??""}:{kind:"text",text:Y.textContent??""}),F=em(L,z.param,H.slotIdx,H.offset);F!==e&&(x.current=!0,t(F))},B=(O,z)=>{const H=f.current;if(!H)return null;const P=Array.from(H.querySelectorAll("[data-seg]")),I=H.ownerDocument,L=I.elementFromPoint(O,z);let F=L?.closest('[data-seg="text"]');if(F)return{slotIdx:P.indexOf(F),offset:tm(I,F,O,z)};const Y=L?.closest('[data-seg="chip"]');if(Y){const X=P.indexOf(Y),Z=Y.getBoundingClientRect();if(O=0){const se=P[X-1];return{slotIdx:X-1,offset:(se.textContent??"").length}}if(X+1({s:X,i:Z})).filter(X=>X.s.dataset.seg==="text").pop();return K?{slotIdx:K.i,offset:(K.s.textContent??"").length}:null},M=()=>{l||!r||r(yd(f.current,e.length))};return u.useEffect(()=>{if(c)return c.current=()=>yd(f.current,e.length),()=>{c.current=null}},[c,e.length]),i.jsxs("div",{style:{display:"flex",flexDirection:"column",gap:4},children:[i.jsxs("div",{ref:f,"data-testid":`g7le-chip-input-${a}`,className:"g7le-chip-input",style:om,children:[p.map((O,z)=>O.kind==="chip"?i.jsxs("span",{"data-seg":"chip","data-param":O.paramName,"data-testid":`g7le-chip-${a}-${O.paramName}`,contentEditable:!1,onPointerDown:H=>E(H,O.paramName??""),onPointerMove:A,onPointerUp:D,title:n("layout_editor.translation.placeholder_locked"),style:{...am,...b===O.paramName?lm:null,touchAction:"none"},children:[i.jsx("span",{"aria-hidden":"true",style:dm,children:"⠿"}),i.jsx("span",{"aria-hidden":"true",style:{marginRight:3},children:"🔗"}),i.jsx("span",{style:cm,children:o?.[O.paramName??""]??O.paramName}),s&&!l&&i.jsx("button",{type:"button","data-testid":`g7le-chip-remove-${a}-${O.paramName}`,"aria-label":n("layout_editor.inline_binding.clear"),title:n("layout_editor.inline_binding.clear"),onPointerDown:H=>{H.stopPropagation(),H.preventDefault()},onClick:H=>N(H,O.paramName??""),style:um,children:"✕"})]},`chip-${O.paramName}`):i.jsx("span",{ref:H=>{k.current[z]=H},"data-seg":"text",contentEditable:!l,suppressContentEditableWarning:!0,onInput:S,"data-testid":`g7le-chip-text-${a}-${z}`,style:{...rm,...b?im:null,...m?.slotIdx===z?sm:null}},`text-slot-${z}`)),v&&i.jsx("span",{"aria-hidden":"true","data-testid":`g7le-chip-caret-${a}`,style:{position:"absolute",left:v.left,top:v.top,width:2,height:v.height,background:"#2563eb",borderRadius:1,pointerEvents:"none",boxShadow:"0 0 0 1px rgba(37,99,235,0.3)"}})]}),i.jsxs("div",{style:{display:"flex",alignItems:"center",gap:6},children:[r&&!d&&i.jsxs("button",{type:"button","data-testid":`g7le-chip-insert-${a}`,onClick:M,disabled:l,style:pm,children:["+ ",n("layout_editor.inline_binding.insert_data")]}),i.jsx("span",{style:fm,children:n("layout_editor.translation.placeholder_locked")})]})]})}function tm(e,t,n,o){const r=(t.textContent??"").length;try{const a=e;let l=null,d=0;if(typeof a.caretRangeFromPoint=="function"){const c=a.caretRangeFromPoint(n,o);c&&(l=c.startContainer,d=c.startOffset)}else if(typeof a.caretPositionFromPoint=="function"){const c=a.caretPositionFromPoint(n,o);c&&(l=c.offsetNode,d=c.offset)}if(l&&t.contains(l))return Math.max(0,Math.min(d,r))}catch{}const s=t.getBoundingClientRect();return n>s.left+s.width/2?r:0}function nm(e,t,n){if(!e)return null;const r=Array.from(e.querySelectorAll("[data-seg]"))[t];if(!r)return null;const s=e.getBoundingClientRect();try{const d=r.firstChild,c=e.ownerDocument;if(d&&d.nodeType===Node.TEXT_NODE&&(d.textContent??"").length>0){const p=c.createRange(),f=Math.max(0,Math.min(n,(d.textContent??"").length));p.setStart(d,f),p.setEnd(d,f);const b=p.getClientRects(),g=b.length>0?b[0]:p.getBoundingClientRect();if(g&&(g.height>0||g.width>=0))return{left:g.left-s.left,top:g.top-s.top,height:g.height||r.getBoundingClientRect().height}}}catch{}const a=r.getBoundingClientRect();return{left:(n>0?a.right:a.left)-s.left,top:a.top-s.top,height:a.height||16}}function yd(e,t){if(!e)return t;try{const n=e.ownerDocument.defaultView?.getSelection();if(!n||n.rangeCount===0)return t;const o=n.getRangeAt(0);let r=o.startContainer,s=null;for(;r&&r!==e;){if(r instanceof HTMLElement&&r.dataset.seg==="text"){s=r;break}r=r.parentNode}if(!s||!e.contains(s))return t;const a=Array.from(e.querySelectorAll("[data-seg]"));let l=0;for(const d of a){if(d===s)break;l+=d.dataset.seg==="chip"?`{${d.dataset.param??""}}`.length:(d.textContent??"").length}return l+o.startOffset}catch{return t}}const om={position:"relative",display:"flex",flexWrap:"wrap",alignItems:"center",gap:2,minHeight:30,padding:"4px 8px",border:"1px solid #cbd5e1",borderRadius:4,background:"#fff",fontSize:13,lineHeight:1.6},rm={outline:"none",minWidth:8,minHeight:"1.2em",whiteSpace:"pre-wrap",color:"#0f172a",display:"inline-block"},im={boxShadow:"inset 0 -2px 0 #93c5fd",background:"#eff6ff",borderRadius:2},sm={background:"#dbeafe",boxShadow:"inset 0 -2px 0 #2563eb"},am={display:"inline-flex",alignItems:"center",gap:1,background:"linear-gradient(180deg,#eff6ff,#dbeafe)",color:"#1d4ed8",border:"1px dashed #60a5fa",borderRadius:9999,padding:"1px 8px 1px 4px",margin:"0 1px",fontSize:12,fontWeight:600,cursor:"grab",userSelect:"none",whiteSpace:"nowrap",boxShadow:"0 1px 2px rgba(37,99,235,0.18)",verticalAlign:"middle"},lm={opacity:.5,borderStyle:"solid",cursor:"grabbing"},dm={color:"#2563eb",opacity:.7,fontSize:11,marginRight:1,cursor:"grab"},cm={textDecoration:"none"},um={marginLeft:4,width:14,height:14,lineHeight:"12px",padding:0,border:"none",borderRadius:9999,background:"rgba(29,78,216,0.12)",color:"#1d4ed8",fontSize:9,fontWeight:700,cursor:"pointer",display:"inline-flex",alignItems:"center",justifyContent:"center",flex:"0 0 auto"},pm={fontSize:11,border:"1px solid #cbd5e1",borderRadius:6,background:"#f8fafc",color:"#334155",padding:"3px 10px",cursor:"pointer"},fm={fontSize:10,color:"#94a3b8"};function bd(e){e&&(e.style.height="auto",e.style.height=`${Math.max(e.scrollHeight,28)}px`)}function gm(e){bd(e)}function hm(e){const t=new Set;for(const n of e)for(const o of Object.keys(n.values??{}))t.add(o);return Array.from(t).sort()}function ym({templateIdentifier:e,layoutName:t,t:n,referencedKeys:o,onClose:r}){const[s,a]=u.useState([]),[l,d]=u.useState("idle"),[c,p]=u.useState(null),[f,b]=u.useState("all"),[g,m]=u.useState(new Set),[h,v]=u.useState({}),[y,_]=u.useState(null),[w,k]=u.useState({}),x=u.useCallback(async()=>{d("loading"),p(null);const z=await Vy(e,t);z.ok?(a(z.data),v({}),m(new Set),k({}),d("idle")):(p(z.message),d("error"))},[e,t]);u.useEffect(()=>{x()},[x]);const $=u.useMemo(()=>hm(s),[s]),S=u.useMemo(()=>s.filter(z=>f==="all"?!0:z.status===f).slice().sort((z,H)=>{const P=z.updated_at?Date.parse(z.updated_at):0,I=H.updated_at?Date.parse(H.updated_at):0;return I!==P?I-P:H.id-z.id}),[s,f]),C=u.useMemo(()=>s.filter(z=>z.status==="orphaned"),[s]),T=u.useCallback(z=>{const H={...z.values??{}};for(const P of Object.keys(H)){const I=It(z.translation_key,P);I!==void 0&&(H[P]=I)}return H},[]),E=u.useCallback(z=>h[z.id]??T(z),[h,T]),A=u.useCallback((z,H,P)=>{v(I=>{const L=I[z]??s.find(F=>F.id===z)?.values??{};return{...I,[z]:{...L,[H]:P}}})},[s]),N=u.useCallback(async z=>{_(z.id),k(I=>({...I,[z.id]:""}));const H=E(z),P=await Uy(e,z.id,H,z.lock_version);if(P.ok)a(I=>I.map(L=>L.id===z.id?P.data:L)),v(I=>{const L={...I};return delete L[z.id],L});else{const I=P.status===409?n("layout_editor.translation_manager.conflict"):P.message;k(L=>({...L,[z.id]:I}))}_(null)},[e,E,n]),D=u.useCallback(z=>typeof window<"u"&&typeof window.confirm=="function"?window.confirm(n("layout_editor.translation_manager.delete_confirm",{count:z})):!0,[n]),B=u.useCallback(async z=>{if(!D(1))return;const H=await qy(e,z.id);H.ok?(a(P=>P.filter(I=>I.id!==z.id)),m(P=>{const I=new Set(P);return I.delete(z.id),I})):p(H.message)},[e,D]),M=u.useCallback(async z=>{if(z.length===0||!D(z.length))return;const H=await Gy(e,z);if(H.ok){const P=new Set(z);a(I=>I.filter(L=>!P.has(L.id))),m(new Set)}else p(H.message)},[e,D]),O=u.useCallback(z=>{m(H=>{const P=new Set(H);return P.has(z)?P.delete(z):P.add(z),P})},[]);return i.jsxs("div",{className:"g7le-translation-manager","data-testid":"g7le-translation-manager",style:bm,children:[i.jsxs("div",{"data-modal-drag-handle":!0,style:mm,children:[i.jsx("span",{style:{fontSize:14,fontWeight:700},"data-testid":"g7le-translation-manager-title",children:n("layout_editor.translation_manager.title")}),i.jsx("button",{type:"button","aria-label":"close",onClick:r,style:vm,"data-testid":"g7le-translation-manager-close",children:"✕"})]}),i.jsxs("div",{style:_m,children:[i.jsx("div",{style:{display:"inline-flex",gap:4},role:"tablist","aria-label":n("layout_editor.translation_manager.filter_label"),children:["all","active","orphaned"].map(z=>i.jsx("button",{type:"button",role:"tab","aria-selected":f===z,"data-active":f===z?"true":"false",onClick:()=>b(z),style:f===z?xm:md,"data-testid":`g7le-translation-filter-${z}`,children:n(`layout_editor.translation_manager.filter_${z}`)},z))}),i.jsx("div",{style:{flex:1}}),g.size>0&&i.jsxs("button",{type:"button",onClick:()=>{M(Array.from(g))},style:vd,"data-testid":"g7le-translation-bulk-delete",children:["🗑 ",n("layout_editor.translation_manager.delete_selected",{count:g.size})]}),C.length>0&&i.jsxs("button",{type:"button",onClick:()=>{M(C.map(z=>z.id))},style:vd,"data-testid":"g7le-translation-purge-orphaned",children:["🧹 ",n("layout_editor.translation_manager.purge_orphaned",{count:C.length})]})]}),c&&i.jsx("div",{style:wm,"data-testid":"g7le-translation-manager-error",children:c}),i.jsx("div",{style:km,children:l==="loading"?i.jsx("div",{style:_d,"data-testid":"g7le-translation-manager-loading",children:n("layout_editor.translation_manager.loading")}):S.length===0&&l!=="error"?i.jsx("div",{style:_d,"data-testid":"g7le-translation-manager-empty",children:n("layout_editor.translation_manager.empty")}):i.jsx("div",{"data-testid":"g7le-translation-list",style:Sm,children:S.map(z=>{const H=E(z),P=z.status==="orphaned",I=o?o.has(z.translation_key):null;return i.jsxs("div",{style:Cm,"data-testid":`g7le-translation-row-${z.id}`,children:[i.jsxs("div",{style:$m,children:[i.jsx("input",{type:"checkbox",checked:g.has(z.id),onChange:()=>O(z.id),"aria-label":n("layout_editor.translation_manager.select_aria",{key:z.translation_key}),"data-testid":`g7le-translation-select-${z.id}`}),i.jsx("code",{style:Em,children:z.translation_key}),P&&i.jsx("span",{style:Im,"data-testid":`g7le-translation-badge-orphaned-${z.id}`,children:n("layout_editor.translation_manager.badge_orphaned")}),I===!0&&i.jsx("span",{style:Am,"data-testid":`g7le-translation-badge-live-inuse-${z.id}`,children:n("layout_editor.translation_manager.badge_live_in_use")}),I===!1&&i.jsx("span",{style:Tm,"data-testid":`g7le-translation-badge-live-unused-${z.id}`,children:n("layout_editor.translation_manager.badge_live_unused")}),i.jsx("div",{style:{flex:1}}),i.jsx("button",{type:"button",onClick:()=>{B(z)},style:Om,"data-testid":`g7le-translation-delete-${z.id}`,children:"🗑"})]}),i.jsxs("div",{style:Dm,children:[$.map(L=>{const F=H[L]??"",Y=An(F).length>0;return i.jsxs("div",{style:zm,"data-testid":`g7le-translation-field-${z.id}-${L}`,children:[i.jsxs("label",{style:Nm,title:L,children:[Gn(L,n),i.jsxs("span",{style:Mm,children:["(",L,")"]})]}),Y?i.jsx("div",{style:{flex:1},"data-testid":`g7le-translation-chip-${z.id}-${L}`,children:i.jsx(xr,{value:F,onChange:K=>A(z.id,L,K),t:n,testIdSuffix:`${z.id}-${L}`})}):i.jsx("textarea",{value:F,rows:1,ref:gm,onChange:K=>{A(z.id,L,K.currentTarget.value),bd(K.currentTarget)},style:Lm,"data-testid":`g7le-translation-input-${z.id}-${L}`})]},L)}),i.jsxs("div",{style:{display:"flex",alignItems:"center",gap:8},children:[i.jsx("button",{type:"button",disabled:y===z.id,onClick:()=>{N(z)},style:Pm,"data-testid":`g7le-translation-save-${z.id}`,children:y===z.id?n("layout_editor.translation_manager.saving"):n("layout_editor.translation_manager.save")}),w[z.id]?i.jsx("span",{style:{fontSize:11,color:"#dc2626"},"data-testid":`g7le-translation-row-error-${z.id}`,children:w[z.id]}):null]})]})]},z.id)})})}),i.jsx("div",{style:Bm,children:i.jsx("button",{type:"button",onClick:r,style:Wm,"data-testid":"g7le-translation-manager-footer-close",children:n("layout_editor.translation_manager.close")})})]})}const bm={display:"flex",flexDirection:"column",minHeight:0,flex:1},mm={display:"flex",alignItems:"center",justifyContent:"space-between",padding:"12px 16px",borderBottom:"1px solid #e2e8f0"},vm={border:"none",background:"transparent",fontSize:16,color:"#94a3b8",cursor:"pointer"},_m={padding:"10px 16px",borderBottom:"1px solid #f1f5f9",display:"flex",gap:8,alignItems:"center"},md={padding:"4px 10px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",color:"#475569",cursor:"pointer"},xm={...md,borderColor:"#2563eb",background:"#eff6ff",color:"#1d4ed8",fontWeight:600},vd={padding:"4px 10px",fontSize:12,border:"1px solid #fecaca",borderRadius:6,background:"#fff",color:"#dc2626",cursor:"pointer"},wm={padding:"8px 16px",fontSize:12,color:"#b91c1c",background:"#fef2f2"},km={padding:16,overflow:"auto",flex:1,minHeight:120,display:"flex",flexDirection:"column",gap:12},_d={fontSize:13,color:"#94a3b8",textAlign:"center",padding:"32px 0"},Sm={display:"flex",flexDirection:"column",gap:20},Cm={border:"1px solid #e2e8f0",borderRadius:8,padding:10,display:"flex",flexDirection:"column",gap:6},$m={display:"flex",alignItems:"center",gap:8},Em={fontSize:11,background:"#f1f5f9",padding:"2px 6px",borderRadius:4,color:"#0f172a"},Im={fontSize:10,color:"#b45309",background:"#fef3c7",padding:"1px 6px",borderRadius:4,fontWeight:600},Am={fontSize:10,color:"#15803d",background:"#dcfce7",padding:"1px 6px",borderRadius:4,fontWeight:600},Tm={fontSize:10,color:"#9f1239",background:"#ffe4e6",padding:"1px 6px",borderRadius:4,fontWeight:600},Dm={display:"flex",flexDirection:"column",gap:6},zm={display:"flex",alignItems:"flex-start",gap:8},Nm={fontSize:12,color:"#475569",minWidth:88,fontWeight:600,paddingTop:4,display:"flex",flexDirection:"column",lineHeight:1.2},Mm={fontSize:10,color:"#94a3b8",fontWeight:400},Lm={flex:1,padding:"4px 8px",fontSize:13,border:"1px solid #cbd5e1",borderRadius:4,color:"#0f172a",resize:"vertical",overflow:"hidden",minHeight:28,lineHeight:1.4,fontFamily:"inherit",boxSizing:"border-box"},Pm={padding:"5px 12px",fontSize:12,border:"none",borderRadius:6,background:"#2563eb",color:"#fff",cursor:"pointer"},Om={padding:"4px 8px",fontSize:12,border:"1px solid #fecaca",borderRadius:6,background:"#fff",color:"#dc2626",cursor:"pointer"},Bm={padding:"10px 16px",borderTop:"1px solid #e2e8f0",display:"flex",justifyContent:"flex-end"},Wm={padding:"6px 14px",fontSize:13,border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",color:"#475569",cursor:"pointer"};function jm(){const e=Ft(),{state:t}=Qe(),n=e?.document?.raw??null,o=t.templateIdentifier??"",r=t.selectedRoute?.layoutName??"",s=u.useCallback((c,p)=>{const f=n?.[c];return f===void 0?p:f},[n]),a=u.useCallback((c,p,...f)=>{e&&(f.length>=1?e.patchDocumentRaw(c,p,f[0]):e.patchDocumentRaw(c,p))},[e]),l=u.useCallback(async(c,p)=>!o||!r?null:qn(o,r,c,p),[o,r]),d=u.useCallback(async(c,p,f)=>o?vr(o,c,p,f):null,[o]);return{raw:n,getValue:s,patch:a,createI18nKey:l,updateI18nKeyValue:d}}function Hm(e){const t=e.__source;return t&&(t.kind==="module"||t.kind==="plugin")&&t.identifier?`${t.kind}:${t.identifier}`:"template"}function xd(e,t){if(!t)return null;const n=Hm(e);if(n&&t.bySource){const a=t.bySource[n]?.[e.id];if(a!==void 0)return{value:a,match:{dataSourceId:e.id,resolvedBy:"spec_byId",sourceKey:n}}}const o=t.byDataSourceId?.[e.id];if(o!==void 0)return{value:o,match:{dataSourceId:e.id,resolvedBy:"spec_byId"}};const r=e.endpoint??"";if(t.byEndpointPattern&&typeof r=="string"&&r.length>0){for(const[s,a]of Object.entries(t.byEndpointPattern))if(Fm(r,s))return{value:a,match:{dataSourceId:e.id,resolvedBy:"spec_byEndpoint"}}}return null}function rs(e,t){const n=xd(e,t);if(n)return n;const o=e.fallback?.data;return o!==void 0?{value:{data:Array.isArray(o)?o:[o]},match:{dataSourceId:e.id,resolvedBy:"fallback"}}:{value:{},match:{dataSourceId:e.id,resolvedBy:"inferred"}}}function Fm(e,t){const n=t.replace(/[.+?^${}()|[\]\\]/g,"\\$&").replace(/\*/g,".*");return new RegExp(`^${n}$`).test(e)}function Km(e={}){const{spec:t,overrideSpec:n,onMatch:o}=e;return{has(r){return!0},resolve(r){const s=n?xd(r,n):null,{value:a,match:l}=s??rs(r,t);if(o)try{o(l)}catch{}return a}}}const wd=Lt(),kd=wd.DataBindingEngine,Sd=wd.dataBindingEngine;mo("PipeRegistry");function Vm(e){let t=null,n=0;for(let o=0;o0?e[o-1]:"",a=o{const a=t[r],l=a==null?[]:String(a).split(",").map(d=>d.trim()).filter(Boolean);return l.length===0?"":l.join(" ?? ")+(s?" ?? ":"")});return n=n.replace(/\{(\w+)\}/g,(o,r)=>{const s=t[r];return s===void 0?o:s}),n}function wr(e){const t=e.trim(),n=/^\{\{([\s\S]*)\}\}$/.exec(t);return n?n[1].trim():t}function $d(e){const t=e.trim();return/^\{\{[\s\S]*\}\}$/.test(t)?t:`{{ ${t} }}`}function Ed(e,t){const n={};for(const[r,s]of Object.entries(t))n[r]=typeof s=="string"?wr(s):s;const o=qm(e.expr,n);return $d(o)}function Gm(e,t){if(typeof e!="string")return null;const n=Id(wr(e));for(const o of t){const r=Ym(n,o.expr,o.params);if(r)return{recipeId:o.id,params:r}}return null}function Id(e){return e.replace(/\s+/g," ").trim()}function Ad(e){return e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}function Ym(e,t,n){const o=[],r=new Set;let s="";const a=/\{(\w+)(\*?)\}(\s*\?\?\s*)?/g;let l=0,d;const c=Id(t);for(;(d=a.exec(c))!==null;){const g=c.slice(l,d.index);s+=Ad(g).replace(/\\ /g,"\\s*");const m=d[2]==="*",h=!!d[3];m?(r.add(d[1]),s+="(.*?)",h&&(s+="(?:\\s*\\?\\?\\s*)?")):(s+="(.+?)",h&&(s+="\\s*\\?\\?\\s*")),o.push(d[1]),l=a.lastIndex}s+=Ad(c.slice(l)).replace(/\\ /g,"\\s*");let p;try{p=new RegExp(`^${s}$`)}catch{return null}const f=p.exec(e);if(!f)return null;const b={};if(o.forEach((g,m)=>{let h=(f[m+1]??"").trim();r.has(g)&&(h=h.split(/\s*\?\?\s*/).map(v=>v.trim()).filter(Boolean).join(", ")),b[g]=h}),n.length>0){const g=new Set(n.map(m=>m.key));for(const m of Object.keys(b))g.has(m)||delete b[m]}return b}function Xm(e){const t=`x.${e.field}`,n=Jm(e.value);switch(e.cmp){case"=":return`${t} === ${n}`;case"!=":return`${t} !== ${n}`;case">":return`${t} > ${n}`;case"<":return`${t} < ${n}`;case">=":return`${t} >= ${n}`;case"<=":return`${t} <= ${n}`;case"includes":return`(${t} ?? []).includes(${n})`;default:return`${t} === ${n}`}}function Jm(e){const t=e.trim();return t==="true"||t==="false"||t!==""&&!Number.isNaN(Number(t))||/^['"].*['"]$/.test(t)||/[.([{]/.test(t)?t:`'${t.replace(/'/g,"\\'")}'`}function is(e){return!e||e.length===0?"true":e.map(Xm).join(" && ")}function Zm(e){const n=`(${(e.source??"").trim()||"[]"} ?? [])`;let o;switch(e.op){case"count":o=`${n}.filter(x => ${is(e.conditions)}).length`;break;case"sum":{const r=(e.sumField??"").trim()||"value";o=`${n}.filter(x => ${is(e.conditions)}).reduce((s, x) => s + (x.${r} ?? 0), 0)`;break}case"filter":o=`${n}.filter(x => ${is(e.conditions)})`;break;case"toOptions":{const r=(e.valueField??"").trim()||"value",s=(e.labelField??"").trim()||"label";o=`${n}.map(x => ({ value: x.${r}, label: x.${s} }))`;break}case"nth":{const r=(e.index??"").trim()||"0",s=(e.prop??"").trim();o=`${n}[${r}]${s?`?.${s}`:""}`;break}case"firstOf":{const r=(e.candidates??[]).map(l=>l.trim()).filter(Boolean),s=r.length>0?r.join(" ?? "):"''",a=(e.fallback??"").trim();o=a?`${s} ?? '${a.replace(/'/g,"\\'")}'`:s;break}case"literal":{const r=e.literalKind??"string",s=(e.literalValue??"").trim();r==="number"?o=s===""?"0":s:r==="boolean"?o=s==="true"?"true":"false":o=`'${s.replace(/'/g,"\\'")}'`;break}default:o="[]"}return $d(o)}function Qm(e){if(typeof e!="string")return null;const t=wr(e).trim();let n=/^\((.+?)\s*\?\?\s*\[\]\)\.filter\(x => (.+)\)\.length$/.exec(t);return n?{key:"",op:"count",source:n[1].trim(),conditions:ss(n[2])}:(n=/^\((.+?)\s*\?\?\s*\[\]\)\.filter\(x => (.+)\)\.reduce\(\(s, x\) => s \+ \(x\.(\w+(?:\.\w+)*) \?\? 0\), 0\)$/.exec(t),n?{key:"",op:"sum",source:n[1].trim(),conditions:ss(n[2]),sumField:n[3]}:(n=/^\((.+?)\s*\?\?\s*\[\]\)\.filter\(x => (.+)\)$/.exec(t),n?{key:"",op:"filter",source:n[1].trim(),conditions:ss(n[2])}:(n=/^\((.+?)\s*\?\?\s*\[\]\)\.map\(x => \(\{ value: x\.(\w+(?:\.\w+)*), label: x\.(\w+(?:\.\w+)*) \}\)\)$/.exec(t),n?{key:"",op:"toOptions",source:n[1].trim(),valueField:n[2],labelField:n[3]}:(n=/^\((.+?)\s*\?\?\s*\[\]\)\[(.+?)\](?:\?\.(\w+(?:\.\w+)*))?$/.exec(t),n?{key:"",op:"nth",source:n[1].trim(),index:n[2].trim(),prop:n[3]}:null))))}function ss(e){const t=e.trim();if(t==="true")return[];const n=[];for(const o of Rm(t)){const r=ev(o.trim());r&&n.push(r)}return n}function Rm(e){const t=[];let n=0,o=0;for(let r=0;r=|<=|>|<)\s*(.+)$/.exec(e),t){const n={"===":"=","!==":"!=",">":">","<":"<",">=":">=","<=":"<="};return{field:t[1],cmp:n[t[2]]??"=",value:Td(t[3])}}return null}function Td(e){const t=e.trim(),n=/^'(.*)'$/.exec(t)||/^"(.*)"$/.exec(t);return n?n[1]:t}function Dd(e,t){const n=Gm(e,t);if(n)return{kind:"preset",recipeId:n.recipeId,params:n.params};const o=Qm(e);return o?{kind:"custom",model:o}:{kind:"advanced"}}function tv(e){if(e==null)return"null";if(Array.isArray(e))return"list";switch(typeof e){case"number":return"number";case"boolean":return"boolean";case"string":return"string";default:return"object"}}function zd(e,t){if(typeof e!="string"||e.trim()==="")return{ok:!1,reason:"empty"};const n=wr(e);try{const o=Vm(n)?Sd.evaluatePipeExpression(n,t??{},{skipCache:!0}):Sd.evaluateExpression(n,t??{},{skipCache:!0});return{ok:!0,value:o,type:tv(o)}}catch(o){return{ok:!1,reason:o instanceof Error?o.message:String(o)}}}function Nd(e,t){const n=Array.isArray(e?.data_sources)?e.data_sources:[],o=[];for(const r of n){const s=r?.id;if(typeof s!="string"||s.length===0)continue;const{value:a}=rs(r,t?.sampleData),l=typeof r.label_key=="string"?r.label_key:void 0;o.push({id:s,sample:a,labelKey:l})}return o}function Md(e,t){const n=[];t?.sampleGlobal&&typeof t.sampleGlobal=="object"&&n.push({scope:"_global",tree:t.sampleGlobal});const o={},r={},s={},a=(l,d)=>{if(!(!d||typeof d!="object"||Array.isArray(d)))for(const[c,p]of Object.entries(d))if(p&&typeof p=="object"&&!Array.isArray(p)){const f=l[c]&&typeof l[c]=="object"&&!Array.isArray(l[c])?l[c]:l[c]={};a(f,p)}else c in l||(l[c]=p)};for(const l of t?.states?.groups??[])for(const d of l.items??[]){const c=d.initialState;c?.local&&a(o,c.local),c?.query&&a(r,c.query),c?.route&&a(s,c.route)}return Object.keys(o).length&&n.push({scope:"_local",tree:o}),Object.keys(r).length&&n.push({scope:"query",tree:r}),Object.keys(s).length&&n.push({scope:"route",tree:s}),e?.computed&&typeof e.computed=="object"&&!Array.isArray(e.computed)&&n.push({scope:"_computed",tree:e.computed}),n}function Ld(e,t){const n={};for(const r of t)r.scope!=="_computed"&&(n[r.scope]=r.tree);const o={};for(const[r,s]of Object.entries(e)){if(typeof s!="string"){o[r]=s;continue}const a=zd(s,n);o[r]=a.ok?a.value:s}return o}function nv(e,t){const n=Nd(e,t),o=Md(e,t),r={};for(const s of n)s.sample!==void 0&&(r[s.id]=s.sample);for(const s of o)s.scope==="_computed"?r[s.scope]=Ld(s.tree,o):r[s.scope]=s.tree;return r}function Pd(e){const{raw:t,spec:n,evaluateComputed:o}=e;return u.useMemo(()=>{const r=Nd(t,n),s=Md(t,n);if(o){const a=s.findIndex(l=>l.scope==="_computed");if(a>=0){const l=Ld(s[a].tree,s);s[a]={scope:"_computed",tree:l}}}return Ki({dataSources:r,states:s,spec:n??void 0})},[t,n,o])}function ov(e){const{raw:t,spec:n,pageType:o}=e;return u.useMemo(()=>{const r=Array.isArray(t?.data_sources)?t.data_sources:[],s=[];for(const d of r){const c=d?.id;if(typeof c!="string"||c.length===0)continue;const{value:p}=rs(d,n?.sampleData),f=typeof d.label_key=="string"?d.label_key:void 0;s.push({id:c,sample:p,labelKey:f})}const a=[];n?.sampleGlobal&&typeof n.sampleGlobal=="object"&&a.push({scope:"_global",tree:n.sampleGlobal}),t?.computed&&typeof t.computed=="object"&&!Array.isArray(t.computed)&&a.push({scope:"_computed",tree:t.computed});const l=Ki({dataSources:s,states:a,spec:n??void 0});if(typeof o=="string"&&o!=="")for(const d of["title","description"])l.push({expression:`{{_seo.${o}.${d}}}`,source:"_seo",sourceId:"_seo",path:`${o}.${d}`,shape:"scalar",preview:""});return l},[t,n,o])}function Od(e,t){const n=[],o=new Set,r=s=>{for(const a of s??[]){if(a.kind==="route"&&!a.isRedirect&&typeof a.path=="string"&&a.path.startsWith("/")&&!o.has(a.path)){o.add(a.path);const l=a.label.startsWith("$t:")?a.label.slice(3):a.label,d=a.label.startsWith("$t:")?t(l):l,c=d&&d!==l&&!d.startsWith("$t:")?d:"";n.push({value:a.path,label:c?`${c} (${a.path})`:a.path})}r(a.children)}};return r(e),n}function Bd(e){const t=Array.isArray(e?.data_sources)?e.data_sources:[],n=[];for(const o of t){const r=o?.id;typeof r=="string"&&r.length>0&&n.push({value:r,label:r})}return n}function as(e,t){const n=typeof e.id=="string"?e.id:"",o=typeof e.label_key=="string"?e.label_key:"";if(!o)return null;const r=o.startsWith("$t:")?o.slice(3):o,s=t(r);return s&&s!==r&&!s.startsWith("$t:")&&s!==n?s:null}function ls(e,t){const n=e.__source;if(!n||typeof n!="object"||Array.isArray(n))return null;const o=n;if(o.kind!=="extension")return null;const r=o.extensionSourceType==="plugin"?t("layout_editor.data_sources.source.plugin"):t("layout_editor.data_sources.source.module"),s=typeof o.extensionIdentifier=="string"?o.extensionIdentifier:"",a=typeof o.extensionName=="string"?o.extensionName:"",l=a&&a!==s?`${a} (${s})`:s;return l?`${r}: ${l}`:r}function rv(e,t,n){const o=Array.isArray(e?.data_sources)?e.data_sources:[],r=[],s=new Set;for(const a of o){const l=a?.id;typeof l!="string"||l.length===0||s.has(l)||(s.add(l),r.push({id:l,friendly:as(a,t),source:ls(a,n)}))}return r}function Wd(e){const t=e?.states?.groups??[],n=new Set;for(const o of t)for(const r of o.items??[]){for(const s of Object.keys(r.initialState?.local??{}))n.add(s);for(const s of Object.keys(r.initialState?.global??{}))n.add(s)}return Array.from(n).map(o=>({value:o,label:o}))}function iv(e,t){const n=Array.isArray(e?.modals)?e.modals:[],o=[],r=new Set;for(const s of n){const a=s?.id;typeof a!="string"||a.length===0||r.has(a)||(r.add(a),o.push({value:a,label:sv(s,a,t)}))}return o}function sv(e,t,n){const r=e?.meta?.editor_label;if(typeof r=="string"&&r.length>0)return jd(r,n);const s=e?.title;return typeof s=="string"&&s.length>0?jd(s,n):t}function jd(e,t){if(e.startsWith("$t:")){const n=e.slice(3),o=t?t(n):n;return o&&o!==n?o:n}return e}const Hd=mo("ActionRecipeEngine"),av={api:"apiCall",nav:"navigate",showToast:"toast",setLocalState:"setState"},lv=new Set(["comment"]);function ko(e){if(!e||typeof e!="object")return[];const t=[];for(const[n,o]of Object.entries(e)){if(lv.has(n)||!o||typeof o!="object")continue;const r=o,s=r.build&&typeof r.build=="object"?r.build:typeof r.handler=="string"?{handler:r.handler}:void 0;!s||typeof s.handler!="string"||s.handler.length===0||t.push({id:n,label:typeof r.label=="string"?r.label:void 0,params:Array.isArray(r.params)?r.params:[],build:s})}return t}function on(e){const t=/^\{\{\s*([\w.]+)\s*\}\}$/.exec(e);return t?t[1]:null}const kr="...";function dv(e,t){return e.replace(/\{\{\s*([\w.]+)\s*\}\}/g,(n,o)=>{const r=t[o];return r==null?"":String(r)})}function ds(e,t){if(typeof e=="string"){const n=on(e);return n!==null?n in t?t[n]:void 0:e.includes("{{")?dv(e,t):e}if(Array.isArray(e))return e.map(n=>ds(n,t)).filter(n=>n!==void 0);if(e&&typeof e=="object"){const n={};for(const[o,r]of Object.entries(e)){if(o===kr&&typeof r=="string"){const d=on(r),c=d!==null?t[d]:void 0;if(c&&typeof c=="object"&&!Array.isArray(c))for(const[p,f]of Object.entries(c))f===void 0||p===""||(n[p]=f);continue}const s=ds(r,t);if(s===void 0||Array.isArray(s)&&s.length===0&&(o==="onSuccess"||o==="onError"))continue;const a=on(o),l=a!==null?a in t?String(t[a]):null:o;l===null||l===""||s&&typeof s=="object"&&!Array.isArray(s)&&Object.keys(s).length===0||(n[l]=s)}return n}return e}function cv(e){const t=e.handler;if(typeof t!="string")return e;const n=av[t];if(n)return Hd.warn(`action recipe used forbidden handler "${t}" — corrected to "${n}" (core handler rules)`),{...e,handler:n};if(t==="apiCall"||t==="navigate"){const o=e.params;if(o&&typeof o=="object")for(const r of["target","onSuccess","onError"])r in o&&Hd.warn(`action recipe build placed "${r}" inside params for handler "${t}" — should be top-level (core handler rules)`)}return e}function Sr(e,t){const n=ds(e.build,t);if(typeof n.handler!="string"&&e.build.handler.includes("{{")&&(n.handler=e.build.handler),e.build.handler.includes("{{")){const o=new Set(e.params.filter(r=>r.required).map(r=>r.key));o.size>0&&Fd(e.build,n,o)}return cv(n)}function Fd(e,t,n){if(!e||typeof e!="object"||Array.isArray(e)||!t||typeof t!="object"||Array.isArray(t))return;const o=e,r=t;for(const[s,a]of Object.entries(o))if(s!=="handler"){if(typeof a=="string"){const l=on(a);l!==null&&n.has(l)&&!(s in r)&&(r[s]=a)}else if(a&&typeof a=="object"){if(!(s in r)||typeof r[s]!="object"||r[s]===null)if(JSON.stringify(a).includes("{{"))r[s]={};else continue;Fd(a,r[s],n)}}}function uv(e,t){if(!e||typeof e!="object")return null;const n=e.handler;if(typeof n!="string")return null;let o=null,r=-1;for(const s of t){const a=s.build.handler.includes("{{");if(!a&&s.build.handler!==n||a&&!pv(s.build,e))continue;const l=fv(s.build,e);if(l===null)continue;const d=Object.keys(l).length;d>r&&(r=d,o={recipeId:s.id,values:l})}return o}function pv(e,t){if(typeof t.handler=="string"&&t.handler===e.handler)return!0;const n=e.params;if(!n||typeof n!="object")return!1;const o=t.params;if(!o||typeof o!="object")return!1;const r=o,s=Object.entries(n).filter(([,a])=>typeof a=="string"&&on(a)!==null);return s.length===0?!1:s.every(([a])=>a in r)}function fv(e,t){const n={},o=(r,s)=>{if(typeof r=="string"){const a=on(r);return a!==null?(n[a]=s,!0):r.includes("{{")?typeof s=="string":r===s}if(Array.isArray(r))return Array.isArray(s);if(r&&typeof r=="object"){if(!s||typeof s!="object"||Array.isArray(s))return!1;const a=s,l=r[kr],d=typeof l=="string"?on(l):null,c=new Set(Object.keys(r).filter(p=>p!==kr));for(const[p,f]of Object.entries(r))if(p!==kr){if(typeof f=="string"&&on(f)!==null){const b=on(f);p in a&&(n[b]=a[p]);continue}if(!(p in a)){if(p==="onSuccess"||p==="onError"||p==="params"||p==="identity_target")continue;return!1}if(!o(f,a[p]))return!1}if(d!==null){const p={};for(const[f,b]of Object.entries(a))c.has(f)||(p[f]=b);Object.keys(p).length>0&&(n[d]=p)}return!0}return r===s};return o(e,t)?n:null}function Yn(e,t){const n=typeof e?.handler=="string"?e.handler:"",o=uv(e,t);return o?{kind:"preset",recipeId:o.recipeId,values:o.values,handler:n}:{kind:"advanced",handler:n}}function Kd(e,t,n){const o=e?.label,r=o?n?n(o):o:"",s=[],a=e?.params??[];for(const l of a){if(l.advanced||l.widget==="action-list")continue;const d=t[l.key];if(d==null||d==="")continue;const c=gv(d);if(c!==""&&(s.push(c),s.length>=2))break}return s.length===0?r:`${r} (${s.join(", ")})`}function gv(e){return typeof e=="string"?e:typeof e=="number"||typeof e=="boolean"?String(e):Array.isArray(e)?`[${e.length}]`:e&&typeof e=="object"?"{…}":""}function Vd(e,t){const n={...e};return typeof t=="string"&&t.trim().length>0?n.if=t.trim():delete n.if,n}function cs(e){const t=e?.if;return typeof t=="string"&&t.trim().length>0?t:null}const hv=/^\s*\$t:(custom\.[a-zA-Z0-9._-]+)\s*$/,yv=/^\s*\$t:([a-zA-Z0-9._-]+)\s*$/,bv=/\{\{.*?\}\}/;function Ud(e,t){const n=typeof e=="string"?e:"",o=hv.exec(n);if(o){const a=o[1],l=t?t(a):"";return{customKey:a,binding:!1,paramKey:!1,displayValue:l}}const r=vt(n);if(r&&r.key.startsWith("custom.")){const a=t?t(r.key):"";return{customKey:r.key,binding:!1,paramKey:!0,displayValue:a}}if(bv.test(n))return{customKey:null,binding:!0,paramKey:!1,displayValue:n};const s=yv.exec(n);if(s){const a=s[1],l=t?t(a):"";return{customKey:null,binding:!1,paramKey:!1,displayValue:l&&l!==a?l:""}}return{customKey:null,binding:!1,paramKey:!1,displayValue:n}}function mv(){const{state:e}=Qe(),t=e.templateIdentifier,n=e.locale,o=e.selectedRoute?.layoutName??null,r=u.useCallback(l=>{try{const c=Pt.getInstance().translate(l,{templateId:t,locale:n});return c&&c!==l?c:""}catch{return""}},[t,n]),s=u.useCallback(l=>Ud(l,r),[r]),a=u.useCallback(async(l,d)=>{const c=Ud(l,r);if(c.binding)return{kind:"blocked"};if(c.customKey){if(d===c.displayValue)return{kind:"noop",customKey:c.customKey};const b=await vr(t,c.customKey,n,d);return b.kind==="error"?{kind:"error",message:b.message}:(await tn(t,n),{kind:"updated",customKey:c.customKey})}const p=await qn(t,o,n,d);if(p.kind==="error"||!p.resource)return{kind:"error",message:p.message};const f=p.resource.translation_key;return await tn(t,n),{kind:"created",customKey:f,token:`$t:${f}`}},[t,n,o,r]);return{locale:n,templateIdentifier:t,translate:r,classify:s,commitText:a}}async function us(e,t,n,o,r,s,a,l,d,c){const p=c?Zi(o,c):null,f=!!(p&&p.keyifiable),b=d?d(o):o,g=f?p.chipValue:Rl(b),h=`p${f?p.bindings.length:fr(b).length}`,v=Math.max(0,Math.min(r,g.length)),y=Ul(g,v,h),_=gr(y).replace(/\{\{?p\d+\}?\}/g," ").replace(/\s+/g," ").trim(),w=_.length>0?_:y,k=await qn(e,t,n,w);if(k.kind==="error"||!k.resource)return{kind:"error",message:k.message};const x=k.resource.translation_key,$=An(y),S=k.resource.values??{},C={[n]:y};for(const N of Object.keys(S)){if(N===n)continue;let D=S[N]??"";for(const B of $)D=Xi(D,B);C[N]=D}wo(e,x,C);const T=En(s,a,l);return{kind:"ok",text:`${f?Zl(`$t:${x}`,p):Ql(`$t:${x}`,b)}|${h}=${T}`,key:x}}async function Cr(e,t,n,o,r,s,a){let l=vt(n);if(!l&&typeof n=="string"){const h=/^\s*\$t:(custom\.[A-Za-z0-9._-]+)\s*$/.exec(n);h&&(l={key:h[1],params:[]})}if(!l)return{kind:"error",message:"not a param key"};if(!l.key.startsWith("custom."))return{kind:"error",message:"not a custom param key"};const d=wb(l.params.map(h=>h.name)),c=En(r,s,a),p=`${n.trim()}|${d}=${c}`,f=await cn(e,l.key),b=Gb(l.key)??{},g=new Set([...Object.keys(f?.values??{}),...Object.keys(b),t]);if(g.size===0)return{kind:"error",message:"custom key not found"};const m={};for(const h of g){const v=It(l.key,h)??f?.values?.[h]??"";m[h]=h===t?Ul(v,o,d):Xi(v,d)}return wo(e,l.key,m),{kind:"ok",text:p,paramName:d}}async function So(e,t,n,o){return _r(e,t,n,o),{kind:"ok"}}async function vv(e,t,n){const o=await cn(e,t);if(!o)return{kind:"error",message:"custom key not found"};const r={};for(const[s,a]of Object.entries(o.values??{})){const l=It(t,s)??a;r[s]=n(l)}wo(e,t,r);try{const s=Pt.getInstance();for(const[a,l]of Object.entries(r))s.setTranslationValue(e,a,t,l)}catch{}return{kind:"ok"}}function qd(e,t,n){return vv(e,t,o=>nd(o,n))}function Gd(e,t){if(!(typeof window>"u"))try{window.dispatchEvent(new CustomEvent(kt,{detail:{templateIdentifier:e,locale:t}}))}catch{}}async function $r(e,t,n,o){const r=await qd(e,t,n);return Gd(e,o),r}const _v=/^\s*\$t:(custom\.[a-zA-Z0-9._-]+)\s*$/;function xv(e,t){const n=s=>{const a=[],l=/\{\{?(p\d+)\}?\}/g;let d;for(;(d=l.exec(s))!==null;)a.push(d[1]);return a},o=n(e),r=n(t);return o.length!==r.length?!0:o.some((s,a)=>s!==r[a])}function Yd(e){const t=e.text;if(typeof t!="string")return null;const n=_v.exec(t);if(n)return n[1];const o=vt(t);return o&&o.key.startsWith("custom.")?o.key:null}function ps(e){const t={},n=vt(typeof e=="string"?e:"");for(const o of n?.params??[])t[o.name]=Vt(o.expression);return t}function wv(e){return ps(typeof e.text=="string"?e.text:"")}function Xd({customKey:e,templateIdentifier:t,t:n,paramLabels:o,locales:r,onRemoveParam:s}){const a=r??hd(),l=Ft(),d=o??{},[c,p]=u.useState({status:"idle",values:{}}),[f,b]=u.useState(!1),[g,m]=u.useState(!1),[h,v]=u.useState(null),y=u.useRef({});u.useEffect(()=>{let E=!1;if(!e){p({status:"not_a_key",values:{}});return}return p({status:"loading",values:{}}),(async()=>{const A=await cn(t,e);if(E)return;if(!A){p({status:"error",values:{}});return}const N={...A.values??{}};for(const D of Object.keys(N)){const B=It(e,D);B!==void 0&&(N[D]=B)}for(const D of a){const B=It(e,D);B!==void 0&&(N[D]=B)}y.current={...N},p({status:"loaded",id:A.id,lockVersion:A.lock_version,values:N})})(),()=>{E=!0}},[e,t]),u.useEffect(()=>{if(typeof window>"u"||!e)return;const E=A=>{const N=A.detail;N?.templateIdentifier&&N.templateIdentifier!==t||p(D=>{if(D.status!=="loaded")return D;let B=!1;const M={...D.values};for(const O of Object.keys(M)){const z=It(e,O);z!==void 0&&z!==M[O]&&(M[O]=z,B=!0)}return B?(y.current={...y.current,...M},{...D,values:M}):D})};return window.addEventListener(kt,E),()=>window.removeEventListener(kt,E)},[e,t]);const _=u.useCallback((E,A)=>{p(N=>{const D=N.values[E]??"",B={...N,values:{...N.values,[E]:A}};return e&&xv(D,A)&&(_r(t,e,E,A),y.current={...y.current,[E]:A},l?.markDirty?.(),Gd(t,E)),B})},[e,t,l]),w=u.useCallback(E=>{p(A=>{const N={};for(const[D,B]of Object.entries(A.values))N[D]=nd(B,E);return y.current={...N},{...A,values:N}}),s?.(E)},[s]),k=u.useMemo(()=>[...Object.values(c.values),...Object.values(y.current)].some(A=>An(A).length>0),[c.values]),x=u.useRef(null),$=u.useCallback(()=>{m(!0),x.current&&clearTimeout(x.current),x.current=setTimeout(()=>m(!1),1500)},[]);u.useEffect(()=>()=>{x.current&&clearTimeout(x.current)},[]);const S=u.useCallback(async()=>{if(m(!1),c.status!=="loaded"||c.id===void 0)return;const E=y.current;for(const[N,D]of Object.entries(c.values)){const B=E[N]??"";if(k){const M=An(B);if(M.length===0)continue;const O=An(D);if(!(M.length===O.length&&M.every((H,P)=>H===O[P]))){v(n("layout_editor.translation.placeholder_mismatch"));return}continue}if(Yi(B)&&!Tb(B,D)){v(n("layout_editor.translation.token_mismatch"));return}}if(b(!0),v(null),k){wo(t,e??"",c.values),y.current={...c.values},l?.markDirty?.(),Et({op:"translation_field_update",translationKey:e,changedLocales:Object.keys(c.values),timestamp:Date.now()}),await new Promise(N=>setTimeout(N,0)),b(!1),$();return}const A=`/api/admin/templates/${encodeURIComponent(t)}/custom-translations/${c.id}`;try{const N=await fetch(A,{method:"PUT",credentials:"same-origin",headers:Ne({"Content-Type":"application/json"}),body:JSON.stringify({values:c.values,expected_lock_version:c.lockVersion??0})}),D=await N.json().catch(()=>null);if(N.status===409){v(n("layout_editor.translation.conflict"));return}if(!N.ok){v(D?.message??n("layout_editor.translation.save_failed"));return}const B=typeof D?.data?.lock_version=="number"?D.data.lock_version:(c.lockVersion??0)+1;p(M=>({...M,lockVersion:B})),y.current={...c.values},await tn(t),Et({op:"translation_field_update",translationKey:e,changedLocales:Object.keys(c.values),timestamp:Date.now()}),$()}catch(N){v(N instanceof Error?N.message:n("layout_editor.translation.save_failed"))}finally{b(!1)}},[c,t,e,n]);if(c.status==="not_a_key"||!e)return i.jsx("div",{"data-testid":"g7le-translation-not-a-key",style:fs,children:n("layout_editor.translation.not_a_key")});if(c.status==="loading"||c.status==="idle")return i.jsx("div",{"data-testid":"g7le-translation-loading",style:fs,children:n("layout_editor.translation.loading")});if(c.status==="error")return i.jsx("div",{"data-testid":"g7le-translation-error",style:fs,children:n("layout_editor.translation.load_failed")});const C=new Set([...a,...Object.keys(c.values)]),T=Array.from(C);return i.jsxs("div",{"data-testid":"g7le-translation-field",style:{display:"flex",flexDirection:"column",gap:8},children:[i.jsxs("div",{"data-testid":"g7le-translation-key",style:kv,children:[n("layout_editor.translation.key_label"),": ",i.jsx("code",{style:Sv,children:e})]}),T.map(E=>{const A=c.values[E]??"",N=A.trim().length===0;return i.jsxs("div",{className:"g7le-translation-row","data-testid":`g7le-translation-row-${E}`,style:Cv,children:[i.jsxs("label",{htmlFor:`g7le-translation-input-${E}`,style:{fontSize:12,color:"#475569",minWidth:72,display:"inline-flex",alignItems:"center",gap:4},children:[Gn(E,n),N&&i.jsx("span",{"data-testid":`g7le-translation-missing-${E}`,style:$v,children:n("layout_editor.translation.needs_translation")})]}),k?i.jsx("div",{style:{flex:1},"data-testid":`g7le-translation-chip-${E}`,children:i.jsx(xr,{value:A,onChange:D=>_(E,D),t:n,paramLabels:d,testIdSuffix:E,onRemoveChip:s?w:void 0})}):i.jsx("input",{id:`g7le-translation-input-${E}`,type:"text","data-testid":`g7le-translation-input-${E}`,value:A,placeholder:N?n("layout_editor.translation.needs_translation"):"",onChange:D=>_(E,D.currentTarget.value),onBlur:S,style:{flex:1,padding:"4px 8px",fontSize:13,border:"1px solid #cbd5e1",borderRadius:4,color:N?"#94a3b8":"#0f172a"}})]},E)}),i.jsxs("div",{style:{display:"flex",alignItems:"center",gap:8},children:[i.jsx("button",{type:"button","data-testid":"g7le-translation-save",disabled:f,onClick:S,style:Ev,children:n(f?"layout_editor.translation.saving":"layout_editor.translation.save")}),g&&!f&&i.jsxs("span",{"data-testid":"g7le-translation-saved",style:{fontSize:11,color:"#16a34a",display:"inline-flex",alignItems:"center",gap:4},children:["✓ ",n("layout_editor.translation.saved")]}),h&&i.jsx("span",{"data-testid":"g7le-translation-save-error",style:{fontSize:11,color:"#dc2626"},children:h})]})]})}const fs={fontSize:12,color:"#94a3b8",padding:"16px 0",textAlign:"center"},kv={fontSize:12,color:"#475569"},Sv={fontSize:11,background:"#f1f5f9",padding:"1px 4px",borderRadius:3,color:"#0f172a"},Cv={display:"flex",alignItems:"center",gap:8},$v={fontSize:10,color:"#b45309",background:"#fef3c7",padding:"0 4px",borderRadius:3},Ev={padding:"6px 12px",fontSize:12,border:"none",borderRadius:6,background:"#2563eb",color:"#fff",cursor:"pointer"};function Xn({anchorRef:e,open:t,onClose:n,children:o,placement:r="bottom",minWidth:s=240,maxWidth:a=360,margin:l=8,testid:d}){const c=u.useRef(null),[p,f]=u.useState(null),b=u.useCallback(()=>{const m=e.current,h=c.current;if(!m||!h)return;const v=m.getBoundingClientRect(),y=window.innerWidth,_=window.innerHeight,w=h.getBoundingClientRect(),k=Math.min(Math.max(w.width,s),a),x=w.height;let $=v.left;$+k+l>y&&($=v.right-k),$=Math.min(Math.max($,l),y-k-l);const S=_-v.bottom-l,C=v.top-l;let T,E;const A=r==="bottom",N=S>=Math.min(x,160),D=C>=Math.min(x,160);A&&N||A&&!D?(T=v.bottom+4,E=S):!A&&D||D?(T=v.top-4-Math.min(x,C),E=C):S>=C?(T=v.bottom+4,E=S):(T=l,E=C),f({left:$,top:T,maxHeight:Math.max(E,120)})},[e,r,s,a,l]);if(u.useLayoutEffect(()=>{if(!t){f(null);return}b()},[t,b]),u.useEffect(()=>{if(!t)return;const m=()=>b();window.addEventListener("resize",m),window.addEventListener("scroll",m,!0);const h=y=>{const _=c.current,w=e.current,k=y.target;_?.contains(k)||w?.contains(k)||n()},v=y=>{y.key==="Escape"&&n()};return document.addEventListener("pointerdown",h,!0),document.addEventListener("keydown",v),()=>{window.removeEventListener("resize",m),window.removeEventListener("scroll",m,!0),document.removeEventListener("pointerdown",h,!0),document.removeEventListener("keydown",v)}},[t,b,n,e]),!t)return null;const g={...Iv,minWidth:s,maxWidth:`min(${a}px, calc(100vw - ${l*2}px))`,...p?{left:p.left,top:p.top,maxHeight:p.maxHeight,visibility:"visible"}:{left:-9999,top:-9999,visibility:"hidden"}};return i.jsx("div",{ref:c,"data-testid":d,style:g,role:"dialog",children:o})}const Iv={position:"fixed",zIndex:1e3,background:"#fff",border:"1px solid #cbd5e1",borderRadius:8,boxShadow:"0 4px 16px rgba(0,0,0,0.14)",padding:6,overflowY:"auto",boxSizing:"border-box"};function Jd(e,t,n){if(!t)return n;const o=e(t);return!o||o===t||o.startsWith("$t:")?n:o}function $t({candidates:e,t,onSelect:n,testIdSuffix:o,defaultOpen:r=!1,floating:s=!0}){const[a,l]=u.useState(r),[d,c]=u.useState(""),p=u.useRef(null),f=u.useMemo(()=>Ui(e,"scalar").map(h=>({...h,resolvedLabel:Jd(t,h.labelKey,h.path||h.sourceId)})),[e,t]),b=u.useMemo(()=>qi(f,d),[f,d]),g=i.jsxs(i.Fragment,{children:[i.jsx("input",{type:"text",autoFocus:!0,"data-testid":`g7le-inline-binding-search-input-${o}`,value:d,placeholder:t("layout_editor.inline_binding.search_placeholder"),onChange:m=>c(m.target.value),style:zv}),i.jsx("div",{style:Nv,children:b.length===0?i.jsx("div",{style:Mv,children:t("layout_editor.inline_binding.no_results")}):b.map(m=>i.jsxs("button",{type:"button","data-testid":`g7le-inline-binding-candidate-${m.expression}`,onClick:()=>{n(m),l(!1),c("")},style:Lv,children:[i.jsxs("span",{style:{display:"flex",flexDirection:"column",alignItems:"flex-start"},children:[i.jsx("span",{style:{fontSize:12},children:m.resolvedLabel}),i.jsxs("span",{style:Pv,children:[Jd(t,m.groupLabelKey,m.sourceId)," · ",m.path||m.sourceId]})]}),i.jsx("span",{style:Ov,children:m.preview})]},m.expression))})]});return i.jsxs("div",{style:s?Dv:{marginTop:4},children:[i.jsx("button",{ref:p,type:"button","data-testid":`g7le-inline-binding-search-toggle-${o}`,onClick:()=>l(m=>!m),title:t("layout_editor.inline_binding.search"),"aria-label":t("layout_editor.inline_binding.search"),"aria-expanded":a,style:Av,children:"🔍"}),s?i.jsx(Xn,{anchorRef:p,open:a,onClose:()=>l(!1),testid:`g7le-inline-binding-picker-${o}`,children:g}):a&&i.jsx("div",{"data-testid":`g7le-inline-binding-picker-${o}`,style:Tv,children:g})]})}const Av={fontSize:13,lineHeight:1,border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",color:"#334155",padding:"4px 8px",cursor:"pointer",textAlign:"center",whiteSpace:"nowrap"},Tv={marginTop:4,border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",padding:6},Dv={display:"inline-block"},zv={width:"100%",padding:"5px 8px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,boxSizing:"border-box"},Nv={marginTop:4,maxHeight:200,overflowY:"auto",display:"flex",flexDirection:"column",gap:2},Mv={fontSize:11,color:"#94a3b8",padding:"8px 0",textAlign:"center"},Lv={display:"flex",alignItems:"center",justifyContent:"space-between",gap:8,border:"none",background:"transparent",cursor:"pointer",padding:"6px 8px",borderRadius:4,textAlign:"left"},Pv={fontSize:10,color:"#94a3b8"},Ov={fontSize:10,color:"#64748b",fontFamily:"monospace",whiteSpace:"nowrap"},Bv=["$localized"];function Jn(e){const t=e.trim();if(!t.startsWith("{{")||!t.endsWith("}}")||t.length<4)return null;let n=0;for(let o=0;oun(n).decomposed)}function Co(e,t,n,o){switch(e.kind){case"leaf":Qd(e.text,t,n,o);return;case"conditional":Co(e.then,t,n,o),o.push({kind:"ellipsis",text:"⋯"});return;case"fallback":Co(e.primary,t,n,o);return;case"concat":e.parts.forEach(r=>Co(r,t,n,o));return;default:o.push({kind:"text",text:e.source});return}}function Qd(e,t,n,o){const r=e.trim();if(/^\{\{([\s\S]*)\}\}$/.exec(r)&&!r.slice(2,-2).includes("}}")){o.push({kind:"chip",text:n(r)});return}if(r.startsWith("$t:")){const a=r.slice(3).split("|")[0];o.push({kind:"text",text:t(a)||a});return}for(const a of e.split(/(\{\{(?:[^}]|\}(?!\}))*\}\})/g))a!==""&&(/^\{\{[\s\S]*\}\}$/.test(a)?o.push({kind:"chip",text:n(a)}):o.push({kind:"text",text:a}))}function gs(e,t,n){const o=[];if(typeof e!="string"||e==="")return o;if(Jn(e)===null&&/\{\{/.test(e)){for(const a of e.split(/(\{\{(?:[^}]|\}(?!\}))*\}\})/g))if(!(a===void 0||a===""))if(/^\{\{[\s\S]*\}\}$/.test(a)){const l=un(a);Co(l.node,t,n,o)}else Qd(a,t,n,o);return o}const s=un(e);return Co(s.node,t,n,o),o}function Wv(e){if(typeof e!="string"||e.trim()==="")return"empty";const t=e.trim();if(Jn(t)===null)return"simple";const n=un(e);return n.node.kind==="leaf"?"simple":n.decomposed?"expression":"raw"}function un(e){const t=typeof e=="string"?e:"",n=t.trim();if(n==="")return{node:{kind:"leaf",text:t},decomposed:!1,original:t};const o=Jn(n);if(o===null)return{node:{kind:"leaf",text:t},decomposed:!1,original:t};let r;try{r=Yv(o)}catch{r=null}if(r===null)return{node:{kind:"raw",source:o},decomposed:!1,original:t};if(r.kind==="leaf"||r.kind==="raw")return{node:r,decomposed:!1,original:t};if(r.kind==="fallback"&&r.op==="??"&&jv(r.primary)&&Hv(r.fallback))return{node:{kind:"leaf",text:t},decomposed:!1,original:t};const s=Zn(r,!0);return Rv(o,s)?{node:r,decomposed:!0,original:t}:{node:{kind:"raw",source:o},decomposed:!1,original:t}}function Zn(e,t=!1){const n=ec(e);return t?n:e.kind==="leaf"?e.text:`{{${n}}}`}function $o(e){return Zn({kind:"conditional",condition:{kind:"simple",left:"",op:"truthy",right:""},then:{kind:"leaf",text:typeof e=="string"?e:""},else:{kind:"leaf",text:""}},!1)}function hs(e){return Zn({kind:"fallback",op:"??",primary:{kind:"leaf",text:typeof e=="string"?e:""},fallback:{kind:"leaf",text:""}},!1)}function Eo(e){switch(e.kind){case"leaf":return e.text;case"conditional":return Eo(e.then);case"fallback":return Eo(e.primary);case"concat":return e.parts.map(Eo).join("");default:return e.kind==="raw"?`{{${e.source}}}`:""}}function WM(e){return e}function Er(e){if(typeof e!="string"||e.trim()==="")return"";if(Jn(e)===null&&/\{\{/.test(e)){for(const n of e.split(/(\{\{(?:[^}]|\}(?!\}))*\}\})/g))if(!(n===void 0||n===""))return/^\{\{[\s\S]*\}\}$/.test(n)?Eo(un(n).node):n;return""}return Eo(un(e).node)}function jv(e){if(e.kind!=="leaf")return!1;const t=/^\{\{([\s\S]*)\}\}$/.exec(e.text.trim());return t?Qn(t[1].trim()):!1}function Hv(e){if(e.kind!=="leaf")return!1;const t=e.text.trim();return!(/\{\{/.test(t)||t.startsWith("$t:"))}function Ir(e){return`'${e.replace(/\\/g,"\\\\").replace(/'/g,"\\'")}'`}function Fv(e){const t=[];let n=0,o=0;const r=e.length;for(;on&&t.push(Ir(e.slice(n,o)));let s=o+2;for(;s=r)break;const a=e.slice(o+2,s).trim();t.push(a),o=s+2,n=o;continue}o++}return nIo(t,"concat")).join(" + ");case"fallback":return`${Io(e.primary,"fallback")} ${e.op} ${Io(e.fallback,"fallback")}`;case"conditional":return`${Vv(e.condition)} ? ${Io(e.then,"conditional")} : ${Io(e.else,"conditional")}`;default:return""}}function Io(e,t){const n=ec(e);return e.kind==="leaf"||e.kind==="raw"?n:e.kind==="conditional"?t==="conditional"?n:`(${n})`:e.kind==="fallback"?t==="concat"?`(${n})`:n:(e.kind==="concat",n)}const qv=["===","!==","??","||","&&","==","!=",">=","<="];function tc(e){const t=[];let n=0;const o=e.length;for(;n=o)throw new Error("unterminated-binding-in-string");d+=e.slice(l,c+2),l=c+2;continue}if(e[l]===a){d+=a,l++;break}if(a==="`"&&e[l]==="$"&&e[l+1]==="{")throw new Error("template-literal-interpolation-unsupported");d+=e[l],l++}if(l>o||d[d.length-1]!==a)throw new Error("unterminated-string");t.push({kind:"string",value:d,start:n,end:l}),n=l;continue}if(r==="{"&&e[n+1]==="{"){let a=n+2;for(;a"||r==="<"){t.push({kind:"op",value:r,start:n,end:n+1}),n++;continue}if(r==="!"){t.push({kind:"op",value:"!",start:n,end:n+1}),n++;continue}if(r==="("){t.push({kind:"lparen",value:"(",start:n,end:n+1}),n++;continue}if(r===")"){t.push({kind:"rparen",value:")",start:n,end:n+1}),n++;continue}if(r==="["){t.push({kind:"lbracket",value:"[",start:n,end:n+1}),n++;continue}if(r==="]"){t.push({kind:"rbracket",value:"]",start:n,end:n+1}),n++;continue}if(r==="."){t.push({kind:"dot",value:".",start:n,end:n+1}),n++;continue}if(/[0-9]/.test(r)){let a=n;for(;a=n)return"";const o=this.toks[t].start,r=this.toks[n-1].end;return this.src.slice(o,r)}parseFallback(){let t=this.parseConcat(),n=this.peek();for(;n&&n.kind==="op"&&(n.value==="??"||n.value==="||");){const o=n.value;this.next();const r=this.parseConcat();t={kind:"fallback",op:o,primary:t,fallback:r},n=this.peek()}return t}parseConcat(){const t=this.parseUnaryOrPrimary(),n=[t];let o=this.peek();for(;o&&o.kind==="op"&&o.value==="+";)this.next(),n.push(this.parseUnaryOrPrimary()),o=this.peek();return n.length===1?t:{kind:"concat",parts:n}}parseUnaryOrPrimary(){return this.parsePrimary()}parsePrimary(){const t=this.peek();if(!t)throw new Error("unexpected-end");if(t.kind==="lparen"){this.next();const n=this.parseTernary(),o=this.next();if(!o||o.kind!=="rparen")throw new Error("expected-rparen");return n}if(t.kind==="string")return this.next(),{kind:"leaf",text:oc(t.value)};if(t.kind==="tkey")return this.next(),{kind:"leaf",text:t.value};if(t.kind==="binding")return this.next(),{kind:"leaf",text:t.value};if(t.kind==="ident"&&Bv.includes(t.value)&&this.toks[this.pos+1]?.kind==="lparen"){const n=this.tryParsePathWrapperCall(t.value);if(n!==null)return n;throw new Error("path-wrapper-non-simple-arg")}if(t.kind==="ident")return{kind:"leaf",text:`{{${this.parsePath()}}}`};if(t.kind==="number")return this.next(),{kind:"leaf",text:t.value};throw new Error(`unparseable-primary:${t.kind}`)}parsePath(){let t="";const n=this.next();if(!n||n.kind!=="ident")throw new Error("expected-ident");for(t+=n.value;;){const o=this.peek();if(!o)break;if(o.kind==="op"&&o.value==="?"&&this.toks[this.pos+1]?.kind==="dot"){this.next(),this.next();const r=this.next();if(!r||r.kind!=="ident")throw new Error("expected-ident-after-optional");t+="?."+r.value;continue}if(o.kind==="dot"){this.next();const r=this.next();if(!r||r.kind!=="ident")throw new Error("expected-ident-after-dot");t+="."+r.value;continue}if(o.kind==="lbracket"){this.next();const r=this.next(),s=this.next();if(!s||s.kind!=="rbracket")throw new Error("expected-rbracket");if(r&&(r.kind==="number"||r.kind==="string")){t+=`[${r.value}]`;continue}throw new Error("non-literal-index")}if(o.kind==="lparen")throw new Error("call-expression");break}return t}tryParsePathWrapperCall(t){const n=this.pos;this.next();const o=this.next();if(!o||o.kind!=="lparen")return this.pos=n,null;let r;try{if(this.peek()?.kind!=="ident")return this.pos=n,null;r=this.parsePath()}catch{return this.pos=n,null}const s=this.next();return!s||s.kind!=="rparen"?(this.pos=n,null):{kind:"leaf",text:`{{${t}(${r})}}`}}}function Yv(e){return new Gv(e).parse()}const Xv=["===","!==","==","!=",">=","<=",">","<"];function Jv(e,t){const n=e.trim();if(n==="")return{kind:"raw",source:n};if(n===Rd)return{kind:"simple",left:"",op:"truthy",right:""};if(n.startsWith("!")){const s=n.slice(1).trim(),a=nc(s);return Qn(a)?{kind:"simple",left:a,op:"falsy",right:""}:{kind:"raw",source:n}}const o=Zv(n);if(o){const{left:s,op:a,right:l}=o,d=s.trim(),c=l.trim();return Qn(d)&&(c==="''"||c==='""')?{kind:"simple",left:d,op:a,right:""}:Qn(d)&&Qv(c)?{kind:"simple",left:d,op:a,right:c}:{kind:"raw",source:n}}const r=nc(n);return Qn(r)?{kind:"simple",left:r,op:"truthy",right:""}:{kind:"raw",source:n}}function Zv(e){let t=0,n=null;for(let o=0;o=o||!/[A-Za-z_$]/.test(t[n]))return!1;for(n++;n=o||!/[A-Za-z_$]/.test(t[n]))return!1;for(n++;n=2&&(t[0]==="'"||t[0]==='"'||t[0]==="`")?t.slice(1,-1).replace(/\\(['"`\\])/g,"$1"):t}function Rv(e,t){try{return Ar(e)===Ar(t)}catch{return!1}}function Ar(e){const t=Jn(e);return t!==null?Ar(t):tc(e).map(o=>{if(o.kind==="string")return`S:${oc(o.value)}`;if(o.kind==="binding"){const r=/^\{\{([\s\S]*)\}\}$/.exec(o.value);return`B:${r?Ar(r[1].trim()):o.value.replace(/\s+/g,"")}`}return o.kind==="tkey"?`S:${o.value}`:o.kind==="op"?`O:${o.value}`:o.kind==="ident"||o.kind==="number"?`I:${o.value}`:o.kind==="dot"?".":o.kind==="lbracket"?"[":o.kind==="rbracket"?"]":o.kind==="lparen"||o.kind==="rparen"?"":`X:${o.value}`}).join("")}function Rn({active:e,testid:t}){return i.jsx("div",{"data-testid":t,"data-active":e?"true":"false","aria-hidden":"true",style:e?t_:e_})}const e_={height:0,margin:0,transition:"height 80ms"},t_={height:3,margin:"1px 0",borderRadius:2,background:"#2563eb",boxShadow:"0 0 0 1px #93c5fd"};function ys(e){const{length:t,onMove:n,canDrag:o,canDropAt:r}=e,[s,a]=u.useState(null),[l,d]=u.useState(null),c=u.useCallback(m=>{o&&!o(m)||(a(m),d(m))},[o]),p=u.useCallback(()=>{a(null),d(null)},[]),f=u.useCallback((m,h)=>{if(s===null)return;const v=h==="before"?m:m+1;if(r&&!r(s,v)){d(null);return}d(v)},[s,r]),b=u.useCallback(()=>{if(s!==null&&l!==null){const m=l>s?l-1:l;m!==s&&(!r||r(s,l))&&n(s,m)}a(null),d(null)},[s,l,n,r]),g=u.useCallback(m=>s!==null&&l===m&&m<=t,[s,l,t]);return{dragIndex:s,dropIndex:l,dragging:s!==null,onDragStart:c,onDragEnd:p,onDragOverItem:f,onDrop:b,isDropTarget:g}}const n_={text:"",expression:"{{false ? '' : ''}}",fallback:"{{'' ?? ''}}",data:""};function rc(e,t){const n=In(e);let o=t;const r=n.map(s=>({id:o++,kind:s.kind==="literal"?"text":"expression",value:s.raw}));return r.length===0&&r.push({id:o++,kind:"text",value:""}),{segs:r,nextSeq:o}}function bs({value:e,onChange:t,t:n,candidates:o,testidPrefix:r="g7le-seg-value",renderLeafInput:s}){const a=u.useRef(0),l=u.useRef(e),d=u.useMemo(()=>{const w=rc(e,a.current);return a.current=w.nextSeq,w.segs},[]),[c,p]=u.useState(d),[f,b]=u.useState(!1);if(e!==l.current){l.current=e;const w=c.map(k=>k.value).join("");if(e!==w){const k=rc(e,a.current);a.current=k.nextSeq,p(k.segs)}}const g=u.useCallback(w=>{p(w);const k=w.map(x=>x.value).join("");l.current=k,t(k)},[t]),m=u.useCallback((w,k)=>{g(c.map(x=>x.id===w?{...x,value:k??""}:x))},[c,g]),h=u.useCallback(w=>{const k=c.filter(x=>x.id!==w);g(k.length>0?k:[{id:a.current++,kind:"text",value:""}])},[c,g]),v=u.useCallback(w=>{g([...c,{id:a.current++,kind:w,value:n_[w]}])},[c,g]),y=u.useCallback((w,k)=>{if(w===k||w<0||k<0||w>=c.length||k>=c.length)return;const x=c.slice(),[$]=x.splice(w,1);x.splice(k,0,$),g(x)},[c,g]),_=ys({length:c.length,onMove:y});return i.jsxs("div",{"data-testid":r,style:i_,children:[i.jsx("div",{style:s_,children:n("layout_editor.value_tree.segmented_hint")}),c.map((w,k)=>i.jsxs(u.Fragment,{children:[i.jsx(Rn,{active:_.isDropTarget(k),testid:`${r}-dropline-${k}`}),i.jsx(r_,{seg:w,index:k,t:n,candidates:o,testidPrefix:r,renderLeafInput:s,onValueChange:x=>m(w.id,x),onRemove:()=>h(w.id),dragging:_.dragIndex===k,onDragStartCard:()=>_.onDragStart(k),onDragEndCard:_.onDragEnd,onDragOverCard:x=>_.onDragOverItem(k,x),onDropCard:_.onDrop})]},w.id)),i.jsx(Rn,{active:_.isDropTarget(c.length),testid:`${r}-dropline-end`}),i.jsxs("div",{style:p_,"data-testid":`${r}-add-row`,children:[i.jsxs("button",{type:"button","data-testid":`${r}-add-text`,onClick:()=>v("text"),style:Tr,children:["+ ",n("layout_editor.value_tree.segment.text")]}),i.jsxs("button",{type:"button","data-testid":`${r}-add-expression`,onClick:()=>v("expression"),style:Tr,children:["+ ",n("layout_editor.value_tree.segment.expression")]}),i.jsxs("button",{type:"button","data-testid":`${r}-add-fallback`,onClick:()=>v("fallback"),style:Tr,children:["+ ",n("layout_editor.value_tree.segment.fallback")]}),i.jsxs("button",{type:"button","data-testid":`${r}-add-data`,onClick:()=>v("data"),style:Tr,children:["+ ",n("layout_editor.value_tree.segment.data")]})]}),i.jsxs("div",{style:h_,children:[i.jsxs("button",{type:"button","data-testid":`${r}-source-toggle`,"aria-expanded":f,onClick:()=>b(w=>!w),style:y_,children:[""," ",n("layout_editor.value_tree.show_source")," ",f?"▴":"▾"]}),f&&i.jsx("code",{"data-testid":`${r}-source-code`,style:b_,children:c.map(w=>w.value).join("")})]})]})}function o_({t:e,candidates:t,onPick:n,testidPrefix:o}){return!t||t.length===0?i.jsx("div",{"data-testid":`${o}-data-empty`,style:g_,children:e("layout_editor.value_tree.segment.data_no_candidates")}):i.jsxs("div",{"data-testid":`${o}-data-picker`,style:{minWidth:0},children:[i.jsx("div",{style:f_,children:e("layout_editor.value_tree.segment.data_pick_hint")}),i.jsx($t,{candidates:t,t:e,onSelect:r=>{n(En(r.sourceId,r.path,"scalar"))},testIdSuffix:`${o}-data`,defaultOpen:!0,floating:!0})]})}function r_({seg:e,index:t,t:n,candidates:o,testidPrefix:r,renderLeafInput:s,onValueChange:a,onRemove:l,dragging:d,onDragStartCard:c,onDragEndCard:p,onDragOverCard:f,onDropCard:b}){const g=e.kind==="text"?"layout_editor.value_tree.segment.text":e.kind==="data"?"layout_editor.value_tree.segment.data":e.kind==="fallback"?"layout_editor.value_tree.segment.fallback":"layout_editor.value_tree.segment.expression";return i.jsxs("div",{"data-testid":`${r}-card-${t}`,"data-seg-kind":e.kind,style:d?{...ic,opacity:.5}:ic,onDragOver:m=>{m.preventDefault();const h=m.currentTarget.getBoundingClientRect();f(m.clientY{m.preventDefault(),b()},children:[i.jsxs("div",{style:a_,children:[i.jsx("span",{"data-testid":`${r}-drag-${t}`,style:c_,draggable:!0,onDragStart:c,onDragEnd:p,title:n("layout_editor.value_tree.drag_reorder"),"aria-label":n("layout_editor.value_tree.drag_reorder"),role:"button",children:"⠿"}),i.jsx("span",{style:l_,children:n(g)}),i.jsx("button",{type:"button","data-testid":`${r}-remove-${t}`,onClick:l,title:n("layout_editor.value_tree.segment.remove"),"aria-label":n("layout_editor.value_tree.segment.remove"),style:u_,children:"✕"})]}),i.jsx("div",{style:d_,children:e.kind==="data"&&e.value.trim()===""?i.jsx(o_,{t:n,candidates:o,onPick:a,testidPrefix:`${r}-field-${t}`}):s?s({value:e.value,onChange:a,t:n,candidates:o,testidPrefix:`${r}-field-${t}`}):i.jsx(_t,{value:e.value,onChange:a,t:n,candidates:o,testidPrefix:`${r}-field-${t}`,enableExpressionTree:!0,expressionSourceToggle:!1})})]})}const i_={display:"flex",flexDirection:"column",gap:6,width:"100%",minWidth:0},s_={fontSize:11,color:"#64748b"},ic={display:"flex",flexDirection:"column",gap:4,padding:"6px 8px",border:"1px solid #e2e8f0",borderLeft:"3px solid #6366f1",borderRadius:6,background:"#f8fafc",minWidth:0},a_={display:"flex",alignItems:"center",gap:6,minWidth:0},l_={fontSize:10,fontWeight:600,color:"#6366f1",flex:1,minWidth:0},d_={minWidth:0},c_={color:"#94a3b8",cursor:"grab",fontSize:14,userSelect:"none"},u_={border:"none",background:"transparent",color:"#94a3b8",cursor:"pointer",fontSize:12,padding:2,flexShrink:0},p_={display:"flex",flexWrap:"wrap",gap:6,marginTop:2},f_={fontSize:11,color:"#64748b",marginBottom:2},g_={fontSize:11,color:"#94a3b8",padding:"4px 0"},Tr={padding:"4px 10px",fontSize:11,border:"1px dashed #cbd5e1",borderRadius:6,background:"#fff",color:"#475569",cursor:"pointer",whiteSpace:"nowrap"},h_={display:"flex",flexDirection:"column",gap:4,marginTop:2},y_={alignSelf:"flex-start",padding:"3px 8px",fontSize:11,border:"1px solid #cbd5e1",borderRadius:6,background:"#f8fafc",color:"#475569",cursor:"pointer",fontFamily:"monospace"},b_={fontSize:11,background:"#0f172a",color:"#e2e8f0",padding:"6px 8px",borderRadius:4,wordBreak:"break-all",fontFamily:"monospace"},m_=[{value:"truthy",labelKey:"layout_editor.value_tree.cond.op_truthy"},{value:"falsy",labelKey:"layout_editor.value_tree.cond.op_falsy"},{value:"===",labelKey:"layout_editor.value_tree.cond.op_eq"},{value:"!==",labelKey:"layout_editor.value_tree.cond.op_neq"},{value:">",labelKey:"layout_editor.value_tree.cond.op_gt"},{value:"<",labelKey:"layout_editor.value_tree.cond.op_lt"},{value:">=",labelKey:"layout_editor.value_tree.cond.op_gte"},{value:"<=",labelKey:"layout_editor.value_tree.cond.op_lte"}];function sc({node:e,onChange:t,t:n,candidates:o,testidPrefix:r="g7le-value-tree",depth:s=0,showSourceToggle:a=!0,renderLeafInput:l}){const[d,c]=u.useState(!1),p=s===0,f=i.jsx(eo,{node:e,onChange:t,t:n,candidates:o,testidPrefix:r,depth:s,renderLeafInput:l});return p?a?i.jsxs("div",{"data-testid":r,style:lc,children:[f,i.jsxs("div",{style:B_,children:[i.jsxs("button",{type:"button","data-testid":`${r}-source-toggle`,"aria-expanded":d,onClick:()=>c(b=>!b),style:W_,children:[""," ",n("layout_editor.value_tree.show_source")," ",d?"▴":"▾"]}),d&&i.jsx("code",{"data-testid":`${r}-source-code`,style:j_,children:Zn(e,!1)})]})]}):i.jsx("div",{"data-testid":r,style:lc,children:f}):f}function eo({node:e,onChange:t,t:n,candidates:o,testidPrefix:r,depth:s,renderLeafInput:a}){switch(e.kind){case"leaf":return i.jsx(bs,{value:e.text,onChange:l=>t({kind:"leaf",text:l}),t:n,candidates:o,testidPrefix:`${r}-leaf`,renderLeafInput:a});case"conditional":return i.jsx(v_,{node:e,onChange:t,t:n,candidates:o,testidPrefix:r,depth:s,renderLeafInput:a});case"fallback":return i.jsx(__,{node:e,onChange:t,t:n,candidates:o,testidPrefix:r,depth:s,renderLeafInput:a});case"concat":return i.jsx(w_,{node:e,onChange:t,t:n,candidates:o,testidPrefix:r,depth:s,renderLeafInput:a});default:return i.jsxs("div",{"data-testid":`${r}-raw`,style:H_,children:[i.jsx("code",{style:uc,children:e.source??""}),i.jsxs("span",{style:pc,children:["🔒 ",n("layout_editor.value_tree.raw_code_only")]})]})}}function v_({node:e,onChange:t,t:n,candidates:o,testidPrefix:r,depth:s,renderLeafInput:a}){const l=u.useCallback(p=>t({...e,condition:p}),[e,t]),d=u.useCallback(p=>t({...e,then:p}),[e,t]),c=u.useCallback(p=>t({...e,else:p}),[e,t]);return i.jsxs("div",{"data-testid":`${r}-conditional`,style:ms(s),children:[i.jsx(k_,{condition:e.condition,onChange:l,t:n,candidates:o,testidPrefix:r}),i.jsx(Dr,{label:n("layout_editor.value_tree.cond.when_true"),testid:`${r}-then`,children:i.jsx(eo,{node:e.then,onChange:d,t:n,candidates:o,testidPrefix:`${r}-then`,depth:s+1,renderLeafInput:a})}),i.jsx(Dr,{label:n("layout_editor.value_tree.cond.when_false"),testid:`${r}-else`,children:i.jsx(eo,{node:e.else,onChange:c,t:n,candidates:o,testidPrefix:`${r}-else`,depth:s+1,renderLeafInput:a})})]})}function __({node:e,onChange:t,t:n,candidates:o,testidPrefix:r,depth:s,renderLeafInput:a}){const l=u.useCallback(c=>t({...e,primary:c}),[e,t]),d=u.useCallback(c=>t({...e,fallback:c}),[e,t]);return i.jsxs("div",{"data-testid":`${r}-fallback`,style:ms(s),children:[i.jsx(Dr,{label:n("layout_editor.value_tree.fallback.primary"),testid:`${r}-primary`,children:i.jsx(eo,{node:e.primary,onChange:l,t:n,candidates:o,testidPrefix:`${r}-primary`,depth:s+1,renderLeafInput:a})}),i.jsx(Dr,{label:n(e.op==="??"?"layout_editor.value_tree.fallback.when_empty":"layout_editor.value_tree.fallback.when_falsy"),testid:`${r}-fallback-branch`,children:i.jsx(eo,{node:e.fallback,onChange:d,t:n,candidates:o,testidPrefix:`${r}-fallbackb`,depth:s+1,renderLeafInput:a})})]})}const x_={text:()=>({kind:"leaf",text:""}),data:()=>({kind:"leaf",text:""}),expression:()=>({kind:"conditional",condition:{kind:"simple",left:"",op:"truthy",right:""},then:{kind:"leaf",text:""},else:{kind:"leaf",text:""}}),fallback:()=>({kind:"fallback",op:"??",primary:{kind:"leaf",text:""},fallback:{kind:"leaf",text:""}})};function w_({node:e,onChange:t,t:n,candidates:o,testidPrefix:r,depth:s,renderLeafInput:a}){const l=u.useCallback((g,m)=>{const h=e.parts.slice();h[g]=m,t({...e,parts:h})},[e,t]),d=u.useCallback(g=>{g.length===0?t({kind:"leaf",text:""}):g.length===1?t(g[0]):t({...e,parts:g})},[e,t]),c=u.useCallback(g=>d(e.parts.filter((m,h)=>h!==g)),[e.parts,d]),p=u.useCallback(g=>d([...e.parts,x_[g]()]),[e.parts,d]),f=u.useCallback((g,m)=>{if(g===m||g<0||m<0||g>=e.parts.length||m>=e.parts.length)return;const h=e.parts.slice(),[v]=h.splice(g,1);h.splice(m,0,v),d(h)},[e.parts,d]),b=ys({length:e.parts.length,onMove:f});return i.jsxs("div",{"data-testid":`${r}-concat`,style:ms(s),children:[i.jsx("div",{style:z_,children:n("layout_editor.value_tree.concat.hint")}),e.parts.map((g,m)=>i.jsxs(u.Fragment,{children:[i.jsx(Rn,{active:b.isDropTarget(m),testid:`${r}-part-dropline-${m}`}),i.jsxs("div",{"data-testid":`${r}-part-card-${m}`,style:b.dragIndex===m?{...cc,opacity:.5}:cc,onDragOver:h=>{h.preventDefault();const v=h.currentTarget.getBoundingClientRect();b.onDragOverItem(m,h.clientY{h.preventDefault(),b.onDrop()},children:[i.jsxs("div",{style:N_,children:[i.jsx("span",{"data-testid":`${r}-part-handle-${m}`,style:P_,draggable:!0,onDragStart:()=>b.onDragStart(m),onDragEnd:b.onDragEnd,title:n("layout_editor.value_tree.drag_reorder"),"aria-label":n("layout_editor.value_tree.drag_reorder"),role:"button",children:"⠿"}),i.jsx("span",{style:M_,children:n("layout_editor.value_tree.concat.part",{n:m+1})}),i.jsx("button",{type:"button","data-testid":`${r}-part-remove-${m}`,onClick:()=>c(m),title:n("layout_editor.value_tree.segment.remove"),"aria-label":n("layout_editor.value_tree.segment.remove"),style:O_,children:"✕"})]}),i.jsx("div",{style:{minWidth:0},children:i.jsx(eo,{node:g,onChange:h=>l(m,h),t:n,candidates:o,testidPrefix:`${r}-part-${m}`,depth:s+1,renderLeafInput:a})})]})]},m)),i.jsx(Rn,{active:b.isDropTarget(e.parts.length),testid:`${r}-part-dropline-end`}),i.jsxs("div",{style:L_,"data-testid":`${r}-part-add-row`,children:[i.jsxs("button",{type:"button","data-testid":`${r}-part-add-text`,onClick:()=>p("text"),style:zr,children:["+ ",n("layout_editor.value_tree.segment.text")]}),i.jsxs("button",{type:"button","data-testid":`${r}-part-add-expression`,onClick:()=>p("expression"),style:zr,children:["+ ",n("layout_editor.value_tree.segment.expression")]}),i.jsxs("button",{type:"button","data-testid":`${r}-part-add-fallback`,onClick:()=>p("fallback"),style:zr,children:["+ ",n("layout_editor.value_tree.segment.fallback")]}),i.jsxs("button",{type:"button","data-testid":`${r}-part-add-data`,onClick:()=>p("data"),style:zr,children:["+ ",n("layout_editor.value_tree.segment.data")]})]})]})}function k_({condition:e,onChange:t,t:n,candidates:o,testidPrefix:r}){if(e.kind==="raw")return i.jsxs("div",{"data-testid":`${r}-cond-raw`,style:I_,children:[i.jsx("span",{style:dc,children:n("layout_editor.value_tree.cond.label")}),i.jsx("code",{style:uc,children:e.source}),i.jsxs("span",{style:pc,children:["🔒 ",n("layout_editor.value_tree.cond.code_only")]})]});const s=e,a=s.op!=="truthy"&&s.op!=="falsy";return i.jsxs("div",{"data-testid":`${r}-cond`,style:E_,children:[i.jsx("span",{style:dc,children:n("layout_editor.value_tree.cond.label")}),i.jsx(ac,{testid:`${r}-cond-left`,value:s.left,onCommit:l=>t({...s,left:l}),placeholder:n("layout_editor.value_tree.cond.field_placeholder"),candidates:o,t:n}),i.jsx("select",{"data-testid":`${r}-cond-op`,value:s.op,onChange:l=>t({...s,op:l.target.value}),style:D_,children:m_.map(l=>i.jsx("option",{value:l.value,children:n(l.labelKey)},l.value))}),a&&i.jsx(ac,{testid:`${r}-cond-right`,value:s.right,onCommit:l=>t({...s,right:l}),placeholder:n("layout_editor.value_tree.cond.value_placeholder"),candidates:o,t:n,quoteDataPick:!0})]})}function ac({testid:e,value:t,onCommit:n,placeholder:o,candidates:r,t:s,quoteDataPick:a=!1}){const[l,d]=u.useState(null),c=l!==null?l:t,p=()=>{l!==null&&(l!==t&&n(l),d(null))},f=(b,g)=>{const m=En(b,g,"scalar"),h=/^\{\{\s*([\s\S]*?)\s*\}\}$/.exec(m.trim());let v=h?h[1]:m;v=v.replace(/\s*\?\?\s*''$/,"").trim(),n(a?`'${v}'`:v),d(null)};return i.jsxs("span",{style:A_,children:[i.jsx("input",{type:"text","data-testid":e,value:c,onChange:b=>d(b.target.value),onBlur:p,onKeyDown:b=>{b.key==="Enter"&&(b.preventDefault(),p())},placeholder:o,style:T_}),r&&r.length>0&&s&&i.jsx($t,{candidates:r,t:s,onSelect:b=>f(b.sourceId,b.path),testIdSuffix:`${e}-data`,floating:!0})]})}function Dr({label:e,testid:t,children:n}){return i.jsxs("div",{"data-testid":t,style:S_,children:[i.jsx("span",{style:C_,children:e}),i.jsx("div",{style:$_,children:n})]})}const lc={display:"flex",flexDirection:"column",gap:6,width:"100%",minWidth:0},ms=e=>({display:"flex",flexDirection:"column",gap:6,padding:"8px",border:"1px solid #e2e8f0",borderLeft:`3px solid ${e%2===0?"#6366f1":"#0ea5e9"}`,borderRadius:6,background:e%2===0?"#f8fafc":"#fefefe",minWidth:0}),S_={display:"flex",flexDirection:"column",gap:3,minWidth:0},C_={fontSize:11,fontWeight:600,color:"#475569"},$_={paddingLeft:8,minWidth:0},E_={display:"flex",flexWrap:"wrap",gap:4,alignItems:"center",minWidth:0},I_={display:"flex",flexWrap:"wrap",gap:4,alignItems:"center",minWidth:0},dc={fontSize:11,fontWeight:600,color:"#0f172a",whiteSpace:"nowrap"},A_={display:"inline-flex",alignItems:"flex-start",gap:2,flex:"1 1 80px",minWidth:60},T_={flex:"1 1 80px",minWidth:60,padding:"4px 6px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6},D_={padding:"4px 6px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,background:"#fff"},z_={fontSize:11,color:"#94a3b8"},cc={display:"flex",flexDirection:"column",gap:4,padding:"6px 8px",border:"1px solid #e2e8f0",borderLeft:"3px solid #6366f1",borderRadius:6,background:"#fff",minWidth:0},N_={display:"flex",alignItems:"center",gap:6,minWidth:0},M_={fontSize:10,fontWeight:600,color:"#6366f1",flex:1,minWidth:0},L_={display:"flex",flexWrap:"wrap",gap:6,marginTop:2},P_={color:"#94a3b8",cursor:"grab",fontSize:14,userSelect:"none"},O_={border:"none",background:"transparent",color:"#94a3b8",cursor:"pointer",fontSize:12,padding:2,flexShrink:0},zr={padding:"4px 10px",fontSize:11,border:"1px dashed #cbd5e1",borderRadius:6,background:"#fff",color:"#475569",cursor:"pointer",whiteSpace:"nowrap"},B_={display:"flex",flexDirection:"column",gap:4,marginTop:2},W_={alignSelf:"flex-start",padding:"3px 8px",fontSize:11,border:"1px solid #cbd5e1",borderRadius:6,background:"#f8fafc",color:"#475569",cursor:"pointer",fontFamily:"monospace"},j_={fontSize:11,background:"#0f172a",color:"#e2e8f0",padding:"6px 8px",borderRadius:4,wordBreak:"break-all",fontFamily:"monospace"},H_={display:"flex",flexDirection:"column",gap:2,minWidth:0},uc={fontSize:11,background:"#f1f5f9",padding:"3px 6px",borderRadius:4,color:"#0f172a",wordBreak:"break-all",fontFamily:"monospace"},pc={fontSize:10,color:"#64748b"};function _t({value:e,onChange:t,t:n,placeholder:o,testidPrefix:r="g7le-i18n-text-field",locales:s,candidates:a,enableExpressionTree:l=!1,expressionTreeCollapsible:d=!1,expressionSourceToggle:c=!0}){const{locale:p,templateIdentifier:f,commitText:b,classify:g,translate:m}=mv(),[h,v]=u.useState(null),[y,_]=u.useState(!1),[w,k]=u.useState(!1),[x,$]=u.useState(!1),[S,C]=u.useState(!1),[T,E]=u.useState(!1),[,A]=u.useState(0);u.useEffect(()=>{if(typeof window>"u")return;const z=H=>{const P=H.detail;P?.templateIdentifier&&P.templateIdentifier!==f||A(I=>I+1)};return window.addEventListener(kt,z),()=>window.removeEventListener(kt,z)},[f]);const N=g(e);u.useEffect(()=>{v(null),E(!1)},[p,e]);const D=h!==null?h:N.displayValue,B=u.useCallback(async()=>{if(h!==null){if(h===N.displayValue){v(null);return}_(!0);try{const z=await b(e,h);z.kind==="created"&&z.token?(t(z.token),Et({op:"prop_i18n_create_key",sourceState:"plain_text",translationKey:z.customKey,toLocale:p,valueLength:h.length,timestamp:Date.now()})):z.kind==="updated"&&Et({op:"prop_i18n_update_value",sourceState:"custom_key",translationKey:z.customKey,toLocale:p,valueLength:h.length,timestamp:Date.now()})}finally{_(!1),v(null)}}},[h,N.displayValue,b,e,t,p]),M=u.useCallback(z=>{typeof e=="string"&&(t(vo(e,z)),N.customKey&&$r(f,N.customKey,z,p))},[e,t,N.customKey,f,p]);if(N.binding){if(l&&typeof e=="string"){const z=Zd(e),H=un(e),P=!z&&Kt(e)!==null,I=(z||H.decomposed)&&!P;if(I&&d&&!x)return i.jsx(V_,{value:e,t:n,translate:m,onEdit:()=>$(!0),testidPrefix:`${r}-preview`});const F=I&&(d||z)?i.jsx(bs,{value:e,onChange:Y=>t(Y),t:n,candidates:a,testidPrefix:`${r}-seg`}):H.decomposed?i.jsx(sc,{node:H.node,onChange:Y=>t(Zn(Y,!1)),t:n,candidates:a,testidPrefix:`${r}-tree`,showSourceToggle:c}):null;if(F){if(!d)return F;const Y=Er(e),K=Y?gs(Y,X=>{const Z=m(X);return Z&&Z!==X&&!Z.startsWith("$t:")?Z:""},X=>Vt(X)):[];return i.jsxs("div",{"data-testid":`${r}-builder`,style:{display:"flex",flexDirection:"column",gap:6,width:"100%",minWidth:0},children:[i.jsxs("div",{style:{display:"flex",justifyContent:"space-between",alignItems:"center",gap:6},children:[i.jsxs("button",{type:"button","data-testid":`${r}-to-plain`,onClick:()=>C(!0),style:Z_,title:n("layout_editor.value_tree.to_plain"),"aria-label":n("layout_editor.value_tree.to_plain"),children:["↩ ",n("layout_editor.value_tree.to_plain")]}),i.jsxs("button",{type:"button","data-testid":`${r}-builder-collapse`,onClick:()=>$(!1),style:hc,title:n("layout_editor.value_tree.collapse"),"aria-label":n("layout_editor.value_tree.collapse"),children:["▴ ",n("layout_editor.value_tree.collapse")]})]}),S&&i.jsxs("div",{"data-testid":`${r}-to-plain-confirm`,style:Q_,children:[i.jsx("p",{style:R_,children:n("layout_editor.value_tree.to_plain_lead")}),i.jsx("div",{style:ex,children:K.length===0?i.jsx("span",{style:{color:"#94a3b8"},children:n("layout_editor.prop_i18n.placeholder")}):K.map((X,Z)=>X.kind==="chip"?i.jsxs("span",{style:fc,children:["🔗 ",X.text]},Z):i.jsx("span",{style:X.kind==="ellipsis"?gc:void 0,children:X.text},Z))}),i.jsxs("p",{style:tx,children:["⚠ ",n("layout_editor.value_tree.to_plain_warn")]}),i.jsxs("div",{style:{display:"flex",justifyContent:"flex-end",gap:6},children:[i.jsx("button",{type:"button","data-testid":`${r}-to-plain-confirm-cancel`,onClick:()=>C(!1),style:hc,children:n("layout_editor.value_tree.to_plain_cancel")}),i.jsx("button",{type:"button","data-testid":`${r}-to-plain-confirm-ok`,onClick:()=>{C(!1),$(!1),t(Er(e)||void 0)},style:nx,children:n("layout_editor.value_tree.to_plain_ok")})]})]}),F]})}}return l&&typeof e=="string"&&a&&a.length>0&&Kt(e)!==null?i.jsx(U_,{value:e,onChange:t,t:n,candidates:a,testidPrefix:`${r}-data`}):i.jsxs("div",{"data-testid":`${r}-binding`,style:lx,children:[i.jsx("code",{style:dx,children:typeof e=="string"?e:""}),i.jsxs("span",{style:cx,children:["🔗 ",n("layout_editor.prop_i18n.bound_code_only")]})]})}if(!T&&typeof e=="string"&&Xl(e)){const z=Jl(e);return i.jsxs("div",{"data-testid":`${r}-settings-ref`,style:ix,children:[i.jsx("div",{"data-testid":`${r}-settings-ref-chips`,style:sx,children:z.map((H,P)=>H.kind==="chip"?i.jsxs("span",{style:ax,children:["🔗 ",H.label]},P):i.jsx("span",{children:H.label},P))}),i.jsxs("button",{type:"button","data-testid":`${r}-settings-ref-edit`,onClick:()=>E(!0),style:yc,title:n("layout_editor.value_tree.edit"),"aria-label":n("layout_editor.value_tree.edit"),children:["✎ ",n("layout_editor.value_tree.edit")]})]})}const O=i.jsxs("button",{type:"button","data-testid":`${r}-toggle`,title:`${n("layout_editor.prop_i18n.edit_languages")} (${Gn(p,n)})`,"aria-label":`${n("layout_editor.prop_i18n.edit_languages")} (${Gn(p,n)})`,"aria-expanded":w,onClick:()=>k(z=>!z),style:yc,children:["🌐 ",N.customKey?w?"⌃":"⌄":"+"]});return i.jsxs("div",{"data-testid":r,style:{display:"flex",flexDirection:"column",gap:4,width:"100%"},children:[i.jsx("div",{style:ox,children:N.paramKey&&N.customKey?i.jsx(F_,{customKey:N.customKey,value:typeof e=="string"?e:"",onChange:t,t:n,candidates:a,testidPrefix:r,showExprButton:l&&d,onToExpression:()=>{$(!0),t($o(typeof e=="string"?e:""))},langToggle:O}):i.jsxs(i.Fragment,{children:[i.jsx("input",{type:"text","data-testid":`${r}-preview`,value:D,placeholder:o??n("layout_editor.prop_i18n.placeholder"),onChange:z=>v(z.target.value),onBlur:()=>{B(),T&&E(!1)},disabled:y,style:rx}),a&&a.length>0&&i.jsx(K_,{value:typeof e=="string"?e:"",customKey:N.customKey,onChange:t,t:n,candidates:a,testidPrefix:r}),l&&d&&i.jsx("button",{type:"button","data-testid":`${r}-to-expr`,onClick:()=>{$(!0),t($o(typeof e=="string"?e:""))},title:n("layout_editor.value_tree.to_expression"),"aria-label":n("layout_editor.value_tree.to_expression"),style:Ao,children:"ƒx"}),O]})}),w&&(N.customKey?i.jsx(Xd,{customKey:N.customKey,templateIdentifier:f,t:n,paramLabels:ps(e),locales:s,onRemoveParam:M},N.customKey):i.jsx("div",{"data-testid":`${r}-expand-hint`,style:ux,children:n("layout_editor.prop_i18n.create_first")}))]})}function F_({customKey:e,value:t,onChange:n,t:o,candidates:r,testidPrefix:s,showExprButton:a=!1,onToExpression:l,langToggle:d}){const{state:c}=Qe(),p=c.templateIdentifier,f=c.locale,b=Ft(),[g,m]=u.useState(null),h=u.useRef(""),v=u.useRef(null),y=u.useRef(null),_=u.useRef(null),[w,k]=u.useState(null),[x,$]=u.useState(!1),S=ps(t),C=u.useCallback(async()=>{const D=It(e,f);if(D!==void 0){h.current=D,m(D);return}const M=(await cn(p,e))?.values?.[f]??"";h.current=M,m(M)},[e,p,f]);u.useEffect(()=>{let D=!1;return(async()=>{const B=It(e,f);if(B!==void 0){D||(h.current=B,m(B));return}const M=await cn(p,e);if(D)return;const O=M?.values?.[f]??"";h.current=O,m(O)})(),()=>{D=!0}},[e,p,f]),u.useEffect(()=>{if(typeof window>"u")return;const D=B=>{const M=B.detail;if(M?.templateIdentifier&&M.templateIdentifier!==p)return;const O=It(e,f);O!==void 0&&O!==g&&(h.current=O,m(O))};return window.addEventListener(kt,D),()=>window.removeEventListener(kt,D)},[e,p,f,g]);const T=u.useCallback(D=>{m(D),So(p,e,f,D).then(()=>{b?.markDirty?.()})},[p,e,f,b]),E=u.useCallback(D=>{n(vo(t,D)),$r(p,e,D,f).then(()=>{C(),b?.markDirty?.()})},[t,n,p,e,f,C,b]),A=u.useCallback(async D=>{if(!(w===null||x)){$(!0);try{g!==null&&g!==h.current&&(await So(p,e,f,g),h.current=g);const B=await Cr(p,f,t,w,D.sourceId,D.path,"scalar");B.kind==="ok"&&(n(B.text),await C(),b?.markDirty?.())}finally{$(!1),k(null)}}},[w,x,g,t,p,e,f,n,C,b]),N=!!r&&r.length>0;return i.jsxs("div",{"data-testid":`${s}-chipfield-wrap`,style:{flex:1,minWidth:0,display:"flex",alignItems:"center",gap:4},children:[i.jsx("div",{ref:v,"data-testid":`${s}-chipfield`,style:{flex:1,minWidth:0},children:g===null?i.jsx("div",{"data-testid":`${s}-chipfield-loading`,style:{fontSize:11,color:"#94a3b8",padding:"4px 0"},children:o("layout_editor.translation.loading")}):i.jsx(xr,{value:g,onChange:T,t:o,paramLabels:S,testIdSuffix:`${s}-field`,onRequestInsert:N?D=>k(D):void 0,onRemoveChip:E,hideInsertButton:!0,caretRef:_})}),N&&i.jsx("button",{ref:y,type:"button","data-testid":`${s}-plus-data-btn`,onClick:()=>k(_.current?_.current():g?.length??0),title:o("layout_editor.inline_binding.insert_data"),"aria-label":o("layout_editor.inline_binding.insert_data"),style:Ao,children:"+"}),a&&l&&i.jsx("button",{type:"button","data-testid":`${s}-to-expr`,onClick:l,title:o("layout_editor.value_tree.to_expression"),"aria-label":o("layout_editor.value_tree.to_expression"),style:Ao,children:"ƒx"}),d,N&&i.jsx(Xn,{anchorRef:y,open:w!==null,onClose:()=>k(null),children:i.jsx("div",{"data-testid":`${s}-chipfield-picker`,children:i.jsx($t,{candidates:r,t:o,onSelect:D=>{A(D)},testIdSuffix:`${s}-field-insert`,defaultOpen:!0,floating:!1})})})]})}function K_({value:e,customKey:t,onChange:n,t:o,candidates:r,testidPrefix:s}){const{state:a}=Qe(),l=a.templateIdentifier,d=a.selectedRoute?.layoutName??null,c=a.locale,p=Ft(),[f,b]=u.useState(!1),[g,m]=u.useState(!1),h=u.useRef(null),v=u.useCallback(w=>{const k=o(w);return k&&k!==w&&!k.startsWith("$t:")?k:""},[o]),y=u.useCallback(w=>w.replace(/\$t:[a-zA-Z0-9._-]+/g,k=>v(k.slice(3))).replace(/\s+/g," ").trim(),[v]),_=u.useCallback(async w=>{if(!g){m(!0);try{if(t){const k=await Cr(l,c,e,Number.MAX_SAFE_INTEGER,w.sourceId,w.path,"scalar");k.kind==="ok"&&(n(k.text),p?.markDirty?.())}else{const k=await us(l,d,c,e,e.length,w.sourceId,w.path,"scalar",y,x=>o(x));k.kind==="ok"&&(n(k.text),p?.markDirty?.())}}finally{m(!1),b(!1)}}},[g,t,l,d,c,e,n,p,o,y]);return i.jsxs("div",{"data-testid":`${s}-plus-data`,style:{display:"inline-block"},children:[i.jsx("button",{ref:h,type:"button","data-testid":`${s}-plus-data-btn`,disabled:g,onClick:()=>b(w=>!w),title:o("layout_editor.inline_binding.insert_data"),"aria-label":o("layout_editor.inline_binding.insert_data"),style:Ao,children:"+"}),i.jsx(Xn,{anchorRef:h,open:f,onClose:()=>b(!1),children:i.jsx($t,{candidates:r,t:o,onSelect:w=>{_(w)},testIdSuffix:`${s}-plus-insert`,defaultOpen:!0,floating:!1})})]})}function V_({value:e,t,translate:n,onEdit:o,testidPrefix:r}){const s=gs(e,a=>{const l=n(a);return l&&l!==a&&!l.startsWith("$t:")?l:""},a=>Vt(a));return i.jsxs("div",{"data-testid":r,style:Y_,children:[i.jsx("div",{"data-testid":`${r}-text`,style:X_,children:s.length===0?i.jsx("span",{style:{color:"#94a3b8"},children:t("layout_editor.prop_i18n.placeholder")}):s.map((a,l)=>a.kind==="chip"?i.jsxs("span",{style:fc,children:["🔗 ",a.text]},l):i.jsx("span",{style:a.kind==="ellipsis"?gc:void 0,children:a.text},l))}),i.jsxs("button",{type:"button","data-testid":`${r}-edit`,onClick:o,style:J_,title:t("layout_editor.value_tree.edit"),"aria-label":t("layout_editor.value_tree.edit"),children:["✎ ",t("layout_editor.value_tree.edit")]})]})}function U_({value:e,onChange:t,t:n,candidates:o,testidPrefix:r}){const[s,a]=u.useState(!1),l=u.useRef(null),d=Vt(e);return i.jsxs("div",{"data-testid":r,style:{display:"flex",flexDirection:"column",gap:4,width:"100%",minWidth:0},children:[i.jsxs("div",{style:q_,children:[i.jsxs("span",{"data-testid":`${r}-chip`,style:G_,children:["🔗 ",d]}),i.jsx("button",{ref:l,type:"button","data-testid":`${r}-change`,onClick:()=>a(c=>!c),style:Ao,title:n("layout_editor.value_tree.change_data"),"aria-label":n("layout_editor.value_tree.change_data"),children:n("layout_editor.value_tree.change_data")})]}),i.jsx(Xn,{anchorRef:l,open:s,onClose:()=>a(!1),children:i.jsx($t,{candidates:o,t:n,onSelect:c=>{const p=c.path?c.path.split(".").filter(Boolean):[],f=[c.sourceId,...p].join("?.");t(`{{${f}}}`),a(!1)},testIdSuffix:`${r}-pick`,defaultOpen:!0,floating:!1})})]})}const q_={display:"flex",alignItems:"center",gap:6,width:"100%",minWidth:0,flexWrap:"wrap"},G_={fontSize:11,background:"#eef2ff",color:"#4338ca",padding:"3px 8px",borderRadius:12,wordBreak:"break-all"},Y_={display:"flex",alignItems:"center",gap:8,width:"100%",minWidth:0},X_={flex:1,minWidth:0,display:"flex",flexWrap:"wrap",alignItems:"center",gap:4,padding:"5px 8px",fontSize:12,color:"#334155",border:"1px solid #e2e8f0",borderRadius:6,background:"#f8fafc",overflow:"hidden"},fc={fontSize:11,background:"#eef2ff",color:"#4338ca",padding:"1px 6px",borderRadius:10,whiteSpace:"nowrap"},gc={color:"#94a3b8"},J_={padding:"4px 10px",fontSize:11,border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",color:"#2563eb",cursor:"pointer",whiteSpace:"nowrap",flexShrink:0},hc={padding:"3px 10px",fontSize:11,border:"1px solid #cbd5e1",borderRadius:6,background:"#f8fafc",color:"#475569",cursor:"pointer",whiteSpace:"nowrap"},Z_={padding:"3px 10px",fontSize:11,border:"1px solid #fca5a5",borderRadius:6,background:"#fef2f2",color:"#b91c1c",cursor:"pointer",whiteSpace:"nowrap"},Q_={display:"flex",flexDirection:"column",gap:6,padding:10,border:"1px solid #fca5a5",borderRadius:8,background:"#fffbeb"},R_={margin:0,fontSize:12,color:"#334155"},ex={padding:"5px 8px",fontSize:12,color:"#0f172a",background:"#fff",border:"1px solid #e2e8f0",borderRadius:6,wordBreak:"break-all"},tx={margin:0,fontSize:11,color:"#b45309"},nx={padding:"3px 12px",fontSize:11,border:"1px solid #dc2626",borderRadius:6,background:"#dc2626",color:"#fff",cursor:"pointer",whiteSpace:"nowrap"},ox={display:"flex",gap:4,alignItems:"center",width:"100%",minWidth:0},rx={flex:1,padding:"5px 8px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,minWidth:0,width:"100%"},yc={padding:"4px 8px",fontSize:11,border:"1px solid #cbd5e1",borderRadius:6,background:"#f8fafc",color:"#475569",cursor:"pointer",whiteSpace:"nowrap"},Ao={padding:"4px 8px",fontSize:13,lineHeight:1,border:"1px solid #cbd5e1",borderRadius:6,background:"#f8fafc",color:"#475569",cursor:"pointer",whiteSpace:"nowrap"},ix={display:"flex",alignItems:"center",gap:6,width:"100%",minWidth:0,flexWrap:"wrap"},sx={flex:1,minWidth:0,display:"flex",flexWrap:"wrap",alignItems:"center",gap:4,fontSize:12,color:"#334155"},ax={fontSize:11,background:"#eef2ff",color:"#4338ca",padding:"2px 8px",borderRadius:12,whiteSpace:"nowrap"},lx={display:"flex",flexDirection:"column",gap:2,width:"100%"},dx={fontSize:11,background:"#f1f5f9",padding:"3px 6px",borderRadius:4,color:"#0f172a",wordBreak:"break-all"},cx={fontSize:10,color:"#64748b"},ux={fontSize:11,color:"#94a3b8",padding:"6px 8px",border:"1px dashed #cbd5e1",borderRadius:6,background:"#f8fafc"};function vs({value:e,onChange:t,candidates:n,t:o}){const r=Array.isArray(e)?e:[],s=n??[],[a,l]=u.useState(!1),d=g=>s.find(m=>m.value===g)?.label??g,c=g=>d(g)!==g,p=g=>{const m=r.filter(h=>h!==g);t(m.length===0?void 0:m)},f=g=>{r.includes(g)||(t([...r,g]),l(!1))},b=s.filter(g=>!r.includes(g.value));return i.jsxs("div",{className:"g7le-widget g7le-widget--tag-input","data-testid":"g7le-widget-tag-input",style:px,children:[i.jsxs("div",{style:fx,children:[r.map(g=>i.jsxs("span",{"data-testid":`g7le-tag-chip-${g}`,style:gx,children:[i.jsx("span",{style:hx,children:d(g)}),c(g)&&i.jsx("code",{"data-testid":`g7le-tag-chip-id-${g}`,style:yx,children:g}),i.jsx("button",{type:"button","aria-label":`remove ${g}`,"data-testid":`g7le-tag-remove-${g}`,onClick:()=>p(g),style:bx,children:"✕"})]},g)),i.jsx("button",{type:"button","data-testid":"g7le-tag-add",onClick:()=>l(g=>!g),disabled:b.length===0,style:mx,children:o("layout_editor.control.tag_add")})]}),a&&b.length>0&&i.jsx("div",{"data-testid":"g7le-tag-candidates",style:vx,children:b.map(g=>{const m=g.value!==g.label;return i.jsxs("button",{type:"button","data-testid":`g7le-tag-candidate-${g.value}`,onClick:()=>f(g.value),style:_x,children:[i.jsx("span",{style:xx,children:g.label}),m&&i.jsx("code",{"data-testid":`g7le-tag-candidate-id-${g.value}`,style:wx,children:g.value})]},g.value)})})]})}const px={display:"flex",flexDirection:"column",gap:6},fx={display:"flex",flexWrap:"wrap",gap:4,alignItems:"center"},gx={display:"inline-flex",alignItems:"center",gap:4,padding:"2px 6px",fontSize:12,background:"#e0e7ff",color:"#1d4ed8",borderRadius:12},hx={fontSize:12},yx={fontSize:10,color:"#4f46e5",background:"#c7d2fe",borderRadius:4,padding:"0 4px",fontFamily:"monospace"},bx={border:"none",background:"transparent",color:"#1d4ed8",cursor:"pointer",fontSize:10,padding:0,lineHeight:1},mx={padding:"2px 8px",fontSize:12,border:"1px dashed #94a3b8",borderRadius:12,background:"#fff",cursor:"pointer",color:"#475569"},vx={display:"flex",flexDirection:"column",maxHeight:180,overflowY:"auto",border:"1px solid #cbd5e1",borderRadius:6,background:"#fff"},_x={display:"flex",flexDirection:"column",gap:1,textAlign:"left",padding:"6px 10px",fontSize:12,border:"none",borderBottom:"1px solid #f1f5f9",background:"transparent",cursor:"pointer",color:"#0f172a"},xx={fontSize:12,color:"#0f172a"},wx={fontSize:10,color:"#64748b",fontFamily:"monospace"},bc=new Map;function Ze(e,t){bc.set(e,t)}function Tn(e){return e?bc.get(e)??null:null}function Nr(e,t){const o=e("meta",{})?.[t];return typeof o=="string"?o:""}function kx({getValue:e,patch:t,t:n,fieldT:o,spec:r,permissionCandidates:s,bindingCandidates:a}){const l=o??n,d=Nr(e,"title"),c=Nr(e,"description"),p=Nr(e,"icon"),f=Nr(e,"editor_label"),b=e("permissions",[]),g=u.useCallback((y,_)=>{const w={...e("meta",{})??{}};_===void 0||_===""?delete w[y]:w[y]=_,t("meta",w)},[e,t]),m=u.useCallback(y=>{const _=Array.isArray(y)?y:[];_.length===0?t("permissions",void 0):t("permissions",_)},[t]),h=Tn("icon-picker"),v=Ct(r,"icon-picker")??Ct(r,"icon")??(r?.controls?Object.values(r.controls).find(y=>y&&typeof y=="object"&&y.widget==="icon-picker")??null:null);return i.jsxs("div",{className:"g7le-meta-form","data-testid":"g7le-meta-form",style:Cx,children:[i.jsx(To,{label:n("layout_editor.page_settings.meta.title"),children:i.jsx(_t,{value:d,onChange:y=>g("title",y),t:l,testidPrefix:"g7le-meta-title",candidates:a,enableExpressionTree:!0,expressionTreeCollapsible:!0})}),i.jsx(To,{label:n("layout_editor.page_settings.meta.description"),children:i.jsx(_t,{value:c,onChange:y=>g("description",y),t:l,testidPrefix:"g7le-meta-description",candidates:a,enableExpressionTree:!0,expressionTreeCollapsible:!0})}),i.jsx(To,{label:n("layout_editor.page_settings.meta.icon"),children:h?i.jsx(Sx,{icon:p,t:n,onChange:y=>g("icon",typeof y=="string"?y:void 0),IconWidget:h,iconControl:v??{widget:"icon-picker"}}):i.jsx("input",{type:"text","data-testid":"g7le-meta-icon",value:p,placeholder:n("layout_editor.page_settings.meta.icon_placeholder"),onChange:y=>g("icon",y.target.value),style:Ax})}),i.jsx(To,{label:n("layout_editor.page_settings.meta.editor_label"),hint:n("layout_editor.page_settings.meta.editor_label_hint"),children:i.jsx(_t,{value:f,onChange:y=>g("editor_label",y),t:l,testidPrefix:"g7le-meta-editor-label",candidates:a,enableExpressionTree:!0,expressionTreeCollapsible:!0})}),i.jsx(To,{label:n("layout_editor.page_settings.meta.permissions"),hint:n("layout_editor.page_settings.meta.permissions_hint"),children:i.jsx("div",{"data-testid":"g7le-meta-permissions",children:i.jsx(vs,{control:{widget:"tag-input"},value:b,onChange:m,t:n,candidates:s})})})]})}function To({label:e,hint:t,children:n}){return i.jsxs("div",{className:"g7le-meta-field",style:$x,children:[i.jsx("label",{style:Ex,children:e}),n,t?i.jsxs("p",{style:Ix,children:["ⓘ ",t]}):null]})}function Sx({icon:e,onChange:t,t:n,IconWidget:o,iconControl:r}){const[s,a]=u.useState(!1),l=e?/\bfa-/.test(e)?i.jsx("i",{className:e,"aria-hidden":"true"}):e:"📄";return i.jsxs("div",{"data-testid":"g7le-meta-icon",style:{display:"flex",flexDirection:"column",gap:6,minWidth:0},children:[i.jsxs("button",{type:"button","data-testid":"g7le-meta-icon-toggle","aria-expanded":s,onClick:()=>a(d=>!d),style:{display:"flex",alignItems:"center",gap:8,padding:"5px 8px",border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",cursor:"pointer",fontSize:12,color:"#475569",width:"fit-content"},children:[i.jsx("span",{style:{fontSize:16},"aria-hidden":"true",children:l}),i.jsx("span",{children:e||n("layout_editor.page_settings.meta.icon_placeholder")}),i.jsx("span",{"aria-hidden":"true",children:s?"▴":"▾"})]}),s?i.jsx(o,{control:r,value:e,onChange:d=>{t(d),a(!1)},t:n}):null]})}const Cx={display:"flex",flexDirection:"column",gap:16,minWidth:0},$x={display:"flex",flexDirection:"column",gap:6,minWidth:0},Ex={fontSize:13,fontWeight:600,color:"#0f172a"},Ix={margin:0,fontSize:11,color:"#94a3b8"},Ax={padding:"5px 8px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,minWidth:0,width:"100%"};function Mr({checked:e,onChange:t,label:n,testid:o,ariaLabel:r,disabled:s=!1}){return i.jsxs("label",{style:{...Mx,...s?{cursor:"not-allowed"}:{}},children:[i.jsx("button",{type:"button","data-testid":o,role:"switch","aria-checked":e,"aria-label":r,disabled:s,onClick:()=>t(!e),style:Lx(e,s),children:i.jsx("span",{style:Px(e)})}),n!=null?i.jsx("span",{style:Ox(s),children:n}):null]})}function Lr({disabled:e,children:t,testid:n,style:o}){return i.jsx("div",{"data-testid":n,"data-disabled":e?"true":"false","aria-disabled":e,style:{...o,...e?{opacity:.5,pointerEvents:"none",userSelect:"none"}:{}},children:t})}function Tx({filled:e,sourceLabel:t,onRevert:n,revertLabel:o,hint:r,children:s,testid:a}){const l=e&&typeof n=="function",d=!e&&t!=null&&t!=="";return i.jsxs("div",{"data-testid":a,style:Dx,children:[s,l?i.jsxs("button",{type:"button","data-testid":a?`${a}-revert`:void 0,onClick:n,title:r??o,"aria-label":o,style:Nx,children:["↩ ",o]}):d?i.jsx("span",{"data-testid":a?`${a}-source`:void 0,title:r,style:zx,children:t}):null]})}const Dx={position:"relative",minWidth:0,width:"100%"},zx={position:"absolute",right:8,top:"50%",transform:"translateY(-50%)",fontSize:10,color:"#94a3b8",background:"#f1f5f9",borderRadius:4,padding:"1px 6px",pointerEvents:"none",maxWidth:"45%",overflow:"hidden",whiteSpace:"nowrap",textOverflow:"ellipsis"},Nx={position:"absolute",right:6,top:"50%",transform:"translateY(-50%)",fontSize:10,color:"#0369a1",background:"#e0f2fe",border:"1px solid #bae6fd",borderRadius:4,padding:"1px 6px",cursor:"pointer",whiteSpace:"nowrap"},Mx={display:"inline-flex",alignItems:"center",gap:8,cursor:"pointer"},Lx=(e,t)=>({position:"relative",width:38,height:22,flexShrink:0,borderRadius:999,border:"none",padding:0,background:t?"#e2e8f0":e?"#2563eb":"#cbd5e1",cursor:t?"not-allowed":"pointer",transition:"background 0.15s ease"}),Px=e=>({position:"absolute",top:2,left:e?18:2,width:18,height:18,borderRadius:"50%",background:"#fff",boxShadow:"0 1px 2px rgba(0,0,0,0.2)",transition:"left 0.15s ease"}),Ox=e=>({fontSize:13,fontWeight:600,color:e?"#94a3b8":"#0f172a"}),Bx=/\$(?:core|module|plugin)_settings:[a-zA-Z0-9._:-]+/;function Wx(e){const t=[];let n=!1;const o=s=>{t.push({kind:"text",text:s}),n=!0},r=(s,a,l)=>{n||o(""),t.push({kind:"chip",raw:s,text:a,removable:l}),n=!1};for(const s of jx(e))s.kind==="text"?o(s.raw):r(s.raw,s.label,s.removable);return n||o(""),t}function jx(e){const t=[];if(typeof e!="string"||e.length===0)return t;const n=e.length;let o=0,r=0;const s=a=>{a>r&&t.push({kind:"text",raw:e.slice(r,a),label:"",removable:!1})};for(;o=n){o++;continue}s(o);const l=e.slice(o,a+2);t.push({kind:"chip",raw:l,label:Vt(l),removable:!0}),o=a+2,r=o;continue}if(e[o]==="$"){const a=Bx.exec(e.slice(o));if(a&&a.index===0){s(o);const l=a[0],d=l.split(":").pop()||l;t.push({kind:"chip",raw:l,label:d,removable:!1}),o+=l.length,r=o;continue}}o++}return s(n),t}function Hx(e,t,n,o){const r=[];let s=!1;return e.forEach((a,l)=>{if(a.kind==="chip"){if(!s&&a.raw===t){s=!0;return}r.push(a.raw??"")}else{const d=a.text??"";if(l===n){const c=Math.max(0,Math.min(o,d.length));r.push(d.slice(0,c),t,d.slice(c))}else r.push(d)}}),r.join("")}function Fx(e,t,n){const o=Math.max(0,Math.min(t,e.length));return e.slice(0,o)+n+e.slice(o)}function Kx(e,t){const n=e.indexOf(t);return n<0?e:e.slice(0,n)+e.slice(n+t.length)}function _s({value:e,onChange:t,t:n,candidates:o,onDone:r,testidPrefix:s="g7le-binding-chip-text",disabled:a=!1}){const l=u.useMemo(()=>Wx(e),[e]),d=u.useRef(null),c=!!o&&o.length>0,[p,f]=u.useState(null),[b,g]=u.useState(null),[m,h]=u.useState(null),[v,y]=u.useState(!1),_=u.useRef(0),w=u.useRef(null),k=u.useMemo(()=>l.map(P=>P.kind==="chip"?`c:${P.raw}`:"t").join("|"),[l]),x=u.useRef(null),$=u.useRef([]),S=u.useRef(!1);u.useLayoutEffect(()=>{const P=S.current;S.current=!1,!(!P&&x.current===k)&&(x.current=k,l.forEach((I,L)=>{if(I.kind!=="text")return;const F=$.current[L];F&&F.textContent!==(I.text??"")&&(F.textContent=I.text??"")}))},[k,l,e]);const C=u.useCallback(()=>{const P=d.current;if(!P)return e;const I=[];for(const L of Array.from(P.querySelectorAll("[data-seg]")))L.dataset.seg==="chip"?I.push(L.dataset.raw??""):I.push(L.textContent??"");return I.join("")},[e]),T=u.useCallback(()=>{a||t(C())},[a,t,C]),E=u.useRef(null),A=u.useRef(!1),N=(P,I)=>{if(!a){P.preventDefault(),P.stopPropagation(),E.current={raw:I,pointerId:P.pointerId},A.current=!1,f(I);try{P.currentTarget.setPointerCapture(P.pointerId)}catch{}}},D=P=>{const I=E.current;if(!I||P.pointerId!==I.pointerId)return;A.current=!0;const L=O(P.clientX,P.clientY);L&&(g({slotIdx:L.slotIdx,offset:L.offset}),h(Ux(d.current,L.slotIdx,L.offset)))},B=(P,I)=>{if(P.stopPropagation(),A.current){A.current=!1;return}S.current=!0,t(Kx(e,I))},M=P=>{const I=E.current;if(!I||P.pointerId!==I.pointerId)return;try{P.currentTarget.releasePointerCapture(P.pointerId)}catch{}E.current=null,A.current&&typeof setTimeout=="function"&&setTimeout(()=>{A.current=!1},0);const L=O(P.clientX,P.clientY);if(f(null),g(null),h(null),!L)return;const F=d.current;if(!F)return;const K=Array.from(F.querySelectorAll("[data-seg]")).map(Z=>Z.dataset.seg==="chip"?{kind:"chip",raw:Z.dataset.raw??""}:{kind:"text",text:Z.textContent??""}),X=Hx(K,I.raw,L.slotIdx,L.offset);X!==e&&(S.current=!0,t(X))},O=(P,I)=>{const L=d.current;if(!L)return null;const F=Array.from(L.querySelectorAll("[data-seg]")),Y=L.ownerDocument,K=Y.elementFromPoint(P,I),X=K?.closest('[data-seg="text"]');if(X)return{slotIdx:F.indexOf(X),offset:Vx(Y,X,P,I)};const Z=K?.closest('[data-seg="chip"]');if(Z){const oe=F.indexOf(Z),G=Z.getBoundingClientRect();if(P=0){const ee=F[oe-1];return{slotIdx:oe-1,offset:(ee.textContent??"").length}}if(oe+1({s:oe,i:G})).filter(oe=>oe.s.dataset.seg==="text").pop();return se?{slotIdx:se.i,offset:(se.s.textContent??"").length}:null},z=()=>{a||!c||(_.current=qx(d.current,e.length),y(P=>!P))},H=P=>{const I=P.path?P.path.split(".").filter(Boolean):[],L=[P.sourceId,...I].join("?.");S.current=!0,t(Fx(e,_.current,`{{${L}}}`)),y(!1)};return i.jsxs("div",{"data-testid":s,style:{display:"flex",flexDirection:"column",gap:4},children:[i.jsxs("div",{ref:d,"data-testid":`${s}-box`,className:"g7le-binding-chip-input",style:Gx,children:[l.map((P,I)=>P.kind==="chip"?i.jsxs("span",{"data-seg":"chip","data-raw":P.raw,"data-testid":`${s}-chip-${I}`,contentEditable:!1,onPointerDown:L=>N(L,P.raw??""),onPointerMove:D,onPointerUp:M,title:P.removable?n("layout_editor.value_tree.change_data"):n("layout_editor.prop_i18n.bound_code_only"),style:{...Zx,...p===P.raw?Qx:null,touchAction:"none"},children:[i.jsx("span",{"aria-hidden":"true",style:Rx,children:"⠿"}),i.jsx("span",{"aria-hidden":"true",style:{marginRight:3},children:"🔗"}),i.jsx("span",{children:P.text}),P.removable&&!a&&i.jsx("button",{type:"button","data-testid":`${s}-chip-remove-${I}`,"aria-label":n("layout_editor.inline_binding.clear"),title:n("layout_editor.inline_binding.clear"),onPointerDown:L=>{L.stopPropagation(),L.preventDefault()},onClick:L=>B(L,P.raw??""),style:ew,children:"✕"})]},`chip-${P.raw}-${I}`):i.jsx("span",{ref:L=>{$.current[I]=L},"data-seg":"text",contentEditable:!a,suppressContentEditableWarning:!0,onInput:T,"data-testid":`${s}-text-${I}`,style:{...Yx,...p?Xx:null,...b?.slotIdx===I?Jx:null}},`text-slot-${I}`)),m&&i.jsx("span",{"aria-hidden":"true","data-testid":`${s}-caret`,style:{position:"absolute",left:m.left,top:m.top,width:2,height:m.height,background:"#2563eb",borderRadius:1,pointerEvents:"none",boxShadow:"0 0 0 1px rgba(37,99,235,0.3)"}})]}),i.jsxs("div",{style:{display:"flex",alignItems:"center",gap:6,flexWrap:"wrap"},children:[r&&i.jsxs("button",{type:"button","data-testid":`${s}-done`,onClick:r,style:mc,title:n("layout_editor.value_tree.done"),"aria-label":n("layout_editor.value_tree.done"),children:["✓ ",n("layout_editor.value_tree.done")]}),c&&i.jsxs("div",{style:{position:"relative"},children:[i.jsxs("button",{ref:w,type:"button","data-testid":`${s}-insert`,onClick:z,disabled:a,style:mc,title:n("layout_editor.inline_binding.insert_data"),"aria-label":n("layout_editor.inline_binding.insert_data"),children:["+ ",n("layout_editor.inline_binding.insert_data")]}),i.jsx(Xn,{anchorRef:w,open:v,onClose:()=>y(!1),children:i.jsx($t,{candidates:o,t:n,onSelect:H,testIdSuffix:`${s}-insert`,defaultOpen:!0,floating:!1})})]}),i.jsx("span",{style:tw,children:n("layout_editor.translation.placeholder_locked")})]})]})}function Vx(e,t,n,o){const r=(t.textContent??"").length;try{const a=e;let l=null,d=0;if(typeof a.caretRangeFromPoint=="function"){const c=a.caretRangeFromPoint(n,o);c&&(l=c.startContainer,d=c.startOffset)}else if(typeof a.caretPositionFromPoint=="function"){const c=a.caretPositionFromPoint(n,o);c&&(l=c.offsetNode,d=c.offset)}if(l&&t.contains(l))return Math.max(0,Math.min(d,r))}catch{}const s=t.getBoundingClientRect();return n>s.left+s.width/2?r:0}function Ux(e,t,n){if(!e)return null;const r=Array.from(e.querySelectorAll("[data-seg]"))[t];if(!r)return null;const s=e.getBoundingClientRect();try{const d=r.firstChild,c=e.ownerDocument;if(d&&d.nodeType===Node.TEXT_NODE&&(d.textContent??"").length>0){const p=c.createRange(),f=Math.max(0,Math.min(n,(d.textContent??"").length));p.setStart(d,f),p.setEnd(d,f);const b=p.getClientRects(),g=b.length>0?b[0]:p.getBoundingClientRect();if(g&&(g.height>0||g.width>=0))return{left:g.left-s.left,top:g.top-s.top,height:g.height||r.getBoundingClientRect().height}}}catch{}const a=r.getBoundingClientRect();return{left:(n>0?a.right:a.left)-s.left,top:a.top-s.top,height:a.height||16}}function qx(e,t){if(!e)return t;try{const n=e.ownerDocument.defaultView?.getSelection();if(!n||n.rangeCount===0)return t;const o=n.getRangeAt(0);let r=o.startContainer,s=null;for(;r&&r!==e;){if(r instanceof HTMLElement&&r.dataset.seg==="text"){s=r;break}r=r.parentNode}if(!s||!e.contains(s))return t;const a=Array.from(e.querySelectorAll("[data-seg]"));let l=0;for(const d of a){if(d===s)break;l+=d.dataset.seg==="chip"?(d.dataset.raw??"").length:(d.textContent??"").length}return l+o.startOffset}catch{return t}}const Gx={position:"relative",display:"flex",flexWrap:"wrap",alignItems:"center",gap:2,minHeight:30,padding:"4px 8px",border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",fontSize:12,lineHeight:1.6,minWidth:0,width:"100%",boxSizing:"border-box"},Yx={outline:"none",minWidth:8,minHeight:"1.2em",whiteSpace:"pre-wrap",color:"#0f172a",display:"inline-block",wordBreak:"break-all"},Xx={boxShadow:"inset 0 -2px 0 #93c5fd",background:"#eff6ff",borderRadius:2},Jx={background:"#dbeafe",boxShadow:"inset 0 -2px 0 #2563eb"},Zx={display:"inline-flex",alignItems:"center",gap:1,background:"linear-gradient(180deg,#eff6ff,#dbeafe)",color:"#1d4ed8",border:"1px dashed #60a5fa",borderRadius:9999,padding:"1px 8px 1px 4px",margin:"0 1px",fontSize:11,fontWeight:600,cursor:"grab",userSelect:"none",whiteSpace:"nowrap",boxShadow:"0 1px 2px rgba(37,99,235,0.18)",verticalAlign:"middle"},Qx={opacity:.5,borderStyle:"solid",cursor:"grabbing"},Rx={color:"#2563eb",opacity:.7,fontSize:11,marginRight:1,cursor:"grab"},ew={marginLeft:4,width:14,height:14,lineHeight:"12px",padding:0,border:"none",borderRadius:9999,background:"rgba(29,78,216,0.12)",color:"#1d4ed8",fontSize:9,fontWeight:700,cursor:"pointer",display:"inline-flex",alignItems:"center",justifyContent:"center",flex:"0 0 auto"},mc={padding:"4px 8px",fontSize:11,border:"1px solid #cbd5e1",borderRadius:6,background:"#f8fafc",color:"#475569",cursor:"pointer",whiteSpace:"nowrap"},tw={fontSize:10,color:"#94a3b8"},nw=Jl;function xs(e){return Eb(e)}function Ke({value:e,onChange:t,t:n,candidates:o,placeholder:r,testidPrefix:s="g7le-data-chip-value",readOnly:a=!1,enableExpressionTree:l=!0}){const d=typeof e=="string"?e:"",c=!!o&&o.length>0,[p,f]=u.useState(!1),[b,g]=u.useState(!1),[m,h]=u.useState(!1),v=u.useCallback(S=>i.jsx(Ke,{value:S.value,onChange:C=>S.onChange(C),t:S.t,candidates:S.candidates,testidPrefix:S.testidPrefix,enableExpressionTree:!0}),[]),y=()=>i.jsx("div",{style:{display:"flex",gap:4,alignItems:"center",width:"100%",minWidth:0},children:i.jsx("input",{type:"text","data-testid":`${s}-input`,value:d,placeholder:r??n("layout_editor.page_settings.seo.value_placeholder"),readOnly:a,onChange:S=>t(S.target.value),onFocus:()=>f(!0),onBlur:()=>f(!1),style:{flex:1,padding:"5px 8px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,minWidth:0,width:"100%",background:a?"#f1f5f9":"#fff",color:a?"#64748b":"#0f172a"}})}),_=u.useCallback(S=>{const C=d?`${d} ${S.expression}`:S.expression;t(C)},[d,t]);if(!l||a)return i.jsxs("div",{"data-testid":s,style:pn,children:[y(),!a&&c&&i.jsx($t,{candidates:o,t:n,onSelect:_,testIdSuffix:`${s}-pick`})]});const w=Wv(d),k=Zd(d),x=Kt(d),$=S=>{const C=Er(d),T=C?gs(C,()=>"",E=>Vt(E)):[];return i.jsxs(i.Fragment,{children:[i.jsx("div",{style:{display:"flex",justifyContent:"flex-start"},children:i.jsxs("button",{type:"button","data-testid":`${S}-to-plain`,onClick:()=>h(!0),style:sw,title:n("layout_editor.value_tree.to_plain"),"aria-label":n("layout_editor.value_tree.to_plain"),children:["↩ ",n("layout_editor.value_tree.to_plain")]})}),m&&i.jsxs("div",{"data-testid":`${S}-to-plain-confirm`,style:aw,children:[i.jsx("p",{style:lw,children:n("layout_editor.value_tree.to_plain_lead")}),i.jsx("div",{style:dw,children:T.length===0?i.jsx("span",{style:{color:"#94a3b8"},children:n("layout_editor.page_settings.seo.value_placeholder")}):T.map((E,A)=>E.kind==="chip"?i.jsxs("span",{style:ws,children:["🔗 ",E.text]},A):i.jsx("span",{style:E.kind==="ellipsis"?{color:"#94a3b8"}:void 0,children:E.text},A))}),i.jsxs("p",{style:cw,children:["⚠ ",n("layout_editor.value_tree.to_plain_warn")]}),i.jsxs("div",{style:{display:"flex",justifyContent:"flex-end",gap:6},children:[i.jsx("button",{type:"button","data-testid":`${S}-to-plain-confirm-cancel`,onClick:()=>h(!1),style:Ut,children:n("layout_editor.value_tree.to_plain_cancel")}),i.jsx("button",{type:"button","data-testid":`${S}-to-plain-confirm-ok`,onClick:()=>{h(!1),t(Er(d)||"")},style:uw,children:n("layout_editor.value_tree.to_plain_ok")})]})]})]})};if(!p&&(w==="expression"||k)){if(k)return i.jsxs("div",{"data-testid":s,style:pn,children:[!a&&$(`${s}-seg`),i.jsx(bs,{value:d,onChange:C=>t(C),t:n,candidates:o,testidPrefix:`${s}-seg`,renderLeafInput:v})]});const S=un(d);return i.jsxs("div",{"data-testid":s,style:pn,children:[!a&&$(`${s}-tree`),i.jsx(sc,{node:S.node,onChange:C=>t(Zn(C,!1)),t:n,candidates:o,testidPrefix:`${s}-tree`,renderLeafInput:v})]})}if(x&&!p&&!b)return i.jsx(ow,{value:d,onChange:t,t:n,candidates:o,testidPrefix:s,onEditRaw:()=>g(!0)});if(!p&&!b&&xs(d)){const S=nw(d);return i.jsx("div",{"data-testid":s,style:pn,children:i.jsxs("div",{style:vc,children:[i.jsx("div",{"data-testid":`${s}-chips`,style:rw,children:S.map((C,T)=>C.kind==="chip"?i.jsxs("span",{style:ws,children:["🔗 ",C.label]},T):i.jsx("span",{children:C.label},T))}),i.jsxs("button",{type:"button","data-testid":`${s}-edit-raw`,onClick:()=>g(!0),style:Ut,title:n("layout_editor.value_tree.edit"),"aria-label":n("layout_editor.value_tree.edit"),children:["✎ ",n("layout_editor.value_tree.edit")]})]})})}return b&&xs(d)?i.jsxs("div",{"data-testid":s,style:pn,children:[i.jsx(_s,{value:d,onChange:t,t:n,candidates:o,disabled:a,onDone:()=>g(!1),testidPrefix:`${s}-chipedit`}),!a&&w!=="raw"&&i.jsxs("div",{style:{display:"flex",gap:4,flexWrap:"wrap"},children:[i.jsxs("button",{type:"button","data-testid":`${s}-to-expr`,onClick:()=>t($o(d)),style:Ut,title:n("layout_editor.value_tree.to_expression"),"aria-label":n("layout_editor.value_tree.to_expression"),children:["ƒx ",n("layout_editor.value_tree.to_expression")]}),i.jsxs("button",{type:"button","data-testid":`${s}-to-fallback`,onClick:()=>t(hs(d)),style:Ut,title:n("layout_editor.value_tree.segment.fallback"),"aria-label":n("layout_editor.value_tree.segment.fallback"),children:["?? ",n("layout_editor.value_tree.segment.fallback")]})]}),w==="raw"&&i.jsxs("p",{"data-testid":`${s}-raw-hint`,style:ks,children:["ⓘ ",n("layout_editor.value_tree.raw_expression_hint")]})]}):!p&&xs(d)?i.jsxs("div",{"data-testid":s,style:pn,children:[i.jsx(_s,{value:d,onChange:t,t:n,candidates:o,disabled:a,onDone:()=>{g(!1),f(!1)},testidPrefix:`${s}-chipedit`}),!a&&w!=="raw"&&i.jsxs("div",{style:{display:"flex",gap:4,flexWrap:"wrap"},children:[i.jsxs("button",{type:"button","data-testid":`${s}-to-expr`,onClick:()=>t($o(d)),style:Ut,title:n("layout_editor.value_tree.to_expression"),"aria-label":n("layout_editor.value_tree.to_expression"),children:["ƒx ",n("layout_editor.value_tree.to_expression")]}),i.jsxs("button",{type:"button","data-testid":`${s}-to-fallback`,onClick:()=>t(hs(d)),style:Ut,title:n("layout_editor.value_tree.segment.fallback"),"aria-label":n("layout_editor.value_tree.segment.fallback"),children:["?? ",n("layout_editor.value_tree.segment.fallback")]})]}),w==="raw"&&i.jsxs("p",{"data-testid":`${s}-raw-hint`,style:ks,children:["ⓘ ",n("layout_editor.value_tree.raw_expression_hint")]})]}):i.jsxs("div",{"data-testid":s,style:pn,children:[y(),i.jsxs("div",{style:iw,children:[c&&i.jsx($t,{candidates:o,t:n,onSelect:_,testIdSuffix:`${s}-pick`}),w!=="raw"&&i.jsxs("div",{style:{display:"flex",gap:4},children:[i.jsxs("button",{type:"button","data-testid":`${s}-to-expr`,onClick:()=>t($o(d)),style:Ut,title:n("layout_editor.value_tree.to_expression"),"aria-label":n("layout_editor.value_tree.to_expression"),children:["ƒx ",n("layout_editor.value_tree.to_expression")]}),i.jsxs("button",{type:"button","data-testid":`${s}-to-fallback`,onClick:()=>t(hs(d)),style:Ut,title:n("layout_editor.value_tree.segment.fallback"),"aria-label":n("layout_editor.value_tree.segment.fallback"),children:["?? ",n("layout_editor.value_tree.segment.fallback")]})]})]}),w==="raw"&&!p&&i.jsxs("p",{"data-testid":`${s}-raw-hint`,style:ks,children:["ⓘ ",n("layout_editor.value_tree.raw_expression_hint")]})]})}function ow({value:e,onChange:t,t:n,candidates:o,testidPrefix:r,onEditRaw:s}){const[a,l]=u.useState(!1),d=u.useRef(null),c=Vt(e),p=!!o&&o.length>0,f=Kt(e)?.localeFn;return i.jsxs("div",{"data-testid":r,style:pn,children:[i.jsxs("div",{style:vc,children:[i.jsxs("span",{"data-testid":`${r}-chip`,style:ws,children:["🔗 ",c]}),p&&i.jsx("button",{ref:d,type:"button","data-testid":`${r}-change`,onClick:()=>l(b=>!b),style:Ut,title:n("layout_editor.value_tree.change_data"),"aria-label":n("layout_editor.value_tree.change_data"),children:n("layout_editor.value_tree.change_data")}),i.jsx("button",{type:"button","data-testid":`${r}-edit-raw`,onClick:s,style:Ut,title:n("layout_editor.value_tree.edit"),"aria-label":n("layout_editor.value_tree.edit"),children:"✎"})]}),p&&i.jsx(Xn,{anchorRef:d,open:a,onClose:()=>l(!1),children:i.jsx($t,{candidates:o,t:n,onSelect:b=>{if(f){const h=[b.sourceId,...b.path?b.path.split(".").filter(Boolean):[]].join(".");t(`{{${f}(${h})}}`),l(!1);return}const g=b.path?b.path.split(".").filter(Boolean):[],m=[b.sourceId,...g].join("?.");t(`{{${m}}}`),l(!1)},testIdSuffix:`${r}-pick`,defaultOpen:!0,floating:!1})})]})}const pn={display:"flex",flexDirection:"column",gap:4,width:"100%",minWidth:0},vc={display:"flex",alignItems:"center",gap:6,width:"100%",minWidth:0,flexWrap:"wrap"},rw={flex:1,minWidth:0,display:"flex",flexWrap:"wrap",alignItems:"center",gap:4,padding:"5px 8px",fontSize:12,color:"#334155",border:"1px solid #e2e8f0",borderRadius:6,background:"#f8fafc"},ws={fontSize:11,background:"#eef2ff",color:"#4338ca",padding:"2px 8px",borderRadius:12,whiteSpace:"nowrap"},Ut={padding:"4px 8px",fontSize:11,border:"1px solid #cbd5e1",borderRadius:6,background:"#f8fafc",color:"#475569",cursor:"pointer",whiteSpace:"nowrap"},iw={display:"flex",flexWrap:"wrap",alignItems:"center",gap:6},ks={margin:"2px 0 0",fontSize:11,color:"#94a3b8",lineHeight:1.4},sw={padding:"3px 10px",fontSize:11,border:"1px solid #fca5a5",borderRadius:6,background:"#fef2f2",color:"#b91c1c",cursor:"pointer",whiteSpace:"nowrap"},aw={display:"flex",flexDirection:"column",gap:6,padding:10,border:"1px solid #fca5a5",borderRadius:8,background:"#fffbeb"},lw={margin:0,fontSize:12,color:"#334155"},dw={padding:"5px 8px",fontSize:12,color:"#0f172a",background:"#fff",border:"1px solid #e2e8f0",borderRadius:6,wordBreak:"break-all",display:"flex",flexWrap:"wrap",gap:4,alignItems:"center"},cw={margin:0,fontSize:11,color:"#b45309"},uw={padding:"3px 12px",fontSize:11,border:"1px solid #dc2626",borderRadius:6,background:"#dc2626",color:"#fff",cursor:"pointer",whiteSpace:"nowrap"};let _c=0;function Pr({value:e,onChange:t,keyField:n,valueField:o="content",t:r,candidates:s,testidPrefix:a="g7le-kv-chip-editor",keyPlaceholder:l,valuePlaceholder:d,addLabel:c,renderKeyInput:p}){const f=u.useMemo(()=>(Array.isArray(e)?e:[]).map(w=>({id:_c++,k:typeof w[n]=="string"?w[n]:"",v:typeof w[o]=="string"?w[o]:""})),[]),[b,g]=u.useState(f),m=u.useCallback(w=>{g(w);const k=w.filter(x=>x.k.trim()!=="").map(x=>({[n]:x.k,[o]:x.v}));t(k)},[n,o,t]),h=u.useCallback(()=>{m([...b,{id:_c++,k:"",v:""}])},[b,m]),v=u.useCallback(w=>{m(b.filter(k=>k.id!==w))},[b,m]),y=u.useCallback((w,k)=>{m(b.map(x=>x.id===w?{...x,k}:x))},[b,m]),_=u.useCallback((w,k)=>{m(b.map(x=>x.id===w?{...x,v:k}:x))},[b,m]);return i.jsxs("div",{"data-testid":a,style:{display:"flex",flexDirection:"column",gap:6},children:[b.map(w=>i.jsxs("div",{"data-testid":`${a}-row-${w.id}`,style:{display:"flex",gap:6,alignItems:"flex-start",border:"1px solid #e2e8f0",borderRadius:8,padding:8},children:[i.jsxs("div",{style:{flex:1,minWidth:0,display:"flex",flexDirection:"column",gap:6},children:[i.jsx("div",{style:{display:"flex",flexDirection:"column",gap:2,minWidth:0},children:p?i.jsx("div",{style:{minWidth:0},children:p({value:w.k,onChange:k=>y(w.id,k),testid:`${a}-key-${w.id}`,placeholder:l??r(`layout_editor.page_settings.seo.extra_${n}`)})}):i.jsx("input",{type:"text","data-testid":`${a}-key-${w.id}`,value:w.k,placeholder:l??r(`layout_editor.page_settings.seo.extra_${n}`),onChange:k=>y(w.id,k.target.value),style:{width:"100%",padding:"5px 8px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,minWidth:0,boxSizing:"border-box"}})}),i.jsx("div",{style:{minWidth:0},children:i.jsx(Ke,{value:w.v,onChange:k=>_(w.id,k),t:r,candidates:s,placeholder:d??r("layout_editor.page_settings.seo.extra_content"),testidPrefix:`${a}-value-${w.id}`})})]}),i.jsx("button",{type:"button","data-testid":`${a}-remove-${w.id}`,onClick:()=>v(w.id),title:r("layout_editor.page_settings.seo.remove"),"aria-label":r("layout_editor.page_settings.seo.remove"),style:{flex:"0 0 auto",padding:"4px 8px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",color:"#64748b",cursor:"pointer"},children:"✕"})]},w.id)),i.jsxs("button",{type:"button","data-testid":`${a}-add`,onClick:h,style:{alignSelf:"flex-start",padding:"5px 10px",fontSize:12,border:"1px dashed #cbd5e1",borderRadius:6,background:"#f8fafc",color:"#475569",cursor:"pointer"},children:["+ ",c??r("layout_editor.page_settings.seo.add_extra")]})]})}const pw=["website","article","product","profile"],fw=["summary","summary_large_image","app","player"];function gw(e){switch(e){case"core":return"layout_editor.page_settings.seo.og_source_core";case"inherited":return"layout_editor.page_settings.seo.og_source_inherited";case"filter":return"layout_editor.page_settings.seo.og_source_filter";case"layout":return null;default:return e?"layout_editor.page_settings.seo.og_source_module":null}}function xc(e){const t={};for(const n of e??[])t[n.key]=n;return t}function wc(e,t){return!!e&&t&&typeof e.sourceExpr=="string"&&e.sourceExpr!==""&&!e.overriddenByLayout&&!e.lockedByFilter}function kc({label:e,sourceExpr:t,onReplace:n,t:o,testid:r}){return i.jsxs("div",{"data-testid":r,style:Sw,children:[i.jsxs("span",{"data-testid":`${r}-chip`,style:Cw,title:t,children:["🔗 ",e||t]}),i.jsx("span",{"data-testid":`${r}-badge`,style:$w,children:o("layout_editor.page_settings.seo.auto_chip_source")}),i.jsx("button",{type:"button","data-testid":`${r}-replace`,onClick:n,style:Ew,children:o("layout_editor.page_settings.seo.auto_chip_replace")})]})}function hw({og:e,twitter:t,onChangeOg:n,onChangeTwitter:o,ogPreview:r,twitterPreview:s,defaultsAvailable:a=!1,candidates:l,t:d,testidPrefix:c="g7le-seo-og"}){const p=e&&typeof e=="object"?e:{},f=t&&typeof t=="object"?t:{},[b,g]=u.useState(p.image_height!=null||p.image_type!=null||p.image_secure_url!=null),m=xc(r),h=xc(s),v=u.useCallback((T,E)=>{const A={...p};E===void 0||E===""||E===null?delete A[T]:A[T]=E,n(A)},[p,n]),y=u.useCallback((T,E)=>{const A={...f};E===void 0||E===""||E===null?delete A[T]:A[T]=E,o(A)},[f,o]),_=(T,E,A)=>!A||!a||!A.lockedByFilter?null:i.jsx("div",{style:{display:"flex",gap:6,alignItems:"center",marginTop:2},children:i.jsxs("span",{"data-testid":`${T}-locked-${E}`,style:xw,children:["🔒 ",d("layout_editor.page_settings.seo.og_locked")]})}),w=T=>{if(!T||!a||T.lockedByFilter||T.overriddenByLayout)return;const E=gw(T.source);return E?d(E):void 0},k=(T,E)=>T&&a?T.overriddenByLayout===!0:E!=="",x=(T,E,A,N,D,B)=>{const M=B[E],O=!!M?.lockedByFilter,z=typeof N[E]=="string"?N[E]:"";return i.jsxs("div",{"data-testid":`${T}-${E}`,style:Dn,children:[i.jsx("label",{style:to,children:d(A)}),O?i.jsx("div",{"data-testid":`${T}-locked-value-${E}`,style:_w,children:String(M?.effectiveValue??"")}):wc(M,a)?i.jsx(kc,{label:M.label??"",sourceExpr:M.sourceExpr,onReplace:()=>D(E,M.sourceExpr),t:d,testid:`${T}-${E}-auto`}):i.jsx(_t,{value:z,onChange:H=>D(E,H??""),t:d,candidates:l,placeholder:a&&M?.effectiveValue!=null?String(M.effectiveValue):void 0,testidPrefix:`${T}-${E}-field`,enableExpressionTree:!0,expressionTreeCollapsible:!0}),_(T,E,M)]})},$=(T,E,A,N,D,B)=>{const M=B[E],O=!!M?.lockedByFilter,z=typeof N[E]=="string"?N[E]:"";return i.jsxs("div",{"data-testid":`${T}-${E}`,style:Dn,children:[i.jsx("label",{style:to,children:d(A)}),wc(M,a)?i.jsx(kc,{label:M.label??"",sourceExpr:M.sourceExpr,onReplace:()=>D(E,M.sourceExpr),t:d,testid:`${T}-${E}-auto`}):i.jsx(Ke,{value:z,onChange:H=>D(E,H),t:d,candidates:l,readOnly:O,placeholder:a&&M?.effectiveValue!=null?String(M.effectiveValue):void 0,testidPrefix:`${T}-${E}-field`}),_(T,E,M)]})},S=(T,E,A,N,D,B)=>{const M=B[E],O=!!M?.lockedByFilter,z=typeof N[E]=="string"?N[E]:"",H=k(M,z),P=H?z:"";return i.jsxs("div",{"data-testid":`${T}-${E}`,style:Dn,children:[i.jsx("label",{style:to,children:d(A)}),i.jsx(Tx,{filled:H,sourceLabel:w(M),onRevert:O?void 0:()=>D(E,void 0),revertLabel:d("layout_editor.page_settings.seo.revert_default"),testid:`${T}-${E}-overlay`,children:i.jsx("div",{"data-testid":`${T}-${E}-input`,style:{minWidth:0},children:i.jsx(Ke,{value:P,onChange:I=>D(E,I),t:d,candidates:l,readOnly:O,placeholder:a&&M?.effectiveValue!=null?String(M.effectiveValue):void 0,testidPrefix:`${T}-${E}-chip`})})}),_(T,E,M)]})},C=(T,E,A,N,D,B,M)=>{const O=M[E],z=typeof D[E]=="string"?D[E]:"",H=k(O,z),P=w(O),I=H?z:"";return i.jsxs("div",{"data-testid":`${T}-${E}`,style:Dn,children:[i.jsxs("div",{style:bw,children:[i.jsx("label",{style:to,children:d(A)}),P?i.jsxs("span",{"data-testid":`${T}-${E}-source-inline`,style:mw,children:["· ",P]}):null]}),i.jsxs("select",{"data-testid":`${T}-${E}-select`,value:I,onChange:L=>B(E,L.target.value),style:vw,children:[i.jsx("option",{value:"",children:a&&O?.effectiveValue!=null&&O.effectiveValue!==""?`${d("layout_editor.page_settings.seo.use_default")} (${String(O.effectiveValue)})`:d("layout_editor.page_settings.seo.use_default")}),N.map(L=>i.jsx("option",{value:L,children:L},L))]}),_(T,E,O)]})};return i.jsxs("div",{"data-testid":c,style:{display:"flex",flexDirection:"column",gap:12},children:[i.jsxs("div",{"data-testid":`${c}-og-section`,style:Sc,children:[i.jsx("div",{style:Cc,children:d("layout_editor.page_settings.seo.og_section")}),i.jsxs("p",{"data-testid":`${c}-intro`,style:yw,children:["ⓘ ",d("layout_editor.page_settings.seo.og_intro")]}),C(c,"type","layout_editor.page_settings.seo.og_type",pw,p,v,m),x(c,"title","layout_editor.page_settings.seo.og_title",p,v,m),x(c,"description","layout_editor.page_settings.seo.og_description",p,v,m),$(c,"image","layout_editor.page_settings.seo.og_image",p,v,m),x(c,"image_alt","layout_editor.page_settings.seo.og_image_alt",p,v,m),i.jsxs("div",{style:Dn,children:[i.jsxs("button",{type:"button","data-testid":`${c}-image-adv-toggle`,"aria-expanded":b,onClick:()=>g(T=>!T),style:ww,children:[b?"▾":"▸"," ",d("layout_editor.page_settings.seo.og_image_adv")]}),b?i.jsxs("div",{"data-testid":`${c}-image-adv`,style:kw,children:[S(c,"image_height","layout_editor.page_settings.seo.og_image_height",p,v,m),S(c,"image_type","layout_editor.page_settings.seo.og_image_type",p,v,m),S(c,"image_secure_url","layout_editor.page_settings.seo.og_image_secure_url",p,v,m)]}):null]}),S(c,"site_name","layout_editor.page_settings.seo.og_site_name",p,v,m),S(c,"locale","layout_editor.page_settings.seo.og_locale",p,v,m),i.jsxs("div",{"data-testid":`${c}-extra`,style:Dn,children:[i.jsx("label",{style:to,children:d("layout_editor.page_settings.seo.og_extra")}),i.jsx(Pr,{value:Array.isArray(p.extra)?p.extra:[],onChange:T=>v("extra",T.length>0?T:void 0),keyField:"property",t:d,candidates:l,testidPrefix:`${c}-extra-editor`})]})]}),i.jsxs("div",{"data-testid":`${c}-tw-section`,style:Sc,children:[i.jsx("div",{style:Cc,children:d("layout_editor.page_settings.seo.tw_section")}),i.jsx("p",{style:{margin:"0 0 6px",fontSize:11,color:"#94a3b8"},children:d("layout_editor.page_settings.seo.tw_fallback_note")}),C("g7le-seo-tw","card","layout_editor.page_settings.seo.tw_card",fw,f,y,h),S("g7le-seo-tw","site","layout_editor.page_settings.seo.tw_site",f,y,h),S("g7le-seo-tw","creator","layout_editor.page_settings.seo.tw_creator",f,y,h),x("g7le-seo-tw","title","layout_editor.page_settings.seo.tw_title",f,y,h),x("g7le-seo-tw","description","layout_editor.page_settings.seo.tw_description",f,y,h),$("g7le-seo-tw","image","layout_editor.page_settings.seo.tw_image",f,y,h),x("g7le-seo-tw","image_alt","layout_editor.page_settings.seo.tw_image_alt",f,y,h),i.jsxs("div",{"data-testid":"g7le-seo-tw-extra",style:Dn,children:[i.jsx("label",{style:to,children:d("layout_editor.page_settings.seo.tw_extra")}),i.jsx(Pr,{value:Array.isArray(f.extra)?f.extra:[],onChange:T=>y("extra",T.length>0?T:void 0),keyField:"name",t:d,candidates:l,testidPrefix:"g7le-seo-tw-extra-editor"})]})]})]})}const Sc={border:"1px solid #e2e8f0",borderRadius:8,padding:12,display:"flex",flexDirection:"column",gap:10},Cc={fontSize:13,fontWeight:600,color:"#0f172a"},yw={margin:0,fontSize:11,color:"#64748b",background:"#f8fafc",borderRadius:6,padding:"6px 8px"},Dn={display:"flex",flexDirection:"column",gap:4},to={fontSize:12,color:"#475569"},bw={display:"flex",alignItems:"center",gap:6},mw={fontSize:11,color:"#94a3b8"},vw={padding:"5px 8px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,width:"100%",minWidth:0,boxSizing:"border-box"},_w={padding:"5px 8px",fontSize:12,border:"1px solid #e2e8f0",borderRadius:6,background:"#f1f5f9",color:"#64748b"},xw={fontSize:11,color:"#7c3aed"},ww={alignSelf:"flex-start",padding:"2px 0",fontSize:12,fontWeight:600,color:"#475569",background:"transparent",border:"none",cursor:"pointer"},kw={display:"flex",flexDirection:"column",gap:8,paddingLeft:14,marginTop:4,minWidth:0},Sw={display:"flex",alignItems:"center",gap:8,flexWrap:"wrap",minWidth:0},Cw={display:"inline-flex",alignItems:"center",maxWidth:"100%",padding:"3px 8px",fontSize:12,color:"#1d4ed8",background:"#eff6ff",border:"1px solid #bfdbfe",borderRadius:6,overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap"},$w={fontSize:11,color:"#94a3b8"},Ew={padding:"2px 8px",fontSize:11,color:"#475569",background:"#fff",border:"1px solid #cbd5e1",borderRadius:6,cursor:"pointer"},Iw=["WebPage","WebSite","ItemList","BreadcrumbList","Organization","Person","CollectionPage","AboutPage","ContactPage","SearchAction"];function Or(e){const t=[],n=(o,r)=>{for(const[s,a]of Object.entries(o)){if(s==="@type"||s==="@context")continue;const l=r?`${r}.${s}`:s;a!==null&&typeof a=="object"&&!Array.isArray(a)?n(a,l):t.push({key:l,value:a==null?"":String(a)})}};return n(e,""),t}function $c(e,t){const n={"@type":e};for(const{key:o,value:r}of t){if(o.trim()==="")continue;const s=o.split(".");let a=n;for(let l=0;lp?Or(e).map(D=>({id:Ss++,...D})):[]),_=u.useCallback((D,B)=>{h(D),y(B),t($c(D,B.map(M=>({key:M.key,value:M.value}))))},[t]),w=u.useCallback(()=>{const D=n,B=typeof D["@type"]=="string"?D["@type"]:m,M=Or(D).map(O=>({id:Ss++,key:O.key,value:o?.[O.key]?.expr??O.value}));return{type:B,rows:M}},[n,o,m]),k=u.useCallback(D=>{if(b(D),D){if(v.length===0&&n&&typeof n=="object"){const B=w();_(B.type,B.rows);return}t($c(m,v.map(B=>({key:B.key,value:B.value}))))}else t(null)},[m,v,n,w,_,t]),x=u.useCallback(()=>{if(!n)return;const D=w();_(D.type,D.rows)},[n,w,_]),$=u.useCallback(()=>{_(m,[...v,{id:Ss++,key:"",value:""}])},[m,v,_]),S=u.useCallback(D=>{_(m,v.filter(B=>B.id!==D))},[m,v,_]),C=u.useCallback((D,B)=>{_(m,v.map(M=>M.id===D?{...M,key:B}:M))},[m,v,_]),T=u.useCallback((D,B)=>{_(m,v.map(M=>M.id===D?{...M,value:B}:M))},[m,v,_]),E=u.useMemo(()=>n?Or(n):[],[n]),A=u.useMemo(()=>s?Or(s):[],[s]),N=v.some(D=>D.key.trim()!==""&&D.value.trim()==="");if(r){const D=s&&typeof s["@type"]=="string"?s["@type"]:"";return i.jsxs("div",{"data-testid":c,style:Ec,children:[i.jsxs("div",{style:Ic,children:[i.jsx("span",{style:Tw,children:"🔒"}),i.jsx("span",{style:{fontSize:13,fontWeight:600},children:d("layout_editor.page_settings.seo.sd_mode")}),i.jsx("span",{"data-testid":`${c}-locked-badge`,style:Dw,children:d("layout_editor.page_settings.seo.sd_filter_locked")})]}),i.jsxs("div",{"data-testid":`${c}-locked`,style:zw,children:[i.jsxs("p",{style:{margin:"0 0 6px",fontSize:12,color:"#475569"},children:["ⓘ ",d("layout_editor.page_settings.seo.sd_filter_locked_note")]}),i.jsxs("div",{"data-testid":`${c}-filtered-block`,children:[D?i.jsxs("div",{style:{fontSize:12,fontWeight:600,marginBottom:4},children:["@type: ",D]}):null,A.map(B=>i.jsxs("div",{"data-testid":`${c}-filtered-row-${B.key}`,style:Ac,children:[i.jsx("code",{style:Tc,children:B.key}),i.jsx("span",{style:Dc,children:B.value})]},B.key))]})]})]})}return i.jsxs("div",{"data-testid":c,style:Ec,children:[i.jsx("div",{style:Ic,children:i.jsx(Mr,{checked:f,onChange:k,testid:`${c}-mode`,label:d("layout_editor.page_settings.seo.sd_manual_toggle")})}),!f&&i.jsxs("div",{"data-testid":`${c}-auto`,style:{marginTop:8},children:[i.jsxs("div",{"data-testid":`${c}-auto-preview`,style:Nw,children:[i.jsxs("div",{style:{fontSize:12,fontWeight:600,marginBottom:6},children:[d("layout_editor.page_settings.seo.sd_auto_title"),n&&typeof n["@type"]=="string"?`: ${n["@type"]}`:"",a?i.jsxs("span",{style:{marginLeft:6,color:"#94a3b8",fontWeight:400},children:["(",a,")"]}):null]}),E.length===0?i.jsx("p",{style:{margin:0,fontSize:12,color:"#94a3b8"},children:d("layout_editor.page_settings.seo.sd_auto_empty")}):E.map(D=>{const B=o?.[D.key];return i.jsxs("div",{"data-testid":`${c}-auto-row-${D.key}`,style:Ac,children:[i.jsx("code",{style:Tc,children:D.key}),B?i.jsxs("span",{"data-testid":`${c}-auto-chip-${D.key}`,style:Mw,title:B.expr,children:["🔗 ",B.label||B.expr]}):i.jsx("span",{style:Dc,children:D.value})]},D.key)})]}),i.jsxs("p",{"data-testid":`${c}-replace-warn`,style:Lw,children:["ⓘ ",d("layout_editor.page_settings.seo.sd_replace_pre_warn")]})]}),f&&i.jsxs("div",{"data-testid":`${c}-manual`,style:{marginTop:8},children:[i.jsxs("p",{"data-testid":`${c}-replace-warn`,style:Pw,children:["⚠ ",d("layout_editor.page_settings.seo.sd_replace_warn")]}),i.jsxs("div",{style:{display:"flex",flexDirection:"column",gap:6},children:[i.jsx("label",{style:{fontSize:12,color:"#475569"},children:d("layout_editor.page_settings.seo.sd_type")}),i.jsx("input",{type:"text","data-testid":`${c}-type`,list:`${c}-type-options`,value:m,onChange:D=>_(D.target.value,v),style:Ow}),i.jsx("datalist",{id:`${c}-type-options`,children:Iw.map(D=>i.jsx("option",{value:D},D))})]}),i.jsx("div",{style:{marginTop:8,display:"flex",flexDirection:"column",gap:6},children:v.map(D=>i.jsxs("div",{"data-testid":`${c}-prop-${D.id}`,style:Bw,children:[i.jsx("input",{type:"text","data-testid":`${c}-prop-key-${D.id}`,value:D.key,placeholder:d("layout_editor.page_settings.seo.sd_prop_key"),onChange:B=>C(D.id,B.target.value),style:{width:160,padding:"5px 8px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,minWidth:0}}),i.jsx("div",{style:{flex:1,minWidth:0},children:i.jsx(Ke,{value:D.value,onChange:B=>T(D.id,B),t:d,candidates:l,placeholder:d("layout_editor.page_settings.seo.sd_prop_value"),testidPrefix:`${c}-prop-value-${D.id}`})}),i.jsx("button",{type:"button","data-testid":`${c}-prop-remove-${D.id}`,onClick:()=>S(D.id),title:d("layout_editor.page_settings.seo.remove"),"aria-label":d("layout_editor.page_settings.seo.remove"),style:Ww,children:"✕"})]},D.id))}),i.jsxs("div",{style:{marginTop:8,display:"flex",gap:8},children:[i.jsxs("button",{type:"button","data-testid":`${c}-add`,onClick:$,style:zc,children:["+ ",d("layout_editor.page_settings.seo.sd_add_prop")]}),n?i.jsxs("button",{type:"button","data-testid":`${c}-seed-from-module`,onClick:x,style:zc,children:["↩ ",d("layout_editor.page_settings.seo.sd_seed_from_module")]}):null]}),i.jsxs("p",{"data-testid":`${c}-empty-note`,style:jw,children:["ⓘ ",d("layout_editor.page_settings.seo.sd_empty_note"),N?i.jsxs("span",{style:{color:"#b45309"},children:[" ",d("layout_editor.page_settings.seo.sd_has_empty")]}):null]})]})]})}const Ec={display:"flex",flexDirection:"column"},Ic={display:"flex",alignItems:"center",gap:8},Tw={fontSize:14},Dw={fontSize:11,padding:"2px 6px",borderRadius:4,background:"#f1f5f9",color:"#64748b"},zw={marginTop:8,border:"1px solid #fcd34d",borderRadius:8,padding:10,background:"#fffbeb"},Nw={border:"1px solid #e2e8f0",borderRadius:8,padding:10,background:"#f8fafc"},Ac={display:"flex",gap:8,alignItems:"center",padding:"2px 0"},Tc={fontSize:11,color:"#475569",background:"#fff",padding:"2px 6px",borderRadius:4,minWidth:120},Dc={fontSize:11,color:"#0f172a",fontFamily:"monospace",wordBreak:"break-all"},Mw={display:"inline-flex",alignItems:"center",padding:"2px 8px",fontSize:11,color:"#1d4ed8",background:"#eff6ff",border:"1px solid #bfdbfe",borderRadius:6},Lw={margin:"6px 0 0",fontSize:12,color:"#64748b"},Pw={margin:"0 0 8px",fontSize:12,color:"#b45309",background:"#fffbeb",border:"1px solid #fcd34d",borderRadius:6,padding:"6px 8px"},Ow={padding:"5px 8px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,width:"100%",minWidth:0},Bw={display:"flex",gap:6,alignItems:"flex-start",border:"1px solid #e2e8f0",borderRadius:8,padding:8},Ww={padding:"4px 8px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",color:"#64748b",cursor:"pointer"},zc={padding:"5px 10px",fontSize:12,border:"1px dashed #cbd5e1",borderRadius:6,background:"#f8fafc",color:"#475569",cursor:"pointer"},jw={margin:"8px 0 0",fontSize:11,color:"#94a3b8"},Nc=new Set(["core_setting","setting","query","route"]);function Hw({vars:e,onChange:t,varCandidates:n,gatingMet:o=!1,candidates:r,t:s,testidPrefix:a="g7le-seo-vars"}){const l=u.useMemo(()=>e&&typeof e=="object"?{...e}:{},[e]),d=u.useMemo(()=>Array.isArray(n)?n:[],[n]),c=u.useMemo(()=>o?d.filter(x=>Nc.has(x.source)):[],[d,o]),p=u.useMemo(()=>o?d.filter(x=>x.source==="data"):[],[d,o]);u.useMemo(()=>new Set(c.map(x=>x.name)),[c]);const f=u.useMemo(()=>new Set(p.map(x=>x.name)),[p]),b=u.useMemo(()=>{const x=new Set;for(const $ of d)Nc.has($.source)&&x.add($.name);return x},[d]),g=u.useMemo(()=>Object.keys(l).filter(x=>!f.has(x)),[l,f]),m=u.useCallback((x,$)=>{const S={...l};$===void 0?delete S[x]:S[x]=$,t(S)},[l,t]),[h,v]=u.useState(""),y=h.trim()!==""&&b.has(h.trim()),_=h.trim()!==""&&Object.prototype.hasOwnProperty.call(l,h.trim()),w=h.trim()!==""&&!y&&!_&&!f.has(h.trim()),k=u.useCallback(()=>{const x=h.trim();x===""||b.has(x)||Object.prototype.hasOwnProperty.call(l,x)||(t({...l,[x]:""}),v(""))},[h,b,l,t]);return i.jsxs("div",{"data-testid":a,style:{display:"flex",flexDirection:"column",gap:10},children:[i.jsx("span",{"data-testid":"g7le-seo-var-list",hidden:!0,"data-gating":o?"1":"0"}),!o&&i.jsxs("div",{"data-testid":`${a}-precondition`,style:Fw,children:["⚠ ",s("layout_editor.page_settings.seo.vars_precondition")]}),c.length>0&&i.jsxs("div",{"data-testid":`${a}-auto-group`,style:Cs,children:[i.jsx("div",{style:$s,children:s("layout_editor.page_settings.seo.vars_auto_group")}),c.map(x=>i.jsxs("div",{"data-testid":`g7le-seo-var-auto-${x.name}`,style:Kw,children:[i.jsx("span",{style:Vw,children:"🔒"}),i.jsx("code",{style:Es,children:x.name}),i.jsxs("span",{style:Uw,children:["〔",s("layout_editor.page_settings.seo.vars_auto"),"·",x.source,"〕"]}),x.owner?.name?i.jsx("span",{style:qw,children:x.owner.name}):null]},x.name))]}),p.length>0&&i.jsxs("div",{"data-testid":`${a}-data-group`,style:Cs,children:[i.jsx("div",{style:$s,children:s("layout_editor.page_settings.seo.vars_data_group")}),p.map(x=>{const $=l[x.name]??"",S=$.trim()==="";return i.jsxs("div",{"data-testid":`g7le-seo-var-data-${x.name}`,style:Mc,children:[i.jsxs("div",{style:{display:"flex",alignItems:"center",gap:6,marginBottom:4},children:[i.jsx("code",{style:Es,children:x.name}),x.required?i.jsx("span",{"data-testid":`g7le-seo-var-required-${x.name}`,style:Gw,children:"*"}):null]}),i.jsx(Ke,{value:$,onChange:C=>m(x.name,C??""),t:s,candidates:r,testidPrefix:`g7le-seo-var-data-field-${x.name}`}),x.required&&S?i.jsxs("p",{"data-testid":`g7le-seo-var-empty-warn-${x.name}`,style:Is,children:["⚠ ",s("layout_editor.page_settings.seo.vars_required_empty")]}):null]},x.name)})]}),i.jsxs("div",{"data-testid":`${a}-custom-group`,style:Cs,children:[i.jsx("div",{style:$s,children:s("layout_editor.page_settings.seo.vars_custom_group")}),g.map(x=>i.jsxs("div",{"data-testid":`g7le-seo-var-custom-${x}`,style:Mc,children:[i.jsxs("div",{style:{display:"flex",alignItems:"center",gap:6,marginBottom:4},children:[i.jsx("code",{style:Es,children:x}),i.jsx("button",{type:"button","data-testid":`g7le-seo-var-custom-remove-${x}`,onClick:()=>m(x,void 0),title:s("layout_editor.page_settings.seo.remove"),"aria-label":s("layout_editor.page_settings.seo.remove"),style:{marginLeft:"auto",padding:"2px 8px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",color:"#64748b",cursor:"pointer"},children:"✕"})]}),i.jsx(Ke,{value:l[x]??"",onChange:$=>m(x,$??""),t:s,candidates:r,testidPrefix:`g7le-seo-var-custom-field-${x}`})]},x)),i.jsxs("div",{"data-testid":"g7le-seo-var-add",style:{display:"flex",flexDirection:"column",gap:4,marginTop:6},children:[i.jsxs("div",{style:{display:"flex",gap:6},children:[i.jsx("input",{type:"text","data-testid":"g7le-seo-var-add-name",value:h,placeholder:s("layout_editor.page_settings.seo.vars_add_name"),onChange:x=>v(x.target.value),style:{flex:1,padding:"5px 8px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,minWidth:0}}),i.jsxs("button",{type:"button","data-testid":"g7le-seo-var-add-confirm",onClick:k,disabled:h.trim()===""||y||_,style:Yw,children:["+ ",s("layout_editor.page_settings.seo.vars_add")]})]}),y?i.jsxs("p",{"data-testid":"g7le-seo-var-add-reserved-warn",style:Is,children:["⚠ ",s("layout_editor.page_settings.seo.vars_name_reserved",{name:h.trim()})]}):null,_&&!y?i.jsxs("p",{"data-testid":"g7le-seo-var-add-duplicate-warn",style:Is,children:["⚠ ",s("layout_editor.page_settings.seo.vars_name_duplicate",{name:h.trim()})]}):null,w?i.jsxs("p",{"data-testid":"g7le-seo-var-add-not-provided-hint",style:{margin:0,fontSize:11,color:"#94a3b8"},children:["ⓘ ",s("layout_editor.page_settings.seo.vars_name_not_provided",{name:h.trim()})]}):null]})]}),i.jsxs("p",{style:{margin:0,fontSize:11,color:"#94a3b8"},children:["ⓘ ",s("layout_editor.page_settings.seo.vars_value_hint")]})]})}const Fw={fontSize:12,color:"#b45309",background:"#fffbeb",border:"1px solid #fcd34d",borderRadius:6,padding:"6px 8px"},Cs={border:"1px solid #e2e8f0",borderRadius:8,padding:10,display:"flex",flexDirection:"column",gap:6},$s={fontSize:12,fontWeight:600,color:"#475569"},Kw={display:"flex",alignItems:"center",gap:6,padding:"2px 0"},Mc={display:"flex",flexDirection:"column",border:"1px solid #f1f5f9",borderRadius:6,padding:8},Vw={fontSize:13},Es={fontSize:12,color:"#0f172a",background:"#f1f5f9",padding:"2px 6px",borderRadius:4},Uw={fontSize:11,color:"#64748b"},qw={fontSize:11,color:"#94a3b8"},Gw={color:"#dc2626",fontWeight:700},Is={margin:"4px 0 0",fontSize:11,color:"#b45309"},Yw={padding:"5px 10px",fontSize:12,border:"1px dashed #cbd5e1",borderRadius:6,background:"#f8fafc",color:"#475569",cursor:"pointer",whiteSpace:"nowrap"};function Xw({templateIdentifier:e,layout:t,url:n,locale:o,seedContext:r,routeParams:s,settingsSignature:a,t:l,debounceMs:d=400,fetchImpl:c,testidPrefix:p="g7le-seo-bot-preview"}){const[f,b]=u.useState(!0),[g,m]=u.useState(null),[h,v]=u.useState(!1),[y,_]=u.useState(null),w=u.useRef(null),k=u.useRef({templateIdentifier:e,layout:t,url:n,locale:o,seedContext:r,routeParams:s});k.current={templateIdentifier:e,layout:t,url:n,locale:o,seedContext:r,routeParams:s};const x=u.useCallback(async()=>{const T=c??(typeof fetch<"u"?fetch:void 0);if(!T)return;const{templateIdentifier:E,layout:A,url:N,locale:D,seedContext:B,routeParams:M}=k.current;v(!0),_(null);try{const O=`/api/admin/templates/${encodeURIComponent(E)}/editor/seo-bot-preview`,z=await T(O,{method:"POST",credentials:"same-origin",headers:Ne({"Content-Type":"application/json"}),body:JSON.stringify({layout:A,url:N,locale:D,seed_context:B??{},route_params:M??{}})}),H=await z.json().catch(()=>null);if(!z.ok){_(H?.message??`HTTP ${z.status}`);return}const P=H?.data??null;m(P)}catch(O){_(O instanceof Error?O.message:String(O))}finally{v(!1)}},[c]);u.useEffect(()=>{if(f)return w.current&&clearTimeout(w.current),w.current=setTimeout(()=>{x()},d),()=>{w.current&&clearTimeout(w.current)}},[a,f,d,x]);const $=u.useCallback(()=>{w.current&&clearTimeout(w.current),x()},[x]),S=g?.html??null,C=g?.enabled??!0;return i.jsxs("div",{"data-testid":p,style:Jw,children:[i.jsxs("div",{style:Zw,children:[i.jsxs("button",{type:"button","data-testid":`${p}-toggle`,onClick:()=>b(T=>!T),"aria-expanded":f,style:Qw,children:[f?"▾":"▸"," ",l("layout_editor.page_settings.seo.bot_preview_title")]}),f?i.jsxs("button",{type:"button","data-testid":`${p}-refresh`,onClick:$,disabled:h,style:Rw,children:["⟳ ",l("layout_editor.page_settings.seo.bot_preview_refresh")]}):null]}),f&&i.jsxs("div",{style:{marginTop:8},children:[g&&!C?i.jsxs("p",{"data-testid":`${p}-disabled`,style:e0,children:["ⓘ ",l("layout_editor.page_settings.seo.bot_preview_disabled")]}):i.jsx("div",{"data-testid":`${p}-body`,style:t0,children:h&&!S?i.jsx("p",{style:As,children:l("layout_editor.translation.loading")}):y?i.jsx("p",{"data-testid":`${p}-error`,style:{...As,color:"#dc2626"},children:y}):S==null?i.jsx("p",{style:As,children:l("layout_editor.page_settings.seo.bot_preview_empty")}):i.jsx("pre",{"data-testid":`${p}-code`,style:n0,children:S})}),i.jsxs("p",{"data-testid":`${p}-sample-note`,style:o0,children:["ⓘ ",l("layout_editor.page_settings.seo.bot_preview_sample_note")]})]})]})}const Jw={borderTop:"1px solid #e2e8f0",paddingTop:10,marginTop:12},Zw={display:"flex",alignItems:"center",gap:8},Qw={background:"none",border:"none",cursor:"pointer",fontSize:13,fontWeight:600,color:"#0f172a",padding:0},Rw={marginLeft:"auto",padding:"4px 10px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",color:"#475569",cursor:"pointer"},e0={margin:0,fontSize:12,color:"#64748b",background:"#f8fafc",border:"1px solid #e2e8f0",borderRadius:6,padding:"8px 10px"},t0={border:"1px solid #e2e8f0",borderRadius:8,background:"#0b1020",padding:0,minHeight:80,overflow:"hidden"},n0={margin:0,padding:12,fontSize:11,lineHeight:1.5,color:"#d1fae5",fontFamily:"monospace",whiteSpace:"pre-wrap",wordBreak:"break-word",maxHeight:360,overflow:"auto"},As={margin:0,padding:12,fontSize:12,color:"#94a3b8"},o0={margin:"8px 0 0",fontSize:11,color:"#94a3b8"};function Lc({option:e,testIdPrefix:t}){const{id:n,friendly:o,source:r}=e;return i.jsxs("span",{style:r0,children:[i.jsx("span",{"data-testid":t?`${t}-title`:void 0,style:i0,children:o??n}),o?i.jsx("span",{"data-testid":t?`${t}-id`:void 0,style:s0,children:n}):null,r?i.jsx("span",{"data-testid":t?`${t}-source`:void 0,style:a0,children:r}):null]})}const r0={display:"inline-flex",alignItems:"baseline",gap:4,flexWrap:"wrap",minWidth:0},i0={fontSize:12,color:"#0f172a"},s0={fontSize:10,color:"#94a3b8",fontFamily:"ui-monospace, monospace"},a0={fontSize:9,fontWeight:600,color:"#7c3aed",background:"#f5f3ff",border:"1px solid #ddd6fe",borderRadius:4,padding:"0 5px",whiteSpace:"nowrap"},l0=["always","hourly","daily","weekly","monthly","yearly","never"];function d0({getValue:e,patch:t,t:n,templateIdentifier:o,candidates:r,availableExtensions:s=[],dataSourceOptions:a=[],previewUrl:l="/",previewLocale:d="ko",seedContext:c,candidatesFetcher:p,ogPreviewFetcher:f,botFetchImpl:b,testidPrefix:g="g7le-seo"}){const m=e("meta",{}).seo,h=m&&typeof m=="object"?m:{},v=u.useCallback((V,te)=>{const be={...e("meta",{})},Ee={...be.seo??{}};te==null||te===""?delete Ee[V]:Ee[V]=te,be.seo=Ee,t("meta",be)},[e,t]),y=h.enabled===!0,_=typeof h.page_type=="string"?h.page_type:"",w=Array.isArray(h.extensions)?h.extensions:[],k=Array.isArray(h.data_sources)?h.data_sources:[],x=w.length>0&&_!=="",[$,S]=u.useState(null),[C,T]=u.useState(null),E=u.useCallback(async(V,te)=>{if(p)return p(V,te);if(typeof fetch>"u")return null;const be=new URLSearchParams;V&&be.set("page_type",V),Array.isArray(te)&&te.length>0&&be.set("extensions",JSON.stringify(te.map(Ue=>({type:Ue.type,id:Ue.id}))));const Ee=be.toString()?`?${be.toString()}`:"",De=await fetch(en(`/api/admin/templates/${encodeURIComponent(o)}/editor/seo-candidates`,"json",null,Ee.replace(/^[?&]+/,"")),{credentials:"same-origin",headers:Ne()}),Se=await De.json().catch(()=>null);return De.ok?Se?.data??null:null},[p,o]),A=u.useMemo(()=>{const be=(e("__editor",{})??{}).original?.meta?.seo;return be&&typeof be=="object"?be:void 0},[e]),N=u.useRef(A);N.current=A;const D=u.useCallback(async V=>{if(f)return f(V);if(typeof fetch>"u")return null;const te=await fetch(`/api/admin/templates/${encodeURIComponent(o)}/editor/seo-og-preview`,{method:"POST",credentials:"same-origin",headers:Ne({"Content-Type":"application/json"}),body:JSON.stringify({seo:V,own_seo:N.current??null,seed_context:c??{},route_params:{}})}),be=await te.json().catch(()=>null);return te.ok?be?.data??null:null},[f,o,c]),B=u.useMemo(()=>JSON.stringify(w),[w]),M=u.useRef(h);M.current=h,u.useEffect(()=>{let V=!1;return(async()=>{const te=await E(_,w);V||S(te)})(),()=>{V=!0}},[_,B]);const O=u.useMemo(()=>JSON.stringify({og:h.og??null,twitter:h.twitter??null,structured_data:h.structured_data??null}),[h.og,h.twitter,h.structured_data]),z=u.useRef(!1);u.useEffect(()=>{let V=!1;const te=()=>{(async()=>{const Ee=await D(M.current);V||T(Ee)})()};if(!z.current)return z.current=!0,te(),()=>{V=!0};const be=setTimeout(te,180);return()=>{V=!0,clearTimeout(be)}},[_,B,O]);const H=$?.page_types??[],P=$?.toggle_settings??[],I=$?.vars??[],L=u.useMemo(()=>JSON.stringify(h),[h]),F=u.useMemo(()=>e("meta")?{meta:e("meta"),components:e("components",[])}:{meta:{},components:[]},[e]),Y=u.useCallback(V=>{w.some(te=>te.type===V.type&&te.id===V.id)||v("extensions",[...w,{type:V.type,id:V.id}])},[w,v]),K=u.useCallback(V=>{const te=w.filter(be=>!(be.type===V.type&&be.id===V.id));v("extensions",te.length>0?te:void 0)},[w,v]),[X,Z]=u.useState(!1),[se,oe]=u.useState(""),G=u.useMemo(()=>{const V=se.trim().toLowerCase();return V?P.filter(te=>`${te.ref} ${te.label??""}`.toLowerCase().includes(V)):P},[P,se]),ee=typeof h.toggle_setting=="string"?h.toggle_setting:"",ge=u.useCallback(V=>{const be=k.includes(V)?k.filter(Ee=>Ee!==V):[...k,V];v("data_sources",be.length>0?be:void 0)},[k,v]);return i.jsxs("div",{"data-testid":g,style:{display:"flex",flexDirection:"column",gap:14},children:[i.jsxs("div",{style:Bt,children:[i.jsx(Mr,{checked:y,onChange:V=>v("enabled",V),testid:`${g}-enabled`,label:n("layout_editor.page_settings.seo.enabled")}),y?null:i.jsxs("p",{"data-testid":`${g}-disabled-note`,style:no,children:["ⓘ ",n("layout_editor.page_settings.seo.disabled_note")]})]}),i.jsx(Lr,{disabled:!y,testid:`${g}-body`,style:{display:"flex",flexDirection:"column",gap:14},children:i.jsxs(i.Fragment,{children:[i.jsxs("div",{style:Bt,children:[i.jsx("label",{style:qt,children:n("layout_editor.page_settings.seo.page_type")}),H.length>0?i.jsxs("select",{"data-testid":`${g}-page-type`,value:_,onChange:V=>v("page_type",V.target.value),style:Br,children:[i.jsx("option",{value:"",children:n("layout_editor.page_settings.seo.page_type_none")}),H.map(V=>i.jsxs("option",{value:V.value,children:[V.label??V.value,V.owner?.name?` (${V.owner.name})`:""]},V.value))]}):i.jsxs(i.Fragment,{children:[i.jsx("input",{type:"text","data-testid":`${g}-page-type-free`,value:_,placeholder:n("layout_editor.page_settings.seo.page_type_free_placeholder"),onChange:V=>v("page_type",V.target.value),style:Br}),i.jsxs("p",{"data-testid":`${g}-page-type-free-note`,style:no,children:["ⓘ ",n("layout_editor.page_settings.seo.page_type_free_note")]})]})]}),i.jsxs("div",{style:Bt,children:[i.jsxs("label",{style:qt,children:["★ ",n("layout_editor.page_settings.seo.extensions")]}),i.jsxs("div",{"data-testid":`${g}-extensions`,style:{display:"flex",flexWrap:"wrap",gap:6},children:[w.map(V=>i.jsxs("span",{"data-testid":`${g}-extension-${V.type}-${V.id}`,style:Pc,children:[V.name??`${V.type}:${V.id}`,i.jsx("button",{type:"button","data-testid":`${g}-extension-remove-${V.type}-${V.id}`,onClick:()=>K(V),style:Oc,children:"✕"})]},`${V.type}:${V.id}`)),i.jsxs("button",{type:"button","data-testid":`${g}-extension-add`,onClick:()=>Z(V=>!V),style:p0,children:["+ ",n("layout_editor.page_settings.seo.add_extension")]})]}),X&&i.jsx("div",{"data-testid":`${g}-extension-picker`,style:Bc,children:s.length===0?i.jsx("p",{style:no,children:n("layout_editor.page_settings.seo.no_extension_candidate")}):s.map(V=>i.jsx("button",{type:"button","data-testid":`${g}-extension-option-${V.type}-${V.id}`,onClick:()=>{Y(V),Z(!1)},style:Wc,children:V.name??`${V.type}:${V.id}`},`${V.type}:${V.id}`))})]}),!x&&i.jsxs("div",{"data-testid":`${g}-defaults-precondition`,style:u0,children:["⚠ ",n("layout_editor.page_settings.seo.defaults_precondition"),i.jsxs("div",{"data-testid":`${g}-defaults-missing`,style:{fontSize:11,marginTop:4},children:[w.length===0?n("layout_editor.page_settings.seo.missing_extensions"):"",_===""?` ${n("layout_editor.page_settings.seo.missing_page_type")}`:""]})]}),i.jsxs("div",{style:Bt,children:[i.jsx("label",{style:qt,children:n("layout_editor.page_settings.seo.toggle_setting")}),i.jsxs("div",{"data-testid":`${g}-toggle-setting`,children:[ee?i.jsxs("div",{style:{display:"flex",gap:6,alignItems:"center",marginBottom:4},children:[i.jsx("code",{style:Pc,children:ee}),i.jsx("button",{type:"button","data-testid":`${g}-toggle-setting-clear`,onClick:()=>v("toggle_setting",void 0),style:Oc,children:"✕"})]}):null,i.jsx("input",{type:"text","data-testid":`${g}-toggle-setting-search`,value:se,placeholder:n("layout_editor.page_settings.seo.toggle_setting_search"),onChange:V=>oe(V.target.value),style:Br}),se.trim()!==""&&i.jsx("div",{"data-testid":`${g}-toggle-setting-results`,style:Bc,children:G.length===0?i.jsx("p",{style:no,children:n("layout_editor.inline_binding.no_results")}):G.map(V=>i.jsx("button",{type:"button","data-testid":`${g}-toggle-setting-option-${V.ref}`,onClick:()=>{v("toggle_setting",V.ref),oe("")},style:Wc,children:V.label??V.ref},V.ref))})]})]}),i.jsxs("div",{style:Bt,children:[i.jsx("label",{style:qt,children:n("layout_editor.page_settings.seo.data_sources")}),i.jsx("p",{style:no,children:n("layout_editor.page_settings.seo.data_sources_hint")}),i.jsx("div",{"data-testid":`${g}-data-sources`,style:{display:"flex",flexDirection:"column",gap:6},children:a.length===0?i.jsx("span",{style:no,children:n("layout_editor.page_settings.seo.data_sources_empty")}):a.map(V=>{const te=k.includes(V.id);return i.jsxs("label",{"data-testid":`${g}-data-source-${V.id}`,style:c0(te),children:[i.jsx("input",{type:"checkbox","data-testid":`${g}-data-source-check-${V.id}`,checked:te,onChange:()=>ge(V.id)}),i.jsx(Lc,{option:V,testIdPrefix:`${g}-data-source-${V.id}`})]},V.id)})})]}),i.jsxs("div",{style:Bt,children:[i.jsx("label",{style:qt,children:n("layout_editor.page_settings.seo.priority")}),i.jsx("input",{type:"range","data-testid":`${g}-priority`,min:0,max:1,step:.1,value:typeof h.priority=="number"?h.priority:.5,onChange:V=>v("priority",Number(V.target.value))}),i.jsx("span",{"data-testid":`${g}-priority-value`,style:{fontSize:12,color:"#475569"},children:typeof h.priority=="number"?h.priority:.5})]}),i.jsxs("div",{style:Bt,children:[i.jsx("label",{style:qt,children:n("layout_editor.page_settings.seo.changefreq")}),i.jsxs("select",{"data-testid":`${g}-changefreq`,value:typeof h.changefreq=="string"?h.changefreq:"",onChange:V=>v("changefreq",V.target.value),style:Br,children:[i.jsx("option",{value:"",children:n("layout_editor.page_settings.seo.use_default")}),l0.map(V=>i.jsx("option",{value:V,children:V},V))]})]}),i.jsxs("div",{style:Bt,children:[i.jsx("label",{style:qt,children:n("layout_editor.page_settings.seo.title")}),i.jsx(_t,{value:typeof h.title=="string"?h.title:"",onChange:V=>v("title",V??""),t:n,candidates:r,testidPrefix:`${g}-title`,enableExpressionTree:!0,expressionTreeCollapsible:!0})]}),i.jsxs("div",{style:Bt,children:[i.jsx("label",{style:qt,children:n("layout_editor.page_settings.seo.description")}),i.jsx(_t,{value:typeof h.description=="string"?h.description:"",onChange:V=>v("description",V??""),t:n,candidates:r,testidPrefix:`${g}-description`,enableExpressionTree:!0,expressionTreeCollapsible:!0})]}),i.jsx(hw,{og:h.og,twitter:h.twitter,onChangeOg:V=>v("og",Object.keys(V).length>0?V:void 0),onChangeTwitter:V=>v("twitter",Object.keys(V).length>0?V:void 0),ogPreview:C?.og,twitterPreview:C?.twitter,defaultsAvailable:C?.defaultsAvailable??!1,candidates:r,t:n}),i.jsxs("div",{style:Bt,children:[i.jsx("label",{style:qt,children:n("layout_editor.page_settings.seo.structured_data")}),i.jsx(Aw,{value:h.structured_data,onChange:V=>v("structured_data",V??void 0),autoBlock:C?.structured?.autoBlock,autoMeta:C?.structured?.autoMeta,lockedByFilter:C?.structured?.lockedByFilter,filteredBlock:C?.structured?.filteredBlock,pageType:_,candidates:r,t:n})]}),i.jsxs("div",{style:Bt,children:[i.jsx("label",{style:qt,children:n("layout_editor.page_settings.seo.vars")}),i.jsx(Hw,{vars:h.vars,onChange:V=>v("vars",Object.keys(V).length>0?V:void 0),varCandidates:I,gatingMet:x,candidates:r,t:n})]}),i.jsx(Xw,{templateIdentifier:o,layout:F,url:l,locale:d,seedContext:c,settingsSignature:L,t:n,fetchImpl:b})]})})]})}const Bt={display:"flex",flexDirection:"column",gap:4},qt={fontSize:12,fontWeight:600,color:"#334155"},Br={padding:"5px 8px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,width:"100%",minWidth:0,boxSizing:"border-box"},no={margin:"4px 0 0",fontSize:11,color:"#94a3b8"},c0=e=>({display:"flex",alignItems:"center",gap:8,padding:"6px 10px",fontSize:12,border:`1px solid ${e?"#3b82f6":"#e2e8f0"}`,borderRadius:8,background:e?"#eff6ff":"#fff",cursor:"pointer",minWidth:0}),u0={fontSize:12,color:"#b45309",background:"#fffbeb",border:"1px solid #fcd34d",borderRadius:6,padding:"8px 10px"},Pc={display:"inline-flex",alignItems:"center",gap:4,fontSize:11,padding:"3px 8px",borderRadius:999,background:"#eef2ff",color:"#3730a3"},Oc={border:"none",background:"transparent",cursor:"pointer",color:"#6366f1",fontSize:11,padding:0},p0={fontSize:11,padding:"3px 10px",borderRadius:999,border:"1px dashed #cbd5e1",background:"#f8fafc",color:"#475569",cursor:"pointer"},Bc={marginTop:4,border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",padding:6,display:"flex",flexDirection:"column",gap:2},Wc={textAlign:"left",border:"none",background:"transparent",cursor:"pointer",padding:"5px 8px",borderRadius:4,fontSize:12,color:"#0f172a"},f0={setState:"state",loadFromLocalStorage:"state",saveToLocalStorage:"state",navigate:"nav",openWindow:"nav",navigateBack:"nav",navigateForward:"nav",replaceUrl:"nav",toast:"notify",openModal:"notify",closeModal:"notify",showAlert:"notify",setError:"notify",refetchDataSource:"data",appendDataSource:"data",updateDataSource:"data",apiCall:"data",scrollIntoView:"etc",login:"etc",logout:"etc",setLocale:"locale",emitEvent:"etc",conditions:"flow",sequence:"flow",parallel:"flow",switch:"flow",suppress:"flow",showErrorPage:"error"},g0={state:"layout_editor.action.group.state",nav:"layout_editor.action.group.nav",notify:"layout_editor.action.group.notify",data:"layout_editor.action.group.data",locale:"layout_editor.action.group.locale",etc:"layout_editor.action.group.etc",flow:"layout_editor.action.group.flow",error:"layout_editor.action.group.error",extension:"layout_editor.action.group.extension"},h0={init:["state","data","notify","nav","locale","etc","flow","error","extension"],component:["nav","notify","data","state","locale","etc","flow","error","extension"]};function y0(e,t){const n=e?.[t];if(n&&typeof n=="object"){const o=n.__source;if(o&&typeof o=="object")return o}return null}function b0(e,t){return!e||e.kind==="core"?t("layout_editor.action.source_core"):e.id??t(`layout_editor.action.source_${e.kind}`)}function jc(e,t,n){return typeof e!="string"?n:e.startsWith("$t:")?t(e.slice(3)):e}function m0({recipes:e,t,onAdd:n,context:o="init",testIdPrefix:r="g7le-action-add"}){const[s,a]=u.useState(!1),[l,d]=u.useState(""),c=u.useMemo(()=>ko(e),[e]),p=u.useMemo(()=>{const f=h0[o],b=new Map;for(const m of c){const h=y0(e,m.id),y=h!=null&&h.kind!=="core"?"extension":f0[m.id]??"etc",_=b.get(y)??[];_.push({recipe:m,source:h}),b.set(y,_)}const g=l.trim().toLowerCase();return f.map(m=>{let h=b.get(m)??[];return g&&(h=h.filter(({recipe:v})=>jc(v.label,t,v.id).toLowerCase().includes(g)||v.id.toLowerCase().includes(g)||v.build.handler.toLowerCase().includes(g))),{groupKey:m,items:h}}).filter(m=>m.items.length>0)},[c,e,o,l,t]);return c.length===0?i.jsx("div",{"data-testid":`${r}-empty`,style:I0,children:t("layout_editor.action.no_recipes")}):i.jsxs("div",{className:r,"data-testid":r,style:{minWidth:0},children:[i.jsxs("button",{type:"button","data-testid":`${r}-toggle`,onClick:()=>a(f=>!f),style:v0,children:[t("layout_editor.action.add_action")," ▾"]}),s?i.jsxs("div",{"data-testid":`${r}-list`,style:_0,children:[i.jsx("input",{type:"text","data-testid":`${r}-search`,value:l,placeholder:t("layout_editor.action.add_search"),onChange:f=>d(f.target.value),style:x0}),p.map(({groupKey:f,items:b})=>i.jsxs("div",{"data-testid":`${r}-group-${f}`,children:[i.jsxs("div",{style:w0,children:["━━ ",t(g0[f]??f)," ━━"]}),b.map(({recipe:g,source:m})=>i.jsxs("button",{type:"button","data-testid":`g7le-init-action-spec-${g.id}`,onClick:()=>{n(Sr(g,{})),a(!1),d("")},style:k0,children:[i.jsx("span",{style:S0,children:jc(g.label,t,g.id)}),i.jsxs("span",{"data-testid":`g7le-init-action-spec-source-${g.id}`,style:C0,children:["〔",b0(m,t),"〕"]}),i.jsx("code",{style:$0,children:g.build.handler})]},g.id))]},f)),p.length===0?i.jsx("div",{"data-testid":`${r}-no-match`,style:E0,children:t("layout_editor.action.add_no_match")}):null]}):null]})}const v0={padding:"6px 12px",fontSize:12,border:"1px dashed #94a3b8",borderRadius:6,background:"#fff",color:"#475569",cursor:"pointer"},_0={marginTop:6,border:"1px solid #cbd5e1",borderRadius:8,background:"#fff",maxHeight:320,overflowY:"auto",padding:8},x0={width:"100%",padding:"5px 8px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,marginBottom:6,boxSizing:"border-box"},w0={fontSize:11,fontWeight:700,color:"#94a3b8",padding:"6px 4px 2px"},k0={display:"flex",alignItems:"center",gap:6,width:"100%",textAlign:"left",padding:"6px 8px",fontSize:12,border:"none",background:"transparent",cursor:"pointer",borderRadius:6},S0={flex:1,minWidth:0,color:"#0f172a"},C0={fontSize:10,color:"#64748b",whiteSpace:"nowrap"},$0={fontSize:10,color:"#cbd5e1",fontFamily:"monospace",whiteSpace:"nowrap"},E0={fontSize:12,color:"#94a3b8",padding:"8px 4px"},I0={fontSize:12,color:"#94a3b8",padding:"8px 0"};function A0(e,t){return t.startsWith("$t:")?e(t.slice(3)):t}function T0({action:e,recipe:t,values:n={},t:o,missingRequired:r=!1,showCodeButton:s=!0,testIdPrefix:a="g7le-action-preview"}){const[l,d]=u.useState(!1),c=t?Kd(t,n,p=>A0(o,p)):typeof e.handler=="string"?e.handler:o("layout_editor.action.advanced_action");return i.jsxs("div",{className:a,"data-testid":a,style:D0,children:[r?i.jsxs("span",{"data-testid":`${a}-missing`,style:N0,children:["⚠ ",o("layout_editor.action.summary_missing_required")]}):i.jsx("span",{"data-testid":`${a}-summary`,style:z0,children:c}),s?i.jsx("button",{type:"button","data-testid":`${a}-code-toggle`,onClick:()=>d(p=>!p),style:M0,"aria-expanded":l,"aria-label":o("layout_editor.action.view_code"),children:""}):null,s&&l?i.jsx("pre",{"data-testid":`${a}-code`,style:L0,children:JSON.stringify(e,null,2)}):null]})}const D0={display:"flex",flexWrap:"wrap",alignItems:"center",gap:6,minWidth:0},z0={flex:1,minWidth:0,fontSize:12,color:"#475569"},N0={flex:1,minWidth:0,fontSize:12,color:"#b45309"},M0={border:"1px solid #cbd5e1",borderRadius:6,background:"#f8fafc",color:"#475569",cursor:"pointer",fontSize:11,padding:"2px 6px",whiteSpace:"nowrap"},L0={flexBasis:"100%",margin:"6px 0 0",padding:8,background:"#0f172a",color:"#e2e8f0",borderRadius:6,fontSize:11,overflow:"auto"},Hc="__g7le_custom__";function Wr({value:e,onChange:t,candidates:n,placeholder:o,testid:r,t:s,bindingCandidates:a}){const l=e==null?"":String(e),d=Array.isArray(n)&&n.length>0,c=d&&n.some(b=>b.value===l),[p,f]=u.useState(!d||l!==""&&!c);return u.useEffect(()=>{l!==""&&d&&!c&&f(!0)},[l,d,c]),d&&!p?i.jsxs("div",{style:Uc,children:[i.jsxs("select",{className:"g7le-widget g7le-widget--picker","data-testid":r,value:c?l:"",onChange:b=>{const g=b.target.value;if(g===Hc){f(!0);return}t(g===""?void 0:g)},style:{...Kc,flex:1,minWidth:0},children:[i.jsx("option",{value:"",children:s("layout_editor.control.default")}),n.map(b=>i.jsx("option",{value:b.value,children:b.label},b.value)),i.jsx("option",{value:Hc,children:s("layout_editor.recipe.custom_input")})]}),i.jsx("button",{type:"button","data-testid":`${r}-to-custom`,title:s("layout_editor.recipe.custom_input"),"aria-label":s("layout_editor.recipe.custom_input"),onClick:()=>f(!0),style:qc,children:"✎"})]}):i.jsxs("div",{style:Uc,children:[i.jsx("div",{style:{flex:1,minWidth:0},children:i.jsx(Ke,{value:l,onChange:b=>t(b===""?void 0:b),t:s,candidates:a,placeholder:o,testidPrefix:`${r}-input`})}),d&&i.jsx("button",{type:"button","data-testid":`${r}-pick`,title:s("layout_editor.recipe.pick_from_list"),onClick:()=>{f(!1),t(void 0)},style:qc,children:"≡"})]})}function P0({value:e,onChange:t,candidates:n,t:o,bindingCandidates:r}){return i.jsx(Wr,{value:e,onChange:t,candidates:n,placeholder:o("layout_editor.recipe.page_placeholder"),testid:"g7le-widget-page-picker",t:o,bindingCandidates:r})}function Fc({value:e,onChange:t,candidates:n,t:o,bindingCandidates:r}){return i.jsx(Wr,{value:e,onChange:t,candidates:n,placeholder:o("layout_editor.recipe.datasource_placeholder"),testid:"g7le-widget-datasource-picker",t:o,bindingCandidates:r})}function Ts({value:e,onChange:t,candidates:n,t:o,bindingCandidates:r}){return i.jsx(Wr,{value:e,onChange:t,candidates:n,placeholder:o("layout_editor.recipe.state_key_placeholder"),testid:"g7le-widget-state-key-picker",t:o,bindingCandidates:r})}function O0({value:e,onChange:t,candidates:n,t:o,bindingCandidates:r}){return i.jsx(Wr,{value:e,onChange:t,candidates:n,placeholder:o("layout_editor.recipe.modal_placeholder"),testid:"g7le-widget-modal-picker",t:o,bindingCandidates:r})}function B0({value:e,onChange:t,t:n,bindingCandidates:o}){const r=typeof e=="string"?e:e==null?"":String(e);return i.jsx("div",{className:"g7le-widget g7le-widget--i18n-text","data-testid":"g7le-widget-i18n-text",style:{flex:1,minWidth:100,width:"100%"},children:i.jsx(_t,{value:r,onChange:s=>t(s===""?void 0:s),t:n,placeholder:n("layout_editor.recipe.i18n_text_placeholder"),testidPrefix:"g7le-widget-i18n-text-field",candidates:o,enableExpressionTree:!0,expressionTreeCollapsible:!0})})}function W0({value:e,onChange:t,candidates:n,t:o}){const r=e==null?"":String(e),s=r.split(",").map(d=>d.trim()).filter(Boolean),a=Array.isArray(n)&&n.length>0,l=d=>{if(!d)return;const c=[...s,d];t(c.join(", "))};return i.jsxs("div",{className:"g7le-widget g7le-widget--binding-list","data-testid":"g7le-widget-binding-list",style:{display:"flex",flexDirection:"column",gap:4,width:"100%"},children:[i.jsx("input",{type:"text","data-testid":"g7le-widget-binding-list-input",value:r,placeholder:o("layout_editor.recipe.datasource_placeholder"),onChange:d=>t(d.target.value===""?void 0:d.target.value),style:Vc}),a?i.jsxs("select",{className:"g7le-widget g7le-widget--picker","data-testid":"g7le-widget-binding-list-add",value:"",onChange:d=>{l(d.target.value),d.target.value=""},style:{...Kc,minWidth:0},children:[i.jsx("option",{value:"",children:o("layout_editor.recipe.pick_from_list")}),n.map(d=>i.jsx("option",{value:d.value,children:d.label},d.value))]}):null]})}function j0({value:e,onChange:t}){const n=e==null?"":String(e);return i.jsx("input",{type:"text",className:"g7le-widget g7le-widget--text","data-testid":"g7le-widget-text",value:n,onChange:o=>t(o.target.value===""?void 0:o.target.value),style:Vc})}const Kc={padding:"5px 8px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",minWidth:140,width:"100%"},Vc={flex:1,padding:"5px 8px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,minWidth:100,width:"100%"},Uc={display:"flex",gap:4,alignItems:"flex-start",width:"100%"},qc={padding:"4px 8px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",color:"#64748b",cursor:"pointer"};function H0(e){if(e==null)return"null";if(Array.isArray(e))return"list";switch(typeof e){case"number":return"number";case"boolean":return"boolean";case"string":return"string";default:return"object"}}function Gc(e){switch(e){case"string":return"";case"number":return 0;case"boolean":return!1;case"null":return null;case"list":return[];case"object":return{};default:return null}}function Ds(e){return/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(e)}function F0(e,t,n){return t.includes(n)?"self":e.includes(n)?"inherited":"self"}function K0(e){const t=e.state&&typeof e.state=="object"&&Object.keys(e.state).length>0,n=e.initLocal&&typeof e.initLocal=="object"?e.initLocal:void 0;if(!t)return{initLocal:n??{},migrated:!1};const o={...e.state};if(n)for(const[r,s]of Object.entries(n))o[r]=s;return{initLocal:o,migrated:!0}}const Yc=[{kind:"string",labelKey:"layout_editor.initstate.kind_string"},{kind:"number",labelKey:"layout_editor.initstate.kind_number"},{kind:"boolean",labelKey:"layout_editor.initstate.kind_boolean"},{kind:"null",labelKey:"layout_editor.initstate.kind_null"},{kind:"list",labelKey:"layout_editor.initstate.kind_list"},{kind:"object",labelKey:"layout_editor.initstate.kind_object"}];function zn({value:e,onChange:t,t:n,path:o,scope:r,depth:s=0,renderStringLeaf:a}){const l=H0(e),d=c=>{t(Gc(c))};return i.jsxs("div",{className:"g7le-initstate-value",style:{paddingLeft:s>0?12:0,minWidth:0},children:[i.jsxs("div",{style:U0,children:[i.jsx("select",{"data-testid":`g7le-initstate-type-${o}`,value:l,onChange:c=>d(c.target.value),style:Xc,children:Yc.map(({kind:c,labelKey:p})=>i.jsx("option",{value:c,children:n(p)},c))}),l==="string"?a?i.jsx("div",{style:{flex:1,minWidth:0},children:a({value:String(e??""),onChange:c=>t(c),testidPrefix:`g7le-initstate-value-${o}`})}):i.jsx("input",{type:"text","data-testid":`g7le-initstate-value-${o}`,value:String(e??""),onChange:c=>t(c.target.value),style:Jc}):l==="number"?i.jsx("input",{type:"number","data-testid":`g7le-initstate-value-${o}`,value:typeof e=="number"?e:"",onChange:c=>{const p=parseFloat(c.target.value);t(Number.isNaN(p)?0:p)},style:Jc}):l==="boolean"?i.jsx(Mr,{checked:!!e,onChange:t,testid:`g7le-initstate-value-${o}`,label:n(e?"layout_editor.overlay.on":"layout_editor.overlay.off")}):l==="null"?i.jsx("span",{"data-testid":`g7le-initstate-value-${o}`,style:q0,children:"null"}):null]}),l==="list"?i.jsxs("div",{style:Zc,children:[e.map((c,p)=>i.jsxs("div",{style:G0,children:[i.jsxs("span",{style:Y0,children:["[",p,"]"]}),i.jsx("div",{style:{flex:1,minWidth:0},children:i.jsx(zn,{value:c,onChange:f=>{const b=e.slice();b[p]=f,t(b)},t:n,path:`${o}.${p}`,scope:r,depth:s+1,renderStringLeaf:a})}),i.jsx("button",{type:"button","data-testid":`g7le-initstate-item-remove-${o}.${p}`,onClick:()=>t(e.filter((f,b)=>b!==p)),style:Qc,children:"✕"})]},p)),i.jsxs("button",{type:"button","data-testid":`g7le-initstate-item-add-${o}`,onClick:()=>t([...e,""]),style:Rc,children:["+ ",n("layout_editor.initstate.add_item")]})]}):null,l==="object"?i.jsx(V0,{value:e,onChange:t,t:n,path:o,scope:r,depth:s+1,renderStringLeaf:a}):null]})}function V0({value:e,onChange:t,t:n,path:o,scope:r,depth:s,renderStringLeaf:a}){const[l,d]=u.useState(""),[c,p]=u.useState("string"),[f,b]=u.useState(null),g=Object.entries(e),m=()=>{const h=l.trim();if(!h){b(n("layout_editor.initstate.name_required"));return}if(!Ds(h)){b(n("layout_editor.initstate.name_invalid"));return}if(h in e){b(n("layout_editor.initstate.name_duplicate"));return}t({...e,[h]:Gc(c)}),d(""),b(null)};return i.jsxs("div",{style:Zc,children:[g.map(([h,v])=>i.jsxs("div",{style:X0,children:[i.jsx("code",{style:J0,children:h}),i.jsx("div",{style:{flex:1,minWidth:0},children:i.jsx(zn,{value:v,onChange:y=>t({...e,[h]:y}),t:n,path:`${o}.${h}`,scope:r,depth:s,renderStringLeaf:a})}),i.jsx("button",{type:"button","data-testid":`g7le-initstate-subremove-${o}.${h}`,onClick:()=>{const y={...e};delete y[h],t(y)},style:Qc,children:"✕"})]},h)),i.jsxs("div",{style:Z0,children:[i.jsx("input",{type:"text","data-testid":`g7le-initstate-subkey-input-${o}`,value:l,placeholder:n("layout_editor.initstate.subkey_name"),onChange:h=>{d(h.target.value),f&&b(null)},onKeyDown:h=>{h.key==="Enter"&&(h.preventDefault(),m())},"aria-invalid":f?!0:void 0,style:{...Q0,...f?R0:null}}),i.jsx("select",{"data-testid":`g7le-initstate-subkey-kind-${o}`,value:c,onChange:h=>p(h.target.value),"aria-label":n("layout_editor.initstate.value_kind"),style:Xc,children:Yc.map(({kind:h,labelKey:v})=>i.jsx("option",{value:h,children:n(v)},h))}),i.jsxs("button",{type:"button","data-testid":`g7le-initstate-subkey-add-${o}`,onClick:m,style:Rc,children:["+ ",n("layout_editor.initstate.add_subkey")]})]}),f?i.jsx("p",{"data-testid":`g7le-initstate-subkey-error-${o}`,style:ek,children:f}):null]})}const U0={display:"flex",gap:6,alignItems:"center",minWidth:0},Xc={padding:"4px 6px",fontSize:11,border:"1px solid #cbd5e1",borderRadius:6,color:"#64748b"},Jc={flex:1,minWidth:0,padding:"4px 8px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,boxSizing:"border-box"},q0={fontSize:12,color:"#94a3b8",fontFamily:"monospace"},Zc={display:"flex",flexDirection:"column",gap:6,marginTop:6,paddingLeft:8,borderLeft:"2px solid #e2e8f0"},G0={display:"flex",gap:6,alignItems:"flex-start",minWidth:0},Y0={fontSize:11,color:"#94a3b8",minWidth:28,paddingTop:4},X0={display:"flex",gap:6,alignItems:"flex-start",minWidth:0},J0={fontSize:11,color:"#475569",fontFamily:"monospace",minWidth:72,paddingTop:4},Qc={border:"none",background:"transparent",color:"#94a3b8",cursor:"pointer",fontSize:12},Rc={alignSelf:"flex-start",padding:"2px 8px",fontSize:11,border:"1px dashed #94a3b8",borderRadius:6,background:"#fff",color:"#475569",cursor:"pointer"},Z0={display:"flex",gap:6,alignItems:"center"},Q0={padding:"4px 8px",fontSize:11,border:"1px solid #cbd5e1",borderRadius:6},R0={border:"1px solid #dc2626"},ek={margin:"2px 0 0",fontSize:11,color:"#dc2626"},tk=new Set(["text","data-chip","number"]),nk=new Set(["select","toggle","page-picker","datasource-picker","state-key-picker","modal-picker","component-target-picker","tag-input","locale-picker","i18n-text","segmented","slider","color","image"]);function ok(e,t,n){return typeof e!="string"?n:e.startsWith("$t:")?t(e.slice(3)):e}function rk(e,t){if(e==="page-picker")return t.pageCandidates;if(e==="datasource-picker")return t.dataSourceCandidates;if(e==="state-key-picker")return t.stateKeyCandidates;if(e==="modal-picker")return t.modalCandidates}function ik({param:e,value:t,t:n,pools:o,onChange:r,testIdPrefix:s,renderActionList:a,siblingValues:l}){const d=ok(e.label,n,e.key);if(e.advanced)return i.jsxs("div",{"data-testid":`${s}-locked-${e.key}`,style:hk,children:[i.jsx("span",{style:yk,children:d}),i.jsxs("span",{style:bk,children:["[",n("layout_editor.action.advanced"),"] ",n("layout_editor.init_actions.advanced_param_hint")]})]});if(e.widget==="action-list"&&a){const f=Array.isArray(t)?t.filter(b=>!!b&&typeof b=="object"):[];return i.jsxs("div",{"data-testid":`${s}-param-${e.key}`,style:{display:"flex",flexDirection:"column",gap:4},children:[i.jsx("span",{style:oo,children:d}),a(f,b=>r(b.length===0?void 0:b),`${s}-${e.key}`,e.key)]})}if(e.widget==="branch-list"&&a){const f=Array.isArray(t)?t.filter(b=>!!b&&typeof b=="object"):[];return i.jsxs("div",{"data-testid":`${s}-param-${e.key}`,style:{display:"flex",flexDirection:"column",gap:4},children:[i.jsx("span",{style:oo,children:d}),i.jsx(sk,{branches:f,onChange:b=>r(b.length===0?void 0:b),t:n,pools:o,renderActionList:a,testIdPrefix:`${s}-${e.key}`})]})}if(e.widget==="key-value")return i.jsxs("div",{"data-testid":`${s}-param-${e.key}`,style:{display:"flex",flexDirection:"column",gap:4},children:[i.jsx("span",{style:oo,children:d}),i.jsx(uk,{value:t,onChange:r,t:n,candidates:o.bindingCandidates,testIdPrefix:`${s}-kv-${e.key}`})]});if(e.widget==="state-key-value")return i.jsxs("div",{"data-testid":`${s}-param-${e.key}`,style:{display:"flex",flexDirection:"column",gap:4},children:[i.jsx("span",{style:oo,children:d}),i.jsx(pk,{value:t,onChange:r,t:n,candidates:o.bindingCandidates,stateKeyCandidates:o.stateKeyCandidates,param:e,siblingTarget:l?.target,testIdPrefix:`${s}-skv-${e.key}`})]});if(tk.has(e.widget??"")||!nk.has(e.widget??""))return i.jsxs("div",{"data-testid":`${s}-param-${e.key}`,style:tu,children:[i.jsx("span",{style:oo,children:d}),i.jsx("div",{style:{flex:1,minWidth:0},children:i.jsx(Ke,{value:typeof t=="string"?t:t==null?"":String(t),onChange:f=>r(f===""?void 0:f),t:n,candidates:o.bindingCandidates,placeholder:e.widget==="number"?n("layout_editor.action_param.number_placeholder"):void 0,testidPrefix:`${s}-chip-${e.key}`})})]});const p=Tn(e.widget);return i.jsxs("div",{"data-testid":`${s}-param-${e.key}`,style:tu,children:[i.jsx("span",{style:oo,children:d}),i.jsx("div",{style:{flex:1,minWidth:0},children:p?i.jsx(p,{control:{...e},value:t,onChange:r,t:n,candidates:rk(e.widget,o),bindingCandidates:o.bindingCandidates}):i.jsx(Ke,{value:typeof t=="string"?t:t==null?"":String(t),onChange:f=>r(f===""?void 0:f),t:n,candidates:o.bindingCandidates,testidPrefix:`${s}-chip-${e.key}`})})]})}function sk({branches:e,onChange:t,t:n,pools:o,renderActionList:r,testIdPrefix:s}){const a=b=>Array.isArray(b)?b.filter(g=>!!g&&typeof g=="object"):b&&typeof b=="object"?[b]:[],l=b=>{if(b.length!==0)return b.length===1?b[0]:b},d=(b,g)=>{const m=e.slice();m[b]=g,t(m)},c=b=>{t(e.filter((g,m)=>m!==b))},p=(b,g)=>{if(g<0||g>=e.length)return;const m=e.slice(),[h]=m.splice(b,1);m.splice(g,0,h),t(m)},f=()=>{t([...e,{if:"",then:void 0}])};return i.jsxs("div",{"data-testid":`${s}-branches`,style:{display:"flex",flexDirection:"column",gap:8},children:[e.map((b,g)=>{const m=typeof b.if=="string"?b.if:"",h=a(b.then);return i.jsxs("div",{"data-testid":`${s}-branch-${g}`,style:ak,children:[i.jsxs("div",{style:lk,children:[i.jsxs("span",{style:dk,children:[n(m?"layout_editor.action_recipe.conditions.branch_when":"layout_editor.action_recipe.conditions.branch_else")," ",g+1]}),i.jsxs("span",{style:{display:"flex",gap:4},children:[i.jsx("button",{type:"button","data-testid":`${s}-branch-${g}-up`,onClick:()=>p(g,g-1),disabled:g===0,style:zs,title:n("layout_editor.action_recipe.conditions.move_up"),children:"↑"}),i.jsx("button",{type:"button","data-testid":`${s}-branch-${g}-down`,onClick:()=>p(g,g+1),disabled:g===e.length-1,style:zs,title:n("layout_editor.action_recipe.conditions.move_down"),children:"↓"}),i.jsx("button",{type:"button","data-testid":`${s}-branch-${g}-remove`,onClick:()=>c(g),style:zs,title:n("layout_editor.action_recipe.conditions.remove_branch"),children:"✕"})]})]}),i.jsxs("div",{style:{display:"flex",flexDirection:"column",gap:3},children:[i.jsx("span",{style:eu,children:n("layout_editor.action_recipe.conditions.param_if")}),i.jsx(Ke,{value:m,onChange:v=>d(g,{...b,if:v}),t:n,candidates:o.bindingCandidates,testidPrefix:`${s}-branch-${g}-if`})]}),i.jsxs("div",{style:{display:"flex",flexDirection:"column",gap:3},children:[i.jsx("span",{style:eu,children:n("layout_editor.action_recipe.conditions.param_then")}),r(h,v=>d(g,{...b,then:l(v)}),`${s}-branch-${g}-then`)]})]},g)}),i.jsxs("button",{type:"button","data-testid":`${s}-add-branch`,onClick:f,style:ck,children:["+ ",n("layout_editor.action_recipe.conditions.add_branch")]})]})}const ak={border:"1px solid #e2e8f0",borderRadius:6,padding:8,display:"flex",flexDirection:"column",gap:6,background:"#f8fafc"},lk={display:"flex",justifyContent:"space-between",alignItems:"center"},dk={fontSize:12,fontWeight:700,color:"#475569"},eu={fontSize:11,color:"#64748b"},zs={border:"1px solid #cbd5e1",background:"#fff",borderRadius:4,width:22,height:22,cursor:"pointer",fontSize:12,lineHeight:"1",color:"#475569"},ck={border:"1px dashed #94a3b8",background:"#fff",borderRadius:6,padding:"6px 10px",cursor:"pointer",fontSize:12,color:"#475569",alignSelf:"flex-start"};function uk({value:e,onChange:t,t:n,candidates:o,testIdPrefix:r,renderKeyInput:s}){const a=e&&typeof e=="object"&&!Array.isArray(e)?Object.entries(e).map(([l,d])=>({key:l,value:typeof d=="string"?d:d==null?"":String(d)})):[];return i.jsx(Pr,{value:a,onChange:l=>{const d={};for(const c of l)c.key.trim()!==""&&(d[c.key]=c.value??"");t(Object.keys(d).length===0?void 0:d)},keyField:"key",valueField:"value",t:n,candidates:o,testidPrefix:r,keyPlaceholder:n("layout_editor.action_param.key_placeholder"),valuePlaceholder:n("layout_editor.action_param.value_placeholder"),addLabel:n("layout_editor.action_param.add_pair"),renderKeyInput:s})}function pk({value:e,onChange:t,t:n,candidates:o,stateKeyCandidates:r,param:s,siblingTarget:a,testIdPrefix:l}){const d=e&&typeof e=="object"&&!Array.isArray(e)?Object.entries(e):[],c=typeof a=="string"?a:"";if((c.startsWith("_local.")||c.startsWith("global.")||c.startsWith("$parent._local.")||c.startsWith("$root._local."))&&d.length===1&&d[0][0]==="value"){const y=d[0][1];return i.jsx("div",{"data-testid":`${l}-path-value`,style:{display:"flex",flexDirection:"column",gap:4},children:i.jsx(zn,{value:y,onChange:_=>t(_===void 0?void 0:{value:_}),t:n,path:"value",scope:`${l}-path-value-val`,renderStringLeaf:_=>i.jsx(Ke,{value:_.value,onChange:_.onChange,t:n,candidates:o,testidPrefix:_.testidPrefix})})})}const b=(y,_)=>{const w={};for(const[k,x]of d)w[k]=k===y?_:x;t(Object.keys(w).length===0?void 0:w)},g=y=>{const _={};for(const[w,k]of d)w!==y&&(_[w]=k);t(Object.keys(_).length===0?void 0:_)},[m,h]=u.useState(""),v=()=>{const y=m.trim();if(y===""||d.some(([w])=>w===y))return;const _={};for(const[w,k]of d)_[w]=k;_[y]="",t(_),h("")};return i.jsxs("div",{"data-testid":l,style:{display:"flex",flexDirection:"column",gap:6},children:[d.map(([y,_])=>i.jsxs("div",{"data-testid":`${l}-row-${y}`,style:{display:"flex",gap:6,alignItems:"flex-start",border:"1px solid #e2e8f0",borderRadius:8,padding:8},children:[i.jsxs("div",{style:{flex:1,minWidth:0,display:"flex",flexDirection:"column",gap:6},children:[i.jsx("div",{style:{minWidth:0},children:i.jsx(Ts,{control:{...s},value:y,onChange:w=>{const k=w==null?"":String(w);if(k===""||k===y||d.some(([$])=>$===k))return;const x={};for(const[$,S]of d)x[$===y?k:$]=S;t(x)},t:n,candidates:r})}),i.jsx("div",{style:{minWidth:0},children:i.jsx(zn,{value:_,onChange:w=>b(y,w),t:n,path:y,scope:`${l}-val`,renderStringLeaf:w=>i.jsx(Ke,{value:w.value,onChange:w.onChange,t:n,candidates:o,testidPrefix:w.testidPrefix})})})]}),i.jsx("button",{type:"button","data-testid":`${l}-remove-${y}`,onClick:()=>g(y),title:n("layout_editor.page_settings.seo.remove"),"aria-label":n("layout_editor.page_settings.seo.remove"),style:{flex:"0 0 auto",padding:"4px 8px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",color:"#64748b",cursor:"pointer"},children:"✕"})]},y)),i.jsxs("div",{style:{display:"flex",gap:6,alignItems:"center"},children:[i.jsx("div",{style:{flex:1,minWidth:0},children:i.jsx(Ts,{control:{...s},value:m,onChange:y=>h(y==null?"":String(y)),t:n,candidates:r})}),i.jsxs("button",{type:"button","data-testid":`${l}-add`,onClick:v,style:{flex:"0 0 auto",padding:"5px 10px",fontSize:12,border:"1px dashed #cbd5e1",borderRadius:6,background:"#f8fafc",color:"#475569",cursor:"pointer"},children:["+ ",n("layout_editor.action_param.add_pair")]})]})]})}function Ns({raw:e,recipe:t,values:n,t:o,pools:r,onChange:s,testIdPrefix:a,renderActionList:l}){const d=(c,p)=>{const f={...n,[c]:p},b=Sr(t,f),g=cs(e);s(g?Vd(b,g):b)};return i.jsx(i.Fragment,{children:t.params.filter(c=>fk(c,n)).map(c=>i.jsx(ik,{param:c,value:n[c.key],t:o,pools:r,onChange:p=>d(c.key,p),testIdPrefix:a,renderActionList:l,siblingValues:n},c.key))})}function fk(e,t){const n=e.dependsOn;return!n||typeof n!="object"||typeof n.param!="string"?!0:t[n.param]===n.equals}function gk({raw:e,t,onChange:n,testIdPrefix:o,candidates:r}){const[s,a]=u.useState(!1);return i.jsxs("div",{"data-testid":`${o}-if`,style:mk,children:[i.jsxs("button",{type:"button",onClick:()=>a(l=>!l),style:vk,children:["▸ ",t("layout_editor.init_actions.if_label")]}),s?i.jsx("div",{"data-testid":`${o}-if-input`,style:{minWidth:0},children:i.jsx(Ke,{value:cs(e)??"",onChange:l=>n(Vd(e,l)),t,candidates:r,placeholder:t("layout_editor.init_actions.if_placeholder"),testidPrefix:`${o}-if-chip`})}):null]})}const tu={display:"flex",alignItems:"center",gap:8,minWidth:0},oo={fontSize:11,color:"#64748b",minWidth:80},hk={display:"flex",alignItems:"center",gap:8,padding:"4px 0"},yk={fontSize:11,color:"#64748b",minWidth:80},bk={fontSize:11,color:"#92400e"},mk={display:"flex",flexDirection:"column",gap:4},vk={textAlign:"left",border:"none",background:"transparent",color:"#475569",cursor:"pointer",fontSize:11,padding:0};function _k(e,t,n){if(!t)return n("layout_editor.action.source_core");const o=e?.[t],r=o&&typeof o=="object"&&o.__source?o.__source:null;return!r||r.kind==="core"?n("layout_editor.action.source_core"):r.id??n(`layout_editor.action.source_${r.kind}`)}function xk(e,t,n){const o=Yn(e,t);if(o.kind==="preset"){const r=t.find(s=>s.id===o.recipeId);return Kd(r??null,o.values,s=>n(s.startsWith("$t:")?s.slice(3):s))}return typeof e.handler=="string"?e.handler:n("layout_editor.action_list.unknown")}function Gt({actions:e,onChange:t,t:n,recipes:o,chipContext:r,actionChipCandidates:s,addContext:a="init",renderAddPicker:l,renderEditor:d,candidatePools:c,showSourceBadge:p=!1,canDrag:f,canDropAt:b,isLocked:g,hideAddPicker:m=!1,testIdPrefix:h="g7le-action-list"}){const v=u.useMemo(()=>ko(o),[o]),y=u.useMemo(()=>{if(r==null||r==="init")return c;const B=Vl(r,n,s??null);if(B.length===0)return c;const M=c??{},O=new Set((M.bindingCandidates??[]).map(H=>H.expression)),z=B.filter(H=>!O.has(H.expression));return z.length===0?c:{...M,bindingCandidates:[...z,...M.bindingCandidates??[]]}},[r,s,c,n]),[_,w]=u.useState(null),[k,x]=u.useState(null),$=u.useCallback((B,M)=>{if(B===M||B<0||M<0||B>=e.length||M>=e.length)return;const O=e.slice(),[z]=O.splice(B,1);O.splice(M,0,z),t(O)},[e,t]),S=ys({length:e.length,onMove:$,canDrag:f,canDropAt:b}),C=u.useCallback(B=>{t(e.filter((M,O)=>O!==B))},[e,t]),T=u.useCallback((B,M)=>{const O=e.slice();O[B]=M,t(O)},[e,t]),E=u.useCallback(B=>{t([...e,B])},[e,t]),A=u.useCallback((B,M)=>{const O=Yn(B,v),z=O.kind==="preset"?v.find(P=>P.id===O.recipeId)??null:null;if(!z)return null;const H=O.kind==="preset"?O.values:{};return i.jsxs("div",{style:Ck,children:[i.jsx(Ns,{raw:B,recipe:z,values:H,t:n,pools:y??{},onChange:M,testIdPrefix:`${h}-edit`,renderActionList:(P,I,L,F)=>{const Y=F==="onSuccess"?"response":F==="onError"?"error":void 0;return i.jsx(Gt,{actions:P,onChange:I,t:n,recipes:o,chipContext:Y,actionChipCandidates:Y?s:void 0,candidatePools:Y?c:y,addContext:a,showSourceBadge:p,testIdPrefix:L})}}),i.jsx(gk,{raw:B,t:n,onChange:M,testIdPrefix:`${h}-edit`,candidates:y?.bindingCandidates})]})},[v,n,y,c,s,h,o,a,p]),N=d??(o?A:void 0),D=m?null:l?l(E):o?i.jsx(m0,{recipes:o,t:n,onAdd:E,context:a,testIdPrefix:`${h}-add-picker`}):null;return i.jsxs("div",{className:h,"data-testid":h,children:[i.jsxs("ul",{style:{listStyle:"none",margin:0,padding:0,display:"flex",flexDirection:"column",gap:6},children:[e.map((B,M)=>{const O=Yn(B,v),z=O.kind==="preset"?O.recipeId:null,H=z?v.find(Y=>Y.id===z)??null:null,P=H===null,I=g?.(M)??!1,L=cs(B)!==null,F=N?N(B,Y=>T(M,Y)):null;return i.jsxs(u.Fragment,{children:[I?null:i.jsx("li",{"aria-hidden":"true",style:{listStyle:"none",margin:0,padding:0},children:i.jsx(Rn,{active:S.isDropTarget(M),testid:`${h}-dropline-${M}`})}),i.jsxs("li",{"data-testid":`${h}-item-${M}`,"data-drag-source":S.dragIndex===M?"true":void 0,style:{border:"1px solid #e2e8f0",borderRadius:8,padding:"8px 10px",...S.dragIndex===M?{opacity:.5}:{}},onDragOver:S.dragging&&!I?Y=>{Y.preventDefault();const K=Y.currentTarget.getBoundingClientRect();S.onDragOverItem(M,Y.clientY{Y.preventDefault(),S.onDrop()}:void 0,children:[i.jsxs("div",{style:{display:"flex",alignItems:"center",gap:8},children:[I?i.jsx("span",{style:{fontSize:12},children:"🔒"}):i.jsx("span",{"data-testid":`${h}-drag-${M}`,style:{color:"#94a3b8",cursor:"grab",fontSize:14,userSelect:"none"},draggable:!0,onDragStart:()=>S.onDragStart(M),onDragEnd:S.onDragEnd,"aria-label":n("layout_editor.action_list.drag_reorder"),role:"button",children:"⠿"}),i.jsx("span",{style:{flex:1,minWidth:0,fontSize:13},"data-testid":`${h}-summary-${M}`,children:xk(B,v,n)}),p?i.jsxs("span",{"data-testid":`${h}-source-${M}`,style:wk,children:["〔",_k(o,z,n),"〕"]}):null,L?i.jsxs("span",{"data-testid":`${h}-cond-badge-${M}`,style:kk,children:["〔",n("layout_editor.init_actions.cond_badge"),"〕"]}):null,P?i.jsxs("span",{"data-testid":`${h}-advanced-${M}`,style:Sk,children:["[",n("layout_editor.action.advanced"),"]"]}):null,!I&&F?i.jsxs("button",{type:"button","data-testid":`${h}-edit-${M}`,onClick:()=>x(k===M?null:M),"aria-label":n("layout_editor.init_actions.edit"),title:n("layout_editor.init_actions.edit"),style:k===M?$k:nu,children:["✎ ",n("layout_editor.init_actions.edit")]}):null,i.jsx("button",{type:"button","data-testid":`${h}-code-${M}`,onClick:()=>w(_===M?null:M),"aria-label":n("layout_editor.action_list.view_code"),title:n("layout_editor.action_list.view_code"),style:_===M?Ek:Ms,children:""}),I?null:i.jsx("button",{type:"button","data-testid":`${h}-remove-${M}`,onClick:()=>C(M),"aria-label":n("layout_editor.action_list.remove"),title:n("layout_editor.action_list.remove"),style:Ik,children:"✕"})]}),H?i.jsx(T0,{action:B,recipe:H,values:O.kind==="preset"?O.values:{},t:n,showCodeButton:!1,testIdPrefix:`${h}-preview-${M}`}):null,k===M&&!I&&F?i.jsx("div",{"data-testid":`${h}-edit-body-${M}`,children:F}):null,d&&k!==M?d(B,Y=>T(M,Y)):null,_===M?i.jsx("pre",{"data-testid":`${h}-code-view-${M}`,style:{margin:"8px 0 0",padding:8,background:"#0f172a",color:"#e2e8f0",borderRadius:6,fontSize:12,overflow:"auto"},children:JSON.stringify(B,null,2)}):null]})]},M)}),i.jsx("li",{"aria-hidden":"true",style:{listStyle:"none",margin:0,padding:0},children:i.jsx(Rn,{active:S.isDropTarget(e.length),testid:`${h}-dropline-end`})})]}),D?i.jsx("div",{style:{marginTop:8},children:D}):null]})}const wk={fontSize:10,color:"#64748b",whiteSpace:"nowrap"},kk={fontSize:10,color:"#7c3aed",whiteSpace:"nowrap"},Sk={fontSize:10,color:"#92400e",whiteSpace:"nowrap"},Ck={display:"flex",flexDirection:"column",gap:6,paddingTop:6,borderTop:"1px dashed #e2e8f0",marginTop:6},nu={flex:"0 0 auto",padding:"3px 8px",fontSize:11,fontWeight:500,border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",color:"#475569",cursor:"pointer",whiteSpace:"nowrap",lineHeight:1.4},$k={...nu,background:"#eff6ff",borderColor:"#bfdbfe",color:"#2563eb"},Ms={flex:"0 0 auto",width:26,height:24,display:"inline-flex",alignItems:"center",justifyContent:"center",fontSize:11,border:"1px solid #e2e8f0",borderRadius:6,background:"#fff",color:"#64748b",cursor:"pointer",fontFamily:"ui-monospace, monospace"},Ek={...Ms,background:"#0f172a",borderColor:"#0f172a",color:"#e2e8f0"},Ik={...Ms,color:"#dc2626",borderColor:"#fecaca",fontFamily:"inherit"};function ou(e){const t=e.__source;return t&&typeof t=="object"?t:null}function Ak({actions:e,onChange:t,recipes:n,t:o,bindingCandidates:r,pageCandidates:s,dataSourceCandidates:a,stateKeyCandidates:l,modalCandidates:d}){const c=u.useMemo(()=>({pageCandidates:s,dataSourceCandidates:a,stateKeyCandidates:l,modalCandidates:d,bindingCandidates:r}),[s,a,l,d,r]),p=u.useMemo(()=>e.filter(h=>ou(h)?.kind==="base"),[e]),f=u.useMemo(()=>e.filter(h=>ou(h)?.kind!=="base"),[e]),b=p.length>0,g=u.useCallback(h=>{t(b?[...p,...h]:h)},[t,b,p]),m=u.useCallback(()=>{},[]);return i.jsxs("div",{className:"g7le-init-actions-form","data-testid":"g7le-init-actions-form",style:Tk,children:[i.jsx("p",{style:Dk,children:o("layout_editor.page_settings.init_actions.heading")}),b?i.jsxs(i.Fragment,{children:[i.jsxs("div",{"data-testid":"g7le-init-action-group-base",style:ru,children:[i.jsx("div",{style:iu,children:o("layout_editor.init_actions.group_base")}),i.jsx(Gt,{actions:p,onChange:m,t:o,recipes:n,candidatePools:c,addContext:"init",showSourceBadge:!0,isLocked:()=>!0,hideAddPicker:!0,testIdPrefix:"g7le-init-action-base"}),i.jsxs("p",{"data-testid":"g7le-init-action-base-hint",style:zk,children:["ⓘ ",o("layout_editor.init_actions.base_readonly_hint")]})]}),i.jsxs("div",{"data-testid":"g7le-init-action-group-self",style:ru,children:[i.jsx("div",{style:iu,children:o("layout_editor.init_actions.group_self")}),f.length===0?i.jsx("p",{style:su,children:o("layout_editor.init_actions.empty_self")}):null,i.jsx(Gt,{actions:f,onChange:g,t:o,recipes:n,candidatePools:c,addContext:"init",showSourceBadge:!0,testIdPrefix:"g7le-init-action-self"})]}),i.jsxs("p",{"data-testid":"g7le-init-action-merge-order",style:Nk,children:["ⓘ ",o("layout_editor.init_actions.merge_order")]})]}):i.jsxs(i.Fragment,{children:[e.length===0?i.jsx("p",{"data-testid":"g7le-init-action-empty",style:su,children:o("layout_editor.init_actions.empty")}):null,i.jsx(Gt,{actions:e,onChange:t,t:o,recipes:n,candidatePools:c,addContext:"init",showSourceBadge:!0,testIdPrefix:"g7le-init-action"})]})]})}const Tk={display:"flex",flexDirection:"column",gap:12,minWidth:0},Dk={margin:0,fontSize:13,fontWeight:700,color:"#0f172a"},ru={border:"1px solid #e2e8f0",borderRadius:10,padding:10,display:"flex",flexDirection:"column",gap:8},iu={fontSize:12,fontWeight:700,color:"#475569"},zk={margin:0,fontSize:11,color:"#64748b"},su={margin:0,fontSize:12,color:"#94a3b8"},Nk={margin:0,fontSize:11,color:"#64748b"};function Mk(e){return e==="spinner"?new Set(["spinner","page"]):new Set(["skeleton"])}function au(e,t,n){return typeof e!="string"?n:e.startsWith("$t:")?t(e.slice(3)):e}function lu({candidates:e,value:t,onSelect:n,styleKind:o,t:r,testIdPrefix:s="g7le-loading-component-picker"}){const a=bo(),l=()=>{const d=a.open({ariaLabel:r("layout_editor.overlay.loading_picker.select"),width:520,maxHeightRatio:.7,content:i.jsx(Lk,{candidates:e,value:t,styleKind:o,t:r,testIdPrefix:s,onPick:c=>{n(c),a.close(d)},onClose:()=>a.close(d)})})};return i.jsx("div",{className:s,"data-testid":s,style:{minWidth:0},children:i.jsxs("div",{style:Pk,children:[i.jsx("span",{"data-testid":`${s}-current`,style:Ok,children:t&&t.length>0?t:r("layout_editor.overlay.loading_picker.none")}),i.jsxs("button",{type:"button","data-testid":`${s}-open`,onClick:l,style:Bk,children:["🔍 ",r("layout_editor.overlay.loading_picker.select")]})]})})}function Lk({candidates:e,value:t,styleKind:n,t:o,testIdPrefix:r,onPick:s,onClose:a}){const[l,d]=u.useState(""),c=Mk(n),p=u.useMemo(()=>{const f=l.trim().toLowerCase();return e.filter(b=>c.has(b.role)).filter(b=>f?au(b.label,o,b.name).toLowerCase().includes(f)||b.name.toLowerCase().includes(f):!0)},[e,n,l,o]);return i.jsxs("div",{"data-testid":`${r}-modal`,style:Wk,children:[i.jsxs("div",{style:jk,children:[i.jsxs("span",{style:Hk,children:["🔍 ",o("layout_editor.overlay.loading_picker.select")]}),i.jsx("button",{type:"button","data-testid":`${r}-close`,onClick:a,"aria-label":o("layout_editor.overlay.loading_picker.select"),style:Fk,children:"✕"})]}),i.jsx("input",{type:"text","data-testid":`${r}-search`,value:l,placeholder:o("layout_editor.overlay.loading_picker.search"),onChange:f=>d(f.target.value),style:Kk,autoFocus:!0}),p.length===0?i.jsx("p",{"data-testid":`${r}-empty`,style:Jk,children:o("layout_editor.overlay.loading_picker.empty")}):i.jsx("ul",{style:Vk,children:p.map(f=>i.jsx("li",{children:i.jsxs("button",{type:"button","data-testid":`${r}-item-${f.name}`,onClick:()=>s(f.name),style:{...Uk,...t===f.name?qk:{}},children:[i.jsx("span",{style:Gk,children:au(f.label,o,f.name)}),i.jsx("code",{style:Yk,children:f.name}),i.jsx("span",{style:Xk,children:f.role})]})},f.name))})]})}const Pk={display:"flex",alignItems:"center",gap:6,minWidth:0},Ok={flex:1,minWidth:0,fontSize:12,color:"#0f172a",fontFamily:"monospace"},Bk={padding:"4px 10px",fontSize:11,border:"1px solid #cbd5e1",borderRadius:6,background:"#f8fafc",color:"#475569",cursor:"pointer",whiteSpace:"nowrap"},Wk={display:"flex",flexDirection:"column",minHeight:0,padding:16,boxSizing:"border-box",fontFamily:"system-ui, -apple-system, sans-serif"},jk={display:"flex",alignItems:"center",gap:8,paddingBottom:8,borderBottom:"1px solid #e2e8f0",marginBottom:10},Hk={flex:1,fontSize:14,fontWeight:700,color:"#0f172a",minWidth:0},Fk={border:"none",background:"transparent",color:"#64748b",cursor:"pointer",fontSize:15,lineHeight:1,padding:4},Kk={width:"100%",padding:"6px 8px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,marginBottom:8,boxSizing:"border-box"},Vk={listStyle:"none",margin:0,padding:0,display:"flex",flexDirection:"column",gap:2,overflowY:"auto",minHeight:0},Uk={display:"flex",alignItems:"center",gap:6,width:"100%",textAlign:"left",padding:"8px 10px",fontSize:12,border:"1px solid transparent",background:"transparent",cursor:"pointer",borderRadius:6},qk={border:"1px solid #3b82f6",background:"#eff6ff"},Gk={flex:1,minWidth:0,color:"#0f172a"},Yk={fontSize:10,color:"#94a3b8",fontFamily:"monospace"},Xk={fontSize:10,color:"#64748b",background:"#f1f5f9",borderRadius:4,padding:"0 4px"},Jk={margin:0,fontSize:12,color:"#94a3b8",padding:"8px 4px"};function du(e){return e===!0?{enabled:!0}:e===!1||e===void 0?{}:e}const Zk=["opaque","blur","fade","skeleton","spinner"];function Qk({value:e,patch:t,t:n,baseValue:o,ownValue:r,loadingComponents:s=[],progressiveDataSources:a=[],bindingCandidates:l}){const d=du(e),c=d.enabled??!1,p=!!o&&Object.keys(o).length>0,f=Tn("component-target-picker"),[b,g]=u.useState((d.wait_for??[]).length>0),m=u.useCallback(k=>{const x={...du(e)};k(x),t(x)},[e,t]),h=r&&typeof r=="object"?r:e&&typeof e=="object"?e:{},v=u.useCallback(k=>p&&o!==void 0&&k in o&&!(k in h),[p,o,h]),y=u.useCallback(k=>{o&&m(x=>{x[k]=o[k]})},[o,m]),_=u.useCallback(k=>m(x=>{x.enabled=k}),[m]),w=d.target!==void 0?"region":"full";return i.jsxs("div",{className:"g7le-overlay-form","data-testid":"g7le-overlay-form",style:Rk,children:[p?i.jsxs("p",{"data-testid":"g7le-overlay-inherit-banner",style:e1,children:["ⓘ ",n("layout_editor.overlay.inherit_banner")]}):null,i.jsxs("div",{style:Hr,children:[i.jsx(Ls,{label:n("layout_editor.overlay.enabled_label"),inherited:v("enabled"),hasInheritance:p,onOverride:()=>y("enabled"),testidKey:"enabled",t:n}),i.jsx(Lr,{disabled:v("enabled"),children:i.jsx(Mr,{checked:c,onChange:_,testid:"g7le-overlay-enabled",ariaLabel:n("layout_editor.overlay.enabled_label"),label:n(c?"layout_editor.overlay.on":"layout_editor.overlay.off")})}),i.jsxs("p",{style:Fr,children:["ⓘ ",n("layout_editor.overlay.enabled_hint")]})]}),i.jsx(Lr,{disabled:!c,testid:"g7le-overlay-body",style:{display:"flex",flexDirection:"column",gap:16,minWidth:0},children:i.jsxs(i.Fragment,{children:[i.jsxs("div",{style:Hr,children:[i.jsx(Ls,{label:n("layout_editor.overlay.scope_label"),inherited:v("target"),hasInheritance:p,onOverride:()=>y("target"),testidKey:"target",t:n}),i.jsxs("div",{"data-testid":"g7le-overlay-scope",style:Ps,children:[i.jsx(jr,{active:w==="full",onClick:()=>m(k=>{delete k.target,delete k.fallback_target}),testid:"g7le-overlay-scope-full",children:n("layout_editor.overlay.scope_full")}),i.jsx(jr,{active:w==="region",onClick:()=>m(k=>{k.target=k.target??""}),testid:"g7le-overlay-scope-region",children:n("layout_editor.overlay.scope_region")})]}),i.jsx(Lr,{disabled:w!=="region",testid:"g7le-overlay-region-box",style:r1,children:i.jsxs(i.Fragment,{children:[i.jsx(Nn,{label:n("layout_editor.overlay.target_label"),children:i.jsx("div",{"data-testid":"g7le-overlay-target-picker",children:f?i.jsx(f,{control:{label:n("layout_editor.overlay.target_label")},value:d.target??"",onChange:k=>m(x=>{x.target=typeof k=="string"?k:""}),t:n}):i.jsx("input",{type:"text","data-testid":"g7le-overlay-target-input",value:d.target??"",onChange:k=>m(x=>{x.target=k.target.value}),style:a1})})}),i.jsx(Nn,{label:n("layout_editor.overlay.fallback_label"),children:i.jsx("div",{"data-testid":"g7le-overlay-fallback-picker",children:f?i.jsx(f,{control:{label:n("layout_editor.overlay.fallback_label")},value:d.fallback_target??"",onChange:k=>m(x=>{const $=typeof k=="string"?k:"";$===""?delete x.fallback_target:x.fallback_target=$}),t:n}):null})}),i.jsxs("p",{style:Fr,children:["ⓘ ",n("layout_editor.overlay.fallback_hint")]})]})})]}),i.jsxs("div",{style:Hr,children:[i.jsx(Ls,{label:n("layout_editor.overlay.style_label"),inherited:v("style"),hasInheritance:p,onOverride:()=>y("style"),testidKey:"style",t:n}),i.jsx("div",{"data-testid":"g7le-overlay-style",style:Ps,children:Zk.map(k=>i.jsx(jr,{active:(d.style??"opaque")===k,onClick:()=>m(x=>{x.style=k}),testid:`g7le-overlay-style-${k}`,children:n(`layout_editor.overlay.style_${k}`)},k))}),d.style==="skeleton"?i.jsxs("div",{style:cu,children:[i.jsx(Nn,{label:n("layout_editor.overlay.skeleton_component"),children:i.jsx(lu,{candidates:s,value:d.skeleton?.component,styleKind:"skeleton",onSelect:k=>m(x=>{x.skeleton={...x.skeleton,component:k}}),t:n,testIdPrefix:"g7le-overlay-skeleton-component"})}),i.jsx(Nn,{label:n("layout_editor.overlay.skeleton_animation"),children:i.jsx("div",{"data-testid":"g7le-overlay-skeleton-anim",style:Ps,children:["wave","pulse","none"].map(k=>i.jsx(jr,{active:(d.skeleton?.animation??"wave")===k,onClick:()=>m(x=>{x.skeleton={...x.skeleton,animation:k}}),testid:`g7le-overlay-skeleton-anim-${k}`,children:n(`layout_editor.overlay.anim_${k}`)},k))})}),i.jsx(Nn,{label:n("layout_editor.overlay.skeleton_count"),children:i.jsx("input",{type:"number","data-testid":"g7le-overlay-skeleton-count",value:d.skeleton?.iteration_count??"",onChange:k=>m(x=>{const $=parseInt(k.target.value,10);x.skeleton={...x.skeleton,iteration_count:Number.isNaN($)?void 0:$}}),style:l1})})]}):null,d.style==="spinner"?i.jsxs("div",{style:cu,children:[i.jsx(Nn,{label:n("layout_editor.overlay.spinner_component"),children:i.jsx(lu,{candidates:s,value:d.spinner?.component,styleKind:"spinner",onSelect:k=>m(x=>{x.spinner={...x.spinner,component:k}}),t:n,testIdPrefix:"g7le-overlay-spinner-component"})}),i.jsx(Nn,{label:n("layout_editor.overlay.spinner_text"),children:i.jsx("div",{"data-testid":"g7le-overlay-spinner-text",style:{minWidth:0},children:i.jsx(_t,{value:d.spinner?.text??"",onChange:k=>m(x=>{x.spinner={...x.spinner,text:k===void 0||k===""?void 0:k}}),t:n,candidates:l,enableExpressionTree:!0,expressionTreeCollapsible:!0,testidPrefix:"g7le-overlay-spinner-text-field"})})})]}):null]}),i.jsxs("div",{style:Hr,children:[i.jsxs("button",{type:"button","data-testid":"g7le-overlay-waitfor-toggle","aria-expanded":b,onClick:()=>g(k=>!k),style:u1,children:[b?"▾":"▸"," ",n("layout_editor.overlay.waitfor_label")]}),b?i.jsxs("div",{"data-testid":"g7le-overlay-waitfor-body",style:p1,children:[i.jsxs("p",{"data-testid":"g7le-overlay-waitfor-hint",style:Fr,children:["ⓘ ",n("layout_editor.overlay.waitfor_hint")]}),a.length===0?i.jsx("p",{"data-testid":"g7le-overlay-waitfor-empty",style:Fr,children:n("layout_editor.overlay.waitfor_empty")}):i.jsx("div",{"data-testid":"g7le-overlay-waitfor",style:{display:"flex",flexWrap:"wrap",gap:6},children:a.map(k=>{const x=(d.wait_for??[]).includes(k.id);return i.jsxs("label",{"data-testid":`g7le-overlay-waitfor-${k.id}`,style:d1(x),children:[i.jsx("input",{type:"checkbox",checked:x,onChange:()=>m($=>{const S=new Set($.wait_for??[]);S.has(k.id)?S.delete(k.id):S.add(k.id);const C=Array.from(S);C.length===0?delete $.wait_for:$.wait_for=C})}),i.jsx(Lc,{option:k,testIdPrefix:`g7le-overlay-waitfor-${k.id}`})]},k.id)})})]}):null]}),p?i.jsxs("button",{type:"button","data-testid":"g7le-overlay-reset-inherit",onClick:()=>t(void 0),style:c1,children:["⟲ ",n("layout_editor.overlay.reset_inherit")]}):null]})})]})}function jr({active:e,onClick:t,children:n,testid:o}){return i.jsx("button",{type:"button","data-testid":o,"aria-pressed":e,onClick:t,style:{...n1,...e?o1:{}},children:n})}function Ls({label:e,inherited:t,hasInheritance:n,onOverride:o,testidKey:r,t:s}){return i.jsxs("div",{style:f1,children:[i.jsx("label",{style:t1,children:e}),n?t?i.jsxs(i.Fragment,{children:[i.jsxs("span",{"data-testid":`g7le-overlay-inherited-${r}`,style:g1,children:["〔",s("layout_editor.overlay.inherited_badge"),"〕"]}),i.jsx("button",{type:"button","data-testid":`g7le-overlay-override-${r}`,onClick:o,style:y1,children:s("layout_editor.overlay.override_field")})]}):i.jsxs("span",{"data-testid":`g7le-overlay-overridden-${r}`,style:h1,children:["〔",s("layout_editor.overlay.overridden_badge"),"〕"]}):null]})}function Nn({label:e,children:t}){return i.jsxs("div",{style:i1,children:[i.jsx("span",{style:s1,children:e}),i.jsx("div",{style:{flex:1,minWidth:0},children:t})]})}const Rk={display:"flex",flexDirection:"column",gap:16,minWidth:0},e1={margin:0,fontSize:12,color:"#1d4ed8",background:"#eff6ff",padding:"8px 10px",borderRadius:6},Hr={display:"flex",flexDirection:"column",gap:6,minWidth:0},t1={fontSize:13,fontWeight:600,color:"#0f172a"},Fr={margin:0,fontSize:11,color:"#94a3b8"},Ps={display:"flex",gap:4,flexWrap:"wrap"},n1={padding:"4px 10px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",color:"#475569",cursor:"pointer"},o1={border:"1px solid #3b82f6",background:"#eff6ff",color:"#1d4ed8"},r1={display:"flex",flexDirection:"column",gap:8,padding:8,border:"1px dashed #e2e8f0",borderRadius:8},cu={display:"flex",flexDirection:"column",gap:8,padding:8,border:"1px dashed #e2e8f0",borderRadius:8},i1={display:"flex",alignItems:"center",gap:8,minWidth:0},s1={fontSize:11,color:"#64748b",minWidth:90},a1={width:"100%",padding:"5px 8px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,boxSizing:"border-box"},l1={width:80,padding:"5px 8px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6},d1=e=>({display:"inline-flex",alignItems:"center",gap:4,padding:"4px 8px",fontSize:12,border:`1px solid ${e?"#3b82f6":"#cbd5e1"}`,borderRadius:12,background:e?"#eff6ff":"#fff",cursor:"pointer"}),c1={alignSelf:"flex-start",padding:"6px 12px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",color:"#475569",cursor:"pointer"},u1={alignSelf:"flex-start",padding:"4px 0",fontSize:13,fontWeight:600,color:"#0f172a",background:"transparent",border:"none",cursor:"pointer"},p1={display:"flex",flexDirection:"column",gap:6,paddingLeft:14,minWidth:0},f1={display:"flex",alignItems:"center",gap:8,flexWrap:"wrap",minWidth:0},g1={fontSize:11,color:"#64748b",background:"#f1f5f9",borderRadius:4,padding:"1px 6px"},h1={fontSize:11,color:"#0369a1",background:"#e0f2fe",borderRadius:4,padding:"1px 6px"},y1={padding:"2px 8px",fontSize:11,border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",color:"#475569",cursor:"pointer"},b1="g7le:computed-effect-preview",m1={number:"layout_editor.computed.type_number",string:"layout_editor.computed.type_string",boolean:"layout_editor.computed.type_boolean",list:"layout_editor.computed.type_list",object:"layout_editor.computed.type_object",null:"layout_editor.computed.type_null"};function v1(e,t,n){if(t==="list"){const o=e,r=o.slice(0,2).map(s=>JSON.stringify(s)).join(", ");return o.length<=2?`[${r}]`:`[${r}, ${n("layout_editor.computed.list_more",{count:o.length})}]`}return t==="object"?JSON.stringify(e):t==="boolean"?e?"true":"false":t==="null"?n("layout_editor.computed.value_none"):String(e)}function uu({expr:e,computedKey:t,sampleContext:n,t:o,isEffect:r=!1,testIdPrefix:s="g7le-computed-preview"}){const a=zd(e,n),l=()=>{typeof window>"u"||!t||window.dispatchEvent(new CustomEvent(b1,{detail:{key:t}}))};return a.ok?i.jsxs("div",{className:s,"data-testid":s,style:_1,children:[i.jsx("span",{style:x1,children:o("layout_editor.computed.preview_label")}),i.jsx("span",{"data-testid":`${s}-value`,style:w1,children:v1(a.value,a.type,o)}),i.jsxs("span",{"data-testid":`${s}-type`,style:k1,children:["(",o(m1[a.type]),")"]}),r&&a.type==="boolean"?i.jsxs("button",{type:"button","data-testid":`${s}-effect`,onClick:l,style:S1,children:["▷ ",o("layout_editor.computed.effect_preview")]}):null]}):i.jsxs("div",{"data-testid":`${s}-error`,style:C1,children:["⚠ ",o("layout_editor.computed.preview_error")]})}const _1={display:"flex",alignItems:"center",flexWrap:"wrap",gap:6,minWidth:0},x1={fontSize:11,color:"#94a3b8"},w1={fontSize:12,color:"#0f172a",fontFamily:"monospace",wordBreak:"break-all"},k1={fontSize:10,color:"#64748b"},S1={padding:"2px 8px",fontSize:11,border:"1px solid #cbd5e1",borderRadius:6,background:"#f8fafc",color:"#475569",cursor:"pointer"},C1={fontSize:12,color:"#b45309"},$1=[{op:"count",labelKey:"layout_editor.computed.op_count"},{op:"sum",labelKey:"layout_editor.computed.op_sum"},{op:"filter",labelKey:"layout_editor.computed.op_filter"},{op:"toOptions",labelKey:"layout_editor.computed.op_to_options"},{op:"nth",labelKey:"layout_editor.computed.op_nth"},{op:"firstOf",labelKey:"layout_editor.computed.op_first_of"},{op:"literal",labelKey:"layout_editor.computed.op_literal"}],E1=["=","!=",">","<",">=","<=","includes"],I1=new Set(["count","sum","filter"]);function pu({model:e,onChange:t,t:n,dataSourceCandidates:o,bindingCandidates:r,existingKeys:s=[]}){const a=u.useCallback(g=>{const m={...e};g(m),t(m)},[e,t]),l=e.conditions??[],d=Tn("datasource-picker"),c=e.key!==""&&s.includes(e.key),p=()=>a(g=>{g.conditions=[...g.conditions??[],{field:"",cmp:"=",value:""}]}),f=(g,m)=>a(h=>{const v=(h.conditions??[]).slice(),y={...v[g]};m(y),v[g]=y,h.conditions=v}),b=g=>a(m=>{m.conditions=(m.conditions??[]).filter((h,v)=>v!==g)});return i.jsxs("div",{className:"g7le-computed-custom","data-testid":"g7le-computed-custom",style:A1,children:[i.jsx(Os,{label:`① ${n("layout_editor.computed.custom_step1")}`,children:i.jsx("div",{"data-testid":"g7le-computed-custom-source",children:d?i.jsx(d,{control:{},value:e.source,onChange:g=>a(m=>{m.source=typeof g=="string"?g:void 0}),t:n,candidates:o}):i.jsx("input",{type:"text",value:e.source??"",onChange:g=>a(m=>{m.source=g.target.value}),style:ro})})}),i.jsxs(Os,{label:`② ${n("layout_editor.computed.custom_step2")}`,children:[i.jsx("select",{"data-testid":"g7le-computed-custom-op",value:e.op,onChange:g=>a(m=>{m.op=g.target.value}),style:ro,children:$1.map(({op:g,labelKey:m})=>i.jsx("option",{value:g,children:n(m)},g))}),e.op==="sum"?i.jsx("div",{"data-testid":"g7le-computed-custom-sumfield",style:{minWidth:0},children:i.jsx(Ke,{value:e.sumField??"",onChange:g=>a(m=>{m.sumField=g}),t:n,candidates:r,placeholder:n("layout_editor.computed.custom_sum_field"),testidPrefix:"g7le-computed-custom-sumfield-chip"})}):null,e.op==="toOptions"?i.jsxs("div",{style:{display:"flex",gap:6},children:[i.jsx("div",{"data-testid":"g7le-computed-custom-valuefield",style:{flex:1,minWidth:0},children:i.jsx(Ke,{value:e.valueField??"",onChange:g=>a(m=>{m.valueField=g}),t:n,candidates:r,placeholder:n("layout_editor.computed.custom_value_field"),testidPrefix:"g7le-computed-custom-valuefield-chip"})}),i.jsx("div",{"data-testid":"g7le-computed-custom-labelfield",style:{flex:1,minWidth:0},children:i.jsx(Ke,{value:e.labelField??"",onChange:g=>a(m=>{m.labelField=g}),t:n,candidates:r,placeholder:n("layout_editor.computed.custom_label_field"),testidPrefix:"g7le-computed-custom-labelfield-chip"})})]}):null,e.op==="nth"?i.jsxs("div",{style:{display:"flex",gap:6},children:[i.jsx("input",{type:"text","data-testid":"g7le-computed-custom-index",value:e.index??"",placeholder:"0",onChange:g=>a(m=>{m.index=g.target.value}),style:ro}),i.jsx("div",{"data-testid":"g7le-computed-custom-prop",style:{flex:1,minWidth:0},children:i.jsx(Ke,{value:e.prop??"",onChange:g=>a(m=>{m.prop=g}),t:n,candidates:r,placeholder:n("layout_editor.computed.custom_prop"),testidPrefix:"g7le-computed-custom-prop-chip"})})]}):null,e.op==="literal"?i.jsxs("div",{style:{display:"flex",gap:6},children:[i.jsxs("select",{"data-testid":"g7le-computed-custom-literalkind",value:e.literalKind??"string",onChange:g=>a(m=>{m.literalKind=g.target.value}),style:ro,children:[i.jsx("option",{value:"string",children:n("layout_editor.computed.kind_string")}),i.jsx("option",{value:"number",children:n("layout_editor.computed.kind_number")}),i.jsx("option",{value:"boolean",children:n("layout_editor.computed.kind_boolean")})]}),i.jsx("div",{"data-testid":"g7le-computed-custom-literalvalue",style:{flex:1,minWidth:0},children:i.jsx(Ke,{value:e.literalValue??"",onChange:g=>a(m=>{m.literalValue=g}),t:n,candidates:r,testidPrefix:"g7le-computed-custom-literalvalue-chip"})})]}):null,e.op==="firstOf"?i.jsxs("div",{style:{display:"flex",flexDirection:"column",gap:6},children:[i.jsx("input",{type:"text","data-testid":"g7le-computed-custom-candidates",value:(e.candidates??[]).join(", "),placeholder:n("layout_editor.computed.custom_candidates"),onChange:g=>a(m=>{m.candidates=g.target.value.split(",").map(h=>h.trim()).filter(Boolean)}),style:ro}),i.jsx("div",{"data-testid":"g7le-computed-custom-fallback",style:{minWidth:0},children:i.jsx(Ke,{value:e.fallback??"",onChange:g=>a(m=>{m.fallback=g}),t:n,candidates:r,placeholder:n("layout_editor.computed.custom_fallback"),testidPrefix:"g7le-computed-custom-fallback-chip"})})]}):null,I1.has(e.op)?i.jsxs("div",{style:N1,children:[l.map((g,m)=>i.jsxs("div",{"data-testid":`g7le-computed-custom-cond-${m}`,style:M1,children:[i.jsx("div",{"data-testid":`g7le-computed-custom-cond-field-${m}`,style:{flex:1,minWidth:0},children:i.jsx(Ke,{value:g.field,onChange:h=>f(m,v=>{v.field=h}),t:n,candidates:r,placeholder:n("layout_editor.computed.cond_field"),testidPrefix:`g7le-computed-custom-cond-field-${m}-chip`})}),i.jsx("select",{value:g.cmp,onChange:h=>f(m,v=>{v.cmp=h.target.value}),style:L1,children:E1.map(h=>i.jsx("option",{value:h,children:h},h))}),i.jsx("div",{"data-testid":`g7le-computed-custom-cond-value-${m}`,style:{flex:1,minWidth:0},children:i.jsx(Ke,{value:g.value,onChange:h=>f(m,v=>{v.value=h}),t:n,candidates:r,placeholder:n("layout_editor.computed.cond_value"),testidPrefix:`g7le-computed-custom-cond-value-${m}-chip`})}),i.jsx("button",{type:"button","data-testid":`g7le-computed-custom-cond-remove-${m}`,onClick:()=>b(m),style:P1,children:"✕"})]},m)),i.jsxs("button",{type:"button","data-testid":"g7le-computed-custom-cond-add",onClick:p,style:O1,children:["+ ",n("layout_editor.computed.cond_add")]})]}):null]}),i.jsxs(Os,{label:`③ ${n("layout_editor.computed.custom_step3")}`,children:[i.jsx("input",{type:"text","data-testid":"g7le-computed-custom-key",value:e.key,onChange:g=>a(m=>{m.key=g.target.value}),style:{...ro,...c?z1:{}}}),c?i.jsxs("p",{"data-testid":"g7le-computed-custom-key-dup",style:B1,children:["⚠ ",n("layout_editor.computed.key_duplicate")]}):null]})]})}function Os({label:e,children:t}){return i.jsxs("div",{style:T1,children:[i.jsx("div",{style:D1,children:e}),i.jsx("div",{style:{display:"flex",flexDirection:"column",gap:6,minWidth:0},children:t})]})}function Bs(e){return Zm(e)}const A1={display:"flex",flexDirection:"column",gap:12,padding:10,border:"1px solid #cbd5e1",borderRadius:8,background:"#fff"},T1={display:"flex",flexDirection:"column",gap:4,minWidth:0},D1={fontSize:12,fontWeight:600,color:"#475569"},ro={padding:"5px 8px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,minWidth:0,width:"100%",boxSizing:"border-box"},z1={border:"1px solid #ef4444"},N1={display:"flex",flexDirection:"column",gap:6,marginTop:4},M1={display:"flex",gap:4,alignItems:"center",minWidth:0},L1={padding:"4px 6px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6},P1={border:"none",background:"transparent",color:"#94a3b8",cursor:"pointer",fontSize:12},O1={alignSelf:"flex-start",padding:"2px 8px",fontSize:11,border:"1px dashed #94a3b8",borderRadius:6,background:"#fff",color:"#475569",cursor:"pointer"},B1={margin:0,fontSize:11,color:"#b45309"};function Ws(e,t,n){return typeof e!="string"?n:e.startsWith("$t:")?t(e.slice(3)):e}function W1(e){if(e&&typeof e=="object"){const t=e.__source;if(t&&typeof t=="object")return t}return null}function j1({computed:e,onChange:t,recipes:n,t:o,sampleContext:r,dataSourceCandidates:s,bindingCandidates:a,computedSource:l={},effectKeys:d=[],onRevert:c}){const p=u.useMemo(()=>Cd(n),[n]),[f,b]=u.useState(!1),[g,m]=u.useState(null),h=Object.keys(e),v=u.useCallback((x,$)=>{t({...e,[x]:$})},[e,t]),y=u.useCallback(x=>{const $={...e};delete $[x],t($)},[e,t]),_=u.useCallback((x,$,S)=>{const C={...e};x&&x!==$&&delete C[x],C[$]=S,t(C)},[e,t]),w=u.useCallback(x=>{const $=Ed(x,{});let S=x.id,C=1;for(;S in e;)S=`${x.id}${++C}`;t({...e,[S]:$}),b(!1)},[e,t]),k=u.useCallback(()=>{!g||!g.key||(t({...e,[g.key]:Bs(g)}),m(null),b(!1))},[g,e,t]);return i.jsxs("div",{className:"g7le-computed-form","data-testid":"g7le-computed-form",style:V1,children:[i.jsx("p",{style:U1,children:o("layout_editor.page_settings.computed.heading")}),h.length>0?h.map(x=>i.jsx(H1,{computedKey:x,expr:e[x],recipes:p,rawRecipes:n,t:o,sampleContext:r,dataSourceCandidates:s,bindingCandidates:a,source:l[x],isEffect:d.includes(x),existingKeys:h,onPatchExpr:$=>v(x,$),onRename:($,S)=>_(x,$,S),onRemove:()=>y(x),onRevert:c?()=>c(x):void 0},x)):i.jsx("p",{"data-testid":"g7le-computed-empty",style:gS,children:o("layout_editor.computed.empty")}),i.jsxs("div",{style:{minWidth:0},children:[i.jsxs("button",{type:"button","data-testid":"g7le-computed-add",onClick:()=>b(x=>!x),style:sS,children:["+ ",o("layout_editor.computed.add")," ▾"]}),f?i.jsxs("div",{"data-testid":"g7le-computed-add-list",style:aS,children:[["common","more"].map(x=>{const $=p.filter(S=>(S.group??"more")===x);return $.length===0?null:i.jsxs("div",{children:[i.jsxs("div",{style:lS,children:["━━ ",o(`layout_editor.computed.group_${x}`)," ━━"]}),$.map(S=>{const C=W1(n?.[S.id]);return i.jsxs("button",{type:"button","data-testid":`g7le-computed-preset-${S.id}`,onClick:()=>w(S),style:dS,children:[i.jsx("span",{style:cS,children:Ws(S.label,o,S.id)}),C&&C.kind!=="core"?i.jsxs("span",{"data-testid":`g7le-computed-preset-source-${S.id}`,style:uS,children:["〔",C.id??C.kind,"〕"]}):null]},S.id)})]},x)}),i.jsxs("div",{style:{borderTop:"1px solid #e2e8f0",marginTop:6,paddingTop:6},children:[i.jsxs("button",{type:"button","data-testid":"g7le-computed-custom-open",onClick:()=>m(g?null:{key:"",op:"count",source:"",conditions:[]}),style:pS,children:["⚙ ",o("layout_editor.computed.custom_open")," ▾"]}),g?i.jsxs("div",{style:{marginTop:6},children:[i.jsx(pu,{model:g,onChange:m,t:o,dataSourceCandidates:s,bindingCandidates:a,existingKeys:h}),i.jsx("div",{"data-testid":"g7le-computed-custom-preview",style:{marginTop:6},children:i.jsx(uu,{expr:Bs(g),sampleContext:r,t:o,testIdPrefix:"g7le-computed-custom-preview-eval"})}),i.jsx("button",{type:"button","data-testid":"g7le-computed-custom-commit",disabled:!g.key||h.includes(g.key),onClick:k,style:fS,children:o("layout_editor.computed.custom_add")})]}):null]})]}):null]})]})}function H1({computedKey:e,expr:t,recipes:n,rawRecipes:o,t:r,sampleContext:s,dataSourceCandidates:a,bindingCandidates:l,source:d,isEffect:c,existingKeys:p,onPatchExpr:f,onRename:b,onRemove:g,onRevert:m}){const[h,v]=u.useState(!1),[y,_]=u.useState(!1),w=u.useMemo(()=>Dd(t,n),[t,n]),k=d==="base",x=d==="route-override",$=w.kind==="advanced",S=w.kind==="preset"?n.find(E=>E.id===w.recipeId)??null:null,C=S?Ws(S.label,r,e):e,T=(E,A)=>{if(w.kind!=="preset"||!S)return;const N={...w.params,[E]:A};f(Ed(S,N))};return i.jsxs("div",{"data-testid":`g7le-computed-item-${e}`,style:q1,children:[i.jsxs("div",{style:G1,children:[i.jsx("span",{style:Y1,children:C}),i.jsx("code",{style:X1,children:e}),k?i.jsxs("span",{"data-testid":`g7le-computed-source-${e}`,style:J1,children:["〔",r("layout_editor.computed.source_base"),"〕"]}):null,x?i.jsxs("span",{"data-testid":`g7le-computed-overridden-${e}`,style:Z1,children:["〔",r("layout_editor.computed.source_overridden"),"〕"]}):null,$?i.jsxs("span",{"data-testid":"g7le-computed-advanced",style:Q1,children:["[",r("layout_editor.action.advanced"),"]"]}):null,i.jsx("span",{style:{flex:1}}),i.jsx("button",{type:"button","data-testid":`g7le-computed-code-${e}`,onClick:()=>_(E=>!E),style:js,"aria-label":r("layout_editor.value_tree.show_source"),title:r("layout_editor.value_tree.show_source"),children:""}),$?null:i.jsx("button",{type:"button","data-testid":`g7le-computed-edit-${e}`,onClick:()=>v(E=>!E),style:js,children:r("layout_editor.init_actions.edit")}),i.jsx("button",{type:"button","data-testid":`g7le-computed-remove-${e}`,onClick:g,style:js,"aria-label":r("layout_editor.init_actions.remove"),children:"✕"})]}),y?i.jsx("pre",{"data-testid":`g7le-computed-code-block-${e}`,style:hS,children:t}):null,i.jsx("div",{"data-testid":`g7le-computed-preview-${e}`,children:i.jsx(uu,{expr:t,computedKey:e,sampleContext:s,t:r,isEffect:c,testIdPrefix:`g7le-computed-preview-${e}-eval`})}),x&&m?i.jsxs("div",{style:R1,children:[i.jsxs("span",{style:eS,children:["ⓘ ",r("layout_editor.computed.overridden_hint")]}),i.jsx("button",{type:"button","data-testid":`g7le-computed-revert-${e}`,onClick:m,style:tS,children:r("layout_editor.computed.revert")})]}):null,h&&!$?i.jsxs("div",{style:nS,children:[k?i.jsxs("p",{"data-testid":`g7le-computed-override-notice-${e}`,style:oS,children:["⚠ ",r("layout_editor.computed.override_notice")]}):null,w.kind==="preset"&&S?i.jsx(i.Fragment,{children:S.params.map(E=>i.jsx(K1,{param:E,value:w.params[E.key]??"",t:r,dataSourceCandidates:a,bindingCandidates:l,onChange:A=>T(E.key,A)},E.key))}):w.kind==="custom"?i.jsx(pu,{model:{...w.model,key:e},onChange:E=>b(E.key,Bs(E)),t:r,dataSourceCandidates:a,bindingCandidates:l,existingKeys:p.filter(E=>E!==e)}):null]}):null]})}const F1=new Set(["datasource-picker","binding-picker","select","toggle","state-key-picker","page-picker"]);function K1({param:e,value:t,t:n,dataSourceCandidates:o,bindingCandidates:r,onChange:s}){const a=Ws(e.label,n,e.key),l=e.widget??"",c=F1.has(l)?Tn(e.widget):null,p=l==="datasource-picker"||l==="binding-picker"?o:void 0;return i.jsxs("div",{"data-testid":`g7le-computed-param-${e.key}`,style:rS,children:[i.jsx("span",{style:iS,children:a}),i.jsx("div",{style:{flex:1,minWidth:0},children:c?i.jsx(c,{control:{...e},value:t,onChange:f=>s(typeof f=="string"?f:""),t:n,candidates:p}):i.jsx(Ke,{value:t,onChange:f=>s(f),t:n,candidates:r,testidPrefix:`g7le-computed-param-${e.key}-chip`})})]})}const V1={display:"flex",flexDirection:"column",gap:12,minWidth:0},U1={margin:0,fontSize:13,fontWeight:700,color:"#0f172a"},q1={border:"1px solid #e2e8f0",borderRadius:8,padding:"8px 10px",display:"flex",flexDirection:"column",gap:6,background:"#f8fafc"},G1={display:"flex",alignItems:"center",gap:6},Y1={fontSize:13,fontWeight:600,color:"#0f172a"},X1={fontSize:10,color:"#64748b",fontFamily:"monospace"},J1={fontSize:10,color:"#0891b2",whiteSpace:"nowrap"},Z1={fontSize:10,color:"#7c3aed",whiteSpace:"nowrap"},Q1={fontSize:10,color:"#92400e",whiteSpace:"nowrap"},R1={display:"flex",alignItems:"center",gap:8,marginTop:4,minWidth:0},eS={fontSize:11,color:"#7c3aed",flex:1,minWidth:0},tS={border:"1px solid #c4b5fd",borderRadius:6,background:"#f5f3ff",color:"#6d28d9",cursor:"pointer",fontSize:11,padding:"2px 8px",whiteSpace:"nowrap"},js={border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",color:"#475569",cursor:"pointer",fontSize:11,padding:"2px 6px"},nS={display:"flex",flexDirection:"column",gap:6,paddingTop:6,borderTop:"1px dashed #e2e8f0"},oS={margin:0,fontSize:11,color:"#b45309",background:"#fef3c7",padding:"6px 8px",borderRadius:6},rS={display:"flex",alignItems:"center",gap:8,minWidth:0},iS={fontSize:11,color:"#64748b",minWidth:90},sS={padding:"6px 12px",fontSize:12,border:"1px dashed #94a3b8",borderRadius:6,background:"#fff",color:"#475569",cursor:"pointer"},aS={marginTop:6,border:"1px solid #cbd5e1",borderRadius:8,background:"#fff",padding:8,maxHeight:380,overflowY:"auto"},lS={fontSize:11,fontWeight:700,color:"#94a3b8",padding:"6px 4px 2px"},dS={display:"flex",alignItems:"center",gap:6,width:"100%",textAlign:"left",padding:"6px 8px",fontSize:12,border:"none",background:"transparent",cursor:"pointer",borderRadius:6},cS={flex:1,minWidth:0,color:"#0f172a"},uS={fontSize:10,color:"#64748b",whiteSpace:"nowrap"},pS={padding:"6px 10px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,background:"#f8fafc",color:"#475569",cursor:"pointer"},fS={marginTop:6,padding:"6px 12px",fontSize:12,border:"1px solid #3b82f6",borderRadius:6,background:"#3b82f6",color:"#fff",cursor:"pointer"},gS={margin:0,fontSize:12,color:"#94a3b8"},hS={margin:"2px 0 0",padding:"6px 8px",fontSize:11,fontFamily:"monospace",color:"#0f172a",background:"#f1f5f9",border:"1px solid #e2e8f0",borderRadius:6,whiteSpace:"pre-wrap",wordBreak:"break-all",overflowX:"auto"};function yS(e,t){return t==="any"?!0:e===null||typeof e!="object"?!1:t==="object"?!Array.isArray(e):!0}function Hs({value:e,onChange:t,onValidityChange:n,t:o,shape:r="any",emptyValue:s=void 0,placeholder:a,minHeight:l=80,testidPrefix:d="g7le-json-block",shapeErrorKey:c,invalidErrorKey:p,ariaLabel:f,id:b,validate:g}){const[m,h]=u.useState(()=>Kr(e)),[v,y]=u.useState(null),_=u.useRef(!1),w=u.useRef(e);u.useEffect(()=>{if(!_.current&&!Object.is(e,w.current)){if(Kr(e)===Kr(w.current)){w.current=e;return}h(Kr(e)),y(null),w.current=e,n?.(!0)}},[e]);const k=x=>{if(_.current=!0,h(x),x.trim()===""){y(null),w.current=s,n?.(!0),t(s);return}let $;try{$=JSON.parse(x)}catch{y(o(p??"layout_editor.json_block.invalid")),n?.(!1);return}if(!yS($,r)){y(o(c??"layout_editor.json_block.invalid")),n?.(!1);return}const S=g?.($)??null;if(S){y(S),n?.(!1);return}y(null),w.current=$,n?.(!0),t($)};return i.jsxs("div",{className:"g7le-json-block",style:bS,children:[i.jsx("textarea",{id:b,"data-testid":d,style:{...mS,minHeight:l},value:m,spellCheck:!1,"aria-label":f,"aria-invalid":v?!0:void 0,placeholder:a,onChange:x=>k(x.target.value),onBlur:()=>{_.current=!1}}),v?i.jsx("div",{"data-testid":`${d}-error`,style:vS,children:v}):null]})}function Kr(e){if(e===void 0)return"";try{return JSON.stringify(e,null,2)??""}catch{return""}}const bS={display:"flex",flexDirection:"column",gap:4,minWidth:0},mS={width:"100%",boxSizing:"border-box",fontFamily:"ui-monospace, SFMono-Regular, Menlo, monospace",fontSize:12,lineHeight:1.5,color:"#0f172a",background:"#fff",border:"1px solid #cbd5e1",borderRadius:6,padding:"6px 8px",resize:"vertical"},vS={fontSize:11,color:"#dc2626"},_S=[{kind:"string",labelKey:"layout_editor.initstate.kind_string"},{kind:"number",labelKey:"layout_editor.initstate.kind_number"},{kind:"boolean",labelKey:"layout_editor.initstate.kind_boolean"},{kind:"null",labelKey:"layout_editor.initstate.kind_null"},{kind:"list",labelKey:"layout_editor.initstate.kind_list"},{kind:"object",labelKey:"layout_editor.initstate.kind_object"}],Fs={local:"initLocal",global:"initGlobal",isolated:"initIsolated"};function xS({raw:e,own:t,patch:n,t:o,isolatedOrphan:r={}}){const s=u.useMemo(()=>{const f=t?.state&&typeof t.state=="object"?t.state:void 0,b=e?.state&&typeof e.state=="object"?e.state:void 0;if(!(!f&&!b))return{...b??{},...f??{}}},[t,e]),{initLocal:a,migrated:l}=u.useMemo(()=>K0({initLocal:e?.initLocal,state:s}),[e,s]),d=f=>{if(f==="local")return a;const b=e?.[Fs[f]];return b&&typeof b=="object"?b:{}},c=f=>{if(f==="local"){const b=t?.initLocal??{},g=s?Object.keys(s):[];return Array.from(new Set([...Object.keys(b),...g]))}return Object.keys(t?.[Fs[f]]??{})},p=u.useCallback((f,b)=>{const g=Fs[f];f==="local"&&l&&n("state",void 0),n(g,Object.keys(b).length===0?void 0:b)},[n,l]);return i.jsxs("div",{className:"g7le-initstate-form","data-testid":"g7le-initstate-form",style:kS,children:[i.jsx("p",{style:SS,children:o("layout_editor.page_settings.initstate.heading")}),l?i.jsxs("p",{"data-testid":"g7le-initstate-legacy-state",style:CS,children:["ⓘ ",o("layout_editor.initstate.legacy_state")]}):null,i.jsx(Ks,{scope:"local",label:o("layout_editor.initstate.section_local"),value:d("local"),ownKeys:c("local"),patchSection:p,t:o}),i.jsx(Ks,{scope:"global",label:o("layout_editor.initstate.section_global"),value:d("global"),ownKeys:c("global"),patchSection:p,t:o}),i.jsx(Ks,{scope:"isolated",label:o("layout_editor.initstate.section_isolated"),description:o("layout_editor.initstate.section_isolated_desc"),value:d("isolated"),ownKeys:c("isolated"),patchSection:p,t:o,orphan:r}),i.jsxs("p",{style:KS,children:["ⓘ ",o("layout_editor.initstate.isolated_dynamic_hint")]})]})}function Ks({scope:e,label:t,description:n,value:o,ownKeys:r,patchSection:s,t:a,orphan:l={}}){const[d,c]=u.useState(""),[p,f]=u.useState("string"),[b,g]=u.useState(null),[m,h]=u.useState(!1),v=Object.keys(o),y=v,_=(x,$)=>{s(e,{...o,[x]:$})},w=x=>{const $={...o};delete $[x],s(e,$)},k=()=>{const x=d.trim();if(!x){g(a("layout_editor.initstate.name_required"));return}if(!Ds(x)){g(a("layout_editor.initstate.name_invalid"));return}if(x in o){g(a("layout_editor.initstate.name_duplicate"));return}const $={string:"",number:0,boolean:!1,null:null,list:[],object:{}};s(e,{...o,[x]:$[p]}),c(""),g(null)};return i.jsxs("div",{"data-testid":`g7le-initstate-section-${e}`,style:$S,children:[i.jsxs("div",{style:ES,children:[i.jsx("div",{style:IS,children:t}),i.jsx("button",{type:"button","data-testid":`g7le-initstate-${e}-mode-toggle`,onClick:()=>h(x=>!x),style:TS,children:a(m?"layout_editor.json_block.mode_block":"layout_editor.json_block.mode_code")})]}),n?i.jsxs("p",{style:AS,children:["ⓘ ",n]}):null,m?i.jsx(Hs,{value:o,shape:"object",emptyValue:{},onChange:x=>s(e,x??{}),t:a,testidPrefix:`g7le-initstate-${e}-code`,placeholder:`{ +var o5=Object.defineProperty;var r5=(un,i,u)=>i in un?o5(un,i,{enumerable:!0,configurable:!0,writable:!0,value:u}):un[i]=u;var Wi=(un,i,u)=>r5(un,typeof i!="symbol"?i+"":i,u);(function(un,i,u,En){"use strict";function Lt(){const e=typeof window<"u"?window.G7Core?.__runtime:void 0;if(!e)throw new Error("[layout-editor] window.G7Core.__runtime 이 없습니다. 코어 번들(template-engine.min.js)이 편집기 번들보다 먼저 로드되어야 합니다.");return e}const ji=Lt();ji.TranslationProvider;const et=ji.useTranslation;ji.TranslationReactContext;function rt(){return typeof window>"u"?null:window.__g7Devtools??null}function Nl(e,t){const n=rt();if(!n?.trackEditorState)return;const o={templateIdentifier:e.templateIdentifier,editMode:e.editMode,selectedRoutePath:e.selectedRoute?.path??null,selectedLayoutName:e.selectedRoute?.layoutName??null,routeTreeSize:e.routeTree.length,isRouteTreeCollapsed:e.isRouteTreeCollapsed,locale:e.locale,previewDevice:e.previewDevice,previewZoom:e.previewZoom,lastAction:t,timestamp:Date.now()};try{n.trackEditorState(o)}catch{}}function by(){const e=rt();if(e?.clearEditorStateData)try{e.clearEditorStateData()}catch{}}function my(e){const t=rt();if(t?.trackEditorSampleMatch)try{t.trackEditorSampleMatch(e)}catch{}}function vy(){const e=rt();if(e?.clearEditorSampleData)try{e.clearEditorSampleData()}catch{}}function _y(e){const t=rt();if(t?.trackEditorHistoryEntry)try{t.trackEditorHistoryEntry(e)}catch{}}function xy(){const e=rt();if(e?.clearEditorHistoryData)try{e.clearEditorHistoryData()}catch{}}function Ml(e){const t=rt();if(t?.trackEditorSelection)try{t.trackEditorSelection(e)}catch{}}function wy(){const e=rt();if(e?.clearEditorSelectionData)try{e.clearEditorSelectionData()}catch{}}function fr(e){const t=rt();if(t?.trackEditorPropertyPatch)try{t.trackEditorPropertyPatch(e)}catch{}}function pn(e){const t=rt();if(t?.trackEditorDnd)try{t.trackEditorDnd(e)}catch{}}function ky(){const e=rt();if(e?.clearEditorDndData)try{e.clearEditorDndData()}catch{}}function it(e){const t=rt();if(t?.trackEditorDocument)try{t.trackEditorDocument(e)}catch{}}function Sy(){const e=rt();if(e?.clearEditorDocumentData)try{e.clearEditorDocumentData()}catch{}}function Cy(e){const t=rt();if(t?.trackEditorSpecMerge)try{t.trackEditorSpecMerge(e)}catch{}}function $y(){const e=rt();if(e?.clearEditorSpecMergeData)try{e.clearEditorSpecMergeData()}catch{}}function Hi(e){let t=5381;for(let n=0;n>>0;return t}function Et(e){const t=rt();if(t?.trackEditorI18n)try{t.trackEditorI18n(e)}catch{}}function Ey(){const e=rt();if(e?.clearEditorI18nData)try{e.clearEditorI18nData()}catch{}}function Ll(e){const t=rt();if(t?.trackPageState)try{t.trackPageState(e)}catch{}}function Iy(){const e=rt();if(e?.clearPageStateData)try{e.clearPageStateData()}catch{}}const Pl="g7le.routeTree.collapsed";function Ay(){if(typeof window>"u")return!1;try{return window.localStorage?.getItem(Pl)==="1"}catch{return!1}}function Ty(e){if(!(typeof window>"u"))try{window.localStorage?.setItem(Pl,e?"1":"0")}catch{}}function Dy(e,t){return{templateIdentifier:e,routeTree:[],routesError:null,selectedRoute:null,isRouteTreeCollapsed:Ay(),editMode:"route",returnRoute:null,returnEditMode:null,locale:t,previewDevice:"desktop",previewZoom:1,previewCustomWidth:1024,previewColorScheme:"light",history:{canUndo:!1,canRedo:!1},isPaletteOpen:!1,availableStates:[],activeStateId:null,layoutVersions:{},extensionVersions:{}}}function zy(e,t){switch(t.type){case"SET_ROUTE_TREE":return{...e,routeTree:t.tree,routesError:null};case"SET_ROUTES_ERROR":return{...e,routesError:t.error};case"SELECT_ROUTE":return{...e,editMode:"route",selectedRoute:t.route,returnRoute:null,returnEditMode:null};case"TOGGLE_ROUTE_TREE":return{...e,isRouteTreeCollapsed:!e.isRouteTreeCollapsed};case"SET_LOCALE":return{...e,locale:t.locale};case"ENTER_BASE_EDIT":return{...e,editMode:"base",returnRoute:t.returnRoute!==void 0?t.returnRoute:e.selectedRoute,selectedRoute:{path:`__base__/${t.layoutName}`,layoutName:t.layoutName}};case"EXIT_BASE_EDIT":return{...e,editMode:"route",selectedRoute:e.returnRoute,returnRoute:null};case"ENTER_MODAL_EDIT":return{...e,editMode:"modal",returnRoute:t.returnRoute!==void 0?t.returnRoute:e.selectedRoute,selectedRoute:{path:`__modal__/${t.modalId}`,layoutName:t.hostLayout}};case"EXIT_MODAL_EDIT":return{...e,editMode:"route",selectedRoute:e.returnRoute,returnRoute:null};case"ENTER_EXTENSION_EDIT":return{...e,editMode:"extension",returnRoute:t.returnRoute!==void 0?t.returnRoute:e.selectedRoute,selectedRoute:{path:`__extension__/${t.extensionId}`,layoutName:t.extensionHost??null}};case"SET_RETURN_ROUTE":return e.editMode==="route"||e.returnRoute!==null?e:{...e,returnRoute:t.route};case"EXIT_EXTENSION_EDIT":return{...e,editMode:"route",selectedRoute:e.returnRoute,returnRoute:null};case"ENTER_ITERATION_ITEM_EDIT":return{...e,editMode:"iteration_item",returnRoute:t.returnRoute!==void 0?t.returnRoute:e.selectedRoute,returnEditMode:e.editMode,selectedRoute:{path:`__iteration__/${t.sourcePath}`,layoutName:t.hostLayout??e.selectedRoute?.layoutName??null}};case"EXIT_ITERATION_ITEM_EDIT":return{...e,editMode:e.returnEditMode??"route",selectedRoute:e.returnRoute,returnRoute:null,returnEditMode:null};case"SET_PREVIEW_DEVICE":return{...e,previewDevice:t.device};case"SET_PREVIEW_ZOOM":return{...e,previewZoom:t.zoom};case"SET_PREVIEW_CUSTOM_WIDTH":return{...e,previewCustomWidth:t.width};case"SET_PREVIEW_COLOR_SCHEME":return{...e,previewColorScheme:t.scheme};case"TOGGLE_PALETTE":return{...e,isPaletteOpen:!e.isPaletteOpen};case"SET_PALETTE_OPEN":return{...e,isPaletteOpen:t.open};case"SET_AVAILABLE_STATES":return{...e,availableStates:t.states,activeStateId:t.activeStateId};case"SET_ACTIVE_STATE":return{...e,activeStateId:t.activeStateId};case"SET_LAYOUT_VERSIONS":return{...e,layoutVersions:t.versions};case"SET_LAYOUT_VERSION":return{...e,layoutVersions:{...e.layoutVersions,[t.layoutName]:t.version}};case"SET_EXTENSION_VERSIONS":return{...e,extensionVersions:t.versions};case"SET_EXTENSION_VERSION":return{...e,extensionVersions:{...e.extensionVersions,[t.extensionId]:t.version}};default:return e}}function Ny(e,t){const n=zy(e,t);return((n.selectedRoute?.path??null)!==(e.selectedRoute?.path??null)||n.editMode!==e.editMode)&&(n.availableStates.length>0||n.activeStateId!==null)?{...n,availableStates:[],activeStateId:null}:n}const Ol=u.createContext(null);function My(e,t){const n=Ny(e,t);return Nl(n,t.type),n}function Ly({templateIdentifier:e,initialLocale:t,children:n}){const[o,r]=u.useReducer(My,void 0,()=>{const a=Dy(e,t);return Nl(a,void 0),a});u.useEffect(()=>{Ty(o.isRouteTreeCollapsed)},[o.isRouteTreeCollapsed]);const s=u.useMemo(()=>({state:o,dispatch:r}),[o]);return u.createElement(Ol.Provider,{value:s},n)}function Qe(){const e=u.useContext(Ol);if(!e)throw new Error("useLayoutEditor must be used within LayoutEditorProvider");return e}const Bl=u.createContext(null);function Py({value:e,children:t}){return u.createElement(Bl.Provider,{value:e},t)}function Kt(){return u.useContext(Bl)}const Fi=u.createContext(null);let Oy=1;function By({children:e,t}){const[n,o]=u.useState([]),r=u.useRef([]),[s,a]=u.useState({}),l=u.useCallback(y=>{const _=y.id??`editor-modal-${Oy++}`,w={id:_,content:y.content,ariaLabel:y.ariaLabel??"",closeOnBackdrop:y.closeOnBackdrop??!0,closeOnEscape:y.closeOnEscape??!0,width:y.width??"auto",height:y.height??"auto",maxHeightRatio:y.maxHeightRatio??.85,draggable:y.draggable??!1,avoidRect:y.avoidRect,onClose:y.onClose};return o(k=>{const $=[...k.filter(C=>C.id!==_),w];return r.current=$,$}),_},[]),d=u.useCallback(y=>{o(_=>{const w=_.find(x=>x.id===y),k=_.filter(x=>x.id!==y);return r.current=k,w?.onClose&&queueMicrotask(()=>w.onClose?.()),k})},[]),c=u.useCallback(()=>{const y=r.current[r.current.length-1];y&&d(y.id)},[d]),p=u.useCallback(()=>{o(y=>{for(let _=y.length-1;_>=0;_--){const w=y[_];w?.onClose&&queueMicrotask(()=>w.onClose?.())}return r.current=[],[]})},[]),f=u.useCallback((y,_)=>{a(w=>{const k=r.current.find($=>$.id===y),x=_??k?.ariaLabel??"";return{...w,[y]:x}})},[]),b=u.useCallback(y=>{a(_=>{if(!(y in _))return _;const w={..._};return delete w[y],w})},[]),g=u.useCallback(y=>{b(y),d(y)},[b,d]),m=u.useCallback(()=>{a({}),p()},[p]),h=u.useMemo(()=>Object.keys(s),[s]),v=u.useMemo(()=>({open:l,close:g,closeTop:c,closeAll:m,stack:n,minimize:f,restore:b,minimizedStack:h}),[l,g,c,m,n,f,b,h]);return u.createElement(Fi.Provider,{value:v},e,u.createElement(jy,{minimized:s,onRestore:b,t,key:"__min_bar"}))}function xo(){const e=u.useContext(Fi);if(!e)throw new Error("useEditorModal must be used within EditorModalProvider");return e}function Wy(){const e=u.useContext(Fi);if(!e)return null;const{stack:t,close:n,minimizedStack:o}=e,r=u.useMemo(()=>new Set(o),[o]);return u.useEffect(()=>{if(t.length===0)return;const s=a=>{if(a.key!=="Escape"&&a.key!=="Esc")return;const l=t.filter(c=>!r.has(c.id)),d=l[l.length-1];d?.closeOnEscape&&(a.preventDefault(),a.stopPropagation(),n(d.id))};return window.addEventListener("keydown",s,!0),()=>window.removeEventListener("keydown",s,!0)},[t,n,r]),t.length===0?null:i.jsx(i.Fragment,{children:t.map((s,a)=>i.jsx(Fy,{entry:s,depth:a,hidden:r.has(s.id),onClose:()=>n(s.id)},s.id))})}function jy({minimized:e,onRestore:t,t:n}){const o=n??(s=>s),r=Object.keys(e);return r.length===0?null:i.jsx("div",{className:"g7le-minimized-bar","data-testid":"g7le-minimized-bar",style:{position:"fixed",left:16,bottom:16,display:"flex",gap:8,zIndex:9300},children:r.map(s=>i.jsxs("button",{type:"button","data-testid":`g7le-minimized-${s}`,onClick:()=>t(s),style:{display:"flex",alignItems:"center",gap:6,padding:"8px 12px",background:"#1e293b",color:"#fff",border:"none",borderRadius:8,boxShadow:"0 8px 24px rgba(15, 23, 42, 0.3)",cursor:"pointer",fontSize:13},children:[i.jsx("span",{"aria-hidden":"true",children:"🗗"}),i.jsx("span",{children:e[s]||o("layout_editor.modal.minimized_default")}),i.jsx("span",{style:{opacity:.7},children:o("layout_editor.modal.restore")})]},s))})}function Hy(e,t,n){if(!e||typeof window>"u")return null;const o=window.innerWidth,r=window.innerHeight,s=16,a=e.left,l=o-(e.left+e.width),d=t+s;let c;if(l>=a&&l>=d)c=e.left+e.width+s;else if(a>=d)c=Math.max(s,e.left-s-t);else return null;c=Math.min(Math.max(s,c),Math.max(s,o-t-s));let p=e.top;return p=Math.min(Math.max(s,p),Math.max(s,r-n-s)),{left:c,top:p}}const Wl="clamp(16px, 6vh, 56px)";function Fy({entry:e,depth:t,hidden:n,onClose:o}){const r=9100+t*10,s=e.maxHeightRatio>0?`${e.maxHeightRatio*100}vh`:void 0,a=e.draggable?s:`calc(100vh - (${Wl}) * 2)`,[l,d]=u.useState(null),c=u.useRef(null),p=u.useRef(null);u.useEffect(()=>{if(!e.draggable)return;const v=c.current;if(!v)return;const y=v.getBoundingClientRect(),_=Hy(e.avoidRect,y.width,y.height);_&&d(_)},[e.id,e.draggable]);const f=u.useCallback(v=>{if(!e.draggable)return;const y=v.target;if(!y.closest("[data-modal-drag-handle]")||y.closest("button, a, input, textarea, select"))return;const w=c.current,k=w?.getBoundingClientRect(),x=l?.left??k?.left??0,$=l?.top??k?.top??0;p.current={startX:v.clientX,startY:v.clientY,baseLeft:x,baseTop:$};const C=D=>{const E=p.current;if(!E)return;const I=window.innerWidth,N=window.innerHeight,T=w?.offsetWidth??0,O=w?.offsetHeight??0;let M=E.baseLeft+(D.clientX-E.startX),B=E.baseTop+(D.clientY-E.startY);M=Math.min(Math.max(0,M),Math.max(0,I-T)),B=Math.min(Math.max(0,B),Math.max(0,N-O)),d({left:M,top:B})},S=()=>{p.current=null,window.removeEventListener("pointermove",C),window.removeEventListener("pointerup",S)};window.addEventListener("pointermove",C),window.addEventListener("pointerup",S)},[e.draggable,l]),b=v=>{e.closeOnBackdrop&&v.target===v.currentTarget&&o()},g=e.draggable&&l!==null,m=e.draggable?{position:"fixed",inset:0,background:"transparent",pointerEvents:"none",zIndex:r,...n?{display:"none"}:{}}:{position:"fixed",inset:0,background:"rgba(15, 23, 42, 0.45)",display:n?"none":"flex",alignItems:"flex-start",justifyContent:"center",paddingTop:Wl,boxSizing:"border-box",zIndex:r},h={background:"#fff",borderRadius:10,boxShadow:"0 20px 60px rgba(15, 23, 42, 0.25)",width:e.width,height:e.height,maxHeight:a,maxWidth:"92vw",overflow:"hidden",display:"flex",flexDirection:"column",...e.draggable?{position:"absolute",pointerEvents:"auto",left:g?l.left:"50%",top:g?l.top:"50%",transform:g?void 0:"translate(-50%, -50%)"}:{}};return i.jsx("div",{className:"g7le-modal-backdrop",role:"presentation","data-testid":`g7le-modal-backdrop-${e.id}`,"data-draggable":e.draggable?"true":"false",onClick:b,style:m,children:i.jsx("div",{ref:c,className:"g7le-modal",role:"dialog","aria-modal":"true","aria-label":e.ariaLabel||void 0,"data-testid":`g7le-modal-${e.id}`,"data-positioned":g?"true":"false",onClick:v=>v.stopPropagation(),onPointerDown:e.draggable?f:void 0,style:h,children:e.content})})}function ht({baseStyle:e,hoverStyle:t,disabled:n,style:o,children:r,...s}){const[a,l]=u.useState(!1),d={outline:"none",...e,...o,...a&&!n?t:{}};return i.jsx("button",{type:"button",disabled:n,onMouseEnter:()=>l(!0),onMouseLeave:()=>l(!1),style:d,...s,children:r})}function Ky({templateIdentifier:e,templateName:t,templateVersion:n,templateList:o,onSwitch:r}){const{t:s}=et(),[a,l]=u.useState(!1),[d,c]=u.useState(!1),p=u.useRef(null);u.useEffect(()=>{if(!a)return;const v=_=>{p.current&&!p.current.contains(_.target)&&l(!1)},y=_=>{_.key==="Escape"&&l(!1)};return document.addEventListener("mousedown",v),document.addEventListener("keydown",y),()=>{document.removeEventListener("mousedown",v),document.removeEventListener("keydown",y)}},[a]);const f=o??[],b=f.some(v=>v.identifier!==e),g=!!r&&b,m=t||e,h=v=>{if(v===e){l(!1);return}l(!1),r?.(v)};return i.jsxs("div",{ref:p,className:"g7le-toolbar__template-switcher",style:{position:"relative",display:"inline-flex"},children:[i.jsxs("button",{type:"button",className:"g7le-toolbar__template","data-testid":"g7le-toolbar-template-label","data-can-switch":g?"true":"false","aria-haspopup":g?"listbox":void 0,"aria-expanded":g?a:void 0,disabled:!g,onClick:()=>g&&l(v=>!v),onMouseEnter:()=>c(!0),onMouseLeave:()=>c(!1),title:g?s("layout_editor.chrome.toolbar.switch_template"):m,style:{display:"inline-flex",alignItems:"center",gap:6,padding:"4px 8px",fontSize:13,fontWeight:600,color:"#0f172a",letterSpacing:.1,borderRadius:6,borderWidth:1,borderStyle:"solid",borderColor:g&&(d||a)?"#cbd5e1":"transparent",backgroundColor:g&&(d||a)?"#f1f5f9":"transparent",cursor:g?"pointer":"default",outline:"none",transition:"background-color 120ms, border-color 120ms"},children:[i.jsx("span",{className:"g7le-toolbar__template-name","data-testid":"g7le-toolbar-template-name",children:m}),n&&i.jsxs("span",{className:"g7le-toolbar__template-version","data-testid":"g7le-toolbar-template-version",style:{fontSize:11,fontWeight:500,color:"#64748b",padding:"1px 6px",borderRadius:999,background:"#f1f5f9",border:"1px solid #e2e8f0"},children:["v",n]}),g&&i.jsx("span",{className:"g7le-toolbar__template-caret","aria-hidden":"true",style:{fontSize:9,color:"#94a3b8",marginLeft:1},children:"▼"})]}),g&&a&&i.jsx("div",{className:"g7le-toolbar__template-menu","data-testid":"g7le-toolbar-template-menu",role:"listbox",style:{position:"absolute",top:"calc(100% + 4px)",left:0,minWidth:240,maxWidth:360,maxHeight:360,overflowY:"auto",background:"#ffffff",border:"1px solid #e2e8f0",borderRadius:8,boxShadow:"0 8px 24px rgba(15, 23, 42, 0.14)",zIndex:30,padding:4},children:f.map(v=>{const y=v.identifier===e;return i.jsxs("button",{type:"button",role:"option","aria-selected":y,"aria-current":y?"true":void 0,className:"g7le-toolbar__template-menu-item","data-testid":"g7le-toolbar-template-menu-item","data-identifier":v.identifier,"data-current":y?"true":"false",onClick:()=>h(v.identifier),style:{display:"flex",alignItems:"center",justifyContent:"space-between",gap:8,width:"100%",padding:"8px 10px",fontSize:13,color:"#1e293b",background:y?"#eff6ff":"transparent",border:"none",borderRadius:6,cursor:y?"default":"pointer",textAlign:"left",outline:"none"},onMouseEnter:_=>{y||(_.currentTarget.style.background="#f1f5f9")},onMouseLeave:_=>{_.currentTarget.style.background=y?"#eff6ff":"transparent"},children:[i.jsxs("span",{style:{display:"flex",alignItems:"center",gap:8,minWidth:0},children:[i.jsx("span",{"aria-hidden":"true",style:{width:12,flex:"0 0 auto",color:"#2563eb",fontSize:12,textAlign:"center"},children:y?"✓":""}),i.jsxs("span",{style:{display:"flex",flexDirection:"column",gap:2,minWidth:0},children:[i.jsx("span",{style:{fontWeight:600,color:y?"#1d4ed8":"#1e293b",overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap"},children:v.name||v.identifier}),i.jsx("span",{style:{fontSize:11,color:"#94a3b8",overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap"},children:v.identifier})]})]}),i.jsxs("span",{style:{display:"inline-flex",alignItems:"center",gap:6,flex:"0 0 auto"},children:[i.jsx("span",{style:{fontSize:10,fontWeight:600,color:v.type==="admin"?"#7c3aed":"#0891b2",textTransform:"uppercase",letterSpacing:.4},children:v.type}),v.version&&i.jsxs("span",{style:{fontSize:11,color:"#64748b"},children:["v",v.version]})]})]},v.identifier)})})]})}function Vy(e={}){const{onSave:t,isDirty:n=!1,onExit:o,onEditCode:r,onManageImages:s,onManageTranslations:a,onPageSettings:l,onReset:d,onShowShortcuts:c,onPreview:p,onShowVersions:f,templateName:b,templateVersion:g,templateList:m,onSwitchTemplate:h,onToggleCustomAssets:v,customAssetsDisabled:y=!1,onManageCustomAssets:_}=e,[w,k]=u.useState(!1),[x,$]=u.useState(!1),{t:C}=et(),{state:S,dispatch:D}=Qe(),[E,I]=u.useState(null);u.useEffect(()=>{const V=()=>{const Z=window.__g7LayoutEditorHistory;I(Z?{undo:Z.undo,redo:Z.redo,canUndo:!!Z.canUndo,canRedo:!!Z.canRedo}:null)};V();const X=window.setInterval(V,250);return()=>window.clearInterval(X)},[]);const N=()=>{D({type:"TOGGLE_ROUTE_TREE"})},T=()=>S.editMode==="base"?C("layout_editor.chrome.toolbar.mode_badge.base"):S.editMode==="modal"?C("layout_editor.chrome.toolbar.mode_badge.modal"):S.editMode==="extension"?C("layout_editor.chrome.toolbar.mode_badge.extension"):S.editMode==="iteration_item"?C("layout_editor.chrome.toolbar.mode_badge.iteration_item"):null,O=()=>{S.editMode==="base"?D({type:"EXIT_BASE_EDIT"}):S.editMode==="modal"?D({type:"EXIT_MODAL_EDIT"}):S.editMode==="extension"?D({type:"EXIT_EXTENSION_EDIT"}):S.editMode==="iteration_item"&&D({type:"EXIT_ITERATION_ITEM_EDIT"})},M=T(),B=S.editMode!=="route",z={display:"inline-flex",alignItems:"center",gap:6,padding:"6px 12px",fontSize:13,fontWeight:500,borderRadius:6,borderWidth:1,borderStyle:"solid",borderColor:"transparent",backgroundColor:"transparent",color:"#475569",cursor:"pointer",transition:"background-color 120ms, color 120ms, border-color 120ms"},H={...z,backgroundColor:"#2563eb",color:"#ffffff",borderColor:"#2563eb",fontWeight:600},P={backgroundColor:"#fef3c7",borderColor:"#f59e0b",color:"#92400e",fontWeight:600},A={...z,borderColor:"#e2e8f0",backgroundColor:"#ffffff",color:"#0f172a"},L={backgroundColor:"#f1f5f9",color:"#0f172a",borderColor:"#cbd5e1"},F={backgroundColor:"#1d4ed8",borderColor:"#1d4ed8"},Y={backgroundColor:"#f8fafc",borderColor:"#cbd5e1"};return i.jsxs("div",{className:"g7le-toolbar","data-testid":"g7le-toolbar",role:"toolbar",style:{display:"flex",alignItems:"center",gap:6,padding:"8px 12px",borderBottom:"1px solid #e2e8f0",background:"#ffffff",boxShadow:"0 1px 2px rgba(15, 23, 42, 0.04)"},children:[i.jsx("style",{children:"@keyframes g7le-spin { to { transform: rotate(360deg); } }.g7le-toolbar > * { flex-shrink: 0; white-space: nowrap; }"}),i.jsx("button",{type:"button",className:"g7le-toolbar__route-tree-toggle","data-testid":"g7le-toolbar-toggle-route-tree","aria-label":C("layout_editor.chrome.toolbar.toggle_route_tree"),title:C("layout_editor.chrome.toolbar.toggle_route_tree"),onClick:N,style:{...z,outline:"none",padding:"6px 10px",fontSize:16,lineHeight:1,color:"#64748b"},children:"☰"}),i.jsx(Ky,{templateIdentifier:S.templateIdentifier,templateName:b,templateVersion:g,templateList:m,onSwitch:h}),M&&i.jsx("span",{className:"g7le-toolbar__mode-badge","data-testid":"g7le-toolbar-mode-badge","data-mode":S.editMode,style:{padding:"2px 8px",borderRadius:999,background:"#fef3c7",color:"#92400e",fontSize:11,fontWeight:600,border:"1px solid #fde68a"},children:M}),i.jsx("div",{style:{flex:1}}),i.jsxs(i.Fragment,{children:[i.jsx(ht,{"data-testid":"g7le-toolbar-undo","aria-label":C("layout_editor.chrome.toolbar.undo"),title:C("layout_editor.chrome.toolbar.undo"),disabled:!E?.canUndo,onClick:()=>E?.undo?.(),baseStyle:z,hoverStyle:L,style:{opacity:E?.canUndo?1:.4},children:"↶"}),i.jsx(ht,{"data-testid":"g7le-toolbar-redo","aria-label":C("layout_editor.chrome.toolbar.redo"),title:C("layout_editor.chrome.toolbar.redo"),disabled:!E?.canRedo,onClick:()=>E?.redo?.(),baseStyle:z,hoverStyle:L,style:{opacity:E?.canRedo?1:.4},children:"↷"}),i.jsxs(ht,{"data-testid":"g7le-toolbar-add-element","data-open":S.isPaletteOpen?"true":"false",disabled:!S.selectedRoute,onClick:()=>D({type:"TOGGLE_PALETTE"}),"aria-pressed":S.isPaletteOpen,baseStyle:z,hoverStyle:L,style:S.isPaletteOpen?{backgroundColor:"#eff6ff",color:"#1d4ed8",borderColor:"#93c5fd"}:void 0,children:["+ ",C("layout_editor.chrome.toolbar.add_element")]}),i.jsx(ht,{"data-testid":"g7le-toolbar-preview","data-previewing":x?"true":"false",disabled:!p||!S.selectedRoute?.layoutName||x,onClick:()=>{const V=p?.();V&&typeof V.then=="function"&&($(!0),V.finally(()=>$(!1)))},baseStyle:z,hoverStyle:L,style:{opacity:x?.7:1},children:x?i.jsxs(i.Fragment,{children:[i.jsx("span",{"data-testid":"g7le-toolbar-preview-spinner","aria-hidden":"true",style:{display:"inline-block",width:12,height:12,border:"2px solid rgba(71,85,105,0.35)",borderTopColor:"#475569",borderRadius:"50%",animation:"g7le-spin 600ms linear infinite"}}),C("layout_editor.chrome.toolbar.previewing")]}):i.jsxs(i.Fragment,{children:["👁 ",C("layout_editor.chrome.toolbar.preview")]})}),i.jsxs(ht,{"data-testid":"g7le-toolbar-versions",disabled:!f||!(S.selectedRoute?.layoutName||S.selectedRoute?.path?.startsWith("__extension__/")),onClick:()=>f?.(),"aria-label":C("layout_editor.chrome.toolbar.version_history"),title:C("layout_editor.chrome.toolbar.version_history"),baseStyle:z,hoverStyle:L,children:["🕘 ",C("layout_editor.chrome.toolbar.version_history")]}),i.jsxs(ht,{"data-testid":"g7le-toolbar-edit-code",disabled:!r,onClick:()=>r?.(),baseStyle:z,hoverStyle:L,children:["⟨/⟩ ",C("layout_editor.chrome.toolbar.edit_code")]}),i.jsxs(ht,{"data-testid":"g7le-toolbar-images",disabled:!s||!S.selectedRoute,onClick:()=>s?.(),baseStyle:z,hoverStyle:L,children:["🖼 ",C("layout_editor.chrome.toolbar.images")]}),i.jsxs(ht,{"data-testid":"g7le-toolbar-translations",disabled:!a||!S.selectedRoute,onClick:()=>a?.(),baseStyle:z,hoverStyle:L,children:["🌐 ",C("layout_editor.chrome.toolbar.translations")]}),i.jsxs(ht,{"data-testid":"g7le-toolbar-custom-assets",disabled:!_,onClick:()=>_?.(),title:C("layout_editor.chrome.toolbar.custom_assets_hint"),baseStyle:z,hoverStyle:L,children:["🎨 ",C("layout_editor.chrome.toolbar.custom_assets")]}),i.jsxs(ht,{"data-testid":"g7le-toolbar-toggle-custom-assets","data-custom-disabled":y?"true":"false",disabled:!v,onClick:()=>v?.(),"aria-pressed":y,title:C(y?"layout_editor.chrome.toolbar.custom_assets_on_hint":"layout_editor.chrome.toolbar.custom_assets_off_hint"),baseStyle:z,hoverStyle:L,style:y?{...z,...P}:void 0,children:[y?"👁":"🚫"," ",C(y?"layout_editor.chrome.toolbar.custom_assets_on":"layout_editor.chrome.toolbar.custom_assets_off")]}),i.jsxs(ht,{"data-testid":"g7le-toolbar-page-settings",disabled:!l||!S.selectedRoute||S.editMode==="extension"||S.editMode==="iteration_item",onClick:()=>l?.(),baseStyle:z,hoverStyle:L,children:["⚙ ",C("layout_editor.chrome.toolbar.page_settings")]}),i.jsxs(ht,{"data-testid":"g7le-toolbar-reset",disabled:!d||!n,onClick:()=>d?.(),"aria-label":C("layout_editor.chrome.toolbar.reset"),title:C("layout_editor.chrome.toolbar.reset"),baseStyle:z,hoverStyle:L,style:{opacity:n?1:.4},children:["↺ ",C("layout_editor.chrome.toolbar.reset")]}),i.jsx(ht,{"data-testid":"g7le-toolbar-save","data-dirty":n?"true":"false","data-saving":w?"true":"false",disabled:!t||w,onClick:()=>{const V=t?.();V&&typeof V.then=="function"&&(k(!0),V.finally(()=>k(!1)))},baseStyle:H,hoverStyle:F,style:{opacity:w?.7:n?1:.7},children:w?i.jsxs(i.Fragment,{children:[i.jsx("span",{"data-testid":"g7le-toolbar-save-spinner","aria-hidden":"true",className:"g7le-toolbar__spinner",style:{display:"inline-block",width:12,height:12,border:"2px solid rgba(255,255,255,0.4)",borderTopColor:"#ffffff",borderRadius:"50%",animation:"g7le-spin 600ms linear infinite"}}),C("layout_editor.chrome.toolbar.saving")]}):i.jsxs(i.Fragment,{children:["💾 ",C("layout_editor.chrome.toolbar.save")]})}),i.jsx(ht,{"data-testid":"g7le-toolbar-shortcuts",disabled:!c,onClick:()=>c?.(),"aria-label":C("layout_editor.shortcuts.title"),title:C("layout_editor.shortcuts.title"),baseStyle:z,hoverStyle:L,children:"⌨"}),i.jsxs(ht,{"data-testid":"g7le-toolbar-exit",disabled:!o,onClick:()=>o?.(),baseStyle:A,hoverStyle:Y,children:["✕ ",C("layout_editor.chrome.toolbar.exit")]})]}),B&&i.jsxs(ht,{"data-testid":"g7le-toolbar-exit-alt-mode",onClick:O,baseStyle:A,hoverStyle:Y,children:["← ",S.editMode==="base"&&C("layout_editor.chrome.toolbar.exit_base_edit"),S.editMode==="modal"&&C("layout_editor.chrome.toolbar.exit_modal_edit"),S.editMode==="extension"&&C("layout_editor.chrome.toolbar.exit_extension_edit"),S.editMode==="iteration_item"&&C("layout_editor.chrome.toolbar.exit_iteration_item_edit")]})]})}function gr(){if(typeof window>"u")return null;try{return window.localStorage?.getItem("auth_token")??null}catch{return null}}function Me(e){const t={Accept:"application/json",...e??{}},n=gr();return n&&(t.Authorization=`Bearer ${n}`),t}function Ki(e){return encodeURIComponent(e)}async function jl(e,t){const n=`/api/admin/templates/${Ki(e)}/layout-attachments?layout_name=${encodeURIComponent(t)}`;try{const o=await fetch(n,{method:"GET",headers:Me(),credentials:"same-origin"}),r=await o.json().catch(()=>null);return!o.ok||!r?.success?{ok:!1,status:o.status,message:Vi(r,o.status)}:{ok:!0,data:Array.isArray(r?.data)?r.data:[]}}catch(o){return{ok:!1,status:0,message:o instanceof Error?o.message:"network error"}}}async function Hl(e,t,n){const o=`/api/admin/templates/${Ki(e)}/layout-attachments`,r=new FormData;r.append("file",n),r.append("layout_name",t);try{const s=await fetch(o,{method:"POST",headers:Me(),body:r,credentials:"same-origin"}),a=await s.json().catch(()=>null);return!s.ok||!a?.success||!a?.data?.url?{ok:!1,status:s.status,message:Vi(a,s.status)}:{ok:!0,data:a.data}}catch(s){return{ok:!1,status:0,message:s instanceof Error?s.message:"network error"}}}async function Fl(e){const t=`/api/admin/templates/layout-attachments/${Ki(String(e))}`;try{const n=await fetch(t,{method:"DELETE",headers:Me(),credentials:"same-origin"}),o=await n.json().catch(()=>null);return!n.ok||o&&o.success===!1?{ok:!1,status:n.status,message:Vi(o,n.status)}:{ok:!0,data:!0}}catch(n){return{ok:!1,status:0,message:n instanceof Error?n.message:"network error"}}}function Vi(e,t){if(e&&typeof e=="object"&&"message"in e){const n=e.message;if(typeof n=="string"&&n.length>0)return n}return`HTTP ${t}`}function Uy(e){return e<1024?`${e} B`:e<1024*1024?`${Math.round(e/1024)} KB`:`${(e/(1024*1024)).toFixed(1)} MB`}function qy(e){if(!e)return"";const t=e.match(/^(\d{4})-(\d{2})-(\d{2})/);return t?`${t[2]}-${t[3]}`:""}function Kl({templateIdentifier:e,layoutName:t,t:n,onSelect:o,onChanged:r,onClose:s}){const[a,l]=u.useState([]),[d,c]=u.useState("idle"),[p,f]=u.useState(!1),[b,g]=u.useState(null),m=u.useRef(null),h=u.useCallback(async()=>{c("loading"),g(null);const _=await jl(e,t);_.ok?(l(_.data),c("idle")):(g(_.message),c("error"))},[e,t]);u.useEffect(()=>{h()},[h]);const v=async _=>{const w=_.target.files?.[0];if(!w)return;f(!0),g(null);const k=await Hl(e,t,w);k.ok?(l(x=>[k.data,...x.filter($=>$.id!==k.data.id)]),r?.()):g(k.message),f(!1),m.current&&(m.current.value="")},y=async _=>{if(!(typeof window<"u"&&typeof window.confirm=="function"?window.confirm(n("layout_editor.attachment_manager.delete_confirm")):!0))return;const k=await Fl(_.id);k.ok?(l(x=>x.filter($=>$.id!==_.id)),r?.()):g(k.message)};return i.jsxs("div",{className:"g7le-attachment-manager","data-testid":"g7le-attachment-manager",style:Gy,children:[i.jsxs("div",{"data-modal-drag-handle":!0,style:Yy,children:[i.jsx("span",{style:{fontSize:14,fontWeight:700},"data-testid":"g7le-attachment-manager-title",children:n("layout_editor.attachment_manager.title")}),i.jsx("button",{type:"button","aria-label":"close",onClick:s,style:Xy,"data-testid":"g7le-attachment-manager-close",children:"✕"})]}),i.jsx("div",{style:Jy,children:i.jsxs("label",{style:Zy,"data-testid":"g7le-attachment-upload",children:[p?"…":`⬆ ${n("layout_editor.attachment_manager.upload")}`,i.jsx("input",{ref:m,type:"file",accept:"image/*",onChange:v,disabled:p,style:{display:"none"},"data-testid":"g7le-attachment-upload-input"})]})}),b&&i.jsx("div",{style:Qy,"data-testid":"g7le-attachment-manager-error",children:b}),i.jsx("div",{style:Ry,children:d==="loading"?i.jsx("div",{style:Vl,"data-testid":"g7le-attachment-manager-loading",children:n("layout_editor.attachment_manager.loading")}):a.length===0&&d!=="error"?i.jsx("div",{style:Vl,"data-testid":"g7le-attachment-manager-empty",children:n("layout_editor.attachment_manager.empty")}):i.jsx("div",{style:eb,"data-testid":"g7le-attachment-grid",children:a.map(_=>i.jsxs("div",{style:tb,"data-testid":`g7le-attachment-card-${_.id}`,children:[i.jsx("div",{style:{...nb,backgroundImage:`url(${_.url})`}}),i.jsx("div",{style:ob,title:_.original_name,children:_.original_name}),i.jsxs("div",{style:rb,children:[Uy(_.size),_.created_at?` · ${qy(_.created_at)}`:""]}),i.jsxs("div",{style:ib,children:[o&&i.jsx("button",{type:"button",onClick:()=>o(_.url),style:sb,"data-testid":`g7le-attachment-use-${_.id}`,children:n("layout_editor.attachment_manager.use_as_bg")}),i.jsx("button",{type:"button",onClick:()=>y(_),style:ab,"data-testid":`g7le-attachment-delete-${_.id}`,children:"🗑"})]})]},_.id))})}),i.jsx("div",{style:lb,children:i.jsx("button",{type:"button",onClick:s,style:db,"data-testid":"g7le-attachment-manager-footer-close",children:n("layout_editor.attachment_manager.close")})})]})}const Gy={display:"flex",flexDirection:"column",minHeight:0,flex:1},Yy={display:"flex",alignItems:"center",justifyContent:"space-between",padding:"12px 16px",borderBottom:"1px solid #e2e8f0"},Xy={border:"none",background:"transparent",fontSize:16,color:"#94a3b8",cursor:"pointer"},Jy={padding:"10px 16px",borderBottom:"1px solid #f1f5f9",display:"flex",gap:8},Zy={padding:"6px 12px",fontSize:13,border:"1px solid #2563eb",borderRadius:6,background:"#fff",color:"#2563eb",cursor:"pointer",display:"inline-flex",alignItems:"center"},Qy={padding:"8px 16px",fontSize:12,color:"#b91c1c",background:"#fef2f2"},Ry={padding:16,overflow:"auto",flex:1,minHeight:120},Vl={fontSize:13,color:"#94a3b8",textAlign:"center",padding:"32px 0"},eb={display:"grid",gridTemplateColumns:"repeat(auto-fill, minmax(140px, 1fr))",gap:12},tb={display:"flex",flexDirection:"column",gap:4,border:"1px solid #e2e8f0",borderRadius:8,padding:8},nb={width:"100%",height:88,borderRadius:6,border:"1px solid #cbd5e1",backgroundSize:"cover",backgroundPosition:"center",backgroundColor:"#f8fafc"},ob={fontSize:12,fontWeight:600,color:"#0f172a",whiteSpace:"nowrap",overflow:"hidden",textOverflow:"ellipsis"},rb={fontSize:11,color:"#64748b"},ib={display:"flex",gap:6,marginTop:2},sb={flex:1,padding:"4px 6px",fontSize:11,border:"1px solid #2563eb",borderRadius:6,background:"#2563eb",color:"#fff",cursor:"pointer"},ab={padding:"4px 8px",fontSize:12,border:"1px solid #fecaca",borderRadius:6,background:"#fff",color:"#dc2626",cursor:"pointer"},lb={padding:"10px 16px",borderTop:"1px solid #e2e8f0",display:"flex",justifyContent:"flex-end"},db={padding:"6px 14px",fontSize:13,border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",color:"#475569",cursor:"pointer"};function hr(e){return encodeURIComponent(e)}async function cb(e,t,n){const o=new URLSearchParams;t&&o.set("layout_name",t);const r=o.toString(),s=`/api/admin/templates/${hr(e)}/custom-translations`+(r?`?${r}`:"");try{const a=await fetch(s,{method:"GET",headers:Me(),credentials:"same-origin"}),l=await a.json().catch(()=>null);return!a.ok||!l?.success?{ok:!1,status:a.status,message:yr(l,a.status)}:{ok:!0,data:Array.isArray(l?.data)?l.data:[]}}catch(a){return{ok:!1,status:0,message:a instanceof Error?a.message:"network error"}}}async function ub(e,t,n,o){const r=`/api/admin/templates/${hr(e)}/custom-translations/${t}`;try{const s=await fetch(r,{method:"PUT",headers:Me({"Content-Type":"application/json"}),body:JSON.stringify({values:n,expected_lock_version:o}),credentials:"same-origin"}),a=await s.json().catch(()=>null);return!s.ok||!a?.success||!a?.data?{ok:!1,status:s.status,message:yr(a,s.status)}:{ok:!0,data:a.data}}catch(s){return{ok:!1,status:0,message:s instanceof Error?s.message:"network error"}}}async function pb(e,t){const n=`/api/admin/templates/${hr(e)}/custom-translations/${t}`;try{const o=await fetch(n,{method:"DELETE",headers:Me(),credentials:"same-origin"}),r=await o.json().catch(()=>null);return!o.ok||r&&r.success===!1?{ok:!1,status:o.status,message:yr(r,o.status)}:{ok:!0,data:!0}}catch(o){return{ok:!1,status:0,message:o instanceof Error?o.message:"network error"}}}async function fb(e,t){const n=`/api/admin/templates/${hr(e)}/custom-translations`;try{const o=await fetch(n,{method:"DELETE",headers:Me({"Content-Type":"application/json"}),body:JSON.stringify({ids:t}),credentials:"same-origin"}),r=await o.json().catch(()=>null);return!o.ok||!r?.success?{ok:!1,status:o.status,message:yr(r,o.status)}:{ok:!0,data:{deleted:typeof r?.data?.deleted=="number"?r.data.deleted:t.length}}}catch(o){return{ok:!1,status:0,message:o instanceof Error?o.message:"network error"}}}const Ul=/\$t:(custom\.[A-Za-z0-9_\-]+\.\d+)/g;function gb(e){const t=new Set,n=o=>{if(typeof o=="string"){if(o.includes("$t:custom.")){let r;for(Ul.lastIndex=0;(r=Ul.exec(o))!==null;)t.add(r[1])}return}if(Array.isArray(o)){for(const r of o)n(r);return}if(o&&typeof o=="object")for(const r of Object.values(o))n(r)};return n(e),t}function yr(e,t){if(e&&typeof e=="object"&&"message"in e){const n=e.message;if(typeof n=="string"&&n.length>0)return n}return`HTTP ${t}`}const Pt=Lt().TranslationEngine,wo=Lt().createLogger,Ui=wo("EditorSpecLoader"),hb=["controls","componentCapabilities","actionRecipes","conditionRecipes","pageSettings","initActionRecipes","errorRecipes","computedRecipes","loadingComponents"],yb=["actionRecipes","initActionRecipes","errorRecipes","computedRecipes","loadingComponents"];function ql(e){if(typeof window>"u")return[];const n=window.G7Config?.[e];if(!Array.isArray(n))return[];const o=[];for(const r of n){const s=r?.identifier;typeof s=="string"&&s.length>0&&o.push(s)}return o}async function bb(e,t,n,o){const s=`${n}/${e==="template"?"templates":e==="module"?"modules":"plugins"}/${encodeURIComponent(t)}/editor-spec`;try{const a=await o(s);if(!a.ok)return a.status!==404&&Ui.warn(`fetchOneSpec: HTTP ${a.status}`,s),null;const l=await a.json();return!l?.success||!l.data?null:l.data.spec??null}catch(a){return Ui.warn("fetchOneSpec: fetch failed",s,a),null}}function mb(e){return yb.includes(e)}function vb(e,t){return e==null||typeof e!="object"?e:{...e,__source:t}}function _b(e,t,n,o){const r=t[n];if(!r||typeof r!="object")return;const s=e[n]??{};if(o&&mb(n)){const a={};for(const[l,d]of Object.entries(r))a[l]=vb(d,o);e[n]={...s,...a};return}e[n]={...s,...r}}function xb(e,t){if(!t.nesting)return;const n=e.nesting??{},o=new Set(n.draggable??[]);for(const r of t.nesting.draggable??[])o.add(r);n.draggable=Array.from(o),n.containers={...n.containers??{},...t.nesting.containers??{}},e.nesting=n}function wb(e,t){if(!t.componentPalette)return;const n=e.componentPalette??{},o=[...n.groups??[],...t.componentPalette.groups??[]];n.groups=o,n.entries={...n.entries??{},...t.componentPalette.entries??{}},e.componentPalette=n}function kb(e,t){if(!t.states)return;const n=e.states??{},o=[...n.groups??[],...t.states.groups??[]];n.groups=o,e.states=n}function Sb(e,t){if(!Array.isArray(t.stateLabels)||t.stateLabels.length===0)return;const n=[...e.stateLabels??[],...t.stateLabels],o=new Map;for(const r of n)!r||typeof r.key!="string"||typeof r.scope!="string"||o.set(`${r.scope}:${r.key}`,r);e.stateLabels=Array.from(o.values())}function Cb(e,t){if(!t.actionChipCandidates)return;const n=e.actionChipCandidates??{};for(const o of["response","error","payload"]){const r=t.actionChipCandidates[o];!Array.isArray(r)||r.length===0||(n[o]=[...n[o]??[],...r])}e.actionChipCandidates=n}function $b(e,t){if(!t.sampleData)return;const n=e.sampleData??{};n.byDataSourceId={...n.byDataSourceId??{},...t.sampleData.byDataSourceId??{}},n.byEndpointPattern={...n.byEndpointPattern??{},...t.sampleData.byEndpointPattern??{}},e.sampleData=n}function Gl(e,t,n){for(const o of hb)_b(e,t,o,n);xb(e,t),wb(e,t),kb(e,t),Sb(e,t),Cb(e,t),$b(e,t),t.version&&(e.version=t.version)}async function Eb(e){const t=e.apiBaseUrl??"/api",n=e.fetcher??(typeof fetch<"u"?fetch:void 0);if(!n)return Ui.warn("loadEditorSpecBundle: fetch unavailable, returning null spec"),{spec:null,sampleGlobalSources:[]};const o=e.activeModuleIdentifiers??ql("activeModules"),r=e.activePluginIdentifiers??ql("activePlugins"),s=[...o.map(p=>({kind:"module",id:p})),...r.map(p=>({kind:"plugin",id:p})),{kind:"template",id:e.templateIdentifier}],a=await Promise.all(s.map(p=>bb(p.kind,p.id,t,n)));let l=null;const d=[],c={};if(e.coreSeed&&(l={},Gl(l,e.coreSeed,{kind:"core"})),s.forEach((p,f)=>{const b=a[f];if(!b)return;b.sampleGlobal&&typeof b.sampleGlobal=="object"&&d.push({id:p.id,kind:p.kind,sampleGlobal:b.sampleGlobal});const g=b.sampleData?.byDataSourceId;if(g&&typeof g=="object"){const m=p.kind==="template"?"template":`${p.kind}:${p.id}`;c[m]={...c[m]??{},...g}}l===null&&(l={}),Gl(l,b,{kind:p.kind,id:p.id})}),l!==null&&Object.keys(c).length>0){const p=l;p.sampleData={...p.sampleData??{},bySource:c}}return{spec:l,sampleGlobalSources:d}}function qi(e,t){return e?.componentCapabilities?.[t]??null}function Ct(e,t){return e?.controls?.[t]??null}function Ib(e){return e?.initActionRecipes??{}}function Ab(e){return e?.computedRecipes??{}}function Tb(e){return e?.errorRecipes??{}}function Db(e){const t=e?.loadingComponents;return!t||typeof t!="object"?[]:Object.values(t).filter(n=>n!=null&&typeof n=="object"&&typeof n.name=="string")}function zb(e,t,n){const o=e?.stateLabels;if(!Array.isArray(o))return null;for(const r of o)if(r?.scope===t&&r?.key===n)return typeof r.label_key=="string"?r.label_key:null;return null}function Gi(e){return e!==null&&typeof e=="object"&&!Array.isArray(e)}function Nb(e){return e===null?"null":typeof e=="string"?e.length>40?`${e.slice(0,40)}…`:e:typeof e=="number"||typeof e=="boolean"?String(e):""}function Mb(e){const t=e.find(n=>Gi(n));if(Gi(t))return Object.keys(t).slice(0,8)}function Lb(e){return Object.keys(e).slice(0,8)}function Pb(e){const t=Object.entries(e);return t.length===0?!1:t.some(([,n])=>n===null||typeof n!="object")}function Yi(e,t,n,o,r){if(Array.isArray(e)){r(t,"array",`[${e.length}]`,Mb(e));return}if(Gi(e)){if(t&&Pb(e)){const s=Lb(e);r(t,"object",`{${s.length}}`,s)}if(n>=o)return;for(const[s,a]of Object.entries(e)){const l=t?`${t}.${s}`:s;Yi(a,l,n+1,o,r)}return}r(t,"scalar",Nb(e))}function Ob(e,t,n){const o=[];let r=e;for(const s of t){if(!r||(r=(Array.isArray(r.children)?r.children:[])[s],!r))break;const l=r.iteration;if(!(!l||l.source===void 0)&&(typeof l.index_var=="string"&&l.index_var.length>0&&o.push({name:l.index_var,kind:"index"}),typeof l.item_var=="string"&&l.item_var.length>0)){const d=typeof l.source=="string"?l.source:"",c=d&&n?.get(Yl(d))||[];o.push({name:l.item_var,kind:"item",itemFields:c})}}return o}function Yl(e){return e.replace(/^\{\{|\}\}$/g,"").trim()}function Bb(e){const t=new Map;for(const n of e)n.shape==="array"&&n.itemFields&&n.itemFields.length>0&&t.set(Yl(n.expression),n.itemFields);return t}function Xi(e){const t=e.maxDepth??4,n=[];for(const o of e.dataSources??[])!o||typeof o.id!="string"||o.id.length===0||Yi(o.sample,"",0,t,(r,s,a,l)=>{const d=r?`{{${o.id}.${r}}}`:`{{${o.id}}}`;n.push({expression:d,source:"data_source",sourceId:o.id,path:r,shape:s,groupLabelKey:o.labelKey?.startsWith("$t:")?o.labelKey.slice(3):o.labelKey,preview:a,itemFields:l})});for(const o of e.states??[])!o||typeof o.scope!="string"||Yi(o.tree,"",0,t,(r,s,a,l)=>{if(!r)return;const d=zb(e.spec,o.scope,r)??void 0;n.push({expression:`{{${o.scope}.${r}}}`,source:o.scope,sourceId:o.scope,path:r,shape:s,labelKey:d?.startsWith("$t:")?d.slice(3):d,preview:a,itemFields:l})});for(const o of e.iterationVars??[])if(!(!o||typeof o.name!="string"||o.name.length===0)){if(o.kind==="index"){n.push({expression:`{{${o.name}}}`,source:"iteration",sourceId:o.name,path:"",shape:"scalar",preview:"0"});continue}for(const r of o.itemFields??[])n.push({expression:`{{${o.name}.${r}}}`,source:"iteration",sourceId:o.name,path:r,shape:"scalar",preview:""})}return n}const Wb=["$localized"];function jb(e){const t=e.trim(),n=t.indexOf("(");if(n===-1||!t.endsWith(")"))return{fn:null,inner:t};const o=t.slice(0,n).trim();if(!Wb.includes(o))return{fn:null,inner:t};const r=t.slice(n+1,-1);let s=0,a=null;for(let l=0;ln.shape===t)}function Qi(e,t){const n=t.trim().toLowerCase();return n===""?e:e.filter(o=>[o.resolvedLabel??"",o.sourceId,o.path,o.preview].join(" ").toLowerCase().includes(n))}function Xl(e,t,n){const o={response:[{path:"data",labelKey:"layout_editor.action_chip.response_data",shape:"object"},{path:"message",labelKey:"layout_editor.action_chip.response_message",shape:"scalar"}],error:[{path:"status",labelKey:"layout_editor.action_chip.error_status",shape:"scalar"},{path:"message",labelKey:"layout_editor.action_chip.error_message",shape:"scalar"},{path:"errors",labelKey:"layout_editor.action_chip.error_errors",shape:"object"},{path:"data",labelKey:"layout_editor.action_chip.error_data",shape:"object"},{path:"statusText",labelKey:"layout_editor.action_chip.error_status_text",shape:"scalar"}],payload:[{path:"data",labelKey:"layout_editor.action_chip.payload_data",shape:"object"}]},r=e==="response"?"response":e==="error"?"error":"message",s=new Set,a=[];for(const d of o[e])s.has(d.path)||(s.add(d.path),a.push(d));const l=n?.[e];if(Array.isArray(l))for(const d of l)!d||typeof d.path!="string"||typeof d.labelKey!="string"||s.has(d.path)||(s.add(d.path),a.push({path:d.path,labelKey:d.labelKey,shape:d.shape??"scalar"}));return a.map(d=>({expression:`{{${r}.${d.path}}}`,source:"_local",sourceId:r,path:d.path,shape:d.shape,labelKey:d.labelKey,preview:t(d.labelKey)}))}const Ri=/\{\{(?:[^}]|\}(?!\}))*\}\}/g;function An(e){const t=[];if(typeof e!="string"||e.length===0)return t;let n=0,o=0;const r=new RegExp(Ri.source,"g");let s;for(;(s=r.exec(e))!==null;){const a=s.index,l=a+s[0].length;a>n&&t.push({kind:"literal",raw:e.slice(n,a),start:n,end:a}),t.push({kind:"binding",raw:s[0],start:a,end:l,parsed:Vt(s[0]),bindingIndex:o++}),n=l}return nt.kind==="binding")}function mr(e){return typeof e!="string"?!1:new RegExp(Ri.source).test(e)}function Fb(e){const t=new Set(e.map(o=>o.trim()));let n=0;for(;t.has(`p${n}`);)n+=1;return`p${n}`}function Jl(e,t,n){const o=typeof e=="string"?e:"",r=Math.max(0,Math.min(t,o.length)),s=o.slice(0,r),a=o.slice(r),l=s.length>0&&!/\s$/.test(s)?" ":"",d=a.length>0&&!/^\s/.test(a)?" ":"";return`${s}${l}{${n}}${d}${a}`}function es(e,t){const n=typeof e=="string"?e:"";return Tn(n).includes(t)?n:n.length===0?`{${t}}`:/\s$/.test(n)?`${n}{${t}}`:`${n} {${t}}`}function Zl(e){return br(e).map(t=>({bindingIndex:t.bindingIndex??0,expression:t.raw,parsed:t.parsed??null,isComplex:(t.parsed??null)===null}))}function vr(e){return typeof e!="string"?"":e.replace(new RegExp(Ri.source,"g")," ").replace(/\s+/g," ").trim()}const Kb=/\$t:([a-zA-Z0-9._-]+)/g,Ql="custom.";function ts(e,t){return typeof e!="string"||e.length===0?typeof e=="string"?e:"":t?An(e).map(n=>n.kind!=="literal"?n.raw:n.raw.replace(Kb,(o,r)=>{if(r.startsWith(Ql))return o;const s=t(r);return s&&s!==r?s:o})).join(""):e}const Un="(?:(?!\\|[a-zA-Z0-9_]+=).)*",Vb=new RegExp(`^\\s*\\$t:([a-zA-Z0-9._-]+)((?:\\|[a-zA-Z0-9_]+=${Un})*)`),Ub=new RegExp(`\\|([a-zA-Z0-9_]+)=(${Un})`,"g");function Ut(e){if(typeof e!="string")return"";const t=Vt(e);if(t)return t.path||t.sourceId;const n=e.trim().replace(/^\{\{\s*/,"").replace(/\s*\}\}$/,"");let o=-1;for(let l=0;l0){const l=n.slice(0,o).trim(),d=Vt(`{{${l}}}`);if(d)return d.path||d.sourceId;const c=l.replace(/\?\./g,".").replace(/\s*\?\?.*$/,"").trim();if(/^[A-Za-z_$][\w$.]*$/.test(c)){const p=c.split(".").filter(Boolean);return p.length>1&&p.slice(1).join(".")||c}}const r=n.replace(/\s+/g," ").trim(),s=r.replace(/\s*(\?\?|\|\|)\s*(\[\s*\]|\{\s*\}|'[^']*'|"[^"]*"|\d+|null|undefined)\s*$/,"").trim(),a=/\.([A-Za-z_$][\w$]*)\s*$/.exec(s);return a?a[1]:r}const Rl=/\$(?:core|module|plugin)_settings:[a-zA-Z0-9._:-]+/g;function ns(e){return typeof e=="string"&&new RegExp(Rl.source).test(e)}function qb(e){return typeof e!="string"?"":e.replace(/^\$(?:core|module|plugin)_settings:/,"").split(/[.:]/).filter(Boolean).pop()||e}function ed(e){const t=[];for(const n of An(e)){if(n.kind==="binding"){t.push({kind:"chip",label:Ut(n.raw)});continue}let o=0;const r=n.raw,s=new RegExp(Rl.source,"g");let a;for(;(a=s.exec(r))!==null;)a.index>o&&t.push({kind:"text",label:r.slice(o,a.index)}),t.push({kind:"chip",label:qb(a[0])}),o=a.index+a[0].length;o{const m=`p${o.length}`;return o.push(g),r[m]=Ut(g),`{${m}}`};let a=e,l="";const d=Vb.exec(e);if(d&&!d[1].startsWith(Ql)){const g=d[1],m=d[2]??"",h=e.slice(d[0].length),v=new Map,y=new RegExp(Ub.source,"g");let _;for(;(_=y.exec(m))!==null;)_[2]&&_[2].includes("{{")&&v.set(_[1],_[2]);const w=t?t(g):"";if(w&&w!==g&&!w.startsWith("$t:"))l=w.replace(/\{\{?([a-zA-Z0-9_]+)\}?\}/g,(k,x)=>{const $=v.get(x);return $?s($):k});else return n;a=h}const c=ts(a,t),p=An(c).map(g=>g.kind==="binding"?s(g.raw):g.raw).join(""),f=`${l}${p}`;return/\$t:[a-zA-Z0-9._-]+/.test(f)||o.length===0?n:{chipValue:f,bindings:o,paramLabels:r,keyifiable:!0}}function td(e,t){return t.bindings.length?e+t.bindings.map((n,o)=>`|p${o}=${n}`).join(""):e}function nd(e,t){const n=br(t).map(r=>r.raw);if(n.length===0)return e;const o=n.map((r,s)=>`|p${s}=${r}`).join("");return`${e}${o}`}function od(e){if(typeof e!="string")return"";const t=An(e);let n=0;return t.map(o=>o.kind==="binding"?`{p${n++}}`:o.raw).join("")}const rd=new RegExp(`^\\s*\\$t:([a-zA-Z0-9._-]+)((?:\\|[a-zA-Z0-9_]+=${Un})+)\\s*$`),Yb=new RegExp(`\\|([a-zA-Z0-9_]+)=(${Un})`,"g");function vt(e){if(typeof e!="string")return null;const t=rd.exec(e);if(!t)return null;const n=t[1],o=t[2],r=[],s=new RegExp(Yb.source,"g");let a;for(;(a=s.exec(o))!==null;){const l=a[1],d=a[2];r.push({name:l,expression:d,parsed:Vt(d)})}return{key:n,params:r}}function id(e){return typeof e=="string"&&rd.test(e)}function Tn(e){if(typeof e!="string")return[];const t=[],n=/\{\{?(p\d+)\}?\}/g;let o;for(;(o=n.exec(e))!==null;)t.push(o[1]);return t.sort()}function Xb(e,t,n){if(typeof e!="string")return e;const o=new RegExp(`(\\|${t}=)${Un}`);return o.test(e)?e.replace(o,`$1${n}`):e}function ko(e,t){if(typeof e!="string")return e;const n=new RegExp(`\\|${t}=${Un}`);return e.replace(n,"")}function sd(e,t){return typeof e!="string"?e:e.replace(new RegExp(`\\s*\\{\\{?${t}\\}?\\}\\s*`,"g")," ").replace(/\s+/g," ").trim()}function Jb(e,t){const n=s=>br(typeof s=="string"?s:"").map(a=>a.raw.replace(/\s+/g,"")).sort(),o=n(e),r=n(t);return o.length!==r.length?!1:o.every((s,a)=>s===r[a])}function ad(e){return e.__source?.kind==="base"}function ld(e){return e.__source?.kind==="partial"}function Zb(e){return e.__source?.kind==="route"}function _r(e,t){return e.__source?.kind!=="extension"?!1:t==null?!0:e.__source.extensionId!==t}function dd(e){return e.type==="extension_point"}function rs(e=[]){for(const t of e)if(t.iteration&&t.iteration.source!==void 0)return!0;return!1}function Qb(e,t=[]){for(const n of t)if(n.iteration&&n.iteration.source!==void 0)return!0;return e.iteration&&e.iteration.source!==void 0?!0:cd(e)}function cd(e){if(typeof e.text=="string"&&ud(e.text)&&!Rb(e.text))return!0;if(e.props&&typeof e.props=="object"){for(const t of Object.values(e.props))if(typeof t=="string"&&ud(t))return!0}return!1}function ud(e){return/\{\{[\s\S]*?\}\}/.test(e)}function Rb(e){const t=vt(e);return t!==null&&t.key.startsWith("custom.")}function em(e,t){return e??t??null}function pd(e,t,n){if(dd(e))return!0;switch(t){case"route":return ad(e)||ld(e)||_r(e);case"base":return _r(e);case"modal":return _r(e);case"extension":return _r(e,n)?!0:ad(e)||Zb(e)||ld(e);default:return!1}}function tt(e){return typeof e=="object"&&e!==null&&typeof e.responsive=="string"}function xr(e,t){return tt(e)?tt(t)&&e.responsive===t.responsive:!tt(t)&&e===t}function at(e){const t=[];let n=!1;for(const o of e){if(tt(o)){t.push("responsive",o.responsive),n=!0;continue}n?t.push("children",String(o)):(t.push(String(o)),n=!0)}return t.join(".")}function Ot(e){return Array.isArray(e.children)?e.children:[]}function So(e,t){const n=e.responsive?.[t];return n&&Array.isArray(n.children)?n.children:[]}function Dn(e,t){const n=[];let o=e,r=Ot(e);for(let s=0;s=o.length||(n=o[r],!n))return null;o=Ot(n)}return n}function tm(e,t,n){if(!e||t.length===0)return t;let o=e,r=Ot(e),s;for(let a=0;a=2?t[t.length-2]:void 0;if(r!==void 0&&tt(r)){const a=Pe(e,t.slice(0,-2));return a?{owner:a,children:So(a,r.responsive),branchKey:r.responsive,index:o}:null}const s=Pe(e,t.slice(0,-1));return s?{owner:s,children:Ot(s),index:o}:null}function is(e,t){if(e.branchKey!==void 0){const n={...e.owner.responsive??{}};n[e.branchKey]={...n[e.branchKey]??{},children:t},e.owner.responsive=n}else e.owner.children=t}function tn(e){const t={...e};t.props&&typeof t.props=="object"&&(t.props={...t.props});const n=Ot(e);if(n.length>0&&(t.children=n.map(tn)),e.responsive&&typeof e.responsive=="object"){const o={};for(const r of Object.keys(e.responsive)){const s=e.responsive[r];if(!s||typeof s!="object"){o[r]=s;continue}const a={...s};Array.isArray(s.children)&&(a.children=s.children.map(tn)),a.props&&typeof a.props=="object"&&(a.props={...a.props}),o[r]=a}t.responsive=o}return t}function ss(e,t,n,o){const r=tn(e),s=t.length>0?t[t.length-1]:void 0;if(s!==void 0&&tt(s)){const p=Pe(r,t.slice(0,-1));if(!p)return r;const f=So(p,s.responsive),b=Math.max(0,Math.min(n,f.length)),g=[...f];return g.splice(b,0,o),is({owner:p,branchKey:s.responsive},g),r}const a=Pe(r,t);if(!a)return r;const l=Ot(a),d=Math.max(0,Math.min(n,l.length)),c=[...l];return c.splice(d,0,o),a.children=c,r}function as(e,t){if(t.length===0)return e;const n=tn(e),o=fd(n,t);if(!o||o.index<0||o.index>=o.children.length)return n;const r=[...o.children];return r.splice(o.index,1),is(o,r),n}function nm(e,t,n,o){if(t.length===0||!n||tt(t[t.length-1]))return e;const r=tn(e),s=Pe(r,t);if(!s)return r;const a=s.responsive&&typeof s.responsive=="object"?s.responsive[n]:void 0;if(a&&Array.isArray(a.children))return r;const l=o&&s.responsive&&typeof s.responsive=="object"?s.responsive[o]:void 0,c=(l&&Array.isArray(l.children)?l.children:Ot(s)).map(tn),p={...s.responsive??{}};return p[n]={...p[n]??{},children:c},s.responsive=p,r}function om(e,t,n){if(t.length===0||!n||tt(t[t.length-1]))return e;const o=tn(e),r=Pe(o,t);if(!r||!r.responsive||typeof r.responsive!="object")return o;const s=r.responsive[n];if(!s||!Array.isArray(s.children))return o;const a={...r.responsive},l={...a[n]};return delete l.children,Object.keys(l).length===0?delete a[n]:a[n]=l,r.responsive=Object.keys(a).length>0?a:void 0,o}function rm(e,t,n,o){if(t.length===0)return e;const r=Pe(e,t);if(!r||im(t,n))return e;const s=tn(r),a=as(e,t),l=gd(n,t);let d=o;const c=t.slice(0,-1);if(hd(c,n)){const p=t[t.length-1];(typeof p=="number"?p:0)o){const s=[...e];return s[n]=r-1,s}return e}function hd(e,t){if(e.length!==t.length)return!1;for(let n=0;ne.length||hd(e,t)}function ls(e){const t={...e};if(t.props&&typeof t.props=="object"&&(t.props={...t.props}),typeof t.id=="string"&&t.id.length>0){const o=typeof t.name=="string"?t.name:"node";t.id=`${o}_${sm()}`}const n=Ot(e);return n.length>0&&(t.children=n.map(ls)),t}function Bt(e,t,n){if(t.length===0)return n(e);const o=tn(e),r=Pe(o,t);if(!r)return o;const s=n(r),a=fd(o,t);if(!a||a.index<0||a.index>=a.children.length)return o;const l=[...a.children];return l[a.index]=s,is(a,l),o}function sm(){return Math.floor(Math.random()*2821109907456).toString(36).padStart(8,"0")}function qn(e){if(!Array.isArray(e)||e.length===0)return[];const t=[];for(const n of e){const o=am(n);o.length>0&&t.push(...o)}return t}function am(e){if(!e||typeof e!="object")return[];const t=e.__source,n=e._fromBase===!0,o=t?.kind;if(o==="extension"||o==="partial")return[];if(o==="base"||n){const s=Array.isArray(e.children)?e.children:[];return qn(s)}const r={...e};return delete r.__source,delete r._fromBase,delete r.__injectedProps,Array.isArray(r.children)&&(r.children=qn(r.children)),[r]}function Co(e){if(!e||typeof e!="object")return e;const t=e.__editor,n=t&&typeof t=="object"&&!Array.isArray(t)?t.original:void 0,o=n&&typeof n=="object"&&!Array.isArray(n)?n:void 0,r=Array.isArray(e.components)?e.components:[],s=r.length>0,a=qn(r),l=o?{...o}:{...e};if(typeof l.extends=="string"&&l.extends.length>0){delete l.components;const c=l.slots&&typeof l.slots=="object"&&!Array.isArray(l.slots)?l.slots:{},p=Object.keys(c);if(!s&&p.length>0){const f={};for(const[b,g]of Object.entries(c))f[b]=Array.isArray(g)?qn(g):g;l.slots=f}else if(p.length===1)l.slots={[p[0]]:a};else if(p.length===0)l.slots={content:a};else{const f={};for(const[b,g]of Object.entries(c))f[b]=Array.isArray(g)?qn(g):g;l.slots=f}}else l.components=a,delete l.slots;return delete l.lock_version,delete l.__editor,l}const yd="extension",Gn="extensionless";function lm(){const e=globalThis?.G7Config;if(globalThis?.__g7AssetUrlMode===Gn)return Gn;const n=e?.assetUrlMode;return n===Gn||n===yd?n:e?.settings?.general?.asset_url_mode===Gn?Gn:yd}function dm(){return lm()===Gn}function nn(e,t,n,o){const r=e.replace(/\/+$/,""),s=t.replace(/^\.+/,""),a=dm()?r:`${r}.${s}`,l=[];return o&&l.push(o.replace(/^[?&]+/,"")),n!=null&&n!==""&&l.push(`v=${n}`),l.length>0?`${a}?${l.join("&")}`:a}const bd=/^\s*\$t:([a-zA-Z0-9._-]+)\s*$/,wr="custom.",cm=/\{\{.*?\}\}/;function um(e){return!!(!e||e.iteration&&typeof e.iteration=="object")}function md(e,t){const n=e?.text,o=typeof n=="string"?n:"";if(um(e)||typeof n!="string")return{sourceState:"plain_text",editable:!1,customKey:null,displayValue:o};const r=vt(o);if(r&&r.key.startsWith(wr)){const a=t?t(r.key):r.key;return{sourceState:"custom_key",editable:!0,customKey:r.key,displayValue:a,isParamKey:!0}}const s=bd.exec(o);if(s){const a=s[1];if(a.startsWith(wr)){const d=t?t(a):a;return{sourceState:"custom_key",editable:!0,customKey:a,displayValue:d}}const l=t?t(a):"";return{sourceState:"plain_text",editable:!0,customKey:null,displayValue:l&&l!==a?l:""}}if(cm.test(o)){const a=vr(o),l=bd.exec(a);if(l&&l[1].startsWith(wr)){const f=l[1],b=t?t(f):f;return{sourceState:"custom_key",editable:!0,customKey:f,displayValue:b}}const d=ts(o,t),c=vr(d),p=/\$t:[a-zA-Z0-9._-]+/.test(c);if(c.length>0){const f=os(o,t);return{sourceState:"plain_with_binding",editable:!0,customKey:null,displayValue:p?"":c,chipValue:f.keyifiable?f.chipValue:void 0,chipParamLabels:f.keyifiable?f.paramLabels:void 0}}return{sourceState:"binding_expression",editable:!1,customKey:null,displayValue:o}}return{sourceState:"plain_text",editable:!0,customKey:null,displayValue:o}}function pm(){const{state:e}=Qe(),t=Kt(),n=e.templateIdentifier,o=e.locale,r=e.selectedRoute?.layoutName??null,s=u.useCallback(p=>{try{const b=Pt.getInstance().translate(p,{templateId:n,locale:o});return b&&b!==p?b:""}catch{return""}},[n,o]),a=u.useCallback(p=>md(p,s),[s]),l=u.useCallback(p=>{Et({op:"inline_edit_cancel",componentPath:p?at(p):null,timestamp:Date.now()})},[]),d=u.useCallback(async(p,f)=>{if(!t)return{kind:"error",message:"no document context"};const g={children:t.document?.raw?.components??[]},m=Pe(g,p),h=md(m,s);if(!h.editable)return Et({op:"inline_edit_blocked_binding",sourceState:h.sourceState,componentPath:at(p),timestamp:Date.now()}),{kind:"blocked"};if(h.customKey){if(f===h.displayValue)return{kind:"noop",translationKey:h.customKey};$o(n,o,h.customKey,f);const C=await kr(n,h.customKey,o,f);return C.kind==="error"?{kind:"error",message:C.message}:(await on(n,o),Et({op:"inline_edit_update_value",sourceState:"custom_key",componentPath:at(p),translationKey:h.customKey,toLocale:o,valueLength:f.length,valueHash:Hi(f),timestamp:Date.now()}),{kind:"updated",translationKey:h.customKey})}const v=typeof m?.text=="string"?m.text:"",y=h.sourceState==="plain_with_binding",_=ts(v,s),w=y?od(_):f,k=C=>y?nd(C,v):C;t.patchLayout(C=>Bt({children:C},p,E=>({...E,text:f})).children??[]);const x=await Yn(n,r,o,w);if(x.kind==="error"||!x.resource)return{kind:"error",message:x.message};const $=x.resource.translation_key;return $o(n,o,$,w),t.patchLayout(C=>Bt({children:C},p,E=>({...E,text:k(`$t:${$}`)})).children??[]),await on(n,o),Et({op:"inline_edit_create_key",sourceState:h.sourceState,componentPath:at(p),translationKey:$,toLocale:o,valueLength:f.length,valueHash:Hi(f),timestamp:Date.now()}),{kind:"created",translationKey:$,id:x.resource.id}},[t,n,o,r,s]),c=u.useCallback(async(p,f)=>{if(!t)return{kind:"error",message:"no document context"};const g={children:t.document?.raw?.components??[]},m=Pe(g,p),h=typeof m?.text=="string"?m.text:"",v=vt(h);if(v&&v.key.startsWith(wr))return Sr(n,v.key,o,f),$o(n,o,v.key,f),t.markDirty?.(),{kind:"updated",translationKey:v.key};const y=os(h,s),_=vr(f.replace(/\{\{?p\d+\}?\}/g," ")).replace(/\s+/g," ").trim(),w=await Yn(n,r,o,_);if(w.kind==="error"||!w.resource)return{kind:"error",message:w.message};const k=w.resource.translation_key,x=Tn(f),$=w.resource.values??{},C={[o]:f};for(const S of Object.keys($)){if(S===o)continue;let D=$[S]??"";for(const E of x)D=es(D,E);C[S]=D}return Eo(n,k,C),$o(n,o,k,f),t.patchLayout(S=>Bt({children:S},p,I=>({...I,text:td(`$t:${k}`,y)})).children??[]),t.markDirty?.(),await on(n,o),$o(n,o,k,f),Et({op:"inline_edit_create_key",sourceState:"plain_with_binding",componentPath:at(p),translationKey:k,toLocale:o,valueLength:f.length,valueHash:Hi(f),timestamp:Date.now()}),{kind:"created",translationKey:k}},[t,n,o,r]);return{classify:a,commit:d,trackCancel:l,keyifyChipValue:c}}async function Yn(e,t,n,o){const r=`/api/admin/templates/${encodeURIComponent(e)}/custom-translations`;try{const s=await fetch(r,{method:"POST",credentials:"same-origin",headers:Me({"Content-Type":"application/json"}),body:JSON.stringify({layout_name:t,locale:n,value:o})}),a=await s.json().catch(()=>null);if(!s.ok)return{kind:"error",message:a?.message??`HTTP ${s.status}`};const l=a?.data;return!l||typeof l.translation_key!="string"?{kind:"error",message:"invalid response"}:{kind:"ok",resource:l}}catch(s){return{kind:"error",message:s instanceof Error?s.message:"network error"}}}async function kr(e,t,n,o){const r=await fn(e,t);if(!r)return{kind:"error",message:"custom key not found"};const s=`/api/admin/templates/${encodeURIComponent(e)}/custom-translations/${r.id}`,a={...r.values??{},[n]:o};try{const l=await fetch(s,{method:"PUT",credentials:"same-origin",headers:Me({"Content-Type":"application/json"}),body:JSON.stringify({values:a,expected_lock_version:r.lock_version})}),d=await l.json().catch(()=>null);return l.status===409?{kind:"error",message:"concurrent_modification"}:l.ok?{kind:"ok"}:{kind:"error",message:d?.message??`HTTP ${l.status}`}}catch(l){return{kind:"error",message:l instanceof Error?l.message:"network error"}}}async function fn(e,t){const n=`/api/admin/templates/${encodeURIComponent(e)}/custom-translations`;try{const o=await fetch(n,{method:"GET",credentials:"same-origin",headers:Me()});if(!o.ok)return null;const s=(await o.json().catch(()=>null))?.data;return Array.isArray(s)?s.find(a=>a.translation_key===t)??null:null}catch{return null}}function $o(e,t,n,o){try{Pt.getInstance().setTranslationValue(e,t,n,o)}catch{}if(typeof window<"u")try{window.dispatchEvent(new CustomEvent(kt,{detail:{templateIdentifier:e,locale:t}}))}catch{}}const kt="g7le:translations-refreshed";async function on(e,t){if(typeof window>"u")return;const n=t??window.__g7EditorContentLocale;if(n){try{const o=Pt.getInstance();try{const r=await fetch(nn(`/api/templates/${encodeURIComponent(e)}/config`,"json"),{headers:{Accept:"application/json"},credentials:"same-origin"}).then(a=>a.ok?a.json():null),s=r?.data?.cache_version??r?.cache_version;if(typeof s=="number"&&s>0){o.setCacheVersion(s);const a=window;a.G7Config&&(a.G7Config.cache_version=s)}}catch{}await o.loadTranslations(e,n,"/api",!0)}catch{}try{window.dispatchEvent(new CustomEvent(kt,{detail:{templateIdentifier:e,locale:n}}))}catch{}}}const rn=new Map;function Sr(e,t,n,o){const r=rn.get(t)??{};r[n]=o,rn.set(t,r);try{Pt.getInstance().setTranslationValue(e,n,t,o)}catch{}if(typeof window<"u")try{window.dispatchEvent(new CustomEvent(kt,{detail:{templateIdentifier:e,locale:n}}))}catch{}}function Eo(e,t,n){for(const[o,r]of Object.entries(n))Sr(e,t,o,r)}function It(e,t){return rn.get(e)?.[t]}function fm(e){const t=rn.get(e);return t?{...t}:void 0}function gm(){return rn.size>0}function vd(e,t){for(const[n,o]of rn.entries())for(const[r,s]of Object.entries(o))try{e.setTranslationValue(t,r,n,s)}catch{}}async function hm(e){if(rn.size===0)return{ok:0,failed:[]};const t=Array.from(rn.entries());let n=0;const o=[];for(const[r,s]of t)try{const a=await fn(e,r);if(!a){o.push(r);continue}const l={...a.values??{},...s},d=`/api/admin/templates/${encodeURIComponent(e)}/custom-translations/${a.id}`;if(!(await fetch(d,{method:"PUT",credentials:"same-origin",headers:Me({"Content-Type":"application/json"}),body:JSON.stringify({values:l,expected_lock_version:a.lock_version})})).ok){o.push(r);continue}n+=1}catch{o.push(r)}for(const[r]of t)o.includes(r)||rn.delete(r);try{await on(e)}catch{}return{ok:n,failed:o}}function Xn(e,t){const n=`layout_editor.locale.${e}`,o=t(n);return o&&o!==n?o:e.toUpperCase()}function _d(){if(typeof window>"u")return[];const t=window.G7Config?.appConfig?.supportedLocales;return Array.isArray(t)?t.filter(n=>typeof n=="string"&&n.length>0):[]}function ym({locales:e}={}){const{t}=et(),{state:n,dispatch:o}=Qe();u.useEffect(()=>{window.__g7EditorContentLocale=n.locale},[n.locale]);const r=e??_d(),s=r.includes(n.locale)?r:[n.locale,...r];if(s.length<=1)return i.jsxs("span",{className:"g7le-locale-switcher g7le-locale-switcher--single","data-testid":"g7le-locale-switcher-single",style:{fontSize:11,color:"#475569",padding:"0 4px"},children:[t("layout_editor.chrome.toolbar.locale_switcher_label"),": ",Xn(n.locale,t)]});const a=l=>{if(l===n.locale)return;const d=n.locale;o({type:"SET_LOCALE",locale:l}),Et({op:"locale_switch",fromLocale:d,toLocale:l,timestamp:Date.now()})};return i.jsxs("div",{className:"g7le-locale-switcher","data-testid":"g7le-locale-switcher",role:"group","aria-label":t("layout_editor.chrome.toolbar.locale_switcher_label"),style:{display:"inline-flex",alignItems:"center",gap:4},children:[i.jsx("span",{style:{fontSize:11,color:"#475569"},children:t("layout_editor.chrome.toolbar.locale_switcher_label")}),i.jsx("div",{style:{display:"inline-flex",gap:2},children:s.map(l=>{const d=l===n.locale;return i.jsx("button",{type:"button","data-testid":`g7le-locale-${l}`,"aria-pressed":d,onClick:()=>a(l),style:{padding:"2px 8px",fontSize:12,cursor:"pointer",background:d?"#0f172a":"#f8fafc",color:d?"#fff":"#0f172a",border:"1px solid #cbd5e1",borderRadius:4},children:Xn(l,t)},l)})})]})}const bm=/\{\{?(p\d+)\}?\}/g;function mm(e){const t=[];if(typeof e!="string"||e.length===0)return t;const n=new RegExp(bm.source,"g");let o=0,r;for(;(r=n.exec(e))!==null;){const s=r.index,a=s+r[0].length;s>o&&t.push({kind:"text",raw:e.slice(o,s),start:o,end:s}),t.push({kind:"chip",raw:r[0],paramName:r[1],start:s,end:a}),o=a}return o{n.push({kind:"text",text:a}),r=!0};for(const a of t)a.kind==="text"?(o=a.raw,s(o),o=""):(r||s(""),n.push({kind:"chip",paramName:a.paramName}),r=!1);return r||s(""),n}function _m(e,t,n,o){const r=[];return e.forEach((s,a)=>{if(s.kind==="chip"){if(s.paramName===t)return;r.push(`{${s.paramName}}`)}else{const l=s.text??"";if(a===n){const d=Math.max(0,Math.min(o,l.length));r.push(l.slice(0,d),`{${t}}`,l.slice(d))}else r.push(l)}}),r.join("")}function Cr({value:e,onChange:t,t:n,paramLabels:o,onRequestInsert:r,onRemoveChip:s,testIdSuffix:a="pc",disabled:l=!1,hideInsertButton:d=!1,caretRef:c}){const p=u.useMemo(()=>vm(e),[e]),f=u.useRef(null),[b,g]=u.useState(null),[m,h]=u.useState(null),[v,y]=u.useState(null),_=u.useMemo(()=>p.map(B=>B.kind==="chip"?`c:${B.paramName}`:"t").join("|"),[p]),w=u.useRef(null),k=u.useRef([]),x=u.useRef(!1);u.useLayoutEffect(()=>{const B=x.current;x.current=!1,!(!B&&w.current===_)&&(w.current=_,p.forEach((z,H)=>{if(z.kind!=="text")return;const P=k.current[H];P&&P.textContent!==(z.text??"")&&(P.textContent=z.text??"")}))},[_,p,e]);const $=()=>{const B=f.current;if(!B)return e;const z=[];for(const H of Array.from(B.querySelectorAll("[data-seg]")))H.dataset.seg==="chip"?z.push(`{${H.dataset.param??""}}`):z.push(H.textContent??"");return z.join("")},C=()=>{l||t($())},S=u.useRef(null),D=u.useRef(!1),E=(B,z)=>{if(!l){B.preventDefault(),B.stopPropagation(),S.current={param:z,pointerId:B.pointerId},D.current=!1,g(z);try{B.currentTarget.setPointerCapture(B.pointerId)}catch{}}},I=B=>{const z=S.current;if(!z||B.pointerId!==z.pointerId)return;D.current=!0;const H=O(B.clientX,B.clientY);H&&(h({slotIdx:H.slotIdx,offset:H.offset}),y(wm(f.current,H.slotIdx,H.offset)))},N=(B,z)=>{if(B.stopPropagation(),D.current){D.current=!1;return}s?.(z)},T=B=>{const z=S.current;if(!z||B.pointerId!==z.pointerId)return;try{B.currentTarget.releasePointerCapture(B.pointerId)}catch{}S.current=null,D.current&&typeof setTimeout=="function"&&setTimeout(()=>{D.current=!1},0);const H=O(B.clientX,B.clientY);if(g(null),h(null),y(null),!H)return;const P=f.current;if(!P)return;const L=Array.from(P.querySelectorAll("[data-seg]")).map(Y=>Y.dataset.seg==="chip"?{kind:"chip",paramName:Y.dataset.param??""}:{kind:"text",text:Y.textContent??""}),F=_m(L,z.param,H.slotIdx,H.offset);F!==e&&(x.current=!0,t(F))},O=(B,z)=>{const H=f.current;if(!H)return null;const P=Array.from(H.querySelectorAll("[data-seg]")),A=H.ownerDocument,L=A.elementFromPoint(B,z);let F=L?.closest('[data-seg="text"]');if(F)return{slotIdx:P.indexOf(F),offset:xm(A,F,B,z)};const Y=L?.closest('[data-seg="chip"]');if(Y){const X=P.indexOf(Y),Z=Y.getBoundingClientRect();if(B=0){const ie=P[X-1];return{slotIdx:X-1,offset:(ie.textContent??"").length}}if(X+1({s:X,i:Z})).filter(X=>X.s.dataset.seg==="text").pop();return V?{slotIdx:V.i,offset:(V.s.textContent??"").length}:null},M=()=>{l||!r||r(xd(f.current,e.length))};return u.useEffect(()=>{if(c)return c.current=()=>xd(f.current,e.length),()=>{c.current=null}},[c,e.length]),i.jsxs("div",{style:{display:"flex",flexDirection:"column",gap:4},children:[i.jsxs("div",{ref:f,"data-testid":`g7le-chip-input-${a}`,className:"g7le-chip-input",style:km,children:[p.map((B,z)=>B.kind==="chip"?i.jsxs("span",{"data-seg":"chip","data-param":B.paramName,"data-testid":`g7le-chip-${a}-${B.paramName}`,contentEditable:!1,onPointerDown:H=>E(H,B.paramName??""),onPointerMove:I,onPointerUp:T,title:n("layout_editor.translation.placeholder_locked"),style:{...Em,...b===B.paramName?Im:null,touchAction:"none"},children:[i.jsx("span",{"aria-hidden":"true",style:Am,children:"⠿"}),i.jsx("span",{"aria-hidden":"true",style:{marginRight:3},children:"🔗"}),i.jsx("span",{style:Tm,children:o?.[B.paramName??""]??B.paramName}),s&&!l&&i.jsx("button",{type:"button","data-testid":`g7le-chip-remove-${a}-${B.paramName}`,"aria-label":n("layout_editor.inline_binding.clear"),title:n("layout_editor.inline_binding.clear"),onPointerDown:H=>{H.stopPropagation(),H.preventDefault()},onClick:H=>N(H,B.paramName??""),style:Dm,children:"✕"})]},`chip-${B.paramName}`):i.jsx("span",{ref:H=>{k.current[z]=H},"data-seg":"text",contentEditable:!l,suppressContentEditableWarning:!0,onInput:C,"data-testid":`g7le-chip-text-${a}-${z}`,style:{...Sm,...b?Cm:null,...m?.slotIdx===z?$m:null}},`text-slot-${z}`)),v&&i.jsx("span",{"aria-hidden":"true","data-testid":`g7le-chip-caret-${a}`,style:{position:"absolute",left:v.left,top:v.top,width:2,height:v.height,background:"#2563eb",borderRadius:1,pointerEvents:"none",boxShadow:"0 0 0 1px rgba(37,99,235,0.3)"}})]}),i.jsxs("div",{style:{display:"flex",alignItems:"center",gap:6},children:[r&&!d&&i.jsxs("button",{type:"button","data-testid":`g7le-chip-insert-${a}`,onClick:M,disabled:l,style:zm,children:["+ ",n("layout_editor.inline_binding.insert_data")]}),i.jsx("span",{style:Nm,children:n("layout_editor.translation.placeholder_locked")})]})]})}function xm(e,t,n,o){const r=(t.textContent??"").length;try{const a=e;let l=null,d=0;if(typeof a.caretRangeFromPoint=="function"){const c=a.caretRangeFromPoint(n,o);c&&(l=c.startContainer,d=c.startOffset)}else if(typeof a.caretPositionFromPoint=="function"){const c=a.caretPositionFromPoint(n,o);c&&(l=c.offsetNode,d=c.offset)}if(l&&t.contains(l))return Math.max(0,Math.min(d,r))}catch{}const s=t.getBoundingClientRect();return n>s.left+s.width/2?r:0}function wm(e,t,n){if(!e)return null;const r=Array.from(e.querySelectorAll("[data-seg]"))[t];if(!r)return null;const s=e.getBoundingClientRect();try{const d=r.firstChild,c=e.ownerDocument;if(d&&d.nodeType===Node.TEXT_NODE&&(d.textContent??"").length>0){const p=c.createRange(),f=Math.max(0,Math.min(n,(d.textContent??"").length));p.setStart(d,f),p.setEnd(d,f);const b=p.getClientRects(),g=b.length>0?b[0]:p.getBoundingClientRect();if(g&&(g.height>0||g.width>=0))return{left:g.left-s.left,top:g.top-s.top,height:g.height||r.getBoundingClientRect().height}}}catch{}const a=r.getBoundingClientRect();return{left:(n>0?a.right:a.left)-s.left,top:a.top-s.top,height:a.height||16}}function xd(e,t){if(!e)return t;try{const n=e.ownerDocument.defaultView?.getSelection();if(!n||n.rangeCount===0)return t;const o=n.getRangeAt(0);let r=o.startContainer,s=null;for(;r&&r!==e;){if(r instanceof HTMLElement&&r.dataset.seg==="text"){s=r;break}r=r.parentNode}if(!s||!e.contains(s))return t;const a=Array.from(e.querySelectorAll("[data-seg]"));let l=0;for(const d of a){if(d===s)break;l+=d.dataset.seg==="chip"?`{${d.dataset.param??""}}`.length:(d.textContent??"").length}return l+o.startOffset}catch{return t}}const km={position:"relative",display:"flex",flexWrap:"wrap",alignItems:"center",gap:2,minHeight:30,padding:"4px 8px",border:"1px solid #cbd5e1",borderRadius:4,background:"#fff",fontSize:13,lineHeight:1.6},Sm={outline:"none",minWidth:8,minHeight:"1.2em",whiteSpace:"pre-wrap",color:"#0f172a",display:"inline-block"},Cm={boxShadow:"inset 0 -2px 0 #93c5fd",background:"#eff6ff",borderRadius:2},$m={background:"#dbeafe",boxShadow:"inset 0 -2px 0 #2563eb"},Em={display:"inline-flex",alignItems:"center",gap:1,background:"linear-gradient(180deg,#eff6ff,#dbeafe)",color:"#1d4ed8",border:"1px dashed #60a5fa",borderRadius:9999,padding:"1px 8px 1px 4px",margin:"0 1px",fontSize:12,fontWeight:600,cursor:"grab",userSelect:"none",whiteSpace:"nowrap",boxShadow:"0 1px 2px rgba(37,99,235,0.18)",verticalAlign:"middle"},Im={opacity:.5,borderStyle:"solid",cursor:"grabbing"},Am={color:"#2563eb",opacity:.7,fontSize:11,marginRight:1,cursor:"grab"},Tm={textDecoration:"none"},Dm={marginLeft:4,width:14,height:14,lineHeight:"12px",padding:0,border:"none",borderRadius:9999,background:"rgba(29,78,216,0.12)",color:"#1d4ed8",fontSize:9,fontWeight:700,cursor:"pointer",display:"inline-flex",alignItems:"center",justifyContent:"center",flex:"0 0 auto"},zm={fontSize:11,border:"1px solid #cbd5e1",borderRadius:6,background:"#f8fafc",color:"#334155",padding:"3px 10px",cursor:"pointer"},Nm={fontSize:10,color:"#94a3b8"};function wd(e){e&&(e.style.height="auto",e.style.height=`${Math.max(e.scrollHeight,28)}px`)}function Mm(e){wd(e)}function Lm(e){const t=new Set;for(const n of e)for(const o of Object.keys(n.values??{}))t.add(o);return Array.from(t).sort()}function Pm({templateIdentifier:e,layoutName:t,t:n,referencedKeys:o,onClose:r}){const[s,a]=u.useState([]),[l,d]=u.useState("idle"),[c,p]=u.useState(null),[f,b]=u.useState("all"),[g,m]=u.useState(new Set),[h,v]=u.useState({}),[y,_]=u.useState(null),[w,k]=u.useState({}),x=u.useCallback(async()=>{d("loading"),p(null);const z=await cb(e,t);z.ok?(a(z.data),v({}),m(new Set),k({}),d("idle")):(p(z.message),d("error"))},[e,t]);u.useEffect(()=>{x()},[x]);const $=u.useMemo(()=>Lm(s),[s]),C=u.useMemo(()=>s.filter(z=>f==="all"?!0:z.status===f).slice().sort((z,H)=>{const P=z.updated_at?Date.parse(z.updated_at):0,A=H.updated_at?Date.parse(H.updated_at):0;return A!==P?A-P:H.id-z.id}),[s,f]),S=u.useMemo(()=>s.filter(z=>z.status==="orphaned"),[s]),D=u.useCallback(z=>{const H={...z.values??{}};for(const P of Object.keys(H)){const A=It(z.translation_key,P);A!==void 0&&(H[P]=A)}return H},[]),E=u.useCallback(z=>h[z.id]??D(z),[h,D]),I=u.useCallback((z,H,P)=>{v(A=>{const L=A[z]??s.find(F=>F.id===z)?.values??{};return{...A,[z]:{...L,[H]:P}}})},[s]),N=u.useCallback(async z=>{_(z.id),k(A=>({...A,[z.id]:""}));const H=E(z),P=await ub(e,z.id,H,z.lock_version);if(P.ok)a(A=>A.map(L=>L.id===z.id?P.data:L)),v(A=>{const L={...A};return delete L[z.id],L});else{const A=P.status===409?n("layout_editor.translation_manager.conflict"):P.message;k(L=>({...L,[z.id]:A}))}_(null)},[e,E,n]),T=u.useCallback(z=>typeof window<"u"&&typeof window.confirm=="function"?window.confirm(n("layout_editor.translation_manager.delete_confirm",{count:z})):!0,[n]),O=u.useCallback(async z=>{if(!T(1))return;const H=await pb(e,z.id);H.ok?(a(P=>P.filter(A=>A.id!==z.id)),m(P=>{const A=new Set(P);return A.delete(z.id),A})):p(H.message)},[e,T]),M=u.useCallback(async z=>{if(z.length===0||!T(z.length))return;const H=await fb(e,z);if(H.ok){const P=new Set(z);a(A=>A.filter(L=>!P.has(L.id))),m(new Set)}else p(H.message)},[e,T]),B=u.useCallback(z=>{m(H=>{const P=new Set(H);return P.has(z)?P.delete(z):P.add(z),P})},[]);return i.jsxs("div",{className:"g7le-translation-manager","data-testid":"g7le-translation-manager",style:Om,children:[i.jsxs("div",{"data-modal-drag-handle":!0,style:Bm,children:[i.jsx("span",{style:{fontSize:14,fontWeight:700},"data-testid":"g7le-translation-manager-title",children:n("layout_editor.translation_manager.title")}),i.jsx("button",{type:"button","aria-label":"close",onClick:r,style:Wm,"data-testid":"g7le-translation-manager-close",children:"✕"})]}),i.jsxs("div",{style:jm,children:[i.jsx("div",{style:{display:"inline-flex",gap:4},role:"tablist","aria-label":n("layout_editor.translation_manager.filter_label"),children:["all","active","orphaned"].map(z=>i.jsx("button",{type:"button",role:"tab","aria-selected":f===z,"data-active":f===z?"true":"false",onClick:()=>b(z),style:f===z?Hm:kd,"data-testid":`g7le-translation-filter-${z}`,children:n(`layout_editor.translation_manager.filter_${z}`)},z))}),i.jsx("div",{style:{flex:1}}),g.size>0&&i.jsxs("button",{type:"button",onClick:()=>void M(Array.from(g)),style:Sd,"data-testid":"g7le-translation-bulk-delete",children:["🗑 ",n("layout_editor.translation_manager.delete_selected",{count:g.size})]}),S.length>0&&i.jsxs("button",{type:"button",onClick:()=>void M(S.map(z=>z.id)),style:Sd,"data-testid":"g7le-translation-purge-orphaned",children:["🧹 ",n("layout_editor.translation_manager.purge_orphaned",{count:S.length})]})]}),c&&i.jsx("div",{style:Fm,"data-testid":"g7le-translation-manager-error",children:c}),i.jsx("div",{style:Km,children:l==="loading"?i.jsx("div",{style:Cd,"data-testid":"g7le-translation-manager-loading",children:n("layout_editor.translation_manager.loading")}):C.length===0&&l!=="error"?i.jsx("div",{style:Cd,"data-testid":"g7le-translation-manager-empty",children:n("layout_editor.translation_manager.empty")}):i.jsx("div",{"data-testid":"g7le-translation-list",style:Vm,children:C.map(z=>{const H=E(z),P=z.status==="orphaned",A=o?o.has(z.translation_key):null;return i.jsxs("div",{style:Um,"data-testid":`g7le-translation-row-${z.id}`,children:[i.jsxs("div",{style:qm,children:[i.jsx("input",{type:"checkbox",checked:g.has(z.id),onChange:()=>B(z.id),"aria-label":n("layout_editor.translation_manager.select_aria",{key:z.translation_key}),"data-testid":`g7le-translation-select-${z.id}`}),i.jsx("code",{style:Gm,children:z.translation_key}),P&&i.jsx("span",{style:Ym,"data-testid":`g7le-translation-badge-orphaned-${z.id}`,children:n("layout_editor.translation_manager.badge_orphaned")}),A===!0&&i.jsx("span",{style:Xm,"data-testid":`g7le-translation-badge-live-inuse-${z.id}`,children:n("layout_editor.translation_manager.badge_live_in_use")}),A===!1&&i.jsx("span",{style:Jm,"data-testid":`g7le-translation-badge-live-unused-${z.id}`,children:n("layout_editor.translation_manager.badge_live_unused")}),i.jsx("div",{style:{flex:1}}),i.jsx("button",{type:"button",onClick:()=>void O(z),style:ov,"data-testid":`g7le-translation-delete-${z.id}`,children:"🗑"})]}),i.jsxs("div",{style:Zm,children:[$.map(L=>{const F=H[L]??"",Y=Tn(F).length>0;return i.jsxs("div",{style:Qm,"data-testid":`g7le-translation-field-${z.id}-${L}`,children:[i.jsxs("label",{style:Rm,title:L,children:[Xn(L,n),i.jsxs("span",{style:ev,children:["(",L,")"]})]}),Y?i.jsx("div",{style:{flex:1},"data-testid":`g7le-translation-chip-${z.id}-${L}`,children:i.jsx(Cr,{value:F,onChange:V=>I(z.id,L,V),t:n,testIdSuffix:`${z.id}-${L}`})}):i.jsx("textarea",{value:F,rows:1,ref:Mm,onChange:V=>{I(z.id,L,V.currentTarget.value),wd(V.currentTarget)},style:tv,"data-testid":`g7le-translation-input-${z.id}-${L}`})]},L)}),i.jsxs("div",{style:{display:"flex",alignItems:"center",gap:8},children:[i.jsx("button",{type:"button",disabled:y===z.id,onClick:()=>void N(z),style:nv,"data-testid":`g7le-translation-save-${z.id}`,children:y===z.id?n("layout_editor.translation_manager.saving"):n("layout_editor.translation_manager.save")}),w[z.id]?i.jsx("span",{style:{fontSize:11,color:"#dc2626"},"data-testid":`g7le-translation-row-error-${z.id}`,children:w[z.id]}):null]})]})]},z.id)})})}),i.jsx("div",{style:rv,children:i.jsx("button",{type:"button",onClick:r,style:iv,"data-testid":"g7le-translation-manager-footer-close",children:n("layout_editor.translation_manager.close")})})]})}const Om={display:"flex",flexDirection:"column",minHeight:0,flex:1},Bm={display:"flex",alignItems:"center",justifyContent:"space-between",padding:"12px 16px",borderBottom:"1px solid #e2e8f0"},Wm={border:"none",background:"transparent",fontSize:16,color:"#94a3b8",cursor:"pointer"},jm={padding:"10px 16px",borderBottom:"1px solid #f1f5f9",display:"flex",gap:8,alignItems:"center"},kd={padding:"4px 10px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",color:"#475569",cursor:"pointer"},Hm={...kd,borderColor:"#2563eb",background:"#eff6ff",color:"#1d4ed8",fontWeight:600},Sd={padding:"4px 10px",fontSize:12,border:"1px solid #fecaca",borderRadius:6,background:"#fff",color:"#dc2626",cursor:"pointer"},Fm={padding:"8px 16px",fontSize:12,color:"#b91c1c",background:"#fef2f2"},Km={padding:16,overflow:"auto",flex:1,minHeight:120,display:"flex",flexDirection:"column",gap:12},Cd={fontSize:13,color:"#94a3b8",textAlign:"center",padding:"32px 0"},Vm={display:"flex",flexDirection:"column",gap:20},Um={border:"1px solid #e2e8f0",borderRadius:8,padding:10,display:"flex",flexDirection:"column",gap:6},qm={display:"flex",alignItems:"center",gap:8},Gm={fontSize:11,background:"#f1f5f9",padding:"2px 6px",borderRadius:4,color:"#0f172a"},Ym={fontSize:10,color:"#b45309",background:"#fef3c7",padding:"1px 6px",borderRadius:4,fontWeight:600},Xm={fontSize:10,color:"#15803d",background:"#dcfce7",padding:"1px 6px",borderRadius:4,fontWeight:600},Jm={fontSize:10,color:"#9f1239",background:"#ffe4e6",padding:"1px 6px",borderRadius:4,fontWeight:600},Zm={display:"flex",flexDirection:"column",gap:6},Qm={display:"flex",alignItems:"flex-start",gap:8},Rm={fontSize:12,color:"#475569",minWidth:88,fontWeight:600,paddingTop:4,display:"flex",flexDirection:"column",lineHeight:1.2},ev={fontSize:10,color:"#94a3b8",fontWeight:400},tv={flex:1,padding:"4px 8px",fontSize:13,border:"1px solid #cbd5e1",borderRadius:4,color:"#0f172a",resize:"vertical",overflow:"hidden",minHeight:28,lineHeight:1.4,fontFamily:"inherit",boxSizing:"border-box"},nv={padding:"5px 12px",fontSize:12,border:"none",borderRadius:6,background:"#2563eb",color:"#fff",cursor:"pointer"},ov={padding:"4px 8px",fontSize:12,border:"1px solid #fecaca",borderRadius:6,background:"#fff",color:"#dc2626",cursor:"pointer"},rv={padding:"10px 16px",borderTop:"1px solid #e2e8f0",display:"flex",justifyContent:"flex-end"},iv={padding:"6px 14px",fontSize:13,border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",color:"#475569",cursor:"pointer"};function sv(){const e=Kt(),{state:t}=Qe(),n=e?.document?.raw??null,o=t.templateIdentifier??"",r=t.selectedRoute?.layoutName??"",s=u.useCallback((c,p)=>{const f=n?.[c];return f===void 0?p:f},[n]),a=u.useCallback((c,p,...f)=>{e&&(f.length>=1?e.patchDocumentRaw(c,p,f[0]):e.patchDocumentRaw(c,p))},[e]),l=u.useCallback(async(c,p)=>!o||!r?null:Yn(o,r,c,p),[o,r]),d=u.useCallback(async(c,p,f)=>o?kr(o,c,p,f):null,[o]);return{raw:n,getValue:s,patch:a,createI18nKey:l,updateI18nKeyValue:d}}function av(e){const t=e.__source;return t&&(t.kind==="module"||t.kind==="plugin")&&t.identifier?`${t.kind}:${t.identifier}`:"template"}function $d(e,t){if(!t)return null;const n=av(e);if(n&&t.bySource){const a=t.bySource[n]?.[e.id];if(a!==void 0)return{value:a,match:{dataSourceId:e.id,resolvedBy:"spec_byId",sourceKey:n}}}const o=t.byDataSourceId?.[e.id];if(o!==void 0)return{value:o,match:{dataSourceId:e.id,resolvedBy:"spec_byId"}};const r=e.endpoint??"";if(t.byEndpointPattern&&typeof r=="string"&&r.length>0){for(const[s,a]of Object.entries(t.byEndpointPattern))if(lv(r,s))return{value:a,match:{dataSourceId:e.id,resolvedBy:"spec_byEndpoint"}}}return null}function ds(e,t){const n=$d(e,t);if(n)return n;const o=e.fallback?.data;return o!==void 0?{value:{data:Array.isArray(o)?o:[o]},match:{dataSourceId:e.id,resolvedBy:"fallback"}}:{value:{},match:{dataSourceId:e.id,resolvedBy:"inferred"}}}function lv(e,t){const n=t.replace(/[.+?^${}()|[\]\\]/g,"\\$&").replace(/\*/g,".*");return new RegExp(`^${n}$`).test(e)}function dv(e={}){const{spec:t,overrideSpec:n,onMatch:o}=e;return{has(r){return!0},resolve(r){const s=n?$d(r,n):null,{value:a,match:l}=s??ds(r,t);if(o)try{o(l)}catch{}return a}}}const Ed=Lt(),Id=Ed.DataBindingEngine,Ad=Ed.dataBindingEngine;wo("PipeRegistry");function cv(e){let t=null,n=0;for(let o=0;o0?e[o-1]:"",a=o{const a=t[r],l=a==null?[]:String(a).split(",").map(d=>d.trim()).filter(Boolean);return l.length===0?"":l.join(" ?? ")+(s?" ?? ":"")});return n=n.replace(/\{(\w+)\}/g,(o,r)=>{const s=t[r];return s===void 0?o:s}),n}function $r(e){const t=e.trim(),n=/^\{\{([\s\S]*)\}\}$/.exec(t);return n?n[1].trim():t}function Dd(e){const t=e.trim();return/^\{\{[\s\S]*\}\}$/.test(t)?t:`{{ ${t} }}`}function zd(e,t){const n={};for(const[r,s]of Object.entries(t))n[r]=typeof s=="string"?$r(s):s;const o=pv(e.expr,n);return Dd(o)}function fv(e,t){if(typeof e!="string")return null;const n=Nd($r(e));for(const o of t){const r=gv(n,o.expr,o.params);if(r)return{recipeId:o.id,params:r}}return null}function Nd(e){return e.replace(/\s+/g," ").trim()}function Md(e){return e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}function gv(e,t,n){const o=[],r=new Set;let s="";const a=/\{(\w+)(\*?)\}(\s*\?\?\s*)?/g;let l=0,d;const c=Nd(t);for(;(d=a.exec(c))!==null;){const g=c.slice(l,d.index);s+=Md(g).replace(/\\ /g,"\\s*");const m=d[2]==="*",h=!!d[3];m?(r.add(d[1]),s+="(.*?)",h&&(s+="(?:\\s*\\?\\?\\s*)?")):(s+="(.+?)",h&&(s+="\\s*\\?\\?\\s*")),o.push(d[1]),l=a.lastIndex}s+=Md(c.slice(l)).replace(/\\ /g,"\\s*");let p;try{p=new RegExp(`^${s}$`)}catch{return null}const f=p.exec(e);if(!f)return null;const b={};if(o.forEach((g,m)=>{let h=(f[m+1]??"").trim();r.has(g)&&(h=h.split(/\s*\?\?\s*/).map(v=>v.trim()).filter(Boolean).join(", ")),b[g]=h}),n.length>0){const g=new Set(n.map(m=>m.key));for(const m of Object.keys(b))g.has(m)||delete b[m]}return b}function hv(e){const t=`x.${e.field}`,n=yv(e.value);switch(e.cmp){case"=":return`${t} === ${n}`;case"!=":return`${t} !== ${n}`;case">":return`${t} > ${n}`;case"<":return`${t} < ${n}`;case">=":return`${t} >= ${n}`;case"<=":return`${t} <= ${n}`;case"includes":return`(${t} ?? []).includes(${n})`;default:return`${t} === ${n}`}}function yv(e){const t=e.trim();return t==="true"||t==="false"||t!==""&&!Number.isNaN(Number(t))||/^['"].*['"]$/.test(t)||/[.([{]/.test(t)?t:`'${t.replace(/'/g,"\\'")}'`}function cs(e){return!e||e.length===0?"true":e.map(hv).join(" && ")}function bv(e){const n=`(${(e.source??"").trim()||"[]"} ?? [])`;let o;switch(e.op){case"count":o=`${n}.filter(x => ${cs(e.conditions)}).length`;break;case"sum":{const r=(e.sumField??"").trim()||"value";o=`${n}.filter(x => ${cs(e.conditions)}).reduce((s, x) => s + (x.${r} ?? 0), 0)`;break}case"filter":o=`${n}.filter(x => ${cs(e.conditions)})`;break;case"toOptions":{const r=(e.valueField??"").trim()||"value",s=(e.labelField??"").trim()||"label";o=`${n}.map(x => ({ value: x.${r}, label: x.${s} }))`;break}case"nth":{const r=(e.index??"").trim()||"0",s=(e.prop??"").trim();o=`${n}[${r}]${s?`?.${s}`:""}`;break}case"firstOf":{const r=(e.candidates??[]).map(l=>l.trim()).filter(Boolean),s=r.length>0?r.join(" ?? "):"''",a=(e.fallback??"").trim();o=a?`${s} ?? '${a.replace(/'/g,"\\'")}'`:s;break}case"literal":{const r=e.literalKind??"string",s=(e.literalValue??"").trim();r==="number"?o=s===""?"0":s:r==="boolean"?o=s==="true"?"true":"false":o=`'${s.replace(/'/g,"\\'")}'`;break}default:o="[]"}return Dd(o)}function mv(e){if(typeof e!="string")return null;const t=$r(e).trim();let n=/^\((.+?)\s*\?\?\s*\[\]\)\.filter\(x => (.+)\)\.length$/.exec(t);return n?{key:"",op:"count",source:n[1].trim(),conditions:us(n[2])}:(n=/^\((.+?)\s*\?\?\s*\[\]\)\.filter\(x => (.+)\)\.reduce\(\(s, x\) => s \+ \(x\.(\w+(?:\.\w+)*) \?\? 0\), 0\)$/.exec(t),n?{key:"",op:"sum",source:n[1].trim(),conditions:us(n[2]),sumField:n[3]}:(n=/^\((.+?)\s*\?\?\s*\[\]\)\.filter\(x => (.+)\)$/.exec(t),n?{key:"",op:"filter",source:n[1].trim(),conditions:us(n[2])}:(n=/^\((.+?)\s*\?\?\s*\[\]\)\.map\(x => \(\{ value: x\.(\w+(?:\.\w+)*), label: x\.(\w+(?:\.\w+)*) \}\)\)$/.exec(t),n?{key:"",op:"toOptions",source:n[1].trim(),valueField:n[2],labelField:n[3]}:(n=/^\((.+?)\s*\?\?\s*\[\]\)\[(.+?)\](?:\?\.(\w+(?:\.\w+)*))?$/.exec(t),n?{key:"",op:"nth",source:n[1].trim(),index:n[2].trim(),prop:n[3]}:null))))}function us(e){const t=e.trim();if(t==="true")return[];const n=[];for(const o of vv(t)){const r=_v(o.trim());r&&n.push(r)}return n}function vv(e){const t=[];let n=0,o=0;for(let r=0;r=|<=|>|<)\s*(.+)$/.exec(e),t){const n={"===":"=","!==":"!=",">":">","<":"<",">=":">=","<=":"<="};return{field:t[1],cmp:n[t[2]]??"=",value:Ld(t[3])}}return null}function Ld(e){const t=e.trim(),n=/^'(.*)'$/.exec(t)||/^"(.*)"$/.exec(t);return n?n[1]:t}function Pd(e,t){const n=fv(e,t);if(n)return{kind:"preset",recipeId:n.recipeId,params:n.params};const o=mv(e);return o?{kind:"custom",model:o}:{kind:"advanced"}}function xv(e){if(e==null)return"null";if(Array.isArray(e))return"list";switch(typeof e){case"number":return"number";case"boolean":return"boolean";case"string":return"string";default:return"object"}}function Od(e,t){if(typeof e!="string"||e.trim()==="")return{ok:!1,reason:"empty"};const n=$r(e);try{const o=cv(n)?Ad.evaluatePipeExpression(n,t??{},{skipCache:!0}):Ad.evaluateExpression(n,t??{},{skipCache:!0});return{ok:!0,value:o,type:xv(o)}}catch(o){return{ok:!1,reason:o instanceof Error?o.message:String(o)}}}function Bd(e,t){const n=Array.isArray(e?.data_sources)?e.data_sources:[],o=[];for(const r of n){const s=r?.id;if(typeof s!="string"||s.length===0)continue;const{value:a}=ds(r,t?.sampleData),l=typeof r.label_key=="string"?r.label_key:void 0;o.push({id:s,sample:a,labelKey:l})}return o}function Wd(e,t){const n=[];t?.sampleGlobal&&typeof t.sampleGlobal=="object"&&n.push({scope:"_global",tree:t.sampleGlobal});const o={},r={},s={},a=(l,d)=>{if(!(!d||typeof d!="object"||Array.isArray(d)))for(const[c,p]of Object.entries(d))if(p&&typeof p=="object"&&!Array.isArray(p)){const f=l[c]&&typeof l[c]=="object"&&!Array.isArray(l[c])?l[c]:l[c]={};a(f,p)}else c in l||(l[c]=p)};for(const l of t?.states?.groups??[])for(const d of l.items??[]){const c=d.initialState;c?.local&&a(o,c.local),c?.query&&a(r,c.query),c?.route&&a(s,c.route)}return Object.keys(o).length&&n.push({scope:"_local",tree:o}),Object.keys(r).length&&n.push({scope:"query",tree:r}),Object.keys(s).length&&n.push({scope:"route",tree:s}),e?.computed&&typeof e.computed=="object"&&!Array.isArray(e.computed)&&n.push({scope:"_computed",tree:e.computed}),n}function jd(e,t){const n={};for(const r of t)r.scope!=="_computed"&&(n[r.scope]=r.tree);const o={};for(const[r,s]of Object.entries(e)){if(typeof s!="string"){o[r]=s;continue}const a=Od(s,n);o[r]=a.ok?a.value:s}return o}function wv(e,t){const n=Bd(e,t),o=Wd(e,t),r={};for(const s of n)s.sample!==void 0&&(r[s.id]=s.sample);for(const s of o)s.scope==="_computed"?r[s.scope]=jd(s.tree,o):r[s.scope]=s.tree;return r}function Hd(e){const{raw:t,spec:n,evaluateComputed:o}=e;return u.useMemo(()=>{const r=Bd(t,n),s=Wd(t,n);if(o){const a=s.findIndex(l=>l.scope==="_computed");if(a>=0){const l=jd(s[a].tree,s);s[a]={scope:"_computed",tree:l}}}return Xi({dataSources:r,states:s,spec:n??void 0})},[t,n,o])}function kv(e){const{raw:t,spec:n,pageType:o}=e;return u.useMemo(()=>{const r=Array.isArray(t?.data_sources)?t.data_sources:[],s=[];for(const d of r){const c=d?.id;if(typeof c!="string"||c.length===0)continue;const{value:p}=ds(d,n?.sampleData),f=typeof d.label_key=="string"?d.label_key:void 0;s.push({id:c,sample:p,labelKey:f})}const a=[];n?.sampleGlobal&&typeof n.sampleGlobal=="object"&&a.push({scope:"_global",tree:n.sampleGlobal}),t?.computed&&typeof t.computed=="object"&&!Array.isArray(t.computed)&&a.push({scope:"_computed",tree:t.computed});const l=Xi({dataSources:s,states:a,spec:n??void 0});if(typeof o=="string"&&o!=="")for(const d of["title","description"])l.push({expression:`{{_seo.${o}.${d}}}`,source:"_seo",sourceId:"_seo",path:`${o}.${d}`,shape:"scalar",preview:""});return l},[t,n,o])}function Fd(e,t){const n=[],o=new Set,r=s=>{for(const a of s??[]){if(a.kind==="route"&&!a.isRedirect&&typeof a.path=="string"&&a.path.startsWith("/")&&!o.has(a.path)){o.add(a.path);const l=a.label.startsWith("$t:")?a.label.slice(3):a.label,d=a.label.startsWith("$t:")?t(l):l,c=d&&d!==l&&!d.startsWith("$t:")?d:"";n.push({value:a.path,label:c?`${c} (${a.path})`:a.path})}r(a.children)}};return r(e),n}function Kd(e){const t=Array.isArray(e?.data_sources)?e.data_sources:[],n=[];for(const o of t){const r=o?.id;typeof r=="string"&&r.length>0&&n.push({value:r,label:r})}return n}function ps(e,t){const n=typeof e.id=="string"?e.id:"",o=typeof e.label_key=="string"?e.label_key:"";if(!o)return null;const r=o.startsWith("$t:")?o.slice(3):o,s=t(r);return s&&s!==r&&!s.startsWith("$t:")&&s!==n?s:null}function fs(e,t){const n=e.__source;if(!n||typeof n!="object"||Array.isArray(n))return null;const o=n;if(o.kind!=="extension")return null;const r=o.extensionSourceType==="plugin"?t("layout_editor.data_sources.source.plugin"):t("layout_editor.data_sources.source.module"),s=typeof o.extensionIdentifier=="string"?o.extensionIdentifier:"",a=typeof o.extensionName=="string"?o.extensionName:"",l=a&&a!==s?`${a} (${s})`:s;return l?`${r}: ${l}`:r}function Sv(e,t,n){const o=Array.isArray(e?.data_sources)?e.data_sources:[],r=[],s=new Set;for(const a of o){const l=a?.id;typeof l!="string"||l.length===0||s.has(l)||(s.add(l),r.push({id:l,friendly:ps(a,t),source:fs(a,n)}))}return r}function Vd(e){const t=e?.states?.groups??[],n=new Set;for(const o of t)for(const r of o.items??[]){for(const s of Object.keys(r.initialState?.local??{}))n.add(s);for(const s of Object.keys(r.initialState?.global??{}))n.add(s)}return Array.from(n).map(o=>({value:o,label:o}))}function Cv(e,t){const n=Array.isArray(e?.modals)?e.modals:[],o=[],r=new Set;for(const s of n){const a=s?.id;typeof a!="string"||a.length===0||r.has(a)||(r.add(a),o.push({value:a,label:$v(s,a,t)}))}return o}function $v(e,t,n){const r=e?.meta?.editor_label;if(typeof r=="string"&&r.length>0)return Ud(r,n);const s=e?.title;return typeof s=="string"&&s.length>0?Ud(s,n):t}function Ud(e,t){if(e.startsWith("$t:")){const n=e.slice(3),o=t?t(n):n;return o&&o!==n?o:n}return e}const qd=wo("ActionRecipeEngine"),Ev={api:"apiCall",nav:"navigate",showToast:"toast",setLocalState:"setState"},Iv=new Set(["comment"]);function Io(e){if(!e||typeof e!="object")return[];const t=[];for(const[n,o]of Object.entries(e)){if(Iv.has(n)||!o||typeof o!="object")continue;const r=o,s=r.build&&typeof r.build=="object"?r.build:typeof r.handler=="string"?{handler:r.handler}:void 0;!s||typeof s.handler!="string"||s.handler.length===0||t.push({id:n,label:typeof r.label=="string"?r.label:void 0,params:Array.isArray(r.params)?r.params:[],build:s})}return t}function sn(e){const t=/^\{\{\s*([\w.]+)\s*\}\}$/.exec(e);return t?t[1]:null}const Er="...";function Av(e,t){return e.replace(/\{\{\s*([\w.]+)\s*\}\}/g,(n,o)=>{const r=t[o];return r==null?"":String(r)})}function gs(e,t){if(typeof e=="string"){const n=sn(e);return n!==null?n in t?t[n]:void 0:e.includes("{{")?Av(e,t):e}if(Array.isArray(e))return e.map(n=>gs(n,t)).filter(n=>n!==void 0);if(e&&typeof e=="object"){const n={};for(const[o,r]of Object.entries(e)){if(o===Er&&typeof r=="string"){const d=sn(r),c=d!==null?t[d]:void 0;if(c&&typeof c=="object"&&!Array.isArray(c))for(const[p,f]of Object.entries(c))f===void 0||p===""||(n[p]=f);continue}const s=gs(r,t);if(s===void 0||Array.isArray(s)&&s.length===0&&(o==="onSuccess"||o==="onError"))continue;const a=sn(o),l=a!==null?a in t?String(t[a]):null:o;l===null||l===""||s&&typeof s=="object"&&!Array.isArray(s)&&Object.keys(s).length===0||(n[l]=s)}return n}return e}function Tv(e){const t=e.handler;if(typeof t!="string")return e;const n=Ev[t];if(n)return qd.warn(`action recipe used forbidden handler "${t}" — corrected to "${n}" (core handler rules)`),{...e,handler:n};if(t==="apiCall"||t==="navigate"){const o=e.params;if(o&&typeof o=="object")for(const r of["target","onSuccess","onError"])r in o&&qd.warn(`action recipe build placed "${r}" inside params for handler "${t}" — should be top-level (core handler rules)`)}return e}function Ir(e,t){const n=gs(e.build,t);if(typeof n.handler!="string"&&e.build.handler.includes("{{")&&(n.handler=e.build.handler),e.build.handler.includes("{{")){const o=new Set(e.params.filter(r=>r.required).map(r=>r.key));o.size>0&&Gd(e.build,n,o)}return Tv(n)}function Gd(e,t,n){if(!e||typeof e!="object"||Array.isArray(e)||!t||typeof t!="object"||Array.isArray(t))return;const o=e,r=t;for(const[s,a]of Object.entries(o))if(s!=="handler"){if(typeof a=="string"){const l=sn(a);l!==null&&n.has(l)&&!(s in r)&&(r[s]=a)}else if(a&&typeof a=="object"){if(!(s in r)||typeof r[s]!="object"||r[s]===null)if(JSON.stringify(a).includes("{{"))r[s]={};else continue;Gd(a,r[s],n)}}}function Dv(e,t){if(!e||typeof e!="object")return null;const n=e.handler;if(typeof n!="string")return null;let o=null,r=-1;for(const s of t){const a=s.build.handler.includes("{{");if(!a&&s.build.handler!==n||a&&!zv(s.build,e))continue;const l=Nv(s.build,e);if(l===null)continue;const d=Object.keys(l).length;d>r&&(r=d,o={recipeId:s.id,values:l})}return o}function zv(e,t){if(typeof t.handler=="string"&&t.handler===e.handler)return!0;const n=e.params;if(!n||typeof n!="object")return!1;const o=t.params;if(!o||typeof o!="object")return!1;const r=o,s=Object.entries(n).filter(([,a])=>typeof a=="string"&&sn(a)!==null);return s.length===0?!1:s.every(([a])=>a in r)}function Nv(e,t){const n={},o=(r,s)=>{if(typeof r=="string"){const a=sn(r);return a!==null?(n[a]=s,!0):r.includes("{{")?typeof s=="string":r===s}if(Array.isArray(r))return Array.isArray(s);if(r&&typeof r=="object"){if(!s||typeof s!="object"||Array.isArray(s))return!1;const a=s,l=r[Er],d=typeof l=="string"?sn(l):null,c=new Set(Object.keys(r).filter(p=>p!==Er));for(const[p,f]of Object.entries(r))if(p!==Er){if(typeof f=="string"&&sn(f)!==null){const b=sn(f);p in a&&(n[b]=a[p]);continue}if(!(p in a)){if(p==="onSuccess"||p==="onError"||p==="params"||p==="identity_target")continue;return!1}if(!o(f,a[p]))return!1}if(d!==null){const p={};for(const[f,b]of Object.entries(a))c.has(f)||(p[f]=b);Object.keys(p).length>0&&(n[d]=p)}return!0}return r===s};return o(e,t)?n:null}function Jn(e,t){const n=typeof e?.handler=="string"?e.handler:"",o=Dv(e,t);return o?{kind:"preset",recipeId:o.recipeId,values:o.values,handler:n}:{kind:"advanced",handler:n}}function Yd(e,t,n){const o=e?.label,r=o?n?n(o):o:"",s=[],a=e?.params??[];for(const l of a){if(l.advanced||l.widget==="action-list")continue;const d=t[l.key];if(d==null||d==="")continue;const c=Mv(d);if(c!==""&&(s.push(c),s.length>=2))break}return s.length===0?r:`${r} (${s.join(", ")})`}function Mv(e){return typeof e=="string"?e:typeof e=="number"||typeof e=="boolean"?String(e):Array.isArray(e)?`[${e.length}]`:e&&typeof e=="object"?"{…}":""}function Xd(e,t){const n={...e};return typeof t=="string"&&t.trim().length>0?n.if=t.trim():delete n.if,n}function hs(e){const t=e?.if;return typeof t=="string"&&t.trim().length>0?t:null}const Lv=/^\s*\$t:(custom\.[a-zA-Z0-9._-]+)\s*$/,Pv=/^\s*\$t:([a-zA-Z0-9._-]+)\s*$/,Ov=/\{\{.*?\}\}/;function Jd(e,t){const n=typeof e=="string"?e:"",o=Lv.exec(n);if(o){const a=o[1],l=t?t(a):"";return{customKey:a,binding:!1,paramKey:!1,displayValue:l}}const r=vt(n);if(r&&r.key.startsWith("custom.")){const a=t?t(r.key):"";return{customKey:r.key,binding:!1,paramKey:!0,displayValue:a}}if(Ov.test(n))return{customKey:null,binding:!0,paramKey:!1,displayValue:n};const s=Pv.exec(n);if(s){const a=s[1],l=t?t(a):"";return{customKey:null,binding:!1,paramKey:!1,displayValue:l&&l!==a?l:""}}return{customKey:null,binding:!1,paramKey:!1,displayValue:n}}function Bv(){const{state:e}=Qe(),t=e.templateIdentifier,n=e.locale,o=e.selectedRoute?.layoutName??null,r=u.useCallback(l=>{try{const c=Pt.getInstance().translate(l,{templateId:t,locale:n});return c&&c!==l?c:""}catch{return""}},[t,n]),s=u.useCallback(l=>Jd(l,r),[r]),a=u.useCallback(async(l,d)=>{const c=Jd(l,r);if(c.binding)return{kind:"blocked"};if(c.customKey){if(d===c.displayValue)return{kind:"noop",customKey:c.customKey};const b=await kr(t,c.customKey,n,d);return b.kind==="error"?{kind:"error",message:b.message}:(await on(t,n),{kind:"updated",customKey:c.customKey})}const p=await Yn(t,o,n,d);if(p.kind==="error"||!p.resource)return{kind:"error",message:p.message};const f=p.resource.translation_key;return await on(t,n),{kind:"created",customKey:f,token:`$t:${f}`}},[t,n,o,r]);return{locale:n,templateIdentifier:t,translate:r,classify:s,commitText:a}}async function ys(e,t,n,o,r,s,a,l,d,c){const p=c?os(o,c):null,f=!!(p&&p.keyifiable),b=d?d(o):o,g=f?p.chipValue:od(b),h=`p${f?p.bindings.length:br(b).length}`,v=Math.max(0,Math.min(r,g.length)),y=Jl(g,v,h),_=vr(y).replace(/\{\{?p\d+\}?\}/g," ").replace(/\s+/g," ").trim(),w=_.length>0?_:y,k=await Yn(e,t,n,w);if(k.kind==="error"||!k.resource)return{kind:"error",message:k.message};const x=k.resource.translation_key,$=Tn(y),C=k.resource.values??{},S={[n]:y};for(const N of Object.keys(C)){if(N===n)continue;let T=C[N]??"";for(const O of $)T=es(T,O);S[N]=T}Eo(e,x,S);const D=In(s,a,l);return{kind:"ok",text:`${f?td(`$t:${x}`,p):nd(`$t:${x}`,b)}|${h}=${D}`,key:x}}async function Ar(e,t,n,o,r,s,a){let l=vt(n);if(!l&&typeof n=="string"){const h=/^\s*\$t:(custom\.[A-Za-z0-9._-]+)\s*$/.exec(n);h&&(l={key:h[1],params:[]})}if(!l)return{kind:"error",message:"not a param key"};if(!l.key.startsWith("custom."))return{kind:"error",message:"not a custom param key"};const d=Fb(l.params.map(h=>h.name)),c=In(r,s,a),p=`${n.trim()}|${d}=${c}`,f=await fn(e,l.key),b=fm(l.key)??{},g=new Set([...Object.keys(f?.values??{}),...Object.keys(b),t]);if(g.size===0)return{kind:"error",message:"custom key not found"};const m={};for(const h of g){const v=It(l.key,h)??f?.values?.[h]??"";m[h]=h===t?Jl(v,o,d):es(v,d)}return Eo(e,l.key,m),{kind:"ok",text:p,paramName:d}}async function Ao(e,t,n,o){return Sr(e,t,n,o),{kind:"ok"}}async function Wv(e,t,n){const o=await fn(e,t);if(!o)return{kind:"error",message:"custom key not found"};const r={};for(const[s,a]of Object.entries(o.values??{})){const l=It(t,s)??a;r[s]=n(l)}Eo(e,t,r);try{const s=Pt.getInstance();for(const[a,l]of Object.entries(r))s.setTranslationValue(e,a,t,l)}catch{}return{kind:"ok"}}function Zd(e,t,n){return Wv(e,t,o=>sd(o,n))}function Qd(e,t){if(!(typeof window>"u"))try{window.dispatchEvent(new CustomEvent(kt,{detail:{templateIdentifier:e,locale:t}}))}catch{}}async function Tr(e,t,n,o){const r=await Zd(e,t,n);return Qd(e,o),r}const jv=/^\s*\$t:(custom\.[a-zA-Z0-9._-]+)\s*$/;function Hv(e,t){const n=s=>{const a=[],l=/\{\{?(p\d+)\}?\}/g;let d;for(;(d=l.exec(s))!==null;)a.push(d[1]);return a},o=n(e),r=n(t);return o.length!==r.length?!0:o.some((s,a)=>s!==r[a])}function Rd(e){const t=e.text;if(typeof t!="string")return null;const n=jv.exec(t);if(n)return n[1];const o=vt(t);return o&&o.key.startsWith("custom.")?o.key:null}function bs(e){const t={},n=vt(typeof e=="string"?e:"");for(const o of n?.params??[])t[o.name]=Ut(o.expression);return t}function Fv(e){return bs(typeof e.text=="string"?e.text:"")}function ec({customKey:e,templateIdentifier:t,t:n,paramLabels:o,locales:r,onRemoveParam:s}){const a=r??_d(),l=Kt(),d=o??{},[c,p]=u.useState({status:"idle",values:{}}),[f,b]=u.useState(!1),[g,m]=u.useState(!1),[h,v]=u.useState(null),y=u.useRef({});u.useEffect(()=>{let E=!1;if(!e){p({status:"not_a_key",values:{}});return}return p({status:"loading",values:{}}),(async()=>{const I=await fn(t,e);if(E)return;if(!I){p({status:"error",values:{}});return}const N={...I.values??{}};for(const T of Object.keys(N)){const O=It(e,T);O!==void 0&&(N[T]=O)}for(const T of a){const O=It(e,T);O!==void 0&&(N[T]=O)}y.current={...N},p({status:"loaded",id:I.id,lockVersion:I.lock_version,values:N})})(),()=>{E=!0}},[e,t]),u.useEffect(()=>{if(typeof window>"u"||!e)return;const E=I=>{const N=I.detail;N?.templateIdentifier&&N.templateIdentifier!==t||p(T=>{if(T.status!=="loaded")return T;let O=!1;const M={...T.values};for(const B of Object.keys(M)){const z=It(e,B);z!==void 0&&z!==M[B]&&(M[B]=z,O=!0)}return O?(y.current={...y.current,...M},{...T,values:M}):T})};return window.addEventListener(kt,E),()=>window.removeEventListener(kt,E)},[e,t]);const _=u.useCallback((E,I)=>{p(N=>{const T=N.values[E]??"",O={...N,values:{...N.values,[E]:I}};return e&&Hv(T,I)&&(Sr(t,e,E,I),y.current={...y.current,[E]:I},l?.markDirty?.(),Qd(t,E)),O})},[e,t,l]),w=u.useCallback(E=>{p(I=>{const N={};for(const[T,O]of Object.entries(I.values))N[T]=sd(O,E);return y.current={...N},{...I,values:N}}),s?.(E)},[s]),k=u.useMemo(()=>[...Object.values(c.values),...Object.values(y.current)].some(I=>Tn(I).length>0),[c.values]),x=u.useRef(null),$=u.useCallback(()=>{m(!0),x.current&&clearTimeout(x.current),x.current=setTimeout(()=>m(!1),1500)},[]);u.useEffect(()=>()=>{x.current&&clearTimeout(x.current)},[]);const C=u.useCallback(async()=>{if(m(!1),c.status!=="loaded"||c.id===void 0)return;const E=y.current;for(const[N,T]of Object.entries(c.values)){const O=E[N]??"";if(k){const M=Tn(O);if(M.length===0)continue;const B=Tn(T);if(!(M.length===B.length&&M.every((H,P)=>H===B[P]))){v(n("layout_editor.translation.placeholder_mismatch"));return}continue}if(mr(O)&&!Jb(O,T)){v(n("layout_editor.translation.token_mismatch"));return}}if(b(!0),v(null),k){Eo(t,e??"",c.values),y.current={...c.values},l?.markDirty?.(),Et({op:"translation_field_update",translationKey:e,changedLocales:Object.keys(c.values),timestamp:Date.now()}),await new Promise(N=>setTimeout(N,0)),b(!1),$();return}const I=`/api/admin/templates/${encodeURIComponent(t)}/custom-translations/${c.id}`;try{const N=await fetch(I,{method:"PUT",credentials:"same-origin",headers:Me({"Content-Type":"application/json"}),body:JSON.stringify({values:c.values,expected_lock_version:c.lockVersion??0})}),T=await N.json().catch(()=>null);if(N.status===409){v(n("layout_editor.translation.conflict"));return}if(!N.ok){v(T?.message??n("layout_editor.translation.save_failed"));return}const O=typeof T?.data?.lock_version=="number"?T.data.lock_version:(c.lockVersion??0)+1;p(M=>({...M,lockVersion:O})),y.current={...c.values},await on(t),Et({op:"translation_field_update",translationKey:e,changedLocales:Object.keys(c.values),timestamp:Date.now()}),$()}catch(N){v(N instanceof Error?N.message:n("layout_editor.translation.save_failed"))}finally{b(!1)}},[c,t,e,n]);if(c.status==="not_a_key"||!e)return i.jsx("div",{"data-testid":"g7le-translation-not-a-key",style:ms,children:n("layout_editor.translation.not_a_key")});if(c.status==="loading"||c.status==="idle")return i.jsx("div",{"data-testid":"g7le-translation-loading",style:ms,children:n("layout_editor.translation.loading")});if(c.status==="error")return i.jsx("div",{"data-testid":"g7le-translation-error",style:ms,children:n("layout_editor.translation.load_failed")});const S=new Set([...a,...Object.keys(c.values)]),D=Array.from(S);return i.jsxs("div",{"data-testid":"g7le-translation-field",style:{display:"flex",flexDirection:"column",gap:8},children:[i.jsxs("div",{"data-testid":"g7le-translation-key",style:Kv,children:[n("layout_editor.translation.key_label"),": ",i.jsx("code",{style:Vv,children:e})]}),D.map(E=>{const I=c.values[E]??"",N=I.trim().length===0;return i.jsxs("div",{className:"g7le-translation-row","data-testid":`g7le-translation-row-${E}`,style:Uv,children:[i.jsxs("label",{htmlFor:`g7le-translation-input-${E}`,style:{fontSize:12,color:"#475569",minWidth:72,display:"inline-flex",alignItems:"center",gap:4},children:[Xn(E,n),N&&i.jsx("span",{"data-testid":`g7le-translation-missing-${E}`,style:qv,children:n("layout_editor.translation.needs_translation")})]}),k?i.jsx("div",{style:{flex:1},"data-testid":`g7le-translation-chip-${E}`,children:i.jsx(Cr,{value:I,onChange:T=>_(E,T),t:n,paramLabels:d,testIdSuffix:E,onRemoveChip:s?w:void 0})}):i.jsx("input",{id:`g7le-translation-input-${E}`,type:"text","data-testid":`g7le-translation-input-${E}`,value:I,placeholder:N?n("layout_editor.translation.needs_translation"):"",onChange:T=>_(E,T.currentTarget.value),onBlur:C,style:{flex:1,padding:"4px 8px",fontSize:13,border:"1px solid #cbd5e1",borderRadius:4,color:N?"#94a3b8":"#0f172a"}})]},E)}),i.jsxs("div",{style:{display:"flex",alignItems:"center",gap:8},children:[i.jsx("button",{type:"button","data-testid":"g7le-translation-save",disabled:f,onClick:C,style:Gv,children:n(f?"layout_editor.translation.saving":"layout_editor.translation.save")}),g&&!f&&i.jsxs("span",{"data-testid":"g7le-translation-saved",style:{fontSize:11,color:"#16a34a",display:"inline-flex",alignItems:"center",gap:4},children:["✓ ",n("layout_editor.translation.saved")]}),h&&i.jsx("span",{"data-testid":"g7le-translation-save-error",style:{fontSize:11,color:"#dc2626"},children:h})]})]})}const ms={fontSize:12,color:"#94a3b8",padding:"16px 0",textAlign:"center"},Kv={fontSize:12,color:"#475569"},Vv={fontSize:11,background:"#f1f5f9",padding:"1px 4px",borderRadius:3,color:"#0f172a"},Uv={display:"flex",alignItems:"center",gap:8},qv={fontSize:10,color:"#b45309",background:"#fef3c7",padding:"0 4px",borderRadius:3},Gv={padding:"6px 12px",fontSize:12,border:"none",borderRadius:6,background:"#2563eb",color:"#fff",cursor:"pointer"};function Zn({anchorRef:e,open:t,onClose:n,children:o,placement:r="bottom",minWidth:s=240,maxWidth:a=360,margin:l=8,testid:d}){const c=u.useRef(null),[p,f]=u.useState(null),b=u.useCallback(()=>{const m=e.current,h=c.current;if(!m||!h)return;const v=m.getBoundingClientRect(),y=window.innerWidth,_=window.innerHeight,w=h.getBoundingClientRect(),k=Math.min(Math.max(w.width,s),a),x=w.height;let $=v.left;$+k+l>y&&($=v.right-k),$=Math.min(Math.max($,l),y-k-l);const C=_-v.bottom-l,S=v.top-l;let D,E;const I=r==="bottom",N=C>=Math.min(x,160),T=S>=Math.min(x,160);I&&N||I&&!T?(D=v.bottom+4,E=C):!I&&T||T?(D=v.top-4-Math.min(x,S),E=S):C>=S?(D=v.bottom+4,E=C):(D=l,E=S),f({left:$,top:D,maxHeight:Math.max(E,120)})},[e,r,s,a,l]);if(u.useLayoutEffect(()=>{if(!t){f(null);return}b()},[t,b]),u.useEffect(()=>{if(!t)return;const m=()=>b();window.addEventListener("resize",m),window.addEventListener("scroll",m,!0);const h=y=>{const _=c.current,w=e.current,k=y.target;_?.contains(k)||w?.contains(k)||n()},v=y=>{y.key==="Escape"&&n()};return document.addEventListener("pointerdown",h,!0),document.addEventListener("keydown",v),()=>{window.removeEventListener("resize",m),window.removeEventListener("scroll",m,!0),document.removeEventListener("pointerdown",h,!0),document.removeEventListener("keydown",v)}},[t,b,n,e]),!t)return null;const g={...Yv,minWidth:s,maxWidth:`min(${a}px, calc(100vw - ${l*2}px))`,...p?{left:p.left,top:p.top,maxHeight:p.maxHeight,visibility:"visible"}:{left:-9999,top:-9999,visibility:"hidden"}};return i.jsx("div",{ref:c,"data-testid":d,style:g,role:"dialog",children:o})}const Yv={position:"fixed",zIndex:1e3,background:"#fff",border:"1px solid #cbd5e1",borderRadius:8,boxShadow:"0 4px 16px rgba(0,0,0,0.14)",padding:6,overflowY:"auto",boxSizing:"border-box"};function tc(e,t,n){if(!t)return n;const o=e(t);return!o||o===t||o.startsWith("$t:")?n:o}function $t({candidates:e,t,onSelect:n,testIdSuffix:o,defaultOpen:r=!1,floating:s=!0}){const[a,l]=u.useState(r),[d,c]=u.useState(""),p=u.useRef(null),f=u.useMemo(()=>Zi(e,"scalar").map(h=>({...h,resolvedLabel:tc(t,h.labelKey,h.path||h.sourceId)})),[e,t]),b=u.useMemo(()=>Qi(f,d),[f,d]),g=i.jsxs(i.Fragment,{children:[i.jsx("input",{type:"text",autoFocus:!0,"data-testid":`g7le-inline-binding-search-input-${o}`,value:d,placeholder:t("layout_editor.inline_binding.search_placeholder"),onChange:m=>c(m.target.value),style:Qv}),i.jsx("div",{style:Rv,children:b.length===0?i.jsx("div",{style:e_,children:t("layout_editor.inline_binding.no_results")}):b.map(m=>i.jsxs("button",{type:"button","data-testid":`g7le-inline-binding-candidate-${m.expression}`,onClick:()=>{n(m),l(!1),c("")},style:t_,children:[i.jsxs("span",{style:{display:"flex",flexDirection:"column",alignItems:"flex-start"},children:[i.jsx("span",{style:{fontSize:12},children:m.resolvedLabel}),i.jsxs("span",{style:n_,children:[tc(t,m.groupLabelKey,m.sourceId)," · ",m.path||m.sourceId]})]}),i.jsx("span",{style:o_,children:m.preview})]},m.expression))})]});return i.jsxs("div",{style:s?Zv:{marginTop:4},children:[i.jsx("button",{ref:p,type:"button","data-testid":`g7le-inline-binding-search-toggle-${o}`,onClick:()=>l(m=>!m),title:t("layout_editor.inline_binding.search"),"aria-label":t("layout_editor.inline_binding.search"),"aria-expanded":a,style:Xv,children:"🔍"}),s?i.jsx(Zn,{anchorRef:p,open:a,onClose:()=>l(!1),testid:`g7le-inline-binding-picker-${o}`,children:g}):a&&i.jsx("div",{"data-testid":`g7le-inline-binding-picker-${o}`,style:Jv,children:g})]})}const Xv={fontSize:13,lineHeight:1,border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",color:"#334155",padding:"4px 8px",cursor:"pointer",textAlign:"center",whiteSpace:"nowrap"},Jv={marginTop:4,border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",padding:6},Zv={display:"inline-block"},Qv={width:"100%",padding:"5px 8px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,boxSizing:"border-box"},Rv={marginTop:4,maxHeight:200,overflowY:"auto",display:"flex",flexDirection:"column",gap:2},e_={fontSize:11,color:"#94a3b8",padding:"8px 0",textAlign:"center"},t_={display:"flex",alignItems:"center",justifyContent:"space-between",gap:8,border:"none",background:"transparent",cursor:"pointer",padding:"6px 8px",borderRadius:4,textAlign:"left"},n_={fontSize:10,color:"#94a3b8"},o_={fontSize:10,color:"#64748b",fontFamily:"monospace",whiteSpace:"nowrap"},r_=["$localized"];function Qn(e){const t=e.trim();if(!t.startsWith("{{")||!t.endsWith("}}")||t.length<4)return null;let n=0;for(let o=0;ogn(n).decomposed)}function To(e,t,n,o){switch(e.kind){case"leaf":oc(e.text,t,n,o);return;case"conditional":To(e.then,t,n,o),o.push({kind:"ellipsis",text:"⋯"});return;case"fallback":To(e.primary,t,n,o);return;case"concat":e.parts.forEach(r=>To(r,t,n,o));return;case"raw":default:o.push({kind:"text",text:e.source});return}}function oc(e,t,n,o){const r=e.trim();if(/^\{\{([\s\S]*)\}\}$/.exec(r)&&!r.slice(2,-2).includes("}}")){o.push({kind:"chip",text:n(r)});return}if(r.startsWith("$t:")){const a=r.slice(3).split("|")[0];o.push({kind:"text",text:t(a)||a});return}for(const a of e.split(/(\{\{(?:[^}]|\}(?!\}))*\}\})/g))a!==""&&(/^\{\{[\s\S]*\}\}$/.test(a)?o.push({kind:"chip",text:n(a)}):o.push({kind:"text",text:a}))}function vs(e,t,n){const o=[];if(typeof e!="string"||e==="")return o;if(Qn(e)===null&&/\{\{/.test(e)){for(const a of e.split(/(\{\{(?:[^}]|\}(?!\}))*\}\})/g))if(!(a===void 0||a===""))if(/^\{\{[\s\S]*\}\}$/.test(a)){const l=gn(a);To(l.node,t,n,o)}else oc(a,t,n,o);return o}const s=gn(e);return To(s.node,t,n,o),o}function i_(e){if(typeof e!="string"||e.trim()==="")return"empty";const t=e.trim();if(Qn(t)===null)return"simple";const n=gn(e);return n.node.kind==="leaf"?"simple":n.decomposed?"expression":"raw"}function gn(e){const t=typeof e=="string"?e:"",n=t.trim();if(n==="")return{node:{kind:"leaf",text:t},decomposed:!1,original:t};const o=Qn(n);if(o===null)return{node:{kind:"leaf",text:t},decomposed:!1,original:t};let r;try{r=g_(o)}catch{r=null}if(r===null)return{node:{kind:"raw",source:o},decomposed:!1,original:t};if(r.kind==="leaf"||r.kind==="raw")return{node:r,decomposed:!1,original:t};if(r.kind==="fallback"&&r.op==="??"&&s_(r.primary)&&a_(r.fallback))return{node:{kind:"leaf",text:t},decomposed:!1,original:t};const s=Rn(r,!0);return v_(o,s)?{node:r,decomposed:!0,original:t}:{node:{kind:"raw",source:o},decomposed:!1,original:t}}function Rn(e,t=!1){const n=ic(e);return t?n:e.kind==="leaf"?e.text:`{{${n}}}`}function Do(e){return Rn({kind:"conditional",condition:{kind:"simple",left:"",op:"truthy",right:""},then:{kind:"leaf",text:typeof e=="string"?e:""},else:{kind:"leaf",text:""}},!1)}function _s(e){return Rn({kind:"fallback",op:"??",primary:{kind:"leaf",text:typeof e=="string"?e:""},fallback:{kind:"leaf",text:""}},!1)}function zo(e){switch(e.kind){case"leaf":return e.text;case"conditional":return zo(e.then);case"fallback":return zo(e.primary);case"concat":return e.parts.map(zo).join("");case"raw":default:return e.kind==="raw"?`{{${e.source}}}`:""}}function i5(e){return e}function Dr(e){if(typeof e!="string"||e.trim()==="")return"";if(Qn(e)===null&&/\{\{/.test(e)){for(const n of e.split(/(\{\{(?:[^}]|\}(?!\}))*\}\})/g))if(!(n===void 0||n===""))return/^\{\{[\s\S]*\}\}$/.test(n)?zo(gn(n).node):n;return""}return zo(gn(e).node)}function s_(e){if(e.kind!=="leaf")return!1;const t=/^\{\{([\s\S]*)\}\}$/.exec(e.text.trim());return t?eo(t[1].trim()):!1}function a_(e){if(e.kind!=="leaf")return!1;const t=e.text.trim();return!(/\{\{/.test(t)||t.startsWith("$t:"))}function zr(e){return`'${e.replace(/\\/g,"\\\\").replace(/'/g,"\\'")}'`}function l_(e){const t=[];let n=0,o=0;const r=e.length;for(;on&&t.push(zr(e.slice(n,o)));let s=o+2;for(;s=r)break;const a=e.slice(o+2,s).trim();t.push(a),o=s+2,n=o;continue}o++}return nNo(t,"concat")).join(" + ");case"fallback":return`${No(e.primary,"fallback")} ${e.op} ${No(e.fallback,"fallback")}`;case"conditional":return`${c_(e.condition)} ? ${No(e.then,"conditional")} : ${No(e.else,"conditional")}`;default:return""}}function No(e,t){const n=ic(e);return e.kind==="leaf"||e.kind==="raw"?n:e.kind==="conditional"?t==="conditional"?n:`(${n})`:e.kind==="fallback"?t==="concat"?`(${n})`:n:(e.kind==="concat",n)}const p_=["===","!==","??","||","&&","==","!=",">=","<="];function sc(e){const t=[];let n=0;const o=e.length;for(;n=o)throw new Error("unterminated-binding-in-string");d+=e.slice(l,c+2),l=c+2;continue}if(e[l]===a){d+=a,l++;break}if(a==="`"&&e[l]==="$"&&e[l+1]==="{")throw new Error("template-literal-interpolation-unsupported");d+=e[l],l++}if(l>o||d[d.length-1]!==a)throw new Error("unterminated-string");t.push({kind:"string",value:d,start:n,end:l}),n=l;continue}if(r==="{"&&e[n+1]==="{"){let a=n+2;for(;a"||r==="<"){t.push({kind:"op",value:r,start:n,end:n+1}),n++;continue}if(r==="!"){t.push({kind:"op",value:"!",start:n,end:n+1}),n++;continue}if(r==="("){t.push({kind:"lparen",value:"(",start:n,end:n+1}),n++;continue}if(r===")"){t.push({kind:"rparen",value:")",start:n,end:n+1}),n++;continue}if(r==="["){t.push({kind:"lbracket",value:"[",start:n,end:n+1}),n++;continue}if(r==="]"){t.push({kind:"rbracket",value:"]",start:n,end:n+1}),n++;continue}if(r==="."){t.push({kind:"dot",value:".",start:n,end:n+1}),n++;continue}if(/[0-9]/.test(r)){let a=n;for(;a=n)return"";const o=this.toks[t].start,r=this.toks[n-1].end;return this.src.slice(o,r)}parseFallback(){let t=this.parseConcat(),n=this.peek();for(;n&&n.kind==="op"&&(n.value==="??"||n.value==="||");){const o=n.value;this.next();const r=this.parseConcat();t={kind:"fallback",op:o,primary:t,fallback:r},n=this.peek()}return t}parseConcat(){const t=this.parseUnaryOrPrimary(),n=[t];let o=this.peek();for(;o&&o.kind==="op"&&o.value==="+";)this.next(),n.push(this.parseUnaryOrPrimary()),o=this.peek();return n.length===1?t:{kind:"concat",parts:n}}parseUnaryOrPrimary(){return this.parsePrimary()}parsePrimary(){const t=this.peek();if(!t)throw new Error("unexpected-end");if(t.kind==="lparen"){this.next();const n=this.parseTernary(),o=this.next();if(!o||o.kind!=="rparen")throw new Error("expected-rparen");return n}if(t.kind==="string")return this.next(),{kind:"leaf",text:lc(t.value)};if(t.kind==="tkey")return this.next(),{kind:"leaf",text:t.value};if(t.kind==="binding")return this.next(),{kind:"leaf",text:t.value};if(t.kind==="ident"&&r_.includes(t.value)&&this.toks[this.pos+1]?.kind==="lparen"){const n=this.tryParsePathWrapperCall(t.value);if(n!==null)return n;throw new Error("path-wrapper-non-simple-arg")}if(t.kind==="ident")return{kind:"leaf",text:`{{${this.parsePath()}}}`};if(t.kind==="number")return this.next(),{kind:"leaf",text:t.value};throw new Error(`unparseable-primary:${t.kind}`)}parsePath(){let t="";const n=this.next();if(!n||n.kind!=="ident")throw new Error("expected-ident");for(t+=n.value;;){const o=this.peek();if(!o)break;if(o.kind==="op"&&o.value==="?"&&this.toks[this.pos+1]?.kind==="dot"){this.next(),this.next();const r=this.next();if(!r||r.kind!=="ident")throw new Error("expected-ident-after-optional");t+="?."+r.value;continue}if(o.kind==="dot"){this.next();const r=this.next();if(!r||r.kind!=="ident")throw new Error("expected-ident-after-dot");t+="."+r.value;continue}if(o.kind==="lbracket"){this.next();const r=this.next(),s=this.next();if(!s||s.kind!=="rbracket")throw new Error("expected-rbracket");if(r&&(r.kind==="number"||r.kind==="string")){t+=`[${r.value}]`;continue}throw new Error("non-literal-index")}if(o.kind==="lparen")throw new Error("call-expression");break}return t}tryParsePathWrapperCall(t){const n=this.pos;this.next();const o=this.next();if(!o||o.kind!=="lparen")return this.pos=n,null;let r;try{if(this.peek()?.kind!=="ident")return this.pos=n,null;r=this.parsePath()}catch{return this.pos=n,null}const s=this.next();return!s||s.kind!=="rparen"?(this.pos=n,null):{kind:"leaf",text:`{{${t}(${r})}}`}}}function g_(e){return new f_(e).parse()}const h_=["===","!==","==","!=",">=","<=",">","<"];function y_(e,t){const n=e.trim();if(n==="")return{kind:"raw",source:n};if(n===rc)return{kind:"simple",left:"",op:"truthy",right:""};if(n.startsWith("!")){const s=n.slice(1).trim(),a=ac(s);return eo(a)?{kind:"simple",left:a,op:"falsy",right:""}:{kind:"raw",source:n}}const o=b_(n);if(o){const{left:s,op:a,right:l}=o,d=s.trim(),c=l.trim();return eo(d)&&(c==="''"||c==='""')?{kind:"simple",left:d,op:a,right:""}:eo(d)&&m_(c)?{kind:"simple",left:d,op:a,right:c}:{kind:"raw",source:n}}const r=ac(n);return eo(r)?{kind:"simple",left:r,op:"truthy",right:""}:{kind:"raw",source:n}}function b_(e){let t=0,n=null;for(let o=0;o=o||!/[A-Za-z_$]/.test(t[n]))return!1;for(n++;n=o||!/[A-Za-z_$]/.test(t[n]))return!1;for(n++;n=2&&(t[0]==="'"||t[0]==='"'||t[0]==="`")?t.slice(1,-1).replace(/\\(['"`\\])/g,"$1"):t}function v_(e,t){try{return Nr(e)===Nr(t)}catch{return!1}}function Nr(e){const t=Qn(e);return t!==null?Nr(t):sc(e).map(o=>{if(o.kind==="string")return`S:${lc(o.value)}`;if(o.kind==="binding"){const r=/^\{\{([\s\S]*)\}\}$/.exec(o.value);return`B:${r?Nr(r[1].trim()):o.value.replace(/\s+/g,"")}`}return o.kind==="tkey"?`S:${o.value}`:o.kind==="op"?`O:${o.value}`:o.kind==="ident"||o.kind==="number"?`I:${o.value}`:o.kind==="dot"?".":o.kind==="lbracket"?"[":o.kind==="rbracket"?"]":o.kind==="lparen"||o.kind==="rparen"?"":`X:${o.value}`}).join("")}function to({active:e,testid:t}){return i.jsx("div",{"data-testid":t,"data-active":e?"true":"false","aria-hidden":"true",style:e?x_:__})}const __={height:0,margin:0,transition:"height 80ms"},x_={height:3,margin:"1px 0",borderRadius:2,background:"#2563eb",boxShadow:"0 0 0 1px #93c5fd"};function xs(e){const{length:t,onMove:n,canDrag:o,canDropAt:r}=e,[s,a]=u.useState(null),[l,d]=u.useState(null),c=u.useCallback(m=>{o&&!o(m)||(a(m),d(m))},[o]),p=u.useCallback(()=>{a(null),d(null)},[]),f=u.useCallback((m,h)=>{if(s===null)return;const v=h==="before"?m:m+1;if(r&&!r(s,v)){d(null);return}d(v)},[s,r]),b=u.useCallback(()=>{if(s!==null&&l!==null){const m=l>s?l-1:l;m!==s&&(!r||r(s,l))&&n(s,m)}a(null),d(null)},[s,l,n,r]),g=u.useCallback(m=>s!==null&&l===m&&m<=t,[s,l,t]);return{dragIndex:s,dropIndex:l,dragging:s!==null,onDragStart:c,onDragEnd:p,onDragOverItem:f,onDrop:b,isDropTarget:g}}const w_={text:"",expression:"{{false ? '' : ''}}",fallback:"{{'' ?? ''}}",data:""};function dc(e,t){const n=An(e);let o=t;const r=n.map(s=>({id:o++,kind:s.kind==="literal"?"text":"expression",value:s.raw}));return r.length===0&&r.push({id:o++,kind:"text",value:""}),{segs:r,nextSeq:o}}function ws({value:e,onChange:t,t:n,candidates:o,testidPrefix:r="g7le-seg-value",renderLeafInput:s}){const a=u.useRef(0),l=u.useRef(e),d=u.useMemo(()=>{const w=dc(e,a.current);return a.current=w.nextSeq,w.segs},[]),[c,p]=u.useState(d),[f,b]=u.useState(!1);if(e!==l.current){l.current=e;const w=c.map(k=>k.value).join("");if(e!==w){const k=dc(e,a.current);a.current=k.nextSeq,p(k.segs)}}const g=u.useCallback(w=>{p(w);const k=w.map(x=>x.value).join("");l.current=k,t(k)},[t]),m=u.useCallback((w,k)=>{g(c.map(x=>x.id===w?{...x,value:k??""}:x))},[c,g]),h=u.useCallback(w=>{const k=c.filter(x=>x.id!==w);g(k.length>0?k:[{id:a.current++,kind:"text",value:""}])},[c,g]),v=u.useCallback(w=>{g([...c,{id:a.current++,kind:w,value:w_[w]}])},[c,g]),y=u.useCallback((w,k)=>{if(w===k||w<0||k<0||w>=c.length||k>=c.length)return;const x=c.slice(),[$]=x.splice(w,1);x.splice(k,0,$),g(x)},[c,g]),_=xs({length:c.length,onMove:y});return i.jsxs("div",{"data-testid":r,style:C_,children:[i.jsx("div",{style:$_,children:n("layout_editor.value_tree.segmented_hint")}),c.map((w,k)=>i.jsxs(u.Fragment,{children:[i.jsx(to,{active:_.isDropTarget(k),testid:`${r}-dropline-${k}`}),i.jsx(S_,{seg:w,index:k,t:n,candidates:o,testidPrefix:r,renderLeafInput:s,onValueChange:x=>m(w.id,x),onRemove:()=>h(w.id),dragging:_.dragIndex===k,onDragStartCard:()=>_.onDragStart(k),onDragEndCard:_.onDragEnd,onDragOverCard:x=>_.onDragOverItem(k,x),onDropCard:_.onDrop})]},w.id)),i.jsx(to,{active:_.isDropTarget(c.length),testid:`${r}-dropline-end`}),i.jsxs("div",{style:z_,"data-testid":`${r}-add-row`,children:[i.jsxs("button",{type:"button","data-testid":`${r}-add-text`,onClick:()=>v("text"),style:Mr,children:["+ ",n("layout_editor.value_tree.segment.text")]}),i.jsxs("button",{type:"button","data-testid":`${r}-add-expression`,onClick:()=>v("expression"),style:Mr,children:["+ ",n("layout_editor.value_tree.segment.expression")]}),i.jsxs("button",{type:"button","data-testid":`${r}-add-fallback`,onClick:()=>v("fallback"),style:Mr,children:["+ ",n("layout_editor.value_tree.segment.fallback")]}),i.jsxs("button",{type:"button","data-testid":`${r}-add-data`,onClick:()=>v("data"),style:Mr,children:["+ ",n("layout_editor.value_tree.segment.data")]})]}),i.jsxs("div",{style:L_,children:[i.jsxs("button",{type:"button","data-testid":`${r}-source-toggle`,"aria-expanded":f,onClick:()=>b(w=>!w),style:P_,children:[""," ",n("layout_editor.value_tree.show_source")," ",f?"▴":"▾"]}),f&&i.jsx("code",{"data-testid":`${r}-source-code`,style:O_,children:c.map(w=>w.value).join("")})]})]})}function k_({t:e,candidates:t,onPick:n,testidPrefix:o}){return!t||t.length===0?i.jsx("div",{"data-testid":`${o}-data-empty`,style:M_,children:e("layout_editor.value_tree.segment.data_no_candidates")}):i.jsxs("div",{"data-testid":`${o}-data-picker`,style:{minWidth:0},children:[i.jsx("div",{style:N_,children:e("layout_editor.value_tree.segment.data_pick_hint")}),i.jsx($t,{candidates:t,t:e,onSelect:r=>{n(In(r.sourceId,r.path,"scalar"))},testIdSuffix:`${o}-data`,defaultOpen:!0,floating:!0})]})}function S_({seg:e,index:t,t:n,candidates:o,testidPrefix:r,renderLeafInput:s,onValueChange:a,onRemove:l,dragging:d,onDragStartCard:c,onDragEndCard:p,onDragOverCard:f,onDropCard:b}){const g=e.kind==="text"?"layout_editor.value_tree.segment.text":e.kind==="data"?"layout_editor.value_tree.segment.data":e.kind==="fallback"?"layout_editor.value_tree.segment.fallback":"layout_editor.value_tree.segment.expression";return i.jsxs("div",{"data-testid":`${r}-card-${t}`,"data-seg-kind":e.kind,style:d?{...cc,opacity:.5}:cc,onDragOver:m=>{m.preventDefault();const h=m.currentTarget.getBoundingClientRect();f(m.clientY{m.preventDefault(),b()},children:[i.jsxs("div",{style:E_,children:[i.jsx("span",{"data-testid":`${r}-drag-${t}`,style:T_,draggable:!0,onDragStart:c,onDragEnd:p,title:n("layout_editor.value_tree.drag_reorder"),"aria-label":n("layout_editor.value_tree.drag_reorder"),role:"button",children:"⠿"}),i.jsx("span",{style:I_,children:n(g)}),i.jsx("button",{type:"button","data-testid":`${r}-remove-${t}`,onClick:l,title:n("layout_editor.value_tree.segment.remove"),"aria-label":n("layout_editor.value_tree.segment.remove"),style:D_,children:"✕"})]}),i.jsx("div",{style:A_,children:e.kind==="data"&&e.value.trim()===""?i.jsx(k_,{t:n,candidates:o,onPick:a,testidPrefix:`${r}-field-${t}`}):s?s({value:e.value,onChange:a,t:n,candidates:o,testidPrefix:`${r}-field-${t}`}):i.jsx(_t,{value:e.value,onChange:a,t:n,candidates:o,testidPrefix:`${r}-field-${t}`,enableExpressionTree:!0,expressionSourceToggle:!1})})]})}const C_={display:"flex",flexDirection:"column",gap:6,width:"100%",minWidth:0},$_={fontSize:11,color:"#64748b"},cc={display:"flex",flexDirection:"column",gap:4,padding:"6px 8px",border:"1px solid #e2e8f0",borderLeft:"3px solid #6366f1",borderRadius:6,background:"#f8fafc",minWidth:0},E_={display:"flex",alignItems:"center",gap:6,minWidth:0},I_={fontSize:10,fontWeight:600,color:"#6366f1",flex:1,minWidth:0},A_={minWidth:0},T_={color:"#94a3b8",cursor:"grab",fontSize:14,userSelect:"none"},D_={border:"none",background:"transparent",color:"#94a3b8",cursor:"pointer",fontSize:12,padding:2,flexShrink:0},z_={display:"flex",flexWrap:"wrap",gap:6,marginTop:2},N_={fontSize:11,color:"#64748b",marginBottom:2},M_={fontSize:11,color:"#94a3b8",padding:"4px 0"},Mr={padding:"4px 10px",fontSize:11,border:"1px dashed #cbd5e1",borderRadius:6,background:"#fff",color:"#475569",cursor:"pointer",whiteSpace:"nowrap"},L_={display:"flex",flexDirection:"column",gap:4,marginTop:2},P_={alignSelf:"flex-start",padding:"3px 8px",fontSize:11,border:"1px solid #cbd5e1",borderRadius:6,background:"#f8fafc",color:"#475569",cursor:"pointer",fontFamily:"monospace"},O_={fontSize:11,background:"#0f172a",color:"#e2e8f0",padding:"6px 8px",borderRadius:4,wordBreak:"break-all",fontFamily:"monospace"},B_=[{value:"truthy",labelKey:"layout_editor.value_tree.cond.op_truthy"},{value:"falsy",labelKey:"layout_editor.value_tree.cond.op_falsy"},{value:"===",labelKey:"layout_editor.value_tree.cond.op_eq"},{value:"!==",labelKey:"layout_editor.value_tree.cond.op_neq"},{value:">",labelKey:"layout_editor.value_tree.cond.op_gt"},{value:"<",labelKey:"layout_editor.value_tree.cond.op_lt"},{value:">=",labelKey:"layout_editor.value_tree.cond.op_gte"},{value:"<=",labelKey:"layout_editor.value_tree.cond.op_lte"}];function uc({node:e,onChange:t,t:n,candidates:o,testidPrefix:r="g7le-value-tree",depth:s=0,showSourceToggle:a=!0,renderLeafInput:l}){const[d,c]=u.useState(!1),p=s===0,f=i.jsx(no,{node:e,onChange:t,t:n,candidates:o,testidPrefix:r,depth:s,renderLeafInput:l});return p?a?i.jsxs("div",{"data-testid":r,style:fc,children:[f,i.jsxs("div",{style:rx,children:[i.jsxs("button",{type:"button","data-testid":`${r}-source-toggle`,"aria-expanded":d,onClick:()=>c(b=>!b),style:ix,children:[""," ",n("layout_editor.value_tree.show_source")," ",d?"▴":"▾"]}),d&&i.jsx("code",{"data-testid":`${r}-source-code`,style:sx,children:Rn(e,!1)})]})]}):i.jsx("div",{"data-testid":r,style:fc,children:f}):f}function no({node:e,onChange:t,t:n,candidates:o,testidPrefix:r,depth:s,renderLeafInput:a}){switch(e.kind){case"leaf":return i.jsx(ws,{value:e.text,onChange:l=>t({kind:"leaf",text:l}),t:n,candidates:o,testidPrefix:`${r}-leaf`,renderLeafInput:a});case"conditional":return i.jsx(W_,{node:e,onChange:t,t:n,candidates:o,testidPrefix:r,depth:s,renderLeafInput:a});case"fallback":return i.jsx(j_,{node:e,onChange:t,t:n,candidates:o,testidPrefix:r,depth:s,renderLeafInput:a});case"concat":return i.jsx(F_,{node:e,onChange:t,t:n,candidates:o,testidPrefix:r,depth:s,renderLeafInput:a});case"raw":default:return i.jsxs("div",{"data-testid":`${r}-raw`,style:ax,children:[i.jsx("code",{style:yc,children:e.source??""}),i.jsxs("span",{style:bc,children:["🔒 ",n("layout_editor.value_tree.raw_code_only")]})]})}}function W_({node:e,onChange:t,t:n,candidates:o,testidPrefix:r,depth:s,renderLeafInput:a}){const l=u.useCallback(p=>t({...e,condition:p}),[e,t]),d=u.useCallback(p=>t({...e,then:p}),[e,t]),c=u.useCallback(p=>t({...e,else:p}),[e,t]);return i.jsxs("div",{"data-testid":`${r}-conditional`,style:ks(s),children:[i.jsx(K_,{condition:e.condition,onChange:l,t:n,candidates:o,testidPrefix:r}),i.jsx(Lr,{label:n("layout_editor.value_tree.cond.when_true"),testid:`${r}-then`,children:i.jsx(no,{node:e.then,onChange:d,t:n,candidates:o,testidPrefix:`${r}-then`,depth:s+1,renderLeafInput:a})}),i.jsx(Lr,{label:n("layout_editor.value_tree.cond.when_false"),testid:`${r}-else`,children:i.jsx(no,{node:e.else,onChange:c,t:n,candidates:o,testidPrefix:`${r}-else`,depth:s+1,renderLeafInput:a})})]})}function j_({node:e,onChange:t,t:n,candidates:o,testidPrefix:r,depth:s,renderLeafInput:a}){const l=u.useCallback(c=>t({...e,primary:c}),[e,t]),d=u.useCallback(c=>t({...e,fallback:c}),[e,t]);return i.jsxs("div",{"data-testid":`${r}-fallback`,style:ks(s),children:[i.jsx(Lr,{label:n("layout_editor.value_tree.fallback.primary"),testid:`${r}-primary`,children:i.jsx(no,{node:e.primary,onChange:l,t:n,candidates:o,testidPrefix:`${r}-primary`,depth:s+1,renderLeafInput:a})}),i.jsx(Lr,{label:n(e.op==="??"?"layout_editor.value_tree.fallback.when_empty":"layout_editor.value_tree.fallback.when_falsy"),testid:`${r}-fallback-branch`,children:i.jsx(no,{node:e.fallback,onChange:d,t:n,candidates:o,testidPrefix:`${r}-fallbackb`,depth:s+1,renderLeafInput:a})})]})}const H_={text:()=>({kind:"leaf",text:""}),data:()=>({kind:"leaf",text:""}),expression:()=>({kind:"conditional",condition:{kind:"simple",left:"",op:"truthy",right:""},then:{kind:"leaf",text:""},else:{kind:"leaf",text:""}}),fallback:()=>({kind:"fallback",op:"??",primary:{kind:"leaf",text:""},fallback:{kind:"leaf",text:""}})};function F_({node:e,onChange:t,t:n,candidates:o,testidPrefix:r,depth:s,renderLeafInput:a}){const l=u.useCallback((g,m)=>{const h=e.parts.slice();h[g]=m,t({...e,parts:h})},[e,t]),d=u.useCallback(g=>{g.length===0?t({kind:"leaf",text:""}):g.length===1?t(g[0]):t({...e,parts:g})},[e,t]),c=u.useCallback(g=>d(e.parts.filter((m,h)=>h!==g)),[e.parts,d]),p=u.useCallback(g=>d([...e.parts,H_[g]()]),[e.parts,d]),f=u.useCallback((g,m)=>{if(g===m||g<0||m<0||g>=e.parts.length||m>=e.parts.length)return;const h=e.parts.slice(),[v]=h.splice(g,1);h.splice(m,0,v),d(h)},[e.parts,d]),b=xs({length:e.parts.length,onMove:f});return i.jsxs("div",{"data-testid":`${r}-concat`,style:ks(s),children:[i.jsx("div",{style:Q_,children:n("layout_editor.value_tree.concat.hint")}),e.parts.map((g,m)=>i.jsxs(u.Fragment,{children:[i.jsx(to,{active:b.isDropTarget(m),testid:`${r}-part-dropline-${m}`}),i.jsxs("div",{"data-testid":`${r}-part-card-${m}`,style:b.dragIndex===m?{...hc,opacity:.5}:hc,onDragOver:h=>{h.preventDefault();const v=h.currentTarget.getBoundingClientRect();b.onDragOverItem(m,h.clientY{h.preventDefault(),b.onDrop()},children:[i.jsxs("div",{style:R_,children:[i.jsx("span",{"data-testid":`${r}-part-handle-${m}`,style:nx,draggable:!0,onDragStart:()=>b.onDragStart(m),onDragEnd:b.onDragEnd,title:n("layout_editor.value_tree.drag_reorder"),"aria-label":n("layout_editor.value_tree.drag_reorder"),role:"button",children:"⠿"}),i.jsx("span",{style:ex,children:n("layout_editor.value_tree.concat.part",{n:m+1})}),i.jsx("button",{type:"button","data-testid":`${r}-part-remove-${m}`,onClick:()=>c(m),title:n("layout_editor.value_tree.segment.remove"),"aria-label":n("layout_editor.value_tree.segment.remove"),style:ox,children:"✕"})]}),i.jsx("div",{style:{minWidth:0},children:i.jsx(no,{node:g,onChange:h=>l(m,h),t:n,candidates:o,testidPrefix:`${r}-part-${m}`,depth:s+1,renderLeafInput:a})})]})]},m)),i.jsx(to,{active:b.isDropTarget(e.parts.length),testid:`${r}-part-dropline-end`}),i.jsxs("div",{style:tx,"data-testid":`${r}-part-add-row`,children:[i.jsxs("button",{type:"button","data-testid":`${r}-part-add-text`,onClick:()=>p("text"),style:Pr,children:["+ ",n("layout_editor.value_tree.segment.text")]}),i.jsxs("button",{type:"button","data-testid":`${r}-part-add-expression`,onClick:()=>p("expression"),style:Pr,children:["+ ",n("layout_editor.value_tree.segment.expression")]}),i.jsxs("button",{type:"button","data-testid":`${r}-part-add-fallback`,onClick:()=>p("fallback"),style:Pr,children:["+ ",n("layout_editor.value_tree.segment.fallback")]}),i.jsxs("button",{type:"button","data-testid":`${r}-part-add-data`,onClick:()=>p("data"),style:Pr,children:["+ ",n("layout_editor.value_tree.segment.data")]})]})]})}function K_({condition:e,onChange:t,t:n,candidates:o,testidPrefix:r}){if(e.kind==="raw")return i.jsxs("div",{"data-testid":`${r}-cond-raw`,style:Y_,children:[i.jsx("span",{style:gc,children:n("layout_editor.value_tree.cond.label")}),i.jsx("code",{style:yc,children:e.source}),i.jsxs("span",{style:bc,children:["🔒 ",n("layout_editor.value_tree.cond.code_only")]})]});const s=e,a=s.op!=="truthy"&&s.op!=="falsy";return i.jsxs("div",{"data-testid":`${r}-cond`,style:G_,children:[i.jsx("span",{style:gc,children:n("layout_editor.value_tree.cond.label")}),i.jsx(pc,{testid:`${r}-cond-left`,value:s.left,onCommit:l=>t({...s,left:l}),placeholder:n("layout_editor.value_tree.cond.field_placeholder"),candidates:o,t:n}),i.jsx("select",{"data-testid":`${r}-cond-op`,value:s.op,onChange:l=>t({...s,op:l.target.value}),style:Z_,children:B_.map(l=>i.jsx("option",{value:l.value,children:n(l.labelKey)},l.value))}),a&&i.jsx(pc,{testid:`${r}-cond-right`,value:s.right,onCommit:l=>t({...s,right:l}),placeholder:n("layout_editor.value_tree.cond.value_placeholder"),candidates:o,t:n,quoteDataPick:!0})]})}function pc({testid:e,value:t,onCommit:n,placeholder:o,candidates:r,t:s,quoteDataPick:a=!1}){const[l,d]=u.useState(null),c=l!==null?l:t,p=()=>{l!==null&&(l!==t&&n(l),d(null))},f=(b,g)=>{const m=In(b,g,"scalar"),h=/^\{\{\s*([\s\S]*?)\s*\}\}$/.exec(m.trim());let v=h?h[1]:m;v=v.replace(/\s*\?\?\s*''$/,"").trim(),n(a?`'${v}'`:v),d(null)};return i.jsxs("span",{style:X_,children:[i.jsx("input",{type:"text","data-testid":e,value:c,onChange:b=>d(b.target.value),onBlur:p,onKeyDown:b=>{b.key==="Enter"&&(b.preventDefault(),p())},placeholder:o,style:J_}),r&&r.length>0&&s&&i.jsx($t,{candidates:r,t:s,onSelect:b=>f(b.sourceId,b.path),testIdSuffix:`${e}-data`,floating:!0})]})}function Lr({label:e,testid:t,children:n}){return i.jsxs("div",{"data-testid":t,style:V_,children:[i.jsx("span",{style:U_,children:e}),i.jsx("div",{style:q_,children:n})]})}const fc={display:"flex",flexDirection:"column",gap:6,width:"100%",minWidth:0},ks=e=>({display:"flex",flexDirection:"column",gap:6,padding:"8px",border:"1px solid #e2e8f0",borderLeft:`3px solid ${e%2===0?"#6366f1":"#0ea5e9"}`,borderRadius:6,background:e%2===0?"#f8fafc":"#fefefe",minWidth:0}),V_={display:"flex",flexDirection:"column",gap:3,minWidth:0},U_={fontSize:11,fontWeight:600,color:"#475569"},q_={paddingLeft:8,minWidth:0},G_={display:"flex",flexWrap:"wrap",gap:4,alignItems:"center",minWidth:0},Y_={display:"flex",flexWrap:"wrap",gap:4,alignItems:"center",minWidth:0},gc={fontSize:11,fontWeight:600,color:"#0f172a",whiteSpace:"nowrap"},X_={display:"inline-flex",alignItems:"flex-start",gap:2,flex:"1 1 80px",minWidth:60},J_={flex:"1 1 80px",minWidth:60,padding:"4px 6px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6},Z_={padding:"4px 6px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,background:"#fff"},Q_={fontSize:11,color:"#94a3b8"},hc={display:"flex",flexDirection:"column",gap:4,padding:"6px 8px",border:"1px solid #e2e8f0",borderLeft:"3px solid #6366f1",borderRadius:6,background:"#fff",minWidth:0},R_={display:"flex",alignItems:"center",gap:6,minWidth:0},ex={fontSize:10,fontWeight:600,color:"#6366f1",flex:1,minWidth:0},tx={display:"flex",flexWrap:"wrap",gap:6,marginTop:2},nx={color:"#94a3b8",cursor:"grab",fontSize:14,userSelect:"none"},ox={border:"none",background:"transparent",color:"#94a3b8",cursor:"pointer",fontSize:12,padding:2,flexShrink:0},Pr={padding:"4px 10px",fontSize:11,border:"1px dashed #cbd5e1",borderRadius:6,background:"#fff",color:"#475569",cursor:"pointer",whiteSpace:"nowrap"},rx={display:"flex",flexDirection:"column",gap:4,marginTop:2},ix={alignSelf:"flex-start",padding:"3px 8px",fontSize:11,border:"1px solid #cbd5e1",borderRadius:6,background:"#f8fafc",color:"#475569",cursor:"pointer",fontFamily:"monospace"},sx={fontSize:11,background:"#0f172a",color:"#e2e8f0",padding:"6px 8px",borderRadius:4,wordBreak:"break-all",fontFamily:"monospace"},ax={display:"flex",flexDirection:"column",gap:2,minWidth:0},yc={fontSize:11,background:"#f1f5f9",padding:"3px 6px",borderRadius:4,color:"#0f172a",wordBreak:"break-all",fontFamily:"monospace"},bc={fontSize:10,color:"#64748b"};function _t({value:e,onChange:t,t:n,placeholder:o,testidPrefix:r="g7le-i18n-text-field",locales:s,candidates:a,enableExpressionTree:l=!1,expressionTreeCollapsible:d=!1,expressionSourceToggle:c=!0}){const{locale:p,templateIdentifier:f,commitText:b,classify:g,translate:m}=Bv(),[h,v]=u.useState(null),[y,_]=u.useState(!1),[w,k]=u.useState(!1),[x,$]=u.useState(!1),[C,S]=u.useState(!1),[D,E]=u.useState(!1),[,I]=u.useState(0);u.useEffect(()=>{if(typeof window>"u")return;const z=H=>{const P=H.detail;P?.templateIdentifier&&P.templateIdentifier!==f||I(A=>A+1)};return window.addEventListener(kt,z),()=>window.removeEventListener(kt,z)},[f]);const N=g(e);u.useEffect(()=>{v(null),E(!1)},[p,e]);const T=h!==null?h:N.displayValue,O=u.useCallback(async()=>{if(h!==null){if(h===N.displayValue){v(null);return}_(!0);try{const z=await b(e,h);z.kind==="created"&&z.token?(t(z.token),Et({op:"prop_i18n_create_key",sourceState:"plain_text",translationKey:z.customKey,toLocale:p,valueLength:h.length,timestamp:Date.now()})):z.kind==="updated"&&Et({op:"prop_i18n_update_value",sourceState:"custom_key",translationKey:z.customKey,toLocale:p,valueLength:h.length,timestamp:Date.now()})}finally{_(!1),v(null)}}},[h,N.displayValue,b,e,t,p]),M=u.useCallback(z=>{typeof e=="string"&&(t(ko(e,z)),N.customKey&&Tr(f,N.customKey,z,p))},[e,t,N.customKey,f,p]);if(N.binding){if(l&&typeof e=="string"){const z=nc(e),H=gn(e),P=!z&&Vt(e)!==null,A=(z||H.decomposed)&&!P;if(A&&d&&!x)return i.jsx(cx,{value:e,t:n,translate:m,onEdit:()=>$(!0),testidPrefix:`${r}-preview`});const F=A&&(d||z)?i.jsx(ws,{value:e,onChange:Y=>t(Y),t:n,candidates:a,testidPrefix:`${r}-seg`}):H.decomposed?i.jsx(uc,{node:H.node,onChange:Y=>t(Rn(Y,!1)),t:n,candidates:a,testidPrefix:`${r}-tree`,showSourceToggle:c}):null;if(F){if(!d)return F;const Y=Dr(e),V=Y?vs(Y,X=>{const Z=m(X);return Z&&Z!==X&&!Z.startsWith("$t:")?Z:""},X=>Ut(X)):[];return i.jsxs("div",{"data-testid":`${r}-builder`,style:{display:"flex",flexDirection:"column",gap:6,width:"100%",minWidth:0},children:[i.jsxs("div",{style:{display:"flex",justifyContent:"space-between",alignItems:"center",gap:6},children:[i.jsxs("button",{type:"button","data-testid":`${r}-to-plain`,onClick:()=>S(!0),style:bx,title:n("layout_editor.value_tree.to_plain"),"aria-label":n("layout_editor.value_tree.to_plain"),children:["↩ ",n("layout_editor.value_tree.to_plain")]}),i.jsxs("button",{type:"button","data-testid":`${r}-builder-collapse`,onClick:()=>$(!1),style:_c,title:n("layout_editor.value_tree.collapse"),"aria-label":n("layout_editor.value_tree.collapse"),children:["▴ ",n("layout_editor.value_tree.collapse")]})]}),C&&i.jsxs("div",{"data-testid":`${r}-to-plain-confirm`,style:mx,children:[i.jsx("p",{style:vx,children:n("layout_editor.value_tree.to_plain_lead")}),i.jsx("div",{style:_x,children:V.length===0?i.jsx("span",{style:{color:"#94a3b8"},children:n("layout_editor.prop_i18n.placeholder")}):V.map((X,Z)=>X.kind==="chip"?i.jsxs("span",{style:mc,children:["🔗 ",X.text]},Z):i.jsx("span",{style:X.kind==="ellipsis"?vc:void 0,children:X.text},Z))}),i.jsxs("p",{style:xx,children:["⚠ ",n("layout_editor.value_tree.to_plain_warn")]}),i.jsxs("div",{style:{display:"flex",justifyContent:"flex-end",gap:6},children:[i.jsx("button",{type:"button","data-testid":`${r}-to-plain-confirm-cancel`,onClick:()=>S(!1),style:_c,children:n("layout_editor.value_tree.to_plain_cancel")}),i.jsx("button",{type:"button","data-testid":`${r}-to-plain-confirm-ok`,onClick:()=>{S(!1),$(!1),t(Dr(e)||void 0)},style:wx,children:n("layout_editor.value_tree.to_plain_ok")})]})]}),F]})}}return l&&typeof e=="string"&&a&&a.length>0&&Vt(e)!==null?i.jsx(ux,{value:e,onChange:t,t:n,candidates:a,testidPrefix:`${r}-data`}):i.jsxs("div",{"data-testid":`${r}-binding`,style:Ix,children:[i.jsx("code",{style:Ax,children:typeof e=="string"?e:""}),i.jsxs("span",{style:Tx,children:["🔗 ",n("layout_editor.prop_i18n.bound_code_only")]})]})}if(!D&&typeof e=="string"&&ns(e)){const z=ed(e);return i.jsxs("div",{"data-testid":`${r}-settings-ref`,style:Cx,children:[i.jsx("div",{"data-testid":`${r}-settings-ref-chips`,style:$x,children:z.map((H,P)=>H.kind==="chip"?i.jsxs("span",{style:Ex,children:["🔗 ",H.label]},P):i.jsx("span",{children:H.label},P))}),i.jsxs("button",{type:"button","data-testid":`${r}-settings-ref-edit`,onClick:()=>E(!0),style:xc,title:n("layout_editor.value_tree.edit"),"aria-label":n("layout_editor.value_tree.edit"),children:["✎ ",n("layout_editor.value_tree.edit")]})]})}const B=i.jsxs("button",{type:"button","data-testid":`${r}-toggle`,title:`${n("layout_editor.prop_i18n.edit_languages")} (${Xn(p,n)})`,"aria-label":`${n("layout_editor.prop_i18n.edit_languages")} (${Xn(p,n)})`,"aria-expanded":w,onClick:()=>k(z=>!z),style:xc,children:["🌐 ",N.customKey?w?"⌃":"⌄":"+"]});return i.jsxs("div",{"data-testid":r,style:{display:"flex",flexDirection:"column",gap:4,width:"100%"},children:[i.jsx("div",{style:kx,children:N.paramKey&&N.customKey?i.jsx(lx,{customKey:N.customKey,value:typeof e=="string"?e:"",onChange:t,t:n,candidates:a,testidPrefix:r,showExprButton:l&&d,onToExpression:()=>{$(!0),t(Do(typeof e=="string"?e:""))},langToggle:B}):i.jsxs(i.Fragment,{children:[i.jsx("input",{type:"text","data-testid":`${r}-preview`,value:T,placeholder:o??n("layout_editor.prop_i18n.placeholder"),onChange:z=>v(z.target.value),onBlur:()=>{O(),D&&E(!1)},disabled:y,style:Sx}),a&&a.length>0&&i.jsx(dx,{value:typeof e=="string"?e:"",customKey:N.customKey,onChange:t,t:n,candidates:a,testidPrefix:r}),l&&d&&i.jsx("button",{type:"button","data-testid":`${r}-to-expr`,onClick:()=>{$(!0),t(Do(typeof e=="string"?e:""))},title:n("layout_editor.value_tree.to_expression"),"aria-label":n("layout_editor.value_tree.to_expression"),style:Mo,children:"ƒx"}),B]})}),w&&(N.customKey?i.jsx(ec,{customKey:N.customKey,templateIdentifier:f,t:n,paramLabels:bs(e),locales:s,onRemoveParam:M},N.customKey):i.jsx("div",{"data-testid":`${r}-expand-hint`,style:Dx,children:n("layout_editor.prop_i18n.create_first")}))]})}function lx({customKey:e,value:t,onChange:n,t:o,candidates:r,testidPrefix:s,showExprButton:a=!1,onToExpression:l,langToggle:d}){const{state:c}=Qe(),p=c.templateIdentifier,f=c.locale,b=Kt(),[g,m]=u.useState(null),h=u.useRef(""),v=u.useRef(null),y=u.useRef(null),_=u.useRef(null),[w,k]=u.useState(null),[x,$]=u.useState(!1),C=bs(t),S=u.useCallback(async()=>{const T=It(e,f);if(T!==void 0){h.current=T,m(T);return}const M=(await fn(p,e))?.values?.[f]??"";h.current=M,m(M)},[e,p,f]);u.useEffect(()=>{let T=!1;return(async()=>{const O=It(e,f);if(O!==void 0){T||(h.current=O,m(O));return}const M=await fn(p,e);if(T)return;const B=M?.values?.[f]??"";h.current=B,m(B)})(),()=>{T=!0}},[e,p,f]),u.useEffect(()=>{if(typeof window>"u")return;const T=O=>{const M=O.detail;if(M?.templateIdentifier&&M.templateIdentifier!==p)return;const B=It(e,f);B!==void 0&&B!==g&&(h.current=B,m(B))};return window.addEventListener(kt,T),()=>window.removeEventListener(kt,T)},[e,p,f,g]);const D=u.useCallback(T=>{m(T),Ao(p,e,f,T).then(()=>{b?.markDirty?.()})},[p,e,f,b]),E=u.useCallback(T=>{n(ko(t,T)),Tr(p,e,T,f).then(()=>{S(),b?.markDirty?.()})},[t,n,p,e,f,S,b]),I=u.useCallback(async T=>{if(!(w===null||x)){$(!0);try{g!==null&&g!==h.current&&(await Ao(p,e,f,g),h.current=g);const O=await Ar(p,f,t,w,T.sourceId,T.path,"scalar");O.kind==="ok"&&(n(O.text),await S(),b?.markDirty?.())}finally{$(!1),k(null)}}},[w,x,g,t,p,e,f,n,S,b]),N=!!r&&r.length>0;return i.jsxs("div",{"data-testid":`${s}-chipfield-wrap`,style:{flex:1,minWidth:0,display:"flex",alignItems:"center",gap:4},children:[i.jsx("div",{ref:v,"data-testid":`${s}-chipfield`,style:{flex:1,minWidth:0},children:g===null?i.jsx("div",{"data-testid":`${s}-chipfield-loading`,style:{fontSize:11,color:"#94a3b8",padding:"4px 0"},children:o("layout_editor.translation.loading")}):i.jsx(Cr,{value:g,onChange:D,t:o,paramLabels:C,testIdSuffix:`${s}-field`,onRequestInsert:N?T=>k(T):void 0,onRemoveChip:E,hideInsertButton:!0,caretRef:_})}),N&&i.jsx("button",{ref:y,type:"button","data-testid":`${s}-plus-data-btn`,onClick:()=>k(_.current?_.current():g?.length??0),title:o("layout_editor.inline_binding.insert_data"),"aria-label":o("layout_editor.inline_binding.insert_data"),style:Mo,children:"+"}),a&&l&&i.jsx("button",{type:"button","data-testid":`${s}-to-expr`,onClick:l,title:o("layout_editor.value_tree.to_expression"),"aria-label":o("layout_editor.value_tree.to_expression"),style:Mo,children:"ƒx"}),d,N&&i.jsx(Zn,{anchorRef:y,open:w!==null,onClose:()=>k(null),children:i.jsx("div",{"data-testid":`${s}-chipfield-picker`,children:i.jsx($t,{candidates:r,t:o,onSelect:T=>void I(T),testIdSuffix:`${s}-field-insert`,defaultOpen:!0,floating:!1})})})]})}function dx({value:e,customKey:t,onChange:n,t:o,candidates:r,testidPrefix:s}){const{state:a}=Qe(),l=a.templateIdentifier,d=a.selectedRoute?.layoutName??null,c=a.locale,p=Kt(),[f,b]=u.useState(!1),[g,m]=u.useState(!1),h=u.useRef(null),v=u.useCallback(w=>{const k=o(w);return k&&k!==w&&!k.startsWith("$t:")?k:""},[o]),y=u.useCallback(w=>w.replace(/\$t:[a-zA-Z0-9._-]+/g,k=>v(k.slice(3))).replace(/\s+/g," ").trim(),[v]),_=u.useCallback(async w=>{if(!g){m(!0);try{if(t){const k=await Ar(l,c,e,Number.MAX_SAFE_INTEGER,w.sourceId,w.path,"scalar");k.kind==="ok"&&(n(k.text),p?.markDirty?.())}else{const k=await ys(l,d,c,e,e.length,w.sourceId,w.path,"scalar",y,x=>o(x));k.kind==="ok"&&(n(k.text),p?.markDirty?.())}}finally{m(!1),b(!1)}}},[g,t,l,d,c,e,n,p,o,y]);return i.jsxs("div",{"data-testid":`${s}-plus-data`,style:{display:"inline-block"},children:[i.jsx("button",{ref:h,type:"button","data-testid":`${s}-plus-data-btn`,disabled:g,onClick:()=>b(w=>!w),title:o("layout_editor.inline_binding.insert_data"),"aria-label":o("layout_editor.inline_binding.insert_data"),style:Mo,children:"+"}),i.jsx(Zn,{anchorRef:h,open:f,onClose:()=>b(!1),children:i.jsx($t,{candidates:r,t:o,onSelect:w=>void _(w),testIdSuffix:`${s}-plus-insert`,defaultOpen:!0,floating:!1})})]})}function cx({value:e,t,translate:n,onEdit:o,testidPrefix:r}){const s=vs(e,a=>{const l=n(a);return l&&l!==a&&!l.startsWith("$t:")?l:""},a=>Ut(a));return i.jsxs("div",{"data-testid":r,style:gx,children:[i.jsx("div",{"data-testid":`${r}-text`,style:hx,children:s.length===0?i.jsx("span",{style:{color:"#94a3b8"},children:t("layout_editor.prop_i18n.placeholder")}):s.map((a,l)=>a.kind==="chip"?i.jsxs("span",{style:mc,children:["🔗 ",a.text]},l):i.jsx("span",{style:a.kind==="ellipsis"?vc:void 0,children:a.text},l))}),i.jsxs("button",{type:"button","data-testid":`${r}-edit`,onClick:o,style:yx,title:t("layout_editor.value_tree.edit"),"aria-label":t("layout_editor.value_tree.edit"),children:["✎ ",t("layout_editor.value_tree.edit")]})]})}function ux({value:e,onChange:t,t:n,candidates:o,testidPrefix:r}){const[s,a]=u.useState(!1),l=u.useRef(null),d=Ut(e);return i.jsxs("div",{"data-testid":r,style:{display:"flex",flexDirection:"column",gap:4,width:"100%",minWidth:0},children:[i.jsxs("div",{style:px,children:[i.jsxs("span",{"data-testid":`${r}-chip`,style:fx,children:["🔗 ",d]}),i.jsx("button",{ref:l,type:"button","data-testid":`${r}-change`,onClick:()=>a(c=>!c),style:Mo,title:n("layout_editor.value_tree.change_data"),"aria-label":n("layout_editor.value_tree.change_data"),children:n("layout_editor.value_tree.change_data")})]}),i.jsx(Zn,{anchorRef:l,open:s,onClose:()=>a(!1),children:i.jsx($t,{candidates:o,t:n,onSelect:c=>{const p=c.path?c.path.split(".").filter(Boolean):[],f=[c.sourceId,...p].join("?.");t(`{{${f}}}`),a(!1)},testIdSuffix:`${r}-pick`,defaultOpen:!0,floating:!1})})]})}const px={display:"flex",alignItems:"center",gap:6,width:"100%",minWidth:0,flexWrap:"wrap"},fx={fontSize:11,background:"#eef2ff",color:"#4338ca",padding:"3px 8px",borderRadius:12,wordBreak:"break-all"},gx={display:"flex",alignItems:"center",gap:8,width:"100%",minWidth:0},hx={flex:1,minWidth:0,display:"flex",flexWrap:"wrap",alignItems:"center",gap:4,padding:"5px 8px",fontSize:12,color:"#334155",border:"1px solid #e2e8f0",borderRadius:6,background:"#f8fafc",overflow:"hidden"},mc={fontSize:11,background:"#eef2ff",color:"#4338ca",padding:"1px 6px",borderRadius:10,whiteSpace:"nowrap"},vc={color:"#94a3b8"},yx={padding:"4px 10px",fontSize:11,border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",color:"#2563eb",cursor:"pointer",whiteSpace:"nowrap",flexShrink:0},_c={padding:"3px 10px",fontSize:11,border:"1px solid #cbd5e1",borderRadius:6,background:"#f8fafc",color:"#475569",cursor:"pointer",whiteSpace:"nowrap"},bx={padding:"3px 10px",fontSize:11,border:"1px solid #fca5a5",borderRadius:6,background:"#fef2f2",color:"#b91c1c",cursor:"pointer",whiteSpace:"nowrap"},mx={display:"flex",flexDirection:"column",gap:6,padding:10,border:"1px solid #fca5a5",borderRadius:8,background:"#fffbeb"},vx={margin:0,fontSize:12,color:"#334155"},_x={padding:"5px 8px",fontSize:12,color:"#0f172a",background:"#fff",border:"1px solid #e2e8f0",borderRadius:6,wordBreak:"break-all"},xx={margin:0,fontSize:11,color:"#b45309"},wx={padding:"3px 12px",fontSize:11,border:"1px solid #dc2626",borderRadius:6,background:"#dc2626",color:"#fff",cursor:"pointer",whiteSpace:"nowrap"},kx={display:"flex",gap:4,alignItems:"center",width:"100%",minWidth:0},Sx={flex:1,padding:"5px 8px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,minWidth:0,width:"100%"},xc={padding:"4px 8px",fontSize:11,border:"1px solid #cbd5e1",borderRadius:6,background:"#f8fafc",color:"#475569",cursor:"pointer",whiteSpace:"nowrap"},Mo={padding:"4px 8px",fontSize:13,lineHeight:1,border:"1px solid #cbd5e1",borderRadius:6,background:"#f8fafc",color:"#475569",cursor:"pointer",whiteSpace:"nowrap"},Cx={display:"flex",alignItems:"center",gap:6,width:"100%",minWidth:0,flexWrap:"wrap"},$x={flex:1,minWidth:0,display:"flex",flexWrap:"wrap",alignItems:"center",gap:4,fontSize:12,color:"#334155"},Ex={fontSize:11,background:"#eef2ff",color:"#4338ca",padding:"2px 8px",borderRadius:12,whiteSpace:"nowrap"},Ix={display:"flex",flexDirection:"column",gap:2,width:"100%"},Ax={fontSize:11,background:"#f1f5f9",padding:"3px 6px",borderRadius:4,color:"#0f172a",wordBreak:"break-all"},Tx={fontSize:10,color:"#64748b"},Dx={fontSize:11,color:"#94a3b8",padding:"6px 8px",border:"1px dashed #cbd5e1",borderRadius:6,background:"#f8fafc"};function Ss({value:e,onChange:t,candidates:n,t:o}){const r=Array.isArray(e)?e:[],s=n??[],[a,l]=u.useState(!1),d=g=>s.find(m=>m.value===g)?.label??g,c=g=>d(g)!==g,p=g=>{const m=r.filter(h=>h!==g);t(m.length===0?void 0:m)},f=g=>{r.includes(g)||(t([...r,g]),l(!1))},b=s.filter(g=>!r.includes(g.value));return i.jsxs("div",{className:"g7le-widget g7le-widget--tag-input","data-testid":"g7le-widget-tag-input",style:zx,children:[i.jsxs("div",{style:Nx,children:[r.map(g=>i.jsxs("span",{"data-testid":`g7le-tag-chip-${g}`,style:Mx,children:[i.jsx("span",{style:Lx,children:d(g)}),c(g)&&i.jsx("code",{"data-testid":`g7le-tag-chip-id-${g}`,style:Px,children:g}),i.jsx("button",{type:"button","aria-label":`remove ${g}`,"data-testid":`g7le-tag-remove-${g}`,onClick:()=>p(g),style:Ox,children:"✕"})]},g)),i.jsx("button",{type:"button","data-testid":"g7le-tag-add",onClick:()=>l(g=>!g),disabled:b.length===0,style:Bx,children:o("layout_editor.control.tag_add")})]}),a&&b.length>0&&i.jsx("div",{"data-testid":"g7le-tag-candidates",style:Wx,children:b.map(g=>{const m=g.value!==g.label;return i.jsxs("button",{type:"button","data-testid":`g7le-tag-candidate-${g.value}`,onClick:()=>f(g.value),style:jx,children:[i.jsx("span",{style:Hx,children:g.label}),m&&i.jsx("code",{"data-testid":`g7le-tag-candidate-id-${g.value}`,style:Fx,children:g.value})]},g.value)})})]})}const zx={display:"flex",flexDirection:"column",gap:6},Nx={display:"flex",flexWrap:"wrap",gap:4,alignItems:"center"},Mx={display:"inline-flex",alignItems:"center",gap:4,padding:"2px 6px",fontSize:12,background:"#e0e7ff",color:"#1d4ed8",borderRadius:12},Lx={fontSize:12},Px={fontSize:10,color:"#4f46e5",background:"#c7d2fe",borderRadius:4,padding:"0 4px",fontFamily:"monospace"},Ox={border:"none",background:"transparent",color:"#1d4ed8",cursor:"pointer",fontSize:10,padding:0,lineHeight:1},Bx={padding:"2px 8px",fontSize:12,border:"1px dashed #94a3b8",borderRadius:12,background:"#fff",cursor:"pointer",color:"#475569"},Wx={display:"flex",flexDirection:"column",maxHeight:180,overflowY:"auto",border:"1px solid #cbd5e1",borderRadius:6,background:"#fff"},jx={display:"flex",flexDirection:"column",gap:1,textAlign:"left",padding:"6px 10px",fontSize:12,border:"none",borderBottom:"1px solid #f1f5f9",background:"transparent",cursor:"pointer",color:"#0f172a"},Hx={fontSize:12,color:"#0f172a"},Fx={fontSize:10,color:"#64748b",fontFamily:"monospace"},wc=new Map;function Ge(e,t){wc.set(e,t)}function zn(e){return e?wc.get(e)??null:null}function Or(e,t){const o=e("meta",{})?.[t];return typeof o=="string"?o:""}function Kx({getValue:e,patch:t,t:n,fieldT:o,spec:r,permissionCandidates:s,bindingCandidates:a}){const l=o??n,d=Or(e,"title"),c=Or(e,"description"),p=Or(e,"icon"),f=Or(e,"editor_label"),b=e("permissions",[]),g=u.useCallback((y,_)=>{const w={...e("meta",{})??{}};_===void 0||_===""?delete w[y]:w[y]=_,t("meta",w)},[e,t]),m=u.useCallback(y=>{const _=Array.isArray(y)?y:[];_.length===0?t("permissions",void 0):t("permissions",_)},[t]),h=zn("icon-picker"),v=Ct(r,"icon-picker")??Ct(r,"icon")??(r?.controls?Object.values(r.controls).find(y=>y&&typeof y=="object"&&y.widget==="icon-picker")??null:null);return i.jsxs("div",{className:"g7le-meta-form","data-testid":"g7le-meta-form",style:Ux,children:[i.jsx(Lo,{label:n("layout_editor.page_settings.meta.title"),children:i.jsx(_t,{value:d,onChange:y=>g("title",y),t:l,testidPrefix:"g7le-meta-title",candidates:a,enableExpressionTree:!0,expressionTreeCollapsible:!0})}),i.jsx(Lo,{label:n("layout_editor.page_settings.meta.description"),children:i.jsx(_t,{value:c,onChange:y=>g("description",y),t:l,testidPrefix:"g7le-meta-description",candidates:a,enableExpressionTree:!0,expressionTreeCollapsible:!0})}),i.jsx(Lo,{label:n("layout_editor.page_settings.meta.icon"),children:h?i.jsx(Vx,{icon:p,t:n,onChange:y=>g("icon",typeof y=="string"?y:void 0),IconWidget:h,iconControl:v??{widget:"icon-picker"}}):i.jsx("input",{type:"text","data-testid":"g7le-meta-icon",value:p,placeholder:n("layout_editor.page_settings.meta.icon_placeholder"),onChange:y=>g("icon",y.target.value),style:Xx})}),i.jsx(Lo,{label:n("layout_editor.page_settings.meta.editor_label"),hint:n("layout_editor.page_settings.meta.editor_label_hint"),children:i.jsx(_t,{value:f,onChange:y=>g("editor_label",y),t:l,testidPrefix:"g7le-meta-editor-label",candidates:a,enableExpressionTree:!0,expressionTreeCollapsible:!0})}),i.jsx(Lo,{label:n("layout_editor.page_settings.meta.permissions"),hint:n("layout_editor.page_settings.meta.permissions_hint"),children:i.jsx("div",{"data-testid":"g7le-meta-permissions",children:i.jsx(Ss,{control:{widget:"tag-input"},value:b,onChange:m,t:n,candidates:s})})})]})}function Lo({label:e,hint:t,children:n}){return i.jsxs("div",{className:"g7le-meta-field",style:qx,children:[i.jsx("label",{style:Gx,children:e}),n,t?i.jsxs("p",{style:Yx,children:["ⓘ ",t]}):null]})}function Vx({icon:e,onChange:t,t:n,IconWidget:o,iconControl:r}){const[s,a]=u.useState(!1),l=e?/\bfa-/.test(e)?i.jsx("i",{className:e,"aria-hidden":"true"}):e:"📄";return i.jsxs("div",{"data-testid":"g7le-meta-icon",style:{display:"flex",flexDirection:"column",gap:6,minWidth:0},children:[i.jsxs("button",{type:"button","data-testid":"g7le-meta-icon-toggle","aria-expanded":s,onClick:()=>a(d=>!d),style:{display:"flex",alignItems:"center",gap:8,padding:"5px 8px",border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",cursor:"pointer",fontSize:12,color:"#475569",width:"fit-content"},children:[i.jsx("span",{style:{fontSize:16},"aria-hidden":"true",children:l}),i.jsx("span",{children:e||n("layout_editor.page_settings.meta.icon_placeholder")}),i.jsx("span",{"aria-hidden":"true",children:s?"▴":"▾"})]}),s?i.jsx(o,{control:r,value:e,onChange:d=>{t(d),a(!1)},t:n}):null]})}const Ux={display:"flex",flexDirection:"column",gap:16,minWidth:0},qx={display:"flex",flexDirection:"column",gap:6,minWidth:0},Gx={fontSize:13,fontWeight:600,color:"#0f172a"},Yx={margin:0,fontSize:11,color:"#94a3b8"},Xx={padding:"5px 8px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,minWidth:0,width:"100%"};function Br({checked:e,onChange:t,label:n,testid:o,ariaLabel:r,disabled:s=!1}){return i.jsxs("label",{style:{...ew,...s?{cursor:"not-allowed"}:{}},children:[i.jsx("button",{type:"button","data-testid":o,role:"switch","aria-checked":e,"aria-label":r,disabled:s,onClick:()=>t(!e),style:tw(e,s),children:i.jsx("span",{style:nw(e)})}),n!=null?i.jsx("span",{style:ow(s),children:n}):null]})}function Wr({disabled:e,children:t,testid:n,style:o}){return i.jsx("div",{"data-testid":n,"data-disabled":e?"true":"false","aria-disabled":e,style:{...o,...e?{opacity:.5,pointerEvents:"none",userSelect:"none"}:{}},children:t})}function Jx({filled:e,sourceLabel:t,onRevert:n,revertLabel:o,hint:r,children:s,testid:a}){const l=e&&typeof n=="function",d=!e&&t!=null&&t!=="";return i.jsxs("div",{"data-testid":a,style:Zx,children:[s,l?i.jsxs("button",{type:"button","data-testid":a?`${a}-revert`:void 0,onClick:n,title:r??o,"aria-label":o,style:Rx,children:["↩ ",o]}):d?i.jsx("span",{"data-testid":a?`${a}-source`:void 0,title:r,style:Qx,children:t}):null]})}const Zx={position:"relative",minWidth:0,width:"100%"},Qx={position:"absolute",right:8,top:"50%",transform:"translateY(-50%)",fontSize:10,color:"#94a3b8",background:"#f1f5f9",borderRadius:4,padding:"1px 6px",pointerEvents:"none",maxWidth:"45%",overflow:"hidden",whiteSpace:"nowrap",textOverflow:"ellipsis"},Rx={position:"absolute",right:6,top:"50%",transform:"translateY(-50%)",fontSize:10,color:"#0369a1",background:"#e0f2fe",border:"1px solid #bae6fd",borderRadius:4,padding:"1px 6px",cursor:"pointer",whiteSpace:"nowrap"},ew={display:"inline-flex",alignItems:"center",gap:8,cursor:"pointer"},tw=(e,t)=>({position:"relative",width:38,height:22,flexShrink:0,borderRadius:999,border:"none",padding:0,background:t?"#e2e8f0":e?"#2563eb":"#cbd5e1",cursor:t?"not-allowed":"pointer",transition:"background 0.15s ease"}),nw=e=>({position:"absolute",top:2,left:e?18:2,width:18,height:18,borderRadius:"50%",background:"#fff",boxShadow:"0 1px 2px rgba(0,0,0,0.2)",transition:"left 0.15s ease"}),ow=e=>({fontSize:13,fontWeight:600,color:e?"#94a3b8":"#0f172a"}),rw=/\$(?:core|module|plugin)_settings:[a-zA-Z0-9._:-]+/;function iw(e){const t=[];let n=!1;const o=s=>{t.push({kind:"text",text:s}),n=!0},r=(s,a,l)=>{n||o(""),t.push({kind:"chip",raw:s,text:a,removable:l}),n=!1};for(const s of sw(e))s.kind==="text"?o(s.raw):r(s.raw,s.label,s.removable);return n||o(""),t}function sw(e){const t=[];if(typeof e!="string"||e.length===0)return t;const n=e.length;let o=0,r=0;const s=a=>{a>r&&t.push({kind:"text",raw:e.slice(r,a),label:"",removable:!1})};for(;o=n){o++;continue}s(o);const l=e.slice(o,a+2);t.push({kind:"chip",raw:l,label:Ut(l),removable:!0}),o=a+2,r=o;continue}if(e[o]==="$"){const a=rw.exec(e.slice(o));if(a&&a.index===0){s(o);const l=a[0],d=l.split(":").pop()||l;t.push({kind:"chip",raw:l,label:d,removable:!1}),o+=l.length,r=o;continue}}o++}return s(n),t}function aw(e,t,n,o){const r=[];let s=!1;return e.forEach((a,l)=>{if(a.kind==="chip"){if(!s&&a.raw===t){s=!0;return}r.push(a.raw??"")}else{const d=a.text??"";if(l===n){const c=Math.max(0,Math.min(o,d.length));r.push(d.slice(0,c),t,d.slice(c))}else r.push(d)}}),r.join("")}function lw(e,t,n){const o=Math.max(0,Math.min(t,e.length));return e.slice(0,o)+n+e.slice(o)}function dw(e,t){const n=e.indexOf(t);return n<0?e:e.slice(0,n)+e.slice(n+t.length)}function Cs({value:e,onChange:t,t:n,candidates:o,onDone:r,testidPrefix:s="g7le-binding-chip-text",disabled:a=!1}){const l=u.useMemo(()=>iw(e),[e]),d=u.useRef(null),c=!!o&&o.length>0,[p,f]=u.useState(null),[b,g]=u.useState(null),[m,h]=u.useState(null),[v,y]=u.useState(!1),_=u.useRef(0),w=u.useRef(null),k=u.useMemo(()=>l.map(P=>P.kind==="chip"?`c:${P.raw}`:"t").join("|"),[l]),x=u.useRef(null),$=u.useRef([]),C=u.useRef(!1);u.useLayoutEffect(()=>{const P=C.current;C.current=!1,!(!P&&x.current===k)&&(x.current=k,l.forEach((A,L)=>{if(A.kind!=="text")return;const F=$.current[L];F&&F.textContent!==(A.text??"")&&(F.textContent=A.text??"")}))},[k,l,e]);const S=u.useCallback(()=>{const P=d.current;if(!P)return e;const A=[];for(const L of Array.from(P.querySelectorAll("[data-seg]")))L.dataset.seg==="chip"?A.push(L.dataset.raw??""):A.push(L.textContent??"");return A.join("")},[e]),D=u.useCallback(()=>{a||t(S())},[a,t,S]),E=u.useRef(null),I=u.useRef(!1),N=(P,A)=>{if(!a){P.preventDefault(),P.stopPropagation(),E.current={raw:A,pointerId:P.pointerId},I.current=!1,f(A);try{P.currentTarget.setPointerCapture(P.pointerId)}catch{}}},T=P=>{const A=E.current;if(!A||P.pointerId!==A.pointerId)return;I.current=!0;const L=B(P.clientX,P.clientY);L&&(g({slotIdx:L.slotIdx,offset:L.offset}),h(uw(d.current,L.slotIdx,L.offset)))},O=(P,A)=>{if(P.stopPropagation(),I.current){I.current=!1;return}C.current=!0,t(dw(e,A))},M=P=>{const A=E.current;if(!A||P.pointerId!==A.pointerId)return;try{P.currentTarget.releasePointerCapture(P.pointerId)}catch{}E.current=null,I.current&&typeof setTimeout=="function"&&setTimeout(()=>{I.current=!1},0);const L=B(P.clientX,P.clientY);if(f(null),g(null),h(null),!L)return;const F=d.current;if(!F)return;const V=Array.from(F.querySelectorAll("[data-seg]")).map(Z=>Z.dataset.seg==="chip"?{kind:"chip",raw:Z.dataset.raw??""}:{kind:"text",text:Z.textContent??""}),X=aw(V,A.raw,L.slotIdx,L.offset);X!==e&&(C.current=!0,t(X))},B=(P,A)=>{const L=d.current;if(!L)return null;const F=Array.from(L.querySelectorAll("[data-seg]")),Y=L.ownerDocument,V=Y.elementFromPoint(P,A),X=V?.closest('[data-seg="text"]');if(X)return{slotIdx:F.indexOf(X),offset:cw(Y,X,P,A)};const Z=V?.closest('[data-seg="chip"]');if(Z){const oe=F.indexOf(Z),G=Z.getBoundingClientRect();if(P=0){const ee=F[oe-1];return{slotIdx:oe-1,offset:(ee.textContent??"").length}}if(oe+1({s:oe,i:G})).filter(oe=>oe.s.dataset.seg==="text").pop();return ie?{slotIdx:ie.i,offset:(ie.s.textContent??"").length}:null},z=()=>{a||!c||(_.current=pw(d.current,e.length),y(P=>!P))},H=P=>{const A=P.path?P.path.split(".").filter(Boolean):[],L=[P.sourceId,...A].join("?.");C.current=!0,t(lw(e,_.current,`{{${L}}}`)),y(!1)};return i.jsxs("div",{"data-testid":s,style:{display:"flex",flexDirection:"column",gap:4},children:[i.jsxs("div",{ref:d,"data-testid":`${s}-box`,className:"g7le-binding-chip-input",style:fw,children:[l.map((P,A)=>P.kind==="chip"?i.jsxs("span",{"data-seg":"chip","data-raw":P.raw,"data-testid":`${s}-chip-${A}`,contentEditable:!1,onPointerDown:L=>N(L,P.raw??""),onPointerMove:T,onPointerUp:M,title:P.removable?n("layout_editor.value_tree.change_data"):n("layout_editor.prop_i18n.bound_code_only"),style:{...bw,...p===P.raw?mw:null,touchAction:"none"},children:[i.jsx("span",{"aria-hidden":"true",style:vw,children:"⠿"}),i.jsx("span",{"aria-hidden":"true",style:{marginRight:3},children:"🔗"}),i.jsx("span",{children:P.text}),P.removable&&!a&&i.jsx("button",{type:"button","data-testid":`${s}-chip-remove-${A}`,"aria-label":n("layout_editor.inline_binding.clear"),title:n("layout_editor.inline_binding.clear"),onPointerDown:L=>{L.stopPropagation(),L.preventDefault()},onClick:L=>O(L,P.raw??""),style:_w,children:"✕"})]},`chip-${P.raw}-${A}`):i.jsx("span",{ref:L=>{$.current[A]=L},"data-seg":"text",contentEditable:!a,suppressContentEditableWarning:!0,onInput:D,"data-testid":`${s}-text-${A}`,style:{...gw,...p?hw:null,...b?.slotIdx===A?yw:null}},`text-slot-${A}`)),m&&i.jsx("span",{"aria-hidden":"true","data-testid":`${s}-caret`,style:{position:"absolute",left:m.left,top:m.top,width:2,height:m.height,background:"#2563eb",borderRadius:1,pointerEvents:"none",boxShadow:"0 0 0 1px rgba(37,99,235,0.3)"}})]}),i.jsxs("div",{style:{display:"flex",alignItems:"center",gap:6,flexWrap:"wrap"},children:[r&&i.jsxs("button",{type:"button","data-testid":`${s}-done`,onClick:r,style:kc,title:n("layout_editor.value_tree.done"),"aria-label":n("layout_editor.value_tree.done"),children:["✓ ",n("layout_editor.value_tree.done")]}),c&&i.jsxs("div",{style:{position:"relative"},children:[i.jsxs("button",{ref:w,type:"button","data-testid":`${s}-insert`,onClick:z,disabled:a,style:kc,title:n("layout_editor.inline_binding.insert_data"),"aria-label":n("layout_editor.inline_binding.insert_data"),children:["+ ",n("layout_editor.inline_binding.insert_data")]}),i.jsx(Zn,{anchorRef:w,open:v,onClose:()=>y(!1),children:i.jsx($t,{candidates:o,t:n,onSelect:H,testIdSuffix:`${s}-insert`,defaultOpen:!0,floating:!1})})]}),i.jsx("span",{style:xw,children:n("layout_editor.translation.placeholder_locked")})]})]})}function cw(e,t,n,o){const r=(t.textContent??"").length;try{const a=e;let l=null,d=0;if(typeof a.caretRangeFromPoint=="function"){const c=a.caretRangeFromPoint(n,o);c&&(l=c.startContainer,d=c.startOffset)}else if(typeof a.caretPositionFromPoint=="function"){const c=a.caretPositionFromPoint(n,o);c&&(l=c.offsetNode,d=c.offset)}if(l&&t.contains(l))return Math.max(0,Math.min(d,r))}catch{}const s=t.getBoundingClientRect();return n>s.left+s.width/2?r:0}function uw(e,t,n){if(!e)return null;const r=Array.from(e.querySelectorAll("[data-seg]"))[t];if(!r)return null;const s=e.getBoundingClientRect();try{const d=r.firstChild,c=e.ownerDocument;if(d&&d.nodeType===Node.TEXT_NODE&&(d.textContent??"").length>0){const p=c.createRange(),f=Math.max(0,Math.min(n,(d.textContent??"").length));p.setStart(d,f),p.setEnd(d,f);const b=p.getClientRects(),g=b.length>0?b[0]:p.getBoundingClientRect();if(g&&(g.height>0||g.width>=0))return{left:g.left-s.left,top:g.top-s.top,height:g.height||r.getBoundingClientRect().height}}}catch{}const a=r.getBoundingClientRect();return{left:(n>0?a.right:a.left)-s.left,top:a.top-s.top,height:a.height||16}}function pw(e,t){if(!e)return t;try{const n=e.ownerDocument.defaultView?.getSelection();if(!n||n.rangeCount===0)return t;const o=n.getRangeAt(0);let r=o.startContainer,s=null;for(;r&&r!==e;){if(r instanceof HTMLElement&&r.dataset.seg==="text"){s=r;break}r=r.parentNode}if(!s||!e.contains(s))return t;const a=Array.from(e.querySelectorAll("[data-seg]"));let l=0;for(const d of a){if(d===s)break;l+=d.dataset.seg==="chip"?(d.dataset.raw??"").length:(d.textContent??"").length}return l+o.startOffset}catch{return t}}const fw={position:"relative",display:"flex",flexWrap:"wrap",alignItems:"center",gap:2,minHeight:30,padding:"4px 8px",border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",fontSize:12,lineHeight:1.6,minWidth:0,width:"100%",boxSizing:"border-box"},gw={outline:"none",minWidth:8,minHeight:"1.2em",whiteSpace:"pre-wrap",color:"#0f172a",display:"inline-block",wordBreak:"break-all"},hw={boxShadow:"inset 0 -2px 0 #93c5fd",background:"#eff6ff",borderRadius:2},yw={background:"#dbeafe",boxShadow:"inset 0 -2px 0 #2563eb"},bw={display:"inline-flex",alignItems:"center",gap:1,background:"linear-gradient(180deg,#eff6ff,#dbeafe)",color:"#1d4ed8",border:"1px dashed #60a5fa",borderRadius:9999,padding:"1px 8px 1px 4px",margin:"0 1px",fontSize:11,fontWeight:600,cursor:"grab",userSelect:"none",whiteSpace:"nowrap",boxShadow:"0 1px 2px rgba(37,99,235,0.18)",verticalAlign:"middle"},mw={opacity:.5,borderStyle:"solid",cursor:"grabbing"},vw={color:"#2563eb",opacity:.7,fontSize:11,marginRight:1,cursor:"grab"},_w={marginLeft:4,width:14,height:14,lineHeight:"12px",padding:0,border:"none",borderRadius:9999,background:"rgba(29,78,216,0.12)",color:"#1d4ed8",fontSize:9,fontWeight:700,cursor:"pointer",display:"inline-flex",alignItems:"center",justifyContent:"center",flex:"0 0 auto"},kc={padding:"4px 8px",fontSize:11,border:"1px solid #cbd5e1",borderRadius:6,background:"#f8fafc",color:"#475569",cursor:"pointer",whiteSpace:"nowrap"},xw={fontSize:10,color:"#94a3b8"},ww=ed;function $s(e){return Gb(e)}function Ke({value:e,onChange:t,t:n,candidates:o,placeholder:r,testidPrefix:s="g7le-data-chip-value",readOnly:a=!1,enableExpressionTree:l=!0}){const d=typeof e=="string"?e:"",c=!!o&&o.length>0,[p,f]=u.useState(!1),[b,g]=u.useState(!1),[m,h]=u.useState(!1),v=u.useCallback(C=>i.jsx(Ke,{value:C.value,onChange:S=>C.onChange(S),t:C.t,candidates:C.candidates,testidPrefix:C.testidPrefix,enableExpressionTree:!0}),[]),y=()=>i.jsx("div",{style:{display:"flex",gap:4,alignItems:"center",width:"100%",minWidth:0},children:i.jsx("input",{type:"text","data-testid":`${s}-input`,value:d,placeholder:r??n("layout_editor.page_settings.seo.value_placeholder"),readOnly:a,onChange:C=>t(C.target.value),onFocus:()=>f(!0),onBlur:()=>f(!1),style:{flex:1,padding:"5px 8px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,minWidth:0,width:"100%",background:a?"#f1f5f9":"#fff",color:a?"#64748b":"#0f172a"}})}),_=u.useCallback(C=>{const S=d?`${d} ${C.expression}`:C.expression;t(S)},[d,t]);if(!l||a)return i.jsxs("div",{"data-testid":s,style:hn,children:[y(),!a&&c&&i.jsx($t,{candidates:o,t:n,onSelect:_,testIdSuffix:`${s}-pick`})]});const w=i_(d),k=nc(d),x=Vt(d),$=C=>{const S=Dr(d),D=S?vs(S,()=>"",E=>Ut(E)):[];return i.jsxs(i.Fragment,{children:[i.jsx("div",{style:{display:"flex",justifyContent:"flex-start"},children:i.jsxs("button",{type:"button","data-testid":`${C}-to-plain`,onClick:()=>h(!0),style:$w,title:n("layout_editor.value_tree.to_plain"),"aria-label":n("layout_editor.value_tree.to_plain"),children:["↩ ",n("layout_editor.value_tree.to_plain")]})}),m&&i.jsxs("div",{"data-testid":`${C}-to-plain-confirm`,style:Ew,children:[i.jsx("p",{style:Iw,children:n("layout_editor.value_tree.to_plain_lead")}),i.jsx("div",{style:Aw,children:D.length===0?i.jsx("span",{style:{color:"#94a3b8"},children:n("layout_editor.page_settings.seo.value_placeholder")}):D.map((E,I)=>E.kind==="chip"?i.jsxs("span",{style:Es,children:["🔗 ",E.text]},I):i.jsx("span",{style:E.kind==="ellipsis"?{color:"#94a3b8"}:void 0,children:E.text},I))}),i.jsxs("p",{style:Tw,children:["⚠ ",n("layout_editor.value_tree.to_plain_warn")]}),i.jsxs("div",{style:{display:"flex",justifyContent:"flex-end",gap:6},children:[i.jsx("button",{type:"button","data-testid":`${C}-to-plain-confirm-cancel`,onClick:()=>h(!1),style:qt,children:n("layout_editor.value_tree.to_plain_cancel")}),i.jsx("button",{type:"button","data-testid":`${C}-to-plain-confirm-ok`,onClick:()=>{h(!1),t(Dr(d)||"")},style:Dw,children:n("layout_editor.value_tree.to_plain_ok")})]})]})]})};if(!p&&(w==="expression"||k)){if(k)return i.jsxs("div",{"data-testid":s,style:hn,children:[!a&&$(`${s}-seg`),i.jsx(ws,{value:d,onChange:S=>t(S),t:n,candidates:o,testidPrefix:`${s}-seg`,renderLeafInput:v})]});const C=gn(d);return i.jsxs("div",{"data-testid":s,style:hn,children:[!a&&$(`${s}-tree`),i.jsx(uc,{node:C.node,onChange:S=>t(Rn(S,!1)),t:n,candidates:o,testidPrefix:`${s}-tree`,renderLeafInput:v})]})}if(x&&!p&&!b)return i.jsx(kw,{value:d,onChange:t,t:n,candidates:o,testidPrefix:s,onEditRaw:()=>g(!0)});if(!p&&!b&&$s(d)){const C=ww(d);return i.jsx("div",{"data-testid":s,style:hn,children:i.jsxs("div",{style:Sc,children:[i.jsx("div",{"data-testid":`${s}-chips`,style:Sw,children:C.map((S,D)=>S.kind==="chip"?i.jsxs("span",{style:Es,children:["🔗 ",S.label]},D):i.jsx("span",{children:S.label},D))}),i.jsxs("button",{type:"button","data-testid":`${s}-edit-raw`,onClick:()=>g(!0),style:qt,title:n("layout_editor.value_tree.edit"),"aria-label":n("layout_editor.value_tree.edit"),children:["✎ ",n("layout_editor.value_tree.edit")]})]})})}return b&&$s(d)?i.jsxs("div",{"data-testid":s,style:hn,children:[i.jsx(Cs,{value:d,onChange:t,t:n,candidates:o,disabled:a,onDone:()=>g(!1),testidPrefix:`${s}-chipedit`}),!a&&w!=="raw"&&i.jsxs("div",{style:{display:"flex",gap:4,flexWrap:"wrap"},children:[i.jsxs("button",{type:"button","data-testid":`${s}-to-expr`,onClick:()=>t(Do(d)),style:qt,title:n("layout_editor.value_tree.to_expression"),"aria-label":n("layout_editor.value_tree.to_expression"),children:["ƒx ",n("layout_editor.value_tree.to_expression")]}),i.jsxs("button",{type:"button","data-testid":`${s}-to-fallback`,onClick:()=>t(_s(d)),style:qt,title:n("layout_editor.value_tree.segment.fallback"),"aria-label":n("layout_editor.value_tree.segment.fallback"),children:["?? ",n("layout_editor.value_tree.segment.fallback")]})]}),w==="raw"&&i.jsxs("p",{"data-testid":`${s}-raw-hint`,style:Is,children:["ⓘ ",n("layout_editor.value_tree.raw_expression_hint")]})]}):!p&&$s(d)?i.jsxs("div",{"data-testid":s,style:hn,children:[i.jsx(Cs,{value:d,onChange:t,t:n,candidates:o,disabled:a,onDone:()=>{g(!1),f(!1)},testidPrefix:`${s}-chipedit`}),!a&&w!=="raw"&&i.jsxs("div",{style:{display:"flex",gap:4,flexWrap:"wrap"},children:[i.jsxs("button",{type:"button","data-testid":`${s}-to-expr`,onClick:()=>t(Do(d)),style:qt,title:n("layout_editor.value_tree.to_expression"),"aria-label":n("layout_editor.value_tree.to_expression"),children:["ƒx ",n("layout_editor.value_tree.to_expression")]}),i.jsxs("button",{type:"button","data-testid":`${s}-to-fallback`,onClick:()=>t(_s(d)),style:qt,title:n("layout_editor.value_tree.segment.fallback"),"aria-label":n("layout_editor.value_tree.segment.fallback"),children:["?? ",n("layout_editor.value_tree.segment.fallback")]})]}),w==="raw"&&i.jsxs("p",{"data-testid":`${s}-raw-hint`,style:Is,children:["ⓘ ",n("layout_editor.value_tree.raw_expression_hint")]})]}):i.jsxs("div",{"data-testid":s,style:hn,children:[y(),i.jsxs("div",{style:Cw,children:[c&&i.jsx($t,{candidates:o,t:n,onSelect:_,testIdSuffix:`${s}-pick`}),w!=="raw"&&i.jsxs("div",{style:{display:"flex",gap:4},children:[i.jsxs("button",{type:"button","data-testid":`${s}-to-expr`,onClick:()=>t(Do(d)),style:qt,title:n("layout_editor.value_tree.to_expression"),"aria-label":n("layout_editor.value_tree.to_expression"),children:["ƒx ",n("layout_editor.value_tree.to_expression")]}),i.jsxs("button",{type:"button","data-testid":`${s}-to-fallback`,onClick:()=>t(_s(d)),style:qt,title:n("layout_editor.value_tree.segment.fallback"),"aria-label":n("layout_editor.value_tree.segment.fallback"),children:["?? ",n("layout_editor.value_tree.segment.fallback")]})]})]}),w==="raw"&&!p&&i.jsxs("p",{"data-testid":`${s}-raw-hint`,style:Is,children:["ⓘ ",n("layout_editor.value_tree.raw_expression_hint")]})]})}function kw({value:e,onChange:t,t:n,candidates:o,testidPrefix:r,onEditRaw:s}){const[a,l]=u.useState(!1),d=u.useRef(null),c=Ut(e),p=!!o&&o.length>0,f=Vt(e)?.localeFn;return i.jsxs("div",{"data-testid":r,style:hn,children:[i.jsxs("div",{style:Sc,children:[i.jsxs("span",{"data-testid":`${r}-chip`,style:Es,children:["🔗 ",c]}),p&&i.jsx("button",{ref:d,type:"button","data-testid":`${r}-change`,onClick:()=>l(b=>!b),style:qt,title:n("layout_editor.value_tree.change_data"),"aria-label":n("layout_editor.value_tree.change_data"),children:n("layout_editor.value_tree.change_data")}),i.jsx("button",{type:"button","data-testid":`${r}-edit-raw`,onClick:s,style:qt,title:n("layout_editor.value_tree.edit"),"aria-label":n("layout_editor.value_tree.edit"),children:"✎"})]}),p&&i.jsx(Zn,{anchorRef:d,open:a,onClose:()=>l(!1),children:i.jsx($t,{candidates:o,t:n,onSelect:b=>{if(f){const h=[b.sourceId,...b.path?b.path.split(".").filter(Boolean):[]].join(".");t(`{{${f}(${h})}}`),l(!1);return}const g=b.path?b.path.split(".").filter(Boolean):[],m=[b.sourceId,...g].join("?.");t(`{{${m}}}`),l(!1)},testIdSuffix:`${r}-pick`,defaultOpen:!0,floating:!1})})]})}const hn={display:"flex",flexDirection:"column",gap:4,width:"100%",minWidth:0},Sc={display:"flex",alignItems:"center",gap:6,width:"100%",minWidth:0,flexWrap:"wrap"},Sw={flex:1,minWidth:0,display:"flex",flexWrap:"wrap",alignItems:"center",gap:4,padding:"5px 8px",fontSize:12,color:"#334155",border:"1px solid #e2e8f0",borderRadius:6,background:"#f8fafc"},Es={fontSize:11,background:"#eef2ff",color:"#4338ca",padding:"2px 8px",borderRadius:12,whiteSpace:"nowrap"},qt={padding:"4px 8px",fontSize:11,border:"1px solid #cbd5e1",borderRadius:6,background:"#f8fafc",color:"#475569",cursor:"pointer",whiteSpace:"nowrap"},Cw={display:"flex",flexWrap:"wrap",alignItems:"center",gap:6},Is={margin:"2px 0 0",fontSize:11,color:"#94a3b8",lineHeight:1.4},$w={padding:"3px 10px",fontSize:11,border:"1px solid #fca5a5",borderRadius:6,background:"#fef2f2",color:"#b91c1c",cursor:"pointer",whiteSpace:"nowrap"},Ew={display:"flex",flexDirection:"column",gap:6,padding:10,border:"1px solid #fca5a5",borderRadius:8,background:"#fffbeb"},Iw={margin:0,fontSize:12,color:"#334155"},Aw={padding:"5px 8px",fontSize:12,color:"#0f172a",background:"#fff",border:"1px solid #e2e8f0",borderRadius:6,wordBreak:"break-all",display:"flex",flexWrap:"wrap",gap:4,alignItems:"center"},Tw={margin:0,fontSize:11,color:"#b45309"},Dw={padding:"3px 12px",fontSize:11,border:"1px solid #dc2626",borderRadius:6,background:"#dc2626",color:"#fff",cursor:"pointer",whiteSpace:"nowrap"};let Cc=0;function jr({value:e,onChange:t,keyField:n,valueField:o="content",t:r,candidates:s,testidPrefix:a="g7le-kv-chip-editor",keyPlaceholder:l,valuePlaceholder:d,addLabel:c,renderKeyInput:p}){const f=u.useMemo(()=>(Array.isArray(e)?e:[]).map(w=>({id:Cc++,k:typeof w[n]=="string"?w[n]:"",v:typeof w[o]=="string"?w[o]:""})),[]),[b,g]=u.useState(f),m=u.useCallback(w=>{g(w);const k=w.filter(x=>x.k.trim()!=="").map(x=>({[n]:x.k,[o]:x.v}));t(k)},[n,o,t]),h=u.useCallback(()=>{m([...b,{id:Cc++,k:"",v:""}])},[b,m]),v=u.useCallback(w=>{m(b.filter(k=>k.id!==w))},[b,m]),y=u.useCallback((w,k)=>{m(b.map(x=>x.id===w?{...x,k}:x))},[b,m]),_=u.useCallback((w,k)=>{m(b.map(x=>x.id===w?{...x,v:k}:x))},[b,m]);return i.jsxs("div",{"data-testid":a,style:{display:"flex",flexDirection:"column",gap:6},children:[b.map(w=>i.jsxs("div",{"data-testid":`${a}-row-${w.id}`,style:{display:"flex",gap:6,alignItems:"flex-start",border:"1px solid #e2e8f0",borderRadius:8,padding:8},children:[i.jsxs("div",{style:{flex:1,minWidth:0,display:"flex",flexDirection:"column",gap:6},children:[i.jsx("div",{style:{display:"flex",flexDirection:"column",gap:2,minWidth:0},children:p?i.jsx("div",{style:{minWidth:0},children:p({value:w.k,onChange:k=>y(w.id,k),testid:`${a}-key-${w.id}`,placeholder:l??r(`layout_editor.page_settings.seo.extra_${n}`)})}):i.jsx("input",{type:"text","data-testid":`${a}-key-${w.id}`,value:w.k,placeholder:l??r(`layout_editor.page_settings.seo.extra_${n}`),onChange:k=>y(w.id,k.target.value),style:{width:"100%",padding:"5px 8px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,minWidth:0,boxSizing:"border-box"}})}),i.jsx("div",{style:{minWidth:0},children:i.jsx(Ke,{value:w.v,onChange:k=>_(w.id,k),t:r,candidates:s,placeholder:d??r("layout_editor.page_settings.seo.extra_content"),testidPrefix:`${a}-value-${w.id}`})})]}),i.jsx("button",{type:"button","data-testid":`${a}-remove-${w.id}`,onClick:()=>v(w.id),title:r("layout_editor.page_settings.seo.remove"),"aria-label":r("layout_editor.page_settings.seo.remove"),style:{flex:"0 0 auto",padding:"4px 8px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",color:"#64748b",cursor:"pointer"},children:"✕"})]},w.id)),i.jsxs("button",{type:"button","data-testid":`${a}-add`,onClick:h,style:{alignSelf:"flex-start",padding:"5px 10px",fontSize:12,border:"1px dashed #cbd5e1",borderRadius:6,background:"#f8fafc",color:"#475569",cursor:"pointer"},children:["+ ",c??r("layout_editor.page_settings.seo.add_extra")]})]})}const zw=["website","article","product","profile"],Nw=["summary","summary_large_image","app","player"];function Mw(e){switch(e){case"core":return"layout_editor.page_settings.seo.og_source_core";case"inherited":return"layout_editor.page_settings.seo.og_source_inherited";case"filter":return"layout_editor.page_settings.seo.og_source_filter";case"layout":return null;default:return e?"layout_editor.page_settings.seo.og_source_module":null}}function $c(e){const t={};for(const n of e??[])t[n.key]=n;return t}function Ec(e,t){return!!e&&t&&typeof e.sourceExpr=="string"&&e.sourceExpr!==""&&!e.overriddenByLayout&&!e.lockedByFilter}function Ic({label:e,sourceExpr:t,onReplace:n,t:o,testid:r}){return i.jsxs("div",{"data-testid":r,style:Vw,children:[i.jsxs("span",{"data-testid":`${r}-chip`,style:Uw,title:t,children:["🔗 ",e||t]}),i.jsx("span",{"data-testid":`${r}-badge`,style:qw,children:o("layout_editor.page_settings.seo.auto_chip_source")}),i.jsx("button",{type:"button","data-testid":`${r}-replace`,onClick:n,style:Gw,children:o("layout_editor.page_settings.seo.auto_chip_replace")})]})}function Lw({og:e,twitter:t,onChangeOg:n,onChangeTwitter:o,ogPreview:r,twitterPreview:s,defaultsAvailable:a=!1,candidates:l,t:d,testidPrefix:c="g7le-seo-og"}){const p=e&&typeof e=="object"?e:{},f=t&&typeof t=="object"?t:{},[b,g]=u.useState(p.image_height!=null||p.image_type!=null||p.image_secure_url!=null),m=$c(r),h=$c(s),v=u.useCallback((D,E)=>{const I={...p};E===void 0||E===""||E===null?delete I[D]:I[D]=E,n(I)},[p,n]),y=u.useCallback((D,E)=>{const I={...f};E===void 0||E===""||E===null?delete I[D]:I[D]=E,o(I)},[f,o]),_=(D,E,I)=>!I||!a||!I.lockedByFilter?null:i.jsx("div",{style:{display:"flex",gap:6,alignItems:"center",marginTop:2},children:i.jsxs("span",{"data-testid":`${D}-locked-${E}`,style:Hw,children:["🔒 ",d("layout_editor.page_settings.seo.og_locked")]})}),w=D=>{if(!D||!a||D.lockedByFilter||D.overriddenByLayout)return;const E=Mw(D.source);return E?d(E):void 0},k=(D,E)=>D&&a?D.overriddenByLayout===!0:E!=="",x=(D,E,I,N,T,O)=>{const M=O[E],B=!!M?.lockedByFilter,z=typeof N[E]=="string"?N[E]:"";return i.jsxs("div",{"data-testid":`${D}-${E}`,style:Nn,children:[i.jsx("label",{style:oo,children:d(I)}),B?i.jsx("div",{"data-testid":`${D}-locked-value-${E}`,style:jw,children:String(M?.effectiveValue??"")}):Ec(M,a)?i.jsx(Ic,{label:M.label??"",sourceExpr:M.sourceExpr,onReplace:()=>T(E,M.sourceExpr),t:d,testid:`${D}-${E}-auto`}):i.jsx(_t,{value:z,onChange:H=>T(E,H??""),t:d,candidates:l,placeholder:a&&M?.effectiveValue!=null?String(M.effectiveValue):void 0,testidPrefix:`${D}-${E}-field`,enableExpressionTree:!0,expressionTreeCollapsible:!0}),_(D,E,M)]})},$=(D,E,I,N,T,O)=>{const M=O[E],B=!!M?.lockedByFilter,z=typeof N[E]=="string"?N[E]:"";return i.jsxs("div",{"data-testid":`${D}-${E}`,style:Nn,children:[i.jsx("label",{style:oo,children:d(I)}),Ec(M,a)?i.jsx(Ic,{label:M.label??"",sourceExpr:M.sourceExpr,onReplace:()=>T(E,M.sourceExpr),t:d,testid:`${D}-${E}-auto`}):i.jsx(Ke,{value:z,onChange:H=>T(E,H),t:d,candidates:l,readOnly:B,placeholder:a&&M?.effectiveValue!=null?String(M.effectiveValue):void 0,testidPrefix:`${D}-${E}-field`}),_(D,E,M)]})},C=(D,E,I,N,T,O)=>{const M=O[E],B=!!M?.lockedByFilter,z=typeof N[E]=="string"?N[E]:"",H=k(M,z),P=H?z:"";return i.jsxs("div",{"data-testid":`${D}-${E}`,style:Nn,children:[i.jsx("label",{style:oo,children:d(I)}),i.jsx(Jx,{filled:H,sourceLabel:w(M),onRevert:B?void 0:()=>T(E,void 0),revertLabel:d("layout_editor.page_settings.seo.revert_default"),testid:`${D}-${E}-overlay`,children:i.jsx("div",{"data-testid":`${D}-${E}-input`,style:{minWidth:0},children:i.jsx(Ke,{value:P,onChange:A=>T(E,A),t:d,candidates:l,readOnly:B,placeholder:a&&M?.effectiveValue!=null?String(M.effectiveValue):void 0,testidPrefix:`${D}-${E}-chip`})})}),_(D,E,M)]})},S=(D,E,I,N,T,O,M)=>{const B=M[E],z=typeof T[E]=="string"?T[E]:"",H=k(B,z),P=w(B),A=H?z:"";return i.jsxs("div",{"data-testid":`${D}-${E}`,style:Nn,children:[i.jsxs("div",{style:Ow,children:[i.jsx("label",{style:oo,children:d(I)}),P?i.jsxs("span",{"data-testid":`${D}-${E}-source-inline`,style:Bw,children:["· ",P]}):null]}),i.jsxs("select",{"data-testid":`${D}-${E}-select`,value:A,onChange:L=>O(E,L.target.value),style:Ww,children:[i.jsx("option",{value:"",children:a&&B?.effectiveValue!=null&&B.effectiveValue!==""?`${d("layout_editor.page_settings.seo.use_default")} (${String(B.effectiveValue)})`:d("layout_editor.page_settings.seo.use_default")}),N.map(L=>i.jsx("option",{value:L,children:L},L))]}),_(D,E,B)]})};return i.jsxs("div",{"data-testid":c,style:{display:"flex",flexDirection:"column",gap:12},children:[i.jsxs("div",{"data-testid":`${c}-og-section`,style:Ac,children:[i.jsx("div",{style:Tc,children:d("layout_editor.page_settings.seo.og_section")}),i.jsxs("p",{"data-testid":`${c}-intro`,style:Pw,children:["ⓘ ",d("layout_editor.page_settings.seo.og_intro")]}),S(c,"type","layout_editor.page_settings.seo.og_type",zw,p,v,m),x(c,"title","layout_editor.page_settings.seo.og_title",p,v,m),x(c,"description","layout_editor.page_settings.seo.og_description",p,v,m),$(c,"image","layout_editor.page_settings.seo.og_image",p,v,m),x(c,"image_alt","layout_editor.page_settings.seo.og_image_alt",p,v,m),i.jsxs("div",{style:Nn,children:[i.jsxs("button",{type:"button","data-testid":`${c}-image-adv-toggle`,"aria-expanded":b,onClick:()=>g(D=>!D),style:Fw,children:[b?"▾":"▸"," ",d("layout_editor.page_settings.seo.og_image_adv")]}),b?i.jsxs("div",{"data-testid":`${c}-image-adv`,style:Kw,children:[C(c,"image_height","layout_editor.page_settings.seo.og_image_height",p,v,m),C(c,"image_type","layout_editor.page_settings.seo.og_image_type",p,v,m),C(c,"image_secure_url","layout_editor.page_settings.seo.og_image_secure_url",p,v,m)]}):null]}),C(c,"site_name","layout_editor.page_settings.seo.og_site_name",p,v,m),C(c,"locale","layout_editor.page_settings.seo.og_locale",p,v,m),i.jsxs("div",{"data-testid":`${c}-extra`,style:Nn,children:[i.jsx("label",{style:oo,children:d("layout_editor.page_settings.seo.og_extra")}),i.jsx(jr,{value:Array.isArray(p.extra)?p.extra:[],onChange:D=>v("extra",D.length>0?D:void 0),keyField:"property",t:d,candidates:l,testidPrefix:`${c}-extra-editor`})]})]}),i.jsxs("div",{"data-testid":`${c}-tw-section`,style:Ac,children:[i.jsx("div",{style:Tc,children:d("layout_editor.page_settings.seo.tw_section")}),i.jsx("p",{style:{margin:"0 0 6px",fontSize:11,color:"#94a3b8"},children:d("layout_editor.page_settings.seo.tw_fallback_note")}),S("g7le-seo-tw","card","layout_editor.page_settings.seo.tw_card",Nw,f,y,h),C("g7le-seo-tw","site","layout_editor.page_settings.seo.tw_site",f,y,h),C("g7le-seo-tw","creator","layout_editor.page_settings.seo.tw_creator",f,y,h),x("g7le-seo-tw","title","layout_editor.page_settings.seo.tw_title",f,y,h),x("g7le-seo-tw","description","layout_editor.page_settings.seo.tw_description",f,y,h),$("g7le-seo-tw","image","layout_editor.page_settings.seo.tw_image",f,y,h),x("g7le-seo-tw","image_alt","layout_editor.page_settings.seo.tw_image_alt",f,y,h),i.jsxs("div",{"data-testid":"g7le-seo-tw-extra",style:Nn,children:[i.jsx("label",{style:oo,children:d("layout_editor.page_settings.seo.tw_extra")}),i.jsx(jr,{value:Array.isArray(f.extra)?f.extra:[],onChange:D=>y("extra",D.length>0?D:void 0),keyField:"name",t:d,candidates:l,testidPrefix:"g7le-seo-tw-extra-editor"})]})]})]})}const Ac={border:"1px solid #e2e8f0",borderRadius:8,padding:12,display:"flex",flexDirection:"column",gap:10},Tc={fontSize:13,fontWeight:600,color:"#0f172a"},Pw={margin:0,fontSize:11,color:"#64748b",background:"#f8fafc",borderRadius:6,padding:"6px 8px"},Nn={display:"flex",flexDirection:"column",gap:4},oo={fontSize:12,color:"#475569"},Ow={display:"flex",alignItems:"center",gap:6},Bw={fontSize:11,color:"#94a3b8"},Ww={padding:"5px 8px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,width:"100%",minWidth:0,boxSizing:"border-box"},jw={padding:"5px 8px",fontSize:12,border:"1px solid #e2e8f0",borderRadius:6,background:"#f1f5f9",color:"#64748b"},Hw={fontSize:11,color:"#7c3aed"},Fw={alignSelf:"flex-start",padding:"2px 0",fontSize:12,fontWeight:600,color:"#475569",background:"transparent",border:"none",cursor:"pointer"},Kw={display:"flex",flexDirection:"column",gap:8,paddingLeft:14,marginTop:4,minWidth:0},Vw={display:"flex",alignItems:"center",gap:8,flexWrap:"wrap",minWidth:0},Uw={display:"inline-flex",alignItems:"center",maxWidth:"100%",padding:"3px 8px",fontSize:12,color:"#1d4ed8",background:"#eff6ff",border:"1px solid #bfdbfe",borderRadius:6,overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap"},qw={fontSize:11,color:"#94a3b8"},Gw={padding:"2px 8px",fontSize:11,color:"#475569",background:"#fff",border:"1px solid #cbd5e1",borderRadius:6,cursor:"pointer"},Yw=["WebPage","WebSite","ItemList","BreadcrumbList","Organization","Person","CollectionPage","AboutPage","ContactPage","SearchAction"];function Hr(e){const t=[],n=(o,r)=>{for(const[s,a]of Object.entries(o)){if(s==="@type"||s==="@context")continue;const l=r?`${r}.${s}`:s;a!==null&&typeof a=="object"&&!Array.isArray(a)?n(a,l):t.push({key:l,value:a==null?"":String(a)})}};return n(e,""),t}function Dc(e,t){const n={"@type":e};for(const{key:o,value:r}of t){if(o.trim()==="")continue;const s=o.split(".");let a=n;for(let l=0;lp?Hr(e).map(T=>({id:As++,...T})):[]),_=u.useCallback((T,O)=>{h(T),y(O),t(Dc(T,O.map(M=>({key:M.key,value:M.value}))))},[t]),w=u.useCallback(()=>{const T=n,O=typeof T["@type"]=="string"?T["@type"]:m,M=Hr(T).map(B=>({id:As++,key:B.key,value:o?.[B.key]?.expr??B.value}));return{type:O,rows:M}},[n,o,m]),k=u.useCallback(T=>{if(b(T),T){if(v.length===0&&n&&typeof n=="object"){const O=w();_(O.type,O.rows);return}t(Dc(m,v.map(O=>({key:O.key,value:O.value}))))}else t(null)},[m,v,n,w,_,t]),x=u.useCallback(()=>{if(!n)return;const T=w();_(T.type,T.rows)},[n,w,_]),$=u.useCallback(()=>{_(m,[...v,{id:As++,key:"",value:""}])},[m,v,_]),C=u.useCallback(T=>{_(m,v.filter(O=>O.id!==T))},[m,v,_]),S=u.useCallback((T,O)=>{_(m,v.map(M=>M.id===T?{...M,key:O}:M))},[m,v,_]),D=u.useCallback((T,O)=>{_(m,v.map(M=>M.id===T?{...M,value:O}:M))},[m,v,_]),E=u.useMemo(()=>n?Hr(n):[],[n]),I=u.useMemo(()=>s?Hr(s):[],[s]),N=v.some(T=>T.key.trim()!==""&&T.value.trim()==="");if(r){const T=s&&typeof s["@type"]=="string"?s["@type"]:"";return i.jsxs("div",{"data-testid":c,style:zc,children:[i.jsxs("div",{style:Nc,children:[i.jsx("span",{style:Jw,children:"🔒"}),i.jsx("span",{style:{fontSize:13,fontWeight:600},children:d("layout_editor.page_settings.seo.sd_mode")}),i.jsx("span",{"data-testid":`${c}-locked-badge`,style:Zw,children:d("layout_editor.page_settings.seo.sd_filter_locked")})]}),i.jsxs("div",{"data-testid":`${c}-locked`,style:Qw,children:[i.jsxs("p",{style:{margin:"0 0 6px",fontSize:12,color:"#475569"},children:["ⓘ ",d("layout_editor.page_settings.seo.sd_filter_locked_note")]}),i.jsxs("div",{"data-testid":`${c}-filtered-block`,children:[T?i.jsxs("div",{style:{fontSize:12,fontWeight:600,marginBottom:4},children:["@type: ",T]}):null,I.map(O=>i.jsxs("div",{"data-testid":`${c}-filtered-row-${O.key}`,style:Mc,children:[i.jsx("code",{style:Lc,children:O.key}),i.jsx("span",{style:Pc,children:O.value})]},O.key))]})]})]})}return i.jsxs("div",{"data-testid":c,style:zc,children:[i.jsx("div",{style:Nc,children:i.jsx(Br,{checked:f,onChange:k,testid:`${c}-mode`,label:d("layout_editor.page_settings.seo.sd_manual_toggle")})}),!f&&i.jsxs("div",{"data-testid":`${c}-auto`,style:{marginTop:8},children:[i.jsxs("div",{"data-testid":`${c}-auto-preview`,style:Rw,children:[i.jsxs("div",{style:{fontSize:12,fontWeight:600,marginBottom:6},children:[d("layout_editor.page_settings.seo.sd_auto_title"),n&&typeof n["@type"]=="string"?`: ${n["@type"]}`:"",a?i.jsxs("span",{style:{marginLeft:6,color:"#94a3b8",fontWeight:400},children:["(",a,")"]}):null]}),E.length===0?i.jsx("p",{style:{margin:0,fontSize:12,color:"#94a3b8"},children:d("layout_editor.page_settings.seo.sd_auto_empty")}):E.map(T=>{const O=o?.[T.key];return i.jsxs("div",{"data-testid":`${c}-auto-row-${T.key}`,style:Mc,children:[i.jsx("code",{style:Lc,children:T.key}),O?i.jsxs("span",{"data-testid":`${c}-auto-chip-${T.key}`,style:e0,title:O.expr,children:["🔗 ",O.label||O.expr]}):i.jsx("span",{style:Pc,children:T.value})]},T.key)})]}),i.jsxs("p",{"data-testid":`${c}-replace-warn`,style:t0,children:["ⓘ ",d("layout_editor.page_settings.seo.sd_replace_pre_warn")]})]}),f&&i.jsxs("div",{"data-testid":`${c}-manual`,style:{marginTop:8},children:[i.jsxs("p",{"data-testid":`${c}-replace-warn`,style:n0,children:["⚠ ",d("layout_editor.page_settings.seo.sd_replace_warn")]}),i.jsxs("div",{style:{display:"flex",flexDirection:"column",gap:6},children:[i.jsx("label",{style:{fontSize:12,color:"#475569"},children:d("layout_editor.page_settings.seo.sd_type")}),i.jsx("input",{type:"text","data-testid":`${c}-type`,list:`${c}-type-options`,value:m,onChange:T=>_(T.target.value,v),style:o0}),i.jsx("datalist",{id:`${c}-type-options`,children:Yw.map(T=>i.jsx("option",{value:T},T))})]}),i.jsx("div",{style:{marginTop:8,display:"flex",flexDirection:"column",gap:6},children:v.map(T=>i.jsxs("div",{"data-testid":`${c}-prop-${T.id}`,style:r0,children:[i.jsx("input",{type:"text","data-testid":`${c}-prop-key-${T.id}`,value:T.key,placeholder:d("layout_editor.page_settings.seo.sd_prop_key"),onChange:O=>S(T.id,O.target.value),style:{width:160,padding:"5px 8px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,minWidth:0}}),i.jsx("div",{style:{flex:1,minWidth:0},children:i.jsx(Ke,{value:T.value,onChange:O=>D(T.id,O),t:d,candidates:l,placeholder:d("layout_editor.page_settings.seo.sd_prop_value"),testidPrefix:`${c}-prop-value-${T.id}`})}),i.jsx("button",{type:"button","data-testid":`${c}-prop-remove-${T.id}`,onClick:()=>C(T.id),title:d("layout_editor.page_settings.seo.remove"),"aria-label":d("layout_editor.page_settings.seo.remove"),style:i0,children:"✕"})]},T.id))}),i.jsxs("div",{style:{marginTop:8,display:"flex",gap:8},children:[i.jsxs("button",{type:"button","data-testid":`${c}-add`,onClick:$,style:Oc,children:["+ ",d("layout_editor.page_settings.seo.sd_add_prop")]}),n?i.jsxs("button",{type:"button","data-testid":`${c}-seed-from-module`,onClick:x,style:Oc,children:["↩ ",d("layout_editor.page_settings.seo.sd_seed_from_module")]}):null]}),i.jsxs("p",{"data-testid":`${c}-empty-note`,style:s0,children:["ⓘ ",d("layout_editor.page_settings.seo.sd_empty_note"),N?i.jsxs("span",{style:{color:"#b45309"},children:[" ",d("layout_editor.page_settings.seo.sd_has_empty")]}):null]})]})]})}const zc={display:"flex",flexDirection:"column"},Nc={display:"flex",alignItems:"center",gap:8},Jw={fontSize:14},Zw={fontSize:11,padding:"2px 6px",borderRadius:4,background:"#f1f5f9",color:"#64748b"},Qw={marginTop:8,border:"1px solid #fcd34d",borderRadius:8,padding:10,background:"#fffbeb"},Rw={border:"1px solid #e2e8f0",borderRadius:8,padding:10,background:"#f8fafc"},Mc={display:"flex",gap:8,alignItems:"center",padding:"2px 0"},Lc={fontSize:11,color:"#475569",background:"#fff",padding:"2px 6px",borderRadius:4,minWidth:120},Pc={fontSize:11,color:"#0f172a",fontFamily:"monospace",wordBreak:"break-all"},e0={display:"inline-flex",alignItems:"center",padding:"2px 8px",fontSize:11,color:"#1d4ed8",background:"#eff6ff",border:"1px solid #bfdbfe",borderRadius:6},t0={margin:"6px 0 0",fontSize:12,color:"#64748b"},n0={margin:"0 0 8px",fontSize:12,color:"#b45309",background:"#fffbeb",border:"1px solid #fcd34d",borderRadius:6,padding:"6px 8px"},o0={padding:"5px 8px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,width:"100%",minWidth:0},r0={display:"flex",gap:6,alignItems:"flex-start",border:"1px solid #e2e8f0",borderRadius:8,padding:8},i0={padding:"4px 8px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",color:"#64748b",cursor:"pointer"},Oc={padding:"5px 10px",fontSize:12,border:"1px dashed #cbd5e1",borderRadius:6,background:"#f8fafc",color:"#475569",cursor:"pointer"},s0={margin:"8px 0 0",fontSize:11,color:"#94a3b8"},Bc=new Set(["core_setting","setting","query","route"]);function a0({vars:e,onChange:t,varCandidates:n,gatingMet:o=!1,candidates:r,t:s,testidPrefix:a="g7le-seo-vars"}){const l=u.useMemo(()=>e&&typeof e=="object"?{...e}:{},[e]),d=u.useMemo(()=>Array.isArray(n)?n:[],[n]),c=u.useMemo(()=>o?d.filter(x=>Bc.has(x.source)):[],[d,o]),p=u.useMemo(()=>o?d.filter(x=>x.source==="data"):[],[d,o]);u.useMemo(()=>new Set(c.map(x=>x.name)),[c]);const f=u.useMemo(()=>new Set(p.map(x=>x.name)),[p]),b=u.useMemo(()=>{const x=new Set;for(const $ of d)Bc.has($.source)&&x.add($.name);return x},[d]),g=u.useMemo(()=>Object.keys(l).filter(x=>!f.has(x)),[l,f]),m=u.useCallback((x,$)=>{const C={...l};$===void 0?delete C[x]:C[x]=$,t(C)},[l,t]),[h,v]=u.useState(""),y=h.trim()!==""&&b.has(h.trim()),_=h.trim()!==""&&Object.prototype.hasOwnProperty.call(l,h.trim()),w=h.trim()!==""&&!y&&!_&&!f.has(h.trim()),k=u.useCallback(()=>{const x=h.trim();x===""||b.has(x)||Object.prototype.hasOwnProperty.call(l,x)||(t({...l,[x]:""}),v(""))},[h,b,l,t]);return i.jsxs("div",{"data-testid":a,style:{display:"flex",flexDirection:"column",gap:10},children:[i.jsx("span",{"data-testid":"g7le-seo-var-list",hidden:!0,"data-gating":o?"1":"0"}),!o&&i.jsxs("div",{"data-testid":`${a}-precondition`,style:l0,children:["⚠ ",s("layout_editor.page_settings.seo.vars_precondition")]}),c.length>0&&i.jsxs("div",{"data-testid":`${a}-auto-group`,style:Ts,children:[i.jsx("div",{style:Ds,children:s("layout_editor.page_settings.seo.vars_auto_group")}),c.map(x=>i.jsxs("div",{"data-testid":`g7le-seo-var-auto-${x.name}`,style:d0,children:[i.jsx("span",{style:c0,children:"🔒"}),i.jsx("code",{style:zs,children:x.name}),i.jsxs("span",{style:u0,children:["〔",s("layout_editor.page_settings.seo.vars_auto"),"·",x.source,"〕"]}),x.owner?.name?i.jsx("span",{style:p0,children:x.owner.name}):null]},x.name))]}),p.length>0&&i.jsxs("div",{"data-testid":`${a}-data-group`,style:Ts,children:[i.jsx("div",{style:Ds,children:s("layout_editor.page_settings.seo.vars_data_group")}),p.map(x=>{const $=l[x.name]??"",C=$.trim()==="";return i.jsxs("div",{"data-testid":`g7le-seo-var-data-${x.name}`,style:Wc,children:[i.jsxs("div",{style:{display:"flex",alignItems:"center",gap:6,marginBottom:4},children:[i.jsx("code",{style:zs,children:x.name}),x.required?i.jsx("span",{"data-testid":`g7le-seo-var-required-${x.name}`,style:f0,children:"*"}):null]}),i.jsx(Ke,{value:$,onChange:S=>m(x.name,S??""),t:s,candidates:r,testidPrefix:`g7le-seo-var-data-field-${x.name}`}),x.required&&C?i.jsxs("p",{"data-testid":`g7le-seo-var-empty-warn-${x.name}`,style:Ns,children:["⚠ ",s("layout_editor.page_settings.seo.vars_required_empty")]}):null]},x.name)})]}),i.jsxs("div",{"data-testid":`${a}-custom-group`,style:Ts,children:[i.jsx("div",{style:Ds,children:s("layout_editor.page_settings.seo.vars_custom_group")}),g.map(x=>i.jsxs("div",{"data-testid":`g7le-seo-var-custom-${x}`,style:Wc,children:[i.jsxs("div",{style:{display:"flex",alignItems:"center",gap:6,marginBottom:4},children:[i.jsx("code",{style:zs,children:x}),i.jsx("button",{type:"button","data-testid":`g7le-seo-var-custom-remove-${x}`,onClick:()=>m(x,void 0),title:s("layout_editor.page_settings.seo.remove"),"aria-label":s("layout_editor.page_settings.seo.remove"),style:{marginLeft:"auto",padding:"2px 8px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",color:"#64748b",cursor:"pointer"},children:"✕"})]}),i.jsx(Ke,{value:l[x]??"",onChange:$=>m(x,$??""),t:s,candidates:r,testidPrefix:`g7le-seo-var-custom-field-${x}`})]},x)),i.jsxs("div",{"data-testid":"g7le-seo-var-add",style:{display:"flex",flexDirection:"column",gap:4,marginTop:6},children:[i.jsxs("div",{style:{display:"flex",gap:6},children:[i.jsx("input",{type:"text","data-testid":"g7le-seo-var-add-name",value:h,placeholder:s("layout_editor.page_settings.seo.vars_add_name"),onChange:x=>v(x.target.value),style:{flex:1,padding:"5px 8px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,minWidth:0}}),i.jsxs("button",{type:"button","data-testid":"g7le-seo-var-add-confirm",onClick:k,disabled:h.trim()===""||y||_,style:g0,children:["+ ",s("layout_editor.page_settings.seo.vars_add")]})]}),y?i.jsxs("p",{"data-testid":"g7le-seo-var-add-reserved-warn",style:Ns,children:["⚠ ",s("layout_editor.page_settings.seo.vars_name_reserved",{name:h.trim()})]}):null,_&&!y?i.jsxs("p",{"data-testid":"g7le-seo-var-add-duplicate-warn",style:Ns,children:["⚠ ",s("layout_editor.page_settings.seo.vars_name_duplicate",{name:h.trim()})]}):null,w?i.jsxs("p",{"data-testid":"g7le-seo-var-add-not-provided-hint",style:{margin:0,fontSize:11,color:"#94a3b8"},children:["ⓘ ",s("layout_editor.page_settings.seo.vars_name_not_provided",{name:h.trim()})]}):null]})]}),i.jsxs("p",{style:{margin:0,fontSize:11,color:"#94a3b8"},children:["ⓘ ",s("layout_editor.page_settings.seo.vars_value_hint")]})]})}const l0={fontSize:12,color:"#b45309",background:"#fffbeb",border:"1px solid #fcd34d",borderRadius:6,padding:"6px 8px"},Ts={border:"1px solid #e2e8f0",borderRadius:8,padding:10,display:"flex",flexDirection:"column",gap:6},Ds={fontSize:12,fontWeight:600,color:"#475569"},d0={display:"flex",alignItems:"center",gap:6,padding:"2px 0"},Wc={display:"flex",flexDirection:"column",border:"1px solid #f1f5f9",borderRadius:6,padding:8},c0={fontSize:13},zs={fontSize:12,color:"#0f172a",background:"#f1f5f9",padding:"2px 6px",borderRadius:4},u0={fontSize:11,color:"#64748b"},p0={fontSize:11,color:"#94a3b8"},f0={color:"#dc2626",fontWeight:700},Ns={margin:"4px 0 0",fontSize:11,color:"#b45309"},g0={padding:"5px 10px",fontSize:12,border:"1px dashed #cbd5e1",borderRadius:6,background:"#f8fafc",color:"#475569",cursor:"pointer",whiteSpace:"nowrap"};function h0({templateIdentifier:e,layout:t,url:n,locale:o,seedContext:r,routeParams:s,settingsSignature:a,t:l,debounceMs:d=400,fetchImpl:c,testidPrefix:p="g7le-seo-bot-preview"}){const[f,b]=u.useState(!0),[g,m]=u.useState(null),[h,v]=u.useState(!1),[y,_]=u.useState(null),w=u.useRef(null),k=u.useRef({templateIdentifier:e,layout:t,url:n,locale:o,seedContext:r,routeParams:s});k.current={templateIdentifier:e,layout:t,url:n,locale:o,seedContext:r,routeParams:s};const x=u.useCallback(async()=>{const D=c??(typeof fetch<"u"?fetch:void 0);if(!D)return;const{templateIdentifier:E,layout:I,url:N,locale:T,seedContext:O,routeParams:M}=k.current;v(!0),_(null);try{const B=`/api/admin/templates/${encodeURIComponent(E)}/editor/seo-bot-preview`,z=await D(B,{method:"POST",credentials:"same-origin",headers:Me({"Content-Type":"application/json"}),body:JSON.stringify({layout:I,url:N,locale:T,seed_context:O??{},route_params:M??{}})}),H=await z.json().catch(()=>null);if(!z.ok){_(H?.message??`HTTP ${z.status}`);return}const P=H?.data??null;m(P)}catch(B){_(B instanceof Error?B.message:String(B))}finally{v(!1)}},[c]);u.useEffect(()=>{if(f)return w.current&&clearTimeout(w.current),w.current=setTimeout(()=>{x()},d),()=>{w.current&&clearTimeout(w.current)}},[a,f,d,x]);const $=u.useCallback(()=>{w.current&&clearTimeout(w.current),x()},[x]),C=g?.html??null,S=g?.enabled??!0;return i.jsxs("div",{"data-testid":p,style:y0,children:[i.jsxs("div",{style:b0,children:[i.jsxs("button",{type:"button","data-testid":`${p}-toggle`,onClick:()=>b(D=>!D),"aria-expanded":f,style:m0,children:[f?"▾":"▸"," ",l("layout_editor.page_settings.seo.bot_preview_title")]}),f?i.jsxs("button",{type:"button","data-testid":`${p}-refresh`,onClick:$,disabled:h,style:v0,children:["⟳ ",l("layout_editor.page_settings.seo.bot_preview_refresh")]}):null]}),f&&i.jsxs("div",{style:{marginTop:8},children:[g&&!S?i.jsxs("p",{"data-testid":`${p}-disabled`,style:_0,children:["ⓘ ",l("layout_editor.page_settings.seo.bot_preview_disabled")]}):i.jsx("div",{"data-testid":`${p}-body`,style:x0,children:h&&!C?i.jsx("p",{style:Ms,children:l("layout_editor.translation.loading")}):y?i.jsx("p",{"data-testid":`${p}-error`,style:{...Ms,color:"#dc2626"},children:y}):C==null?i.jsx("p",{style:Ms,children:l("layout_editor.page_settings.seo.bot_preview_empty")}):i.jsx("pre",{"data-testid":`${p}-code`,style:w0,children:C})}),i.jsxs("p",{"data-testid":`${p}-sample-note`,style:k0,children:["ⓘ ",l("layout_editor.page_settings.seo.bot_preview_sample_note")]})]})]})}const y0={borderTop:"1px solid #e2e8f0",paddingTop:10,marginTop:12},b0={display:"flex",alignItems:"center",gap:8},m0={background:"none",border:"none",cursor:"pointer",fontSize:13,fontWeight:600,color:"#0f172a",padding:0},v0={marginLeft:"auto",padding:"4px 10px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",color:"#475569",cursor:"pointer"},_0={margin:0,fontSize:12,color:"#64748b",background:"#f8fafc",border:"1px solid #e2e8f0",borderRadius:6,padding:"8px 10px"},x0={border:"1px solid #e2e8f0",borderRadius:8,background:"#0b1020",padding:0,minHeight:80,overflow:"hidden"},w0={margin:0,padding:12,fontSize:11,lineHeight:1.5,color:"#d1fae5",fontFamily:"monospace",whiteSpace:"pre-wrap",wordBreak:"break-word",maxHeight:360,overflow:"auto"},Ms={margin:0,padding:12,fontSize:12,color:"#94a3b8"},k0={margin:"8px 0 0",fontSize:11,color:"#94a3b8"};function jc({option:e,testIdPrefix:t}){const{id:n,friendly:o,source:r}=e;return i.jsxs("span",{style:S0,children:[i.jsx("span",{"data-testid":t?`${t}-title`:void 0,style:C0,children:o??n}),o?i.jsx("span",{"data-testid":t?`${t}-id`:void 0,style:$0,children:n}):null,r?i.jsx("span",{"data-testid":t?`${t}-source`:void 0,style:E0,children:r}):null]})}const S0={display:"inline-flex",alignItems:"baseline",gap:4,flexWrap:"wrap",minWidth:0},C0={fontSize:12,color:"#0f172a"},$0={fontSize:10,color:"#94a3b8",fontFamily:"ui-monospace, monospace"},E0={fontSize:9,fontWeight:600,color:"#7c3aed",background:"#f5f3ff",border:"1px solid #ddd6fe",borderRadius:4,padding:"0 5px",whiteSpace:"nowrap"},I0=["always","hourly","daily","weekly","monthly","yearly","never"];function A0({getValue:e,patch:t,t:n,templateIdentifier:o,candidates:r,availableExtensions:s=[],dataSourceOptions:a=[],previewUrl:l="/",previewLocale:d="ko",seedContext:c,candidatesFetcher:p,ogPreviewFetcher:f,botFetchImpl:b,testidPrefix:g="g7le-seo"}){const m=e("meta",{}).seo,h=m&&typeof m=="object"?m:{},v=u.useCallback((K,te)=>{const ye={...e("meta",{})},Se={...ye.seo??{}};te==null||te===""?delete Se[K]:Se[K]=te,ye.seo=Se,t("meta",ye)},[e,t]),y=h.enabled===!0,_=typeof h.page_type=="string"?h.page_type:"",w=Array.isArray(h.extensions)?h.extensions:[],k=Array.isArray(h.data_sources)?h.data_sources:[],x=w.length>0&&_!=="",[$,C]=u.useState(null),[S,D]=u.useState(null),E=u.useCallback(async(K,te)=>{if(p)return p(K,te);if(typeof fetch>"u")return null;const ye=new URLSearchParams;K&&ye.set("page_type",K),Array.isArray(te)&&te.length>0&&ye.set("extensions",JSON.stringify(te.map(Ve=>({type:Ve.type,id:Ve.id}))));const Se=ye.toString()?`?${ye.toString()}`:"",De=await fetch(nn(`/api/admin/templates/${encodeURIComponent(o)}/editor/seo-candidates`,"json",null,Se.replace(/^[?&]+/,"")),{credentials:"same-origin",headers:Me()}),Ie=await De.json().catch(()=>null);return De.ok?Ie?.data??null:null},[p,o]),I=u.useMemo(()=>{const ye=(e("__editor",{})??{}).original?.meta?.seo;return ye&&typeof ye=="object"?ye:void 0},[e]),N=u.useRef(I);N.current=I;const T=u.useCallback(async K=>{if(f)return f(K);if(typeof fetch>"u")return null;const te=await fetch(`/api/admin/templates/${encodeURIComponent(o)}/editor/seo-og-preview`,{method:"POST",credentials:"same-origin",headers:Me({"Content-Type":"application/json"}),body:JSON.stringify({seo:K,own_seo:N.current??null,seed_context:c??{},route_params:{}})}),ye=await te.json().catch(()=>null);return te.ok?ye?.data??null:null},[f,o,c]),O=u.useMemo(()=>JSON.stringify(w),[w]),M=u.useRef(h);M.current=h,u.useEffect(()=>{let K=!1;return(async()=>{const te=await E(_,w);K||C(te)})(),()=>{K=!0}},[_,O]);const B=u.useMemo(()=>JSON.stringify({og:h.og??null,twitter:h.twitter??null,structured_data:h.structured_data??null}),[h.og,h.twitter,h.structured_data]),z=u.useRef(!1);u.useEffect(()=>{let K=!1;const te=()=>{(async()=>{const Se=await T(M.current);K||D(Se)})()};if(!z.current)return z.current=!0,te(),()=>{K=!0};const ye=setTimeout(te,180);return()=>{K=!0,clearTimeout(ye)}},[_,O,B]);const H=$?.page_types??[],P=$?.toggle_settings??[],A=$?.vars??[],L=u.useMemo(()=>JSON.stringify(h),[h]),F=u.useMemo(()=>e("meta")?{meta:e("meta"),components:e("components",[])}:{meta:{},components:[]},[e]),Y=u.useCallback(K=>{w.some(te=>te.type===K.type&&te.id===K.id)||v("extensions",[...w,{type:K.type,id:K.id}])},[w,v]),V=u.useCallback(K=>{const te=w.filter(ye=>!(ye.type===K.type&&ye.id===K.id));v("extensions",te.length>0?te:void 0)},[w,v]),[X,Z]=u.useState(!1),[ie,oe]=u.useState(""),G=u.useMemo(()=>{const K=ie.trim().toLowerCase();return K?P.filter(te=>`${te.ref} ${te.label??""}`.toLowerCase().includes(K)):P},[P,ie]),ee=typeof h.toggle_setting=="string"?h.toggle_setting:"",fe=u.useCallback(K=>{const ye=k.includes(K)?k.filter(Se=>Se!==K):[...k,K];v("data_sources",ye.length>0?ye:void 0)},[k,v]);return i.jsxs("div",{"data-testid":g,style:{display:"flex",flexDirection:"column",gap:14},children:[i.jsxs("div",{style:Wt,children:[i.jsx(Br,{checked:y,onChange:K=>v("enabled",K),testid:`${g}-enabled`,label:n("layout_editor.page_settings.seo.enabled")}),y?null:i.jsxs("p",{"data-testid":`${g}-disabled-note`,style:ro,children:["ⓘ ",n("layout_editor.page_settings.seo.disabled_note")]})]}),i.jsx(Wr,{disabled:!y,testid:`${g}-body`,style:{display:"flex",flexDirection:"column",gap:14},children:i.jsxs(i.Fragment,{children:[i.jsxs("div",{style:Wt,children:[i.jsx("label",{style:Gt,children:n("layout_editor.page_settings.seo.page_type")}),H.length>0?i.jsxs("select",{"data-testid":`${g}-page-type`,value:_,onChange:K=>v("page_type",K.target.value),style:Fr,children:[i.jsx("option",{value:"",children:n("layout_editor.page_settings.seo.page_type_none")}),H.map(K=>i.jsxs("option",{value:K.value,children:[K.label??K.value,K.owner?.name?` (${K.owner.name})`:""]},K.value))]}):i.jsxs(i.Fragment,{children:[i.jsx("input",{type:"text","data-testid":`${g}-page-type-free`,value:_,placeholder:n("layout_editor.page_settings.seo.page_type_free_placeholder"),onChange:K=>v("page_type",K.target.value),style:Fr}),i.jsxs("p",{"data-testid":`${g}-page-type-free-note`,style:ro,children:["ⓘ ",n("layout_editor.page_settings.seo.page_type_free_note")]})]})]}),i.jsxs("div",{style:Wt,children:[i.jsxs("label",{style:Gt,children:["★ ",n("layout_editor.page_settings.seo.extensions")]}),i.jsxs("div",{"data-testid":`${g}-extensions`,style:{display:"flex",flexWrap:"wrap",gap:6},children:[w.map(K=>i.jsxs("span",{"data-testid":`${g}-extension-${K.type}-${K.id}`,style:Hc,children:[K.name??`${K.type}:${K.id}`,i.jsx("button",{type:"button","data-testid":`${g}-extension-remove-${K.type}-${K.id}`,onClick:()=>V(K),style:Fc,children:"✕"})]},`${K.type}:${K.id}`)),i.jsxs("button",{type:"button","data-testid":`${g}-extension-add`,onClick:()=>Z(K=>!K),style:z0,children:["+ ",n("layout_editor.page_settings.seo.add_extension")]})]}),X&&i.jsx("div",{"data-testid":`${g}-extension-picker`,style:Kc,children:s.length===0?i.jsx("p",{style:ro,children:n("layout_editor.page_settings.seo.no_extension_candidate")}):s.map(K=>i.jsx("button",{type:"button","data-testid":`${g}-extension-option-${K.type}-${K.id}`,onClick:()=>{Y(K),Z(!1)},style:Vc,children:K.name??`${K.type}:${K.id}`},`${K.type}:${K.id}`))})]}),!x&&i.jsxs("div",{"data-testid":`${g}-defaults-precondition`,style:D0,children:["⚠ ",n("layout_editor.page_settings.seo.defaults_precondition"),i.jsxs("div",{"data-testid":`${g}-defaults-missing`,style:{fontSize:11,marginTop:4},children:[w.length===0?n("layout_editor.page_settings.seo.missing_extensions"):"",_===""?` ${n("layout_editor.page_settings.seo.missing_page_type")}`:""]})]}),i.jsxs("div",{style:Wt,children:[i.jsx("label",{style:Gt,children:n("layout_editor.page_settings.seo.toggle_setting")}),i.jsxs("div",{"data-testid":`${g}-toggle-setting`,children:[ee?i.jsxs("div",{style:{display:"flex",gap:6,alignItems:"center",marginBottom:4},children:[i.jsx("code",{style:Hc,children:ee}),i.jsx("button",{type:"button","data-testid":`${g}-toggle-setting-clear`,onClick:()=>v("toggle_setting",void 0),style:Fc,children:"✕"})]}):null,i.jsx("input",{type:"text","data-testid":`${g}-toggle-setting-search`,value:ie,placeholder:n("layout_editor.page_settings.seo.toggle_setting_search"),onChange:K=>oe(K.target.value),style:Fr}),ie.trim()!==""&&i.jsx("div",{"data-testid":`${g}-toggle-setting-results`,style:Kc,children:G.length===0?i.jsx("p",{style:ro,children:n("layout_editor.inline_binding.no_results")}):G.map(K=>i.jsx("button",{type:"button","data-testid":`${g}-toggle-setting-option-${K.ref}`,onClick:()=>{v("toggle_setting",K.ref),oe("")},style:Vc,children:K.label??K.ref},K.ref))})]})]}),i.jsxs("div",{style:Wt,children:[i.jsx("label",{style:Gt,children:n("layout_editor.page_settings.seo.data_sources")}),i.jsx("p",{style:ro,children:n("layout_editor.page_settings.seo.data_sources_hint")}),i.jsx("div",{"data-testid":`${g}-data-sources`,style:{display:"flex",flexDirection:"column",gap:6},children:a.length===0?i.jsx("span",{style:ro,children:n("layout_editor.page_settings.seo.data_sources_empty")}):a.map(K=>{const te=k.includes(K.id);return i.jsxs("label",{"data-testid":`${g}-data-source-${K.id}`,style:T0(te),children:[i.jsx("input",{type:"checkbox","data-testid":`${g}-data-source-check-${K.id}`,checked:te,onChange:()=>fe(K.id)}),i.jsx(jc,{option:K,testIdPrefix:`${g}-data-source-${K.id}`})]},K.id)})})]}),i.jsxs("div",{style:Wt,children:[i.jsx("label",{style:Gt,children:n("layout_editor.page_settings.seo.priority")}),i.jsx("input",{type:"range","data-testid":`${g}-priority`,min:0,max:1,step:.1,value:typeof h.priority=="number"?h.priority:.5,onChange:K=>v("priority",Number(K.target.value))}),i.jsx("span",{"data-testid":`${g}-priority-value`,style:{fontSize:12,color:"#475569"},children:typeof h.priority=="number"?h.priority:.5})]}),i.jsxs("div",{style:Wt,children:[i.jsx("label",{style:Gt,children:n("layout_editor.page_settings.seo.changefreq")}),i.jsxs("select",{"data-testid":`${g}-changefreq`,value:typeof h.changefreq=="string"?h.changefreq:"",onChange:K=>v("changefreq",K.target.value),style:Fr,children:[i.jsx("option",{value:"",children:n("layout_editor.page_settings.seo.use_default")}),I0.map(K=>i.jsx("option",{value:K,children:K},K))]})]}),i.jsxs("div",{style:Wt,children:[i.jsx("label",{style:Gt,children:n("layout_editor.page_settings.seo.title")}),i.jsx(_t,{value:typeof h.title=="string"?h.title:"",onChange:K=>v("title",K??""),t:n,candidates:r,testidPrefix:`${g}-title`,enableExpressionTree:!0,expressionTreeCollapsible:!0})]}),i.jsxs("div",{style:Wt,children:[i.jsx("label",{style:Gt,children:n("layout_editor.page_settings.seo.description")}),i.jsx(_t,{value:typeof h.description=="string"?h.description:"",onChange:K=>v("description",K??""),t:n,candidates:r,testidPrefix:`${g}-description`,enableExpressionTree:!0,expressionTreeCollapsible:!0})]}),i.jsx(Lw,{og:h.og,twitter:h.twitter,onChangeOg:K=>v("og",Object.keys(K).length>0?K:void 0),onChangeTwitter:K=>v("twitter",Object.keys(K).length>0?K:void 0),ogPreview:S?.og,twitterPreview:S?.twitter,defaultsAvailable:S?.defaultsAvailable??!1,candidates:r,t:n}),i.jsxs("div",{style:Wt,children:[i.jsx("label",{style:Gt,children:n("layout_editor.page_settings.seo.structured_data")}),i.jsx(Xw,{value:h.structured_data,onChange:K=>v("structured_data",K??void 0),autoBlock:S?.structured?.autoBlock,autoMeta:S?.structured?.autoMeta,lockedByFilter:S?.structured?.lockedByFilter,filteredBlock:S?.structured?.filteredBlock,pageType:_,candidates:r,t:n})]}),i.jsxs("div",{style:Wt,children:[i.jsx("label",{style:Gt,children:n("layout_editor.page_settings.seo.vars")}),i.jsx(a0,{vars:h.vars,onChange:K=>v("vars",Object.keys(K).length>0?K:void 0),varCandidates:A,gatingMet:x,candidates:r,t:n})]}),i.jsx(h0,{templateIdentifier:o,layout:F,url:l,locale:d,seedContext:c,settingsSignature:L,t:n,fetchImpl:b})]})})]})}const Wt={display:"flex",flexDirection:"column",gap:4},Gt={fontSize:12,fontWeight:600,color:"#334155"},Fr={padding:"5px 8px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,width:"100%",minWidth:0,boxSizing:"border-box"},ro={margin:"4px 0 0",fontSize:11,color:"#94a3b8"},T0=e=>({display:"flex",alignItems:"center",gap:8,padding:"6px 10px",fontSize:12,border:`1px solid ${e?"#3b82f6":"#e2e8f0"}`,borderRadius:8,background:e?"#eff6ff":"#fff",cursor:"pointer",minWidth:0}),D0={fontSize:12,color:"#b45309",background:"#fffbeb",border:"1px solid #fcd34d",borderRadius:6,padding:"8px 10px"},Hc={display:"inline-flex",alignItems:"center",gap:4,fontSize:11,padding:"3px 8px",borderRadius:999,background:"#eef2ff",color:"#3730a3"},Fc={border:"none",background:"transparent",cursor:"pointer",color:"#6366f1",fontSize:11,padding:0},z0={fontSize:11,padding:"3px 10px",borderRadius:999,border:"1px dashed #cbd5e1",background:"#f8fafc",color:"#475569",cursor:"pointer"},Kc={marginTop:4,border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",padding:6,display:"flex",flexDirection:"column",gap:2},Vc={textAlign:"left",border:"none",background:"transparent",cursor:"pointer",padding:"5px 8px",borderRadius:4,fontSize:12,color:"#0f172a"},N0={setState:"state",loadFromLocalStorage:"state",saveToLocalStorage:"state",navigate:"nav",openWindow:"nav",navigateBack:"nav",navigateForward:"nav",replaceUrl:"nav",toast:"notify",openModal:"notify",closeModal:"notify",showAlert:"notify",setError:"notify",refetchDataSource:"data",appendDataSource:"data",updateDataSource:"data",apiCall:"data",scrollIntoView:"etc",login:"etc",loginTwoFactor:"etc",loginTwoFactorResend:"etc",logout:"etc",setLocale:"locale",emitEvent:"etc",conditions:"flow",sequence:"flow",parallel:"flow",switch:"flow",suppress:"flow",showErrorPage:"error"},M0={state:"layout_editor.action.group.state",nav:"layout_editor.action.group.nav",notify:"layout_editor.action.group.notify",data:"layout_editor.action.group.data",locale:"layout_editor.action.group.locale",etc:"layout_editor.action.group.etc",flow:"layout_editor.action.group.flow",error:"layout_editor.action.group.error",extension:"layout_editor.action.group.extension"},L0={init:["state","data","notify","nav","locale","etc","flow","error","extension"],component:["nav","notify","data","state","locale","etc","flow","error","extension"]};function P0(e,t){const n=e?.[t];if(n&&typeof n=="object"){const o=n.__source;if(o&&typeof o=="object")return o}return null}function O0(e,t){return!e||e.kind==="core"?t("layout_editor.action.source_core"):e.id??t(`layout_editor.action.source_${e.kind}`)}function Uc(e,t,n){return typeof e!="string"?n:e.startsWith("$t:")?t(e.slice(3)):e}function B0({recipes:e,t,onAdd:n,context:o="init",testIdPrefix:r="g7le-action-add"}){const[s,a]=u.useState(!1),[l,d]=u.useState(""),c=u.useMemo(()=>Io(e),[e]),p=u.useMemo(()=>{const f=L0[o],b=new Map;for(const m of c){const h=P0(e,m.id),y=h!=null&&h.kind!=="core"?"extension":N0[m.id]??"etc",_=b.get(y)??[];_.push({recipe:m,source:h}),b.set(y,_)}const g=l.trim().toLowerCase();return f.map(m=>{let h=b.get(m)??[];return g&&(h=h.filter(({recipe:v})=>Uc(v.label,t,v.id).toLowerCase().includes(g)||v.id.toLowerCase().includes(g)||v.build.handler.toLowerCase().includes(g))),{groupKey:m,items:h}}).filter(m=>m.items.length>0)},[c,e,o,l,t]);return c.length===0?i.jsx("div",{"data-testid":`${r}-empty`,style:Y0,children:t("layout_editor.action.no_recipes")}):i.jsxs("div",{className:r,"data-testid":r,style:{minWidth:0},children:[i.jsxs("button",{type:"button","data-testid":`${r}-toggle`,onClick:()=>a(f=>!f),style:W0,children:[t("layout_editor.action.add_action")," ▾"]}),s?i.jsxs("div",{"data-testid":`${r}-list`,style:j0,children:[i.jsx("input",{type:"text","data-testid":`${r}-search`,value:l,placeholder:t("layout_editor.action.add_search"),onChange:f=>d(f.target.value),style:H0}),p.map(({groupKey:f,items:b})=>i.jsxs("div",{"data-testid":`${r}-group-${f}`,children:[i.jsxs("div",{style:F0,children:["━━ ",t(M0[f]??f)," ━━"]}),b.map(({recipe:g,source:m})=>i.jsxs("button",{type:"button","data-testid":`g7le-init-action-spec-${g.id}`,onClick:()=>{n(Ir(g,{})),a(!1),d("")},style:K0,children:[i.jsx("span",{style:V0,children:Uc(g.label,t,g.id)}),i.jsxs("span",{"data-testid":`g7le-init-action-spec-source-${g.id}`,style:U0,children:["〔",O0(m,t),"〕"]}),i.jsx("code",{style:q0,children:g.build.handler})]},g.id))]},f)),p.length===0?i.jsx("div",{"data-testid":`${r}-no-match`,style:G0,children:t("layout_editor.action.add_no_match")}):null]}):null]})}const W0={padding:"6px 12px",fontSize:12,border:"1px dashed #94a3b8",borderRadius:6,background:"#fff",color:"#475569",cursor:"pointer"},j0={marginTop:6,border:"1px solid #cbd5e1",borderRadius:8,background:"#fff",maxHeight:320,overflowY:"auto",padding:8},H0={width:"100%",padding:"5px 8px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,marginBottom:6,boxSizing:"border-box"},F0={fontSize:11,fontWeight:700,color:"#94a3b8",padding:"6px 4px 2px"},K0={display:"flex",alignItems:"center",gap:6,width:"100%",textAlign:"left",padding:"6px 8px",fontSize:12,border:"none",background:"transparent",cursor:"pointer",borderRadius:6},V0={flex:1,minWidth:0,color:"#0f172a"},U0={fontSize:10,color:"#64748b",whiteSpace:"nowrap"},q0={fontSize:10,color:"#cbd5e1",fontFamily:"monospace",whiteSpace:"nowrap"},G0={fontSize:12,color:"#94a3b8",padding:"8px 4px"},Y0={fontSize:12,color:"#94a3b8",padding:"8px 0"};function X0(e,t){return t.startsWith("$t:")?e(t.slice(3)):t}function J0({action:e,recipe:t,values:n={},t:o,missingRequired:r=!1,showCodeButton:s=!0,testIdPrefix:a="g7le-action-preview"}){const[l,d]=u.useState(!1),c=t?Yd(t,n,p=>X0(o,p)):typeof e.handler=="string"?e.handler:o("layout_editor.action.advanced_action");return i.jsxs("div",{className:a,"data-testid":a,style:Z0,children:[r?i.jsxs("span",{"data-testid":`${a}-missing`,style:R0,children:["⚠ ",o("layout_editor.action.summary_missing_required")]}):i.jsx("span",{"data-testid":`${a}-summary`,style:Q0,children:c}),s?i.jsx("button",{type:"button","data-testid":`${a}-code-toggle`,onClick:()=>d(p=>!p),style:ek,"aria-expanded":l,"aria-label":o("layout_editor.action.view_code"),children:""}):null,s&&l?i.jsx("pre",{"data-testid":`${a}-code`,style:tk,children:JSON.stringify(e,null,2)}):null]})}const Z0={display:"flex",flexWrap:"wrap",alignItems:"center",gap:6,minWidth:0},Q0={flex:1,minWidth:0,fontSize:12,color:"#475569"},R0={flex:1,minWidth:0,fontSize:12,color:"#b45309"},ek={border:"1px solid #cbd5e1",borderRadius:6,background:"#f8fafc",color:"#475569",cursor:"pointer",fontSize:11,padding:"2px 6px",whiteSpace:"nowrap"},tk={flexBasis:"100%",margin:"6px 0 0",padding:8,background:"#0f172a",color:"#e2e8f0",borderRadius:6,fontSize:11,overflow:"auto"},qc="__g7le_custom__";function Kr({value:e,onChange:t,candidates:n,placeholder:o,testid:r,t:s,bindingCandidates:a}){const l=e==null?"":String(e),d=Array.isArray(n)&&n.length>0,c=d&&n.some(b=>b.value===l),[p,f]=u.useState(!d||l!==""&&!c);return u.useEffect(()=>{l!==""&&d&&!c&&f(!0)},[l,d,c]),d&&!p?i.jsxs("div",{style:Jc,children:[i.jsxs("select",{className:"g7le-widget g7le-widget--picker","data-testid":r,value:c?l:"",onChange:b=>{const g=b.target.value;if(g===qc){f(!0);return}t(g===""?void 0:g)},style:{...Yc,flex:1,minWidth:0},children:[i.jsx("option",{value:"",children:s("layout_editor.control.default")}),n.map(b=>i.jsx("option",{value:b.value,children:b.label},b.value)),i.jsx("option",{value:qc,children:s("layout_editor.recipe.custom_input")})]}),i.jsx("button",{type:"button","data-testid":`${r}-to-custom`,title:s("layout_editor.recipe.custom_input"),"aria-label":s("layout_editor.recipe.custom_input"),onClick:()=>f(!0),style:Zc,children:"✎"})]}):i.jsxs("div",{style:Jc,children:[i.jsx("div",{style:{flex:1,minWidth:0},children:i.jsx(Ke,{value:l,onChange:b=>t(b===""?void 0:b),t:s,candidates:a,placeholder:o,testidPrefix:`${r}-input`})}),d&&i.jsx("button",{type:"button","data-testid":`${r}-pick`,title:s("layout_editor.recipe.pick_from_list"),onClick:()=>{f(!1),t(void 0)},style:Zc,children:"≡"})]})}function nk({value:e,onChange:t,candidates:n,t:o,bindingCandidates:r}){return i.jsx(Kr,{value:e,onChange:t,candidates:n,placeholder:o("layout_editor.recipe.page_placeholder"),testid:"g7le-widget-page-picker",t:o,bindingCandidates:r})}function Gc({value:e,onChange:t,candidates:n,t:o,bindingCandidates:r}){return i.jsx(Kr,{value:e,onChange:t,candidates:n,placeholder:o("layout_editor.recipe.datasource_placeholder"),testid:"g7le-widget-datasource-picker",t:o,bindingCandidates:r})}function Ls({value:e,onChange:t,candidates:n,t:o,bindingCandidates:r}){return i.jsx(Kr,{value:e,onChange:t,candidates:n,placeholder:o("layout_editor.recipe.state_key_placeholder"),testid:"g7le-widget-state-key-picker",t:o,bindingCandidates:r})}function ok({value:e,onChange:t,candidates:n,t:o,bindingCandidates:r}){return i.jsx(Kr,{value:e,onChange:t,candidates:n,placeholder:o("layout_editor.recipe.modal_placeholder"),testid:"g7le-widget-modal-picker",t:o,bindingCandidates:r})}function rk({value:e,onChange:t,t:n,bindingCandidates:o}){const r=typeof e=="string"?e:e==null?"":String(e);return i.jsx("div",{className:"g7le-widget g7le-widget--i18n-text","data-testid":"g7le-widget-i18n-text",style:{flex:1,minWidth:100,width:"100%"},children:i.jsx(_t,{value:r,onChange:s=>t(s===""?void 0:s),t:n,placeholder:n("layout_editor.recipe.i18n_text_placeholder"),testidPrefix:"g7le-widget-i18n-text-field",candidates:o,enableExpressionTree:!0,expressionTreeCollapsible:!0})})}function ik({value:e,onChange:t,candidates:n,t:o}){const r=e==null?"":String(e),s=r.split(",").map(d=>d.trim()).filter(Boolean),a=Array.isArray(n)&&n.length>0,l=d=>{if(!d)return;const c=[...s,d];t(c.join(", "))};return i.jsxs("div",{className:"g7le-widget g7le-widget--binding-list","data-testid":"g7le-widget-binding-list",style:{display:"flex",flexDirection:"column",gap:4,width:"100%"},children:[i.jsx("input",{type:"text","data-testid":"g7le-widget-binding-list-input",value:r,placeholder:o("layout_editor.recipe.datasource_placeholder"),onChange:d=>t(d.target.value===""?void 0:d.target.value),style:Xc}),a?i.jsxs("select",{className:"g7le-widget g7le-widget--picker","data-testid":"g7le-widget-binding-list-add",value:"",onChange:d=>{l(d.target.value),d.target.value=""},style:{...Yc,minWidth:0},children:[i.jsx("option",{value:"",children:o("layout_editor.recipe.pick_from_list")}),n.map(d=>i.jsx("option",{value:d.value,children:d.label},d.value))]}):null]})}function sk({value:e,onChange:t}){const n=e==null?"":String(e);return i.jsx("input",{type:"text",className:"g7le-widget g7le-widget--text","data-testid":"g7le-widget-text",value:n,onChange:o=>t(o.target.value===""?void 0:o.target.value),style:Xc})}const Yc={padding:"5px 8px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",minWidth:140,width:"100%"},Xc={flex:1,padding:"5px 8px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,minWidth:100,width:"100%"},Jc={display:"flex",gap:4,alignItems:"flex-start",width:"100%"},Zc={padding:"4px 8px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",color:"#64748b",cursor:"pointer"};function ak(e){if(e==null)return"null";if(Array.isArray(e))return"list";switch(typeof e){case"number":return"number";case"boolean":return"boolean";case"string":return"string";default:return"object"}}function Qc(e){switch(e){case"string":return"";case"number":return 0;case"boolean":return!1;case"null":return null;case"list":return[];case"object":return{};default:return null}}function Ps(e){return/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(e)}function lk(e,t,n){return t.includes(n)?"self":e.includes(n)?"inherited":"self"}function dk(e){const t=e.state&&typeof e.state=="object"&&Object.keys(e.state).length>0,n=e.initLocal&&typeof e.initLocal=="object"?e.initLocal:void 0;if(!t)return{initLocal:n??{},migrated:!1};const o={...e.state};if(n)for(const[r,s]of Object.entries(n))o[r]=s;return{initLocal:o,migrated:!0}}const Rc=[{kind:"string",labelKey:"layout_editor.initstate.kind_string"},{kind:"number",labelKey:"layout_editor.initstate.kind_number"},{kind:"boolean",labelKey:"layout_editor.initstate.kind_boolean"},{kind:"null",labelKey:"layout_editor.initstate.kind_null"},{kind:"list",labelKey:"layout_editor.initstate.kind_list"},{kind:"object",labelKey:"layout_editor.initstate.kind_object"}];function Mn({value:e,onChange:t,t:n,path:o,scope:r,depth:s=0,renderStringLeaf:a}){const l=ak(e),d=c=>{t(Qc(c))};return i.jsxs("div",{className:"g7le-initstate-value",style:{paddingLeft:s>0?12:0,minWidth:0},children:[i.jsxs("div",{style:uk,children:[i.jsx("select",{"data-testid":`g7le-initstate-type-${o}`,value:l,onChange:c=>d(c.target.value),style:eu,children:Rc.map(({kind:c,labelKey:p})=>i.jsx("option",{value:c,children:n(p)},c))}),l==="string"?a?i.jsx("div",{style:{flex:1,minWidth:0},children:a({value:String(e??""),onChange:c=>t(c),testidPrefix:`g7le-initstate-value-${o}`})}):i.jsx("input",{type:"text","data-testid":`g7le-initstate-value-${o}`,value:String(e??""),onChange:c=>t(c.target.value),style:tu}):l==="number"?i.jsx("input",{type:"number","data-testid":`g7le-initstate-value-${o}`,value:typeof e=="number"?e:"",onChange:c=>{const p=parseFloat(c.target.value);t(Number.isNaN(p)?0:p)},style:tu}):l==="boolean"?i.jsx(Br,{checked:!!e,onChange:t,testid:`g7le-initstate-value-${o}`,label:n(e?"layout_editor.overlay.on":"layout_editor.overlay.off")}):l==="null"?i.jsx("span",{"data-testid":`g7le-initstate-value-${o}`,style:pk,children:"null"}):null]}),l==="list"?i.jsxs("div",{style:nu,children:[e.map((c,p)=>i.jsxs("div",{style:fk,children:[i.jsxs("span",{style:gk,children:["[",p,"]"]}),i.jsx("div",{style:{flex:1,minWidth:0},children:i.jsx(Mn,{value:c,onChange:f=>{const b=e.slice();b[p]=f,t(b)},t:n,path:`${o}.${p}`,scope:r,depth:s+1,renderStringLeaf:a})}),i.jsx("button",{type:"button","data-testid":`g7le-initstate-item-remove-${o}.${p}`,onClick:()=>t(e.filter((f,b)=>b!==p)),style:ou,children:"✕"})]},p)),i.jsxs("button",{type:"button","data-testid":`g7le-initstate-item-add-${o}`,onClick:()=>t([...e,""]),style:ru,children:["+ ",n("layout_editor.initstate.add_item")]})]}):null,l==="object"?i.jsx(ck,{value:e,onChange:t,t:n,path:o,scope:r,depth:s+1,renderStringLeaf:a}):null]})}function ck({value:e,onChange:t,t:n,path:o,scope:r,depth:s,renderStringLeaf:a}){const[l,d]=u.useState(""),[c,p]=u.useState("string"),[f,b]=u.useState(null),g=Object.entries(e),m=()=>{const h=l.trim();if(!h){b(n("layout_editor.initstate.name_required"));return}if(!Ps(h)){b(n("layout_editor.initstate.name_invalid"));return}if(h in e){b(n("layout_editor.initstate.name_duplicate"));return}t({...e,[h]:Qc(c)}),d(""),b(null)};return i.jsxs("div",{style:nu,children:[g.map(([h,v])=>i.jsxs("div",{style:hk,children:[i.jsx("code",{style:yk,children:h}),i.jsx("div",{style:{flex:1,minWidth:0},children:i.jsx(Mn,{value:v,onChange:y=>t({...e,[h]:y}),t:n,path:`${o}.${h}`,scope:r,depth:s,renderStringLeaf:a})}),i.jsx("button",{type:"button","data-testid":`g7le-initstate-subremove-${o}.${h}`,onClick:()=>{const y={...e};delete y[h],t(y)},style:ou,children:"✕"})]},h)),i.jsxs("div",{style:bk,children:[i.jsx("input",{type:"text","data-testid":`g7le-initstate-subkey-input-${o}`,value:l,placeholder:n("layout_editor.initstate.subkey_name"),onChange:h=>{d(h.target.value),f&&b(null)},onKeyDown:h=>{h.key==="Enter"&&(h.preventDefault(),m())},"aria-invalid":f?!0:void 0,style:{...mk,...f?vk:null}}),i.jsx("select",{"data-testid":`g7le-initstate-subkey-kind-${o}`,value:c,onChange:h=>p(h.target.value),"aria-label":n("layout_editor.initstate.value_kind"),style:eu,children:Rc.map(({kind:h,labelKey:v})=>i.jsx("option",{value:h,children:n(v)},h))}),i.jsxs("button",{type:"button","data-testid":`g7le-initstate-subkey-add-${o}`,onClick:m,style:ru,children:["+ ",n("layout_editor.initstate.add_subkey")]})]}),f?i.jsx("p",{"data-testid":`g7le-initstate-subkey-error-${o}`,style:_k,children:f}):null]})}const uk={display:"flex",gap:6,alignItems:"center",minWidth:0},eu={padding:"4px 6px",fontSize:11,border:"1px solid #cbd5e1",borderRadius:6,color:"#64748b"},tu={flex:1,minWidth:0,padding:"4px 8px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,boxSizing:"border-box"},pk={fontSize:12,color:"#94a3b8",fontFamily:"monospace"},nu={display:"flex",flexDirection:"column",gap:6,marginTop:6,paddingLeft:8,borderLeft:"2px solid #e2e8f0"},fk={display:"flex",gap:6,alignItems:"flex-start",minWidth:0},gk={fontSize:11,color:"#94a3b8",minWidth:28,paddingTop:4},hk={display:"flex",gap:6,alignItems:"flex-start",minWidth:0},yk={fontSize:11,color:"#475569",fontFamily:"monospace",minWidth:72,paddingTop:4},ou={border:"none",background:"transparent",color:"#94a3b8",cursor:"pointer",fontSize:12},ru={alignSelf:"flex-start",padding:"2px 8px",fontSize:11,border:"1px dashed #94a3b8",borderRadius:6,background:"#fff",color:"#475569",cursor:"pointer"},bk={display:"flex",gap:6,alignItems:"center"},mk={padding:"4px 8px",fontSize:11,border:"1px solid #cbd5e1",borderRadius:6},vk={border:"1px solid #dc2626"},_k={margin:"2px 0 0",fontSize:11,color:"#dc2626"},xk=new Set(["text","data-chip","number"]),wk=new Set(["select","toggle","page-picker","datasource-picker","state-key-picker","modal-picker","component-target-picker","tag-input","locale-picker","i18n-text","segmented","slider","color","image"]);function kk(e,t,n){return typeof e!="string"?n:e.startsWith("$t:")?t(e.slice(3)):e}function Sk(e,t){if(e==="page-picker")return t.pageCandidates;if(e==="datasource-picker")return t.dataSourceCandidates;if(e==="state-key-picker")return t.stateKeyCandidates;if(e==="modal-picker")return t.modalCandidates}function Ck({param:e,value:t,t:n,pools:o,onChange:r,testIdPrefix:s,renderActionList:a,siblingValues:l}){const d=kk(e.label,n,e.key);if(e.advanced)return i.jsxs("div",{"data-testid":`${s}-locked-${e.key}`,style:Lk,children:[i.jsx("span",{style:Pk,children:d}),i.jsxs("span",{style:Ok,children:["[",n("layout_editor.action.advanced"),"] ",n("layout_editor.init_actions.advanced_param_hint")]})]});if(e.widget==="action-list"&&a){const f=Array.isArray(t)?t.filter(b=>!!b&&typeof b=="object"):[];return i.jsxs("div",{"data-testid":`${s}-param-${e.key}`,style:{display:"flex",flexDirection:"column",gap:4},children:[i.jsx("span",{style:io,children:d}),a(f,b=>r(b.length===0?void 0:b),`${s}-${e.key}`,e.key)]})}if(e.widget==="branch-list"&&a){const f=Array.isArray(t)?t.filter(b=>!!b&&typeof b=="object"):[];return i.jsxs("div",{"data-testid":`${s}-param-${e.key}`,style:{display:"flex",flexDirection:"column",gap:4},children:[i.jsx("span",{style:io,children:d}),i.jsx($k,{branches:f,onChange:b=>r(b.length===0?void 0:b),t:n,pools:o,renderActionList:a,testIdPrefix:`${s}-${e.key}`})]})}if(e.widget==="key-value")return i.jsxs("div",{"data-testid":`${s}-param-${e.key}`,style:{display:"flex",flexDirection:"column",gap:4},children:[i.jsx("span",{style:io,children:d}),i.jsx(Dk,{value:t,onChange:r,t:n,candidates:o.bindingCandidates,testIdPrefix:`${s}-kv-${e.key}`})]});if(e.widget==="state-key-value")return i.jsxs("div",{"data-testid":`${s}-param-${e.key}`,style:{display:"flex",flexDirection:"column",gap:4},children:[i.jsx("span",{style:io,children:d}),i.jsx(zk,{value:t,onChange:r,t:n,candidates:o.bindingCandidates,stateKeyCandidates:o.stateKeyCandidates,param:e,siblingTarget:l?.target,testIdPrefix:`${s}-skv-${e.key}`})]});if(xk.has(e.widget??"")||!wk.has(e.widget??""))return i.jsxs("div",{"data-testid":`${s}-param-${e.key}`,style:su,children:[i.jsx("span",{style:io,children:d}),i.jsx("div",{style:{flex:1,minWidth:0},children:i.jsx(Ke,{value:typeof t=="string"?t:t==null?"":String(t),onChange:f=>r(f===""?void 0:f),t:n,candidates:o.bindingCandidates,placeholder:e.widget==="number"?n("layout_editor.action_param.number_placeholder"):void 0,testidPrefix:`${s}-chip-${e.key}`})})]});const p=zn(e.widget);return i.jsxs("div",{"data-testid":`${s}-param-${e.key}`,style:su,children:[i.jsx("span",{style:io,children:d}),i.jsx("div",{style:{flex:1,minWidth:0},children:p?i.jsx(p,{control:{...e},value:t,onChange:r,t:n,candidates:Sk(e.widget,o),bindingCandidates:o.bindingCandidates}):i.jsx(Ke,{value:typeof t=="string"?t:t==null?"":String(t),onChange:f=>r(f===""?void 0:f),t:n,candidates:o.bindingCandidates,testidPrefix:`${s}-chip-${e.key}`})})]})}function $k({branches:e,onChange:t,t:n,pools:o,renderActionList:r,testIdPrefix:s}){const a=b=>Array.isArray(b)?b.filter(g=>!!g&&typeof g=="object"):b&&typeof b=="object"?[b]:[],l=b=>{if(b.length!==0)return b.length===1?b[0]:b},d=(b,g)=>{const m=e.slice();m[b]=g,t(m)},c=b=>{t(e.filter((g,m)=>m!==b))},p=(b,g)=>{if(g<0||g>=e.length)return;const m=e.slice(),[h]=m.splice(b,1);m.splice(g,0,h),t(m)},f=()=>{t([...e,{if:"",then:void 0}])};return i.jsxs("div",{"data-testid":`${s}-branches`,style:{display:"flex",flexDirection:"column",gap:8},children:[e.map((b,g)=>{const m=typeof b.if=="string"?b.if:"",h=a(b.then);return i.jsxs("div",{"data-testid":`${s}-branch-${g}`,style:Ek,children:[i.jsxs("div",{style:Ik,children:[i.jsxs("span",{style:Ak,children:[n(m?"layout_editor.action_recipe.conditions.branch_when":"layout_editor.action_recipe.conditions.branch_else")," ",g+1]}),i.jsxs("span",{style:{display:"flex",gap:4},children:[i.jsx("button",{type:"button","data-testid":`${s}-branch-${g}-up`,onClick:()=>p(g,g-1),disabled:g===0,style:Os,title:n("layout_editor.action_recipe.conditions.move_up"),children:"↑"}),i.jsx("button",{type:"button","data-testid":`${s}-branch-${g}-down`,onClick:()=>p(g,g+1),disabled:g===e.length-1,style:Os,title:n("layout_editor.action_recipe.conditions.move_down"),children:"↓"}),i.jsx("button",{type:"button","data-testid":`${s}-branch-${g}-remove`,onClick:()=>c(g),style:Os,title:n("layout_editor.action_recipe.conditions.remove_branch"),children:"✕"})]})]}),i.jsxs("div",{style:{display:"flex",flexDirection:"column",gap:3},children:[i.jsx("span",{style:iu,children:n("layout_editor.action_recipe.conditions.param_if")}),i.jsx(Ke,{value:m,onChange:v=>d(g,{...b,if:v}),t:n,candidates:o.bindingCandidates,testidPrefix:`${s}-branch-${g}-if`})]}),i.jsxs("div",{style:{display:"flex",flexDirection:"column",gap:3},children:[i.jsx("span",{style:iu,children:n("layout_editor.action_recipe.conditions.param_then")}),r(h,v=>d(g,{...b,then:l(v)}),`${s}-branch-${g}-then`)]})]},g)}),i.jsxs("button",{type:"button","data-testid":`${s}-add-branch`,onClick:f,style:Tk,children:["+ ",n("layout_editor.action_recipe.conditions.add_branch")]})]})}const Ek={border:"1px solid #e2e8f0",borderRadius:6,padding:8,display:"flex",flexDirection:"column",gap:6,background:"#f8fafc"},Ik={display:"flex",justifyContent:"space-between",alignItems:"center"},Ak={fontSize:12,fontWeight:700,color:"#475569"},iu={fontSize:11,color:"#64748b"},Os={border:"1px solid #cbd5e1",background:"#fff",borderRadius:4,width:22,height:22,cursor:"pointer",fontSize:12,lineHeight:"1",color:"#475569"},Tk={border:"1px dashed #94a3b8",background:"#fff",borderRadius:6,padding:"6px 10px",cursor:"pointer",fontSize:12,color:"#475569",alignSelf:"flex-start"};function Dk({value:e,onChange:t,t:n,candidates:o,testIdPrefix:r,renderKeyInput:s}){const a=e&&typeof e=="object"&&!Array.isArray(e)?Object.entries(e).map(([l,d])=>({key:l,value:typeof d=="string"?d:d==null?"":String(d)})):[];return i.jsx(jr,{value:a,onChange:l=>{const d={};for(const c of l)c.key.trim()!==""&&(d[c.key]=c.value??"");t(Object.keys(d).length===0?void 0:d)},keyField:"key",valueField:"value",t:n,candidates:o,testidPrefix:r,keyPlaceholder:n("layout_editor.action_param.key_placeholder"),valuePlaceholder:n("layout_editor.action_param.value_placeholder"),addLabel:n("layout_editor.action_param.add_pair"),renderKeyInput:s})}function zk({value:e,onChange:t,t:n,candidates:o,stateKeyCandidates:r,param:s,siblingTarget:a,testIdPrefix:l}){const d=e&&typeof e=="object"&&!Array.isArray(e)?Object.entries(e):[],c=typeof a=="string"?a:"";if((c.startsWith("_local.")||c.startsWith("global.")||c.startsWith("$parent._local.")||c.startsWith("$root._local."))&&d.length===1&&d[0][0]==="value"){const y=d[0][1];return i.jsx("div",{"data-testid":`${l}-path-value`,style:{display:"flex",flexDirection:"column",gap:4},children:i.jsx(Mn,{value:y,onChange:_=>t(_===void 0?void 0:{value:_}),t:n,path:"value",scope:`${l}-path-value-val`,renderStringLeaf:_=>i.jsx(Ke,{value:_.value,onChange:_.onChange,t:n,candidates:o,testidPrefix:_.testidPrefix})})})}const b=(y,_)=>{const w={};for(const[k,x]of d)w[k]=k===y?_:x;t(Object.keys(w).length===0?void 0:w)},g=y=>{const _={};for(const[w,k]of d)w!==y&&(_[w]=k);t(Object.keys(_).length===0?void 0:_)},[m,h]=u.useState(""),v=()=>{const y=m.trim();if(y===""||d.some(([w])=>w===y))return;const _={};for(const[w,k]of d)_[w]=k;_[y]="",t(_),h("")};return i.jsxs("div",{"data-testid":l,style:{display:"flex",flexDirection:"column",gap:6},children:[d.map(([y,_])=>i.jsxs("div",{"data-testid":`${l}-row-${y}`,style:{display:"flex",gap:6,alignItems:"flex-start",border:"1px solid #e2e8f0",borderRadius:8,padding:8},children:[i.jsxs("div",{style:{flex:1,minWidth:0,display:"flex",flexDirection:"column",gap:6},children:[i.jsx("div",{style:{minWidth:0},children:i.jsx(Ls,{control:{...s},value:y,onChange:w=>{const k=w==null?"":String(w);if(k===""||k===y||d.some(([$])=>$===k))return;const x={};for(const[$,C]of d)x[$===y?k:$]=C;t(x)},t:n,candidates:r})}),i.jsx("div",{style:{minWidth:0},children:i.jsx(Mn,{value:_,onChange:w=>b(y,w),t:n,path:y,scope:`${l}-val`,renderStringLeaf:w=>i.jsx(Ke,{value:w.value,onChange:w.onChange,t:n,candidates:o,testidPrefix:w.testidPrefix})})})]}),i.jsx("button",{type:"button","data-testid":`${l}-remove-${y}`,onClick:()=>g(y),title:n("layout_editor.page_settings.seo.remove"),"aria-label":n("layout_editor.page_settings.seo.remove"),style:{flex:"0 0 auto",padding:"4px 8px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",color:"#64748b",cursor:"pointer"},children:"✕"})]},y)),i.jsxs("div",{style:{display:"flex",gap:6,alignItems:"center"},children:[i.jsx("div",{style:{flex:1,minWidth:0},children:i.jsx(Ls,{control:{...s},value:m,onChange:y=>h(y==null?"":String(y)),t:n,candidates:r})}),i.jsxs("button",{type:"button","data-testid":`${l}-add`,onClick:v,style:{flex:"0 0 auto",padding:"5px 10px",fontSize:12,border:"1px dashed #cbd5e1",borderRadius:6,background:"#f8fafc",color:"#475569",cursor:"pointer"},children:["+ ",n("layout_editor.action_param.add_pair")]})]})]})}function Bs({raw:e,recipe:t,values:n,t:o,pools:r,onChange:s,testIdPrefix:a,renderActionList:l}){const d=(c,p)=>{const f={...n,[c]:p},b=Ir(t,f),g=hs(e);s(g?Xd(b,g):b)};return i.jsx(i.Fragment,{children:t.params.filter(c=>Nk(c,n)).map(c=>i.jsx(Ck,{param:c,value:n[c.key],t:o,pools:r,onChange:p=>d(c.key,p),testIdPrefix:a,renderActionList:l,siblingValues:n},c.key))})}function Nk(e,t){const n=e.dependsOn;return!n||typeof n!="object"||typeof n.param!="string"?!0:t[n.param]===n.equals}function Mk({raw:e,t,onChange:n,testIdPrefix:o,candidates:r}){const[s,a]=u.useState(!1);return i.jsxs("div",{"data-testid":`${o}-if`,style:Bk,children:[i.jsxs("button",{type:"button",onClick:()=>a(l=>!l),style:Wk,children:["▸ ",t("layout_editor.init_actions.if_label")]}),s?i.jsx("div",{"data-testid":`${o}-if-input`,style:{minWidth:0},children:i.jsx(Ke,{value:hs(e)??"",onChange:l=>n(Xd(e,l)),t,candidates:r,placeholder:t("layout_editor.init_actions.if_placeholder"),testidPrefix:`${o}-if-chip`})}):null]})}const su={display:"flex",alignItems:"center",gap:8,minWidth:0},io={fontSize:11,color:"#64748b",minWidth:80},Lk={display:"flex",alignItems:"center",gap:8,padding:"4px 0"},Pk={fontSize:11,color:"#64748b",minWidth:80},Ok={fontSize:11,color:"#92400e"},Bk={display:"flex",flexDirection:"column",gap:4},Wk={textAlign:"left",border:"none",background:"transparent",color:"#475569",cursor:"pointer",fontSize:11,padding:0};function jk(e,t,n){if(!t)return n("layout_editor.action.source_core");const o=e?.[t],r=o&&typeof o=="object"&&o.__source?o.__source:null;return!r||r.kind==="core"?n("layout_editor.action.source_core"):r.id??n(`layout_editor.action.source_${r.kind}`)}function Hk(e,t,n){const o=Jn(e,t);if(o.kind==="preset"){const r=t.find(s=>s.id===o.recipeId);return Yd(r??null,o.values,s=>n(s.startsWith("$t:")?s.slice(3):s))}return typeof e.handler=="string"?e.handler:n("layout_editor.action_list.unknown")}function Yt({actions:e,onChange:t,t:n,recipes:o,chipContext:r,actionChipCandidates:s,addContext:a="init",renderAddPicker:l,renderEditor:d,candidatePools:c,showSourceBadge:p=!1,canDrag:f,canDropAt:b,isLocked:g,hideAddPicker:m=!1,testIdPrefix:h="g7le-action-list"}){const v=u.useMemo(()=>Io(o),[o]),y=u.useMemo(()=>{if(r==null||r==="init")return c;const O=Xl(r,n,s??null);if(O.length===0)return c;const M=c??{},B=new Set((M.bindingCandidates??[]).map(H=>H.expression)),z=O.filter(H=>!B.has(H.expression));return z.length===0?c:{...M,bindingCandidates:[...z,...M.bindingCandidates??[]]}},[r,s,c,n]),[_,w]=u.useState(null),[k,x]=u.useState(null),$=u.useCallback((O,M)=>{if(O===M||O<0||M<0||O>=e.length||M>=e.length)return;const B=e.slice(),[z]=B.splice(O,1);B.splice(M,0,z),t(B)},[e,t]),C=xs({length:e.length,onMove:$,canDrag:f,canDropAt:b}),S=u.useCallback(O=>{t(e.filter((M,B)=>B!==O))},[e,t]),D=u.useCallback((O,M)=>{const B=e.slice();B[O]=M,t(B)},[e,t]),E=u.useCallback(O=>{t([...e,O])},[e,t]),I=u.useCallback((O,M)=>{const B=Jn(O,v),z=B.kind==="preset"?v.find(P=>P.id===B.recipeId)??null:null;if(!z)return null;const H=B.kind==="preset"?B.values:{};return i.jsxs("div",{style:Uk,children:[i.jsx(Bs,{raw:O,recipe:z,values:H,t:n,pools:y??{},onChange:M,testIdPrefix:`${h}-edit`,renderActionList:(P,A,L,F)=>{const Y=F==="onSuccess"?"response":F==="onError"?"error":void 0;return i.jsx(Yt,{actions:P,onChange:A,t:n,recipes:o,chipContext:Y,actionChipCandidates:Y?s:void 0,candidatePools:Y?c:y,addContext:a,showSourceBadge:p,testIdPrefix:L})}}),i.jsx(Mk,{raw:O,t:n,onChange:M,testIdPrefix:`${h}-edit`,candidates:y?.bindingCandidates})]})},[v,n,y,c,s,h,o,a,p]),N=d??(o?I:void 0),T=m?null:l?l(E):o?i.jsx(B0,{recipes:o,t:n,onAdd:E,context:a,testIdPrefix:`${h}-add-picker`}):null;return i.jsxs("div",{className:h,"data-testid":h,children:[i.jsxs("ul",{style:{listStyle:"none",margin:0,padding:0,display:"flex",flexDirection:"column",gap:6},children:[e.map((O,M)=>{const B=Jn(O,v),z=B.kind==="preset"?B.recipeId:null,H=z?v.find(Y=>Y.id===z)??null:null,P=H===null,A=g?.(M)??!1,L=hs(O)!==null,F=N?N(O,Y=>D(M,Y)):null;return i.jsxs(u.Fragment,{children:[A?null:i.jsx("li",{"aria-hidden":"true",style:{listStyle:"none",margin:0,padding:0},children:i.jsx(to,{active:C.isDropTarget(M),testid:`${h}-dropline-${M}`})}),i.jsxs("li",{"data-testid":`${h}-item-${M}`,"data-drag-source":C.dragIndex===M?"true":void 0,style:{border:"1px solid #e2e8f0",borderRadius:8,padding:"8px 10px",...C.dragIndex===M?{opacity:.5}:{}},onDragOver:C.dragging&&!A?Y=>{Y.preventDefault();const V=Y.currentTarget.getBoundingClientRect();C.onDragOverItem(M,Y.clientY{Y.preventDefault(),C.onDrop()}:void 0,children:[i.jsxs("div",{style:{display:"flex",alignItems:"center",gap:8},children:[A?i.jsx("span",{style:{fontSize:12},children:"🔒"}):i.jsx("span",{"data-testid":`${h}-drag-${M}`,style:{color:"#94a3b8",cursor:"grab",fontSize:14,userSelect:"none"},draggable:!0,onDragStart:()=>C.onDragStart(M),onDragEnd:C.onDragEnd,"aria-label":n("layout_editor.action_list.drag_reorder"),role:"button",children:"⠿"}),i.jsx("span",{style:{flex:1,minWidth:0,fontSize:13},"data-testid":`${h}-summary-${M}`,children:Hk(O,v,n)}),p?i.jsxs("span",{"data-testid":`${h}-source-${M}`,style:Fk,children:["〔",jk(o,z,n),"〕"]}):null,L?i.jsxs("span",{"data-testid":`${h}-cond-badge-${M}`,style:Kk,children:["〔",n("layout_editor.init_actions.cond_badge"),"〕"]}):null,P?i.jsxs("span",{"data-testid":`${h}-advanced-${M}`,style:Vk,children:["[",n("layout_editor.action.advanced"),"]"]}):null,!A&&F?i.jsxs("button",{type:"button","data-testid":`${h}-edit-${M}`,onClick:()=>x(k===M?null:M),"aria-label":n("layout_editor.init_actions.edit"),title:n("layout_editor.init_actions.edit"),style:k===M?qk:au,children:["✎ ",n("layout_editor.init_actions.edit")]}):null,i.jsx("button",{type:"button","data-testid":`${h}-code-${M}`,onClick:()=>w(_===M?null:M),"aria-label":n("layout_editor.action_list.view_code"),title:n("layout_editor.action_list.view_code"),style:_===M?Gk:Ws,children:""}),A?null:i.jsx("button",{type:"button","data-testid":`${h}-remove-${M}`,onClick:()=>S(M),"aria-label":n("layout_editor.action_list.remove"),title:n("layout_editor.action_list.remove"),style:Yk,children:"✕"})]}),H?i.jsx(J0,{action:O,recipe:H,values:B.kind==="preset"?B.values:{},t:n,showCodeButton:!1,testIdPrefix:`${h}-preview-${M}`}):null,k===M&&!A&&F?i.jsx("div",{"data-testid":`${h}-edit-body-${M}`,children:F}):null,d&&k!==M?d(O,Y=>D(M,Y)):null,_===M?i.jsx("pre",{"data-testid":`${h}-code-view-${M}`,style:{margin:"8px 0 0",padding:8,background:"#0f172a",color:"#e2e8f0",borderRadius:6,fontSize:12,overflow:"auto"},children:JSON.stringify(O,null,2)}):null]})]},M)}),i.jsx("li",{"aria-hidden":"true",style:{listStyle:"none",margin:0,padding:0},children:i.jsx(to,{active:C.isDropTarget(e.length),testid:`${h}-dropline-end`})})]}),T?i.jsx("div",{style:{marginTop:8},children:T}):null]})}const Fk={fontSize:10,color:"#64748b",whiteSpace:"nowrap"},Kk={fontSize:10,color:"#7c3aed",whiteSpace:"nowrap"},Vk={fontSize:10,color:"#92400e",whiteSpace:"nowrap"},Uk={display:"flex",flexDirection:"column",gap:6,paddingTop:6,borderTop:"1px dashed #e2e8f0",marginTop:6},au={flex:"0 0 auto",padding:"3px 8px",fontSize:11,fontWeight:500,border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",color:"#475569",cursor:"pointer",whiteSpace:"nowrap",lineHeight:1.4},qk={...au,background:"#eff6ff",borderColor:"#bfdbfe",color:"#2563eb"},Ws={flex:"0 0 auto",width:26,height:24,display:"inline-flex",alignItems:"center",justifyContent:"center",fontSize:11,border:"1px solid #e2e8f0",borderRadius:6,background:"#fff",color:"#64748b",cursor:"pointer",fontFamily:"ui-monospace, monospace"},Gk={...Ws,background:"#0f172a",borderColor:"#0f172a",color:"#e2e8f0"},Yk={...Ws,color:"#dc2626",borderColor:"#fecaca",fontFamily:"inherit"};function lu(e){const t=e.__source;return t&&typeof t=="object"?t:null}function Xk({actions:e,onChange:t,recipes:n,t:o,bindingCandidates:r,pageCandidates:s,dataSourceCandidates:a,stateKeyCandidates:l,modalCandidates:d}){const c=u.useMemo(()=>({pageCandidates:s,dataSourceCandidates:a,stateKeyCandidates:l,modalCandidates:d,bindingCandidates:r}),[s,a,l,d,r]),p=u.useMemo(()=>e.filter(h=>lu(h)?.kind==="base"),[e]),f=u.useMemo(()=>e.filter(h=>lu(h)?.kind!=="base"),[e]),b=p.length>0,g=u.useCallback(h=>{t(b?[...p,...h]:h)},[t,b,p]),m=u.useCallback(()=>{},[]);return i.jsxs("div",{className:"g7le-init-actions-form","data-testid":"g7le-init-actions-form",style:Jk,children:[i.jsx("p",{style:Zk,children:o("layout_editor.page_settings.init_actions.heading")}),b?i.jsxs(i.Fragment,{children:[i.jsxs("div",{"data-testid":"g7le-init-action-group-base",style:du,children:[i.jsx("div",{style:cu,children:o("layout_editor.init_actions.group_base")}),i.jsx(Yt,{actions:p,onChange:m,t:o,recipes:n,candidatePools:c,addContext:"init",showSourceBadge:!0,isLocked:()=>!0,hideAddPicker:!0,testIdPrefix:"g7le-init-action-base"}),i.jsxs("p",{"data-testid":"g7le-init-action-base-hint",style:Qk,children:["ⓘ ",o("layout_editor.init_actions.base_readonly_hint")]})]}),i.jsxs("div",{"data-testid":"g7le-init-action-group-self",style:du,children:[i.jsx("div",{style:cu,children:o("layout_editor.init_actions.group_self")}),f.length===0?i.jsx("p",{style:uu,children:o("layout_editor.init_actions.empty_self")}):null,i.jsx(Yt,{actions:f,onChange:g,t:o,recipes:n,candidatePools:c,addContext:"init",showSourceBadge:!0,testIdPrefix:"g7le-init-action-self"})]}),i.jsxs("p",{"data-testid":"g7le-init-action-merge-order",style:Rk,children:["ⓘ ",o("layout_editor.init_actions.merge_order")]})]}):i.jsxs(i.Fragment,{children:[e.length===0?i.jsx("p",{"data-testid":"g7le-init-action-empty",style:uu,children:o("layout_editor.init_actions.empty")}):null,i.jsx(Yt,{actions:e,onChange:t,t:o,recipes:n,candidatePools:c,addContext:"init",showSourceBadge:!0,testIdPrefix:"g7le-init-action"})]})]})}const Jk={display:"flex",flexDirection:"column",gap:12,minWidth:0},Zk={margin:0,fontSize:13,fontWeight:700,color:"#0f172a"},du={border:"1px solid #e2e8f0",borderRadius:10,padding:10,display:"flex",flexDirection:"column",gap:8},cu={fontSize:12,fontWeight:700,color:"#475569"},Qk={margin:0,fontSize:11,color:"#64748b"},uu={margin:0,fontSize:12,color:"#94a3b8"},Rk={margin:0,fontSize:11,color:"#64748b"};function e1(e){return e==="spinner"?new Set(["spinner","page"]):new Set(["skeleton"])}function pu(e,t,n){return typeof e!="string"?n:e.startsWith("$t:")?t(e.slice(3)):e}function fu({candidates:e,value:t,onSelect:n,styleKind:o,t:r,testIdPrefix:s="g7le-loading-component-picker"}){const a=xo(),l=()=>{const d=a.open({ariaLabel:r("layout_editor.overlay.loading_picker.select"),width:520,maxHeightRatio:.7,content:i.jsx(t1,{candidates:e,value:t,styleKind:o,t:r,testIdPrefix:s,onPick:c=>{n(c),a.close(d)},onClose:()=>a.close(d)})})};return i.jsx("div",{className:s,"data-testid":s,style:{minWidth:0},children:i.jsxs("div",{style:n1,children:[i.jsx("span",{"data-testid":`${s}-current`,style:o1,children:t&&t.length>0?t:r("layout_editor.overlay.loading_picker.none")}),i.jsxs("button",{type:"button","data-testid":`${s}-open`,onClick:l,style:r1,children:["🔍 ",r("layout_editor.overlay.loading_picker.select")]})]})})}function t1({candidates:e,value:t,styleKind:n,t:o,testIdPrefix:r,onPick:s,onClose:a}){const[l,d]=u.useState(""),c=e1(n),p=u.useMemo(()=>{const f=l.trim().toLowerCase();return e.filter(b=>c.has(b.role)).filter(b=>f?pu(b.label,o,b.name).toLowerCase().includes(f)||b.name.toLowerCase().includes(f):!0)},[e,n,l,o]);return i.jsxs("div",{"data-testid":`${r}-modal`,style:i1,children:[i.jsxs("div",{style:s1,children:[i.jsxs("span",{style:a1,children:["🔍 ",o("layout_editor.overlay.loading_picker.select")]}),i.jsx("button",{type:"button","data-testid":`${r}-close`,onClick:a,"aria-label":o("layout_editor.overlay.loading_picker.select"),style:l1,children:"✕"})]}),i.jsx("input",{type:"text","data-testid":`${r}-search`,value:l,placeholder:o("layout_editor.overlay.loading_picker.search"),onChange:f=>d(f.target.value),style:d1,autoFocus:!0}),p.length===0?i.jsx("p",{"data-testid":`${r}-empty`,style:y1,children:o("layout_editor.overlay.loading_picker.empty")}):i.jsx("ul",{style:c1,children:p.map(f=>i.jsx("li",{children:i.jsxs("button",{type:"button","data-testid":`${r}-item-${f.name}`,onClick:()=>s(f.name),style:{...u1,...t===f.name?p1:{}},children:[i.jsx("span",{style:f1,children:pu(f.label,o,f.name)}),i.jsx("code",{style:g1,children:f.name}),i.jsx("span",{style:h1,children:f.role})]})},f.name))})]})}const n1={display:"flex",alignItems:"center",gap:6,minWidth:0},o1={flex:1,minWidth:0,fontSize:12,color:"#0f172a",fontFamily:"monospace"},r1={padding:"4px 10px",fontSize:11,border:"1px solid #cbd5e1",borderRadius:6,background:"#f8fafc",color:"#475569",cursor:"pointer",whiteSpace:"nowrap"},i1={display:"flex",flexDirection:"column",minHeight:0,padding:16,boxSizing:"border-box",fontFamily:"system-ui, -apple-system, sans-serif"},s1={display:"flex",alignItems:"center",gap:8,paddingBottom:8,borderBottom:"1px solid #e2e8f0",marginBottom:10},a1={flex:1,fontSize:14,fontWeight:700,color:"#0f172a",minWidth:0},l1={border:"none",background:"transparent",color:"#64748b",cursor:"pointer",fontSize:15,lineHeight:1,padding:4},d1={width:"100%",padding:"6px 8px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,marginBottom:8,boxSizing:"border-box"},c1={listStyle:"none",margin:0,padding:0,display:"flex",flexDirection:"column",gap:2,overflowY:"auto",minHeight:0},u1={display:"flex",alignItems:"center",gap:6,width:"100%",textAlign:"left",padding:"8px 10px",fontSize:12,border:"1px solid transparent",background:"transparent",cursor:"pointer",borderRadius:6},p1={border:"1px solid #3b82f6",background:"#eff6ff"},f1={flex:1,minWidth:0,color:"#0f172a"},g1={fontSize:10,color:"#94a3b8",fontFamily:"monospace"},h1={fontSize:10,color:"#64748b",background:"#f1f5f9",borderRadius:4,padding:"0 4px"},y1={margin:0,fontSize:12,color:"#94a3b8",padding:"8px 4px"};function gu(e){return e===!0?{enabled:!0}:e===!1||e===void 0?{}:e}const b1=["opaque","blur","fade","skeleton","spinner"];function m1({value:e,patch:t,t:n,baseValue:o,ownValue:r,loadingComponents:s=[],progressiveDataSources:a=[],bindingCandidates:l}){const d=gu(e),c=d.enabled??!1,p=!!o&&Object.keys(o).length>0,f=zn("component-target-picker"),[b,g]=u.useState((d.wait_for??[]).length>0),m=u.useCallback(k=>{const x={...gu(e)};k(x),t(x)},[e,t]),h=r&&typeof r=="object"?r:e&&typeof e=="object"?e:{},v=u.useCallback(k=>p&&o!==void 0&&k in o&&!(k in h),[p,o,h]),y=u.useCallback(k=>{o&&m(x=>{x[k]=o[k]})},[o,m]),_=u.useCallback(k=>m(x=>{x.enabled=k}),[m]),w=d.target!==void 0?"region":"full";return i.jsxs("div",{className:"g7le-overlay-form","data-testid":"g7le-overlay-form",style:v1,children:[p?i.jsxs("p",{"data-testid":"g7le-overlay-inherit-banner",style:_1,children:["ⓘ ",n("layout_editor.overlay.inherit_banner")]}):null,i.jsxs("div",{style:Ur,children:[i.jsx(js,{label:n("layout_editor.overlay.enabled_label"),inherited:v("enabled"),hasInheritance:p,onOverride:()=>y("enabled"),testidKey:"enabled",t:n}),i.jsx(Wr,{disabled:v("enabled"),children:i.jsx(Br,{checked:c,onChange:_,testid:"g7le-overlay-enabled",ariaLabel:n("layout_editor.overlay.enabled_label"),label:n(c?"layout_editor.overlay.on":"layout_editor.overlay.off")})}),i.jsxs("p",{style:qr,children:["ⓘ ",n("layout_editor.overlay.enabled_hint")]})]}),i.jsx(Wr,{disabled:!c,testid:"g7le-overlay-body",style:{display:"flex",flexDirection:"column",gap:16,minWidth:0},children:i.jsxs(i.Fragment,{children:[i.jsxs("div",{style:Ur,children:[i.jsx(js,{label:n("layout_editor.overlay.scope_label"),inherited:v("target"),hasInheritance:p,onOverride:()=>y("target"),testidKey:"target",t:n}),i.jsxs("div",{"data-testid":"g7le-overlay-scope",style:Hs,children:[i.jsx(Vr,{active:w==="full",onClick:()=>m(k=>{delete k.target,delete k.fallback_target}),testid:"g7le-overlay-scope-full",children:n("layout_editor.overlay.scope_full")}),i.jsx(Vr,{active:w==="region",onClick:()=>m(k=>{k.target=k.target??""}),testid:"g7le-overlay-scope-region",children:n("layout_editor.overlay.scope_region")})]}),i.jsx(Wr,{disabled:w!=="region",testid:"g7le-overlay-region-box",style:S1,children:i.jsxs(i.Fragment,{children:[i.jsx(Ln,{label:n("layout_editor.overlay.target_label"),children:i.jsx("div",{"data-testid":"g7le-overlay-target-picker",children:f?i.jsx(f,{control:{label:n("layout_editor.overlay.target_label")},value:d.target??"",onChange:k=>m(x=>{x.target=typeof k=="string"?k:""}),t:n}):i.jsx("input",{type:"text","data-testid":"g7le-overlay-target-input",value:d.target??"",onChange:k=>m(x=>{x.target=k.target.value}),style:E1})})}),i.jsx(Ln,{label:n("layout_editor.overlay.fallback_label"),children:i.jsx("div",{"data-testid":"g7le-overlay-fallback-picker",children:f?i.jsx(f,{control:{label:n("layout_editor.overlay.fallback_label")},value:d.fallback_target??"",onChange:k=>m(x=>{const $=typeof k=="string"?k:"";$===""?delete x.fallback_target:x.fallback_target=$}),t:n}):null})}),i.jsxs("p",{style:qr,children:["ⓘ ",n("layout_editor.overlay.fallback_hint")]})]})})]}),i.jsxs("div",{style:Ur,children:[i.jsx(js,{label:n("layout_editor.overlay.style_label"),inherited:v("style"),hasInheritance:p,onOverride:()=>y("style"),testidKey:"style",t:n}),i.jsx("div",{"data-testid":"g7le-overlay-style",style:Hs,children:b1.map(k=>i.jsx(Vr,{active:(d.style??"opaque")===k,onClick:()=>m(x=>{x.style=k}),testid:`g7le-overlay-style-${k}`,children:n(`layout_editor.overlay.style_${k}`)},k))}),d.style==="skeleton"?i.jsxs("div",{style:hu,children:[i.jsx(Ln,{label:n("layout_editor.overlay.skeleton_component"),children:i.jsx(fu,{candidates:s,value:d.skeleton?.component,styleKind:"skeleton",onSelect:k=>m(x=>{x.skeleton={...x.skeleton,component:k}}),t:n,testIdPrefix:"g7le-overlay-skeleton-component"})}),i.jsx(Ln,{label:n("layout_editor.overlay.skeleton_animation"),children:i.jsx("div",{"data-testid":"g7le-overlay-skeleton-anim",style:Hs,children:["wave","pulse","none"].map(k=>i.jsx(Vr,{active:(d.skeleton?.animation??"wave")===k,onClick:()=>m(x=>{x.skeleton={...x.skeleton,animation:k}}),testid:`g7le-overlay-skeleton-anim-${k}`,children:n(`layout_editor.overlay.anim_${k}`)},k))})}),i.jsx(Ln,{label:n("layout_editor.overlay.skeleton_count"),children:i.jsx("input",{type:"number","data-testid":"g7le-overlay-skeleton-count",value:d.skeleton?.iteration_count??"",onChange:k=>m(x=>{const $=parseInt(k.target.value,10);x.skeleton={...x.skeleton,iteration_count:Number.isNaN($)?void 0:$}}),style:I1})})]}):null,d.style==="spinner"?i.jsxs("div",{style:hu,children:[i.jsx(Ln,{label:n("layout_editor.overlay.spinner_component"),children:i.jsx(fu,{candidates:s,value:d.spinner?.component,styleKind:"spinner",onSelect:k=>m(x=>{x.spinner={...x.spinner,component:k}}),t:n,testIdPrefix:"g7le-overlay-spinner-component"})}),i.jsx(Ln,{label:n("layout_editor.overlay.spinner_text"),children:i.jsx("div",{"data-testid":"g7le-overlay-spinner-text",style:{minWidth:0},children:i.jsx(_t,{value:d.spinner?.text??"",onChange:k=>m(x=>{x.spinner={...x.spinner,text:k===void 0||k===""?void 0:k}}),t:n,candidates:l,enableExpressionTree:!0,expressionTreeCollapsible:!0,testidPrefix:"g7le-overlay-spinner-text-field"})})})]}):null]}),i.jsxs("div",{style:Ur,children:[i.jsxs("button",{type:"button","data-testid":"g7le-overlay-waitfor-toggle","aria-expanded":b,onClick:()=>g(k=>!k),style:D1,children:[b?"▾":"▸"," ",n("layout_editor.overlay.waitfor_label")]}),b?i.jsxs("div",{"data-testid":"g7le-overlay-waitfor-body",style:z1,children:[i.jsxs("p",{"data-testid":"g7le-overlay-waitfor-hint",style:qr,children:["ⓘ ",n("layout_editor.overlay.waitfor_hint")]}),a.length===0?i.jsx("p",{"data-testid":"g7le-overlay-waitfor-empty",style:qr,children:n("layout_editor.overlay.waitfor_empty")}):i.jsx("div",{"data-testid":"g7le-overlay-waitfor",style:{display:"flex",flexWrap:"wrap",gap:6},children:a.map(k=>{const x=(d.wait_for??[]).includes(k.id);return i.jsxs("label",{"data-testid":`g7le-overlay-waitfor-${k.id}`,style:A1(x),children:[i.jsx("input",{type:"checkbox",checked:x,onChange:()=>m($=>{const C=new Set($.wait_for??[]);C.has(k.id)?C.delete(k.id):C.add(k.id);const S=Array.from(C);S.length===0?delete $.wait_for:$.wait_for=S})}),i.jsx(jc,{option:k,testIdPrefix:`g7le-overlay-waitfor-${k.id}`})]},k.id)})})]}):null]}),p?i.jsxs("button",{type:"button","data-testid":"g7le-overlay-reset-inherit",onClick:()=>t(void 0),style:T1,children:["⟲ ",n("layout_editor.overlay.reset_inherit")]}):null]})})]})}function Vr({active:e,onClick:t,children:n,testid:o}){return i.jsx("button",{type:"button","data-testid":o,"aria-pressed":e,onClick:t,style:{...w1,...e?k1:{}},children:n})}function js({label:e,inherited:t,hasInheritance:n,onOverride:o,testidKey:r,t:s}){return i.jsxs("div",{style:N1,children:[i.jsx("label",{style:x1,children:e}),n?t?i.jsxs(i.Fragment,{children:[i.jsxs("span",{"data-testid":`g7le-overlay-inherited-${r}`,style:M1,children:["〔",s("layout_editor.overlay.inherited_badge"),"〕"]}),i.jsx("button",{type:"button","data-testid":`g7le-overlay-override-${r}`,onClick:o,style:P1,children:s("layout_editor.overlay.override_field")})]}):i.jsxs("span",{"data-testid":`g7le-overlay-overridden-${r}`,style:L1,children:["〔",s("layout_editor.overlay.overridden_badge"),"〕"]}):null]})}function Ln({label:e,children:t}){return i.jsxs("div",{style:C1,children:[i.jsx("span",{style:$1,children:e}),i.jsx("div",{style:{flex:1,minWidth:0},children:t})]})}const v1={display:"flex",flexDirection:"column",gap:16,minWidth:0},_1={margin:0,fontSize:12,color:"#1d4ed8",background:"#eff6ff",padding:"8px 10px",borderRadius:6},Ur={display:"flex",flexDirection:"column",gap:6,minWidth:0},x1={fontSize:13,fontWeight:600,color:"#0f172a"},qr={margin:0,fontSize:11,color:"#94a3b8"},Hs={display:"flex",gap:4,flexWrap:"wrap"},w1={padding:"4px 10px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",color:"#475569",cursor:"pointer"},k1={border:"1px solid #3b82f6",background:"#eff6ff",color:"#1d4ed8"},S1={display:"flex",flexDirection:"column",gap:8,padding:8,border:"1px dashed #e2e8f0",borderRadius:8},hu={display:"flex",flexDirection:"column",gap:8,padding:8,border:"1px dashed #e2e8f0",borderRadius:8},C1={display:"flex",alignItems:"center",gap:8,minWidth:0},$1={fontSize:11,color:"#64748b",minWidth:90},E1={width:"100%",padding:"5px 8px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,boxSizing:"border-box"},I1={width:80,padding:"5px 8px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6},A1=e=>({display:"inline-flex",alignItems:"center",gap:4,padding:"4px 8px",fontSize:12,border:`1px solid ${e?"#3b82f6":"#cbd5e1"}`,borderRadius:12,background:e?"#eff6ff":"#fff",cursor:"pointer"}),T1={alignSelf:"flex-start",padding:"6px 12px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",color:"#475569",cursor:"pointer"},D1={alignSelf:"flex-start",padding:"4px 0",fontSize:13,fontWeight:600,color:"#0f172a",background:"transparent",border:"none",cursor:"pointer"},z1={display:"flex",flexDirection:"column",gap:6,paddingLeft:14,minWidth:0},N1={display:"flex",alignItems:"center",gap:8,flexWrap:"wrap",minWidth:0},M1={fontSize:11,color:"#64748b",background:"#f1f5f9",borderRadius:4,padding:"1px 6px"},L1={fontSize:11,color:"#0369a1",background:"#e0f2fe",borderRadius:4,padding:"1px 6px"},P1={padding:"2px 8px",fontSize:11,border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",color:"#475569",cursor:"pointer"},O1="g7le:computed-effect-preview",B1={number:"layout_editor.computed.type_number",string:"layout_editor.computed.type_string",boolean:"layout_editor.computed.type_boolean",list:"layout_editor.computed.type_list",object:"layout_editor.computed.type_object",null:"layout_editor.computed.type_null"};function W1(e,t,n){if(t==="list"){const o=e,r=o.slice(0,2).map(s=>JSON.stringify(s)).join(", ");return o.length<=2?`[${r}]`:`[${r}, ${n("layout_editor.computed.list_more",{count:o.length})}]`}return t==="object"?JSON.stringify(e):t==="boolean"?e?"true":"false":t==="null"?n("layout_editor.computed.value_none"):String(e)}function yu({expr:e,computedKey:t,sampleContext:n,t:o,isEffect:r=!1,testIdPrefix:s="g7le-computed-preview"}){const a=Od(e,n),l=()=>{typeof window>"u"||!t||window.dispatchEvent(new CustomEvent(O1,{detail:{key:t}}))};return a.ok?i.jsxs("div",{className:s,"data-testid":s,style:j1,children:[i.jsx("span",{style:H1,children:o("layout_editor.computed.preview_label")}),i.jsx("span",{"data-testid":`${s}-value`,style:F1,children:W1(a.value,a.type,o)}),i.jsxs("span",{"data-testid":`${s}-type`,style:K1,children:["(",o(B1[a.type]),")"]}),r&&a.type==="boolean"?i.jsxs("button",{type:"button","data-testid":`${s}-effect`,onClick:l,style:V1,children:["▷ ",o("layout_editor.computed.effect_preview")]}):null]}):i.jsxs("div",{"data-testid":`${s}-error`,style:U1,children:["⚠ ",o("layout_editor.computed.preview_error")]})}const j1={display:"flex",alignItems:"center",flexWrap:"wrap",gap:6,minWidth:0},H1={fontSize:11,color:"#94a3b8"},F1={fontSize:12,color:"#0f172a",fontFamily:"monospace",wordBreak:"break-all"},K1={fontSize:10,color:"#64748b"},V1={padding:"2px 8px",fontSize:11,border:"1px solid #cbd5e1",borderRadius:6,background:"#f8fafc",color:"#475569",cursor:"pointer"},U1={fontSize:12,color:"#b45309"},q1=[{op:"count",labelKey:"layout_editor.computed.op_count"},{op:"sum",labelKey:"layout_editor.computed.op_sum"},{op:"filter",labelKey:"layout_editor.computed.op_filter"},{op:"toOptions",labelKey:"layout_editor.computed.op_to_options"},{op:"nth",labelKey:"layout_editor.computed.op_nth"},{op:"firstOf",labelKey:"layout_editor.computed.op_first_of"},{op:"literal",labelKey:"layout_editor.computed.op_literal"}],G1=["=","!=",">","<",">=","<=","includes"],Y1=new Set(["count","sum","filter"]);function bu({model:e,onChange:t,t:n,dataSourceCandidates:o,bindingCandidates:r,existingKeys:s=[]}){const a=u.useCallback(g=>{const m={...e};g(m),t(m)},[e,t]),l=e.conditions??[],d=zn("datasource-picker"),c=e.key!==""&&s.includes(e.key),p=()=>a(g=>{g.conditions=[...g.conditions??[],{field:"",cmp:"=",value:""}]}),f=(g,m)=>a(h=>{const v=(h.conditions??[]).slice(),y={...v[g]};m(y),v[g]=y,h.conditions=v}),b=g=>a(m=>{m.conditions=(m.conditions??[]).filter((h,v)=>v!==g)});return i.jsxs("div",{className:"g7le-computed-custom","data-testid":"g7le-computed-custom",style:X1,children:[i.jsx(Fs,{label:`① ${n("layout_editor.computed.custom_step1")}`,children:i.jsx("div",{"data-testid":"g7le-computed-custom-source",children:d?i.jsx(d,{control:{},value:e.source,onChange:g=>a(m=>{m.source=typeof g=="string"?g:void 0}),t:n,candidates:o}):i.jsx("input",{type:"text",value:e.source??"",onChange:g=>a(m=>{m.source=g.target.value}),style:so})})}),i.jsxs(Fs,{label:`② ${n("layout_editor.computed.custom_step2")}`,children:[i.jsx("select",{"data-testid":"g7le-computed-custom-op",value:e.op,onChange:g=>a(m=>{m.op=g.target.value}),style:so,children:q1.map(({op:g,labelKey:m})=>i.jsx("option",{value:g,children:n(m)},g))}),e.op==="sum"?i.jsx("div",{"data-testid":"g7le-computed-custom-sumfield",style:{minWidth:0},children:i.jsx(Ke,{value:e.sumField??"",onChange:g=>a(m=>{m.sumField=g}),t:n,candidates:r,placeholder:n("layout_editor.computed.custom_sum_field"),testidPrefix:"g7le-computed-custom-sumfield-chip"})}):null,e.op==="toOptions"?i.jsxs("div",{style:{display:"flex",gap:6},children:[i.jsx("div",{"data-testid":"g7le-computed-custom-valuefield",style:{flex:1,minWidth:0},children:i.jsx(Ke,{value:e.valueField??"",onChange:g=>a(m=>{m.valueField=g}),t:n,candidates:r,placeholder:n("layout_editor.computed.custom_value_field"),testidPrefix:"g7le-computed-custom-valuefield-chip"})}),i.jsx("div",{"data-testid":"g7le-computed-custom-labelfield",style:{flex:1,minWidth:0},children:i.jsx(Ke,{value:e.labelField??"",onChange:g=>a(m=>{m.labelField=g}),t:n,candidates:r,placeholder:n("layout_editor.computed.custom_label_field"),testidPrefix:"g7le-computed-custom-labelfield-chip"})})]}):null,e.op==="nth"?i.jsxs("div",{style:{display:"flex",gap:6},children:[i.jsx("input",{type:"text","data-testid":"g7le-computed-custom-index",value:e.index??"",placeholder:"0",onChange:g=>a(m=>{m.index=g.target.value}),style:so}),i.jsx("div",{"data-testid":"g7le-computed-custom-prop",style:{flex:1,minWidth:0},children:i.jsx(Ke,{value:e.prop??"",onChange:g=>a(m=>{m.prop=g}),t:n,candidates:r,placeholder:n("layout_editor.computed.custom_prop"),testidPrefix:"g7le-computed-custom-prop-chip"})})]}):null,e.op==="literal"?i.jsxs("div",{style:{display:"flex",gap:6},children:[i.jsxs("select",{"data-testid":"g7le-computed-custom-literalkind",value:e.literalKind??"string",onChange:g=>a(m=>{m.literalKind=g.target.value}),style:so,children:[i.jsx("option",{value:"string",children:n("layout_editor.computed.kind_string")}),i.jsx("option",{value:"number",children:n("layout_editor.computed.kind_number")}),i.jsx("option",{value:"boolean",children:n("layout_editor.computed.kind_boolean")})]}),i.jsx("div",{"data-testid":"g7le-computed-custom-literalvalue",style:{flex:1,minWidth:0},children:i.jsx(Ke,{value:e.literalValue??"",onChange:g=>a(m=>{m.literalValue=g}),t:n,candidates:r,testidPrefix:"g7le-computed-custom-literalvalue-chip"})})]}):null,e.op==="firstOf"?i.jsxs("div",{style:{display:"flex",flexDirection:"column",gap:6},children:[i.jsx("input",{type:"text","data-testid":"g7le-computed-custom-candidates",value:(e.candidates??[]).join(", "),placeholder:n("layout_editor.computed.custom_candidates"),onChange:g=>a(m=>{m.candidates=g.target.value.split(",").map(h=>h.trim()).filter(Boolean)}),style:so}),i.jsx("div",{"data-testid":"g7le-computed-custom-fallback",style:{minWidth:0},children:i.jsx(Ke,{value:e.fallback??"",onChange:g=>a(m=>{m.fallback=g}),t:n,candidates:r,placeholder:n("layout_editor.computed.custom_fallback"),testidPrefix:"g7le-computed-custom-fallback-chip"})})]}):null,Y1.has(e.op)?i.jsxs("div",{style:R1,children:[l.map((g,m)=>i.jsxs("div",{"data-testid":`g7le-computed-custom-cond-${m}`,style:eS,children:[i.jsx("div",{"data-testid":`g7le-computed-custom-cond-field-${m}`,style:{flex:1,minWidth:0},children:i.jsx(Ke,{value:g.field,onChange:h=>f(m,v=>{v.field=h}),t:n,candidates:r,placeholder:n("layout_editor.computed.cond_field"),testidPrefix:`g7le-computed-custom-cond-field-${m}-chip`})}),i.jsx("select",{value:g.cmp,onChange:h=>f(m,v=>{v.cmp=h.target.value}),style:tS,children:G1.map(h=>i.jsx("option",{value:h,children:h},h))}),i.jsx("div",{"data-testid":`g7le-computed-custom-cond-value-${m}`,style:{flex:1,minWidth:0},children:i.jsx(Ke,{value:g.value,onChange:h=>f(m,v=>{v.value=h}),t:n,candidates:r,placeholder:n("layout_editor.computed.cond_value"),testidPrefix:`g7le-computed-custom-cond-value-${m}-chip`})}),i.jsx("button",{type:"button","data-testid":`g7le-computed-custom-cond-remove-${m}`,onClick:()=>b(m),style:nS,children:"✕"})]},m)),i.jsxs("button",{type:"button","data-testid":"g7le-computed-custom-cond-add",onClick:p,style:oS,children:["+ ",n("layout_editor.computed.cond_add")]})]}):null]}),i.jsxs(Fs,{label:`③ ${n("layout_editor.computed.custom_step3")}`,children:[i.jsx("input",{type:"text","data-testid":"g7le-computed-custom-key",value:e.key,onChange:g=>a(m=>{m.key=g.target.value}),style:{...so,...c?Q1:{}}}),c?i.jsxs("p",{"data-testid":"g7le-computed-custom-key-dup",style:rS,children:["⚠ ",n("layout_editor.computed.key_duplicate")]}):null]})]})}function Fs({label:e,children:t}){return i.jsxs("div",{style:J1,children:[i.jsx("div",{style:Z1,children:e}),i.jsx("div",{style:{display:"flex",flexDirection:"column",gap:6,minWidth:0},children:t})]})}function Ks(e){return bv(e)}const X1={display:"flex",flexDirection:"column",gap:12,padding:10,border:"1px solid #cbd5e1",borderRadius:8,background:"#fff"},J1={display:"flex",flexDirection:"column",gap:4,minWidth:0},Z1={fontSize:12,fontWeight:600,color:"#475569"},so={padding:"5px 8px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,minWidth:0,width:"100%",boxSizing:"border-box"},Q1={border:"1px solid #ef4444"},R1={display:"flex",flexDirection:"column",gap:6,marginTop:4},eS={display:"flex",gap:4,alignItems:"center",minWidth:0},tS={padding:"4px 6px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6},nS={border:"none",background:"transparent",color:"#94a3b8",cursor:"pointer",fontSize:12},oS={alignSelf:"flex-start",padding:"2px 8px",fontSize:11,border:"1px dashed #94a3b8",borderRadius:6,background:"#fff",color:"#475569",cursor:"pointer"},rS={margin:0,fontSize:11,color:"#b45309"};function Vs(e,t,n){return typeof e!="string"?n:e.startsWith("$t:")?t(e.slice(3)):e}function iS(e){if(e&&typeof e=="object"){const t=e.__source;if(t&&typeof t=="object")return t}return null}function sS({computed:e,onChange:t,recipes:n,t:o,sampleContext:r,dataSourceCandidates:s,bindingCandidates:a,computedSource:l={},effectKeys:d=[],onRevert:c}){const p=u.useMemo(()=>Td(n),[n]),[f,b]=u.useState(!1),[g,m]=u.useState(null),h=Object.keys(e),v=u.useCallback((x,$)=>{t({...e,[x]:$})},[e,t]),y=u.useCallback(x=>{const $={...e};delete $[x],t($)},[e,t]),_=u.useCallback((x,$,C)=>{const S={...e};x&&x!==$&&delete S[x],S[$]=C,t(S)},[e,t]),w=u.useCallback(x=>{const $=zd(x,{});let C=x.id,S=1;for(;C in e;)C=`${x.id}${++S}`;t({...e,[C]:$}),b(!1)},[e,t]),k=u.useCallback(()=>{!g||!g.key||(t({...e,[g.key]:Ks(g)}),m(null),b(!1))},[g,e,t]);return i.jsxs("div",{className:"g7le-computed-form","data-testid":"g7le-computed-form",style:cS,children:[i.jsx("p",{style:uS,children:o("layout_editor.page_settings.computed.heading")}),h.length>0?h.map(x=>i.jsx(aS,{computedKey:x,expr:e[x],recipes:p,rawRecipes:n,t:o,sampleContext:r,dataSourceCandidates:s,bindingCandidates:a,source:l[x],isEffect:d.includes(x),existingKeys:h,onPatchExpr:$=>v(x,$),onRename:($,C)=>_(x,$,C),onRemove:()=>y(x),onRevert:c?()=>c(x):void 0},x)):i.jsx("p",{"data-testid":"g7le-computed-empty",style:MS,children:o("layout_editor.computed.empty")}),i.jsxs("div",{style:{minWidth:0},children:[i.jsxs("button",{type:"button","data-testid":"g7le-computed-add",onClick:()=>b(x=>!x),style:$S,children:["+ ",o("layout_editor.computed.add")," ▾"]}),f?i.jsxs("div",{"data-testid":"g7le-computed-add-list",style:ES,children:[["common","more"].map(x=>{const $=p.filter(C=>(C.group??"more")===x);return $.length===0?null:i.jsxs("div",{children:[i.jsxs("div",{style:IS,children:["━━ ",o(`layout_editor.computed.group_${x}`)," ━━"]}),$.map(C=>{const S=iS(n?.[C.id]);return i.jsxs("button",{type:"button","data-testid":`g7le-computed-preset-${C.id}`,onClick:()=>w(C),style:AS,children:[i.jsx("span",{style:TS,children:Vs(C.label,o,C.id)}),S&&S.kind!=="core"?i.jsxs("span",{"data-testid":`g7le-computed-preset-source-${C.id}`,style:DS,children:["〔",S.id??S.kind,"〕"]}):null]},C.id)})]},x)}),i.jsxs("div",{style:{borderTop:"1px solid #e2e8f0",marginTop:6,paddingTop:6},children:[i.jsxs("button",{type:"button","data-testid":"g7le-computed-custom-open",onClick:()=>m(g?null:{key:"",op:"count",source:"",conditions:[]}),style:zS,children:["⚙ ",o("layout_editor.computed.custom_open")," ▾"]}),g?i.jsxs("div",{style:{marginTop:6},children:[i.jsx(bu,{model:g,onChange:m,t:o,dataSourceCandidates:s,bindingCandidates:a,existingKeys:h}),i.jsx("div",{"data-testid":"g7le-computed-custom-preview",style:{marginTop:6},children:i.jsx(yu,{expr:Ks(g),sampleContext:r,t:o,testIdPrefix:"g7le-computed-custom-preview-eval"})}),i.jsx("button",{type:"button","data-testid":"g7le-computed-custom-commit",disabled:!g.key||h.includes(g.key),onClick:k,style:NS,children:o("layout_editor.computed.custom_add")})]}):null]})]}):null]})]})}function aS({computedKey:e,expr:t,recipes:n,rawRecipes:o,t:r,sampleContext:s,dataSourceCandidates:a,bindingCandidates:l,source:d,isEffect:c,existingKeys:p,onPatchExpr:f,onRename:b,onRemove:g,onRevert:m}){const[h,v]=u.useState(!1),[y,_]=u.useState(!1),w=u.useMemo(()=>Pd(t,n),[t,n]),k=d==="base",x=d==="route-override",$=w.kind==="advanced",C=w.kind==="preset"?n.find(E=>E.id===w.recipeId)??null:null,S=C?Vs(C.label,r,e):e,D=(E,I)=>{if(w.kind!=="preset"||!C)return;const N={...w.params,[E]:I};f(zd(C,N))};return i.jsxs("div",{"data-testid":`g7le-computed-item-${e}`,style:pS,children:[i.jsxs("div",{style:fS,children:[i.jsx("span",{style:gS,children:S}),i.jsx("code",{style:hS,children:e}),k?i.jsxs("span",{"data-testid":`g7le-computed-source-${e}`,style:yS,children:["〔",r("layout_editor.computed.source_base"),"〕"]}):null,x?i.jsxs("span",{"data-testid":`g7le-computed-overridden-${e}`,style:bS,children:["〔",r("layout_editor.computed.source_overridden"),"〕"]}):null,$?i.jsxs("span",{"data-testid":"g7le-computed-advanced",style:mS,children:["[",r("layout_editor.action.advanced"),"]"]}):null,i.jsx("span",{style:{flex:1}}),i.jsx("button",{type:"button","data-testid":`g7le-computed-code-${e}`,onClick:()=>_(E=>!E),style:Us,"aria-label":r("layout_editor.value_tree.show_source"),title:r("layout_editor.value_tree.show_source"),children:""}),$?null:i.jsx("button",{type:"button","data-testid":`g7le-computed-edit-${e}`,onClick:()=>v(E=>!E),style:Us,children:r("layout_editor.init_actions.edit")}),i.jsx("button",{type:"button","data-testid":`g7le-computed-remove-${e}`,onClick:g,style:Us,"aria-label":r("layout_editor.init_actions.remove"),children:"✕"})]}),y?i.jsx("pre",{"data-testid":`g7le-computed-code-block-${e}`,style:LS,children:t}):null,i.jsx("div",{"data-testid":`g7le-computed-preview-${e}`,children:i.jsx(yu,{expr:t,computedKey:e,sampleContext:s,t:r,isEffect:c,testIdPrefix:`g7le-computed-preview-${e}-eval`})}),x&&m?i.jsxs("div",{style:vS,children:[i.jsxs("span",{style:_S,children:["ⓘ ",r("layout_editor.computed.overridden_hint")]}),i.jsx("button",{type:"button","data-testid":`g7le-computed-revert-${e}`,onClick:m,style:xS,children:r("layout_editor.computed.revert")})]}):null,h&&!$?i.jsxs("div",{style:wS,children:[k?i.jsxs("p",{"data-testid":`g7le-computed-override-notice-${e}`,style:kS,children:["⚠ ",r("layout_editor.computed.override_notice")]}):null,w.kind==="preset"&&C?i.jsx(i.Fragment,{children:C.params.map(E=>i.jsx(dS,{param:E,value:w.params[E.key]??"",t:r,dataSourceCandidates:a,bindingCandidates:l,onChange:I=>D(E.key,I)},E.key))}):w.kind==="custom"?i.jsx(bu,{model:{...w.model,key:e},onChange:E=>b(E.key,Ks(E)),t:r,dataSourceCandidates:a,bindingCandidates:l,existingKeys:p.filter(E=>E!==e)}):null]}):null]})}const lS=new Set(["datasource-picker","binding-picker","select","toggle","state-key-picker","page-picker"]);function dS({param:e,value:t,t:n,dataSourceCandidates:o,bindingCandidates:r,onChange:s}){const a=Vs(e.label,n,e.key),l=e.widget??"",c=lS.has(l)?zn(e.widget):null,p=l==="datasource-picker"||l==="binding-picker"?o:void 0;return i.jsxs("div",{"data-testid":`g7le-computed-param-${e.key}`,style:SS,children:[i.jsx("span",{style:CS,children:a}),i.jsx("div",{style:{flex:1,minWidth:0},children:c?i.jsx(c,{control:{...e},value:t,onChange:f=>s(typeof f=="string"?f:""),t:n,candidates:p}):i.jsx(Ke,{value:t,onChange:f=>s(f),t:n,candidates:r,testidPrefix:`g7le-computed-param-${e.key}-chip`})})]})}const cS={display:"flex",flexDirection:"column",gap:12,minWidth:0},uS={margin:0,fontSize:13,fontWeight:700,color:"#0f172a"},pS={border:"1px solid #e2e8f0",borderRadius:8,padding:"8px 10px",display:"flex",flexDirection:"column",gap:6,background:"#f8fafc"},fS={display:"flex",alignItems:"center",gap:6},gS={fontSize:13,fontWeight:600,color:"#0f172a"},hS={fontSize:10,color:"#64748b",fontFamily:"monospace"},yS={fontSize:10,color:"#0891b2",whiteSpace:"nowrap"},bS={fontSize:10,color:"#7c3aed",whiteSpace:"nowrap"},mS={fontSize:10,color:"#92400e",whiteSpace:"nowrap"},vS={display:"flex",alignItems:"center",gap:8,marginTop:4,minWidth:0},_S={fontSize:11,color:"#7c3aed",flex:1,minWidth:0},xS={border:"1px solid #c4b5fd",borderRadius:6,background:"#f5f3ff",color:"#6d28d9",cursor:"pointer",fontSize:11,padding:"2px 8px",whiteSpace:"nowrap"},Us={border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",color:"#475569",cursor:"pointer",fontSize:11,padding:"2px 6px"},wS={display:"flex",flexDirection:"column",gap:6,paddingTop:6,borderTop:"1px dashed #e2e8f0"},kS={margin:0,fontSize:11,color:"#b45309",background:"#fef3c7",padding:"6px 8px",borderRadius:6},SS={display:"flex",alignItems:"center",gap:8,minWidth:0},CS={fontSize:11,color:"#64748b",minWidth:90},$S={padding:"6px 12px",fontSize:12,border:"1px dashed #94a3b8",borderRadius:6,background:"#fff",color:"#475569",cursor:"pointer"},ES={marginTop:6,border:"1px solid #cbd5e1",borderRadius:8,background:"#fff",padding:8,maxHeight:380,overflowY:"auto"},IS={fontSize:11,fontWeight:700,color:"#94a3b8",padding:"6px 4px 2px"},AS={display:"flex",alignItems:"center",gap:6,width:"100%",textAlign:"left",padding:"6px 8px",fontSize:12,border:"none",background:"transparent",cursor:"pointer",borderRadius:6},TS={flex:1,minWidth:0,color:"#0f172a"},DS={fontSize:10,color:"#64748b",whiteSpace:"nowrap"},zS={padding:"6px 10px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,background:"#f8fafc",color:"#475569",cursor:"pointer"},NS={marginTop:6,padding:"6px 12px",fontSize:12,border:"1px solid #3b82f6",borderRadius:6,background:"#3b82f6",color:"#fff",cursor:"pointer"},MS={margin:0,fontSize:12,color:"#94a3b8"},LS={margin:"2px 0 0",padding:"6px 8px",fontSize:11,fontFamily:"monospace",color:"#0f172a",background:"#f1f5f9",border:"1px solid #e2e8f0",borderRadius:6,whiteSpace:"pre-wrap",wordBreak:"break-all",overflowX:"auto"};function PS(e,t){return t==="any"?!0:e===null||typeof e!="object"?!1:t==="object"?!Array.isArray(e):!0}function qs({value:e,onChange:t,onValidityChange:n,t:o,shape:r="any",emptyValue:s=void 0,placeholder:a,minHeight:l=80,testidPrefix:d="g7le-json-block",shapeErrorKey:c,invalidErrorKey:p,ariaLabel:f,id:b,validate:g}){const[m,h]=u.useState(()=>Gr(e)),[v,y]=u.useState(null),_=u.useRef(!1),w=u.useRef(e);u.useEffect(()=>{if(!_.current&&!Object.is(e,w.current)){if(Gr(e)===Gr(w.current)){w.current=e;return}h(Gr(e)),y(null),w.current=e,n?.(!0)}},[e]);const k=x=>{if(_.current=!0,h(x),x.trim()===""){y(null),w.current=s,n?.(!0),t(s);return}let $;try{$=JSON.parse(x)}catch{y(o(p??"layout_editor.json_block.invalid")),n?.(!1);return}if(!PS($,r)){y(o(c??"layout_editor.json_block.invalid")),n?.(!1);return}const C=g?.($)??null;if(C){y(C),n?.(!1);return}y(null),w.current=$,n?.(!0),t($)};return i.jsxs("div",{className:"g7le-json-block",style:OS,children:[i.jsx("textarea",{id:b,"data-testid":d,style:{...BS,minHeight:l},value:m,spellCheck:!1,"aria-label":f,"aria-invalid":v?!0:void 0,placeholder:a,onChange:x=>k(x.target.value),onBlur:()=>{_.current=!1}}),v?i.jsx("div",{"data-testid":`${d}-error`,style:WS,children:v}):null]})}function Gr(e){if(e===void 0)return"";try{return JSON.stringify(e,null,2)??""}catch{return""}}const OS={display:"flex",flexDirection:"column",gap:4,minWidth:0},BS={width:"100%",boxSizing:"border-box",fontFamily:"ui-monospace, SFMono-Regular, Menlo, monospace",fontSize:12,lineHeight:1.5,color:"#0f172a",background:"#fff",border:"1px solid #cbd5e1",borderRadius:6,padding:"6px 8px",resize:"vertical"},WS={fontSize:11,color:"#dc2626"},jS=[{kind:"string",labelKey:"layout_editor.initstate.kind_string"},{kind:"number",labelKey:"layout_editor.initstate.kind_number"},{kind:"boolean",labelKey:"layout_editor.initstate.kind_boolean"},{kind:"null",labelKey:"layout_editor.initstate.kind_null"},{kind:"list",labelKey:"layout_editor.initstate.kind_list"},{kind:"object",labelKey:"layout_editor.initstate.kind_object"}],Gs={local:"initLocal",global:"initGlobal",isolated:"initIsolated"};function HS({raw:e,own:t,patch:n,t:o,isolatedOrphan:r={}}){const s=u.useMemo(()=>{const f=t?.state&&typeof t.state=="object"?t.state:void 0,b=e?.state&&typeof e.state=="object"?e.state:void 0;if(!(!f&&!b))return{...b??{},...f??{}}},[t,e]),{initLocal:a,migrated:l}=u.useMemo(()=>dk({initLocal:e?.initLocal,state:s}),[e,s]),d=f=>{if(f==="local")return a;const b=e?.[Gs[f]];return b&&typeof b=="object"?b:{}},c=f=>{if(f==="local"){const b=t?.initLocal??{},g=s?Object.keys(s):[];return Array.from(new Set([...Object.keys(b),...g]))}return Object.keys(t?.[Gs[f]]??{})},p=u.useCallback((f,b)=>{const g=Gs[f];f==="local"&&l&&n("state",void 0),n(g,Object.keys(b).length===0?void 0:b)},[n,l]);return i.jsxs("div",{className:"g7le-initstate-form","data-testid":"g7le-initstate-form",style:KS,children:[i.jsx("p",{style:VS,children:o("layout_editor.page_settings.initstate.heading")}),l?i.jsxs("p",{"data-testid":"g7le-initstate-legacy-state",style:US,children:["ⓘ ",o("layout_editor.initstate.legacy_state")]}):null,i.jsx(Ys,{scope:"local",label:o("layout_editor.initstate.section_local"),value:d("local"),ownKeys:c("local"),patchSection:p,t:o}),i.jsx(Ys,{scope:"global",label:o("layout_editor.initstate.section_global"),value:d("global"),ownKeys:c("global"),patchSection:p,t:o}),i.jsx(Ys,{scope:"isolated",label:o("layout_editor.initstate.section_isolated"),description:o("layout_editor.initstate.section_isolated_desc"),value:d("isolated"),ownKeys:c("isolated"),patchSection:p,t:o,orphan:r}),i.jsxs("p",{style:dC,children:["ⓘ ",o("layout_editor.initstate.isolated_dynamic_hint")]})]})}function Ys({scope:e,label:t,description:n,value:o,ownKeys:r,patchSection:s,t:a,orphan:l={}}){const[d,c]=u.useState(""),[p,f]=u.useState("string"),[b,g]=u.useState(null),[m,h]=u.useState(!1),v=Object.keys(o),y=v,_=(x,$)=>{s(e,{...o,[x]:$})},w=x=>{const $={...o};delete $[x],s(e,$)},k=()=>{const x=d.trim();if(!x){g(a("layout_editor.initstate.name_required"));return}if(!Ps(x)){g(a("layout_editor.initstate.name_invalid"));return}if(x in o){g(a("layout_editor.initstate.name_duplicate"));return}const $={string:"",number:0,boolean:!1,null:null,list:[],object:{}};s(e,{...o,[x]:$[p]}),c(""),g(null)};return i.jsxs("div",{"data-testid":`g7le-initstate-section-${e}`,style:qS,children:[i.jsxs("div",{style:GS,children:[i.jsx("div",{style:YS,children:t}),i.jsx("button",{type:"button","data-testid":`g7le-initstate-${e}-mode-toggle`,onClick:()=>h(x=>!x),style:JS,children:a(m?"layout_editor.json_block.mode_block":"layout_editor.json_block.mode_code")})]}),n?i.jsxs("p",{style:XS,children:["ⓘ ",n]}):null,m?i.jsx(qs,{value:o,shape:"object",emptyValue:{},onChange:x=>s(e,x??{}),t:a,testidPrefix:`g7le-initstate-${e}-code`,placeholder:`{ "keyword": "", "page": 1 -}`,shapeErrorKey:"layout_editor.json_block.must_be_object",validate:x=>{const $=Object.keys(x??{}).filter(S=>!Ds(S));return $.length?a("layout_editor.json_block.invalid_keys",{keys:$.join(", ")}):null}}):i.jsxs(i.Fragment,{children:[v.length===0?i.jsx("p",{"data-testid":`g7le-initstate-${e}-empty`,style:FS,children:a("layout_editor.initstate.no_values")}):v.map(x=>{const S=F0(y,r,x)==="inherited",C=e==="isolated"&&l[x];return i.jsx(wS,{scope:e,itemKey:x,value:o[x],inherited:S,isOrphan:!!C,onChangeValue:T=>_(x,T),onRemove:()=>w(x),t:a},x)}),i.jsxs("div",{style:PS,children:[i.jsx("input",{type:"text","data-testid":`g7le-initstate-${e}-add-name`,value:d,placeholder:a("layout_editor.initstate.value_name"),onChange:x=>{c(x.target.value),b&&g(null)},onKeyDown:x=>{x.key==="Enter"&&(x.preventDefault(),k())},"aria-invalid":b?!0:void 0,style:{...OS,...b?BS:null}}),i.jsx("select",{"data-testid":`g7le-initstate-${e}-add-kind`,value:p,onChange:x=>f(x.target.value),"aria-label":a("layout_editor.initstate.value_kind"),style:jS,children:_S.map(x=>i.jsx("option",{value:x.kind,children:a(x.labelKey)},x.kind))}),i.jsxs("button",{type:"button","data-testid":`g7le-initstate-${e}-add`,onClick:k,style:HS,children:["+ ",a("layout_editor.initstate.add_value")]})]}),b?i.jsx("p",{"data-testid":`g7le-initstate-${e}-add-error`,style:WS,children:b}):null]})]})}function wS({scope:e,itemKey:t,value:n,inherited:o,isOrphan:r,onChangeValue:s,onRemove:a,t:l}){const[d,c]=u.useState(!1);let p;try{p=JSON.stringify(n,null,2)??String(n)}catch{p=String(n)}return i.jsxs("div",{"data-testid":`g7le-initstate-${e}-item-${t}`,style:DS,children:[i.jsxs("div",{style:zS,children:[o?i.jsx("span",{"data-testid":`g7le-initstate-inherited-${t}`,style:NS,children:"🔗"}):null,i.jsx("code",{style:MS,children:t}),i.jsx("span",{style:{flex:1}}),i.jsx("button",{type:"button","data-testid":`g7le-initstate-code-${t}`,onClick:()=>c(f=>!f),style:Vs,"aria-label":l("layout_editor.value_tree.show_source"),title:l("layout_editor.value_tree.show_source"),children:""}),o?i.jsx("button",{type:"button","data-testid":`g7le-initstate-revert-${t}`,onClick:a,style:Vs,children:l("layout_editor.initstate.revert")}):i.jsx("button",{type:"button","data-testid":`g7le-initstate-remove-${t}`,onClick:a,style:Vs,"aria-label":l("layout_editor.init_actions.remove"),children:"✕"})]}),d?i.jsx("pre",{"data-testid":`g7le-initstate-code-block-${t}`,style:VS,children:p}):null,i.jsx(zn,{value:n,onChange:s,t:l,path:t,scope:e}),r?i.jsxs("p",{"data-testid":`g7le-initstate-isolated-orphan-${t}`,style:LS,children:["⚠ ",l("layout_editor.initstate.isolated_orphan")]}):null]})}const kS={display:"flex",flexDirection:"column",gap:14,minWidth:0},SS={margin:0,fontSize:13,fontWeight:700,color:"#0f172a"},CS={margin:0,fontSize:11,color:"#1d4ed8",background:"#eff6ff",padding:"6px 8px",borderRadius:6},$S={display:"flex",flexDirection:"column",gap:8,padding:10,border:"1px solid #e2e8f0",borderRadius:10,minWidth:0},ES={display:"flex",alignItems:"center",justifyContent:"space-between",gap:8},IS={fontSize:12,fontWeight:700,color:"#475569"},AS={margin:0,fontSize:11,color:"#94a3b8"},TS={padding:"2px 8px",fontSize:11,border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",color:"#475569",cursor:"pointer"},DS={display:"flex",flexDirection:"column",gap:4,padding:"6px 8px",border:"1px solid #f1f5f9",borderRadius:6,minWidth:0},zS={display:"flex",alignItems:"center",gap:6},NS={fontSize:12},MS={fontSize:12,color:"#0f172a",fontFamily:"monospace"},Vs={border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",color:"#475569",cursor:"pointer",fontSize:11,padding:"2px 6px"},LS={margin:0,fontSize:11,color:"#b45309",background:"#fef3c7",padding:"4px 6px",borderRadius:6},PS={display:"flex",gap:6,alignItems:"center"},OS={padding:"4px 8px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6},BS={border:"1px solid #dc2626"},WS={margin:0,fontSize:11,color:"#dc2626"},jS={padding:"4px 8px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",color:"#0f172a"},HS={padding:"2px 10px",fontSize:11,border:"1px dashed #94a3b8",borderRadius:6,background:"#fff",color:"#475569",cursor:"pointer"},FS={margin:0,fontSize:12,color:"#94a3b8"},KS={margin:0,fontSize:11,color:"#94a3b8"},VS={margin:"2px 0",padding:"6px 8px",fontSize:11,fontFamily:"monospace",color:"#0f172a",background:"#f1f5f9",border:"1px solid #e2e8f0",borderRadius:6,whiteSpace:"pre-wrap",wordBreak:"break-all",overflowX:"auto"},US={self:"layout_editor.error_rows.source_self",inherited:"layout_editor.error_rows.source_inherited",template:"layout_editor.error_rows.source_template",none:"layout_editor.error_rows.source_none"};function Us({value:e,onChange:t,t:n,codes:o,mode:r="local",sourceOf:s,renderActionList:a,testIdPrefix:l="g7le-error-rows"}){const d=o??Object.keys(e),c=u.useCallback((f,b)=>{t({...e,[f]:b})},[e,t]),p=u.useCallback(f=>{const b={...e};delete b[f],t(b)},[e,t]);return i.jsx("div",{className:l,"data-testid":l,children:d.map(f=>{const b=e[f],g=b!==void 0,m=r==="badge"&&s?s(f):null;return i.jsxs("div",{"data-testid":`${l}-row-${f}`,style:{border:"1px solid #e2e8f0",borderRadius:8,padding:"8px 10px",marginBottom:6},children:[i.jsxs("div",{style:{display:"flex",alignItems:"center",gap:8},children:[i.jsx("strong",{"data-testid":`${l}-code-${f}`,style:{fontSize:13},children:f==="default"?n("layout_editor.error_rows.default"):f}),m?i.jsxs("span",{"data-testid":`${l}-source-${f}`,style:{fontSize:11,padding:"2px 6px",borderRadius:4,background:"#f1f5f9",color:"#475569"},children:["〔",n(US[m]),"〕"]}):null,g?i.jsx("button",{type:"button","data-testid":`${l}-clear-${f}`,onClick:()=>p(f),style:{marginLeft:"auto",fontSize:12},children:n("layout_editor.error_rows.clear")}):i.jsx("span",{style:{marginLeft:"auto",fontSize:12,color:"#94a3b8"},children:n("layout_editor.error_rows.no_action")})]}),m==="inherited"||m==="template"?i.jsxs("div",{"data-testid":`${l}-override-${f}`,style:{marginTop:6,display:"flex",alignItems:"flex-start",gap:8},children:[i.jsxs("p",{style:{margin:0,flex:1,fontSize:12,color:"#64748b"},children:["ⓘ ",n(m==="inherited"?"layout_editor.error_rows.override_inherited":"layout_editor.error_rows.override_template")]}),g?i.jsx("button",{type:"button","data-testid":`${l}-revert-${f}`,onClick:()=>p(f),style:{fontSize:12,padding:"2px 8px",border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",color:"#475569",cursor:"pointer",whiteSpace:"nowrap"},children:n("layout_editor.error_rows.revert")}):i.jsx("button",{type:"button","data-testid":`${l}-override-here-${f}`,onClick:()=>c(f,{}),style:{fontSize:12,padding:"2px 8px",border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",color:"#475569",cursor:"pointer",whiteSpace:"nowrap"},children:n("layout_editor.error_rows.override_here")})]}):null,f==="default"?i.jsxs("p",{"data-testid":`${l}-default-warn-${f}`,style:{margin:"6px 0 0",fontSize:12,color:"#b45309"},children:["⚠ ",n("layout_editor.error_rows.default_warning")]}):null,i.jsx("div",{style:{marginTop:6},children:a?a(f,b,h=>c(f,h)):i.jsx("pre",{"data-testid":`${l}-json-${f}`,style:{margin:0,fontSize:12,color:"#475569"},children:g?JSON.stringify(b):""})})]},f)})})}function qS(e){return[...e.filter(n=>n!=="maintenance"),"default"]}function GS({value:e,onChange:t,t:n,errorConfigCodes:o=[],ownCodes:r=[],templateCodes:s=[],recipes:a,pageCandidates:l,modalCandidates:d,dataSourceCandidates:c,stateKeyCandidates:p}){const f=u.useMemo(()=>ko(a),[a]),b=u.useMemo(()=>qS(o),[o]),g=f.length>0,m=u.useMemo(()=>Vl("error",n),[n]),h=u.useMemo(()=>({pageCandidates:l,dataSourceCandidates:c,stateKeyCandidates:p,modalCandidates:d,bindingCandidates:m}),[l,c,p,d,m]),v=u.useCallback(y=>r.includes(y)?"self":y in e?"inherited":s.includes(y)?"template":"none",[r,e,s]);return g?i.jsxs("div",{className:"g7le-error-form","data-testid":"g7le-error-form",style:fu,children:[i.jsx("p",{style:gu,children:n("layout_editor.page_settings.error.heading")}),i.jsxs("p",{style:XS,children:["ⓘ ",n("layout_editor.error.intro")]}),i.jsx(Us,{value:e,onChange:t,t:n,codes:b,mode:"badge",sourceOf:v,testIdPrefix:"g7le-error-rows",renderActionList:(y,_,w)=>i.jsx(YS,{code:y,action:_,recipes:a,normalized:f,t:n,pools:h,onPatch:w})})]}):i.jsxs("div",{className:"g7le-error-form","data-testid":"g7le-error-form",style:fu,children:[i.jsx("p",{style:gu,children:n("layout_editor.page_settings.error.heading")}),i.jsxs("p",{"data-testid":"g7le-error-degrade",style:JS,children:["ⓘ ",n("layout_editor.error.degrade")]}),i.jsx(Us,{value:e,onChange:t,t:n,codes:b,mode:"badge",sourceOf:v,testIdPrefix:"g7le-error-rows"})]})}function YS({code:e,action:t,recipes:n,normalized:o,t:r,pools:s,onPatch:a}){const[l,d]=u.useState(!1),c=Array.isArray(t)?t[0]:t,p=typeof c?.handler=="string"?c.handler:"",f=o.find(_=>_.id===p)??null,b=c?Yn(c,o):null,g=b?.kind==="preset"?b.values:{},m=p==="sequence"||p==="parallel",h=_=>{if(_===""){a({});return}const w=o.find(k=>k.id===_);w&&a(Sr(w,{}))},v=["","showErrorPage","navigate","openModal","toast","setState","sequence","parallel"],y=m&&c?Array.isArray(c.params?.actions)?c.params.actions:[]:[];return i.jsxs("div",{"data-testid":`g7le-error-action-${e}`,style:ZS,children:[i.jsxs("div",{style:{display:"flex",alignItems:"center",gap:6},children:[i.jsx("select",{"data-testid":`g7le-error-handler-${e}`,value:p,onChange:_=>h(_.target.value),style:{...QS,flex:1},children:v.map(_=>i.jsx("option",{value:_,children:r(_===""?"layout_editor.error.handler_none":`layout_editor.error.handler_${_}`)},_))}),p!==""?i.jsx("button",{type:"button","data-testid":`g7le-error-code-${e}`,onClick:()=>d(_=>!_),"aria-label":r("layout_editor.action_list.view_code"),title:r("layout_editor.action_list.view_code"),style:l?RS:hu,children:""}):null]}),m?i.jsx("div",{"data-testid":`g7le-error-actions-${e}`,children:i.jsx(Gt,{actions:y,onChange:_=>a({handler:p,params:{actions:_}}),t:r,recipes:n,candidatePools:s,chipContext:"error",testIdPrefix:`g7le-error-seq-${e}`})}):f&&c?i.jsx(Ns,{raw:c,recipe:f,values:g,t:r,pools:s,onChange:_=>a(_),testIdPrefix:`g7le-error-action-${e}-edit`}):null,l&&c?i.jsx("pre",{"data-testid":`g7le-error-code-view-${e}`,style:eC,children:JSON.stringify(t,null,2)}):null]})}const fu={display:"flex",flexDirection:"column",gap:12,minWidth:0},gu={margin:0,fontSize:13,fontWeight:700,color:"#0f172a"},XS={margin:0,fontSize:11,color:"#94a3b8"},JS={margin:0,fontSize:12,color:"#b45309",background:"#fef3c7",padding:"8px 10px",borderRadius:6},ZS={display:"flex",flexDirection:"column",gap:6,minWidth:0},QS={padding:"5px 8px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6},hu={flex:"0 0 auto",width:26,height:26,display:"inline-flex",alignItems:"center",justifyContent:"center",fontSize:11,border:"1px solid #e2e8f0",borderRadius:6,background:"#fff",color:"#64748b",cursor:"pointer",fontFamily:"ui-monospace, monospace"},RS={...hu,background:"#0f172a",borderColor:"#0f172a",color:"#e2e8f0"},eC={margin:"4px 0 0",padding:8,background:"#0f172a",color:"#e2e8f0",borderRadius:6,fontSize:12,overflow:"auto",fontFamily:"ui-monospace, monospace"};function Vr(e){const t=e.trim(),n=/^\{\{([\s\S]*)\}\}$/.exec(t);return n?n[1].trim():t}function tC(e){let t=e;for(;t.length>=2&&t[0]==="("&&t[t.length-1]===")";){let n=0,o=!0;for(let r=0;r!!o&&typeof o.value=="string"&&typeof o.expr=="string").map(o=>({value:o.value,label:typeof o.label=="string"?o.label:void 0,params:Array.isArray(o.params)?o.params:[],expr:Vr(o.expr),aliases:yu(o.aliases)}));const n=[];for(const[o,r]of Object.entries(e)){if(o==="comment"||o==="operators"||!r||typeof r!="object")continue;const s=r;typeof s.expr=="string"&&n.push({value:o,label:typeof s.label=="string"?s.label:void 0,params:Array.isArray(s.params)?s.params:[],expr:Vr(s.expr),aliases:yu(s.aliases)})}return n}function yu(e){if(!Array.isArray(e))return[];const t=[];for(const n of e){if(typeof n!="string")continue;const o=Vr(n);o.trim().length>0&&t.push(o)}return t}function oC(e){const t=e.params.length*1e3;return Ur(e.expr).replace(/\{\w+\}/g,"").length-t}function rC(e){return e.map((t,n)=>({op:t,idx:n,score:oC(t)})).sort((t,n)=>n.score-t.score||t.idx-n.idx).map(t=>t.op)}function iC(e,t){return e.replace(/\{(\w+)\}/g,(n,o)=>{const r=t[o];return r===void 0?n:r})}function sC(e,t){const n=t.find(o=>o.value===e.operator);return n?iC(n.expr,e.params??{}):null}function bu(e,t,n){const o=e.map(a=>sC(a,n)).filter(a=>typeof a=="string"&&a.trim().length>0);if(o.length===0)return"";if(o.length===1)return`{{ ${o[0]} }}`;const r=t==="or"?" || ":" && ";return`{{ ${o.map(a=>`(${a})`).join(r)} }}`}function mu(e,t){const n=Ur(e);for(const o of rC(t)){if(o.params.length===0){for(const r of[o.expr,...o.aliases])if(Ur(r)===n)return{operator:o.value,params:{}};continue}for(const r of[o.expr,...o.aliases]){const s=aC(o,r,n);if(s)return s}}return null}function aC(e,t,n){const o=t.trim(),r=[];let s="",a=0;for(;a=|<=|>|<|\?|:|\(|\)|!/.test(b))return null;return{operator:e.value,params:c}}function lC(e){const t=new Set,n=/(['"])\{(\w+)\}\1/g;let o;for(;(o=n.exec(e))!==null;)t.add(o[2]);return t}function dC(e,t){if(typeof e!="string"||e.trim()==="")return null;const n=Vr(e),o=mu(n,t);if(o)return{clauses:[o],combinator:"and"};const r=n.includes(") || (")?"or":(n.includes(") && ("),"and"),s=r==="or"?") || (":") && (";if(!n.startsWith("(")||!n.endsWith(")")||!n.includes(s))return null;const l=n.slice(1,-1).split(s),d=[];for(const c of l){const p=mu(c.trim(),t);if(!p)return null;d.push(p)}return{clauses:d,combinator:r}}const vu=Lt(),Mn=vu.responsiveManager,Do=vu.BREAKPOINT_PRESETS;function _u(e){const t=e.groupPrefixes;return{group:typeof e.group=="string"?e.group:void 0,apply:e.apply&&typeof e.apply=="object"?e.apply:void 0,options:Array.isArray(e.options)?e.options:void 0,scale:Array.isArray(e.scale)?e.scale:void 0,groupPrefixes:Array.isArray(t)?t.filter(n=>typeof n=="string"&&n.length>0):void 0,groupTokens:Array.isArray(e.groupTokens)?e.groupTokens.filter(n=>typeof n=="string"&&n.length>0):void 0}}function xu(e){const t={...e??{}},n=t.style&&typeof t.style=="object"&&!Array.isArray(t.style)?{...t.style}:{};return t.style=n,{props:t,style:n}}function qr(e){return typeof e!="string"?[]:e.split(/\s+/).filter(t=>t.length>0)}function wu(e,t=!1){const n=new Set,o=[],r=s=>{if(s){for(const a of s.tokens??[])n.add(a);s.tokenTemplate&&s.tokenTemplate.includes("{value}")&&o.push(s.tokenTemplate.split("{value}")[0]??"")}};r(e.apply);for(const s of e.options??[])r(s.apply);for(const s of e.groupPrefixes??[])o.push(s);for(const s of e.groupTokens??[])n.add(s);return s=>{if(t){if(!Yt(s))return!1;const a=Xs(s);return n.has(a)?!0:o.some(l=>l.length>0&&a.startsWith(l))}return Yt(s)?!1:n.has(s)?!0:o.some(a=>a.length>0&&s.startsWith(a))}}function cC(e,t,n,o,r=!1){const s=wu(t,r),a=qr(e.className).filter(p=>!s(p)),l=[];if(Array.isArray(n.tokens))l.push(...n.tokens);else if(n.tokenTemplate&&n.tokenTemplate.includes("{value}")&&o!=null){const p=String(o).split(/\s+/).filter(f=>f.length>0);for(const f of p)l.push(n.tokenTemplate.replace("{value}",f))}const d=r?l.map(Iu):l,c=[...a,...d];c.length===0?delete e.className:e.className=c.join(" ")}const qs={url:"backgroundImage",size:"backgroundSize",repeat:"backgroundRepeat",position:"backgroundPosition"};function uC(e){return!e||typeof e!="object"||Array.isArray(e)?!1:Object.keys(qs).some(t=>t in e)}function pC(e){return/^url\(/i.test(e.trim())?e:`url(${e})`}function ku(e){const t=/^url\(\s*(['"]?)(.*?)\1\s*\)$/i.exec(e.trim());return t?t[2]:e}function fC(e){return e.includes("backgroundImage")}function Gs(e,t){if(e!=null){if(typeof e=="object"){const n=e[t];return typeof n=="string"||typeof n=="number"?n:void 0}return e}}function gC(e){const t=e.backgroundImage,n=Gs(e.backgroundSize,"size"),o=Gs(e.backgroundRepeat,"repeat"),r=Gs(e.backgroundPosition,"position");let s;if(typeof t=="string"?s=ku(t):t&&typeof t=="object"?s=t.url:s=void 0,s===void 0&&n===void 0&&o===void 0&&r===void 0)return;const a={};return s!==void 0&&(a.url=typeof s=="string"?ku(s):s),n!==void 0&&(a.size=n),o!==void 0&&(a.repeat=o),r!==void 0&&(a.position=r),a}function hC(e,t,n){const o=(r,s)=>{s==null||s===""?delete e[r]:e[r]=s};if(Array.isArray(t.props)){if(!t.values&&uC(n)){for(const r of t.props){const s=Object.keys(qs).find(d=>qs[d]===r);let a=s?n[s]:void 0;a!==null&&typeof a=="object"&&(a=void 0);const l=r==="backgroundImage"&&typeof a=="string"&&a!==""?pC(a):a;o(r,l)}return}for(const r of t.props){const s=t.values&&r in t.values?t.values[r]:n;o(r,s)}return}typeof t.prop=="string"&&o(t.prop,t.value!==void 0?t.value:n)}function yC(e,t,n){if(typeof t.varName!="string")return;const o=t.value!==void 0?t.value:n;o==null||o===""?delete e[t.varName]:e[t.varName]=o}function bC(e,t,n){if(typeof t.propKey!="string")return;const o=t.value!==void 0?t.value:n;o==null||o===""?delete e[t.propKey]:e[t.propKey]=o}function fn(e,t,n,o=Wt){const r=_u(t),s=o.colorScheme==="dark";let a,l=!1;if(r.options){const b=r.options.find(g=>g.value===n);l=b!==void 0,a=b?.apply??r.apply}else a=r.apply;const d=n==null||n===""||l&&a===void 0,p=(d?r.apply:a)?.type==="classToken"||(r.options?.some(b=>b.apply?.type==="classToken")??!1);if(s&&!p)return e;const f=o.breakpoint!=="base"&&p?(()=>{const b=e.props??{};return typeof b.className=="string"?b.className:void 0})():void 0;return Eu(e,o,(b,g)=>{if(f!==void 0&&typeof b.className!="string"&&(b.className=f),d){const m=wu(r,s),h=qr(b.className).filter(v=>!m(v));if(h.length===0?delete b.className:b.className=h.join(" "),!s&&r.apply){const v=r.apply;if(v.type==="styleProp")for(const y of v.props??(v.prop?[v.prop]:[]))delete g[y];else v.type==="cssVar"&&v.varName?delete g[v.varName]:v.type==="propValue"&&v.propKey&&delete b[v.propKey]}}else if(a)switch(a.type){case"classToken":cC(b,r,a,n,s);break;case"styleProp":hC(g,a,n);break;case"cssVar":yC(g,a,n);break;case"propValue":bC(b,a,n);break}})}function Gr(e,t){const n=qr(e);return t?n.filter(Yt).map(Xs):n.filter(o=>!Yt(o))}function mC(e,t,n,o){const r=new Set(t.filter(l=>typeof l=="string"&&l.length>0)),s=l=>o?Yt(l)?r.has(Xs(l)):!1:Yt(l)?!1:r.has(l),a=qr(e).filter(l=>!s(l));return n&&n.length>0&&a.push(o?Iu(n):n),a.join(" ").trim()}function Su(e,t,n,o){if(e.options&&e.options.some(s=>s.apply)){const s=Gr(t.className,o),a=[];for(const l of e.options)l.apply&&_C(l.apply,s,n,t)&&a.push(l.value);if(a.length>0)return{value:a[0],matched:!0,conflict:a.length>1?!0:void 0};if(!e.apply)return{value:void 0,matched:!1}}const r=e.apply;if(!r)return{value:void 0,matched:!1};switch(r.type){case"styleProp":{if(!r.values&&Array.isArray(r.props)&&fC(r.props)){const l=gC(n);return l===void 0?{value:void 0,matched:!1}:{value:l,matched:!0}}const s=r.prop??r.props?.[0];if(!s)return{value:void 0,matched:!1};const a=n[s];return a===void 0?{value:void 0,matched:!1}:{value:a,matched:!0}}case"cssVar":{if(!r.varName)return{value:void 0,matched:!1};const s=n[r.varName];return s===void 0?{value:void 0,matched:!1}:{value:s,matched:!0}}case"propValue":{if(!r.propKey)return{value:void 0,matched:!1};const s=t[r.propKey];return s===void 0?{value:void 0,matched:!1}:{value:s,matched:!0}}case"classToken":{const s=Gr(t.className,o);if(r.tokenTemplate&&r.tokenTemplate.includes("{value}")){const[a,l]=r.tokenTemplate.split("{value}");if((a??"")===""&&(l??"")===""&&e.groupPrefixes?.length){const d=s.filter(c=>e.groupPrefixes.some(p=>c.startsWith(p)));return d.length>0?{value:d.join(" "),matched:!0}:{value:void 0,matched:!1}}for(const d of s)if(d.startsWith(a)&&d.endsWith(l??""))return{value:d.slice(a.length,l?d.length-l.length:void 0),matched:!0}}return Array.isArray(r.tokens)&&r.tokens.every(a=>s.includes(a))?{value:!0,matched:!0}:{value:void 0,matched:!1}}default:return{value:void 0,matched:!1}}}function Cu(e){return e.style&&typeof e.style=="object"&&!Array.isArray(e.style)?e.style:{}}function zo(e,t,n=Wt){const o=_u(t),r=n.colorScheme==="dark";if(r&&!Ys(o.apply)&&!o.options?.some(m=>Ys(m.apply)))return{value:void 0,matched:!1,darkReadonly:!0};const s=CC(e,n),a=Cu(s),l=Su(o,s,a,r);if(n.breakpoint==="base")return l;const d=l.matched?l.value:void 0,c=e.props??{},p=Cu(c),f=Su(o,c,p,r),b=l.matched&&l.value!==void 0,g=b&&!vC(l.value,f.value);return{value:b?l.value:f.value,matched:g?l.matched:f.matched,conflict:g?l.conflict:f.conflict,scopedValue:g?l.value:void 0,baseFallback:g?void 0:f.value,scopedRawValue:d}}function vC(e,t){if(e===t)return!0;if(e===null||t===null||typeof e!="object"||typeof t!="object")return!1;try{return JSON.stringify(e)===JSON.stringify(t)}catch{return!1}}function _C(e,t,n,o){switch(e.type){case"classToken":return Array.isArray(e.tokens)&&e.tokens.length>0?e.tokens.every(r=>t.includes(r)):!1;case"styleProp":{const r=e.prop??e.props?.[0];if(!r)return!1;const s=e.value;return s!==void 0?n[r]===s:n[r]!==void 0}case"cssVar":return e.varName?n[e.varName]!==void 0:!1;case"propValue":return e.propKey?e.value!==void 0?o[e.propKey]===e.value:o[e.propKey]!==void 0:!1;default:return!1}}const Wt={colorScheme:"base",breakpoint:"base"},No="dark:";function xC(e){return e==="base"?!0:Mn.parseRange(e)!==null}function $u(e){return e in Do}function Yr(e,t,n){if(e==="custom"){if(typeof n=="number"){const o=t.responsive;if(o&&typeof o=="object"){const r=Mn.getMatchingKey(o,n);if(r)return r}}return"base"}return e}function wC(e){return!!e&&typeof e=="object"&&Array.isArray(e.children)}function kC(e,t,n){let o,r,s;if(e!=="custom"&&e in Do){o=e;const l=Do[e];r=l.min,s=l.max}else if(e!=="custom"){const l=Mn.parseRange(e);if(l)o=e,r=l.min,s=l.max;else{const d=typeof n=="number"?n:Mn.getWidth();o=`${d}-${d}`,r=d,s=d}}else{const l=typeof n=="number"?n:Mn.getWidth();r=l,s=l,o=`${l}-${l}`}const a=t.responsive;if(a&&typeof a=="object"){let l=null,d=1/0;for(const c of Object.keys(a)){if(!wC(a[c]))continue;const p=Mn.parseRange(c);if(p){if(p.min===r&&p.max===s)return{mode:"merge",key:c};if(p.min<=r&&p.max>=s){const f=p.max-p.min;ft.length>0):[]}function IC(e,t){if(t.colorScheme==="dark")return Eu(e,t,l=>{const d=Xr(l.className).filter(c=>!Yt(c));d.length===0?delete l.className:l.className=d.join(" ")});if(t.breakpoint==="base")return e;const o=t.breakpoint;if(!e.responsive?.[o])return e;const r={...e.responsive},s={...r[o]};delete s.props,Object.keys(s).length===0?delete r[o]:r[o]=s;const a={...e};return Object.keys(r).length===0?delete a.responsive:a.responsive=r,a}function Js(e,t){if(t.colorScheme==="dark"){const c=t.breakpoint==="base"?e.props:e.responsive?.[t.breakpoint]?.props;return Xr(c?.className).some(Yt)}if(t.breakpoint==="base")return!1;const o=e.responsive?.[t.breakpoint];if(!o)return!1;const r=o.props;if(Object.keys(o).filter(c=>c!=="props").length>0)return!0;if(!r)return!1;if(Object.keys(r).filter(c=>c!=="className").length>0)return!0;const l=new Set(Xr(e.props?.className).filter(c=>!Yt(c))),d=Xr(r.className).filter(c=>!Yt(c));return d.length!==l.size?!0:d.some(c=>!l.has(c))}function Au(e,t,n=""){return typeof e!="string"?n:e.startsWith("$t:")?t(e.slice(3)):e}function Tu(e,t,n){if(n.breakpoint!=="base")return EC(e,n,t.trim());const o={...e};if(o.props&&typeof o.props=="object"&&"if"in o.props){const r={...o.props};delete r.if,o.props=r}return t.trim()===""?delete o.if:o.if=t,o}function Du({node:e,spec:t,t:n,onPatchNode:o,dataSourceCandidates:r,stateKeyCandidates:s,scope:a=Wt}){const l=u.useMemo(()=>nC(t?.conditionRecipes),[t]),d=a.breakpoint!=="base"?$C(e,a):e.if??e.props?.if,c=u.useMemo(()=>dC(d,l),[d,l]),p=c?.clauses??[],f=c?.combinator??"and",b=typeof d=="string"&&d.trim()!==""&&c===null;if(l.length===0)return i.jsx("div",{"data-testid":"g7le-condition-no-recipes",style:AC,children:n("layout_editor.visibility.no_recipes")});const g=(w,k)=>{o(Tu(e,bu(w,k,l),a))},m=(w,k)=>{g(p.map((x,$)=>$===w?k:x),f)},h=w=>{g(p.filter((k,x)=>x!==w),f)},v=w=>{const k=l[0];g([...p,{operator:k.value,params:{}}],w)},y=w=>{if(w==="datasource-picker")return r;if(w==="state-key-picker")return s};if(b)return i.jsxs("div",{className:"g7le-condition-advanced","data-testid":"g7le-condition-advanced",style:{display:"flex",flexDirection:"column",gap:8},children:[i.jsx("div",{style:BC,children:n("layout_editor.visibility.advanced_preserved")}),i.jsx("code",{"data-testid":"g7le-condition-advanced-expr",style:Nu,children:String(d)}),i.jsx("button",{type:"button","data-testid":"g7le-condition-clear-advanced",onClick:()=>o(Tu(e,"",a)),style:WC,children:n("layout_editor.visibility.clear")})]});const _=bu(p,f,l);return i.jsxs("div",{className:"g7le-condition-builder","data-testid":"g7le-condition-builder",style:{display:"flex",flexDirection:"column",gap:10},children:[i.jsx("div",{style:TC,children:n("layout_editor.visibility.show_when")}),p.map((w,k)=>{const x=l.find($=>$.value===w.operator)??l[0];return i.jsxs("div",{className:"g7le-condition-clause","data-testid":`g7le-condition-clause-${k}`,style:DC,children:[i.jsxs("div",{style:{display:"flex",gap:6,alignItems:"center"},children:[k>0&&i.jsx("span",{"data-testid":`g7le-condition-combinator-${k}`,style:NC,children:n(f==="or"?"layout_editor.visibility.or":"layout_editor.visibility.and")}),i.jsx("select",{"data-testid":`g7le-condition-select-${k}`,value:w.operator,onChange:$=>m(k,{operator:$.target.value,params:{}}),style:zC,children:l.map($=>i.jsx("option",{value:$.value,children:Au($.label,n,$.value)},$.value))}),i.jsx("button",{type:"button","data-testid":`g7le-condition-remove-${k}`,"aria-label":n("layout_editor.visibility.remove"),onClick:()=>h(k),style:MC,children:"✕"})]}),x.params.map($=>{const S=Tn($.widget);return i.jsxs("div",{className:"g7le-condition-param","data-testid":`g7le-condition-param-${k}-${$.key}`,style:LC,children:[i.jsx("span",{style:PC,children:Au($.label,n,$.key)}),i.jsx("div",{style:{flex:1},children:S?i.jsx(S,{control:{...$},value:w.params[$.key],onChange:C=>m(k,{operator:w.operator,params:{...w.params,[$.key]:C===void 0?"":String(C)}}),t:n,candidates:y($.widget)}):i.jsx("span",{style:{fontSize:11,color:"#94a3b8"},children:n("layout_editor.property_modal.unsupported_widget")})})]},$.key)})]},k)}),i.jsxs("div",{style:{display:"flex",gap:6},children:[i.jsxs("button",{type:"button","data-testid":"g7le-condition-add-and",onClick:()=>v("and"),style:zu,children:["+ ",n("layout_editor.visibility.and")]}),i.jsxs("button",{type:"button","data-testid":"g7le-condition-add-or",onClick:()=>v("or"),style:zu,children:["+ ",n("layout_editor.visibility.or")]})]}),_!==""&&i.jsxs("div",{"data-testid":"g7le-condition-preview",style:OC,children:[i.jsx("span",{style:{fontSize:11,color:"#64748b"},children:n("layout_editor.visibility.preview")}),i.jsx("code",{style:Nu,children:_})]})]})}const AC={fontSize:12,color:"#94a3b8",padding:"16px 0",textAlign:"center"},TC={fontSize:12,fontWeight:600,color:"#0f172a"},DC={border:"1px solid #e2e8f0",borderRadius:8,padding:10,display:"flex",flexDirection:"column",gap:8,background:"#f8fafc"},zC={flex:1,padding:"5px 8px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,background:"#fff"},NC={fontSize:11,fontWeight:700,color:"#2563eb",background:"#dbeafe",padding:"2px 8px",borderRadius:999},MC={border:"none",background:"transparent",color:"#94a3b8",cursor:"pointer",fontSize:12},zu={padding:"5px 12px",fontSize:12,border:"1px dashed #94a3b8",borderRadius:6,background:"#fff",color:"#475569",cursor:"pointer"},LC={display:"flex",alignItems:"center",gap:8},PC={fontSize:11,color:"#64748b",minWidth:64},OC={display:"flex",flexDirection:"column",gap:4,padding:10,background:"#f1f5f9",borderRadius:6},Nu={fontSize:11,color:"#0f172a",fontFamily:"monospace",wordBreak:"break-all"},BC={fontSize:11,color:"#92400e",background:"#fef3c7",padding:"6px 8px",borderRadius:6},WC={padding:"5px 12px",fontSize:12,border:"1px solid #fecaca",borderRadius:6,background:"#fff",color:"#dc2626",cursor:"pointer",alignSelf:"flex-start"};function jC(e){return typeof e=="string"?e:e===void 0?"":JSON.stringify(e)}function HC(e){const t=e.trim();if(t==="")return"";if(/^-?\d+(\.\d+)?$/.test(t)||t==="true"||t==="false"||t==="null")try{return JSON.parse(t)}catch{return e}return e}function Mu(e){return!e||typeof e!="object"||Array.isArray(e)?[]:Object.entries(e).map(([t,n])=>({key:t,value:jC(n)}))}function Zs(e){const t={};for(const n of e){const o=n.key.trim();o!==""&&(t[o]=HC(n.value))}return t}function Lu(e){return!e||typeof e!="object"||Array.isArray(e)?!1:Object.values(e).some(t=>t!==null&&typeof t=="object")}function FC(e){const t={};return typeof e.if=="string"&&e.if.trim().length>0&&(t.if=e.if),t}function KC(e,t){const n={...t},o=e.if;return typeof o=="string"&&o.trim().length>0?n.if=o:delete n.if,n}const VC=["api","static","route_params","query_params","websocket"],UC=["GET","POST","PUT","PATCH","DELETE"],qC=["none","optional","required"],GC=["progressive","blocking","background"],YC=["application/json","multipart/form-data"],XC=["public","private","presence"];function JC(e){if(!e||typeof e!="object")return{own:[],inherited:[]};const t=e.__editor,n=t&&typeof t=="object"&&!Array.isArray(t)?t.original:void 0,o=n&&typeof n=="object"&&!Array.isArray(n)?n.data_sources:e.data_sources,r=Array.isArray(o)?o.filter(c=>c&&typeof c=="object"):[],s=new Set(r.map(c=>typeof c.id=="string"?c.id:"").filter(Boolean)),a=e.data_sources,d=(Array.isArray(a)?a.filter(c=>c&&typeof c=="object"):[]).filter(c=>typeof c.id=="string"&&!s.has(c.id));return{own:r,inherited:d}}function ZC(e,t){const n=(o,r="")=>typeof o=="string"?o:r;return{index:t,id:n(e.id),type:n(e.type,"api"),label_key:n(e.label_key),endpoint:n(e.endpoint),method:n(e.method,"GET"),auth_mode:n(e.auth_mode,"none"),loading_strategy:n(e.loading_strategy,"progressive"),auto_fetch:e.auto_fetch!==!1,paramRows:Mu(e.params),paramsRaw:Lu(e.params),paramsRawValue:e.params&&typeof e.params=="object"&&!Array.isArray(e.params)?e.params:{},paramsRawValid:!0,fallbackEnabled:e.fallback!==void 0,fallbackValue:e.fallback,if:n(e.if),contentType:n(e.contentType),refetchOnMount:e.refetchOnMount===!0,onSuccess:Array.isArray(e.onSuccess)?e.onSuccess.filter(o=>!!o&&typeof o=="object"):[],onError:Array.isArray(e.onError)?e.onError.filter(o=>!!o&&typeof o=="object"):[],errorHandling:e.errorHandling&&typeof e.errorHandling=="object"&&!Array.isArray(e.errorHandling)?e.errorHandling:{},channel:n(e.channel),event:n(e.event),channel_type:n(e.channel_type,"public"),target_source:n(e.target_source),onReceive:Array.isArray(e.onReceive)?e.onReceive.filter(o=>!!o&&typeof o=="object"):[]}}function QC(){return{index:-1,id:"",type:"api",label_key:"",endpoint:"",method:"GET",auth_mode:"none",loading_strategy:"progressive",auto_fetch:!0,paramRows:[],paramsRaw:!1,paramsRawValue:{},paramsRawValid:!0,fallbackEnabled:!1,fallbackValue:void 0,if:"",contentType:"",refetchOnMount:!1,onSuccess:[],onError:[],errorHandling:{},channel:"",event:"",channel_type:"public",target_source:"",onReceive:[]}}function RC(e,t,n,o){const r=e.id.trim();if(!r)return{error:o("layout_editor.data_sources.error.id_required")};if(!/^[A-Za-z_][\w]*$/.test(r))return{error:o("layout_editor.data_sources.error.id_invalid")};if(n.has(r))return{error:o("layout_editor.data_sources.error.id_duplicate",{id:r})};const s=t?{...t}:{};if(s.id=r,s.type=e.type,e.label_key.trim()?s.label_key=e.label_key.trim():delete s.label_key,e.type==="api"||e.type==="websocket"?(e.endpoint.trim()?s.endpoint=e.endpoint.trim():delete s.endpoint,s.method=e.method,s.auth_mode=e.auth_mode,s.loading_strategy=e.loading_strategy):(delete s.endpoint,delete s.method),s.auto_fetch=e.auto_fetch,e.paramsRaw){if(!e.paramsRawValid)return{error:o("layout_editor.data_sources.error.params_json")};const a=e.paramsRawValue;a&&typeof a=="object"&&Object.keys(a).length>0?s.params=a:delete s.params}else{const a=Zs(e.paramRows);Object.keys(a).length>0?s.params=a:delete s.params}return e.fallbackEnabled?s.fallback=e.fallbackValue===void 0?{}:e.fallbackValue:delete s.fallback,e.if.trim()?s.if=e.if.trim():delete s.if,e.type==="api"&&e.contentType?s.contentType=e.contentType:delete s.contentType,e.refetchOnMount?s.refetchOnMount=!0:delete s.refetchOnMount,e.onSuccess.length>0?s.onSuccess=e.onSuccess:delete s.onSuccess,e.onError.length>0?s.onError=e.onError:delete s.onError,Object.keys(e.errorHandling).length>0?s.errorHandling=e.errorHandling:delete s.errorHandling,e.type==="websocket"?(e.channel?s.channel=e.channel:delete s.channel,e.event?s.event=e.event:delete s.event,s.channel_type=e.channel_type,e.target_source?s.target_source=e.target_source:delete s.target_source,e.onReceive.length>0?s.onReceive=e.onReceive:delete s.onReceive):(delete s.channel,delete s.event,delete s.channel_type,delete s.target_source,delete s.onReceive),{entry:s}}const jt={width:"100%",padding:"6px 8px",fontSize:13,border:"1px solid #cbd5e1",borderRadius:6,outline:"none",boxSizing:"border-box",fontFamily:"inherit",color:"#0f172a",background:"#ffffff"},ct={display:"block",fontSize:12,fontWeight:600,color:"#475569",marginBottom:4},ut={marginBottom:12},rn={padding:"6px 12px",fontSize:13,fontWeight:500,borderRadius:6,border:"1px solid #cbd5e1",background:"#ffffff",color:"#0f172a",cursor:"pointer"},Pu={...rn,background:"#2563eb",borderColor:"#2563eb",color:"#ffffff",fontWeight:600},Mo={marginTop:12,padding:10,border:"1px solid #e2e8f0",borderRadius:8,background:"#f8fafc"},Lo={fontSize:12,fontWeight:700,color:"#475569",marginBottom:8},e$={margin:"0 0 6px",fontSize:11,color:"#94a3b8"},Ou={marginTop:8},Bu={alignSelf:"flex-start",padding:"2px 0",fontSize:11,border:"none",background:"none",color:"#64748b",cursor:"pointer"},Wu={margin:0,padding:8,background:"#0f172a",color:"#e2e8f0",borderRadius:6,fontSize:11,lineHeight:1.5,overflow:"auto",fontFamily:"ui-monospace, monospace"};function t$({code:e,action:t,recipes:n,pools:o,t:r,onPatch:s}){const[a,l]=u.useState(!1),d=u.useMemo(()=>ko(n),[n]),c=Array.isArray(t)?t[0]:t,p=typeof c?.handler=="string"?c.handler:"",f=d.find(_=>_.id===p)??null,b=c?Yn(c,d):null,g=b?.kind==="preset"?b.values:{},m=p==="sequence"||p==="parallel",h=["","showErrorPage","navigate","openModal","toast","setState","sequence","parallel"],v=_=>{if(_===""){s({});return}const w=d.find(k=>k.id===_);s(w?Sr(w,{}):{handler:_})},y=m&&c?Array.isArray(c.params?.actions)?c.params.actions:[]:[];return i.jsxs("div",{"data-testid":`g7le-ds-errorhandling-${e}`,style:{display:"flex",flexDirection:"column",gap:6},children:[i.jsxs("div",{style:{display:"flex",alignItems:"center",gap:6},children:[i.jsx("select",{"data-testid":`g7le-ds-eh-handler-${e}`,style:{...jt,flex:1},value:p,onChange:_=>v(_.target.value),children:h.map(_=>i.jsx("option",{value:_,children:r(_===""?"layout_editor.error.handler_none":`layout_editor.error.handler_${_}`)},_))}),p!==""?i.jsx("button",{type:"button","data-testid":`g7le-ds-eh-code-${e}`,onClick:()=>l(_=>!_),"aria-label":r("layout_editor.action_list.view_code"),title:r("layout_editor.action_list.view_code"),style:a?n$:ju,children:""}):null]}),m?i.jsx("div",{"data-testid":`g7le-ds-eh-actions-${e}`,children:i.jsx(Gt,{actions:y,onChange:_=>s({handler:p,params:{actions:_}}),t:r,recipes:n,candidatePools:o,chipContext:"error",testIdPrefix:`g7le-ds-eh-seq-${e}`})}):f&&c?i.jsx(Ns,{raw:c,recipe:f,values:g,t:r,pools:o,onChange:_=>s(_),testIdPrefix:`g7le-ds-eh-action-${e}-edit`}):null,a&&c?i.jsx("pre",{"data-testid":`g7le-ds-eh-code-view-${e}`,style:o$,children:JSON.stringify(t,null,2)}):null]})}const ju={flex:"0 0 auto",width:26,height:26,display:"inline-flex",alignItems:"center",justifyContent:"center",fontSize:11,border:"1px solid #e2e8f0",borderRadius:6,background:"#fff",color:"#64748b",cursor:"pointer",fontFamily:"ui-monospace, monospace"},n$={...ju,background:"#0f172a",borderColor:"#0f172a",color:"#e2e8f0"},o$={margin:"4px 0 0",padding:8,background:"#0f172a",color:"#e2e8f0",borderRadius:6,fontSize:12,overflow:"auto",fontFamily:"ui-monospace, monospace"};function r$({raw:e,onChange:t,t:n,resolveLabel:o,onClose:r,actionRecipes:s,errorRecipes:a,conditionSpec:l,bindingCandidates:d,pageCandidates:c,dataSourceCandidates:p,stateKeyCandidates:f}){const b=o??n,g=u.useMemo(()=>JC(e),[e]),m=g.inherited,[h,v]=u.useState(g.own),[y,_]=u.useState(null),[w,k]=u.useState(null),[x,$]=u.useState(!1),[S,C]=u.useState(!1),[T,E]=u.useState(()=>new Set),A=u.useCallback(I=>{E(L=>{const F=new Set(L);return F.has(I)?F.delete(I):F.add(I),F})},[]),N=u.useMemo(()=>({pageCandidates:c,dataSourceCandidates:p,stateKeyCandidates:f,bindingCandidates:d}),[c,p,f,d]),D=u.useCallback(I=>{v(I),t([...m,...I],I)},[m,t]),B=u.useCallback(()=>{k(null),_(QC())},[]),M=u.useCallback((I,L)=>{k(null),_(ZC(I,L))},[]),O=u.useCallback(I=>{const L=h.filter((F,Y)=>Y!==I);D(L)},[h,D]),z=u.useCallback(()=>{_(null),k(null)},[]),H=u.useCallback(()=>{if(!y)return;const I=new Set;h.forEach((K,X)=>{X!==y.index&&typeof K.id=="string"&&I.add(K.id)}),m.forEach(K=>{typeof K.id=="string"&&I.add(K.id)});const L=y.index>=0?h[y.index]??null:null,F=RC(y,L,I,n);if("error"in F){k(F.error);return}const Y=y.index>=0?h.map((K,X)=>X===y.index?F.entry:K):[...h,F.entry];D(Y),_(null),k(null)},[y,h,m,D,n]),P=y?.type==="api"||y?.type==="websocket";return i.jsxs("div",{className:"g7le-data-sources","data-testid":"g7le-data-sources-panel",style:{flex:"1 1 auto",minHeight:0,display:"flex",flexDirection:"column",fontSize:13,color:"#0f172a",boxSizing:"border-box"},children:[i.jsxs("div",{className:"g7le-data-sources__header",style:{flex:"0 0 auto",padding:"16px 16px 12px",borderBottom:"1px solid #e2e8f0"},children:[i.jsxs("div",{style:{display:"flex",alignItems:"center",justifyContent:"space-between",marginBottom:8},children:[i.jsx("h2",{style:{margin:0,fontSize:16,fontWeight:700},children:n("layout_editor.data_sources.title")}),i.jsx("button",{type:"button","data-testid":"g7le-data-sources-close",onClick:r,"aria-label":n("layout_editor.data_sources.close"),style:{...rn,padding:"4px 10px"},children:"✕"})]}),i.jsx("p",{style:{margin:0,fontSize:12,color:"#64748b",lineHeight:1.5},children:n("layout_editor.data_sources.description")})]}),i.jsxs("div",{className:"g7le-data-sources__body",style:{flex:"1 1 auto",minHeight:0,overflowY:"auto",padding:16},children:[y&&i.jsxs("div",{className:"g7le-data-sources__form","data-testid":"g7le-data-sources-form",children:[i.jsx("div",{style:{fontSize:13,fontWeight:700,marginBottom:12},children:y.index>=0?n("layout_editor.data_sources.form.edit_title"):n("layout_editor.data_sources.form.add_title")}),i.jsxs("div",{style:ut,children:[i.jsx("label",{style:ct,htmlFor:"g7le-ds-id",children:n("layout_editor.data_sources.field.id")}),i.jsx("input",{id:"g7le-ds-id","data-testid":"g7le-data-sources-field-id",style:jt,value:y.id,onChange:I=>_({...y,id:I.target.value}),placeholder:"products"})]}),i.jsxs("div",{style:ut,children:[i.jsx("label",{style:ct,htmlFor:"g7le-ds-label",children:n("layout_editor.data_sources.field.label_key")}),i.jsx(_t,{value:y.label_key,onChange:I=>_({...y,label_key:I??""}),t:n,placeholder:n("layout_editor.data_sources.field.label_key_placeholder"),testidPrefix:"g7le-data-sources-field-label",candidates:d,enableExpressionTree:!0,expressionTreeCollapsible:!0}),i.jsx("div",{style:{fontSize:11,color:"#94a3b8",marginTop:4},children:n("layout_editor.data_sources.field.label_key_hint")})]}),i.jsxs("div",{style:ut,children:[i.jsx("label",{style:ct,htmlFor:"g7le-ds-type",children:n("layout_editor.data_sources.field.type")}),i.jsx("select",{id:"g7le-ds-type","data-testid":"g7le-data-sources-field-type",style:jt,value:y.type,onChange:I=>_({...y,type:I.target.value}),children:VC.map(I=>i.jsx("option",{value:I,children:n(`layout_editor.data_sources.ds_type.${I}`)},I))})]}),P&&i.jsxs(i.Fragment,{children:[i.jsxs("div",{style:ut,children:[i.jsx("label",{style:ct,children:n("layout_editor.data_sources.field.endpoint")}),i.jsx("div",{"data-testid":"g7le-data-sources-field-endpoint",children:i.jsx(Ke,{value:y.endpoint,onChange:I=>_({...y,endpoint:I}),t:n,candidates:d,placeholder:"/api/shop/products/{{route.id}}",testidPrefix:"g7le-ds-endpoint"})})]}),i.jsxs("div",{style:{display:"flex",gap:12},children:[i.jsxs("div",{style:{...ut,flex:1},children:[i.jsx("label",{style:ct,htmlFor:"g7le-ds-method",children:n("layout_editor.data_sources.field.method")}),i.jsx("select",{id:"g7le-ds-method","data-testid":"g7le-data-sources-field-method",style:jt,value:y.method,onChange:I=>_({...y,method:I.target.value}),children:UC.map(I=>i.jsx("option",{value:I,children:I},I))})]}),i.jsxs("div",{style:{...ut,flex:1},children:[i.jsx("label",{style:ct,htmlFor:"g7le-ds-auth",children:n("layout_editor.data_sources.field.auth_mode")}),i.jsx("select",{id:"g7le-ds-auth","data-testid":"g7le-data-sources-field-auth",style:jt,value:y.auth_mode,onChange:I=>_({...y,auth_mode:I.target.value}),children:qC.map(I=>i.jsx("option",{value:I,children:n(`layout_editor.data_sources.auth_mode.${I}`)},I))})]})]}),i.jsxs("div",{style:ut,children:[i.jsx("label",{style:ct,htmlFor:"g7le-ds-loading",children:n("layout_editor.data_sources.field.loading_strategy")}),i.jsx("select",{id:"g7le-ds-loading","data-testid":"g7le-data-sources-field-loading",style:jt,value:y.loading_strategy,onChange:I=>_({...y,loading_strategy:I.target.value}),children:GC.map(I=>i.jsx("option",{value:I,children:n(`layout_editor.data_sources.loading_strategy.${I}`)},I))})]}),y.type==="api"&&i.jsxs("div",{style:ut,children:[i.jsx("label",{style:ct,htmlFor:"g7le-ds-contenttype",children:n("layout_editor.data_sources.field.content_type")}),i.jsx("select",{id:"g7le-ds-contenttype","data-testid":"g7le-ds-contenttype",style:jt,value:y.contentType||"application/json",onChange:I=>_({...y,contentType:I.target.value}),children:YC.map(I=>i.jsx("option",{value:I,children:I},I))})]})]}),i.jsx("div",{style:ut,children:i.jsxs("label",{style:{...ct,display:"flex",alignItems:"center",gap:8,cursor:"pointer"},children:[i.jsx("input",{type:"checkbox","data-testid":"g7le-data-sources-field-autofetch",checked:y.auto_fetch,onChange:I=>_({...y,auto_fetch:I.target.checked})}),n("layout_editor.data_sources.field.auto_fetch")]})}),i.jsx("div",{style:ut,children:i.jsxs("label",{style:{...ct,display:"flex",alignItems:"center",gap:8,cursor:"pointer"},children:[i.jsx("input",{type:"checkbox","data-testid":"g7le-ds-refetch",checked:y.refetchOnMount,onChange:I=>_({...y,refetchOnMount:I.target.checked})}),n("layout_editor.data_sources.field.refetch_on_mount")]})}),i.jsxs("div",{style:ut,children:[i.jsxs("div",{style:{display:"flex",alignItems:"center",justifyContent:"space-between"},children:[i.jsx("label",{style:ct,htmlFor:"g7le-ds-params",children:n("layout_editor.data_sources.field.params")}),i.jsx("button",{type:"button","data-testid":"g7le-ds-params-mode-toggle",onClick:()=>{if(y.paramsRaw){if(y.paramsRawValid){const I=y.paramsRawValue;I&&typeof I=="object"&&!Array.isArray(I)&&!Lu(I)&&_({...y,paramsRaw:!1,paramRows:Mu(I)})}}else{const I=Zs(y.paramRows);_({...y,paramsRaw:!0,paramsRawValue:I,paramsRawValid:!0})}},style:{...rn,padding:"2px 8px",fontSize:11},children:y.paramsRaw?n("layout_editor.data_sources.field.params_mode_block"):n("layout_editor.data_sources.field.params_mode_code")})]}),y.paramsRaw?i.jsx(Hs,{id:"g7le-ds-params",value:y.paramsRawValue,shape:"object",emptyValue:{},minHeight:64,testidPrefix:"g7le-data-sources-field-params",placeholder:`{ +}`,shapeErrorKey:"layout_editor.json_block.must_be_object",validate:x=>{const $=Object.keys(x??{}).filter(C=>!Ps(C));return $.length?a("layout_editor.json_block.invalid_keys",{keys:$.join(", ")}):null}}):i.jsxs(i.Fragment,{children:[v.length===0?i.jsx("p",{"data-testid":`g7le-initstate-${e}-empty`,style:lC,children:a("layout_editor.initstate.no_values")}):v.map(x=>{const C=lk(y,r,x)==="inherited",S=e==="isolated"&&l[x];return i.jsx(FS,{scope:e,itemKey:x,value:o[x],inherited:C,isOrphan:!!S,onChangeValue:D=>_(x,D),onRemove:()=>w(x),t:a},x)}),i.jsxs("div",{style:nC,children:[i.jsx("input",{type:"text","data-testid":`g7le-initstate-${e}-add-name`,value:d,placeholder:a("layout_editor.initstate.value_name"),onChange:x=>{c(x.target.value),b&&g(null)},onKeyDown:x=>{x.key==="Enter"&&(x.preventDefault(),k())},"aria-invalid":b?!0:void 0,style:{...oC,...b?rC:null}}),i.jsx("select",{"data-testid":`g7le-initstate-${e}-add-kind`,value:p,onChange:x=>f(x.target.value),"aria-label":a("layout_editor.initstate.value_kind"),style:sC,children:jS.map(x=>i.jsx("option",{value:x.kind,children:a(x.labelKey)},x.kind))}),i.jsxs("button",{type:"button","data-testid":`g7le-initstate-${e}-add`,onClick:k,style:aC,children:["+ ",a("layout_editor.initstate.add_value")]})]}),b?i.jsx("p",{"data-testid":`g7le-initstate-${e}-add-error`,style:iC,children:b}):null]})]})}function FS({scope:e,itemKey:t,value:n,inherited:o,isOrphan:r,onChangeValue:s,onRemove:a,t:l}){const[d,c]=u.useState(!1);let p;try{p=JSON.stringify(n,null,2)??String(n)}catch{p=String(n)}return i.jsxs("div",{"data-testid":`g7le-initstate-${e}-item-${t}`,style:ZS,children:[i.jsxs("div",{style:QS,children:[o?i.jsx("span",{"data-testid":`g7le-initstate-inherited-${t}`,style:RS,children:"🔗"}):null,i.jsx("code",{style:eC,children:t}),i.jsx("span",{style:{flex:1}}),i.jsx("button",{type:"button","data-testid":`g7le-initstate-code-${t}`,onClick:()=>c(f=>!f),style:Xs,"aria-label":l("layout_editor.value_tree.show_source"),title:l("layout_editor.value_tree.show_source"),children:""}),o?i.jsx("button",{type:"button","data-testid":`g7le-initstate-revert-${t}`,onClick:a,style:Xs,children:l("layout_editor.initstate.revert")}):i.jsx("button",{type:"button","data-testid":`g7le-initstate-remove-${t}`,onClick:a,style:Xs,"aria-label":l("layout_editor.init_actions.remove"),children:"✕"})]}),d?i.jsx("pre",{"data-testid":`g7le-initstate-code-block-${t}`,style:cC,children:p}):null,i.jsx(Mn,{value:n,onChange:s,t:l,path:t,scope:e}),r?i.jsxs("p",{"data-testid":`g7le-initstate-isolated-orphan-${t}`,style:tC,children:["⚠ ",l("layout_editor.initstate.isolated_orphan")]}):null]})}const KS={display:"flex",flexDirection:"column",gap:14,minWidth:0},VS={margin:0,fontSize:13,fontWeight:700,color:"#0f172a"},US={margin:0,fontSize:11,color:"#1d4ed8",background:"#eff6ff",padding:"6px 8px",borderRadius:6},qS={display:"flex",flexDirection:"column",gap:8,padding:10,border:"1px solid #e2e8f0",borderRadius:10,minWidth:0},GS={display:"flex",alignItems:"center",justifyContent:"space-between",gap:8},YS={fontSize:12,fontWeight:700,color:"#475569"},XS={margin:0,fontSize:11,color:"#94a3b8"},JS={padding:"2px 8px",fontSize:11,border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",color:"#475569",cursor:"pointer"},ZS={display:"flex",flexDirection:"column",gap:4,padding:"6px 8px",border:"1px solid #f1f5f9",borderRadius:6,minWidth:0},QS={display:"flex",alignItems:"center",gap:6},RS={fontSize:12},eC={fontSize:12,color:"#0f172a",fontFamily:"monospace"},Xs={border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",color:"#475569",cursor:"pointer",fontSize:11,padding:"2px 6px"},tC={margin:0,fontSize:11,color:"#b45309",background:"#fef3c7",padding:"4px 6px",borderRadius:6},nC={display:"flex",gap:6,alignItems:"center"},oC={padding:"4px 8px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6},rC={border:"1px solid #dc2626"},iC={margin:0,fontSize:11,color:"#dc2626"},sC={padding:"4px 8px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",color:"#0f172a"},aC={padding:"2px 10px",fontSize:11,border:"1px dashed #94a3b8",borderRadius:6,background:"#fff",color:"#475569",cursor:"pointer"},lC={margin:0,fontSize:12,color:"#94a3b8"},dC={margin:0,fontSize:11,color:"#94a3b8"},cC={margin:"2px 0",padding:"6px 8px",fontSize:11,fontFamily:"monospace",color:"#0f172a",background:"#f1f5f9",border:"1px solid #e2e8f0",borderRadius:6,whiteSpace:"pre-wrap",wordBreak:"break-all",overflowX:"auto"},uC={self:"layout_editor.error_rows.source_self",inherited:"layout_editor.error_rows.source_inherited",template:"layout_editor.error_rows.source_template",none:"layout_editor.error_rows.source_none"};function Js({value:e,onChange:t,t:n,codes:o,mode:r="local",sourceOf:s,renderActionList:a,testIdPrefix:l="g7le-error-rows"}){const d=o??Object.keys(e),c=u.useCallback((f,b)=>{t({...e,[f]:b})},[e,t]),p=u.useCallback(f=>{const b={...e};delete b[f],t(b)},[e,t]);return i.jsx("div",{className:l,"data-testid":l,children:d.map(f=>{const b=e[f],g=b!==void 0,m=r==="badge"&&s?s(f):null;return i.jsxs("div",{"data-testid":`${l}-row-${f}`,style:{border:"1px solid #e2e8f0",borderRadius:8,padding:"8px 10px",marginBottom:6},children:[i.jsxs("div",{style:{display:"flex",alignItems:"center",gap:8},children:[i.jsx("strong",{"data-testid":`${l}-code-${f}`,style:{fontSize:13},children:f==="default"?n("layout_editor.error_rows.default"):f}),m?i.jsxs("span",{"data-testid":`${l}-source-${f}`,style:{fontSize:11,padding:"2px 6px",borderRadius:4,background:"#f1f5f9",color:"#475569"},children:["〔",n(uC[m]),"〕"]}):null,g?i.jsx("button",{type:"button","data-testid":`${l}-clear-${f}`,onClick:()=>p(f),style:{marginLeft:"auto",fontSize:12},children:n("layout_editor.error_rows.clear")}):i.jsx("span",{style:{marginLeft:"auto",fontSize:12,color:"#94a3b8"},children:n("layout_editor.error_rows.no_action")})]}),m==="inherited"||m==="template"?i.jsxs("div",{"data-testid":`${l}-override-${f}`,style:{marginTop:6,display:"flex",alignItems:"flex-start",gap:8},children:[i.jsxs("p",{style:{margin:0,flex:1,fontSize:12,color:"#64748b"},children:["ⓘ ",n(m==="inherited"?"layout_editor.error_rows.override_inherited":"layout_editor.error_rows.override_template")]}),g?i.jsx("button",{type:"button","data-testid":`${l}-revert-${f}`,onClick:()=>p(f),style:{fontSize:12,padding:"2px 8px",border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",color:"#475569",cursor:"pointer",whiteSpace:"nowrap"},children:n("layout_editor.error_rows.revert")}):i.jsx("button",{type:"button","data-testid":`${l}-override-here-${f}`,onClick:()=>c(f,{}),style:{fontSize:12,padding:"2px 8px",border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",color:"#475569",cursor:"pointer",whiteSpace:"nowrap"},children:n("layout_editor.error_rows.override_here")})]}):null,f==="default"?i.jsxs("p",{"data-testid":`${l}-default-warn-${f}`,style:{margin:"6px 0 0",fontSize:12,color:"#b45309"},children:["⚠ ",n("layout_editor.error_rows.default_warning")]}):null,i.jsx("div",{style:{marginTop:6},children:a?a(f,b,h=>c(f,h)):i.jsx("pre",{"data-testid":`${l}-json-${f}`,style:{margin:0,fontSize:12,color:"#475569"},children:g?JSON.stringify(b):""})})]},f)})})}function pC(e){return[...e.filter(n=>n!=="maintenance"),"default"]}function fC({value:e,onChange:t,t:n,errorConfigCodes:o=[],ownCodes:r=[],templateCodes:s=[],recipes:a,pageCandidates:l,modalCandidates:d,dataSourceCandidates:c,stateKeyCandidates:p}){const f=u.useMemo(()=>Io(a),[a]),b=u.useMemo(()=>pC(o),[o]),g=f.length>0,m=u.useMemo(()=>Xl("error",n),[n]),h=u.useMemo(()=>({pageCandidates:l,dataSourceCandidates:c,stateKeyCandidates:p,modalCandidates:d,bindingCandidates:m}),[l,c,p,d,m]),v=u.useCallback(y=>r.includes(y)?"self":y in e?"inherited":s.includes(y)?"template":"none",[r,e,s]);return g?i.jsxs("div",{className:"g7le-error-form","data-testid":"g7le-error-form",style:mu,children:[i.jsx("p",{style:vu,children:n("layout_editor.page_settings.error.heading")}),i.jsxs("p",{style:hC,children:["ⓘ ",n("layout_editor.error.intro")]}),i.jsx(Js,{value:e,onChange:t,t:n,codes:b,mode:"badge",sourceOf:v,testIdPrefix:"g7le-error-rows",renderActionList:(y,_,w)=>i.jsx(gC,{code:y,action:_,recipes:a,normalized:f,t:n,pools:h,onPatch:w})})]}):i.jsxs("div",{className:"g7le-error-form","data-testid":"g7le-error-form",style:mu,children:[i.jsx("p",{style:vu,children:n("layout_editor.page_settings.error.heading")}),i.jsxs("p",{"data-testid":"g7le-error-degrade",style:yC,children:["ⓘ ",n("layout_editor.error.degrade")]}),i.jsx(Js,{value:e,onChange:t,t:n,codes:b,mode:"badge",sourceOf:v,testIdPrefix:"g7le-error-rows"})]})}function gC({code:e,action:t,recipes:n,normalized:o,t:r,pools:s,onPatch:a}){const[l,d]=u.useState(!1),c=Array.isArray(t)?t[0]:t,p=typeof c?.handler=="string"?c.handler:"",f=o.find(_=>_.id===p)??null,b=c?Jn(c,o):null,g=b?.kind==="preset"?b.values:{},m=p==="sequence"||p==="parallel",h=_=>{if(_===""){a({});return}const w=o.find(k=>k.id===_);w&&a(Ir(w,{}))},v=["","showErrorPage","navigate","openModal","toast","setState","sequence","parallel"],y=m&&c?Array.isArray(c.params?.actions)?c.params.actions:[]:[];return i.jsxs("div",{"data-testid":`g7le-error-action-${e}`,style:bC,children:[i.jsxs("div",{style:{display:"flex",alignItems:"center",gap:6},children:[i.jsx("select",{"data-testid":`g7le-error-handler-${e}`,value:p,onChange:_=>h(_.target.value),style:{...mC,flex:1},children:v.map(_=>i.jsx("option",{value:_,children:r(_===""?"layout_editor.error.handler_none":`layout_editor.error.handler_${_}`)},_))}),p!==""?i.jsx("button",{type:"button","data-testid":`g7le-error-code-${e}`,onClick:()=>d(_=>!_),"aria-label":r("layout_editor.action_list.view_code"),title:r("layout_editor.action_list.view_code"),style:l?vC:_u,children:""}):null]}),m?i.jsx("div",{"data-testid":`g7le-error-actions-${e}`,children:i.jsx(Yt,{actions:y,onChange:_=>a({handler:p,params:{actions:_}}),t:r,recipes:n,candidatePools:s,chipContext:"error",testIdPrefix:`g7le-error-seq-${e}`})}):f&&c?i.jsx(Bs,{raw:c,recipe:f,values:g,t:r,pools:s,onChange:_=>a(_),testIdPrefix:`g7le-error-action-${e}-edit`}):null,l&&c?i.jsx("pre",{"data-testid":`g7le-error-code-view-${e}`,style:_C,children:JSON.stringify(t,null,2)}):null]})}const mu={display:"flex",flexDirection:"column",gap:12,minWidth:0},vu={margin:0,fontSize:13,fontWeight:700,color:"#0f172a"},hC={margin:0,fontSize:11,color:"#94a3b8"},yC={margin:0,fontSize:12,color:"#b45309",background:"#fef3c7",padding:"8px 10px",borderRadius:6},bC={display:"flex",flexDirection:"column",gap:6,minWidth:0},mC={padding:"5px 8px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6},_u={flex:"0 0 auto",width:26,height:26,display:"inline-flex",alignItems:"center",justifyContent:"center",fontSize:11,border:"1px solid #e2e8f0",borderRadius:6,background:"#fff",color:"#64748b",cursor:"pointer",fontFamily:"ui-monospace, monospace"},vC={..._u,background:"#0f172a",borderColor:"#0f172a",color:"#e2e8f0"},_C={margin:"4px 0 0",padding:8,background:"#0f172a",color:"#e2e8f0",borderRadius:6,fontSize:12,overflow:"auto",fontFamily:"ui-monospace, monospace"};function Yr(e){const t=e.trim(),n=/^\{\{([\s\S]*)\}\}$/.exec(t);return n?n[1].trim():t}function xC(e){let t=e;for(;t.length>=2&&t[0]==="("&&t[t.length-1]===")";){let n=0,o=!0;for(let r=0;r!!o&&typeof o.value=="string"&&typeof o.expr=="string").map(o=>({value:o.value,label:typeof o.label=="string"?o.label:void 0,params:Array.isArray(o.params)?o.params:[],expr:Yr(o.expr),aliases:xu(o.aliases)}));const n=[];for(const[o,r]of Object.entries(e)){if(o==="comment"||o==="operators"||!r||typeof r!="object")continue;const s=r;typeof s.expr=="string"&&n.push({value:o,label:typeof s.label=="string"?s.label:void 0,params:Array.isArray(s.params)?s.params:[],expr:Yr(s.expr),aliases:xu(s.aliases)})}return n}function xu(e){if(!Array.isArray(e))return[];const t=[];for(const n of e){if(typeof n!="string")continue;const o=Yr(n);o.trim().length>0&&t.push(o)}return t}function kC(e){const t=e.params.length*1e3;return Xr(e.expr).replace(/\{\w+\}/g,"").length-t}function SC(e){return e.map((t,n)=>({op:t,idx:n,score:kC(t)})).sort((t,n)=>n.score-t.score||t.idx-n.idx).map(t=>t.op)}function CC(e,t){return e.replace(/\{(\w+)\}/g,(n,o)=>{const r=t[o];return r===void 0?n:r})}function $C(e,t){const n=t.find(o=>o.value===e.operator);return n?CC(n.expr,e.params??{}):null}function wu(e,t,n){const o=e.map(a=>$C(a,n)).filter(a=>typeof a=="string"&&a.trim().length>0);if(o.length===0)return"";if(o.length===1)return`{{ ${o[0]} }}`;const r=t==="or"?" || ":" && ";return`{{ ${o.map(a=>`(${a})`).join(r)} }}`}function ku(e,t){const n=Xr(e);for(const o of SC(t)){if(o.params.length===0){for(const r of[o.expr,...o.aliases])if(Xr(r)===n)return{operator:o.value,params:{}};continue}for(const r of[o.expr,...o.aliases]){const s=EC(o,r,n);if(s)return s}}return null}function EC(e,t,n){const o=t.trim(),r=[];let s="",a=0;for(;a=|<=|>|<|\?|:|\(|\)|!/.test(b))return null;return{operator:e.value,params:c}}function IC(e){const t=new Set,n=/(['"])\{(\w+)\}\1/g;let o;for(;(o=n.exec(e))!==null;)t.add(o[2]);return t}function AC(e,t){if(typeof e!="string"||e.trim()==="")return null;const n=Yr(e),o=ku(n,t);if(o)return{clauses:[o],combinator:"and"};const r=n.includes(") || (")?"or":(n.includes(") && ("),"and"),s=r==="or"?") || (":") && (";if(!n.startsWith("(")||!n.endsWith(")")||!n.includes(s))return null;const l=n.slice(1,-1).split(s),d=[];for(const c of l){const p=ku(c.trim(),t);if(!p)return null;d.push(p)}return{clauses:d,combinator:r}}const Su=Lt(),Pn=Su.responsiveManager,Po=Su.BREAKPOINT_PRESETS;function Cu(e){const t=e.groupPrefixes;return{group:typeof e.group=="string"?e.group:void 0,widget:typeof e.widget=="string"?e.widget:void 0,apply:e.apply&&typeof e.apply=="object"?e.apply:void 0,options:Array.isArray(e.options)?e.options:void 0,scale:Array.isArray(e.scale)?e.scale:void 0,groupPrefixes:Array.isArray(t)?t.filter(n=>typeof n=="string"&&n.length>0):void 0,groupTokens:Array.isArray(e.groupTokens)?e.groupTokens.filter(n=>typeof n=="string"&&n.length>0):void 0}}function $u(e){const t={...e??{}},n=t.style&&typeof t.style=="object"&&!Array.isArray(t.style)?{...t.style}:{};return t.style=n,{props:t,style:n}}function Jr(e){return typeof e!="string"?[]:e.split(/\s+/).filter(t=>t.length>0)}function Eu(e,t=!1){const n=new Set,o=[],r=s=>{if(s){for(const a of s.tokens??[])n.add(a);s.tokenTemplate&&s.tokenTemplate.includes("{value}")&&o.push(s.tokenTemplate.split("{value}")[0]??"")}};r(e.apply);for(const s of e.options??[])r(s.apply);for(const s of e.groupPrefixes??[])o.push(s);for(const s of e.groupTokens??[])n.add(s);return s=>{if(t){if(!Xt(s))return!1;const a=ta(s);return n.has(a)?!0:o.some(l=>l.length>0&&a.startsWith(l))}return Xt(s)?!1:n.has(s)?!0:o.some(a=>a.length>0&&s.startsWith(a))}}function TC(e,t,n,o,r=!1){const s=Eu(t,r),a=Jr(e.className).filter(p=>!s(p)),l=[];if(Array.isArray(n.tokens))l.push(...n.tokens);else if(n.tokenTemplate&&n.tokenTemplate.includes("{value}")&&o!=null){const p=String(o).split(/\s+/).filter(f=>f.length>0);for(const f of p)l.push(n.tokenTemplate.replace("{value}",f))}const d=r?l.map(Lu):l,c=[...a,...d];c.length===0?delete e.className:e.className=c.join(" ")}const Zs={url:"backgroundImage",size:"backgroundSize",repeat:"backgroundRepeat",position:"backgroundPosition"};function Iu(e){return!e||typeof e!="object"||Array.isArray(e)?!1:Object.keys(Zs).some(t=>t in e)}function Au(e){return/^url\(/i.test(e.trim())?e:`url(${e})`}function Zr(e){const t=/^url\(\s*(['"]?)(.*?)\1\s*\)$/i.exec(e.trim());return t?t[2]:e}function DC(e){return e.includes("backgroundImage")}function Qs(e,t){if(!Iu(e))return e;const n=e.url;if(typeof n!="string"||n.trim()==="")return"";const o=Zr(n);return t?Au(o):o}function Rs(e,t){if(e!=null){if(typeof e=="object"){const n=e[t];return typeof n=="string"||typeof n=="number"?n:void 0}return e}}function zC(e){const t=e.backgroundImage,n=Rs(e.backgroundSize,"size"),o=Rs(e.backgroundRepeat,"repeat"),r=Rs(e.backgroundPosition,"position");let s;if(typeof t=="string"?s=Zr(t):t&&typeof t=="object"?s=t.url:s=void 0,s===void 0&&n===void 0&&o===void 0&&r===void 0)return;const a={};return s!==void 0&&(a.url=typeof s=="string"?Zr(s):s),n!==void 0&&(a.size=n),o!==void 0&&(a.repeat=o),r!==void 0&&(a.position=r),a}const Tu=new Set(["type","name","props","children","text","if","iteration","responsive","__source","__injectedProps"]);function NC(e,t,n){const o=t.nodeKey;if(typeof o!="string"||o===""||Tu.has(o))return e;const r={...e};return n==null||n===""?delete r[o]:r[o]=n,r}function MC(e,t,n,o=!1){const r=(s,a)=>{a==null||a===""?delete e[s]:e[s]=a};if(Array.isArray(t.props)){if(!t.values&&Iu(n)){for(const s of t.props){const a=Object.keys(Zs).find(c=>Zs[c]===s);let l=a?n[a]:void 0;l!==null&&typeof l=="object"&&(l=void 0);const d=s==="backgroundImage"&&typeof l=="string"&&l!==""?Au(l):l;r(s,d)}return}for(const s of t.props){const a=t.values&&s in t.values?t.values[s]:n;r(s,a)}return}if(typeof t.prop=="string"){const s=t.value!==void 0?t.value:n;r(t.prop,o?Qs(s,!0):s)}}function LC(e,t,n,o=!1){if(typeof t.varName!="string")return;const r=t.value!==void 0?t.value:n,s=o?Qs(r,!0):r;s==null||s===""?delete e[t.varName]:e[t.varName]=s}function PC(e,t,n,o=!1){if(typeof t.propKey!="string")return;const r=t.value!==void 0?t.value:n,s=o?Qs(r,!1):r;s==null||s===""?delete e[t.propKey]:e[t.propKey]=s}function yn(e,t,n,o=jt){const r=Cu(t),s=o.colorScheme==="dark";let a,l=!1;if(r.options){const b=r.options.find(g=>g.value===n);l=b!==void 0,a=b?.apply??r.apply}else a=r.apply;const d=n==null||n===""||l&&a===void 0,c=d?r.apply:a,p=c?.type==="classToken"||(r.options?.some(b=>b.apply?.type==="classToken")??!1);if(s&&!p)return e;if(c?.type==="nodeKey")return NC(e,c,d?void 0:n);const f=o.breakpoint!=="base"&&p?(()=>{const b=e.props??{};return typeof b.className=="string"?b.className:void 0})():void 0;return Mu(e,o,(b,g)=>{if(f!==void 0&&typeof b.className!="string"&&(b.className=f),d){const m=Eu(r,s),h=Jr(b.className).filter(v=>!m(v));if(h.length===0?delete b.className:b.className=h.join(" "),!s&&r.apply){const v=r.apply;if(v.type==="styleProp")for(const y of v.props??(v.prop?[v.prop]:[]))delete g[y];else v.type==="cssVar"&&v.varName?delete g[v.varName]:v.type==="propValue"&&v.propKey&&delete b[v.propKey]}}else if(a){const m=r.widget==="image";switch(a.type){case"classToken":TC(b,r,a,n,s);break;case"styleProp":MC(g,a,n,m);break;case"cssVar":LC(g,a,n,m);break;case"propValue":PC(b,a,n,m);break}}})}function Qr(e,t){const n=Jr(e);return t?n.filter(Xt).map(ta):n.filter(o=>!Xt(o))}function OC(e,t,n,o){const r=new Set(t.filter(l=>typeof l=="string"&&l.length>0)),s=l=>o?Xt(l)?r.has(ta(l)):!1:Xt(l)?!1:r.has(l),a=Jr(e).filter(l=>!s(l));return n&&n.length>0&&a.push(o?Lu(n):n),a.join(" ").trim()}function Du(e,t,n,o){if(e.options&&e.options.some(s=>s.apply)){const s=Qr(t.className,o),a=[];for(const l of e.options)l.apply&&WC(l.apply,s,n,t)&&a.push(l.value);if(a.length>0)return{value:a[0],matched:!0,conflict:a.length>1?!0:void 0};if(!e.apply)return{value:void 0,matched:!1}}const r=e.apply;if(!r)return{value:void 0,matched:!1};switch(r.type){case"styleProp":{if(!r.values&&Array.isArray(r.props)&&DC(r.props)){const l=zC(n);return l===void 0?{value:void 0,matched:!1}:{value:l,matched:!0}}const s=r.prop??r.props?.[0];if(!s)return{value:void 0,matched:!1};const a=n[s];return a===void 0?{value:void 0,matched:!1}:{value:a,matched:!0}}case"cssVar":{if(!r.varName)return{value:void 0,matched:!1};const s=n[r.varName];return s===void 0?{value:void 0,matched:!1}:{value:s,matched:!0}}case"propValue":{if(!r.propKey)return{value:void 0,matched:!1};const s=t[r.propKey];return s===void 0?{value:void 0,matched:!1}:e.widget==="image"&&typeof s=="string"?{value:{url:Zr(s)},matched:!0}:{value:s,matched:!0}}case"classToken":{const s=Qr(t.className,o);if(r.tokenTemplate&&r.tokenTemplate.includes("{value}")){const[a,l]=r.tokenTemplate.split("{value}");if((a??"")===""&&(l??"")===""&&e.groupPrefixes?.length){const d=s.filter(c=>e.groupPrefixes.some(p=>c.startsWith(p)));return d.length>0?{value:d.join(" "),matched:!0}:{value:void 0,matched:!1}}for(const d of s)if(d.startsWith(a)&&d.endsWith(l??""))return{value:d.slice(a.length,l?d.length-l.length:void 0),matched:!0}}return Array.isArray(r.tokens)&&r.tokens.every(a=>s.includes(a))?{value:!0,matched:!0}:{value:void 0,matched:!1}}default:return{value:void 0,matched:!1}}}function zu(e){return e.style&&typeof e.style=="object"&&!Array.isArray(e.style)?e.style:{}}function Oo(e,t,n=jt){const o=Cu(t),r=n.colorScheme==="dark";if(r&&!ea(o.apply)&&!o.options?.some(m=>ea(m.apply)))return{value:void 0,matched:!1,darkReadonly:!0};if(o.apply?.type==="nodeKey"){const m=o.apply.nodeKey;if(typeof m!="string"||m===""||Tu.has(m))return{value:void 0,matched:!1};const h=e[m];return h===void 0?{value:void 0,matched:!1}:{value:h,matched:!0}}const s=VC(e,n),a=zu(s),l=Du(o,s,a,r);if(n.breakpoint==="base")return l;const d=l.matched?l.value:void 0,c=e.props??{},p=zu(c),f=Du(o,c,p,r),b=l.matched&&l.value!==void 0,g=b&&!BC(l.value,f.value);return{value:b?l.value:f.value,matched:g?l.matched:f.matched,conflict:g?l.conflict:f.conflict,scopedValue:g?l.value:void 0,baseFallback:g?void 0:f.value,scopedRawValue:d}}function BC(e,t){if(e===t)return!0;if(e===null||t===null||typeof e!="object"||typeof t!="object")return!1;try{return JSON.stringify(e)===JSON.stringify(t)}catch{return!1}}function WC(e,t,n,o){switch(e.type){case"classToken":return Array.isArray(e.tokens)&&e.tokens.length>0?e.tokens.every(r=>t.includes(r)):!1;case"styleProp":{const r=e.prop??e.props?.[0];if(!r)return!1;const s=e.value;return s!==void 0?n[r]===s:n[r]!==void 0}case"cssVar":return e.varName?n[e.varName]!==void 0:!1;case"propValue":return e.propKey?e.value!==void 0?o[e.propKey]===e.value:o[e.propKey]!==void 0:!1;default:return!1}}const jt={colorScheme:"base",breakpoint:"base"},Bo="dark:";function jC(e){return e==="base"?!0:Pn.parseRange(e)!==null}function Nu(e){return e in Po}function Rr(e,t,n){if(e==="custom"){if(typeof n=="number"){const o=t.responsive;if(o&&typeof o=="object"){const r=Pn.getMatchingKey(o,n);if(r)return r}}return"base"}return e}function HC(e){return!!e&&typeof e=="object"&&Array.isArray(e.children)}function FC(e,t,n){let o,r,s;if(e!=="custom"&&e in Po){o=e;const l=Po[e];r=l.min,s=l.max}else if(e!=="custom"){const l=Pn.parseRange(e);if(l)o=e,r=l.min,s=l.max;else{const d=typeof n=="number"?n:Pn.getWidth();o=`${d}-${d}`,r=d,s=d}}else{const l=typeof n=="number"?n:Pn.getWidth();r=l,s=l,o=`${l}-${l}`}const a=t.responsive;if(a&&typeof a=="object"){let l=null,d=1/0;for(const c of Object.keys(a)){if(!HC(a[c]))continue;const p=Pn.parseRange(c);if(p){if(p.min===r&&p.max===s)return{mode:"merge",key:c};if(p.min<=r&&p.max>=s){const f=p.max-p.min;ft.length>0):[]}function GC(e,t){if(t.colorScheme==="dark")return Mu(e,t,l=>{const d=ei(l.className).filter(c=>!Xt(c));d.length===0?delete l.className:l.className=d.join(" ")});if(t.breakpoint==="base")return e;const o=t.breakpoint;if(!e.responsive?.[o])return e;const r={...e.responsive},s={...r[o]};delete s.props,Object.keys(s).length===0?delete r[o]:r[o]=s;const a={...e};return Object.keys(r).length===0?delete a.responsive:a.responsive=r,a}function na(e,t){if(t.colorScheme==="dark"){const c=t.breakpoint==="base"?e.props:e.responsive?.[t.breakpoint]?.props;return ei(c?.className).some(Xt)}if(t.breakpoint==="base")return!1;const o=e.responsive?.[t.breakpoint];if(!o)return!1;const r=o.props;if(Object.keys(o).filter(c=>c!=="props").length>0)return!0;if(!r)return!1;if(Object.keys(r).filter(c=>c!=="className").length>0)return!0;const l=new Set(ei(e.props?.className).filter(c=>!Xt(c))),d=ei(r.className).filter(c=>!Xt(c));return d.length!==l.size?!0:d.some(c=>!l.has(c))}function Pu(e,t,n=""){return typeof e!="string"?n:e.startsWith("$t:")?t(e.slice(3)):e}function Ou(e,t,n){if(n.breakpoint!=="base")return qC(e,n,t.trim());const o={...e};if(o.props&&typeof o.props=="object"&&"if"in o.props){const r={...o.props};delete r.if,o.props=r}return t.trim()===""?delete o.if:o.if=t,o}function Bu({node:e,spec:t,t:n,onPatchNode:o,dataSourceCandidates:r,stateKeyCandidates:s,scope:a=jt}){const l=u.useMemo(()=>wC(t?.conditionRecipes),[t]),d=a.breakpoint!=="base"?UC(e,a):e.if??e.props?.if,c=u.useMemo(()=>AC(d,l),[d,l]),p=c?.clauses??[],f=c?.combinator??"and",b=typeof d=="string"&&d.trim()!==""&&c===null;if(l.length===0)return i.jsx("div",{"data-testid":"g7le-condition-no-recipes",style:YC,children:n("layout_editor.visibility.no_recipes")});const g=(w,k)=>{o(Ou(e,wu(w,k,l),a))},m=(w,k)=>{g(p.map((x,$)=>$===w?k:x),f)},h=w=>{g(p.filter((k,x)=>x!==w),f)},v=w=>{const k=l[0];g([...p,{operator:k.value,params:{}}],w)},y=w=>{if(w==="datasource-picker")return r;if(w==="state-key-picker")return s};if(b)return i.jsxs("div",{className:"g7le-condition-advanced","data-testid":"g7le-condition-advanced",style:{display:"flex",flexDirection:"column",gap:8},children:[i.jsx("div",{style:o$,children:n("layout_editor.visibility.advanced_preserved")}),i.jsx("code",{"data-testid":"g7le-condition-advanced-expr",style:ju,children:String(d)}),i.jsx("button",{type:"button","data-testid":"g7le-condition-clear-advanced",onClick:()=>o(Ou(e,"",a)),style:r$,children:n("layout_editor.visibility.clear")})]});const _=wu(p,f,l);return i.jsxs("div",{className:"g7le-condition-builder","data-testid":"g7le-condition-builder",style:{display:"flex",flexDirection:"column",gap:10},children:[i.jsx("div",{style:XC,children:n("layout_editor.visibility.show_when")}),p.map((w,k)=>{const x=l.find($=>$.value===w.operator)??l[0];return i.jsxs("div",{className:"g7le-condition-clause","data-testid":`g7le-condition-clause-${k}`,style:JC,children:[i.jsxs("div",{style:{display:"flex",gap:6,alignItems:"center"},children:[k>0&&i.jsx("span",{"data-testid":`g7le-condition-combinator-${k}`,style:QC,children:n(f==="or"?"layout_editor.visibility.or":"layout_editor.visibility.and")}),i.jsx("select",{"data-testid":`g7le-condition-select-${k}`,value:w.operator,onChange:$=>m(k,{operator:$.target.value,params:{}}),style:ZC,children:l.map($=>i.jsx("option",{value:$.value,children:Pu($.label,n,$.value)},$.value))}),i.jsx("button",{type:"button","data-testid":`g7le-condition-remove-${k}`,"aria-label":n("layout_editor.visibility.remove"),onClick:()=>h(k),style:RC,children:"✕"})]}),x.params.map($=>{const C=zn($.widget);return i.jsxs("div",{className:"g7le-condition-param","data-testid":`g7le-condition-param-${k}-${$.key}`,style:e$,children:[i.jsx("span",{style:t$,children:Pu($.label,n,$.key)}),i.jsx("div",{style:{flex:1},children:C?i.jsx(C,{control:{...$},value:w.params[$.key],onChange:S=>m(k,{operator:w.operator,params:{...w.params,[$.key]:S===void 0?"":String(S)}}),t:n,candidates:y($.widget)}):i.jsx("span",{style:{fontSize:11,color:"#94a3b8"},children:n("layout_editor.property_modal.unsupported_widget")})})]},$.key)})]},k)}),i.jsxs("div",{style:{display:"flex",gap:6},children:[i.jsxs("button",{type:"button","data-testid":"g7le-condition-add-and",onClick:()=>v("and"),style:Wu,children:["+ ",n("layout_editor.visibility.and")]}),i.jsxs("button",{type:"button","data-testid":"g7le-condition-add-or",onClick:()=>v("or"),style:Wu,children:["+ ",n("layout_editor.visibility.or")]})]}),_!==""&&i.jsxs("div",{"data-testid":"g7le-condition-preview",style:n$,children:[i.jsx("span",{style:{fontSize:11,color:"#64748b"},children:n("layout_editor.visibility.preview")}),i.jsx("code",{style:ju,children:_})]})]})}const YC={fontSize:12,color:"#94a3b8",padding:"16px 0",textAlign:"center"},XC={fontSize:12,fontWeight:600,color:"#0f172a"},JC={border:"1px solid #e2e8f0",borderRadius:8,padding:10,display:"flex",flexDirection:"column",gap:8,background:"#f8fafc"},ZC={flex:1,padding:"5px 8px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,background:"#fff"},QC={fontSize:11,fontWeight:700,color:"#2563eb",background:"#dbeafe",padding:"2px 8px",borderRadius:999},RC={border:"none",background:"transparent",color:"#94a3b8",cursor:"pointer",fontSize:12},Wu={padding:"5px 12px",fontSize:12,border:"1px dashed #94a3b8",borderRadius:6,background:"#fff",color:"#475569",cursor:"pointer"},e$={display:"flex",alignItems:"center",gap:8},t$={fontSize:11,color:"#64748b",minWidth:64},n$={display:"flex",flexDirection:"column",gap:4,padding:10,background:"#f1f5f9",borderRadius:6},ju={fontSize:11,color:"#0f172a",fontFamily:"monospace",wordBreak:"break-all"},o$={fontSize:11,color:"#92400e",background:"#fef3c7",padding:"6px 8px",borderRadius:6},r$={padding:"5px 12px",fontSize:12,border:"1px solid #fecaca",borderRadius:6,background:"#fff",color:"#dc2626",cursor:"pointer",alignSelf:"flex-start"};function i$(e){return typeof e=="string"?e:e===void 0?"":JSON.stringify(e)}function s$(e){const t=e.trim();if(t==="")return"";if(/^-?\d+(\.\d+)?$/.test(t)||t==="true"||t==="false"||t==="null")try{return JSON.parse(t)}catch{return e}return e}function Hu(e){return!e||typeof e!="object"||Array.isArray(e)?[]:Object.entries(e).map(([t,n])=>({key:t,value:i$(n)}))}function oa(e){const t={};for(const n of e){const o=n.key.trim();o!==""&&(t[o]=s$(n.value))}return t}function Fu(e){return!e||typeof e!="object"||Array.isArray(e)?!1:Object.values(e).some(t=>t!==null&&typeof t=="object")}function a$(e){const t={};return typeof e.if=="string"&&e.if.trim().length>0&&(t.if=e.if),t}function l$(e,t){const n={...t},o=e.if;return typeof o=="string"&&o.trim().length>0?n.if=o:delete n.if,n}const d$=["api","static","route_params","query_params","websocket"],c$=["GET","POST","PUT","PATCH","DELETE"],u$=["none","optional","required"],p$=["progressive","blocking","background"],f$=["application/json","multipart/form-data"],g$=["public","private","presence"];function h$(e){if(!e||typeof e!="object")return{own:[],inherited:[]};const t=e.__editor,n=t&&typeof t=="object"&&!Array.isArray(t)?t.original:void 0,o=n&&typeof n=="object"&&!Array.isArray(n)?n.data_sources:e.data_sources,r=Array.isArray(o)?o.filter(c=>c&&typeof c=="object"):[],s=new Set(r.map(c=>typeof c.id=="string"?c.id:"").filter(Boolean)),a=e.data_sources,d=(Array.isArray(a)?a.filter(c=>c&&typeof c=="object"):[]).filter(c=>typeof c.id=="string"&&!s.has(c.id));return{own:r,inherited:d}}function y$(e,t){const n=(o,r="")=>typeof o=="string"?o:r;return{index:t,id:n(e.id),type:n(e.type,"api"),label_key:n(e.label_key),endpoint:n(e.endpoint),method:n(e.method,"GET"),auth_mode:n(e.auth_mode,"none"),loading_strategy:n(e.loading_strategy,"progressive"),auto_fetch:e.auto_fetch!==!1,paramRows:Hu(e.params),paramsRaw:Fu(e.params),paramsRawValue:e.params&&typeof e.params=="object"&&!Array.isArray(e.params)?e.params:{},paramsRawValid:!0,fallbackEnabled:e.fallback!==void 0,fallbackValue:e.fallback,if:n(e.if),contentType:n(e.contentType),refetchOnMount:e.refetchOnMount===!0,onSuccess:Array.isArray(e.onSuccess)?e.onSuccess.filter(o=>!!o&&typeof o=="object"):[],onError:Array.isArray(e.onError)?e.onError.filter(o=>!!o&&typeof o=="object"):[],errorHandling:e.errorHandling&&typeof e.errorHandling=="object"&&!Array.isArray(e.errorHandling)?e.errorHandling:{},channel:n(e.channel),event:n(e.event),channel_type:n(e.channel_type,"public"),target_source:n(e.target_source),onReceive:Array.isArray(e.onReceive)?e.onReceive.filter(o=>!!o&&typeof o=="object"):[]}}function b$(){return{index:-1,id:"",type:"api",label_key:"",endpoint:"",method:"GET",auth_mode:"none",loading_strategy:"progressive",auto_fetch:!0,paramRows:[],paramsRaw:!1,paramsRawValue:{},paramsRawValid:!0,fallbackEnabled:!1,fallbackValue:void 0,if:"",contentType:"",refetchOnMount:!1,onSuccess:[],onError:[],errorHandling:{},channel:"",event:"",channel_type:"public",target_source:"",onReceive:[]}}function m$(e,t,n,o){const r=e.id.trim();if(!r)return{error:o("layout_editor.data_sources.error.id_required")};if(!/^[A-Za-z_][\w]*$/.test(r))return{error:o("layout_editor.data_sources.error.id_invalid")};if(n.has(r))return{error:o("layout_editor.data_sources.error.id_duplicate",{id:r})};const s=t?{...t}:{};if(s.id=r,s.type=e.type,e.label_key.trim()?s.label_key=e.label_key.trim():delete s.label_key,e.type==="api"||e.type==="websocket"?(e.endpoint.trim()?s.endpoint=e.endpoint.trim():delete s.endpoint,s.method=e.method,s.auth_mode=e.auth_mode,s.loading_strategy=e.loading_strategy):(delete s.endpoint,delete s.method),s.auto_fetch=e.auto_fetch,e.paramsRaw){if(!e.paramsRawValid)return{error:o("layout_editor.data_sources.error.params_json")};const a=e.paramsRawValue;a&&typeof a=="object"&&Object.keys(a).length>0?s.params=a:delete s.params}else{const a=oa(e.paramRows);Object.keys(a).length>0?s.params=a:delete s.params}return e.fallbackEnabled?s.fallback=e.fallbackValue===void 0?{}:e.fallbackValue:delete s.fallback,e.if.trim()?s.if=e.if.trim():delete s.if,e.type==="api"&&e.contentType?s.contentType=e.contentType:delete s.contentType,e.refetchOnMount?s.refetchOnMount=!0:delete s.refetchOnMount,e.onSuccess.length>0?s.onSuccess=e.onSuccess:delete s.onSuccess,e.onError.length>0?s.onError=e.onError:delete s.onError,Object.keys(e.errorHandling).length>0?s.errorHandling=e.errorHandling:delete s.errorHandling,e.type==="websocket"?(e.channel?s.channel=e.channel:delete s.channel,e.event?s.event=e.event:delete s.event,s.channel_type=e.channel_type,e.target_source?s.target_source=e.target_source:delete s.target_source,e.onReceive.length>0?s.onReceive=e.onReceive:delete s.onReceive):(delete s.channel,delete s.event,delete s.channel_type,delete s.target_source,delete s.onReceive),{entry:s}}const Ht={width:"100%",padding:"6px 8px",fontSize:13,border:"1px solid #cbd5e1",borderRadius:6,outline:"none",boxSizing:"border-box",fontFamily:"inherit",color:"#0f172a",background:"#ffffff"},ct={display:"block",fontSize:12,fontWeight:600,color:"#475569",marginBottom:4},ut={marginBottom:12},an={padding:"6px 12px",fontSize:13,fontWeight:500,borderRadius:6,border:"1px solid #cbd5e1",background:"#ffffff",color:"#0f172a",cursor:"pointer"},Ku={...an,background:"#2563eb",borderColor:"#2563eb",color:"#ffffff",fontWeight:600},Wo={marginTop:12,padding:10,border:"1px solid #e2e8f0",borderRadius:8,background:"#f8fafc"},jo={fontSize:12,fontWeight:700,color:"#475569",marginBottom:8},v$={margin:"0 0 6px",fontSize:11,color:"#94a3b8"},Vu={marginTop:8},Uu={alignSelf:"flex-start",padding:"2px 0",fontSize:11,border:"none",background:"none",color:"#64748b",cursor:"pointer"},qu={margin:0,padding:8,background:"#0f172a",color:"#e2e8f0",borderRadius:6,fontSize:11,lineHeight:1.5,overflow:"auto",fontFamily:"ui-monospace, monospace"};function _$({code:e,action:t,recipes:n,pools:o,t:r,onPatch:s}){const[a,l]=u.useState(!1),d=u.useMemo(()=>Io(n),[n]),c=Array.isArray(t)?t[0]:t,p=typeof c?.handler=="string"?c.handler:"",f=d.find(_=>_.id===p)??null,b=c?Jn(c,d):null,g=b?.kind==="preset"?b.values:{},m=p==="sequence"||p==="parallel",h=["","showErrorPage","navigate","openModal","toast","setState","sequence","parallel"],v=_=>{if(_===""){s({});return}const w=d.find(k=>k.id===_);s(w?Ir(w,{}):{handler:_})},y=m&&c?Array.isArray(c.params?.actions)?c.params.actions:[]:[];return i.jsxs("div",{"data-testid":`g7le-ds-errorhandling-${e}`,style:{display:"flex",flexDirection:"column",gap:6},children:[i.jsxs("div",{style:{display:"flex",alignItems:"center",gap:6},children:[i.jsx("select",{"data-testid":`g7le-ds-eh-handler-${e}`,style:{...Ht,flex:1},value:p,onChange:_=>v(_.target.value),children:h.map(_=>i.jsx("option",{value:_,children:r(_===""?"layout_editor.error.handler_none":`layout_editor.error.handler_${_}`)},_))}),p!==""?i.jsx("button",{type:"button","data-testid":`g7le-ds-eh-code-${e}`,onClick:()=>l(_=>!_),"aria-label":r("layout_editor.action_list.view_code"),title:r("layout_editor.action_list.view_code"),style:a?x$:Gu,children:""}):null]}),m?i.jsx("div",{"data-testid":`g7le-ds-eh-actions-${e}`,children:i.jsx(Yt,{actions:y,onChange:_=>s({handler:p,params:{actions:_}}),t:r,recipes:n,candidatePools:o,chipContext:"error",testIdPrefix:`g7le-ds-eh-seq-${e}`})}):f&&c?i.jsx(Bs,{raw:c,recipe:f,values:g,t:r,pools:o,onChange:_=>s(_),testIdPrefix:`g7le-ds-eh-action-${e}-edit`}):null,a&&c?i.jsx("pre",{"data-testid":`g7le-ds-eh-code-view-${e}`,style:w$,children:JSON.stringify(t,null,2)}):null]})}const Gu={flex:"0 0 auto",width:26,height:26,display:"inline-flex",alignItems:"center",justifyContent:"center",fontSize:11,border:"1px solid #e2e8f0",borderRadius:6,background:"#fff",color:"#64748b",cursor:"pointer",fontFamily:"ui-monospace, monospace"},x$={...Gu,background:"#0f172a",borderColor:"#0f172a",color:"#e2e8f0"},w$={margin:"4px 0 0",padding:8,background:"#0f172a",color:"#e2e8f0",borderRadius:6,fontSize:12,overflow:"auto",fontFamily:"ui-monospace, monospace"};function k$({raw:e,onChange:t,t:n,resolveLabel:o,onClose:r,actionRecipes:s,errorRecipes:a,conditionSpec:l,bindingCandidates:d,pageCandidates:c,dataSourceCandidates:p,stateKeyCandidates:f}){const b=o??n,g=u.useMemo(()=>h$(e),[e]),m=g.inherited,[h,v]=u.useState(g.own),[y,_]=u.useState(null),[w,k]=u.useState(null),[x,$]=u.useState(!1),[C,S]=u.useState(!1),[D,E]=u.useState(()=>new Set),I=u.useCallback(A=>{E(L=>{const F=new Set(L);return F.has(A)?F.delete(A):F.add(A),F})},[]),N=u.useMemo(()=>({pageCandidates:c,dataSourceCandidates:p,stateKeyCandidates:f,bindingCandidates:d}),[c,p,f,d]),T=u.useCallback(A=>{v(A),t([...m,...A],A)},[m,t]),O=u.useCallback(()=>{k(null),_(b$())},[]),M=u.useCallback((A,L)=>{k(null),_(y$(A,L))},[]),B=u.useCallback(A=>{const L=h.filter((F,Y)=>Y!==A);T(L)},[h,T]),z=u.useCallback(()=>{_(null),k(null)},[]),H=u.useCallback(()=>{if(!y)return;const A=new Set;h.forEach((V,X)=>{X!==y.index&&typeof V.id=="string"&&A.add(V.id)}),m.forEach(V=>{typeof V.id=="string"&&A.add(V.id)});const L=y.index>=0?h[y.index]??null:null,F=m$(y,L,A,n);if("error"in F){k(F.error);return}const Y=y.index>=0?h.map((V,X)=>X===y.index?F.entry:V):[...h,F.entry];T(Y),_(null),k(null)},[y,h,m,T,n]),P=y?.type==="api"||y?.type==="websocket";return i.jsxs("div",{className:"g7le-data-sources","data-testid":"g7le-data-sources-panel",style:{flex:"1 1 auto",minHeight:0,display:"flex",flexDirection:"column",fontSize:13,color:"#0f172a",boxSizing:"border-box"},children:[i.jsxs("div",{className:"g7le-data-sources__header",style:{flex:"0 0 auto",padding:"16px 16px 12px",borderBottom:"1px solid #e2e8f0"},children:[i.jsxs("div",{style:{display:"flex",alignItems:"center",justifyContent:"space-between",marginBottom:8},children:[i.jsx("h2",{style:{margin:0,fontSize:16,fontWeight:700},children:n("layout_editor.data_sources.title")}),i.jsx("button",{type:"button","data-testid":"g7le-data-sources-close",onClick:r,"aria-label":n("layout_editor.data_sources.close"),style:{...an,padding:"4px 10px"},children:"✕"})]}),i.jsx("p",{style:{margin:0,fontSize:12,color:"#64748b",lineHeight:1.5},children:n("layout_editor.data_sources.description")})]}),i.jsxs("div",{className:"g7le-data-sources__body",style:{flex:"1 1 auto",minHeight:0,overflowY:"auto",padding:16},children:[y&&i.jsxs("div",{className:"g7le-data-sources__form","data-testid":"g7le-data-sources-form",children:[i.jsx("div",{style:{fontSize:13,fontWeight:700,marginBottom:12},children:y.index>=0?n("layout_editor.data_sources.form.edit_title"):n("layout_editor.data_sources.form.add_title")}),i.jsxs("div",{style:ut,children:[i.jsx("label",{style:ct,htmlFor:"g7le-ds-id",children:n("layout_editor.data_sources.field.id")}),i.jsx("input",{id:"g7le-ds-id","data-testid":"g7le-data-sources-field-id",style:Ht,value:y.id,onChange:A=>_({...y,id:A.target.value}),placeholder:"products"})]}),i.jsxs("div",{style:ut,children:[i.jsx("label",{style:ct,htmlFor:"g7le-ds-label",children:n("layout_editor.data_sources.field.label_key")}),i.jsx(_t,{value:y.label_key,onChange:A=>_({...y,label_key:A??""}),t:n,placeholder:n("layout_editor.data_sources.field.label_key_placeholder"),testidPrefix:"g7le-data-sources-field-label",candidates:d,enableExpressionTree:!0,expressionTreeCollapsible:!0}),i.jsx("div",{style:{fontSize:11,color:"#94a3b8",marginTop:4},children:n("layout_editor.data_sources.field.label_key_hint")})]}),i.jsxs("div",{style:ut,children:[i.jsx("label",{style:ct,htmlFor:"g7le-ds-type",children:n("layout_editor.data_sources.field.type")}),i.jsx("select",{id:"g7le-ds-type","data-testid":"g7le-data-sources-field-type",style:Ht,value:y.type,onChange:A=>_({...y,type:A.target.value}),children:d$.map(A=>i.jsx("option",{value:A,children:n(`layout_editor.data_sources.ds_type.${A}`)},A))})]}),P&&i.jsxs(i.Fragment,{children:[i.jsxs("div",{style:ut,children:[i.jsx("label",{style:ct,children:n("layout_editor.data_sources.field.endpoint")}),i.jsx("div",{"data-testid":"g7le-data-sources-field-endpoint",children:i.jsx(Ke,{value:y.endpoint,onChange:A=>_({...y,endpoint:A}),t:n,candidates:d,placeholder:"/api/shop/products/{{route.id}}",testidPrefix:"g7le-ds-endpoint"})})]}),i.jsxs("div",{style:{display:"flex",gap:12},children:[i.jsxs("div",{style:{...ut,flex:1},children:[i.jsx("label",{style:ct,htmlFor:"g7le-ds-method",children:n("layout_editor.data_sources.field.method")}),i.jsx("select",{id:"g7le-ds-method","data-testid":"g7le-data-sources-field-method",style:Ht,value:y.method,onChange:A=>_({...y,method:A.target.value}),children:c$.map(A=>i.jsx("option",{value:A,children:A},A))})]}),i.jsxs("div",{style:{...ut,flex:1},children:[i.jsx("label",{style:ct,htmlFor:"g7le-ds-auth",children:n("layout_editor.data_sources.field.auth_mode")}),i.jsx("select",{id:"g7le-ds-auth","data-testid":"g7le-data-sources-field-auth",style:Ht,value:y.auth_mode,onChange:A=>_({...y,auth_mode:A.target.value}),children:u$.map(A=>i.jsx("option",{value:A,children:n(`layout_editor.data_sources.auth_mode.${A}`)},A))})]})]}),i.jsxs("div",{style:ut,children:[i.jsx("label",{style:ct,htmlFor:"g7le-ds-loading",children:n("layout_editor.data_sources.field.loading_strategy")}),i.jsx("select",{id:"g7le-ds-loading","data-testid":"g7le-data-sources-field-loading",style:Ht,value:y.loading_strategy,onChange:A=>_({...y,loading_strategy:A.target.value}),children:p$.map(A=>i.jsx("option",{value:A,children:n(`layout_editor.data_sources.loading_strategy.${A}`)},A))})]}),y.type==="api"&&i.jsxs("div",{style:ut,children:[i.jsx("label",{style:ct,htmlFor:"g7le-ds-contenttype",children:n("layout_editor.data_sources.field.content_type")}),i.jsx("select",{id:"g7le-ds-contenttype","data-testid":"g7le-ds-contenttype",style:Ht,value:y.contentType||"application/json",onChange:A=>_({...y,contentType:A.target.value}),children:f$.map(A=>i.jsx("option",{value:A,children:A},A))})]})]}),i.jsx("div",{style:ut,children:i.jsxs("label",{style:{...ct,display:"flex",alignItems:"center",gap:8,cursor:"pointer"},children:[i.jsx("input",{type:"checkbox","data-testid":"g7le-data-sources-field-autofetch",checked:y.auto_fetch,onChange:A=>_({...y,auto_fetch:A.target.checked})}),n("layout_editor.data_sources.field.auto_fetch")]})}),i.jsx("div",{style:ut,children:i.jsxs("label",{style:{...ct,display:"flex",alignItems:"center",gap:8,cursor:"pointer"},children:[i.jsx("input",{type:"checkbox","data-testid":"g7le-ds-refetch",checked:y.refetchOnMount,onChange:A=>_({...y,refetchOnMount:A.target.checked})}),n("layout_editor.data_sources.field.refetch_on_mount")]})}),i.jsxs("div",{style:ut,children:[i.jsxs("div",{style:{display:"flex",alignItems:"center",justifyContent:"space-between"},children:[i.jsx("label",{style:ct,htmlFor:"g7le-ds-params",children:n("layout_editor.data_sources.field.params")}),i.jsx("button",{type:"button","data-testid":"g7le-ds-params-mode-toggle",onClick:()=>{if(y.paramsRaw){if(y.paramsRawValid){const A=y.paramsRawValue;A&&typeof A=="object"&&!Array.isArray(A)&&!Fu(A)&&_({...y,paramsRaw:!1,paramRows:Hu(A)})}}else{const A=oa(y.paramRows);_({...y,paramsRaw:!0,paramsRawValue:A,paramsRawValid:!0})}},style:{...an,padding:"2px 8px",fontSize:11},children:y.paramsRaw?n("layout_editor.data_sources.field.params_mode_block"):n("layout_editor.data_sources.field.params_mode_code")})]}),y.paramsRaw?i.jsx(qs,{id:"g7le-ds-params",value:y.paramsRawValue,shape:"object",emptyValue:{},minHeight:64,testidPrefix:"g7le-data-sources-field-params",placeholder:`{ "page": "{{query.page ?? 1}}" -}`,shapeErrorKey:"layout_editor.data_sources.error.params_object",invalidErrorKey:"layout_editor.data_sources.error.params_json",t:n,onChange:I=>_(L=>L&&{...L,paramsRawValue:I??{},paramsRawValid:!0}),onValidityChange:I=>_(L=>L&&{...L,paramsRawValid:I})}):i.jsxs("div",{"data-testid":"g7le-data-sources-field-params",children:[y.paramRows.length===0&&i.jsx("p",{"data-testid":"g7le-ds-params-empty",style:e$,children:n("layout_editor.data_sources.field.params_empty")}),i.jsx(Pr,{value:y.paramRows.map(I=>({key:I.key,value:I.value})),onChange:I=>_({...y,paramRows:I.map(L=>({key:L.key??"",value:L.value??""}))}),keyField:"key",valueField:"value",t:n,candidates:d,testidPrefix:"g7le-ds-params-kv",keyPlaceholder:n("layout_editor.data_sources.field.param_key_placeholder"),valuePlaceholder:"{{query.page ?? 1}}",addLabel:n("layout_editor.data_sources.field.param_add")}),(()=>{const I=Zs(y.paramRows);return Object.keys(I).length===0?null:i.jsxs("div",{style:Ou,children:[i.jsxs("button",{type:"button","data-testid":"g7le-ds-params-preview-toggle","aria-expanded":x,onClick:()=>$(L=>!L),style:Bu,children:[x?"▴":"▾"," ",n("layout_editor.data_sources.field.params_preview")]}),x&&i.jsx("pre",{"data-testid":"g7le-ds-params-preview",style:{...Wu,marginTop:4},children:JSON.stringify(I,null,2)})]})})()]}),i.jsx("div",{style:{fontSize:11,color:"#94a3b8",marginTop:4},children:n("layout_editor.data_sources.field.params_hint")})]}),i.jsxs("div",{style:ut,children:[i.jsx("label",{style:ct,children:n("layout_editor.data_sources.field.fallback")}),y.fallbackEnabled?i.jsxs("div",{"data-testid":"g7le-data-sources-field-fallback",style:{display:"flex",flexDirection:"column",gap:6},children:[i.jsx(zn,{value:y.fallbackValue===void 0?{}:y.fallbackValue,onChange:I=>_({...y,fallbackValue:I}),t:n,path:"fallback",scope:"ds-fallback",renderStringLeaf:I=>i.jsx(Ke,{value:I.value,onChange:I.onChange,t:n,candidates:d,testidPrefix:I.testidPrefix})}),(()=>{const I=y.fallbackValue===void 0?{}:y.fallbackValue;return i.jsxs("div",{style:Ou,children:[i.jsxs("button",{type:"button","data-testid":"g7le-ds-fallback-preview-toggle","aria-expanded":S,onClick:()=>C(L=>!L),style:Bu,children:[S?"▴":"▾"," ",n("layout_editor.data_sources.field.fallback_preview")]}),S&&i.jsx("pre",{"data-testid":"g7le-ds-fallback-preview",style:{...Wu,marginTop:4},children:JSON.stringify(I,null,2)})]})})(),i.jsx("button",{type:"button","data-testid":"g7le-data-sources-field-fallback-clear",onClick:()=>_({...y,fallbackEnabled:!1,fallbackValue:void 0}),style:{...rn,padding:"2px 8px",fontSize:11,alignSelf:"flex-start",color:"#dc2626",borderColor:"#fecaca"},children:n("layout_editor.data_sources.field.fallback_clear")})]}):i.jsxs("button",{type:"button","data-testid":"g7le-data-sources-field-fallback-add",onClick:()=>_({...y,fallbackEnabled:!0,fallbackValue:{}}),style:{...rn,padding:"4px 10px",display:"block"},children:["+ ",n("layout_editor.data_sources.field.fallback_add")]})]}),y.type==="websocket"&&i.jsxs("div",{"data-testid":"g7le-ds-ws-section",style:Mo,children:[i.jsx("div",{style:Lo,children:n("layout_editor.data_sources.section.websocket")}),i.jsxs("div",{style:ut,children:[i.jsx("label",{style:ct,children:n("layout_editor.data_sources.field.channel")}),i.jsx("input",{"data-testid":"g7le-ds-ws-channel",style:jt,value:y.channel,onChange:I=>_({...y,channel:I.target.value}),placeholder:"core.admin.dashboard"})]}),i.jsxs("div",{style:ut,children:[i.jsx("label",{style:ct,children:n("layout_editor.data_sources.field.event")}),i.jsx("input",{"data-testid":"g7le-ds-ws-event",style:jt,value:y.event,onChange:I=>_({...y,event:I.target.value}),placeholder:"dashboard.stats.updated"})]}),i.jsxs("div",{style:ut,children:[i.jsx("label",{style:ct,children:n("layout_editor.data_sources.field.channel_type")}),i.jsx("select",{"data-testid":"g7le-ds-ws-channeltype",style:jt,value:y.channel_type,onChange:I=>_({...y,channel_type:I.target.value}),children:XC.map(I=>i.jsx("option",{value:I,children:I},I))})]}),i.jsxs("div",{style:ut,children:[i.jsx("label",{style:ct,children:n("layout_editor.data_sources.field.target_source")}),i.jsx("input",{"data-testid":"g7le-ds-ws-target",style:jt,value:y.target_source,onChange:I=>_({...y,target_source:I.target.value}),list:"g7le-ds-ws-target-list"}),i.jsx("datalist",{id:"g7le-ds-ws-target-list",children:h.filter(I=>I.id!==y.id).map(I=>typeof I.id=="string"?i.jsx("option",{value:I.id},I.id):null)})]}),i.jsxs("div",{style:ut,children:[i.jsx("label",{style:ct,children:n("layout_editor.data_sources.field.on_receive")}),i.jsx("div",{"data-testid":"g7le-ds-ws-onreceive",children:i.jsx(Gt,{actions:y.onReceive,onChange:I=>_({...y,onReceive:I}),t:n,recipes:s,candidatePools:N,chipContext:"payload",actionChipCandidates:l?.actionChipCandidates??null,testIdPrefix:"g7le-ds-onreceive-list"})})]})]}),i.jsxs("div",{style:Mo,children:[i.jsx("div",{style:Lo,children:n("layout_editor.data_sources.section.on_success")}),i.jsx("div",{"data-testid":"g7le-ds-onsuccess",children:i.jsx(Gt,{actions:y.onSuccess,onChange:I=>_({...y,onSuccess:I}),t:n,recipes:s,candidatePools:N,chipContext:"response",actionChipCandidates:l?.actionChipCandidates??null,testIdPrefix:"g7le-ds-onsuccess-list"})})]}),i.jsxs("div",{style:Mo,children:[i.jsx("div",{style:Lo,children:n("layout_editor.data_sources.section.on_error")}),i.jsx("div",{"data-testid":"g7le-ds-onerror",children:i.jsx(Gt,{actions:y.onError,onChange:I=>_({...y,onError:I}),t:n,recipes:s,candidatePools:N,chipContext:"error",actionChipCandidates:l?.actionChipCandidates??null,testIdPrefix:"g7le-ds-onerror-list"})})]}),i.jsxs("div",{style:Mo,children:[i.jsx("div",{style:Lo,children:n("layout_editor.data_sources.section.error_handling")}),i.jsxs("div",{"data-testid":"g7le-ds-errorhandling",children:[i.jsx(Us,{value:y.errorHandling,onChange:I=>_({...y,errorHandling:I}),t:n,mode:"local",testIdPrefix:"g7le-ds-eh",renderActionList:(I,L,F)=>i.jsx(t$,{code:I,action:L,recipes:a,pools:N,t:n,onPatch:F})}),i.jsxs("button",{type:"button","data-testid":"g7le-ds-errorhandling-add",onClick:()=>{const I=Object.keys(y.errorHandling),L=["403","404","500","422","default"].find(F=>!I.includes(F))??"default";_({...y,errorHandling:{...y.errorHandling,[L]:{handler:"toast"}}})},style:{...rn,padding:"4px 10px",marginTop:6},children:["+ ",n("layout_editor.data_sources.add_error_code")]})]})]}),i.jsxs("div",{style:Mo,children:[i.jsx("div",{style:Lo,children:n("layout_editor.data_sources.section.load_condition")}),i.jsx("div",{"data-testid":"g7le-ds-loadcondition",children:i.jsx(Du,{node:FC({if:y.if||void 0}),spec:l??null,t:n,dataSourceCandidates:p,stateKeyCandidates:f,onPatchNode:I=>{const L=KC(I,{});_({...y,if:typeof L.if=="string"?L.if:""})}})})]})]}),!y&&i.jsxs("button",{type:"button","data-testid":"g7le-data-sources-add",onClick:B,style:{...Pu,marginBottom:16},children:["+ ",n("layout_editor.data_sources.add")]}),i.jsxs("div",{className:"g7le-data-sources__list","data-testid":"g7le-data-sources-list",style:{display:"flex",flexDirection:"column",gap:8},children:[h.length===0&&!y&&i.jsx("div",{"data-testid":"g7le-data-sources-empty",style:{fontSize:12,color:"#94a3b8",padding:"12px 0",textAlign:"center"},children:n("layout_editor.data_sources.empty")}),h.map((I,L)=>{const F=typeof I.id=="string"?I.id:`#${L}`,Y=typeof I.endpoint=="string"?I.endpoint:"",K=typeof I.label_key=="string"?I.label_key:"",X=as(I,b);return i.jsxs("div",{"data-testid":"g7le-data-sources-item","data-ds-id":F,style:{border:"1px solid #e2e8f0",borderRadius:8,padding:"10px 12px",display:"flex",alignItems:"center",gap:8,background:"#ffffff"},children:[i.jsxs("div",{style:{flex:1,minWidth:0},children:[i.jsxs("div",{"data-testid":"g7le-data-sources-item-title",style:{fontWeight:600,fontSize:13,display:"flex",alignItems:"baseline",gap:6,flexWrap:"wrap"},children:[i.jsx("span",{children:X??F}),X&&i.jsx("span",{"data-testid":"g7le-data-sources-item-id",style:{fontSize:11,fontWeight:400,color:"#94a3b8"},children:F}),(()=>{const Z=ls(I,n);return Z?i.jsx("span",{"data-testid":"g7le-data-sources-ext-badge",style:{fontSize:10,fontWeight:600,color:"#7c3aed",background:"#f5f3ff",border:"1px solid #ddd6fe",borderRadius:4,padding:"1px 6px"},children:Z}):null})()]}),Y&&i.jsx("div",{style:{fontSize:11,color:"#94a3b8",overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap"},children:Y}),!K&&i.jsx("div",{"data-testid":"g7le-data-sources-nolabel",style:{fontSize:11,color:"#f59e0b"},children:n("layout_editor.data_sources.no_label")})]}),i.jsx("button",{type:"button","data-testid":"g7le-data-sources-edit",onClick:()=>M(I,L),style:{...rn,padding:"4px 10px"},children:n("layout_editor.data_sources.edit")}),i.jsx("button",{type:"button","data-testid":"g7le-data-sources-remove",onClick:()=>O(L),style:{...rn,padding:"4px 10px",color:"#dc2626",borderColor:"#fecaca"},children:n("layout_editor.data_sources.remove")})]},`${F}-${L}`)})]}),m.length>0&&i.jsxs("div",{style:{marginTop:16},children:[i.jsx("div",{style:{fontSize:12,fontWeight:600,color:"#64748b",textTransform:"uppercase",letterSpacing:.4,marginBottom:8},children:n("layout_editor.data_sources.inherited_title")}),i.jsx("div",{style:{display:"flex",flexDirection:"column",gap:6},children:m.map((I,L)=>{const F=typeof I.id=="string"?I.id:`#${L}`,Y=T.has(F),K=[],X=(Z,se)=>{const oe=I[Z];oe!=null&&K.push([se,typeof oe=="object"?JSON.stringify(oe):String(oe)])};return X("type",n("layout_editor.data_sources.field.type")),X("endpoint",n("layout_editor.data_sources.field.endpoint")),X("method",n("layout_editor.data_sources.field.method")),X("auth_mode",n("layout_editor.data_sources.field.auth_mode")),X("loading_strategy",n("layout_editor.data_sources.field.loading_strategy")),X("auto_fetch",n("layout_editor.data_sources.field.auto_fetch")),X("params",n("layout_editor.data_sources.field.params")),i.jsxs("div",{"data-testid":"g7le-data-sources-inherited-item","data-ds-id":F,"data-expanded":Y?"true":"false",style:{border:"1px dashed #e2e8f0",borderRadius:6,fontSize:12,color:"#64748b",background:"#f8fafc",overflow:"hidden"},children:[i.jsxs("button",{type:"button","data-testid":"g7le-data-sources-inherited-toggle",onClick:()=>A(F),"aria-expanded":Y,style:{width:"100%",display:"flex",alignItems:"center",gap:8,padding:"8px 12px",background:"transparent",border:"none",cursor:"pointer",color:"inherit",fontSize:12,textAlign:"left",outline:"none"},children:[i.jsx("span",{style:{flex:"0 0 auto",width:12,transition:"transform 120ms",transform:Y?"rotate(90deg)":"none"},children:"▶"}),(()=>{const Z=as(I,b),se=ls(I,n);return i.jsxs("span",{style:{flex:1,display:"inline-flex",alignItems:"baseline",gap:6,flexWrap:"wrap"},children:[i.jsx("span",{style:{fontWeight:600},children:Z??F}),Z&&i.jsx("span",{style:{fontSize:11,fontWeight:400,color:"#94a3b8"},children:F}),se&&i.jsx("span",{"data-testid":"g7le-data-sources-ext-badge",style:{fontSize:10,fontWeight:600,color:"#7c3aed",background:"#f5f3ff",border:"1px solid #ddd6fe",borderRadius:4,padding:"1px 6px"},children:se})]})})()]}),Y&&i.jsxs("div",{"data-testid":"g7le-data-sources-inherited-info",style:{padding:"0 12px 10px 32px",display:"flex",flexDirection:"column",gap:3},children:[K.length===0&&i.jsx("span",{style:{color:"#94a3b8"},children:"—"}),K.map(([Z,se])=>i.jsxs("div",{style:{display:"flex",gap:6},children:[i.jsxs("span",{style:{flex:"0 0 auto",color:"#94a3b8"},children:[Z,":"]}),i.jsx("span",{style:{flex:1,minWidth:0,wordBreak:"break-all",color:"#475569"},children:se})]},Z))]})]},`inh-${F}-${L}`)})})]})]}),y&&i.jsxs("div",{className:"g7le-data-sources__footer",style:{flex:"0 0 auto",padding:"12px 16px",borderTop:"1px solid #e2e8f0",background:"#ffffff"},children:[w&&i.jsx("div",{"data-testid":"g7le-data-sources-form-error",role:"alert",style:{fontSize:12,color:"#dc2626",marginBottom:10},children:w}),i.jsxs("div",{style:{display:"flex",gap:8,justifyContent:"flex-end"},children:[i.jsx("button",{type:"button","data-testid":"g7le-data-sources-form-cancel",onClick:z,style:rn,children:n("layout_editor.data_sources.form.cancel")}),i.jsx("button",{type:"button","data-testid":"g7le-data-sources-form-submit",onClick:H,style:Pu,children:n("layout_editor.data_sources.form.save")})]})]})]})}function i$(e){const t=e?.globalHeaders;return Array.isArray(t)?t:[]}function s$(e){const t=e?.scripts;return Array.isArray(t)?t:[]}function a$(e){const t=i$(e.raw),n=s$(e.raw);return i.jsxs("div",{className:"g7le-data-source-tab","data-testid":"g7le-data-source-tab",style:l$,children:[i.jsx(r$,{...e}),i.jsxs("div",{style:Hu,"data-testid":"g7le-global-headers",children:[i.jsx("div",{style:Fu,children:e.t("layout_editor.data_sources.global_headers")}),t.length===0?i.jsx("p",{"data-testid":"g7le-global-header-empty",style:Gu,children:e.t("layout_editor.data_sources.global_headers_empty")}):t.map((o,r)=>i.jsxs("div",{"data-testid":`g7le-global-header-${r}`,style:Ku,children:[i.jsx("code",{style:Vu,children:o.pattern??"*"}),i.jsx("span",{style:d$,children:"→"}),i.jsx("span",{style:Uu,children:Object.keys(o.headers??{}).join(", ")})]},r)),i.jsxs("p",{style:qu,children:["ⓘ ",e.t("layout_editor.data_sources.global_headers_hint")]})]}),i.jsxs("div",{style:Hu,"data-testid":"g7le-scripts",children:[i.jsx("div",{style:Fu,children:e.t("layout_editor.data_sources.scripts")}),n.length===0?i.jsx("p",{"data-testid":"g7le-script-empty",style:Gu,children:e.t("layout_editor.data_sources.scripts_empty")}):n.map((o,r)=>i.jsxs("div",{"data-testid":`g7le-script-${o.id??r}`,style:Ku,children:[i.jsx("code",{style:Vu,children:o.id??`#${r}`}),i.jsx("span",{style:Uu,children:o.src??""}),o.loading?i.jsx("span",{style:c$,children:o.loading}):null]},o.id??r)),i.jsxs("p",{style:qu,children:["ⓘ ",e.t("layout_editor.data_sources.scripts_hint")]})]})]})}const l$={display:"flex",flexDirection:"column",gap:16,minWidth:0},Hu={borderTop:"1px solid #e2e8f0",paddingTop:12,display:"flex",flexDirection:"column",gap:6},Fu={fontSize:12,fontWeight:700,color:"#475569"},Ku={display:"flex",alignItems:"center",gap:8,fontSize:12,padding:"4px 8px",border:"1px solid #f1f5f9",borderRadius:6},Vu={fontSize:11,color:"#0f172a",fontFamily:"monospace"},d$={color:"#94a3b8"},Uu={flex:1,minWidth:0,color:"#475569",wordBreak:"break-all"},c$={fontSize:10,color:"#64748b",background:"#f1f5f9",borderRadius:4,padding:"0 4px"},qu={margin:0,fontSize:11,color:"#94a3b8"},Gu={margin:0,fontSize:12,color:"#94a3b8"},u$=["meta","seo","init","overlay","computed","state","error","data"],p$=["401","403","404","500","503"],f$={meta:"layout_editor.page_settings.tab.meta",seo:"layout_editor.page_settings.tab.seo",init:"layout_editor.page_settings.tab.init",overlay:"layout_editor.page_settings.tab.overlay",computed:"layout_editor.page_settings.tab.computed",state:"layout_editor.page_settings.tab.state",error:"layout_editor.page_settings.tab.error",data:"layout_editor.page_settings.tab.data"};async function g$(e){if(typeof fetch!="function"||!e)return[];try{const t=await fetch(en(`/api/admin/templates/${encodeURIComponent(e)}/editor/seo-candidates`,"json"),{credentials:"same-origin",headers:Ne()});if(!t.ok)return[];const o=(await t.json().catch(()=>null))?.data?.extensions;if(!Array.isArray(o))return[];const r=[];for(const s of o)if(s&&typeof s=="object"){const a=s.type,l=s.id,d=s.label;typeof a=="string"&&typeof l=="string"&&l.length>0&&r.push({type:a,id:l,name:typeof d=="string"?d:l})}return r}catch{return[]}}function h$(e,t){let n=0;for(const o of e)Yn(o,t).kind==="advanced"&&(n+=1);return n}function y$(e,t){let n=0;for(const o of Object.keys(e))Dd(e[o],t).kind==="advanced"&&(n+=1);return n}function b$(e,t,n){const o=e("meta",{})??{},r=o.title;if(typeof r=="string"&&r.length>0){if(r.startsWith("$t:")){const a=n(r.slice(3));if(a&&a!==r.slice(3))return a}else if(!/\{\{[\s\S]*\}\}/.test(r))return r}const s=o.editor_label;return typeof s=="string"&&s.length>0?s.startsWith("$t:")?n(s.slice(3)):s:t||""}function m$({templateIdentifier:e,spec:t,t:n,resolveLabel:o,permissionCandidates:r,onClose:s,initialTab:a="meta",extensionsFetcher:l}){const{state:d}=Qe(),{raw:c,getValue:p,patch:f}=jm(),[b,g]=u.useState(a),m=d.selectedRoute?.path??"",h=u.useMemo(()=>Od(d.routeTree,o),[d.routeTree,o]),v=u.useMemo(()=>Bd(c),[c]),y=u.useMemo(()=>Wd(t),[t]),_=u.useMemo(()=>iv(c,o),[c,o]),w=Pd({raw:c,spec:t}),k=u.useMemo(()=>lb(t),[t]),x=u.useMemo(()=>db(t),[t]),$=u.useMemo(()=>cb(t),[t]),S=u.useMemo(()=>ub(t),[t]),C=u.useMemo(()=>ko(k),[k]),T=u.useMemo(()=>Cd(x),[x]),E=p("init_actions",void 0)??p("initActions",[]),A=p("computed",{}),N=p("__computedSource",{}),D=p("errorHandling",{}),B=p("transition_overlay",void 0),M=(p("__editor",{})??{}).original,O=u.useMemo(()=>{if(M===void 0)return;const G=M.transition_overlay;return G&&typeof G=="object"&&!Array.isArray(G)?G:{}},[M]),z=u.useMemo(()=>{const G=B;if(!G||typeof G!="object"||Array.isArray(G))return;const ee=O??{},ge={};for(const[V,te]of Object.entries(G))V in ee||(ge[V]=te);return Object.keys(ge).length>0?ge:void 0},[B,O]),[H,P]=u.useState([]);u.useEffect(()=>{let G=!1;const ee=l??g$;return(async()=>{const ge=await ee(e);G||P(ge)})(),()=>{G=!0}},[e,l]);const I=(p("meta",{})??{}).seo?.page_type,L=ov({raw:c,spec:t,pageType:typeof I=="string"?I:null}),F=u.useMemo(()=>nv(c,t),[c,t]),Y=u.useMemo(()=>({meta:0,seo:0,init:h$(E,C),overlay:0,computed:y$(A,T),state:0,error:0,data:0}),[E,C,A,T]),K=u.useMemo(()=>Object.values(Y).reduce((G,ee)=>G+ee,0),[Y]),X=u.useMemo(()=>rv(c,o,n),[c,o,n]),Z=u.useCallback(G=>{const ge={...M?.computed??{}};delete ge[G];const V={...A};delete V[G],f("computed",V,Object.keys(ge).length===0?void 0:ge)},[M,A,f]),se=b$(p,m,n),oe=()=>{switch(b){case"meta":return i.jsx(kx,{getValue:p,patch:f,t:n,fieldT:o,spec:t,permissionCandidates:r,bindingCandidates:w});case"seo":return i.jsx(d0,{getValue:p,patch:f,t:n,templateIdentifier:e,candidates:L,availableExtensions:H,dataSourceOptions:X,previewUrl:m||"/",seedContext:F});case"init":return i.jsx(Ak,{actions:E,onChange:G=>f("init_actions",G),recipes:k,t:o,bindingCandidates:w,pageCandidates:h,dataSourceCandidates:v,stateKeyCandidates:y,modalCandidates:_});case"overlay":return i.jsx(Qk,{value:B,ownValue:O,baseValue:z,patch:G=>f("transition_overlay",G),t:o,loadingComponents:S,progressiveDataSources:X,bindingCandidates:w});case"computed":return i.jsx(j1,{computed:A,onChange:G=>f("computed",G),recipes:x,t:o,sampleContext:F,dataSourceCandidates:v,bindingCandidates:w,computedSource:N,onRevert:Z});case"state":return i.jsx(xS,{raw:c,own:M?{initLocal:M.initLocal,initGlobal:M.initGlobal,initIsolated:M.initIsolated,state:M.state}:void 0,patch:f,t:n});case"error":return i.jsx(GS,{value:D,onChange:G=>f("errorHandling",G),t:o,errorConfigCodes:p$,templateCodes:Object.keys(D),ownCodes:Object.keys(M?.errorHandling??{}),recipes:$,pageCandidates:h,modalCandidates:_,dataSourceCandidates:v,stateKeyCandidates:y});case"data":return i.jsx(a$,{raw:c,t:n,resolveLabel:o,onChange:(G,ee)=>f("data_sources",G,ee),onClose:s,actionRecipes:k,errorRecipes:$,conditionSpec:t,bindingCandidates:w,pageCandidates:h,dataSourceCandidates:v,stateKeyCandidates:y});default:return i.jsx("div",{})}};return i.jsxs("div",{className:"g7le-page-settings","data-testid":"g7le-page-settings",style:v$,children:[i.jsxs("div",{style:_$,children:[i.jsx("span",{"data-testid":"g7le-page-settings-title",style:x$,children:n("layout_editor.page_settings.title",{name:se})}),i.jsx("button",{type:"button","data-testid":"g7le-page-settings-close-x",onClick:s,"aria-label":n("layout_editor.page_settings.close"),style:w$,children:"✕"})]}),i.jsx("div",{role:"tablist",style:k$,children:u$.map(G=>{const ee=Y[G];return i.jsxs("button",{type:"button",role:"tab","aria-selected":b===G,"data-testid":`g7le-page-settings-tab-${G}`,"data-active":b===G?"true":"false",onClick:()=>g(G),style:b===G?S$:Yu,children:[n(f$[G]),ee>0?i.jsxs("span",{"data-testid":`g7le-page-settings-tab-badge-${G}`,style:C$,children:["(",ee,")"]}):null]},G)})}),K>0?i.jsxs("p",{"data-testid":"g7le-page-settings-advanced-summary",style:$$,children:["⚠ ",n("layout_editor.page_settings.advanced_summary",{count:K})," ⓘ"]}):null,i.jsx("div",{"data-testid":"g7le-page-settings-panel",style:E$,children:oe()}),i.jsx("div",{style:I$,children:i.jsx("button",{type:"button","data-testid":"g7le-page-settings-close",onClick:s,style:A$,children:n("layout_editor.page_settings.close")})})]})}const v$={display:"flex",flexDirection:"column",minWidth:0,maxWidth:"100%",padding:"20px 24px",boxSizing:"border-box",flex:1,minHeight:0,maxHeight:"100%",overflow:"hidden",fontFamily:"system-ui, -apple-system, sans-serif"},_$={display:"flex",alignItems:"center",gap:8,paddingBottom:8,borderBottom:"1px solid #e2e8f0"},x$={flex:1,fontSize:15,fontWeight:700,color:"#0f172a",minWidth:0},w$={border:"none",background:"transparent",color:"#64748b",cursor:"pointer",fontSize:16,lineHeight:1,padding:4},k$={display:"flex",gap:2,paddingTop:8,borderBottom:"1px solid #e2e8f0",flexWrap:"nowrap",overflowX:"auto",overflowY:"hidden",flexShrink:0,minWidth:0},Yu={border:"none",borderBottomWidth:"2px",borderBottomStyle:"solid",borderBottomColor:"transparent",background:"transparent",color:"#64748b",cursor:"pointer",fontSize:12,padding:"6px 10px",whiteSpace:"nowrap"},S$={...Yu,color:"#2563eb",borderBottomColor:"#2563eb",fontWeight:600},C$={marginLeft:4,fontSize:10,color:"#b45309"},$$={margin:0,marginTop:8,fontSize:11,color:"#b45309",background:"#fffbeb",padding:"6px 8px",borderRadius:6},E$={flex:1,overflowY:"auto",paddingTop:12,minWidth:0,minHeight:0},I$={display:"flex",justifyContent:"flex-end",paddingTop:8,borderTop:"1px solid #e2e8f0"},A$={border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",color:"#475569",cursor:"pointer",fontSize:13,padding:"6px 16px"};function Qs(e,t){const n=`/api/admin/templates/${encodeURIComponent(e)}`;return t.kind==="layout"?`${n}/layouts/${t.layoutName}/versions`:`${n}/layout-extensions/${encodeURIComponent(t.extensionId)}/versions`}const Xu=100,Ju=500;function T$(e,t,n){const[o,r]=u.useState([]),[s,a]=u.useState(!1),[l,d]=u.useState(!1),[c,p]=u.useState(null),[f,b]=u.useState(null),[g,m]=u.useState(Xu),[h,v]=u.useState(!1),y=u.useCallback(async($,S)=>{if(!t){r([]),p(null),v(!1);return}S==="more"?d(!0):a(!0),p(null);try{const C=`${Qs(e,t)}?limit=${$}`,T=await fetch(C,{credentials:"same-origin",headers:Ne()}),E=await T.json().catch(()=>null);if(!T.ok){p(E?.message??`HTTP ${T.status}`),S==="reload"&&r([]);return}const A=E?.data,N=Array.isArray(A)?A:[];r(N),v(N.length>=$&&${await y(g,"reload")},[y,g]),w=u.useCallback(async()=>{const $=Math.min(g+Xu,Ju);if($<=g){v(!1);return}await y($,"more")},[y,g]),k=u.useCallback(async $=>{if(!t)return{kind:"not_found"};b($);try{const S=`${Qs(e,t)}/${$}/restore`,C=await fetch(S,{method:"POST",credentials:"same-origin",headers:Ne({"Content-Type":"application/json"})}),T=await C.json().catch(()=>null);if(C.status===404)return{kind:"not_found"};if(!C.ok)return{kind:"network_error",message:T?.message??`HTTP ${C.status}`};const E=typeof T?.data?.version=="number"?T.data.version:-1;return await n?.(E>=0?E:void 0),{kind:"success",newVersion:E}}catch(S){return{kind:"network_error",message:S instanceof Error?S.message:"network error"}}finally{b(null)}},[e,t,n]),x=u.useCallback(async $=>{if(!t)return{kind:"not_found"};try{const S=`${Qs(e,t)}/${$}`,C=await fetch(S,{credentials:"same-origin",headers:Ne()}),T=await C.json().catch(()=>null);if(C.status===404)return{kind:"not_found"};if(!C.ok)return{kind:"network_error",message:T?.message??`HTTP ${C.status}`};const E=T?.data;if(!E)return{kind:"not_found"};if(t.kind==="extension"&&!E.full_content){const A=E.content;if(typeof A=="string")try{const N=JSON.parse(A);N&&typeof N=="object"&&(E.full_content=N)}catch{}}return{kind:"success",detail:E}}catch(S){return{kind:"network_error",message:S instanceof Error?S.message:"network error"}}},[e,t]);return{versions:o,isLoading:s,isLoadingMore:l,error:c,restoringId:f,hasMore:h,loadVersions:_,loadMore:w,restore:k,loadVersionDetail:x}}function D$(e,t){const n=e.length,o=t.length,r=Array.from({length:n+1},()=>new Array(o+1).fill(0));for(let s=n-1;s>=0;s--)for(let a=o-1;a>=0;a--)e[s]===t[a]?r[s][a]=r[s+1][a+1]+1:r[s][a]=Math.max(r[s+1][a],r[s][a+1]);return r}function z$(e,t,n){const o=[];let r=0,s=0;const a=e.length,l=t.length;for(;r=n[r][s+1]?(o.push({kind:"remove",oldIndex:r,content:e[r]}),r++):(o.push({kind:"add",newIndex:s,content:t[s]}),s++);for(;r_(L=>L&&{...L,paramsRawValue:A??{},paramsRawValid:!0}),onValidityChange:A=>_(L=>L&&{...L,paramsRawValid:A})}):i.jsxs("div",{"data-testid":"g7le-data-sources-field-params",children:[y.paramRows.length===0&&i.jsx("p",{"data-testid":"g7le-ds-params-empty",style:v$,children:n("layout_editor.data_sources.field.params_empty")}),i.jsx(jr,{value:y.paramRows.map(A=>({key:A.key,value:A.value})),onChange:A=>_({...y,paramRows:A.map(L=>({key:L.key??"",value:L.value??""}))}),keyField:"key",valueField:"value",t:n,candidates:d,testidPrefix:"g7le-ds-params-kv",keyPlaceholder:n("layout_editor.data_sources.field.param_key_placeholder"),valuePlaceholder:"{{query.page ?? 1}}",addLabel:n("layout_editor.data_sources.field.param_add")}),(()=>{const A=oa(y.paramRows);return Object.keys(A).length===0?null:i.jsxs("div",{style:Vu,children:[i.jsxs("button",{type:"button","data-testid":"g7le-ds-params-preview-toggle","aria-expanded":x,onClick:()=>$(L=>!L),style:Uu,children:[x?"▴":"▾"," ",n("layout_editor.data_sources.field.params_preview")]}),x&&i.jsx("pre",{"data-testid":"g7le-ds-params-preview",style:{...qu,marginTop:4},children:JSON.stringify(A,null,2)})]})})()]}),i.jsx("div",{style:{fontSize:11,color:"#94a3b8",marginTop:4},children:n("layout_editor.data_sources.field.params_hint")})]}),i.jsxs("div",{style:ut,children:[i.jsx("label",{style:ct,children:n("layout_editor.data_sources.field.fallback")}),y.fallbackEnabled?i.jsxs("div",{"data-testid":"g7le-data-sources-field-fallback",style:{display:"flex",flexDirection:"column",gap:6},children:[i.jsx(Mn,{value:y.fallbackValue===void 0?{}:y.fallbackValue,onChange:A=>_({...y,fallbackValue:A}),t:n,path:"fallback",scope:"ds-fallback",renderStringLeaf:A=>i.jsx(Ke,{value:A.value,onChange:A.onChange,t:n,candidates:d,testidPrefix:A.testidPrefix})}),(()=>{const A=y.fallbackValue===void 0?{}:y.fallbackValue;return i.jsxs("div",{style:Vu,children:[i.jsxs("button",{type:"button","data-testid":"g7le-ds-fallback-preview-toggle","aria-expanded":C,onClick:()=>S(L=>!L),style:Uu,children:[C?"▴":"▾"," ",n("layout_editor.data_sources.field.fallback_preview")]}),C&&i.jsx("pre",{"data-testid":"g7le-ds-fallback-preview",style:{...qu,marginTop:4},children:JSON.stringify(A,null,2)})]})})(),i.jsx("button",{type:"button","data-testid":"g7le-data-sources-field-fallback-clear",onClick:()=>_({...y,fallbackEnabled:!1,fallbackValue:void 0}),style:{...an,padding:"2px 8px",fontSize:11,alignSelf:"flex-start",color:"#dc2626",borderColor:"#fecaca"},children:n("layout_editor.data_sources.field.fallback_clear")})]}):i.jsxs("button",{type:"button","data-testid":"g7le-data-sources-field-fallback-add",onClick:()=>_({...y,fallbackEnabled:!0,fallbackValue:{}}),style:{...an,padding:"4px 10px",display:"block"},children:["+ ",n("layout_editor.data_sources.field.fallback_add")]})]}),y.type==="websocket"&&i.jsxs("div",{"data-testid":"g7le-ds-ws-section",style:Wo,children:[i.jsx("div",{style:jo,children:n("layout_editor.data_sources.section.websocket")}),i.jsxs("div",{style:ut,children:[i.jsx("label",{style:ct,children:n("layout_editor.data_sources.field.channel")}),i.jsx("input",{"data-testid":"g7le-ds-ws-channel",style:Ht,value:y.channel,onChange:A=>_({...y,channel:A.target.value}),placeholder:"core.admin.dashboard"})]}),i.jsxs("div",{style:ut,children:[i.jsx("label",{style:ct,children:n("layout_editor.data_sources.field.event")}),i.jsx("input",{"data-testid":"g7le-ds-ws-event",style:Ht,value:y.event,onChange:A=>_({...y,event:A.target.value}),placeholder:"dashboard.stats.updated"})]}),i.jsxs("div",{style:ut,children:[i.jsx("label",{style:ct,children:n("layout_editor.data_sources.field.channel_type")}),i.jsx("select",{"data-testid":"g7le-ds-ws-channeltype",style:Ht,value:y.channel_type,onChange:A=>_({...y,channel_type:A.target.value}),children:g$.map(A=>i.jsx("option",{value:A,children:A},A))})]}),i.jsxs("div",{style:ut,children:[i.jsx("label",{style:ct,children:n("layout_editor.data_sources.field.target_source")}),i.jsx("input",{"data-testid":"g7le-ds-ws-target",style:Ht,value:y.target_source,onChange:A=>_({...y,target_source:A.target.value}),list:"g7le-ds-ws-target-list"}),i.jsx("datalist",{id:"g7le-ds-ws-target-list",children:h.filter(A=>A.id!==y.id).map(A=>typeof A.id=="string"?i.jsx("option",{value:A.id},A.id):null)})]}),i.jsxs("div",{style:ut,children:[i.jsx("label",{style:ct,children:n("layout_editor.data_sources.field.on_receive")}),i.jsx("div",{"data-testid":"g7le-ds-ws-onreceive",children:i.jsx(Yt,{actions:y.onReceive,onChange:A=>_({...y,onReceive:A}),t:n,recipes:s,candidatePools:N,chipContext:"payload",actionChipCandidates:l?.actionChipCandidates??null,testIdPrefix:"g7le-ds-onreceive-list"})})]})]}),i.jsxs("div",{style:Wo,children:[i.jsx("div",{style:jo,children:n("layout_editor.data_sources.section.on_success")}),i.jsx("div",{"data-testid":"g7le-ds-onsuccess",children:i.jsx(Yt,{actions:y.onSuccess,onChange:A=>_({...y,onSuccess:A}),t:n,recipes:s,candidatePools:N,chipContext:"response",actionChipCandidates:l?.actionChipCandidates??null,testIdPrefix:"g7le-ds-onsuccess-list"})})]}),i.jsxs("div",{style:Wo,children:[i.jsx("div",{style:jo,children:n("layout_editor.data_sources.section.on_error")}),i.jsx("div",{"data-testid":"g7le-ds-onerror",children:i.jsx(Yt,{actions:y.onError,onChange:A=>_({...y,onError:A}),t:n,recipes:s,candidatePools:N,chipContext:"error",actionChipCandidates:l?.actionChipCandidates??null,testIdPrefix:"g7le-ds-onerror-list"})})]}),i.jsxs("div",{style:Wo,children:[i.jsx("div",{style:jo,children:n("layout_editor.data_sources.section.error_handling")}),i.jsxs("div",{"data-testid":"g7le-ds-errorhandling",children:[i.jsx(Js,{value:y.errorHandling,onChange:A=>_({...y,errorHandling:A}),t:n,mode:"local",testIdPrefix:"g7le-ds-eh",renderActionList:(A,L,F)=>i.jsx(_$,{code:A,action:L,recipes:a,pools:N,t:n,onPatch:F})}),i.jsxs("button",{type:"button","data-testid":"g7le-ds-errorhandling-add",onClick:()=>{const A=Object.keys(y.errorHandling),L=["403","404","500","422","default"].find(F=>!A.includes(F))??"default";_({...y,errorHandling:{...y.errorHandling,[L]:{handler:"toast"}}})},style:{...an,padding:"4px 10px",marginTop:6},children:["+ ",n("layout_editor.data_sources.add_error_code")]})]})]}),i.jsxs("div",{style:Wo,children:[i.jsx("div",{style:jo,children:n("layout_editor.data_sources.section.load_condition")}),i.jsx("div",{"data-testid":"g7le-ds-loadcondition",children:i.jsx(Bu,{node:a$({if:y.if||void 0}),spec:l??null,t:n,dataSourceCandidates:p,stateKeyCandidates:f,onPatchNode:A=>{const L=l$(A,{});_({...y,if:typeof L.if=="string"?L.if:""})}})})]})]}),!y&&i.jsxs("button",{type:"button","data-testid":"g7le-data-sources-add",onClick:O,style:{...Ku,marginBottom:16},children:["+ ",n("layout_editor.data_sources.add")]}),i.jsxs("div",{className:"g7le-data-sources__list","data-testid":"g7le-data-sources-list",style:{display:"flex",flexDirection:"column",gap:8},children:[h.length===0&&!y&&i.jsx("div",{"data-testid":"g7le-data-sources-empty",style:{fontSize:12,color:"#94a3b8",padding:"12px 0",textAlign:"center"},children:n("layout_editor.data_sources.empty")}),h.map((A,L)=>{const F=typeof A.id=="string"?A.id:`#${L}`,Y=typeof A.endpoint=="string"?A.endpoint:"",V=typeof A.label_key=="string"?A.label_key:"",X=ps(A,b);return i.jsxs("div",{"data-testid":"g7le-data-sources-item","data-ds-id":F,style:{border:"1px solid #e2e8f0",borderRadius:8,padding:"10px 12px",display:"flex",alignItems:"center",gap:8,background:"#ffffff"},children:[i.jsxs("div",{style:{flex:1,minWidth:0},children:[i.jsxs("div",{"data-testid":"g7le-data-sources-item-title",style:{fontWeight:600,fontSize:13,display:"flex",alignItems:"baseline",gap:6,flexWrap:"wrap"},children:[i.jsx("span",{children:X??F}),X&&i.jsx("span",{"data-testid":"g7le-data-sources-item-id",style:{fontSize:11,fontWeight:400,color:"#94a3b8"},children:F}),(()=>{const Z=fs(A,n);return Z?i.jsx("span",{"data-testid":"g7le-data-sources-ext-badge",style:{fontSize:10,fontWeight:600,color:"#7c3aed",background:"#f5f3ff",border:"1px solid #ddd6fe",borderRadius:4,padding:"1px 6px"},children:Z}):null})()]}),Y&&i.jsx("div",{style:{fontSize:11,color:"#94a3b8",overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap"},children:Y}),!V&&i.jsx("div",{"data-testid":"g7le-data-sources-nolabel",style:{fontSize:11,color:"#f59e0b"},children:n("layout_editor.data_sources.no_label")})]}),i.jsx("button",{type:"button","data-testid":"g7le-data-sources-edit",onClick:()=>M(A,L),style:{...an,padding:"4px 10px"},children:n("layout_editor.data_sources.edit")}),i.jsx("button",{type:"button","data-testid":"g7le-data-sources-remove",onClick:()=>B(L),style:{...an,padding:"4px 10px",color:"#dc2626",borderColor:"#fecaca"},children:n("layout_editor.data_sources.remove")})]},`${F}-${L}`)})]}),m.length>0&&i.jsxs("div",{style:{marginTop:16},children:[i.jsx("div",{style:{fontSize:12,fontWeight:600,color:"#64748b",textTransform:"uppercase",letterSpacing:.4,marginBottom:8},children:n("layout_editor.data_sources.inherited_title")}),i.jsx("div",{style:{display:"flex",flexDirection:"column",gap:6},children:m.map((A,L)=>{const F=typeof A.id=="string"?A.id:`#${L}`,Y=D.has(F),V=[],X=(Z,ie)=>{const oe=A[Z];oe!=null&&V.push([ie,typeof oe=="object"?JSON.stringify(oe):String(oe)])};return X("type",n("layout_editor.data_sources.field.type")),X("endpoint",n("layout_editor.data_sources.field.endpoint")),X("method",n("layout_editor.data_sources.field.method")),X("auth_mode",n("layout_editor.data_sources.field.auth_mode")),X("loading_strategy",n("layout_editor.data_sources.field.loading_strategy")),X("auto_fetch",n("layout_editor.data_sources.field.auto_fetch")),X("params",n("layout_editor.data_sources.field.params")),i.jsxs("div",{"data-testid":"g7le-data-sources-inherited-item","data-ds-id":F,"data-expanded":Y?"true":"false",style:{border:"1px dashed #e2e8f0",borderRadius:6,fontSize:12,color:"#64748b",background:"#f8fafc",overflow:"hidden"},children:[i.jsxs("button",{type:"button","data-testid":"g7le-data-sources-inherited-toggle",onClick:()=>I(F),"aria-expanded":Y,style:{width:"100%",display:"flex",alignItems:"center",gap:8,padding:"8px 12px",background:"transparent",border:"none",cursor:"pointer",color:"inherit",fontSize:12,textAlign:"left",outline:"none"},children:[i.jsx("span",{style:{flex:"0 0 auto",width:12,transition:"transform 120ms",transform:Y?"rotate(90deg)":"none"},children:"▶"}),(()=>{const Z=ps(A,b),ie=fs(A,n);return i.jsxs("span",{style:{flex:1,display:"inline-flex",alignItems:"baseline",gap:6,flexWrap:"wrap"},children:[i.jsx("span",{style:{fontWeight:600},children:Z??F}),Z&&i.jsx("span",{style:{fontSize:11,fontWeight:400,color:"#94a3b8"},children:F}),ie&&i.jsx("span",{"data-testid":"g7le-data-sources-ext-badge",style:{fontSize:10,fontWeight:600,color:"#7c3aed",background:"#f5f3ff",border:"1px solid #ddd6fe",borderRadius:4,padding:"1px 6px"},children:ie})]})})()]}),Y&&i.jsxs("div",{"data-testid":"g7le-data-sources-inherited-info",style:{padding:"0 12px 10px 32px",display:"flex",flexDirection:"column",gap:3},children:[V.length===0&&i.jsx("span",{style:{color:"#94a3b8"},children:"—"}),V.map(([Z,ie])=>i.jsxs("div",{style:{display:"flex",gap:6},children:[i.jsxs("span",{style:{flex:"0 0 auto",color:"#94a3b8"},children:[Z,":"]}),i.jsx("span",{style:{flex:1,minWidth:0,wordBreak:"break-all",color:"#475569"},children:ie})]},Z))]})]},`inh-${F}-${L}`)})})]})]}),y&&i.jsxs("div",{className:"g7le-data-sources__footer",style:{flex:"0 0 auto",padding:"12px 16px",borderTop:"1px solid #e2e8f0",background:"#ffffff"},children:[w&&i.jsx("div",{"data-testid":"g7le-data-sources-form-error",role:"alert",style:{fontSize:12,color:"#dc2626",marginBottom:10},children:w}),i.jsxs("div",{style:{display:"flex",gap:8,justifyContent:"flex-end"},children:[i.jsx("button",{type:"button","data-testid":"g7le-data-sources-form-cancel",onClick:z,style:an,children:n("layout_editor.data_sources.form.cancel")}),i.jsx("button",{type:"button","data-testid":"g7le-data-sources-form-submit",onClick:H,style:Ku,children:n("layout_editor.data_sources.form.save")})]})]})]})}function S$(e){const t=e?.globalHeaders;return Array.isArray(t)?t:[]}function C$(e){const t=e?.scripts;return Array.isArray(t)?t:[]}function $$(e){const t=S$(e.raw),n=C$(e.raw);return i.jsxs("div",{className:"g7le-data-source-tab","data-testid":"g7le-data-source-tab",style:E$,children:[i.jsx(k$,{...e}),i.jsxs("div",{style:Yu,"data-testid":"g7le-global-headers",children:[i.jsx("div",{style:Xu,children:e.t("layout_editor.data_sources.global_headers")}),t.length===0?i.jsx("p",{"data-testid":"g7le-global-header-empty",style:ep,children:e.t("layout_editor.data_sources.global_headers_empty")}):t.map((o,r)=>i.jsxs("div",{"data-testid":`g7le-global-header-${r}`,style:Ju,children:[i.jsx("code",{style:Zu,children:o.pattern??"*"}),i.jsx("span",{style:I$,children:"→"}),i.jsx("span",{style:Qu,children:Object.keys(o.headers??{}).join(", ")})]},r)),i.jsxs("p",{style:Ru,children:["ⓘ ",e.t("layout_editor.data_sources.global_headers_hint")]})]}),i.jsxs("div",{style:Yu,"data-testid":"g7le-scripts",children:[i.jsx("div",{style:Xu,children:e.t("layout_editor.data_sources.scripts")}),n.length===0?i.jsx("p",{"data-testid":"g7le-script-empty",style:ep,children:e.t("layout_editor.data_sources.scripts_empty")}):n.map((o,r)=>i.jsxs("div",{"data-testid":`g7le-script-${o.id??r}`,style:Ju,children:[i.jsx("code",{style:Zu,children:o.id??`#${r}`}),i.jsx("span",{style:Qu,children:o.src??""}),o.loading?i.jsx("span",{style:A$,children:o.loading}):null]},o.id??r)),i.jsxs("p",{style:Ru,children:["ⓘ ",e.t("layout_editor.data_sources.scripts_hint")]})]})]})}const E$={display:"flex",flexDirection:"column",gap:16,minWidth:0},Yu={borderTop:"1px solid #e2e8f0",paddingTop:12,display:"flex",flexDirection:"column",gap:6},Xu={fontSize:12,fontWeight:700,color:"#475569"},Ju={display:"flex",alignItems:"center",gap:8,fontSize:12,padding:"4px 8px",border:"1px solid #f1f5f9",borderRadius:6},Zu={fontSize:11,color:"#0f172a",fontFamily:"monospace"},I$={color:"#94a3b8"},Qu={flex:1,minWidth:0,color:"#475569",wordBreak:"break-all"},A$={fontSize:10,color:"#64748b",background:"#f1f5f9",borderRadius:4,padding:"0 4px"},Ru={margin:0,fontSize:11,color:"#94a3b8"},ep={margin:0,fontSize:12,color:"#94a3b8"},T$=["meta","seo","init","overlay","computed","state","error","data"],D$=["401","403","404","500","503"],z$={meta:"layout_editor.page_settings.tab.meta",seo:"layout_editor.page_settings.tab.seo",init:"layout_editor.page_settings.tab.init",overlay:"layout_editor.page_settings.tab.overlay",computed:"layout_editor.page_settings.tab.computed",state:"layout_editor.page_settings.tab.state",error:"layout_editor.page_settings.tab.error",data:"layout_editor.page_settings.tab.data"};async function N$(e){if(typeof fetch!="function"||!e)return[];try{const t=await fetch(nn(`/api/admin/templates/${encodeURIComponent(e)}/editor/seo-candidates`,"json"),{credentials:"same-origin",headers:Me()});if(!t.ok)return[];const o=(await t.json().catch(()=>null))?.data?.extensions;if(!Array.isArray(o))return[];const r=[];for(const s of o)if(s&&typeof s=="object"){const a=s.type,l=s.id,d=s.label;typeof a=="string"&&typeof l=="string"&&l.length>0&&r.push({type:a,id:l,name:typeof d=="string"?d:l})}return r}catch{return[]}}function M$(e,t){let n=0;for(const o of e)Jn(o,t).kind==="advanced"&&(n+=1);return n}function L$(e,t){let n=0;for(const o of Object.keys(e))Pd(e[o],t).kind==="advanced"&&(n+=1);return n}function P$(e,t,n){const o=e("meta",{})??{},r=o.title;if(typeof r=="string"&&r.length>0){if(r.startsWith("$t:")){const a=n(r.slice(3));if(a&&a!==r.slice(3))return a}else if(!/\{\{[\s\S]*\}\}/.test(r))return r}const s=o.editor_label;return typeof s=="string"&&s.length>0?s.startsWith("$t:")?n(s.slice(3)):s:t||""}function O$({templateIdentifier:e,spec:t,t:n,resolveLabel:o,permissionCandidates:r,onClose:s,initialTab:a="meta",extensionsFetcher:l}){const{state:d}=Qe(),{raw:c,getValue:p,patch:f}=sv(),[b,g]=u.useState(a),m=d.selectedRoute?.path??"",h=u.useMemo(()=>Fd(d.routeTree,o),[d.routeTree,o]),v=u.useMemo(()=>Kd(c),[c]),y=u.useMemo(()=>Vd(t),[t]),_=u.useMemo(()=>Cv(c,o),[c,o]),w=Hd({raw:c,spec:t}),k=u.useMemo(()=>Ib(t),[t]),x=u.useMemo(()=>Ab(t),[t]),$=u.useMemo(()=>Tb(t),[t]),C=u.useMemo(()=>Db(t),[t]),S=u.useMemo(()=>Io(k),[k]),D=u.useMemo(()=>Td(x),[x]),E=p("init_actions",void 0)??p("initActions",[]),I=p("computed",{}),N=p("__computedSource",{}),T=p("errorHandling",{}),O=p("transition_overlay",void 0),M=(p("__editor",{})??{}).original,B=u.useMemo(()=>{if(M===void 0)return;const G=M.transition_overlay;return G&&typeof G=="object"&&!Array.isArray(G)?G:{}},[M]),z=u.useMemo(()=>{const G=O;if(!G||typeof G!="object"||Array.isArray(G))return;const ee=B??{},fe={};for(const[K,te]of Object.entries(G))K in ee||(fe[K]=te);return Object.keys(fe).length>0?fe:void 0},[O,B]),[H,P]=u.useState([]);u.useEffect(()=>{let G=!1;const ee=l??N$;return(async()=>{const fe=await ee(e);G||P(fe)})(),()=>{G=!0}},[e,l]);const A=(p("meta",{})??{}).seo?.page_type,L=kv({raw:c,spec:t,pageType:typeof A=="string"?A:null}),F=u.useMemo(()=>wv(c,t),[c,t]),Y=u.useMemo(()=>({meta:0,seo:0,init:M$(E,S),overlay:0,computed:L$(I,D),state:0,error:0,data:0}),[E,S,I,D]),V=u.useMemo(()=>Object.values(Y).reduce((G,ee)=>G+ee,0),[Y]),X=u.useMemo(()=>Sv(c,o,n),[c,o,n]),Z=u.useCallback(G=>{const fe={...M?.computed??{}};delete fe[G];const K={...I};delete K[G],f("computed",K,Object.keys(fe).length===0?void 0:fe)},[M,I,f]),ie=P$(p,m,n),oe=()=>{switch(b){case"meta":return i.jsx(Kx,{getValue:p,patch:f,t:n,fieldT:o,spec:t,permissionCandidates:r,bindingCandidates:w});case"seo":return i.jsx(A0,{getValue:p,patch:f,t:n,templateIdentifier:e,candidates:L,availableExtensions:H,dataSourceOptions:X,previewUrl:m||"/",seedContext:F});case"init":return i.jsx(Xk,{actions:E,onChange:G=>f("init_actions",G),recipes:k,t:o,bindingCandidates:w,pageCandidates:h,dataSourceCandidates:v,stateKeyCandidates:y,modalCandidates:_});case"overlay":return i.jsx(m1,{value:O,ownValue:B,baseValue:z,patch:G=>f("transition_overlay",G),t:o,loadingComponents:C,progressiveDataSources:X,bindingCandidates:w});case"computed":return i.jsx(sS,{computed:I,onChange:G=>f("computed",G),recipes:x,t:o,sampleContext:F,dataSourceCandidates:v,bindingCandidates:w,computedSource:N,onRevert:Z});case"state":return i.jsx(HS,{raw:c,own:M?{initLocal:M.initLocal,initGlobal:M.initGlobal,initIsolated:M.initIsolated,state:M.state}:void 0,patch:f,t:n});case"error":return i.jsx(fC,{value:T,onChange:G=>f("errorHandling",G),t:o,errorConfigCodes:D$,templateCodes:Object.keys(T),ownCodes:Object.keys(M?.errorHandling??{}),recipes:$,pageCandidates:h,modalCandidates:_,dataSourceCandidates:v,stateKeyCandidates:y});case"data":return i.jsx($$,{raw:c,t:n,resolveLabel:o,onChange:(G,ee)=>f("data_sources",G,ee),onClose:s,actionRecipes:k,errorRecipes:$,conditionSpec:t,bindingCandidates:w,pageCandidates:h,dataSourceCandidates:v,stateKeyCandidates:y});default:return i.jsx("div",{})}};return i.jsxs("div",{className:"g7le-page-settings","data-testid":"g7le-page-settings",style:B$,children:[i.jsxs("div",{style:W$,children:[i.jsx("span",{"data-testid":"g7le-page-settings-title",style:j$,children:n("layout_editor.page_settings.title",{name:ie})}),i.jsx("button",{type:"button","data-testid":"g7le-page-settings-close-x",onClick:s,"aria-label":n("layout_editor.page_settings.close"),style:H$,children:"✕"})]}),i.jsx("div",{role:"tablist",style:F$,children:T$.map(G=>{const ee=Y[G];return i.jsxs("button",{type:"button",role:"tab","aria-selected":b===G,"data-testid":`g7le-page-settings-tab-${G}`,"data-active":b===G?"true":"false",onClick:()=>g(G),style:b===G?K$:tp,children:[n(z$[G]),ee>0?i.jsxs("span",{"data-testid":`g7le-page-settings-tab-badge-${G}`,style:V$,children:["(",ee,")"]}):null]},G)})}),V>0?i.jsxs("p",{"data-testid":"g7le-page-settings-advanced-summary",style:U$,children:["⚠ ",n("layout_editor.page_settings.advanced_summary",{count:V})," ⓘ"]}):null,i.jsx("div",{"data-testid":"g7le-page-settings-panel",style:q$,children:oe()}),i.jsx("div",{style:G$,children:i.jsx("button",{type:"button","data-testid":"g7le-page-settings-close",onClick:s,style:Y$,children:n("layout_editor.page_settings.close")})})]})}const B$={display:"flex",flexDirection:"column",minWidth:0,maxWidth:"100%",padding:"20px 24px",boxSizing:"border-box",flex:1,minHeight:0,maxHeight:"100%",overflow:"hidden",fontFamily:"system-ui, -apple-system, sans-serif"},W$={display:"flex",alignItems:"center",gap:8,paddingBottom:8,borderBottom:"1px solid #e2e8f0"},j$={flex:1,fontSize:15,fontWeight:700,color:"#0f172a",minWidth:0},H$={border:"none",background:"transparent",color:"#64748b",cursor:"pointer",fontSize:16,lineHeight:1,padding:4},F$={display:"flex",gap:2,paddingTop:8,borderBottom:"1px solid #e2e8f0",flexWrap:"nowrap",overflowX:"auto",overflowY:"hidden",flexShrink:0,minWidth:0},tp={border:"none",borderBottomWidth:"2px",borderBottomStyle:"solid",borderBottomColor:"transparent",background:"transparent",color:"#64748b",cursor:"pointer",fontSize:12,padding:"6px 10px",whiteSpace:"nowrap"},K$={...tp,color:"#2563eb",borderBottomColor:"#2563eb",fontWeight:600},V$={marginLeft:4,fontSize:10,color:"#b45309"},U$={margin:0,marginTop:8,fontSize:11,color:"#b45309",background:"#fffbeb",padding:"6px 8px",borderRadius:6},q$={flex:1,overflowY:"auto",paddingTop:12,minWidth:0,minHeight:0},G$={display:"flex",justifyContent:"flex-end",paddingTop:8,borderTop:"1px solid #e2e8f0"},Y$={border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",color:"#475569",cursor:"pointer",fontSize:13,padding:"6px 16px"};function ra(e,t){const n=`/api/admin/templates/${encodeURIComponent(e)}`;return t.kind==="layout"?`${n}/layouts/${t.layoutName}/versions`:`${n}/layout-extensions/${encodeURIComponent(t.extensionId)}/versions`}const np=100,op=500;function X$(e,t,n){const[o,r]=u.useState([]),[s,a]=u.useState(!1),[l,d]=u.useState(!1),[c,p]=u.useState(null),[f,b]=u.useState(null),[g,m]=u.useState(np),[h,v]=u.useState(!1),y=u.useCallback(async($,C)=>{if(!t){r([]),p(null),v(!1);return}C==="more"?d(!0):a(!0),p(null);try{const S=`${ra(e,t)}?limit=${$}`,D=await fetch(S,{credentials:"same-origin",headers:Me()}),E=await D.json().catch(()=>null);if(!D.ok){p(E?.message??`HTTP ${D.status}`),C==="reload"&&r([]);return}const I=E?.data,N=Array.isArray(I)?I:[];r(N),v(N.length>=$&&${await y(g,"reload")},[y,g]),w=u.useCallback(async()=>{const $=Math.min(g+np,op);if($<=g){v(!1);return}await y($,"more")},[y,g]),k=u.useCallback(async $=>{if(!t)return{kind:"not_found"};b($);try{const C=`${ra(e,t)}/${$}/restore`,S=await fetch(C,{method:"POST",credentials:"same-origin",headers:Me({"Content-Type":"application/json"})}),D=await S.json().catch(()=>null);if(S.status===404)return{kind:"not_found"};if(!S.ok)return{kind:"network_error",message:D?.message??`HTTP ${S.status}`};const E=typeof D?.data?.version=="number"?D.data.version:-1;return await n?.(E>=0?E:void 0),{kind:"success",newVersion:E}}catch(C){return{kind:"network_error",message:C instanceof Error?C.message:"network error"}}finally{b(null)}},[e,t,n]),x=u.useCallback(async $=>{if(!t)return{kind:"not_found"};try{const C=`${ra(e,t)}/${$}`,S=await fetch(C,{credentials:"same-origin",headers:Me()}),D=await S.json().catch(()=>null);if(S.status===404)return{kind:"not_found"};if(!S.ok)return{kind:"network_error",message:D?.message??`HTTP ${S.status}`};const E=D?.data;if(!E)return{kind:"not_found"};if(t.kind==="extension"&&!E.full_content){const I=E.content;if(typeof I=="string")try{const N=JSON.parse(I);N&&typeof N=="object"&&(E.full_content=N)}catch{}}return{kind:"success",detail:E}}catch(C){return{kind:"network_error",message:C instanceof Error?C.message:"network error"}}},[e,t]);return{versions:o,isLoading:s,isLoadingMore:l,error:c,restoringId:f,hasMore:h,loadVersions:_,loadMore:w,restore:k,loadVersionDetail:x}}function J$(e,t){const n=e.length,o=t.length,r=Array.from({length:n+1},()=>new Array(o+1).fill(0));for(let s=n-1;s>=0;s--)for(let a=o-1;a>=0;a--)e[s]===t[a]?r[s][a]=r[s+1][a+1]+1:r[s][a]=Math.max(r[s+1][a],r[s][a+1]);return r}function Z$(e,t,n){const o=[];let r=0,s=0;const a=e.length,l=t.length;for(;r=n[r][s+1]?(o.push({kind:"remove",oldIndex:r,content:e[r]}),r++):(o.push({kind:"add",newIndex:s,content:t[s]}),s++);for(;r0&&t[t.length-1]===""&&t.pop(),t}const Qu=4e3;function N$(e,t,n=3){const o=Zu(e),r=Zu(t);let s=0;const a=Math.min(o.length,r.length);for(;sQu||p.length>Qu)return{hunks:[],addedCount:-1,removedCount:-1,identical:!1};const f=D$(c,p),b=z$(c,p,f),g=[];let m=s,h=s,v=0,y=0;for(let k=0;k{const s=e.slice(r.start,r.end+1);let a=0,l=0,d=0,c=0;for(const g of s)g.oldLine!==null&&(a===0&&(a=g.oldLine),d++),g.newLine!==null&&(l===0&&(l=g.newLine),c++);const p=a||(d===0?0:1),f=l||(c===0?0:1);return{header:`@@ -${p},${d} +${f},${c} @@`,oldStart:p,oldCount:d,newStart:f,newCount:c,lines:s}})}function Ru(e){return JSON.stringify(Rs(e),null,2)}function Rs(e){if(Array.isArray(e))return e.map(Rs);if(e&&typeof e=="object"){const t=e,n={};for(const o of Object.keys(t).sort())n[o]=Rs(t[o]);return n}return e}const L$={display:"flex",flexDirection:"column",height:"100%",minHeight:0},P$={display:"flex",alignItems:"center",gap:8,padding:"12px 16px",borderBottom:"1px solid #e2e8f0",background:"#f8fafc"},O$={display:"inline-flex",alignItems:"center",gap:4,padding:"4px 10px",borderRadius:6,border:"1px solid #cbd5e1",background:"#ffffff",color:"#0f172a",fontSize:12,fontWeight:600,cursor:"pointer",outline:"none"},B$={marginLeft:"auto",border:"none",background:"transparent",fontSize:16,color:"#64748b",cursor:"pointer",outline:"none"},W$={display:"flex",alignItems:"center",gap:12,padding:"8px 16px",borderBottom:"1px solid #e2e8f0",background:"#ffffff",fontSize:12,fontWeight:600},j$={flex:1,minHeight:0,overflow:"auto",background:"#ffffff",fontFamily:'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace',fontSize:12,lineHeight:1.5},ep={padding:"24px 12px",textAlign:"center",color:"#94a3b8",fontSize:13},H$={padding:"2px 12px",background:"#f1f5f9",color:"#64748b",borderTop:"1px solid #e2e8f0",borderBottom:"1px solid #e2e8f0",userSelect:"none"};function F$(e){return e==="add"?{row:{background:"#e6ffed"},gutter:{background:"#cdffd8",color:"#22863a"},sign:"+",signColor:"#22863a"}:e==="remove"?{row:{background:"#ffeef0"},gutter:{background:"#ffdce0",color:"#b31d28"},sign:"-",signColor:"#b31d28"}:{row:{background:"#ffffff"},gutter:{background:"#f8fafc",color:"#94a3b8"},sign:" ",signColor:"#94a3b8"}}const tp={display:"inline-block",width:44,textAlign:"right",padding:"0 6px",color:"#94a3b8",userSelect:"none",borderRight:"1px solid #e2e8f0",whiteSpace:"nowrap"};function K$({oldVersion:e,newVersion:t,oldContent:n,newContent:o,t:r,onBack:s,onClose:a}){const l=u.useMemo(()=>{const c=Ru(n),p=Ru(o);return N$(c,p)},[n,o]),d=l.addedCount<0;return i.jsxs("div",{className:"g7le-version-diff","data-testid":"g7le-version-diff",style:L$,children:[i.jsxs("div",{"data-modal-drag-handle":!0,style:P$,children:[i.jsxs("button",{type:"button",onClick:s,style:O$,"data-testid":"g7le-version-diff-back",children:["← ",r("layout_editor.version_history.diff_back")]}),i.jsx("span",{style:{fontSize:14,fontWeight:700},"data-testid":"g7le-version-diff-title",children:r("layout_editor.version_history.diff_title",{old:e,new:t})}),i.jsx("button",{type:"button","aria-label":r("layout_editor.version_history.close"),onClick:a,style:B$,"data-testid":"g7le-version-diff-close",children:"✕"})]}),!d&&i.jsxs("div",{style:W$,"data-testid":"g7le-version-diff-summary",children:[i.jsx("span",{style:{color:"#16a34a"},"data-testid":"g7le-version-diff-added",children:r("layout_editor.version_history.added",{count:Math.max(0,l.addedCount)})}),i.jsx("span",{style:{color:"#dc2626"},"data-testid":"g7le-version-diff-removed",children:r("layout_editor.version_history.removed",{count:Math.max(0,l.removedCount)})})]}),i.jsx("div",{style:j$,children:d?i.jsx("div",{style:ep,"data-testid":"g7le-version-diff-too-large",children:r("layout_editor.version_history.diff_too_large")}):l.identical?i.jsx("div",{style:ep,"data-testid":"g7le-version-diff-identical",children:r("layout_editor.version_history.diff_identical")}):l.hunks.map((c,p)=>i.jsxs("div",{"data-testid":`g7le-version-diff-hunk-${p}`,children:[i.jsx("div",{style:H$,"data-testid":`g7le-version-diff-hunk-header-${p}`,children:c.header}),c.lines.map((f,b)=>{const g=F$(f.kind);return i.jsxs("div",{style:{display:"flex",alignItems:"stretch",...g.row},"data-kind":f.kind,"data-testid":`g7le-version-diff-line-${p}-${b}`,children:[i.jsx("span",{style:{...tp,...g.gutter},"aria-hidden":"true",children:f.oldLine??""}),i.jsx("span",{style:{...tp,...g.gutter},"aria-hidden":"true",children:f.newLine??""}),i.jsx("span",{style:{display:"inline-block",width:16,textAlign:"center",color:g.signColor,userSelect:"none",fontWeight:700},"aria-hidden":"true",children:g.sign}),i.jsx("span",{style:{whiteSpace:"pre-wrap",wordBreak:"break-word",flex:1,padding:"0 4px"},children:f.content})]},b)})]},p))})]})}const V$={display:"flex",flexDirection:"column",height:"100%",minHeight:0},U$={display:"flex",alignItems:"center",gap:8,padding:"12px 16px",borderBottom:"1px solid #e2e8f0",background:"#f8fafc"},q$={marginLeft:"auto",border:"none",background:"transparent",fontSize:16,color:"#64748b",cursor:"pointer",outline:"none"},G$={flex:1,minHeight:0,overflowY:"auto",padding:12},np={padding:"24px 12px",textAlign:"center",color:"#94a3b8",fontSize:13},Y$={margin:"8px 16px 0",padding:"8px 12px",borderRadius:6,background:"#fef2f2",color:"#b91c1c",fontSize:12,border:"1px solid #fecaca"},X$={display:"flex",flexDirection:"column",gap:8},J$={display:"flex",alignItems:"center",gap:12,padding:"10px 12px",borderRadius:8,border:"1px solid #e2e8f0",background:"#ffffff"},Z$={display:"inline-flex",alignItems:"center",justifyContent:"center",minWidth:44,padding:"2px 8px",borderRadius:999,background:"#eff6ff",color:"#1d4ed8",fontSize:12,fontWeight:700},Q$={display:"inline-flex",alignItems:"center",padding:"1px 8px",borderRadius:999,background:"#0f172a",color:"#ffffff",fontSize:10,fontWeight:700,letterSpacing:.3},R$={display:"flex",alignItems:"center",gap:10,fontSize:11,fontWeight:600},eE={display:"flex",flexDirection:"column",gap:2,minWidth:0,flex:1},op={padding:"6px 12px",borderRadius:6,border:"1px solid #cbd5e1",background:"#ffffff",color:"#0f172a",fontSize:12,fontWeight:600,cursor:"pointer",outline:"none",whiteSpace:"nowrap"};function rp(e){return e.full_content&&typeof e.full_content=="object"?e.full_content:{endpoint:e.endpoint,components:e.components,data_sources:e.data_sources,metadata:e.metadata}}function tE(e){if(!e)return"";const t=Date.parse(e);if(Number.isNaN(t))return e;try{return new Date(t).toLocaleString()}catch{return e}}function nE({templateIdentifier:e,target:t,t:n,onRestored:o,onClose:r}){const{versions:s,isLoading:a,isLoadingMore:l,error:d,restoringId:c,hasMore:p,loadVersions:f,loadMore:b,restore:g,loadVersionDetail:m}=T$(e,t,o),[h,v]=u.useState(null),[y,_]=u.useState(null),[w,k]=u.useState(null);u.useEffect(()=>{f()},[f]);const x=u.useCallback(async C=>{v(null),k(C.version);try{const T=await m(C.version);if(T.kind!=="success"){v(T.kind==="not_found"?n("layout_editor.version_history.restore_not_found"):T.message);return}const E=C.version-1,A=s.some(D=>D.version===E);let N={};if(A){const D=await m(E);if(D.kind==="success")N=rp(D.detail);else if(D.kind==="network_error"){v(D.message);return}}_({oldVersion:A?E:0,newVersion:C.version,oldContent:N,newContent:rp(T.detail)})}finally{k(null)}},[m,s,n]),$=u.useCallback(async C=>{v(null);const T=n("layout_editor.version_history.restore_confirm");if(!(typeof window<"u"?window.confirm(T):!0))return;const A=await g(C);A.kind==="success"?r():A.kind==="not_found"?v(n("layout_editor.version_history.restore_not_found")):v(A.message)},[g,r,n]),S=h??d;return y?i.jsx(K$,{oldVersion:y.oldVersion,newVersion:y.newVersion,oldContent:y.oldContent,newContent:y.newContent,t:n,onBack:()=>_(null),onClose:r}):i.jsxs("div",{className:"g7le-version-history","data-testid":"g7le-version-history",style:V$,children:[i.jsxs("div",{"data-modal-drag-handle":!0,style:U$,children:[i.jsxs("span",{style:{fontSize:14,fontWeight:700},"data-testid":"g7le-version-history-title",children:["🕘 ",n("layout_editor.version_history.title")]}),i.jsx("button",{type:"button","aria-label":n("layout_editor.version_history.close"),onClick:r,style:q$,"data-testid":"g7le-version-history-close",children:"✕"})]}),S&&i.jsx("div",{style:Y$,"data-testid":"g7le-version-history-error",children:S}),i.jsx("div",{style:G$,children:a?i.jsx("div",{style:np,"data-testid":"g7le-version-history-loading",children:n("layout_editor.version_history.loading")}):s.length===0?i.jsx("div",{style:np,"data-testid":"g7le-version-history-empty",children:n("layout_editor.version_history.empty")}):i.jsxs("div",{"data-testid":"g7le-version-history-list",style:X$,children:[s.map((C,T)=>{const E=C.changes_summary,A=E?.added_count??0,N=E?.removed_count??0,D=E?.char_diff??0,B=tE(C.created_at),M=c===C.id,O=T===0;return i.jsxs("div",{style:J$,"data-testid":`g7le-version-row-${C.id}`,children:[i.jsxs("span",{style:Z$,"data-testid":`g7le-version-badge-${C.id}`,children:["v",C.version]}),i.jsxs("div",{style:eE,children:[i.jsxs("span",{style:{display:"flex",alignItems:"center",gap:8},children:[B&&i.jsx("span",{style:{fontSize:12,color:"#475569",fontWeight:600},children:B}),O&&i.jsx("span",{style:Q$,"data-testid":`g7le-version-latest-${C.id}`,children:n("layout_editor.version_history.latest")})]}),i.jsxs("span",{style:R$,"data-testid":`g7le-version-summary-${C.id}`,children:[i.jsx("span",{style:{color:"#16a34a"},"data-testid":`g7le-version-added-${C.id}`,children:n("layout_editor.version_history.added",{count:A})}),i.jsx("span",{style:{color:"#dc2626"},"data-testid":`g7le-version-removed-${C.id}`,children:n("layout_editor.version_history.removed",{count:N})}),i.jsx("span",{style:{color:"#94a3b8",fontWeight:500},"data-testid":`g7le-version-chars-${C.id}`,children:n("layout_editor.version_history.chars",{diff:`${D>=0?"+":""}${D}`})})]}),i.jsx("span",{style:{fontSize:11,color:"#94a3b8"},"data-testid":`g7le-version-author-${C.id}`,children:n("layout_editor.version_history.saved_by",{name:C.created_by_name??n("layout_editor.version_history.unknown_author")})})]}),i.jsxs("div",{style:{display:"flex",alignItems:"center",gap:6},children:[i.jsx("button",{type:"button",onClick:()=>{x(C)},disabled:w!==null,style:{...op,background:"#f8fafc",opacity:w!==null?.6:1},"data-testid":`g7le-version-compare-${C.id}`,children:w===C.version?n("layout_editor.version_history.comparing"):n("layout_editor.version_history.compare")}),!O&&i.jsx("button",{type:"button",onClick:()=>{$(C.id)},disabled:M||c!==null,style:{...op,opacity:M||c!==null?.6:1},"data-testid":`g7le-version-restore-${C.id}`,children:n(M?"layout_editor.version_history.restoring":"layout_editor.version_history.restore")})]})]},C.id)}),p&&i.jsx("button",{type:"button","data-testid":"g7le-version-history-load-more",style:oE,disabled:l,onClick:()=>{b()},children:n(l?"layout_editor.version_history.loading":"layout_editor.version_history.load_more")})]})})]})}const oE={width:"100%",marginTop:8,padding:"8px 12px",fontSize:12,color:"#475569",background:"#f8fafc",border:"1px solid #e2e8f0",borderRadius:6,cursor:"pointer"};function Po(e){return`/api/admin/extensions/${encodeURIComponent(e.type)}/${encodeURIComponent(e.identifier)}/custom-assets`}function Oo(e,t){const n=e?.message;return typeof n=="string"&&n!==""?n:`HTTP ${t}`}function rE(e){const[t,n]=u.useState([]),[o,r]=u.useState(null),[s,a]=u.useState(!1),[l,d]=u.useState(!1),[c,p]=u.useState(null),f=u.useCallback(async()=>{a(!0),p(null);try{const v=await fetch(Po(e),{credentials:"same-origin",headers:Ne()}),y=await v.json().catch(()=>null);if(v.status===401||v.status===403){d(!0),n([]);return}if(d(!1),!v.ok){p(Oo(y,v.status)),n([]);return}const _=y?.data??{},w=_.files;n(Array.isArray(w)?w:[]),r({editable_extensions:_.editable_extensions??[],uploadable_extensions:_.uploadable_extensions??[],max_text_bytes:_.max_text_bytes??0,max_upload_bytes:_.max_upload_bytes??0})}catch(v){p(v instanceof Error?v.message:"network error"),n([])}finally{a(!1)}},[e.type,e.identifier]),b=u.useCallback(async v=>{p(null);try{const y=`${Po(e)}/content?path=${encodeURIComponent(v)}`,_=await fetch(y,{credentials:"same-origin",headers:Ne()}),w=await _.json().catch(()=>null);if(!_.ok)return p(Oo(w,_.status)),null;const k=w?.data?.content;return typeof k=="string"?k:""}catch(y){return p(y instanceof Error?y.message:"network error"),null}},[e.type,e.identifier]),g=u.useCallback(async(v,y)=>{p(null);try{const _=await fetch(`${Po(e)}/content`,{method:"PUT",credentials:"same-origin",headers:Ne({"Content-Type":"application/json"}),body:JSON.stringify({path:v,content:y})}),w=await _.json().catch(()=>null);return _.ok?(await f(),!0):(p(Oo(w,_.status)),!1)}catch(_){return p(_ instanceof Error?_.message:"network error"),!1}},[e.type,e.identifier,f]),m=u.useCallback(async(v,y)=>{p(null);try{const _=new FormData;_.append("file",v),y&&_.append("directory",y);const w=await fetch(`${Po(e)}/upload`,{method:"POST",credentials:"same-origin",headers:Ne(),body:_}),k=await w.json().catch(()=>null);return w.ok?(await f(),!0):(p(Oo(k,w.status)),!1)}catch(_){return p(_ instanceof Error?_.message:"network error"),!1}},[e.type,e.identifier,f]),h=u.useCallback(async v=>{p(null);try{const y=`${Po(e)}?path=${encodeURIComponent(v)}`,_=await fetch(y,{method:"DELETE",credentials:"same-origin",headers:Ne()}),w=await _.json().catch(()=>null);return _.ok?(await f(),!0):(p(Oo(w,_.status)),!1)}catch(y){return p(y instanceof Error?y.message:"network error"),!1}},[e.type,e.identifier,f]);return{files:t,meta:o,isLoading:s,forbidden:l,error:c,reload:f,read:b,save:g,upload:m,remove:h}}const iE={display:"flex",flexDirection:"column",height:"100%",minHeight:0},sE={display:"flex",alignItems:"center",gap:8,padding:"12px 16px",borderBottom:"1px solid #e2e8f0",background:"#f8fafc",fontWeight:600,color:"#0f172a"},aE={marginLeft:"auto",border:"none",background:"transparent",fontSize:16,color:"#64748b",cursor:"pointer",outline:"none"},lE={flex:1,minHeight:0,display:"flex"},dE={width:260,flexShrink:0,borderRight:"1px solid #e2e8f0",overflowY:"auto",padding:8},cE={flex:1,minWidth:0,display:"flex",flexDirection:"column",padding:12,gap:8},ip={display:"flex",alignItems:"center",gap:6,width:"100%",padding:"6px 8px",borderRadius:6,borderWidth:1,borderStyle:"solid",borderColor:"transparent",backgroundColor:"transparent",fontSize:12,color:"#334155",cursor:"pointer",textAlign:"left"},uE={...ip,backgroundColor:"#eff6ff",borderColor:"#bfdbfe",color:"#1d4ed8",fontWeight:600},pE={margin:16,padding:16,borderRadius:8,border:"1px solid #fcd34d",background:"#fffbeb",color:"#92400e",fontSize:13,lineHeight:1.7},fE={padding:"8px 12px",borderRadius:6,border:"1px solid #fecaca",background:"#fef2f2",color:"#b91c1c",fontSize:12},gE={flex:1,minHeight:220,width:"100%",boxSizing:"border-box",padding:10,borderRadius:6,borderWidth:1,borderStyle:"solid",borderColor:"#cbd5e1",fontFamily:"ui-monospace, SFMono-Regular, Menlo, Consolas, monospace",fontSize:12,lineHeight:1.6,resize:"vertical"},sp={display:"flex",alignItems:"center",gap:8,flexWrap:"wrap"},io={padding:"6px 12px",borderRadius:6,borderWidth:1,borderStyle:"solid",borderColor:"#cbd5e1",backgroundColor:"#ffffff",color:"#334155",fontSize:12,cursor:"pointer"},hE={...io,backgroundColor:"#2563eb",borderColor:"#2563eb",color:"#ffffff",fontWeight:600},yE={...io,borderColor:"#fca5a5",color:"#b91c1c"},bE={...io,cursor:"text",minWidth:200},mE={...io,cursor:"pointer",minWidth:220};function vE(e,t){if(typeof e=="string"&&e!=="")return e;if(e&&typeof e=="object"){const n=e,o=window?.G7Config?.locale??"ko",r=n[o]??n.ko??n.en??Object.values(n)[0];if(typeof r=="string"&&r!=="")return r}return t}function _E(e){const t=(typeof window<"u"?window.G7Config:null)??{},n=[{type:"template",identifier:e,label:e}];for(const[o,r]of[["module",t.activeModules],["plugin",t.activePlugins]])if(Array.isArray(r))for(const s of r){const a=typeof s?.identifier=="string"?s.identifier:"";a&&n.push({type:o,identifier:a,label:vE(s?.display_name,a)})}return n}function xE(e){return e<1024?`${e} B`:e<1024*1024?`${Math.round(e/1024)} KB`:`${(e/(1024*1024)).toFixed(1)} MB`}function wE(e){const{templateIdentifier:t,t:n,onClose:o}=e,r=u.useMemo(()=>_E(t),[t]),[s,a]=u.useState(`template:${t}`),l=u.useMemo(()=>r.find(S=>`${S.type}:${S.identifier}`===s)??r[0],[r,s]),d=rE({type:l.type,identifier:l.identifier}),{reload:c}=d,[p,f]=u.useState(null),[b,g]=u.useState(""),[m,h]=u.useState(""),[v,y]=u.useState(!1),_=u.useRef(null);u.useEffect(()=>{c()},[c]),u.useEffect(()=>{f(null),g(""),h("")},[s]);const w=u.useCallback(async S=>{if(f(S),!S.editable){g("");return}y(!0);const C=await d.read(S.path);y(!1),g(C??"")},[d]),k=u.useCallback(async()=>{const S=p?.path??m.trim();if(!S)return;y(!0);const C=await d.save(S,b);y(!1),C&&!p&&(h(""),f({path:S,name:S.split("/").pop()??S,extension:(S.split(".").pop()??"").toLowerCase(),size:b.length,modified_at:new Date().toISOString(),editable:!0,loaded:!0}))},[d,p,m,b]),x=u.useCallback(async()=>{if(!p||typeof window<"u"&&!window.confirm(n("layout_editor.custom_assets.delete_confirm")))return;y(!0);const S=await d.remove(p.path);y(!1),S&&(f(null),g(""))},[d,p,n]),$=u.useCallback(async S=>{const C=S.target.files?.[0];C&&(y(!0),await d.upload(C),y(!1),_.current&&(_.current.value=""))},[d]);return i.jsxs("div",{className:"g7le-custom-assets",style:iE,"data-testid":"g7le-custom-assets-modal",children:[i.jsxs("div",{style:sE,children:[i.jsxs("span",{children:["🎨 ",n("layout_editor.custom_assets.title")]}),i.jsx("select",{value:s,onChange:S=>a(S.target.value),style:mE,"aria-label":n("layout_editor.custom_assets.target_label"),"data-testid":"g7le-custom-assets-target",children:r.map(S=>i.jsxs("option",{value:`${S.type}:${S.identifier}`,children:[n(`layout_editor.custom_assets.type_${S.type}`)," · ",S.label]},`${S.type}:${S.identifier}`))}),i.jsx("button",{type:"button",style:aE,onClick:o,"aria-label":n("layout_editor.custom_assets.close"),"data-testid":"g7le-custom-assets-close",children:"✕"})]}),d.forbidden?i.jsx("div",{style:pE,"data-testid":"g7le-custom-assets-forbidden",children:n("layout_editor.custom_assets.forbidden")}):i.jsxs("div",{style:lE,children:[i.jsxs("div",{style:dE,"data-testid":"g7le-custom-assets-list",children:[d.isLoading&&d.files.length===0?i.jsx("div",{style:{padding:12,color:"#94a3b8",fontSize:12},children:n("layout_editor.custom_assets.loading")}):null,!d.isLoading&&d.files.length===0?i.jsx("div",{style:{padding:12,color:"#94a3b8",fontSize:12},children:n("layout_editor.custom_assets.empty")}):null,d.files.map(S=>i.jsxs("button",{type:"button",style:p?.path===S.path?uE:ip,onClick:()=>{w(S)},"data-testid":`g7le-custom-asset-row-${S.path}`,"data-loaded":S.loaded?"true":"false",children:[i.jsx("span",{style:{flex:1,minWidth:0,overflow:"hidden",textOverflow:"ellipsis"},children:S.path}),i.jsx("span",{style:{color:"#94a3b8",fontSize:11},children:xE(S.size)})]},S.path))]}),i.jsxs("div",{style:cE,children:[d.error?i.jsx("div",{style:fE,"data-testid":"g7le-custom-assets-error",children:d.error}):null,i.jsxs("div",{style:sp,children:[i.jsx("input",{type:"text",value:p?p.path:m,readOnly:!!p,onChange:S=>h(S.target.value),placeholder:n("layout_editor.custom_assets.new_path_placeholder"),style:bE,"aria-label":n("layout_editor.custom_assets.path_label"),"data-testid":"g7le-custom-assets-path"}),p?i.jsx("button",{type:"button",style:io,onClick:()=>{f(null),g("")},"data-testid":"g7le-custom-assets-new",children:n("layout_editor.custom_assets.new_file")}):null]}),p&&!p.editable?i.jsx("div",{style:{color:"#64748b",fontSize:12,lineHeight:1.7},children:n("layout_editor.custom_assets.binary_hint",{extension:p.extension})}):i.jsx("textarea",{value:b,onChange:S=>g(S.target.value),style:gE,spellCheck:!1,"aria-label":n("layout_editor.custom_assets.content_label"),"data-testid":"g7le-custom-assets-content"}),i.jsxs("div",{style:sp,children:[i.jsx("button",{type:"button",style:hE,disabled:v||(p?!p.editable:m.trim()===""),onClick:()=>{k()},"data-testid":"g7le-custom-assets-save",children:n("layout_editor.custom_assets.save")}),i.jsx("button",{type:"button",style:io,disabled:v,onClick:()=>_.current?.click(),"data-testid":"g7le-custom-assets-upload",children:n("layout_editor.custom_assets.upload")}),i.jsx("input",{ref:_,type:"file",style:{display:"none"},onChange:S=>{$(S)},"data-testid":"g7le-custom-assets-file-input"}),i.jsx("button",{type:"button",style:yE,disabled:v||!p,onClick:()=>{x()},"data-testid":"g7le-custom-assets-delete",children:n("layout_editor.custom_assets.delete")}),i.jsx("span",{style:{color:"#94a3b8",fontSize:11},children:n("layout_editor.custom_assets.apply_hint")})]})]})]})]})}const ap=[{id:"copy",group:"clipboard",combos:[{key:"c",mod:!0}],labelKey:"layout_editor.shortcuts.action.copy",requiresSelection:!0},{id:"cut",group:"clipboard",combos:[{key:"x",mod:!0}],labelKey:"layout_editor.shortcuts.action.cut",requiresSelection:!0},{id:"paste",group:"clipboard",combos:[{key:"v",mod:!0}],labelKey:"layout_editor.shortcuts.action.paste"},{id:"openProps",group:"element",combos:[{key:"enter"}],labelKey:"layout_editor.shortcuts.action.open_props",requiresSelection:!0},{id:"delete",group:"element",combos:[{key:"delete"},{key:"backspace"}],labelKey:"layout_editor.shortcuts.action.delete",requiresSelection:!0},{id:"selectParent",group:"element",combos:[{key:"arrowup"}],labelKey:"layout_editor.shortcuts.action.select_parent",requiresSelection:!0},{id:"deselect",group:"element",combos:[{key:"escape"}],labelKey:"layout_editor.shortcuts.action.deselect"},{id:"addElement",group:"element",combos:[{key:"/",mod:!0}],labelKey:"layout_editor.shortcuts.action.add_element"},{id:"undo",group:"history",combos:[{key:"z",mod:!0}],labelKey:"layout_editor.shortcuts.action.undo"},{id:"redo",group:"history",combos:[{key:"z",mod:!0,shift:!0},{key:"y",mod:!0}],labelKey:"layout_editor.shortcuts.action.redo"},{id:"editCode",group:"view",combos:[{key:"e",mod:!0}],labelKey:"layout_editor.shortcuts.action.edit_code"},{id:"preview",group:"view",combos:[{key:"p",mod:!0}],labelKey:"layout_editor.shortcuts.action.preview"},{id:"translations",group:"view",combos:[{key:"l",mod:!0}],labelKey:"layout_editor.shortcuts.action.translations"},{id:"help",group:"view",combos:[{key:"?"}],labelKey:"layout_editor.shortcuts.action.help"},{id:"reset",group:"document",combos:[{key:"r",mod:!0,shift:!0}],labelKey:"layout_editor.shortcuts.action.reset"},{id:"save",group:"document",combos:[{key:"s",mod:!0}],labelKey:"layout_editor.shortcuts.action.save"},{id:"exit",group:"document",combos:[{key:"escape"}],labelKey:"layout_editor.shortcuts.action.exit"}];function kE(e){return{key:(e.key||"").toLowerCase(),mod:e.ctrlKey||e.metaKey,shift:e.shiftKey,alt:e.altKey}}function SE(e,t){return e.key===t.key&&!!e.mod==!!t.mod&&!!e.shift==!!t.shift&&!!e.alt==!!t.alt}function CE(e){for(const t of ap)if(t.combos.some(n=>SE(n,e)))return t;return null}function $E(e,t){const n=[];return e.mod&&n.push(t?"⌘":"Ctrl"),e.shift&&n.push(t?"⇧":"Shift"),e.alt&&n.push(t?"⌥":"Alt"),n.push(EE(e.key)),n.join(" + ")}function EE(e){return{arrowup:"↑",arrowdown:"↓",arrowleft:"←",arrowright:"→",enter:"Enter",escape:"Esc",delete:"Delete",backspace:"Backspace"," ":"Space"}[e]??(e.length===1?e.toUpperCase():e)}const IE=["clipboard","element","history","view","document"],AE={clipboard:"layout_editor.shortcuts.group.clipboard",element:"layout_editor.shortcuts.group.element",history:"layout_editor.shortcuts.group.history",view:"layout_editor.shortcuts.group.view",document:"layout_editor.shortcuts.group.document"};function TE(){return typeof navigator>"u"?!1:/mac|iphone|ipad|ipod/i.test(navigator.platform||navigator.userAgent||"")}function DE({t:e,onClose:t,isMac:n}){const o=n??TE(),r=u.useMemo(()=>{const s=new Map;for(const a of ap){const l=s.get(a.group)??[];l.push(a),s.set(a.group,l)}return s},[]);return i.jsxs("div",{className:"g7le-shortcut-help","data-testid":"g7le-shortcut-help",style:zE,children:[i.jsxs("div",{style:NE,children:[i.jsx("span",{style:ME,children:e("layout_editor.shortcuts.title")}),i.jsx("button",{type:"button","data-testid":"g7le-shortcut-help-close",onClick:t,"aria-label":e("layout_editor.shortcuts.close"),style:LE,children:"✕"})]}),i.jsx("div",{style:PE,children:IE.map(s=>{const a=r.get(s);return!a||a.length===0?null:i.jsxs("div",{style:OE,"data-testid":`g7le-shortcut-group-${s}`,children:[i.jsx("div",{style:BE,children:e(AE[s])}),i.jsx("table",{style:WE,children:i.jsx("tbody",{children:a.map(l=>i.jsxs("tr",{"data-testid":`g7le-shortcut-row-${l.id}`,children:[i.jsx("td",{style:jE,children:e(l.labelKey)}),i.jsx("td",{style:HE,children:l.combos.map((d,c)=>i.jsxs(u.Fragment,{children:[c>0&&i.jsx("span",{style:KE,children:e("layout_editor.shortcuts.or")}),i.jsx("kbd",{style:FE,children:$E(d,o)})]},c))})]},l.id))})})]},s)})}),i.jsx("div",{style:VE,children:e("layout_editor.shortcuts.footnote")})]})}const zE={display:"flex",flexDirection:"column",padding:20,gap:16,width:"100%",boxSizing:"border-box"},NE={display:"flex",alignItems:"center",justifyContent:"space-between",paddingBottom:12,borderBottom:"1px solid #e2e8f0"},ME={fontSize:15,fontWeight:700,color:"#0f172a"},LE={width:28,height:28,border:"none",background:"transparent",color:"#64748b",cursor:"pointer",fontSize:14,borderRadius:6},PE={display:"grid",gridTemplateColumns:"1fr 1fr",columnGap:28,rowGap:18,alignItems:"start"},OE={display:"flex",flexDirection:"column",gap:6},BE={fontSize:12,fontWeight:700,color:"#2563eb"},WE={width:"100%",borderCollapse:"collapse"},jE={padding:"4px 0",fontSize:12.5,color:"#334155",textAlign:"left",whiteSpace:"nowrap",verticalAlign:"middle"},HE={padding:"4px 0",paddingLeft:12,textAlign:"right",verticalAlign:"middle"},FE={display:"inline-block",padding:"1px 6px",fontSize:11,fontFamily:"ui-monospace, monospace",color:"#0f172a",background:"#f1f5f9",border:"1px solid #cbd5e1",borderBottomWidth:2,borderRadius:5,whiteSpace:"nowrap"},KE={fontSize:11,color:"#94a3b8",margin:"0 4px"},VE={fontSize:11,color:"#94a3b8",paddingTop:12,borderTop:"1px solid #f1f5f9"};function UE(e){if(!e||!(e instanceof HTMLElement))return!1;const t=e.tagName;return!!(t==="INPUT"||t==="TEXTAREA"||t==="SELECT"||e.isContentEditable)}function qE(){return typeof document>"u"?!1:!!document.querySelector(".g7le-modal-backdrop, .g7le-modal")}function lp(e){const{handlers:t,hasSelection:n,enabled:o=!0}=e;u.useEffect(()=>{if(!o||typeof window>"u")return;const r=s=>{if(UE(s.target))return;const a=kE(s),l=CE(a);if(!l||qE()||l.requiresSelection&&!n)return;if(l.key==="escape"||a.key==="escape"){if(n){const c=t.deselect;c&&(s.preventDefault(),c())}else{const c=t.exit;c&&(s.preventDefault(),c())}return}const d=t[l.id];d&&(s.preventDefault(),d())};return window.addEventListener("keydown",r),()=>window.removeEventListener("keydown",r)},[t,n,o])}function GE(e){const{result:t,onDismiss:n,onLoadLatest:o,onKeepMyChanges:r}=e,{t:s}=tt();if(u.useEffect(()=>{if(!t||t.kind==="concurrent_modification"||t.kind==="validation_failed")return;const a=setTimeout(n,5e3);return()=>clearTimeout(a)},[t,n]),!t)return null;if(t.kind==="success")return i.jsx(Bo,{tone:"success",testId:"g7le-save-banner-success",onDismiss:n,children:s("layout_editor.save.success")});if(t.kind==="validation_failed"){const a=t.errors??{},l=Object.entries(a);return i.jsxs(Bo,{tone:"error",testId:"g7le-save-banner-validation",onDismiss:n,children:[i.jsx("strong",{children:s("layout_editor.save.validation_failed")}),l.length>0&&i.jsx("ul",{style:dp,"data-testid":"g7le-save-banner-validation-errors",children:l.map(([d,c])=>i.jsxs("li",{children:[i.jsx("code",{style:cp,children:d}),": ",c.join(", ")]},d))})]})}return t.kind==="blocked_inactive_extension"?i.jsxs(Bo,{tone:"warning",testId:"g7le-save-banner-blocked",onDismiss:n,children:[i.jsx("strong",{children:s("layout_editor.save.blocked_inactive_extension")}),t.blockedPaths.length>0&&i.jsx("ul",{style:dp,"data-testid":"g7le-save-banner-blocked-paths",children:t.blockedPaths.map(a=>i.jsx("li",{children:i.jsx("code",{style:cp,children:a})},a))})]}):t.kind==="network_error"?i.jsxs(Bo,{tone:"error",testId:"g7le-save-banner-network",onDismiss:n,children:[i.jsx("strong",{children:s("layout_editor.save.network_error_title")}),i.jsx("div",{style:{marginTop:4,fontSize:12},children:t.message})]}):t.kind==="guard_no_document"?i.jsx(Bo,{tone:"neutral",testId:"g7le-save-banner-guard-no-document",onDismiss:n,children:s("layout_editor.save.guard_no_document")}):i.jsx("div",{role:"dialog","aria-modal":"true","aria-label":s("layout_editor.save.concurrent.title"),"data-testid":"g7le-save-banner-concurrent",style:ZE,children:i.jsxs("div",{style:QE,children:[i.jsx("h2",{style:RE,children:s("layout_editor.save.concurrent.title")}),i.jsx("p",{style:e7,children:s("layout_editor.save.concurrent.message")}),i.jsx("div",{style:t7,children:s("layout_editor.save.concurrent.version_info",{current:String(t.currentVersion),yours:String(t.yourVersion)})}),i.jsxs("div",{style:n7,children:[i.jsx("button",{type:"button","data-testid":"g7le-save-banner-concurrent-load-latest",onClick:()=>{o?.(),n()},style:o7,children:s("layout_editor.save.concurrent.load_latest")}),i.jsx("button",{type:"button","data-testid":"g7le-save-banner-concurrent-keep-mine",onClick:()=>{r?.(),n()},style:r7,children:s("layout_editor.save.concurrent.view_my_changes")}),i.jsx("button",{type:"button","data-testid":"g7le-save-banner-concurrent-cancel",onClick:n,style:i7,children:s("layout_editor.save.concurrent.cancel")})]})]})})}function Bo(e){const{tone:t,testId:n,onDismiss:o,children:r}=e,s=YE[t];return i.jsxs("div",{role:"status","data-testid":n,style:{...XE,background:s.bg,borderColor:s.border,color:s.fg},children:[i.jsx("div",{style:{flex:1,minWidth:0},children:r}),i.jsx("button",{type:"button",onClick:o,"aria-label":"close","data-testid":`${n}-close`,style:JE,children:"×"})]})}const YE={success:{bg:"#dcfce7",border:"#86efac",fg:"#14532d"},error:{bg:"#fee2e2",border:"#fca5a5",fg:"#7f1d1d"},warning:{bg:"#fef3c7",border:"#fcd34d",fg:"#78350f"},neutral:{bg:"#f1f5f9",border:"#cbd5e1",fg:"#334155"}},XE={display:"flex",alignItems:"flex-start",gap:12,padding:"12px 16px",border:"1px solid",borderRadius:0,fontSize:13,lineHeight:1.5},JE={background:"transparent",border:"none",fontSize:18,cursor:"pointer",lineHeight:1,color:"inherit",padding:"0 4px",flexShrink:0},dp={margin:"8px 0 0",paddingLeft:20,fontSize:12},cp={background:"rgba(0, 0, 0, 0.06)",padding:"1px 4px",borderRadius:3,fontFamily:"monospace"},ZE={position:"fixed",inset:0,background:"rgba(15, 23, 42, 0.6)",display:"flex",alignItems:"center",justifyContent:"center",zIndex:1e4},QE={background:"#fff",borderRadius:8,padding:24,width:"min(480px, 90vw)",boxShadow:"0 10px 25px rgba(0,0,0,0.15)"},RE={margin:"0 0 8px",fontSize:16,fontWeight:600,color:"#0f172a"},e7={margin:"0 0 12px",color:"#334155",fontSize:13,lineHeight:1.5},t7={marginBottom:16,padding:"8px 12px",background:"#f8fafc",borderRadius:6,fontSize:12,color:"#64748b",fontFamily:"monospace"},n7={display:"flex",gap:8,justifyContent:"flex-end"},o7={padding:"8px 14px",background:"#2563eb",color:"#fff",border:"none",borderRadius:6,fontSize:13,fontWeight:600,cursor:"pointer"},r7={padding:"8px 14px",background:"#fff",color:"#334155",border:"1px solid #cbd5e1",borderRadius:6,fontSize:13,fontWeight:500,cursor:"pointer"},i7={padding:"8px 14px",background:"transparent",color:"#64748b",border:"none",borderRadius:6,fontSize:13,cursor:"pointer"};function up(e){if(!e)return null;const n=new URLSearchParams(e).get("route");return n&&n.length>0?n:null}function ea(e,t,n){const o=`/admin/layout-editor/${encodeURIComponent(e)}`;if(!t||t.length===0)return o;if(t.startsWith("__")){let r=`${o}?edit=${encodeURIComponent(t)}`;return(t.startsWith("__modal__/")||t.startsWith("__iteration__/"))&&n&&(r+=`&host=${encodeURIComponent(n)}`),r}return`${o}?route=${encodeURIComponent(t)}`}function pp(e){if(!e)return null;const n=new URLSearchParams(e).get("host");return n&&n.length>0?n:null}function fp(e){if(!e)return null;const n=new URLSearchParams(e).get("edit");return n&&n.length>0&&n.startsWith("__")?n:null}function s7(e,t){const n=`/admin/templates/${encodeURIComponent(e)}/edit`;if(!t||t.length===0||t.startsWith("__"))return n;const o=t.replace(/^\*+/,"");return o.length===0?n:`${n}?route=${encodeURIComponent(o)}`}const a7="g7le-editor-route",l7=new Set;function ta(e){return e.path.startsWith("__group__/")||e.path.startsWith("__conngroup__/")}function gp(e,t){const{t:n,translationEngine:o,translationContext:r,templateIdentifier:s}=t,a=ta(e);if(!e.label.startsWith("$t:"))return e.label;const l=e.label.slice(3),d=l.indexOf("|"),c=d===-1?l:l.slice(0,d),p={};if(d!==-1)for(const b of l.slice(d+1).split("|")){const g=b.indexOf("=");g!==-1&&(p[b.slice(0,g)]=b.slice(g+1))}if((e.kind==="route"||e.kind==="modal")&&!a&&o&&r&&s!==r.templateId){const b={templateId:s,locale:r.locale},g=Object.keys(p).length>0?"|"+Object.entries(p).map(([h,v])=>`${h}=${v}`).join("|"):void 0,m=o.translate(c,b,g);if(m!==c)return m}const f=n(c,p);if(f===c&&typeof window<"u"){const b=window.G7Core?.t;if(typeof b=="function")try{const g=b(c,p);if(g!==c)return g}catch{}}if(f===c&&e.kind==="route"&&!a){const b=e.path;if(b&&b!=="/"){const g=b.split("/").filter(Boolean);return g[g.length-1]??b}return b==="/"?"/":c}return f}function d7(e){return e.trim().toLocaleLowerCase()}function hp(e,t){if(!t)return e;const n=e.toLocaleLowerCase(),o=[];let r=0,s=n.indexOf(t,r);if(s===-1)return e;let a=0;for(;s!==-1;)s>r&&o.push(e.slice(r,s)),o.push(i.jsx("mark",{className:"g7le-route-tree__highlight",style:{background:"#fde68a",color:"inherit",borderRadius:2,padding:"0 1px"},children:e.slice(s,s+t.length)},`m${a++}`)),r=s+t.length,s=n.indexOf(t,r);return r{const z=f.selectedRoute?.path??"";return z.startsWith("__extension__/")||z.startsWith("__modal__/")?z.slice(z.lastIndexOf("/")+1):null},[f.selectedRoute?.path]),h=u.useMemo(()=>!g||!m?!1:(e.children??[]).some(z=>z.kind==="extension"&&z.extensionId===m||z.kind==="modal"&&z.modalId===m),[g,m,e.children]),[v,y]=u.useState(!0);u.useEffect(()=>{h&&y(!1)},[h]);const _=f.editMode==="extension"?f.selectedRoute?.layoutName??null:null,w=e.kind==="extension"?bp(e,f.routeTree)??null:null,k=e.kind==="extension"&&o===e.path&&(_===null||w===null||_===w),x=f.editMode==="modal"&&o?.startsWith("__modal__/")?o.slice(o.lastIndexOf("/")+1):null,$=f.editMode==="modal"?f.selectedRoute?.layoutName??null:null,S=e.kind==="modal"&&!!x&&e.modalId===x&&($===null||e.modalHostLayout==null||$===e.modalHostLayout),C=e.kind!=="extension"&&e.kind!=="modal"&&o===e.path||k||S||e.kind==="route"&&!b&&r===e.path,E=(e.children?.length??0)>0&&(!g||l||!v),A=!b&&!!e.layoutName&&s.has(e.layoutName),N=b?void 0:e.kind==="extension"?e.extensionId?f.extensionVersions[e.extensionId]:void 0:e.layoutName?f.layoutVersions[e.layoutName]:void 0,D=()=>{if(g){y(z=>!z);return}b||n(e)},B=gp(e,{t:d,translationEngine:c,translationContext:p,templateIdentifier:f.templateIdentifier}),M=b?null:c7(e),O={paddingLeft:12+t*14,paddingRight:12,paddingTop:4,paddingBottom:4,opacity:e.isHidden?.55:1,cursor:g?"pointer":b?"default":"pointer",background:C?"#eff6ff":"transparent",borderLeft:C?"2px solid #2563eb":"2px solid transparent",fontWeight:b||C?600:400,fontSize:b?11:13,color:b?"#64748b":C?"#1d4ed8":"#1e293b",textTransform:b?"uppercase":"none",letterSpacing:b?.6:void 0,transition:"background-color 100ms, color 100ms",display:"flex",flexDirection:"column",alignItems:"stretch",gap:2,userSelect:"none"};return i.jsxs("li",{children:[i.jsxs("div",{"data-testid":b?"g7le-route-tree-group":"g7le-route-tree-item","data-route-path":e.path,"data-route-kind":e.kind,"data-conn-collapsed":g?l?"false":String(v):void 0,role:g||!b?"button":void 0,tabIndex:g||!b?0:void 0,"aria-expanded":g?l?!0:!v:void 0,onClick:D,onKeyDown:z=>{g&&(z.key==="Enter"||z.key===" ")?(z.preventDefault(),y(H=>!H)):!b&&(z.key==="Enter"||z.key===" ")&&(z.preventDefault(),n(e))},style:O,children:[i.jsxs("span",{style:{display:"flex",alignItems:"center",gap:6,minWidth:0},children:[g&&i.jsx("span",{"aria-hidden":"true",style:{flex:"0 0 auto",fontSize:9,color:"#94a3b8",width:10,display:"inline-block"},children:l||!v?"▾":"▸"}),A&&i.jsx("span",{className:"g7le-route-tree__dirty-dot","data-testid":"g7le-route-tree-dirty",title:d("layout_editor.chrome.route_tree.badge.modified"),"aria-label":d("layout_editor.chrome.route_tree.badge.modified"),style:{width:7,height:7,borderRadius:"50%",background:"#f59e0b",flex:"0 0 auto"}}),b?B:hp(B,a),typeof N=="number"&&i.jsxs("span",{className:"g7le-route-tree__version-badge","data-testid":"g7le-route-tree-version",title:d("layout_editor.chrome.route_tree.badge.version_tooltip",{version:String(N)}),style:{flex:"0 0 auto",padding:"0 5px",fontSize:10,fontWeight:600,lineHeight:"16px",borderRadius:4,background:"#eef2ff",color:"#4f46e5",border:"1px solid #e0e7ff",letterSpacing:.2},children:["v",N]}),i.jsx(u7,{node:e})]}),M&&i.jsx("span",{className:"g7le-route-tree__layout-path","data-testid":"g7le-route-tree-layout-path",title:M,style:{fontSize:11,fontWeight:400,color:"#94a3b8",textTransform:"none",letterSpacing:0,overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap"},children:hp(M,a)})]}),E&&i.jsx("ul",{style:{listStyle:"none",margin:0,padding:0},children:e.children.map(z=>i.jsx(yp,{node:z,depth:t+1,onSelect:n,selectedRoutePath:o,activeHostRoutePath:r,dirtyLayoutNames:s,query:a,forceExpand:l},z.path))})]})}function p7(e,t){if(!t||!t.startsWith("__"))return null;const n=t.startsWith("__modal__/"),o=t.startsWith("__extension__/");if(!n&&!o)return null;const r=t.slice(t.lastIndexOf("/")+1),s=a=>{for(const l of a){if(l.connectedHostRoutePath&&(n&&l.kind==="modal"&&l.modalId===r||o&&l.kind==="extension"&&l.extensionId===r))return l.connectedHostRoutePath;if(l.children&&l.children.length>0){const d=s(l.children);if(d)return d}}return null};return s(e)}function bp(e,t){if(e.connectedHostRoutePath){const n=mp(t,e.connectedHostRoutePath);if(n?.layoutName)return n.layoutName}if(e.extensionType==="overlay"&&e.extensionTargetName)return e.extensionTargetName}function mp(e,t){for(const n of e){if(n.kind==="route"&&n.path===t)return n;if(n.children&&n.children.length>0){const o=mp(n.children,t);if(o)return o}}return null}function vp(e,t,n){const o=[];for(const r of e){const s=ta(r),a=r.children&&r.children.length>0?vp(r.children,t,n):[];if(s){a.length>0&&o.push({...r,children:a});continue}const l=gp(r,n).toLocaleLowerCase(),d=r.path.toLocaleLowerCase();(l.includes(t)||!d.startsWith("__")&&d.includes(t)||a.length>0)&&o.push({...r,children:a})}return o}function f7(){const{t:e,translationEngine:t,translationContext:n}=tt(),{state:o,dispatch:r}=Qe(),a=Ft()?.dirtyLayoutNames??l7,[l,d]=u.useState(""),c=d7(l),p=u.useMemo(()=>c?vp(o.routeTree,c,{t:e,translationEngine:t,translationContext:n,templateIdentifier:o.templateIdentifier}):o.routeTree,[c,o.routeTree,o.templateIdentifier,e,t,n]),f=h=>{if(h.kind==="base"&&h.layoutName){r({type:"ENTER_BASE_EDIT",layoutName:h.layoutName});return}if(h.kind==="modal"&&h.modalId&&h.modalHostLayout){r({type:"ENTER_MODAL_EDIT",modalId:h.modalId,hostLayout:h.modalHostLayout});return}if(h.kind==="extension"&&h.extensionId){const v=bp(h,o.routeTree);r({type:"ENTER_EXTENSION_EDIT",extensionId:h.extensionId,extensionHost:v});return}if(r({type:"SELECT_ROUTE",route:{path:h.path,layoutName:h.layoutName}}),typeof window<"u"&&window.history?.pushState){const v=ea(o.templateIdentifier,h.path);`${window.location.pathname}${window.location.search}`!==v&&window.history.pushState({source:a7,routePath:h.path},"",v)}},b=o.selectedRoute?.path??null,g=u.useMemo(()=>p7(o.routeTree,b),[o.routeTree,b]);if(o.isRouteTreeCollapsed)return i.jsx("aside",{className:"g7le-route-tree g7le-route-tree--collapsed","data-testid":"g7le-route-tree-panel","data-collapsed":"true",style:{width:28,borderRight:"1px solid #e2e8f0",background:"#ffffff"}});const m=o.routeTree.length>0;return i.jsxs("aside",{className:"g7le-route-tree","data-testid":"g7le-route-tree-panel","data-collapsed":"false",style:{width:280,borderRight:"1px solid #e2e8f0",background:"#ffffff",position:"sticky",top:"var(--g7le-header-h, 0px)",alignSelf:"start",height:"calc(100vh - var(--g7le-header-h, 0px))",overflow:"hidden",minHeight:0,display:"flex",flexDirection:"column"},"aria-label":e("layout_editor.chrome.route_tree.panel_title"),children:[i.jsx("div",{className:"g7le-route-tree__title",style:{padding:"10px 14px",fontSize:12,fontWeight:600,color:"#475569",textTransform:"uppercase",letterSpacing:.6,borderBottom:"1px solid #e2e8f0",background:"#f8fafc",flex:"0 0 auto"},children:e("layout_editor.chrome.route_tree.panel_title")}),m&&i.jsxs("div",{className:"g7le-route-tree__search",style:{padding:"8px 10px",borderBottom:"1px solid #e2e8f0",background:"#ffffff",flex:"0 0 auto",position:"relative"},children:[i.jsx("input",{type:"text",className:"g7le-route-tree__search-input","data-testid":"g7le-route-tree-search",value:l,onChange:h=>d(h.target.value),placeholder:e("layout_editor.chrome.route_tree.search_placeholder"),"aria-label":e("layout_editor.chrome.route_tree.search_placeholder"),style:{width:"100%",boxSizing:"border-box",padding:l?"6px 26px 6px 10px":"6px 10px",fontSize:12,lineHeight:1.4,color:"#1e293b",border:"1px solid #cbd5e1",borderRadius:6,outline:"none",background:"#f8fafc"}}),l&&i.jsx("button",{type:"button",className:"g7le-route-tree__search-clear","data-testid":"g7le-route-tree-search-clear",onClick:()=>d(""),"aria-label":e("layout_editor.chrome.route_tree.search_clear"),title:e("layout_editor.chrome.route_tree.search_clear"),style:{position:"absolute",right:16,top:"50%",transform:"translateY(-50%)",width:18,height:18,display:"inline-flex",alignItems:"center",justifyContent:"center",border:"none",background:"transparent",color:"#94a3b8",cursor:"pointer",fontSize:14,lineHeight:1,padding:0},children:"✕"})]}),!m&&i.jsx("div",{className:"g7le-route-tree__empty","data-testid":"g7le-route-tree-empty",style:{padding:16,fontSize:12,color:"#94a3b8",textAlign:"center"},children:e("layout_editor.chrome.route_tree.empty")}),m&&c&&p.length===0&&i.jsx("div",{className:"g7le-route-tree__no-results","data-testid":"g7le-route-tree-no-results",style:{padding:16,fontSize:12,color:"#94a3b8",textAlign:"center"},children:e("layout_editor.chrome.route_tree.search_no_results")}),m&&p.length>0&&i.jsx("nav",{className:"g7le-route-tree__scroll","data-testid":"g7le-route-tree-scroll",style:{flex:"1 1 auto",minHeight:0,overflowY:"auto"},children:i.jsx("ul",{style:{listStyle:"none",margin:0,padding:0},children:p.map(h=>i.jsx(yp,{node:h,depth:0,onSelect:f,selectedRoutePath:b,activeHostRoutePath:g,dirtyLayoutNames:a,query:c,forceExpand:!!c},h.path))})})]})}const g7=Lt().DynamicRenderer,na=Lt(),h7=na.ResponsiveProvider;na.useResponsive,na.ResponsiveContext;const y7=Lt().ActionDispatcher,_p=Lt(),b7=_p.DataSourceManager;_p.dataSourceManager;function Jr(e,t,n){const o=t??{},r=o.message||o.error||`HTTP ${e}`,s=o?.data?.required_permissions??o?.required_permissions;let a;return e===401?a="unauthorized":e===403?a="forbidden":e===404?a="not_found":e>=500?a="server_error":a="unknown",{kind:a,status:e,message:String(r),requiredPermissions:s?String(s):void 0,source:n}}function so(e,t){return{kind:"network",status:0,message:e instanceof Error?e.message:String(e??"Unknown network error"),source:t}}function xp(e){if(typeof e!="object"||e===null)return!1;const t=e;return typeof t.kind=="string"&&typeof t.status=="number"&&typeof t.message=="string"}function Re(e){if(!e)return[];const t=[],n=e.split(".");for(let o=0;o0&&(t.push({responsive:a}),o+=1);continue}const s=Number(r);Number.isFinite(s)&&Number.isInteger(s)&&s>=0&&t.push(s)}return t}function m7(e,t){if(!e)return{affordance:"none",targetPath:null};const n=v7(e);return n?_7(n)?{affordance:"dynamic_path",targetPath:n}:/^(https?:)?\/\//.test(n)?{affordance:"external_url",targetPath:n}:n.startsWith("/")?t?{affordance:t(n),targetPath:n}:{affordance:"route_not_in_tree",targetPath:n}:{affordance:"route_not_in_tree",targetPath:n}:{affordance:"none",targetPath:null}}function v7(e){const t=e.actions;if(Array.isArray(t)){for(const n of t)if(n&&typeof n=="object"&&n.handler==="navigate"){const o=n.params?.path;if(typeof o=="string"&&o.length>0)return o}}if(e.name==="A"||e.type==="A"){const n=e.props?.href;if(typeof n=="string"&&n.length>0)return n}return null}function _7(e){return/\{\{[\s\S]*?\}\}/.test(e)}function x7(e,t,n){const o=s=>!!(s?.iteration&&s.iteration.source!==void 0),r=s=>{if(!s)return s;const a=s.indexOf(".iteration."),l=s.indexOf(".sortable."),d=[a,l].filter(c=>c>=0).sort((c,p)=>c-p)[0];return d===void 0?s:s.slice(0,d)};return o(e)?{isIteration:!0,iterationSourcePath:r(n)}:n&&n.includes(".iteration.")?{isIteration:!0,iterationSourcePath:r(n)}:t.some(o)?{isIteration:!0,iterationSourcePath:r(n)}:{isIteration:!1,iterationSourcePath:null}}function w7(e,t){const n=a=>{const l=a?.iteration&&a.iteration.source;return typeof l=="string"&&l.length>0?l:null},o=n(e)??t.map(n).find(a=>a!==null)??null;if(o!==null){const a=S7(o);if(a!==null)return a}const r=[];if(typeof e.text=="string"&&r.push(...wp(e.text)),e.props&&typeof e.props=="object")for(const a of Object.values(e.props))typeof a=="string"&&r.push(...wp(a));return r.length===0?null:r.find(a=>!k7.has(a))??r[0]}const k7=new Set(["_local","_global","_computed","route","query","props","item","index","event","$event","$response","response"]);function wp(e){const t=[],n=/\{\{([\s\S]*?)\}\}/g;let o;for(;(o=n.exec(e))!==null;){const r=kp(o[1]);r!==null&&t.push(r)}return t}function S7(e){const t=e.replace(/\{\{|\}\}/g,"");return kp(t)}function kp(e){const t=/[A-Za-z_$][A-Za-z0-9_$]*/.exec(e.trim());return t?t[0]:null}function gn(e,t,n,o=[]){if(!e)return"none";if(id(e)||typeof e.slot=="string"||typeof e.__editorSlotName=="string")return"extension_point";if(t!=="route"&&Ri(e,t,n)){const s=e.__source?.kind;return s==="base"?"base":s==="partial"?"partial":s==="extension"?"extension":"base"}if(zb(e,o))return"data_bound";if(!Ri(e,t,n))return"none";const r=e.__source?.kind;return r==="base"?"base":r==="partial"?"partial":r==="extension"?"extension":"none"}function Sp(e){if(!e)return null;if(e.__source?.kind==="extension"&&typeof e.__source.extensionId=="number")return e.__source.extensionId;const t=e.__injectedProps;if(Array.isArray(t)&&t.length>0){const n=t[0]?.extensionId;if(typeof n=="number")return n}return null}function C7(e,t){if(t<=0)return e;const n=e.split(".");let o=0;const r=[];for(let s=0;s=t)break;r.push(a);continue}if(a==="iteration"||a==="sortable"){if(o>=t)break;r.push(a);continue}r.push(a);const l=n[s-1];if((l===void 0||l==="children")&&(o+=1),o>=t)break}return r.join(".")}function Cp(e,t,n,o){if(!e||!t)return t;const r=n(t),s=Lb(e,r,o);return s.length===r.length?t:C7(t,s.length)}function $p(e){return e==="none"||e==="data_bound"}function $7(e){const{rootNode:t,editMode:n,currentExtensionId:o,editableRootPath:r,pathParser:s=Re,resolveRouteMatch:a}=e,[l,d]=u.useState(null),[c,p]=u.useState(null),f=u.useCallback(_=>{if(_===null||n==="route")return!0;if(n==="iteration_item"||n==="modal"){if(!r||r.length===0)return!0;const $=s(_);return Ep($,r)}const w=oa(t,_,s);if(!w)return!0;const k=ra(t,_,s),x=gn(w,n,o,k);return x==="none"||x==="data_bound"},[t,s,n,o,r]),b=u.useCallback((_,w)=>{const x=(w?.currentTarget?.dataset??w?.dataset)?.editorPath??null,$=Cp(t,x,s,o);f($)&&d($)},[t,s,o,f]),g=u.useCallback((_,w)=>{if(_===null){p(null);return}const x=(w?.currentTarget?.dataset??w?.dataset)?.editorPath??null,$=Cp(t,x,s,o);if(!f($)){p(null);return}p($)},[t,s,o,f]),m=u.useCallback(()=>{d(null)},[]),h=u.useMemo(()=>{const _=oa(t,l,s),w=oa(t,c,s),k=_?ra(t,l,s):[];let x=gn(_,n,o,k);(n==="iteration_item"||n==="modal")&&l!==null&&_&&r&&r.length>0&&Ep(s(l),r)&&(sd(_)?x="data_bound":x==="data_bound"&&(x="none"));const{affordance:$,targetPath:S}=m7(_,a),C=x==="base"&&typeof _?.__source?.layout=="string"?_.__source.layout:null,T=x==="data_bound"&&_?w7(_,k):null,{isIteration:E,iterationSourcePath:A}=x==="data_bound"&&_?x7(_,k,l):{isIteration:!1,iterationSourcePath:null};return{selectedNode:_,hoverNode:w,selectedLockKind:x,selectedNavAffordance:$,selectedNavTargetPath:S,selectedBaseLayout:C,selectedDataSourceId:T,selectedIsIteration:E,selectedIterationSourcePath:A}},[t,l,c,n,o,r,s,a]),v=u.useRef(void 0);v.current!==l&&(v.current=l,Al({op:l===null?"clear":"select",componentPath:l,componentName:Ip(h.selectedNode),lockKind:h.selectedLockKind,navAffordance:h.selectedNavAffordance,sourceExtensionId:Sp(h.selectedNode),timestamp:Date.now()}));const y=u.useRef(void 0);return y.current!==c&&(y.current=c,c!==null&&Al({op:"hover",componentPath:c,componentName:Ip(h.hoverNode),lockKind:gn(h.hoverNode,n,o,ra(t,c,s)),navAffordance:"none",sourceExtensionId:Sp(h.hoverNode),timestamp:Date.now()})),{selectedPath:l,hoverPath:c,...h,handleSelect:b,handleHover:g,clearSelection:m}}function oa(e,t,n){if(!e||!t)return null;const o=n(t);return Be(e,o)}function Ep(e,t){if(e.length0?e.name:typeof e.type=="string"&&e.type.length>0?e.type:null:null}let Ap=0;function Tp(){return Ap}function ia(){Ap+=1}function E7(e){if(typeof e!="string")return null;const t="__modal__/";if(!e.startsWith(t))return null;const n=e.slice(t.length);return n===""||n.includes("/")?null:n}function I7(e){if(typeof e!="string")return null;const t="__iteration__/";if(!e.startsWith(t))return null;const n=e.slice(t.length);return n!==""?n:null}function Dp(){const{state:e}=Qe(),t=e.templateIdentifier,n=e.selectedRoute?.layoutName??null,o=e.editMode,r=o==="modal"?E7(e.selectedRoute?.path):null,s=o==="iteration_item"?I7(e.selectedRoute?.path):null,[a,l]=u.useState(null),[d,c]=u.useState(!1),[p,f]=u.useState(null),[b,g]=u.useState(!1),[m,h]=u.useState(0),[v,y]=u.useState(0),_=u.useRef(new Map),[w,k]=u.useState(()=>new Set),x=u.useCallback((P,I)=>`${P}|${I??""}`,[]),$=u.useRef(null);u.useEffect(()=>{$.current=a},[a]);const S=u.useCallback((P,I)=>{const L=x(o,n);_.current.set(L,{raw:P,lockVersion:I}),k(F=>{if(F.has(L))return F;const Y=new Set(F);return Y.add(L),Y})},[x,o,n]),C=u.useCallback(P=>{_.current.delete(P),k(I=>{if(!I.has(P))return I;const L=new Set(I);return L.delete(P),L})},[]),T=u.useCallback(P=>{const I=`|${P??""}`,L=[];for(const F of _.current.keys())F.endsWith(I)&&L.push(F);for(const F of L)_.current.delete(F);L.length>0&&k(F=>{let Y=!1;const K=new Set(F);for(const X of L)K.delete(X)&&(Y=!0);return Y?K:F})},[]),E=u.useCallback(async()=>{if(!n){l(null),f(null),g(!1);return}const P=x(o,n),I=_.current.get(P);if(I){l({layoutName:n,raw:I.raw,lockVersion:I.lockVersion}),g(!0),f(null),c(!1),st({op:"load",layoutName:n,editMode:o,statusCode:0,isDirty:!0,timestamp:Date.now()});return}c(!0),f(null);try{const L=window.G7Config?.cache_version??0,F=en(`/api/layouts/${encodeURIComponent(t)}/${n}`,"json",`${L}.${Tp()}`,"with_source_meta=1"),Y=cr(),K={Accept:"application/json"};Y&&(K.Authorization=`Bearer ${Y}`);const X=await fetch(F,{headers:K,credentials:"same-origin"}),Z=await X.json().catch(()=>null);if(!X.ok){f(Jr(X.status,Z,"layout")),l(null),g(!1);return}const se=(Z&&(Z.data||Z))??{},oe=typeof se.lock_version=="number"?se.lock_version:0;if(o==="modal"&&r){const ee=se,ge=Array.isArray(ee.modals)?ee.modals:[],V=ge.findIndex(te=>te&&(te.id===r||te.modal_id===r));if(V>=0){const te=ge[V],be={...te,props:{...te.props??{},isOpen:!0}},De=[...Array.isArray(ee.components)?ee.components:[],be],Se=[De.length-1];l({layoutName:n,raw:{...ee,components:De},lockVersion:oe,modalContext:{modalId:r,modalIndex:V,hostRaw:ee,originalModalNode:te,editIndexPath:Se}}),g(!1),st({op:"load",layoutName:n,editMode:o,statusCode:X.status,timestamp:Date.now()});return}}if(o==="iteration_item"&&s){const ee=se,ge=Re(s),V={children:ee.components??[]},te=Be(V,ge);if(te&&Array.isArray(te.children)){l({layoutName:n,raw:ee,lockVersion:oe,iterationContext:{sourcePath:s,sourceIndexPath:ge,hostRaw:ee}}),g(!1),st({op:"load",layoutName:n,editMode:o,statusCode:X.status,timestamp:Date.now()});return}}l({layoutName:n,raw:se,lockVersion:oe}),g(!1),st({op:"load",layoutName:n,editMode:o,statusCode:X.status,timestamp:Date.now()})}catch(L){f(so(L,"layout")),l(null),g(!1)}finally{c(!1)}},[t,n,o,r,s,x]);u.useEffect(()=>{E()},[E]);const A=u.useCallback(async()=>{C(x(o,n)),ia(),y(P=>P+1),await E()},[C,x,o,n,E]),N=u.useCallback(()=>{C(x(o,n))},[C,x,o,n]),D=u.useCallback(P=>{l(I=>{if(!I)return I;const L=I.raw.components??[],F=P(L),Y={...I.raw,components:F};return S(Y,I.lockVersion),{...I,raw:Y}}),g(!0),st({op:"patch",layoutName:n,editMode:o,isDirty:!0,timestamp:Date.now()})},[n,o,S]),B=u.useCallback(()=>{g(!0),st({op:"patch",layoutName:n,editMode:o,isDirty:!0,timestamp:Date.now()})},[n,o]),M=u.useCallback(P=>{l(I=>{if(!I)return I;const L={...I.raw,components:P};return S(L,I.lockVersion),{...I,raw:L}}),g(!0),st({op:"patch",layoutName:n,editMode:o,isDirty:!0,timestamp:Date.now()})},[n,o,S]),O=u.useCallback((P,I,...L)=>{const F=L.length>=1?L[0]:I;l(Y=>{if(!Y)return Y;const K={...Y.raw,[P]:I},X=Y.raw.__editor;if(X&&typeof X=="object"&&!Array.isArray(X)){const Z=X,se=Z.original;se&&typeof se=="object"&&!Array.isArray(se)&&(K.__editor={...Z,original:{...se,[P]:F}})}return S(K,Y.lockVersion),{...Y,raw:K}}),g(!0),st({op:"patch",layoutName:n,editMode:o,isDirty:!0,timestamp:Date.now()})},[n,o,S]),z=u.useCallback(async P=>{const I=$.current;if(!I)return st({op:"save_guard_result",layoutName:n,editMode:o,guardBlocked:!1,timestamp:Date.now()}),{kind:"guard_no_document"};if(P?.resolveActiveExtensions&&P.sessionAddedPaths?.length)try{const ge=await P.resolveActiveExtensions(),V=A7(I.raw.components,P.sessionAddedPaths,ge);if(V.length>0)return st({op:"save_guard_result",layoutName:n,editMode:o,guardBlocked:!0,guardBlockedPaths:V,timestamp:Date.now()}),{kind:"blocked_inactive_extension",blockedPaths:V}}catch{}if(Yb())try{await Xb(t)}catch{}const L=`/api/admin/templates/${encodeURIComponent(t)}/layouts/${I.layoutName}`,F=cr(),Y={Accept:"application/json","Content-Type":"application/json"};F&&(Y.Authorization=`Bearer ${F}`);const K=!!I.modalContext,X=!!I.iterationContext,Z=K||X?"host_layout_modal_patch":"layout";st({op:"save",layoutName:I.layoutName,editMode:o,saveTarget:Z,endpoint:L,isDirty:b,timestamp:Date.now()});let se;if(K&&I.modalContext){const{modalIndex:ge,hostRaw:V,originalModalNode:te,editIndexPath:be}=I.modalContext,Ee=Array.isArray(V.modals)?[...V.modals]:[],De=Array.isArray(I.raw.components)?I.raw.components:[],Se=be&&be.length===1?be[0]:0,Ue=De.length>Se?De[Se]:void 0,Me=Ue?Vn([Ue]):[],Oe=Me.length>0?{...Me[0]}:void 0;if(ge>=0&&ge({...ze,children:De})).children??[];se={...Se,components:Ye}}else se=_o(I.raw);const oe=se;let G;try{G=await fetch(L,{method:"PUT",headers:Y,credentials:"same-origin",body:JSON.stringify({content:JSON.stringify(oe),expected_lock_version:I.lockVersion})})}catch(ge){const V=ge instanceof Error?ge.message:"network error";return st({op:"save_response",layoutName:I.layoutName,editMode:o,endpoint:L,statusCode:0,timestamp:Date.now()}),{kind:"network_error",message:V}}const ee=await G.json().catch(()=>null);if(st({op:"save_response",layoutName:I.layoutName,editMode:o,endpoint:L,statusCode:G.status,conflict:G.status===409?{currentVersion:ee?.current_version,yourVersion:ee?.your_version}:void 0,timestamp:Date.now()}),G.ok){const ge=typeof ee?.data?.lock_version=="number"?ee.data.lock_version:I.lockVersion+1;l({...I,lockVersion:ge}),g(!1),C(x(o,I.layoutName)),T(I.layoutName),ia(),h(te=>te+1);const V=typeof ee?.data?.current_version=="number"?ee.data.current_version:void 0;return{kind:"success",newLockVersion:ge,newContentVersion:V,savedLayoutName:I.layoutName}}return G.status===409?{kind:"concurrent_modification",currentVersion:ee?.current_version??-1,yourVersion:ee?.your_version??I.lockVersion}:G.status===422?{kind:"validation_failed",status:422,errors:ee?.errors??null}:{kind:"network_error",message:ee?.message??`HTTP ${G.status}`}},[t,n,o,b,C,T,x,r,s]),H=u.useMemo(()=>{const P=new Set,I="route|";for(const L of w)if(L.startsWith(I)){const F=L.slice(I.length);F&&P.add(F)}return P},[w]);return{document:a,isLoading:d,error:p,isDirty:b,saveSuccessCounter:m,reloadCounter:v,dirtyKeys:w,dirtyLayoutNames:H,invalidateCurrentCache:N,reload:A,patchLayout:D,setLayoutComponents:M,patchDocumentRaw:O,save:z,markDirty:B}}function A7(e,t,n){if(!e||e.length===0)return[];const o=[],r=new Set([...n.moduleIds,...n.pluginIds]);for(const s of t){const a=T7(e,s);if(!a)continue;const l=a.__source;if(!l||l.kind!=="extension")continue;const d=String(l.extensionId??"");d.length!==0&&(r.has(d)||o.push(s))}return o}function T7(e,t){const n=Re(t);return n.length===0?null:Be({children:e},n)}function ao(e,t){if(!e)return null;const n=e.getBoundingClientRect();if(n.width===0&&n.height===0)return null;if(!t)return{left:n.left,top:n.top,width:n.width,height:n.height,scale:1};const o=t.getBoundingClientRect();return{left:n.left-o.left,top:n.top-o.top,width:n.width,height:n.height,scale:1}}function lo(e,t){if(!e)return!1;if(!t)return!0;const n=t.getBoundingClientRect(),o=1;return!(e.left>=n.width-o||e.left+e.width<=o||e.top>=n.height-o||e.top+e.height<=o)}function zp(e,t){if(typeof window>"u")return()=>{};const n=()=>{e()};window.addEventListener("scroll",n,!0),window.addEventListener("resize",n);let o=null;t&&typeof ResizeObserver<"u"&&(o=new ResizeObserver(()=>e()),o.observe(t));let r=null,s=null;const a=typeof requestAnimationFrame<"u",l=()=>{if(!a){e();return}s===null&&(s=requestAnimationFrame(()=>{s=null,e()}))};return t&&typeof MutationObserver<"u"&&(r=new MutationObserver(l),r.observe(t,{childList:!0,subtree:!0})),()=>{window.removeEventListener("scroll",n,!0),window.removeEventListener("resize",n),o&&(o.disconnect(),o=null),r&&(r.disconnect(),r=null),s!==null&&a&&typeof cancelAnimationFrame<"u"&&(cancelAnimationFrame(s),s=null)}}const Np=44;function Mp(e){if(!e)return"inside";const t=e.scale||1,n=e.width*t,o=e.height*t;return n"u"||typeof window.getComputedStyle!="function")return{kind:"unknown",display:"",flexDirection:"",flexWrap:""};let t=e,n=(window.getComputedStyle(t).display??"").toLowerCase();for(;n==="contents"&&t.parentElement;)t=t.parentElement,n=(window.getComputedStyle(t).display??"").toLowerCase();const o=window.getComputedStyle(t),r=(o.display??"").toLowerCase(),s=(o.flexDirection??"").toLowerCase(),a=(o.flexWrap??"").toLowerCase(),l=r==="flex"||r==="inline-flex";if(r==="grid"||r==="inline-grid"){const f=Array.from(e.children).filter(h=>h.offsetParent!==null||h.getClientRects().length>0);if(f.length<=1)return{kind:"grid_2d",display:r,flexDirection:s,flexWrap:a};const b=f.map(h=>h.getBoundingClientRect()),g=new Set(b.map(h=>Math.round(h.top))),m=new Set(b.map(h=>Math.round(h.left)));return g.size===1?{kind:"grid_single_row",display:r,flexDirection:s,flexWrap:a}:m.size===1?{kind:"grid_single_column",display:r,flexDirection:s,flexWrap:a}:{kind:"grid_2d",display:r,flexDirection:s,flexWrap:a}}if(!l)return{kind:"block",display:r,flexDirection:s,flexWrap:a};const c=a==="wrap"||a==="wrap-reverse";return s==="column"||s==="column-reverse"?{kind:c?"flex_column_wrap":"flex_column_single",display:r,flexDirection:s,flexWrap:a}:{kind:c?"flex_row_wrap":"flex_row_single",display:r,flexDirection:s,flexWrap:a}}function z7(e){const{parentEl:t,selectedPath:n,selectedEl:o,explicitFlow:r}=e;return u.useMemo(()=>{if(!n||n.length===0)return{points:[],flow:"unknown"};const s=r??Op(t).kind,a=n.slice(0,-1),l=n[n.length-1]??0;if((s==="flex_row_wrap"||s==="flex_column_wrap"||s==="grid_2d")&&!!o){const p=N7(s,o,a,l);if(p)return{points:p,flow:s}}return{points:M7(s,a,l),flow:s}},[t,n,o,r])}function N7(e,t,n,o){let r=t.parentElement;for(;r&&Bp(r)==="contents";)r=r.parentElement;if(!r)return null;const s=[];if(Wp(r,s),s.length===0)return null;const a=t.getBoundingClientRect(),l=a.top+a.height/2,d=[],c=[],p=[];for(const v of s){const y=v.rect,_=y.top+y.height/2,w=Math.min(a.bottom,y.bottom)-Math.max(a.top,y.top),k=Math.min(a.height,y.height)||1;w/k>.4?d.push({rect:y,path:v.path}):_v.rect.left-y.rect.left),c.sort((v,y)=>v.rect.top-y.rect.top),p.sort((v,y)=>v.rect.top-y.rect.top);const f=d.findIndex(v=>Math.abs(v.rect.left-a.left)<1&&Math.abs(v.rect.top-a.top)<1),b={direction:"above",disabled:!0,insertion:null},g={direction:"below",disabled:!0,insertion:null},m={direction:"left",disabled:!0,insertion:null},h={direction:"right",disabled:!0,insertion:null};if(f>0){const v=d[f-1],y=Qr(v.path);y&&(m.disabled=!1,m.insertion=y)}else if(d.length>0){const v=d[0],y=Zr(v.path);y&&(m.disabled=!1,m.insertion=y)}if(f>=0&&f0){const v=d[d.length-1],y=Qr(v.path);y&&(h.disabled=!1,h.insertion=y)}if(c.length>0){const v=c[c.length-1],y=Qr(v.path);y&&(b.disabled=!1,b.insertion=y)}else if(s.length>0){const v=s[0],y=Zr(v.path);y&&(b.disabled=!1,b.insertion=y)}if(p.length>0){const v=p[0],y=Zr(v.path);y&&(g.disabled=!1,g.insertion=y)}else if(s.length>0){const v=s[s.length-1],y=Qr(v.path);y&&(g.disabled=!1,g.insertion=y)}else g.disabled=!1,g.insertion={parentPath:n,index:o+1};return b.disabled&&g.disabled&&m.disabled&&h.disabled?null:[b,g,m,h]}function Bp(e){return typeof window>"u"?"":(window.getComputedStyle(e).display??"").toLowerCase()}function Wp(e,t){const n=Array.from(e.children);for(const o of n){if(Bp(o)==="contents"){Wp(o,t);continue}const s=o.getAttribute("data-editor-path");if(s&&/\.iteration\.\d+(?:$|\.)/.test(s)||!s)continue;const a=o.getBoundingClientRect();a.width===0&&a.height===0||t.push({el:o,rect:a,path:Re(s)})}}function Zr(e){if(!e||e.length===0)return null;const t=e[e.length-1];return typeof t!="number"?null:{parentPath:e.slice(0,-1),index:t}}function Qr(e){if(!e||e.length===0)return null;const t=e[e.length-1];return typeof t!="number"?null:{parentPath:e.slice(0,-1),index:t+1}}function M7(e,t,n){const o={direction:"above",disabled:!0,insertion:null},r={direction:"below",disabled:!0,insertion:null},s={direction:"left",disabled:!0,insertion:null},a={direction:"right",disabled:!0,insertion:null},l=d=>({parentPath:t,index:d});switch(e){case"block":case"unknown":{o.disabled=!1,o.insertion=l(n),r.disabled=!1,r.insertion=l(n+1);break}case"flex_row_single":case"grid_single_row":{s.disabled=!1,s.insertion=l(n),a.disabled=!1,a.insertion=l(n+1);break}case"flex_column_single":case"grid_single_column":{o.disabled=!1,o.insertion=l(n),r.disabled=!1,r.insertion=l(n+1);break}case"flex_row_wrap":case"grid_2d":{s.disabled=!1,s.insertion=l(n),a.disabled=!1,a.insertion=l(n+1),o.disabled=!1,o.insertion=l(0),r.disabled=!1,r.insertion=l(-1);break}case"flex_column_wrap":{o.disabled=!1,o.insertion=l(n),r.disabled=!1,r.insertion=l(n+1),s.disabled=!1,s.insertion=l(0),a.disabled=!1,a.insertion=l(-1);break}}return[o,r,s,a]}function L7(e=50){const t=u.useRef([]),n=u.useRef(-1),[,o]=u.useState(0),r=()=>{o(p=>(p+1)%1e6)},s=(p,f)=>{const b=t.current.length,g=n.current;ey({op:p,actionKind:f?.actionKind,label:f?.label,stackSize:b,cursor:g,canUndo:g>0,canRedo:g>=0&&g{const b=t.current.slice(0,n.current+1);for(b.push(p);b.length>e;)b.shift();t.current=b,n.current=b.length-1,r(),s("push",p)},[e]),l=u.useCallback(()=>{if(n.current<=0)return null;n.current-=1,r();const p=t.current[n.current]??null;return s("undo",p??void 0),p},[]),d=u.useCallback(()=>{const p=n.current+1;if(p>=t.current.length)return null;n.current=p,r();const f=t.current[p]??null;return s("redo",f??void 0),f},[]),c=u.useCallback(()=>{t.current=[],n.current=-1,r(),s("clear")},[]);return{push:a,undo:l,redo:d,clear:c,canUndo:n.current>0,canRedo:n.current>=0&&n.current{if(!o||typeof window>"u")return;const r=s=>{if(P7(s.target)||!(s.ctrlKey||s.metaKey))return;const l=s.key.toLowerCase();if(l==="z"&&!s.shiftKey){s.preventDefault(),t();return}if(l==="z"&&s.shiftKey||l==="y"){s.preventDefault(),n();return}};return window.addEventListener("keydown",r),()=>{window.removeEventListener("keydown",r)}},[t,n,o])}const B7=20,jp=50,W7=90,Ln=120,j7=130,Hp=80,H7=9e3,Fp=1e4;function F7(e){const t=e<0?0:e>jp?jp:e;return B7+t}function K7(e){const{anchor:t,open:n,onClose:o,onEditProps:r,onDuplicate:s,onDelete:a,onSeparateBranch:l,separateBranchLabel:d,onMergeBranch:c,mergeBranchLabel:p}=e,{t:f}=tt(),[b,g]=u.useState(null);if(!n||!t)return null;const m=[{index:0,testid:"g7le-context-menu-edit-props",label:f("layout_editor.context_menu.edit_props"),onSelect:r},{index:1,testid:"g7le-context-menu-duplicate",label:f("layout_editor.context_menu.duplicate"),onSelect:s}];return l?m.push({index:m.length,testid:"g7le-context-menu-separate-branch",label:d??f("layout_editor.context_menu.separate_branch"),onSelect:l,hint:f("layout_editor.context_menu.separate_branch_hint")}):c&&m.push({index:m.length,testid:"g7le-context-menu-merge-branch",label:p??f("layout_editor.context_menu.merge_branch"),onSelect:c,hint:f("layout_editor.context_menu.merge_branch_hint")}),m.push({index:m.length,testid:"g7le-context-menu-delete",label:f("layout_editor.context_menu.delete"),onSelect:a,danger:!0}),i.jsx("div",{className:"g7le-context-menu","data-testid":"g7le-context-menu",role:"menu",style:{position:"absolute",left:t.left,top:t.top,background:"#fff",border:"1px solid #cbd5e1",borderRadius:6,boxShadow:"0 4px 12px rgba(0,0,0,0.15)",zIndex:1200,minWidth:160,padding:4,pointerEvents:"auto"},onMouseLeave:()=>{g(null),o()},children:m.map(h=>{const v=b===h.index,y=h.danger?"#dc2626":"#0f172a",_=h.danger?"#fee2e2":"#e0e7ff",w=h.danger?"#b91c1c":"#1d4ed8";return i.jsx("button",{type:"button",role:"menuitem","data-testid":h.testid,title:h.hint,onClick:k=>{k.stopPropagation(),h.onSelect(),g(null),o()},onMouseEnter:()=>g(h.index),onFocus:()=>g(h.index),style:{...V7,color:v?w:y,background:v?_:"transparent"},children:h.label},h.testid)})})}const V7={display:"block",width:"100%",textAlign:"left",padding:"6px 12px",border:"none",fontSize:13,cursor:"pointer",borderRadius:4,pointerEvents:"auto",transition:"background-color 120ms ease, color 120ms ease"};function U7(e){const{hoverBox:t,selectedBox:n,lockKind:o,navAffordance:r,onEditProps:s,onDuplicate:a,onDelete:l,onSeparateBranch:d,separateBranchLabel:c,onMergeBranch:p,mergeBranchLabel:f,definedDeviceBranches:b,onJumpToDevice:g,onLinkEditDestination:m,onEditBase:h,onEditExtension:v,extensionLabel:y,baseLayoutLabel:_,dataSourceLabel:w,branchLabel:k,onEditIteration:x,resizeEnabledAxes:$,onResizeHandlePointerDown:S,selectedName:C,onSelectParent:T}=e,{t:E}=tt(),[A,N]=u.useState(!1),D=$p(o),B=A&&D,M=Mp(n),O=M==="outside",z=O&&n?Pp(n):null,H=z?{left:z.right.left,top:z.above.top}:{right:-30,top:-30},P={top:-28,left:0},L=O&&!!C?{top:-50,left:0}:P,F=z?{top:z.below.top+28,left:0}:{top:"auto",bottom:-28},Y=z?{top:z.below.top+56,left:0}:{top:"auto",bottom:-56};return i.jsxs("div",{className:"g7le-element-overlay","data-testid":"g7le-element-overlay",style:{position:"absolute",inset:0,pointerEvents:"none"},children:[t&&i.jsx("div",{"data-testid":"g7le-overlay-hover",style:{position:"absolute",left:t.left,top:t.top,width:t.width,height:t.height,border:"1px dashed #2563eb",pointerEvents:"none"}}),n&&i.jsxs("div",{"data-testid":"g7le-overlay-selected","data-lock-kind":o,style:{position:"absolute",left:n.left,top:n.top,width:n.width,height:n.height,border:o==="none"?"2px solid #2563eb":"2px dashed #f59e0b",pointerEvents:"none"},children:[k?i.jsxs("span",{"data-testid":"g7le-overlay-branch-badge",style:G7,children:["📱 ",k]}):null,C&&(T?i.jsxs("button",{type:"button","data-testid":"g7le-overlay-type-label","data-placement":O?"outside":"inside",title:E("layout_editor.overlay.select_parent"),"aria-label":E("layout_editor.overlay.select_parent"),onClick:K=>{K.preventDefault(),K.stopPropagation(),T()},style:O?X7:Y7,children:[i.jsx("span",{"aria-hidden":!0,style:{marginRight:3,opacity:.85},children:"↑"}),C]}):i.jsx("span",{"data-testid":"g7le-overlay-type-label","data-placement":O?"outside":"inside",style:O?Vp:Kp,children:C})),D&&i.jsx("button",{type:"button","data-testid":"g7le-overlay-info-button","aria-label":"info",onClick:K=>{K.stopPropagation(),N(X=>!X)},"data-placement":M,style:{position:"absolute",...H,width:20,height:20,borderRadius:10,border:"1px solid #2563eb",background:"#fff",color:"#2563eb",fontSize:12,fontWeight:700,cursor:"pointer",pointerEvents:"auto",padding:0,lineHeight:"18px",zIndex:Ln},children:"ⓘ"}),o==="none"&&i.jsx(q7,{enabledAxes:$,onHandlePointerDown:S}),o==="base"&&h&&i.jsxs("button",{type:"button","data-testid":"g7le-overlay-edit-base",onClick:K=>{K.stopPropagation(),h()},style:{...Wo,...P},children:["🔒 ",E("layout_editor.overlay.edit_common_layout"),_?i.jsx("span",{"data-testid":"g7le-overlay-base-layout-label",style:aa,children:_}):null]}),o==="extension"&&v&&i.jsxs("button",{type:"button","data-testid":"g7le-overlay-edit-extension",onClick:K=>{K.stopPropagation(),v()},style:{...Wo,...P},children:["🔒 ",E("layout_editor.overlay.edit_extension"),y?i.jsx("span",{"data-testid":"g7le-overlay-extension-label",style:aa,children:y}):null]}),o==="data_bound"&&i.jsxs("span",{"data-testid":"g7le-overlay-data-bound-notice",style:{...Wo,...L,pointerEvents:"none",cursor:"default"},children:[E("layout_editor.overlay.locked_data_bound"),w?i.jsx("span",{"data-testid":"g7le-overlay-data-source-label",style:aa,children:w}):null]}),o==="data_bound"&&x&&i.jsxs("button",{type:"button","data-testid":"g7le-overlay-edit-iteration",onClick:K=>{K.stopPropagation(),x()},style:{...Wo,...F},children:["↳ ",E("layout_editor.overlay.edit_iteration_item")]}),r==="route_in_tree"&&i.jsx("button",{type:"button","data-testid":"g7le-overlay-link-edit-destination",onClick:K=>{K.stopPropagation(),m()},style:{...Wo,...F},children:E("layout_editor.overlay.edit_destination_route")}),(d||p||g&&(b??[]).length>0)&&i.jsxs("div",{"data-testid":"g7le-overlay-branch-affordances",style:{position:"absolute",...Y,display:"flex",flexDirection:"column",alignItems:"flex-start",gap:4,pointerEvents:"none"},children:[g&&(b??[]).map(K=>i.jsxs("button",{type:"button","data-testid":`g7le-overlay-jump-device-${K.key}`,title:E("layout_editor.overlay.jump_device_hint",{device:K.label}),onClick:X=>{X.stopPropagation(),g(K.key)},style:sa,children:["↗ ",E("layout_editor.overlay.jump_device",{device:K.label})]},K.key)),d&&i.jsxs("button",{type:"button","data-testid":"g7le-overlay-separate-branch",title:E("layout_editor.context_menu.separate_branch_hint"),onClick:K=>{K.stopPropagation(),d()},style:sa,children:["📱 ",c??E("layout_editor.context_menu.separate_branch")]}),p&&i.jsxs("button",{type:"button","data-testid":"g7le-overlay-merge-branch",title:E("layout_editor.context_menu.merge_branch_hint"),onClick:K=>{K.stopPropagation(),p()},style:sa,children:["↩️ ",f??E("layout_editor.context_menu.merge_branch")]})]})]}),i.jsx(K7,{anchor:n&&B?{left:n.left+n.width+8,top:n.top-4}:null,open:B,onClose:()=>N(!1),onEditProps:s,onDuplicate:a,onDelete:l,onSeparateBranch:d,separateBranchLabel:c,onMergeBranch:p,mergeBranchLabel:f})]})}function q7(e){const{enabledAxes:t,onHandlePointerDown:n}=e,o=t?.width??!1,r=t?.height??!1,s=[{key:"nw",style:{left:-4,top:-4},cursor:"nwse-resize",needsWidth:!0,needsHeight:!0},{key:"n",style:{left:"50%",top:-4,transform:"translateX(-50%)"},cursor:"ns-resize",needsWidth:!1,needsHeight:!0},{key:"ne",style:{right:-4,top:-4},cursor:"nesw-resize",needsWidth:!0,needsHeight:!0},{key:"e",style:{right:-4,top:"50%",transform:"translateY(-50%)"},cursor:"ew-resize",needsWidth:!0,needsHeight:!1},{key:"se",style:{right:-4,bottom:-4},cursor:"nwse-resize",needsWidth:!0,needsHeight:!0},{key:"s",style:{left:"50%",bottom:-4,transform:"translateX(-50%)"},cursor:"ns-resize",needsWidth:!1,needsHeight:!0},{key:"sw",style:{left:-4,bottom:-4},cursor:"nesw-resize",needsWidth:!0,needsHeight:!0},{key:"w",style:{left:-4,top:"50%",transform:"translateY(-50%)"},cursor:"ew-resize",needsWidth:!0,needsHeight:!1}];return i.jsx(i.Fragment,{children:s.map(a=>{const l=!a.needsWidth||o,d=!a.needsHeight||r,c=l&&d&&!!n;return!l||!d?null:i.jsx("span",{"data-testid":`g7le-resize-handle-${a.key}`,"data-resize-handle-active":c?"true":"false",onPointerDown:c?p=>{p.preventDefault(),p.stopPropagation(),n?.(a.key,{clientX:p.clientX,clientY:p.clientY})}:void 0,style:{position:"absolute",...a.style,width:8,height:8,background:"#fff",border:"1px solid #2563eb",cursor:a.cursor,pointerEvents:c?"auto":"none",zIndex:Ln}},a.key)})})}const Wo={position:"absolute",top:-28,left:0,background:"#fff",border:"1px solid #2563eb",color:"#2563eb",padding:"2px 6px",fontSize:11,borderRadius:4,cursor:"pointer",pointerEvents:"auto",whiteSpace:"nowrap",zIndex:Ln},sa={background:"#fff",border:"1px solid #2563eb",color:"#2563eb",padding:"2px 6px",fontSize:11,borderRadius:4,cursor:"pointer",pointerEvents:"auto",whiteSpace:"nowrap",zIndex:Ln},aa={marginLeft:6,padding:"0 5px",borderRadius:3,background:"#eef2ff",border:"1px solid #c7d2fe",fontSize:10,fontWeight:600,color:"#3730a3"},G7={position:"absolute",top:0,right:0,transform:"translateY(-100%)",background:"rgba(124,58,237,0.92)",color:"#fff",padding:"1px 6px",borderRadius:"3px 3px 0 0",fontSize:10,fontWeight:600,whiteSpace:"nowrap",pointerEvents:"none"},Kp={position:"absolute",top:0,left:0,background:"rgba(37,99,235,0.55)",color:"#fff",padding:"1px 6px",fontSize:10,fontWeight:600,lineHeight:"14px",borderBottomRightRadius:4,letterSpacing:"0.02em",whiteSpace:"nowrap",pointerEvents:"none",userSelect:"none",maxWidth:"100%",overflow:"hidden",textOverflow:"ellipsis"},Vp={position:"absolute",bottom:"100%",left:0,marginBottom:2,background:"rgba(37,99,235,0.55)",color:"#fff",padding:"1px 6px",fontSize:10,fontWeight:600,lineHeight:"14px",borderRadius:4,letterSpacing:"0.02em",whiteSpace:"nowrap",pointerEvents:"none",userSelect:"none"},Y7={...Kp,pointerEvents:"auto",cursor:"pointer",background:"rgba(37,99,235,0.82)",border:"none",display:"inline-flex",alignItems:"center",font:"inherit",fontSize:10,fontWeight:600,zIndex:Ln},X7={...Vp,pointerEvents:"auto",cursor:"pointer",background:"rgba(37,99,235,0.82)",border:"none",display:"inline-flex",alignItems:"center",font:"inherit",fontSize:10,fontWeight:600,zIndex:Ln},J7={above:{top:-30,left:"50%",transform:"translateX(-50%)"},below:{bottom:-30,left:"50%",transform:"translateX(-50%)"},left:{left:-30,top:"50%",transform:"translateY(-50%)"},right:{right:-30,top:"50%",transform:"translateY(-50%)"}};function Z7(e){const{selectedBox:t,points:n,onAddRequest:o}=e,{t:r}=tt();if(!t||n.length===0)return null;const s=Mp(t),l=s==="outside"?Pp(t):null;return i.jsx("div",{className:"g7le-insertion-affordances","data-testid":"g7le-insertion-affordances",style:{position:"absolute",left:t.left,top:t.top,width:t.width,height:t.height,pointerEvents:"none"},children:n.map(d=>{const c=l?{left:l[d.direction].left,top:l[d.direction].top}:J7[d.direction],p=r(`layout_editor.insertion.${d.direction}`);return i.jsx("button",{type:"button",disabled:d.disabled,"data-testid":`g7le-insertion-${d.direction}`,"data-disabled":d.disabled?"true":"false","data-placement":s,"aria-label":p,title:p,onClick:f=>{f.stopPropagation(),d.disabled||o(d)},style:{position:"absolute",...c,width:24,height:24,borderRadius:12,border:"1px solid #2563eb",background:d.disabled?"#cbd5e1":"#fff",color:d.disabled?"#94a3b8":"#2563eb",cursor:d.disabled?"not-allowed":"pointer",opacity:d.disabled?.4:1,fontSize:16,fontWeight:700,lineHeight:"20px",padding:0,pointerEvents:"auto",boxShadow:"0 1px 3px rgba(0,0,0,0.15)",zIndex:Ln},children:"+"},d.direction)})})}const Rr=typeof window<"u"&&typeof window.document<"u"&&typeof window.document.createElement<"u";function co(e){const t=Object.prototype.toString.call(e);return t==="[object Window]"||t==="[object global]"}function la(e){return"nodeType"in e}function xt(e){var t,n;return e?co(e)?e:la(e)&&(t=(n=e.ownerDocument)==null?void 0:n.defaultView)!=null?t:window:window}function da(e){const{Document:t}=xt(e);return e instanceof t}function jo(e){return co(e)?!1:e instanceof xt(e).HTMLElement}function Up(e){return e instanceof xt(e).SVGElement}function uo(e){return e?co(e)?e.document:la(e)?da(e)?e:jo(e)||Up(e)?e.ownerDocument:document:document:document}const sn=Rr?u.useLayoutEffect:u.useEffect;function ei(e){const t=u.useRef(e);return sn(()=>{t.current=e}),u.useCallback(function(){for(var n=arguments.length,o=new Array(n),r=0;r{e.current=setInterval(o,r)},[]),n=u.useCallback(()=>{e.current!==null&&(clearInterval(e.current),e.current=null)},[]);return[t,n]}function Ho(e,t){t===void 0&&(t=[e]);const n=u.useRef(e);return sn(()=>{n.current!==e&&(n.current=e)},t),n}function Fo(e,t){const n=u.useRef();return u.useMemo(()=>{const o=e(n.current);return n.current=o,o},[...t])}function ti(e){const t=ei(e),n=u.useRef(null),o=u.useCallback(r=>{r!==n.current&&t?.(r,n.current),n.current=r},[]);return[n,o]}function ni(e){const t=u.useRef();return u.useEffect(()=>{t.current=e},[e]),t.current}let ca={};function oi(e,t){return u.useMemo(()=>{if(t)return t;const n=ca[e]==null?0:ca[e]+1;return ca[e]=n,e+"-"+n},[e,t])}function qp(e){return function(t){for(var n=arguments.length,o=new Array(n>1?n-1:0),r=1;r{const l=Object.entries(a);for(const[d,c]of l){const p=s[d];p!=null&&(s[d]=p+e*c)}return s},{...t})}}const po=qp(1),ri=qp(-1);function R7(e){return"clientX"in e&&"clientY"in e}function ua(e){if(!e)return!1;const{KeyboardEvent:t}=xt(e.target);return t&&e instanceof t}function e2(e){if(!e)return!1;const{TouchEvent:t}=xt(e.target);return t&&e instanceof t}function ii(e){if(e2(e)){if(e.touches&&e.touches.length){const{clientX:t,clientY:n}=e.touches[0];return{x:t,y:n}}else if(e.changedTouches&&e.changedTouches.length){const{clientX:t,clientY:n}=e.changedTouches[0];return{x:t,y:n}}}return R7(e)?{x:e.clientX,y:e.clientY}:null}const Ko=Object.freeze({Translate:{toString(e){if(!e)return;const{x:t,y:n}=e;return"translate3d("+(t?Math.round(t):0)+"px, "+(n?Math.round(n):0)+"px, 0)"}},Scale:{toString(e){if(!e)return;const{scaleX:t,scaleY:n}=e;return"scaleX("+t+") scaleY("+n+")"}},Transform:{toString(e){if(e)return[Ko.Translate.toString(e),Ko.Scale.toString(e)].join(" ")}},Transition:{toString(e){let{property:t,duration:n,easing:o}=e;return t+" "+n+"ms "+o}}}),Gp="a,frame,iframe,input:not([type=hidden]):not(:disabled),select:not(:disabled),textarea:not(:disabled),button:not(:disabled),*[tabindex]";function t2(e){return e.matches(Gp)?e:e.querySelector(Gp)}const n2={display:"none"};function o2(e){let{id:t,value:n}=e;return u.createElement("div",{id:t,style:n2},n)}function r2(e){let{id:t,announcement:n,ariaLiveType:o="assertive"}=e;const r={position:"fixed",top:0,left:0,width:1,height:1,margin:-1,border:0,padding:0,overflow:"hidden",clip:"rect(0 0 0 0)",clipPath:"inset(100%)",whiteSpace:"nowrap"};return u.createElement("div",{id:t,style:r,role:"status","aria-live":o,"aria-atomic":!0},n)}function i2(){const[e,t]=u.useState("");return{announce:u.useCallback(o=>{o!=null&&t(o)},[]),announcement:e}}const Yp=u.createContext(null);function s2(e){const t=u.useContext(Yp);u.useEffect(()=>{if(!t)throw new Error("useDndMonitor must be used within a children of ");return t(e)},[e,t])}function a2(){const[e]=u.useState(()=>new Set),t=u.useCallback(o=>(e.add(o),()=>e.delete(o)),[e]);return[u.useCallback(o=>{let{type:r,event:s}=o;e.forEach(a=>{var l;return(l=a[r])==null?void 0:l.call(a,s)})},[e]),t]}const l2={draggable:` +`);return t.length>0&&t[t.length-1]===""&&t.pop(),t}const ip=4e3;function Q$(e,t,n=3){const o=rp(e),r=rp(t);let s=0;const a=Math.min(o.length,r.length);for(;sip||p.length>ip)return{hunks:[],addedCount:-1,removedCount:-1,identical:!1};const f=J$(c,p),b=Z$(c,p,f),g=[];let m=s,h=s,v=0,y=0;for(let k=0;k{const s=e.slice(r.start,r.end+1);let a=0,l=0,d=0,c=0;for(const g of s)g.oldLine!==null&&(a===0&&(a=g.oldLine),d++),g.newLine!==null&&(l===0&&(l=g.newLine),c++);const p=a||(d===0?0:1),f=l||(c===0?0:1);return{header:`@@ -${p},${d} +${f},${c} @@`,oldStart:p,oldCount:d,newStart:f,newCount:c,lines:s}})}function sp(e){return JSON.stringify(ia(e),null,2)}function ia(e){if(Array.isArray(e))return e.map(ia);if(e&&typeof e=="object"){const t=e,n={};for(const o of Object.keys(t).sort())n[o]=ia(t[o]);return n}return e}const eE={display:"flex",flexDirection:"column",height:"100%",minHeight:0},tE={display:"flex",alignItems:"center",gap:8,padding:"12px 16px",borderBottom:"1px solid #e2e8f0",background:"#f8fafc"},nE={display:"inline-flex",alignItems:"center",gap:4,padding:"4px 10px",borderRadius:6,border:"1px solid #cbd5e1",background:"#ffffff",color:"#0f172a",fontSize:12,fontWeight:600,cursor:"pointer",outline:"none"},oE={marginLeft:"auto",border:"none",background:"transparent",fontSize:16,color:"#64748b",cursor:"pointer",outline:"none"},rE={display:"flex",alignItems:"center",gap:12,padding:"8px 16px",borderBottom:"1px solid #e2e8f0",background:"#ffffff",fontSize:12,fontWeight:600},iE={flex:1,minHeight:0,overflow:"auto",background:"#ffffff",fontFamily:'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace',fontSize:12,lineHeight:1.5},ap={padding:"24px 12px",textAlign:"center",color:"#94a3b8",fontSize:13},sE={padding:"2px 12px",background:"#f1f5f9",color:"#64748b",borderTop:"1px solid #e2e8f0",borderBottom:"1px solid #e2e8f0",userSelect:"none"};function aE(e){return e==="add"?{row:{background:"#e6ffed"},gutter:{background:"#cdffd8",color:"#22863a"},sign:"+",signColor:"#22863a"}:e==="remove"?{row:{background:"#ffeef0"},gutter:{background:"#ffdce0",color:"#b31d28"},sign:"-",signColor:"#b31d28"}:{row:{background:"#ffffff"},gutter:{background:"#f8fafc",color:"#94a3b8"},sign:" ",signColor:"#94a3b8"}}const lp={display:"inline-block",width:44,textAlign:"right",padding:"0 6px",color:"#94a3b8",userSelect:"none",borderRight:"1px solid #e2e8f0",whiteSpace:"nowrap"};function lE({oldVersion:e,newVersion:t,oldContent:n,newContent:o,t:r,onBack:s,onClose:a}){const l=u.useMemo(()=>{const c=sp(n),p=sp(o);return Q$(c,p)},[n,o]),d=l.addedCount<0;return i.jsxs("div",{className:"g7le-version-diff","data-testid":"g7le-version-diff",style:eE,children:[i.jsxs("div",{"data-modal-drag-handle":!0,style:tE,children:[i.jsxs("button",{type:"button",onClick:s,style:nE,"data-testid":"g7le-version-diff-back",children:["← ",r("layout_editor.version_history.diff_back")]}),i.jsx("span",{style:{fontSize:14,fontWeight:700},"data-testid":"g7le-version-diff-title",children:r("layout_editor.version_history.diff_title",{old:e,new:t})}),i.jsx("button",{type:"button","aria-label":r("layout_editor.version_history.close"),onClick:a,style:oE,"data-testid":"g7le-version-diff-close",children:"✕"})]}),!d&&i.jsxs("div",{style:rE,"data-testid":"g7le-version-diff-summary",children:[i.jsx("span",{style:{color:"#16a34a"},"data-testid":"g7le-version-diff-added",children:r("layout_editor.version_history.added",{count:Math.max(0,l.addedCount)})}),i.jsx("span",{style:{color:"#dc2626"},"data-testid":"g7le-version-diff-removed",children:r("layout_editor.version_history.removed",{count:Math.max(0,l.removedCount)})})]}),i.jsx("div",{style:iE,children:d?i.jsx("div",{style:ap,"data-testid":"g7le-version-diff-too-large",children:r("layout_editor.version_history.diff_too_large")}):l.identical?i.jsx("div",{style:ap,"data-testid":"g7le-version-diff-identical",children:r("layout_editor.version_history.diff_identical")}):l.hunks.map((c,p)=>i.jsxs("div",{"data-testid":`g7le-version-diff-hunk-${p}`,children:[i.jsx("div",{style:sE,"data-testid":`g7le-version-diff-hunk-header-${p}`,children:c.header}),c.lines.map((f,b)=>{const g=aE(f.kind);return i.jsxs("div",{style:{display:"flex",alignItems:"stretch",...g.row},"data-kind":f.kind,"data-testid":`g7le-version-diff-line-${p}-${b}`,children:[i.jsx("span",{style:{...lp,...g.gutter},"aria-hidden":"true",children:f.oldLine??""}),i.jsx("span",{style:{...lp,...g.gutter},"aria-hidden":"true",children:f.newLine??""}),i.jsx("span",{style:{display:"inline-block",width:16,textAlign:"center",color:g.signColor,userSelect:"none",fontWeight:700},"aria-hidden":"true",children:g.sign}),i.jsx("span",{style:{whiteSpace:"pre-wrap",wordBreak:"break-word",flex:1,padding:"0 4px"},children:f.content})]},b)})]},p))})]})}const dE={display:"flex",flexDirection:"column",height:"100%",minHeight:0},cE={display:"flex",alignItems:"center",gap:8,padding:"12px 16px",borderBottom:"1px solid #e2e8f0",background:"#f8fafc"},uE={marginLeft:"auto",border:"none",background:"transparent",fontSize:16,color:"#64748b",cursor:"pointer",outline:"none"},pE={flex:1,minHeight:0,overflowY:"auto",padding:12},dp={padding:"24px 12px",textAlign:"center",color:"#94a3b8",fontSize:13},fE={margin:"8px 16px 0",padding:"8px 12px",borderRadius:6,background:"#fef2f2",color:"#b91c1c",fontSize:12,border:"1px solid #fecaca"},gE={display:"flex",flexDirection:"column",gap:8},hE={display:"flex",alignItems:"center",gap:12,padding:"10px 12px",borderRadius:8,border:"1px solid #e2e8f0",background:"#ffffff"},yE={display:"inline-flex",alignItems:"center",justifyContent:"center",minWidth:44,padding:"2px 8px",borderRadius:999,background:"#eff6ff",color:"#1d4ed8",fontSize:12,fontWeight:700},bE={display:"inline-flex",alignItems:"center",padding:"1px 8px",borderRadius:999,background:"#0f172a",color:"#ffffff",fontSize:10,fontWeight:700,letterSpacing:.3},mE={display:"flex",alignItems:"center",gap:10,fontSize:11,fontWeight:600},vE={display:"flex",flexDirection:"column",gap:2,minWidth:0,flex:1},cp={padding:"6px 12px",borderRadius:6,border:"1px solid #cbd5e1",background:"#ffffff",color:"#0f172a",fontSize:12,fontWeight:600,cursor:"pointer",outline:"none",whiteSpace:"nowrap"};function up(e){return e.full_content&&typeof e.full_content=="object"?e.full_content:{endpoint:e.endpoint,components:e.components,data_sources:e.data_sources,metadata:e.metadata}}function _E(e){if(!e)return"";const t=Date.parse(e);if(Number.isNaN(t))return e;try{return new Date(t).toLocaleString()}catch{return e}}function xE({templateIdentifier:e,target:t,t:n,onRestored:o,onClose:r}){const{versions:s,isLoading:a,isLoadingMore:l,error:d,restoringId:c,hasMore:p,loadVersions:f,loadMore:b,restore:g,loadVersionDetail:m}=X$(e,t,o),[h,v]=u.useState(null),[y,_]=u.useState(null),[w,k]=u.useState(null);u.useEffect(()=>{f()},[f]);const x=u.useCallback(async S=>{v(null),k(S.version);try{const D=await m(S.version);if(D.kind!=="success"){v(D.kind==="not_found"?n("layout_editor.version_history.restore_not_found"):D.message);return}const E=S.version-1,I=s.some(T=>T.version===E);let N={};if(I){const T=await m(E);if(T.kind==="success")N=up(T.detail);else if(T.kind==="network_error"){v(T.message);return}}_({oldVersion:I?E:0,newVersion:S.version,oldContent:N,newContent:up(D.detail)})}finally{k(null)}},[m,s,n]),$=u.useCallback(async S=>{v(null);const D=n("layout_editor.version_history.restore_confirm");if(!(typeof window<"u"?window.confirm(D):!0))return;const I=await g(S);I.kind==="success"?r():I.kind==="not_found"?v(n("layout_editor.version_history.restore_not_found")):v(I.message)},[g,r,n]),C=h??d;return y?i.jsx(lE,{oldVersion:y.oldVersion,newVersion:y.newVersion,oldContent:y.oldContent,newContent:y.newContent,t:n,onBack:()=>_(null),onClose:r}):i.jsxs("div",{className:"g7le-version-history","data-testid":"g7le-version-history",style:dE,children:[i.jsxs("div",{"data-modal-drag-handle":!0,style:cE,children:[i.jsxs("span",{style:{fontSize:14,fontWeight:700},"data-testid":"g7le-version-history-title",children:["🕘 ",n("layout_editor.version_history.title")]}),i.jsx("button",{type:"button","aria-label":n("layout_editor.version_history.close"),onClick:r,style:uE,"data-testid":"g7le-version-history-close",children:"✕"})]}),C&&i.jsx("div",{style:fE,"data-testid":"g7le-version-history-error",children:C}),i.jsx("div",{style:pE,children:a?i.jsx("div",{style:dp,"data-testid":"g7le-version-history-loading",children:n("layout_editor.version_history.loading")}):s.length===0?i.jsx("div",{style:dp,"data-testid":"g7le-version-history-empty",children:n("layout_editor.version_history.empty")}):i.jsxs("div",{"data-testid":"g7le-version-history-list",style:gE,children:[s.map((S,D)=>{const E=S.changes_summary,I=E?.added_count??0,N=E?.removed_count??0,T=E?.char_diff??0,O=_E(S.created_at),M=c===S.id,B=D===0;return i.jsxs("div",{style:hE,"data-testid":`g7le-version-row-${S.id}`,children:[i.jsxs("span",{style:yE,"data-testid":`g7le-version-badge-${S.id}`,children:["v",S.version]}),i.jsxs("div",{style:vE,children:[i.jsxs("span",{style:{display:"flex",alignItems:"center",gap:8},children:[O&&i.jsx("span",{style:{fontSize:12,color:"#475569",fontWeight:600},children:O}),B&&i.jsx("span",{style:bE,"data-testid":`g7le-version-latest-${S.id}`,children:n("layout_editor.version_history.latest")})]}),i.jsxs("span",{style:mE,"data-testid":`g7le-version-summary-${S.id}`,children:[i.jsx("span",{style:{color:"#16a34a"},"data-testid":`g7le-version-added-${S.id}`,children:n("layout_editor.version_history.added",{count:I})}),i.jsx("span",{style:{color:"#dc2626"},"data-testid":`g7le-version-removed-${S.id}`,children:n("layout_editor.version_history.removed",{count:N})}),i.jsx("span",{style:{color:"#94a3b8",fontWeight:500},"data-testid":`g7le-version-chars-${S.id}`,children:n("layout_editor.version_history.chars",{diff:`${T>=0?"+":""}${T}`})})]}),i.jsx("span",{style:{fontSize:11,color:"#94a3b8"},"data-testid":`g7le-version-author-${S.id}`,children:n("layout_editor.version_history.saved_by",{name:S.created_by_name??n("layout_editor.version_history.unknown_author")})})]}),i.jsxs("div",{style:{display:"flex",alignItems:"center",gap:6},children:[i.jsx("button",{type:"button",onClick:()=>void x(S),disabled:w!==null,style:{...cp,background:"#f8fafc",opacity:w!==null?.6:1},"data-testid":`g7le-version-compare-${S.id}`,children:w===S.version?n("layout_editor.version_history.comparing"):n("layout_editor.version_history.compare")}),!B&&i.jsx("button",{type:"button",onClick:()=>void $(S.id),disabled:M||c!==null,style:{...cp,opacity:M||c!==null?.6:1},"data-testid":`g7le-version-restore-${S.id}`,children:n(M?"layout_editor.version_history.restoring":"layout_editor.version_history.restore")})]})]},S.id)}),p&&i.jsx("button",{type:"button","data-testid":"g7le-version-history-load-more",style:wE,disabled:l,onClick:()=>{b()},children:n(l?"layout_editor.version_history.loading":"layout_editor.version_history.load_more")})]})})]})}const wE={width:"100%",marginTop:8,padding:"8px 12px",fontSize:12,color:"#475569",background:"#f8fafc",border:"1px solid #e2e8f0",borderRadius:6,cursor:"pointer"};function Ho(e){return`/api/admin/extensions/${encodeURIComponent(e.type)}/${encodeURIComponent(e.identifier)}/custom-assets`}function Fo(e,t){const n=e?.message;return typeof n=="string"&&n!==""?n:`HTTP ${t}`}function kE(e){const[t,n]=u.useState([]),[o,r]=u.useState(null),[s,a]=u.useState(!1),[l,d]=u.useState(!1),[c,p]=u.useState(null),f=u.useCallback(async()=>{a(!0),p(null);try{const v=await fetch(Ho(e),{credentials:"same-origin",headers:Me()}),y=await v.json().catch(()=>null);if(v.status===401||v.status===403){d(!0),n([]);return}if(d(!1),!v.ok){p(Fo(y,v.status)),n([]);return}const _=y?.data??{},w=_.files;n(Array.isArray(w)?w:[]),r({editable_extensions:_.editable_extensions??[],uploadable_extensions:_.uploadable_extensions??[],max_text_bytes:_.max_text_bytes??0,max_upload_bytes:_.max_upload_bytes??0})}catch(v){p(v instanceof Error?v.message:"network error"),n([])}finally{a(!1)}},[e.type,e.identifier]),b=u.useCallback(async v=>{p(null);try{const y=`${Ho(e)}/content?path=${encodeURIComponent(v)}`,_=await fetch(y,{credentials:"same-origin",headers:Me()}),w=await _.json().catch(()=>null);if(!_.ok)return p(Fo(w,_.status)),null;const k=w?.data?.content;return typeof k=="string"?k:""}catch(y){return p(y instanceof Error?y.message:"network error"),null}},[e.type,e.identifier]),g=u.useCallback(async(v,y)=>{p(null);try{const _=await fetch(`${Ho(e)}/content`,{method:"PUT",credentials:"same-origin",headers:Me({"Content-Type":"application/json"}),body:JSON.stringify({path:v,content:y})}),w=await _.json().catch(()=>null);return _.ok?(await f(),!0):(p(Fo(w,_.status)),!1)}catch(_){return p(_ instanceof Error?_.message:"network error"),!1}},[e.type,e.identifier,f]),m=u.useCallback(async(v,y)=>{p(null);try{const _=new FormData;_.append("file",v),y&&_.append("directory",y);const w=await fetch(`${Ho(e)}/upload`,{method:"POST",credentials:"same-origin",headers:Me(),body:_}),k=await w.json().catch(()=>null);return w.ok?(await f(),!0):(p(Fo(k,w.status)),!1)}catch(_){return p(_ instanceof Error?_.message:"network error"),!1}},[e.type,e.identifier,f]),h=u.useCallback(async v=>{p(null);try{const y=`${Ho(e)}?path=${encodeURIComponent(v)}`,_=await fetch(y,{method:"DELETE",credentials:"same-origin",headers:Me()}),w=await _.json().catch(()=>null);return _.ok?(await f(),!0):(p(Fo(w,_.status)),!1)}catch(y){return p(y instanceof Error?y.message:"network error"),!1}},[e.type,e.identifier,f]);return{files:t,meta:o,isLoading:s,forbidden:l,error:c,reload:f,read:b,save:g,upload:m,remove:h}}const SE={display:"flex",flexDirection:"column",height:"100%",minHeight:0},CE={display:"flex",alignItems:"center",gap:8,padding:"12px 16px",borderBottom:"1px solid #e2e8f0",background:"#f8fafc",fontWeight:600,color:"#0f172a"},$E={marginLeft:"auto",border:"none",background:"transparent",fontSize:16,color:"#64748b",cursor:"pointer",outline:"none"},EE={flex:1,minHeight:0,display:"flex"},IE={width:260,flexShrink:0,borderRight:"1px solid #e2e8f0",overflowY:"auto",padding:8},AE={flex:1,minWidth:0,display:"flex",flexDirection:"column",padding:12,gap:8},pp={display:"flex",alignItems:"center",gap:6,width:"100%",padding:"6px 8px",borderRadius:6,borderWidth:1,borderStyle:"solid",borderColor:"transparent",backgroundColor:"transparent",fontSize:12,color:"#334155",cursor:"pointer",textAlign:"left"},TE={...pp,backgroundColor:"#eff6ff",borderColor:"#bfdbfe",color:"#1d4ed8",fontWeight:600},DE={margin:16,padding:16,borderRadius:8,border:"1px solid #fcd34d",background:"#fffbeb",color:"#92400e",fontSize:13,lineHeight:1.7},zE={padding:"8px 12px",borderRadius:6,border:"1px solid #fecaca",background:"#fef2f2",color:"#b91c1c",fontSize:12},NE={flex:1,minHeight:220,width:"100%",boxSizing:"border-box",padding:10,borderRadius:6,borderWidth:1,borderStyle:"solid",borderColor:"#cbd5e1",fontFamily:"ui-monospace, SFMono-Regular, Menlo, Consolas, monospace",fontSize:12,lineHeight:1.6,resize:"vertical"},fp={display:"flex",alignItems:"center",gap:8,flexWrap:"wrap"},ao={padding:"6px 12px",borderRadius:6,borderWidth:1,borderStyle:"solid",borderColor:"#cbd5e1",backgroundColor:"#ffffff",color:"#334155",fontSize:12,cursor:"pointer"},ME={...ao,backgroundColor:"#2563eb",borderColor:"#2563eb",color:"#ffffff",fontWeight:600},LE={...ao,borderColor:"#fca5a5",color:"#b91c1c"},PE={...ao,cursor:"text",minWidth:200},OE={...ao,cursor:"pointer",minWidth:220};function BE(e,t){if(typeof e=="string"&&e!=="")return e;if(e&&typeof e=="object"){const n=e,o=window?.G7Config?.locale??"ko",r=n[o]??n.ko??n.en??Object.values(n)[0];if(typeof r=="string"&&r!=="")return r}return t}function WE(e){const t=(typeof window<"u"?window.G7Config:null)??{},n=[{type:"template",identifier:e,label:e}];for(const[o,r]of[["module",t.activeModules],["plugin",t.activePlugins]])if(Array.isArray(r))for(const s of r){const a=typeof s?.identifier=="string"?s.identifier:"";a&&n.push({type:o,identifier:a,label:BE(s?.display_name,a)})}return n}function jE(e){return e<1024?`${e} B`:e<1024*1024?`${Math.round(e/1024)} KB`:`${(e/(1024*1024)).toFixed(1)} MB`}function HE(e){const{templateIdentifier:t,t:n,onClose:o}=e,r=u.useMemo(()=>WE(t),[t]),[s,a]=u.useState(`template:${t}`),l=u.useMemo(()=>r.find(C=>`${C.type}:${C.identifier}`===s)??r[0],[r,s]),d=kE({type:l.type,identifier:l.identifier}),{reload:c}=d,[p,f]=u.useState(null),[b,g]=u.useState(""),[m,h]=u.useState(""),[v,y]=u.useState(!1),_=u.useRef(null);u.useEffect(()=>{c()},[c]),u.useEffect(()=>{f(null),g(""),h("")},[s]);const w=u.useCallback(async C=>{if(f(C),!C.editable){g("");return}y(!0);const S=await d.read(C.path);y(!1),g(S??"")},[d]),k=u.useCallback(async()=>{const C=p?.path??m.trim();if(!C)return;y(!0);const S=await d.save(C,b);y(!1),S&&!p&&(h(""),f({path:C,name:C.split("/").pop()??C,extension:(C.split(".").pop()??"").toLowerCase(),size:b.length,modified_at:new Date().toISOString(),editable:!0,loaded:!0}))},[d,p,m,b]),x=u.useCallback(async()=>{if(!p||typeof window<"u"&&!window.confirm(n("layout_editor.custom_assets.delete_confirm")))return;y(!0);const C=await d.remove(p.path);y(!1),C&&(f(null),g(""))},[d,p,n]),$=u.useCallback(async C=>{const S=C.target.files?.[0];S&&(y(!0),await d.upload(S),y(!1),_.current&&(_.current.value=""))},[d]);return i.jsxs("div",{className:"g7le-custom-assets",style:SE,"data-testid":"g7le-custom-assets-modal",children:[i.jsxs("div",{style:CE,children:[i.jsxs("span",{children:["🎨 ",n("layout_editor.custom_assets.title")]}),i.jsx("select",{value:s,onChange:C=>a(C.target.value),style:OE,"aria-label":n("layout_editor.custom_assets.target_label"),"data-testid":"g7le-custom-assets-target",children:r.map(C=>i.jsxs("option",{value:`${C.type}:${C.identifier}`,children:[n(`layout_editor.custom_assets.type_${C.type}`)," · ",C.label]},`${C.type}:${C.identifier}`))}),i.jsx("button",{type:"button",style:$E,onClick:o,"aria-label":n("layout_editor.custom_assets.close"),"data-testid":"g7le-custom-assets-close",children:"✕"})]}),d.forbidden?i.jsx("div",{style:DE,"data-testid":"g7le-custom-assets-forbidden",children:n("layout_editor.custom_assets.forbidden")}):i.jsxs("div",{style:EE,children:[i.jsxs("div",{style:IE,"data-testid":"g7le-custom-assets-list",children:[d.isLoading&&d.files.length===0?i.jsx("div",{style:{padding:12,color:"#94a3b8",fontSize:12},children:n("layout_editor.custom_assets.loading")}):null,!d.isLoading&&d.files.length===0?i.jsx("div",{style:{padding:12,color:"#94a3b8",fontSize:12},children:n("layout_editor.custom_assets.empty")}):null,d.files.map(C=>i.jsxs("button",{type:"button",style:p?.path===C.path?TE:pp,onClick:()=>void w(C),"data-testid":`g7le-custom-asset-row-${C.path}`,"data-loaded":C.loaded?"true":"false",children:[i.jsx("span",{style:{flex:1,minWidth:0,overflow:"hidden",textOverflow:"ellipsis"},children:C.path}),i.jsx("span",{style:{color:"#94a3b8",fontSize:11},children:jE(C.size)})]},C.path))]}),i.jsxs("div",{style:AE,children:[d.error?i.jsx("div",{style:zE,"data-testid":"g7le-custom-assets-error",children:d.error}):null,i.jsxs("div",{style:fp,children:[i.jsx("input",{type:"text",value:p?p.path:m,readOnly:!!p,onChange:C=>h(C.target.value),placeholder:n("layout_editor.custom_assets.new_path_placeholder"),style:PE,"aria-label":n("layout_editor.custom_assets.path_label"),"data-testid":"g7le-custom-assets-path"}),p?i.jsx("button",{type:"button",style:ao,onClick:()=>{f(null),g("")},"data-testid":"g7le-custom-assets-new",children:n("layout_editor.custom_assets.new_file")}):null]}),p&&!p.editable?i.jsx("div",{style:{color:"#64748b",fontSize:12,lineHeight:1.7},children:n("layout_editor.custom_assets.binary_hint",{extension:p.extension})}):i.jsx("textarea",{value:b,onChange:C=>g(C.target.value),style:NE,spellCheck:!1,"aria-label":n("layout_editor.custom_assets.content_label"),"data-testid":"g7le-custom-assets-content"}),i.jsxs("div",{style:fp,children:[i.jsx("button",{type:"button",style:ME,disabled:v||(p?!p.editable:m.trim()===""),onClick:()=>void k(),"data-testid":"g7le-custom-assets-save",children:n("layout_editor.custom_assets.save")}),i.jsx("button",{type:"button",style:ao,disabled:v,onClick:()=>_.current?.click(),"data-testid":"g7le-custom-assets-upload",children:n("layout_editor.custom_assets.upload")}),i.jsx("input",{ref:_,type:"file",style:{display:"none"},onChange:C=>void $(C),"data-testid":"g7le-custom-assets-file-input"}),i.jsx("button",{type:"button",style:LE,disabled:v||!p,onClick:()=>void x(),"data-testid":"g7le-custom-assets-delete",children:n("layout_editor.custom_assets.delete")}),i.jsx("span",{style:{color:"#94a3b8",fontSize:11},children:n("layout_editor.custom_assets.apply_hint")})]})]})]})]})}const gp=[{id:"copy",group:"clipboard",combos:[{key:"c",mod:!0}],labelKey:"layout_editor.shortcuts.action.copy",requiresSelection:!0},{id:"cut",group:"clipboard",combos:[{key:"x",mod:!0}],labelKey:"layout_editor.shortcuts.action.cut",requiresSelection:!0},{id:"paste",group:"clipboard",combos:[{key:"v",mod:!0}],labelKey:"layout_editor.shortcuts.action.paste"},{id:"openProps",group:"element",combos:[{key:"enter"}],labelKey:"layout_editor.shortcuts.action.open_props",requiresSelection:!0},{id:"delete",group:"element",combos:[{key:"delete"},{key:"backspace"}],labelKey:"layout_editor.shortcuts.action.delete",requiresSelection:!0},{id:"selectParent",group:"element",combos:[{key:"arrowup"}],labelKey:"layout_editor.shortcuts.action.select_parent",requiresSelection:!0},{id:"deselect",group:"element",combos:[{key:"escape"}],labelKey:"layout_editor.shortcuts.action.deselect"},{id:"addElement",group:"element",combos:[{key:"/",mod:!0}],labelKey:"layout_editor.shortcuts.action.add_element"},{id:"undo",group:"history",combos:[{key:"z",mod:!0}],labelKey:"layout_editor.shortcuts.action.undo"},{id:"redo",group:"history",combos:[{key:"z",mod:!0,shift:!0},{key:"y",mod:!0}],labelKey:"layout_editor.shortcuts.action.redo"},{id:"editCode",group:"view",combos:[{key:"e",mod:!0}],labelKey:"layout_editor.shortcuts.action.edit_code"},{id:"preview",group:"view",combos:[{key:"p",mod:!0}],labelKey:"layout_editor.shortcuts.action.preview"},{id:"translations",group:"view",combos:[{key:"l",mod:!0}],labelKey:"layout_editor.shortcuts.action.translations"},{id:"help",group:"view",combos:[{key:"?"}],labelKey:"layout_editor.shortcuts.action.help"},{id:"reset",group:"document",combos:[{key:"r",mod:!0,shift:!0}],labelKey:"layout_editor.shortcuts.action.reset"},{id:"save",group:"document",combos:[{key:"s",mod:!0}],labelKey:"layout_editor.shortcuts.action.save"},{id:"exit",group:"document",combos:[{key:"escape"}],labelKey:"layout_editor.shortcuts.action.exit"}];function FE(e){return{key:(e.key||"").toLowerCase(),mod:e.ctrlKey||e.metaKey,shift:e.shiftKey,alt:e.altKey}}function KE(e,t){return e.key===t.key&&!!e.mod==!!t.mod&&!!e.shift==!!t.shift&&!!e.alt==!!t.alt}function VE(e){for(const t of gp)if(t.combos.some(n=>KE(n,e)))return t;return null}function UE(e,t){const n=[];return e.mod&&n.push(t?"⌘":"Ctrl"),e.shift&&n.push(t?"⇧":"Shift"),e.alt&&n.push(t?"⌥":"Alt"),n.push(qE(e.key)),n.join(" + ")}function qE(e){return{arrowup:"↑",arrowdown:"↓",arrowleft:"←",arrowright:"→",enter:"Enter",escape:"Esc",delete:"Delete",backspace:"Backspace"," ":"Space"}[e]??(e.length===1?e.toUpperCase():e)}const GE=["clipboard","element","history","view","document"],YE={clipboard:"layout_editor.shortcuts.group.clipboard",element:"layout_editor.shortcuts.group.element",history:"layout_editor.shortcuts.group.history",view:"layout_editor.shortcuts.group.view",document:"layout_editor.shortcuts.group.document"};function XE(){return typeof navigator>"u"?!1:/mac|iphone|ipad|ipod/i.test(navigator.platform||navigator.userAgent||"")}function JE({t:e,onClose:t,isMac:n}){const o=n??XE(),r=u.useMemo(()=>{const s=new Map;for(const a of gp){const l=s.get(a.group)??[];l.push(a),s.set(a.group,l)}return s},[]);return i.jsxs("div",{className:"g7le-shortcut-help","data-testid":"g7le-shortcut-help",style:ZE,children:[i.jsxs("div",{style:QE,children:[i.jsx("span",{style:RE,children:e("layout_editor.shortcuts.title")}),i.jsx("button",{type:"button","data-testid":"g7le-shortcut-help-close",onClick:t,"aria-label":e("layout_editor.shortcuts.close"),style:e7,children:"✕"})]}),i.jsx("div",{style:t7,children:GE.map(s=>{const a=r.get(s);return!a||a.length===0?null:i.jsxs("div",{style:n7,"data-testid":`g7le-shortcut-group-${s}`,children:[i.jsx("div",{style:o7,children:e(YE[s])}),i.jsx("table",{style:r7,children:i.jsx("tbody",{children:a.map(l=>i.jsxs("tr",{"data-testid":`g7le-shortcut-row-${l.id}`,children:[i.jsx("td",{style:i7,children:e(l.labelKey)}),i.jsx("td",{style:s7,children:l.combos.map((d,c)=>i.jsxs(u.Fragment,{children:[c>0&&i.jsx("span",{style:l7,children:e("layout_editor.shortcuts.or")}),i.jsx("kbd",{style:a7,children:UE(d,o)})]},c))})]},l.id))})})]},s)})}),i.jsx("div",{style:d7,children:e("layout_editor.shortcuts.footnote")})]})}const ZE={display:"flex",flexDirection:"column",padding:20,gap:16,width:"100%",boxSizing:"border-box"},QE={display:"flex",alignItems:"center",justifyContent:"space-between",paddingBottom:12,borderBottom:"1px solid #e2e8f0"},RE={fontSize:15,fontWeight:700,color:"#0f172a"},e7={width:28,height:28,border:"none",background:"transparent",color:"#64748b",cursor:"pointer",fontSize:14,borderRadius:6},t7={display:"grid",gridTemplateColumns:"1fr 1fr",columnGap:28,rowGap:18,alignItems:"start"},n7={display:"flex",flexDirection:"column",gap:6},o7={fontSize:12,fontWeight:700,color:"#2563eb"},r7={width:"100%",borderCollapse:"collapse"},i7={padding:"4px 0",fontSize:12.5,color:"#334155",textAlign:"left",whiteSpace:"nowrap",verticalAlign:"middle"},s7={padding:"4px 0",paddingLeft:12,textAlign:"right",verticalAlign:"middle"},a7={display:"inline-block",padding:"1px 6px",fontSize:11,fontFamily:"ui-monospace, monospace",color:"#0f172a",background:"#f1f5f9",border:"1px solid #cbd5e1",borderBottomWidth:2,borderRadius:5,whiteSpace:"nowrap"},l7={fontSize:11,color:"#94a3b8",margin:"0 4px"},d7={fontSize:11,color:"#94a3b8",paddingTop:12,borderTop:"1px solid #f1f5f9"};function c7(e){if(!e||!(e instanceof HTMLElement))return!1;const t=e.tagName;return!!(t==="INPUT"||t==="TEXTAREA"||t==="SELECT"||e.isContentEditable)}function u7(){return typeof document>"u"?!1:!!document.querySelector(".g7le-modal-backdrop, .g7le-modal")}function hp(e){const{handlers:t,hasSelection:n,enabled:o=!0}=e;u.useEffect(()=>{if(!o||typeof window>"u")return;const r=s=>{if(c7(s.target))return;const a=FE(s),l=VE(a);if(!l||u7()||l.requiresSelection&&!n)return;if(l.key==="escape"||a.key==="escape"){if(n){const c=t.deselect;c&&(s.preventDefault(),c())}else{const c=t.exit;c&&(s.preventDefault(),c())}return}const d=t[l.id];d&&(s.preventDefault(),d())};return window.addEventListener("keydown",r),()=>window.removeEventListener("keydown",r)},[t,n,o])}function p7(e){const{result:t,onDismiss:n,onLoadLatest:o,onKeepMyChanges:r}=e,{t:s}=et();if(u.useEffect(()=>{if(!t||t.kind==="concurrent_modification"||t.kind==="validation_failed"||t.kind==="guard_extension_reassembly")return;const a=setTimeout(n,5e3);return()=>clearTimeout(a)},[t,n]),!t)return null;if(t.kind==="success")return i.jsx(lo,{tone:"success",testId:"g7le-save-banner-success",onDismiss:n,children:s("layout_editor.save.success")});if(t.kind==="validation_failed"){const a=t.errors??{},l=Object.entries(a);return i.jsxs(lo,{tone:"error",testId:"g7le-save-banner-validation",onDismiss:n,children:[i.jsx("strong",{children:s("layout_editor.save.validation_failed")}),l.length>0&&i.jsx("ul",{style:yp,"data-testid":"g7le-save-banner-validation-errors",children:l.map(([d,c])=>i.jsxs("li",{children:[i.jsx("code",{style:bp,children:d}),": ",c.join(", ")]},d))})]})}return t.kind==="blocked_inactive_extension"?i.jsxs(lo,{tone:"warning",testId:"g7le-save-banner-blocked",onDismiss:n,children:[i.jsx("strong",{children:s("layout_editor.save.blocked_inactive_extension")}),t.blockedPaths.length>0&&i.jsx("ul",{style:yp,"data-testid":"g7le-save-banner-blocked-paths",children:t.blockedPaths.map(a=>i.jsx("li",{children:i.jsx("code",{style:bp,children:a})},a))})]}):t.kind==="network_error"?i.jsxs(lo,{tone:"error",testId:"g7le-save-banner-network",onDismiss:n,children:[i.jsx("strong",{children:s("layout_editor.save.network_error_title")}),i.jsx("div",{style:{marginTop:4,fontSize:12},children:t.message})]}):t.kind==="guard_extension_reassembly"?i.jsxs(lo,{tone:"error",testId:"g7le-save-banner-guard-extension-reassembly",onDismiss:n,children:[i.jsx("strong",{children:s("layout_editor.save.guard_extension_reassembly_title")}),i.jsx("div",{style:{marginTop:4,fontSize:12},children:s("layout_editor.save.guard_extension_reassembly",{count:String(t.unassigned)})})]}):t.kind==="guard_no_document"?i.jsx(lo,{tone:"neutral",testId:"g7le-save-banner-guard-no-document",onDismiss:n,children:s("layout_editor.save.guard_no_document")}):i.jsx("div",{role:"dialog","aria-modal":"true","aria-label":s("layout_editor.save.concurrent.title"),"data-testid":"g7le-save-banner-concurrent",style:y7,children:i.jsxs("div",{style:b7,children:[i.jsx("h2",{style:m7,children:s("layout_editor.save.concurrent.title")}),i.jsx("p",{style:v7,children:s("layout_editor.save.concurrent.message")}),i.jsx("div",{style:_7,children:s("layout_editor.save.concurrent.version_info",{current:String(t.currentVersion),yours:String(t.yourVersion)})}),i.jsxs("div",{style:x7,children:[i.jsx("button",{type:"button","data-testid":"g7le-save-banner-concurrent-load-latest",onClick:()=>{o?.(),n()},style:w7,children:s("layout_editor.save.concurrent.load_latest")}),i.jsx("button",{type:"button","data-testid":"g7le-save-banner-concurrent-keep-mine",onClick:()=>{r?.(),n()},style:k7,children:s("layout_editor.save.concurrent.view_my_changes")}),i.jsx("button",{type:"button","data-testid":"g7le-save-banner-concurrent-cancel",onClick:n,style:S7,children:s("layout_editor.save.concurrent.cancel")})]})]})})}function lo(e){const{tone:t,testId:n,onDismiss:o,children:r}=e,s=f7[t];return i.jsxs("div",{role:"status","data-testid":n,style:{...g7,background:s.bg,borderColor:s.border,color:s.fg},children:[i.jsx("div",{style:{flex:1,minWidth:0},children:r}),i.jsx("button",{type:"button",onClick:o,"aria-label":"close","data-testid":`${n}-close`,style:h7,children:"×"})]})}const f7={success:{bg:"#dcfce7",border:"#86efac",fg:"#14532d"},error:{bg:"#fee2e2",border:"#fca5a5",fg:"#7f1d1d"},warning:{bg:"#fef3c7",border:"#fcd34d",fg:"#78350f"},neutral:{bg:"#f1f5f9",border:"#cbd5e1",fg:"#334155"}},g7={display:"flex",alignItems:"flex-start",gap:12,padding:"12px 16px",border:"1px solid",borderRadius:0,fontSize:13,lineHeight:1.5},h7={background:"transparent",border:"none",fontSize:18,cursor:"pointer",lineHeight:1,color:"inherit",padding:"0 4px",flexShrink:0},yp={margin:"8px 0 0",paddingLeft:20,fontSize:12},bp={background:"rgba(0, 0, 0, 0.06)",padding:"1px 4px",borderRadius:3,fontFamily:"monospace"},y7={position:"fixed",inset:0,background:"rgba(15, 23, 42, 0.6)",display:"flex",alignItems:"center",justifyContent:"center",zIndex:1e4},b7={background:"#fff",borderRadius:8,padding:24,width:"min(480px, 90vw)",boxShadow:"0 10px 25px rgba(0,0,0,0.15)"},m7={margin:"0 0 8px",fontSize:16,fontWeight:600,color:"#0f172a"},v7={margin:"0 0 12px",color:"#334155",fontSize:13,lineHeight:1.5},_7={marginBottom:16,padding:"8px 12px",background:"#f8fafc",borderRadius:6,fontSize:12,color:"#64748b",fontFamily:"monospace"},x7={display:"flex",gap:8,justifyContent:"flex-end"},w7={padding:"8px 14px",background:"#2563eb",color:"#fff",border:"none",borderRadius:6,fontSize:13,fontWeight:600,cursor:"pointer"},k7={padding:"8px 14px",background:"#fff",color:"#334155",border:"1px solid #cbd5e1",borderRadius:6,fontSize:13,fontWeight:500,cursor:"pointer"},S7={padding:"8px 14px",background:"transparent",color:"#64748b",border:"none",borderRadius:6,fontSize:13,cursor:"pointer"};function mp(e){if(!e)return null;const n=new URLSearchParams(e).get("route");return n&&n.length>0?n:null}function sa(e,t,n){const o=`/admin/layout-editor/${encodeURIComponent(e)}`;if(!t||t.length===0)return o;if(t.startsWith("__")){let r=`${o}?edit=${encodeURIComponent(t)}`;return(t.startsWith("__modal__/")||t.startsWith("__iteration__/"))&&n&&(r+=`&host=${encodeURIComponent(n)}`),r}return`${o}?route=${encodeURIComponent(t)}`}function vp(e){if(!e)return null;const n=new URLSearchParams(e).get("host");return n&&n.length>0?n:null}function _p(e){if(!e)return null;const n=new URLSearchParams(e).get("edit");return n&&n.length>0&&n.startsWith("__")?n:null}function C7(e,t){const n=`/admin/templates/${encodeURIComponent(e)}/edit`;if(!t||t.length===0||t.startsWith("__"))return n;const o=t.replace(/^\*+/,"");return o.length===0?n:`${n}?route=${encodeURIComponent(o)}`}const $7="g7le-editor-route",E7=new Set;function aa(e){return e.path.startsWith("__group__/")||e.path.startsWith("__conngroup__/")}function xp(e,t){const{t:n,translationEngine:o,translationContext:r,templateIdentifier:s}=t,a=aa(e);if(!e.label.startsWith("$t:"))return e.label;const l=e.label.slice(3),d=l.indexOf("|"),c=d===-1?l:l.slice(0,d),p={};if(d!==-1)for(const b of l.slice(d+1).split("|")){const g=b.indexOf("=");g!==-1&&(p[b.slice(0,g)]=b.slice(g+1))}if((e.kind==="route"||e.kind==="modal")&&!a&&o&&r&&s!==r.templateId){const b={templateId:s,locale:r.locale},g=Object.keys(p).length>0?"|"+Object.entries(p).map(([h,v])=>`${h}=${v}`).join("|"):void 0,m=o.translate(c,b,g);if(m!==c)return m}const f=n(c,p);if(f===c&&typeof window<"u"){const b=window.G7Core?.t;if(typeof b=="function")try{const g=b(c,p);if(g!==c)return g}catch{}}if(f===c&&e.kind==="route"&&!a){const b=e.path;if(b&&b!=="/"){const g=b.split("/").filter(Boolean);return g[g.length-1]??b}return b==="/"?"/":c}return f}function I7(e){return e.trim().toLocaleLowerCase()}function wp(e,t){if(!t)return e;const n=e.toLocaleLowerCase(),o=[];let r=0,s=n.indexOf(t,r);if(s===-1)return e;let a=0;for(;s!==-1;)s>r&&o.push(e.slice(r,s)),o.push(i.jsx("mark",{className:"g7le-route-tree__highlight",style:{background:"#fde68a",color:"inherit",borderRadius:2,padding:"0 1px"},children:e.slice(s,s+t.length)},`m${a++}`)),r=s+t.length,s=n.indexOf(t,r);return r{const z=f.selectedRoute?.path??"";return z.startsWith("__extension__/")||z.startsWith("__modal__/")?z.slice(z.lastIndexOf("/")+1):null},[f.selectedRoute?.path]),h=u.useMemo(()=>!g||!m?!1:(e.children??[]).some(z=>z.kind==="extension"&&z.extensionId===m||z.kind==="modal"&&z.modalId===m),[g,m,e.children]),[v,y]=u.useState(!0);u.useEffect(()=>{h&&y(!1)},[h]);const _=f.editMode==="extension"?f.selectedRoute?.layoutName??null:null,w=e.kind==="extension"?Sp(e,f.routeTree)??null:null,k=e.kind==="extension"&&o===e.path&&(_===null||w===null||_===w),x=f.editMode==="modal"&&o?.startsWith("__modal__/")?o.slice(o.lastIndexOf("/")+1):null,$=f.editMode==="modal"?f.selectedRoute?.layoutName??null:null,C=e.kind==="modal"&&!!x&&e.modalId===x&&($===null||e.modalHostLayout==null||$===e.modalHostLayout),S=e.kind!=="extension"&&e.kind!=="modal"&&o===e.path||k||C||e.kind==="route"&&!b&&r===e.path,E=(e.children?.length??0)>0&&(!g||l||!v),I=!b&&!!e.layoutName&&s.has(e.layoutName),N=b?void 0:e.kind==="extension"?e.extensionId?f.extensionVersions[e.extensionId]:void 0:e.layoutName?f.layoutVersions[e.layoutName]:void 0,T=()=>{if(g){y(z=>!z);return}b||n(e)},O=xp(e,{t:d,translationEngine:c,translationContext:p,templateIdentifier:f.templateIdentifier}),M=b?null:A7(e),B={paddingLeft:12+t*14,paddingRight:12,paddingTop:4,paddingBottom:4,opacity:e.isHidden?.55:1,cursor:g?"pointer":b?"default":"pointer",background:S?"#eff6ff":"transparent",borderLeft:S?"2px solid #2563eb":"2px solid transparent",fontWeight:b||S?600:400,fontSize:b?11:13,color:b?"#64748b":S?"#1d4ed8":"#1e293b",textTransform:b?"uppercase":"none",letterSpacing:b?.6:void 0,transition:"background-color 100ms, color 100ms",display:"flex",flexDirection:"column",alignItems:"stretch",gap:2,userSelect:"none"};return i.jsxs("li",{children:[i.jsxs("div",{"data-testid":b?"g7le-route-tree-group":"g7le-route-tree-item","data-route-path":e.path,"data-route-kind":e.kind,"data-conn-collapsed":g?l?"false":String(v):void 0,role:g||!b?"button":void 0,tabIndex:g||!b?0:void 0,"aria-expanded":g?l?!0:!v:void 0,onClick:T,onKeyDown:z=>{g&&(z.key==="Enter"||z.key===" ")?(z.preventDefault(),y(H=>!H)):!b&&(z.key==="Enter"||z.key===" ")&&(z.preventDefault(),n(e))},style:B,children:[i.jsxs("span",{style:{display:"flex",alignItems:"center",gap:6,minWidth:0},children:[g&&i.jsx("span",{"aria-hidden":"true",style:{flex:"0 0 auto",fontSize:9,color:"#94a3b8",width:10,display:"inline-block"},children:l||!v?"▾":"▸"}),I&&i.jsx("span",{className:"g7le-route-tree__dirty-dot","data-testid":"g7le-route-tree-dirty",title:d("layout_editor.chrome.route_tree.badge.modified"),"aria-label":d("layout_editor.chrome.route_tree.badge.modified"),style:{width:7,height:7,borderRadius:"50%",background:"#f59e0b",flex:"0 0 auto"}}),b?O:wp(O,a),typeof N=="number"&&i.jsxs("span",{className:"g7le-route-tree__version-badge","data-testid":"g7le-route-tree-version",title:d("layout_editor.chrome.route_tree.badge.version_tooltip",{version:String(N)}),style:{flex:"0 0 auto",padding:"0 5px",fontSize:10,fontWeight:600,lineHeight:"16px",borderRadius:4,background:"#eef2ff",color:"#4f46e5",border:"1px solid #e0e7ff",letterSpacing:.2},children:["v",N]}),i.jsx(T7,{node:e})]}),M&&i.jsx("span",{className:"g7le-route-tree__layout-path","data-testid":"g7le-route-tree-layout-path",title:M,style:{fontSize:11,fontWeight:400,color:"#94a3b8",textTransform:"none",letterSpacing:0,overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap"},children:wp(M,a)})]}),E&&i.jsx("ul",{style:{listStyle:"none",margin:0,padding:0},children:e.children.map(z=>i.jsx(kp,{node:z,depth:t+1,onSelect:n,selectedRoutePath:o,activeHostRoutePath:r,dirtyLayoutNames:s,query:a,forceExpand:l},z.path))})]})}function D7(e,t){if(!t||!t.startsWith("__"))return null;const n=t.startsWith("__modal__/"),o=t.startsWith("__extension__/");if(!n&&!o)return null;const r=t.slice(t.lastIndexOf("/")+1),s=a=>{for(const l of a){if(l.connectedHostRoutePath&&(n&&l.kind==="modal"&&l.modalId===r||o&&l.kind==="extension"&&l.extensionId===r))return l.connectedHostRoutePath;if(l.children&&l.children.length>0){const d=s(l.children);if(d)return d}}return null};return s(e)}function Sp(e,t){if(e.connectedHostRoutePath){const n=Cp(t,e.connectedHostRoutePath);if(n?.layoutName)return n.layoutName}if(e.extensionType==="overlay"&&e.extensionTargetName)return e.extensionTargetName}function Cp(e,t){for(const n of e){if(n.kind==="route"&&n.path===t)return n;if(n.children&&n.children.length>0){const o=Cp(n.children,t);if(o)return o}}return null}function $p(e,t,n){const o=[];for(const r of e){const s=aa(r),a=r.children&&r.children.length>0?$p(r.children,t,n):[];if(s){a.length>0&&o.push({...r,children:a});continue}const l=xp(r,n).toLocaleLowerCase(),d=r.path.toLocaleLowerCase();(l.includes(t)||!d.startsWith("__")&&d.includes(t)||a.length>0)&&o.push({...r,children:a})}return o}function z7(){const{t:e,translationEngine:t,translationContext:n}=et(),{state:o,dispatch:r}=Qe(),a=Kt()?.dirtyLayoutNames??E7,[l,d]=u.useState(""),c=I7(l),p=u.useMemo(()=>c?$p(o.routeTree,c,{t:e,translationEngine:t,translationContext:n,templateIdentifier:o.templateIdentifier}):o.routeTree,[c,o.routeTree,o.templateIdentifier,e,t,n]),f=h=>{if(h.kind==="base"&&h.layoutName){r({type:"ENTER_BASE_EDIT",layoutName:h.layoutName});return}if(h.kind==="modal"&&h.modalId&&h.modalHostLayout){r({type:"ENTER_MODAL_EDIT",modalId:h.modalId,hostLayout:h.modalHostLayout});return}if(h.kind==="extension"&&h.extensionId){const v=Sp(h,o.routeTree);r({type:"ENTER_EXTENSION_EDIT",extensionId:h.extensionId,extensionHost:v});return}if(r({type:"SELECT_ROUTE",route:{path:h.path,layoutName:h.layoutName}}),typeof window<"u"&&window.history?.pushState){const v=sa(o.templateIdentifier,h.path);`${window.location.pathname}${window.location.search}`!==v&&window.history.pushState({source:$7,routePath:h.path},"",v)}},b=o.selectedRoute?.path??null,g=u.useMemo(()=>D7(o.routeTree,b),[o.routeTree,b]);if(o.isRouteTreeCollapsed)return i.jsx("aside",{className:"g7le-route-tree g7le-route-tree--collapsed","data-testid":"g7le-route-tree-panel","data-collapsed":"true",style:{width:28,borderRight:"1px solid #e2e8f0",background:"#ffffff"}});const m=o.routeTree.length>0;return i.jsxs("aside",{className:"g7le-route-tree","data-testid":"g7le-route-tree-panel","data-collapsed":"false",style:{width:280,borderRight:"1px solid #e2e8f0",background:"#ffffff",position:"sticky",top:"var(--g7le-header-h, 0px)",alignSelf:"start",height:"calc(100vh - var(--g7le-header-h, 0px))",overflow:"hidden",minHeight:0,display:"flex",flexDirection:"column"},"aria-label":e("layout_editor.chrome.route_tree.panel_title"),children:[i.jsx("div",{className:"g7le-route-tree__title",style:{padding:"10px 14px",fontSize:12,fontWeight:600,color:"#475569",textTransform:"uppercase",letterSpacing:.6,borderBottom:"1px solid #e2e8f0",background:"#f8fafc",flex:"0 0 auto"},children:e("layout_editor.chrome.route_tree.panel_title")}),m&&i.jsxs("div",{className:"g7le-route-tree__search",style:{padding:"8px 10px",borderBottom:"1px solid #e2e8f0",background:"#ffffff",flex:"0 0 auto",position:"relative"},children:[i.jsx("input",{type:"text",className:"g7le-route-tree__search-input","data-testid":"g7le-route-tree-search",value:l,onChange:h=>d(h.target.value),placeholder:e("layout_editor.chrome.route_tree.search_placeholder"),"aria-label":e("layout_editor.chrome.route_tree.search_placeholder"),style:{width:"100%",boxSizing:"border-box",padding:l?"6px 26px 6px 10px":"6px 10px",fontSize:12,lineHeight:1.4,color:"#1e293b",border:"1px solid #cbd5e1",borderRadius:6,outline:"none",background:"#f8fafc"}}),l&&i.jsx("button",{type:"button",className:"g7le-route-tree__search-clear","data-testid":"g7le-route-tree-search-clear",onClick:()=>d(""),"aria-label":e("layout_editor.chrome.route_tree.search_clear"),title:e("layout_editor.chrome.route_tree.search_clear"),style:{position:"absolute",right:16,top:"50%",transform:"translateY(-50%)",width:18,height:18,display:"inline-flex",alignItems:"center",justifyContent:"center",border:"none",background:"transparent",color:"#94a3b8",cursor:"pointer",fontSize:14,lineHeight:1,padding:0},children:"✕"})]}),!m&&i.jsx("div",{className:"g7le-route-tree__empty","data-testid":"g7le-route-tree-empty",style:{padding:16,fontSize:12,color:"#94a3b8",textAlign:"center"},children:e("layout_editor.chrome.route_tree.empty")}),m&&c&&p.length===0&&i.jsx("div",{className:"g7le-route-tree__no-results","data-testid":"g7le-route-tree-no-results",style:{padding:16,fontSize:12,color:"#94a3b8",textAlign:"center"},children:e("layout_editor.chrome.route_tree.search_no_results")}),m&&p.length>0&&i.jsx("nav",{className:"g7le-route-tree__scroll","data-testid":"g7le-route-tree-scroll",style:{flex:"1 1 auto",minHeight:0,overflowY:"auto"},children:i.jsx("ul",{style:{listStyle:"none",margin:0,padding:0},children:p.map(h=>i.jsx(kp,{node:h,depth:0,onSelect:f,selectedRoutePath:b,activeHostRoutePath:g,dirtyLayoutNames:a,query:c,forceExpand:!!c},h.path))})})]})}const N7=Lt().DynamicRenderer,la=Lt(),M7=la.ResponsiveProvider;la.useResponsive,la.ResponsiveContext;const L7=Lt().ActionDispatcher,Ep=Lt(),P7=Ep.DataSourceManager;Ep.dataSourceManager;function co(e,t){const n=e,o=n?.errors?.[t];if(typeof o=="number")return o;const r=n?.[t];return typeof r=="number"?r:void 0}function ti(e,t,n){const o=t??{},r=o.message||o.error||`HTTP ${e}`,s=o?.data?.required_permissions??o?.required_permissions;let a;return e===401?a="unauthorized":e===403?a="forbidden":e===404?a="not_found":e>=500?a="server_error":a="unknown",{kind:a,status:e,message:String(r),requiredPermissions:s?String(s):void 0,source:n}}function uo(e,t){return{kind:"network",status:0,message:e instanceof Error?e.message:String(e??"Unknown network error"),source:t}}function Ip(e){if(typeof e!="object"||e===null)return!1;const t=e;return typeof t.kind=="string"&&typeof t.status=="number"&&typeof t.message=="string"}function Re(e){if(!e)return[];const t=[],n=e.split(".");for(let o=0;o0&&(t.push({responsive:a}),o+=1);continue}const s=Number(r);Number.isFinite(s)&&Number.isInteger(s)&&s>=0&&t.push(s)}return t}function O7(e,t){if(!e)return{affordance:"none",targetPath:null};const n=B7(e);return n?W7(n)?{affordance:"dynamic_path",targetPath:n}:/^(https?:)?\/\//.test(n)?{affordance:"external_url",targetPath:n}:n.startsWith("/")?t?{affordance:t(n),targetPath:n}:{affordance:"route_not_in_tree",targetPath:n}:{affordance:"route_not_in_tree",targetPath:n}:{affordance:"none",targetPath:null}}function B7(e){const t=e.actions;if(Array.isArray(t)){for(const n of t)if(n&&typeof n=="object"&&n.handler==="navigate"){const o=n.params?.path;if(typeof o=="string"&&o.length>0)return o}}if(e.name==="A"||e.type==="A"){const n=e.props?.href;if(typeof n=="string"&&n.length>0)return n}return null}function W7(e){return/\{\{[\s\S]*?\}\}/.test(e)}function j7(e,t,n){const o=s=>!!(s?.iteration&&s.iteration.source!==void 0),r=s=>{if(!s)return s;const a=s.indexOf(".iteration."),l=s.indexOf(".sortable."),d=[a,l].filter(c=>c>=0).sort((c,p)=>c-p)[0];return d===void 0?s:s.slice(0,d)};return o(e)?{isIteration:!0,iterationSourcePath:r(n)}:n&&n.includes(".iteration.")?{isIteration:!0,iterationSourcePath:r(n)}:t.some(o)?{isIteration:!0,iterationSourcePath:r(n)}:{isIteration:!1,iterationSourcePath:null}}function H7(e,t){const n=a=>{const l=a?.iteration&&a.iteration.source;return typeof l=="string"&&l.length>0?l:null},o=n(e)??t.map(n).find(a=>a!==null)??null;if(o!==null){const a=K7(o);if(a!==null)return a}const r=[];if(typeof e.text=="string"&&r.push(...Ap(e.text)),e.props&&typeof e.props=="object")for(const a of Object.values(e.props))typeof a=="string"&&r.push(...Ap(a));return r.length===0?null:r.find(a=>!F7.has(a))??r[0]}const F7=new Set(["_local","_global","_computed","route","query","props","item","index","event","$event","$response","response"]);function Ap(e){const t=[],n=/\{\{([\s\S]*?)\}\}/g;let o;for(;(o=n.exec(e))!==null;){const r=Tp(o[1]);r!==null&&t.push(r)}return t}function K7(e){const t=e.replace(/\{\{|\}\}/g,"");return Tp(t)}function Tp(e){const t=/[A-Za-z_$][A-Za-z0-9_$]*/.exec(e.trim());return t?t[0]:null}function Jt(e,t,n,o=[]){if(!e)return"none";if(dd(e)||typeof e.slot=="string"||typeof e.__editorSlotName=="string")return"extension_point";if(pd(e,t,n)){const r=e.__source?.kind;return r==="base"?"base":r==="partial"?"partial":r==="extension"?"extension":"base"}return Qb(e,o)?"data_bound":"none"}function Zt(e){return e==="none"||e==="data_bound"}function Dp(e){return Zt(e)?null:e==="extension"||e==="extension_point"?"denied_extension_locked":"denied_base_locked"}function zp(e){if(!e)return null;if(e.__source?.kind==="extension"&&typeof e.__source.extensionId=="number")return e.__source.extensionId;const t=e.__injectedProps;if(Array.isArray(t)&&t.length>0){const n=t[0]?.extensionId;if(typeof n=="number")return n}return null}function V7(e,t){if(t<=0)return e;const n=e.split(".");let o=0;const r=[];for(let s=0;s=t)break;r.push(a);continue}if(a==="iteration"||a==="sortable"){if(o>=t)break;r.push(a);continue}r.push(a);const l=n[s-1];if((l===void 0||l==="children")&&(o+=1),o>=t)break}return r.join(".")}function Np(e,t,n,o){if(!e||!t)return t;const r=n(t),s=tm(e,r,o);return s.length===r.length?t:V7(t,s.length)}function Mp(e){return Zt(e)}function U7(e){const{rootNode:t,editMode:n,currentExtensionId:o,editableRootPath:r,pathParser:s=Re,resolveRouteMatch:a}=e,[l,d]=u.useState(null),[c,p]=u.useState(null),f=u.useCallback(_=>{if(_===null||n==="route")return!0;if(n==="iteration_item"||n==="modal"){if(!r||r.length===0)return!0;const $=s(_);return Lp($,r)}const w=da(t,_,s);if(!w)return!0;const k=ca(t,_,s),x=Jt(w,n,o,k);return Zt(x)},[t,s,n,o,r]),b=u.useCallback((_,w)=>{const x=(w?.currentTarget?.dataset??w?.dataset)?.editorPath??null,$=Np(t,x,s,o);f($)&&d($)},[t,s,o,f]),g=u.useCallback((_,w)=>{if(_===null){p(null);return}const x=(w?.currentTarget?.dataset??w?.dataset)?.editorPath??null,$=Np(t,x,s,o);if(!f($)){p(null);return}p($)},[t,s,o,f]),m=u.useCallback(()=>{d(null)},[]),h=u.useMemo(()=>{const _=da(t,l,s),w=da(t,c,s),k=_?ca(t,l,s):[];let x=Jt(_,n,o,k);(n==="iteration_item"||n==="modal")&&l!==null&&_&&r&&r.length>0&&Lp(s(l),r)&&(cd(_)?x="data_bound":x==="data_bound"&&(x="none"));const{affordance:$,targetPath:C}=O7(_,a),S=x==="base"&&typeof _?.__source?.layout=="string"?_.__source.layout:null,D=x==="data_bound"&&_?H7(_,k):null,{isIteration:E,iterationSourcePath:I}=x==="data_bound"&&_?j7(_,k,l):{isIteration:!1,iterationSourcePath:null};return{selectedNode:_,hoverNode:w,selectedLockKind:x,selectedNavAffordance:$,selectedNavTargetPath:C,selectedBaseLayout:S,selectedDataSourceId:D,selectedIsIteration:E,selectedIterationSourcePath:I}},[t,l,c,n,o,r,s,a]),v=u.useRef(void 0);v.current!==l&&(v.current=l,Ml({op:l===null?"clear":"select",componentPath:l,componentName:Pp(h.selectedNode),lockKind:h.selectedLockKind,navAffordance:h.selectedNavAffordance,sourceExtensionId:zp(h.selectedNode),timestamp:Date.now()}));const y=u.useRef(void 0);return y.current!==c&&(y.current=c,c!==null&&Ml({op:"hover",componentPath:c,componentName:Pp(h.hoverNode),lockKind:Jt(h.hoverNode,n,o,ca(t,c,s)),navAffordance:"none",sourceExtensionId:zp(h.hoverNode),timestamp:Date.now()})),{selectedPath:l,hoverPath:c,...h,handleSelect:b,handleHover:g,clearSelection:m}}function da(e,t,n){if(!e||!t)return null;const o=n(t);return Pe(e,o)}function Lp(e,t){if(e.length0?e.name:typeof e.type=="string"&&e.type.length>0?e.type:null:null}let Op=0;function Bp(){return Op}function ua(){Op+=1}function q7(e){if(typeof e!="string")return null;const t="__modal__/";if(!e.startsWith(t))return null;const n=e.slice(t.length);return n===""||n.includes("/")?null:n}function G7(e){if(typeof e!="string")return null;const t="__iteration__/";if(!e.startsWith(t))return null;const n=e.slice(t.length);return n!==""?n:null}function Wp(){const{state:e}=Qe(),t=e.templateIdentifier,n=e.selectedRoute?.layoutName??null,o=e.editMode,r=o==="modal"?q7(e.selectedRoute?.path):null,s=o==="iteration_item"?G7(e.selectedRoute?.path):null,[a,l]=u.useState(null),[d,c]=u.useState(!1),[p,f]=u.useState(null),[b,g]=u.useState(!1),[m,h]=u.useState(0),[v,y]=u.useState(0),_=u.useRef(new Map),[w,k]=u.useState(()=>new Set),x=u.useCallback((P,A)=>`${P}|${A??""}`,[]),$=u.useRef(null);u.useEffect(()=>{$.current=a},[a]);const C=u.useCallback((P,A)=>{const L=x(o,n);_.current.set(L,{raw:P,lockVersion:A}),k(F=>{if(F.has(L))return F;const Y=new Set(F);return Y.add(L),Y})},[x,o,n]),S=u.useCallback(P=>{_.current.delete(P),k(A=>{if(!A.has(P))return A;const L=new Set(A);return L.delete(P),L})},[]),D=u.useCallback(P=>{const A=`|${P??""}`,L=[];for(const F of _.current.keys())F.endsWith(A)&&L.push(F);for(const F of L)_.current.delete(F);L.length>0&&k(F=>{let Y=!1;const V=new Set(F);for(const X of L)V.delete(X)&&(Y=!0);return Y?V:F})},[]),E=u.useCallback(async()=>{if(!n){l(null),f(null),g(!1);return}const P=x(o,n),A=_.current.get(P);if(A){l({layoutName:n,raw:A.raw,lockVersion:A.lockVersion}),g(!0),f(null),c(!1),it({op:"load",layoutName:n,editMode:o,statusCode:0,isDirty:!0,timestamp:Date.now()});return}c(!0),f(null);try{const L=window.G7Config?.cache_version??0,F=nn(`/api/layouts/${encodeURIComponent(t)}/${n}`,"json",`${L}.${Bp()}`,"with_source_meta=1"),Y=gr(),V={Accept:"application/json"};Y&&(V.Authorization=`Bearer ${Y}`);const X=await fetch(F,{headers:V,credentials:"same-origin"}),Z=await X.json().catch(()=>null);if(!X.ok){f(ti(X.status,Z,"layout")),l(null),g(!1);return}const ie=(Z&&(Z.data||Z))??{},oe=typeof ie.lock_version=="number"?ie.lock_version:0;if(o==="modal"&&r){const ee=ie,fe=Array.isArray(ee.modals)?ee.modals:[],K=fe.findIndex(te=>te&&(te.id===r||te.modal_id===r));if(K>=0){const te=fe[K],ye={...te,props:{...te.props??{},isOpen:!0}},De=[...Array.isArray(ee.components)?ee.components:[],ye],Ie=[De.length-1];l({layoutName:n,raw:{...ee,components:De},lockVersion:oe,modalContext:{modalId:r,modalIndex:K,hostRaw:ee,originalModalNode:te,editIndexPath:Ie}}),g(!1),it({op:"load",layoutName:n,editMode:o,statusCode:X.status,timestamp:Date.now()});return}}if(o==="iteration_item"&&s){const ee=ie,fe=Re(s),K={children:ee.components??[]},te=Pe(K,fe);if(te&&Array.isArray(te.children)){l({layoutName:n,raw:ee,lockVersion:oe,iterationContext:{sourcePath:s,sourceIndexPath:fe,hostRaw:ee}}),g(!1),it({op:"load",layoutName:n,editMode:o,statusCode:X.status,timestamp:Date.now()});return}}l({layoutName:n,raw:ie,lockVersion:oe}),g(!1),it({op:"load",layoutName:n,editMode:o,statusCode:X.status,timestamp:Date.now()})}catch(L){f(uo(L,"layout")),l(null),g(!1)}finally{c(!1)}},[t,n,o,r,s,x]);u.useEffect(()=>{E()},[E]);const I=u.useCallback(async()=>{S(x(o,n)),ua(),y(P=>P+1),await E()},[S,x,o,n,E]),N=u.useCallback(()=>{S(x(o,n))},[S,x,o,n]),T=u.useCallback(P=>{l(A=>{if(!A)return A;const L=A.raw.components??[],F=P(L),Y={...A.raw,components:F};return C(Y,A.lockVersion),{...A,raw:Y}}),g(!0),it({op:"patch",layoutName:n,editMode:o,isDirty:!0,timestamp:Date.now()})},[n,o,C]),O=u.useCallback(()=>{g(!0),it({op:"patch",layoutName:n,editMode:o,isDirty:!0,timestamp:Date.now()})},[n,o]),M=u.useCallback(P=>{l(A=>{if(!A)return A;const L={...A.raw,components:P};return C(L,A.lockVersion),{...A,raw:L}}),g(!0),it({op:"patch",layoutName:n,editMode:o,isDirty:!0,timestamp:Date.now()})},[n,o,C]),B=u.useCallback((P,A,...L)=>{const F=L.length>=1?L[0]:A;l(Y=>{if(!Y)return Y;const V={...Y.raw,[P]:A},X=Y.raw.__editor;if(X&&typeof X=="object"&&!Array.isArray(X)){const Z=X,ie=Z.original;ie&&typeof ie=="object"&&!Array.isArray(ie)&&(V.__editor={...Z,original:{...ie,[P]:F}})}return C(V,Y.lockVersion),{...Y,raw:V}}),g(!0),it({op:"patch",layoutName:n,editMode:o,isDirty:!0,timestamp:Date.now()})},[n,o,C]),z=u.useCallback(async P=>{const A=$.current;if(!A)return it({op:"save_guard_result",layoutName:n,editMode:o,guardBlocked:!1,timestamp:Date.now()}),{kind:"guard_no_document"};if(P?.resolveActiveExtensions&&P.sessionAddedPaths?.length)try{const fe=await P.resolveActiveExtensions(),K=Y7(A.raw.components,P.sessionAddedPaths,fe);if(K.length>0)return it({op:"save_guard_result",layoutName:n,editMode:o,guardBlocked:!0,guardBlockedPaths:K,timestamp:Date.now()}),{kind:"blocked_inactive_extension",blockedPaths:K}}catch{}if(gm())try{await hm(t)}catch{}const L=`/api/admin/templates/${encodeURIComponent(t)}/layouts/${A.layoutName}`,F=gr(),Y={Accept:"application/json","Content-Type":"application/json"};F&&(Y.Authorization=`Bearer ${F}`);const V=!!A.modalContext,X=!!A.iterationContext,Z=V||X?"host_layout_modal_patch":"layout";it({op:"save",layoutName:A.layoutName,editMode:o,saveTarget:Z,endpoint:L,isDirty:b,timestamp:Date.now()});let ie;if(V&&A.modalContext){const{modalIndex:fe,hostRaw:K,originalModalNode:te,editIndexPath:ye}=A.modalContext,Se=Array.isArray(K.modals)?[...K.modals]:[],De=Array.isArray(A.raw.components)?A.raw.components:[],Ie=ye&&ye.length===1?ye[0]:0,Ve=De.length>Ie?De[Ie]:void 0,ze=Ve?qn([Ve]):[],Be=ze.length>0?{...ze[0]}:void 0;if(fe>=0&&fe({...Ne,children:De})).children??[];ie={...Ie,components:Xe}}else ie=Co(A.raw);const oe=ie;let G;try{G=await fetch(L,{method:"PUT",headers:Y,credentials:"same-origin",body:JSON.stringify({content:JSON.stringify(oe),expected_lock_version:A.lockVersion})})}catch(fe){const K=fe instanceof Error?fe.message:"network error";return it({op:"save_response",layoutName:A.layoutName,editMode:o,endpoint:L,statusCode:0,timestamp:Date.now()}),{kind:"network_error",message:K}}const ee=await G.json().catch(()=>null);if(it({op:"save_response",layoutName:A.layoutName,editMode:o,endpoint:L,statusCode:G.status,conflict:G.status===409?{currentVersion:ee?.current_version,yourVersion:ee?.your_version}:void 0,timestamp:Date.now()}),G.ok){const fe=typeof ee?.data?.lock_version=="number"?ee.data.lock_version:A.lockVersion+1;l({...A,lockVersion:fe}),g(!1),S(x(o,A.layoutName)),D(A.layoutName),ua(),h(te=>te+1);const K=typeof ee?.data?.current_version=="number"?ee.data.current_version:void 0;return{kind:"success",newLockVersion:fe,newContentVersion:K,savedLayoutName:A.layoutName}}return G.status===409?{kind:"concurrent_modification",currentVersion:co(ee,"current_version")??-1,yourVersion:co(ee,"your_version")??A.lockVersion}:G.status===422?{kind:"validation_failed",status:422,errors:ee?.errors??null}:{kind:"network_error",message:ee?.message??`HTTP ${G.status}`}},[t,n,o,b,S,D,x,r,s]),H=u.useMemo(()=>{const P=new Set,A="route|";for(const L of w)if(L.startsWith(A)){const F=L.slice(A.length);F&&P.add(F)}return P},[w]);return{document:a,isLoading:d,error:p,isDirty:b,saveSuccessCounter:m,reloadCounter:v,dirtyKeys:w,dirtyLayoutNames:H,invalidateCurrentCache:N,reload:I,patchLayout:T,setLayoutComponents:M,patchDocumentRaw:B,save:z,markDirty:O}}function Y7(e,t,n){if(!e||e.length===0)return[];const o=[],r=new Set([...n.moduleIds,...n.pluginIds]);for(const s of t){const a=X7(e,s);if(!a)continue;const l=a.__source;if(!l||l.kind!=="extension")continue;const d=String(l.extensionId??"");d.length!==0&&(r.has(d)||o.push(s))}return o}function X7(e,t){const n=Re(t);return n.length===0?null:Pe({children:e},n)}function po(e,t){if(!e)return null;const n=e.getBoundingClientRect();if(n.width===0&&n.height===0)return null;if(!t)return{left:n.left,top:n.top,width:n.width,height:n.height,scale:1};const o=t.getBoundingClientRect();return{left:n.left-o.left,top:n.top-o.top,width:n.width,height:n.height,scale:1}}function fo(e,t){if(!e)return!1;if(!t)return!0;const n=t.getBoundingClientRect(),o=1;return!(e.left>=n.width-o||e.left+e.width<=o||e.top>=n.height-o||e.top+e.height<=o)}function jp(e,t){if(typeof window>"u")return()=>{};const n=()=>{e()};window.addEventListener("scroll",n,!0),window.addEventListener("resize",n);let o=null;t&&typeof ResizeObserver<"u"&&(o=new ResizeObserver(()=>e()),o.observe(t));let r=null,s=null;const a=typeof requestAnimationFrame<"u",l=()=>{if(!a){e();return}s===null&&(s=requestAnimationFrame(()=>{s=null,e()}))};return t&&typeof MutationObserver<"u"&&(r=new MutationObserver(l),r.observe(t,{childList:!0,subtree:!0})),()=>{window.removeEventListener("scroll",n,!0),window.removeEventListener("resize",n),o&&(o.disconnect(),o=null),r&&(r.disconnect(),r=null),s!==null&&a&&typeof cancelAnimationFrame<"u"&&(cancelAnimationFrame(s),s=null)}}const Hp=44;function Fp(e){if(!e)return"inside";const t=e.scale||1,n=e.width*t,o=e.height*t;return n"u"||typeof window.getComputedStyle!="function")return{kind:"unknown",display:"",flexDirection:"",flexWrap:""};let t=e,n=(window.getComputedStyle(t).display??"").toLowerCase();for(;n==="contents"&&t.parentElement;)t=t.parentElement,n=(window.getComputedStyle(t).display??"").toLowerCase();const o=window.getComputedStyle(t),r=(o.display??"").toLowerCase(),s=(o.flexDirection??"").toLowerCase(),a=(o.flexWrap??"").toLowerCase(),l=r==="flex"||r==="inline-flex";if(r==="grid"||r==="inline-grid"){const f=Array.from(e.children).filter(h=>h.offsetParent!==null||h.getClientRects().length>0);if(f.length<=1)return{kind:"grid_2d",display:r,flexDirection:s,flexWrap:a};const b=f.map(h=>h.getBoundingClientRect()),g=new Set(b.map(h=>Math.round(h.top))),m=new Set(b.map(h=>Math.round(h.left)));return g.size===1?{kind:"grid_single_row",display:r,flexDirection:s,flexWrap:a}:m.size===1?{kind:"grid_single_column",display:r,flexDirection:s,flexWrap:a}:{kind:"grid_2d",display:r,flexDirection:s,flexWrap:a}}if(!l)return{kind:"block",display:r,flexDirection:s,flexWrap:a};const c=a==="wrap"||a==="wrap-reverse";return s==="column"||s==="column-reverse"?{kind:c?"flex_column_wrap":"flex_column_single",display:r,flexDirection:s,flexWrap:a}:{kind:c?"flex_row_wrap":"flex_row_single",display:r,flexDirection:s,flexWrap:a}}function Z7(e){const{parentEl:t,selectedPath:n,selectedEl:o,explicitFlow:r}=e;return u.useMemo(()=>{if(!n||n.length===0)return{points:[],flow:"unknown"};const s=r??Up(t).kind,a=n.slice(0,-1),l=n[n.length-1]??0;if((s==="flex_row_wrap"||s==="flex_column_wrap"||s==="grid_2d")&&!!o){const p=Q7(s,o,a,l);if(p)return{points:p,flow:s}}return{points:R7(s,a,l),flow:s}},[t,n,o,r])}function Q7(e,t,n,o){let r=t.parentElement;for(;r&&qp(r)==="contents";)r=r.parentElement;if(!r)return null;const s=[];if(Gp(r,s),s.length===0)return null;const a=t.getBoundingClientRect(),l=a.top+a.height/2,d=[],c=[],p=[];for(const v of s){const y=v.rect,_=y.top+y.height/2,w=Math.min(a.bottom,y.bottom)-Math.max(a.top,y.top),k=Math.min(a.height,y.height)||1;w/k>.4?d.push({rect:y,path:v.path}):_v.rect.left-y.rect.left),c.sort((v,y)=>v.rect.top-y.rect.top),p.sort((v,y)=>v.rect.top-y.rect.top);const f=d.findIndex(v=>Math.abs(v.rect.left-a.left)<1&&Math.abs(v.rect.top-a.top)<1),b={direction:"above",disabled:!0,insertion:null},g={direction:"below",disabled:!0,insertion:null},m={direction:"left",disabled:!0,insertion:null},h={direction:"right",disabled:!0,insertion:null};if(f>0){const v=d[f-1],y=oi(v.path);y&&(m.disabled=!1,m.insertion=y)}else if(d.length>0){const v=d[0],y=ni(v.path);y&&(m.disabled=!1,m.insertion=y)}if(f>=0&&f0){const v=d[d.length-1],y=oi(v.path);y&&(h.disabled=!1,h.insertion=y)}if(c.length>0){const v=c[c.length-1],y=oi(v.path);y&&(b.disabled=!1,b.insertion=y)}else if(s.length>0){const v=s[0],y=ni(v.path);y&&(b.disabled=!1,b.insertion=y)}if(p.length>0){const v=p[0],y=ni(v.path);y&&(g.disabled=!1,g.insertion=y)}else if(s.length>0){const v=s[s.length-1],y=oi(v.path);y&&(g.disabled=!1,g.insertion=y)}else g.disabled=!1,g.insertion={parentPath:n,index:o+1};return b.disabled&&g.disabled&&m.disabled&&h.disabled?null:[b,g,m,h]}function qp(e){return typeof window>"u"?"":(window.getComputedStyle(e).display??"").toLowerCase()}function Gp(e,t){const n=Array.from(e.children);for(const o of n){if(qp(o)==="contents"){Gp(o,t);continue}const s=o.getAttribute("data-editor-path");if(s&&/\.iteration\.\d+(?:$|\.)/.test(s)||!s)continue;const a=o.getBoundingClientRect();a.width===0&&a.height===0||t.push({el:o,rect:a,path:Re(s)})}}function ni(e){if(!e||e.length===0)return null;const t=e[e.length-1];return typeof t!="number"?null:{parentPath:e.slice(0,-1),index:t}}function oi(e){if(!e||e.length===0)return null;const t=e[e.length-1];return typeof t!="number"?null:{parentPath:e.slice(0,-1),index:t+1}}function R7(e,t,n){const o={direction:"above",disabled:!0,insertion:null},r={direction:"below",disabled:!0,insertion:null},s={direction:"left",disabled:!0,insertion:null},a={direction:"right",disabled:!0,insertion:null},l=d=>({parentPath:t,index:d});switch(e){case"block":case"unknown":{o.disabled=!1,o.insertion=l(n),r.disabled=!1,r.insertion=l(n+1);break}case"flex_row_single":case"grid_single_row":{s.disabled=!1,s.insertion=l(n),a.disabled=!1,a.insertion=l(n+1);break}case"flex_column_single":case"grid_single_column":{o.disabled=!1,o.insertion=l(n),r.disabled=!1,r.insertion=l(n+1);break}case"flex_row_wrap":case"grid_2d":{s.disabled=!1,s.insertion=l(n),a.disabled=!1,a.insertion=l(n+1),o.disabled=!1,o.insertion=l(0),r.disabled=!1,r.insertion=l(-1);break}case"flex_column_wrap":{o.disabled=!1,o.insertion=l(n),r.disabled=!1,r.insertion=l(n+1),s.disabled=!1,s.insertion=l(0),a.disabled=!1,a.insertion=l(-1);break}}return[o,r,s,a]}function e2(e=50){const t=u.useRef([]),n=u.useRef(-1),[,o]=u.useState(0),r=()=>{o(p=>(p+1)%1e6)},s=(p,f)=>{const b=t.current.length,g=n.current;_y({op:p,actionKind:f?.actionKind,label:f?.label,stackSize:b,cursor:g,canUndo:g>0,canRedo:g>=0&&g{const b=t.current.slice(0,n.current+1);for(b.push(p);b.length>e;)b.shift();t.current=b,n.current=b.length-1,r(),s("push",p)},[e]),l=u.useCallback(()=>{if(n.current<=0)return null;n.current-=1,r();const p=t.current[n.current]??null;return s("undo",p??void 0),p},[]),d=u.useCallback(()=>{const p=n.current+1;if(p>=t.current.length)return null;n.current=p,r();const f=t.current[p]??null;return s("redo",f??void 0),f},[]),c=u.useCallback(()=>{t.current=[],n.current=-1,r(),s("clear")},[]);return{push:a,undo:l,redo:d,clear:c,canUndo:n.current>0,canRedo:n.current>=0&&n.current{if(!o||typeof window>"u")return;const r=s=>{if(t2(s.target)||!(s.ctrlKey||s.metaKey))return;const l=s.key.toLowerCase();if(l==="z"&&!s.shiftKey){s.preventDefault(),t();return}if(l==="z"&&s.shiftKey||l==="y"){s.preventDefault(),n();return}};return window.addEventListener("keydown",r),()=>{window.removeEventListener("keydown",r)}},[t,n,o])}const o2=20,Yp=50,r2=90,On=120,i2=130,Xp=80,s2=9e3,Jp=1e4;function a2(e){const t=e<0?0:e>Yp?Yp:e;return o2+t}function l2(e){const{anchor:t,open:n,onClose:o,onEditProps:r,onDuplicate:s,onDelete:a,onSeparateBranch:l,separateBranchLabel:d,onMergeBranch:c,mergeBranchLabel:p}=e,{t:f}=et(),[b,g]=u.useState(null);if(!n||!t)return null;const m=[{index:0,testid:"g7le-context-menu-edit-props",label:f("layout_editor.context_menu.edit_props"),onSelect:r},{index:1,testid:"g7le-context-menu-duplicate",label:f("layout_editor.context_menu.duplicate"),onSelect:s}];return l?m.push({index:m.length,testid:"g7le-context-menu-separate-branch",label:d??f("layout_editor.context_menu.separate_branch"),onSelect:l,hint:f("layout_editor.context_menu.separate_branch_hint")}):c&&m.push({index:m.length,testid:"g7le-context-menu-merge-branch",label:p??f("layout_editor.context_menu.merge_branch"),onSelect:c,hint:f("layout_editor.context_menu.merge_branch_hint")}),m.push({index:m.length,testid:"g7le-context-menu-delete",label:f("layout_editor.context_menu.delete"),onSelect:a,danger:!0}),i.jsx("div",{className:"g7le-context-menu","data-testid":"g7le-context-menu",role:"menu",style:{position:"absolute",left:t.left,top:t.top,background:"#fff",border:"1px solid #cbd5e1",borderRadius:6,boxShadow:"0 4px 12px rgba(0,0,0,0.15)",zIndex:1200,minWidth:160,padding:4,pointerEvents:"auto"},onMouseLeave:()=>{g(null),o()},children:m.map(h=>{const v=b===h.index,y=h.danger?"#dc2626":"#0f172a",_=h.danger?"#fee2e2":"#e0e7ff",w=h.danger?"#b91c1c":"#1d4ed8";return i.jsx("button",{type:"button",role:"menuitem","data-testid":h.testid,title:h.hint,onClick:k=>{k.stopPropagation(),h.onSelect(),g(null),o()},onMouseEnter:()=>g(h.index),onFocus:()=>g(h.index),style:{...d2,color:v?w:y,background:v?_:"transparent"},children:h.label},h.testid)})})}const d2={display:"block",width:"100%",textAlign:"left",padding:"6px 12px",border:"none",fontSize:13,cursor:"pointer",borderRadius:4,pointerEvents:"auto",transition:"background-color 120ms ease, color 120ms ease"};function c2(e){const{hoverBox:t,selectedBox:n,lockKind:o,navAffordance:r,onEditProps:s,onDuplicate:a,onDelete:l,onSeparateBranch:d,separateBranchLabel:c,onMergeBranch:p,mergeBranchLabel:f,definedDeviceBranches:b,onJumpToDevice:g,onLinkEditDestination:m,onEditBase:h,onEditExtension:v,extensionLabel:y,baseLayoutLabel:_,dataSourceLabel:w,branchLabel:k,onEditIteration:x,resizeEnabledAxes:$,onResizeHandlePointerDown:C,selectedName:S,onSelectParent:D}=e,{t:E}=et(),[I,N]=u.useState(!1),T=Mp(o),O=I&&T,M=Fp(n),B=M==="outside",z=B&&n?Vp(n):null,H=z?{left:z.right.left,top:z.above.top}:{right:-30,top:-30},P={top:-28,left:0},L=B&&!!S?{top:-50,left:0}:P,F=z?{top:z.below.top+28,left:0}:{top:"auto",bottom:-28},Y=z?{top:z.below.top+56,left:0}:{top:"auto",bottom:-56};return i.jsxs("div",{className:"g7le-element-overlay","data-testid":"g7le-element-overlay",style:{position:"absolute",inset:0,pointerEvents:"none"},children:[t&&i.jsx("div",{"data-testid":"g7le-overlay-hover",style:{position:"absolute",left:t.left,top:t.top,width:t.width,height:t.height,border:"1px dashed #2563eb",pointerEvents:"none"}}),n&&i.jsxs("div",{"data-testid":"g7le-overlay-selected","data-lock-kind":o,style:{position:"absolute",left:n.left,top:n.top,width:n.width,height:n.height,border:o==="none"?"2px solid #2563eb":"2px dashed #f59e0b",pointerEvents:"none"},children:[k?i.jsxs("span",{"data-testid":"g7le-overlay-branch-badge",style:p2,children:["📱 ",k]}):null,S&&(D?i.jsxs("button",{type:"button","data-testid":"g7le-overlay-type-label","data-placement":B?"outside":"inside",title:E("layout_editor.overlay.select_parent"),"aria-label":E("layout_editor.overlay.select_parent"),onClick:V=>{V.preventDefault(),V.stopPropagation(),D()},style:B?g2:f2,children:[i.jsx("span",{"aria-hidden":!0,style:{marginRight:3,opacity:.85},children:"↑"}),S]}):i.jsx("span",{"data-testid":"g7le-overlay-type-label","data-placement":B?"outside":"inside",style:B?Qp:Zp,children:S})),T&&i.jsx("button",{type:"button","data-testid":"g7le-overlay-info-button","aria-label":"info",onClick:V=>{V.stopPropagation(),N(X=>!X)},"data-placement":M,style:{position:"absolute",...H,width:20,height:20,borderRadius:10,border:"1px solid #2563eb",background:"#fff",color:"#2563eb",fontSize:12,fontWeight:700,cursor:"pointer",pointerEvents:"auto",padding:0,lineHeight:"18px",zIndex:On},children:"ⓘ"}),o==="none"&&i.jsx(u2,{enabledAxes:$,onHandlePointerDown:C}),o==="base"&&h&&i.jsxs("button",{type:"button","data-testid":"g7le-overlay-edit-base",onClick:V=>{V.stopPropagation(),h()},style:{...Ko,...P},children:["🔒 ",E("layout_editor.overlay.edit_common_layout"),_?i.jsx("span",{"data-testid":"g7le-overlay-base-layout-label",style:fa,children:_}):null]}),o==="extension"&&v&&i.jsxs("button",{type:"button","data-testid":"g7le-overlay-edit-extension",onClick:V=>{V.stopPropagation(),v()},style:{...Ko,...P},children:["🔒 ",E("layout_editor.overlay.edit_extension"),y?i.jsx("span",{"data-testid":"g7le-overlay-extension-label",style:fa,children:y}):null]}),o==="data_bound"&&i.jsxs("span",{"data-testid":"g7le-overlay-data-bound-notice",style:{...Ko,...L,pointerEvents:"none",cursor:"default"},children:[E("layout_editor.overlay.locked_data_bound"),w?i.jsx("span",{"data-testid":"g7le-overlay-data-source-label",style:fa,children:w}):null]}),o==="data_bound"&&x&&i.jsxs("button",{type:"button","data-testid":"g7le-overlay-edit-iteration",onClick:V=>{V.stopPropagation(),x()},style:{...Ko,...F},children:["↳ ",E("layout_editor.overlay.edit_iteration_item")]}),r==="route_in_tree"&&i.jsx("button",{type:"button","data-testid":"g7le-overlay-link-edit-destination",onClick:V=>{V.stopPropagation(),m()},style:{...Ko,...F},children:E("layout_editor.overlay.edit_destination_route")}),(d||p||g&&(b??[]).length>0)&&i.jsxs("div",{"data-testid":"g7le-overlay-branch-affordances",style:{position:"absolute",...Y,display:"flex",flexDirection:"column",alignItems:"flex-start",gap:4,pointerEvents:"none"},children:[g&&(b??[]).map(V=>i.jsxs("button",{type:"button","data-testid":`g7le-overlay-jump-device-${V.key}`,title:E("layout_editor.overlay.jump_device_hint",{device:V.label}),onClick:X=>{X.stopPropagation(),g(V.key)},style:pa,children:["↗ ",E("layout_editor.overlay.jump_device",{device:V.label})]},V.key)),d&&i.jsxs("button",{type:"button","data-testid":"g7le-overlay-separate-branch",title:E("layout_editor.context_menu.separate_branch_hint"),onClick:V=>{V.stopPropagation(),d()},style:pa,children:["📱 ",c??E("layout_editor.context_menu.separate_branch")]}),p&&i.jsxs("button",{type:"button","data-testid":"g7le-overlay-merge-branch",title:E("layout_editor.context_menu.merge_branch_hint"),onClick:V=>{V.stopPropagation(),p()},style:pa,children:["↩️ ",f??E("layout_editor.context_menu.merge_branch")]})]})]}),i.jsx(l2,{anchor:n&&O?{left:n.left+n.width+8,top:n.top-4}:null,open:O,onClose:()=>N(!1),onEditProps:s,onDuplicate:a,onDelete:l,onSeparateBranch:d,separateBranchLabel:c,onMergeBranch:p,mergeBranchLabel:f})]})}function u2(e){const{enabledAxes:t,onHandlePointerDown:n}=e,o=t?.width??!1,r=t?.height??!1,s=[{key:"nw",style:{left:-4,top:-4},cursor:"nwse-resize",needsWidth:!0,needsHeight:!0},{key:"n",style:{left:"50%",top:-4,transform:"translateX(-50%)"},cursor:"ns-resize",needsWidth:!1,needsHeight:!0},{key:"ne",style:{right:-4,top:-4},cursor:"nesw-resize",needsWidth:!0,needsHeight:!0},{key:"e",style:{right:-4,top:"50%",transform:"translateY(-50%)"},cursor:"ew-resize",needsWidth:!0,needsHeight:!1},{key:"se",style:{right:-4,bottom:-4},cursor:"nwse-resize",needsWidth:!0,needsHeight:!0},{key:"s",style:{left:"50%",bottom:-4,transform:"translateX(-50%)"},cursor:"ns-resize",needsWidth:!1,needsHeight:!0},{key:"sw",style:{left:-4,bottom:-4},cursor:"nesw-resize",needsWidth:!0,needsHeight:!0},{key:"w",style:{left:-4,top:"50%",transform:"translateY(-50%)"},cursor:"ew-resize",needsWidth:!0,needsHeight:!1}];return i.jsx(i.Fragment,{children:s.map(a=>{const l=!a.needsWidth||o,d=!a.needsHeight||r,c=l&&d&&!!n;return!l||!d?null:i.jsx("span",{"data-testid":`g7le-resize-handle-${a.key}`,"data-resize-handle-active":c?"true":"false",onPointerDown:c?p=>{p.preventDefault(),p.stopPropagation(),n?.(a.key,{clientX:p.clientX,clientY:p.clientY})}:void 0,style:{position:"absolute",...a.style,width:8,height:8,background:"#fff",border:"1px solid #2563eb",cursor:a.cursor,pointerEvents:c?"auto":"none",zIndex:On}},a.key)})})}const Ko={position:"absolute",top:-28,left:0,background:"#fff",border:"1px solid #2563eb",color:"#2563eb",padding:"2px 6px",fontSize:11,borderRadius:4,cursor:"pointer",pointerEvents:"auto",whiteSpace:"nowrap",zIndex:On},pa={background:"#fff",border:"1px solid #2563eb",color:"#2563eb",padding:"2px 6px",fontSize:11,borderRadius:4,cursor:"pointer",pointerEvents:"auto",whiteSpace:"nowrap",zIndex:On},fa={marginLeft:6,padding:"0 5px",borderRadius:3,background:"#eef2ff",border:"1px solid #c7d2fe",fontSize:10,fontWeight:600,color:"#3730a3"},p2={position:"absolute",top:0,right:0,transform:"translateY(-100%)",background:"rgba(124,58,237,0.92)",color:"#fff",padding:"1px 6px",borderRadius:"3px 3px 0 0",fontSize:10,fontWeight:600,whiteSpace:"nowrap",pointerEvents:"none"},Zp={position:"absolute",top:0,left:0,background:"rgba(37,99,235,0.55)",color:"#fff",padding:"1px 6px",fontSize:10,fontWeight:600,lineHeight:"14px",borderBottomRightRadius:4,letterSpacing:"0.02em",whiteSpace:"nowrap",pointerEvents:"none",userSelect:"none",maxWidth:"100%",overflow:"hidden",textOverflow:"ellipsis"},Qp={position:"absolute",bottom:"100%",left:0,marginBottom:2,background:"rgba(37,99,235,0.55)",color:"#fff",padding:"1px 6px",fontSize:10,fontWeight:600,lineHeight:"14px",borderRadius:4,letterSpacing:"0.02em",whiteSpace:"nowrap",pointerEvents:"none",userSelect:"none"},f2={...Zp,pointerEvents:"auto",cursor:"pointer",background:"rgba(37,99,235,0.82)",border:"none",display:"inline-flex",alignItems:"center",font:"inherit",fontSize:10,fontWeight:600,zIndex:On},g2={...Qp,pointerEvents:"auto",cursor:"pointer",background:"rgba(37,99,235,0.82)",border:"none",display:"inline-flex",alignItems:"center",font:"inherit",fontSize:10,fontWeight:600,zIndex:On},h2={above:{top:-30,left:"50%",transform:"translateX(-50%)"},below:{bottom:-30,left:"50%",transform:"translateX(-50%)"},left:{left:-30,top:"50%",transform:"translateY(-50%)"},right:{right:-30,top:"50%",transform:"translateY(-50%)"}};function y2(e){const{selectedBox:t,points:n,onAddRequest:o}=e,{t:r}=et();if(!t||n.length===0)return null;const s=Fp(t),l=s==="outside"?Vp(t):null;return i.jsx("div",{className:"g7le-insertion-affordances","data-testid":"g7le-insertion-affordances",style:{position:"absolute",left:t.left,top:t.top,width:t.width,height:t.height,pointerEvents:"none"},children:n.map(d=>{const c=l?{left:l[d.direction].left,top:l[d.direction].top}:h2[d.direction],p=r(`layout_editor.insertion.${d.direction}`);return i.jsx("button",{type:"button",disabled:d.disabled,"data-testid":`g7le-insertion-${d.direction}`,"data-disabled":d.disabled?"true":"false","data-placement":s,"aria-label":p,title:p,onClick:f=>{f.stopPropagation(),d.disabled||o(d)},style:{position:"absolute",...c,width:24,height:24,borderRadius:12,border:"1px solid #2563eb",background:d.disabled?"#cbd5e1":"#fff",color:d.disabled?"#94a3b8":"#2563eb",cursor:d.disabled?"not-allowed":"pointer",opacity:d.disabled?.4:1,fontSize:16,fontWeight:700,lineHeight:"20px",padding:0,pointerEvents:"auto",boxShadow:"0 1px 3px rgba(0,0,0,0.15)",zIndex:On},children:"+"},d.direction)})})}const ri=typeof window<"u"&&typeof window.document<"u"&&typeof window.document.createElement<"u";function go(e){const t=Object.prototype.toString.call(e);return t==="[object Window]"||t==="[object global]"}function ga(e){return"nodeType"in e}function xt(e){var t,n;return e?go(e)?e:ga(e)&&(t=(n=e.ownerDocument)==null?void 0:n.defaultView)!=null?t:window:window}function ha(e){const{Document:t}=xt(e);return e instanceof t}function Vo(e){return go(e)?!1:e instanceof xt(e).HTMLElement}function Rp(e){return e instanceof xt(e).SVGElement}function ho(e){return e?go(e)?e.document:ga(e)?ha(e)?e:Vo(e)||Rp(e)?e.ownerDocument:document:document:document}const ln=ri?u.useLayoutEffect:u.useEffect;function ii(e){const t=u.useRef(e);return ln(()=>{t.current=e}),u.useCallback(function(){for(var n=arguments.length,o=new Array(n),r=0;r{e.current=setInterval(o,r)},[]),n=u.useCallback(()=>{e.current!==null&&(clearInterval(e.current),e.current=null)},[]);return[t,n]}function Uo(e,t){t===void 0&&(t=[e]);const n=u.useRef(e);return ln(()=>{n.current!==e&&(n.current=e)},t),n}function qo(e,t){const n=u.useRef();return u.useMemo(()=>{const o=e(n.current);return n.current=o,o},[...t])}function si(e){const t=ii(e),n=u.useRef(null),o=u.useCallback(r=>{r!==n.current&&t?.(r,n.current),n.current=r},[]);return[n,o]}function ai(e){const t=u.useRef();return u.useEffect(()=>{t.current=e},[e]),t.current}let ya={};function li(e,t){return u.useMemo(()=>{if(t)return t;const n=ya[e]==null?0:ya[e]+1;return ya[e]=n,e+"-"+n},[e,t])}function ef(e){return function(t){for(var n=arguments.length,o=new Array(n>1?n-1:0),r=1;r{const l=Object.entries(a);for(const[d,c]of l){const p=s[d];p!=null&&(s[d]=p+e*c)}return s},{...t})}}const yo=ef(1),di=ef(-1);function m2(e){return"clientX"in e&&"clientY"in e}function ba(e){if(!e)return!1;const{KeyboardEvent:t}=xt(e.target);return t&&e instanceof t}function v2(e){if(!e)return!1;const{TouchEvent:t}=xt(e.target);return t&&e instanceof t}function ci(e){if(v2(e)){if(e.touches&&e.touches.length){const{clientX:t,clientY:n}=e.touches[0];return{x:t,y:n}}else if(e.changedTouches&&e.changedTouches.length){const{clientX:t,clientY:n}=e.changedTouches[0];return{x:t,y:n}}}return m2(e)?{x:e.clientX,y:e.clientY}:null}const Go=Object.freeze({Translate:{toString(e){if(!e)return;const{x:t,y:n}=e;return"translate3d("+(t?Math.round(t):0)+"px, "+(n?Math.round(n):0)+"px, 0)"}},Scale:{toString(e){if(!e)return;const{scaleX:t,scaleY:n}=e;return"scaleX("+t+") scaleY("+n+")"}},Transform:{toString(e){if(e)return[Go.Translate.toString(e),Go.Scale.toString(e)].join(" ")}},Transition:{toString(e){let{property:t,duration:n,easing:o}=e;return t+" "+n+"ms "+o}}}),tf="a,frame,iframe,input:not([type=hidden]):not(:disabled),select:not(:disabled),textarea:not(:disabled),button:not(:disabled),*[tabindex]";function _2(e){return e.matches(tf)?e:e.querySelector(tf)}const x2={display:"none"};function w2(e){let{id:t,value:n}=e;return u.createElement("div",{id:t,style:x2},n)}function k2(e){let{id:t,announcement:n,ariaLiveType:o="assertive"}=e;const r={position:"fixed",top:0,left:0,width:1,height:1,margin:-1,border:0,padding:0,overflow:"hidden",clip:"rect(0 0 0 0)",clipPath:"inset(100%)",whiteSpace:"nowrap"};return u.createElement("div",{id:t,style:r,role:"status","aria-live":o,"aria-atomic":!0},n)}function S2(){const[e,t]=u.useState("");return{announce:u.useCallback(o=>{o!=null&&t(o)},[]),announcement:e}}const nf=u.createContext(null);function C2(e){const t=u.useContext(nf);u.useEffect(()=>{if(!t)throw new Error("useDndMonitor must be used within a children of ");return t(e)},[e,t])}function $2(){const[e]=u.useState(()=>new Set),t=u.useCallback(o=>(e.add(o),()=>e.delete(o)),[e]);return[u.useCallback(o=>{let{type:r,event:s}=o;e.forEach(a=>{var l;return(l=a[r])==null?void 0:l.call(a,s)})},[e]),t]}const E2={draggable:` To pick up a draggable item, press the space bar. While dragging, use the arrow keys to move the item. Press space again to drop the item in its new position, or press escape to cancel. - `},d2={onDragStart(e){let{active:t}=e;return"Picked up draggable item "+t.id+"."},onDragOver(e){let{active:t,over:n}=e;return n?"Draggable item "+t.id+" was moved over droppable area "+n.id+".":"Draggable item "+t.id+" is no longer over a droppable area."},onDragEnd(e){let{active:t,over:n}=e;return n?"Draggable item "+t.id+" was dropped over droppable area "+n.id:"Draggable item "+t.id+" was dropped."},onDragCancel(e){let{active:t}=e;return"Dragging was cancelled. Draggable item "+t.id+" was dropped."}};function c2(e){let{announcements:t=d2,container:n,hiddenTextDescribedById:o,screenReaderInstructions:r=l2}=e;const{announce:s,announcement:a}=i2(),l=oi("DndLiveRegion"),[d,c]=u.useState(!1);if(u.useEffect(()=>{c(!0)},[]),s2(u.useMemo(()=>({onDragStart(f){let{active:b}=f;s(t.onDragStart({active:b}))},onDragMove(f){let{active:b,over:g}=f;t.onDragMove&&s(t.onDragMove({active:b,over:g}))},onDragOver(f){let{active:b,over:g}=f;s(t.onDragOver({active:b,over:g}))},onDragEnd(f){let{active:b,over:g}=f;s(t.onDragEnd({active:b,over:g}))},onDragCancel(f){let{active:b,over:g}=f;s(t.onDragCancel({active:b,over:g}))}}),[s,t])),!d)return null;const p=u.createElement(u.Fragment,null,u.createElement(o2,{id:o,value:r.draggable}),u.createElement(r2,{id:l,announcement:a}));return n?Cn.createPortal(p,n):p}var it;(function(e){e.DragStart="dragStart",e.DragMove="dragMove",e.DragEnd="dragEnd",e.DragCancel="dragCancel",e.DragOver="dragOver",e.RegisterDroppable="registerDroppable",e.SetDroppableDisabled="setDroppableDisabled",e.UnregisterDroppable="unregisterDroppable"})(it||(it={}));function si(){}function u2(e,t){return u.useMemo(()=>({sensor:e,options:t??{}}),[e,t])}function p2(){for(var e=arguments.length,t=new Array(e),n=0;n[...t].filter(o=>o!=null),[...t])}const Ht=Object.freeze({x:0,y:0});function f2(e,t){return Math.sqrt(Math.pow(e.x-t.x,2)+Math.pow(e.y-t.y,2))}function g2(e,t){const n=ii(e);if(!n)return"0 0";const o={x:(n.x-t.left)/t.width*100,y:(n.y-t.top)/t.height*100};return o.x+"% "+o.y+"%"}function h2(e,t){let{data:{value:n}}=e,{data:{value:o}}=t;return n-o}function y2(e,t){let{data:{value:n}}=e,{data:{value:o}}=t;return o-n}function b2(e){let{left:t,top:n,height:o,width:r}=e;return[{x:t,y:n},{x:t+r,y:n},{x:t,y:n+o},{x:t+r,y:n+o}]}function m2(e,t){if(!e||e.length===0)return null;const[n]=e;return n[t]}function v2(e,t){const n=Math.max(t.top,e.top),o=Math.max(t.left,e.left),r=Math.min(t.left+t.width,e.left+e.width),s=Math.min(t.top+t.height,e.top+e.height),a=r-o,l=s-n;if(o{let{collisionRect:t,droppableRects:n,droppableContainers:o}=e;const r=[];for(const s of o){const{id:a}=s,l=n.get(a);if(l){const d=v2(l,t);d>0&&r.push({id:a,data:{droppableContainer:s,value:d}})}}return r.sort(y2)};function x2(e,t){const{top:n,left:o,bottom:r,right:s}=t;return n<=e.y&&e.y<=r&&o<=e.x&&e.x<=s}const w2=e=>{let{droppableContainers:t,droppableRects:n,pointerCoordinates:o}=e;if(!o)return[];const r=[];for(const s of t){const{id:a}=s,l=n.get(a);if(l&&x2(o,l)){const c=b2(l).reduce((f,b)=>f+f2(o,b),0),p=Number((c/4).toFixed(4));r.push({id:a,data:{droppableContainer:s,value:p}})}}return r.sort(h2)};function k2(e,t,n){return{...e,scaleX:t&&n?t.width/n.width:1,scaleY:t&&n?t.height/n.height:1}}function Xp(e,t){return e&&t?{x:e.left-t.left,y:e.top-t.top}:Ht}function S2(e){return function(n){for(var o=arguments.length,r=new Array(o>1?o-1:0),s=1;s({...a,top:a.top+e*l.y,bottom:a.bottom+e*l.y,left:a.left+e*l.x,right:a.right+e*l.x}),{...n})}}const C2=S2(1);function Jp(e){if(e.startsWith("matrix3d(")){const t=e.slice(9,-1).split(/, /);return{x:+t[12],y:+t[13],scaleX:+t[0],scaleY:+t[5]}}else if(e.startsWith("matrix(")){const t=e.slice(7,-1).split(/, /);return{x:+t[4],y:+t[5],scaleX:+t[0],scaleY:+t[3]}}return null}function $2(e,t,n){const o=Jp(t);if(!o)return e;const{scaleX:r,scaleY:s,x:a,y:l}=o,d=e.left-a-(1-r)*parseFloat(n),c=e.top-l-(1-s)*parseFloat(n.slice(n.indexOf(" ")+1)),p=r?e.width/r:e.width,f=s?e.height/s:e.height;return{width:p,height:f,top:c,right:d+p,bottom:c+f,left:d}}const E2={ignoreTransform:!1};function Vo(e,t){t===void 0&&(t=E2);let n=e.getBoundingClientRect();if(t.ignoreTransform){const{transform:c,transformOrigin:p}=xt(e).getComputedStyle(e);c&&(n=$2(n,c,p))}const{top:o,left:r,width:s,height:a,bottom:l,right:d}=n;return{top:o,left:r,width:s,height:a,bottom:l,right:d}}function Zp(e){return Vo(e,{ignoreTransform:!0})}function I2(e){const t=e.innerWidth,n=e.innerHeight;return{top:0,left:0,right:t,bottom:n,width:t,height:n}}function A2(e,t){return t===void 0&&(t=xt(e).getComputedStyle(e)),t.position==="fixed"}function T2(e,t){t===void 0&&(t=xt(e).getComputedStyle(e));const n=/(auto|scroll|overlay)/;return["overflow","overflowX","overflowY"].some(r=>{const s=t[r];return typeof s=="string"?n.test(s):!1})}function pa(e,t){const n=[];function o(r){if(t!=null&&n.length>=t||!r)return n;if(da(r)&&r.scrollingElement!=null&&!n.includes(r.scrollingElement))return n.push(r.scrollingElement),n;if(!jo(r)||Up(r)||n.includes(r))return n;const s=xt(e).getComputedStyle(r);return r!==e&&T2(r,s)&&n.push(r),A2(r,s)?n:o(r.parentNode)}return e?o(e):n}function Qp(e){const[t]=pa(e,1);return t??null}function fa(e){return!Rr||!e?null:co(e)?e:la(e)?da(e)||e===uo(e).scrollingElement?window:jo(e)?e:null:null}function Rp(e){return co(e)?e.scrollX:e.scrollLeft}function ef(e){return co(e)?e.scrollY:e.scrollTop}function ga(e){return{x:Rp(e),y:ef(e)}}var lt;(function(e){e[e.Forward=1]="Forward",e[e.Backward=-1]="Backward"})(lt||(lt={}));function tf(e){return!Rr||!e?!1:e===document.scrollingElement}function nf(e){const t={x:0,y:0},n=tf(e)?{height:window.innerHeight,width:window.innerWidth}:{height:e.clientHeight,width:e.clientWidth},o={x:e.scrollWidth-n.width,y:e.scrollHeight-n.height},r=e.scrollTop<=t.y,s=e.scrollLeft<=t.x,a=e.scrollTop>=o.y,l=e.scrollLeft>=o.x;return{isTop:r,isLeft:s,isBottom:a,isRight:l,maxScroll:o,minScroll:t}}const D2={x:.2,y:.2};function z2(e,t,n,o,r){let{top:s,left:a,right:l,bottom:d}=n;o===void 0&&(o=10),r===void 0&&(r=D2);const{isTop:c,isBottom:p,isLeft:f,isRight:b}=nf(e),g={x:0,y:0},m={x:0,y:0},h={height:t.height*r.y,width:t.width*r.x};return!c&&s<=t.top+h.height?(g.y=lt.Backward,m.y=o*Math.abs((t.top+h.height-s)/h.height)):!p&&d>=t.bottom-h.height&&(g.y=lt.Forward,m.y=o*Math.abs((t.bottom-h.height-d)/h.height)),!b&&l>=t.right-h.width?(g.x=lt.Forward,m.x=o*Math.abs((t.right-h.width-l)/h.width)):!f&&a<=t.left+h.width&&(g.x=lt.Backward,m.x=o*Math.abs((t.left+h.width-a)/h.width)),{direction:g,speed:m}}function N2(e){if(e===document.scrollingElement){const{innerWidth:s,innerHeight:a}=window;return{top:0,left:0,right:s,bottom:a,width:s,height:a}}const{top:t,left:n,right:o,bottom:r}=e.getBoundingClientRect();return{top:t,left:n,right:o,bottom:r,width:e.clientWidth,height:e.clientHeight}}function of(e){return e.reduce((t,n)=>po(t,ga(n)),Ht)}function M2(e){return e.reduce((t,n)=>t+Rp(n),0)}function L2(e){return e.reduce((t,n)=>t+ef(n),0)}function rf(e,t){if(t===void 0&&(t=Vo),!e)return;const{top:n,left:o,bottom:r,right:s}=t(e);Qp(e)&&(r<=0||s<=0||n>=window.innerHeight||o>=window.innerWidth)&&e.scrollIntoView({block:"center",inline:"center"})}const P2=[["x",["left","right"],M2],["y",["top","bottom"],L2]];class ha{constructor(t,n){this.rect=void 0,this.width=void 0,this.height=void 0,this.top=void 0,this.bottom=void 0,this.right=void 0,this.left=void 0;const o=pa(n),r=of(o);this.rect={...t},this.width=t.width,this.height=t.height;for(const[s,a,l]of P2)for(const d of a)Object.defineProperty(this,d,{get:()=>{const c=l(o),p=r[s]-c;return this.rect[d]+p},enumerable:!0});Object.defineProperty(this,"rect",{enumerable:!1})}}class Uo{constructor(t){this.target=void 0,this.listeners=[],this.removeAll=()=>{this.listeners.forEach(n=>{var o;return(o=this.target)==null?void 0:o.removeEventListener(...n)})},this.target=t}add(t,n,o){var r;(r=this.target)==null||r.addEventListener(t,n,o),this.listeners.push([t,n,o])}}function O2(e){const{EventTarget:t}=xt(e);return e instanceof t?e:uo(e)}function ya(e,t){const n=Math.abs(e.x),o=Math.abs(e.y);return typeof t=="number"?Math.sqrt(n**2+o**2)>t:"x"in t&&"y"in t?n>t.x&&o>t.y:"x"in t?n>t.x:"y"in t?o>t.y:!1}var At;(function(e){e.Click="click",e.DragStart="dragstart",e.Keydown="keydown",e.ContextMenu="contextmenu",e.Resize="resize",e.SelectionChange="selectionchange",e.VisibilityChange="visibilitychange"})(At||(At={}));function sf(e){e.preventDefault()}function B2(e){e.stopPropagation()}var Fe;(function(e){e.Space="Space",e.Down="ArrowDown",e.Right="ArrowRight",e.Left="ArrowLeft",e.Up="ArrowUp",e.Esc="Escape",e.Enter="Enter",e.Tab="Tab"})(Fe||(Fe={}));const af={start:[Fe.Space,Fe.Enter],cancel:[Fe.Esc],end:[Fe.Space,Fe.Enter,Fe.Tab]},W2=(e,t)=>{let{currentCoordinates:n}=t;switch(e.code){case Fe.Right:return{...n,x:n.x+25};case Fe.Left:return{...n,x:n.x-25};case Fe.Down:return{...n,y:n.y+25};case Fe.Up:return{...n,y:n.y-25}}};class lf{constructor(t){this.props=void 0,this.autoScrollEnabled=!1,this.referenceCoordinates=void 0,this.listeners=void 0,this.windowListeners=void 0,this.props=t;const{event:{target:n}}=t;this.props=t,this.listeners=new Uo(uo(n)),this.windowListeners=new Uo(xt(n)),this.handleKeyDown=this.handleKeyDown.bind(this),this.handleCancel=this.handleCancel.bind(this),this.attach()}attach(){this.handleStart(),this.windowListeners.add(At.Resize,this.handleCancel),this.windowListeners.add(At.VisibilityChange,this.handleCancel),setTimeout(()=>this.listeners.add(At.Keydown,this.handleKeyDown))}handleStart(){const{activeNode:t,onStart:n}=this.props,o=t.node.current;o&&rf(o),n(Ht)}handleKeyDown(t){if(ua(t)){const{active:n,context:o,options:r}=this.props,{keyboardCodes:s=af,coordinateGetter:a=W2,scrollBehavior:l="smooth"}=r,{code:d}=t;if(s.end.includes(d)){this.handleEnd(t);return}if(s.cancel.includes(d)){this.handleCancel(t);return}const{collisionRect:c}=o.current,p=c?{x:c.left,y:c.top}:Ht;this.referenceCoordinates||(this.referenceCoordinates=p);const f=a(t,{active:n,context:o.current,currentCoordinates:p});if(f){const b=ri(f,p),g={x:0,y:0},{scrollableAncestors:m}=o.current;for(const h of m){const v=t.code,{isTop:y,isRight:_,isLeft:w,isBottom:k,maxScroll:x,minScroll:$}=nf(h),S=N2(h),C={x:Math.min(v===Fe.Right?S.right-S.width/2:S.right,Math.max(v===Fe.Right?S.left:S.left+S.width/2,f.x)),y:Math.min(v===Fe.Down?S.bottom-S.height/2:S.bottom,Math.max(v===Fe.Down?S.top:S.top+S.height/2,f.y))},T=v===Fe.Right&&!_||v===Fe.Left&&!w,E=v===Fe.Down&&!k||v===Fe.Up&&!y;if(T&&C.x!==f.x){const A=h.scrollLeft+b.x,N=v===Fe.Right&&A<=x.x||v===Fe.Left&&A>=$.x;if(N&&!b.y){h.scrollTo({left:A,behavior:l});return}N?g.x=h.scrollLeft-A:g.x=v===Fe.Right?h.scrollLeft-x.x:h.scrollLeft-$.x,g.x&&h.scrollBy({left:-g.x,behavior:l});break}else if(E&&C.y!==f.y){const A=h.scrollTop+b.y,N=v===Fe.Down&&A<=x.y||v===Fe.Up&&A>=$.y;if(N&&!b.x){h.scrollTo({top:A,behavior:l});return}N?g.y=h.scrollTop-A:g.y=v===Fe.Down?h.scrollTop-x.y:h.scrollTop-$.y,g.y&&h.scrollBy({top:-g.y,behavior:l});break}}this.handleMove(t,po(ri(f,this.referenceCoordinates),g))}}}handleMove(t,n){const{onMove:o}=this.props;t.preventDefault(),o(n)}handleEnd(t){const{onEnd:n}=this.props;t.preventDefault(),this.detach(),n()}handleCancel(t){const{onCancel:n}=this.props;t.preventDefault(),this.detach(),n()}detach(){this.listeners.removeAll(),this.windowListeners.removeAll()}}lf.activators=[{eventName:"onKeyDown",handler:(e,t,n)=>{let{keyboardCodes:o=af,onActivation:r}=t,{active:s}=n;const{code:a}=e.nativeEvent;if(o.start.includes(a)){const l=s.activatorNode.current;return l&&e.target!==l?!1:(e.preventDefault(),r?.({event:e.nativeEvent}),!0)}return!1}}];function df(e){return!!(e&&"distance"in e)}function cf(e){return!!(e&&"delay"in e)}class ba{constructor(t,n,o){var r;o===void 0&&(o=O2(t.event.target)),this.props=void 0,this.events=void 0,this.autoScrollEnabled=!0,this.document=void 0,this.activated=!1,this.initialCoordinates=void 0,this.timeoutId=null,this.listeners=void 0,this.documentListeners=void 0,this.windowListeners=void 0,this.props=t,this.events=n;const{event:s}=t,{target:a}=s;this.props=t,this.events=n,this.document=uo(a),this.documentListeners=new Uo(this.document),this.listeners=new Uo(o),this.windowListeners=new Uo(xt(a)),this.initialCoordinates=(r=ii(s))!=null?r:Ht,this.handleStart=this.handleStart.bind(this),this.handleMove=this.handleMove.bind(this),this.handleEnd=this.handleEnd.bind(this),this.handleCancel=this.handleCancel.bind(this),this.handleKeydown=this.handleKeydown.bind(this),this.removeTextSelection=this.removeTextSelection.bind(this),this.attach()}attach(){const{events:t,props:{options:{activationConstraint:n,bypassActivationConstraint:o}}}=this;if(this.listeners.add(t.move.name,this.handleMove,{passive:!1}),this.listeners.add(t.end.name,this.handleEnd),t.cancel&&this.listeners.add(t.cancel.name,this.handleCancel),this.windowListeners.add(At.Resize,this.handleCancel),this.windowListeners.add(At.DragStart,sf),this.windowListeners.add(At.VisibilityChange,this.handleCancel),this.windowListeners.add(At.ContextMenu,sf),this.documentListeners.add(At.Keydown,this.handleKeydown),n){if(o!=null&&o({event:this.props.event,activeNode:this.props.activeNode,options:this.props.options}))return this.handleStart();if(cf(n)){this.timeoutId=setTimeout(this.handleStart,n.delay),this.handlePending(n);return}if(df(n)){this.handlePending(n);return}}this.handleStart()}detach(){this.listeners.removeAll(),this.windowListeners.removeAll(),setTimeout(this.documentListeners.removeAll,50),this.timeoutId!==null&&(clearTimeout(this.timeoutId),this.timeoutId=null)}handlePending(t,n){const{active:o,onPending:r}=this.props;r(o,t,this.initialCoordinates,n)}handleStart(){const{initialCoordinates:t}=this,{onStart:n}=this.props;t&&(this.activated=!0,this.documentListeners.add(At.Click,B2,{capture:!0}),this.removeTextSelection(),this.documentListeners.add(At.SelectionChange,this.removeTextSelection),n(t))}handleMove(t){var n;const{activated:o,initialCoordinates:r,props:s}=this,{onMove:a,options:{activationConstraint:l}}=s;if(!r)return;const d=(n=ii(t))!=null?n:Ht,c=ri(r,d);if(!o&&l){if(df(l)){if(l.tolerance!=null&&ya(c,l.tolerance))return this.handleCancel();if(ya(c,l.distance))return this.handleStart()}if(cf(l)&&ya(c,l.tolerance))return this.handleCancel();this.handlePending(l,c);return}t.cancelable&&t.preventDefault(),a(d)}handleEnd(){const{onAbort:t,onEnd:n}=this.props;this.detach(),this.activated||t(this.props.active),n()}handleCancel(){const{onAbort:t,onCancel:n}=this.props;this.detach(),this.activated||t(this.props.active),n()}handleKeydown(t){t.code===Fe.Esc&&this.handleCancel()}removeTextSelection(){var t;(t=this.document.getSelection())==null||t.removeAllRanges()}}const j2={cancel:{name:"pointercancel"},move:{name:"pointermove"},end:{name:"pointerup"}};class ma extends ba{constructor(t){const{event:n}=t,o=uo(n.target);super(t,j2,o)}}ma.activators=[{eventName:"onPointerDown",handler:(e,t)=>{let{nativeEvent:n}=e,{onActivation:o}=t;return!n.isPrimary||n.button!==0?!1:(o?.({event:n}),!0)}}];const H2={move:{name:"mousemove"},end:{name:"mouseup"}};var va;(function(e){e[e.RightClick=2]="RightClick"})(va||(va={}));class F2 extends ba{constructor(t){super(t,H2,uo(t.event.target))}}F2.activators=[{eventName:"onMouseDown",handler:(e,t)=>{let{nativeEvent:n}=e,{onActivation:o}=t;return n.button===va.RightClick?!1:(o?.({event:n}),!0)}}];const _a={cancel:{name:"touchcancel"},move:{name:"touchmove"},end:{name:"touchend"}};class K2 extends ba{constructor(t){super(t,_a)}static setup(){return window.addEventListener(_a.move.name,t,{capture:!1,passive:!1}),function(){window.removeEventListener(_a.move.name,t)};function t(){}}}K2.activators=[{eventName:"onTouchStart",handler:(e,t)=>{let{nativeEvent:n}=e,{onActivation:o}=t;const{touches:r}=n;return r.length>1?!1:(o?.({event:n}),!0)}}];var qo;(function(e){e[e.Pointer=0]="Pointer",e[e.DraggableRect=1]="DraggableRect"})(qo||(qo={}));var ai;(function(e){e[e.TreeOrder=0]="TreeOrder",e[e.ReversedTreeOrder=1]="ReversedTreeOrder"})(ai||(ai={}));function V2(e){let{acceleration:t,activator:n=qo.Pointer,canScroll:o,draggingRect:r,enabled:s,interval:a=5,order:l=ai.TreeOrder,pointerCoordinates:d,scrollableAncestors:c,scrollableAncestorRects:p,delta:f,threshold:b}=e;const g=q2({delta:f,disabled:!s}),[m,h]=Q7(),v=u.useRef({x:0,y:0}),y=u.useRef({x:0,y:0}),_=u.useMemo(()=>{switch(n){case qo.Pointer:return d?{top:d.y,bottom:d.y,left:d.x,right:d.x}:null;case qo.DraggableRect:return r}},[n,r,d]),w=u.useRef(null),k=u.useCallback(()=>{const $=w.current;if(!$)return;const S=v.current.x*y.current.x,C=v.current.y*y.current.y;$.scrollBy(S,C)},[]),x=u.useMemo(()=>l===ai.TreeOrder?[...c].reverse():c,[l,c]);u.useEffect(()=>{if(!s||!c.length||!_){h();return}for(const $ of x){if(o?.($)===!1)continue;const S=c.indexOf($),C=p[S];if(!C)continue;const{direction:T,speed:E}=z2($,C,_,t,b);for(const A of["x","y"])g[A][T[A]]||(E[A]=0,T[A]=0);if(E.x>0||E.y>0){h(),w.current=$,m(k,a),v.current=E,y.current=T;return}}v.current={x:0,y:0},y.current={x:0,y:0},h()},[t,k,o,h,s,a,JSON.stringify(_),JSON.stringify(g),m,c,x,p,JSON.stringify(b)])}const U2={x:{[lt.Backward]:!1,[lt.Forward]:!1},y:{[lt.Backward]:!1,[lt.Forward]:!1}};function q2(e){let{delta:t,disabled:n}=e;const o=ni(t);return Fo(r=>{if(n||!o||!r)return U2;const s={x:Math.sign(t.x-o.x),y:Math.sign(t.y-o.y)};return{x:{[lt.Backward]:r.x[lt.Backward]||s.x===-1,[lt.Forward]:r.x[lt.Forward]||s.x===1},y:{[lt.Backward]:r.y[lt.Backward]||s.y===-1,[lt.Forward]:r.y[lt.Forward]||s.y===1}}},[n,t,o])}function G2(e,t){const n=t!=null?e.get(t):void 0,o=n?n.node.current:null;return Fo(r=>{var s;return t==null?null:(s=o??r)!=null?s:null},[o,t])}function Y2(e,t){return u.useMemo(()=>e.reduce((n,o)=>{const{sensor:r}=o,s=r.activators.map(a=>({eventName:a.eventName,handler:t(a.handler,o)}));return[...n,...s]},[]),[e,t])}var Go;(function(e){e[e.Always=0]="Always",e[e.BeforeDragging=1]="BeforeDragging",e[e.WhileDragging=2]="WhileDragging"})(Go||(Go={}));var xa;(function(e){e.Optimized="optimized"})(xa||(xa={}));const uf=new Map;function X2(e,t){let{dragging:n,dependencies:o,config:r}=t;const[s,a]=u.useState(null),{frequency:l,measure:d,strategy:c}=r,p=u.useRef(e),f=v(),b=Ho(f),g=u.useCallback(function(y){y===void 0&&(y=[]),!b.current&&a(_=>_===null?y:_.concat(y.filter(w=>!_.includes(w))))},[b]),m=u.useRef(null),h=Fo(y=>{if(f&&!n)return uf;if(!y||y===uf||p.current!==e||s!=null){const _=new Map;for(let w of e){if(!w)continue;if(s&&s.length>0&&!s.includes(w.id)&&w.rect.current){_.set(w.id,w.rect.current);continue}const k=w.node.current,x=k?new ha(d(k),k):null;w.rect.current=x,x&&_.set(w.id,x)}return _}return y},[e,s,n,f,d]);return u.useEffect(()=>{p.current=e},[e]),u.useEffect(()=>{f||g()},[n,f]),u.useEffect(()=>{s&&s.length>0&&a(null)},[JSON.stringify(s)]),u.useEffect(()=>{f||typeof l!="number"||m.current!==null||(m.current=setTimeout(()=>{g(),m.current=null},l))},[l,f,g,...o]),{droppableRects:h,measureDroppableContainers:g,measuringScheduled:s!=null};function v(){switch(c){case Go.Always:return!1;case Go.BeforeDragging:return n;default:return!n}}}function wa(e,t){return Fo(n=>e?n||(typeof t=="function"?t(e):e):null,[t,e])}function J2(e,t){return wa(e,t)}function Z2(e){let{callback:t,disabled:n}=e;const o=ei(t),r=u.useMemo(()=>{if(n||typeof window>"u"||typeof window.MutationObserver>"u")return;const{MutationObserver:s}=window;return new s(o)},[o,n]);return u.useEffect(()=>()=>r?.disconnect(),[r]),r}function li(e){let{callback:t,disabled:n}=e;const o=ei(t),r=u.useMemo(()=>{if(n||typeof window>"u"||typeof window.ResizeObserver>"u")return;const{ResizeObserver:s}=window;return new s(o)},[n]);return u.useEffect(()=>()=>r?.disconnect(),[r]),r}function Q2(e){return new ha(Vo(e),e)}function pf(e,t,n){t===void 0&&(t=Q2);const[o,r]=u.useState(null);function s(){r(d=>{if(!e)return null;if(e.isConnected===!1){var c;return(c=d??n)!=null?c:null}const p=t(e);return JSON.stringify(d)===JSON.stringify(p)?d:p})}const a=Z2({callback(d){if(e)for(const c of d){const{type:p,target:f}=c;if(p==="childList"&&f instanceof HTMLElement&&f.contains(e)){s();break}}}}),l=li({callback:s});return sn(()=>{s(),e?(l?.observe(e),a?.observe(document.body,{childList:!0,subtree:!0})):(l?.disconnect(),a?.disconnect())},[e]),o}function R2(e){const t=wa(e);return Xp(e,t)}const ff=[];function eI(e){const t=u.useRef(e),n=Fo(o=>e?o&&o!==ff&&e&&t.current&&e.parentNode===t.current.parentNode?o:pa(e):ff,[e]);return u.useEffect(()=>{t.current=e},[e]),n}function tI(e){const[t,n]=u.useState(null),o=u.useRef(e),r=u.useCallback(s=>{const a=fa(s.target);a&&n(l=>l?(l.set(a,ga(a)),new Map(l)):null)},[]);return u.useEffect(()=>{const s=o.current;if(e!==s){a(s);const l=e.map(d=>{const c=fa(d);return c?(c.addEventListener("scroll",r,{passive:!0}),[c,ga(c)]):null}).filter(d=>d!=null);n(l.length?new Map(l):null),o.current=e}return()=>{a(e),a(s)};function a(l){l.forEach(d=>{const c=fa(d);c?.removeEventListener("scroll",r)})}},[r,e]),u.useMemo(()=>e.length?t?Array.from(t.values()).reduce((s,a)=>po(s,a),Ht):of(e):Ht,[e,t])}function gf(e,t){t===void 0&&(t=[]);const n=u.useRef(null);return u.useEffect(()=>{n.current=null},t),u.useEffect(()=>{const o=e!==Ht;o&&!n.current&&(n.current=e),!o&&n.current&&(n.current=null)},[e]),n.current?ri(e,n.current):Ht}function nI(e){u.useEffect(()=>{if(!Rr)return;const t=e.map(n=>{let{sensor:o}=n;return o.setup==null?void 0:o.setup()});return()=>{for(const n of t)n?.()}},e.map(t=>{let{sensor:n}=t;return n}))}function oI(e,t){return u.useMemo(()=>e.reduce((n,o)=>{let{eventName:r,handler:s}=o;return n[r]=a=>{s(a,t)},n},{}),[e,t])}function hf(e){return u.useMemo(()=>e?I2(e):null,[e])}const yf=[];function rI(e,t){t===void 0&&(t=Vo);const[n]=e,o=hf(n?xt(n):null),[r,s]=u.useState(yf);function a(){s(()=>e.length?e.map(d=>tf(d)?o:new ha(t(d),d)):yf)}const l=li({callback:a});return sn(()=>{l?.disconnect(),a(),e.forEach(d=>l?.observe(d))},[e]),r}function bf(e){if(!e)return null;if(e.children.length>1)return e;const t=e.children[0];return jo(t)?t:e}function iI(e){let{measure:t}=e;const[n,o]=u.useState(null),r=u.useCallback(c=>{for(const{target:p}of c)if(jo(p)){o(f=>{const b=t(p);return f?{...f,width:b.width,height:b.height}:b});break}},[t]),s=li({callback:r}),a=u.useCallback(c=>{const p=bf(c);s?.disconnect(),p&&s?.observe(p),o(p?t(p):null)},[t,s]),[l,d]=ti(a);return u.useMemo(()=>({nodeRef:l,rect:n,setRef:d}),[n,l,d])}const sI=[{sensor:ma,options:{}},{sensor:lf,options:{}}],aI={current:{}},di={draggable:{measure:Zp},droppable:{measure:Zp,strategy:Go.WhileDragging,frequency:xa.Optimized},dragOverlay:{measure:Vo}};class Yo extends Map{get(t){var n;return t!=null&&(n=super.get(t))!=null?n:void 0}toArray(){return Array.from(this.values())}getEnabled(){return this.toArray().filter(t=>{let{disabled:n}=t;return!n})}getNodeFor(t){var n,o;return(n=(o=this.get(t))==null?void 0:o.node.current)!=null?n:void 0}}const lI={activatorEvent:null,active:null,activeNode:null,activeNodeRect:null,collisions:null,containerNodeRect:null,draggableNodes:new Map,droppableRects:new Map,droppableContainers:new Yo,over:null,dragOverlay:{nodeRef:{current:null},rect:null,setRef:si},scrollableAncestors:[],scrollableAncestorRects:[],measuringConfiguration:di,measureDroppableContainers:si,windowRect:null,measuringScheduled:!1},mf={activatorEvent:null,activators:[],active:null,activeNodeRect:null,ariaDescribedById:{draggable:""},dispatch:si,draggableNodes:new Map,over:null,measureDroppableContainers:si},Xo=u.createContext(mf),vf=u.createContext(lI);function dI(){return{draggable:{active:null,initialCoordinates:{x:0,y:0},nodes:new Map,translate:{x:0,y:0}},droppable:{containers:new Yo}}}function cI(e,t){switch(t.type){case it.DragStart:return{...e,draggable:{...e.draggable,initialCoordinates:t.initialCoordinates,active:t.active}};case it.DragMove:return e.draggable.active==null?e:{...e,draggable:{...e.draggable,translate:{x:t.coordinates.x-e.draggable.initialCoordinates.x,y:t.coordinates.y-e.draggable.initialCoordinates.y}}};case it.DragEnd:case it.DragCancel:return{...e,draggable:{...e.draggable,active:null,initialCoordinates:{x:0,y:0},translate:{x:0,y:0}}};case it.RegisterDroppable:{const{element:n}=t,{id:o}=n,r=new Yo(e.droppable.containers);return r.set(o,n),{...e,droppable:{...e.droppable,containers:r}}}case it.SetDroppableDisabled:{const{id:n,key:o,disabled:r}=t,s=e.droppable.containers.get(n);if(!s||o!==s.key)return e;const a=new Yo(e.droppable.containers);return a.set(n,{...s,disabled:r}),{...e,droppable:{...e.droppable,containers:a}}}case it.UnregisterDroppable:{const{id:n,key:o}=t,r=e.droppable.containers.get(n);if(!r||o!==r.key)return e;const s=new Yo(e.droppable.containers);return s.delete(n),{...e,droppable:{...e.droppable,containers:s}}}default:return e}}function uI(e){let{disabled:t}=e;const{active:n,activatorEvent:o,draggableNodes:r}=u.useContext(Xo),s=ni(o),a=ni(n?.id);return u.useEffect(()=>{if(!t&&!o&&s&&a!=null){if(!ua(s)||document.activeElement===s.target)return;const l=r.get(a);if(!l)return;const{activatorNode:d,node:c}=l;if(!d.current&&!c.current)return;requestAnimationFrame(()=>{for(const p of[d.current,c.current]){if(!p)continue;const f=t2(p);if(f){f.focus();break}}})}},[o,t,r,a,s]),null}function _f(e,t){let{transform:n,...o}=t;return e!=null&&e.length?e.reduce((r,s)=>s({transform:r,...o}),n):n}function pI(e){return u.useMemo(()=>({draggable:{...di.draggable,...e?.draggable},droppable:{...di.droppable,...e?.droppable},dragOverlay:{...di.dragOverlay,...e?.dragOverlay}}),[e?.draggable,e?.droppable,e?.dragOverlay])}function fI(e){let{activeNode:t,measure:n,initialRect:o,config:r=!0}=e;const s=u.useRef(!1),{x:a,y:l}=typeof r=="boolean"?{x:r,y:r}:r;sn(()=>{if(!a&&!l||!t){s.current=!1;return}if(s.current||!o)return;const c=t?.node.current;if(!c||c.isConnected===!1)return;const p=n(c),f=Xp(p,o);if(a||(f.x=0),l||(f.y=0),s.current=!0,Math.abs(f.x)>0||Math.abs(f.y)>0){const b=Qp(c);b&&b.scrollBy({top:f.y,left:f.x})}},[t,a,l,o,n])}const ci=u.createContext({...Ht,scaleX:1,scaleY:1});var hn;(function(e){e[e.Uninitialized=0]="Uninitialized",e[e.Initializing=1]="Initializing",e[e.Initialized=2]="Initialized"})(hn||(hn={}));const gI=u.memo(function(t){var n,o,r,s;let{id:a,accessibility:l,autoScroll:d=!0,children:c,sensors:p=sI,collisionDetection:f=_2,measuring:b,modifiers:g,...m}=t;const h=u.useReducer(cI,void 0,dI),[v,y]=h,[_,w]=a2(),[k,x]=u.useState(hn.Uninitialized),$=k===hn.Initialized,{draggable:{active:S,nodes:C,translate:T},droppable:{containers:E}}=v,A=S!=null?C.get(S):null,N=u.useRef({initial:null,translated:null}),D=u.useMemo(()=>{var Q;return S!=null?{id:S,data:(Q=A?.data)!=null?Q:aI,rect:N}:null},[S,A]),B=u.useRef(null),[M,O]=u.useState(null),[z,H]=u.useState(null),P=Ho(m,Object.values(m)),I=oi("DndDescribedBy",a),L=u.useMemo(()=>E.getEnabled(),[E]),F=pI(b),{droppableRects:Y,measureDroppableContainers:K,measuringScheduled:X}=X2(L,{dragging:$,dependencies:[T.x,T.y],config:F.droppable}),Z=G2(C,S),se=u.useMemo(()=>z?ii(z):null,[z]),oe=pe(),G=J2(Z,F.draggable.measure);fI({activeNode:S!=null?C.get(S):null,config:oe.layoutShiftCompensation,initialRect:G,measure:F.draggable.measure});const ee=pf(Z,F.draggable.measure,G),ge=pf(Z?Z.parentElement:null),V=u.useRef({activatorEvent:null,active:null,activeNode:Z,collisionRect:null,collisions:null,droppableRects:Y,draggableNodes:C,draggingNode:null,draggingNodeRect:null,droppableContainers:E,over:null,scrollableAncestors:[],scrollAdjustedTranslate:null}),te=E.getNodeFor((n=V.current.over)==null?void 0:n.id),be=iI({measure:F.dragOverlay.measure}),Ee=(o=be.nodeRef.current)!=null?o:Z,De=$?(r=be.rect)!=null?r:ee:null,Se=!!(be.nodeRef.current&&be.rect),Ue=R2(Se?null:ee),Me=hf(Ee?xt(Ee):null),Oe=eI($?te??Z:null),Ye=rI(Oe),ze=_f(g,{transform:{x:T.x-Ue.x,y:T.y-Ue.y,scaleX:1,scaleY:1},activatorEvent:z,active:D,activeNodeRect:ee,containerNodeRect:ge,draggingNodeRect:De,over:V.current.over,overlayNodeRect:be.rect,scrollableAncestors:Oe,scrollableAncestorRects:Ye,windowRect:Me}),Xe=se?po(se,T):null,ve=tI(Oe),qe=gf(ve),We=gf(ve,[ee]),ce=po(ze,qe),q=De?C2(De,ze):null,re=D&&q?f({active:D,collisionRect:q,droppableRects:Y,droppableContainers:L,pointerCoordinates:Xe}):null,ie=m2(re,"id"),[me,Ce]=u.useState(null),$e=Se?ze:po(ze,We),U=k2($e,(s=me?.rect)!=null?s:null,ee),ue=u.useRef(null),fe=u.useCallback((Q,ae)=>{let{sensor:Ae,options:dt}=ae;if(B.current==null)return;const pt=C.get(B.current);if(!pt)return;const ot=Q.nativeEvent,Le=new Ae({active:B.current,activeNode:pt,event:ot,options:dt,context:V,onAbort(Je){if(!C.get(Je))return;const{onDragAbort:Dt}=P.current,zt={id:Je};Dt?.(zt),_({type:"onDragAbort",event:zt})},onPending(Je,Tt,Dt,zt){if(!C.get(Je))return;const{onDragPending:wn}=P.current,Zt={id:Je,constraint:Tt,initialCoordinates:Dt,offset:zt};wn?.(Zt),_({type:"onDragPending",event:Zt})},onStart(Je){const Tt=B.current;if(Tt==null)return;const Dt=C.get(Tt);if(!Dt)return;const{onDragStart:zt}=P.current,xn={activatorEvent:ot,active:{id:Tt,data:Dt.data,rect:N}};Cn.unstable_batchedUpdates(()=>{zt?.(xn),x(hn.Initializing),y({type:it.DragStart,initialCoordinates:Je,active:Tt}),_({type:"onDragStart",event:xn}),O(ue.current),H(ot)})},onMove(Je){y({type:it.DragMove,coordinates:Je})},onEnd:je(it.DragEnd),onCancel:je(it.DragCancel)});ue.current=Le;function je(Je){return async function(){const{active:Dt,collisions:zt,over:xn,scrollAdjustedTranslate:wn}=V.current;let Zt=null;if(Dt&&wn){const{cancelDrop:Hn}=P.current;Zt={activatorEvent:ot,active:Dt,collisions:zt,delta:wn,over:xn},Je===it.DragEnd&&typeof Hn=="function"&&await Promise.resolve(Hn(Zt))&&(Je=it.DragCancel)}B.current=null,Cn.unstable_batchedUpdates(()=>{y({type:Je}),x(hn.Uninitialized),Ce(null),O(null),H(null),ue.current=null;const Hn=Je===it.DragEnd?"onDragEnd":"onDragCancel";if(Zt){const ln=P.current[Hn];ln?.(Zt),_({type:Hn,event:Zt})}})}}},[C]),xe=u.useCallback((Q,ae)=>(Ae,dt)=>{const pt=Ae.nativeEvent,ot=C.get(dt);if(B.current!==null||!ot||pt.dndKit||pt.defaultPrevented)return;const Le={active:ot};Q(Ae,ae.options,Le)===!0&&(pt.dndKit={capturedBy:ae.sensor},B.current=dt,fe(Ae,ae))},[C,fe]),he=Y2(p,xe);nI(p),sn(()=>{ee&&k===hn.Initializing&&x(hn.Initialized)},[ee,k]),u.useEffect(()=>{const{onDragMove:Q}=P.current,{active:ae,activatorEvent:Ae,collisions:dt,over:pt}=V.current;if(!ae||!Ae)return;const ot={active:ae,activatorEvent:Ae,collisions:dt,delta:{x:ce.x,y:ce.y},over:pt};Cn.unstable_batchedUpdates(()=>{Q?.(ot),_({type:"onDragMove",event:ot})})},[ce.x,ce.y]),u.useEffect(()=>{const{active:Q,activatorEvent:ae,collisions:Ae,droppableContainers:dt,scrollAdjustedTranslate:pt}=V.current;if(!Q||B.current==null||!ae||!pt)return;const{onDragOver:ot}=P.current,Le=dt.get(ie),je=Le&&Le.rect.current?{id:Le.id,rect:Le.rect.current,data:Le.data,disabled:Le.disabled}:null,Je={active:Q,activatorEvent:ae,collisions:Ae,delta:{x:pt.x,y:pt.y},over:je};Cn.unstable_batchedUpdates(()=>{Ce(je),ot?.(Je),_({type:"onDragOver",event:Je})})},[ie]),sn(()=>{V.current={activatorEvent:z,active:D,activeNode:Z,collisionRect:q,collisions:re,droppableRects:Y,draggableNodes:C,draggingNode:Ee,draggingNodeRect:De,droppableContainers:E,over:me,scrollableAncestors:Oe,scrollAdjustedTranslate:ce},N.current={initial:De,translated:q}},[D,Z,re,q,C,Ee,De,Y,E,me,Oe,ce]),V2({...oe,delta:T,draggingRect:q,pointerCoordinates:Xe,scrollableAncestors:Oe,scrollableAncestorRects:Ye});const j=u.useMemo(()=>({active:D,activeNode:Z,activeNodeRect:ee,activatorEvent:z,collisions:re,containerNodeRect:ge,dragOverlay:be,draggableNodes:C,droppableContainers:E,droppableRects:Y,over:me,measureDroppableContainers:K,scrollableAncestors:Oe,scrollableAncestorRects:Ye,measuringConfiguration:F,measuringScheduled:X,windowRect:Me}),[D,Z,ee,z,re,ge,be,C,E,Y,me,K,Oe,Ye,F,X,Me]),ne=u.useMemo(()=>({activatorEvent:z,activators:he,active:D,activeNodeRect:ee,ariaDescribedById:{draggable:I},dispatch:y,draggableNodes:C,over:me,measureDroppableContainers:K}),[z,he,D,ee,y,I,C,me,K]);return u.createElement(Yp.Provider,{value:w},u.createElement(Xo.Provider,{value:ne},u.createElement(vf.Provider,{value:j},u.createElement(ci.Provider,{value:U},c)),u.createElement(uI,{disabled:l?.restoreFocus===!1})),u.createElement(c2,{...l,hiddenTextDescribedById:I}));function pe(){const Q=M?.autoScrollEnabled===!1,ae=typeof d=="object"?d.enabled===!1:d===!1,Ae=$&&!Q&&!ae;return typeof d=="object"?{...d,enabled:Ae}:{enabled:Ae}}}),hI=u.createContext(null),xf="button",yI="Draggable";function bI(e){let{id:t,data:n,disabled:o=!1,attributes:r}=e;const s=oi(yI),{activators:a,activatorEvent:l,active:d,activeNodeRect:c,ariaDescribedById:p,draggableNodes:f,over:b}=u.useContext(Xo),{role:g=xf,roleDescription:m="draggable",tabIndex:h=0}=r??{},v=d?.id===t,y=u.useContext(v?ci:hI),[_,w]=ti(),[k,x]=ti(),$=oI(a,t),S=Ho(n);sn(()=>(f.set(t,{id:t,key:s,node:_,activatorNode:k,data:S}),()=>{const T=f.get(t);T&&T.key===s&&f.delete(t)}),[f,t]);const C=u.useMemo(()=>({role:g,tabIndex:h,"aria-disabled":o,"aria-pressed":v&&g===xf?!0:void 0,"aria-roledescription":m,"aria-describedby":p.draggable}),[o,g,h,v,m,p.draggable]);return{active:d,activatorEvent:l,activeNodeRect:c,attributes:C,isDragging:v,listeners:o?void 0:$,node:_,over:b,setNodeRef:w,setActivatorNodeRef:x,transform:y}}function mI(){return u.useContext(vf)}const vI="Droppable",_I={timeout:25};function xI(e){let{data:t,disabled:n=!1,id:o,resizeObserverConfig:r}=e;const s=oi(vI),{active:a,dispatch:l,over:d,measureDroppableContainers:c}=u.useContext(Xo),p=u.useRef({disabled:n}),f=u.useRef(!1),b=u.useRef(null),g=u.useRef(null),{disabled:m,updateMeasurementsFor:h,timeout:v}={..._I,...r},y=Ho(h??o),_=u.useCallback(()=>{if(!f.current){f.current=!0;return}g.current!=null&&clearTimeout(g.current),g.current=setTimeout(()=>{c(Array.isArray(y.current)?y.current:[y.current]),g.current=null},v)},[v]),w=li({callback:_,disabled:m||!a}),k=u.useCallback((C,T)=>{w&&(T&&(w.unobserve(T),f.current=!1),C&&w.observe(C))},[w]),[x,$]=ti(k),S=Ho(t);return u.useEffect(()=>{!w||!x.current||(w.disconnect(),f.current=!1,w.observe(x.current))},[x,w]),u.useEffect(()=>(l({type:it.RegisterDroppable,element:{id:o,key:s,disabled:n,node:x,rect:b,data:S}}),()=>l({type:it.UnregisterDroppable,key:s,id:o})),[o]),u.useEffect(()=>{n!==p.current.disabled&&(l({type:it.SetDroppableDisabled,id:o,key:s,disabled:n}),p.current.disabled=n)},[o,s,n,l]),{active:a,rect:b,isOver:d?.id===o,node:x,over:d,setNodeRef:$}}function wI(e){let{animation:t,children:n}=e;const[o,r]=u.useState(null),[s,a]=u.useState(null),l=ni(n);return!n&&!o&&l&&r(l),sn(()=>{if(!s)return;const d=o?.key,c=o?.props.id;if(d==null||c==null){r(null);return}Promise.resolve(t(c,s)).then(()=>{r(null)})},[t,o,s]),u.createElement(u.Fragment,null,n,o?u.cloneElement(o,{ref:a}):null)}const kI={x:0,y:0,scaleX:1,scaleY:1};function SI(e){let{children:t}=e;return u.createElement(Xo.Provider,{value:mf},u.createElement(ci.Provider,{value:kI},t))}const CI={position:"fixed",touchAction:"none"},$I=e=>ua(e)?"transform 250ms ease":void 0,EI=u.forwardRef((e,t)=>{let{as:n,activatorEvent:o,adjustScale:r,children:s,className:a,rect:l,style:d,transform:c,transition:p=$I}=e;if(!l)return null;const f=r?c:{...c,scaleX:1,scaleY:1},b={...CI,width:l.width,height:l.height,top:l.top,left:l.left,transform:Ko.Transform.toString(f),transformOrigin:r&&o?g2(o,l):void 0,transition:typeof p=="function"?p(o):p,...d};return u.createElement(n,{className:a,style:b,ref:t},s)}),II={duration:250,easing:"ease",keyframes:e=>{let{transform:{initial:t,final:n}}=e;return[{transform:Ko.Transform.toString(t)},{transform:Ko.Transform.toString(n)}]},sideEffects:(e=>t=>{let{active:n,dragOverlay:o}=t;const r={},{styles:s,className:a}=e;if(s!=null&&s.active)for(const[l,d]of Object.entries(s.active))d!==void 0&&(r[l]=n.node.style.getPropertyValue(l),n.node.style.setProperty(l,d));if(s!=null&&s.dragOverlay)for(const[l,d]of Object.entries(s.dragOverlay))d!==void 0&&o.node.style.setProperty(l,d);return a!=null&&a.active&&n.node.classList.add(a.active),a!=null&&a.dragOverlay&&o.node.classList.add(a.dragOverlay),function(){for(const[d,c]of Object.entries(r))n.node.style.setProperty(d,c);a!=null&&a.active&&n.node.classList.remove(a.active)}})({styles:{active:{opacity:"0"}}})};function AI(e){let{config:t,draggableNodes:n,droppableContainers:o,measuringConfiguration:r}=e;return ei((s,a)=>{if(t===null)return;const l=n.get(s);if(!l)return;const d=l.node.current;if(!d)return;const c=bf(a);if(!c)return;const{transform:p}=xt(a).getComputedStyle(a),f=Jp(p);if(!f)return;const b=typeof t=="function"?t:TI(t);return rf(d,r.draggable.measure),b({active:{id:s,data:l.data,node:d,rect:r.draggable.measure(d)},draggableNodes:n,dragOverlay:{node:a,rect:r.dragOverlay.measure(c)},droppableContainers:o,measuringConfiguration:r,transform:f})})}function TI(e){const{duration:t,easing:n,sideEffects:o,keyframes:r}={...II,...e};return s=>{let{active:a,dragOverlay:l,transform:d,...c}=s;if(!t)return;const p={x:l.rect.left-a.rect.left,y:l.rect.top-a.rect.top},f={scaleX:d.scaleX!==1?a.rect.width*d.scaleX/l.rect.width:1,scaleY:d.scaleY!==1?a.rect.height*d.scaleY/l.rect.height:1},b={x:d.x-p.x,y:d.y-p.y,...f},g=r({...c,active:a,dragOverlay:l,transform:{initial:d,final:b}}),[m]=g,h=g[g.length-1];if(JSON.stringify(m)===JSON.stringify(h))return;const v=o?.({active:a,dragOverlay:l,...c}),y=l.node.animate(g,{duration:t,easing:n,fill:"forwards"});return new Promise(_=>{y.onfinish=()=>{v?.(),_()}})}}let wf=0;function DI(e){return u.useMemo(()=>{if(e!=null)return wf++,wf},[e])}const zI=u.memo(e=>{let{adjustScale:t=!1,children:n,dropAnimation:o,style:r,transition:s,modifiers:a,wrapperElement:l="div",className:d,zIndex:c=999}=e;const{activatorEvent:p,active:f,activeNodeRect:b,containerNodeRect:g,draggableNodes:m,droppableContainers:h,dragOverlay:v,over:y,measuringConfiguration:_,scrollableAncestors:w,scrollableAncestorRects:k,windowRect:x}=mI(),$=u.useContext(ci),S=DI(f?.id),C=_f(a,{activatorEvent:p,active:f,activeNodeRect:b,containerNodeRect:g,draggingNodeRect:v.rect,over:y,overlayNodeRect:v.rect,scrollableAncestors:w,scrollableAncestorRects:k,transform:$,windowRect:x}),T=wa(b),E=AI({config:o,draggableNodes:m,droppableContainers:h,measuringConfiguration:_}),A=T?v.setRef:void 0;return u.createElement(SI,null,u.createElement(wI,{animation:E},f&&S?u.createElement(EI,{key:S,id:f.id,ref:A,as:l,activatorEvent:p,adjustScale:t,className:d,transition:s,rect:T,style:{zIndex:c,...r},transform:C},n):null))});function Jo(e,t){return!t||!Array.isArray(t.draggable)?!1:t.draggable.includes(e)}function NI({draggedComponentName:e,targetContainerName:t,nesting:n}){if(!n||!Jo(e,n))return!1;const o=n.containers;if(!o||typeof o!="object")return!1;const r=o[t];return!r||!Array.isArray(r.accepts)?!1:r.accepts.includes(e)}function kf(e,t){if(!t?.containers)return!1;const n=t.containers[e];return!!(n&&Array.isArray(n.accepts)&&n.accepts.length>0)}function MI(e,t,n,o,r){if(!n||n.length===0)return{parentPath:[],index:r};if(e&&kf(e,o))return{parentPath:n,index:t};const s=n.slice(0,-1),a=n[n.length-1]??0;return{parentPath:s,index:a+1}}function Sf(e,t){return e===t||e.startsWith(`${t}.`)}function Cf(e,t){if(e==="")return/^\d+$/.test(t)?Number(t):null;const n=`${e}.children.`;if(!t.startsWith(n))return null;const o=t.slice(n.length);return/^\d+$/.test(o)?Number(o):null}function LI(e,t){const n=[];return e.querySelectorAll("[data-editor-path]").forEach(o=>{if(o.hasAttribute("data-dnd-placeholder")||o.closest("[data-dnd-placeholder]"))return;const r=o.dataset.editorPath;typeof r=="string"&&n.push({path:t(r),domPath:r,rect:o.getBoundingClientRect()})}),n}const PI=16;function OI(e){const{frameEl:t,draggedPath:n,acceptsContainer:o,allowsNestSlot:r,includeContainer:s}=e,a=e.normalizeContainerPath??(g=>g),l=t.getBoundingClientRect(),d=LI(t,a),c=new Map;for(const g of d)c.has(g.path)||c.set(g.path,g.domPath);const p=a(n),f=new Set([""]);for(const g of d){const m=WI(g.path);m!==null&&f.add(m)}for(const g of d)f.add(g.path);const b=[];for(const g of f){const m=g==="",h=m?null:c.get(g)??g,v=m?t:t.querySelector(`[data-editor-path="${If(h)}"]`);if(!v&&!m||!m&&Sf(g,p))continue;const y=g;if(!m&&s&&!s(y))continue;const _=$=>{const S=c.get($);if(!S||typeof window>"u")return!1;const C=t.querySelector(`[data-editor-path="${If(S)}"]`);return C?(window.getComputedStyle(C).display??"").toLowerCase()==="contents":!1},w=d.filter($=>Cf(g,$.path)!==null).filter($=>!Sf($.path,p)).filter($=>!_($.path)).map($=>({origIndex:Cf(g,$.path),rect:BI($.rect,$.path,d)})).filter($=>$.rect.width>0||$.rect.height>0).sort(($,S)=>$.origIndex-S.origIndex),k=Op(m?t:v).kind,x=k==="flex_row_single"||k==="flex_row_wrap"||k==="grid_single_row"||k==="grid_2d";if(w.length>0){if(o(y)){w.forEach(S=>{b.push($f(y,S.origIndex,S.rect,l,x,"before"))});const $=w[w.length-1];b.push($f(y,$.origIndex+1,$.rect,l,x,"after"))}}else if(!m&&r(y)&&v){const $=v.getBoundingClientRect();$.width>0&&$.height>0&&b.push({id:`slot:${y}:0`,kind:"nest",containerPath:y,index:0,box:Ef($,l),orientation:"area"})}else m&&b.push({id:"slot::0",kind:"gap",containerPath:"",index:0,box:Ef(l,l),orientation:"area"})}return b.filter(g=>lo(g.box,t))}function $f(e,t,n,o,r,s){const a=PI;let l;if(r)l={left:(s==="before"?n.left:n.right)-a/2,top:n.top,width:a,height:n.height};else{const d=s==="before"?n.top:n.bottom;l={left:n.left,top:d-a/2,width:n.width,height:a}}return{id:`slot:${e}:${t}`,kind:"gap",containerPath:e,index:t,box:{left:l.left-o.left,top:l.top-o.top,width:l.width,height:l.height,scale:1},orientation:r?"vertical":"horizontal"}}function BI(e,t,n){if(e.width>0||e.height>0)return e;const o=`${t}.children.`;let r=1/0,s=1/0,a=-1/0,l=-1/0;for(const d of n)d.path.startsWith(o)&&(d.rect.width===0&&d.rect.height===0||(r=Math.min(r,d.rect.left),s=Math.min(s,d.rect.top),a=Math.max(a,d.rect.right),l=Math.max(l,d.rect.bottom)));return r===1/0?e:{left:r,top:s,width:a-r,height:l-s,right:a,bottom:l,x:r,y:s,toJSON:()=>({})}}function Ef(e,t){return{left:e.left-t.left,top:e.top-t.top,width:e.width,height:e.height,scale:1}}function WI(e){if(e==="")return null;const t=e.lastIndexOf(".children.");return t<0?"":e.slice(0,t)}function jI(e){if(!e.startsWith("slot:"))return null;const t=e.slice(5),n=t.lastIndexOf(":");if(n<0)return null;const o=t.slice(0,n),r=Number(t.slice(n+1));return Number.isFinite(r)?{containerPath:o,index:r}:null}function If(e){return typeof CSS<"u"&&typeof CSS.escape=="function"?CSS.escape(e):e.replace(/[^a-zA-Z0-9_\-.]/g,t=>`\\${t}`)}function ka(e){return typeof e=="string"&&e.length>0?e:null}function Sa(e,t){return t&&e.startsWith(`${t}.`)?t:e}function Af(e){if(typeof e!="string")return null;const t=jI(e);return t?{containerPath:t.containerPath,index:t.index}:null}function Ca(e,t){return t?e.startsWith(`${t}.iteration.`):!1}function $a(e,t,n){return t?n==="iteration_item"?Ca(e,t):e===t||e.startsWith(`${t}.`):!1}function HI(e){const{frameEl:t,nesting:n,editMode:o,currentExtensionId:r,editableRootPath:s,components:a,patchLayout:l,pushHistory:d,onMovePath:c,selectedPath:p,showBranchBoundaryHint:f}=e,b=(o==="iteration_item"||o==="modal")&&s&&s.length>0?at(s):null,[g,m]=u.useState(null),[h,v]=u.useState(null),[y,_]=u.useState(null),w=u.useRef({components:a,nesting:n,editMode:o,currentExtensionId:r,selectedPath:p,editableRootSourcePath:b});w.current={components:a,nesting:n,editMode:o,currentExtensionId:r,selectedPath:p,editableRootSourcePath:b};const k=u.useRef(null),x=u.useCallback(M=>({children:M}),[]),$=u.useCallback((M,O)=>{const z=Be(x(M),O);return z?typeof z.name=="string"?z.name:typeof z.type=="string"?z.type:null:null},[x]),S=u.useCallback(M=>O=>{const z=w.current;if(O==="")return Jo(M,z.nesting);const H=Re(O),P=Be(x(z.components),H);if(!P)return!1;const I=typeof P.name=="string"?P.name:typeof P.type=="string"?P.type:"";if(!I)return!1;const L=!!z.editableRootSourcePath&&(O===z.editableRootSourcePath||O.startsWith(`${z.editableRootSourcePath}.`));if(z.editableRootSourcePath&&!L)return!1;if(!L){const F=Ea(x(z.components),H);if(gn(P,z.editMode,z.currentExtensionId,F)!=="none")return!1}return kf(I,z.nesting)?NI({draggedComponentName:M,targetContainerName:I,nesting:z.nesting}):!1},[x]),C=u.useCallback(M=>{if(M==="")return!0;const O=w.current,z=Re(M),H=k.current;if(H&&H.length>0){const L=H.slice(0,-1);if(ui(z,L))return!0}const P=Be(x(O.components),z);return P?(typeof P.name=="string"?P.name:typeof P.type=="string"?P.type:"").length>0:!1},[x]),T=u.useCallback(M=>{const O=S(M),z=w.current,H=k.current,P=H?H.slice(0,-1):null,I=new Set;if(P)for(let K=P.length;K>=0;K--)I.add(pi(P.slice(0,K)));const L=pi(H??[]),F=K=>!!H&&(K===L||K.startsWith(`${L}.`)),Y=(K,X)=>{for(let Z=0;ZK===""?!0:F(K)?!1:I.has(K),allowsNestSlot:K=>{if(!O(K))return!1;if(P){const X=Re(K);if(ui(X,P))return!1}return C(K)}}},[S,C,x]),E=u.useCallback(M=>{const O=ka(M.active.id),z=w.current;if(!O)return;const H=Sa(O,z.selectedPath),P=Re(H),I=Be(x(z.components),P),L=I&&typeof I.name=="string"?I.name:I&&typeof I.type=="string"?I.type:null;m(H),v(L),_(null),k.current=I?P:null;let F="allowed";if(!L||!Jo(L,z.nesting))F="denied_no_draggable";else if(I){const Y=Ea(x(z.components),P),K=gn(I,z.editMode,z.currentExtensionId,Y);z.editableRootSourcePath&&!$a(H,z.editableRootSourcePath,z.editMode)||Qi(Y)&&!Ca(H,z.editableRootSourcePath)?F="denied_data_bound":K==="base"||K==="partial"?F="denied_base_locked":(K==="extension"||K==="extension_point")&&(F="denied_extension_locked")}$n({source:"drag",draggedComponentName:L,targetContainerName:null,targetContainerPath:null,decision:F,result:F==="allowed"?"completed":"denied",timestamp:Date.now()})},[x]),A=u.useCallback(M=>{if(!t)return;const O=ka(M.active.id);if(!O)return;const z=h;if(!z){_(null);return}const H=w.current,P=Sa(O,H.selectedPath),I=Re(P),L=Be(x(H.components),I);if(!L||!Jo(z,H.nesting)){_(null);return}const F=Ea(x(H.components),I);if(H.editableRootSourcePath&&!$a(P,H.editableRootSourcePath,H.editMode)){_(null);return}const Y=gn(L,H.editMode,H.currentExtensionId,F),K=Ca(P,H.editableRootSourcePath);if(Qi(F)&&!K||Y!=="none"&&Y!=="data_bound"&&!K){_(null);return}_(Af(M.over?.id))},[t,h,x]),N=u.useCallback(()=>{m(null),v(null),_(null),k.current=null},[]),D=u.useCallback(M=>{const O=ka(M.active.id),z=h,H=w.current,P=O?Sa(O,H.selectedPath):null;if(!t||!P||!z){N();return}const I=Af(M.over?.id),L=Re(P);if(!I){$n({source:"drag",draggedComponentName:z,targetContainerName:null,targetContainerPath:null,decision:"denied_no_accepts",result:"cancelled",timestamp:Date.now()}),N();return}const F=I.containerPath===""?[]:Re(I.containerPath),Y=I.containerPath===""?null:$(H.components,F);if(!FI(L.slice(0,-1),F)){$n({source:"drag",draggedComponentName:z,targetContainerName:Y,targetContainerPath:I.containerPath||null,decision:"denied_responsive_branch_boundary",result:"denied",timestamp:Date.now()}),f?.(),N();return}if(H.editableRootSourcePath&&!$a(I.containerPath,H.editableRootSourcePath,H.editMode)){$n({source:"drag",draggedComponentName:z,targetContainerName:Y,targetContainerPath:I.containerPath||null,decision:"denied_data_bound",result:"denied",timestamp:Date.now()}),N();return}const K=L.slice(0,-1),X=L[L.length-1]??-1;if(ui(K,F)&&(I.index===X||I.index===X+1)){$n({source:"drag",draggedComponentName:z,targetContainerName:Y,targetContainerPath:I.containerPath||null,decision:"allowed",insertionIndex:I.index,result:"cancelled",timestamp:Date.now()}),N();return}const oe=ui(K,F),G=I.index;let ee=[];l(be=>{const Se=Bb({children:be},L,F,G).children??[];return ee=Se,Se}),d(ee,`move ${z}`);const ge=dd(F,L),V=oe&&X{N()},[N]);return{activeDragPath:g,activeDragName:h,activeDropZone:y,onDragStart:E,onDragMove:A,onDragEnd:D,onDragCancel:B,buildSlotPredicates:T}}function Ea(e,t){const n=[];let o=e,r=Array.isArray(e.children)?e.children:[];for(let s=0;s0?at(s):null,{t:m}=tt(),[h,v]=u.useState(!1),y=u.useRef(null),_=u.useCallback(()=>{v(!0),y.current&&clearTimeout(y.current),y.current=setTimeout(()=>v(!1),2600)},[]);u.useEffect(()=>()=>{y.current&&clearTimeout(y.current)},[]);const w=HI({frameEl:t,nesting:n,editMode:o,currentExtensionId:r,editableRootPath:s,components:a,patchLayout:l,pushHistory:d,onMovePath:p,selectedPath:b,showBranchBoundaryHint:_}),k=p2(u2(ma,{activationConstraint:{distance:8}})),[x,$]=u.useState([]),S=u.useCallback(()=>{if(!t){$([]);return}if(w.activeDragPath!==null)return;const P={children:a},I=t.querySelectorAll("[data-editor-path]"),L=[],F=new Map;I.forEach(Y=>{const K=Y.dataset.editorPath;if(typeof K!="string"||o!=="iteration_item"&&g&&!UI(K,g,o))return;const X=KI(K);if(X!==null&&!Zo(K,g)){if(/\.iteration\.\d+$/.test(K)){const V=ao(Y,t);if(V&&lo(V,t)){const te=F.get(X)??[];te.push(V),F.set(X,te)}}return}if(Zo(K,g)&&/\.iteration\.\d+$/.test(K))return;const Z=Re(K),se=Be(P,Z);if(!se||se.iteration&&se.iteration.source!==void 0)return;const oe=typeof se.name=="string"?se.name:typeof se.type=="string"?se.type:null;if(!oe||!Jo(oe,n))return;const G=Df(P,Z);if(Qi(G)&&!Zo(K,g))return;const ee=gn(se,o,r,G);if(ee!=="none"&&ee!=="data_bound"&&!Zo(K,g))return;const ge=ao(Y,t);ge&&lo(ge,t)&&L.push({path:K,name:oe,box:ge})});for(const[Y,K]of F){const X=Re(Y),Z=Be(P,X);if(!Z)continue;const se=typeof Z.name=="string"?Z.name:typeof Z.type=="string"?Z.type:null;if(!se)continue;const oe=Df(P,X),G=gn(Z,o,r,oe);if(G!=="none"&&G!=="data_bound")continue;const ee=qI(K);ee&&lo(ee,t)&&L.push({path:Y,name:se,box:ee,isIterationGroup:!0})}$(L)},[t,a,n,o,r,g,w.activeDragPath]);u.useEffect(()=>{S()},[S]),u.useEffect(()=>{if(t)return zp(S,t)},[t,S]);const C=w.activeDragPath!==null,T=w.activeDragName,E=w.activeDragPath,[A,N]=u.useState(null),D=u.useRef(null);u.useEffect(()=>{if(!E||!t){E||(D.current=null,N(null));return}if(D.current===E)return;D.current=E;const P=t.querySelector(`[data-editor-path="${Tf(E)}"]`);if(P){const oe=P.getBoundingClientRect();N({node:P.cloneNode(!0),width:oe.width,height:oe.height});return}const I=Array.from(t.querySelectorAll(`[data-editor-path^="${Tf(E)}.iteration."]`)).filter(oe=>/\.iteration\.\d+$/.test(oe.dataset.editorPath??""));if(I.length===0){N(null);return}let L=1/0,F=1/0,Y=-1/0,K=-1/0;for(const oe of I){const G=oe.getBoundingClientRect();L=Math.min(L,G.left),F=Math.min(F,G.top),Y=Math.max(Y,G.right),K=Math.max(K,G.bottom)}const X=Y-L,Z=K-F,se=document.createElement("div");se.style.position="relative",se.style.width=`${X}px`,se.style.height=`${Z}px`;for(const oe of I){const G=oe.getBoundingClientRect(),ee=oe.cloneNode(!0);ee.style.position="absolute",ee.style.left=`${G.left-L}px`,ee.style.top=`${G.top-F}px`,ee.style.width=`${G.width}px`,ee.style.height=`${G.height}px`,ee.style.margin="0",se.appendChild(ee)}N({node:se,width:X,height:Z})},[E,t]);const[B,M]=u.useState([]),O=u.useRef(null),z=u.useRef(w.buildSlotPredicates);if(z.current=w.buildSlotPredicates,u.useEffect(()=>{if(!E||!T||!t){O.current=null,M(F=>F.length===0?F:[]);return}if(O.current===E)return;O.current=E;const{acceptsContainer:P,allowsNestSlot:I,includeContainer:L}=z.current(T);M(OI({frameEl:t,draggedPath:E,acceptsContainer:P,allowsNestSlot:I,includeContainer:L,normalizeContainerPath:F=>VI(F)}))},[E,T,t]),!t)return null;const H=w.activeDropZone?`slot:${w.activeDropZone.containerPath}:${w.activeDropZone.index}`:null;return i.jsxs(gI,{sensors:k,collisionDetection:w2,onDragStart:w.onDragStart,onDragMove:w.onDragMove,onDragEnd:w.onDragEnd,onDragCancel:w.onDragCancel,children:[x.map(P=>i.jsx(JI,{path:P.path,name:P.name,box:P.box,dimmed:C&&w.activeDragPath!==P.path,isIterationGroup:P.isIterationGroup,onSelect:c,onRequestInlineEdit:f},P.path)),C&&B.map(P=>i.jsx(XI,{slot:P,active:P.id===H},P.id)),Cn.createPortal(i.jsx(zI,{dropAnimation:null,zIndex:Fp,children:T?A?i.jsx(YI,{name:T,node:A.node,width:A.width,height:A.height}):i.jsx("div",{className:"g7le-dnd-drag-ghost","data-testid":"g7le-dnd-drag-ghost",style:{padding:"4px 10px",background:"rgba(79,70,229,0.95)",color:"#fff",fontSize:12,fontWeight:600,borderRadius:4,pointerEvents:"none",whiteSpace:"nowrap"},children:`<${T}>`}):null}),document.body),h&&Cn.createPortal(i.jsxs("div",{"data-testid":"g7le-branch-boundary-hint",role:"status",style:{position:"absolute",left:"50%",top:12,transform:"translateX(-50%)",background:"rgba(245,158,11,0.97)",color:"#fff",fontSize:13,fontWeight:600,padding:"8px 16px",borderRadius:8,boxShadow:"0 4px 14px rgba(0,0,0,0.2)",pointerEvents:"none",whiteSpace:"nowrap",zIndex:Fp},children:["⚠️ ",m("layout_editor.overlay.branch_boundary_denied")]}),t)]})}function YI(e){const{node:t,width:n,height:o}=e,r=u.useRef(null);return u.useEffect(()=>{const s=r.current;if(s)return s.replaceChildren(),s.appendChild(t.cloneNode(!0)),()=>{s.replaceChildren()}},[t]),i.jsx("div",{ref:r,className:"g7le-dnd-drag-ghost","data-testid":"g7le-dnd-drag-ghost",style:{width:n,height:o,opacity:.7,pointerEvents:"none",boxShadow:"0 6px 18px rgba(0,0,0,0.25)",borderRadius:4,overflow:"hidden",cursor:"grabbing"}})}function XI(e){const{slot:t,active:n}=e,{setNodeRef:o}=xI({id:t.id}),r=t.orientation!=="area";return i.jsx("div",{ref:o,"data-testid":`g7le-dnd-slot-${t.id}`,"data-dnd-slot-id":t.id,style:{position:"absolute",left:t.box.left,top:t.box.top,width:t.box.width,height:t.box.height,pointerEvents:"auto",zIndex:H7,boxSizing:"border-box",...r?{background:n?"rgba(79,70,229,0.15)":"transparent",...n?t.orientation==="vertical"?{borderLeft:"2px solid #4f46e5"}:{borderTop:"2px solid #4f46e5"}:{}}:{border:n?"2px dashed #4f46e5":"1px dashed transparent",background:n?"rgba(79,70,229,0.08)":"transparent",borderRadius:4}}})}function Tf(e){return typeof CSS<"u"&&typeof CSS.escape=="function"?CSS.escape(e):e.replace(/[^a-zA-Z0-9_\-.]/g,t=>`\\${t}`)}function JI(e){const{path:t,box:n,dimmed:o,isIterationGroup:r,onSelect:s,onRequestInlineEdit:a}=e,{attributes:l,listeners:d,setNodeRef:c,isDragging:p}=bI({id:t}),f=(t.match(/\.children\./g)||[]).length,b=F7(f);return i.jsx("div",{ref:c,"data-testid":`g7le-dnd-handle-${t}`,"data-dnd-handle-path":t,"data-dnd-iteration-group":r?"true":void 0,...d,...l,onClick:g=>{if(g.stopPropagation(),!r&&typeof document<"u"&&typeof document.elementsFromPoint=="function"){const m=document.elementsFromPoint(g.clientX,g.clientY);for(const h of m){const y=h.closest?.("[data-editor-path]")?.dataset?.editorPath;if(typeof y=="string"&&(y===t||y.startsWith(`${t}.`))){s(y);return}}}s(t)},onDoubleClick:g=>{g.stopPropagation(),g.preventDefault(),a?.(t)},style:{position:"absolute",left:n.left,top:n.top,width:n.width,height:n.height,cursor:"grab",pointerEvents:"auto",background:p?"rgba(79,70,229,0.06)":"transparent",...r?{border:"1px dashed rgba(79,70,229,0.5)",borderRadius:4}:{},opacity:o?.6:1,zIndex:b,touchAction:"none"}})}function Df(e,t){const n=[];let o=e,r=Array.isArray(e.children)?e.children:[];for(let s=0;s0?[o]:[];const r=[],s=(a,l)=>{a.forEach((d,c)=>{const p=[...l,c];if(!Ri(d,t,n)){r.push(p);return}const b=Array.isArray(d.children)?d.children:[];b.length>0&&s(b,p)})};return s(e,[]),r}function QI(e){return at(e)}function fi(e){return typeof CSS<"u"&&typeof CSS.escape=="function"?CSS.escape(e):e.replace(/["\\]/g,"\\$&")}function RI({frameEl:e,components:t,editMode:n,currentExtensionId:o,editableRootPath:r}){const[s,a]=u.useState(null),[l]=u.useState(()=>`g7le-dim-mask-${Math.floor(Math.random()*1e9)}`);if(u.useEffect(()=>{if(!e){a(null);return}let g=0;const m=()=>{const _=ZI(t,n,o,r);if(_.length===0){a(null);return}const w=e.getBoundingClientRect(),k={left:0,top:0,width:w.width,height:w.height},x=[];for(const $ of _){const S=QI($),C=e.querySelector(`[data-editor-path="${fi(S)}"]`);let T=null;if(C){const E=C.getBoundingClientRect();E.width>0&&E.height>0&&(T={left:E.left,top:E.top,width:E.width,height:E.height})}if(!T){const E=Array.from(e.querySelectorAll(`[data-editor-path^="${fi(S)}.iteration."], [data-editor-path^="${fi(S)}.sortable."], [data-editor-path^="${fi(S)}.children."]`));let A=1/0,N=1/0,D=-1/0,B=-1/0;for(const M of E){const O=M.getBoundingClientRect();O.width<=0||O.height<=0||(A=Math.min(A,O.left),N=Math.min(N,O.top),D=Math.max(D,O.right),B=Math.max(B,O.bottom))}if(D>A&&B>N&&(T={left:A,top:N,width:D-A,height:B-N}),T&&E.length>0){const M=E[0].closest('[role="dialog"]');if(M&&e.contains(M)){const O=M.getBoundingClientRect();O.width>0&&O.height>0&&(T={left:O.left,top:O.top,width:O.width,height:O.height})}}}T&&x.push({left:T.left-w.left,top:T.top-w.top,width:T.width,height:T.height})}if(x.length===0){a(null);return}a({frame:k,holes:x})},h=()=>{g&&cancelAnimationFrame(g),g=requestAnimationFrame(m)};h();const v=typeof ResizeObserver<"u"?new ResizeObserver(h):null;v?.observe(e);const y=typeof MutationObserver<"u"?new MutationObserver(h):null;return y?.observe(e,{childList:!0,subtree:!0,attributes:!0}),window.addEventListener("scroll",h,!0),window.addEventListener("resize",h),()=>{g&&cancelAnimationFrame(g),v?.disconnect(),y?.disconnect(),window.removeEventListener("scroll",h,!0),window.removeEventListener("resize",h)}},[e,t,n,o,r?.join(".")??""]),!s||s.holes.length===0)return null;const{frame:d,holes:c}=s,p=n==="modal"||n==="iteration_item";let f=[];if(p){let g=1/0,m=1/0,h=-1/0,v=-1/0;for(const $ of c)g=Math.min(g,$.left),m=Math.min(m,$.top),h=Math.max(h,$.left+$.width),v=Math.max(v,$.top+$.height);const y=($,S,C)=>Math.max(S,Math.min(C,$)),_=y(g,0,d.width),w=y(m,0,d.height),k=y(h,0,d.width),x=y(v,0,d.height);f=[{left:0,top:0,width:d.width,height:w},{left:0,top:x,width:d.width,height:d.height-x},{left:0,top:w,width:_,height:x-w},{left:k,top:w,width:d.width-k,height:x-w}].filter($=>$.width>0&&$.height>0)}const b=g=>{g.preventDefault(),g.stopPropagation()};return i.jsxs(i.Fragment,{children:[f.map((g,m)=>i.jsx("div",{className:"g7le-source-lock-block","data-testid":"g7le-source-lock-block",onClickCapture:b,onPointerDownCapture:b,onMouseDownCapture:b,onDragStartCapture:b,style:{position:"absolute",left:g.left,top:g.top,width:g.width,height:g.height,zIndex:Hp,pointerEvents:"auto",cursor:"not-allowed",background:"transparent"}},m)),i.jsxs("svg",{className:"g7le-source-lock-dim","data-testid":"g7le-source-lock-dim",width:d.width,height:d.height,style:{position:"absolute",left:0,top:0,width:d.width,height:d.height,zIndex:Hp,pointerEvents:"none"},children:[i.jsx("defs",{children:i.jsxs("mask",{id:l,children:[i.jsx("rect",{x:0,y:0,width:d.width,height:d.height,fill:"white"}),c.map((g,m)=>i.jsx("rect",{"data-testid":"g7le-source-lock-dim-hole",x:g.left,y:g.top,width:g.width,height:g.height,rx:4,fill:"black"},m))]})}),i.jsx("rect",{"data-testid":"g7le-source-lock-dim-box",x:0,y:0,width:d.width,height:d.height,fill:"rgba(15, 23, 42, 0.45)",mask:`url(#${l})`})]})]})}function zf(e){const t={type:e.type??"basic",name:e.name},n={};if(e.props)for(const[o,r]of Object.entries(e.props))o!=="children"&&r?.default!==void 0&&(n[o]=r.default);return Object.keys(n).length>0&&(t.props=n),t}function eA(e){if(!e?.components)return[];const t=[];for(const[n,o]of Object.entries(e.components))if(Array.isArray(o))for(const r of o)t.push({...r,type:r.type??n});return t}function tA(e,t,n,o,r){const s=eA(e);let a=null;if(n&&t?.containers){const l=t.containers[n];l&&Array.isArray(l.accepts)?a=new Set(l.accepts):a=new Set}else!n&&t?.draggable&&(a=new Set(t.draggable));return s.filter(l=>{if(a&&!a.has(l.name))return!1;if(o){const d=o.toLowerCase(),c=r(l).toLowerCase(),p=l.name.toLowerCase();if(!c.includes(d)&&!p.includes(d)&&!`<${p}>`.includes(d)&&!(l.description??"").toLowerCase().includes(d))return!1}return!0})}function nA(e,t){const n={key:"all",label:"layout_editor.palette.category.all",entries:e,isI18nKey:!0};if(t?.groups&&t.groups.length>0){const a=new Map(e.map(p=>[p.name,p])),l=new Set,d=[];for(const p of t.groups){const f=[];for(const b of p.components??[]){const g=a.get(b);g&&(f.push(g),l.add(b))}f.length!==0&&d.push({key:p.kind??p.label,label:p.label,entries:f,isI18nKey:p.label.startsWith("$t:")||/^[a-z_.]+$/i.test(p.label)})}const c=e.filter(p=>!l.has(p.name));return c.length>0&&d.push({key:"other",label:"layout_editor.palette.category.other",entries:c,isI18nKey:!0}),[n,...d]}const o=new Map;for(const a of e){const l=(a.type??"basic").toLowerCase(),d=o.get(l)??[];d.push(a),o.set(l,d)}const r=["basic","composite","layout"],s=[];for(const a of r){const l=o.get(a);l&&l.length>0&&(s.push({key:a,label:`layout_editor.palette.category.fallback.${a}`,entries:l,isI18nKey:!0}),o.delete(a))}for(const[a,l]of o.entries())s.push({key:a,label:a,entries:l,isI18nKey:!1});return[n,...s]}function Nf(e){const{manifest:t,nesting:n,componentPalette:o,targetParentPath:r,targetContainerName:s,onInsert:a,insertionIndex:l,onClose:d,editorTemplateId:c,editorLocale:p}=e,{t:f,translationEngine:b}=tt(),g=u.useMemo(()=>{if(!c||!b)return f;const A=p??null;return N=>{const D={templateId:c,locale:A??"ko"},B=b.translate(N,D);return B!==N?B:f(N)}},[c,p,b,f]),[m,h]=u.useState(""),[v,y]=u.useState("all"),_=o?.entries??null,w=u.useMemo(()=>A=>{const N=_?.[A.name];return N?.label?gi(N.label,g):A.description??A.name},[_,g]),k=u.useMemo(()=>tA(t,n,s,m,w),[t,n,s,m,w]),x=u.useMemo(()=>nA(k,o??null),[k,o]),$=x.find(A=>A.key===v)??x[0]??null,S=!n||!n.draggable&&!n.containers,C=k.length===0,T=$?.entries??[],E=A=>{const N=_?.[A.name],D=!!N?.defaultNode&&typeof N.defaultNode=="object";if(N?.requiresDefaultNode&&!D)return;const B=D?JSON.parse(JSON.stringify(N.defaultNode)):zf(A),M=r??[];a(B,M,l),$n({source:"palette",draggedComponentName:A.name,targetContainerName:s,targetContainerPath:M.length===0?null:M.join("/"),decision:"allowed",insertionIndex:l,result:"completed",timestamp:Date.now()}),d()};return i.jsxs("div",{className:"g7le-palette","data-testid":"g7le-palette",style:sA,children:[i.jsxs("div",{style:aA,children:[i.jsx("strong",{style:{fontSize:16},children:f("layout_editor.palette.title")}),i.jsx("button",{type:"button",onClick:d,"aria-label":"close","data-testid":"g7le-palette-close",style:lA,children:"×"})]}),i.jsxs("div",{style:dA,children:[i.jsx("aside",{style:cA,"data-testid":"g7le-palette-sidebar",children:i.jsx("nav",{children:i.jsx("ul",{style:uA,children:x.map(A=>{const N=$?.key===A.key,D=A.isI18nKey?gi(A.label,g):A.label;return i.jsx("li",{children:i.jsxs("button",{type:"button","data-testid":`g7le-palette-category-${A.key}`,"data-active":N?"true":"false",onClick:()=>y(A.key),style:{...pA,background:N?"#eff6ff":"transparent",color:N?"#1d4ed8":"#334155",fontWeight:N?600:500},children:[i.jsx("span",{children:D}),i.jsx("span",{style:yA,children:A.entries.length})]})},A.key)})})})}),i.jsxs("main",{style:fA,children:[i.jsx("input",{type:"text",placeholder:f("layout_editor.palette.search_placeholder"),value:m,onChange:A=>h(A.target.value),"data-testid":"g7le-palette-search",style:gA}),i.jsx("div",{style:hA,children:S?i.jsx("div",{style:Mf,"data-testid":"g7le-palette-spec-missing",children:f("layout_editor.palette.spec_missing")}):C||T.length===0?i.jsx("div",{style:Mf,"data-testid":"g7le-palette-empty",children:f("layout_editor.palette.empty")}):i.jsx("div",{"data-testid":`g7le-palette-group-${$?.key??"all"}`,style:{marginBottom:16},children:i.jsx("div",{style:bA,children:T.map(A=>i.jsx(oA,{entry:A,entrySpec:_?.[A.name]??null,onClick:E,t:f,entryT:g},A.name))})})})]})]})]})}function oA(e){const{entry:t,entrySpec:n,onClick:o,t:r,entryT:s}=e,a=n?.label?gi(n.label,s):t.description??t.name,l=(t.type??"").toLowerCase(),d=l==="basic"||l==="composite"||l==="layout",c=!!n?.defaultNode&&typeof n.defaultNode=="object",p=!!n?.requiresDefaultNode&&!c,f=!c,b=p?r("layout_editor.palette.requires_default_node_missing"):n?.label?gi(n.label,s):t.name;return i.jsxs("button",{type:"button",onClick:()=>o(t),disabled:p,"aria-disabled":p||void 0,"data-testid":`g7le-palette-item-${t.name}`,"data-component-type":l||void 0,"data-blocked":p?"true":void 0,"data-default-node-missing":f?"true":void 0,title:b,style:p?vA:mA,children:[i.jsx("div",{style:xA,"aria-hidden":"true",children:iA(t)}),i.jsx("div",{style:wA,children:a}),i.jsx("div",{"data-testid":`g7le-palette-item-${t.name}-tag`,style:kA,children:`<${t.name}>`}),d&&i.jsx("div",{"data-testid":`g7le-palette-item-${t.name}-badge`,style:rA(l),children:l}),f&&i.jsx("div",{"data-testid":`g7le-palette-item-${t.name}-default-missing-badge`,style:_A,children:r("layout_editor.palette.default_node_missing_badge")})]})}function gi(e,t){return e.startsWith("$t:")?t(e.slice(3)):t(e)}function rA(e){const n={basic:{bg:"#dbeafe",fg:"#1e40af"},composite:{bg:"#dcfce7",fg:"#14532d"},layout:{bg:"#fef3c7",fg:"#854d0e"}}[e]??{bg:"#e2e8f0",fg:"#334155"};return{fontSize:9,fontWeight:600,color:n.fg,background:n.bg,padding:"1px 6px",borderRadius:999,textTransform:"uppercase",letterSpacing:.4,lineHeight:1.6}}function iA(e){const t=e.name;return{Button:"🔘",A:"🔗",Img:"🖼",Icon:"✨",Hr:"➖",P:"📄",Span:"T",H1:"H₁",H2:"H₂",H3:"H₃",H4:"H₄",Ul:"☰",Li:"•",Nav:"🧭",Div:"▢",Container:"▢",Flex:"⇆",Grid:"▦",SectionLayout:"▤",ThreeColumnLayout:"⫼",Form:"📝",Input:"⎕",Textarea:"▭",Select:"⏷",Checkbox:"☑",PasswordInput:"🔒",Label:"🏷",DataGrid:"▦",Pagination:"↔",SearchBar:"🔍",ProductCard:"🛍",ImageGallery:"🖼",Avatar:"👤",UserInfo:"👥",HtmlContent:"⟨/⟩",HtmlEditor:"✏",RichTextEditor:"✏",TabNavigation:"◰",QuantitySelector:"#",ProductImageViewer:"🖼",FileUploader:"📎",AvatarUploader:"📷",SocialLoginButtons:"🌐",PostReactions:"❤"}[t]??"◻"}const sA={display:"flex",flexDirection:"column",width:"100%",height:"100%",minHeight:0,fontFamily:'-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Apple SD Gothic Neo", "Noto Sans KR", sans-serif'},aA={display:"flex",justifyContent:"space-between",alignItems:"center",padding:"14px 20px",borderBottom:"1px solid #e2e8f0",flexShrink:0},lA={background:"transparent",border:"none",fontSize:24,cursor:"pointer",lineHeight:1,color:"#64748b",padding:"0 4px"},dA={display:"flex",flex:1,minHeight:0},cA={width:180,flexShrink:0,borderRight:"1px solid #e2e8f0",padding:"14px 8px",background:"#f8fafc",overflowY:"auto"},uA={listStyle:"none",margin:0,padding:0,display:"flex",flexDirection:"column",gap:4},pA={display:"flex",width:"100%",alignItems:"center",justifyContent:"space-between",textAlign:"left",padding:"8px 12px",border:"none",borderRadius:6,fontSize:13,cursor:"pointer",transition:"background-color 120ms"},fA={flex:1,display:"flex",flexDirection:"column",minHeight:0,padding:"14px 20px"},gA={width:"100%",padding:"8px 10px",border:"1px solid #cbd5e1",borderRadius:6,marginBottom:12,fontSize:13,flexShrink:0,boxSizing:"border-box"},hA={flex:1,overflowY:"auto",minHeight:0},yA={fontSize:11,color:"#94a3b8",marginLeft:8,fontWeight:500},bA={display:"grid",gridTemplateColumns:"repeat(auto-fill, minmax(96px, 1fr))",gap:10},mA={display:"flex",flexDirection:"column",alignItems:"center",justifyContent:"center",gap:6,padding:"14px 6px",background:"#fff",border:"1px solid #e2e8f0",borderRadius:8,cursor:"pointer",fontSize:12,minHeight:84,transition:"border-color 120ms, box-shadow 120ms"},vA={display:"flex",flexDirection:"column",alignItems:"center",justifyContent:"center",gap:6,padding:"14px 6px",background:"#f8fafc",border:"1px dashed #cbd5e1",borderRadius:8,cursor:"not-allowed",fontSize:12,minHeight:84,color:"#94a3b8",opacity:.65},_A={fontSize:9,fontWeight:600,color:"#92400e",background:"#fef3c7",padding:"1px 6px",borderRadius:999,textTransform:"none",letterSpacing:0,lineHeight:1.6},xA={fontSize:22,lineHeight:1,color:"#475569"},wA={fontSize:12,fontWeight:500,color:"#0f172a",textAlign:"center",wordBreak:"break-word"},kA={fontFamily:'ui-monospace, SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace',fontSize:10,color:"#64748b",textAlign:"center",wordBreak:"break-all"},Mf={padding:24,color:"#64748b",fontSize:13,textAlign:"center"};function SA(e){const t=[],n=r=>{if(Array.isArray(r))for(const s of r)s&&typeof s=="object"&&t.push(s)};n(e.children);const o=e.responsive;if(o&&typeof o=="object")for(const r of Object.values(o))r&&typeof r=="object"&&n(r.children);return t}function Lf(e){const t=[],n=o=>{const r="isolatedState"in o&&o.isolatedState!=null,s=o.isolatedScopeId;(r||typeof s=="string"&&s.length>0)&&t.push({scopeId:typeof s=="string"?s:void 0,initialState:o.isolatedState&&typeof o.isolatedState=="object"?o.isolatedState:void 0});for(const a of SA(o))n(a)};if(Array.isArray(e))for(const o of e)o&&typeof o=="object"&&n(o);return t}const CA=["scroll","selector","wizard","slider"];function $A(e,t){const n=new Set;for(const o of e)o.scopeId&&n.add(o.scopeId);for(const o of t){n.add(o);for(const r of CA)n.add(`${o}-${r}`)}return Array.from(n)}function EA(e,t,n){const o=t.label;return typeof o=="string"?o.startsWith("$t:")?n(o.slice(3)):o:e}function hi({controlKey:e,control:t,node:n,t:o,onPatch:r,candidates:s,bindingCandidates:a,scope:l=Wt}){const d=Tn(t.widget),c=EA(e,t,o),p=u.useMemo(()=>zo(n,t,l),[n,t,l]),f=t.widget==="i18n-text"&&t.apply?.type==="propValue",b=t.widget==="text"&&t.apply?.type==="propValue",g=p.darkReadonly===!0||l.colorScheme==="dark"&&t.apply!==void 0&&!Ys(t.apply),m=t.apply,h=l.colorScheme==="dark"&&!g&&m?.type==="classToken"&&typeof m.tokenTemplate=="string"&&m.tokenTemplate.includes("{value}"),v=p.scopedValue===void 0&&p.baseFallback!==void 0,y=u.useCallback(_=>{if(g)return;const w=fn(n,t,_,l);r(w)},[n,t,r,l,g]);return i.jsxs("div",{className:"g7le-control-row","data-testid":`g7le-control-${e}`,style:IA,children:[i.jsxs("span",{className:"g7le-control-label",style:AA,children:[c,p.conflict&&i.jsx("span",{"data-testid":`g7le-control-conflict-${e}`,title:o("layout_editor.property_modal.value_conflict"),style:TA,children:"!"})]}),i.jsx("div",{className:"g7le-control-widget",style:{flex:1,minWidth:0,opacity:v?.45:1},"data-placeholder":v?"true":void 0,children:f?i.jsx(_t,{value:typeof p.value=="string"?p.value:"",onChange:_=>y(_),t:o,testidPrefix:`g7le-prop-i18n-${e}`,candidates:a,enableExpressionTree:!0,expressionTreeCollapsible:!0}):b?i.jsx(Ke,{value:typeof p.value=="string"?p.value:"",onChange:_=>y(_===""?void 0:_),t:o,testidPrefix:`g7le-prop-value-${e}`,candidates:a}):g?i.jsx("span",{"data-testid":`g7le-control-dark-readonly-${e}`,style:zA,children:o("layout_editor.property_modal.dark_code_only")}):d?i.jsx(d,{control:t,value:p.value,onChange:y,t:o,candidates:s,bindingCandidates:a,freeValueDisabled:h}):i.jsx("span",{"data-testid":`g7le-control-unsupported-${e}`,style:DA,children:o("layout_editor.property_modal.unsupported_widget")})})]})}const IA={display:"flex",alignItems:"center",gap:12,padding:"6px 0",minHeight:32,minWidth:0},AA={fontSize:12,color:"#475569",minWidth:88,display:"inline-flex",alignItems:"center",gap:4},TA={display:"inline-flex",width:14,height:14,borderRadius:7,background:"#f59e0b",color:"#fff",fontSize:10,alignItems:"center",justifyContent:"center",fontWeight:700},DA={fontSize:11,color:"#94a3b8",fontStyle:"italic"},zA={fontSize:11,color:"#94a3b8",fontStyle:"italic"};function yi(e,t,n){return typeof e=="string"?e.startsWith("$t:")?n(e.slice(3)):e:t}function NA(e,t){const n=e.props??{};return t.key in n?n[t.key]:t.default}function MA({spec:e,node:t,t:n,onPatchProp:o}){const r=e.groups??[];return i.jsx("div",{className:"g7le-composite-settings","data-testid":"g7le-composite-settings",style:PA,children:r.map((s,a)=>i.jsxs("div",{className:"g7le-settings-group",style:OA,children:[i.jsx("div",{style:BA,children:yi(s.label,n("layout_editor.property_modal.settings_group",{index:a+1}),n)}),(s.fields??[]).map(l=>i.jsxs("div",{className:"g7le-settings-field","data-testid":`g7le-setting-${l.key}`,style:WA,children:[i.jsx("span",{style:jA,children:yi(l.label,l.key,n)}),i.jsx("div",{style:{flex:1},children:LA(l,t,n,o)})]},l.key))]},a))})}function LA(e,t,n,o){const r=NA(t,e);switch(e.type){case"number":return i.jsx("input",{type:"number","data-testid":`g7le-setting-input-${e.key}`,value:typeof r=="number"?r:"",min:e.min,max:e.max,onChange:s=>o(e.key,s.target.value===""?void 0:Number(s.target.value)),style:Ia});case"text":return i.jsx("input",{type:"text","data-testid":`g7le-setting-input-${e.key}`,value:typeof r=="string"?r:"",onChange:s=>o(e.key,s.target.value===""?void 0:s.target.value),style:Ia});case"toggle":return i.jsx("input",{type:"checkbox","data-testid":`g7le-setting-input-${e.key}`,checked:r===!0,onChange:s=>o(e.key,s.target.checked)});case"checkbox-group":{const s=Array.isArray(r)?r:[];return i.jsx("div",{style:HA,"data-testid":`g7le-setting-input-${e.key}`,children:(e.options??[]).map(a=>{const l=s.includes(a.value);return i.jsxs("label",{style:FA,children:[i.jsx("input",{type:"checkbox",checked:l,onChange:d=>{const c=d.target.checked?[...s,a.value]:s.filter(p=>p!==a.value);o(e.key,c)}}),yi(a.label,String(a.value),n)]},String(a.value))})})}default:return i.jsxs("select",{"data-testid":`g7le-setting-input-${e.key}`,value:r==null?"":String(r),onChange:s=>{const a=s.target.value;if(a===""){o(e.key,void 0);return}const l=(e.options??[]).find(d=>String(d.value)===a);o(e.key,l?l.value:a)},style:Ia,children:[i.jsx("option",{value:"",children:"—"}),(e.options??[]).map(s=>i.jsx("option",{value:String(s.value),children:yi(s.label,String(s.value),n)},String(s.value)))]})}}const PA={display:"flex",flexDirection:"column",gap:16},OA={display:"flex",flexDirection:"column",gap:6},BA={fontSize:12,fontWeight:700,color:"#0f172a",borderBottom:"1px solid #e2e8f0",paddingBottom:4},WA={display:"flex",alignItems:"center",gap:12,minHeight:30},jA={fontSize:12,color:"#475569",minWidth:96},Ia={padding:"5px 8px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,width:"100%",boxSizing:"border-box"},HA={display:"flex",flexWrap:"wrap",gap:10},FA={display:"inline-flex",alignItems:"center",gap:4,fontSize:12,color:"#0f172a"},KA=["data_binding","isolatedScopeId","parentFormContextProp","skipBindingKeys","isolatedState","component_layout","sortable","itemTemplate"],VA=/\{\{|\$switch|\$get|\|\s*(date|number|truncate|currency|relative)\b/;function bi(e,t=0){return t>6?!1:typeof e=="string"?VA.test(e):Array.isArray(e)?e.some(n=>bi(n,t+1)):e&&typeof e=="object"?Object.values(e).some(n=>bi(n,t+1)):!1}function UA(e){if(td(e))return!1;const t=ql(e);return t.length===0?!1:t.some(n=>n.isComplex)}function Pf(e){const t=[],n=e.props??{};for(const o of KA)o in e&&e[o]!==void 0?t.push({key:o,kind:"developer_prop"}):o in n&&n[o]!==void 0&&t.push({key:`props.${o}`,kind:"developer_prop"});typeof e.text=="string"?UA(e.text)&&t.push({key:"text",kind:"complex_expression"}):bi(e.text)&&t.push({key:"text",kind:"complex_expression"});for(const[o,r]of Object.entries(n))o==="style"||o==="className"||bi(r)&&t.push({key:`props.${o}`,kind:"complex_expression"});return t}function qA(e){return Pf(e).length>0}const GA=new Set(["permissions","blur_until_loaded","comment"]);function YA({node:e,advanced:t,t:n,onPatch:o,permissionCandidates:r}){const s=u.useMemo(()=>Pf(e),[e]),a=(l,d)=>{const c={...e};d===void 0?delete c[l]:c[l]=d,o(c)};return i.jsxs("div",{className:"g7le-advanced-props","data-testid":"g7le-advanced-props",style:XA,children:[t.map(l=>{if(!GA.has(l))return i.jsxs("div",{"data-testid":`g7le-advanced-deferred-${l}`,style:ZA,children:[n("layout_editor.property_modal.advanced_deferred"),":",l]},l);if(l==="permissions")return i.jsxs("div",{className:"g7le-advanced-group","data-testid":"g7le-advanced-permissions",style:Aa,children:[i.jsx("div",{style:Ta,children:n("layout_editor.property_modal.advanced.permissions")}),i.jsx(vs,{control:{widget:"tag-input"},value:e.permissions,onChange:d=>a("permissions",d),t:n,candidates:r})]},l);if(l==="blur_until_loaded"){const d=e.blur_until_loaded===!0||e.blur_until_loaded!=null&&e.blur_until_loaded!==!1;return i.jsxs("div",{className:"g7le-advanced-group","data-testid":"g7le-advanced-blur",style:Aa,children:[i.jsx("div",{style:Ta,children:n("layout_editor.property_modal.advanced.blur_until_loaded")}),i.jsxs("label",{style:{display:"inline-flex",alignItems:"center",gap:6,fontSize:12},children:[i.jsx("input",{type:"checkbox","data-testid":"g7le-advanced-blur-toggle",checked:!!d,onChange:c=>a("blur_until_loaded",c.target.checked?!0:void 0)}),n("layout_editor.property_modal.advanced.blur_hint")]})]},l)}return i.jsxs("div",{className:"g7le-advanced-group","data-testid":"g7le-advanced-comment",style:Aa,children:[i.jsx("div",{style:Ta,children:n("layout_editor.property_modal.advanced.comment")}),i.jsx("input",{type:"text","data-testid":"g7le-advanced-comment-input",value:typeof e.comment=="string"?e.comment:"",onChange:d=>a("comment",d.target.value===""?void 0:d.target.value),style:JA})]},l)}),s.length>0&&i.jsxs("div",{className:"g7le-advanced-preserved","data-testid":"g7le-advanced-preserved",style:QA,children:[i.jsx("div",{style:RA,children:n("layout_editor.property_modal.advanced_preserved_title",{count:s.length})}),i.jsx("ul",{style:e6,children:s.map(l=>i.jsxs("li",{"data-testid":`g7le-advanced-preserved-${l.key}`,style:t6,children:[i.jsx("code",{style:{fontSize:11},children:l.key}),i.jsx("span",{style:{fontSize:11,color:"#94a3b8"},children:l.kind==="developer_prop"?n("layout_editor.property_modal.advanced_kind_developer"):n("layout_editor.property_modal.advanced_kind_expression")})]},l.key))})]})]})}const XA={display:"flex",flexDirection:"column",gap:14},Aa={display:"flex",flexDirection:"column",gap:6},Ta={fontSize:12,fontWeight:700,color:"#0f172a"},JA={padding:"5px 8px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,width:"100%",boxSizing:"border-box"},ZA={fontSize:11,color:"#94a3b8",fontStyle:"italic"},QA={borderTop:"1px dashed #cbd5e1",paddingTop:10,marginTop:4},RA={fontSize:11,color:"#b45309",marginBottom:6},e6={listStyle:"none",margin:0,padding:0,display:"flex",flexDirection:"column",gap:4},t6={display:"flex",justifyContent:"space-between",gap:8,padding:"3px 0"},n6={onClick:"click",onHover:"mouseenter",onChange:"change",onSubmit:"submit",onMount:"mount",onUnmount:"unmount",onFocus:"focus",onBlur:"blur"};function Da(e){return n6[e]??e.replace(/^on/,"").toLowerCase()}function Of(e){const t=e.actions;if(Array.isArray(t))return t.filter(r=>!!r&&typeof r=="object");const n=e.props??{};if(Array.isArray(n.actions))return n.actions.filter(r=>!!r&&typeof r=="object");const o=n.events;if(o&&typeof o=="object"){const r=[];for(const[s,a]of Object.entries(o)){if(!Array.isArray(a))continue;const l=Da(s);for(const d of a)d&&typeof d=="object"&&r.push("type"in d?d:{type:l,...d})}return r}return[]}function o6(e,t){const n=Da(t);return Of(e).filter(o=>o.type===n||t==="onClick"&&o.type===void 0)}function r6(e,t,n){const o=Da(t),r=Of(e).filter(d=>!(d.type===o||t==="onClick"&&d.type===void 0)),s=n.map(d=>"type"in d?d:{type:o,...d}),a={...e};if(a.props&&typeof a.props=="object"){const d={...a.props};let c=!1;"actions"in d&&(delete d.actions,c=!0),"events"in d&&(delete d.events,c=!0),c&&(a.props=d)}const l=[...r,...s];return l.length===0?delete a.actions:a.actions=l,a}function i6({node:e,spec:t,capability:n,t:o,onPatchNode:r,pageCandidates:s,dataSourceCandidates:a,stateKeyCandidates:l,bindingCandidates:d}){const c=t?.actionRecipes,p=u.useMemo(()=>({pageCandidates:s,dataSourceCandidates:a,stateKeyCandidates:l,bindingCandidates:d}),[s,a,l,d]),f=Array.isArray(n?.events)?n.events:[];return f.length===0?i.jsx("div",{"data-testid":"g7le-action-no-events",style:Bf,children:o("layout_editor.action.no_events")}):!c||Object.keys(c).length===0?i.jsx("div",{"data-testid":"g7le-action-no-recipes",style:Bf,children:o("layout_editor.action.no_recipes")}):i.jsx("div",{className:"g7le-action-editor","data-testid":"g7le-action-editor",style:{display:"flex",flexDirection:"column",gap:14},children:f.map(b=>i.jsxs("div",{className:"g7le-action-event-slot","data-testid":`g7le-action-event-${b}`,children:[i.jsx("div",{style:s6,children:o(`layout_editor.action.event.${b}`)}),i.jsx(Gt,{actions:o6(e,b),onChange:g=>r(r6(e,b,g)),t:o,recipes:c,candidatePools:p,actionChipCandidates:t?.actionChipCandidates??null,addContext:"component",showSourceBadge:!0,testIdPrefix:`g7le-action-slot-${b}`})]},b))})}const Bf={fontSize:12,color:"#94a3b8",padding:"16px 0",textAlign:"center"},s6={fontSize:12,fontWeight:700,color:"#475569",marginBottom:6};function za(e,t,n){if(!t)return n;const o=e(t);return!o||o===t||o.startsWith("$t:")?n:o}function a6(e,t){return za(t,e.labelKey,e.path||e.sourceId)}function l6({node:e,spec:t,candidates:n,t:o,onPatchNode:r}){const a=(e.props??{})[t.propKey],l=typeof a=="string"?a:void 0,d=u.useMemo(()=>Kt(a),[a]),c=l!==void 0&&l!==""&&d===null,[p,f]=u.useState(!1),[b,g]=u.useState(""),m=za(o,t.label,t.propKey),h=o(t.shape==="array"?"layout_editor.binding.shape_array":t.shape==="object"?"layout_editor.binding.shape_object":"layout_editor.binding.shape_scalar"),v=u.useMemo(()=>Ui(n,t.shape).map(x=>({...x,resolvedLabel:a6(x,o)})),[n,t.shape,o]),y=u.useMemo(()=>qi(v,b),[v,b]),_=k=>{const x={...e.props??{}};k===void 0?delete x[t.propKey]:x[t.propKey]=k,r({...e,props:x})},w=k=>{_(En(k.sourceId,k.path,t.shape)),f(!1),g("")};return i.jsxs("div",{"data-testid":`g7le-binding-row-${t.propKey}`,style:p6,children:[i.jsxs("div",{style:f6,children:[i.jsx("span",{style:{fontWeight:600,fontSize:12},children:m}),i.jsx("span",{style:g6,children:h}),t.required&&i.jsx("span",{"data-testid":`g7le-binding-required-${t.propKey}`,style:h6,children:o("layout_editor.binding.required")})]}),c?i.jsxs("div",{"data-testid":`g7le-binding-complex-${t.propKey}`,style:v6,children:[o("layout_editor.binding.complex"),i.jsx("code",{style:Wf,children:l})]}):d?i.jsxs("div",{style:y6,children:[i.jsx("code",{"data-testid":`g7le-binding-expr-${t.propKey}`,style:Wf,children:l}),i.jsx("button",{type:"button","data-testid":`g7le-binding-clear-${t.propKey}`,onClick:()=>_(void 0),style:m6,children:o("layout_editor.binding.clear")})]}):i.jsx("div",{"data-testid":`g7le-binding-empty-${t.propKey}`,style:b6,children:o("layout_editor.binding.not_connected")}),d&&(t.shape==="array"||t.shape==="object")&&t.itemFields&&t.itemFields.length>0&&i.jsxs("div",{style:_6,children:[o("layout_editor.binding.item_fields"),": ",t.itemFields.join(", ")]}),!c&&i.jsxs("div",{style:{marginTop:4},children:[i.jsxs("button",{type:"button","data-testid":`g7le-binding-search-toggle-${t.propKey}`,onClick:()=>f(k=>!k),style:x6,children:["🔍 ",o("layout_editor.binding.search")]}),p&&i.jsxs("div",{"data-testid":`g7le-binding-picker-${t.propKey}`,style:w6,children:[i.jsx("input",{type:"text",autoFocus:!0,"data-testid":`g7le-binding-search-input-${t.propKey}`,value:b,placeholder:o("layout_editor.binding.search_placeholder"),onChange:k=>g(k.target.value),style:k6}),i.jsx("div",{style:S6,children:y.length===0?i.jsx("div",{style:C6,children:o("layout_editor.binding.no_results")}):y.map(k=>i.jsxs("button",{type:"button","data-testid":`g7le-binding-candidate-${k.expression}`,onClick:()=>w(k),style:$6,children:[i.jsxs("span",{style:{display:"flex",flexDirection:"column",alignItems:"flex-start"},children:[i.jsx("span",{style:{fontSize:12},children:k.resolvedLabel}),i.jsxs("span",{style:E6,children:[za(o,k.groupLabelKey,k.sourceId)," · ",k.path||k.sourceId]})]}),i.jsx("span",{style:I6,children:k.preview})]},k.expression))})]})]})]})}function d6({node:e,dataProps:t,candidates:n,t:o,onPatchNode:r}){return!Array.isArray(t)||t.length===0?null:i.jsxs("div",{"data-testid":"g7le-data-binding-section",style:c6,children:[i.jsxs("div",{style:u6,children:["🔗 ",o("layout_editor.binding.section_title")]}),t.map(s=>i.jsx(l6,{node:e,spec:s,candidates:n,t:o,onPatchNode:r},s.propKey)),i.jsx("div",{style:A6})]})}const c6={display:"flex",flexDirection:"column",marginBottom:8},u6={fontSize:12,fontWeight:700,color:"#0f172a",marginBottom:6},p6={display:"flex",flexDirection:"column",gap:4,padding:"8px 0"},f6={display:"flex",alignItems:"center",gap:6},g6={fontSize:10,padding:"1px 6px",borderRadius:4,background:"#e0e7ff",color:"#3730a3"},h6={fontSize:10,padding:"1px 6px",borderRadius:4,background:"#fef3c7",color:"#92400e"},y6={display:"flex",alignItems:"center",gap:6},b6={fontSize:11,color:"#94a3b8"},Wf={fontSize:11,fontFamily:"monospace",background:"#f1f5f9",padding:"2px 6px",borderRadius:4,color:"#0f172a",wordBreak:"break-all"},m6={fontSize:11,border:"1px solid #e2e8f0",borderRadius:4,background:"#fff",color:"#dc2626",padding:"2px 8px",cursor:"pointer"},v6={display:"flex",flexDirection:"column",gap:2,fontSize:11,color:"#64748b"},_6={fontSize:10,color:"#94a3b8"},x6={fontSize:11,border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",color:"#334155",padding:"4px 10px",cursor:"pointer",textAlign:"left",width:"100%"},w6={marginTop:4,border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",padding:6},k6={width:"100%",padding:"5px 8px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,boxSizing:"border-box"},S6={marginTop:4,maxHeight:200,overflowY:"auto",display:"flex",flexDirection:"column",gap:2},C6={fontSize:11,color:"#94a3b8",padding:"8px 0",textAlign:"center"},$6={display:"flex",alignItems:"center",justifyContent:"space-between",gap:8,border:"none",background:"transparent",cursor:"pointer",padding:"6px 8px",borderRadius:4,textAlign:"left"},E6={fontSize:10,color:"#94a3b8"},I6={fontSize:10,color:"#64748b",fontFamily:"monospace",whiteSpace:"nowrap"},A6={borderTop:"1px solid #e2e8f0",marginTop:4};function T6({node:e,candidates:t,t:n,onPatchNode:o,templateIdentifier:r}){const s=typeof e.text=="string"?e.text:"",{state:a}=Qe(),l=r??a.templateIdentifier,d=a.selectedRoute?.layoutName??null,c=a.locale,[p,f]=u.useState(!1),b=/^\s*\$t:custom\.[A-Za-z0-9._-]+\s*$/.test(s),m=td(s)&&(vt(s)?.key.startsWith("custom.")??!1)||b,h=u.useMemo(()=>m?vt(s):null,[m,s]),v=u.useMemo(()=>m?[]:ql(s),[m,s]),y=T=>{o({...e,text:T})},_=()=>{if(!(typeof window>"u"))try{window.dispatchEvent(new CustomEvent(kt,{detail:{templateIdentifier:l,locale:c}}))}catch{}},w=(T,E)=>{const A=En(E.sourceId,E.path,"scalar");y(Ab(s,T,A))},k=T=>{const E=vt(s);y(vo(s,T));const A=E?.key;l&&A&&qd(l,A,T).then(_)},x=T=>{const E=n(T);return E&&E!==T&&!E.startsWith("$t:")?E:""},$=T=>T.replace(/\$t:[a-zA-Z0-9._-]+/g,E=>x(E.slice(3))).replace(/\s+/g," ").trim(),S=T=>{p||(f(!0),us(l,d,c,s,s.length,T.sourceId,T.path,"scalar",$,E=>n(E)).then(E=>{E.kind==="ok"&&(y(E.text),_())}).finally(()=>f(!1)))},C=T=>{p||!h&&!b||(f(!0),Cr(l,c,s,Number.MAX_SAFE_INTEGER,T.sourceId,T.path,"scalar").then(E=>{E.kind==="ok"&&(y(E.text),_())}).finally(()=>f(!1)))};if(m){const T=h?.params??[];return i.jsxs("div",{"data-testid":"g7le-inline-binding-section",style:jf,children:[i.jsxs("div",{style:Hf,children:["🔗 ",n("layout_editor.inline_binding.section_title")]}),T.length===0?i.jsx("div",{"data-testid":"g7le-inline-binding-none",style:Vf,children:n("layout_editor.inline_binding.none")}):T.map(E=>i.jsx("div",{"data-testid":`g7le-inline-binding-param-${E.name}`,style:Ff,children:E.parsed===null?i.jsxs("div",{"data-testid":`g7le-inline-binding-param-complex-${E.name}`,style:z6,children:[n("layout_editor.inline_binding.complex"),i.jsx("code",{style:Na,children:E.expression})]}):i.jsxs(i.Fragment,{children:[i.jsxs("div",{style:Kf,children:[i.jsx("code",{"data-testid":`g7le-inline-binding-param-expr-${E.name}`,style:Na,children:E.expression}),i.jsx("button",{type:"button","data-testid":`g7le-inline-binding-param-clear-${E.name}`,onClick:()=>k(E.name),style:D6,children:n("layout_editor.inline_binding.clear")})]}),i.jsx($t,{candidates:t,t:n,onSelect:A=>w(E.name,A),testIdSuffix:`param-${E.name}`})]})},E.name)),i.jsxs("div",{"data-testid":"g7le-inline-binding-param-append",style:Uf,children:[i.jsx("div",{style:Ma,children:n("layout_editor.inline_binding.append_hint")}),i.jsx($t,{candidates:t,t:n,onSelect:C,testIdSuffix:"param-append"})]}),i.jsx("div",{"data-testid":"g7le-inline-binding-param-hint",style:Ma,children:n("layout_editor.inline_binding.param_hint")}),i.jsx("div",{style:qf})]})}return i.jsxs("div",{"data-testid":"g7le-inline-binding-section",style:jf,children:[i.jsxs("div",{style:Hf,children:["🔗 ",n("layout_editor.inline_binding.section_title")]}),v.length===0?i.jsx("div",{"data-testid":"g7le-inline-binding-none",style:Vf,children:n("layout_editor.inline_binding.none")}):v.map(T=>i.jsx("div",{"data-testid":`g7le-inline-binding-row-${T.bindingIndex}`,style:Ff,children:i.jsx("div",{style:Kf,children:i.jsx("code",{"data-testid":`g7le-inline-binding-expr-${T.bindingIndex}`,style:Na,children:T.expression})})},T.bindingIndex)),i.jsxs("div",{"data-testid":"g7le-inline-binding-append",style:Uf,children:[i.jsx("div",{style:Ma,children:n("layout_editor.inline_binding.append_hint")}),i.jsx($t,{candidates:t,t:n,onSelect:S,testIdSuffix:"append"})]}),i.jsx("div",{style:qf})]})}const jf={display:"flex",flexDirection:"column",marginBottom:8},Hf={fontSize:12,fontWeight:700,color:"#0f172a",marginBottom:6},Ff={display:"flex",flexDirection:"column",gap:4,padding:"8px 0"},Kf={display:"flex",alignItems:"center",gap:6},Vf={fontSize:11,color:"#94a3b8"},Na={fontSize:11,fontFamily:"monospace",background:"#f1f5f9",padding:"2px 6px",borderRadius:4,color:"#0f172a",wordBreak:"break-all"},D6={fontSize:11,border:"1px solid #e2e8f0",borderRadius:4,background:"#fff",color:"#dc2626",padding:"2px 8px",cursor:"pointer"},z6={display:"flex",flexDirection:"column",gap:2,fontSize:11,color:"#64748b"},Uf={display:"flex",flexDirection:"column",gap:2,padding:"8px 0 0"},Ma={fontSize:11,color:"#475569",fontWeight:600},qf={borderTop:"1px solid #e2e8f0",marginTop:4};function Gf(e,t,n){if(!t)return n;const o=e(t);return!o||o===t||o.startsWith("$t:")?n:o}function N6(e,t){return Gf(t,e.labelKey,e.path||e.sourceId)}function M6(e){const t=e.iteration;return t&&typeof t=="object"?t:null}function L6({node:e,candidates:t,t:n,onPatchNode:o}){const r=M6(e),s=r?.source,a=typeof s=="string"?s:void 0,l=u.useMemo(()=>Kt(s),[s]),d=a!==void 0&&a!==""&&l===null,[c,p]=u.useState(!1),[f,b]=u.useState(""),g=u.useMemo(()=>Ui(t,"array").map(k=>({...k,resolvedLabel:N6(k,n)})),[t,n]),m=u.useMemo(()=>qi(g,f),[g,f]);if(!r)return null;const h=typeof r.item_var=="string"?r.item_var:void 0,v=typeof r.index_var=="string"?r.index_var:void 0,y=w=>{const k={...r};w===void 0?delete k.source:k.source=w,o({...e,iteration:k})},_=w=>{y(En(w.sourceId,w.path,"array")),p(!1),b("")};return i.jsxs("div",{"data-testid":"g7le-iteration-binding-section",style:P6,children:[i.jsxs("div",{style:O6,children:["🔁 ",n("layout_editor.binding.iteration_title")]}),i.jsx("div",{style:B6,children:n("layout_editor.binding.iteration_hint")}),i.jsxs("div",{style:W6,children:[i.jsxs("div",{style:j6,children:[i.jsx("span",{style:{fontWeight:600,fontSize:12},children:n("layout_editor.binding.iteration_label")}),i.jsx("span",{style:H6,children:n("layout_editor.binding.shape_array")})]}),d?i.jsxs("div",{"data-testid":"g7le-iteration-binding-complex",style:U6,children:[n("layout_editor.binding.complex"),i.jsx("code",{style:Yf,children:a})]}):l?i.jsxs("div",{style:F6,children:[i.jsx("code",{"data-testid":"g7le-iteration-binding-expr",style:Yf,children:a}),i.jsx("button",{type:"button","data-testid":"g7le-iteration-binding-clear",onClick:()=>y(void 0),style:V6,children:n("layout_editor.binding.clear")})]}):i.jsx("div",{"data-testid":"g7le-iteration-binding-empty",style:K6,children:n("layout_editor.binding.not_connected")}),(h||v)&&i.jsxs("div",{"data-testid":"g7le-iteration-vars",style:q6,children:[h&&i.jsxs("span",{children:[n("layout_editor.binding.iteration_item_var"),": ",i.jsx("code",{style:Xf,children:h})]}),v&&i.jsxs("span",{children:[n("layout_editor.binding.iteration_index_var"),": ",i.jsx("code",{style:Xf,children:v})]})]}),!d&&i.jsxs("div",{style:{marginTop:4},children:[i.jsxs("button",{type:"button","data-testid":"g7le-iteration-binding-search-toggle",onClick:()=>p(w=>!w),style:G6,children:["🔍 ",n("layout_editor.binding.search")]}),c&&i.jsxs("div",{"data-testid":"g7le-iteration-binding-picker",style:Y6,children:[i.jsx("input",{type:"text",autoFocus:!0,"data-testid":"g7le-iteration-binding-search-input",value:f,placeholder:n("layout_editor.binding.search_placeholder"),onChange:w=>b(w.target.value),style:X6}),i.jsx("div",{style:J6,children:m.length===0?i.jsx("div",{style:Z6,children:n("layout_editor.binding.no_results")}):m.map(w=>i.jsxs("button",{type:"button","data-testid":`g7le-iteration-binding-candidate-${w.expression}`,onClick:()=>_(w),style:Q6,children:[i.jsxs("span",{style:{display:"flex",flexDirection:"column",alignItems:"flex-start"},children:[i.jsx("span",{style:{fontSize:12},children:w.resolvedLabel}),i.jsxs("span",{style:R6,children:[Gf(n,w.groupLabelKey,w.sourceId)," · ",w.path||w.sourceId]})]}),i.jsx("span",{style:eT,children:w.preview})]},w.expression))})]})]})]}),i.jsx("div",{style:tT})]})}const P6={display:"flex",flexDirection:"column",marginBottom:8},O6={fontSize:12,fontWeight:700,color:"#0f172a",marginBottom:4},B6={fontSize:10,color:"#94a3b8",marginBottom:6,lineHeight:1.4},W6={display:"flex",flexDirection:"column",gap:4,padding:"8px 0"},j6={display:"flex",alignItems:"center",gap:6},H6={fontSize:10,padding:"1px 6px",borderRadius:4,background:"#e0e7ff",color:"#3730a3"},F6={display:"flex",alignItems:"center",gap:6},K6={fontSize:11,color:"#94a3b8"},Yf={fontSize:11,fontFamily:"monospace",background:"#f1f5f9",padding:"2px 6px",borderRadius:4,color:"#0f172a",wordBreak:"break-all"},V6={fontSize:11,border:"1px solid #e2e8f0",borderRadius:4,background:"#fff",color:"#dc2626",padding:"2px 8px",cursor:"pointer"},U6={display:"flex",flexDirection:"column",gap:2,fontSize:11,color:"#64748b"},q6={display:"flex",gap:12,fontSize:10,color:"#94a3b8",marginTop:2},Xf={fontSize:10,fontFamily:"monospace",background:"#f1f5f9",padding:"1px 4px",borderRadius:3,color:"#475569"},G6={fontSize:11,border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",color:"#334155",padding:"4px 10px",cursor:"pointer",textAlign:"left",width:"100%"},Y6={marginTop:4,border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",padding:6},X6={width:"100%",padding:"5px 8px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,boxSizing:"border-box"},J6={marginTop:4,maxHeight:200,overflowY:"auto",display:"flex",flexDirection:"column",gap:2},Z6={fontSize:11,color:"#94a3b8",padding:"8px 0",textAlign:"center"},Q6={display:"flex",alignItems:"center",justifyContent:"space-between",gap:8,border:"none",background:"transparent",cursor:"pointer",padding:"6px 8px",borderRadius:4,textAlign:"left"},R6={fontSize:10,color:"#94a3b8"},eT={fontSize:10,color:"#64748b",fontFamily:"monospace",whiteSpace:"nowrap"},tT={borderTop:"1px solid #e2e8f0",marginTop:4},nT=new Set(["Span","P","H1","H2","H3","H4","H5","H6","A","Button","Li","Label"]);function oT(e,t){if(!e||e.iteration&&typeof e.iteration=="object")return!1;const n=t?.textBinding;if(n===!1)return!1;if(n===!0)return!0;const o=typeof e.name=="string"?e.name:"";return nT.has(o)?!0:typeof e.text=="string"}const yn={direction:"flexDirection",justify:"flexJustify",align:"flexAlign",wrap:"flexWrap",gap:"flexGap",enable:"flexEnable"},mi={grow:"flexItemGrow",selfAlign:"flexItemAlign",order:"flexItemOrder"};function Jf(e){const t=e?.flexEditor;return t==="container"||t==="item"||t==="auto"?t:null}function rT(e){if(!e||typeof window>"u"||typeof window.getComputedStyle!="function")return!1;const t=(window.getComputedStyle(e).display??"").toLowerCase();return t==="flex"||t==="inline-flex"}function iT(e,t,n=Wt){if(!e||!t)return!1;const o=zo(e,t,n),r=t.options,s=Array.isArray(r)?t.onValue??r[0]?.value:void 0,a=l=>s!==void 0?l===s:l!==void 0&&l!==!1;return n.breakpoint!=="base"?a(o.scopedRawValue):o.matched&&a(o.value)}function sT(e,t,n=!1,o=!1,r=Wt){if(e==="container")return{showContainer:!0,showEnableButton:!1,showDisableButton:!1};if(e==="item")return{showContainer:!1,showEnableButton:!1,showDisableButton:!1};if(e==="auto"){const s=r.breakpoint==="base";return!o&&!s?{showContainer:!1,showEnableButton:!1,showDisableButton:!1}:(o?n:rT(t))?{showContainer:!0,showEnableButton:!1,showDisableButton:o}:{showContainer:!1,showEnableButton:!0,showDisableButton:!1}}return{showContainer:!1,showEnableButton:!1,showDisableButton:!1}}const aT={[yn.direction]:"layout_editor.flex.direction",[yn.justify]:"layout_editor.flex.justify",[yn.align]:"layout_editor.flex.align",[yn.wrap]:"layout_editor.flex.wrap",[yn.gap]:"layout_editor.flex.gap"},lT={[mi.grow]:"layout_editor.flex.item_grow",[mi.selfAlign]:"layout_editor.flex.item_align",[mi.order]:"layout_editor.flex.item_order"};function dT({node:e,spec:t,capability:n,t:o,onPatchNode:r,liveElement:s,scope:a=Wt}){const l=Jf(n),d=Ct(t,yn.enable),c=iT(e,d,a),{showContainer:p,showEnableButton:f,showDisableButton:b}=sT(l,s,c,!!d,a),g=Object.values(yn).filter(x=>x!==yn.enable&&Ct(t,x)),m=Object.values(mi).filter(x=>Ct(t,x)),h=d?.options,v=d?.onValue??h?.[0]?.value??!0,y=Array.isArray(h)&&h.length>=2?h[1].value:void 0,_=()=>{if(!d)return;const x=fn(e,d,v,a);r(x)},w=()=>{if(!d)return;const x=a.breakpoint!=="base"&&y!==void 0?y:void 0,$=fn(e,d,x,a);r($)},k=(x,$,S)=>i.jsx("div",{"data-testid":S,style:{display:"flex",flexDirection:"column"},children:x.map(C=>{const T=Ct(t,C);if(!T)return null;const E=T.label?T:{...T,label:`$t:${$[C]}`};return i.jsx(hi,{controlKey:C,control:E,node:e,t:o,onPatch:r,scope:a},C)})});return i.jsxs("div",{className:"g7le-flex-editor","data-testid":"g7le-flex-editor",style:{display:"flex",flexDirection:"column",gap:12},children:[f&&i.jsx("button",{type:"button","data-testid":"g7le-flex-enable",onClick:_,style:cT,children:o("layout_editor.flex.make_flex")}),p&&g.length>0&&i.jsxs("div",{className:"g7le-flex-container-section","data-testid":"g7le-flex-container-section",children:[i.jsx("div",{style:Zf,children:o("layout_editor.flex.container_title")}),k(g,aT,"g7le-flex-container-controls"),b&&i.jsx("button",{type:"button","data-testid":"g7le-flex-disable",onClick:w,style:uT,children:o("layout_editor.flex.unmake_flex")})]}),l==="item"&&m.length>0&&i.jsxs("div",{className:"g7le-flex-item-section","data-testid":"g7le-flex-item-section",children:[i.jsx("div",{style:Zf,children:o("layout_editor.flex.item_title")}),k(m,lT,"g7le-flex-item-controls")]}),!p&&!f&&l!=="item"&&i.jsx("div",{"data-testid":"g7le-flex-empty",style:pT,children:o("layout_editor.flex.not_flex")})]})}const Zf={fontSize:12,fontWeight:700,color:"#0f172a",marginBottom:6},cT={padding:"8px 12px",fontSize:12,border:"1px dashed #2563eb",borderRadius:6,background:"#eff6ff",color:"#2563eb",cursor:"pointer"},uT={marginTop:8,padding:"6px 10px",fontSize:12,border:"1px solid #e2e8f0",borderRadius:6,background:"#f8fafc",color:"#64748b",cursor:"pointer"},pT={fontSize:12,color:"#94a3b8",padding:"16px 0",textAlign:"center"},Qf=["desktop","tablet","mobile","portable"],La=1280,fT=820,gT=390,hT=1920,yT=320;function bT(e){const t=[],n=new Set,o=r=>{if(!r||typeof r!="object")return;const s=r.responsive;if(s&&typeof s=="object")for(const a of Object.keys(s)){n.has(a)||(n.add(a),t.push(a));const l=s[a]?.children;if(Array.isArray(l))for(const d of l)o(d)}if(Array.isArray(r.children))for(const a of r.children)o(a)};if(Array.isArray(e))for(const r of e)o(r);return t}function mT(e){const t=[...Qf],n=new Set(Qf);for(const o of bT(e))!n.has(o)&&!t.includes(o)&&t.push(o);return t}function vT(e,t){const n=e?.responsive;if(!n||typeof n!="object")return[];const o=[];for(const r of Object.keys(n)){const s=n[r];!s||!Array.isArray(s.children)||r!==t&&o.push(r)}return o}function _T(e){switch(e){case"desktop":return La;case"tablet":return fT;case"mobile":return gT}const t=e in Do?Do[e]:Mn.parseRange(e);return!t||!Number.isFinite(t.max)?La:Math.round(Math.max(yT,Math.min(hT,t.max)))}function Rf(){const t=Ft()?.document?.raw?.components;return u.useMemo(()=>mT(t??null),[t])}const xT={desktop:"layout_editor.property_modal.scope.device_desktop",tablet:"layout_editor.property_modal.scope.device_tablet",mobile:"layout_editor.property_modal.scope.device_mobile",portable:"layout_editor.property_modal.scope.device_portable"},wT=[{key:"base",labelKey:"layout_editor.property_modal.scope.scheme_light"},{key:"dark",labelKey:"layout_editor.property_modal.scope.scheme_dark"}];function eg({scope:e,onChange:t,node:n,t:o,showColorScheme:r=!0,onClearScope:s}){const[a,l]=u.useState(!1),[d,c]=u.useState(""),[p,f]=u.useState(""),[b,g]=u.useState(null),m=Rf(),h=u.useMemo(()=>{const w=["base",...m],k=new Set(w);for(const x of Object.keys(n.responsive??{}))x!=="base"&&!k.has(x)&&(k.add(x),w.push(x));return e.breakpoint!=="base"&&!k.has(e.breakpoint)&&w.push(e.breakpoint),w},[m,n.responsive,e.breakpoint]),v=w=>t({...e,colorScheme:w}),y=w=>t({...e,breakpoint:w}),_=()=>{const w=d.trim(),k=p.trim();if(w===""&&k===""){g(o("layout_editor.property_modal.scope.custom_invalid"));return}const x=`${w}-${k}`;if(!xC(x)){g(o("layout_editor.property_modal.scope.custom_invalid"));return}g(null),l(!1),c(""),f(""),y(x)};return i.jsxs("div",{className:"g7le-style-scope-tabs","data-testid":"g7le-style-scope-tabs",style:kT,children:[r&&i.jsx("div",{className:"g7le-style-scope-scheme","data-testid":"g7le-style-scope-scheme",role:"tablist","aria-label":o("layout_editor.property_modal.scope.scheme_light"),style:tg,children:wT.map(w=>{const k=e.colorScheme===w.key;return i.jsxs("button",{type:"button",role:"tab","aria-selected":k,"data-testid":`g7le-style-scheme-${w.key}`,"data-active":k?"true":"false",onClick:()=>v(w.key),style:ng(k),children:[o(w.labelKey),w.key==="dark"&&Js(n,{colorScheme:"dark",breakpoint:e.breakpoint})&&i.jsx("span",{"data-testid":"g7le-style-scheme-dark-dot",style:og})]},w.key)})}),i.jsxs("div",{className:"g7le-style-scope-device","data-testid":"g7le-style-scope-device",role:"tablist","aria-label":o("layout_editor.property_modal.scope.device_base"),style:tg,children:[h.map(w=>{const k=e.breakpoint===w,x=w==="base"||$u(w),$=w==="base"?"layout_editor.property_modal.scope.device_base":xT[w],S=$?o($):w,C=x?`g7le-style-bp-${w}`:`g7le-style-bp-custom-${w}`;return i.jsxs("button",{type:"button",role:"tab","aria-selected":k,"data-testid":C,"data-active":k?"true":"false",onClick:()=>y(w),style:ng(k),children:[S,Js(n,{colorScheme:"base",breakpoint:w})&&i.jsx("span",{"data-testid":`${C}-dot`,style:og})]},String(w))}),i.jsxs("button",{type:"button","data-testid":"g7le-style-bp-add-custom",onClick:()=>l(w=>!w),style:$T,children:["+ ",o("layout_editor.property_modal.scope.add_custom")]})]}),a&&i.jsxs("div",{className:"g7le-style-scope-custom-input","data-testid":"g7le-style-scope-custom-input",style:ST,children:[i.jsx("input",{type:"number","data-testid":"g7le-style-custom-min",placeholder:o("layout_editor.property_modal.scope.custom_min"),value:d,onChange:w=>c(w.target.value),style:rg}),i.jsx("span",{style:{color:"#94a3b8"},children:"—"}),i.jsx("input",{type:"number","data-testid":"g7le-style-custom-max",placeholder:o("layout_editor.property_modal.scope.custom_max"),value:p,onChange:w=>f(w.target.value),style:rg}),i.jsx("button",{type:"button","data-testid":"g7le-style-custom-confirm",onClick:_,style:ET,children:o("layout_editor.property_modal.scope.add_custom")})]}),b&&i.jsx("div",{"data-testid":"g7le-style-custom-error",style:IT,children:b}),e.breakpoint!=="base"&&!$u(e.breakpoint)&&i.jsx("div",{"data-testid":"g7le-style-custom-priority-note",style:AT,children:o("layout_editor.property_modal.scope.custom_priority_note")}),s&&Js(n,e)&&i.jsxs("button",{type:"button","data-testid":"g7le-style-scope-reset",onClick:s,style:CT,children:["↺ ",o("layout_editor.property_modal.scope.reset_to_base")]})]})}const kT={display:"flex",flexDirection:"column",gap:0,padding:"0 0 8px",borderBottom:"1px solid #e2e8f0",marginBottom:8},ST={display:"flex",gap:6,alignItems:"center",marginTop:4},tg={display:"flex",gap:2,flexWrap:"wrap",borderBottom:"1px solid #e2e8f0",margin:"0 -16px",padding:"0 12px"};function ng(e){return{display:"inline-flex",alignItems:"center",gap:4,padding:"6px 12px",fontSize:12,border:"none",borderBottom:`2px solid ${e?"#2563eb":"transparent"}`,background:"transparent",color:e?"#2563eb":"#64748b",cursor:"pointer",fontWeight:e?700:500,marginBottom:-1}}const og={display:"inline-block",width:5,height:5,borderRadius:3,background:"#f59e0b"},CT={alignSelf:"flex-start",marginTop:2,padding:"3px 8px",fontSize:10,border:"1px solid #cbd5e1",borderRadius:6,background:"#f8fafc",color:"#475569",cursor:"pointer"},$T={padding:"6px 12px",fontSize:11,border:"none",borderBottom:"2px dashed #cbd5e1",background:"transparent",color:"#475569",cursor:"pointer",marginBottom:-1},rg={width:70,padding:"4px 6px",fontSize:11,border:"1px solid #cbd5e1",borderRadius:6},ET={padding:"4px 10px",fontSize:11,border:"none",borderRadius:6,background:"#2563eb",color:"#fff",cursor:"pointer"},IT={fontSize:11,color:"#dc2626"},AT={fontSize:10,color:"#94a3b8"},ig=new Map;function Pn(e,t){ig.set(e,t)}function TT(e){return e?ig.get(e)??null:null}const Pa={id:{label:"$t:layout_editor.core_props.id.label",widget:"core-id",placeholder:"$t:layout_editor.core_props.id.placeholder",apply:{type:"propValue",propKey:"id"}},dataKey:{label:"$t:layout_editor.core_props.dataKey.label",widget:"core-datakey",placeholder:"$t:layout_editor.core_props.dataKey.placeholder",apply:{type:"nodeKey",nodeKey:"dataKey"}},isolatedState:{label:"$t:layout_editor.core_props.isolatedState.label",widget:"toggle",apply:{type:"nodeKey",nodeKey:"isolatedState"}},isolatedScopeId:{label:"$t:layout_editor.core_props.isolatedScopeId.label",widget:"core-scopeid",placeholder:"$t:layout_editor.core_props.isolatedScopeId.placeholder",apply:{type:"nodeKey",nodeKey:"isolatedScopeId"}}},DT=["id"];function zT(e){return e===!1?[]:Array.isArray(e)?e.filter(t=>t in Pa):[...DT]}function sg(e){const t=e.props?.id??e.id;return typeof t=="string"&&t.length>0?`${t}-scope`:"scope"}function NT({node:e,onPatchNode:t,t:n,scopeIdCandidates:o=[],usedScopeIds:r=[]}){const s=e,a="isolatedState"in s&&s.isolatedState!=null,l=typeof s.isolatedScopeId=="string"?s.isolatedScopeId:"",[d,c]=u.useState(""),p=()=>{const h={...e};a?(delete h.isolatedState,delete h.isolatedScopeId):(h.isolatedState={},h.isolatedScopeId=sg(e)),t(h)},f=h=>{const v={...e};v.isolatedScopeId=h.trim()===""?sg(e):h,t(v)},b=h=>{const v={...e};v.isolatedState=h,t(v)},g=u.useMemo(()=>{const h=d.trim().toLowerCase();return o.filter(v=>!h||v.toLowerCase().includes(h))},[o,d]),m=l!==""&&r.includes(l);return i.jsxs("div",{className:"g7le-isolated-scope","data-testid":"g7le-isolated-scope-control",style:MT,children:[i.jsx("div",{style:LT,children:n("layout_editor.core_props.isolatedState.label")}),i.jsxs("div",{style:PT,children:[i.jsx("button",{type:"button","data-testid":"g7le-isolated-toggle",role:"switch","aria-checked":a,onClick:p,style:OT(a),children:n(a?"layout_editor.overlay.on":"layout_editor.overlay.off")}),a?null:i.jsx("span",{"data-testid":"g7le-isolated-off-hint",style:BT,children:n("layout_editor.core_props.isolatedState.off_hint")})]}),a?i.jsxs(i.Fragment,{children:[i.jsxs("div",{style:ag,children:[i.jsx("label",{style:lg,children:n("layout_editor.core_props.isolatedScopeId.label")}),i.jsx("input",{type:"text","data-testid":"g7le-isolated-scopeid-input",value:l,placeholder:n("layout_editor.core_props.isolatedScopeId.placeholder"),onChange:h=>f(h.target.value),style:WT}),g.length>0?i.jsxs("div",{"data-testid":"g7le-isolated-scopeid-picker",style:jT,children:[i.jsx("input",{type:"text","data-testid":"g7le-isolated-scopeid-search",value:d,placeholder:n("layout_editor.core_props.isolatedScopeId.search"),onChange:h=>c(h.target.value),style:HT}),i.jsx("div",{style:FT,children:g.map(h=>i.jsx("button",{type:"button","data-testid":`g7le-isolated-scopeid-candidate-${h}`,onClick:()=>{f(h),c("")},style:KT,children:h},h))})]}):null,m?i.jsxs("p",{"data-testid":"g7le-isolated-scopeid-dup",style:VT,children:["ⓘ ",n("layout_editor.core_props.isolatedScopeId.duplicate")]}):null]}),i.jsxs("div",{style:ag,children:[i.jsx("label",{style:lg,children:n("layout_editor.core_props.isolatedState.initial_label")}),i.jsx("div",{"data-testid":"g7le-isolated-initial-value",children:i.jsx(zn,{value:s.isolatedState??{},onChange:b,t:n,path:"isolatedState",scope:"isolated-node"})})]})]}):null]})}const MT={display:"flex",flexDirection:"column",gap:8,padding:10,border:"1px solid #e2e8f0",borderRadius:8,minWidth:0},LT={fontSize:12,fontWeight:700,color:"#0f172a"},PT={display:"flex",gap:8,alignItems:"center"},OT=e=>({padding:"4px 12px",fontSize:11,border:`1px solid ${e?"#3b82f6":"#cbd5e1"}`,borderRadius:14,background:e?"#eff6ff":"#fff",color:e?"#1d4ed8":"#64748b",cursor:"pointer"}),BT={fontSize:11,color:"#94a3b8"},ag={display:"flex",flexDirection:"column",gap:4,minWidth:0},lg={fontSize:11,color:"#64748b"},WT={padding:"5px 8px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,boxSizing:"border-box",width:"100%"},jT={border:"1px solid #e2e8f0",borderRadius:6,padding:4,background:"#fff"},HT={width:"100%",padding:"4px 6px",fontSize:11,border:"1px solid #cbd5e1",borderRadius:6,marginBottom:4,boxSizing:"border-box"},FT={display:"flex",flexDirection:"column",maxHeight:120,overflowY:"auto"},KT={textAlign:"left",padding:"4px 6px",fontSize:12,border:"none",background:"transparent",cursor:"pointer",borderRadius:4,color:"#0f172a"},VT={margin:0,fontSize:11,color:"#64748b"},UT={borderTop:"2px solid #c7d2fe",background:"#eef2ff",padding:"10px 12px",display:"flex",flexDirection:"column",gap:8},dg={fontSize:13,fontWeight:700,color:"#3730a3",display:"flex",alignItems:"center",gap:6},qT={fontSize:11,fontWeight:600,color:"#4338ca",background:"#e0e7ff",border:"1px solid #c7d2fe",borderRadius:4,padding:"1px 6px"},Oa={fontSize:11,color:"#6366f1"},GT={alignSelf:"flex-start",fontSize:12,fontWeight:600,color:"#fff",background:"#4f46e5",border:"none",borderRadius:4,padding:"4px 10px",cursor:"pointer"},YT={opacity:.5,cursor:"not-allowed"},XT={fontSize:11,color:"#dc2626"};function JT({entry:e,t,onSave:n}){const[o,r]=u.useState(()=>e.props),[s,a]=u.useState(!0),[l,d]=u.useState(null),[c,p]=u.useState(!1),[f,b]=u.useState(!1),g=e.extensionName??e.extensionIdentifier??`#${e.extensionId}`,m=async()=>{if(!s){d(t("layout_editor.property_modal.injected_props.invalid_json"));return}d(null),p(!0),b(!1);try{await n(o),b(!0)}catch(h){d(h instanceof Error?h.message:String(h))}finally{p(!1)}};return i.jsxs("div",{"data-testid":`g7le-injected-props-entry-${e.extensionId}`,style:{display:"flex",flexDirection:"column",gap:6},children:[i.jsx("div",{style:dg,children:i.jsx("span",{style:qT,"data-testid":"g7le-injected-props-origin-badge",children:t("layout_editor.property_modal.injected_props.origin",{source:g})})}),i.jsx(Hs,{value:o,shape:"object",emptyValue:{},testidPrefix:"g7le-injected-props-json",minHeight:80,invalidErrorKey:"layout_editor.property_modal.injected_props.invalid_json",shapeErrorKey:"layout_editor.property_modal.injected_props.invalid_json",t,onChange:h=>{r(h??{}),b(!1)},onValidityChange:h=>{a(h),h&&d(null),b(!1)}}),l&&i.jsx("div",{style:XT,"data-testid":"g7le-injected-props-error",children:l}),f&&i.jsx("div",{style:{...Oa,color:"#16a34a"},"data-testid":"g7le-injected-props-saved",children:t("layout_editor.property_modal.injected_props.saved")}),i.jsx("div",{style:Oa,children:t("layout_editor.property_modal.injected_props.save_target")}),i.jsx("button",{type:"button","data-testid":"g7le-injected-props-save",style:{...GT,...c||!s?YT:null},disabled:c||!s,onClick:m,children:c?t("layout_editor.property_modal.injected_props.saving"):t("layout_editor.property_modal.injected_props.save_button",{source:g})})]})}function ZT({injectedProps:e,t,onSaveInjectedProps:n}){return!Array.isArray(e)||e.length===0?null:i.jsxs("div",{style:UT,"data-testid":"g7le-injected-props-section",children:[i.jsxs("div",{style:dg,children:["🧩 ",t("layout_editor.property_modal.injected_props.title")]}),i.jsx("div",{style:Oa,children:t("layout_editor.property_modal.injected_props.help")}),e.map(o=>i.jsx(JT,{entry:o,t,onSave:r=>n(o.extensionId,r)},o.extensionId))]})}function QT(e,t){const n=e?.components??{};for(const o of Object.values(n)){const r=o?.find(s=>s.name===t);if(r)return r}return null}function RT({node:e,spec:t,manifest:n,t:o,onPatchNode:r,onClose:s,onDelete:a,permissionCandidates:l,pageCandidates:d,dataSourceCandidates:c,stateKeyCandidates:p,bindingCandidates:f,liveElement:b,scope:g=Wt,onScopeChange:m,templateIdentifier:h,initialTab:v,onSaveInjectedProps:y,branchLabel:_,isolatedScopeIdCandidates:w,usedScopeIds:k}){const x=e.__injectedProps,$=Array.isArray(x)&&x.length>0&&!!y,S=ve=>{m?.(ve)},C=()=>{r(IC(e,g))},T=typeof e.name=="string"?e.name:"",E=ji(t,T),N=QT(n,T)?.settings,D=!!(N?.groups&&N.groups.length>0),B=Array.isArray(E?.propControls)?E.propControls:[],M=zT(E?.coreProps),O=new Set(M.map(ve=>Pa[ve].apply?.propKey)),z=B.filter(ve=>{const We=Ct(t,ve)?.apply?.propKey;return!(We!==void 0&&O.has(We))}),H=typeof E?.nodeEditor?.kind=="string"?E.nodeEditor.kind:void 0,P=E?.nodeEditor?.params,I=H?TT(H):null,L=!!I,F=Array.isArray(E?.dataProps)?E.dataProps:[],Y=F.length>0,K=!!(e.iteration&&typeof e.iteration=="object"),X=oT(e,E),Z=M.length>0||z.length>0||L||Y||X||K,se=Array.isArray(E?.styleControls)?E.styleControls:[],oe=Array.isArray(E?.advanced)?E.advanced:[],ee=(Array.isArray(E?.events)?E.events:[]).length>0,ge=Jf(E),V=se.length>0||ge!==null,be=!(E?.visibilityCondition===!1)&&(Z||V||ee||D||oe.length>0),Ee=u.useMemo(()=>{const ve=[];return D&&ve.push("settings"),Z&&ve.push("props"),V&&ve.push("style"),ee&&ve.push("action"),be&&ve.push("visibility"),ve.push("translation"),oe.length>0&&ve.push("advanced"),ve},[D,Z,V,ee,be,oe.length]),De=z.length>0||L||Y||K,[Se,Ue]=u.useState(v&&Ee.includes(v)?v:D?"settings":De?"props":V?"style":Z?"props":Ee[0]??"style"),Me=qA(e),Oe=!Z&&!V&&!D&&!ee&&!be&&oe.length===0,Ye=E?.label?E.label.startsWith("$t:")?o(E.label.slice(3)):E.label:T||o("layout_editor.property_modal.element"),ze=(ve,qe)=>{const We={...e.props??{}};qe===void 0?delete We[ve]:We[ve]=qe,r({...e,props:We})},Xe={settings:o("layout_editor.property_modal.tab.settings"),props:o("layout_editor.property_modal.tab.props"),style:o("layout_editor.property_modal.tab.style"),action:o("layout_editor.property_modal.tab.action"),visibility:o("layout_editor.property_modal.tab.visibility"),translation:o("layout_editor.property_modal.tab.translation"),advanced:o("layout_editor.property_modal.tab.advanced")};return i.jsxs("div",{className:"g7le-property-modal","data-testid":"g7le-property-modal",style:e4,children:[i.jsxs("div",{"data-modal-drag-handle":!0,"data-testid":"g7le-property-modal-header",style:t4,children:[i.jsx("span",{"data-testid":"g7le-property-modal-title",style:{fontSize:14,fontWeight:700},children:D&&Se==="settings"?o("layout_editor.property_modal.settings_title",{name:Ye}):o("layout_editor.property_modal.title",{name:Ye})}),i.jsx("button",{type:"button","data-testid":"g7le-property-modal-close","aria-label":"close",onClick:s,style:n4,children:"✕"})]}),Me&&i.jsx("div",{"data-testid":"g7le-property-modal-advanced-badge",style:o4,children:o("layout_editor.property_modal.advanced_badge")}),_&&i.jsx("div",{"data-testid":"g7le-property-modal-branch-badge",style:r4,children:o("layout_editor.property_modal.branch_badge",{device:_})}),i.jsx("div",{style:i4,role:"tablist",children:Ee.map(ve=>i.jsx("button",{type:"button",role:"tab","aria-selected":Se===ve,"data-testid":`g7le-property-tab-${ve}`,"data-active":Se===ve?"true":"false",onClick:()=>Ue(ve),style:{...s4,color:Se===ve?"#2563eb":"#64748b",borderBottomColor:Se===ve?"#2563eb":"transparent"},children:Xe[ve]},ve))}),i.jsxs("div",{style:a4,"data-testid":"g7le-property-modal-body",children:[Oe&&!$&&i.jsx("div",{"data-testid":"g7le-property-modal-no-editable",style:ug,children:o("layout_editor.property_modal.no_editable_props")}),$&&i.jsx(ZT,{injectedProps:x,t:o,onSaveInjectedProps:y}),Se==="settings"&&N&&i.jsx(MA,{spec:N,node:e,t:o,onPatchProp:ze}),Se==="props"&&i.jsxs("div",{"data-testid":"g7le-property-props-tab",style:{display:"flex",flexDirection:"column"},children:[K&&i.jsx(L6,{node:e,candidates:f??[],t:o,onPatchNode:r}),Y&&i.jsx(d6,{node:e,dataProps:F,candidates:f??[],t:o,onPatchNode:r}),X&&i.jsx(T6,{node:e,candidates:f??[],t:o,onPatchNode:r,templateIdentifier:h}),L&&I&&i.jsx(I,{node:e,params:P,spec:t,manifest:n,t:o,onPatchNode:r,templateIdentifier:h,candidates:f}),M.map(ve=>i.jsx(hi,{controlKey:`core-${ve}`,control:Pa[ve],node:e,t:o,onPatch:r,scope:Wt,bindingCandidates:f},`core-${ve}`)),z.map(ve=>{const qe=Ct(t,ve);return qe?i.jsx(hi,{controlKey:ve,control:qe,node:e,t:o,onPatch:r,scope:Wt,bindingCandidates:f},ve):null}),i.jsx(NT,{node:e,onPatchNode:r,t:o,scopeIdCandidates:w,usedScopeIds:k})]}),Se==="style"&&i.jsxs("div",{"data-testid":"g7le-property-style-tab",style:{display:"flex",flexDirection:"column"},children:[i.jsx("div",{style:cg,children:i.jsx(eg,{scope:g,onChange:S,node:e,t:o,onClearScope:C})}),ge!==null&&i.jsx(dT,{node:e,spec:t,capability:E,t:o,onPatchNode:r,liveElement:b,scope:g}),se.map(ve=>{const qe=Ct(t,ve);return qe?i.jsx(hi,{controlKey:ve,control:qe,node:e,t:o,onPatch:r,scope:g},ve):null})]}),Se==="action"&&i.jsx(i6,{node:e,spec:t,capability:E,t:o,onPatchNode:r,pageCandidates:d,dataSourceCandidates:c,stateKeyCandidates:p,bindingCandidates:f}),Se==="visibility"&&i.jsxs("div",{"data-testid":"g7le-property-visibility-tab",style:{display:"flex",flexDirection:"column"},children:[i.jsx("div",{style:cg,children:i.jsx(eg,{scope:g,onChange:S,node:e,t:o,showColorScheme:!1})}),i.jsx(Du,{node:e,spec:t,t:o,onPatchNode:r,dataSourceCandidates:c,stateKeyCandidates:p,scope:g})]}),Se==="translation"&&(h?i.jsx(Xd,{customKey:Yd(e),templateIdentifier:h,t:o,paramLabels:wv(e),onRemoveParam:ve=>{const qe=typeof e.text=="string"?e.text:"";r({...e,text:vo(qe,ve)});const We=Yd(e);h&&We&&$r(h,We,ve)}}):i.jsx("div",{"data-testid":"g7le-property-translation-deferred",style:ug,children:o("layout_editor.translation.not_a_key")})),Se==="advanced"&&i.jsx(YA,{node:e,advanced:oe,t:o,onPatch:r,permissionCandidates:l})]}),i.jsxs("div",{style:l4,children:[i.jsx("span",{"data-testid":"g7le-property-modal-node-id",style:{fontSize:11,color:"#94a3b8"},children:typeof e.id=="string"||typeof e.id=="number"?`ID: ${e.id}`:""}),i.jsxs("div",{style:{display:"flex",gap:8},children:[i.jsx("button",{type:"button","data-testid":"g7le-property-modal-delete",onClick:a,style:d4,children:o("layout_editor.property_modal.delete_element")}),i.jsx("button",{type:"button","data-testid":"g7le-property-modal-done",onClick:s,style:c4,children:o("layout_editor.property_modal.close")})]})]})]})}const e4={display:"flex",flexDirection:"column",width:420,maxWidth:"92vw"},t4={display:"flex",alignItems:"center",justifyContent:"space-between",padding:"12px 16px",borderBottom:"1px solid #e2e8f0",cursor:"move",userSelect:"none"},n4={border:"none",background:"transparent",fontSize:14,cursor:"pointer",color:"#64748b"},o4={margin:"8px 16px 0",padding:"6px 10px",fontSize:11,background:"#fef3c7",color:"#92400e",borderRadius:6},r4={margin:"8px 16px 0",padding:"6px 10px",fontSize:11,background:"#dbeafe",color:"#1e40af",borderRadius:6},i4={display:"flex",gap:2,padding:"0 12px",borderBottom:"1px solid #e2e8f0",overflowX:"auto"},s4={padding:"8px 10px",fontSize:12,border:"none",borderBottom:"2px solid transparent",background:"transparent",cursor:"pointer",whiteSpace:"nowrap"},a4={padding:16,overflowY:"auto",maxHeight:"60vh"},cg={position:"sticky",top:-16,zIndex:2,background:"#fff",margin:"-16px -16px 0",padding:"6px 16px 0"},ug={fontSize:12,color:"#94a3b8",padding:"16px 0",textAlign:"center"},l4={display:"flex",alignItems:"center",justifyContent:"space-between",padding:"12px 16px",borderTop:"1px solid #e2e8f0"},d4={padding:"6px 12px",fontSize:12,border:"1px solid #fecaca",borderRadius:6,background:"#fff",color:"#dc2626",cursor:"pointer"},c4={padding:"6px 12px",fontSize:12,border:"none",borderRadius:6,background:"#2563eb",color:"#fff",cursor:"pointer"},Ba="g7le:component-target-picked",Wa="g7le:component-target-pick-request";let u4=1;function p4({value:e,onChange:t,t:n,control:o}){const r=bo(),s=e==null?"":String(e),a=o?.allowManualInput!==!1,l=u.useCallback(()=>{if(typeof window>"u")return;const d=`pick-${u4++}`,p=r.stack[r.stack.length-1]?.id;p&&r.minimize(p,n("layout_editor.target_picker.minimized_hint"));const f=b=>{const g=b.detail;g?.requestId===d&&(window.removeEventListener(Ba,f),p&&r.restore(p),!g.cancelled&&typeof g.id=="string"&&t(g.id))};window.addEventListener(Ba,f),window.dispatchEvent(new CustomEvent(Wa,{detail:{requestId:d}}))},[r,t,n]);return i.jsx("div",{className:"g7le-component-target-picker","data-testid":"g7le-component-target-picker",children:i.jsxs("div",{style:{display:"flex",gap:6,alignItems:"center"},children:[a?i.jsx("input",{type:"text",className:"g7le-widget g7le-widget--text","data-testid":"g7le-component-target-input",value:s,placeholder:n("layout_editor.target_picker.id_placeholder"),onChange:d=>t(d.target.value===""?void 0:d.target.value),style:{flex:1,minWidth:0,padding:"6px 8px",border:"1px solid #cbd5e1",borderRadius:6,fontSize:13}}):i.jsx("span",{"data-testid":"g7le-component-target-value",style:{flex:1,minWidth:0},children:s||n("layout_editor.target_picker.none")}),i.jsxs("button",{type:"button","data-testid":"g7le-component-target-pick",onClick:l,style:{display:"flex",alignItems:"center",gap:4,padding:"6px 10px",background:"#1e293b",color:"#fff",border:"none",borderRadius:6,cursor:"pointer",fontSize:13,whiteSpace:"nowrap"},children:[i.jsx("span",{"aria-hidden":"true",children:"🎯"}),n("layout_editor.target_picker.select_area")]})]})})}async function f4(e,t,n,o,r=fetch){const s=`/api/admin/templates/${encodeURIComponent(e)}/layout-extensions/${t}`;let a;try{a=await r(s,{headers:Ne(),credentials:"same-origin"})}catch(y){return{kind:"error",message:y instanceof Error?y.message:String(y)}}if(a.status===404)return{kind:"not_found"};if(!a.ok)return{kind:"error",message:`HTTP ${a.status}`};const l=await a.json().catch(()=>null),d=(l&&(l.data||l))??{};let c={};const p=d.content;if(typeof p=="string")try{c=JSON.parse(p)}catch{return{kind:"error",message:"invalid extension content"}}else p&&typeof p=="object"&&(c=p);const f=typeof d.lock_version=="number"?d.lock_version:0,b=Array.isArray(c.injections)?[...c.injections]:[],g=b.findIndex(y=>y&&y.position==="inject_props"&&y.target_id===n);if(g<0)return{kind:"injection_not_found"};b[g]={...b[g],props:o};const m={...c,injections:b};let h;try{h=await r(s,{method:"PUT",headers:Ne({"Content-Type":"application/json"}),credentials:"same-origin",body:JSON.stringify({content:m,expected_lock_version:f})})}catch(y){return{kind:"error",message:y instanceof Error?y.message:String(y)}}const v=await h.json().catch(()=>null);return h.ok?{kind:"success",newLockVersion:typeof v?.data?.lock_version=="number"?v.data.lock_version:f+1}:h.status===409?{kind:"conflict",currentVersion:v?.current_version??-1,yourVersion:v?.your_version??f}:{kind:"error",message:v?.message??`HTTP ${h.status}`}}const pg=new Map;function ja(e,t){pg.set(e,t)}function g4(e){return e?pg.get(e)??null:null}const fg="g7le.clipboard";function gg(){try{return typeof window>"u"||!window.sessionStorage?null:window.sessionStorage}catch{return null}}function h4(e){const t=JSON.parse(JSON.stringify(e)),n=o=>{for(const r of Object.keys(o))r.startsWith("__")&&delete o[r];Array.isArray(o.children)&&o.children.forEach(n)};return n(t),t}function hg(e,t=0){const n=gg();if(!n)return!1;try{const o={node:h4(e),copiedAt:t};return n.setItem(fg,JSON.stringify(o)),!0}catch{return!1}}function y4(){const e=gg();if(!e)return null;try{const t=e.getItem(fg);if(!t)return null;const n=JSON.parse(t);return!n||typeof n.node!="object"||n.node===null?null:JSON.parse(JSON.stringify(n.node))}catch{return null}}const Qo={nw:{width:-1,height:-1},n:{width:0,height:-1},ne:{width:1,height:-1},e:{width:1,height:0},se:{width:1,height:1},s:{width:0,height:1},sw:{width:-1,height:1},w:{width:-1,height:0}};function Ha(e){if(typeof e=="number")return e;if(typeof e=="string"){const t=e.match(/^(-?\d+(?:\.\d+)?)px$/);if(t)return Number(t[1])}return null}function b4(e,t){const n=Array.isArray(e.options)?e.options:[],o=Array.isArray(e.scale)?e.scale:[],r=n.length>0?n.map(l=>l.value):o;if(r.length===0)return;let s=r[0],a=1/0;return r.forEach((l,d)=>{const c=Ha(l)??d*40,p=Math.abs(c-t);p{if(!t)return;const m=Qo[b];if(m.width!==0&&!n||m.height!==0&&!o)return;const h=t.props?.style??{},v=s?.()??null,y=Ha(h.width)??(v?Math.round(v.width):0),_=Ha(h.height)??(v?Math.round(v.height):0);p.current={handle:b,startX:g.clientX,startY:g.clientY,startW:y,startH:_,node:t};const w=m.width!==0&&m.height!==0?"both":m.width!==0?"width":"height";d?.(w);let k=null;const x=S=>{const C=p.current;if(!C)return;const T=r||1,E=(S.clientX-C.startX)/T*Qo[C.handle].width,A=(S.clientY-C.startY)/T*Qo[C.handle].height;let N=C.node;if(Qo[C.handle].width!==0&&n){const D=yg(n,C.startW+E);N=fn(N,n,D)}if(Qo[C.handle].height!==0&&o){const D=yg(o,C.startH+A);N=fn(N,o,D)}k=N,a(N,w)},$=()=>{p.current=null,d?.(null),window.removeEventListener("pointermove",x),window.removeEventListener("pointerup",$),k&&l?.(k,w)};window.addEventListener("pointermove",x),window.addEventListener("pointerup",$)},[t,n,o,r,a,l,d]);return{enabledAxes:c,onHandlePointerDown:f}}const bg=320,mg=1920,_4=1024;function Fa(e){return Number.isFinite(e)?Math.round(Math.max(bg,Math.min(mg,e))):_4}function Ka(e={}){const{state:t,dispatch:n}=Qe(),{previewDevice:o,previewZoom:r,previewCustomWidth:s,previewColorScheme:a}=t,l=o==="custom"?Fa(s):_T(o),d=u.useMemo(()=>{const g=e.availableWidth??l,m=l*r;return r>=1||m<=g?r:Math.min(r,g/l)},[l,r,e.availableWidth]),c=u.useCallback(g=>{n({type:"SET_PREVIEW_DEVICE",device:g})},[n]),p=u.useCallback(g=>{const m=Math.max(.5,Math.min(1,g));n({type:"SET_PREVIEW_ZOOM",zoom:m})},[n]),f=u.useCallback(g=>{n({type:"SET_PREVIEW_CUSTOM_WIDTH",width:Fa(g)})},[n]),b=u.useCallback(g=>{n({type:"SET_PREVIEW_COLOR_SCHEME",scheme:g})},[n]);return{device:o,deviceWidth:l,zoom:r,scale:d,customWidth:Fa(s),setDevice:c,setZoom:p,setCustomWidth:f,colorScheme:a,setColorScheme:b}}const vi="g7le-dnd-handle-";function x4(e){if(typeof e!="string"||!e.startsWith(vi))return null;const t=e.slice(vi.length);return t.length>0?t:null}function w4(e){return typeof CSS<"u"&&typeof CSS.escape=="function"?CSS.escape(e):e.replace(/["\\\]]/g,"\\$&")}function k4(e,t,n,o){const r=o?.handleFallback??!1;if(e instanceof HTMLElement){const s=e.closest("[data-editor-path]");if(s)return s;const a=e.closest('[data-testid^="g7le-"]');if(a&&!(a.getAttribute("data-testid")??"").startsWith(vi))return null;if(r){const l=e.closest(`[data-testid^="${vi}"]`);if(l&&t){const d=x4(l.getAttribute("data-testid"));if(d){const c=t.querySelector(`[data-editor-path="${w4(d)}"]`);if(c)return c}}}}if(r&&n&&typeof document<"u"&&typeof document.elementsFromPoint=="function")for(const s of document.elementsFromPoint(n.x,n.y)){const a=s.closest?.("[data-editor-path]");if(a)return a}return null}function S4(e,t,n){if(typeof e=="string"&&e.trim().length>0)return{color:e};const o=typeof n?.color=="string"&&n.color.length>0,r=typeof t=="string"&&t.trim().length>0;return o||r?{}:{color:"#0f172a"}}const C4="#ffffff",$4="#0f172a";function E4(e){const t=e.trim(),n=t.match(/^rgba?\(\s*([\d.]+)[\s,]+([\d.]+)[\s,]+([\d.]+)/i);if(n)return[Number(n[1]),Number(n[2]),Number(n[3])];const o=t.replace(/^#/,"");return/^[0-9a-f]{3}$/i.test(o)?[parseInt(o[0]+o[0],16),parseInt(o[1]+o[1],16),parseInt(o[2]+o[2],16)]:/^[0-9a-f]{6}$/i.test(o)?[parseInt(o.slice(0,2),16),parseInt(o.slice(2,4),16),parseInt(o.slice(4,6),16)]:null}function I4(e){if(!e||/transparent/i.test(e))return!1;const t=e.trim().match(/^oklch\(\s*([\d.]+)(%?)/i);if(t)return(t[2]==="%"?Number(t[1])/100:Number(t[1]))>=.62;const n=E4(e);if(!n)return!1;const[o,r,s]=n;return .299*o+.587*r+.114*s>=180}function A4(e,t,n){const o=typeof n?.color=="string"?n.color:null;return I4(e??o??t)?$4:C4}function T4({box:e,initialValue:t,isCustomKey:n,t:o,onCommit:r,onCancel:s,mirrorClassName:a,mirrorStyle:l,nodeEffectiveColor:d,onOpenTranslations:c}){const p=u.useRef(null),f=u.useRef(!1),[b,g]=u.useState(t.trim().length>0),[m,h]=u.useState(null);u.useEffect(()=>{const k=p.current;if(k){k.textContent=t,k.focus();try{const x=window.document.createRange();x.selectNodeContents(k);const $=window.getSelection();$?.removeAllRanges(),$?.addRange(x)}catch{}f.current=!1}},[t]),u.useEffect(()=>{const k=p.current;if(!k||typeof window.getComputedStyle!="function"){h(null);return}try{h(window.getComputedStyle(k).color||null)}catch{h(null)}},[a,l]);const v=()=>(p.current?.textContent??"").trim(),y=()=>{f.current||(f.current=!0,r(v()))},_=()=>{f.current||(f.current=!0,s())},w=k=>{k.key==="Enter"&&!k.shiftKey?(k.preventDefault(),y()):k.key==="Escape"&&(k.preventDefault(),_())};return i.jsxs("div",{className:"g7le-inline-text-editor","data-testid":"g7le-inline-text-editor","data-custom-key":n?"true":"false",style:{position:"absolute",left:e.left,top:e.top,minWidth:Math.max(e.width,60),minHeight:e.height,pointerEvents:"auto",zIndex:1e4},children:[i.jsx("div",{ref:p,role:"textbox","aria-label":o("layout_editor.inline_edit.aria_label"),"aria-multiline":"false","data-testid":"g7le-inline-text-editable",className:a,contentEditable:!0,suppressContentEditableWarning:!0,spellCheck:!1,onInput:()=>g(v().length>0),onKeyDown:w,onBlur:y,style:{outline:"2px solid #2563eb",outlineOffset:1,borderRadius:3,background:A4(d,m,l),padding:"2px 4px",minHeight:e.height,whiteSpace:"pre-wrap",wordBreak:"break-word",boxShadow:"0 2px 8px rgba(37, 99, 235, 0.25)",...l??{},...S4(d,a,l)}}),i.jsxs("div",{className:"g7le-inline-text-editor__hint","data-testid":"g7le-inline-text-editor-hint",style:{marginTop:2,display:"inline-flex",alignItems:"center",gap:4,whiteSpace:"nowrap"},children:[c?i.jsxs("button",{type:"button","data-testid":"g7le-inline-text-editor-open-translations",onMouseDown:k=>k.preventDefault(),onClick:k=>{k.preventDefault(),k.stopPropagation(),c()},title:o("layout_editor.inline_edit.open_translations"),style:{padding:"2px 8px",fontSize:11,background:n?"#eff6ff":"#f0fdf4",color:n?"#1d4ed8":"#15803d",border:`1px solid ${n?"#bfdbfe":"#bbf7d0"}`,borderRadius:4,display:"inline-flex",alignItems:"center",gap:4,cursor:"pointer",whiteSpace:"nowrap"},children:[i.jsx("span",{"aria-hidden":"true",children:"🌐"}),i.jsx("span",{children:o(n?"layout_editor.inline_edit.existing_key_hint":"layout_editor.inline_edit.new_key_hint")}),i.jsxs("span",{"aria-hidden":"true",style:{opacity:.7},children:["· ",o("layout_editor.inline_edit.open_translations")," ›"]})]}):i.jsx("span",{style:{padding:"2px 6px",fontSize:11,background:n?"#eff6ff":"#f0fdf4",color:n?"#1d4ed8":"#15803d",border:`1px solid ${n?"#bfdbfe":"#bbf7d0"}`,borderRadius:4,display:"inline-flex",alignItems:"center",gap:4,whiteSpace:"nowrap"},children:n?i.jsxs(i.Fragment,{children:["⌐ ",o("layout_editor.inline_edit.existing_key_hint")]}):i.jsxs(i.Fragment,{children:["✎ ",o("layout_editor.inline_edit.new_key_hint")]})}),!b&&i.jsx("span",{"data-testid":"g7le-inline-text-editor-empty-warn",style:{color:"#b91c1c",marginLeft:4},children:o("layout_editor.inline_edit.empty_warning")})]})]})}function D4({box:e,node:t,customKey:n,initialChipValue:o,chipParamLabels:r,onKeyify:s,templateIdentifier:a,locale:l,t:d,candidates:c,onInsertBinding:p,onRemoveBinding:f,onCommit:b,onCancel:g}){const[m,h]=u.useState(null),v=u.useRef(""),[y,_]=u.useState(!1),w=u.useRef(!1),k=u.useRef(null),x=Ft(),$=u.useRef(()=>{}),[S,C]=u.useState(null),[T,E]=u.useState(!1),A=u.useRef(n??null);u.useEffect(()=>{n&&A.current!==n&&(A.current=n)},[n]);const N=u.useMemo(()=>{if(!n&&r)return r;const P={},I=vt(typeof t.text=="string"?t.text:"");for(const L of I?.params??[])P[L.name]=Vt(L.expression);return P},[t.text,n,r]);u.useEffect(()=>{let P=!1;if(!n){const I=o??"";return v.current=I,h(I),()=>{P=!0}}return(async()=>{const I=It(n,l);if(I!==void 0){v.current=I,h(I);return}const L=await cn(a,n);if(P)return;const F=L?.values?.[l]??"";v.current=F,h(F)})(),()=>{P=!0}},[a,n,l,o]);const D=async()=>{if(w.current)return;w.current=!0;const P=m??"";if(P===v.current){b();return}_(!0);let I=A.current;if(!I){if(!s){_(!1),w.current=!1;return}if(I=await s(P),_(!1),!I){w.current=!1;return}A.current=I,b();return}const L=await So(a,I,l,P);if(_(!1),L.kind==="error"){w.current=!1;return}x?.markDirty?.(),b()},B=()=>{w.current||(w.current=!0,g())},M=P=>{C(P)},O=async P=>{if(!(S===null||!p||T)){E(!0);try{const I=A.current;I&&m!==null&&m!==v.current&&(await So(a,I,l,m),v.current=m);const L=await p(S,P);L!==null&&(h(L),v.current=L,x?.markDirty?.())}finally{E(!1),C(null)}}},z=async P=>{if(!(!f||T)){E(!0);try{const I=A.current;I&&m!==null&&m!==v.current&&(await So(a,I,l,m),v.current=m);const L=await f(P);L!==null&&(h(L),v.current=L,x?.markDirty?.())}finally{E(!1)}}},H=P=>{P.key==="Enter"&&!P.shiftKey?(P.preventDefault(),D()):P.key==="Escape"&&(P.preventDefault(),B())};return $.current=()=>{D()},u.useEffect(()=>{const P=L=>{const F=k.current;if(!F)return;const Y=L.target;if(Y&&F.contains(Y))return;const K=Y;K&&K.closest&&K.closest('[data-testid="g7le-inline-toolbar"], .g7le-inline-toolbar')||$.current()},I=k.current?.ownerDocument??document;return I.addEventListener("pointerdown",P,!0),()=>I.removeEventListener("pointerdown",P,!0)},[]),i.jsxs("div",{ref:k,className:"g7le-inline-param-chip-editor","data-testid":"g7le-inline-param-chip-editor",onKeyDown:H,style:{position:"absolute",left:e.left,top:e.top,minWidth:Math.max(e.width,120),pointerEvents:"auto",zIndex:1e4,background:"#fff",border:"2px solid #2563eb",borderRadius:4,boxShadow:"0 2px 8px rgba(37,99,235,0.25)",padding:4},children:[m===null?i.jsx("div",{"data-testid":"g7le-inline-param-chip-loading",style:{fontSize:12,color:"#94a3b8",padding:4},children:d("layout_editor.translation.loading")}):i.jsx(xr,{value:m,onChange:h,t:d,paramLabels:N,testIdSuffix:"inline",onRequestInsert:p?M:void 0,onRemoveChip:f?P=>{z(P)}:void 0}),S!==null&&p&&i.jsx("div",{"data-testid":"g7le-inline-param-chip-insert-picker",style:{marginTop:4},children:i.jsx($t,{candidates:c??[],t:d,onSelect:P=>{O(P)},testIdSuffix:"inline-insert",defaultOpen:!0,floating:!1})}),i.jsxs("div",{style:{display:"flex",alignItems:"center",gap:6,marginTop:4},children:[i.jsx("button",{type:"button","data-testid":"g7le-inline-param-chip-save",disabled:y,onMouseDown:P=>P.preventDefault(),onClick:()=>{D()},style:{fontSize:11,border:"none",borderRadius:6,background:"#2563eb",color:"#fff",padding:"4px 12px",cursor:"pointer"},children:d(y?"layout_editor.translation.saving":"layout_editor.translation.save")}),i.jsx("span",{style:{fontSize:10,color:"#94a3b8"},children:d("layout_editor.translation.placeholder_locked")})]})]})}const z4=["fontWeight","fontBold","fontStyle","fontItalic","textDecoration","textUnderline","textAlign","fontSize","textColor"],N4={fontWeight:"B",fontBold:"B",fontStyle:"I",fontItalic:"I",textDecoration:"U",textUnderline:"U",textAlign:"≡",fontSize:"T",textColor:"A"},M4=[["fontWeight","fontBold"],["fontItalic","fontStyle"],["textUnderline","textDecoration"]];function vg({node:e,spec:t,t:n,onApplyControl:o,box:r,scope:s=Wt}){const a=typeof e.name=="string"?e.name:"",l=ji(t,a),d=Array.isArray(l?.styleControls)?l.styleControls:[];let c=z4.filter(p=>d.includes(p)&&Ct(t,p)!==null);for(const p of M4){const f=p.filter(b=>c.includes(b));if(f.length>1){const b=f[0];c=c.filter(g=>!p.includes(g)||g===b)}}return c.length===0?null:i.jsx("div",{className:"g7le-inline-toolbar","data-testid":"g7le-inline-toolbar",role:"toolbar","aria-label":n("layout_editor.inline_edit.toolbar_label"),style:{position:"absolute",left:r.left,top:r.top-40,display:"inline-flex",gap:2,padding:4,background:"#0f172a",borderRadius:8,boxShadow:"0 4px 12px rgba(15, 23, 42, 0.35)",pointerEvents:"auto",zIndex:10001},children:c.map(p=>i.jsx(P4,{controlKey:p,node:e,spec:t,t:n,scope:s,onApplyControl:o},p))})}const L4={fontWeight:"bold",fontBold:"bold",fontStyle:"italic",fontItalic:"italic",textDecoration:"underline",textUnderline:"underline"};function _g(e,t){const n=r=>(r.split(/[-\s]/).pop()??r)===t||r===t;return e.find(r=>{const s=typeof r.value=="string"?r.value:"",a=Array.isArray(r.apply?.tokens)?r.apply.tokens:[];return n(s)||a.some(n)})?.value}function P4({controlKey:e,node:t,spec:n,t:o,scope:r,onApplyControl:s}){const[a,l]=u.useState(!1),d=Ct(n,e);if(!d)return null;const c=d.options,p=Array.isArray(c),f=zo(t,d,r),b=L4[e],g=b&&Array.isArray(c)?_g(c,b):void 0,m=!b,h=g!==void 0?f.value===g:p&&f.matched&&f.value!==void 0&&f.value!=="",v=o(`layout_editor.inline_edit.format.${e}`),y=x=>typeof x.label=="string"?x.label.startsWith("$t:")?o(x.label.slice(3)):x.label:String(x.value??""),_=()=>{s(e,x=>{const $=zo(x,d,r),S=Array.isArray(c)?_g(c,b):void 0,C=S!==void 0&&$.value===S?void 0:S;return fn(x,d,C,r)})},w=x=>{s(e,$=>{const C=zo($,d,r).value===x?void 0:x;return fn($,d,C,r)}),l(!1)},k=x=>{if(x.preventDefault(),x.stopPropagation(),!m&&Array.isArray(c)){_();return}l($=>!$)};return i.jsxs("div",{style:{position:"relative",display:"inline-flex"},children:[i.jsxs("button",{type:"button","data-testid":`g7le-inline-format-${e}`,"data-active":h?"true":"false","aria-label":v,"aria-pressed":p?h:void 0,title:v,onMouseDown:x=>x.preventDefault(),onClick:k,style:{display:"inline-flex",alignItems:"center",gap:5,height:28,padding:"0 9px",fontSize:12,border:"none",borderRadius:5,cursor:"pointer",background:h?"#2563eb":"transparent",color:"#ffffff",whiteSpace:"nowrap"},children:[i.jsx("span",{"aria-hidden":"true",style:{fontWeight:e==="fontWeight"||e==="fontBold"?700:600,fontStyle:e==="fontStyle"||e==="fontItalic"?"italic":"normal",textDecoration:e==="textDecoration"||e==="textUnderline"?"underline":"none"},children:N4[e]}),i.jsx("span",{children:v}),m&&i.jsx("span",{"aria-hidden":"true",style:{fontSize:9,opacity:.8},children:"▾"})]}),m&&a&&i.jsx("div",{"data-testid":`g7le-inline-popover-${e}`,role:"listbox",onMouseDown:x=>x.preventDefault(),style:{position:"absolute",top:32,left:0,minWidth:140,maxHeight:240,overflowY:"auto",background:"#0f172a",border:"1px solid #334155",borderRadius:8,boxShadow:"0 8px 24px rgba(15,23,42,0.45)",padding:4,zIndex:10002},children:Array.isArray(c)&&c.length>0?c.map((x,$)=>{const S=f.value===x.value;return i.jsxs("button",{type:"button",role:"option","aria-selected":S,"data-testid":`g7le-inline-option-${e}-${String(x.value??$)}`,onMouseDown:C=>C.preventDefault(),onClick:C=>{C.preventDefault(),C.stopPropagation(),w(x.value)},style:{display:"flex",alignItems:"center",gap:8,width:"100%",padding:"6px 8px",fontSize:12,textAlign:"left",border:"none",borderRadius:5,cursor:"pointer",background:S?"#2563eb":"transparent",color:"#ffffff"},children:[typeof x.swatch=="string"&&i.jsx("span",{"aria-hidden":"true",style:{width:14,height:14,borderRadius:3,background:x.swatch,border:"1px solid rgba(255,255,255,0.3)"}}),i.jsx("span",{children:y(x)}),S&&i.jsx("span",{"aria-hidden":"true",style:{marginLeft:"auto"},children:"✓"})]},String(x.value??$))}):i.jsx(O4,{testid:`g7le-inline-freeinput-${e}`,placeholder:o(`layout_editor.inline_edit.prompt.${e}`),initial:typeof f.value=="string"?f.value:"",onSubmit:x=>{s(e,$=>fn($,d,x,r)),l(!1)}})})]})}function O4({testid:e,placeholder:t,initial:n,onSubmit:o}){const[r,s]=u.useState(n);return i.jsx("input",{type:"text","data-testid":e,value:r,placeholder:t,autoFocus:!0,onMouseDown:a=>a.stopPropagation(),onChange:a=>s(a.currentTarget.value),onKeyDown:a=>{a.key==="Enter"&&(a.preventDefault(),o(r.trim()))},style:{width:130,padding:"6px 8px",fontSize:12,border:"1px solid #334155",borderRadius:5,background:"#1e293b",color:"#ffffff"}})}function B4(e){const{frameEl:t,manifest:n,nesting:o,componentPalette:r,spec:s=null,permissionCandidates:a,resolveRouteMatch:l,onNavigateToDestination:d,onSessionAddedPathsChange:c,onEditProps:p}=e,{state:f,dispatch:b}=Qe(),{t:g}=tt(),m=Ka(),h=u.useCallback((W,J)=>{const R=window.G7Core;if(R&&typeof R.t=="function"){const le=R.t(W,J);if(le&&le!==W&&!le.startsWith("$t:"))return le}return g(W,J)},[g]),v=Ft(),y=L7(50),_=bo(),w=u.useRef([]),k=u.useMemo(()=>v?.document?.raw?.components??[],[v?.document?.raw?.components]),x=u.useMemo(()=>k.length===0?null:{children:k},[k]),$=u.useMemo(()=>{if(f.editMode!=="extension")return;const W=/^__extension__\/(\d+)/.exec(f.selectedRoute?.path??"");return W?Number(W[1]):void 0},[f.editMode,f.selectedRoute?.path]),S=u.useMemo(()=>{if(f.editMode==="iteration_item"){const W=v?.document?.iterationContext?.sourceIndexPath;return Array.isArray(W)&&W.length>0?W:null}if(f.editMode==="modal"){const W=v?.document?.modalContext?.editIndexPath;return Array.isArray(W)&&W.length>0?W:null}return null},[f.editMode,v?.document?.iterationContext?.sourceIndexPath,v?.document?.modalContext?.editIndexPath]),C=$7({rootNode:x,editMode:f.editMode,currentExtensionId:$,editableRootPath:S,pathParser:Re,resolveRouteMatch:l}),[T,E]=u.useState(null),[A,N]=u.useState(null),[D,B]=u.useState(null),M=u.useRef(null);M.current=D;const[O,z]=u.useState(null),H=u.useRef(null),P=u.useRef(null);P.current=C.selectedPath;const I=qb(),[L,F]=u.useState(null),Y=u.useCallback(W=>{if(!t)return;const J=Re(W);if(!J)return;const R={children:ze.current.components},le=Be(R,J),ye=I.classify(le);if(!ye.editable){Et({op:"inline_edit_blocked_binding",sourceState:ye.sourceState,componentPath:at(J),timestamp:Date.now()});return}const we=fo(t,W);if(!we)return;const Te=le?.props??{},ke=wg(t,W);F({path:J,domPath:W,box:we,initialValue:ye.displayValue,isCustomKey:ye.sourceState==="custom_key",isParamKey:ye.isParamKey===!0,customKey:ye.customKey,chipValue:ye.chipValue,chipParamLabels:ye.chipParamLabels,mirrorClassName:typeof Te.className=="string"?Te.className:void 0,mirrorStyle:Te.style??void 0,nodeEffectiveColor:ke}),Et({op:"inline_edit_enter",sourceState:ye.sourceState,componentPath:at(J),timestamp:Date.now()})},[t,I]),K=u.useCallback(()=>{if(!t){E(null),N(null);return}N(fo(t,C.selectedPath)),E(fo(t,C.hoverPath))},[t,C.selectedPath,C.hoverPath,k]);u.useEffect(()=>{if(K(),typeof window<"u"&&typeof window.requestAnimationFrame=="function"){const W=window.requestAnimationFrame(()=>K());return()=>window.cancelAnimationFrame(W)}},[K]),u.useEffect(()=>{if(t)return zp(K,t)},[t,K]);const X=u.useCallback((W,J)=>{if(typeof window>"u")return;window.dispatchEvent(new CustomEvent(Ba,{detail:J===null?{requestId:W,cancelled:!0}:{requestId:W,id:J}})),B(null),z(null);const R=H.current;R?C.handleSelect("",{dataset:{editorPath:R}}):C.clearSelection(),H.current=null},[C]);u.useEffect(()=>{if(typeof window>"u")return;const W=J=>{const R=J.detail;if(typeof R?.requestId!="string")return;const le=M.current;le&&le!==R.requestId&&X(le,null),H.current=P.current,B(R.requestId)};return window.addEventListener(Wa,W),()=>window.removeEventListener(Wa,W)},[X]),u.useEffect(()=>{if(!D)return;const W=J=>{J.key==="Escape"&&(J.preventDefault(),X(D,null))};return window.addEventListener("keydown",W,!0),()=>window.removeEventListener("keydown",W,!0)},[D,X]),u.useEffect(()=>{if(!t)return;const W=f.editMode==="iteration_item"||f.editMode==="modal",J=(_e,Ie)=>k4(_e,t,Ie?{x:Ie.clientX,y:Ie.clientY}:void 0,{handleFallback:W}),R=_e=>{const Ie=_e.dataset.editorPath??"",He=Ie?Re(Ie):null,Nt={children:ze.current.components},gt=He?Be(Nt,He):null,Mt=gt&&typeof gt.props?.id=="string"?gt.props.id:typeof gt?.id=="string"?gt.id:"";return Mt.startsWith("auto_")?"":Mt},le=_e=>{const Ie=J(_e.target,_e);if(!Ie)return;const He=M.current;if(He){_e.preventDefault(),_e.stopPropagation();const Nt=R(Ie);Nt&&X(He,Nt);return}_e.preventDefault(),_e.stopPropagation(),C.handleSelect(Ie.dataset.editorId??"",{currentTarget:Ie,dataset:Ie.dataset})},ye=_e=>{const Ie=J(_e.target,_e);if(M.current){if(!Ie){z(null);return}const He=fo(t,Ie.dataset.editorPath??null);if(!He){z(null);return}z({box:He,hasId:R(Ie)!==""});return}if(!Ie){C.handleHover(null,{});return}C.handleHover(Ie.dataset.editorId??"",{currentTarget:Ie,dataset:Ie.dataset})},we=()=>{if(M.current){z(null);return}C.handleHover(null,{})},Te=_e=>{const Ie=J(_e.target,_e);if(!Ie)return;const He=Ie.dataset.editorPath??"";He&&(_e.preventDefault(),_e.stopPropagation(),Y(He))},ke=t.parentElement??t;return ke.addEventListener("click",le,!0),ke.addEventListener("mousemove",ye,!0),ke.addEventListener("mouseleave",we,!0),ke.addEventListener("dblclick",Te,!0),()=>{ke.removeEventListener("click",le,!0),ke.removeEventListener("mousemove",ye,!0),ke.removeEventListener("mouseleave",we,!0),ke.removeEventListener("dblclick",Te,!0)}},[t,C,Y,f.editMode,X]);const Z=u.useMemo(()=>{const W=C.selectedPath;if(!W)return null;const J=W.match(/^(.*)\.children\.\d+$/);return J?J[1]:null},[C.selectedPath]),se=u.useCallback(()=>{Z&&C.handleSelect("",{dataset:{editorPath:Z}})},[Z,C]),oe=u.useMemo(()=>!t||!C.selectedPath?null:t.querySelector(`[data-editor-path="${an(C.selectedPath)}"]`),[t,C.selectedPath]),G=u.useMemo(()=>C.selectedPath?Re(C.selectedPath):null,[C.selectedPath]),ee=u.useMemo(()=>{if(!G)return null;const W=G.find(et);if(!W)return null;const{i18nKey:J,raw:R}=SC(W.responsive);return J?g(J):g("layout_editor.overlay.branch_custom",{range:R})},[G,g]),ge=u.useMemo(()=>C.selectedPath?C.selectedIsIteration&&C.selectedIterationSourcePath?C.selectedIterationSourcePath:C.selectedPath:null,[C.selectedPath,C.selectedIsIteration,C.selectedIterationSourcePath]),V=u.useMemo(()=>ge?Re(ge):null,[ge]),te=u.useMemo(()=>!t||!ge?null:t.querySelector(`[data-editor-path="${an(ge)}"]`),[t,ge]),be=u.useMemo(()=>{if(te?.parentElement)return te.parentElement;if(!t||!ge)return null;const W=ge.match(/^(.*)\.children\.\d+$/);return W?t.querySelector(`[data-editor-path="${an(W[1])}"]`):null},[te,t,ge]),Ee=u.useMemo(()=>{const W=C.selectedNode;return W&&(typeof W.name=="string"?W.name:typeof W.type=="string"?W.type:"")||null},[C.selectedNode]),De=u.useMemo(()=>{if(C.selectedLockKind!=="extension")return null;const W=C.selectedNode?.__source;if(!W||W.kind!=="extension")return null;const J=W.extensionName?.trim(),R=W.extensionIdentifier?.trim();return J&&R?`${J} (${R})`:J||R||W.extensionSourceType||null},[C.selectedLockKind,C.selectedNode]),Se=z7({parentEl:be,selectedPath:V,selectedEl:te}),Ue=u.useMemo(()=>!t||!ge||ge===C.selectedPath?A:fo(t,ge),[t,ge,A,C.selectedPath,k]),Me=u.useRef(null),Oe=u.useCallback(()=>{const W=Me.current;W&&(_.close(W),Me.current=null)},[_]),Ye=u.useRef(()=>{}),ze=u.useRef({manifest:n,nesting:o,componentPalette:r,components:k});ze.current={manifest:n,nesting:o,componentPalette:r,components:k};const Xe=u.useCallback(W=>{const J=W.insertion?.parentPath??[],R=W.insertion?.index??0,le=ze.current,ye=xg(le.components,J);Oe();const we=_.open({ariaLabel:h("layout_editor.palette.title"),width:880,maxHeightRatio:.82,content:u.createElement(Nf,{manifest:le.manifest,nesting:le.nesting,componentPalette:le.componentPalette??null,targetParentPath:J,targetContainerName:ye,onInsert:(Te,ke,_e)=>{Ye.current(Te,ke,_e)},insertionIndex:R,onClose:()=>Oe(),editorTemplateId:f.templateIdentifier,editorLocale:f.locale}),onClose:()=>{Me.current=null}});Me.current=we},[_,Oe]),ve=u.useMemo(()=>{if(!f.isPaletteOpen)return null;if(!G)return{parentPath:[],index:k.length};if(C.selectedLockKind!=="none"){const le=V;return!le||le.length===0?{parentPath:[],index:k.length}:{parentPath:le.slice(0,-1),index:(le[le.length-1]??0)+1}}const J=C.selectedNode,R=Array.isArray(J?.children)?J.children.length:0;return MI(J?.name,R,G,o,k.length)},[f.isPaletteOpen,G,V,C.selectedLockKind,C.selectedNode,k.length,o]),qe=u.useCallback(()=>{b({type:"SET_PALETTE_OPEN",open:!1})},[b]),We=u.useRef(null);u.useEffect(()=>{if(!f.isPaletteOpen){const ye=We.current;ye&&(_.close(ye),We.current=null);return}if(!ve||We.current)return;const W=ve,J=ze.current,R=xg(J.components,W.parentPath),le=_.open({ariaLabel:h("layout_editor.palette.title"),width:880,maxHeightRatio:.82,content:u.createElement(Nf,{manifest:J.manifest,nesting:J.nesting,componentPalette:J.componentPalette??null,targetParentPath:W.parentPath,targetContainerName:R,onInsert:(ye,we,Te)=>{Ye.current(ye,we,Te),qe()},insertionIndex:W.index,onClose:()=>qe(),editorTemplateId:f.templateIdentifier,editorLocale:f.locale}),onClose:()=>{We.current=null,b({type:"SET_PALETTE_OPEN",open:!1})}});We.current=le},[f.isPaletteOpen,ve,_,qe,b]);const ce=u.useRef({layoutName:null,pushed:!1});u.useEffect(()=>{const W=v?.document?.layoutName??null;if(!W){ce.current={layoutName:null,pushed:!1};return}ce.current.layoutName!==W&&(y.clear(),y.push({actionKind:"inline_text_edit",label:"baseline",snapshot:k}),ce.current={layoutName:W,pushed:!0})},[v?.document?.layoutName,k,y]);const q=u.useRef(0);u.useEffect(()=>{const W=v?.saveSuccessCounter??0;W!==q.current&&(q.current=W,W!==0&&(y.clear(),y.push({actionKind:"inline_text_edit",label:"save_baseline",snapshot:k}),w.current=[],c?.([])))},[v?.saveSuccessCounter,k,y,c]);const re=u.useRef(0);u.useEffect(()=>{const W=v?.reloadCounter??0;W!==re.current&&(re.current=W,W!==0&&(ce.current={layoutName:null,pushed:!1},w.current=[],c?.([])))},[v?.reloadCounter,c]);const ie=u.useCallback((W,J,R)=>{if(!v)return;let le=[];v.patchLayout(ke=>{const _e={children:ke},Ie=Va(_e,J),He=R<0?Array.isArray(Ie?.children)?Ie.children.length:0:R,gt=ts(_e,J,He,W).children??[];return le=gt,gt}),y.push({actionKind:"insert",label:`insert ${W.name??W.type??""}`,snapshot:le});const ye=Va({children:le},J),we=R<0?Array.isArray(ye?.children)?ye.children.length-1:0:R,Te=at([...J,we]);if(w.current=[...w.current,Te],c?.(w.current),t){const ke=`[data-editor-path="${an(Te)}"]`,_e=()=>{const Ie=t.querySelector(ke);Ie&&typeof Ie.scrollIntoView=="function"&&Ie.scrollIntoView({behavior:"smooth",block:"center",inline:"nearest"})};typeof window<"u"&&typeof window.requestAnimationFrame=="function"?window.requestAnimationFrame(()=>window.requestAnimationFrame(_e)):setTimeout(_e,32)}},[v,y,c,t]);Ye.current=ie;const me=u.useCallback(()=>{if(!v||!C.selectedNode||!G)return;const W=os(C.selectedNode);let J=[];v.patchLayout(R=>{const le={children:R},ye=G.slice(0,-1),we=G[G.length-1]??0,ke=ts(le,ye,we+1,W).children??[];return J=ke,ke}),y.push({actionKind:"insert",label:"duplicate",snapshot:J})},[v,C.selectedNode,G,y]),Ce=u.useCallback(()=>{if(!v||!G)return;let W=[];v.patchLayout(J=>{const ye=ns({children:J},G).children??[];return W=ye,ye}),y.push({actionKind:"remove",label:"remove",snapshot:W}),C.clearSelection()},[v,G,y,C]),$e=u.useMemo(()=>{if(!C.selectedNode||!G||G.some(et))return null;const W=C.selectedNode;if(!Array.isArray(W.children))return null;const J=kC(f.previewDevice,W,f.previewCustomWidth);return{mode:J.mode,key:J.key,sourceKey:J.sourceKey}},[C.selectedNode,G,f.previewDevice,f.previewCustomWidth]),U=u.useCallback(()=>{if(!v||!G||$e?.mode!=="separate")return;const W=$e.key,J=$e.sourceKey;let R=[];v.patchLayout(le=>(R=Pb({children:le},G,W,J).children??[],R)),y.push({actionKind:"insert",label:"separate_branch",snapshot:R})},[v,G,$e,y]),ue=u.useCallback(()=>{if(!v||!G||$e?.mode!=="merge")return;const W=$e.key;let J=[];v.patchLayout(R=>(J=Ob({children:R},G,W).children??[],J)),y.push({actionKind:"remove",label:"merge_branch",snapshot:J})},[v,G,$e,y]),fe=u.useCallback(W=>{switch(W){case"mobile":return g("layout_editor.context_menu.device_mobile");case"portable":return g("layout_editor.context_menu.device_portable");case"tablet":return g("layout_editor.context_menu.device_tablet");case"desktop":return g("layout_editor.context_menu.device_desktop");default:return g("layout_editor.context_menu.device_custom",{range:W})}},[g]),xe=u.useMemo(()=>{if(!$e)return null;const W=fe($e.key);return{separate:g("layout_editor.context_menu.separate_branch_device",{device:W}),merge:g("layout_editor.context_menu.merge_branch_device",{device:W})}},[$e,g,fe]),he=u.useMemo(()=>{if(!C.selectedNode||!G)return[];if(G.some(et))return[];const W=Yr(f.previewDevice,C.selectedNode,f.previewCustomWidth);return vT(C.selectedNode,W).map(J=>({key:J,label:fe(J)}))},[C.selectedNode,G,f.previewDevice,f.previewCustomWidth,fe]),j=u.useCallback(W=>{if(W==="desktop"||W==="tablet"||W==="mobile"||W==="portable"){m.setDevice(W);return}m.setDevice(W)},[m]),ne=u.useCallback(()=>{C.selectedNode&&hg(C.selectedNode)},[C.selectedNode]),pe=u.useCallback(()=>{C.selectedNode&&(hg(C.selectedNode),Ce())},[C.selectedNode,Ce]),Q=u.useCallback(()=>{if(!v)return;const W=y4();if(!W)return;const J=os(W);if(G&&G.length>0){const R=G.slice(0,-1),le=(G[G.length-1]??0)+1;ie(J,R,le)}else ie(J,[],k.length)},[v,G,ie,k.length]),ae=u.useCallback((W,J)=>{y.push({actionKind:"move",label:J,snapshot:W})},[y]),Ae=u.useCallback(()=>{if(C.selectedNavAffordance==="route_in_tree"&&C.selectedNavTargetPath){if(d){d(C.selectedNavTargetPath);return}b({type:"SELECT_ROUTE",route:{path:C.selectedNavTargetPath,layoutName:null}})}},[C.selectedNavAffordance,C.selectedNavTargetPath,d,b]),dt=u.useCallback(()=>{const W=Mb(C.selectedBaseLayout,f.selectedRoute?.layoutName);W&&b({type:"ENTER_BASE_EDIT",layoutName:W})},[C.selectedBaseLayout,f.selectedRoute?.layoutName,b]),pt=u.useCallback(()=>{const W=C.selectedNode?.__source?.extensionId;W!=null&&b({type:"ENTER_EXTENSION_EDIT",extensionId:String(W)})},[C.selectedNode,b]),ot=u.useCallback(()=>{const W=C.selectedIterationSourcePath;W&&b({type:"ENTER_ITERATION_ITEM_EDIT",sourcePath:W})},[C.selectedIterationSourcePath,b]),Le=u.useRef(null),je=u.useRef(null),Je=u.useRef(null),Tt=u.useRef({colorScheme:"base",breakpoint:"base"}),Dt=Le.current!==null?_.stack.some(W=>W.id===Le.current):!1,zt=u.useMemo(()=>Od(f.routeTree,h),[f.routeTree,h]),xn=u.useMemo(()=>Bd(v?.document?.raw),[v?.document?.raw]),wn=u.useMemo(()=>Wd(s),[s?.states]),Zt=u.useMemo(()=>{const W=v?.document?.raw,J=Lf(W?.components),R=W?.initIsolated,le=R&&typeof R=="object"&&!Array.isArray(R)?Object.keys(R):[];return $A(J,le)},[v?.document?.raw]),Hn=u.useMemo(()=>{const W=v?.document?.raw;return Lf(W?.components).map(J=>J.scopeId).filter(J=>typeof J=="string"&&J.length>0)},[v?.document?.raw]),ln=Pd({raw:v?.document?.raw,spec:s}),Di=u.useCallback((W,J)=>{if(!v)return;const R={children:ze.current.components},le=Be(R,W);if(!le)return;Tt.current={colorScheme:f.previewColorScheme==="dark"?"dark":"base",breakpoint:Yr(f.previewDevice,le,f.previewCustomWidth)};const ye=t?.getBoundingClientRect(),we=A&&ye?{left:ye.left+A.left,top:ye.top+A.top,width:A.width,height:A.height}:void 0,Te=oe,ke=W.find(et),_e=ke?fe(ke.responsive):null,Ie=W.filter(kn=>typeof kn=="number"),He=bb({children:ze.current.components},Ie,mb(ln)),Nt=He.length>0?[...Ki({iterationVars:He}),...ln]:ln,gt=(kn,PM)=>u.createElement(RT,{key:`pem-${at(je.current??[])}`,node:kn,spec:s,manifest:ze.current.manifest,t:h,permissionCandidates:a,pageCandidates:zt,dataSourceCandidates:xn,stateKeyCandidates:wn,bindingCandidates:Nt,liveElement:Te,templateIdentifier:f.templateIdentifier,initialTab:PM,branchLabel:_e,isolatedScopeIdCandidates:Zt,usedScopeIds:Hn,onSaveInjectedProps:async(mt,Sn)=>{const zi=typeof kn.id=="string"?kn.id:"",Fn=await f4(f.templateIdentifier,mt,zi,Sn);if(st({op:"save",layoutName:`extension:${mt}`,editMode:f.editMode,saveTarget:"layout_extension",endpoint:`/api/admin/templates/${f.templateIdentifier}/layout-extensions/${mt}`,isDirty:!0,timestamp:Date.now()}),Fn.kind!=="success")throw new Error(Fn.kind==="conflict"?h("layout_editor.save.conflict"):Fn.kind==="injection_not_found"?"injection not found":Fn.kind==="not_found"?"extension not found":Fn.message??"save failed")},scope:Tt.current,onScopeChange:mt=>{Tt.current=mt;const Sn=Le.current;Sn&&_.open({id:Sn,ariaLabel:h("layout_editor.property_modal.title"),width:420,draggable:!0,avoidRect:we,content:gt(kn),onClose:()=>{Le.current=null,je.current=null}})},onPatchNode:mt=>{let Sn=[];v.patchLayout(Fn=>(Sn=Ot({children:Fn},W,()=>mt).children??[],Sn)),y.push({actionKind:"inline_text_edit",label:"edit_props",snapshot:Sn}),dr({source:"style_control",componentPath:at(W),componentName:typeof mt.name=="string"?mt.name:null,patchKey:null,timestamp:Date.now()});const zi=Le.current;zi&&_.open({id:zi,ariaLabel:h("layout_editor.property_modal.title"),width:420,draggable:!0,avoidRect:we,content:gt(mt),onClose:()=>{Le.current=null,je.current=null}})},onDelete:()=>{const mt=Le.current;mt&&_.close(mt),Ce()},onClose:()=>{const mt=Le.current;mt&&_.close(mt)}});je.current=W;const Mt=_.open({ariaLabel:h("layout_editor.property_modal.title"),width:420,draggable:!0,avoidRect:we,content:gt(le,J),onClose:()=>{Le.current=null,je.current=null,Je.current=null}});Le.current=Mt,Je.current=kn=>{_.open({id:Mt,ariaLabel:h("layout_editor.property_modal.title"),width:420,draggable:!0,avoidRect:we,content:gt(kn),onClose:()=>{Le.current=null,je.current=null,Je.current=null}})}},[v,s,h,a,zt,xn,wn,ln,oe,y,_,Ce,t,A,f.previewColorScheme,f.previewDevice,f.previewCustomWidth,fe]);u.useEffect(()=>{const W=Je.current,J=je.current;if(!W||!J)return;const R={children:ze.current.components},le=Be(R,J);le?W(le):Le.current&&_.close(Le.current)},[k]);const vM=()=>{if(!Dt)return null;const J={position:"absolute",background:"rgba(15, 23, 42, 0.45)",pointerEvents:"auto",zIndex:W7},R=ke=>{ke.preventDefault(),ke.stopPropagation()};if(!A)return i.jsx("div",{"data-testid":"g7le-edit-lock-full",onClick:R,onMouseMove:R,style:{...J,inset:0}});const{left:le,top:ye,width:we,height:Te}=A;return i.jsxs("div",{"data-testid":"g7le-edit-lock",style:{position:"absolute",inset:0,pointerEvents:"none"},children:[i.jsx("div",{"data-testid":"g7le-edit-lock-top",onClick:R,onMouseMove:R,style:{...J,left:0,top:0,right:0,height:Math.max(0,ye)}}),i.jsx("div",{"data-testid":"g7le-edit-lock-bottom",onClick:R,onMouseMove:R,style:{...J,left:0,top:ye+Te,right:0,bottom:0}}),i.jsx("div",{"data-testid":"g7le-edit-lock-left",onClick:R,onMouseMove:R,style:{...J,left:0,top:ye,width:Math.max(0,le),height:Te}}),i.jsx("div",{"data-testid":"g7le-edit-lock-right",onClick:R,onMouseMove:R,style:{...J,left:le+we,top:ye,right:0,height:Te}})]})},wl=u.useCallback(()=>{if(G&&$p(C.selectedLockKind)){if(p&&C.selectedPath){p(C.selectedPath);return}Di(G)}},[G,C.selectedPath,C.selectedLockKind,p,Di]),{scale:kl}=m,Sl=typeof C.selectedNode?.name=="string"?C.selectedNode.name:"",Uh=s?.componentCapabilities?.[Sl]??null,qh=Array.isArray(Uh?.styleControls)?Uh.styleControls:[],_M=qh.includes("width")?Ct(s,"width"):null,xM=qh.includes("height")?Ct(s,"height"):null,wM=u.useCallback((W,J)=>{if(!v||!G)return;let R=[];v.patchLayout(le=>(R=Ot({children:le},G,()=>W).children??[],R)),dr({source:"resize",componentPath:at(G),componentName:Sl||null,patchKey:J==="height"?"height":"width",resizeAxis:J,timestamp:Date.now()})},[v,G,Sl]),kM=u.useCallback((W,J)=>{if(!v||!G)return;let R=[];v.patchLayout(le=>(R=Ot({children:le},G,()=>W).children??[],R)),y.push({actionKind:"inline_text_edit",label:`resize ${J}`,snapshot:R})},[v,G,y]),SM=u.useCallback(()=>{if(!oe)return null;const W=oe.getBoundingClientRect(),J=kl||1;return{width:W.width/J,height:W.height/J}},[oe,kl]),lr=v4({node:C.selectedNode,widthControl:_M,heightControl:xM,scale:kl,measureStartSize:SM,onResize:wM,onResizeEnd:kM}),CM=u.useCallback((W,J)=>{lr.onHandlePointerDown(W,J)},[lr]),Cl=u.useCallback(()=>{const W=y.undo();!W||!v||v.setLayoutComponents(W.snapshot)},[y,v]),$l=u.useCallback(()=>{const W=y.redo();!W||!v||v.setLayoutComponents(W.snapshot)},[y,v]);O7({onUndo:Cl,onRedo:$l});const $M=u.useMemo(()=>({copy:ne,cut:pe,paste:Q,delete:Ce,openProps:wl,selectParent:Z?se:void 0,deselect:C.clearSelection}),[ne,pe,Q,Ce,wl,Z,se,C.clearSelection]);lp({handlers:$M,hasSelection:!!C.selectedPath}),u.useEffect(()=>(window.__g7LayoutEditorHasSelection=!!C.selectedPath,()=>{try{delete window.__g7LayoutEditorHasSelection}catch{}}),[C.selectedPath]),u.useEffect(()=>(window.__g7LayoutEditorHistory={undo:Cl,redo:$l,canUndo:y.canUndo,canRedo:y.canRedo},()=>{try{delete window.__g7LayoutEditorHistory}catch{}}),[Cl,$l,y.canUndo,y.canRedo]);const EM=u.useCallback(W=>{const J=L;F(null),J&&(async()=>{if((await I.commit(J.path,W)).kind==="created"&&v){const le=v.document?.raw?.components??[];y.push({actionKind:"inline_text_edit",label:"inline_create_key",snapshot:le})}})()},[L,I,v,y]),Gh=u.useCallback(()=>{const W=L;F(null),I.trackCancel(W?.path??null)},[L,I]),IM=u.useCallback(()=>{F(null)},[]),AM=u.useCallback(async W=>{const J=L;if(!J)return null;const R=await I.keyifyChipValue(J.path,W);if(R.kind!=="created"||!R.translationKey)return null;const le=v?.document?.raw?.components??[];return y.push({actionKind:"inline_text_edit",label:"inline_keyify_chip",snapshot:le}),R.translationKey},[L,I,v,y]),TM=u.useCallback(()=>{const W=L;W&&(F(null),C.handleSelect("",{dataset:{editorPath:W.domPath}}),Di(W.path,"translation"))},[L,C,Di]),wt=u.useMemo(()=>{if(!L)return null;const W=v?.document?.raw?.components??k;return Be({children:W},L.path)},[L,v?.document,k]),DM=u.useCallback(async(W,J)=>{const R=L;if(!v||!R)return null;const le=typeof wt?.text=="string"?wt.text:"";let ye;if(R.isParamKey)ye=await Cr(f.templateIdentifier,f.locale,le,W,J.sourceId,J.path,"scalar");else{const He=Nt=>Nt.replace(/\$t:[a-zA-Z0-9._-]+/g,gt=>{const Mt=h(gt.slice(3));return Mt&&Mt!==gt.slice(3)&&!Mt.startsWith("$t:")?Mt:""}).replace(/\s+/g," ").trim();ye=await us(f.templateIdentifier,f.selectedRoute?.layoutName??null,f.locale,le,W,J.sourceId,J.path,"scalar",He,h)}if(ye.kind==="error")return null;const we=R.path;let Te=[];v.patchLayout(He=>(Te=Ot({children:He},we,Mt=>({...Mt,text:ye.text})).children??[],Te)),y.push({actionKind:"inline_text_edit",label:"inline_insert_binding",snapshot:Te});const ke=ye.kind==="ok"&&"key"in ye?ye.key:void 0;!R.isParamKey&&ke&&F(He=>He&&He.path===we?{...He,isParamKey:!0,customKey:ke,chipValue:void 0,chipParamLabels:void 0}:He);try{window.dispatchEvent(new CustomEvent(kt,{detail:{templateIdentifier:f.templateIdentifier,locale:f.locale}}))}catch{}const _e=vt(ye.text),Ie=R.customKey??_e?.key??"";return Ie?It(Ie,f.locale)??null:null},[v,L,wt,f.templateIdentifier,f.locale,f.selectedRoute,y,h]),zM=u.useCallback(async W=>{const J=L;if(!v||!J)return null;const R=typeof wt?.text=="string"?wt.text:"",le=vt(R),ye=J.customKey??le?.key??null,we=vo(R,W),Te=J.path;let ke=[];return v.patchLayout(_e=>(ke=Ot({children:_e},Te,Nt=>({...Nt,text:we})).children??[],ke)),y.push({actionKind:"inline_text_edit",label:"inline_remove_binding",snapshot:ke}),ye&&await $r(f.templateIdentifier,ye,W,f.locale),ye?It(ye,f.locale)??null:null},[v,L,wt,f.templateIdentifier,f.locale,y]),[El,Yh]=u.useState({}),NM=["fontSize","color","fontStyle","textDecoration","textAlign","letterSpacing","lineHeight","fontFamily"];u.useEffect(()=>{if(!L||!t||typeof MutationObserver>"u"){Yh({});return}const W=`[data-editor-path="${an(L.domPath)}"]`,J=()=>{const ye=t.querySelector(W);if(!ye)return;const we={};for(const ke of NM){const _e=ye.style.getPropertyValue(ke.replace(/([A-Z])/g,"-$1").toLowerCase());_e&&(we[ke]=_e)}const Te=wg(t,L.domPath)??void 0;Yh({className:ye.className,style:we,effectiveColor:Te})};J();const R=t.querySelector(W);if(!R)return;const le=new MutationObserver(J);return le.observe(R,{attributes:!0,attributeFilter:["class","style"]}),le.observe(t,{childList:!0,subtree:!0}),()=>le.disconnect()},[L?.path?at(L.path):null,t]);const Xh=u.useCallback((W,J)=>{if(!v||!L)return;const R=L.path;let le=[],ye=null;v.patchLayout(we=>(le=Ot({children:we},R,_e=>{const Ie=J(_e);return ye=typeof Ie.name=="string"?Ie.name:null,Ie}).children??[],le)),y.push({actionKind:"inline_text_edit",label:`inline_format ${W}`,snapshot:le}),dr({source:"style_control",componentPath:at(R),componentName:ye,patchKey:W,timestamp:Date.now()})},[v,L,y]),ft=u.useMemo(()=>{const W=C.selectedPath;if(!W||!x)return null;const J=[];let R=W;for(;R;){J.push(R);const le=R.match(/^(.*)\.children\.\d+$/);R=le?le[1]:null}for(const le of J){const ye=Re(le);if(!ye)continue;const we=Be(x,ye),Te=typeof we?.name=="string"?we.name:null;if(!Te)continue;const ke=ji(s,Te),_e=ke?.canvasOverlay?.kind;if(typeof _e!="string"||!_e||!we)continue;const Ie=ke.canvasOverlay.params&&typeof ke.canvasOverlay.params=="object"?ke.canvasOverlay.params:void 0,He=le===W?null:W.slice(le.length+1);return{kind:_e,params:Ie,tablePath:le,tableNode:we,selectedCellRel:He}}return null},[C.selectedPath,x,s]),yo=ft?g4(ft.kind):null,Jh=u.useMemo(()=>!yo||!t||!ft?null:fo(t,ft.tablePath),[yo,t,ft,A,k]),MM=u.useMemo(()=>{if(!yo||!t||!ft)return[];const W=ft.tablePath,J=an(W),R=[];return t.querySelectorAll(`[data-editor-path^="${J}.children."]`).forEach(we=>{const Te=we.dataset.editorPath??"";if(!Te.startsWith(W+"."))return;const ke=Te.slice(W.length+1),_e=ao(we,t);_e&&R.push({path:ke,top:_e.top,left:_e.left,width:_e.width,height:_e.height})}),t.querySelectorAll(`[data-editor-item-path^="${J}."]`).forEach(we=>{const Te=we.dataset.editorItemPath??"";if(!Te.startsWith(W+"."))return;const ke=Te.slice(W.length+1),_e=ao(we,t);_e&&R.push({path:ke,top:_e.top,left:_e.left,width:_e.width,height:_e.height})}),R},[yo,t,ft,A,k]),LM=u.useCallback(W=>{const J=ft?Re(ft.tablePath):null;if(!v||!J)return;let R=[];v.patchLayout(le=>(R=Ot({children:le},J,()=>W).children??[],R)),y.push({actionKind:"inline_text_edit",label:"inplace_edit",snapshot:R}),dr({source:"style_control",componentPath:ft?.tablePath??"",componentName:typeof W.name=="string"?W.name:null,patchKey:"canvas_inplace",timestamp:Date.now()})},[v,G,y]);return t?i.jsxs("div",{className:"g7le-editor-canvas-overlay","data-testid":"g7le-editor-canvas-overlay",style:{position:"absolute",inset:0,pointerEvents:"none"},children:[!D&&vM(),D&&i.jsxs("div",{"data-testid":"g7le-canvas-pick-banner",style:{position:"absolute",top:8,left:"50%",transform:"translateX(-50%)",zIndex:9999,display:"flex",alignItems:"center",gap:12,padding:"8px 14px",background:"#1e293b",color:"#fff",borderRadius:8,fontSize:13,boxShadow:"0 4px 12px rgba(0,0,0,0.25)",pointerEvents:"auto"},children:[i.jsxs("span",{children:["🎯 ",h("layout_editor.target_picker.canvas_pick_hint")]}),i.jsx("button",{type:"button","data-testid":"g7le-canvas-pick-cancel",onClick:()=>X(D,null),style:{border:"1px solid #475569",borderRadius:6,background:"transparent",color:"#cbd5e1",cursor:"pointer",fontSize:12,padding:"3px 10px"},children:h("layout_editor.target_picker.cancel")})]}),f.editMode!=="route"&&i.jsx(RI,{frameEl:t,components:k,editMode:f.editMode,currentExtensionId:$,editableRootPath:S}),L&&((L.isParamKey&&L.customKey||L.chipValue)&&wt?i.jsxs(i.Fragment,{children:[wt&&i.jsx(vg,{node:wt,spec:s,t:h,onApplyControl:Xh,box:L.box,scope:{colorScheme:f.previewColorScheme==="dark"?"dark":"base",breakpoint:Yr(f.previewDevice,wt,f.previewCustomWidth)}}),i.jsx(D4,{box:L.box,node:wt,customKey:L.customKey??null,initialChipValue:L.chipValue,chipParamLabels:L.chipParamLabels,onKeyify:AM,templateIdentifier:f.templateIdentifier,locale:f.locale,t:h,candidates:ln,onInsertBinding:DM,onRemoveBinding:zM,onCommit:IM,onCancel:Gh})]}):i.jsxs(i.Fragment,{children:[wt&&i.jsx(vg,{node:wt,spec:s,t:h,onApplyControl:Xh,box:L.box,scope:{colorScheme:f.previewColorScheme==="dark"?"dark":"base",breakpoint:wt?Yr(f.previewDevice,wt,f.previewCustomWidth):"base"}}),i.jsx(T4,{box:L.box,initialValue:L.initialValue,isCustomKey:L.isCustomKey,t:h,onCommit:EM,onCancel:Gh,mirrorClassName:El.className,mirrorStyle:El.style,nodeEffectiveColor:El.effectiveColor??L.nodeEffectiveColor,onOpenTranslations:TM})]})),!D&&i.jsx(U7,{hoverBox:T,selectedBox:A,lockKind:C.selectedLockKind,navAffordance:C.selectedNavAffordance,onEditProps:wl,onDuplicate:me,onDelete:Ce,onSeparateBranch:$e?.mode==="separate"?U:void 0,separateBranchLabel:xe?.separate,onMergeBranch:$e?.mode==="merge"?ue:void 0,mergeBranchLabel:xe?.merge,definedDeviceBranches:he,onJumpToDevice:he.length>0?j:void 0,onLinkEditDestination:Ae,onEditBase:dt,onEditExtension:pt,extensionLabel:De,baseLayoutLabel:C.selectedBaseLayout,dataSourceLabel:C.selectedDataSourceId,branchLabel:ee,onEditIteration:C.selectedIsIteration&&f.editMode!=="iteration_item"?ot:void 0,resizeEnabledAxes:lr.enabledAxes,onResizeHandlePointerDown:lr.enabledAxes.width||lr.enabledAxes.height?CM:void 0,selectedName:Ee,onSelectParent:Z?se:void 0}),D&&O&&i.jsx("div",{"data-testid":"g7le-canvas-pick-hover","data-pick-has-id":O.hasId?"true":"false",style:{position:"absolute",left:O.box.left,top:O.box.top,width:O.box.width,height:O.box.height,pointerEvents:"none",zIndex:9998,boxSizing:"border-box",border:O.hasId?"2px solid #2563eb":"2px dashed #94a3b8",background:O.hasId?"rgba(37,99,235,0.10)":"repeating-linear-gradient(45deg, rgba(148,163,184,0.18) 0, rgba(148,163,184,0.18) 6px, transparent 6px, transparent 12px)",cursor:O.hasId?"pointer":"not-allowed"},children:!O.hasId&&i.jsx("span",{"data-testid":"g7le-canvas-pick-hover-hint",style:{position:"absolute",top:0,left:0,transform:"translateY(-100%)",background:"#475569",color:"#fff",fontSize:11,lineHeight:1.4,padding:"2px 6px",borderRadius:4,whiteSpace:"nowrap"},children:h("layout_editor.target_picker.needs_id")})}),!D&&i.jsx(Z7,{selectedBox:Ue,points:Se.points,onAddRequest:Xe}),yo&&ft&&Jh&&C.selectedLockKind==="none"&&i.jsx(yo,{node:ft.tableNode,params:ft.params,nodeBox:Jh,cellBoxes:MM,selectedCellPath:ft.selectedCellRel??void 0,colorScheme:f.previewColorScheme==="dark"?"dark":"light",t:h,onPatchNode:LM,onInsertChild:ie,onRemoveChild:W=>{const J=Re(W);if(!v||!J)return;let R=[];v.patchLayout(le=>(R=ns({children:le},J).children??[],R)),y.push({actionKind:"remove",label:"inplace_remove",snapshot:R})},onRequestInlineEdit:W=>{ft&&Y(`${ft.tablePath}.${W}`)}}),v&&i.jsx(GI,{frameEl:t,nesting:o,editMode:f.editMode,currentExtensionId:$,editableRootPath:S,components:k,patchLayout:v.patchLayout,pushHistory:ae,selectedPath:C.selectedPath,onSelectPath:W=>C.handleSelect("",{dataset:{editorPath:W}}),onMovePath:W=>C.handleSelect("",{dataset:{editorPath:W}}),onRequestInlineEdit:Y})]}):null}function xg(e,t){if(t.length===0)return null;const o=Va({children:e},t);return o&&typeof o.name=="string"?o.name:null}function Va(e,t){return Be(e,t)}function an(e){return typeof CSS<"u"&&typeof CSS.escape=="function"?CSS.escape(e):e.replace(/[^a-zA-Z0-9_\-.]/g,t=>`\\${t}`)}function wg(e,t){if(!t)return null;const n=e.querySelector(`[data-editor-path="${an(t)}"]`);if(!n||typeof window.getComputedStyle!="function")return null;let o=n;const r=n.ownerDocument.createTreeWalker(n,NodeFilter.SHOW_TEXT);let s=r.nextNode();for(;s;){if((s.textContent??"").trim().length>0){const a=s.parentElement;if(a&&n.contains(a)){o=a;break}}s=r.nextNode()}try{return window.getComputedStyle(o).color||null}catch{return null}}function fo(e,t){if(!t)return null;const n=e.querySelector(`[data-editor-path="${an(t)}"]`);if(n){const c=ao(n,e);return c&&lo(c,e)?c:null}const o=e.querySelectorAll(`[data-editor-path^="${an(t)}.iteration."]`);let r=1/0,s=1/0,a=-1/0,l=-1/0,d=!1;return o.forEach(c=>{const p=c.dataset.editorPath??"";if(!new RegExp("\\.iteration\\.\\d+$").test(p))return;const f=ao(c,e);f&&lo(f,e)&&(d=!0,r=Math.min(r,f.left),s=Math.min(s,f.top),a=Math.max(a,f.left+f.width),l=Math.max(l,f.top+f.height))}),d?{left:r,top:s,width:a-r,height:l-s,scale:1}:null}function W4({isEditMode:e,editorSpec:t,sampleOverride:n}){const o=t?.sampleData;return u.useMemo(()=>Km({spec:o,overrideSpec:n,onMatch:r=>{Qh(r)}}),[e,o,n])}const j4=Lt().ComponentRegistry;function H4(e){return e.replace(/[\t\n\r]/g,"").replace(/\\/g,"/").replace(/^([a-z][a-z0-9+.\-]*:)?\/{2,}/i,"$1//")}function F4(e,t){try{const n=typeof window<"u"&&window.location?.protocol||"https:",o=t||(typeof window<"u"?window.location?.origin:void 0)||"https://localhost",r=e.startsWith("//")?`${n}${e}`:e,s=new URL(r,o);return s.protocol!=="http:"&&s.protocol!=="https:"?null:s.hostname.toLowerCase()}catch{return null}}function Ua(){const e=typeof window<"u"?window.G7Config?.trustedScriptHosts:void 0;return Array.isArray(e)?e.map(t=>String(t).toLowerCase()):[]}function kg(e,t){if(typeof e!="string")return!1;const n=e.trim();if(n==="")return!1;const o=H4(n),r=o.startsWith("//"),s=/^[a-z][a-z0-9+.-]*:/i.test(o);if(!r&&!s&&o.startsWith("/"))return!0;const a=F4(o),l=t??Ua();return a!==null&&l.map(d=>String(d).toLowerCase()).includes(a)}function K4(e){return e.split(/[-_]/).map(t=>t.charAt(0).toUpperCase()+t.slice(1).toLowerCase()).join("")}async function V4(e){const t=typeof window<"u"?window.localStorage?.getItem("auth_token"):null,n={Accept:"application/json"};t&&(n.Authorization=`Bearer ${t}`);const o=await fetch(`/api/admin/templates/${encodeURIComponent(e)}/editor-assets`,{headers:n,credentials:"same-origin"});if(!o.ok){const s=await o.json().catch(()=>null);throw Jr(o.status,s,"assets")}const r=await o.json();return r.data||r}async function U4(e){try{if(typeof fetch!="function")return 0;const t=await fetch(en(`/api/templates/${encodeURIComponent(e)}/config`,"json"),{credentials:"same-origin",headers:{Accept:"application/json"}});if(!t.ok)return 0;const n=await t.json().catch(()=>null),o=n?.data?.cache_version??n?.cache_version;return typeof o=="number"&&o>0?o:0}catch{return 0}}function q4(e){return new Promise((t,n)=>{if(typeof document>"u"){n(new Error("document undefined"));return}if(!kg(e,Ua())){n(new Error(`Blocked untrusted script src (same-origin path or declared trusted host required): ${e}`));return}if(document.querySelector(`script[data-g7le-asset="${e}"]`)){t();return}const r=document.createElement("script");r.src=e,r.async=!1,r.setAttribute("data-g7le-asset",e),r.addEventListener("load",()=>t()),r.addEventListener("error",()=>n(new Error(`script load failed: ${e}`))),document.head.appendChild(r)})}async function G4(e){if(typeof document>"u")return;const t=`[data-g7le-asset="${e}"]`;if(document.querySelector(`link${t}, style${t}`))return;if(e.includes("/api/admin/"))try{const r=typeof window<"u"?window.localStorage?.getItem("auth_token"):null,s={Accept:"text/css"};r&&(s.Authorization=`Bearer ${r}`);const a=await fetch(e,{headers:s,credentials:"same-origin"});if(a.ok){const l=await a.text(),d=document.createElement("style");d.textContent=l,d.setAttribute("data-g7le-asset",e),document.head.appendChild(d);return}}catch{}const o=document.createElement("link");o.rel="stylesheet",o.href=e,o.setAttribute("data-g7le-asset",e),document.head.appendChild(o)}function Y4(e,t,n="user"){const[o,r]=u.useState({componentRegistry:null,translationEngine:null,isReady:!1,error:null});return u.useEffect(()=>{let s=!1;return(async()=>{r(a=>({...a,isReady:!1,error:null}));try{const a=await V4(e);if(s)return;for(const b of a.css??[])if(await G4(b),s)return;const l=K4(e);if(!(typeof window<"u"&&typeof window[l]=="object")){for(const b of a.js??[])if(await q4(b),s)return}const c=j4.createIsolatedInstance();if(await c.loadComponents(e,n),s)return;const p=await U4(e);if(s)return;const f=new Pt({defaultLocale:t});if(p>0&&f.setCacheVersion(p),await f.loadTranslations(e,t),s)return;gd(f,e);try{const b=Pt.getInstance();p>b.getCacheVersion()&&b.setCacheVersion(p),await b.loadTranslations(e,t),gd(b,e)}catch{}if(s)return;r({componentRegistry:c,translationEngine:f,isReady:!0,error:null})}catch(a){if(s)return;const l=xp(a)?a:so(a,"assets");r({componentRegistry:null,translationEngine:null,isReady:!1,error:l})}})(),()=>{s=!0}},[e,t,n]),o}const Sg=Lt().AuthManager;function X4(e){switch(e){case"unauthorized":return{icon:"🔒",tone:"blue",titleKey:"layout_editor.access_error.unauthorized.title",messageKey:"layout_editor.access_error.unauthorized.message",showRequiredPermissions:!0};case"forbidden":return{icon:"⛔",tone:"rose",titleKey:"layout_editor.access_error.forbidden.title",messageKey:"layout_editor.access_error.forbidden.message",showRequiredPermissions:!0};case"not_found":return{icon:"🗂️",tone:"slate",titleKey:"layout_editor.access_error.not_found.title",messageKey:"layout_editor.access_error.not_found.message",showRequiredPermissions:!1};case"server_error":return{icon:"🛠️",tone:"amber",titleKey:"layout_editor.access_error.server_error.title",messageKey:"layout_editor.access_error.server_error.message",showRequiredPermissions:!1};case"network":return{icon:"📡",tone:"amber",titleKey:"layout_editor.access_error.network.title",messageKey:"layout_editor.access_error.network.message",showRequiredPermissions:!1};default:return{icon:"⚠️",tone:"slate",titleKey:"layout_editor.access_error.unknown.title",messageKey:"layout_editor.access_error.unknown.message",showRequiredPermissions:!1}}}const J4={blue:{bg:"#eff6ff",border:"#bfdbfe",iconBg:"#dbeafe"},rose:{bg:"#fff1f2",border:"#fecdd3",iconBg:"#ffe4e6"},amber:{bg:"#fffbeb",border:"#fde68a",iconBg:"#fef3c7"},slate:{bg:"#f8fafc",border:"#e2e8f0",iconBg:"#f1f5f9"}};function Z4({error:e}){return u.useEffect(()=>{if(e.kind!=="unauthorized"||typeof window>"u")return;const t=Sg.getInstance(),n=`${window.location.pathname}${window.location.search}`,o=t.getLoginRedirectUrl("admin",n,"session_expired"),r=window.setTimeout(()=>{window.location.href=o},600);return()=>window.clearTimeout(r)},[e.kind]),null}function qa({error:e}){const{t}=tt(),n=X4(e.kind),o=J4[n.tone],r=()=>{typeof window<"u"&&(window.location.href="/admin")},s=()=>{typeof window<"u"&&window.location.reload()},a=()=>{if(typeof window>"u")return;const l=Sg.getInstance(),d=`${window.location.pathname}${window.location.search}`;window.location.href=l.getLoginRedirectUrl("admin",d)};return i.jsxs("div",{className:"g7le-access-error","data-testid":"g7le-access-error","data-error-kind":e.kind,style:{flex:1,display:"flex",alignItems:"center",justifyContent:"center",padding:24,background:"#f1f5f9"},children:[i.jsx(Z4,{error:e}),i.jsxs("div",{style:{maxWidth:480,width:"100%",background:o.bg,border:`1px solid ${o.border}`,borderRadius:12,padding:"28px 32px",boxShadow:"0 2px 8px rgba(15, 23, 42, 0.05)",textAlign:"center"},children:[i.jsx("div",{style:{width:64,height:64,margin:"0 auto 16px",borderRadius:"50%",background:o.iconBg,display:"flex",alignItems:"center",justifyContent:"center",fontSize:32,lineHeight:1},"aria-hidden":"true",children:n.icon}),i.jsx("h2",{"data-testid":"g7le-access-error-title",style:{margin:"0 0 8px",fontSize:18,fontWeight:700,color:"#0f172a"},children:t(n.titleKey)}),i.jsx("p",{"data-testid":"g7le-access-error-message",style:{margin:"0 0 16px",fontSize:13,lineHeight:1.6,color:"#475569"},children:t(n.messageKey)}),n.showRequiredPermissions&&e.requiredPermissions&&i.jsxs("div",{"data-testid":"g7le-access-error-permissions",style:{display:"inline-flex",flexWrap:"wrap",gap:6,justifyContent:"center",padding:"8px 12px",marginBottom:16,background:"rgba(15, 23, 42, 0.04)",borderRadius:8,fontSize:12,color:"#475569"},children:[i.jsx("span",{style:{fontWeight:600},children:t("layout_editor.access_error.required_permission_label")}),e.requiredPermissions.split(/[,\s]+/).filter(Boolean).map(l=>i.jsx("code",{style:{padding:"2px 6px",background:"#fff",border:"1px solid #e2e8f0",borderRadius:4,fontFamily:"ui-monospace, SFMono-Regular, Menlo, monospace",fontSize:11,color:"#1e293b"},children:l},l))]}),e.message&&i.jsxs("details",{"data-testid":"g7le-access-error-detail",style:{marginBottom:16,fontSize:11,color:"#94a3b8"},children:[i.jsx("summary",{style:{cursor:"pointer"},children:t("layout_editor.access_error.detail_summary")}),i.jsxs("pre",{style:{marginTop:8,padding:8,background:"#fff",border:"1px solid #e2e8f0",borderRadius:6,overflowX:"auto",whiteSpace:"pre-wrap",wordBreak:"break-word",textAlign:"left",fontSize:11,color:"#475569"},children:[e.status>0?`HTTP ${e.status} -`:"",e.message]})]}),i.jsxs("div",{style:{display:"flex",justifyContent:"center",gap:8,flexWrap:"wrap"},children:[e.kind==="unauthorized"&&i.jsx("button",{type:"button","data-testid":"g7le-access-error-action-signin",onClick:a,style:Ga,children:t("layout_editor.access_error.action.sign_in")}),e.kind==="forbidden"&&i.jsx("button",{type:"button","data-testid":"g7le-access-error-action-home",onClick:r,style:Ga,children:t("layout_editor.access_error.action.go_home")}),(e.kind==="not_found"||e.kind==="server_error"||e.kind==="network"||e.kind==="unknown")&&i.jsx("button",{type:"button","data-testid":"g7le-access-error-action-retry",onClick:s,style:Ga,children:t("layout_editor.access_error.action.retry")}),e.kind!=="unauthorized"&&i.jsx("button",{type:"button","data-testid":"g7le-access-error-action-home-secondary",onClick:r,style:Q4,children:t("layout_editor.access_error.action.go_admin_home")})]})]})]})}const Ga={padding:"8px 16px",background:"#2563eb",color:"#fff",border:"1px solid #1d4ed8",borderRadius:6,fontSize:13,fontWeight:600,cursor:"pointer"},Q4={padding:"8px 16px",background:"#fff",color:"#475569",border:"1px solid #cbd5e1",borderRadius:6,fontSize:13,fontWeight:500,cursor:"pointer"};function R4(e){const t=window;if(typeof t>"u")return{getSnapshot:()=>({globalState:{},localState:{}}),restore:()=>{}};const n=t.G7Core||{},o={templateApp:t.__templateApp,g7CoreState:n.state,g7CoreDispatch:n.dispatch,g7CoreData:n.data,g7CoreModal:n.modal,g7CoreToast:n.toast};let r={...e.initialGlobalState??{}};r._local||(r._local={});const s=[],a=()=>{const c={globalState:{...r},localState:{...r._local??{}}};for(const p of s)try{p(r)}catch{}e.onChange?.(c)},l={},d={getGlobalState:()=>r,setGlobalState:(c,p)=>{typeof c=="function"?r=c(r):r={...r,...c},a()},onGlobalStateChange:c=>(s.push(c),()=>{const p=s.indexOf(c);p>=0&&s.splice(p,1)}),get globalState(){return r},getActionDispatcher:()=>e.actionDispatcher,getDataSource:c=>l[c]??null,setDataSource:(c,p)=>{l[c]=p},refetchDataSource:async c=>null,updateDataSourceItem:(c,p,f)=>l[c]??null,getLocale:()=>e.locale,changeLocale:c=>{},getRouter:()=>({navigate:()=>{},replace:()=>{},push:()=>{}}),updateQueryParams:()=>{}};return t.__templateApp=d,n&&typeof n.dispatch=="function"&&(n.dispatch=(c,p)=>{try{return e.actionDispatcher.executeAction(c,p??{})}catch{return}}),n&&typeof n.modal=="object"&&n.modal!==null&&(n.modal={...n.modal,open:()=>{},close:()=>{},closeAll:()=>{}}),n&&typeof n.toast=="object"&&n.toast!==null&&(n.toast={...n.toast,push:()=>{},dismiss:()=>{}}),{getSnapshot:()=>({globalState:{...r},localState:{...r._local??{}}}),restore:()=>{t.__templateApp===d&&(t.__templateApp=o.templateApp),t.G7Core&&(o.g7CoreState!==void 0&&(t.G7Core.state=o.g7CoreState),o.g7CoreDispatch!==void 0&&(t.G7Core.dispatch=o.g7CoreDispatch),o.g7CoreData!==void 0&&(t.G7Core.data=o.g7CoreData),o.g7CoreModal!==void 0&&(t.G7Core.modal=o.g7CoreModal),o.g7CoreToast!==void 0&&(t.G7Core.toast=o.g7CoreToast)),s.length=0}}}const eD=new Set(["id","userId","postId","orderId","productId","categoryId","commentId","noticeId","inquiryId","addressId","wishlistId","notificationId","roleId","menuId"]);function tD(e){if(!e)return{};const t={},n=/:([a-zA-Z_][a-zA-Z0-9_]*)/g;let o;for(;(o=n.exec(e))!==null;){const r=o[1];t[r]=nD(r)}return t}function nD(e){return eD.has(e)||/Id$|_id$/.test(e)?"1":"sample"}const oD=mo("LayoutEditor");function Ro(e){return typeof e=="object"&&e!==null&&!Array.isArray(e)}function _i(e){if(Array.isArray(e))return e.map(t=>_i(t));if(Ro(e)){const t={};for(const[n,o]of Object.entries(e))t[n]=_i(o);return t}return e}function Cg(e,t="",n=new Set){for(const[o,r]of Object.entries(e)){const s=t?`${t}.${o}`:o;Ro(r)?Cg(r,s,n):n.add(s)}return n}function $g(e,t,n,o,r,s=""){for(const[a,l]of Object.entries(t)){const d=s?`${s}.${a}`:a;if(n.has(d)){r(`[LayoutEditor] sampleGlobal key conflict: extension '${o}' override of core key '${d}' ignored`);continue}const c=e[a];Ro(l)&&Ro(c)?$g(c,l,n,o,r,d):e[a]=_i(l)}}function Ya(e){const t=e.warn??(r=>oD.warn(r)),n=_i(e.coreSeed??{});e.isGuestOnly&&"currentUser"in n&&delete n.currentUser;const o=Cg(n);for(const r of e.sources)Ro(r.sampleGlobal)&&$g(n,r.sampleGlobal,o,r.id,t);return e.isGuestOnly&&"currentUser"in n&&delete n.currentUser,n}function rD(e){const t={};if(!e||typeof e!="object"||Array.isArray(e))return t;for(const[n,o]of Object.entries(e))n.startsWith("$")&&(t[n]=o);return t}function iD(e){const t={};if(!e||typeof e!="object"||Array.isArray(e))return t;for(const[n,o]of Object.entries(e))n.startsWith("$")||(t[n]=o);return t}function sD(e){if(typeof e!="string")return null;const t=/^\s*\{\{\s*([\s\S]*?)\s*\}\}\s*$/.exec(e);if(!t)return null;let n=t[1];const o=n.indexOf("??");return o>=0&&(n=n.slice(0,o)),n=n.replace(/\?\./g,".").replace(/\s+/g,""),n===""||!/^[A-Za-z_$][A-Za-z0-9_$]*(\.[A-Za-z_$][A-Za-z0-9_$]*)*$/.test(n)?null:n.split(".")}function aD(e,t){const n=sD(t);if(!n||n.length===0)return e;let o=e;for(let a=0;ao.map(r=>{if(!r||typeof r!="object")return r;const s=r;let a=s;if(typeof s.slot=="string"&&!t.has(s.slot)){const{slot:l,...d}=s;a={...d,__editorSlotName:l}}if(Array.isArray(a.children)){const l=n(a.children);l!==a.children&&(a={...a,children:l})}return a});return n(e)}function St(e){return typeof e=="object"&&e!==null&&!Array.isArray(e)}function Xt(e){if(Array.isArray(e))return e.map(t=>Xt(t));if(St(e)){const t={};for(const[n,o]of Object.entries(e))t[n]=Xt(o);return t}return e}function xi(e,t){for(const[n,o]of Object.entries(t)){const r=e[n];St(o)&&St(r)?xi(r,o):e[n]=Xt(o)}}function cD(e){const t=[];let n="";for(let o=0;o0&&(t.push(n),n="");const s=e[o+1];if(s==="'"||s==='"'){const l=e.indexOf(`${s}]`,o+2);if(l!==-1){t.push(e.slice(o+2,l)),o=l+1;continue}}const a=e.indexOf("]",o+1);if(a!==-1){t.push(e.slice(o+1,a)),o=a;continue}n+=r}else r==="."?n.length>0&&(t.push(n),n=""):n+=r}return n.length>0&&t.push(n),t.filter(o=>o.length>0)}function Xa(e,t,n){const o=cD(t);if(o.length===0)return;let r=e;for(let s=0;s0?t:void 0}const Za="g7le:editor-t-ready",Ag="data-g7le-vh-expanded";function pD(e,t){if(!e||!t)return;const n=t;e.querySelectorAll("*").forEach(r=>{const s=window.getComputedStyle(r),a=parseFloat(s.height),l=Number.isFinite(a)&&Math.abs(a-n)<=2,d=parseFloat(s.minHeight),c=Number.isFinite(d)&&Math.abs(d-n)<=2,p=s.overflowY==="auto"||s.overflowY==="scroll"||s.overflowY==="hidden"||s.overflow==="auto"||s.overflow==="scroll"||s.overflow==="hidden";l&&p?(r.style.setProperty("height","auto","important"),c&&r.style.setProperty("min-height","0","important"),r.style.setProperty("overflow","visible","important"),r.setAttribute(Ag,"1")):(s.overflowY==="auto"||s.overflowY==="scroll")&&r.scrollHeight>r.clientHeight+4&&r.clientHeight>120&&(r.style.setProperty("overflow","visible","important"),r.style.setProperty("height","auto","important"),r.setAttribute(Ag,"1"))})}const fD=[];function gD(e={}){const{manifest:t=null,nesting:n=null,componentPalette:o=null,spec:r=null,permissionCandidates:s,sampleGlobalSources:a=fD,resolveRouteMatch:l,onNavigateToDestination:d,onSessionAddedPathsChange:c}=e,{state:p}=Qe(),f=Ft(),b=Dp(),{document:g,isLoading:m,error:h}=f??b,v=u.useRef(null),[y,_]=u.useState(null),{deviceWidth:w,scale:k,colorScheme:x}=Ka(),$=u.useMemo(()=>p.activeStateId?p.availableStates.find(ce=>ce.id===p.activeStateId)??null:null,[p.activeStateId,p.availableStates]),S=u.useMemo(()=>Ig($),[$]),C=W4({isEditMode:!0,editorSpec:r??void 0,sampleOverride:S}),{t:T}=tt(),{componentRegistry:E,translationEngine:A,isReady:N,error:D}=Y4(p.templateIdentifier,p.locale),B=u.useRef(null);B.current===null&&(B.current=new kd);const M=u.useRef(null);if(M.current===null){M.current=new y7({navigate:()=>{}}),M.current.setPreviewMode(!0);try{const q=window.__templateApp?.getActionDispatcher?.();if(q&&typeof q.getRegisteredHandlers=="function"){const re=q.getRegisteredHandlers();for(const ie of re){if(!ie.includes("."))continue;const me=typeof q.getHandler=="function"?q.getHandler(ie):void 0;typeof me=="function"&&M.current.registerHandler(ie,me,{category:"plugin",source:"preview-canvas-cloned-from-host"})}}}catch{}}u.useEffect(()=>{if(!N)return;const ce=M.current;if(ce)try{const q=window.G7Core?.getActionDispatcher?.();if(!q||typeof q.getRegisteredHandlers!="function")return;const re=q.getRegisteredHandlers();for(const ie of re){if(!ie.includes(".")||typeof ce.getHandler=="function"&&ce.getHandler(ie))continue;const me=typeof q.getHandler=="function"?q.getHandler(ie):void 0;typeof me=="function"&&ce.registerHandler(ie,me,{category:"plugin",source:"preview-canvas-iife-reclone"})}}catch{}},[N]);const O=u.useMemo(()=>new b7({sampleProvider:C,actionDispatcher:M.current}),[C]),z=u.useMemo(()=>g?.raw?.meta?.guest_only===!0,[g]),H=u.useMemo(()=>Ya({sources:a,isGuestOnly:z}),[a,z]),P=u.useMemo(()=>{const ce=hD(Ja($),A,{templateId:p.templateIdentifier,locale:p.locale}),q=H._local,{global:re,local:ie}=uD({globalBaseline:H,localBaseline:q??{},patch:$?.initialState??null,formErrors:ce});return{...re,_local:ie}},[H,$,A,p.templateIdentifier,p.locale]),I=u.useMemo(()=>!$||!(!!$.initialState?.local||!!Ja($))?null:{...P._local??{},_forceLocalInit:$.id,_merge:"deep"},[$,P]),L=ce=>typeof ce=="object"&&ce!==null&&!Array.isArray(ce),F=u.useMemo(()=>L($?.initialState?.query)?$.initialState.query:null,[$]),Y=u.useMemo(()=>L($?.initialState?.route)?$.initialState.route:null,[$]),[K,X]=u.useState(()=>Ya({sources:[],isGuestOnly:!1})),Z=u.useRef(null);u.useEffect(()=>{const ce={...P},q=R4({actionDispatcher:M.current,locale:p.locale,initialGlobalState:ce,onChange:ie=>{X(ie.globalState)}});Z.current=q;const re=window.__templateApp;return re?.setGlobalState&&M.current.setGlobalStateUpdater((ie,me)=>{re.setGlobalState(ie,me)}),X(ce),Tl({kind:"applyPatch",activeStateId:p.activeStateId,routePath:p.selectedRoute?.path??null,hasInitialPatch:!!$?.initialState,hasFormErrors:!!Ja($),hasSampleOverride:!!Ig($)}),()=>{q.restore(),Z.current=null}},[p.locale,P]),u.useEffect(()=>{if(!A)return;const ce=window.G7Core;if(!ce||typeof ce.t!="function")return;const q=ce.t,re=p.templateIdentifier,ie=p.locale;return ce.t=(me,Ce)=>{try{const $e=Ce?"|"+Object.entries(Ce).map(([ue,fe])=>`${ue}=${fe}`).join("|"):void 0,U=A.translate(me,{templateId:re,locale:ie},$e);if(U!==me&&!U.startsWith("$t:"))return U}catch{}return q(me,Ce)},typeof window<"u"&&window.dispatchEvent(new CustomEvent(Za)),()=>{window.G7Core?.t&&window.G7Core.t!==q&&(window.G7Core.t=q)}},[A,p.templateIdentifier,p.locale]);const se=u.useMemo(()=>{const ce=g?.raw?.components??[];return p.editMode!=="base"?ce:dD(ce)},[g,p.editMode]),oe=g?.raw?.__editability,G=g?.raw?.__extensionId,ee=u.useMemo(()=>p.editMode!=="extension"||oe!=="ok"?[]:typeof G!="number"?[]:yD(se,G),[p.editMode,oe,G,se]),[ge,V]=u.useState(null),[te,be]=u.useState(!1),Ee=g?.layoutName??null,De=u.useMemo(()=>{const ce=g?.raw?.scripts??[];return Array.isArray(ce)?ce.map(q=>q&&typeof q.src=="string"?q.src:"").join("|"):""},[g]);u.useEffect(()=>{be(!1)},[Ee,De]),u.useEffect(()=>{let ce=!1;const q=g?.raw?.scripts??[];if(!Array.isArray(q)||q.length===0){be(!0);return}const re=[];for(const ie of q){if(!ie?.src||typeof ie.src!="string")continue;if(!kg(ie.src,Ua())){console.warn(`[PreviewCanvas] Blocked untrusted script src (same-origin path or declared trusted host required): ${ie.src}`);continue}const me=ie.id?`script#${CSS.escape(ie.id)}, script[data-g7le-canvas-script="${ie.src}"]`:`script[data-g7le-canvas-script="${ie.src}"], script[src="${ie.src}"]`,Ce=window.document.querySelector(me);if(Ce){const $e=Ce.hasAttribute("data-g7le-canvas-script");if(Ce.hasAttribute("data-g7le-script-loaded")||!$e)continue;re.push(new Promise(ue=>{if(Ce.hasAttribute("data-g7le-script-loaded")){ue();return}Ce.addEventListener("load",()=>ue()),Ce.addEventListener("error",()=>ue())}));continue}re.push(new Promise($e=>{const U=window.document.createElement("script");U.src=ie.src,U.async=!1,ie.id&&(U.id=ie.id),U.setAttribute("data-g7le-canvas-script",ie.src);const ue=()=>{U.setAttribute("data-g7le-script-loaded","1"),$e()};U.addEventListener("load",ue),U.addEventListener("error",ue),window.document.head.appendChild(U)}))}if(re.length===0){be(!0);return}return Promise.all(re).then(()=>{ce||requestAnimationFrame(()=>{requestAnimationFrame(()=>{ce||be(!0)})})}),()=>{ce=!0}},[g]);const Se=u.useMemo(()=>g?.raw?.data_sources??[],[g]),Ue=u.useMemo(()=>g?.raw?.computed??{},[g]),[Me,Oe]=u.useState({}),Ye=u.useMemo(()=>{if(p.editMode!=="iteration_item")return Me;const ce=g?.iterationContext;if(!ce)return Me;const q=g?.raw?.components??[],re=Be({children:q},ce.sourceIndexPath),ie=re&&re.iteration&&typeof re.iteration.source=="string"?re.iteration.source:null;return ie?aD(Me,ie):Me},[p.editMode,Me,g]),[ze,Xe]=u.useState(0);u.useEffect(()=>{if(typeof window>"u")return;const ce=()=>Xe(q=>q+1);return window.addEventListener(kt,ce),()=>window.removeEventListener(kt,ce)},[]);const ve=u.useMemo(()=>tD(p.selectedRoute?.path??null),[p.selectedRoute?.path]);if(u.useEffect(()=>{let ce=!1;return(async()=>{if(Se.length===0){ce||Oe({});return}try{const q=await O.fetchDataSources(Se,ve,new URLSearchParams,void 0,void 0);ce||Oe(q)}catch{ce||Oe({})}})(),()=>{ce=!0}},[Se,O,ve]),u.useEffect(()=>{const ce=y;if(!ce||typeof window>"u")return;let q=0,re=0,ie=0;const me=()=>pD(ce,window.innerHeight);q=window.requestAnimationFrame(()=>{re=window.requestAnimationFrame(me)});let Ce=null;return typeof MutationObserver<"u"&&(Ce=new MutationObserver(()=>{ie||(ie=window.requestAnimationFrame(()=>{ie=0,me()}))}),Ce.observe(ce,{childList:!0,subtree:!0})),()=>{window.cancelAnimationFrame(q),window.cancelAnimationFrame(re),ie&&window.cancelAnimationFrame(ie),Ce&&Ce.disconnect()}},[y,se,te,w,Me]),u.useEffect(()=>{const ce=y;if(!ce||typeof window>"u"||p.editMode!=="extension"||oe!=="ok"||ee.length===0){V(null);return}let q=0,re=0;const ie=ue=>{if(!ue)return!1;const fe=ue.getBoundingClientRect();return fe.width>0&&fe.height>0},me=ue=>{const fe=ce.querySelector(`[data-editor-path="${ue}"]`);if(ie(fe))return!0;const xe=ce.querySelectorAll(`[data-editor-path^="${ue}.children."]`);for(const he of Array.from(xe))if(ie(he))return!0;return!1},Ce=()=>{V(ee.some(me))};q=window.requestAnimationFrame(()=>{re=window.requestAnimationFrame(Ce)});let $e=null,U=0;return typeof MutationObserver<"u"&&($e=new MutationObserver(()=>{U||(U=window.requestAnimationFrame(()=>{U=0,Ce()}))}),$e.observe(ce,{childList:!0,subtree:!0})),()=>{window.cancelAnimationFrame(q),window.cancelAnimationFrame(re),U&&window.cancelAnimationFrame(U),$e&&$e.disconnect()}},[y,p.editMode,oe,ee,te,Me]),u.useEffect(()=>{if(typeof window>"u"||p.editMode!=="base")return;const ce=window.document,q="g7le-base-slot-style";if(ce.getElementById(q))return;const re=ce.createElement("style");re.id=q;const ie=T("layout_editor.preview.slot_area_label");return re.textContent=`.g7le-preview-frame [data-editor-slot]{position:relative;min-height:96px;outline:2px dashed #94a3b8;outline-offset:-2px;}.g7le-preview-frame [data-editor-slot]::after{content:'${ie.replace(/'/g,"\\'")} · ' attr(data-editor-slot);position:absolute;top:8px;left:50%;transform:translateX(-50%);font-size:12px;line-height:1;color:#64748b;background:rgba(241,245,249,0.92);border:1px solid #cbd5e1;border-radius:9999px;padding:4px 12px;pointer-events:none;white-space:nowrap;}`,ce.head.appendChild(re),()=>{ce.getElementById(q)?.remove()}},[p.editMode,T]),!(p.editMode==="extension"||p.editMode==="iteration_item")&&(p.selectedRoute?.layoutName===null||p.selectedRoute===null))return i.jsx("div",{className:"g7le-preview-canvas g7le-preview-canvas--empty","data-testid":"g7le-preview-empty",style:er,children:T("layout_editor.preview.empty")});if(m)return i.jsx("div",{className:"g7le-preview-canvas g7le-preview-canvas--loading","data-testid":"g7le-preview-loading",style:er,children:T("layout_editor.preview.loading")});if(h)return i.jsx(qa,{error:h});if(D)return i.jsx(qa,{error:D});if(!N||E===null||A===null)return i.jsx("div",{className:"g7le-preview-canvas g7le-preview-canvas--assets-loading","data-testid":"g7le-preview-assets-loading",style:er,children:T("layout_editor.preview.assets_loading")});if(se.length===0)return i.jsx("div",{className:"g7le-preview-canvas g7le-preview-canvas--empty-layout","data-testid":"g7le-preview-empty-layout",style:er,children:T("layout_editor.preview.empty_layout")});const We=p.editMode==="extension"?oe==="no-injection"?"no-injection":oe==="ok"&&ee.length>0&&ge!==!0?"gated-no-state":null:null;return te?i.jsx("div",{className:"g7le-preview-canvas","data-testid":"g7le-preview-canvas",style:{overflow:"visible",background:"#f1f5f9",padding:24,display:"flex",justifyContent:"center",alignItems:"flex-start",minHeight:"calc(100vh - var(--g7le-header-h, 0px))",boxSizing:"border-box"},children:i.jsxs("div",{className:"g7le-preview-frame-wrapper","data-testid":"g7le-preview-frame-wrapper",style:{position:"relative",width:w,transform:`scale(${k})`,transformOrigin:"top center"},children:[i.jsx("div",{className:x==="dark"?"g7le-preview-frame g7le-preview-dark":"g7le-preview-frame","data-testid":"g7le-preview-frame","data-color-scheme":x,ref:ce=>{v.current=ce,_(ce)},style:{width:w,background:"#fff",border:"1px solid #cbd5e1",boxShadow:"0 1px 3px rgba(0,0,0,0.05)",minHeight:400,position:"relative",transform:"translateZ(0)",overflow:"hidden"},children:i.jsx(h7,{overrideWidth:w,children:se.map((ce,q)=>i.jsx(g7,{componentDef:ce,dataContext:{...Ye,...rD(K),_global:iD(K),_computedDefinitions:Ue,...I?{_localInit:I,_local:{...Me._local??{},...K._local??{}}}:{},route:bD({...Me.route??{},...ve,path:p.selectedRoute?.path??""},Y),query:{...Me.query??{},...F??{}}},translationContext:{templateId:p.templateIdentifier,locale:p.locale},registry:E,bindingEngine:B.current,translationEngine:Pt.getInstance(),actionDispatcher:M.current,isEditMode:!0,isRootRenderer:q===0,componentPath:`${q}`,onComponentSelect:(re,ie)=>{},onComponentHover:(re,ie)=>{}},`${ce.id??`preview-${q}`}:t${ze}${p.editMode==="extension"?`:s${te?1:0}`:""}`))})}),i.jsx("div",{className:"g7le-overlay-layer","data-testid":"g7le-overlay-layer",style:{position:"absolute",left:0,top:0,width:w,height:y?y.getBoundingClientRect().height/(k||1):"100%",overflow:"visible",pointerEvents:"none"},children:i.jsx(B4,{frameEl:y,manifest:t,nesting:n,componentPalette:o,spec:r,permissionCandidates:s,resolveRouteMatch:l,onNavigateToDestination:d,onSessionAddedPathsChange:c})}),We!==null&&i.jsx("div",{className:"g7le-preview-canvas--extension-fallback","data-testid":"g7le-preview-extension-fallback","data-fallback-reason":We,style:{position:"absolute",left:0,top:0,right:0,bottom:0,minHeight:y?y.getBoundingClientRect().height/(k||1):400,background:"#f1f5f9",display:"flex",alignItems:"flex-start",justifyContent:"center",padding:24,boxSizing:"border-box",pointerEvents:"auto",zIndex:50},children:i.jsxs("div",{className:"g7le-extension-fallback-card",style:{maxWidth:440,textAlign:"center",position:"sticky",top:"calc(40vh - var(--g7le-header-h, 0px))"},children:[i.jsx("p",{className:"g7le-extension-fallback-title",style:{fontWeight:600,marginBottom:8,color:"#0f172a"},children:T(We==="no-injection"?"layout_editor.preview.extension_no_injection_title":"layout_editor.preview.extension_gated_title")}),i.jsx("p",{className:"g7le-extension-fallback-desc",style:{color:"#475569",lineHeight:1.6},children:We==="no-injection"?T("layout_editor.preview.extension_no_injection_desc"):p.availableStates.length>1?T("layout_editor.preview.extension_gated_desc_with_states"):T("layout_editor.preview.extension_gated_desc")})]})})]})}):i.jsx("div",{className:"g7le-preview-canvas g7le-preview-canvas--scripts-loading","data-testid":"g7le-preview-scripts-loading",style:er,children:T("layout_editor.preview.loading")})}function hD(e,t,n){if(!e)return;if(!t)return e;const o=s=>{if(typeof s=="string"&&s.startsWith("$t:"))try{const a=s.slice(3),l=a.indexOf("|");if(l===-1)return t.translate(a,n);const d=a.slice(0,l),c=a.slice(l);return t.translate(d,n,c)}catch{return s}return Array.isArray(s)?s.map(o):s},r={};for(const[s,a]of Object.entries(e))r[s]=o(a);return r}function yD(e,t){const n=[],o=(r,s)=>{Array.isArray(r)&&r.forEach((a,l)=>{if(!a||typeof a!="object")return;const d=s?`${s}.children.${l}`:`${l}`,c=a.__source;if(c?.kind==="extension"&&c.extensionId===t){n.push(d);return}Array.isArray(a.children)&&o(a.children,d)})};return o(e,""),n}function bD(e,t){if(!t)return e;const n={...e};for(const[o,r]of Object.entries(t))r===null?delete n[o]:n[o]=r;return n}const er={flex:1,display:"flex",alignItems:"center",justifyContent:"center",padding:24,background:"#f1f5f9",color:"#64748b",fontSize:13},mD={desktop:"layout_editor.device.desktop",tablet:"layout_editor.device.tablet",mobile:"layout_editor.device.mobile",portable:"layout_editor.device.portable"};function vD(){const{device:e,zoom:t,customWidth:n,setDevice:o,setZoom:r,setCustomWidth:s,colorScheme:a,setColorScheme:l}=Ka(),{t:d}=tt(),c=Rf();return i.jsxs("div",{className:"g7le-device-toolbar","data-testid":"g7le-device-toolbar",style:{display:"inline-flex",alignItems:"center",gap:8,padding:"4px 8px",background:"#fff",border:"1px solid #cbd5e1",borderRadius:6},children:[i.jsxs("div",{role:"group","aria-label":d("layout_editor.device.group_label"),children:[c.map(p=>{const f=e===p,b=mD[p],g=b?d(b):p;return i.jsx("button",{type:"button","data-testid":`g7le-device-${p}`,"aria-pressed":f,onClick:()=>o(p),style:{padding:"2px 8px",marginRight:2,fontSize:12,cursor:"pointer",background:f?"#0f172a":"#f8fafc",color:f?"#fff":"#0f172a",border:"1px solid #cbd5e1",borderRadius:4},children:g},p)}),(()=>{const p=e==="custom";return i.jsx("button",{type:"button","data-testid":"g7le-device-custom","aria-pressed":p,onClick:()=>o("custom"),style:{padding:"2px 8px",marginRight:2,fontSize:12,cursor:"pointer",background:p?"#0f172a":"#f8fafc",color:p?"#fff":"#0f172a",border:"1px solid #cbd5e1",borderRadius:4},children:d("layout_editor.device.custom")})})()]}),e==="custom"&&i.jsxs("div",{style:{display:"inline-flex",alignItems:"center",gap:4},children:[i.jsx("label",{htmlFor:"g7le-custom-width-input",style:{fontSize:11,color:"#475569"},children:d("layout_editor.device.custom_width_label")}),i.jsx("input",{id:"g7le-custom-width-input",type:"number",min:bg,max:mg,step:10,value:n,"data-testid":"g7le-custom-width-input","aria-label":d("layout_editor.device.custom_width_label"),onChange:p=>{const f=parseInt(p.currentTarget.value,10);Number.isFinite(f)&&s(f)},style:{width:72,padding:"2px 6px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:4}}),i.jsx("span",{style:{fontSize:11,color:"#475569"},children:"px"})]}),i.jsx("div",{role:"group","aria-label":d("layout_editor.toolbar.color_scheme_group"),style:{display:"inline-flex",gap:2},children:["light","dark"].map(p=>{const f=a===p;return i.jsx("button",{type:"button","data-testid":`g7le-toolbar-scheme-${p}`,"aria-pressed":f,onClick:()=>l(p),style:{padding:"2px 8px",fontSize:12,cursor:"pointer",background:f?"#0f172a":"#f8fafc",color:f?"#fff":"#0f172a",border:"1px solid #cbd5e1",borderRadius:4},children:d(p==="light"?"layout_editor.toolbar.color_scheme_light":"layout_editor.toolbar.color_scheme_dark")},p)})}),i.jsxs("div",{style:{display:"inline-flex",alignItems:"center",gap:4},children:[i.jsx("span",{style:{fontSize:11,color:"#475569"},children:d("layout_editor.zoom.label")}),i.jsx("input",{type:"range",min:.5,max:1,step:.05,value:t,"data-testid":"g7le-zoom-slider","aria-label":d("layout_editor.zoom.aria_label"),onChange:p=>r(parseFloat(p.currentTarget.value)),style:{width:100}}),i.jsxs("span",{style:{fontSize:11,color:"#475569",minWidth:36,textAlign:"right"},children:[Math.round(t*100),"%"]})]}),i.jsx(Jb,{})]})}function _D(){const{t:e}=tt(),{state:t,dispatch:n}=Qe(),{availableStates:o,activeStateId:r}=t,[,s]=u.useState(0);u.useEffect(()=>{if(typeof window>"u")return;const p=()=>s(b=>b+1),f=window.requestAnimationFrame(p);return window.addEventListener(Za,p),()=>{window.cancelAnimationFrame(f),window.removeEventListener(Za,p)}},[]);const a=u.useCallback((p,f)=>{if(!p)return f;if(!p.startsWith("$t:"))return p;const b=p.slice(3),g=window.G7Core;if(g&&typeof g.t=="function"){const h=g.t(b);if(h&&h!==b&&!h.startsWith("$t:"))return h}const m=e(b);return m&&m!==b?m:f},[e]);if(!Array.isArray(o)||o.length<=1)return null;const l=p=>{const f=p.target.value;n({type:"SET_ACTIVE_STATE",activeStateId:f}),Tl({kind:"change",activeStateId:f,routePath:t.selectedRoute?.path??null,availableCount:o.length})},d=o.find(p=>p.id===r)??o[0],c=d?.description?a(d.description,""):"";return i.jsxs("div",{className:"g7le-state-switcher","data-testid":"g7le-state-switcher","data-active-state":r??"",style:{display:"inline-flex",alignItems:"center",gap:6,padding:"4px 8px",background:"#fff",border:"1px solid #cbd5e1",borderRadius:6},children:[i.jsx("span",{style:{fontSize:12,fontWeight:600,color:"#475569",whiteSpace:"nowrap"},children:e("layout_editor.toolbar.state")}),i.jsx("select",{"data-testid":"g7le-state-switcher-select","aria-label":e("layout_editor.toolbar.state"),value:r??o[0].id,onChange:l,style:{fontSize:12,padding:"2px 6px",border:"1px solid #cbd5e1",borderRadius:4,background:"#fff",color:"#0f172a",cursor:"pointer",outline:"none"},children:o.map(p=>i.jsx("option",{value:p.id,children:a(p.label,p.id)},p.id))}),c&&i.jsx("span",{"data-testid":"g7le-state-switcher-description",style:{fontSize:11,color:"#94a3b8",whiteSpace:"nowrap"},children:c})]})}function xD(e,t,n){if(e==="extension"){if(typeof n=="string"&&n.length>0){const o=Qa(n,"__base__/");return o?{kind:"base",match:o}:{kind:"route",match:n}}return null}if(!t)return null;switch(e){case"route":return{kind:"route",match:t};case"base":{const o=Qa(t,"__base__/");return o?{kind:"base",match:o}:null}case"modal":{const o=Qa(t,"__modal__/");return o?{kind:"modal",match:o}:null}default:return null}}function wD(e,t){if(!t||!Array.isArray(e)||e.length===0)return[];const n=[];for(const o of e)if(SD(o.scope,t))for(const r of o.items??[])r&&typeof r.id=="string"&&r.id.length>0&&n.push(r);return n}function kD(e){if(e.length===0)return null;const t=e.find(n=>n.default===!0);return t?t.id:e[0].id}function SD(e,t){if(!e||e.kind!==t.kind)return!1;const n=e.match;return typeof n!="string"||n.length===0?!1:t.kind==="route"?CD(t.match,n):n===t.match}function CD(e,t){if(t===e)return!0;if(!t.includes("*"))return!1;const n=t.replace(/[.+?^${}()|[\]\\]/g,"\\$&").replace(/\/\*\\\?/g,"(?:/[^/]+)?").replace(/\*/g,"[^/]+");return new RegExp(`^${n}$`).test(e)}function Qa(e,t){return e.startsWith(t)?e.slice(t.length):""}const $D="__conngroup__/modals/",ED="__conngroup__/extensions/";function ID(e){return e.meta?.editor_label?{label:e.meta.editor_label,labelSource:"editor_label"}:e.meta?.title?{label:e.meta.title,labelSource:"title"}:{label:e.path,labelSource:"path"}}function AD(e){switch(e.source.kind){case"template":return"template";case"module":return`module:${e.source.identifier??""}`;case"plugin":return`plugin:${e.source.identifier??""}`;case"core":return null;default:return null}}function TD(e,t){if(e.meta?.parent&&t.has(e.meta.parent))return e.meta.parent;const n=e.path.split("/").filter(Boolean);for(let o=n.length-1;o>=1;o--){const r="/"+n.slice(0,o).join("/");if(r!==e.path&&t.has(r))return r}return null}function DD(e){const t=new Map;for(const n of e)n.layout&&t.set(n.layout,(t.get(n.layout)??0)+1);return t}function zD(e){const t=new Map;for(const n of e){if(!n.hostLayout)continue;const o=t.get(n.hostLayout)??[];o.push(n),t.set(n.hostLayout,o)}return t}function ND(e){const t=new Map;for(const n of e)for(const o of n.extensions)for(const r of o.hostLayouts??[]){if(!r)continue;const s=t.get(r)??[];s.push({extension:o,group:n}),t.set(r,s)}return t}function MD(e,t){return{path:`__modal__/${e.hostLayout}/${e.modalId}`,layoutName:e.hostLayout,label:e.label??e.modalId,labelSource:e.label?"editor_label":"path",source:{kind:"core",identifier:null},kind:"modal",modalId:e.modalId,modalHostLayout:e.hostLayout,connectedHostRoutePath:t,children:[]}}function LD(e,t){const{extension:n,group:o}=e,r=o.sourceType==="plugin"?"🔌":"🧩";return{path:`__extension__/${n.id}`,layoutName:null,label:`${r} ${o.sourceLabel} · ${n.targetName}`,labelSource:"path",source:{kind:o.sourceType,identifier:o.sourceIdentifier},kind:"extension",extensionId:String(n.id),extensionType:n.extensionType,extensionPriority:n.priority,extensionTargetName:n.targetName,isModified:n.isModified,isInactive:!n.isActive,connectedHostRoutePath:t,children:[]}}function Tg(e,t,n){if(e.kind!=="route"&&e.kind!=="base"||!e.layoutName)return;const o=[],r=t.get(e.layoutName)??[];r.length>0&&o.push({path:`${$D}${e.path}`,layoutName:null,label:`$t:layout_editor.chrome.route_tree.connected.modals|count=${r.length}`,labelSource:"editor_label",source:{kind:"core",identifier:null},kind:"route",children:r.map(a=>MD(a,e.path))});const s=n.get(e.layoutName)??[];s.length>0&&o.push({path:`${ED}${e.path}`,layoutName:null,label:`$t:layout_editor.chrome.route_tree.connected.extensions|count=${s.length}`,labelSource:"editor_label",source:{kind:"core",identifier:null},kind:"route",children:s.map(a=>LD(a,e.path))}),o.length>0&&(e.children=[...o,...e.children??[]])}function PD(e){const t=[],n=new Set(e.routes.map(o=>o.path));for(const o of e.routes){const r=AD(o);if(!r)continue;const{label:s,labelSource:a}=ID(o),l=TD(o,n),d={path:o.path,layoutName:o.layout??null,label:s,labelSource:a,icon:o.meta?.icon??null,source:o.source,isRedirect:!!o.redirect,isHidden:!!o.meta?.hidden,kind:"route",children:[]};t.push({node:d,parentKey:l,groupKey:r})}return t}function OD(e,t){if(e==="template")return"$t:layout_editor.chrome.route_tree.group.template";if(e.startsWith("module:")){const n=e.slice(7);return`$t:layout_editor.chrome.route_tree.group.module|name=${t.moduleDisplayNames[n]??n}`}if(e.startsWith("plugin:")){const n=e.slice(7);return`$t:layout_editor.chrome.route_tree.group.plugin|name=${t.pluginDisplayNames[n]??n}`}return e}function BD(e,t){DD(e.routes);const n=zD(e.modals),o=ND(e.extensionGroups??[]),r=new Map;for(const d of t)r.has(d.groupKey)||r.set(d.groupKey,[]),r.get(d.groupKey).push(d);const s=Array.from(r.keys()).sort((d,c)=>{const p=g=>g==="template"?1:g.startsWith("module:")?2:g.startsWith("plugin:")?3:4,f=p(d),b=p(c);return f!==b?f-b:d.localeCompare(c)}),a=[];if(e.baseLayouts.length>0){const d=e.baseLayouts.map(c=>({path:`__base__/${c.layoutName}`,layoutName:c.layoutName,label:c.label??c.layoutName,labelSource:c.label?"editor_label":"path",source:{kind:"core",identifier:null},kind:"base",children:[]}));for(const c of d)Tg(c,n,o);a.push({path:"__group__/base",layoutName:null,label:"$t:layout_editor.chrome.route_tree.group.base",labelSource:"editor_label",source:{kind:"core",identifier:null},kind:"route",children:d})}for(const d of s){const c=r.get(d),p=new Map;for(const b of c)p.set(b.node.path,b);const f=[];for(const b of c)if(b.parentKey&&p.has(b.parentKey)){const g=p.get(b.parentKey);g.node.children=g.node.children??[],g.node.children.push(b.node)}else f.push(b.node);for(const b of c)Tg(b.node,n,o);a.push({path:`__group__/${d}`,layoutName:null,label:OD(d,e),labelSource:"editor_label",source:{kind:"core",identifier:null},kind:"route",children:f})}if(e.modals.length>0){const d=e.modals.map(c=>({path:`__modal__/${c.hostLayout}/${c.modalId}`,layoutName:c.hostLayout,label:c.label??c.modalId,labelSource:c.label?"editor_label":"path",source:{kind:"core",identifier:null},kind:"modal",modalId:c.modalId,modalHostLayout:c.hostLayout,children:[]}));a.push({path:"__group__/modal",layoutName:null,label:"$t:layout_editor.chrome.route_tree.group.modal",labelSource:"editor_label",source:{kind:"core",identifier:null},kind:"route",children:d})}const l=e.extensionGroups??[];if(l.length>0){const d=l.map(c=>{const p=c.sourceType==="plugin"?"🔌":"🧩",f=c.extensions.map(b=>({path:`__extension__/${b.id}`,layoutName:null,label:`${c.sourceLabel} · ${b.targetName}`,labelSource:"path",source:{kind:c.sourceType,identifier:c.sourceIdentifier},kind:"extension",extensionId:String(b.id),extensionType:b.extensionType,extensionPriority:b.priority,extensionTargetName:b.targetName,isModified:b.isModified,isInactive:!b.isActive,children:[]}));return{path:`__extgroup__/${c.sourceType}/${c.sourceIdentifier}`,layoutName:null,label:`${p} ${c.sourceLabel}`,labelSource:"path",source:{kind:c.sourceType,identifier:c.sourceIdentifier},kind:"route",children:f}});a.push({path:"__group__/extension",layoutName:null,label:"$t:layout_editor.chrome.route_tree.group.extension",labelSource:"editor_label",source:{kind:"core",identifier:null},kind:"route",children:d})}return a}function WD(e){const t=PD(e);return BD(e,t)}const On=mo("useEditorRoutes");function jD(e,t){if(typeof e=="string")return e.length>0?e:null;if(e&&typeof e=="object"){const n=e[t];if(typeof n=="string"&&n.length>0)return n;if(typeof e.en=="string"&&e.en.length>0)return e.en;for(const o of Object.values(e))if(typeof o=="string"&&o.length>0)return o}return null}function Dg(e,t){const n={};if(typeof window>"u")return n;const r=window.G7Config?.[e];if(!Array.isArray(r))return n;for(const s of r)s&&typeof s.identifier=="string"&&(n[s.identifier]=jD(s.display_name,t)??s.identifier);return n}function HD(e){const t=new kd,n=typeof window<"u"?window.G7Config:void 0,o={_global:{settings:n?.settings??{},plugins:n?.plugins??{},modules:n?.modules??{},appConfig:n?.appConfig??{}}};return e.map(r=>{const s={...r};if(typeof s.path=="string"&&s.path.includes("{{")){const a=s.path;let l=t.resolveBindings(s.path,o);l=l.replace(/\/\/+/g,"/")||"/",l.includes("{{")&&(On.warn("편집기 route path 표현식 평가 실패 — fallback 적용",{originalPath:a}),l=a.replace(/\{\{[^}]+\}\}/g,"").replace(/\/\/+/g,"/")||"/"),s.path=l}if(typeof s.redirect=="string"&&s.redirect.includes("{{")){const a=s.redirect;let l=t.resolveBindings(a,o);l=l.replace(/\/\/+/g,"/")||"/",l.includes("{{")&&(On.warn("편집기 route redirect 표현식 평가 실패 — fallback 적용",{originalRedirect:a}),l=a.replace(/\{\{[^}]+\}\}/g,"").replace(/\/\/+/g,"/")||"/"),s.redirect=l}return s})}function FD(e){const{dispatch:t}=Qe(),{translationEngine:n,translationContext:o}=tt(),{templateIdentifier:r,cacheVersion:s}=e;u.useEffect(()=>{let a=!1;const l=s??(typeof window<"u"?window.G7Config?.cache_version:void 0)??"",d=l!==""?`?v=${encodeURIComponent(String(l))}`:"",c=en(`/api/admin/templates/${encodeURIComponent(r)}/editor/routes`,"json",null,d.replace(/^[?&]+/,"")),p=o?.locale??"ko",f=n&&r!==o?.templateId?n.loadTranslations(r,p).then(()=>{On.log("편집 대상 lang 로드 완료",{targetTemplate:r,locale:p})}).catch(h=>{On.warn("편집 대상 lang 로드 실패 — 라우트 라벨 fallback 사용",h)}):Promise.resolve(),b=(()=>{if(typeof fetch!="function")return Promise.reject(so(new Error("fetch unavailable"),"routes"));try{const h=fetch(c,{credentials:"same-origin",headers:Ne()});return!h||typeof h.then!="function"?Promise.reject(so(new Error("fetch returned non-Promise (test stub?)"),"routes")):h.then(async v=>{if(!v.ok){const y=await v.json().catch(()=>null);throw Jr(v.status,y,"routes")}return v.json()})}catch(h){return Promise.reject(h)}})(),g=`/api/admin/templates/${encodeURIComponent(r)}/layout-extensions${d}`,m=(()=>{if(typeof fetch!="function")return Promise.resolve(null);try{const h=fetch(g,{credentials:"same-origin",headers:Ne()});return!h||typeof h.then!="function"?Promise.resolve(null):h.then(async v=>v.ok?await v.json():null).catch(()=>null)}catch{return Promise.resolve(null)}})();return Promise.all([b,f,m]).then(([h,,v])=>{if(a)return;if(!h||!h.success||!Array.isArray(h.data?.routes)){On.warn("routes 응답 형식이 비정상 — empty tree 로 둠",h),t({type:"SET_ROUTES_ERROR",error:{kind:"unknown",status:200,message:"routes 응답 형식이 비정상입니다.",source:"routes"}});return}const y=HD(h.data.routes),_=Dg("activeModules",p),w=Dg("activePlugins",p),k=(h.data.base_layouts??[]).map(A=>({layoutName:A.layout_name,label:A.label??void 0})),x=(h.data.modals??[]).map(A=>({modalId:A.modal_id,hostLayout:A.host_layout,label:A.label??void 0})),$=(v?.success&&Array.isArray(v.data)?v.data:[]).map(A=>({sourceIdentifier:A.source_identifier,sourceType:A.source_type,sourceLabel:A.source_label,extensions:(A.extensions??[]).map(N=>({id:N.id,extensionType:N.extension_type,targetName:N.target_name,priority:N.priority,isActive:N.is_active,isModified:N.is_modified,hostLayouts:Array.isArray(N.host_layouts)?N.host_layouts:[],currentVersion:typeof N.current_version=="number"?N.current_version:void 0}))})),S=WD({routes:y,modals:x,baseLayouts:k,extensionGroups:$,moduleDisplayNames:_,pluginDisplayNames:w});t({type:"SET_ROUTE_TREE",tree:S});const C=h.data.layout_versions,T={};if(C&&typeof C=="object"&&!Array.isArray(C))for(const[A,N]of Object.entries(C))typeof N=="number"&&Number.isFinite(N)&&(T[A]=N);t({type:"SET_LAYOUT_VERSIONS",versions:T});const E={};for(const A of $)for(const N of A.extensions){const D=N.currentVersion;typeof D=="number"&&Number.isFinite(D)&&(E[String(N.id)]=D)}t({type:"SET_EXTENSION_VERSIONS",versions:E}),On.log("routes 로드 완료",{count:y.length,groups:S.length})}).catch(h=>{if(a)return;On.error("routes 로드 실패",h);const v=xp(h)?h:so(h,"routes");t({type:"SET_ROUTES_ERROR",error:v})}),()=>{a=!0}},[r,s,t,n,o?.locale,o?.templateId])}function zg(e,t,n,o=[]){const r=t?Ng(o,t):null,s=r!==null?{path:r.path,layoutName:r.layoutName}:void 0;if(e.startsWith("__base__/")){const a=e.slice(9);if(a)return n({type:"ENTER_BASE_EDIT",layoutName:a,returnRoute:s}),!0}else if(e.startsWith("__modal__/")){const a=e.slice(10);return a&&!a.includes("/")&&t?(n({type:"ENTER_MODAL_EDIT",modalId:a,hostLayout:t,returnRoute:s}),!0):!1}else if(e.startsWith("__extension__/")){const a=e.slice(14);if(a&&!a.includes("/"))return n({type:"ENTER_EXTENSION_EDIT",extensionId:a,returnRoute:s}),!0}else if(e.startsWith("__iteration__/")){const a=e.slice(14);return a&&t?(n({type:"ENTER_ITERATION_ITEM_EDIT",sourcePath:a,hostLayout:t,returnRoute:s}),!0):!1}return!1}function Ng(e,t){for(const n of e){if(n.kind==="route"&&n.layoutName===t)return n;if(n.children&&n.children.length>0){const o=Ng(n.children,t);if(o)return o}}return null}function Ra(e,t){for(const n of e){if(n.kind==="route"&&n.path===t)return n;if(n.children&&n.children.length>0){const o=Ra(n.children,t);if(o)return o}}return null}function KD(e){const{state:t,dispatch:n}=Qe(),o=u.useRef(!1),r=u.useRef(t.editMode);r.current=t.editMode,u.useEffect(()=>{if(o.current||t.routeTree.length===0||typeof window>"u")return;const a=fp(window.location.search);if(a){const c=pp(window.location.search),p=zg(a,c,n,t.routeTree);if(o.current=!0,p)return}const l=up(window.location.search);if(!l){o.current=!0;return}const d=Ra(t.routeTree,l);d&&n({type:"SELECT_ROUTE",route:{path:d.path,layoutName:d.layoutName}}),o.current=!0},[t.routeTree,n]),u.useEffect(()=>{if(typeof window>"u")return;const a=()=>{const l=fp(window.location.search);if(l){const p=pp(window.location.search);if(zg(l,p,n,t.routeTree))return}if(r.current!=="route")switch(r.current){case"base":n({type:"EXIT_BASE_EDIT"});return;case"modal":n({type:"EXIT_MODAL_EDIT"});return;case"extension":n({type:"EXIT_EXTENSION_EDIT"});return;case"iteration_item":n({type:"EXIT_ITERATION_ITEM_EDIT"});return}const d=up(window.location.search);if(!d){n({type:"SELECT_ROUTE",route:null});return}const c=Ra(t.routeTree,d);c&&n({type:"SELECT_ROUTE",route:{path:c.path,layoutName:c.layoutName}})};return window.addEventListener("popstate",a),()=>{window.removeEventListener("popstate",a)}},[t.routeTree,n]);const s=u.useRef("route");u.useEffect(()=>{if(typeof window>"u"||!window.history?.replaceState||t.routeTree.length===0)return;const a=ea(e,t.selectedRoute?.path??null,t.selectedRoute?.layoutName??null),l=`${window.location.pathname}${window.location.search}`,d=s.current;if(s.current=t.editMode,l===a)return;d==="route"&&t.editMode!=="route"&&window.history.pushState?window.history.pushState(window.history.state,"",a):window.history.replaceState(window.history.state,"",a)},[t.selectedRoute,t.editMode,t.routeTree,e])}function VD(){u.useEffect(()=>{if(typeof document>"u")return;const e=document.documentElement;let t=e.classList.contains("dark");const n=()=>{e.classList.contains("dark")&&(t=!0,e.classList.remove("dark"))};n();let o=null;return typeof MutationObserver<"u"&&(o=new MutationObserver(n),o.observe(e,{attributes:!0,attributeFilter:["class"]})),()=>{o?.disconnect(),t&&e.classList.add("dark")}},[])}function UD(){u.useEffect(()=>{if(typeof document>"u"||!document.body)return;const e=document.body,t=()=>{e.style.overflow==="hidden"&&(e.style.overflow=""),e.style.overflowY==="hidden"&&(e.style.overflowY="")};t();let n=null;return typeof MutationObserver<"u"&&(n=new MutationObserver(t),n.observe(e,{attributes:!0,attributeFilter:["style"]})),()=>{n?.disconnect()}},[])}function qD(e){if(typeof e!="string")return null;const t="__extension__/";if(!e.startsWith(t))return null;const n=e.slice(t.length);return n===""||n.includes("/")?null:n}function GD(e){if(Array.isArray(e.components))return e.components;if(Array.isArray(e.injections)){const t=[];return e.injections.forEach((n,o)=>{if(n&&Array.isArray(n.components))for(const r of n.components)t.push({...r,__injectionIndex:o})}),t}return[]}function YD(e,t){if(Array.isArray(e.components)){const n=t.map(o=>el(o));return{...e,components:n}}if(Array.isArray(e.injections)){const n=e.injections.map(o=>({...o}));n.forEach(o=>{Array.isArray(o.components)&&(o.components=[])});for(const o of t){const r=o.__injectionIndex,s=typeof r=="number"&&r>=0&&rel(n))),t}function Mg(e,t){const n=[],o=r=>{if(Array.isArray(r))for(const s of r){if(!s||typeof s!="object")continue;const a=s.__source;if(a?.kind==="extension"&&a.extensionId===t){n.push(s);continue}Array.isArray(s.children)&&o(s.children)}};return o(e),n}function Lg(e,t){const o=Mg(e,t)[0]?.__source;return o&&o.kind==="extension"&&o.extensionId===t?o:{kind:"extension",extensionId:t}}function tl(e,t){for(const n of e){const o=n?.__source;if(o?.kind==="extension"&&o.extensionId===t||Array.isArray(n.children)&&tl(n.children,t))return!0}return!1}function XD(){const{state:e}=Qe(),t=e.templateIdentifier,n=e.editMode==="extension"?qD(e.selectedRoute?.path):null,o=e.editMode==="extension"&&typeof e.selectedRoute?.layoutName=="string"?e.selectedRoute.layoutName:null,[r,s]=u.useState(null),[a,l]=u.useState(!1),[d,c]=u.useState(null),[p,f]=u.useState(!1),[b,g]=u.useState(0),[m,h]=u.useState(null),v=u.useRef({}),y=u.useRef({kind:"extension",extensionId:0}),_=u.useRef(null);u.useEffect(()=>{_.current=r},[r]),u.useEffect(()=>{h(null)},[n]);const w=u.useCallback(async A=>{const N=window.G7Config?.cache_version??0,D=en(`/api/layouts/${encodeURIComponent(t)}/${A}`,"json",`${N}.${Tp()}`,"with_source_meta=1"),B=cr(),M={Accept:"application/json"};B&&(M.Authorization=`Bearer ${B}`);const O=await fetch(D,{headers:M,credentials:"same-origin"});if(!O.ok)return null;const z=await O.json().catch(()=>null);return(z&&(z.data||z))??{}},[t]),k=u.useCallback(async()=>{if(!n){s(null),c(null),f(!1);return}l(!0),c(null);try{const A=window.G7Config?.cache_version??0,N=`/api/admin/templates/${encodeURIComponent(t)}/layout-extensions/${encodeURIComponent(n)}?v=${A}`,D=await fetch(N,{headers:Ne(),credentials:"same-origin"}),B=await D.json().catch(()=>null);if(!D.ok){c(Jr(D.status,B,"layout")),s(null),f(!1);return}const M=(B&&(B.data||B))??{};let O={};const z=M.content;if(typeof z=="string")try{O=JSON.parse(z)}catch{O={}}else z&&typeof z=="object"&&(O=z);const H=M.extension_type==="overlay"?"overlay":"extension_point",P=typeof M.lock_version=="number"?M.lock_version:0,I=Array.isArray(M?.host_layouts)?M.host_layouts.filter(oe=>typeof oe=="string"):[],L=o??(I.length===1?I[0]:m&&I.includes(m)?m:null),F=Number(n);v.current=O;let K=GD(O),X={},Z="pending";const se={kind:"extension",extensionId:F};if(y.current=se,L){const oe=await w(L);if(oe){X=oe;const G=Array.isArray(oe.components)?oe.components:[],ee=Lg(G,F);if(y.current=ee,tl(G,F))K=G,Z="ok";else{const V=(Array.isArray(oe.modals)?oe.modals:[]).filter(te=>te&&typeof te=="object"&&tl([te],F));if(V.length>0){const te=V.map(be=>({...be,props:{...be.props??{},isOpen:!0}}));K=[...G,...te],y.current=Lg(V,F),Z="ok"}else K=G,Z="no-injection"}}}s({extensionId:F,content:O,extensionType:H,components:K,lockVersion:P,hostLayouts:I,hostLayoutName:L,hostRaw:X,editability:Z}),f(!1),st({op:"load",layoutName:`extension:${n}`,editMode:"extension",statusCode:D.status,timestamp:Date.now()})}catch(A){c(so(A,"layout")),s(null),f(!1)}finally{l(!1)}},[t,n,m,o,w]);u.useEffect(()=>{k()},[k]);const x=u.useCallback(A=>{h(A)},[]),$=u.useCallback(A=>{s(N=>N&&{...N,components:A}),f(!0),st({op:"patch",layoutName:`extension:${_.current?.extensionId??""}`,editMode:"extension",isDirty:!0,timestamp:Date.now()})},[]),S=u.useCallback(A=>{s(N=>N&&{...N,components:A(N.components)}),f(!0)},[]),C=u.useCallback(async()=>{const A=_.current;if(!A)return{kind:"guard_no_document"};const N=`/api/admin/templates/${encodeURIComponent(t)}/layout-extensions/${A.extensionId}`,D=Mg(A.components,A.extensionId),B=D.length>0?D:A.components,M=YD(v.current,B);st({op:"save",layoutName:`extension:${A.extensionId}`,editMode:"extension",saveTarget:"layout_extension",endpoint:N,isDirty:p,timestamp:Date.now()});let O;try{O=await fetch(N,{method:"PUT",headers:Ne({"Content-Type":"application/json"}),credentials:"same-origin",body:JSON.stringify({content:M,expected_lock_version:A.lockVersion})})}catch(H){return{kind:"network_error",message:H instanceof Error?H.message:"network error"}}const z=await O.json().catch(()=>null);if(st({op:"save_response",layoutName:`extension:${A.extensionId}`,editMode:"extension",endpoint:N,statusCode:O.status,conflict:O.status===409?{currentVersion:z?.errors?.current_version??z?.current_version,yourVersion:z?.errors?.your_version??z?.your_version}:void 0,timestamp:Date.now()}),O.ok){const H=z?.data??{},P=typeof H.lock_version=="number"?H.lock_version:A.lockVersion+1;s(L=>L&&{...L,lockVersion:P}),f(!1),g(L=>L+1);const I=typeof H.current_version=="number"?H.current_version:void 0;return{kind:"success",newLockVersion:P,newContentVersion:I,savedExtensionId:String(A.extensionId)}}return O.status===409?{kind:"concurrent_modification",currentVersion:z?.current_version??-1,yourVersion:z?.your_version??A.lockVersion}:O.status===422?{kind:"validation_failed",status:422,errors:z?.errors??null}:{kind:"network_error",message:z?.message??`HTTP ${O.status}`}},[t,p]),T=u.useCallback(async()=>{ia(),await k()},[k]),E=!!r&&r.hostLayouts.length>1&&r.hostLayoutName===null;return{document:r,isLoading:a,error:d,isDirty:p,saveSuccessCounter:b,setLayoutComponents:$,patchLayout:S,save:C,reload:T,needsHostPicker:E,selectHost:x}}const Pg=new Set;function JD(e,t,n){const r=e&&Object.keys(e).length>0?e:t,s=Array.isArray(r.data_sources)?r.data_sources:[],a=Array.isArray(t.data_sources)?t.data_sources:[],l=new Set,d=[];for(const c of[...s,...a]){const p=c&&typeof c=="object"?c.id:void 0,f=typeof p=="string"?p:`__anon_${d.length}`;l.has(f)||(l.add(f),d.push(c))}return{...r,components:n,...d.length>0?{data_sources:d}:{},lock_version:void 0}}function ZD(e){return{document:e.document?{layoutName:`extension:${e.document.extensionId}`,raw:{...JD(e.document.hostRaw,e.document.content,e.document.components),__editability:e.document.editability,__extensionId:e.document.extensionId},lockVersion:e.document.lockVersion}:null,isLoading:e.isLoading,error:e.error,isDirty:e.isDirty,saveSuccessCounter:e.saveSuccessCounter,reload:e.reload,patchLayout:e.patchLayout,setLayoutComponents:e.setLayoutComponents,patchDocumentRaw:()=>{},dirtyKeys:Pg,dirtyLayoutNames:Pg,invalidateCurrentCache:()=>{},reloadCounter:0,save:()=>e.save(),markDirty:()=>{}}}function QD({hostLayouts:e,extensionLabel:t,onSelect:n}){const{t:o}=tt();return i.jsx("div",{className:"g7le-host-picker__backdrop","data-testid":"g7le-host-picker",role:"dialog","aria-modal":"true","aria-label":o("layout_editor.host_picker.title"),style:{position:"fixed",inset:0,zIndex:9500,display:"flex",alignItems:"center",justifyContent:"center",background:"rgba(15, 23, 42, 0.55)"},children:i.jsxs("div",{className:"g7le-host-picker__panel",style:{width:"min(520px, 92vw)",maxHeight:"80vh",overflow:"auto",background:"#ffffff",color:"#0f172a",borderRadius:12,boxShadow:"0 20px 48px rgba(15, 23, 42, 0.28)",padding:"20px 22px"},children:[i.jsx("h2",{className:"g7le-host-picker__title",style:{margin:"0 0 6px",fontSize:16,fontWeight:700},children:o("layout_editor.host_picker.title")}),i.jsx("p",{className:"g7le-host-picker__desc",style:{margin:"0 0 16px",fontSize:13,color:"#475569",lineHeight:1.6},children:t?o("layout_editor.host_picker.description_with_label",{label:t}):o("layout_editor.host_picker.description")}),i.jsx("ul",{className:"g7le-host-picker__list",style:{listStyle:"none",margin:0,padding:0,display:"grid",gap:8},children:e.map(r=>i.jsx("li",{children:i.jsxs("button",{type:"button",className:"g7le-host-picker__item","data-testid":"g7le-host-picker-item","data-host":r,onClick:()=>n(r),style:{width:"100%",textAlign:"left",padding:"12px 14px",border:"1px solid #e2e8f0",borderRadius:8,background:"#f8fafc",cursor:"pointer",fontSize:13,fontWeight:600,color:"#0f172a",display:"flex",alignItems:"center",gap:8},children:[i.jsx("span",{"aria-hidden":!0,style:{fontSize:14},children:"🖼"}),i.jsx("span",{style:{flex:1,minWidth:0,overflow:"hidden",textOverflow:"ellipsis"},children:r}),i.jsx("span",{"aria-hidden":!0,style:{color:"#94a3b8"},children:"→"})]})},r))})]})})}function RD(e,t){const[n,o]=u.useState(!1),[r,s]=u.useState(null),a=u.useCallback(async l=>{if(!t||!l)return{kind:"no_document"};o(!0),s(null);try{const d=_o(l),c=`/api/admin/templates/${encodeURIComponent(e)}/layouts/${t}/preview`,p=await fetch(c,{method:"POST",credentials:"same-origin",headers:Ne({"Content-Type":"application/json"}),body:JSON.stringify({content:JSON.stringify(d)})}),f=await p.json().catch(()=>null);if(!p.ok){const h=f?.message??`HTTP ${p.status}`;return s(h),{kind:"network_error",message:h}}const b=f?.data,g=typeof b?.token=="string"?b.token:"",m=typeof b?.preview_url=="string"&&b.preview_url?b.preview_url:g?`/preview/${g}`:"";return m?(window.G7Core?.dispatch?.({handler:"openWindow",params:{path:m}}),{kind:"success",previewUrl:m,token:g}):(s("invalid preview response"),{kind:"network_error",message:"invalid preview response"})}catch(d){const c=d instanceof Error?d.message:"network error";return s(c),{kind:"network_error",message:c}}finally{o(!1)}},[e,t]);return{isCreating:n,error:r,createPreview:a}}const de="layout_editor.action_recipe",bn={setState:{label:`$t:${de}.set_state.label`,params:[{key:"target",label:`$t:${de}.set_state.param_target`,widget:"select",options:[{value:"local"},{value:"global"}]},{key:"state",label:`$t:${de}.set_state.param_state`,widget:"state-key-value"},{key:"merge",label:`$t:${de}.set_state.param_merge`,widget:"toggle"}],build:{handler:"setState",params:{target:"{{target}}",merge:"{{merge}}","...":"{{state}}"}}},loadFromLocalStorage:{label:`$t:${de}.load_local.label`,params:[{key:"key",label:`$t:${de}.load_local.param_key`,widget:"text",required:!0},{key:"stateKey",label:`$t:${de}.load_local.param_state_key`,widget:"state-key-picker"},{key:"defaultValue",label:`$t:${de}.load_local.param_default`,widget:"text"}],build:{handler:"loadFromLocalStorage",params:{key:"{{key}}",stateKey:"{{stateKey}}",defaultValue:"{{defaultValue}}"}}},saveToLocalStorage:{label:`$t:${de}.save_local.label`,params:[{key:"key",label:`$t:${de}.save_local.param_key`,widget:"text",required:!0},{key:"value",label:`$t:${de}.save_local.param_value`,widget:"data-chip"}],build:{handler:"saveToLocalStorage",params:{key:"{{key}}",value:"{{value}}"}}},navigate:{label:`$t:${de}.navigate.label`,params:[{key:"path",label:`$t:${de}.navigate.param_path`,widget:"page-picker",required:!0},{key:"query",label:`$t:${de}.navigate.param_query`,widget:"key-value"},{key:"mergeQuery",label:`$t:${de}.navigate.param_merge_query`,widget:"toggle"},{key:"scroll",label:`$t:${de}.navigate.param_scroll`,widget:"select",options:[{value:"top"},{value:"preserve"},{value:"none"}]},{key:"replace",label:`$t:${de}.navigate.param_replace`,widget:"toggle"},{key:"transition_overlay_target",label:`$t:${de}.navigate.param_overlay_target`,widget:"component-target-picker",dependsOn:{param:"replace",equals:!0}}],build:{handler:"navigate",params:{path:"{{path}}",query:"{{query}}",mergeQuery:"{{mergeQuery}}",scroll:"{{scroll}}",replace:"{{replace}}",transition_overlay_target:"{{transition_overlay_target}}"}}},openWindow:{label:`$t:${de}.open_window.label`,params:[{key:"target",label:`$t:${de}.open_window.param_target`,widget:"page-picker",required:!0},{key:"windowTarget",label:`$t:${de}.open_window.param_window_target`,widget:"select",options:[{value:"_blank"},{value:"_self"}]}],build:{handler:"openWindow",target:"{{target}}",params:{windowTarget:"{{windowTarget}}"}}},navigateBack:{label:`$t:${de}.navigate_back.label`,params:[],build:{handler:"navigateBack"}},navigateForward:{label:`$t:${de}.navigate_forward.label`,params:[],build:{handler:"navigateForward"}},replaceUrl:{label:`$t:${de}.replace_url.label`,params:[{key:"path",label:`$t:${de}.replace_url.param_path`,widget:"page-picker"},{key:"query",label:`$t:${de}.replace_url.param_query`,widget:"key-value"}],build:{handler:"replaceUrl",params:{path:"{{path}}",query:"{{query}}"}}},toast:{label:`$t:${de}.toast.label`,params:[{key:"message",label:`$t:${de}.toast.param_message`,widget:"i18n-text",required:!0},{key:"type",label:`$t:${de}.toast.param_type`,widget:"select",options:[{value:"info"},{value:"success"},{value:"warning"},{value:"error"}]},{key:"duration",label:`$t:${de}.toast.param_duration`,widget:"number"},{key:"icon",label:`$t:${de}.toast.param_icon`,widget:"text"}],build:{handler:"toast",params:{message:"{{message}}",type:"{{type}}",duration:"{{duration}}",icon:"{{icon}}"}}},openModal:{label:`$t:${de}.open_modal.label`,params:[{key:"target",label:`$t:${de}.open_modal.param_target`,widget:"modal-picker",required:!0}],build:{handler:"openModal",target:"{{target}}"}},closeModal:{label:`$t:${de}.close_modal.label`,params:[],build:{handler:"closeModal"}},showAlert:{label:`$t:${de}.show_alert.label`,params:[{key:"target",label:`$t:${de}.show_alert.param_message`,widget:"i18n-text",required:!0}],build:{handler:"showAlert",target:"{{target}}"}},setError:{label:`$t:${de}.set_error.label`,params:[{key:"target",label:`$t:${de}.set_error.param_message`,widget:"i18n-text"},{key:"stateTarget",label:`$t:${de}.set_error.param_state_target`,widget:"select",options:[{value:"local"},{value:"global"}]}],build:{handler:"setError",target:"{{target}}",params:{stateTarget:"{{stateTarget}}"}}},refetchDataSource:{label:`$t:${de}.refetch.label`,params:[{key:"dataSourceId",label:`$t:${de}.refetch.param_data_source`,widget:"datasource-picker",required:!0},{key:"sync",label:`$t:${de}.refetch.param_sync`,widget:"toggle"}],build:{handler:"refetchDataSource",params:{dataSourceId:"{{dataSourceId}}",sync:"{{sync}}"}}},appendDataSource:{label:`$t:${de}.append_data.label`,params:[{key:"dataSourceId",label:`$t:${de}.append_data.param_data_source`,widget:"datasource-picker",required:!0},{key:"dataPath",label:`$t:${de}.append_data.param_data_path`,widget:"text"},{key:"newData",label:`$t:${de}.append_data.param_new_data`,widget:"data-chip"}],build:{handler:"appendDataSource",params:{dataSourceId:"{{dataSourceId}}",dataPath:"{{dataPath}}",newData:"{{newData}}"}}},updateDataSource:{label:`$t:${de}.update_data.label`,params:[{key:"dataSourceId",label:`$t:${de}.update_data.param_data_source`,widget:"datasource-picker",required:!0},{key:"data",label:`$t:${de}.update_data.param_data`,widget:"data-chip"},{key:"merge",label:`$t:${de}.update_data.param_merge`,widget:"toggle"}],build:{handler:"updateDataSource",params:{dataSourceId:"{{dataSourceId}}",data:"{{data}}",merge:"{{merge}}"}}},scrollIntoView:{label:`$t:${de}.scroll.label`,params:[{key:"selector",label:`$t:${de}.scroll.param_selector`,widget:"text",required:!0},{key:"behavior",label:`$t:${de}.scroll.param_behavior`,widget:"select",options:[{value:"smooth"},{value:"auto"}]},{key:"block",label:`$t:${de}.scroll.param_block`,widget:"select",options:[{value:"start"},{value:"center"},{value:"end"},{value:"nearest"}]}],build:{handler:"scrollIntoView",params:{selector:"{{selector}}",behavior:"{{behavior}}",block:"{{block}}"}}},login:{label:`$t:${de}.login.label`,params:[{key:"body",label:`$t:${de}.login.param_body`,widget:"key-value"}],build:{handler:"login",params:{body:"{{body}}"}}},logout:{label:`$t:${de}.logout.label`,params:[{key:"target",label:`$t:${de}.logout.param_target`,widget:"text"}],build:{handler:"logout",target:"{{target}}"}},setLocale:{label:`$t:${de}.set_locale.label`,params:[{key:"target",label:`$t:${de}.set_locale.param_target`,widget:"locale-picker",required:!0}],build:{handler:"setLocale",target:"{{target}}"}},emitEvent:{label:`$t:${de}.emit_event.label`,params:[{key:"event",label:`$t:${de}.emit_event.param_event`,widget:"text",required:!0},{key:"data",label:`$t:${de}.emit_event.param_data`,widget:"data-chip"}],build:{handler:"emitEvent",params:{event:"{{event}}",data:"{{data}}"}}},apiCall:{label:`$t:${de}.api_call.label`,params:[{key:"target",label:`$t:${de}.api_call.param_target`,widget:"page-picker",required:!0},{key:"method",label:`$t:${de}.api_call.param_method`,widget:"select",options:[{value:"GET"},{value:"POST"},{value:"PUT"},{value:"PATCH"},{value:"DELETE"}]},{key:"body",label:`$t:${de}.api_call.param_body`,widget:"key-value"},{key:"query",label:`$t:${de}.api_call.param_query`,widget:"key-value"},{key:"identity_target_email",label:`$t:${de}.api_call.param_identity_target_email`,widget:"data-chip"},{key:"identity_target_phone",label:`$t:${de}.api_call.param_identity_target_phone`,widget:"data-chip"},{key:"onSuccess",label:`$t:${de}.api_call.param_on_success`,widget:"action-list"},{key:"onError",label:`$t:${de}.api_call.param_on_error`,widget:"action-list"}],build:{handler:"apiCall",target:"{{target}}",identity_target:{email:"{{identity_target_email}}",phone:"{{identity_target_phone}}"},onSuccess:"{{onSuccess}}",onError:"{{onError}}",params:{method:"{{method}}",body:"{{body}}",query:"{{query}}"}}},conditions:{label:`$t:${de}.conditions.label`,params:[{key:"branches",label:`$t:${de}.conditions.param_branches`,widget:"branch-list"}],build:{handler:"conditions",conditions:"{{branches}}"}},sequence:{label:`$t:${de}.sequence.label`,params:[{key:"actions",label:`$t:${de}.sequence.param_actions`,widget:"action-list"}],build:{handler:"sequence",params:{actions:"{{actions}}"}}},parallel:{label:`$t:${de}.parallel.label`,params:[{key:"actions",label:`$t:${de}.parallel.param_actions`,widget:"action-list"}],build:{handler:"parallel",params:{actions:"{{actions}}"}}},switch:{label:`$t:${de}.switch.label`,params:[{key:"value",label:`$t:${de}.switch.param_value`,widget:"data-chip"},{key:"cases",label:`$t:${de}.switch.param_cases`,widget:"action-list",advanced:!0}],build:{handler:"switch",params:{value:"{{value}}",cases:"{{cases}}"}}},suppress:{label:`$t:${de}.suppress.label`,params:[],build:{handler:"suppress"}}};function e8(){return{actionRecipes:bn,initActionRecipes:bn,errorRecipes:{navigate:bn.navigate,openModal:bn.openModal,toast:bn.toast,setState:bn.setState,sequence:bn.sequence,parallel:bn.parallel,showErrorPage:{label:`$t:${de}.show_error_page.label`,params:[{key:"target",label:`$t:${de}.show_error_page.param_target`,widget:"select",options:[{value:"content"},{value:"full"}]}],build:{handler:"showErrorPage",params:{target:"{{target}}"}}}}}}function nl(e,t){return typeof e.label=="string"?e.label.startsWith("$t:")?t(e.label.slice(3)):e.label:String(e.value)}function wi(e){return Array.isArray(e.options)?e.options:[]}function t8({control:e,value:t,onChange:n,t:o}){const r=wi(e);return i.jsx("div",{className:"g7le-widget g7le-widget--segmented","data-testid":"g7le-widget-segmented",style:f8,children:r.map(s=>{const a=s.value===t;return i.jsx("button",{type:"button","data-testid":`g7le-segment-${String(s.value)}`,"data-active":a?"true":"false",onClick:()=>n(a?void 0:s.value),style:{...x8,background:a?"#2563eb":"#fff",color:a?"#fff":"#0f172a",borderColor:a?"#2563eb":"#cbd5e1"},children:nl(s,o)},String(s.value))})})}function n8({control:e,value:t,onChange:n,t:o}){const r=Array.isArray(e.scale)?e.scale:[],s=r.findIndex(l=>l===t),a=t!=null;return i.jsxs("div",{className:"g7le-widget g7le-widget--slider","data-testid":"g7le-widget-slider",style:Bg,children:[i.jsxs("label",{style:ol,children:[i.jsx("input",{type:"checkbox","data-testid":"g7le-slider-enabled",checked:a,onChange:l=>{l.target.checked?n(r.length>0?r[Math.max(0,s===-1?0:s)]:void 0):n(void 0)}}),i.jsx("span",{style:{fontSize:12,color:"#64748b"},children:o("layout_editor.control.use")})]}),i.jsx("input",{type:"range","data-testid":"g7le-slider-range",min:0,max:Math.max(0,r.length-1),step:1,disabled:!a||r.length===0,value:s===-1?0:s,onChange:l=>{const d=Number(l.target.value);n(r[d])},style:{flex:1}}),i.jsx("span",{"data-testid":"g7le-slider-value",style:{fontSize:12,color:"#0f172a",minWidth:48,textAlign:"right"},children:a?String(t):o("layout_editor.control.default")})]})}function o8({control:e,value:t,onChange:n,t:o}){const r=wi(e);return i.jsxs("select",{className:"g7le-widget g7le-widget--select","data-testid":"g7le-widget-select",value:t==null?"":String(t),onChange:s=>{const a=s.target.value;if(a===""){n(void 0);return}const l=r.find(d=>String(d.value)===a);n(l?l.value:a)},style:w8,children:[i.jsx("option",{value:"",children:o("layout_editor.control.default")}),r.map(s=>i.jsx("option",{value:String(s.value),children:nl(s,o)},String(s.value)))]})}function r8({control:e,value:t,onChange:n}){const o=wi(e),r=o[1]?.value??!0,s=o[0]?.value??void 0,a=t===r;return i.jsx("button",{type:"button",className:"g7le-widget g7le-widget--toggle","data-testid":"g7le-widget-toggle",role:"switch","aria-checked":a,onClick:()=>n(a?s:r),style:{...k8,background:a?"#2563eb":"#cbd5e1"},children:i.jsx("span",{style:{...S8,transform:a?"translateX(18px)":"translateX(0)"}})})}function i8({control:e,value:t,onChange:n,t:o,freeValueDisabled:r}){const s=wi(e),a=t==null?"":String(t),[l,d]=u.useState(a);u.useEffect(()=>{d(a)},[a]);const c=p=>{const f=p.trim();if(f===""){n(void 0);return}n(f)};return i.jsxs("div",{className:"g7le-widget g7le-widget--dimension","data-testid":"g7le-widget-dimension",style:g8,children:[r?i.jsx("div",{"data-testid":"g7le-dimension-free-disabled",style:v8,children:o("layout_editor.property_modal.dark_preset_only")}):i.jsxs("div",{style:h8,children:[i.jsx("input",{type:"text","data-testid":"g7le-dimension-input",value:l,placeholder:o("layout_editor.control.dimension.placeholder"),onChange:p=>d(p.target.value),onBlur:p=>c(p.target.value),onKeyDown:p=>{p.key==="Enter"&&(p.preventDefault(),c(p.target.value))},style:y8}),i.jsx("button",{type:"button","data-testid":"g7le-dimension-clear",title:o("layout_editor.control.default"),onClick:()=>{d(""),n(void 0)},style:b8,children:o("layout_editor.control.default")})]}),s.length>0&&i.jsx("div",{style:m8,"data-testid":"g7le-dimension-chips",children:s.map(p=>{const f=String(p.value),b=f===a;return i.jsx("button",{type:"button","data-testid":`g7le-dimension-chip-${f}`,"data-active":b?"true":"false",onClick:()=>{d(f),n(f)},style:{..._8,background:b?"#2563eb":"#fff",color:b?"#fff":"#0f172a",borderColor:b?"#2563eb":"#cbd5e1"},children:nl(p,o)},f)})})]})}function s8(e){return[{key:"t",infix:`${e}t-`,labelKey:"layout_editor.control.spacing.top"},{key:"r",infix:`${e}r-`,labelKey:"layout_editor.control.spacing.right"},{key:"b",infix:`${e}b-`,labelKey:"layout_editor.control.spacing.bottom"},{key:"l",infix:`${e}l-`,labelKey:"layout_editor.control.spacing.left"}]}function a8(e,t,n,o){const r={all:-1,sides:{}};if(typeof e!="string"||e.trim()==="")return r;const s=e.split(/\s+/).filter(a=>a.length>0);for(const a of s){const l=n.find(d=>a.startsWith(d.infix));if(l){r.sides[l.key]=o.indexOf(a.slice(l.infix.length));continue}if(a.startsWith(`${t}x-`)){const d=o.indexOf(a.slice(`${t}x-`.length));r.sides.l=d,r.sides.r=d;continue}if(a.startsWith(`${t}y-`)){const d=o.indexOf(a.slice(`${t}y-`.length));r.sides.t=d,r.sides.b=d;continue}a.startsWith(`${t}-`)&&(r.all=o.indexOf(a.slice(`${t}-`.length)))}return r}function l8({control:e,value:t,onChange:n,t:o}){const r=typeof e.spacingPrefix=="string"?e.spacingPrefix:"p",s=(Array.isArray(e.scale)?e.scale:[]).map(y=>String(y)),a=s8(r),l=a8(t,r,a,s),d=Object.values(l.sides).some(y=>y>=0),c=l.all>=0||d,[p,f]=u.useState(d);u.useEffect(()=>{d&&f(!0)},[d]);const b=Math.min(4,Math.max(0,s.length-1)),g=y=>{const _=[];for(const w of a){const k=y[w.key];k!==void 0&&k>=0&&_.push(`${w.infix}${s[k]}`)}n(_.length>0?_.join(" "):void 0)},m=y=>{n(y>=0?`${r}-${s[Math.max(0,Math.min(y,s.length-1))]}`:void 0)},h=()=>({...l.sides}),v=(y,_,w,k,x)=>{const $=w>=0;return i.jsxs("div",{style:p8,"data-testid":y,children:[i.jsxs("label",{style:{...ol,minWidth:64},children:[i.jsx("input",{type:"checkbox","data-testid":`${y}-enabled`,checked:$,onChange:S=>x(S.target.checked)}),i.jsx("span",{style:{fontSize:12,color:"#0f172a"},children:o(_)})]}),i.jsx("input",{type:"range","data-testid":`${y}-range`,min:0,max:Math.max(0,s.length-1),step:1,disabled:!$||s.length===0,value:w<0?0:w,onChange:S=>k(Number(S.target.value)),style:{flex:1}}),i.jsx("span",{"data-testid":`${y}-value`,style:{fontSize:12,color:$?"#0f172a":"#94a3b8",minWidth:28,textAlign:"right"},children:$?s[w]:"–"})]},y)};return i.jsxs("div",{className:"g7le-widget g7le-widget--spacing","data-testid":"g7le-widget-spacing",style:d8,children:[i.jsxs("div",{style:c8,children:[i.jsxs("div",{style:u8,"data-testid":"g7le-spacing-mode",children:[i.jsx("button",{type:"button","data-testid":"g7le-spacing-mode-all","data-active":p?"false":"true",onClick:()=>{f(!1),d&&n(void 0)},style:{...Og,background:p?"#fff":"#2563eb",color:p?"#0f172a":"#fff",borderColor:p?"#cbd5e1":"#2563eb"},children:o("layout_editor.control.spacing.mode_all")}),i.jsx("button",{type:"button","data-testid":"g7le-spacing-mode-sides","data-active":p?"true":"false",onClick:()=>{if(f(!0),l.all>=0){const y={};for(const _ of a)y[_.key]=l.all;g(y)}},style:{...Og,background:p?"#2563eb":"#fff",color:p?"#fff":"#0f172a",borderColor:p?"#2563eb":"#cbd5e1"},children:o("layout_editor.control.spacing.mode_sides")})]}),i.jsx("span",{"data-testid":"g7le-spacing-value",style:{fontSize:11,color:"#64748b",minWidth:80,textAlign:"right"},children:c?String(t):o("layout_editor.control.default")})]}),p?i.jsx("div",{"data-testid":"g7le-spacing-sides",style:{display:"flex",flexDirection:"column",gap:4},children:a.map(y=>v(`g7le-spacing-side-${y.key}`,y.labelKey,l.sides[y.key]??-1,_=>{const w=h();w[y.key]=_,g(w)},_=>{const w=h();_?w[y.key]=l.sides[y.key]>=0?l.sides[y.key]:b:delete w[y.key],g(w)}))}):i.jsxs("div",{style:Bg,children:[i.jsxs("label",{style:{...ol,minWidth:64},children:[i.jsx("input",{type:"checkbox","data-testid":"g7le-spacing-all-enabled",checked:l.all>=0,onChange:y=>m(y.target.checked?b:-1)}),i.jsx("span",{style:{fontSize:12,color:"#64748b"},children:o("layout_editor.control.spacing.all")})]}),i.jsx("input",{type:"range","data-testid":"g7le-spacing-all-range",min:0,max:Math.max(0,s.length-1),step:1,disabled:l.all<0||s.length===0,value:l.all<0?0:l.all,onChange:y=>m(Number(y.target.value)),style:{flex:1}}),i.jsx("span",{"data-testid":"g7le-spacing-all-value",style:{fontSize:12,color:l.all>=0?"#0f172a":"#94a3b8",minWidth:28,textAlign:"right"},children:l.all>=0?s[l.all]:"–"})]})]})}const d8={display:"flex",flexDirection:"column",gap:6,width:"100%"},c8={display:"flex",alignItems:"center",justifyContent:"space-between",gap:8},u8={display:"inline-flex",gap:0,border:"1px solid #cbd5e1",borderRadius:6,overflow:"hidden"},Og={padding:"3px 10px",fontSize:11,border:"none",borderRight:"1px solid #e2e8f0",cursor:"pointer"},p8={display:"flex",alignItems:"center",gap:8,width:"100%"},f8={display:"inline-flex",gap:0,border:"1px solid #cbd5e1",borderRadius:6,overflow:"hidden"},g8={display:"flex",flexDirection:"column",gap:6,width:"100%"},h8={display:"flex",gap:6,alignItems:"center"},y8={flex:1,padding:"5px 8px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,minWidth:80},b8={padding:"4px 8px",fontSize:11,border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",color:"#64748b",cursor:"pointer",whiteSpace:"nowrap"},m8={display:"flex",flexWrap:"wrap",gap:4},v8={fontSize:11,color:"#94a3b8",fontStyle:"italic",padding:"2px 0"},_8={padding:"3px 9px",fontSize:11,border:"1px solid #cbd5e1",borderRadius:999,background:"#fff",cursor:"pointer"},x8={padding:"4px 10px",fontSize:12,border:"none",borderRight:"1px solid #e2e8f0",cursor:"pointer"},Bg={display:"flex",alignItems:"center",gap:8,width:"100%"},ol={display:"inline-flex",alignItems:"center",gap:4},w8={padding:"5px 8px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",minWidth:120},k8={position:"relative",width:38,height:20,borderRadius:10,border:"none",cursor:"pointer",padding:2,display:"inline-flex",alignItems:"center",transition:"background 120ms ease"},S8={display:"block",width:16,height:16,borderRadius:8,background:"#fff",transition:"transform 120ms ease"},C8=["#0f172a","#334155","#64748b","#94a3b8","#e2e8f0","#ffffff","#dc2626","#ea580c","#d97706","#16a34a","#0891b2","#2563eb","#7c3aed","#db2777"],Wg=/^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/;function $8(e){return Array.isArray(e.options)?e.options.filter(t=>typeof t.value=="string").map(t=>({value:t.value,swatch:typeof t.swatch=="string"?t.swatch:void 0,label:typeof t.label=="string"?t.label:void 0})):[]}function E8({control:e,value:t,onChange:n,t:o,freeValueDisabled:r}){const s=$8(e),a=s.length>0,l=typeof t=="string"&&s.some(g=>g.value===t)?t:"",d=typeof t=="string"&&Wg.test(t)?t:"",[c,p]=u.useState(d);u.useEffect(()=>{p(d)},[d]);const f=g=>{const m=g.trim();if(m===""){n(void 0);return}const h=m.startsWith("#")?m:`#${m}`;Wg.test(h)&&n(h)},b=C8;return i.jsxs("div",{className:"g7le-widget g7le-widget--color","data-testid":"g7le-widget-color",style:I8,children:[r?i.jsx("div",{"data-testid":"g7le-color-free-disabled",style:M8,children:o("layout_editor.property_modal.dark_preset_only")}):i.jsxs("div",{style:A8,children:[i.jsxs("label",{style:T8,children:[i.jsx("span",{"data-testid":"g7le-color-swatch",style:{...D8,background:d||"transparent",backgroundImage:d?void 0:"linear-gradient(45deg,#e2e8f0 25%,transparent 25%,transparent 75%,#e2e8f0 75%),linear-gradient(45deg,#e2e8f0 25%,#fff 25%,#fff 75%,#e2e8f0 75%)",backgroundSize:"8px 8px",backgroundPosition:"0 0, 4px 4px"}}),i.jsx("input",{type:"color","data-testid":"g7le-color-native",value:d||"#000000",onChange:g=>n(g.target.value),style:{width:0,height:0,opacity:0,position:"absolute"}})]}),i.jsx("input",{type:"text","data-testid":"g7le-color-hex",value:c,placeholder:o("layout_editor.control.color_hex_placeholder"),onChange:g=>p(g.target.value),onBlur:g=>f(g.target.value),onKeyDown:g=>{g.key==="Enter"&&f(g.target.value)},style:z8}),i.jsx("button",{type:"button","data-testid":"g7le-color-clear",onClick:()=>n(void 0),style:N8,children:o("layout_editor.control.default")})]}),a?i.jsx("div",{style:jg,"data-testid":"g7le-color-token-presets",children:s.map(g=>{const m=l===g.value,h=g.label?g.label.startsWith("$t:")?o(g.label.slice(3)):g.label:g.value;return i.jsx("button",{type:"button","aria-label":h,title:h,"data-testid":`g7le-color-token-${g.value}`,"data-active":m?"true":"false",onClick:()=>n(m?void 0:g.value),style:{...Hg,background:g.swatch||"#e2e8f0",outline:m?"2px solid #2563eb":"none"}},g.value)})}):!r&&i.jsx("div",{style:jg,"data-testid":"g7le-color-presets",children:b.map(g=>i.jsx("button",{type:"button","aria-label":g,"data-testid":`g7le-color-preset-${g}`,onClick:()=>n(g),style:{...Hg,background:g,outline:d.toLowerCase()===g.toLowerCase()?"2px solid #2563eb":"none"}},g))})]})}const I8={display:"flex",flexDirection:"column",gap:8},A8={display:"flex",alignItems:"center",gap:6},T8={position:"relative",display:"inline-flex",cursor:"pointer"},D8={width:24,height:24,borderRadius:4,border:"1px solid #cbd5e1",display:"inline-block"},z8={flex:1,padding:"5px 8px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,fontFamily:"monospace"},N8={padding:"4px 8px",fontSize:11,border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",cursor:"pointer",color:"#64748b"},jg={display:"grid",gridTemplateColumns:"repeat(7, 1fr)",gap:4},Hg={width:"100%",height:18,borderRadius:4,border:"1px solid #cbd5e1",cursor:"pointer",padding:0},M8={fontSize:11,color:"#94a3b8",fontStyle:"italic",padding:"2px 0"},tr=[{value:"fill",labelKey:"layout_editor.control.background_image.mode_fill",size:"cover",repeat:"no-repeat"},{value:"fit",labelKey:"layout_editor.control.background_image.mode_fit",size:"contain",repeat:"no-repeat"},{value:"tile",labelKey:"layout_editor.control.background_image.mode_tile",size:"auto",repeat:"repeat"}];function L8(e){return e&&typeof e=="object"&&!Array.isArray(e)?e:{}}function Fg(e){return tr.find(n=>n.size===e.size&&n.repeat===e.repeat)?.value??"fill"}function P8({value:e,onChange:t,t:n}){const o=L8(e),{state:r}=Qe(),s=bo(),[a,l]=u.useState(!1),[d,c]=u.useState(null),[p,f]=u.useState(o.url??""),[b,g]=u.useState([]),m=u.useRef(null),h=r.templateIdentifier,v=r.selectedRoute?.layoutName??"";u.useEffect(()=>{f(o.url??"")},[o.url]);const y=u.useCallback(async()=>{if(!h||!v){g([]);return}const S=await Ll(h,v);S.ok&&g(S.data)},[h,v]);u.useEffect(()=>{y()},[y]);const _=S=>{if(!S){t(void 0);return}const C=tr.find(T=>T.value===Fg(o))??tr[0];t({url:S,size:o.size??C.size,repeat:o.repeat??C.repeat,position:o.position??"center"})},w=S=>{const C=tr.find(T=>T.value===S);!C||!o.url||t({url:o.url,size:C.size,repeat:C.repeat,position:o.position??"center"})},k=async S=>{const C=S.target.files?.[0];if(!C)return;l(!0),c(null);const T=await Pl(h,v,C);T.ok?(_(T.data.url),y()):c(n("layout_editor.control.background_image.upload_failed")),l(!1),m.current&&(m.current.value="")},x=async S=>{(await Ol(S.id)).ok&&g(T=>T.filter(E=>E.id!==S.id))},$=()=>{const S=s.open({ariaLabel:n("layout_editor.attachment_manager.title"),width:720,maxHeightRatio:.82,content:u.createElement(Bl,{templateIdentifier:h,layoutName:v,t:n,onSelect:C=>{_(C),s.close(S)},onChanged:()=>{y()},onClose:()=>s.close(S)})})};return i.jsxs("div",{className:"g7le-widget g7le-widget--image","data-testid":"g7le-widget-image",style:O8,children:[o.url&&i.jsx("div",{"data-testid":"g7le-image-preview",style:{...B8,backgroundImage:`url(${o.url})`,backgroundSize:o.size??"cover",backgroundRepeat:o.repeat??"no-repeat",backgroundPosition:o.position??"center"}}),i.jsxs("div",{style:W8,children:[i.jsxs("label",{style:j8,children:[a?"…":n("layout_editor.control.background_image.upload"),i.jsx("input",{ref:m,type:"file",accept:"image/*","data-testid":"g7le-image-file",onChange:k,disabled:a,style:{display:"none"}})]}),i.jsx("button",{type:"button","data-testid":"g7le-image-clear",onClick:()=>t(void 0),style:H8,children:n("layout_editor.control.background_image.clear")})]}),i.jsx("input",{type:"text","data-testid":"g7le-image-url",placeholder:"https://… / URL",value:p,onChange:S=>f(S.target.value),onBlur:S=>_(S.target.value.trim()||void 0),style:F8}),i.jsx("div",{style:K8,"data-testid":"g7le-image-modes",children:tr.map(S=>{const C=Fg(o)===S.value;return i.jsx("button",{type:"button","data-testid":`g7le-image-mode-${S.value}`,"data-active":C?"true":"false",disabled:!o.url,onClick:()=>w(S.value),style:{...V8,background:C&&o.url?"#2563eb":"#fff",color:C&&o.url?"#fff":"#0f172a"},children:n(S.labelKey)},S.value)})}),i.jsxs("div",{style:U8,"data-testid":"g7le-image-gallery",children:[i.jsxs("div",{style:q8,children:[i.jsx("span",{style:{fontSize:11,color:"#64748b"},children:n("layout_editor.attachment_manager.recent")}),i.jsxs("button",{type:"button","data-testid":"g7le-image-manage",onClick:$,style:G8,children:["🖼 ",n("layout_editor.attachment_manager.manage_link")]})]}),b.length>0?i.jsx("div",{style:Y8,"data-testid":"g7le-image-thumbs",children:b.slice(0,8).map(S=>i.jsxs("div",{style:X8,"data-testid":`g7le-image-thumb-${S.id}`,children:[i.jsx("button",{type:"button",title:S.original_name,onClick:()=>_(S.url),style:{...J8,backgroundImage:`url(${S.url})`},"data-testid":`g7le-image-thumb-use-${S.id}`}),i.jsx("button",{type:"button",title:n("layout_editor.attachment_manager.delete"),onClick:()=>x(S),style:Z8,"data-testid":`g7le-image-thumb-del-${S.id}`,children:"✕"})]},S.id))}):i.jsx("span",{style:{fontSize:11,color:"#94a3b8"},children:n("layout_editor.attachment_manager.empty")})]}),d&&i.jsx("span",{"data-testid":"g7le-image-error",style:{fontSize:11,color:"#dc2626"},children:d})]})}const O8={display:"flex",flexDirection:"column",gap:6},B8={width:"100%",height:80,borderRadius:6,border:"1px solid #cbd5e1"},W8={display:"flex",gap:6},j8={padding:"5px 10px",fontSize:12,border:"1px solid #2563eb",borderRadius:6,background:"#fff",color:"#2563eb",cursor:"pointer",display:"inline-flex",alignItems:"center"},H8={padding:"5px 10px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",color:"#64748b",cursor:"pointer"},F8={padding:"5px 8px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6},K8={display:"inline-flex",border:"1px solid #cbd5e1",borderRadius:6,overflow:"hidden"},V8={padding:"4px 10px",fontSize:12,border:"none",borderRight:"1px solid #e2e8f0",cursor:"pointer"},U8={display:"flex",flexDirection:"column",gap:4,border:"1px solid #e2e8f0",borderRadius:6,padding:6,marginTop:2},q8={display:"flex",alignItems:"center",justifyContent:"space-between"},G8={fontSize:11,color:"#2563eb",background:"transparent",border:"none",cursor:"pointer",padding:0},Y8={display:"flex",gap:6,flexWrap:"wrap"},X8={position:"relative",width:44,height:44},J8={width:44,height:44,borderRadius:6,border:"1px solid #cbd5e1",backgroundSize:"cover",backgroundPosition:"center",cursor:"pointer",padding:0},Z8={position:"absolute",top:-6,right:-6,width:16,height:16,borderRadius:"50%",border:"none",background:"#dc2626",color:"#fff",fontSize:9,lineHeight:"16px",cursor:"pointer",padding:0};function Q8(e){return Array.isArray(e)&&e.every(t=>t!==null&&typeof t=="object"&&!Array.isArray(t))}function R8(e){return typeof e=="string"&&/\{\{.*\}\}/.test(e)}function ez({value:e,onChange:t,t:n,bindingCandidates:o}){if(R8(e))return i.jsx("div",{className:"g7le-widget g7le-widget--options-list","data-testid":"g7le-widget-options-list-bound",style:lz,children:n("layout_editor.list_editor.bound_degraded")});const r=Q8(e)?e:[],s=b=>{t(b.length===0?void 0:b)},a=(b,g)=>{const m=r.map((h,v)=>v===b?{...h,...g}:h);s(m)},l=b=>{s(r.filter((g,m)=>m!==b))},d=(b,g)=>{const m=b+g;if(m<0||m>=r.length)return;const h=[...r];[h[b],h[m]]=[h[m],h[b]],s(h)},c=()=>{s([...r,{value:"",label:""}])},p=b=>typeof b.label=="string"?b.label:b.label==null?"":String(b.label),f=b=>b.value===void 0||b.value===null?"":String(b.value);return i.jsxs("div",{className:"g7le-widget g7le-widget--options-list","data-testid":"g7le-widget-options-list",style:tz,children:[r.length===0&&i.jsx("div",{"data-testid":"g7le-options-empty",style:az,children:n("layout_editor.list_editor.empty")}),r.map((b,g)=>i.jsxs("div",{"data-testid":`g7le-options-row-${g}`,style:nz,children:[i.jsxs("div",{style:Kg,children:[i.jsx("span",{style:Vg,children:n("layout_editor.list_editor.option_value")}),i.jsx("input",{type:"text","data-testid":`g7le-options-value-${g}`,value:f(b),placeholder:n("layout_editor.list_editor.option_value"),onChange:m=>a(g,{value:m.target.value}),style:rz})]}),i.jsxs("div",{style:Kg,children:[i.jsx("span",{style:Vg,children:n("layout_editor.list_editor.option_label")}),i.jsx("div",{"data-testid":`g7le-options-label-${g}`,style:{flex:1,minWidth:0},children:i.jsx(_t,{value:p(b),onChange:m=>a(g,{label:m??""}),t:n,placeholder:n("layout_editor.list_editor.option_label"),testidPrefix:`g7le-options-label-i18n-${g}`,candidates:o,enableExpressionTree:!0,expressionTreeCollapsible:!0})})]}),i.jsxs("div",{style:oz,children:[i.jsx("button",{type:"button","data-testid":`g7le-options-up-${g}`,title:n("layout_editor.list_editor.move_up"),disabled:g===0,onClick:()=>d(g,-1),style:Ug,children:"↑"}),i.jsx("button",{type:"button","data-testid":`g7le-options-down-${g}`,title:n("layout_editor.list_editor.move_down"),disabled:g===r.length-1,onClick:()=>d(g,1),style:Ug,children:"↓"}),i.jsx("button",{type:"button","data-testid":`g7le-options-remove-${g}`,title:n("layout_editor.list_editor.remove"),onClick:()=>l(g),style:iz,children:"✕"})]})]},g)),i.jsx("button",{type:"button","data-testid":"g7le-options-add",onClick:c,style:sz,children:n("layout_editor.list_editor.add")})]})}const tz={display:"flex",flexDirection:"column",gap:8,width:"100%"},nz={display:"flex",flexDirection:"column",gap:4,width:"100%",minWidth:0,paddingBottom:6,borderBottom:"1px solid #f1f5f9"},Kg={display:"flex",gap:6,alignItems:"center",width:"100%",minWidth:0},Vg={flexShrink:0,width:36,fontSize:11,color:"#94a3b8",textAlign:"right"},oz={display:"flex",gap:4,alignItems:"center",justifyContent:"flex-end"},rz={flex:1,minWidth:0,padding:"4px 6px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6},Ug={padding:"2px 6px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",color:"#475569",cursor:"pointer"},iz={padding:"2px 6px",fontSize:12,border:"1px solid #fecaca",borderRadius:6,background:"#fff",color:"#dc2626",cursor:"pointer"},sz={padding:"4px 8px",fontSize:12,border:"1px dashed #94a3b8",borderRadius:6,background:"#fff",color:"#475569",cursor:"pointer",alignSelf:"flex-start"},az={fontSize:11,color:"#94a3b8",fontStyle:"italic"},lz={fontSize:11,color:"#b45309",fontStyle:"italic",padding:"4px 2px"},qg=/[^A-Za-z0-9\-_:.]/g;function dz(e){return typeof e=="string"&&Yi(e)}function Gg(e){return Yi(e)?In(e).map(t=>t.kind==="binding"?t.raw:t.raw.replace(qg,"")).join(""):e.replace(qg,"")}const cz={width:"100%",padding:"6px 8px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:4,fontFamily:"monospace"},uz={marginTop:4,fontSize:11,color:"#94a3b8"},pz={display:"flex",gap:6,alignItems:"center"},fz={flexShrink:0,padding:"5px 8px",fontSize:11,border:"1px solid #cbd5e1",borderRadius:4,background:"#f8fafc",cursor:"pointer",whiteSpace:"nowrap"};function gz({value:e,onChange:t,t:n,bindingCandidates:o}){const r=e==null?"":String(e),s=dz(e),a=!!o&&o.length>0,[l,d]=u.useState(!1),c=p=>{const f=Gg(p);t(f===""?void 0:f)};return s||l?i.jsxs("div",{"data-testid":"g7le-core-id-chip",children:[i.jsx(_s,{value:r,onChange:c,t:n,candidates:o,onDone:()=>d(!1),testidPrefix:"g7le-core-id-chip-input"}),i.jsx("div",{style:uz,children:n("layout_editor.core_props.id.binding_hint")})]}):i.jsx("div",{children:i.jsxs("div",{style:pz,children:[i.jsx("input",{type:"text",className:"g7le-widget g7le-widget--core-id","data-testid":"g7le-widget-core-id",value:r,onChange:p=>{const f=Gg(p.target.value);t(f===""?void 0:f)},spellCheck:!1,autoComplete:"off",style:cz}),a&&i.jsxs("button",{type:"button","data-testid":"g7le-core-id-add-data",onClick:()=>d(!0),style:fz,title:n("layout_editor.core_props.id.add_data"),children:["🔗 ",n("layout_editor.core_props.id.add_data")]})]})})}const hz=/[^A-Za-z0-9.\-_]/g;function yz(e){return typeof e=="string"&&/\{\{[\s\S]*?\}\}/.test(e)}function bz(e){return e.replace(hz,"")}const Yg={width:"100%",padding:"6px 8px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:4,fontFamily:"monospace",boxSizing:"border-box"},mz={...Yg,background:"#f1f5f9",color:"#64748b",cursor:"not-allowed"},Xg={marginTop:4,fontSize:11,color:"#94a3b8"};function vz({value:e,onChange:t,t:n}){const o=e==null?"":String(e);return yz(e)?i.jsxs("div",{"data-testid":"g7le-core-datakey-bound",children:[i.jsx("input",{type:"text",className:"g7le-widget g7le-widget--core-datakey-bound","data-testid":"g7le-widget-core-datakey",value:o,readOnly:!0,disabled:!0,style:mz}),i.jsx("div",{style:Xg,children:n("layout_editor.core_props.dataKey.bound_degraded")})]}):i.jsxs("div",{children:[i.jsx("input",{type:"text",className:"g7le-widget g7le-widget--core-datakey","data-testid":"g7le-widget-core-datakey",value:o,placeholder:n("layout_editor.core_props.dataKey.placeholder"),onChange:s=>{const a=bz(s.target.value);t(a===""?void 0:a)},spellCheck:!1,autoComplete:"off",style:Yg}),i.jsx("div",{style:Xg,children:n("layout_editor.core_props.dataKey.hint")})]})}let Jg=!1;function _z(){Jg||(Ze("segmented",t8),Ze("slider",n8),Ze("select",o8),Ze("dimension",i8),Ze("spacing",l8),Ze("toggle",r8),Ze("color",E8),Ze("image",P8),Ze("tag-input",vs),Ze("page-picker",P0),Ze("datasource-picker",Fc),Ze("binding-picker",Fc),Ze("binding-list",W0),Ze("state-key-picker",Ts),Ze("modal-picker",O0),Ze("i18n-text",B0),Ze("text",j0),Ze("options-list",ez),Ze("core-id",gz),Ze("core-datakey",vz),Ze("component-target-picker",p4),Jg=!0)}function nr(e){return Array.isArray(e.children)?e.children:[]}function or(e){if(typeof e.text=="string")return[];let t=null;const n=(o,r)=>{const s=nr(o);for(let a=0;ad===o?rr(l,r,n):l);return{...e,children:a}}function Zg(e,t){const n=r=>typeof r.props?.[t]=="string";if(n(e))return[];const o=(r,s)=>{const a=nr(r);for(let l=0;lc===r?Qg(d,s,n,o):d);return{...e,children:l}}function xz(e){return Array.isArray(e.children)?e.children:[]}function wz(e){const t=e?.itemFields;if(!Array.isArray(t))return[{kind:"text"}];const n=[];for(const o of t){if(!o||typeof o!="object")continue;const r=o;r.kind==="text"?n.push({kind:"text",label:typeof r.label=="string"?r.label:void 0}):r.kind==="prop"&&typeof r.prop=="string"&&r.prop.length>0&&n.push({kind:"prop",prop:r.prop,label:typeof r.label=="string"?r.label:void 0})}return n.length>0?n:[{kind:"text"}]}function rl(e,t){if(!(typeof e!="string"||e.length===0))return e.startsWith("$t:")?t(e.slice(3)):e}function Rg({node:e,params:t,spec:n,manifest:o,t:r,onPatchNode:s,templateIdentifier:a,candidates:l}){const d=typeof t?.childComponent=="string"?t.childComponent:null,c=t?.childTemplate&&typeof t.childTemplate=="object"?t.childTemplate:null,p=wz(t),f=rl(typeof t?.childLabel=="string"?t.childLabel:void 0,r)??d??"",b=xz(e),g=u.useCallback(x=>{s({...e,children:x})},[e,s]),m=u.useCallback(()=>{if(c)return JSON.parse(JSON.stringify(c));if(!d)return null;const x=n?.componentPalette?.entries?.[d];if(x?.defaultNode&&typeof x.defaultNode=="object")return JSON.parse(JSON.stringify(x.defaultNode));const $=o?.components??{};let S=null;for(const C of Object.values($))if(Array.isArray(C)){const T=C.find(E=>E.name===d);if(T){S=T;break}}return S?zf(S):{type:"basic",name:d}},[c,d,n,o]),h=u.useCallback(()=>{const x=m();if(x){if(x.text==="")x.text=r("layout_editor.list_editor.new_item");else{const $=or(x);if($!==null&&$.length>0){const S=ki(x,$);if(S&&S.text===""){const C=rr(x,$,r("layout_editor.list_editor.new_item"));g([...b,C]);return}}}g([...b,x])}},[m,g,b,r]),v=u.useCallback(x=>{g(b.filter(($,S)=>S!==x))},[g,b]),y=u.useCallback((x,$)=>{const S=x+$;if(S<0||S>=b.length)return;const C=[...b];[C[x],C[S]]=[C[S],C[x]],g(C)},[g,b]),_=u.useCallback((x,$)=>{if(typeof $!="string"||$.length===0)return;const S=b[x];if(!S)return;const C=or(S);if(C===null)return;const T=b.map((E,A)=>A===x?rr(E,C,$):E);g(T)},[b,g]),w=u.useCallback((x,$,S)=>{if(typeof S!="string"||S.length===0)return;const C=b[x];if(!C)return;const T=Zg(C,$);if(T===null)return;const E=b.map((A,N)=>N===x?Qg(A,T,$,S):A);g(E)},[b,g]);if(!d&&!c)return i.jsx("div",{className:"g7le-node-editor g7le-node-editor--children","data-testid":"g7le-children-editor-misconfigured",style:nh,children:r("layout_editor.list_editor.no_child_component")});const k=p.length>1||p.some(x=>x.label);return i.jsxs("div",{className:"g7le-node-editor g7le-node-editor--children","data-testid":"g7le-children-editor",style:kz,children:[i.jsx("div",{style:Sz,children:r("layout_editor.list_editor.items_title")}),b.length===0&&i.jsx("div",{"data-testid":"g7le-children-empty",style:nh,children:r("layout_editor.list_editor.empty")}),b.map((x,$)=>{const S=[];for(const T of p)if(T.kind==="text"){const E=or(x),A=E!==null?ki(x,E):null;A&&typeof A.text=="string"&&S.push({key:"text",caption:rl(T.label,r)??(k?r("layout_editor.list_editor.item_label"):void 0),value:A.text,onChange:N=>_($,N),testid:`g7le-children-i18n-${$}`})}else if(T.kind==="prop"&&T.prop){const E=Zg(x,T.prop);if(E!==null){const N=ki(x,E)?.props?.[T.prop];S.push({key:`prop-${T.prop}`,caption:rl(T.label,r)??T.prop,value:typeof N=="string"?N:"",onChange:D=>w($,T.prop,D),testid:`g7le-children-prop-${T.prop}-${$}`})}}const C=typeof x?.name=="string"?x.name:r("layout_editor.list_editor.item");return i.jsxs("div",{"data-testid":`g7le-children-row-${$}`,style:Cz,children:[S.length>0?S.map(T=>i.jsx("div",{style:eh,children:i.jsxs("div",{"data-testid":T.key==="text"?`g7le-children-text-${$}`:`g7le-children-field-${T.key}-${$}`,style:$z,children:[T.caption!==void 0&&i.jsx("div",{"data-testid":`g7le-children-caption-${T.key}-${$}`,style:Ez,children:T.caption}),i.jsx(_t,{value:T.value,onChange:T.onChange,t:r,placeholder:r("layout_editor.list_editor.item_text"),testidPrefix:T.testid,candidates:l,enableExpressionTree:!0,expressionTreeCollapsible:!0})]})},T.key)):i.jsx("div",{style:eh,children:i.jsx("span",{"data-testid":`g7le-children-label-${$}`,style:Az,children:C})}),i.jsxs("div",{style:Iz,children:[i.jsx("button",{type:"button","data-testid":`g7le-children-up-${$}`,title:r("layout_editor.list_editor.move_up"),disabled:$===0,onClick:()=>y($,-1),style:th,children:"↑"}),i.jsx("button",{type:"button","data-testid":`g7le-children-down-${$}`,title:r("layout_editor.list_editor.move_down"),disabled:$===b.length-1,onClick:()=>y($,1),style:th,children:"↓"}),i.jsx("button",{type:"button","data-testid":`g7le-children-remove-${$}`,title:r("layout_editor.list_editor.remove"),onClick:()=>v($),style:Tz,children:"✕"})]})]},$)}),i.jsx("button",{type:"button","data-testid":"g7le-children-add",onClick:h,style:Dz,children:r("layout_editor.list_editor.add_child",{component:f})})]})}const kz={display:"flex",flexDirection:"column",gap:6,width:"100%",marginBottom:12},Sz={fontSize:12,fontWeight:700,color:"#0f172a",marginBottom:2},Cz={display:"flex",flexDirection:"column",gap:4,width:"100%",minWidth:0,paddingBottom:6,borderBottom:"1px solid #f1f5f9"},eh={display:"flex",gap:4,alignItems:"flex-start",width:"100%",minWidth:0},$z={flex:1,minWidth:0},Ez={fontSize:11,color:"#64748b",marginBottom:2},Iz={display:"flex",gap:4,alignItems:"center",justifyContent:"flex-end"},Az={flex:1,minWidth:0,padding:"4px 6px",fontSize:12,color:"#475569",fontStyle:"italic",overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap"},th={padding:"2px 6px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",color:"#475569",cursor:"pointer"},Tz={padding:"2px 6px",fontSize:12,border:"1px solid #fecaca",borderRadius:6,background:"#fff",color:"#dc2626",cursor:"pointer"},Dz={padding:"4px 8px",fontSize:12,border:"1px dashed #94a3b8",borderRadius:6,background:"#fff",color:"#475569",cursor:"pointer",alignSelf:"flex-start"},nh={fontSize:11,color:"#94a3b8",fontStyle:"italic"},go={rowContainer:"Tbody",row:"Tr",cell:"Td",headerCell:"Th",colSpanProp:"colSpan",rowSpanProp:"rowSpan"};function yt(e){return{rowContainer:e?.rowContainer??go.rowContainer,row:e?.row??go.row,cell:e?.cell??go.cell,headerCell:e?.headerCell??go.headerCell,colSpanProp:e?.colSpanProp??go.colSpanProp,rowSpanProp:e?.rowSpanProp??go.rowSpanProp}}function Bn(e){return e?Array.isArray(e.children)?e.children:[]:[]}function oh(e,t){const n=e.props?.[t],o=typeof n=="number"?n:typeof n=="string"?parseInt(n,10):NaN;return Number.isFinite(o)&&o>=1?Math.floor(o):1}function Pe(e,t){const n=yt(t),o=[],r=Bn(e),s=[];r.forEach((f,b)=>{(Bn(f).some(h=>h.name===n.row)||f.name===n.rowContainer||f.name==="Thead"||f.name==="Tfoot")&&s.push({container:f,containerIndex:b,isHeader:f.name==="Thead"})}),s.length===0&&r.some(f=>f.name===n.row)&&s.push({container:e,containerIndex:-1,isHeader:!1});const a=[],l=[];let d=0,c=0;const p=f=>{for(;a.length<=f;)a.push([]);return a[f]};s.forEach((f,b)=>{Bn(f.container).filter(m=>m.name===n.row).forEach((m,h)=>{const v=[],y=p(d);let _=0;Bn(m).filter(k=>k.name===n.cell||k.name===n.headerCell).forEach((k,x)=>{for(;y[_];)_++;const $=oh(k,n.colSpanProp),S=oh(k,n.rowSpanProp),C={cell:k,sectionIndex:b,rowIndexInSection:h,cellIndexInRow:x,gridRow:d,gridCol:_,colSpan:$,rowSpan:S,isHeader:k.name===n.headerCell};v.push(C);for(let T=0;T=0?(a.push(s.containerIndex),l=Bn(e)[s.containerIndex]??e):l=e;let d=-1,c=-1,p=null;const f=Bn(l);for(let m=0;m=r.gridRow&&t=r.gridCol&&nr){s=!0;break}if(s)break}s||t.add(r)}const n=Array.from(t).sort((r,s)=>r-s),o=[];for(let r=0;re.rows.find(l=>(l.cells[0]?.gridRow??-1)===a)?.sectionIndex??e.rows[a]?.sectionIndex??0,o=new Set([0,t]);for(let a=1;aa){l=!0;break}if(l)break}l||o.add(a)}const r=Array.from(o).sort((a,l)=>a-l),s=[];for(let a=0;at>=a.start&&t=o.length?{ok:!1,reason:"boundary"}:o[r].sectionIndex!==o[s].sectionIndex?{ok:!1,reason:"section"}:{ok:!0}}function Ci(e,t,n){const o=rh(e),r=o.findIndex(a=>t>=a.start&&t=o.length?{ok:!1,reason:"boundary"}:{ok:!0}}function Ge(e){return e?Array.isArray(e.children)?e.children:[]:[]}function mn(e,t,n){const o={...e.props??{}};n<=1?delete o[t]:o[t]=n;const r={...e,props:o};return Object.keys(o).length===0&&delete r.props,r}function $i(e,t,n=!1){const o={type:"basic",name:n?e.headerCell:e.cell},r=t?.props?.className;return typeof r=="string"&&r&&(o.props={className:r}),o.text="",o}function ir(e,t){return e.name===t.cell||e.name===t.headerCell}function nt(e,t,n,o){const r=t.sections[n];if(!r)return e;if(r.containerIndex===-1)return o(e);const s=Ge(e).map((a,l)=>l===r.containerIndex?o(a):a);return{...e,children:s}}function bt(e,t,n,o){let r=-1;const s=Ge(e).map(a=>a.name===t&&(r++,r===n)?o(a):a);return{...e,children:s}}function Ei(e,t,n){let o=-1;const r=Ge(e);for(let s=0;s0?nt(e,r,r.sections.length-1,_=>({..._,children:[...Ge(_),y]})):{...e,children:[...Ge(e),y]}}const s=Math.max(-1,Math.min(n,r.rows.length-1)),a=s<0?r.rows[0]:r.rows[s],l=a.cells[0]?.gridRow??s,d=s<0?0:l+1,c=a.sectionIndex,p=s<0?0:a.rowIndexInSection+1,f=[],b=[];let g=0;for(;g0?Ve(r,d-1,g):null;y&&y.gridRow+y.rowSpan>d&&y.rowSpan>1?(b.push(y),g+=y.colSpan):(f.push($i(o)),g+=1)}let m=e;for(const y of b)m=nt(m,r,y.sectionIndex,_=>bt(_,o.row,y.rowIndexInSection,w=>({...w,children:Ge(w).map((k,x)=>x===y.cellIndexInRow?mn(k,o.rowSpanProp,y.rowSpan+1):k)})));const h={type:"basic",name:o.row,children:f},v=Pe(m,t);return m=nt(m,v,c,y=>{const _=Ge(y);let w=0,k=_.length;for(let x=0;x<_.length;x++)if(_[x].name===o.row){if(w===p){k=x;break}w++}return{...y,children:[..._.slice(0,k),h,..._.slice(k)]}}),m}function sh(e,t,n){const o=yt(t),r=Pe(e,t);if(n<0||n>=r.rows.length||r.rows.length<=1)return e;const s=r.rows[n],a=s.cells[0]?.gridRow??n;let l=e;const d=new Set;for(let p=0;pbt(b,o.row,f.rowIndexInSection,g=>({...g,children:Ge(g).map((m,h)=>h===f.cellIndexInRow?mn(m,o.rowSpanProp,f.rowSpan-1):m)})))),p+=f?f.colSpan:1}const c=Pe(l,t);return l=nt(l,c,s.sectionIndex,p=>{let f=-1;const b=Ge(p).filter(g=>g.name===o.row?(f++,f!==s.rowIndexInSection):!0);return{...p,children:b}}),l}function ah(e,t,n,o){const r=yt(t),s=Pe(e,t);if(!Si(s,n,o).ok)return e;const a=ih(s),l=a.findIndex(h=>n>=h.start&&ns.rows.filter(v=>{const y=v.cells[0]?.gridRow??-1;return v.sectionIndex===f&&y>=h.start&&yv.rowIndexInSection),g=b(c),m=b(p);return g.length===0||m.length===0?e:nt(e,s,f,h=>{const v=[...Ge(h)],y=[];v.forEach((M,O)=>{M.name===r.row&&y.push(O)});const _=g.map(M=>y[M]).filter(M=>M!==void 0),w=m.map(M=>y[M]).filter(M=>M!==void 0);if(_.length===0||w.length===0)return h;const k=_.map(M=>v[M]),x=w.map(M=>v[M]),$=Math.min(..._)n>=m.start&&nk.gridCol>=f.start&&k.gridColk.gridCol>=b.start&&k.gridColk.cellIndexInRow),_=v.map(k=>k.cellIndexInRow),w=Pe(g,t);g=nt(g,w,m.sectionIndex,k=>bt(k,r.row,m.rowIndexInSection,x=>{const $=[...Ge(x)],S=y.map(M=>Ei(x,r,M)).filter(M=>M>=0),C=_.map(M=>Ei(x,r,M)).filter(M=>M>=0);if(S.length===0||C.length===0)return x;const T=S.map(M=>$[M]),E=C.map(M=>$[M]),A=Math.min(...S,...C),N=Math.max(...S,...C),D=[...E,...T],B=[...$.slice(0,A),...D,...$.slice(N+1)];return{...x,children:B}}))}return g}function dh(e,t,n){const o=yt(t),r=Pe(e,t);if(r.rows.length===0||r.colCount===0)return sl(e,t,-1);const s=Math.max(0,Math.min(n+1,r.colCount));let a=e;const l=new Set;for(const d of r.rows){const c=d.cells[0]?.gridRow??0,p=s>0?Ve(r,c,s-1):null,f=s1){if(!l.has(p.cell)){l.add(p.cell);const h=Pe(a,t);a=nt(a,h,p.sectionIndex,v=>bt(v,o.row,p.rowIndexInSection,y=>({...y,children:Ge(y).map((_,w)=>w===p.cellIndexInRow?mn(_,o.colSpanProp,p.colSpan+1):_)})))}continue}if(p&&p.gridRow0&&d.cells.every(h=>h.isHeader),m=Pe(a,t);a=nt(a,m,d.sectionIndex,h=>bt(h,o.row,d.rowIndexInSection,v=>{const y=[...Ge(v)];let _;if(b<0)_=y.findIndex(w=>ir(w,o)),_<0&&(_=y.length);else{const w=Ei(v,o,b);_=w<0?y.length:w+1}return y.splice(_,0,$i(o,void 0,g)),{...v,children:y}}))}return a}function ch(e,t,n){const o=yt(t),r=Pe(e,t);if(n<0||n>=r.colCount||r.colCount<=1)return e;let s=e;const a=new Set;for(const l of r.rows){const d=l.cells[0]?.gridRow??0,c=Ve(r,d,n);if(!(!c||a.has(c.cell)||c.gridRow1){const p=Pe(s,t);s=nt(s,p,c.sectionIndex,f=>bt(f,o.row,c.rowIndexInSection,b=>({...b,children:Ge(b).map((g,m)=>m===c.cellIndexInRow?mn(g,o.colSpanProp,c.colSpan-1):g)})))}else{const p=Pe(s,t);s=nt(s,p,c.sectionIndex,f=>bt(f,o.row,c.rowIndexInSection,b=>{let g=-1;const m=Ge(b).filter(h=>ir(h,o)?(g++,g!==c.cellIndexInRow):!0);return{...b,children:m}}))}}return s}function al(e,t,n,o,r,s){const a=yt(t),l=Pe(e,t),d=Math.min(n,r),c=Math.max(n,r),p=Math.min(o,s),f=Math.max(o,s);if(d===c&&p===f)return{table:e,ok:!1,reason:"single"};if(!l.valid)return{table:e,ok:!1,reason:"invalid-grid"};const b=new Set;for(let y=d;y<=c;y++)for(let _=p;_<=f;_++){const w=Ve(l,y,_);if(!w)return{table:e,ok:!1,reason:"hole"};if(w.gridRowc||w.gridCol+w.colSpan-1>f)return{table:e,ok:!1,reason:"not-rectangular"};b.add(w)}const g=Ve(l,d,p),m=Array.from(b).filter(y=>y!==g);let h=nt(e,l,g.sectionIndex,y=>bt(y,a.row,g.rowIndexInSection,_=>({..._,children:Ge(_).map((w,k)=>{if(k!==g.cellIndexInRow)return w;let x=mn(w,a.colSpanProp,f-p+1);return x=mn(x,a.rowSpanProp,c-d+1),x})})));const v=new Map;for(const y of m){const _=`${y.sectionIndex}:${y.rowIndexInSection}`,w=v.get(_)??[];w.push(y),v.set(_,w)}for(const[y,_]of v){const w=parseInt(y.split(":")[0],10),k=_[0].rowIndexInSection,x=new Set(_.map(S=>S.cellIndexInRow)),$=Pe(h,t);h=nt(h,$,w,S=>bt(S,a.row,k,C=>{let T=-1;const E=Ge(C).filter(A=>ir(A,a)?(T++,!x.has(T)):!0);return{...C,children:E}}))}return{table:h,ok:!0}}function uh(e,t,n,o){const r=yt(t),s=Pe(e,t),a=Ve(s,n,o);if(!a||a.colSpan<=1&&a.rowSpan<=1)return e;const l=a.gridRow,d=a.gridCol,c=a.gridRow+a.rowSpan-1,p=a.gridCol+a.colSpan-1,f=a.isHeader,b=typeof a.cell.props?.className=="string"?a.cell.props.className:void 0;let g=nt(e,s,a.sectionIndex,m=>bt(m,r.row,a.rowIndexInSection,h=>({...h,children:Ge(h).map((v,y)=>{if(y!==a.cellIndexInRow)return v;let _=mn(v,r.colSpanProp,1);return _=mn(_,r.rowSpanProp,1),_})})));for(let m=l;m<=c;m++)for(let h=d;h<=p;h++){if(m===l&&h===d)continue;const v=Pe(g,t),y=v.rows.find(k=>k.cells.some(x=>x.gridRow===m))??v.rows[m];if(!y)continue;let _=-1;for(const k of y.cells)k.gridRow===m&&k.gridColbt(k,r.row,y.rowIndexInSection,x=>{const $=[...Ge(x)];let S;if(_<0)S=$.findIndex(C=>ir(C,r)),S<0&&(S=$.length);else{const C=Ei(x,r,_);S=C<0?$.length:C+1}return $.splice(S,0,$i(r,w,f)),{...x,children:$}}))}return g}function Ii(e,t,n,o,r){const s=yt(t),a=Pe(e,t),l=Ve(a,n,o);return l?nt(e,a,l.sectionIndex,d=>bt(d,s.row,l.rowIndexInSection,c=>({...c,children:Ge(c).map((p,f)=>{if(f!==l.cellIndexInRow)return p;const b={...p.props??{}};r.trim()===""?delete b.className:b.className=r;const g={...p,props:b};return Object.keys(b).length===0&&delete g.props,g})}))):e}function Jt(e,t,n,o,r,s){const a=yt(t),l=Pe(e,t),d=Ve(l,n,o);return d?nt(e,l,d.sectionIndex,c=>bt(c,a.row,d.rowIndexInSection,p=>({...p,children:Ge(p).map((f,b)=>{if(b!==d.cellIndexInRow)return f;const g={...f.props??{}},m={...g.style??{}};!s||s.trim()===""?delete m[r]:m[r]=s,Object.keys(m).length===0?delete g.style:g.style=m;const h={...f,props:g};return Object.keys(g).length===0&&delete h.props,h})}))):e}function ho(e,t,n,o,r,s,a){const l=yt(t),d=Pe(e,t),c=Ve(d,n,o);if(!c)return e;const p=a==="dark";return nt(e,d,c.sectionIndex,f=>bt(f,l.row,c.rowIndexInSection,b=>({...b,children:Ge(b).map((g,m)=>{if(m!==c.cellIndexInRow)return g;const h={...g.props??{}},v=typeof h.className=="string"?h.className:"",y=mC(v,s,r,p);y===""?delete h.className:h.className=y;const _={...g,props:h};return Object.keys(h).length===0&&delete _.props,_})})))}function ph(e,t,n,o,r){const s=yt(t),a=Pe(e,t),l=Ve(a,n,o);return l?nt(e,a,l.sectionIndex,d=>bt(d,s.row,l.rowIndexInSection,c=>({...c,children:Ge(c).map((p,f)=>f===l.cellIndexInRow?{...p,text:r}:p)}))):e}function fh(e,t,n,o,r){const s=yt(t),a=Pe(e,t),l=Ve(a,n,o);return l?nt(e,a,l.sectionIndex,d=>bt(d,s.row,l.rowIndexInSection,c=>({...c,children:Ge(c).map((p,f)=>f===l.cellIndexInRow?r:p)}))):e}const gh=/^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/;function zz(e){return e.split(/\s+/).filter(Boolean)}function hh(e,t){for(const n of t)if(e===n||/^-?\d+$/.test(e.slice(n.length+1))&&e.startsWith(n+"-"))return!0;return!1}function yh({className:e,catalog:t,t:n,onChange:o,colorScheme:r="light",onPresetToken:s,colorStyle:a,onColorStyle:l,disabled:d}){const c=t?.sides??[],p=t?.widths??[],f=t?.colors??[],b=r==="dark",g=u.useMemo(()=>c.map(E=>E.prefix),[c]),m=u.useMemo(()=>new Set(f.map(E=>E.token)),[f]),h=u.useMemo(()=>zz(e),[e]),v=u.useMemo(()=>Gr(e,b),[e,b]),y=u.useMemo(()=>v.find(E=>m.has(E))??null,[v,m]),_=u.useMemo(()=>{const E=new Set;for(const A of c)h.some(D=>D===A.prefix||D.startsWith(A.prefix+"-")&&/^-?\d+$/.test(D.slice(A.prefix.length+1)))&&E.add(A.value);return E},[h,c]),w=u.useMemo(()=>{const E=h.find(N=>hh(N,g));if(!E)return"none";const A=p.filter(N=>N.value!=="none");for(const N of c){if(E===N.prefix)return A.find(D=>(D.suffix??"")==="")?.value??"thin";if(E.startsWith(N.prefix+"-")){const D=E.slice(N.prefix.length),B=A.find(M=>(M.suffix??"")===D);if(B)return B.value}}return"thin"},[h,g,c,p]);if(!t||c.length===0||p.length===0)return null;function k(E){return E.filter(A=>!hh(A,g))}function x(E,A){const N=k(h),D=p.find(O=>O.value===A);if(!D||A==="none"||E.size===0)return N.join(" ").trim();const B=D.suffix??"",M=[...N];for(const O of c)E.has(O.value)&&M.push(B?`${O.prefix}${B}`:O.prefix);return M.join(" ").trim()}const $=E=>{const A=new Set(_);A.has(E)?A.delete(E):(E==="all"?A.clear():A.delete("all"),A.add(E));const N=w==="none"?p.find(D=>D.value!=="none")?.value??"thin":w;o(x(A,N))},S=E=>{const A=_.size===0?new Set(["all"]):_;o(x(A,E))},C=E=>{s?.(E)},T=(E,A)=>E&&E.startsWith("$t:")?n(E.slice(3)):E||A;return i.jsxs("div",{className:"g7le-cell-border","data-testid":"g7le-cell-border",style:Nz,children:[i.jsx("div",{style:Mz,children:i.jsx("span",{style:Lz,children:n("layout_editor.table_editor.cell_border")})}),i.jsx("div",{style:ll,"data-testid":"g7le-cell-border-widths",children:p.map(E=>i.jsx("button",{type:"button",disabled:d,"data-testid":`g7le-cell-border-width-${E.value}`,"aria-pressed":w===E.value,onClick:()=>S(E.value),style:w===E.value?bh:dl,children:T(E.label,E.value)},E.value))}),w!=="none"&&i.jsx("div",{style:ll,"data-testid":"g7le-cell-border-sides",children:c.map(E=>i.jsx("button",{type:"button",disabled:d,"data-testid":`g7le-cell-border-side-${E.value}`,"aria-pressed":_.has(E.value),onClick:()=>$(E.value),style:_.has(E.value)?bh:dl,children:T(E.label,E.value)},E.value))}),w!=="none"&&(f.length>0||l)&&i.jsx(i.Fragment,{children:i.jsxs("div",{style:ll,"data-testid":"g7le-cell-border-colors",children:[f.map(E=>{const A=y===E.token;return i.jsx("button",{type:"button",disabled:d,title:T(E.label,E.value),"data-testid":`g7le-cell-border-color-${E.value}`,"aria-pressed":A,onClick:()=>C(E.token),style:{...mh,background:E.swatch??"#fff",outline:A?"2px solid #2563eb":"1px solid #cbd5e1"}},E.value)}),l&&(()=>{const E=!!a&&f.some(N=>N.swatch&&N.swatch.toLowerCase()===a.toLowerCase()),A=!b&&!!a&&!E;return i.jsx("label",{title:n(b?"layout_editor.table_editor.dark_free_color_hint":"layout_editor.table_inplace.custom_color"),"data-testid":"g7le-cell-border-color-custom",style:{...mh,position:"relative",overflow:"hidden",display:"inline-flex",alignItems:"center",justifyContent:"center",background:A&&gh.test(a)?a:"conic-gradient(red, yellow, lime, aqua, blue, magenta, red)",outline:A?"2px solid #2563eb":"1px solid #cbd5e1",opacity:b?.4:1,cursor:b?"not-allowed":"pointer"},children:i.jsx("input",{type:"color",disabled:d||b,"data-testid":"g7le-cell-border-color-picker",value:a&&gh.test(a)?a:"#000000",onChange:N=>l(N.target.value),style:{position:"absolute",width:0,height:0,opacity:0}})})})()]})})]})}const Nz={display:"flex",flexDirection:"column",gap:6},Mz={display:"flex",alignItems:"center",justifyContent:"space-between",gap:8},Lz={fontSize:12,fontWeight:600,color:"#475569"},ll={display:"flex",gap:4,flexWrap:"wrap",alignItems:"center"},dl={padding:"3px 8px",fontSize:11,border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",color:"#475569",cursor:"pointer"},bh={...dl,border:"1px solid #2563eb",background:"#eff6ff",color:"#1d4ed8",fontWeight:600},mh={width:22,height:22,borderRadius:5,padding:0,cursor:"pointer",boxSizing:"border-box"},vh=/^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/;function _h({colorStyle:e,className:t,catalog:n,t:o,colorScheme:r="light",onPresetToken:s,onCustomColor:a,onClear:l,disabled:d}){const c=n?.colors??[],p=r==="dark",f=e??"",b=Gr(t??"",p),g=c.find(_=>_.token&&b.includes(_.token))?.token??null,m=(_,w)=>_&&_.startsWith("$t:")?o(_.slice(3)):_||w,h=!g&&(p||f.trim()===""),v=!!f&&c.some(_=>_.swatch&&_.swatch.toLowerCase()===f.toLowerCase()),y=!p&&!!f&&!v;return i.jsxs("div",{className:"g7le-cell-fill","data-testid":"g7le-cell-fill",style:Pz,children:[i.jsx("div",{style:Oz,children:i.jsx("span",{style:Bz,children:o("layout_editor.table_editor.cell_fill")})}),i.jsxs("div",{style:Wz,"data-testid":"g7le-cell-fill-colors",children:[i.jsx("button",{type:"button",disabled:d,title:o("layout_editor.table_editor.cell_fill_none"),"data-testid":"g7le-cell-fill-none","aria-pressed":h,onClick:()=>l?.(),style:{...cl,background:"repeating-conic-gradient(#e2e8f0 0% 25%, #ffffff 0% 50%) 50% / 10px 10px",outline:h?"2px solid #2563eb":"1px solid #cbd5e1"}}),c.map(_=>{const w=!!_.token&&g===_.token,k=!p&&!!_.swatch&&f.toLowerCase()===(_.swatch??"").toLowerCase(),x=w||k,$=d||p&&!_.token;return i.jsx("button",{type:"button",disabled:$,title:m(_.label,_.value),"data-testid":`g7le-cell-fill-color-${_.value}`,"aria-pressed":x,onClick:()=>{_.token&&s?s(_.token):!p&&_.swatch&&a&&a(_.swatch)},style:{...cl,background:_.swatch??"#fff",outline:x?"2px solid #2563eb":"1px solid #cbd5e1",opacity:$?.4:1,cursor:$?"not-allowed":"pointer"}},_.value)}),a&&i.jsx("label",{title:o(p?"layout_editor.table_editor.dark_free_color_hint":"layout_editor.table_editor.cell_fill_custom"),"data-testid":"g7le-cell-fill-custom",style:{...cl,position:"relative",overflow:"hidden",display:"inline-flex",alignItems:"center",justifyContent:"center",background:y&&vh.test(f)?f:"conic-gradient(red, yellow, lime, aqua, blue, magenta, red)",outline:y?"2px solid #2563eb":"1px solid #cbd5e1",opacity:p?.4:1,cursor:p?"not-allowed":"pointer"},children:i.jsx("input",{type:"color",disabled:d||p,"data-testid":"g7le-cell-fill-color-picker",value:f&&vh.test(f)?f:"#ffffff",onChange:_=>a(_.target.value),style:{position:"absolute",width:0,height:0,opacity:0}})})]})]})}const Pz={display:"flex",flexDirection:"column",gap:6},Oz={display:"flex",alignItems:"center",justifyContent:"space-between",gap:8},Bz={fontSize:12,fontWeight:600,color:"#475569"},Wz={display:"flex",gap:4,flexWrap:"wrap",alignItems:"center"},cl={width:22,height:22,borderRadius:5,padding:0,cursor:"pointer",boxSizing:"border-box"};function jz(e){const t=/^(\d+)px$/.exec(e.trim());return t?parseInt(t[1],10):null}function xh({paddingStyle:e,catalog:t,t:n,onChange:o,disabled:r}){const s=t?.steps??[],a=e??"",l=jz(a),d=(h,v)=>h&&h.startsWith("$t:")?n(h.slice(3)):h||v,c=s.some(h=>(h.px??0)===(l??-1)),p=l!==null&&!c?String(l):"",[f,b]=u.useState(p);u.useEffect(()=>{b(p)},[e]);const g=h=>{o(h&&h>0?`${h}px`:""),b("")},m=h=>{const v=parseInt(h,10);Number.isFinite(v)&&v>=0&&o(v>0?`${v}px`:"")};return i.jsxs("div",{className:"g7le-cell-padding","data-testid":"g7le-cell-padding",style:Hz,children:[i.jsx("div",{style:Fz,children:n("layout_editor.table_editor.cell_padding")}),i.jsxs("div",{style:Kz,"data-testid":"g7le-cell-padding-steps",children:[s.map(h=>{const v=(h.px??0)===(l??-1)||!h.px&&a.trim()==="";return i.jsx("button",{type:"button",disabled:r,"data-testid":`g7le-cell-padding-step-${h.value}`,"aria-pressed":v,onClick:()=>g(h.px),style:v?Vz:wh,children:d(h.label,h.value)},h.value)}),i.jsxs("span",{style:{display:"inline-flex",alignItems:"center",gap:3},children:[i.jsx("input",{type:"number",min:0,disabled:r,"data-testid":"g7le-cell-padding-free",value:f,placeholder:n("layout_editor.table_editor.cell_padding_custom"),onChange:h=>b(h.target.value),onBlur:()=>f.trim()!==""&&m(f),onKeyDown:h=>{h.key==="Enter"&&f.trim()!==""&&m(f)},style:Uz}),i.jsx("span",{style:{fontSize:11,color:"#94a3b8"},children:"px"})]})]})]})}const Hz={display:"flex",flexDirection:"column",gap:6},Fz={fontSize:12,fontWeight:600,color:"#475569"},Kz={display:"flex",gap:4,flexWrap:"wrap",alignItems:"center"},wh={padding:"3px 8px",fontSize:11,border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",color:"#475569",cursor:"pointer"},Vz={...wh,border:"1px solid #2563eb",background:"#eff6ff",color:"#1d4ed8",fontWeight:600},Uz={width:48,padding:"3px 5px",fontSize:11,border:"1px solid #cbd5e1",borderRadius:6,boxSizing:"border-box"};function kh({colorScheme:e,onChange:t,t:n,disabled:o}){const r=e==="dark";return i.jsxs("div",{className:"g7le-color-scheme-tabs","data-testid":"g7le-color-scheme-tabs",style:qz,children:[i.jsxs("div",{style:Gz,children:[i.jsx("span",{style:Yz,children:n("layout_editor.table_editor.cell_color")}),i.jsxs("div",{style:Xz,children:[i.jsx("button",{type:"button",disabled:o,"data-testid":"g7le-color-scheme-light","aria-pressed":!r,onClick:()=>t("light"),style:r?ul:Sh,children:n("layout_editor.property_modal.scope.scheme_light")}),i.jsx("button",{type:"button",disabled:o,"data-testid":"g7le-color-scheme-dark","aria-pressed":r,onClick:()=>t("dark"),style:r?Sh:ul,children:n("layout_editor.property_modal.scope.scheme_dark")})]})]}),r&&i.jsx("div",{style:Jz,"data-testid":"g7le-color-scheme-dark-hint",children:n("layout_editor.table_editor.dark_free_color_hint")})]})}const qz={display:"flex",flexDirection:"column",gap:3},Gz={display:"flex",alignItems:"center",justifyContent:"space-between",gap:8},Yz={fontSize:12,fontWeight:700,color:"#0f172a"},Xz={display:"flex",gap:2},ul={padding:"2px 9px",fontSize:11,border:"none",borderBottom:"2px solid transparent",background:"transparent",color:"#94a3b8",cursor:"pointer"},Sh={...ul,color:"#1d4ed8",borderBottom:"2px solid #2563eb",fontWeight:600},Jz={fontSize:10,color:"#94a3b8",fontStyle:"italic"},pl="dark:";function Ch(e){return typeof e=="string"?e.split(/\s+/).filter(Boolean):[]}function $h(e,t){return t==="dark"?e.filter(n=>n.startsWith(pl)).map(n=>n.slice(pl.length)):e.filter(n=>!n.startsWith(pl))}const Zz={none:0,thin:1,medium:2,thick:4},Eh={top:{w:"borderTopWidth",s:"borderTopStyle",c:"borderTopColor"},bottom:{w:"borderBottomWidth",s:"borderBottomStyle",c:"borderBottomColor"},left:{w:"borderLeftWidth",s:"borderLeftStyle",c:"borderLeftColor"},right:{w:"borderRightWidth",s:"borderRightStyle",c:"borderRightColor"}},Ih=["top","bottom","left","right"];function Qz(e,t,n="light"){if(!e||!t)return{};const o=t.sides??[],r=t.widths??[],s=t.colors??[],a=Ch(e.props?.className);if(a.length===0&&!e.props?.style)return{};const l=[...r].filter(y=>y.value!=="none").sort((y,_)=>(_.suffix??"").length-(y.suffix??"").length),d={};for(const y of o)for(const _ of l){const w=_.suffix?`${y.prefix}${_.suffix}`:y.prefix;if(a.includes(w)){d[y.value]=_.value;break}}const c=e.props?.style??{},p=$h(a,n),f=s.find(y=>p.includes(y.token))?.swatch??"",g=(n==="light"&&typeof c.borderColor=="string"?c.borderColor:"")||f||"#94a3b8",m=y=>{const _=Eh[y]?.c,w=_?c[_]:void 0;return typeof w=="string"&&w?w:g},h={},v=(y,_)=>{const w=Eh[y];if(!w)return;const k=Zz[_]??1;h[w.w]=`${k}px`,h[w.s]=k>0?"solid":"none",k>0&&(h[w.c]=m(y))};if(d.all)for(const y of Ih)v(y,d.all);for(const y of Ih)d[y]&&v(y,d[y]);return h}function Rz(e,t,n="light"){if(!e?.props)return{};const o=e.props.style??{},r={},s=Ch(e.props.className),a=t?.colors?.find(c=>c.token&&$h(s,n).includes(c.token))?.swatch??"",d=(n==="light"&&typeof o.backgroundColor=="string"?o.backgroundColor:"")||a;return d&&(r.backgroundColor=d),typeof o.padding=="string"&&o.padding&&(r.padding=o.padding),r}const eN=/^\s*\$t:(custom\.[a-zA-Z0-9._-]+)\s*$/,tN=/^\s*\$t:([a-zA-Z0-9._-]+)\s*$/,nN=/\{\{.*?\}\}/;function Ah(e,t,n){const o=e?.text;if(typeof o!="string")return{display:"",editable:!1,isKey:!1};const r=vt(o);if(r&&r.key.startsWith("custom.")){let a="";try{const l=Pt.getInstance().translate(r.key,{templateId:t,locale:n});a=l&&l!==r.key?l:""}catch{a=""}return{display:a,editable:!0,isKey:!0}}if(nN.test(o))return{display:o,editable:!1,isKey:!1};const s=tN.exec(o);if(s){const a=s[1];let l="";try{const d=Pt.getInstance().translate(a,{templateId:t,locale:n});l=d&&d!==a?d:""}catch{l=""}return{display:l,editable:!0,isKey:!0}}return{display:o,editable:!0,isKey:!1}}function fl(e){const t=or(e);return t===null?{node:null,path:null}:{node:ki(e,t),path:t}}function oN(e){if(!e||typeof e.text!="string")return null;const t=eN.exec(e.text);if(t)return t[1];const n=vt(e.text);return n&&n.key.startsWith("custom.")?n.key:null}function rN({node:e,params:t,t:n,onPatchNode:o,templateIdentifier:r,candidates:s}){const{state:a}=Qe(),l=a.selectedRoute?.layoutName??null,d=a.locale,c=r??a.templateIdentifier,p=u.useMemo(()=>t&&typeof t=="object"?t:null,[t]);u.useMemo(()=>yt(p),[p]);const f=u.useMemo(()=>Pe(e,p),[e,p]),b=u.useMemo(()=>{const U=p?.cellBorder;return U&&typeof U=="object"?U:null},[p]),g=u.useMemo(()=>{const U=p?.cellBackground;return U&&typeof U=="object"?U:null},[p]),m=u.useMemo(()=>{const U=p?.cellPadding;return U&&typeof U=="object"?U:null},[p]),[h,v]=u.useState(null),[y,_]=u.useState(null),[w,k]=u.useState({}),[x,$]=u.useState(!1),[S,C]=u.useState(null),[T,E]=u.useState(null),[A,N]=u.useState("light"),D=u.useCallback(U=>o(U),[o]),B=u.useMemo(()=>h?Ve(f,h.r,h.c):null,[f,h]),M=T!==null?T:h?.c??null,O=S!==null?S:h?.r??null,z=u.useMemo(()=>{const U=(b?.sides??[]).find(xe=>xe.value==="all")??(b?.sides??[])[0],ue=(b?.widths??[]).find(xe=>xe.value!=="none");if(!U||!ue)return[];const fe=ue.suffix??"";return[fe?`${U.prefix}${fe}`:U.prefix]},[b]),H=u.useCallback((U,ue,fe)=>{if(z.length===0)return U;const xe=(b?.sides??[]).map(Q=>Q.prefix),he=Ve(Pe(U,p),ue,fe),ne=(typeof he?.cell?.props?.className=="string"?he.cell.props.className:"").split(/\s+/).filter(Boolean);return ne.some(Q=>xe.some(ae=>Q===ae||Q.startsWith(ae+"-")&&/^-?\d+$/.test(Q.slice(ae.length+1))))?U:Ii(U,p,ue,fe,[...ne,...z].join(" ").trim())},[z,b,p]),P=u.useCallback(U=>{if(!h)return;const ue=Math.min(h.r,y?.r??h.r),fe=Math.max(h.r,y?.r??h.r),xe=Math.min(h.c,y?.c??h.c),he=Math.max(h.c,y?.c??h.c),j=new Set,ne=[];for(let Q=ue;Q<=fe;Q++)for(let ae=xe;ae<=he;ae++)j.add(`${Q}:${ae}`),ne.push({r:Q,c:ae});let pe=e;for(const Q of ne){if(pe=H(pe,Q.r,Q.c),pe=Jt(pe,p,Q.r,Q.c,"borderColor",U),Q.r>0&&!j.has(`${Q.r-1}:${Q.c}`)){const ae=Ve(Pe(pe,p),Q.r-1,Q.c);ae&&(pe=Jt(pe,p,ae.gridRow,ae.gridCol,"borderBottomColor",U))}if(Q.c>0&&!j.has(`${Q.r}:${Q.c-1}`)){const ae=Ve(Pe(pe,p),Q.r,Q.c-1);ae&&(pe=Jt(pe,p,ae.gridRow,ae.gridCol,"borderRightColor",U))}}D(pe)},[h,y,e,p,D,H]),I=u.useCallback(U=>P(U),[P]),L=u.useMemo(()=>(b?.colors??[]).map(U=>U.token).filter(Boolean),[b]),F=u.useCallback(U=>{if(!h||L.length===0)return;const ue=Math.min(h.r,y?.r??h.r),fe=Math.max(h.r,y?.r??h.r),xe=Math.min(h.c,y?.c??h.c),he=Math.max(h.c,y?.c??h.c);let j=e;for(let ne=ue;ne<=fe;ne++)for(let pe=xe;pe<=he;pe++)j=H(j,ne,pe),j=ho(j,p,ne,pe,U,L,A);D(j)},[h,y,e,p,D,H,L,A]),Y=u.useMemo(()=>{if(A==="dark")return"";const U=B?.cell.props?.style;return typeof U?.borderColor=="string"?U.borderColor:""},[B,A]),K=u.useCallback((U,ue)=>{if(!h)return;const fe=Math.min(h.r,y?.r??h.r),xe=Math.max(h.r,y?.r??h.r),he=Math.min(h.c,y?.c??h.c),j=Math.max(h.c,y?.c??h.c);let ne=e;for(let pe=fe;pe<=xe;pe++)for(let Q=he;Q<=j;Q++)ne=Jt(ne,p,pe,Q,U,ue);D(ne)},[h,y,e,p,D]),X=u.useMemo(()=>(g?.colors??[]).map(U=>U.token).filter(U=>!!U),[g]),Z=u.useCallback(()=>h?{r0:Math.min(h.r,y?.r??h.r),r1:Math.max(h.r,y?.r??h.r),c0:Math.min(h.c,y?.c??h.c),c1:Math.max(h.c,y?.c??h.c)}:null,[h,y]),se=u.useCallback(U=>{const ue=Z();if(!ue||X.length===0)return;let fe=e;for(let xe=ue.r0;xe<=ue.r1;xe++)for(let he=ue.c0;he<=ue.c1;he++)fe=ho(fe,p,xe,he,U,X,A);D(fe)},[Z,e,p,D,X,A]),oe=u.useCallback(U=>K("backgroundColor",U),[K]),G=u.useCallback(()=>{const U=Z();if(!U)return;let ue=e;for(let fe=U.r0;fe<=U.r1;fe++)for(let xe=U.c0;xe<=U.c1;xe++)ue=ho(ue,p,fe,xe,void 0,X,A),A==="light"&&(ue=Jt(ue,p,fe,xe,"backgroundColor",void 0));D(ue)},[Z,e,p,D,X,A]),ee=u.useCallback(U=>K("padding",U),[K]),ge=u.useMemo(()=>{if(A==="dark")return"";const U=B?.cell.props?.style;return typeof U?.backgroundColor=="string"?U.backgroundColor:""},[B,A]),V=u.useMemo(()=>typeof B?.cell.props?.className=="string"?B.cell.props.className:"",[B]),te=u.useMemo(()=>{const U=B?.cell.props?.style;return typeof U?.padding=="string"?U.padding:""},[B]),be=u.useCallback(U=>U==="section"?n("layout_editor.table_editor.move_blocked_section"):"",[n]),Ee=u.useCallback((U,ue)=>Si(f,U,ue),[f]),De=u.useCallback((U,ue)=>Ci(f,U,ue),[f]),Se=u.useCallback(U=>D(sl(e,p,U)),[e,p,D]),Ue=u.useCallback(U=>{D(sh(e,p,U)),v(null),_(null)},[e,p,D]),Me=u.useCallback((U,ue)=>D(ah(e,p,U,ue)),[e,p,D]),Oe=u.useCallback(U=>D(dh(e,p,U)),[e,p,D]),Ye=u.useCallback(U=>{D(ch(e,p,U)),v(null),_(null)},[e,p,D]),ze=u.useCallback((U,ue)=>D(lh(e,p,U,ue)),[e,p,D]),Xe=u.useCallback(()=>{if(!h||!y)return;const U=al(e,p,h.r,h.c,y.r,y.c);U.ok&&(D(U.table),v({r:Math.min(h.r,y.r),c:Math.min(h.c,y.c)}),_(null))},[e,p,h,y,D]),ve=u.useCallback(()=>{h&&(D(uh(e,p,h.r,h.c)),_(null))},[e,p,h,D]),qe=u.useCallback(U=>{if(!h)return;const ue=Math.min(h.r,y?.r??h.r),fe=Math.max(h.r,y?.r??h.r),xe=Math.min(h.c,y?.c??h.c),he=Math.max(h.c,y?.c??h.c);let j=e;for(let ne=ue;ne<=fe;ne++)for(let pe=xe;pe<=he;pe++)j=Ii(j,p,ne,pe,U);D(j)},[e,p,h,y,D]),We=u.useCallback(async(U,ue)=>{const fe=`${U}:${ue}`,xe=w[fe];if(xe===void 0)return;const he=Ve(f,U,ue);if(!he)return;const{node:j,path:ne}=fl(he.cell);if(ne===null)return;const pe=Ah(j,c,d);if(pe.editable){if(xe===pe.display){k(Q=>{const ae={...Q};return delete ae[fe],ae});return}$(!0);try{const Q=oN(j);if(Q)await vr(c,Q,d,xe),await tn(c,d);else{const ae=await qn(c,l,d,xe);if(ae.kind==="ok"&&ae.resource){const Ae=`$t:${ae.resource.translation_key}`;if(ne.length===0)D(ph(e,p,U,ue,Ae));else{const dt=rr(he.cell,ne,Ae);D(fh(e,p,U,ue,dt))}await tn(c,d)}}}finally{$(!1),k(Q=>{const ae={...Q};return delete ae[fe],ae})}}},[w,f,c,d,l,e,p,D]),ce=u.useMemo(()=>{if(!B)return null;const{node:U,path:ue}=fl(B.cell);return ue===null?null:{value:typeof U?.text=="string"?U.text:"",editable:!0}},[B]),q=u.useCallback(U=>{if(typeof U!="string"||U.length===0||!h)return;const ue=Ve(f,h.r,h.c);if(!ue)return;const fe=or(ue.cell);if(fe!==null)if(fe.length===0)D(ph(e,p,h.r,h.c,U));else{const xe=rr(ue.cell,fe,U);D(fh(e,p,h.r,h.c,xe))}},[h,f,e,p,D]),re=u.useCallback((U,ue,fe)=>{fe?_({r:U,c:ue}):(v({r:U,c:ue}),_(null))},[]),ie=u.useMemo(()=>Array.from({length:f.colCount},(U,ue)=>ue),[f.colCount]),me=!!(h&&y&&(h.r!==y.r||h.c!==y.c)),Ce=!!(B&&(B.colSpan>1||B.rowSpan>1)),$e=B&&typeof B.cell.props?.className=="string"?B.cell.props.className:"";return i.jsxs("div",{className:"g7le-node-editor g7le-node-editor--table","data-testid":"g7le-table-editor",style:iN,children:[i.jsx("div",{style:sN,children:n("layout_editor.table_editor.title")}),i.jsx("div",{style:{overflowX:"auto"},children:i.jsxs("table",{"data-testid":"g7le-table-editor-grid",style:aN,children:[i.jsx("thead",{children:i.jsxs("tr",{children:[i.jsx("th",{style:lN}),ie.map(U=>{const ue=De(U,-1),fe=De(U,1),xe=M===U;return i.jsx("th",{style:dN,"data-testid":`g7le-table-colgutter-${U}`,onMouseEnter:()=>E(U),onMouseLeave:()=>E(he=>he===U?null:he),children:i.jsxs("div",{style:{...uN,visibility:xe?"visible":"hidden"},children:[i.jsx("button",{type:"button",title:ue.ok?n("layout_editor.table_editor.move_col_left"):be(ue.reason)||n("layout_editor.table_editor.move_col_left"),disabled:!ue.ok||x,onClick:()=>ze(U,-1),"data-testid":`g7le-table-col-left-${U}`,style:ue.ok?vn:Ai,children:"←"}),i.jsx("button",{type:"button",title:n("layout_editor.table_editor.add_col"),disabled:x,onClick:()=>Oe(U),"data-testid":`g7le-table-col-add-${U}`,style:vn,children:"+"}),i.jsx("button",{type:"button",title:n("layout_editor.table_editor.remove_col"),disabled:f.colCount<=1||x,onClick:()=>Ye(U),"data-testid":`g7le-table-col-remove-${U}`,style:Th,children:"✕"}),i.jsx("button",{type:"button",title:fe.ok?n("layout_editor.table_editor.move_col_right"):be(fe.reason)||n("layout_editor.table_editor.move_col_right"),disabled:!fe.ok||x,onClick:()=>ze(U,1),"data-testid":`g7le-table-col-right-${U}`,style:fe.ok?vn:Ai,children:"→"})]})},U)})]})}),i.jsx("tbody",{children:f.rows.map((U,ue)=>{const fe=U.cells[0]?.gridRow??ue,xe=O===fe;return i.jsxs("tr",{"data-testid":`g7le-table-row-${fe}`,onMouseEnter:()=>C(fe),onMouseLeave:()=>C(he=>he===fe?null:he),children:[i.jsx("td",{style:cN,"data-testid":`g7le-table-rowgutter-${fe}`,children:(()=>{const he=Ee(fe,-1),j=Ee(fe,1);return i.jsxs("div",{style:{...pN,visibility:xe?"visible":"hidden"},children:[i.jsx("button",{type:"button",title:he.ok?n("layout_editor.table_editor.move_row_up"):be(he.reason)||n("layout_editor.table_editor.move_row_up"),disabled:!he.ok||x,onClick:()=>Me(fe,-1),"data-testid":`g7le-table-row-up-${fe}`,style:he.ok?vn:Ai,children:"↑"}),i.jsx("button",{type:"button",title:n("layout_editor.table_editor.add_row"),disabled:x,onClick:()=>Se(fe),"data-testid":`g7le-table-row-add-${fe}`,style:vn,children:"+"}),i.jsx("button",{type:"button",title:n("layout_editor.table_editor.remove_row"),disabled:f.rows.length<=1||x,onClick:()=>Ue(fe),"data-testid":`g7le-table-row-remove-${fe}`,style:Th,children:"✕"}),i.jsx("button",{type:"button",title:j.ok?n("layout_editor.table_editor.move_row_down"):be(j.reason)||n("layout_editor.table_editor.move_row_down"),disabled:!j.ok||x,onClick:()=>Me(fe,1),"data-testid":`g7le-table-row-down-${fe}`,style:j.ok?vn:Ai,children:"↓"})]})})()}),U.cells.map(he=>{const j=`${he.gridRow}:${he.gridCol}`,{node:ne,path:pe}=fl(he.cell),Q=Ah(ne,c,d),ae=pe!==null&&Q.editable,Ae=pe===null,dt=w[j],pt=dt!==void 0?dt:Q.display,ot=h&&h.r===he.gridRow&&h.c===he.gridCol,Le=h&&y&&he.gridRow>=Math.min(h.r,y.r)&&he.gridRow<=Math.max(h.r,y.r)&&he.gridCol>=Math.min(h.c,y.c)&&he.gridCol<=Math.max(h.c,y.c);return i.jsx("td",{colSpan:he.colSpan,rowSpan:he.rowSpan,"data-testid":`g7le-table-cell-${he.gridRow}-${he.gridCol}`,onClick:je=>re(he.gridRow,he.gridCol,je.shiftKey),style:{...fN,...Qz(he.cell,b,A),...Rz(he.cell,g,A),...he.isHeader?gN:{},...ot?hN:{},...Le&&!ot?yN:{}},children:ae?i.jsx("input",{type:"text","data-testid":`g7le-table-cell-input-${he.gridRow}-${he.gridCol}`,value:pt,placeholder:n("layout_editor.table_editor.cell_text"),disabled:x,onMouseDown:je=>{je.shiftKey&&je.preventDefault(),re(he.gridRow,he.gridCol,je.shiftKey)},onClick:je=>je.stopPropagation(),onChange:je=>k(Je=>({...Je,[j]:je.target.value})),onBlur:()=>{We(he.gridRow,he.gridCol)},style:bN}):Ae?i.jsx("span",{"data-testid":`g7le-table-cell-struct-${he.gridRow}-${he.gridCol}`,style:Dh,children:n("layout_editor.table_editor.structural_cell")}):i.jsx("span",{style:Dh,children:Q.display})},j)})]},ue)})})]})}),i.jsxs("button",{type:"button","data-testid":"g7le-table-add-row-bottom",onClick:()=>Se(f.rows.length-1),disabled:x,style:mN,children:["+ ",n("layout_editor.table_editor.add_row")]}),i.jsxs("div",{style:vN,"data-testid":"g7le-table-cell-tools",children:[i.jsx("button",{type:"button","data-testid":"g7le-table-merge",onClick:Xe,disabled:!me||x,style:zh,children:n("layout_editor.table_editor.merge_cells")}),i.jsx("button",{type:"button","data-testid":"g7le-table-unmerge",onClick:ve,disabled:!Ce||x,style:zh,children:n("layout_editor.table_editor.unmerge_cells")})]}),B&&ce&&i.jsxs("div",{style:xN,"data-testid":"g7le-table-cell-i18n-section",children:[i.jsx("div",{style:wN,children:n("layout_editor.table_editor.cell_i18n_label")}),i.jsx(_t,{value:ce.value,onChange:q,t:n,placeholder:n("layout_editor.table_editor.cell_text"),testidPrefix:"g7le-table-cell-i18n",candidates:s,enableExpressionTree:!0,expressionTreeCollapsible:!0})]}),B&&i.jsxs("div",{style:_N,"data-testid":"g7le-table-cell-color-section",children:[i.jsx(kh,{colorScheme:A,onChange:N,t:n,disabled:x}),b&&i.jsx("div",{style:gl,"data-testid":"g7le-table-cell-border-row",children:i.jsx(yh,{className:$e,catalog:b,t:n,onChange:qe,colorScheme:A,onPresetToken:F,colorStyle:Y,onColorStyle:I,disabled:x})}),i.jsx("div",{style:gl,"data-testid":"g7le-table-cell-fill-row",children:i.jsx(_h,{colorStyle:ge,className:V,catalog:g,t:n,colorScheme:A,onPresetToken:se,onCustomColor:oe,onClear:G,disabled:x})})]}),B&&i.jsx("div",{style:gl,"data-testid":"g7le-table-cell-padding-row",children:i.jsx(xh,{paddingStyle:te,catalog:m,t:n,onChange:ee,disabled:x})}),B&&!b&&i.jsxs("label",{style:kN,"data-testid":"g7le-table-cell-border-row",children:[i.jsx("span",{style:SN,children:n("layout_editor.table_editor.cell_border")}),i.jsx("input",{type:"text","data-testid":"g7le-table-cell-border-input",value:$e,placeholder:n("layout_editor.table_editor.cell_class_placeholder"),onChange:U=>qe(U.target.value),disabled:x,style:CN})]}),!f.valid&&i.jsx("div",{"data-testid":"g7le-table-grid-warning",style:$N,children:n("layout_editor.table_editor.grid_invalid")}),i.jsx("div",{style:EN,children:n("layout_editor.table_editor.select_hint")})]})}const iN={display:"flex",flexDirection:"column",gap:8,width:"100%",marginBottom:12},sN={fontSize:12,fontWeight:700,color:"#0f172a"},aN={borderCollapse:"collapse",width:"auto"},lN={width:28,background:"transparent",border:"none"},dN={padding:2,border:"none",background:"transparent"},cN={padding:2,border:"none",background:"transparent",verticalAlign:"middle"},uN={display:"flex",gap:2,justifyContent:"center"},pN={display:"grid",gridTemplateColumns:"repeat(2, 16px)",gap:2,justifyContent:"center"},vn={width:16,height:16,lineHeight:"14px",padding:0,fontSize:10,border:"1px solid #cbd5e1",borderRadius:4,background:"#fff",color:"#475569",cursor:"pointer"},Th={...vn,border:"1px solid #fecaca",color:"#dc2626"},Ai={...vn,cursor:"not-allowed",opacity:.4,color:"#94a3b8",borderColor:"#e2e8f0"},fN={border:"1px solid #cbd5e1",padding:0,width:64,minWidth:64,maxWidth:120,height:24,background:"#fff",cursor:"pointer"},gN={background:"#f1f5f9",fontWeight:600},hN={outline:"2px solid #2563eb",outlineOffset:-2},yN={background:"#dbeafe"},bN={width:"100%",boxSizing:"border-box",padding:"2px 5px",fontSize:11,height:22,lineHeight:"18px",border:"none",background:"transparent",outline:"none"},Dh={display:"block",padding:"2px 5px",fontSize:10,color:"#64748b",fontStyle:"italic",overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap",maxWidth:110},mN={padding:"4px 8px",fontSize:12,border:"1px dashed #94a3b8",borderRadius:6,background:"#fff",color:"#475569",cursor:"pointer",alignSelf:"flex-start"},vN={display:"flex",gap:6},zh={padding:"4px 8px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",color:"#475569",cursor:"pointer"},_N={display:"flex",flexDirection:"column",gap:8,borderTop:"1px solid #f1f5f9",paddingTop:8},xN={display:"flex",flexDirection:"column",gap:6,borderTop:"1px solid #f1f5f9",paddingTop:8},wN={fontSize:12,fontWeight:700,color:"#0f172a"},gl={display:"flex",flexDirection:"column",gap:6},kN={display:"flex",alignItems:"center",gap:6},SN={fontSize:12,color:"#475569",minWidth:60},CN={flex:1,minWidth:0,padding:"4px 6px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6},$N={fontSize:11,color:"#b45309",background:"#fffbeb",border:"1px solid #fde68a",borderRadius:6,padding:"4px 6px"},EN={fontSize:11,color:"#94a3b8"},IN=/\{\{.*?\}\}/;function AN(e,t,n){const r=(e.props??{})[t];return Array.isArray(r)?r:r===void 0&&n?JSON.parse(JSON.stringify(n)):[]}function TN(e){return typeof e=="string"&&IN.test(e)}function hl(e,t,n){return e?e.startsWith("$t:")?t(e.slice(3)):e:n}function DN(e,t,n){return typeof e=="object"&&e!==null?e[t.key]:n?e:void 0}function zN(e,t,n,o){if(typeof e=="object"&&e!==null){const r={...e};return o===void 0?delete r[t.key]:r[t.key]=o,r}return n?o:{[t.key]:o}}function Nh({node:e,params:t,t:n,onPatchNode:o,templateIdentifier:r,candidates:s}){const a=typeof t?.arrayProp=="string"?t.arrayProp:null,l=Array.isArray(t?.fields)?t.fields:[],d=hl(typeof t?.itemLabel=="string"?t.itemLabel:void 0,n,n("layout_editor.array_editor.item")),c=t?.newItem,p=c&&typeof c=="object"?c:null,f=typeof c=="string"||typeof c=="number"?c:null,b=(l.find($=>$.primary)??l[0])?.key,g=(e.props??{})[a??""],m=Array.isArray(t?.defaultItems)?t.defaultItems:null,h=a?AN(e,a,m):[],v=u.useCallback($=>{if(!a)return;const S={...e.props??{}};S[a]=$,o({...e,props:S})},[a,e,o]),y=u.useCallback(()=>{if(p)return JSON.parse(JSON.stringify(p));if(f!==null)return f;const $={};for(const S of l)(S.widget??"text")==="boolean"?$[S.key]=!1:$[S.key]="";return $},[p,f,l]),_=u.useCallback(()=>{v([...h,y()])},[v,h,y]),w=u.useCallback($=>{v(h.filter((S,C)=>C!==$))},[v,h]),k=u.useCallback(($,S)=>{const C=$+S;if(C<0||C>=h.length)return;const T=[...h];[T[$],T[C]]=[T[C],T[$]],v(T)},[v,h]),x=u.useCallback(($,S,C)=>{const T=S.key===b,E=h.map((A,N)=>N===$?zN(A,S,T,C):A);v(E)},[h,v,b]);return a?TN(g)||g!==void 0&&!Array.isArray(g)?i.jsx("div",{className:"g7le-node-editor g7le-node-editor--array","data-testid":"g7le-array-editor-bound",style:HN,children:n("layout_editor.array_editor.bound_degraded")}):i.jsxs("div",{className:"g7le-node-editor g7le-node-editor--array","data-testid":"g7le-array-editor",style:NN,children:[i.jsx("div",{style:MN,children:n("layout_editor.array_editor.items_title")}),h.length===0&&i.jsx("div",{"data-testid":"g7le-array-empty",style:Lh,children:n("layout_editor.array_editor.empty")}),h.map(($,S)=>i.jsxs("div",{"data-testid":`g7le-array-row-${S}`,style:LN,children:[i.jsxs("div",{style:PN,children:[i.jsxs("span",{style:ON,children:["#",S+1]}),i.jsx("div",{style:{flex:1}}),i.jsx("button",{type:"button","data-testid":`g7le-array-up-${S}`,title:n("layout_editor.array_editor.move_up"),disabled:S===0,onClick:()=>k(S,-1),style:Mh,children:"↑"}),i.jsx("button",{type:"button","data-testid":`g7le-array-down-${S}`,title:n("layout_editor.array_editor.move_down"),disabled:S===h.length-1,onClick:()=>k(S,1),style:Mh,children:"↓"}),i.jsx("button",{type:"button","data-testid":`g7le-array-remove-${S}`,title:n("layout_editor.array_editor.remove"),onClick:()=>w(S),style:WN,children:"✕"})]}),l.map(C=>{const T=C.widget??"text",E=C.key===b,A=hl(C.label,n,C.key),N=DN($,C,E),D=`g7le-array-field-${S}-${C.key}`;if(T==="boolean")return i.jsxs("label",{style:Wn,children:[i.jsx("span",{style:jn,children:A}),i.jsx("input",{type:"checkbox","data-testid":D,checked:N===!0,onChange:M=>x(S,C,M.target.checked)})]},C.key);if(T==="select"){const M=Array.isArray(C.options)?C.options:[];return i.jsxs("label",{style:Wn,children:[i.jsx("span",{style:jn,children:A}),i.jsxs("select",{"data-testid":D,value:N==null?"":String(N),onChange:O=>x(S,C,O.target.value===""?void 0:O.target.value),style:sr,children:[i.jsx("option",{value:"",children:"—"}),M.map((O,z)=>i.jsx("option",{value:String(O.value),children:hl(O.label,n,String(O.value))},z))]})]},C.key)}if(T==="i18n-text"){const M=typeof N=="string"?N:N==null?"":String(N);return i.jsxs("label",{style:Wn,children:[i.jsx("span",{style:jn,children:A}),i.jsx("div",{"data-testid":D,style:{flex:1,minWidth:0},children:i.jsx(_t,{value:M,onChange:O=>x(S,C,O===""?void 0:O),t:n,placeholder:A,testidPrefix:`${D}-i18n`,candidates:s,enableExpressionTree:!0,expressionTreeCollapsible:!0})})]},C.key)}if(T==="number"){const M=N==null?"":String(N);return i.jsxs("label",{style:Wn,children:[i.jsx("span",{style:jn,children:A}),i.jsx("input",{type:"number","data-testid":D,value:M,onChange:O=>{const z=O.target.value;x(S,C,z===""?void 0:Number(z))},style:sr})]},C.key)}if(T==="color"){const M=typeof N=="string"?N:"",O=/^#[0-9a-fA-F]{6}$/.test(M)?M:"#000000";return i.jsxs("label",{style:Wn,children:[i.jsx("span",{style:jn,children:A}),i.jsx("input",{type:"color","data-testid":`${D}-swatch`,value:O,onChange:z=>x(S,C,z.target.value),style:BN}),i.jsx("input",{type:"text","data-testid":D,value:M,placeholder:"#7C3AED",onChange:z=>x(S,C,z.target.value===""?void 0:z.target.value),style:sr})]},C.key)}if(T==="number-list"){const M=Array.isArray(N)?N.join(", "):"";return i.jsxs("label",{style:Wn,children:[i.jsx("span",{style:jn,children:A}),i.jsx("input",{type:"text","data-testid":D,value:M,placeholder:n("layout_editor.array_editor.number_list_placeholder"),onChange:O=>{const z=O.target.value.split(",").map(H=>H.trim()).filter(H=>H!=="").map(H=>Number(H)).filter(H=>!Number.isNaN(H));x(S,C,z)},style:sr})]},C.key)}const B=N==null?"":String(N);return i.jsxs("label",{style:Wn,children:[i.jsx("span",{style:jn,children:A}),i.jsx("input",{type:"text","data-testid":D,value:B,placeholder:T==="icon"?n("layout_editor.array_editor.icon_placeholder"):A,onChange:M=>x(S,C,M.target.value===""?void 0:M.target.value),style:sr})]},C.key)})]},S)),i.jsx("button",{type:"button","data-testid":"g7le-array-add",onClick:_,style:jN,children:n("layout_editor.array_editor.add",{item:d})})]}):i.jsx("div",{className:"g7le-node-editor g7le-node-editor--array","data-testid":"g7le-array-editor-misconfigured",style:Lh,children:n("layout_editor.array_editor.no_array_prop")})}const NN={display:"flex",flexDirection:"column",gap:8,width:"100%",marginBottom:12},MN={fontSize:12,fontWeight:700,color:"#0f172a",marginBottom:2},LN={display:"flex",flexDirection:"column",gap:4,padding:8,border:"1px solid #e2e8f0",borderRadius:8,background:"#f8fafc"},PN={display:"flex",gap:4,alignItems:"center"},ON={fontSize:11,fontWeight:700,color:"#64748b"},Wn={display:"flex",flexWrap:"wrap",gap:6,alignItems:"center"},jn={fontSize:11,color:"#475569",minWidth:64,flexShrink:0},sr={flex:1,minWidth:0,padding:"4px 6px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6},BN={width:32,height:28,padding:0,border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",cursor:"pointer",flexShrink:0},Mh={padding:"2px 6px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",color:"#475569",cursor:"pointer"},WN={padding:"2px 6px",fontSize:12,border:"1px solid #fecaca",borderRadius:6,background:"#fff",color:"#dc2626",cursor:"pointer"},jN={padding:"4px 8px",fontSize:12,border:"1px dashed #94a3b8",borderRadius:6,background:"#fff",color:"#475569",cursor:"pointer",alignSelf:"flex-start"},Lh={fontSize:11,color:"#94a3b8",fontStyle:"italic"},HN={fontSize:11,color:"#b45309",fontStyle:"italic",padding:"4px 2px"};function FN(e,t){return e?e.startsWith("$t:")?t(e.slice(3)):e:null}function KN(e){const{params:t,t:n}=e,o=Array.isArray(t?.groups)?t.groups:[];return o.length===0?i.jsx("div",{className:"g7le-node-editor g7le-node-editor--array-group","data-testid":"g7le-array-group-misconfigured",style:GN,children:n("layout_editor.array_editor.no_array_prop")}):i.jsx("div",{className:"g7le-node-editor g7le-node-editor--array-group","data-testid":"g7le-array-group-editor",style:VN,children:o.map((r,s)=>{const a=FN(r.title,n),{title:l,...d}=r;return i.jsxs("div",{"data-testid":`g7le-array-group-${r.arrayProp??s}`,style:UN,children:[a&&i.jsx("div",{style:qN,children:a}),i.jsx(Nh,{...e,params:d})]},r.arrayProp??s)})})}const VN={display:"flex",flexDirection:"column",gap:16,width:"100%",marginBottom:12},UN={display:"flex",flexDirection:"column",gap:4},qN={fontSize:12,fontWeight:700,color:"#1e293b",borderBottom:"1px solid #e2e8f0",paddingBottom:4},GN={fontSize:11,color:"#94a3b8",fontStyle:"italic"},YN=/\{\{.*?\}\}/;function XN(e){return typeof e=="string"&&YN.test(e)}function JN(e,t,n){return e?e.startsWith("$t:")?t(e.slice(3)):e:n}function ZN(e){const{node:t,params:n,t:o,onPatchNode:r}=e,s=typeof n?.arrayProp=="string"?n.arrayProp:null,a=typeof n?.idField=="string"?n.idField:"id",l=typeof n?.cellChildrenProp=="string"?n.cellChildrenProp:"cellChildren",d=typeof n?.cellChildComponent=="string"?n.cellChildComponent:"Div",c=JN(typeof n?.itemLabel=="string"?n.itemLabel:void 0,o,o("layout_editor.array_editor.item")),[p,f]=u.useState(null),b=s?(t.props??{})[s]:void 0,g=Array.isArray(b)?b:[],m=u.useCallback($=>{if(!s)return;const S={...t.props??{}};S[s]=$,r({...t,props:S})},[s,t,r]),h=u.useCallback(()=>{const $=n?.newItem;return $&&typeof $=="object"?JSON.parse(JSON.stringify($)):{[a]:"",[l]:[]}},[n,a,l]),v=u.useCallback(()=>{m([...g,h()])},[m,g,h]),y=u.useCallback($=>{m(g.filter((S,C)=>C!==$)),f(null)},[m,g]),_=u.useCallback(($,S)=>{const C=$+S;if(C<0||C>=g.length)return;const T=[...g];[T[$],T[C]]=[T[C],T[$]],m(T)},[m,g]),w=u.useCallback(($,S)=>{const C=g.map((T,E)=>E===$?{...T,[a]:S}:T);m(C)},[g,m,a]),k=u.useCallback($=>({type:"composite",name:"CardGridCell",children:Array.isArray($[l])?$[l]:[]}),[l]),x=u.useCallback($=>S=>{const C=Array.isArray(S.children)?S.children:[],T=g.map((E,A)=>A===$?{...E,[l]:C}:E);m(T)},[g,m,l]);return s?XN(b)||b!==void 0&&!Array.isArray(b)?i.jsx("div",{className:"g7le-node-editor g7le-node-editor--cell-tree","data-testid":"g7le-cell-tree-bound",style:lM,children:o("layout_editor.array_editor.bound_degraded")}):i.jsxs("div",{className:"g7le-node-editor g7le-node-editor--cell-tree","data-testid":"g7le-cell-tree-editor",style:QN,children:[i.jsx("div",{style:RN,children:o("layout_editor.cell_tree_editor.columns_title")}),g.length===0&&i.jsx("div",{"data-testid":"g7le-cell-tree-empty",style:Oh,children:o("layout_editor.array_editor.empty")}),g.map(($,S)=>{const C=typeof $[a]=="string"?$[a]:"",T=p===S;return i.jsxs("div",{"data-testid":`g7le-cell-tree-row-${S}`,style:eM,children:[i.jsxs("div",{style:tM,children:[i.jsxs("span",{style:nM,children:["#",S+1]}),i.jsx("input",{type:"text","data-testid":`g7le-cell-tree-id-${S}`,value:C,placeholder:o("layout_editor.cell_tree_editor.id_placeholder"),onChange:E=>w(S,E.target.value),style:oM}),i.jsx("button",{type:"button","data-testid":`g7le-cell-tree-up-${S}`,title:o("layout_editor.array_editor.move_up"),disabled:S===0,onClick:()=>_(S,-1),style:Ph,children:"↑"}),i.jsx("button",{type:"button","data-testid":`g7le-cell-tree-down-${S}`,title:o("layout_editor.array_editor.move_down"),disabled:S===g.length-1,onClick:()=>_(S,1),style:Ph,children:"↓"}),i.jsx("button",{type:"button","data-testid":`g7le-cell-tree-remove-${S}`,title:o("layout_editor.array_editor.remove"),onClick:()=>y(S),style:rM,children:"✕"})]}),i.jsx("button",{type:"button","data-testid":`g7le-cell-tree-toggle-${S}`,onClick:()=>f(T?null:S),style:iM,children:o(T?"layout_editor.cell_tree_editor.collapse_cells":"layout_editor.cell_tree_editor.expand_cells")}),T&&i.jsx("div",{"data-testid":`g7le-cell-tree-cells-${S}`,style:sM,children:i.jsx(Rg,{...e,node:k($),params:{childComponent:d},onPatchNode:x(S)})})]},S)}),i.jsx("button",{type:"button","data-testid":"g7le-cell-tree-add",onClick:v,style:aM,children:o("layout_editor.cell_tree_editor.add_column",{item:c})})]}):i.jsx("div",{className:"g7le-node-editor g7le-node-editor--cell-tree","data-testid":"g7le-cell-tree-misconfigured",style:Oh,children:o("layout_editor.array_editor.no_array_prop")})}const QN={display:"flex",flexDirection:"column",gap:8,width:"100%",marginBottom:12},RN={fontSize:12,fontWeight:700,color:"#0f172a",marginBottom:2},eM={display:"flex",flexDirection:"column",gap:6,padding:8,border:"1px solid #e2e8f0",borderRadius:8,background:"#f8fafc"},tM={display:"flex",gap:4,alignItems:"center"},nM={fontSize:11,fontWeight:700,color:"#64748b",flexShrink:0},oM={flex:1,minWidth:0,padding:"4px 6px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6},Ph={padding:"2px 6px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",color:"#475569",cursor:"pointer"},rM={padding:"2px 6px",fontSize:12,border:"1px solid #fecaca",borderRadius:6,background:"#fff",color:"#dc2626",cursor:"pointer"},iM={padding:"3px 8px",fontSize:11,border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",color:"#2563eb",cursor:"pointer",alignSelf:"flex-start"},sM={paddingLeft:8,borderLeft:"2px solid #e2e8f0"},aM={padding:"4px 8px",fontSize:12,border:"1px dashed #94a3b8",borderRadius:6,background:"#fff",color:"#475569",cursor:"pointer",alignSelf:"flex-start"},Oh={fontSize:11,color:"#94a3b8",fontStyle:"italic"},lM={fontSize:11,color:"#b45309",fontStyle:"italic",padding:"4px 2px"};function dM(e,t,n){const o=Pe(e,t);if(!n||n.length===0)return{measured:[],grid:o};const r=[];for(const s of o.rows)for(const a of s.cells){const d=il(e,a,t).map(p=>`children.${p}`).join("."),c=n.find(p=>p.path===d||p.path.endsWith("."+d));c&&r.push({ref:a,box:{top:c.top,left:c.left,width:c.width,height:c.height}})}return{measured:r,grid:o}}const yl=26,bl=50,ml=6,Bh=44,Wh=18,jh="g7le-table-inplace-empty-cell-style";function cM(){if(typeof document>"u"||document.getElementById(jh))return;const e=document.createElement("style");e.id=jh,e.textContent=".g7le-preview-frame td[data-editor-name],.g7le-preview-frame th[data-editor-name]{min-width:56px;height:38px;}.g7le-preview-frame table[data-editor-name]{min-width:120px;}",document.head.appendChild(e)}function uM({node:e,params:t,nodeBox:n,cellBoxes:o,selectedCellPath:r,colorScheme:s,t:a,onPatchNode:l,onRequestInlineEdit:d}){const c=u.useMemo(()=>t&&typeof t=="object"?t:null,[t]),{measured:p,grid:f}=u.useMemo(()=>dM(e,c,o),[e,c,o]),[b,g]=u.useState(null),[m,h]=u.useState(null),v=u.useMemo(()=>{if(!r)return null;for(const j of f.rows)for(const ne of j.cells){const pe=il(e,ne,c).map(Q=>`children.${Q}`).join(".");if(r===pe||r.startsWith(pe+"."))return{r:ne.gridRow,c:ne.gridCol}}return null},[r,f,e,c]),y=u.useMemo(()=>b&&Ve(f,b.r,b.c)?b:v,[b,v,f]),[_,w]=u.useState(!1),[k,x]=u.useState(s==="dark"?"dark":"light"),[$,S]=u.useState(null),[C,T]=u.useState(null),E=u.useRef(null),A=u.useCallback(()=>{E.current!==null&&(clearTimeout(E.current),E.current=null)},[]),N=u.useCallback(()=>{A(),E.current=setTimeout(()=>{S(null),T(null),E.current=null},240)},[A]);u.useEffect(()=>()=>A(),[A]),u.useEffect(()=>{cM()},[]);const D=u.useCallback(j=>l(j),[l]),B=u.useMemo(()=>y?Ve(f,y.r,y.c):null,[f,y]),M=u.useMemo(()=>B&&typeof B.cell.props?.className=="string"?B.cell.props.className:"",[B]),O=u.useCallback(j=>D(sl(e,c,j)),[e,c,D]),z=u.useCallback(j=>D(sh(e,c,j)),[e,c,D]),H=u.useCallback((j,ne)=>D(ah(e,c,j,ne)),[e,c,D]),P=u.useCallback(j=>D(dh(e,c,j)),[e,c,D]),I=u.useCallback(j=>D(ch(e,c,j)),[e,c,D]),L=u.useCallback((j,ne)=>D(lh(e,c,j,ne)),[e,c,D]),F=u.useCallback(j=>{if(!B)return;const ne=B.gridRow,pe=B.gridCol,Q=j==="down"?ne+B.rowSpan:ne+B.rowSpan-1,ae=j==="right"?pe+B.colSpan:pe+B.colSpan-1;if(Q>=f.rows.length||ae>=f.colCount)return;const Ae=al(e,c,ne,pe,Q,ae);Ae.ok&&D(Ae.table)},[B,f,e,c,D]),Y=!!(B&&B.gridCol+B.colSpan!y||!m||!Ve(f,m.r,m.c)?null:{r0:Math.min(y.r,m.r),c0:Math.min(y.c,m.c),r1:Math.max(y.r,m.r),c1:Math.max(y.c,m.c)},[y,m,f]),Z=u.useMemo(()=>{if(!X)return[];const j=new Set,ne=[];for(const pe of f.rows)for(const Q of pe.cells)if(Q.gridRow>=X.r0&&Q.gridRow<=X.r1&&Q.gridCol>=X.c0&&Q.gridCol<=X.c1){const ae=`${Q.gridRow}:${Q.gridCol}`;j.has(ae)||(j.add(ae),ne.push({r:Q.gridRow,c:Q.gridCol}))}return ne},[X,f]),se=Z.length>1,oe=u.useCallback(()=>{if(!X)return;const j=al(e,c,X.r0,X.c0,X.r1,X.c1);j.ok&&(D(j.table),h(null))},[X,e,c,D]),G=u.useCallback(()=>{y&&D(uh(e,c,y.r,y.c))},[e,c,y,D]),ee=u.useMemo(()=>{const j=c?.cellBorder;return j&&typeof j=="object"?j:null},[c]),ge=u.useMemo(()=>{const j=c?.cellBackground;return j&&typeof j=="object"?j:null},[c]),V=u.useMemo(()=>{const j=c?.cellPadding;return j&&typeof j=="object"?j:null},[c]),te=u.useMemo(()=>{if(_){const j=new Set,ne=[];for(const pe of f.rows)for(const Q of pe.cells){const ae=`${Q.gridRow}:${Q.gridCol}`;j.has(ae)||(j.add(ae),ne.push({r:Q.gridRow,c:Q.gridCol}))}return ne}return se?Z:y?[{r:y.r,c:y.c}]:[]},[_,se,Z,y,f]),be=u.useCallback(j=>{if(te.length===0)return;let ne=e;for(const pe of te){const Q=(ee?.colors??[]).map(Ae=>Ae.token),ae=j.split(/\s+/).filter(Ae=>Ae&&!Q.includes(Ae)).join(" ");ne=Ii(ne,c,pe.r,pe.c,ae)}D(ne)},[e,c,te,ee,D]),Ee=u.useMemo(()=>{const j=(ee?.sides??[]).find(Q=>Q.value==="all")??(ee?.sides??[])[0],ne=(ee?.widths??[]).find(Q=>Q.value!=="none");if(!j||!ne)return[];const pe=ne.suffix??"";return[pe?`${j.prefix}${pe}`:j.prefix]},[ee]),De=u.useCallback((j,ne,pe)=>{if(Ee.length===0)return j;const Q=(ee?.sides??[]).map(Le=>Le.prefix),ae=Ve(Pe(j,c),ne,pe),dt=(typeof ae?.cell?.props?.className=="string"?ae.cell.props.className:"").split(/\s+/).filter(Boolean);if(dt.some(Le=>Q.some(je=>Le===je||Le.startsWith(je+"-")&&/^-?\d+$/.test(Le.slice(je.length+1)))))return j;const ot=[...dt,...Ee].join(" ").trim();return Ii(j,c,ne,pe,ot)},[Ee,ee,c]),Se=u.useCallback((j,ne)=>{if(j.length===0)return;const pe=new Set(j.map(ae=>`${ae.r}:${ae.c}`));let Q=e;for(const ae of j){if(Q=De(Q,ae.r,ae.c),Q=Jt(Q,c,ae.r,ae.c,"borderColor",ne),ae.r>0&&!pe.has(`${ae.r-1}:${ae.c}`)){const Ae=Ve(Pe(Q,c),ae.r-1,ae.c);Ae&&(Q=Jt(Q,c,Ae.gridRow,Ae.gridCol,"borderBottomColor",ne))}if(ae.c>0&&!pe.has(`${ae.r}:${ae.c-1}`)){const Ae=Ve(Pe(Q,c),ae.r,ae.c-1);Ae&&(Q=Jt(Q,c,Ae.gridRow,Ae.gridCol,"borderRightColor",ne))}}D(Q)},[e,c,D,De]),Ue=u.useMemo(()=>(ee?.colors??[]).map(j=>j.token).filter(Boolean),[ee]),Me=u.useCallback(j=>{if(te.length===0||Ue.length===0)return;let ne=e;for(const pe of te)ne=De(ne,pe.r,pe.c),ne=ho(ne,c,pe.r,pe.c,j,Ue,k);D(ne)},[te,e,c,D,De,Ue,k]),Oe=u.useCallback(j=>Se(te,j),[Se,te]),Ye=u.useMemo(()=>{if(k==="dark")return"";const j=B?.cell.props?.style;return typeof j?.borderColor=="string"?j.borderColor:""},[B,k]),ze=u.useCallback((j,ne)=>{if(te.length===0)return;let pe=e;for(const Q of te)pe=Jt(pe,c,Q.r,Q.c,j,ne);D(pe)},[te,e,c,D]),Xe=u.useMemo(()=>(ge?.colors??[]).map(j=>j.token).filter(j=>!!j),[ge]),ve=u.useCallback(j=>{if(te.length===0||Xe.length===0)return;let ne=e;for(const pe of te)ne=ho(ne,c,pe.r,pe.c,j,Xe,k);D(ne)},[te,e,c,D,Xe,k]),qe=u.useCallback(j=>ze("backgroundColor",j),[ze]),We=u.useCallback(()=>{if(te.length===0)return;let j=e;for(const ne of te)j=ho(j,c,ne.r,ne.c,void 0,Xe,k),k==="light"&&(j=Jt(j,c,ne.r,ne.c,"backgroundColor",void 0));D(j)},[te,e,c,D,Xe,k]),ce=u.useCallback(j=>ze("padding",j),[ze]),q=u.useMemo(()=>{if(k==="dark")return"";const j=B?.cell.props?.style;return typeof j?.backgroundColor=="string"?j.backgroundColor:""},[B,k]),re=u.useMemo(()=>typeof B?.cell.props?.className=="string"?B.cell.props.className:"",[B]),ie=u.useMemo(()=>{const j=B?.cell.props?.style;return typeof j?.padding=="string"?j.padding:""},[B]),me=u.useCallback(j=>{const ne=j.currentTarget,pe=ne.style.pointerEvents;ne.style.pointerEvents="none";const Q=document.elementFromPoint(j.clientX,j.clientY);ne.style.pointerEvents=pe;const ae=Q?.closest?.("[data-dnd-handle-path]");ae&&ae.dispatchEvent(new PointerEvent("pointerdown",{bubbles:!0,cancelable:!0,clientX:j.clientX,clientY:j.clientY,pointerId:j.pointerId,pointerType:j.pointerType,button:j.button,isPrimary:!0}))},[]),Ce=u.useMemo(()=>{const j=new Map;for(const ne of p)j.has(ne.ref.gridCol)||j.set(ne.ref.gridCol,ne.box);return j},[p]),$e=u.useMemo(()=>{const j=new Map;for(const ne of p)j.has(ne.ref.gridRow)||j.set(ne.ref.gridRow,ne.box);return j},[p]);if(p.length===0)return null;const U=!!(B&&(B.colSpan>1||B.rowSpan>1)),ue=C!==null?C:y?.c??null,fe=$!==null?$:y?.r??null,xe=n.top-yl-ml-Bh,he=n.left-bl-ml-Bh;return i.jsxs("div",{className:"g7le-table-inplace","data-testid":"g7le-table-inplace",style:{position:"absolute",inset:0,pointerEvents:"none",zIndex:j7},children:[p.map(j=>{const ne=y&&y.r===j.ref.gridRow&&y.c===j.ref.gridCol,pe=X&&j.ref.gridRow>=X.r0&&j.ref.gridRow<=X.r1&&j.ref.gridCol>=X.c0&&j.ref.gridCol<=X.c1;return i.jsx("div",{"data-testid":`g7le-inplace-cell-${j.ref.gridRow}-${j.ref.gridCol}`,role:"button",tabIndex:-1,"aria-label":a("layout_editor.table_inplace.select_cell"),onMouseEnter:()=>{A(),S(j.ref.gridRow),T(j.ref.gridCol)},onMouseLeave:()=>{N()},onPointerDown:Q=>me(Q),onClick:Q=>{if(Q.stopPropagation(),Q.shiftKey&&(b||y))h({r:j.ref.gridRow,c:j.ref.gridCol});else{g({r:j.ref.gridRow,c:j.ref.gridCol}),h(null);const ae=il(e,j.ref,c).map(Ae=>`children.${Ae}`).join(".");d?.(ae)}},style:{position:"absolute",top:j.box.top,left:j.box.left,width:j.box.width,height:j.box.height,minWidth:Wh,minHeight:Wh,boxSizing:"border-box",pointerEvents:"auto",cursor:"pointer",outline:ne?"2px solid #2563eb":"none",outlineOffset:-2,background:pe&&!ne?"rgba(37,99,235,0.14)":"transparent"}},`cell-${j.ref.gridRow}-${j.ref.gridCol}`)}),i.jsx("div",{"data-testid":"g7le-inplace-col-rail",onMouseEnter:A,onMouseLeave:N,style:{position:"absolute",top:xe,left:n.left,width:n.width,height:yl,pointerEvents:"none"},children:ue!==null&&Ce.has(ue)&&(()=>{const j=ue,ne=Ce.get(j),pe=Ci(f,j,-1),Q=Ci(f,j,1),ae=ne.left-n.left;return i.jsxs("div",{"data-testid":`g7le-inplace-colgutter-${j}`,onMouseEnter:()=>{A(),T(j)},onMouseLeave:N,style:{position:"absolute",top:0,left:ae,width:ne.width,minWidth:96,height:yl,display:"flex",gap:3,justifyContent:"center",alignItems:"center",pointerEvents:"auto"},children:[i.jsx("button",{type:"button",title:a("layout_editor.table_inplace.move_col_left"),disabled:!pe.ok,onClick:()=>L(j,-1),"data-testid":`g7le-inplace-col-left-${j}`,style:pe.ok?_n:Ti,children:"←"}),i.jsx("button",{type:"button",title:a("layout_editor.table_inplace.add_col"),onClick:()=>P(j),"data-testid":`g7le-inplace-col-add-${j}`,style:_n,children:"+"}),i.jsx("button",{type:"button",title:a("layout_editor.table_inplace.remove_col"),disabled:f.colCount<=1,onClick:()=>I(j),"data-testid":`g7le-inplace-col-remove-${j}`,style:Hh,children:"✕"}),i.jsx("button",{type:"button",title:a("layout_editor.table_inplace.move_col_right"),disabled:!Q.ok,onClick:()=>L(j,1),"data-testid":`g7le-inplace-col-right-${j}`,style:Q.ok?_n:Ti,children:"→"})]},`colgut-${j}`)})()}),i.jsx("div",{"data-testid":"g7le-inplace-row-rail",style:{position:"absolute",top:n.top,left:he,width:bl,height:n.height,pointerEvents:"none"},children:fe!==null&&$e.has(fe)&&(()=>{const j=fe,ne=$e.get(j),pe=Si(f,j,-1),Q=Si(f,j,1),ae=ne.top-n.top;return i.jsxs("div",{"data-testid":`g7le-inplace-rowgutter-${j}`,onMouseEnter:()=>{A(),S(j)},onMouseLeave:N,style:{position:"absolute",top:ae,left:0,width:bl,height:ne.height,minHeight:48,display:"grid",gridTemplateColumns:"repeat(2, 22px)",gridAutoRows:"22px",gap:3,justifyContent:"center",alignContent:"center",pointerEvents:"auto"},children:[i.jsx("button",{type:"button",title:a("layout_editor.table_inplace.move_row_up"),disabled:!pe.ok,onClick:()=>H(j,-1),"data-testid":`g7le-inplace-row-up-${j}`,style:pe.ok?_n:Ti,children:"↑"}),i.jsx("button",{type:"button",title:a("layout_editor.table_inplace.add_row"),onClick:()=>O(j),"data-testid":`g7le-inplace-row-add-${j}`,style:_n,children:"+"}),i.jsx("button",{type:"button",title:a("layout_editor.table_inplace.remove_row"),disabled:f.rows.length<=1,onClick:()=>z(j),"data-testid":`g7le-inplace-row-remove-${j}`,style:Hh,children:"✕"}),i.jsx("button",{type:"button",title:a("layout_editor.table_inplace.move_row_down"),disabled:!Q.ok,onClick:()=>H(j,1),"data-testid":`g7le-inplace-row-down-${j}`,style:Q.ok?_n:Ti,children:"↓"})]},`rowgut-${j}`)})()}),i.jsxs("div",{"data-testid":"g7le-inplace-corner-add",style:{position:"absolute",top:n.top+n.height+ml,left:n.left,display:"flex",flexDirection:"column",gap:6,pointerEvents:"auto",minWidth:200,maxWidth:Math.max(240,n.width),background:"#fff",border:"1px solid #e2e8f0",borderRadius:8,padding:8,boxShadow:"0 2px 8px rgba(0,0,0,0.12)"},children:[i.jsxs("div",{style:{display:"flex",gap:6,flexWrap:"wrap"},children:[i.jsxs("button",{type:"button","data-testid":"g7le-inplace-add-row-bottom",onClick:()=>O(f.rows.length-1),style:Fh,children:["+ ",a("layout_editor.table_inplace.add_row")]}),i.jsxs("button",{type:"button","data-testid":"g7le-inplace-add-col-right",onClick:()=>P(f.colCount-1),style:Fh,children:["+ ",a("layout_editor.table_inplace.add_col")]})]}),!y&&i.jsx("div",{"data-testid":"g7le-inplace-select-hint",style:{fontSize:11,color:"#94a3b8",borderTop:"1px solid #f1f5f9",paddingTop:6},children:a("layout_editor.table_inplace.select_cell_hint")}),y&&i.jsxs("div",{style:{display:"flex",flexDirection:"column",gap:6,borderTop:"1px solid #f1f5f9",paddingTop:6},children:[se&&i.jsx("div",{"data-testid":"g7le-inplace-range-count",style:{fontSize:11,color:"#2563eb"},children:a("layout_editor.table_inplace.range_selected",{count:Z.length})}),i.jsxs("div",{style:{display:"flex",gap:6,flexWrap:"wrap",alignItems:"center"},children:[se?i.jsx("button",{type:"button","data-testid":"g7le-inplace-merge-range",onClick:oe,style:ar,children:a("layout_editor.table_inplace.merge_range")}):i.jsxs(i.Fragment,{children:[i.jsx("button",{type:"button","data-testid":"g7le-inplace-merge-right",disabled:!Y,onClick:()=>F("right"),style:Y?ar:vl,children:a("layout_editor.table_inplace.merge_right")}),i.jsx("button",{type:"button","data-testid":"g7le-inplace-merge-down",disabled:!K,onClick:()=>F("down"),style:K?ar:vl,children:a("layout_editor.table_inplace.merge_down")})]}),i.jsx("button",{type:"button","data-testid":"g7le-inplace-unmerge",disabled:!U,onClick:G,style:U?ar:vl,children:a("layout_editor.table_inplace.unmerge")})]}),i.jsxs("div",{"data-testid":"g7le-inplace-cell-color",style:{borderTop:"1px solid #f1f5f9",paddingTop:6,display:"flex",flexDirection:"column",gap:6},children:[i.jsxs("label",{style:{display:"flex",alignItems:"center",gap:6,fontSize:11,color:"#475569",cursor:"pointer"},children:[i.jsx("input",{type:"checkbox","data-testid":"g7le-inplace-border-all-toggle",checked:_,onChange:j=>w(j.target.checked)}),a("layout_editor.table_inplace.border_apply_all")]}),i.jsx(kh,{colorScheme:k,onChange:x,t:a}),ee&&i.jsx("div",{"data-testid":"g7le-inplace-cell-border",children:i.jsx(yh,{className:_?"":M,catalog:ee,t:a,onChange:be,colorScheme:k,onPresetToken:Me,colorStyle:_?"":Ye,onColorStyle:Oe})}),i.jsx("div",{"data-testid":"g7le-inplace-cell-fill",children:i.jsx(_h,{colorStyle:_?"":q,className:_?"":re,catalog:ge,t:a,colorScheme:k,onPresetToken:ve,onCustomColor:qe,onClear:We})})]}),i.jsx("div",{"data-testid":"g7le-inplace-cell-padding",style:{borderTop:"1px solid #f1f5f9",paddingTop:6},children:i.jsx(xh,{paddingStyle:_?"":ie,catalog:V,t:a,onChange:ce})})]})]})]})}const _n={width:22,height:22,lineHeight:"20px",padding:0,fontSize:13,border:"1px solid #cbd5e1",borderRadius:5,background:"#fff",color:"#475569",cursor:"pointer",boxShadow:"0 1px 3px rgba(0,0,0,0.18)"},Hh={..._n,border:"1px solid #fecaca",color:"#dc2626"},Ti={..._n,cursor:"not-allowed",opacity:.4,color:"#94a3b8",borderColor:"#e2e8f0"},ar={padding:"4px 10px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",color:"#475569",cursor:"pointer",boxShadow:"0 1px 2px rgba(0,0,0,0.12)"},vl={...ar,cursor:"not-allowed",opacity:.4},Fh={padding:"3px 8px",fontSize:11,border:"1px dashed #94a3b8",borderRadius:6,background:"#fff",color:"#475569",cursor:"pointer"};let Kh=!1;function pM(){Kh||(Pn("children",Rg),Pn("table",rN),Pn("array",Nh),Pn("array-group",KN),Pn("array-cell-tree",ZN),ja("table",uM),Kh=!0)}function fM(){if(typeof window>"u")return;const e=window.G7Core;if(!e)return;const t=e.layoutEditor,n=Array.isArray(t?.__queue)?t.__queue:[],o=Array.isArray(t?.__readyCallbacks)?t.__readyCallbacks:[];e.layoutEditor={registerWidget:Ze,registerNodeEditor:Pn,registerCanvasOverlay:ja,onReady:r=>{typeof r=="function"&&r()}};for(const r of n){const[s,...a]=r;try{s==="widget"?Ze(a[0],a[1]):s==="nodeEditor"?Pn(a[0],a[1]):s==="canvasOverlay"&&ja(a[0],a[1])}catch{}}for(const r of o)try{r()}catch{}}const gM=1280;_z(),pM(),fM();async function hM(e){if(typeof fetch!="function")return[];try{const t=await fetch(en(`/api/admin/templates/${encodeURIComponent(e)}/editor/permission-candidates`,"json"),{credentials:"same-origin",headers:Ne()});if(!t.ok)return[];const o=(await t.json().catch(()=>null))?.data?.permissions;if(!Array.isArray(o))return[];const r=[];for(const s of o)if(s&&typeof s=="object"){const a=s.key,l=s.name;typeof a=="string"&&a.length>0&&r.push({value:a,label:typeof l=="string"&&l?l:a})}return r}catch{return[]}}function yM({templateIdentifier:e}){FD({templateIdentifier:e}),KD(e);const{state:t,dispatch:n}=Qe(),{t:o}=tt(),r=bo(),s=Dp(),a=XD(),l=t.selectedRoute?.layoutName??null,d=RD(e,l),c=t.editMode==="extension",p=u.useMemo(()=>c?ZD(a):s,[c,a,s]),[f,b]=u.useState(null),[g,m]=u.useState(null),[h,v]=u.useState([]),[y,_]=u.useState("user"),[w,k]=u.useState(null),[x,$]=u.useState(null),[S,C]=u.useState([]);u.useEffect(()=>{let q=!1;return(async()=>{try{const re=await fetch(en(`/api/templates/${encodeURIComponent(e)}/components`,"json"),{credentials:"same-origin",headers:{Accept:"application/json"}});if(!re.ok)return;const ie=await re.json().catch(()=>null);!q&&ie&&typeof ie=="object"&&b(ie)}catch{}})(),()=>{q=!0}},[e]),u.useEffect(()=>{let q=!1;return(async()=>{const{spec:re,sampleGlobalSources:ie}=await ab({templateIdentifier:e,coreSeed:e8()});q||(m(re),v(ie),mM(e,re,ie))})(),()=>{q=!0}},[e]),u.useEffect(()=>{let q=!1;return(async()=>{if(typeof fetch=="function")try{const re=await fetch(`/api/admin/templates/${encodeURIComponent(e)}`,{credentials:"same-origin",headers:Ne()});if(!re.ok)return;const me=(await re.json().catch(()=>null))?.data,Ce=me?.type;q||((Ce==="admin"||Ce==="user")&&_(Ce),typeof me?.name=="string"&&me.name&&k(me.name),typeof me?.version=="string"&&me.version&&$(me.version))}catch{}})(),()=>{q=!0}},[e]),u.useEffect(()=>{let q=!1;return(async()=>{if(typeof fetch=="function")try{const re=await fetch("/api/admin/templates/?per_page=100&page=1",{credentials:"same-origin",headers:Ne()});if(!re.ok)return;const me=(await re.json().catch(()=>null))?.data?.data;if(!q&&Array.isArray(me)){const Ce=[];for(const $e of me){if(!$e||typeof $e!="object")continue;const U=$e.identifier;if(typeof U!="string"||!U||$e.is_pending===!0)continue;const fe=$e.type==="admin"?"admin":"user",xe=$e.name,he=$e.version;Ce.push({identifier:U,name:typeof xe=="string"?xe:U,version:typeof he=="string"?he:"",type:fe})}C(Ce)}}catch{}})(),()=>{q=!0}},[e]);const T=g?.nesting??null,E=t.selectedRoute?.path??null,A=t.editMode,N=t.selectedRoute?.layoutName??(c?a.document?.hostLayoutName??null:null),D=t.routeTree,B=u.useMemo(()=>{if(A!=="extension"||!N)return null;const q=xl(D,N);return q||`__base__/${N}`},[A,N,D]),M=c&&t.returnRoute===null&&!!N;u.useEffect(()=>{if(!M||!N)return;const q=xl(D,N);q&&n({type:"SET_RETURN_ROUTE",route:{path:q,layoutName:N}})},[M,N,D,n]),u.useEffect(()=>{const q=xD(A,E,B),re=wD(g?.states?.groups,q);n({type:"SET_AVAILABLE_STATES",states:re,activeStateId:kD(re)})},[g,A,E,B,n]);const[O,z]=u.useState([]);u.useEffect(()=>{let q=!1;return(async()=>{const re=await hM(e);q||z(re)})(),()=>{q=!0}},[e]),VD(),UD();const H=u.useCallback(q=>{const re=t.routeTree,ie=q.split("?")[0]?.split("#")[0]??q;return bM(re,ie)?"route_in_tree":"route_not_in_tree"},[t.routeTree]),P=u.useCallback(q=>{const re=q.split("?")[0]?.split("#")[0]??q,ie=_l(t.routeTree,re);ie&&(p.isDirty&&!(!(typeof window<"u")||window.confirm(o("layout_editor.chrome.dirty_guard.message")))||n({type:"SELECT_ROUTE",route:{path:ie.path,layoutName:ie.layoutName}}))},[t.routeTree,p.isDirty,n]),I=u.useRef([]),L=u.useCallback(q=>{I.current=q},[]),[F,Y]=u.useState(null),K=u.useCallback(()=>Y(null),[]),X=u.useRef(null),Z=u.useRef(null);u.useEffect(()=>{const q=Z.current,re=X.current;if(!q||!re||typeof ResizeObserver>"u")return;const ie=()=>{re.style.setProperty("--g7le-header-h",`${Math.round(q.getBoundingClientRect().height)}px`)};ie();const me=new ResizeObserver(ie);return me.observe(q),()=>me.disconnect()},[]);const se=u.useCallback(()=>{p.reload()},[p]),oe=u.useCallback(()=>{p.isDirty&&!(!(typeof window<"u")||window.confirm(o("layout_editor.chrome.dirty_guard.message")))||window.G7Core?.dispatch?.({handler:"openWindow",params:{path:`/admin/templates/${y}`,target:"_self"}})},[p.isDirty,o,y]),G=u.useCallback(q=>{if(!q||q===e||p.isDirty&&!(typeof window<"u"?window.confirm(o("layout_editor.chrome.dirty_guard.message")):!0))return;const re=ea(q);window.G7Core?.dispatch?.({handler:"openWindow",params:{path:re,target:"_self"}})},[e,p.isDirty,o]),ee=u.useCallback(()=>{const q=r.open({ariaLabel:o("layout_editor.custom_assets.title"),width:860,maxHeightRatio:.86,content:u.createElement(wE,{templateIdentifier:e,t:o,onClose:()=>r.close(q)})})},[r,e,o]),ge=typeof window<"u"&&(!!window.G7Config?.customAssetsDisabled||new URLSearchParams(window.location.search).get("custom")==="off"),V=u.useCallback(()=>{if(typeof window>"u"||p.isDirty&&!window.confirm(o("layout_editor.chrome.dirty_guard.message")))return;const q=new URL(window.location.href);if(ge){if(q.searchParams.delete("custom"),q.toString()===window.location.href){window.location.reload();return}}else q.searchParams.set("custom","off");window.G7Core?.dispatch?.({handler:"openWindow",params:{path:`${q.pathname}${q.search}${q.hash}`,target:"_self"}})},[p.isDirty,ge,o]),te=u.useCallback(()=>{const q=s7(e,t.selectedRoute?.path??null);window.G7Core?.dispatch?.({handler:"openWindow",params:{path:q}})},[e,t.selectedRoute]),be=u.useCallback(()=>{const q=t.selectedRoute?.layoutName??"";if(!q)return;const re=r.open({ariaLabel:o("layout_editor.attachment_manager.title"),width:720,maxHeightRatio:.82,content:u.createElement(Bl,{templateIdentifier:e,layoutName:q,t:o,onClose:()=>r.close(re)})})},[r,e,t.selectedRoute,o]),Ee=u.useCallback(()=>{const q=t.selectedRoute?.layoutName??"";if(!q)return;const re=Yy(s.document?.raw??{}),ie=r.open({ariaLabel:o("layout_editor.translation_manager.title"),width:720,maxHeightRatio:.82,content:u.createElement(ym,{templateIdentifier:e,layoutName:q,t:o,referencedKeys:re,onClose:()=>r.close(ie)})})},[r,e,t.selectedRoute,o,s.document]),De=u.useCallback((q,re)=>{const ie=window.G7Core;if(ie&&typeof ie.t=="function"){const me=ie.t(q,re);if(me&&me!==q&&!me.startsWith("$t:"))return me}return o(q,re)},[o]),Se=u.useCallback(()=>{if(!t.selectedRoute||t.editMode==="extension"||t.editMode==="iteration_item")return;const q=r.open({ariaLabel:o("layout_editor.page_settings.title",{name:t.selectedRoute.path??""}),width:1080,maxHeightRatio:.88,closeOnEscape:!1,content:u.createElement(m$,{templateIdentifier:e,spec:g,t:o,resolveLabel:De,permissionCandidates:O,onClose:()=>r.close(q)})})},[r,t.selectedRoute,t.editMode,o,De,e,g,O]),Ue=u.useCallback(async()=>{const q=await d.createPreview(s.document?.raw??null);if(q.kind!=="success"){const re=q.kind==="network_error"?q.message:o("layout_editor.preview_action.no_document");typeof window<"u"&&window.alert(re)}},[d,s.document,o]),Me=u.useCallback(()=>{const q=t.selectedRoute?.layoutName??"",re=t.selectedRoute?.path??"",ie=t.editMode==="extension"&&re.startsWith("__extension__/")?re.slice(14):null,me=ie?{kind:"extension",extensionId:ie}:q?{kind:"layout",layoutName:q}:null;if(!me)return;const Ce=r.open({ariaLabel:o("layout_editor.version_history.title"),width:640,maxHeightRatio:.82,content:u.createElement(nE,{templateIdentifier:e,target:me,t:o,onRestored:async $e=>{typeof $e=="number"&&(me.kind==="extension"?n({type:"SET_EXTENSION_VERSION",extensionId:me.extensionId,version:$e}):n({type:"SET_LAYOUT_VERSION",layoutName:me.layoutName,version:$e})),await p.reload()},onClose:()=>r.close(Ce)})})},[r,e,t.selectedRoute,t.editMode,o,p,n]),Oe=p.dirtyKeys.size>0||p.isDirty;u.useEffect(()=>{if(!Oe)return;const q=re=>{re.preventDefault(),re.returnValue=""};return window.addEventListener("beforeunload",q),()=>window.removeEventListener("beforeunload",q)},[Oe]);const Ye=u.useCallback(()=>{!p.isDirty||!(!(typeof window<"u")||window.confirm(o("layout_editor.chrome.reset_guard.message")))||p.reload()},[p,o]),ze=u.useCallback(async()=>{const q=await p.save({sessionAddedPaths:I.current});Y(q),q.kind==="success"&&typeof q.newContentVersion=="number"&&(q.savedExtensionId?n({type:"SET_EXTENSION_VERSION",extensionId:q.savedExtensionId,version:q.newContentVersion}):q.savedLayoutName&&n({type:"SET_LAYOUT_VERSION",layoutName:q.savedLayoutName,version:q.newContentVersion}))},[p,n]),Xe=u.useCallback(()=>{const q=r.open({ariaLabel:o("layout_editor.shortcuts.title"),width:560,maxHeightRatio:.85,content:u.createElement(DE,{t:o,onClose:()=>r.close(q)})})},[r,o]),ve=u.useMemo(()=>({save:()=>{ze()},exit:oe,editCode:te,reset:Ye,translations:Ee,addElement:()=>{t.selectedRoute&&n({type:"TOGGLE_PALETTE"})},help:Xe,preview:()=>{t.selectedRoute?.layoutName&&Ue()}}),[ze,oe,te,Ye,Ee,t.selectedRoute,n,Xe,Ue]),[qe,We]=u.useState(!1);u.useEffect(()=>{const q=window.setInterval(()=>{We(!!window.__g7LayoutEditorHasSelection)},200);return()=>window.clearInterval(q)},[]),lp({handlers:ve,hasSelection:qe});const ce=p;return t.routesError?i.jsx("div",{className:"g7le-chrome g7le-chrome--routes-error","data-testid":"g7le-chrome-routes-error",style:{position:"fixed",inset:0,display:"flex",background:"#f8fafc",color:"#0f172a",fontFamily:'-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Apple SD Gothic Neo", "Noto Sans KR", sans-serif',fontSize:13,lineHeight:1.5,zIndex:9e3},children:i.jsx(qa,{error:t.routesError})}):i.jsx("div",{className:"g7le-chrome","data-testid":"g7le-chrome",ref:X,style:{position:"static",minHeight:"100vh",minWidth:gM,background:"#f8fafc",color:"#0f172a",fontFamily:'-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Apple SD Gothic Neo", "Noto Sans KR", sans-serif',fontSize:13,lineHeight:1.5,zIndex:9e3},children:i.jsxs(yy,{value:ce,children:[c&&a.needsHostPicker&&a.document&&i.jsx(QD,{hostLayouts:a.document.hostLayouts,extensionLabel:null,onSelect:a.selectHost}),i.jsxs("div",{className:"g7le-chrome__header","data-testid":"g7le-chrome-header",ref:Z,style:{position:"sticky",top:0,zIndex:20,background:"#f8fafc"},children:[i.jsx(Sy,{onSave:()=>{ze()},isDirty:p.isDirty,onExit:oe,onEditCode:te,onManageImages:be,onManageTranslations:Ee,onPageSettings:Se,onReset:Ye,onShowShortcuts:Xe,onPreview:Ue,onShowVersions:Me,templateName:w,templateVersion:x,templateList:S,onSwitchTemplate:G,onToggleCustomAssets:V,customAssetsDisabled:ge,onManageCustomAssets:ee}),i.jsx(GE,{result:F,onDismiss:K,onLoadLatest:se}),i.jsxs("div",{className:"g7le-chrome__device-toolbar","data-testid":"g7le-chrome-device-toolbar",style:{display:"flex",justifyContent:"center",padding:"6px 12px",background:"#f1f5f9",borderBottom:"1px solid #e2e8f0"},children:[i.jsx(vD,{}),i.jsx(_D,{})]})]}),i.jsxs("div",{className:"g7le-chrome__body",style:{display:"grid",gridTemplateColumns:"auto 1fr",alignItems:"stretch"},children:[i.jsx(f7,{}),i.jsx(gD,{manifest:f,nesting:T,componentPalette:g?.componentPalette??null,spec:g,permissionCandidates:O,sampleGlobalSources:h,resolveRouteMatch:H,onNavigateToDestination:P,onSessionAddedPathsChange:L})]}),i.jsx(vy,{})]})})}function _l(e,t){for(const n of e){if(n.kind==="route"&&n.path===t)return n;if(n.children&&n.children.length>0){const o=_l(n.children,t);if(o)return o}}return null}function bM(e,t){return _l(e,t)!==null}function xl(e,t){for(const n of e){if(n.kind==="route"&&n.layoutName===t&&typeof n.path=="string")return n.path;if(n.children&&n.children.length>0){const o=xl(n.children,t);if(o)return o}}return null}function mM(e,t,n){const o=[];Ya({sources:n,isGuestOnly:!1,warn:s=>{const a=/extension '([^']+)' override of core key '([^']+)'/.exec(s);a&&o.push(`${a[1]}:${a[2]}`)}});const r=t?.sampleData?.byDataSourceId?Object.keys(t.sampleData.byDataSourceId).length:0;iy({templateIdentifier:e,mergedSources:[...n.map(s=>({kind:s.kind,id:s.id}))],blockCounts:{controls:t?.controls?Object.keys(t.controls).length:0,componentCapabilities:t?.componentCapabilities?Object.keys(t.componentCapabilities).length:0,actionRecipes:t?.actionRecipes?Object.keys(t.actionRecipes).length:0,conditionRecipes:t?.conditionRecipes?Object.keys(t.conditionRecipes).length:0,paletteGroups:t?.componentPalette?.groups?.length??0,stateGroups:t?.states?.groups?.length??0,sampleDataIds:r},sampleGlobalSourceCount:n.length,sampleGlobalConflicts:o,timestamp:Date.now()})}function Vh({templateIdentifier:e,initialLocale:t}){return u.useEffect(()=>()=>{Zh(),Rh(),ty(),ny(),oy(),ry(),sy(),ay(),ly()},[]),i.jsx(hy,{templateIdentifier:e,initialLocale:t,children:i.jsx(my,{children:i.jsx(yM,{templateIdentifier:e})})})}if(typeof window<"u"){const e=window.G7Core||(window.G7Core={});if(e.__LayoutEditorChrome=Vh,typeof e.__onChromeReady=="function")try{e.__onChromeReady()}catch{}}dn.LayoutEditorChrome=Vh,Object.defineProperty(dn,Symbol.toStringTag,{value:"Module"})})(this.G7LayoutEditor=this.G7LayoutEditor||{},ReactJSXRuntime,React,ReactDOM); + `},I2={onDragStart(e){let{active:t}=e;return"Picked up draggable item "+t.id+"."},onDragOver(e){let{active:t,over:n}=e;return n?"Draggable item "+t.id+" was moved over droppable area "+n.id+".":"Draggable item "+t.id+" is no longer over a droppable area."},onDragEnd(e){let{active:t,over:n}=e;return n?"Draggable item "+t.id+" was dropped over droppable area "+n.id:"Draggable item "+t.id+" was dropped."},onDragCancel(e){let{active:t}=e;return"Dragging was cancelled. Draggable item "+t.id+" was dropped."}};function A2(e){let{announcements:t=I2,container:n,hiddenTextDescribedById:o,screenReaderInstructions:r=E2}=e;const{announce:s,announcement:a}=S2(),l=li("DndLiveRegion"),[d,c]=u.useState(!1);if(u.useEffect(()=>{c(!0)},[]),C2(u.useMemo(()=>({onDragStart(f){let{active:b}=f;s(t.onDragStart({active:b}))},onDragMove(f){let{active:b,over:g}=f;t.onDragMove&&s(t.onDragMove({active:b,over:g}))},onDragOver(f){let{active:b,over:g}=f;s(t.onDragOver({active:b,over:g}))},onDragEnd(f){let{active:b,over:g}=f;s(t.onDragEnd({active:b,over:g}))},onDragCancel(f){let{active:b,over:g}=f;s(t.onDragCancel({active:b,over:g}))}}),[s,t])),!d)return null;const p=u.createElement(u.Fragment,null,u.createElement(w2,{id:o,value:r.draggable}),u.createElement(k2,{id:l,announcement:a}));return n?En.createPortal(p,n):p}var st;(function(e){e.DragStart="dragStart",e.DragMove="dragMove",e.DragEnd="dragEnd",e.DragCancel="dragCancel",e.DragOver="dragOver",e.RegisterDroppable="registerDroppable",e.SetDroppableDisabled="setDroppableDisabled",e.UnregisterDroppable="unregisterDroppable"})(st||(st={}));function ui(){}function T2(e,t){return u.useMemo(()=>({sensor:e,options:t??{}}),[e,t])}function D2(){for(var e=arguments.length,t=new Array(e),n=0;n[...t].filter(o=>o!=null),[...t])}const Ft=Object.freeze({x:0,y:0});function z2(e,t){return Math.sqrt(Math.pow(e.x-t.x,2)+Math.pow(e.y-t.y,2))}function N2(e,t){const n=ci(e);if(!n)return"0 0";const o={x:(n.x-t.left)/t.width*100,y:(n.y-t.top)/t.height*100};return o.x+"% "+o.y+"%"}function M2(e,t){let{data:{value:n}}=e,{data:{value:o}}=t;return n-o}function L2(e,t){let{data:{value:n}}=e,{data:{value:o}}=t;return o-n}function P2(e){let{left:t,top:n,height:o,width:r}=e;return[{x:t,y:n},{x:t+r,y:n},{x:t,y:n+o},{x:t+r,y:n+o}]}function O2(e,t){if(!e||e.length===0)return null;const[n]=e;return n[t]}function B2(e,t){const n=Math.max(t.top,e.top),o=Math.max(t.left,e.left),r=Math.min(t.left+t.width,e.left+e.width),s=Math.min(t.top+t.height,e.top+e.height),a=r-o,l=s-n;if(o{let{collisionRect:t,droppableRects:n,droppableContainers:o}=e;const r=[];for(const s of o){const{id:a}=s,l=n.get(a);if(l){const d=B2(l,t);d>0&&r.push({id:a,data:{droppableContainer:s,value:d}})}}return r.sort(L2)};function j2(e,t){const{top:n,left:o,bottom:r,right:s}=t;return n<=e.y&&e.y<=r&&o<=e.x&&e.x<=s}const H2=e=>{let{droppableContainers:t,droppableRects:n,pointerCoordinates:o}=e;if(!o)return[];const r=[];for(const s of t){const{id:a}=s,l=n.get(a);if(l&&j2(o,l)){const c=P2(l).reduce((f,b)=>f+z2(o,b),0),p=Number((c/4).toFixed(4));r.push({id:a,data:{droppableContainer:s,value:p}})}}return r.sort(M2)};function F2(e,t,n){return{...e,scaleX:t&&n?t.width/n.width:1,scaleY:t&&n?t.height/n.height:1}}function of(e,t){return e&&t?{x:e.left-t.left,y:e.top-t.top}:Ft}function K2(e){return function(n){for(var o=arguments.length,r=new Array(o>1?o-1:0),s=1;s({...a,top:a.top+e*l.y,bottom:a.bottom+e*l.y,left:a.left+e*l.x,right:a.right+e*l.x}),{...n})}}const V2=K2(1);function rf(e){if(e.startsWith("matrix3d(")){const t=e.slice(9,-1).split(/, /);return{x:+t[12],y:+t[13],scaleX:+t[0],scaleY:+t[5]}}else if(e.startsWith("matrix(")){const t=e.slice(7,-1).split(/, /);return{x:+t[4],y:+t[5],scaleX:+t[0],scaleY:+t[3]}}return null}function U2(e,t,n){const o=rf(t);if(!o)return e;const{scaleX:r,scaleY:s,x:a,y:l}=o,d=e.left-a-(1-r)*parseFloat(n),c=e.top-l-(1-s)*parseFloat(n.slice(n.indexOf(" ")+1)),p=r?e.width/r:e.width,f=s?e.height/s:e.height;return{width:p,height:f,top:c,right:d+p,bottom:c+f,left:d}}const q2={ignoreTransform:!1};function Yo(e,t){t===void 0&&(t=q2);let n=e.getBoundingClientRect();if(t.ignoreTransform){const{transform:c,transformOrigin:p}=xt(e).getComputedStyle(e);c&&(n=U2(n,c,p))}const{top:o,left:r,width:s,height:a,bottom:l,right:d}=n;return{top:o,left:r,width:s,height:a,bottom:l,right:d}}function sf(e){return Yo(e,{ignoreTransform:!0})}function G2(e){const t=e.innerWidth,n=e.innerHeight;return{top:0,left:0,right:t,bottom:n,width:t,height:n}}function Y2(e,t){return t===void 0&&(t=xt(e).getComputedStyle(e)),t.position==="fixed"}function X2(e,t){t===void 0&&(t=xt(e).getComputedStyle(e));const n=/(auto|scroll|overlay)/;return["overflow","overflowX","overflowY"].some(r=>{const s=t[r];return typeof s=="string"?n.test(s):!1})}function ma(e,t){const n=[];function o(r){if(t!=null&&n.length>=t||!r)return n;if(ha(r)&&r.scrollingElement!=null&&!n.includes(r.scrollingElement))return n.push(r.scrollingElement),n;if(!Vo(r)||Rp(r)||n.includes(r))return n;const s=xt(e).getComputedStyle(r);return r!==e&&X2(r,s)&&n.push(r),Y2(r,s)?n:o(r.parentNode)}return e?o(e):n}function af(e){const[t]=ma(e,1);return t??null}function va(e){return!ri||!e?null:go(e)?e:ga(e)?ha(e)||e===ho(e).scrollingElement?window:Vo(e)?e:null:null}function lf(e){return go(e)?e.scrollX:e.scrollLeft}function df(e){return go(e)?e.scrollY:e.scrollTop}function _a(e){return{x:lf(e),y:df(e)}}var lt;(function(e){e[e.Forward=1]="Forward",e[e.Backward=-1]="Backward"})(lt||(lt={}));function cf(e){return!ri||!e?!1:e===document.scrollingElement}function uf(e){const t={x:0,y:0},n=cf(e)?{height:window.innerHeight,width:window.innerWidth}:{height:e.clientHeight,width:e.clientWidth},o={x:e.scrollWidth-n.width,y:e.scrollHeight-n.height},r=e.scrollTop<=t.y,s=e.scrollLeft<=t.x,a=e.scrollTop>=o.y,l=e.scrollLeft>=o.x;return{isTop:r,isLeft:s,isBottom:a,isRight:l,maxScroll:o,minScroll:t}}const J2={x:.2,y:.2};function Z2(e,t,n,o,r){let{top:s,left:a,right:l,bottom:d}=n;o===void 0&&(o=10),r===void 0&&(r=J2);const{isTop:c,isBottom:p,isLeft:f,isRight:b}=uf(e),g={x:0,y:0},m={x:0,y:0},h={height:t.height*r.y,width:t.width*r.x};return!c&&s<=t.top+h.height?(g.y=lt.Backward,m.y=o*Math.abs((t.top+h.height-s)/h.height)):!p&&d>=t.bottom-h.height&&(g.y=lt.Forward,m.y=o*Math.abs((t.bottom-h.height-d)/h.height)),!b&&l>=t.right-h.width?(g.x=lt.Forward,m.x=o*Math.abs((t.right-h.width-l)/h.width)):!f&&a<=t.left+h.width&&(g.x=lt.Backward,m.x=o*Math.abs((t.left+h.width-a)/h.width)),{direction:g,speed:m}}function Q2(e){if(e===document.scrollingElement){const{innerWidth:s,innerHeight:a}=window;return{top:0,left:0,right:s,bottom:a,width:s,height:a}}const{top:t,left:n,right:o,bottom:r}=e.getBoundingClientRect();return{top:t,left:n,right:o,bottom:r,width:e.clientWidth,height:e.clientHeight}}function pf(e){return e.reduce((t,n)=>yo(t,_a(n)),Ft)}function R2(e){return e.reduce((t,n)=>t+lf(n),0)}function eI(e){return e.reduce((t,n)=>t+df(n),0)}function ff(e,t){if(t===void 0&&(t=Yo),!e)return;const{top:n,left:o,bottom:r,right:s}=t(e);af(e)&&(r<=0||s<=0||n>=window.innerHeight||o>=window.innerWidth)&&e.scrollIntoView({block:"center",inline:"center"})}const tI=[["x",["left","right"],R2],["y",["top","bottom"],eI]];class xa{constructor(t,n){this.rect=void 0,this.width=void 0,this.height=void 0,this.top=void 0,this.bottom=void 0,this.right=void 0,this.left=void 0;const o=ma(n),r=pf(o);this.rect={...t},this.width=t.width,this.height=t.height;for(const[s,a,l]of tI)for(const d of a)Object.defineProperty(this,d,{get:()=>{const c=l(o),p=r[s]-c;return this.rect[d]+p},enumerable:!0});Object.defineProperty(this,"rect",{enumerable:!1})}}class Xo{constructor(t){this.target=void 0,this.listeners=[],this.removeAll=()=>{this.listeners.forEach(n=>{var o;return(o=this.target)==null?void 0:o.removeEventListener(...n)})},this.target=t}add(t,n,o){var r;(r=this.target)==null||r.addEventListener(t,n,o),this.listeners.push([t,n,o])}}function nI(e){const{EventTarget:t}=xt(e);return e instanceof t?e:ho(e)}function wa(e,t){const n=Math.abs(e.x),o=Math.abs(e.y);return typeof t=="number"?Math.sqrt(n**2+o**2)>t:"x"in t&&"y"in t?n>t.x&&o>t.y:"x"in t?n>t.x:"y"in t?o>t.y:!1}var At;(function(e){e.Click="click",e.DragStart="dragstart",e.Keydown="keydown",e.ContextMenu="contextmenu",e.Resize="resize",e.SelectionChange="selectionchange",e.VisibilityChange="visibilitychange"})(At||(At={}));function gf(e){e.preventDefault()}function oI(e){e.stopPropagation()}var Fe;(function(e){e.Space="Space",e.Down="ArrowDown",e.Right="ArrowRight",e.Left="ArrowLeft",e.Up="ArrowUp",e.Esc="Escape",e.Enter="Enter",e.Tab="Tab"})(Fe||(Fe={}));const hf={start:[Fe.Space,Fe.Enter],cancel:[Fe.Esc],end:[Fe.Space,Fe.Enter,Fe.Tab]},rI=(e,t)=>{let{currentCoordinates:n}=t;switch(e.code){case Fe.Right:return{...n,x:n.x+25};case Fe.Left:return{...n,x:n.x-25};case Fe.Down:return{...n,y:n.y+25};case Fe.Up:return{...n,y:n.y-25}}};class yf{constructor(t){this.props=void 0,this.autoScrollEnabled=!1,this.referenceCoordinates=void 0,this.listeners=void 0,this.windowListeners=void 0,this.props=t;const{event:{target:n}}=t;this.props=t,this.listeners=new Xo(ho(n)),this.windowListeners=new Xo(xt(n)),this.handleKeyDown=this.handleKeyDown.bind(this),this.handleCancel=this.handleCancel.bind(this),this.attach()}attach(){this.handleStart(),this.windowListeners.add(At.Resize,this.handleCancel),this.windowListeners.add(At.VisibilityChange,this.handleCancel),setTimeout(()=>this.listeners.add(At.Keydown,this.handleKeyDown))}handleStart(){const{activeNode:t,onStart:n}=this.props,o=t.node.current;o&&ff(o),n(Ft)}handleKeyDown(t){if(ba(t)){const{active:n,context:o,options:r}=this.props,{keyboardCodes:s=hf,coordinateGetter:a=rI,scrollBehavior:l="smooth"}=r,{code:d}=t;if(s.end.includes(d)){this.handleEnd(t);return}if(s.cancel.includes(d)){this.handleCancel(t);return}const{collisionRect:c}=o.current,p=c?{x:c.left,y:c.top}:Ft;this.referenceCoordinates||(this.referenceCoordinates=p);const f=a(t,{active:n,context:o.current,currentCoordinates:p});if(f){const b=di(f,p),g={x:0,y:0},{scrollableAncestors:m}=o.current;for(const h of m){const v=t.code,{isTop:y,isRight:_,isLeft:w,isBottom:k,maxScroll:x,minScroll:$}=uf(h),C=Q2(h),S={x:Math.min(v===Fe.Right?C.right-C.width/2:C.right,Math.max(v===Fe.Right?C.left:C.left+C.width/2,f.x)),y:Math.min(v===Fe.Down?C.bottom-C.height/2:C.bottom,Math.max(v===Fe.Down?C.top:C.top+C.height/2,f.y))},D=v===Fe.Right&&!_||v===Fe.Left&&!w,E=v===Fe.Down&&!k||v===Fe.Up&&!y;if(D&&S.x!==f.x){const I=h.scrollLeft+b.x,N=v===Fe.Right&&I<=x.x||v===Fe.Left&&I>=$.x;if(N&&!b.y){h.scrollTo({left:I,behavior:l});return}N?g.x=h.scrollLeft-I:g.x=v===Fe.Right?h.scrollLeft-x.x:h.scrollLeft-$.x,g.x&&h.scrollBy({left:-g.x,behavior:l});break}else if(E&&S.y!==f.y){const I=h.scrollTop+b.y,N=v===Fe.Down&&I<=x.y||v===Fe.Up&&I>=$.y;if(N&&!b.x){h.scrollTo({top:I,behavior:l});return}N?g.y=h.scrollTop-I:g.y=v===Fe.Down?h.scrollTop-x.y:h.scrollTop-$.y,g.y&&h.scrollBy({top:-g.y,behavior:l});break}}this.handleMove(t,yo(di(f,this.referenceCoordinates),g))}}}handleMove(t,n){const{onMove:o}=this.props;t.preventDefault(),o(n)}handleEnd(t){const{onEnd:n}=this.props;t.preventDefault(),this.detach(),n()}handleCancel(t){const{onCancel:n}=this.props;t.preventDefault(),this.detach(),n()}detach(){this.listeners.removeAll(),this.windowListeners.removeAll()}}yf.activators=[{eventName:"onKeyDown",handler:(e,t,n)=>{let{keyboardCodes:o=hf,onActivation:r}=t,{active:s}=n;const{code:a}=e.nativeEvent;if(o.start.includes(a)){const l=s.activatorNode.current;return l&&e.target!==l?!1:(e.preventDefault(),r?.({event:e.nativeEvent}),!0)}return!1}}];function bf(e){return!!(e&&"distance"in e)}function mf(e){return!!(e&&"delay"in e)}class ka{constructor(t,n,o){var r;o===void 0&&(o=nI(t.event.target)),this.props=void 0,this.events=void 0,this.autoScrollEnabled=!0,this.document=void 0,this.activated=!1,this.initialCoordinates=void 0,this.timeoutId=null,this.listeners=void 0,this.documentListeners=void 0,this.windowListeners=void 0,this.props=t,this.events=n;const{event:s}=t,{target:a}=s;this.props=t,this.events=n,this.document=ho(a),this.documentListeners=new Xo(this.document),this.listeners=new Xo(o),this.windowListeners=new Xo(xt(a)),this.initialCoordinates=(r=ci(s))!=null?r:Ft,this.handleStart=this.handleStart.bind(this),this.handleMove=this.handleMove.bind(this),this.handleEnd=this.handleEnd.bind(this),this.handleCancel=this.handleCancel.bind(this),this.handleKeydown=this.handleKeydown.bind(this),this.removeTextSelection=this.removeTextSelection.bind(this),this.attach()}attach(){const{events:t,props:{options:{activationConstraint:n,bypassActivationConstraint:o}}}=this;if(this.listeners.add(t.move.name,this.handleMove,{passive:!1}),this.listeners.add(t.end.name,this.handleEnd),t.cancel&&this.listeners.add(t.cancel.name,this.handleCancel),this.windowListeners.add(At.Resize,this.handleCancel),this.windowListeners.add(At.DragStart,gf),this.windowListeners.add(At.VisibilityChange,this.handleCancel),this.windowListeners.add(At.ContextMenu,gf),this.documentListeners.add(At.Keydown,this.handleKeydown),n){if(o!=null&&o({event:this.props.event,activeNode:this.props.activeNode,options:this.props.options}))return this.handleStart();if(mf(n)){this.timeoutId=setTimeout(this.handleStart,n.delay),this.handlePending(n);return}if(bf(n)){this.handlePending(n);return}}this.handleStart()}detach(){this.listeners.removeAll(),this.windowListeners.removeAll(),setTimeout(this.documentListeners.removeAll,50),this.timeoutId!==null&&(clearTimeout(this.timeoutId),this.timeoutId=null)}handlePending(t,n){const{active:o,onPending:r}=this.props;r(o,t,this.initialCoordinates,n)}handleStart(){const{initialCoordinates:t}=this,{onStart:n}=this.props;t&&(this.activated=!0,this.documentListeners.add(At.Click,oI,{capture:!0}),this.removeTextSelection(),this.documentListeners.add(At.SelectionChange,this.removeTextSelection),n(t))}handleMove(t){var n;const{activated:o,initialCoordinates:r,props:s}=this,{onMove:a,options:{activationConstraint:l}}=s;if(!r)return;const d=(n=ci(t))!=null?n:Ft,c=di(r,d);if(!o&&l){if(bf(l)){if(l.tolerance!=null&&wa(c,l.tolerance))return this.handleCancel();if(wa(c,l.distance))return this.handleStart()}if(mf(l)&&wa(c,l.tolerance))return this.handleCancel();this.handlePending(l,c);return}t.cancelable&&t.preventDefault(),a(d)}handleEnd(){const{onAbort:t,onEnd:n}=this.props;this.detach(),this.activated||t(this.props.active),n()}handleCancel(){const{onAbort:t,onCancel:n}=this.props;this.detach(),this.activated||t(this.props.active),n()}handleKeydown(t){t.code===Fe.Esc&&this.handleCancel()}removeTextSelection(){var t;(t=this.document.getSelection())==null||t.removeAllRanges()}}const iI={cancel:{name:"pointercancel"},move:{name:"pointermove"},end:{name:"pointerup"}};class Sa extends ka{constructor(t){const{event:n}=t,o=ho(n.target);super(t,iI,o)}}Sa.activators=[{eventName:"onPointerDown",handler:(e,t)=>{let{nativeEvent:n}=e,{onActivation:o}=t;return!n.isPrimary||n.button!==0?!1:(o?.({event:n}),!0)}}];const sI={move:{name:"mousemove"},end:{name:"mouseup"}};var Ca;(function(e){e[e.RightClick=2]="RightClick"})(Ca||(Ca={}));class aI extends ka{constructor(t){super(t,sI,ho(t.event.target))}}aI.activators=[{eventName:"onMouseDown",handler:(e,t)=>{let{nativeEvent:n}=e,{onActivation:o}=t;return n.button===Ca.RightClick?!1:(o?.({event:n}),!0)}}];const $a={cancel:{name:"touchcancel"},move:{name:"touchmove"},end:{name:"touchend"}};class lI extends ka{constructor(t){super(t,$a)}static setup(){return window.addEventListener($a.move.name,t,{capture:!1,passive:!1}),function(){window.removeEventListener($a.move.name,t)};function t(){}}}lI.activators=[{eventName:"onTouchStart",handler:(e,t)=>{let{nativeEvent:n}=e,{onActivation:o}=t;const{touches:r}=n;return r.length>1?!1:(o?.({event:n}),!0)}}];var Jo;(function(e){e[e.Pointer=0]="Pointer",e[e.DraggableRect=1]="DraggableRect"})(Jo||(Jo={}));var pi;(function(e){e[e.TreeOrder=0]="TreeOrder",e[e.ReversedTreeOrder=1]="ReversedTreeOrder"})(pi||(pi={}));function dI(e){let{acceleration:t,activator:n=Jo.Pointer,canScroll:o,draggingRect:r,enabled:s,interval:a=5,order:l=pi.TreeOrder,pointerCoordinates:d,scrollableAncestors:c,scrollableAncestorRects:p,delta:f,threshold:b}=e;const g=uI({delta:f,disabled:!s}),[m,h]=b2(),v=u.useRef({x:0,y:0}),y=u.useRef({x:0,y:0}),_=u.useMemo(()=>{switch(n){case Jo.Pointer:return d?{top:d.y,bottom:d.y,left:d.x,right:d.x}:null;case Jo.DraggableRect:return r}},[n,r,d]),w=u.useRef(null),k=u.useCallback(()=>{const $=w.current;if(!$)return;const C=v.current.x*y.current.x,S=v.current.y*y.current.y;$.scrollBy(C,S)},[]),x=u.useMemo(()=>l===pi.TreeOrder?[...c].reverse():c,[l,c]);u.useEffect(()=>{if(!s||!c.length||!_){h();return}for(const $ of x){if(o?.($)===!1)continue;const C=c.indexOf($),S=p[C];if(!S)continue;const{direction:D,speed:E}=Z2($,S,_,t,b);for(const I of["x","y"])g[I][D[I]]||(E[I]=0,D[I]=0);if(E.x>0||E.y>0){h(),w.current=$,m(k,a),v.current=E,y.current=D;return}}v.current={x:0,y:0},y.current={x:0,y:0},h()},[t,k,o,h,s,a,JSON.stringify(_),JSON.stringify(g),m,c,x,p,JSON.stringify(b)])}const cI={x:{[lt.Backward]:!1,[lt.Forward]:!1},y:{[lt.Backward]:!1,[lt.Forward]:!1}};function uI(e){let{delta:t,disabled:n}=e;const o=ai(t);return qo(r=>{if(n||!o||!r)return cI;const s={x:Math.sign(t.x-o.x),y:Math.sign(t.y-o.y)};return{x:{[lt.Backward]:r.x[lt.Backward]||s.x===-1,[lt.Forward]:r.x[lt.Forward]||s.x===1},y:{[lt.Backward]:r.y[lt.Backward]||s.y===-1,[lt.Forward]:r.y[lt.Forward]||s.y===1}}},[n,t,o])}function pI(e,t){const n=t!=null?e.get(t):void 0,o=n?n.node.current:null;return qo(r=>{var s;return t==null?null:(s=o??r)!=null?s:null},[o,t])}function fI(e,t){return u.useMemo(()=>e.reduce((n,o)=>{const{sensor:r}=o,s=r.activators.map(a=>({eventName:a.eventName,handler:t(a.handler,o)}));return[...n,...s]},[]),[e,t])}var Zo;(function(e){e[e.Always=0]="Always",e[e.BeforeDragging=1]="BeforeDragging",e[e.WhileDragging=2]="WhileDragging"})(Zo||(Zo={}));var Ea;(function(e){e.Optimized="optimized"})(Ea||(Ea={}));const vf=new Map;function gI(e,t){let{dragging:n,dependencies:o,config:r}=t;const[s,a]=u.useState(null),{frequency:l,measure:d,strategy:c}=r,p=u.useRef(e),f=v(),b=Uo(f),g=u.useCallback(function(y){y===void 0&&(y=[]),!b.current&&a(_=>_===null?y:_.concat(y.filter(w=>!_.includes(w))))},[b]),m=u.useRef(null),h=qo(y=>{if(f&&!n)return vf;if(!y||y===vf||p.current!==e||s!=null){const _=new Map;for(let w of e){if(!w)continue;if(s&&s.length>0&&!s.includes(w.id)&&w.rect.current){_.set(w.id,w.rect.current);continue}const k=w.node.current,x=k?new xa(d(k),k):null;w.rect.current=x,x&&_.set(w.id,x)}return _}return y},[e,s,n,f,d]);return u.useEffect(()=>{p.current=e},[e]),u.useEffect(()=>{f||g()},[n,f]),u.useEffect(()=>{s&&s.length>0&&a(null)},[JSON.stringify(s)]),u.useEffect(()=>{f||typeof l!="number"||m.current!==null||(m.current=setTimeout(()=>{g(),m.current=null},l))},[l,f,g,...o]),{droppableRects:h,measureDroppableContainers:g,measuringScheduled:s!=null};function v(){switch(c){case Zo.Always:return!1;case Zo.BeforeDragging:return n;default:return!n}}}function Ia(e,t){return qo(n=>e?n||(typeof t=="function"?t(e):e):null,[t,e])}function hI(e,t){return Ia(e,t)}function yI(e){let{callback:t,disabled:n}=e;const o=ii(t),r=u.useMemo(()=>{if(n||typeof window>"u"||typeof window.MutationObserver>"u")return;const{MutationObserver:s}=window;return new s(o)},[o,n]);return u.useEffect(()=>()=>r?.disconnect(),[r]),r}function fi(e){let{callback:t,disabled:n}=e;const o=ii(t),r=u.useMemo(()=>{if(n||typeof window>"u"||typeof window.ResizeObserver>"u")return;const{ResizeObserver:s}=window;return new s(o)},[n]);return u.useEffect(()=>()=>r?.disconnect(),[r]),r}function bI(e){return new xa(Yo(e),e)}function _f(e,t,n){t===void 0&&(t=bI);const[o,r]=u.useState(null);function s(){r(d=>{if(!e)return null;if(e.isConnected===!1){var c;return(c=d??n)!=null?c:null}const p=t(e);return JSON.stringify(d)===JSON.stringify(p)?d:p})}const a=yI({callback(d){if(e)for(const c of d){const{type:p,target:f}=c;if(p==="childList"&&f instanceof HTMLElement&&f.contains(e)){s();break}}}}),l=fi({callback:s});return ln(()=>{s(),e?(l?.observe(e),a?.observe(document.body,{childList:!0,subtree:!0})):(l?.disconnect(),a?.disconnect())},[e]),o}function mI(e){const t=Ia(e);return of(e,t)}const xf=[];function vI(e){const t=u.useRef(e),n=qo(o=>e?o&&o!==xf&&e&&t.current&&e.parentNode===t.current.parentNode?o:ma(e):xf,[e]);return u.useEffect(()=>{t.current=e},[e]),n}function _I(e){const[t,n]=u.useState(null),o=u.useRef(e),r=u.useCallback(s=>{const a=va(s.target);a&&n(l=>l?(l.set(a,_a(a)),new Map(l)):null)},[]);return u.useEffect(()=>{const s=o.current;if(e!==s){a(s);const l=e.map(d=>{const c=va(d);return c?(c.addEventListener("scroll",r,{passive:!0}),[c,_a(c)]):null}).filter(d=>d!=null);n(l.length?new Map(l):null),o.current=e}return()=>{a(e),a(s)};function a(l){l.forEach(d=>{const c=va(d);c?.removeEventListener("scroll",r)})}},[r,e]),u.useMemo(()=>e.length?t?Array.from(t.values()).reduce((s,a)=>yo(s,a),Ft):pf(e):Ft,[e,t])}function wf(e,t){t===void 0&&(t=[]);const n=u.useRef(null);return u.useEffect(()=>{n.current=null},t),u.useEffect(()=>{const o=e!==Ft;o&&!n.current&&(n.current=e),!o&&n.current&&(n.current=null)},[e]),n.current?di(e,n.current):Ft}function xI(e){u.useEffect(()=>{if(!ri)return;const t=e.map(n=>{let{sensor:o}=n;return o.setup==null?void 0:o.setup()});return()=>{for(const n of t)n?.()}},e.map(t=>{let{sensor:n}=t;return n}))}function wI(e,t){return u.useMemo(()=>e.reduce((n,o)=>{let{eventName:r,handler:s}=o;return n[r]=a=>{s(a,t)},n},{}),[e,t])}function kf(e){return u.useMemo(()=>e?G2(e):null,[e])}const Sf=[];function kI(e,t){t===void 0&&(t=Yo);const[n]=e,o=kf(n?xt(n):null),[r,s]=u.useState(Sf);function a(){s(()=>e.length?e.map(d=>cf(d)?o:new xa(t(d),d)):Sf)}const l=fi({callback:a});return ln(()=>{l?.disconnect(),a(),e.forEach(d=>l?.observe(d))},[e]),r}function Cf(e){if(!e)return null;if(e.children.length>1)return e;const t=e.children[0];return Vo(t)?t:e}function SI(e){let{measure:t}=e;const[n,o]=u.useState(null),r=u.useCallback(c=>{for(const{target:p}of c)if(Vo(p)){o(f=>{const b=t(p);return f?{...f,width:b.width,height:b.height}:b});break}},[t]),s=fi({callback:r}),a=u.useCallback(c=>{const p=Cf(c);s?.disconnect(),p&&s?.observe(p),o(p?t(p):null)},[t,s]),[l,d]=si(a);return u.useMemo(()=>({nodeRef:l,rect:n,setRef:d}),[n,l,d])}const CI=[{sensor:Sa,options:{}},{sensor:yf,options:{}}],$I={current:{}},gi={draggable:{measure:sf},droppable:{measure:sf,strategy:Zo.WhileDragging,frequency:Ea.Optimized},dragOverlay:{measure:Yo}};class Qo extends Map{get(t){var n;return t!=null&&(n=super.get(t))!=null?n:void 0}toArray(){return Array.from(this.values())}getEnabled(){return this.toArray().filter(t=>{let{disabled:n}=t;return!n})}getNodeFor(t){var n,o;return(n=(o=this.get(t))==null?void 0:o.node.current)!=null?n:void 0}}const EI={activatorEvent:null,active:null,activeNode:null,activeNodeRect:null,collisions:null,containerNodeRect:null,draggableNodes:new Map,droppableRects:new Map,droppableContainers:new Qo,over:null,dragOverlay:{nodeRef:{current:null},rect:null,setRef:ui},scrollableAncestors:[],scrollableAncestorRects:[],measuringConfiguration:gi,measureDroppableContainers:ui,windowRect:null,measuringScheduled:!1},$f={activatorEvent:null,activators:[],active:null,activeNodeRect:null,ariaDescribedById:{draggable:""},dispatch:ui,draggableNodes:new Map,over:null,measureDroppableContainers:ui},Ro=u.createContext($f),Ef=u.createContext(EI);function II(){return{draggable:{active:null,initialCoordinates:{x:0,y:0},nodes:new Map,translate:{x:0,y:0}},droppable:{containers:new Qo}}}function AI(e,t){switch(t.type){case st.DragStart:return{...e,draggable:{...e.draggable,initialCoordinates:t.initialCoordinates,active:t.active}};case st.DragMove:return e.draggable.active==null?e:{...e,draggable:{...e.draggable,translate:{x:t.coordinates.x-e.draggable.initialCoordinates.x,y:t.coordinates.y-e.draggable.initialCoordinates.y}}};case st.DragEnd:case st.DragCancel:return{...e,draggable:{...e.draggable,active:null,initialCoordinates:{x:0,y:0},translate:{x:0,y:0}}};case st.RegisterDroppable:{const{element:n}=t,{id:o}=n,r=new Qo(e.droppable.containers);return r.set(o,n),{...e,droppable:{...e.droppable,containers:r}}}case st.SetDroppableDisabled:{const{id:n,key:o,disabled:r}=t,s=e.droppable.containers.get(n);if(!s||o!==s.key)return e;const a=new Qo(e.droppable.containers);return a.set(n,{...s,disabled:r}),{...e,droppable:{...e.droppable,containers:a}}}case st.UnregisterDroppable:{const{id:n,key:o}=t,r=e.droppable.containers.get(n);if(!r||o!==r.key)return e;const s=new Qo(e.droppable.containers);return s.delete(n),{...e,droppable:{...e.droppable,containers:s}}}default:return e}}function TI(e){let{disabled:t}=e;const{active:n,activatorEvent:o,draggableNodes:r}=u.useContext(Ro),s=ai(o),a=ai(n?.id);return u.useEffect(()=>{if(!t&&!o&&s&&a!=null){if(!ba(s)||document.activeElement===s.target)return;const l=r.get(a);if(!l)return;const{activatorNode:d,node:c}=l;if(!d.current&&!c.current)return;requestAnimationFrame(()=>{for(const p of[d.current,c.current]){if(!p)continue;const f=_2(p);if(f){f.focus();break}}})}},[o,t,r,a,s]),null}function If(e,t){let{transform:n,...o}=t;return e!=null&&e.length?e.reduce((r,s)=>s({transform:r,...o}),n):n}function DI(e){return u.useMemo(()=>({draggable:{...gi.draggable,...e?.draggable},droppable:{...gi.droppable,...e?.droppable},dragOverlay:{...gi.dragOverlay,...e?.dragOverlay}}),[e?.draggable,e?.droppable,e?.dragOverlay])}function zI(e){let{activeNode:t,measure:n,initialRect:o,config:r=!0}=e;const s=u.useRef(!1),{x:a,y:l}=typeof r=="boolean"?{x:r,y:r}:r;ln(()=>{if(!a&&!l||!t){s.current=!1;return}if(s.current||!o)return;const c=t?.node.current;if(!c||c.isConnected===!1)return;const p=n(c),f=of(p,o);if(a||(f.x=0),l||(f.y=0),s.current=!0,Math.abs(f.x)>0||Math.abs(f.y)>0){const b=af(c);b&&b.scrollBy({top:f.y,left:f.x})}},[t,a,l,o,n])}const hi=u.createContext({...Ft,scaleX:1,scaleY:1});var bn;(function(e){e[e.Uninitialized=0]="Uninitialized",e[e.Initializing=1]="Initializing",e[e.Initialized=2]="Initialized"})(bn||(bn={}));const NI=u.memo(function(t){var n,o,r,s;let{id:a,accessibility:l,autoScroll:d=!0,children:c,sensors:p=CI,collisionDetection:f=W2,measuring:b,modifiers:g,...m}=t;const h=u.useReducer(AI,void 0,II),[v,y]=h,[_,w]=$2(),[k,x]=u.useState(bn.Uninitialized),$=k===bn.Initialized,{draggable:{active:C,nodes:S,translate:D},droppable:{containers:E}}=v,I=C!=null?S.get(C):null,N=u.useRef({initial:null,translated:null}),T=u.useMemo(()=>{var Q;return C!=null?{id:C,data:(Q=I?.data)!=null?Q:$I,rect:N}:null},[C,I]),O=u.useRef(null),[M,B]=u.useState(null),[z,H]=u.useState(null),P=Uo(m,Object.values(m)),A=li("DndDescribedBy",a),L=u.useMemo(()=>E.getEnabled(),[E]),F=DI(b),{droppableRects:Y,measureDroppableContainers:V,measuringScheduled:X}=gI(L,{dragging:$,dependencies:[D.x,D.y],config:F.droppable}),Z=pI(S,C),ie=u.useMemo(()=>z?ci(z):null,[z]),oe=pe(),G=hI(Z,F.draggable.measure);zI({activeNode:C!=null?S.get(C):null,config:oe.layoutShiftCompensation,initialRect:G,measure:F.draggable.measure});const ee=_f(Z,F.draggable.measure,G),fe=_f(Z?Z.parentElement:null),K=u.useRef({activatorEvent:null,active:null,activeNode:Z,collisionRect:null,collisions:null,droppableRects:Y,draggableNodes:S,draggingNode:null,draggingNodeRect:null,droppableContainers:E,over:null,scrollableAncestors:[],scrollAdjustedTranslate:null}),te=E.getNodeFor((n=K.current.over)==null?void 0:n.id),ye=SI({measure:F.dragOverlay.measure}),Se=(o=ye.nodeRef.current)!=null?o:Z,De=$?(r=ye.rect)!=null?r:ee:null,Ie=!!(ye.nodeRef.current&&ye.rect),Ve=mI(Ie?null:ee),ze=kf(Se?xt(Se):null),Be=vI($?te??Z:null),Xe=kI(Be),Ne=If(g,{transform:{x:D.x-Ve.x,y:D.y-Ve.y,scaleX:1,scaleY:1},activatorEvent:z,active:T,activeNodeRect:ee,containerNodeRect:fe,draggingNodeRect:De,over:K.current.over,overlayNodeRect:ye.rect,scrollableAncestors:Be,scrollableAncestorRects:Xe,windowRect:ze}),Je=ie?yo(ie,D):null,ve=_I(Be),qe=wf(ve),We=wf(ve,[ee]),ce=yo(Ne,qe),q=De?V2(De,Ne):null,se=T&&q?f({active:T,collisionRect:q,droppableRects:Y,droppableContainers:L,pointerCoordinates:Je}):null,ae=O2(se,"id"),[be,Ce]=u.useState(null),$e=Ie?Ne:yo(Ne,We),U=F2($e,(s=be?.rect)!=null?s:null,ee),ue=u.useRef(null),ge=u.useCallback((Q,de)=>{let{sensor:Ae,options:dt}=de;if(O.current==null)return;const pt=S.get(O.current);if(!pt)return;const ot=Q.nativeEvent,Le=new Ae({active:O.current,activeNode:pt,event:ot,options:dt,context:K,onAbort(Ze){if(!S.get(Ze))return;const{onDragAbort:Dt}=P.current,zt={id:Ze};Dt?.(zt),_({type:"onDragAbort",event:zt})},onPending(Ze,Tt,Dt,zt){if(!S.get(Ze))return;const{onDragPending:Sn}=P.current,en={id:Ze,constraint:Tt,initialCoordinates:Dt,offset:zt};Sn?.(en),_({type:"onDragPending",event:en})},onStart(Ze){const Tt=O.current;if(Tt==null)return;const Dt=S.get(Tt);if(!Dt)return;const{onDragStart:zt}=P.current,kn={activatorEvent:ot,active:{id:Tt,data:Dt.data,rect:N}};En.unstable_batchedUpdates(()=>{zt?.(kn),x(bn.Initializing),y({type:st.DragStart,initialCoordinates:Ze,active:Tt}),_({type:"onDragStart",event:kn}),B(ue.current),H(ot)})},onMove(Ze){y({type:st.DragMove,coordinates:Ze})},onEnd:je(st.DragEnd),onCancel:je(st.DragCancel)});ue.current=Le;function je(Ze){return async function(){const{active:Dt,collisions:zt,over:kn,scrollAdjustedTranslate:Sn}=K.current;let en=null;if(Dt&&Sn){const{cancelDrop:Kn}=P.current;en={activatorEvent:ot,active:Dt,collisions:zt,delta:Sn,over:kn},Ze===st.DragEnd&&typeof Kn=="function"&&await Promise.resolve(Kn(en))&&(Ze=st.DragCancel)}O.current=null,En.unstable_batchedUpdates(()=>{y({type:Ze}),x(bn.Uninitialized),Ce(null),B(null),H(null),ue.current=null;const Kn=Ze===st.DragEnd?"onDragEnd":"onDragCancel";if(en){const cn=P.current[Kn];cn?.(en),_({type:Kn,event:en})}})}}},[S]),ke=u.useCallback((Q,de)=>(Ae,dt)=>{const pt=Ae.nativeEvent,ot=S.get(dt);if(O.current!==null||!ot||pt.dndKit||pt.defaultPrevented)return;const Le={active:ot};Q(Ae,de.options,Le)===!0&&(pt.dndKit={capturedBy:de.sensor},O.current=dt,ge(Ae,de))},[S,ge]),he=fI(p,ke);xI(p),ln(()=>{ee&&k===bn.Initializing&&x(bn.Initialized)},[ee,k]),u.useEffect(()=>{const{onDragMove:Q}=P.current,{active:de,activatorEvent:Ae,collisions:dt,over:pt}=K.current;if(!de||!Ae)return;const ot={active:de,activatorEvent:Ae,collisions:dt,delta:{x:ce.x,y:ce.y},over:pt};En.unstable_batchedUpdates(()=>{Q?.(ot),_({type:"onDragMove",event:ot})})},[ce.x,ce.y]),u.useEffect(()=>{const{active:Q,activatorEvent:de,collisions:Ae,droppableContainers:dt,scrollAdjustedTranslate:pt}=K.current;if(!Q||O.current==null||!de||!pt)return;const{onDragOver:ot}=P.current,Le=dt.get(ae),je=Le&&Le.rect.current?{id:Le.id,rect:Le.rect.current,data:Le.data,disabled:Le.disabled}:null,Ze={active:Q,activatorEvent:de,collisions:Ae,delta:{x:pt.x,y:pt.y},over:je};En.unstable_batchedUpdates(()=>{Ce(je),ot?.(Ze),_({type:"onDragOver",event:Ze})})},[ae]),ln(()=>{K.current={activatorEvent:z,active:T,activeNode:Z,collisionRect:q,collisions:se,droppableRects:Y,draggableNodes:S,draggingNode:Se,draggingNodeRect:De,droppableContainers:E,over:be,scrollableAncestors:Be,scrollAdjustedTranslate:ce},N.current={initial:De,translated:q}},[T,Z,se,q,S,Se,De,Y,E,be,Be,ce]),dI({...oe,delta:D,draggingRect:q,pointerCoordinates:Je,scrollableAncestors:Be,scrollableAncestorRects:Xe});const j=u.useMemo(()=>({active:T,activeNode:Z,activeNodeRect:ee,activatorEvent:z,collisions:se,containerNodeRect:fe,dragOverlay:ye,draggableNodes:S,droppableContainers:E,droppableRects:Y,over:be,measureDroppableContainers:V,scrollableAncestors:Be,scrollableAncestorRects:Xe,measuringConfiguration:F,measuringScheduled:X,windowRect:ze}),[T,Z,ee,z,se,fe,ye,S,E,Y,be,V,Be,Xe,F,X,ze]),ne=u.useMemo(()=>({activatorEvent:z,activators:he,active:T,activeNodeRect:ee,ariaDescribedById:{draggable:A},dispatch:y,draggableNodes:S,over:be,measureDroppableContainers:V}),[z,he,T,ee,y,A,S,be,V]);return u.createElement(nf.Provider,{value:w},u.createElement(Ro.Provider,{value:ne},u.createElement(Ef.Provider,{value:j},u.createElement(hi.Provider,{value:U},c)),u.createElement(TI,{disabled:l?.restoreFocus===!1})),u.createElement(A2,{...l,hiddenTextDescribedById:A}));function pe(){const Q=M?.autoScrollEnabled===!1,de=typeof d=="object"?d.enabled===!1:d===!1,Ae=$&&!Q&&!de;return typeof d=="object"?{...d,enabled:Ae}:{enabled:Ae}}}),MI=u.createContext(null),Af="button",LI="Draggable";function PI(e){let{id:t,data:n,disabled:o=!1,attributes:r}=e;const s=li(LI),{activators:a,activatorEvent:l,active:d,activeNodeRect:c,ariaDescribedById:p,draggableNodes:f,over:b}=u.useContext(Ro),{role:g=Af,roleDescription:m="draggable",tabIndex:h=0}=r??{},v=d?.id===t,y=u.useContext(v?hi:MI),[_,w]=si(),[k,x]=si(),$=wI(a,t),C=Uo(n);ln(()=>(f.set(t,{id:t,key:s,node:_,activatorNode:k,data:C}),()=>{const D=f.get(t);D&&D.key===s&&f.delete(t)}),[f,t]);const S=u.useMemo(()=>({role:g,tabIndex:h,"aria-disabled":o,"aria-pressed":v&&g===Af?!0:void 0,"aria-roledescription":m,"aria-describedby":p.draggable}),[o,g,h,v,m,p.draggable]);return{active:d,activatorEvent:l,activeNodeRect:c,attributes:S,isDragging:v,listeners:o?void 0:$,node:_,over:b,setNodeRef:w,setActivatorNodeRef:x,transform:y}}function OI(){return u.useContext(Ef)}const BI="Droppable",WI={timeout:25};function jI(e){let{data:t,disabled:n=!1,id:o,resizeObserverConfig:r}=e;const s=li(BI),{active:a,dispatch:l,over:d,measureDroppableContainers:c}=u.useContext(Ro),p=u.useRef({disabled:n}),f=u.useRef(!1),b=u.useRef(null),g=u.useRef(null),{disabled:m,updateMeasurementsFor:h,timeout:v}={...WI,...r},y=Uo(h??o),_=u.useCallback(()=>{if(!f.current){f.current=!0;return}g.current!=null&&clearTimeout(g.current),g.current=setTimeout(()=>{c(Array.isArray(y.current)?y.current:[y.current]),g.current=null},v)},[v]),w=fi({callback:_,disabled:m||!a}),k=u.useCallback((S,D)=>{w&&(D&&(w.unobserve(D),f.current=!1),S&&w.observe(S))},[w]),[x,$]=si(k),C=Uo(t);return u.useEffect(()=>{!w||!x.current||(w.disconnect(),f.current=!1,w.observe(x.current))},[x,w]),u.useEffect(()=>(l({type:st.RegisterDroppable,element:{id:o,key:s,disabled:n,node:x,rect:b,data:C}}),()=>l({type:st.UnregisterDroppable,key:s,id:o})),[o]),u.useEffect(()=>{n!==p.current.disabled&&(l({type:st.SetDroppableDisabled,id:o,key:s,disabled:n}),p.current.disabled=n)},[o,s,n,l]),{active:a,rect:b,isOver:d?.id===o,node:x,over:d,setNodeRef:$}}function HI(e){let{animation:t,children:n}=e;const[o,r]=u.useState(null),[s,a]=u.useState(null),l=ai(n);return!n&&!o&&l&&r(l),ln(()=>{if(!s)return;const d=o?.key,c=o?.props.id;if(d==null||c==null){r(null);return}Promise.resolve(t(c,s)).then(()=>{r(null)})},[t,o,s]),u.createElement(u.Fragment,null,n,o?u.cloneElement(o,{ref:a}):null)}const FI={x:0,y:0,scaleX:1,scaleY:1};function KI(e){let{children:t}=e;return u.createElement(Ro.Provider,{value:$f},u.createElement(hi.Provider,{value:FI},t))}const VI={position:"fixed",touchAction:"none"},UI=e=>ba(e)?"transform 250ms ease":void 0,qI=u.forwardRef((e,t)=>{let{as:n,activatorEvent:o,adjustScale:r,children:s,className:a,rect:l,style:d,transform:c,transition:p=UI}=e;if(!l)return null;const f=r?c:{...c,scaleX:1,scaleY:1},b={...VI,width:l.width,height:l.height,top:l.top,left:l.left,transform:Go.Transform.toString(f),transformOrigin:r&&o?N2(o,l):void 0,transition:typeof p=="function"?p(o):p,...d};return u.createElement(n,{className:a,style:b,ref:t},s)}),GI={duration:250,easing:"ease",keyframes:e=>{let{transform:{initial:t,final:n}}=e;return[{transform:Go.Transform.toString(t)},{transform:Go.Transform.toString(n)}]},sideEffects:(e=>t=>{let{active:n,dragOverlay:o}=t;const r={},{styles:s,className:a}=e;if(s!=null&&s.active)for(const[l,d]of Object.entries(s.active))d!==void 0&&(r[l]=n.node.style.getPropertyValue(l),n.node.style.setProperty(l,d));if(s!=null&&s.dragOverlay)for(const[l,d]of Object.entries(s.dragOverlay))d!==void 0&&o.node.style.setProperty(l,d);return a!=null&&a.active&&n.node.classList.add(a.active),a!=null&&a.dragOverlay&&o.node.classList.add(a.dragOverlay),function(){for(const[d,c]of Object.entries(r))n.node.style.setProperty(d,c);a!=null&&a.active&&n.node.classList.remove(a.active)}})({styles:{active:{opacity:"0"}}})};function YI(e){let{config:t,draggableNodes:n,droppableContainers:o,measuringConfiguration:r}=e;return ii((s,a)=>{if(t===null)return;const l=n.get(s);if(!l)return;const d=l.node.current;if(!d)return;const c=Cf(a);if(!c)return;const{transform:p}=xt(a).getComputedStyle(a),f=rf(p);if(!f)return;const b=typeof t=="function"?t:XI(t);return ff(d,r.draggable.measure),b({active:{id:s,data:l.data,node:d,rect:r.draggable.measure(d)},draggableNodes:n,dragOverlay:{node:a,rect:r.dragOverlay.measure(c)},droppableContainers:o,measuringConfiguration:r,transform:f})})}function XI(e){const{duration:t,easing:n,sideEffects:o,keyframes:r}={...GI,...e};return s=>{let{active:a,dragOverlay:l,transform:d,...c}=s;if(!t)return;const p={x:l.rect.left-a.rect.left,y:l.rect.top-a.rect.top},f={scaleX:d.scaleX!==1?a.rect.width*d.scaleX/l.rect.width:1,scaleY:d.scaleY!==1?a.rect.height*d.scaleY/l.rect.height:1},b={x:d.x-p.x,y:d.y-p.y,...f},g=r({...c,active:a,dragOverlay:l,transform:{initial:d,final:b}}),[m]=g,h=g[g.length-1];if(JSON.stringify(m)===JSON.stringify(h))return;const v=o?.({active:a,dragOverlay:l,...c}),y=l.node.animate(g,{duration:t,easing:n,fill:"forwards"});return new Promise(_=>{y.onfinish=()=>{v?.(),_()}})}}let Tf=0;function JI(e){return u.useMemo(()=>{if(e!=null)return Tf++,Tf},[e])}const ZI=u.memo(e=>{let{adjustScale:t=!1,children:n,dropAnimation:o,style:r,transition:s,modifiers:a,wrapperElement:l="div",className:d,zIndex:c=999}=e;const{activatorEvent:p,active:f,activeNodeRect:b,containerNodeRect:g,draggableNodes:m,droppableContainers:h,dragOverlay:v,over:y,measuringConfiguration:_,scrollableAncestors:w,scrollableAncestorRects:k,windowRect:x}=OI(),$=u.useContext(hi),C=JI(f?.id),S=If(a,{activatorEvent:p,active:f,activeNodeRect:b,containerNodeRect:g,draggingNodeRect:v.rect,over:y,overlayNodeRect:v.rect,scrollableAncestors:w,scrollableAncestorRects:k,transform:$,windowRect:x}),D=Ia(b),E=YI({config:o,draggableNodes:m,droppableContainers:h,measuringConfiguration:_}),I=D?v.setRef:void 0;return u.createElement(KI,null,u.createElement(HI,{animation:E},f&&C?u.createElement(qI,{key:C,id:f.id,ref:I,as:l,activatorEvent:p,adjustScale:t,className:d,transition:s,rect:D,style:{zIndex:c,...r},transform:S},n):null))});function er(e,t){return!t||!Array.isArray(t.draggable)?!1:t.draggable.includes(e)}function QI({draggedComponentName:e,targetContainerName:t,nesting:n}){if(!n||!er(e,n))return!1;const o=n.containers;if(!o||typeof o!="object")return!1;const r=o[t];return!r||!Array.isArray(r.accepts)?!1:r.accepts.includes(e)}function Df(e,t){if(!t?.containers)return!1;const n=t.containers[e];return!!(n&&Array.isArray(n.accepts)&&n.accepts.length>0)}function RI(e,t,n,o,r){if(!n||n.length===0)return{parentPath:[],index:r};if(e&&Df(e,o))return{parentPath:n,index:t};const s=n.slice(0,-1),a=n[n.length-1]??0;return{parentPath:s,index:a+1}}function zf(e,t){return e===t||e.startsWith(`${t}.`)}function Nf(e,t){if(e==="")return/^\d+$/.test(t)?Number(t):null;const n=`${e}.children.`;if(!t.startsWith(n))return null;const o=t.slice(n.length);return/^\d+$/.test(o)?Number(o):null}function eA(e,t){const n=[];return e.querySelectorAll("[data-editor-path]").forEach(o=>{if(o.hasAttribute("data-dnd-placeholder")||o.closest("[data-dnd-placeholder]"))return;const r=o.dataset.editorPath;typeof r=="string"&&n.push({path:t(r),domPath:r,rect:o.getBoundingClientRect()})}),n}const tA=16;function nA(e){const{frameEl:t,draggedPath:n,acceptsContainer:o,allowsNestSlot:r,includeContainer:s}=e,a=e.normalizeContainerPath??(g=>g),l=t.getBoundingClientRect(),d=eA(t,a),c=new Map;for(const g of d)c.has(g.path)||c.set(g.path,g.domPath);const p=a(n),f=new Set([""]);for(const g of d){const m=rA(g.path);m!==null&&f.add(m)}for(const g of d)f.add(g.path);const b=[];for(const g of f){const m=g==="",h=m?null:c.get(g)??g,v=m?t:t.querySelector(`[data-editor-path="${Pf(h)}"]`);if(!v&&!m||!m&&zf(g,p))continue;const y=g;if(!m&&s&&!s(y))continue;const _=$=>{const C=c.get($);if(!C||typeof window>"u")return!1;const S=t.querySelector(`[data-editor-path="${Pf(C)}"]`);return S?(window.getComputedStyle(S).display??"").toLowerCase()==="contents":!1},w=d.filter($=>Nf(g,$.path)!==null).filter($=>!zf($.path,p)).filter($=>!_($.path)).map($=>({origIndex:Nf(g,$.path),rect:oA($.rect,$.path,d)})).filter($=>$.rect.width>0||$.rect.height>0).sort(($,C)=>$.origIndex-C.origIndex),k=Up(m?t:v).kind,x=k==="flex_row_single"||k==="flex_row_wrap"||k==="grid_single_row"||k==="grid_2d";if(w.length>0){if(o(y)){w.forEach(C=>{b.push(Mf(y,C.origIndex,C.rect,l,x,"before"))});const $=w[w.length-1];b.push(Mf(y,$.origIndex+1,$.rect,l,x,"after"))}}else if(!m&&r(y)&&v){const $=v.getBoundingClientRect();$.width>0&&$.height>0&&b.push({id:`slot:${y}:0`,kind:"nest",containerPath:y,index:0,box:Lf($,l),orientation:"area"})}else m&&b.push({id:"slot::0",kind:"gap",containerPath:"",index:0,box:Lf(l,l),orientation:"area"})}return b.filter(g=>fo(g.box,t))}function Mf(e,t,n,o,r,s){const a=tA;let l;if(r)l={left:(s==="before"?n.left:n.right)-a/2,top:n.top,width:a,height:n.height};else{const d=s==="before"?n.top:n.bottom;l={left:n.left,top:d-a/2,width:n.width,height:a}}return{id:`slot:${e}:${t}`,kind:"gap",containerPath:e,index:t,box:{left:l.left-o.left,top:l.top-o.top,width:l.width,height:l.height,scale:1},orientation:r?"vertical":"horizontal"}}function oA(e,t,n){if(e.width>0||e.height>0)return e;const o=`${t}.children.`;let r=1/0,s=1/0,a=-1/0,l=-1/0;for(const d of n)d.path.startsWith(o)&&(d.rect.width===0&&d.rect.height===0||(r=Math.min(r,d.rect.left),s=Math.min(s,d.rect.top),a=Math.max(a,d.rect.right),l=Math.max(l,d.rect.bottom)));return r===1/0?e:{left:r,top:s,width:a-r,height:l-s,right:a,bottom:l,x:r,y:s,toJSON:()=>({})}}function Lf(e,t){return{left:e.left-t.left,top:e.top-t.top,width:e.width,height:e.height,scale:1}}function rA(e){if(e==="")return null;const t=e.lastIndexOf(".children.");return t<0?"":e.slice(0,t)}function iA(e){if(!e.startsWith("slot:"))return null;const t=e.slice(5),n=t.lastIndexOf(":");if(n<0)return null;const o=t.slice(0,n),r=Number(t.slice(n+1));return Number.isFinite(r)?{containerPath:o,index:r}:null}function Pf(e){return typeof CSS<"u"&&typeof CSS.escape=="function"?CSS.escape(e):e.replace(/[^a-zA-Z0-9_\-.]/g,t=>`\\${t}`)}function Aa(e){return typeof e=="string"&&e.length>0?e:null}function Ta(e,t){return t&&e.startsWith(`${t}.`)?t:e}function Of(e){if(typeof e!="string")return null;const t=iA(e);return t?{containerPath:t.containerPath,index:t.index}:null}function Da(e,t){return t?e.startsWith(`${t}.iteration.`):!1}function za(e,t,n){return t?n==="iteration_item"?Da(e,t):e===t||e.startsWith(`${t}.`):!1}function sA(e){const{frameEl:t,nesting:n,editMode:o,currentExtensionId:r,editableRootPath:s,components:a,patchLayout:l,pushHistory:d,onMovePath:c,selectedPath:p,showBranchBoundaryHint:f}=e,b=(o==="iteration_item"||o==="modal")&&s&&s.length>0?at(s):null,[g,m]=u.useState(null),[h,v]=u.useState(null),[y,_]=u.useState(null),w=u.useRef({components:a,nesting:n,editMode:o,currentExtensionId:r,selectedPath:p,editableRootSourcePath:b});w.current={components:a,nesting:n,editMode:o,currentExtensionId:r,selectedPath:p,editableRootSourcePath:b};const k=u.useRef(null),x=u.useCallback(M=>({children:M}),[]),$=u.useCallback((M,B)=>{const z=Pe(x(M),B);return z?typeof z.name=="string"?z.name:typeof z.type=="string"?z.type:null:null},[x]),C=u.useCallback(M=>B=>{const z=w.current;if(B==="")return er(M,z.nesting);const H=Re(B),P=Pe(x(z.components),H);if(!P)return!1;const A=typeof P.name=="string"?P.name:typeof P.type=="string"?P.type:"";if(!A)return!1;const L=!!z.editableRootSourcePath&&(B===z.editableRootSourcePath||B.startsWith(`${z.editableRootSourcePath}.`));if(z.editableRootSourcePath&&!L)return!1;if(!L){const F=Dn(x(z.components),H);if(Jt(P,z.editMode,z.currentExtensionId,F)!=="none")return!1}return Df(A,z.nesting)?QI({draggedComponentName:M,targetContainerName:A,nesting:z.nesting}):!1},[x]),S=u.useCallback(M=>{if(M==="")return!0;const B=w.current,z=Re(M),H=k.current;if(H&&H.length>0){const L=H.slice(0,-1);if(yi(z,L))return!0}const P=Pe(x(B.components),z);return P?(typeof P.name=="string"?P.name:typeof P.type=="string"?P.type:"").length>0:!1},[x]),D=u.useCallback(M=>{const B=C(M),z=w.current,H=k.current,P=H?H.slice(0,-1):null,A=new Set;if(P)for(let V=P.length;V>=0;V--)A.add(bi(P.slice(0,V)));const L=bi(H??[]),F=V=>!!H&&(V===L||V.startsWith(`${L}.`)),Y=(V,X)=>{for(let Z=0;ZV===""?!0:F(V)?!1:A.has(V),allowsNestSlot:V=>{if(!B(V))return!1;if(P){const X=Re(V);if(yi(X,P))return!1}return S(V)}}},[C,S,x]),E=u.useCallback(M=>{const B=Aa(M.active.id),z=w.current;if(!B)return;const H=Ta(B,z.selectedPath),P=Re(H),A=Pe(x(z.components),P),L=A&&typeof A.name=="string"?A.name:A&&typeof A.type=="string"?A.type:null;m(H),v(L),_(null),k.current=A?P:null;let F="allowed";if(!L||!er(L,z.nesting))F="denied_no_draggable";else if(A){const Y=Dn(x(z.components),P),V=Jt(A,z.editMode,z.currentExtensionId,Y);if(z.editableRootSourcePath&&!za(H,z.editableRootSourcePath,z.editMode))F="denied_data_bound";else if(rs(Y)&&!Da(H,z.editableRootSourcePath))F="denied_data_bound";else{const X=Dp(V);X&&(F=X)}}F!=="allowed"&&m(null),pn({source:"drag",draggedComponentName:L,targetContainerName:null,targetContainerPath:null,decision:F,result:F==="allowed"?"completed":"denied",timestamp:Date.now()})},[x]),I=u.useCallback(M=>{if(!t)return;const B=Aa(M.active.id);if(!B)return;const z=h;if(!z){_(null);return}const H=w.current,P=Ta(B,H.selectedPath),A=Re(P),L=Pe(x(H.components),A);if(!L||!er(z,H.nesting)){_(null);return}const F=Dn(x(H.components),A);if(H.editableRootSourcePath&&!za(P,H.editableRootSourcePath,H.editMode)){_(null);return}const Y=Jt(L,H.editMode,H.currentExtensionId,F),V=Da(P,H.editableRootSourcePath);if(rs(F)&&!V||!Zt(Y)&&!V){_(null);return}_(Of(M.over?.id))},[t,h,x]),N=u.useCallback(()=>{m(null),v(null),_(null),k.current=null},[]),T=u.useCallback(M=>{const B=Aa(M.active.id),z=h,H=w.current,P=B?Ta(B,H.selectedPath):null;if(!t||!P||!z){N();return}const A=Of(M.over?.id),L=Re(P);if(!A){pn({source:"drag",draggedComponentName:z,targetContainerName:null,targetContainerPath:null,decision:"denied_no_accepts",result:"cancelled",timestamp:Date.now()}),N();return}const F=A.containerPath===""?[]:Re(A.containerPath),Y=A.containerPath===""?null:$(H.components,F);if(!aA(L.slice(0,-1),F)){pn({source:"drag",draggedComponentName:z,targetContainerName:Y,targetContainerPath:A.containerPath||null,decision:"denied_responsive_branch_boundary",result:"denied",timestamp:Date.now()}),f?.(),N();return}if(H.editableRootSourcePath&&!za(A.containerPath,H.editableRootSourcePath,H.editMode)){pn({source:"drag",draggedComponentName:z,targetContainerName:Y,targetContainerPath:A.containerPath||null,decision:"denied_data_bound",result:"denied",timestamp:Date.now()}),N();return}const V=Pe(x(H.components),L),X=V?Dp(Jt(V,H.editMode,H.currentExtensionId,Dn(x(H.components),L))):null;if(X){pn({source:"drag",draggedComponentName:z,targetContainerName:Y,targetContainerPath:A.containerPath||null,decision:X,result:"denied",timestamp:Date.now()}),N();return}const Z=L.slice(0,-1),ie=L[L.length-1]??-1;if(yi(Z,F)&&(A.index===ie||A.index===ie+1)){pn({source:"drag",draggedComponentName:z,targetContainerName:Y,targetContainerPath:A.containerPath||null,decision:"allowed",insertionIndex:A.index,result:"cancelled",timestamp:Date.now()}),N();return}const ee=yi(Z,F),fe=A.index;let K=[];l(De=>{const ze=rm({children:De},L,F,fe).children??[];return K=ze,ze}),d(K,`move ${z}`);const te=gd(F,L),ye=ee&&ie{N()},[N]);return{activeDragPath:g,activeDragName:h,activeDropZone:y,onDragStart:E,onDragMove:I,onDragEnd:T,onDragCancel:O,buildSlotPredicates:D}}function yi(e,t){if(e.length!==t.length)return!1;for(let n=0;n0?at(s):null,{t:m}=et(),[h,v]=u.useState(!1),y=u.useRef(null),_=u.useCallback(()=>{v(!0),y.current&&clearTimeout(y.current),y.current=setTimeout(()=>v(!1),2600)},[]);u.useEffect(()=>()=>{y.current&&clearTimeout(y.current)},[]);const w=sA({frameEl:t,nesting:n,editMode:o,currentExtensionId:r,editableRootPath:s,components:a,patchLayout:l,pushHistory:d,onMovePath:p,selectedPath:b,showBranchBoundaryHint:_}),k=D2(T2(Sa,{activationConstraint:{distance:8}})),[x,$]=u.useState([]),C=u.useCallback(()=>{if(!t){$([]);return}if(w.activeDragPath!==null)return;const P={children:a},A=t.querySelectorAll("[data-editor-path]"),L=[],F=new Map;A.forEach(Y=>{const V=Y.dataset.editorPath;if(typeof V!="string"||o!=="iteration_item"&&g&&!cA(V,g,o))return;const X=lA(V);if(X!==null&&!tr(V,g)){if(/\.iteration\.\d+$/.test(V)){const K=po(Y,t);if(K&&fo(K,t)){const te=F.get(X)??[];te.push(K),F.set(X,te)}}return}if(tr(V,g)&&/\.iteration\.\d+$/.test(V))return;const Z=Re(V),ie=Pe(P,Z);if(!ie||ie.iteration&&ie.iteration.source!==void 0)return;const oe=typeof ie.name=="string"?ie.name:typeof ie.type=="string"?ie.type:null;if(!oe||!er(oe,n))return;const G=Dn(P,Z);if(rs(G)&&!tr(V,g))return;const ee=Jt(ie,o,r,G);if(!Zt(ee)&&!tr(V,g))return;const fe=po(Y,t);fe&&fo(fe,t)&&L.push({path:V,name:oe,box:fe})});for(const[Y,V]of F){const X=Re(Y),Z=Pe(P,X);if(!Z)continue;const ie=typeof Z.name=="string"?Z.name:typeof Z.type=="string"?Z.type:null;if(!ie)continue;const oe=Dn(P,X),G=Jt(Z,o,r,oe);if(!Zt(G))continue;const ee=uA(V);ee&&fo(ee,t)&&L.push({path:Y,name:ie,box:ee,isIterationGroup:!0})}$(L)},[t,a,n,o,r,g,w.activeDragPath]);u.useEffect(()=>{C()},[C]),u.useEffect(()=>{if(t)return jp(C,t)},[t,C]);const S=w.activeDragPath!==null,D=w.activeDragName,E=w.activeDragPath,[I,N]=u.useState(null),T=u.useRef(null);u.useEffect(()=>{if(!E||!t){E||(T.current=null,N(null));return}if(T.current===E)return;T.current=E;const P=t.querySelector(`[data-editor-path="${Bf(E)}"]`);if(P){const oe=P.getBoundingClientRect();N({node:P.cloneNode(!0),width:oe.width,height:oe.height});return}const A=Array.from(t.querySelectorAll(`[data-editor-path^="${Bf(E)}.iteration."]`)).filter(oe=>/\.iteration\.\d+$/.test(oe.dataset.editorPath??""));if(A.length===0){N(null);return}let L=1/0,F=1/0,Y=-1/0,V=-1/0;for(const oe of A){const G=oe.getBoundingClientRect();L=Math.min(L,G.left),F=Math.min(F,G.top),Y=Math.max(Y,G.right),V=Math.max(V,G.bottom)}const X=Y-L,Z=V-F,ie=document.createElement("div");ie.style.position="relative",ie.style.width=`${X}px`,ie.style.height=`${Z}px`;for(const oe of A){const G=oe.getBoundingClientRect(),ee=oe.cloneNode(!0);ee.style.position="absolute",ee.style.left=`${G.left-L}px`,ee.style.top=`${G.top-F}px`,ee.style.width=`${G.width}px`,ee.style.height=`${G.height}px`,ee.style.margin="0",ie.appendChild(ee)}N({node:ie,width:X,height:Z})},[E,t]);const[O,M]=u.useState([]),B=u.useRef(null),z=u.useRef(w.buildSlotPredicates);if(z.current=w.buildSlotPredicates,u.useEffect(()=>{if(!E||!D||!t){B.current=null,M(F=>F.length===0?F:[]);return}if(B.current===E)return;B.current=E;const{acceptsContainer:P,allowsNestSlot:A,includeContainer:L}=z.current(D);M(nA({frameEl:t,draggedPath:E,acceptsContainer:P,allowsNestSlot:A,includeContainer:L,normalizeContainerPath:F=>dA(F)}))},[E,D,t]),!t)return null;const H=w.activeDropZone?`slot:${w.activeDropZone.containerPath}:${w.activeDropZone.index}`:null;return i.jsxs(NI,{sensors:k,collisionDetection:H2,onDragStart:w.onDragStart,onDragMove:w.onDragMove,onDragEnd:w.onDragEnd,onDragCancel:w.onDragCancel,children:[x.map(P=>i.jsx(hA,{path:P.path,name:P.name,box:P.box,dimmed:S&&w.activeDragPath!==P.path,isIterationGroup:P.isIterationGroup,onSelect:c,onRequestInlineEdit:f},P.path)),S&&O.map(P=>i.jsx(gA,{slot:P,active:P.id===H},P.id)),En.createPortal(i.jsx(ZI,{dropAnimation:null,zIndex:Jp,children:D?I?i.jsx(fA,{name:D,node:I.node,width:I.width,height:I.height}):i.jsx("div",{className:"g7le-dnd-drag-ghost","data-testid":"g7le-dnd-drag-ghost",style:{padding:"4px 10px",background:"rgba(79,70,229,0.95)",color:"#fff",fontSize:12,fontWeight:600,borderRadius:4,pointerEvents:"none",whiteSpace:"nowrap"},children:`<${D}>`}):null}),document.body),h&&En.createPortal(i.jsxs("div",{"data-testid":"g7le-branch-boundary-hint",role:"status",style:{position:"absolute",left:"50%",top:12,transform:"translateX(-50%)",background:"rgba(245,158,11,0.97)",color:"#fff",fontSize:13,fontWeight:600,padding:"8px 16px",borderRadius:8,boxShadow:"0 4px 14px rgba(0,0,0,0.2)",pointerEvents:"none",whiteSpace:"nowrap",zIndex:Jp},children:["⚠️ ",m("layout_editor.overlay.branch_boundary_denied")]}),t)]})}function fA(e){const{node:t,width:n,height:o}=e,r=u.useRef(null);return u.useEffect(()=>{const s=r.current;if(s)return s.replaceChildren(),s.appendChild(t.cloneNode(!0)),()=>{s.replaceChildren()}},[t]),i.jsx("div",{ref:r,className:"g7le-dnd-drag-ghost","data-testid":"g7le-dnd-drag-ghost",style:{width:n,height:o,opacity:.7,pointerEvents:"none",boxShadow:"0 6px 18px rgba(0,0,0,0.25)",borderRadius:4,overflow:"hidden",cursor:"grabbing"}})}function gA(e){const{slot:t,active:n}=e,{setNodeRef:o}=jI({id:t.id}),r=t.orientation!=="area";return i.jsx("div",{ref:o,"data-testid":`g7le-dnd-slot-${t.id}`,"data-dnd-slot-id":t.id,style:{position:"absolute",left:t.box.left,top:t.box.top,width:t.box.width,height:t.box.height,pointerEvents:"auto",zIndex:s2,boxSizing:"border-box",...r?{background:n?"rgba(79,70,229,0.15)":"transparent",...n?t.orientation==="vertical"?{borderLeft:"2px solid #4f46e5"}:{borderTop:"2px solid #4f46e5"}:{}}:{border:n?"2px dashed #4f46e5":"1px dashed transparent",background:n?"rgba(79,70,229,0.08)":"transparent",borderRadius:4}}})}function Bf(e){return typeof CSS<"u"&&typeof CSS.escape=="function"?CSS.escape(e):e.replace(/[^a-zA-Z0-9_\-.]/g,t=>`\\${t}`)}function hA(e){const{path:t,box:n,dimmed:o,isIterationGroup:r,onSelect:s,onRequestInlineEdit:a}=e,{attributes:l,listeners:d,setNodeRef:c,isDragging:p}=PI({id:t}),f=(t.match(/\.children\./g)||[]).length,b=a2(f);return i.jsx("div",{ref:c,"data-testid":`g7le-dnd-handle-${t}`,"data-dnd-handle-path":t,"data-dnd-iteration-group":r?"true":void 0,...d,...l,onClick:g=>{if(g.stopPropagation(),!r&&typeof document<"u"&&typeof document.elementsFromPoint=="function"){const m=document.elementsFromPoint(g.clientX,g.clientY);for(const h of m){const y=h.closest?.("[data-editor-path]")?.dataset?.editorPath;if(typeof y=="string"&&(y===t||y.startsWith(`${t}.`))){s(y);return}}}s(t)},onDoubleClick:g=>{g.stopPropagation(),g.preventDefault(),a?.(t)},style:{position:"absolute",left:n.left,top:n.top,width:n.width,height:n.height,cursor:"grab",pointerEvents:"auto",background:p?"rgba(79,70,229,0.06)":"transparent",...r?{border:"1px dashed rgba(79,70,229,0.5)",borderRadius:4}:{},opacity:o?.6:1,zIndex:b,touchAction:"none"}})}function yA(e,t,n,o){if(t==="route")return[];if(t==="modal"||t==="iteration_item")return o&&o.length>0?[o]:[];const r=[],s=(a,l)=>{a.forEach((d,c)=>{const p=[...l,c];if(!pd(d,t,n)){r.push(p);return}const b=Array.isArray(d.children)?d.children:[];b.length>0&&s(b,p)})};return s(e,[]),r}function bA(e){return at(e)}function mi(e){return typeof CSS<"u"&&typeof CSS.escape=="function"?CSS.escape(e):e.replace(/["\\]/g,"\\$&")}function mA({frameEl:e,components:t,editMode:n,currentExtensionId:o,editableRootPath:r}){const[s,a]=u.useState(null),[l]=u.useState(()=>`g7le-dim-mask-${Math.floor(Math.random()*1e9)}`);if(u.useEffect(()=>{if(!e){a(null);return}let g=0;const m=()=>{const _=yA(t,n,o,r);if(_.length===0){a(null);return}const w=e.getBoundingClientRect(),k={left:0,top:0,width:w.width,height:w.height},x=[];for(const $ of _){const C=bA($),S=e.querySelector(`[data-editor-path="${mi(C)}"]`);let D=null;if(S){const E=S.getBoundingClientRect();E.width>0&&E.height>0&&(D={left:E.left,top:E.top,width:E.width,height:E.height})}if(!D){const E=Array.from(e.querySelectorAll(`[data-editor-path^="${mi(C)}.iteration."], [data-editor-path^="${mi(C)}.sortable."], [data-editor-path^="${mi(C)}.children."]`));let I=1/0,N=1/0,T=-1/0,O=-1/0;for(const M of E){const B=M.getBoundingClientRect();B.width<=0||B.height<=0||(I=Math.min(I,B.left),N=Math.min(N,B.top),T=Math.max(T,B.right),O=Math.max(O,B.bottom))}if(T>I&&O>N&&(D={left:I,top:N,width:T-I,height:O-N}),D&&E.length>0){const M=E[0].closest('[role="dialog"]');if(M&&e.contains(M)){const B=M.getBoundingClientRect();B.width>0&&B.height>0&&(D={left:B.left,top:B.top,width:B.width,height:B.height})}}}D&&x.push({left:D.left-w.left,top:D.top-w.top,width:D.width,height:D.height})}if(x.length===0){a(null);return}a({frame:k,holes:x})},h=()=>{g&&cancelAnimationFrame(g),g=requestAnimationFrame(m)};h();const v=typeof ResizeObserver<"u"?new ResizeObserver(h):null;v?.observe(e);const y=typeof MutationObserver<"u"?new MutationObserver(h):null;return y?.observe(e,{childList:!0,subtree:!0,attributes:!0}),window.addEventListener("scroll",h,!0),window.addEventListener("resize",h),()=>{g&&cancelAnimationFrame(g),v?.disconnect(),y?.disconnect(),window.removeEventListener("scroll",h,!0),window.removeEventListener("resize",h)}},[e,t,n,o,r?.join(".")??""]),!s||s.holes.length===0)return null;const{frame:d,holes:c}=s,p=n==="modal"||n==="iteration_item";let f=[];if(p){let g=1/0,m=1/0,h=-1/0,v=-1/0;for(const $ of c)g=Math.min(g,$.left),m=Math.min(m,$.top),h=Math.max(h,$.left+$.width),v=Math.max(v,$.top+$.height);const y=($,C,S)=>Math.max(C,Math.min(S,$)),_=y(g,0,d.width),w=y(m,0,d.height),k=y(h,0,d.width),x=y(v,0,d.height);f=[{left:0,top:0,width:d.width,height:w},{left:0,top:x,width:d.width,height:d.height-x},{left:0,top:w,width:_,height:x-w},{left:k,top:w,width:d.width-k,height:x-w}].filter($=>$.width>0&&$.height>0)}const b=g=>{g.preventDefault(),g.stopPropagation()};return i.jsxs(i.Fragment,{children:[f.map((g,m)=>i.jsx("div",{className:"g7le-source-lock-block","data-testid":"g7le-source-lock-block",onClickCapture:b,onPointerDownCapture:b,onMouseDownCapture:b,onDragStartCapture:b,style:{position:"absolute",left:g.left,top:g.top,width:g.width,height:g.height,zIndex:Xp,pointerEvents:"auto",cursor:"not-allowed",background:"transparent"}},m)),i.jsxs("svg",{className:"g7le-source-lock-dim","data-testid":"g7le-source-lock-dim",width:d.width,height:d.height,style:{position:"absolute",left:0,top:0,width:d.width,height:d.height,zIndex:Xp,pointerEvents:"none"},children:[i.jsx("defs",{children:i.jsxs("mask",{id:l,children:[i.jsx("rect",{x:0,y:0,width:d.width,height:d.height,fill:"white"}),c.map((g,m)=>i.jsx("rect",{"data-testid":"g7le-source-lock-dim-hole",x:g.left,y:g.top,width:g.width,height:g.height,rx:4,fill:"black"},m))]})}),i.jsx("rect",{"data-testid":"g7le-source-lock-dim-box",x:0,y:0,width:d.width,height:d.height,fill:"rgba(15, 23, 42, 0.45)",mask:`url(#${l})`})]})]})}function Wf(e){const t={type:e.type??"basic",name:e.name},n={};if(e.props)for(const[o,r]of Object.entries(e.props))o!=="children"&&r?.default!==void 0&&(n[o]=r.default);return Object.keys(n).length>0&&(t.props=n),t}function vA(e){if(!e?.components)return[];const t=[];for(const[n,o]of Object.entries(e.components))if(Array.isArray(o))for(const r of o)t.push({...r,type:r.type??n});return t}function _A(e,t,n,o,r){const s=vA(e);let a=null;if(n&&t?.containers){const l=t.containers[n];l&&Array.isArray(l.accepts)?a=new Set(l.accepts):a=new Set}else!n&&t?.draggable&&(a=new Set(t.draggable));return s.filter(l=>{if(a&&!a.has(l.name))return!1;if(o){const d=o.toLowerCase(),c=r(l).toLowerCase(),p=l.name.toLowerCase();if(!c.includes(d)&&!p.includes(d)&&!`<${p}>`.includes(d)&&!(l.description??"").toLowerCase().includes(d))return!1}return!0})}function xA(e,t){const n={key:"all",label:"layout_editor.palette.category.all",entries:e,isI18nKey:!0};if(t?.groups&&t.groups.length>0){const a=new Map(e.map(p=>[p.name,p])),l=new Set,d=[];for(const p of t.groups){const f=[];for(const b of p.components??[]){const g=a.get(b);g&&(f.push(g),l.add(b))}f.length!==0&&d.push({key:p.kind??p.label,label:p.label,entries:f,isI18nKey:p.label.startsWith("$t:")||/^[a-z_.]+$/i.test(p.label)})}const c=e.filter(p=>!l.has(p.name));return c.length>0&&d.push({key:"other",label:"layout_editor.palette.category.other",entries:c,isI18nKey:!0}),[n,...d]}const o=new Map;for(const a of e){const l=(a.type??"basic").toLowerCase(),d=o.get(l)??[];d.push(a),o.set(l,d)}const r=["basic","composite","layout"],s=[];for(const a of r){const l=o.get(a);l&&l.length>0&&(s.push({key:a,label:`layout_editor.palette.category.fallback.${a}`,entries:l,isI18nKey:!0}),o.delete(a))}for(const[a,l]of o.entries())s.push({key:a,label:a,entries:l,isI18nKey:!1});return[n,...s]}function jf(e){const{manifest:t,nesting:n,componentPalette:o,targetParentPath:r,targetContainerName:s,onInsert:a,insertionIndex:l,onClose:d,editorTemplateId:c,editorLocale:p}=e,{t:f,translationEngine:b}=et(),g=u.useMemo(()=>{if(!c||!b)return f;const I=p??null;return N=>{const T={templateId:c,locale:I??"ko"},O=b.translate(N,T);return O!==N?O:f(N)}},[c,p,b,f]),[m,h]=u.useState(""),[v,y]=u.useState("all"),_=o?.entries??null,w=u.useMemo(()=>I=>{const N=_?.[I.name];return N?.label?vi(N.label,g):I.description??I.name},[_,g]),k=u.useMemo(()=>_A(t,n,s,m,w),[t,n,s,m,w]),x=u.useMemo(()=>xA(k,o??null),[k,o]),$=x.find(I=>I.key===v)??x[0]??null,C=!n||!n.draggable&&!n.containers,S=k.length===0,D=$?.entries??[],E=I=>{const N=_?.[I.name],T=!!N?.defaultNode&&typeof N.defaultNode=="object";if(N?.requiresDefaultNode&&!T)return;const O=T?JSON.parse(JSON.stringify(N.defaultNode)):Wf(I),M=r??[];a(O,M,l),pn({source:"palette",draggedComponentName:I.name,targetContainerName:s,targetContainerPath:M.length===0?null:M.join("/"),decision:"allowed",insertionIndex:l,result:"completed",timestamp:Date.now()}),d()};return i.jsxs("div",{className:"g7le-palette","data-testid":"g7le-palette",style:CA,children:[i.jsxs("div",{style:$A,children:[i.jsx("strong",{style:{fontSize:16},children:f("layout_editor.palette.title")}),i.jsx("button",{type:"button",onClick:d,"aria-label":"close","data-testid":"g7le-palette-close",style:EA,children:"×"})]}),i.jsxs("div",{style:IA,children:[i.jsx("aside",{style:AA,"data-testid":"g7le-palette-sidebar",children:i.jsx("nav",{children:i.jsx("ul",{style:TA,children:x.map(I=>{const N=$?.key===I.key,T=I.isI18nKey?vi(I.label,g):I.label;return i.jsx("li",{children:i.jsxs("button",{type:"button","data-testid":`g7le-palette-category-${I.key}`,"data-active":N?"true":"false",onClick:()=>y(I.key),style:{...DA,background:N?"#eff6ff":"transparent",color:N?"#1d4ed8":"#334155",fontWeight:N?600:500},children:[i.jsx("span",{children:T}),i.jsx("span",{style:LA,children:I.entries.length})]})},I.key)})})})}),i.jsxs("main",{style:zA,children:[i.jsx("input",{type:"text",placeholder:f("layout_editor.palette.search_placeholder"),value:m,onChange:I=>h(I.target.value),"data-testid":"g7le-palette-search",style:NA}),i.jsx("div",{style:MA,children:C?i.jsx("div",{style:Hf,"data-testid":"g7le-palette-spec-missing",children:f("layout_editor.palette.spec_missing")}):S||D.length===0?i.jsx("div",{style:Hf,"data-testid":"g7le-palette-empty",children:f("layout_editor.palette.empty")}):i.jsx("div",{"data-testid":`g7le-palette-group-${$?.key??"all"}`,style:{marginBottom:16},children:i.jsx("div",{style:PA,children:D.map(I=>i.jsx(wA,{entry:I,entrySpec:_?.[I.name]??null,onClick:E,t:f,entryT:g},I.name))})})})]})]})]})}function wA(e){const{entry:t,entrySpec:n,onClick:o,t:r,entryT:s}=e,a=n?.label?vi(n.label,s):t.description??t.name,l=(t.type??"").toLowerCase(),d=l==="basic"||l==="composite"||l==="layout",c=!!n?.defaultNode&&typeof n.defaultNode=="object",p=!!n?.requiresDefaultNode&&!c,f=!c,b=p?r("layout_editor.palette.requires_default_node_missing"):n?.label?vi(n.label,s):t.name;return i.jsxs("button",{type:"button",onClick:()=>o(t),disabled:p,"aria-disabled":p||void 0,"data-testid":`g7le-palette-item-${t.name}`,"data-component-type":l||void 0,"data-blocked":p?"true":void 0,"data-default-node-missing":f?"true":void 0,title:b,style:p?BA:OA,children:[i.jsx("div",{style:jA,"aria-hidden":"true",children:SA(t)}),i.jsx("div",{style:HA,children:a}),i.jsx("div",{"data-testid":`g7le-palette-item-${t.name}-tag`,style:FA,children:`<${t.name}>`}),d&&i.jsx("div",{"data-testid":`g7le-palette-item-${t.name}-badge`,style:kA(l),children:l}),f&&i.jsx("div",{"data-testid":`g7le-palette-item-${t.name}-default-missing-badge`,style:WA,children:r("layout_editor.palette.default_node_missing_badge")})]})}function vi(e,t){return e.startsWith("$t:")?t(e.slice(3)):t(e)}function kA(e){const n={basic:{bg:"#dbeafe",fg:"#1e40af"},composite:{bg:"#dcfce7",fg:"#14532d"},layout:{bg:"#fef3c7",fg:"#854d0e"}}[e]??{bg:"#e2e8f0",fg:"#334155"};return{fontSize:9,fontWeight:600,color:n.fg,background:n.bg,padding:"1px 6px",borderRadius:999,textTransform:"uppercase",letterSpacing:.4,lineHeight:1.6}}function SA(e){const t=e.name;return{Button:"🔘",A:"🔗",Img:"🖼",Icon:"✨",Hr:"➖",P:"📄",Span:"T",H1:"H₁",H2:"H₂",H3:"H₃",H4:"H₄",Ul:"☰",Li:"•",Nav:"🧭",Div:"▢",Container:"▢",Flex:"⇆",Grid:"▦",SectionLayout:"▤",ThreeColumnLayout:"⫼",Form:"📝",Input:"⎕",Textarea:"▭",Select:"⏷",Checkbox:"☑",PasswordInput:"🔒",Label:"🏷",DataGrid:"▦",Pagination:"↔",SearchBar:"🔍",ProductCard:"🛍",ImageGallery:"🖼",Avatar:"👤",UserInfo:"👥",HtmlContent:"⟨/⟩",HtmlEditor:"✏",RichTextEditor:"✏",TabNavigation:"◰",QuantitySelector:"#",ProductImageViewer:"🖼",FileUploader:"📎",AvatarUploader:"📷",SocialLoginButtons:"🌐",PostReactions:"❤"}[t]??"◻"}const CA={display:"flex",flexDirection:"column",width:"100%",height:"100%",minHeight:0,fontFamily:'-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Apple SD Gothic Neo", "Noto Sans KR", sans-serif'},$A={display:"flex",justifyContent:"space-between",alignItems:"center",padding:"14px 20px",borderBottom:"1px solid #e2e8f0",flexShrink:0},EA={background:"transparent",border:"none",fontSize:24,cursor:"pointer",lineHeight:1,color:"#64748b",padding:"0 4px"},IA={display:"flex",flex:1,minHeight:0},AA={width:180,flexShrink:0,borderRight:"1px solid #e2e8f0",padding:"14px 8px",background:"#f8fafc",overflowY:"auto"},TA={listStyle:"none",margin:0,padding:0,display:"flex",flexDirection:"column",gap:4},DA={display:"flex",width:"100%",alignItems:"center",justifyContent:"space-between",textAlign:"left",padding:"8px 12px",border:"none",borderRadius:6,fontSize:13,cursor:"pointer",transition:"background-color 120ms"},zA={flex:1,display:"flex",flexDirection:"column",minHeight:0,padding:"14px 20px"},NA={width:"100%",padding:"8px 10px",border:"1px solid #cbd5e1",borderRadius:6,marginBottom:12,fontSize:13,flexShrink:0,boxSizing:"border-box"},MA={flex:1,overflowY:"auto",minHeight:0},LA={fontSize:11,color:"#94a3b8",marginLeft:8,fontWeight:500},PA={display:"grid",gridTemplateColumns:"repeat(auto-fill, minmax(96px, 1fr))",gap:10},OA={display:"flex",flexDirection:"column",alignItems:"center",justifyContent:"center",gap:6,padding:"14px 6px",background:"#fff",border:"1px solid #e2e8f0",borderRadius:8,cursor:"pointer",fontSize:12,minHeight:84,transition:"border-color 120ms, box-shadow 120ms"},BA={display:"flex",flexDirection:"column",alignItems:"center",justifyContent:"center",gap:6,padding:"14px 6px",background:"#f8fafc",border:"1px dashed #cbd5e1",borderRadius:8,cursor:"not-allowed",fontSize:12,minHeight:84,color:"#94a3b8",opacity:.65},WA={fontSize:9,fontWeight:600,color:"#92400e",background:"#fef3c7",padding:"1px 6px",borderRadius:999,textTransform:"none",letterSpacing:0,lineHeight:1.6},jA={fontSize:22,lineHeight:1,color:"#475569"},HA={fontSize:12,fontWeight:500,color:"#0f172a",textAlign:"center",wordBreak:"break-word"},FA={fontFamily:'ui-monospace, SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace',fontSize:10,color:"#64748b",textAlign:"center",wordBreak:"break-all"},Hf={padding:24,color:"#64748b",fontSize:13,textAlign:"center"};function KA(e){const t=[],n=r=>{if(Array.isArray(r))for(const s of r)s&&typeof s=="object"&&t.push(s)};n(e.children);const o=e.responsive;if(o&&typeof o=="object")for(const r of Object.values(o))r&&typeof r=="object"&&n(r.children);return t}function Ff(e){const t=[],n=o=>{const r="isolatedState"in o&&o.isolatedState!=null,s=o.isolatedScopeId;(r||typeof s=="string"&&s.length>0)&&t.push({scopeId:typeof s=="string"?s:void 0,initialState:o.isolatedState&&typeof o.isolatedState=="object"?o.isolatedState:void 0});for(const a of KA(o))n(a)};if(Array.isArray(e))for(const o of e)o&&typeof o=="object"&&n(o);return t}const VA=["scroll","selector","wizard","slider"];function UA(e,t){const n=new Set;for(const o of e)o.scopeId&&n.add(o.scopeId);for(const o of t){n.add(o);for(const r of VA)n.add(`${o}-${r}`)}return Array.from(n)}function Kf(e){return typeof e=="string"&&(mr(e)||ns(e))}function Vf(e,t,n=o=>o){const[o,r]=u.useState(!1),[s,a]=u.useState(null),l=n(e),d=Kf(l)&&!o,c=u.useCallback(()=>{a(Kf(l)?l:null),r(!0)},[l]),p=u.useCallback(()=>{s!==null&&l!==s&&t(s),r(!1),a(null)},[s,l,t]);return{bound:d,replacing:o,original:s,beginReplace:c,restore:p}}const qA={display:"flex",flexDirection:"column",gap:4,padding:"8px 10px",border:"1px dashed #cbd5e1",borderRadius:6,background:"#f8fafc"},GA={fontFamily:"ui-monospace, SFMono-Regular, Menlo, monospace",fontSize:11,color:"#0f172a",wordBreak:"break-all"},Uf={alignSelf:"flex-start",background:"none",border:"none",padding:0,fontSize:11,color:"#2563eb",cursor:"pointer",textDecoration:"underline"};function qf({expression:e,t,onReplace:n,testIdPrefix:o}){return i.jsxs("div",{style:qA,children:[i.jsx("code",{"data-testid":`${o}-expression`,style:GA,children:e}),i.jsx("span",{style:{fontSize:11,color:"#64748b"},children:t("layout_editor.control.bound_value.notice")}),i.jsx("button",{type:"button","data-testid":`${o}-expression-replace`,onClick:n,style:Uf,children:t("layout_editor.control.bound_value.replace")})]})}function Gf({t:e,onRestore:t,testIdPrefix:n}){return i.jsx("button",{type:"button","data-testid":`${n}-expression-restore`,onClick:t,style:Uf,children:e("layout_editor.control.bound_value.restore")})}function YA(e,t,n){const o=t.label;return typeof o=="string"?o.startsWith("$t:")?n(o.slice(3)):o:e}const XA=new Set(["image","core-id","i18n-text","text"]);function _i({controlKey:e,control:t,node:n,t:o,onPatch:r,candidates:s,bindingCandidates:a,scope:l=jt}){const d=zn(t.widget),c=YA(e,t,o),p=u.useMemo(()=>Oo(n,t,l),[n,t,l]),f=t.widget==="i18n-text"&&t.apply?.type==="propValue",b=t.widget==="text"&&t.apply?.type==="propValue",g=p.darkReadonly===!0||l.colorScheme==="dark"&&t.apply!==void 0&&!ea(t.apply),m=t.apply,h=l.colorScheme==="dark"&&!g&&m?.type==="classToken"&&typeof m.tokenTemplate=="string"&&m.tokenTemplate.includes("{value}"),v=p.scopedValue===void 0&&p.baseFallback!==void 0,y=u.useCallback(k=>{if(g)return;const x=yn(n,t,k,l);r(x)},[n,t,r,l,g]),_=Vf(p.value,y),w=!XA.has(t.widget??"");return i.jsxs("div",{className:"g7le-control-row","data-testid":`g7le-control-${e}`,style:JA,children:[i.jsxs("span",{className:"g7le-control-label",style:ZA,children:[c,p.conflict&&i.jsx("span",{"data-testid":`g7le-control-conflict-${e}`,title:o("layout_editor.property_modal.value_conflict"),style:QA,children:"!"})]}),i.jsx("div",{className:"g7le-control-widget",style:{flex:1,minWidth:0,opacity:v?.45:1},"data-placeholder":v?"true":void 0,children:f?i.jsx(_t,{value:typeof p.value=="string"?p.value:"",onChange:k=>y(k),t:o,testidPrefix:`g7le-prop-i18n-${e}`,candidates:a,enableExpressionTree:!0,expressionTreeCollapsible:!0}):b?i.jsx(Ke,{value:typeof p.value=="string"?p.value:"",onChange:k=>y(k===""?void 0:k),t:o,testidPrefix:`g7le-prop-value-${e}`,candidates:a}):g?i.jsx("span",{"data-testid":`g7le-control-dark-readonly-${e}`,style:e6,children:o("layout_editor.property_modal.dark_code_only")}):w&&_.bound?i.jsx(qf,{expression:String(p.value),t:o,onReplace:_.beginReplace,testIdPrefix:`g7le-control-${e}`}):d?i.jsxs(i.Fragment,{children:[i.jsx(d,{control:t,value:p.value,onChange:y,t:o,candidates:s,bindingCandidates:a,freeValueDisabled:h}),w&&_.replacing&&_.original!==null&&i.jsx(Gf,{t:o,onRestore:_.restore,testIdPrefix:`g7le-control-${e}`})]}):i.jsx("span",{"data-testid":`g7le-control-unsupported-${e}`,style:RA,children:o("layout_editor.property_modal.unsupported_widget")})})]})}const JA={display:"flex",alignItems:"center",gap:12,padding:"6px 0",minHeight:32,minWidth:0},ZA={fontSize:12,color:"#475569",minWidth:88,display:"inline-flex",alignItems:"center",gap:4},QA={display:"inline-flex",width:14,height:14,borderRadius:7,background:"#f59e0b",color:"#fff",fontSize:10,alignItems:"center",justifyContent:"center",fontWeight:700},RA={fontSize:11,color:"#94a3b8",fontStyle:"italic"},e6={fontSize:11,color:"#94a3b8",fontStyle:"italic"};function xi(e,t,n){return typeof e=="string"?e.startsWith("$t:")?n(e.slice(3)):e:t}function t6(e,t){const n=e.props??{};return t.key in n?n[t.key]:t.default}function n6({spec:e,node:t,t:n,onPatchProp:o}){const r=e.groups??[];return i.jsx("div",{className:"g7le-composite-settings","data-testid":"g7le-composite-settings",style:r6,children:r.map((s,a)=>i.jsxs("div",{className:"g7le-settings-group",style:i6,children:[i.jsx("div",{style:s6,children:xi(s.label,n("layout_editor.property_modal.settings_group",{index:a+1}),n)}),(s.fields??[]).map(l=>i.jsxs("div",{className:"g7le-settings-field","data-testid":`g7le-setting-${l.key}`,style:a6,children:[i.jsx("span",{style:l6,children:xi(l.label,l.key,n)}),i.jsx("div",{style:{flex:1},children:o6(l,t,n,o)})]},l.key))]},a))})}function o6(e,t,n,o){const r=t6(t,e);switch(e.type){case"number":return i.jsx("input",{type:"number","data-testid":`g7le-setting-input-${e.key}`,value:typeof r=="number"?r:"",min:e.min,max:e.max,onChange:s=>o(e.key,s.target.value===""?void 0:Number(s.target.value)),style:Na});case"text":return i.jsx("input",{type:"text","data-testid":`g7le-setting-input-${e.key}`,value:typeof r=="string"?r:"",onChange:s=>o(e.key,s.target.value===""?void 0:s.target.value),style:Na});case"toggle":return i.jsx("input",{type:"checkbox","data-testid":`g7le-setting-input-${e.key}`,checked:r===!0,onChange:s=>o(e.key,s.target.checked)});case"checkbox-group":{const s=Array.isArray(r)?r:[];return i.jsx("div",{style:d6,"data-testid":`g7le-setting-input-${e.key}`,children:(e.options??[]).map(a=>{const l=s.includes(a.value);return i.jsxs("label",{style:c6,children:[i.jsx("input",{type:"checkbox",checked:l,onChange:d=>{const c=d.target.checked?[...s,a.value]:s.filter(p=>p!==a.value);o(e.key,c)}}),xi(a.label,String(a.value),n)]},String(a.value))})})}case"select":case"board-select":case"datasource-select":default:return i.jsxs("select",{"data-testid":`g7le-setting-input-${e.key}`,value:r==null?"":String(r),onChange:s=>{const a=s.target.value;if(a===""){o(e.key,void 0);return}const l=(e.options??[]).find(d=>String(d.value)===a);o(e.key,l?l.value:a)},style:Na,children:[i.jsx("option",{value:"",children:"—"}),(e.options??[]).map(s=>i.jsx("option",{value:String(s.value),children:xi(s.label,String(s.value),n)},String(s.value)))]})}}const r6={display:"flex",flexDirection:"column",gap:16},i6={display:"flex",flexDirection:"column",gap:6},s6={fontSize:12,fontWeight:700,color:"#0f172a",borderBottom:"1px solid #e2e8f0",paddingBottom:4},a6={display:"flex",alignItems:"center",gap:12,minHeight:30},l6={fontSize:12,color:"#475569",minWidth:96},Na={padding:"5px 8px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,width:"100%",boxSizing:"border-box"},d6={display:"flex",flexWrap:"wrap",gap:10},c6={display:"inline-flex",alignItems:"center",gap:4,fontSize:12,color:"#0f172a"},u6=["data_binding","isolatedScopeId","parentFormContextProp","skipBindingKeys","isolatedState","component_layout","sortable","itemTemplate"],p6=/\{\{|\$switch|\$get|\|\s*(date|number|truncate|currency|relative)\b/;function wi(e,t=0){return t>6?!1:typeof e=="string"?p6.test(e):Array.isArray(e)?e.some(n=>wi(n,t+1)):e&&typeof e=="object"?Object.values(e).some(n=>wi(n,t+1)):!1}function f6(e){if(id(e))return!1;const t=Zl(e);return t.length===0?!1:t.some(n=>n.isComplex)}function Yf(e){const t=[],n=e.props??{};for(const o of u6)o in e&&e[o]!==void 0?t.push({key:o,kind:"developer_prop"}):o in n&&n[o]!==void 0&&t.push({key:`props.${o}`,kind:"developer_prop"});typeof e.text=="string"?f6(e.text)&&t.push({key:"text",kind:"complex_expression"}):wi(e.text)&&t.push({key:"text",kind:"complex_expression"});for(const[o,r]of Object.entries(n))o==="style"||o==="className"||wi(r)&&t.push({key:`props.${o}`,kind:"complex_expression"});return t}function g6(e){return Yf(e).length>0}const h6=new Set(["permissions","blur_until_loaded","comment"]);function y6({node:e,advanced:t,t:n,onPatch:o,permissionCandidates:r}){const s=u.useMemo(()=>Yf(e),[e]),a=(l,d)=>{const c={...e};d===void 0?delete c[l]:c[l]=d,o(c)};return i.jsxs("div",{className:"g7le-advanced-props","data-testid":"g7le-advanced-props",style:b6,children:[t.map(l=>{if(!h6.has(l))return i.jsxs("div",{"data-testid":`g7le-advanced-deferred-${l}`,style:v6,children:[n("layout_editor.property_modal.advanced_deferred"),":",l]},l);if(l==="permissions")return i.jsxs("div",{className:"g7le-advanced-group","data-testid":"g7le-advanced-permissions",style:Ma,children:[i.jsx("div",{style:La,children:n("layout_editor.property_modal.advanced.permissions")}),i.jsx(Ss,{control:{widget:"tag-input"},value:e.permissions,onChange:d=>a("permissions",d),t:n,candidates:r})]},l);if(l==="blur_until_loaded"){const d=e.blur_until_loaded===!0||e.blur_until_loaded!=null&&e.blur_until_loaded!==!1;return i.jsxs("div",{className:"g7le-advanced-group","data-testid":"g7le-advanced-blur",style:Ma,children:[i.jsx("div",{style:La,children:n("layout_editor.property_modal.advanced.blur_until_loaded")}),i.jsxs("label",{style:{display:"inline-flex",alignItems:"center",gap:6,fontSize:12},children:[i.jsx("input",{type:"checkbox","data-testid":"g7le-advanced-blur-toggle",checked:!!d,onChange:c=>a("blur_until_loaded",c.target.checked?!0:void 0)}),n("layout_editor.property_modal.advanced.blur_hint")]})]},l)}return i.jsxs("div",{className:"g7le-advanced-group","data-testid":"g7le-advanced-comment",style:Ma,children:[i.jsx("div",{style:La,children:n("layout_editor.property_modal.advanced.comment")}),i.jsx("input",{type:"text","data-testid":"g7le-advanced-comment-input",value:typeof e.comment=="string"?e.comment:"",onChange:d=>a("comment",d.target.value===""?void 0:d.target.value),style:m6})]},l)}),s.length>0&&i.jsxs("div",{className:"g7le-advanced-preserved","data-testid":"g7le-advanced-preserved",style:_6,children:[i.jsx("div",{style:x6,children:n("layout_editor.property_modal.advanced_preserved_title",{count:s.length})}),i.jsx("ul",{style:w6,children:s.map(l=>i.jsxs("li",{"data-testid":`g7le-advanced-preserved-${l.key}`,style:k6,children:[i.jsx("code",{style:{fontSize:11},children:l.key}),i.jsx("span",{style:{fontSize:11,color:"#94a3b8"},children:l.kind==="developer_prop"?n("layout_editor.property_modal.advanced_kind_developer"):n("layout_editor.property_modal.advanced_kind_expression")})]},l.key))})]})]})}const b6={display:"flex",flexDirection:"column",gap:14},Ma={display:"flex",flexDirection:"column",gap:6},La={fontSize:12,fontWeight:700,color:"#0f172a"},m6={padding:"5px 8px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,width:"100%",boxSizing:"border-box"},v6={fontSize:11,color:"#94a3b8",fontStyle:"italic"},_6={borderTop:"1px dashed #cbd5e1",paddingTop:10,marginTop:4},x6={fontSize:11,color:"#b45309",marginBottom:6},w6={listStyle:"none",margin:0,padding:0,display:"flex",flexDirection:"column",gap:4},k6={display:"flex",justifyContent:"space-between",gap:8,padding:"3px 0"},S6={onClick:"click",onHover:"mouseenter",onChange:"change",onSubmit:"submit",onMount:"mount",onUnmount:"unmount",onFocus:"focus",onBlur:"blur"};function Pa(e){return S6[e]??e.replace(/^on/,"").toLowerCase()}function Xf(e){const t=e.actions;if(Array.isArray(t))return t.filter(r=>!!r&&typeof r=="object");const n=e.props??{};if(Array.isArray(n.actions))return n.actions.filter(r=>!!r&&typeof r=="object");const o=n.events;if(o&&typeof o=="object"){const r=[];for(const[s,a]of Object.entries(o)){if(!Array.isArray(a))continue;const l=Pa(s);for(const d of a)d&&typeof d=="object"&&r.push("type"in d?d:{type:l,...d})}return r}return[]}function C6(e,t){const n=Pa(t);return Xf(e).filter(o=>o.type===n||t==="onClick"&&o.type===void 0)}function $6(e,t,n){const o=Pa(t),r=Xf(e).filter(d=>!(d.type===o||t==="onClick"&&d.type===void 0)),s=n.map(d=>"type"in d?d:{type:o,...d}),a={...e};if(a.props&&typeof a.props=="object"){const d={...a.props};let c=!1;"actions"in d&&(delete d.actions,c=!0),"events"in d&&(delete d.events,c=!0),c&&(a.props=d)}const l=[...r,...s];return l.length===0?delete a.actions:a.actions=l,a}function E6({node:e,spec:t,capability:n,t:o,onPatchNode:r,pageCandidates:s,dataSourceCandidates:a,stateKeyCandidates:l,bindingCandidates:d}){const c=t?.actionRecipes,p=u.useMemo(()=>({pageCandidates:s,dataSourceCandidates:a,stateKeyCandidates:l,bindingCandidates:d}),[s,a,l,d]),f=Array.isArray(n?.events)?n.events:[];return f.length===0?i.jsx("div",{"data-testid":"g7le-action-no-events",style:Jf,children:o("layout_editor.action.no_events")}):!c||Object.keys(c).length===0?i.jsx("div",{"data-testid":"g7le-action-no-recipes",style:Jf,children:o("layout_editor.action.no_recipes")}):i.jsx("div",{className:"g7le-action-editor","data-testid":"g7le-action-editor",style:{display:"flex",flexDirection:"column",gap:14},children:f.map(b=>i.jsxs("div",{className:"g7le-action-event-slot","data-testid":`g7le-action-event-${b}`,children:[i.jsx("div",{style:I6,children:o(`layout_editor.action.event.${b}`)}),i.jsx(Yt,{actions:C6(e,b),onChange:g=>r($6(e,b,g)),t:o,recipes:c,candidatePools:p,actionChipCandidates:t?.actionChipCandidates??null,addContext:"component",showSourceBadge:!0,testIdPrefix:`g7le-action-slot-${b}`})]},b))})}const Jf={fontSize:12,color:"#94a3b8",padding:"16px 0",textAlign:"center"},I6={fontSize:12,fontWeight:700,color:"#475569",marginBottom:6};function Oa(e,t,n){if(!t)return n;const o=e(t);return!o||o===t||o.startsWith("$t:")?n:o}function A6(e,t){return Oa(t,e.labelKey,e.path||e.sourceId)}function T6({node:e,spec:t,candidates:n,t:o,onPatchNode:r}){const a=(e.props??{})[t.propKey],l=typeof a=="string"?a:void 0,d=u.useMemo(()=>Vt(a),[a]),c=l!==void 0&&l!==""&&d===null,[p,f]=u.useState(!1),[b,g]=u.useState(""),m=Oa(o,t.label,t.propKey),h=o(t.shape==="array"?"layout_editor.binding.shape_array":t.shape==="object"?"layout_editor.binding.shape_object":"layout_editor.binding.shape_scalar"),v=u.useMemo(()=>Zi(n,t.shape).map(x=>({...x,resolvedLabel:A6(x,o)})),[n,t.shape,o]),y=u.useMemo(()=>Qi(v,b),[v,b]),_=k=>{const x={...e.props??{}};k===void 0?delete x[t.propKey]:x[t.propKey]=k,r({...e,props:x})},w=k=>{_(In(k.sourceId,k.path,t.shape)),f(!1),g("")};return i.jsxs("div",{"data-testid":`g7le-binding-row-${t.propKey}`,style:M6,children:[i.jsxs("div",{style:L6,children:[i.jsx("span",{style:{fontWeight:600,fontSize:12},children:m}),i.jsx("span",{style:P6,children:h}),t.required&&i.jsx("span",{"data-testid":`g7le-binding-required-${t.propKey}`,style:O6,children:o("layout_editor.binding.required")})]}),c?i.jsxs("div",{"data-testid":`g7le-binding-complex-${t.propKey}`,style:H6,children:[o("layout_editor.binding.complex"),i.jsx("code",{style:Zf,children:l})]}):d?i.jsxs("div",{style:B6,children:[i.jsx("code",{"data-testid":`g7le-binding-expr-${t.propKey}`,style:Zf,children:l}),i.jsx("button",{type:"button","data-testid":`g7le-binding-clear-${t.propKey}`,onClick:()=>_(void 0),style:j6,children:o("layout_editor.binding.clear")})]}):i.jsx("div",{"data-testid":`g7le-binding-empty-${t.propKey}`,style:W6,children:o("layout_editor.binding.not_connected")}),d&&(t.shape==="array"||t.shape==="object")&&t.itemFields&&t.itemFields.length>0&&i.jsxs("div",{style:F6,children:[o("layout_editor.binding.item_fields"),": ",t.itemFields.join(", ")]}),!c&&i.jsxs("div",{style:{marginTop:4},children:[i.jsxs("button",{type:"button","data-testid":`g7le-binding-search-toggle-${t.propKey}`,onClick:()=>f(k=>!k),style:K6,children:["🔍 ",o("layout_editor.binding.search")]}),p&&i.jsxs("div",{"data-testid":`g7le-binding-picker-${t.propKey}`,style:V6,children:[i.jsx("input",{type:"text",autoFocus:!0,"data-testid":`g7le-binding-search-input-${t.propKey}`,value:b,placeholder:o("layout_editor.binding.search_placeholder"),onChange:k=>g(k.target.value),style:U6}),i.jsx("div",{style:q6,children:y.length===0?i.jsx("div",{style:G6,children:o("layout_editor.binding.no_results")}):y.map(k=>i.jsxs("button",{type:"button","data-testid":`g7le-binding-candidate-${k.expression}`,onClick:()=>w(k),style:Y6,children:[i.jsxs("span",{style:{display:"flex",flexDirection:"column",alignItems:"flex-start"},children:[i.jsx("span",{style:{fontSize:12},children:k.resolvedLabel}),i.jsxs("span",{style:X6,children:[Oa(o,k.groupLabelKey,k.sourceId)," · ",k.path||k.sourceId]})]}),i.jsx("span",{style:J6,children:k.preview})]},k.expression))})]})]})]})}function D6({node:e,dataProps:t,candidates:n,t:o,onPatchNode:r}){return!Array.isArray(t)||t.length===0?null:i.jsxs("div",{"data-testid":"g7le-data-binding-section",style:z6,children:[i.jsxs("div",{style:N6,children:["🔗 ",o("layout_editor.binding.section_title")]}),t.map(s=>i.jsx(T6,{node:e,spec:s,candidates:n,t:o,onPatchNode:r},s.propKey)),i.jsx("div",{style:Z6})]})}const z6={display:"flex",flexDirection:"column",marginBottom:8},N6={fontSize:12,fontWeight:700,color:"#0f172a",marginBottom:6},M6={display:"flex",flexDirection:"column",gap:4,padding:"8px 0"},L6={display:"flex",alignItems:"center",gap:6},P6={fontSize:10,padding:"1px 6px",borderRadius:4,background:"#e0e7ff",color:"#3730a3"},O6={fontSize:10,padding:"1px 6px",borderRadius:4,background:"#fef3c7",color:"#92400e"},B6={display:"flex",alignItems:"center",gap:6},W6={fontSize:11,color:"#94a3b8"},Zf={fontSize:11,fontFamily:"monospace",background:"#f1f5f9",padding:"2px 6px",borderRadius:4,color:"#0f172a",wordBreak:"break-all"},j6={fontSize:11,border:"1px solid #e2e8f0",borderRadius:4,background:"#fff",color:"#dc2626",padding:"2px 8px",cursor:"pointer"},H6={display:"flex",flexDirection:"column",gap:2,fontSize:11,color:"#64748b"},F6={fontSize:10,color:"#94a3b8"},K6={fontSize:11,border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",color:"#334155",padding:"4px 10px",cursor:"pointer",textAlign:"left",width:"100%"},V6={marginTop:4,border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",padding:6},U6={width:"100%",padding:"5px 8px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,boxSizing:"border-box"},q6={marginTop:4,maxHeight:200,overflowY:"auto",display:"flex",flexDirection:"column",gap:2},G6={fontSize:11,color:"#94a3b8",padding:"8px 0",textAlign:"center"},Y6={display:"flex",alignItems:"center",justifyContent:"space-between",gap:8,border:"none",background:"transparent",cursor:"pointer",padding:"6px 8px",borderRadius:4,textAlign:"left"},X6={fontSize:10,color:"#94a3b8"},J6={fontSize:10,color:"#64748b",fontFamily:"monospace",whiteSpace:"nowrap"},Z6={borderTop:"1px solid #e2e8f0",marginTop:4};function Q6({node:e,candidates:t,t:n,onPatchNode:o,templateIdentifier:r}){const s=typeof e.text=="string"?e.text:"",{state:a}=Qe(),l=r??a.templateIdentifier,d=a.selectedRoute?.layoutName??null,c=a.locale,[p,f]=u.useState(!1),b=/^\s*\$t:custom\.[A-Za-z0-9._-]+\s*$/.test(s),m=id(s)&&(vt(s)?.key.startsWith("custom.")??!1)||b,h=u.useMemo(()=>m?vt(s):null,[m,s]),v=u.useMemo(()=>m?[]:Zl(s),[m,s]),y=D=>{o({...e,text:D})},_=()=>{if(!(typeof window>"u"))try{window.dispatchEvent(new CustomEvent(kt,{detail:{templateIdentifier:l,locale:c}}))}catch{}},w=(D,E)=>{const I=In(E.sourceId,E.path,"scalar");y(Xb(s,D,I))},k=D=>{const E=vt(s);y(ko(s,D));const I=E?.key;l&&I&&Zd(l,I,D).then(_)},x=D=>{const E=n(D);return E&&E!==D&&!E.startsWith("$t:")?E:""},$=D=>D.replace(/\$t:[a-zA-Z0-9._-]+/g,E=>x(E.slice(3))).replace(/\s+/g," ").trim(),C=D=>{p||(f(!0),ys(l,d,c,s,s.length,D.sourceId,D.path,"scalar",$,E=>n(E)).then(E=>{E.kind==="ok"&&(y(E.text),_())}).finally(()=>f(!1)))},S=D=>{p||!h&&!b||(f(!0),Ar(l,c,s,Number.MAX_SAFE_INTEGER,D.sourceId,D.path,"scalar").then(E=>{E.kind==="ok"&&(y(E.text),_())}).finally(()=>f(!1)))};if(m){const D=h?.params??[];return i.jsxs("div",{"data-testid":"g7le-inline-binding-section",style:Qf,children:[i.jsxs("div",{style:Rf,children:["🔗 ",n("layout_editor.inline_binding.section_title")]}),D.length===0?i.jsx("div",{"data-testid":"g7le-inline-binding-none",style:ng,children:n("layout_editor.inline_binding.none")}):D.map(E=>i.jsx("div",{"data-testid":`g7le-inline-binding-param-${E.name}`,style:eg,children:E.parsed===null?i.jsxs("div",{"data-testid":`g7le-inline-binding-param-complex-${E.name}`,style:eT,children:[n("layout_editor.inline_binding.complex"),i.jsx("code",{style:Ba,children:E.expression})]}):i.jsxs(i.Fragment,{children:[i.jsxs("div",{style:tg,children:[i.jsx("code",{"data-testid":`g7le-inline-binding-param-expr-${E.name}`,style:Ba,children:E.expression}),i.jsx("button",{type:"button","data-testid":`g7le-inline-binding-param-clear-${E.name}`,onClick:()=>k(E.name),style:R6,children:n("layout_editor.inline_binding.clear")})]}),i.jsx($t,{candidates:t,t:n,onSelect:I=>w(E.name,I),testIdSuffix:`param-${E.name}`})]})},E.name)),i.jsxs("div",{"data-testid":"g7le-inline-binding-param-append",style:og,children:[i.jsx("div",{style:Wa,children:n("layout_editor.inline_binding.append_hint")}),i.jsx($t,{candidates:t,t:n,onSelect:S,testIdSuffix:"param-append"})]}),i.jsx("div",{"data-testid":"g7le-inline-binding-param-hint",style:Wa,children:n("layout_editor.inline_binding.param_hint")}),i.jsx("div",{style:rg})]})}return i.jsxs("div",{"data-testid":"g7le-inline-binding-section",style:Qf,children:[i.jsxs("div",{style:Rf,children:["🔗 ",n("layout_editor.inline_binding.section_title")]}),v.length===0?i.jsx("div",{"data-testid":"g7le-inline-binding-none",style:ng,children:n("layout_editor.inline_binding.none")}):v.map(D=>i.jsx("div",{"data-testid":`g7le-inline-binding-row-${D.bindingIndex}`,style:eg,children:i.jsx("div",{style:tg,children:i.jsx("code",{"data-testid":`g7le-inline-binding-expr-${D.bindingIndex}`,style:Ba,children:D.expression})})},D.bindingIndex)),i.jsxs("div",{"data-testid":"g7le-inline-binding-append",style:og,children:[i.jsx("div",{style:Wa,children:n("layout_editor.inline_binding.append_hint")}),i.jsx($t,{candidates:t,t:n,onSelect:C,testIdSuffix:"append"})]}),i.jsx("div",{style:rg})]})}const Qf={display:"flex",flexDirection:"column",marginBottom:8},Rf={fontSize:12,fontWeight:700,color:"#0f172a",marginBottom:6},eg={display:"flex",flexDirection:"column",gap:4,padding:"8px 0"},tg={display:"flex",alignItems:"center",gap:6},ng={fontSize:11,color:"#94a3b8"},Ba={fontSize:11,fontFamily:"monospace",background:"#f1f5f9",padding:"2px 6px",borderRadius:4,color:"#0f172a",wordBreak:"break-all"},R6={fontSize:11,border:"1px solid #e2e8f0",borderRadius:4,background:"#fff",color:"#dc2626",padding:"2px 8px",cursor:"pointer"},eT={display:"flex",flexDirection:"column",gap:2,fontSize:11,color:"#64748b"},og={display:"flex",flexDirection:"column",gap:2,padding:"8px 0 0"},Wa={fontSize:11,color:"#475569",fontWeight:600},rg={borderTop:"1px solid #e2e8f0",marginTop:4};function ig(e,t,n){if(!t)return n;const o=e(t);return!o||o===t||o.startsWith("$t:")?n:o}function tT(e,t){return ig(t,e.labelKey,e.path||e.sourceId)}function nT(e){const t=e.iteration;return t&&typeof t=="object"?t:null}function oT({node:e,candidates:t,t:n,onPatchNode:o}){const r=nT(e),s=r?.source,a=typeof s=="string"?s:void 0,l=u.useMemo(()=>Vt(s),[s]),d=a!==void 0&&a!==""&&l===null,[c,p]=u.useState(!1),[f,b]=u.useState(""),g=u.useMemo(()=>Zi(t,"array").map(k=>({...k,resolvedLabel:tT(k,n)})),[t,n]),m=u.useMemo(()=>Qi(g,f),[g,f]);if(!r)return null;const h=typeof r.item_var=="string"?r.item_var:void 0,v=typeof r.index_var=="string"?r.index_var:void 0,y=w=>{const k={...r};w===void 0?delete k.source:k.source=w,o({...e,iteration:k})},_=w=>{y(In(w.sourceId,w.path,"array")),p(!1),b("")};return i.jsxs("div",{"data-testid":"g7le-iteration-binding-section",style:rT,children:[i.jsxs("div",{style:iT,children:["🔁 ",n("layout_editor.binding.iteration_title")]}),i.jsx("div",{style:sT,children:n("layout_editor.binding.iteration_hint")}),i.jsxs("div",{style:aT,children:[i.jsxs("div",{style:lT,children:[i.jsx("span",{style:{fontWeight:600,fontSize:12},children:n("layout_editor.binding.iteration_label")}),i.jsx("span",{style:dT,children:n("layout_editor.binding.shape_array")})]}),d?i.jsxs("div",{"data-testid":"g7le-iteration-binding-complex",style:fT,children:[n("layout_editor.binding.complex"),i.jsx("code",{style:sg,children:a})]}):l?i.jsxs("div",{style:cT,children:[i.jsx("code",{"data-testid":"g7le-iteration-binding-expr",style:sg,children:a}),i.jsx("button",{type:"button","data-testid":"g7le-iteration-binding-clear",onClick:()=>y(void 0),style:pT,children:n("layout_editor.binding.clear")})]}):i.jsx("div",{"data-testid":"g7le-iteration-binding-empty",style:uT,children:n("layout_editor.binding.not_connected")}),(h||v)&&i.jsxs("div",{"data-testid":"g7le-iteration-vars",style:gT,children:[h&&i.jsxs("span",{children:[n("layout_editor.binding.iteration_item_var"),": ",i.jsx("code",{style:ag,children:h})]}),v&&i.jsxs("span",{children:[n("layout_editor.binding.iteration_index_var"),": ",i.jsx("code",{style:ag,children:v})]})]}),!d&&i.jsxs("div",{style:{marginTop:4},children:[i.jsxs("button",{type:"button","data-testid":"g7le-iteration-binding-search-toggle",onClick:()=>p(w=>!w),style:hT,children:["🔍 ",n("layout_editor.binding.search")]}),c&&i.jsxs("div",{"data-testid":"g7le-iteration-binding-picker",style:yT,children:[i.jsx("input",{type:"text",autoFocus:!0,"data-testid":"g7le-iteration-binding-search-input",value:f,placeholder:n("layout_editor.binding.search_placeholder"),onChange:w=>b(w.target.value),style:bT}),i.jsx("div",{style:mT,children:m.length===0?i.jsx("div",{style:vT,children:n("layout_editor.binding.no_results")}):m.map(w=>i.jsxs("button",{type:"button","data-testid":`g7le-iteration-binding-candidate-${w.expression}`,onClick:()=>_(w),style:_T,children:[i.jsxs("span",{style:{display:"flex",flexDirection:"column",alignItems:"flex-start"},children:[i.jsx("span",{style:{fontSize:12},children:w.resolvedLabel}),i.jsxs("span",{style:xT,children:[ig(n,w.groupLabelKey,w.sourceId)," · ",w.path||w.sourceId]})]}),i.jsx("span",{style:wT,children:w.preview})]},w.expression))})]})]})]}),i.jsx("div",{style:kT})]})}const rT={display:"flex",flexDirection:"column",marginBottom:8},iT={fontSize:12,fontWeight:700,color:"#0f172a",marginBottom:4},sT={fontSize:10,color:"#94a3b8",marginBottom:6,lineHeight:1.4},aT={display:"flex",flexDirection:"column",gap:4,padding:"8px 0"},lT={display:"flex",alignItems:"center",gap:6},dT={fontSize:10,padding:"1px 6px",borderRadius:4,background:"#e0e7ff",color:"#3730a3"},cT={display:"flex",alignItems:"center",gap:6},uT={fontSize:11,color:"#94a3b8"},sg={fontSize:11,fontFamily:"monospace",background:"#f1f5f9",padding:"2px 6px",borderRadius:4,color:"#0f172a",wordBreak:"break-all"},pT={fontSize:11,border:"1px solid #e2e8f0",borderRadius:4,background:"#fff",color:"#dc2626",padding:"2px 8px",cursor:"pointer"},fT={display:"flex",flexDirection:"column",gap:2,fontSize:11,color:"#64748b"},gT={display:"flex",gap:12,fontSize:10,color:"#94a3b8",marginTop:2},ag={fontSize:10,fontFamily:"monospace",background:"#f1f5f9",padding:"1px 4px",borderRadius:3,color:"#475569"},hT={fontSize:11,border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",color:"#334155",padding:"4px 10px",cursor:"pointer",textAlign:"left",width:"100%"},yT={marginTop:4,border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",padding:6},bT={width:"100%",padding:"5px 8px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,boxSizing:"border-box"},mT={marginTop:4,maxHeight:200,overflowY:"auto",display:"flex",flexDirection:"column",gap:2},vT={fontSize:11,color:"#94a3b8",padding:"8px 0",textAlign:"center"},_T={display:"flex",alignItems:"center",justifyContent:"space-between",gap:8,border:"none",background:"transparent",cursor:"pointer",padding:"6px 8px",borderRadius:4,textAlign:"left"},xT={fontSize:10,color:"#94a3b8"},wT={fontSize:10,color:"#64748b",fontFamily:"monospace",whiteSpace:"nowrap"},kT={borderTop:"1px solid #e2e8f0",marginTop:4},ST=new Set(["Span","P","H1","H2","H3","H4","H5","H6","A","Button","Li","Label"]);function CT(e,t){if(!e||e.iteration&&typeof e.iteration=="object")return!1;const n=t?.textBinding;if(n===!1)return!1;if(n===!0)return!0;const o=typeof e.name=="string"?e.name:"";return ST.has(o)?!0:typeof e.text=="string"}const mn={direction:"flexDirection",justify:"flexJustify",align:"flexAlign",wrap:"flexWrap",gap:"flexGap",enable:"flexEnable"},ki={grow:"flexItemGrow",selfAlign:"flexItemAlign",order:"flexItemOrder"};function lg(e){const t=e?.flexEditor;return t==="container"||t==="item"||t==="auto"?t:null}function $T(e){if(!e||typeof window>"u"||typeof window.getComputedStyle!="function")return!1;const t=(window.getComputedStyle(e).display??"").toLowerCase();return t==="flex"||t==="inline-flex"}function ET(e,t,n=jt){if(!e||!t)return!1;const o=Oo(e,t,n),r=t.options,s=Array.isArray(r)?t.onValue??r[0]?.value:void 0,a=l=>s!==void 0?l===s:l!==void 0&&l!==!1;return n.breakpoint!=="base"?a(o.scopedRawValue):o.matched&&a(o.value)}function IT(e,t,n=!1,o=!1,r=jt){if(e==="container")return{showContainer:!0,showEnableButton:!1,showDisableButton:!1};if(e==="item")return{showContainer:!1,showEnableButton:!1,showDisableButton:!1};if(e==="auto"){const s=r.breakpoint==="base";return!o&&!s?{showContainer:!1,showEnableButton:!1,showDisableButton:!1}:(o?n:$T(t))?{showContainer:!0,showEnableButton:!1,showDisableButton:o}:{showContainer:!1,showEnableButton:!0,showDisableButton:!1}}return{showContainer:!1,showEnableButton:!1,showDisableButton:!1}}const AT={[mn.direction]:"layout_editor.flex.direction",[mn.justify]:"layout_editor.flex.justify",[mn.align]:"layout_editor.flex.align",[mn.wrap]:"layout_editor.flex.wrap",[mn.gap]:"layout_editor.flex.gap"},TT={[ki.grow]:"layout_editor.flex.item_grow",[ki.selfAlign]:"layout_editor.flex.item_align",[ki.order]:"layout_editor.flex.item_order"};function DT({node:e,spec:t,capability:n,t:o,onPatchNode:r,liveElement:s,scope:a=jt}){const l=lg(n),d=Ct(t,mn.enable),c=ET(e,d,a),{showContainer:p,showEnableButton:f,showDisableButton:b}=IT(l,s,c,!!d,a),g=Object.values(mn).filter(x=>x!==mn.enable&&Ct(t,x)),m=Object.values(ki).filter(x=>Ct(t,x)),h=d?.options,v=d?.onValue??h?.[0]?.value??!0,y=Array.isArray(h)&&h.length>=2?h[1].value:void 0,_=()=>{if(!d)return;const x=yn(e,d,v,a);r(x)},w=()=>{if(!d)return;const x=a.breakpoint!=="base"&&y!==void 0?y:void 0,$=yn(e,d,x,a);r($)},k=(x,$,C)=>i.jsx("div",{"data-testid":C,style:{display:"flex",flexDirection:"column"},children:x.map(S=>{const D=Ct(t,S);if(!D)return null;const E=D.label?D:{...D,label:`$t:${$[S]}`};return i.jsx(_i,{controlKey:S,control:E,node:e,t:o,onPatch:r,scope:a},S)})});return i.jsxs("div",{className:"g7le-flex-editor","data-testid":"g7le-flex-editor",style:{display:"flex",flexDirection:"column",gap:12},children:[f&&i.jsx("button",{type:"button","data-testid":"g7le-flex-enable",onClick:_,style:zT,children:o("layout_editor.flex.make_flex")}),p&&g.length>0&&i.jsxs("div",{className:"g7le-flex-container-section","data-testid":"g7le-flex-container-section",children:[i.jsx("div",{style:dg,children:o("layout_editor.flex.container_title")}),k(g,AT,"g7le-flex-container-controls"),b&&i.jsx("button",{type:"button","data-testid":"g7le-flex-disable",onClick:w,style:NT,children:o("layout_editor.flex.unmake_flex")})]}),l==="item"&&m.length>0&&i.jsxs("div",{className:"g7le-flex-item-section","data-testid":"g7le-flex-item-section",children:[i.jsx("div",{style:dg,children:o("layout_editor.flex.item_title")}),k(m,TT,"g7le-flex-item-controls")]}),!p&&!f&&l!=="item"&&i.jsx("div",{"data-testid":"g7le-flex-empty",style:MT,children:o("layout_editor.flex.not_flex")})]})}const dg={fontSize:12,fontWeight:700,color:"#0f172a",marginBottom:6},zT={padding:"8px 12px",fontSize:12,border:"1px dashed #2563eb",borderRadius:6,background:"#eff6ff",color:"#2563eb",cursor:"pointer"},NT={marginTop:8,padding:"6px 10px",fontSize:12,border:"1px solid #e2e8f0",borderRadius:6,background:"#f8fafc",color:"#64748b",cursor:"pointer"},MT={fontSize:12,color:"#94a3b8",padding:"16px 0",textAlign:"center"},cg=["desktop","tablet","mobile","portable"],ja=1280,LT=820,PT=390,OT=1920,BT=320;function WT(e){const t=[],n=new Set,o=r=>{if(!r||typeof r!="object")return;const s=r.responsive;if(s&&typeof s=="object")for(const a of Object.keys(s)){n.has(a)||(n.add(a),t.push(a));const l=s[a]?.children;if(Array.isArray(l))for(const d of l)o(d)}if(Array.isArray(r.children))for(const a of r.children)o(a)};if(Array.isArray(e))for(const r of e)o(r);return t}function jT(e){const t=[...cg],n=new Set(cg);for(const o of WT(e))!n.has(o)&&!t.includes(o)&&t.push(o);return t}function HT(e,t){const n=e?.responsive;if(!n||typeof n!="object")return[];const o=[];for(const r of Object.keys(n)){const s=n[r];!s||!Array.isArray(s.children)||r!==t&&o.push(r)}return o}function FT(e){switch(e){case"desktop":return ja;case"tablet":return LT;case"mobile":return PT}const t=e in Po?Po[e]:Pn.parseRange(e);return!t||!Number.isFinite(t.max)?ja:Math.round(Math.max(BT,Math.min(OT,t.max)))}function ug(){const t=Kt()?.document?.raw?.components;return u.useMemo(()=>jT(t??null),[t])}const KT={desktop:"layout_editor.property_modal.scope.device_desktop",tablet:"layout_editor.property_modal.scope.device_tablet",mobile:"layout_editor.property_modal.scope.device_mobile",portable:"layout_editor.property_modal.scope.device_portable"},VT=[{key:"base",labelKey:"layout_editor.property_modal.scope.scheme_light"},{key:"dark",labelKey:"layout_editor.property_modal.scope.scheme_dark"}];function pg({scope:e,onChange:t,node:n,t:o,showColorScheme:r=!0,onClearScope:s}){const[a,l]=u.useState(!1),[d,c]=u.useState(""),[p,f]=u.useState(""),[b,g]=u.useState(null),m=ug(),h=u.useMemo(()=>{const w=["base",...m],k=new Set(w);for(const x of Object.keys(n.responsive??{}))x!=="base"&&!k.has(x)&&(k.add(x),w.push(x));return e.breakpoint!=="base"&&!k.has(e.breakpoint)&&w.push(e.breakpoint),w},[m,n.responsive,e.breakpoint]),v=w=>t({...e,colorScheme:w}),y=w=>t({...e,breakpoint:w}),_=()=>{const w=d.trim(),k=p.trim();if(w===""&&k===""){g(o("layout_editor.property_modal.scope.custom_invalid"));return}const x=`${w}-${k}`;if(!jC(x)){g(o("layout_editor.property_modal.scope.custom_invalid"));return}g(null),l(!1),c(""),f(""),y(x)};return i.jsxs("div",{className:"g7le-style-scope-tabs","data-testid":"g7le-style-scope-tabs",style:UT,children:[r&&i.jsx("div",{className:"g7le-style-scope-scheme","data-testid":"g7le-style-scope-scheme",role:"tablist","aria-label":o("layout_editor.property_modal.scope.scheme_light"),style:fg,children:VT.map(w=>{const k=e.colorScheme===w.key;return i.jsxs("button",{type:"button",role:"tab","aria-selected":k,"data-testid":`g7le-style-scheme-${w.key}`,"data-active":k?"true":"false",onClick:()=>v(w.key),style:gg(k),children:[o(w.labelKey),w.key==="dark"&&na(n,{colorScheme:"dark",breakpoint:e.breakpoint})&&i.jsx("span",{"data-testid":"g7le-style-scheme-dark-dot",style:hg})]},w.key)})}),i.jsxs("div",{className:"g7le-style-scope-device","data-testid":"g7le-style-scope-device",role:"tablist","aria-label":o("layout_editor.property_modal.scope.device_base"),style:fg,children:[h.map(w=>{const k=e.breakpoint===w,x=w==="base"||Nu(w),$=w==="base"?"layout_editor.property_modal.scope.device_base":KT[w],C=$?o($):w,S=x?`g7le-style-bp-${w}`:`g7le-style-bp-custom-${w}`;return i.jsxs("button",{type:"button",role:"tab","aria-selected":k,"data-testid":S,"data-active":k?"true":"false",onClick:()=>y(w),style:gg(k),children:[C,na(n,{colorScheme:"base",breakpoint:w})&&i.jsx("span",{"data-testid":`${S}-dot`,style:hg})]},String(w))}),i.jsxs("button",{type:"button","data-testid":"g7le-style-bp-add-custom",onClick:()=>l(w=>!w),style:YT,children:["+ ",o("layout_editor.property_modal.scope.add_custom")]})]}),a&&i.jsxs("div",{className:"g7le-style-scope-custom-input","data-testid":"g7le-style-scope-custom-input",style:qT,children:[i.jsx("input",{type:"number","data-testid":"g7le-style-custom-min",placeholder:o("layout_editor.property_modal.scope.custom_min"),value:d,onChange:w=>c(w.target.value),style:yg}),i.jsx("span",{style:{color:"#94a3b8"},children:"—"}),i.jsx("input",{type:"number","data-testid":"g7le-style-custom-max",placeholder:o("layout_editor.property_modal.scope.custom_max"),value:p,onChange:w=>f(w.target.value),style:yg}),i.jsx("button",{type:"button","data-testid":"g7le-style-custom-confirm",onClick:_,style:XT,children:o("layout_editor.property_modal.scope.add_custom")})]}),b&&i.jsx("div",{"data-testid":"g7le-style-custom-error",style:JT,children:b}),e.breakpoint!=="base"&&!Nu(e.breakpoint)&&i.jsx("div",{"data-testid":"g7le-style-custom-priority-note",style:ZT,children:o("layout_editor.property_modal.scope.custom_priority_note")}),s&&na(n,e)&&i.jsxs("button",{type:"button","data-testid":"g7le-style-scope-reset",onClick:s,style:GT,children:["↺ ",o("layout_editor.property_modal.scope.reset_to_base")]})]})}const UT={display:"flex",flexDirection:"column",gap:0,padding:"0 0 8px",borderBottom:"1px solid #e2e8f0",marginBottom:8},qT={display:"flex",gap:6,alignItems:"center",marginTop:4},fg={display:"flex",gap:2,flexWrap:"wrap",borderBottom:"1px solid #e2e8f0",margin:"0 -16px",padding:"0 12px"};function gg(e){return{display:"inline-flex",alignItems:"center",gap:4,padding:"6px 12px",fontSize:12,border:"none",borderBottom:`2px solid ${e?"#2563eb":"transparent"}`,background:"transparent",color:e?"#2563eb":"#64748b",cursor:"pointer",fontWeight:e?700:500,marginBottom:-1}}const hg={display:"inline-block",width:5,height:5,borderRadius:3,background:"#f59e0b"},GT={alignSelf:"flex-start",marginTop:2,padding:"3px 8px",fontSize:10,border:"1px solid #cbd5e1",borderRadius:6,background:"#f8fafc",color:"#475569",cursor:"pointer"},YT={padding:"6px 12px",fontSize:11,border:"none",borderBottom:"2px dashed #cbd5e1",background:"transparent",color:"#475569",cursor:"pointer",marginBottom:-1},yg={width:70,padding:"4px 6px",fontSize:11,border:"1px solid #cbd5e1",borderRadius:6},XT={padding:"4px 10px",fontSize:11,border:"none",borderRadius:6,background:"#2563eb",color:"#fff",cursor:"pointer"},JT={fontSize:11,color:"#dc2626"},ZT={fontSize:10,color:"#94a3b8"},bg=new Map;function Bn(e,t){bg.set(e,t)}function QT(e){return e?bg.get(e)??null:null}const Ha={id:{label:"$t:layout_editor.core_props.id.label",widget:"core-id",placeholder:"$t:layout_editor.core_props.id.placeholder",apply:{type:"propValue",propKey:"id"}},dataKey:{label:"$t:layout_editor.core_props.dataKey.label",widget:"core-datakey",placeholder:"$t:layout_editor.core_props.dataKey.placeholder",apply:{type:"nodeKey",nodeKey:"dataKey"}},isolatedState:{label:"$t:layout_editor.core_props.isolatedState.label",widget:"toggle",apply:{type:"nodeKey",nodeKey:"isolatedState"}},isolatedScopeId:{label:"$t:layout_editor.core_props.isolatedScopeId.label",widget:"core-scopeid",placeholder:"$t:layout_editor.core_props.isolatedScopeId.placeholder",apply:{type:"nodeKey",nodeKey:"isolatedScopeId"}}},RT=["id"];function e4(e){return e===!1?[]:Array.isArray(e)?e.filter(t=>t in Ha&&!mg.has(t)):RT.filter(t=>!mg.has(t))}const mg=new Set(["isolatedState","isolatedScopeId"]);function vg(e){const t=e.props?.id??e.id;return typeof t=="string"&&t.length>0?`${t}-scope`:"scope"}function t4({node:e,onPatchNode:t,t:n,scopeIdCandidates:o=[],usedScopeIds:r=[]}){const s=e,a="isolatedState"in s&&s.isolatedState!=null,l=typeof s.isolatedScopeId=="string"?s.isolatedScopeId:"",[d,c]=u.useState(""),p=()=>{const h={...e};a?(delete h.isolatedState,delete h.isolatedScopeId):(h.isolatedState={},h.isolatedScopeId=vg(e)),t(h)},f=h=>{const v={...e};v.isolatedScopeId=h.trim()===""?vg(e):h,t(v)},b=h=>{const v={...e};v.isolatedState=h,t(v)},g=u.useMemo(()=>{const h=d.trim().toLowerCase();return o.filter(v=>!h||v.toLowerCase().includes(h))},[o,d]),m=l!==""&&r.includes(l);return i.jsxs("div",{className:"g7le-isolated-scope","data-testid":"g7le-isolated-scope-control",style:n4,children:[i.jsx("div",{style:o4,children:n("layout_editor.core_props.isolatedState.label")}),i.jsxs("div",{style:r4,children:[i.jsx("button",{type:"button","data-testid":"g7le-isolated-toggle",role:"switch","aria-checked":a,onClick:p,style:i4(a),children:n(a?"layout_editor.overlay.on":"layout_editor.overlay.off")}),a?null:i.jsx("span",{"data-testid":"g7le-isolated-off-hint",style:s4,children:n("layout_editor.core_props.isolatedState.off_hint")})]}),a?i.jsxs(i.Fragment,{children:[i.jsxs("div",{style:_g,children:[i.jsx("label",{style:xg,children:n("layout_editor.core_props.isolatedScopeId.label")}),i.jsx("input",{type:"text","data-testid":"g7le-isolated-scopeid-input",value:l,placeholder:n("layout_editor.core_props.isolatedScopeId.placeholder"),onChange:h=>f(h.target.value),style:a4}),g.length>0?i.jsxs("div",{"data-testid":"g7le-isolated-scopeid-picker",style:l4,children:[i.jsx("input",{type:"text","data-testid":"g7le-isolated-scopeid-search",value:d,placeholder:n("layout_editor.core_props.isolatedScopeId.search"),onChange:h=>c(h.target.value),style:d4}),i.jsx("div",{style:c4,children:g.map(h=>i.jsx("button",{type:"button","data-testid":`g7le-isolated-scopeid-candidate-${h}`,onClick:()=>{f(h),c("")},style:u4,children:h},h))})]}):null,m?i.jsxs("p",{"data-testid":"g7le-isolated-scopeid-dup",style:p4,children:["ⓘ ",n("layout_editor.core_props.isolatedScopeId.duplicate")]}):null]}),i.jsxs("div",{style:_g,children:[i.jsx("label",{style:xg,children:n("layout_editor.core_props.isolatedState.initial_label")}),i.jsx("div",{"data-testid":"g7le-isolated-initial-value",children:i.jsx(Mn,{value:s.isolatedState??{},onChange:b,t:n,path:"isolatedState",scope:"isolated-node"})})]})]}):null]})}const n4={display:"flex",flexDirection:"column",gap:8,padding:10,border:"1px solid #e2e8f0",borderRadius:8,minWidth:0},o4={fontSize:12,fontWeight:700,color:"#0f172a"},r4={display:"flex",gap:8,alignItems:"center"},i4=e=>({padding:"4px 12px",fontSize:11,border:`1px solid ${e?"#3b82f6":"#cbd5e1"}`,borderRadius:14,background:e?"#eff6ff":"#fff",color:e?"#1d4ed8":"#64748b",cursor:"pointer"}),s4={fontSize:11,color:"#94a3b8"},_g={display:"flex",flexDirection:"column",gap:4,minWidth:0},xg={fontSize:11,color:"#64748b"},a4={padding:"5px 8px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,boxSizing:"border-box",width:"100%"},l4={border:"1px solid #e2e8f0",borderRadius:6,padding:4,background:"#fff"},d4={width:"100%",padding:"4px 6px",fontSize:11,border:"1px solid #cbd5e1",borderRadius:6,marginBottom:4,boxSizing:"border-box"},c4={display:"flex",flexDirection:"column",maxHeight:120,overflowY:"auto"},u4={textAlign:"left",padding:"4px 6px",fontSize:12,border:"none",background:"transparent",cursor:"pointer",borderRadius:4,color:"#0f172a"},p4={margin:0,fontSize:11,color:"#64748b"},f4={borderTop:"2px solid #c7d2fe",background:"#eef2ff",padding:"10px 12px",display:"flex",flexDirection:"column",gap:8},wg={fontSize:13,fontWeight:700,color:"#3730a3",display:"flex",alignItems:"center",gap:6},g4={fontSize:11,fontWeight:600,color:"#4338ca",background:"#e0e7ff",border:"1px solid #c7d2fe",borderRadius:4,padding:"1px 6px"},Fa={fontSize:11,color:"#6366f1"},h4={alignSelf:"flex-start",fontSize:12,fontWeight:600,color:"#fff",background:"#4f46e5",border:"none",borderRadius:4,padding:"4px 10px",cursor:"pointer"},y4={opacity:.5,cursor:"not-allowed"},b4={fontSize:11,color:"#dc2626"};function m4({entry:e,t,onSave:n}){const[o,r]=u.useState(()=>e.props),[s,a]=u.useState(!0),[l,d]=u.useState(null),[c,p]=u.useState(!1),[f,b]=u.useState(!1),g=e.extensionName??e.extensionIdentifier??`#${e.extensionId}`,m=async()=>{if(!s){d(t("layout_editor.property_modal.injected_props.invalid_json"));return}d(null),p(!0),b(!1);try{await n(o),b(!0)}catch(h){d(h instanceof Error?h.message:String(h))}finally{p(!1)}};return i.jsxs("div",{"data-testid":`g7le-injected-props-entry-${e.extensionId}`,style:{display:"flex",flexDirection:"column",gap:6},children:[i.jsx("div",{style:wg,children:i.jsx("span",{style:g4,"data-testid":"g7le-injected-props-origin-badge",children:t("layout_editor.property_modal.injected_props.origin",{source:g})})}),i.jsx(qs,{value:o,shape:"object",emptyValue:{},testidPrefix:"g7le-injected-props-json",minHeight:80,invalidErrorKey:"layout_editor.property_modal.injected_props.invalid_json",shapeErrorKey:"layout_editor.property_modal.injected_props.invalid_json",t,onChange:h=>{r(h??{}),b(!1)},onValidityChange:h=>{a(h),h&&d(null),b(!1)}}),l&&i.jsx("div",{style:b4,"data-testid":"g7le-injected-props-error",children:l}),f&&i.jsx("div",{style:{...Fa,color:"#16a34a"},"data-testid":"g7le-injected-props-saved",children:t("layout_editor.property_modal.injected_props.saved")}),i.jsx("div",{style:Fa,children:t("layout_editor.property_modal.injected_props.save_target")}),i.jsx("button",{type:"button","data-testid":"g7le-injected-props-save",style:{...h4,...c||!s?y4:null},disabled:c||!s,onClick:m,children:c?t("layout_editor.property_modal.injected_props.saving"):t("layout_editor.property_modal.injected_props.save_button",{source:g})})]})}function v4({injectedProps:e,t,onSaveInjectedProps:n}){return!Array.isArray(e)||e.length===0?null:i.jsxs("div",{style:f4,"data-testid":"g7le-injected-props-section",children:[i.jsxs("div",{style:wg,children:["🧩 ",t("layout_editor.property_modal.injected_props.title")]}),i.jsx("div",{style:Fa,children:t("layout_editor.property_modal.injected_props.help")}),e.map(o=>i.jsx(m4,{entry:o,t,onSave:r=>n(o.extensionId,r)},o.extensionId))]})}function _4(e,t){const n=e?.components??{};for(const o of Object.values(n)){const r=o?.find(s=>s.name===t);if(r)return r}return null}function x4({node:e,spec:t,manifest:n,t:o,onPatchNode:r,onClose:s,onDelete:a,permissionCandidates:l,pageCandidates:d,dataSourceCandidates:c,stateKeyCandidates:p,bindingCandidates:f,liveElement:b,scope:g=jt,onScopeChange:m,templateIdentifier:h,initialTab:v,onSaveInjectedProps:y,branchLabel:_,isolatedScopeIdCandidates:w,usedScopeIds:k}){const x=e.__injectedProps,$=Array.isArray(x)&&x.length>0&&!!y,C=ve=>{m?.(ve)},S=()=>{r(GC(e,g))},D=typeof e.name=="string"?e.name:"",E=qi(t,D),N=_4(n,D)?.settings,T=!!(N?.groups&&N.groups.length>0),O=Array.isArray(E?.propControls)?E.propControls:[],M=e4(E?.coreProps),B=new Set(M.map(ve=>Ha[ve].apply?.propKey)),z=O.filter(ve=>{const We=Ct(t,ve)?.apply?.propKey;return!(We!==void 0&&B.has(We))}),H=typeof E?.nodeEditor?.kind=="string"?E.nodeEditor.kind:void 0,P=E?.nodeEditor?.params,A=H?QT(H):null,L=!!A,F=Array.isArray(E?.dataProps)?E.dataProps:[],Y=F.length>0,V=!!(e.iteration&&typeof e.iteration=="object"),X=CT(e,E),Z=M.length>0||z.length>0||L||Y||X||V,ie=Array.isArray(E?.styleControls)?E.styleControls:[],oe=Array.isArray(E?.advanced)?E.advanced:[],ee=(Array.isArray(E?.events)?E.events:[]).length>0,fe=lg(E),K=ie.length>0||fe!==null,ye=!(E?.visibilityCondition===!1)&&(Z||K||ee||T||oe.length>0),Se=u.useMemo(()=>{const ve=[];return T&&ve.push("settings"),Z&&ve.push("props"),K&&ve.push("style"),ee&&ve.push("action"),ye&&ve.push("visibility"),ve.push("translation"),oe.length>0&&ve.push("advanced"),ve},[T,Z,K,ee,ye,oe.length]),De=z.length>0||L||Y||V,[Ie,Ve]=u.useState(v&&Se.includes(v)?v:T?"settings":De?"props":K?"style":Z?"props":Se[0]??"style"),ze=g6(e),Be=!Z&&!K&&!T&&!ee&&!ye&&oe.length===0,Xe=E?.label?E.label.startsWith("$t:")?o(E.label.slice(3)):E.label:D||o("layout_editor.property_modal.element"),Ne=(ve,qe)=>{const We={...e.props??{}};qe===void 0?delete We[ve]:We[ve]=qe,r({...e,props:We})},Je={settings:o("layout_editor.property_modal.tab.settings"),props:o("layout_editor.property_modal.tab.props"),style:o("layout_editor.property_modal.tab.style"),action:o("layout_editor.property_modal.tab.action"),visibility:o("layout_editor.property_modal.tab.visibility"),translation:o("layout_editor.property_modal.tab.translation"),advanced:o("layout_editor.property_modal.tab.advanced")};return i.jsxs("div",{className:"g7le-property-modal","data-testid":"g7le-property-modal",style:w4,children:[i.jsxs("div",{"data-modal-drag-handle":!0,"data-testid":"g7le-property-modal-header",style:k4,children:[i.jsx("span",{"data-testid":"g7le-property-modal-title",style:{fontSize:14,fontWeight:700},children:T&&Ie==="settings"?o("layout_editor.property_modal.settings_title",{name:Xe}):o("layout_editor.property_modal.title",{name:Xe})}),i.jsx("button",{type:"button","data-testid":"g7le-property-modal-close","aria-label":"close",onClick:s,style:S4,children:"✕"})]}),ze&&i.jsx("div",{"data-testid":"g7le-property-modal-advanced-badge",style:C4,children:o("layout_editor.property_modal.advanced_badge")}),_&&i.jsx("div",{"data-testid":"g7le-property-modal-branch-badge",style:$4,children:o("layout_editor.property_modal.branch_badge",{device:_})}),i.jsx("div",{style:E4,role:"tablist",children:Se.map(ve=>i.jsx("button",{type:"button",role:"tab","aria-selected":Ie===ve,"data-testid":`g7le-property-tab-${ve}`,"data-active":Ie===ve?"true":"false",onClick:()=>Ve(ve),style:{...I4,color:Ie===ve?"#2563eb":"#64748b",borderBottomColor:Ie===ve?"#2563eb":"transparent"},children:Je[ve]},ve))}),i.jsxs("div",{style:A4,"data-testid":"g7le-property-modal-body",children:[Be&&!$&&i.jsx("div",{"data-testid":"g7le-property-modal-no-editable",style:Sg,children:o("layout_editor.property_modal.no_editable_props")}),$&&i.jsx(v4,{injectedProps:x,t:o,onSaveInjectedProps:y}),Ie==="settings"&&N&&i.jsx(n6,{spec:N,node:e,t:o,onPatchProp:Ne}),Ie==="props"&&i.jsxs("div",{"data-testid":"g7le-property-props-tab",style:{display:"flex",flexDirection:"column"},children:[V&&i.jsx(oT,{node:e,candidates:f??[],t:o,onPatchNode:r}),Y&&i.jsx(D6,{node:e,dataProps:F,candidates:f??[],t:o,onPatchNode:r}),X&&i.jsx(Q6,{node:e,candidates:f??[],t:o,onPatchNode:r,templateIdentifier:h}),L&&A&&i.jsx(A,{node:e,params:P,spec:t,manifest:n,t:o,onPatchNode:r,templateIdentifier:h,candidates:f}),M.map(ve=>i.jsx(_i,{controlKey:`core-${ve}`,control:Ha[ve],node:e,t:o,onPatch:r,scope:jt,bindingCandidates:f},`core-${ve}`)),z.map(ve=>{const qe=Ct(t,ve);return qe?i.jsx(_i,{controlKey:ve,control:qe,node:e,t:o,onPatch:r,scope:jt,bindingCandidates:f},ve):null}),i.jsx(t4,{node:e,onPatchNode:r,t:o,scopeIdCandidates:w,usedScopeIds:k})]}),Ie==="style"&&i.jsxs("div",{"data-testid":"g7le-property-style-tab",style:{display:"flex",flexDirection:"column"},children:[i.jsx("div",{style:kg,children:i.jsx(pg,{scope:g,onChange:C,node:e,t:o,onClearScope:S})}),fe!==null&&i.jsx(DT,{node:e,spec:t,capability:E,t:o,onPatchNode:r,liveElement:b,scope:g}),ie.map(ve=>{const qe=Ct(t,ve);return qe?i.jsx(_i,{controlKey:ve,control:qe,node:e,t:o,onPatch:r,scope:g},ve):null})]}),Ie==="action"&&i.jsx(E6,{node:e,spec:t,capability:E,t:o,onPatchNode:r,pageCandidates:d,dataSourceCandidates:c,stateKeyCandidates:p,bindingCandidates:f}),Ie==="visibility"&&i.jsxs("div",{"data-testid":"g7le-property-visibility-tab",style:{display:"flex",flexDirection:"column"},children:[i.jsx("div",{style:kg,children:i.jsx(pg,{scope:g,onChange:C,node:e,t:o,showColorScheme:!1})}),i.jsx(Bu,{node:e,spec:t,t:o,onPatchNode:r,dataSourceCandidates:c,stateKeyCandidates:p,scope:g})]}),Ie==="translation"&&(h?i.jsx(ec,{customKey:Rd(e),templateIdentifier:h,t:o,paramLabels:Fv(e),onRemoveParam:ve=>{const qe=typeof e.text=="string"?e.text:"";r({...e,text:ko(qe,ve)});const We=Rd(e);h&&We&&Tr(h,We,ve)}}):i.jsx("div",{"data-testid":"g7le-property-translation-deferred",style:Sg,children:o("layout_editor.translation.not_a_key")})),Ie==="advanced"&&i.jsx(y6,{node:e,advanced:oe,t:o,onPatch:r,permissionCandidates:l})]}),i.jsxs("div",{style:T4,children:[i.jsx("span",{"data-testid":"g7le-property-modal-node-id",style:{fontSize:11,color:"#94a3b8"},children:typeof e.id=="string"||typeof e.id=="number"?`ID: ${e.id}`:""}),i.jsxs("div",{style:{display:"flex",gap:8},children:[i.jsx("button",{type:"button","data-testid":"g7le-property-modal-delete",onClick:a,style:D4,children:o("layout_editor.property_modal.delete_element")}),i.jsx("button",{type:"button","data-testid":"g7le-property-modal-done",onClick:s,style:z4,children:o("layout_editor.property_modal.close")})]})]})]})}const w4={display:"flex",flexDirection:"column",width:420,maxWidth:"92vw"},k4={display:"flex",alignItems:"center",justifyContent:"space-between",padding:"12px 16px",borderBottom:"1px solid #e2e8f0",cursor:"move",userSelect:"none"},S4={border:"none",background:"transparent",fontSize:14,cursor:"pointer",color:"#64748b"},C4={margin:"8px 16px 0",padding:"6px 10px",fontSize:11,background:"#fef3c7",color:"#92400e",borderRadius:6},$4={margin:"8px 16px 0",padding:"6px 10px",fontSize:11,background:"#dbeafe",color:"#1e40af",borderRadius:6},E4={display:"flex",gap:2,padding:"0 12px",borderBottom:"1px solid #e2e8f0",overflowX:"auto"},I4={padding:"8px 10px",fontSize:12,border:"none",borderBottom:"2px solid transparent",background:"transparent",cursor:"pointer",whiteSpace:"nowrap"},A4={padding:16,overflowY:"auto",maxHeight:"60vh"},kg={position:"sticky",top:-16,zIndex:2,background:"#fff",margin:"-16px -16px 0",padding:"6px 16px 0"},Sg={fontSize:12,color:"#94a3b8",padding:"16px 0",textAlign:"center"},T4={display:"flex",alignItems:"center",justifyContent:"space-between",padding:"12px 16px",borderTop:"1px solid #e2e8f0"},D4={padding:"6px 12px",fontSize:12,border:"1px solid #fecaca",borderRadius:6,background:"#fff",color:"#dc2626",cursor:"pointer"},z4={padding:"6px 12px",fontSize:12,border:"none",borderRadius:6,background:"#2563eb",color:"#fff",cursor:"pointer"},Ka="g7le:component-target-picked",Va="g7le:component-target-pick-request";let N4=1;function M4({value:e,onChange:t,t:n,control:o}){const r=xo(),s=e==null?"":String(e),a=o?.allowManualInput!==!1,l=u.useCallback(()=>{if(typeof window>"u")return;const d=`pick-${N4++}`,p=r.stack[r.stack.length-1]?.id;p&&r.minimize(p,n("layout_editor.target_picker.minimized_hint"));const f=b=>{const g=b.detail;g?.requestId===d&&(window.removeEventListener(Ka,f),p&&r.restore(p),!g.cancelled&&typeof g.id=="string"&&t(g.id))};window.addEventListener(Ka,f),window.dispatchEvent(new CustomEvent(Va,{detail:{requestId:d}}))},[r,t,n]);return i.jsx("div",{className:"g7le-component-target-picker","data-testid":"g7le-component-target-picker",children:i.jsxs("div",{style:{display:"flex",gap:6,alignItems:"center"},children:[a?i.jsx("input",{type:"text",className:"g7le-widget g7le-widget--text","data-testid":"g7le-component-target-input",value:s,placeholder:n("layout_editor.target_picker.id_placeholder"),onChange:d=>t(d.target.value===""?void 0:d.target.value),style:{flex:1,minWidth:0,padding:"6px 8px",border:"1px solid #cbd5e1",borderRadius:6,fontSize:13}}):i.jsx("span",{"data-testid":"g7le-component-target-value",style:{flex:1,minWidth:0},children:s||n("layout_editor.target_picker.none")}),i.jsxs("button",{type:"button","data-testid":"g7le-component-target-pick",onClick:l,style:{display:"flex",alignItems:"center",gap:4,padding:"6px 10px",background:"#1e293b",color:"#fff",border:"none",borderRadius:6,cursor:"pointer",fontSize:13,whiteSpace:"nowrap"},children:[i.jsx("span",{"aria-hidden":"true",children:"🎯"}),n("layout_editor.target_picker.select_area")]})]})})}async function L4(e,t,n,o,r=fetch){const s=`/api/admin/templates/${encodeURIComponent(e)}/layout-extensions/${t}`;let a;try{a=await r(s,{headers:Me(),credentials:"same-origin"})}catch(y){return{kind:"error",message:y instanceof Error?y.message:String(y)}}if(a.status===404)return{kind:"not_found"};if(!a.ok)return{kind:"error",message:`HTTP ${a.status}`};const l=await a.json().catch(()=>null),d=(l&&(l.data||l))??{};let c={};const p=d.content;if(typeof p=="string")try{c=JSON.parse(p)}catch{return{kind:"error",message:"invalid extension content"}}else p&&typeof p=="object"&&(c=p);const f=typeof d.lock_version=="number"?d.lock_version:0,b=Array.isArray(c.injections)?[...c.injections]:[],g=b.findIndex(y=>y&&y.position==="inject_props"&&y.target_id===n);if(g<0)return{kind:"injection_not_found"};b[g]={...b[g],props:o};const m={...c,injections:b};let h;try{h=await r(s,{method:"PUT",headers:Me({"Content-Type":"application/json"}),credentials:"same-origin",body:JSON.stringify({content:m,expected_lock_version:f})})}catch(y){return{kind:"error",message:y instanceof Error?y.message:String(y)}}const v=await h.json().catch(()=>null);return h.ok?{kind:"success",newLockVersion:typeof v?.data?.lock_version=="number"?v.data.lock_version:f+1}:h.status===409?{kind:"conflict",currentVersion:co(v,"current_version")??-1,yourVersion:co(v,"your_version")??f}:{kind:"error",message:v?.message??`HTTP ${h.status}`}}const Cg=new Map;function Ua(e,t){Cg.set(e,t)}function P4(e){return e?Cg.get(e)??null:null}const $g="g7le.clipboard";function Eg(){try{return typeof window>"u"||!window.sessionStorage?null:window.sessionStorage}catch{return null}}function O4(e){const t=JSON.parse(JSON.stringify(e)),n=o=>{for(const r of Object.keys(o))r.startsWith("__")&&delete o[r];Array.isArray(o.children)&&o.children.forEach(n)};return n(t),t}function Ig(e,t=0){const n=Eg();if(!n)return!1;try{const o={node:O4(e),copiedAt:t};return n.setItem($g,JSON.stringify(o)),!0}catch{return!1}}function B4(){const e=Eg();if(!e)return null;try{const t=e.getItem($g);if(!t)return null;const n=JSON.parse(t);return!n||typeof n.node!="object"||n.node===null?null:JSON.parse(JSON.stringify(n.node))}catch{return null}}const nr={nw:{width:-1,height:-1},n:{width:0,height:-1},ne:{width:1,height:-1},e:{width:1,height:0},se:{width:1,height:1},s:{width:0,height:1},sw:{width:-1,height:1},w:{width:-1,height:0}};function qa(e){if(typeof e=="number")return e;if(typeof e=="string"){const t=e.match(/^(-?\d+(?:\.\d+)?)px$/);if(t)return Number(t[1])}return null}function W4(e,t){const n=Array.isArray(e.options)?e.options:[],o=Array.isArray(e.scale)?e.scale:[],r=n.length>0?n.map(l=>l.value):o;if(r.length===0)return;let s=r[0],a=1/0;return r.forEach((l,d)=>{const c=qa(l)??d*40,p=Math.abs(c-t);p{if(!t)return;const m=nr[b];if(m.width!==0&&!n||m.height!==0&&!o)return;const h=t.props?.style??{},v=s?.()??null,y=qa(h.width)??(v?Math.round(v.width):0),_=qa(h.height)??(v?Math.round(v.height):0);p.current={handle:b,startX:g.clientX,startY:g.clientY,startW:y,startH:_,node:t};const w=m.width!==0&&m.height!==0?"both":m.width!==0?"width":"height";d?.(w);let k=null;const x=C=>{const S=p.current;if(!S)return;const D=r||1,E=(C.clientX-S.startX)/D*nr[S.handle].width,I=(C.clientY-S.startY)/D*nr[S.handle].height;let N=S.node;if(nr[S.handle].width!==0&&n){const T=Ag(n,S.startW+E);N=yn(N,n,T)}if(nr[S.handle].height!==0&&o){const T=Ag(o,S.startH+I);N=yn(N,o,T)}k=N,a(N,w)},$=()=>{p.current=null,d?.(null),window.removeEventListener("pointermove",x),window.removeEventListener("pointerup",$),k&&l?.(k,w)};window.addEventListener("pointermove",x),window.addEventListener("pointerup",$)},[t,n,o,r,a,l,d]);return{enabledAxes:c,onHandlePointerDown:f}}const Tg=320,Dg=1920,F4=1024;function Ga(e){return Number.isFinite(e)?Math.round(Math.max(Tg,Math.min(Dg,e))):F4}function Ya(e={}){const{state:t,dispatch:n}=Qe(),{previewDevice:o,previewZoom:r,previewCustomWidth:s,previewColorScheme:a}=t,l=o==="custom"?Ga(s):FT(o),d=u.useMemo(()=>{const g=e.availableWidth??l,m=l*r;return r>=1||m<=g?r:Math.min(r,g/l)},[l,r,e.availableWidth]),c=u.useCallback(g=>{n({type:"SET_PREVIEW_DEVICE",device:g})},[n]),p=u.useCallback(g=>{const m=Math.max(.5,Math.min(1,g));n({type:"SET_PREVIEW_ZOOM",zoom:m})},[n]),f=u.useCallback(g=>{n({type:"SET_PREVIEW_CUSTOM_WIDTH",width:Ga(g)})},[n]),b=u.useCallback(g=>{n({type:"SET_PREVIEW_COLOR_SCHEME",scheme:g})},[n]);return{device:o,deviceWidth:l,zoom:r,scale:d,customWidth:Ga(s),setDevice:c,setZoom:p,setCustomWidth:f,colorScheme:a,setColorScheme:b}}const Si="g7le-dnd-handle-";function K4(e){if(typeof e!="string"||!e.startsWith(Si))return null;const t=e.slice(Si.length);return t.length>0?t:null}function V4(e){return typeof CSS<"u"&&typeof CSS.escape=="function"?CSS.escape(e):e.replace(/["\\\]]/g,"\\$&")}function U4(e,t,n,o){const r=o?.handleFallback??!1;if(e instanceof HTMLElement){const s=e.closest("[data-editor-path]");if(s)return s;const a=e.closest('[data-testid^="g7le-"]');if(a&&!(a.getAttribute("data-testid")??"").startsWith(Si))return null;if(r){const l=e.closest(`[data-testid^="${Si}"]`);if(l&&t){const d=K4(l.getAttribute("data-testid"));if(d){const c=t.querySelector(`[data-editor-path="${V4(d)}"]`);if(c)return c}}}}if(r&&n&&typeof document<"u"&&typeof document.elementsFromPoint=="function")for(const s of document.elementsFromPoint(n.x,n.y)){const a=s.closest?.("[data-editor-path]");if(a)return a}return null}function q4(e,t,n){if(typeof e=="string"&&e.trim().length>0)return{color:e};const o=typeof n?.color=="string"&&n.color.length>0,r=typeof t=="string"&&t.trim().length>0;return o||r?{}:{color:"#0f172a"}}const G4="#ffffff",Y4="#0f172a";function X4(e){const t=e.trim(),n=t.match(/^rgba?\(\s*([\d.]+)[\s,]+([\d.]+)[\s,]+([\d.]+)/i);if(n)return[Number(n[1]),Number(n[2]),Number(n[3])];const o=t.replace(/^#/,"");return/^[0-9a-f]{3}$/i.test(o)?[parseInt(o[0]+o[0],16),parseInt(o[1]+o[1],16),parseInt(o[2]+o[2],16)]:/^[0-9a-f]{6}$/i.test(o)?[parseInt(o.slice(0,2),16),parseInt(o.slice(2,4),16),parseInt(o.slice(4,6),16)]:null}function J4(e){if(!e||/transparent/i.test(e))return!1;const t=e.trim().match(/^oklch\(\s*([\d.]+)(%?)/i);if(t)return(t[2]==="%"?Number(t[1])/100:Number(t[1]))>=.62;const n=X4(e);if(!n)return!1;const[o,r,s]=n;return .299*o+.587*r+.114*s>=180}function Z4(e,t,n){const o=typeof n?.color=="string"?n.color:null;return J4(e??o??t)?Y4:G4}function Q4({box:e,initialValue:t,isCustomKey:n,t:o,onCommit:r,onCancel:s,mirrorClassName:a,mirrorStyle:l,nodeEffectiveColor:d,onOpenTranslations:c}){const p=u.useRef(null),f=u.useRef(!1),[b,g]=u.useState(t.trim().length>0),[m,h]=u.useState(null);u.useEffect(()=>{const k=p.current;if(k){k.textContent=t,k.focus();try{const x=window.document.createRange();x.selectNodeContents(k);const $=window.getSelection();$?.removeAllRanges(),$?.addRange(x)}catch{}f.current=!1}},[t]),u.useEffect(()=>{const k=p.current;if(!k||typeof window.getComputedStyle!="function"){h(null);return}try{h(window.getComputedStyle(k).color||null)}catch{h(null)}},[a,l]);const v=()=>(p.current?.textContent??"").trim(),y=()=>{f.current||(f.current=!0,r(v()))},_=()=>{f.current||(f.current=!0,s())},w=k=>{k.key==="Enter"&&!k.shiftKey?(k.preventDefault(),y()):k.key==="Escape"&&(k.preventDefault(),_())};return i.jsxs("div",{className:"g7le-inline-text-editor","data-testid":"g7le-inline-text-editor","data-custom-key":n?"true":"false",style:{position:"absolute",left:e.left,top:e.top,minWidth:Math.max(e.width,60),minHeight:e.height,pointerEvents:"auto",zIndex:1e4},children:[i.jsx("div",{ref:p,role:"textbox","aria-label":o("layout_editor.inline_edit.aria_label"),"aria-multiline":"false","data-testid":"g7le-inline-text-editable",className:a,contentEditable:!0,suppressContentEditableWarning:!0,spellCheck:!1,onInput:()=>g(v().length>0),onKeyDown:w,onBlur:y,style:{outline:"2px solid #2563eb",outlineOffset:1,borderRadius:3,background:Z4(d,m,l),padding:"2px 4px",minHeight:e.height,whiteSpace:"pre-wrap",wordBreak:"break-word",boxShadow:"0 2px 8px rgba(37, 99, 235, 0.25)",...l??{},...q4(d,a,l)}}),i.jsxs("div",{className:"g7le-inline-text-editor__hint","data-testid":"g7le-inline-text-editor-hint",style:{marginTop:2,display:"inline-flex",alignItems:"center",gap:4,whiteSpace:"nowrap"},children:[c?i.jsxs("button",{type:"button","data-testid":"g7le-inline-text-editor-open-translations",onMouseDown:k=>k.preventDefault(),onClick:k=>{k.preventDefault(),k.stopPropagation(),c()},title:o("layout_editor.inline_edit.open_translations"),style:{padding:"2px 8px",fontSize:11,background:n?"#eff6ff":"#f0fdf4",color:n?"#1d4ed8":"#15803d",border:`1px solid ${n?"#bfdbfe":"#bbf7d0"}`,borderRadius:4,display:"inline-flex",alignItems:"center",gap:4,cursor:"pointer",whiteSpace:"nowrap"},children:[i.jsx("span",{"aria-hidden":"true",children:"🌐"}),i.jsx("span",{children:o(n?"layout_editor.inline_edit.existing_key_hint":"layout_editor.inline_edit.new_key_hint")}),i.jsxs("span",{"aria-hidden":"true",style:{opacity:.7},children:["· ",o("layout_editor.inline_edit.open_translations")," ›"]})]}):i.jsx("span",{style:{padding:"2px 6px",fontSize:11,background:n?"#eff6ff":"#f0fdf4",color:n?"#1d4ed8":"#15803d",border:`1px solid ${n?"#bfdbfe":"#bbf7d0"}`,borderRadius:4,display:"inline-flex",alignItems:"center",gap:4,whiteSpace:"nowrap"},children:n?i.jsxs(i.Fragment,{children:["⌐ ",o("layout_editor.inline_edit.existing_key_hint")]}):i.jsxs(i.Fragment,{children:["✎ ",o("layout_editor.inline_edit.new_key_hint")]})}),!b&&i.jsx("span",{"data-testid":"g7le-inline-text-editor-empty-warn",style:{color:"#b91c1c",marginLeft:4},children:o("layout_editor.inline_edit.empty_warning")})]})]})}function R4({box:e,node:t,customKey:n,initialChipValue:o,chipParamLabels:r,onKeyify:s,templateIdentifier:a,locale:l,t:d,candidates:c,onInsertBinding:p,onRemoveBinding:f,onCommit:b,onCancel:g}){const[m,h]=u.useState(null),v=u.useRef(""),[y,_]=u.useState(!1),w=u.useRef(!1),k=u.useRef(null),x=Kt(),$=u.useRef(()=>{}),[C,S]=u.useState(null),[D,E]=u.useState(!1),I=u.useRef(n??null);u.useEffect(()=>{n&&I.current!==n&&(I.current=n)},[n]);const N=u.useMemo(()=>{if(!n&&r)return r;const P={},A=vt(typeof t.text=="string"?t.text:"");for(const L of A?.params??[])P[L.name]=Ut(L.expression);return P},[t.text,n,r]);u.useEffect(()=>{let P=!1;if(!n){const A=o??"";return v.current=A,h(A),()=>{P=!0}}return(async()=>{const A=It(n,l);if(A!==void 0){v.current=A,h(A);return}const L=await fn(a,n);if(P)return;const F=L?.values?.[l]??"";v.current=F,h(F)})(),()=>{P=!0}},[a,n,l,o]);const T=async()=>{if(w.current)return;w.current=!0;const P=m??"";if(P===v.current){b();return}_(!0);let A=I.current;if(!A){if(!s){_(!1),w.current=!1;return}if(A=await s(P),_(!1),!A){w.current=!1;return}I.current=A,b();return}const L=await Ao(a,A,l,P);if(_(!1),L.kind==="error"){w.current=!1;return}x?.markDirty?.(),b()},O=()=>{w.current||(w.current=!0,g())},M=P=>{S(P)},B=async P=>{if(!(C===null||!p||D)){E(!0);try{const A=I.current;A&&m!==null&&m!==v.current&&(await Ao(a,A,l,m),v.current=m);const L=await p(C,P);L!==null&&(h(L),v.current=L,x?.markDirty?.())}finally{E(!1),S(null)}}},z=async P=>{if(!(!f||D)){E(!0);try{const A=I.current;A&&m!==null&&m!==v.current&&(await Ao(a,A,l,m),v.current=m);const L=await f(P);L!==null&&(h(L),v.current=L,x?.markDirty?.())}finally{E(!1)}}},H=P=>{P.key==="Enter"&&!P.shiftKey?(P.preventDefault(),T()):P.key==="Escape"&&(P.preventDefault(),O())};return $.current=()=>{T()},u.useEffect(()=>{const P=L=>{const F=k.current;if(!F)return;const Y=L.target;if(Y&&F.contains(Y))return;const V=Y;V&&V.closest&&V.closest('[data-testid="g7le-inline-toolbar"], .g7le-inline-toolbar')||$.current()},A=k.current?.ownerDocument??document;return A.addEventListener("pointerdown",P,!0),()=>A.removeEventListener("pointerdown",P,!0)},[]),i.jsxs("div",{ref:k,className:"g7le-inline-param-chip-editor","data-testid":"g7le-inline-param-chip-editor",onKeyDown:H,style:{position:"absolute",left:e.left,top:e.top,minWidth:Math.max(e.width,120),pointerEvents:"auto",zIndex:1e4,background:"#fff",border:"2px solid #2563eb",borderRadius:4,boxShadow:"0 2px 8px rgba(37,99,235,0.25)",padding:4},children:[m===null?i.jsx("div",{"data-testid":"g7le-inline-param-chip-loading",style:{fontSize:12,color:"#94a3b8",padding:4},children:d("layout_editor.translation.loading")}):i.jsx(Cr,{value:m,onChange:h,t:d,paramLabels:N,testIdSuffix:"inline",onRequestInsert:p?M:void 0,onRemoveChip:f?P=>void z(P):void 0}),C!==null&&p&&i.jsx("div",{"data-testid":"g7le-inline-param-chip-insert-picker",style:{marginTop:4},children:i.jsx($t,{candidates:c??[],t:d,onSelect:P=>void B(P),testIdSuffix:"inline-insert",defaultOpen:!0,floating:!1})}),i.jsxs("div",{style:{display:"flex",alignItems:"center",gap:6,marginTop:4},children:[i.jsx("button",{type:"button","data-testid":"g7le-inline-param-chip-save",disabled:y,onMouseDown:P=>P.preventDefault(),onClick:()=>void T(),style:{fontSize:11,border:"none",borderRadius:6,background:"#2563eb",color:"#fff",padding:"4px 12px",cursor:"pointer"},children:d(y?"layout_editor.translation.saving":"layout_editor.translation.save")}),i.jsx("span",{style:{fontSize:10,color:"#94a3b8"},children:d("layout_editor.translation.placeholder_locked")})]})]})}const eD=["fontWeight","fontBold","fontStyle","fontItalic","textDecoration","textUnderline","textAlign","fontSize","textColor"],tD={fontWeight:"B",fontBold:"B",fontStyle:"I",fontItalic:"I",textDecoration:"U",textUnderline:"U",textAlign:"≡",fontSize:"T",textColor:"A"},nD=[["fontWeight","fontBold"],["fontItalic","fontStyle"],["textUnderline","textDecoration"]];function zg({node:e,spec:t,t:n,onApplyControl:o,box:r,scope:s=jt}){const a=typeof e.name=="string"?e.name:"",l=qi(t,a),d=Array.isArray(l?.styleControls)?l.styleControls:[];let c=eD.filter(p=>d.includes(p)&&Ct(t,p)!==null);for(const p of nD){const f=p.filter(b=>c.includes(b));if(f.length>1){const b=f[0];c=c.filter(g=>!p.includes(g)||g===b)}}return c.length===0?null:i.jsx("div",{className:"g7le-inline-toolbar","data-testid":"g7le-inline-toolbar",role:"toolbar","aria-label":n("layout_editor.inline_edit.toolbar_label"),style:{position:"absolute",left:r.left,top:r.top-40,display:"inline-flex",gap:2,padding:4,background:"#0f172a",borderRadius:8,boxShadow:"0 4px 12px rgba(15, 23, 42, 0.35)",pointerEvents:"auto",zIndex:10001},children:c.map(p=>i.jsx(rD,{controlKey:p,node:e,spec:t,t:n,scope:s,onApplyControl:o},p))})}const oD={fontWeight:"bold",fontBold:"bold",fontStyle:"italic",fontItalic:"italic",textDecoration:"underline",textUnderline:"underline"};function Ng(e,t){const n=r=>(r.split(/[-\s]/).pop()??r)===t||r===t;return e.find(r=>{const s=typeof r.value=="string"?r.value:"",a=Array.isArray(r.apply?.tokens)?r.apply.tokens:[];return n(s)||a.some(n)})?.value}function rD({controlKey:e,node:t,spec:n,t:o,scope:r,onApplyControl:s}){const[a,l]=u.useState(!1),d=Ct(n,e);if(!d)return null;const c=d.options,p=Array.isArray(c),f=Oo(t,d,r),b=oD[e],g=b&&Array.isArray(c)?Ng(c,b):void 0,m=!b,h=g!==void 0?f.value===g:p&&f.matched&&f.value!==void 0&&f.value!=="",v=o(`layout_editor.inline_edit.format.${e}`),y=x=>typeof x.label=="string"?x.label.startsWith("$t:")?o(x.label.slice(3)):x.label:String(x.value??""),_=()=>{s(e,x=>{const $=Oo(x,d,r),C=Array.isArray(c)?Ng(c,b):void 0,S=C!==void 0&&$.value===C?void 0:C;return yn(x,d,S,r)})},w=x=>{s(e,$=>{const S=Oo($,d,r).value===x?void 0:x;return yn($,d,S,r)}),l(!1)},k=x=>{if(x.preventDefault(),x.stopPropagation(),!m&&Array.isArray(c)){_();return}l($=>!$)};return i.jsxs("div",{style:{position:"relative",display:"inline-flex"},children:[i.jsxs("button",{type:"button","data-testid":`g7le-inline-format-${e}`,"data-active":h?"true":"false","aria-label":v,"aria-pressed":p?h:void 0,title:v,onMouseDown:x=>x.preventDefault(),onClick:k,style:{display:"inline-flex",alignItems:"center",gap:5,height:28,padding:"0 9px",fontSize:12,border:"none",borderRadius:5,cursor:"pointer",background:h?"#2563eb":"transparent",color:"#ffffff",whiteSpace:"nowrap"},children:[i.jsx("span",{"aria-hidden":"true",style:{fontWeight:e==="fontWeight"||e==="fontBold"?700:600,fontStyle:e==="fontStyle"||e==="fontItalic"?"italic":"normal",textDecoration:e==="textDecoration"||e==="textUnderline"?"underline":"none"},children:tD[e]}),i.jsx("span",{children:v}),m&&i.jsx("span",{"aria-hidden":"true",style:{fontSize:9,opacity:.8},children:"▾"})]}),m&&a&&i.jsx("div",{"data-testid":`g7le-inline-popover-${e}`,role:"listbox",onMouseDown:x=>x.preventDefault(),style:{position:"absolute",top:32,left:0,minWidth:140,maxHeight:240,overflowY:"auto",background:"#0f172a",border:"1px solid #334155",borderRadius:8,boxShadow:"0 8px 24px rgba(15,23,42,0.45)",padding:4,zIndex:10002},children:Array.isArray(c)&&c.length>0?c.map((x,$)=>{const C=f.value===x.value;return i.jsxs("button",{type:"button",role:"option","aria-selected":C,"data-testid":`g7le-inline-option-${e}-${String(x.value??$)}`,onMouseDown:S=>S.preventDefault(),onClick:S=>{S.preventDefault(),S.stopPropagation(),w(x.value)},style:{display:"flex",alignItems:"center",gap:8,width:"100%",padding:"6px 8px",fontSize:12,textAlign:"left",border:"none",borderRadius:5,cursor:"pointer",background:C?"#2563eb":"transparent",color:"#ffffff"},children:[typeof x.swatch=="string"&&i.jsx("span",{"aria-hidden":"true",style:{width:14,height:14,borderRadius:3,background:x.swatch,border:"1px solid rgba(255,255,255,0.3)"}}),i.jsx("span",{children:y(x)}),C&&i.jsx("span",{"aria-hidden":"true",style:{marginLeft:"auto"},children:"✓"})]},String(x.value??$))}):i.jsx(iD,{testid:`g7le-inline-freeinput-${e}`,placeholder:o(`layout_editor.inline_edit.prompt.${e}`),initial:typeof f.value=="string"?f.value:"",onSubmit:x=>{s(e,$=>yn($,d,x,r)),l(!1)}})})]})}function iD({testid:e,placeholder:t,initial:n,onSubmit:o}){const[r,s]=u.useState(n);return i.jsx("input",{type:"text","data-testid":e,value:r,placeholder:t,autoFocus:!0,onMouseDown:a=>a.stopPropagation(),onChange:a=>s(a.currentTarget.value),onKeyDown:a=>{a.key==="Enter"&&(a.preventDefault(),o(r.trim()))},style:{width:130,padding:"6px 8px",fontSize:12,border:"1px solid #334155",borderRadius:5,background:"#1e293b",color:"#ffffff"}})}function sD(e){const{frameEl:t,manifest:n,nesting:o,componentPalette:r,spec:s=null,permissionCandidates:a,resolveRouteMatch:l,onNavigateToDestination:d,onSessionAddedPathsChange:c,onEditProps:p}=e,{state:f,dispatch:b}=Qe(),{t:g}=et(),m=Ya(),h=u.useCallback((W,J)=>{const R=window.G7Core;if(R&&typeof R.t=="function"){const le=R.t(W,J);if(le&&le!==W&&!le.startsWith("$t:"))return le}return g(W,J)},[g]),v=Kt(),y=e2(50),_=xo(),w=u.useRef([]),k=u.useMemo(()=>v?.document?.raw?.components??[],[v?.document?.raw?.components]),x=u.useMemo(()=>k.length===0?null:{children:k},[k]),$=u.useMemo(()=>{if(f.editMode!=="extension")return;const W=/^__extension__\/(\d+)/.exec(f.selectedRoute?.path??"");return W?Number(W[1]):void 0},[f.editMode,f.selectedRoute?.path]),C=u.useMemo(()=>{if(f.editMode==="iteration_item"){const W=v?.document?.iterationContext?.sourceIndexPath;return Array.isArray(W)&&W.length>0?W:null}if(f.editMode==="modal"){const W=v?.document?.modalContext?.editIndexPath;return Array.isArray(W)&&W.length>0?W:null}return null},[f.editMode,v?.document?.iterationContext?.sourceIndexPath,v?.document?.modalContext?.editIndexPath]),S=U7({rootNode:x,editMode:f.editMode,currentExtensionId:$,editableRootPath:C,pathParser:Re,resolveRouteMatch:l}),[D,E]=u.useState(null),[I,N]=u.useState(null),[T,O]=u.useState(null),M=u.useRef(null);M.current=T;const[B,z]=u.useState(null),H=u.useRef(null),P=u.useRef(null);P.current=S.selectedPath;const A=pm(),[L,F]=u.useState(null),Y=u.useCallback(W=>{if(!t)return;const J=Re(W);if(!J)return;const R={children:Ne.current.components},le=Pe(R,J),me=Jt(le,f.editMode,$,Dn(R,J));if(!Zt(me))return;const xe=A.classify(le);if(!xe.editable){Et({op:"inline_edit_blocked_binding",sourceState:xe.sourceState,componentPath:at(J),timestamp:Date.now()});return}const Te=bo(t,W);if(!Te)return;const we=le?.props??{},_e=Lg(t,W);F({path:J,domPath:W,box:Te,initialValue:xe.displayValue,isCustomKey:xe.sourceState==="custom_key",isParamKey:xe.isParamKey===!0,customKey:xe.customKey,chipValue:xe.chipValue,chipParamLabels:xe.chipParamLabels,mirrorClassName:typeof we.className=="string"?we.className:void 0,mirrorStyle:we.style??void 0,nodeEffectiveColor:_e}),Et({op:"inline_edit_enter",sourceState:xe.sourceState,componentPath:at(J),timestamp:Date.now()})},[t,A,f.editMode,$]),V=u.useCallback(()=>{if(!t){E(null),N(null);return}N(bo(t,S.selectedPath)),E(bo(t,S.hoverPath))},[t,S.selectedPath,S.hoverPath,k]);u.useEffect(()=>{if(V(),typeof window<"u"&&typeof window.requestAnimationFrame=="function"){const W=window.requestAnimationFrame(()=>V());return()=>window.cancelAnimationFrame(W)}},[V]),u.useEffect(()=>{if(t)return jp(V,t)},[t,V]);const X=u.useCallback((W,J)=>{if(typeof window>"u")return;window.dispatchEvent(new CustomEvent(Ka,{detail:J===null?{requestId:W,cancelled:!0}:{requestId:W,id:J}})),O(null),z(null);const R=H.current;R?S.handleSelect("",{dataset:{editorPath:R}}):S.clearSelection(),H.current=null},[S]);u.useEffect(()=>{if(typeof window>"u")return;const W=J=>{const R=J.detail;if(typeof R?.requestId!="string")return;const le=M.current;le&&le!==R.requestId&&X(le,null),H.current=P.current,O(R.requestId)};return window.addEventListener(Va,W),()=>window.removeEventListener(Va,W)},[X]),u.useEffect(()=>{if(!T)return;const W=J=>{J.key==="Escape"&&(J.preventDefault(),X(T,null))};return window.addEventListener("keydown",W,!0),()=>window.removeEventListener("keydown",W,!0)},[T,X]),u.useEffect(()=>{if(!t)return;const W=f.editMode==="iteration_item"||f.editMode==="modal",J=(_e,Ee)=>U4(_e,t,Ee?{x:Ee.clientX,y:Ee.clientY}:void 0,{handleFallback:W}),R=_e=>{const Ee=_e.dataset.editorPath??"",He=Ee?Re(Ee):null,Nt={children:Ne.current.components},gt=He?Pe(Nt,He):null,Mt=gt&&typeof gt.props?.id=="string"?gt.props.id:typeof gt?.id=="string"?gt.id:"";return Mt.startsWith("auto_")?"":Mt},le=_e=>{const Ee=J(_e.target,_e);if(!Ee)return;const He=M.current;if(He){_e.preventDefault(),_e.stopPropagation();const Nt=R(Ee);Nt&&X(He,Nt);return}_e.preventDefault(),_e.stopPropagation(),S.handleSelect(Ee.dataset.editorId??"",{currentTarget:Ee,dataset:Ee.dataset})},me=_e=>{const Ee=J(_e.target,_e);if(M.current){if(!Ee){z(null);return}const He=bo(t,Ee.dataset.editorPath??null);if(!He){z(null);return}z({box:He,hasId:R(Ee)!==""});return}if(!Ee){S.handleHover(null,{});return}S.handleHover(Ee.dataset.editorId??"",{currentTarget:Ee,dataset:Ee.dataset})},xe=()=>{if(M.current){z(null);return}S.handleHover(null,{})},Te=_e=>{const Ee=J(_e.target,_e);if(!Ee)return;const He=Ee.dataset.editorPath??"";He&&(_e.preventDefault(),_e.stopPropagation(),Y(He))},we=t.parentElement??t;return we.addEventListener("click",le,!0),we.addEventListener("mousemove",me,!0),we.addEventListener("mouseleave",xe,!0),we.addEventListener("dblclick",Te,!0),()=>{we.removeEventListener("click",le,!0),we.removeEventListener("mousemove",me,!0),we.removeEventListener("mouseleave",xe,!0),we.removeEventListener("dblclick",Te,!0)}},[t,S,Y,f.editMode,X]);const Z=u.useMemo(()=>{const W=S.selectedPath;if(!W)return null;const J=W.match(/^(.*)\.children\.\d+$/);return J?J[1]:null},[S.selectedPath]),ie=u.useCallback(()=>{Z&&S.handleSelect("",{dataset:{editorPath:Z}})},[Z,S]),oe=u.useMemo(()=>!t||!S.selectedPath?null:t.querySelector(`[data-editor-path="${dn(S.selectedPath)}"]`),[t,S.selectedPath]),G=u.useMemo(()=>S.selectedPath?Re(S.selectedPath):null,[S.selectedPath]),ee=u.useMemo(()=>{if(!G)return null;const W=G.find(tt);if(!W)return null;const{i18nKey:J,raw:R}=KC(W.responsive);return J?g(J):g("layout_editor.overlay.branch_custom",{range:R})},[G,g]),fe=u.useMemo(()=>S.selectedPath?S.selectedIsIteration&&S.selectedIterationSourcePath?S.selectedIterationSourcePath:S.selectedPath:null,[S.selectedPath,S.selectedIsIteration,S.selectedIterationSourcePath]),K=u.useMemo(()=>fe?Re(fe):null,[fe]),te=u.useMemo(()=>!t||!fe?null:t.querySelector(`[data-editor-path="${dn(fe)}"]`),[t,fe]),ye=u.useMemo(()=>{if(te?.parentElement)return te.parentElement;if(!t||!fe)return null;const W=fe.match(/^(.*)\.children\.\d+$/);return W?t.querySelector(`[data-editor-path="${dn(W[1])}"]`):null},[te,t,fe]),Se=u.useMemo(()=>{const W=S.selectedNode;return W&&(typeof W.name=="string"?W.name:typeof W.type=="string"?W.type:"")||null},[S.selectedNode]),De=u.useMemo(()=>{if(S.selectedLockKind!=="extension")return null;const W=S.selectedNode?.__source;if(!W||W.kind!=="extension")return null;const J=W.extensionName?.trim(),R=W.extensionIdentifier?.trim();return J&&R?`${J} (${R})`:J||R||W.extensionSourceType||null},[S.selectedLockKind,S.selectedNode]),Ie=Z7({parentEl:ye,selectedPath:K,selectedEl:te}),Ve=u.useMemo(()=>!t||!fe||fe===S.selectedPath?I:bo(t,fe),[t,fe,I,S.selectedPath,k]),ze=u.useRef(null),Be=u.useCallback(()=>{const W=ze.current;W&&(_.close(W),ze.current=null)},[_]),Xe=u.useRef(()=>{}),Ne=u.useRef({manifest:n,nesting:o,componentPalette:r,components:k});Ne.current={manifest:n,nesting:o,componentPalette:r,components:k};const Je=u.useCallback(W=>{const J=W.insertion?.parentPath??[],R=W.insertion?.index??0,le=Ne.current,me=Mg(le.components,J);Be();const xe=_.open({ariaLabel:h("layout_editor.palette.title"),width:880,maxHeightRatio:.82,content:u.createElement(jf,{manifest:le.manifest,nesting:le.nesting,componentPalette:le.componentPalette??null,targetParentPath:J,targetContainerName:me,onInsert:(Te,we,_e)=>{Xe.current(Te,we,_e)},insertionIndex:R,onClose:()=>Be(),editorTemplateId:f.templateIdentifier,editorLocale:f.locale}),onClose:()=>{ze.current=null}});ze.current=xe},[_,Be]),ve=u.useMemo(()=>{if(!f.isPaletteOpen)return null;if(!G)return{parentPath:[],index:k.length};if(S.selectedLockKind!=="none"){const le=K;return!le||le.length===0?{parentPath:[],index:k.length}:{parentPath:le.slice(0,-1),index:(le[le.length-1]??0)+1}}const J=S.selectedNode,R=Array.isArray(J?.children)?J.children.length:0;return RI(J?.name,R,G,o,k.length)},[f.isPaletteOpen,G,K,S.selectedLockKind,S.selectedNode,k.length,o]),qe=u.useCallback(()=>{b({type:"SET_PALETTE_OPEN",open:!1})},[b]),We=u.useRef(null);u.useEffect(()=>{if(!f.isPaletteOpen){const me=We.current;me&&(_.close(me),We.current=null);return}if(!ve||We.current)return;const W=ve,J=Ne.current,R=Mg(J.components,W.parentPath),le=_.open({ariaLabel:h("layout_editor.palette.title"),width:880,maxHeightRatio:.82,content:u.createElement(jf,{manifest:J.manifest,nesting:J.nesting,componentPalette:J.componentPalette??null,targetParentPath:W.parentPath,targetContainerName:R,onInsert:(me,xe,Te)=>{Xe.current(me,xe,Te),qe()},insertionIndex:W.index,onClose:()=>qe(),editorTemplateId:f.templateIdentifier,editorLocale:f.locale}),onClose:()=>{We.current=null,b({type:"SET_PALETTE_OPEN",open:!1})}});We.current=le},[f.isPaletteOpen,ve,_,qe,b]);const ce=u.useRef({layoutName:null,pushed:!1});u.useEffect(()=>{const W=v?.document?.layoutName??null;if(!W){ce.current={layoutName:null,pushed:!1};return}ce.current.layoutName!==W&&(y.clear(),y.push({actionKind:"inline_text_edit",label:"baseline",snapshot:k}),ce.current={layoutName:W,pushed:!0})},[v?.document?.layoutName,k,y]);const q=u.useRef(0);u.useEffect(()=>{const W=v?.saveSuccessCounter??0;W!==q.current&&(q.current=W,W!==0&&(y.clear(),y.push({actionKind:"inline_text_edit",label:"save_baseline",snapshot:k}),w.current=[],c?.([])))},[v?.saveSuccessCounter,k,y,c]);const se=u.useRef(0);u.useEffect(()=>{const W=v?.reloadCounter??0;W!==se.current&&(se.current=W,W!==0&&(ce.current={layoutName:null,pushed:!1},w.current=[],c?.([])))},[v?.reloadCounter,c]);const ae=u.useCallback((W,J,R)=>{if(!v)return;let le=[];v.patchLayout(we=>{const _e={children:we},Ee=Xa(_e,J),He=R<0?Array.isArray(Ee?.children)?Ee.children.length:0:R,gt=ss(_e,J,He,W).children??[];return le=gt,gt}),y.push({actionKind:"insert",label:`insert ${W.name??W.type??""}`,snapshot:le});const me=Xa({children:le},J),xe=R<0?Array.isArray(me?.children)?me.children.length-1:0:R,Te=at([...J,xe]);if(w.current=[...w.current,Te],c?.(w.current),t){const we=`[data-editor-path="${dn(Te)}"]`,_e=()=>{const Ee=t.querySelector(we);Ee&&typeof Ee.scrollIntoView=="function"&&Ee.scrollIntoView({behavior:"smooth",block:"center",inline:"nearest"})};typeof window<"u"&&typeof window.requestAnimationFrame=="function"?window.requestAnimationFrame(()=>window.requestAnimationFrame(_e)):setTimeout(_e,32)}},[v,y,c,t]);Xe.current=ae;const be=u.useCallback(()=>{if(!v||!S.selectedNode||!G||!Zt(S.selectedLockKind))return;const W=ls(S.selectedNode);let J=[];v.patchLayout(R=>{const le={children:R},me=G.slice(0,-1),xe=G[G.length-1]??0,we=ss(le,me,xe+1,W).children??[];return J=we,we}),y.push({actionKind:"insert",label:"duplicate",snapshot:J})},[v,S.selectedNode,S.selectedLockKind,G,y]),Ce=u.useCallback(()=>{if(!v||!G||!Zt(S.selectedLockKind))return;let W=[];v.patchLayout(J=>{const me=as({children:J},G).children??[];return W=me,me}),y.push({actionKind:"remove",label:"remove",snapshot:W}),S.clearSelection()},[v,G,y,S]),$e=u.useMemo(()=>{if(!S.selectedNode||!G||G.some(tt))return null;const W=S.selectedNode;if(!Array.isArray(W.children))return null;const J=FC(f.previewDevice,W,f.previewCustomWidth);return{mode:J.mode,key:J.key,sourceKey:J.sourceKey}},[S.selectedNode,G,f.previewDevice,f.previewCustomWidth]),U=u.useCallback(()=>{if(!v||!G||$e?.mode!=="separate")return;const W=$e.key,J=$e.sourceKey;let R=[];v.patchLayout(le=>(R=nm({children:le},G,W,J).children??[],R)),y.push({actionKind:"insert",label:"separate_branch",snapshot:R})},[v,G,$e,y]),ue=u.useCallback(()=>{if(!v||!G||$e?.mode!=="merge")return;const W=$e.key;let J=[];v.patchLayout(R=>(J=om({children:R},G,W).children??[],J)),y.push({actionKind:"remove",label:"merge_branch",snapshot:J})},[v,G,$e,y]),ge=u.useCallback(W=>{switch(W){case"mobile":return g("layout_editor.context_menu.device_mobile");case"portable":return g("layout_editor.context_menu.device_portable");case"tablet":return g("layout_editor.context_menu.device_tablet");case"desktop":return g("layout_editor.context_menu.device_desktop");default:return g("layout_editor.context_menu.device_custom",{range:W})}},[g]),ke=u.useMemo(()=>{if(!$e)return null;const W=ge($e.key);return{separate:g("layout_editor.context_menu.separate_branch_device",{device:W}),merge:g("layout_editor.context_menu.merge_branch_device",{device:W})}},[$e,g,ge]),he=u.useMemo(()=>{if(!S.selectedNode||!G)return[];if(G.some(tt))return[];const W=Rr(f.previewDevice,S.selectedNode,f.previewCustomWidth);return HT(S.selectedNode,W).map(J=>({key:J,label:ge(J)}))},[S.selectedNode,G,f.previewDevice,f.previewCustomWidth,ge]),j=u.useCallback(W=>{if(W==="desktop"||W==="tablet"||W==="mobile"||W==="portable"){m.setDevice(W);return}m.setDevice(W)},[m]),ne=u.useCallback(()=>{S.selectedNode&&Ig(S.selectedNode)},[S.selectedNode]),pe=u.useCallback(()=>{S.selectedNode&&Zt(S.selectedLockKind)&&(Ig(S.selectedNode),Ce())},[S.selectedNode,S.selectedLockKind,Ce]),Q=u.useCallback(()=>{if(!v)return;const W=B4();if(!W)return;const J=ls(W);if(G&&G.length>0){const R=G.slice(0,-1),le=(G[G.length-1]??0)+1;ae(J,R,le)}else ae(J,[],k.length)},[v,G,ae,k.length]),de=u.useCallback((W,J)=>{y.push({actionKind:"move",label:J,snapshot:W})},[y]),Ae=u.useCallback(()=>{if(S.selectedNavAffordance==="route_in_tree"&&S.selectedNavTargetPath){if(d){d(S.selectedNavTargetPath);return}b({type:"SELECT_ROUTE",route:{path:S.selectedNavTargetPath,layoutName:null}})}},[S.selectedNavAffordance,S.selectedNavTargetPath,d,b]),dt=u.useCallback(()=>{const W=em(S.selectedBaseLayout,f.selectedRoute?.layoutName);W&&b({type:"ENTER_BASE_EDIT",layoutName:W})},[S.selectedBaseLayout,f.selectedRoute?.layoutName,b]),pt=u.useCallback(()=>{const W=S.selectedNode?.__source?.extensionId;W!=null&&b({type:"ENTER_EXTENSION_EDIT",extensionId:String(W)})},[S.selectedNode,b]),ot=u.useCallback(()=>{const W=S.selectedIterationSourcePath;W&&b({type:"ENTER_ITERATION_ITEM_EDIT",sourcePath:W})},[S.selectedIterationSourcePath,b]),Le=u.useRef(null),je=u.useRef(null),Ze=u.useRef(null),Tt=u.useRef({colorScheme:"base",breakpoint:"base"}),Dt=Le.current!==null?_.stack.some(W=>W.id===Le.current):!1,zt=u.useMemo(()=>Fd(f.routeTree,h),[f.routeTree,h]),kn=u.useMemo(()=>Kd(v?.document?.raw),[v?.document?.raw]),Sn=u.useMemo(()=>Vd(s),[s?.states]),en=u.useMemo(()=>{const W=v?.document?.raw,J=Ff(W?.components),R=W?.initIsolated,le=R&&typeof R=="object"&&!Array.isArray(R)?Object.keys(R):[];return UA(J,le)},[v?.document?.raw]),Kn=u.useMemo(()=>{const W=v?.document?.raw;return Ff(W?.components).map(J=>J.scopeId).filter(J=>typeof J=="string"&&J.length>0)},[v?.document?.raw]),cn=Hd({raw:v?.document?.raw,spec:s}),Oi=u.useCallback((W,J)=>{if(!v)return;const R={children:Ne.current.components},le=Pe(R,W);if(!le)return;Tt.current={colorScheme:f.previewColorScheme==="dark"?"dark":"base",breakpoint:Rr(f.previewDevice,le,f.previewCustomWidth)};const me=t?.getBoundingClientRect(),xe=I&&me?{left:me.left+I.left,top:me.top+I.top,width:I.width,height:I.height}:void 0,Te=oe,we=W.find(tt),_e=we?ge(we.responsive):null,Ee=W.filter(Cn=>typeof Cn=="number"),He=Ob({children:Ne.current.components},Ee,Bb(cn)),Nt=He.length>0?[...Xi({iterationVars:He}),...cn]:cn,gt=(Cn,n5)=>u.createElement(x4,{key:`pem-${at(je.current??[])}`,node:Cn,spec:s,manifest:Ne.current.manifest,t:h,permissionCandidates:a,pageCandidates:zt,dataSourceCandidates:kn,stateKeyCandidates:Sn,bindingCandidates:Nt,liveElement:Te,templateIdentifier:f.templateIdentifier,initialTab:n5,branchLabel:_e,isolatedScopeIdCandidates:en,usedScopeIds:Kn,onSaveInjectedProps:async(mt,$n)=>{const Bi=typeof Cn.id=="string"?Cn.id:"",Vn=await L4(f.templateIdentifier,mt,Bi,$n);if(it({op:"save",layoutName:`extension:${mt}`,editMode:f.editMode,saveTarget:"layout_extension",endpoint:`/api/admin/templates/${f.templateIdentifier}/layout-extensions/${mt}`,isDirty:!0,timestamp:Date.now()}),Vn.kind!=="success")throw new Error(Vn.kind==="conflict"?h("layout_editor.save.conflict"):Vn.kind==="injection_not_found"?"injection not found":Vn.kind==="not_found"?"extension not found":Vn.message??"save failed")},scope:Tt.current,onScopeChange:mt=>{Tt.current=mt;const $n=Le.current;$n&&_.open({id:$n,ariaLabel:h("layout_editor.property_modal.title"),width:420,draggable:!0,avoidRect:xe,content:gt(Cn),onClose:()=>{Le.current=null,je.current=null}})},onPatchNode:mt=>{let $n=[];v.patchLayout(Vn=>($n=Bt({children:Vn},W,()=>mt).children??[],$n)),y.push({actionKind:"inline_text_edit",label:"edit_props",snapshot:$n}),fr({source:"style_control",componentPath:at(W),componentName:typeof mt.name=="string"?mt.name:null,patchKey:null,timestamp:Date.now()});const Bi=Le.current;Bi&&_.open({id:Bi,ariaLabel:h("layout_editor.property_modal.title"),width:420,draggable:!0,avoidRect:xe,content:gt(mt),onClose:()=>{Le.current=null,je.current=null}})},onDelete:()=>{const mt=Le.current;mt&&_.close(mt),Ce()},onClose:()=>{const mt=Le.current;mt&&_.close(mt)}});je.current=W;const Mt=_.open({ariaLabel:h("layout_editor.property_modal.title"),width:420,draggable:!0,avoidRect:xe,content:gt(le,J),onClose:()=>{Le.current=null,je.current=null,Ze.current=null}});Le.current=Mt,Ze.current=Cn=>{_.open({id:Mt,ariaLabel:h("layout_editor.property_modal.title"),width:420,draggable:!0,avoidRect:xe,content:gt(Cn),onClose:()=>{Le.current=null,je.current=null,Ze.current=null}})}},[v,s,h,a,zt,kn,Sn,cn,oe,y,_,Ce,t,I,f.previewColorScheme,f.previewDevice,f.previewCustomWidth,ge]);u.useEffect(()=>{const W=Ze.current,J=je.current;if(!W||!J)return;const R={children:Ne.current.components},le=Pe(R,J);le?W(le):Le.current&&_.close(Le.current)},[k]);const WM=()=>{if(!Dt)return null;const J={position:"absolute",background:"rgba(15, 23, 42, 0.45)",pointerEvents:"auto",zIndex:r2},R=we=>{we.preventDefault(),we.stopPropagation()};if(!I)return i.jsx("div",{"data-testid":"g7le-edit-lock-full",onClick:R,onMouseMove:R,style:{...J,inset:0}});const{left:le,top:me,width:xe,height:Te}=I;return i.jsxs("div",{"data-testid":"g7le-edit-lock",style:{position:"absolute",inset:0,pointerEvents:"none"},children:[i.jsx("div",{"data-testid":"g7le-edit-lock-top",onClick:R,onMouseMove:R,style:{...J,left:0,top:0,right:0,height:Math.max(0,me)}}),i.jsx("div",{"data-testid":"g7le-edit-lock-bottom",onClick:R,onMouseMove:R,style:{...J,left:0,top:me+Te,right:0,bottom:0}}),i.jsx("div",{"data-testid":"g7le-edit-lock-left",onClick:R,onMouseMove:R,style:{...J,left:0,top:me,width:Math.max(0,le),height:Te}}),i.jsx("div",{"data-testid":"g7le-edit-lock-right",onClick:R,onMouseMove:R,style:{...J,left:le+xe,top:me,right:0,height:Te}})]})},El=u.useCallback(()=>{if(G&&Mp(S.selectedLockKind)){if(p&&S.selectedPath){p(S.selectedPath);return}Oi(G)}},[G,S.selectedPath,S.selectedLockKind,p,Oi]),{scale:Il}=m,Al=typeof S.selectedNode?.name=="string"?S.selectedNode.name:"",uy=s?.componentCapabilities?.[Al]??null,py=Array.isArray(uy?.styleControls)?uy.styleControls:[],jM=py.includes("width")?Ct(s,"width"):null,HM=py.includes("height")?Ct(s,"height"):null,FM=u.useCallback((W,J)=>{if(!v||!G)return;let R=[];v.patchLayout(le=>(R=Bt({children:le},G,()=>W).children??[],R)),fr({source:"resize",componentPath:at(G),componentName:Al||null,patchKey:J==="height"?"height":"width",resizeAxis:J,timestamp:Date.now()})},[v,G,Al]),KM=u.useCallback((W,J)=>{if(!v||!G)return;let R=[];v.patchLayout(le=>(R=Bt({children:le},G,()=>W).children??[],R)),y.push({actionKind:"inline_text_edit",label:`resize ${J}`,snapshot:R})},[v,G,y]),VM=u.useCallback(()=>{if(!oe)return null;const W=oe.getBoundingClientRect(),J=Il||1;return{width:W.width/J,height:W.height/J}},[oe,Il]),pr=H4({node:S.selectedNode,widthControl:jM,heightControl:HM,scale:Il,measureStartSize:VM,onResize:FM,onResizeEnd:KM}),UM=u.useCallback((W,J)=>{pr.onHandlePointerDown(W,J)},[pr]),Tl=u.useCallback(()=>{const W=y.undo();!W||!v||v.setLayoutComponents(W.snapshot)},[y,v]),Dl=u.useCallback(()=>{const W=y.redo();!W||!v||v.setLayoutComponents(W.snapshot)},[y,v]);n2({onUndo:Tl,onRedo:Dl});const qM=u.useMemo(()=>({copy:ne,cut:pe,paste:Q,delete:Ce,openProps:El,selectParent:Z?ie:void 0,deselect:S.clearSelection}),[ne,pe,Q,Ce,El,Z,ie,S.clearSelection]);hp({handlers:qM,hasSelection:!!S.selectedPath}),u.useEffect(()=>(window.__g7LayoutEditorHasSelection=!!S.selectedPath,()=>{try{delete window.__g7LayoutEditorHasSelection}catch{}}),[S.selectedPath]),u.useEffect(()=>(window.__g7LayoutEditorHistory={undo:Tl,redo:Dl,canUndo:y.canUndo,canRedo:y.canRedo},()=>{try{delete window.__g7LayoutEditorHistory}catch{}}),[Tl,Dl,y.canUndo,y.canRedo]);const GM=u.useCallback(W=>{const J=L;F(null),J&&(async()=>{if((await A.commit(J.path,W)).kind==="created"&&v){const le=v.document?.raw?.components??[];y.push({actionKind:"inline_text_edit",label:"inline_create_key",snapshot:le})}})()},[L,A,v,y]),fy=u.useCallback(()=>{const W=L;F(null),A.trackCancel(W?.path??null)},[L,A]),YM=u.useCallback(()=>{F(null)},[]),XM=u.useCallback(async W=>{const J=L;if(!J)return null;const R=await A.keyifyChipValue(J.path,W);if(R.kind!=="created"||!R.translationKey)return null;const le=v?.document?.raw?.components??[];return y.push({actionKind:"inline_text_edit",label:"inline_keyify_chip",snapshot:le}),R.translationKey},[L,A,v,y]),JM=u.useCallback(()=>{const W=L;W&&(F(null),S.handleSelect("",{dataset:{editorPath:W.domPath}}),Oi(W.path,"translation"))},[L,S,Oi]),wt=u.useMemo(()=>{if(!L)return null;const W=v?.document?.raw?.components??k;return Pe({children:W},L.path)},[L,v?.document,k]),ZM=u.useCallback(async(W,J)=>{const R=L;if(!v||!R)return null;const le=typeof wt?.text=="string"?wt.text:"";let me;if(R.isParamKey)me=await Ar(f.templateIdentifier,f.locale,le,W,J.sourceId,J.path,"scalar");else{const He=Nt=>Nt.replace(/\$t:[a-zA-Z0-9._-]+/g,gt=>{const Mt=h(gt.slice(3));return Mt&&Mt!==gt.slice(3)&&!Mt.startsWith("$t:")?Mt:""}).replace(/\s+/g," ").trim();me=await ys(f.templateIdentifier,f.selectedRoute?.layoutName??null,f.locale,le,W,J.sourceId,J.path,"scalar",He,h)}if(me.kind==="error")return null;const xe=R.path;let Te=[];v.patchLayout(He=>(Te=Bt({children:He},xe,Mt=>({...Mt,text:me.text})).children??[],Te)),y.push({actionKind:"inline_text_edit",label:"inline_insert_binding",snapshot:Te});const we=me.kind==="ok"&&"key"in me?me.key:void 0;!R.isParamKey&&we&&F(He=>He&&He.path===xe?{...He,isParamKey:!0,customKey:we,chipValue:void 0,chipParamLabels:void 0}:He);try{window.dispatchEvent(new CustomEvent(kt,{detail:{templateIdentifier:f.templateIdentifier,locale:f.locale}}))}catch{}const _e=vt(me.text),Ee=R.customKey??_e?.key??"";return Ee?It(Ee,f.locale)??null:null},[v,L,wt,f.templateIdentifier,f.locale,f.selectedRoute,y,h]),QM=u.useCallback(async W=>{const J=L;if(!v||!J)return null;const R=typeof wt?.text=="string"?wt.text:"",le=vt(R),me=J.customKey??le?.key??null,xe=ko(R,W),Te=J.path;let we=[];return v.patchLayout(_e=>(we=Bt({children:_e},Te,Nt=>({...Nt,text:xe})).children??[],we)),y.push({actionKind:"inline_text_edit",label:"inline_remove_binding",snapshot:we}),me&&await Tr(f.templateIdentifier,me,W,f.locale),me?It(me,f.locale)??null:null},[v,L,wt,f.templateIdentifier,f.locale,y]),[zl,gy]=u.useState({}),RM=["fontSize","color","fontStyle","textDecoration","textAlign","letterSpacing","lineHeight","fontFamily"];u.useEffect(()=>{if(!L||!t||typeof MutationObserver>"u"){gy({});return}const W=`[data-editor-path="${dn(L.domPath)}"]`,J=()=>{const me=t.querySelector(W);if(!me)return;const xe={};for(const we of RM){const _e=me.style.getPropertyValue(we.replace(/([A-Z])/g,"-$1").toLowerCase());_e&&(xe[we]=_e)}const Te=Lg(t,L.domPath)??void 0;gy({className:me.className,style:xe,effectiveColor:Te})};J();const R=t.querySelector(W);if(!R)return;const le=new MutationObserver(J);return le.observe(R,{attributes:!0,attributeFilter:["class","style"]}),le.observe(t,{childList:!0,subtree:!0}),()=>le.disconnect()},[L?.path?at(L.path):null,t]);const hy=u.useCallback((W,J)=>{if(!v||!L)return;const R=L.path;let le=[],me=null;v.patchLayout(xe=>(le=Bt({children:xe},R,_e=>{const Ee=J(_e);return me=typeof Ee.name=="string"?Ee.name:null,Ee}).children??[],le)),y.push({actionKind:"inline_text_edit",label:`inline_format ${W}`,snapshot:le}),fr({source:"style_control",componentPath:at(R),componentName:me,patchKey:W,timestamp:Date.now()})},[v,L,y]),ft=u.useMemo(()=>{const W=S.selectedPath;if(!W||!x)return null;const J=[];let R=W;for(;R;){J.push(R);const le=R.match(/^(.*)\.children\.\d+$/);R=le?le[1]:null}for(const le of J){const me=Re(le);if(!me)continue;const xe=Pe(x,me),Te=typeof xe?.name=="string"?xe.name:null;if(!Te)continue;const we=qi(s,Te),_e=we?.canvasOverlay?.kind;if(typeof _e!="string"||!_e||!xe)continue;const Ee=we.canvasOverlay.params&&typeof we.canvasOverlay.params=="object"?we.canvasOverlay.params:void 0,He=le===W?null:W.slice(le.length+1);return{kind:_e,params:Ee,tablePath:le,tableNode:xe,selectedCellRel:He}}return null},[S.selectedPath,x,s]),_o=ft?P4(ft.kind):null,yy=u.useMemo(()=>!_o||!t||!ft?null:bo(t,ft.tablePath),[_o,t,ft,I,k]),e5=u.useMemo(()=>{if(!_o||!t||!ft)return[];const W=ft.tablePath,J=dn(W),R=[];return t.querySelectorAll(`[data-editor-path^="${J}.children."]`).forEach(xe=>{const Te=xe.dataset.editorPath??"";if(!Te.startsWith(W+"."))return;const we=Te.slice(W.length+1),_e=po(xe,t);_e&&R.push({path:we,top:_e.top,left:_e.left,width:_e.width,height:_e.height})}),t.querySelectorAll(`[data-editor-item-path^="${J}."]`).forEach(xe=>{const Te=xe.dataset.editorItemPath??"";if(!Te.startsWith(W+"."))return;const we=Te.slice(W.length+1),_e=po(xe,t);_e&&R.push({path:we,top:_e.top,left:_e.left,width:_e.width,height:_e.height})}),R},[_o,t,ft,I,k]),t5=u.useCallback(W=>{const J=ft?Re(ft.tablePath):null;if(!v||!J)return;let R=[];v.patchLayout(le=>(R=Bt({children:le},J,()=>W).children??[],R)),y.push({actionKind:"inline_text_edit",label:"inplace_edit",snapshot:R}),fr({source:"style_control",componentPath:ft?.tablePath??"",componentName:typeof W.name=="string"?W.name:null,patchKey:"canvas_inplace",timestamp:Date.now()})},[v,G,y]);return t?i.jsxs("div",{className:"g7le-editor-canvas-overlay","data-testid":"g7le-editor-canvas-overlay",style:{position:"absolute",inset:0,pointerEvents:"none"},children:[!T&&WM(),T&&i.jsxs("div",{"data-testid":"g7le-canvas-pick-banner",style:{position:"absolute",top:8,left:"50%",transform:"translateX(-50%)",zIndex:9999,display:"flex",alignItems:"center",gap:12,padding:"8px 14px",background:"#1e293b",color:"#fff",borderRadius:8,fontSize:13,boxShadow:"0 4px 12px rgba(0,0,0,0.25)",pointerEvents:"auto"},children:[i.jsxs("span",{children:["🎯 ",h("layout_editor.target_picker.canvas_pick_hint")]}),i.jsx("button",{type:"button","data-testid":"g7le-canvas-pick-cancel",onClick:()=>X(T,null),style:{border:"1px solid #475569",borderRadius:6,background:"transparent",color:"#cbd5e1",cursor:"pointer",fontSize:12,padding:"3px 10px"},children:h("layout_editor.target_picker.cancel")})]}),f.editMode!=="route"&&i.jsx(mA,{frameEl:t,components:k,editMode:f.editMode,currentExtensionId:$,editableRootPath:C}),L&&((L.isParamKey&&L.customKey||L.chipValue)&&wt?i.jsxs(i.Fragment,{children:[wt&&i.jsx(zg,{node:wt,spec:s,t:h,onApplyControl:hy,box:L.box,scope:{colorScheme:f.previewColorScheme==="dark"?"dark":"base",breakpoint:Rr(f.previewDevice,wt,f.previewCustomWidth)}}),i.jsx(R4,{box:L.box,node:wt,customKey:L.customKey??null,initialChipValue:L.chipValue,chipParamLabels:L.chipParamLabels,onKeyify:XM,templateIdentifier:f.templateIdentifier,locale:f.locale,t:h,candidates:cn,onInsertBinding:ZM,onRemoveBinding:QM,onCommit:YM,onCancel:fy})]}):i.jsxs(i.Fragment,{children:[wt&&i.jsx(zg,{node:wt,spec:s,t:h,onApplyControl:hy,box:L.box,scope:{colorScheme:f.previewColorScheme==="dark"?"dark":"base",breakpoint:wt?Rr(f.previewDevice,wt,f.previewCustomWidth):"base"}}),i.jsx(Q4,{box:L.box,initialValue:L.initialValue,isCustomKey:L.isCustomKey,t:h,onCommit:GM,onCancel:fy,mirrorClassName:zl.className,mirrorStyle:zl.style,nodeEffectiveColor:zl.effectiveColor??L.nodeEffectiveColor,onOpenTranslations:JM})]})),!T&&i.jsx(c2,{hoverBox:D,selectedBox:I,lockKind:S.selectedLockKind,navAffordance:S.selectedNavAffordance,onEditProps:El,onDuplicate:be,onDelete:Ce,onSeparateBranch:$e?.mode==="separate"?U:void 0,separateBranchLabel:ke?.separate,onMergeBranch:$e?.mode==="merge"?ue:void 0,mergeBranchLabel:ke?.merge,definedDeviceBranches:he,onJumpToDevice:he.length>0?j:void 0,onLinkEditDestination:Ae,onEditBase:dt,onEditExtension:pt,extensionLabel:De,baseLayoutLabel:S.selectedBaseLayout,dataSourceLabel:S.selectedDataSourceId,branchLabel:ee,onEditIteration:S.selectedIsIteration&&f.editMode!=="iteration_item"?ot:void 0,resizeEnabledAxes:pr.enabledAxes,onResizeHandlePointerDown:pr.enabledAxes.width||pr.enabledAxes.height?UM:void 0,selectedName:Se,onSelectParent:Z?ie:void 0}),T&&B&&i.jsx("div",{"data-testid":"g7le-canvas-pick-hover","data-pick-has-id":B.hasId?"true":"false",style:{position:"absolute",left:B.box.left,top:B.box.top,width:B.box.width,height:B.box.height,pointerEvents:"none",zIndex:9998,boxSizing:"border-box",border:B.hasId?"2px solid #2563eb":"2px dashed #94a3b8",background:B.hasId?"rgba(37,99,235,0.10)":"repeating-linear-gradient(45deg, rgba(148,163,184,0.18) 0, rgba(148,163,184,0.18) 6px, transparent 6px, transparent 12px)",cursor:B.hasId?"pointer":"not-allowed"},children:!B.hasId&&i.jsx("span",{"data-testid":"g7le-canvas-pick-hover-hint",style:{position:"absolute",top:0,left:0,transform:"translateY(-100%)",background:"#475569",color:"#fff",fontSize:11,lineHeight:1.4,padding:"2px 6px",borderRadius:4,whiteSpace:"nowrap"},children:h("layout_editor.target_picker.needs_id")})}),!T&&i.jsx(y2,{selectedBox:Ve,points:Ie.points,onAddRequest:Je}),_o&&ft&&yy&&S.selectedLockKind==="none"&&i.jsx(_o,{node:ft.tableNode,params:ft.params,nodeBox:yy,cellBoxes:e5,selectedCellPath:ft.selectedCellRel??void 0,colorScheme:f.previewColorScheme==="dark"?"dark":"light",t:h,onPatchNode:t5,onInsertChild:ae,onRemoveChild:W=>{const J=Re(W);if(!v||!J)return;let R=[];v.patchLayout(le=>(R=as({children:le},J).children??[],R)),y.push({actionKind:"remove",label:"inplace_remove",snapshot:R})},onRequestInlineEdit:W=>{ft&&Y(`${ft.tablePath}.${W}`)}}),v&&i.jsx(pA,{frameEl:t,nesting:o,editMode:f.editMode,currentExtensionId:$,editableRootPath:C,components:k,patchLayout:v.patchLayout,pushHistory:de,selectedPath:S.selectedPath,onSelectPath:W=>S.handleSelect("",{dataset:{editorPath:W}}),onMovePath:W=>S.handleSelect("",{dataset:{editorPath:W}}),onRequestInlineEdit:Y})]}):null}function Mg(e,t){if(t.length===0)return null;const o=Xa({children:e},t);return o&&typeof o.name=="string"?o.name:null}function Xa(e,t){return Pe(e,t)}function dn(e){return typeof CSS<"u"&&typeof CSS.escape=="function"?CSS.escape(e):e.replace(/[^a-zA-Z0-9_\-.]/g,t=>`\\${t}`)}function Lg(e,t){if(!t)return null;const n=e.querySelector(`[data-editor-path="${dn(t)}"]`);if(!n||typeof window.getComputedStyle!="function")return null;let o=n;const r=n.ownerDocument.createTreeWalker(n,NodeFilter.SHOW_TEXT);let s=r.nextNode();for(;s;){if((s.textContent??"").trim().length>0){const a=s.parentElement;if(a&&n.contains(a)){o=a;break}}s=r.nextNode()}try{return window.getComputedStyle(o).color||null}catch{return null}}function bo(e,t){if(!t)return null;const n=e.querySelector(`[data-editor-path="${dn(t)}"]`);if(n){const c=po(n,e);return c&&fo(c,e)?c:null}const o=e.querySelectorAll(`[data-editor-path^="${dn(t)}.iteration."]`);let r=1/0,s=1/0,a=-1/0,l=-1/0,d=!1;return o.forEach(c=>{const p=c.dataset.editorPath??"";if(!new RegExp("\\.iteration\\.\\d+$").test(p))return;const f=po(c,e);f&&fo(f,e)&&(d=!0,r=Math.min(r,f.left),s=Math.min(s,f.top),a=Math.max(a,f.left+f.width),l=Math.max(l,f.top+f.height))}),d?{left:r,top:s,width:a-r,height:l-s,scale:1}:null}function aD({isEditMode:e,editorSpec:t,sampleOverride:n}){const o=t?.sampleData;return u.useMemo(()=>dv({spec:o,overrideSpec:n,onMatch:r=>{my(r)}}),[e,o,n])}const lD=Lt().ComponentRegistry;function dD(e){return e.replace(/[\t\n\r]/g,"").replace(/\\/g,"/").replace(/^([a-z][a-z0-9+.\-]*:)?\/{2,}/i,"$1//")}function cD(e,t){try{const n=typeof window<"u"&&window.location?.protocol||"https:",o=t||(typeof window<"u"?window.location?.origin:void 0)||"https://localhost",r=e.startsWith("//")?`${n}${e}`:e,s=new URL(r,o);return s.protocol!=="http:"&&s.protocol!=="https:"?null:s.hostname.toLowerCase()}catch{return null}}function Ja(){const e=typeof window<"u"?window.G7Config?.trustedScriptHosts:void 0;return Array.isArray(e)?e.map(t=>String(t).toLowerCase()):[]}function Pg(e,t){if(typeof e!="string")return!1;const n=e.trim();if(n==="")return!1;const o=dD(n),r=o.startsWith("//"),s=/^[a-z][a-z0-9+.-]*:/i.test(o);if(!r&&!s&&o.startsWith("/"))return!0;const a=cD(o),l=t??Ja();return a!==null&&l.map(d=>String(d).toLowerCase()).includes(a)}function uD(e){return e.split(/[-_]/).map(t=>t.charAt(0).toUpperCase()+t.slice(1).toLowerCase()).join("")}async function pD(e){const t=typeof window<"u"?window.localStorage?.getItem("auth_token"):null,n={Accept:"application/json"};t&&(n.Authorization=`Bearer ${t}`);const o=await fetch(`/api/admin/templates/${encodeURIComponent(e)}/editor-assets`,{headers:n,credentials:"same-origin"});if(!o.ok){const s=await o.json().catch(()=>null);throw ti(o.status,s,"assets")}const r=await o.json();return r.data||r}async function fD(e){try{if(typeof fetch!="function")return 0;const t=await fetch(nn(`/api/templates/${encodeURIComponent(e)}/config`,"json"),{credentials:"same-origin",headers:{Accept:"application/json"}});if(!t.ok)return 0;const n=await t.json().catch(()=>null),o=n?.data?.cache_version??n?.cache_version;return typeof o=="number"&&o>0?o:0}catch{return 0}}function gD(e){return new Promise((t,n)=>{if(typeof document>"u"){n(new Error("document undefined"));return}if(!Pg(e,Ja())){n(new Error(`Blocked untrusted script src (same-origin path or declared trusted host required): ${e}`));return}if(document.querySelector(`script[data-g7le-asset="${e}"]`)){t();return}const r=document.createElement("script");r.src=e,r.async=!1,r.setAttribute("data-g7le-asset",e),r.addEventListener("load",()=>t()),r.addEventListener("error",()=>n(new Error(`script load failed: ${e}`))),document.head.appendChild(r)})}async function hD(e){if(typeof document>"u")return;const t=`[data-g7le-asset="${e}"]`;if(document.querySelector(`link${t}, style${t}`))return;if(e.includes("/api/admin/"))try{const r=typeof window<"u"?window.localStorage?.getItem("auth_token"):null,s={Accept:"text/css"};r&&(s.Authorization=`Bearer ${r}`);const a=await fetch(e,{headers:s,credentials:"same-origin"});if(a.ok){const l=await a.text(),d=document.createElement("style");d.textContent=l,d.setAttribute("data-g7le-asset",e),document.head.appendChild(d);return}}catch{}const o=document.createElement("link");o.rel="stylesheet",o.href=e,o.setAttribute("data-g7le-asset",e),document.head.appendChild(o)}function yD(e,t,n="user"){const[o,r]=u.useState({componentRegistry:null,translationEngine:null,isReady:!1,error:null});return u.useEffect(()=>{let s=!1;return(async()=>{r(a=>({...a,isReady:!1,error:null}));try{const a=await pD(e);if(s)return;for(const b of a.css??[])if(await hD(b),s)return;const l=uD(e);if(!(typeof window<"u"&&typeof window[l]=="object")){for(const b of a.js??[])if(await gD(b),s)return}const c=lD.createIsolatedInstance();if(await c.loadComponents(e,n),s)return;const p=await fD(e);if(s)return;const f=new Pt({defaultLocale:t});if(p>0&&f.setCacheVersion(p),await f.loadTranslations(e,t),s)return;vd(f,e);try{const b=Pt.getInstance();p>b.getCacheVersion()&&b.setCacheVersion(p),await b.loadTranslations(e,t),vd(b,e)}catch{}if(s)return;r({componentRegistry:c,translationEngine:f,isReady:!0,error:null})}catch(a){if(s)return;const l=Ip(a)?a:uo(a,"assets");r({componentRegistry:null,translationEngine:null,isReady:!1,error:l})}})(),()=>{s=!0}},[e,t,n]),o}const Og=Lt().AuthManager;function bD(e){switch(e){case"unauthorized":return{icon:"🔒",tone:"blue",titleKey:"layout_editor.access_error.unauthorized.title",messageKey:"layout_editor.access_error.unauthorized.message",showRequiredPermissions:!0};case"forbidden":return{icon:"⛔",tone:"rose",titleKey:"layout_editor.access_error.forbidden.title",messageKey:"layout_editor.access_error.forbidden.message",showRequiredPermissions:!0};case"not_found":return{icon:"🗂️",tone:"slate",titleKey:"layout_editor.access_error.not_found.title",messageKey:"layout_editor.access_error.not_found.message",showRequiredPermissions:!1};case"server_error":return{icon:"🛠️",tone:"amber",titleKey:"layout_editor.access_error.server_error.title",messageKey:"layout_editor.access_error.server_error.message",showRequiredPermissions:!1};case"network":return{icon:"📡",tone:"amber",titleKey:"layout_editor.access_error.network.title",messageKey:"layout_editor.access_error.network.message",showRequiredPermissions:!1};default:return{icon:"⚠️",tone:"slate",titleKey:"layout_editor.access_error.unknown.title",messageKey:"layout_editor.access_error.unknown.message",showRequiredPermissions:!1}}}const mD={blue:{bg:"#eff6ff",border:"#bfdbfe",iconBg:"#dbeafe"},rose:{bg:"#fff1f2",border:"#fecdd3",iconBg:"#ffe4e6"},amber:{bg:"#fffbeb",border:"#fde68a",iconBg:"#fef3c7"},slate:{bg:"#f8fafc",border:"#e2e8f0",iconBg:"#f1f5f9"}};function vD({error:e}){return u.useEffect(()=>{if(e.kind!=="unauthorized"||typeof window>"u")return;const t=Og.getInstance(),n=`${window.location.pathname}${window.location.search}`,o=t.getLoginRedirectUrl("admin",n,"session_expired"),r=window.setTimeout(()=>{window.location.href=o},600);return()=>window.clearTimeout(r)},[e.kind]),null}function Za({error:e}){const{t}=et(),n=bD(e.kind),o=mD[n.tone],r=()=>{typeof window<"u"&&(window.location.href="/admin")},s=()=>{typeof window<"u"&&window.location.reload()},a=()=>{if(typeof window>"u")return;const l=Og.getInstance(),d=`${window.location.pathname}${window.location.search}`;window.location.href=l.getLoginRedirectUrl("admin",d)};return i.jsxs("div",{className:"g7le-access-error","data-testid":"g7le-access-error","data-error-kind":e.kind,style:{flex:1,display:"flex",alignItems:"center",justifyContent:"center",padding:24,background:"#f1f5f9"},children:[i.jsx(vD,{error:e}),i.jsxs("div",{style:{maxWidth:480,width:"100%",background:o.bg,border:`1px solid ${o.border}`,borderRadius:12,padding:"28px 32px",boxShadow:"0 2px 8px rgba(15, 23, 42, 0.05)",textAlign:"center"},children:[i.jsx("div",{style:{width:64,height:64,margin:"0 auto 16px",borderRadius:"50%",background:o.iconBg,display:"flex",alignItems:"center",justifyContent:"center",fontSize:32,lineHeight:1},"aria-hidden":"true",children:n.icon}),i.jsx("h2",{"data-testid":"g7le-access-error-title",style:{margin:"0 0 8px",fontSize:18,fontWeight:700,color:"#0f172a"},children:t(n.titleKey)}),i.jsx("p",{"data-testid":"g7le-access-error-message",style:{margin:"0 0 16px",fontSize:13,lineHeight:1.6,color:"#475569"},children:t(n.messageKey)}),n.showRequiredPermissions&&e.requiredPermissions&&i.jsxs("div",{"data-testid":"g7le-access-error-permissions",style:{display:"inline-flex",flexWrap:"wrap",gap:6,justifyContent:"center",padding:"8px 12px",marginBottom:16,background:"rgba(15, 23, 42, 0.04)",borderRadius:8,fontSize:12,color:"#475569"},children:[i.jsx("span",{style:{fontWeight:600},children:t("layout_editor.access_error.required_permission_label")}),e.requiredPermissions.split(/[,\s]+/).filter(Boolean).map(l=>i.jsx("code",{style:{padding:"2px 6px",background:"#fff",border:"1px solid #e2e8f0",borderRadius:4,fontFamily:"ui-monospace, SFMono-Regular, Menlo, monospace",fontSize:11,color:"#1e293b"},children:l},l))]}),e.message&&i.jsxs("details",{"data-testid":"g7le-access-error-detail",style:{marginBottom:16,fontSize:11,color:"#94a3b8"},children:[i.jsx("summary",{style:{cursor:"pointer"},children:t("layout_editor.access_error.detail_summary")}),i.jsxs("pre",{style:{marginTop:8,padding:8,background:"#fff",border:"1px solid #e2e8f0",borderRadius:6,overflowX:"auto",whiteSpace:"pre-wrap",wordBreak:"break-word",textAlign:"left",fontSize:11,color:"#475569"},children:[e.status>0?`HTTP ${e.status} +`:"",e.message]})]}),i.jsxs("div",{style:{display:"flex",justifyContent:"center",gap:8,flexWrap:"wrap"},children:[e.kind==="unauthorized"&&i.jsx("button",{type:"button","data-testid":"g7le-access-error-action-signin",onClick:a,style:Qa,children:t("layout_editor.access_error.action.sign_in")}),e.kind==="forbidden"&&i.jsx("button",{type:"button","data-testid":"g7le-access-error-action-home",onClick:r,style:Qa,children:t("layout_editor.access_error.action.go_home")}),(e.kind==="not_found"||e.kind==="server_error"||e.kind==="network"||e.kind==="unknown")&&i.jsx("button",{type:"button","data-testid":"g7le-access-error-action-retry",onClick:s,style:Qa,children:t("layout_editor.access_error.action.retry")}),e.kind!=="unauthorized"&&i.jsx("button",{type:"button","data-testid":"g7le-access-error-action-home-secondary",onClick:r,style:_D,children:t("layout_editor.access_error.action.go_admin_home")})]})]})]})}const Qa={padding:"8px 16px",background:"#2563eb",color:"#fff",border:"1px solid #1d4ed8",borderRadius:6,fontSize:13,fontWeight:600,cursor:"pointer"},_D={padding:"8px 16px",background:"#fff",color:"#475569",border:"1px solid #cbd5e1",borderRadius:6,fontSize:13,fontWeight:500,cursor:"pointer"};function xD(e){const t=window;if(typeof t>"u")return{getSnapshot:()=>({globalState:{},localState:{}}),restore:()=>{}};const n=t.G7Core||{},o={templateApp:t.__templateApp,g7CoreState:n.state,g7CoreDispatch:n.dispatch,g7CoreData:n.data,g7CoreModal:n.modal,g7CoreToast:n.toast};let r={...e.initialGlobalState??{}};r._local||(r._local={});const s=[],a=()=>{const c={globalState:{...r},localState:{...r._local??{}}};for(const p of s)try{p(r)}catch{}e.onChange?.(c)},l={},d={getGlobalState:()=>r,setGlobalState:(c,p)=>{typeof c=="function"?r=c(r):r={...r,...c},a()},onGlobalStateChange:c=>(s.push(c),()=>{const p=s.indexOf(c);p>=0&&s.splice(p,1)}),get globalState(){return r},getActionDispatcher:()=>e.actionDispatcher,getDataSource:c=>l[c]??null,setDataSource:(c,p)=>{l[c]=p},refetchDataSource:async c=>null,updateDataSourceItem:(c,p,f)=>l[c]??null,getLocale:()=>e.locale,changeLocale:c=>{},getRouter:()=>({navigate:()=>{},replace:()=>{},push:()=>{}}),updateQueryParams:()=>{}};return t.__templateApp=d,n&&typeof n.dispatch=="function"&&(n.dispatch=(c,p)=>{try{return e.actionDispatcher.executeAction(c,p??{})}catch{return}}),n&&typeof n.modal=="object"&&n.modal!==null&&(n.modal={...n.modal,open:()=>{},close:()=>{},closeAll:()=>{}}),n&&typeof n.toast=="object"&&n.toast!==null&&(n.toast={...n.toast,push:()=>{},dismiss:()=>{}}),{getSnapshot:()=>({globalState:{...r},localState:{...r._local??{}}}),restore:()=>{t.__templateApp===d&&(t.__templateApp=o.templateApp),t.G7Core&&(o.g7CoreState!==void 0&&(t.G7Core.state=o.g7CoreState),o.g7CoreDispatch!==void 0&&(t.G7Core.dispatch=o.g7CoreDispatch),o.g7CoreData!==void 0&&(t.G7Core.data=o.g7CoreData),o.g7CoreModal!==void 0&&(t.G7Core.modal=o.g7CoreModal),o.g7CoreToast!==void 0&&(t.G7Core.toast=o.g7CoreToast)),s.length=0}}}const wD=new Set(["id","userId","postId","orderId","productId","categoryId","commentId","noticeId","inquiryId","addressId","wishlistId","notificationId","roleId","menuId"]);function kD(e){if(!e)return{};const t={},n=/:([a-zA-Z_][a-zA-Z0-9_]*)/g;let o;for(;(o=n.exec(e))!==null;){const r=o[1];t[r]=SD(r)}return t}function SD(e){return wD.has(e)||/Id$|_id$/.test(e)?"1":"sample"}const CD=wo("LayoutEditor");function or(e){return typeof e=="object"&&e!==null&&!Array.isArray(e)}function Ci(e){if(Array.isArray(e))return e.map(t=>Ci(t));if(or(e)){const t={};for(const[n,o]of Object.entries(e))t[n]=Ci(o);return t}return e}function Bg(e,t="",n=new Set){for(const[o,r]of Object.entries(e)){const s=t?`${t}.${o}`:o;or(r)?Bg(r,s,n):n.add(s)}return n}function Wg(e,t,n,o,r,s=""){for(const[a,l]of Object.entries(t)){const d=s?`${s}.${a}`:a;if(n.has(d)){r(`[LayoutEditor] sampleGlobal key conflict: extension '${o}' override of core key '${d}' ignored`);continue}const c=e[a];or(l)&&or(c)?Wg(c,l,n,o,r,d):e[a]=Ci(l)}}function Ra(e){const t=e.warn??(r=>CD.warn(r)),n=Ci(e.coreSeed??{});e.isGuestOnly&&"currentUser"in n&&delete n.currentUser;const o=Bg(n);for(const r of e.sources)or(r.sampleGlobal)&&Wg(n,r.sampleGlobal,o,r.id,t);return e.isGuestOnly&&"currentUser"in n&&delete n.currentUser,n}function $D(e){const t={};if(!e||typeof e!="object"||Array.isArray(e))return t;for(const[n,o]of Object.entries(e))n.startsWith("$")&&(t[n]=o);return t}function ED(e){const t={};if(!e||typeof e!="object"||Array.isArray(e))return t;for(const[n,o]of Object.entries(e))n.startsWith("$")||(t[n]=o);return t}function ID(e){if(typeof e!="string")return null;const t=/^\s*\{\{\s*([\s\S]*?)\s*\}\}\s*$/.exec(e);if(!t)return null;let n=t[1];const o=n.indexOf("??");return o>=0&&(n=n.slice(0,o)),n=n.replace(/\?\./g,".").replace(/\s+/g,""),n===""||!/^[A-Za-z_$][A-Za-z0-9_$]*(\.[A-Za-z_$][A-Za-z0-9_$]*)*$/.test(n)?null:n.split(".")}function AD(e,t){const n=ID(t);if(!n||n.length===0)return e;let o=e;for(let a=0;ao.map(r=>{if(!r||typeof r!="object")return r;const s=r;let a=s;if(typeof s.slot=="string"&&!t.has(s.slot)){const{slot:l,...d}=s;a={...d,__editorSlotName:l}}if(Array.isArray(a.children)){const l=n(a.children);l!==a.children&&(a={...a,children:l})}return a});return n(e)}function St(e){return typeof e=="object"&&e!==null&&!Array.isArray(e)}function Qt(e){if(Array.isArray(e))return e.map(t=>Qt(t));if(St(e)){const t={};for(const[n,o]of Object.entries(e))t[n]=Qt(o);return t}return e}function $i(e,t){for(const[n,o]of Object.entries(t)){const r=e[n];St(o)&&St(r)?$i(r,o):e[n]=Qt(o)}}function zD(e){const t=[];let n="";for(let o=0;o0&&(t.push(n),n="");const s=e[o+1];if(s==="'"||s==='"'){const l=e.indexOf(`${s}]`,o+2);if(l!==-1){t.push(e.slice(o+2,l)),o=l+1;continue}}const a=e.indexOf("]",o+1);if(a!==-1){t.push(e.slice(o+1,a)),o=a;continue}n+=r}else r==="."?n.length>0&&(t.push(n),n=""):n+=r}return n.length>0&&t.push(n),t.filter(o=>o.length>0)}function el(e,t,n){const o=zD(t);if(o.length===0)return;let r=e;for(let s=0;s0?t:void 0}const nl="g7le:editor-t-ready",Fg="data-g7le-vh-expanded";function MD(e,t){if(!e||!t)return;const n=t;e.querySelectorAll("*").forEach(r=>{const s=window.getComputedStyle(r),a=parseFloat(s.height),l=Number.isFinite(a)&&Math.abs(a-n)<=2,d=parseFloat(s.minHeight),c=Number.isFinite(d)&&Math.abs(d-n)<=2,p=s.overflowY==="auto"||s.overflowY==="scroll"||s.overflowY==="hidden"||s.overflow==="auto"||s.overflow==="scroll"||s.overflow==="hidden";l&&p?(r.style.setProperty("height","auto","important"),c&&r.style.setProperty("min-height","0","important"),r.style.setProperty("overflow","visible","important"),r.setAttribute(Fg,"1")):(s.overflowY==="auto"||s.overflowY==="scroll")&&r.scrollHeight>r.clientHeight+4&&r.clientHeight>120&&(r.style.setProperty("overflow","visible","important"),r.style.setProperty("height","auto","important"),r.setAttribute(Fg,"1"))})}const LD=[];function PD(e={}){const{manifest:t=null,nesting:n=null,componentPalette:o=null,spec:r=null,permissionCandidates:s,sampleGlobalSources:a=LD,resolveRouteMatch:l,onNavigateToDestination:d,onSessionAddedPathsChange:c}=e,{state:p}=Qe(),f=Kt(),b=Wp(),{document:g,isLoading:m,error:h}=f??b,v=u.useRef(null),[y,_]=u.useState(null),{deviceWidth:w,scale:k,colorScheme:x}=Ya(),$=u.useMemo(()=>p.activeStateId?p.availableStates.find(ce=>ce.id===p.activeStateId)??null:null,[p.activeStateId,p.availableStates]),C=u.useMemo(()=>Hg($),[$]),S=aD({isEditMode:!0,editorSpec:r??void 0,sampleOverride:C}),{t:D}=et(),{componentRegistry:E,translationEngine:I,isReady:N,error:T}=yD(p.templateIdentifier,p.locale),O=u.useRef(null);O.current===null&&(O.current=new Id);const M=u.useRef(null);if(M.current===null){M.current=new L7({navigate:()=>{}}),M.current.setPreviewMode(!0);try{const q=window.__templateApp?.getActionDispatcher?.();if(q&&typeof q.getRegisteredHandlers=="function"){const se=q.getRegisteredHandlers();for(const ae of se){if(!ae.includes("."))continue;const be=typeof q.getHandler=="function"?q.getHandler(ae):void 0;typeof be=="function"&&M.current.registerHandler(ae,be,{category:"plugin",source:"preview-canvas-cloned-from-host"})}}}catch{}}u.useEffect(()=>{if(!N)return;const ce=M.current;if(ce)try{const q=window.G7Core?.getActionDispatcher?.();if(!q||typeof q.getRegisteredHandlers!="function")return;const se=q.getRegisteredHandlers();for(const ae of se){if(!ae.includes(".")||typeof ce.getHandler=="function"&&ce.getHandler(ae))continue;const be=typeof q.getHandler=="function"?q.getHandler(ae):void 0;typeof be=="function"&&ce.registerHandler(ae,be,{category:"plugin",source:"preview-canvas-iife-reclone"})}}catch{}},[N]);const B=u.useMemo(()=>new P7({sampleProvider:S,actionDispatcher:M.current}),[S]),z=u.useMemo(()=>g?.raw?.meta?.guest_only===!0,[g]),H=u.useMemo(()=>Ra({sources:a,isGuestOnly:z}),[a,z]),P=u.useMemo(()=>{const ce=OD(tl($),I,{templateId:p.templateIdentifier,locale:p.locale}),q=H._local,{global:se,local:ae}=ND({globalBaseline:H,localBaseline:q??{},patch:$?.initialState??null,formErrors:ce});return{...se,_local:ae}},[H,$,I,p.templateIdentifier,p.locale]),A=u.useMemo(()=>!$||!(!!$.initialState?.local||!!tl($))?null:{...P._local??{},_forceLocalInit:$.id,_merge:"deep"},[$,P]),L=ce=>typeof ce=="object"&&ce!==null&&!Array.isArray(ce),F=u.useMemo(()=>L($?.initialState?.query)?$.initialState.query:null,[$]),Y=u.useMemo(()=>L($?.initialState?.route)?$.initialState.route:null,[$]),[V,X]=u.useState(()=>Ra({sources:[],isGuestOnly:!1})),Z=u.useRef(null);u.useEffect(()=>{const ce={...P},q=xD({actionDispatcher:M.current,locale:p.locale,initialGlobalState:ce,onChange:ae=>{X(ae.globalState)}});Z.current=q;const se=window.__templateApp;return se?.setGlobalState&&M.current.setGlobalStateUpdater((ae,be)=>{se.setGlobalState(ae,be)}),X(ce),Ll({kind:"applyPatch",activeStateId:p.activeStateId,routePath:p.selectedRoute?.path??null,hasInitialPatch:!!$?.initialState,hasFormErrors:!!tl($),hasSampleOverride:!!Hg($)}),()=>{q.restore(),Z.current=null}},[p.locale,P]),u.useEffect(()=>{if(!I)return;const ce=window.G7Core;if(!ce||typeof ce.t!="function")return;const q=ce.t,se=p.templateIdentifier,ae=p.locale;return ce.t=(be,Ce)=>{try{const $e=Ce?"|"+Object.entries(Ce).map(([ue,ge])=>`${ue}=${ge}`).join("|"):void 0,U=I.translate(be,{templateId:se,locale:ae},$e);if(U!==be&&!U.startsWith("$t:"))return U}catch{}return q(be,Ce)},typeof window<"u"&&window.dispatchEvent(new CustomEvent(nl)),()=>{window.G7Core?.t&&window.G7Core.t!==q&&(window.G7Core.t=q)}},[I,p.templateIdentifier,p.locale]);const ie=u.useMemo(()=>{const ce=g?.raw?.components??[];return p.editMode!=="base"?ce:DD(ce)},[g,p.editMode]),oe=g?.raw?.__editability,G=g?.raw?.__extensionId,ee=u.useMemo(()=>p.editMode!=="extension"||oe!=="ok"?[]:typeof G!="number"?[]:BD(ie,G),[p.editMode,oe,G,ie]),[fe,K]=u.useState(null),[te,ye]=u.useState(!1),Se=g?.layoutName??null,De=u.useMemo(()=>{const ce=g?.raw?.scripts??[];return Array.isArray(ce)?ce.map(q=>q&&typeof q.src=="string"?q.src:"").join("|"):""},[g]);u.useEffect(()=>{ye(!1)},[Se,De]),u.useEffect(()=>{let ce=!1;const q=g?.raw?.scripts??[];if(!Array.isArray(q)||q.length===0){ye(!0);return}const se=[];for(const ae of q){if(!ae?.src||typeof ae.src!="string")continue;if(!Pg(ae.src,Ja())){console.warn(`[PreviewCanvas] Blocked untrusted script src (same-origin path or declared trusted host required): ${ae.src}`);continue}const be=ae.id?`script#${CSS.escape(ae.id)}, script[data-g7le-canvas-script="${ae.src}"]`:`script[data-g7le-canvas-script="${ae.src}"], script[src="${ae.src}"]`,Ce=window.document.querySelector(be);if(Ce){const $e=Ce.hasAttribute("data-g7le-canvas-script");if(Ce.hasAttribute("data-g7le-script-loaded")||!$e)continue;se.push(new Promise(ue=>{if(Ce.hasAttribute("data-g7le-script-loaded")){ue();return}Ce.addEventListener("load",()=>ue()),Ce.addEventListener("error",()=>ue())}));continue}se.push(new Promise($e=>{const U=window.document.createElement("script");U.src=ae.src,U.async=!1,ae.id&&(U.id=ae.id),U.setAttribute("data-g7le-canvas-script",ae.src);const ue=()=>{U.setAttribute("data-g7le-script-loaded","1"),$e()};U.addEventListener("load",ue),U.addEventListener("error",ue),window.document.head.appendChild(U)}))}if(se.length===0){ye(!0);return}return Promise.all(se).then(()=>{ce||requestAnimationFrame(()=>{requestAnimationFrame(()=>{ce||ye(!0)})})}),()=>{ce=!0}},[g]);const Ie=u.useMemo(()=>g?.raw?.data_sources??[],[g]),Ve=u.useMemo(()=>g?.raw?.computed??{},[g]),[ze,Be]=u.useState({}),Xe=u.useMemo(()=>{if(p.editMode!=="iteration_item")return ze;const ce=g?.iterationContext;if(!ce)return ze;const q=g?.raw?.components??[],se=Pe({children:q},ce.sourceIndexPath),ae=se&&se.iteration&&typeof se.iteration.source=="string"?se.iteration.source:null;return ae?AD(ze,ae):ze},[p.editMode,ze,g]),[Ne,Je]=u.useState(0);u.useEffect(()=>{if(typeof window>"u")return;const ce=()=>Je(q=>q+1);return window.addEventListener(kt,ce),()=>window.removeEventListener(kt,ce)},[]);const ve=u.useMemo(()=>kD(p.selectedRoute?.path??null),[p.selectedRoute?.path]);if(u.useEffect(()=>{let ce=!1;return(async()=>{if(Ie.length===0){ce||Be({});return}try{const q=await B.fetchDataSources(Ie,ve,new URLSearchParams,void 0,void 0);ce||Be(q)}catch{ce||Be({})}})(),()=>{ce=!0}},[Ie,B,ve]),u.useEffect(()=>{const ce=y;if(!ce||typeof window>"u")return;let q=0,se=0,ae=0;const be=()=>MD(ce,window.innerHeight);q=window.requestAnimationFrame(()=>{se=window.requestAnimationFrame(be)});let Ce=null;return typeof MutationObserver<"u"&&(Ce=new MutationObserver(()=>{ae||(ae=window.requestAnimationFrame(()=>{ae=0,be()}))}),Ce.observe(ce,{childList:!0,subtree:!0})),()=>{window.cancelAnimationFrame(q),window.cancelAnimationFrame(se),ae&&window.cancelAnimationFrame(ae),Ce&&Ce.disconnect()}},[y,ie,te,w,ze]),u.useEffect(()=>{const ce=y;if(!ce||typeof window>"u"||p.editMode!=="extension"||oe!=="ok"||ee.length===0){K(null);return}let q=0,se=0;const ae=ue=>{if(!ue)return!1;const ge=ue.getBoundingClientRect();return ge.width>0&&ge.height>0},be=ue=>{const ge=ce.querySelector(`[data-editor-path="${ue}"]`);if(ae(ge))return!0;const ke=ce.querySelectorAll(`[data-editor-path^="${ue}.children."]`);for(const he of Array.from(ke))if(ae(he))return!0;return!1},Ce=()=>{K(ee.some(be))};q=window.requestAnimationFrame(()=>{se=window.requestAnimationFrame(Ce)});let $e=null,U=0;return typeof MutationObserver<"u"&&($e=new MutationObserver(()=>{U||(U=window.requestAnimationFrame(()=>{U=0,Ce()}))}),$e.observe(ce,{childList:!0,subtree:!0})),()=>{window.cancelAnimationFrame(q),window.cancelAnimationFrame(se),U&&window.cancelAnimationFrame(U),$e&&$e.disconnect()}},[y,p.editMode,oe,ee,te,ze]),u.useEffect(()=>{if(typeof window>"u"||p.editMode!=="base")return;const ce=window.document,q="g7le-base-slot-style";if(ce.getElementById(q))return;const se=ce.createElement("style");se.id=q;const ae=D("layout_editor.preview.slot_area_label");return se.textContent=`.g7le-preview-frame [data-editor-slot]{position:relative;min-height:96px;outline:2px dashed #94a3b8;outline-offset:-2px;}.g7le-preview-frame [data-editor-slot]::after{content:'${ae.replace(/'/g,"\\'")} · ' attr(data-editor-slot);position:absolute;top:8px;left:50%;transform:translateX(-50%);font-size:12px;line-height:1;color:#64748b;background:rgba(241,245,249,0.92);border:1px solid #cbd5e1;border-radius:9999px;padding:4px 12px;pointer-events:none;white-space:nowrap;}`,ce.head.appendChild(se),()=>{ce.getElementById(q)?.remove()}},[p.editMode,D]),!(p.editMode==="extension"||p.editMode==="iteration_item")&&(p.selectedRoute?.layoutName===null||p.selectedRoute===null))return i.jsx("div",{className:"g7le-preview-canvas g7le-preview-canvas--empty","data-testid":"g7le-preview-empty",style:rr,children:D("layout_editor.preview.empty")});if(m)return i.jsx("div",{className:"g7le-preview-canvas g7le-preview-canvas--loading","data-testid":"g7le-preview-loading",style:rr,children:D("layout_editor.preview.loading")});if(h)return i.jsx(Za,{error:h});if(T)return i.jsx(Za,{error:T});if(!N||E===null||I===null)return i.jsx("div",{className:"g7le-preview-canvas g7le-preview-canvas--assets-loading","data-testid":"g7le-preview-assets-loading",style:rr,children:D("layout_editor.preview.assets_loading")});if(ie.length===0)return i.jsx("div",{className:"g7le-preview-canvas g7le-preview-canvas--empty-layout","data-testid":"g7le-preview-empty-layout",style:rr,children:D("layout_editor.preview.empty_layout")});const We=p.editMode==="extension"?oe==="no-injection"?"no-injection":oe==="ok"&&ee.length>0&&fe!==!0?"gated-no-state":null:null;return te?i.jsx("div",{className:"g7le-preview-canvas","data-testid":"g7le-preview-canvas",style:{overflow:"visible",background:"#f1f5f9",padding:24,display:"flex",justifyContent:"center",alignItems:"flex-start",minHeight:"calc(100vh - var(--g7le-header-h, 0px))",boxSizing:"border-box"},children:i.jsxs("div",{className:"g7le-preview-frame-wrapper","data-testid":"g7le-preview-frame-wrapper",style:{position:"relative",width:w,transform:`scale(${k})`,transformOrigin:"top center"},children:[i.jsx("div",{className:x==="dark"?"g7le-preview-frame g7le-preview-dark":"g7le-preview-frame","data-testid":"g7le-preview-frame","data-color-scheme":x,ref:ce=>{v.current=ce,_(ce)},style:{width:w,background:"#fff",border:"1px solid #cbd5e1",boxShadow:"0 1px 3px rgba(0,0,0,0.05)",minHeight:400,position:"relative",transform:"translateZ(0)",overflow:"hidden"},children:i.jsx(M7,{overrideWidth:w,children:ie.map((ce,q)=>i.jsx(N7,{componentDef:ce,dataContext:{...Xe,...$D(V),_global:ED(V),_computedDefinitions:Ve,...A?{_localInit:A,_local:{...ze._local??{},...V._local??{}}}:{},route:WD({...ze.route??{},...ve,path:p.selectedRoute?.path??""},Y),query:{...ze.query??{},...F??{}}},translationContext:{templateId:p.templateIdentifier,locale:p.locale},registry:E,bindingEngine:O.current,translationEngine:Pt.getInstance(),actionDispatcher:M.current,isEditMode:!0,isRootRenderer:q===0,componentPath:`${q}`,onComponentSelect:(se,ae)=>{},onComponentHover:(se,ae)=>{}},`${ce.id??`preview-${q}`}:t${Ne}${p.editMode==="extension"?`:s${te?1:0}`:""}`))})}),i.jsx("div",{className:"g7le-overlay-layer","data-testid":"g7le-overlay-layer",style:{position:"absolute",left:0,top:0,width:w,height:y?y.getBoundingClientRect().height/(k||1):"100%",overflow:"visible",pointerEvents:"none"},children:i.jsx(sD,{frameEl:y,manifest:t,nesting:n,componentPalette:o,spec:r,permissionCandidates:s,resolveRouteMatch:l,onNavigateToDestination:d,onSessionAddedPathsChange:c})}),We!==null&&i.jsx("div",{className:"g7le-preview-canvas--extension-fallback","data-testid":"g7le-preview-extension-fallback","data-fallback-reason":We,style:{position:"absolute",left:0,top:0,right:0,bottom:0,minHeight:y?y.getBoundingClientRect().height/(k||1):400,background:"#f1f5f9",display:"flex",alignItems:"flex-start",justifyContent:"center",padding:24,boxSizing:"border-box",pointerEvents:"auto",zIndex:50},children:i.jsxs("div",{className:"g7le-extension-fallback-card",style:{maxWidth:440,textAlign:"center",position:"sticky",top:"calc(40vh - var(--g7le-header-h, 0px))"},children:[i.jsx("p",{className:"g7le-extension-fallback-title",style:{fontWeight:600,marginBottom:8,color:"#0f172a"},children:D(We==="no-injection"?"layout_editor.preview.extension_no_injection_title":"layout_editor.preview.extension_gated_title")}),i.jsx("p",{className:"g7le-extension-fallback-desc",style:{color:"#475569",lineHeight:1.6},children:We==="no-injection"?D("layout_editor.preview.extension_no_injection_desc"):p.availableStates.length>1?D("layout_editor.preview.extension_gated_desc_with_states"):D("layout_editor.preview.extension_gated_desc")})]})})]})}):i.jsx("div",{className:"g7le-preview-canvas g7le-preview-canvas--scripts-loading","data-testid":"g7le-preview-scripts-loading",style:rr,children:D("layout_editor.preview.loading")})}function OD(e,t,n){if(!e)return;if(!t)return e;const o=s=>{if(typeof s=="string"&&s.startsWith("$t:"))try{const a=s.slice(3),l=a.indexOf("|");if(l===-1)return t.translate(a,n);const d=a.slice(0,l),c=a.slice(l);return t.translate(d,n,c)}catch{return s}return Array.isArray(s)?s.map(o):s},r={};for(const[s,a]of Object.entries(e))r[s]=o(a);return r}function BD(e,t){const n=[],o=(r,s)=>{Array.isArray(r)&&r.forEach((a,l)=>{if(!a||typeof a!="object")return;const d=s?`${s}.children.${l}`:`${l}`,c=a.__source;if(c?.kind==="extension"&&c.extensionId===t){n.push(d);return}Array.isArray(a.children)&&o(a.children,d)})};return o(e,""),n}function WD(e,t){if(!t)return e;const n={...e};for(const[o,r]of Object.entries(t))r===null?delete n[o]:n[o]=r;return n}const rr={flex:1,display:"flex",alignItems:"center",justifyContent:"center",padding:24,background:"#f1f5f9",color:"#64748b",fontSize:13},jD={desktop:"layout_editor.device.desktop",tablet:"layout_editor.device.tablet",mobile:"layout_editor.device.mobile",portable:"layout_editor.device.portable"};function HD(){const{device:e,zoom:t,customWidth:n,setDevice:o,setZoom:r,setCustomWidth:s,colorScheme:a,setColorScheme:l}=Ya(),{t:d}=et(),c=ug();return i.jsxs("div",{className:"g7le-device-toolbar","data-testid":"g7le-device-toolbar",style:{display:"inline-flex",alignItems:"center",gap:8,padding:"4px 8px",background:"#fff",border:"1px solid #cbd5e1",borderRadius:6},children:[i.jsxs("div",{role:"group","aria-label":d("layout_editor.device.group_label"),children:[c.map(p=>{const f=e===p,b=jD[p],g=b?d(b):p;return i.jsx("button",{type:"button","data-testid":`g7le-device-${p}`,"aria-pressed":f,onClick:()=>o(p),style:{padding:"2px 8px",marginRight:2,fontSize:12,cursor:"pointer",background:f?"#0f172a":"#f8fafc",color:f?"#fff":"#0f172a",border:"1px solid #cbd5e1",borderRadius:4},children:g},p)}),(()=>{const p=e==="custom";return i.jsx("button",{type:"button","data-testid":"g7le-device-custom","aria-pressed":p,onClick:()=>o("custom"),style:{padding:"2px 8px",marginRight:2,fontSize:12,cursor:"pointer",background:p?"#0f172a":"#f8fafc",color:p?"#fff":"#0f172a",border:"1px solid #cbd5e1",borderRadius:4},children:d("layout_editor.device.custom")})})()]}),e==="custom"&&i.jsxs("div",{style:{display:"inline-flex",alignItems:"center",gap:4},children:[i.jsx("label",{htmlFor:"g7le-custom-width-input",style:{fontSize:11,color:"#475569"},children:d("layout_editor.device.custom_width_label")}),i.jsx("input",{id:"g7le-custom-width-input",type:"number",min:Tg,max:Dg,step:10,value:n,"data-testid":"g7le-custom-width-input","aria-label":d("layout_editor.device.custom_width_label"),onChange:p=>{const f=parseInt(p.currentTarget.value,10);Number.isFinite(f)&&s(f)},style:{width:72,padding:"2px 6px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:4}}),i.jsx("span",{style:{fontSize:11,color:"#475569"},children:"px"})]}),i.jsx("div",{role:"group","aria-label":d("layout_editor.toolbar.color_scheme_group"),style:{display:"inline-flex",gap:2},children:["light","dark"].map(p=>{const f=a===p;return i.jsx("button",{type:"button","data-testid":`g7le-toolbar-scheme-${p}`,"aria-pressed":f,onClick:()=>l(p),style:{padding:"2px 8px",fontSize:12,cursor:"pointer",background:f?"#0f172a":"#f8fafc",color:f?"#fff":"#0f172a",border:"1px solid #cbd5e1",borderRadius:4},children:d(p==="light"?"layout_editor.toolbar.color_scheme_light":"layout_editor.toolbar.color_scheme_dark")},p)})}),i.jsxs("div",{style:{display:"inline-flex",alignItems:"center",gap:4},children:[i.jsx("span",{style:{fontSize:11,color:"#475569"},children:d("layout_editor.zoom.label")}),i.jsx("input",{type:"range",min:.5,max:1,step:.05,value:t,"data-testid":"g7le-zoom-slider","aria-label":d("layout_editor.zoom.aria_label"),onChange:p=>r(parseFloat(p.currentTarget.value)),style:{width:100}}),i.jsxs("span",{style:{fontSize:11,color:"#475569",minWidth:36,textAlign:"right"},children:[Math.round(t*100),"%"]})]}),i.jsx(ym,{})]})}function FD(){const{t:e}=et(),{state:t,dispatch:n}=Qe(),{availableStates:o,activeStateId:r}=t,[,s]=u.useState(0);u.useEffect(()=>{if(typeof window>"u")return;const p=()=>s(b=>b+1),f=window.requestAnimationFrame(p);return window.addEventListener(nl,p),()=>{window.cancelAnimationFrame(f),window.removeEventListener(nl,p)}},[]);const a=u.useCallback((p,f)=>{if(!p)return f;if(!p.startsWith("$t:"))return p;const b=p.slice(3),g=window.G7Core;if(g&&typeof g.t=="function"){const h=g.t(b);if(h&&h!==b&&!h.startsWith("$t:"))return h}const m=e(b);return m&&m!==b?m:f},[e]);if(!Array.isArray(o)||o.length<=1)return null;const l=p=>{const f=p.target.value;n({type:"SET_ACTIVE_STATE",activeStateId:f}),Ll({kind:"change",activeStateId:f,routePath:t.selectedRoute?.path??null,availableCount:o.length})},d=o.find(p=>p.id===r)??o[0],c=d?.description?a(d.description,""):"";return i.jsxs("div",{className:"g7le-state-switcher","data-testid":"g7le-state-switcher","data-active-state":r??"",style:{display:"inline-flex",alignItems:"center",gap:6,padding:"4px 8px",background:"#fff",border:"1px solid #cbd5e1",borderRadius:6},children:[i.jsx("span",{style:{fontSize:12,fontWeight:600,color:"#475569",whiteSpace:"nowrap"},children:e("layout_editor.toolbar.state")}),i.jsx("select",{"data-testid":"g7le-state-switcher-select","aria-label":e("layout_editor.toolbar.state"),value:r??o[0].id,onChange:l,style:{fontSize:12,padding:"2px 6px",border:"1px solid #cbd5e1",borderRadius:4,background:"#fff",color:"#0f172a",cursor:"pointer",outline:"none"},children:o.map(p=>i.jsx("option",{value:p.id,children:a(p.label,p.id)},p.id))}),c&&i.jsx("span",{"data-testid":"g7le-state-switcher-description",style:{fontSize:11,color:"#94a3b8",whiteSpace:"nowrap"},children:c})]})}function KD(e,t,n){if(e==="extension"){if(typeof n=="string"&&n.length>0){const o=ol(n,"__base__/");return o?{kind:"base",match:o}:{kind:"route",match:n}}return null}if(!t)return null;switch(e){case"route":return{kind:"route",match:t};case"base":{const o=ol(t,"__base__/");return o?{kind:"base",match:o}:null}case"modal":{const o=ol(t,"__modal__/");return o?{kind:"modal",match:o}:null}default:return null}}function VD(e,t){if(!t||!Array.isArray(e)||e.length===0)return[];const n=[];for(const o of e)if(qD(o.scope,t))for(const r of o.items??[])r&&typeof r.id=="string"&&r.id.length>0&&n.push(r);return n}function UD(e){if(e.length===0)return null;const t=e.find(n=>n.default===!0);return t?t.id:e[0].id}function qD(e,t){if(!e||e.kind!==t.kind)return!1;const n=e.match;return typeof n!="string"||n.length===0?!1:t.kind==="route"?GD(t.match,n):n===t.match}function GD(e,t){if(t===e)return!0;if(!t.includes("*"))return!1;const n=t.replace(/[.+?^${}()|[\]\\]/g,"\\$&").replace(/\/\*\\\?/g,"(?:/[^/]+)?").replace(/\*/g,"[^/]+");return new RegExp(`^${n}$`).test(e)}function ol(e,t){return e.startsWith(t)?e.slice(t.length):""}const YD="__conngroup__/modals/",XD="__conngroup__/extensions/";function JD(e){return e.meta?.editor_label?{label:e.meta.editor_label,labelSource:"editor_label"}:e.meta?.title?{label:e.meta.title,labelSource:"title"}:{label:e.path,labelSource:"path"}}function ZD(e){switch(e.source.kind){case"template":return"template";case"module":return`module:${e.source.identifier??""}`;case"plugin":return`plugin:${e.source.identifier??""}`;case"core":return null;default:return null}}function QD(e,t){if(e.meta?.parent&&t.has(e.meta.parent))return e.meta.parent;const n=e.path.split("/").filter(Boolean);for(let o=n.length-1;o>=1;o--){const r="/"+n.slice(0,o).join("/");if(r!==e.path&&t.has(r))return r}return null}function RD(e){const t=new Map;for(const n of e)n.layout&&t.set(n.layout,(t.get(n.layout)??0)+1);return t}function e8(e){const t=new Map;for(const n of e){if(!n.hostLayout)continue;const o=t.get(n.hostLayout)??[];o.push(n),t.set(n.hostLayout,o)}return t}function t8(e){const t=new Map;for(const n of e)for(const o of n.extensions)for(const r of o.hostLayouts??[]){if(!r)continue;const s=t.get(r)??[];s.push({extension:o,group:n}),t.set(r,s)}return t}function n8(e,t){return{path:`__modal__/${e.hostLayout}/${e.modalId}`,layoutName:e.hostLayout,label:e.label??e.modalId,labelSource:e.label?"editor_label":"path",source:{kind:"core",identifier:null},kind:"modal",modalId:e.modalId,modalHostLayout:e.hostLayout,connectedHostRoutePath:t,children:[]}}function o8(e,t){const{extension:n,group:o}=e,r=o.sourceType==="plugin"?"🔌":"🧩";return{path:`__extension__/${n.id}`,layoutName:null,label:`${r} ${o.sourceLabel} · ${n.targetName}`,labelSource:"path",source:{kind:o.sourceType,identifier:o.sourceIdentifier},kind:"extension",extensionId:String(n.id),extensionType:n.extensionType,extensionPriority:n.priority,extensionTargetName:n.targetName,isModified:n.isModified,isInactive:!n.isActive,connectedHostRoutePath:t,children:[]}}function Kg(e,t,n){if(e.kind!=="route"&&e.kind!=="base"||!e.layoutName)return;const o=[],r=t.get(e.layoutName)??[];r.length>0&&o.push({path:`${YD}${e.path}`,layoutName:null,label:`$t:layout_editor.chrome.route_tree.connected.modals|count=${r.length}`,labelSource:"editor_label",source:{kind:"core",identifier:null},kind:"route",children:r.map(a=>n8(a,e.path))});const s=n.get(e.layoutName)??[];s.length>0&&o.push({path:`${XD}${e.path}`,layoutName:null,label:`$t:layout_editor.chrome.route_tree.connected.extensions|count=${s.length}`,labelSource:"editor_label",source:{kind:"core",identifier:null},kind:"route",children:s.map(a=>o8(a,e.path))}),o.length>0&&(e.children=[...o,...e.children??[]])}function r8(e){const t=[],n=new Set(e.routes.map(o=>o.path));for(const o of e.routes){const r=ZD(o);if(!r)continue;const{label:s,labelSource:a}=JD(o),l=QD(o,n),d={path:o.path,layoutName:o.layout??null,label:s,labelSource:a,icon:o.meta?.icon??null,source:o.source,isRedirect:!!o.redirect,isHidden:!!o.meta?.hidden,kind:"route",children:[]};t.push({node:d,parentKey:l,groupKey:r})}return t}function i8(e,t){if(e==="template")return"$t:layout_editor.chrome.route_tree.group.template";if(e.startsWith("module:")){const n=e.slice(7);return`$t:layout_editor.chrome.route_tree.group.module|name=${t.moduleDisplayNames[n]??n}`}if(e.startsWith("plugin:")){const n=e.slice(7);return`$t:layout_editor.chrome.route_tree.group.plugin|name=${t.pluginDisplayNames[n]??n}`}return e}function s8(e,t){RD(e.routes);const n=e8(e.modals),o=t8(e.extensionGroups??[]),r=new Map;for(const d of t)r.has(d.groupKey)||r.set(d.groupKey,[]),r.get(d.groupKey).push(d);const s=Array.from(r.keys()).sort((d,c)=>{const p=g=>g==="template"?1:g.startsWith("module:")?2:g.startsWith("plugin:")?3:4,f=p(d),b=p(c);return f!==b?f-b:d.localeCompare(c)}),a=[];if(e.baseLayouts.length>0){const d=e.baseLayouts.map(c=>({path:`__base__/${c.layoutName}`,layoutName:c.layoutName,label:c.label??c.layoutName,labelSource:c.label?"editor_label":"path",source:{kind:"core",identifier:null},kind:"base",children:[]}));for(const c of d)Kg(c,n,o);a.push({path:"__group__/base",layoutName:null,label:"$t:layout_editor.chrome.route_tree.group.base",labelSource:"editor_label",source:{kind:"core",identifier:null},kind:"route",children:d})}for(const d of s){const c=r.get(d),p=new Map;for(const b of c)p.set(b.node.path,b);const f=[];for(const b of c)if(b.parentKey&&p.has(b.parentKey)){const g=p.get(b.parentKey);g.node.children=g.node.children??[],g.node.children.push(b.node)}else f.push(b.node);for(const b of c)Kg(b.node,n,o);a.push({path:`__group__/${d}`,layoutName:null,label:i8(d,e),labelSource:"editor_label",source:{kind:"core",identifier:null},kind:"route",children:f})}if(e.modals.length>0){const d=e.modals.map(c=>({path:`__modal__/${c.hostLayout}/${c.modalId}`,layoutName:c.hostLayout,label:c.label??c.modalId,labelSource:c.label?"editor_label":"path",source:{kind:"core",identifier:null},kind:"modal",modalId:c.modalId,modalHostLayout:c.hostLayout,children:[]}));a.push({path:"__group__/modal",layoutName:null,label:"$t:layout_editor.chrome.route_tree.group.modal",labelSource:"editor_label",source:{kind:"core",identifier:null},kind:"route",children:d})}const l=e.extensionGroups??[];if(l.length>0){const d=l.map(c=>{const p=c.sourceType==="plugin"?"🔌":"🧩",f=c.extensions.map(b=>({path:`__extension__/${b.id}`,layoutName:null,label:`${c.sourceLabel} · ${b.targetName}`,labelSource:"path",source:{kind:c.sourceType,identifier:c.sourceIdentifier},kind:"extension",extensionId:String(b.id),extensionType:b.extensionType,extensionPriority:b.priority,extensionTargetName:b.targetName,isModified:b.isModified,isInactive:!b.isActive,children:[]}));return{path:`__extgroup__/${c.sourceType}/${c.sourceIdentifier}`,layoutName:null,label:`${p} ${c.sourceLabel}`,labelSource:"path",source:{kind:c.sourceType,identifier:c.sourceIdentifier},kind:"route",children:f}});a.push({path:"__group__/extension",layoutName:null,label:"$t:layout_editor.chrome.route_tree.group.extension",labelSource:"editor_label",source:{kind:"core",identifier:null},kind:"route",children:d})}return a}function a8(e){const t=r8(e);return s8(e,t)}const Wn=wo("useEditorRoutes");function l8(e,t){if(typeof e=="string")return e.length>0?e:null;if(e&&typeof e=="object"){const n=e[t];if(typeof n=="string"&&n.length>0)return n;if(typeof e.en=="string"&&e.en.length>0)return e.en;for(const o of Object.values(e))if(typeof o=="string"&&o.length>0)return o}return null}function Vg(e,t){const n={};if(typeof window>"u")return n;const r=window.G7Config?.[e];if(!Array.isArray(r))return n;for(const s of r)s&&typeof s.identifier=="string"&&(n[s.identifier]=l8(s.display_name,t)??s.identifier);return n}function d8(e){const t=new Id,n=typeof window<"u"?window.G7Config:void 0,o={_global:{settings:n?.settings??{},plugins:n?.plugins??{},modules:n?.modules??{},appConfig:n?.appConfig??{}}};return e.map(r=>{const s={...r};if(typeof s.path=="string"&&s.path.includes("{{")){const a=s.path;let l=t.resolveBindings(s.path,o);l=l.replace(/\/\/+/g,"/")||"/",l.includes("{{")&&(Wn.warn("편집기 route path 표현식 평가 실패 — fallback 적용",{originalPath:a}),l=a.replace(/\{\{[^}]+\}\}/g,"").replace(/\/\/+/g,"/")||"/"),s.path=l}if(typeof s.redirect=="string"&&s.redirect.includes("{{")){const a=s.redirect;let l=t.resolveBindings(a,o);l=l.replace(/\/\/+/g,"/")||"/",l.includes("{{")&&(Wn.warn("편집기 route redirect 표현식 평가 실패 — fallback 적용",{originalRedirect:a}),l=a.replace(/\{\{[^}]+\}\}/g,"").replace(/\/\/+/g,"/")||"/"),s.redirect=l}return s})}function c8(e){const{dispatch:t}=Qe(),{translationEngine:n,translationContext:o}=et(),{templateIdentifier:r,cacheVersion:s}=e;u.useEffect(()=>{let a=!1;const l=s??(typeof window<"u"?window.G7Config?.cache_version:void 0)??"",d=l!==""?`?v=${encodeURIComponent(String(l))}`:"",c=nn(`/api/admin/templates/${encodeURIComponent(r)}/editor/routes`,"json",null,d.replace(/^[?&]+/,"")),p=o?.locale??"ko",f=n&&r!==o?.templateId?n.loadTranslations(r,p).then(()=>{Wn.log("편집 대상 lang 로드 완료",{targetTemplate:r,locale:p})}).catch(h=>{Wn.warn("편집 대상 lang 로드 실패 — 라우트 라벨 fallback 사용",h)}):Promise.resolve(),b=(()=>{if(typeof fetch!="function")return Promise.reject(uo(new Error("fetch unavailable"),"routes"));try{const h=fetch(c,{credentials:"same-origin",headers:Me()});return!h||typeof h.then!="function"?Promise.reject(uo(new Error("fetch returned non-Promise (test stub?)"),"routes")):h.then(async v=>{if(!v.ok){const y=await v.json().catch(()=>null);throw ti(v.status,y,"routes")}return v.json()})}catch(h){return Promise.reject(h)}})(),g=`/api/admin/templates/${encodeURIComponent(r)}/layout-extensions${d}`,m=(()=>{if(typeof fetch!="function")return Promise.resolve(null);try{const h=fetch(g,{credentials:"same-origin",headers:Me()});return!h||typeof h.then!="function"?Promise.resolve(null):h.then(async v=>v.ok?await v.json():null).catch(()=>null)}catch{return Promise.resolve(null)}})();return Promise.all([b,f,m]).then(([h,,v])=>{if(a)return;if(!h||!h.success||!Array.isArray(h.data?.routes)){Wn.warn("routes 응답 형식이 비정상 — empty tree 로 둠",h),t({type:"SET_ROUTES_ERROR",error:{kind:"unknown",status:200,message:"routes 응답 형식이 비정상입니다.",source:"routes"}});return}const y=d8(h.data.routes),_=Vg("activeModules",p),w=Vg("activePlugins",p),k=(h.data.base_layouts??[]).map(I=>({layoutName:I.layout_name,label:I.label??void 0})),x=(h.data.modals??[]).map(I=>({modalId:I.modal_id,hostLayout:I.host_layout,label:I.label??void 0})),$=(v?.success&&Array.isArray(v.data)?v.data:[]).map(I=>({sourceIdentifier:I.source_identifier,sourceType:I.source_type,sourceLabel:I.source_label,extensions:(I.extensions??[]).map(N=>({id:N.id,extensionType:N.extension_type,targetName:N.target_name,priority:N.priority,isActive:N.is_active,isModified:N.is_modified,hostLayouts:Array.isArray(N.host_layouts)?N.host_layouts:[],currentVersion:typeof N.current_version=="number"?N.current_version:void 0}))})),C=a8({routes:y,modals:x,baseLayouts:k,extensionGroups:$,moduleDisplayNames:_,pluginDisplayNames:w});t({type:"SET_ROUTE_TREE",tree:C});const S=h.data.layout_versions,D={};if(S&&typeof S=="object"&&!Array.isArray(S))for(const[I,N]of Object.entries(S))typeof N=="number"&&Number.isFinite(N)&&(D[I]=N);t({type:"SET_LAYOUT_VERSIONS",versions:D});const E={};for(const I of $)for(const N of I.extensions){const T=N.currentVersion;typeof T=="number"&&Number.isFinite(T)&&(E[String(N.id)]=T)}t({type:"SET_EXTENSION_VERSIONS",versions:E}),Wn.log("routes 로드 완료",{count:y.length,groups:C.length})}).catch(h=>{if(a)return;Wn.error("routes 로드 실패",h);const v=Ip(h)?h:uo(h,"routes");t({type:"SET_ROUTES_ERROR",error:v})}),()=>{a=!0}},[r,s,t,n,o?.locale,o?.templateId])}function Ug(e,t,n,o=[]){const r=t?qg(o,t):null,s=r!==null?{path:r.path,layoutName:r.layoutName}:void 0;if(e.startsWith("__base__/")){const a=e.slice(9);if(a)return n({type:"ENTER_BASE_EDIT",layoutName:a,returnRoute:s}),!0}else if(e.startsWith("__modal__/")){const a=e.slice(10);return a&&!a.includes("/")&&t?(n({type:"ENTER_MODAL_EDIT",modalId:a,hostLayout:t,returnRoute:s}),!0):!1}else if(e.startsWith("__extension__/")){const a=e.slice(14);if(a&&!a.includes("/"))return n({type:"ENTER_EXTENSION_EDIT",extensionId:a,returnRoute:s}),!0}else if(e.startsWith("__iteration__/")){const a=e.slice(14);return a&&t?(n({type:"ENTER_ITERATION_ITEM_EDIT",sourcePath:a,hostLayout:t,returnRoute:s}),!0):!1}return!1}function qg(e,t){for(const n of e){if(n.kind==="route"&&n.layoutName===t)return n;if(n.children&&n.children.length>0){const o=qg(n.children,t);if(o)return o}}return null}function rl(e,t){for(const n of e){if(n.kind==="route"&&n.path===t)return n;if(n.children&&n.children.length>0){const o=rl(n.children,t);if(o)return o}}return null}function u8(e){const{state:t,dispatch:n}=Qe(),o=u.useRef(!1),r=u.useRef(t.editMode);r.current=t.editMode,u.useEffect(()=>{if(o.current||t.routeTree.length===0||typeof window>"u")return;const a=_p(window.location.search);if(a){const c=vp(window.location.search),p=Ug(a,c,n,t.routeTree);if(o.current=!0,p)return}const l=mp(window.location.search);if(!l){o.current=!0;return}const d=rl(t.routeTree,l);d&&n({type:"SELECT_ROUTE",route:{path:d.path,layoutName:d.layoutName}}),o.current=!0},[t.routeTree,n]),u.useEffect(()=>{if(typeof window>"u")return;const a=()=>{const l=_p(window.location.search);if(l){const p=vp(window.location.search);if(Ug(l,p,n,t.routeTree))return}if(r.current!=="route")switch(r.current){case"base":n({type:"EXIT_BASE_EDIT"});return;case"modal":n({type:"EXIT_MODAL_EDIT"});return;case"extension":n({type:"EXIT_EXTENSION_EDIT"});return;case"iteration_item":n({type:"EXIT_ITERATION_ITEM_EDIT"});return}const d=mp(window.location.search);if(!d){n({type:"SELECT_ROUTE",route:null});return}const c=rl(t.routeTree,d);c&&n({type:"SELECT_ROUTE",route:{path:c.path,layoutName:c.layoutName}})};return window.addEventListener("popstate",a),()=>{window.removeEventListener("popstate",a)}},[t.routeTree,n]);const s=u.useRef("route");u.useEffect(()=>{if(typeof window>"u"||!window.history?.replaceState||t.routeTree.length===0)return;const a=sa(e,t.selectedRoute?.path??null,t.selectedRoute?.layoutName??null),l=`${window.location.pathname}${window.location.search}`,d=s.current;if(s.current=t.editMode,l===a)return;d==="route"&&t.editMode!=="route"&&window.history.pushState?window.history.pushState(window.history.state,"",a):window.history.replaceState(window.history.state,"",a)},[t.selectedRoute,t.editMode,t.routeTree,e])}function p8(){u.useEffect(()=>{if(typeof document>"u")return;const e=document.documentElement;let t=e.classList.contains("dark");const n=()=>{e.classList.contains("dark")&&(t=!0,e.classList.remove("dark"))};n();let o=null;return typeof MutationObserver<"u"&&(o=new MutationObserver(n),o.observe(e,{attributes:!0,attributeFilter:["class"]})),()=>{o?.disconnect(),t&&e.classList.add("dark")}},[])}function f8(){u.useEffect(()=>{if(typeof document>"u"||!document.body)return;const e=document.body,t=()=>{e.style.overflow==="hidden"&&(e.style.overflow=""),e.style.overflowY==="hidden"&&(e.style.overflowY="")};t();let n=null;return typeof MutationObserver<"u"&&(n=new MutationObserver(t),n.observe(e,{attributes:!0,attributeFilter:["style"]})),()=>{n?.disconnect()}},[])}function g8(e){if(typeof e!="string")return null;const t="__extension__/";if(!e.startsWith(t))return null;const n=e.slice(t.length);return n===""||n.includes("/")?null:n}function h8(e){if(Array.isArray(e.components))return e.components;if(Array.isArray(e.injections)){const t=[];return e.injections.forEach((n,o)=>{if(n&&Array.isArray(n.components))for(const r of n.components)t.push({...r,__injectionIndex:o})}),t}return[]}function y8(e,t){if(Array.isArray(e.components)){const n=t.map(o=>il(o));return{...e,components:n}}return Array.isArray(e.injections)?Yg(e,t).content:e}function Gg(e,t){if(Array.isArray(e))for(const n of e){if(!n||typeof n!="object")continue;const o=n.id;typeof o=="string"&&o!==""&&t.add(o),Gg(n.children,t)}}function Yg(e,t){const n=Array.isArray(e.injections)?e.injections:[],o=n.map(a=>({...a})),r=n.map(a=>{const l=new Set;return a&&Array.isArray(a.components)&&Gg(a.components,l),l});o.forEach(a=>{Array.isArray(a.components)&&(a.components=[])});let s=0;for(const a of t){const l=a.__injectionIndex,d=a.__source?.injectionIndex;let c=typeof l=="number"?l:typeof d=="number"?d:void 0;if(c===void 0){const f=a.id;if(typeof f=="string"){const b=r.findIndex(g=>g.has(f));b>=0&&(c=b)}}const p=typeof c=="number"&&c>=0&&ct+(n&&Array.isArray(n.components)?n.components.length:0),0):0}function il(e){if(!e||typeof e!="object")return e;const t={...e};return delete t.__injectionIndex,delete t.__source,delete t.__injectedProps,delete t._fromBase,Array.isArray(t.children)&&(t.children=t.children.map(n=>il(n))),t}function Xg(e,t){const n=[],o=r=>{if(Array.isArray(r))for(const s of r){if(!s||typeof s!="object")continue;const a=s.__source;if(a?.kind==="extension"&&a.extensionId===t){n.push(s);continue}Array.isArray(s.children)&&o(s.children)}};return o(e),n}function Jg(e,t){const o=Xg(e,t)[0]?.__source;return o&&o.kind==="extension"&&o.extensionId===t?o:{kind:"extension",extensionId:t}}function sl(e,t){for(const n of e){const o=n?.__source;if(o?.kind==="extension"&&o.extensionId===t||Array.isArray(n.children)&&sl(n.children,t))return!0}return!1}function m8(){const{state:e}=Qe(),t=e.templateIdentifier,n=e.editMode==="extension"?g8(e.selectedRoute?.path):null,o=e.editMode==="extension"&&typeof e.selectedRoute?.layoutName=="string"?e.selectedRoute.layoutName:null,[r,s]=u.useState(null),[a,l]=u.useState(!1),[d,c]=u.useState(null),[p,f]=u.useState(!1),[b,g]=u.useState(0),[m,h]=u.useState(null),v=u.useRef({}),y=u.useRef({kind:"extension",extensionId:0}),_=u.useRef(null);u.useEffect(()=>{_.current=r},[r]),u.useEffect(()=>{h(null)},[n]);const w=u.useCallback(async I=>{const N=window.G7Config?.cache_version??0,T=nn(`/api/layouts/${encodeURIComponent(t)}/${I}`,"json",`${N}.${Bp()}`,"with_source_meta=1"),O=gr(),M={Accept:"application/json"};O&&(M.Authorization=`Bearer ${O}`);const B=await fetch(T,{headers:M,credentials:"same-origin"});if(!B.ok)return null;const z=await B.json().catch(()=>null);return(z&&(z.data||z))??{}},[t]),k=u.useCallback(async()=>{if(!n){s(null),c(null),f(!1);return}l(!0),c(null);try{const I=window.G7Config?.cache_version??0,N=`/api/admin/templates/${encodeURIComponent(t)}/layout-extensions/${encodeURIComponent(n)}?v=${I}`,T=await fetch(N,{headers:Me(),credentials:"same-origin"}),O=await T.json().catch(()=>null);if(!T.ok){c(ti(T.status,O,"layout")),s(null),f(!1);return}const M=(O&&(O.data||O))??{};let B={};const z=M.content;if(typeof z=="string")try{B=JSON.parse(z)}catch{B={}}else z&&typeof z=="object"&&(B=z);const H=M.extension_type==="overlay"?"overlay":"extension_point",P=typeof M.lock_version=="number"?M.lock_version:0,A=Array.isArray(M?.host_layouts)?M.host_layouts.filter(oe=>typeof oe=="string"):[],L=o??(A.length===1?A[0]:m&&A.includes(m)?m:null),F=Number(n);v.current=B;let V=h8(B),X={},Z="pending";const ie={kind:"extension",extensionId:F};if(y.current=ie,L){const oe=await w(L);if(oe){X=oe;const G=Array.isArray(oe.components)?oe.components:[],ee=Jg(G,F);if(y.current=ee,sl(G,F))V=G,Z="ok";else{const K=(Array.isArray(oe.modals)?oe.modals:[]).filter(te=>te&&typeof te=="object"&&sl([te],F));if(K.length>0){const te=K.map(ye=>({...ye,props:{...ye.props??{},isOpen:!0}}));V=[...G,...te],y.current=Jg(K,F),Z="ok"}else V=G,Z="no-injection"}}}s({extensionId:F,content:B,extensionType:H,components:V,lockVersion:P,hostLayouts:A,hostLayoutName:L,hostRaw:X,editability:Z}),f(!1),it({op:"load",layoutName:`extension:${n}`,editMode:"extension",statusCode:T.status,timestamp:Date.now()})}catch(I){c(uo(I,"layout")),s(null),f(!1)}finally{l(!1)}},[t,n,m,o,w]);u.useEffect(()=>{k()},[k]);const x=u.useCallback(I=>{h(I)},[]),$=u.useCallback(I=>{s(N=>N&&{...N,components:I}),f(!0),it({op:"patch",layoutName:`extension:${_.current?.extensionId??""}`,editMode:"extension",isDirty:!0,timestamp:Date.now()})},[]),C=u.useCallback(I=>{s(N=>N&&{...N,components:I(N.components)}),f(!0)},[]),S=u.useCallback(async()=>{const I=_.current;if(!I)return{kind:"guard_no_document"};const N=`/api/admin/templates/${encodeURIComponent(t)}/layout-extensions/${I.extensionId}`,T=Xg(I.components,I.extensionId),O=T.length>0?T:I.components;let M;if(Array.isArray(v.current.injections)&&!Array.isArray(v.current.components)){const P=Yg(v.current,O);if(P.unassigned>0&&b8(v.current)>0)return it({op:"save",layoutName:`extension:${I.extensionId}`,editMode:"extension",saveTarget:"layout_extension",endpoint:N,isDirty:p,timestamp:Date.now()}),{kind:"guard_extension_reassembly",unassigned:P.unassigned};M=P.content}else M=y8(v.current,O);it({op:"save",layoutName:`extension:${I.extensionId}`,editMode:"extension",saveTarget:"layout_extension",endpoint:N,isDirty:p,timestamp:Date.now()});let z;try{z=await fetch(N,{method:"PUT",headers:Me({"Content-Type":"application/json"}),credentials:"same-origin",body:JSON.stringify({content:M,expected_lock_version:I.lockVersion})})}catch(P){return{kind:"network_error",message:P instanceof Error?P.message:"network error"}}const H=await z.json().catch(()=>null);if(it({op:"save_response",layoutName:`extension:${I.extensionId}`,editMode:"extension",endpoint:N,statusCode:z.status,conflict:z.status===409?{currentVersion:H?.errors?.current_version??H?.current_version,yourVersion:H?.errors?.your_version??H?.your_version}:void 0,timestamp:Date.now()}),z.ok){const P=H?.data??{},A=typeof P.lock_version=="number"?P.lock_version:I.lockVersion+1;s(F=>F&&{...F,lockVersion:A}),f(!1),g(F=>F+1);const L=typeof P.current_version=="number"?P.current_version:void 0;return{kind:"success",newLockVersion:A,newContentVersion:L,savedExtensionId:String(I.extensionId)}}return z.status===409?{kind:"concurrent_modification",currentVersion:co(H,"current_version")??-1,yourVersion:co(H,"your_version")??I.lockVersion}:z.status===422?{kind:"validation_failed",status:422,errors:H?.errors??null}:{kind:"network_error",message:H?.message??`HTTP ${z.status}`}},[t,p]),D=u.useCallback(async()=>{ua(),await k()},[k]),E=!!r&&r.hostLayouts.length>1&&r.hostLayoutName===null;return{document:r,isLoading:a,error:d,isDirty:p,saveSuccessCounter:b,setLayoutComponents:$,patchLayout:C,save:S,reload:D,needsHostPicker:E,selectHost:x}}const Zg=new Set;function v8(e,t,n){const r=e&&Object.keys(e).length>0?e:t,s=Array.isArray(r.data_sources)?r.data_sources:[],a=Array.isArray(t.data_sources)?t.data_sources:[],l=new Set,d=[];for(const c of[...s,...a]){const p=c&&typeof c=="object"?c.id:void 0,f=typeof p=="string"?p:`__anon_${d.length}`;l.has(f)||(l.add(f),d.push(c))}return{...r,components:n,...d.length>0?{data_sources:d}:{},lock_version:void 0}}function _8(e){return{document:e.document?{layoutName:`extension:${e.document.extensionId}`,raw:{...v8(e.document.hostRaw,e.document.content,e.document.components),__editability:e.document.editability,__extensionId:e.document.extensionId},lockVersion:e.document.lockVersion}:null,isLoading:e.isLoading,error:e.error,isDirty:e.isDirty,saveSuccessCounter:e.saveSuccessCounter,reload:e.reload,patchLayout:e.patchLayout,setLayoutComponents:e.setLayoutComponents,patchDocumentRaw:()=>{},dirtyKeys:Zg,dirtyLayoutNames:Zg,invalidateCurrentCache:()=>{},reloadCounter:0,save:()=>e.save(),markDirty:()=>{}}}function x8({hostLayouts:e,extensionLabel:t,onSelect:n}){const{t:o}=et();return i.jsx("div",{className:"g7le-host-picker__backdrop","data-testid":"g7le-host-picker",role:"dialog","aria-modal":"true","aria-label":o("layout_editor.host_picker.title"),style:{position:"fixed",inset:0,zIndex:9500,display:"flex",alignItems:"center",justifyContent:"center",background:"rgba(15, 23, 42, 0.55)"},children:i.jsxs("div",{className:"g7le-host-picker__panel",style:{width:"min(520px, 92vw)",maxHeight:"80vh",overflow:"auto",background:"#ffffff",color:"#0f172a",borderRadius:12,boxShadow:"0 20px 48px rgba(15, 23, 42, 0.28)",padding:"20px 22px"},children:[i.jsx("h2",{className:"g7le-host-picker__title",style:{margin:"0 0 6px",fontSize:16,fontWeight:700},children:o("layout_editor.host_picker.title")}),i.jsx("p",{className:"g7le-host-picker__desc",style:{margin:"0 0 16px",fontSize:13,color:"#475569",lineHeight:1.6},children:t?o("layout_editor.host_picker.description_with_label",{label:t}):o("layout_editor.host_picker.description")}),i.jsx("ul",{className:"g7le-host-picker__list",style:{listStyle:"none",margin:0,padding:0,display:"grid",gap:8},children:e.map(r=>i.jsx("li",{children:i.jsxs("button",{type:"button",className:"g7le-host-picker__item","data-testid":"g7le-host-picker-item","data-host":r,onClick:()=>n(r),style:{width:"100%",textAlign:"left",padding:"12px 14px",border:"1px solid #e2e8f0",borderRadius:8,background:"#f8fafc",cursor:"pointer",fontSize:13,fontWeight:600,color:"#0f172a",display:"flex",alignItems:"center",gap:8},children:[i.jsx("span",{"aria-hidden":!0,style:{fontSize:14},children:"🖼"}),i.jsx("span",{style:{flex:1,minWidth:0,overflow:"hidden",textOverflow:"ellipsis"},children:r}),i.jsx("span",{"aria-hidden":!0,style:{color:"#94a3b8"},children:"→"})]})},r))})]})})}function w8(e,t){const[n,o]=u.useState(!1),[r,s]=u.useState(null),a=u.useCallback(async l=>{if(!t||!l)return{kind:"no_document"};o(!0),s(null);try{const d=Co(l),c=`/api/admin/templates/${encodeURIComponent(e)}/layouts/${t}/preview`,p=await fetch(c,{method:"POST",credentials:"same-origin",headers:Me({"Content-Type":"application/json"}),body:JSON.stringify({content:JSON.stringify(d)})}),f=await p.json().catch(()=>null);if(!p.ok){const h=f?.message??`HTTP ${p.status}`;return s(h),{kind:"network_error",message:h}}const b=f?.data,g=typeof b?.token=="string"?b.token:"",m=typeof b?.preview_url=="string"&&b.preview_url?b.preview_url:g?`/preview/${g}`:"";return m?(window.G7Core?.dispatch?.({handler:"openWindow",params:{path:m}}),{kind:"success",previewUrl:m,token:g}):(s("invalid preview response"),{kind:"network_error",message:"invalid preview response"})}catch(d){const c=d instanceof Error?d.message:"network error";return s(c),{kind:"network_error",message:c}}finally{o(!1)}},[e,t]);return{isCreating:n,error:r,createPreview:a}}const re="layout_editor.action_recipe",vn={setState:{label:`$t:${re}.set_state.label`,params:[{key:"target",label:`$t:${re}.set_state.param_target`,widget:"select",options:[{value:"local"},{value:"global"}]},{key:"state",label:`$t:${re}.set_state.param_state`,widget:"state-key-value"},{key:"merge",label:`$t:${re}.set_state.param_merge`,widget:"toggle"}],build:{handler:"setState",params:{target:"{{target}}",merge:"{{merge}}","...":"{{state}}"}}},loadFromLocalStorage:{label:`$t:${re}.load_local.label`,params:[{key:"key",label:`$t:${re}.load_local.param_key`,widget:"text",required:!0},{key:"stateKey",label:`$t:${re}.load_local.param_state_key`,widget:"state-key-picker"},{key:"defaultValue",label:`$t:${re}.load_local.param_default`,widget:"text"}],build:{handler:"loadFromLocalStorage",params:{key:"{{key}}",stateKey:"{{stateKey}}",defaultValue:"{{defaultValue}}"}}},saveToLocalStorage:{label:`$t:${re}.save_local.label`,params:[{key:"key",label:`$t:${re}.save_local.param_key`,widget:"text",required:!0},{key:"value",label:`$t:${re}.save_local.param_value`,widget:"data-chip"}],build:{handler:"saveToLocalStorage",params:{key:"{{key}}",value:"{{value}}"}}},navigate:{label:`$t:${re}.navigate.label`,params:[{key:"path",label:`$t:${re}.navigate.param_path`,widget:"page-picker",required:!0},{key:"query",label:`$t:${re}.navigate.param_query`,widget:"key-value"},{key:"mergeQuery",label:`$t:${re}.navigate.param_merge_query`,widget:"toggle"},{key:"scroll",label:`$t:${re}.navigate.param_scroll`,widget:"select",options:[{value:"top"},{value:"preserve"},{value:"none"}]},{key:"replace",label:`$t:${re}.navigate.param_replace`,widget:"toggle"},{key:"transition_overlay_target",label:`$t:${re}.navigate.param_overlay_target`,widget:"component-target-picker",dependsOn:{param:"replace",equals:!0}}],build:{handler:"navigate",params:{path:"{{path}}",query:"{{query}}",mergeQuery:"{{mergeQuery}}",scroll:"{{scroll}}",replace:"{{replace}}",transition_overlay_target:"{{transition_overlay_target}}"}}},openWindow:{label:`$t:${re}.open_window.label`,params:[{key:"target",label:`$t:${re}.open_window.param_target`,widget:"page-picker",required:!0},{key:"windowTarget",label:`$t:${re}.open_window.param_window_target`,widget:"select",options:[{value:"_blank"},{value:"_self"}]}],build:{handler:"openWindow",target:"{{target}}",params:{windowTarget:"{{windowTarget}}"}}},navigateBack:{label:`$t:${re}.navigate_back.label`,params:[],build:{handler:"navigateBack"}},navigateForward:{label:`$t:${re}.navigate_forward.label`,params:[],build:{handler:"navigateForward"}},replaceUrl:{label:`$t:${re}.replace_url.label`,params:[{key:"path",label:`$t:${re}.replace_url.param_path`,widget:"page-picker"},{key:"query",label:`$t:${re}.replace_url.param_query`,widget:"key-value"}],build:{handler:"replaceUrl",params:{path:"{{path}}",query:"{{query}}"}}},toast:{label:`$t:${re}.toast.label`,params:[{key:"message",label:`$t:${re}.toast.param_message`,widget:"i18n-text",required:!0},{key:"type",label:`$t:${re}.toast.param_type`,widget:"select",options:[{value:"info"},{value:"success"},{value:"warning"},{value:"error"}]},{key:"duration",label:`$t:${re}.toast.param_duration`,widget:"number"},{key:"icon",label:`$t:${re}.toast.param_icon`,widget:"text"}],build:{handler:"toast",params:{message:"{{message}}",type:"{{type}}",duration:"{{duration}}",icon:"{{icon}}"}}},openModal:{label:`$t:${re}.open_modal.label`,params:[{key:"target",label:`$t:${re}.open_modal.param_target`,widget:"modal-picker",required:!0}],build:{handler:"openModal",target:"{{target}}"}},closeModal:{label:`$t:${re}.close_modal.label`,params:[],build:{handler:"closeModal"}},showAlert:{label:`$t:${re}.show_alert.label`,params:[{key:"target",label:`$t:${re}.show_alert.param_message`,widget:"i18n-text",required:!0}],build:{handler:"showAlert",target:"{{target}}"}},setError:{label:`$t:${re}.set_error.label`,params:[{key:"target",label:`$t:${re}.set_error.param_message`,widget:"i18n-text"},{key:"stateTarget",label:`$t:${re}.set_error.param_state_target`,widget:"select",options:[{value:"local"},{value:"global"}]}],build:{handler:"setError",target:"{{target}}",params:{stateTarget:"{{stateTarget}}"}}},refetchDataSource:{label:`$t:${re}.refetch.label`,params:[{key:"dataSourceId",label:`$t:${re}.refetch.param_data_source`,widget:"datasource-picker",required:!0},{key:"sync",label:`$t:${re}.refetch.param_sync`,widget:"toggle"}],build:{handler:"refetchDataSource",params:{dataSourceId:"{{dataSourceId}}",sync:"{{sync}}"}}},appendDataSource:{label:`$t:${re}.append_data.label`,params:[{key:"dataSourceId",label:`$t:${re}.append_data.param_data_source`,widget:"datasource-picker",required:!0},{key:"dataPath",label:`$t:${re}.append_data.param_data_path`,widget:"text"},{key:"newData",label:`$t:${re}.append_data.param_new_data`,widget:"data-chip"}],build:{handler:"appendDataSource",params:{dataSourceId:"{{dataSourceId}}",dataPath:"{{dataPath}}",newData:"{{newData}}"}}},updateDataSource:{label:`$t:${re}.update_data.label`,params:[{key:"dataSourceId",label:`$t:${re}.update_data.param_data_source`,widget:"datasource-picker",required:!0},{key:"data",label:`$t:${re}.update_data.param_data`,widget:"data-chip"},{key:"merge",label:`$t:${re}.update_data.param_merge`,widget:"toggle"}],build:{handler:"updateDataSource",params:{dataSourceId:"{{dataSourceId}}",data:"{{data}}",merge:"{{merge}}"}}},scrollIntoView:{label:`$t:${re}.scroll.label`,params:[{key:"selector",label:`$t:${re}.scroll.param_selector`,widget:"text",required:!0},{key:"behavior",label:`$t:${re}.scroll.param_behavior`,widget:"select",options:[{value:"smooth"},{value:"auto"}]},{key:"block",label:`$t:${re}.scroll.param_block`,widget:"select",options:[{value:"start"},{value:"center"},{value:"end"},{value:"nearest"}]}],build:{handler:"scrollIntoView",params:{selector:"{{selector}}",behavior:"{{behavior}}",block:"{{block}}"}}},login:{label:`$t:${re}.login.label`,params:[{key:"body",label:`$t:${re}.login.param_body`,widget:"key-value"}],build:{handler:"login",params:{body:"{{body}}"}}},loginTwoFactor:{label:`$t:${re}.login_two_factor.label`,params:[{key:"body",label:`$t:${re}.login_two_factor.param_body`,widget:"key-value"}],build:{handler:"loginTwoFactor",params:{body:"{{body}}"}}},loginTwoFactorResend:{label:`$t:${re}.login_two_factor_resend.label`,params:[{key:"body",label:`$t:${re}.login_two_factor_resend.param_body`,widget:"key-value"}],build:{handler:"loginTwoFactorResend",params:{body:"{{body}}"}}},logout:{label:`$t:${re}.logout.label`,params:[{key:"target",label:`$t:${re}.logout.param_target`,widget:"text"}],build:{handler:"logout",target:"{{target}}"}},setLocale:{label:`$t:${re}.set_locale.label`,params:[{key:"target",label:`$t:${re}.set_locale.param_target`,widget:"locale-picker",required:!0}],build:{handler:"setLocale",target:"{{target}}"}},emitEvent:{label:`$t:${re}.emit_event.label`,params:[{key:"event",label:`$t:${re}.emit_event.param_event`,widget:"text",required:!0},{key:"data",label:`$t:${re}.emit_event.param_data`,widget:"data-chip"}],build:{handler:"emitEvent",params:{event:"{{event}}",data:"{{data}}"}}},apiCall:{label:`$t:${re}.api_call.label`,params:[{key:"target",label:`$t:${re}.api_call.param_target`,widget:"page-picker",required:!0},{key:"method",label:`$t:${re}.api_call.param_method`,widget:"select",options:[{value:"GET"},{value:"POST"},{value:"PUT"},{value:"PATCH"},{value:"DELETE"}]},{key:"body",label:`$t:${re}.api_call.param_body`,widget:"key-value"},{key:"query",label:`$t:${re}.api_call.param_query`,widget:"key-value"},{key:"identity_target_email",label:`$t:${re}.api_call.param_identity_target_email`,widget:"data-chip"},{key:"identity_target_phone",label:`$t:${re}.api_call.param_identity_target_phone`,widget:"data-chip"},{key:"onSuccess",label:`$t:${re}.api_call.param_on_success`,widget:"action-list"},{key:"onError",label:`$t:${re}.api_call.param_on_error`,widget:"action-list"}],build:{handler:"apiCall",target:"{{target}}",identity_target:{email:"{{identity_target_email}}",phone:"{{identity_target_phone}}"},onSuccess:"{{onSuccess}}",onError:"{{onError}}",params:{method:"{{method}}",body:"{{body}}",query:"{{query}}"}}},conditions:{label:`$t:${re}.conditions.label`,params:[{key:"branches",label:`$t:${re}.conditions.param_branches`,widget:"branch-list"}],build:{handler:"conditions",conditions:"{{branches}}"}},sequence:{label:`$t:${re}.sequence.label`,params:[{key:"actions",label:`$t:${re}.sequence.param_actions`,widget:"action-list"}],build:{handler:"sequence",params:{actions:"{{actions}}"}}},parallel:{label:`$t:${re}.parallel.label`,params:[{key:"actions",label:`$t:${re}.parallel.param_actions`,widget:"action-list"}],build:{handler:"parallel",params:{actions:"{{actions}}"}}},switch:{label:`$t:${re}.switch.label`,params:[{key:"value",label:`$t:${re}.switch.param_value`,widget:"data-chip"},{key:"cases",label:`$t:${re}.switch.param_cases`,widget:"action-list",advanced:!0}],build:{handler:"switch",params:{value:"{{value}}",cases:"{{cases}}"}}},suppress:{label:`$t:${re}.suppress.label`,params:[],build:{handler:"suppress"}}};function k8(){return{actionRecipes:vn,initActionRecipes:vn,errorRecipes:{navigate:vn.navigate,openModal:vn.openModal,toast:vn.toast,setState:vn.setState,sequence:vn.sequence,parallel:vn.parallel,showErrorPage:{label:`$t:${re}.show_error_page.label`,params:[{key:"target",label:`$t:${re}.show_error_page.param_target`,widget:"select",options:[{value:"content"},{value:"full"}]}],build:{handler:"showErrorPage",params:{target:"{{target}}"}}}}}}function al(e,t){return typeof e.label=="string"?e.label.startsWith("$t:")?t(e.label.slice(3)):e.label:String(e.value)}function Ei(e){return Array.isArray(e.options)?e.options:[]}function S8({control:e,value:t,onChange:n,t:o}){const r=Ei(e);return i.jsx("div",{className:"g7le-widget g7le-widget--segmented","data-testid":"g7le-widget-segmented",style:O8,children:r.map(s=>{const a=s.value===t;return i.jsx("button",{type:"button","data-testid":`g7le-segment-${String(s.value)}`,"data-active":a?"true":"false",onClick:()=>n(a?void 0:s.value),style:{...H8,background:a?"#2563eb":"#fff",color:a?"#fff":"#0f172a",borderColor:a?"#2563eb":"#cbd5e1"},children:al(s,o)},String(s.value))})})}function C8({control:e,value:t,onChange:n,t:o}){const r=Array.isArray(e.scale)?e.scale:[],s=r.findIndex(l=>l===t),a=t!=null;return i.jsxs("div",{className:"g7le-widget g7le-widget--slider","data-testid":"g7le-widget-slider",style:oh,children:[i.jsxs("label",{style:ll,children:[i.jsx("input",{type:"checkbox","data-testid":"g7le-slider-enabled",checked:a,onChange:l=>{l.target.checked?n(r.length>0?r[Math.max(0,s===-1?0:s)]:void 0):n(void 0)}}),i.jsx("span",{style:{fontSize:12,color:"#64748b"},children:o("layout_editor.control.use")})]}),i.jsx("input",{type:"range","data-testid":"g7le-slider-range",min:0,max:Math.max(0,r.length-1),step:1,disabled:!a||r.length===0,value:s===-1?0:s,onChange:l=>{const d=Number(l.target.value);n(r[d])},style:{flex:1}}),i.jsx("span",{"data-testid":"g7le-slider-value",style:{fontSize:12,color:"#0f172a",minWidth:48,textAlign:"right"},children:a?String(t):o("layout_editor.control.default")})]})}function $8({control:e,value:t,onChange:n,t:o}){const r=Ei(e);return i.jsxs("select",{className:"g7le-widget g7le-widget--select","data-testid":"g7le-widget-select",value:t==null?"":String(t),onChange:s=>{const a=s.target.value;if(a===""){n(void 0);return}const l=r.find(d=>String(d.value)===a);n(l?l.value:a)},style:F8,children:[i.jsx("option",{value:"",children:o("layout_editor.control.default")}),r.map(s=>i.jsx("option",{value:String(s.value),children:al(s,o)},String(s.value)))]})}function E8({control:e,value:t,onChange:n}){const o=Ei(e),r=o[1]?.value??!0,s=o[0]?.value??void 0,a=t===r;return i.jsx("button",{type:"button",className:"g7le-widget g7le-widget--toggle","data-testid":"g7le-widget-toggle",role:"switch","aria-checked":a,onClick:()=>n(a?s:r),style:{...K8,background:a?"#2563eb":"#cbd5e1"},children:i.jsx("span",{style:{...V8,transform:a?"translateX(18px)":"translateX(0)"}})})}function I8({control:e,value:t,onChange:n,t:o,freeValueDisabled:r}){const s=Ei(e),a=t==null?"":String(t),[l,d]=u.useState(a);u.useEffect(()=>{d(a)},[a]);const c=p=>{const f=p.trim();if(f===""){n(void 0);return}n(f)};return i.jsxs("div",{className:"g7le-widget g7le-widget--dimension","data-testid":"g7le-widget-dimension",style:Rg,children:[r?i.jsx("div",{"data-testid":"g7le-dimension-free-disabled",style:W8,children:o("layout_editor.property_modal.dark_preset_only")}):i.jsxs("div",{style:eh,children:[i.jsx("input",{type:"text","data-testid":"g7le-dimension-input",value:l,placeholder:o("layout_editor.control.dimension.placeholder"),onChange:p=>d(p.target.value),onBlur:p=>c(p.target.value),onKeyDown:p=>{p.key==="Enter"&&(p.preventDefault(),c(p.target.value))},style:th}),i.jsx("button",{type:"button","data-testid":"g7le-dimension-clear",title:o("layout_editor.control.default"),onClick:()=>{d(""),n(void 0)},style:nh,children:o("layout_editor.control.default")})]}),s.length>0&&i.jsx("div",{style:B8,"data-testid":"g7le-dimension-chips",children:s.map(p=>{const f=String(p.value),b=f===a;return i.jsx("button",{type:"button","data-testid":`g7le-dimension-chip-${f}`,"data-active":b?"true":"false",onClick:()=>{d(f),n(f)},style:{...j8,background:b?"#2563eb":"#fff",color:b?"#fff":"#0f172a",borderColor:b?"#2563eb":"#cbd5e1"},children:al(p,o)},f)})})]})}function A8({control:e,value:t,onChange:n,t:o}){const r=e,s=t==null?"":String(t),[a,l]=u.useState(s);u.useEffect(()=>{l(s)},[s]);const d=c=>{const p=c.trim();if(p===""){n(void 0);return}const f=Number(p);Number.isFinite(f)&&n(f)};return i.jsx("div",{className:"g7le-widget g7le-widget--number","data-testid":"g7le-widget-number",style:Rg,children:i.jsxs("div",{style:eh,children:[i.jsx("input",{type:"text",inputMode:"numeric","data-testid":"g7le-number-input",value:a,placeholder:o("layout_editor.control.number.placeholder"),min:typeof r.min=="number"?r.min:void 0,max:typeof r.max=="number"?r.max:void 0,step:typeof r.step=="number"?r.step:void 0,onChange:c=>l(c.target.value),onBlur:c=>d(c.target.value),onKeyDown:c=>{c.key==="Enter"&&(c.preventDefault(),d(c.target.value))},style:th}),i.jsx("button",{type:"button","data-testid":"g7le-number-clear",title:o("layout_editor.control.default"),onClick:()=>{l(""),n(void 0)},style:nh,children:o("layout_editor.control.default")})]})})}function T8(e){return[{key:"t",infix:`${e}t-`,labelKey:"layout_editor.control.spacing.top"},{key:"r",infix:`${e}r-`,labelKey:"layout_editor.control.spacing.right"},{key:"b",infix:`${e}b-`,labelKey:"layout_editor.control.spacing.bottom"},{key:"l",infix:`${e}l-`,labelKey:"layout_editor.control.spacing.left"}]}function D8(e,t,n,o){const r={all:-1,sides:{}};if(typeof e!="string"||e.trim()==="")return r;const s=e.split(/\s+/).filter(a=>a.length>0);for(const a of s){const l=n.find(d=>a.startsWith(d.infix));if(l){r.sides[l.key]=o.indexOf(a.slice(l.infix.length));continue}if(a.startsWith(`${t}x-`)){const d=o.indexOf(a.slice(`${t}x-`.length));r.sides.l=d,r.sides.r=d;continue}if(a.startsWith(`${t}y-`)){const d=o.indexOf(a.slice(`${t}y-`.length));r.sides.t=d,r.sides.b=d;continue}a.startsWith(`${t}-`)&&(r.all=o.indexOf(a.slice(`${t}-`.length)))}return r}function z8({control:e,value:t,onChange:n,t:o}){const r=typeof e.spacingPrefix=="string"?e.spacingPrefix:"p",s=(Array.isArray(e.scale)?e.scale:[]).map(y=>String(y)),a=T8(r),l=D8(t,r,a,s),d=Object.values(l.sides).some(y=>y>=0),c=l.all>=0||d,[p,f]=u.useState(d);u.useEffect(()=>{d&&f(!0)},[d]);const b=Math.min(4,Math.max(0,s.length-1)),g=y=>{const _=[];for(const w of a){const k=y[w.key];k!==void 0&&k>=0&&_.push(`${w.infix}${s[k]}`)}n(_.length>0?_.join(" "):void 0)},m=y=>{n(y>=0?`${r}-${s[Math.max(0,Math.min(y,s.length-1))]}`:void 0)},h=()=>({...l.sides}),v=(y,_,w,k,x)=>{const $=w>=0;return i.jsxs("div",{style:P8,"data-testid":y,children:[i.jsxs("label",{style:{...ll,minWidth:64},children:[i.jsx("input",{type:"checkbox","data-testid":`${y}-enabled`,checked:$,onChange:C=>x(C.target.checked)}),i.jsx("span",{style:{fontSize:12,color:"#0f172a"},children:o(_)})]}),i.jsx("input",{type:"range","data-testid":`${y}-range`,min:0,max:Math.max(0,s.length-1),step:1,disabled:!$||s.length===0,value:w<0?0:w,onChange:C=>k(Number(C.target.value)),style:{flex:1}}),i.jsx("span",{"data-testid":`${y}-value`,style:{fontSize:12,color:$?"#0f172a":"#94a3b8",minWidth:28,textAlign:"right"},children:$?s[w]:"–"})]},y)};return i.jsxs("div",{className:"g7le-widget g7le-widget--spacing","data-testid":"g7le-widget-spacing",style:N8,children:[i.jsxs("div",{style:M8,children:[i.jsxs("div",{style:L8,"data-testid":"g7le-spacing-mode",children:[i.jsx("button",{type:"button","data-testid":"g7le-spacing-mode-all","data-active":p?"false":"true",onClick:()=>{f(!1),d&&n(void 0)},style:{...Qg,background:p?"#fff":"#2563eb",color:p?"#0f172a":"#fff",borderColor:p?"#cbd5e1":"#2563eb"},children:o("layout_editor.control.spacing.mode_all")}),i.jsx("button",{type:"button","data-testid":"g7le-spacing-mode-sides","data-active":p?"true":"false",onClick:()=>{if(f(!0),l.all>=0){const y={};for(const _ of a)y[_.key]=l.all;g(y)}},style:{...Qg,background:p?"#2563eb":"#fff",color:p?"#fff":"#0f172a",borderColor:p?"#2563eb":"#cbd5e1"},children:o("layout_editor.control.spacing.mode_sides")})]}),i.jsx("span",{"data-testid":"g7le-spacing-value",style:{fontSize:11,color:"#64748b",minWidth:80,textAlign:"right"},children:c?String(t):o("layout_editor.control.default")})]}),p?i.jsx("div",{"data-testid":"g7le-spacing-sides",style:{display:"flex",flexDirection:"column",gap:4},children:a.map(y=>v(`g7le-spacing-side-${y.key}`,y.labelKey,l.sides[y.key]??-1,_=>{const w=h();w[y.key]=_,g(w)},_=>{const w=h();_?w[y.key]=l.sides[y.key]>=0?l.sides[y.key]:b:delete w[y.key],g(w)}))}):i.jsxs("div",{style:oh,children:[i.jsxs("label",{style:{...ll,minWidth:64},children:[i.jsx("input",{type:"checkbox","data-testid":"g7le-spacing-all-enabled",checked:l.all>=0,onChange:y=>m(y.target.checked?b:-1)}),i.jsx("span",{style:{fontSize:12,color:"#64748b"},children:o("layout_editor.control.spacing.all")})]}),i.jsx("input",{type:"range","data-testid":"g7le-spacing-all-range",min:0,max:Math.max(0,s.length-1),step:1,disabled:l.all<0||s.length===0,value:l.all<0?0:l.all,onChange:y=>m(Number(y.target.value)),style:{flex:1}}),i.jsx("span",{"data-testid":"g7le-spacing-all-value",style:{fontSize:12,color:l.all>=0?"#0f172a":"#94a3b8",minWidth:28,textAlign:"right"},children:l.all>=0?s[l.all]:"–"})]})]})}const N8={display:"flex",flexDirection:"column",gap:6,width:"100%"},M8={display:"flex",alignItems:"center",justifyContent:"space-between",gap:8},L8={display:"inline-flex",gap:0,border:"1px solid #cbd5e1",borderRadius:6,overflow:"hidden"},Qg={padding:"3px 10px",fontSize:11,border:"none",borderRight:"1px solid #e2e8f0",cursor:"pointer"},P8={display:"flex",alignItems:"center",gap:8,width:"100%"},O8={display:"inline-flex",gap:0,border:"1px solid #cbd5e1",borderRadius:6,overflow:"hidden"},Rg={display:"flex",flexDirection:"column",gap:6,width:"100%"},eh={display:"flex",gap:6,alignItems:"center"},th={flex:1,padding:"5px 8px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,minWidth:80},nh={padding:"4px 8px",fontSize:11,border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",color:"#64748b",cursor:"pointer",whiteSpace:"nowrap"},B8={display:"flex",flexWrap:"wrap",gap:4},W8={fontSize:11,color:"#94a3b8",fontStyle:"italic",padding:"2px 0"},j8={padding:"3px 9px",fontSize:11,border:"1px solid #cbd5e1",borderRadius:999,background:"#fff",cursor:"pointer"},H8={padding:"4px 10px",fontSize:12,border:"none",borderRight:"1px solid #e2e8f0",cursor:"pointer"},oh={display:"flex",alignItems:"center",gap:8,width:"100%"},ll={display:"inline-flex",alignItems:"center",gap:4},F8={padding:"5px 8px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",minWidth:120},K8={position:"relative",width:38,height:20,borderRadius:10,border:"none",cursor:"pointer",padding:2,display:"inline-flex",alignItems:"center",transition:"background 120ms ease"},V8={display:"block",width:16,height:16,borderRadius:8,background:"#fff",transition:"transform 120ms ease"},U8=["#0f172a","#334155","#64748b","#94a3b8","#e2e8f0","#ffffff","#dc2626","#ea580c","#d97706","#16a34a","#0891b2","#2563eb","#7c3aed","#db2777"],rh=/^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/;function q8(e){return Array.isArray(e.options)?e.options.filter(t=>typeof t.value=="string").map(t=>({value:t.value,swatch:typeof t.swatch=="string"?t.swatch:void 0,label:typeof t.label=="string"?t.label:void 0})):[]}function G8({control:e,value:t,onChange:n,t:o,freeValueDisabled:r}){const s=q8(e),a=s.length>0,l=typeof t=="string"&&s.some(g=>g.value===t)?t:"",d=typeof t=="string"&&rh.test(t)?t:"",[c,p]=u.useState(d);u.useEffect(()=>{p(d)},[d]);const f=g=>{const m=g.trim();if(m===""){n(void 0);return}const h=m.startsWith("#")?m:`#${m}`;rh.test(h)&&n(h)},b=U8;return i.jsxs("div",{className:"g7le-widget g7le-widget--color","data-testid":"g7le-widget-color",style:Y8,children:[r?i.jsx("div",{"data-testid":"g7le-color-free-disabled",style:ez,children:o("layout_editor.property_modal.dark_preset_only")}):i.jsxs("div",{style:X8,children:[i.jsxs("label",{style:J8,children:[i.jsx("span",{"data-testid":"g7le-color-swatch",style:{...Z8,background:d||"transparent",backgroundImage:d?void 0:"linear-gradient(45deg,#e2e8f0 25%,transparent 25%,transparent 75%,#e2e8f0 75%),linear-gradient(45deg,#e2e8f0 25%,#fff 25%,#fff 75%,#e2e8f0 75%)",backgroundSize:"8px 8px",backgroundPosition:"0 0, 4px 4px"}}),i.jsx("input",{type:"color","data-testid":"g7le-color-native",value:d||"#000000",onChange:g=>n(g.target.value),style:{width:0,height:0,opacity:0,position:"absolute"}})]}),i.jsx("input",{type:"text","data-testid":"g7le-color-hex",value:c,placeholder:o("layout_editor.control.color_hex_placeholder"),onChange:g=>p(g.target.value),onBlur:g=>f(g.target.value),onKeyDown:g=>{g.key==="Enter"&&f(g.target.value)},style:Q8}),i.jsx("button",{type:"button","data-testid":"g7le-color-clear",onClick:()=>n(void 0),style:R8,children:o("layout_editor.control.default")})]}),a?i.jsx("div",{style:ih,"data-testid":"g7le-color-token-presets",children:s.map(g=>{const m=l===g.value,h=g.label?g.label.startsWith("$t:")?o(g.label.slice(3)):g.label:g.value;return i.jsx("button",{type:"button","aria-label":h,title:h,"data-testid":`g7le-color-token-${g.value}`,"data-active":m?"true":"false",onClick:()=>n(m?void 0:g.value),style:{...sh,background:g.swatch||"#e2e8f0",outline:m?"2px solid #2563eb":"none"}},g.value)})}):!r&&i.jsx("div",{style:ih,"data-testid":"g7le-color-presets",children:b.map(g=>i.jsx("button",{type:"button","aria-label":g,"data-testid":`g7le-color-preset-${g}`,onClick:()=>n(g),style:{...sh,background:g,outline:d.toLowerCase()===g.toLowerCase()?"2px solid #2563eb":"none"}},g))})]})}const Y8={display:"flex",flexDirection:"column",gap:8},X8={display:"flex",alignItems:"center",gap:6},J8={position:"relative",display:"inline-flex",cursor:"pointer"},Z8={width:24,height:24,borderRadius:4,border:"1px solid #cbd5e1",display:"inline-block"},Q8={flex:1,padding:"5px 8px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,fontFamily:"monospace"},R8={padding:"4px 8px",fontSize:11,border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",cursor:"pointer",color:"#64748b"},ih={display:"grid",gridTemplateColumns:"repeat(7, 1fr)",gap:4},sh={width:"100%",height:18,borderRadius:4,border:"1px solid #cbd5e1",cursor:"pointer",padding:0},ez={fontSize:11,color:"#94a3b8",fontStyle:"italic",padding:"2px 0"},ir=[{value:"fill",labelKey:"layout_editor.control.background_image.mode_fill",size:"cover",repeat:"no-repeat"},{value:"fit",labelKey:"layout_editor.control.background_image.mode_fit",size:"contain",repeat:"no-repeat"},{value:"tile",labelKey:"layout_editor.control.background_image.mode_tile",size:"auto",repeat:"repeat"}];function ah(e){return e&&typeof e=="object"&&!Array.isArray(e)?e:{}}function lh(e){return ir.find(n=>n.size===e.size&&n.repeat===e.repeat)?.value??"fill"}function tz(e){if(!e)return!1;const t=[],n=e.apply;n&&typeof n=="object"&&t.push(n);for(const o of e.options??[])o&&typeof o=="object"&&o.apply&&typeof o.apply=="object"&&t.push(o.apply);return t.length===0?!1:t.every(o=>o.type==="propValue"||o.type==="cssVar"||o.type==="styleProp"&&!Array.isArray(o.props))}function nz({control:e,value:t,onChange:n,t:o}){const r=ah(t),s=tz(e),a=Vf(t,I=>n({url:I}),I=>ah(I).url),l=a.bound,{state:d}=Qe(),c=xo(),[p,f]=u.useState(!1),[b,g]=u.useState(null),[m,h]=u.useState(r.url??""),[v,y]=u.useState([]),_=u.useRef(null),w=d.templateIdentifier,k=d.selectedRoute?.layoutName??"";u.useEffect(()=>{h(r.url??"")},[r.url]);const x=u.useCallback(async()=>{if(!w||!k){y([]);return}const I=await jl(w,k);I.ok&&y(I.data)},[w,k]);u.useEffect(()=>{x()},[x]);const $=I=>{if(!I){n(void 0);return}if(s){n({url:I});return}const N=ir.find(T=>T.value===lh(r))??ir[0];n({url:I,size:r.size??N.size,repeat:r.repeat??N.repeat,position:r.position??"center"})},C=I=>{const N=ir.find(T=>T.value===I);!N||!r.url||n({url:r.url,size:N.size,repeat:N.repeat,position:r.position??"center"})},S=async I=>{const N=I.target.files?.[0];if(!N)return;f(!0),g(null);const T=await Hl(w,k,N);T.ok?($(T.data.url),x()):g(o("layout_editor.control.background_image.upload_failed")),f(!1),_.current&&(_.current.value="")},D=async I=>{(await Fl(I.id)).ok&&y(T=>T.filter(O=>O.id!==I.id))},E=()=>{const I=c.open({ariaLabel:o("layout_editor.attachment_manager.title"),width:720,maxHeightRatio:.82,content:u.createElement(Kl,{templateIdentifier:w,layoutName:k,t:o,onSelect:N=>{$(N),c.close(I)},onChanged:()=>{x()},onClose:()=>c.close(I)})})};return i.jsxs("div",{className:"g7le-widget g7le-widget--image","data-testid":"g7le-widget-image",style:oz,children:[l?i.jsx(qf,{expression:r.url??"",t:o,onReplace:a.beginReplace,testIdPrefix:"g7le-image"}):r.url&&i.jsx("div",{"data-testid":"g7le-image-preview",style:{...rz,backgroundImage:`url(${r.url})`,backgroundSize:s?"contain":r.size??"cover",backgroundRepeat:s?"no-repeat":r.repeat??"no-repeat",backgroundPosition:s?"center":r.position??"center"}}),i.jsxs("div",{style:iz,children:[i.jsxs("label",{style:{...sz,...l?Ii:null},children:[p?"…":o("layout_editor.control.background_image.upload"),i.jsx("input",{ref:_,type:"file",accept:"image/*","data-testid":"g7le-image-file",onChange:S,disabled:p||l,style:{display:"none"}})]}),i.jsx("button",{type:"button","data-testid":"g7le-image-clear",disabled:l,onClick:()=>n(void 0),style:{...az,...l?Ii:null},children:o("layout_editor.control.background_image.clear")})]}),i.jsx("input",{type:"text","data-testid":"g7le-image-url",placeholder:"https://… / URL",value:m,readOnly:l,onChange:I=>h(I.target.value),onBlur:I=>{l||$(I.target.value.trim()||void 0)},style:lz}),a.replacing&&a.original!==null&&i.jsx(Gf,{t:o,onRestore:a.restore,testIdPrefix:"g7le-image"}),!s&&i.jsx("div",{style:dz,"data-testid":"g7le-image-modes",children:ir.map(I=>{const N=lh(r)===I.value;return i.jsx("button",{type:"button","data-testid":`g7le-image-mode-${I.value}`,"data-active":N?"true":"false",disabled:!r.url||l,onClick:()=>C(I.value),style:{...cz,background:N&&r.url?"#2563eb":"#fff",color:N&&r.url?"#fff":"#0f172a"},children:o(I.labelKey)},I.value)})}),i.jsxs("div",{style:uz,"data-testid":"g7le-image-gallery",children:[i.jsxs("div",{style:pz,children:[i.jsx("span",{style:{fontSize:11,color:"#64748b"},children:o("layout_editor.attachment_manager.recent")}),i.jsxs("button",{type:"button","data-testid":"g7le-image-manage",disabled:l,onClick:E,style:{...fz,...l?Ii:null},children:["🖼 ",o("layout_editor.attachment_manager.manage_link")]})]}),v.length>0?i.jsx("div",{style:gz,"data-testid":"g7le-image-thumbs",children:v.slice(0,8).map(I=>i.jsxs("div",{style:hz,"data-testid":`g7le-image-thumb-${I.id}`,children:[i.jsx("button",{type:"button",title:I.original_name,disabled:l,onClick:()=>$(I.url),style:{...yz,backgroundImage:`url(${I.url})`,...l?Ii:null},"data-testid":`g7le-image-thumb-use-${I.id}`}),i.jsx("button",{type:"button",title:o("layout_editor.attachment_manager.delete"),onClick:()=>D(I),style:bz,"data-testid":`g7le-image-thumb-del-${I.id}`,children:"✕"})]},I.id))}):i.jsx("span",{style:{fontSize:11,color:"#94a3b8"},children:o("layout_editor.attachment_manager.empty")})]}),b&&i.jsx("span",{"data-testid":"g7le-image-error",style:{fontSize:11,color:"#dc2626"},children:b})]})}const oz={display:"flex",flexDirection:"column",gap:6},rz={width:"100%",height:80,borderRadius:6,border:"1px solid #cbd5e1"},iz={display:"flex",gap:6},sz={padding:"5px 10px",fontSize:12,border:"1px solid #2563eb",borderRadius:6,background:"#fff",color:"#2563eb",cursor:"pointer",display:"inline-flex",alignItems:"center"},az={padding:"5px 10px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",color:"#64748b",cursor:"pointer"},lz={padding:"5px 8px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6},Ii={opacity:.45,cursor:"not-allowed"},dz={display:"inline-flex",border:"1px solid #cbd5e1",borderRadius:6,overflow:"hidden"},cz={padding:"4px 10px",fontSize:12,border:"none",borderRight:"1px solid #e2e8f0",cursor:"pointer"},uz={display:"flex",flexDirection:"column",gap:4,border:"1px solid #e2e8f0",borderRadius:6,padding:6,marginTop:2},pz={display:"flex",alignItems:"center",justifyContent:"space-between"},fz={fontSize:11,color:"#2563eb",background:"transparent",border:"none",cursor:"pointer",padding:0},gz={display:"flex",gap:6,flexWrap:"wrap"},hz={position:"relative",width:44,height:44},yz={width:44,height:44,borderRadius:6,border:"1px solid #cbd5e1",backgroundSize:"cover",backgroundPosition:"center",cursor:"pointer",padding:0},bz={position:"absolute",top:-6,right:-6,width:16,height:16,borderRadius:"50%",border:"none",background:"#dc2626",color:"#fff",fontSize:9,lineHeight:"16px",cursor:"pointer",padding:0};function mz(e){return Array.isArray(e)&&e.every(t=>t!==null&&typeof t=="object"&&!Array.isArray(t))}function vz(e){return typeof e=="string"&&/\{\{.*\}\}/.test(e)}function _z({value:e,onChange:t,t:n,bindingCandidates:o}){if(vz(e))return i.jsx("div",{className:"g7le-widget g7le-widget--options-list","data-testid":"g7le-widget-options-list-bound",style:Iz,children:n("layout_editor.list_editor.bound_degraded")});const r=mz(e)?e:[],s=b=>{t(b.length===0?void 0:b)},a=(b,g)=>{const m=r.map((h,v)=>v===b?{...h,...g}:h);s(m)},l=b=>{s(r.filter((g,m)=>m!==b))},d=(b,g)=>{const m=b+g;if(m<0||m>=r.length)return;const h=[...r];[h[b],h[m]]=[h[m],h[b]],s(h)},c=()=>{s([...r,{value:"",label:""}])},p=b=>typeof b.label=="string"?b.label:b.label==null?"":String(b.label),f=b=>b.value===void 0||b.value===null?"":String(b.value);return i.jsxs("div",{className:"g7le-widget g7le-widget--options-list","data-testid":"g7le-widget-options-list",style:xz,children:[r.length===0&&i.jsx("div",{"data-testid":"g7le-options-empty",style:Ez,children:n("layout_editor.list_editor.empty")}),r.map((b,g)=>i.jsxs("div",{"data-testid":`g7le-options-row-${g}`,style:wz,children:[i.jsxs("div",{style:dh,children:[i.jsx("span",{style:ch,children:n("layout_editor.list_editor.option_value")}),i.jsx("input",{type:"text","data-testid":`g7le-options-value-${g}`,value:f(b),placeholder:n("layout_editor.list_editor.option_value"),onChange:m=>a(g,{value:m.target.value}),style:Sz})]}),i.jsxs("div",{style:dh,children:[i.jsx("span",{style:ch,children:n("layout_editor.list_editor.option_label")}),i.jsx("div",{"data-testid":`g7le-options-label-${g}`,style:{flex:1,minWidth:0},children:i.jsx(_t,{value:p(b),onChange:m=>a(g,{label:m??""}),t:n,placeholder:n("layout_editor.list_editor.option_label"),testidPrefix:`g7le-options-label-i18n-${g}`,candidates:o,enableExpressionTree:!0,expressionTreeCollapsible:!0})})]}),i.jsxs("div",{style:kz,children:[i.jsx("button",{type:"button","data-testid":`g7le-options-up-${g}`,title:n("layout_editor.list_editor.move_up"),disabled:g===0,onClick:()=>d(g,-1),style:uh,children:"↑"}),i.jsx("button",{type:"button","data-testid":`g7le-options-down-${g}`,title:n("layout_editor.list_editor.move_down"),disabled:g===r.length-1,onClick:()=>d(g,1),style:uh,children:"↓"}),i.jsx("button",{type:"button","data-testid":`g7le-options-remove-${g}`,title:n("layout_editor.list_editor.remove"),onClick:()=>l(g),style:Cz,children:"✕"})]})]},g)),i.jsx("button",{type:"button","data-testid":"g7le-options-add",onClick:c,style:$z,children:n("layout_editor.list_editor.add")})]})}const xz={display:"flex",flexDirection:"column",gap:8,width:"100%"},wz={display:"flex",flexDirection:"column",gap:4,width:"100%",minWidth:0,paddingBottom:6,borderBottom:"1px solid #f1f5f9"},dh={display:"flex",gap:6,alignItems:"center",width:"100%",minWidth:0},ch={flexShrink:0,width:36,fontSize:11,color:"#94a3b8",textAlign:"right"},kz={display:"flex",gap:4,alignItems:"center",justifyContent:"flex-end"},Sz={flex:1,minWidth:0,padding:"4px 6px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6},uh={padding:"2px 6px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",color:"#475569",cursor:"pointer"},Cz={padding:"2px 6px",fontSize:12,border:"1px solid #fecaca",borderRadius:6,background:"#fff",color:"#dc2626",cursor:"pointer"},$z={padding:"4px 8px",fontSize:12,border:"1px dashed #94a3b8",borderRadius:6,background:"#fff",color:"#475569",cursor:"pointer",alignSelf:"flex-start"},Ez={fontSize:11,color:"#94a3b8",fontStyle:"italic"},Iz={fontSize:11,color:"#b45309",fontStyle:"italic",padding:"4px 2px"},ph=/[^A-Za-z0-9\-_:.]/g;function Az(e){return typeof e=="string"&&mr(e)}function fh(e){return mr(e)?An(e).map(t=>t.kind==="binding"?t.raw:t.raw.replace(ph,"")).join(""):e.replace(ph,"")}const Tz={width:"100%",padding:"6px 8px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:4,fontFamily:"monospace"},Dz={marginTop:4,fontSize:11,color:"#94a3b8"},zz={display:"flex",gap:6,alignItems:"center"},Nz={flexShrink:0,padding:"5px 8px",fontSize:11,border:"1px solid #cbd5e1",borderRadius:4,background:"#f8fafc",cursor:"pointer",whiteSpace:"nowrap"};function Mz({value:e,onChange:t,t:n,bindingCandidates:o}){const r=e==null?"":String(e),s=Az(e),a=!!o&&o.length>0,[l,d]=u.useState(!1),c=p=>{const f=fh(p);t(f===""?void 0:f)};return s||l?i.jsxs("div",{"data-testid":"g7le-core-id-chip",children:[i.jsx(Cs,{value:r,onChange:c,t:n,candidates:o,onDone:()=>d(!1),testidPrefix:"g7le-core-id-chip-input"}),i.jsx("div",{style:Dz,children:n("layout_editor.core_props.id.binding_hint")})]}):i.jsx("div",{children:i.jsxs("div",{style:zz,children:[i.jsx("input",{type:"text",className:"g7le-widget g7le-widget--core-id","data-testid":"g7le-widget-core-id",value:r,onChange:p=>{const f=fh(p.target.value);t(f===""?void 0:f)},spellCheck:!1,autoComplete:"off",style:Tz}),a&&i.jsxs("button",{type:"button","data-testid":"g7le-core-id-add-data",onClick:()=>d(!0),style:Nz,title:n("layout_editor.core_props.id.add_data"),children:["🔗 ",n("layout_editor.core_props.id.add_data")]})]})})}const Lz=/[^A-Za-z0-9.\-_]/g;function Pz(e){return typeof e=="string"&&/\{\{[\s\S]*?\}\}/.test(e)}function Oz(e){return e.replace(Lz,"")}const gh={width:"100%",padding:"6px 8px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:4,fontFamily:"monospace",boxSizing:"border-box"},Bz={...gh,background:"#f1f5f9",color:"#64748b",cursor:"not-allowed"},hh={marginTop:4,fontSize:11,color:"#94a3b8"};function Wz({value:e,onChange:t,t:n}){const o=e==null?"":String(e);return Pz(e)?i.jsxs("div",{"data-testid":"g7le-core-datakey-bound",children:[i.jsx("input",{type:"text",className:"g7le-widget g7le-widget--core-datakey-bound","data-testid":"g7le-widget-core-datakey",value:o,readOnly:!0,disabled:!0,style:Bz}),i.jsx("div",{style:hh,children:n("layout_editor.core_props.dataKey.bound_degraded")})]}):i.jsxs("div",{children:[i.jsx("input",{type:"text",className:"g7le-widget g7le-widget--core-datakey","data-testid":"g7le-widget-core-datakey",value:o,placeholder:n("layout_editor.core_props.dataKey.placeholder"),onChange:s=>{const a=Oz(s.target.value);t(a===""?void 0:a)},spellCheck:!1,autoComplete:"off",style:gh}),i.jsx("div",{style:hh,children:n("layout_editor.core_props.dataKey.hint")})]})}let yh=!1;function jz(){yh||(Ge("segmented",S8),Ge("slider",C8),Ge("select",$8),Ge("dimension",I8),Ge("number",A8),Ge("spacing",z8),Ge("toggle",E8),Ge("color",G8),Ge("image",nz),Ge("tag-input",Ss),Ge("page-picker",nk),Ge("datasource-picker",Gc),Ge("binding-picker",Gc),Ge("binding-list",ik),Ge("state-key-picker",Ls),Ge("modal-picker",ok),Ge("i18n-text",rk),Ge("text",sk),Ge("options-list",_z),Ge("core-id",Mz),Ge("core-datakey",Wz),Ge("component-target-picker",M4),yh=!0)}function sr(e){return Array.isArray(e.children)?e.children:[]}function ar(e){if(typeof e.text=="string")return[];let t=null;const n=(o,r)=>{const s=sr(o);for(let a=0;ad===o?lr(l,r,n):l);return{...e,children:a}}function bh(e,t){const n=r=>typeof r.props?.[t]=="string";if(n(e))return[];const o=(r,s)=>{const a=sr(r);for(let l=0;lc===r?mh(d,s,n,o):d);return{...e,children:l}}function Hz(e){return Array.isArray(e.children)?e.children:[]}function Fz(e){const t=e?.itemFields;if(!Array.isArray(t))return[{kind:"text"}];const n=[];for(const o of t){if(!o||typeof o!="object")continue;const r=o;r.kind==="text"?n.push({kind:"text",label:typeof r.label=="string"?r.label:void 0}):r.kind==="prop"&&typeof r.prop=="string"&&r.prop.length>0&&n.push({kind:"prop",prop:r.prop,label:typeof r.label=="string"?r.label:void 0})}return n.length>0?n:[{kind:"text"}]}function dl(e,t){if(!(typeof e!="string"||e.length===0))return e.startsWith("$t:")?t(e.slice(3)):e}function vh({node:e,params:t,spec:n,manifest:o,t:r,onPatchNode:s,templateIdentifier:a,candidates:l}){const d=typeof t?.childComponent=="string"?t.childComponent:null,c=t?.childTemplate&&typeof t.childTemplate=="object"?t.childTemplate:null,p=Fz(t),f=dl(typeof t?.childLabel=="string"?t.childLabel:void 0,r)??d??"",b=Hz(e),g=u.useCallback(x=>{s({...e,children:x})},[e,s]),m=u.useCallback(()=>{if(c)return JSON.parse(JSON.stringify(c));if(!d)return null;const x=n?.componentPalette?.entries?.[d];if(x?.defaultNode&&typeof x.defaultNode=="object")return JSON.parse(JSON.stringify(x.defaultNode));const $=o?.components??{};let C=null;for(const S of Object.values($))if(Array.isArray(S)){const D=S.find(E=>E.name===d);if(D){C=D;break}}return C?Wf(C):{type:"basic",name:d}},[c,d,n,o]),h=u.useCallback(()=>{const x=m();if(x){if(x.text==="")x.text=r("layout_editor.list_editor.new_item");else{const $=ar(x);if($!==null&&$.length>0){const C=Ai(x,$);if(C&&C.text===""){const S=lr(x,$,r("layout_editor.list_editor.new_item"));g([...b,S]);return}}}g([...b,x])}},[m,g,b,r]),v=u.useCallback(x=>{g(b.filter(($,C)=>C!==x))},[g,b]),y=u.useCallback((x,$)=>{const C=x+$;if(C<0||C>=b.length)return;const S=[...b];[S[x],S[C]]=[S[C],S[x]],g(S)},[g,b]),_=u.useCallback((x,$)=>{if(typeof $!="string"||$.length===0)return;const C=b[x];if(!C)return;const S=ar(C);if(S===null)return;const D=b.map((E,I)=>I===x?lr(E,S,$):E);g(D)},[b,g]),w=u.useCallback((x,$,C)=>{if(typeof C!="string"||C.length===0)return;const S=b[x];if(!S)return;const D=bh(S,$);if(D===null)return;const E=b.map((I,N)=>N===x?mh(I,D,$,C):I);g(E)},[b,g]);if(!d&&!c)return i.jsx("div",{className:"g7le-node-editor g7le-node-editor--children","data-testid":"g7le-children-editor-misconfigured",style:wh,children:r("layout_editor.list_editor.no_child_component")});const k=p.length>1||p.some(x=>x.label);return i.jsxs("div",{className:"g7le-node-editor g7le-node-editor--children","data-testid":"g7le-children-editor",style:Kz,children:[i.jsx("div",{style:Vz,children:r("layout_editor.list_editor.items_title")}),b.length===0&&i.jsx("div",{"data-testid":"g7le-children-empty",style:wh,children:r("layout_editor.list_editor.empty")}),b.map((x,$)=>{const C=[];for(const D of p)if(D.kind==="text"){const E=ar(x),I=E!==null?Ai(x,E):null;I&&typeof I.text=="string"&&C.push({key:"text",caption:dl(D.label,r)??(k?r("layout_editor.list_editor.item_label"):void 0),value:I.text,onChange:N=>_($,N),testid:`g7le-children-i18n-${$}`})}else if(D.kind==="prop"&&D.prop){const E=bh(x,D.prop);if(E!==null){const N=Ai(x,E)?.props?.[D.prop];C.push({key:`prop-${D.prop}`,caption:dl(D.label,r)??D.prop,value:typeof N=="string"?N:"",onChange:T=>w($,D.prop,T),testid:`g7le-children-prop-${D.prop}-${$}`})}}const S=typeof x?.name=="string"?x.name:r("layout_editor.list_editor.item");return i.jsxs("div",{"data-testid":`g7le-children-row-${$}`,style:Uz,children:[C.length>0?C.map(D=>i.jsx("div",{style:_h,children:i.jsxs("div",{"data-testid":D.key==="text"?`g7le-children-text-${$}`:`g7le-children-field-${D.key}-${$}`,style:qz,children:[D.caption!==void 0&&i.jsx("div",{"data-testid":`g7le-children-caption-${D.key}-${$}`,style:Gz,children:D.caption}),i.jsx(_t,{value:D.value,onChange:D.onChange,t:r,placeholder:r("layout_editor.list_editor.item_text"),testidPrefix:D.testid,candidates:l,enableExpressionTree:!0,expressionTreeCollapsible:!0})]})},D.key)):i.jsx("div",{style:_h,children:i.jsx("span",{"data-testid":`g7le-children-label-${$}`,style:Xz,children:S})}),i.jsxs("div",{style:Yz,children:[i.jsx("button",{type:"button","data-testid":`g7le-children-up-${$}`,title:r("layout_editor.list_editor.move_up"),disabled:$===0,onClick:()=>y($,-1),style:xh,children:"↑"}),i.jsx("button",{type:"button","data-testid":`g7le-children-down-${$}`,title:r("layout_editor.list_editor.move_down"),disabled:$===b.length-1,onClick:()=>y($,1),style:xh,children:"↓"}),i.jsx("button",{type:"button","data-testid":`g7le-children-remove-${$}`,title:r("layout_editor.list_editor.remove"),onClick:()=>v($),style:Jz,children:"✕"})]})]},$)}),i.jsx("button",{type:"button","data-testid":"g7le-children-add",onClick:h,style:Zz,children:r("layout_editor.list_editor.add_child",{component:f})})]})}const Kz={display:"flex",flexDirection:"column",gap:6,width:"100%",marginBottom:12},Vz={fontSize:12,fontWeight:700,color:"#0f172a",marginBottom:2},Uz={display:"flex",flexDirection:"column",gap:4,width:"100%",minWidth:0,paddingBottom:6,borderBottom:"1px solid #f1f5f9"},_h={display:"flex",gap:4,alignItems:"flex-start",width:"100%",minWidth:0},qz={flex:1,minWidth:0},Gz={fontSize:11,color:"#64748b",marginBottom:2},Yz={display:"flex",gap:4,alignItems:"center",justifyContent:"flex-end"},Xz={flex:1,minWidth:0,padding:"4px 6px",fontSize:12,color:"#475569",fontStyle:"italic",overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap"},xh={padding:"2px 6px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",color:"#475569",cursor:"pointer"},Jz={padding:"2px 6px",fontSize:12,border:"1px solid #fecaca",borderRadius:6,background:"#fff",color:"#dc2626",cursor:"pointer"},Zz={padding:"4px 8px",fontSize:12,border:"1px dashed #94a3b8",borderRadius:6,background:"#fff",color:"#475569",cursor:"pointer",alignSelf:"flex-start"},wh={fontSize:11,color:"#94a3b8",fontStyle:"italic"},mo={rowContainer:"Tbody",row:"Tr",cell:"Td",headerCell:"Th",colSpanProp:"colSpan",rowSpanProp:"rowSpan"};function yt(e){return{rowContainer:e?.rowContainer??mo.rowContainer,row:e?.row??mo.row,cell:e?.cell??mo.cell,headerCell:e?.headerCell??mo.headerCell,colSpanProp:e?.colSpanProp??mo.colSpanProp,rowSpanProp:e?.rowSpanProp??mo.rowSpanProp}}function jn(e){return e?Array.isArray(e.children)?e.children:[]:[]}function kh(e,t){const n=e.props?.[t],o=typeof n=="number"?n:typeof n=="string"?parseInt(n,10):NaN;return Number.isFinite(o)&&o>=1?Math.floor(o):1}function Oe(e,t){const n=yt(t),o=[],r=jn(e),s=[];r.forEach((f,b)=>{(jn(f).some(h=>h.name===n.row)||f.name===n.rowContainer||f.name==="Thead"||f.name==="Tfoot")&&s.push({container:f,containerIndex:b,isHeader:f.name==="Thead"})}),s.length===0&&r.some(f=>f.name===n.row)&&s.push({container:e,containerIndex:-1,isHeader:!1});const a=[],l=[];let d=0,c=0;const p=f=>{for(;a.length<=f;)a.push([]);return a[f]};s.forEach((f,b)=>{jn(f.container).filter(m=>m.name===n.row).forEach((m,h)=>{const v=[],y=p(d);let _=0;jn(m).filter(k=>k.name===n.cell||k.name===n.headerCell).forEach((k,x)=>{for(;y[_];)_++;const $=kh(k,n.colSpanProp),C=kh(k,n.rowSpanProp),S={cell:k,sectionIndex:b,rowIndexInSection:h,cellIndexInRow:x,gridRow:d,gridCol:_,colSpan:$,rowSpan:C,isHeader:k.name===n.headerCell};v.push(S);for(let D=0;D=0?(a.push(s.containerIndex),l=jn(e)[s.containerIndex]??e):l=e;let d=-1,c=-1,p=null;const f=jn(l);for(let m=0;m=r.gridRow&&t=r.gridCol&&nr){s=!0;break}if(s)break}s||t.add(r)}const n=Array.from(t).sort((r,s)=>r-s),o=[];for(let r=0;re.rows.find(l=>(l.cells[0]?.gridRow??-1)===a)?.sectionIndex??e.rows[a]?.sectionIndex??0,o=new Set([0,t]);for(let a=1;aa){l=!0;break}if(l)break}l||o.add(a)}const r=Array.from(o).sort((a,l)=>a-l),s=[];for(let a=0;at>=a.start&&t=o.length?{ok:!1,reason:"boundary"}:o[r].sectionIndex!==o[s].sectionIndex?{ok:!1,reason:"section"}:{ok:!0}}function Di(e,t,n){const o=Sh(e),r=o.findIndex(a=>t>=a.start&&t=o.length?{ok:!1,reason:"boundary"}:{ok:!0}}function Ye(e){return e?Array.isArray(e.children)?e.children:[]:[]}function _n(e,t,n){const o={...e.props??{}};n<=1?delete o[t]:o[t]=n;const r={...e,props:o};return Object.keys(o).length===0&&delete r.props,r}function zi(e,t,n=!1){const o={type:"basic",name:n?e.headerCell:e.cell},r=t?.props?.className;return typeof r=="string"&&r&&(o.props={className:r}),o.text="",o}function dr(e,t){return e.name===t.cell||e.name===t.headerCell}function nt(e,t,n,o){const r=t.sections[n];if(!r)return e;if(r.containerIndex===-1)return o(e);const s=Ye(e).map((a,l)=>l===r.containerIndex?o(a):a);return{...e,children:s}}function bt(e,t,n,o){let r=-1;const s=Ye(e).map(a=>a.name===t&&(r++,r===n)?o(a):a);return{...e,children:s}}function Ni(e,t,n){let o=-1;const r=Ye(e);for(let s=0;s0?nt(e,r,r.sections.length-1,_=>({..._,children:[...Ye(_),y]})):{...e,children:[...Ye(e),y]}}const s=Math.max(-1,Math.min(n,r.rows.length-1)),a=s<0?r.rows[0]:r.rows[s],l=a.cells[0]?.gridRow??s,d=s<0?0:l+1,c=a.sectionIndex,p=s<0?0:a.rowIndexInSection+1,f=[],b=[];let g=0;for(;g0?Ue(r,d-1,g):null;y&&y.gridRow+y.rowSpan>d&&y.rowSpan>1?(b.push(y),g+=y.colSpan):(f.push(zi(o)),g+=1)}let m=e;for(const y of b)m=nt(m,r,y.sectionIndex,_=>bt(_,o.row,y.rowIndexInSection,w=>({...w,children:Ye(w).map((k,x)=>x===y.cellIndexInRow?_n(k,o.rowSpanProp,y.rowSpan+1):k)})));const h={type:"basic",name:o.row,children:f},v=Oe(m,t);return m=nt(m,v,c,y=>{const _=Ye(y);let w=0,k=_.length;for(let x=0;x<_.length;x++)if(_[x].name===o.row){if(w===p){k=x;break}w++}return{...y,children:[..._.slice(0,k),h,..._.slice(k)]}}),m}function $h(e,t,n){const o=yt(t),r=Oe(e,t);if(n<0||n>=r.rows.length||r.rows.length<=1)return e;const s=r.rows[n],a=s.cells[0]?.gridRow??n;let l=e;const d=new Set;for(let p=0;pbt(b,o.row,f.rowIndexInSection,g=>({...g,children:Ye(g).map((m,h)=>h===f.cellIndexInRow?_n(m,o.rowSpanProp,f.rowSpan-1):m)})))),p+=f?f.colSpan:1}const c=Oe(l,t);return l=nt(l,c,s.sectionIndex,p=>{let f=-1;const b=Ye(p).filter(g=>g.name===o.row?(f++,f!==s.rowIndexInSection):!0);return{...p,children:b}}),l}function Eh(e,t,n,o){const r=yt(t),s=Oe(e,t);if(!Ti(s,n,o).ok)return e;const a=Ch(s),l=a.findIndex(h=>n>=h.start&&ns.rows.filter(v=>{const y=v.cells[0]?.gridRow??-1;return v.sectionIndex===f&&y>=h.start&&yv.rowIndexInSection),g=b(c),m=b(p);return g.length===0||m.length===0?e:nt(e,s,f,h=>{const v=[...Ye(h)],y=[];v.forEach((M,B)=>{M.name===r.row&&y.push(B)});const _=g.map(M=>y[M]).filter(M=>M!==void 0),w=m.map(M=>y[M]).filter(M=>M!==void 0);if(_.length===0||w.length===0)return h;const k=_.map(M=>v[M]),x=w.map(M=>v[M]),$=Math.min(..._)n>=m.start&&nk.gridCol>=f.start&&k.gridColk.gridCol>=b.start&&k.gridColk.cellIndexInRow),_=v.map(k=>k.cellIndexInRow),w=Oe(g,t);g=nt(g,w,m.sectionIndex,k=>bt(k,r.row,m.rowIndexInSection,x=>{const $=[...Ye(x)],C=y.map(M=>Ni(x,r,M)).filter(M=>M>=0),S=_.map(M=>Ni(x,r,M)).filter(M=>M>=0);if(C.length===0||S.length===0)return x;const D=C.map(M=>$[M]),E=S.map(M=>$[M]),I=Math.min(...C,...S),N=Math.max(...C,...S),T=[...E,...D],O=[...$.slice(0,I),...T,...$.slice(N+1)];return{...x,children:O}}))}return g}function Ah(e,t,n){const o=yt(t),r=Oe(e,t);if(r.rows.length===0||r.colCount===0)return ul(e,t,-1);const s=Math.max(0,Math.min(n+1,r.colCount));let a=e;const l=new Set;for(const d of r.rows){const c=d.cells[0]?.gridRow??0,p=s>0?Ue(r,c,s-1):null,f=s1){if(!l.has(p.cell)){l.add(p.cell);const h=Oe(a,t);a=nt(a,h,p.sectionIndex,v=>bt(v,o.row,p.rowIndexInSection,y=>({...y,children:Ye(y).map((_,w)=>w===p.cellIndexInRow?_n(_,o.colSpanProp,p.colSpan+1):_)})))}continue}if(p&&p.gridRow0&&d.cells.every(h=>h.isHeader),m=Oe(a,t);a=nt(a,m,d.sectionIndex,h=>bt(h,o.row,d.rowIndexInSection,v=>{const y=[...Ye(v)];let _;if(b<0)_=y.findIndex(w=>dr(w,o)),_<0&&(_=y.length);else{const w=Ni(v,o,b);_=w<0?y.length:w+1}return y.splice(_,0,zi(o,void 0,g)),{...v,children:y}}))}return a}function Th(e,t,n){const o=yt(t),r=Oe(e,t);if(n<0||n>=r.colCount||r.colCount<=1)return e;let s=e;const a=new Set;for(const l of r.rows){const d=l.cells[0]?.gridRow??0,c=Ue(r,d,n);if(!(!c||a.has(c.cell)||c.gridRow1){const p=Oe(s,t);s=nt(s,p,c.sectionIndex,f=>bt(f,o.row,c.rowIndexInSection,b=>({...b,children:Ye(b).map((g,m)=>m===c.cellIndexInRow?_n(g,o.colSpanProp,c.colSpan-1):g)})))}else{const p=Oe(s,t);s=nt(s,p,c.sectionIndex,f=>bt(f,o.row,c.rowIndexInSection,b=>{let g=-1;const m=Ye(b).filter(h=>dr(h,o)?(g++,g!==c.cellIndexInRow):!0);return{...b,children:m}}))}}return s}function pl(e,t,n,o,r,s){const a=yt(t),l=Oe(e,t),d=Math.min(n,r),c=Math.max(n,r),p=Math.min(o,s),f=Math.max(o,s);if(d===c&&p===f)return{table:e,ok:!1,reason:"single"};if(!l.valid)return{table:e,ok:!1,reason:"invalid-grid"};const b=new Set;for(let y=d;y<=c;y++)for(let _=p;_<=f;_++){const w=Ue(l,y,_);if(!w)return{table:e,ok:!1,reason:"hole"};if(w.gridRowc||w.gridCol+w.colSpan-1>f)return{table:e,ok:!1,reason:"not-rectangular"};b.add(w)}const g=Ue(l,d,p),m=Array.from(b).filter(y=>y!==g);let h=nt(e,l,g.sectionIndex,y=>bt(y,a.row,g.rowIndexInSection,_=>({..._,children:Ye(_).map((w,k)=>{if(k!==g.cellIndexInRow)return w;let x=_n(w,a.colSpanProp,f-p+1);return x=_n(x,a.rowSpanProp,c-d+1),x})})));const v=new Map;for(const y of m){const _=`${y.sectionIndex}:${y.rowIndexInSection}`,w=v.get(_)??[];w.push(y),v.set(_,w)}for(const[y,_]of v){const w=parseInt(y.split(":")[0],10),k=_[0].rowIndexInSection,x=new Set(_.map(C=>C.cellIndexInRow)),$=Oe(h,t);h=nt(h,$,w,C=>bt(C,a.row,k,S=>{let D=-1;const E=Ye(S).filter(I=>dr(I,a)?(D++,!x.has(D)):!0);return{...S,children:E}}))}return{table:h,ok:!0}}function Dh(e,t,n,o){const r=yt(t),s=Oe(e,t),a=Ue(s,n,o);if(!a||a.colSpan<=1&&a.rowSpan<=1)return e;const l=a.gridRow,d=a.gridCol,c=a.gridRow+a.rowSpan-1,p=a.gridCol+a.colSpan-1,f=a.isHeader,b=typeof a.cell.props?.className=="string"?a.cell.props.className:void 0;let g=nt(e,s,a.sectionIndex,m=>bt(m,r.row,a.rowIndexInSection,h=>({...h,children:Ye(h).map((v,y)=>{if(y!==a.cellIndexInRow)return v;let _=_n(v,r.colSpanProp,1);return _=_n(_,r.rowSpanProp,1),_})})));for(let m=l;m<=c;m++)for(let h=d;h<=p;h++){if(m===l&&h===d)continue;const v=Oe(g,t),y=v.rows.find(k=>k.cells.some(x=>x.gridRow===m))??v.rows[m];if(!y)continue;let _=-1;for(const k of y.cells)k.gridRow===m&&k.gridColbt(k,r.row,y.rowIndexInSection,x=>{const $=[...Ye(x)];let C;if(_<0)C=$.findIndex(S=>dr(S,r)),C<0&&(C=$.length);else{const S=Ni(x,r,_);C=S<0?$.length:S+1}return $.splice(C,0,zi(r,w,f)),{...x,children:$}}))}return g}function Mi(e,t,n,o,r){const s=yt(t),a=Oe(e,t),l=Ue(a,n,o);return l?nt(e,a,l.sectionIndex,d=>bt(d,s.row,l.rowIndexInSection,c=>({...c,children:Ye(c).map((p,f)=>{if(f!==l.cellIndexInRow)return p;const b={...p.props??{}};r.trim()===""?delete b.className:b.className=r;const g={...p,props:b};return Object.keys(b).length===0&&delete g.props,g})}))):e}function Rt(e,t,n,o,r,s){const a=yt(t),l=Oe(e,t),d=Ue(l,n,o);return d?nt(e,l,d.sectionIndex,c=>bt(c,a.row,d.rowIndexInSection,p=>({...p,children:Ye(p).map((f,b)=>{if(b!==d.cellIndexInRow)return f;const g={...f.props??{}},m={...g.style??{}};!s||s.trim()===""?delete m[r]:m[r]=s,Object.keys(m).length===0?delete g.style:g.style=m;const h={...f,props:g};return Object.keys(g).length===0&&delete h.props,h})}))):e}function vo(e,t,n,o,r,s,a){const l=yt(t),d=Oe(e,t),c=Ue(d,n,o);if(!c)return e;const p=a==="dark";return nt(e,d,c.sectionIndex,f=>bt(f,l.row,c.rowIndexInSection,b=>({...b,children:Ye(b).map((g,m)=>{if(m!==c.cellIndexInRow)return g;const h={...g.props??{}},v=typeof h.className=="string"?h.className:"",y=OC(v,s,r,p);y===""?delete h.className:h.className=y;const _={...g,props:h};return Object.keys(h).length===0&&delete _.props,_})})))}function zh(e,t,n,o,r){const s=yt(t),a=Oe(e,t),l=Ue(a,n,o);return l?nt(e,a,l.sectionIndex,d=>bt(d,s.row,l.rowIndexInSection,c=>({...c,children:Ye(c).map((p,f)=>f===l.cellIndexInRow?{...p,text:r}:p)}))):e}function Nh(e,t,n,o,r){const s=yt(t),a=Oe(e,t),l=Ue(a,n,o);return l?nt(e,a,l.sectionIndex,d=>bt(d,s.row,l.rowIndexInSection,c=>({...c,children:Ye(c).map((p,f)=>f===l.cellIndexInRow?r:p)}))):e}const Mh=/^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/;function Qz(e){return e.split(/\s+/).filter(Boolean)}function Lh(e,t){for(const n of t)if(e===n||/^-?\d+$/.test(e.slice(n.length+1))&&e.startsWith(n+"-"))return!0;return!1}function Ph({className:e,catalog:t,t:n,onChange:o,colorScheme:r="light",onPresetToken:s,colorStyle:a,onColorStyle:l,disabled:d}){const c=t?.sides??[],p=t?.widths??[],f=t?.colors??[],b=r==="dark",g=u.useMemo(()=>c.map(E=>E.prefix),[c]),m=u.useMemo(()=>new Set(f.map(E=>E.token)),[f]),h=u.useMemo(()=>Qz(e),[e]),v=u.useMemo(()=>Qr(e,b),[e,b]),y=u.useMemo(()=>v.find(E=>m.has(E))??null,[v,m]),_=u.useMemo(()=>{const E=new Set;for(const I of c)h.some(T=>T===I.prefix||T.startsWith(I.prefix+"-")&&/^-?\d+$/.test(T.slice(I.prefix.length+1)))&&E.add(I.value);return E},[h,c]),w=u.useMemo(()=>{const E=h.find(N=>Lh(N,g));if(!E)return"none";const I=p.filter(N=>N.value!=="none");for(const N of c){if(E===N.prefix)return I.find(T=>(T.suffix??"")==="")?.value??"thin";if(E.startsWith(N.prefix+"-")){const T=E.slice(N.prefix.length),O=I.find(M=>(M.suffix??"")===T);if(O)return O.value}}return"thin"},[h,g,c,p]);if(!t||c.length===0||p.length===0)return null;function k(E){return E.filter(I=>!Lh(I,g))}function x(E,I){const N=k(h),T=p.find(B=>B.value===I);if(!T||I==="none"||E.size===0)return N.join(" ").trim();const O=T.suffix??"",M=[...N];for(const B of c)E.has(B.value)&&M.push(O?`${B.prefix}${O}`:B.prefix);return M.join(" ").trim()}const $=E=>{const I=new Set(_);I.has(E)?I.delete(E):(E==="all"?I.clear():I.delete("all"),I.add(E));const N=w==="none"?p.find(T=>T.value!=="none")?.value??"thin":w;o(x(I,N))},C=E=>{const I=_.size===0?new Set(["all"]):_;o(x(I,E))},S=E=>{s?.(E)},D=(E,I)=>E&&E.startsWith("$t:")?n(E.slice(3)):E||I;return i.jsxs("div",{className:"g7le-cell-border","data-testid":"g7le-cell-border",style:Rz,children:[i.jsx("div",{style:eN,children:i.jsx("span",{style:tN,children:n("layout_editor.table_editor.cell_border")})}),i.jsx("div",{style:fl,"data-testid":"g7le-cell-border-widths",children:p.map(E=>i.jsx("button",{type:"button",disabled:d,"data-testid":`g7le-cell-border-width-${E.value}`,"aria-pressed":w===E.value,onClick:()=>C(E.value),style:w===E.value?Oh:gl,children:D(E.label,E.value)},E.value))}),w!=="none"&&i.jsx("div",{style:fl,"data-testid":"g7le-cell-border-sides",children:c.map(E=>i.jsx("button",{type:"button",disabled:d,"data-testid":`g7le-cell-border-side-${E.value}`,"aria-pressed":_.has(E.value),onClick:()=>$(E.value),style:_.has(E.value)?Oh:gl,children:D(E.label,E.value)},E.value))}),w!=="none"&&(f.length>0||l)&&i.jsx(i.Fragment,{children:i.jsxs("div",{style:fl,"data-testid":"g7le-cell-border-colors",children:[f.map(E=>{const I=y===E.token;return i.jsx("button",{type:"button",disabled:d,title:D(E.label,E.value),"data-testid":`g7le-cell-border-color-${E.value}`,"aria-pressed":I,onClick:()=>S(E.token),style:{...Bh,background:E.swatch??"#fff",outline:I?"2px solid #2563eb":"1px solid #cbd5e1"}},E.value)}),l&&(()=>{const E=!!a&&f.some(N=>N.swatch&&N.swatch.toLowerCase()===a.toLowerCase()),I=!b&&!!a&&!E;return i.jsx("label",{title:n(b?"layout_editor.table_editor.dark_free_color_hint":"layout_editor.table_inplace.custom_color"),"data-testid":"g7le-cell-border-color-custom",style:{...Bh,position:"relative",overflow:"hidden",display:"inline-flex",alignItems:"center",justifyContent:"center",background:I&&Mh.test(a)?a:"conic-gradient(red, yellow, lime, aqua, blue, magenta, red)",outline:I?"2px solid #2563eb":"1px solid #cbd5e1",opacity:b?.4:1,cursor:b?"not-allowed":"pointer"},children:i.jsx("input",{type:"color",disabled:d||b,"data-testid":"g7le-cell-border-color-picker",value:a&&Mh.test(a)?a:"#000000",onChange:N=>l(N.target.value),style:{position:"absolute",width:0,height:0,opacity:0}})})})()]})})]})}const Rz={display:"flex",flexDirection:"column",gap:6},eN={display:"flex",alignItems:"center",justifyContent:"space-between",gap:8},tN={fontSize:12,fontWeight:600,color:"#475569"},fl={display:"flex",gap:4,flexWrap:"wrap",alignItems:"center"},gl={padding:"3px 8px",fontSize:11,border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",color:"#475569",cursor:"pointer"},Oh={...gl,border:"1px solid #2563eb",background:"#eff6ff",color:"#1d4ed8",fontWeight:600},Bh={width:22,height:22,borderRadius:5,padding:0,cursor:"pointer",boxSizing:"border-box"},Wh=/^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/;function jh({colorStyle:e,className:t,catalog:n,t:o,colorScheme:r="light",onPresetToken:s,onCustomColor:a,onClear:l,disabled:d}){const c=n?.colors??[],p=r==="dark",f=e??"",b=Qr(t??"",p),g=c.find(_=>_.token&&b.includes(_.token))?.token??null,m=(_,w)=>_&&_.startsWith("$t:")?o(_.slice(3)):_||w,h=!g&&(p||f.trim()===""),v=!!f&&c.some(_=>_.swatch&&_.swatch.toLowerCase()===f.toLowerCase()),y=!p&&!!f&&!v;return i.jsxs("div",{className:"g7le-cell-fill","data-testid":"g7le-cell-fill",style:nN,children:[i.jsx("div",{style:oN,children:i.jsx("span",{style:rN,children:o("layout_editor.table_editor.cell_fill")})}),i.jsxs("div",{style:iN,"data-testid":"g7le-cell-fill-colors",children:[i.jsx("button",{type:"button",disabled:d,title:o("layout_editor.table_editor.cell_fill_none"),"data-testid":"g7le-cell-fill-none","aria-pressed":h,onClick:()=>l?.(),style:{...hl,background:"repeating-conic-gradient(#e2e8f0 0% 25%, #ffffff 0% 50%) 50% / 10px 10px",outline:h?"2px solid #2563eb":"1px solid #cbd5e1"}}),c.map(_=>{const w=!!_.token&&g===_.token,k=!p&&!!_.swatch&&f.toLowerCase()===(_.swatch??"").toLowerCase(),x=w||k,$=d||p&&!_.token;return i.jsx("button",{type:"button",disabled:$,title:m(_.label,_.value),"data-testid":`g7le-cell-fill-color-${_.value}`,"aria-pressed":x,onClick:()=>{_.token&&s?s(_.token):!p&&_.swatch&&a&&a(_.swatch)},style:{...hl,background:_.swatch??"#fff",outline:x?"2px solid #2563eb":"1px solid #cbd5e1",opacity:$?.4:1,cursor:$?"not-allowed":"pointer"}},_.value)}),a&&i.jsx("label",{title:o(p?"layout_editor.table_editor.dark_free_color_hint":"layout_editor.table_editor.cell_fill_custom"),"data-testid":"g7le-cell-fill-custom",style:{...hl,position:"relative",overflow:"hidden",display:"inline-flex",alignItems:"center",justifyContent:"center",background:y&&Wh.test(f)?f:"conic-gradient(red, yellow, lime, aqua, blue, magenta, red)",outline:y?"2px solid #2563eb":"1px solid #cbd5e1",opacity:p?.4:1,cursor:p?"not-allowed":"pointer"},children:i.jsx("input",{type:"color",disabled:d||p,"data-testid":"g7le-cell-fill-color-picker",value:f&&Wh.test(f)?f:"#ffffff",onChange:_=>a(_.target.value),style:{position:"absolute",width:0,height:0,opacity:0}})})]})]})}const nN={display:"flex",flexDirection:"column",gap:6},oN={display:"flex",alignItems:"center",justifyContent:"space-between",gap:8},rN={fontSize:12,fontWeight:600,color:"#475569"},iN={display:"flex",gap:4,flexWrap:"wrap",alignItems:"center"},hl={width:22,height:22,borderRadius:5,padding:0,cursor:"pointer",boxSizing:"border-box"};function sN(e){const t=/^(\d+)px$/.exec(e.trim());return t?parseInt(t[1],10):null}function Hh({paddingStyle:e,catalog:t,t:n,onChange:o,disabled:r}){const s=t?.steps??[],a=e??"",l=sN(a),d=(h,v)=>h&&h.startsWith("$t:")?n(h.slice(3)):h||v,c=s.some(h=>(h.px??0)===(l??-1)),p=l!==null&&!c?String(l):"",[f,b]=u.useState(p);u.useEffect(()=>{b(p)},[e]);const g=h=>{o(h&&h>0?`${h}px`:""),b("")},m=h=>{const v=parseInt(h,10);Number.isFinite(v)&&v>=0&&o(v>0?`${v}px`:"")};return i.jsxs("div",{className:"g7le-cell-padding","data-testid":"g7le-cell-padding",style:aN,children:[i.jsx("div",{style:lN,children:n("layout_editor.table_editor.cell_padding")}),i.jsxs("div",{style:dN,"data-testid":"g7le-cell-padding-steps",children:[s.map(h=>{const v=(h.px??0)===(l??-1)||!h.px&&a.trim()==="";return i.jsx("button",{type:"button",disabled:r,"data-testid":`g7le-cell-padding-step-${h.value}`,"aria-pressed":v,onClick:()=>g(h.px),style:v?cN:Fh,children:d(h.label,h.value)},h.value)}),i.jsxs("span",{style:{display:"inline-flex",alignItems:"center",gap:3},children:[i.jsx("input",{type:"number",min:0,disabled:r,"data-testid":"g7le-cell-padding-free",value:f,placeholder:n("layout_editor.table_editor.cell_padding_custom"),onChange:h=>b(h.target.value),onBlur:()=>f.trim()!==""&&m(f),onKeyDown:h=>{h.key==="Enter"&&f.trim()!==""&&m(f)},style:uN}),i.jsx("span",{style:{fontSize:11,color:"#94a3b8"},children:"px"})]})]})]})}const aN={display:"flex",flexDirection:"column",gap:6},lN={fontSize:12,fontWeight:600,color:"#475569"},dN={display:"flex",gap:4,flexWrap:"wrap",alignItems:"center"},Fh={padding:"3px 8px",fontSize:11,border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",color:"#475569",cursor:"pointer"},cN={...Fh,border:"1px solid #2563eb",background:"#eff6ff",color:"#1d4ed8",fontWeight:600},uN={width:48,padding:"3px 5px",fontSize:11,border:"1px solid #cbd5e1",borderRadius:6,boxSizing:"border-box"};function Kh({colorScheme:e,onChange:t,t:n,disabled:o}){const r=e==="dark";return i.jsxs("div",{className:"g7le-color-scheme-tabs","data-testid":"g7le-color-scheme-tabs",style:pN,children:[i.jsxs("div",{style:fN,children:[i.jsx("span",{style:gN,children:n("layout_editor.table_editor.cell_color")}),i.jsxs("div",{style:hN,children:[i.jsx("button",{type:"button",disabled:o,"data-testid":"g7le-color-scheme-light","aria-pressed":!r,onClick:()=>t("light"),style:r?yl:Vh,children:n("layout_editor.property_modal.scope.scheme_light")}),i.jsx("button",{type:"button",disabled:o,"data-testid":"g7le-color-scheme-dark","aria-pressed":r,onClick:()=>t("dark"),style:r?Vh:yl,children:n("layout_editor.property_modal.scope.scheme_dark")})]})]}),r&&i.jsx("div",{style:yN,"data-testid":"g7le-color-scheme-dark-hint",children:n("layout_editor.table_editor.dark_free_color_hint")})]})}const pN={display:"flex",flexDirection:"column",gap:3},fN={display:"flex",alignItems:"center",justifyContent:"space-between",gap:8},gN={fontSize:12,fontWeight:700,color:"#0f172a"},hN={display:"flex",gap:2},yl={padding:"2px 9px",fontSize:11,border:"none",borderBottom:"2px solid transparent",background:"transparent",color:"#94a3b8",cursor:"pointer"},Vh={...yl,color:"#1d4ed8",borderBottom:"2px solid #2563eb",fontWeight:600},yN={fontSize:10,color:"#94a3b8",fontStyle:"italic"},bl="dark:";function Uh(e){return typeof e=="string"?e.split(/\s+/).filter(Boolean):[]}function qh(e,t){return t==="dark"?e.filter(n=>n.startsWith(bl)).map(n=>n.slice(bl.length)):e.filter(n=>!n.startsWith(bl))}const bN={none:0,thin:1,medium:2,thick:4},Gh={top:{w:"borderTopWidth",s:"borderTopStyle",c:"borderTopColor"},bottom:{w:"borderBottomWidth",s:"borderBottomStyle",c:"borderBottomColor"},left:{w:"borderLeftWidth",s:"borderLeftStyle",c:"borderLeftColor"},right:{w:"borderRightWidth",s:"borderRightStyle",c:"borderRightColor"}},Yh=["top","bottom","left","right"];function mN(e,t,n="light"){if(!e||!t)return{};const o=t.sides??[],r=t.widths??[],s=t.colors??[],a=Uh(e.props?.className);if(a.length===0&&!e.props?.style)return{};const l=[...r].filter(y=>y.value!=="none").sort((y,_)=>(_.suffix??"").length-(y.suffix??"").length),d={};for(const y of o)for(const _ of l){const w=_.suffix?`${y.prefix}${_.suffix}`:y.prefix;if(a.includes(w)){d[y.value]=_.value;break}}const c=e.props?.style??{},p=qh(a,n),f=s.find(y=>p.includes(y.token))?.swatch??"",g=(n==="light"&&typeof c.borderColor=="string"?c.borderColor:"")||f||"#94a3b8",m=y=>{const _=Gh[y]?.c,w=_?c[_]:void 0;return typeof w=="string"&&w?w:g},h={},v=(y,_)=>{const w=Gh[y];if(!w)return;const k=bN[_]??1;h[w.w]=`${k}px`,h[w.s]=k>0?"solid":"none",k>0&&(h[w.c]=m(y))};if(d.all)for(const y of Yh)v(y,d.all);for(const y of Yh)d[y]&&v(y,d[y]);return h}function vN(e,t,n="light"){if(!e?.props)return{};const o=e.props.style??{},r={},s=Uh(e.props.className),a=t?.colors?.find(c=>c.token&&qh(s,n).includes(c.token))?.swatch??"",d=(n==="light"&&typeof o.backgroundColor=="string"?o.backgroundColor:"")||a;return d&&(r.backgroundColor=d),typeof o.padding=="string"&&o.padding&&(r.padding=o.padding),r}const _N=/^\s*\$t:(custom\.[a-zA-Z0-9._-]+)\s*$/,xN=/^\s*\$t:([a-zA-Z0-9._-]+)\s*$/,wN=/\{\{.*?\}\}/;function Xh(e,t,n){const o=e?.text;if(typeof o!="string")return{display:"",editable:!1,isKey:!1};const r=vt(o);if(r&&r.key.startsWith("custom.")){let a="";try{const l=Pt.getInstance().translate(r.key,{templateId:t,locale:n});a=l&&l!==r.key?l:""}catch{a=""}return{display:a,editable:!0,isKey:!0}}if(wN.test(o))return{display:o,editable:!1,isKey:!1};const s=xN.exec(o);if(s){const a=s[1];let l="";try{const d=Pt.getInstance().translate(a,{templateId:t,locale:n});l=d&&d!==a?d:""}catch{l=""}return{display:l,editable:!0,isKey:!0}}return{display:o,editable:!0,isKey:!1}}function ml(e){const t=ar(e);return t===null?{node:null,path:null}:{node:Ai(e,t),path:t}}function kN(e){if(!e||typeof e.text!="string")return null;const t=_N.exec(e.text);if(t)return t[1];const n=vt(e.text);return n&&n.key.startsWith("custom.")?n.key:null}function SN({node:e,params:t,t:n,onPatchNode:o,templateIdentifier:r,candidates:s}){const{state:a}=Qe(),l=a.selectedRoute?.layoutName??null,d=a.locale,c=r??a.templateIdentifier,p=u.useMemo(()=>t&&typeof t=="object"?t:null,[t]);u.useMemo(()=>yt(p),[p]);const f=u.useMemo(()=>Oe(e,p),[e,p]),b=u.useMemo(()=>{const U=p?.cellBorder;return U&&typeof U=="object"?U:null},[p]),g=u.useMemo(()=>{const U=p?.cellBackground;return U&&typeof U=="object"?U:null},[p]),m=u.useMemo(()=>{const U=p?.cellPadding;return U&&typeof U=="object"?U:null},[p]),[h,v]=u.useState(null),[y,_]=u.useState(null),[w,k]=u.useState({}),[x,$]=u.useState(!1),[C,S]=u.useState(null),[D,E]=u.useState(null),[I,N]=u.useState("light"),T=u.useCallback(U=>o(U),[o]),O=u.useMemo(()=>h?Ue(f,h.r,h.c):null,[f,h]),M=D!==null?D:h?.c??null,B=C!==null?C:h?.r??null,z=u.useMemo(()=>{const U=(b?.sides??[]).find(ke=>ke.value==="all")??(b?.sides??[])[0],ue=(b?.widths??[]).find(ke=>ke.value!=="none");if(!U||!ue)return[];const ge=ue.suffix??"";return[ge?`${U.prefix}${ge}`:U.prefix]},[b]),H=u.useCallback((U,ue,ge)=>{if(z.length===0)return U;const ke=(b?.sides??[]).map(Q=>Q.prefix),he=Ue(Oe(U,p),ue,ge),ne=(typeof he?.cell?.props?.className=="string"?he.cell.props.className:"").split(/\s+/).filter(Boolean);return ne.some(Q=>ke.some(de=>Q===de||Q.startsWith(de+"-")&&/^-?\d+$/.test(Q.slice(de.length+1))))?U:Mi(U,p,ue,ge,[...ne,...z].join(" ").trim())},[z,b,p]),P=u.useCallback(U=>{if(!h)return;const ue=Math.min(h.r,y?.r??h.r),ge=Math.max(h.r,y?.r??h.r),ke=Math.min(h.c,y?.c??h.c),he=Math.max(h.c,y?.c??h.c),j=new Set,ne=[];for(let Q=ue;Q<=ge;Q++)for(let de=ke;de<=he;de++)j.add(`${Q}:${de}`),ne.push({r:Q,c:de});let pe=e;for(const Q of ne){if(pe=H(pe,Q.r,Q.c),pe=Rt(pe,p,Q.r,Q.c,"borderColor",U),Q.r>0&&!j.has(`${Q.r-1}:${Q.c}`)){const de=Ue(Oe(pe,p),Q.r-1,Q.c);de&&(pe=Rt(pe,p,de.gridRow,de.gridCol,"borderBottomColor",U))}if(Q.c>0&&!j.has(`${Q.r}:${Q.c-1}`)){const de=Ue(Oe(pe,p),Q.r,Q.c-1);de&&(pe=Rt(pe,p,de.gridRow,de.gridCol,"borderRightColor",U))}}T(pe)},[h,y,e,p,T,H]),A=u.useCallback(U=>P(U),[P]),L=u.useMemo(()=>(b?.colors??[]).map(U=>U.token).filter(Boolean),[b]),F=u.useCallback(U=>{if(!h||L.length===0)return;const ue=Math.min(h.r,y?.r??h.r),ge=Math.max(h.r,y?.r??h.r),ke=Math.min(h.c,y?.c??h.c),he=Math.max(h.c,y?.c??h.c);let j=e;for(let ne=ue;ne<=ge;ne++)for(let pe=ke;pe<=he;pe++)j=H(j,ne,pe),j=vo(j,p,ne,pe,U,L,I);T(j)},[h,y,e,p,T,H,L,I]),Y=u.useMemo(()=>{if(I==="dark")return"";const U=O?.cell.props?.style;return typeof U?.borderColor=="string"?U.borderColor:""},[O,I]),V=u.useCallback((U,ue)=>{if(!h)return;const ge=Math.min(h.r,y?.r??h.r),ke=Math.max(h.r,y?.r??h.r),he=Math.min(h.c,y?.c??h.c),j=Math.max(h.c,y?.c??h.c);let ne=e;for(let pe=ge;pe<=ke;pe++)for(let Q=he;Q<=j;Q++)ne=Rt(ne,p,pe,Q,U,ue);T(ne)},[h,y,e,p,T]),X=u.useMemo(()=>(g?.colors??[]).map(U=>U.token).filter(U=>!!U),[g]),Z=u.useCallback(()=>h?{r0:Math.min(h.r,y?.r??h.r),r1:Math.max(h.r,y?.r??h.r),c0:Math.min(h.c,y?.c??h.c),c1:Math.max(h.c,y?.c??h.c)}:null,[h,y]),ie=u.useCallback(U=>{const ue=Z();if(!ue||X.length===0)return;let ge=e;for(let ke=ue.r0;ke<=ue.r1;ke++)for(let he=ue.c0;he<=ue.c1;he++)ge=vo(ge,p,ke,he,U,X,I);T(ge)},[Z,e,p,T,X,I]),oe=u.useCallback(U=>V("backgroundColor",U),[V]),G=u.useCallback(()=>{const U=Z();if(!U)return;let ue=e;for(let ge=U.r0;ge<=U.r1;ge++)for(let ke=U.c0;ke<=U.c1;ke++)ue=vo(ue,p,ge,ke,void 0,X,I),I==="light"&&(ue=Rt(ue,p,ge,ke,"backgroundColor",void 0));T(ue)},[Z,e,p,T,X,I]),ee=u.useCallback(U=>V("padding",U),[V]),fe=u.useMemo(()=>{if(I==="dark")return"";const U=O?.cell.props?.style;return typeof U?.backgroundColor=="string"?U.backgroundColor:""},[O,I]),K=u.useMemo(()=>typeof O?.cell.props?.className=="string"?O.cell.props.className:"",[O]),te=u.useMemo(()=>{const U=O?.cell.props?.style;return typeof U?.padding=="string"?U.padding:""},[O]),ye=u.useCallback(U=>U==="section"?n("layout_editor.table_editor.move_blocked_section"):"",[n]),Se=u.useCallback((U,ue)=>Ti(f,U,ue),[f]),De=u.useCallback((U,ue)=>Di(f,U,ue),[f]),Ie=u.useCallback(U=>T(ul(e,p,U)),[e,p,T]),Ve=u.useCallback(U=>{T($h(e,p,U)),v(null),_(null)},[e,p,T]),ze=u.useCallback((U,ue)=>T(Eh(e,p,U,ue)),[e,p,T]),Be=u.useCallback(U=>T(Ah(e,p,U)),[e,p,T]),Xe=u.useCallback(U=>{T(Th(e,p,U)),v(null),_(null)},[e,p,T]),Ne=u.useCallback((U,ue)=>T(Ih(e,p,U,ue)),[e,p,T]),Je=u.useCallback(()=>{if(!h||!y)return;const U=pl(e,p,h.r,h.c,y.r,y.c);U.ok&&(T(U.table),v({r:Math.min(h.r,y.r),c:Math.min(h.c,y.c)}),_(null))},[e,p,h,y,T]),ve=u.useCallback(()=>{h&&(T(Dh(e,p,h.r,h.c)),_(null))},[e,p,h,T]),qe=u.useCallback(U=>{if(!h)return;const ue=Math.min(h.r,y?.r??h.r),ge=Math.max(h.r,y?.r??h.r),ke=Math.min(h.c,y?.c??h.c),he=Math.max(h.c,y?.c??h.c);let j=e;for(let ne=ue;ne<=ge;ne++)for(let pe=ke;pe<=he;pe++)j=Mi(j,p,ne,pe,U);T(j)},[e,p,h,y,T]),We=u.useCallback(async(U,ue)=>{const ge=`${U}:${ue}`,ke=w[ge];if(ke===void 0)return;const he=Ue(f,U,ue);if(!he)return;const{node:j,path:ne}=ml(he.cell);if(ne===null)return;const pe=Xh(j,c,d);if(pe.editable){if(ke===pe.display){k(Q=>{const de={...Q};return delete de[ge],de});return}$(!0);try{const Q=kN(j);if(Q)await kr(c,Q,d,ke),await on(c,d);else{const de=await Yn(c,l,d,ke);if(de.kind==="ok"&&de.resource){const Ae=`$t:${de.resource.translation_key}`;if(ne.length===0)T(zh(e,p,U,ue,Ae));else{const dt=lr(he.cell,ne,Ae);T(Nh(e,p,U,ue,dt))}await on(c,d)}}}finally{$(!1),k(Q=>{const de={...Q};return delete de[ge],de})}}},[w,f,c,d,l,e,p,T]),ce=u.useMemo(()=>{if(!O)return null;const{node:U,path:ue}=ml(O.cell);return ue===null?null:{value:typeof U?.text=="string"?U.text:"",editable:!0}},[O]),q=u.useCallback(U=>{if(typeof U!="string"||U.length===0||!h)return;const ue=Ue(f,h.r,h.c);if(!ue)return;const ge=ar(ue.cell);if(ge!==null)if(ge.length===0)T(zh(e,p,h.r,h.c,U));else{const ke=lr(ue.cell,ge,U);T(Nh(e,p,h.r,h.c,ke))}},[h,f,e,p,T]),se=u.useCallback((U,ue,ge)=>{ge?_({r:U,c:ue}):(v({r:U,c:ue}),_(null))},[]),ae=u.useMemo(()=>Array.from({length:f.colCount},(U,ue)=>ue),[f.colCount]),be=!!(h&&y&&(h.r!==y.r||h.c!==y.c)),Ce=!!(O&&(O.colSpan>1||O.rowSpan>1)),$e=O&&typeof O.cell.props?.className=="string"?O.cell.props.className:"";return i.jsxs("div",{className:"g7le-node-editor g7le-node-editor--table","data-testid":"g7le-table-editor",style:CN,children:[i.jsx("div",{style:$N,children:n("layout_editor.table_editor.title")}),i.jsx("div",{style:{overflowX:"auto"},children:i.jsxs("table",{"data-testid":"g7le-table-editor-grid",style:EN,children:[i.jsx("thead",{children:i.jsxs("tr",{children:[i.jsx("th",{style:IN}),ae.map(U=>{const ue=De(U,-1),ge=De(U,1),ke=M===U;return i.jsx("th",{style:AN,"data-testid":`g7le-table-colgutter-${U}`,onMouseEnter:()=>E(U),onMouseLeave:()=>E(he=>he===U?null:he),children:i.jsxs("div",{style:{...DN,visibility:ke?"visible":"hidden"},children:[i.jsx("button",{type:"button",title:ue.ok?n("layout_editor.table_editor.move_col_left"):ye(ue.reason)||n("layout_editor.table_editor.move_col_left"),disabled:!ue.ok||x,onClick:()=>Ne(U,-1),"data-testid":`g7le-table-col-left-${U}`,style:ue.ok?xn:Li,children:"←"}),i.jsx("button",{type:"button",title:n("layout_editor.table_editor.add_col"),disabled:x,onClick:()=>Be(U),"data-testid":`g7le-table-col-add-${U}`,style:xn,children:"+"}),i.jsx("button",{type:"button",title:n("layout_editor.table_editor.remove_col"),disabled:f.colCount<=1||x,onClick:()=>Xe(U),"data-testid":`g7le-table-col-remove-${U}`,style:Jh,children:"✕"}),i.jsx("button",{type:"button",title:ge.ok?n("layout_editor.table_editor.move_col_right"):ye(ge.reason)||n("layout_editor.table_editor.move_col_right"),disabled:!ge.ok||x,onClick:()=>Ne(U,1),"data-testid":`g7le-table-col-right-${U}`,style:ge.ok?xn:Li,children:"→"})]})},U)})]})}),i.jsx("tbody",{children:f.rows.map((U,ue)=>{const ge=U.cells[0]?.gridRow??ue,ke=B===ge;return i.jsxs("tr",{"data-testid":`g7le-table-row-${ge}`,onMouseEnter:()=>S(ge),onMouseLeave:()=>S(he=>he===ge?null:he),children:[i.jsx("td",{style:TN,"data-testid":`g7le-table-rowgutter-${ge}`,children:(()=>{const he=Se(ge,-1),j=Se(ge,1);return i.jsxs("div",{style:{...zN,visibility:ke?"visible":"hidden"},children:[i.jsx("button",{type:"button",title:he.ok?n("layout_editor.table_editor.move_row_up"):ye(he.reason)||n("layout_editor.table_editor.move_row_up"),disabled:!he.ok||x,onClick:()=>ze(ge,-1),"data-testid":`g7le-table-row-up-${ge}`,style:he.ok?xn:Li,children:"↑"}),i.jsx("button",{type:"button",title:n("layout_editor.table_editor.add_row"),disabled:x,onClick:()=>Ie(ge),"data-testid":`g7le-table-row-add-${ge}`,style:xn,children:"+"}),i.jsx("button",{type:"button",title:n("layout_editor.table_editor.remove_row"),disabled:f.rows.length<=1||x,onClick:()=>Ve(ge),"data-testid":`g7le-table-row-remove-${ge}`,style:Jh,children:"✕"}),i.jsx("button",{type:"button",title:j.ok?n("layout_editor.table_editor.move_row_down"):ye(j.reason)||n("layout_editor.table_editor.move_row_down"),disabled:!j.ok||x,onClick:()=>ze(ge,1),"data-testid":`g7le-table-row-down-${ge}`,style:j.ok?xn:Li,children:"↓"})]})})()}),U.cells.map(he=>{const j=`${he.gridRow}:${he.gridCol}`,{node:ne,path:pe}=ml(he.cell),Q=Xh(ne,c,d),de=pe!==null&&Q.editable,Ae=pe===null,dt=w[j],pt=dt!==void 0?dt:Q.display,ot=h&&h.r===he.gridRow&&h.c===he.gridCol,Le=h&&y&&he.gridRow>=Math.min(h.r,y.r)&&he.gridRow<=Math.max(h.r,y.r)&&he.gridCol>=Math.min(h.c,y.c)&&he.gridCol<=Math.max(h.c,y.c);return i.jsx("td",{colSpan:he.colSpan,rowSpan:he.rowSpan,"data-testid":`g7le-table-cell-${he.gridRow}-${he.gridCol}`,onClick:je=>se(he.gridRow,he.gridCol,je.shiftKey),style:{...NN,...mN(he.cell,b,I),...vN(he.cell,g,I),...he.isHeader?MN:{},...ot?LN:{},...Le&&!ot?PN:{}},children:de?i.jsx("input",{type:"text","data-testid":`g7le-table-cell-input-${he.gridRow}-${he.gridCol}`,value:pt,placeholder:n("layout_editor.table_editor.cell_text"),disabled:x,onMouseDown:je=>{je.shiftKey&&je.preventDefault(),se(he.gridRow,he.gridCol,je.shiftKey)},onClick:je=>je.stopPropagation(),onChange:je=>k(Ze=>({...Ze,[j]:je.target.value})),onBlur:()=>void We(he.gridRow,he.gridCol),style:ON}):Ae?i.jsx("span",{"data-testid":`g7le-table-cell-struct-${he.gridRow}-${he.gridCol}`,style:Zh,children:n("layout_editor.table_editor.structural_cell")}):i.jsx("span",{style:Zh,children:Q.display})},j)})]},ue)})})]})}),i.jsxs("button",{type:"button","data-testid":"g7le-table-add-row-bottom",onClick:()=>Ie(f.rows.length-1),disabled:x,style:BN,children:["+ ",n("layout_editor.table_editor.add_row")]}),i.jsxs("div",{style:WN,"data-testid":"g7le-table-cell-tools",children:[i.jsx("button",{type:"button","data-testid":"g7le-table-merge",onClick:Je,disabled:!be||x,style:Qh,children:n("layout_editor.table_editor.merge_cells")}),i.jsx("button",{type:"button","data-testid":"g7le-table-unmerge",onClick:ve,disabled:!Ce||x,style:Qh,children:n("layout_editor.table_editor.unmerge_cells")})]}),O&&ce&&i.jsxs("div",{style:HN,"data-testid":"g7le-table-cell-i18n-section",children:[i.jsx("div",{style:FN,children:n("layout_editor.table_editor.cell_i18n_label")}),i.jsx(_t,{value:ce.value,onChange:q,t:n,placeholder:n("layout_editor.table_editor.cell_text"),testidPrefix:"g7le-table-cell-i18n",candidates:s,enableExpressionTree:!0,expressionTreeCollapsible:!0})]}),O&&i.jsxs("div",{style:jN,"data-testid":"g7le-table-cell-color-section",children:[i.jsx(Kh,{colorScheme:I,onChange:N,t:n,disabled:x}),b&&i.jsx("div",{style:vl,"data-testid":"g7le-table-cell-border-row",children:i.jsx(Ph,{className:$e,catalog:b,t:n,onChange:qe,colorScheme:I,onPresetToken:F,colorStyle:Y,onColorStyle:A,disabled:x})}),i.jsx("div",{style:vl,"data-testid":"g7le-table-cell-fill-row",children:i.jsx(jh,{colorStyle:fe,className:K,catalog:g,t:n,colorScheme:I,onPresetToken:ie,onCustomColor:oe,onClear:G,disabled:x})})]}),O&&i.jsx("div",{style:vl,"data-testid":"g7le-table-cell-padding-row",children:i.jsx(Hh,{paddingStyle:te,catalog:m,t:n,onChange:ee,disabled:x})}),O&&!b&&i.jsxs("label",{style:KN,"data-testid":"g7le-table-cell-border-row",children:[i.jsx("span",{style:VN,children:n("layout_editor.table_editor.cell_border")}),i.jsx("input",{type:"text","data-testid":"g7le-table-cell-border-input",value:$e,placeholder:n("layout_editor.table_editor.cell_class_placeholder"),onChange:U=>qe(U.target.value),disabled:x,style:UN})]}),!f.valid&&i.jsx("div",{"data-testid":"g7le-table-grid-warning",style:qN,children:n("layout_editor.table_editor.grid_invalid")}),i.jsx("div",{style:GN,children:n("layout_editor.table_editor.select_hint")})]})}const CN={display:"flex",flexDirection:"column",gap:8,width:"100%",marginBottom:12},$N={fontSize:12,fontWeight:700,color:"#0f172a"},EN={borderCollapse:"collapse",width:"auto"},IN={width:28,background:"transparent",border:"none"},AN={padding:2,border:"none",background:"transparent"},TN={padding:2,border:"none",background:"transparent",verticalAlign:"middle"},DN={display:"flex",gap:2,justifyContent:"center"},zN={display:"grid",gridTemplateColumns:"repeat(2, 16px)",gap:2,justifyContent:"center"},xn={width:16,height:16,lineHeight:"14px",padding:0,fontSize:10,border:"1px solid #cbd5e1",borderRadius:4,background:"#fff",color:"#475569",cursor:"pointer"},Jh={...xn,border:"1px solid #fecaca",color:"#dc2626"},Li={...xn,cursor:"not-allowed",opacity:.4,color:"#94a3b8",borderColor:"#e2e8f0"},NN={border:"1px solid #cbd5e1",padding:0,width:64,minWidth:64,maxWidth:120,height:24,background:"#fff",cursor:"pointer"},MN={background:"#f1f5f9",fontWeight:600},LN={outline:"2px solid #2563eb",outlineOffset:-2},PN={background:"#dbeafe"},ON={width:"100%",boxSizing:"border-box",padding:"2px 5px",fontSize:11,height:22,lineHeight:"18px",border:"none",background:"transparent",outline:"none"},Zh={display:"block",padding:"2px 5px",fontSize:10,color:"#64748b",fontStyle:"italic",overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap",maxWidth:110},BN={padding:"4px 8px",fontSize:12,border:"1px dashed #94a3b8",borderRadius:6,background:"#fff",color:"#475569",cursor:"pointer",alignSelf:"flex-start"},WN={display:"flex",gap:6},Qh={padding:"4px 8px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",color:"#475569",cursor:"pointer"},jN={display:"flex",flexDirection:"column",gap:8,borderTop:"1px solid #f1f5f9",paddingTop:8},HN={display:"flex",flexDirection:"column",gap:6,borderTop:"1px solid #f1f5f9",paddingTop:8},FN={fontSize:12,fontWeight:700,color:"#0f172a"},vl={display:"flex",flexDirection:"column",gap:6},KN={display:"flex",alignItems:"center",gap:6},VN={fontSize:12,color:"#475569",minWidth:60},UN={flex:1,minWidth:0,padding:"4px 6px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6},qN={fontSize:11,color:"#b45309",background:"#fffbeb",border:"1px solid #fde68a",borderRadius:6,padding:"4px 6px"},GN={fontSize:11,color:"#94a3b8"},YN=/\{\{.*?\}\}/;function XN(e,t,n){const r=(e.props??{})[t];return Array.isArray(r)?r:r===void 0&&n?JSON.parse(JSON.stringify(n)):[]}function JN(e){return typeof e=="string"&&YN.test(e)}function _l(e,t,n){return e?e.startsWith("$t:")?t(e.slice(3)):e:n}function ZN(e,t,n){return typeof e=="object"&&e!==null?e[t.key]:n?e:void 0}function QN(e,t,n,o){if(typeof e=="object"&&e!==null){const r={...e};return o===void 0?delete r[t.key]:r[t.key]=o,r}return n?o:{[t.key]:o}}function Rh({node:e,params:t,t:n,onPatchNode:o,templateIdentifier:r,candidates:s}){const a=typeof t?.arrayProp=="string"?t.arrayProp:null,l=Array.isArray(t?.fields)?t.fields:[],d=_l(typeof t?.itemLabel=="string"?t.itemLabel:void 0,n,n("layout_editor.array_editor.item")),c=t?.newItem,p=c&&typeof c=="object"?c:null,f=typeof c=="string"||typeof c=="number"?c:null,b=(l.find($=>$.primary)??l[0])?.key,g=(e.props??{})[a??""],m=Array.isArray(t?.defaultItems)?t.defaultItems:null,h=a?XN(e,a,m):[],v=u.useCallback($=>{if(!a)return;const C={...e.props??{}};C[a]=$,o({...e,props:C})},[a,e,o]),y=u.useCallback(()=>{if(p)return JSON.parse(JSON.stringify(p));if(f!==null)return f;const $={};for(const C of l)(C.widget??"text")==="boolean"?$[C.key]=!1:$[C.key]="";return $},[p,f,l]),_=u.useCallback(()=>{v([...h,y()])},[v,h,y]),w=u.useCallback($=>{v(h.filter((C,S)=>S!==$))},[v,h]),k=u.useCallback(($,C)=>{const S=$+C;if(S<0||S>=h.length)return;const D=[...h];[D[$],D[S]]=[D[S],D[$]],v(D)},[v,h]),x=u.useCallback(($,C,S)=>{const D=C.key===b,E=h.map((I,N)=>N===$?QN(I,C,D,S):I);v(E)},[h,v,b]);return a?JN(g)||g!==void 0&&!Array.isArray(g)?i.jsx("div",{className:"g7le-node-editor g7le-node-editor--array","data-testid":"g7le-array-editor-bound",style:aM,children:n("layout_editor.array_editor.bound_degraded")}):i.jsxs("div",{className:"g7le-node-editor g7le-node-editor--array","data-testid":"g7le-array-editor",style:RN,children:[i.jsx("div",{style:eM,children:n("layout_editor.array_editor.items_title")}),h.length===0&&i.jsx("div",{"data-testid":"g7le-array-empty",style:ty,children:n("layout_editor.array_editor.empty")}),h.map(($,C)=>i.jsxs("div",{"data-testid":`g7le-array-row-${C}`,style:tM,children:[i.jsxs("div",{style:nM,children:[i.jsxs("span",{style:oM,children:["#",C+1]}),i.jsx("div",{style:{flex:1}}),i.jsx("button",{type:"button","data-testid":`g7le-array-up-${C}`,title:n("layout_editor.array_editor.move_up"),disabled:C===0,onClick:()=>k(C,-1),style:ey,children:"↑"}),i.jsx("button",{type:"button","data-testid":`g7le-array-down-${C}`,title:n("layout_editor.array_editor.move_down"),disabled:C===h.length-1,onClick:()=>k(C,1),style:ey,children:"↓"}),i.jsx("button",{type:"button","data-testid":`g7le-array-remove-${C}`,title:n("layout_editor.array_editor.remove"),onClick:()=>w(C),style:iM,children:"✕"})]}),l.map(S=>{const D=S.widget??"text",E=S.key===b,I=_l(S.label,n,S.key),N=ZN($,S,E),T=`g7le-array-field-${C}-${S.key}`;if(D==="boolean")return i.jsxs("label",{style:Hn,children:[i.jsx("span",{style:Fn,children:I}),i.jsx("input",{type:"checkbox","data-testid":T,checked:N===!0,onChange:M=>x(C,S,M.target.checked)})]},S.key);if(D==="select"){const M=Array.isArray(S.options)?S.options:[];return i.jsxs("label",{style:Hn,children:[i.jsx("span",{style:Fn,children:I}),i.jsxs("select",{"data-testid":T,value:N==null?"":String(N),onChange:B=>x(C,S,B.target.value===""?void 0:B.target.value),style:cr,children:[i.jsx("option",{value:"",children:"—"}),M.map((B,z)=>i.jsx("option",{value:String(B.value),children:_l(B.label,n,String(B.value))},z))]})]},S.key)}if(D==="i18n-text"){const M=typeof N=="string"?N:N==null?"":String(N);return i.jsxs("label",{style:Hn,children:[i.jsx("span",{style:Fn,children:I}),i.jsx("div",{"data-testid":T,style:{flex:1,minWidth:0},children:i.jsx(_t,{value:M,onChange:B=>x(C,S,B===""?void 0:B),t:n,placeholder:I,testidPrefix:`${T}-i18n`,candidates:s,enableExpressionTree:!0,expressionTreeCollapsible:!0})})]},S.key)}if(D==="number"){const M=N==null?"":String(N);return i.jsxs("label",{style:Hn,children:[i.jsx("span",{style:Fn,children:I}),i.jsx("input",{type:"number","data-testid":T,value:M,onChange:B=>{const z=B.target.value;x(C,S,z===""?void 0:Number(z))},style:cr})]},S.key)}if(D==="color"){const M=typeof N=="string"?N:"",B=/^#[0-9a-fA-F]{6}$/.test(M)?M:"#000000";return i.jsxs("label",{style:Hn,children:[i.jsx("span",{style:Fn,children:I}),i.jsx("input",{type:"color","data-testid":`${T}-swatch`,value:B,onChange:z=>x(C,S,z.target.value),style:rM}),i.jsx("input",{type:"text","data-testid":T,value:M,placeholder:"#7C3AED",onChange:z=>x(C,S,z.target.value===""?void 0:z.target.value),style:cr})]},S.key)}if(D==="number-list"){const M=Array.isArray(N)?N.join(", "):"";return i.jsxs("label",{style:Hn,children:[i.jsx("span",{style:Fn,children:I}),i.jsx("input",{type:"text","data-testid":T,value:M,placeholder:n("layout_editor.array_editor.number_list_placeholder"),onChange:B=>{const z=B.target.value.split(",").map(H=>H.trim()).filter(H=>H!=="").map(H=>Number(H)).filter(H=>!Number.isNaN(H));x(C,S,z)},style:cr})]},S.key)}const O=N==null?"":String(N);return i.jsxs("label",{style:Hn,children:[i.jsx("span",{style:Fn,children:I}),i.jsx("input",{type:"text","data-testid":T,value:O,placeholder:D==="icon"?n("layout_editor.array_editor.icon_placeholder"):I,onChange:M=>x(C,S,M.target.value===""?void 0:M.target.value),style:cr})]},S.key)})]},C)),i.jsx("button",{type:"button","data-testid":"g7le-array-add",onClick:_,style:sM,children:n("layout_editor.array_editor.add",{item:d})})]}):i.jsx("div",{className:"g7le-node-editor g7le-node-editor--array","data-testid":"g7le-array-editor-misconfigured",style:ty,children:n("layout_editor.array_editor.no_array_prop")})}const RN={display:"flex",flexDirection:"column",gap:8,width:"100%",marginBottom:12},eM={fontSize:12,fontWeight:700,color:"#0f172a",marginBottom:2},tM={display:"flex",flexDirection:"column",gap:4,padding:8,border:"1px solid #e2e8f0",borderRadius:8,background:"#f8fafc"},nM={display:"flex",gap:4,alignItems:"center"},oM={fontSize:11,fontWeight:700,color:"#64748b"},Hn={display:"flex",flexWrap:"wrap",gap:6,alignItems:"center"},Fn={fontSize:11,color:"#475569",minWidth:64,flexShrink:0},cr={flex:1,minWidth:0,padding:"4px 6px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6},rM={width:32,height:28,padding:0,border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",cursor:"pointer",flexShrink:0},ey={padding:"2px 6px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",color:"#475569",cursor:"pointer"},iM={padding:"2px 6px",fontSize:12,border:"1px solid #fecaca",borderRadius:6,background:"#fff",color:"#dc2626",cursor:"pointer"},sM={padding:"4px 8px",fontSize:12,border:"1px dashed #94a3b8",borderRadius:6,background:"#fff",color:"#475569",cursor:"pointer",alignSelf:"flex-start"},ty={fontSize:11,color:"#94a3b8",fontStyle:"italic"},aM={fontSize:11,color:"#b45309",fontStyle:"italic",padding:"4px 2px"};function lM(e,t){return e?e.startsWith("$t:")?t(e.slice(3)):e:null}function dM(e){const{params:t,t:n}=e,o=Array.isArray(t?.groups)?t.groups:[];return o.length===0?i.jsx("div",{className:"g7le-node-editor g7le-node-editor--array-group","data-testid":"g7le-array-group-misconfigured",style:fM,children:n("layout_editor.array_editor.no_array_prop")}):i.jsx("div",{className:"g7le-node-editor g7le-node-editor--array-group","data-testid":"g7le-array-group-editor",style:cM,children:o.map((r,s)=>{const a=lM(r.title,n),{title:l,...d}=r;return i.jsxs("div",{"data-testid":`g7le-array-group-${r.arrayProp??s}`,style:uM,children:[a&&i.jsx("div",{style:pM,children:a}),i.jsx(Rh,{...e,params:d})]},r.arrayProp??s)})})}const cM={display:"flex",flexDirection:"column",gap:16,width:"100%",marginBottom:12},uM={display:"flex",flexDirection:"column",gap:4},pM={fontSize:12,fontWeight:700,color:"#1e293b",borderBottom:"1px solid #e2e8f0",paddingBottom:4},fM={fontSize:11,color:"#94a3b8",fontStyle:"italic"},gM=/\{\{.*?\}\}/;function hM(e){return typeof e=="string"&&gM.test(e)}function yM(e,t,n){return e?e.startsWith("$t:")?t(e.slice(3)):e:n}function bM(e){const{node:t,params:n,t:o,onPatchNode:r}=e,s=typeof n?.arrayProp=="string"?n.arrayProp:null,a=typeof n?.idField=="string"?n.idField:"id",l=typeof n?.cellChildrenProp=="string"?n.cellChildrenProp:"cellChildren",d=typeof n?.cellChildComponent=="string"?n.cellChildComponent:"Div",c=yM(typeof n?.itemLabel=="string"?n.itemLabel:void 0,o,o("layout_editor.array_editor.item")),[p,f]=u.useState(null),b=s?(t.props??{})[s]:void 0,g=Array.isArray(b)?b:[],m=u.useCallback($=>{if(!s)return;const C={...t.props??{}};C[s]=$,r({...t,props:C})},[s,t,r]),h=u.useCallback(()=>{const $=n?.newItem;return $&&typeof $=="object"?JSON.parse(JSON.stringify($)):{[a]:"",[l]:[]}},[n,a,l]),v=u.useCallback(()=>{m([...g,h()])},[m,g,h]),y=u.useCallback($=>{m(g.filter((C,S)=>S!==$)),f(null)},[m,g]),_=u.useCallback(($,C)=>{const S=$+C;if(S<0||S>=g.length)return;const D=[...g];[D[$],D[S]]=[D[S],D[$]],m(D)},[m,g]),w=u.useCallback(($,C)=>{const S=g.map((D,E)=>E===$?{...D,[a]:C}:D);m(S)},[g,m,a]),k=u.useCallback($=>({type:"composite",name:"CardGridCell",children:Array.isArray($[l])?$[l]:[]}),[l]),x=u.useCallback($=>C=>{const S=Array.isArray(C.children)?C.children:[],D=g.map((E,I)=>I===$?{...E,[l]:S}:E);m(D)},[g,m,l]);return s?hM(b)||b!==void 0&&!Array.isArray(b)?i.jsx("div",{className:"g7le-node-editor g7le-node-editor--cell-tree","data-testid":"g7le-cell-tree-bound",style:IM,children:o("layout_editor.array_editor.bound_degraded")}):i.jsxs("div",{className:"g7le-node-editor g7le-node-editor--cell-tree","data-testid":"g7le-cell-tree-editor",style:mM,children:[i.jsx("div",{style:vM,children:o("layout_editor.cell_tree_editor.columns_title")}),g.length===0&&i.jsx("div",{"data-testid":"g7le-cell-tree-empty",style:oy,children:o("layout_editor.array_editor.empty")}),g.map(($,C)=>{const S=typeof $[a]=="string"?$[a]:"",D=p===C;return i.jsxs("div",{"data-testid":`g7le-cell-tree-row-${C}`,style:_M,children:[i.jsxs("div",{style:xM,children:[i.jsxs("span",{style:wM,children:["#",C+1]}),i.jsx("input",{type:"text","data-testid":`g7le-cell-tree-id-${C}`,value:S,placeholder:o("layout_editor.cell_tree_editor.id_placeholder"),onChange:E=>w(C,E.target.value),style:kM}),i.jsx("button",{type:"button","data-testid":`g7le-cell-tree-up-${C}`,title:o("layout_editor.array_editor.move_up"),disabled:C===0,onClick:()=>_(C,-1),style:ny,children:"↑"}),i.jsx("button",{type:"button","data-testid":`g7le-cell-tree-down-${C}`,title:o("layout_editor.array_editor.move_down"),disabled:C===g.length-1,onClick:()=>_(C,1),style:ny,children:"↓"}),i.jsx("button",{type:"button","data-testid":`g7le-cell-tree-remove-${C}`,title:o("layout_editor.array_editor.remove"),onClick:()=>y(C),style:SM,children:"✕"})]}),i.jsx("button",{type:"button","data-testid":`g7le-cell-tree-toggle-${C}`,onClick:()=>f(D?null:C),style:CM,children:o(D?"layout_editor.cell_tree_editor.collapse_cells":"layout_editor.cell_tree_editor.expand_cells")}),D&&i.jsx("div",{"data-testid":`g7le-cell-tree-cells-${C}`,style:$M,children:i.jsx(vh,{...e,node:k($),params:{childComponent:d},onPatchNode:x(C)})})]},C)}),i.jsx("button",{type:"button","data-testid":"g7le-cell-tree-add",onClick:v,style:EM,children:o("layout_editor.cell_tree_editor.add_column",{item:c})})]}):i.jsx("div",{className:"g7le-node-editor g7le-node-editor--cell-tree","data-testid":"g7le-cell-tree-misconfigured",style:oy,children:o("layout_editor.array_editor.no_array_prop")})}const mM={display:"flex",flexDirection:"column",gap:8,width:"100%",marginBottom:12},vM={fontSize:12,fontWeight:700,color:"#0f172a",marginBottom:2},_M={display:"flex",flexDirection:"column",gap:6,padding:8,border:"1px solid #e2e8f0",borderRadius:8,background:"#f8fafc"},xM={display:"flex",gap:4,alignItems:"center"},wM={fontSize:11,fontWeight:700,color:"#64748b",flexShrink:0},kM={flex:1,minWidth:0,padding:"4px 6px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6},ny={padding:"2px 6px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",color:"#475569",cursor:"pointer"},SM={padding:"2px 6px",fontSize:12,border:"1px solid #fecaca",borderRadius:6,background:"#fff",color:"#dc2626",cursor:"pointer"},CM={padding:"3px 8px",fontSize:11,border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",color:"#2563eb",cursor:"pointer",alignSelf:"flex-start"},$M={paddingLeft:8,borderLeft:"2px solid #e2e8f0"},EM={padding:"4px 8px",fontSize:12,border:"1px dashed #94a3b8",borderRadius:6,background:"#fff",color:"#475569",cursor:"pointer",alignSelf:"flex-start"},oy={fontSize:11,color:"#94a3b8",fontStyle:"italic"},IM={fontSize:11,color:"#b45309",fontStyle:"italic",padding:"4px 2px"};function AM(e,t,n){const o=Oe(e,t);if(!n||n.length===0)return{measured:[],grid:o};const r=[];for(const s of o.rows)for(const a of s.cells){const d=cl(e,a,t).map(p=>`children.${p}`).join("."),c=n.find(p=>p.path===d||p.path.endsWith("."+d));c&&r.push({ref:a,box:{top:c.top,left:c.left,width:c.width,height:c.height}})}return{measured:r,grid:o}}const xl=26,wl=50,kl=6,ry=44,iy=18,sy="g7le-table-inplace-empty-cell-style";function TM(){if(typeof document>"u"||document.getElementById(sy))return;const e=document.createElement("style");e.id=sy,e.textContent=".g7le-preview-frame td[data-editor-name],.g7le-preview-frame th[data-editor-name]{min-width:56px;height:38px;}.g7le-preview-frame table[data-editor-name]{min-width:120px;}",document.head.appendChild(e)}function DM({node:e,params:t,nodeBox:n,cellBoxes:o,selectedCellPath:r,colorScheme:s,t:a,onPatchNode:l,onRequestInlineEdit:d}){const c=u.useMemo(()=>t&&typeof t=="object"?t:null,[t]),{measured:p,grid:f}=u.useMemo(()=>AM(e,c,o),[e,c,o]),[b,g]=u.useState(null),[m,h]=u.useState(null),v=u.useMemo(()=>{if(!r)return null;for(const j of f.rows)for(const ne of j.cells){const pe=cl(e,ne,c).map(Q=>`children.${Q}`).join(".");if(r===pe||r.startsWith(pe+"."))return{r:ne.gridRow,c:ne.gridCol}}return null},[r,f,e,c]),y=u.useMemo(()=>b&&Ue(f,b.r,b.c)?b:v,[b,v,f]),[_,w]=u.useState(!1),[k,x]=u.useState(s==="dark"?"dark":"light"),[$,C]=u.useState(null),[S,D]=u.useState(null),E=u.useRef(null),I=u.useCallback(()=>{E.current!==null&&(clearTimeout(E.current),E.current=null)},[]),N=u.useCallback(()=>{I(),E.current=setTimeout(()=>{C(null),D(null),E.current=null},240)},[I]);u.useEffect(()=>()=>I(),[I]),u.useEffect(()=>{TM()},[]);const T=u.useCallback(j=>l(j),[l]),O=u.useMemo(()=>y?Ue(f,y.r,y.c):null,[f,y]),M=u.useMemo(()=>O&&typeof O.cell.props?.className=="string"?O.cell.props.className:"",[O]),B=u.useCallback(j=>T(ul(e,c,j)),[e,c,T]),z=u.useCallback(j=>T($h(e,c,j)),[e,c,T]),H=u.useCallback((j,ne)=>T(Eh(e,c,j,ne)),[e,c,T]),P=u.useCallback(j=>T(Ah(e,c,j)),[e,c,T]),A=u.useCallback(j=>T(Th(e,c,j)),[e,c,T]),L=u.useCallback((j,ne)=>T(Ih(e,c,j,ne)),[e,c,T]),F=u.useCallback(j=>{if(!O)return;const ne=O.gridRow,pe=O.gridCol,Q=j==="down"?ne+O.rowSpan:ne+O.rowSpan-1,de=j==="right"?pe+O.colSpan:pe+O.colSpan-1;if(Q>=f.rows.length||de>=f.colCount)return;const Ae=pl(e,c,ne,pe,Q,de);Ae.ok&&T(Ae.table)},[O,f,e,c,T]),Y=!!(O&&O.gridCol+O.colSpan!y||!m||!Ue(f,m.r,m.c)?null:{r0:Math.min(y.r,m.r),c0:Math.min(y.c,m.c),r1:Math.max(y.r,m.r),c1:Math.max(y.c,m.c)},[y,m,f]),Z=u.useMemo(()=>{if(!X)return[];const j=new Set,ne=[];for(const pe of f.rows)for(const Q of pe.cells)if(Q.gridRow>=X.r0&&Q.gridRow<=X.r1&&Q.gridCol>=X.c0&&Q.gridCol<=X.c1){const de=`${Q.gridRow}:${Q.gridCol}`;j.has(de)||(j.add(de),ne.push({r:Q.gridRow,c:Q.gridCol}))}return ne},[X,f]),ie=Z.length>1,oe=u.useCallback(()=>{if(!X)return;const j=pl(e,c,X.r0,X.c0,X.r1,X.c1);j.ok&&(T(j.table),h(null))},[X,e,c,T]),G=u.useCallback(()=>{y&&T(Dh(e,c,y.r,y.c))},[e,c,y,T]),ee=u.useMemo(()=>{const j=c?.cellBorder;return j&&typeof j=="object"?j:null},[c]),fe=u.useMemo(()=>{const j=c?.cellBackground;return j&&typeof j=="object"?j:null},[c]),K=u.useMemo(()=>{const j=c?.cellPadding;return j&&typeof j=="object"?j:null},[c]),te=u.useMemo(()=>{if(_){const j=new Set,ne=[];for(const pe of f.rows)for(const Q of pe.cells){const de=`${Q.gridRow}:${Q.gridCol}`;j.has(de)||(j.add(de),ne.push({r:Q.gridRow,c:Q.gridCol}))}return ne}return ie?Z:y?[{r:y.r,c:y.c}]:[]},[_,ie,Z,y,f]),ye=u.useCallback(j=>{if(te.length===0)return;let ne=e;for(const pe of te){const Q=(ee?.colors??[]).map(Ae=>Ae.token),de=j.split(/\s+/).filter(Ae=>Ae&&!Q.includes(Ae)).join(" ");ne=Mi(ne,c,pe.r,pe.c,de)}T(ne)},[e,c,te,ee,T]),Se=u.useMemo(()=>{const j=(ee?.sides??[]).find(Q=>Q.value==="all")??(ee?.sides??[])[0],ne=(ee?.widths??[]).find(Q=>Q.value!=="none");if(!j||!ne)return[];const pe=ne.suffix??"";return[pe?`${j.prefix}${pe}`:j.prefix]},[ee]),De=u.useCallback((j,ne,pe)=>{if(Se.length===0)return j;const Q=(ee?.sides??[]).map(Le=>Le.prefix),de=Ue(Oe(j,c),ne,pe),dt=(typeof de?.cell?.props?.className=="string"?de.cell.props.className:"").split(/\s+/).filter(Boolean);if(dt.some(Le=>Q.some(je=>Le===je||Le.startsWith(je+"-")&&/^-?\d+$/.test(Le.slice(je.length+1)))))return j;const ot=[...dt,...Se].join(" ").trim();return Mi(j,c,ne,pe,ot)},[Se,ee,c]),Ie=u.useCallback((j,ne)=>{if(j.length===0)return;const pe=new Set(j.map(de=>`${de.r}:${de.c}`));let Q=e;for(const de of j){if(Q=De(Q,de.r,de.c),Q=Rt(Q,c,de.r,de.c,"borderColor",ne),de.r>0&&!pe.has(`${de.r-1}:${de.c}`)){const Ae=Ue(Oe(Q,c),de.r-1,de.c);Ae&&(Q=Rt(Q,c,Ae.gridRow,Ae.gridCol,"borderBottomColor",ne))}if(de.c>0&&!pe.has(`${de.r}:${de.c-1}`)){const Ae=Ue(Oe(Q,c),de.r,de.c-1);Ae&&(Q=Rt(Q,c,Ae.gridRow,Ae.gridCol,"borderRightColor",ne))}}T(Q)},[e,c,T,De]),Ve=u.useMemo(()=>(ee?.colors??[]).map(j=>j.token).filter(Boolean),[ee]),ze=u.useCallback(j=>{if(te.length===0||Ve.length===0)return;let ne=e;for(const pe of te)ne=De(ne,pe.r,pe.c),ne=vo(ne,c,pe.r,pe.c,j,Ve,k);T(ne)},[te,e,c,T,De,Ve,k]),Be=u.useCallback(j=>Ie(te,j),[Ie,te]),Xe=u.useMemo(()=>{if(k==="dark")return"";const j=O?.cell.props?.style;return typeof j?.borderColor=="string"?j.borderColor:""},[O,k]),Ne=u.useCallback((j,ne)=>{if(te.length===0)return;let pe=e;for(const Q of te)pe=Rt(pe,c,Q.r,Q.c,j,ne);T(pe)},[te,e,c,T]),Je=u.useMemo(()=>(fe?.colors??[]).map(j=>j.token).filter(j=>!!j),[fe]),ve=u.useCallback(j=>{if(te.length===0||Je.length===0)return;let ne=e;for(const pe of te)ne=vo(ne,c,pe.r,pe.c,j,Je,k);T(ne)},[te,e,c,T,Je,k]),qe=u.useCallback(j=>Ne("backgroundColor",j),[Ne]),We=u.useCallback(()=>{if(te.length===0)return;let j=e;for(const ne of te)j=vo(j,c,ne.r,ne.c,void 0,Je,k),k==="light"&&(j=Rt(j,c,ne.r,ne.c,"backgroundColor",void 0));T(j)},[te,e,c,T,Je,k]),ce=u.useCallback(j=>Ne("padding",j),[Ne]),q=u.useMemo(()=>{if(k==="dark")return"";const j=O?.cell.props?.style;return typeof j?.backgroundColor=="string"?j.backgroundColor:""},[O,k]),se=u.useMemo(()=>typeof O?.cell.props?.className=="string"?O.cell.props.className:"",[O]),ae=u.useMemo(()=>{const j=O?.cell.props?.style;return typeof j?.padding=="string"?j.padding:""},[O]),be=u.useCallback(j=>{const ne=j.currentTarget,pe=ne.style.pointerEvents;ne.style.pointerEvents="none";const Q=document.elementFromPoint(j.clientX,j.clientY);ne.style.pointerEvents=pe;const de=Q?.closest?.("[data-dnd-handle-path]");de&&de.dispatchEvent(new PointerEvent("pointerdown",{bubbles:!0,cancelable:!0,clientX:j.clientX,clientY:j.clientY,pointerId:j.pointerId,pointerType:j.pointerType,button:j.button,isPrimary:!0}))},[]),Ce=u.useMemo(()=>{const j=new Map;for(const ne of p)j.has(ne.ref.gridCol)||j.set(ne.ref.gridCol,ne.box);return j},[p]),$e=u.useMemo(()=>{const j=new Map;for(const ne of p)j.has(ne.ref.gridRow)||j.set(ne.ref.gridRow,ne.box);return j},[p]);if(p.length===0)return null;const U=!!(O&&(O.colSpan>1||O.rowSpan>1)),ue=S!==null?S:y?.c??null,ge=$!==null?$:y?.r??null,ke=n.top-xl-kl-ry,he=n.left-wl-kl-ry;return i.jsxs("div",{className:"g7le-table-inplace","data-testid":"g7le-table-inplace",style:{position:"absolute",inset:0,pointerEvents:"none",zIndex:i2},children:[p.map(j=>{const ne=y&&y.r===j.ref.gridRow&&y.c===j.ref.gridCol,pe=X&&j.ref.gridRow>=X.r0&&j.ref.gridRow<=X.r1&&j.ref.gridCol>=X.c0&&j.ref.gridCol<=X.c1;return i.jsx("div",{"data-testid":`g7le-inplace-cell-${j.ref.gridRow}-${j.ref.gridCol}`,role:"button",tabIndex:-1,"aria-label":a("layout_editor.table_inplace.select_cell"),onMouseEnter:()=>{I(),C(j.ref.gridRow),D(j.ref.gridCol)},onMouseLeave:()=>{N()},onPointerDown:Q=>be(Q),onClick:Q=>{if(Q.stopPropagation(),Q.shiftKey&&(b||y))h({r:j.ref.gridRow,c:j.ref.gridCol});else{g({r:j.ref.gridRow,c:j.ref.gridCol}),h(null);const de=cl(e,j.ref,c).map(Ae=>`children.${Ae}`).join(".");d?.(de)}},style:{position:"absolute",top:j.box.top,left:j.box.left,width:j.box.width,height:j.box.height,minWidth:iy,minHeight:iy,boxSizing:"border-box",pointerEvents:"auto",cursor:"pointer",outline:ne?"2px solid #2563eb":"none",outlineOffset:-2,background:pe&&!ne?"rgba(37,99,235,0.14)":"transparent"}},`cell-${j.ref.gridRow}-${j.ref.gridCol}`)}),i.jsx("div",{"data-testid":"g7le-inplace-col-rail",onMouseEnter:I,onMouseLeave:N,style:{position:"absolute",top:ke,left:n.left,width:n.width,height:xl,pointerEvents:"none"},children:ue!==null&&Ce.has(ue)&&(()=>{const j=ue,ne=Ce.get(j),pe=Di(f,j,-1),Q=Di(f,j,1),de=ne.left-n.left;return i.jsxs("div",{"data-testid":`g7le-inplace-colgutter-${j}`,onMouseEnter:()=>{I(),D(j)},onMouseLeave:N,style:{position:"absolute",top:0,left:de,width:ne.width,minWidth:96,height:xl,display:"flex",gap:3,justifyContent:"center",alignItems:"center",pointerEvents:"auto"},children:[i.jsx("button",{type:"button",title:a("layout_editor.table_inplace.move_col_left"),disabled:!pe.ok,onClick:()=>L(j,-1),"data-testid":`g7le-inplace-col-left-${j}`,style:pe.ok?wn:Pi,children:"←"}),i.jsx("button",{type:"button",title:a("layout_editor.table_inplace.add_col"),onClick:()=>P(j),"data-testid":`g7le-inplace-col-add-${j}`,style:wn,children:"+"}),i.jsx("button",{type:"button",title:a("layout_editor.table_inplace.remove_col"),disabled:f.colCount<=1,onClick:()=>A(j),"data-testid":`g7le-inplace-col-remove-${j}`,style:ay,children:"✕"}),i.jsx("button",{type:"button",title:a("layout_editor.table_inplace.move_col_right"),disabled:!Q.ok,onClick:()=>L(j,1),"data-testid":`g7le-inplace-col-right-${j}`,style:Q.ok?wn:Pi,children:"→"})]},`colgut-${j}`)})()}),i.jsx("div",{"data-testid":"g7le-inplace-row-rail",style:{position:"absolute",top:n.top,left:he,width:wl,height:n.height,pointerEvents:"none"},children:ge!==null&&$e.has(ge)&&(()=>{const j=ge,ne=$e.get(j),pe=Ti(f,j,-1),Q=Ti(f,j,1),de=ne.top-n.top;return i.jsxs("div",{"data-testid":`g7le-inplace-rowgutter-${j}`,onMouseEnter:()=>{I(),C(j)},onMouseLeave:N,style:{position:"absolute",top:de,left:0,width:wl,height:ne.height,minHeight:48,display:"grid",gridTemplateColumns:"repeat(2, 22px)",gridAutoRows:"22px",gap:3,justifyContent:"center",alignContent:"center",pointerEvents:"auto"},children:[i.jsx("button",{type:"button",title:a("layout_editor.table_inplace.move_row_up"),disabled:!pe.ok,onClick:()=>H(j,-1),"data-testid":`g7le-inplace-row-up-${j}`,style:pe.ok?wn:Pi,children:"↑"}),i.jsx("button",{type:"button",title:a("layout_editor.table_inplace.add_row"),onClick:()=>B(j),"data-testid":`g7le-inplace-row-add-${j}`,style:wn,children:"+"}),i.jsx("button",{type:"button",title:a("layout_editor.table_inplace.remove_row"),disabled:f.rows.length<=1,onClick:()=>z(j),"data-testid":`g7le-inplace-row-remove-${j}`,style:ay,children:"✕"}),i.jsx("button",{type:"button",title:a("layout_editor.table_inplace.move_row_down"),disabled:!Q.ok,onClick:()=>H(j,1),"data-testid":`g7le-inplace-row-down-${j}`,style:Q.ok?wn:Pi,children:"↓"})]},`rowgut-${j}`)})()}),i.jsxs("div",{"data-testid":"g7le-inplace-corner-add",style:{position:"absolute",top:n.top+n.height+kl,left:n.left,display:"flex",flexDirection:"column",gap:6,pointerEvents:"auto",minWidth:200,maxWidth:Math.max(240,n.width),background:"#fff",border:"1px solid #e2e8f0",borderRadius:8,padding:8,boxShadow:"0 2px 8px rgba(0,0,0,0.12)"},children:[i.jsxs("div",{style:{display:"flex",gap:6,flexWrap:"wrap"},children:[i.jsxs("button",{type:"button","data-testid":"g7le-inplace-add-row-bottom",onClick:()=>B(f.rows.length-1),style:ly,children:["+ ",a("layout_editor.table_inplace.add_row")]}),i.jsxs("button",{type:"button","data-testid":"g7le-inplace-add-col-right",onClick:()=>P(f.colCount-1),style:ly,children:["+ ",a("layout_editor.table_inplace.add_col")]})]}),!y&&i.jsx("div",{"data-testid":"g7le-inplace-select-hint",style:{fontSize:11,color:"#94a3b8",borderTop:"1px solid #f1f5f9",paddingTop:6},children:a("layout_editor.table_inplace.select_cell_hint")}),y&&i.jsxs("div",{style:{display:"flex",flexDirection:"column",gap:6,borderTop:"1px solid #f1f5f9",paddingTop:6},children:[ie&&i.jsx("div",{"data-testid":"g7le-inplace-range-count",style:{fontSize:11,color:"#2563eb"},children:a("layout_editor.table_inplace.range_selected",{count:Z.length})}),i.jsxs("div",{style:{display:"flex",gap:6,flexWrap:"wrap",alignItems:"center"},children:[ie?i.jsx("button",{type:"button","data-testid":"g7le-inplace-merge-range",onClick:oe,style:ur,children:a("layout_editor.table_inplace.merge_range")}):i.jsxs(i.Fragment,{children:[i.jsx("button",{type:"button","data-testid":"g7le-inplace-merge-right",disabled:!Y,onClick:()=>F("right"),style:Y?ur:Sl,children:a("layout_editor.table_inplace.merge_right")}),i.jsx("button",{type:"button","data-testid":"g7le-inplace-merge-down",disabled:!V,onClick:()=>F("down"),style:V?ur:Sl,children:a("layout_editor.table_inplace.merge_down")})]}),i.jsx("button",{type:"button","data-testid":"g7le-inplace-unmerge",disabled:!U,onClick:G,style:U?ur:Sl,children:a("layout_editor.table_inplace.unmerge")})]}),i.jsxs("div",{"data-testid":"g7le-inplace-cell-color",style:{borderTop:"1px solid #f1f5f9",paddingTop:6,display:"flex",flexDirection:"column",gap:6},children:[i.jsxs("label",{style:{display:"flex",alignItems:"center",gap:6,fontSize:11,color:"#475569",cursor:"pointer"},children:[i.jsx("input",{type:"checkbox","data-testid":"g7le-inplace-border-all-toggle",checked:_,onChange:j=>w(j.target.checked)}),a("layout_editor.table_inplace.border_apply_all")]}),i.jsx(Kh,{colorScheme:k,onChange:x,t:a}),ee&&i.jsx("div",{"data-testid":"g7le-inplace-cell-border",children:i.jsx(Ph,{className:_?"":M,catalog:ee,t:a,onChange:ye,colorScheme:k,onPresetToken:ze,colorStyle:_?"":Xe,onColorStyle:Be})}),i.jsx("div",{"data-testid":"g7le-inplace-cell-fill",children:i.jsx(jh,{colorStyle:_?"":q,className:_?"":se,catalog:fe,t:a,colorScheme:k,onPresetToken:ve,onCustomColor:qe,onClear:We})})]}),i.jsx("div",{"data-testid":"g7le-inplace-cell-padding",style:{borderTop:"1px solid #f1f5f9",paddingTop:6},children:i.jsx(Hh,{paddingStyle:_?"":ae,catalog:K,t:a,onChange:ce})})]})]})]})}const wn={width:22,height:22,lineHeight:"20px",padding:0,fontSize:13,border:"1px solid #cbd5e1",borderRadius:5,background:"#fff",color:"#475569",cursor:"pointer",boxShadow:"0 1px 3px rgba(0,0,0,0.18)"},ay={...wn,border:"1px solid #fecaca",color:"#dc2626"},Pi={...wn,cursor:"not-allowed",opacity:.4,color:"#94a3b8",borderColor:"#e2e8f0"},ur={padding:"4px 10px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",color:"#475569",cursor:"pointer",boxShadow:"0 1px 2px rgba(0,0,0,0.12)"},Sl={...ur,cursor:"not-allowed",opacity:.4},ly={padding:"3px 8px",fontSize:11,border:"1px dashed #94a3b8",borderRadius:6,background:"#fff",color:"#475569",cursor:"pointer"};let dy=!1;function zM(){dy||(Bn("children",vh),Bn("table",SN),Bn("array",Rh),Bn("array-group",dM),Bn("array-cell-tree",bM),Ua("table",DM),dy=!0)}function NM(){if(typeof window>"u")return;const e=window.G7Core;if(!e)return;const t=e.layoutEditor,n=Array.isArray(t?.__queue)?t.__queue:[],o=Array.isArray(t?.__readyCallbacks)?t.__readyCallbacks:[];e.layoutEditor={registerWidget:Ge,registerNodeEditor:Bn,registerCanvasOverlay:Ua,onReady:r=>{typeof r=="function"&&r()}};for(const r of n){const[s,...a]=r;try{s==="widget"?Ge(a[0],a[1]):s==="nodeEditor"?Bn(a[0],a[1]):s==="canvasOverlay"&&Ua(a[0],a[1])}catch{}}for(const r of o)try{r()}catch{}}const MM=1280;jz(),zM(),NM();async function LM(e){if(typeof fetch!="function")return[];try{const t=await fetch(nn(`/api/admin/templates/${encodeURIComponent(e)}/editor/permission-candidates`,"json"),{credentials:"same-origin",headers:Me()});if(!t.ok)return[];const o=(await t.json().catch(()=>null))?.data?.permissions;if(!Array.isArray(o))return[];const r=[];for(const s of o)if(s&&typeof s=="object"){const a=s.key,l=s.name;typeof a=="string"&&a.length>0&&r.push({value:a,label:typeof l=="string"&&l?l:a})}return r}catch{return[]}}function PM({templateIdentifier:e}){c8({templateIdentifier:e}),u8(e);const{state:t,dispatch:n}=Qe(),{t:o}=et(),r=xo(),s=Wp(),a=m8(),l=t.selectedRoute?.layoutName??null,d=w8(e,l),c=t.editMode==="extension",p=u.useMemo(()=>c?_8(a):s,[c,a,s]),[f,b]=u.useState(null),[g,m]=u.useState(null),[h,v]=u.useState([]),[y,_]=u.useState("user"),[w,k]=u.useState(null),[x,$]=u.useState(null),[C,S]=u.useState([]);u.useEffect(()=>{let q=!1;return(async()=>{try{const se=await fetch(nn(`/api/templates/${encodeURIComponent(e)}/components`,"json"),{credentials:"same-origin",headers:{Accept:"application/json"}});if(!se.ok)return;const ae=await se.json().catch(()=>null);!q&&ae&&typeof ae=="object"&&b(ae)}catch{}})(),()=>{q=!0}},[e]),u.useEffect(()=>{let q=!1;return(async()=>{const{spec:se,sampleGlobalSources:ae}=await Eb({templateIdentifier:e,coreSeed:k8()});q||(m(se),v(ae),BM(e,se,ae))})(),()=>{q=!0}},[e]),u.useEffect(()=>{let q=!1;return(async()=>{if(typeof fetch=="function")try{const se=await fetch(`/api/admin/templates/${encodeURIComponent(e)}`,{credentials:"same-origin",headers:Me()});if(!se.ok)return;const be=(await se.json().catch(()=>null))?.data,Ce=be?.type;q||((Ce==="admin"||Ce==="user")&&_(Ce),typeof be?.name=="string"&&be.name&&k(be.name),typeof be?.version=="string"&&be.version&&$(be.version))}catch{}})(),()=>{q=!0}},[e]),u.useEffect(()=>{let q=!1;return(async()=>{if(typeof fetch=="function")try{const se=await fetch("/api/admin/templates/?per_page=100&page=1",{credentials:"same-origin",headers:Me()});if(!se.ok)return;const be=(await se.json().catch(()=>null))?.data?.data;if(!q&&Array.isArray(be)){const Ce=[];for(const $e of be){if(!$e||typeof $e!="object")continue;const U=$e.identifier;if(typeof U!="string"||!U||$e.is_pending===!0)continue;const ge=$e.type==="admin"?"admin":"user",ke=$e.name,he=$e.version;Ce.push({identifier:U,name:typeof ke=="string"?ke:U,version:typeof he=="string"?he:"",type:ge})}S(Ce)}}catch{}})(),()=>{q=!0}},[e]);const D=g?.nesting??null,E=t.selectedRoute?.path??null,I=t.editMode,N=t.selectedRoute?.layoutName??(c?a.document?.hostLayoutName??null:null),T=t.routeTree,O=u.useMemo(()=>{if(I!=="extension"||!N)return null;const q=$l(T,N);return q||`__base__/${N}`},[I,N,T]),M=c&&t.returnRoute===null&&!!N;u.useEffect(()=>{if(!M||!N)return;const q=$l(T,N);q&&n({type:"SET_RETURN_ROUTE",route:{path:q,layoutName:N}})},[M,N,T,n]),u.useEffect(()=>{const q=KD(I,E,O),se=VD(g?.states?.groups,q);n({type:"SET_AVAILABLE_STATES",states:se,activeStateId:UD(se)})},[g,I,E,O,n]);const[B,z]=u.useState([]);u.useEffect(()=>{let q=!1;return(async()=>{const se=await LM(e);q||z(se)})(),()=>{q=!0}},[e]),p8(),f8();const H=u.useCallback(q=>{const se=t.routeTree,ae=q.split("?")[0]?.split("#")[0]??q;return OM(se,ae)?"route_in_tree":"route_not_in_tree"},[t.routeTree]),P=u.useCallback(q=>{const se=q.split("?")[0]?.split("#")[0]??q,ae=Cl(t.routeTree,se);ae&&(p.isDirty&&!(!(typeof window<"u")||window.confirm(o("layout_editor.chrome.dirty_guard.message")))||n({type:"SELECT_ROUTE",route:{path:ae.path,layoutName:ae.layoutName}}))},[t.routeTree,p.isDirty,n]),A=u.useRef([]),L=u.useCallback(q=>{A.current=q},[]),[F,Y]=u.useState(null),V=u.useCallback(()=>Y(null),[]),X=u.useRef(null),Z=u.useRef(null);u.useEffect(()=>{const q=Z.current,se=X.current;if(!q||!se||typeof ResizeObserver>"u")return;const ae=()=>{se.style.setProperty("--g7le-header-h",`${Math.round(q.getBoundingClientRect().height)}px`)};ae();const be=new ResizeObserver(ae);return be.observe(q),()=>be.disconnect()},[]);const ie=u.useCallback(()=>{p.reload()},[p]),oe=u.useCallback(()=>{p.isDirty&&!(!(typeof window<"u")||window.confirm(o("layout_editor.chrome.dirty_guard.message")))||window.G7Core?.dispatch?.({handler:"openWindow",params:{path:`/admin/templates/${y}`,target:"_self"}})},[p.isDirty,o,y]),G=u.useCallback(q=>{if(!q||q===e||p.isDirty&&!(typeof window<"u"?window.confirm(o("layout_editor.chrome.dirty_guard.message")):!0))return;const se=sa(q);window.G7Core?.dispatch?.({handler:"openWindow",params:{path:se,target:"_self"}})},[e,p.isDirty,o]),ee=u.useCallback(()=>{const q=r.open({ariaLabel:o("layout_editor.custom_assets.title"),width:860,maxHeightRatio:.86,content:u.createElement(HE,{templateIdentifier:e,t:o,onClose:()=>r.close(q)})})},[r,e,o]),fe=typeof window<"u"&&(!!window.G7Config?.customAssetsDisabled||new URLSearchParams(window.location.search).get("custom")==="off"),K=u.useCallback(()=>{if(typeof window>"u"||p.isDirty&&!window.confirm(o("layout_editor.chrome.dirty_guard.message")))return;const q=new URL(window.location.href);if(fe){if(q.searchParams.delete("custom"),q.toString()===window.location.href){window.location.reload();return}}else q.searchParams.set("custom","off");window.G7Core?.dispatch?.({handler:"openWindow",params:{path:`${q.pathname}${q.search}${q.hash}`,target:"_self"}})},[p.isDirty,fe,o]),te=u.useCallback(()=>{const q=C7(e,t.selectedRoute?.path??null);window.G7Core?.dispatch?.({handler:"openWindow",params:{path:q}})},[e,t.selectedRoute]),ye=u.useCallback(()=>{const q=t.selectedRoute?.layoutName??"";if(!q)return;const se=r.open({ariaLabel:o("layout_editor.attachment_manager.title"),width:720,maxHeightRatio:.82,content:u.createElement(Kl,{templateIdentifier:e,layoutName:q,t:o,onClose:()=>r.close(se)})})},[r,e,t.selectedRoute,o]),Se=u.useCallback(()=>{const q=t.selectedRoute?.layoutName??"";if(!q)return;const se=gb(s.document?.raw??{}),ae=r.open({ariaLabel:o("layout_editor.translation_manager.title"),width:720,maxHeightRatio:.82,content:u.createElement(Pm,{templateIdentifier:e,layoutName:q,t:o,referencedKeys:se,onClose:()=>r.close(ae)})})},[r,e,t.selectedRoute,o,s.document]),De=u.useCallback((q,se)=>{const ae=window.G7Core;if(ae&&typeof ae.t=="function"){const be=ae.t(q,se);if(be&&be!==q&&!be.startsWith("$t:"))return be}return o(q,se)},[o]),Ie=u.useCallback(()=>{if(!t.selectedRoute||t.editMode==="extension"||t.editMode==="iteration_item")return;const q=r.open({ariaLabel:o("layout_editor.page_settings.title",{name:t.selectedRoute.path??""}),width:1080,maxHeightRatio:.88,closeOnEscape:!1,content:u.createElement(O$,{templateIdentifier:e,spec:g,t:o,resolveLabel:De,permissionCandidates:B,onClose:()=>r.close(q)})})},[r,t.selectedRoute,t.editMode,o,De,e,g,B]),Ve=u.useCallback(async()=>{const q=await d.createPreview(s.document?.raw??null);if(q.kind!=="success"){const se=q.kind==="network_error"?q.message:o("layout_editor.preview_action.no_document");typeof window<"u"&&window.alert(se)}},[d,s.document,o]),ze=u.useCallback(()=>{const q=t.selectedRoute?.layoutName??"",se=t.selectedRoute?.path??"",ae=t.editMode==="extension"&&se.startsWith("__extension__/")?se.slice(14):null,be=ae?{kind:"extension",extensionId:ae}:q?{kind:"layout",layoutName:q}:null;if(!be)return;const Ce=r.open({ariaLabel:o("layout_editor.version_history.title"),width:640,maxHeightRatio:.82,content:u.createElement(xE,{templateIdentifier:e,target:be,t:o,onRestored:async $e=>{typeof $e=="number"&&(be.kind==="extension"?n({type:"SET_EXTENSION_VERSION",extensionId:be.extensionId,version:$e}):n({type:"SET_LAYOUT_VERSION",layoutName:be.layoutName,version:$e})),await p.reload()},onClose:()=>r.close(Ce)})})},[r,e,t.selectedRoute,t.editMode,o,p,n]),Be=p.dirtyKeys.size>0||p.isDirty;u.useEffect(()=>{if(!Be)return;const q=se=>{se.preventDefault(),se.returnValue=""};return window.addEventListener("beforeunload",q),()=>window.removeEventListener("beforeunload",q)},[Be]);const Xe=u.useCallback(()=>{!p.isDirty||!(!(typeof window<"u")||window.confirm(o("layout_editor.chrome.reset_guard.message")))||p.reload()},[p,o]),Ne=u.useCallback(async()=>{const q=await p.save({sessionAddedPaths:A.current});Y(q),q.kind==="success"&&typeof q.newContentVersion=="number"&&(q.savedExtensionId?n({type:"SET_EXTENSION_VERSION",extensionId:q.savedExtensionId,version:q.newContentVersion}):q.savedLayoutName&&n({type:"SET_LAYOUT_VERSION",layoutName:q.savedLayoutName,version:q.newContentVersion}))},[p,n]),Je=u.useCallback(()=>{const q=r.open({ariaLabel:o("layout_editor.shortcuts.title"),width:560,maxHeightRatio:.85,content:u.createElement(JE,{t:o,onClose:()=>r.close(q)})})},[r,o]),ve=u.useMemo(()=>({save:()=>{Ne()},exit:oe,editCode:te,reset:Xe,translations:Se,addElement:()=>{t.selectedRoute&&n({type:"TOGGLE_PALETTE"})},help:Je,preview:()=>{t.selectedRoute?.layoutName&&Ve()}}),[Ne,oe,te,Xe,Se,t.selectedRoute,n,Je,Ve]),[qe,We]=u.useState(!1);u.useEffect(()=>{const q=window.setInterval(()=>{We(!!window.__g7LayoutEditorHasSelection)},200);return()=>window.clearInterval(q)},[]),hp({handlers:ve,hasSelection:qe});const ce=p;return t.routesError?i.jsx("div",{className:"g7le-chrome g7le-chrome--routes-error","data-testid":"g7le-chrome-routes-error",style:{position:"fixed",inset:0,display:"flex",background:"#f8fafc",color:"#0f172a",fontFamily:'-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Apple SD Gothic Neo", "Noto Sans KR", sans-serif',fontSize:13,lineHeight:1.5,zIndex:9e3},children:i.jsx(Za,{error:t.routesError})}):i.jsx("div",{className:"g7le-chrome","data-testid":"g7le-chrome",ref:X,style:{position:"static",minHeight:"100vh",minWidth:MM,background:"#f8fafc",color:"#0f172a",fontFamily:'-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Apple SD Gothic Neo", "Noto Sans KR", sans-serif',fontSize:13,lineHeight:1.5,zIndex:9e3},children:i.jsxs(Py,{value:ce,children:[c&&a.needsHostPicker&&a.document&&i.jsx(x8,{hostLayouts:a.document.hostLayouts,extensionLabel:null,onSelect:a.selectHost}),i.jsxs("div",{className:"g7le-chrome__header","data-testid":"g7le-chrome-header",ref:Z,style:{position:"sticky",top:0,zIndex:20,background:"#f8fafc"},children:[i.jsx(Vy,{onSave:()=>{Ne()},isDirty:p.isDirty,onExit:oe,onEditCode:te,onManageImages:ye,onManageTranslations:Se,onPageSettings:Ie,onReset:Xe,onShowShortcuts:Je,onPreview:Ve,onShowVersions:ze,templateName:w,templateVersion:x,templateList:C,onSwitchTemplate:G,onToggleCustomAssets:K,customAssetsDisabled:fe,onManageCustomAssets:ee}),i.jsx(p7,{result:F,onDismiss:V,onLoadLatest:ie}),i.jsxs("div",{className:"g7le-chrome__device-toolbar","data-testid":"g7le-chrome-device-toolbar",style:{display:"flex",justifyContent:"center",padding:"6px 12px",background:"#f1f5f9",borderBottom:"1px solid #e2e8f0"},children:[i.jsx(HD,{}),i.jsx(FD,{})]})]}),i.jsxs("div",{className:"g7le-chrome__body",style:{display:"grid",gridTemplateColumns:"auto 1fr",alignItems:"stretch"},children:[i.jsx(z7,{}),i.jsx(PD,{manifest:f,nesting:D,componentPalette:g?.componentPalette??null,spec:g,permissionCandidates:B,sampleGlobalSources:h,resolveRouteMatch:H,onNavigateToDestination:P,onSessionAddedPathsChange:L})]}),i.jsx(Wy,{})]})})}function Cl(e,t){for(const n of e){if(n.kind==="route"&&n.path===t)return n;if(n.children&&n.children.length>0){const o=Cl(n.children,t);if(o)return o}}return null}function OM(e,t){return Cl(e,t)!==null}function $l(e,t){for(const n of e){if(n.kind==="route"&&n.layoutName===t&&typeof n.path=="string")return n.path;if(n.children&&n.children.length>0){const o=$l(n.children,t);if(o)return o}}return null}function BM(e,t,n){const o=[];Ra({sources:n,isGuestOnly:!1,warn:s=>{const a=/extension '([^']+)' override of core key '([^']+)'/.exec(s);a&&o.push(`${a[1]}:${a[2]}`)}});const r=t?.sampleData?.byDataSourceId?Object.keys(t.sampleData.byDataSourceId).length:0;Cy({templateIdentifier:e,mergedSources:[...n.map(s=>({kind:s.kind,id:s.id}))],blockCounts:{controls:t?.controls?Object.keys(t.controls).length:0,componentCapabilities:t?.componentCapabilities?Object.keys(t.componentCapabilities).length:0,actionRecipes:t?.actionRecipes?Object.keys(t.actionRecipes).length:0,conditionRecipes:t?.conditionRecipes?Object.keys(t.conditionRecipes).length:0,paletteGroups:t?.componentPalette?.groups?.length??0,stateGroups:t?.states?.groups?.length??0,sampleDataIds:r},sampleGlobalSourceCount:n.length,sampleGlobalConflicts:o,timestamp:Date.now()})}function cy({templateIdentifier:e,initialLocale:t}){return u.useEffect(()=>()=>{by(),vy(),xy(),wy(),ky(),Sy(),$y(),Ey(),Iy()},[]),i.jsx(Ly,{templateIdentifier:e,initialLocale:t,children:i.jsx(By,{children:i.jsx(PM,{templateIdentifier:e})})})}if(typeof window<"u"){const e=window.G7Core||(window.G7Core={});if(e.__LayoutEditorChrome=cy,typeof e.__onChromeReady=="function")try{e.__onChromeReady()}catch{}}un.LayoutEditorChrome=cy,Object.defineProperty(un,Symbol.toStringTag,{value:"Module"})})(this.G7LayoutEditor=this.G7LayoutEditor||{},ReactJSXRuntime,React,ReactDOM); diff --git a/public/build/core/template-engine.min.js b/public/build/core/template-engine.min.js index a386983b..62d342e4 100644 --- a/public/build/core/template-engine.min.js +++ b/public/build/core/template-engine.min.js @@ -1,23 +1,63 @@ -var CR=Object.defineProperty;var ER=(tn,na,ra)=>na in tn?CR(tn,na,{enumerable:!0,configurable:!0,writable:!0,value:ra}):tn[na]=ra;var M=(tn,na,ra)=>ER(tn,typeof na!="symbol"?na+"":na,ra);(function(tn){"use strict";function na(s,e){for(var n=0;nr[i]})}}}return Object.freeze(Object.defineProperty(s,Symbol.toStringTag,{value:"Module"}))}function ra(s){return s&&s.__esModule&&Object.prototype.hasOwnProperty.call(s,"default")?s.default:s}var Ad={exports:{}},Ye={};var Mg;function a0(){if(Mg)return Ye;Mg=1;var s=Symbol.for("react.transitional.element"),e=Symbol.for("react.portal"),n=Symbol.for("react.fragment"),r=Symbol.for("react.strict_mode"),i=Symbol.for("react.profiler"),l=Symbol.for("react.consumer"),c=Symbol.for("react.context"),d=Symbol.for("react.forward_ref"),f=Symbol.for("react.suspense"),h=Symbol.for("react.memo"),m=Symbol.for("react.lazy"),y=Symbol.for("react.activity"),v=Symbol.iterator;function S(H){return H===null||typeof H!="object"?null:(H=v&&H[v]||H["@@iterator"],typeof H=="function"?H:null)}var _={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},x=Object.assign,A={};function D(H,T,fe){this.props=H,this.context=T,this.refs=A,this.updater=fe||_}D.prototype.isReactComponent={},D.prototype.setState=function(H,T){if(typeof H!="object"&&typeof H!="function"&&H!=null)throw Error("takes an object of state variables to update or a function which returns an object of state variables.");this.updater.enqueueSetState(this,H,T,"setState")},D.prototype.forceUpdate=function(H){this.updater.enqueueForceUpdate(this,H,"forceUpdate")};function O(){}O.prototype=D.prototype;function k(H,T,fe){this.props=H,this.context=T,this.refs=A,this.updater=fe||_}var L=k.prototype=new O;L.constructor=k,x(L,D.prototype),L.isPureReactComponent=!0;var B=Array.isArray;function q(){}var z={H:null,A:null,T:null,S:null},W=Object.prototype.hasOwnProperty;function de(H,T,fe){var he=fe.ref;return{$$typeof:s,type:H,key:T,ref:he!==void 0?he:null,props:fe}}function _e(H,T){return de(H.type,T,H.props)}function Se(H){return typeof H=="object"&&H!==null&&H.$$typeof===s}function Ie(H){var T={"=":"=0",":":"=2"};return"$"+H.replace(/[=:]/g,function(fe){return T[fe]})}var Fe=/\/+/g;function ze(H,T){return typeof H=="object"&&H!==null&&H.key!=null?Ie(""+H.key):T.toString(36)}function Et(H){switch(H.status){case"fulfilled":return H.value;case"rejected":throw H.reason;default:switch(typeof H.status=="string"?H.then(q,q):(H.status="pending",H.then(function(T){H.status==="pending"&&(H.status="fulfilled",H.value=T)},function(T){H.status==="pending"&&(H.status="rejected",H.reason=T)})),H.status){case"fulfilled":return H.value;case"rejected":throw H.reason}}throw H}function Q(H,T,fe,he,Ae){var se=typeof H;(se==="undefined"||se==="boolean")&&(H=null);var ge=!1;if(H===null)ge=!0;else switch(se){case"bigint":case"string":case"number":ge=!0;break;case"object":switch(H.$$typeof){case s:case e:ge=!0;break;case m:return ge=H._init,Q(ge(H._payload),T,fe,he,Ae)}}if(ge)return Ae=Ae(H),ge=he===""?"."+ze(H,0):he,B(Ae)?(fe="",ge!=null&&(fe=ge.replace(Fe,"$&/")+"/"),Q(Ae,T,fe,"",function(je){return je})):Ae!=null&&(Se(Ae)&&(Ae=_e(Ae,fe+(Ae.key==null||H&&H.key===Ae.key?"":(""+Ae.key).replace(Fe,"$&/")+"/")+ge)),T.push(Ae)),1;ge=0;var $e=he===""?".":he+":";if(B(H))for(var Te=0;Te>>1,me=Q[ee];if(0>>1;eei(fe,De))hei(Ae,fe)?(Q[ee]=Ae,Q[he]=De,ee=he):(Q[ee]=fe,Q[T]=De,ee=T);else if(hei(Ae,De))Q[ee]=Ae,Q[he]=De,ee=he;else break e}}return ue}function i(Q,ue){var De=Q.sortIndex-ue.sortIndex;return De!==0?De:Q.id-ue.id}if(s.unstable_now=void 0,typeof performance=="object"&&typeof performance.now=="function"){var l=performance;s.unstable_now=function(){return l.now()}}else{var c=Date,d=c.now();s.unstable_now=function(){return c.now()-d}}var f=[],h=[],m=1,y=null,v=3,S=!1,_=!1,x=!1,A=!1,D=typeof setTimeout=="function"?setTimeout:null,O=typeof clearTimeout=="function"?clearTimeout:null,k=typeof setImmediate<"u"?setImmediate:null;function L(Q){for(var ue=n(h);ue!==null;){if(ue.callback===null)r(h);else if(ue.startTime<=Q)r(h),ue.sortIndex=ue.expirationTime,e(f,ue);else break;ue=n(h)}}function B(Q){if(x=!1,L(Q),!_)if(n(f)!==null)_=!0,q||(q=!0,Ie());else{var ue=n(h);ue!==null&&Et(B,ue.startTime-Q)}}var q=!1,z=-1,W=5,de=-1;function _e(){return A?!0:!(s.unstable_now()-deQ&&_e());){var ee=y.callback;if(typeof ee=="function"){y.callback=null,v=y.priorityLevel;var me=ee(y.expirationTime<=Q);if(Q=s.unstable_now(),typeof me=="function"){y.callback=me,L(Q),ue=!0;break t}y===n(f)&&r(f),L(Q)}else r(f);y=n(f)}if(y!==null)ue=!0;else{var H=n(h);H!==null&&Et(B,H.startTime-Q),ue=!1}}break e}finally{y=null,v=De,S=!1}ue=void 0}}finally{ue?Ie():q=!1}}}var Ie;if(typeof k=="function")Ie=function(){k(Se)};else if(typeof MessageChannel<"u"){var Fe=new MessageChannel,ze=Fe.port2;Fe.port1.onmessage=Se,Ie=function(){ze.postMessage(null)}}else Ie=function(){D(Se,0)};function Et(Q,ue){z=D(function(){Q(s.unstable_now())},ue)}s.unstable_IdlePriority=5,s.unstable_ImmediatePriority=1,s.unstable_LowPriority=4,s.unstable_NormalPriority=3,s.unstable_Profiling=null,s.unstable_UserBlockingPriority=2,s.unstable_cancelCallback=function(Q){Q.callback=null},s.unstable_forceFrameRate=function(Q){0>Q||125ee?(Q.sortIndex=De,e(h,Q),n(f)===null&&Q===n(h)&&(x?(O(z),z=-1):x=!0,Et(B,De-ee))):(Q.sortIndex=me,e(f,Q),_||S||(_=!0,q||(q=!0,Ie()))),Q},s.unstable_shouldYield=_e,s.unstable_wrapCallback=function(Q){var ue=v;return function(){var De=v;v=ue;try{return Q.apply(this,arguments)}finally{v=De}}}})(Rd)),Rd}var jg;function s0(){return jg||(jg=1,kd.exports=i0()),kd.exports}var Dd={exports:{}},An={};var Hg;function o0(){if(Hg)return An;Hg=1;var s=xd();function e(f){var h="https://react.dev/errors/"+f;if(1"u"||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!="function"))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(s)}catch(e){console.error(e)}}return s(),Dd.exports=o0(),Dd.exports}var Ug;function l0(){if(Ug)return fo;Ug=1;var s=s0(),e=xd(),n=Pg();function r(t){var a="https://react.dev/errors/"+t;if(1me||(t.current=ee[me],ee[me]=null,me--)}function fe(t,a){me++,ee[me]=t.current,t.current=a}var he=H(null),Ae=H(null),se=H(null),ge=H(null);function $e(t,a){switch(fe(se,a),fe(Ae,t),fe(he,null),a.nodeType){case 9:case 11:t=(t=a.documentElement)&&(t=t.namespaceURI)?Aw(t):0;break;default:if(t=a.tagName,a=a.namespaceURI)a=Aw(a),t=xw(a,t);else switch(t){case"svg":t=1;break;case"math":t=2;break;default:t=0}}T(he),fe(he,t)}function Te(){T(he),T(Ae),T(se)}function je(t){t.memoizedState!==null&&fe(ge,t);var a=he.current,o=xw(a,t.type);a!==o&&(fe(Ae,t),fe(he,o))}function xt(t){Ae.current===t&&(T(he),T(Ae)),ge.current===t&&(T(ge),Xl._currentValue=De)}var rt,gt;function at(t){if(rt===void 0)try{throw Error()}catch(o){var a=o.stack.trim().match(/\n( *(at )?)/);rt=a&&a[1]||"",gt=-1na in tn?wR(tn,na,{enumerable:!0,configurable:!0,writable:!0,value:ra}):tn[na]=ra;var M=(tn,na,ra)=>CR(tn,typeof na!="symbol"?na+"":na,ra);(function(tn){"use strict";function na(s,e){for(var n=0;nr[i]})}}}return Object.freeze(Object.defineProperty(s,Symbol.toStringTag,{value:"Module"}))}function ra(s){return s&&s.__esModule&&Object.prototype.hasOwnProperty.call(s,"default")?s.default:s}var Ed={exports:{}},Ye={};/** + * @license React + * react.production.js + * + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */var $g;function i0(){if($g)return Ye;$g=1;var s=Symbol.for("react.transitional.element"),e=Symbol.for("react.portal"),n=Symbol.for("react.fragment"),r=Symbol.for("react.strict_mode"),i=Symbol.for("react.profiler"),l=Symbol.for("react.consumer"),c=Symbol.for("react.context"),d=Symbol.for("react.forward_ref"),f=Symbol.for("react.suspense"),h=Symbol.for("react.memo"),m=Symbol.for("react.lazy"),y=Symbol.for("react.activity"),v=Symbol.iterator;function S(H){return H===null||typeof H!="object"?null:(H=v&&H[v]||H["@@iterator"],typeof H=="function"?H:null)}var _={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},x=Object.assign,A={};function D(H,T,fe){this.props=H,this.context=T,this.refs=A,this.updater=fe||_}D.prototype.isReactComponent={},D.prototype.setState=function(H,T){if(typeof H!="object"&&typeof H!="function"&&H!=null)throw Error("takes an object of state variables to update or a function which returns an object of state variables.");this.updater.enqueueSetState(this,H,T,"setState")},D.prototype.forceUpdate=function(H){this.updater.enqueueForceUpdate(this,H,"forceUpdate")};function O(){}O.prototype=D.prototype;function k(H,T,fe){this.props=H,this.context=T,this.refs=A,this.updater=fe||_}var L=k.prototype=new O;L.constructor=k,x(L,D.prototype),L.isPureReactComponent=!0;var B=Array.isArray;function q(){}var z={H:null,A:null,T:null,S:null},W=Object.prototype.hasOwnProperty;function de(H,T,fe){var he=fe.ref;return{$$typeof:s,type:H,key:T,ref:he!==void 0?he:null,props:fe}}function _e(H,T){return de(H.type,T,H.props)}function Se(H){return typeof H=="object"&&H!==null&&H.$$typeof===s}function je(H){var T={"=":"=0",":":"=2"};return"$"+H.replace(/[=:]/g,function(fe){return T[fe]})}var Fe=/\/+/g;function ze(H,T){return typeof H=="object"&&H!==null&&H.key!=null?je(""+H.key):T.toString(36)}function Et(H){switch(H.status){case"fulfilled":return H.value;case"rejected":throw H.reason;default:switch(typeof H.status=="string"?H.then(q,q):(H.status="pending",H.then(function(T){H.status==="pending"&&(H.status="fulfilled",H.value=T)},function(T){H.status==="pending"&&(H.status="rejected",H.reason=T)})),H.status){case"fulfilled":return H.value;case"rejected":throw H.reason}}throw H}function Q(H,T,fe,he,Ae){var se=typeof H;(se==="undefined"||se==="boolean")&&(H=null);var ge=!1;if(H===null)ge=!0;else switch(se){case"bigint":case"string":case"number":ge=!0;break;case"object":switch(H.$$typeof){case s:case e:ge=!0;break;case m:return ge=H._init,Q(ge(H._payload),T,fe,he,Ae)}}if(ge)return Ae=Ae(H),ge=he===""?"."+ze(H,0):he,B(Ae)?(fe="",ge!=null&&(fe=ge.replace(Fe,"$&/")+"/"),Q(Ae,T,fe,"",function(Ie){return Ie})):Ae!=null&&(Se(Ae)&&(Ae=_e(Ae,fe+(Ae.key==null||H&&H.key===Ae.key?"":(""+Ae.key).replace(Fe,"$&/")+"/")+ge)),T.push(Ae)),1;ge=0;var $e=he===""?".":he+":";if(B(H))for(var Te=0;Te>>1,me=Q[ee];if(0>>1;eei(fe,De))hei(Ae,fe)?(Q[ee]=Ae,Q[he]=De,ee=he):(Q[ee]=fe,Q[T]=De,ee=T);else if(hei(Ae,De))Q[ee]=Ae,Q[he]=De,ee=he;else break e}}return ue}function i(Q,ue){var De=Q.sortIndex-ue.sortIndex;return De!==0?De:Q.id-ue.id}if(s.unstable_now=void 0,typeof performance=="object"&&typeof performance.now=="function"){var l=performance;s.unstable_now=function(){return l.now()}}else{var c=Date,d=c.now();s.unstable_now=function(){return c.now()-d}}var f=[],h=[],m=1,y=null,v=3,S=!1,_=!1,x=!1,A=!1,D=typeof setTimeout=="function"?setTimeout:null,O=typeof clearTimeout=="function"?clearTimeout:null,k=typeof setImmediate<"u"?setImmediate:null;function L(Q){for(var ue=n(h);ue!==null;){if(ue.callback===null)r(h);else if(ue.startTime<=Q)r(h),ue.sortIndex=ue.expirationTime,e(f,ue);else break;ue=n(h)}}function B(Q){if(x=!1,L(Q),!_)if(n(f)!==null)_=!0,q||(q=!0,je());else{var ue=n(h);ue!==null&&Et(B,ue.startTime-Q)}}var q=!1,z=-1,W=5,de=-1;function _e(){return A?!0:!(s.unstable_now()-deQ&&_e());){var ee=y.callback;if(typeof ee=="function"){y.callback=null,v=y.priorityLevel;var me=ee(y.expirationTime<=Q);if(Q=s.unstable_now(),typeof me=="function"){y.callback=me,L(Q),ue=!0;break t}y===n(f)&&r(f),L(Q)}else r(f);y=n(f)}if(y!==null)ue=!0;else{var H=n(h);H!==null&&Et(B,H.startTime-Q),ue=!1}}break e}finally{y=null,v=De,S=!1}ue=void 0}}finally{ue?je():q=!1}}}var je;if(typeof k=="function")je=function(){k(Se)};else if(typeof MessageChannel<"u"){var Fe=new MessageChannel,ze=Fe.port2;Fe.port1.onmessage=Se,je=function(){ze.postMessage(null)}}else je=function(){D(Se,0)};function Et(Q,ue){z=D(function(){Q(s.unstable_now())},ue)}s.unstable_IdlePriority=5,s.unstable_ImmediatePriority=1,s.unstable_LowPriority=4,s.unstable_NormalPriority=3,s.unstable_Profiling=null,s.unstable_UserBlockingPriority=2,s.unstable_cancelCallback=function(Q){Q.callback=null},s.unstable_forceFrameRate=function(Q){0>Q||125ee?(Q.sortIndex=De,e(h,Q),n(f)===null&&Q===n(h)&&(x?(O(z),z=-1):x=!0,Et(B,De-ee))):(Q.sortIndex=me,e(f,Q),_||S||(_=!0,q||(q=!0,je()))),Q},s.unstable_shouldYield=_e,s.unstable_wrapCallback=function(Q){var ue=v;return function(){var De=v;v=ue;try{return Q.apply(this,arguments)}finally{v=De}}}})(Td)),Td}var Ig;function o0(){return Ig||(Ig=1,xd.exports=s0()),xd.exports}var kd={exports:{}},An={};/** + * @license React + * react-dom.production.js + * + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */var jg;function l0(){if(jg)return An;jg=1;var s=_d();function e(f){var h="https://react.dev/errors/"+f;if(1"u"||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!="function"))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(s)}catch(e){console.error(e)}}return s(),kd.exports=l0(),kd.exports}/** + * @license React + * react-dom-client.production.js + * + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */var Pg;function c0(){if(Pg)return fo;Pg=1;var s=o0(),e=_d(),n=zg();function r(t){var a="https://react.dev/errors/"+t;if(1me||(t.current=ee[me],ee[me]=null,me--)}function fe(t,a){me++,ee[me]=t.current,t.current=a}var he=H(null),Ae=H(null),se=H(null),ge=H(null);function $e(t,a){switch(fe(se,a),fe(Ae,t),fe(he,null),a.nodeType){case 9:case 11:t=(t=a.documentElement)&&(t=t.namespaceURI)?Aw(t):0;break;default:if(t=a.tagName,a=a.namespaceURI)a=Aw(a),t=xw(a,t);else switch(t){case"svg":t=1;break;case"math":t=2;break;default:t=0}}T(he),fe(he,t)}function Te(){T(he),T(Ae),T(se)}function Ie(t){t.memoizedState!==null&&fe(ge,t);var a=he.current,o=xw(a,t.type);a!==o&&(fe(Ae,t),fe(he,o))}function xt(t){Ae.current===t&&(T(he),T(Ae)),ge.current===t&&(T(ge),Yl._currentValue=De)}var rt,gt;function at(t){if(rt===void 0)try{throw Error()}catch(o){var a=o.stack.trim().match(/\n( *(at )?)/);rt=a&&a[1]||"",gt=-1)":-1p||P[u]!==X[p]){var re=` `+P[u].replace(" at new "," at ");return t.displayName&&re.includes("")&&(re=re.replace("",t.displayName)),re}while(1<=u&&0<=p);break}}}finally{Yt=!1,Error.prepareStackTrace=o}return(o=t?t.displayName||t.name:"")?at(o):""}function bt(t,a){switch(t.tag){case 26:case 27:case 5:return at(t.type);case 16:return at("Lazy");case 13:return t.child!==a&&a!==null?at("Suspense Fallback"):at("Suspense");case 19:return at("SuspenseList");case 0:case 15:return Ht(t.type,!1);case 11:return Ht(t.type.render,!1);case 1:return Ht(t.type,!0);case 31:return at("Activity");default:return""}}function qe(t){try{var a="",o=null;do a+=bt(t,o),o=t,t=t.return;while(t);return a}catch(u){return` Error generating stack: `+u.message+` -`+u.stack}}var mt=Object.prototype.hasOwnProperty,Dt=s.unstable_scheduleCallback,zt=s.unstable_cancelCallback,kn=s.unstable_shouldYield,dr=s.unstable_requestPaint,Ot=s.unstable_now,$i=s.unstable_getCurrentPriorityLevel,ua=s.unstable_ImmediatePriority,bn=s.unstable_UserBlockingPriority,Mn=s.unstable_NormalPriority,it=s.unstable_LowPriority,Pn=s.unstable_IdlePriority,vn=s.log,da=s.unstable_setDisableYieldValue,fr=null,un=null;function Rn(t){if(typeof vn=="function"&&da(t),un&&typeof un.setStrictMode=="function")try{un.setStrictMode(fr,t)}catch{}}var dn=Math.clz32?Math.clz32:ce,V=Math.log,ie=Math.LN2;function ce(t){return t>>>=0,t===0?32:31-(V(t)/ie|0)|0}var Z=256,ve=262144,pe=4194304;function Ee(t){var a=t&42;if(a!==0)return a;switch(t&-t){case 1:return 1;case 2:return 2;case 4:return 4;case 8:return 8;case 16:return 16;case 32:return 32;case 64:return 64;case 128:return 128;case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:return t&261888;case 262144:case 524288:case 1048576:case 2097152:return t&3932160;case 4194304:case 8388608:case 16777216:case 33554432:return t&62914560;case 67108864:return 67108864;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 0;default:return t}}function xe(t,a,o){var u=t.pendingLanes;if(u===0)return 0;var p=0,g=t.suspendedLanes,C=t.pingedLanes;t=t.warmLanes;var R=u&134217727;return R!==0?(u=R&~g,u!==0?p=Ee(u):(C&=R,C!==0?p=Ee(C):o||(o=R&~t,o!==0&&(p=Ee(o))))):(R=u&~g,R!==0?p=Ee(R):C!==0?p=Ee(C):o||(o=u&~t,o!==0&&(p=Ee(o)))),p===0?0:a!==0&&a!==p&&(a&g)===0&&(g=p&-p,o=a&-a,g>=o||g===32&&(o&4194048)!==0)?a:p}function Ke(t,a){return(t.pendingLanes&~(t.suspendedLanes&~t.pingedLanes)&a)===0}function ke(t,a){switch(t){case 1:case 2:case 4:case 8:case 64:return a+250;case 16:case 32:case 128:case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return a+5e3;case 4194304:case 8388608:case 16777216:case 33554432:return-1;case 67108864:case 134217728:case 268435456:case 536870912:case 1073741824:return-1;default:return-1}}function Me(){var t=pe;return pe<<=1,(pe&62914560)===0&&(pe=4194304),t}function Re(t){for(var a=[],o=0;31>o;o++)a.push(t);return a}function tt(t,a){t.pendingLanes|=a,a!==268435456&&(t.suspendedLanes=0,t.pingedLanes=0,t.warmLanes=0)}function Pe(t,a,o,u,p,g){var C=t.pendingLanes;t.pendingLanes=o,t.suspendedLanes=0,t.pingedLanes=0,t.warmLanes=0,t.expiredLanes&=o,t.entangledLanes&=o,t.errorRecoveryDisabledLanes&=o,t.shellSuspendCounter=0;var R=t.entanglements,P=t.expirationTimes,X=t.hiddenUpdates;for(o=C&~o;0"u")return null;try{return t.activeElement||t.body}catch{return t.body}}var Sh=/[\n"\\]/g;function Bn(t){return t.replace(Sh,function(a){return"\\"+a.charCodeAt(0).toString(16)+" "})}function Os(t,a,o,u,p,g,C,R){t.name="",C!=null&&typeof C!="function"&&typeof C!="symbol"&&typeof C!="boolean"?t.type=C:t.removeAttribute("type"),a!=null?C==="number"?(a===0&&t.value===""||t.value!=a)&&(t.value=""+On(a)):t.value!==""+On(a)&&(t.value=""+On(a)):C!=="submit"&&C!=="reset"||t.removeAttribute("value"),a!=null?ha(t,C,On(a)):o!=null?ha(t,C,On(o)):u!=null&&t.removeAttribute("value"),p==null&&g!=null&&(t.defaultChecked=!!g),p!=null&&(t.checked=p&&typeof p!="function"&&typeof p!="symbol"),R!=null&&typeof R!="function"&&typeof R!="symbol"&&typeof R!="boolean"?t.name=""+On(R):t.removeAttribute("name")}function Ii(t,a,o,u,p,g,C,R){if(g!=null&&typeof g!="function"&&typeof g!="symbol"&&typeof g!="boolean"&&(t.type=g),a!=null||o!=null){if(!(g!=="submit"&&g!=="reset"||a!=null)){il(t);return}o=o!=null?""+On(o):"",a=a!=null?""+On(a):o,R||a===t.value||(t.value=a),t.defaultValue=a}u=u??p,u=typeof u!="function"&&typeof u!="symbol"&&!!u,t.checked=R?t.checked:!!u,t.defaultChecked=!!u,C!=null&&typeof C!="function"&&typeof C!="symbol"&&typeof C!="boolean"&&(t.name=C),il(t)}function ha(t,a,o){a==="number"&&Va(t.ownerDocument)===t||t.defaultValue===""+o||(t.defaultValue=""+o)}function Ka(t,a,o,u){if(t=t.options,a){a={};for(var p=0;p"u"||typeof window.document>"u"||typeof window.document.createElement>"u"),ul=!1;if(Nr)try{var Hi={};Object.defineProperty(Hi,"passive",{get:function(){ul=!0}}),window.addEventListener("test",Hi,Hi),window.removeEventListener("test",Hi,Hi)}catch{ul=!1}var Jr=null,dl=null,$s=null;function du(){if($s)return $s;var t,a=dl,o=a.length,u,p="value"in Jr?Jr.value:Jr.textContent,g=p.length;for(t=0;t=ne),We=" ",ft=!1;function vt(t,a){switch(t){case"keyup":return $.indexOf(a.keyCode)!==-1;case"keydown":return a.keyCode!==229;case"keypress":case"mousedown":case"focusout":return!0;default:return!1}}function Vt(t){return t=t.detail,typeof t=="object"&&"data"in t?t.data:null}var jr=!1;function Hh(t,a){switch(t){case"compositionend":return Vt(a);case"keypress":return a.which!==32?null:(ft=!0,We);case"textInput":return t=a.data,t===We&&ft?null:t;default:return null}}function bu(t,a){if(jr)return t==="compositionend"||!N&&vt(t,a)?(t=du(),$s=dl=Jr=null,jr=!1,t):null;switch(t){case"paste":return null;case"keypress":if(!(a.ctrlKey||a.altKey||a.metaKey)||a.ctrlKey&&a.altKey){if(a.char&&1=a)return{node:o,offset:a-t};t=u}e:{for(;o;){if(o.nextSibling){o=o.nextSibling;break e}o=o.parentNode}o=void 0}o=qb(o)}}function Fb(t,a){return t&&a?t===a?!0:t&&t.nodeType===3?!1:a&&a.nodeType===3?Fb(t,a.parentNode):"contains"in t?t.contains(a):t.compareDocumentPosition?!!(t.compareDocumentPosition(a)&16):!1:!1}function Vb(t){t=t!=null&&t.ownerDocument!=null&&t.ownerDocument.defaultView!=null?t.ownerDocument.defaultView:window;for(var a=Va(t.document);a instanceof t.HTMLIFrameElement;){try{var o=typeof a.contentWindow.location.href=="string"}catch{o=!1}if(o)t=a.contentWindow;else break;a=Va(t.document)}return a}function qh(t){var a=t&&t.nodeName&&t.nodeName.toLowerCase();return a&&(a==="input"&&(t.type==="text"||t.type==="search"||t.type==="tel"||t.type==="url"||t.type==="password")||a==="textarea"||t.contentEditable==="true")}var tk=Nr&&"documentMode"in document&&11>=document.documentMode,js=null,Gh=null,wl=null,Fh=!1;function Kb(t,a,o){var u=o.window===o?o.document:o.nodeType===9?o:o.ownerDocument;Fh||js==null||js!==Va(u)||(u=js,"selectionStart"in u&&qh(u)?u={start:u.selectionStart,end:u.selectionEnd}:(u=(u.ownerDocument&&u.ownerDocument.defaultView||window).getSelection(),u={anchorNode:u.anchorNode,anchorOffset:u.anchorOffset,focusNode:u.focusNode,focusOffset:u.focusOffset}),wl&&Sl(wl,u)||(wl=u,u=ud(Gh,"onSelect"),0>=C,p-=C,Qr=1<<32-dn(a)+p|o<Ze?(ut=Ne,Ne=null):ut=Ne.sibling;var wt=J(K,Ne,Y[Ze],oe);if(wt===null){Ne===null&&(Ne=ut);break}t&&Ne&&wt.alternate===null&&a(K,Ne),F=g(wt,F,Ze),St===null?Be=wt:St.sibling=wt,St=wt,Ne=ut}if(Ze===Y.length)return o(K,Ne),ht&&ya(K,Ze),Be;if(Ne===null){for(;ZeZe?(ut=Ne,Ne=null):ut=Ne.sibling;var vi=J(K,Ne,wt.value,oe);if(vi===null){Ne===null&&(Ne=ut);break}t&&Ne&&vi.alternate===null&&a(K,Ne),F=g(vi,F,Ze),St===null?Be=vi:St.sibling=vi,St=vi,Ne=ut}if(wt.done)return o(K,Ne),ht&&ya(K,Ze),Be;if(Ne===null){for(;!wt.done;Ze++,wt=Y.next())wt=le(K,wt.value,oe),wt!==null&&(F=g(wt,F,Ze),St===null?Be=wt:St.sibling=wt,St=wt);return ht&&ya(K,Ze),Be}for(Ne=u(Ne);!wt.done;Ze++,wt=Y.next())wt=te(Ne,K,Ze,wt.value,oe),wt!==null&&(t&&wt.alternate!==null&&Ne.delete(wt.key===null?Ze:wt.key),F=g(wt,F,Ze),St===null?Be=wt:St.sibling=wt,St=wt);return t&&Ne.forEach(function(wR){return a(K,wR)}),ht&&ya(K,Ze),Be}function Nt(K,F,Y,oe){if(typeof Y=="object"&&Y!==null&&Y.type===x&&Y.key===null&&(Y=Y.props.children),typeof Y=="object"&&Y!==null){switch(Y.$$typeof){case S:e:{for(var Be=Y.key;F!==null;){if(F.key===Be){if(Be=Y.type,Be===x){if(F.tag===7){o(K,F.sibling),oe=p(F,Y.props.children),oe.return=K,K=oe;break e}}else if(F.elementType===Be||typeof Be=="object"&&Be!==null&&Be.$$typeof===W&&es(Be)===F.type){o(K,F.sibling),oe=p(F,Y.props),Tl(oe,Y),oe.return=K,K=oe;break e}o(K,F);break}else a(K,F);F=F.sibling}Y.type===x?(oe=Yi(Y.props.children,K.mode,oe,Y.key),oe.return=K,K=oe):(oe=Tu(Y.type,Y.key,Y.props,null,K.mode,oe),Tl(oe,Y),oe.return=K,K=oe)}return C(K);case _:e:{for(Be=Y.key;F!==null;){if(F.key===Be)if(F.tag===4&&F.stateNode.containerInfo===Y.containerInfo&&F.stateNode.implementation===Y.implementation){o(K,F.sibling),oe=p(F,Y.children||[]),oe.return=K,K=oe;break e}else{o(K,F);break}else a(K,F);F=F.sibling}oe=Qh(Y,K.mode,oe),oe.return=K,K=oe}return C(K);case W:return Y=es(Y),Nt(K,F,Y,oe)}if(Et(Y))return Oe(K,F,Y,oe);if(Ie(Y)){if(Be=Ie(Y),typeof Be!="function")throw Error(r(150));return Y=Be.call(Y),Ge(K,F,Y,oe)}if(typeof Y.then=="function")return Nt(K,F,Mu(Y),oe);if(Y.$$typeof===k)return Nt(K,F,Du(K,Y),oe);Nu(K,Y)}return typeof Y=="string"&&Y!==""||typeof Y=="number"||typeof Y=="bigint"?(Y=""+Y,F!==null&&F.tag===6?(o(K,F.sibling),oe=p(F,Y),oe.return=K,K=oe):(o(K,F),oe=Jh(Y,K.mode,oe),oe.return=K,K=oe),C(K)):o(K,F)}return function(K,F,Y,oe){try{xl=0;var Be=Nt(K,F,Y,oe);return Ws=null,Be}catch(Ne){if(Ne===Ks||Ne===Lu)throw Ne;var St=er(29,Ne,null,K.mode);return St.lanes=oe,St.return=K,St}}}var ns=mv(!0),yv=mv(!1),ni=!1;function up(t){t.updateQueue={baseState:t.memoizedState,firstBaseUpdate:null,lastBaseUpdate:null,shared:{pending:null,lanes:0,hiddenCallbacks:null},callbacks:null}}function dp(t,a){t=t.updateQueue,a.updateQueue===t&&(a.updateQueue={baseState:t.baseState,firstBaseUpdate:t.firstBaseUpdate,lastBaseUpdate:t.lastBaseUpdate,shared:t.shared,callbacks:null})}function ri(t){return{lane:t,tag:0,payload:null,callback:null,next:null}}function ai(t,a,o){var u=t.updateQueue;if(u===null)return null;if(u=u.shared,(_t&2)!==0){var p=u.pending;return p===null?a.next=a:(a.next=p.next,p.next=a),u.pending=a,a=xu(t),ev(t,null,o),a}return Au(t,u,a,o),xu(t)}function kl(t,a,o){if(a=a.updateQueue,a!==null&&(a=a.shared,(o&4194048)!==0)){var u=a.lanes;u&=t.pendingLanes,o|=u,a.lanes=o,Ce(t,o)}}function fp(t,a){var o=t.updateQueue,u=t.alternate;if(u!==null&&(u=u.updateQueue,o===u)){var p=null,g=null;if(o=o.firstBaseUpdate,o!==null){do{var C={lane:o.lane,tag:o.tag,payload:o.payload,callback:null,next:null};g===null?p=g=C:g=g.next=C,o=o.next}while(o!==null);g===null?p=g=a:g=g.next=a}else p=g=a;o={baseState:u.baseState,firstBaseUpdate:p,lastBaseUpdate:g,shared:u.shared,callbacks:u.callbacks},t.updateQueue=o;return}t=o.lastBaseUpdate,t===null?o.firstBaseUpdate=a:t.next=a,o.lastBaseUpdate=a}var hp=!1;function Rl(){if(hp){var t=Vs;if(t!==null)throw t}}function Dl(t,a,o,u){hp=!1;var p=t.updateQueue;ni=!1;var g=p.firstBaseUpdate,C=p.lastBaseUpdate,R=p.shared.pending;if(R!==null){p.shared.pending=null;var P=R,X=P.next;P.next=null,C===null?g=X:C.next=X,C=P;var re=t.alternate;re!==null&&(re=re.updateQueue,R=re.lastBaseUpdate,R!==C&&(R===null?re.firstBaseUpdate=X:R.next=X,re.lastBaseUpdate=P))}if(g!==null){var le=p.baseState;C=0,re=X=P=null,R=g;do{var J=R.lane&-536870913,te=J!==R.lane;if(te?(ct&J)===J:(u&J)===J){J!==0&&J===Fs&&(hp=!0),re!==null&&(re=re.next={lane:0,tag:R.tag,payload:R.payload,callback:null,next:null});e:{var Oe=t,Ge=R;J=a;var Nt=o;switch(Ge.tag){case 1:if(Oe=Ge.payload,typeof Oe=="function"){le=Oe.call(Nt,le,J);break e}le=Oe;break e;case 3:Oe.flags=Oe.flags&-65537|128;case 0:if(Oe=Ge.payload,J=typeof Oe=="function"?Oe.call(Nt,le,J):Oe,J==null)break e;le=y({},le,J);break e;case 2:ni=!0}}J=R.callback,J!==null&&(t.flags|=64,te&&(t.flags|=8192),te=p.callbacks,te===null?p.callbacks=[J]:te.push(J))}else te={lane:J,tag:R.tag,payload:R.payload,callback:R.callback,next:null},re===null?(X=re=te,P=le):re=re.next=te,C|=J;if(R=R.next,R===null){if(R=p.shared.pending,R===null)break;te=R,R=te.next,te.next=null,p.lastBaseUpdate=te,p.shared.pending=null}}while(!0);re===null&&(P=le),p.baseState=P,p.firstBaseUpdate=X,p.lastBaseUpdate=re,g===null&&(p.shared.lanes=0),ci|=C,t.lanes=C,t.memoizedState=le}}function bv(t,a){if(typeof t!="function")throw Error(r(191,t));t.call(a)}function vv(t,a){var o=t.callbacks;if(o!==null)for(t.callbacks=null,t=0;tg?g:8;var C=Q.T,R={};Q.T=R,Lp(t,!1,a,o);try{var P=p(),X=Q.S;if(X!==null&&X(R,P),P!==null&&typeof P=="object"&&typeof P.then=="function"){var re=uk(P,u);$l(t,a,re,ir(t))}else $l(t,a,u,ir(t))}catch(le){$l(t,a,{then:function(){},status:"rejected",reason:le},ir())}finally{ue.p=g,C!==null&&R.types!==null&&(C.types=R.types),Q.T=C}}function mk(){}function Dp(t,a,o,u){if(t.tag!==5)throw Error(r(476));var p=Jv(t).queue;Xv(t,p,a,De,o===null?mk:function(){return Qv(t),o(u)})}function Jv(t){var a=t.memoizedState;if(a!==null)return a;a={memoizedState:De,baseState:De,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:wa,lastRenderedState:De},next:null};var o={};return a.next={memoizedState:o,baseState:o,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:wa,lastRenderedState:o},next:null},t.memoizedState=a,t=t.alternate,t!==null&&(t.memoizedState=a),a}function Qv(t){var a=Jv(t);a.next===null&&(a=t.alternate.memoizedState),$l(t,a.next.queue,{},ir())}function Op(){return Cn(Xl)}function Zv(){return en().memoizedState}function eS(){return en().memoizedState}function yk(t){for(var a=t.return;a!==null;){switch(a.tag){case 24:case 3:var o=ir();t=ri(o);var u=ai(a,t,o);u!==null&&(Kn(u,a,o),kl(u,a,o)),a={cache:sp()},t.payload=a;return}a=a.return}}function bk(t,a,o){var u=ir();o={lane:u,revertLane:0,gesture:null,action:o,hasEagerState:!1,eagerState:null,next:null},Fu(t)?nS(a,o):(o=Yh(t,a,o,u),o!==null&&(Kn(o,t,u),rS(o,a,u)))}function tS(t,a,o){var u=ir();$l(t,a,o,u)}function $l(t,a,o,u){var p={lane:u,revertLane:0,gesture:null,action:o,hasEagerState:!1,eagerState:null,next:null};if(Fu(t))nS(a,p);else{var g=t.alternate;if(t.lanes===0&&(g===null||g.lanes===0)&&(g=a.lastRenderedReducer,g!==null))try{var C=a.lastRenderedState,R=g(C,o);if(p.hasEagerState=!0,p.eagerState=R,Zn(R,C))return Au(t,a,p,0),It===null&&_u(),!1}catch{}if(o=Yh(t,a,p,u),o!==null)return Kn(o,t,u),rS(o,a,u),!0}return!1}function Lp(t,a,o,u){if(u={lane:2,revertLane:ug(),gesture:null,action:u,hasEagerState:!1,eagerState:null,next:null},Fu(t)){if(a)throw Error(r(479))}else a=Yh(t,o,u,2),a!==null&&Kn(a,t,2)}function Fu(t){var a=t.alternate;return t===Je||a!==null&&a===Je}function nS(t,a){Xs=Hu=!0;var o=t.pending;o===null?a.next=a:(a.next=o.next,o.next=a),t.pending=a}function rS(t,a,o){if((o&4194048)!==0){var u=a.lanes;u&=t.pendingLanes,o|=u,a.lanes=o,Ce(t,o)}}var Ml={readContext:Cn,use:Uu,useCallback:Xt,useContext:Xt,useEffect:Xt,useImperativeHandle:Xt,useLayoutEffect:Xt,useInsertionEffect:Xt,useMemo:Xt,useReducer:Xt,useRef:Xt,useState:Xt,useDebugValue:Xt,useDeferredValue:Xt,useTransition:Xt,useSyncExternalStore:Xt,useId:Xt,useHostTransitionStatus:Xt,useFormState:Xt,useActionState:Xt,useOptimistic:Xt,useMemoCache:Xt,useCacheRefresh:Xt};Ml.useEffectEvent=Xt;var aS={readContext:Cn,use:Uu,useCallback:function(t,a){return In().memoizedState=[t,a===void 0?null:a],t},useContext:Cn,useEffect:Uv,useImperativeHandle:function(t,a,o){o=o!=null?o.concat([t]):null,qu(4194308,4,Fv.bind(null,a,t),o)},useLayoutEffect:function(t,a){return qu(4194308,4,t,a)},useInsertionEffect:function(t,a){qu(4,2,t,a)},useMemo:function(t,a){var o=In();a=a===void 0?null:a;var u=t();if(rs){Rn(!0);try{t()}finally{Rn(!1)}}return o.memoizedState=[u,a],u},useReducer:function(t,a,o){var u=In();if(o!==void 0){var p=o(a);if(rs){Rn(!0);try{o(a)}finally{Rn(!1)}}}else p=a;return u.memoizedState=u.baseState=p,t={pending:null,lanes:0,dispatch:null,lastRenderedReducer:t,lastRenderedState:p},u.queue=t,t=t.dispatch=bk.bind(null,Je,t),[u.memoizedState,t]},useRef:function(t){var a=In();return t={current:t},a.memoizedState=t},useState:function(t){t=Ap(t);var a=t.queue,o=tS.bind(null,Je,a);return a.dispatch=o,[t.memoizedState,o]},useDebugValue:kp,useDeferredValue:function(t,a){var o=In();return Rp(o,t,a)},useTransition:function(){var t=Ap(!1);return t=Xv.bind(null,Je,t.queue,!0,!1),In().memoizedState=t,[!1,t]},useSyncExternalStore:function(t,a,o){var u=Je,p=In();if(ht){if(o===void 0)throw Error(r(407));o=o()}else{if(o=a(),It===null)throw Error(r(349));(ct&127)!==0||Av(u,a,o)}p.memoizedState=o;var g={value:o,getSnapshot:a};return p.queue=g,Uv(Tv.bind(null,u,g,t),[t]),u.flags|=2048,Qs(9,{destroy:void 0},xv.bind(null,u,g,o,a),null),o},useId:function(){var t=In(),a=It.identifierPrefix;if(ht){var o=Zr,u=Qr;o=(u&~(1<<32-dn(u)-1)).toString(32)+o,a="_"+a+"R_"+o,o=zu++,0<\/script>",g=g.removeChild(g.firstChild);break;case"select":g=typeof u.is=="string"?C.createElement("select",{is:u.is}):C.createElement("select"),u.multiple?g.multiple=!0:u.size&&(g.size=u.size);break;default:g=typeof u.is=="string"?C.createElement(p,{is:u.is}):C.createElement(p)}}g[Ft]=a,g[Dn]=u;e:for(C=a.child;C!==null;){if(C.tag===5||C.tag===6)g.appendChild(C.stateNode);else if(C.tag!==4&&C.tag!==27&&C.child!==null){C.child.return=C,C=C.child;continue}if(C===a)break e;for(;C.sibling===null;){if(C.return===null||C.return===a)break e;C=C.return}C.sibling.return=C.return,C=C.sibling}a.stateNode=g;e:switch(_n(g,p,u),p){case"button":case"input":case"select":case"textarea":u=!!u.autoFocus;break e;case"img":u=!0;break e;default:u=!1}u&&Ea(a)}}return Ut(a),Vp(a,a.type,t===null?null:t.memoizedProps,a.pendingProps,o),null;case 6:if(t&&a.stateNode!=null)t.memoizedProps!==u&&Ea(a);else{if(typeof u!="string"&&a.stateNode===null)throw Error(r(166));if(t=se.current,qs(a)){if(t=a.stateNode,o=a.memoizedProps,u=null,p=wn,p!==null)switch(p.tag){case 27:case 5:u=p.memoizedProps}t[Ft]=a,t=!!(t.nodeValue===o||u!==null&&u.suppressHydrationWarning===!0||Ew(t.nodeValue,o)),t||ei(a,!0)}else t=dd(t).createTextNode(u),t[Ft]=a,a.stateNode=t}return Ut(a),null;case 31:if(o=a.memoizedState,t===null||t.memoizedState!==null){if(u=qs(a),o!==null){if(t===null){if(!u)throw Error(r(318));if(t=a.memoizedState,t=t!==null?t.dehydrated:null,!t)throw Error(r(557));t[Ft]=a}else Xi(),(a.flags&128)===0&&(a.memoizedState=null),a.flags|=4;Ut(a),t=!1}else o=np(),t!==null&&t.memoizedState!==null&&(t.memoizedState.hydrationErrors=o),t=!0;if(!t)return a.flags&256?(nr(a),a):(nr(a),null);if((a.flags&128)!==0)throw Error(r(558))}return Ut(a),null;case 13:if(u=a.memoizedState,t===null||t.memoizedState!==null&&t.memoizedState.dehydrated!==null){if(p=qs(a),u!==null&&u.dehydrated!==null){if(t===null){if(!p)throw Error(r(318));if(p=a.memoizedState,p=p!==null?p.dehydrated:null,!p)throw Error(r(317));p[Ft]=a}else Xi(),(a.flags&128)===0&&(a.memoizedState=null),a.flags|=4;Ut(a),p=!1}else p=np(),t!==null&&t.memoizedState!==null&&(t.memoizedState.hydrationErrors=p),p=!0;if(!p)return a.flags&256?(nr(a),a):(nr(a),null)}return nr(a),(a.flags&128)!==0?(a.lanes=o,a):(o=u!==null,t=t!==null&&t.memoizedState!==null,o&&(u=a.child,p=null,u.alternate!==null&&u.alternate.memoizedState!==null&&u.alternate.memoizedState.cachePool!==null&&(p=u.alternate.memoizedState.cachePool.pool),g=null,u.memoizedState!==null&&u.memoizedState.cachePool!==null&&(g=u.memoizedState.cachePool.pool),g!==p&&(u.flags|=2048)),o!==t&&o&&(a.child.flags|=8192),Xu(a,a.updateQueue),Ut(a),null);case 4:return Te(),t===null&&pg(a.stateNode.containerInfo),Ut(a),null;case 10:return va(a.type),Ut(a),null;case 19:if(T(Zt),u=a.memoizedState,u===null)return Ut(a),null;if(p=(a.flags&128)!==0,g=u.rendering,g===null)if(p)Il(u,!1);else{if(Jt!==0||t!==null&&(t.flags&128)!==0)for(t=a.child;t!==null;){if(g=ju(t),g!==null){for(a.flags|=128,Il(u,!1),t=g.updateQueue,a.updateQueue=t,Xu(a,t),a.subtreeFlags=0,t=o,o=a.child;o!==null;)tv(o,t),o=o.sibling;return fe(Zt,Zt.current&1|2),ht&&ya(a,u.treeForkCount),a.child}t=t.sibling}u.tail!==null&&Ot()>td&&(a.flags|=128,p=!0,Il(u,!1),a.lanes=4194304)}else{if(!p)if(t=ju(g),t!==null){if(a.flags|=128,p=!0,t=t.updateQueue,a.updateQueue=t,Xu(a,t),Il(u,!0),u.tail===null&&u.tailMode==="hidden"&&!g.alternate&&!ht)return Ut(a),null}else 2*Ot()-u.renderingStartTime>td&&o!==536870912&&(a.flags|=128,p=!0,Il(u,!1),a.lanes=4194304);u.isBackwards?(g.sibling=a.child,a.child=g):(t=u.last,t!==null?t.sibling=g:a.child=g,u.last=g)}return u.tail!==null?(t=u.tail,u.rendering=t,u.tail=t.sibling,u.renderingStartTime=Ot(),t.sibling=null,o=Zt.current,fe(Zt,p?o&1|2:o&1),ht&&ya(a,u.treeForkCount),t):(Ut(a),null);case 22:case 23:return nr(a),gp(),u=a.memoizedState!==null,t!==null?t.memoizedState!==null!==u&&(a.flags|=8192):u&&(a.flags|=8192),u?(o&536870912)!==0&&(a.flags&128)===0&&(Ut(a),a.subtreeFlags&6&&(a.flags|=8192)):Ut(a),o=a.updateQueue,o!==null&&Xu(a,o.retryQueue),o=null,t!==null&&t.memoizedState!==null&&t.memoizedState.cachePool!==null&&(o=t.memoizedState.cachePool.pool),u=null,a.memoizedState!==null&&a.memoizedState.cachePool!==null&&(u=a.memoizedState.cachePool.pool),u!==o&&(a.flags|=2048),t!==null&&T(Zi),null;case 24:return o=null,t!==null&&(o=t.memoizedState.cache),a.memoizedState.cache!==o&&(a.flags|=2048),va(rn),Ut(a),null;case 25:return null;case 30:return null}throw Error(r(156,a.tag))}function Ek(t,a){switch(ep(a),a.tag){case 1:return t=a.flags,t&65536?(a.flags=t&-65537|128,a):null;case 3:return va(rn),Te(),t=a.flags,(t&65536)!==0&&(t&128)===0?(a.flags=t&-65537|128,a):null;case 26:case 27:case 5:return xt(a),null;case 31:if(a.memoizedState!==null){if(nr(a),a.alternate===null)throw Error(r(340));Xi()}return t=a.flags,t&65536?(a.flags=t&-65537|128,a):null;case 13:if(nr(a),t=a.memoizedState,t!==null&&t.dehydrated!==null){if(a.alternate===null)throw Error(r(340));Xi()}return t=a.flags,t&65536?(a.flags=t&-65537|128,a):null;case 19:return T(Zt),null;case 4:return Te(),null;case 10:return va(a.type),null;case 22:case 23:return nr(a),gp(),t!==null&&T(Zi),t=a.flags,t&65536?(a.flags=t&-65537|128,a):null;case 24:return va(rn),null;case 25:return null;default:return null}}function kS(t,a){switch(ep(a),a.tag){case 3:va(rn),Te();break;case 26:case 27:case 5:xt(a);break;case 4:Te();break;case 31:a.memoizedState!==null&&nr(a);break;case 13:nr(a);break;case 19:T(Zt);break;case 10:va(a.type);break;case 22:case 23:nr(a),gp(),t!==null&&T(Zi);break;case 24:va(rn)}}function jl(t,a){try{var o=a.updateQueue,u=o!==null?o.lastEffect:null;if(u!==null){var p=u.next;o=p;do{if((o.tag&t)===t){u=void 0;var g=o.create,C=o.inst;u=g(),C.destroy=u}o=o.next}while(o!==p)}}catch(R){kt(a,a.return,R)}}function oi(t,a,o){try{var u=a.updateQueue,p=u!==null?u.lastEffect:null;if(p!==null){var g=p.next;u=g;do{if((u.tag&t)===t){var C=u.inst,R=C.destroy;if(R!==void 0){C.destroy=void 0,p=a;var P=o,X=R;try{X()}catch(re){kt(p,P,re)}}}u=u.next}while(u!==g)}}catch(re){kt(a,a.return,re)}}function RS(t){var a=t.updateQueue;if(a!==null){var o=t.stateNode;try{vv(a,o)}catch(u){kt(t,t.return,u)}}}function DS(t,a,o){o.props=as(t.type,t.memoizedProps),o.state=t.memoizedState;try{o.componentWillUnmount()}catch(u){kt(t,a,u)}}function Hl(t,a){try{var o=t.ref;if(o!==null){switch(t.tag){case 26:case 27:case 5:var u=t.stateNode;break;case 30:u=t.stateNode;break;default:u=t.stateNode}typeof o=="function"?t.refCleanup=o(u):o.current=u}}catch(p){kt(t,a,p)}}function ea(t,a){var o=t.ref,u=t.refCleanup;if(o!==null)if(typeof u=="function")try{u()}catch(p){kt(t,a,p)}finally{t.refCleanup=null,t=t.alternate,t!=null&&(t.refCleanup=null)}else if(typeof o=="function")try{o(null)}catch(p){kt(t,a,p)}else o.current=null}function OS(t){var a=t.type,o=t.memoizedProps,u=t.stateNode;try{e:switch(a){case"button":case"input":case"select":case"textarea":o.autoFocus&&u.focus();break e;case"img":o.src?u.src=o.src:o.srcSet&&(u.srcset=o.srcSet)}}catch(p){kt(t,t.return,p)}}function Kp(t,a,o){try{var u=t.stateNode;Fk(u,t.type,o,a),u[Dn]=a}catch(p){kt(t,t.return,p)}}function LS(t){return t.tag===5||t.tag===3||t.tag===26||t.tag===27&&pi(t.type)||t.tag===4}function Wp(t){e:for(;;){for(;t.sibling===null;){if(t.return===null||LS(t.return))return null;t=t.return}for(t.sibling.return=t.return,t=t.sibling;t.tag!==5&&t.tag!==6&&t.tag!==18;){if(t.tag===27&&pi(t.type)||t.flags&2||t.child===null||t.tag===4)continue e;t.child.return=t,t=t.child}if(!(t.flags&2))return t.stateNode}}function Yp(t,a,o){var u=t.tag;if(u===5||u===6)t=t.stateNode,a?(o.nodeType===9?o.body:o.nodeName==="HTML"?o.ownerDocument.body:o).insertBefore(t,a):(a=o.nodeType===9?o.body:o.nodeName==="HTML"?o.ownerDocument.body:o,a.appendChild(t),o=o._reactRootContainer,o!=null||a.onclick!==null||(a.onclick=Mr));else if(u!==4&&(u===27&&pi(t.type)&&(o=t.stateNode,a=null),t=t.child,t!==null))for(Yp(t,a,o),t=t.sibling;t!==null;)Yp(t,a,o),t=t.sibling}function Ju(t,a,o){var u=t.tag;if(u===5||u===6)t=t.stateNode,a?o.insertBefore(t,a):o.appendChild(t);else if(u!==4&&(u===27&&pi(t.type)&&(o=t.stateNode),t=t.child,t!==null))for(Ju(t,a,o),t=t.sibling;t!==null;)Ju(t,a,o),t=t.sibling}function $S(t){var a=t.stateNode,o=t.memoizedProps;try{for(var u=t.type,p=a.attributes;p.length;)a.removeAttributeNode(p[0]);_n(a,u,o),a[Ft]=t,a[Dn]=o}catch(g){kt(t,t.return,g)}}var _a=!1,on=!1,Xp=!1,MS=typeof WeakSet=="function"?WeakSet:Set,yn=null;function _k(t,a){if(t=t.containerInfo,yg=bd,t=Vb(t),qh(t)){if("selectionStart"in t)var o={start:t.selectionStart,end:t.selectionEnd};else e:{o=(o=t.ownerDocument)&&o.defaultView||window;var u=o.getSelection&&o.getSelection();if(u&&u.rangeCount!==0){o=u.anchorNode;var p=u.anchorOffset,g=u.focusNode;u=u.focusOffset;try{o.nodeType,g.nodeType}catch{o=null;break e}var C=0,R=-1,P=-1,X=0,re=0,le=t,J=null;t:for(;;){for(var te;le!==o||p!==0&&le.nodeType!==3||(R=C+p),le!==g||u!==0&&le.nodeType!==3||(P=C+u),le.nodeType===3&&(C+=le.nodeValue.length),(te=le.firstChild)!==null;)J=le,le=te;for(;;){if(le===t)break t;if(J===o&&++X===p&&(R=C),J===g&&++re===u&&(P=C),(te=le.nextSibling)!==null)break;le=J,J=le.parentNode}le=te}o=R===-1||P===-1?null:{start:R,end:P}}else o=null}o=o||{start:0,end:0}}else o=null;for(bg={focusedElem:t,selectionRange:o},bd=!1,yn=a;yn!==null;)if(a=yn,t=a.child,(a.subtreeFlags&1028)!==0&&t!==null)t.return=a,yn=t;else for(;yn!==null;){switch(a=yn,g=a.alternate,t=a.flags,a.tag){case 0:if((t&4)!==0&&(t=a.updateQueue,t=t!==null?t.events:null,t!==null))for(o=0;o title"))),_n(g,u,o),g[Ft]=t,nn(g),u=g;break e;case"link":var C=Pw("link","href",p).get(u+(o.href||""));if(C){for(var R=0;RNt&&(C=Nt,Nt=Ge,Ge=C);var K=Gb(R,Ge),F=Gb(R,Nt);if(K&&F&&(te.rangeCount!==1||te.anchorNode!==K.node||te.anchorOffset!==K.offset||te.focusNode!==F.node||te.focusOffset!==F.offset)){var Y=le.createRange();Y.setStart(K.node,K.offset),te.removeAllRanges(),Ge>Nt?(te.addRange(Y),te.extend(F.node,F.offset)):(Y.setEnd(F.node,F.offset),te.addRange(Y))}}}}for(le=[],te=R;te=te.parentNode;)te.nodeType===1&&le.push({element:te,left:te.scrollLeft,top:te.scrollTop});for(typeof R.focus=="function"&&R.focus(),R=0;Ro?32:o,Q.T=null,o=rg,rg=null;var g=di,C=Ra;if(hn=0,ro=di=null,Ra=0,(_t&6)!==0)throw Error(r(331));var R=_t;if(_t|=4,FS(g.current),BS(g,g.current,C,o),_t=R,Gl(0,!1),un&&typeof un.onPostCommitFiberRoot=="function")try{un.onPostCommitFiberRoot(fr,g)}catch{}return!0}finally{ue.p=p,Q.T=u,cw(t,a)}}function dw(t,a,o){a=gr(o,a),a=Ip(t.stateNode,a,2),t=ai(t,a,2),t!==null&&(tt(t,2),ta(t))}function kt(t,a,o){if(t.tag===3)dw(t,t,o);else for(;a!==null;){if(a.tag===3){dw(a,t,o);break}else if(a.tag===1){var u=a.stateNode;if(typeof a.type.getDerivedStateFromError=="function"||typeof u.componentDidCatch=="function"&&(ui===null||!ui.has(u))){t=gr(o,t),o=fS(2),u=ai(a,o,2),u!==null&&(hS(o,u,a,t),tt(u,2),ta(u));break}}a=a.return}}function og(t,a,o){var u=t.pingCache;if(u===null){u=t.pingCache=new Tk;var p=new Set;u.set(a,p)}else p=u.get(a),p===void 0&&(p=new Set,u.set(a,p));p.has(o)||(Zp=!0,p.add(o),t=Lk.bind(null,t,a,o),a.then(t,t))}function Lk(t,a,o){var u=t.pingCache;u!==null&&u.delete(a),t.pingedLanes|=t.suspendedLanes&o,t.warmLanes&=~o,It===t&&(ct&o)===o&&(Jt===4||Jt===3&&(ct&62914560)===ct&&300>Ot()-ed?(_t&2)===0&&ao(t,0):eg|=o,no===ct&&(no=0)),ta(t)}function fw(t,a){a===0&&(a=Me()),t=Wi(t,a),t!==null&&(tt(t,a),ta(t))}function $k(t){var a=t.memoizedState,o=0;a!==null&&(o=a.retryLane),fw(t,o)}function Mk(t,a){var o=0;switch(t.tag){case 31:case 13:var u=t.stateNode,p=t.memoizedState;p!==null&&(o=p.retryLane);break;case 19:u=t.stateNode;break;case 22:u=t.stateNode._retryCache;break;default:throw Error(r(314))}u!==null&&u.delete(a),fw(t,o)}function Nk(t,a){return Dt(t,a)}var od=null,so=null,lg=!1,ld=!1,cg=!1,hi=0;function ta(t){t!==so&&t.next===null&&(so===null?od=so=t:so=so.next=t),ld=!0,lg||(lg=!0,jk())}function Gl(t,a){if(!cg&&ld){cg=!0;do for(var o=!1,u=od;u!==null;){if(t!==0){var p=u.pendingLanes;if(p===0)var g=0;else{var C=u.suspendedLanes,R=u.pingedLanes;g=(1<<31-dn(42|t)+1)-1,g&=p&~(C&~R),g=g&201326741?g&201326741|1:g?g|2:0}g!==0&&(o=!0,mw(u,g))}else g=ct,g=xe(u,u===It?g:0,u.cancelPendingCommit!==null||u.timeoutHandle!==-1),(g&3)===0||Ke(u,g)||(o=!0,mw(u,g));u=u.next}while(o);cg=!1}}function Ik(){hw()}function hw(){ld=lg=!1;var t=0;hi!==0&&Kk()&&(t=hi);for(var a=Ot(),o=null,u=od;u!==null;){var p=u.next,g=pw(u,a);g===0?(u.next=null,o===null?od=p:o.next=p,p===null&&(so=o)):(o=u,(t!==0||(g&3)!==0)&&(ld=!0)),u=p}hn!==0&&hn!==5||Gl(t),hi!==0&&(hi=0)}function pw(t,a){for(var o=t.suspendedLanes,u=t.pingedLanes,p=t.expirationTimes,g=t.pendingLanes&-62914561;0R)break;var re=P.transferSize,le=P.initiatorType;re&&_w(le)&&(P=P.responseEnd,C+=re*(P"u"?null:document;function Iw(t,a,o){var u=oo;if(u&&typeof a=="string"&&a){var p=Bn(a);p='link[rel="'+t+'"][href="'+p+'"]',typeof o=="string"&&(p+='[crossorigin="'+o+'"]'),Nw.has(p)||(Nw.add(p),t={rel:t,crossOrigin:o,href:a},u.querySelector(p)===null&&(a=u.createElement("link"),_n(a,"link",t),nn(a),u.head.appendChild(a)))}}function nR(t){Da.D(t),Iw("dns-prefetch",t,null)}function rR(t,a){Da.C(t,a),Iw("preconnect",t,a)}function aR(t,a,o){Da.L(t,a,o);var u=oo;if(u&&t&&a){var p='link[rel="preload"][as="'+Bn(a)+'"]';a==="image"&&o&&o.imageSrcSet?(p+='[imagesrcset="'+Bn(o.imageSrcSet)+'"]',typeof o.imageSizes=="string"&&(p+='[imagesizes="'+Bn(o.imageSizes)+'"]')):p+='[href="'+Bn(t)+'"]';var g=p;switch(a){case"style":g=lo(t);break;case"script":g=co(t)}wr.has(g)||(t=y({rel:"preload",href:a==="image"&&o&&o.imageSrcSet?void 0:t,as:a},o),wr.set(g,t),u.querySelector(p)!==null||a==="style"&&u.querySelector(Wl(g))||a==="script"&&u.querySelector(Yl(g))||(a=u.createElement("link"),_n(a,"link",t),nn(a),u.head.appendChild(a)))}}function iR(t,a){Da.m(t,a);var o=oo;if(o&&t){var u=a&&typeof a.as=="string"?a.as:"script",p='link[rel="modulepreload"][as="'+Bn(u)+'"][href="'+Bn(t)+'"]',g=p;switch(u){case"audioworklet":case"paintworklet":case"serviceworker":case"sharedworker":case"worker":case"script":g=co(t)}if(!wr.has(g)&&(t=y({rel:"modulepreload",href:t},a),wr.set(g,t),o.querySelector(p)===null)){switch(u){case"audioworklet":case"paintworklet":case"serviceworker":case"sharedworker":case"worker":case"script":if(o.querySelector(Yl(g)))return}u=o.createElement("link"),_n(u,"link",t),nn(u),o.head.appendChild(u)}}}function sR(t,a,o){Da.S(t,a,o);var u=oo;if(u&&t){var p=Ga(u).hoistableStyles,g=lo(t);a=a||"default";var C=p.get(g);if(!C){var R={loading:0,preload:null};if(C=u.querySelector(Wl(g)))R.loading=5;else{t=y({rel:"stylesheet",href:t,"data-precedence":a},o),(o=wr.get(g))&&Ag(t,o);var P=C=u.createElement("link");nn(P),_n(P,"link",t),P._p=new Promise(function(X,re){P.onload=X,P.onerror=re}),P.addEventListener("load",function(){R.loading|=1}),P.addEventListener("error",function(){R.loading|=2}),R.loading|=4,hd(C,a,u)}C={type:"stylesheet",instance:C,count:1,state:R},p.set(g,C)}}}function oR(t,a){Da.X(t,a);var o=oo;if(o&&t){var u=Ga(o).hoistableScripts,p=co(t),g=u.get(p);g||(g=o.querySelector(Yl(p)),g||(t=y({src:t,async:!0},a),(a=wr.get(p))&&xg(t,a),g=o.createElement("script"),nn(g),_n(g,"link",t),o.head.appendChild(g)),g={type:"script",instance:g,count:1,state:null},u.set(p,g))}}function lR(t,a){Da.M(t,a);var o=oo;if(o&&t){var u=Ga(o).hoistableScripts,p=co(t),g=u.get(p);g||(g=o.querySelector(Yl(p)),g||(t=y({src:t,async:!0,type:"module"},a),(a=wr.get(p))&&xg(t,a),g=o.createElement("script"),nn(g),_n(g,"link",t),o.head.appendChild(g)),g={type:"script",instance:g,count:1,state:null},u.set(p,g))}}function jw(t,a,o,u){var p=(p=se.current)?fd(p):null;if(!p)throw Error(r(446));switch(t){case"meta":case"title":return null;case"style":return typeof o.precedence=="string"&&typeof o.href=="string"?(a=lo(o.href),o=Ga(p).hoistableStyles,u=o.get(a),u||(u={type:"style",instance:null,count:0,state:null},o.set(a,u)),u):{type:"void",instance:null,count:0,state:null};case"link":if(o.rel==="stylesheet"&&typeof o.href=="string"&&typeof o.precedence=="string"){t=lo(o.href);var g=Ga(p).hoistableStyles,C=g.get(t);if(C||(p=p.ownerDocument||p,C={type:"stylesheet",instance:null,count:0,state:{loading:0,preload:null}},g.set(t,C),(g=p.querySelector(Wl(t)))&&!g._p&&(C.instance=g,C.state.loading=5),wr.has(t)||(o={rel:"preload",as:"style",href:o.href,crossOrigin:o.crossOrigin,integrity:o.integrity,media:o.media,hrefLang:o.hrefLang,referrerPolicy:o.referrerPolicy},wr.set(t,o),g||cR(p,t,o,C.state))),a&&u===null)throw Error(r(528,""));return C}if(a&&u!==null)throw Error(r(529,""));return null;case"script":return a=o.async,o=o.src,typeof o=="string"&&a&&typeof a!="function"&&typeof a!="symbol"?(a=co(o),o=Ga(p).hoistableScripts,u=o.get(a),u||(u={type:"script",instance:null,count:0,state:null},o.set(a,u)),u):{type:"void",instance:null,count:0,state:null};default:throw Error(r(444,t))}}function lo(t){return'href="'+Bn(t)+'"'}function Wl(t){return'link[rel="stylesheet"]['+t+"]"}function Hw(t){return y({},t,{"data-precedence":t.precedence,precedence:null})}function cR(t,a,o,u){t.querySelector('link[rel="preload"][as="style"]['+a+"]")?u.loading=1:(a=t.createElement("link"),u.preload=a,a.addEventListener("load",function(){return u.loading|=1}),a.addEventListener("error",function(){return u.loading|=2}),_n(a,"link",o),nn(a),t.head.appendChild(a))}function co(t){return'[src="'+Bn(t)+'"]'}function Yl(t){return"script[async]"+t}function zw(t,a,o){if(a.count++,a.instance===null)switch(a.type){case"style":var u=t.querySelector('style[data-href~="'+Bn(o.href)+'"]');if(u)return a.instance=u,nn(u),u;var p=y({},o,{"data-href":o.href,"data-precedence":o.precedence,href:null,precedence:null});return u=(t.ownerDocument||t).createElement("style"),nn(u),_n(u,"style",p),hd(u,o.precedence,t),a.instance=u;case"stylesheet":p=lo(o.href);var g=t.querySelector(Wl(p));if(g)return a.state.loading|=4,a.instance=g,nn(g),g;u=Hw(o),(p=wr.get(p))&&Ag(u,p),g=(t.ownerDocument||t).createElement("link"),nn(g);var C=g;return C._p=new Promise(function(R,P){C.onload=R,C.onerror=P}),_n(g,"link",u),a.state.loading|=4,hd(g,o.precedence,t),a.instance=g;case"script":return g=co(o.src),(p=t.querySelector(Yl(g)))?(a.instance=p,nn(p),p):(u=o,(p=wr.get(g))&&(u=y({},o),xg(u,p)),t=t.ownerDocument||t,p=t.createElement("script"),nn(p),_n(p,"link",u),t.head.appendChild(p),a.instance=p);case"void":return null;default:throw Error(r(443,a.type))}else a.type==="stylesheet"&&(a.state.loading&4)===0&&(u=a.instance,a.state.loading|=4,hd(u,o.precedence,t));return a.instance}function hd(t,a,o){for(var u=o.querySelectorAll('link[rel="stylesheet"][data-precedence],style[data-precedence]'),p=u.length?u[u.length-1]:null,g=p,C=0;C title"):null)}function uR(t,a,o){if(o===1||a.itemProp!=null)return!1;switch(t){case"meta":case"title":return!0;case"style":if(typeof a.precedence!="string"||typeof a.href!="string"||a.href==="")break;return!0;case"link":if(typeof a.rel!="string"||typeof a.href!="string"||a.href===""||a.onLoad||a.onError)break;return a.rel==="stylesheet"?(t=a.disabled,typeof a.precedence=="string"&&t==null):!0;case"script":if(a.async&&typeof a.async!="function"&&typeof a.async!="symbol"&&!a.onLoad&&!a.onError&&a.src&&typeof a.src=="string")return!0}return!1}function Bw(t){return!(t.type==="stylesheet"&&(t.state.loading&3)===0)}function dR(t,a,o,u){if(o.type==="stylesheet"&&(typeof u.media!="string"||matchMedia(u.media).matches!==!1)&&(o.state.loading&4)===0){if(o.instance===null){var p=lo(u.href),g=a.querySelector(Wl(p));if(g){a=g._p,a!==null&&typeof a=="object"&&typeof a.then=="function"&&(t.count++,t=gd.bind(t),a.then(t,t)),o.state.loading|=4,o.instance=g,nn(g);return}g=a.ownerDocument||a,u=Hw(u),(p=wr.get(p))&&Ag(u,p),g=g.createElement("link"),nn(g);var C=g;C._p=new Promise(function(R,P){C.onload=R,C.onerror=P}),_n(g,"link",u),o.instance=g}t.stylesheets===null&&(t.stylesheets=new Map),t.stylesheets.set(o,a),(a=o.state.preload)&&(o.state.loading&3)===0&&(t.count++,o=gd.bind(t),a.addEventListener("load",o),a.addEventListener("error",o))}}var Tg=0;function fR(t,a){return t.stylesheets&&t.count===0&&yd(t,t.stylesheets),0Tg?50:800)+a);return t.unsuspend=o,function(){t.unsuspend=null,clearTimeout(u),clearTimeout(p)}}:null}function gd(){if(this.count--,this.count===0&&(this.imgCount===0||!this.waitingForImages)){if(this.stylesheets)yd(this,this.stylesheets);else if(this.unsuspend){var t=this.unsuspend;this.unsuspend=null,t()}}}var md=null;function yd(t,a){t.stylesheets=null,t.unsuspend!==null&&(t.count++,md=new Map,a.forEach(hR,t),md=null,gd.call(t))}function hR(t,a){if(!(a.state.loading&4)){var o=md.get(t);if(o)var u=o.get(null);else{o=new Map,md.set(t,o);for(var p=t.querySelectorAll("link[data-precedence],style[data-precedence]"),g=0;g"u"||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!="function"))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(s)}catch(e){console.error(e)}}return s(),Td.exports=l0(),Td.exports}var Od=c0();const Ld=ra(Od),ho=[],u0=50;let $d=!1;function qg(){try{return window.G7Core?.devTools}catch{return}}function os(s,e,n){const r=qg();if($d&&r?.isEnabled?.()){r.trackLog?.(s,e,n);return}$d||(s==="error"||s==="warn")&&ho.length{n.isDebugEnabled()&&console.log(e,...r),os("log",s,r)},warn:(...r)=>{n.isDebugEnabled()&&console.warn(e,...r),os("warn",s,r)},error:(...r)=>{n.isDebugEnabled()&&console.error(e,...r),os("error",s,r)}}}po.getInstance();const Gg="extension",ls="extensionless",Fg="file";function d0(){const s=globalThis?.G7Config;if(globalThis?.__g7AssetUrlMode===ls)return ls;const n=s?.assetUrlMode;return n===ls||n===Gg?n:s?.settings?.general?.asset_url_mode===ls?ls:Gg}function Nd(){return d0()===ls}function f0(s,e,n){return Id("templates",s,e,n)}function h0(s,e){const n=e.replace(/^\/+/,"").replace(/\/+$/,""),r=encodeURIComponent(s),i=go(`templates/${s}/assets/${n}`);return i!==null?i:`/api/templates/assets/${r}/${n}`}function p0(s,e,n){return Id("modules",s,e,n)}function g0(s,e,n){return Id("plugins",s,e,n)}function Id(s,e,n,r){const i=n.replace(/^\/+/,""),l=encodeURIComponent(e);if(!Nd())return`/api/${s}/assets/${l}/${i}${b0(r)}`;let c=`${Fg}=${encodeURIComponent(i)}`;return r!=null&&r!==""&&(c+=`&v=${r}`),`/api/${s}/assets/${l}?${c}`}function Ur(s,e,n,r){const i=s.replace(/\/+$/,""),l=e.replace(/^\.+/,""),c=Nd()?i:`${i}.${l}`,d=[];return r&&d.push(r.replace(/^[?&]+/,"")),n!=null&&n!==""&&d.push(`v=${n}`),d.length>0?`${c}?${d.join("&")}`:c}function Vg(){const s=globalThis?.G7Config?.staticBase;return typeof s=="string"&&s!==""?s.replace(/\/+$/,""):null}function m0(){const s=Vg();if(!s)return null;const e=s.match(/\/(\d+)$/);return e?Number(e[1]):null}function go(s,e){const n=Vg();if(!n)return null;const r=s.replace(/^\/+/,"");return e!==void 0&&e>0&&e!==m0()?`${n.replace(/\/\d+$/,"")}/${e}/${r}`:`${n}/${r}`}function jd(s){if(!s)return null;const e=globalThis?.location?.origin,r=(e&&s.startsWith(e)?s.slice(e.length):s).match(/^\/build\/ext\/(\d+)\/(.+)$/);if(!r)return null;const i=r[1],l=r[2].split("?")[0],c=l.match(/^(templates|modules|plugins)\/([^/]+)\/assets\/(.+)$/);if(c){const[,h,m,y]=c;return`/api/${h}/assets/${m}/${y}?v=${i}`}const d=l.match(/^bundles\/(modules|plugins)\.(js|css)$/);if(d)return`/api/${d[1]}/bundle.${d[2]}?v=${i}`;const f=l.match(/^templates\/([^/]+)\/(routes\.json|components\.json|lang\/([a-zA-Z-]+)\.json)$/);if(f){const[,h,m]=f,y=m.replace(/\.json$/,"");return`/api/templates/${h}/${y}.json?v=${i}`}return null}function Ln(s){if(!s||!Nd())return s;const e=globalThis?.location?.origin,n=e&&s.startsWith(e)?s.slice(e.length):s;if(!n.startsWith("/api/"))return s;const[r,i]=y0(n),l=r.match(/^\/api\/(modules|plugins)\/bundle\.(js|css)$/);if(l)return Kg(`/api/${l[1]}/bundle/${l[2]}`,i);const c=r.match(/^\/api\/(templates|modules|plugins)\/assets\/([^/]+)\/(.+)$/);if(c){const[,f,h,m]=c,y=`${Fg}=${encodeURIComponent(decodeURIComponent(m))}`;return`/api/${f}/assets/${h}?${y}${i?`&${i}`:""}`}const d=r.match(/^(\/api\/.+)\.(json|js|css)$/);return d?Kg(d[1],i):s}function y0(s){const e=s.indexOf("?");return e===-1?[s,""]:[s.slice(0,e),s.slice(e+1)]}function Kg(s,e){return e?`${s}?${e}`:s}function b0(s){return s==null||s===""?"":`?v=${s}`}const aa=pt("networkResilience"),Hd=2,zd=300,Pd=2e3,v0=15e3;function Ud(s){return s?.name==="AbortError"}function Wg(s){return Ud(s)?!1:s instanceof TypeError}let ec=!1,Yg=!1;function tc(){return ec}function S0(){typeof window>"u"||Yg||(Yg=!0,window.addEventListener("pagehide",()=>{ec=!0}),window.addEventListener("pageshow",s=>{s.persisted&&(ec=!1)}),window.addEventListener("beforeunload",()=>{ec=!0}))}function Bd(s,e,n){const r=Math.min(e*Math.pow(2,s),n),i=r*.25*(Math.random()*2-1);return Math.max(0,Math.round(r+i))}function qd(s){return new Promise(e=>setTimeout(e,s))}async function nc(s,e={}){const{retries:n=Hd,baseDelayMs:r=zd,maxDelayMs:i=Pd,timeoutMs:l=v0,label:c=s,init:d}=e,f=n+1;let h;for(let m=0;m0?new AbortController:null;try{const _={...d};if(S){const A=d?.signal;A&&(A.aborted?S.abort():A.addEventListener("abort",()=>S.abort(),{once:!0})),_.signal=S.signal,v=setTimeout(()=>{y=!0,S.abort()},l)}return await fetch(s,_)}catch(_){if(h=_,Ud(_)&&!y||!(y||Wg(_)))throw _;if(tc())throw aa.warn(`Document unloading, aborting retries: ${c}`),_;if(m===f-1)throw aa.warn(`All ${f} attempts failed: ${c}`,_),_;const D=Bd(m,r,i);aa.warn(`Network failure (attempt ${m+1}/${f}), retrying in ${D}ms: ${c}`),await qd(D)}finally{v!==void 0&&clearTimeout(v)}}throw h}async function Cr(s,e={},n={}){const{retries:r=Hd,baseDelayMs:i=zd,maxDelayMs:l=Pd,label:c=s}=n,d=r+1;for(let f=0;f{const i=document.createElement("script");i.src=s,i.async=!1;for(const[l,c]of Object.entries(e))l==="id"?i.id=c:i.setAttribute(l,c);i.onload=()=>n(),i.onerror=()=>r(new Error(`Failed to load script: ${s}`)),document.head.appendChild(i)})}async function ia(s,e={},n={}){const{retries:r=Hd,baseDelayMs:i=zd,maxDelayMs:l=Pd,label:c=s}=n,d=r+1;for(let f=0;f{s.onload=()=>e(),s.onerror=()=>n(new Error(`Failed to load stylesheet: ${s.href}`))})}async function rc(s,e,n={}){if(!s)return nc(e,n);try{const r=await fetch(s,n.init);if(r.ok){const i=await r.text();try{return JSON.parse(i),new Response(i,{status:r.status,statusText:r.statusText,headers:r.headers})}catch{return console.warn(`[fetchStaticFirst] Static fast path returned malformed JSON — falling back to API: ${s}`),nc(e,n)}}console.warn(`[fetchStaticFirst] Static fast path miss (${r.status}) — falling back to API: ${s}`)}catch(r){console.warn(`[fetchStaticFirst] Static fast path fetch failed — falling back to API: ${s}`,r)}return nc(e,n)}const Br=pt("ComponentRegistry");class pn extends Error{constructor(n,r,i){super(n);M(this,"code");M(this,"details");this.code=r,this.details=i,this.name="ComponentRegistryError"}}const Qn=class Qn{constructor(){M(this,"registry",{});M(this,"manifest",null);M(this,"cacheVersion",0);M(this,"loadingState","idle");M(this,"error",null);M(this,"templateId",null);M(this,"templateType",null)}static getInstance(){return Qn.instance||(Qn.instance=new Qn),Qn.instance}static createIsolatedInstance(){return new Qn}static resetInstance(){Qn.instance=null}async loadComponents(e,n,r=0){if(this.loadingState==="loading")throw new pn("Components are already being loaded","LOADING_IN_PROGRESS");if(this.loadingState==="loaded"&&this.templateId===e&&this.templateType===n){Br.log("Components already loaded for template:",e,n);return}this.loadingState="loading",this.templateId=e,this.templateType=n,this.cacheVersion=r,this.error=null;try{await this.loadManifest(),await this.loadComponentBundle(),this.loadingState="loaded",Br.log("Successfully loaded components:",Object.keys(this.registry).length)}catch(i){throw this.loadingState="error",this.error=i instanceof Error?i:new Error(String(i)),new pn(`Failed to load components: ${this.error.message}`,"LOAD_FAILED",{originalError:this.error})}}async loadManifest(){try{if(!this.templateId)throw new pn("Template ID not set","TEMPLATE_ID_NOT_SET");const e=`${this.templateId}:${this.templateType}:v${this.cacheVersion||0}`;if(Qn.manifestCache.has(e)){this.manifest=Qn.manifestCache.get(e),Br.log("Manifest loaded from cache:",this.manifest.templateId);return}const n=Ur(`/api/templates/${this.templateId}/components`,"json",this.cacheVersion>0?this.cacheVersion:null),r=await rc(this.cacheVersion>0?go(`templates/${this.templateId}/components.json`,this.cacheVersion):null,n,{label:"components.json"});if(!r.ok)throw new pn(`Failed to fetch manifest: ${r.status} ${r.statusText}`,"MANIFEST_FETCH_FAILED",{status:r.status,statusText:r.statusText});const i=await r.json();this.validateManifest(i),this.manifest=i,Qn.manifestCache.set(e,i),Br.log("Manifest loaded and cached:",i.templateId)}catch(e){throw e instanceof pn?e:new pn("Failed to load component manifest","MANIFEST_LOAD_FAILED",{originalError:e})}}validateManifest(e){if(!e.version)throw new pn("Manifest missing required field: version","MANIFEST_INVALID",{field:"version"});if(!e.templateId)throw new pn("Manifest missing required field: templateId","MANIFEST_INVALID",{field:"templateId"});if(!e.components||typeof e.components!="object")throw new pn("Manifest missing required field: components","MANIFEST_INVALID",{field:"components"});const n=["basic","composite","layout"];for(const r of n){if(!Array.isArray(e.components[r]))throw new pn(`Manifest field 'components.${r}' must be an array`,"MANIFEST_INVALID",{field:`components.${r}`,value:e.components[r]});e.components[r].forEach((i,l)=>{if(!i.name||typeof i.name!="string")throw new pn(`Invalid component metadata at ${r}[${l}]: missing or invalid 'name'`,"MANIFEST_INVALID",{type:r,index:l,metadata:i});if(!i.type||typeof i.type!="string")throw new pn(`Invalid component metadata at ${r}[${l}]: missing or invalid 'type'`,"MANIFEST_INVALID",{type:r,index:l,metadata:i})})}Br.log("Manifest validation passed")}async loadComponentBundle(){try{if(!this.templateId)throw new pn("Template ID not set","TEMPLATE_ID_NOT_SET");const e=this.getGlobalVariableName(),n=window[e];if(!n||typeof n!="object")throw new pn(`Component bundle not loaded. Expected global variable: ${e}. Ensure admin.blade.php includes the IIFE bundle script.`,"BUNDLE_NOT_LOADED",{expectedVariable:e});await this.registerComponentsFromManifest(n),Br.log("Component bundle loaded from global variable:",e)}catch(e){throw e instanceof pn?e:new pn("Failed to load component bundle","BUNDLE_LOAD_FAILED",{originalError:e})}}getGlobalVariableName(){if(!this.templateId)throw new pn("Template ID not set","TEMPLATE_ID_NOT_SET");return this.templateId.split(/[-_]/).map(e=>e.charAt(0).toUpperCase()+e.slice(1).toLowerCase()).join("")}async registerComponentsFromManifest(e){if(!this.manifest)throw new pn("Manifest not loaded","MANIFEST_NOT_LOADED");const n=["basic","composite","layout"];for(const r of n){const i=this.manifest.components[r]||[];for(const l of i){const c=l.name,d=e[c];if(!d){Br.warn(`Component '${c}' not found in bundle`);continue}this.registerComponent(c,d,l)}}}registerComponent(e,n,r){this.registry[e]&&Br.warn(`Component '${e}' already registered, overwriting`);const i=Xe.memo(n);this.registry[e]={component:i,metadata:r},Br.log(`[ComponentRegistry] Registered component: ${e} (${r.type})`)}getComponent(e){const n=this.registry[e];return n?n.component:null}getMetadata(e){const n=this.registry[e];return n?n.metadata:null}hasComponent(e){return e in this.registry}getComponentsByType(e){return Object.entries(this.registry).filter(([n,r])=>r.metadata.type===e).map(([n,r])=>n)}getAllComponents(){return Object.keys(this.registry)}getComponentMap(){const e={};for(const[n,r]of Object.entries(this.registry))e[n]=r.component;return e}getLoadingState(){return this.loadingState}getError(){return this.error}getTemplateId(){return this.templateId}getManifest(){return this.manifest}clear(){this.registry={},this.manifest=null,this.loadingState="idle",this.error=null,this.templateId=null,this.templateType=null,Br.log("Registry cleared")}};M(Qn,"instance",null),M(Qn,"manifestCache",new Map);let Er=Qn;const Xg=pt("TranslationEngine");class Gd extends Error{constructor(n,r,i){super(n);M(this,"key");M(this,"locale");this.key=r,this.locale=i,this.name="TranslationError"}}const zn=class zn{constructor(e={}){M(this,"cache",new Map);M(this,"translations",new Map);M(this,"options");M(this,"cacheVersion",0);this.options={defaultLocale:e.defaultLocale||"ko",fallbackLocale:e.fallbackLocale||"en",cacheTTL:e.cacheTTL||3e5}}static getInstance(e={}){return zn.instance||(zn.instance=new zn(e)),zn.instance}static resetInstance(){zn.instance=null}setCacheVersion(e){this.cacheVersion!==e&&(Xg.log("Cache version updated:",this.cacheVersion,"->",e),this.cacheVersion=e,this.cache.clear())}getCacheVersion(){return this.cacheVersion}async loadTranslations(e,n,r="/api",i=!1){const l=`${e}:${n}`;if(!i){const c=this.getFromCache(l);if(c)return this.translations.set(l,c),c}try{const c=[];i&&c.push(`_=${Date.now()}`);const d=Ur(`${r}/templates/${e}/lang/${n}`,"json",this.cacheVersion>0?this.cacheVersion:null,c.length>0?c.join("&"):void 0),f=!i&&this.cacheVersion>0?go(`templates/${e}/lang/${n}.json`,this.cacheVersion):null,h=f!==null?await rc(f,d,{label:`lang/${n}.json`}):await fetch(d);if(!h.ok)throw new Gd(`Failed to load translations: ${h.statusText}`,void 0,n);const y=await h.json();return this.saveToCache(l,y),this.translations.set(l,y),y}catch(c){throw new Gd(`Failed to fetch translations for ${n}: ${c instanceof Error?c.message:String(c)}`,void 0,n)}}resolveTranslations(e,n,r){let i=e;const l=5;let c=0;for(;c"="+this.translate(m,n,void 0,r)),i===d)break;c++}return i.replace(zn.TRANSLATION_PATTERN,(d,f,h)=>{const{cleanedParams:m,trailing:y}=this.separateTrailingText(h);return this.translate(f,n,m,r)+y})}translate(e,n,r,i){const l=this.getTranslation(e,n),c=r&&this.cleanParamsStr(r);if(c){const d=this.parseParams(c,i);return this.replaceParams(l,d)}return l}getTranslation(e,n){const{templateId:r,locale:i}=n,l=`${r}:${i}`,c=this.translations.get(l);if(c){const d=this.getNestedValue(c,e);if(d!==null)return d}if(i!==this.options.fallbackLocale){const d=`${r}:${this.options.fallbackLocale}`,f=this.translations.get(d);if(f){const h=this.getNestedValue(f,e);if(h!==null)return h}}return e}getNestedValue(e,n){const r=n.split(".");let i=e;for(const l of r){if(i==null||typeof i!="object")return null;i=i[l]}return typeof i=="string"?i:null}setTranslationValue(e,n,r,i){const l=`${e}:${n}`,c=this.translations.get(l)??{},d=r.split(".");let f=c;for(let h=0;h(l.push(f),`__PLACEHOLDER_${l.length-1}__`)).matchAll(zn.PARAM_PATTERN);for(const f of d){const[,h,m]=f,y=m.replace(/__PLACEHOLDER_(\d+)__/g,(v,S)=>l[parseInt(S,10)]);r[h.trim()]=this.resolveParamValue(y.trim(),n)}return r}resolveParamValue(e,n){if(e.startsWith("{{")&&e.endsWith("}}")){const r=e.slice(2,-2).trim();if(/[|&()[\]!?:+\-*/%<>=\s]/.test(r)&&n)try{const l=of.evaluateExpression(r,n);return String(l??"")}catch(l){return Xg.error("Expression evaluation failed:",r,l),""}else{const l=this.getNestedDataValue(n,r);return String(l??"")}}return e}separateTrailingText(e){if(!e)return{cleanedParams:void 0,trailing:""};if(e.trimEnd().endsWith("}}"))return{cleanedParams:e,trailing:""};if(!(e.startsWith("|")?e.slice(1):e).includes("="))return{cleanedParams:void 0,trailing:e};const r=e.match(/(\s+[^\p{L}\w=|&\s][^\p{L}\w=]*\s*)$/u);return r?{cleanedParams:e.slice(0,-r[1].length),trailing:r[1]}:{cleanedParams:e,trailing:""}}cleanParamsStr(e){return this.separateTrailingText(e).cleanedParams||""}getNestedDataValue(e,n){if(!e)return;const r=n.split(".");let i=e;for(const l of r){if(i==null)return;i=i[l]}return i}replaceParams(e,n){let r=e;for(const[i,l]of Object.entries(n)){const c=new RegExp(`\\{\\{${i}\\}\\}`,"g");r=r.replace(c,String(l));const d=new RegExp(`\\{${i}\\}`,"g");r=r.replace(d,String(l))}return r}getFromCache(e){const n=this.cache.get(e);return n?Date.now()-n.timestamp>this.options.cacheTTL?(this.cache.delete(e),null):n.data:null}saveToCache(e,n){this.cache.set(e,{data:n,timestamp:Date.now()})}clearCache(){this.cache.clear(),this.translations.clear()}pruneCache(){const e=Date.now(),n=[];for(const[r,i]of this.cache.entries())e-i.timestamp>this.options.cacheTTL&&n.push(r);for(const r of n)this.cache.delete(r)}getCacheStats(){const e=Date.now();let n=0;for(const r of this.cache.values())e-r.timestamp>this.options.cacheTTL&&n++;return{size:this.cache.size,expired:n}}};M(zn,"instance",null),M(zn,"TRANSLATION_PATTERN",/\$t:(?:defer:)?([a-zA-Z0-9._-]+)(\|(?:(?!\$t:).)+)?/g),M(zn,"NESTED_TRANSLATION_PARAM_PATTERN",/=(\$t:([a-zA-Z0-9._-]+))(?=[|&\s]|$)/g),M(zn,"PARAM_PATTERN",/([^=|&]+)=([^|&]+)/g);let Oa=zn,Fd=null;function _0(s){return Fd||(Fd=new Oa(s)),Fd}const Jg=Object.freeze(Object.defineProperty({__proto__:null,TranslationEngine:Oa,TranslationError:Gd,getTranslationEngine:_0},Symbol.toStringTag,{value:"Module"})),cs=pt("PipeRegistry");function Qg(s,e){const n=s.getFullYear(),r=String(s.getMonth()+1).padStart(2,"0"),i=String(s.getDate()).padStart(2,"0"),l=String(s.getHours()).padStart(2,"0"),c=String(s.getMinutes()).padStart(2,"0"),d=String(s.getSeconds()).padStart(2,"0");return e.replace("YYYY",String(n)).replace("YY",String(n).slice(-2)).replace("MM",r).replace("M",String(s.getMonth()+1)).replace("DD",i).replace("D",String(s.getDate())).replace("HH",l).replace("H",String(s.getHours())).replace("mm",c).replace("m",String(s.getMinutes())).replace("ss",d).replace("s",String(s.getSeconds()))}function A0(s,e="ko"){const r=new Date().getTime()-s.getTime(),i=Math.floor(r/1e3),l=Math.floor(i/60),c=Math.floor(l/60),d=Math.floor(c/24),f=Math.floor(d/7),h=Math.floor(d/30),m=Math.floor(d/365);return e==="ko"?i<60?"방금 전":l<60?`${l}분 전`:c<24?`${c}시간 전`:d<7?`${d}일 전`:f<4?`${f}주 전`:h<12?`${h}개월 전`:`${m}년 전`:i<60?"just now":l<60?`${l} minute${l===1?"":"s"} ago`:c<24?`${c} hour${c===1?"":"s"} ago`:d<7?`${d} day${d===1?"":"s"} ago`:f<4?`${f} week${f===1?"":"s"} ago`:h<12?`${h} month${h===1?"":"s"} ago`:`${m} year${m===1?"":"s"} ago`}function Vd(s){if(s==null)return null;if(s instanceof Date)return s;if(typeof s=="number")return new Date(s);if(typeof s=="string"){const e=new Date(s);return isNaN(e.getTime())?null:e}return null}const mo={date:{fn:(s,e="YYYY-MM-DD")=>{const n=Vd(s);return n?Qg(n,e):""},description:"날짜 포맷 (기본: YYYY-MM-DD)"},datetime:{fn:(s,e="YYYY-MM-DD HH:mm")=>{const n=Vd(s);return n?Qg(n,e):""},description:"날짜+시간 포맷 (기본: YYYY-MM-DD HH:mm)"},relativeTime:{fn:(s,e="ko")=>{const n=Vd(s);return n?A0(n,e):""},description:'상대 시간 표시 (예: "3분 전")'},number:{fn:(s,e)=>{const n=Number(s);if(isNaN(n))return String(s??"");const r={};return e!==void 0&&(r.minimumFractionDigits=e,r.maximumFractionDigits=e),n.toLocaleString(void 0,r)},description:"숫자 포맷 (천단위 구분, 선택적 소수점)"},truncate:{fn:(s,e=100,n="...")=>typeof s!="string"?String(s??""):s.length<=e?s:s.slice(0,e)+n,description:'문자열 자르기 (기본: 100자, 접미사: "...")'},uppercase:{fn:s=>typeof s!="string"?String(s??""):s.toUpperCase(),description:"대문자 변환"},lowercase:{fn:s=>typeof s!="string"?String(s??""):s.toLowerCase(),description:"소문자 변환"},stripHtml:{fn:s=>typeof s!="string"?String(s??""):s.replace(/<[^>]*>/g,""),description:"HTML 태그 제거"},default:{fn:(s,e="")=>s==null||s===""?e:s,description:"기본값 설정 (null, undefined, 빈문자열 시)"},fallback:{fn:(s,e)=>s??e,description:"폴백 값 설정 (null, undefined 시만)"},first:{fn:s=>{if(Array.isArray(s))return s[0]},description:"배열의 첫 번째 요소"},last:{fn:s=>{if(Array.isArray(s))return s[s.length-1]},description:"배열의 마지막 요소"},join:{fn:(s,e=", ")=>Array.isArray(s)?s.join(e):"",description:'배열을 문자열로 결합 (기본 구분자: ", ")'},length:{fn:s=>Array.isArray(s)||typeof s=="string"?s.length:0,description:"배열/문자열 길이"},filterBy:{fn:(s,e,n)=>Array.isArray(s)?Array.isArray(e)?s.filter(r=>{const i=n?r?.[n]:r;return e.includes(i)}):s:[],description:"배열 필터링 (allowList에 포함된 항목만 반환)"},keys:{fn:s=>s==null||typeof s!="object"?[]:Object.keys(s),description:"객체의 키 배열"},values:{fn:s=>s==null||typeof s!="object"?[]:Object.values(s),description:"객체의 값 배열"},json:{fn:(s,e)=>{try{return JSON.stringify(s,null,e)}catch{return""}},description:"JSON 문자열로 변환"},localized:{fn:(s,e)=>s==null?"":typeof s=="string"?s:typeof s!="object"?String(s):s[e||"ko"]||s.ko||s.en||Object.values(s)[0]||"",description:"다국어 객체에서 로케일에 맞는 값 추출"}},us=new Map,Oi=class Oi{static getInstance(){return Oi.instance||(Oi.instance=new Oi),Oi.instance}register(e,n,r){if(mo[e]){cs.warn(`[PipeRegistry] 내장 파이프를 덮어쓸 수 없습니다: ${e}`);return}us.set(e,{fn:n,description:r}),cs.log(`[PipeRegistry] 커스텀 파이프 등록됨: ${e}`)}unregister(e){if(mo[e])return cs.warn(`[PipeRegistry] 내장 파이프는 해제할 수 없습니다: ${e}`),!1;const n=us.delete(e);return n&&cs.log(`[PipeRegistry] 커스텀 파이프 해제됨: ${e}`),n}get(e){const n=mo[e];return n?n.fn:us.get(e)?.fn}has(e){return!!mo[e]||us.has(e)}execute(e,n,r=[]){const i=this.get(e);if(!i)return cs.warn(`[PipeRegistry] 알 수 없는 파이프: ${e}`),n;try{return i(n,...r)}catch(l){return cs.error(`[PipeRegistry] 파이프 실행 오류 (${e}):`,l),n}}list(){const e=[];for(const[n,r]of Object.entries(mo))e.push({name:n,description:r.description,type:"built-in"});for(const[n,r]of us.entries())e.push({name:n,description:r.description,type:"custom"});return e.sort((n,r)=>n.name.localeCompare(r.name))}clearCustomPipes(){us.clear()}};M(Oi,"instance",null);let ac=Oi;ac.getInstance();function x0(s){const e=s.trim(),n=e.indexOf("(");if(n===-1)return{name:e,args:[]};const r=e.slice(0,n).trim(),i=e.slice(n+1,-1);if(!i.trim())return{name:r,args:[]};const l=[];let c="",d=null,f=0;for(let h=0;h0?i[h-1]:"")==="\\"){c+=m;continue}if((m==='"'||m==="'")&&!d){d=m;continue}if(m===d){d=null;continue}if(d){c+=m;continue}if(m==="("||m==="["||m==="{"){f++,c+=m;continue}if(m===")"||m==="]"||m==="}"){f--,c+=m;continue}if(m===","&&f===0){l.push(Zg(c.trim())),c="";continue}c+=m}return c.trim()&&l.push(Zg(c.trim())),{name:r,args:l}}function Zg(s){if(s==="null")return null;if(s==="undefined")return;if(s==="true")return!0;if(s==="false")return!1;const e=Number(s);return!isNaN(e)&&s!==""?e:s}function T0(s){const e=[];let n="",r=null,i=0;for(let l=0;l0?s[l-1]:"",f=l0?s[r-1]:"",c=r0){r--;continue}return null}}return r===0?e.trim():null}function R0(s){const e=[];if(typeof s!="string")return e;for(let n=0;n0){i--;continue}if(s[c+1]==="}"){l=c;break}break}}l!==-1&&(e.push({start:n,end:l+2,expr:s.slice(n+2,l)}),n=l+1)}return e}function yo(s){return/[?:|&!+\-*/<>=()[\]{}]/.test(s)}function em(s){const e=typeof s=="string"?s.trim():"";return e===""?"empty":Kd.has(e)?"literal":Wn(e)?"pipe":yo(e)?"expression":"path"}function Wd(s,e,n,r){const i=typeof s=="string"?s.trim():"",l=em(i),c=r?.skipCache?{skipCache:!0}:void 0;if(l==="empty"){r?.onEmpty?.(s);return}if(l==="literal")return Kd.get(i);try{return l==="pipe"?n.evaluatePipeExpression(i,e,c,r?.trackingInfo):l==="expression"?n.evaluateExpression(i,e,r?.trackingInfo):n.resolve(i,e,c,r?.trackingInfo)}catch(d){if(r?.onError)return r.onError(d,i);throw d}}const sr="raw:";function tm(s){return s.startsWith(sr)?s.slice(sr.length):s}const bo="﷐",Si="﷑",$a="﷒";function vo(s){return bo+s+Si}function ic(s){return s.length>=2&&s.charCodeAt(0)===64976&&s.charCodeAt(s.length-1)===64977}function sc(s){return s.includes(bo)}function Yd(s){return s.slice(1,-1)}function ds(s){if(typeof s=="string")return vo(s);if(Array.isArray(s))return s.map(ds);if(s&&typeof s=="object"){const e={};for(const[n,r]of Object.entries(s))e[n]=ds(r);return e}return s}function Ma(s){if(typeof s=="string")return ic(s)?Yd(s):sc(s)||s.includes(Si)?s.split(bo).join("").split(Si).join(""):s;if(Array.isArray(s)){let e=!1;const n=s.map(r=>{const i=Ma(r);return i!==r&&(e=!0),i});return e?n:s}if(s&&typeof s=="object"){if(s.$$typeof!==void 0)return s;let e=!1;const n={};for(const[r,i]of Object.entries(s)){const l=Ma(i);l!==i&&(e=!0),n[r]=l}return e?n:s}return s}const So=new Set(["constructor","__proto__","prototype","__lookupGetter__","__lookupSetter__","__defineGetter__","__defineSetter__"]),D0=new Set(["Function","eval","globalThis","window","self","document","require","module","process","Reflect","Proxy","WebAssembly","import","constructor"]);function nm(s,e){if(s!==null&&(typeof s=="object"||typeof s=="function")&&rm.has(s))throw new Error(`Object.${e} on a built-in global object is not allowed`);return s}const O0=Object.freeze({keys:Object.keys,values:Object.values,entries:Object.entries,assign:(s,...e)=>{const n=nm(s,"assign");for(const r of e)if(r!=null)for(const i of Object.keys(r)){if(So.has(i))throw new Error(`Access to "${i}" is forbidden`);rf(n,i,r[i])}return n},freeze:s=>Object.freeze(nm(s,"freeze")),fromEntries:Object.fromEntries,create:Object.create,isFrozen:Object.isFrozen}),Xd={Math,JSON,Date,Array,Object:O0,Number,String,Boolean,Set,Map,WeakSet,WeakMap,parseInt,parseFloat,isNaN,isFinite},Jd={Date,Set,Map,WeakSet,WeakMap,Array,Number,String,Boolean,Object},rm=new Set([...Object.values(Xd),...Object.values(Jd)].filter(s=>s!==null&&(typeof s=="object"||typeof s=="function"))),oc=new Set(["function"]),_r=Symbol("short-circuit");class Qd{constructor(e){M(this,"value");this.value=e}}class Zd{}class ef{}function L0(s){return/[A-Za-z_$]/.test(s)}function $0(s){return/[A-Za-z0-9_$]/.test(s)}function wi(s){return s>="0"&&s<="9"}function M0(s,e,n){let r=e+1;for(;r0;){const h=s[n];if(h==="{")f++,n++;else if(h==="}"){if(f--,f===0)break;n++}else h==='"'||h==="'"?n=M0(s,n,h):h==="`"?n=am(s,n).end:n++}if(f!==0)throw new Error("Unterminated ${...} in template literal");i.push(s.slice(d,n)),n++;continue}l+=c,n++}throw new Error(`Unterminated template literal at position ${e}`)}function N0(s){const e=[];let n=0;const r=s.length,i=l=>n+l>>=0,t===0?32:31-(V(t)/ie|0)|0}var Z=256,ve=262144,pe=4194304;function Ee(t){var a=t&42;if(a!==0)return a;switch(t&-t){case 1:return 1;case 2:return 2;case 4:return 4;case 8:return 8;case 16:return 16;case 32:return 32;case 64:return 64;case 128:return 128;case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:return t&261888;case 262144:case 524288:case 1048576:case 2097152:return t&3932160;case 4194304:case 8388608:case 16777216:case 33554432:return t&62914560;case 67108864:return 67108864;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 0;default:return t}}function xe(t,a,o){var u=t.pendingLanes;if(u===0)return 0;var p=0,g=t.suspendedLanes,w=t.pingedLanes;t=t.warmLanes;var R=u&134217727;return R!==0?(u=R&~g,u!==0?p=Ee(u):(w&=R,w!==0?p=Ee(w):o||(o=R&~t,o!==0&&(p=Ee(o))))):(R=u&~g,R!==0?p=Ee(R):w!==0?p=Ee(w):o||(o=u&~t,o!==0&&(p=Ee(o)))),p===0?0:a!==0&&a!==p&&(a&g)===0&&(g=p&-p,o=a&-a,g>=o||g===32&&(o&4194048)!==0)?a:p}function Ke(t,a){return(t.pendingLanes&~(t.suspendedLanes&~t.pingedLanes)&a)===0}function ke(t,a){switch(t){case 1:case 2:case 4:case 8:case 64:return a+250;case 16:case 32:case 128:case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return a+5e3;case 4194304:case 8388608:case 16777216:case 33554432:return-1;case 67108864:case 134217728:case 268435456:case 536870912:case 1073741824:return-1;default:return-1}}function Me(){var t=pe;return pe<<=1,(pe&62914560)===0&&(pe=4194304),t}function Re(t){for(var a=[],o=0;31>o;o++)a.push(t);return a}function tt(t,a){t.pendingLanes|=a,a!==268435456&&(t.suspendedLanes=0,t.pingedLanes=0,t.warmLanes=0)}function Pe(t,a,o,u,p,g){var w=t.pendingLanes;t.pendingLanes=o,t.suspendedLanes=0,t.pingedLanes=0,t.warmLanes=0,t.expiredLanes&=o,t.entangledLanes&=o,t.errorRecoveryDisabledLanes&=o,t.shellSuspendCounter=0;var R=t.entanglements,P=t.expirationTimes,X=t.hiddenUpdates;for(o=w&~o;0"u")return null;try{return t.activeElement||t.body}catch{return t.body}}var vh=/[\n"\\]/g;function qn(t){return t.replace(vh,function(a){return"\\"+a.charCodeAt(0).toString(16)+" "})}function Os(t,a,o,u,p,g,w,R){t.name="",w!=null&&typeof w!="function"&&typeof w!="symbol"&&typeof w!="boolean"?t.type=w:t.removeAttribute("type"),a!=null?w==="number"?(a===0&&t.value===""||t.value!=a)&&(t.value=""+On(a)):t.value!==""+On(a)&&(t.value=""+On(a)):w!=="submit"&&w!=="reset"||t.removeAttribute("value"),a!=null?ha(t,w,On(a)):o!=null?ha(t,w,On(o)):u!=null&&t.removeAttribute("value"),p==null&&g!=null&&(t.defaultChecked=!!g),p!=null&&(t.checked=p&&typeof p!="function"&&typeof p!="symbol"),R!=null&&typeof R!="function"&&typeof R!="symbol"&&typeof R!="boolean"?t.name=""+On(R):t.removeAttribute("name")}function ji(t,a,o,u,p,g,w,R){if(g!=null&&typeof g!="function"&&typeof g!="symbol"&&typeof g!="boolean"&&(t.type=g),a!=null||o!=null){if(!(g!=="submit"&&g!=="reset"||a!=null)){al(t);return}o=o!=null?""+On(o):"",a=a!=null?""+On(a):o,R||a===t.value||(t.value=a),t.defaultValue=a}u=u??p,u=typeof u!="function"&&typeof u!="symbol"&&!!u,t.checked=R?t.checked:!!u,t.defaultChecked=!!u,w!=null&&typeof w!="function"&&typeof w!="symbol"&&typeof w!="boolean"&&(t.name=w),al(t)}function ha(t,a,o){a==="number"&&Fa(t.ownerDocument)===t||t.defaultValue===""+o||(t.defaultValue=""+o)}function Va(t,a,o,u){if(t=t.options,a){a={};for(var p=0;p"u"||typeof window.document>"u"||typeof window.document.createElement>"u"),cl=!1;if(Ir)try{var zi={};Object.defineProperty(zi,"passive",{get:function(){cl=!0}}),window.addEventListener("test",zi,zi),window.removeEventListener("test",zi,zi)}catch{cl=!1}var Jr=null,ul=null,$s=null;function cu(){if($s)return $s;var t,a=ul,o=a.length,u,p="value"in Jr?Jr.value:Jr.textContent,g=p.length;for(t=0;t=ne),We=" ",ft=!1;function vt(t,a){switch(t){case"keyup":return $.indexOf(a.keyCode)!==-1;case"keydown":return a.keyCode!==229;case"keypress":case"mousedown":case"focusout":return!0;default:return!1}}function Vt(t){return t=t.detail,typeof t=="object"&&"data"in t?t.data:null}var Hr=!1;function jh(t,a){switch(t){case"compositionend":return Vt(a);case"keypress":return a.which!==32?null:(ft=!0,We);case"textInput":return t=a.data,t===We&&ft?null:t;default:return null}}function mu(t,a){if(Hr)return t==="compositionend"||!j&&vt(t,a)?(t=cu(),$s=ul=Jr=null,Hr=!1,t):null;switch(t){case"paste":return null;case"keypress":if(!(a.ctrlKey||a.altKey||a.metaKey)||a.ctrlKey&&a.altKey){if(a.char&&1=a)return{node:o,offset:a-t};t=u}e:{for(;o;){if(o.nextSibling){o=o.nextSibling;break e}o=o.parentNode}o=void 0}o=qb(o)}}function Fb(t,a){return t&&a?t===a?!0:t&&t.nodeType===3?!1:a&&a.nodeType===3?Fb(t,a.parentNode):"contains"in t?t.contains(a):t.compareDocumentPosition?!!(t.compareDocumentPosition(a)&16):!1:!1}function Vb(t){t=t!=null&&t.ownerDocument!=null&&t.ownerDocument.defaultView!=null?t.ownerDocument.defaultView:window;for(var a=Fa(t.document);a instanceof t.HTMLIFrameElement;){try{var o=typeof a.contentWindow.location.href=="string"}catch{o=!1}if(o)t=a.contentWindow;else break;a=Fa(t.document)}return a}function Bh(t){var a=t&&t.nodeName&&t.nodeName.toLowerCase();return a&&(a==="input"&&(t.type==="text"||t.type==="search"||t.type==="tel"||t.type==="url"||t.type==="password")||a==="textarea"||t.contentEditable==="true")}var ek=Ir&&"documentMode"in document&&11>=document.documentMode,js=null,qh=null,Sl=null,Gh=!1;function Kb(t,a,o){var u=o.window===o?o.document:o.nodeType===9?o:o.ownerDocument;Gh||js==null||js!==Fa(u)||(u=js,"selectionStart"in u&&Bh(u)?u={start:u.selectionStart,end:u.selectionEnd}:(u=(u.ownerDocument&&u.ownerDocument.defaultView||window).getSelection(),u={anchorNode:u.anchorNode,anchorOffset:u.anchorOffset,focusNode:u.focusNode,focusOffset:u.focusOffset}),Sl&&vl(Sl,u)||(Sl=u,u=ld(qh,"onSelect"),0>=w,p-=w,Qr=1<<32-dn(a)+p|o<Ze?(ut=Ne,Ne=null):ut=Ne.sibling;var wt=J(K,Ne,Y[Ze],oe);if(wt===null){Ne===null&&(Ne=ut);break}t&&Ne&&wt.alternate===null&&a(K,Ne),F=g(wt,F,Ze),St===null?Be=wt:St.sibling=wt,St=wt,Ne=ut}if(Ze===Y.length)return o(K,Ne),ht&&ya(K,Ze),Be;if(Ne===null){for(;ZeZe?(ut=Ne,Ne=null):ut=Ne.sibling;var bi=J(K,Ne,wt.value,oe);if(bi===null){Ne===null&&(Ne=ut);break}t&&Ne&&bi.alternate===null&&a(K,Ne),F=g(bi,F,Ze),St===null?Be=bi:St.sibling=bi,St=bi,Ne=ut}if(wt.done)return o(K,Ne),ht&&ya(K,Ze),Be;if(Ne===null){for(;!wt.done;Ze++,wt=Y.next())wt=le(K,wt.value,oe),wt!==null&&(F=g(wt,F,Ze),St===null?Be=wt:St.sibling=wt,St=wt);return ht&&ya(K,Ze),Be}for(Ne=u(Ne);!wt.done;Ze++,wt=Y.next())wt=te(Ne,K,Ze,wt.value,oe),wt!==null&&(t&&wt.alternate!==null&&Ne.delete(wt.key===null?Ze:wt.key),F=g(wt,F,Ze),St===null?Be=wt:St.sibling=wt,St=wt);return t&&Ne.forEach(function(SR){return a(K,SR)}),ht&&ya(K,Ze),Be}function Nt(K,F,Y,oe){if(typeof Y=="object"&&Y!==null&&Y.type===x&&Y.key===null&&(Y=Y.props.children),typeof Y=="object"&&Y!==null){switch(Y.$$typeof){case S:e:{for(var Be=Y.key;F!==null;){if(F.key===Be){if(Be=Y.type,Be===x){if(F.tag===7){o(K,F.sibling),oe=p(F,Y.props.children),oe.return=K,K=oe;break e}}else if(F.elementType===Be||typeof Be=="object"&&Be!==null&&Be.$$typeof===W&&ts(Be)===F.type){o(K,F.sibling),oe=p(F,Y.props),xl(oe,Y),oe.return=K,K=oe;break e}o(K,F);break}else a(K,F);F=F.sibling}Y.type===x?(oe=Xi(Y.props.children,K.mode,oe,Y.key),oe.return=K,K=oe):(oe=Au(Y.type,Y.key,Y.props,null,K.mode,oe),xl(oe,Y),oe.return=K,K=oe)}return w(K);case _:e:{for(Be=Y.key;F!==null;){if(F.key===Be)if(F.tag===4&&F.stateNode.containerInfo===Y.containerInfo&&F.stateNode.implementation===Y.implementation){o(K,F.sibling),oe=p(F,Y.children||[]),oe.return=K,K=oe;break e}else{o(K,F);break}else a(K,F);F=F.sibling}oe=Jh(Y,K.mode,oe),oe.return=K,K=oe}return w(K);case W:return Y=ts(Y),Nt(K,F,Y,oe)}if(Et(Y))return Oe(K,F,Y,oe);if(je(Y)){if(Be=je(Y),typeof Be!="function")throw Error(r(150));return Y=Be.call(Y),Ge(K,F,Y,oe)}if(typeof Y.then=="function")return Nt(K,F,Lu(Y),oe);if(Y.$$typeof===k)return Nt(K,F,ku(K,Y),oe);$u(K,Y)}return typeof Y=="string"&&Y!==""||typeof Y=="number"||typeof Y=="bigint"?(Y=""+Y,F!==null&&F.tag===6?(o(K,F.sibling),oe=p(F,Y),oe.return=K,K=oe):(o(K,F),oe=Xh(Y,K.mode,oe),oe.return=K,K=oe),w(K)):o(K,F)}return function(K,F,Y,oe){try{Al=0;var Be=Nt(K,F,Y,oe);return Ws=null,Be}catch(Ne){if(Ne===Ks||Ne===Du)throw Ne;var St=nr(29,Ne,null,K.mode);return St.lanes=oe,St.return=K,St}finally{}}}var rs=mv(!0),yv=mv(!1),ti=!1;function cp(t){t.updateQueue={baseState:t.memoizedState,firstBaseUpdate:null,lastBaseUpdate:null,shared:{pending:null,lanes:0,hiddenCallbacks:null},callbacks:null}}function up(t,a){t=t.updateQueue,a.updateQueue===t&&(a.updateQueue={baseState:t.baseState,firstBaseUpdate:t.firstBaseUpdate,lastBaseUpdate:t.lastBaseUpdate,shared:t.shared,callbacks:null})}function ni(t){return{lane:t,tag:0,payload:null,callback:null,next:null}}function ri(t,a,o){var u=t.updateQueue;if(u===null)return null;if(u=u.shared,(_t&2)!==0){var p=u.pending;return p===null?a.next=a:(a.next=p.next,p.next=a),u.pending=a,a=_u(t),ev(t,null,o),a}return Eu(t,u,a,o),_u(t)}function Tl(t,a,o){if(a=a.updateQueue,a!==null&&(a=a.shared,(o&4194048)!==0)){var u=a.lanes;u&=t.pendingLanes,o|=u,a.lanes=o,Ce(t,o)}}function dp(t,a){var o=t.updateQueue,u=t.alternate;if(u!==null&&(u=u.updateQueue,o===u)){var p=null,g=null;if(o=o.firstBaseUpdate,o!==null){do{var w={lane:o.lane,tag:o.tag,payload:o.payload,callback:null,next:null};g===null?p=g=w:g=g.next=w,o=o.next}while(o!==null);g===null?p=g=a:g=g.next=a}else p=g=a;o={baseState:u.baseState,firstBaseUpdate:p,lastBaseUpdate:g,shared:u.shared,callbacks:u.callbacks},t.updateQueue=o;return}t=o.lastBaseUpdate,t===null?o.firstBaseUpdate=a:t.next=a,o.lastBaseUpdate=a}var fp=!1;function kl(){if(fp){var t=Vs;if(t!==null)throw t}}function Rl(t,a,o,u){fp=!1;var p=t.updateQueue;ti=!1;var g=p.firstBaseUpdate,w=p.lastBaseUpdate,R=p.shared.pending;if(R!==null){p.shared.pending=null;var P=R,X=P.next;P.next=null,w===null?g=X:w.next=X,w=P;var re=t.alternate;re!==null&&(re=re.updateQueue,R=re.lastBaseUpdate,R!==w&&(R===null?re.firstBaseUpdate=X:R.next=X,re.lastBaseUpdate=P))}if(g!==null){var le=p.baseState;w=0,re=X=P=null,R=g;do{var J=R.lane&-536870913,te=J!==R.lane;if(te?(ct&J)===J:(u&J)===J){J!==0&&J===Fs&&(fp=!0),re!==null&&(re=re.next={lane:0,tag:R.tag,payload:R.payload,callback:null,next:null});e:{var Oe=t,Ge=R;J=a;var Nt=o;switch(Ge.tag){case 1:if(Oe=Ge.payload,typeof Oe=="function"){le=Oe.call(Nt,le,J);break e}le=Oe;break e;case 3:Oe.flags=Oe.flags&-65537|128;case 0:if(Oe=Ge.payload,J=typeof Oe=="function"?Oe.call(Nt,le,J):Oe,J==null)break e;le=y({},le,J);break e;case 2:ti=!0}}J=R.callback,J!==null&&(t.flags|=64,te&&(t.flags|=8192),te=p.callbacks,te===null?p.callbacks=[J]:te.push(J))}else te={lane:J,tag:R.tag,payload:R.payload,callback:R.callback,next:null},re===null?(X=re=te,P=le):re=re.next=te,w|=J;if(R=R.next,R===null){if(R=p.shared.pending,R===null)break;te=R,R=te.next,te.next=null,p.lastBaseUpdate=te,p.shared.pending=null}}while(!0);re===null&&(P=le),p.baseState=P,p.firstBaseUpdate=X,p.lastBaseUpdate=re,g===null&&(p.shared.lanes=0),li|=w,t.lanes=w,t.memoizedState=le}}function bv(t,a){if(typeof t!="function")throw Error(r(191,t));t.call(a)}function vv(t,a){var o=t.callbacks;if(o!==null)for(t.callbacks=null,t=0;tg?g:8;var w=Q.T,R={};Q.T=R,Op(t,!1,a,o);try{var P=p(),X=Q.S;if(X!==null&&X(R,P),P!==null&&typeof P=="object"&&typeof P.then=="function"){var re=ck(P,u);Ll(t,a,re,or(t))}else Ll(t,a,u,or(t))}catch(le){Ll(t,a,{then:function(){},status:"rejected",reason:le},or())}finally{ue.p=g,w!==null&&R.types!==null&&(w.types=R.types),Q.T=w}}function gk(){}function Rp(t,a,o,u){if(t.tag!==5)throw Error(r(476));var p=Jv(t).queue;Xv(t,p,a,De,o===null?gk:function(){return Qv(t),o(u)})}function Jv(t){var a=t.memoizedState;if(a!==null)return a;a={memoizedState:De,baseState:De,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:wa,lastRenderedState:De},next:null};var o={};return a.next={memoizedState:o,baseState:o,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:wa,lastRenderedState:o},next:null},t.memoizedState=a,t=t.alternate,t!==null&&(t.memoizedState=a),a}function Qv(t){var a=Jv(t);a.next===null&&(a=t.alternate.memoizedState),Ll(t,a.next.queue,{},or())}function Dp(){return Cn(Yl)}function Zv(){return en().memoizedState}function eS(){return en().memoizedState}function mk(t){for(var a=t.return;a!==null;){switch(a.tag){case 24:case 3:var o=or();t=ni(o);var u=ri(a,t,o);u!==null&&(Wn(u,a,o),Tl(u,a,o)),a={cache:ip()},t.payload=a;return}a=a.return}}function yk(t,a,o){var u=or();o={lane:u,revertLane:0,gesture:null,action:o,hasEagerState:!1,eagerState:null,next:null},qu(t)?nS(a,o):(o=Wh(t,a,o,u),o!==null&&(Wn(o,t,u),rS(o,a,u)))}function tS(t,a,o){var u=or();Ll(t,a,o,u)}function Ll(t,a,o,u){var p={lane:u,revertLane:0,gesture:null,action:o,hasEagerState:!1,eagerState:null,next:null};if(qu(t))nS(a,p);else{var g=t.alternate;if(t.lanes===0&&(g===null||g.lanes===0)&&(g=a.lastRenderedReducer,g!==null))try{var w=a.lastRenderedState,R=g(w,o);if(p.hasEagerState=!0,p.eagerState=R,tr(R,w))return Eu(t,a,p,0),jt===null&&Cu(),!1}catch{}finally{}if(o=Wh(t,a,p,u),o!==null)return Wn(o,t,u),rS(o,a,u),!0}return!1}function Op(t,a,o,u){if(u={lane:2,revertLane:cg(),gesture:null,action:u,hasEagerState:!1,eagerState:null,next:null},qu(t)){if(a)throw Error(r(479))}else a=Wh(t,o,u,2),a!==null&&Wn(a,t,2)}function qu(t){var a=t.alternate;return t===Qe||a!==null&&a===Qe}function nS(t,a){Xs=Iu=!0;var o=t.pending;o===null?a.next=a:(a.next=o.next,o.next=a),t.pending=a}function rS(t,a,o){if((o&4194048)!==0){var u=a.lanes;u&=t.pendingLanes,o|=u,a.lanes=o,Ce(t,o)}}var $l={readContext:Cn,use:zu,useCallback:Xt,useContext:Xt,useEffect:Xt,useImperativeHandle:Xt,useLayoutEffect:Xt,useInsertionEffect:Xt,useMemo:Xt,useReducer:Xt,useRef:Xt,useState:Xt,useDebugValue:Xt,useDeferredValue:Xt,useTransition:Xt,useSyncExternalStore:Xt,useId:Xt,useHostTransitionStatus:Xt,useFormState:Xt,useActionState:Xt,useOptimistic:Xt,useMemoCache:Xt,useCacheRefresh:Xt};$l.useEffectEvent=Xt;var aS={readContext:Cn,use:zu,useCallback:function(t,a){return In().memoizedState=[t,a===void 0?null:a],t},useContext:Cn,useEffect:Uv,useImperativeHandle:function(t,a,o){o=o!=null?o.concat([t]):null,Uu(4194308,4,Fv.bind(null,a,t),o)},useLayoutEffect:function(t,a){return Uu(4194308,4,t,a)},useInsertionEffect:function(t,a){Uu(4,2,t,a)},useMemo:function(t,a){var o=In();a=a===void 0?null:a;var u=t();if(as){Rn(!0);try{t()}finally{Rn(!1)}}return o.memoizedState=[u,a],u},useReducer:function(t,a,o){var u=In();if(o!==void 0){var p=o(a);if(as){Rn(!0);try{o(a)}finally{Rn(!1)}}}else p=a;return u.memoizedState=u.baseState=p,t={pending:null,lanes:0,dispatch:null,lastRenderedReducer:t,lastRenderedState:p},u.queue=t,t=t.dispatch=yk.bind(null,Qe,t),[u.memoizedState,t]},useRef:function(t){var a=In();return t={current:t},a.memoizedState=t},useState:function(t){t=_p(t);var a=t.queue,o=tS.bind(null,Qe,a);return a.dispatch=o,[t.memoizedState,o]},useDebugValue:Tp,useDeferredValue:function(t,a){var o=In();return kp(o,t,a)},useTransition:function(){var t=_p(!1);return t=Xv.bind(null,Qe,t.queue,!0,!1),In().memoizedState=t,[!1,t]},useSyncExternalStore:function(t,a,o){var u=Qe,p=In();if(ht){if(o===void 0)throw Error(r(407));o=o()}else{if(o=a(),jt===null)throw Error(r(349));(ct&127)!==0||Av(u,a,o)}p.memoizedState=o;var g={value:o,getSnapshot:a};return p.queue=g,Uv(Tv.bind(null,u,g,t),[t]),u.flags|=2048,Qs(9,{destroy:void 0},xv.bind(null,u,g,o,a),null),o},useId:function(){var t=In(),a=jt.identifierPrefix;if(ht){var o=Zr,u=Qr;o=(u&~(1<<32-dn(u)-1)).toString(32)+o,a="_"+a+"R_"+o,o=ju++,0<\/script>",g=g.removeChild(g.firstChild);break;case"select":g=typeof u.is=="string"?w.createElement("select",{is:u.is}):w.createElement("select"),u.multiple?g.multiple=!0:u.size&&(g.size=u.size);break;default:g=typeof u.is=="string"?w.createElement(p,{is:u.is}):w.createElement(p)}}g[Ft]=a,g[Dn]=u;e:for(w=a.child;w!==null;){if(w.tag===5||w.tag===6)g.appendChild(w.stateNode);else if(w.tag!==4&&w.tag!==27&&w.child!==null){w.child.return=w,w=w.child;continue}if(w===a)break e;for(;w.sibling===null;){if(w.return===null||w.return===a)break e;w=w.return}w.sibling.return=w.return,w=w.sibling}a.stateNode=g;e:switch(_n(g,p,u),p){case"button":case"input":case"select":case"textarea":u=!!u.autoFocus;break e;case"img":u=!0;break e;default:u=!1}u&&Ea(a)}}return Ut(a),Fp(a,a.type,t===null?null:t.memoizedProps,a.pendingProps,o),null;case 6:if(t&&a.stateNode!=null)t.memoizedProps!==u&&Ea(a);else{if(typeof u!="string"&&a.stateNode===null)throw Error(r(166));if(t=se.current,qs(a)){if(t=a.stateNode,o=a.memoizedProps,u=null,p=wn,p!==null)switch(p.tag){case 27:case 5:u=p.memoizedProps}t[Ft]=a,t=!!(t.nodeValue===o||u!==null&&u.suppressHydrationWarning===!0||Ew(t.nodeValue,o)),t||Za(a,!0)}else t=cd(t).createTextNode(u),t[Ft]=a,a.stateNode=t}return Ut(a),null;case 31:if(o=a.memoizedState,t===null||t.memoizedState!==null){if(u=qs(a),o!==null){if(t===null){if(!u)throw Error(r(318));if(t=a.memoizedState,t=t!==null?t.dehydrated:null,!t)throw Error(r(557));t[Ft]=a}else Ji(),(a.flags&128)===0&&(a.memoizedState=null),a.flags|=4;Ut(a),t=!1}else o=tp(),t!==null&&t.memoizedState!==null&&(t.memoizedState.hydrationErrors=o),t=!0;if(!t)return a.flags&256?(ar(a),a):(ar(a),null);if((a.flags&128)!==0)throw Error(r(558))}return Ut(a),null;case 13:if(u=a.memoizedState,t===null||t.memoizedState!==null&&t.memoizedState.dehydrated!==null){if(p=qs(a),u!==null&&u.dehydrated!==null){if(t===null){if(!p)throw Error(r(318));if(p=a.memoizedState,p=p!==null?p.dehydrated:null,!p)throw Error(r(317));p[Ft]=a}else Ji(),(a.flags&128)===0&&(a.memoizedState=null),a.flags|=4;Ut(a),p=!1}else p=tp(),t!==null&&t.memoizedState!==null&&(t.memoizedState.hydrationErrors=p),p=!0;if(!p)return a.flags&256?(ar(a),a):(ar(a),null)}return ar(a),(a.flags&128)!==0?(a.lanes=o,a):(o=u!==null,t=t!==null&&t.memoizedState!==null,o&&(u=a.child,p=null,u.alternate!==null&&u.alternate.memoizedState!==null&&u.alternate.memoizedState.cachePool!==null&&(p=u.alternate.memoizedState.cachePool.pool),g=null,u.memoizedState!==null&&u.memoizedState.cachePool!==null&&(g=u.memoizedState.cachePool.pool),g!==p&&(u.flags|=2048)),o!==t&&o&&(a.child.flags|=8192),Wu(a,a.updateQueue),Ut(a),null);case 4:return Te(),t===null&&hg(a.stateNode.containerInfo),Ut(a),null;case 10:return va(a.type),Ut(a),null;case 19:if(T(Zt),u=a.memoizedState,u===null)return Ut(a),null;if(p=(a.flags&128)!==0,g=u.rendering,g===null)if(p)Nl(u,!1);else{if(Jt!==0||t!==null&&(t.flags&128)!==0)for(t=a.child;t!==null;){if(g=Nu(t),g!==null){for(a.flags|=128,Nl(u,!1),t=g.updateQueue,a.updateQueue=t,Wu(a,t),a.subtreeFlags=0,t=o,o=a.child;o!==null;)tv(o,t),o=o.sibling;return fe(Zt,Zt.current&1|2),ht&&ya(a,u.treeForkCount),a.child}t=t.sibling}u.tail!==null&&Ot()>Zu&&(a.flags|=128,p=!0,Nl(u,!1),a.lanes=4194304)}else{if(!p)if(t=Nu(g),t!==null){if(a.flags|=128,p=!0,t=t.updateQueue,a.updateQueue=t,Wu(a,t),Nl(u,!0),u.tail===null&&u.tailMode==="hidden"&&!g.alternate&&!ht)return Ut(a),null}else 2*Ot()-u.renderingStartTime>Zu&&o!==536870912&&(a.flags|=128,p=!0,Nl(u,!1),a.lanes=4194304);u.isBackwards?(g.sibling=a.child,a.child=g):(t=u.last,t!==null?t.sibling=g:a.child=g,u.last=g)}return u.tail!==null?(t=u.tail,u.rendering=t,u.tail=t.sibling,u.renderingStartTime=Ot(),t.sibling=null,o=Zt.current,fe(Zt,p?o&1|2:o&1),ht&&ya(a,u.treeForkCount),t):(Ut(a),null);case 22:case 23:return ar(a),pp(),u=a.memoizedState!==null,t!==null?t.memoizedState!==null!==u&&(a.flags|=8192):u&&(a.flags|=8192),u?(o&536870912)!==0&&(a.flags&128)===0&&(Ut(a),a.subtreeFlags&6&&(a.flags|=8192)):Ut(a),o=a.updateQueue,o!==null&&Wu(a,o.retryQueue),o=null,t!==null&&t.memoizedState!==null&&t.memoizedState.cachePool!==null&&(o=t.memoizedState.cachePool.pool),u=null,a.memoizedState!==null&&a.memoizedState.cachePool!==null&&(u=a.memoizedState.cachePool.pool),u!==o&&(a.flags|=2048),t!==null&&T(es),null;case 24:return o=null,t!==null&&(o=t.memoizedState.cache),a.memoizedState.cache!==o&&(a.flags|=2048),va(rn),Ut(a),null;case 25:return null;case 30:return null}throw Error(r(156,a.tag))}function Ck(t,a){switch(Zh(a),a.tag){case 1:return t=a.flags,t&65536?(a.flags=t&-65537|128,a):null;case 3:return va(rn),Te(),t=a.flags,(t&65536)!==0&&(t&128)===0?(a.flags=t&-65537|128,a):null;case 26:case 27:case 5:return xt(a),null;case 31:if(a.memoizedState!==null){if(ar(a),a.alternate===null)throw Error(r(340));Ji()}return t=a.flags,t&65536?(a.flags=t&-65537|128,a):null;case 13:if(ar(a),t=a.memoizedState,t!==null&&t.dehydrated!==null){if(a.alternate===null)throw Error(r(340));Ji()}return t=a.flags,t&65536?(a.flags=t&-65537|128,a):null;case 19:return T(Zt),null;case 4:return Te(),null;case 10:return va(a.type),null;case 22:case 23:return ar(a),pp(),t!==null&&T(es),t=a.flags,t&65536?(a.flags=t&-65537|128,a):null;case 24:return va(rn),null;case 25:return null;default:return null}}function kS(t,a){switch(Zh(a),a.tag){case 3:va(rn),Te();break;case 26:case 27:case 5:xt(a);break;case 4:Te();break;case 31:a.memoizedState!==null&&ar(a);break;case 13:ar(a);break;case 19:T(Zt);break;case 10:va(a.type);break;case 22:case 23:ar(a),pp(),t!==null&&T(es);break;case 24:va(rn)}}function Il(t,a){try{var o=a.updateQueue,u=o!==null?o.lastEffect:null;if(u!==null){var p=u.next;o=p;do{if((o.tag&t)===t){u=void 0;var g=o.create,w=o.inst;u=g(),w.destroy=u}o=o.next}while(o!==p)}}catch(R){kt(a,a.return,R)}}function si(t,a,o){try{var u=a.updateQueue,p=u!==null?u.lastEffect:null;if(p!==null){var g=p.next;u=g;do{if((u.tag&t)===t){var w=u.inst,R=w.destroy;if(R!==void 0){w.destroy=void 0,p=a;var P=o,X=R;try{X()}catch(re){kt(p,P,re)}}}u=u.next}while(u!==g)}}catch(re){kt(a,a.return,re)}}function RS(t){var a=t.updateQueue;if(a!==null){var o=t.stateNode;try{vv(a,o)}catch(u){kt(t,t.return,u)}}}function DS(t,a,o){o.props=is(t.type,t.memoizedProps),o.state=t.memoizedState;try{o.componentWillUnmount()}catch(u){kt(t,a,u)}}function jl(t,a){try{var o=t.ref;if(o!==null){switch(t.tag){case 26:case 27:case 5:var u=t.stateNode;break;case 30:u=t.stateNode;break;default:u=t.stateNode}typeof o=="function"?t.refCleanup=o(u):o.current=u}}catch(p){kt(t,a,p)}}function ea(t,a){var o=t.ref,u=t.refCleanup;if(o!==null)if(typeof u=="function")try{u()}catch(p){kt(t,a,p)}finally{t.refCleanup=null,t=t.alternate,t!=null&&(t.refCleanup=null)}else if(typeof o=="function")try{o(null)}catch(p){kt(t,a,p)}else o.current=null}function OS(t){var a=t.type,o=t.memoizedProps,u=t.stateNode;try{e:switch(a){case"button":case"input":case"select":case"textarea":o.autoFocus&&u.focus();break e;case"img":o.src?u.src=o.src:o.srcSet&&(u.srcset=o.srcSet)}}catch(p){kt(t,t.return,p)}}function Vp(t,a,o){try{var u=t.stateNode;Gk(u,t.type,o,a),u[Dn]=a}catch(p){kt(t,t.return,p)}}function LS(t){return t.tag===5||t.tag===3||t.tag===26||t.tag===27&&hi(t.type)||t.tag===4}function Kp(t){e:for(;;){for(;t.sibling===null;){if(t.return===null||LS(t.return))return null;t=t.return}for(t.sibling.return=t.return,t=t.sibling;t.tag!==5&&t.tag!==6&&t.tag!==18;){if(t.tag===27&&hi(t.type)||t.flags&2||t.child===null||t.tag===4)continue e;t.child.return=t,t=t.child}if(!(t.flags&2))return t.stateNode}}function Wp(t,a,o){var u=t.tag;if(u===5||u===6)t=t.stateNode,a?(o.nodeType===9?o.body:o.nodeName==="HTML"?o.ownerDocument.body:o).insertBefore(t,a):(a=o.nodeType===9?o.body:o.nodeName==="HTML"?o.ownerDocument.body:o,a.appendChild(t),o=o._reactRootContainer,o!=null||a.onclick!==null||(a.onclick=Nr));else if(u!==4&&(u===27&&hi(t.type)&&(o=t.stateNode,a=null),t=t.child,t!==null))for(Wp(t,a,o),t=t.sibling;t!==null;)Wp(t,a,o),t=t.sibling}function Yu(t,a,o){var u=t.tag;if(u===5||u===6)t=t.stateNode,a?o.insertBefore(t,a):o.appendChild(t);else if(u!==4&&(u===27&&hi(t.type)&&(o=t.stateNode),t=t.child,t!==null))for(Yu(t,a,o),t=t.sibling;t!==null;)Yu(t,a,o),t=t.sibling}function $S(t){var a=t.stateNode,o=t.memoizedProps;try{for(var u=t.type,p=a.attributes;p.length;)a.removeAttributeNode(p[0]);_n(a,u,o),a[Ft]=t,a[Dn]=o}catch(g){kt(t,t.return,g)}}var _a=!1,on=!1,Yp=!1,MS=typeof WeakSet=="function"?WeakSet:Set,yn=null;function Ek(t,a){if(t=t.containerInfo,mg=md,t=Vb(t),Bh(t)){if("selectionStart"in t)var o={start:t.selectionStart,end:t.selectionEnd};else e:{o=(o=t.ownerDocument)&&o.defaultView||window;var u=o.getSelection&&o.getSelection();if(u&&u.rangeCount!==0){o=u.anchorNode;var p=u.anchorOffset,g=u.focusNode;u=u.focusOffset;try{o.nodeType,g.nodeType}catch{o=null;break e}var w=0,R=-1,P=-1,X=0,re=0,le=t,J=null;t:for(;;){for(var te;le!==o||p!==0&&le.nodeType!==3||(R=w+p),le!==g||u!==0&&le.nodeType!==3||(P=w+u),le.nodeType===3&&(w+=le.nodeValue.length),(te=le.firstChild)!==null;)J=le,le=te;for(;;){if(le===t)break t;if(J===o&&++X===p&&(R=w),J===g&&++re===u&&(P=w),(te=le.nextSibling)!==null)break;le=J,J=le.parentNode}le=te}o=R===-1||P===-1?null:{start:R,end:P}}else o=null}o=o||{start:0,end:0}}else o=null;for(yg={focusedElem:t,selectionRange:o},md=!1,yn=a;yn!==null;)if(a=yn,t=a.child,(a.subtreeFlags&1028)!==0&&t!==null)t.return=a,yn=t;else for(;yn!==null;){switch(a=yn,g=a.alternate,t=a.flags,a.tag){case 0:if((t&4)!==0&&(t=a.updateQueue,t=t!==null?t.events:null,t!==null))for(o=0;o title"))),_n(g,u,o),g[Ft]=t,nn(g),u=g;break e;case"link":var w=Pw("link","href",p).get(u+(o.href||""));if(w){for(var R=0;RNt&&(w=Nt,Nt=Ge,Ge=w);var K=Gb(R,Ge),F=Gb(R,Nt);if(K&&F&&(te.rangeCount!==1||te.anchorNode!==K.node||te.anchorOffset!==K.offset||te.focusNode!==F.node||te.focusOffset!==F.offset)){var Y=le.createRange();Y.setStart(K.node,K.offset),te.removeAllRanges(),Ge>Nt?(te.addRange(Y),te.extend(F.node,F.offset)):(Y.setEnd(F.node,F.offset),te.addRange(Y))}}}}for(le=[],te=R;te=te.parentNode;)te.nodeType===1&&le.push({element:te,left:te.scrollLeft,top:te.scrollTop});for(typeof R.focus=="function"&&R.focus(),R=0;Ro?32:o,Q.T=null,o=ng,ng=null;var g=ui,w=Ra;if(hn=0,ro=ui=null,Ra=0,(_t&6)!==0)throw Error(r(331));var R=_t;if(_t|=4,FS(g.current),BS(g,g.current,w,o),_t=R,ql(0,!1),un&&typeof un.onPostCommitFiberRoot=="function")try{un.onPostCommitFiberRoot(pr,g)}catch{}return!0}finally{ue.p=p,Q.T=u,cw(t,a)}}function dw(t,a,o){a=yr(o,a),a=Np(t.stateNode,a,2),t=ri(t,a,2),t!==null&&(tt(t,2),ta(t))}function kt(t,a,o){if(t.tag===3)dw(t,t,o);else for(;a!==null;){if(a.tag===3){dw(a,t,o);break}else if(a.tag===1){var u=a.stateNode;if(typeof a.type.getDerivedStateFromError=="function"||typeof u.componentDidCatch=="function"&&(ci===null||!ci.has(u))){t=yr(o,t),o=fS(2),u=ri(a,o,2),u!==null&&(hS(o,u,a,t),tt(u,2),ta(u));break}}a=a.return}}function sg(t,a,o){var u=t.pingCache;if(u===null){u=t.pingCache=new xk;var p=new Set;u.set(a,p)}else p=u.get(a),p===void 0&&(p=new Set,u.set(a,p));p.has(o)||(Qp=!0,p.add(o),t=Ok.bind(null,t,a,o),a.then(t,t))}function Ok(t,a,o){var u=t.pingCache;u!==null&&u.delete(a),t.pingedLanes|=t.suspendedLanes&o,t.warmLanes&=~o,jt===t&&(ct&o)===o&&(Jt===4||Jt===3&&(ct&62914560)===ct&&300>Ot()-Qu?(_t&2)===0&&ao(t,0):Zp|=o,no===ct&&(no=0)),ta(t)}function fw(t,a){a===0&&(a=Me()),t=Yi(t,a),t!==null&&(tt(t,a),ta(t))}function Lk(t){var a=t.memoizedState,o=0;a!==null&&(o=a.retryLane),fw(t,o)}function $k(t,a){var o=0;switch(t.tag){case 31:case 13:var u=t.stateNode,p=t.memoizedState;p!==null&&(o=p.retryLane);break;case 19:u=t.stateNode;break;case 22:u=t.stateNode._retryCache;break;default:throw Error(r(314))}u!==null&&u.delete(a),fw(t,o)}function Mk(t,a){return Dt(t,a)}var id=null,so=null,og=!1,sd=!1,lg=!1,fi=0;function ta(t){t!==so&&t.next===null&&(so===null?id=so=t:so=so.next=t),sd=!0,og||(og=!0,Ik())}function ql(t,a){if(!lg&&sd){lg=!0;do for(var o=!1,u=id;u!==null;){if(t!==0){var p=u.pendingLanes;if(p===0)var g=0;else{var w=u.suspendedLanes,R=u.pingedLanes;g=(1<<31-dn(42|t)+1)-1,g&=p&~(w&~R),g=g&201326741?g&201326741|1:g?g|2:0}g!==0&&(o=!0,mw(u,g))}else g=ct,g=xe(u,u===jt?g:0,u.cancelPendingCommit!==null||u.timeoutHandle!==-1),(g&3)===0||Ke(u,g)||(o=!0,mw(u,g));u=u.next}while(o);lg=!1}}function Nk(){hw()}function hw(){sd=og=!1;var t=0;fi!==0&&Vk()&&(t=fi);for(var a=Ot(),o=null,u=id;u!==null;){var p=u.next,g=pw(u,a);g===0?(u.next=null,o===null?id=p:o.next=p,p===null&&(so=o)):(o=u,(t!==0||(g&3)!==0)&&(sd=!0)),u=p}hn!==0&&hn!==5||ql(t),fi!==0&&(fi=0)}function pw(t,a){for(var o=t.suspendedLanes,u=t.pingedLanes,p=t.expirationTimes,g=t.pendingLanes&-62914561;0R)break;var re=P.transferSize,le=P.initiatorType;re&&_w(le)&&(P=P.responseEnd,w+=re*(P"u"?null:document;function Iw(t,a,o){var u=oo;if(u&&typeof a=="string"&&a){var p=qn(a);p='link[rel="'+t+'"][href="'+p+'"]',typeof o=="string"&&(p+='[crossorigin="'+o+'"]'),Nw.has(p)||(Nw.add(p),t={rel:t,crossOrigin:o,href:a},u.querySelector(p)===null&&(a=u.createElement("link"),_n(a,"link",t),nn(a),u.head.appendChild(a)))}}function tR(t){Da.D(t),Iw("dns-prefetch",t,null)}function nR(t,a){Da.C(t,a),Iw("preconnect",t,a)}function rR(t,a,o){Da.L(t,a,o);var u=oo;if(u&&t&&a){var p='link[rel="preload"][as="'+qn(a)+'"]';a==="image"&&o&&o.imageSrcSet?(p+='[imagesrcset="'+qn(o.imageSrcSet)+'"]',typeof o.imageSizes=="string"&&(p+='[imagesizes="'+qn(o.imageSizes)+'"]')):p+='[href="'+qn(t)+'"]';var g=p;switch(a){case"style":g=lo(t);break;case"script":g=co(t)}Er.has(g)||(t=y({rel:"preload",href:a==="image"&&o&&o.imageSrcSet?void 0:t,as:a},o),Er.set(g,t),u.querySelector(p)!==null||a==="style"&&u.querySelector(Kl(g))||a==="script"&&u.querySelector(Wl(g))||(a=u.createElement("link"),_n(a,"link",t),nn(a),u.head.appendChild(a)))}}function aR(t,a){Da.m(t,a);var o=oo;if(o&&t){var u=a&&typeof a.as=="string"?a.as:"script",p='link[rel="modulepreload"][as="'+qn(u)+'"][href="'+qn(t)+'"]',g=p;switch(u){case"audioworklet":case"paintworklet":case"serviceworker":case"sharedworker":case"worker":case"script":g=co(t)}if(!Er.has(g)&&(t=y({rel:"modulepreload",href:t},a),Er.set(g,t),o.querySelector(p)===null)){switch(u){case"audioworklet":case"paintworklet":case"serviceworker":case"sharedworker":case"worker":case"script":if(o.querySelector(Wl(g)))return}u=o.createElement("link"),_n(u,"link",t),nn(u),o.head.appendChild(u)}}}function iR(t,a,o){Da.S(t,a,o);var u=oo;if(u&&t){var p=qa(u).hoistableStyles,g=lo(t);a=a||"default";var w=p.get(g);if(!w){var R={loading:0,preload:null};if(w=u.querySelector(Kl(g)))R.loading=5;else{t=y({rel:"stylesheet",href:t,"data-precedence":a},o),(o=Er.get(g))&&_g(t,o);var P=w=u.createElement("link");nn(P),_n(P,"link",t),P._p=new Promise(function(X,re){P.onload=X,P.onerror=re}),P.addEventListener("load",function(){R.loading|=1}),P.addEventListener("error",function(){R.loading|=2}),R.loading|=4,dd(w,a,u)}w={type:"stylesheet",instance:w,count:1,state:R},p.set(g,w)}}}function sR(t,a){Da.X(t,a);var o=oo;if(o&&t){var u=qa(o).hoistableScripts,p=co(t),g=u.get(p);g||(g=o.querySelector(Wl(p)),g||(t=y({src:t,async:!0},a),(a=Er.get(p))&&Ag(t,a),g=o.createElement("script"),nn(g),_n(g,"link",t),o.head.appendChild(g)),g={type:"script",instance:g,count:1,state:null},u.set(p,g))}}function oR(t,a){Da.M(t,a);var o=oo;if(o&&t){var u=qa(o).hoistableScripts,p=co(t),g=u.get(p);g||(g=o.querySelector(Wl(p)),g||(t=y({src:t,async:!0,type:"module"},a),(a=Er.get(p))&&Ag(t,a),g=o.createElement("script"),nn(g),_n(g,"link",t),o.head.appendChild(g)),g={type:"script",instance:g,count:1,state:null},u.set(p,g))}}function jw(t,a,o,u){var p=(p=se.current)?ud(p):null;if(!p)throw Error(r(446));switch(t){case"meta":case"title":return null;case"style":return typeof o.precedence=="string"&&typeof o.href=="string"?(a=lo(o.href),o=qa(p).hoistableStyles,u=o.get(a),u||(u={type:"style",instance:null,count:0,state:null},o.set(a,u)),u):{type:"void",instance:null,count:0,state:null};case"link":if(o.rel==="stylesheet"&&typeof o.href=="string"&&typeof o.precedence=="string"){t=lo(o.href);var g=qa(p).hoistableStyles,w=g.get(t);if(w||(p=p.ownerDocument||p,w={type:"stylesheet",instance:null,count:0,state:{loading:0,preload:null}},g.set(t,w),(g=p.querySelector(Kl(t)))&&!g._p&&(w.instance=g,w.state.loading=5),Er.has(t)||(o={rel:"preload",as:"style",href:o.href,crossOrigin:o.crossOrigin,integrity:o.integrity,media:o.media,hrefLang:o.hrefLang,referrerPolicy:o.referrerPolicy},Er.set(t,o),g||lR(p,t,o,w.state))),a&&u===null)throw Error(r(528,""));return w}if(a&&u!==null)throw Error(r(529,""));return null;case"script":return a=o.async,o=o.src,typeof o=="string"&&a&&typeof a!="function"&&typeof a!="symbol"?(a=co(o),o=qa(p).hoistableScripts,u=o.get(a),u||(u={type:"script",instance:null,count:0,state:null},o.set(a,u)),u):{type:"void",instance:null,count:0,state:null};default:throw Error(r(444,t))}}function lo(t){return'href="'+qn(t)+'"'}function Kl(t){return'link[rel="stylesheet"]['+t+"]"}function Hw(t){return y({},t,{"data-precedence":t.precedence,precedence:null})}function lR(t,a,o,u){t.querySelector('link[rel="preload"][as="style"]['+a+"]")?u.loading=1:(a=t.createElement("link"),u.preload=a,a.addEventListener("load",function(){return u.loading|=1}),a.addEventListener("error",function(){return u.loading|=2}),_n(a,"link",o),nn(a),t.head.appendChild(a))}function co(t){return'[src="'+qn(t)+'"]'}function Wl(t){return"script[async]"+t}function zw(t,a,o){if(a.count++,a.instance===null)switch(a.type){case"style":var u=t.querySelector('style[data-href~="'+qn(o.href)+'"]');if(u)return a.instance=u,nn(u),u;var p=y({},o,{"data-href":o.href,"data-precedence":o.precedence,href:null,precedence:null});return u=(t.ownerDocument||t).createElement("style"),nn(u),_n(u,"style",p),dd(u,o.precedence,t),a.instance=u;case"stylesheet":p=lo(o.href);var g=t.querySelector(Kl(p));if(g)return a.state.loading|=4,a.instance=g,nn(g),g;u=Hw(o),(p=Er.get(p))&&_g(u,p),g=(t.ownerDocument||t).createElement("link"),nn(g);var w=g;return w._p=new Promise(function(R,P){w.onload=R,w.onerror=P}),_n(g,"link",u),a.state.loading|=4,dd(g,o.precedence,t),a.instance=g;case"script":return g=co(o.src),(p=t.querySelector(Wl(g)))?(a.instance=p,nn(p),p):(u=o,(p=Er.get(g))&&(u=y({},o),Ag(u,p)),t=t.ownerDocument||t,p=t.createElement("script"),nn(p),_n(p,"link",u),t.head.appendChild(p),a.instance=p);case"void":return null;default:throw Error(r(443,a.type))}else a.type==="stylesheet"&&(a.state.loading&4)===0&&(u=a.instance,a.state.loading|=4,dd(u,o.precedence,t));return a.instance}function dd(t,a,o){for(var u=o.querySelectorAll('link[rel="stylesheet"][data-precedence],style[data-precedence]'),p=u.length?u[u.length-1]:null,g=p,w=0;w title"):null)}function cR(t,a,o){if(o===1||a.itemProp!=null)return!1;switch(t){case"meta":case"title":return!0;case"style":if(typeof a.precedence!="string"||typeof a.href!="string"||a.href==="")break;return!0;case"link":if(typeof a.rel!="string"||typeof a.href!="string"||a.href===""||a.onLoad||a.onError)break;switch(a.rel){case"stylesheet":return t=a.disabled,typeof a.precedence=="string"&&t==null;default:return!0}case"script":if(a.async&&typeof a.async!="function"&&typeof a.async!="symbol"&&!a.onLoad&&!a.onError&&a.src&&typeof a.src=="string")return!0}return!1}function Bw(t){return!(t.type==="stylesheet"&&(t.state.loading&3)===0)}function uR(t,a,o,u){if(o.type==="stylesheet"&&(typeof u.media!="string"||matchMedia(u.media).matches!==!1)&&(o.state.loading&4)===0){if(o.instance===null){var p=lo(u.href),g=a.querySelector(Kl(p));if(g){a=g._p,a!==null&&typeof a=="object"&&typeof a.then=="function"&&(t.count++,t=hd.bind(t),a.then(t,t)),o.state.loading|=4,o.instance=g,nn(g);return}g=a.ownerDocument||a,u=Hw(u),(p=Er.get(p))&&_g(u,p),g=g.createElement("link"),nn(g);var w=g;w._p=new Promise(function(R,P){w.onload=R,w.onerror=P}),_n(g,"link",u),o.instance=g}t.stylesheets===null&&(t.stylesheets=new Map),t.stylesheets.set(o,a),(a=o.state.preload)&&(o.state.loading&3)===0&&(t.count++,o=hd.bind(t),a.addEventListener("load",o),a.addEventListener("error",o))}}var xg=0;function dR(t,a){return t.stylesheets&&t.count===0&&gd(t,t.stylesheets),0xg?50:800)+a);return t.unsuspend=o,function(){t.unsuspend=null,clearTimeout(u),clearTimeout(p)}}:null}function hd(){if(this.count--,this.count===0&&(this.imgCount===0||!this.waitingForImages)){if(this.stylesheets)gd(this,this.stylesheets);else if(this.unsuspend){var t=this.unsuspend;this.unsuspend=null,t()}}}var pd=null;function gd(t,a){t.stylesheets=null,t.unsuspend!==null&&(t.count++,pd=new Map,a.forEach(fR,t),pd=null,hd.call(t))}function fR(t,a){if(!(a.state.loading&4)){var o=pd.get(t);if(o)var u=o.get(null);else{o=new Map,pd.set(t,o);for(var p=t.querySelectorAll("link[data-precedence],style[data-precedence]"),g=0;g"u"||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!="function"))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(s)}catch(e){console.error(e)}}return s(),Ad.exports=c0(),Ad.exports}var Rd=u0();const Dd=ra(Rd),ho=[],d0=50;let Od=!1;function Bg(){try{return window.G7Core?.devTools}catch{return}}function ls(s,e,n){const r=Bg();if(Od&&r?.isEnabled?.()){r.trackLog?.(s,e,n);return}Od||(s==="error"||s==="warn")&&ho.length{n.isDebugEnabled()&&console.log(e,...r),ls("log",s,r)},warn:(...r)=>{n.isDebugEnabled()&&console.warn(e,...r),ls("warn",s,r)},error:(...r)=>{n.isDebugEnabled()&&console.error(e,...r),ls("error",s,r)}}}po.getInstance();const qg="extension",cs="extensionless",Gg="file";function f0(){const s=globalThis?.G7Config;if(globalThis?.__g7AssetUrlMode===cs)return cs;const n=s?.assetUrlMode;return n===cs||n===qg?n:s?.settings?.general?.asset_url_mode===cs?cs:qg}function $d(){return f0()===cs}function h0(s,e,n){return Md("templates",s,e,n)}function p0(s,e){const n=e.replace(/^\/+/,"").replace(/\/+$/,""),r=encodeURIComponent(s),i=go(`templates/${s}/assets/${n}`);return i!==null?i:`/api/templates/assets/${r}/${n}`}function g0(s,e,n){return Md("modules",s,e,n)}function m0(s,e,n){return Md("plugins",s,e,n)}function Md(s,e,n,r){const i=n.replace(/^\/+/,""),l=encodeURIComponent(e);if(!$d())return`/api/${s}/assets/${l}/${i}${v0(r)}`;let c=`${Gg}=${encodeURIComponent(i)}`;return r!=null&&r!==""&&(c+=`&v=${r}`),`/api/${s}/assets/${l}?${c}`}function Br(s,e,n,r){const i=s.replace(/\/+$/,""),l=e.replace(/^\.+/,""),c=$d()?i:`${i}.${l}`,d=[];return r&&d.push(r.replace(/^[?&]+/,"")),n!=null&&n!==""&&d.push(`v=${n}`),d.length>0?`${c}?${d.join("&")}`:c}function Fg(){const s=globalThis?.G7Config?.staticBase;return typeof s=="string"&&s!==""?s.replace(/\/+$/,""):null}function y0(){const s=Fg();if(!s)return null;const e=s.match(/\/(\d+)$/);return e?Number(e[1]):null}function go(s,e){const n=Fg();if(!n)return null;const r=s.replace(/^\/+/,"");return e!==void 0&&e>0&&e!==y0()?`${n.replace(/\/\d+$/,"")}/${e}/${r}`:`${n}/${r}`}function Nd(s){if(!s)return null;const e=globalThis?.location?.origin,r=(e&&s.startsWith(e)?s.slice(e.length):s).match(/^\/build\/ext\/(\d+)\/(.+)$/);if(!r)return null;const i=r[1],l=r[2].split("?")[0],c=l.match(/^(templates|modules|plugins)\/([^/]+)\/assets\/(.+)$/);if(c){const[,h,m,y]=c;return`/api/${h}/assets/${m}/${y}?v=${i}`}const d=l.match(/^bundles\/(modules|plugins)\.(js|css)$/);if(d)return`/api/${d[1]}/bundle.${d[2]}?v=${i}`;const f=l.match(/^templates\/([^/]+)\/(routes\.json|components\.json|lang\/([a-zA-Z-]+)\.json)$/);if(f){const[,h,m]=f,y=m.replace(/\.json$/,"");return`/api/templates/${h}/${y}.json?v=${i}`}return null}function Ln(s){if(!s||!$d())return s;const e=globalThis?.location?.origin,n=e&&s.startsWith(e)?s.slice(e.length):s;if(!n.startsWith("/api/"))return s;const[r,i]=b0(n),l=r.match(/^\/api\/(modules|plugins)\/bundle\.(js|css)$/);if(l)return Vg(`/api/${l[1]}/bundle/${l[2]}`,i);const c=r.match(/^\/api\/(templates|modules|plugins)\/assets\/([^/]+)\/(.+)$/);if(c){const[,f,h,m]=c,y=`${Gg}=${encodeURIComponent(decodeURIComponent(m))}`;return`/api/${f}/assets/${h}?${y}${i?`&${i}`:""}`}const d=r.match(/^(\/api\/.+)\.(json|js|css)$/);return d?Vg(d[1],i):s}function b0(s){const e=s.indexOf("?");return e===-1?[s,""]:[s.slice(0,e),s.slice(e+1)]}function Vg(s,e){return e?`${s}?${e}`:s}function v0(s){return s==null||s===""?"":`?v=${s}`}const aa=pt("networkResilience"),Id=2,jd=300,Hd=2e3,S0=15e3;function zd(s){return s?.name==="AbortError"}function Kg(s){if(zd(s))return!1;if(s instanceof TypeError)return!0;const e=s?.code;return e==="ERR_NETWORK"||e==="ECONNABORTED"}let Zl=!1,Wg=!1;function ec(){return Zl}function w0(){typeof window>"u"||Wg||(Wg=!0,window.addEventListener("pagehide",()=>{Zl=!0}),window.addEventListener("pageshow",s=>{s.persisted&&(Zl=!1)}),window.addEventListener("beforeunload",()=>{Zl=!0}))}function Pd(s,e,n){const r=Math.min(e*Math.pow(2,s),n),i=r*.25*(Math.random()*2-1);return Math.max(0,Math.round(r+i))}function Ud(s){return new Promise(e=>setTimeout(e,s))}async function tc(s,e={}){const{retries:n=Id,baseDelayMs:r=jd,maxDelayMs:i=Hd,timeoutMs:l=S0,label:c=s,init:d}=e,f=n+1;let h;for(let m=0;m0?new AbortController:null;try{const _={...d};if(S){const A=d?.signal;A&&(A.aborted?S.abort():A.addEventListener("abort",()=>S.abort(),{once:!0})),_.signal=S.signal,v=setTimeout(()=>{y=!0,S.abort()},l)}return await fetch(s,_)}catch(_){if(h=_,zd(_)&&!y||!(y||Kg(_)))throw _;if(ec())throw aa.warn(`Document unloading, aborting retries: ${c}`),_;if(m===f-1)throw aa.warn(`All ${f} attempts failed: ${c}`,_),_;const D=Pd(m,r,i);aa.warn(`Network failure (attempt ${m+1}/${f}), retrying in ${D}ms: ${c}`),await Ud(D)}finally{v!==void 0&&clearTimeout(v)}}throw h}async function _r(s,e={},n={}){const{retries:r=Id,baseDelayMs:i=jd,maxDelayMs:l=Hd,label:c=s}=n,d=r+1;for(let f=0;f{const i=document.createElement("script");i.src=s,i.async=!1;for(const[l,c]of Object.entries(e))l==="id"?i.id=c:i.setAttribute(l,c);i.onload=()=>n(),i.onerror=()=>r(new Error(`Failed to load script: ${s}`)),document.head.appendChild(i)})}async function ia(s,e={},n={}){const{retries:r=Id,baseDelayMs:i=jd,maxDelayMs:l=Hd,label:c=s}=n,d=r+1;for(let f=0;f{s.onload=()=>e(),s.onerror=()=>n(new Error(`Failed to load stylesheet: ${s.href}`))})}async function nc(s,e,n={}){if(!s)return tc(e,n);try{const r=await fetch(s,n.init);if(r.ok){const i=await r.text();try{return JSON.parse(i),new Response(i,{status:r.status,statusText:r.statusText,headers:r.headers})}catch{return console.warn(`[fetchStaticFirst] Static fast path returned malformed JSON — falling back to API: ${s}`),tc(e,n)}}console.warn(`[fetchStaticFirst] Static fast path miss (${r.status}) — falling back to API: ${s}`)}catch(r){console.warn(`[fetchStaticFirst] Static fast path fetch failed — falling back to API: ${s}`,r)}return tc(e,n)}const qr=pt("ComponentRegistry");class pn extends Error{constructor(e,n,r){super(e),this.code=n,this.details=r,this.name="ComponentRegistryError"}}const er=class er{constructor(){M(this,"registry",{});M(this,"manifest",null);M(this,"cacheVersion",0);M(this,"loadingState","idle");M(this,"error",null);M(this,"templateId",null);M(this,"templateType",null)}static getInstance(){return er.instance||(er.instance=new er),er.instance}static createIsolatedInstance(){return new er}static resetInstance(){er.instance=null}async loadComponents(e,n,r=0){if(this.loadingState==="loading")throw new pn("Components are already being loaded","LOADING_IN_PROGRESS");if(this.loadingState==="loaded"&&this.templateId===e&&this.templateType===n){qr.log("Components already loaded for template:",e,n);return}this.loadingState="loading",this.templateId=e,this.templateType=n,this.cacheVersion=r,this.error=null;try{await this.loadManifest(),await this.loadComponentBundle(),this.loadingState="loaded",qr.log("Successfully loaded components:",Object.keys(this.registry).length)}catch(i){throw this.loadingState="error",this.error=i instanceof Error?i:new Error(String(i)),new pn(`Failed to load components: ${this.error.message}`,"LOAD_FAILED",{originalError:this.error})}}async loadManifest(){try{if(!this.templateId)throw new pn("Template ID not set","TEMPLATE_ID_NOT_SET");const e=`${this.templateId}:${this.templateType}:v${this.cacheVersion||0}`;if(er.manifestCache.has(e)){this.manifest=er.manifestCache.get(e),qr.log("Manifest loaded from cache:",this.manifest.templateId);return}const n=Br(`/api/templates/${this.templateId}/components`,"json",this.cacheVersion>0?this.cacheVersion:null),r=await nc(this.cacheVersion>0?go(`templates/${this.templateId}/components.json`,this.cacheVersion):null,n,{label:"components.json"});if(!r.ok)throw new pn(`Failed to fetch manifest: ${r.status} ${r.statusText}`,"MANIFEST_FETCH_FAILED",{status:r.status,statusText:r.statusText});const i=await r.json();this.validateManifest(i),this.manifest=i,er.manifestCache.set(e,i),qr.log("Manifest loaded and cached:",i.templateId)}catch(e){throw e instanceof pn?e:new pn("Failed to load component manifest","MANIFEST_LOAD_FAILED",{originalError:e})}}validateManifest(e){if(!e.version)throw new pn("Manifest missing required field: version","MANIFEST_INVALID",{field:"version"});if(!e.templateId)throw new pn("Manifest missing required field: templateId","MANIFEST_INVALID",{field:"templateId"});if(!e.components||typeof e.components!="object")throw new pn("Manifest missing required field: components","MANIFEST_INVALID",{field:"components"});const n=["basic","composite","layout"];for(const r of n){if(!Array.isArray(e.components[r]))throw new pn(`Manifest field 'components.${r}' must be an array`,"MANIFEST_INVALID",{field:`components.${r}`,value:e.components[r]});e.components[r].forEach((i,l)=>{if(!i.name||typeof i.name!="string")throw new pn(`Invalid component metadata at ${r}[${l}]: missing or invalid 'name'`,"MANIFEST_INVALID",{type:r,index:l,metadata:i});if(!i.type||typeof i.type!="string")throw new pn(`Invalid component metadata at ${r}[${l}]: missing or invalid 'type'`,"MANIFEST_INVALID",{type:r,index:l,metadata:i})})}qr.log("Manifest validation passed")}async loadComponentBundle(){try{if(!this.templateId)throw new pn("Template ID not set","TEMPLATE_ID_NOT_SET");const e=this.getGlobalVariableName(),n=window[e];if(!n||typeof n!="object")throw new pn(`Component bundle not loaded. Expected global variable: ${e}. Ensure admin.blade.php includes the IIFE bundle script.`,"BUNDLE_NOT_LOADED",{expectedVariable:e});await this.registerComponentsFromManifest(n),qr.log("Component bundle loaded from global variable:",e)}catch(e){throw e instanceof pn?e:new pn("Failed to load component bundle","BUNDLE_LOAD_FAILED",{originalError:e})}}getGlobalVariableName(){if(!this.templateId)throw new pn("Template ID not set","TEMPLATE_ID_NOT_SET");return this.templateId.split(/[-_]/).map(e=>e.charAt(0).toUpperCase()+e.slice(1).toLowerCase()).join("")}async registerComponentsFromManifest(e){if(!this.manifest)throw new pn("Manifest not loaded","MANIFEST_NOT_LOADED");const n=["basic","composite","layout"];for(const r of n){const i=this.manifest.components[r]||[];for(const l of i){const c=l.name,d=e[c];if(!d){qr.warn(`Component '${c}' not found in bundle`);continue}this.registerComponent(c,d,l)}}}registerComponent(e,n,r){this.registry[e]&&qr.warn(`Component '${e}' already registered, overwriting`);const i=Xe.memo(n);this.registry[e]={component:i,metadata:r},qr.log(`[ComponentRegistry] Registered component: ${e} (${r.type})`)}getComponent(e){const n=this.registry[e];return n?n.component:null}getMetadata(e){const n=this.registry[e];return n?n.metadata:null}hasComponent(e){return e in this.registry}getComponentsByType(e){return Object.entries(this.registry).filter(([n,r])=>r.metadata.type===e).map(([n,r])=>n)}getAllComponents(){return Object.keys(this.registry)}getComponentMap(){const e={};for(const[n,r]of Object.entries(this.registry))e[n]=r.component;return e}getLoadingState(){return this.loadingState}getError(){return this.error}getTemplateId(){return this.templateId}getManifest(){return this.manifest}clear(){this.registry={},this.manifest=null,this.loadingState="idle",this.error=null,this.templateId=null,this.templateType=null,qr.log("Registry cleared")}};M(er,"instance",null),M(er,"manifestCache",new Map);let Ar=er;const us=pt("PipeRegistry");function Yg(s,e){const n=s.getFullYear(),r=String(s.getMonth()+1).padStart(2,"0"),i=String(s.getDate()).padStart(2,"0"),l=String(s.getHours()).padStart(2,"0"),c=String(s.getMinutes()).padStart(2,"0"),d=String(s.getSeconds()).padStart(2,"0");return e.replace("YYYY",String(n)).replace("YY",String(n).slice(-2)).replace("MM",r).replace("M",String(s.getMonth()+1)).replace("DD",i).replace("D",String(s.getDate())).replace("HH",l).replace("H",String(s.getHours())).replace("mm",c).replace("m",String(s.getMinutes())).replace("ss",d).replace("s",String(s.getSeconds()))}function A0(s,e="ko"){const r=new Date().getTime()-s.getTime(),i=Math.floor(r/1e3),l=Math.floor(i/60),c=Math.floor(l/60),d=Math.floor(c/24),f=Math.floor(d/7),h=Math.floor(d/30),m=Math.floor(d/365);return e==="ko"?i<60?"방금 전":l<60?`${l}분 전`:c<24?`${c}시간 전`:d<7?`${d}일 전`:f<4?`${f}주 전`:h<12?`${h}개월 전`:`${m}년 전`:i<60?"just now":l<60?`${l} minute${l===1?"":"s"} ago`:c<24?`${c} hour${c===1?"":"s"} ago`:d<7?`${d} day${d===1?"":"s"} ago`:f<4?`${f} week${f===1?"":"s"} ago`:h<12?`${h} month${h===1?"":"s"} ago`:`${m} year${m===1?"":"s"} ago`}function Bd(s){if(s==null)return null;if(s instanceof Date)return s;if(typeof s=="number")return new Date(s);if(typeof s=="string"){const e=new Date(s);return isNaN(e.getTime())?null:e}return null}const mo={date:{fn:(s,e="YYYY-MM-DD")=>{const n=Bd(s);return n?Yg(n,e):""},description:"날짜 포맷 (기본: YYYY-MM-DD)"},datetime:{fn:(s,e="YYYY-MM-DD HH:mm")=>{const n=Bd(s);return n?Yg(n,e):""},description:"날짜+시간 포맷 (기본: YYYY-MM-DD HH:mm)"},relativeTime:{fn:(s,e="ko")=>{const n=Bd(s);return n?A0(n,e):""},description:'상대 시간 표시 (예: "3분 전")'},number:{fn:(s,e)=>{const n=Number(s);if(isNaN(n))return String(s??"");const r={};return e!==void 0&&(r.minimumFractionDigits=e,r.maximumFractionDigits=e),n.toLocaleString(void 0,r)},description:"숫자 포맷 (천단위 구분, 선택적 소수점)"},truncate:{fn:(s,e=100,n="...")=>typeof s!="string"?String(s??""):s.length<=e?s:s.slice(0,e)+n,description:'문자열 자르기 (기본: 100자, 접미사: "...")'},uppercase:{fn:s=>typeof s!="string"?String(s??""):s.toUpperCase(),description:"대문자 변환"},lowercase:{fn:s=>typeof s!="string"?String(s??""):s.toLowerCase(),description:"소문자 변환"},stripHtml:{fn:s=>typeof s!="string"?String(s??""):s.replace(/<[^>]*>/g,""),description:"HTML 태그 제거"},default:{fn:(s,e="")=>s==null||s===""?e:s,description:"기본값 설정 (null, undefined, 빈문자열 시)"},fallback:{fn:(s,e)=>s??e,description:"폴백 값 설정 (null, undefined 시만)"},first:{fn:s=>{if(Array.isArray(s))return s[0]},description:"배열의 첫 번째 요소"},last:{fn:s=>{if(Array.isArray(s))return s[s.length-1]},description:"배열의 마지막 요소"},join:{fn:(s,e=", ")=>Array.isArray(s)?s.join(e):"",description:'배열을 문자열로 결합 (기본 구분자: ", ")'},length:{fn:s=>Array.isArray(s)||typeof s=="string"?s.length:0,description:"배열/문자열 길이"},filterBy:{fn:(s,e,n)=>Array.isArray(s)?Array.isArray(e)?s.filter(r=>{const i=n?r?.[n]:r;return e.includes(i)}):s:[],description:"배열 필터링 (allowList에 포함된 항목만 반환)"},keys:{fn:s=>s==null||typeof s!="object"?[]:Object.keys(s),description:"객체의 키 배열"},values:{fn:s=>s==null||typeof s!="object"?[]:Object.values(s),description:"객체의 값 배열"},json:{fn:(s,e)=>{try{return JSON.stringify(s,null,e)}catch{return""}},description:"JSON 문자열로 변환"},localized:{fn:(s,e)=>s==null?"":typeof s=="string"?s:typeof s!="object"?String(s):s[e||"ko"]||s.ko||s.en||Object.values(s)[0]||"",description:"다국어 객체에서 로케일에 맞는 값 추출"}},ds=new Map,Li=class Li{static getInstance(){return Li.instance||(Li.instance=new Li),Li.instance}register(e,n,r){if(mo[e]){us.warn(`[PipeRegistry] 내장 파이프를 덮어쓸 수 없습니다: ${e}`);return}ds.set(e,{fn:n,description:r}),us.log(`[PipeRegistry] 커스텀 파이프 등록됨: ${e}`)}unregister(e){if(mo[e])return us.warn(`[PipeRegistry] 내장 파이프는 해제할 수 없습니다: ${e}`),!1;const n=ds.delete(e);return n&&us.log(`[PipeRegistry] 커스텀 파이프 해제됨: ${e}`),n}get(e){const n=mo[e];return n?n.fn:ds.get(e)?.fn}has(e){return!!mo[e]||ds.has(e)}execute(e,n,r=[]){const i=this.get(e);if(!i)return us.warn(`[PipeRegistry] 알 수 없는 파이프: ${e}`),n;try{return i(n,...r)}catch(l){return us.error(`[PipeRegistry] 파이프 실행 오류 (${e}):`,l),n}}list(){const e=[];for(const[n,r]of Object.entries(mo))e.push({name:n,description:r.description,type:"built-in"});for(const[n,r]of ds.entries())e.push({name:n,description:r.description,type:"custom"});return e.sort((n,r)=>n.name.localeCompare(r.name))}clearCustomPipes(){ds.clear()}};M(Li,"instance",null);let rc=Li;rc.getInstance();function x0(s){const e=s.trim(),n=e.indexOf("(");if(n===-1)return{name:e,args:[]};const r=e.slice(0,n).trim(),i=e.slice(n+1,-1);if(!i.trim())return{name:r,args:[]};const l=[];let c="",d=null,f=0;for(let h=0;h0?i[h-1]:"")==="\\"){c+=m;continue}if((m==='"'||m==="'")&&!d){d=m;continue}if(m===d){d=null;continue}if(d){c+=m;continue}if(m==="("||m==="["||m==="{"){f++,c+=m;continue}if(m===")"||m==="]"||m==="}"){f--,c+=m;continue}if(m===","&&f===0){l.push(Xg(c.trim())),c="";continue}c+=m}return c.trim()&&l.push(Xg(c.trim())),{name:r,args:l}}function Xg(s){if(s==="null")return null;if(s==="undefined")return;if(s==="true")return!0;if(s==="false")return!1;const e=Number(s);return!isNaN(e)&&s!==""?e:s}function T0(s){const e=[];let n="",r=null,i=0;for(let l=0;l0?s[l-1]:"",f=l0?s[r-1]:"",c=r",e),this.cacheVersion=e,this.cache.clear())}getCacheVersion(){return this.cacheVersion}async loadTranslations(e,n,r="/api",i=!1){const l=`${e}:${n}`;if(!i){const c=this.getFromCache(l);if(c)return this.translations.set(l,c),c}try{const c=[];i&&c.push(`_=${Date.now()}`);const d=Br(`${r}/templates/${e}/lang/${n}`,"json",this.cacheVersion>0?this.cacheVersion:null,c.length>0?c.join("&"):void 0),f=!i&&this.cacheVersion>0?go(`templates/${e}/lang/${n}.json`,this.cacheVersion):null,h=f!==null?await nc(f,d,{label:`lang/${n}.json`}):await fetch(d);if(!h.ok)throw new Gd(`Failed to load translations: ${h.statusText}`,void 0,n);const y=await h.json();return this.saveToCache(l,y),this.translations.set(l,y),y}catch(c){throw new Gd(`Failed to fetch translations for ${n}: ${c instanceof Error?c.message:String(c)}`,void 0,n)}}resolveTranslations(e,n,r){let i=e;const l=5;let c=0;for(;c"="+this.translate(m,n,void 0,r)),i===d)break;c++}return i.replace(Pn.TRANSLATION_PATTERN,(d,f,h)=>{const{cleanedParams:m,trailing:y}=this.separateTrailingText(h);return this.translate(f,n,m,r)+y})}translate(e,n,r,i){const l=this.getTranslation(e,n),c=r&&this.cleanParamsStr(r);if(c){const d=this.parseParams(c,i);return this.replaceParams(l,d)}return l}getTranslation(e,n){const{templateId:r,locale:i}=n,l=`${r}:${i}`,c=this.translations.get(l);if(c){const d=this.getNestedValue(c,e);if(d!==null)return d}if(i!==this.options.fallbackLocale){const d=`${r}:${this.options.fallbackLocale}`,f=this.translations.get(d);if(f){const h=this.getNestedValue(f,e);if(h!==null)return h}}return e}getNestedValue(e,n){const r=n.split(".");let i=e;for(const l of r){if(i==null||typeof i!="object")return null;i=i[l]}return typeof i=="string"?i:null}setTranslationValue(e,n,r,i){const l=`${e}:${n}`,c=this.translations.get(l)??{},d=r.split(".");let f=c;for(let h=0;h(l.push(f),`__PLACEHOLDER_${l.length-1}__`)).matchAll(Pn.PARAM_PATTERN);for(const f of d){const[,h,m]=f,y=m.replace(/__PLACEHOLDER_(\d+)__/g,(v,S)=>l[parseInt(S,10)]);r[h.trim()]=this.resolveParamValue(y.trim(),n)}return r}resolveParamValue(e,n){if(e.startsWith("{{")&&e.endsWith("}}")){const r=e.slice(2,-2).trim();if(jn(r)&&n)try{const l=oc.evaluatePipeExpression(r,n);return String(l??"")}catch(l){return qd.error("Pipe expression evaluation failed:",r,l),""}if(/[|&()[\]!?:+\-*/%<>=\s]/.test(r)&&n)try{const l=oc.evaluateExpression(r,n);return String(l??"")}catch(l){return qd.error("Expression evaluation failed:",r,l),""}else{const l=this.getNestedDataValue(n,r);return String(l??"")}}return e}separateTrailingText(e){if(!e)return{cleanedParams:void 0,trailing:""};if(e.trimEnd().endsWith("}}"))return{cleanedParams:e,trailing:""};if(!(e.startsWith("|")?e.slice(1):e).includes("="))return{cleanedParams:void 0,trailing:e};const r=e.match(/(\s+[^\p{L}\w=|&\s][^\p{L}\w=]*\s*)$/u);return r?{cleanedParams:e.slice(0,-r[1].length),trailing:r[1]}:{cleanedParams:e,trailing:""}}cleanParamsStr(e){return this.separateTrailingText(e).cleanedParams||""}getNestedDataValue(e,n){if(!e)return;const r=n.split(".");let i=e;for(const l of r){if(i==null)return;i=i[l]}return i}replaceParams(e,n){let r=e;for(const[i,l]of Object.entries(n)){const c=new RegExp(`\\{\\{${i}\\}\\}`,"g");r=r.replace(c,String(l));const d=new RegExp(`\\{${i}\\}`,"g");r=r.replace(d,String(l))}return r}getFromCache(e){const n=this.cache.get(e);return n?Date.now()-n.timestamp>this.options.cacheTTL?(this.cache.delete(e),null):n.data:null}saveToCache(e,n){this.cache.set(e,{data:n,timestamp:Date.now()})}clearCache(){this.cache.clear(),this.translations.clear()}pruneCache(){const e=Date.now(),n=[];for(const[r,i]of this.cache.entries())e-i.timestamp>this.options.cacheTTL&&n.push(r);for(const r of n)this.cache.delete(r)}getCacheStats(){const e=Date.now();let n=0;for(const r of this.cache.values())e-r.timestamp>this.options.cacheTTL&&n++;return{size:this.cache.size,expired:n}}};M(Pn,"instance",null),M(Pn,"TRANSLATION_PATTERN",/\$t:(?:defer:)?([a-zA-Z0-9._-]+)(\|(?:(?!\$t:).)+)?/g),M(Pn,"NESTED_TRANSLATION_PARAM_PATTERN",/=(\$t:([a-zA-Z0-9._-]+))(?=[|&\s]|$)/g),M(Pn,"PARAM_PATTERN",/([^=|&]+)=([^|&]+)/g);let vi=Pn;const Jg=Object.freeze(Object.defineProperty({__proto__:null,TranslationEngine:vi,TranslationError:Gd},Symbol.toStringTag,{value:"Module"})),Fd=new Map([["true",!0],["false",!1],["null",null],["undefined",void 0]]);function Oa(s){if(typeof s!="string"||!s.startsWith("{{")||!s.endsWith("}}")||s.length<4)return null;const e=s.slice(2,-2);let n=null,r=0;for(let i=0;i0){r--;continue}return null}}return r===0?e.trim():null}function R0(s){const e=[];if(typeof s!="string")return e;for(let n=0;n0){i--;continue}if(s[c+1]==="}"){l=c;break}break}}l!==-1&&(e.push({start:n,end:l+2,expr:s.slice(n+2,l)}),n=l+1)}return e}function yo(s){return/[?:|&!+\-*/<>=()[\]{}]/.test(s)}function Qg(s){const e=typeof s=="string"?s.trim():"";return e===""?"empty":Fd.has(e)?"literal":jn(e)?"pipe":yo(e)?"expression":"path"}function Vd(s,e,n,r){const i=typeof s=="string"?s.trim():"",l=Qg(i),c=r?.skipCache?{skipCache:!0}:void 0;if(l==="empty"){r?.onEmpty?.(s);return}if(l==="literal")return Fd.get(i);try{return l==="pipe"?n.evaluatePipeExpression(i,e,c,r?.trackingInfo):l==="expression"?n.evaluateExpression(i,e,r?.trackingInfo):n.resolve(i,e,c,r?.trackingInfo)}catch(d){if(r?.onError)return r.onError(d,i);throw d}}const lr="raw:";function Zg(s){return s.startsWith(lr)?s.slice(lr.length):s}const bo="﷐",Si="﷑",La="﷒";function vo(s){return bo+s+Si}function ac(s){return s.length>=2&&s.charCodeAt(0)===64976&&s.charCodeAt(s.length-1)===64977}function ic(s){return s.includes(bo)}function Kd(s){return s.slice(1,-1)}function fs(s){if(typeof s=="string")return vo(s);if(Array.isArray(s))return s.map(fs);if(s&&typeof s=="object"){const e={};for(const[n,r]of Object.entries(s))e[n]=fs(r);return e}return s}function $a(s){if(typeof s=="string")return ac(s)?Kd(s):ic(s)||s.includes(Si)?s.split(bo).join("").split(Si).join(""):s;if(Array.isArray(s)){let e=!1;const n=s.map(r=>{const i=$a(r);return i!==r&&(e=!0),i});return e?n:s}if(s&&typeof s=="object"){if(s.$$typeof!==void 0)return s;let e=!1;const n={};for(const[r,i]of Object.entries(s)){const l=$a(i);l!==i&&(e=!0),n[r]=l}return e?n:s}return s}const So=new Set(["constructor","__proto__","prototype","__lookupGetter__","__lookupSetter__","__defineGetter__","__defineSetter__"]),D0=new Set(["Function","eval","globalThis","window","self","document","require","module","process","Reflect","Proxy","WebAssembly","import","constructor"]);function em(s,e){if(s!==null&&(typeof s=="object"||typeof s=="function")&&tm.has(s))throw new Error(`Object.${e} on a built-in global object is not allowed`);return s}const O0=Object.freeze({keys:Object.keys,values:Object.values,entries:Object.entries,assign:(s,...e)=>{const n=em(s,"assign");for(const r of e)if(r!=null)for(const i of Object.keys(r)){if(So.has(i))throw new Error(`Access to "${i}" is forbidden`);tf(n,i,r[i])}return n},freeze:s=>Object.freeze(em(s,"freeze")),fromEntries:Object.fromEntries,create:Object.create,isFrozen:Object.isFrozen}),Wd={Math,JSON,Date,Array,Object:O0,Number,String,Boolean,Set,Map,WeakSet,WeakMap,parseInt,parseFloat,isNaN,isFinite},Yd={Date,Set,Map,WeakSet,WeakMap,Array,Number,String,Boolean,Object},tm=new Set([...Object.values(Wd),...Object.values(Yd)].filter(s=>s!==null&&(typeof s=="object"||typeof s=="function"))),sc=new Set(["function"]),xr=Symbol("short-circuit");class Xd{constructor(e){this.value=e}}class Jd{}class Qd{}function L0(s){return/[A-Za-z_$]/.test(s)}function $0(s){return/[A-Za-z0-9_$]/.test(s)}function wi(s){return s>="0"&&s<="9"}function M0(s,e,n){let r=e+1;for(;r0;){const h=s[n];if(h==="{")f++,n++;else if(h==="}"){if(f--,f===0)break;n++}else h==='"'||h==="'"?n=M0(s,n,h):h==="`"?n=nm(s,n).end:n++}if(f!==0)throw new Error("Unterminated ${...} in template literal");i.push(s.slice(d,n)),n++;continue}l+=c,n++}throw new Error(`Unterminated template literal at position ${e}`)}function N0(s){const e=[];let n=0;const r=s.length,i=l=>n+l=r)throw new Error(`Unterminated string literal at position ${h}`);n++,e.push({type:"str",value:y,pos:h});continue}if(L0(l)){const h=n;for(n++;n"?(e.push({type:"punct",value:"=>",pos:c}),n+=2):i(1)==="="?i(2)==="="?(e.push({type:"punct",value:"===",pos:c}),n+=3):(e.push({type:"punct",value:"==",pos:c}),n+=2):(e.push({type:"punct",value:"=",pos:c}),n+=1);continue}case"!":{i(1)==="="?i(2)==="="?(e.push({type:"punct",value:"!==",pos:c}),n+=3):(e.push({type:"punct",value:"!=",pos:c}),n+=2):(e.push({type:"punct",value:"!",pos:c}),n+=1);continue}case"<":{if(i(1)==="=")e.push({type:"punct",value:"<=",pos:c}),n+=2;else{if(i(1)==="<")throw new Error("Bitwise/shift operators are not allowed");e.push({type:"punct",value:"<",pos:c}),n+=1}continue}case">":{if(i(1)==="=")e.push({type:"punct",value:">=",pos:c}),n+=2;else{if(i(1)===">")throw new Error("Bitwise/shift operators are not allowed");e.push({type:"punct",value:">",pos:c}),n+=1}continue}case"&":{if(i(1)==="&")e.push({type:"punct",value:"&&",pos:c}),n+=2;else throw new Error("Bitwise operators are not allowed");continue}case"|":{if(i(1)==="|")e.push({type:"punct",value:"||",pos:c}),n+=2;else throw new Error("Bitwise operators are not allowed");continue}case"?":{i(1)==="."&&!wi(i(2))?(e.push({type:"punct",value:"?.",pos:c}),n+=2):i(1)==="?"?(e.push({type:"punct",value:"??",pos:c}),n+=2):(e.push({type:"punct",value:"?",pos:c}),n+=1);continue}case"+":{if(i(1)==="+")throw new Error("Increment operator is not allowed");if(i(1)==="=")throw new Error("Assignment operators are not allowed");e.push({type:"punct",value:"+",pos:c}),n+=1;continue}case"-":{if(i(1)==="-")throw new Error("Decrement operator is not allowed");if(i(1)==="=")throw new Error("Assignment operators are not allowed");e.push({type:"punct",value:"-",pos:c}),n+=1;continue}case"*":{if(i(1)==="=")throw new Error("Assignment operators are not allowed");if(i(1)==="*")throw new Error("Exponentiation operator is not supported");e.push({type:"punct",value:"*",pos:c}),n+=1;continue}case"/":{if(i(1)==="=")throw new Error("Assignment operators are not allowed");e.push({type:"punct",value:"/",pos:c}),n+=1;continue}case"%":{if(i(1)==="=")throw new Error("Assignment operators are not allowed");e.push({type:"punct",value:"%",pos:c}),n+=1;continue}case"~":case"^":throw new Error("Bitwise operators are not allowed");case".":case":":case"(":case")":case"[":case"]":case"{":case"}":case",":e.push({type:"punct",value:l,pos:c}),n+=1;continue;case";":e.push({type:"punct",value:";",pos:c}),n+=1;continue;default:throw new Error(`Unexpected character "${l}" at position ${c}`)}}return e}const I0={"??":1,"||":1,"&&":2,"===":3,"!==":3,"==":3,"!=":3,"<":4,">":4,"<=":4,">=":4,"+":5,"-":5,"*":6,"/":6,"%":6},j0=new Set(["??","||","&&"]);class H0{constructor(e){M(this,"tokens");M(this,"pos",0);this.tokens=e}atEnd(){return this.pos>=this.tokens.length}peek(e=0){const n=this.pos+e;return n"}"`);this.pos++}parseExpression(){const e=this.tryParseArrow();return e||this.parseTernary()}tryParseArrow(){const e=this.pos,n=this.peek();if(!n)return null;if(n.type==="ident"&&this.isPunct("=>",1)&&!oc.has(String(n.value))){this.pos+=1,this.pos+=1;const{body:r,isBlock:i}=this.parseArrowBody();return{type:"Arrow",params:[{name:String(n.value),default:null}],body:r,isBlock:i}}if(n.type==="punct"&&n.value==="("){this.pos+=1;const r=this.tryParseParamList();if(r&&this.isPunct(")")&&(this.pos+=1,this.isPunct("=>"))){this.pos+=1;const{body:i,isBlock:l}=this.parseArrowBody();return{type:"Arrow",params:r,body:i,isBlock:l}}return this.pos=e,null}return this.pos=e,null}tryParseParamList(){const e=[];if(this.isPunct(")"))return e;for(;;){const n=this.peek();let r=null,i;if(n&&n.type==="punct"&&n.value==="["){const c=this.tryParseArrayPattern();if(!c)return null;i=c}else if(n&&n.type==="ident"&&!oc.has(String(n.value)))r=String(n.value),this.pos+=1;else return null;let l=null;if(this.isPunct("=")&&(this.pos+=1,l=this.parseExpression()),e.push(i?{name:r,elements:i,default:l}:{name:r,default:l}),this.isPunct(",")){this.pos+=1;continue}break}return e}tryParseArrayPattern(){const e=this.pos;this.pos+=1;const n=[];for(;;){if(this.isPunct("]"))return this.pos+=1,n;if(this.isPunct(",")){n.push(null),this.pos+=1;continue}const r=this.peek();if(!r||r.type!=="ident"||oc.has(String(r.value)))return this.pos=e,null;if(n.push(String(r.value)),this.pos+=1,this.isPunct(",")){this.pos+=1;continue}return this.isPunct("]")?(this.pos+=1,n):(this.pos=e,null)}}parseArrowBody(){return this.isPunct("{")?{body:this.parseBlock(),isBlock:!0}:{body:this.parseExpression(),isBlock:!1}}parseBlock(){this.expectPunct("{");const e=[];for(;!this.isPunct("}");){if(this.atEnd())throw new Error("Unterminated block");e.push(this.parseStatement())}return this.expectPunct("}"),{type:"Block",body:e}}parseStatement(){const e=this.peek();if(!e)throw new Error("Unexpected end of statement");if(e.type==="punct"&&e.value==="{")return this.parseBlock();if(e.type==="punct"&&e.value===";")return this.pos+=1,{type:"Empty"};if(e.type==="ident")switch(String(e.value)){case"const":case"let":return this.parseVarDecl();case"if":return this.parseIf();case"for":return this.parseForOf();case"return":return this.parseReturn();case"try":return this.parseTry();case"break":return this.pos+=1,this.consumeSemicolon(),{type:"Break"};case"continue":return this.pos+=1,this.consumeSemicolon(),{type:"Continue"}}const n=this.parseExpression();return this.consumeSemicolon(),{type:"ExprStmt",expression:n}}consumeSemicolon(){this.isPunct(";")&&(this.pos+=1)}parseVarDecl(){this.pos+=1;const e=[];for(;;){const n=this.peek();if(!n||n.type!=="ident")throw new Error("Expected variable name in declaration");const r=String(n.value);this.pos+=1,this.expectPunct("=");const i=this.parseExpression();if(e.push({name:r,init:i}),this.isPunct(",")){this.pos+=1;continue}break}return this.consumeSemicolon(),{type:"VarDecl",declarations:e}}parseIf(){this.pos+=1,this.expectPunct("(");const e=this.parseExpression();this.expectPunct(")");const n=this.parseStatement();let r=null;const i=this.peek();return i&&i.type==="ident"&&i.value==="else"&&(this.pos+=1,r=this.parseStatement()),{type:"If",test:e,consequent:n,alternate:r}}parseForOf(){this.pos+=1,this.expectPunct("(");const e=this.peek();if(!e||e.type!=="ident"||e.value!=="const"&&e.value!=="let")throw new Error('Only "for (const x of …)" / "for (let x of …)" loops are supported');this.pos+=1;const n=this.peek();if(!n||n.type!=="ident")throw new Error("Expected loop variable name");const r=String(n.value);this.pos+=1;const i=this.peek();if(!i||i.type!=="ident"||i.value!=="of")throw new Error("Only for-of loops are supported (for-in / C-style for are not allowed)");this.pos+=1;const l=this.parseExpression();this.expectPunct(")");const c=this.parseStatement();return{type:"ForOf",name:r,iterable:l,body:c}}parseReturn(){if(this.pos+=1,this.isPunct(";")||this.isPunct("}")||this.atEnd())return this.consumeSemicolon(),{type:"Return",argument:null};const e=this.parseExpression();return this.consumeSemicolon(),{type:"Return",argument:e}}parseTry(){this.pos+=1;const e=this.parseBlock();let n=null,r=null,i=null;const l=this.peek();if(l&&l.type==="ident"&&l.value==="catch"){if(this.pos+=1,this.isPunct("(")){this.pos+=1;const d=this.peek();d&&d.type==="ident"&&(n=String(d.value),this.pos+=1),this.expectPunct(")")}r=this.parseBlock()}const c=this.peek();if(c&&c.type==="ident"&&c.value==="finally"&&(this.pos+=1,i=this.parseBlock()),!r&&!i)throw new Error("Missing catch or finally after try");return{type:"Try",block:e,handlerParam:n,handler:r,finalizer:i}}parseFunctionExpression(){this.pos+=1;let e=null;const n=this.peek();n&&n.type==="ident"&&n.value!==void 0&&!this.isPunct("(")&&(e=String(n.value),this.pos+=1),this.expectPunct("(");const r=this.tryParseParamList();if(!r)throw new Error("Invalid function parameter list");this.expectPunct(")");const i=this.parseBlock();return{type:"Function",name:e,params:r,body:i}}parseTernary(){const e=this.parseBinary(0);if(this.isPunct("?")){this.pos+=1;const n=this.parseExpression();this.expectPunct(":");const r=this.parseExpression();return{type:"Conditional",test:e,consequent:n,alternate:r}}return e}parseBinary(e){let n=this.parseUnary();for(;;){const r=this.peek();if(!r||r.type!=="punct")break;const i=String(r.value),l=I0[i];if(l===void 0||lim(l));return{type:"Template",quasis:n,expressions:i}}if(e.type==="ident"&&e.value==="new")return this.parseNew();if(e.type==="ident"&&e.value==="function")return this.parseFunctionExpression();if(e.type==="ident"){const n=String(e.value);if(oc.has(n))throw new Error(`Keyword "${n}" is not allowed`);switch(this.pos+=1,n){case"true":return{type:"Literal",value:!0};case"false":return{type:"Literal",value:!1};case"null":return{type:"Literal",value:null};case"undefined":return{type:"Literal",value:void 0};default:return{type:"Identifier",name:n}}}if(e.type==="punct"){if(e.value==="("){this.pos+=1;const n=this.parseExpression();return this.expectPunct(")"),n}if(e.value==="[")return this.parseArrayLiteral();if(e.value==="{")return this.parseObjectLiteral()}throw new Error(`Unexpected token "${e.value}"`)}parseArrayLiteral(){this.expectPunct("[");const e=[];for(;!this.isPunct("]");){if(this.isPunct(",")){this.pos+=1,e.push({type:"Literal",value:void 0});continue}if(this.isPunct("...")?(this.pos+=1,e.push({type:"Spread",argument:this.parseExpression()})):e.push(this.parseExpression()),this.isPunct(",")){this.pos+=1;continue}break}return this.expectPunct("]"),{type:"Array",elements:e}}parseObjectLiteral(){this.expectPunct("{");const e=[];for(;!this.isPunct("}");){if(this.isPunct("..."))this.pos+=1,e.push({kind:"spread",value:this.parseExpression()});else{let n,r=!1;const i=this.peek();if(!i)throw new Error("Unexpected end of expression in object literal");if(i.type==="punct"&&i.value==="[")this.pos+=1,n=this.parseExpression(),this.expectPunct("]"),r=!0;else if(i.type==="str")this.pos+=1,n={type:"Literal",value:i.value};else if(i.type==="num")this.pos+=1,n={type:"Literal",value:String(i.value)};else if(i.type==="ident")this.pos+=1,n={type:"Literal",value:String(i.value)};else throw new Error(`Unexpected token "${i.value}" in object literal`);if(this.isPunct(":")){this.pos+=1;const l=this.parseExpression();e.push({kind:"init",key:n,computed:r,value:l})}else{if(r||n.type!=="Literal"||typeof n.value!="string")throw new Error("Invalid shorthand property in object literal");if(i.type!=="ident")throw new Error("Invalid shorthand property in object literal");e.push({kind:"init",key:n,computed:!1,value:{type:"Identifier",name:n.value}})}}if(this.isPunct(",")){this.pos+=1;continue}break}return this.expectPunct("}"),{type:"Object",properties:e}}}function im(s){const e=N0(s),n=new H0(e),r=n.parseExpression();if(!n.atEnd()){const i=n.peek();throw i&&i.type==="punct"&&i.value===","?new Error("The comma/sequence operator is not allowed"):new Error(`Unexpected token "${i?i.value:""}" after expression`)}return r}function z0(s,e){let n=e;for(;n;){if(Object.prototype.hasOwnProperty.call(n.vars,s))return n.vars[s];n=n.parent}if(Object.prototype.hasOwnProperty.call(Xd,s))return Xd[s];if(D0.has(s))throw new Error(`Reference to forbidden global "${s}" is not allowed`)}function tf(s){const e=typeof s=="symbol"?s:String(s);if(typeof e=="string"&&So.has(e))throw new Error(`Access to "${e}" is forbidden`);return e}function nf(s,e){return s.type==="Member"?sm(s,e):s.type==="Call"?om(s,e):et(s,e)}function sm(s,e){const n=nf(s.object,e);if(n===_r||s.optional&&n==null)return _r;let r;s.computed?r=et(s.property,e):r=s.property.value;const i=tf(r);if(n!=null)return n[i]}function om(s,e){let n,r;if(s.callee.type==="Member"){const l=s.callee,c=nf(l.object,e);if(c===_r||l.optional&&c==null)return _r;let d;l.computed?d=et(l.property,e):d=l.property.value;const f=tf(d);c==null?n=void 0:(n=c[f],r=c)}else if(n=nf(s.callee,e),n===_r)return _r;if(s.optional&&n==null)return _r;if(typeof n!="function")throw new Error("Attempted to call a non-function value");const i=lm(s.args,e);return n.apply(r,i)}function lm(s,e){const n=[];for(const r of s)if(r.type==="Spread"){const i=et(r.argument,e);if(i!=null)for(const l of i)n.push(l)}else n.push(et(r,e));return n}function cm(s,e,n){for(let r=0;r":return n>r;case"<=":return n<=r;case">=":return n>=r;default:throw new Error(`Unknown binary operator "${s.operator}"`)}}case"Logical":{const n=et(s.left,e);switch(s.operator){case"&&":return n&&et(s.right,e);case"||":return n||et(s.right,e);case"??":return n??et(s.right,e);default:throw new Error(`Unknown logical operator "${s.operator}"`)}}case"Conditional":return et(s.test,e)?et(s.consequent,e):et(s.alternate,e);case"Array":{const n=[];for(const r of s.elements)if(r.type==="Spread"){const i=et(r.argument,e);if(i!=null)for(const l of i)n.push(l)}else n.push(et(r,e));return n}case"Object":{const n={};for(const r of s.properties)if(r.kind==="spread"){const i=et(r.value,e);if(i!=null&&typeof i=="object")for(const l of Object.keys(i))rf(n,l,i[l])}else{let i;r.computed?i=et(r.key,e):i=r.key.value;const l=et(r.value,e);rf(n,String(i),l)}return n}case"Arrow":{const n=e,r=s.params,i=s.body,l=s.isBlock;return function(...d){const f={vars:{},parent:n};return cm(r,d,f),l?um(i,f):et(i,f)}}case"Function":{const n=e,r=s.params,i=s.body,l=s.name,c=function(...f){const h={vars:{},parent:n};return l&&(h.vars[l]=c),cm(r,f,h),um(i,h)};return c}case"Delete":{const n=s.argument,r=et(n.object,e);let i;n.computed?i=et(n.property,e):i=n.property.value;const l=tf(i);if(r==null||typeof r!="object"&&typeof r!="function")return!0;if(rm.has(r))throw new Error("delete on a built-in global object is not allowed");return delete r[l]}case"Block":{const n={vars:{},parent:e};for(const r of s.body)et(r,n);return}case"VarDecl":{for(const n of s.declarations)e.vars[n.name]=et(n.init,e);return}case"If":{et(s.test,e)?et(s.consequent,e):s.alternate&&et(s.alternate,e);return}case"ForOf":{const n=et(s.iterable,e);if(n!=null)for(const r of n){const i={vars:{},parent:e};i.vars[s.name]=r;try{et(s.body,i)}catch(l){if(l instanceof ef)continue;if(l instanceof Zd)break;throw l}}return}case"Return":throw new Qd(s.argument?et(s.argument,e):void 0);case"ExprStmt":et(s.expression,e);return;case"Break":throw new Zd;case"Continue":throw new ef;case"Empty":return;case"Try":{try{try{et(s.block,e)}catch(n){if(n instanceof Qd||n instanceof Zd||n instanceof ef)throw n;if(s.handler){const r={vars:{},parent:e};s.handlerParam&&(r.vars[s.handlerParam]=n),et(s.handler,r)}else throw n}}finally{s.finalizer&&et(s.finalizer,e)}return}case"New":{const n=Jd[s.ctor];if(typeof n!="function")throw new Error("new on non-whitelisted constructor");const r=lm(s.args,e);return new n(...r)}case"Template":{let n=s.quasis[0]??"";for(let r=0;r0}getFromRenderCycleCache(e){if(this.isRenderCycleCacheActive())return this.renderCycleCache.get(e)}saveToRenderCycleCache(e,n){this.isRenderCycleCacheActive()&&this.renderCycleCache.set(e,n)}resolveBindings(e,n,r,i){const l={...this.defaultOptions,...r},c=wo(),d=n._computed&&!n.$computed?{...n,$computed:n._computed}:n,f=(v,S)=>{const _=S.trim();let x=!1,A=_;if(_.startsWith(sr)&&(x=!0,A=_.slice(sr.length)),Wn(A))try{const z=this.evaluatePipeExpression(A,d,l,{...i,method:"resolveBindings",displayExpression:`{{${_}}}`}),W=this.formatValue(z);return x?vo(W):W}catch(z){return Na.error("Pipe expression evaluation failed:",A,z),v}if(yo(A)){const z=c?.isEnabled()?performance.now():0;try{let W,de=!1;if(l.skipCache)W=this.evaluateExpression(A,d);else{const Se=`expr:${A}`,Ie=this.getFromRenderCycleCache(Se);Ie!==void 0?(W=Ie,de=!0):(W=this.evaluateExpression(A,d),this.saveToRenderCycleCache(Se,W))}if(c?.isEnabled()){const Se=performance.now()-z;c.trackExpressionEval({expression:`{{${_}}}`,result:this.sanitizeResultForTracking(W),resultType:this.getResultType(W),componentId:i?.componentId,componentName:i?.componentName,propName:i?.propName,fromCache:de,duration:Se,method:"resolveBindings",skipCache:l.skipCache})}const _e=this.formatValue(W);return x?vo(_e):_e}catch(W){return Na.error("Expression evaluation failed:",A,W),v}}if(l.skipCache){const z=c?.isEnabled()?performance.now():0,W=this.resolvePath(A,d,l);if(c?.isEnabled()){const _e=performance.now()-z;c.trackExpressionEval({expression:`{{${_}}}`,result:this.sanitizeResultForTracking(W),resultType:this.getResultType(W),componentId:i?.componentId,componentName:i?.componentName,propName:i?.propName,fromCache:!1,duration:_e,method:"resolveBindings",skipCache:!0})}const de=this.formatValue(W);return x?vo(de):de}const O=A.startsWith("_global")||A.startsWith("_local")||A.startsWith("_isolated")||A.startsWith("$parent"),k=c?.isEnabled()?performance.now():0;let L,B=!1;if(O){const z=this.getFromRenderCycleCache(A);z!==void 0?(L=z,B=!0):(L=this.resolvePath(A,d,l),this.saveToRenderCycleCache(A,L))}else{const z=this.getFromCache(A);z!==void 0?(L=z,B=!0):(L=this.resolvePath(A,d,l),this.saveToCache(A,L))}if(c?.isEnabled()){const z=performance.now()-k;c.trackExpressionEval({expression:`{{${_}}}`,result:this.sanitizeResultForTracking(L),resultType:this.getResultType(L),componentId:i?.componentId,componentName:i?.componentName,propName:i?.propName,fromCache:B,duration:z,method:"resolveBindings",skipCache:l.skipCache})}const q=this.formatValue(L);return x?vo(q):q},h=R0(e);if(h.length===0)return e;let m="",y=0;for(const v of h)m+=e.slice(y,v.start),m+=f(e.slice(v.start,v.end),v.expr),y=v.end;return m+e.slice(y)}evaluatePipeExpression(e,n,r,i){const l={...this.defaultOptions,...r},c=wo(),d=c?.isEnabled()?performance.now():0,f=n._computed&&!n.$computed?{...n,$computed:n._computed}:n,[h,m]=T0(e);let y,v=!1;const S=yo(h),_=h.startsWith("_global")||h.startsWith("_local")||h.startsWith("_isolated")||h.startsWith("$parent");if(l.skipCache)S?y=this.evaluateExpression(h,f):y=this.resolvePath(h,f,l);else if(S){const A=this.getFromRenderCycleCache(`expr:${h}`);A!==void 0?(y=A,v=!0):(y=this.evaluateExpression(h,f),this.saveToRenderCycleCache(`expr:${h}`,y))}else if(_){const A=this.getFromRenderCycleCache(h);A!==void 0?(y=A,v=!0):(y=this.resolvePath(h,f,l),this.saveToRenderCycleCache(h,y))}else{const A=this.getFromCache(h);A!==void 0?(y=A,v=!0):(y=this.resolvePath(h,f,l),this.saveToCache(h,y))}const x=k0(y,m);if(c?.isEnabled()){const A=performance.now()-d;c.trackExpressionEval({expression:i?.displayExpression??`{{${e}}}`,result:this.sanitizeResultForTracking(x),resultType:this.getResultType(x),componentId:i?.componentId,componentName:i?.componentName,propName:i?.propName,fromCache:v,duration:A,method:i?.method??"evaluatePipeExpression",skipCache:l.skipCache})}return x}resolve(e,n,r,i){const l={...this.defaultOptions,...r},c=wo(),d=c?.isEnabled()?performance.now():0;if(l.skipCache){const y=this.resolvePath(e,n,l);return c?.isEnabled()&&c.trackExpressionEval({expression:e,result:this.sanitizeResultForTracking(y),resultType:this.getResultType(y),componentId:i?.componentId,componentName:i?.componentName,propName:i?.propName,fromCache:!1,duration:performance.now()-d,method:"resolve",skipCache:!0}),y}const f=e.startsWith("_global")||e.startsWith("_local")||e.startsWith("_isolated")||e.startsWith("$parent");let h=!1;if(f){const y=this.getFromRenderCycleCache(e);if(y!==void 0)return h=!0,c?.isEnabled()&&c.trackExpressionEval({expression:e,result:this.sanitizeResultForTracking(y),resultType:this.getResultType(y),componentId:i?.componentId,componentName:i?.componentName,propName:i?.propName,fromCache:!0,duration:performance.now()-d,method:"resolve",skipCache:l.skipCache}),y}else{const y=this.getFromCache(e);if(y!==void 0)return h=!0,c?.isEnabled()&&c.trackExpressionEval({expression:e,result:this.sanitizeResultForTracking(y),resultType:this.getResultType(y),componentId:i?.componentId,componentName:i?.componentName,propName:i?.propName,fromCache:!0,duration:performance.now()-d,method:"resolve",skipCache:l.skipCache}),y}const m=this.resolvePath(e,n,l);return f?this.saveToRenderCycleCache(e,m):this.saveToCache(e,m),c?.isEnabled()&&c.trackExpressionEval({expression:e,result:this.sanitizeResultForTracking(m),resultType:this.getResultType(m),componentId:i?.componentId,componentName:i?.componentName,propName:i?.propName,fromCache:h,duration:performance.now()-d,method:"resolve",skipCache:l.skipCache}),m}resolvePath(e,n,r,i=0,l=new WeakSet){if(typeof e!="string"||e.trim()===""){Na.warn("resolvePath: 빈 경로가 전달되었습니다 — undefined 로 해석합니다.");return}if(r.maxDepth&&i>r.maxDepth){if(r.detectCircular)throw new sf(`Maximum depth exceeded (${r.maxDepth}). Possible circular reference.`,e,n);return r.defaultValue}const c=this.parsePath(e);let d=n;for(let f=0;fca.CACHE_EXPIRY){this.cache.delete(e),r?.isEnabled()&&r.recordCacheMiss();return}return r?.isEnabled()&&r.recordCacheHit(),n.value}saveToCache(e,n){this.cache.set(e,{value:n,timestamp:Date.now()})}isActionDefinition(e){return typeof e.handler!="string"?!1:e.params!==void 0||Array.isArray(e.actions)||typeof e.target=="string"||e.onSuccess!==void 0||e.onError!==void 0}resolveObject(e,n,r){if(this.isSwitchExpression(e)){const d=e;return this.resolveSwitch(d,n,r)}if(this.isActionDefinition(e))return{...e};if("iteration"in e)return{...e};const i={},c=[...["cellChildren","expandChildren","expandContext","render"],...r?.skipBindingKeys||[]];for(const[d,f]of Object.entries(e)){if(c.includes(d)){i[d]=f;continue}try{this.resolveObjectEntry(i,d,f,n,r)}catch(h){Na.warn(`resolveObject: 값 해석 실패 (key: ${d}):`,h),i[d]=void 0}}return i}resolveObjectEntry(e,n,r,i,l){if(typeof r=="string"){const c=La(r);if(c!==null){const d=c.trim();let f=!1,h=d;d.startsWith(sr)&&(f=!0,h=d.slice(sr.length));let m;switch(em(h)){case"empty":Na.warn(`resolveObject: 빈 바인딩 \`{{}}\` (key: ${n}) — undefined 로 해석합니다.`),m=void 0;break;case"literal":m=Kd.get(h);break;case"pipe":m=this.evaluatePipeExpression(h,i,l);break;case"expression":m=this.evaluateExpression(h,i,l);break;default:m=this.resolve(h,i,l)}e[n]=f&&m!=null?ds(m):m}else e[n]=this.resolveBindings(r,i,l)}else Array.isArray(r)?e[n]=r.map(c=>typeof c=="string"?this.resolveBindings(c,i,l):typeof c=="object"&&c!==null?this.resolveObject(c,i,l):c):r&&typeof r=="object"?e[n]=this.resolveObject(r,i,l):e[n]=r}clearCache(){this.cache.clear()}invalidateCacheByKeys(e){for(const n of this.cache.keys())for(const r of e)if(n===r||n.startsWith(`${r}.`)||n.startsWith(`${r}[`)){this.cache.delete(n);break}}pruneCache(){const e=Date.now();for(const[n,r]of this.cache.entries())e-r.timestamp>ca.CACHE_EXPIRY&&this.cache.delete(n)}getCacheStats(){const e=Date.now();let n=0;for(const r of this.cache.values())e-r.timestamp>ca.CACHE_EXPIRY&&n++;return{size:this.cache.size,expired:n}}evaluateExpression(e,n,r){const i=wo(),l=i?.isEnabled()?performance.now():0;i?.isEnabled()&&i.trackBindingEval();try{let c=this.preprocessOptionalChaining(e);c=this.preprocessTranslationTokens(c);const d=this.extractVariablesFromExpression(c),f={...n};for(const A of d)A in f||(f[A]=void 0);n._computed&&!f.$computed&&(f.$computed=n._computed);let h=n.$templateId,m=n.$locale;if(typeof window<"u"&&(!h||!m)){const A=window.__templateApp?.getConfig?.();h??(h=A?.templateId),m??(m=A?.locale)}const y=m||"ko";f.$localized=(A,D)=>{if(A==null&&!D)return"";if(typeof A=="string")return A;if(A&&typeof A=="object"&&A[y])return A[y];if(D&&typeof D=="string"){const O=f.$t?f.$t(D):D;if(O&&O!==D)return O}return A&&typeof A=="object"?A.ko||A.en||Object.values(A)[0]||"":A==null?"":String(A)};const v=typeof window<"u"?window.G7Core:void 0;f.$uuid=()=>v?.uuid?v.uuid():typeof crypto<"u"&&crypto.randomUUID?crypto.randomUUID():"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,A=>{const D=Math.random()*16|0;return(A==="x"?D:D&3|8).toString(16)});const S={templateId:h||"",locale:y};f.$t=A=>{if(!A||typeof A!="string")return"";try{return Oa.getInstance().translate(A,S)}catch(D){return Na.warn("$t() translation failed for key:",A,D),A}},f.$get=(A,D,O=void 0)=>{if(A==null)return O;const k=Array.isArray(D)?D:[D];if(k.length===0)return A;let L=A;for(const B of k){if(L==null||B==null)return O;L=L[B]}return L??O};const _=!1,x=af(c,f);if(i?.isEnabled()){const A=performance.now()-l;i.trackExpressionEval({expression:e,result:this.sanitizeResultForTracking(x),resultType:this.getResultType(x),componentId:r?.componentId,componentName:r?.componentName,propName:r?.propName,fromCache:_,duration:A,method:"evaluateExpression",skipCache:r?.skipCache})}return x}catch(c){throw new Error(`Failed to evaluate expression "${e}": ${c instanceof Error?c.message:String(c)}`)}}getResultType(e){return e===null?"null":e===void 0?"undefined":Array.isArray(e)?"array":typeof e}sanitizeResultForTracking(e){if(e==null||typeof e!="object")return e;try{if(Array.isArray(e))return e.length>10?`[Array(${e.length})]`:e.slice(0,10);const n=Object.keys(e);return n.length>20?`{Object(${n.length} keys)}`:(JSON.stringify(e),e)}catch{return"[Complex Object]"}}preprocessTranslationTokens(e){return e=e.replace(/(['"])(\$t:([a-zA-Z_][a-zA-Z0-9_.\-]*))\1/g,(n,r,i,l)=>`$t('${l}')`),e.replace(/(['"]\s*)?(\$t:[a-zA-Z_][a-zA-Z0-9_.\-]*)(?!\s*['"])/g,(n,r,i)=>r?n:`"${i}"`)}preprocessOptionalChaining(e){const n=[];let r=e.replace(/(['"])(?:(?!\1|\\).|\\.)*\1/g,l=>(n.push(l),`__STRING_LITERAL_${n.length-1}__`));const i=[];return r=r.replace(/\$t:[a-zA-Z_][a-zA-Z0-9_.\-]*(?:\|[^'"\s,)]+)?/g,l=>(i.push(l),`__TRANSLATION_TOKEN_${i.length-1}__`)),r=r.replace(/([a-zA-Z_$][a-zA-Z0-9_$]*)\.(?!\?)/g,"$1?."),r=r.replace(/__TRANSLATION_TOKEN_(\d+)__/g,(l,c)=>i[parseInt(c,10)]),r=r.replace(/__STRING_LITERAL_(\d+)__/g,(l,c)=>n[parseInt(c,10)]),r}extractVariablesFromExpression(e){const n=new Set(["true","false","null","undefined","NaN","Infinity","if","else","for","while","do","switch","case","break","continue","return","function","class","const","let","var","new","delete","typeof","instanceof","this","super","import","export","default","try","catch","finally","throw","Math","Date","JSON","Array","Object","String","Number","Boolean","RegExp","Set","Map","WeakSet","WeakMap","Symbol","Promise","BigInt","Error","parseInt","parseFloat","isNaN","isFinite","encodeURI","decodeURI","encodeURIComponent","decodeURIComponent"]),r=/(^|[^.\w$])([a-zA-Z_$][a-zA-Z0-9_$]*)/g,i=new Set;let l;for(;(l=r.exec(e))!==null;){const c=l[2];n.has(c)||i.add(c)}return Array.from(i)}isSwitchExpression(e){return e!==null&&typeof e=="object"&&!Array.isArray(e)&&"$switch"in e&&"$cases"in e}resolveSwitch(e,n,r){const i={...r,skipCache:!0};let l;try{l=this.resolveBindings(e.$switch,n,i)?.toString()?.trim()??""}catch(d){Na.warn("resolveSwitch: $switch 키 평가 실패:",e.$switch,d),l=""}let c;if(l&&e.$cases&&l in e.$cases)c=e.$cases[l];else if("$default"in e)c=e.$default;else return;return typeof c=="string"?c.includes("{{")?this.resolveBindings(c,n,i):c:c!==null&&typeof c=="object"?this.resolveObject(c,n,i):c}};M(ca,"PATH_SEGMENT_PATTERN",/([^.[\]]+)|\[(\d+)\]/g),M(ca,"CACHE_EXPIRY",3e4);let $n=ca;const of=new $n,lc={mobile:{min:0,max:767},tablet:{min:768,max:1023},desktop:{min:1024,max:1/0},portable:{min:0,max:1023}},cc=new Map;function P0(s,e){let n=null;return((...r)=>{n!==null&&clearTimeout(n),n=setTimeout(()=>{s(...r)},e)})}class U0{constructor(){M(this,"subscribers",new Set);M(this,"currentWidth",1024);M(this,"debouncedHandler",null);M(this,"isInitialized",!1);this.initialize()}initialize(){typeof window>"u"||(this.currentWidth=window.innerWidth,this.isInitialized=!0,this.debouncedHandler=P0(()=>{this.currentWidth=window.innerWidth,this.notifySubscribers()},150),window.addEventListener("resize",this.debouncedHandler))}notifySubscribers(){this.subscribers.forEach(e=>{e(this.currentWidth)})}subscribe(e){return this.subscribers.add(e),e(this.currentWidth),()=>{this.subscribers.delete(e)}}getWidth(){return this.currentWidth}parseRange(e){if(cc.has(e))return cc.get(e)??null;let n=null;if(lc[e])n={...lc[e]};else{const r=e.match(/^(-?\d*)-(-?\d*)$/);if(r){const[,i,l]=r,c=i===""?0:parseInt(i,10),d=l===""?1/0:parseInt(l,10);!isNaN(c)&&!isNaN(d)&&c<=d&&(n={min:c,max:d})}}return cc.set(e,n),n}getMatchingKey(e,n){const r=[];for(const i of Object.keys(e)){const l=this.parseRange(i);l&&n>=l.min&&n<=l.max&&r.push({key:i,range:l,isPreset:!!lc[i]})}return r.length===0?null:(r.sort((i,l)=>{if(i.isPreset!==l.isPreset)return i.isPreset?1:-1;const c=i.range.max-i.range.min,d=l.range.max-l.range.min;return c-d}),r[0].key)}matches(e){const n=this.parseRange(e);return n?this.currentWidth>=n.min&&this.currentWidth<=n.max:!1}clearSubscribers(){this.subscribers.clear()}destroy(){this.debouncedHandler&&typeof window<"u"&&window.removeEventListener("resize",this.debouncedHandler),this.clearSubscribers(),cc.clear()}_setWidthForTesting(e){this.currentWidth=e,this.notifySubscribers()}}const Ci=new U0,Co=pt("ConditionEvaluator");function B0(s,e,n,r){return Wd(tm(s),e,n,{skipCache:!0,onEmpty:()=>{Co.warn(`${r}: 빈 바인딩 \`{{}}\` — undefined 로 해석합니다.`)}})}function dm(s,e,n){if(!s)return!0;try{const r=La(s);let i;if(r!==null?i=B0(r,e,n,"evaluateStringCondition"):i=n.resolveBindings(s,e,{skipCache:!0}),typeof i=="string"){const l=i.toLowerCase().trim();if(l==="false"||l==="0"||l===""||l==="null"||l==="undefined")return!1}return!!i}catch(r){return Co.warn(`evaluateStringCondition: 조건 평가 실패: ${s}`,r),!1}}function uc(s,e,n){if(typeof s=="string")return dm(s,e,n);if("and"in s){if(!Array.isArray(s.and)||s.and.length===0)return Co.warn("evaluateConditionExpression: AND 그룹이 비어있습니다"),!0;for(const r of s.and)if(!uc(r,e,n))return!1;return!0}if("or"in s){if(!Array.isArray(s.or)||s.or.length===0)return Co.warn("evaluateConditionExpression: OR 그룹이 비어있습니다"),!1;for(const r of s.or)if(uc(r,e,n))return!0;return!1}return Co.warn("evaluateConditionExpression: 알 수 없는 조건 형식",s),!1}function fm(s,e,n){if(!Array.isArray(s)||s.length===0)return{matched:!1,branchIndex:-1};for(let r=0;r{or.warn(`evaluateIfCondition: 빈 바인딩 \`{{}}\` (컴포넌트: ${r||"unknown"}) — undefined 로 해석합니다.`)}}):l=n.resolveBindings(s,e,{skipCache:!0}),typeof l=="string"){const c=l.toLowerCase().trim();if(c==="false"||c==="0"||c===""||c==="null"||c==="undefined")return!1}return!!l}catch(i){return or.warn(`evaluateIfCondition: 조건 평가 실패 (컴포넌트: ${r||"unknown"}):`,i),!1}}function fs(s,e,n,r){if(s.if!==void 0)return mm(s.if,e,n,r);if(s.condition!==void 0)return mm(s.condition,e,n,r);if(s.conditions===void 0)return!0;const i=s.conditions;try{return q0(i)?uc(i,e,n):G0(i)?fm(i,e,n).matched:(or.warn(`evaluateRenderCondition: 알 수 없는 conditions 형식 (컴포넌트: ${r||"unknown"})`),!0)}catch(l){return or.warn(`evaluateRenderCondition: conditions 평가 실패 (컴포넌트: ${r||"unknown"}):`,l),!1}}function ym(s){if(!s.responsive)return s;const e=Ci.getWidth(),n=Ci.getMatchingKey(s.responsive,e);if(!n)return s;const r=s.responsive[n];return{...s,props:{...s.props,...r.props},children:r.children??s.children,text:r.text??s.text,if:r.if??s.if,iteration:r.iteration??s.iteration}}function Eo(s,e,n,r,i){if(!s||s.length===0)return[];const l=i?.bindingEngine??new $n,c=i?.translationEngine??Oa.getInstance(),d=i?.translationContext??{templateId:"",locale:"ko"},f=V0(e,i?.componentContext),h=(S,_)=>{if(typeof S=="string")return ic(S)||sc(S)||!/\$t:[a-zA-Z0-9._-]+/.test(S)?S:c.resolveTranslations(S,d,_);if(Array.isArray(S))return S.map(x=>h(x,_));if(S&&typeof S=="object"){const x={};for(const[A,D]of Object.entries(S))x[A]=h(D,_);return x}return S},m=(S,_)=>{if(typeof S=="string"){if(ic(S))return Yd(S);if(sc(S)){const A=[],D=S.replace(new RegExp(`${bo}([^${Si}]*)${Si}`,"g"),(k,L)=>(A.push(L),`${$a}${A.length-1}${$a}`));let O=D;return/\$t:[a-zA-Z0-9._-]+/.test(D)&&(O=c.resolveTranslations(D,d,_)),O.replace(new RegExp(`${$a}(\\d+)${$a}`,"g"),(k,L)=>A[parseInt(L)])}if(S.startsWith("$t:defer:")){const A="$t:"+S.slice(9);return c.resolveTranslations(A,d,_)}if(S.startsWith("$t:"))return c.resolveTranslations(S,d,_);if(/\$t:[a-zA-Z0-9._-]+/.test(S)){const A=S.trim();if(!(A.startsWith("{")&&A.endsWith("}")||A.startsWith("[")&&A.endsWith("]"))&&!S.includes("{{"))return c.resolveTranslations(S,d,_)}const x=gm(S);if(x!==null){let A=!1,D=x;x.startsWith(sr)&&(A=!0,D=x.slice(sr.length));let O=!1,k=Wd(D,_,l,{skipCache:!0,onError:L=>{or.warn("renderItemChildren: 표현식 평가 실패:",L),O=!0},onEmpty:()=>{or.warn("renderItemChildren: 빈 바인딩 `{{}}` 은 해석하지 않습니다.")}});return O?void 0:(A||(k=h(k,_)),A&&k!=null?ds(k):k)}if(S.includes("{{")){const A=l.resolveBindings(S,_,{skipCache:!0});return typeof A=="string"&&/\$t:[a-zA-Z0-9._-]+/.test(A)?c.resolveTranslations(A,d,_):A}return S}if(Array.isArray(S))return S.map(x=>m(x,_));if(S&&typeof S=="object"){if(l.isSwitchExpression(S))return l.resolveSwitch(S,_,{skipCache:!0});if(l.isActionDefinition(S))return{...S};const x={};for(const[A,D]of Object.entries(S))x[A]=m(D,_);return x}return S},y=(S,_)=>{if(!S)return{};const x={};for(const[A,D]of Object.entries(S))x[A]=m(D,_);return x},v=(S,_,x)=>{const A=ym(S),D=A.iteration;if(!D)return[];const O=lf(D.source,x,l);if(!Array.isArray(O))return or.warn(`renderItemChildren: iteration.source가 배열이 아닙니다: ${D.source}`),[];const k=hm();if(k?.isEnabled()){const B=`${_}-iteration`;k.trackIteration(B,D.source,D.item_var,D.index_var,O.length)}const L=n[A.name];return L?O.flatMap((B,q)=>{const z={...x,[D.item_var]:B,[`${D.item_var}_index`]:q,...D.index_var?{[D.index_var]:q}:{}},W=fs({if:A.if,condition:A.condition,conditions:A.conditions},z,l,A.id);if(A.if&&k?.isEnabled()){const Et=`${_}-iter-${q}-if`;k.trackIfCondition(Et,A.if,W,A.name)}if(!W)return[];const de=A.id?String(Ma(m(A.id,z))):void 0,_e=`${_}-iter-${q}`,Se=i?.getRemountKey?i.getRemountKey(de,_e):_e,Ie=y(A.props,z),Fe=cf(Ie,A.actions,z,{actionDispatcher:i?.actionDispatcher,componentContext:i?.componentContext});let ze=null;return A.text!==void 0?ze=m(A.text,z):A.children&&A.children.length>0&&(ze=Eo(A.children,z,n,Se,i)),k?.isEnabled()&&k.trackRender(A.name),[Xe.createElement(L,{key:Se,...Ma(Fe)},Ma(ze))]}):(or.warn(`renderItemChildren: 컴포넌트를 찾을 수 없습니다: ${A.name}`),[])};return s.flatMap((S,_)=>{const x=ym(S),A=x.id?String(Ma(m(x.id,f))):void 0,D=r?`${r}-${A||_}`:A||`child-${_}`,O=i?.getRemountKey?i.getRemountKey(A,D):D;if(x.iteration)return v(x,O,f);const k=fs({if:x.if,condition:x.condition,conditions:x.conditions},f,l,x.id),L=hm();if(x.if&&L?.isEnabled()){const de=`${O}-if`;L.trackIfCondition(de,x.if,k,x.name)}if(!k)return[];const B=n[x.name];if(!B)return or.warn(`renderItemChildren: 컴포넌트를 찾을 수 없습니다: ${x.name}`),[];const q=y(x.props,f),z=cf(q,x.actions,f,{actionDispatcher:i?.actionDispatcher,componentContext:i?.componentContext});let W=null;return x.text!==void 0?W=m(x.text,f):x.children&&x.children.length>0&&(W=Eo(x.children,f,n,O,i)),L?.isEnabled()&&L.trackRender(x.name),[Xe.createElement(B,{key:O,...Ma(z)},Ma(W))]})}function hs(s,e,n,r){let i,l;return n instanceof $n?(i=n,l=r):(i=pm,l=n),i.resolveBindings(s,e,l)}function K0(s,e,n,r){const i=n??pm,l={skipCache:!0,...r};let c;try{c=i.resolveBindings(s.key,e,{skipCache:l.skipCache})?.toString()?.trim()??""}catch(h){or.warn("resolveClassMap: key 평가 실패:",s.key,h),c=""}let d="";c&&s.variants&&c in s.variants?d=s.variants[c]:s.default&&(d=s.default);const f=[];return s.base?.trim()&&f.push(s.base.trim()),d?.trim()&&f.push(d.trim()),f.join(" ")}function bm(s,e){return function(){return s.apply(e,arguments)}}const{toString:W0}=Object.prototype,{getPrototypeOf:Ia}=Object,{iterator:_o,toStringTag:vm}=Symbol,Ao=(({hasOwnProperty:s})=>(e,n)=>s.call(e,n))(Object.prototype),Sm=s=>typeof s=="string"&&(s==="__proto__"||s==="constructor"||s==="prototype"),wm=(s,e,n)=>s===Object.prototype||!n&&e===null,Y0=s=>{if(!Object.isExtensible(s))return!1;const e=Object.getOwnPropertyNames(s);return Object.getOwnPropertySymbols&&e.push(...Object.getOwnPropertySymbols(s)),e.every(n=>{if(Sm(n))return!1;const r=Object.getOwnPropertyDescriptor(s,n);return!!r&&r.configurable&&r.writable===!0})},xo=(s,e)=>{let n=s;const r=[];for(;n!=null;){if(r.indexOf(n)!==-1)return!1;r.push(n);const i=Ia(n);if(wm(n,i,n===s))return!1;if(Ao(n,e))return!0;n=i}return!1},X0=(s,e)=>s!=null&&xo(s,e)?s[e]:void 0,J0=s=>{if(s==null||typeof s!="object"&&typeof s!="function")return s;const e=Ia(s);if(e===null&&Y0(s))return s;const n=Object.create(null),r=Object.create(null),i=[];let l=s;for(;l!=null&&i.indexOf(l)===-1;){i.push(l);const c=l===s?e:Ia(l);if(wm(l,c,l===s))break;const d=Object.getOwnPropertyNames(l);Object.getOwnPropertySymbols&&d.push(...Object.getOwnPropertySymbols(l));for(const f of d)Sm(f)||Ao(r,f)||(n[f]=s[f],r[f]=!0);l=c}return n},uf=(s=>e=>{const n=W0.call(e);return s[n]||(s[n]=n.slice(8,-1).toLowerCase())})(Object.create(null)),lr=s=>(s=s.toLowerCase(),e=>uf(e)===s),dc=s=>e=>typeof e===s,{isArray:Ei}=Array,_i=dc("undefined");function ps(s){return s!==null&&!_i(s)&&s.constructor!==null&&!_i(s.constructor)&&jn(s.constructor.isBuffer)&&s.constructor.isBuffer(s)}const Cm=lr("ArrayBuffer");function Q0(s){let e;return typeof ArrayBuffer<"u"&&ArrayBuffer.isView?e=ArrayBuffer.isView(s):e=s&&s.buffer&&Cm(s.buffer),e}const Z0=dc("string"),jn=dc("function"),Em=dc("number"),gs=s=>s!==null&&typeof s=="object",eC=s=>s===!0||s===!1,fc=s=>{if(!gs(s))return!1;const e=Ia(s);return(e===null||e===Object.prototype||Ia(e)===null)&&!xo(s,vm)&&!xo(s,_o)},tC=s=>{if(!gs(s)||ps(s))return!1;try{return Object.keys(s).length===0&&Object.getPrototypeOf(s)===Object.prototype}catch{return!1}},nC=lr("Date"),rC=lr("File"),aC=s=>!!(s&&typeof s.uri<"u"),iC=s=>s&&typeof s.getParts<"u",sC=lr("Blob"),oC=lr("FileList"),lC=lr("Set"),cC=s=>gs(s)&&jn(s.pipe);function uC(){return typeof globalThis<"u"?globalThis:typeof self<"u"?self:typeof window<"u"?window:typeof global<"u"?global:{}}const _m=uC(),Am=typeof _m.FormData<"u"?_m.FormData:void 0,dC=s=>{if(!s)return!1;if(Am&&s instanceof Am)return!0;const e=Ia(s);if(!e||e===Object.prototype||!jn(s.append))return!1;const n=uf(s);return n==="formdata"||n==="object"&&jn(s.toString)&&s.toString()==="[object FormData]"},fC=lr("URLSearchParams"),[hC,pC,gC,mC]=["ReadableStream","Request","Response","Headers"].map(lr),yC=s=>s.trim?s.trim():s.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,"");function To(s,e,{allOwnKeys:n=!1}={}){if(s===null||typeof s>"u")return;let r,i;if(typeof s!="object"&&(s=[s]),Ei(s))for(r=0,i=s.length;r0;)if(i=n[r],e===i.toLowerCase())return i;return null}const Ai=typeof globalThis<"u"?globalThis:typeof self<"u"?self:typeof window<"u"?window:global,Tm=s=>!_i(s)&&s!==Ai;function df(...s){const{caseless:e,skipUndefined:n}=Tm(this)&&this||{},r={},i=(l,c)=>{if(c==="__proto__"||c==="constructor"||c==="prototype")return;const d=e&&typeof c=="string"&&xm(r,c)||c,f=Ao(r,d)?r[d]:void 0;fc(f)&&fc(l)?r[d]=df(f,l):fc(l)?r[d]=df({},l):Ei(l)?r[d]=l.slice():(!n||!_i(l))&&(r[d]=l)};for(let l=0,c=s.length;l(To(e,(i,l)=>{n&&jn(i)?Object.defineProperty(s,l,{__proto__:null,value:bm(i,n),writable:!0,enumerable:!0,configurable:!0}):Object.defineProperty(s,l,{__proto__:null,value:i,writable:!0,enumerable:!0,configurable:!0})},{allOwnKeys:r}),s),vC=s=>(s.charCodeAt(0)===65279&&(s=s.slice(1)),s),SC=(s,e,n,r)=>{s.prototype=Object.create(e.prototype,r),Object.defineProperty(s.prototype,"constructor",{__proto__:null,value:s,writable:!0,enumerable:!1,configurable:!0}),Object.defineProperty(s,"super",{__proto__:null,value:e.prototype}),n&&Object.assign(s.prototype,n)},wC=(s,e,n,r)=>{let i,l,c;const d={};if(e=e||{},s==null)return e;do{for(i=Object.getOwnPropertyNames(s),l=i.length;l-- >0;)c=i[l],(!r||r(c,s,e))&&!d[c]&&(e[c]=s[c],d[c]=!0);s=n!==!1&&Ia(s)}while(s&&(!n||n(s,e))&&s!==Object.prototype);return e},CC=(s,e,n)=>{s=String(s),(n===void 0||n>s.length)&&(n=s.length),n-=e.length;const r=s.indexOf(e,n);return r!==-1&&r===n},EC=s=>{if(!s)return null;if(Ei(s))return s;let e=s.length;if(!Em(e))return null;const n=new Array(e);for(;e-- >0;)n[e]=s[e];return n},_C=(s=>e=>s&&e instanceof s)(typeof Uint8Array<"u"&&Ia(Uint8Array)),AC=(s,e)=>{const r=(s&&s[_o]).call(s);let i;for(;(i=r.next())&&!i.done;){const l=i.value;e.call(s,l[0],l[1])}},xC=(s,e)=>{let n;const r=[];for(;(n=s.exec(e))!==null;)r.push(n);return r},TC=lr("HTMLFormElement"),kC=s=>s.toLowerCase().replace(/[-_\s]([a-z\d])(\w*)/g,function(n,r,i){return r.toUpperCase()+i}),{propertyIsEnumerable:RC}=Object.prototype,DC=lr("RegExp"),km=(s,e)=>{const n=Object.getOwnPropertyDescriptors(s),r={};To(n,(i,l)=>{let c;(c=e(i,l,s))!==!1&&(r[l]=c||i)}),Object.defineProperties(s,r)},OC=s=>{km(s,(e,n)=>{if(jn(s)&&["arguments","caller","callee"].includes(n))return!1;const r=s[n];if(jn(r)){if(e.enumerable=!1,"writable"in e){e.writable=!1;return}e.set||(e.set=()=>{throw Error("Can not rewrite read-only method '"+n+"'")})}})},LC=(s,e)=>{const n={},r=i=>{i.forEach(l=>{n[l]=!0})};return Ei(s)?r(s):r(String(s).split(e)),n},$C=()=>{},MC=(s,e)=>s!=null&&Number.isFinite(s=+s)?s:e;function NC(s){return!!(s&&jn(s.append)&&s[vm]==="FormData"&&s[_o])}const IC=s=>{const e=new WeakSet,n=r=>{if(gs(r)){if(e.has(r))return;if(ps(r))return r;if(!("toJSON"in r)){e.add(r);let i;if(lC(r)){i=[];for(const l of r){const c=n(l);!_i(c)&&i.push(c)}}else i=Ei(r)?[]:{},To(r,(l,c)=>{const d=n(l);!_i(d)&&(i[c]=d)});return e.delete(r),i}}return r};return n(s)},jC=lr("AsyncFunction"),HC=s=>s&&(gs(s)||jn(s))&&jn(s.then)&&jn(s.catch),Rm=((s,e)=>s?setImmediate:e?((n,r)=>(Ai.addEventListener("message",({source:i,data:l})=>{i===Ai&&l===n&&r.length&&r.shift()()},!1),i=>{r.push(i),Ai.postMessage(n,"*")}))(`axios@${Math.random()}`,[]):n=>setTimeout(n))(typeof setImmediate=="function",jn(Ai.postMessage)),zC=typeof queueMicrotask<"u"?queueMicrotask.bind(Ai):typeof process<"u"&&process.nextTick||Rm,Dm=s=>s!=null&&jn(s[_o]),G={isArray:Ei,isArrayBuffer:Cm,isBuffer:ps,isFormData:dC,isArrayBufferView:Q0,isString:Z0,isNumber:Em,isBoolean:eC,isObject:gs,isPlainObject:fc,isEmptyObject:tC,isReadableStream:hC,isRequest:pC,isResponse:gC,isHeaders:mC,isUndefined:_i,isDate:nC,isFile:rC,isReactNativeBlob:aC,isReactNative:iC,isBlob:sC,isRegExp:DC,isFunction:jn,isStream:cC,isURLSearchParams:fC,isTypedArray:_C,isFileList:oC,forEach:To,merge:df,extend:bC,trim:yC,stripBOM:vC,inherits:SC,toFlatObject:wC,kindOf:uf,kindOfTest:lr,endsWith:CC,toArray:EC,forEachEntry:AC,matchAll:xC,isHTMLForm:TC,hasOwnProperty:Ao,hasOwnProp:Ao,hasOwnInPrototypeChain:xo,getSafeProp:X0,toSafeFlatObject:J0,reduceDescriptors:km,freezeMethods:OC,toObjectSet:LC,toCamelCase:kC,noop:$C,toFiniteNumber:MC,findKey:xm,global:Ai,isContextDefined:Tm,isSpecCompliantForm:NC,toJSONObject:IC,isAsyncFn:jC,isThenable:HC,setImmediate:Rm,asap:zC,isIterable:Dm,isSafeIterable:s=>s!=null&&xo(s,_o)&&Dm(s)},PC=G.toObjectSet(["age","authorization","content-length","content-type","etag","expires","from","host","if-modified-since","if-unmodified-since","last-modified","location","max-forwards","proxy-authorization","referer","retry-after","user-agent"]),UC=s=>{const e={};let n,r,i;return s&&s.split(` -`).forEach(function(c){i=c.indexOf(":"),n=c.substring(0,i).trim().toLowerCase(),r=c.substring(i+1).trim();const d=G.hasOwnProp(e,n);!n||d&&G.hasOwnProp(PC,n)||(n==="set-cookie"?d?e[n].push(r):e[n]=[r]:e[n]=d?e[n]+", "+r:r)}),e};function BC(s){let e=0,n=s.length;for(;ee;){const r=s.charCodeAt(n-1);if(r!==9&&r!==32)break;n-=1}return e===0&&n===s.length?s:s.slice(e,n)}const qC=new RegExp("[\\u0000-\\u0008\\u000a-\\u001f\\u007f]+","g"),GC=new RegExp("[^\\u0009\\u0020-\\u007e\\u0080-\\u00ff]+","g");function ff(s,e){return G.isArray(s)?s.map(n=>ff(n,e)):BC(String(s).replace(e,""))}const FC=s=>ff(s,qC),VC=s=>ff(s,GC);function Om(s){const e=Object.create(null);return G.forEach(s.toJSON(),(n,r)=>{e[r]=VC(n)}),e}const Lm=Symbol("internals");function ko(s){return s&&String(s).trim().toLowerCase()}function hc(s){return s===!1||s==null?s:G.isArray(s)?s.map(hc):FC(String(s))}function KC(s){const e=Object.create(null),n=/([^\s,;=]+)\s*(?:=\s*([^,;]+))?/g;let r;for(;r=n.exec(s);)e[r[1]]=r[2];return e}const WC=/^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/;function hf(s){let e=0,n=s.length;for(;ee;){const r=s.charCodeAt(n-1);if(r!==9&&r!==32)break;n-=1}return e===0&&n===s.length?s:s.slice(e,n)}function YC(s){const e=s.length-1;if(e<1||s.charCodeAt(0)!==34||s.charCodeAt(e)!==34)return s;let n="";for(let r=1;r=e))return s;n+=s[r]}return n}function XC(s){const e=Object.create(null),n=String(s);let r=0,i=!1,l=!1;function c(d){const f=hf(n.slice(r,d)),h=f.indexOf("=");if(h<1)return;const m=hf(f.slice(0,h));if(!WC.test(m))return;const y=m.toLowerCase();if(y==="__proto__"||y==="constructor"||y==="prototype")return;const v=hf(f.slice(h+1));e[y]=YC(v)}for(let d=0;d/^[-_a-zA-Z0-9^`|~,!#$%&'*+.]+$/.test(s.trim());function pf(s,e,n,r,i){if(G.isFunction(r))return r.call(this,e,n);if(i&&(e=n),!!G.isString(e)){if(G.isString(r))return e.indexOf(r)!==-1;if(G.isRegExp(r))return r.test(e)}}function QC(s){return s.trim().toLowerCase().replace(/([a-z\d])(\w*)/g,(e,n,r)=>n.toUpperCase()+r)}function ZC(s,e){const n=G.toCamelCase(" "+e);["get","set","has"].forEach(r=>{Object.defineProperty(s,r+n,{__proto__:null,value:function(i,l,c){return this[r].call(this,e,i,l,c)},configurable:!0})})}let xn=class{constructor(e){e&&this.set(e)}set(e,n,r){const i=this;function l(d,f,h){const m=ko(f);if(!m)return;const y=G.findKey(i,m);(!y||i[y]===void 0||h===!0||h===void 0&&i[y]!==!1)&&(i[y||f]=hc(d))}const c=(d,f)=>G.forEach(d,(h,m)=>l(h,m,f));if(G.isPlainObject(e)||e instanceof this.constructor)c(e,n);else if(G.isString(e)&&(e=e.trim())&&!JC(e))c(UC(e),n);else if(G.isObject(e)&&G.isSafeIterable(e)){let d=Object.create(null),f,h;for(const m of e){if(!G.isArray(m))throw new TypeError("Object iterator must return a key-value pair");h=m[0],G.hasOwnProp(d,h)?(f=d[h],d[h]=G.isArray(f)?[...f,m[1]]:[f,m[1]]):d[h]=m[1]}c(d,n)}else e!=null&&l(n,e,r);return this}get(e,n){if(e=ko(e),e){const r=G.findKey(this,e);if(r){const i=this[r];if(!n)return i;if(n===!0)return KC(i);if(G.isFunction(n))return n.call(this,i,r);if(G.isRegExp(n))return n.exec(i);throw new TypeError("parser must be boolean|regexp|function")}}}has(e,n){if(e=ko(e),e){const r=G.findKey(this,e);return!!(r&&this[r]!==void 0&&(!n||pf(this,this[r],r,n)))}return!1}delete(e,n){const r=this;let i=!1;function l(c){if(c=ko(c),c){const d=G.findKey(r,c);d&&(!n||pf(r,r[d],d,n))&&(delete r[d],i=!0)}}return G.isArray(e)?e.forEach(l):l(e),i}clear(e){const n=Object.keys(this);let r=n.length,i=!1;for(;r--;){const l=n[r];(!e||pf(this,this[l],l,e,!0))&&(delete this[l],i=!0)}return i}normalize(e){const n=this,r={};return G.forEach(this,(i,l)=>{const c=G.findKey(r,l);if(c){n[c]=hc(i),delete n[l];return}const d=e?QC(l):String(l).trim();d!==l&&delete n[l],n[d]=hc(i),r[d]=!0}),this}concat(...e){return this.constructor.concat(this,...e)}toJSON(e){const n=Object.create(null);return G.forEach(this,(r,i)=>{r!=null&&r!==!1&&(n[i]=e&&G.isArray(r)?r.join(", "):r)}),n}[Symbol.iterator](){return Object.entries(this.toJSON())[Symbol.iterator]()}toString(){return Object.entries(this.toJSON()).map(([e,n])=>e+": "+n).join(` -`)}getSetCookie(){const e=this.get("set-cookie");return G.isArray(e)?e:e==null||e===!1?[]:[e]}get[Symbol.toStringTag](){return"AxiosHeaders"}static from(e){return e instanceof this?e:new this(e)}static parseParameters(e){return XC(e)}static concat(e,...n){const r=new this(e);return n.forEach(i=>r.set(i)),r}static accessor(e){const r=(this[Lm]=this[Lm]={accessors:{}}).accessors,i=this.prototype;function l(c){const d=ko(c);r[d]||(ZC(i,c),r[d]=!0)}return G.isArray(e)?e.forEach(l):l(e),this}};xn.accessor(["Content-Type","Content-Length","Accept","Accept-Encoding","User-Agent","Authorization"]),G.reduceDescriptors(xn.prototype,({value:s},e)=>{let n=e[0].toUpperCase()+e.slice(1);return{get:()=>s,set(r){this[n]=r}}}),G.freezeMethods(xn);const pc="[REDACTED ****]";function eE(s){if(G.hasOwnProp(s,"toJSON"))return!0;let e=Object.getPrototypeOf(s);for(;e&&e!==Object.prototype;){if(G.hasOwnProp(e,"toJSON"))return!0;e=Object.getPrototypeOf(e)}return!1}function tE(s,e){const n=new Set(e.map(l=>String(l).toLowerCase())),r=[],i=l=>{if(l===null||typeof l!="object"||G.isBuffer(l))return l;if(r.indexOf(l)!==-1)return;l instanceof xn&&(l=l.toJSON()),r.push(l);let c;if(G.isArray(l))c=[],l.forEach((d,f)=>{const h=i(d);G.isUndefined(h)||(c[f]=h)});else{if(!G.isPlainObject(l)&&eE(l))return r.pop(),l;c=Object.create(null);for(const[d,f]of Object.entries(l)){const h=n.has(d.toLowerCase())?pc:i(f);G.isUndefined(h)||(c[d]=h)}}return r.pop(),c};return i(s)}function $m(s){try{return String(s)}catch{return""}}function nE(s){return s.errors.map(n=>{try{return n&&n.message?$m(n.message):$m(n)}catch{return""}}).filter(Boolean).join("; ")||s.name||"AggregateError"}let we=class n0 extends Error{static from(e,n,r,i,l,c){let d=e.message;!d&&G.isArray(e.errors)&&e.errors.length&&(d=nE(e));const f=new n0(d,n||e.code,r,i,l);return Object.defineProperty(f,"cause",{__proto__:null,value:e,writable:!0,enumerable:!1,configurable:!0}),f.name=e.name,e.status!=null&&f.status==null&&(f.status=e.status),c&&Object.assign(f,c),f}constructor(e,n,r,i,l){super(e),Object.defineProperty(this,"message",{__proto__:null,value:e,enumerable:!0,writable:!0,configurable:!0}),this.name="AxiosError",this.isAxiosError=!0,n&&(this.code=n),r&&(this.config=r),i&&(this.request=i),l&&(this.response=l,this.status=l.status)}toJSON(){const e=this.config,n=e&&G.hasOwnProp(e,"redact")?e.redact:void 0,r=G.isArray(n)&&n.length>0?tE(e,n):G.toJSONObject(e);return{message:this.message,name:this.name,description:this.description,number:this.number,fileName:this.fileName,lineNumber:this.lineNumber,columnNumber:this.columnNumber,stack:this.stack,config:r,code:this.code,status:this.status}}};we.ERR_BAD_OPTION_VALUE="ERR_BAD_OPTION_VALUE",we.ERR_BAD_OPTION="ERR_BAD_OPTION",we.ECONNABORTED="ECONNABORTED",we.ETIMEDOUT="ETIMEDOUT",we.ECONNREFUSED="ECONNREFUSED",we.ERR_NETWORK="ERR_NETWORK",we.ERR_FR_TOO_MANY_REDIRECTS="ERR_FR_TOO_MANY_REDIRECTS",we.ERR_DEPRECATED="ERR_DEPRECATED",we.ERR_BAD_RESPONSE="ERR_BAD_RESPONSE",we.ERR_BAD_REQUEST="ERR_BAD_REQUEST",we.ERR_CANCELED="ERR_CANCELED",we.ERR_NOT_SUPPORT="ERR_NOT_SUPPORT",we.ERR_INVALID_URL="ERR_INVALID_URL",we.ERR_FORM_DATA_DEPTH_EXCEEDED="ERR_FORM_DATA_DEPTH_EXCEEDED";const rE=null,Mm=100;function gf(s){return G.isPlainObject(s)||G.isArray(s)}function Nm(s){return G.endsWith(s,"[]")?s.slice(0,-2):s}function mf(s,e,n){return s?s.concat(e).map(function(i,l){return i=Nm(i),!n&&l?"["+i+"]":i}).join(n?".":""):e}function aE(s){return G.isArray(s)&&!s.some(gf)}const iE=G.toFlatObject(G,{},null,function(e){return/^is[A-Z]/.test(e)});function gc(s,e,n){if(!G.isObject(s))throw new TypeError("target must be an object");e=e||new FormData;const r=(O,k)=>{const L=G.getSafeProp(n,O);return G.isUndefined(L)?k:L},i=r("metaTokens",!0),l=r("visitor")||x,c=r("dots",!1),d=r("indexes",!1),f=r("Blob")||typeof Blob<"u"&&Blob,h=r("maxDepth",Mm),m=f&&G.isSpecCompliantForm(e),y=[];if(!G.isFunction(l))throw new TypeError("visitor must be a function");function v(O){if(O===null)return"";if(G.isDate(O))return O.toISOString();if(G.isBoolean(O))return O.toString();if(!m&&G.isBlob(O))throw new we("Blob is not supported. Use a Buffer instead.");if(G.isArrayBuffer(O)||G.isTypedArray(O)){if(m&&typeof f=="function")return new f([O]);throw new we("Blob is not supported. Use a Buffer instead.",we.ERR_NOT_SUPPORT)}return O}function S(O){if(O>h)throw new we("Object is too deeply nested ("+O+" levels). Max depth: "+h,we.ERR_FORM_DATA_DEPTH_EXCEEDED)}function _(O,k){if(h===1/0)return JSON.stringify(O);const L=[];return JSON.stringify(O,function(q,z){if(!G.isObject(z))return z;for(;L.length&&L[L.length-1]!==this;)L.pop();return L.push(z),S(k+L.length-1),z})}function x(O,k,L){let B=O;if(G.isReactNative(e)&&G.isReactNativeBlob(O))return e.append(mf(L,k,c),v(O)),!1;if(O&&!L&&typeof O=="object"){if(G.endsWith(k,"{}"))k=i?k:k.slice(0,-2),O=_(O,1);else if(G.isArray(O)&&aE(O)||(G.isFileList(O)||G.endsWith(k,"[]"))&&(B=G.toArray(O)))return k=Nm(k),B.forEach(function(z,W){!(G.isUndefined(z)||z===null)&&e.append(d===!0?mf([k],W,c):d===null?k:k+"[]",v(z))}),!1}return gf(O)?!0:(e.append(mf(L,k,c),v(O)),!1)}const A=Object.assign(iE,{defaultVisitor:x,convertValue:v,isVisitable:gf});function D(O,k,L=0){if(!G.isUndefined(O)){if(S(L),y.indexOf(O)!==-1)throw new Error("Circular reference detected in "+k.join("."));y.push(O),G.forEach(O,function(q,z){(!(G.isUndefined(q)||q===null)&&l.call(e,q,G.isString(z)?z.trim():z,k,A))===!0&&D(q,k?k.concat(z):[z],L+1)}),y.pop()}}if(!G.isObject(s))throw new TypeError("data must be an object");return D(s),e}function Im(s){const e={"!":"%21","'":"%27","(":"%28",")":"%29","~":"%7E","%20":"+"};return encodeURIComponent(s).replace(/[!'()~]|%20/g,function(r){return e[r]})}function yf(s,e){this._pairs=[],s&&gc(s,this,e)}const jm=yf.prototype;jm.append=function(e,n){this._pairs.push([e,n])},jm.toString=function(e){const n=e?r=>e.call(this,r,Im):Im;return this._pairs.map(function(i){return n(i[0])+"="+n(i[1])},"").join("&")};function sE(s){return encodeURIComponent(s).replace(/%3A/gi,":").replace(/%24/g,"$").replace(/%2C/gi,",").replace(/%20/g,"+")}function Hm(s,e,n){if(!e)return s;s=s||"";const r=G.isFunction(n)?{serialize:n}:n,i=G.getSafeProp(r,"encode")||sE,l=G.getSafeProp(r,"serialize");let c;if(l?c=l(e,r):c=G.isURLSearchParams(e)?e.toString():new yf(e,r).toString(i),c){const d=s.indexOf("#");d!==-1&&(s=s.slice(0,d)),s+=(s.indexOf("?")===-1?"?":"&")+c}return s}const Ro=Symbol("internals");function zm(s){return s?s.length:0}function Pm(s){if(s)for(;s.length&&s[s.length-1]===null;)s.pop()}function Do(s,e){const n=s.handlers,r=zm(n);n!==e.handlersRef?(e.handlersRef=n,e.handlerEntries.clear()):r!==e.handlersLength&&(r?e.handlerEntries.forEach(function(l,c){n[l.index]!==l.handler&&e.handlerEntries.delete(c)}):e.handlerEntries.clear()),e.handlersLength=r}class Um{constructor(){this.handlers=[],this[Ro]={handlersRef:this.handlers,handlersLength:this.handlers.length,handlerEntries:new Map,iterationDepth:0,nextId:0}}use(e,n,r){const i={fulfilled:e,rejected:n,synchronous:r?r.synchronous:!1,runWhen:r?r.runWhen:null},l=this[Ro];this.handlers==null&&(this.handlers=[]),Do(this,l);const c=l.nextId++;return this.handlers.push(i),l.handlerEntries.set(c,{handler:i,index:this.handlers.length-1}),l.handlersLength=this.handlers.length,c}eject(e){const n=this[Ro];Do(this,n);const r=n.handlerEntries.get(e);if(r){if(n.handlerEntries.delete(e),this.handlers[r.index]!==r.handler)return;this.handlers[r.index]=null,n.iterationDepth||(Pm(this.handlers),n.handlersLength=this.handlers.length)}}clear(){this.handlers&&(this.handlers=[],Do(this,this[Ro]))}forEach(e){const n=this[Ro];Do(this,n),n.iterationDepth++;try{G.forEach(this.handlers,function(i){i!==null&&e(i)})}finally{--n.iterationDepth||(Do(this,n),Pm(this.handlers),n.handlersLength=zm(this.handlers))}}}const bf={silentJSONParsing:!0,forcedJSONParsing:!0,clarifyTimeoutError:!1,legacyInterceptorReqResOrdering:!0,advertiseZstdAcceptEncoding:!1,validateStatusUndefinedResolves:!0},oE={isBrowser:!0,classes:{URLSearchParams:typeof URLSearchParams<"u"?URLSearchParams:yf,FormData:typeof FormData<"u"?FormData:null,Blob:typeof Blob<"u"?Blob:null},protocols:["http","https","file","blob","url","data"]},vf=typeof window<"u"&&typeof document<"u",Sf=typeof navigator=="object"&&navigator||void 0,lE=vf&&(!Sf||["ReactNative","NativeScript","NS"].indexOf(Sf.product)<0),cE=typeof WorkerGlobalScope<"u"&&self instanceof WorkerGlobalScope&&typeof self.importScripts=="function",uE=vf&&window.location.href||"http://localhost",gn={...Object.freeze(Object.defineProperty({__proto__:null,hasBrowserEnv:vf,hasStandardBrowserEnv:lE,hasStandardBrowserWebWorkerEnv:cE,navigator:Sf,origin:uE},Symbol.toStringTag,{value:"Module"})),...oE};function dE(s,e){return gc(s,new gn.classes.URLSearchParams,{visitor:function(n,r,i,l){return gn.isNode&&G.isBuffer(n)?(this.append(r,n.toString("base64")),!1):l.defaultVisitor.apply(this,arguments)},...e})}const Bm=Mm;function qm(s){if(s>Bm)throw new we("FormData field is too deeply nested ("+s+" levels). Max depth: "+Bm,we.ERR_FORM_DATA_DEPTH_EXCEEDED)}function fE(s){const e=[],n=/[^.[\]]+|\[([^.[\]]*)]/g;let r;for(;(r=n.exec(s))!==null;)qm(e.length),e.push(r[0]==="[]"?"":r[1]||r[0]);return e}function hE(s){const e={},n=Object.keys(s);let r;const i=n.length;let l;for(r=0;r=n.length;return c=!c&&G.isArray(i)?i.length:c,f?(G.hasOwnProp(i,c)?i[c]=G.isArray(i[c])?i[c].concat(r):[i[c],r]:i[c]=r,!d):((!G.hasOwnProp(i,c)||!G.isObject(i[c]))&&(i[c]=[]),e(n,r,i[c],l)&&G.isArray(i[c])&&(i[c]=hE(i[c])),!d)}if(G.isFormData(s)&&G.isFunction(s.entries)){const n={};return G.forEachEntry(s,(r,i)=>{e(fE(r),i,n,0)}),n}return null}const Fm=Object.freeze(["get","delete","head","options","post","put","patch","purge","link","unlink","query"]),ms=(s,e)=>s!=null&&G.hasOwnProp(s,e)?s[e]:void 0;function pE(s,e,n){if(G.isString(s))try{return(e||JSON.parse)(s),G.trim(s)}catch(r){if(r.name!=="SyntaxError")throw r}return(n||JSON.stringify)(s)}const Oo={transitional:bf,adapter:["xhr","http","fetch"],transformRequest:[function(e,n){const r=n.getContentType()||"",i=r.indexOf("application/json")>-1,l=G.isObject(e);if(l&&G.isHTMLForm(e)&&(e=new FormData(e)),G.isFormData(e))return i?JSON.stringify(Gm(e)):e;if(G.isArrayBuffer(e)||G.isBuffer(e)||G.isStream(e)||G.isFile(e)||G.isBlob(e)||G.isReadableStream(e))return e;if(G.isArrayBufferView(e))return e.buffer;if(G.isURLSearchParams(e))return n.setContentType("application/x-www-form-urlencoded;charset=utf-8",!1),e.toString();let d;if(l){const f=ms(this,"formSerializer");if(r.indexOf("application/x-www-form-urlencoded")>-1)return dE(e,f).toString();if((d=G.isFileList(e))||r.indexOf("multipart/form-data")>-1){const h=ms(this,"env"),m=h&&h.FormData;return gc(d?{"files[]":e}:e,m&&new m,f)}}return l||i?(n.setContentType("application/json",!1),pE(e)):e}],transformResponse:[function(e){const n=ms(this,"transitional")||Oo.transitional,r=n&&n.forcedJSONParsing,i=ms(this,"responseType"),l=i==="json";if(G.isResponse(e)||G.isReadableStream(e))return e;if(e&&G.isString(e)&&(r&&!i||l)){const d=!(n&&n.silentJSONParsing)&&l;try{return JSON.parse(e,ms(this,"parseReviver"))}catch(f){if(d)throw f.name==="SyntaxError"?we.from(f,we.ERR_BAD_RESPONSE,this,null,ms(this,"response")):f}}return e}],timeout:0,xsrfCookieName:"XSRF-TOKEN",xsrfHeaderName:"X-XSRF-TOKEN",maxContentLength:-1,maxBodyLength:-1,env:{FormData:gn.classes.FormData,Blob:gn.classes.Blob},validateStatus:function(e){return e>=200&&e<300},headers:{common:{Accept:"application/json, text/plain, */*","Content-Type":void 0}}};G.forEach(Fm,s=>{Oo.headers[s]={}});function wf(s,e){const n=this||Oo,r=e||n,i=xn.from(r.headers);let l=r.data;return G.forEach(s,function(d){l=d.call(n,l,i.normalize(),e?e.status:void 0)}),i.normalize(),l}function Vm(s){return!!(s&&s.__CANCEL__)}let Lo=class extends we{constructor(e,n,r){super(e??"canceled",we.ERR_CANCELED,n,r),this.name="CanceledError",this.__CANCEL__=!0}};function Km(s,e,n){const r=n.config.validateStatus;!n.status||!r||r(n.status)?s(n):e(new we("Request failed with status code "+n.status,n.status>=400&&n.status<500?we.ERR_BAD_REQUEST:we.ERR_BAD_RESPONSE,n.config,n.request,n))}const gE=/[\t\n\r]/g;function Wm(s){if(typeof s!="string")return s;let e=0;for(;e{n=y,i=null,l&&(clearTimeout(l),l=null),s(...m)};return[(...m)=>{const y=Date.now(),v=y-n;v>=r?c(m,y):(i=m,l||(l=setTimeout(()=>{l=null,c(i)},r-v)))},()=>i&&c(i),(...m)=>c(m)]}const mc=(s,e,n=3)=>{let r=0;const i=mE(50,250);return yE(l=>{if(!l||!G.isNumber(l.loaded))return;const c=l.loaded,d=l.lengthComputable?l.total:void 0,f=Math.max(0,d!=null?Math.min(c,d):c),h=Math.max(0,f-r),m=i(h);r=Math.max(r,f);const y={loaded:f,total:d,progress:d?f/d:void 0,bytes:h,rate:m||void 0,estimated:m&&d?(d-f)/m:void 0,event:l,lengthComputable:d!=null,[e?"download":"upload"]:!0};s(y)},n)},Ym=(s,e)=>{const n=s!=null;return[r=>e[0]({lengthComputable:n,total:s,loaded:r}),e[1]]},Xm=(s,e=G.asap)=>(...n)=>e(()=>s(...n)),bE=gn.hasStandardBrowserEnv?((s,e)=>n=>(n=new URL(n,gn.origin),s.protocol===n.protocol&&s.host===n.host&&(e||s.port===n.port)))(new URL(gn.origin),gn.navigator&&/(msie|trident)/i.test(gn.navigator.userAgent)):()=>!0,vE=gn.hasStandardBrowserEnv?{write(s,e,n,r,i,l,c){if(typeof document>"u")return;const d=[`${s}=${encodeURIComponent(e)}`];G.isNumber(n)&&d.push(`expires=${new Date(n).toUTCString()}`),G.isString(r)&&d.push(`path=${r}`),G.isString(i)&&d.push(`domain=${i}`),l===!0&&d.push("secure"),G.isString(c)&&d.push(`SameSite=${c}`),document.cookie=d.join("; ")},read(s){if(typeof document>"u")return null;const e=document.cookie.split(";");for(let n=0;n0&&s.charCodeAt(n-1)===47;)n--;return s.slice(0,n)+"/"+e.replace(/^\/+/,"")}const CE=/^https?:(?!\/\/)/i;function EE(s){return s&&s.replace(/(^|&)([^=&]*=)?[^&]+/g,(e,n,r="")=>`${n}${r}${pc}`)}function _E(s){const e=s.replace(/^(https?:\/{0,2})[^/?#]*@/i,`$1${pc}@`),n=e.indexOf("#"),i=(n===-1?e:e.slice(0,n)).replace(/([?&][^=&#]*=)[^&#]*/g,`$1${pc}`);return n===-1?i:`${i}#${EE(e.slice(n+1))}`}function Jm(s,e){if(typeof s=="string"){const n=Wm(s);if(CE.test(n))throw new we(`Invalid URL ${JSON.stringify(_E(n))}: missing "//" after protocol`,we.ERR_INVALID_URL,e)}}function Qm(s,e,n,r){Jm(e,r);let i=!SE(e);return s&&(i||n===!1)?(Jm(s,r),wE(s,e)):e}const Zm=s=>s instanceof xn?{...s}:s,AE=s=>Object.getOwnPropertySymbols&&Object.getOwnPropertyDescriptor?Object.keys(s).concat(Object.getOwnPropertySymbols(s).filter(e=>Object.getOwnPropertyDescriptor(s,e).enumerable)):Object.keys(s);function xi(s,e){s=s||{},e=e||{};const n=Object.create(null);Object.defineProperty(n,"hasOwnProperty",{__proto__:null,value:Object.prototype.hasOwnProperty,enumerable:!1,writable:!0,configurable:!0});function r(m,y,v,S){return G.isPlainObject(m)&&G.isPlainObject(y)?G.merge.call({caseless:S},m,y):G.isPlainObject(y)?G.merge({},y):G.isArray(y)?y.slice():y}function i(m,y,v,S){if(G.isUndefined(y)){if(!G.isUndefined(m))return r(void 0,m,v,S)}else return r(m,y,v,S)}function l(m,y){if(!G.isUndefined(y))return r(void 0,y)}function c(m,y){if(G.isUndefined(y)){if(!G.isUndefined(m))return r(void 0,m)}else return r(void 0,y)}function d(m){const y=G.hasOwnProp(e,"transitional")?e.transitional:void 0;if(!G.isUndefined(y))if(G.isPlainObject(y)){if(G.hasOwnProp(y,m))return y[m]}else return;const v=G.hasOwnProp(s,"transitional")?s.transitional:void 0;if(G.isPlainObject(v)&&G.hasOwnProp(v,m))return v[m]}function f(m,y,v){if(G.hasOwnProp(e,v))return r(m,y);if(G.hasOwnProp(s,v))return r(void 0,m)}const h={url:l,method:l,data:l,baseURL:c,transformRequest:c,transformResponse:c,paramsSerializer:c,timeout:c,timeoutErrorMessage:c,withCredentials:c,withXSRFToken:c,adapter:c,responseType:c,xsrfCookieName:c,xsrfHeaderName:c,onUploadProgress:c,onDownloadProgress:c,decompress:c,maxContentLength:c,maxBodyLength:c,beforeRedirect:c,transport:c,httpAgent:c,httpsAgent:c,cancelToken:c,socketPath:c,allowedSocketPaths:c,responseEncoding:c,validateStatus:f,headers:(m,y,v)=>i(Zm(m),Zm(y),v,!0)};return G.forEach(AE({...s,...e}),function(y){if(y==="__proto__"||y==="constructor"||y==="prototype")return;const v=G.hasOwnProp(h,y)?h[y]:i,S=G.hasOwnProp(s,y)?s[y]:void 0,_=G.hasOwnProp(e,y)?e[y]:void 0,x=v(S,_,y);G.isUndefined(x)&&v!==f||(n[y]=x)}),G.hasOwnProp(e,"validateStatus")&&G.isUndefined(e.validateStatus)&&d("validateStatusUndefinedResolves")===!1&&(G.hasOwnProp(s,"validateStatus")?n.validateStatus=r(void 0,s.validateStatus):delete n.validateStatus),n}const xE=["content-type","content-length"];function TE(s,e,n){if(n!=="content-only"){s.set(e);return}Object.entries(e||{}).forEach(([r,i])=>{xE.includes(r.toLowerCase())&&s.set(r,i)})}const kE=s=>encodeURIComponent(s).replace(/%([0-9A-F]{2})/gi,(e,n)=>String.fromCharCode(parseInt(n,16)));function ey(s){const e=xi({},s),n=v=>G.hasOwnProp(e,v)?e[v]:void 0,r=n("data");let i=n("withXSRFToken");const l=n("xsrfHeaderName"),c=n("xsrfCookieName");let d=n("headers");const f=n("auth"),h=n("baseURL"),m=n("allowAbsoluteUrls"),y=n("url");if(e.headers=d=xn.from(d),e.url=Hm(Qm(h,y,m,e),n("params"),n("paramsSerializer")),f){const v=G.getSafeProp(f,"username")||"",S=G.getSafeProp(f,"password")||"";try{d.set("Authorization","Basic "+btoa(v+":"+(S?kE(S):"")))}catch(_){throw we.from(_,we.ERR_BAD_OPTION_VALUE,s)}}if(G.isFormData(r)){const v=G.getSafeProp(r,"getHeaders");gn.hasStandardBrowserEnv||gn.hasStandardBrowserWebWorkerEnv||G.isReactNative(r)?d.setContentType(void 0):G.isFunction(v)&&TE(d,v.call(r),n("formDataHeaderPolicy"))}if(gn.hasStandardBrowserEnv&&(G.isFunction(i)&&(i=i(e)),i===!0||i==null&&bE(e.url))){const S=l&&c&&vE.read(c);S&&d.set(l,S)}return e}const RE=typeof XMLHttpRequest<"u"&&function(s){return new Promise(function(n,r){const i=ey(s);let l=i.data;const c=xn.from(i.headers).normalize();let{responseType:d,onUploadProgress:f,onDownloadProgress:h}=i,m,y,v,S,_,x;function A(){S&&S(),_&&_(),i.cancelToken&&i.cancelToken.unsubscribe(m),i.signal&&i.signal.removeEventListener("abort",m)}let D=new XMLHttpRequest;D.open(i.method.toUpperCase(),i.url,!0),D.timeout=i.timeout;function O(L){if(!D)return;if(D.status===0&&(Cf(Wm(i.url))||Cf(gn.origin))!=="file"&&!(D.responseURL&&D.responseURL.startsWith("file:"))){r(new we("Request aborted",we.ECONNABORTED,s,D)),A(),D=null;return}try{L?x&&x(L):_&&_()}catch(W){setTimeout(()=>{throw W})}if(!D)return;const B=xn.from("getAllResponseHeaders"in D&&D.getAllResponseHeaders()),z={data:!d||d==="text"||d==="json"?D.responseText:D.response,status:D.status,statusText:D.statusText,headers:B,config:s,request:D};Km(function(de){n(de),A()},function(de){r(de),A()},z),D=null}"onloadend"in D?D.onloadend=O:D.onreadystatechange=function(){!D||D.readyState!==4||D.status===0&&!(D.responseURL&&D.responseURL.startsWith("file:"))||setTimeout(O)},D.onabort=function(){D&&(r(new we("Request aborted",we.ECONNABORTED,s,D)),A(),D=null)},D.onerror=function(B){const q=B&&B.message?B.message:"Network Error",z=new we(q,we.ERR_NETWORK,s,D);z.event=B||null,r(z),A(),D=null},D.ontimeout=function(){let B=i.timeout?"timeout of "+i.timeout+"ms exceeded":"timeout exceeded";const q=i.transitional||bf;i.timeoutErrorMessage&&(B=i.timeoutErrorMessage),r(new we(B,q.clarifyTimeoutError?we.ETIMEDOUT:we.ECONNABORTED,s,D)),A(),D=null},l===void 0&&c.setContentType(null),"setRequestHeader"in D&&G.forEach(Om(c),function(B,q){D.setRequestHeader(q,B)}),G.isUndefined(i.withCredentials)||(D.withCredentials=!!i.withCredentials),d&&d!=="json"&&(D.responseType=i.responseType),h&&([v,_,x]=mc(h,!0),D.addEventListener("progress",v)),f&&D.upload&&([y,S]=mc(f),D.upload.addEventListener("progress",y),D.upload.addEventListener("loadend",S)),(i.cancelToken||i.signal)&&(m=L=>{D&&(r(!L||L.type?new Lo(null,s,D):L),D.abort(),A(),D=null)},i.cancelToken&&i.cancelToken.subscribe(m),i.signal&&(i.signal.aborted?m():i.signal.addEventListener("abort",m)));const k=Cf(i.url);if(k&&!gn.protocols.includes(k)){r(new we("Unsupported protocol "+k+":",we.ERR_BAD_REQUEST,s)),A();return}D.send(l||null)})},DE=(s,e)=>{if(s=s?s.filter(Boolean):[],!e&&!s.length)return;const n=new AbortController;let r=!1;const i=function(f){if(!r){r=!0,c();const h=f instanceof Error?f:this.reason;n.abort(h instanceof we?h:new Lo(h instanceof Error?h.message:h))}};let l=e&&setTimeout(()=>{l=null,i(new we(`timeout of ${e}ms exceeded`,we.ETIMEDOUT))},e);const c=()=>{s&&(l&&clearTimeout(l),l=null,s.forEach(f=>{f.unsubscribe?f.unsubscribe(i):f.removeEventListener("abort",i)}),s=null)};s.forEach(f=>{if(!r){if(f.aborted){i.call(f);return}f.addEventListener("abort",i,{once:!0})}});const{signal:d}=n;return d.unsubscribe=()=>G.asap(c),d},OE=function*(s,e){let n=s.byteLength;if(n{const i=LE(s,e);let l=0,c,d=f=>{c||(c=!0,r&&r(f))};return new ReadableStream({async pull(f){try{const{done:h,value:m}=await i.next();if(h){d(),f.close();return}let y=m.byteLength;if(n){let v=l+=y;n(v)}f.enqueue(new Uint8Array(m))}catch(h){throw d(h),h}},cancel(f){return d(f),i.return()}},{highWaterMark:2})},ny=s=>s>=48&&s<=57||s>=65&&s<=70||s>=97&&s<=102,ry=(s,e,n)=>e+2s<=57?s-48:(s&223)-55,ME=s=>s>=65&&s<=90||s>=97&&s<=122||s>=48&&s<=57||s===43||s===47||s===45||s===95,NE=s=>s===9||s===10||s===12||s===13||s===32,IE=s=>{const e=Math.floor(s/4),n=s%4;return e*3+(n===2?1:n===3?2:0)},jE=s=>{const e=s.length;let n=0;return e>0&&s.charCodeAt(e-1)===61&&(n++,e>1&&s.charCodeAt(e-2)===61&&n++),Math.floor((e-n)*3/4)},HE=s=>{const e=s.length;let n=0,r=0,i=!1;for(let l=0;l0){i=!0;continue}n++}}return i||r>2||r>0&&(n+r)%4!==0||n%4===1?jE(s):IE(n)},zE=(s,e)=>{if(!s||typeof s!="string"||!s.startsWith("data:"))return 0;const n=s.indexOf(",");if(n<0)return 0;const r=s.slice(5,n),i=s.slice(n+1);if(/;base64/i.test(r))return e(i);let c=0;for(let d=0,f=i.length;d=55296&&h<=56319&&d+1=56320&&m<=57343?(c+=4,d++):c+=3}else c+=3}return c};function PE(s){const e=typeof s=="string"?s.indexOf("#"):-1;return zE(e===-1?s:s.slice(0,e),HE)}const Ef="1.20.0",iy=64*1024,UE={cache:"default",redirect:"follow",referrer:"about:client",referrerPolicy:"",mode:"cors",integrity:"",keepalive:!1,priority:"auto",window:null},{isFunction:yc}=G,BE=s=>encodeURIComponent(s).replace(/%([0-9A-F]{2})/gi,(e,n)=>String.fromCharCode(parseInt(n,16))),sy=s=>{if(!G.isString(s))return s;try{return decodeURIComponent(s)}catch{return s}},oy=(s,...e)=>{try{return!!s(...e)}catch{return!1}},qE=s=>{const e=s.indexOf("://");let n=s;return e!==-1&&(n=n.slice(e+3)),n.includes("@")||n.includes(":")},GE=s=>{const e=G.global!==void 0&&G.global!==null?G.global:globalThis,{ReadableStream:n,TextEncoder:r}=e;s=G.merge.call({skipUndefined:!0},{Request:e.Request,Response:e.Response},s);const{fetch:i,Request:l,Response:c}=s,d=i?yc(i):typeof fetch=="function",f=yc(l),h=yc(c);if(!d)return!1;const m=d&&yc(n),y=d&&(typeof r=="function"?(D=>O=>D.encode(O))(new r):async D=>new Uint8Array(await new l(D).arrayBuffer())),v=f&&m&&oy(()=>{let D=!1;const O=new l(gn.origin,{body:new n,method:"POST",get duplex(){return D=!0,"half"}}),k=O.headers.has("Content-Type");return O.body!=null&&O.body.cancel(),D&&!k}),S=h&&m&&oy(()=>G.isReadableStream(new c("").body)),_={stream:S&&(D=>D.body)};d&&["text","arrayBuffer","blob","formData","stream"].forEach(D=>{!_[D]&&(_[D]=(O,k)=>{let L=O&&O[D];if(L)return L.call(O);throw new we(`Response type '${D}' is not supported`,we.ERR_NOT_SUPPORT,k)})});const x=async D=>{if(D==null)return 0;if(G.isBlob(D))return D.size;if(G.isSpecCompliantForm(D))return(await new l(gn.origin,{method:"POST",body:D}).arrayBuffer()).byteLength;if(G.isArrayBufferView(D)||G.isArrayBuffer(D))return D.byteLength;if(G.isURLSearchParams(D)&&(D=D+""),G.isString(D))return(await y(D)).byteLength},A=async(D,O)=>{const k=G.toFiniteNumber(D.getContentLength());return k??x(O)};return async D=>{let{url:O,method:k,data:L,signal:B,cancelToken:q,timeout:z,onDownloadProgress:W,onUploadProgress:de,responseType:_e,headers:Se,withCredentials:Ie="same-origin",fetchOptions:Fe,maxContentLength:ze,maxBodyLength:Et,maxRedirects:Q}=ey(D);const ue=G.isNumber(ze)&&ze>-1,De=G.isNumber(Et)&&Et>-1,ee=ge=>G.hasOwnProp(D,ge)?D[ge]:void 0;let me=i||fetch;_e=_e?(_e+"").toLowerCase():"text";let H=DE([B,q&&q.toAbortSignal()],z),T=null;const fe=H&&H.unsubscribe&&(()=>{H.unsubscribe()});let he,Ae=null;const se=()=>new we("Request body larger than maxBodyLength limit",we.ERR_BAD_REQUEST,D,T);try{let ge;const $e=ee("auth");if($e){const qe=G.getSafeProp($e,"username")||"",mt=G.getSafeProp($e,"password")||"";ge={username:qe,password:mt}}if(qE(O)){const qe=new URL(O,gn.origin);if(!ge&&(qe.username||qe.password)){const mt=sy(qe.username),Dt=sy(qe.password);ge={username:mt,password:Dt}}(qe.username||qe.password)&&(qe.username="",qe.password="",O=qe.href)}if(ge&&(Se.delete("authorization"),Se.set("Authorization","Basic "+btoa(BE((ge.username||"")+":"+(ge.password||""))))),ue&&typeof O=="string"&&O.startsWith("data:")&&PE(O)>ze)throw new we("maxContentLength size of "+ze+" exceeded",we.ERR_BAD_RESPONSE,D,T);if(De&&k!=="get"&&k!=="head"){const qe=await x(L);if(typeof qe=="number"&&isFinite(qe)&&(he=qe,qe>Et))throw se()}const Te=De&&(G.isReadableStream(L)||G.isStream(L)),je=(qe,mt,Dt)=>ty(qe,iy,zt=>{if(De&&zt>Et)throw Ae=se();mt&&mt(zt)},Dt);if(v&&k!=="get"&&k!=="head"&&(de||Te)){if(he=he??await A(Se,L),he!==0||Te){let qe=new l(O,{method:"POST",body:L,duplex:"half"}),mt;if(G.isFormData(L)&&(mt=qe.headers.get("content-type"))&&Se.setContentType(mt),qe.body){const[Dt,zt]=de&&Ym(he,mc(Xm(de)))||[];L=je(qe.body,Dt,zt)}}}else if(Te&&!f&&m&&k!=="get"&&k!=="head")L=je(L);else if(Te&&f&&!v&&k!=="get"&&k!=="head")throw new we("Stream request bodies are not supported by the current fetch implementation",we.ERR_NOT_SUPPORT,D,T);G.isString(Ie)||(Ie=Ie?"include":"omit");const xt=f&&"credentials"in l.prototype;if(G.isFormData(L)){const qe=Se.getContentType();qe&&/^multipart\/form-data/i.test(qe)&&!/boundary=/i.test(qe)&&Se.delete("content-type")}Se.set("User-Agent","axios/"+Ef,!1);const rt=Fe==null?Fe:Object.assign(Object.create(null),Fe);rt&&(delete rt.body,delete rt.headers,delete rt.method,delete rt.signal,delete rt.duplex,delete rt.credentials);const gt=Object.assign(Object.create(null),rt,{signal:H,method:k.toUpperCase(),headers:Om(Se.normalize()),body:L,duplex:"half",credentials:xt?Ie:void 0});f&&(G.forEach(UE,(qe,mt)=>{gt[mt]===void 0&&(gt[mt]=qe)}),gt.signal===void 0&&(gt.signal=null),gt.body===void 0&&(gt.body=null)),Q===0&&(gt.redirect="manual",rt&&(rt.redirect="manual")),T=f&&new l(O,gt);let at=await(f?me(T,rt):me(O,gt));const Yt=xn.from(at.headers);if(ue){const qe=G.toFiniteNumber(Yt.getContentLength());if(qe!=null&&qe>ze)throw new we("maxContentLength size of "+ze+" exceeded",we.ERR_BAD_RESPONSE,D,T)}const Ht=S&&(_e==="stream"||_e==="response");if(S&&at.body&&(W||ue||Ht&&fe)){const qe={};["status","statusText","headers"].forEach(Ot=>{qe[Ot]=at[Ot]});const mt=G.toFiniteNumber(Yt.getContentLength()),[Dt,zt]=W&&Ym(mt,mc(Xm(W),!0))||[];let kn=0;const dr=Ot=>{if(ue&&(kn=Ot,kn>ze))throw new we("maxContentLength size of "+ze+" exceeded",we.ERR_BAD_RESPONSE,D,T);Dt&&Dt(Ot)};at=new c(ty(at.body,iy,dr,()=>{zt&&zt(),fe&&fe()}),qe)}_e=_e||"text";let bt=await _[G.findKey(_,_e)||"text"](at,D);if(ue&&!S&&!Ht){let qe;if(bt!=null&&(typeof bt.byteLength=="number"?qe=bt.byteLength:typeof bt.size=="number"?qe=bt.size:typeof bt=="string"&&(qe=typeof r=="function"?new r().encode(bt).byteLength:bt.length)),typeof qe=="number"&&qe>ze)throw new we("maxContentLength size of "+ze+" exceeded",we.ERR_BAD_RESPONSE,D,T)}return!Ht&&fe&&fe(),await new Promise((qe,mt)=>{Km(qe,mt,{data:bt,headers:xn.from(at.headers),status:at.status,statusText:at.statusText,config:D,request:T})})}catch(ge){if(fe&&fe(),H&&H.aborted&&H.reason instanceof we){const $e=H.reason;throw $e.config=D,T&&($e.request=T),ge!==$e&&Object.defineProperty($e,"cause",{__proto__:null,value:ge,writable:!0,enumerable:!1,configurable:!0}),$e}if(Ae)throw T&&!Ae.request&&(Ae.request=T),Ae;if(ge instanceof we)throw T&&!ge.request&&(ge.request=T),ge;if(ge&&ge.name==="TypeError"&&/Load failed|fetch/i.test(ge.message)){const $e=new we("Network Error",we.ERR_NETWORK,D,T,ge&&ge.response);throw Object.defineProperty($e,"cause",{__proto__:null,value:ge.cause||ge,writable:!0,enumerable:!1,configurable:!0}),$e}throw we.from(ge,ge&&ge.code,D,T,ge&&ge.response)}}},FE=new Map,ly=s=>{let e=s&&s.env||{};const{fetch:n,Request:r,Response:i}=e,l=[r,i,n];let c=l.length,d=c,f,h,m=FE;for(;d--;)f=l[d],h=m.get(f),h===void 0&&m.set(f,h=d?new Map:GE(e)),m=h;return h};ly();const _f={http:rE,xhr:RE,fetch:{get:ly}};G.forEach(_f,(s,e)=>{if(s){try{Object.defineProperty(s,"name",{__proto__:null,value:e})}catch{}Object.defineProperty(s,"adapterName",{__proto__:null,value:e})}});const cy=s=>`- ${s}`,VE=s=>G.isFunction(s)||s===null||s===!1;function KE(s,e){s=G.isArray(s)?s:[s];const{length:n}=s;let r,i;const l={};for(let c=0;c`adapter ${f} `+(h===!1?"is not supported by the environment":"is not available in the build"));let d=n?c.length>1?`since : -`+c.map(cy).join(` -`):" "+cy(c[0]):"as no adapter specified";throw new we("There is no suitable adapter to dispatch the request "+d,we.ERR_NOT_SUPPORT)}return i}const uy={getAdapter:KE,adapters:_f};function Af(s){if(s.cancelToken&&s.cancelToken.throwIfRequested(),s.signal&&s.signal.aborted)throw new Lo(null,s)}function xf(s){const e=G.toSafeFlatObject(s);return Af(e),e.headers=xn.from(G.getSafeProp(e,"headers")),e.data=wf.call(e,e.transformRequest),["post","put","patch"].indexOf(e.method)!==-1&&e.headers.setContentType("application/x-www-form-urlencoded",!1),uy.getAdapter(e.adapter||Oo.adapter,e)(e).then(function(i){Af(e),e.response=i;try{i.data=wf.call(e,e.transformResponse,i)}finally{delete e.response}return i.headers=xn.from(i.headers),i},function(i){if(!Vm(i)&&(Af(e),i&&i.response)){e.response=i.response;try{i.response.data=wf.call(e,e.transformResponse,i.response)}finally{delete e.response}i.response.headers=xn.from(i.response.headers)}return Promise.reject(i)})}const bc={};["object","boolean","number","function","string","symbol"].forEach((s,e)=>{bc[s]=function(r){return typeof r===s||"a"+(e<1?"n ":" ")+s}});const dy={};bc.transitional=function(e,n,r){function i(l,c){return"[Axios v"+Ef+"] Transitional option '"+l+"'"+c+(r?". "+r:"")}return(l,c,d)=>{if(e===!1)throw new we(i(c," has been removed"+(n?" in "+n:"")),we.ERR_DEPRECATED);return n&&!dy[c]&&(dy[c]=!0,console.warn(i(c," has been deprecated since v"+n+" and will be removed in the near future"))),e?e(l,c,d):!0}},bc.spelling=function(e){return(n,r)=>(console.warn(`${r} is likely a misspelling of ${e}`),!0)};function WE(s,e,n){if(typeof s!="object"||s===null)throw new we("options must be an object",we.ERR_BAD_OPTION_VALUE);const r=Object.keys(s);let i=r.length;for(;i-- >0;){const l=r[i],c=Object.prototype.hasOwnProperty.call(e,l)?e[l]:void 0;if(c){const d=s[l],f=d===void 0||c(d,l,s);if(f!==!0)throw new we("option "+l+" must be "+f,we.ERR_BAD_OPTION_VALUE);continue}if(n!==!0)throw new we("Unknown option "+l,we.ERR_BAD_OPTION)}}const vc={assertOptions:WE,validators:bc},Tn=vc.validators;let Ti=class{constructor(e){this.defaults=e||{},this.interceptors={request:new Um,response:new Um}}async request(e,n){try{return await this._request(e,n)}catch(r){if(r instanceof Error)try{let i={};Error.captureStackTrace?Error.captureStackTrace(i):i=new Error;const l=i.stack;let c="";if(typeof l=="string"){const d=l.indexOf(` +`;break;case"t":y+=" ";break;case"r":y+="\r";break;case"b":y+="\b";break;case"f":y+="\f";break;case"v":y+="\v";break;case"0":y+="\0";break;case"\\":y+="\\";break;case"'":y+="'";break;case'"':y+='"';break;case"`":y+="`";break;default:y+=v;break}n++}else y+=s[n],n++;if(n>=r)throw new Error(`Unterminated string literal at position ${h}`);n++,e.push({type:"str",value:y,pos:h});continue}if(L0(l)){const h=n;for(n++;n"?(e.push({type:"punct",value:"=>",pos:c}),n+=2):i(1)==="="?i(2)==="="?(e.push({type:"punct",value:"===",pos:c}),n+=3):(e.push({type:"punct",value:"==",pos:c}),n+=2):(e.push({type:"punct",value:"=",pos:c}),n+=1);continue}case"!":{i(1)==="="?i(2)==="="?(e.push({type:"punct",value:"!==",pos:c}),n+=3):(e.push({type:"punct",value:"!=",pos:c}),n+=2):(e.push({type:"punct",value:"!",pos:c}),n+=1);continue}case"<":{if(i(1)==="=")e.push({type:"punct",value:"<=",pos:c}),n+=2;else{if(i(1)==="<")throw new Error("Bitwise/shift operators are not allowed");e.push({type:"punct",value:"<",pos:c}),n+=1}continue}case">":{if(i(1)==="=")e.push({type:"punct",value:">=",pos:c}),n+=2;else{if(i(1)===">")throw new Error("Bitwise/shift operators are not allowed");e.push({type:"punct",value:">",pos:c}),n+=1}continue}case"&":{if(i(1)==="&")e.push({type:"punct",value:"&&",pos:c}),n+=2;else throw new Error("Bitwise operators are not allowed");continue}case"|":{if(i(1)==="|")e.push({type:"punct",value:"||",pos:c}),n+=2;else throw new Error("Bitwise operators are not allowed");continue}case"?":{i(1)==="."&&!wi(i(2))?(e.push({type:"punct",value:"?.",pos:c}),n+=2):i(1)==="?"?(e.push({type:"punct",value:"??",pos:c}),n+=2):(e.push({type:"punct",value:"?",pos:c}),n+=1);continue}case"+":{if(i(1)==="+")throw new Error("Increment operator is not allowed");if(i(1)==="=")throw new Error("Assignment operators are not allowed");e.push({type:"punct",value:"+",pos:c}),n+=1;continue}case"-":{if(i(1)==="-")throw new Error("Decrement operator is not allowed");if(i(1)==="=")throw new Error("Assignment operators are not allowed");e.push({type:"punct",value:"-",pos:c}),n+=1;continue}case"*":{if(i(1)==="=")throw new Error("Assignment operators are not allowed");if(i(1)==="*")throw new Error("Exponentiation operator is not supported");e.push({type:"punct",value:"*",pos:c}),n+=1;continue}case"/":{if(i(1)==="=")throw new Error("Assignment operators are not allowed");e.push({type:"punct",value:"/",pos:c}),n+=1;continue}case"%":{if(i(1)==="=")throw new Error("Assignment operators are not allowed");e.push({type:"punct",value:"%",pos:c}),n+=1;continue}case"~":case"^":throw new Error("Bitwise operators are not allowed");case".":case":":case"(":case")":case"[":case"]":case"{":case"}":case",":e.push({type:"punct",value:l,pos:c}),n+=1;continue;case";":e.push({type:"punct",value:";",pos:c}),n+=1;continue;default:throw new Error(`Unexpected character "${l}" at position ${c}`)}}return e}const I0={"??":1,"||":1,"&&":2,"===":3,"!==":3,"==":3,"!=":3,"<":4,">":4,"<=":4,">=":4,"+":5,"-":5,"*":6,"/":6,"%":6},j0=new Set(["??","||","&&"]);class H0{constructor(e){M(this,"tokens");M(this,"pos",0);this.tokens=e}atEnd(){return this.pos>=this.tokens.length}peek(e=0){const n=this.pos+e;return n"}"`);this.pos++}parseExpression(){const e=this.tryParseArrow();return e||this.parseTernary()}tryParseArrow(){const e=this.pos,n=this.peek();if(!n)return null;if(n.type==="ident"&&this.isPunct("=>",1)&&!sc.has(String(n.value))){this.pos+=1,this.pos+=1;const{body:r,isBlock:i}=this.parseArrowBody();return{type:"Arrow",params:[{name:String(n.value),default:null}],body:r,isBlock:i}}if(n.type==="punct"&&n.value==="("){this.pos+=1;const r=this.tryParseParamList();if(r&&this.isPunct(")")&&(this.pos+=1,this.isPunct("=>"))){this.pos+=1;const{body:i,isBlock:l}=this.parseArrowBody();return{type:"Arrow",params:r,body:i,isBlock:l}}return this.pos=e,null}return this.pos=e,null}tryParseParamList(){const e=[];if(this.isPunct(")"))return e;for(;;){const n=this.peek();let r=null,i;if(n&&n.type==="punct"&&n.value==="["){const c=this.tryParseArrayPattern();if(!c)return null;i=c}else if(n&&n.type==="ident"&&!sc.has(String(n.value)))r=String(n.value),this.pos+=1;else return null;let l=null;if(this.isPunct("=")&&(this.pos+=1,l=this.parseExpression()),e.push(i?{name:r,elements:i,default:l}:{name:r,default:l}),this.isPunct(",")){this.pos+=1;continue}break}return e}tryParseArrayPattern(){const e=this.pos;this.pos+=1;const n=[];for(;;){if(this.isPunct("]"))return this.pos+=1,n;if(this.isPunct(",")){n.push(null),this.pos+=1;continue}const r=this.peek();if(!r||r.type!=="ident"||sc.has(String(r.value)))return this.pos=e,null;if(n.push(String(r.value)),this.pos+=1,this.isPunct(",")){this.pos+=1;continue}return this.isPunct("]")?(this.pos+=1,n):(this.pos=e,null)}}parseArrowBody(){return this.isPunct("{")?{body:this.parseBlock(),isBlock:!0}:{body:this.parseExpression(),isBlock:!1}}parseBlock(){this.expectPunct("{");const e=[];for(;!this.isPunct("}");){if(this.atEnd())throw new Error("Unterminated block");e.push(this.parseStatement())}return this.expectPunct("}"),{type:"Block",body:e}}parseStatement(){const e=this.peek();if(!e)throw new Error("Unexpected end of statement");if(e.type==="punct"&&e.value==="{")return this.parseBlock();if(e.type==="punct"&&e.value===";")return this.pos+=1,{type:"Empty"};if(e.type==="ident")switch(String(e.value)){case"const":case"let":return this.parseVarDecl();case"if":return this.parseIf();case"for":return this.parseForOf();case"return":return this.parseReturn();case"try":return this.parseTry();case"break":return this.pos+=1,this.consumeSemicolon(),{type:"Break"};case"continue":return this.pos+=1,this.consumeSemicolon(),{type:"Continue"}}const n=this.parseExpression();return this.consumeSemicolon(),{type:"ExprStmt",expression:n}}consumeSemicolon(){this.isPunct(";")&&(this.pos+=1)}parseVarDecl(){this.pos+=1;const e=[];for(;;){const n=this.peek();if(!n||n.type!=="ident")throw new Error("Expected variable name in declaration");const r=String(n.value);this.pos+=1,this.expectPunct("=");const i=this.parseExpression();if(e.push({name:r,init:i}),this.isPunct(",")){this.pos+=1;continue}break}return this.consumeSemicolon(),{type:"VarDecl",declarations:e}}parseIf(){this.pos+=1,this.expectPunct("(");const e=this.parseExpression();this.expectPunct(")");const n=this.parseStatement();let r=null;const i=this.peek();return i&&i.type==="ident"&&i.value==="else"&&(this.pos+=1,r=this.parseStatement()),{type:"If",test:e,consequent:n,alternate:r}}parseForOf(){this.pos+=1,this.expectPunct("(");const e=this.peek();if(!e||e.type!=="ident"||e.value!=="const"&&e.value!=="let")throw new Error('Only "for (const x of …)" / "for (let x of …)" loops are supported');this.pos+=1;const n=this.peek();if(!n||n.type!=="ident")throw new Error("Expected loop variable name");const r=String(n.value);this.pos+=1;const i=this.peek();if(!i||i.type!=="ident"||i.value!=="of")throw new Error("Only for-of loops are supported (for-in / C-style for are not allowed)");this.pos+=1;const l=this.parseExpression();this.expectPunct(")");const c=this.parseStatement();return{type:"ForOf",name:r,iterable:l,body:c}}parseReturn(){if(this.pos+=1,this.isPunct(";")||this.isPunct("}")||this.atEnd())return this.consumeSemicolon(),{type:"Return",argument:null};const e=this.parseExpression();return this.consumeSemicolon(),{type:"Return",argument:e}}parseTry(){this.pos+=1;const e=this.parseBlock();let n=null,r=null,i=null;const l=this.peek();if(l&&l.type==="ident"&&l.value==="catch"){if(this.pos+=1,this.isPunct("(")){this.pos+=1;const d=this.peek();d&&d.type==="ident"&&(n=String(d.value),this.pos+=1),this.expectPunct(")")}r=this.parseBlock()}const c=this.peek();if(c&&c.type==="ident"&&c.value==="finally"&&(this.pos+=1,i=this.parseBlock()),!r&&!i)throw new Error("Missing catch or finally after try");return{type:"Try",block:e,handlerParam:n,handler:r,finalizer:i}}parseFunctionExpression(){this.pos+=1;let e=null;const n=this.peek();n&&n.type==="ident"&&n.value!==void 0&&!this.isPunct("(")&&(e=String(n.value),this.pos+=1),this.expectPunct("(");const r=this.tryParseParamList();if(!r)throw new Error("Invalid function parameter list");this.expectPunct(")");const i=this.parseBlock();return{type:"Function",name:e,params:r,body:i}}parseTernary(){const e=this.parseBinary(0);if(this.isPunct("?")){this.pos+=1;const n=this.parseExpression();this.expectPunct(":");const r=this.parseExpression();return{type:"Conditional",test:e,consequent:n,alternate:r}}return e}parseBinary(e){let n=this.parseUnary();for(;;){const r=this.peek();if(!r||r.type!=="punct")break;const i=String(r.value),l=I0[i];if(l===void 0||lrm(l));return{type:"Template",quasis:n,expressions:i}}if(e.type==="ident"&&e.value==="new")return this.parseNew();if(e.type==="ident"&&e.value==="function")return this.parseFunctionExpression();if(e.type==="ident"){const n=String(e.value);if(sc.has(n))throw new Error(`Keyword "${n}" is not allowed`);switch(this.pos+=1,n){case"true":return{type:"Literal",value:!0};case"false":return{type:"Literal",value:!1};case"null":return{type:"Literal",value:null};case"undefined":return{type:"Literal",value:void 0};default:return{type:"Identifier",name:n}}}if(e.type==="punct"){if(e.value==="("){this.pos+=1;const n=this.parseExpression();return this.expectPunct(")"),n}if(e.value==="[")return this.parseArrayLiteral();if(e.value==="{")return this.parseObjectLiteral()}throw new Error(`Unexpected token "${e.value}"`)}parseArrayLiteral(){this.expectPunct("[");const e=[];for(;!this.isPunct("]");){if(this.isPunct(",")){this.pos+=1,e.push({type:"Literal",value:void 0});continue}if(this.isPunct("...")?(this.pos+=1,e.push({type:"Spread",argument:this.parseExpression()})):e.push(this.parseExpression()),this.isPunct(",")){this.pos+=1;continue}break}return this.expectPunct("]"),{type:"Array",elements:e}}parseObjectLiteral(){this.expectPunct("{");const e=[];for(;!this.isPunct("}");){if(this.isPunct("..."))this.pos+=1,e.push({kind:"spread",value:this.parseExpression()});else{let n,r=!1;const i=this.peek();if(!i)throw new Error("Unexpected end of expression in object literal");if(i.type==="punct"&&i.value==="[")this.pos+=1,n=this.parseExpression(),this.expectPunct("]"),r=!0;else if(i.type==="str")this.pos+=1,n={type:"Literal",value:i.value};else if(i.type==="num")this.pos+=1,n={type:"Literal",value:String(i.value)};else if(i.type==="ident")this.pos+=1,n={type:"Literal",value:String(i.value)};else throw new Error(`Unexpected token "${i.value}" in object literal`);if(this.isPunct(":")){this.pos+=1;const l=this.parseExpression();e.push({kind:"init",key:n,computed:r,value:l})}else{if(r||n.type!=="Literal"||typeof n.value!="string")throw new Error("Invalid shorthand property in object literal");if(i.type!=="ident")throw new Error("Invalid shorthand property in object literal");e.push({kind:"init",key:n,computed:!1,value:{type:"Identifier",name:n.value}})}}if(this.isPunct(",")){this.pos+=1;continue}break}return this.expectPunct("}"),{type:"Object",properties:e}}}function rm(s){const e=N0(s),n=new H0(e),r=n.parseExpression();if(!n.atEnd()){const i=n.peek();throw i&&i.type==="punct"&&i.value===","?new Error("The comma/sequence operator is not allowed"):new Error(`Unexpected token "${i?i.value:""}" after expression`)}return r}function z0(s,e){let n=e;for(;n;){if(Object.prototype.hasOwnProperty.call(n.vars,s))return n.vars[s];n=n.parent}if(Object.prototype.hasOwnProperty.call(Wd,s))return Wd[s];if(D0.has(s))throw new Error(`Reference to forbidden global "${s}" is not allowed`)}function Zd(s){const e=typeof s=="symbol"?s:String(s);if(typeof e=="string"&&So.has(e))throw new Error(`Access to "${e}" is forbidden`);return e}function ef(s,e){return s.type==="Member"?am(s,e):s.type==="Call"?im(s,e):et(s,e)}function am(s,e){const n=ef(s.object,e);if(n===xr||s.optional&&n==null)return xr;let r;s.computed?r=et(s.property,e):r=s.property.value;const i=Zd(r);if(n!=null)return n[i]}function im(s,e){let n,r;if(s.callee.type==="Member"){const l=s.callee,c=ef(l.object,e);if(c===xr||l.optional&&c==null)return xr;let d;l.computed?d=et(l.property,e):d=l.property.value;const f=Zd(d);c==null?n=void 0:(n=c[f],r=c)}else if(n=ef(s.callee,e),n===xr)return xr;if(s.optional&&n==null)return xr;if(typeof n!="function")throw new Error("Attempted to call a non-function value");const i=sm(s.args,e);return n.apply(r,i)}function sm(s,e){const n=[];for(const r of s)if(r.type==="Spread"){const i=et(r.argument,e);if(i!=null)for(const l of i)n.push(l)}else n.push(et(r,e));return n}function om(s,e,n){for(let r=0;r":return n>r;case"<=":return n<=r;case">=":return n>=r;default:throw new Error(`Unknown binary operator "${s.operator}"`)}}case"Logical":{const n=et(s.left,e);switch(s.operator){case"&&":return n&&et(s.right,e);case"||":return n||et(s.right,e);case"??":return n??et(s.right,e);default:throw new Error(`Unknown logical operator "${s.operator}"`)}}case"Conditional":return et(s.test,e)?et(s.consequent,e):et(s.alternate,e);case"Array":{const n=[];for(const r of s.elements)if(r.type==="Spread"){const i=et(r.argument,e);if(i!=null)for(const l of i)n.push(l)}else n.push(et(r,e));return n}case"Object":{const n={};for(const r of s.properties)if(r.kind==="spread"){const i=et(r.value,e);if(i!=null&&typeof i=="object")for(const l of Object.keys(i))tf(n,l,i[l])}else{let i;r.computed?i=et(r.key,e):i=r.key.value;const l=et(r.value,e);tf(n,String(i),l)}return n}case"Arrow":{const n=e,r=s.params,i=s.body,l=s.isBlock;return function(...d){const f={vars:{},parent:n};return om(r,d,f),l?lm(i,f):et(i,f)}}case"Function":{const n=e,r=s.params,i=s.body,l=s.name,c=function(...f){const h={vars:{},parent:n};return l&&(h.vars[l]=c),om(r,f,h),lm(i,h)};return c}case"Delete":{const n=s.argument,r=et(n.object,e);let i;n.computed?i=et(n.property,e):i=n.property.value;const l=Zd(i);if(r==null||typeof r!="object"&&typeof r!="function")return!0;if(tm.has(r))throw new Error("delete on a built-in global object is not allowed");return delete r[l]}case"Block":{const n={vars:{},parent:e};for(const r of s.body)et(r,n);return}case"VarDecl":{for(const n of s.declarations)e.vars[n.name]=et(n.init,e);return}case"If":{et(s.test,e)?et(s.consequent,e):s.alternate&&et(s.alternate,e);return}case"ForOf":{const n=et(s.iterable,e);if(n!=null)for(const r of n){const i={vars:{},parent:e};i.vars[s.name]=r;try{et(s.body,i)}catch(l){if(l instanceof Qd)continue;if(l instanceof Jd)break;throw l}}return}case"Return":throw new Xd(s.argument?et(s.argument,e):void 0);case"ExprStmt":et(s.expression,e);return;case"Break":throw new Jd;case"Continue":throw new Qd;case"Empty":return;case"Try":{try{try{et(s.block,e)}catch(n){if(n instanceof Xd||n instanceof Jd||n instanceof Qd)throw n;if(s.handler){const r={vars:{},parent:e};s.handlerParam&&(r.vars[s.handlerParam]=n),et(s.handler,r)}else throw n}}finally{s.finalizer&&et(s.finalizer,e)}return}case"New":{const n=Yd[s.ctor];if(typeof n!="function")throw new Error("new on non-whitelisted constructor");const r=sm(s.args,e);return new n(...r)}case"Template":{let n=s.quasis[0]??"";for(let r=0;r0}getFromRenderCycleCache(e){if(this.isRenderCycleCacheActive())return this.renderCycleCache.get(e)}saveToRenderCycleCache(e,n){this.isRenderCycleCacheActive()&&this.renderCycleCache.set(e,n)}resolveBindings(e,n,r,i){const l={...this.defaultOptions,...r},c=wo(),d=n._computed&&!n.$computed?{...n,$computed:n._computed}:n,f=(v,S)=>{const _=S.trim();let x=!1,A=_;if(_.startsWith(lr)&&(x=!0,A=_.slice(lr.length)),jn(A))try{const z=this.evaluatePipeExpression(A,d,l,{...i,method:"resolveBindings",displayExpression:`{{${_}}}`}),W=this.formatValue(z);return x?vo(W):W}catch(z){return Ma.error("Pipe expression evaluation failed:",A,z),v}if(yo(A)){const z=c?.isEnabled()?performance.now():0;try{let W,de=!1;if(l.skipCache)W=this.evaluateExpression(A,d);else{const Se=`expr:${A}`,je=this.getFromRenderCycleCache(Se);je!==void 0?(W=je,de=!0):(W=this.evaluateExpression(A,d),this.saveToRenderCycleCache(Se,W))}if(c?.isEnabled()){const Se=performance.now()-z;c.trackExpressionEval({expression:`{{${_}}}`,result:this.sanitizeResultForTracking(W),resultType:this.getResultType(W),componentId:i?.componentId,componentName:i?.componentName,propName:i?.propName,fromCache:de,duration:Se,method:"resolveBindings",skipCache:l.skipCache})}const _e=this.formatValue(W);return x?vo(_e):_e}catch(W){return Ma.error("Expression evaluation failed:",A,W),v}}if(l.skipCache){const z=c?.isEnabled()?performance.now():0,W=this.resolvePath(A,d,l);if(c?.isEnabled()){const _e=performance.now()-z;c.trackExpressionEval({expression:`{{${_}}}`,result:this.sanitizeResultForTracking(W),resultType:this.getResultType(W),componentId:i?.componentId,componentName:i?.componentName,propName:i?.propName,fromCache:!1,duration:_e,method:"resolveBindings",skipCache:!0})}const de=this.formatValue(W);return x?vo(de):de}const O=A.startsWith("_global")||A.startsWith("_local")||A.startsWith("_isolated")||A.startsWith("$parent"),k=c?.isEnabled()?performance.now():0;let L,B=!1;if(O){const z=this.getFromRenderCycleCache(A);z!==void 0?(L=z,B=!0):(L=this.resolvePath(A,d,l),this.saveToRenderCycleCache(A,L))}else{const z=this.getFromCache(A);z!==void 0?(L=z,B=!0):(L=this.resolvePath(A,d,l),this.saveToCache(A,L))}if(c?.isEnabled()){const z=performance.now()-k;c.trackExpressionEval({expression:`{{${_}}}`,result:this.sanitizeResultForTracking(L),resultType:this.getResultType(L),componentId:i?.componentId,componentName:i?.componentName,propName:i?.propName,fromCache:B,duration:z,method:"resolveBindings",skipCache:l.skipCache})}const q=this.formatValue(L);return x?vo(q):q},h=R0(e);if(h.length===0)return e;let m="",y=0;for(const v of h)m+=e.slice(y,v.start),m+=f(e.slice(v.start,v.end),v.expr),y=v.end;return m+e.slice(y)}evaluatePipeExpression(e,n,r,i){const l={...this.defaultOptions,...r},c=wo(),d=c?.isEnabled()?performance.now():0,f=n._computed&&!n.$computed?{...n,$computed:n._computed}:n,[h,m]=T0(e);let y,v=!1;const S=yo(h),_=h.startsWith("_global")||h.startsWith("_local")||h.startsWith("_isolated")||h.startsWith("$parent");if(l.skipCache)S?y=this.evaluateExpression(h,f):y=this.resolvePath(h,f,l);else if(S){const A=this.getFromRenderCycleCache(`expr:${h}`);A!==void 0?(y=A,v=!0):(y=this.evaluateExpression(h,f),this.saveToRenderCycleCache(`expr:${h}`,y))}else if(_){const A=this.getFromRenderCycleCache(h);A!==void 0?(y=A,v=!0):(y=this.resolvePath(h,f,l),this.saveToRenderCycleCache(h,y))}else{const A=this.getFromCache(h);A!==void 0?(y=A,v=!0):(y=this.resolvePath(h,f,l),this.saveToCache(h,y))}const x=k0(y,m);if(c?.isEnabled()){const A=performance.now()-d;c.trackExpressionEval({expression:i?.displayExpression??`{{${e}}}`,result:this.sanitizeResultForTracking(x),resultType:this.getResultType(x),componentId:i?.componentId,componentName:i?.componentName,propName:i?.propName,fromCache:v,duration:A,method:i?.method??"evaluatePipeExpression",skipCache:l.skipCache})}return x}resolve(e,n,r,i){const l={...this.defaultOptions,...r},c=wo(),d=c?.isEnabled()?performance.now():0;if(l.skipCache){const y=this.resolvePath(e,n,l);return c?.isEnabled()&&c.trackExpressionEval({expression:e,result:this.sanitizeResultForTracking(y),resultType:this.getResultType(y),componentId:i?.componentId,componentName:i?.componentName,propName:i?.propName,fromCache:!1,duration:performance.now()-d,method:"resolve",skipCache:!0}),y}const f=e.startsWith("_global")||e.startsWith("_local")||e.startsWith("_isolated")||e.startsWith("$parent");let h=!1;if(f){const y=this.getFromRenderCycleCache(e);if(y!==void 0)return h=!0,c?.isEnabled()&&c.trackExpressionEval({expression:e,result:this.sanitizeResultForTracking(y),resultType:this.getResultType(y),componentId:i?.componentId,componentName:i?.componentName,propName:i?.propName,fromCache:!0,duration:performance.now()-d,method:"resolve",skipCache:l.skipCache}),y}else{const y=this.getFromCache(e);if(y!==void 0)return h=!0,c?.isEnabled()&&c.trackExpressionEval({expression:e,result:this.sanitizeResultForTracking(y),resultType:this.getResultType(y),componentId:i?.componentId,componentName:i?.componentName,propName:i?.propName,fromCache:!0,duration:performance.now()-d,method:"resolve",skipCache:l.skipCache}),y}const m=this.resolvePath(e,n,l);return f?this.saveToRenderCycleCache(e,m):this.saveToCache(e,m),c?.isEnabled()&&c.trackExpressionEval({expression:e,result:this.sanitizeResultForTracking(m),resultType:this.getResultType(m),componentId:i?.componentId,componentName:i?.componentName,propName:i?.propName,fromCache:h,duration:performance.now()-d,method:"resolve",skipCache:l.skipCache}),m}resolvePath(e,n,r,i=0,l=new WeakSet){if(typeof e!="string"||e.trim()===""){Ma.warn("resolvePath: 빈 경로가 전달되었습니다 — undefined 로 해석합니다.");return}if(r.maxDepth&&i>r.maxDepth){if(r.detectCircular)throw new rf(`Maximum depth exceeded (${r.maxDepth}). Possible circular reference.`,e,n);return r.defaultValue}const c=this.parsePath(e);let d=n;for(let f=0;fca.CACHE_EXPIRY){this.cache.delete(e),r?.isEnabled()&&r.recordCacheMiss();return}return r?.isEnabled()&&r.recordCacheHit(),n.value}saveToCache(e,n){this.cache.set(e,{value:n,timestamp:Date.now()})}isActionDefinition(e){return typeof e.handler!="string"?!1:e.params!==void 0||Array.isArray(e.actions)||typeof e.target=="string"||e.onSuccess!==void 0||e.onError!==void 0}resolveObject(e,n,r){if(this.isSwitchExpression(e)){const d=e;return this.resolveSwitch(d,n,r)}if(this.isActionDefinition(e))return{...e};if("iteration"in e)return{...e};const i={},c=[...["cellChildren","expandChildren","expandContext","render"],...r?.skipBindingKeys||[]];for(const[d,f]of Object.entries(e)){if(c.includes(d)){i[d]=f;continue}try{this.resolveObjectEntry(i,d,f,n,r)}catch(h){Ma.warn(`resolveObject: 값 해석 실패 (key: ${d}):`,h),i[d]=void 0}}return i}resolveObjectEntry(e,n,r,i,l){if(typeof r=="string"){const c=Oa(r);if(c!==null){const d=c.trim();let f=!1,h=d;d.startsWith(lr)&&(f=!0,h=d.slice(lr.length));let m;switch(Qg(h)){case"empty":Ma.warn(`resolveObject: 빈 바인딩 \`{{}}\` (key: ${n}) — undefined 로 해석합니다.`),m=void 0;break;case"literal":m=Fd.get(h);break;case"pipe":m=this.evaluatePipeExpression(h,i,l);break;case"expression":m=this.evaluateExpression(h,i,l);break;default:m=this.resolve(h,i,l)}e[n]=f&&m!=null?fs(m):m}else e[n]=this.resolveBindings(r,i,l)}else Array.isArray(r)?e[n]=r.map(c=>typeof c=="string"?this.resolveBindings(c,i,l):typeof c=="object"&&c!==null?this.resolveObject(c,i,l):c):r&&typeof r=="object"?e[n]=this.resolveObject(r,i,l):e[n]=r}clearCache(){this.cache.clear()}invalidateCacheByKeys(e){for(const n of this.cache.keys())for(const r of e)if(n===r||n.startsWith(`${r}.`)||n.startsWith(`${r}[`)){this.cache.delete(n);break}}pruneCache(){const e=Date.now();for(const[n,r]of this.cache.entries())e-r.timestamp>ca.CACHE_EXPIRY&&this.cache.delete(n)}getCacheStats(){const e=Date.now();let n=0;for(const r of this.cache.values())e-r.timestamp>ca.CACHE_EXPIRY&&n++;return{size:this.cache.size,expired:n}}evaluateExpression(e,n,r){const i=wo(),l=i?.isEnabled()?performance.now():0;i?.isEnabled()&&i.trackBindingEval();try{let c=this.preprocessOptionalChaining(e);c=this.preprocessTranslationTokens(c);const d=this.extractVariablesFromExpression(c),f={...n};for(const A of d)A in f||(f[A]=void 0);n._computed&&!f.$computed&&(f.$computed=n._computed);let h=n.$templateId,m=n.$locale;if(typeof window<"u"&&(!h||!m)){const A=window.__templateApp?.getConfig?.();h??(h=A?.templateId),m??(m=A?.locale)}const y=m||"ko";f.$localized=(A,D)=>{if(A==null&&!D)return"";if(typeof A=="string")return A;if(A&&typeof A=="object"&&A[y])return A[y];if(D&&typeof D=="string"){const O=f.$t?f.$t(D):D;if(O&&O!==D)return O}return A&&typeof A=="object"?A.ko||A.en||Object.values(A)[0]||"":A==null?"":String(A)};const v=typeof window<"u"?window.G7Core:void 0;f.$uuid=()=>v?.uuid?v.uuid():typeof crypto<"u"&&crypto.randomUUID?crypto.randomUUID():"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,A=>{const D=Math.random()*16|0;return(A==="x"?D:D&3|8).toString(16)});const S={templateId:h||"",locale:y};f.$t=A=>{if(!A||typeof A!="string")return"";try{return vi.getInstance().translate(A,S)}catch(D){return Ma.warn("$t() translation failed for key:",A,D),A}},f.$get=(A,D,O=void 0)=>{if(A==null)return O;const k=Array.isArray(D)?D:[D];if(k.length===0)return A;let L=A;for(const B of k){if(L==null||B==null)return O;L=L[B]}return L??O};const _=!1,x=nf(c,f);if(i?.isEnabled()){const A=performance.now()-l;i.trackExpressionEval({expression:e,result:this.sanitizeResultForTracking(x),resultType:this.getResultType(x),componentId:r?.componentId,componentName:r?.componentName,propName:r?.propName,fromCache:_,duration:A,method:"evaluateExpression",skipCache:r?.skipCache})}return x}catch(c){throw new Error(`Failed to evaluate expression "${e}": ${c instanceof Error?c.message:String(c)}`)}}getResultType(e){return e===null?"null":e===void 0?"undefined":Array.isArray(e)?"array":typeof e}sanitizeResultForTracking(e){if(e==null||typeof e!="object")return e;try{if(Array.isArray(e))return e.length>10?`[Array(${e.length})]`:e.slice(0,10);const n=Object.keys(e);return n.length>20?`{Object(${n.length} keys)}`:(JSON.stringify(e),e)}catch{return"[Complex Object]"}}preprocessTranslationTokens(e){return e=e.replace(/(['"])(\$t:([a-zA-Z_][a-zA-Z0-9_.\-]*))\1/g,(n,r,i,l)=>`$t('${l}')`),e.replace(/(['"]\s*)?(\$t:[a-zA-Z_][a-zA-Z0-9_.\-]*)(?!\s*['"])/g,(n,r,i)=>r?n:`"${i}"`)}preprocessOptionalChaining(e){const n=[];let r=e.replace(/(['"])(?:(?!\1|\\).|\\.)*\1/g,l=>(n.push(l),`__STRING_LITERAL_${n.length-1}__`));const i=[];return r=r.replace(/\$t:[a-zA-Z_][a-zA-Z0-9_.\-]*(?:\|[^'"\s,)]+)?/g,l=>(i.push(l),`__TRANSLATION_TOKEN_${i.length-1}__`)),r=r.replace(/([a-zA-Z_$][a-zA-Z0-9_$]*)\.(?!\?)/g,"$1?."),r=r.replace(/__TRANSLATION_TOKEN_(\d+)__/g,(l,c)=>i[parseInt(c,10)]),r=r.replace(/__STRING_LITERAL_(\d+)__/g,(l,c)=>n[parseInt(c,10)]),r}extractVariablesFromExpression(e){const n=new Set(["true","false","null","undefined","NaN","Infinity","if","else","for","while","do","switch","case","break","continue","return","function","class","const","let","var","new","delete","typeof","instanceof","this","super","import","export","default","try","catch","finally","throw","Math","Date","JSON","Array","Object","String","Number","Boolean","RegExp","Set","Map","WeakSet","WeakMap","Symbol","Promise","BigInt","Error","parseInt","parseFloat","isNaN","isFinite","encodeURI","decodeURI","encodeURIComponent","decodeURIComponent"]),r=/(^|[^.\w$])([a-zA-Z_$][a-zA-Z0-9_$]*)/g,i=new Set;let l;for(;(l=r.exec(e))!==null;){const c=l[2];n.has(c)||i.add(c)}return Array.from(i)}isSwitchExpression(e){return e!==null&&typeof e=="object"&&!Array.isArray(e)&&"$switch"in e&&"$cases"in e}resolveSwitch(e,n,r){const i={...r,skipCache:!0};let l;try{l=this.resolveBindings(e.$switch,n,i)?.toString()?.trim()??""}catch(d){Ma.warn("resolveSwitch: $switch 키 평가 실패:",e.$switch,d),l=""}let c;if(l&&e.$cases&&l in e.$cases)c=e.$cases[l];else if("$default"in e)c=e.$default;else return;return typeof c=="string"?c.includes("{{")?this.resolveBindings(c,n,i):c:c!==null&&typeof c=="object"?this.resolveObject(c,n,i):c}};M(ca,"PATH_SEGMENT_PATTERN",/([^.[\]]+)|\[(\d+)\]/g),M(ca,"CACHE_EXPIRY",3e4);let $n=ca;const oc=new $n,lc={mobile:{min:0,max:767},tablet:{min:768,max:1023},desktop:{min:1024,max:1/0},portable:{min:0,max:1023}},cc=new Map;function P0(s,e){let n=null;return((...r)=>{n!==null&&clearTimeout(n),n=setTimeout(()=>{s(...r)},e)})}class U0{constructor(){M(this,"subscribers",new Set);M(this,"currentWidth",1024);M(this,"debouncedHandler",null);M(this,"isInitialized",!1);this.initialize()}initialize(){typeof window>"u"||(this.currentWidth=window.innerWidth,this.isInitialized=!0,this.debouncedHandler=P0(()=>{this.currentWidth=window.innerWidth,this.notifySubscribers()},150),window.addEventListener("resize",this.debouncedHandler))}notifySubscribers(){this.subscribers.forEach(e=>{e(this.currentWidth)})}subscribe(e){return this.subscribers.add(e),e(this.currentWidth),()=>{this.subscribers.delete(e)}}getWidth(){return this.currentWidth}parseRange(e){if(cc.has(e))return cc.get(e)??null;let n=null;if(lc[e])n={...lc[e]};else{const r=e.match(/^(-?\d*)-(-?\d*)$/);if(r){const[,i,l]=r,c=i===""?0:parseInt(i,10),d=l===""?1/0:parseInt(l,10);!isNaN(c)&&!isNaN(d)&&c<=d&&(n={min:c,max:d})}}return cc.set(e,n),n}getMatchingKey(e,n){const r=[];for(const i of Object.keys(e)){const l=this.parseRange(i);l&&n>=l.min&&n<=l.max&&r.push({key:i,range:l,isPreset:!!lc[i]})}return r.length===0?null:(r.sort((i,l)=>{if(i.isPreset!==l.isPreset)return i.isPreset?1:-1;const c=i.range.max-i.range.min,d=l.range.max-l.range.min;return c-d}),r[0].key)}matches(e){const n=this.parseRange(e);return n?this.currentWidth>=n.min&&this.currentWidth<=n.max:!1}clearSubscribers(){this.subscribers.clear()}destroy(){this.debouncedHandler&&typeof window<"u"&&window.removeEventListener("resize",this.debouncedHandler),this.clearSubscribers(),cc.clear()}_setWidthForTesting(e){this.currentWidth=e,this.notifySubscribers()}}const Ci=new U0,Co=pt("ConditionEvaluator");function B0(s,e,n,r){return Vd(Zg(s),e,n,{skipCache:!0,onEmpty:()=>{Co.warn(`${r}: 빈 바인딩 \`{{}}\` — undefined 로 해석합니다.`)}})}function cm(s,e,n){if(!s)return!0;try{const r=Oa(s);let i;if(r!==null?i=B0(r,e,n,"evaluateStringCondition"):i=n.resolveBindings(s,e,{skipCache:!0}),typeof i=="string"){const l=i.toLowerCase().trim();if(l==="false"||l==="0"||l===""||l==="null"||l==="undefined")return!1}return!!i}catch(r){return Co.warn(`evaluateStringCondition: 조건 평가 실패: ${s}`,r),!1}}function uc(s,e,n){if(typeof s=="string")return cm(s,e,n);if("and"in s){if(!Array.isArray(s.and)||s.and.length===0)return Co.warn("evaluateConditionExpression: AND 그룹이 비어있습니다"),!0;for(const r of s.and)if(!uc(r,e,n))return!1;return!0}if("or"in s){if(!Array.isArray(s.or)||s.or.length===0)return Co.warn("evaluateConditionExpression: OR 그룹이 비어있습니다"),!1;for(const r of s.or)if(uc(r,e,n))return!0;return!1}return Co.warn("evaluateConditionExpression: 알 수 없는 조건 형식",s),!1}function um(s,e,n){if(!Array.isArray(s)||s.length===0)return{matched:!1,branchIndex:-1};for(let r=0;r{cr.warn(`evaluateIfCondition: 빈 바인딩 \`{{}}\` (컴포넌트: ${r||"unknown"}) — undefined 로 해석합니다.`)}}):l=n.resolveBindings(s,e,{skipCache:!0}),typeof l=="string"){const c=l.toLowerCase().trim();if(c==="false"||c==="0"||c===""||c==="null"||c==="undefined")return!1}return!!l}catch(i){return cr.warn(`evaluateIfCondition: 조건 평가 실패 (컴포넌트: ${r||"unknown"}):`,i),!1}}function hs(s,e,n,r){if(s.if!==void 0)return pm(s.if,e,n,r);if(s.condition!==void 0)return pm(s.condition,e,n,r);if(s.conditions===void 0)return!0;const i=s.conditions;try{return q0(i)?uc(i,e,n):G0(i)?um(i,e,n).matched:(cr.warn(`evaluateRenderCondition: 알 수 없는 conditions 형식 (컴포넌트: ${r||"unknown"})`),!0)}catch(l){return cr.warn(`evaluateRenderCondition: conditions 평가 실패 (컴포넌트: ${r||"unknown"}):`,l),!1}}function gm(s){if(!s.responsive)return s;const e=Ci.getWidth(),n=Ci.getMatchingKey(s.responsive,e);if(!n)return s;const r=s.responsive[n];return{...s,props:{...s.props,...r.props},children:r.children??s.children,text:r.text??s.text,if:r.if??s.if,iteration:r.iteration??s.iteration}}function Eo(s,e,n,r,i){if(!s||s.length===0)return[];const l=i?.bindingEngine??new $n,c=i?.translationEngine??vi.getInstance(),d=i?.translationContext??{templateId:"",locale:"ko"},f=V0(e,i?.componentContext),h=(S,_)=>{if(typeof S=="string")return ac(S)||ic(S)||!/\$t:[a-zA-Z0-9._-]+/.test(S)?S:c.resolveTranslations(S,d,_);if(Array.isArray(S))return S.map(x=>h(x,_));if(S&&typeof S=="object"){const x={};for(const[A,D]of Object.entries(S))x[A]=h(D,_);return x}return S},m=(S,_)=>{if(typeof S=="string"){if(ac(S))return Kd(S);if(ic(S)){const A=[],D=S.replace(new RegExp(`${bo}([^${Si}]*)${Si}`,"g"),(k,L)=>(A.push(L),`${La}${A.length-1}${La}`));let O=D;return/\$t:[a-zA-Z0-9._-]+/.test(D)&&(O=c.resolveTranslations(D,d,_)),O.replace(new RegExp(`${La}(\\d+)${La}`,"g"),(k,L)=>A[parseInt(L)])}if(S.startsWith("$t:defer:")){const A="$t:"+S.slice(9);return c.resolveTranslations(A,d,_)}if(S.startsWith("$t:"))return c.resolveTranslations(S,d,_);if(/\$t:[a-zA-Z0-9._-]+/.test(S)){const A=S.trim();if(!(A.startsWith("{")&&A.endsWith("}")||A.startsWith("[")&&A.endsWith("]"))&&!S.includes("{{"))return c.resolveTranslations(S,d,_)}const x=hm(S);if(x!==null){let A=!1,D=x;x.startsWith(lr)&&(A=!0,D=x.slice(lr.length));let O=!1,k=Vd(D,_,l,{skipCache:!0,onError:L=>{cr.warn("renderItemChildren: 표현식 평가 실패:",L),O=!0},onEmpty:()=>{cr.warn("renderItemChildren: 빈 바인딩 `{{}}` 은 해석하지 않습니다.")}});return O?void 0:(A||(k=h(k,_)),A&&k!=null?fs(k):k)}if(S.includes("{{")){const A=l.resolveBindings(S,_,{skipCache:!0});return typeof A=="string"&&/\$t:[a-zA-Z0-9._-]+/.test(A)?c.resolveTranslations(A,d,_):A}return S}if(Array.isArray(S))return S.map(x=>m(x,_));if(S&&typeof S=="object"){if(l.isSwitchExpression(S))return l.resolveSwitch(S,_,{skipCache:!0});if(l.isActionDefinition(S))return{...S};const x={};for(const[A,D]of Object.entries(S))x[A]=m(D,_);return x}return S},y=(S,_)=>{if(!S)return{};const x={};for(const[A,D]of Object.entries(S))x[A]=m(D,_);return x},v=(S,_,x)=>{const A=gm(S),D=A.iteration;if(!D)return[];const O=af(D.source,x,l);if(!Array.isArray(O))return cr.warn(`renderItemChildren: iteration.source가 배열이 아닙니다: ${D.source}`),[];const k=dm();if(k?.isEnabled()){const B=`${_}-iteration`;k.trackIteration(B,D.source,D.item_var,D.index_var,O.length)}const L=n[A.name];return L?O.flatMap((B,q)=>{const z={...x,[D.item_var]:B,[`${D.item_var}_index`]:q,...D.index_var?{[D.index_var]:q}:{}},W=hs({if:A.if,condition:A.condition,conditions:A.conditions},z,l,A.id);if(A.if&&k?.isEnabled()){const Et=`${_}-iter-${q}-if`;k.trackIfCondition(Et,A.if,W,A.name)}if(!W)return[];const de=A.id?String($a(m(A.id,z))):void 0,_e=`${_}-iter-${q}`,Se=i?.getRemountKey?i.getRemountKey(de,_e):_e,je=y(A.props,z),Fe=sf(je,A.actions,z,{actionDispatcher:i?.actionDispatcher,componentContext:i?.componentContext});let ze=null;return A.text!==void 0?ze=m(A.text,z):A.children&&A.children.length>0&&(ze=Eo(A.children,z,n,Se,i)),k?.isEnabled()&&k.trackRender(A.name),[Xe.createElement(L,{key:Se,...$a(Fe)},$a(ze))]}):(cr.warn(`renderItemChildren: 컴포넌트를 찾을 수 없습니다: ${A.name}`),[])};return s.flatMap((S,_)=>{const x=gm(S),A=x.id?String($a(m(x.id,f))):void 0,D=r?`${r}-${A||_}`:A||`child-${_}`,O=i?.getRemountKey?i.getRemountKey(A,D):D;if(x.iteration)return v(x,O,f);const k=hs({if:x.if,condition:x.condition,conditions:x.conditions},f,l,x.id),L=dm();if(x.if&&L?.isEnabled()){const de=`${O}-if`;L.trackIfCondition(de,x.if,k,x.name)}if(!k)return[];const B=n[x.name];if(!B)return cr.warn(`renderItemChildren: 컴포넌트를 찾을 수 없습니다: ${x.name}`),[];const q=y(x.props,f),z=sf(q,x.actions,f,{actionDispatcher:i?.actionDispatcher,componentContext:i?.componentContext});let W=null;return x.text!==void 0?W=m(x.text,f):x.children&&x.children.length>0&&(W=Eo(x.children,f,n,O,i)),L?.isEnabled()&&L.trackRender(x.name),[Xe.createElement(B,{key:O,...$a(z)},$a(W))]})}function ps(s,e,n,r){let i,l;return n instanceof $n?(i=n,l=r):(i=fm,l=n),i.resolveBindings(s,e,l)}function K0(s,e,n,r){const i=n??fm,l={skipCache:!0,...r};let c;try{c=i.resolveBindings(s.key,e,{skipCache:l.skipCache})?.toString()?.trim()??""}catch(h){cr.warn("resolveClassMap: key 평가 실패:",s.key,h),c=""}let d="";c&&s.variants&&c in s.variants?d=s.variants[c]:s.default&&(d=s.default);const f=[];return s.base?.trim()&&f.push(s.base.trim()),d?.trim()&&f.push(d.trim()),f.join(" ")}function mm(s,e){return function(){return s.apply(e,arguments)}}const{toString:W0}=Object.prototype,{getPrototypeOf:Na}=Object,{iterator:_o,toStringTag:ym}=Symbol,Ao=(({hasOwnProperty:s})=>(e,n)=>s.call(e,n))(Object.prototype),bm=s=>typeof s=="string"&&(s==="__proto__"||s==="constructor"||s==="prototype"),vm=(s,e,n)=>s===Object.prototype||!n&&e===null,Y0=s=>{if(!Object.isExtensible(s))return!1;const e=Object.getOwnPropertyNames(s);return Object.getOwnPropertySymbols&&e.push(...Object.getOwnPropertySymbols(s)),e.every(n=>{if(bm(n))return!1;const r=Object.getOwnPropertyDescriptor(s,n);return!!r&&r.configurable&&r.writable===!0})},xo=(s,e)=>{let n=s;const r=[];for(;n!=null;){if(r.indexOf(n)!==-1)return!1;r.push(n);const i=Na(n);if(vm(n,i,n===s))return!1;if(Ao(n,e))return!0;n=i}return!1},X0=(s,e)=>s!=null&&xo(s,e)?s[e]:void 0,J0=s=>{if(s==null||typeof s!="object"&&typeof s!="function")return s;const e=Na(s);if(e===null&&Y0(s))return s;const n=Object.create(null),r=Object.create(null),i=[];let l=s;for(;l!=null&&i.indexOf(l)===-1;){i.push(l);const c=l===s?e:Na(l);if(vm(l,c,l===s))break;const d=Object.getOwnPropertyNames(l);Object.getOwnPropertySymbols&&d.push(...Object.getOwnPropertySymbols(l));for(const f of d)bm(f)||Ao(r,f)||(n[f]=s[f],r[f]=!0);l=c}return n},of=(s=>e=>{const n=W0.call(e);return s[n]||(s[n]=n.slice(8,-1).toLowerCase())})(Object.create(null)),ur=s=>(s=s.toLowerCase(),e=>of(e)===s),dc=s=>e=>typeof e===s,{isArray:Ei}=Array,_i=dc("undefined");function gs(s){return s!==null&&!_i(s)&&s.constructor!==null&&!_i(s.constructor)&&Hn(s.constructor.isBuffer)&&s.constructor.isBuffer(s)}const Sm=ur("ArrayBuffer");function Q0(s){let e;return typeof ArrayBuffer<"u"&&ArrayBuffer.isView?e=ArrayBuffer.isView(s):e=s&&s.buffer&&Sm(s.buffer),e}const Z0=dc("string"),Hn=dc("function"),wm=dc("number"),ms=s=>s!==null&&typeof s=="object",eC=s=>s===!0||s===!1,fc=s=>{if(!ms(s))return!1;const e=Na(s);return(e===null||e===Object.prototype||Na(e)===null)&&!xo(s,ym)&&!xo(s,_o)},tC=s=>{if(!ms(s)||gs(s))return!1;try{return Object.keys(s).length===0&&Object.getPrototypeOf(s)===Object.prototype}catch{return!1}},nC=ur("Date"),rC=ur("File"),aC=s=>!!(s&&typeof s.uri<"u"),iC=s=>s&&typeof s.getParts<"u",sC=ur("Blob"),oC=ur("FileList"),lC=ur("Set"),cC=s=>ms(s)&&Hn(s.pipe);function uC(){return typeof globalThis<"u"?globalThis:typeof self<"u"?self:typeof window<"u"?window:typeof global<"u"?global:{}}const Cm=uC(),Em=typeof Cm.FormData<"u"?Cm.FormData:void 0,dC=s=>{if(!s)return!1;if(Em&&s instanceof Em)return!0;const e=Na(s);if(!e||e===Object.prototype||!Hn(s.append))return!1;const n=of(s);return n==="formdata"||n==="object"&&Hn(s.toString)&&s.toString()==="[object FormData]"},fC=ur("URLSearchParams"),[hC,pC,gC,mC]=["ReadableStream","Request","Response","Headers"].map(ur),yC=s=>s.trim?s.trim():s.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,"");function To(s,e,{allOwnKeys:n=!1}={}){if(s===null||typeof s>"u")return;let r,i;if(typeof s!="object"&&(s=[s]),Ei(s))for(r=0,i=s.length;r0;)if(i=n[r],e===i.toLowerCase())return i;return null}const Ai=typeof globalThis<"u"?globalThis:typeof self<"u"?self:typeof window<"u"?window:global,Am=s=>!_i(s)&&s!==Ai;function lf(...s){const{caseless:e,skipUndefined:n}=Am(this)&&this||{},r={},i=(l,c)=>{if(c==="__proto__"||c==="constructor"||c==="prototype")return;const d=e&&typeof c=="string"&&_m(r,c)||c,f=Ao(r,d)?r[d]:void 0;fc(f)&&fc(l)?r[d]=lf(f,l):fc(l)?r[d]=lf({},l):Ei(l)?r[d]=l.slice():(!n||!_i(l))&&(r[d]=l)};for(let l=0,c=s.length;l(To(e,(i,l)=>{n&&Hn(i)?Object.defineProperty(s,l,{__proto__:null,value:mm(i,n),writable:!0,enumerable:!0,configurable:!0}):Object.defineProperty(s,l,{__proto__:null,value:i,writable:!0,enumerable:!0,configurable:!0})},{allOwnKeys:r}),s),vC=s=>(s.charCodeAt(0)===65279&&(s=s.slice(1)),s),SC=(s,e,n,r)=>{s.prototype=Object.create(e.prototype,r),Object.defineProperty(s.prototype,"constructor",{__proto__:null,value:s,writable:!0,enumerable:!1,configurable:!0}),Object.defineProperty(s,"super",{__proto__:null,value:e.prototype}),n&&Object.assign(s.prototype,n)},wC=(s,e,n,r)=>{let i,l,c;const d={};if(e=e||{},s==null)return e;do{for(i=Object.getOwnPropertyNames(s),l=i.length;l-- >0;)c=i[l],(!r||r(c,s,e))&&!d[c]&&(e[c]=s[c],d[c]=!0);s=n!==!1&&Na(s)}while(s&&(!n||n(s,e))&&s!==Object.prototype);return e},CC=(s,e,n)=>{s=String(s),(n===void 0||n>s.length)&&(n=s.length),n-=e.length;const r=s.indexOf(e,n);return r!==-1&&r===n},EC=s=>{if(!s)return null;if(Ei(s))return s;let e=s.length;if(!wm(e))return null;const n=new Array(e);for(;e-- >0;)n[e]=s[e];return n},_C=(s=>e=>s&&e instanceof s)(typeof Uint8Array<"u"&&Na(Uint8Array)),AC=(s,e)=>{const r=(s&&s[_o]).call(s);let i;for(;(i=r.next())&&!i.done;){const l=i.value;e.call(s,l[0],l[1])}},xC=(s,e)=>{let n;const r=[];for(;(n=s.exec(e))!==null;)r.push(n);return r},TC=ur("HTMLFormElement"),kC=s=>s.toLowerCase().replace(/[-_\s]([a-z\d])(\w*)/g,function(n,r,i){return r.toUpperCase()+i}),{propertyIsEnumerable:RC}=Object.prototype,DC=ur("RegExp"),xm=(s,e)=>{const n=Object.getOwnPropertyDescriptors(s),r={};To(n,(i,l)=>{let c;(c=e(i,l,s))!==!1&&(r[l]=c||i)}),Object.defineProperties(s,r)},OC=s=>{xm(s,(e,n)=>{if(Hn(s)&&["arguments","caller","callee"].includes(n))return!1;const r=s[n];if(Hn(r)){if(e.enumerable=!1,"writable"in e){e.writable=!1;return}e.set||(e.set=()=>{throw Error("Can not rewrite read-only method '"+n+"'")})}})},LC=(s,e)=>{const n={},r=i=>{i.forEach(l=>{n[l]=!0})};return Ei(s)?r(s):r(String(s).split(e)),n},$C=()=>{},MC=(s,e)=>s!=null&&Number.isFinite(s=+s)?s:e;function NC(s){return!!(s&&Hn(s.append)&&s[ym]==="FormData"&&s[_o])}const IC=s=>{const e=new WeakSet,n=r=>{if(ms(r)){if(e.has(r))return;if(gs(r))return r;if(!("toJSON"in r)){e.add(r);let i;if(lC(r)){i=[];for(const l of r){const c=n(l);!_i(c)&&i.push(c)}}else i=Ei(r)?[]:{},To(r,(l,c)=>{const d=n(l);!_i(d)&&(i[c]=d)});return e.delete(r),i}}return r};return n(s)},jC=ur("AsyncFunction"),HC=s=>s&&(ms(s)||Hn(s))&&Hn(s.then)&&Hn(s.catch),Tm=((s,e)=>s?setImmediate:e?((n,r)=>(Ai.addEventListener("message",({source:i,data:l})=>{i===Ai&&l===n&&r.length&&r.shift()()},!1),i=>{r.push(i),Ai.postMessage(n,"*")}))(`axios@${Math.random()}`,[]):n=>setTimeout(n))(typeof setImmediate=="function",Hn(Ai.postMessage)),zC=typeof queueMicrotask<"u"?queueMicrotask.bind(Ai):typeof process<"u"&&process.nextTick||Tm,km=s=>s!=null&&Hn(s[_o]),G={isArray:Ei,isArrayBuffer:Sm,isBuffer:gs,isFormData:dC,isArrayBufferView:Q0,isString:Z0,isNumber:wm,isBoolean:eC,isObject:ms,isPlainObject:fc,isEmptyObject:tC,isReadableStream:hC,isRequest:pC,isResponse:gC,isHeaders:mC,isUndefined:_i,isDate:nC,isFile:rC,isReactNativeBlob:aC,isReactNative:iC,isBlob:sC,isRegExp:DC,isFunction:Hn,isStream:cC,isURLSearchParams:fC,isTypedArray:_C,isFileList:oC,forEach:To,merge:lf,extend:bC,trim:yC,stripBOM:vC,inherits:SC,toFlatObject:wC,kindOf:of,kindOfTest:ur,endsWith:CC,toArray:EC,forEachEntry:AC,matchAll:xC,isHTMLForm:TC,hasOwnProperty:Ao,hasOwnProp:Ao,hasOwnInPrototypeChain:xo,getSafeProp:X0,toSafeFlatObject:J0,reduceDescriptors:xm,freezeMethods:OC,toObjectSet:LC,toCamelCase:kC,noop:$C,toFiniteNumber:MC,findKey:_m,global:Ai,isContextDefined:Am,isSpecCompliantForm:NC,toJSONObject:IC,isAsyncFn:jC,isThenable:HC,setImmediate:Tm,asap:zC,isIterable:km,isSafeIterable:s=>s!=null&&xo(s,_o)&&km(s)},PC=G.toObjectSet(["age","authorization","content-length","content-type","etag","expires","from","host","if-modified-since","if-unmodified-since","last-modified","location","max-forwards","proxy-authorization","referer","retry-after","user-agent"]),UC=s=>{const e={};let n,r,i;return s&&s.split(` +`).forEach(function(c){i=c.indexOf(":"),n=c.substring(0,i).trim().toLowerCase(),r=c.substring(i+1).trim();const d=G.hasOwnProp(e,n);!n||d&&G.hasOwnProp(PC,n)||(n==="set-cookie"?d?e[n].push(r):e[n]=[r]:e[n]=d?e[n]+", "+r:r)}),e};function BC(s){let e=0,n=s.length;for(;ee;){const r=s.charCodeAt(n-1);if(r!==9&&r!==32)break;n-=1}return e===0&&n===s.length?s:s.slice(e,n)}const qC=new RegExp("[\\u0000-\\u0008\\u000a-\\u001f\\u007f]+","g"),GC=new RegExp("[^\\u0009\\u0020-\\u007e\\u0080-\\u00ff]+","g");function cf(s,e){return G.isArray(s)?s.map(n=>cf(n,e)):BC(String(s).replace(e,""))}const FC=s=>cf(s,qC),VC=s=>cf(s,GC);function Rm(s){const e=Object.create(null);return G.forEach(s.toJSON(),(n,r)=>{e[r]=VC(n)}),e}const Dm=Symbol("internals");function ko(s){return s&&String(s).trim().toLowerCase()}function hc(s){return s===!1||s==null?s:G.isArray(s)?s.map(hc):FC(String(s))}function KC(s){const e=Object.create(null),n=/([^\s,;=]+)\s*(?:=\s*([^,;]+))?/g;let r;for(;r=n.exec(s);)e[r[1]]=r[2];return e}const WC=/^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/;function uf(s){let e=0,n=s.length;for(;ee;){const r=s.charCodeAt(n-1);if(r!==9&&r!==32)break;n-=1}return e===0&&n===s.length?s:s.slice(e,n)}function YC(s){const e=s.length-1;if(e<1||s.charCodeAt(0)!==34||s.charCodeAt(e)!==34)return s;let n="";for(let r=1;r=e))return s;n+=s[r]}return n}function XC(s){const e=Object.create(null),n=String(s);let r=0,i=!1,l=!1;function c(d){const f=uf(n.slice(r,d)),h=f.indexOf("=");if(h<1)return;const m=uf(f.slice(0,h));if(!WC.test(m))return;const y=m.toLowerCase();if(y==="__proto__"||y==="constructor"||y==="prototype")return;const v=uf(f.slice(h+1));e[y]=YC(v)}for(let d=0;d/^[-_a-zA-Z0-9^`|~,!#$%&'*+.]+$/.test(s.trim());function df(s,e,n,r,i){if(G.isFunction(r))return r.call(this,e,n);if(i&&(e=n),!!G.isString(e)){if(G.isString(r))return e.indexOf(r)!==-1;if(G.isRegExp(r))return r.test(e)}}function QC(s){return s.trim().toLowerCase().replace(/([a-z\d])(\w*)/g,(e,n,r)=>n.toUpperCase()+r)}function ZC(s,e){const n=G.toCamelCase(" "+e);["get","set","has"].forEach(r=>{Object.defineProperty(s,r+n,{__proto__:null,value:function(i,l,c){return this[r].call(this,e,i,l,c)},configurable:!0})})}let xn=class{constructor(e){e&&this.set(e)}set(e,n,r){const i=this;function l(d,f,h){const m=ko(f);if(!m)return;const y=G.findKey(i,m);(!y||i[y]===void 0||h===!0||h===void 0&&i[y]!==!1)&&(i[y||f]=hc(d))}const c=(d,f)=>G.forEach(d,(h,m)=>l(h,m,f));if(G.isPlainObject(e)||e instanceof this.constructor)c(e,n);else if(G.isString(e)&&(e=e.trim())&&!JC(e))c(UC(e),n);else if(G.isObject(e)&&G.isSafeIterable(e)){let d=Object.create(null),f,h;for(const m of e){if(!G.isArray(m))throw new TypeError("Object iterator must return a key-value pair");h=m[0],G.hasOwnProp(d,h)?(f=d[h],d[h]=G.isArray(f)?[...f,m[1]]:[f,m[1]]):d[h]=m[1]}c(d,n)}else e!=null&&l(n,e,r);return this}get(e,n){if(e=ko(e),e){const r=G.findKey(this,e);if(r){const i=this[r];if(!n)return i;if(n===!0)return KC(i);if(G.isFunction(n))return n.call(this,i,r);if(G.isRegExp(n))return n.exec(i);throw new TypeError("parser must be boolean|regexp|function")}}}has(e,n){if(e=ko(e),e){const r=G.findKey(this,e);return!!(r&&this[r]!==void 0&&(!n||df(this,this[r],r,n)))}return!1}delete(e,n){const r=this;let i=!1;function l(c){if(c=ko(c),c){const d=G.findKey(r,c);d&&(!n||df(r,r[d],d,n))&&(delete r[d],i=!0)}}return G.isArray(e)?e.forEach(l):l(e),i}clear(e){const n=Object.keys(this);let r=n.length,i=!1;for(;r--;){const l=n[r];(!e||df(this,this[l],l,e,!0))&&(delete this[l],i=!0)}return i}normalize(e){const n=this,r={};return G.forEach(this,(i,l)=>{const c=G.findKey(r,l);if(c){n[c]=hc(i),delete n[l];return}const d=e?QC(l):String(l).trim();d!==l&&delete n[l],n[d]=hc(i),r[d]=!0}),this}concat(...e){return this.constructor.concat(this,...e)}toJSON(e){const n=Object.create(null);return G.forEach(this,(r,i)=>{r!=null&&r!==!1&&(n[i]=e&&G.isArray(r)?r.join(", "):r)}),n}[Symbol.iterator](){return Object.entries(this.toJSON())[Symbol.iterator]()}toString(){return Object.entries(this.toJSON()).map(([e,n])=>e+": "+n).join(` +`)}getSetCookie(){const e=this.get("set-cookie");return G.isArray(e)?e:e==null||e===!1?[]:[e]}get[Symbol.toStringTag](){return"AxiosHeaders"}static from(e){return e instanceof this?e:new this(e)}static parseParameters(e){return XC(e)}static concat(e,...n){const r=new this(e);return n.forEach(i=>r.set(i)),r}static accessor(e){const r=(this[Dm]=this[Dm]={accessors:{}}).accessors,i=this.prototype;function l(c){const d=ko(c);r[d]||(ZC(i,c),r[d]=!0)}return G.isArray(e)?e.forEach(l):l(e),this}};xn.accessor(["Content-Type","Content-Length","Accept","Accept-Encoding","User-Agent","Authorization"]),G.reduceDescriptors(xn.prototype,({value:s},e)=>{let n=e[0].toUpperCase()+e.slice(1);return{get:()=>s,set(r){this[n]=r}}}),G.freezeMethods(xn);const pc="[REDACTED ****]";function eE(s){if(G.hasOwnProp(s,"toJSON"))return!0;let e=Object.getPrototypeOf(s);for(;e&&e!==Object.prototype;){if(G.hasOwnProp(e,"toJSON"))return!0;e=Object.getPrototypeOf(e)}return!1}function tE(s,e){const n=new Set(e.map(l=>String(l).toLowerCase())),r=[],i=l=>{if(l===null||typeof l!="object"||G.isBuffer(l))return l;if(r.indexOf(l)!==-1)return;l instanceof xn&&(l=l.toJSON()),r.push(l);let c;if(G.isArray(l))c=[],l.forEach((d,f)=>{const h=i(d);G.isUndefined(h)||(c[f]=h)});else{if(!G.isPlainObject(l)&&eE(l))return r.pop(),l;c=Object.create(null);for(const[d,f]of Object.entries(l)){const h=n.has(d.toLowerCase())?pc:i(f);G.isUndefined(h)||(c[d]=h)}}return r.pop(),c};return i(s)}function Om(s){try{return String(s)}catch{return""}}function nE(s){return s.errors.map(n=>{try{return n&&n.message?Om(n.message):Om(n)}catch{return""}}).filter(Boolean).join("; ")||s.name||"AggregateError"}let we=class n0 extends Error{static from(e,n,r,i,l,c){let d=e.message;!d&&G.isArray(e.errors)&&e.errors.length&&(d=nE(e));const f=new n0(d,n||e.code,r,i,l);return Object.defineProperty(f,"cause",{__proto__:null,value:e,writable:!0,enumerable:!1,configurable:!0}),f.name=e.name,e.status!=null&&f.status==null&&(f.status=e.status),c&&Object.assign(f,c),f}constructor(e,n,r,i,l){super(e),Object.defineProperty(this,"message",{__proto__:null,value:e,enumerable:!0,writable:!0,configurable:!0}),this.name="AxiosError",this.isAxiosError=!0,n&&(this.code=n),r&&(this.config=r),i&&(this.request=i),l&&(this.response=l,this.status=l.status)}toJSON(){const e=this.config,n=e&&G.hasOwnProp(e,"redact")?e.redact:void 0,r=G.isArray(n)&&n.length>0?tE(e,n):G.toJSONObject(e);return{message:this.message,name:this.name,description:this.description,number:this.number,fileName:this.fileName,lineNumber:this.lineNumber,columnNumber:this.columnNumber,stack:this.stack,config:r,code:this.code,status:this.status}}};we.ERR_BAD_OPTION_VALUE="ERR_BAD_OPTION_VALUE",we.ERR_BAD_OPTION="ERR_BAD_OPTION",we.ECONNABORTED="ECONNABORTED",we.ETIMEDOUT="ETIMEDOUT",we.ECONNREFUSED="ECONNREFUSED",we.ERR_NETWORK="ERR_NETWORK",we.ERR_FR_TOO_MANY_REDIRECTS="ERR_FR_TOO_MANY_REDIRECTS",we.ERR_DEPRECATED="ERR_DEPRECATED",we.ERR_BAD_RESPONSE="ERR_BAD_RESPONSE",we.ERR_BAD_REQUEST="ERR_BAD_REQUEST",we.ERR_CANCELED="ERR_CANCELED",we.ERR_NOT_SUPPORT="ERR_NOT_SUPPORT",we.ERR_INVALID_URL="ERR_INVALID_URL",we.ERR_FORM_DATA_DEPTH_EXCEEDED="ERR_FORM_DATA_DEPTH_EXCEEDED";const rE=null,Lm=100;function ff(s){return G.isPlainObject(s)||G.isArray(s)}function $m(s){return G.endsWith(s,"[]")?s.slice(0,-2):s}function hf(s,e,n){return s?s.concat(e).map(function(i,l){return i=$m(i),!n&&l?"["+i+"]":i}).join(n?".":""):e}function aE(s){return G.isArray(s)&&!s.some(ff)}const iE=G.toFlatObject(G,{},null,function(e){return/^is[A-Z]/.test(e)});function gc(s,e,n){if(!G.isObject(s))throw new TypeError("target must be an object");e=e||new FormData;const r=(O,k)=>{const L=G.getSafeProp(n,O);return G.isUndefined(L)?k:L},i=r("metaTokens",!0),l=r("visitor")||x,c=r("dots",!1),d=r("indexes",!1),f=r("Blob")||typeof Blob<"u"&&Blob,h=r("maxDepth",Lm),m=f&&G.isSpecCompliantForm(e),y=[];if(!G.isFunction(l))throw new TypeError("visitor must be a function");function v(O){if(O===null)return"";if(G.isDate(O))return O.toISOString();if(G.isBoolean(O))return O.toString();if(!m&&G.isBlob(O))throw new we("Blob is not supported. Use a Buffer instead.");if(G.isArrayBuffer(O)||G.isTypedArray(O)){if(m&&typeof f=="function")return new f([O]);throw new we("Blob is not supported. Use a Buffer instead.",we.ERR_NOT_SUPPORT)}return O}function S(O){if(O>h)throw new we("Object is too deeply nested ("+O+" levels). Max depth: "+h,we.ERR_FORM_DATA_DEPTH_EXCEEDED)}function _(O,k){if(h===1/0)return JSON.stringify(O);const L=[];return JSON.stringify(O,function(q,z){if(!G.isObject(z))return z;for(;L.length&&L[L.length-1]!==this;)L.pop();return L.push(z),S(k+L.length-1),z})}function x(O,k,L){let B=O;if(G.isReactNative(e)&&G.isReactNativeBlob(O))return e.append(hf(L,k,c),v(O)),!1;if(O&&!L&&typeof O=="object"){if(G.endsWith(k,"{}"))k=i?k:k.slice(0,-2),O=_(O,1);else if(G.isArray(O)&&aE(O)||(G.isFileList(O)||G.endsWith(k,"[]"))&&(B=G.toArray(O)))return k=$m(k),B.forEach(function(z,W){!(G.isUndefined(z)||z===null)&&e.append(d===!0?hf([k],W,c):d===null?k:k+"[]",v(z))}),!1}return ff(O)?!0:(e.append(hf(L,k,c),v(O)),!1)}const A=Object.assign(iE,{defaultVisitor:x,convertValue:v,isVisitable:ff});function D(O,k,L=0){if(!G.isUndefined(O)){if(S(L),y.indexOf(O)!==-1)throw new Error("Circular reference detected in "+k.join("."));y.push(O),G.forEach(O,function(q,z){(!(G.isUndefined(q)||q===null)&&l.call(e,q,G.isString(z)?z.trim():z,k,A))===!0&&D(q,k?k.concat(z):[z],L+1)}),y.pop()}}if(!G.isObject(s))throw new TypeError("data must be an object");return D(s),e}function Mm(s){const e={"!":"%21","'":"%27","(":"%28",")":"%29","~":"%7E","%20":"+"};return encodeURIComponent(s).replace(/[!'()~]|%20/g,function(r){return e[r]})}function pf(s,e){this._pairs=[],s&&gc(s,this,e)}const Nm=pf.prototype;Nm.append=function(e,n){this._pairs.push([e,n])},Nm.toString=function(e){const n=e?r=>e.call(this,r,Mm):Mm;return this._pairs.map(function(i){return n(i[0])+"="+n(i[1])},"").join("&")};function sE(s){return encodeURIComponent(s).replace(/%3A/gi,":").replace(/%24/g,"$").replace(/%2C/gi,",").replace(/%20/g,"+")}function Im(s,e,n){if(!e)return s;s=s||"";const r=G.isFunction(n)?{serialize:n}:n,i=G.getSafeProp(r,"encode")||sE,l=G.getSafeProp(r,"serialize");let c;if(l?c=l(e,r):c=G.isURLSearchParams(e)?e.toString():new pf(e,r).toString(i),c){const d=s.indexOf("#");d!==-1&&(s=s.slice(0,d)),s+=(s.indexOf("?")===-1?"?":"&")+c}return s}const Ro=Symbol("internals");function jm(s){return s?s.length:0}function Hm(s){if(s)for(;s.length&&s[s.length-1]===null;)s.pop()}function Do(s,e){const n=s.handlers,r=jm(n);n!==e.handlersRef?(e.handlersRef=n,e.handlerEntries.clear()):r!==e.handlersLength&&(r?e.handlerEntries.forEach(function(l,c){n[l.index]!==l.handler&&e.handlerEntries.delete(c)}):e.handlerEntries.clear()),e.handlersLength=r}class zm{constructor(){this.handlers=[],this[Ro]={handlersRef:this.handlers,handlersLength:this.handlers.length,handlerEntries:new Map,iterationDepth:0,nextId:0}}use(e,n,r){const i={fulfilled:e,rejected:n,synchronous:r?r.synchronous:!1,runWhen:r?r.runWhen:null},l=this[Ro];this.handlers==null&&(this.handlers=[]),Do(this,l);const c=l.nextId++;return this.handlers.push(i),l.handlerEntries.set(c,{handler:i,index:this.handlers.length-1}),l.handlersLength=this.handlers.length,c}eject(e){const n=this[Ro];Do(this,n);const r=n.handlerEntries.get(e);if(r){if(n.handlerEntries.delete(e),this.handlers[r.index]!==r.handler)return;this.handlers[r.index]=null,n.iterationDepth||(Hm(this.handlers),n.handlersLength=this.handlers.length)}}clear(){this.handlers&&(this.handlers=[],Do(this,this[Ro]))}forEach(e){const n=this[Ro];Do(this,n),n.iterationDepth++;try{G.forEach(this.handlers,function(i){i!==null&&e(i)})}finally{--n.iterationDepth||(Do(this,n),Hm(this.handlers),n.handlersLength=jm(this.handlers))}}}const gf={silentJSONParsing:!0,forcedJSONParsing:!0,clarifyTimeoutError:!1,legacyInterceptorReqResOrdering:!0,advertiseZstdAcceptEncoding:!1,validateStatusUndefinedResolves:!0},oE={isBrowser:!0,classes:{URLSearchParams:typeof URLSearchParams<"u"?URLSearchParams:pf,FormData:typeof FormData<"u"?FormData:null,Blob:typeof Blob<"u"?Blob:null},protocols:["http","https","file","blob","url","data"]},mf=typeof window<"u"&&typeof document<"u",yf=typeof navigator=="object"&&navigator||void 0,lE=mf&&(!yf||["ReactNative","NativeScript","NS"].indexOf(yf.product)<0),cE=typeof WorkerGlobalScope<"u"&&self instanceof WorkerGlobalScope&&typeof self.importScripts=="function",uE=mf&&window.location.href||"http://localhost",gn={...Object.freeze(Object.defineProperty({__proto__:null,hasBrowserEnv:mf,hasStandardBrowserEnv:lE,hasStandardBrowserWebWorkerEnv:cE,navigator:yf,origin:uE},Symbol.toStringTag,{value:"Module"})),...oE};function dE(s,e){return gc(s,new gn.classes.URLSearchParams,{visitor:function(n,r,i,l){return gn.isNode&&G.isBuffer(n)?(this.append(r,n.toString("base64")),!1):l.defaultVisitor.apply(this,arguments)},...e})}const Pm=Lm;function Um(s){if(s>Pm)throw new we("FormData field is too deeply nested ("+s+" levels). Max depth: "+Pm,we.ERR_FORM_DATA_DEPTH_EXCEEDED)}function fE(s){const e=[],n=/[^.[\]]+|\[([^.[\]]*)]/g;let r;for(;(r=n.exec(s))!==null;)Um(e.length),e.push(r[0]==="[]"?"":r[1]||r[0]);return e}function hE(s){const e={},n=Object.keys(s);let r;const i=n.length;let l;for(r=0;r=n.length;return c=!c&&G.isArray(i)?i.length:c,f?(G.hasOwnProp(i,c)?i[c]=G.isArray(i[c])?i[c].concat(r):[i[c],r]:i[c]=r,!d):((!G.hasOwnProp(i,c)||!G.isObject(i[c]))&&(i[c]=[]),e(n,r,i[c],l)&&G.isArray(i[c])&&(i[c]=hE(i[c])),!d)}if(G.isFormData(s)&&G.isFunction(s.entries)){const n={};return G.forEachEntry(s,(r,i)=>{e(fE(r),i,n,0)}),n}return null}const qm=Object.freeze(["get","delete","head","options","post","put","patch","purge","link","unlink","query"]),ys=(s,e)=>s!=null&&G.hasOwnProp(s,e)?s[e]:void 0;function pE(s,e,n){if(G.isString(s))try{return(e||JSON.parse)(s),G.trim(s)}catch(r){if(r.name!=="SyntaxError")throw r}return(n||JSON.stringify)(s)}const Oo={transitional:gf,adapter:["xhr","http","fetch"],transformRequest:[function(e,n){const r=n.getContentType()||"",i=r.indexOf("application/json")>-1,l=G.isObject(e);if(l&&G.isHTMLForm(e)&&(e=new FormData(e)),G.isFormData(e))return i?JSON.stringify(Bm(e)):e;if(G.isArrayBuffer(e)||G.isBuffer(e)||G.isStream(e)||G.isFile(e)||G.isBlob(e)||G.isReadableStream(e))return e;if(G.isArrayBufferView(e))return e.buffer;if(G.isURLSearchParams(e))return n.setContentType("application/x-www-form-urlencoded;charset=utf-8",!1),e.toString();let d;if(l){const f=ys(this,"formSerializer");if(r.indexOf("application/x-www-form-urlencoded")>-1)return dE(e,f).toString();if((d=G.isFileList(e))||r.indexOf("multipart/form-data")>-1){const h=ys(this,"env"),m=h&&h.FormData;return gc(d?{"files[]":e}:e,m&&new m,f)}}return l||i?(n.setContentType("application/json",!1),pE(e)):e}],transformResponse:[function(e){const n=ys(this,"transitional")||Oo.transitional,r=n&&n.forcedJSONParsing,i=ys(this,"responseType"),l=i==="json";if(G.isResponse(e)||G.isReadableStream(e))return e;if(e&&G.isString(e)&&(r&&!i||l)){const d=!(n&&n.silentJSONParsing)&&l;try{return JSON.parse(e,ys(this,"parseReviver"))}catch(f){if(d)throw f.name==="SyntaxError"?we.from(f,we.ERR_BAD_RESPONSE,this,null,ys(this,"response")):f}}return e}],timeout:0,xsrfCookieName:"XSRF-TOKEN",xsrfHeaderName:"X-XSRF-TOKEN",maxContentLength:-1,maxBodyLength:-1,env:{FormData:gn.classes.FormData,Blob:gn.classes.Blob},validateStatus:function(e){return e>=200&&e<300},headers:{common:{Accept:"application/json, text/plain, */*","Content-Type":void 0}}};G.forEach(qm,s=>{Oo.headers[s]={}});function bf(s,e){const n=this||Oo,r=e||n,i=xn.from(r.headers);let l=r.data;return G.forEach(s,function(d){l=d.call(n,l,i.normalize(),e?e.status:void 0)}),i.normalize(),l}function Gm(s){return!!(s&&s.__CANCEL__)}let Lo=class extends we{constructor(e,n,r){super(e??"canceled",we.ERR_CANCELED,n,r),this.name="CanceledError",this.__CANCEL__=!0}};function Fm(s,e,n){const r=n.config.validateStatus;!n.status||!r||r(n.status)?s(n):e(new we("Request failed with status code "+n.status,n.status>=400&&n.status<500?we.ERR_BAD_REQUEST:we.ERR_BAD_RESPONSE,n.config,n.request,n))}const gE=/[\t\n\r]/g;function Vm(s){if(typeof s!="string")return s;let e=0;for(;e{n=y,i=null,l&&(clearTimeout(l),l=null),s(...m)};return[(...m)=>{const y=Date.now(),v=y-n;v>=r?c(m,y):(i=m,l||(l=setTimeout(()=>{l=null,c(i)},r-v)))},()=>i&&c(i),(...m)=>c(m)]}const mc=(s,e,n=3)=>{let r=0;const i=mE(50,250);return yE(l=>{if(!l||!G.isNumber(l.loaded))return;const c=l.loaded,d=l.lengthComputable?l.total:void 0,f=Math.max(0,d!=null?Math.min(c,d):c),h=Math.max(0,f-r),m=i(h);r=Math.max(r,f);const y={loaded:f,total:d,progress:d?f/d:void 0,bytes:h,rate:m||void 0,estimated:m&&d?(d-f)/m:void 0,event:l,lengthComputable:d!=null,[e?"download":"upload"]:!0};s(y)},n)},Km=(s,e)=>{const n=s!=null;return[r=>e[0]({lengthComputable:n,total:s,loaded:r}),e[1]]},Wm=(s,e=G.asap)=>(...n)=>e(()=>s(...n)),bE=gn.hasStandardBrowserEnv?((s,e)=>n=>(n=new URL(n,gn.origin),s.protocol===n.protocol&&s.host===n.host&&(e||s.port===n.port)))(new URL(gn.origin),gn.navigator&&/(msie|trident)/i.test(gn.navigator.userAgent)):()=>!0,vE=gn.hasStandardBrowserEnv?{write(s,e,n,r,i,l,c){if(typeof document>"u")return;const d=[`${s}=${encodeURIComponent(e)}`];G.isNumber(n)&&d.push(`expires=${new Date(n).toUTCString()}`),G.isString(r)&&d.push(`path=${r}`),G.isString(i)&&d.push(`domain=${i}`),l===!0&&d.push("secure"),G.isString(c)&&d.push(`SameSite=${c}`),document.cookie=d.join("; ")},read(s){if(typeof document>"u")return null;const e=document.cookie.split(";");for(let n=0;n0&&s.charCodeAt(n-1)===47;)n--;return s.slice(0,n)+"/"+e.replace(/^\/+/,"")}const CE=/^https?:(?!\/\/)/i;function EE(s){return s&&s.replace(/(^|&)([^=&]*=)?[^&]+/g,(e,n,r="")=>`${n}${r}${pc}`)}function _E(s){const e=s.replace(/^(https?:\/{0,2})[^/?#]*@/i,`$1${pc}@`),n=e.indexOf("#"),i=(n===-1?e:e.slice(0,n)).replace(/([?&][^=&#]*=)[^&#]*/g,`$1${pc}`);return n===-1?i:`${i}#${EE(e.slice(n+1))}`}function Ym(s,e){if(typeof s=="string"){const n=Vm(s);if(CE.test(n))throw new we(`Invalid URL ${JSON.stringify(_E(n))}: missing "//" after protocol`,we.ERR_INVALID_URL,e)}}function Xm(s,e,n,r){Ym(e,r);let i=!SE(e);return s&&(i||n===!1)?(Ym(s,r),wE(s,e)):e}const Jm=s=>s instanceof xn?{...s}:s,AE=s=>Object.getOwnPropertySymbols&&Object.getOwnPropertyDescriptor?Object.keys(s).concat(Object.getOwnPropertySymbols(s).filter(e=>Object.getOwnPropertyDescriptor(s,e).enumerable)):Object.keys(s);function xi(s,e){s=s||{},e=e||{};const n=Object.create(null);Object.defineProperty(n,"hasOwnProperty",{__proto__:null,value:Object.prototype.hasOwnProperty,enumerable:!1,writable:!0,configurable:!0});function r(m,y,v,S){return G.isPlainObject(m)&&G.isPlainObject(y)?G.merge.call({caseless:S},m,y):G.isPlainObject(y)?G.merge({},y):G.isArray(y)?y.slice():y}function i(m,y,v,S){if(G.isUndefined(y)){if(!G.isUndefined(m))return r(void 0,m,v,S)}else return r(m,y,v,S)}function l(m,y){if(!G.isUndefined(y))return r(void 0,y)}function c(m,y){if(G.isUndefined(y)){if(!G.isUndefined(m))return r(void 0,m)}else return r(void 0,y)}function d(m){const y=G.hasOwnProp(e,"transitional")?e.transitional:void 0;if(!G.isUndefined(y))if(G.isPlainObject(y)){if(G.hasOwnProp(y,m))return y[m]}else return;const v=G.hasOwnProp(s,"transitional")?s.transitional:void 0;if(G.isPlainObject(v)&&G.hasOwnProp(v,m))return v[m]}function f(m,y,v){if(G.hasOwnProp(e,v))return r(m,y);if(G.hasOwnProp(s,v))return r(void 0,m)}const h={url:l,method:l,data:l,baseURL:c,transformRequest:c,transformResponse:c,paramsSerializer:c,timeout:c,timeoutErrorMessage:c,withCredentials:c,withXSRFToken:c,adapter:c,responseType:c,xsrfCookieName:c,xsrfHeaderName:c,onUploadProgress:c,onDownloadProgress:c,decompress:c,maxContentLength:c,maxBodyLength:c,beforeRedirect:c,transport:c,httpAgent:c,httpsAgent:c,cancelToken:c,socketPath:c,allowedSocketPaths:c,responseEncoding:c,validateStatus:f,headers:(m,y,v)=>i(Jm(m),Jm(y),v,!0)};return G.forEach(AE({...s,...e}),function(y){if(y==="__proto__"||y==="constructor"||y==="prototype")return;const v=G.hasOwnProp(h,y)?h[y]:i,S=G.hasOwnProp(s,y)?s[y]:void 0,_=G.hasOwnProp(e,y)?e[y]:void 0,x=v(S,_,y);G.isUndefined(x)&&v!==f||(n[y]=x)}),G.hasOwnProp(e,"validateStatus")&&G.isUndefined(e.validateStatus)&&d("validateStatusUndefinedResolves")===!1&&(G.hasOwnProp(s,"validateStatus")?n.validateStatus=r(void 0,s.validateStatus):delete n.validateStatus),n}const xE=["content-type","content-length"];function TE(s,e,n){if(n!=="content-only"){s.set(e);return}Object.entries(e||{}).forEach(([r,i])=>{xE.includes(r.toLowerCase())&&s.set(r,i)})}const kE=s=>encodeURIComponent(s).replace(/%([0-9A-F]{2})/gi,(e,n)=>String.fromCharCode(parseInt(n,16)));function Qm(s){const e=xi({},s),n=v=>G.hasOwnProp(e,v)?e[v]:void 0,r=n("data");let i=n("withXSRFToken");const l=n("xsrfHeaderName"),c=n("xsrfCookieName");let d=n("headers");const f=n("auth"),h=n("baseURL"),m=n("allowAbsoluteUrls"),y=n("url");if(e.headers=d=xn.from(d),e.url=Im(Xm(h,y,m,e),n("params"),n("paramsSerializer")),f){const v=G.getSafeProp(f,"username")||"",S=G.getSafeProp(f,"password")||"";try{d.set("Authorization","Basic "+btoa(v+":"+(S?kE(S):"")))}catch(_){throw we.from(_,we.ERR_BAD_OPTION_VALUE,s)}}if(G.isFormData(r)){const v=G.getSafeProp(r,"getHeaders");gn.hasStandardBrowserEnv||gn.hasStandardBrowserWebWorkerEnv||G.isReactNative(r)?d.setContentType(void 0):G.isFunction(v)&&TE(d,v.call(r),n("formDataHeaderPolicy"))}if(gn.hasStandardBrowserEnv&&(G.isFunction(i)&&(i=i(e)),i===!0||i==null&&bE(e.url))){const S=l&&c&&vE.read(c);S&&d.set(l,S)}return e}const RE=typeof XMLHttpRequest<"u"&&function(s){return new Promise(function(n,r){const i=Qm(s);let l=i.data;const c=xn.from(i.headers).normalize();let{responseType:d,onUploadProgress:f,onDownloadProgress:h}=i,m,y,v,S,_,x;function A(){S&&S(),_&&_(),i.cancelToken&&i.cancelToken.unsubscribe(m),i.signal&&i.signal.removeEventListener("abort",m)}let D=new XMLHttpRequest;D.open(i.method.toUpperCase(),i.url,!0),D.timeout=i.timeout;function O(L){if(!D)return;if(D.status===0&&(vf(Vm(i.url))||vf(gn.origin))!=="file"&&!(D.responseURL&&D.responseURL.startsWith("file:"))){r(new we("Request aborted",we.ECONNABORTED,s,D)),A(),D=null;return}try{L?x&&x(L):_&&_()}catch(W){setTimeout(()=>{throw W})}if(!D)return;const B=xn.from("getAllResponseHeaders"in D&&D.getAllResponseHeaders()),z={data:!d||d==="text"||d==="json"?D.responseText:D.response,status:D.status,statusText:D.statusText,headers:B,config:s,request:D};Fm(function(de){n(de),A()},function(de){r(de),A()},z),D=null}"onloadend"in D?D.onloadend=O:D.onreadystatechange=function(){!D||D.readyState!==4||D.status===0&&!(D.responseURL&&D.responseURL.startsWith("file:"))||setTimeout(O)},D.onabort=function(){D&&(r(new we("Request aborted",we.ECONNABORTED,s,D)),A(),D=null)},D.onerror=function(B){const q=B&&B.message?B.message:"Network Error",z=new we(q,we.ERR_NETWORK,s,D);z.event=B||null,r(z),A(),D=null},D.ontimeout=function(){let B=i.timeout?"timeout of "+i.timeout+"ms exceeded":"timeout exceeded";const q=i.transitional||gf;i.timeoutErrorMessage&&(B=i.timeoutErrorMessage),r(new we(B,q.clarifyTimeoutError?we.ETIMEDOUT:we.ECONNABORTED,s,D)),A(),D=null},l===void 0&&c.setContentType(null),"setRequestHeader"in D&&G.forEach(Rm(c),function(B,q){D.setRequestHeader(q,B)}),G.isUndefined(i.withCredentials)||(D.withCredentials=!!i.withCredentials),d&&d!=="json"&&(D.responseType=i.responseType),h&&([v,_,x]=mc(h,!0),D.addEventListener("progress",v)),f&&D.upload&&([y,S]=mc(f),D.upload.addEventListener("progress",y),D.upload.addEventListener("loadend",S)),(i.cancelToken||i.signal)&&(m=L=>{D&&(r(!L||L.type?new Lo(null,s,D):L),D.abort(),A(),D=null)},i.cancelToken&&i.cancelToken.subscribe(m),i.signal&&(i.signal.aborted?m():i.signal.addEventListener("abort",m)));const k=vf(i.url);if(k&&!gn.protocols.includes(k)){r(new we("Unsupported protocol "+k+":",we.ERR_BAD_REQUEST,s)),A();return}D.send(l||null)})},DE=(s,e)=>{if(s=s?s.filter(Boolean):[],!e&&!s.length)return;const n=new AbortController;let r=!1;const i=function(f){if(!r){r=!0,c();const h=f instanceof Error?f:this.reason;n.abort(h instanceof we?h:new Lo(h instanceof Error?h.message:h))}};let l=e&&setTimeout(()=>{l=null,i(new we(`timeout of ${e}ms exceeded`,we.ETIMEDOUT))},e);const c=()=>{s&&(l&&clearTimeout(l),l=null,s.forEach(f=>{f.unsubscribe?f.unsubscribe(i):f.removeEventListener("abort",i)}),s=null)};s.forEach(f=>{if(!r){if(f.aborted){i.call(f);return}f.addEventListener("abort",i,{once:!0})}});const{signal:d}=n;return d.unsubscribe=()=>G.asap(c),d},OE=function*(s,e){let n=s.byteLength;if(n{const i=LE(s,e);let l=0,c,d=f=>{c||(c=!0,r&&r(f))};return new ReadableStream({async pull(f){try{const{done:h,value:m}=await i.next();if(h){d(),f.close();return}let y=m.byteLength;if(n){let v=l+=y;n(v)}f.enqueue(new Uint8Array(m))}catch(h){throw d(h),h}},cancel(f){return d(f),i.return()}},{highWaterMark:2})},ey=s=>s>=48&&s<=57||s>=65&&s<=70||s>=97&&s<=102,ty=(s,e,n)=>e+2s<=57?s-48:(s&223)-55,ME=s=>s>=65&&s<=90||s>=97&&s<=122||s>=48&&s<=57||s===43||s===47||s===45||s===95,NE=s=>s===9||s===10||s===12||s===13||s===32,IE=s=>{const e=Math.floor(s/4),n=s%4;return e*3+(n===2?1:n===3?2:0)},jE=s=>{const e=s.length;let n=0;return e>0&&s.charCodeAt(e-1)===61&&(n++,e>1&&s.charCodeAt(e-2)===61&&n++),Math.floor((e-n)*3/4)},HE=s=>{const e=s.length;let n=0,r=0,i=!1;for(let l=0;l0){i=!0;continue}n++}}return i||r>2||r>0&&(n+r)%4!==0||n%4===1?jE(s):IE(n)},zE=(s,e)=>{if(!s||typeof s!="string"||!s.startsWith("data:"))return 0;const n=s.indexOf(",");if(n<0)return 0;const r=s.slice(5,n),i=s.slice(n+1);if(/;base64/i.test(r))return e(i);let c=0;for(let d=0,f=i.length;d=55296&&h<=56319&&d+1=56320&&m<=57343?(c+=4,d++):c+=3}else c+=3}return c};function PE(s){const e=typeof s=="string"?s.indexOf("#"):-1;return zE(e===-1?s:s.slice(0,e),HE)}const Sf="1.20.0",ry=64*1024,UE={cache:"default",redirect:"follow",referrer:"about:client",referrerPolicy:"",mode:"cors",integrity:"",keepalive:!1,priority:"auto",window:null},{isFunction:yc}=G,BE=s=>encodeURIComponent(s).replace(/%([0-9A-F]{2})/gi,(e,n)=>String.fromCharCode(parseInt(n,16))),ay=s=>{if(!G.isString(s))return s;try{return decodeURIComponent(s)}catch{return s}},iy=(s,...e)=>{try{return!!s(...e)}catch{return!1}},qE=s=>{const e=s.indexOf("://");let n=s;return e!==-1&&(n=n.slice(e+3)),n.includes("@")||n.includes(":")},GE=s=>{const e=G.global!==void 0&&G.global!==null?G.global:globalThis,{ReadableStream:n,TextEncoder:r}=e;s=G.merge.call({skipUndefined:!0},{Request:e.Request,Response:e.Response},s);const{fetch:i,Request:l,Response:c}=s,d=i?yc(i):typeof fetch=="function",f=yc(l),h=yc(c);if(!d)return!1;const m=d&&yc(n),y=d&&(typeof r=="function"?(D=>O=>D.encode(O))(new r):async D=>new Uint8Array(await new l(D).arrayBuffer())),v=f&&m&&iy(()=>{let D=!1;const O=new l(gn.origin,{body:new n,method:"POST",get duplex(){return D=!0,"half"}}),k=O.headers.has("Content-Type");return O.body!=null&&O.body.cancel(),D&&!k}),S=h&&m&&iy(()=>G.isReadableStream(new c("").body)),_={stream:S&&(D=>D.body)};d&&["text","arrayBuffer","blob","formData","stream"].forEach(D=>{!_[D]&&(_[D]=(O,k)=>{let L=O&&O[D];if(L)return L.call(O);throw new we(`Response type '${D}' is not supported`,we.ERR_NOT_SUPPORT,k)})});const x=async D=>{if(D==null)return 0;if(G.isBlob(D))return D.size;if(G.isSpecCompliantForm(D))return(await new l(gn.origin,{method:"POST",body:D}).arrayBuffer()).byteLength;if(G.isArrayBufferView(D)||G.isArrayBuffer(D))return D.byteLength;if(G.isURLSearchParams(D)&&(D=D+""),G.isString(D))return(await y(D)).byteLength},A=async(D,O)=>{const k=G.toFiniteNumber(D.getContentLength());return k??x(O)};return async D=>{let{url:O,method:k,data:L,signal:B,cancelToken:q,timeout:z,onDownloadProgress:W,onUploadProgress:de,responseType:_e,headers:Se,withCredentials:je="same-origin",fetchOptions:Fe,maxContentLength:ze,maxBodyLength:Et,maxRedirects:Q}=Qm(D);const ue=G.isNumber(ze)&&ze>-1,De=G.isNumber(Et)&&Et>-1,ee=ge=>G.hasOwnProp(D,ge)?D[ge]:void 0;let me=i||fetch;_e=_e?(_e+"").toLowerCase():"text";let H=DE([B,q&&q.toAbortSignal()],z),T=null;const fe=H&&H.unsubscribe&&(()=>{H.unsubscribe()});let he,Ae=null;const se=()=>new we("Request body larger than maxBodyLength limit",we.ERR_BAD_REQUEST,D,T);try{let ge;const $e=ee("auth");if($e){const qe=G.getSafeProp($e,"username")||"",mt=G.getSafeProp($e,"password")||"";ge={username:qe,password:mt}}if(qE(O)){const qe=new URL(O,gn.origin);if(!ge&&(qe.username||qe.password)){const mt=ay(qe.username),Dt=ay(qe.password);ge={username:mt,password:Dt}}(qe.username||qe.password)&&(qe.username="",qe.password="",O=qe.href)}if(ge&&(Se.delete("authorization"),Se.set("Authorization","Basic "+btoa(BE((ge.username||"")+":"+(ge.password||""))))),ue&&typeof O=="string"&&O.startsWith("data:")&&PE(O)>ze)throw new we("maxContentLength size of "+ze+" exceeded",we.ERR_BAD_RESPONSE,D,T);if(De&&k!=="get"&&k!=="head"){const qe=await x(L);if(typeof qe=="number"&&isFinite(qe)&&(he=qe,qe>Et))throw se()}const Te=De&&(G.isReadableStream(L)||G.isStream(L)),Ie=(qe,mt,Dt)=>Zm(qe,ry,zt=>{if(De&&zt>Et)throw Ae=se();mt&&mt(zt)},Dt);if(v&&k!=="get"&&k!=="head"&&(de||Te)){if(he=he??await A(Se,L),he!==0||Te){let qe=new l(O,{method:"POST",body:L,duplex:"half"}),mt;if(G.isFormData(L)&&(mt=qe.headers.get("content-type"))&&Se.setContentType(mt),qe.body){const[Dt,zt]=de&&Km(he,mc(Wm(de)))||[];L=Ie(qe.body,Dt,zt)}}}else if(Te&&!f&&m&&k!=="get"&&k!=="head")L=Ie(L);else if(Te&&f&&!v&&k!=="get"&&k!=="head")throw new we("Stream request bodies are not supported by the current fetch implementation",we.ERR_NOT_SUPPORT,D,T);G.isString(je)||(je=je?"include":"omit");const xt=f&&"credentials"in l.prototype;if(G.isFormData(L)){const qe=Se.getContentType();qe&&/^multipart\/form-data/i.test(qe)&&!/boundary=/i.test(qe)&&Se.delete("content-type")}Se.set("User-Agent","axios/"+Sf,!1);const rt=Fe==null?Fe:Object.assign(Object.create(null),Fe);rt&&(delete rt.body,delete rt.headers,delete rt.method,delete rt.signal,delete rt.duplex,delete rt.credentials);const gt=Object.assign(Object.create(null),rt,{signal:H,method:k.toUpperCase(),headers:Rm(Se.normalize()),body:L,duplex:"half",credentials:xt?je:void 0});f&&(G.forEach(UE,(qe,mt)=>{gt[mt]===void 0&&(gt[mt]=qe)}),gt.signal===void 0&&(gt.signal=null),gt.body===void 0&&(gt.body=null)),Q===0&&(gt.redirect="manual",rt&&(rt.redirect="manual")),T=f&&new l(O,gt);let at=await(f?me(T,rt):me(O,gt));const Yt=xn.from(at.headers);if(ue){const qe=G.toFiniteNumber(Yt.getContentLength());if(qe!=null&&qe>ze)throw new we("maxContentLength size of "+ze+" exceeded",we.ERR_BAD_RESPONSE,D,T)}const Ht=S&&(_e==="stream"||_e==="response");if(S&&at.body&&(W||ue||Ht&&fe)){const qe={};["status","statusText","headers"].forEach(Ot=>{qe[Ot]=at[Ot]});const mt=G.toFiniteNumber(Yt.getContentLength()),[Dt,zt]=W&&Km(mt,mc(Wm(W),!0))||[];let kn=0;const hr=Ot=>{if(ue&&(kn=Ot,kn>ze))throw new we("maxContentLength size of "+ze+" exceeded",we.ERR_BAD_RESPONSE,D,T);Dt&&Dt(Ot)};at=new c(Zm(at.body,ry,hr,()=>{zt&&zt(),fe&&fe()}),qe)}_e=_e||"text";let bt=await _[G.findKey(_,_e)||"text"](at,D);if(ue&&!S&&!Ht){let qe;if(bt!=null&&(typeof bt.byteLength=="number"?qe=bt.byteLength:typeof bt.size=="number"?qe=bt.size:typeof bt=="string"&&(qe=typeof r=="function"?new r().encode(bt).byteLength:bt.length)),typeof qe=="number"&&qe>ze)throw new we("maxContentLength size of "+ze+" exceeded",we.ERR_BAD_RESPONSE,D,T)}return!Ht&&fe&&fe(),await new Promise((qe,mt)=>{Fm(qe,mt,{data:bt,headers:xn.from(at.headers),status:at.status,statusText:at.statusText,config:D,request:T})})}catch(ge){if(fe&&fe(),H&&H.aborted&&H.reason instanceof we){const $e=H.reason;throw $e.config=D,T&&($e.request=T),ge!==$e&&Object.defineProperty($e,"cause",{__proto__:null,value:ge,writable:!0,enumerable:!1,configurable:!0}),$e}if(Ae)throw T&&!Ae.request&&(Ae.request=T),Ae;if(ge instanceof we)throw T&&!ge.request&&(ge.request=T),ge;if(ge&&ge.name==="TypeError"&&/Load failed|fetch/i.test(ge.message)){const $e=new we("Network Error",we.ERR_NETWORK,D,T,ge&&ge.response);throw Object.defineProperty($e,"cause",{__proto__:null,value:ge.cause||ge,writable:!0,enumerable:!1,configurable:!0}),$e}throw we.from(ge,ge&&ge.code,D,T,ge&&ge.response)}}},FE=new Map,sy=s=>{let e=s&&s.env||{};const{fetch:n,Request:r,Response:i}=e,l=[r,i,n];let c=l.length,d=c,f,h,m=FE;for(;d--;)f=l[d],h=m.get(f),h===void 0&&m.set(f,h=d?new Map:GE(e)),m=h;return h};sy();const wf={http:rE,xhr:RE,fetch:{get:sy}};G.forEach(wf,(s,e)=>{if(s){try{Object.defineProperty(s,"name",{__proto__:null,value:e})}catch{}Object.defineProperty(s,"adapterName",{__proto__:null,value:e})}});const oy=s=>`- ${s}`,VE=s=>G.isFunction(s)||s===null||s===!1;function KE(s,e){s=G.isArray(s)?s:[s];const{length:n}=s;let r,i;const l={};for(let c=0;c`adapter ${f} `+(h===!1?"is not supported by the environment":"is not available in the build"));let d=n?c.length>1?`since : +`+c.map(oy).join(` +`):" "+oy(c[0]):"as no adapter specified";throw new we("There is no suitable adapter to dispatch the request "+d,we.ERR_NOT_SUPPORT)}return i}const ly={getAdapter:KE,adapters:wf};function Cf(s){if(s.cancelToken&&s.cancelToken.throwIfRequested(),s.signal&&s.signal.aborted)throw new Lo(null,s)}function Ef(s){const e=G.toSafeFlatObject(s);return Cf(e),e.headers=xn.from(G.getSafeProp(e,"headers")),e.data=bf.call(e,e.transformRequest),["post","put","patch"].indexOf(e.method)!==-1&&e.headers.setContentType("application/x-www-form-urlencoded",!1),ly.getAdapter(e.adapter||Oo.adapter,e)(e).then(function(i){Cf(e),e.response=i;try{i.data=bf.call(e,e.transformResponse,i)}finally{delete e.response}return i.headers=xn.from(i.headers),i},function(i){if(!Gm(i)&&(Cf(e),i&&i.response)){e.response=i.response;try{i.response.data=bf.call(e,e.transformResponse,i.response)}finally{delete e.response}i.response.headers=xn.from(i.response.headers)}return Promise.reject(i)})}const bc={};["object","boolean","number","function","string","symbol"].forEach((s,e)=>{bc[s]=function(r){return typeof r===s||"a"+(e<1?"n ":" ")+s}});const cy={};bc.transitional=function(e,n,r){function i(l,c){return"[Axios v"+Sf+"] Transitional option '"+l+"'"+c+(r?". "+r:"")}return(l,c,d)=>{if(e===!1)throw new we(i(c," has been removed"+(n?" in "+n:"")),we.ERR_DEPRECATED);return n&&!cy[c]&&(cy[c]=!0,console.warn(i(c," has been deprecated since v"+n+" and will be removed in the near future"))),e?e(l,c,d):!0}},bc.spelling=function(e){return(n,r)=>(console.warn(`${r} is likely a misspelling of ${e}`),!0)};function WE(s,e,n){if(typeof s!="object"||s===null)throw new we("options must be an object",we.ERR_BAD_OPTION_VALUE);const r=Object.keys(s);let i=r.length;for(;i-- >0;){const l=r[i],c=Object.prototype.hasOwnProperty.call(e,l)?e[l]:void 0;if(c){const d=s[l],f=d===void 0||c(d,l,s);if(f!==!0)throw new we("option "+l+" must be "+f,we.ERR_BAD_OPTION_VALUE);continue}if(n!==!0)throw new we("Unknown option "+l,we.ERR_BAD_OPTION)}}const vc={assertOptions:WE,validators:bc},Tn=vc.validators;let Ti=class{constructor(e){this.defaults=e||{},this.interceptors={request:new zm,response:new zm}}async request(e,n){try{return await this._request(e,n)}catch(r){if(r instanceof Error)try{let i={};Error.captureStackTrace?Error.captureStackTrace(i):i=new Error;const l=i.stack;let c="";if(typeof l=="string"){const d=l.indexOf(` `);c=d===-1?"":l.slice(d+1)}if(!r.stack)r.stack=c;else if(c){const d=c.indexOf(` `),f=d===-1?-1:c.indexOf(` `,d+1),h=f===-1?"":c.slice(f+1);String(r.stack).endsWith(h)||(r.stack+=` -`+c)}}catch{}throw r}}_request(e,n){typeof e=="string"?(n=n||{},n.url=e):n=e||{},n=xi(this.defaults,n);const{transitional:r,paramsSerializer:i,headers:l}=n;r!==void 0&&vc.assertOptions(r,{silentJSONParsing:Tn.transitional(Tn.boolean),forcedJSONParsing:Tn.transitional(Tn.boolean),clarifyTimeoutError:Tn.transitional(Tn.boolean),legacyInterceptorReqResOrdering:Tn.transitional(Tn.boolean),advertiseZstdAcceptEncoding:Tn.transitional(Tn.boolean),validateStatusUndefinedResolves:Tn.transitional(Tn.boolean)},!1),i!=null&&(G.isFunction(i)?n.paramsSerializer={serialize:i}:vc.assertOptions(i,{encode:Tn.function,serialize:Tn.function},!0)),n.allowAbsoluteUrls!==void 0||(this.defaults.allowAbsoluteUrls!==void 0?n.allowAbsoluteUrls=this.defaults.allowAbsoluteUrls:n.allowAbsoluteUrls=!0),vc.assertOptions(n,{baseUrl:Tn.spelling("baseURL"),withXsrfToken:Tn.spelling("withXSRFToken")},!0),n.method=(G.getSafeProp(n,"method")||G.getSafeProp(this.defaults,"method")||"get").toLowerCase();let c=l&&G.merge(l.common,l[n.method]);l&&G.forEach(Fm.concat("common"),_=>{delete l[_]}),n.headers=xn.concat(c,l);const d=[];let f=!0;this.interceptors.request.forEach(function(x){if(typeof x.runWhen=="function"&&x.runWhen(n)===!1)return;f=f&&x.synchronous;const A=n.transitional||bf;A&&A.legacyInterceptorReqResOrdering?d.unshift(x.fulfilled,x.rejected):d.push(x.fulfilled,x.rejected)});const h=[];this.interceptors.response.forEach(function(x){h.push(x.fulfilled,x.rejected)});let m,y=0,v;if(!f){const _=[xf.bind(this),void 0];for(_.unshift(...d),_.push(...h),v=_.length,m=Promise.resolve(n);yxf.call(this,S)))}catch(D){m=Promise.reject(D)}break}}if(!m)try{m=xf.call(this,S)}catch(_){m=Promise.reject(_)}for(y=0,v=h.length;y{if(!r._listeners)return;let l=r._listeners.length;for(;l-- >0;)r._listeners[l](i);r._listeners=null}),this.promise.then=i=>{let l;const c=new Promise(d=>{r.subscribe(d),l=d}).then(i);return c.cancel=function(){r.unsubscribe(l)},c},e(function(l,c,d){r.reason||(r.reason=new Lo(l,c,d),n(r.reason))})}throwIfRequested(){if(this.reason)throw this.reason}subscribe(e){if(this.reason){e(this.reason);return}this._listeners?this._listeners.push(e):this._listeners=[e]}unsubscribe(e){if(!this._listeners)return;const n=this._listeners.indexOf(e);n!==-1&&this._listeners.splice(n,1)}toAbortSignal(){const e=new AbortController,n=r=>{e.abort(r)};return this.subscribe(n),e.signal.unsubscribe=()=>this.unsubscribe(n),e.signal}static source(){let e;return{token:new r0(function(i){e=i}),cancel:e}}};function XE(s){return function(n){return s.apply(null,n)}}function JE(s){return G.isObject(s)&&s.isAxiosError===!0}const Sc={Continue:100,SwitchingProtocols:101,Processing:102,EarlyHints:103,Ok:200,Created:201,Accepted:202,NonAuthoritativeInformation:203,NoContent:204,ResetContent:205,PartialContent:206,MultiStatus:207,AlreadyReported:208,ImUsed:226,MultipleChoices:300,MovedPermanently:301,Found:302,SeeOther:303,NotModified:304,UseProxy:305,Unused:306,TemporaryRedirect:307,PermanentRedirect:308,BadRequest:400,Unauthorized:401,PaymentRequired:402,Forbidden:403,NotFound:404,MethodNotAllowed:405,NotAcceptable:406,ProxyAuthenticationRequired:407,RequestTimeout:408,Conflict:409,Gone:410,LengthRequired:411,PreconditionFailed:412,PayloadTooLarge:413,ContentTooLarge:413,UriTooLong:414,UnsupportedMediaType:415,RangeNotSatisfiable:416,ExpectationFailed:417,ImATeapot:418,MisdirectedRequest:421,UnprocessableEntity:422,UnprocessableContent:422,Locked:423,FailedDependency:424,TooEarly:425,UpgradeRequired:426,PreconditionRequired:428,TooManyRequests:429,RequestHeaderFieldsTooLarge:431,UnavailableForLegalReasons:451,InternalServerError:500,NotImplemented:501,BadGateway:502,ServiceUnavailable:503,GatewayTimeout:504,HttpVersionNotSupported:505,VariantAlsoNegotiates:506,InsufficientStorage:507,LoopDetected:508,NotExtended:510,NetworkAuthenticationRequired:511,WebServerReturnsAnUnknownError:520,WebServerIsDown:521,ConnectionTimedOut:522,OriginIsUnreachable:523,TimeoutOccurred:524,SslHandshakeFailed:525,InvalidSslCertificate:526};Object.entries(Sc).forEach(([s,e])=>{Sc[e]===void 0&&(Sc[e]=s)});function fy(s){const e=new Ti(s),n=bm(Ti.prototype.request,e);return G.extend(n,Ti.prototype,e,{allOwnKeys:!0}),G.extend(n,e,null,{allOwnKeys:!0}),n.create=function(i){return fy(xi(s,i))},n}const Qt=fy(Oo);Qt.Axios=Ti,Qt.CanceledError=Lo,Qt.CancelToken=YE,Qt.isCancel=Vm,Qt.VERSION=Ef,Qt.toFormData=gc,Qt.AxiosError=we,Qt.Cancel=Qt.CanceledError,Qt.all=function(e){return Promise.all(e)},Qt.spread=XE,Qt.isAxiosError=JE,Qt.mergeConfig=xi,Qt.AxiosHeaders=xn,Qt.formToJSON=s=>Gm(G.isHTMLForm(s)?new FormData(s):s),Qt.getAdapter=uy.getAdapter,Qt.HttpStatusCode=Sc,Qt.default=Qt;const{Axios:DR,AxiosError:OR,CanceledError:LR,isCancel:$R,CancelToken:MR,VERSION:NR,all:IR,Cancel:jR,isAxiosError:HR,spread:zR,toFormData:PR,AxiosHeaders:UR,HttpStatusCode:BR,formToJSON:qR,getAdapter:GR,mergeConfig:FR,create:VR}=Qt,Tf="g7.identity.redirectStash",wc=pt("IdentityGuardInterceptor");class At{static setLauncher(e){this.launcher=e}static hasLauncher(){return this.launcher!==null}static markDomainNoticeShown(){this.domainNoticeShown=!0}static consumeDomainNoticeShown(){const e=this.domainNoticeShown;return this.domainNoticeShown=!1,e}static resolveDeferred(e){const n=this.deferredResolver;if(!n){wc.warn("IdentityGuardInterceptor.resolveDeferred 호출됐지만 대기 중인 launcher 가 없습니다. 모달이 launcher 외부에서 열렸는지 확인하세요.");return}this.deferredResolver=null,n(e)}static createDeferred(){if(this.deferredResolver){const e=this.deferredResolver;this.deferredResolver=null,e({status:"cancelled"})}return new Promise(e=>{this.deferredResolver=e})}static isIdentityRequired(e,n){return e===428&&typeof n=="object"&&n!==null&&n.error_code==="identity_verification_required"}static async handle(e,n,r){this.domainNoticeShown=!1;const i=this.launcher??ZE,c=!!(r&&(r.email||r.phone))?{...e.verification,target:r}:e.verification,d=await i(c);if(d.status!=="verified")return null;const f=c.return_request;if(!f)return null;const h=QE(f.url,d.token);return fetch(h,{method:f.method,headers:n?.headers??{Accept:"application/json","Content-Type":"application/json"},body:n?.body,credentials:n?.credentials??"same-origin"})}static redirectExternally(e){const n=e.redirect_url;if(!n)return wc.error("redirectExternally 호출 시 verification.redirect_url 이 없습니다. payload:",e),Promise.resolve({status:"failed",failureCode:"MISSING_REDIRECT_URL",reason:"verification.redirect_url is required for external_redirect flow"});if(typeof window<"u"){const r={return_url:window.location.href,payload:e,stashed_at:Date.now()};try{window.sessionStorage?.setItem(Tf,JSON.stringify(r))}catch(i){wc.warn("sessionStorage 접근 실패 — stash 없이 redirect 합니다.",i)}window.location.href=n}return new Promise(()=>{})}static reset(){if(this.deferredResolver){const e=this.deferredResolver;this.deferredResolver=null;try{e({status:"cancelled"})}catch{}}this.launcher=null,this.domainNoticeShown=!1}}M(At,"launcher",null),M(At,"deferredResolver",null),M(At,"domainNoticeShown",!1);function QE(s,e){if(!e)return s;try{const n=typeof window<"u"?window.location.origin:"http://localhost",r=new URL(s,n);return r.searchParams.set("verification_token",e),/^https?:\/\//i.test(s)?r.toString():`${r.pathname}${r.search}${r.hash}`}catch{const n=s.includes("?")?"&":"?";return`${s}${n}verification_token=${encodeURIComponent(e)}`}}const ZE=async s=>{if(typeof window>"u")return{status:"cancelled"};if(s.render_hint==="external_redirect"||s.redirect_url)return At.redirectExternally(s);const e=window.G7Core;if(!e?.dispatch)return console.error("[IdentityGuardInterceptor] defaultLauncher: G7Core 가 초기화되지 않아 본인인증 흐름을 시작할 수 없습니다."),{status:"failed",failureCode:"G7_NOT_READY"};try{await e.dispatch({handler:"toast",params:{message:"본인 확인이 필요합니다.",variant:"warning"}})}catch{}const n=window.location.href,r={return_url:n,payload:s,stashed_at:Date.now()};try{window.sessionStorage?.setItem(Tf,JSON.stringify(r))}catch{}const i=`/identity/challenge?return=${encodeURIComponent(n)}`;try{await e.dispatch({handler:"navigate",params:{path:i}})}catch(l){wc.warn("navigate 실패 — window.location 으로 폴백",l),window.location.href=i}return new Promise(()=>{})},e_=pt("ApiClient");function kf(){return typeof window<"u"&&window.__G7_DEVTOOLS__?window.__G7_DEVTOOLS__:null}class t_{constructor(e={}){M(this,"client");M(this,"config");M(this,"TOKEN_KEY","auth_token");this.config={baseURL:e.baseURL||"/api",timeout:e.timeout||3e4,onTokenExpired:e.onTokenExpired,onUnauthorized:e.onUnauthorized,onError:e.onError},this.client=Qt.create({baseURL:this.config.baseURL,timeout:this.config.timeout,headers:{"Content-Type":"application/json",Accept:"application/json"},paramsSerializer:{serialize:n=>{const r=[];for(const[i,l]of Object.entries(n))if(l!=null)if(Array.isArray(l)){const c=i.endsWith("[]")?i:`${i}[]`;for(const d of l)r.push(`${encodeURIComponent(c)}=${encodeURIComponent(String(d))}`)}else r.push(`${encodeURIComponent(i)}=${encodeURIComponent(String(l))}`);return r.join("&")}}}),this.setupInterceptors()}setToken(e){typeof window<"u"&&localStorage.setItem(this.TOKEN_KEY,e)}getToken(){return typeof window<"u"?localStorage.getItem(this.TOKEN_KEY):null}removeToken(){typeof window<"u"&&localStorage.removeItem(this.TOKEN_KEY)}setupInterceptors(){this.client.interceptors.request.use(e=>{e.url?.startsWith("/api")&&e.baseURL==="/api"&&(e.baseURL="");const n=this.getToken();if(n&&e.headers&&!this.isCrossOriginRequest(e.url)&&(e.headers.Authorization=`Bearer ${n}`),typeof window<"u"&&e.headers){const i=localStorage.getItem("g7_locale");i&&(e.headers["Accept-Language"]=i)}const r=kf();if(r?.isEnabled()){const i=this.buildFullUrl(e),l=(e.method||"GET").toUpperCase(),c=r.trackRequest(i,l,{requestBody:e.data});e._devToolsRequestId=c}return e},e=>Promise.reject(e)),this.client.interceptors.response.use(e=>{const n=e.config,r=n._devToolsRequestId;if(r){const i=kf();i?.isEnabled()&&i.completeRequest(r,e.status,e.data),n._devToolsRequestId=null}return e},async e=>{const n=e.config,r=n?.url||"",l=["/auth/","/layouts/"].some(c=>r.includes(c));if(e.response?.status===401&&!n?._retry&&!l){if(n._retry=!0,await Ar.getInstance().refreshToken()){const f=this.getToken();return f&&(n.headers.Authorization=`Bearer ${f}`),n._devToolsRequestId=null,this.client(n)}return this.completeDevToolsRequest(n,e),this.removeToken(),this.config.onUnauthorized&&this.config.onUnauthorized(),Promise.reject(e)}if(e.response?.status===401&&l)return this.completeDevToolsRequest(n,e),Promise.reject(e);if(At.isIdentityRequired(e.response?.status,e.response?.data)){this.completeDevToolsRequest(n,e);const c={},d=n?.headers??{};for(const[v,S]of Object.entries(d))typeof S=="string"&&(c[v]=S);let f;n?.data!==void 0&&n?.data!==null&&(f=typeof n.data=="string"?n.data:JSON.stringify(n.data),c["Content-Type"]=c["Content-Type"]??"application/json");const h=n?.identity_target,m=await At.handle(e.response.data,{headers:c,body:f,credentials:"same-origin"},h);if(!m)return Promise.reject(e);let y=null;try{y=await m.json()}catch{y=null}return m.ok?{...e.response,status:m.status,data:y}:Promise.reject(Object.assign(new Error("replay failed"),{response:{status:m.status,data:y}}))}return e.response?.status===403?(this.completeDevToolsRequest(n,e),this.callOnError(e),Promise.reject(e)):(this.completeDevToolsRequest(n,e),e.response&&this.callOnError(e),Promise.reject(e))})}async get(e,n){return(await this.client.get(e,n)).data}async post(e,n,r){return(await this.client.post(e,n,r)).data}async put(e,n,r){return(await this.client.put(e,n,r)).data}async patch(e,n,r){return(await this.client.patch(e,n,r)).data}async delete(e,n){return(await this.client.delete(e,n)).data}getInstance(){return this.client}setOnUnauthorized(e){this.config.onUnauthorized=e}setOnError(e){this.config.onError=e}completeDevToolsRequest(e,n){if(!e?._devToolsRequestId)return;const r=kf();if(!r?.isEnabled())return;const i=e._devToolsRequestId;e._devToolsRequestId=null,n.response?r.completeRequest(i,n.response.status,n.response.data):n.request?r.failRequest(i,n.message||"Network error"):r.failRequest(i,n.message||"Request error")}isCrossOriginRequest(e){if(!e||typeof window>"u"||!window.location||!/^(https?:)?\/\//i.test(e))return!1;try{return new URL(e,window.location.href).origin!==window.location.origin}catch{return!1}}buildFullUrl(e){let n=e.url||"";if(e.baseURL&&!n.startsWith("http")&&(n=`${e.baseURL}${n.startsWith("/")?"":"/"}${n}`),e.params&&typeof e.params=="object"){const r=e.params,i=[];for(const[l,c]of Object.entries(r))if(c!=null)if(Array.isArray(c)){const d=l.endsWith("[]")?l:`${l}[]`;for(const f of c)i.push(`${encodeURIComponent(d)}=${encodeURIComponent(String(f))}`)}else i.push(`${encodeURIComponent(l)}=${encodeURIComponent(String(c))}`);i.length>0&&(n+=(n.includes("?")?"&":"?")+i.join("&"))}return n}callOnError(e){if(!this.config.onError)return;const n={status:e.response?.status||0,message:e.response?.data?.message||e.message||"Unknown error",data:e.response?.data,statusText:e.response?.statusText};try{this.config.onError(n)}catch(r){e_.error("Error in onError handler:",r)}}}let Rf=null;function qr(){return Rf||(Rf=new t_),Rf}const $o=pt("AuthManager");function ys(s,e,n,r){try{window.G7Core?.devTools?.trackAuthEvent?.(s,e,n,r)}catch{}}const Cc={admin:{type:"admin",loginPath:"/admin/login",defaultPath:"/admin",userEndpoint:"/admin/auth/user",loginEndpoint:"/auth/admin/login",logoutEndpoint:"/admin/auth/logout",refreshEndpoint:"/admin/auth/refresh"},user:{type:"user",loginPath:"/login",defaultPath:"/",userEndpoint:"/auth/user",loginEndpoint:"/auth/login",logoutEndpoint:"/auth/logout",refreshEndpoint:"/auth/refresh"}},Or=class Or{constructor(){M(this,"state");M(this,"config");M(this,"isRefreshing",!1);M(this,"refreshPromise",null);M(this,"eventHandlers",new Map);this.state={isAuthenticated:!1,user:null,type:null},this.config=new Map,this.config.set("admin",Cc.admin),this.config.set("user",Cc.user)}static getInstance(){return Or.instance||(Or.instance=new Or),Or.instance}on(e,n){this.eventHandlers.has(e)||this.eventHandlers.set(e,[]),this.eventHandlers.get(e).push(n)}emit(e,...n){const r=this.eventHandlers.get(e);r&&r.forEach(i=>i(...n))}isAuthenticated(){return this.state.isAuthenticated}getUser(){return this.state.user}getAuthType(){return this.state.type}async checkAuth(e){const n=qr();if(!n.getToken())return this.clearState(),!1;const i=this.config.get(e);if(!i)return $o.error(`Unknown auth type: ${e}`),!1;try{const l=await n.get(i.userEndpoint);return l.success&&l.data?(this.state={isAuthenticated:!0,user:l.data,type:e},this.emit("authStateChange",this.state),!0):(this.clearState(),!1)}catch(l){return l.response?.status===401&&await this.refreshToken()?this.checkAuth(e):(this.clearState(),!1)}}async preloadAuth(e){try{return await this.checkAuth(e)}catch(n){return $o.warn(`Preload auth failed for type ${e}:`,n),!1}}async login(e,n,r){const i=qr();if(!this.config.get(e))throw new Error(`Unknown auth type: ${e}`);const c=e==="admin"?Cc.admin.loginEndpoint:Cc.user.loginEndpoint;try{const d=r?.headers?{headers:r.headers}:void 0,f=await i.post(c,n,d);if(f.success&&f.data){i.setToken(f.data.token);const h=f.data.user.language,m=localStorage.getItem(Or.LOCALE_STORAGE_KEY),y=h&&h!==m;if(h)try{localStorage.setItem(Or.LOCALE_STORAGE_KEY,h)}catch(v){$o.warn("Failed to save user language to localStorage:",v)}return this.state={isAuthenticated:!0,user:f.data.user,type:e},this.emit("login",this.state),this.emit("authStateChange",this.state),ys("login",!0,void 0,{userId:f.data.user.uuid,email:f.data.user.email,type:e}),y&&window.__templateApp&&window.__templateApp.changeLocale(h),f.data.user}throw new Error("Login failed")}catch(d){this.clearState();const f=d.response?.data?.message,h=new Error(f||d.message||"Login failed");throw h.response=d.response,h.status=d.response?.status,ys("login",!1,h.message,{type:e,status:d.response?.status}),h}}async logout(){const e=qr(),n=this.state.type?this.config.get(this.state.type):null;try{n&&await e.post(n.logoutEndpoint)}catch(r){$o.warn("Logout API call failed:",r)}finally{e.removeToken();const r={...this.state};if(this.clearState(),this.emit("logout",r),this.emit("authStateChange",this.state),ys("logout",!0,void 0,{previousType:r.type}),n&&r.type){const i=window.location.pathname+window.location.search;window.location.href=this.getLoginRedirectUrl(r.type,i)}}}async refreshToken(){if(this.isRefreshing&&this.refreshPromise)return this.refreshPromise;this.isRefreshing=!0,this.refreshPromise=this.doRefreshToken();try{return await this.refreshPromise}finally{this.isRefreshing=!1,this.refreshPromise=null}}async doRefreshToken(){const e=this.state.type;if(!e)return!1;const n=this.config.get(e);if(!n)return!1;const r=qr();try{const i=await r.post(n.refreshEndpoint);return i.success&&i.data?.token?(r.setToken(i.data.token),this.emit("tokenRefreshed"),ys("token-refresh",!0,void 0,{type:e}),!0):(ys("token-refresh",!1,"No token in response",{type:e}),!1)}catch(i){return $o.error("Token refresh failed:",i),ys("token-refresh",!1,i instanceof Error?i.message:"Unknown error",{type:e}),!1}}getLoginRedirectUrl(e,n,r){const i=this.config.get(e);if(!i)return"/login";const l=encodeURIComponent(n);let c=`${i.loginPath}?redirect=${l}`;return r&&(c+=`&reason=${encodeURIComponent(r)}`),c}updateConfig(e,n){if(n.loginPath!==void 0){const i=n.loginPath;if(!i.startsWith("/")||i.startsWith("//"))throw new Error(`AuthManager.updateConfig: loginPath must be a same-origin path starting with '/' (got: ${i})`)}const r=this.config.get(e);r&&this.config.set(e,{...r,...n})}getRedirectUrl(e){const r=this.config.get(e)?.defaultPath||"/",l=new URLSearchParams(window.location.search).get("redirect");if(l)try{const c=decodeURIComponent(l);if(c.startsWith("/"))return c}catch{}return r}getConfig(e){return this.config.get(e)}clearState(){this.state={isAuthenticated:!1,user:null,type:null}}static resetInstance(){Or.instance=null}};M(Or,"instance"),M(Or,"LOCALE_STORAGE_KEY","g7_locale");let Ar=Or;const hy={401:{handler:"navigate",params:{path:"{{auth.loginPath}}"}},403:{handler:"toast",params:{type:"error",message:"{{error.message}}"}},404:{handler:"toast",params:{type:"error",message:"{{error.message}}"}},422:{handler:"toast",params:{type:"error",message:"{{error.message}}"}},default:{handler:"toast",params:{type:"error",message:"{{error.message}}"}}},sa={findHandler(s,e){if(s){if(s[e])return s[e];if(s[String(e)])return s[String(e)];if(s.default)return s.default}},normalizeOnError(s){return s?Array.isArray(s)?s:[s]:[]},injectErrorCode(s,e,n){if(s.handler!=="showErrorPage"||s.params?.errorCode!==void 0)return s;const r=typeof e=="string"?parseInt(e,10):e,i=isNaN(r)?n:r;return i===void 0?s:{...s,params:{...s.params,errorCode:i}}}},xr=pt("ErrorHandlingResolver"),Ua=class Ua{constructor(){M(this,"templateErrorHandling",null);M(this,"layoutErrorHandling",null);M(this,"executeAction",null)}static getInstance(){return Ua.instance||(Ua.instance=new Ua),Ua.instance}static resetInstance(){Ua.instance=null}setTemplateConfig(e){this.templateErrorHandling=e,xr.log("Template config set:",e)}setLayoutConfig(e){this.layoutErrorHandling=e,xr.log("Layout config set:",e)}setActionExecutor(e){this.executeAction=e}resolve(e,n={}){const{errorHandling:r,onError:i}=n;xr.log("Resolving error:",e,{hasActionErrorHandling:!!r,hasActionOnError:!!i,hasLayoutConfig:!!this.layoutErrorHandling,hasTemplateConfig:!!this.templateErrorHandling});let l=sa.findHandler(r,e);if(l){const c=this.getMatchedKey(r,e);return{handler:sa.injectErrorCode(l,c,e),level:"action",matchedKey:c}}if(i){const c=sa.normalizeOnError(i);if(c.length>0)return{handler:c.length===1?c[0]:{handler:"sequence",actions:c},level:"action",matchedKey:"onError"}}if(l=sa.findHandler(this.layoutErrorHandling||void 0,e),l){const c=this.getMatchedKey(this.layoutErrorHandling,e);return{handler:sa.injectErrorCode(l,c,e),level:"layout",matchedKey:c}}if(l=sa.findHandler(this.templateErrorHandling||void 0,e),l){const c=this.getMatchedKey(this.templateErrorHandling,e);return{handler:sa.injectErrorCode(l,c,e),level:"template",matchedKey:c}}if(l=sa.findHandler(hy,e),l){const c=this.getMatchedKey(hy,e);return{handler:sa.injectErrorCode(l,c,e),level:"system",matchedKey:c}}return{handler:null,level:null,matchedKey:null}}async execute(e,n){if(!this.executeAction){xr.error("Action executor is not set");return}xr.log("Executing handler:",e.handler,{errorContext:n});try{return await this.executeAction(e,{error:n})}catch(r){throw xr.error("Failed to execute handler:",r),r}}async resolveAndExecute(e,n,r={}){const i=this.resolve(e,r);if(xr.log("Resolve result:",{errorCode:e,handler:i.handler?.handler,level:i.level,matchedKey:i.matchedKey}),!i.handler)return xr.warn("No handler found for error:",e),{handled:!1};try{return{handled:!0,result:await this.execute(i.handler,n)}}catch(l){return xr.error("Handler execution failed:",l),{handled:!1}}}getMatchedKey(e,n){return e[n]?n:e[String(n)]?String(n):"default"}clearLayoutConfig(){this.layoutErrorHandling=null,xr.log("Layout config cleared")}clearAllConfig(){this.templateErrorHandling=null,this.layoutErrorHandling=null,xr.log("All config cleared")}getConfigStatus(){return{hasTemplate:this.templateErrorHandling!==null,hasLayout:this.layoutErrorHandling!==null,hasExecutor:this.executeAction!==null}}};M(Ua,"instance",null);let Df=Ua;function Tr(){return Df.getInstance()}function Mo(s,e){const n={...s};for(const r of Object.keys(e))r in n?n[r]!==null&&typeof n[r]=="object"&&!Array.isArray(n[r])&&e[r]!==null&&typeof e[r]=="object"&&!Array.isArray(e[r])&&(n[r]=Mo(n[r],e[r])):n[r]=e[r];return n}var Of={exports:{}},No={};var py;function n_(){if(py)return No;py=1;var s=Symbol.for("react.transitional.element"),e=Symbol.for("react.fragment");function n(r,i,l){var c=null;if(l!==void 0&&(c=""+l),i.key!==void 0&&(c=""+i.key),"key"in i){l={};for(var d in i)d!=="key"&&(l[d]=i[d])}else l=i;return i=l.ref,{$$typeof:s,type:r,key:c,ref:i!==void 0?i:null,props:l}}return No.Fragment=e,No.jsx=n,No.jsxs=n,No}var gy;function r_(){return gy||(gy=1,Of.exports=n_()),Of.exports}var yt=r_();const a_=na({__proto__:null,default:ra(yt)},[yt]),Lf=pt("ParentContextProvider"),my=I.createContext(null);let Ec=null;function $f(){Ec?(Lf.log("[triggerModalParentUpdate] 모달 부모 컨텍스트 업데이트 트리거"),Ec()):Lf.warn("[triggerModalParentUpdate] Provider가 아직 마운트되지 않음")}const yy=({children:s})=>{const[e,n]=I.useState(0),r=I.useRef(!0),i=I.useCallback(()=>{const c=window.__g7LayoutContextStack||[];return c[c.length-1]?.dataContext},[]);I.useEffect(()=>(r.current=!0,Ec=()=>{r.current&&(n(c=>c+1),Lf.log("[ParentContextProvider] 버전 업데이트됨"))},()=>{r.current=!1,Ec=null}),[]);const l={version:e,getParentDataContext:i};return yt.jsx(my.Provider,{value:l,children:s})};function i_(){return I.useContext(my)}function by(s){return s.replace(/[\t\n\r]/g,"").replace(/\\/g,"/").replace(/^([a-z][a-z0-9+.\-]*:)?\/{2,}/i,"$1//")}function vy(s,e){try{const n=typeof window<"u"&&window.location?.protocol||"https:",r=e||(typeof window<"u"?window.location?.origin:void 0)||"https://localhost",i=s.startsWith("//")?`${n}${s}`:s,l=new URL(i,r);return l.protocol!=="http:"&&l.protocol!=="https:"?null:l.hostname.toLowerCase()}catch{return null}}function bs(){const s=typeof window<"u"?window.G7Config?.trustedScriptHosts:void 0;return Array.isArray(s)?s.map(e=>String(e).toLowerCase()):[]}function Io(s,e){if(typeof s!="string")return!1;const n=s.trim();if(n==="")return!1;const r=by(n),i=r.startsWith("//"),l=/^[a-z][a-z0-9+.-]*:/i.test(r);if(!i&&!l&&r.startsWith("/"))return!0;const c=vy(r),d=e??bs();return c!==null&&d.map(f=>String(f).toLowerCase()).includes(c)}const j=pt("ActionDispatcher");function s_(s,e,n,r){return n||(Wg(s)||Ud(s)?"$t:core.errors.network_request_failed":r||`Failed to execute action: ${e}`)}const Sy=new Set(["navigate","navigateBack","navigateForward","replaceUrl","refresh","logout"]),o_=new Set(["redirect"]);function Yn(){try{return window.G7Core?.devTools}catch{return}}class jt extends Error{constructor(n,r,i){super(n);M(this,"action");M(this,"originalError");M(this,"unknownHandler",!1);this.action=r,this.originalError=i,this.name="ActionError"}}const wy={click:"onClick",change:"onChange",input:"onInput",submit:"onSubmit",focus:"onFocus",blur:"onBlur",keydown:"onKeyDown",keyup:"onKeyUp",keypress:"onKeyPress",mousedown:"onMouseDown",mouseup:"onMouseUp",mouseenter:"onMouseEnter",mouseleave:"onMouseLeave",scroll:"onScroll",dragstart:"onDragStart",drag:"onDrag",dragend:"onDragEnd",dragenter:"onDragEnter",dragover:"onDragOver",dragleave:"onDragLeave",drop:"onDrop"},cn=class cn{constructor(e={},n,r){M(this,"bindingEngine");M(this,"translationEngine");M(this,"translationContext");M(this,"customHandlers",new Map);M(this,"defaultContext");M(this,"globalStateUpdater");M(this,"errorHandlingSetup",!1);M(this,"debounceTimers",new Map);M(this,"pendingDebounceFlushers",new Map);M(this,"debounceAccumulatedValues",new Map);M(this,"globalHeaders",[]);M(this,"namedActions",{});M(this,"previewMode",!1);M(this,"localMirrorDepth",0);M(this,"intervals",new Map);this.bindingEngine=new $n,this.translationEngine=n,this.translationContext=r,this.defaultContext=e,this.registerDefaultHandlers(),this.setupErrorHandling()}setGlobalHeaders(e){this.globalHeaders=e||[]}setNamedActions(e){this.namedActions=e||{};const n=Yn();n?.isEnabled?.()&&n.setNamedActionDefinitions?.(this.namedActions),j.log("[setNamedActions] registered:",Object.keys(this.namedActions))}getNamedActions(){return this.namedActions}setPreviewMode(e){this.previewMode=e,j.log("Preview mode:",e?"enabled":"disabled")}isPreviewMode(){return this.previewMode}static getPreviewSuppressedHandlers(){return Sy}static getPreviewSuppressedLayoutFeatures(){return o_}resolveActionRef(e){if(!e.actionRef)return e;const n=this.namedActions[e.actionRef];if(!n)return j.warn(`[resolveActionRef] named action not found: "${e.actionRef}"`),e;const r=Yn();r?.isEnabled?.()&&r.trackNamedActionRef?.({actionRefName:e.actionRef,resolvedHandler:n.handler,timestamp:Date.now()});const{actionRef:i,type:l,key:c,event:d,...f}=e,h={...n,...Object.fromEntries(Object.entries(f).filter(([,m])=>m!==void 0)),type:l??n.type};return c!==void 0&&(h.key=c),d!==void 0&&(h.event=d),h}matchesPattern(e,n){if(n==="*")return!0;const r=n.replace(/[.+?^${}()|[\]\\]/g,"\\$&").replace(/\*/g,".*");return new RegExp(`^${r}$`).test(e)}getMatchingGlobalHeaders(e,n){const r={};for(const i of this.globalHeaders)this.matchesPattern(e,i.pattern)&&Object.entries(i.headers).forEach(([l,c])=>{const d=hs(c,n,{skipCache:!0});d!=null&&d!==""&&(r[l]=String(d))});return r}setupErrorHandling(){if(this.errorHandlingSetup)return;Tr().setActionExecutor(async(n,r)=>{const i={type:"click",handler:n.handler,target:n.target,params:n.params,actions:n.actions},l={data:{...this.defaultContext.data,error:r.error}};return await this.dispatchAction(i,l)}),this.errorHandlingSetup=!0}registerDefaultHandlers(){this.registerHandler("refetchDataSource",async(n,r)=>{const i=n.params?.dataSourceId,l=n.params?.sync;if(!i){j.warn("refetchDataSource: dataSourceId is required");return}if(typeof window<"u"&&window.G7Core?.dataSource?.refetch){const c=window.G7Core?.state?.get?.()||{},d={...r.state?._global,...c},f=window.G7Core?.state?.getLocal?.()||{},h=r.data?._local||r.state||{},m={...f,...h},y=r.isolatedContext?.state;j.log("[refetchDataSource] localStateOverride:",m),await window.G7Core.dataSource.refetch(i,{...l?{sync:!0}:{},...d?{globalStateOverride:d}:{},...m?{localStateOverride:m}:{},...y?{isolatedStateOverride:y}:{}})}else j.warn("refetchDataSource: G7Core.dataSource.refetch is not available")}),this.registerHandler("appendDataSource",async(n,r)=>{const{dataSourceId:i,dataPath:l,newData:c}=n.params||{};if(!i){j.warn("appendDataSource: dataSourceId is required");return}if(c===void 0){j.warn("appendDataSource: newData is required");return}if(typeof window<"u"&&window.G7Core?.dataSource?.updateData){if(!Array.isArray(c)){j.warn("appendDataSource: newData must be an array, got:",typeof c,c);return}await window.G7Core.dataSource.updateData(i,l||null,c,"append")}else j.warn("appendDataSource: G7Core.dataSource.updateData is not available")}),this.registerHandler("updateDataSource",async(n,r)=>{const{dataSourceId:i,data:l,merge:c=!1}=n.params||{};if(!i){j.warn("updateDataSource: dataSourceId is required");return}if(l===void 0){j.warn("updateDataSource: data is required");return}typeof window<"u"&&window.G7Core?.dataSource?.set?(j.log(`[updateDataSource] Updating dataSource '${i}' with:`,l),window.G7Core.dataSource.set(i,l,{merge:c})):j.warn("updateDataSource: G7Core.dataSource.set is not available")}),this.registerHandler("scrollIntoView",async(n,r)=>{const{selector:i,behavior:l="smooth",block:c="nearest",inline:d="nearest",waitForElement:f=!1,timeout:h=2e3,delay:m=0,retryCount:y=0,retryInterval:v=50,scrollContainer:S}=n.params||{};if(!i){j.warn("scrollIntoView: selector is required");return}if(typeof window>"u"||typeof document>"u"){j.warn("scrollIntoView: window/document is not available");return}let _=null;if(f){if(_=await new Promise(x=>{const A=document.querySelector(i);if(A){x(A);return}const D=setTimeout(()=>{O.disconnect(),x(null)},h),O=new MutationObserver(()=>{const k=document.querySelector(i);k&&(clearTimeout(D),O.disconnect(),x(k))});O.observe(document.body,{childList:!0,subtree:!0})}),!_){j.warn(`scrollIntoView: element not found for selector "${i}" after ${h}ms timeout`);return}}else{m>0&&await new Promise(A=>setTimeout(A,m)),_=document.querySelector(i);let x=0;for(;!_&&xsetTimeout(A,v)),_=document.querySelector(i),x++;if(!_){j.warn(`scrollIntoView: element not found for selector "${i}" after ${x+1} attempts`);return}}if(S){const x=document.querySelector(S);if(!x){j.warn(`scrollIntoView: container not found for selector "${S}"`);return}const A=_.getBoundingClientRect(),D=x.getBoundingClientRect(),O=A.top-D.top+x.scrollTop,k=O+A.height;let L;switch(c){case"start":L=O;break;case"center":L=O-x.clientHeight/2+A.height/2;break;case"end":L=k-x.clientHeight;break;default:const B=A.topD.bottom;if(B)L=O;else if(q)L=k-x.clientHeight;else return;break}x.scrollTo({top:Math.max(0,L),behavior:l});return}_.scrollIntoView({behavior:l,block:c,inline:d})}),this.registerHandler("reloadExtensions",async(n,r)=>{if(typeof window>"u"){j.warn("reloadExtensions: window is not available");return}const i=window.__templateApp;if(!i){j.warn("reloadExtensions: TemplateApp not initialized");return}if(typeof i.reloadExtensionState=="function")try{await i.reloadExtensionState()}catch(d){throw j.error("reloadExtensions: reloadExtensionState failed",d),d}else j.warn("reloadExtensions: TemplateApp.reloadExtensionState unavailable");const{moduleInfo:l,pluginInfo:c}=n.params||{};if(l)try{await this.executeAction({handler:"reloadModuleHandlers",params:n.params},r)}catch(d){j.error("reloadExtensions: reloadModuleHandlers failed",d)}if(c)try{await this.executeAction({handler:"reloadPluginHandlers",params:n.params},r)}catch(d){j.error("reloadExtensions: reloadPluginHandlers failed",d)}j.log("reloadExtensions: done")}),this.registerHandler("reloadRoutes",async(n,r)=>{if(typeof window>"u"){j.warn("reloadRoutes: window is not available");return}const i=window.__templateApp;if(!i){j.warn("reloadRoutes: TemplateApp not initialized");return}if(typeof i.reloadExtensionState=="function")try{await i.reloadExtensionState(),j.log("reloadRoutes: delegated to reloadExtensionState")}catch(l){throw j.error("reloadRoutes: Failed to reload routes",l),l}else{const l=i.getRouter?.();l&&(await l.loadRoutes(),j.log("reloadRoutes: Routes reloaded (legacy fallback)"))}}),this.registerHandler("refresh",async(n,r)=>{if(typeof window>"u"){j.warn("refresh: window is not available");return}const i=Number(n.params?.delayMs??0);i>0&&await new Promise(l=>setTimeout(l,i)),window.location.reload()}),this.registerHandler("remount",async(n,r)=>{const{componentId:i}=n.params||{};if(!i){j.warn("remount: componentId parameter is required");return}if(!this.globalStateUpdater){j.warn("remount: globalStateUpdater is not set");return}const d=(window.G7Core?.state?.get()||{})._global?._remountKeys||{},f=d[i]||0;this.globalStateUpdater({_remountKeys:{...d,[i]:f+1}}),j.log(`remount: ${i} key incremented to ${f+1}`)}),this.registerHandler("reloadTranslations",async(n,r)=>{if(typeof window>"u"){j.warn("reloadTranslations: window is not available");return}const i=window.__templateApp;if(!i){j.warn("reloadTranslations: TemplateApp not initialized");return}if(typeof i.reloadExtensionState=="function")try{await i.reloadExtensionState(),j.log("reloadTranslations: delegated to reloadExtensionState")}catch(l){throw j.error("reloadTranslations: Failed to reload translations",l),l}}),this.registerHandler("reloadModuleHandlers",async(n,r)=>{if(typeof window>"u"){j.warn("reloadModuleHandlers: window is not available");return}const{moduleInfo:i,action:l}=n.params||{};if(!i){j.warn("reloadModuleHandlers: moduleInfo is required");return}const c=i.data||i;if(!c.identifier){j.warn("reloadModuleHandlers: moduleInfo with identifier is required");return}if(!l||l!=="add"&&l!=="remove"){j.warn('reloadModuleHandlers: action must be "add" or "remove"');return}const d=window.G7Config;if(!d){j.warn("reloadModuleHandlers: G7Config not available");return}const f=c.identifier;try{if(l==="add"){if(c.assets){if(d.moduleAssets=d.moduleAssets||{},d.moduleAssets[f]=c.assets,j.log(`reloadModuleHandlers: Added assets for ${f}`),c.assets.js){const h=c.assets.js,m=`module-${f}`;this.assertAllowedExtensionAssetUrl(h,`module script (${f})`,n),document.getElementById(m)?j.warn(`reloadModuleHandlers: Script ${m} already loaded`):(await Cr(h,{id:m},{label:`module-script:${f}`}),j.log(`reloadModuleHandlers: Script loaded successfully for ${f}`))}if(c.assets.css){const h=c.assets.css,m=`module-css-${f}`;this.assertAllowedExtensionAssetUrl(h,`module stylesheet (${f})`,n),document.getElementById(m)||(await ia(h,{id:m},{label:`module-css:${f}`}),j.log(`reloadModuleHandlers: CSS loaded for ${f}`))}}}else if(l==="remove"){d.moduleAssets&&d.moduleAssets[f]&&(delete d.moduleAssets[f],j.log(`reloadModuleHandlers: Removed assets for ${f}`));const h=`module-${f}`,m=document.getElementById(h);m&&(m.remove(),j.log(`reloadModuleHandlers: Removed script for ${f}`));const y=`module-css-${f}`,v=document.getElementById(y);v&&(v.remove(),j.log(`reloadModuleHandlers: Removed CSS for ${f}`))}}catch(h){throw j.error(`reloadModuleHandlers: Failed to ${l} module assets`,h),h}}),this.registerHandler("reloadPluginHandlers",async(n,r)=>{if(typeof window>"u"){j.warn("reloadPluginHandlers: window is not available");return}const{pluginInfo:i,action:l}=n.params||{};if(!i){j.warn("reloadPluginHandlers: pluginInfo is required");return}const c=i.data||i;if(!c.identifier){j.warn("reloadPluginHandlers: pluginInfo with identifier is required");return}if(!l||l!=="add"&&l!=="remove"){j.warn('reloadPluginHandlers: action must be "add" or "remove"');return}const d=window.G7Config;if(!d){j.warn("reloadPluginHandlers: G7Config not available");return}const f=c.identifier;try{if(l==="add"){if(c.assets){if(d.pluginAssets=d.pluginAssets||{},d.pluginAssets[f]=c.assets,j.log(`reloadPluginHandlers: Added assets for ${f}`),c.assets.js){const h=c.assets.js,m=`plugin-${f}`;this.assertAllowedExtensionAssetUrl(h,`plugin script (${f})`,n),document.getElementById(m)?j.warn(`reloadPluginHandlers: Script ${m} already loaded`):(await Cr(h,{id:m},{label:`plugin-script:${f}`}),j.log(`reloadPluginHandlers: Script loaded successfully for ${f}`))}if(c.assets.css){const h=c.assets.css,m=`plugin-css-${f}`;this.assertAllowedExtensionAssetUrl(h,`plugin stylesheet (${f})`,n),document.getElementById(m)||(await ia(h,{id:m},{label:`plugin-css:${f}`}),j.log(`reloadPluginHandlers: CSS loaded for ${f}`))}}}else if(l==="remove"){d.pluginAssets&&d.pluginAssets[f]&&(delete d.pluginAssets[f],j.log(`reloadPluginHandlers: Removed assets for ${f}`));const h=`plugin-${f}`,m=document.getElementById(h);m&&(m.remove(),j.log(`reloadPluginHandlers: Removed script for ${f}`));const y=`plugin-css-${f}`,v=document.getElementById(y);v&&(v.remove(),j.log(`reloadPluginHandlers: Removed CSS for ${f}`))}}catch(h){throw j.error(`reloadPluginHandlers: Failed to ${l} plugin assets`,h),h}});let e=!1;this.registerHandler("showErrorPage",async(n,r)=>{if(e){j.log("showErrorPage: Already active, skipping duplicate call");return}if(e=!0,typeof window>"u"){e=!1,j.warn("showErrorPage: window is not available");return}const i=window.__templateApp;if(!i){e=!1,j.warn("showErrorPage: TemplateApp not initialized");return}const l=i.getErrorPageHandler?.();if(!l){e=!1,j.warn("showErrorPage: ErrorPageHandler not available");return}const c=n.params?.errorCode||500,d=n.params?.target||"content";let f=n.params?.containerId;f||(f=d==="full"?"app":"main_content"),j.log("showErrorPage:",{errorCode:c,target:d,containerId:f});const h=(m,y=6e4)=>new Promise((v,S)=>{const _=document.getElementById(m);if(_){v(_);return}j.log(`showErrorPage: Waiting for container #${m}...`);let x;const A=new MutationObserver((D,O)=>{const k=document.getElementById(m);k&&(j.log(`showErrorPage: Container #${m} found`),O.disconnect(),clearTimeout(x),v(k))});A.observe(document.body,{childList:!0,subtree:!0}),x=setTimeout(()=>{A.disconnect(),S(new Error(`Container #${m} not found within ${y}ms`))},y)});try{await h(f);const m=Tr(),y=m.layoutErrorHandling;m.clearLayoutConfig();try{await l.renderError(c,f)||j.warn(`showErrorPage: Failed to render error page for code ${c}`)}finally{m.setLayoutConfig(y)}}catch(m){throw j.error("showErrorPage: Error rendering error page:",m),m}finally{e=!1}}),this.registerHandler("emitEvent",async(n,r)=>{const i=n.params?.event,l=n.params?.data;if(!i){j.warn("emitEvent: event parameter is required");return}if(typeof window>"u"){j.warn("emitEvent: window is not available");return}const c=window.G7Core;if(!c?.componentEvent?.emit){j.warn("emitEvent: G7Core.componentEvent is not available");return}try{j.log(`emitEvent: Emitting "${i}"`,l);const d={...l,_context:{data:r.data,state:r.state}},f=await c.componentEvent.emit(i,d);if(!f||f.length===0?j.warn(`emitEvent: No listeners found for "${i}"`):j.log(`emitEvent: Event "${i}" completed with ${f.length} listener(s)`,f),this.globalStateUpdater){const h=(c?.state?.get()||{})._local||{},m={event:i,success:!0,data:f.length===1?f[0]:f,listeners:f.length};this.globalStateUpdater({_local:{...h,_eventResult:m}});const y=r.state&&typeof r.state=="object"&&!Array.isArray(r.state)?r.state:h;window.__g7SequenceLocalSync={...y,_eventResult:m}}}catch(d){if(j.error(`emitEvent: Event "${i}" failed`,d),this.globalStateUpdater){const f=c?.state?.get()||{};this.globalStateUpdater({_local:{...f._local,_eventResult:{event:i,success:!1,error:d instanceof Error?d.message:String(d)}}})}throw d}}),this.registerHandler("updateProductField",async(n,r)=>{const{productId:i,field:l,value:c}=n.params||{};if(!i||!l){j.warn("updateProductField: productId and field are required");return}if(typeof window>"u"){j.warn("updateProductField: window is not available");return}const d=window.G7Core;if(!d?.state?.get||!this.globalStateUpdater){j.warn("updateProductField: G7Core.state or globalStateUpdater is not available");return}try{const f=d.state.get()||{},m=(f.products?.data?.data||[]).map(v=>v.id===i?{...v,[l]:c,_modified:!0}:v),y=new Set(f._local?.modifiedProductIds||[]);y.add(i),this.globalStateUpdater({products:{...f.products,data:{...f.products?.data,data:m}},_local:{...f._local,modifiedProductIds:Array.from(y)}}),j.log(`updateProductField: Updated product ${i}, field: ${l}, value:`,c)}catch(f){throw j.error("updateProductField: Error updating product field",f),f}}),this.registerHandler("updateOptionField",async(n,r)=>{const{productId:i,optionId:l,field:c,value:d}=n.params||{};if(!i||!l||!c){j.warn("updateOptionField: productId, optionId and field are required");return}if(typeof window>"u"){j.warn("updateOptionField: window is not available");return}const f=window.G7Core;if(!f?.state?.get||!this.globalStateUpdater){j.warn("updateOptionField: G7Core.state or globalStateUpdater is not available");return}try{const h=f.state.get()||{},y=(h.products?.data?.data||[]).map(S=>{if(S.id===i&&S.options){const _=S.options.map(x=>x.id===l?{...x,[c]:d,_modified:!0}:x);return{...S,options:_,_modified:!0}}return S}),v=new Set(h._local?.modifiedProductIds||[]);v.add(i),this.globalStateUpdater({products:{...h.products,data:{...h.products?.data,data:y}},_local:{...h._local,modifiedProductIds:Array.from(v)}}),j.log(`updateOptionField: Updated product ${i} option ${l}, field: ${c}, value:`,d)}catch(h){throw j.error("updateOptionField: Error updating option field",h),h}}),this.registerHandler("setLocale",async(n,r)=>{const i=n.target;if(!i||typeof i!="string"){j.warn("setLocale: Invalid locale:",i);return}const l=window.__templateApp;if(l&&typeof l.changeLocale=="function")try{await l.changeLocale(i),j.log("setLocale: Locale changed to",i)}catch(c){j.error("setLocale: Failed to change locale:",c),window.location.reload()}else{j.warn("setLocale: TemplateApp not found, falling back to page reload");try{localStorage.setItem("g7_locale",i)}catch{}window.location.reload()}}),this.registerHandler("suppress",async()=>{j.log("suppress: Error intentionally suppressed")}),this.registerBuiltInHandlerMetadata()}registerBuiltInHandlerMetadata(){const e=Yn();if(!e?.isEnabled())return;const n=[{name:"refetchDataSource",description:"데이터 소스를 다시 fetch합니다"},{name:"appendDataSource",description:"데이터 소스에 새 데이터를 병합합니다 (무한 스크롤용)"},{name:"updateDataSource",description:"API 응답으로 데이터 소스를 직접 업데이트합니다 (refetch 대체)"},{name:"reloadRoutes",description:"라우트를 다시 로드합니다"},{name:"refresh",description:"현재 페이지를 새로고침합니다"},{name:"remount",description:"컴포넌트를 리마운트합니다"},{name:"reloadTranslations",description:"다국어 파일을 다시 로드합니다"},{name:"showErrorPage",description:"에러 페이지를 표시합니다"},{name:"emitEvent",description:"이벤트를 발생시킵니다"},{name:"updateProductField",description:"상품 필드를 인라인 수정합니다"},{name:"updateOptionField",description:"상품 옵션 필드를 인라인 수정합니다"},{name:"setLocale",description:"언어를 변경합니다 (DB 저장 + UI 리렌더링)"}];for(const r of n)e.trackHandlerRegistration(r.name,"built-in",r.description)}createHandler(e,n,r){j.log("createHandler called for:",e.handler,e.type);const i=window.__g7DevTools,l=Date.now(),c=r?.state?{...r.state}:null,d=`handler_${e.handler}_${l}`;return async f=>{if(j.log("Handler invoked for:",e.handler,"event:",f.type),i?.isEnabled?.()&&c){const m=window.G7Core?.state?.getLocal?.()??r?.state??{},y=Date.now()-l;for(const v of Object.keys(c)){const S=c[v],_=m[v];S!==_&&y>100&&(i.trackStaleClosureWarning?.({type:"event-handler-stale",location:`createHandler(${e.handler})`,capturedPath:`_local.${v}`,capturedValue:S,capturedAt:l,currentValue:_,actionId:d,stackTrace:new Error().stack}),j.warn(`[Stale Closure] _local.${v} changed after handler creation:`,`captured="${S}" → current="${_}" (${y}ms ago)`))}}try{f.type!=="change"&&f.preventDefault();let h={};if(f.type==="submit"&&f.target instanceof HTMLFormElement){const v=f.target;new FormData(v).forEach((_,x)=>{h[x]=_})}const m={...this.defaultContext,data:{...n,form:h,_local:r?.state||{},$event:f},event:f,...r&&{state:r.state,setState:r.setState},isolatedContext:r?.isolatedContext};if(e.confirm){let v=this.resolveValue(e.confirm,m.data);if(this.translationEngine&&this.translationContext&&v.startsWith("$t:")&&(v=this.translationEngine.resolveTranslations(v,this.translationContext,m.data)),!confirm(v))return}const y=window.__g7ActionContext;window.__g7ActionContext={...r,data:m.data};try{await this.executeAction(e,m)}finally{window.__g7ActionContext=y}}catch(h){if(j.error("Action execution failed:",h),e.onError){const m=h instanceof jt&&h.originalError?h.originalError:h,y={...this.defaultContext,data:{...n,error:m,_local:r?.state||{},$event:f},event:f,...r&&{state:r.state,setState:r.setState}},v=Array.isArray(e.onError)?e.onError:[e.onError];for(const S of v)await this.executeAction(S,y)}}}}generateActionId(e,n){const r=e.handler,i=n||"no-target",l=Date.now(),c=Math.random().toString(36).substring(2,9);return`${r}_${i.replace(/[^a-zA-Z0-9]/g,"_")}_${l}_${c}`}async executeAction(e,n){if(e=this.resolveActionRef(e),typeof e.handler=="string"&&e.handler.includes("{{")){const f=this.evaluateExpression(e.handler,n.data);e={...e,handler:f==null?"":String(f)}}const r=Yn(),i=r?.isEnabled()?`action_${Date.now()}_${Math.random().toString(36).substring(2,11)}`:void 0,l=performance.now();let c;if(i&&r&&r.logAction({id:i,type:e.handler,params:this.sanitizeForDevTools(e.params),context:this.sanitizeForDevTools({hasState:!!n.state,hasSetState:!!n.setState,dataKeys:n.data?Object.keys(n.data):[]}),startTime:l,status:"started"}),e.if!==void 0)try{const f=this.resolveValue(e.if,n.data),h=f===!0||f==="true"||f&&f!=="false"&&f!=="0"&&f!==!1;if(j.log("[executeAction] if condition result:",e.handler,h),!h)return i&&r&&r.logAction({id:i,type:e.handler,startTime:l,endTime:performance.now(),duration:performance.now()-l,status:"success",result:{skipped:!0,reason:"if condition false"}}),{success:!0,data:void 0}}catch(f){return j.error("[executeAction] if condition error:",e.handler,f),i&&r&&r.logAction({id:i,type:e.handler,startTime:l,endTime:performance.now(),duration:performance.now()-l,status:"error",error:{name:"ConditionEvaluationError",message:f instanceof Error?f.message:String(f),stack:f instanceof Error?f.stack:void 0}}),{success:!0,data:void 0}}let d;try{const f=this.resolveParams(e.params,n.data);i&&(c=this.sanitizeForDevTools(f));const h=e.target?this.resolveValue(e.target,n.data):void 0;if(e.handler==="apiCall"&&n.setState){d=this.generateActionId(e,h);const y=n.state?.loadingActions||{};n.setState({loadingActions:{...y,[d]:!0}})}if(this.previewMode&&Sy.has(e.handler))return j.warn(`Preview mode: "${e.handler}" suppressed`,h||f),i&&r&&r.logAction({id:i,type:e.handler,startTime:l,endTime:performance.now(),duration:performance.now()-l,status:"skipped",metadata:{reason:"preview_mode_suppressed"}}),{success:!0,data:void 0};let m;switch(e.handler){case"ensureIdentityVerified":m=await this.handleEnsureIdentityVerified(f);break;case"resolveIdentityChallenge":m=this.handleResolveIdentityChallenge(f);break;case"navigate":const y=f.path||h;m=await this.handleNavigate(y,f,n);break;case"navigateBack":m=await this.handleNavigateBack();break;case"navigateForward":m=await this.handleNavigateForward();break;case"openWindow":{const _=f.path||h;m=await this.handleOpenWindow(_,f);break}case"replaceUrl":{const _=f.path||h||window.location.pathname;m=await this.handleReplaceUrl(_,f);break}case"apiCall":if(e.onSuccess&&r?.isEnabled()&&i){const _=window.G7Core,x={};if(_?.state?.get){const A=_.state.get();x._global=A}n.state&&(x._local=n.state),r.registerStateCaptureForHandler?.(i,["_global","_local"],x)}const v=e.auth_mode??(e.auth_required?"required":"none"),S=e.identity_target?this.resolveParams(e.identity_target,n.data):void 0;m=await this.handleApiCall(h,f,n,v,S);break;case"login":m=await this.handleLogin(h,f,n);break;case"logout":m=await this.handleLogout(h,n);break;case"setState":j.log("[executeAction] setState resolvedParams:",f),m=await this.handleSetState(e.render!==void 0?{...f,__render:e.render}:f,n);break;case"setError":m=await this.handleSetError(h,f,n);break;case"openModal":m=await this.handleOpenModal(h,n);break;case"closeModal":m=await this.handleCloseModal(n);break;case"showAlert":m=await this.handleShowAlert(h,n);break;case"toast":m=await this.handleToast(f,n);break;case"switch":m=await this.handleSwitch(e,n);break;case"conditions":m=await this.handleConditions(e,n);break;case"sequence":m=await this.handleSequence(e,n);break;case"parallel":m=await this.handleParallel(e,n);break;case"startInterval":m=this.handleStartInterval(f,n);break;case"stopInterval":m=this.handleStopInterval(f);break;case"loadScript":m=await this.handleLoadScript(f,e,n);break;case"callExternal":m=await this.handleCallExternal(f,e,n);break;case"callExternalEmbed":m=await this.handleCallExternalEmbed(f,e,n);break;case"saveToLocalStorage":m=await this.handleSaveToLocalStorage(f,n);break;case"loadFromLocalStorage":m=await this.handleLoadFromLocalStorage(f,n);break;default:m=await this.handleCustomAction({...e,target:h,params:f},n);break}if(e.resultTo&&m!==void 0){const{target:y,key:v,merge:S}=e.resultTo,_=this.resolveValue(v,n.data),x=S==="replace"?"replace":S==="shallow"?"shallow":"deep";if(y==="_local"&&n.setState){const A=this.buildNestedUpdate(_,m),D=x!=="deep"?{...A,__mergeMode:x}:A;this.writeLocalState(n,D),j.log(`[resultTo] Saved to _local.${_} (merge=${x}):`,m)}else if(y==="_local"&&this.globalStateUpdater){const O=(window.G7Core?.state?.get()||{})._local||{},k=this.buildNestedUpdate(_,m);let L;x==="replace"?L=k:x==="shallow"?L={...O,...k}:L=this.deepMergeWithState(k,O),this.globalStateUpdater({_local:L}),j.log(`[resultTo] Saved to _local.${_} via globalStateUpdater (merge=${x}):`,m)}else if(y==="_global"&&this.globalStateUpdater){const A=this.buildNestedUpdate(_,m);this.globalStateUpdater(A),j.log(`[resultTo] Saved to _global.${_}:`,m)}else if(y==="_isolated"&&n.isolatedContext){const A=this.buildNestedUpdate(_,m);n.isolatedContext.mergeState(A,x),j.log(`[resultTo] Saved to _isolated.${_} (merge=${x}):`,m)}else j.warn(`[resultTo] Cannot save result: target=${y}, setState=${!!n.setState}, globalStateUpdater=${!!this.globalStateUpdater}, isolatedContext=${!!n.isolatedContext}`)}if(e.onSuccess){if(r?.isEnabled()&&i&&e.handler==="apiCall"){const S=window.G7Core,_={};S?.state?.get&&(_._global=S.state.get()),n.state&&(_._local=n.state),r.detectStaleClosure?.(i,`${e.handler} → onSuccess`,_,"callback-state-capture",i)}const y={...n,data:{...n.data,result:m,response:m}},v=Array.isArray(e.onSuccess)?e.onSuccess:[e.onSuccess];v.length>1?await this.handleSequence({handler:"sequence",type:"click",actions:v},y):v.length===1&&await this.executeAction(v[0],y)}return i&&r&&r.logAction({id:i,type:e.handler,params:this.sanitizeForDevTools(e.params),resolvedParams:c,startTime:l,endTime:performance.now(),duration:performance.now()-l,status:"success",result:this.sanitizeForDevTools(m)}),{success:!0,data:m}}catch(f){const h=f instanceof jt?f:new jt(`Failed to execute action: ${e.handler}`,e,f instanceof Error?f:void 0),m=h.originalError?.response||{},y=m.data||{},v=h.originalError?.status||m.status||500;let S=s_(h.originalError??f,e.handler,y.message,h.message);this.translationEngine&&this.translationContext&&S.startsWith("$t:")&&(S=this.translationEngine.resolveTranslations(S,this.translationContext));const _={status:v,message:S,errors:y.errors||m.errors,data:y,statusText:h.originalError?.statusText,error_code:y.error_code??m.error_code};if(h.unknownHandler)throw j.warn(`Unknown action handler "${e.handler}" — skipped (extension not loaded?). Not surfaced to the user.`),h;const A=Tr().resolve(v,{errorHandling:e.errorHandling,onError:e.onError});if(A.handler){if(r?.isEnabled()&&i&&e.handler==="apiCall"){const O=window.G7Core,k={};O?.state?.get&&(k._global=O.state.get()),n.state&&(k._local=n.state),r.detectStaleClosure?.(i,`${e.handler} → onError`,k,"callback-state-capture",i)}const D={...n,data:{...n.data,error:_}};try{const O={type:"click",handler:A.handler.handler,target:A.handler.target,params:A.handler.params,actions:A.handler.actions};return await this.executeAction(O,D),{success:!1,error:h}}catch(O){return j.error("Error executing error handler:",O),{success:!1,error:h}}}throw i&&r&&r.logAction({id:i,type:e.handler,params:this.sanitizeForDevTools(e.params),resolvedParams:c,startTime:l,endTime:performance.now(),duration:performance.now()-l,status:"error",error:{name:h.name,message:h.message,stack:h.stack}}),h}finally{if(e.handler==="apiCall"&&n.setState&&d){const f=n.state?.loadingActions||{},{[d]:h,...m}=f;n.setState({loadingActions:m})}}}async handleNavigate(e,n,r){let i=e;if(n.query||n.mergeQuery===!0)if(n.mergeQuery===!0)i=this.buildMergedQueryPath(e,n.query??{});else{const c=new URLSearchParams;for(const[f,h]of Object.entries(n.query))if(h!=null&&h!=="")if(Array.isArray(h)){const m=f.endsWith("[]")?f:`${f}[]`;for(const y of h)y!=null&&y!==""&&c.append(m,String(y))}else c.set(f,String(h));const d=c.toString();d&&(i=`${e}?${d}`)}if(j.log("handleNavigate:",{target:e,params:n,finalPath:i,replace:n.replace,windowLocationSearch:window.location.search}),n.replace===!0){const c=window.G7Core;if(c?.updateQueryParams){const d=typeof n.transition_overlay_target=="string"?n.transition_overlay_target:void 0;await c.updateQueryParams(i,d?{transitionOverlayTarget:d}:void 0),j.log("handleNavigate: Used updateQueryParams for replace mode",d?{transitionOverlayTarget:d}:void 0),this.applyScrollOption(n.scroll,n.scrollBehavior,"top");return}j.warn("handleNavigate: G7Core.updateQueryParams not available, falling back to React Router")}const l=this.resolveNavigateFallbackAction(i,n);if(l){j.warn(`handleNavigate: No route matched, falling back to "${l.handler}"`),await this.dispatchAction({type:"click",handler:l.handler,params:l.params},r),this.applyScrollOption(n.scroll,n.scrollBehavior,"top");return}if(!r.navigate)throw new jt("Navigate function is not provided in context");r.navigate(i,{replace:n.replace===!0}),this.applyScrollOption(n.scroll,n.scrollBehavior,"top")}resolveNavigateFallbackAction(e,n){const r=n.fallback;if(r===!1||n.replace===!0)return null;const l=window.__templateApp?.getRouter?.();if(!l||typeof l.match!="function"||typeof l.getRoutes=="function"&&l.getRoutes().length===0)return null;const c=e.split("?")[0];return l.match(c)?null:this.resolveNavigateFallback(r,e,n)}resolveNavigateFallback(e,n,r){return e==null?{handler:"openWindow",params:{path:n,target:"_self"}}:typeof e=="string"?{handler:e,params:{path:n}}:typeof e=="object"&&typeof e.handler=="string"?{handler:e.handler,params:{path:n,...e.params||{}}}:(j.warn("resolveNavigateFallback: unrecognized fallback option, using openWindow",e),{handler:"openWindow",params:{path:n,target:"_blank"}})}applyScrollOption(e,n,r){const i=e===void 0?r:e;if(i==="preserve")return;const l=n==="smooth"?"smooth":"instant",c=y=>{if(typeof y!="object"||y===null)return!1;const v=y;return"container"in v||"to"in v||"block"in v||"offset"in v},d=(y,v,S=0)=>{y===window?window.scrollTo({top:v,left:S,behavior:l}):y.scrollTo({top:v,left:S,behavior:l})},f=(y,v,S,_)=>{if(v===window){y.scrollIntoView({behavior:l,block:S}),_&&window.scrollBy({top:-_,left:0,behavior:l});return}const x=v,A=y.getBoundingClientRect(),D=x.getBoundingClientRect(),O=x.scrollTop+(A.top-D.top);let k;S==="center"?k=O-(x.clientHeight-y.clientHeight)/2:S==="end"?k=O-(x.clientHeight-y.clientHeight):k=O,k-=_,x.scrollTo({top:Math.max(0,k),left:0,behavior:l})},h=()=>{window.scrollTo({top:0,left:0,behavior:l});const y=document.getElementById("app")??document.body;if(!y)return;y.querySelectorAll("*").forEach(S=>{if(S.scrollTop===0&&S.scrollLeft===0)return;const _=window.getComputedStyle(S),x=_.overflowY,A=_.overflowX;(x==="auto"||x==="scroll"||A==="auto"||A==="scroll")&&S.scrollTo({top:0,left:0,behavior:l})})},m=()=>{try{if(c(i)){const y=i,v=y.block??"start",S=typeof y.offset=="number"?y.offset:0;let _=window;if(typeof y.container=="string"&&y.container.length>0){const A=document.querySelector(y.container);if(!A){j.warn(`applyScrollOption: container not found: ${y.container}`);return}_=A}const x=y.to??"top";if(x==="top"){_===window?h():d(_,0);return}if(typeof x=="number"){d(_,x-S);return}if(typeof x=="object"&&x!==null&&("x"in x||"y"in x)){const{x:A=0,y:D=0}=x;d(_,D-S,A);return}if(typeof x=="string"&&(x.startsWith("#")||x.startsWith("."))){const A=document.querySelector(x);A?f(A,_,v,S):j.warn(`applyScrollOption: target element not found: ${x}`);return}j.warn('applyScrollOption: invalid "to" value in extended form',x);return}if(i==="top"){h();return}if(typeof i=="number"){window.scrollTo({top:i,left:0,behavior:l});return}if(typeof i=="object"&&i!==null&&("x"in i||"y"in i)){const{x:y=0,y:v=0}=i;window.scrollTo({top:v,left:y,behavior:l});return}if(typeof i=="string"&&(i.startsWith("#")||i.startsWith("."))){const y=document.querySelector(i);y&&y.scrollIntoView({behavior:l,block:"start"});return}}catch(y){j.warn("applyScrollOption: failed to apply scroll",y)}};typeof window.requestAnimationFrame=="function"?window.requestAnimationFrame(m):m()}async handleOpenWindow(e,n){let r=e;if(n.query){const l=new URLSearchParams;for(const[d,f]of Object.entries(n.query))f!=null&&f!==""&&l.set(d,String(f));const c=l.toString();c&&(r=`${e}?${c}`)}const i=n.target==="_self"?"_self":"_blank";j.log("handleOpenWindow:",{target:e,params:n,finalPath:r,windowTarget:i}),i==="_self"?window.location.assign(r):window.open(r,"_blank")}async handleNavigateBack(){j.log("handleNavigateBack"),window.history.back()}async handleNavigateForward(){j.log("handleNavigateForward"),window.history.forward()}async handleReplaceUrl(e,n){let r=e;if(n.query||n.mergeQuery===!0)if(n.mergeQuery===!0)r=this.buildMergedQueryPath(e,n.query??{});else{const i=new URLSearchParams;for(const[c,d]of Object.entries(n.query))if(d!=null&&d!=="")if(Array.isArray(d)){const f=c.endsWith("[]")?c:`${c}[]`;for(const h of d)h!=null&&h!==""&&i.append(f,String(h))}else i.set(c,String(d));const l=i.toString();l&&(r=`${e}?${l}`)}j.log("handleReplaceUrl:",{target:e,params:n,finalPath:r}),window.history.replaceState(null,"",r),this.applyScrollOption(n.scroll,n.scrollBehavior,"preserve")}buildMergedQueryPath(e,n){const r=new URLSearchParams(window.location.search);for(const[c,d]of Object.entries(n))if(d==null||d==="")r.delete(c);else if(Array.isArray(d)){const f=c.endsWith("[]")?c:`${c}[]`;r.delete(c),r.delete(f);for(const h of d)h!=null&&h!==""&&r.append(f,String(h))}else r.set(c,String(d));const i=r.toString(),l=e.split("?")[0];return i?`${l}?${i}`:l}async ensureCsrfToken(){try{await fetch("/sanctum/csrf-cookie",{credentials:"include"})}catch(e){throw new jt("Failed to fetch CSRF token",void 0,e instanceof Error?e:void 0)}}getCsrfTokenFromCookie(){const r=`; ${document.cookie}`.split("; XSRF-TOKEN=");if(r.length===2){const i=r.pop()?.split(";").shift();return i?decodeURIComponent(i):null}return null}buildQueryString(e){const n=new URLSearchParams;for(const[r,i]of Object.entries(e))if(!(i==null||i===""))if(Array.isArray(i))for(const l of i)l!=null&&l!==""&&n.append(`${r}[]`,String(l));else typeof i=="object"?n.append(r,JSON.stringify(i)):n.append(r,String(i));return n.toString()}async handleEnsureIdentityVerified(e){const n=typeof e.purpose=="string"?e.purpose:"sensitive_action",r=e.target&&typeof e.target=="object"?e.target:void 0;return await At.handle({success:!1,error_code:"identity_verification_required",message:"",verification:{policy_key:e.policy_key??"",purpose:n,provider_id:e.provider_id??null,render_hint:e.render_hint??null,return_request:null}},void 0,r)!==null}handleResolveIdentityChallenge(e){const n=typeof e.result=="string"?e.result:"cancelled";let r;switch(n){case"verified":{const i=typeof e.token=="string"?e.token:"";if(!i){j.warn("resolveIdentityChallenge: result=verified 인데 token 이 비어있습니다. failed 로 강등합니다."),r={status:"failed",failureCode:"MISSING_TOKEN"};break}r={status:"verified",token:i,providerData:e.providerData&&typeof e.providerData=="object"?e.providerData:void 0};break}case"pending":{const i=typeof e.pollUrl=="string"?e.pollUrl:"",l=typeof e.expiresAt=="string"?e.expiresAt:"";if(!i||!l){j.warn("resolveIdentityChallenge: result=pending 인데 pollUrl/expiresAt 누락 — failed 로 강등합니다."),r={status:"failed",failureCode:"MALFORMED_PENDING"};break}r={status:"pending",pollUrl:i,pollIntervalMs:typeof e.pollIntervalMs=="number"?e.pollIntervalMs:void 0,expiresAt:l};break}case"failed":r={status:"failed",failureCode:typeof e.failureCode=="string"?e.failureCode:"UNKNOWN",reason:typeof e.reason=="string"?e.reason:void 0};break;default:r={status:"cancelled"};break}return At.resolveDeferred(r),!0}async handleApiCall(e,n,r,i="none",l){const{method:c="GET",body:d,headers:f,contentType:h}=n;c!=="GET"&&c!=="HEAD"&&await this.ensureCsrfToken();const m=this.getCsrfTokenFromCookie();let y={};if(i==="required"||i==="optional"){const W=qr().getToken();W&&(y={Authorization:`Bearer ${W}`})}let v=e;const S=n.query||(c==="GET"?d:null);if(S&&typeof S=="object"){const z=this.buildQueryString(S);if(z){const W=e.includes("?")?"&":"?";v=`${e}${W}${z}`}}const _=window.G7Core?.state?.getGlobal?.()||r.state?._global||{},x=window.G7Core?.state?.getLocal?.()||r.state?._local||{},A={_global:_,_local:x},D=this.getMatchingGlobalHeaders(e,A),O={};if(typeof window<"u"){const z=localStorage.getItem("g7_locale");z&&(O["Accept-Language"]=z)}const k=h==="multipart/form-data",L={method:c,headers:{...k?{}:{"Content-Type":"application/json"},Accept:"application/json",...O,...m&&{"X-XSRF-TOKEN":m},...y,...D,...f},credentials:"include"};if(d&&c!=="GET")if(k){const z=new FormData;for(const[W,de]of Object.entries(d))de instanceof File||de instanceof Blob?z.append(W,de):de!=null&&z.append(W,typeof de=="object"?JSON.stringify(de):String(de));L.body=z}else L.body=JSON.stringify(d);const B=Yn();let q=null;B?.isEnabled()&&(q=B.trackRequest(v,c));try{let z=await fetch(v,L),W;try{W=await z.json()}catch{W=null}if(At.isIdentityRequired(z.status,W)){const de=await At.handle(W,{body:L.body,headers:L.headers,credentials:L.credentials},l);if(de){z=de;try{W=await z.json()}catch{W=null}}}if(q&&B?.isEnabled()&&(B.completeRequest(q,z.status,W),q=null),!z.ok){const de=W||{},_e=new Error(de.message||`API call failed: ${z.statusText}`);throw _e.response=de,_e.status=z.status,_e.statusText=z.statusText,new jt(de.message||`API call failed: ${z.statusText}`,void 0,_e)}return W}catch(z){throw q&&B?.isEnabled()&&B.failRequest(q,z instanceof Error?z.message:String(z)),z}}async handleLogin(e,n,r){const{body:i}=n;if(!i||!i.email||!i.password)throw new jt("Login requires email and password in body params");const l=e==="user"?"user":"admin",c=l==="admin"?"/api/auth/admin/login":"/api/auth/login",d=window.G7Core?.state?.getGlobal?.()||r.state?._global||{},f=window.G7Core?.state?.getLocal?.()||r.state?._local||{},h={_global:d,_local:f},m=this.getMatchingGlobalHeaders(c,h),y=Ar.getInstance();try{const v=Object.keys(m).length>0?{headers:m}:void 0;return{user:await y.login(l,{email:i.email,password:i.password},v)}}catch(v){const S=v.message||"Login failed",_=new Error(S);throw _.response=v.response?.data||{},_.status=v.status||v.response?.status||500,new jt(S,void 0,_)}}async handleLogout(e,n){await Ar.getInstance().logout()}async handleSetState(e,n){j.log("[handleSetState] START, params:",e);const{target:r="component",scope:i,merge:l,__render:c,...d}=e,f=Yn(),h=window.G7Core;if(typeof r=="string"&&(r.startsWith("$parent.")||r.startsWith("$root.")))return this.handleParentScopeSetState(r,d,l,n);const m=n.state&&Object.keys(n.state).length>0?n.state:h?.state?.get()||{},y=r==="global"?"_global":r.startsWith("_local.")?r:"_local",v=y==="_global"?m._global:y.startsWith("_local.")?this.getNestedProperty(m._local||{},y.slice(7)):m._local,S=f?.isEnabled()?f.startStateChange(y,v,d,{actionId:n.actionId,handlerType:"setState",source:`target=${r}`}):void 0,_=l==="replace"?"replace":l==="shallow"?"shallow":"deep",x=_!=="deep"?{...d,__mergeMode:_,...S?{__setStateId:S}:{}}:{...d,...S?{__setStateId:S}:{}};if(r==="global"){if(!this.globalStateUpdater){j.warn("Global state updater is not set"),S&&f&&f.completeStateChange(S);return}const A=h?.state?.get()||{},{__setStateId:D,__mergeMode:O,...k}=x,L=_==="deep"?this.deepMergeWithState(k,A):k,B={...L,...S?{__setStateId:S}:{},..._!=="deep"?{__mergeMode:_}:{}};return j.log("setState global:",B),this.globalStateUpdater(B,{render:c}),S&&f&&setTimeout(()=>f.completeStateChange(S),0),{__target:"global",..._!=="deep"?{__mergeMode:_}:{},...L}}else if(r==="isolated"){const A=n.isolatedContext;if(A){const{__mergeMode:D,__setStateId:O,...k}=x;return A.mergeState(k,_),j.log("[handleSetState] isolated state updated:",k,"mergeMode:",_),S&&f&&setTimeout(()=>f.completeStateChange(S),0),{__target:"isolated",...k}}else if(j.warn('[handleSetState] isolated target used but no IsolatedStateContext found. Make sure the component has "isolatedState" attribute. Falling back to local state.'),n.setState){const O=window.__g7PendingLocalState||n.state||{},k=_==="deep"?this.deepMergeWithState(x,O):x;return this.writeLocalState(n,k),S&&f&&setTimeout(()=>f.completeStateChange(S),0),k}else throw S&&f&&f.completeStateChange(S),new jt("isolated target used but no IsolatedStateContext found and no setState function in context")}else if(r==="local"||r==="component"||r==="_local"){if(i==="parent"||i==="root"){const D=window.__g7LayoutContextStack||[];if(D.length>0){const O=i==="parent"?D[D.length-1]:D[0];if(O?.setState){const{__mergeMode:k,__setStateId:L,...B}=x,q=_==="deep"?this.deepMergeWithState(B,O.state||{}):B;return j.log(`[handleSetState] scope=${i}: 타겟 컨텍스트에 상태 업데이트`,q),O.setState(q),S&&f&&setTimeout(()=>f.completeStateChange(S),0),q}}j.warn(`[handleSetState] scope=${i}: 레이아웃 컨텍스트 스택이 비어있습니다. current로 폴백합니다.`)}const A=n.setState&&!n._isDispatchFallbackContext;if(j.log("[handleSetState] isRealComponentContext:",A,"context.setState:",!!n.setState,"_isDispatchFallbackContext:",n._isDispatchFallbackContext),A){j.log("[handleSetState] Using COMPONENT setState path"),j.log("[handleSetState] resolvedPayload:",x),j.log("[handleSetState] context.state:",n.state),j.log("[handleSetState] mergeMode:",_);const O=window.__g7PendingLocalState||n.state||{},k=_==="deep"?this.deepMergeWithState(x,{}):x;j.log("[handleSetState] convertedPayload (변경 필드만, dot notation 변환):",k),n.setState(k);const L=_==="deep"?this.deepMergeWithState(x,O):_==="shallow"?{...n.state||{},...x}:x,{__mergeMode:B,__setStateId:q,...z}=L;window.__g7PendingLocalState=z,j.log("[handleSetState] __g7PendingLocalState updated:",z);const W=(window.__g7LayoutContextStack||[]).length;let de;if(this.globalStateUpdater){const ze=W===0?window.__templateApp?.getGlobalState?.()?._local:void 0;if(_!=="replace"&&!!ze&&typeof ze=="object"&&!Array.isArray(ze)){const ue=_==="deep"?this.deepMergeWithState(x,ze):{...ze,...x},{__mergeMode:De,__setStateId:ee,...me}=ue;de=me}else _!=="replace"&&j.log("[handleSetState] canonical _local 미사용 → 전체 스냅샷 폴백",{modalDepth:W,hasTemplateApp:!!window.__templateApp});const Q=de?Mo(de,z):z;this.globalStateUpdater({_local:Q},{render:!1}),j.log("[handleSetState] _global._local synced (render:false):",Q)}const{__mergeMode:_e,__setStateId:Se,...Ie}=x;if(_==="replace")window.__g7ForcedLocalFields=Ie;else{const ze=window.__g7ForcedLocalFields||{};window.__g7ForcedLocalFields=this.deepMergeWithState(Ie,ze)}return j.log("[handleSetState] __g7ForcedLocalFields updated:",Ie),S&&f&&setTimeout(()=>f.completeStateChange(S),0),{__target:"local",...de?Mo(de,z):L,..._!=="deep"?{__mergeMode:_}:{}}}else if(this.globalStateUpdater){j.log("[handleSetState] Using GLOBAL STATE UPDATER path for _local");const O=window.__g7PendingLocalState||m._local||{};j.log("[handleSetState] currentLocal (with pending):",O);const k=_==="deep"?this.deepMergeWithState(x,O):x;return j.log("[handleSetState] finalLocal (merged):",k),this.globalStateUpdater({_local:k},{render:c}),window.__g7PendingLocalState=k,j.log("[handleSetState] __g7PendingLocalState updated for globalStateUpdater path"),S&&f&&setTimeout(()=>f.completeStateChange(S),0),{__target:"local",...k}}else throw S&&f&&f.completeStateChange(S),new jt("setState function is not provided in context and globalStateUpdater is not set")}else if(r.startsWith("_local.")){const A=r.slice(7),{__mergeMode:D,__setStateId:O,...k}=x,L=k.value!==void 0?k.value:Object.values(k)[0];if(n.setState){const q=window.__g7PendingLocalState||n.state||{},z=this.createNestedUpdate(A,L,q),W={...q,...z};if(n.setState(z),this.globalStateUpdater&&i!=="parent"&&i!=="root"){const de=window.__templateApp?.getGlobalState?.()?._local,_e=de&&typeof de=="object"?{...de,...z}:W;this.globalStateUpdater({_local:_e},{render:!1}),j.log("[handleSetState] _global._local synced for dot notation (render:false):",_e)}return S&&f&&setTimeout(()=>f.completeStateChange(S),0),W}else throw S&&f&&f.completeStateChange(S),new jt("setState function is not provided in context")}else{if(!n.setState)throw S&&f&&f.completeStateChange(S),new jt("setState function is not provided in context");const D=window.__g7PendingLocalState||n.state||{},O=_==="deep"?this.deepMergeWithState(x,D):x;return this.writeLocalState(n,O,void 0,{scope:i}),S&&f&&setTimeout(()=>f.completeStateChange(S),0),O}}async handleParentScopeSetState(e,n,r,i){const l=Yn(),c=window.G7Core,d=r==="replace"?"replace":r==="shallow"?"shallow":"deep",f=e.startsWith("$parent."),h=f?"$parent.":"$root.",m=e.slice(h.length);let y,v;if(m==="_local"||m==="_global")y=m,v=void 0;else if(m.startsWith("_local."))y="_local",v=m.slice(7);else if(m.startsWith("_global."))y="_global",v=m.slice(8);else{j.warn(`[handleParentScopeSetState] 지원하지 않는 타겟 형식: ${e}. _local 또는 _global으로 시작해야 합니다.`);return}const S=window.__g7LayoutContextStack||[];if(S.length===0){j.warn(`[handleParentScopeSetState] 레이아웃 컨텍스트 스택이 비어있습니다. target=${e}`);return}const _=f?S[S.length-1]:S[0];if(!_){j.warn(`[handleParentScopeSetState] 타겟 컨텍스트를 찾을 수 없습니다. target=${e}`);return}const x=y==="_global"?_.state?._global:_.state?._local,A=l?.isEnabled()?l.startStateChange(e,x,n,{actionId:i.actionId,handlerType:"setState",source:`target=${e}`}):void 0,{__mergeMode:D,__setStateId:O,...k}=n;if(y==="_global"){if(!this.globalStateUpdater){j.warn("[handleParentScopeSetState] globalStateUpdater가 설정되지 않았습니다."),A&&l&&l.completeStateChange(A);return}const L=c?.state?.get()||{};if(v){const B=k.value!==void 0?k.value:Object.values(k)[0],q=this.createNestedUpdate(v,B,L),z={...L,...q};j.log(`[handleParentScopeSetState] ${e}: 전역 상태 중첩 경로 업데이트`,z),this.globalStateUpdater(z)}else{const B=d==="deep"?this.deepMergeWithState(k,L):k;j.log(`[handleParentScopeSetState] ${e}: 전역 상태 업데이트 (mergeMode=${d})`,B),this.globalStateUpdater(d!=="deep"?{...B,__mergeMode:d}:B)}return A&&l&&setTimeout(()=>l.completeStateChange(A),0),{__target:e,...k}}else{if(!_.setState){j.warn(`[handleParentScopeSetState] 타겟 컨텍스트에 setState가 없습니다. target=${e}`),A&&l&&l.completeStateChange(A);return}const L=_.state||{};if(v){const B=k.value!==void 0?k.value:Object.values(k)[0],q=this.createNestedUpdate(v,B,L),z={...L,...q};return j.log(`[handleParentScopeSetState] ${e}: 로컬 상태 중첩 경로 업데이트`,q),_.setState(q),_.state=z,_.dataContext&&(_.dataContext._local=z),$f(),A&&l&&setTimeout(()=>l.completeStateChange(A),0),z}else{const B=d!=="deep"?{...k,__mergeMode:d}:k,q=d==="replace"?k:d==="shallow"?{...L,...k}:this.deepMergeWithState(k,L);return j.log(`[handleParentScopeSetState] ${e}: 로컬 상태 업데이트 (mergeMode=${d})`,q),_.setState(B),_.state=q,_.dataContext&&(_.dataContext._local=q),$f(),A&&l&&setTimeout(()=>l.completeStateChange(A),0),q}}}deepMergeWithState(e,n){const r=["errors"];let i=e;if("..."in e){const c=e["..."];if(c&&typeof c=="object"&&!Array.isArray(c)){const{"...":d,...f}=e;i={...c,...f}}else{const{"...":d,...f}=e;i=f}}const l={...n};for(const[c,d]of Object.entries(i)){if(r.includes(c)){l[c]=d;continue}if(c.includes(".")&&c!=="..."){const f=this.createNestedUpdate(c,d,l);this.deepMergeInto(l,f,l);continue}if(d!==null&&typeof d=="object"&&!Array.isArray(d)&&Object.getPrototypeOf(d)!==Object.prototype&&Object.getPrototypeOf(d)!==null){l[c]=d;continue}d!==null&&typeof d=="object"&&!Array.isArray(d)&&n[c]!==null&&typeof n[c]=="object"&&!Array.isArray(n[c])?l[c]=this.deepMergeWithState(d,n[c]):l[c]=d}return l}deepMergeInto(e,n,r){for(const[i,l]of Object.entries(n)){if(l===null){e[i]=l;continue}if(e[i]===null){e[i]=l;continue}typeof l=="object"&&!Array.isArray(l)&&e[i]!==void 0&&e[i]!==null&&typeof e[i]=="object"&&!Array.isArray(e[i])?(e[i]===r[i]&&(e[i]={...e[i]}),this.deepMergeInto(e[i],l,r[i]||{})):typeof l=="object"&&!Array.isArray(l)&&e[i]===void 0&&r[i]!==null&&typeof r[i]=="object"&&!Array.isArray(r[i])?e[i]={...r[i],...l}:e[i]=l}}createNestedUpdate(e,n,r){const i=e.split(".");if(i.length===1)return{[i[0]]:n};const l={};let c=l,d=r;for(let f=0;f{this.globalStateUpdater({_local:S})};m.push({state:y,setState:v,dataContext:{_local:y||n.data?._local,_global:l?n.data?._global:n.data?._global||i?.state?.get?.(),_computed:n.data?._computed}}),window.__g7LayoutContextStack=m,j.log(`[handleOpenModal] 레이아웃 컨텍스트 스택에 push, 스택 크기: ${m.length}, state:`,y,l?"(direct)":"(fallback via G7Core)")}else j.warn(`[handleOpenModal] 컨텍스트 스택에 push 실패 - setState: ${!!n.setState}, state: ${n.state!==void 0}, G7Core: ${!!i?.state?.getLocal}`);const f=[...r,e];this.globalStateUpdater({modalStack:f,activeModal:e});const h=Yn();if(h?.isEnabled()){const m=r.length>0?r[r.length-1]:void 0;h.trackModalOpen?.({modalId:e,modalName:e,scopeType:"isolated",parentModalId:m,initialState:n.state?{...n.state}:{}})}}async handleCloseModal(e){if(!this.globalStateUpdater){j.warn("Global state updater is not set for closeModal");return}const n=e?.data?._global?.modalStack||[],r=n.length>0?n[n.length-1]:null,i=n.slice(0,-1),l=i.length>0?i[i.length-1]:null,c=window.__g7LayoutContextStack||[];if(c.length>0){const d=c[c.length-1];d&&(d.dataContext=void 0),c.pop(),window.__g7LayoutContextStack=c,j.log(`[handleCloseModal] 레이아웃 컨텍스트 스택에서 pop, 스택 크기: ${c.length}`)}if(r){const d=Yn();d?.isEnabled()&&d.trackModalClose?.(r,e?.state)}this.globalStateUpdater({modalStack:i,activeModal:l})}async handleShowAlert(e,n){let r=e;this.translationEngine&&this.translationContext&&e.startsWith("$t:")&&(r=this.translationEngine.resolveTranslations(e,this.translationContext,n.data)),alert(r)}async handleToast(e,n){const{type:r="info",message:i,icon:l,duration:c}=e;if(r==="error"){const f=n.data?.error;if((f?.error_code==="identity_verification_required"||f?.data?.error_code==="identity_verification_required")&&At.consumeDomainNoticeShown()){j.log("[Toast] IDV 도메인 안내 표출됨 — 중복 가드 토스트 1건 skip");return}}let d=i;if(this.translationEngine&&this.translationContext&&i?.startsWith("$t:")&&(d=this.translationEngine.resolveTranslations(i,this.translationContext,n.data)),this.globalStateUpdater){const h={id:`toast_${Date.now()}_${Math.random().toString(36).substring(2,9)}`,type:r,message:d,...l&&{icon:l},...c&&{duration:c}},S=[...window.G7Core?.state?.get()?.toasts||[],h];this.globalStateUpdater({toasts:S})}else j.log(`[Toast ${r}] ${d}`)}async handleSwitch(e,n){const{cases:r}=e;if(!r){j.warn("switch handler requires cases property");return}const i=this.resolveParams(e.params,n.data);let l;if(i?.value!==void 0?(l=i.value,j.log("switch handler: using params.value as case key:",l)):(l=n.data?.$args?.[0],j.log("switch handler: using $args[0] as case key:",l)),l==null){if(r.default)return j.log("switch handler: case key is undefined, using default case"),await this.executeAction(r.default,n);j.warn("switch handler: case key is not defined and no default case");return}const c=String(l);let d=r[c];if(!d&&r.default&&(j.log(`switch handler: no case found for key "${c}", using default case`),d=r.default),!d){j.warn(`switch handler: no case found for key "${c}" and no default case`);return}return await this.executeAction(d,n)}async handleConditions(e,n){const{conditions:r}=e;if(!r||!Array.isArray(r)){j.warn("conditions handler requires conditions array");return}const i=n.data||{},l=fm(r,i,this.bindingEngine);if(!l.matched){j.log("conditions handler: no matching branch found");return}const c=r[l.branchIndex];if(j.log(`conditions handler: matched branch ${l.branchIndex}`),!c.then){j.warn('conditions handler: matched branch has no "then" action');return}return Array.isArray(c.then)?await this.handleSequence({...e,actions:c.then},n):await this.executeAction(c.then,n)}handleStartInterval(e,n){const r=typeof e.id=="string"?e.id:"",i=Number(e.intervalMs??1e3),l=Array.isArray(e.actions)?e.actions:[];if(r===""||!l.length||i<=0)return j.warn("startInterval requires non-empty id, positive intervalMs, and actions array"),{success:!1};const c=this.intervals.get(r);c&&(clearInterval(c),this.intervals.delete(r));const d=setInterval(()=>{for(const f of l)this.executeAction(f,n).catch(h=>{j.error(`[startInterval:${r}] tick action failed`,h)})},i);return this.intervals.set(r,d),{success:!0,id:r}}handleStopInterval(e){const n=typeof e.id=="string"?e.id:"";if(n==="")return j.warn("stopInterval requires id parameter"),{success:!1};const r=this.intervals.get(n);return r&&(clearInterval(r),this.intervals.delete(n)),{success:!0,id:n}}stopAllIntervals(){for(const e of this.intervals.values())clearInterval(e);this.intervals.clear()}async handleSequence(e,n){const r=e.actions||e.params?.actions;if(!r||!Array.isArray(r)||r.length===0)return j.warn("sequence handler requires non-empty actions array"),[];const i=Yn(),l=i?.isEnabled()&&i.startSequenceExecution?.({eventType:e.type})||"";window.__g7SequenceLocalSync=void 0;const c=[];let d,f=n.state?{...n.state}:{},h=n.isolatedContext?.state?{...n.isolatedContext.state}:{},m=n.data?._computed||{};const y=window.G7Core,v=()=>({_global:y?.state?.get()||{},_local:{...f},_isolated:Object.keys(h).length>0?{...h}:void 0});for(let S=0;S0){const k={},L={...n.data,_local:f,_computed:k,$computed:k,_isolated:h};for(const[B,q]of Object.entries(O))if(typeof q=="string"){const z=q.trim();if(z.startsWith("{{")&&z.endsWith("}}"))try{const W=z.slice(2,-2).trim();k[B]=Wn(W)?this.bindingEngine.evaluatePipeExpression(W,L,{skipCache:!0}):this.bindingEngine.evaluateExpression(W,L,{skipCache:!0})}catch{k[B]=m[B]}}m=k,j.log("[handleSequence] _computed recalculated after setState:",m)}}if(_.handler!=="setState"){const O=window.__g7SequenceLocalSync;if(O&&O!==f){f=O,window.__g7SequenceLocalSync=void 0,j.log("[handleSequence] currentState synchronized from __g7SequenceLocalSync after custom handler:",_.handler);const k=n.data?._computedDefinitions;if(k&&Object.keys(k).length>0){const L={},B={...n.data,_local:f,_computed:L,$computed:L,_isolated:h};for(const[q,z]of Object.entries(k))if(typeof z=="string"){const W=z.trim();if(W.startsWith("{{")&&W.endsWith("}}"))try{const de=W.slice(2,-2).trim();L[q]=Wn(de)?this.bindingEngine.evaluatePipeExpression(de,B,{skipCache:!0}):this.bindingEngine.evaluateExpression(de,B,{skipCache:!0})}catch{L[q]=m[q]}}m=L,j.log("[handleSequence] _computed recalculated after custom handler:",m)}}}l&&i?.isEnabled()&&i.captureSequenceActionAfter?.(l,S,v(),Date.now()-x,D.data)}catch(D){throw l&&i?.isEnabled()&&(i.captureSequenceActionAfter?.(l,S,v(),Date.now()-x,void 0,D),i.endSequenceExecution?.(l,D)),j.error(`sequence handler: action[${S}] failed:`,D),D}}return window.__g7SequenceLocalSync=void 0,l&&i?.isEnabled()&&i.endSequenceExecution?.(l),c}async handleParallel(e,n){const r=e.actions||e.params?.actions;if(!r||!Array.isArray(r)||r.length===0)return j.warn("parallel handler requires non-empty actions array"),[];j.log(`parallel handler: executing ${r.length} actions in parallel`);const i=n.isolatedContext?{...n.isolatedContext,state:{...n.isolatedContext.state}}:null,l=r.map((f,h)=>{const m={...n,isolatedContext:i};return this.executeAction(f,m).catch(y=>{throw j.error(`parallel handler: action[${h}] failed:`,y),y})}),c=await Promise.allSettled(l),d=c.filter(f=>f.status==="rejected").length;return d>0&&j.warn(`parallel handler: ${d}/${r.length} actions failed`),c}assertAllowedExtensionAssetUrl(e,n,r){if(!Io(e,bs()))throw j.error(`Blocked untrusted ${n} asset url: ${e}`),new jt(`Blocked untrusted ${n} url (same-origin path or declared trusted host required): ${e}`,r)}async handleLoadScript(e,n,r){const{src:i,id:l,async:c=!0,defer:d=!1}=e;if(!i)throw new jt('loadScript handler requires "src" parameter',n);if(!Io(i,bs()))throw j.warn(`loadScript: blocked untrusted script src (same-origin path or declared trusted host required): ${i}`),new jt(`Blocked untrusted script src (same-origin path or declared trusted host required): ${i}`,n);const f=l||`script_${i.replace(/[^a-zA-Z0-9]/g,"_")}`;if(cn.loadedScripts.has(f))return j.log(`loadScript: script already loaded, skipping: ${f}`),n.onLoad&&await this.executeAction(n.onLoad,r),!0;const h=cn.loadingScripts.get(f);if(h)return j.log(`loadScript: joining in-flight load: ${f}`),await h,n.onLoad&&await this.executeAction(n.onLoad,r),!0;if(document.getElementById(f))return j.log(`loadScript: script element already exists: ${f}`),cn.loadedScripts.add(f),n.onLoad&&await this.executeAction(n.onLoad,r),!0;j.log(`loadScript: loading script: ${i}`);const y=document.createElement("script");if(y.id=f,y.src=i,y.async=c,y.defer=d,y.hasAttribute("data-gdpr-blocked-src"))return j.warn(`loadScript: script blocked by consent manager (not loaded): ${i}`),!1;const v=new Promise((S,_)=>{y.onload=()=>{j.log(`loadScript: script loaded successfully: ${f}`),cn.loadedScripts.add(f),cn.loadingScripts.delete(f),S()},y.onerror=x=>{j.error(`loadScript: failed to load script: ${i}`,x),cn.loadingScripts.delete(f),_(new jt(`Failed to load script: ${i}`,n))},document.head.appendChild(y)});if(v.catch(()=>{}),cn.loadingScripts.set(f,v),await v,n.onLoad)try{await this.executeAction(n.onLoad,r)}catch(S){j.error("loadScript: onLoad action failed:",S)}return!0}async handleCallExternal(e,n,r){const i=e.constructor,l=e.args||{},c=e.method,d=e.methodArgs||[],f=e.callbackEvent,h=e.embedTarget,m=e.callbackSetState,y=e.callbackAction;if(!i)throw new jt('callExternal handler requires "constructor" parameter',n);const v=this.getNestedProperty(window,i);if(!v||typeof v!="function")throw new jt(`Constructor not found or not a function: ${i}. Make sure the script is loaded first using loadScript handler.`,n);this.assertCallableExternalConstructor(v,i,n),j.log(`callExternal: calling constructor ${i}`);const S={...l};for(const[x,A]of Object.entries(l))A===!0&&(S[x]=D=>{if(j.log(`callExternal: callback triggered for ${x}`,D),f&&typeof window<"u"&&window.G7Core?.componentEvent&&window.G7Core.componentEvent.emit(f,D),m&&r.setState){const O=B=>{const q={};for(const[z,W]of Object.entries(B)){if(cn.isPrototypePathSegment(z)){j.warn(`callExternal: skipped prototype-polluting mapping key: ${z}`);continue}typeof W=="string"?q[z]=this.getNestedProperty(D,W):typeof W=="object"&&W!==null&&(q[z]=O(W))}return q},k=O(m);j.log("callExternal: callbackSetState mapping result",k);const L=this.deepMergeWithState(k,r.state||{});j.log("callExternal: merged with existing state",L),this.writeLocalState(r,L)}else f&&r.setState&&this.writeLocalState(r,{[`${f.replace(/:/g,"_")}_result`]:D});if(y){const O={...r,data:{...r.data,$event:D}},k=Array.isArray(y)?y:[y];for(const L of k)try{this.executeAction(L,O)}catch(B){j.error("callExternal: callbackAction failed:",B)}}});const _=new v(S);if(c&&typeof _[c]=="function"){if(j.log(`callExternal: calling method ${c}`),c==="embed"&&h){const x=document.querySelector(h);if(x)return _[c](x,...d);throw new jt(`Embed target element not found: ${h}`,n)}return _[c](...d)}return _}async handleCallExternalEmbed(e,n,r){const i=e.constructor,l=e.args||{},c=e.callbackSetState,d=e.callbackEvent,f=e.layerClassName,h=e.callbackAction;if(!i)throw new jt('callExternalEmbed handler requires "constructor" parameter',n);const m=this.getNestedProperty(window,i);if(!m||typeof m!="function")throw new jt(`Constructor not found or not a function: ${i}. Make sure the script is loaded first.`,n);this.assertCallableExternalConstructor(m,i,n),j.log(`callExternalEmbed: creating layer for ${i}`);const{layer:y,closeLayer:v}=this.createEmbedLayer(f),S={...l};for(const[x,A]of Object.entries(l))A===!0&&(S[x]=D=>{if(j.log(`callExternalEmbed: callback triggered for ${x}`,D),v(),d&&typeof window<"u"&&window.G7Core?.componentEvent&&window.G7Core.componentEvent.emit(d,D),c&&r.setState){const O=B=>{const q={};for(const[z,W]of Object.entries(B)){if(cn.isPrototypePathSegment(z)){j.warn(`callExternal: skipped prototype-polluting mapping key: ${z}`);continue}typeof W=="string"?q[z]=this.getNestedProperty(D,W):typeof W=="object"&&W!==null&&(q[z]=O(W))}return q},k=O(c);j.log("callExternalEmbed: callbackSetState mapping result",k);const L=this.deepMergeWithState(k,r.state||{});j.log("callExternalEmbed: merged with existing state",L),this.writeLocalState(r,L)}if(h){const O={...r,data:{...r.data,$event:D}},k=Array.isArray(h)?h:[h];for(const L of k)try{this.executeAction(L,O)}catch(B){j.error("callExternalEmbed: callbackAction failed:",B)}}});const _=new m(S);if(typeof _.embed=="function")_.embed(y),j.log("callExternalEmbed: embedded in layer");else throw v(),new jt(`Constructor ${i} does not have embed method`,n);return _}isImeComposing(e){return e?.isComposing===!0||e?.keyCode===229}createEmbedLayer(e){const n=document.createElement("div");n.id="g7-embed-overlay",n.style.cssText=` +`+c)}}catch{}throw r}}_request(e,n){typeof e=="string"?(n=n||{},n.url=e):n=e||{},n=xi(this.defaults,n);const{transitional:r,paramsSerializer:i,headers:l}=n;r!==void 0&&vc.assertOptions(r,{silentJSONParsing:Tn.transitional(Tn.boolean),forcedJSONParsing:Tn.transitional(Tn.boolean),clarifyTimeoutError:Tn.transitional(Tn.boolean),legacyInterceptorReqResOrdering:Tn.transitional(Tn.boolean),advertiseZstdAcceptEncoding:Tn.transitional(Tn.boolean),validateStatusUndefinedResolves:Tn.transitional(Tn.boolean)},!1),i!=null&&(G.isFunction(i)?n.paramsSerializer={serialize:i}:vc.assertOptions(i,{encode:Tn.function,serialize:Tn.function},!0)),n.allowAbsoluteUrls!==void 0||(this.defaults.allowAbsoluteUrls!==void 0?n.allowAbsoluteUrls=this.defaults.allowAbsoluteUrls:n.allowAbsoluteUrls=!0),vc.assertOptions(n,{baseUrl:Tn.spelling("baseURL"),withXsrfToken:Tn.spelling("withXSRFToken")},!0),n.method=(G.getSafeProp(n,"method")||G.getSafeProp(this.defaults,"method")||"get").toLowerCase();let c=l&&G.merge(l.common,l[n.method]);l&&G.forEach(qm.concat("common"),_=>{delete l[_]}),n.headers=xn.concat(c,l);const d=[];let f=!0;this.interceptors.request.forEach(function(x){if(typeof x.runWhen=="function"&&x.runWhen(n)===!1)return;f=f&&x.synchronous;const A=n.transitional||gf;A&&A.legacyInterceptorReqResOrdering?d.unshift(x.fulfilled,x.rejected):d.push(x.fulfilled,x.rejected)});const h=[];this.interceptors.response.forEach(function(x){h.push(x.fulfilled,x.rejected)});let m,y=0,v;if(!f){const _=[Ef.bind(this),void 0];for(_.unshift(...d),_.push(...h),v=_.length,m=Promise.resolve(n);yEf.call(this,S)))}catch(D){m=Promise.reject(D)}break}}if(!m)try{m=Ef.call(this,S)}catch(_){m=Promise.reject(_)}for(y=0,v=h.length;y{if(!r._listeners)return;let l=r._listeners.length;for(;l-- >0;)r._listeners[l](i);r._listeners=null}),this.promise.then=i=>{let l;const c=new Promise(d=>{r.subscribe(d),l=d}).then(i);return c.cancel=function(){r.unsubscribe(l)},c},e(function(l,c,d){r.reason||(r.reason=new Lo(l,c,d),n(r.reason))})}throwIfRequested(){if(this.reason)throw this.reason}subscribe(e){if(this.reason){e(this.reason);return}this._listeners?this._listeners.push(e):this._listeners=[e]}unsubscribe(e){if(!this._listeners)return;const n=this._listeners.indexOf(e);n!==-1&&this._listeners.splice(n,1)}toAbortSignal(){const e=new AbortController,n=r=>{e.abort(r)};return this.subscribe(n),e.signal.unsubscribe=()=>this.unsubscribe(n),e.signal}static source(){let e;return{token:new r0(function(i){e=i}),cancel:e}}};function XE(s){return function(n){return s.apply(null,n)}}function JE(s){return G.isObject(s)&&s.isAxiosError===!0}const Sc={Continue:100,SwitchingProtocols:101,Processing:102,EarlyHints:103,Ok:200,Created:201,Accepted:202,NonAuthoritativeInformation:203,NoContent:204,ResetContent:205,PartialContent:206,MultiStatus:207,AlreadyReported:208,ImUsed:226,MultipleChoices:300,MovedPermanently:301,Found:302,SeeOther:303,NotModified:304,UseProxy:305,Unused:306,TemporaryRedirect:307,PermanentRedirect:308,BadRequest:400,Unauthorized:401,PaymentRequired:402,Forbidden:403,NotFound:404,MethodNotAllowed:405,NotAcceptable:406,ProxyAuthenticationRequired:407,RequestTimeout:408,Conflict:409,Gone:410,LengthRequired:411,PreconditionFailed:412,PayloadTooLarge:413,ContentTooLarge:413,UriTooLong:414,UnsupportedMediaType:415,RangeNotSatisfiable:416,ExpectationFailed:417,ImATeapot:418,MisdirectedRequest:421,UnprocessableEntity:422,UnprocessableContent:422,Locked:423,FailedDependency:424,TooEarly:425,UpgradeRequired:426,PreconditionRequired:428,TooManyRequests:429,RequestHeaderFieldsTooLarge:431,UnavailableForLegalReasons:451,InternalServerError:500,NotImplemented:501,BadGateway:502,ServiceUnavailable:503,GatewayTimeout:504,HttpVersionNotSupported:505,VariantAlsoNegotiates:506,InsufficientStorage:507,LoopDetected:508,NotExtended:510,NetworkAuthenticationRequired:511,WebServerReturnsAnUnknownError:520,WebServerIsDown:521,ConnectionTimedOut:522,OriginIsUnreachable:523,TimeoutOccurred:524,SslHandshakeFailed:525,InvalidSslCertificate:526};Object.entries(Sc).forEach(([s,e])=>{Sc[e]===void 0&&(Sc[e]=s)});function uy(s){const e=new Ti(s),n=mm(Ti.prototype.request,e);return G.extend(n,Ti.prototype,e,{allOwnKeys:!0}),G.extend(n,e,null,{allOwnKeys:!0}),n.create=function(i){return uy(xi(s,i))},n}const Qt=uy(Oo);Qt.Axios=Ti,Qt.CanceledError=Lo,Qt.CancelToken=YE,Qt.isCancel=Gm,Qt.VERSION=Sf,Qt.toFormData=gc,Qt.AxiosError=we,Qt.Cancel=Qt.CanceledError,Qt.all=function(e){return Promise.all(e)},Qt.spread=XE,Qt.isAxiosError=JE,Qt.mergeConfig=xi,Qt.AxiosHeaders=xn,Qt.formToJSON=s=>Bm(G.isHTMLForm(s)?new FormData(s):s),Qt.getAdapter=ly.getAdapter,Qt.HttpStatusCode=Sc,Qt.default=Qt;const{Axios:RR,AxiosError:DR,CanceledError:OR,isCancel:LR,CancelToken:$R,VERSION:MR,all:NR,Cancel:IR,isAxiosError:jR,spread:HR,toFormData:zR,AxiosHeaders:PR,HttpStatusCode:UR,formToJSON:BR,getAdapter:qR,mergeConfig:GR,create:FR}=Qt,_f="g7.identity.redirectStash",wc=pt("IdentityGuardInterceptor");class At{static setLauncher(e){this.launcher=e}static hasLauncher(){return this.launcher!==null}static markDomainNoticeShown(){this.domainNoticeShown=!0}static consumeDomainNoticeShown(){const e=this.domainNoticeShown;return this.domainNoticeShown=!1,e}static resolveDeferred(e){const n=this.deferredResolver;if(!n){wc.warn("IdentityGuardInterceptor.resolveDeferred 호출됐지만 대기 중인 launcher 가 없습니다. 모달이 launcher 외부에서 열렸는지 확인하세요.");return}this.deferredResolver=null,n(e)}static createDeferred(){if(this.deferredResolver){const e=this.deferredResolver;this.deferredResolver=null,e({status:"cancelled"})}return new Promise(e=>{this.deferredResolver=e})}static isIdentityRequired(e,n){return e===428&&typeof n=="object"&&n!==null&&n.error_code==="identity_verification_required"}static async handle(e,n,r){this.domainNoticeShown=!1;const i=this.launcher??ZE,c=!!(r&&(r.email||r.phone))?{...e.verification,target:r}:e.verification,d=await i(c);if(d.status!=="verified")return null;const f=c.return_request;if(!f)return null;const h=QE(f.url,d.token);return fetch(h,{method:f.method,headers:n?.headers??{Accept:"application/json","Content-Type":"application/json"},body:n?.body,credentials:n?.credentials??"same-origin"})}static redirectExternally(e){const n=e.redirect_url;if(!n)return wc.error("redirectExternally 호출 시 verification.redirect_url 이 없습니다. payload:",e),Promise.resolve({status:"failed",failureCode:"MISSING_REDIRECT_URL",reason:"verification.redirect_url is required for external_redirect flow"});if(typeof window<"u"){const r={return_url:window.location.href,payload:e,stashed_at:Date.now()};try{window.sessionStorage?.setItem(_f,JSON.stringify(r))}catch(i){wc.warn("sessionStorage 접근 실패 — stash 없이 redirect 합니다.",i)}window.location.href=n}return new Promise(()=>{})}static reset(){if(this.deferredResolver){const e=this.deferredResolver;this.deferredResolver=null;try{e({status:"cancelled"})}catch{}}this.launcher=null,this.domainNoticeShown=!1}}M(At,"launcher",null),M(At,"deferredResolver",null),M(At,"domainNoticeShown",!1);function QE(s,e){if(!e)return s;try{const n=typeof window<"u"?window.location.origin:"http://localhost",r=new URL(s,n);return r.searchParams.set("verification_token",e),/^https?:\/\//i.test(s)?r.toString():`${r.pathname}${r.search}${r.hash}`}catch{const n=s.includes("?")?"&":"?";return`${s}${n}verification_token=${encodeURIComponent(e)}`}}const ZE=async s=>{if(typeof window>"u")return{status:"cancelled"};if(s.render_hint==="external_redirect"||s.redirect_url)return At.redirectExternally(s);const e=window.G7Core;if(!e?.dispatch)return console.error("[IdentityGuardInterceptor] defaultLauncher: G7Core 가 초기화되지 않아 본인인증 흐름을 시작할 수 없습니다."),{status:"failed",failureCode:"G7_NOT_READY"};try{await e.dispatch({handler:"toast",params:{message:"본인 확인이 필요합니다.",variant:"warning"}})}catch{}const n=window.location.href,r={return_url:n,payload:s,stashed_at:Date.now()};try{window.sessionStorage?.setItem(_f,JSON.stringify(r))}catch{}const i=`/identity/challenge?return=${encodeURIComponent(n)}`;try{await e.dispatch({handler:"navigate",params:{path:i}})}catch(l){wc.warn("navigate 실패 — window.location 으로 폴백",l),window.location.href=i}return new Promise(()=>{})},dy=pt("ApiClient");function Af(){return typeof window<"u"&&window.__G7_DEVTOOLS__?window.__G7_DEVTOOLS__:null}class e_{constructor(e={}){M(this,"client");M(this,"config");M(this,"TOKEN_KEY","auth_token");this.config={baseURL:e.baseURL||"/api",timeout:e.timeout||3e4,onTokenExpired:e.onTokenExpired,onUnauthorized:e.onUnauthorized,onError:e.onError},this.client=Qt.create({baseURL:this.config.baseURL,timeout:this.config.timeout,headers:{"Content-Type":"application/json",Accept:"application/json"},paramsSerializer:{serialize:n=>{const r=[];for(const[i,l]of Object.entries(n))if(l!=null)if(Array.isArray(l)){const c=i.endsWith("[]")?i:`${i}[]`;for(const d of l)r.push(`${encodeURIComponent(c)}=${encodeURIComponent(String(d))}`)}else r.push(`${encodeURIComponent(i)}=${encodeURIComponent(String(l))}`);return r.join("&")}}}),this.setupInterceptors()}setToken(e){if(typeof e!="string"||e===""){dy.warn("Ignoring invalid auth token — token must be a non-empty string.");return}typeof window<"u"&&localStorage.setItem(this.TOKEN_KEY,e)}getToken(){return typeof window<"u"?localStorage.getItem(this.TOKEN_KEY):null}removeToken(){typeof window<"u"&&localStorage.removeItem(this.TOKEN_KEY)}setupInterceptors(){this.client.interceptors.request.use(e=>{e.url?.startsWith("/api")&&e.baseURL==="/api"&&(e.baseURL="");const n=this.getToken();if(n&&e.headers&&!this.isCrossOriginRequest(e.url)&&(e.headers.Authorization=`Bearer ${n}`),typeof window<"u"&&e.headers){const i=localStorage.getItem("g7_locale");i&&(e.headers["Accept-Language"]=i)}const r=Af();if(r?.isEnabled()){const i=this.buildFullUrl(e),l=(e.method||"GET").toUpperCase(),c=r.trackRequest(i,l,{requestBody:e.data});e._devToolsRequestId=c}return e},e=>Promise.reject(e)),this.client.interceptors.response.use(e=>{const n=e.config,r=n._devToolsRequestId;if(r){const i=Af();i?.isEnabled()&&i.completeRequest(r,e.status,e.data),n._devToolsRequestId=null}return e},async e=>{const n=e.config,r=n?.url||"",l=["/auth/","/layouts/"].some(c=>r.includes(c));if(e.response?.status===401&&!n?._retry&&!l){if(n._retry=!0,await Xn.getInstance().refreshToken()){const f=this.getToken();return f&&(n.headers.Authorization=`Bearer ${f}`),n._devToolsRequestId=null,this.client(n)}return this.completeDevToolsRequest(n,e),this.removeToken(),this.config.onUnauthorized&&this.config.onUnauthorized(),Promise.reject(e)}if(e.response?.status===401&&l)return this.completeDevToolsRequest(n,e),Promise.reject(e);if(At.isIdentityRequired(e.response?.status,e.response?.data)){this.completeDevToolsRequest(n,e);const c={},d=n?.headers??{};for(const[v,S]of Object.entries(d))typeof S=="string"&&(c[v]=S);let f;n?.data!==void 0&&n?.data!==null&&(f=typeof n.data=="string"?n.data:JSON.stringify(n.data),c["Content-Type"]=c["Content-Type"]??"application/json");const h=n?.identity_target,m=await At.handle(e.response.data,{headers:c,body:f,credentials:"same-origin"},h);if(!m)return Promise.reject(e);let y=null;try{y=await m.json()}catch{y=null}return m.ok?{...e.response,status:m.status,data:y}:Promise.reject(Object.assign(new Error("replay failed"),{response:{status:m.status,data:y}}))}return e.response?.status===403?(this.completeDevToolsRequest(n,e),this.callOnError(e),Promise.reject(e)):(this.completeDevToolsRequest(n,e),e.response&&this.callOnError(e),Promise.reject(e))})}async get(e,n){return(await this.client.get(e,n)).data}async post(e,n,r){return(await this.client.post(e,n,r)).data}async put(e,n,r){return(await this.client.put(e,n,r)).data}async patch(e,n,r){return(await this.client.patch(e,n,r)).data}async delete(e,n){return(await this.client.delete(e,n)).data}getInstance(){return this.client}setOnUnauthorized(e){this.config.onUnauthorized=e}setOnError(e){this.config.onError=e}completeDevToolsRequest(e,n){if(!e?._devToolsRequestId)return;const r=Af();if(!r?.isEnabled())return;const i=e._devToolsRequestId;e._devToolsRequestId=null,n.response?r.completeRequest(i,n.response.status,n.response.data):n.request?r.failRequest(i,n.message||"Network error"):r.failRequest(i,n.message||"Request error")}isCrossOriginRequest(e){if(!e||typeof window>"u"||!window.location||!/^(https?:)?\/\//i.test(e))return!1;try{return new URL(e,window.location.href).origin!==window.location.origin}catch{return!1}}buildFullUrl(e){let n=e.url||"";if(e.baseURL&&!n.startsWith("http")&&(n=`${e.baseURL}${n.startsWith("/")?"":"/"}${n}`),e.params&&typeof e.params=="object"){const r=e.params,i=[];for(const[l,c]of Object.entries(r))if(c!=null)if(Array.isArray(c)){const d=l.endsWith("[]")?l:`${l}[]`;for(const f of c)i.push(`${encodeURIComponent(d)}=${encodeURIComponent(String(f))}`)}else i.push(`${encodeURIComponent(l)}=${encodeURIComponent(String(c))}`);i.length>0&&(n+=(n.includes("?")?"&":"?")+i.join("&"))}return n}callOnError(e){if(!this.config.onError)return;const n={status:e.response?.status||0,message:e.response?.data?.message||e.message||"Unknown error",data:e.response?.data,statusText:e.response?.statusText};try{this.config.onError(n)}catch(r){dy.error("Error in onError handler:",r)}}}let xf=null;function Yn(){return xf||(xf=new e_),xf}const $o=pt("AuthManager");function ki(s,e,n,r){try{window.G7Core?.devTools?.trackAuthEvent?.(s,e,n,r)}catch{}}const fy={admin:{type:"admin",loginPath:"/admin/login",defaultPath:"/admin",userEndpoint:"/admin/auth/user",loginEndpoint:"/auth/admin/login",logoutEndpoint:"/admin/auth/logout",refreshEndpoint:"/admin/auth/refresh",twoFactorEndpoint:"/auth/admin/login/two-factor",twoFactorResendEndpoint:"/auth/admin/login/two-factor/resend"},user:{type:"user",loginPath:"/login",defaultPath:"/",userEndpoint:"/auth/user",loginEndpoint:"/auth/login",logoutEndpoint:"/auth/logout",refreshEndpoint:"/auth/refresh",twoFactorEndpoint:"/auth/login/two-factor",twoFactorResendEndpoint:"/auth/login/two-factor/resend"}},Lr=class Lr{constructor(){M(this,"state");M(this,"config");M(this,"isRefreshing",!1);M(this,"refreshPromise",null);M(this,"eventHandlers",new Map);this.state={isAuthenticated:!1,user:null,type:null},this.config=new Map,this.config.set("admin",fy.admin),this.config.set("user",fy.user)}static getInstance(){return Lr.instance||(Lr.instance=new Lr),Lr.instance}on(e,n){this.eventHandlers.has(e)||this.eventHandlers.set(e,[]),this.eventHandlers.get(e).push(n)}emit(e,...n){const r=this.eventHandlers.get(e);r&&r.forEach(i=>i(...n))}isAuthenticated(){return this.state.isAuthenticated}getUser(){return this.state.user}getAuthType(){return this.state.type}async checkAuth(e){const n=Yn();if(!n.getToken())return this.clearState(),!1;const i=this.config.get(e);if(!i)return $o.error(`Unknown auth type: ${e}`),!1;try{const l=await n.get(i.userEndpoint);return l.success&&l.data?(this.state={isAuthenticated:!0,user:l.data,type:e},this.emit("authStateChange",this.state),!0):(this.clearState(),!1)}catch(l){return l.response?.status===401&&await this.refreshToken()?this.checkAuth(e):(this.clearState(),!1)}}async preloadAuth(e){try{return await this.checkAuth(e)}catch(n){return $o.warn(`Preload auth failed for type ${e}:`,n),!1}}async login(e,n,r){const i=Yn(),l=this.config.get(e);if(!l)throw new Error(`Unknown auth type: ${e}`);try{const c=r?.headers?{headers:r.headers}:void 0,d=await i.post(l.loginEndpoint,n,c);if(d.success&&d.data){if(d.data.two_factor_required===!0)return ki("login",!0,void 0,{type:e,two_factor:!0}),{status:"two_factor_required",challenge:{challengeId:String(d.data.challenge_id??""),providerId:String(d.data.provider_id??""),expiresAt:d.data.expires_at??null}};if(d.data.token&&d.data.user)return{status:"authenticated",user:this.establishSession(e,d.data.token,d.data.user)}}throw new Error("Login failed")}catch(c){throw this.clearState(),this.enhanceAuthError(c,e,"Login failed")}}async completeTwoFactor(e,n,r){const i=Yn(),l=this.config.get(e);if(!l)throw new Error(`Unknown auth type: ${e}`);try{const c=r?.headers?{headers:r.headers}:void 0,d=await i.post(l.twoFactorEndpoint,{challenge_id:n.challengeId,code:n.code},c);if(d.success&&d.data?.token&&d.data?.user)return this.establishSession(e,d.data.token,d.data.user);throw new Error("Login failed")}catch(c){throw this.clearState(),this.enhanceAuthError(c,e,"Login failed")}}async resendTwoFactor(e,n,r){const i=Yn(),l=this.config.get(e);if(!l)throw new Error(`Unknown auth type: ${e}`);try{const c=r?.headers?{headers:r.headers}:void 0,d=await i.post(l.twoFactorResendEndpoint,{challenge_id:n.challengeId},c);if(d.success&&d.data?.challenge_id)return{challengeId:String(d.data.challenge_id),providerId:String(d.data.provider_id??""),expiresAt:d.data.expires_at??null};throw new Error("Resend failed")}catch(c){throw this.enhanceAuthError(c,e,"Resend failed")}}establishSession(e,n,r){Yn().setToken(n);const l=r.language,c=localStorage.getItem(Lr.LOCALE_STORAGE_KEY),d=l&&l!==c;if(l)try{localStorage.setItem(Lr.LOCALE_STORAGE_KEY,l)}catch(f){$o.warn("Failed to save user language to localStorage:",f)}return this.state={isAuthenticated:!0,user:r,type:e},this.emit("login",this.state),this.emit("authStateChange",this.state),ki("login",!0,void 0,{userId:r.uuid,email:r.email,type:e}),d&&window.__templateApp&&window.__templateApp.changeLocale(l),r}enhanceAuthError(e,n,r){const i=e?.response?.data?.message,l=new Error(i||e?.message||r);return l.response=e?.response,l.status=e?.response?.status,l.code=e?.code,ki("login",!1,l.message,{type:n,status:e?.response?.status}),l}async logout(){const e=Yn(),n=this.state.type?this.config.get(this.state.type):null;try{n&&await e.post(n.logoutEndpoint)}catch(r){$o.warn("Logout API call failed:",r)}finally{e.removeToken();const r={...this.state};if(this.clearState(),this.emit("logout",r),this.emit("authStateChange",this.state),ki("logout",!0,void 0,{previousType:r.type}),n&&r.type){const i=window.location.pathname+window.location.search;window.location.href=this.getLoginRedirectUrl(r.type,i)}}}async refreshToken(){if(this.isRefreshing&&this.refreshPromise)return this.refreshPromise;this.isRefreshing=!0,this.refreshPromise=this.doRefreshToken();try{return await this.refreshPromise}finally{this.isRefreshing=!1,this.refreshPromise=null}}async doRefreshToken(){const e=this.state.type;if(!e)return!1;const n=this.config.get(e);if(!n)return!1;const r=Yn();try{const i=await r.post(n.refreshEndpoint);return i.success&&i.data?.token?(r.setToken(i.data.token),this.emit("tokenRefreshed"),ki("token-refresh",!0,void 0,{type:e}),!0):(ki("token-refresh",!1,"No token in response",{type:e}),!1)}catch(i){return $o.error("Token refresh failed:",i),ki("token-refresh",!1,i instanceof Error?i.message:"Unknown error",{type:e}),!1}}getLoginRedirectUrl(e,n,r){const i=this.config.get(e);if(!i)return"/login";const l=encodeURIComponent(n);let c=`${i.loginPath}?redirect=${l}`;return r&&(c+=`&reason=${encodeURIComponent(r)}`),c}updateConfig(e,n){if(n.loginPath!==void 0){const i=n.loginPath;if(!i.startsWith("/")||i.startsWith("//"))throw new Error(`AuthManager.updateConfig: loginPath must be a same-origin path starting with '/' (got: ${i})`)}const r=this.config.get(e);r&&this.config.set(e,{...r,...n})}getRedirectUrl(e){const r=this.config.get(e)?.defaultPath||"/",l=new URLSearchParams(window.location.search).get("redirect");if(l)try{const c=decodeURIComponent(l);if(c.startsWith("/"))return c}catch{}return r}getConfig(e){return this.config.get(e)}clearState(){this.state={isAuthenticated:!1,user:null,type:null}}static resetInstance(){Lr.instance=null}};M(Lr,"instance"),M(Lr,"LOCALE_STORAGE_KEY","g7_locale");let Xn=Lr;const hy={401:{handler:"navigate",params:{path:"{{auth.loginPath}}"}},403:{handler:"toast",params:{type:"error",message:"{{error.message}}"}},404:{handler:"toast",params:{type:"error",message:"{{error.message}}"}},422:{handler:"toast",params:{type:"error",message:"{{error.message}}"}},default:{handler:"toast",params:{type:"error",message:"{{error.message}}"}}},sa={findHandler(s,e){if(s){if(s[e])return s[e];if(s[String(e)])return s[String(e)];if(s.default)return s.default}},normalizeOnError(s){return s?Array.isArray(s)?s:[s]:[]},injectErrorCode(s,e,n){if(s.handler!=="showErrorPage"||s.params?.errorCode!==void 0)return s;const r=typeof e=="string"?parseInt(e,10):e,i=isNaN(r)?n:r;return i===void 0?s:{...s,params:{...s.params,errorCode:i}}}},Tr=pt("ErrorHandlingResolver"),Pa=class Pa{constructor(){M(this,"templateErrorHandling",null);M(this,"layoutErrorHandling",null);M(this,"executeAction",null)}static getInstance(){return Pa.instance||(Pa.instance=new Pa),Pa.instance}static resetInstance(){Pa.instance=null}setTemplateConfig(e){this.templateErrorHandling=e,Tr.log("Template config set:",e)}setLayoutConfig(e){this.layoutErrorHandling=e,Tr.log("Layout config set:",e)}setActionExecutor(e){this.executeAction=e}resolve(e,n={}){const{errorHandling:r,onError:i}=n;Tr.log("Resolving error:",e,{hasActionErrorHandling:!!r,hasActionOnError:!!i,hasLayoutConfig:!!this.layoutErrorHandling,hasTemplateConfig:!!this.templateErrorHandling});let l=sa.findHandler(r,e);if(l){const c=this.getMatchedKey(r,e);return{handler:sa.injectErrorCode(l,c,e),level:"action",matchedKey:c}}if(i){const c=sa.normalizeOnError(i);if(c.length>0)return{handler:c.length===1?c[0]:{handler:"sequence",actions:c},level:"action",matchedKey:"onError"}}if(l=sa.findHandler(this.layoutErrorHandling||void 0,e),l){const c=this.getMatchedKey(this.layoutErrorHandling,e);return{handler:sa.injectErrorCode(l,c,e),level:"layout",matchedKey:c}}if(l=sa.findHandler(this.templateErrorHandling||void 0,e),l){const c=this.getMatchedKey(this.templateErrorHandling,e);return{handler:sa.injectErrorCode(l,c,e),level:"template",matchedKey:c}}if(l=sa.findHandler(hy,e),l){const c=this.getMatchedKey(hy,e);return{handler:sa.injectErrorCode(l,c,e),level:"system",matchedKey:c}}return{handler:null,level:null,matchedKey:null}}async execute(e,n){if(!this.executeAction){Tr.error("Action executor is not set");return}Tr.log("Executing handler:",e.handler,{errorContext:n});try{return await this.executeAction(e,{error:n})}catch(r){throw Tr.error("Failed to execute handler:",r),r}}async resolveAndExecute(e,n,r={}){const i=this.resolve(e,r);if(Tr.log("Resolve result:",{errorCode:e,handler:i.handler?.handler,level:i.level,matchedKey:i.matchedKey}),!i.handler)return Tr.warn("No handler found for error:",e),{handled:!1};try{return{handled:!0,result:await this.execute(i.handler,n)}}catch(l){return Tr.error("Handler execution failed:",l),{handled:!1}}}getMatchedKey(e,n){return e[n]?n:e[String(n)]?String(n):"default"}clearLayoutConfig(){this.layoutErrorHandling=null,Tr.log("Layout config cleared")}clearAllConfig(){this.templateErrorHandling=null,this.layoutErrorHandling=null,Tr.log("All config cleared")}getConfigStatus(){return{hasTemplate:this.templateErrorHandling!==null,hasLayout:this.layoutErrorHandling!==null,hasExecutor:this.executeAction!==null}}};M(Pa,"instance",null);let Tf=Pa;function kr(){return Tf.getInstance()}function Mo(s,e){const n={...s};for(const r of Object.keys(e))r in n?n[r]!==null&&typeof n[r]=="object"&&!Array.isArray(n[r])&&e[r]!==null&&typeof e[r]=="object"&&!Array.isArray(e[r])&&(n[r]=Mo(n[r],e[r])):n[r]=e[r];return n}var kf={exports:{}},No={};/** + * @license React + * react-jsx-runtime.production.js + * + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */var py;function t_(){if(py)return No;py=1;var s=Symbol.for("react.transitional.element"),e=Symbol.for("react.fragment");function n(r,i,l){var c=null;if(l!==void 0&&(c=""+l),i.key!==void 0&&(c=""+i.key),"key"in i){l={};for(var d in i)d!=="key"&&(l[d]=i[d])}else l=i;return i=l.ref,{$$typeof:s,type:r,key:c,ref:i!==void 0?i:null,props:l}}return No.Fragment=e,No.jsx=n,No.jsxs=n,No}var gy;function n_(){return gy||(gy=1,kf.exports=t_()),kf.exports}var yt=n_();const r_=na({__proto__:null,default:ra(yt)},[yt]),Rf=pt("ParentContextProvider"),my=N.createContext(null);let Cc=null;function Df(){Cc?(Rf.log("[triggerModalParentUpdate] 모달 부모 컨텍스트 업데이트 트리거"),Cc()):Rf.warn("[triggerModalParentUpdate] Provider가 아직 마운트되지 않음")}const yy=({children:s})=>{const[e,n]=N.useState(0),r=N.useRef(!0),i=N.useCallback(()=>{const c=window.__g7LayoutContextStack||[];return c[c.length-1]?.dataContext},[]);N.useEffect(()=>(r.current=!0,Cc=()=>{r.current&&(n(c=>c+1),Rf.log("[ParentContextProvider] 버전 업데이트됨"))},()=>{r.current=!1,Cc=null}),[]);const l={version:e,getParentDataContext:i};return yt.jsx(my.Provider,{value:l,children:s})};function a_(){return N.useContext(my)}function by(s){return s.replace(/[\t\n\r]/g,"").replace(/\\/g,"/").replace(/^([a-z][a-z0-9+.\-]*:)?\/{2,}/i,"$1//")}function vy(s,e){try{const n=typeof window<"u"&&window.location?.protocol||"https:",r=e||(typeof window<"u"?window.location?.origin:void 0)||"https://localhost",i=s.startsWith("//")?`${n}${s}`:s,l=new URL(i,r);return l.protocol!=="http:"&&l.protocol!=="https:"?null:l.hostname.toLowerCase()}catch{return null}}function bs(){const s=typeof window<"u"?window.G7Config?.trustedScriptHosts:void 0;return Array.isArray(s)?s.map(e=>String(e).toLowerCase()):[]}function Io(s,e){if(typeof s!="string")return!1;const n=s.trim();if(n==="")return!1;const r=by(n),i=r.startsWith("//"),l=/^[a-z][a-z0-9+.-]*:/i.test(r);if(!i&&!l&&r.startsWith("/"))return!0;const c=vy(r),d=e??bs();return c!==null&&d.map(f=>String(f).toLowerCase()).includes(c)}const I=pt("ActionDispatcher");function i_(s,e,n,r){return n||(Kg(s)||zd(s)?"$t:core.errors.network_request_failed":r||`Failed to execute action: ${e}`)}const Sy=new Set(["navigate","navigateBack","navigateForward","replaceUrl","refresh","logout"]),s_=new Set(["redirect"]);function Jn(){try{return window.G7Core?.devTools}catch{return}}class It extends Error{constructor(n,r,i){super(n);M(this,"unknownHandler",!1);this.action=r,this.originalError=i,this.name="ActionError"}}const wy={click:"onClick",change:"onChange",input:"onInput",submit:"onSubmit",focus:"onFocus",blur:"onBlur",keydown:"onKeyDown",keyup:"onKeyUp",keypress:"onKeyPress",mousedown:"onMouseDown",mouseup:"onMouseUp",mouseenter:"onMouseEnter",mouseleave:"onMouseLeave",scroll:"onScroll",dragstart:"onDragStart",drag:"onDrag",dragend:"onDragEnd",dragenter:"onDragEnter",dragover:"onDragOver",dragleave:"onDragLeave",drop:"onDrop"},cn=class cn{constructor(e={},n,r){M(this,"bindingEngine");M(this,"translationEngine");M(this,"translationContext");M(this,"customHandlers",new Map);M(this,"defaultContext");M(this,"globalStateUpdater");M(this,"errorHandlingSetup",!1);M(this,"debounceTimers",new Map);M(this,"pendingDebounceFlushers",new Map);M(this,"debounceAccumulatedValues",new Map);M(this,"globalHeaders",[]);M(this,"namedActions",{});M(this,"previewMode",!1);M(this,"localMirrorDepth",0);M(this,"intervals",new Map);this.bindingEngine=new $n,this.translationEngine=n,this.translationContext=r,this.defaultContext=e,this.registerDefaultHandlers(),this.setupErrorHandling()}setGlobalHeaders(e){this.globalHeaders=e||[]}setNamedActions(e){this.namedActions=e||{};const n=Jn();n?.isEnabled?.()&&n.setNamedActionDefinitions?.(this.namedActions),I.log("[setNamedActions] registered:",Object.keys(this.namedActions))}getNamedActions(){return this.namedActions}setPreviewMode(e){this.previewMode=e,I.log("Preview mode:",e?"enabled":"disabled")}isPreviewMode(){return this.previewMode}static getPreviewSuppressedHandlers(){return Sy}static getPreviewSuppressedLayoutFeatures(){return s_}resolveActionRef(e){if(!e.actionRef)return e;const n=this.namedActions[e.actionRef];if(!n)return I.warn(`[resolveActionRef] named action not found: "${e.actionRef}"`),e;const r=Jn();r?.isEnabled?.()&&r.trackNamedActionRef?.({actionRefName:e.actionRef,resolvedHandler:n.handler,timestamp:Date.now()});const{actionRef:i,type:l,key:c,event:d,...f}=e,h={...n,...Object.fromEntries(Object.entries(f).filter(([,m])=>m!==void 0)),type:l??n.type};return c!==void 0&&(h.key=c),d!==void 0&&(h.event=d),h}matchesPattern(e,n){if(n==="*")return!0;const r=n.replace(/[.+?^${}()|[\]\\]/g,"\\$&").replace(/\*/g,".*");return new RegExp(`^${r}$`).test(e)}getMatchingGlobalHeaders(e,n){const r={};for(const i of this.globalHeaders)this.matchesPattern(e,i.pattern)&&Object.entries(i.headers).forEach(([l,c])=>{const d=ps(c,n,{skipCache:!0});d!=null&&d!==""&&(r[l]=String(d))});return r}setupErrorHandling(){if(this.errorHandlingSetup)return;kr().setActionExecutor(async(n,r)=>{const i={type:"click",handler:n.handler,target:n.target,params:n.params,actions:n.actions},l={data:{...this.defaultContext.data,error:r.error}};return await this.dispatchAction(i,l)}),this.errorHandlingSetup=!0}registerDefaultHandlers(){this.registerHandler("refetchDataSource",async(n,r)=>{const i=n.params?.dataSourceId,l=n.params?.sync;if(!i){I.warn("refetchDataSource: dataSourceId is required");return}if(typeof window<"u"&&window.G7Core?.dataSource?.refetch){const c=window.G7Core?.state?.get?.()||{},d={...r.state?._global,...c},f=window.G7Core?.state?.getLocal?.()||{},h=r.data?._local||r.state||{},m={...f,...h},y=r.isolatedContext?.state;I.log("[refetchDataSource] localStateOverride:",m),await window.G7Core.dataSource.refetch(i,{...l?{sync:!0}:{},...d?{globalStateOverride:d}:{},...m?{localStateOverride:m}:{},...y?{isolatedStateOverride:y}:{}})}else I.warn("refetchDataSource: G7Core.dataSource.refetch is not available")}),this.registerHandler("appendDataSource",async(n,r)=>{const{dataSourceId:i,dataPath:l,newData:c}=n.params||{};if(!i){I.warn("appendDataSource: dataSourceId is required");return}if(c===void 0){I.warn("appendDataSource: newData is required");return}if(typeof window<"u"&&window.G7Core?.dataSource?.updateData){if(!Array.isArray(c)){I.warn("appendDataSource: newData must be an array, got:",typeof c,c);return}await window.G7Core.dataSource.updateData(i,l||null,c,"append")}else I.warn("appendDataSource: G7Core.dataSource.updateData is not available")}),this.registerHandler("updateDataSource",async(n,r)=>{const{dataSourceId:i,data:l,merge:c=!1}=n.params||{};if(!i){I.warn("updateDataSource: dataSourceId is required");return}if(l===void 0){I.warn("updateDataSource: data is required");return}typeof window<"u"&&window.G7Core?.dataSource?.set?(I.log(`[updateDataSource] Updating dataSource '${i}' with:`,l),window.G7Core.dataSource.set(i,l,{merge:c})):I.warn("updateDataSource: G7Core.dataSource.set is not available")}),this.registerHandler("scrollIntoView",async(n,r)=>{const{selector:i,behavior:l="smooth",block:c="nearest",inline:d="nearest",waitForElement:f=!1,timeout:h=2e3,delay:m=0,retryCount:y=0,retryInterval:v=50,scrollContainer:S}=n.params||{};if(!i){I.warn("scrollIntoView: selector is required");return}if(typeof window>"u"||typeof document>"u"){I.warn("scrollIntoView: window/document is not available");return}let _=null;if(f){if(_=await new Promise(x=>{const A=document.querySelector(i);if(A){x(A);return}const D=setTimeout(()=>{O.disconnect(),x(null)},h),O=new MutationObserver(()=>{const k=document.querySelector(i);k&&(clearTimeout(D),O.disconnect(),x(k))});O.observe(document.body,{childList:!0,subtree:!0})}),!_){I.warn(`scrollIntoView: element not found for selector "${i}" after ${h}ms timeout`);return}}else{m>0&&await new Promise(A=>setTimeout(A,m)),_=document.querySelector(i);let x=0;for(;!_&&xsetTimeout(A,v)),_=document.querySelector(i),x++;if(!_){I.warn(`scrollIntoView: element not found for selector "${i}" after ${x+1} attempts`);return}}if(S){const x=document.querySelector(S);if(!x){I.warn(`scrollIntoView: container not found for selector "${S}"`);return}const A=_.getBoundingClientRect(),D=x.getBoundingClientRect(),O=A.top-D.top+x.scrollTop,k=O+A.height;let L;switch(c){case"start":L=O;break;case"center":L=O-x.clientHeight/2+A.height/2;break;case"end":L=k-x.clientHeight;break;case"nearest":default:const B=A.topD.bottom;if(B)L=O;else if(q)L=k-x.clientHeight;else return;break}x.scrollTo({top:Math.max(0,L),behavior:l});return}_.scrollIntoView({behavior:l,block:c,inline:d})}),this.registerHandler("reloadExtensions",async(n,r)=>{if(typeof window>"u"){I.warn("reloadExtensions: window is not available");return}const i=window.__templateApp;if(!i){I.warn("reloadExtensions: TemplateApp not initialized");return}if(typeof i.reloadExtensionState=="function")try{await i.reloadExtensionState()}catch(d){throw I.error("reloadExtensions: reloadExtensionState failed",d),d}else I.warn("reloadExtensions: TemplateApp.reloadExtensionState unavailable");const{moduleInfo:l,pluginInfo:c}=n.params||{};if(l)try{await this.executeAction({handler:"reloadModuleHandlers",params:n.params},r)}catch(d){I.error("reloadExtensions: reloadModuleHandlers failed",d)}if(c)try{await this.executeAction({handler:"reloadPluginHandlers",params:n.params},r)}catch(d){I.error("reloadExtensions: reloadPluginHandlers failed",d)}I.log("reloadExtensions: done")}),this.registerHandler("reloadRoutes",async(n,r)=>{if(typeof window>"u"){I.warn("reloadRoutes: window is not available");return}const i=window.__templateApp;if(!i){I.warn("reloadRoutes: TemplateApp not initialized");return}if(typeof i.reloadExtensionState=="function")try{await i.reloadExtensionState(),I.log("reloadRoutes: delegated to reloadExtensionState")}catch(l){throw I.error("reloadRoutes: Failed to reload routes",l),l}else{const l=i.getRouter?.();l&&(await l.loadRoutes(),I.log("reloadRoutes: Routes reloaded (legacy fallback)"))}}),this.registerHandler("refresh",async(n,r)=>{if(typeof window>"u"){I.warn("refresh: window is not available");return}const i=Number(n.params?.delayMs??0);i>0&&await new Promise(l=>setTimeout(l,i)),window.location.reload()}),this.registerHandler("remount",async(n,r)=>{const{componentId:i}=n.params||{};if(!i){I.warn("remount: componentId parameter is required");return}if(!this.globalStateUpdater){I.warn("remount: globalStateUpdater is not set");return}const d=(window.G7Core?.state?.get()||{})._global?._remountKeys||{},f=d[i]||0;this.globalStateUpdater({_remountKeys:{...d,[i]:f+1}}),I.log(`remount: ${i} key incremented to ${f+1}`)}),this.registerHandler("reloadTranslations",async(n,r)=>{if(typeof window>"u"){I.warn("reloadTranslations: window is not available");return}const i=window.__templateApp;if(!i){I.warn("reloadTranslations: TemplateApp not initialized");return}if(typeof i.reloadExtensionState=="function")try{await i.reloadExtensionState(),I.log("reloadTranslations: delegated to reloadExtensionState")}catch(l){throw I.error("reloadTranslations: Failed to reload translations",l),l}}),this.registerHandler("reloadModuleHandlers",async(n,r)=>{if(typeof window>"u"){I.warn("reloadModuleHandlers: window is not available");return}const{moduleInfo:i,action:l}=n.params||{};if(!i){I.warn("reloadModuleHandlers: moduleInfo is required");return}const c=i.data||i;if(!c.identifier){I.warn("reloadModuleHandlers: moduleInfo with identifier is required");return}if(!l||l!=="add"&&l!=="remove"){I.warn('reloadModuleHandlers: action must be "add" or "remove"');return}const d=window.G7Config;if(!d){I.warn("reloadModuleHandlers: G7Config not available");return}const f=c.identifier;try{if(l==="add"){if(c.assets){if(d.moduleAssets=d.moduleAssets||{},d.moduleAssets[f]=c.assets,I.log(`reloadModuleHandlers: Added assets for ${f}`),c.assets.js){const h=c.assets.js,m=`module-${f}`;this.assertAllowedExtensionAssetUrl(h,`module script (${f})`,n),document.getElementById(m)?I.warn(`reloadModuleHandlers: Script ${m} already loaded`):(await _r(h,{id:m},{label:`module-script:${f}`}),I.log(`reloadModuleHandlers: Script loaded successfully for ${f}`))}if(c.assets.css){const h=c.assets.css,m=`module-css-${f}`;this.assertAllowedExtensionAssetUrl(h,`module stylesheet (${f})`,n),document.getElementById(m)||(await ia(h,{id:m},{label:`module-css:${f}`}),I.log(`reloadModuleHandlers: CSS loaded for ${f}`))}}}else if(l==="remove"){d.moduleAssets&&d.moduleAssets[f]&&(delete d.moduleAssets[f],I.log(`reloadModuleHandlers: Removed assets for ${f}`));const h=`module-${f}`,m=document.getElementById(h);m&&(m.remove(),I.log(`reloadModuleHandlers: Removed script for ${f}`));const y=`module-css-${f}`,v=document.getElementById(y);v&&(v.remove(),I.log(`reloadModuleHandlers: Removed CSS for ${f}`))}}catch(h){throw I.error(`reloadModuleHandlers: Failed to ${l} module assets`,h),h}}),this.registerHandler("reloadPluginHandlers",async(n,r)=>{if(typeof window>"u"){I.warn("reloadPluginHandlers: window is not available");return}const{pluginInfo:i,action:l}=n.params||{};if(!i){I.warn("reloadPluginHandlers: pluginInfo is required");return}const c=i.data||i;if(!c.identifier){I.warn("reloadPluginHandlers: pluginInfo with identifier is required");return}if(!l||l!=="add"&&l!=="remove"){I.warn('reloadPluginHandlers: action must be "add" or "remove"');return}const d=window.G7Config;if(!d){I.warn("reloadPluginHandlers: G7Config not available");return}const f=c.identifier;try{if(l==="add"){if(c.assets){if(d.pluginAssets=d.pluginAssets||{},d.pluginAssets[f]=c.assets,I.log(`reloadPluginHandlers: Added assets for ${f}`),c.assets.js){const h=c.assets.js,m=`plugin-${f}`;this.assertAllowedExtensionAssetUrl(h,`plugin script (${f})`,n),document.getElementById(m)?I.warn(`reloadPluginHandlers: Script ${m} already loaded`):(await _r(h,{id:m},{label:`plugin-script:${f}`}),I.log(`reloadPluginHandlers: Script loaded successfully for ${f}`))}if(c.assets.css){const h=c.assets.css,m=`plugin-css-${f}`;this.assertAllowedExtensionAssetUrl(h,`plugin stylesheet (${f})`,n),document.getElementById(m)||(await ia(h,{id:m},{label:`plugin-css:${f}`}),I.log(`reloadPluginHandlers: CSS loaded for ${f}`))}}}else if(l==="remove"){d.pluginAssets&&d.pluginAssets[f]&&(delete d.pluginAssets[f],I.log(`reloadPluginHandlers: Removed assets for ${f}`));const h=`plugin-${f}`,m=document.getElementById(h);m&&(m.remove(),I.log(`reloadPluginHandlers: Removed script for ${f}`));const y=`plugin-css-${f}`,v=document.getElementById(y);v&&(v.remove(),I.log(`reloadPluginHandlers: Removed CSS for ${f}`))}}catch(h){throw I.error(`reloadPluginHandlers: Failed to ${l} plugin assets`,h),h}});let e=!1;this.registerHandler("showErrorPage",async(n,r)=>{if(e){I.log("showErrorPage: Already active, skipping duplicate call");return}if(e=!0,typeof window>"u"){e=!1,I.warn("showErrorPage: window is not available");return}const i=window.__templateApp;if(!i){e=!1,I.warn("showErrorPage: TemplateApp not initialized");return}const l=i.getErrorPageHandler?.();if(!l){e=!1,I.warn("showErrorPage: ErrorPageHandler not available");return}const c=n.params?.errorCode||500,d=n.params?.target||"content";let f=n.params?.containerId;f||(f=d==="full"?"app":"main_content"),I.log("showErrorPage:",{errorCode:c,target:d,containerId:f});const h=(m,y=6e4)=>new Promise((v,S)=>{const _=document.getElementById(m);if(_){v(_);return}I.log(`showErrorPage: Waiting for container #${m}...`);let x;const A=new MutationObserver((D,O)=>{const k=document.getElementById(m);k&&(I.log(`showErrorPage: Container #${m} found`),O.disconnect(),clearTimeout(x),v(k))});A.observe(document.body,{childList:!0,subtree:!0}),x=setTimeout(()=>{A.disconnect(),S(new Error(`Container #${m} not found within ${y}ms`))},y)});try{await h(f);const m=kr(),y=m.layoutErrorHandling;m.clearLayoutConfig();try{await l.renderError(c,f)||I.warn(`showErrorPage: Failed to render error page for code ${c}`)}finally{m.setLayoutConfig(y)}}catch(m){throw I.error("showErrorPage: Error rendering error page:",m),m}finally{e=!1}}),this.registerHandler("emitEvent",async(n,r)=>{const i=n.params?.event,l=n.params?.data;if(!i){I.warn("emitEvent: event parameter is required");return}if(typeof window>"u"){I.warn("emitEvent: window is not available");return}const c=window.G7Core;if(!c?.componentEvent?.emit){I.warn("emitEvent: G7Core.componentEvent is not available");return}try{I.log(`emitEvent: Emitting "${i}"`,l);const d={...l,_context:{data:r.data,state:r.state}},f=await c.componentEvent.emit(i,d);if(!f||f.length===0?I.warn(`emitEvent: No listeners found for "${i}"`):I.log(`emitEvent: Event "${i}" completed with ${f.length} listener(s)`,f),this.globalStateUpdater){const h=(c?.state?.get()||{})._local||{},m={event:i,success:!0,data:f.length===1?f[0]:f,listeners:f.length};this.globalStateUpdater({_local:{...h,_eventResult:m}});const y=r.state&&typeof r.state=="object"&&!Array.isArray(r.state)?r.state:h;window.__g7SequenceLocalSync={...y,_eventResult:m}}}catch(d){if(I.error(`emitEvent: Event "${i}" failed`,d),this.globalStateUpdater){const f=c?.state?.get()||{};this.globalStateUpdater({_local:{...f._local,_eventResult:{event:i,success:!1,error:d instanceof Error?d.message:String(d)}}})}throw d}}),this.registerHandler("updateProductField",async(n,r)=>{const{productId:i,field:l,value:c}=n.params||{};if(!i||!l){I.warn("updateProductField: productId and field are required");return}if(typeof window>"u"){I.warn("updateProductField: window is not available");return}const d=window.G7Core;if(!d?.state?.get||!this.globalStateUpdater){I.warn("updateProductField: G7Core.state or globalStateUpdater is not available");return}try{const f=d.state.get()||{},m=(f.products?.data?.data||[]).map(v=>v.id===i?{...v,[l]:c,_modified:!0}:v),y=new Set(f._local?.modifiedProductIds||[]);y.add(i),this.globalStateUpdater({products:{...f.products,data:{...f.products?.data,data:m}},_local:{...f._local,modifiedProductIds:Array.from(y)}}),I.log(`updateProductField: Updated product ${i}, field: ${l}, value:`,c)}catch(f){throw I.error("updateProductField: Error updating product field",f),f}}),this.registerHandler("updateOptionField",async(n,r)=>{const{productId:i,optionId:l,field:c,value:d}=n.params||{};if(!i||!l||!c){I.warn("updateOptionField: productId, optionId and field are required");return}if(typeof window>"u"){I.warn("updateOptionField: window is not available");return}const f=window.G7Core;if(!f?.state?.get||!this.globalStateUpdater){I.warn("updateOptionField: G7Core.state or globalStateUpdater is not available");return}try{const h=f.state.get()||{},y=(h.products?.data?.data||[]).map(S=>{if(S.id===i&&S.options){const _=S.options.map(x=>x.id===l?{...x,[c]:d,_modified:!0}:x);return{...S,options:_,_modified:!0}}return S}),v=new Set(h._local?.modifiedProductIds||[]);v.add(i),this.globalStateUpdater({products:{...h.products,data:{...h.products?.data,data:y}},_local:{...h._local,modifiedProductIds:Array.from(v)}}),I.log(`updateOptionField: Updated product ${i} option ${l}, field: ${c}, value:`,d)}catch(h){throw I.error("updateOptionField: Error updating option field",h),h}}),this.registerHandler("setLocale",async(n,r)=>{const i=n.target;if(!i||typeof i!="string"){I.warn("setLocale: Invalid locale:",i);return}const l=window.__templateApp;if(l&&typeof l.changeLocale=="function")try{await l.changeLocale(i),I.log("setLocale: Locale changed to",i)}catch(c){I.error("setLocale: Failed to change locale:",c),window.location.reload()}else{I.warn("setLocale: TemplateApp not found, falling back to page reload");try{localStorage.setItem("g7_locale",i)}catch{}window.location.reload()}}),this.registerHandler("suppress",async()=>{I.log("suppress: Error intentionally suppressed")}),this.registerBuiltInHandlerMetadata()}registerBuiltInHandlerMetadata(){const e=Jn();if(!e?.isEnabled())return;const n=[{name:"refetchDataSource",description:"데이터 소스를 다시 fetch합니다"},{name:"appendDataSource",description:"데이터 소스에 새 데이터를 병합합니다 (무한 스크롤용)"},{name:"updateDataSource",description:"API 응답으로 데이터 소스를 직접 업데이트합니다 (refetch 대체)"},{name:"reloadRoutes",description:"라우트를 다시 로드합니다"},{name:"refresh",description:"현재 페이지를 새로고침합니다"},{name:"remount",description:"컴포넌트를 리마운트합니다"},{name:"reloadTranslations",description:"다국어 파일을 다시 로드합니다"},{name:"showErrorPage",description:"에러 페이지를 표시합니다"},{name:"emitEvent",description:"이벤트를 발생시킵니다"},{name:"updateProductField",description:"상품 필드를 인라인 수정합니다"},{name:"updateOptionField",description:"상품 옵션 필드를 인라인 수정합니다"},{name:"setLocale",description:"언어를 변경합니다 (DB 저장 + UI 리렌더링)"}];for(const r of n)e.trackHandlerRegistration(r.name,"built-in",r.description)}createHandler(e,n,r){I.log("createHandler called for:",e.handler,e.type);const i=window.__g7DevTools,l=Date.now(),c=r?.state?{...r.state}:null,d=`handler_${e.handler}_${l}`;return async f=>{if(I.log("Handler invoked for:",e.handler,"event:",f.type),i?.isEnabled?.()&&c){const m=window.G7Core?.state?.getLocal?.()??r?.state??{},y=Date.now()-l;for(const v of Object.keys(c)){const S=c[v],_=m[v];S!==_&&y>100&&(i.trackStaleClosureWarning?.({type:"event-handler-stale",location:`createHandler(${e.handler})`,capturedPath:`_local.${v}`,capturedValue:S,capturedAt:l,currentValue:_,actionId:d,stackTrace:new Error().stack}),I.warn(`[Stale Closure] _local.${v} changed after handler creation:`,`captured="${S}" → current="${_}" (${y}ms ago)`))}}try{f.type!=="change"&&f.preventDefault();let h={};if(f.type==="submit"&&f.target instanceof HTMLFormElement){const v=f.target;new FormData(v).forEach((_,x)=>{h[x]=_})}const m={...this.defaultContext,data:{...n,form:h,_local:r?.state||{},$event:f},event:f,...r&&{state:r.state,setState:r.setState},isolatedContext:r?.isolatedContext};if(e.confirm){let v=this.resolveValue(e.confirm,m.data);if(this.translationEngine&&this.translationContext&&v.startsWith("$t:")&&(v=this.translationEngine.resolveTranslations(v,this.translationContext,m.data)),!confirm(v))return}const y=window.__g7ActionContext;window.__g7ActionContext={...r,data:m.data};try{await this.executeAction(e,m)}finally{window.__g7ActionContext=y}}catch(h){if(I.error("Action execution failed:",h),e.onError){const m=h instanceof It&&h.originalError?h.originalError:h,y={...this.defaultContext,data:{...n,error:m,_local:r?.state||{},$event:f},event:f,...r&&{state:r.state,setState:r.setState}},v=Array.isArray(e.onError)?e.onError:[e.onError];for(const S of v)await this.executeAction(S,y)}}}}generateActionId(e,n){const r=e.handler,i=n||"no-target",l=Date.now(),c=Math.random().toString(36).substring(2,9);return`${r}_${i.replace(/[^a-zA-Z0-9]/g,"_")}_${l}_${c}`}async executeAction(e,n){if(e=this.resolveActionRef(e),typeof e.handler=="string"&&e.handler.includes("{{")){const f=this.evaluateExpression(e.handler,n.data);e={...e,handler:f==null?"":String(f)}}const r=Jn(),i=r?.isEnabled()?`action_${Date.now()}_${Math.random().toString(36).substring(2,11)}`:void 0,l=performance.now();let c;if(i&&r&&r.logAction({id:i,type:e.handler,params:this.sanitizeForDevTools(e.params),context:this.sanitizeForDevTools({hasState:!!n.state,hasSetState:!!n.setState,dataKeys:n.data?Object.keys(n.data):[]}),startTime:l,status:"started"}),e.if!==void 0)try{const f=this.resolveValue(e.if,n.data),h=f===!0||f==="true"||f&&f!=="false"&&f!=="0"&&f!==!1;if(I.log("[executeAction] if condition result:",e.handler,h),!h)return i&&r&&r.logAction({id:i,type:e.handler,startTime:l,endTime:performance.now(),duration:performance.now()-l,status:"success",result:{skipped:!0,reason:"if condition false"}}),{success:!0,data:void 0}}catch(f){return I.error("[executeAction] if condition error:",e.handler,f),i&&r&&r.logAction({id:i,type:e.handler,startTime:l,endTime:performance.now(),duration:performance.now()-l,status:"error",error:{name:"ConditionEvaluationError",message:f instanceof Error?f.message:String(f),stack:f instanceof Error?f.stack:void 0}}),{success:!0,data:void 0}}let d;try{const f=this.resolveParams(e.params,n.data);i&&(c=this.sanitizeForDevTools(f));const h=e.target?this.resolveValue(e.target,n.data):void 0;if(e.handler==="apiCall"&&n.setState){d=this.generateActionId(e,h);const y=n.state?.loadingActions||{};n.setState({loadingActions:{...y,[d]:!0}})}if(this.previewMode&&Sy.has(e.handler))return I.warn(`Preview mode: "${e.handler}" suppressed`,h||f),i&&r&&r.logAction({id:i,type:e.handler,startTime:l,endTime:performance.now(),duration:performance.now()-l,status:"skipped",metadata:{reason:"preview_mode_suppressed"}}),{success:!0,data:void 0};let m;switch(e.handler){case"ensureIdentityVerified":m=await this.handleEnsureIdentityVerified(f);break;case"resolveIdentityChallenge":m=this.handleResolveIdentityChallenge(f);break;case"navigate":const y=f.path||h;m=await this.handleNavigate(y,f,n);break;case"navigateBack":m=await this.handleNavigateBack();break;case"navigateForward":m=await this.handleNavigateForward();break;case"openWindow":{const _=f.path||h;m=await this.handleOpenWindow(_,f);break}case"replaceUrl":{const _=f.path||h||window.location.pathname;m=await this.handleReplaceUrl(_,f);break}case"apiCall":if(e.onSuccess&&r?.isEnabled()&&i){const _=window.G7Core,x={};if(_?.state?.get){const A=_.state.get();x._global=A}n.state&&(x._local=n.state),r.registerStateCaptureForHandler?.(i,["_global","_local"],x)}const v=e.auth_mode??(e.auth_required?"required":"none"),S=e.identity_target?this.resolveParams(e.identity_target,n.data):void 0;m=await this.handleApiCall(h,f,n,v,S);break;case"login":m=await this.handleLogin(h,f,n);break;case"loginTwoFactor":m=await this.handleLoginTwoFactor(h,f,n);break;case"loginTwoFactorResend":m=await this.handleLoginTwoFactorResend(h,f,n);break;case"logout":m=await this.handleLogout(h,n);break;case"setState":I.log("[executeAction] setState resolvedParams:",f),m=await this.handleSetState(e.render!==void 0?{...f,__render:e.render}:f,n);break;case"setError":m=await this.handleSetError(h,f,n);break;case"openModal":m=await this.handleOpenModal(h,n);break;case"closeModal":m=await this.handleCloseModal(n);break;case"showAlert":m=await this.handleShowAlert(h,n);break;case"toast":m=await this.handleToast(f,n);break;case"switch":m=await this.handleSwitch(e,n);break;case"conditions":m=await this.handleConditions(e,n);break;case"sequence":m=await this.handleSequence(e,n);break;case"parallel":m=await this.handleParallel(e,n);break;case"startInterval":m=this.handleStartInterval(f,n);break;case"stopInterval":m=this.handleStopInterval(f);break;case"loadScript":m=await this.handleLoadScript(f,e,n);break;case"callExternal":m=await this.handleCallExternal(f,e,n);break;case"callExternalEmbed":m=await this.handleCallExternalEmbed(f,e,n);break;case"saveToLocalStorage":m=await this.handleSaveToLocalStorage(f,n);break;case"loadFromLocalStorage":m=await this.handleLoadFromLocalStorage(f,n);break;default:m=await this.handleCustomAction({...e,target:h,params:f},n);break}if(e.resultTo&&m!==void 0){const{target:y,key:v,merge:S}=e.resultTo,_=this.resolveValue(v,n.data),x=S==="replace"?"replace":S==="shallow"?"shallow":"deep";if(y==="_local"&&n.setState){const A=this.buildNestedUpdate(_,m),D=x!=="deep"?{...A,__mergeMode:x}:A;this.writeLocalState(n,D),I.log(`[resultTo] Saved to _local.${_} (merge=${x}):`,m)}else if(y==="_local"&&this.globalStateUpdater){const O=(window.G7Core?.state?.get()||{})._local||{},k=this.buildNestedUpdate(_,m);let L;x==="replace"?L=k:x==="shallow"?L={...O,...k}:L=this.deepMergeWithState(k,O),this.globalStateUpdater({_local:L}),I.log(`[resultTo] Saved to _local.${_} via globalStateUpdater (merge=${x}):`,m)}else if(y==="_global"&&this.globalStateUpdater){const A=this.buildNestedUpdate(_,m);this.globalStateUpdater(A),I.log(`[resultTo] Saved to _global.${_}:`,m)}else if(y==="_isolated"&&n.isolatedContext){const A=this.buildNestedUpdate(_,m);n.isolatedContext.mergeState(A,x),I.log(`[resultTo] Saved to _isolated.${_} (merge=${x}):`,m)}else I.warn(`[resultTo] Cannot save result: target=${y}, setState=${!!n.setState}, globalStateUpdater=${!!this.globalStateUpdater}, isolatedContext=${!!n.isolatedContext}`)}if(e.onSuccess){if(r?.isEnabled()&&i&&e.handler==="apiCall"){const S=window.G7Core,_={};S?.state?.get&&(_._global=S.state.get()),n.state&&(_._local=n.state),r.detectStaleClosure?.(i,`${e.handler} → onSuccess`,_,"callback-state-capture",i)}const y={...n,data:{...n.data,result:m,response:m}},v=Array.isArray(e.onSuccess)?e.onSuccess:[e.onSuccess];v.length>1?await this.handleSequence({handler:"sequence",type:"click",actions:v},y):v.length===1&&await this.executeAction(v[0],y)}return i&&r&&r.logAction({id:i,type:e.handler,params:this.sanitizeForDevTools(e.params),resolvedParams:c,startTime:l,endTime:performance.now(),duration:performance.now()-l,status:"success",result:this.sanitizeForDevTools(m)}),{success:!0,data:m}}catch(f){const h=f instanceof It?f:new It(`Failed to execute action: ${e.handler}`,e,f instanceof Error?f:void 0),m=h.originalError?.response||{},y=m.data||{},v=h.originalError?.status||m.status||500;let S=i_(h.originalError??f,e.handler,y.message,h.message);this.translationEngine&&this.translationContext&&S.startsWith("$t:")&&(S=this.translationEngine.resolveTranslations(S,this.translationContext));const _={status:v,message:S,errors:y.errors||m.errors,data:y,statusText:h.originalError?.statusText,error_code:y.error_code??m.error_code};if(h.unknownHandler)throw I.warn(`Unknown action handler "${e.handler}" — skipped (extension not loaded?). Not surfaced to the user.`),h;const A=kr().resolve(v,{errorHandling:e.errorHandling,onError:e.onError});if(A.handler){if(r?.isEnabled()&&i&&e.handler==="apiCall"){const O=window.G7Core,k={};O?.state?.get&&(k._global=O.state.get()),n.state&&(k._local=n.state),r.detectStaleClosure?.(i,`${e.handler} → onError`,k,"callback-state-capture",i)}const D={...n,data:{...n.data,error:_}};try{const O={type:"click",handler:A.handler.handler,target:A.handler.target,params:A.handler.params,actions:A.handler.actions};return await this.executeAction(O,D),{success:!1,error:h}}catch(O){return I.error("Error executing error handler:",O),{success:!1,error:h}}}throw i&&r&&r.logAction({id:i,type:e.handler,params:this.sanitizeForDevTools(e.params),resolvedParams:c,startTime:l,endTime:performance.now(),duration:performance.now()-l,status:"error",error:{name:h.name,message:h.message,stack:h.stack}}),h}finally{if(e.handler==="apiCall"&&n.setState&&d){const f=n.state?.loadingActions||{},{[d]:h,...m}=f;n.setState({loadingActions:m})}}}async handleNavigate(e,n,r){let i=e;if(n.query||n.mergeQuery===!0)if(n.mergeQuery===!0)i=this.buildMergedQueryPath(e,n.query??{});else{const c=new URLSearchParams;for(const[f,h]of Object.entries(n.query))if(h!=null&&h!=="")if(Array.isArray(h)){const m=f.endsWith("[]")?f:`${f}[]`;for(const y of h)y!=null&&y!==""&&c.append(m,String(y))}else c.set(f,String(h));const d=c.toString();d&&(i=`${e}?${d}`)}if(I.log("handleNavigate:",{target:e,params:n,finalPath:i,replace:n.replace,windowLocationSearch:window.location.search}),n.replace===!0){const c=window.G7Core;if(c?.updateQueryParams){const d=typeof n.transition_overlay_target=="string"?n.transition_overlay_target:void 0;await c.updateQueryParams(i,d?{transitionOverlayTarget:d}:void 0),I.log("handleNavigate: Used updateQueryParams for replace mode",d?{transitionOverlayTarget:d}:void 0),this.applyScrollOption(n.scroll,n.scrollBehavior,"top");return}I.warn("handleNavigate: G7Core.updateQueryParams not available, falling back to React Router")}const l=this.resolveNavigateFallbackAction(i,n);if(l){I.warn(`handleNavigate: No route matched, falling back to "${l.handler}"`),await this.dispatchAction({type:"click",handler:l.handler,params:l.params},r),this.applyScrollOption(n.scroll,n.scrollBehavior,"top");return}if(!r.navigate)throw new It("Navigate function is not provided in context");r.navigate(i,{replace:n.replace===!0}),this.applyScrollOption(n.scroll,n.scrollBehavior,"top")}resolveNavigateFallbackAction(e,n){const r=n.fallback;if(r===!1||n.replace===!0)return null;const l=window.__templateApp?.getRouter?.();if(!l||typeof l.match!="function"||typeof l.getRoutes=="function"&&l.getRoutes().length===0)return null;const c=e.split("?")[0];return l.match(c)?null:this.resolveNavigateFallback(r,e,n)}resolveNavigateFallback(e,n,r){return e==null?{handler:"openWindow",params:{path:n,target:"_self"}}:typeof e=="string"?{handler:e,params:{path:n}}:typeof e=="object"&&typeof e.handler=="string"?{handler:e.handler,params:{path:n,...e.params||{}}}:(I.warn("resolveNavigateFallback: unrecognized fallback option, using openWindow",e),{handler:"openWindow",params:{path:n,target:"_blank"}})}applyScrollOption(e,n,r){const i=e===void 0?r:e;if(i==="preserve")return;const l=n==="smooth"?"smooth":"instant",c=y=>{if(typeof y!="object"||y===null)return!1;const v=y;return"container"in v||"to"in v||"block"in v||"offset"in v},d=(y,v,S=0)=>{y===window?window.scrollTo({top:v,left:S,behavior:l}):y.scrollTo({top:v,left:S,behavior:l})},f=(y,v,S,_)=>{if(v===window){y.scrollIntoView({behavior:l,block:S}),_&&window.scrollBy({top:-_,left:0,behavior:l});return}const x=v,A=y.getBoundingClientRect(),D=x.getBoundingClientRect(),O=x.scrollTop+(A.top-D.top);let k;S==="center"?k=O-(x.clientHeight-y.clientHeight)/2:S==="end"?k=O-(x.clientHeight-y.clientHeight):k=O,k-=_,x.scrollTo({top:Math.max(0,k),left:0,behavior:l})},h=()=>{window.scrollTo({top:0,left:0,behavior:l});const y=document.getElementById("app")??document.body;if(!y)return;y.querySelectorAll("*").forEach(S=>{if(S.scrollTop===0&&S.scrollLeft===0)return;const _=window.getComputedStyle(S),x=_.overflowY,A=_.overflowX;(x==="auto"||x==="scroll"||A==="auto"||A==="scroll")&&S.scrollTo({top:0,left:0,behavior:l})})},m=()=>{try{if(c(i)){const y=i,v=y.block??"start",S=typeof y.offset=="number"?y.offset:0;let _=window;if(typeof y.container=="string"&&y.container.length>0){const A=document.querySelector(y.container);if(!A){I.warn(`applyScrollOption: container not found: ${y.container}`);return}_=A}const x=y.to??"top";if(x==="top"){_===window?h():d(_,0);return}if(typeof x=="number"){d(_,x-S);return}if(typeof x=="object"&&x!==null&&("x"in x||"y"in x)){const{x:A=0,y:D=0}=x;d(_,D-S,A);return}if(typeof x=="string"&&(x.startsWith("#")||x.startsWith("."))){const A=document.querySelector(x);A?f(A,_,v,S):I.warn(`applyScrollOption: target element not found: ${x}`);return}I.warn('applyScrollOption: invalid "to" value in extended form',x);return}if(i==="top"){h();return}if(typeof i=="number"){window.scrollTo({top:i,left:0,behavior:l});return}if(typeof i=="object"&&i!==null&&("x"in i||"y"in i)){const{x:y=0,y:v=0}=i;window.scrollTo({top:v,left:y,behavior:l});return}if(typeof i=="string"&&(i.startsWith("#")||i.startsWith("."))){const y=document.querySelector(i);y&&y.scrollIntoView({behavior:l,block:"start"});return}}catch(y){I.warn("applyScrollOption: failed to apply scroll",y)}};typeof window.requestAnimationFrame=="function"?window.requestAnimationFrame(m):m()}async handleOpenWindow(e,n){let r=e;if(n.query){const l=new URLSearchParams;for(const[d,f]of Object.entries(n.query))f!=null&&f!==""&&l.set(d,String(f));const c=l.toString();c&&(r=`${e}?${c}`)}const i=n.target==="_self"?"_self":"_blank";I.log("handleOpenWindow:",{target:e,params:n,finalPath:r,windowTarget:i}),i==="_self"?window.location.assign(r):window.open(r,"_blank")}async handleNavigateBack(){I.log("handleNavigateBack"),window.history.back()}async handleNavigateForward(){I.log("handleNavigateForward"),window.history.forward()}async handleReplaceUrl(e,n){let r=e;if(n.query||n.mergeQuery===!0)if(n.mergeQuery===!0)r=this.buildMergedQueryPath(e,n.query??{});else{const i=new URLSearchParams;for(const[c,d]of Object.entries(n.query))if(d!=null&&d!=="")if(Array.isArray(d)){const f=c.endsWith("[]")?c:`${c}[]`;for(const h of d)h!=null&&h!==""&&i.append(f,String(h))}else i.set(c,String(d));const l=i.toString();l&&(r=`${e}?${l}`)}I.log("handleReplaceUrl:",{target:e,params:n,finalPath:r}),window.history.replaceState(null,"",r),this.applyScrollOption(n.scroll,n.scrollBehavior,"preserve")}buildMergedQueryPath(e,n){const r=new URLSearchParams(window.location.search);for(const[c,d]of Object.entries(n))if(d==null||d==="")r.delete(c);else if(Array.isArray(d)){const f=c.endsWith("[]")?c:`${c}[]`;r.delete(c),r.delete(f);for(const h of d)h!=null&&h!==""&&r.append(f,String(h))}else r.set(c,String(d));const i=r.toString(),l=e.split("?")[0];return i?`${l}?${i}`:l}async ensureCsrfToken(){try{await fetch("/sanctum/csrf-cookie",{credentials:"include"})}catch(e){throw new It("Failed to fetch CSRF token",void 0,e instanceof Error?e:void 0)}}getCsrfTokenFromCookie(){const r=`; ${document.cookie}`.split("; XSRF-TOKEN=");if(r.length===2){const i=r.pop()?.split(";").shift();return i?decodeURIComponent(i):null}return null}buildQueryString(e){const n=new URLSearchParams;for(const[r,i]of Object.entries(e))if(!(i==null||i===""))if(Array.isArray(i))for(const l of i)l!=null&&l!==""&&n.append(`${r}[]`,String(l));else typeof i=="object"?n.append(r,JSON.stringify(i)):n.append(r,String(i));return n.toString()}async handleEnsureIdentityVerified(e){const n=typeof e.purpose=="string"?e.purpose:"sensitive_action",r=e.target&&typeof e.target=="object"?e.target:void 0;return await At.handle({success:!1,error_code:"identity_verification_required",message:"",verification:{policy_key:e.policy_key??"",purpose:n,provider_id:e.provider_id??null,render_hint:e.render_hint??null,return_request:null}},void 0,r)!==null}handleResolveIdentityChallenge(e){const n=typeof e.result=="string"?e.result:"cancelled";let r;switch(n){case"verified":{const i=typeof e.token=="string"?e.token:"";if(!i){I.warn("resolveIdentityChallenge: result=verified 인데 token 이 비어있습니다. failed 로 강등합니다."),r={status:"failed",failureCode:"MISSING_TOKEN"};break}r={status:"verified",token:i,providerData:e.providerData&&typeof e.providerData=="object"?e.providerData:void 0};break}case"pending":{const i=typeof e.pollUrl=="string"?e.pollUrl:"",l=typeof e.expiresAt=="string"?e.expiresAt:"";if(!i||!l){I.warn("resolveIdentityChallenge: result=pending 인데 pollUrl/expiresAt 누락 — failed 로 강등합니다."),r={status:"failed",failureCode:"MALFORMED_PENDING"};break}r={status:"pending",pollUrl:i,pollIntervalMs:typeof e.pollIntervalMs=="number"?e.pollIntervalMs:void 0,expiresAt:l};break}case"failed":r={status:"failed",failureCode:typeof e.failureCode=="string"?e.failureCode:"UNKNOWN",reason:typeof e.reason=="string"?e.reason:void 0};break;case"cancelled":default:r={status:"cancelled"};break}return At.resolveDeferred(r),!0}async handleApiCall(e,n,r,i="none",l){const{method:c="GET",body:d,headers:f,contentType:h}=n;c!=="GET"&&c!=="HEAD"&&await this.ensureCsrfToken();const m=this.getCsrfTokenFromCookie();let y={};if(i==="required"||i==="optional"){const W=Yn().getToken();W&&(y={Authorization:`Bearer ${W}`})}let v=e;const S=n.query||(c==="GET"?d:null);if(S&&typeof S=="object"){const z=this.buildQueryString(S);if(z){const W=e.includes("?")?"&":"?";v=`${e}${W}${z}`}}const _=window.G7Core?.state?.getGlobal?.()||r.state?._global||{},x=window.G7Core?.state?.getLocal?.()||r.state?._local||{},A={_global:_,_local:x},D=this.getMatchingGlobalHeaders(e,A),O={};if(typeof window<"u"){const z=localStorage.getItem("g7_locale");z&&(O["Accept-Language"]=z)}const k=h==="multipart/form-data",L={method:c,headers:{...k?{}:{"Content-Type":"application/json"},Accept:"application/json",...O,...m&&{"X-XSRF-TOKEN":m},...y,...D,...f},credentials:"include"};if(d&&c!=="GET")if(k){const z=new FormData;for(const[W,de]of Object.entries(d))de instanceof File||de instanceof Blob?z.append(W,de):de!=null&&z.append(W,typeof de=="object"?JSON.stringify(de):String(de));L.body=z}else L.body=JSON.stringify(d);const B=Jn();let q=null;B?.isEnabled()&&(q=B.trackRequest(v,c));try{let z=await fetch(v,L),W;try{W=await z.json()}catch{W=null}if(At.isIdentityRequired(z.status,W)){const de=await At.handle(W,{body:L.body,headers:L.headers,credentials:L.credentials},l);if(de){z=de;try{W=await z.json()}catch{W=null}}}if(q&&B?.isEnabled()&&(B.completeRequest(q,z.status,W),q=null),!z.ok){const de=W||{},_e=new Error(de.message||`API call failed: ${z.statusText}`);throw _e.response=de,_e.status=z.status,_e.statusText=z.statusText,new It(de.message||`API call failed: ${z.statusText}`,void 0,_e)}return W}catch(z){throw q&&B?.isEnabled()&&B.failRequest(q,z instanceof Error?z.message:String(z)),z}}async handleLogin(e,n,r){const{body:i}=n;if(!i||!i.email||!i.password)throw new It("Login requires email and password in body params");const l=this.resolveAuthType(e),c=l==="admin"?"/api/auth/admin/login":"/api/auth/login",d=Xn.getInstance();try{const f=await d.login(l,{email:i.email,password:i.password},this.buildAuthRequestOptions(c,r));return f.status==="two_factor_required"?{user:null,two_factor_required:!0,challenge_id:f.challenge.challengeId,provider_id:f.challenge.providerId,expires_at:f.challenge.expiresAt}:{user:f.user,two_factor_required:!1}}catch(f){throw this.toLoginActionError(f,"Login failed")}}async handleLoginTwoFactor(e,n,r){const{body:i}=n;if(!i||!i.challenge_id||!i.code)throw new It("loginTwoFactor requires challenge_id and code in body params");const l=this.resolveAuthType(e),c=l==="admin"?"/api/auth/admin/login/two-factor":"/api/auth/login/two-factor";try{return{user:await Xn.getInstance().completeTwoFactor(l,{challengeId:String(i.challenge_id),code:String(i.code)},this.buildAuthRequestOptions(c,r))}}catch(d){throw this.toLoginActionError(d,"Login failed")}}async handleLoginTwoFactorResend(e,n,r){const{body:i}=n;if(!i||!i.challenge_id)throw new It("loginTwoFactorResend requires challenge_id in body params");const l=this.resolveAuthType(e),c=l==="admin"?"/api/auth/admin/login/two-factor/resend":"/api/auth/login/two-factor/resend";try{const d=await Xn.getInstance().resendTwoFactor(l,{challengeId:String(i.challenge_id)},this.buildAuthRequestOptions(c,r));return{two_factor_required:!0,challenge_id:d.challengeId,provider_id:d.providerId,expires_at:d.expiresAt}}catch(d){throw this.toLoginActionError(d,"Resend failed")}}resolveAuthType(e){return e==="user"?"user":"admin"}buildAuthRequestOptions(e,n){const r=window.G7Core?.state?.getGlobal?.()||n.state?._global||{},i=window.G7Core?.state?.getLocal?.()||n.state?._local||{},l={_global:r,_local:i},c=this.getMatchingGlobalHeaders(e,l);return Object.keys(c).length>0?{headers:c}:void 0}toLoginActionError(e,n){const r=e?.message||n,i=new Error(r);return i.response=e?.response?.data||{},i.status=e?.status||e?.response?.status||500,i.code=e?.code,new It(r,void 0,i)}async handleLogout(e,n){await Xn.getInstance().logout()}async handleSetState(e,n){I.log("[handleSetState] START, params:",e);const{target:r="component",scope:i,merge:l,__render:c,...d}=e,f=Jn(),h=window.G7Core;if(typeof r=="string"&&(r.startsWith("$parent.")||r.startsWith("$root.")))return this.handleParentScopeSetState(r,d,l,n);const m=n.state&&Object.keys(n.state).length>0?n.state:h?.state?.get()||{},y=r==="global"?"_global":r.startsWith("_local.")?r:"_local",v=y==="_global"?m._global:y.startsWith("_local.")?this.getNestedProperty(m._local||{},y.slice(7)):m._local,S=f?.isEnabled()?f.startStateChange(y,v,d,{actionId:n.actionId,handlerType:"setState",source:`target=${r}`}):void 0,_=l==="replace"?"replace":l==="shallow"?"shallow":"deep",x=_!=="deep"?{...d,__mergeMode:_,...S?{__setStateId:S}:{}}:{...d,...S?{__setStateId:S}:{}};if(r==="global"){if(!this.globalStateUpdater){I.warn("Global state updater is not set"),S&&f&&f.completeStateChange(S);return}const A=h?.state?.get()||{},{__setStateId:D,__mergeMode:O,...k}=x,L=_==="deep"?this.deepMergeWithState(k,A):k,B={...L,...S?{__setStateId:S}:{},..._!=="deep"?{__mergeMode:_}:{}};return I.log("setState global:",B),this.globalStateUpdater(B,{render:c}),S&&f&&setTimeout(()=>f.completeStateChange(S),0),{__target:"global",..._!=="deep"?{__mergeMode:_}:{},...L}}else if(r==="isolated"){const A=n.isolatedContext;if(A){const{__mergeMode:D,__setStateId:O,...k}=x;return A.mergeState(k,_),I.log("[handleSetState] isolated state updated:",k,"mergeMode:",_),S&&f&&setTimeout(()=>f.completeStateChange(S),0),{__target:"isolated",...k}}else if(I.warn('[handleSetState] isolated target used but no IsolatedStateContext found. Make sure the component has "isolatedState" attribute. Falling back to local state.'),n.setState){const O=window.__g7PendingLocalState||n.state||{},k=_==="deep"?this.deepMergeWithState(x,O):x;return this.writeLocalState(n,k),S&&f&&setTimeout(()=>f.completeStateChange(S),0),k}else throw S&&f&&f.completeStateChange(S),new It("isolated target used but no IsolatedStateContext found and no setState function in context")}else if(r==="local"||r==="component"||r==="_local"){if(i==="parent"||i==="root"){const D=window.__g7LayoutContextStack||[];if(D.length>0){const O=i==="parent"?D[D.length-1]:D[0];if(O?.setState){const{__mergeMode:k,__setStateId:L,...B}=x,q=_==="deep"?this.deepMergeWithState(B,O.state||{}):B;return I.log(`[handleSetState] scope=${i}: 타겟 컨텍스트에 상태 업데이트`,q),O.setState(q),S&&f&&setTimeout(()=>f.completeStateChange(S),0),q}}I.warn(`[handleSetState] scope=${i}: 레이아웃 컨텍스트 스택이 비어있습니다. current로 폴백합니다.`)}const A=n.setState&&!n._isDispatchFallbackContext;if(I.log("[handleSetState] isRealComponentContext:",A,"context.setState:",!!n.setState,"_isDispatchFallbackContext:",n._isDispatchFallbackContext),A){I.log("[handleSetState] Using COMPONENT setState path"),I.log("[handleSetState] resolvedPayload:",x),I.log("[handleSetState] context.state:",n.state),I.log("[handleSetState] mergeMode:",_);const O=window.__g7PendingLocalState||n.state||{},k=_==="deep"?this.deepMergeWithState(x,{}):x;I.log("[handleSetState] convertedPayload (변경 필드만, dot notation 변환):",k),n.setState(k);const L=_==="deep"?this.deepMergeWithState(x,O):_==="shallow"?{...n.state||{},...x}:x,{__mergeMode:B,__setStateId:q,...z}=L;window.__g7PendingLocalState=z,I.log("[handleSetState] __g7PendingLocalState updated:",z);const W=(window.__g7LayoutContextStack||[]).length;let de;if(this.globalStateUpdater){const ze=W===0?window.__templateApp?.getGlobalState?.()?._local:void 0;if(_!=="replace"&&!!ze&&typeof ze=="object"&&!Array.isArray(ze)){const ue=_==="deep"?this.deepMergeWithState(x,ze):{...ze,...x},{__mergeMode:De,__setStateId:ee,...me}=ue;de=me}else _!=="replace"&&I.log("[handleSetState] canonical _local 미사용 → 전체 스냅샷 폴백",{modalDepth:W,hasTemplateApp:!!window.__templateApp});const Q=de?Mo(de,z):z;this.globalStateUpdater({_local:Q},{render:!1}),I.log("[handleSetState] _global._local synced (render:false):",Q)}const{__mergeMode:_e,__setStateId:Se,...je}=x;if(_==="replace")window.__g7ForcedLocalFields=je;else{const ze=window.__g7ForcedLocalFields||{};window.__g7ForcedLocalFields=this.deepMergeWithState(je,ze)}return I.log("[handleSetState] __g7ForcedLocalFields updated:",je),S&&f&&setTimeout(()=>f.completeStateChange(S),0),{__target:"local",...de?Mo(de,z):L,..._!=="deep"?{__mergeMode:_}:{}}}else if(this.globalStateUpdater){I.log("[handleSetState] Using GLOBAL STATE UPDATER path for _local");const O=window.__g7PendingLocalState||m._local||{};I.log("[handleSetState] currentLocal (with pending):",O);const k=_==="deep"?this.deepMergeWithState(x,O):x;return I.log("[handleSetState] finalLocal (merged):",k),this.globalStateUpdater({_local:k},{render:c}),window.__g7PendingLocalState=k,I.log("[handleSetState] __g7PendingLocalState updated for globalStateUpdater path"),S&&f&&setTimeout(()=>f.completeStateChange(S),0),{__target:"local",...k}}else throw S&&f&&f.completeStateChange(S),new It("setState function is not provided in context and globalStateUpdater is not set")}else if(r.startsWith("_local.")){const A=r.slice(7),{__mergeMode:D,__setStateId:O,...k}=x,L=k.value!==void 0?k.value:Object.values(k)[0];if(n.setState){const q=window.__g7PendingLocalState||n.state||{},z=this.createNestedUpdate(A,L,q),W={...q,...z};if(n.setState(z),this.globalStateUpdater&&i!=="parent"&&i!=="root"){const de=window.__templateApp?.getGlobalState?.()?._local,_e=de&&typeof de=="object"?{...de,...z}:W;this.globalStateUpdater({_local:_e},{render:!1}),I.log("[handleSetState] _global._local synced for dot notation (render:false):",_e)}return S&&f&&setTimeout(()=>f.completeStateChange(S),0),W}else throw S&&f&&f.completeStateChange(S),new It("setState function is not provided in context")}else{if(!n.setState)throw S&&f&&f.completeStateChange(S),new It("setState function is not provided in context");const D=window.__g7PendingLocalState||n.state||{},O=_==="deep"?this.deepMergeWithState(x,D):x;return this.writeLocalState(n,O,void 0,{scope:i}),S&&f&&setTimeout(()=>f.completeStateChange(S),0),O}}async handleParentScopeSetState(e,n,r,i){const l=Jn(),c=window.G7Core,d=r==="replace"?"replace":r==="shallow"?"shallow":"deep",f=e.startsWith("$parent."),h=f?"$parent.":"$root.",m=e.slice(h.length);let y,v;if(m==="_local"||m==="_global")y=m,v=void 0;else if(m.startsWith("_local."))y="_local",v=m.slice(7);else if(m.startsWith("_global."))y="_global",v=m.slice(8);else{I.warn(`[handleParentScopeSetState] 지원하지 않는 타겟 형식: ${e}. _local 또는 _global으로 시작해야 합니다.`);return}const S=window.__g7LayoutContextStack||[];if(S.length===0){I.warn(`[handleParentScopeSetState] 레이아웃 컨텍스트 스택이 비어있습니다. target=${e}`);return}const _=f?S[S.length-1]:S[0];if(!_){I.warn(`[handleParentScopeSetState] 타겟 컨텍스트를 찾을 수 없습니다. target=${e}`);return}const x=y==="_global"?_.state?._global:_.state?._local,A=l?.isEnabled()?l.startStateChange(e,x,n,{actionId:i.actionId,handlerType:"setState",source:`target=${e}`}):void 0,{__mergeMode:D,__setStateId:O,...k}=n;if(y==="_global"){if(!this.globalStateUpdater){I.warn("[handleParentScopeSetState] globalStateUpdater가 설정되지 않았습니다."),A&&l&&l.completeStateChange(A);return}const L=c?.state?.get()||{};if(v){const B=k.value!==void 0?k.value:Object.values(k)[0],q=this.createNestedUpdate(v,B,L),z={...L,...q};I.log(`[handleParentScopeSetState] ${e}: 전역 상태 중첩 경로 업데이트`,z),this.globalStateUpdater(z)}else{const B=d==="deep"?this.deepMergeWithState(k,L):k;I.log(`[handleParentScopeSetState] ${e}: 전역 상태 업데이트 (mergeMode=${d})`,B),this.globalStateUpdater(d!=="deep"?{...B,__mergeMode:d}:B)}return A&&l&&setTimeout(()=>l.completeStateChange(A),0),{__target:e,...k}}else{if(!_.setState){I.warn(`[handleParentScopeSetState] 타겟 컨텍스트에 setState가 없습니다. target=${e}`),A&&l&&l.completeStateChange(A);return}const L=_.state||{};if(v){const B=k.value!==void 0?k.value:Object.values(k)[0],q=this.createNestedUpdate(v,B,L),z={...L,...q};return I.log(`[handleParentScopeSetState] ${e}: 로컬 상태 중첩 경로 업데이트`,q),_.setState(q),_.state=z,_.dataContext&&(_.dataContext._local=z),Df(),A&&l&&setTimeout(()=>l.completeStateChange(A),0),z}else{const B=d!=="deep"?{...k,__mergeMode:d}:k,q=d==="replace"?k:d==="shallow"?{...L,...k}:this.deepMergeWithState(k,L);return I.log(`[handleParentScopeSetState] ${e}: 로컬 상태 업데이트 (mergeMode=${d})`,q),_.setState(B),_.state=q,_.dataContext&&(_.dataContext._local=q),Df(),A&&l&&setTimeout(()=>l.completeStateChange(A),0),q}}}deepMergeWithState(e,n){const r=["errors"];let i=e;if("..."in e){const c=e["..."];if(c&&typeof c=="object"&&!Array.isArray(c)){const{"...":d,...f}=e;i={...c,...f}}else{const{"...":d,...f}=e;i=f}}const l={...n};for(const[c,d]of Object.entries(i)){if(r.includes(c)){l[c]=d;continue}if(c.includes(".")&&c!=="..."){const f=this.createNestedUpdate(c,d,l);this.deepMergeInto(l,f,l);continue}if(d!==null&&typeof d=="object"&&!Array.isArray(d)&&Object.getPrototypeOf(d)!==Object.prototype&&Object.getPrototypeOf(d)!==null){l[c]=d;continue}d!==null&&typeof d=="object"&&!Array.isArray(d)&&n[c]!==null&&typeof n[c]=="object"&&!Array.isArray(n[c])?l[c]=this.deepMergeWithState(d,n[c]):l[c]=d}return l}deepMergeInto(e,n,r){for(const[i,l]of Object.entries(n)){if(l===null){e[i]=l;continue}if(e[i]===null){e[i]=l;continue}typeof l=="object"&&!Array.isArray(l)&&e[i]!==void 0&&e[i]!==null&&typeof e[i]=="object"&&!Array.isArray(e[i])?(e[i]===r[i]&&(e[i]={...e[i]}),this.deepMergeInto(e[i],l,r[i]||{})):typeof l=="object"&&!Array.isArray(l)&&e[i]===void 0&&r[i]!==null&&typeof r[i]=="object"&&!Array.isArray(r[i])?e[i]={...r[i],...l}:e[i]=l}}createNestedUpdate(e,n,r){const i=e.split(".");if(i.length===1)return{[i[0]]:n};const l={};let c=l,d=r;for(let f=0;f{this.globalStateUpdater({_local:S})};m.push({state:y,setState:v,dataContext:{_local:y||n.data?._local,_global:l?n.data?._global:n.data?._global||i?.state?.get?.(),_computed:n.data?._computed}}),window.__g7LayoutContextStack=m,I.log(`[handleOpenModal] 레이아웃 컨텍스트 스택에 push, 스택 크기: ${m.length}, state:`,y,l?"(direct)":"(fallback via G7Core)")}else I.warn(`[handleOpenModal] 컨텍스트 스택에 push 실패 - setState: ${!!n.setState}, state: ${n.state!==void 0}, G7Core: ${!!i?.state?.getLocal}`);const f=[...r,e];this.globalStateUpdater({modalStack:f,activeModal:e});const h=Jn();if(h?.isEnabled()){const m=r.length>0?r[r.length-1]:void 0;h.trackModalOpen?.({modalId:e,modalName:e,scopeType:"isolated",parentModalId:m,initialState:n.state?{...n.state}:{}})}}async handleCloseModal(e){if(!this.globalStateUpdater){I.warn("Global state updater is not set for closeModal");return}const n=e?.data?._global?.modalStack||[],r=n.length>0?n[n.length-1]:null,i=n.slice(0,-1),l=i.length>0?i[i.length-1]:null,c=window.__g7LayoutContextStack||[];if(c.length>0){const d=c[c.length-1];d&&(d.dataContext=void 0),c.pop(),window.__g7LayoutContextStack=c,I.log(`[handleCloseModal] 레이아웃 컨텍스트 스택에서 pop, 스택 크기: ${c.length}`)}if(r){const d=Jn();d?.isEnabled()&&d.trackModalClose?.(r,e?.state)}this.globalStateUpdater({modalStack:i,activeModal:l})}async handleShowAlert(e,n){let r=e;this.translationEngine&&this.translationContext&&e.startsWith("$t:")&&(r=this.translationEngine.resolveTranslations(e,this.translationContext,n.data)),alert(r)}async handleToast(e,n){const{type:r="info",message:i,icon:l,duration:c}=e;if(r==="error"){const f=n.data?.error;if((f?.error_code==="identity_verification_required"||f?.data?.error_code==="identity_verification_required")&&At.consumeDomainNoticeShown()){I.log("[Toast] IDV 도메인 안내 표출됨 — 중복 가드 토스트 1건 skip");return}}let d=i;if(this.translationEngine&&this.translationContext&&i?.startsWith("$t:")&&(d=this.translationEngine.resolveTranslations(i,this.translationContext,n.data)),this.globalStateUpdater){const h={id:`toast_${Date.now()}_${Math.random().toString(36).substring(2,9)}`,type:r,message:d,...l&&{icon:l},...c&&{duration:c}},S=[...window.G7Core?.state?.get()?.toasts||[],h];this.globalStateUpdater({toasts:S})}else I.log(`[Toast ${r}] ${d}`)}async handleSwitch(e,n){const{cases:r}=e;if(!r){I.warn("switch handler requires cases property");return}const i=this.resolveParams(e.params,n.data);let l;if(i?.value!==void 0?(l=i.value,I.log("switch handler: using params.value as case key:",l)):(l=n.data?.$args?.[0],I.log("switch handler: using $args[0] as case key:",l)),l==null){if(r.default)return I.log("switch handler: case key is undefined, using default case"),await this.executeAction(r.default,n);I.warn("switch handler: case key is not defined and no default case");return}const c=String(l);let d=r[c];if(!d&&r.default&&(I.log(`switch handler: no case found for key "${c}", using default case`),d=r.default),!d){I.warn(`switch handler: no case found for key "${c}" and no default case`);return}return await this.executeAction(d,n)}async handleConditions(e,n){const{conditions:r}=e;if(!r||!Array.isArray(r)){I.warn("conditions handler requires conditions array");return}const i=n.data||{},l=um(r,i,this.bindingEngine);if(!l.matched){I.log("conditions handler: no matching branch found");return}const c=r[l.branchIndex];if(I.log(`conditions handler: matched branch ${l.branchIndex}`),!c.then){I.warn('conditions handler: matched branch has no "then" action');return}return Array.isArray(c.then)?await this.handleSequence({...e,actions:c.then},n):await this.executeAction(c.then,n)}handleStartInterval(e,n){const r=typeof e.id=="string"?e.id:"",i=Number(e.intervalMs??1e3),l=Array.isArray(e.actions)?e.actions:[];if(r===""||!l.length||i<=0)return I.warn("startInterval requires non-empty id, positive intervalMs, and actions array"),{success:!1};const c=this.intervals.get(r);c&&(clearInterval(c),this.intervals.delete(r));const d=setInterval(()=>{for(const f of l)this.executeAction(f,n).catch(h=>{I.error(`[startInterval:${r}] tick action failed`,h)})},i);return this.intervals.set(r,d),{success:!0,id:r}}handleStopInterval(e){const n=typeof e.id=="string"?e.id:"";if(n==="")return I.warn("stopInterval requires id parameter"),{success:!1};const r=this.intervals.get(n);return r&&(clearInterval(r),this.intervals.delete(n)),{success:!0,id:n}}stopAllIntervals(){for(const e of this.intervals.values())clearInterval(e);this.intervals.clear()}async handleSequence(e,n){const r=e.actions||e.params?.actions;if(!r||!Array.isArray(r)||r.length===0)return I.warn("sequence handler requires non-empty actions array"),[];const i=Jn(),l=i?.isEnabled()&&i.startSequenceExecution?.({eventType:e.type})||"";window.__g7SequenceLocalSync=void 0;const c=[];let d,f=n.state?{...n.state}:{},h=n.isolatedContext?.state?{...n.isolatedContext.state}:{},m=n.data?._computed||{};const y=window.G7Core,v=()=>({_global:y?.state?.get()||{},_local:{...f},_isolated:Object.keys(h).length>0?{...h}:void 0});for(let S=0;S0){const k={},L={...n.data,_local:f,_computed:k,$computed:k,_isolated:h};for(const[B,q]of Object.entries(O))if(typeof q=="string"){const z=q.trim();if(z.startsWith("{{")&&z.endsWith("}}"))try{const W=z.slice(2,-2).trim();k[B]=jn(W)?this.bindingEngine.evaluatePipeExpression(W,L,{skipCache:!0}):this.bindingEngine.evaluateExpression(W,L,{skipCache:!0})}catch{k[B]=m[B]}}m=k,I.log("[handleSequence] _computed recalculated after setState:",m)}}if(_.handler!=="setState"){const O=window.__g7SequenceLocalSync;if(O&&O!==f){f=O,window.__g7SequenceLocalSync=void 0,I.log("[handleSequence] currentState synchronized from __g7SequenceLocalSync after custom handler:",_.handler);const k=n.data?._computedDefinitions;if(k&&Object.keys(k).length>0){const L={},B={...n.data,_local:f,_computed:L,$computed:L,_isolated:h};for(const[q,z]of Object.entries(k))if(typeof z=="string"){const W=z.trim();if(W.startsWith("{{")&&W.endsWith("}}"))try{const de=W.slice(2,-2).trim();L[q]=jn(de)?this.bindingEngine.evaluatePipeExpression(de,B,{skipCache:!0}):this.bindingEngine.evaluateExpression(de,B,{skipCache:!0})}catch{L[q]=m[q]}}m=L,I.log("[handleSequence] _computed recalculated after custom handler:",m)}}}l&&i?.isEnabled()&&i.captureSequenceActionAfter?.(l,S,v(),Date.now()-x,D.data)}catch(D){throw l&&i?.isEnabled()&&(i.captureSequenceActionAfter?.(l,S,v(),Date.now()-x,void 0,D),i.endSequenceExecution?.(l,D)),I.error(`sequence handler: action[${S}] failed:`,D),D}}return window.__g7SequenceLocalSync=void 0,l&&i?.isEnabled()&&i.endSequenceExecution?.(l),c}async handleParallel(e,n){const r=e.actions||e.params?.actions;if(!r||!Array.isArray(r)||r.length===0)return I.warn("parallel handler requires non-empty actions array"),[];I.log(`parallel handler: executing ${r.length} actions in parallel`);const i=n.isolatedContext?{...n.isolatedContext,state:{...n.isolatedContext.state}}:null,l=r.map((f,h)=>{const m={...n,isolatedContext:i};return this.executeAction(f,m).catch(y=>{throw I.error(`parallel handler: action[${h}] failed:`,y),y})}),c=await Promise.allSettled(l),d=c.filter(f=>f.status==="rejected").length;return d>0&&I.warn(`parallel handler: ${d}/${r.length} actions failed`),c}assertAllowedExtensionAssetUrl(e,n,r){if(!Io(e,bs()))throw I.error(`Blocked untrusted ${n} asset url: ${e}`),new It(`Blocked untrusted ${n} url (same-origin path or declared trusted host required): ${e}`,r)}async handleLoadScript(e,n,r){const{src:i,id:l,async:c=!0,defer:d=!1}=e;if(!i)throw new It('loadScript handler requires "src" parameter',n);if(!Io(i,bs()))throw I.warn(`loadScript: blocked untrusted script src (same-origin path or declared trusted host required): ${i}`),new It(`Blocked untrusted script src (same-origin path or declared trusted host required): ${i}`,n);const f=l||`script_${i.replace(/[^a-zA-Z0-9]/g,"_")}`;if(cn.loadedScripts.has(f))return I.log(`loadScript: script already loaded, skipping: ${f}`),n.onLoad&&await this.executeAction(n.onLoad,r),!0;const h=cn.loadingScripts.get(f);if(h)return I.log(`loadScript: joining in-flight load: ${f}`),await h,n.onLoad&&await this.executeAction(n.onLoad,r),!0;if(document.getElementById(f))return I.log(`loadScript: script element already exists: ${f}`),cn.loadedScripts.add(f),n.onLoad&&await this.executeAction(n.onLoad,r),!0;I.log(`loadScript: loading script: ${i}`);const y=document.createElement("script");if(y.id=f,y.src=i,y.async=c,y.defer=d,y.hasAttribute("data-gdpr-blocked-src"))return I.warn(`loadScript: script blocked by consent manager (not loaded): ${i}`),!1;const v=new Promise((S,_)=>{y.onload=()=>{I.log(`loadScript: script loaded successfully: ${f}`),cn.loadedScripts.add(f),cn.loadingScripts.delete(f),S()},y.onerror=x=>{I.error(`loadScript: failed to load script: ${i}`,x),cn.loadingScripts.delete(f),_(new It(`Failed to load script: ${i}`,n))},document.head.appendChild(y)});if(v.catch(()=>{}),cn.loadingScripts.set(f,v),await v,n.onLoad)try{await this.executeAction(n.onLoad,r)}catch(S){I.error("loadScript: onLoad action failed:",S)}return!0}async handleCallExternal(e,n,r){const i=e.constructor,l=e.args||{},c=e.method,d=e.methodArgs||[],f=e.callbackEvent,h=e.embedTarget,m=e.callbackSetState,y=e.callbackAction;if(!i)throw new It('callExternal handler requires "constructor" parameter',n);const v=this.getNestedProperty(window,i);if(!v||typeof v!="function")throw new It(`Constructor not found or not a function: ${i}. Make sure the script is loaded first using loadScript handler.`,n);this.assertCallableExternalConstructor(v,i,n),I.log(`callExternal: calling constructor ${i}`);const S={...l};for(const[x,A]of Object.entries(l))A===!0&&(S[x]=D=>{if(I.log(`callExternal: callback triggered for ${x}`,D),f&&typeof window<"u"&&window.G7Core?.componentEvent&&window.G7Core.componentEvent.emit(f,D),m&&r.setState){const O=B=>{const q={};for(const[z,W]of Object.entries(B)){if(cn.isPrototypePathSegment(z)){I.warn(`callExternal: skipped prototype-polluting mapping key: ${z}`);continue}typeof W=="string"?q[z]=this.getNestedProperty(D,W):typeof W=="object"&&W!==null&&(q[z]=O(W))}return q},k=O(m);I.log("callExternal: callbackSetState mapping result",k);const L=this.deepMergeWithState(k,r.state||{});I.log("callExternal: merged with existing state",L),this.writeLocalState(r,L)}else f&&r.setState&&this.writeLocalState(r,{[`${f.replace(/:/g,"_")}_result`]:D});if(y){const O={...r,data:{...r.data,$event:D}},k=Array.isArray(y)?y:[y];for(const L of k)try{this.executeAction(L,O)}catch(B){I.error("callExternal: callbackAction failed:",B)}}});const _=new v(S);if(c&&typeof _[c]=="function"){if(I.log(`callExternal: calling method ${c}`),c==="embed"&&h){const x=document.querySelector(h);if(x)return _[c](x,...d);throw new It(`Embed target element not found: ${h}`,n)}return _[c](...d)}return _}async handleCallExternalEmbed(e,n,r){const i=e.constructor,l=e.args||{},c=e.callbackSetState,d=e.callbackEvent,f=e.layerClassName,h=e.callbackAction;if(!i)throw new It('callExternalEmbed handler requires "constructor" parameter',n);const m=this.getNestedProperty(window,i);if(!m||typeof m!="function")throw new It(`Constructor not found or not a function: ${i}. Make sure the script is loaded first.`,n);this.assertCallableExternalConstructor(m,i,n),I.log(`callExternalEmbed: creating layer for ${i}`);const{layer:y,closeLayer:v}=this.createEmbedLayer(f),S={...l};for(const[x,A]of Object.entries(l))A===!0&&(S[x]=D=>{if(I.log(`callExternalEmbed: callback triggered for ${x}`,D),v(),d&&typeof window<"u"&&window.G7Core?.componentEvent&&window.G7Core.componentEvent.emit(d,D),c&&r.setState){const O=B=>{const q={};for(const[z,W]of Object.entries(B)){if(cn.isPrototypePathSegment(z)){I.warn(`callExternal: skipped prototype-polluting mapping key: ${z}`);continue}typeof W=="string"?q[z]=this.getNestedProperty(D,W):typeof W=="object"&&W!==null&&(q[z]=O(W))}return q},k=O(c);I.log("callExternalEmbed: callbackSetState mapping result",k);const L=this.deepMergeWithState(k,r.state||{});I.log("callExternalEmbed: merged with existing state",L),this.writeLocalState(r,L)}if(h){const O={...r,data:{...r.data,$event:D}},k=Array.isArray(h)?h:[h];for(const L of k)try{this.executeAction(L,O)}catch(B){I.error("callExternalEmbed: callbackAction failed:",B)}}});const _=new m(S);if(typeof _.embed=="function")_.embed(y),I.log("callExternalEmbed: embedded in layer");else throw v(),new It(`Constructor ${i} does not have embed method`,n);return _}isImeComposing(e){return e?.isComposing===!0||e?.keyCode===229}createEmbedLayer(e){const n=document.createElement("div");n.id="g7-embed-overlay",n.style.cssText=` position: fixed; inset: 0; background-color: rgba(0, 0, 0, 0.5); @@ -50,11 +90,11 @@ Error generating stack: `+u.message+` justify-content: center; color: #666; transition: background-color 0.2s; - `,i.onmouseenter=()=>{i.style.backgroundColor="rgba(0, 0, 0, 0.2)"},i.onmouseleave=()=>{i.style.backgroundColor="rgba(0, 0, 0, 0.1)"};const l=()=>{n.parentNode&&n.parentNode.removeChild(n)};i.onclick=l;const c=d=>{this.isImeComposing(d)||d.key==="Escape"&&(l(),document.removeEventListener("keydown",c))};return document.addEventListener("keydown",c),n.onclick=d=>{d.target===n&&l()},r.appendChild(i),n.appendChild(r),document.body.appendChild(n),{overlay:n,layer:r,closeLayer:l}}static isPrototypePathSegment(e){return cn.PROTOTYPE_PATH_SEGMENTS.includes(e)}getNestedProperty(e,n){return n.split(".").reduce((r,i)=>{if(!cn.isPrototypePathSegment(i))return r&&r[i]!==void 0?r[i]:void 0},e)}assertCallableExternalConstructor(e,n,r){if(typeof window>"u")return;if([window.Function,window.eval,window.setTimeout,window.setInterval].some(l=>l!==void 0&&l===e))throw j.error(`callExternal: blocked arbitrary code execution seam: ${n}`),new jt(`Blocked constructor (arbitrary code execution): ${n}`,r)}async handleSaveToLocalStorage(e,n){const{key:r,value:i}=e;if(!r)return j.error("saveToLocalStorage: key parameter is required"),!1;try{const l=typeof i=="string"?i:JSON.stringify(i);return localStorage.setItem(r,l),j.log(`saveToLocalStorage: saved ${r}`,i),!0}catch(l){return j.error("saveToLocalStorage: failed to save",{key:r,error:l}),!1}}async handleLoadFromLocalStorage(e,n){const{key:r,target:i,stateKey:l,defaultValue:c}=e;if(!r)return j.error("loadFromLocalStorage: key parameter is required"),c;try{const d=localStorage.getItem(r);if(d===null)return j.log(`loadFromLocalStorage: no value found for ${r}, using default`),l&&n.setState&&this.writeLocalState(n,{[l]:c}),c;let f;try{f=JSON.parse(d)}catch{f=d}return j.log(`loadFromLocalStorage: loaded ${r}`,f),l&&n.setState&&this.writeLocalState(n,{[l]:f}),f}catch(d){return j.error("loadFromLocalStorage: failed to load",{key:r,error:d}),c}}writeLocalState(e,n,r,i){const l=r||e.setState;if(!l)return;if(typeof n=="function"){j.warn("[writeLocalState] 함수형 업데이터는 저장소 B 미러 대상이 아닙니다 (payload 추출 불가). 저장소 A 만 갱신합니다."),l(n);return}if(i?.scope==="parent"||i?.scope==="root"){l(n);return}if(this.localMirrorDepth>0||l?.__g7CanonicalWriter){l(n);return}const c=n&&typeof n=="object"?n:{},d=c.__mergeMode;if(d==="replace"){l(n);return}const{__mergeMode:f,__setStateId:h,...m}=c;if("errors"in m||!cn.isMirrorSafePayload(m)){l(n);return}const y=window;if((y.__g7LayoutContextStack||[]).length>0){l(n);return}const v=y.G7Core?.state?.setLocal;if(!y.__templateApp||typeof v!="function"){l(n);return}const S=this.deepMergeWithState(m,{});this.localMirrorDepth++;try{v(S,{render:!1,merge:d??"deep"}),y.__g7ActionContext?.setState!==l&&l(n),d&&(y.__g7ForcedLocalFields={...y.__g7ForcedLocalFields||{},...S})}finally{this.localMirrorDepth--}}static isMirrorSafePayload(e,n=0){if(e===null||typeof e!="object"||n>=8)return!0;if(Array.isArray(e))return e.every(i=>cn.isMirrorSafePayload(i,n+1));const r=Object.getPrototypeOf(e);return r!==Object.prototype&&r!==null?!1:Object.values(e).every(i=>cn.isMirrorSafePayload(i,n+1))}makeLocalSetStateProxy(e){const n=e.setState;if(!n)return;if(n.__g7CanonicalWriter)return n;const r=i=>{this.writeLocalState(e,i,n)};return r.__g7CanonicalWriter=!0,r}async handleCustomAction(e,n){const r=this.customHandlers.get(e.handler);if(!r){if(this.previewMode){j.warn(`[Preview] Unknown action handler "${e.handler}" — skipped (preview mode)`);return}const c=new jt(`Unknown action handler: ${e.handler}`,e);throw c.unknownHandler=!0,c}const i=this.makeLocalSetStateProxy(n),l=i&&i!==n.setState?{...n,setState:i}:n;return await r(e,l)}resolveParams(e,n){if(!e)return{};const r={};for(const[i,l]of Object.entries(e))i.includes("{{")&&j.warn(`[resolveParams] setState params의 키에 표현식이 포함되어 있습니다: "${i}". 키는 해석되지 않습니다. 배열 항목 수정은 .map()/.filter() 패턴을 사용하세요.`),typeof l=="string"&&l.includes("{{")?r[i]=this.evaluateExpression(l,n):typeof l=="string"?r[i]=l:Array.isArray(l)?r[i]=l.map(c=>typeof c=="string"&&c.includes("{{")?this.evaluateExpression(c,n):c):typeof l=="object"&&l!==null&&!Array.isArray(l)&&Object.getPrototypeOf(l)!==Object.prototype&&Object.getPrototypeOf(l)!==null?r[i]=l:typeof l=="object"&&l!==null?r[i]=this.resolveParams(l,n):r[i]=l;return r}resolveValue(e,n){return n?this.bindingEngine.resolveBindings(e,n,{skipCache:!0}):e}evaluateExpressions(e,n){let r={};for(const[i,l]of Object.entries(e))if(i==="..."){let c=l;typeof l=="string"&&l.includes("{{")&&(c=this.evaluateExpression(l,n)),c&&typeof c=="object"&&!Array.isArray(c)&&(r={...r,...c})}else typeof l=="string"&&l.includes("{{")?r[i]=this.evaluateExpression(l,n):typeof l=="object"&&l!==null&&!Array.isArray(l)?r[i]=this.evaluateExpressions(l,n):r[i]=l;return r}evaluateExpressionsIfNeeded(e,n){const r={};for(const[i,l]of Object.entries(e))typeof l=="string"&&l.includes("{{")?r[i]=this.evaluateExpression(l,n):r[i]=l;return r}evaluateExpression(e,n){if(!n)return e;const r=La(e);if(r===null){let c=n;if(e.includes("_global")){const f=window.G7Core?.state?.get()?._global;f&&(c={...n,_global:f})}if(e.includes("_computed")||e.includes("$computed")){const f=window.__g7ActionContext?.computedRef?.current;f&&Object.keys(f).length>0&&(c={...c,_computed:f,$computed:f})}return this.bindingEngine.resolveBindings(e,c,{skipCache:!0})}let i=r;i=i.replace(/\$args\.(\d+)/g,"$args[$1]");let l=n;if(i.includes("_global")){const d=window.G7Core?.state?.get()?._global;d&&(l={...n,_global:d})}if(i.includes("_computed")||i.includes("$computed")){const d=window.__g7ActionContext?.computedRef?.current;d&&Object.keys(d).length>0&&(l={...l,_computed:d,$computed:d})}i.includes("_global.modules")&&(j.log("[evaluateExpression] expression:",i),j.log("[evaluateExpression] dataContext._global:",l._global),j.log("[evaluateExpression] dataContext._global?.modules:",l._global?.modules));try{const c=Wn(i)?this.bindingEngine.evaluatePipeExpression(i,l,{skipCache:!0}):this.bindingEngine.evaluateExpression(i,l);return i.includes("_global.modules")&&j.log("[evaluateExpression] result:",c),typeof c=="string"&&c.startsWith("$t:")&&this.translationEngine&&this.translationContext?this.translationEngine.resolveTranslations(c,this.translationContext,n):c}catch(c){return j.error("Expression evaluation failed:",e,c),e}}setDefaultContext(e){this.defaultContext={...this.defaultContext,...e}}setGlobalStateUpdater(e){this.globalStateUpdater=e}getGlobalStateUpdater(){return this.globalStateUpdater}async dispatchAction(e,n){const r={...this.defaultContext,...n,data:{...this.defaultContext.data,...n?.data}};if(e.confirm){let i=this.resolveValue(e.confirm,r.data);if(this.translationEngine&&this.translationContext&&i.startsWith("$t:")&&(i=this.translationEngine.resolveTranslations(i,this.translationContext,r.data)),!confirm(i))return{success:!1}}try{return await this.executeAction(e,r)}catch(i){if(j.error("dispatchAction failed:",i),e.onError){const l=i instanceof jt&&i.originalError?{message:i.originalError.message,response:i.originalError.response}:{message:i instanceof Error?i.message:String(i)},c={...r,data:{...r.data,error:l}},d=Array.isArray(e.onError)?e.onError:[e.onError];for(const f of d)await this.executeAction(f,c)}return{success:!1,error:i instanceof Error?i:new Error(String(i))}}}registerHandler(e,n,r){this.customHandlers.set(e,n);const i=Yn();i?.isEnabled()&&i.trackHandlerRegistration(e,r?.category??"custom",r?.description,r?.source)}unregisterHandler(e){this.customHandlers.delete(e);const n=Yn();n?.isEnabled()&&n.trackHandlerUnregistration(e)}getRegisteredHandlers(){return Array.from(this.customHandlers.keys())}getHandler(e){return this.customHandlers.get(e)}normalizeDebounceConfig(e){return typeof e=="number"?{delay:e,leading:!1,trailing:!0}:{delay:e.delay,leading:e.leading??!1,trailing:e.trailing??!0}}extractEventData(e){const n=e.target;if(n&&!("tagName"in n)){const l={type:e.type??"custom",target:{value:n.value,name:n.name??""}};return e._changedKeys&&(l._changedKeys=e._changedKeys),l}const i=n?{value:n.value,name:n.name??"",checked:n.checked??!1,type:n.type??"",tagName:n.tagName}:{};if(e.type==="scroll"&&n){const l=n;i.scrollHeight=l.scrollHeight??0,i.scrollTop=l.scrollTop??0,i.clientHeight=l.clientHeight??0,i.scrollLeft=l.scrollLeft??0,i.clientWidth=l.clientWidth??0,i.scrollWidth=l.scrollWidth??0}return{type:e.type,target:n?i:null}}executeDebouncedAction(e,n,r,i,l){const c={...r,$event:n,$args:l},d={type:n.type,target:n.target,preventDefault:()=>{},stopPropagation:()=>{}};this.createHandler(e,c,i)(d)}handleDebouncedAction(e,n,r,i,l,c){const d=this.normalizeDebounceConfig(e.debounce),f=l||`default-${e.handler}-${Date.now()}`,h=this.extractEventData(n),m=h._changedKeys;if(m&&Array.isArray(m)&&h?.target?.value&&typeof h.target.value=="object"&&!Array.isArray(h.target.value)){const _=this.debounceAccumulatedValues.get(f);if(_){const x={..._};for(const A of m)x[A]=h.target.value[A];h.target.value=x}this.debounceAccumulatedValues.set(f,h.target.value)}const y=this.debounceTimers.get(f);y&&(clearTimeout(y),this.debounceTimers.delete(f));const v=Yn();v?.isEnabled()&&v.trackAction({handler:typeof e.handler=="string"?e.handler:String(e.handler),type:e.type,status:"pending",params:e.params,debounce:{delay:d.delay,status:"pending",scheduledAt:Date.now()}});const S=!this.debounceTimers.has(f+"_leading");if(d.leading&&S&&(this.debounceTimers.set(f+"_leading",setTimeout(()=>{},0)),this.executeDebouncedAction(e,h,r,i,c),v?.isEnabled()&&v.trackAction({handler:typeof e.handler=="string"?e.handler:String(e.handler),type:e.type,status:"success",params:e.params,debounce:{delay:d.delay,status:"executed",executedAt:Date.now(),mode:"leading"}})),d.trailing){const _=()=>{this.executeDebouncedAction(e,h,r,i,c),v?.isEnabled()&&v.trackAction({handler:typeof e.handler=="string"?e.handler:String(e.handler),type:e.type,status:"success",params:e.params,debounce:{delay:d.delay,status:"executed",executedAt:Date.now(),mode:"trailing"}})},x=setTimeout(()=>{this.debounceTimers.delete(f),this.debounceTimers.delete(f+"_leading"),this.pendingDebounceFlushers.delete(f),this.debounceAccumulatedValues.delete(f),_()},d.delay);this.debounceTimers.set(f,x),this.pendingDebounceFlushers.set(f,_)}}clearDebounceTimers(e){if(e)for(const[n,r]of this.debounceTimers)n.startsWith(e+"-")&&(clearTimeout(r),this.debounceTimers.delete(n),this.pendingDebounceFlushers.delete(n),this.debounceAccumulatedValues.delete(n));else{for(const n of this.debounceTimers.values())clearTimeout(n);this.debounceTimers.clear(),this.pendingDebounceFlushers.clear(),this.debounceAccumulatedValues.clear()}}debouncedCall(e,n,r){const i=this.debounceTimers.get(e);i&&(clearTimeout(i),this.debounceTimers.delete(e));const l=setTimeout(()=>{this.debounceTimers.delete(e),this.pendingDebounceFlushers.delete(e),r()},n);this.debounceTimers.set(e,l),this.pendingDebounceFlushers.set(e,r)}flushPendingDebounceTimers(){if(this.pendingDebounceFlushers.size===0)return;const e=new Map(this.pendingDebounceFlushers);for(const[n,r]of e){const i=this.debounceTimers.get(n);i&&(clearTimeout(i),this.debounceTimers.delete(n)),this.debounceTimers.delete(n+"_leading"),this.pendingDebounceFlushers.delete(n),r()}this.globalStateUpdater&&this.globalStateUpdater({})}bindActionsToProps(e,n,r){const i={...e};if(e.actions&&Array.isArray(e.actions)){const l=new Map;for(const m of e.actions){const y=this.resolveActionRef(m),v=y.event?this.normalizeEventPropName(y.event):this.getEventHandlerName(y.type);l.has(v)||l.set(v,[]),l.get(v).push(y)}const c=e.name||e.id||"unknown",d=l.has("onDrop"),f=l.has("onDragOver"),h=l.has("onDragEnter");d&&!f&&(i.onDragOver=m=>{m.preventDefault()}),d&&!h&&(i.onDragEnter=m=>{m.preventDefault()});for(const[m,y]of l)i[m]=(...v)=>{for(const S of y){const _=v[0],x=_&&typeof _=="object"&&"preventDefault"in _;if(x&&(m==="onDragOver"||m==="onDrop")&&_.preventDefault(),x&&S.key&&"key"in _){if(this.isImeComposing(_)){j.log("Key filter skipped (IME composing):",S.key);continue}if(_.key!==S.key){j.log("Key filter not matched:",S.key,"actual:",_.key);continue}j.log("Key filter matched:",S.key)}if(S.debounce){const A=`${c}-${S.handler}-${m}`,D=_&&typeof _=="object"&&!("preventDefault"in _)&&"target"in _&&_.target!==null;let O;if(x)O=_;else if(D){const L={type:"custom",target:_.target,preventDefault:()=>{},stopPropagation:()=>{}};_._changedKeys&&(L._changedKeys=_._changedKeys),O=L}else O=new Event("custom");const k=S.event?{...n,$args:v}:n;this.handleDebouncedAction(S,O,k,r,A,v)}else if(this.flushPendingDebounceTimers(),S.event){const A={...n,$args:v},D=this.resolveEventForHandler(_,x);this.createHandler(S,A,r)(D)}else if(x)this.createHandler(S,n,r)(_);else if(_&&typeof _=="object"&&!("preventDefault"in _)&&"target"in _&&_.target!==null){const D={type:"custom",target:_.target,preventDefault:()=>{},stopPropagation:()=>{}};this.createHandler(S,n,r)(D)}}}}return i}getEventHandlerName(e){return wy[e]||`on${e.charAt(0).toUpperCase()}${e.slice(1)}`}normalizeEventPropName(e){return wy[e]??e}resolveEventForHandler(e,n){if(n)return e;if(!(e&&typeof e=="object"&&!("preventDefault"in e)&&"target"in e&&e.target!==null))return new Event("custom");const i={type:"custom",target:e.target,preventDefault:()=>{},stopPropagation:()=>{}};return e._changedKeys&&(i._changedKeys=e._changedKeys),i}sanitizeForDevTools(e,n=5){const r=new WeakSet,i=(l,c)=>{if(l==null||typeof l=="string"||typeof l=="number"||typeof l=="boolean")return l;if(c>n)return"[Max Depth Exceeded]";if(typeof l=="function")return`[Function: ${l.name||"anonymous"}]`;if(l instanceof Element)return`[Element: ${l.tagName}${l.className?"."+l.className.split(" ").join("."):""}]`;if(l instanceof Event)return`[Event: ${l.type}]`;if(Array.isArray(l)){if(r.has(l))return"[Circular Array]";r.add(l);const d=l.slice(0,10).map(f=>i(f,c+1));return l.length>10&&d.push(`... ${l.length-10} more items`),d}if(typeof l=="object"){if(r.has(l))return"[Circular Reference]";r.add(l);const d={},f=Object.keys(l),h=f.slice(0,20);for(const m of h)try{d[m]=i(l[m],c+1)}catch{d[m]="[Error accessing property]"}return f.length>20&&(d["..."]=`${f.length-20} more properties`),d}return String(l)};return i(e,0)}};M(cn,"loadedScripts",new Set),M(cn,"loadingScripts",new Map),M(cn,"PROTOTYPE_PATH_SEGMENTS",["__proto__","prototype","constructor"]);let jo=cn,_c=null;function Cy(s){return _c||(_c=new jo(s)),_c}function l_(s){_c=s}const Ey=pt("TransitionManager");class c_{constructor(){M(this,"isPending",!1);M(this,"subscribers",new Set)}setPending(e){this.isPending!==e&&(Ey.log("setPending:",{from:this.isPending,to:e,stack:new Error().stack}),this.isPending=e,this.notifySubscribers())}getIsPending(){return this.isPending}subscribe(e){return this.subscribers.add(e),()=>{this.subscribers.delete(e)}}notifySubscribers(){Ey.log("notifySubscribers:",{isPending:this.isPending,subscriberCount:this.subscribers.size}),this.subscribers.forEach(e=>{e(this.isPending)})}clearSubscribers(){this.subscribers.clear()}}const Gr=new c_,Ac=pt("TransitionContext"),_y=I.createContext({isTransitioning:!1}),Mf=({children:s})=>{const[e,n]=I.useState(()=>{const i=Gr.getIsPending();return Ac.log("Initial state:",i),i});I.useEffect(()=>{Ac.log("Subscribing to TransitionManager");const i=Gr.subscribe(l=>{Ac.log("Received isPending update:",l),n(l)});return()=>{Ac.log("Unsubscribing from TransitionManager"),i()}},[]);const r=I.useMemo(()=>({isTransitioning:e}),[e]);return yt.jsx(_y.Provider,{value:r,children:s})},Ay=()=>I.useContext(_y),xc="__g7LocalInitTracking";function Nf(){const s=globalThis;return s[xc]||(s[xc]={hash:"",timestamp:void 0,consumed:void 0}),s[xc]}function u_(){const s=globalThis;s[xc]={hash:"",timestamp:void 0,consumed:void 0}}function d_(s){Nf().consumed=s}function f_(s){return Nf().consumed===s}function h_(s){return s.globalTrackedKey!==s.trackingKey?"apply":s.instanceHandledKey===s.trackingKey?"skip":"prune"}function p_(s,e){return typeof s=="number"&&typeof e=="number"?Math.max(s,e):e??s}function g_(s,e){if(e===void 0)return s;if(!(s!==null&&typeof s=="object"&&e!==null&&typeof e=="object"&&!f_(s)))return e;const{_forceLocalInit:r,...i}=s,{_forceLocalInit:l,...c}=e,d={...i,...c},f=p_(r,l);return f!==void 0&&(d._forceLocalInit=f),d}function m_(s){return s<768?"mobile":s<1024?"tablet":"desktop"}function xy(s){const e=m_(s);return{width:s,isMobile:e==="mobile",isTablet:e==="tablet",isDesktop:e==="desktop",matchedPreset:e}}const If=I.createContext(xy(1024)),Tc=({children:s,overrideWidth:e})=>{const[n,r]=I.useState(()=>Ci.getWidth());I.useEffect(()=>{const c=Ci.subscribe(d=>{r(d)});return()=>{c()}},[]);const i=e??n,l=I.useMemo(()=>xy(i),[i]);return yt.jsx(If.Provider,{value:l,children:s})},jf=()=>I.useContext(If);window.G7Core?.createLogger?.("Core:State");function y_(s,e,n,r){const{syncOnControlledChange:i=!0,isEqual:l}=r??{},c=s!==void 0;I.useRef(c);const[d,f]=I.useState(s!==void 0?s:e),h=d,m=I.useRef(s),y=I.useRef(!1);I.useEffect(()=>{if(y.current){m.current=s;return}i&&c&&s!==m.current&&(l?!l(d,s):d!==s)&&f(s),m.current=s},[s,c,i,l,d]);const v=I.useCallback(S=>{const _=typeof S=="function"?S(h):S;(l?l(h,_):h===_)||(y.current=!0,f(_),n?.(_),requestAnimationFrame(()=>{y.current=!1}))},[h,c,n,l]);return[h,v]}function b_(s,e){return s===e?!0:s.length!==e.length?!1:s.every((n,r)=>n===e[r])}function Ty(s,e){if(s===e)return!0;const n=Object.keys(s),r=Object.keys(e);return n.length!==r.length?!1:n.every(i=>s[i]===e[i])}const ky=I.createContext({}),v_=({children:s,value:e})=>yt.jsx(ky.Provider,{value:e,children:s}),S_=()=>I.useContext(ky),w_=(s,e)=>{if(!s)return;const n=e.split(".");let r=s;for(const i of n){if(r==null)return;r=r[i]}return r},Ry=(s,e,n)=>{const r=e.split(".");if(r.length===1)return{...s,[r[0]]:n};const i={...s};let l=i;for(let c=0;c{vs.warn("IsolatedStateContext not available: setState called outside IsolatedStateProvider")},getState:()=>{},mergeState:()=>{vs.warn("IsolatedStateContext not available: mergeState called outside IsolatedStateProvider")},stateRef:{current:{}},scopeId:""},Dy=I.createContext(C_);let E_=0;function __(){return`isolated-${++E_}-${Date.now().toString(36)}`}function Oy(s,e){if(e==null)return s;if(typeof e!="object"||Array.isArray(e))return e;const n={...s};for(const r of Object.keys(e)){const i=e[r],l=s?.[r];i!==null&&typeof i=="object"&&!Array.isArray(i)&&l!==null&&typeof l=="object"&&!Array.isArray(l)?n[r]=Oy(l,i):n[r]=i}return n}function A_(s,e){return e?e.split(".").reduce((n,r)=>n?.[r],s):s}function x_(s,e,n){const r=e.split("."),i={...s};let l=i;for(let c=0;c{const i=I.useRef(e||__()).current,[l,c]=I.useState(s),d=I.useRef(l);d.current=l;const f=I.useCallback(S=>A_(d.current,S),[]),h=I.useCallback((S,_)=>{c(x=>{const A=x_(x,S,_);return vs.log(`[${i}] setState: ${S} =`,_),A})},[i]),m=I.useCallback((S,_="deep")=>{c(x=>{let A;return _==="replace"?A=S:_==="shallow"?A={...x,...S}:A=Oy(x,S),vs.log(`[${i}] mergeState (${_}):`,S),A})},[i]),y=I.useMemo(()=>({state:l,setState:h,getState:f,mergeState:m,stateRef:d,scopeId:i}),[l,h,f,m,i]),v=I.useMemo(()=>({state:d.current,setState:h,getState:f,mergeState:m}),[h,f,m]);return I.useEffect(()=>{const S=window.G7Core,_=window.__g7IsolatedStates||{};return window.__g7IsolatedStates||(window.__g7IsolatedStates=_),_[i]=v,S&&(S._isolatedStates||(S._isolatedStates={}),S._isolatedStates[i]=l),S?.devTools?.isEnabled?.()&&S.devTools.emit?.("isolated:created",{id:i,state:l}),vs.log(`[${i}] IsolatedStateProvider mounted with initial state:`,l),()=>{delete _[i],S?._isolatedStates&&delete S._isolatedStates[i],S?.devTools?.isEnabled?.()&&S.devTools.emit?.("isolated:destroyed",{id:i}),vs.log(`[${i}] IsolatedStateProvider unmounted`)}},[i,v]),I.useEffect(()=>{const S=window.G7Core,_=window.__g7IsolatedStates;_?.[i]&&(_[i].state=l),S?._isolatedStates&&(S._isolatedStates[i]=l),S?.devTools?.isEnabled?.()&&S.devTools.emit?.("isolated:updated",{id:i,state:l})},[l,i]),yt.jsx(Dy.Provider,{value:y,children:n})},k_=()=>{const s=I.useContext(Dy);return s.scopeId?s:null};var ja=Pg();function R_(){for(var s=arguments.length,e=new Array(s),n=0;nr=>{e.forEach(i=>i(r))},e)}const kc=typeof window<"u"&&typeof window.document<"u"&&typeof window.document.createElement<"u";function Ss(s){const e=Object.prototype.toString.call(s);return e==="[object Window]"||e==="[object global]"}function Hf(s){return"nodeType"in s}function Hn(s){var e,n;return s?Ss(s)?s:Hf(s)&&(e=(n=s.ownerDocument)==null?void 0:n.defaultView)!=null?e:window:window}function zf(s){const{Document:e}=Hn(s);return s instanceof e}function Ho(s){return Ss(s)?!1:s instanceof Hn(s).HTMLElement}function Ly(s){return s instanceof Hn(s).SVGElement}function ws(s){return s?Ss(s)?s.document:Hf(s)?zf(s)?s:Ho(s)||Ly(s)?s.ownerDocument:document:document:document}const Fr=kc?I.useLayoutEffect:I.useEffect;function Pf(s){const e=I.useRef(s);return Fr(()=>{e.current=s}),I.useCallback(function(){for(var n=arguments.length,r=new Array(n),i=0;i{s.current=setInterval(r,i)},[]),n=I.useCallback(()=>{s.current!==null&&(clearInterval(s.current),s.current=null)},[]);return[e,n]}function zo(s,e){e===void 0&&(e=[s]);const n=I.useRef(s);return Fr(()=>{n.current!==s&&(n.current=s)},e),n}function Po(s,e){const n=I.useRef();return I.useMemo(()=>{const r=s(n.current);return n.current=r,r},[...e])}function Rc(s){const e=Pf(s),n=I.useRef(null),r=I.useCallback(i=>{i!==n.current&&e?.(i,n.current),n.current=i},[]);return[n,r]}function Uf(s){const e=I.useRef();return I.useEffect(()=>{e.current=s},[s]),e.current}let Bf={};function Uo(s,e){return I.useMemo(()=>{if(e)return e;const n=Bf[s]==null?0:Bf[s]+1;return Bf[s]=n,s+"-"+n},[s,e])}function $y(s){return function(e){for(var n=arguments.length,r=new Array(n>1?n-1:0),i=1;i{const d=Object.entries(c);for(const[f,h]of d){const m=l[f];m!=null&&(l[f]=m+s*h)}return l},{...e})}}const Cs=$y(1),Bo=$y(-1);function O_(s){return"clientX"in s&&"clientY"in s}function qf(s){if(!s)return!1;const{KeyboardEvent:e}=Hn(s.target);return e&&s instanceof e}function L_(s){if(!s)return!1;const{TouchEvent:e}=Hn(s.target);return e&&s instanceof e}function Gf(s){if(L_(s)){if(s.touches&&s.touches.length){const{clientX:e,clientY:n}=s.touches[0];return{x:e,y:n}}else if(s.changedTouches&&s.changedTouches.length){const{clientX:e,clientY:n}=s.changedTouches[0];return{x:e,y:n}}}return O_(s)?{x:s.clientX,y:s.clientY}:null}const qo=Object.freeze({Translate:{toString(s){if(!s)return;const{x:e,y:n}=s;return"translate3d("+(e?Math.round(e):0)+"px, "+(n?Math.round(n):0)+"px, 0)"}},Scale:{toString(s){if(!s)return;const{scaleX:e,scaleY:n}=s;return"scaleX("+e+") scaleY("+n+")"}},Transform:{toString(s){if(s)return[qo.Translate.toString(s),qo.Scale.toString(s)].join(" ")}},Transition:{toString(s){let{property:e,duration:n,easing:r}=s;return e+" "+n+"ms "+r}}}),My="a,frame,iframe,input:not([type=hidden]):not(:disabled),select:not(:disabled),textarea:not(:disabled),button:not(:disabled),*[tabindex]";function $_(s){return s.matches(My)?s:s.querySelector(My)}const M_={display:"none"};function N_(s){let{id:e,value:n}=s;return Xe.createElement("div",{id:e,style:M_},n)}function I_(s){let{id:e,announcement:n,ariaLiveType:r="assertive"}=s;const i={position:"fixed",top:0,left:0,width:1,height:1,margin:-1,border:0,padding:0,overflow:"hidden",clip:"rect(0 0 0 0)",clipPath:"inset(100%)",whiteSpace:"nowrap"};return Xe.createElement("div",{id:e,style:i,role:"status","aria-live":r,"aria-atomic":!0},n)}function j_(){const[s,e]=I.useState("");return{announce:I.useCallback(r=>{r!=null&&e(r)},[]),announcement:s}}const Ny=I.createContext(null);function H_(s){const e=I.useContext(Ny);I.useEffect(()=>{if(!e)throw new Error("useDndMonitor must be used within a children of ");return e(s)},[s,e])}function z_(){const[s]=I.useState(()=>new Set),e=I.useCallback(r=>(s.add(r),()=>s.delete(r)),[s]);return[I.useCallback(r=>{let{type:i,event:l}=r;s.forEach(c=>{var d;return(d=c[i])==null?void 0:d.call(c,l)})},[s]),e]}const P_={draggable:` + `,i.onmouseenter=()=>{i.style.backgroundColor="rgba(0, 0, 0, 0.2)"},i.onmouseleave=()=>{i.style.backgroundColor="rgba(0, 0, 0, 0.1)"};const l=()=>{n.parentNode&&n.parentNode.removeChild(n)};i.onclick=l;const c=d=>{this.isImeComposing(d)||d.key==="Escape"&&(l(),document.removeEventListener("keydown",c))};return document.addEventListener("keydown",c),n.onclick=d=>{d.target===n&&l()},r.appendChild(i),n.appendChild(r),document.body.appendChild(n),{overlay:n,layer:r,closeLayer:l}}static isPrototypePathSegment(e){return cn.PROTOTYPE_PATH_SEGMENTS.includes(e)}getNestedProperty(e,n){return n.split(".").reduce((r,i)=>{if(!cn.isPrototypePathSegment(i))return r&&r[i]!==void 0?r[i]:void 0},e)}assertCallableExternalConstructor(e,n,r){if(typeof window>"u")return;if([window.Function,window.eval,window.setTimeout,window.setInterval].some(l=>l!==void 0&&l===e))throw I.error(`callExternal: blocked arbitrary code execution seam: ${n}`),new It(`Blocked constructor (arbitrary code execution): ${n}`,r)}async handleSaveToLocalStorage(e,n){const{key:r,value:i}=e;if(!r)return I.error("saveToLocalStorage: key parameter is required"),!1;try{const l=typeof i=="string"?i:JSON.stringify(i);return localStorage.setItem(r,l),I.log(`saveToLocalStorage: saved ${r}`,i),!0}catch(l){return I.error("saveToLocalStorage: failed to save",{key:r,error:l}),!1}}async handleLoadFromLocalStorage(e,n){const{key:r,target:i,stateKey:l,defaultValue:c}=e;if(!r)return I.error("loadFromLocalStorage: key parameter is required"),c;try{const d=localStorage.getItem(r);if(d===null)return I.log(`loadFromLocalStorage: no value found for ${r}, using default`),l&&n.setState&&this.writeLocalState(n,{[l]:c}),c;let f;try{f=JSON.parse(d)}catch{f=d}return I.log(`loadFromLocalStorage: loaded ${r}`,f),l&&n.setState&&this.writeLocalState(n,{[l]:f}),f}catch(d){return I.error("loadFromLocalStorage: failed to load",{key:r,error:d}),c}}writeLocalState(e,n,r,i){const l=r||e.setState;if(!l)return;if(typeof n=="function"){I.warn("[writeLocalState] 함수형 업데이터는 저장소 B 미러 대상이 아닙니다 (payload 추출 불가). 저장소 A 만 갱신합니다."),l(n);return}if(i?.scope==="parent"||i?.scope==="root"){l(n);return}if(this.localMirrorDepth>0||l?.__g7CanonicalWriter){l(n);return}const c=n&&typeof n=="object"?n:{},d=c.__mergeMode;if(d==="replace"){l(n);return}const{__mergeMode:f,__setStateId:h,...m}=c;if("errors"in m||!cn.isMirrorSafePayload(m)){l(n);return}const y=window;if((y.__g7LayoutContextStack||[]).length>0){l(n);return}const v=y.G7Core?.state?.setLocal;if(!y.__templateApp||typeof v!="function"){l(n);return}const S=this.deepMergeWithState(m,{});this.localMirrorDepth++;try{v(S,{render:!1,merge:d??"deep"}),y.__g7ActionContext?.setState!==l&&l(n),d&&(y.__g7ForcedLocalFields={...y.__g7ForcedLocalFields||{},...S})}finally{this.localMirrorDepth--}}static isMirrorSafePayload(e,n=0){if(e===null||typeof e!="object"||n>=8)return!0;if(Array.isArray(e))return e.every(i=>cn.isMirrorSafePayload(i,n+1));const r=Object.getPrototypeOf(e);return r!==Object.prototype&&r!==null?!1:Object.values(e).every(i=>cn.isMirrorSafePayload(i,n+1))}makeLocalSetStateProxy(e){const n=e.setState;if(!n)return;if(n.__g7CanonicalWriter)return n;const r=i=>{this.writeLocalState(e,i,n)};return r.__g7CanonicalWriter=!0,r}async handleCustomAction(e,n){const r=this.customHandlers.get(e.handler);if(!r){if(this.previewMode){I.warn(`[Preview] Unknown action handler "${e.handler}" — skipped (preview mode)`);return}const c=new It(`Unknown action handler: ${e.handler}`,e);throw c.unknownHandler=!0,c}const i=this.makeLocalSetStateProxy(n),l=i&&i!==n.setState?{...n,setState:i}:n;return await r(e,l)}resolveParams(e,n){if(!e)return{};const r={};for(const[i,l]of Object.entries(e))i.includes("{{")&&I.warn(`[resolveParams] setState params의 키에 표현식이 포함되어 있습니다: "${i}". 키는 해석되지 않습니다. 배열 항목 수정은 .map()/.filter() 패턴을 사용하세요.`),typeof l=="string"&&l.includes("{{")?r[i]=this.evaluateExpression(l,n):typeof l=="string"?r[i]=l:Array.isArray(l)?r[i]=l.map(c=>typeof c=="string"&&c.includes("{{")?this.evaluateExpression(c,n):c):typeof l=="object"&&l!==null&&!Array.isArray(l)&&Object.getPrototypeOf(l)!==Object.prototype&&Object.getPrototypeOf(l)!==null?r[i]=l:typeof l=="object"&&l!==null?r[i]=this.resolveParams(l,n):r[i]=l;return r}resolveValue(e,n){return n?this.bindingEngine.resolveBindings(e,n,{skipCache:!0}):e}evaluateExpressions(e,n){let r={};for(const[i,l]of Object.entries(e))if(i==="..."){let c=l;typeof l=="string"&&l.includes("{{")&&(c=this.evaluateExpression(l,n)),c&&typeof c=="object"&&!Array.isArray(c)&&(r={...r,...c})}else typeof l=="string"&&l.includes("{{")?r[i]=this.evaluateExpression(l,n):typeof l=="object"&&l!==null&&!Array.isArray(l)?r[i]=this.evaluateExpressions(l,n):r[i]=l;return r}evaluateExpressionsIfNeeded(e,n){const r={};for(const[i,l]of Object.entries(e))typeof l=="string"&&l.includes("{{")?r[i]=this.evaluateExpression(l,n):r[i]=l;return r}evaluateExpression(e,n){if(!n)return e;const r=Oa(e);if(r===null){let c=n;if(e.includes("_global")){const f=window.G7Core?.state?.get()?._global;f&&(c={...n,_global:f})}if(e.includes("_computed")||e.includes("$computed")){const f=window.__g7ActionContext?.computedRef?.current;f&&Object.keys(f).length>0&&(c={...c,_computed:f,$computed:f})}return this.bindingEngine.resolveBindings(e,c,{skipCache:!0})}let i=r;i=i.replace(/\$args\.(\d+)/g,"$args[$1]");let l=n;if(i.includes("_global")){const d=window.G7Core?.state?.get()?._global;d&&(l={...n,_global:d})}if(i.includes("_computed")||i.includes("$computed")){const d=window.__g7ActionContext?.computedRef?.current;d&&Object.keys(d).length>0&&(l={...l,_computed:d,$computed:d})}i.includes("_global.modules")&&(I.log("[evaluateExpression] expression:",i),I.log("[evaluateExpression] dataContext._global:",l._global),I.log("[evaluateExpression] dataContext._global?.modules:",l._global?.modules));try{const c=jn(i)?this.bindingEngine.evaluatePipeExpression(i,l,{skipCache:!0}):this.bindingEngine.evaluateExpression(i,l);return i.includes("_global.modules")&&I.log("[evaluateExpression] result:",c),typeof c=="string"&&c.startsWith("$t:")&&this.translationEngine&&this.translationContext?this.translationEngine.resolveTranslations(c,this.translationContext,n):c}catch(c){return I.error("Expression evaluation failed:",e,c),e}}setDefaultContext(e){this.defaultContext={...this.defaultContext,...e}}setGlobalStateUpdater(e){this.globalStateUpdater=e}getGlobalStateUpdater(){return this.globalStateUpdater}async dispatchAction(e,n){const r={...this.defaultContext,...n,data:{...this.defaultContext.data,...n?.data}};if(e.confirm){let i=this.resolveValue(e.confirm,r.data);if(this.translationEngine&&this.translationContext&&i.startsWith("$t:")&&(i=this.translationEngine.resolveTranslations(i,this.translationContext,r.data)),!confirm(i))return{success:!1}}try{return await this.executeAction(e,r)}catch(i){if(I.error("dispatchAction failed:",i),e.onError){const l=i instanceof It&&i.originalError?{message:i.originalError.message,response:i.originalError.response}:{message:i instanceof Error?i.message:String(i)},c={...r,data:{...r.data,error:l}},d=Array.isArray(e.onError)?e.onError:[e.onError];for(const f of d)await this.executeAction(f,c)}return{success:!1,error:i instanceof Error?i:new Error(String(i))}}}registerHandler(e,n,r){this.customHandlers.set(e,n);const i=Jn();i?.isEnabled()&&i.trackHandlerRegistration(e,r?.category??"custom",r?.description,r?.source)}unregisterHandler(e){this.customHandlers.delete(e);const n=Jn();n?.isEnabled()&&n.trackHandlerUnregistration(e)}getRegisteredHandlers(){return Array.from(this.customHandlers.keys())}getHandler(e){return this.customHandlers.get(e)}normalizeDebounceConfig(e){return typeof e=="number"?{delay:e,leading:!1,trailing:!0}:{delay:e.delay,leading:e.leading??!1,trailing:e.trailing??!0}}extractEventData(e){const n=e.target;if(n&&!("tagName"in n)){const l={type:e.type??"custom",target:{value:n.value,name:n.name??""}};return e._changedKeys&&(l._changedKeys=e._changedKeys),l}const i=n?{value:n.value,name:n.name??"",checked:n.checked??!1,type:n.type??"",tagName:n.tagName}:{};if(e.type==="scroll"&&n){const l=n;i.scrollHeight=l.scrollHeight??0,i.scrollTop=l.scrollTop??0,i.clientHeight=l.clientHeight??0,i.scrollLeft=l.scrollLeft??0,i.clientWidth=l.clientWidth??0,i.scrollWidth=l.scrollWidth??0}return{type:e.type,target:n?i:null}}executeDebouncedAction(e,n,r,i,l){const c={...r,$event:n,$args:l},d={type:n.type,target:n.target,preventDefault:()=>{},stopPropagation:()=>{}};this.createHandler(e,c,i)(d)}handleDebouncedAction(e,n,r,i,l,c){const d=this.normalizeDebounceConfig(e.debounce),f=l||`default-${e.handler}-${Date.now()}`,h=this.extractEventData(n),m=h._changedKeys;if(m&&Array.isArray(m)&&h?.target?.value&&typeof h.target.value=="object"&&!Array.isArray(h.target.value)){const _=this.debounceAccumulatedValues.get(f);if(_){const x={..._};for(const A of m)x[A]=h.target.value[A];h.target.value=x}this.debounceAccumulatedValues.set(f,h.target.value)}const y=this.debounceTimers.get(f);y&&(clearTimeout(y),this.debounceTimers.delete(f));const v=Jn();v?.isEnabled()&&v.trackAction({handler:typeof e.handler=="string"?e.handler:String(e.handler),type:e.type,status:"pending",params:e.params,debounce:{delay:d.delay,status:"pending",scheduledAt:Date.now()}});const S=!this.debounceTimers.has(f+"_leading");if(d.leading&&S&&(this.debounceTimers.set(f+"_leading",setTimeout(()=>{},0)),this.executeDebouncedAction(e,h,r,i,c),v?.isEnabled()&&v.trackAction({handler:typeof e.handler=="string"?e.handler:String(e.handler),type:e.type,status:"success",params:e.params,debounce:{delay:d.delay,status:"executed",executedAt:Date.now(),mode:"leading"}})),d.trailing){const _=()=>{this.executeDebouncedAction(e,h,r,i,c),v?.isEnabled()&&v.trackAction({handler:typeof e.handler=="string"?e.handler:String(e.handler),type:e.type,status:"success",params:e.params,debounce:{delay:d.delay,status:"executed",executedAt:Date.now(),mode:"trailing"}})},x=setTimeout(()=>{this.debounceTimers.delete(f),this.debounceTimers.delete(f+"_leading"),this.pendingDebounceFlushers.delete(f),this.debounceAccumulatedValues.delete(f),_()},d.delay);this.debounceTimers.set(f,x),this.pendingDebounceFlushers.set(f,_)}}clearDebounceTimers(e){if(e)for(const[n,r]of this.debounceTimers)n.startsWith(e+"-")&&(clearTimeout(r),this.debounceTimers.delete(n),this.pendingDebounceFlushers.delete(n),this.debounceAccumulatedValues.delete(n));else{for(const n of this.debounceTimers.values())clearTimeout(n);this.debounceTimers.clear(),this.pendingDebounceFlushers.clear(),this.debounceAccumulatedValues.clear()}}debouncedCall(e,n,r){const i=this.debounceTimers.get(e);i&&(clearTimeout(i),this.debounceTimers.delete(e));const l=setTimeout(()=>{this.debounceTimers.delete(e),this.pendingDebounceFlushers.delete(e),r()},n);this.debounceTimers.set(e,l),this.pendingDebounceFlushers.set(e,r)}flushPendingDebounceTimers(){if(this.pendingDebounceFlushers.size===0)return;const e=new Map(this.pendingDebounceFlushers);for(const[n,r]of e){const i=this.debounceTimers.get(n);i&&(clearTimeout(i),this.debounceTimers.delete(n)),this.debounceTimers.delete(n+"_leading"),this.pendingDebounceFlushers.delete(n),r()}this.globalStateUpdater&&this.globalStateUpdater({})}bindActionsToProps(e,n,r){const i={...e};if(e.actions&&Array.isArray(e.actions)){const l=new Map;for(const m of e.actions){const y=this.resolveActionRef(m),v=y.event?this.normalizeEventPropName(y.event):this.getEventHandlerName(y.type);l.has(v)||l.set(v,[]),l.get(v).push(y)}const c=e.name||e.id||"unknown",d=l.has("onDrop"),f=l.has("onDragOver"),h=l.has("onDragEnter");d&&!f&&(i.onDragOver=m=>{m.preventDefault()}),d&&!h&&(i.onDragEnter=m=>{m.preventDefault()});for(const[m,y]of l)i[m]=(...v)=>{for(const S of y){const _=v[0],x=_&&typeof _=="object"&&"preventDefault"in _;if(x&&(m==="onDragOver"||m==="onDrop")&&_.preventDefault(),x&&S.key&&"key"in _){if(this.isImeComposing(_)){I.log("Key filter skipped (IME composing):",S.key);continue}if(_.key!==S.key){I.log("Key filter not matched:",S.key,"actual:",_.key);continue}I.log("Key filter matched:",S.key)}if(S.debounce){const A=`${c}-${S.handler}-${m}`,D=_&&typeof _=="object"&&!("preventDefault"in _)&&"target"in _&&_.target!==null;let O;if(x)O=_;else if(D){const L={type:"custom",target:_.target,preventDefault:()=>{},stopPropagation:()=>{}};_._changedKeys&&(L._changedKeys=_._changedKeys),O=L}else O=new Event("custom");const k=S.event?{...n,$args:v}:n;this.handleDebouncedAction(S,O,k,r,A,v)}else if(this.flushPendingDebounceTimers(),S.event){const A={...n,$args:v},D=this.resolveEventForHandler(_,x);this.createHandler(S,A,r)(D)}else if(x)this.createHandler(S,n,r)(_);else if(_&&typeof _=="object"&&!("preventDefault"in _)&&"target"in _&&_.target!==null){const D={type:"custom",target:_.target,preventDefault:()=>{},stopPropagation:()=>{}};this.createHandler(S,n,r)(D)}}}}return i}getEventHandlerName(e){return wy[e]||`on${e.charAt(0).toUpperCase()}${e.slice(1)}`}normalizeEventPropName(e){return wy[e]??e}resolveEventForHandler(e,n){if(n)return e;if(!(e&&typeof e=="object"&&!("preventDefault"in e)&&"target"in e&&e.target!==null))return new Event("custom");const i={type:"custom",target:e.target,preventDefault:()=>{},stopPropagation:()=>{}};return e._changedKeys&&(i._changedKeys=e._changedKeys),i}sanitizeForDevTools(e,n=5){const r=new WeakSet,i=(l,c)=>{if(l==null||typeof l=="string"||typeof l=="number"||typeof l=="boolean")return l;if(c>n)return"[Max Depth Exceeded]";if(typeof l=="function")return`[Function: ${l.name||"anonymous"}]`;if(l instanceof Element)return`[Element: ${l.tagName}${l.className?"."+l.className.split(" ").join("."):""}]`;if(l instanceof Event)return`[Event: ${l.type}]`;if(Array.isArray(l)){if(r.has(l))return"[Circular Array]";r.add(l);const d=l.slice(0,10).map(f=>i(f,c+1));return l.length>10&&d.push(`... ${l.length-10} more items`),d}if(typeof l=="object"){if(r.has(l))return"[Circular Reference]";r.add(l);const d={},f=Object.keys(l),h=f.slice(0,20);for(const m of h)try{d[m]=i(l[m],c+1)}catch{d[m]="[Error accessing property]"}return f.length>20&&(d["..."]=`${f.length-20} more properties`),d}return String(l)};return i(e,0)}};M(cn,"loadedScripts",new Set),M(cn,"loadingScripts",new Map),M(cn,"PROTOTYPE_PATH_SEGMENTS",["__proto__","prototype","constructor"]);let jo=cn,Ec=null;function Cy(s){return Ec||(Ec=new jo(s)),Ec}function o_(s){Ec=s}const Ey=pt("TransitionManager");class l_{constructor(){M(this,"isPending",!1);M(this,"subscribers",new Set)}setPending(e){this.isPending!==e&&(Ey.log("setPending:",{from:this.isPending,to:e,stack:new Error().stack}),this.isPending=e,this.notifySubscribers())}getIsPending(){return this.isPending}subscribe(e){return this.subscribers.add(e),()=>{this.subscribers.delete(e)}}notifySubscribers(){Ey.log("notifySubscribers:",{isPending:this.isPending,subscriberCount:this.subscribers.size}),this.subscribers.forEach(e=>{e(this.isPending)})}clearSubscribers(){this.subscribers.clear()}}const Gr=new l_,_c=pt("TransitionContext"),_y=N.createContext({isTransitioning:!1}),Of=({children:s})=>{const[e,n]=N.useState(()=>{const i=Gr.getIsPending();return _c.log("Initial state:",i),i});N.useEffect(()=>{_c.log("Subscribing to TransitionManager");const i=Gr.subscribe(l=>{_c.log("Received isPending update:",l),n(l)});return()=>{_c.log("Unsubscribing from TransitionManager"),i()}},[]);const r=N.useMemo(()=>({isTransitioning:e}),[e]);return yt.jsx(_y.Provider,{value:r,children:s})},Ay=()=>N.useContext(_y),Ac="__g7LocalInitTracking";function Lf(){const s=globalThis;return s[Ac]||(s[Ac]={hash:"",timestamp:void 0,consumed:void 0}),s[Ac]}function c_(){const s=globalThis;s[Ac]={hash:"",timestamp:void 0,consumed:void 0}}function u_(s){Lf().consumed=s}function d_(s){return Lf().consumed===s}function f_(s){return s.globalTrackedKey!==s.trackingKey?"apply":s.instanceHandledKey===s.trackingKey?"skip":"prune"}function h_(s,e){return typeof s=="number"&&typeof e=="number"?Math.max(s,e):e??s}function p_(s,e){if(e===void 0)return s;if(!(s!==null&&typeof s=="object"&&e!==null&&typeof e=="object"&&!d_(s)))return e;const{_forceLocalInit:r,...i}=s,{_forceLocalInit:l,...c}=e,d={...i,...c},f=h_(r,l);return f!==void 0&&(d._forceLocalInit=f),d}function g_(s){return s<768?"mobile":s<1024?"tablet":"desktop"}function xy(s){const e=g_(s);return{width:s,isMobile:e==="mobile",isTablet:e==="tablet",isDesktop:e==="desktop",matchedPreset:e}}const $f=N.createContext(xy(1024)),xc=({children:s,overrideWidth:e})=>{const[n,r]=N.useState(()=>Ci.getWidth());N.useEffect(()=>{const c=Ci.subscribe(d=>{r(d)});return()=>{c()}},[]);const i=e??n,l=N.useMemo(()=>xy(i),[i]);return yt.jsx($f.Provider,{value:l,children:s})},Mf=()=>N.useContext($f);window.G7Core?.createLogger?.("Core:State");function m_(s,e,n,r){const{syncOnControlledChange:i=!0,isEqual:l}=r??{},c=s!==void 0;N.useRef(c);const[d,f]=N.useState(s!==void 0?s:e),h=d,m=N.useRef(s),y=N.useRef(!1);N.useEffect(()=>{if(y.current){m.current=s;return}i&&c&&s!==m.current&&(l?!l(d,s):d!==s)&&f(s),m.current=s},[s,c,i,l,d]);const v=N.useCallback(S=>{const _=typeof S=="function"?S(h):S;(l?l(h,_):h===_)||(y.current=!0,f(_),n?.(_),requestAnimationFrame(()=>{y.current=!1}))},[h,c,n,l]);return[h,v]}function y_(s,e){return s===e?!0:s.length!==e.length?!1:s.every((n,r)=>n===e[r])}function Ty(s,e){if(s===e)return!0;const n=Object.keys(s),r=Object.keys(e);return n.length!==r.length?!1:n.every(i=>s[i]===e[i])}const ky=N.createContext({}),b_=({children:s,value:e})=>yt.jsx(ky.Provider,{value:e,children:s}),v_=()=>N.useContext(ky),S_=(s,e)=>{if(!s)return;const n=e.split(".");let r=s;for(const i of n){if(r==null)return;r=r[i]}return r},Ry=(s,e,n)=>{const r=e.split(".");if(r.length===1)return{...s,[r[0]]:n};const i={...s};let l=i;for(let c=0;c{vs.warn("IsolatedStateContext not available: setState called outside IsolatedStateProvider")},getState:()=>{},mergeState:()=>{vs.warn("IsolatedStateContext not available: mergeState called outside IsolatedStateProvider")},stateRef:{current:{}},scopeId:""},Dy=N.createContext(w_);let C_=0;function E_(){return`isolated-${++C_}-${Date.now().toString(36)}`}function Oy(s,e){if(e==null)return s;if(typeof e!="object"||Array.isArray(e))return e;const n={...s};for(const r of Object.keys(e)){const i=e[r],l=s?.[r];i!==null&&typeof i=="object"&&!Array.isArray(i)&&l!==null&&typeof l=="object"&&!Array.isArray(l)?n[r]=Oy(l,i):n[r]=i}return n}function __(s,e){return e?e.split(".").reduce((n,r)=>n?.[r],s):s}function A_(s,e,n){const r=e.split("."),i={...s};let l=i;for(let c=0;c{const i=N.useRef(e||E_()).current,[l,c]=N.useState(s),d=N.useRef(l);d.current=l;const f=N.useCallback(S=>__(d.current,S),[]),h=N.useCallback((S,_)=>{c(x=>{const A=A_(x,S,_);return vs.log(`[${i}] setState: ${S} =`,_),A})},[i]),m=N.useCallback((S,_="deep")=>{c(x=>{let A;return _==="replace"?A=S:_==="shallow"?A={...x,...S}:A=Oy(x,S),vs.log(`[${i}] mergeState (${_}):`,S),A})},[i]),y=N.useMemo(()=>({state:l,setState:h,getState:f,mergeState:m,stateRef:d,scopeId:i}),[l,h,f,m,i]),v=N.useMemo(()=>({state:d.current,setState:h,getState:f,mergeState:m}),[h,f,m]);return N.useEffect(()=>{const S=window.G7Core,_=window.__g7IsolatedStates||{};return window.__g7IsolatedStates||(window.__g7IsolatedStates=_),_[i]=v,S&&(S._isolatedStates||(S._isolatedStates={}),S._isolatedStates[i]=l),S?.devTools?.isEnabled?.()&&S.devTools.emit?.("isolated:created",{id:i,state:l}),vs.log(`[${i}] IsolatedStateProvider mounted with initial state:`,l),()=>{delete _[i],S?._isolatedStates&&delete S._isolatedStates[i],S?.devTools?.isEnabled?.()&&S.devTools.emit?.("isolated:destroyed",{id:i}),vs.log(`[${i}] IsolatedStateProvider unmounted`)}},[i,v]),N.useEffect(()=>{const S=window.G7Core,_=window.__g7IsolatedStates;_?.[i]&&(_[i].state=l),S?._isolatedStates&&(S._isolatedStates[i]=l),S?.devTools?.isEnabled?.()&&S.devTools.emit?.("isolated:updated",{id:i,state:l})},[l,i]),yt.jsx(Dy.Provider,{value:y,children:n})},T_=()=>{const s=N.useContext(Dy);return s.scopeId?s:null};var Ia=zg();function k_(){for(var s=arguments.length,e=new Array(s),n=0;nr=>{e.forEach(i=>i(r))},e)}const Tc=typeof window<"u"&&typeof window.document<"u"&&typeof window.document.createElement<"u";function Ss(s){const e=Object.prototype.toString.call(s);return e==="[object Window]"||e==="[object global]"}function Nf(s){return"nodeType"in s}function zn(s){var e,n;return s?Ss(s)?s:Nf(s)&&(e=(n=s.ownerDocument)==null?void 0:n.defaultView)!=null?e:window:window}function If(s){const{Document:e}=zn(s);return s instanceof e}function Ho(s){return Ss(s)?!1:s instanceof zn(s).HTMLElement}function Ly(s){return s instanceof zn(s).SVGElement}function ws(s){return s?Ss(s)?s.document:Nf(s)?If(s)?s:Ho(s)||Ly(s)?s.ownerDocument:document:document:document}const Fr=Tc?N.useLayoutEffect:N.useEffect;function jf(s){const e=N.useRef(s);return Fr(()=>{e.current=s}),N.useCallback(function(){for(var n=arguments.length,r=new Array(n),i=0;i{s.current=setInterval(r,i)},[]),n=N.useCallback(()=>{s.current!==null&&(clearInterval(s.current),s.current=null)},[]);return[e,n]}function zo(s,e){e===void 0&&(e=[s]);const n=N.useRef(s);return Fr(()=>{n.current!==s&&(n.current=s)},e),n}function Po(s,e){const n=N.useRef();return N.useMemo(()=>{const r=s(n.current);return n.current=r,r},[...e])}function kc(s){const e=jf(s),n=N.useRef(null),r=N.useCallback(i=>{i!==n.current&&e?.(i,n.current),n.current=i},[]);return[n,r]}function Hf(s){const e=N.useRef();return N.useEffect(()=>{e.current=s},[s]),e.current}let zf={};function Uo(s,e){return N.useMemo(()=>{if(e)return e;const n=zf[s]==null?0:zf[s]+1;return zf[s]=n,s+"-"+n},[s,e])}function $y(s){return function(e){for(var n=arguments.length,r=new Array(n>1?n-1:0),i=1;i{const d=Object.entries(c);for(const[f,h]of d){const m=l[f];m!=null&&(l[f]=m+s*h)}return l},{...e})}}const Cs=$y(1),Bo=$y(-1);function D_(s){return"clientX"in s&&"clientY"in s}function Pf(s){if(!s)return!1;const{KeyboardEvent:e}=zn(s.target);return e&&s instanceof e}function O_(s){if(!s)return!1;const{TouchEvent:e}=zn(s.target);return e&&s instanceof e}function Uf(s){if(O_(s)){if(s.touches&&s.touches.length){const{clientX:e,clientY:n}=s.touches[0];return{x:e,y:n}}else if(s.changedTouches&&s.changedTouches.length){const{clientX:e,clientY:n}=s.changedTouches[0];return{x:e,y:n}}}return D_(s)?{x:s.clientX,y:s.clientY}:null}const qo=Object.freeze({Translate:{toString(s){if(!s)return;const{x:e,y:n}=s;return"translate3d("+(e?Math.round(e):0)+"px, "+(n?Math.round(n):0)+"px, 0)"}},Scale:{toString(s){if(!s)return;const{scaleX:e,scaleY:n}=s;return"scaleX("+e+") scaleY("+n+")"}},Transform:{toString(s){if(s)return[qo.Translate.toString(s),qo.Scale.toString(s)].join(" ")}},Transition:{toString(s){let{property:e,duration:n,easing:r}=s;return e+" "+n+"ms "+r}}}),My="a,frame,iframe,input:not([type=hidden]):not(:disabled),select:not(:disabled),textarea:not(:disabled),button:not(:disabled),*[tabindex]";function L_(s){return s.matches(My)?s:s.querySelector(My)}const $_={display:"none"};function M_(s){let{id:e,value:n}=s;return Xe.createElement("div",{id:e,style:$_},n)}function N_(s){let{id:e,announcement:n,ariaLiveType:r="assertive"}=s;const i={position:"fixed",top:0,left:0,width:1,height:1,margin:-1,border:0,padding:0,overflow:"hidden",clip:"rect(0 0 0 0)",clipPath:"inset(100%)",whiteSpace:"nowrap"};return Xe.createElement("div",{id:e,style:i,role:"status","aria-live":r,"aria-atomic":!0},n)}function I_(){const[s,e]=N.useState("");return{announce:N.useCallback(r=>{r!=null&&e(r)},[]),announcement:s}}const Ny=N.createContext(null);function j_(s){const e=N.useContext(Ny);N.useEffect(()=>{if(!e)throw new Error("useDndMonitor must be used within a children of ");return e(s)},[s,e])}function H_(){const[s]=N.useState(()=>new Set),e=N.useCallback(r=>(s.add(r),()=>s.delete(r)),[s]);return[N.useCallback(r=>{let{type:i,event:l}=r;s.forEach(c=>{var d;return(d=c[i])==null?void 0:d.call(c,l)})},[s]),e]}const z_={draggable:` To pick up a draggable item, press the space bar. While dragging, use the arrow keys to move the item. Press space again to drop the item in its new position, or press escape to cancel. - `},U_={onDragStart(s){let{active:e}=s;return"Picked up draggable item "+e.id+"."},onDragOver(s){let{active:e,over:n}=s;return n?"Draggable item "+e.id+" was moved over droppable area "+n.id+".":"Draggable item "+e.id+" is no longer over a droppable area."},onDragEnd(s){let{active:e,over:n}=s;return n?"Draggable item "+e.id+" was dropped over droppable area "+n.id:"Draggable item "+e.id+" was dropped."},onDragCancel(s){let{active:e}=s;return"Dragging was cancelled. Draggable item "+e.id+" was dropped."}};function B_(s){let{announcements:e=U_,container:n,hiddenTextDescribedById:r,screenReaderInstructions:i=P_}=s;const{announce:l,announcement:c}=j_(),d=Uo("DndLiveRegion"),[f,h]=I.useState(!1);if(I.useEffect(()=>{h(!0)},[]),H_(I.useMemo(()=>({onDragStart(y){let{active:v}=y;l(e.onDragStart({active:v}))},onDragMove(y){let{active:v,over:S}=y;e.onDragMove&&l(e.onDragMove({active:v,over:S}))},onDragOver(y){let{active:v,over:S}=y;l(e.onDragOver({active:v,over:S}))},onDragEnd(y){let{active:v,over:S}=y;l(e.onDragEnd({active:v,over:S}))},onDragCancel(y){let{active:v,over:S}=y;l(e.onDragCancel({active:v,over:S}))}}),[l,e])),!f)return null;const m=Xe.createElement(Xe.Fragment,null,Xe.createElement(N_,{id:r,value:i.draggable}),Xe.createElement(I_,{id:d,announcement:c}));return n?ja.createPortal(m,n):m}var ln;(function(s){s.DragStart="dragStart",s.DragMove="dragMove",s.DragEnd="dragEnd",s.DragCancel="dragCancel",s.DragOver="dragOver",s.RegisterDroppable="registerDroppable",s.SetDroppableDisabled="setDroppableDisabled",s.UnregisterDroppable="unregisterDroppable"})(ln||(ln={}));function Dc(){}function Iy(s,e){return I.useMemo(()=>({sensor:s,options:e??{}}),[s,e])}function q_(){for(var s=arguments.length,e=new Array(s),n=0;n[...e].filter(r=>r!=null),[...e])}const kr=Object.freeze({x:0,y:0});function jy(s,e){return Math.sqrt(Math.pow(s.x-e.x,2)+Math.pow(s.y-e.y,2))}function Hy(s,e){let{data:{value:n}}=s,{data:{value:r}}=e;return n-r}function G_(s,e){let{data:{value:n}}=s,{data:{value:r}}=e;return r-n}function zy(s){let{left:e,top:n,height:r,width:i}=s;return[{x:e,y:n},{x:e+i,y:n},{x:e,y:n+r},{x:e+i,y:n+r}]}function Py(s,e){if(!s||s.length===0)return null;const[n]=s;return n[e]}function Uy(s,e,n){return e===void 0&&(e=s.left),n===void 0&&(n=s.top),{x:e+s.width*.5,y:n+s.height*.5}}const F_=s=>{let{collisionRect:e,droppableRects:n,droppableContainers:r}=s;const i=Uy(e,e.left,e.top),l=[];for(const c of r){const{id:d}=c,f=n.get(d);if(f){const h=jy(Uy(f),i);l.push({id:d,data:{droppableContainer:c,value:h}})}}return l.sort(Hy)},V_=s=>{let{collisionRect:e,droppableRects:n,droppableContainers:r}=s;const i=zy(e),l=[];for(const c of r){const{id:d}=c,f=n.get(d);if(f){const h=zy(f),m=i.reduce((v,S,_)=>v+jy(h[_],S),0),y=Number((m/4).toFixed(4));l.push({id:d,data:{droppableContainer:c,value:y}})}}return l.sort(Hy)};function K_(s,e){const n=Math.max(e.top,s.top),r=Math.max(e.left,s.left),i=Math.min(e.left+e.width,s.left+s.width),l=Math.min(e.top+e.height,s.top+s.height),c=i-r,d=l-n;if(r{let{collisionRect:e,droppableRects:n,droppableContainers:r}=s;const i=[];for(const l of r){const{id:c}=l,d=n.get(c);if(d){const f=K_(d,e);f>0&&i.push({id:c,data:{droppableContainer:l,value:f}})}}return i.sort(G_)};function Y_(s,e,n){return{...s,scaleX:e&&n?e.width/n.width:1,scaleY:e&&n?e.height/n.height:1}}function By(s,e){return s&&e?{x:s.left-e.left,y:s.top-e.top}:kr}function X_(s){return function(n){for(var r=arguments.length,i=new Array(r>1?r-1:0),l=1;l({...c,top:c.top+s*d.y,bottom:c.bottom+s*d.y,left:c.left+s*d.x,right:c.right+s*d.x}),{...n})}}const J_=X_(1);function Q_(s){if(s.startsWith("matrix3d(")){const e=s.slice(9,-1).split(/, /);return{x:+e[12],y:+e[13],scaleX:+e[0],scaleY:+e[5]}}else if(s.startsWith("matrix(")){const e=s.slice(7,-1).split(/, /);return{x:+e[4],y:+e[5],scaleX:+e[0],scaleY:+e[3]}}return null}function Z_(s,e,n){const r=Q_(e);if(!r)return s;const{scaleX:i,scaleY:l,x:c,y:d}=r,f=s.left-c-(1-i)*parseFloat(n),h=s.top-d-(1-l)*parseFloat(n.slice(n.indexOf(" ")+1)),m=i?s.width/i:s.width,y=l?s.height/l:s.height;return{width:m,height:y,top:h,right:f+m,bottom:h+y,left:f}}const eA={ignoreTransform:!1};function Es(s,e){e===void 0&&(e=eA);let n=s.getBoundingClientRect();if(e.ignoreTransform){const{transform:h,transformOrigin:m}=Hn(s).getComputedStyle(s);h&&(n=Z_(n,h,m))}const{top:r,left:i,width:l,height:c,bottom:d,right:f}=n;return{top:r,left:i,width:l,height:c,bottom:d,right:f}}function qy(s){return Es(s,{ignoreTransform:!0})}function tA(s){const e=s.innerWidth,n=s.innerHeight;return{top:0,left:0,right:e,bottom:n,width:e,height:n}}function nA(s,e){return e===void 0&&(e=Hn(s).getComputedStyle(s)),e.position==="fixed"}function rA(s,e){e===void 0&&(e=Hn(s).getComputedStyle(s));const n=/(auto|scroll|overlay)/;return["overflow","overflowX","overflowY"].some(i=>{const l=e[i];return typeof l=="string"?n.test(l):!1})}function Oc(s,e){const n=[];function r(i){if(e!=null&&n.length>=e||!i)return n;if(zf(i)&&i.scrollingElement!=null&&!n.includes(i.scrollingElement))return n.push(i.scrollingElement),n;if(!Ho(i)||Ly(i)||n.includes(i))return n;const l=Hn(s).getComputedStyle(i);return i!==s&&rA(i,l)&&n.push(i),nA(i,l)?n:r(i.parentNode)}return s?r(s):n}function Gy(s){const[e]=Oc(s,1);return e??null}function Ff(s){return!kc||!s?null:Ss(s)?s:Hf(s)?zf(s)||s===ws(s).scrollingElement?window:Ho(s)?s:null:null}function Fy(s){return Ss(s)?s.scrollX:s.scrollLeft}function Vy(s){return Ss(s)?s.scrollY:s.scrollTop}function Vf(s){return{x:Fy(s),y:Vy(s)}}var mn;(function(s){s[s.Forward=1]="Forward",s[s.Backward=-1]="Backward"})(mn||(mn={}));function Ky(s){return!kc||!s?!1:s===document.scrollingElement}function Wy(s){const e={x:0,y:0},n=Ky(s)?{height:window.innerHeight,width:window.innerWidth}:{height:s.clientHeight,width:s.clientWidth},r={x:s.scrollWidth-n.width,y:s.scrollHeight-n.height},i=s.scrollTop<=e.y,l=s.scrollLeft<=e.x,c=s.scrollTop>=r.y,d=s.scrollLeft>=r.x;return{isTop:i,isLeft:l,isBottom:c,isRight:d,maxScroll:r,minScroll:e}}const aA={x:.2,y:.2};function iA(s,e,n,r,i){let{top:l,left:c,right:d,bottom:f}=n;r===void 0&&(r=10),i===void 0&&(i=aA);const{isTop:h,isBottom:m,isLeft:y,isRight:v}=Wy(s),S={x:0,y:0},_={x:0,y:0},x={height:e.height*i.y,width:e.width*i.x};return!h&&l<=e.top+x.height?(S.y=mn.Backward,_.y=r*Math.abs((e.top+x.height-l)/x.height)):!m&&f>=e.bottom-x.height&&(S.y=mn.Forward,_.y=r*Math.abs((e.bottom-x.height-f)/x.height)),!v&&d>=e.right-x.width?(S.x=mn.Forward,_.x=r*Math.abs((e.right-x.width-d)/x.width)):!y&&c<=e.left+x.width&&(S.x=mn.Backward,_.x=r*Math.abs((e.left+x.width-c)/x.width)),{direction:S,speed:_}}function sA(s){if(s===document.scrollingElement){const{innerWidth:l,innerHeight:c}=window;return{top:0,left:0,right:l,bottom:c,width:l,height:c}}const{top:e,left:n,right:r,bottom:i}=s.getBoundingClientRect();return{top:e,left:n,right:r,bottom:i,width:s.clientWidth,height:s.clientHeight}}function Yy(s){return s.reduce((e,n)=>Cs(e,Vf(n)),kr)}function oA(s){return s.reduce((e,n)=>e+Fy(n),0)}function lA(s){return s.reduce((e,n)=>e+Vy(n),0)}function cA(s,e){if(e===void 0&&(e=Es),!s)return;const{top:n,left:r,bottom:i,right:l}=e(s);Gy(s)&&(i<=0||l<=0||n>=window.innerHeight||r>=window.innerWidth)&&s.scrollIntoView({block:"center",inline:"center"})}const uA=[["x",["left","right"],oA],["y",["top","bottom"],lA]];class Kf{constructor(e,n){this.rect=void 0,this.width=void 0,this.height=void 0,this.top=void 0,this.bottom=void 0,this.right=void 0,this.left=void 0;const r=Oc(n),i=Yy(r);this.rect={...e},this.width=e.width,this.height=e.height;for(const[l,c,d]of uA)for(const f of c)Object.defineProperty(this,f,{get:()=>{const h=d(r),m=i[l]-h;return this.rect[f]+m},enumerable:!0});Object.defineProperty(this,"rect",{enumerable:!1})}}class Go{constructor(e){this.target=void 0,this.listeners=[],this.removeAll=()=>{this.listeners.forEach(n=>{var r;return(r=this.target)==null?void 0:r.removeEventListener(...n)})},this.target=e}add(e,n,r){var i;(i=this.target)==null||i.addEventListener(e,n,r),this.listeners.push([e,n,r])}}function dA(s){const{EventTarget:e}=Hn(s);return s instanceof e?s:ws(s)}function Wf(s,e){const n=Math.abs(s.x),r=Math.abs(s.y);return typeof e=="number"?Math.sqrt(n**2+r**2)>e:"x"in e&&"y"in e?n>e.x&&r>e.y:"x"in e?n>e.x:"y"in e?r>e.y:!1}var cr;(function(s){s.Click="click",s.DragStart="dragstart",s.Keydown="keydown",s.ContextMenu="contextmenu",s.Resize="resize",s.SelectionChange="selectionchange",s.VisibilityChange="visibilitychange"})(cr||(cr={}));function Xy(s){s.preventDefault()}function fA(s){s.stopPropagation()}var dt;(function(s){s.Space="Space",s.Down="ArrowDown",s.Right="ArrowRight",s.Left="ArrowLeft",s.Up="ArrowUp",s.Esc="Escape",s.Enter="Enter",s.Tab="Tab"})(dt||(dt={}));const Jy={start:[dt.Space,dt.Enter],cancel:[dt.Esc],end:[dt.Space,dt.Enter,dt.Tab]},hA=(s,e)=>{let{currentCoordinates:n}=e;switch(s.code){case dt.Right:return{...n,x:n.x+25};case dt.Left:return{...n,x:n.x-25};case dt.Down:return{...n,y:n.y+25};case dt.Up:return{...n,y:n.y-25}}};class Yf{constructor(e){this.props=void 0,this.autoScrollEnabled=!1,this.referenceCoordinates=void 0,this.listeners=void 0,this.windowListeners=void 0,this.props=e;const{event:{target:n}}=e;this.props=e,this.listeners=new Go(ws(n)),this.windowListeners=new Go(Hn(n)),this.handleKeyDown=this.handleKeyDown.bind(this),this.handleCancel=this.handleCancel.bind(this),this.attach()}attach(){this.handleStart(),this.windowListeners.add(cr.Resize,this.handleCancel),this.windowListeners.add(cr.VisibilityChange,this.handleCancel),setTimeout(()=>this.listeners.add(cr.Keydown,this.handleKeyDown))}handleStart(){const{activeNode:e,onStart:n}=this.props,r=e.node.current;r&&cA(r),n(kr)}handleKeyDown(e){if(qf(e)){const{active:n,context:r,options:i}=this.props,{keyboardCodes:l=Jy,coordinateGetter:c=hA,scrollBehavior:d="smooth"}=i,{code:f}=e;if(l.end.includes(f)){this.handleEnd(e);return}if(l.cancel.includes(f)){this.handleCancel(e);return}const{collisionRect:h}=r.current,m=h?{x:h.left,y:h.top}:kr;this.referenceCoordinates||(this.referenceCoordinates=m);const y=c(e,{active:n,context:r.current,currentCoordinates:m});if(y){const v=Bo(y,m),S={x:0,y:0},{scrollableAncestors:_}=r.current;for(const x of _){const A=e.code,{isTop:D,isRight:O,isLeft:k,isBottom:L,maxScroll:B,minScroll:q}=Wy(x),z=sA(x),W={x:Math.min(A===dt.Right?z.right-z.width/2:z.right,Math.max(A===dt.Right?z.left:z.left+z.width/2,y.x)),y:Math.min(A===dt.Down?z.bottom-z.height/2:z.bottom,Math.max(A===dt.Down?z.top:z.top+z.height/2,y.y))},de=A===dt.Right&&!O||A===dt.Left&&!k,_e=A===dt.Down&&!L||A===dt.Up&&!D;if(de&&W.x!==y.x){const Se=x.scrollLeft+v.x,Ie=A===dt.Right&&Se<=B.x||A===dt.Left&&Se>=q.x;if(Ie&&!v.y){x.scrollTo({left:Se,behavior:d});return}Ie?S.x=x.scrollLeft-Se:S.x=A===dt.Right?x.scrollLeft-B.x:x.scrollLeft-q.x,S.x&&x.scrollBy({left:-S.x,behavior:d});break}else if(_e&&W.y!==y.y){const Se=x.scrollTop+v.y,Ie=A===dt.Down&&Se<=B.y||A===dt.Up&&Se>=q.y;if(Ie&&!v.x){x.scrollTo({top:Se,behavior:d});return}Ie?S.y=x.scrollTop-Se:S.y=A===dt.Down?x.scrollTop-B.y:x.scrollTop-q.y,S.y&&x.scrollBy({top:-S.y,behavior:d});break}}this.handleMove(e,Cs(Bo(y,this.referenceCoordinates),S))}}}handleMove(e,n){const{onMove:r}=this.props;e.preventDefault(),r(n)}handleEnd(e){const{onEnd:n}=this.props;e.preventDefault(),this.detach(),n()}handleCancel(e){const{onCancel:n}=this.props;e.preventDefault(),this.detach(),n()}detach(){this.listeners.removeAll(),this.windowListeners.removeAll()}}Yf.activators=[{eventName:"onKeyDown",handler:(s,e,n)=>{let{keyboardCodes:r=Jy,onActivation:i}=e,{active:l}=n;const{code:c}=s.nativeEvent;if(r.start.includes(c)){const d=l.activatorNode.current;return d&&s.target!==d?!1:(s.preventDefault(),i?.({event:s.nativeEvent}),!0)}return!1}}];function Qy(s){return!!(s&&"distance"in s)}function Zy(s){return!!(s&&"delay"in s)}class Xf{constructor(e,n,r){var i;r===void 0&&(r=dA(e.event.target)),this.props=void 0,this.events=void 0,this.autoScrollEnabled=!0,this.document=void 0,this.activated=!1,this.initialCoordinates=void 0,this.timeoutId=null,this.listeners=void 0,this.documentListeners=void 0,this.windowListeners=void 0,this.props=e,this.events=n;const{event:l}=e,{target:c}=l;this.props=e,this.events=n,this.document=ws(c),this.documentListeners=new Go(this.document),this.listeners=new Go(r),this.windowListeners=new Go(Hn(c)),this.initialCoordinates=(i=Gf(l))!=null?i:kr,this.handleStart=this.handleStart.bind(this),this.handleMove=this.handleMove.bind(this),this.handleEnd=this.handleEnd.bind(this),this.handleCancel=this.handleCancel.bind(this),this.handleKeydown=this.handleKeydown.bind(this),this.removeTextSelection=this.removeTextSelection.bind(this),this.attach()}attach(){const{events:e,props:{options:{activationConstraint:n,bypassActivationConstraint:r}}}=this;if(this.listeners.add(e.move.name,this.handleMove,{passive:!1}),this.listeners.add(e.end.name,this.handleEnd),e.cancel&&this.listeners.add(e.cancel.name,this.handleCancel),this.windowListeners.add(cr.Resize,this.handleCancel),this.windowListeners.add(cr.DragStart,Xy),this.windowListeners.add(cr.VisibilityChange,this.handleCancel),this.windowListeners.add(cr.ContextMenu,Xy),this.documentListeners.add(cr.Keydown,this.handleKeydown),n){if(r!=null&&r({event:this.props.event,activeNode:this.props.activeNode,options:this.props.options}))return this.handleStart();if(Zy(n)){this.timeoutId=setTimeout(this.handleStart,n.delay),this.handlePending(n);return}if(Qy(n)){this.handlePending(n);return}}this.handleStart()}detach(){this.listeners.removeAll(),this.windowListeners.removeAll(),setTimeout(this.documentListeners.removeAll,50),this.timeoutId!==null&&(clearTimeout(this.timeoutId),this.timeoutId=null)}handlePending(e,n){const{active:r,onPending:i}=this.props;i(r,e,this.initialCoordinates,n)}handleStart(){const{initialCoordinates:e}=this,{onStart:n}=this.props;e&&(this.activated=!0,this.documentListeners.add(cr.Click,fA,{capture:!0}),this.removeTextSelection(),this.documentListeners.add(cr.SelectionChange,this.removeTextSelection),n(e))}handleMove(e){var n;const{activated:r,initialCoordinates:i,props:l}=this,{onMove:c,options:{activationConstraint:d}}=l;if(!i)return;const f=(n=Gf(e))!=null?n:kr,h=Bo(i,f);if(!r&&d){if(Qy(d)){if(d.tolerance!=null&&Wf(h,d.tolerance))return this.handleCancel();if(Wf(h,d.distance))return this.handleStart()}if(Zy(d)&&Wf(h,d.tolerance))return this.handleCancel();this.handlePending(d,h);return}e.cancelable&&e.preventDefault(),c(f)}handleEnd(){const{onAbort:e,onEnd:n}=this.props;this.detach(),this.activated||e(this.props.active),n()}handleCancel(){const{onAbort:e,onCancel:n}=this.props;this.detach(),this.activated||e(this.props.active),n()}handleKeydown(e){e.code===dt.Esc&&this.handleCancel()}removeTextSelection(){var e;(e=this.document.getSelection())==null||e.removeAllRanges()}}const pA={cancel:{name:"pointercancel"},move:{name:"pointermove"},end:{name:"pointerup"}};class Jf extends Xf{constructor(e){const{event:n}=e,r=ws(n.target);super(e,pA,r)}}Jf.activators=[{eventName:"onPointerDown",handler:(s,e)=>{let{nativeEvent:n}=s,{onActivation:r}=e;return!n.isPrimary||n.button!==0?!1:(r?.({event:n}),!0)}}];const gA={move:{name:"mousemove"},end:{name:"mouseup"}};var Qf;(function(s){s[s.RightClick=2]="RightClick"})(Qf||(Qf={}));class mA extends Xf{constructor(e){super(e,gA,ws(e.event.target))}}mA.activators=[{eventName:"onMouseDown",handler:(s,e)=>{let{nativeEvent:n}=s,{onActivation:r}=e;return n.button===Qf.RightClick?!1:(r?.({event:n}),!0)}}];const Zf={cancel:{name:"touchcancel"},move:{name:"touchmove"},end:{name:"touchend"}};class yA extends Xf{constructor(e){super(e,Zf)}static setup(){return window.addEventListener(Zf.move.name,e,{capture:!1,passive:!1}),function(){window.removeEventListener(Zf.move.name,e)};function e(){}}}yA.activators=[{eventName:"onTouchStart",handler:(s,e)=>{let{nativeEvent:n}=s,{onActivation:r}=e;const{touches:i}=n;return i.length>1?!1:(r?.({event:n}),!0)}}];var Fo;(function(s){s[s.Pointer=0]="Pointer",s[s.DraggableRect=1]="DraggableRect"})(Fo||(Fo={}));var Lc;(function(s){s[s.TreeOrder=0]="TreeOrder",s[s.ReversedTreeOrder=1]="ReversedTreeOrder"})(Lc||(Lc={}));function bA(s){let{acceleration:e,activator:n=Fo.Pointer,canScroll:r,draggingRect:i,enabled:l,interval:c=5,order:d=Lc.TreeOrder,pointerCoordinates:f,scrollableAncestors:h,scrollableAncestorRects:m,delta:y,threshold:v}=s;const S=SA({delta:y,disabled:!l}),[_,x]=D_(),A=I.useRef({x:0,y:0}),D=I.useRef({x:0,y:0}),O=I.useMemo(()=>{switch(n){case Fo.Pointer:return f?{top:f.y,bottom:f.y,left:f.x,right:f.x}:null;case Fo.DraggableRect:return i}},[n,i,f]),k=I.useRef(null),L=I.useCallback(()=>{const q=k.current;if(!q)return;const z=A.current.x*D.current.x,W=A.current.y*D.current.y;q.scrollBy(z,W)},[]),B=I.useMemo(()=>d===Lc.TreeOrder?[...h].reverse():h,[d,h]);I.useEffect(()=>{if(!l||!h.length||!O){x();return}for(const q of B){if(r?.(q)===!1)continue;const z=h.indexOf(q),W=m[z];if(!W)continue;const{direction:de,speed:_e}=iA(q,W,O,e,v);for(const Se of["x","y"])S[Se][de[Se]]||(_e[Se]=0,de[Se]=0);if(_e.x>0||_e.y>0){x(),k.current=q,_(L,c),A.current=_e,D.current=de;return}}A.current={x:0,y:0},D.current={x:0,y:0},x()},[e,L,r,x,l,c,JSON.stringify(O),JSON.stringify(S),_,h,B,m,JSON.stringify(v)])}const vA={x:{[mn.Backward]:!1,[mn.Forward]:!1},y:{[mn.Backward]:!1,[mn.Forward]:!1}};function SA(s){let{delta:e,disabled:n}=s;const r=Uf(e);return Po(i=>{if(n||!r||!i)return vA;const l={x:Math.sign(e.x-r.x),y:Math.sign(e.y-r.y)};return{x:{[mn.Backward]:i.x[mn.Backward]||l.x===-1,[mn.Forward]:i.x[mn.Forward]||l.x===1},y:{[mn.Backward]:i.y[mn.Backward]||l.y===-1,[mn.Forward]:i.y[mn.Forward]||l.y===1}}},[n,e,r])}function wA(s,e){const n=e!=null?s.get(e):void 0,r=n?n.node.current:null;return Po(i=>{var l;return e==null?null:(l=r??i)!=null?l:null},[r,e])}function CA(s,e){return I.useMemo(()=>s.reduce((n,r)=>{const{sensor:i}=r,l=i.activators.map(c=>({eventName:c.eventName,handler:e(c.handler,r)}));return[...n,...l]},[]),[s,e])}var Vo;(function(s){s[s.Always=0]="Always",s[s.BeforeDragging=1]="BeforeDragging",s[s.WhileDragging=2]="WhileDragging"})(Vo||(Vo={}));var eh;(function(s){s.Optimized="optimized"})(eh||(eh={}));const eb=new Map;function EA(s,e){let{dragging:n,dependencies:r,config:i}=e;const[l,c]=I.useState(null),{frequency:d,measure:f,strategy:h}=i,m=I.useRef(s),y=A(),v=zo(y),S=I.useCallback(function(D){D===void 0&&(D=[]),!v.current&&c(O=>O===null?D:O.concat(D.filter(k=>!O.includes(k))))},[v]),_=I.useRef(null),x=Po(D=>{if(y&&!n)return eb;if(!D||D===eb||m.current!==s||l!=null){const O=new Map;for(let k of s){if(!k)continue;if(l&&l.length>0&&!l.includes(k.id)&&k.rect.current){O.set(k.id,k.rect.current);continue}const L=k.node.current,B=L?new Kf(f(L),L):null;k.rect.current=B,B&&O.set(k.id,B)}return O}return D},[s,l,n,y,f]);return I.useEffect(()=>{m.current=s},[s]),I.useEffect(()=>{y||S()},[n,y]),I.useEffect(()=>{l&&l.length>0&&c(null)},[JSON.stringify(l)]),I.useEffect(()=>{y||typeof d!="number"||_.current!==null||(_.current=setTimeout(()=>{S(),_.current=null},d))},[d,y,S,...r]),{droppableRects:x,measureDroppableContainers:S,measuringScheduled:l!=null};function A(){switch(h){case Vo.Always:return!1;case Vo.BeforeDragging:return n;default:return!n}}}function tb(s,e){return Po(n=>s?n||(typeof e=="function"?e(s):s):null,[e,s])}function _A(s,e){return tb(s,e)}function AA(s){let{callback:e,disabled:n}=s;const r=Pf(e),i=I.useMemo(()=>{if(n||typeof window>"u"||typeof window.MutationObserver>"u")return;const{MutationObserver:l}=window;return new l(r)},[r,n]);return I.useEffect(()=>()=>i?.disconnect(),[i]),i}function $c(s){let{callback:e,disabled:n}=s;const r=Pf(e),i=I.useMemo(()=>{if(n||typeof window>"u"||typeof window.ResizeObserver>"u")return;const{ResizeObserver:l}=window;return new l(r)},[n]);return I.useEffect(()=>()=>i?.disconnect(),[i]),i}function xA(s){return new Kf(Es(s),s)}function nb(s,e,n){e===void 0&&(e=xA);const[r,i]=I.useState(null);function l(){i(f=>{if(!s)return null;if(s.isConnected===!1){var h;return(h=f??n)!=null?h:null}const m=e(s);return JSON.stringify(f)===JSON.stringify(m)?f:m})}const c=AA({callback(f){if(s)for(const h of f){const{type:m,target:y}=h;if(m==="childList"&&y instanceof HTMLElement&&y.contains(s)){l();break}}}}),d=$c({callback:l});return Fr(()=>{l(),s?(d?.observe(s),c?.observe(document.body,{childList:!0,subtree:!0})):(d?.disconnect(),c?.disconnect())},[s]),r}function TA(s){const e=tb(s);return By(s,e)}const rb=[];function kA(s){const e=I.useRef(s),n=Po(r=>s?r&&r!==rb&&s&&e.current&&s.parentNode===e.current.parentNode?r:Oc(s):rb,[s]);return I.useEffect(()=>{e.current=s},[s]),n}function RA(s){const[e,n]=I.useState(null),r=I.useRef(s),i=I.useCallback(l=>{const c=Ff(l.target);c&&n(d=>d?(d.set(c,Vf(c)),new Map(d)):null)},[]);return I.useEffect(()=>{const l=r.current;if(s!==l){c(l);const d=s.map(f=>{const h=Ff(f);return h?(h.addEventListener("scroll",i,{passive:!0}),[h,Vf(h)]):null}).filter(f=>f!=null);n(d.length?new Map(d):null),r.current=s}return()=>{c(s),c(l)};function c(d){d.forEach(f=>{const h=Ff(f);h?.removeEventListener("scroll",i)})}},[i,s]),I.useMemo(()=>s.length?e?Array.from(e.values()).reduce((l,c)=>Cs(l,c),kr):Yy(s):kr,[s,e])}function ab(s,e){e===void 0&&(e=[]);const n=I.useRef(null);return I.useEffect(()=>{n.current=null},e),I.useEffect(()=>{const r=s!==kr;r&&!n.current&&(n.current=s),!r&&n.current&&(n.current=null)},[s]),n.current?Bo(s,n.current):kr}function DA(s){I.useEffect(()=>{if(!kc)return;const e=s.map(n=>{let{sensor:r}=n;return r.setup==null?void 0:r.setup()});return()=>{for(const n of e)n?.()}},s.map(e=>{let{sensor:n}=e;return n}))}function OA(s,e){return I.useMemo(()=>s.reduce((n,r)=>{let{eventName:i,handler:l}=r;return n[i]=c=>{l(c,e)},n},{}),[s,e])}function ib(s){return I.useMemo(()=>s?tA(s):null,[s])}const sb=[];function LA(s,e){e===void 0&&(e=Es);const[n]=s,r=ib(n?Hn(n):null),[i,l]=I.useState(sb);function c(){l(()=>s.length?s.map(f=>Ky(f)?r:new Kf(e(f),f)):sb)}const d=$c({callback:c});return Fr(()=>{d?.disconnect(),c(),s.forEach(f=>d?.observe(f))},[s]),i}function $A(s){if(!s)return null;if(s.children.length>1)return s;const e=s.children[0];return Ho(e)?e:s}function MA(s){let{measure:e}=s;const[n,r]=I.useState(null),i=I.useCallback(h=>{for(const{target:m}of h)if(Ho(m)){r(y=>{const v=e(m);return y?{...y,width:v.width,height:v.height}:v});break}},[e]),l=$c({callback:i}),c=I.useCallback(h=>{const m=$A(h);l?.disconnect(),m&&l?.observe(m),r(m?e(m):null)},[e,l]),[d,f]=Rc(c);return I.useMemo(()=>({nodeRef:d,rect:n,setRef:f}),[n,d,f])}const NA=[{sensor:Jf,options:{}},{sensor:Yf,options:{}}],IA={current:{}},Mc={draggable:{measure:qy},droppable:{measure:qy,strategy:Vo.WhileDragging,frequency:eh.Optimized},dragOverlay:{measure:Es}};class Ko extends Map{get(e){var n;return e!=null&&(n=super.get(e))!=null?n:void 0}toArray(){return Array.from(this.values())}getEnabled(){return this.toArray().filter(e=>{let{disabled:n}=e;return!n})}getNodeFor(e){var n,r;return(n=(r=this.get(e))==null?void 0:r.node.current)!=null?n:void 0}}const jA={activatorEvent:null,active:null,activeNode:null,activeNodeRect:null,collisions:null,containerNodeRect:null,draggableNodes:new Map,droppableRects:new Map,droppableContainers:new Ko,over:null,dragOverlay:{nodeRef:{current:null},rect:null,setRef:Dc},scrollableAncestors:[],scrollableAncestorRects:[],measuringConfiguration:Mc,measureDroppableContainers:Dc,windowRect:null,measuringScheduled:!1},HA={activatorEvent:null,activators:[],active:null,activeNodeRect:null,ariaDescribedById:{draggable:""},dispatch:Dc,draggableNodes:new Map,over:null,measureDroppableContainers:Dc},Nc=I.createContext(HA),ob=I.createContext(jA);function zA(){return{draggable:{active:null,initialCoordinates:{x:0,y:0},nodes:new Map,translate:{x:0,y:0}},droppable:{containers:new Ko}}}function PA(s,e){switch(e.type){case ln.DragStart:return{...s,draggable:{...s.draggable,initialCoordinates:e.initialCoordinates,active:e.active}};case ln.DragMove:return s.draggable.active==null?s:{...s,draggable:{...s.draggable,translate:{x:e.coordinates.x-s.draggable.initialCoordinates.x,y:e.coordinates.y-s.draggable.initialCoordinates.y}}};case ln.DragEnd:case ln.DragCancel:return{...s,draggable:{...s.draggable,active:null,initialCoordinates:{x:0,y:0},translate:{x:0,y:0}}};case ln.RegisterDroppable:{const{element:n}=e,{id:r}=n,i=new Ko(s.droppable.containers);return i.set(r,n),{...s,droppable:{...s.droppable,containers:i}}}case ln.SetDroppableDisabled:{const{id:n,key:r,disabled:i}=e,l=s.droppable.containers.get(n);if(!l||r!==l.key)return s;const c=new Ko(s.droppable.containers);return c.set(n,{...l,disabled:i}),{...s,droppable:{...s.droppable,containers:c}}}case ln.UnregisterDroppable:{const{id:n,key:r}=e,i=s.droppable.containers.get(n);if(!i||r!==i.key)return s;const l=new Ko(s.droppable.containers);return l.delete(n),{...s,droppable:{...s.droppable,containers:l}}}default:return s}}function UA(s){let{disabled:e}=s;const{active:n,activatorEvent:r,draggableNodes:i}=I.useContext(Nc),l=Uf(r),c=Uf(n?.id);return I.useEffect(()=>{if(!e&&!r&&l&&c!=null){if(!qf(l)||document.activeElement===l.target)return;const d=i.get(c);if(!d)return;const{activatorNode:f,node:h}=d;if(!f.current&&!h.current)return;requestAnimationFrame(()=>{for(const m of[f.current,h.current]){if(!m)continue;const y=$_(m);if(y){y.focus();break}}})}},[r,e,i,c,l]),null}function BA(s,e){let{transform:n,...r}=e;return s!=null&&s.length?s.reduce((i,l)=>l({transform:i,...r}),n):n}function qA(s){return I.useMemo(()=>({draggable:{...Mc.draggable,...s?.draggable},droppable:{...Mc.droppable,...s?.droppable},dragOverlay:{...Mc.dragOverlay,...s?.dragOverlay}}),[s?.draggable,s?.droppable,s?.dragOverlay])}function GA(s){let{activeNode:e,measure:n,initialRect:r,config:i=!0}=s;const l=I.useRef(!1),{x:c,y:d}=typeof i=="boolean"?{x:i,y:i}:i;Fr(()=>{if(!c&&!d||!e){l.current=!1;return}if(l.current||!r)return;const h=e?.node.current;if(!h||h.isConnected===!1)return;const m=n(h),y=By(m,r);if(c||(y.x=0),d||(y.y=0),l.current=!0,Math.abs(y.x)>0||Math.abs(y.y)>0){const v=Gy(h);v&&v.scrollBy({top:y.y,left:y.x})}},[e,c,d,r,n])}const lb=I.createContext({...kr,scaleX:1,scaleY:1});var Ha;(function(s){s[s.Uninitialized=0]="Uninitialized",s[s.Initializing=1]="Initializing",s[s.Initialized=2]="Initialized"})(Ha||(Ha={}));const FA=I.memo(function(e){var n,r,i,l;let{id:c,accessibility:d,autoScroll:f=!0,children:h,sensors:m=NA,collisionDetection:y=W_,measuring:v,modifiers:S,..._}=e;const x=I.useReducer(PA,void 0,zA),[A,D]=x,[O,k]=z_(),[L,B]=I.useState(Ha.Uninitialized),q=L===Ha.Initialized,{draggable:{active:z,nodes:W,translate:de},droppable:{containers:_e}}=A,Se=z!=null?W.get(z):null,Ie=I.useRef({initial:null,translated:null}),Fe=I.useMemo(()=>{var pe;return z!=null?{id:z,data:(pe=Se?.data)!=null?pe:IA,rect:Ie}:null},[z,Se]),ze=I.useRef(null),[Et,Q]=I.useState(null),[ue,De]=I.useState(null),ee=zo(_,Object.values(_)),me=Uo("DndDescribedBy",c),H=I.useMemo(()=>_e.getEnabled(),[_e]),T=qA(v),{droppableRects:fe,measureDroppableContainers:he,measuringScheduled:Ae}=EA(H,{dragging:q,dependencies:[de.x,de.y],config:T.droppable}),se=wA(W,z),ge=I.useMemo(()=>ue?Gf(ue):null,[ue]),$e=ve(),Te=_A(se,T.draggable.measure);GA({activeNode:z!=null?W.get(z):null,config:$e.layoutShiftCompensation,initialRect:Te,measure:T.draggable.measure});const je=nb(se,T.draggable.measure,Te),xt=nb(se?se.parentElement:null),rt=I.useRef({activatorEvent:null,active:null,activeNode:se,collisionRect:null,collisions:null,droppableRects:fe,draggableNodes:W,draggingNode:null,draggingNodeRect:null,droppableContainers:_e,over:null,scrollableAncestors:[],scrollAdjustedTranslate:null}),gt=_e.getNodeFor((n=rt.current.over)==null?void 0:n.id),at=MA({measure:T.dragOverlay.measure}),Yt=(r=at.nodeRef.current)!=null?r:se,Ht=q?(i=at.rect)!=null?i:je:null,bt=!!(at.nodeRef.current&&at.rect),qe=TA(bt?null:je),mt=ib(Yt?Hn(Yt):null),Dt=kA(q?gt??se:null),zt=LA(Dt),kn=BA(S,{transform:{x:de.x-qe.x,y:de.y-qe.y,scaleX:1,scaleY:1},activatorEvent:ue,active:Fe,activeNodeRect:je,containerNodeRect:xt,draggingNodeRect:Ht,over:rt.current.over,overlayNodeRect:at.rect,scrollableAncestors:Dt,scrollableAncestorRects:zt,windowRect:mt}),dr=ge?Cs(ge,de):null,Ot=RA(Dt),$i=ab(Ot),ua=ab(Ot,[je]),bn=Cs(kn,$i),Mn=Ht?J_(Ht,kn):null,it=Fe&&Mn?y({active:Fe,collisionRect:Mn,droppableRects:fe,droppableContainers:H,pointerCoordinates:dr}):null,Pn=Py(it,"id"),[vn,da]=I.useState(null),fr=bt?kn:Cs(kn,ua),un=Y_(fr,(l=vn?.rect)!=null?l:null,je),Rn=I.useRef(null),dn=I.useCallback((pe,Ee)=>{let{sensor:xe,options:Ke}=Ee;if(ze.current==null)return;const ke=W.get(ze.current);if(!ke)return;const Me=pe.nativeEvent,Re=new xe({active:ze.current,activeNode:ke,event:Me,options:Ke,context:rt,onAbort(Pe){if(!W.get(Pe))return;const{onDragAbort:Ce}=ee.current,He={id:Pe};Ce?.(He),O({type:"onDragAbort",event:He})},onPending(Pe,Ve,Ce,He){if(!W.get(Pe))return;const{onDragPending:Lt}=ee.current,Bt={id:Pe,constraint:Ve,initialCoordinates:Ce,offset:He};Lt?.(Bt),O({type:"onDragPending",event:Bt})},onStart(Pe){const Ve=ze.current;if(Ve==null)return;const Ce=W.get(Ve);if(!Ce)return;const{onDragStart:He}=ee.current,nt={activatorEvent:Me,active:{id:Ve,data:Ce.data,rect:Ie}};ja.unstable_batchedUpdates(()=>{He?.(nt),B(Ha.Initializing),D({type:ln.DragStart,initialCoordinates:Pe,active:Ve}),O({type:"onDragStart",event:nt}),Q(Rn.current),De(Me)})},onMove(Pe){D({type:ln.DragMove,coordinates:Pe})},onEnd:tt(ln.DragEnd),onCancel:tt(ln.DragCancel)});Rn.current=Re;function tt(Pe){return async function(){const{active:Ce,collisions:He,over:nt,scrollAdjustedTranslate:Lt}=rt.current;let Bt=null;if(Ce&&Lt){const{cancelDrop:Nn}=ee.current;Bt={activatorEvent:Me,active:Ce,collisions:He,delta:Lt,over:nt},Pe===ln.DragEnd&&typeof Nn=="function"&&await Promise.resolve(Nn(Bt))&&(Pe=ln.DragCancel)}ze.current=null,ja.unstable_batchedUpdates(()=>{D({type:Pe}),B(Ha.Uninitialized),da(null),Q(null),De(null),Rn.current=null;const Nn=Pe===ln.DragEnd?"onDragEnd":"onDragCancel";if(Bt){const Gt=ee.current[Nn];Gt?.(Bt),O({type:Nn,event:Bt})}})}}},[W]),V=I.useCallback((pe,Ee)=>(xe,Ke)=>{const ke=xe.nativeEvent,Me=W.get(Ke);if(ze.current!==null||!Me||ke.dndKit||ke.defaultPrevented)return;const Re={active:Me};pe(xe,Ee.options,Re)===!0&&(ke.dndKit={capturedBy:Ee.sensor},ze.current=Ke,dn(xe,Ee))},[W,dn]),ie=CA(m,V);DA(m),Fr(()=>{je&&L===Ha.Initializing&&B(Ha.Initialized)},[je,L]),I.useEffect(()=>{const{onDragMove:pe}=ee.current,{active:Ee,activatorEvent:xe,collisions:Ke,over:ke}=rt.current;if(!Ee||!xe)return;const Me={active:Ee,activatorEvent:xe,collisions:Ke,delta:{x:bn.x,y:bn.y},over:ke};ja.unstable_batchedUpdates(()=>{pe?.(Me),O({type:"onDragMove",event:Me})})},[bn.x,bn.y]),I.useEffect(()=>{const{active:pe,activatorEvent:Ee,collisions:xe,droppableContainers:Ke,scrollAdjustedTranslate:ke}=rt.current;if(!pe||ze.current==null||!Ee||!ke)return;const{onDragOver:Me}=ee.current,Re=Ke.get(Pn),tt=Re&&Re.rect.current?{id:Re.id,rect:Re.rect.current,data:Re.data,disabled:Re.disabled}:null,Pe={active:pe,activatorEvent:Ee,collisions:xe,delta:{x:ke.x,y:ke.y},over:tt};ja.unstable_batchedUpdates(()=>{da(tt),Me?.(Pe),O({type:"onDragOver",event:Pe})})},[Pn]),Fr(()=>{rt.current={activatorEvent:ue,active:Fe,activeNode:se,collisionRect:Mn,collisions:it,droppableRects:fe,draggableNodes:W,draggingNode:Yt,draggingNodeRect:Ht,droppableContainers:_e,over:vn,scrollableAncestors:Dt,scrollAdjustedTranslate:bn},Ie.current={initial:Ht,translated:Mn}},[Fe,se,it,Mn,W,Yt,Ht,fe,_e,vn,Dt,bn]),bA({...$e,delta:de,draggingRect:Mn,pointerCoordinates:dr,scrollableAncestors:Dt,scrollableAncestorRects:zt});const ce=I.useMemo(()=>({active:Fe,activeNode:se,activeNodeRect:je,activatorEvent:ue,collisions:it,containerNodeRect:xt,dragOverlay:at,draggableNodes:W,droppableContainers:_e,droppableRects:fe,over:vn,measureDroppableContainers:he,scrollableAncestors:Dt,scrollableAncestorRects:zt,measuringConfiguration:T,measuringScheduled:Ae,windowRect:mt}),[Fe,se,je,ue,it,xt,at,W,_e,fe,vn,he,Dt,zt,T,Ae,mt]),Z=I.useMemo(()=>({activatorEvent:ue,activators:ie,active:Fe,activeNodeRect:je,ariaDescribedById:{draggable:me},dispatch:D,draggableNodes:W,over:vn,measureDroppableContainers:he}),[ue,ie,Fe,je,D,me,W,vn,he]);return Xe.createElement(Ny.Provider,{value:k},Xe.createElement(Nc.Provider,{value:Z},Xe.createElement(ob.Provider,{value:ce},Xe.createElement(lb.Provider,{value:un},h)),Xe.createElement(UA,{disabled:d?.restoreFocus===!1})),Xe.createElement(B_,{...d,hiddenTextDescribedById:me}));function ve(){const pe=Et?.autoScrollEnabled===!1,Ee=typeof f=="object"?f.enabled===!1:f===!1,xe=q&&!pe&&!Ee;return typeof f=="object"?{...f,enabled:xe}:{enabled:xe}}}),VA=I.createContext(null),cb="button",KA="Draggable";function WA(s){let{id:e,data:n,disabled:r=!1,attributes:i}=s;const l=Uo(KA),{activators:c,activatorEvent:d,active:f,activeNodeRect:h,ariaDescribedById:m,draggableNodes:y,over:v}=I.useContext(Nc),{role:S=cb,roleDescription:_="draggable",tabIndex:x=0}=i??{},A=f?.id===e,D=I.useContext(A?lb:VA),[O,k]=Rc(),[L,B]=Rc(),q=OA(c,e),z=zo(n);Fr(()=>(y.set(e,{id:e,key:l,node:O,activatorNode:L,data:z}),()=>{const de=y.get(e);de&&de.key===l&&y.delete(e)}),[y,e]);const W=I.useMemo(()=>({role:S,tabIndex:x,"aria-disabled":r,"aria-pressed":A&&S===cb?!0:void 0,"aria-roledescription":_,"aria-describedby":m.draggable}),[r,S,x,A,_,m.draggable]);return{active:f,activatorEvent:d,activeNodeRect:h,attributes:W,isDragging:A,listeners:r?void 0:q,node:O,over:v,setNodeRef:k,setActivatorNodeRef:B,transform:D}}function YA(){return I.useContext(ob)}const XA="Droppable",JA={timeout:25};function QA(s){let{data:e,disabled:n=!1,id:r,resizeObserverConfig:i}=s;const l=Uo(XA),{active:c,dispatch:d,over:f,measureDroppableContainers:h}=I.useContext(Nc),m=I.useRef({disabled:n}),y=I.useRef(!1),v=I.useRef(null),S=I.useRef(null),{disabled:_,updateMeasurementsFor:x,timeout:A}={...JA,...i},D=zo(x??r),O=I.useCallback(()=>{if(!y.current){y.current=!0;return}S.current!=null&&clearTimeout(S.current),S.current=setTimeout(()=>{h(Array.isArray(D.current)?D.current:[D.current]),S.current=null},A)},[A]),k=$c({callback:O,disabled:_||!c}),L=I.useCallback((W,de)=>{k&&(de&&(k.unobserve(de),y.current=!1),W&&k.observe(W))},[k]),[B,q]=Rc(L),z=zo(e);return I.useEffect(()=>{!k||!B.current||(k.disconnect(),y.current=!1,k.observe(B.current))},[B,k]),I.useEffect(()=>(d({type:ln.RegisterDroppable,element:{id:r,key:l,disabled:n,node:B,rect:v,data:z}}),()=>d({type:ln.UnregisterDroppable,key:l,id:r})),[r]),I.useEffect(()=>{n!==m.current.disabled&&(d({type:ln.SetDroppableDisabled,id:r,key:l,disabled:n}),m.current.disabled=n)},[r,l,n,d]),{active:c,rect:v,isOver:f?.id===r,node:B,over:f,setNodeRef:q}}function th(s,e,n){const r=s.slice();return r.splice(n<0?r.length+n:n,0,r.splice(e,1)[0]),r}function ZA(s,e){return s.reduce((n,r,i)=>{const l=e.get(r);return l&&(n[i]=l),n},Array(s.length))}function Ic(s){return s!==null&&s>=0}function ex(s,e){if(s===e)return!0;if(s.length!==e.length)return!1;for(let n=0;n{var e;let{rects:n,activeNodeRect:r,activeIndex:i,overIndex:l,index:c}=s;const d=(e=n[i])!=null?e:r;if(!d)return null;const f=rx(n,c,i);if(c===i){const h=n[l];return h?{x:ii&&c<=l?{x:-d.width-f,y:0,...jc}:c=l?{x:d.width+f,y:0,...jc}:{x:0,y:0,...jc}};function rx(s,e,n){const r=s[e],i=s[e-1],l=s[e+1];return!r||!i&&!l?0:n{let{rects:e,activeIndex:n,overIndex:r,index:i}=s;const l=th(e,r,n),c=e[i],d=l[i];return!d||!c?null:{x:d.left-c.left,y:d.top-c.top,scaleX:d.width/c.width,scaleY:d.height/c.height}},Hc={scaleX:1,scaleY:1},ax=s=>{var e;let{activeIndex:n,activeNodeRect:r,index:i,rects:l,overIndex:c}=s;const d=(e=l[n])!=null?e:r;if(!d)return null;if(i===n){const h=l[c];return h?{x:0,y:nn&&i<=c?{x:0,y:-d.height-f,...Hc}:i=c?{x:0,y:d.height+f,...Hc}:{x:0,y:0,...Hc}};function ix(s,e,n){const r=s[e],i=s[e-1],l=s[e+1];return r?nr.map(q=>typeof q=="object"&&"id"in q?q.id:q),[r]),_=c!=null,x=c?S.indexOf(c.id):-1,A=h?S.indexOf(h.id):-1,D=I.useRef(S),O=!ex(S,D.current),k=A!==-1&&x===-1||O,L=tx(l);Fr(()=>{O&&_&&m(S)},[O,S,_,m]),I.useEffect(()=>{D.current=S},[S]);const B=I.useMemo(()=>({activeIndex:x,containerId:y,disabled:L,disableTransforms:k,items:S,overIndex:A,useDragOverlay:v,sortedRects:ZA(S,f),strategy:i}),[x,y,L.draggable,L.droppable,k,S,A,f,v,i]);return Xe.createElement(db.Provider,{value:B},e)}const ox=s=>{let{id:e,items:n,activeIndex:r,overIndex:i}=s;return th(n,r,i).indexOf(e)},lx=s=>{let{containerId:e,isSorting:n,wasDragging:r,index:i,items:l,newIndex:c,previousItems:d,previousContainerId:f,transition:h}=s;return!h||!r||d!==l&&i===c?!1:n?!0:c!==i&&e===f},cx={duration:200,easing:"ease"},fb="transform",ux=qo.Transition.toString({property:fb,duration:0,easing:"linear"}),dx={roleDescription:"sortable"};function fx(s){let{disabled:e,index:n,node:r,rect:i}=s;const[l,c]=I.useState(null),d=I.useRef(n);return Fr(()=>{if(!e&&n!==d.current&&r.current){const f=i.current;if(f){const h=Es(r.current,{ignoreTransform:!0}),m={x:f.left-h.left,y:f.top-h.top,scaleX:f.width/h.width,scaleY:f.height/h.height};(m.x||m.y)&&c(m)}}n!==d.current&&(d.current=n)},[e,n,r,i]),I.useEffect(()=>{l&&c(null)},[l]),l}function hx(s){let{animateLayoutChanges:e=lx,attributes:n,disabled:r,data:i,getNewIndex:l=ox,id:c,strategy:d,resizeObserverConfig:f,transition:h=cx}=s;const{items:m,containerId:y,activeIndex:v,disabled:S,disableTransforms:_,sortedRects:x,overIndex:A,useDragOverlay:D,strategy:O}=I.useContext(db),k=px(r,S),L=m.indexOf(c),B=I.useMemo(()=>({sortable:{containerId:y,index:L,items:m},...i}),[y,i,L,m]),q=I.useMemo(()=>m.slice(m.indexOf(c)),[m,c]),{rect:z,node:W,isOver:de,setNodeRef:_e}=QA({id:c,data:B,disabled:k.droppable,resizeObserverConfig:{updateMeasurementsFor:q,...f}}),{active:Se,activatorEvent:Ie,activeNodeRect:Fe,attributes:ze,setNodeRef:Et,listeners:Q,isDragging:ue,over:De,setActivatorNodeRef:ee,transform:me}=WA({id:c,data:B,attributes:{...dx,...n},disabled:k.draggable}),H=R_(_e,Et),T=!!Se,fe=T&&!_&&Ic(v)&&Ic(A),he=!D&&ue,Ae=he&&fe?me:null,ge=fe?Ae??(d??O)({rects:x,activeNodeRect:Fe,activeIndex:v,overIndex:A,index:L}):null,$e=Ic(v)&&Ic(A)?l({id:c,items:m,activeIndex:v,overIndex:A}):L,Te=Se?.id,je=I.useRef({activeId:Te,items:m,newIndex:$e,containerId:y}),xt=m!==je.current.items,rt=e({active:Se,containerId:y,isDragging:ue,isSorting:T,id:c,index:L,items:m,newIndex:je.current.newIndex,previousItems:je.current.items,previousContainerId:je.current.containerId,transition:h,wasDragging:je.current.activeId!=null}),gt=fx({disabled:!rt,index:L,node:W,rect:z});return I.useEffect(()=>{T&&je.current.newIndex!==$e&&(je.current.newIndex=$e),y!==je.current.containerId&&(je.current.containerId=y),m!==je.current.items&&(je.current.items=m)},[T,$e,y,m]),I.useEffect(()=>{if(Te===je.current.activeId)return;if(Te!=null&&je.current.activeId==null){je.current.activeId=Te;return}const Yt=setTimeout(()=>{je.current.activeId=Te},50);return()=>clearTimeout(Yt)},[Te]),{active:Se,activeIndex:v,attributes:ze,data:B,rect:z,index:L,newIndex:$e,items:m,isOver:de,isSorting:T,isDragging:ue,listeners:Q,node:W,overIndex:A,over:De,setNodeRef:H,setActivatorNodeRef:ee,setDroppableNodeRef:_e,setDraggableNodeRef:Et,transform:gt??ge,transition:at()};function at(){if(gt||xt&&je.current.newIndex===L)return ux;if(!(he&&!qf(Ie)||!h)&&(T||rt))return qo.Transition.toString({...h,property:fb})}}function px(s,e){var n,r;return typeof s=="boolean"?{draggable:s,droppable:!1}:{draggable:(n=s?.draggable)!=null?n:e.draggable,droppable:(r=s?.droppable)!=null?r:e.droppable}}function zc(s){if(!s)return!1;const e=s.data.current;return!!(e&&"sortable"in e&&typeof e.sortable=="object"&&"containerId"in e.sortable&&"items"in e.sortable&&"index"in e.sortable)}const gx=[dt.Down,dt.Right,dt.Up,dt.Left],mx=(s,e)=>{let{context:{active:n,collisionRect:r,droppableRects:i,droppableContainers:l,over:c,scrollableAncestors:d}}=e;if(gx.includes(s.code)){if(s.preventDefault(),!n||!r)return;const f=[];l.getEnabled().forEach(y=>{if(!y||y!=null&&y.disabled)return;const v=i.get(y.id);if(v)switch(s.code){case dt.Down:r.topv.top&&f.push(y);break;case dt.Left:r.left>v.left&&f.push(y);break;case dt.Right:r.left1&&(m=h[1].id),m!=null){const y=l.get(n.id),v=l.get(m),S=v?i.get(v.id):null,_=v?.node.current;if(_&&S&&y&&v){const A=Oc(_).some((q,z)=>d[z]!==q),D=hb(y,v),O=yx(y,v),k=A||!D?{x:0,y:0}:{x:O?r.width-S.width:0,y:O?r.height-S.height:0},L={x:S.left,y:S.top};return k.x&&k.y?L:Bo(L,k)}}}};function hb(s,e){return!zc(s)||!zc(e)?!1:s.data.current.sortable.containerId===e.data.current.sortable.containerId}function yx(s,e){return!zc(s)||!zc(e)||!hb(s,e)?!1:s.data.current.sortable.index{const d=I.useId(),f=I.useRef({inProgress:!1,lastSortTime:0,lastActiveId:null}),h=q_(Iy(Jf,{activationConstraint:{distance:5}}),Iy(Yf,{coordinateGetter:mx})),m=I.useCallback(S=>{f.current={inProgress:!0,lastSortTime:Date.now(),lastActiveId:S.active.id},l?.({activeId:S.active.id})},[l]),y=I.useCallback(S=>{const{active:_,over:x}=S,A=Date.now();if(!x||_.id===x.id){f.current.inProgress=!1;return}const{lastSortTime:D,lastActiveId:O,inProgress:k}=f.current;if(O===_.id&&A-D<500&&!k)return;f.current={inProgress:!1,lastSortTime:A,lastActiveId:_.id};const L=s.findIndex(q=>String(q[e])===String(_.id)),B=s.findIndex(q=>String(q[e])===String(x.id));if(L!==-1&&B!==-1&&L!==B){const q=th(s,L,B);i?.(q,{oldIndex:L,newIndex:B})}},[s,e,i]),v=s.map(S=>String(S[e]));return yt.jsx(FA,{id:d,sensors:h,collisionDetection:F_,onDragStart:m,onDragEnd:y,children:yt.jsx(sx,{items:v,strategy:bx[n],children:r})},c!==void 0?`${d}-v${c}`:d)},pb=I.createContext(null),Sx=({value:s,children:e})=>yt.jsx(pb.Provider,{value:s,children:e}),wx=()=>I.useContext(pb),Cx=({id:s,handle:e,as:n="div",children:r})=>{const{attributes:i,listeners:l,setNodeRef:c,transform:d,transition:f,isDragging:h}=hx({id:s}),m={transform:qo.Transform.toString(d),transition:f,opacity:h?.5:1,position:"relative"};return yt.jsx(n,{ref:c,style:m,...i,...e?{}:l,"data-sortable-item":!0,"data-sortable-id":String(s),"data-dragging":h,children:yt.jsx(Sx,{value:{listeners:l,handle:e,isDragging:h},children:r})})},lt=pt("DynamicRenderer");function oa(){try{return window.G7Core?.devTools}catch{return}}function rh(s,e,n,r){if(typeof s=="string"){if(/\$t:[a-zA-Z_][a-zA-Z0-9_.\-]*/.test(s))try{return e.resolveTranslations(s,n,r)}catch{return s}return s}if(Array.isArray(s))return s.map(i=>rh(i,e,n,r));if(s&&typeof s=="object"){const i={};for(const[l,c]of Object.entries(s))i[l]=rh(c,e,n,r);return i}return s}function Ex(s){const e=Object.keys(s);return e.length>0&&e.every(n=>/^\d+$/.test(n))}const ur=(s,e)=>{const n=["errors"],r={...s};for(const i of Object.keys(e)){const l=e[i],c=s[i];if(l===null){r[i]=null;continue}if(n.includes(i)){r[i]=l;continue}if(Array.isArray(l)){r[i]=l;continue}if(Array.isArray(c)&&l!==null&&typeof l=="object"&&!Array.isArray(l)&&Ex(l)){const d=Object.keys(l).map(h=>parseInt(h,10));if((d.length>0?Math.max(...d):0)>=c.length+d.length+10)r[i]={...l};else{const h=[...c];for(const[m,y]of Object.entries(l)){const v=parseInt(m,10);v>=0&&v=h.length&&(h[v]=y)}r[i]=h}}else l!==null&&typeof l=="object"&&c!==null&&typeof c=="object"&&!Array.isArray(c)?r[i]=ur(c,l):r[i]=l}return r},_x=(s,e,n,r)=>{if(!e||typeof e!="object"||Array.isArray(e))return s;const i=ur(s,e);return Ry(i,n,r)},Pc=(s,e)=>{let n=null;const r=()=>(n===null&&(n={...s}),n);for(const i of Object.keys(e)){if(!(i in s))continue;const l=e[i],c=s[i];if(l!==null&&typeof l=="object"&&!Array.isArray(l)&&c!==null&&typeof c=="object"&&!Array.isArray(c)){const d=Pc(c,l);if(d===c)continue;Object.keys(d).length===0?delete r()[i]:r()[i]=d}else delete r()[i]}return n??s},Ax=s=>{const e={},n=s.split(";").filter(r=>r.trim());for(const r of n){const i=r.indexOf(":");if(i===-1)continue;const l=r.substring(0,i).trim(),c=r.substring(i+1).trim();if(l&&c){const d=l.replace(/-([a-z])/g,(f,h)=>h.toUpperCase());e[d]=c}}return e};class xx extends I.Component{constructor(e){super(e),this.state={hasError:!1,error:null}}static getDerivedStateFromError(e){return{hasError:!0,error:e}}componentDidCatch(e,n){lt.error(`컴포넌트 렌더링 에러 (ID: ${this.props.componentId}, Name: ${this.props.componentName}):`,e,n)}render(){return this.state.hasError?this.props.fallback?this.props.fallback:yt.jsxs("div",{className:"p-4 my-2 bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded-lg text-red-800 dark:text-red-400",children:[yt.jsx("div",{className:"font-semibold mb-1",children:"컴포넌트 로드 실패"}),yt.jsxs("div",{className:"text-xs opacity-80",children:[this.props.componentName&&`[${this.props.componentName}] `,"데이터를 표시할 수 없습니다."]}),this.state.error&&yt.jsxs("details",{className:"mt-2",children:[yt.jsx("summary",{className:"cursor-pointer text-xs opacity-70 hover:opacity-100",children:"상세 정보"}),yt.jsx("div",{className:"mt-1 p-2 bg-red-100 dark:bg-red-900/30 rounded text-xs font-mono whitespace-pre-wrap break-all",children:this.state.error.message})]})]}):this.props.children}}const Vr=I.memo(({componentDef:s,dataContext:e,translationContext:n,registry:r,bindingEngine:i,translationEngine:l,actionDispatcher:c,parentComponentContext:d,parentFormContextProp:f,parentDataContext:h,isRootRenderer:m=!1,isInsideIteration:y=!1,isEditMode:v=!1,onComponentSelect:S,onComponentHover:_,onDragStart:x,onDragEnd:A,componentPath:D,layoutKey:O})=>{const[k,L]=I.useState({loadingActions:{}});I.useLayoutEffect(()=>{d||(i.startRenderCycle(),window.__g7SetLocalOverrideKeys||(window.__g7ForcedLocalFields=void 0),window.__g7PendingLocalState=null)});const B=I.useRef(null);I.useLayoutEffect(()=>{if(!d&&e._localInit&&typeof e._localInit=="object"){const{_forceLocalInit:V,_merge:ie,...ce}=e._localInit,Z=`${JSON.stringify(ce)}:${V||"no-force"}`;if(B.current!==Z){B.current=Z;const ve=ie||"shallow",Ee=window.G7Core?.state?.get?.()?._local||{},xe=window.__g7PendingLocalState||{},Ke={...Ee,...xe};ve==="replace"?window.__g7PendingLocalState={loadingActions:Ke.loadingActions||{},...ce,hasChanges:!1}:ve==="deep"?window.__g7PendingLocalState=ur(Ke,{...ce,hasChanges:!1}):window.__g7PendingLocalState={...Ke,...ce,hasChanges:!1};const ke=window.__g7SetLocalOverrideKeys;ke&&typeof ke=="object"&&(ve==="replace"?window.__g7SetLocalOverrideKeys={...ce,hasChanges:!1}:window.__g7SetLocalOverrideKeys=ur(ke,{...ce,hasChanges:!1}))}}}),I.useLayoutEffect(()=>{if(!d){const V=window.__g7SetLocalOverrideKeys;if(V){L(ve=>Pc(ve,V));const ie=V,ce=window.__g7ForcedLocalFields,Z=window.__g7LastSetLocalSnapshot;queueMicrotask(()=>{window.__g7SetLocalOverrideKeys===ie&&(window.__g7SetLocalOverrideKeys=void 0),window.__g7ForcedLocalFields===ce&&(window.__g7ForcedLocalFields=void 0),window.__g7LastSetLocalSnapshot===Z&&(window.__g7LastSetLocalSnapshot=void 0)})}}},[e._local]);const q=I.useRef(O);I.useEffect(()=>{s._fromBase&&O&&q.current!==O&&(q.current=O,L({loadingActions:{}}))},[O,s._fromBase]);const z=I.useRef(""),W=I.useRef(!1),de=I.useRef(null),_e=I.useRef(null),Se=I.useRef({}),Ie=I.useRef({}),Fe=I.useRef(h),ze=k_(),Et=i_(),Q=Et?.getParentDataContext()??h;I.useEffect(()=>{if(e._localInit&&typeof e._localInit=="object"){const{_forceLocalInit:V,...ie}=e._localInit,ce=JSON.stringify(ie),Z=V!==void 0,ve=Nf(),pe=`${ce}:${V||"no-force"}`,Ee=h_({globalTrackedKey:ve.hash,instanceHandledKey:_e.current,trackingKey:pe});if(Ee==="apply"){_e.current=pe,ve.hash=pe,ve.timestamp=V,z.current=ce,Z&&(W.current=!0);const{_merge:Ke,...ke}=ie,Me=Ke||"shallow";L(Re=>Me==="replace"?{loadingActions:Re.loadingActions||{},...ke,hasChanges:!1}:Me==="deep"?ur(Re,{...ke,hasChanges:!1}):{...Re,...ke,hasChanges:!1}),e._globalSetState&&e._globalSetState(Re=>{const tt=Re?._local||{};let Pe;return Me==="replace"?Pe={...ke,hasChanges:!1}:Me==="deep"?Pe=ur(tt,{...ke,hasChanges:!1}):Pe={...tt,...ke,hasChanges:!1},{...Re,_local:Pe}}),i.invalidateCacheByKeys(["_local"]),lt.log("_localInit applied (data changed):",Object.keys(ie))}else if(Ee==="prune"){_e.current=pe;const{_merge:Ke,...ke}=ie;L(Re=>Pc(Re,ke)),Pc(k,ke)!==k&&(i.invalidateCacheByKeys(["_local"]),lt.log("_localInit pruned (applied by another renderer):",Object.keys(ke)))}de.current=e._localInit,d_(e._localInit)}},[e._localInit,h,i]);const{isTransitioning:ue}=Ay(),De=S_(),ee=f??De,{width:me}=jf(),H=wx(),T=I.useMemo(()=>{let V=s;if(s.type==="iterator"&&s.data){const{data:Z,itemName:ve,indexName:pe,...Ee}=s;V={...Ee,iteration:{source:Z,item_var:ve||"item",index_var:pe}}}if(!V.responsive)return V;const ie=Ci.getMatchingKey(V.responsive,me);if(!ie)return V;const ce=V.responsive[ie];return{...V,props:{...V.props,...ce.props},children:ce.children??V.children,text:ce.text??V.text,if:ce.if??V.if,iteration:ce.iteration??V.iteration,__responsiveChildrenKey:ce.children!==void 0?ie:void 0}},[s,me]),fe=I.useCallback(V=>{const ie=V?.__setStateId;if(L(ce=>{const Z=window.__g7PendingLocalState,pe=window.__g7SetLocalOverrideKeys||Z,Ee=pe?ur(ce,pe):ce;if(typeof V=="function"){const Re=V(Ee);return ur(Ee,Re)}const{__mergeMode:xe,__setStateId:Ke,...ke}=V;let Me;if(xe==="replace"?Me=ke:xe==="shallow"?Me={...Ee,...ke}:Me=ur(Ee,ke),xe!==void 0){const Re=window.__g7ForcedLocalFields;Re&&(window.__g7ForcedLocalFields={...Re,...ke})}return Me}),ie){const ce=oa();if(ce?.isEnabled()){const Z=T.id||"unknown",ve=T.name||"Unknown";setTimeout(()=>{ce.trackComponentRender?.(Z,ve,0,["_local"],[]),ce.completeStateChange?.(ie)},0)}}},[T.id,T.name]),he=d?.state??k,Ae=d?.setState??fe,se=I.useMemo(()=>{const V=e._localInit&&e._localInit!==de.current;let ie;V&&e._local?ie=e._local:ie=e._local?ur(e._local,he):he;const ce=window.__g7ForcedLocalFields;ce&&(ie=ur(ie,ce));const Z={...e,_local:ie},ve=e._computedDefinitions;if(ve&&Object.keys(ve).length>0){const pe=new $n,Ee={...Z,...ze?{_isolated:ze.state}:{}},xe={},Ke=oa(),ke=Ke?.isEnabled()??!1;for(const[Me,Re]of Object.entries(ve)){const tt=ke?Date.now():0,Pe=ke?e._computed?.[Me]:void 0;let Ve;try{if(typeof Re=="string")if(Re.startsWith("{{")&&Re.endsWith("}}")){const Ce=Re.slice(2,-2).trim(),He=Wn(Ce)?pe.evaluatePipeExpression(Ce,Ee,{skipCache:!0}):pe.evaluateExpression(Ce,Ee,{skipCache:!0});xe[Me]=He}else xe[Me]=Re;else if(Re&&typeof Re=="object"&&"$switch"in Re){const Ce=pe.resolveSwitch(Re,Ee,{skipCache:!0});xe[Me]=Ce}}catch(Ce){lt.warn(`Failed to recalculate computed value: ${Me}`,Ce),xe[Me]=e._computed?.[Me],Ve=Ce instanceof Error?Ce.message:String(Ce)}if(ke&&(Ve||Pe!==xe[Me])){const He=Date.now()-tt;Ke.trackComputedProperty?.(Me,typeof Re=="string"?Re:JSON.stringify(Re),[{type:"_local",path:"_local",value:Z._local}],xe[Me],He,T.id,Ve),Pe!==xe[Me]&&Ke.trackComputedRecalc?.(Me,"dependency-change",Pe,xe[Me],He,{type:"_local",path:"_local"},T.id)}}Z._computed=xe}if(ze&&(Z._isolated=ze.state),Q&&(Z.$parent=Q),s.extensionPointProps){const pe=i.resolveObject(s.extensionPointProps,Z,y?{skipCache:!0}:void 0);Z.extensionPointProps=v&&l?rh(pe,l,n,Z):pe}return s.extensionPointCallbacks&&(Z.extensionPointCallbacks=s.extensionPointCallbacks),v&&(Z.$templateId===void 0&&n?.templateId&&(Z.$templateId=n.templateId),Z.$locale===void 0&&n?.locale&&(Z.$locale=n.locale)),Z},[e,he,ze?.state,Q,Et?.version,s.extensionPointProps,s.extensionPointCallbacks]);Se.current=se._local,Ie.current=se._computed||{},Fe.current=Q,I.useEffect(()=>{const V=oa();if(!V?.isEnabled())return;const ie=se._local;ie&&V.updateLocalState(ie);const ce=se._computed;ce&&V.updateComputedState(ce),Q&&V.updateParentContext?.(Q);const Z=T.id||`comp-${Date.now()}`,ve=T.name||"Unknown",pe={global:Object.keys(e._global||{}),local:Object.keys(ie||{}),context:d?Object.keys(d.state||{}):[]},Ee={type:d?"componentContext":"dynamicState",hasComponentContext:!!d,parentId:d?"parent":void 0};V.trackComponentStateSource?.(Z,ve,pe,Ee),he&&Object.keys(he).length>0&&V.trackDynamicState?.(Z,he);const xe=!!(T.expandChildren||T.props?.expandChildren);V.trackContextFlow?.(Z,ve,!!d,xe,!0)},[se._local,e,T.id,T.name,d,he,T.expandChildren,T.props?.expandChildren,Q]);const ge=I.useMemo(()=>({state:{...se._local},setState:Ae,stateRef:Se,computedRef:Ie,isolatedContext:ze,parentDataContext:se}),[se._local,Ae,k,ze,se]),$e=I.useMemo(()=>{const V=fs({if:T.if,condition:T.condition,conditions:T.conditions},se,i,T.id);if(T.if||T.condition!==void 0||T.conditions){const ce=oa();ce?.isEnabled()&&T.if&&ce.trackIfCondition(T.id||`if-${Date.now()}`,T.if,V)}return V},[T.if,T.conditions,se,i,T.id]),Te=I.useMemo(()=>{if(!T.slot)return null;const V=T.slot;if(V.startsWith("{{")&&V.endsWith("}}")){const ie=V.slice(2,-2).trim();try{const ce={componentId:T.id,componentName:T.name,propName:"slot"},Z=Wn(ie)?i.evaluatePipeExpression(ie,se,void 0,ce):i.evaluateExpression(ie,se,ce);return lt.log(`[Slot] 표현식 평가: "${ie}" => "${Z}" (컴포넌트: ${T.id})`),typeof Z=="string"?Z:null}catch(ce){return lt.warn(`slot 표현식 평가 실패 (컴포넌트: ${T.id}):`,ce),null}}return lt.log(`[Slot] 정적 슬롯 ID: "${V}" (컴포넌트: ${T.id})`),V},[T.slot,T.id,se,i]),je=I.useMemo(()=>!Te||!T.id?"":`${T.id}-${Te}-${JSON.stringify(T.slotOrder??0)}`,[Te,T.id,T.slotOrder]),xt=I.useRef(null);I.useEffect(()=>{const V=window.__slotContextValue,ie=V?.isEnabled??!1;if(T.slot&<.log(`[Slot Registration] 컴포넌트: ${T.id}, isSlotEnabled: ${ie}, shouldRender: ${$e}, resolvedSlotId: ${Te}`),!ie||!$e){T.slot&<.log(`[Slot Registration] 등록 스킵 - isSlotEnabled: ${ie}, shouldRender: ${$e}`),xt.current&&T.id&&V&&(V.unregisterFromSlot(xt.current,T.id),xt.current=null);return}if(!Te||!T.id){T.slot&<.log(`[Slot Registration] 등록 스킵 - resolvedSlotId: ${Te}, id: ${T.id}`),xt.current&&T.id&&V&&(V.unregisterFromSlot(xt.current,T.id),xt.current=null);return}xt.current&&xt.current!==Te&&V.unregisterFromSlot(xt.current,T.id);const ce={...T,slot:void 0,slotOrder:void 0};return V.registerToSlot(Te,T.id,{componentDef:ce,dataContext:e,order:T.slotOrder??0,parentFormContext:f??null,getParentComponentContext:()=>ge,translationContext:n,registrationKey:je}),xt.current=Te,()=>{const Z=window.__slotContextValue;Te&&T.id&&Z&&Z.unregisterFromSlot(Te,T.id)}},[$e,Te,T,e,f,ge,n,je]);const rt=I.useCallback(V=>{if(typeof V=="string"){if(ic(V))return Yd(V);if(sc(V)){const ie=[],ce=V.replace(new RegExp(`${bo}([^${Si}]*)${Si}`,"g"),(ve,pe)=>(ie.push(pe),`${$a}${ie.length-1}${$a}`));let Z=ce;return/\$t:[a-zA-Z0-9._-]+/.test(ce)&&(Z=l.resolveTranslations(ce,n,se)),Z.replace(new RegExp(`${$a}(\\d+)${$a}`,"g"),(ve,pe)=>ie[parseInt(pe)])}if(V.startsWith("$t:defer:"))return V;if(/\$t:[a-zA-Z0-9._-]+/.test(V)){const ie=V.trim();return ie.startsWith("{")&&ie.endsWith("}")||ie.startsWith("[")&&ie.endsWith("]")?V:l.resolveTranslations(V,n,se)}return V}if(Array.isArray(V))return V.map(ie=>rt(ie));if(V&&typeof V=="object"){const ie={};for(const[ce,Z]of Object.entries(V))ie[ce]=rt(Z);return ie}return V},[l,n,se]),gt=I.useCallback(V=>yo(V),[]),at=I.useCallback(V=>La(V),[]),Yt=I.useMemo(()=>{let V={...T.props||{}};const ie=["cellChildren","expandChildren","expandContext","render"],Z=r.getMetadata(T.name)?.skipBindingKeys||[],ve=[...new Set([...ie,...Z])],pe=y?{skipCache:!0}:void 0,Ee={...pe??{},skipBindingKeys:ve},xe=Ce=>{if(pe)return pe;if(Ce.startsWith("_computed")||Ce.startsWith("$computed"))return{skipCache:!0}};for(const[Ce,He]of Object.entries(V))if(!ve.includes(Ce))if(typeof He=="string"){const nt=at(He);if(nt!==null){let Lt=!1,Bt=nt;nt.startsWith(sr)&&(Lt=!0,Bt=nt.slice(sr.length));const Nn={componentId:T.id,componentName:T.name,propName:Ce};let Gt;if(Wn(Bt))try{Gt=i.evaluatePipeExpression(Bt,se,xe(Bt),Nn)}catch(Ft){lt.warn(`파이프 표현식 평가 실패 (컴포넌트: ${T.id}, prop: ${Ce}):`,Ft),Gt=void 0}else if(gt(Bt))try{Gt=i.evaluateExpression(Bt,se,Nn)}catch(Ft){lt.warn(`표현식 평가 실패 (컴포넌트: ${T.id}, prop: ${Ce}):`,Ft),Gt=void 0}else Gt=i.resolve(Bt,se,xe(Bt),Nn);V[Ce]=Lt&&Gt!=null?ds(Gt):Gt}else{const Lt={componentId:T.id,componentName:T.name,propName:Ce};V[Ce]=i.resolveBindings(He,se,pe,Lt)}}else Array.isArray(He)?V[Ce]=He.map(nt=>typeof nt=="string"?i.resolveBindings(nt,se,pe):typeof nt=="object"&&nt!==null?i.resolveObject(nt,se,Ee):nt):He&&typeof He=="object"&&(V[Ce]=i.resolveObject(He,se,Ee));for(const[Ce,He]of Object.entries(V))V[Ce]=rt(He);if(typeof V.style=="string"&&(V.style=Ax(V.style)),V=cf(V,v?void 0:T.actions,se,{componentContext:ge,actionDispatcher:c}),T.classMap){const Ce=K0(T.classMap,se,i,{skipCache:!0});if(Ce){const He=V.className||"";V.className=He?`${He} ${Ce}`:Ce}}T.style&&(V.style=T.style),T.actions&&T.actions.length>0&&T.type==="composite"&&Object.assign(V,he),T.component_layout&&(V.__componentLayoutDefs=T.component_layout);const Ke=T.expandChildren||T.props?.expandChildren,ke=T.props?.cellChildren,Me=T.children&&T.children.length>0,Re=T.component_layout,tt=T.slot,Pe=T.props?.modalId,Ve=T.type==="composite";return(Ke||ke||Me||Re||tt||Pe||Ve)&&(V.__componentContext=ge),V},[T.props,T.actions,T.style,T.classMap,T.id,se,n,i,l,ge,he,rt,gt,at,y]),Ht=I.useRef(null),bt=I.useMemo(()=>Ht.current&&Ty(Ht.current,Yt)?Ht.current:(Ht.current=Yt,Yt),[Yt]),qe=I.useMemo(()=>{const V=T.id;if(typeof V!="string"||!V.includes("{{"))return V;const ie=i.resolveBindings(V,se,{skipCache:!0});return typeof ie=="string"?ie:V},[T.id,se,i]),mt=I.useMemo(()=>{const V=T.dataKey,ie=T.trackChanges,ce=T.debounce;if(V){const Z=V.startsWith("_global."),ve=Z?V.slice(8):V,pe=Z?e._global:se._local;return{dataKey:ve,trackChanges:ie??!1,debounce:ce,setState:Z?xe=>{e._globalSetState?e._globalSetState(xe):lt.warn("_global.formData 사용 시 _globalSetState가 필요합니다.")}:Ae,state:pe??{},_isGlobal:Z}}if(ee.dataKey)return ee},[T.dataKey,T.trackChanges,T.debounce,T.id,Ae,se._local,e._global,e._globalSetState,ee]),Dt=I.useRef(new Set);I.useEffect(()=>{Dt.current.clear()},[e._global?._remountKeys]);const zt=I.useCallback((V,ie)=>{const ce=e._global?._remountKeys;return V&&ce?.[V]&&!Dt.current.has(V)?(Dt.current.add(V),`${V}-remount-${ce[V]}`):V||ie},[e._global?._remountKeys]),kn=I.useMemo(()=>{if(T.text!==void 0){if(typeof T.text=="string"){const V=T.text;let ie=V;const ce={componentId:T.id,componentName:T.name,propName:"text",skipCache:!0},Z=at(V);if(Z!==null){let ve=!1,pe=Z;if(Z.startsWith(sr)&&(ve=!0,pe=Z.slice(sr.length)),Wn(pe))try{ie=i.evaluatePipeExpression(pe,se,{skipCache:!0},ce)}catch(Ee){lt.warn(`text 파이프 표현식 평가 실패 (컴포넌트: ${T.id}):`,Ee),ie=""}else if(gt(pe))try{ie=i.evaluateExpression(pe,se,ce)}catch(Ee){lt.warn(`text 표현식 평가 실패 (컴포넌트: ${T.id}):`,Ee),ie=""}else ie=i.resolve(pe,se,{skipCache:!0},ce);ve&&ie!=null&&(ie=ds(ie))}else V.includes("{{")&&(ie=i.resolveBindings(V,se,{skipCache:!0},ce));return ie=rt(ie),ie??""}return T.text}return!T.children||T.children.length===0?null:T.children.map((V,ie)=>{if(typeof V=="string")return V;const ce=T.__responsiveChildrenKey,Z=ce?`responsive.${ce}.children`:"children",ve=D?`${D}.${Z}.${ie}`:`${ie}`,pe=V._fromBase?V.id||`child-${ie}`:zt(V.id,`child-${ie}`),Ee=!V._fromBase&&O?`${pe}_${O}`:pe;return yt.jsx(Vr,{componentDef:V,dataContext:se,translationContext:n,registry:r,bindingEngine:i,translationEngine:l,actionDispatcher:c,parentComponentContext:ge,parentFormContextProp:mt,isInsideIteration:y,isEditMode:v,onComponentSelect:S,onComponentHover:_,componentPath:ve,onDragStart:x,onDragEnd:A,layoutKey:O},Ee)})},[T.text,T.children,T.id,e,se,n,r,i,l,c,ge,mt,at,gt,rt,zt,y,v,S,_,D,x,A,O]),dr=I.useMemo(()=>{if(!T.iteration)return null;try{const V=lf(T.iteration.source,se,i);if(!Array.isArray(V))return lt.warn(`iteration source가 배열이 아닙니다 (컴포넌트: ${T.id}):`,V),null;const ie=oa();return ie?.isEnabled()&&ie.trackIteration(T.id||`iteration-${Date.now()}`,T.iteration.source,T.iteration.item_var,T.iteration.index_var,V.length),V.map((ce,Z)=>{const ve={...se,[T.iteration.item_var]:ce,[`${T.iteration.item_var}_index`]:Z};T.iteration.index_var&&(ve[T.iteration.index_var]=Z);const pe=Z===0||Z===V.length-1;ie?.isEnabled()&&pe&&ie.trackNestedContext?.({componentId:`${T.id||"unknown"}-iter-${Z}`,componentType:"iteration",parentContext:{available:Object.keys(se),values:{}},ownContext:{added:[T.iteration.item_var,...T.iteration.index_var?[T.iteration.index_var]:[]],values:{[T.iteration.item_var]:ce,...T.iteration.index_var?{[T.iteration.index_var]:Z}:{}}},depth:1});const Ee={...T,iteration:void 0,responsive:void 0},xe=D?`${D}.iteration.${Z}`:`iteration.${Z}`,Ke=`${zt(T.id,"item")}-${Z}`;return yt.jsx(Vr,{componentDef:Ee,dataContext:ve,translationContext:n,registry:r,bindingEngine:i,translationEngine:l,actionDispatcher:c,parentComponentContext:ge,parentFormContextProp:mt,isInsideIteration:!0,isEditMode:v,onComponentSelect:S,onComponentHover:_,componentPath:xe,onDragStart:x,onDragEnd:A,layoutKey:O},Ke)})}catch(V){return lt.error(`iteration 렌더링 실패 (컴포넌트: ${T.id}):`,V),null}},[T.iteration,T.children,T.id,e,se,n,r,i,l,c,ge,mt,zt,v,S,_,D,x,A,O]),[Ot,$i]=I.useState(0),ua=I.useMemo(()=>{if(!T.sortable||!T.itemTemplate)return null;const{sortable:V,itemTemplate:ie}=T;try{const ce=lf(V.source,se,i);if(!Array.isArray(ce))return lt.warn(`sortable source가 배열이 아닙니다 (컴포넌트: ${T.id}):`,ce),null;if(ce.length===0)return null;const Z=V.itemKey||"id",ve=V.strategy||"verticalList",pe=V.itemVar||"$item",Ee=V.indexVar||"$index",xe=V.handle,Ke=V.wrapperElement,ke=(tt,Pe)=>{const Ve=T.actions?.filter(Ce=>Ce.event==="onSortEnd"||Ce.type==="onSortEnd");if($i(Ce=>Ce+1),Ve&&Ve.length>0&&c){const Ce={...se,$sortedItems:tt,$sortEvent:Pe};Ve.forEach(He=>{try{const nt=c.createHandler(He,Ce,ge),Lt=new Event("sortend");nt(Lt)}catch(nt){lt.error(`onSortEnd action failed: ${He.handler}`,nt)}})}},Me=tt=>{const Pe=T.actions?.filter(Ve=>Ve.event==="onSortStart"||Ve.type==="onSortStart");if(Pe&&Pe.length>0&&c){const Ve={...se,$activeId:tt.activeId};Pe.forEach(Ce=>{try{const He=c.createHandler(Ce,Ve,ge),nt=new Event("sortstart");He(nt)}catch(He){lt.error(`onSortStart action failed: ${Ce.handler}`,He)}})}},Re=ce.map((tt,Pe)=>{const Ve=String(tt[Z]),Ce={...se,[pe]:tt,[Ee]:Pe},He=D?`${D}.sortable.${Pe}`:`sortable.${Pe}`,nt=`${zt(T.id,"sortable-item")}-v${Ot}-${Ve}`;return yt.jsx(Cx,{id:Ve,handle:xe,...Ke?{as:Ke}:{},children:(Ke&&ie.children?ie.children:[ie]).map((Lt,Bt)=>yt.jsx(Vr,{componentDef:Lt,dataContext:Ce,translationContext:n,registry:r,bindingEngine:i,translationEngine:l,actionDispatcher:c,parentComponentContext:ge,parentFormContextProp:void 0,isInsideIteration:!0,isEditMode:v,onComponentSelect:S,onComponentHover:_,componentPath:He,onDragStart:x,onDragEnd:A,layoutKey:O},`${nt}-child-${Bt}`))},nt)});return yt.jsx(vx,{items:ce,itemKey:Z,strategy:ve,onSortEnd:ke,onSortStart:Me,sortVersion:Ot,children:Re})}catch(ce){return lt.error(`sortable 렌더링 실패 (컴포넌트: ${T.id}):`,ce),null}},[T.sortable,T.itemTemplate,T.actions,T.id,e,se,n,r,i,l,c,ge,zt,v,S,_,D,x,A,Ot,O]),bn=I.useRef(!1);I.useEffect(()=>{if(bn.current)return;bn.current=!0;const V=oa();if(V?.isEnabled()&&V.trackMount(s.id,{name:s.name,type:s.type||"component",props:bt}),s.lifecycle?.onMount&&s.lifecycle.onMount.length>0)for(const ce of s.lifecycle.onMount)try{const Z=c.createHandler(ce,se,ge),ve=new Event("mount");Z(ve),lt.log(`Lifecycle onMount executed: ${ce.handler} (Component: ${s.id})`)}catch(Z){lt.error(`Lifecycle onMount failed: ${ce.handler} (Component: ${s.id})`,Z)}const ie=[];if(s.onComponentEvent&&s.onComponentEvent.length>0){const ce=window.G7Core;if(ce?.componentEvent?.on)for(const Z of s.onComponentEvent){if(!Z.event){lt.warn(`onComponentEvent: event name is required (Component: ${s.id})`);continue}const ve=async Ee=>{try{const xe={...se,_eventData:Ee},ke=await c.createHandler({handler:Z.handler,params:Z.params,onSuccess:Z.onSuccess,onError:Z.onError},xe,ge)(new CustomEvent(Z.event,{detail:Ee}));return lt.log(`onComponentEvent "${Z.event}" handled: ${Z.handler} (Component: ${s.id})`,{eventData:Ee,result:ke}),ke}catch(xe){throw lt.error(`onComponentEvent "${Z.event}" failed: ${Z.handler} (Component: ${s.id})`,xe),xe}},pe=ce.componentEvent.on(Z.event,ve);ie.push(pe),lt.log(`onComponentEvent: Subscribed to "${Z.event}" (Component: ${s.id})`)}else lt.warn(`onComponentEvent: G7Core.componentEvent is not available (Component: ${s.id})`)}return()=>{const ce=oa();ce?.isEnabled()&&ce.trackUnmount(s.id);for(const Z of ie)try{Z()}catch(ve){lt.error(`onComponentEvent: Failed to unsubscribe (Component: ${s.id})`,ve)}if(ie.length>0&<.log(`onComponentEvent: Unsubscribed ${ie.length} event(s) (Component: ${s.id})`),s.lifecycle?.onUnmount&&s.lifecycle.onUnmount.length>0)for(const Z of s.lifecycle.onUnmount)try{const ve=c.createHandler(Z,se,ge),pe=new Event("unmount");ve(pe),lt.log(`Lifecycle onUnmount executed: ${Z.handler} (Component: ${s.id})`)}catch(ve){lt.error(`Lifecycle onUnmount failed: ${Z.handler} (Component: ${s.id})`,ve)}}},[]);const Mn=I.useMemo(()=>{const V=bt.dataKey,ie=bt.trackChanges,ce=bt.debounce;return V?{dataKey:V,trackChanges:ie??!1,debounce:ce,setState:Ae,state:he}:null},[bt.dataKey,bt.trackChanges,bt.debounce,Ae,he]);I.useEffect(()=>{const V=T.dataKey||bt.dataKey;if(!V)return;const ie=oa();if(!ie?.isEnabled())return;const ce=[],Z=pe=>{if(pe)for(const Ee of pe){const xe=Ee?.props?.name;if(xe&&ce.push({name:xe,type:Ee.name||Ee.type||"unknown"}),Ee?.children&&Z(Ee.children),Ee?.slots)for(const Ke of Object.values(Ee.slots))Array.isArray(Ke)&&Z(Ke)}};if(s.children&&Z(s.children),s.slots)for(const pe of Object.values(s.slots))Array.isArray(pe)&&Z(pe);const ve=s.id||`form-${V}`;return ie.trackForm(ve,V,ce),()=>{ie.untrackForm(ve)}},[T,bt.dataKey,s.id,s.children]);const it=I.useRef(null),Pn=I.useRef(void 0);I.useEffect(()=>{const V=bt?.name;if(!V||!ee.dataKey||!ee.setState||bt?.autoBinding===!1)return;const ie=`${ee.dataKey}.${V}`,ce=window.__g7AutoBindingPaths??new Map;return window.__g7AutoBindingPaths=ce,ce.set(ie,(ce.get(ie)??0)+1),()=>{const Z=ce.get(ie)??0;Z<=1?ce.delete(ie):ce.set(ie,Z-1)}},[bt?.name,bt?.autoBinding,ee.dataKey,ee.setState]);const vn=I.useMemo(()=>{const{dataKey:V,trackChanges:ie,debounce:ce,...Z}=bt,ve=Z.name;if(!ve||!ee.dataKey||!ee.setState)return Z;if(Z.autoBinding===!1){const{autoBinding:Ve,...Ce}=Z;return Ce}const pe=`${ee.dataKey}.${ve}`,Ee=w_(ee.state,pe),xe=Ee!==void 0?Ee:Z.value,ke=r.getMetadata(s.name)?.bindingType,Me=typeof xe=="boolean"&&(ke==="checked"||ke==="checkable"&&["checkbox","radio"].includes(Z?.type)),Re=ee.debounce,tt=Ve=>{const Ce=ee.state||{},He=Ry(Ce,pe,Ve);if(ee.trackChanges&&(He.hasChanges=!0),window.__g7PendingLocalState=_x(He,window.__g7ForcedLocalFields,pe,Ve),ee.setState(He),ee._isGlobal)return;const nt=window.G7Core,Lt={[pe]:Ve};ee.trackChanges&&(Lt.hasChanges=!0),nt?.state?.setLocal?.(Lt,{render:!1})},Pe=(Ve,Ce)=>{Re&&Re>0?(it.current&&clearTimeout(it.current),it.current=setTimeout(()=>{tt(Ve),it.current=null,Ce&&(Ce(),window.__g7PendingLocalState=null)},Re)):(tt(Ve),Ce&&(Ce(),window.__g7PendingLocalState=null))};if(Me)return{...Z,checked:xe,onChange:Ce=>{const He=Ce.target.checked;tt(He),Z.onChange&&Z.onChange(Ce)}};{const Ve=He=>{const nt=He?.target?.value!==void 0?He.target.value:He;Re&&Re>0&&(Pn.current=nt);const Lt=He?.target?{target:{value:nt,name:He.target.name,type:He.target.type,checked:He.target.checked},currentTarget:He.currentTarget?{value:nt,name:He.currentTarget.name}:void 0}:He;Pe(nt,()=>{Z.onChange&&Z.onChange(Lt)})};let Ce;return Pn.current!==void 0?it.current!==null?Ce=Pn.current:String(xe??"")===String(Pn.current)?(Pn.current=void 0,Ce=xe??""):Ce=Pn.current:Ce=xe??"",{...Z,value:Ce,onChange:Ve}}},[bt,ee,ee.state,ee.debounce]),da=I.useMemo(()=>{const V=oa();if(V?.isEnabled()&&V.trackRender(s.name),!$e)return T.iteration?dr:null;if(T.sortable&&T.itemTemplate)return ua;if(T.iteration)return dr;if(T.type==="extension_point"){const{layout:ke,columns:Me,gap:Re,className:tt,...Pe}=T.props||{};let Ve=tt||"";return ke==="grid"&&Me?Ve=`${Ve} grid grid-cols-${Me} gap-${Re||4}`.trim():ke==="flex"&&(Ve=`${Ve} flex flex-col gap-${Re||4}`.trim()),yt.jsx("div",{id:qe,className:Ve||void 0,...Pe,children:kn})}const ie=r.getComponent(T.name);if(!ie)return lt.error(`컴포넌트를 찾을 수 없습니다: ${T.name} (ID: ${T.id})`,"componentDef:",JSON.stringify(T,null,2)),null;const ce=e._global?._remountKeys,Z={...vn,id:vn.id??qe,...ce&&Object.keys(ce).length>0&&{__remountTrigger:JSON.stringify(ce)}};if(H?.listeners&&H.handle&&vn["data-drag-handle"]!==void 0&&(Object.assign(Z,H.listeners),Z.style={...Z.style,cursor:H.isDragging?"grabbing":"grab"}),v){const ke=T.id||`auto_${T.name||T.type||"unknown"}_${Math.random().toString(36).substring(2,8)}`,Me=Pe=>{typeof Pe.preventDefault=="function"&&Pe.preventDefault(),S&&(Pe.stopPropagation(),S(ke,Pe))},Re={"data-editor-id":ke,"data-editor-name":T.name,"data-editor-type":T.type,onClick:Me};D&&(Re["data-editor-path"]=D);const tt=T.__editorSlotName??T.slot;typeof tt=="string"&&(Re["data-editor-slot"]=tt),_&&(Re.onMouseMove=Pe=>{Pe.stopPropagation(),_(ke,Pe)},Re.onMouseLeave=Pe=>{_(null,Pe)}),Object.assign(Z,Re),Z.editorAttrs=Re}const ve=T.type==="basic",pe=T.type==="layout",xe=ve||pe?(()=>{const ke={};for(const[Me,Re]of Object.entries(Z))Me.startsWith("__")||Me==="editorAttrs"&&ve||(ke[Me]=Re);return ke})():Z;let Ke=yt.jsx(ie,{...xe,children:kn});if(mt&&(Ke=yt.jsx(v_,{value:mt,children:Ke})),T.isolatedState){const ke=typeof T.isolatedState=="object"?T.isolatedState:{},Me=e?._isolatedInit,Re=Me?{...ke,...Me}:ke;Ke=yt.jsx(T_,{initialState:Re,scopeId:T.isolatedScopeId,children:Ke})}return Ke},[$e,T.iteration,T.sortable,T.itemTemplate,T.type,T.name,T.id,T.props,T.isolatedState,T.isolatedScopeId,r,vn,kn,dr,ua,H,Mn,v,S,_,x,A,D,e._global?._remountKeys]),fr=I.useMemo(()=>{const V=T.blur_until_loaded;if(!V||e?._global?.__isPreview)return!1;if(typeof V=="string"){const Z=at(V);if(Z!==null)try{const ve={componentId:T.id,componentName:T.name,propName:"blur_until_loaded"};return!!i.evaluateExpression(Z,se,ve)}catch(ve){return lt.warn(`blur_until_loaded 표현식 평가 실패 (컴포넌트: ${T.id}):`,ve),!1}return!1}if(typeof V=="object"&&V!==null){if(!V.enabled)return!1;if(V.data_sources)return(Array.isArray(V.data_sources)?V.data_sources:[V.data_sources]).some(Ee=>e[Ee]===void 0);if(ue)return!0;const Z=["route","query","_global","_local","_dataSourceErrors"];return Object.keys(e).filter(pe=>!Z.includes(pe)&&!pe.startsWith("_")).some(pe=>e[pe]===void 0)}if(ue)return!0;const ie=["route","query","_global","_local","_dataSourceErrors"];return Object.keys(e).filter(Z=>!ie.includes(Z)&&!Z.startsWith("_")).some(Z=>e[Z]===void 0)},[T.blur_until_loaded,T.id,ue,e,se,i,at]);if(fr){const V=T.blur_until_loaded;if(typeof V=="object"&&V?.data_sources){const ce=(Array.isArray(V.data_sources)?V.data_sources:[V.data_sources]).reduce((ve,pe)=>(ve[pe]=e[pe]===void 0?"UNDEFINED":"LOADED",ve),{}),Z=Object.keys(e).filter(ve=>!ve.startsWith("_"));lt.log(`[BLUR APPLIED] Component: ${T.id}, data_sources status:`,ce,"Available keys:",Z)}else lt.log(`[BLUR APPLIED] Component: ${T.id}, blur_until_loaded:`,V)}const un=I.useMemo(()=>{if(!T.blur_until_loaded)return{wrapperClasses:"",innerClasses:""};const V=Yt?.className||"";if(!V)return{wrapperClasses:"",innerClasses:""};const ie=[/^(sm:|md:|lg:|xl:|2xl:)?(col-span-|row-span-|col-start-|col-end-|row-start-|row-end-)/,/^(sm:|md:|lg:|xl:|2xl:)?(self-|place-self-|order-)/,/^(sm:|md:|lg:|xl:|2xl:)?(flex-shrink|flex-grow|flex-\d|basis-)/],ce=V.split(/\s+/).filter(Boolean),Z=[],ve=[];for(const pe of ce)ie.some(xe=>xe.test(pe))?Z.push(pe):ve.push(pe);return{wrapperClasses:Z.join(" "),innerClasses:ve.join(" ")}},[T.blur_until_loaded,Yt?.className]),Rn=window.__slotContextValue;return Te&&Rn?.isEnabled?null:yt.jsx(xx,{componentId:s.id,componentName:s.name,children:T.blur_until_loaded?yt.jsx("div",{className:[un.wrapperClasses,fr?"opacity-50 blur-sm transition-all duration-300 pointer-events-none":void 0].filter(Boolean).join(" ")||void 0,children:da}):da})});Vr.displayName="DynamicRenderer",typeof window<"u"&&(window.__DynamicRenderer=Vr);function Tx(s){const e={};try{const r=new URL(s,typeof window<"u"?window.location.origin:"http://localhost").searchParams,i=new Set;for(const l of r.keys()){if(i.has(l))continue;i.add(l);const c=r.getAll(l);c.length>1||l.endsWith("[]")?e[l]=c:e[l]=c[0]}}catch{}return e}function kx(s){try{return new URL(s,typeof window<"u"?window.location.origin:"http://localhost").pathname}catch{return s.split("?")[0]}}const Li=class Li{constructor(){M(this,"config",{enabled:!1,maxHistorySize:100,logLevel:"info",serverEndpoint:"/_boost/g7-debug/dump-state",autoCapture:!0});M(this,"stateHistory",[]);M(this,"snapshotIdCounter",0);M(this,"stateWatchers",new Map);M(this,"currentLocalState",{});M(this,"currentComputedState",{});M(this,"currentParentContext");M(this,"actionHistory",[]);M(this,"actionWatchers",new Set);M(this,"cacheStats",{hits:0,misses:0,entries:0});M(this,"cacheDecisions",[]);M(this,"cacheDecisionIdCounter",0);M(this,"maxCacheDecisions",200);M(this,"currentRenderCycleId",null);M(this,"isInActionExecution",!1);M(this,"isInIteration",!1);M(this,"mountedComponents",new Map);M(this,"eventListeners",new Map);M(this,"mountWatchers",new Set);M(this,"unmountWatchers",new Set);M(this,"renderCounts",new Map);M(this,"bindingEvalCount",0);M(this,"profilingData",[]);M(this,"isProfiling",!1);M(this,"profilingStartTime",0);M(this,"activeRequests",new Map);M(this,"requestHistory",[]);M(this,"pendingDataSources",new Set);M(this,"requestWatchers",new Set);M(this,"wsConnections",new Map);M(this,"wsMessageHistory",[]);M(this,"wsMessageWatchers",new Set);M(this,"ifConditions",new Map);M(this,"iterations",new Map);M(this,"conditionWatchers",new Set);M(this,"forms",new Map);M(this,"formWatchers",new Set);M(this,"expressionHistory",[]);M(this,"expressionWatchers",new Set);M(this,"expressionIdCounter",0);M(this,"dataSources",new Map);M(this,"dataPathTransforms",[]);M(this,"maxDataPathTransforms",100);M(this,"nestedContexts",[]);M(this,"nestedContextIdCounter",0);M(this,"maxNestedContexts",100);M(this,"formBindingIssues",[]);M(this,"formBindingValidations",[]);M(this,"formBindingIssueIdCounter",0);M(this,"maxFormBindingIssues",100);M(this,"computedProperties",new Map);M(this,"computedRecalcLogs",[]);M(this,"computedRecalcIdCounter",0);M(this,"maxComputedRecalcLogs",100);M(this,"handlers",new Map);M(this,"componentEventSubscriptions",new Map);M(this,"componentEventEmitHistory",[]);M(this,"componentEventIdCounter",0);M(this,"maxComponentEventHistory",100);M(this,"stateRenderingLogs",[]);M(this,"currentStateChangeContext",null);M(this,"stateRenderingIdCounter",0);M(this,"componentRenderCounts",new Map);M(this,"stateToComponentMap",new Map);M(this,"maxStateRenderingLogs",100);M(this,"componentStateSources",new Map);M(this,"contextFlowNodes",new Map);M(this,"dynamicStates",new Map);M(this,"styleIssues",[]);M(this,"componentStyles",new Map);M(this,"authEvents",[]);M(this,"authHeaderHistory",[]);M(this,"authEventIdCounter",0);M(this,"maxAuthEventHistory",50);M(this,"logHistory",[]);M(this,"logIdCounter",0);M(this,"maxLogHistory",500);M(this,"currentLayout",null);M(this,"layoutHistory",[]);M(this,"layoutIdCounter",0);M(this,"layoutStats",{totalLoads:0,cacheHits:0,apiLoads:0});M(this,"maxLayoutHistory",50);M(this,"executionDetails",new Map);M(this,"stateChangeHistory",[]);M(this,"dataSourceChangeHistory",[]);M(this,"changeAlerts",[]);M(this,"changeExpectations",new Map);M(this,"executionIdCounter",0);M(this,"stateChangeIdCounter",0);M(this,"alertIdCounter",0);M(this,"maxExecutionDetails",100);M(this,"maxStateChangeHistory",200);M(this,"maxChangeAlerts",100);M(this,"sequenceExecutions",[]);M(this,"sequenceExecutionStack",[]);M(this,"sequenceIdCounter",0);M(this,"maxSequenceExecutions",50);M(this,"staleClosureWarnings",[]);M(this,"staleClosureIdCounter",0);M(this,"maxStaleClosureWarnings",100);M(this,"stateCaptureRegistry",new Map);M(this,"modalStates",new Map);M(this,"modalStateIssues",[]);M(this,"modalStateRelations",[]);M(this,"modalStateChangeLogs",[]);M(this,"modalStateIssueIdCounter",0);M(this,"modalStateChangeLogIdCounter",0);M(this,"maxModalStateIssues",100);M(this,"maxModalStateChangeLogs",200);M(this,"namedActionDefinitions",{});M(this,"namedActionRefLogs",[]);M(this,"namedActionRefIdCounter",0);M(this,"maxNamedActionRefLogs",200);M(this,"debounceActionHistory",[]);M(this,"dataSourceUpdateHistory",[]);M(this,"debounceActionIdCounter",0);M(this,"dataSourceUpdateIdCounter",0)}static getInstance(){return Li.instance||(Li.instance=new Li),Li.instance}initialize(){if(this.config.enabled=this.checkDebugMode(),!this.config.enabled){console.log("[G7DevTools] 비활성화됨 (환경설정 > 고급 설정 > 디버그 모드를 켜세요)");return}console.log("[G7DevTools] 활성화됨"),this.setupGlobalErrorHandler()}checkDebugMode(){try{if(window.G7Config?.debug===!0)return!0;const r=window.G7Core?.state?.get?.();return r?.settings?.advanced?.debug_mode===!0||r?._global?.settings?.advanced?.debug_mode===!0}catch{return!1}}isEnabled(){return this.config.enabled}setupGlobalErrorHandler(){window.addEventListener("error",e=>{this.config.enabled&&this.logAction({id:this.generateId(),type:"globalError",startTime:Date.now(),status:"error",error:{name:"Error",message:e.message,stack:e.error?.stack}})}),window.addEventListener("unhandledrejection",e=>{this.config.enabled&&this.logAction({id:this.generateId(),type:"unhandledRejection",startTime:Date.now(),status:"error",error:{name:"UnhandledRejection",message:String(e.reason)}})})}captureStateSnapshot(e){if(!this.config.enabled)return;const n={id:++this.snapshotIdCounter,timestamp:Date.now(),source:e.source,prev:this.sanitizeObject(e.prev),next:this.sanitizeObject(e.next),diff:e.diff};this.stateHistory.push(n),this.stateHistory.length>this.config.maxHistorySize&&this.stateHistory.shift(),this.notifyStateWatchers(n)}getState(){try{const e=window.G7Core,n=e?.state?.get?.()||{},r={};if(e?._isolatedStates&&typeof e._isolatedStates=="object")for(const[i,l]of Object.entries(e._isolatedStates))r[i]=this.sanitizeObject(l);return{_global:n._global||n,_local:this.currentLocalState,_computed:this.currentComputedState,_isolated:Object.keys(r).length>0?r:void 0,$parent:this.currentParentContext}}catch{return{_global:{},_local:this.currentLocalState,_computed:this.currentComputedState}}}updateLocalState(e){this.config.enabled&&(this.currentLocalState=this.sanitizeObject(e))}updateComputedState(e){this.config.enabled&&(this.currentComputedState=this.sanitizeObject(e))}updateParentContext(e){this.config.enabled&&(this.currentParentContext=e?this.sanitizeObject(e):void 0)}getStateHistory(){return[...this.stateHistory]}getDualStorageMismatch(){const e=this.currentLocalState||{};let n={};try{n=(window.G7Core?.state?.get?.()||{})._local||{}}catch{n={}}const r=this.findMismatchedLeafPaths(e,n);return{hasMismatch:r.length>0,mismatchedPaths:r,storageA:e,storageB:n}}findMismatchedLeafPaths(e,n,r=""){const i=[],l=new Set([...Object.keys(e||{}),...Object.keys(n||{})]);for(const c of l){const d=r?`${r}.${c}`:c,f=e?.[c],h=n?.[c],m=f&&typeof f=="object"&&!Array.isArray(f),y=h&&typeof h=="object"&&!Array.isArray(h);if(m&&y)i.push(...this.findMismatchedLeafPaths(f,h,d));else{const v=f===void 0?"__undefined__":JSON.stringify(f),S=h===void 0?"__undefined__":JSON.stringify(h);v!==S&&i.push(d)}}return i}watchState(e,n){return this.stateWatchers.has(e)||this.stateWatchers.set(e,new Set),this.stateWatchers.get(e).add(n),()=>{this.stateWatchers.get(e)?.delete(n)}}notifyStateWatchers(e){if(this.stateWatchers.get("*")?.forEach(n=>{try{n(e.next,e.prev,"*")}catch(r){console.error("[G7DevTools] State watcher error:",r)}}),e.diff)for(const n of e.diff.changed)this.stateWatchers.get(n.path)?.forEach(r=>{try{r(n.newValue,n.oldValue,n.path)}catch(i){console.error("[G7DevTools] State watcher error:",i)}})}diffSnapshots(e,n){const r=this.stateHistory.find(l=>l.id===e),i=this.stateHistory.find(l=>l.id===n);return!r||!i?null:this.calculateDiff(r.next,i.next)}calculateDiff(e,n,r=""){const i={added:[],removed:[],changed:[]},l=new Set(Object.keys(e)),c=new Set(Object.keys(n));for(const d of c){const f=r?`${r}.${d}`:d;l.has(d)?JSON.stringify(e[d])!==JSON.stringify(n[d])&&i.changed.push({path:f,oldValue:e[d],newValue:n[d]}):i.added.push(f)}for(const d of l){const f=r?`${r}.${d}`:d;c.has(d)||i.removed.push(f)}return i}logAction(e){if(!this.config.enabled)return;const n=this.actionHistory.findIndex(r=>r.id===e.id);n>=0?this.actionHistory[n]={...this.actionHistory[n],...e}:(this.actionHistory.push(e),this.actionHistory.length>this.config.maxHistorySize&&this.actionHistory.shift()),this.isProfiling&&this.profilingData.push({type:"action",action:e.type,timestamp:performance.now(),duration:e.duration}),this.notifyActionWatchers(e)}getActionHistory(){return[...this.actionHistory]}watchActions(e){return this.actionWatchers.add(e),()=>{this.actionWatchers.delete(e)}}notifyActionWatchers(e){this.actionWatchers.forEach(n=>{try{n(e)}catch(r){console.error("[G7DevTools] Action watcher error:",r)}})}getActionMetrics(){const e=this.actionHistory.filter(l=>l.status==="success"),n=this.actionHistory.filter(l=>l.status==="error"),r={};for(const l of this.actionHistory)r[l.type]=(r[l.type]||0)+1;const i=e.reduce((l,c)=>l+(c.duration||0),0);return{totalActions:this.actionHistory.length,successCount:e.length,errorCount:n.length,averageDuration:e.length>0?i/e.length:0,actionsByType:r}}recordCacheHit(){this.config.enabled&&this.cacheStats.hits++}recordCacheMiss(){this.config.enabled&&this.cacheStats.misses++}updateCacheEntries(e){this.config.enabled&&(this.cacheStats.entries=e)}getCacheStats(){const e=this.cacheStats.hits+this.cacheStats.misses;return{...this.cacheStats,hitRate:e>0?this.cacheStats.hits/e:0}}resetCacheStats(){this.cacheStats={hits:0,misses:0,entries:0}}trackCacheDecision(e){if(!this.config.enabled)return;const n={id:`cache-${++this.cacheDecisionIdCounter}`,timestamp:Date.now(),expression:e.expression,decision:e.decision,reason:e.reason,context:{isInIteration:this.isInIteration,isInAction:this.isInActionExecution,renderCycleId:this.currentRenderCycleId||void 0,componentId:e.componentId,skipCacheOption:e.skipCacheOption},cachedValue:e.cachedValue!==void 0?this.safeClone(e.cachedValue):void 0,freshValue:e.freshValue!==void 0?this.safeClone(e.freshValue):void 0,valueMatch:e.cachedValue!==void 0&&e.freshValue!==void 0?JSON.stringify(e.cachedValue)===JSON.stringify(e.freshValue):void 0,duration:e.duration};this.cacheDecisions.push(n),this.cacheDecisions.length>this.maxCacheDecisions&&this.cacheDecisions.shift(),e.decision==="cache_hit"?this.cacheStats.hits++:(e.decision==="cache_miss"||e.decision==="skip_cache")&&this.cacheStats.misses++}startRenderCycle(e){return this.currentRenderCycleId=`render-${Date.now()}-${e||"root"}`,this.currentRenderCycleId}endRenderCycle(){this.currentRenderCycleId=null}startActionContext(){this.isInActionExecution=!0}endActionContext(){this.isInActionExecution=!1}startIterationContext(){this.isInIteration=!0}endIterationContext(){this.isInIteration=!1}getCacheDecisionTrackingInfo(){const e=this.getCacheDecisionStats();return{decisions:[...this.cacheDecisions],stats:e,timestamp:Date.now()}}getCacheDecisionStats(){const e=this.cacheDecisions;let n=0,r=0,i=0,l=0;const c={},d={};for(const h of e)h.decision==="cache_hit"?n++:h.decision==="cache_miss"?r++:h.decision==="skip_cache"?i++:h.decision==="invalidate"&&l++,c[h.reason]=(c[h.reason]||0)+1,h.context.componentId&&(d[h.context.componentId]=(d[h.context.componentId]||0)+1);const f=n+r+i;return{totalDecisions:e.length,cacheHits:n,cacheMisses:r,skipCacheCount:i,invalidateCount:l,byReason:c,byComponent:d,avgHitRate:f>0?n/f:0}}getRecentCacheDecisions(e=20,n){let r=[...this.cacheDecisions];return n&&(r=r.filter(i=>i.decision===n)),r.slice(-e)}clearCacheDecisionData(){this.cacheDecisions=[],this.cacheDecisionIdCounter=0}summarizePropsForLifecycle(e){if(!e||typeof e!="object")return e;const n={},r=["children","ref","key"];for(const[i,l]of Object.entries(e))if(!r.includes(i))if(l==null)n[i]=l;else if(Array.isArray(l))n[i]=`[Array(${l.length})]`;else if(typeof l=="object"){const c=Object.keys(l);c.length<=3?n[i]=`{${c.join(", ")}}`:n[i]=`{${c.slice(0,3).join(", ")}, ... +${c.length-3}}`}else typeof l=="string"&&l.length>50?n[i]=l.substring(0,50)+"...":typeof l=="function"?n[i]="[Function]":n[i]=l;return n}trackMount(e,n){if(!this.config.enabled)return;const r=this.summarizePropsForLifecycle(n.props),i={...n,props:r,id:e,mountTime:Date.now()};this.mountedComponents.set(e,i),this.mountWatchers.forEach(l=>{try{l({componentId:e,componentName:n.name,timestamp:i.mountTime})}catch(c){console.error("[G7DevTools] Mount watcher error:",c)}})}trackUnmount(e){if(!this.config.enabled)return;const n=this.mountedComponents.get(e),r=this.eventListeners.get(e)||[];r.length>0&&console.warn(`[G7DevTools] 컴포넌트 ${e} 언마운트 시 ${r.length}개 리스너 미정리`),n&&this.unmountWatchers.forEach(i=>{try{i({componentId:e,componentName:n.name,timestamp:Date.now(),orphanedListeners:r.length})}catch(l){console.error("[G7DevTools] Unmount watcher error:",l)}}),this.mountedComponents.delete(e)}trackListener(e,n,r){if(!this.config.enabled)return;const i=this.eventListeners.get(e)||[];i.push({type:n,target:r,addedAt:Date.now()}),this.eventListeners.set(e,i)}removeListener(e,n){if(!this.config.enabled)return;const r=this.eventListeners.get(e)||[],i=r.findIndex(l=>l.type===n);i>=0&&r.splice(i,1)}getMountedComponents(){return Array.from(this.mountedComponents.values())}getOrphanedListeners(){const e=[];for(const[n,r]of this.eventListeners)this.mountedComponents.has(n)||e.push(...r.map(i=>({...i,componentId:n})));return e}watchMount(e){return this.mountWatchers.add(e),()=>{this.mountWatchers.delete(e)}}watchUnmount(e){return this.unmountWatchers.add(e),()=>{this.unmountWatchers.delete(e)}}trackRender(e){if(!this.config.enabled)return;const n=this.renderCounts.get(e)||0;this.renderCounts.set(e,n+1),this.isProfiling&&this.profilingData.push({type:"render",component:e,timestamp:performance.now()})}trackBindingEval(e){this.config.enabled&&(this.bindingEvalCount++,this.isProfiling&&e&&this.profilingData.push({type:"binding",expression:e,timestamp:performance.now()}))}getRenderCount(){return new Map(this.renderCounts)}getBindingEvalCount(){return this.bindingEvalCount}getMemoryWarnings(){const e=[];try{const i=this.getState(),l=JSON.stringify(i).length;l>1e6&&e.push({type:"large-state",message:`상태 크기가 ${(l/1024/1024).toFixed(2)}MB입니다`,suggestion:"불필요한 데이터 정리 필요",severity:l>5e6?"error":"warning"})}catch{}this.stateHistory.length>this.config.maxHistorySize*.8&&e.push({type:"large-history",message:`상태 이력이 ${this.stateHistory.length}개입니다`,suggestion:"maxHistory 설정 조정 필요",severity:"warning"});const n=this.getOrphanedListeners().length;n>0&&e.push({type:"orphaned-listeners",message:`${n}개의 정리되지 않은 이벤트 리스너`,suggestion:"useEffect cleanup 또는 removeEventListener 확인",severity:n>10?"error":"warning"});const r=Array.from(this.renderCounts.entries()).filter(([,i])=>i>50);return r.length>0&&e.push({type:"excessive-renders",message:`${r.length}개 컴포넌트가 50회 이상 렌더링`,suggestion:"React.memo, useMemo, useCallback 사용 고려",severity:"warning"}),e}startProfiling(){this.config.enabled&&(this.isProfiling=!0,this.profilingData=[],this.profilingStartTime=performance.now(),console.log("[G7DevTools] 프로파일링 시작"))}stopProfiling(){this.isProfiling=!1;const e=performance.now()-this.profilingStartTime,n=this.analyzeProfile(e);return console.log("[G7DevTools] 프로파일링 완료",n),n}analyzeProfile(e){const n=this.profilingData.filter(h=>h.type==="render"),r=this.profilingData.filter(h=>h.type==="binding"),i=this.profilingData.filter(h=>h.type==="action"),l={};for(const h of n)h.component&&(l[h.component]||(l[h.component]={count:0,durations:[]}),l[h.component].count++,h.duration&&l[h.component].durations.push(h.duration));const c=Object.entries(l).map(([h,m])=>({name:h,renderCount:m.count,avgDuration:m.durations.length>0?m.durations.reduce((y,v)=>y+v,0)/m.durations.length:0})).sort((h,m)=>m.renderCount-h.renderCount).slice(0,10),d={};for(const h of r)h.expression&&(d[h.expression]=(d[h.expression]||0)+1);const f=Object.entries(d).sort(([,h],[,m])=>m-h).slice(0,10).map(([h])=>h);return{duration:e,entries:this.profilingData,summary:{totalRenders:n.length,totalBindings:r.length,totalActions:i.length,slowestComponents:c,hotPaths:f}}}resetPerformanceStats(){this.renderCounts.clear(),this.bindingEvalCount=0,this.profilingData=[]}trackRequest(e,n,r){if(!this.config.enabled)return"";const i=this.generateId(),l=kx(e),c=Tx(e);return this.activeRequests.set(i,{id:i,url:l,fullUrl:e,queryParams:c,method:n,startTime:Date.now(),status:"pending",requestBody:r?.requestBody?this.sanitizeObject(r.requestBody):void 0,dataSourceId:r?.dataSourceId}),this.isProfiling&&this.profilingData.push({type:"network",timestamp:performance.now()}),i}completeRequest(e,n,r){if(!this.config.enabled)return;const i=this.activeRequests.get(e);if(!i)return;const l={...i,status:n>=200&&n<300?"success":"error",statusCode:n,duration:Date.now()-i.startTime,endTime:Date.now(),response:this.sanitizeObject(r)};this.requestHistory.push(l),this.activeRequests.delete(e),this.requestHistory.length>this.config.maxHistorySize&&this.requestHistory.shift(),this.notifyRequestWatchers(l)}failRequest(e,n){if(!this.config.enabled)return;const r=this.activeRequests.get(e);if(!r)return;const i={...r,status:"error",error:n,duration:Date.now()-r.startTime,endTime:Date.now()};this.requestHistory.push(i),this.activeRequests.delete(e),this.notifyRequestWatchers(i)}trackDataSource(e){this.config.enabled&&this.pendingDataSources.add(e)}completeDataSource(e){this.config.enabled&&this.pendingDataSources.delete(e)}getActiveRequests(){return Array.from(this.activeRequests.values())}getRequestHistory(){return[...this.requestHistory]}getPendingDataSources(){return Array.from(this.pendingDataSources)}watchRequest(e){return this.requestWatchers.add(e),()=>{this.requestWatchers.delete(e)}}notifyRequestWatchers(e){this.requestWatchers.forEach(n=>{try{n(e)}catch(r){console.error("[G7DevTools] Request watcher error:",r)}})}trackWebSocketConnection(e,n,r){this.config.enabled&&this.wsConnections.set(e,{id:e,url:n,state:r,connectedAt:r==="open"?Date.now():void 0})}trackWebSocketMessage(e,n,r,i){if(!this.config.enabled)return;const l={id:this.generateId(),connectionId:e,direction:n,type:r,payload:this.sanitizeObject(i),timestamp:Date.now(),sequence:this.wsMessageHistory.length};this.wsMessageHistory.push(l),this.wsMessageHistory.length>this.config.maxHistorySize&&this.wsMessageHistory.shift(),this.notifyWebSocketWatchers(l)}getWebSocketConnections(){return Array.from(this.wsConnections.values())}getWebSocketMessageHistory(){return[...this.wsMessageHistory]}getWebSocketConnectionState(){const e=Array.from(this.wsConnections.values());return e.some(n=>n.state==="open")?"connected":e.some(n=>n.state==="connecting")?"reconnecting":"disconnected"}watchWebSocketMessage(e){return this.wsMessageWatchers.add(e),()=>{this.wsMessageWatchers.delete(e)}}notifyWebSocketWatchers(e){this.wsMessageWatchers.forEach(n=>{try{n(e)}catch(r){console.error("[G7DevTools] WebSocket watcher error:",r)}})}trackIfCondition(e,n,r){if(!this.config.enabled)return;const i=this.ifConditions.get(e),l=i?.evaluatedValue;this.ifConditions.set(e,{id:e,expression:n,evaluatedValue:r,evaluationCount:(i?.evaluationCount||0)+1,lastEvaluated:Date.now()}),i&&l!==r&&this.notifyConditionWatchers({id:e,expression:n,oldValue:l,newValue:r,timestamp:Date.now()})}trackIteration(e,n){this.config.enabled&&this.iterations.set(e,{id:e,...n,lastRendered:Date.now()})}getIfConditions(){return Array.from(this.ifConditions.values())}getIterations(){return Array.from(this.iterations.values())}getScopeChain(e){const n=[],r=this.getState();return n.push({name:"_global",value:r._global,source:"global"}),n.push({name:"_local",value:r._local,source:"local"}),n.push({name:"_computed",value:r._computed,source:"computed"}),n}watchConditionChange(e){return this.conditionWatchers.add(e),()=>{this.conditionWatchers.delete(e)}}notifyConditionWatchers(e){this.conditionWatchers.forEach(n=>{try{n(e)}catch(r){console.error("[G7DevTools] Condition watcher error:",r)}})}trackForm(e,n,r){this.config.enabled&&this.forms.set(e,{id:e,dataKey:n,inputs:r,trackedAt:Date.now()})}trackFormChange(e,n,r){if(!this.config.enabled)return;const i={formId:e,inputName:n,value:r,timestamp:Date.now()};this.notifyFormWatchers(i)}untrackForm(e){this.forms.delete(e)}getForms(){return Array.from(this.forms.values())}getFormState(e){const n=this.getState();return this.getNestedValue(n,e)||{}}getBindingPath(e,n){return`${e}.${n}`}watchFormChange(e){return this.formWatchers.add(e),()=>{this.formWatchers.delete(e)}}notifyFormWatchers(e){this.formWatchers.forEach(n=>{try{n(e)}catch(r){console.error("[G7DevTools] Form watcher error:",r)}})}trackFormBindingIssue(e,n,r){if(!this.config.enabled)return;const i={id:`form-issue-${++this.formBindingIssueIdCounter}`,timestamp:Date.now(),type:e,severity:n,formId:r.formId,formDataKey:r.formDataKey,inputInfo:r.inputInfo,contextInfo:r.contextInfo,description:r.description,suggestion:r.suggestion,docLink:r.docLink};this.formBindingIssues.push(i),this.formBindingIssues.length>this.maxFormBindingIssues&&this.formBindingIssues.shift()}validateFormBinding(e,n,r,i){const l=r.map(f=>{const h=[];let m=!0;return f.name||(h.push("Input에 name 속성이 없습니다"),m=!1,this.trackFormBindingIssue("missing-input-name","error",{formId:e,formDataKey:n,inputInfo:{name:f.name,type:f.type},description:"Input에 name 속성이 없어 자동 바인딩이 동작하지 않습니다",suggestion:"Input에 name 속성을 추가하세요",docLink:"troubleshooting-components-form.md"})),n||(h.push("Form에 dataKey가 없습니다"),m=!1),{inputName:f.name||"(unnamed)",inputType:f.type,bindingPath:n&&f.name?`${n}.${f.name}`:"-",isValid:m,currentValue:f.value,issues:h}});if(n||this.trackFormBindingIssue("missing-datakey","error",{formId:e,description:"Form에 dataKey가 설정되지 않아 자동 바인딩이 동작하지 않습니다",suggestion:"Form 컴포넌트에 dataKey props를 설정하세요",docLink:"troubleshooting-components-form.md"}),i.isContextBroken){const f=i.breakReason?.includes("sortable")?"sortable-context-break":i.breakReason?.includes("modal")?"modal-context-isolation":"context-not-propagated";this.trackFormBindingIssue(f,"warning",{formId:e,formDataKey:n,contextInfo:{hasParentFormContext:i.hasParentContext,parentFormContextProp:i.parentContextProp,isInsideSortable:i.breakReason?.includes("sortable")||!1,isInsideModal:i.breakReason?.includes("modal")||!1,depth:0},description:i.breakReason||"Form 컨텍스트가 제대로 전파되지 않았습니다",suggestion:"parentFormContextProp={undefined}를 확인하거나 중첩 구조를 검토하세요",docLink:"troubleshooting-components-form.md"})}const c={formId:e,dataKey:n,timestamp:Date.now(),contextPropagation:i,inputBindings:l,isValid:l.every(f=>f.isValid)&&!!n&&!i.isContextBroken,issueCount:l.filter(f=>!f.isValid).length+(n?0:1)+(i.isContextBroken?1:0)},d=this.formBindingValidations.findIndex(f=>f.formId===e);return d>=0?this.formBindingValidations[d]=c:this.formBindingValidations.push(c),c}getFormBindingValidationTrackingInfo(){const e=this.getFormBindingValidationStats();return{issues:[...this.formBindingIssues],validations:[...this.formBindingValidations],stats:e,timestamp:Date.now()}}getFormBindingValidationStats(){const e={"missing-datakey":0,"missing-input-name":0,"context-not-propagated":0,"sortable-context-break":0,"modal-context-isolation":0,"duplicate-input-name":0,"value-type-mismatch":0,"binding-path-invalid":0},n={info:0,warning:0,error:0};for(const i of this.formBindingIssues)e[i.type]++,n[i.severity]++;const r=Object.entries(e).filter(([,i])=>i>0).sort((i,l)=>l[1]-i[1]).slice(0,5).map(([i,l])=>({type:i,count:l,description:this.getIssueTypeDescription(i)}));return{totalFormsValidated:this.formBindingValidations.length,validForms:this.formBindingValidations.filter(i=>i.isValid).length,formsWithIssues:this.formBindingValidations.filter(i=>!i.isValid).length,totalIssues:this.formBindingIssues.length,byIssueType:e,bySeverity:n,topIssues:r}}getIssueTypeDescription(e){return{"missing-datakey":"Form에 dataKey 누락","missing-input-name":"Input에 name 속성 누락","context-not-propagated":"Form 컨텍스트 전파 실패","sortable-context-break":"Sortable 컨테이너에서 컨텍스트 단절","modal-context-isolation":"모달에서 부모 Form 컨텍스트 격리","duplicate-input-name":"동일 Form 내 중복 Input name","value-type-mismatch":"값 타입과 Input 타입 불일치","binding-path-invalid":"바인딩 경로 유효하지 않음"}[e]||e}clearFormBindingIssues(){this.formBindingIssues=[],this.formBindingValidations=[],this.formBindingIssueIdCounter=0}trackComputedProperty(e,n,r,i,l,c,d){if(!this.config.enabled)return;const f=`computed-${e}-${c||"global"}`,h={id:f,name:e,expression:n,componentId:c,dependencies:r,currentValue:this.safeClone(i),lastComputedAt:Date.now(),computationTime:l,error:d};this.computedProperties.set(f,h)}trackComputedRecalc(e,n,r,i,l,c,d){if(!this.config.enabled)return;const f=`computed-${e}-${d||"global"}`,h=JSON.stringify(r)!==JSON.stringify(i),m={id:`recalc-${++this.computedRecalcIdCounter}`,computedId:f,computedName:e,timestamp:Date.now(),trigger:n,triggeredBy:c,previousValue:this.safeClone(r),newValue:this.safeClone(i),valueChanged:h,computationTime:l,cascadeCount:0};this.computedRecalcLogs.push(m),this.computedRecalcLogs.length>this.maxComputedRecalcLogs&&this.computedRecalcLogs.shift();const y=this.computedProperties.get(f);y&&(y.currentValue=this.safeClone(i),y.lastComputedAt=Date.now(),y.computationTime=l)}analyzeComputedDependencyChain(e,n){const r=new Set,i=[];let l=!1,c,d=0;const f=(m,y)=>{if(d=Math.max(d,y),r.has(m))return l=!0,c=[...i,m],{name:m,type:"computed",children:[],depth:y};r.add(m),i.push(m);const v=this.computedProperties.get(`computed-${m}-${n||"global"}`),S=[];if(v)for(const _ of v.dependencies)_.type==="computed"?S.push(f(_.path,y+1)):S.push({name:_.path,type:_.type,children:[],depth:y+1});return i.pop(),r.delete(m),{name:m,type:"computed",children:S,depth:y}},h=f(e,0);return{root:e,tree:h,hasCycle:l,cyclePath:c,maxDepth:d}}getComputedDependencyTrackingInfo(){const e=Array.from(this.computedProperties.values()),n=this.getComputedDependencyStats(),r=[];for(const i of e){const l=this.analyzeComputedDependencyChain(i.name,i.componentId);r.push(l)}return{properties:e,recalcLogs:[...this.computedRecalcLogs],dependencyChains:r,stats:n,timestamp:Date.now()}}getComputedDependencyStats(){const e={"state-change":0,"datasource-update":0,"dependency-change":0,manual:0,initial:0},n={},r={};let i=0,l=0;for(const h of this.computedRecalcLogs)e[h.trigger]++,h.valueChanged||l++,n[h.computedName]=(n[h.computedName]||0)+1,r[h.computedName]||(r[h.computedName]=[]),r[h.computedName].push(h.computationTime),i+=h.computationTime;const c=Object.entries(n).sort((h,m)=>m[1]-h[1]).slice(0,5).map(([h,m])=>({name:h,count:m})),d=Object.entries(r).map(([h,m])=>({name:h,avgTime:m.reduce((y,v)=>y+v,0)/m.length})).sort((h,m)=>m.avgTime-h.avgTime).slice(0,5);let f=0;for(const h of this.computedProperties.values())this.analyzeComputedDependencyChain(h.name,h.componentId).hasCycle&&f++;return{totalComputed:this.computedProperties.size,totalRecalculations:this.computedRecalcLogs.length,unnecessaryRecalculations:l,avgComputationTime:this.computedRecalcLogs.length>0?i/this.computedRecalcLogs.length:0,topRecalculated:c,slowestComputed:d,cycleDetectionCount:f,byTrigger:e}}clearComputedTracking(){this.computedProperties.clear(),this.computedRecalcLogs=[],this.computedRecalcIdCounter=0}trackModalOpen(e){if(!this.config.enabled)return;const n={modalId:e.modalId,modalName:e.modalName,openedAt:Date.now(),closedAt:null,scopeType:e.scopeType||"isolated",parentModalId:e.parentModalId,componentId:e.componentId,initialState:e.initialState?{...e.initialState}:{},currentState:e.initialState?{...e.initialState}:{},stateChangeCount:0,isolatedStateKeys:e.isolatedStateKeys||[],sharedStateKeys:e.sharedStateKeys||[]};this.modalStates.set(e.modalId,n),this.validateModalDefinitionExists(e.modalId,e.modalName),e.parentModalId&&(this.modalStateRelations.find(i=>i.parentModalId===e.parentModalId&&i.childModalId===e.modalId)||this.modalStateRelations.push({parentModalId:e.parentModalId,childModalId:e.modalId,sharedKeys:e.sharedStateKeys||[],isolatedKeys:e.isolatedStateKeys||[],relationType:"parent-child"}))}trackModalClose(e,n){if(!this.config.enabled)return;const r=this.modalStates.get(e);r&&(r.closedAt=Date.now(),n&&(r.currentState={...n}),this.detectStateLeakage(r))}validateModalDefinitionExists(e,n){const r=this.currentLayout;if(!r?.layoutJson)return;const i=r.layoutJson.modals;if(!i){this.recordModalStateIssue({type:"missing-definition",modalId:e,modalName:n,severity:"error",description:`모달 "${e}"이(가) modalStack에 추가되었으나, 현재 레이아웃에 modals 섹션이 없습니다. 레이아웃 경로: ${r.layoutPath}`,affectedStateKeys:[],expectedValue:`modals 섹션에 id="${e}" 정의 존재`,actualValue:"modals 섹션 없음"});return}const l=Array.isArray(i)?i:Object.values(i);l.some(d=>d?.id===e||d?.props?.id===e)||this.recordModalStateIssue({type:"missing-definition",modalId:e,modalName:n,severity:"error",description:`모달 "${e}"이(가) modalStack에 추가되었으나, 렌더링된 레이아웃의 modals 섹션에 해당 ID의 정의가 없습니다. partial 로딩 실패, 레이아웃 병합 문제, extends 상속 누락 등을 확인하세요. 레이아웃 경로: ${r.layoutPath}`,affectedStateKeys:[],expectedValue:`modals 배열에 id="${e}" 항목 존재`,actualValue:`modals 배열에 ${l.length}개 모달 정의 (${l.map(d=>d?.id||"unknown").join(", ")})`})}trackModalStateChange(e){if(!this.config.enabled)return;const n=this.modalStates.get(e.modalId);if(!n)return;n.stateChangeCount++,n.currentState[e.stateKey]=e.newValue;const r=this.checkIsolationViolation(n,e.stateKey),i={id:`modal-change-${++this.modalStateChangeLogIdCounter}`,modalId:e.modalId,modalName:n.modalName,timestamp:Date.now(),stateKey:e.stateKey,previousValue:e.previousValue,newValue:e.newValue,changeSource:e.changeSource||"user-action",violatesIsolation:r};this.modalStateChangeLogs.push(i),this.modalStateChangeLogs.length>this.maxModalStateChangeLogs&&this.modalStateChangeLogs.shift(),r&&this.recordModalStateIssue({type:"isolation-violation",modalId:e.modalId,modalName:n.modalName,severity:"warning",description:`모달 '${n.modalName}'에서 격리된 상태 키 '${e.stateKey}'가 변경됨`,affectedStateKeys:[e.stateKey]})}detectStateLeakage(e){if(e.scopeType==="isolated")for(const n of e.isolatedStateKeys){const r=e.initialState[n],i=e.currentState[n];if(r!==i&&e.parentModalId){const l=this.modalStates.get(e.parentModalId);l&&l.currentState.hasOwnProperty(n)&&this.recordModalStateIssue({type:"state-leakage",modalId:e.modalId,modalName:e.modalName,severity:"error",description:`모달 닫힘 후 상태 '${n}'가 부모 모달로 유출될 수 있음`,affectedStateKeys:[n],leakedValue:i,expectedValue:r})}}}checkIsolationViolation(e,n){return e.scopeType==="isolated",!1}recordModalStateIssue(e){const n={id:`modal-issue-${++this.modalStateIssueIdCounter}`,type:e.type,modalId:e.modalId,modalName:e.modalName,timestamp:Date.now(),severity:e.severity,description:e.description,affectedStateKeys:e.affectedStateKeys,leakedValue:e.leakedValue,expectedValue:e.expectedValue,actualValue:e.actualValue,stackInfo:e.stackInfo};this.modalStateIssues.push(n),this.modalStateIssues.length>this.maxModalStateIssues&&this.modalStateIssues.shift()}getModalStateScopeTrackingInfo(){const e=Array.from(this.modalStates.values()),n=this.getModalStateScopeStats();return{modals:e,issues:[...this.modalStateIssues],relations:[...this.modalStateRelations],changeLogs:[...this.modalStateChangeLogs],stats:n,timestamp:Date.now()}}getModalStateScopeStats(){const e=Array.from(this.modalStates.values()),n={warning:0,error:0},r={"state-leakage":0,"isolation-violation":0,"parent-mutation":0,"orphaned-state":0,"scope-mismatch":0,"cleanup-failure":0,"missing-definition":0};for(const l of this.modalStateIssues)n[l.severity]++,r[l.type]++;const i={isolated:0,shared:0,inherited:0};for(const l of e)i[l.scopeType]++;return{totalModals:e.length,openModals:e.filter(l=>l.closedAt===null).length,nestedModals:e.filter(l=>l.parentModalId!==void 0).length,totalIssues:this.modalStateIssues.length,issuesBySeverity:n,issuesByType:r,byScope:i,leakageDetectionCount:r["state-leakage"],cleanupFailureCount:r["cleanup-failure"]}}clearModalStateScopeTracking(){this.modalStates.clear(),this.modalStateIssues=[],this.modalStateRelations=[],this.modalStateChangeLogs=[],this.modalStateIssueIdCounter=0,this.modalStateChangeLogIdCounter=0}setNamedActionDefinitions(e){this.config.enabled&&(this.namedActionDefinitions=e||{})}trackNamedActionRef(e){if(!this.config.enabled)return;const n={...e,id:`named_action_ref_${++this.namedActionRefIdCounter}`};this.namedActionRefLogs.push(n),this.namedActionRefLogs.length>this.maxNamedActionRefLogs&&(this.namedActionRefLogs=this.namedActionRefLogs.slice(-this.maxNamedActionRefLogs))}getNamedActionTrackingInfo(){const e={};for(const r of this.namedActionRefLogs)e[r.actionRefName]=(e[r.actionRefName]||0)+1;const n=Object.keys(this.namedActionDefinitions).filter(r=>!e[r]);return{definitions:this.namedActionDefinitions,refLogs:[...this.namedActionRefLogs],stats:{totalDefinitions:Object.keys(this.namedActionDefinitions).length,totalRefs:this.namedActionRefLogs.length,refCountByName:e,unusedDefinitions:n},timestamp:Date.now()}}clearNamedActionTracking(){this.namedActionDefinitions={},this.namedActionRefLogs=[],this.namedActionRefIdCounter=0}trackExpressionEval(e){if(!this.config.enabled)return;const n={...e,id:`expr-${++this.expressionIdCounter}`,timestamp:Date.now(),warning:this.detectExpressionWarning(e)};this.expressionHistory.push(n),this.expressionHistory.length>500&&this.expressionHistory.shift(),this.isProfiling&&this.profilingData.push({type:"binding",expression:e.expression,timestamp:performance.now(),duration:e.duration}),this.notifyExpressionWatchers(n)}detectExpressionWarning(e){const{expression:n,result:r,resultType:i,method:l,duration:c}=e;if(i==="undefined"&&!n.includes("??")&&!n.includes("||")&&/^[a-zA-Z_$][a-zA-Z0-9_$.]*$/.test(n.replace(/{{|}}/g,"").trim()))return{type:"undefined-result",message:"표현식 결과가 undefined입니다",suggestion:`데이터가 로드되었는지 확인하거나 fallback 값을 추가하세요: ${n} ?? ''`};if(i==="null")return{type:"null-result",message:"표현식 결과가 null입니다",suggestion:`nullish coalescing 연산자를 사용하세요: ${n} ?? ''`};if(l==="resolveBindings"&&i==="string"){const d=String(r);if(d.includes("[object Object]")||/^\[.*\]$/.test(d))return{type:"array-to-string",message:"배열/객체가 문자열로 변환되었습니다",suggestion:"evaluateExpression()을 사용하여 원본 타입을 유지하세요"}}if(l==="resolveBindings"&&i==="string"&&String(r).includes("[object Object]"))return{type:"object-to-string",message:"객체가 [object Object]로 변환되었습니다",suggestion:"evaluateExpression()을 사용하거나 특정 속성에 접근하세요"};if(i==="undefined"&&n.includes(".")&&!n.includes("?.")){const d=n.replace(/{{|}}/g,"").trim().split(".");if(d.length>=2)return{type:"missing-optional-chain",message:"Optional chaining(?.) 누락 가능성",suggestion:`안전한 접근을 위해 ?. 사용을 고려하세요: ${d.join("?.")}`}}if((n.includes("{{item.")||n.includes("{{index}}"))&&Array.from(this.iterations.values()).some(h=>h.itemVar!=="item"))return{type:"wrong-iteration-var",message:"'item'/'index' 대신 iteration에서 정의한 변수명을 사용해야 합니다",suggestion:"iteration의 item_var/index_var에 정의된 변수명을 확인하세요"};if(c&&c>10)return{type:"slow-evaluation",message:`표현식 평가에 ${c.toFixed(2)}ms 소요됨`,suggestion:"복잡한 표현식을 단순화하거나 computed 값으로 분리하세요"}}getExpressions(){return[...this.expressionHistory]}getExpressionWarnings(){return this.expressionHistory.filter(e=>e.warning!=null)}searchExpressions(e){const n=e.toLowerCase();return this.expressionHistory.filter(r=>r.expression.toLowerCase().includes(n)||r.componentName?.toLowerCase().includes(n)||r.propName?.toLowerCase().includes(n))}getExpressionStats(){const e=this.expressionHistory,n=new Set(e.map(f=>f.expression)),r=e.filter(f=>f.warning!=null),i=e.filter(f=>f.fromCache),l=e.filter(f=>f.duration!=null).map(f=>f.duration),c={};for(const f of e)c[f.resultType]=(c[f.resultType]||0)+1;const d={};for(const f of r)f.warning&&(d[f.warning.type]=(d[f.warning.type]||0)+1);return{totalEvaluations:e.length,uniqueExpressions:n.size,warningCount:r.length,cacheHitRate:e.length>0?i.length/e.length:0,averageDuration:l.length>0?l.reduce((f,h)=>f+h,0)/l.length:0,byType:c,byWarning:d}}clearExpressions(){this.expressionHistory=[],this.expressionIdCounter=0}watchExpressions(e){return this.expressionWatchers.add(e),()=>{this.expressionWatchers.delete(e)}}notifyExpressionWatchers(e){this.expressionWatchers.forEach(n=>{try{n(e)}catch(r){console.error("[G7DevTools] Expression watcher error:",r)}})}setLogLevel(e){this.config.logLevel=e}setMaxHistory(e){this.config.maxHistorySize=Math.max(10,Math.min(1e3,e))}setServerEndpoint(e){this.config.serverEndpoint=e}getConfig(){return{...this.config}}generateId(){return`${Date.now()}-${Math.random().toString(36).substr(2,9)}`}sanitizeObject(e,n=new WeakSet){if(e===null||typeof e!="object")return e;if(n.has(e))return"[Circular]";if(n.add(e),Array.isArray(e))return e.map(i=>this.sanitizeObject(i,n));const r={};for(const i of Object.keys(e))try{r[i]=this.sanitizeObject(e[i],n)}catch{r[i]="[Unable to serialize]"}return r}getNestedValue(e,n){if(!n)return e;const r=n.split(".");let i=e;for(const l of r){if(i==null)return;i=i[l]}return i}getLifecycleInfo(){return{mountedComponents:Array.from(this.mountedComponents.values()),orphanedListeners:this.getOrphanedListeners()}}getPerformanceInfo(){return{renderCounts:this.renderCounts,bindingEvalCount:this.bindingEvalCount,memoryWarnings:this.getMemoryWarnings()}}getNetworkInfo(){return{activeRequests:Array.from(this.activeRequests.values()),requestHistory:[...this.requestHistory],pendingDataSources:Array.from(this.pendingDataSources)}}getConditionalInfo(){return{ifConditions:Array.from(this.ifConditions.values()),iterations:Array.from(this.iterations.values())}}getFormInfo(){return Array.from(this.forms.values())}getWebSocketInfo(){return{connections:Array.from(this.wsConnections.values()),messageHistory:[...this.wsMessageHistory],connectionState:this.getWebSocketConnectionState()}}getStateAtTime(e){const n=[...this.stateHistory].reverse().find(r=>r.timestamp<=e);if(n)return{_global:n.next,_local:{}}}reset(){this.stateHistory=[],this.actionHistory=[],this.cacheStats={hits:0,misses:0,entries:0},this.cacheDecisions=[],this.cacheDecisionIdCounter=0,this.currentRenderCycleId=null,this.isInActionExecution=!1,this.isInIteration=!1,this.mountedComponents.clear(),this.eventListeners.clear(),this.renderCounts.clear(),this.bindingEvalCount=0,this.profilingData=[],this.activeRequests.clear(),this.requestHistory=[],this.pendingDataSources.clear(),this.wsConnections.clear(),this.wsMessageHistory=[],this.ifConditions.clear(),this.iterations.clear(),this.forms.clear(),this.expressionHistory=[],this.expressionIdCounter=0,this.dataSources.clear(),this.dataPathTransforms=[],this.nestedContexts=[],this.nestedContextIdCounter=0,this.formBindingIssues=[],this.formBindingValidations=[],this.formBindingIssueIdCounter=0,this.computedProperties.clear(),this.computedRecalcLogs=[],this.computedRecalcIdCounter=0,this.modalStates.clear(),this.modalStateIssues=[],this.modalStateRelations=[],this.modalStateChangeLogs=[],this.modalStateIssueIdCounter=0,this.modalStateChangeLogIdCounter=0,this.logHistory=[],this.logIdCounter=0}getExpressionInfo(){return{expressions:this.getExpressions(),stats:this.getExpressionStats()}}trackLog(e,n,r){if(!this.config.enabled)return;const i=r.map(c=>{if(typeof c=="string")return c;try{return JSON.stringify(c)}catch{return String(c)}}).join(" "),l={id:`log-${++this.logIdCounter}`,level:e,prefix:n,message:i,args:this.sanitizeObject(r),timestamp:Date.now(),stack:e==="error"?new Error().stack:void 0};this.logHistory.push(l),this.logHistory.length>this.maxLogHistory&&this.logHistory.shift()}trackAction(e){if(!this.config.enabled)return;const n={id:`debounce-action-${++this.debounceActionIdCounter}`,...e,timestamp:Date.now()};this.debounceActionHistory.push(n),this.debounceActionHistory.length>100&&this.debounceActionHistory.shift()}trackDataSourceUpdate(e){if(!this.config.enabled)return;const n={id:`ds-update-${++this.dataSourceUpdateIdCounter}`,...e};this.dataSourceUpdateHistory.push(n),this.dataSourceUpdateHistory.length>100&&this.dataSourceUpdateHistory.shift()}getDebounceActionHistory(e){let n=[...this.debounceActionHistory];return e?.handler&&(n=n.filter(r=>r.handler.includes(e.handler))),e?.status&&(n=n.filter(r=>r.status===e.status)),e?.limit&&(n=n.slice(-e.limit)),n}getDataSourceUpdateHistory(e){let n=[...this.dataSourceUpdateHistory];return e?.dataSourceId&&(n=n.filter(r=>r.dataSourceId===e.dataSourceId)),e?.updateType&&(n=n.filter(r=>r.updateType===e.updateType)),e?.limit&&(n=n.slice(-e.limit)),n}getLogs(e){let n=[...this.logHistory];if(e?.level){const r=Array.isArray(e.level)?e.level:[e.level];n=n.filter(i=>r.includes(i.level))}if(e?.prefix){const r=e.prefix.toLowerCase();n=n.filter(i=>i.prefix.toLowerCase().includes(r))}if(e?.search){const r=e.search.toLowerCase();n=n.filter(i=>i.message.toLowerCase().includes(r))}return e?.since&&(n=n.filter(r=>r.timestamp>=e.since)),e?.limit&&(n=n.slice(-e.limit)),n}getLogStats(){const n=Date.now()-6e4,r={log:0,warn:0,error:0,debug:0,info:0},i={};let l=0,c=0;for(const d of this.logHistory)r[d.level]++,i[d.prefix]=(i[d.prefix]||0)+1,d.timestamp>=n&&(d.level==="error"&&l++,d.level==="warn"&&c++);return{totalLogs:this.logHistory.length,byLevel:r,byPrefix:i,recentErrors:l,recentWarnings:c}}clearLogs(){this.logHistory=[],this.logIdCounter=0}getLogInfo(){return{entries:this.getLogs(),stats:this.getLogStats()}}trackDataSourceDefinition(e){this.config.enabled&&this.dataSources.set(e.id,{...e,status:"idle"})}trackDataSourceLoading(e){if(!this.config.enabled)return;const n=this.dataSources.get(e);n&&this.dataSources.set(e,{...n,status:"loading"})}trackDataSourceLoaded(e,n,r){if(!this.config.enabled)return;const i=this.dataSources.get(e);if(!i)return;let l,c,d=r;n&&(Array.isArray(n)?(l=n.length,c=n.length>0&&typeof n[0]=="object"?Object.keys(n[0]):void 0):typeof n=="object"&&(Array.isArray(n.data)?(d=d||"data",l=n.data.length,c=n.data.length>0&&typeof n.data[0]=="object"?Object.keys(n.data[0]):void 0):Array.isArray(n.items)?(d=d||"items",l=n.items.length,c=n.items.length>0&&typeof n.items[0]=="object"?Object.keys(n.items[0]):void 0):c=Object.keys(n))),this.dataSources.set(e,{...i,status:"loaded",dataPath:d,itemCount:l,keys:c,lastLoadedAt:Date.now(),error:void 0})}trackDataSourceError(e,n){if(!this.config.enabled)return;const r=this.dataSources.get(e);r&&this.dataSources.set(e,{...r,status:"error",error:n,lastLoadedAt:Date.now()})}untrackDataSource(e){this.dataSources.delete(e)}getDataSources(){return Array.from(this.dataSources.values())}getDataSource(e){return this.dataSources.get(e)}clearDataSources(){this.dataSources.clear()}trackDataPathTransform(e){if(!this.config.enabled)return;let n=this.dataPathTransforms.find(r=>r.dataSourceId===e.dataSourceId);n||(n={dataSourceId:e.dataSourceId,timestamp:Date.now(),transformSteps:[],warnings:[]},this.dataPathTransforms.push(n),this.dataPathTransforms.length>this.maxDataPathTransforms&&this.dataPathTransforms.shift()),n.transformSteps.push({step:e.step,inputPath:e.inputPath,inputValue:this.safeClone(e.inputValue),outputPath:e.outputPath,outputValue:this.safeClone(e.outputValue),config:e.config}),e.warning&&n.warnings.push(e.warning)}setDataPathFinalBinding(e,n){if(!this.config.enabled)return;const r=this.dataPathTransforms.find(i=>i.dataSourceId===e);r&&(r.finalBinding={expression:n.expression,resolvedPath:n.resolvedPath,value:this.safeClone(n.value)})}getDataPathTransformTrackingInfo(){const e=this.getDataPathTransformStats();return{transforms:[...this.dataPathTransforms],stats:e,timestamp:Date.now()}}getDataPathTransformStats(){const e=this.dataPathTransforms,n={};let r=0;const i={};for(const c of e){n[c.dataSourceId]=(n[c.dataSourceId]||0)+1,r+=c.warnings.length;for(const d of c.warnings)i[d]=(i[d]||0)+1}const l=Object.entries(i).map(([c,d])=>({warning:c,count:d})).sort((c,d)=>d.count-c.count).slice(0,10);return{totalTransforms:e.length,byDataSource:n,warningsCount:r,commonWarnings:l}}getDataPathTransformForDataSource(e){return this.dataPathTransforms.find(n=>n.dataSourceId===e)}clearDataPathTransforms(){this.dataPathTransforms=[]}trackNestedContext(e){if(!this.config.enabled)return"";const n=`nested-${++this.nestedContextIdCounter}`,r=[...new Set([...e.parentContext.available,...e.ownContext.added])],i={...e.parentContext.values,...e.ownContext.values},l={id:n,timestamp:Date.now(),componentId:e.componentId,componentType:e.componentType,parentContext:{available:e.parentContext.available,values:this.safeClone(e.parentContext.values)},ownContext:{added:e.ownContext.added,values:this.safeClone(e.ownContext.values)},mergedContext:{all:r,values:this.safeClone(i)},accessAttempts:[],depth:e.depth,parentId:e.parentId};return this.nestedContexts.push(l),this.nestedContexts.length>this.maxNestedContexts&&this.nestedContexts.shift(),n}trackNestedContextAccess(e,n){if(!this.config.enabled)return;const r=this.nestedContexts.find(i=>i.id===e);r&&r.accessAttempts.push({path:n.path,found:n.found,value:n.found?this.safeClone(n.value):void 0,error:n.error})}getNestedContextTrackingInfo(){const e=this.getNestedContextStats();return{contexts:[...this.nestedContexts],stats:e,timestamp:Date.now()}}getNestedContextStats(){const e=this.nestedContexts,n={expandChildren:0,cellChildren:0,iteration:0,modal:0,slot:0};let r=0,i=0;const l={};for(const d of e){n[d.componentType]++,d.depth>r&&(r=d.depth);for(const f of d.accessAttempts)f.found||(i++,l[f.path]=(l[f.path]||0)+1)}const c=Object.entries(l).map(([d,f])=>({path:d,count:f})).sort((d,f)=>f.count-d.count).slice(0,10);return{totalContexts:e.length,byType:n,maxDepth:r,failedAccessCount:i,commonFailedPaths:c}}getNestedContextForComponent(e){return this.nestedContexts.find(n=>n.componentId===e)}clearNestedContexts(){this.nestedContexts=[],this.nestedContextIdCounter=0}trackHandlerRegistration(e,n="custom",r,i){this.isEnabled&&this.handlers.set(e,{name:e,category:n,description:r,registeredAt:Date.now(),source:i})}trackHandlerUnregistration(e){this.isEnabled&&this.handlers.delete(e)}getHandlers(){return Array.from(this.handlers.values())}getHandler(e){return this.handlers.get(e)}getHandlersByCategory(e){return Array.from(this.handlers.values()).filter(n=>n.category===e)}clearHandlers(){this.handlers.clear()}trackEventSubscribe(e){if(!this.isEnabled)return;const n=this.componentEventSubscriptions.get(e),r=Date.now();n?(n.subscriberCount++,n.lastSubscribedAt=r):this.componentEventSubscriptions.set(e,{eventName:e,subscriberCount:1,firstSubscribedAt:r,lastSubscribedAt:r})}trackEventUnsubscribe(e){if(!this.isEnabled)return;const n=this.componentEventSubscriptions.get(e);n&&(n.subscriberCount--,n.subscriberCount<=0&&this.componentEventSubscriptions.delete(e))}trackEventEmit(e,n,r,i,l){if(!this.isEnabled)return;const c={id:`evt_${++this.componentEventIdCounter}`,eventName:e,data:this.sanitizeForLogging(n),timestamp:Date.now(),listenerCount:r,results:i?this.sanitizeForLogging(i):void 0,hasError:!!l,errorMessage:l?.message};this.componentEventEmitHistory.push(c),this.componentEventEmitHistory.length>this.maxComponentEventHistory&&(this.componentEventEmitHistory=this.componentEventEmitHistory.slice(-this.maxComponentEventHistory))}trackEventOff(e){this.isEnabled&&this.componentEventSubscriptions.delete(e)}trackEventClear(){this.isEnabled&&this.componentEventSubscriptions.clear()}getComponentEventInfo(){const e=Array.from(this.componentEventSubscriptions.values());return{subscriptions:e,emitHistory:[...this.componentEventEmitHistory],totalSubscribers:e.reduce((n,r)=>n+r.subscriberCount,0),totalEmits:this.componentEventEmitHistory.length}}getEventEmitHistory(){return[...this.componentEventEmitHistory]}getEventSubscriptions(){return Array.from(this.componentEventSubscriptions.values())}clearComponentEvents(){this.componentEventSubscriptions.clear(),this.componentEventEmitHistory=[],this.componentEventIdCounter=0}sanitizeForLogging(e){if(e==null||typeof e!="object")return e;try{return JSON.parse(JSON.stringify(e))}catch{return"[Circular or Non-serializable]"}}startStateChange(e,n,r,i){if(!this.config.enabled)return"";const l=`setState_${++this.stateRenderingIdCounter}`;this.currentStateChangeContext={setStateId:l,startTime:performance.now(),changedPath:e,trigger:i||{},renderedComponents:[]};const c={id:`sr_${this.stateRenderingIdCounter}`,setStateId:l,statePath:e,oldValue:this.sanitizeForLogging(n),newValue:this.sanitizeForLogging(r),timestamp:Date.now(),triggeredBy:i||{},renderedComponents:[],totalRenderDuration:0,affectedBindingsCount:0};return this.stateRenderingLogs.push(c),this.stateRenderingLogs.length>this.maxStateRenderingLogs&&this.stateRenderingLogs.shift(),l}trackComponentRender(e,n,r,i=[],l=[],c){if(!this.config.enabled)return;const d=this.componentRenderCounts.get(n)||0;this.componentRenderCounts.set(n,d+1);for(const f of i)this.stateToComponentMap.has(f)||this.stateToComponentMap.set(f,new Set),this.stateToComponentMap.get(f).add(n);if(this.currentStateChangeContext){const f={componentId:e,componentName:n,renderDuration:r,accessedStatePaths:i,evaluatedBindings:l,renderOrder:this.currentStateChangeContext.renderedComponents.length,parentId:c};this.currentStateChangeContext.renderedComponents.push(f)}this.trackRender(n)}completeStateChange(e){if(!this.config.enabled||!this.currentStateChangeContext||this.currentStateChangeContext.setStateId!==e)return;const n=this.currentStateChangeContext,i=performance.now()-n.startTime,l=this.stateRenderingLogs.findIndex(c=>c.setStateId===e);if(l>=0){const c=this.stateRenderingLogs[l];c.renderedComponents=n.renderedComponents,c.totalRenderDuration=i,c.affectedBindingsCount=n.renderedComponents.reduce((d,f)=>d+f.evaluatedBindings.length,0)}this.currentStateChangeContext=null}getCurrentStateChangeContext(){return this.currentStateChangeContext}getStateRenderingLogs(){return[...this.stateRenderingLogs]}getStateRenderingInfo(){const e=this.stateRenderingLogs,n=Object.fromEntries(this.componentRenderCounts),r={};for(const[i,l]of this.stateToComponentMap)r[i]=Array.from(l);return{logs:e,componentRenderCounts:n,stateToComponentMap:r,stats:this.calculateStateRenderingStats()}}calculateStateRenderingStats(){const e=this.stateRenderingLogs;if(e.length===0)return{totalStateChanges:0,totalRenders:0,avgRenderDuration:0,avgComponentsPerChange:0,topRenderedComponents:[],topInfluentialPaths:[]};const n=e.reduce((d,f)=>d+f.renderedComponents.length,0),r=e.reduce((d,f)=>d+f.totalRenderDuration,0)/e.length,i=n/e.length,l=Array.from(this.componentRenderCounts.entries()).sort((d,f)=>f[1]-d[1]).slice(0,5).map(([d,f])=>({name:d,count:f})),c=Array.from(this.stateToComponentMap.entries()).sort((d,f)=>f[1].size-d[1].size).slice(0,5).map(([d,f])=>({path:d,affectedComponents:f.size}));return{totalStateChanges:e.length,totalRenders:n,avgRenderDuration:r,avgComponentsPerChange:i,topRenderedComponents:l,topInfluentialPaths:c}}getComponentsAffectedByState(e){const n=this.stateToComponentMap.get(e);return n?Array.from(n):[]}getComponentRenderHistory(e){return this.stateRenderingLogs.filter(n=>n.renderedComponents.some(r=>r.componentName===e))}clearStateRenderingLogs(){this.stateRenderingLogs=[],this.componentRenderCounts.clear(),this.stateToComponentMap.clear(),this.currentStateChangeContext=null,this.stateRenderingIdCounter=0}trackComponentStateSource(e,n,r,i){this.config.enabled&&this.componentStateSources.set(e,{componentId:e,componentName:n,stateSource:r,stateProvider:i})}trackDynamicState(e,n){this.config.enabled&&this.dynamicStates.set(e,n)}trackContextFlow(e,n,r,i,l,c){if(!this.config.enabled)return;const d={component:n,componentId:e,contextReceived:r,passedToChildren:i,usedInRender:l};if(this.contextFlowNodes.set(e,d),c){const f=this.contextFlowNodes.get(c);f&&(f.children||(f.children=[]),f.children.push(d))}}getStateHierarchyInfo(e=!0){const n=this.buildStateHierarchyLayers(),r=this.detectStateConflicts(n),i=Array.from(this.componentStateSources.values());return{layers:e?n.map(c=>({...c,values:this.summarizeValues(c.values,2)})):n,conflicts:r,componentStateSources:i,timestamp:Date.now()}}summarizeValues(e,n,r=0){if(e==null)return e;if(typeof e!="object")return typeof e=="string"&&e.length>100?e.substring(0,100)+`... (${e.length}자)`:e;if(r>=n){if(Array.isArray(e))return`[Array(${e.length})]`;const c=Object.keys(e);return`{${c.slice(0,5).join(", ")}${c.length>5?`, ... +${c.length-5}`:""}}`}if(Array.isArray(e))return e.length<=3?e.map(c=>this.summarizeValues(c,n,r+1)):{__type:"array",__length:e.length,__preview:e.slice(0,3).map(c=>this.summarizeValues(c,n,r+1))};const i={},l=Object.keys(e);for(const c of l)i[c]=this.summarizeValues(e[c],n,r+1);return i}buildStateHierarchyLayers(){const e=[];try{const r=window.G7Core?.state?.get?.();if(!r)return e;r._local&&e.push({name:"Global _local",type:"global",path:"_local",values:r._local,priority:1}),r._global&&e.push({name:"Global _global",type:"global",path:"_global",values:r._global,priority:1});for(const[l,c]of this.dynamicStates)c&&Object.keys(c).length>0&&e.push({name:`DynamicState (${l})`,type:"dynamicState",componentId:l,values:c,priority:2});const i=this.computeEffectiveState(r._local||{});e.push({name:"Effective _local (merged)",type:"effective",values:i,priority:3})}catch(n){console.warn("[G7DevTools] 상태 계층 빌드 실패:",n)}return e}computeEffectiveState(e){const n={...e};for(const[,r]of this.dynamicStates)r._local&&Object.assign(n,r._local);return n}detectStateConflicts(e){const n=[],r=e.find(l=>l.type==="global"&&l.path==="_local");if(!r)return n;const i=e.filter(l=>l.type==="dynamicState");for(const l of i){const c=l.values._local||l.values;for(const[d,f]of Object.entries(c)){const h=r.values[d];if(JSON.stringify(h)!==JSON.stringify(f)){const m=[],y=[];for(const v of this.componentStateSources.values())v.stateSource.local.some(_=>_===`_local.${d}`||_===d)&&(v.stateProvider.type==="dynamicState"?m.push(v.componentName):v.stateProvider.type==="globalState"&&y.push(v.componentName));n.push({path:`_local.${d}`,globalValue:h,dynamicStateValue:f,effectiveValue:f,usedBy:m,notUsedBy:y,severity:y.length>0?"warning":"info",description:y.length>0?`${y.join(", ")}은(는) globalState._local을 읽어 dynamicState 값을 사용하지 못함`:"dynamicState 값이 globalState와 다르지만 모든 컴포넌트가 dynamicState를 사용"})}}}return n}getContextFlowInfo(){const e=[],n=new Set(this.contextFlowNodes.keys());for(const r of this.contextFlowNodes.values())if(r.children)for(const i of r.children)n.delete(i.componentId);for(const r of n){const i=this.contextFlowNodes.get(r);i&&e.push(i)}return{rootComponent:e.length>0?e[0].component:"Unknown",contextFlow:e,timestamp:Date.now()}}clearStateHierarchyData(){this.componentStateSources.clear(),this.contextFlowNodes.clear(),this.dynamicStates.clear()}trackComponentStyle(e,n,r,i){if(!this.config.enabled)return;const l=this.analyzeTailwindClasses(r),c={componentId:e,componentName:n,classes:r,computedStyles:i,tailwindAnalysis:l};this.componentStyles.set(e,c),this.detectStyleIssues(c)}analyzeTailwindClasses(e){const n=[],r=[],i=[],l=[],c=["sm:","md:","lg:","xl:","2xl:"],d=[/bg-\w+-\d+/,/text-\w+-\d+/,/border-\w+-\d+/];for(const f of e){l.push(f),f.startsWith("dark:")&&n.push(f);for(const h of c)if(f.startsWith(h)){r.push(f);break}for(const h of d)if(h.test(f)){i.push(f);break}}return{usedClasses:l,darkClasses:n,responsiveClasses:r,dynamicClasses:i}}detectStyleIssues(e){const{componentId:n,componentName:r,computedStyles:i,tailwindAnalysis:l}=e;(i.opacity==="0"||i.visibility==="hidden"||i.display==="none"||i.width==="0px"&&i.height==="0px")&&this.addStyleIssue({id:this.generateId(),type:"invisible-element",componentId:n,componentName:r,property:i.opacity==="0"?"opacity":i.visibility==="hidden"?"visibility":i.display==="none"?"display":"width/height",currentValue:i.opacity==="0"?"0":i.visibility==="hidden"?"hidden":i.display==="none"?"none":"0px",severity:"warning",description:"요소가 화면에 보이지 않습니다.",suggestion:"opacity, visibility, display, width/height 값을 확인하세요."});const c=l.usedClasses.filter(f=>f.startsWith("bg-")&&!f.startsWith("dark:")),d=l.darkClasses.filter(f=>f.includes("bg-"));c.length>0&&d.length===0&&this.addStyleIssue({id:this.generateId(),type:"dark-mode-missing",componentId:n,componentName:r,property:"background-color",currentValue:c.join(", "),expectedValue:"dark:bg-* 클래스 필요",severity:"info",description:"다크 모드 배경색 클래스가 누락되었습니다.",suggestion:`${c[0]}에 대응하는 dark: 클래스를 추가하세요.`}),l.dynamicClasses.length>0&&this.addStyleIssue({id:this.generateId(),type:"tailwind-purging",componentId:n,componentName:r,property:"class",currentValue:l.dynamicClasses.join(", "),severity:"info",description:"동적으로 생성된 Tailwind 클래스가 있습니다.",suggestion:"tailwind.config.js의 safelist에 추가하거나 전체 클래스명을 상수로 정의하세요."})}addStyleIssue(e){this.styleIssues.some(r=>r.componentId===e.componentId&&r.type===e.type&&r.property===e.property)||this.styleIssues.push(e)}getStyleValidationInfo(){const e=Array.from(this.componentStyles.values());return{issues:[...this.styleIssues],componentStyles:e,stats:{totalComponents:e.length,invisibleCount:this.styleIssues.filter(n=>n.type==="invisible-element").length,tailwindIssueCount:this.styleIssues.filter(n=>n.type==="tailwind-purging").length,darkModeIssueCount:this.styleIssues.filter(n=>n.type==="dark-mode-missing").length},timestamp:Date.now()}}clearStyleValidationData(){this.styleIssues=[],this.componentStyles.clear()}trackAuthEvent(e,n,r,i){if(!this.config.enabled)return;const l={id:`auth_${++this.authEventIdCounter}`,type:e,timestamp:Date.now(),success:n,error:r,details:i};this.authEvents.push(l),this.authEvents.length>this.maxAuthEventHistory&&(this.authEvents=this.authEvents.slice(-this.maxAuthEventHistory))}trackAuthHeader(e,n,r,i,l){if(!this.config.enabled)return;const c={url:e,hasAuthHeader:n,headerType:r,tokenValid:i??!1,responseStatus:l,timestamp:Date.now()};this.authHeaderHistory.push(c),this.authHeaderHistory.length>this.maxAuthEventHistory&&(this.authHeaderHistory=this.authHeaderHistory.slice(-this.maxAuthEventHistory)),l===401&&this.trackAuthEvent("api-unauthorized",!1,`401 Unauthorized: ${e}`,{url:e})}getAuthState(){try{const e=window.G7Core,n=e?.api;let r=e?.auth;!r&&e?.AuthManager?.getInstance&&(r=e.AuthManager.getInstance());const i=n?.getToken?.()||r?.getAccessToken?.()||localStorage.getItem("auth_token")||localStorage.getItem("access_token"),l=r?.getRefreshToken?.()||localStorage.getItem("refresh_token");let c="unknown",d=r?.getUser?.();if(d&&(c="AuthManager"),!d){const S=e?.state?.get?.();d=S?._global?.user||S?._global?.currentUser||S?.user||S?.currentUser,d&&(c="G7Core.state")}const f=r?.isAuthenticated?.()??!1;let h="memory";localStorage.getItem("auth_token")||localStorage.getItem("access_token")?h="localStorage":(sessionStorage.getItem("auth_token")||sessionStorage.getItem("access_token"))&&(h="sessionStorage");const m=!!i&&(!!d||f);let y;i&&typeof i=="string"&&i.length>20?y=`${i.substring(0,10)}...${i.substring(i.length-6)}`:i&&(y="***");const v=d?{id:d.id,email:d.email,name:d.name,roles:d.roles?.map(S=>typeof S=="string"?{name:S}:{id:S.id,name:S.name,guard_name:S.guard_name}),permissions:d.permissions?.map(S=>typeof S=="string"?{name:S}:{id:S.id,name:S.name,guard_name:S.guard_name}),avatar:d.avatar||d.profile_photo_url||d.profile_image,created_at:d.created_at,updated_at:d.updated_at,last_login_at:d.last_login_at,email_verified_at:d.email_verified_at,...d.phone&&{phone:d.phone},...d.nickname&&{nickname:d.nickname},...d.status&&{status:d.status},...d.locale&&{locale:d.locale}}:void 0;return{isAuthenticated:m,user:v,tokens:{hasAccessToken:!!i,hasRefreshToken:!!l,storage:h,accessTokenPreview:y},lastActivity:Date.now(),source:c}}catch{return{isAuthenticated:!1,tokens:{hasAccessToken:!1,hasRefreshToken:!1,storage:"memory"},source:"unknown"}}}getAuthDebugInfo(){const e=[...this.authEvents];return{state:this.getAuthState(),events:e,headerAnalysis:[...this.authHeaderHistory],stats:{loginAttempts:e.filter(n=>n.type==="login").length,successfulLogins:e.filter(n=>n.type==="login"&&n.success).length,failedLogins:e.filter(n=>n.type==="login"&&!n.success).length,tokenRefreshes:e.filter(n=>n.type==="token-refresh").length,unauthorizedResponses:e.filter(n=>n.type==="api-unauthorized").length},timestamp:Date.now()}}clearAuthData(){this.authEvents=[],this.authHeaderHistory=[],this.authEventIdCounter=0}trackLayoutLoad(e,n,r,i="api"){if(!this.config.enabled)return;const l=Date.now();this.currentLayout={layoutPath:e,templateId:n,layoutJson:this.sanitizeObject(r),loadedAt:l,version:r.version,layoutName:r.layout_name,source:i};const c={id:`layout-${++this.layoutIdCounter}`,layoutPath:e,templateId:n,loadedAt:l,source:i,version:r.version};this.layoutHistory.push(c),this.layoutHistory.length>this.maxLayoutHistory&&this.layoutHistory.shift(),this.layoutStats.totalLoads++,i==="cache"?this.layoutStats.cacheHits++:this.layoutStats.apiLoads++}getCurrentLayout(){return this.currentLayout}getLayoutHistory(){return[...this.layoutHistory]}getLayoutDebugInfo(){return{current:this.currentLayout,history:[...this.layoutHistory],stats:{...this.layoutStats}}}clearLayoutData(){this.currentLayout=null,this.layoutHistory=[],this.layoutIdCounter=0,this.layoutStats={totalLoads:0,cacheHits:0,apiLoads:0}}startHandlerExecution(e){if(!this.config.enabled)return"";const n=`exec_${++this.executionIdCounter}_${Date.now()}`,r={handlerName:e,executionId:n,startTime:Date.now(),stateChanges:[],dataSourceChanges:[],alerts:[]};if(this.executionDetails.set(n,r),this.executionDetails.size>this.maxExecutionDetails){const i=Array.from(this.executionDetails.keys());i.slice(0,i.length-this.maxExecutionDetails).forEach(c=>this.executionDetails.delete(c))}return n}endHandlerExecution(e,n,r,i){if(!this.config.enabled||!e)return;const l=this.executionDetails.get(e);if(!l)return;l.endTime=Date.now(),l.duration=l.endTime-l.startTime,l.exitReason=n,l.exitLocation=r,l.exitDescription=i;const c=this.changeExpectations.get(e);c&&(l.expectedChanges=c,this.checkExpectations(e,l,c),this.changeExpectations.delete(e)),n==="normal"&&l.stateChanges.length===0&&l.dataSourceChanges.length===0&&this.addChangeAlert(e,{type:"no-state-change",severity:"warning",message:`핸들러 "${l.handlerName}"이(가) 상태 변경 없이 완료됨`,description:"핸들러가 성공적으로 완료되었지만 상태나 데이터소스 변경이 없습니다. 의도한 동작인지 확인하세요.",handlerName:l.handlerName,suggestion:'핸들러 내부 조건문을 확인하거나 DevTools의 "변경감지" 탭에서 exitLocation을 확인하세요.',docLink:"troubleshooting-state.md"}),(n==="early-return-condition"||n==="early-return-validation")&&this.addChangeAlert(e,{type:"early-return-detected",severity:"info",message:`핸들러 "${l.handlerName}"이(가) early return으로 종료됨`,description:i||`종료 위치: ${r||"알 수 없음"}`,handlerName:l.handlerName,suggestion:n==="early-return-validation"?"검증 실패로 인한 early return입니다. 입력 데이터를 확인하세요.":"조건부 early return입니다. 조건문의 평가 결과를 확인하세요."})}recordExitReason(e,n,r,i){if(!this.config.enabled||!e)return;const l=this.executionDetails.get(e);l&&(l.exitReason=n,l.exitLocation=r,l.exitDescription=i)}recordStateChange(e,n,r,i,l){if(!this.config.enabled)return;const c={id:`sc_${++this.stateChangeIdCounter}`,path:n,changeType:r,oldValue:this.safeClone(i),newValue:this.safeClone(l),timestamp:Date.now(),comparison:this.compareValues(i,l),executionId:e};if(this.stateChangeHistory.push(c),e){const d=this.executionDetails.get(e);d&&d.stateChanges.push(c)}this.stateChangeHistory.length>this.maxStateChangeHistory&&this.stateChangeHistory.splice(0,this.stateChangeHistory.length-this.maxStateChangeHistory),c.comparison.isDeepEqual&&typeof i=="object"&&i!==null&&this.addChangeAlert(e||"",{type:"object-reference-same",severity:"warning",message:`상태 "${n}"가 변경되었으나 값이 동일함`,description:"객체 참조가 동일하거나 깊은 비교 결과 변경이 없습니다. 변경 감지가 실패할 수 있습니다.",handlerName:e&&this.executionDetails.get(e)?.handlerName||"unknown",statePath:n,suggestion:"불변성을 유지하여 새 객체를 생성하세요. 예: { ...oldObject, newField: value }",docLink:"troubleshooting-state.md#object-mutation"})}recordDataSourceChange(e,n,r,i,l){if(!this.config.enabled)return;const c={dataSourceId:n,changeType:r,timestamp:Date.now(),previousStatus:i,newStatus:l,executionId:e};if(this.dataSourceChangeHistory.push(c),e){const d=this.executionDetails.get(e);d&&d.dataSourceChanges.push(c)}this.dataSourceChangeHistory.length>this.maxStateChangeHistory&&this.dataSourceChangeHistory.splice(0,this.dataSourceChangeHistory.length-this.maxStateChangeHistory)}expectChange(e,n,r){if(!this.config.enabled||!e)return;const i=this.executionDetails.get(e);if(!i)return;const l={expectedStatePaths:n,expectedDataSources:r,setAt:Date.now(),source:i.handlerName};this.changeExpectations.set(e,l)}checkExpectations(e,n,r){const i=new Set(n.stateChanges.map(c=>c.path)),l=new Set(n.dataSourceChanges.map(c=>c.dataSourceId));for(const c of r.expectedStatePaths)i.has(c)||this.addChangeAlert(e,{type:"expected-not-fulfilled",severity:"error",message:`기대한 상태 변경 "${c}"가 발생하지 않음`,description:`핸들러 "${n.handlerName}"이(가) "${c}" 변경을 예고했지만 실제로 변경되지 않았습니다.`,handlerName:n.handlerName,statePath:c,suggestion:"핸들러 내부 로직을 확인하세요. early return이나 조건부 분기로 인해 변경이 생략되었을 수 있습니다.",docLink:"troubleshooting-state.md"});for(const c of r.expectedDataSources)l.has(c)||this.addChangeAlert(e,{type:"expected-not-fulfilled",severity:"error",message:`기대한 데이터소스 갱신 "${c}"가 발생하지 않음`,description:`핸들러 "${n.handlerName}"이(가) "${c}" 갱신을 예고했지만 실제로 갱신되지 않았습니다.`,handlerName:n.handlerName,dataSourceId:c,suggestion:"refetchDataSource 호출이 실행되었는지 확인하세요.",docLink:"data-sources.md"})}addChangeAlert(e,n){const r={...n,id:`alert_${++this.alertIdCounter}`,timestamp:Date.now()};if(this.changeAlerts.push(r),e){const i=this.executionDetails.get(e);i&&i.alerts.push(r)}this.changeAlerts.length>this.maxChangeAlerts&&this.changeAlerts.splice(0,this.changeAlerts.length-this.maxChangeAlerts)}compareValues(e,n){const r=this.getValueType(e),i=this.getValueType(n),l=r!==i;let c=!1,d;return!l&&(r==="object"||r==="array")?(c=this.deepEqual(e,n),!c&&r==="object"&&(d=this.getChangedKeys(e,n))):c=e===n,{typeChanged:l,oldType:r,newType:i,isDeepEqual:c,changedKeys:d}}getValueType(e){return e===null?"null":e===void 0?"undefined":Array.isArray(e)?"array":typeof e}deepEqual(e,n){if(e===n)return!0;if(typeof e!=typeof n)return!1;if(e===null||n===null||typeof e!="object")return e===n;if(Array.isArray(e)!==Array.isArray(n))return!1;const r=Object.keys(e),i=Object.keys(n);if(r.length!==i.length)return!1;for(const l of r)if(!i.includes(l)||!this.deepEqual(e[l],n[l]))return!1;return!0}getChangedKeys(e,n){const r=[],i=new Set([...Object.keys(e),...Object.keys(n)]);for(const l of i)this.deepEqual(e[l],n[l])||r.push(l);return r}getChangeDetectionInfo(){const e=Array.from(this.executionDetails.values()),n={totalExecutions:e.length,executionsWithStateChange:e.filter(r=>r.stateChanges.length>0).length,executionsWithoutStateChange:e.filter(r=>r.stateChanges.length===0&&r.exitReason==="normal").length,earlyReturnCount:e.filter(r=>r.exitReason==="early-return-condition"||r.exitReason==="early-return-validation").length,alertCount:this.changeAlerts.length,alertsByType:this.getAlertsByType()};return{executionDetails:e,stateChangeHistory:[...this.stateChangeHistory],dataSourceChangeHistory:[...this.dataSourceChangeHistory],alerts:[...this.changeAlerts],stats:n,timestamp:Date.now()}}getAlertsByType(){const e={"no-state-change":0,"no-datasource-change":0,"expected-not-fulfilled":0,"object-reference-same":0,"early-return-detected":0,"async-timing-issue":0};for(const n of this.changeAlerts)e[n.type]++;return e}clearChangeDetectionData(){this.executionDetails.clear(),this.stateChangeHistory=[],this.dataSourceChangeHistory=[],this.changeAlerts=[],this.changeExpectations.clear(),this.executionIdCounter=0,this.stateChangeIdCounter=0,this.alertIdCounter=0}getRecentAlerts(e=10,n){let r=[...this.changeAlerts];return n&&(r=r.filter(i=>i.severity===n)),r.slice(-e)}getHandlerExecutions(e,n=10){return Array.from(this.executionDetails.values()).filter(i=>i.handlerName===e).slice(-n)}findExecutionInStack(e){return this.sequenceExecutionStack.find(n=>n.sequenceId===e)}startSequenceExecution(e){if(!this.config.enabled)return"";const n=`seq_${++this.sequenceIdCounter}_${Date.now()}`,r={sequenceId:n,startTime:Date.now(),totalDuration:0,trigger:e,actions:[],status:"running"};return this.sequenceExecutionStack.push(r),n}captureSequenceActionBefore(e,n,r,i,l){if(!this.config.enabled)return;const c=this.findExecutionInStack(e);if(!c)return;const d={index:n,handler:r,params:this.safeClone(i),stateBeforeAction:{_global:this.safeClone(l._global),_local:this.safeClone(l._local),_isolated:l._isolated?this.safeClone(l._isolated):void 0},stateAfterAction:{_global:{},_local:{}},duration:0,status:"success"};c.actions.push(d)}captureSequenceActionAfter(e,n,r,i,l,c){if(!this.config.enabled)return;const d=this.findExecutionInStack(e);if(!d)return;const f=d.actions.find(m=>m.index===n);if(!f)return;f.stateAfterAction={_global:this.safeClone(r._global),_local:this.safeClone(r._local),_isolated:r._isolated?this.safeClone(r._isolated):void 0},f.duration=i,f.result=this.safeClone(l),f.stateDiff={global:this.computeStateDiff(f.stateBeforeAction._global,f.stateAfterAction._global),local:this.computeStateDiff(f.stateBeforeAction._local,f.stateAfterAction._local)},f.stateBeforeAction._isolated&&f.stateAfterAction._isolated&&(f.stateDiff.isolated=this.computeStateDiff(f.stateBeforeAction._isolated,f.stateAfterAction._isolated));const h=window.__g7PendingLocalState;h&&(f.pendingState=this.safeClone(h)),c&&(f.status="error",f.error={name:c.name,message:c.message,stack:c.stack})}endSequenceExecution(e,n){if(!this.config.enabled)return;const r=this.sequenceExecutionStack.findIndex(l=>l.sequenceId===e);if(r===-1)return;const i=this.sequenceExecutionStack[r];if(i.endTime=Date.now(),i.totalDuration=i.endTime-i.startTime,n){i.status="error",i.error={name:n.name,message:n.message,stack:n.stack};const l=i.actions.find(c=>c.status==="error");l&&(i.failedAtIndex=l.index)}else i.status="success";this.sequenceExecutions.push(i),this.sequenceExecutions.length>this.maxSequenceExecutions&&this.sequenceExecutions.shift(),this.sequenceExecutionStack.splice(r,1)}computeStateDiff(e,n){const r=[],i=[],l=[],c=new Set(Object.keys(e)),d=new Set(Object.keys(n));for(const f of d)c.has(f)||r.push(f);for(const f of c)d.has(f)||i.push(f);for(const f of c)if(d.has(f)){const h=e[f],m=n[f];JSON.stringify(h)!==JSON.stringify(m)&&l.push({path:f,oldValue:h,newValue:m})}return{added:r,removed:i,changed:l}}getSequenceTrackingInfo(){const e=this.getSequenceStats(),n=this.sequenceExecutionStack.length>0?this.sequenceExecutionStack[this.sequenceExecutionStack.length-1]:void 0;return{executions:[...this.sequenceExecutions],currentExecution:n,stats:e}}getSequenceStats(){const e=this.sequenceExecutions,n=e.length,r=e.filter(h=>h.status==="success").length,i=e.filter(h=>h.status==="error").length,l=e.reduce((h,m)=>h+m.actions.length,0),c=n>0?e.reduce((h,m)=>h+m.totalDuration,0)/n:0,d={};for(const h of e)for(const m of h.actions)d[m.handler]=(d[m.handler]||0)+1;const f=Object.entries(d).sort(([,h],[,m])=>m-h).slice(0,5).map(([h,m])=>({handler:h,count:m}));return{totalExecutions:n,successCount:r,errorCount:i,totalActions:l,avgDuration:c,topHandlers:f}}clearSequenceData(){this.sequenceExecutions=[],this.sequenceExecutionStack=[],this.sequenceIdCounter=0}getSequenceExecution(e){const n=this.findExecutionInStack(e);return n||this.sequenceExecutions.find(r=>r.sequenceId===e)}getRecentSequences(e=10){return this.sequenceExecutions.slice(-e)}registerStateCaptureForHandler(e,n,r){if(!this.config.enabled)return;const i=new Map,l=Date.now();for(const c of n)i.set(c,{value:this.safeClone(r[c]),capturedAt:l});this.stateCaptureRegistry.set(e,i)}detectStaleClosure(e,n,r,i,l){if(!this.config.enabled)return[];const c=this.stateCaptureRegistry.get(e);if(!c)return[];const d=Date.now(),f=[];for(const[h,m]of c){const y=this.getValueByPath(r,h),v=d-m.capturedAt;if(!this.isDeepEqual(m.value,y)&&v>0){const S=this.createStaleClosureWarning(i,n,h,m.value,m.capturedAt,y,d,v,l);f.push(S),this.addStaleClosureWarning(S)}}return this.stateCaptureRegistry.delete(e),f}trackStaleClosureWarning(e){if(!this.config.enabled)return;const n=Date.now(),r=this.createStaleClosureWarning(e.type,e.location,e.capturedPath,e.capturedValue,e.capturedAt,e.currentValue,n,n-e.capturedAt,e.actionId,e.stackTrace);this.addStaleClosureWarning(r)}createStaleClosureWarning(e,n,r,i,l,c,d,f,h,m){const y=this.getStaleClosureSeverity(e,f),{description:v,suggestion:S,docLink:_}=this.getStaleClosureMessages(e,r,f);return{id:`stale_${++this.staleClosureIdCounter}_${Date.now()}`,timestamp:Date.now(),type:e,location:n,capturedState:{path:r,capturedValue:this.safeClone(i),capturedAt:l},currentState:{path:r,currentValue:this.safeClone(c),retrievedAt:d},timeDiff:f,severity:y,description:v,suggestion:S,docLink:_,actionId:h,stackTrace:m}}getStaleClosureSeverity(e,n){if(n>5e3)return"error";if(n>1e3)return"warning";switch(e){case"async-state-capture":case"sequence-state-mismatch":return"warning";case"callback-state-capture":case"timeout-state-capture":return"info";case"event-handler-stale":return"warning";default:return"info"}}getStaleClosureMessages(e,n,r){const i=r<1e3?`${r}ms`:`${(r/1e3).toFixed(1)}s`;switch(e){case"async-state-capture":return{description:`await 후 ${i} 경과 시점에 '${n}' 상태가 변경됨. 캡처된 상태 대신 최신 상태를 사용해야 함`,suggestion:"await 이후에는 G7Core.state.get()으로 최신 상태를 다시 조회하거나, useRef + getter 패턴을 사용하세요",docLink:"troubleshooting-state-closure.md"};case"callback-state-capture":return{description:`콜백에서 ${i} 전에 캡처된 '${n}' 상태를 사용 중. 최신 상태와 다름`,suggestion:"콜백에서 상태를 참조할 때는 stateRef.current 패턴 또는 G7Core.state.get()를 사용하세요",docLink:"troubleshooting-state-closure.md"};case"timeout-state-capture":return{description:`setTimeout/setInterval 콜백에서 '${n}' 상태가 ${i} 전 값 사용 중`,suggestion:"타이머 콜백 내에서는 G7Core.state.get()으로 최신 상태를 조회하세요",docLink:"troubleshooting-state-closure.md"};case"sequence-state-mismatch":return{description:`sequence 내 '${n}' 상태가 이전 액션 결과와 불일치. context.state 대신 캡처된 값 사용 의심`,suggestion:"sequence 내에서는 context.state 또는 $prev를 사용하여 최신 상태를 참조하세요",docLink:"troubleshooting-state-setstate.md"};case"event-handler-stale":return{description:`이벤트 핸들러에서 ${i} 전에 바인딩된 '${n}' 상태 사용 중`,suggestion:"이벤트 핸들러에서 상태를 참조할 때는 useRef 패턴 또는 G7Core.state.get()를 사용하세요",docLink:"troubleshooting-state-closure.md"};default:return{description:`'${n}' 상태에서 stale closure 감지됨 (${i} 경과)`,suggestion:"상태 참조 시 최신 값을 사용하는지 확인하세요"}}}addStaleClosureWarning(e){this.staleClosureWarnings.push(e),this.staleClosureWarnings.length>this.maxStaleClosureWarnings&&this.staleClosureWarnings.shift()}getValueByPath(e,n){const r=n.split(".");let i=e;for(const l of r){if(i==null)return;i=i[l]}return i}isDeepEqual(e,n){if(e===n)return!0;if(e===null||n===null)return e===n;if(typeof e!=typeof n)return!1;if(typeof e!="object")return e===n;try{return JSON.stringify(e)===JSON.stringify(n)}catch{return!1}}getStaleClosureTrackingInfo(){const e=this.getStaleClosureStats();return{warnings:[...this.staleClosureWarnings],stats:e,timestamp:Date.now()}}getStaleClosureStats(){const e=this.staleClosureWarnings,n={"async-state-capture":0,"callback-state-capture":0,"timeout-state-capture":0,"sequence-state-mismatch":0,"event-handler-stale":0};for(const f of e)n[f.type]++;const r={info:0,warning:0,error:0};for(const f of e)r[f.severity]++;const i={};for(const f of e)i[f.location]=(i[f.location]||0)+1;const l=Object.entries(i).sort(([,f],[,h])=>h-f).slice(0,5).map(([f,h])=>({location:f,count:h})),c={};for(const f of e)c[f.capturedState.path]=(c[f.capturedState.path]||0)+1;const d=Object.entries(c).sort(([,f],[,h])=>h-f).slice(0,5).map(([f,h])=>({path:f,count:h}));return{totalWarnings:e.length,warningsByType:n,warningsBySeverity:r,topLocations:l,topAffectedPaths:d}}clearStaleClosureData(){this.staleClosureWarnings=[],this.staleClosureIdCounter=0,this.stateCaptureRegistry.clear()}getRecentStaleClosureWarnings(e=10,n){let r=[...this.staleClosureWarnings];return n&&(r=r.filter(i=>i.severity===n)),r.slice(-e)}safeClone(e){if(e==null||typeof e!="object")return e;try{return JSON.parse(JSON.stringify(e))}catch{if(Array.isArray(e))return e.map(r=>{try{return JSON.parse(JSON.stringify(r))}catch{return"[Unserializable]"}});const n={};for(const r of Object.keys(e))try{n[r]=JSON.parse(JSON.stringify(e[r]))}catch{n[r]="[Unserializable]"}return n}}};M(Li,"instance");let za=Li;const Xn=pt("LayoutLoader");class Rr extends Error{constructor(n,r,i){super(n);M(this,"code");M(this,"details");this.code=r,this.details=i,this.name="LayoutLoaderError"}}class ah{constructor(e){M(this,"componentRegistry");M(this,"currentLayout",null);M(this,"layoutCache",new Map);M(this,"cacheVersion",0);this.componentRegistry=e}setCacheVersion(e){this.cacheVersion!==e&&(Xn.log("Cache version updated:",this.cacheVersion,"->",e),this.cacheVersion=e,this.layoutCache.clear())}getCacheVersion(){return this.cacheVersion}async loadLayout(e,n){const r=`${e}:${n}`;if(this.layoutCache.has(r)){Xn.log("Loading layout from cache:",n);const l=await this.layoutCache.get(r),c=JSON.parse(JSON.stringify(l));return this.currentLayout=c,za.getInstance().trackLayoutLoad(n,e,c,"cache"),c}const i=this.fetchLayout(e,n);return this.layoutCache.set(r,i),i.catch(()=>{this.layoutCache.get(r)===i&&this.layoutCache.delete(r)}),i}async fetchLayout(e,n,r=!1){try{const i=this.cacheVersion>0?this.cacheVersion:null;let l;if(n.startsWith("__preview__/")){const v=n.replace("__preview__/","");l=Ur(`/api/layouts/preview/${v}`,"json",i)}else l=Ur(`/api/layouts/${e}/${n}`,"json",i);Xn.log("Fetching layout from API:",l);const c=qr(),d=r?null:c.getToken(),f={Accept:"application/json"};d&&(f.Authorization=`Bearer ${d}`);const h=await nc(l,{init:{headers:f},label:`layout: ${n}`});let m;try{m=await h.json()}catch{m=null}if(!h.ok){if(h.status===401&&!r&&d){Xn.log("Token invalid, removing and retrying without token"),c.removeToken();try{return await this.fetchLayout(e,n,!0)}catch(S){throw S instanceof Rr&&S.details?.status===401?new Rr(S.message,S.code,{...S.details,hadToken:!0}):S}}const v=m?.message||m?.error;throw new Rr(`Failed to fetch layout: ${h.status} ${h.statusText}`,"FETCH_FAILED",{status:h.status,statusText:h.statusText,url:l,apiMessage:v})}const y=m.data||m;return this.validateLayoutData(y),this.currentLayout=y,za.getInstance().trackLayoutLoad(n,e,y,"api"),Xn.log("Layout fetched and cached successfully:",y.layout_name),y}catch(i){const l=`${e}:${n}`;throw this.layoutCache.delete(l),i instanceof Rr?i:new Rr("Failed to load layout","LOAD_FAILED",{originalError:i})}}prefetchLayout(e,n){return this.loadLayout(e,n)}validateLayoutData(e){if(!e.version)throw new Rr("Layout data missing required field: version","VALIDATION_FAILED",{field:"version"});if(!e.layout_name)throw new Rr("Layout data missing required field: layout_name","VALIDATION_FAILED",{field:"layout_name"});if(!Array.isArray(e.components))throw new Rr('Layout data field "components" must be an array',"VALIDATION_FAILED",{field:"components",value:e.components});Xn.log("Layout data validation passed")}renderLayout(e,n){try{const r=n||this.currentLayout;if(!r)throw new Rr("No layout data to render","NO_LAYOUT_DATA");Xn.log("Rendering layout:",r.layout_name),e.innerHTML="",this.renderComponents(e,r.components),Xn.log("Layout rendered successfully")}catch(r){Xn.error("Render error:",r),this.renderErrorState(e,r)}}renderComponents(e,n){for(const r of n){const i=this.renderComponent(r);i&&e.appendChild(i)}}renderComponent(e){try{if(!this.componentRegistry.getComponent(e.type))return Xn.warn(`Component not found: ${e.type}`),this.createPlaceholderElement(e.type);const r=document.createElement("div");return r.setAttribute("data-component",e.type),e.props&&Object.entries(e.props).forEach(([i,l])=>{r.setAttribute(`data-prop-${i}`,JSON.stringify(l))}),e.children&&e.children.length>0?this.renderComponents(r,e.children):e.text&&(r.textContent=e.text),r}catch(n){return Xn.error(`Error rendering component ${e.type}:`,n),this.createErrorElement(e.type,n)}}createPlaceholderElement(e){const n=document.createElement("div");return n.className="component-placeholder",n.setAttribute("data-component-type",e),n.innerHTML=` + `},P_={onDragStart(s){let{active:e}=s;return"Picked up draggable item "+e.id+"."},onDragOver(s){let{active:e,over:n}=s;return n?"Draggable item "+e.id+" was moved over droppable area "+n.id+".":"Draggable item "+e.id+" is no longer over a droppable area."},onDragEnd(s){let{active:e,over:n}=s;return n?"Draggable item "+e.id+" was dropped over droppable area "+n.id:"Draggable item "+e.id+" was dropped."},onDragCancel(s){let{active:e}=s;return"Dragging was cancelled. Draggable item "+e.id+" was dropped."}};function U_(s){let{announcements:e=P_,container:n,hiddenTextDescribedById:r,screenReaderInstructions:i=z_}=s;const{announce:l,announcement:c}=I_(),d=Uo("DndLiveRegion"),[f,h]=N.useState(!1);if(N.useEffect(()=>{h(!0)},[]),j_(N.useMemo(()=>({onDragStart(y){let{active:v}=y;l(e.onDragStart({active:v}))},onDragMove(y){let{active:v,over:S}=y;e.onDragMove&&l(e.onDragMove({active:v,over:S}))},onDragOver(y){let{active:v,over:S}=y;l(e.onDragOver({active:v,over:S}))},onDragEnd(y){let{active:v,over:S}=y;l(e.onDragEnd({active:v,over:S}))},onDragCancel(y){let{active:v,over:S}=y;l(e.onDragCancel({active:v,over:S}))}}),[l,e])),!f)return null;const m=Xe.createElement(Xe.Fragment,null,Xe.createElement(M_,{id:r,value:i.draggable}),Xe.createElement(N_,{id:d,announcement:c}));return n?Ia.createPortal(m,n):m}var ln;(function(s){s.DragStart="dragStart",s.DragMove="dragMove",s.DragEnd="dragEnd",s.DragCancel="dragCancel",s.DragOver="dragOver",s.RegisterDroppable="registerDroppable",s.SetDroppableDisabled="setDroppableDisabled",s.UnregisterDroppable="unregisterDroppable"})(ln||(ln={}));function Rc(){}function Iy(s,e){return N.useMemo(()=>({sensor:s,options:e??{}}),[s,e])}function B_(){for(var s=arguments.length,e=new Array(s),n=0;n[...e].filter(r=>r!=null),[...e])}const Rr=Object.freeze({x:0,y:0});function jy(s,e){return Math.sqrt(Math.pow(s.x-e.x,2)+Math.pow(s.y-e.y,2))}function Hy(s,e){let{data:{value:n}}=s,{data:{value:r}}=e;return n-r}function q_(s,e){let{data:{value:n}}=s,{data:{value:r}}=e;return r-n}function zy(s){let{left:e,top:n,height:r,width:i}=s;return[{x:e,y:n},{x:e+i,y:n},{x:e,y:n+r},{x:e+i,y:n+r}]}function Py(s,e){if(!s||s.length===0)return null;const[n]=s;return n[e]}function Uy(s,e,n){return e===void 0&&(e=s.left),n===void 0&&(n=s.top),{x:e+s.width*.5,y:n+s.height*.5}}const G_=s=>{let{collisionRect:e,droppableRects:n,droppableContainers:r}=s;const i=Uy(e,e.left,e.top),l=[];for(const c of r){const{id:d}=c,f=n.get(d);if(f){const h=jy(Uy(f),i);l.push({id:d,data:{droppableContainer:c,value:h}})}}return l.sort(Hy)},F_=s=>{let{collisionRect:e,droppableRects:n,droppableContainers:r}=s;const i=zy(e),l=[];for(const c of r){const{id:d}=c,f=n.get(d);if(f){const h=zy(f),m=i.reduce((v,S,_)=>v+jy(h[_],S),0),y=Number((m/4).toFixed(4));l.push({id:d,data:{droppableContainer:c,value:y}})}}return l.sort(Hy)};function V_(s,e){const n=Math.max(e.top,s.top),r=Math.max(e.left,s.left),i=Math.min(e.left+e.width,s.left+s.width),l=Math.min(e.top+e.height,s.top+s.height),c=i-r,d=l-n;if(r{let{collisionRect:e,droppableRects:n,droppableContainers:r}=s;const i=[];for(const l of r){const{id:c}=l,d=n.get(c);if(d){const f=V_(d,e);f>0&&i.push({id:c,data:{droppableContainer:l,value:f}})}}return i.sort(q_)};function W_(s,e,n){return{...s,scaleX:e&&n?e.width/n.width:1,scaleY:e&&n?e.height/n.height:1}}function By(s,e){return s&&e?{x:s.left-e.left,y:s.top-e.top}:Rr}function Y_(s){return function(n){for(var r=arguments.length,i=new Array(r>1?r-1:0),l=1;l({...c,top:c.top+s*d.y,bottom:c.bottom+s*d.y,left:c.left+s*d.x,right:c.right+s*d.x}),{...n})}}const X_=Y_(1);function J_(s){if(s.startsWith("matrix3d(")){const e=s.slice(9,-1).split(/, /);return{x:+e[12],y:+e[13],scaleX:+e[0],scaleY:+e[5]}}else if(s.startsWith("matrix(")){const e=s.slice(7,-1).split(/, /);return{x:+e[4],y:+e[5],scaleX:+e[0],scaleY:+e[3]}}return null}function Q_(s,e,n){const r=J_(e);if(!r)return s;const{scaleX:i,scaleY:l,x:c,y:d}=r,f=s.left-c-(1-i)*parseFloat(n),h=s.top-d-(1-l)*parseFloat(n.slice(n.indexOf(" ")+1)),m=i?s.width/i:s.width,y=l?s.height/l:s.height;return{width:m,height:y,top:h,right:f+m,bottom:h+y,left:f}}const Z_={ignoreTransform:!1};function Es(s,e){e===void 0&&(e=Z_);let n=s.getBoundingClientRect();if(e.ignoreTransform){const{transform:h,transformOrigin:m}=zn(s).getComputedStyle(s);h&&(n=Q_(n,h,m))}const{top:r,left:i,width:l,height:c,bottom:d,right:f}=n;return{top:r,left:i,width:l,height:c,bottom:d,right:f}}function qy(s){return Es(s,{ignoreTransform:!0})}function eA(s){const e=s.innerWidth,n=s.innerHeight;return{top:0,left:0,right:e,bottom:n,width:e,height:n}}function tA(s,e){return e===void 0&&(e=zn(s).getComputedStyle(s)),e.position==="fixed"}function nA(s,e){e===void 0&&(e=zn(s).getComputedStyle(s));const n=/(auto|scroll|overlay)/;return["overflow","overflowX","overflowY"].some(i=>{const l=e[i];return typeof l=="string"?n.test(l):!1})}function Dc(s,e){const n=[];function r(i){if(e!=null&&n.length>=e||!i)return n;if(If(i)&&i.scrollingElement!=null&&!n.includes(i.scrollingElement))return n.push(i.scrollingElement),n;if(!Ho(i)||Ly(i)||n.includes(i))return n;const l=zn(s).getComputedStyle(i);return i!==s&&nA(i,l)&&n.push(i),tA(i,l)?n:r(i.parentNode)}return s?r(s):n}function Gy(s){const[e]=Dc(s,1);return e??null}function Bf(s){return!Tc||!s?null:Ss(s)?s:Nf(s)?If(s)||s===ws(s).scrollingElement?window:Ho(s)?s:null:null}function Fy(s){return Ss(s)?s.scrollX:s.scrollLeft}function Vy(s){return Ss(s)?s.scrollY:s.scrollTop}function qf(s){return{x:Fy(s),y:Vy(s)}}var mn;(function(s){s[s.Forward=1]="Forward",s[s.Backward=-1]="Backward"})(mn||(mn={}));function Ky(s){return!Tc||!s?!1:s===document.scrollingElement}function Wy(s){const e={x:0,y:0},n=Ky(s)?{height:window.innerHeight,width:window.innerWidth}:{height:s.clientHeight,width:s.clientWidth},r={x:s.scrollWidth-n.width,y:s.scrollHeight-n.height},i=s.scrollTop<=e.y,l=s.scrollLeft<=e.x,c=s.scrollTop>=r.y,d=s.scrollLeft>=r.x;return{isTop:i,isLeft:l,isBottom:c,isRight:d,maxScroll:r,minScroll:e}}const rA={x:.2,y:.2};function aA(s,e,n,r,i){let{top:l,left:c,right:d,bottom:f}=n;r===void 0&&(r=10),i===void 0&&(i=rA);const{isTop:h,isBottom:m,isLeft:y,isRight:v}=Wy(s),S={x:0,y:0},_={x:0,y:0},x={height:e.height*i.y,width:e.width*i.x};return!h&&l<=e.top+x.height?(S.y=mn.Backward,_.y=r*Math.abs((e.top+x.height-l)/x.height)):!m&&f>=e.bottom-x.height&&(S.y=mn.Forward,_.y=r*Math.abs((e.bottom-x.height-f)/x.height)),!v&&d>=e.right-x.width?(S.x=mn.Forward,_.x=r*Math.abs((e.right-x.width-d)/x.width)):!y&&c<=e.left+x.width&&(S.x=mn.Backward,_.x=r*Math.abs((e.left+x.width-c)/x.width)),{direction:S,speed:_}}function iA(s){if(s===document.scrollingElement){const{innerWidth:l,innerHeight:c}=window;return{top:0,left:0,right:l,bottom:c,width:l,height:c}}const{top:e,left:n,right:r,bottom:i}=s.getBoundingClientRect();return{top:e,left:n,right:r,bottom:i,width:s.clientWidth,height:s.clientHeight}}function Yy(s){return s.reduce((e,n)=>Cs(e,qf(n)),Rr)}function sA(s){return s.reduce((e,n)=>e+Fy(n),0)}function oA(s){return s.reduce((e,n)=>e+Vy(n),0)}function lA(s,e){if(e===void 0&&(e=Es),!s)return;const{top:n,left:r,bottom:i,right:l}=e(s);Gy(s)&&(i<=0||l<=0||n>=window.innerHeight||r>=window.innerWidth)&&s.scrollIntoView({block:"center",inline:"center"})}const cA=[["x",["left","right"],sA],["y",["top","bottom"],oA]];class Gf{constructor(e,n){this.rect=void 0,this.width=void 0,this.height=void 0,this.top=void 0,this.bottom=void 0,this.right=void 0,this.left=void 0;const r=Dc(n),i=Yy(r);this.rect={...e},this.width=e.width,this.height=e.height;for(const[l,c,d]of cA)for(const f of c)Object.defineProperty(this,f,{get:()=>{const h=d(r),m=i[l]-h;return this.rect[f]+m},enumerable:!0});Object.defineProperty(this,"rect",{enumerable:!1})}}class Go{constructor(e){this.target=void 0,this.listeners=[],this.removeAll=()=>{this.listeners.forEach(n=>{var r;return(r=this.target)==null?void 0:r.removeEventListener(...n)})},this.target=e}add(e,n,r){var i;(i=this.target)==null||i.addEventListener(e,n,r),this.listeners.push([e,n,r])}}function uA(s){const{EventTarget:e}=zn(s);return s instanceof e?s:ws(s)}function Ff(s,e){const n=Math.abs(s.x),r=Math.abs(s.y);return typeof e=="number"?Math.sqrt(n**2+r**2)>e:"x"in e&&"y"in e?n>e.x&&r>e.y:"x"in e?n>e.x:"y"in e?r>e.y:!1}var dr;(function(s){s.Click="click",s.DragStart="dragstart",s.Keydown="keydown",s.ContextMenu="contextmenu",s.Resize="resize",s.SelectionChange="selectionchange",s.VisibilityChange="visibilitychange"})(dr||(dr={}));function Xy(s){s.preventDefault()}function dA(s){s.stopPropagation()}var dt;(function(s){s.Space="Space",s.Down="ArrowDown",s.Right="ArrowRight",s.Left="ArrowLeft",s.Up="ArrowUp",s.Esc="Escape",s.Enter="Enter",s.Tab="Tab"})(dt||(dt={}));const Jy={start:[dt.Space,dt.Enter],cancel:[dt.Esc],end:[dt.Space,dt.Enter,dt.Tab]},fA=(s,e)=>{let{currentCoordinates:n}=e;switch(s.code){case dt.Right:return{...n,x:n.x+25};case dt.Left:return{...n,x:n.x-25};case dt.Down:return{...n,y:n.y+25};case dt.Up:return{...n,y:n.y-25}}};class Vf{constructor(e){this.props=void 0,this.autoScrollEnabled=!1,this.referenceCoordinates=void 0,this.listeners=void 0,this.windowListeners=void 0,this.props=e;const{event:{target:n}}=e;this.props=e,this.listeners=new Go(ws(n)),this.windowListeners=new Go(zn(n)),this.handleKeyDown=this.handleKeyDown.bind(this),this.handleCancel=this.handleCancel.bind(this),this.attach()}attach(){this.handleStart(),this.windowListeners.add(dr.Resize,this.handleCancel),this.windowListeners.add(dr.VisibilityChange,this.handleCancel),setTimeout(()=>this.listeners.add(dr.Keydown,this.handleKeyDown))}handleStart(){const{activeNode:e,onStart:n}=this.props,r=e.node.current;r&&lA(r),n(Rr)}handleKeyDown(e){if(Pf(e)){const{active:n,context:r,options:i}=this.props,{keyboardCodes:l=Jy,coordinateGetter:c=fA,scrollBehavior:d="smooth"}=i,{code:f}=e;if(l.end.includes(f)){this.handleEnd(e);return}if(l.cancel.includes(f)){this.handleCancel(e);return}const{collisionRect:h}=r.current,m=h?{x:h.left,y:h.top}:Rr;this.referenceCoordinates||(this.referenceCoordinates=m);const y=c(e,{active:n,context:r.current,currentCoordinates:m});if(y){const v=Bo(y,m),S={x:0,y:0},{scrollableAncestors:_}=r.current;for(const x of _){const A=e.code,{isTop:D,isRight:O,isLeft:k,isBottom:L,maxScroll:B,minScroll:q}=Wy(x),z=iA(x),W={x:Math.min(A===dt.Right?z.right-z.width/2:z.right,Math.max(A===dt.Right?z.left:z.left+z.width/2,y.x)),y:Math.min(A===dt.Down?z.bottom-z.height/2:z.bottom,Math.max(A===dt.Down?z.top:z.top+z.height/2,y.y))},de=A===dt.Right&&!O||A===dt.Left&&!k,_e=A===dt.Down&&!L||A===dt.Up&&!D;if(de&&W.x!==y.x){const Se=x.scrollLeft+v.x,je=A===dt.Right&&Se<=B.x||A===dt.Left&&Se>=q.x;if(je&&!v.y){x.scrollTo({left:Se,behavior:d});return}je?S.x=x.scrollLeft-Se:S.x=A===dt.Right?x.scrollLeft-B.x:x.scrollLeft-q.x,S.x&&x.scrollBy({left:-S.x,behavior:d});break}else if(_e&&W.y!==y.y){const Se=x.scrollTop+v.y,je=A===dt.Down&&Se<=B.y||A===dt.Up&&Se>=q.y;if(je&&!v.x){x.scrollTo({top:Se,behavior:d});return}je?S.y=x.scrollTop-Se:S.y=A===dt.Down?x.scrollTop-B.y:x.scrollTop-q.y,S.y&&x.scrollBy({top:-S.y,behavior:d});break}}this.handleMove(e,Cs(Bo(y,this.referenceCoordinates),S))}}}handleMove(e,n){const{onMove:r}=this.props;e.preventDefault(),r(n)}handleEnd(e){const{onEnd:n}=this.props;e.preventDefault(),this.detach(),n()}handleCancel(e){const{onCancel:n}=this.props;e.preventDefault(),this.detach(),n()}detach(){this.listeners.removeAll(),this.windowListeners.removeAll()}}Vf.activators=[{eventName:"onKeyDown",handler:(s,e,n)=>{let{keyboardCodes:r=Jy,onActivation:i}=e,{active:l}=n;const{code:c}=s.nativeEvent;if(r.start.includes(c)){const d=l.activatorNode.current;return d&&s.target!==d?!1:(s.preventDefault(),i?.({event:s.nativeEvent}),!0)}return!1}}];function Qy(s){return!!(s&&"distance"in s)}function Zy(s){return!!(s&&"delay"in s)}class Kf{constructor(e,n,r){var i;r===void 0&&(r=uA(e.event.target)),this.props=void 0,this.events=void 0,this.autoScrollEnabled=!0,this.document=void 0,this.activated=!1,this.initialCoordinates=void 0,this.timeoutId=null,this.listeners=void 0,this.documentListeners=void 0,this.windowListeners=void 0,this.props=e,this.events=n;const{event:l}=e,{target:c}=l;this.props=e,this.events=n,this.document=ws(c),this.documentListeners=new Go(this.document),this.listeners=new Go(r),this.windowListeners=new Go(zn(c)),this.initialCoordinates=(i=Uf(l))!=null?i:Rr,this.handleStart=this.handleStart.bind(this),this.handleMove=this.handleMove.bind(this),this.handleEnd=this.handleEnd.bind(this),this.handleCancel=this.handleCancel.bind(this),this.handleKeydown=this.handleKeydown.bind(this),this.removeTextSelection=this.removeTextSelection.bind(this),this.attach()}attach(){const{events:e,props:{options:{activationConstraint:n,bypassActivationConstraint:r}}}=this;if(this.listeners.add(e.move.name,this.handleMove,{passive:!1}),this.listeners.add(e.end.name,this.handleEnd),e.cancel&&this.listeners.add(e.cancel.name,this.handleCancel),this.windowListeners.add(dr.Resize,this.handleCancel),this.windowListeners.add(dr.DragStart,Xy),this.windowListeners.add(dr.VisibilityChange,this.handleCancel),this.windowListeners.add(dr.ContextMenu,Xy),this.documentListeners.add(dr.Keydown,this.handleKeydown),n){if(r!=null&&r({event:this.props.event,activeNode:this.props.activeNode,options:this.props.options}))return this.handleStart();if(Zy(n)){this.timeoutId=setTimeout(this.handleStart,n.delay),this.handlePending(n);return}if(Qy(n)){this.handlePending(n);return}}this.handleStart()}detach(){this.listeners.removeAll(),this.windowListeners.removeAll(),setTimeout(this.documentListeners.removeAll,50),this.timeoutId!==null&&(clearTimeout(this.timeoutId),this.timeoutId=null)}handlePending(e,n){const{active:r,onPending:i}=this.props;i(r,e,this.initialCoordinates,n)}handleStart(){const{initialCoordinates:e}=this,{onStart:n}=this.props;e&&(this.activated=!0,this.documentListeners.add(dr.Click,dA,{capture:!0}),this.removeTextSelection(),this.documentListeners.add(dr.SelectionChange,this.removeTextSelection),n(e))}handleMove(e){var n;const{activated:r,initialCoordinates:i,props:l}=this,{onMove:c,options:{activationConstraint:d}}=l;if(!i)return;const f=(n=Uf(e))!=null?n:Rr,h=Bo(i,f);if(!r&&d){if(Qy(d)){if(d.tolerance!=null&&Ff(h,d.tolerance))return this.handleCancel();if(Ff(h,d.distance))return this.handleStart()}if(Zy(d)&&Ff(h,d.tolerance))return this.handleCancel();this.handlePending(d,h);return}e.cancelable&&e.preventDefault(),c(f)}handleEnd(){const{onAbort:e,onEnd:n}=this.props;this.detach(),this.activated||e(this.props.active),n()}handleCancel(){const{onAbort:e,onCancel:n}=this.props;this.detach(),this.activated||e(this.props.active),n()}handleKeydown(e){e.code===dt.Esc&&this.handleCancel()}removeTextSelection(){var e;(e=this.document.getSelection())==null||e.removeAllRanges()}}const hA={cancel:{name:"pointercancel"},move:{name:"pointermove"},end:{name:"pointerup"}};class Wf extends Kf{constructor(e){const{event:n}=e,r=ws(n.target);super(e,hA,r)}}Wf.activators=[{eventName:"onPointerDown",handler:(s,e)=>{let{nativeEvent:n}=s,{onActivation:r}=e;return!n.isPrimary||n.button!==0?!1:(r?.({event:n}),!0)}}];const pA={move:{name:"mousemove"},end:{name:"mouseup"}};var Yf;(function(s){s[s.RightClick=2]="RightClick"})(Yf||(Yf={}));class gA extends Kf{constructor(e){super(e,pA,ws(e.event.target))}}gA.activators=[{eventName:"onMouseDown",handler:(s,e)=>{let{nativeEvent:n}=s,{onActivation:r}=e;return n.button===Yf.RightClick?!1:(r?.({event:n}),!0)}}];const Xf={cancel:{name:"touchcancel"},move:{name:"touchmove"},end:{name:"touchend"}};class mA extends Kf{constructor(e){super(e,Xf)}static setup(){return window.addEventListener(Xf.move.name,e,{capture:!1,passive:!1}),function(){window.removeEventListener(Xf.move.name,e)};function e(){}}}mA.activators=[{eventName:"onTouchStart",handler:(s,e)=>{let{nativeEvent:n}=s,{onActivation:r}=e;const{touches:i}=n;return i.length>1?!1:(r?.({event:n}),!0)}}];var Fo;(function(s){s[s.Pointer=0]="Pointer",s[s.DraggableRect=1]="DraggableRect"})(Fo||(Fo={}));var Oc;(function(s){s[s.TreeOrder=0]="TreeOrder",s[s.ReversedTreeOrder=1]="ReversedTreeOrder"})(Oc||(Oc={}));function yA(s){let{acceleration:e,activator:n=Fo.Pointer,canScroll:r,draggingRect:i,enabled:l,interval:c=5,order:d=Oc.TreeOrder,pointerCoordinates:f,scrollableAncestors:h,scrollableAncestorRects:m,delta:y,threshold:v}=s;const S=vA({delta:y,disabled:!l}),[_,x]=R_(),A=N.useRef({x:0,y:0}),D=N.useRef({x:0,y:0}),O=N.useMemo(()=>{switch(n){case Fo.Pointer:return f?{top:f.y,bottom:f.y,left:f.x,right:f.x}:null;case Fo.DraggableRect:return i}},[n,i,f]),k=N.useRef(null),L=N.useCallback(()=>{const q=k.current;if(!q)return;const z=A.current.x*D.current.x,W=A.current.y*D.current.y;q.scrollBy(z,W)},[]),B=N.useMemo(()=>d===Oc.TreeOrder?[...h].reverse():h,[d,h]);N.useEffect(()=>{if(!l||!h.length||!O){x();return}for(const q of B){if(r?.(q)===!1)continue;const z=h.indexOf(q),W=m[z];if(!W)continue;const{direction:de,speed:_e}=aA(q,W,O,e,v);for(const Se of["x","y"])S[Se][de[Se]]||(_e[Se]=0,de[Se]=0);if(_e.x>0||_e.y>0){x(),k.current=q,_(L,c),A.current=_e,D.current=de;return}}A.current={x:0,y:0},D.current={x:0,y:0},x()},[e,L,r,x,l,c,JSON.stringify(O),JSON.stringify(S),_,h,B,m,JSON.stringify(v)])}const bA={x:{[mn.Backward]:!1,[mn.Forward]:!1},y:{[mn.Backward]:!1,[mn.Forward]:!1}};function vA(s){let{delta:e,disabled:n}=s;const r=Hf(e);return Po(i=>{if(n||!r||!i)return bA;const l={x:Math.sign(e.x-r.x),y:Math.sign(e.y-r.y)};return{x:{[mn.Backward]:i.x[mn.Backward]||l.x===-1,[mn.Forward]:i.x[mn.Forward]||l.x===1},y:{[mn.Backward]:i.y[mn.Backward]||l.y===-1,[mn.Forward]:i.y[mn.Forward]||l.y===1}}},[n,e,r])}function SA(s,e){const n=e!=null?s.get(e):void 0,r=n?n.node.current:null;return Po(i=>{var l;return e==null?null:(l=r??i)!=null?l:null},[r,e])}function wA(s,e){return N.useMemo(()=>s.reduce((n,r)=>{const{sensor:i}=r,l=i.activators.map(c=>({eventName:c.eventName,handler:e(c.handler,r)}));return[...n,...l]},[]),[s,e])}var Vo;(function(s){s[s.Always=0]="Always",s[s.BeforeDragging=1]="BeforeDragging",s[s.WhileDragging=2]="WhileDragging"})(Vo||(Vo={}));var Jf;(function(s){s.Optimized="optimized"})(Jf||(Jf={}));const eb=new Map;function CA(s,e){let{dragging:n,dependencies:r,config:i}=e;const[l,c]=N.useState(null),{frequency:d,measure:f,strategy:h}=i,m=N.useRef(s),y=A(),v=zo(y),S=N.useCallback(function(D){D===void 0&&(D=[]),!v.current&&c(O=>O===null?D:O.concat(D.filter(k=>!O.includes(k))))},[v]),_=N.useRef(null),x=Po(D=>{if(y&&!n)return eb;if(!D||D===eb||m.current!==s||l!=null){const O=new Map;for(let k of s){if(!k)continue;if(l&&l.length>0&&!l.includes(k.id)&&k.rect.current){O.set(k.id,k.rect.current);continue}const L=k.node.current,B=L?new Gf(f(L),L):null;k.rect.current=B,B&&O.set(k.id,B)}return O}return D},[s,l,n,y,f]);return N.useEffect(()=>{m.current=s},[s]),N.useEffect(()=>{y||S()},[n,y]),N.useEffect(()=>{l&&l.length>0&&c(null)},[JSON.stringify(l)]),N.useEffect(()=>{y||typeof d!="number"||_.current!==null||(_.current=setTimeout(()=>{S(),_.current=null},d))},[d,y,S,...r]),{droppableRects:x,measureDroppableContainers:S,measuringScheduled:l!=null};function A(){switch(h){case Vo.Always:return!1;case Vo.BeforeDragging:return n;default:return!n}}}function tb(s,e){return Po(n=>s?n||(typeof e=="function"?e(s):s):null,[e,s])}function EA(s,e){return tb(s,e)}function _A(s){let{callback:e,disabled:n}=s;const r=jf(e),i=N.useMemo(()=>{if(n||typeof window>"u"||typeof window.MutationObserver>"u")return;const{MutationObserver:l}=window;return new l(r)},[r,n]);return N.useEffect(()=>()=>i?.disconnect(),[i]),i}function Lc(s){let{callback:e,disabled:n}=s;const r=jf(e),i=N.useMemo(()=>{if(n||typeof window>"u"||typeof window.ResizeObserver>"u")return;const{ResizeObserver:l}=window;return new l(r)},[n]);return N.useEffect(()=>()=>i?.disconnect(),[i]),i}function AA(s){return new Gf(Es(s),s)}function nb(s,e,n){e===void 0&&(e=AA);const[r,i]=N.useState(null);function l(){i(f=>{if(!s)return null;if(s.isConnected===!1){var h;return(h=f??n)!=null?h:null}const m=e(s);return JSON.stringify(f)===JSON.stringify(m)?f:m})}const c=_A({callback(f){if(s)for(const h of f){const{type:m,target:y}=h;if(m==="childList"&&y instanceof HTMLElement&&y.contains(s)){l();break}}}}),d=Lc({callback:l});return Fr(()=>{l(),s?(d?.observe(s),c?.observe(document.body,{childList:!0,subtree:!0})):(d?.disconnect(),c?.disconnect())},[s]),r}function xA(s){const e=tb(s);return By(s,e)}const rb=[];function TA(s){const e=N.useRef(s),n=Po(r=>s?r&&r!==rb&&s&&e.current&&s.parentNode===e.current.parentNode?r:Dc(s):rb,[s]);return N.useEffect(()=>{e.current=s},[s]),n}function kA(s){const[e,n]=N.useState(null),r=N.useRef(s),i=N.useCallback(l=>{const c=Bf(l.target);c&&n(d=>d?(d.set(c,qf(c)),new Map(d)):null)},[]);return N.useEffect(()=>{const l=r.current;if(s!==l){c(l);const d=s.map(f=>{const h=Bf(f);return h?(h.addEventListener("scroll",i,{passive:!0}),[h,qf(h)]):null}).filter(f=>f!=null);n(d.length?new Map(d):null),r.current=s}return()=>{c(s),c(l)};function c(d){d.forEach(f=>{const h=Bf(f);h?.removeEventListener("scroll",i)})}},[i,s]),N.useMemo(()=>s.length?e?Array.from(e.values()).reduce((l,c)=>Cs(l,c),Rr):Yy(s):Rr,[s,e])}function ab(s,e){e===void 0&&(e=[]);const n=N.useRef(null);return N.useEffect(()=>{n.current=null},e),N.useEffect(()=>{const r=s!==Rr;r&&!n.current&&(n.current=s),!r&&n.current&&(n.current=null)},[s]),n.current?Bo(s,n.current):Rr}function RA(s){N.useEffect(()=>{if(!Tc)return;const e=s.map(n=>{let{sensor:r}=n;return r.setup==null?void 0:r.setup()});return()=>{for(const n of e)n?.()}},s.map(e=>{let{sensor:n}=e;return n}))}function DA(s,e){return N.useMemo(()=>s.reduce((n,r)=>{let{eventName:i,handler:l}=r;return n[i]=c=>{l(c,e)},n},{}),[s,e])}function ib(s){return N.useMemo(()=>s?eA(s):null,[s])}const sb=[];function OA(s,e){e===void 0&&(e=Es);const[n]=s,r=ib(n?zn(n):null),[i,l]=N.useState(sb);function c(){l(()=>s.length?s.map(f=>Ky(f)?r:new Gf(e(f),f)):sb)}const d=Lc({callback:c});return Fr(()=>{d?.disconnect(),c(),s.forEach(f=>d?.observe(f))},[s]),i}function LA(s){if(!s)return null;if(s.children.length>1)return s;const e=s.children[0];return Ho(e)?e:s}function $A(s){let{measure:e}=s;const[n,r]=N.useState(null),i=N.useCallback(h=>{for(const{target:m}of h)if(Ho(m)){r(y=>{const v=e(m);return y?{...y,width:v.width,height:v.height}:v});break}},[e]),l=Lc({callback:i}),c=N.useCallback(h=>{const m=LA(h);l?.disconnect(),m&&l?.observe(m),r(m?e(m):null)},[e,l]),[d,f]=kc(c);return N.useMemo(()=>({nodeRef:d,rect:n,setRef:f}),[n,d,f])}const MA=[{sensor:Wf,options:{}},{sensor:Vf,options:{}}],NA={current:{}},$c={draggable:{measure:qy},droppable:{measure:qy,strategy:Vo.WhileDragging,frequency:Jf.Optimized},dragOverlay:{measure:Es}};class Ko extends Map{get(e){var n;return e!=null&&(n=super.get(e))!=null?n:void 0}toArray(){return Array.from(this.values())}getEnabled(){return this.toArray().filter(e=>{let{disabled:n}=e;return!n})}getNodeFor(e){var n,r;return(n=(r=this.get(e))==null?void 0:r.node.current)!=null?n:void 0}}const IA={activatorEvent:null,active:null,activeNode:null,activeNodeRect:null,collisions:null,containerNodeRect:null,draggableNodes:new Map,droppableRects:new Map,droppableContainers:new Ko,over:null,dragOverlay:{nodeRef:{current:null},rect:null,setRef:Rc},scrollableAncestors:[],scrollableAncestorRects:[],measuringConfiguration:$c,measureDroppableContainers:Rc,windowRect:null,measuringScheduled:!1},jA={activatorEvent:null,activators:[],active:null,activeNodeRect:null,ariaDescribedById:{draggable:""},dispatch:Rc,draggableNodes:new Map,over:null,measureDroppableContainers:Rc},Mc=N.createContext(jA),ob=N.createContext(IA);function HA(){return{draggable:{active:null,initialCoordinates:{x:0,y:0},nodes:new Map,translate:{x:0,y:0}},droppable:{containers:new Ko}}}function zA(s,e){switch(e.type){case ln.DragStart:return{...s,draggable:{...s.draggable,initialCoordinates:e.initialCoordinates,active:e.active}};case ln.DragMove:return s.draggable.active==null?s:{...s,draggable:{...s.draggable,translate:{x:e.coordinates.x-s.draggable.initialCoordinates.x,y:e.coordinates.y-s.draggable.initialCoordinates.y}}};case ln.DragEnd:case ln.DragCancel:return{...s,draggable:{...s.draggable,active:null,initialCoordinates:{x:0,y:0},translate:{x:0,y:0}}};case ln.RegisterDroppable:{const{element:n}=e,{id:r}=n,i=new Ko(s.droppable.containers);return i.set(r,n),{...s,droppable:{...s.droppable,containers:i}}}case ln.SetDroppableDisabled:{const{id:n,key:r,disabled:i}=e,l=s.droppable.containers.get(n);if(!l||r!==l.key)return s;const c=new Ko(s.droppable.containers);return c.set(n,{...l,disabled:i}),{...s,droppable:{...s.droppable,containers:c}}}case ln.UnregisterDroppable:{const{id:n,key:r}=e,i=s.droppable.containers.get(n);if(!i||r!==i.key)return s;const l=new Ko(s.droppable.containers);return l.delete(n),{...s,droppable:{...s.droppable,containers:l}}}default:return s}}function PA(s){let{disabled:e}=s;const{active:n,activatorEvent:r,draggableNodes:i}=N.useContext(Mc),l=Hf(r),c=Hf(n?.id);return N.useEffect(()=>{if(!e&&!r&&l&&c!=null){if(!Pf(l)||document.activeElement===l.target)return;const d=i.get(c);if(!d)return;const{activatorNode:f,node:h}=d;if(!f.current&&!h.current)return;requestAnimationFrame(()=>{for(const m of[f.current,h.current]){if(!m)continue;const y=L_(m);if(y){y.focus();break}}})}},[r,e,i,c,l]),null}function UA(s,e){let{transform:n,...r}=e;return s!=null&&s.length?s.reduce((i,l)=>l({transform:i,...r}),n):n}function BA(s){return N.useMemo(()=>({draggable:{...$c.draggable,...s?.draggable},droppable:{...$c.droppable,...s?.droppable},dragOverlay:{...$c.dragOverlay,...s?.dragOverlay}}),[s?.draggable,s?.droppable,s?.dragOverlay])}function qA(s){let{activeNode:e,measure:n,initialRect:r,config:i=!0}=s;const l=N.useRef(!1),{x:c,y:d}=typeof i=="boolean"?{x:i,y:i}:i;Fr(()=>{if(!c&&!d||!e){l.current=!1;return}if(l.current||!r)return;const h=e?.node.current;if(!h||h.isConnected===!1)return;const m=n(h),y=By(m,r);if(c||(y.x=0),d||(y.y=0),l.current=!0,Math.abs(y.x)>0||Math.abs(y.y)>0){const v=Gy(h);v&&v.scrollBy({top:y.y,left:y.x})}},[e,c,d,r,n])}const lb=N.createContext({...Rr,scaleX:1,scaleY:1});var ja;(function(s){s[s.Uninitialized=0]="Uninitialized",s[s.Initializing=1]="Initializing",s[s.Initialized=2]="Initialized"})(ja||(ja={}));const GA=N.memo(function(e){var n,r,i,l;let{id:c,accessibility:d,autoScroll:f=!0,children:h,sensors:m=MA,collisionDetection:y=K_,measuring:v,modifiers:S,..._}=e;const x=N.useReducer(zA,void 0,HA),[A,D]=x,[O,k]=H_(),[L,B]=N.useState(ja.Uninitialized),q=L===ja.Initialized,{draggable:{active:z,nodes:W,translate:de},droppable:{containers:_e}}=A,Se=z!=null?W.get(z):null,je=N.useRef({initial:null,translated:null}),Fe=N.useMemo(()=>{var pe;return z!=null?{id:z,data:(pe=Se?.data)!=null?pe:NA,rect:je}:null},[z,Se]),ze=N.useRef(null),[Et,Q]=N.useState(null),[ue,De]=N.useState(null),ee=zo(_,Object.values(_)),me=Uo("DndDescribedBy",c),H=N.useMemo(()=>_e.getEnabled(),[_e]),T=BA(v),{droppableRects:fe,measureDroppableContainers:he,measuringScheduled:Ae}=CA(H,{dragging:q,dependencies:[de.x,de.y],config:T.droppable}),se=SA(W,z),ge=N.useMemo(()=>ue?Uf(ue):null,[ue]),$e=ve(),Te=EA(se,T.draggable.measure);qA({activeNode:z!=null?W.get(z):null,config:$e.layoutShiftCompensation,initialRect:Te,measure:T.draggable.measure});const Ie=nb(se,T.draggable.measure,Te),xt=nb(se?se.parentElement:null),rt=N.useRef({activatorEvent:null,active:null,activeNode:se,collisionRect:null,collisions:null,droppableRects:fe,draggableNodes:W,draggingNode:null,draggingNodeRect:null,droppableContainers:_e,over:null,scrollableAncestors:[],scrollAdjustedTranslate:null}),gt=_e.getNodeFor((n=rt.current.over)==null?void 0:n.id),at=$A({measure:T.dragOverlay.measure}),Yt=(r=at.nodeRef.current)!=null?r:se,Ht=q?(i=at.rect)!=null?i:Ie:null,bt=!!(at.nodeRef.current&&at.rect),qe=xA(bt?null:Ie),mt=ib(Yt?zn(Yt):null),Dt=TA(q?gt??se:null),zt=OA(Dt),kn=UA(S,{transform:{x:de.x-qe.x,y:de.y-qe.y,scaleX:1,scaleY:1},activatorEvent:ue,active:Fe,activeNodeRect:Ie,containerNodeRect:xt,draggingNodeRect:Ht,over:rt.current.over,overlayNodeRect:at.rect,scrollableAncestors:Dt,scrollableAncestorRects:zt,windowRect:mt}),hr=ge?Cs(ge,de):null,Ot=kA(Dt),Mi=ab(Ot),ua=ab(Ot,[Ie]),bn=Cs(kn,Mi),Mn=Ht?X_(Ht,kn):null,it=Fe&&Mn?y({active:Fe,collisionRect:Mn,droppableRects:fe,droppableContainers:H,pointerCoordinates:hr}):null,Un=Py(it,"id"),[vn,da]=N.useState(null),pr=bt?kn:Cs(kn,ua),un=W_(pr,(l=vn?.rect)!=null?l:null,Ie),Rn=N.useRef(null),dn=N.useCallback((pe,Ee)=>{let{sensor:xe,options:Ke}=Ee;if(ze.current==null)return;const ke=W.get(ze.current);if(!ke)return;const Me=pe.nativeEvent,Re=new xe({active:ze.current,activeNode:ke,event:Me,options:Ke,context:rt,onAbort(Pe){if(!W.get(Pe))return;const{onDragAbort:Ce}=ee.current,He={id:Pe};Ce?.(He),O({type:"onDragAbort",event:He})},onPending(Pe,Ve,Ce,He){if(!W.get(Pe))return;const{onDragPending:Lt}=ee.current,Bt={id:Pe,constraint:Ve,initialCoordinates:Ce,offset:He};Lt?.(Bt),O({type:"onDragPending",event:Bt})},onStart(Pe){const Ve=ze.current;if(Ve==null)return;const Ce=W.get(Ve);if(!Ce)return;const{onDragStart:He}=ee.current,nt={activatorEvent:Me,active:{id:Ve,data:Ce.data,rect:je}};Ia.unstable_batchedUpdates(()=>{He?.(nt),B(ja.Initializing),D({type:ln.DragStart,initialCoordinates:Pe,active:Ve}),O({type:"onDragStart",event:nt}),Q(Rn.current),De(Me)})},onMove(Pe){D({type:ln.DragMove,coordinates:Pe})},onEnd:tt(ln.DragEnd),onCancel:tt(ln.DragCancel)});Rn.current=Re;function tt(Pe){return async function(){const{active:Ce,collisions:He,over:nt,scrollAdjustedTranslate:Lt}=rt.current;let Bt=null;if(Ce&&Lt){const{cancelDrop:Nn}=ee.current;Bt={activatorEvent:Me,active:Ce,collisions:He,delta:Lt,over:nt},Pe===ln.DragEnd&&typeof Nn=="function"&&await Promise.resolve(Nn(Bt))&&(Pe=ln.DragCancel)}ze.current=null,Ia.unstable_batchedUpdates(()=>{D({type:Pe}),B(ja.Uninitialized),da(null),Q(null),De(null),Rn.current=null;const Nn=Pe===ln.DragEnd?"onDragEnd":"onDragCancel";if(Bt){const Gt=ee.current[Nn];Gt?.(Bt),O({type:Nn,event:Bt})}})}}},[W]),V=N.useCallback((pe,Ee)=>(xe,Ke)=>{const ke=xe.nativeEvent,Me=W.get(Ke);if(ze.current!==null||!Me||ke.dndKit||ke.defaultPrevented)return;const Re={active:Me};pe(xe,Ee.options,Re)===!0&&(ke.dndKit={capturedBy:Ee.sensor},ze.current=Ke,dn(xe,Ee))},[W,dn]),ie=wA(m,V);RA(m),Fr(()=>{Ie&&L===ja.Initializing&&B(ja.Initialized)},[Ie,L]),N.useEffect(()=>{const{onDragMove:pe}=ee.current,{active:Ee,activatorEvent:xe,collisions:Ke,over:ke}=rt.current;if(!Ee||!xe)return;const Me={active:Ee,activatorEvent:xe,collisions:Ke,delta:{x:bn.x,y:bn.y},over:ke};Ia.unstable_batchedUpdates(()=>{pe?.(Me),O({type:"onDragMove",event:Me})})},[bn.x,bn.y]),N.useEffect(()=>{const{active:pe,activatorEvent:Ee,collisions:xe,droppableContainers:Ke,scrollAdjustedTranslate:ke}=rt.current;if(!pe||ze.current==null||!Ee||!ke)return;const{onDragOver:Me}=ee.current,Re=Ke.get(Un),tt=Re&&Re.rect.current?{id:Re.id,rect:Re.rect.current,data:Re.data,disabled:Re.disabled}:null,Pe={active:pe,activatorEvent:Ee,collisions:xe,delta:{x:ke.x,y:ke.y},over:tt};Ia.unstable_batchedUpdates(()=>{da(tt),Me?.(Pe),O({type:"onDragOver",event:Pe})})},[Un]),Fr(()=>{rt.current={activatorEvent:ue,active:Fe,activeNode:se,collisionRect:Mn,collisions:it,droppableRects:fe,draggableNodes:W,draggingNode:Yt,draggingNodeRect:Ht,droppableContainers:_e,over:vn,scrollableAncestors:Dt,scrollAdjustedTranslate:bn},je.current={initial:Ht,translated:Mn}},[Fe,se,it,Mn,W,Yt,Ht,fe,_e,vn,Dt,bn]),yA({...$e,delta:de,draggingRect:Mn,pointerCoordinates:hr,scrollableAncestors:Dt,scrollableAncestorRects:zt});const ce=N.useMemo(()=>({active:Fe,activeNode:se,activeNodeRect:Ie,activatorEvent:ue,collisions:it,containerNodeRect:xt,dragOverlay:at,draggableNodes:W,droppableContainers:_e,droppableRects:fe,over:vn,measureDroppableContainers:he,scrollableAncestors:Dt,scrollableAncestorRects:zt,measuringConfiguration:T,measuringScheduled:Ae,windowRect:mt}),[Fe,se,Ie,ue,it,xt,at,W,_e,fe,vn,he,Dt,zt,T,Ae,mt]),Z=N.useMemo(()=>({activatorEvent:ue,activators:ie,active:Fe,activeNodeRect:Ie,ariaDescribedById:{draggable:me},dispatch:D,draggableNodes:W,over:vn,measureDroppableContainers:he}),[ue,ie,Fe,Ie,D,me,W,vn,he]);return Xe.createElement(Ny.Provider,{value:k},Xe.createElement(Mc.Provider,{value:Z},Xe.createElement(ob.Provider,{value:ce},Xe.createElement(lb.Provider,{value:un},h)),Xe.createElement(PA,{disabled:d?.restoreFocus===!1})),Xe.createElement(U_,{...d,hiddenTextDescribedById:me}));function ve(){const pe=Et?.autoScrollEnabled===!1,Ee=typeof f=="object"?f.enabled===!1:f===!1,xe=q&&!pe&&!Ee;return typeof f=="object"?{...f,enabled:xe}:{enabled:xe}}}),FA=N.createContext(null),cb="button",VA="Draggable";function KA(s){let{id:e,data:n,disabled:r=!1,attributes:i}=s;const l=Uo(VA),{activators:c,activatorEvent:d,active:f,activeNodeRect:h,ariaDescribedById:m,draggableNodes:y,over:v}=N.useContext(Mc),{role:S=cb,roleDescription:_="draggable",tabIndex:x=0}=i??{},A=f?.id===e,D=N.useContext(A?lb:FA),[O,k]=kc(),[L,B]=kc(),q=DA(c,e),z=zo(n);Fr(()=>(y.set(e,{id:e,key:l,node:O,activatorNode:L,data:z}),()=>{const de=y.get(e);de&&de.key===l&&y.delete(e)}),[y,e]);const W=N.useMemo(()=>({role:S,tabIndex:x,"aria-disabled":r,"aria-pressed":A&&S===cb?!0:void 0,"aria-roledescription":_,"aria-describedby":m.draggable}),[r,S,x,A,_,m.draggable]);return{active:f,activatorEvent:d,activeNodeRect:h,attributes:W,isDragging:A,listeners:r?void 0:q,node:O,over:v,setNodeRef:k,setActivatorNodeRef:B,transform:D}}function WA(){return N.useContext(ob)}const YA="Droppable",XA={timeout:25};function JA(s){let{data:e,disabled:n=!1,id:r,resizeObserverConfig:i}=s;const l=Uo(YA),{active:c,dispatch:d,over:f,measureDroppableContainers:h}=N.useContext(Mc),m=N.useRef({disabled:n}),y=N.useRef(!1),v=N.useRef(null),S=N.useRef(null),{disabled:_,updateMeasurementsFor:x,timeout:A}={...XA,...i},D=zo(x??r),O=N.useCallback(()=>{if(!y.current){y.current=!0;return}S.current!=null&&clearTimeout(S.current),S.current=setTimeout(()=>{h(Array.isArray(D.current)?D.current:[D.current]),S.current=null},A)},[A]),k=Lc({callback:O,disabled:_||!c}),L=N.useCallback((W,de)=>{k&&(de&&(k.unobserve(de),y.current=!1),W&&k.observe(W))},[k]),[B,q]=kc(L),z=zo(e);return N.useEffect(()=>{!k||!B.current||(k.disconnect(),y.current=!1,k.observe(B.current))},[B,k]),N.useEffect(()=>(d({type:ln.RegisterDroppable,element:{id:r,key:l,disabled:n,node:B,rect:v,data:z}}),()=>d({type:ln.UnregisterDroppable,key:l,id:r})),[r]),N.useEffect(()=>{n!==m.current.disabled&&(d({type:ln.SetDroppableDisabled,id:r,key:l,disabled:n}),m.current.disabled=n)},[r,l,n,d]),{active:c,rect:v,isOver:f?.id===r,node:B,over:f,setNodeRef:q}}function Qf(s,e,n){const r=s.slice();return r.splice(n<0?r.length+n:n,0,r.splice(e,1)[0]),r}function QA(s,e){return s.reduce((n,r,i)=>{const l=e.get(r);return l&&(n[i]=l),n},Array(s.length))}function Nc(s){return s!==null&&s>=0}function ZA(s,e){if(s===e)return!0;if(s.length!==e.length)return!1;for(let n=0;n{var e;let{rects:n,activeNodeRect:r,activeIndex:i,overIndex:l,index:c}=s;const d=(e=n[i])!=null?e:r;if(!d)return null;const f=nx(n,c,i);if(c===i){const h=n[l];return h?{x:ii&&c<=l?{x:-d.width-f,y:0,...Ic}:c=l?{x:d.width+f,y:0,...Ic}:{x:0,y:0,...Ic}};function nx(s,e,n){const r=s[e],i=s[e-1],l=s[e+1];return!r||!i&&!l?0:n{let{rects:e,activeIndex:n,overIndex:r,index:i}=s;const l=Qf(e,r,n),c=e[i],d=l[i];return!d||!c?null:{x:d.left-c.left,y:d.top-c.top,scaleX:d.width/c.width,scaleY:d.height/c.height}},jc={scaleX:1,scaleY:1},rx=s=>{var e;let{activeIndex:n,activeNodeRect:r,index:i,rects:l,overIndex:c}=s;const d=(e=l[n])!=null?e:r;if(!d)return null;if(i===n){const h=l[c];return h?{x:0,y:nn&&i<=c?{x:0,y:-d.height-f,...jc}:i=c?{x:0,y:d.height+f,...jc}:{x:0,y:0,...jc}};function ax(s,e,n){const r=s[e],i=s[e-1],l=s[e+1];return r?nr.map(q=>typeof q=="object"&&"id"in q?q.id:q),[r]),_=c!=null,x=c?S.indexOf(c.id):-1,A=h?S.indexOf(h.id):-1,D=N.useRef(S),O=!ZA(S,D.current),k=A!==-1&&x===-1||O,L=ex(l);Fr(()=>{O&&_&&m(S)},[O,S,_,m]),N.useEffect(()=>{D.current=S},[S]);const B=N.useMemo(()=>({activeIndex:x,containerId:y,disabled:L,disableTransforms:k,items:S,overIndex:A,useDragOverlay:v,sortedRects:QA(S,f),strategy:i}),[x,y,L.draggable,L.droppable,k,S,A,f,v,i]);return Xe.createElement(db.Provider,{value:B},e)}const sx=s=>{let{id:e,items:n,activeIndex:r,overIndex:i}=s;return Qf(n,r,i).indexOf(e)},ox=s=>{let{containerId:e,isSorting:n,wasDragging:r,index:i,items:l,newIndex:c,previousItems:d,previousContainerId:f,transition:h}=s;return!h||!r||d!==l&&i===c?!1:n?!0:c!==i&&e===f},lx={duration:200,easing:"ease"},fb="transform",cx=qo.Transition.toString({property:fb,duration:0,easing:"linear"}),ux={roleDescription:"sortable"};function dx(s){let{disabled:e,index:n,node:r,rect:i}=s;const[l,c]=N.useState(null),d=N.useRef(n);return Fr(()=>{if(!e&&n!==d.current&&r.current){const f=i.current;if(f){const h=Es(r.current,{ignoreTransform:!0}),m={x:f.left-h.left,y:f.top-h.top,scaleX:f.width/h.width,scaleY:f.height/h.height};(m.x||m.y)&&c(m)}}n!==d.current&&(d.current=n)},[e,n,r,i]),N.useEffect(()=>{l&&c(null)},[l]),l}function fx(s){let{animateLayoutChanges:e=ox,attributes:n,disabled:r,data:i,getNewIndex:l=sx,id:c,strategy:d,resizeObserverConfig:f,transition:h=lx}=s;const{items:m,containerId:y,activeIndex:v,disabled:S,disableTransforms:_,sortedRects:x,overIndex:A,useDragOverlay:D,strategy:O}=N.useContext(db),k=hx(r,S),L=m.indexOf(c),B=N.useMemo(()=>({sortable:{containerId:y,index:L,items:m},...i}),[y,i,L,m]),q=N.useMemo(()=>m.slice(m.indexOf(c)),[m,c]),{rect:z,node:W,isOver:de,setNodeRef:_e}=JA({id:c,data:B,disabled:k.droppable,resizeObserverConfig:{updateMeasurementsFor:q,...f}}),{active:Se,activatorEvent:je,activeNodeRect:Fe,attributes:ze,setNodeRef:Et,listeners:Q,isDragging:ue,over:De,setActivatorNodeRef:ee,transform:me}=KA({id:c,data:B,attributes:{...ux,...n},disabled:k.draggable}),H=k_(_e,Et),T=!!Se,fe=T&&!_&&Nc(v)&&Nc(A),he=!D&&ue,Ae=he&&fe?me:null,ge=fe?Ae??(d??O)({rects:x,activeNodeRect:Fe,activeIndex:v,overIndex:A,index:L}):null,$e=Nc(v)&&Nc(A)?l({id:c,items:m,activeIndex:v,overIndex:A}):L,Te=Se?.id,Ie=N.useRef({activeId:Te,items:m,newIndex:$e,containerId:y}),xt=m!==Ie.current.items,rt=e({active:Se,containerId:y,isDragging:ue,isSorting:T,id:c,index:L,items:m,newIndex:Ie.current.newIndex,previousItems:Ie.current.items,previousContainerId:Ie.current.containerId,transition:h,wasDragging:Ie.current.activeId!=null}),gt=dx({disabled:!rt,index:L,node:W,rect:z});return N.useEffect(()=>{T&&Ie.current.newIndex!==$e&&(Ie.current.newIndex=$e),y!==Ie.current.containerId&&(Ie.current.containerId=y),m!==Ie.current.items&&(Ie.current.items=m)},[T,$e,y,m]),N.useEffect(()=>{if(Te===Ie.current.activeId)return;if(Te!=null&&Ie.current.activeId==null){Ie.current.activeId=Te;return}const Yt=setTimeout(()=>{Ie.current.activeId=Te},50);return()=>clearTimeout(Yt)},[Te]),{active:Se,activeIndex:v,attributes:ze,data:B,rect:z,index:L,newIndex:$e,items:m,isOver:de,isSorting:T,isDragging:ue,listeners:Q,node:W,overIndex:A,over:De,setNodeRef:H,setActivatorNodeRef:ee,setDroppableNodeRef:_e,setDraggableNodeRef:Et,transform:gt??ge,transition:at()};function at(){if(gt||xt&&Ie.current.newIndex===L)return cx;if(!(he&&!Pf(je)||!h)&&(T||rt))return qo.Transition.toString({...h,property:fb})}}function hx(s,e){var n,r;return typeof s=="boolean"?{draggable:s,droppable:!1}:{draggable:(n=s?.draggable)!=null?n:e.draggable,droppable:(r=s?.droppable)!=null?r:e.droppable}}function Hc(s){if(!s)return!1;const e=s.data.current;return!!(e&&"sortable"in e&&typeof e.sortable=="object"&&"containerId"in e.sortable&&"items"in e.sortable&&"index"in e.sortable)}const px=[dt.Down,dt.Right,dt.Up,dt.Left],gx=(s,e)=>{let{context:{active:n,collisionRect:r,droppableRects:i,droppableContainers:l,over:c,scrollableAncestors:d}}=e;if(px.includes(s.code)){if(s.preventDefault(),!n||!r)return;const f=[];l.getEnabled().forEach(y=>{if(!y||y!=null&&y.disabled)return;const v=i.get(y.id);if(v)switch(s.code){case dt.Down:r.topv.top&&f.push(y);break;case dt.Left:r.left>v.left&&f.push(y);break;case dt.Right:r.left1&&(m=h[1].id),m!=null){const y=l.get(n.id),v=l.get(m),S=v?i.get(v.id):null,_=v?.node.current;if(_&&S&&y&&v){const A=Dc(_).some((q,z)=>d[z]!==q),D=hb(y,v),O=mx(y,v),k=A||!D?{x:0,y:0}:{x:O?r.width-S.width:0,y:O?r.height-S.height:0},L={x:S.left,y:S.top};return k.x&&k.y?L:Bo(L,k)}}}};function hb(s,e){return!Hc(s)||!Hc(e)?!1:s.data.current.sortable.containerId===e.data.current.sortable.containerId}function mx(s,e){return!Hc(s)||!Hc(e)||!hb(s,e)?!1:s.data.current.sortable.index{const d=N.useId(),f=N.useRef({inProgress:!1,lastSortTime:0,lastActiveId:null}),h=B_(Iy(Wf,{activationConstraint:{distance:5}}),Iy(Vf,{coordinateGetter:gx})),m=N.useCallback(S=>{f.current={inProgress:!0,lastSortTime:Date.now(),lastActiveId:S.active.id},l?.({activeId:S.active.id})},[l]),y=N.useCallback(S=>{const{active:_,over:x}=S,A=Date.now();if(!x||_.id===x.id){f.current.inProgress=!1;return}const{lastSortTime:D,lastActiveId:O,inProgress:k}=f.current;if(O===_.id&&A-D<500&&!k)return;f.current={inProgress:!1,lastSortTime:A,lastActiveId:_.id};const L=s.findIndex(q=>String(q[e])===String(_.id)),B=s.findIndex(q=>String(q[e])===String(x.id));if(L!==-1&&B!==-1&&L!==B){const q=Qf(s,L,B);i?.(q,{oldIndex:L,newIndex:B})}},[s,e,i]),v=s.map(S=>String(S[e]));return yt.jsx(GA,{id:d,sensors:h,collisionDetection:G_,onDragStart:m,onDragEnd:y,children:yt.jsx(ix,{items:v,strategy:yx[n],children:r})},c!==void 0?`${d}-v${c}`:d)},pb=N.createContext(null),vx=({value:s,children:e})=>yt.jsx(pb.Provider,{value:s,children:e}),Sx=()=>N.useContext(pb),wx=({id:s,handle:e,as:n="div",children:r})=>{const{attributes:i,listeners:l,setNodeRef:c,transform:d,transition:f,isDragging:h}=fx({id:s}),m={transform:qo.Transform.toString(d),transition:f,opacity:h?.5:1,position:"relative"};return yt.jsx(n,{ref:c,style:m,...i,...e?{}:l,"data-sortable-item":!0,"data-sortable-id":String(s),"data-dragging":h,children:yt.jsx(vx,{value:{listeners:l,handle:e,isDragging:h},children:r})})},lt=pt("DynamicRenderer");function oa(){try{return window.G7Core?.devTools}catch{return}}function eh(s,e,n,r){if(typeof s=="string"){if(/\$t:[a-zA-Z_][a-zA-Z0-9_.\-]*/.test(s))try{return e.resolveTranslations(s,n,r)}catch{return s}return s}if(Array.isArray(s))return s.map(i=>eh(i,e,n,r));if(s&&typeof s=="object"){const i={};for(const[l,c]of Object.entries(s))i[l]=eh(c,e,n,r);return i}return s}function Cx(s){const e=Object.keys(s);return e.length>0&&e.every(n=>/^\d+$/.test(n))}const fr=(s,e)=>{const n=["errors"],r={...s};for(const i of Object.keys(e)){const l=e[i],c=s[i];if(l===null){r[i]=null;continue}if(n.includes(i)){r[i]=l;continue}if(Array.isArray(l)){r[i]=l;continue}if(Array.isArray(c)&&l!==null&&typeof l=="object"&&!Array.isArray(l)&&Cx(l)){const d=Object.keys(l).map(h=>parseInt(h,10));if((d.length>0?Math.max(...d):0)>=c.length+d.length+10)r[i]={...l};else{const h=[...c];for(const[m,y]of Object.entries(l)){const v=parseInt(m,10);v>=0&&v=h.length&&(h[v]=y)}r[i]=h}}else l!==null&&typeof l=="object"&&c!==null&&typeof c=="object"&&!Array.isArray(c)?r[i]=fr(c,l):r[i]=l}return r},Ex=(s,e,n,r)=>{if(!e||typeof e!="object"||Array.isArray(e))return s;const i=fr(s,e);return Ry(i,n,r)},zc=(s,e)=>{let n=null;const r=()=>(n===null&&(n={...s}),n);for(const i of Object.keys(e)){if(!(i in s))continue;const l=e[i],c=s[i];if(l!==null&&typeof l=="object"&&!Array.isArray(l)&&c!==null&&typeof c=="object"&&!Array.isArray(c)){const d=zc(c,l);if(d===c)continue;Object.keys(d).length===0?delete r()[i]:r()[i]=d}else delete r()[i]}return n??s},_x=s=>{const e={},n=s.split(";").filter(r=>r.trim());for(const r of n){const i=r.indexOf(":");if(i===-1)continue;const l=r.substring(0,i).trim(),c=r.substring(i+1).trim();if(l&&c){const d=l.replace(/-([a-z])/g,(f,h)=>h.toUpperCase());e[d]=c}}return e};class Ax extends N.Component{constructor(e){super(e),this.state={hasError:!1,error:null}}static getDerivedStateFromError(e){return{hasError:!0,error:e}}componentDidCatch(e,n){lt.error(`컴포넌트 렌더링 에러 (ID: ${this.props.componentId}, Name: ${this.props.componentName}):`,e,n)}render(){return this.state.hasError?this.props.fallback?this.props.fallback:yt.jsxs("div",{className:"p-4 my-2 bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded-lg text-red-800 dark:text-red-400",children:[yt.jsx("div",{className:"font-semibold mb-1",children:"컴포넌트 로드 실패"}),yt.jsxs("div",{className:"text-xs opacity-80",children:[this.props.componentName&&`[${this.props.componentName}] `,"데이터를 표시할 수 없습니다."]}),this.state.error&&yt.jsxs("details",{className:"mt-2",children:[yt.jsx("summary",{className:"cursor-pointer text-xs opacity-70 hover:opacity-100",children:"상세 정보"}),yt.jsx("div",{className:"mt-1 p-2 bg-red-100 dark:bg-red-900/30 rounded text-xs font-mono whitespace-pre-wrap break-all",children:this.state.error.message})]})]}):this.props.children}}const Vr=N.memo(({componentDef:s,dataContext:e,translationContext:n,registry:r,bindingEngine:i,translationEngine:l,actionDispatcher:c,parentComponentContext:d,parentFormContextProp:f,parentDataContext:h,isRootRenderer:m=!1,isInsideIteration:y=!1,isEditMode:v=!1,onComponentSelect:S,onComponentHover:_,onDragStart:x,onDragEnd:A,componentPath:D,layoutKey:O})=>{const[k,L]=N.useState({loadingActions:{}});N.useLayoutEffect(()=>{d||(i.startRenderCycle(),window.__g7SetLocalOverrideKeys||(window.__g7ForcedLocalFields=void 0),window.__g7PendingLocalState=null)});const B=N.useRef(null);N.useLayoutEffect(()=>{if(!d&&e._localInit&&typeof e._localInit=="object"){const{_forceLocalInit:V,_merge:ie,...ce}=e._localInit,Z=`${JSON.stringify(ce)}:${V||"no-force"}`;if(B.current!==Z){B.current=Z;const ve=ie||"shallow",Ee=window.G7Core?.state?.get?.()?._local||{},xe=window.__g7PendingLocalState||{},Ke={...Ee,...xe};ve==="replace"?window.__g7PendingLocalState={loadingActions:Ke.loadingActions||{},...ce,hasChanges:!1}:ve==="deep"?window.__g7PendingLocalState=fr(Ke,{...ce,hasChanges:!1}):window.__g7PendingLocalState={...Ke,...ce,hasChanges:!1};const ke=window.__g7SetLocalOverrideKeys;ke&&typeof ke=="object"&&(ve==="replace"?window.__g7SetLocalOverrideKeys={...ce,hasChanges:!1}:window.__g7SetLocalOverrideKeys=fr(ke,{...ce,hasChanges:!1}))}}}),N.useLayoutEffect(()=>{if(!d){const V=window.__g7SetLocalOverrideKeys;if(V){L(ve=>zc(ve,V));const ie=V,ce=window.__g7ForcedLocalFields,Z=window.__g7LastSetLocalSnapshot;queueMicrotask(()=>{window.__g7SetLocalOverrideKeys===ie&&(window.__g7SetLocalOverrideKeys=void 0),window.__g7ForcedLocalFields===ce&&(window.__g7ForcedLocalFields=void 0),window.__g7LastSetLocalSnapshot===Z&&(window.__g7LastSetLocalSnapshot=void 0)})}}},[e._local]);const q=N.useRef(O);N.useEffect(()=>{s._fromBase&&O&&q.current!==O&&(q.current=O,L({loadingActions:{}}))},[O,s._fromBase]);const z=N.useRef(""),W=N.useRef(!1),de=N.useRef(null),_e=N.useRef(null),Se=N.useRef({}),je=N.useRef({}),Fe=N.useRef(h),ze=T_(),Et=a_(),Q=Et?.getParentDataContext()??h;N.useEffect(()=>{if(e._localInit&&typeof e._localInit=="object"){const{_forceLocalInit:V,...ie}=e._localInit,ce=JSON.stringify(ie),Z=V!==void 0,ve=Lf(),pe=`${ce}:${V||"no-force"}`,Ee=f_({globalTrackedKey:ve.hash,instanceHandledKey:_e.current,trackingKey:pe});if(Ee==="apply"){_e.current=pe,ve.hash=pe,ve.timestamp=V,z.current=ce,Z&&(W.current=!0);const{_merge:Ke,...ke}=ie,Me=Ke||"shallow";L(Re=>Me==="replace"?{loadingActions:Re.loadingActions||{},...ke,hasChanges:!1}:Me==="deep"?fr(Re,{...ke,hasChanges:!1}):{...Re,...ke,hasChanges:!1}),e._globalSetState&&e._globalSetState(Re=>{const tt=Re?._local||{};let Pe;return Me==="replace"?Pe={...ke,hasChanges:!1}:Me==="deep"?Pe=fr(tt,{...ke,hasChanges:!1}):Pe={...tt,...ke,hasChanges:!1},{...Re,_local:Pe}}),i.invalidateCacheByKeys(["_local"]),lt.log("_localInit applied (data changed):",Object.keys(ie))}else if(Ee==="prune"){_e.current=pe;const{_merge:Ke,...ke}=ie;L(Re=>zc(Re,ke)),zc(k,ke)!==k&&(i.invalidateCacheByKeys(["_local"]),lt.log("_localInit pruned (applied by another renderer):",Object.keys(ke)))}de.current=e._localInit,u_(e._localInit)}},[e._localInit,h,i]);const{isTransitioning:ue}=Ay(),De=v_(),ee=f??De,{width:me}=Mf(),H=Sx(),T=N.useMemo(()=>{let V=s;if(s.type==="iterator"&&s.data){const{data:Z,itemName:ve,indexName:pe,...Ee}=s;V={...Ee,iteration:{source:Z,item_var:ve||"item",index_var:pe}}}if(!V.responsive)return V;const ie=Ci.getMatchingKey(V.responsive,me);if(!ie)return V;const ce=V.responsive[ie];return{...V,props:{...V.props,...ce.props},children:ce.children??V.children,text:ce.text??V.text,if:ce.if??V.if,iteration:ce.iteration??V.iteration,__responsiveChildrenKey:ce.children!==void 0?ie:void 0}},[s,me]),fe=N.useCallback(V=>{const ie=V?.__setStateId;if(L(ce=>{const Z=window.__g7PendingLocalState,pe=window.__g7SetLocalOverrideKeys||Z,Ee=pe?fr(ce,pe):ce;if(typeof V=="function"){const Re=V(Ee);return fr(Ee,Re)}const{__mergeMode:xe,__setStateId:Ke,...ke}=V;let Me;if(xe==="replace"?Me=ke:xe==="shallow"?Me={...Ee,...ke}:Me=fr(Ee,ke),xe!==void 0){const Re=window.__g7ForcedLocalFields;Re&&(window.__g7ForcedLocalFields={...Re,...ke})}return Me}),ie){const ce=oa();if(ce?.isEnabled()){const Z=T.id||"unknown",ve=T.name||"Unknown";setTimeout(()=>{ce.trackComponentRender?.(Z,ve,0,["_local"],[]),ce.completeStateChange?.(ie)},0)}}},[T.id,T.name]),he=d?.state??k,Ae=d?.setState??fe,se=N.useMemo(()=>{const V=e._localInit&&e._localInit!==de.current;let ie;V&&e._local?ie=e._local:ie=e._local?fr(e._local,he):he;const ce=window.__g7ForcedLocalFields;ce&&(ie=fr(ie,ce));const Z={...e,_local:ie},ve=e._computedDefinitions;if(ve&&Object.keys(ve).length>0){const pe=new $n,Ee={...Z,...ze?{_isolated:ze.state}:{}},xe={},Ke=oa(),ke=Ke?.isEnabled()??!1;for(const[Me,Re]of Object.entries(ve)){const tt=ke?Date.now():0,Pe=ke?e._computed?.[Me]:void 0;let Ve;try{if(typeof Re=="string")if(Re.startsWith("{{")&&Re.endsWith("}}")){const Ce=Re.slice(2,-2).trim(),He=jn(Ce)?pe.evaluatePipeExpression(Ce,Ee,{skipCache:!0}):pe.evaluateExpression(Ce,Ee,{skipCache:!0});xe[Me]=He}else xe[Me]=Re;else if(Re&&typeof Re=="object"&&"$switch"in Re){const Ce=pe.resolveSwitch(Re,Ee,{skipCache:!0});xe[Me]=Ce}}catch(Ce){lt.warn(`Failed to recalculate computed value: ${Me}`,Ce),xe[Me]=e._computed?.[Me],Ve=Ce instanceof Error?Ce.message:String(Ce)}if(ke&&(Ve||Pe!==xe[Me])){const He=Date.now()-tt;Ke.trackComputedProperty?.(Me,typeof Re=="string"?Re:JSON.stringify(Re),[{type:"_local",path:"_local",value:Z._local}],xe[Me],He,T.id,Ve),Pe!==xe[Me]&&Ke.trackComputedRecalc?.(Me,"dependency-change",Pe,xe[Me],He,{type:"_local",path:"_local"},T.id)}}Z._computed=xe}if(ze&&(Z._isolated=ze.state),Q&&(Z.$parent=Q),s.extensionPointProps){const pe=i.resolveObject(s.extensionPointProps,Z,y?{skipCache:!0}:void 0);Z.extensionPointProps=v&&l?eh(pe,l,n,Z):pe}return s.extensionPointCallbacks&&(Z.extensionPointCallbacks=s.extensionPointCallbacks),v&&(Z.$templateId===void 0&&n?.templateId&&(Z.$templateId=n.templateId),Z.$locale===void 0&&n?.locale&&(Z.$locale=n.locale)),Z},[e,he,ze?.state,Q,Et?.version,s.extensionPointProps,s.extensionPointCallbacks]);Se.current=se._local,je.current=se._computed||{},Fe.current=Q,N.useEffect(()=>{const V=oa();if(!V?.isEnabled())return;const ie=se._local;ie&&V.updateLocalState(ie);const ce=se._computed;ce&&V.updateComputedState(ce),Q&&V.updateParentContext?.(Q);const Z=T.id||`comp-${Date.now()}`,ve=T.name||"Unknown",pe={global:Object.keys(e._global||{}),local:Object.keys(ie||{}),context:d?Object.keys(d.state||{}):[]},Ee={type:d?"componentContext":"dynamicState",hasComponentContext:!!d,parentId:d?"parent":void 0};V.trackComponentStateSource?.(Z,ve,pe,Ee),he&&Object.keys(he).length>0&&V.trackDynamicState?.(Z,he);const xe=!!(T.expandChildren||T.props?.expandChildren);V.trackContextFlow?.(Z,ve,!!d,xe,!0)},[se._local,e,T.id,T.name,d,he,T.expandChildren,T.props?.expandChildren,Q]);const ge=N.useMemo(()=>({state:{...se._local},setState:Ae,stateRef:Se,computedRef:je,isolatedContext:ze,parentDataContext:se}),[se._local,Ae,k,ze,se]),$e=N.useMemo(()=>{const V=hs({if:T.if,condition:T.condition,conditions:T.conditions},se,i,T.id);if(T.if||T.condition!==void 0||T.conditions){const ce=oa();ce?.isEnabled()&&T.if&&ce.trackIfCondition(T.id||`if-${Date.now()}`,T.if,V)}return V},[T.if,T.conditions,se,i,T.id]),Te=N.useMemo(()=>{if(!T.slot)return null;const V=T.slot;if(V.startsWith("{{")&&V.endsWith("}}")){const ie=V.slice(2,-2).trim();try{const ce={componentId:T.id,componentName:T.name,propName:"slot"},Z=jn(ie)?i.evaluatePipeExpression(ie,se,void 0,ce):i.evaluateExpression(ie,se,ce);return lt.log(`[Slot] 표현식 평가: "${ie}" => "${Z}" (컴포넌트: ${T.id})`),typeof Z=="string"?Z:null}catch(ce){return lt.warn(`slot 표현식 평가 실패 (컴포넌트: ${T.id}):`,ce),null}}return lt.log(`[Slot] 정적 슬롯 ID: "${V}" (컴포넌트: ${T.id})`),V},[T.slot,T.id,se,i]),Ie=N.useMemo(()=>!Te||!T.id?"":`${T.id}-${Te}-${JSON.stringify(T.slotOrder??0)}`,[Te,T.id,T.slotOrder]),xt=N.useRef(null);N.useEffect(()=>{const V=window.__slotContextValue,ie=V?.isEnabled??!1;if(T.slot&<.log(`[Slot Registration] 컴포넌트: ${T.id}, isSlotEnabled: ${ie}, shouldRender: ${$e}, resolvedSlotId: ${Te}`),!ie||!$e){T.slot&<.log(`[Slot Registration] 등록 스킵 - isSlotEnabled: ${ie}, shouldRender: ${$e}`),xt.current&&T.id&&V&&(V.unregisterFromSlot(xt.current,T.id),xt.current=null);return}if(!Te||!T.id){T.slot&<.log(`[Slot Registration] 등록 스킵 - resolvedSlotId: ${Te}, id: ${T.id}`),xt.current&&T.id&&V&&(V.unregisterFromSlot(xt.current,T.id),xt.current=null);return}xt.current&&xt.current!==Te&&V.unregisterFromSlot(xt.current,T.id);const ce={...T,slot:void 0,slotOrder:void 0};return V.registerToSlot(Te,T.id,{componentDef:ce,dataContext:e,order:T.slotOrder??0,parentFormContext:f??null,getParentComponentContext:()=>ge,translationContext:n,registrationKey:Ie}),xt.current=Te,()=>{const Z=window.__slotContextValue;Te&&T.id&&Z&&Z.unregisterFromSlot(Te,T.id)}},[$e,Te,T,e,f,ge,n,Ie]);const rt=N.useCallback(V=>{if(typeof V=="string"){if(ac(V))return Kd(V);if(ic(V)){const ie=[],ce=V.replace(new RegExp(`${bo}([^${Si}]*)${Si}`,"g"),(ve,pe)=>(ie.push(pe),`${La}${ie.length-1}${La}`));let Z=ce;return/\$t:[a-zA-Z0-9._-]+/.test(ce)&&(Z=l.resolveTranslations(ce,n,se)),Z.replace(new RegExp(`${La}(\\d+)${La}`,"g"),(ve,pe)=>ie[parseInt(pe)])}if(V.startsWith("$t:defer:"))return V;if(/\$t:[a-zA-Z0-9._-]+/.test(V)){const ie=V.trim();return ie.startsWith("{")&&ie.endsWith("}")||ie.startsWith("[")&&ie.endsWith("]")?V:l.resolveTranslations(V,n,se)}return V}if(Array.isArray(V))return V.map(ie=>rt(ie));if(V&&typeof V=="object"){const ie={};for(const[ce,Z]of Object.entries(V))ie[ce]=rt(Z);return ie}return V},[l,n,se]),gt=N.useCallback(V=>yo(V),[]),at=N.useCallback(V=>Oa(V),[]),Yt=N.useMemo(()=>{let V={...T.props||{}};const ie=["cellChildren","expandChildren","expandContext","render"],Z=r.getMetadata(T.name)?.skipBindingKeys||[],ve=[...new Set([...ie,...Z])],pe=y?{skipCache:!0}:void 0,Ee={...pe??{},skipBindingKeys:ve},xe=Ce=>{if(pe)return pe;if(Ce.startsWith("_computed")||Ce.startsWith("$computed"))return{skipCache:!0}};for(const[Ce,He]of Object.entries(V))if(!ve.includes(Ce))if(typeof He=="string"){const nt=at(He);if(nt!==null){let Lt=!1,Bt=nt;nt.startsWith(lr)&&(Lt=!0,Bt=nt.slice(lr.length));const Nn={componentId:T.id,componentName:T.name,propName:Ce};let Gt;if(jn(Bt))try{Gt=i.evaluatePipeExpression(Bt,se,xe(Bt),Nn)}catch(Ft){lt.warn(`파이프 표현식 평가 실패 (컴포넌트: ${T.id}, prop: ${Ce}):`,Ft),Gt=void 0}else if(gt(Bt))try{Gt=i.evaluateExpression(Bt,se,Nn)}catch(Ft){lt.warn(`표현식 평가 실패 (컴포넌트: ${T.id}, prop: ${Ce}):`,Ft),Gt=void 0}else Gt=i.resolve(Bt,se,xe(Bt),Nn);V[Ce]=Lt&&Gt!=null?fs(Gt):Gt}else{const Lt={componentId:T.id,componentName:T.name,propName:Ce};V[Ce]=i.resolveBindings(He,se,pe,Lt)}}else Array.isArray(He)?V[Ce]=He.map(nt=>typeof nt=="string"?i.resolveBindings(nt,se,pe):typeof nt=="object"&&nt!==null?i.resolveObject(nt,se,Ee):nt):He&&typeof He=="object"&&(V[Ce]=i.resolveObject(He,se,Ee));for(const[Ce,He]of Object.entries(V))V[Ce]=rt(He);if(typeof V.style=="string"&&(V.style=_x(V.style)),V=sf(V,v?void 0:T.actions,se,{componentContext:ge,actionDispatcher:c}),T.classMap){const Ce=K0(T.classMap,se,i,{skipCache:!0});if(Ce){const He=V.className||"";V.className=He?`${He} ${Ce}`:Ce}}T.style&&(V.style=T.style),T.actions&&T.actions.length>0&&T.type==="composite"&&Object.assign(V,he),T.component_layout&&(V.__componentLayoutDefs=T.component_layout);const Ke=T.expandChildren||T.props?.expandChildren,ke=T.props?.cellChildren,Me=T.children&&T.children.length>0,Re=T.component_layout,tt=T.slot,Pe=T.props?.modalId,Ve=T.type==="composite";return(Ke||ke||Me||Re||tt||Pe||Ve)&&(V.__componentContext=ge),V},[T.props,T.actions,T.style,T.classMap,T.id,se,n,i,l,ge,he,rt,gt,at,y]),Ht=N.useRef(null),bt=N.useMemo(()=>Ht.current&&Ty(Ht.current,Yt)?Ht.current:(Ht.current=Yt,Yt),[Yt]),qe=N.useMemo(()=>{const V=T.id;if(typeof V!="string"||!V.includes("{{"))return V;const ie=i.resolveBindings(V,se,{skipCache:!0});return typeof ie=="string"?ie:V},[T.id,se,i]),mt=N.useMemo(()=>{const V=T.dataKey,ie=T.trackChanges,ce=T.debounce;if(V){const Z=V.startsWith("_global."),ve=Z?V.slice(8):V,pe=Z?e._global:se._local;return{dataKey:ve,trackChanges:ie??!1,debounce:ce,setState:Z?xe=>{e._globalSetState?e._globalSetState(xe):lt.warn("_global.formData 사용 시 _globalSetState가 필요합니다.")}:Ae,state:pe??{},_isGlobal:Z}}if(ee.dataKey)return ee},[T.dataKey,T.trackChanges,T.debounce,T.id,Ae,se._local,e._global,e._globalSetState,ee]),Dt=N.useRef(new Set);N.useEffect(()=>{Dt.current.clear()},[e._global?._remountKeys]);const zt=N.useCallback((V,ie)=>{const ce=e._global?._remountKeys;return V&&ce?.[V]&&!Dt.current.has(V)?(Dt.current.add(V),`${V}-remount-${ce[V]}`):V||ie},[e._global?._remountKeys]),kn=N.useMemo(()=>{if(T.text!==void 0){if(typeof T.text=="string"){const V=T.text;let ie=V;const ce={componentId:T.id,componentName:T.name,propName:"text",skipCache:!0},Z=at(V);if(Z!==null){let ve=!1,pe=Z;if(Z.startsWith(lr)&&(ve=!0,pe=Z.slice(lr.length)),jn(pe))try{ie=i.evaluatePipeExpression(pe,se,{skipCache:!0},ce)}catch(Ee){lt.warn(`text 파이프 표현식 평가 실패 (컴포넌트: ${T.id}):`,Ee),ie=""}else if(gt(pe))try{ie=i.evaluateExpression(pe,se,ce)}catch(Ee){lt.warn(`text 표현식 평가 실패 (컴포넌트: ${T.id}):`,Ee),ie=""}else ie=i.resolve(pe,se,{skipCache:!0},ce);ve&&ie!=null&&(ie=fs(ie))}else V.includes("{{")&&(ie=i.resolveBindings(V,se,{skipCache:!0},ce));return ie=rt(ie),ie??""}return T.text}return!T.children||T.children.length===0?null:T.children.map((V,ie)=>{if(typeof V=="string")return V;const ce=T.__responsiveChildrenKey,Z=ce?`responsive.${ce}.children`:"children",ve=D?`${D}.${Z}.${ie}`:`${ie}`,pe=V._fromBase?V.id||`child-${ie}`:zt(V.id,`child-${ie}`),Ee=!V._fromBase&&O?`${pe}_${O}`:pe;return yt.jsx(Vr,{componentDef:V,dataContext:se,translationContext:n,registry:r,bindingEngine:i,translationEngine:l,actionDispatcher:c,parentComponentContext:ge,parentFormContextProp:mt,isInsideIteration:y,isEditMode:v,onComponentSelect:S,onComponentHover:_,componentPath:ve,onDragStart:x,onDragEnd:A,layoutKey:O},Ee)})},[T.text,T.children,T.id,e,se,n,r,i,l,c,ge,mt,at,gt,rt,zt,y,v,S,_,D,x,A,O]),hr=N.useMemo(()=>{if(!T.iteration)return null;try{const V=af(T.iteration.source,se,i);if(!Array.isArray(V))return lt.warn(`iteration source가 배열이 아닙니다 (컴포넌트: ${T.id}):`,V),null;const ie=oa();return ie?.isEnabled()&&ie.trackIteration(T.id||`iteration-${Date.now()}`,T.iteration.source,T.iteration.item_var,T.iteration.index_var,V.length),V.map((ce,Z)=>{const ve={...se,[T.iteration.item_var]:ce,[`${T.iteration.item_var}_index`]:Z};T.iteration.index_var&&(ve[T.iteration.index_var]=Z);const pe=Z===0||Z===V.length-1;ie?.isEnabled()&&pe&&ie.trackNestedContext?.({componentId:`${T.id||"unknown"}-iter-${Z}`,componentType:"iteration",parentContext:{available:Object.keys(se),values:{}},ownContext:{added:[T.iteration.item_var,...T.iteration.index_var?[T.iteration.index_var]:[]],values:{[T.iteration.item_var]:ce,...T.iteration.index_var?{[T.iteration.index_var]:Z}:{}}},depth:1});const Ee={...T,iteration:void 0,responsive:void 0},xe=D?`${D}.iteration.${Z}`:`iteration.${Z}`,Ke=`${zt(T.id,"item")}-${Z}`;return yt.jsx(Vr,{componentDef:Ee,dataContext:ve,translationContext:n,registry:r,bindingEngine:i,translationEngine:l,actionDispatcher:c,parentComponentContext:ge,parentFormContextProp:mt,isInsideIteration:!0,isEditMode:v,onComponentSelect:S,onComponentHover:_,componentPath:xe,onDragStart:x,onDragEnd:A,layoutKey:O},Ke)})}catch(V){return lt.error(`iteration 렌더링 실패 (컴포넌트: ${T.id}):`,V),null}},[T.iteration,T.children,T.id,e,se,n,r,i,l,c,ge,mt,zt,v,S,_,D,x,A,O]),[Ot,Mi]=N.useState(0),ua=N.useMemo(()=>{if(!T.sortable||!T.itemTemplate)return null;const{sortable:V,itemTemplate:ie}=T;try{const ce=af(V.source,se,i);if(!Array.isArray(ce))return lt.warn(`sortable source가 배열이 아닙니다 (컴포넌트: ${T.id}):`,ce),null;if(ce.length===0)return null;const Z=V.itemKey||"id",ve=V.strategy||"verticalList",pe=V.itemVar||"$item",Ee=V.indexVar||"$index",xe=V.handle,Ke=V.wrapperElement,ke=(tt,Pe)=>{const Ve=T.actions?.filter(Ce=>Ce.event==="onSortEnd"||Ce.type==="onSortEnd");if(Mi(Ce=>Ce+1),Ve&&Ve.length>0&&c){const Ce={...se,$sortedItems:tt,$sortEvent:Pe};Ve.forEach(He=>{try{const nt=c.createHandler(He,Ce,ge),Lt=new Event("sortend");nt(Lt)}catch(nt){lt.error(`onSortEnd action failed: ${He.handler}`,nt)}})}},Me=tt=>{const Pe=T.actions?.filter(Ve=>Ve.event==="onSortStart"||Ve.type==="onSortStart");if(Pe&&Pe.length>0&&c){const Ve={...se,$activeId:tt.activeId};Pe.forEach(Ce=>{try{const He=c.createHandler(Ce,Ve,ge),nt=new Event("sortstart");He(nt)}catch(He){lt.error(`onSortStart action failed: ${Ce.handler}`,He)}})}},Re=ce.map((tt,Pe)=>{const Ve=String(tt[Z]),Ce={...se,[pe]:tt,[Ee]:Pe},He=D?`${D}.sortable.${Pe}`:`sortable.${Pe}`,nt=`${zt(T.id,"sortable-item")}-v${Ot}-${Ve}`;return yt.jsx(wx,{id:Ve,handle:xe,...Ke?{as:Ke}:{},children:(Ke&&ie.children?ie.children:[ie]).map((Lt,Bt)=>yt.jsx(Vr,{componentDef:Lt,dataContext:Ce,translationContext:n,registry:r,bindingEngine:i,translationEngine:l,actionDispatcher:c,parentComponentContext:ge,parentFormContextProp:void 0,isInsideIteration:!0,isEditMode:v,onComponentSelect:S,onComponentHover:_,componentPath:He,onDragStart:x,onDragEnd:A,layoutKey:O},`${nt}-child-${Bt}`))},nt)});return yt.jsx(bx,{items:ce,itemKey:Z,strategy:ve,onSortEnd:ke,onSortStart:Me,sortVersion:Ot,children:Re})}catch(ce){return lt.error(`sortable 렌더링 실패 (컴포넌트: ${T.id}):`,ce),null}},[T.sortable,T.itemTemplate,T.actions,T.id,e,se,n,r,i,l,c,ge,zt,v,S,_,D,x,A,Ot,O]),bn=N.useRef(!1);N.useEffect(()=>{if(bn.current)return;bn.current=!0;const V=oa();if(V?.isEnabled()&&V.trackMount(s.id,{name:s.name,type:s.type||"component",props:bt}),s.lifecycle?.onMount&&s.lifecycle.onMount.length>0)for(const ce of s.lifecycle.onMount)try{const Z=c.createHandler(ce,se,ge),ve=new Event("mount");Z(ve),lt.log(`Lifecycle onMount executed: ${ce.handler} (Component: ${s.id})`)}catch(Z){lt.error(`Lifecycle onMount failed: ${ce.handler} (Component: ${s.id})`,Z)}const ie=[];if(s.onComponentEvent&&s.onComponentEvent.length>0){const ce=window.G7Core;if(ce?.componentEvent?.on)for(const Z of s.onComponentEvent){if(!Z.event){lt.warn(`onComponentEvent: event name is required (Component: ${s.id})`);continue}const ve=async Ee=>{try{const xe={...se,_eventData:Ee},ke=await c.createHandler({handler:Z.handler,params:Z.params,onSuccess:Z.onSuccess,onError:Z.onError},xe,ge)(new CustomEvent(Z.event,{detail:Ee}));return lt.log(`onComponentEvent "${Z.event}" handled: ${Z.handler} (Component: ${s.id})`,{eventData:Ee,result:ke}),ke}catch(xe){throw lt.error(`onComponentEvent "${Z.event}" failed: ${Z.handler} (Component: ${s.id})`,xe),xe}},pe=ce.componentEvent.on(Z.event,ve);ie.push(pe),lt.log(`onComponentEvent: Subscribed to "${Z.event}" (Component: ${s.id})`)}else lt.warn(`onComponentEvent: G7Core.componentEvent is not available (Component: ${s.id})`)}return()=>{const ce=oa();ce?.isEnabled()&&ce.trackUnmount(s.id);for(const Z of ie)try{Z()}catch(ve){lt.error(`onComponentEvent: Failed to unsubscribe (Component: ${s.id})`,ve)}if(ie.length>0&<.log(`onComponentEvent: Unsubscribed ${ie.length} event(s) (Component: ${s.id})`),s.lifecycle?.onUnmount&&s.lifecycle.onUnmount.length>0)for(const Z of s.lifecycle.onUnmount)try{const ve=c.createHandler(Z,se,ge),pe=new Event("unmount");ve(pe),lt.log(`Lifecycle onUnmount executed: ${Z.handler} (Component: ${s.id})`)}catch(ve){lt.error(`Lifecycle onUnmount failed: ${Z.handler} (Component: ${s.id})`,ve)}}},[]);const Mn=N.useMemo(()=>{const V=bt.dataKey,ie=bt.trackChanges,ce=bt.debounce;return V?{dataKey:V,trackChanges:ie??!1,debounce:ce,setState:Ae,state:he}:null},[bt.dataKey,bt.trackChanges,bt.debounce,Ae,he]);N.useEffect(()=>{const V=T.dataKey||bt.dataKey;if(!V)return;const ie=oa();if(!ie?.isEnabled())return;const ce=[],Z=pe=>{if(pe)for(const Ee of pe){const xe=Ee?.props?.name;if(xe&&ce.push({name:xe,type:Ee.name||Ee.type||"unknown"}),Ee?.children&&Z(Ee.children),Ee?.slots)for(const Ke of Object.values(Ee.slots))Array.isArray(Ke)&&Z(Ke)}};if(s.children&&Z(s.children),s.slots)for(const pe of Object.values(s.slots))Array.isArray(pe)&&Z(pe);const ve=s.id||`form-${V}`;return ie.trackForm(ve,V,ce),()=>{ie.untrackForm(ve)}},[T,bt.dataKey,s.id,s.children]);const it=N.useRef(null),Un=N.useRef(void 0);N.useEffect(()=>{const V=bt?.name;if(!V||!ee.dataKey||!ee.setState||bt?.autoBinding===!1)return;const ie=`${ee.dataKey}.${V}`,ce=window.__g7AutoBindingPaths??new Map;return window.__g7AutoBindingPaths=ce,ce.set(ie,(ce.get(ie)??0)+1),()=>{const Z=ce.get(ie)??0;Z<=1?ce.delete(ie):ce.set(ie,Z-1)}},[bt?.name,bt?.autoBinding,ee.dataKey,ee.setState]);const vn=N.useMemo(()=>{const{dataKey:V,trackChanges:ie,debounce:ce,...Z}=bt,ve=Z.name;if(!ve||!ee.dataKey||!ee.setState)return Z;if(Z.autoBinding===!1){const{autoBinding:Ve,...Ce}=Z;return Ce}const pe=`${ee.dataKey}.${ve}`,Ee=S_(ee.state,pe),xe=Ee!==void 0?Ee:Z.value,ke=r.getMetadata(s.name)?.bindingType,Me=typeof xe=="boolean"&&(ke==="checked"||ke==="checkable"&&["checkbox","radio"].includes(Z?.type)),Re=ee.debounce,tt=Ve=>{const Ce=ee.state||{},He=Ry(Ce,pe,Ve);if(ee.trackChanges&&(He.hasChanges=!0),window.__g7PendingLocalState=Ex(He,window.__g7ForcedLocalFields,pe,Ve),ee.setState(He),ee._isGlobal)return;const nt=window.G7Core,Lt={[pe]:Ve};ee.trackChanges&&(Lt.hasChanges=!0),nt?.state?.setLocal?.(Lt,{render:!1})},Pe=(Ve,Ce)=>{Re&&Re>0?(it.current&&clearTimeout(it.current),it.current=setTimeout(()=>{tt(Ve),it.current=null,Ce&&(Ce(),window.__g7PendingLocalState=null)},Re)):(tt(Ve),Ce&&(Ce(),window.__g7PendingLocalState=null))};if(Me)return{...Z,checked:xe,onChange:Ce=>{const He=Ce.target.checked;tt(He),Z.onChange&&Z.onChange(Ce)}};{const Ve=He=>{const nt=He?.target?.value!==void 0?He.target.value:He;Re&&Re>0&&(Un.current=nt);const Lt=He?.target?{target:{value:nt,name:He.target.name,type:He.target.type,checked:He.target.checked},currentTarget:He.currentTarget?{value:nt,name:He.currentTarget.name}:void 0}:He;Pe(nt,()=>{Z.onChange&&Z.onChange(Lt)})};let Ce;return Un.current!==void 0?it.current!==null?Ce=Un.current:String(xe??"")===String(Un.current)?(Un.current=void 0,Ce=xe??""):Ce=Un.current:Ce=xe??"",{...Z,value:Ce,onChange:Ve}}},[bt,ee,ee.state,ee.debounce]),da=N.useMemo(()=>{const V=oa();if(V?.isEnabled()&&V.trackRender(s.name),!$e)return T.iteration?hr:null;if(T.sortable&&T.itemTemplate)return ua;if(T.iteration)return hr;if(T.type==="extension_point"){const{layout:ke,columns:Me,gap:Re,className:tt,...Pe}=T.props||{};let Ve=tt||"";return ke==="grid"&&Me?Ve=`${Ve} grid grid-cols-${Me} gap-${Re||4}`.trim():ke==="flex"&&(Ve=`${Ve} flex flex-col gap-${Re||4}`.trim()),yt.jsx("div",{id:qe,className:Ve||void 0,...Pe,children:kn})}const ie=r.getComponent(T.name);if(!ie)return lt.error(`컴포넌트를 찾을 수 없습니다: ${T.name} (ID: ${T.id})`,"componentDef:",JSON.stringify(T,null,2)),null;const ce=e._global?._remountKeys,Z={...vn,id:vn.id??qe,...ce&&Object.keys(ce).length>0&&{__remountTrigger:JSON.stringify(ce)}};if(H?.listeners&&H.handle&&vn["data-drag-handle"]!==void 0&&(Object.assign(Z,H.listeners),Z.style={...Z.style,cursor:H.isDragging?"grabbing":"grab"}),v){const ke=T.id||`auto_${T.name||T.type||"unknown"}_${Math.random().toString(36).substring(2,8)}`,Me=Pe=>{typeof Pe.preventDefault=="function"&&Pe.preventDefault(),S&&(Pe.stopPropagation(),S(ke,Pe))},Re={"data-editor-id":ke,"data-editor-name":T.name,"data-editor-type":T.type,onClick:Me};D&&(Re["data-editor-path"]=D);const tt=T.__editorSlotName??T.slot;typeof tt=="string"&&(Re["data-editor-slot"]=tt),_&&(Re.onMouseMove=Pe=>{Pe.stopPropagation(),_(ke,Pe)},Re.onMouseLeave=Pe=>{_(null,Pe)}),Object.assign(Z,Re),Z.editorAttrs=Re}const ve=T.type==="basic",pe=T.type==="layout",xe=ve||pe?(()=>{const ke={};for(const[Me,Re]of Object.entries(Z))Me.startsWith("__")||Me==="editorAttrs"&&ve||(ke[Me]=Re);return ke})():Z;let Ke=yt.jsx(ie,{...xe,children:kn});if(mt&&(Ke=yt.jsx(b_,{value:mt,children:Ke})),T.isolatedState){const ke=typeof T.isolatedState=="object"?T.isolatedState:{},Me=e?._isolatedInit,Re=Me?{...ke,...Me}:ke;Ke=yt.jsx(x_,{initialState:Re,scopeId:T.isolatedScopeId,children:Ke})}return Ke},[$e,T.iteration,T.sortable,T.itemTemplate,T.type,T.name,T.id,T.props,T.isolatedState,T.isolatedScopeId,r,vn,kn,hr,ua,H,Mn,v,S,_,x,A,D,e._global?._remountKeys]),pr=N.useMemo(()=>{const V=T.blur_until_loaded;if(!V||e?._global?.__isPreview)return!1;if(typeof V=="string"){const Z=at(V);if(Z!==null)try{const ve={componentId:T.id,componentName:T.name,propName:"blur_until_loaded"};return!!i.evaluateExpression(Z,se,ve)}catch(ve){return lt.warn(`blur_until_loaded 표현식 평가 실패 (컴포넌트: ${T.id}):`,ve),!1}return!1}if(typeof V=="object"&&V!==null){if(!V.enabled)return!1;if(V.data_sources)return(Array.isArray(V.data_sources)?V.data_sources:[V.data_sources]).some(Ee=>e[Ee]===void 0);if(ue)return!0;const Z=["route","query","_global","_local","_dataSourceErrors"];return Object.keys(e).filter(pe=>!Z.includes(pe)&&!pe.startsWith("_")).some(pe=>e[pe]===void 0)}if(ue)return!0;const ie=["route","query","_global","_local","_dataSourceErrors"];return Object.keys(e).filter(Z=>!ie.includes(Z)&&!Z.startsWith("_")).some(Z=>e[Z]===void 0)},[T.blur_until_loaded,T.id,ue,e,se,i,at]);if(pr){const V=T.blur_until_loaded;if(typeof V=="object"&&V?.data_sources){const ce=(Array.isArray(V.data_sources)?V.data_sources:[V.data_sources]).reduce((ve,pe)=>(ve[pe]=e[pe]===void 0?"UNDEFINED":"LOADED",ve),{}),Z=Object.keys(e).filter(ve=>!ve.startsWith("_"));lt.log(`[BLUR APPLIED] Component: ${T.id}, data_sources status:`,ce,"Available keys:",Z)}else lt.log(`[BLUR APPLIED] Component: ${T.id}, blur_until_loaded:`,V)}const un=N.useMemo(()=>{if(!T.blur_until_loaded)return{wrapperClasses:"",innerClasses:""};const V=Yt?.className||"";if(!V)return{wrapperClasses:"",innerClasses:""};const ie=[/^(sm:|md:|lg:|xl:|2xl:)?(col-span-|row-span-|col-start-|col-end-|row-start-|row-end-)/,/^(sm:|md:|lg:|xl:|2xl:)?(self-|place-self-|order-)/,/^(sm:|md:|lg:|xl:|2xl:)?(flex-shrink|flex-grow|flex-\d|basis-)/],ce=V.split(/\s+/).filter(Boolean),Z=[],ve=[];for(const pe of ce)ie.some(xe=>xe.test(pe))?Z.push(pe):ve.push(pe);return{wrapperClasses:Z.join(" "),innerClasses:ve.join(" ")}},[T.blur_until_loaded,Yt?.className]),Rn=window.__slotContextValue;return Te&&Rn?.isEnabled?null:yt.jsx(Ax,{componentId:s.id,componentName:s.name,children:T.blur_until_loaded?yt.jsx("div",{className:[un.wrapperClasses,pr?"opacity-50 blur-sm transition-all duration-300 pointer-events-none":void 0].filter(Boolean).join(" ")||void 0,children:da}):da})});Vr.displayName="DynamicRenderer",typeof window<"u"&&(window.__DynamicRenderer=Vr);function xx(s){const e={};try{const r=new URL(s,typeof window<"u"?window.location.origin:"http://localhost").searchParams,i=new Set;for(const l of r.keys()){if(i.has(l))continue;i.add(l);const c=r.getAll(l);c.length>1||l.endsWith("[]")?e[l]=c:e[l]=c[0]}}catch{}return e}function Tx(s){try{return new URL(s,typeof window<"u"?window.location.origin:"http://localhost").pathname}catch{return s.split("?")[0]}}const $i=class $i{constructor(){M(this,"config",{enabled:!1,maxHistorySize:100,logLevel:"info",serverEndpoint:"/_boost/g7-debug/dump-state",autoCapture:!0});M(this,"stateHistory",[]);M(this,"snapshotIdCounter",0);M(this,"stateWatchers",new Map);M(this,"currentLocalState",{});M(this,"currentComputedState",{});M(this,"currentParentContext");M(this,"actionHistory",[]);M(this,"actionWatchers",new Set);M(this,"cacheStats",{hits:0,misses:0,entries:0});M(this,"cacheDecisions",[]);M(this,"cacheDecisionIdCounter",0);M(this,"maxCacheDecisions",200);M(this,"currentRenderCycleId",null);M(this,"isInActionExecution",!1);M(this,"isInIteration",!1);M(this,"mountedComponents",new Map);M(this,"eventListeners",new Map);M(this,"mountWatchers",new Set);M(this,"unmountWatchers",new Set);M(this,"renderCounts",new Map);M(this,"bindingEvalCount",0);M(this,"profilingData",[]);M(this,"isProfiling",!1);M(this,"profilingStartTime",0);M(this,"activeRequests",new Map);M(this,"requestHistory",[]);M(this,"pendingDataSources",new Set);M(this,"requestWatchers",new Set);M(this,"wsConnections",new Map);M(this,"wsMessageHistory",[]);M(this,"wsMessageWatchers",new Set);M(this,"ifConditions",new Map);M(this,"iterations",new Map);M(this,"conditionWatchers",new Set);M(this,"forms",new Map);M(this,"formWatchers",new Set);M(this,"expressionHistory",[]);M(this,"expressionWatchers",new Set);M(this,"expressionIdCounter",0);M(this,"dataSources",new Map);M(this,"dataPathTransforms",[]);M(this,"maxDataPathTransforms",100);M(this,"nestedContexts",[]);M(this,"nestedContextIdCounter",0);M(this,"maxNestedContexts",100);M(this,"formBindingIssues",[]);M(this,"formBindingValidations",[]);M(this,"formBindingIssueIdCounter",0);M(this,"maxFormBindingIssues",100);M(this,"computedProperties",new Map);M(this,"computedRecalcLogs",[]);M(this,"computedRecalcIdCounter",0);M(this,"maxComputedRecalcLogs",100);M(this,"handlers",new Map);M(this,"componentEventSubscriptions",new Map);M(this,"componentEventEmitHistory",[]);M(this,"componentEventIdCounter",0);M(this,"maxComponentEventHistory",100);M(this,"stateRenderingLogs",[]);M(this,"currentStateChangeContext",null);M(this,"stateRenderingIdCounter",0);M(this,"componentRenderCounts",new Map);M(this,"stateToComponentMap",new Map);M(this,"maxStateRenderingLogs",100);M(this,"componentStateSources",new Map);M(this,"contextFlowNodes",new Map);M(this,"dynamicStates",new Map);M(this,"styleIssues",[]);M(this,"componentStyles",new Map);M(this,"authEvents",[]);M(this,"authHeaderHistory",[]);M(this,"authEventIdCounter",0);M(this,"maxAuthEventHistory",50);M(this,"logHistory",[]);M(this,"logIdCounter",0);M(this,"maxLogHistory",500);M(this,"currentLayout",null);M(this,"layoutHistory",[]);M(this,"layoutIdCounter",0);M(this,"layoutStats",{totalLoads:0,cacheHits:0,apiLoads:0});M(this,"maxLayoutHistory",50);M(this,"executionDetails",new Map);M(this,"stateChangeHistory",[]);M(this,"dataSourceChangeHistory",[]);M(this,"changeAlerts",[]);M(this,"changeExpectations",new Map);M(this,"executionIdCounter",0);M(this,"stateChangeIdCounter",0);M(this,"alertIdCounter",0);M(this,"maxExecutionDetails",100);M(this,"maxStateChangeHistory",200);M(this,"maxChangeAlerts",100);M(this,"sequenceExecutions",[]);M(this,"sequenceExecutionStack",[]);M(this,"sequenceIdCounter",0);M(this,"maxSequenceExecutions",50);M(this,"staleClosureWarnings",[]);M(this,"staleClosureIdCounter",0);M(this,"maxStaleClosureWarnings",100);M(this,"stateCaptureRegistry",new Map);M(this,"modalStates",new Map);M(this,"modalStateIssues",[]);M(this,"modalStateRelations",[]);M(this,"modalStateChangeLogs",[]);M(this,"modalStateIssueIdCounter",0);M(this,"modalStateChangeLogIdCounter",0);M(this,"maxModalStateIssues",100);M(this,"maxModalStateChangeLogs",200);M(this,"namedActionDefinitions",{});M(this,"namedActionRefLogs",[]);M(this,"namedActionRefIdCounter",0);M(this,"maxNamedActionRefLogs",200);M(this,"debounceActionHistory",[]);M(this,"dataSourceUpdateHistory",[]);M(this,"debounceActionIdCounter",0);M(this,"dataSourceUpdateIdCounter",0)}static getInstance(){return $i.instance||($i.instance=new $i),$i.instance}initialize(){if(this.config.enabled=this.checkDebugMode(),!this.config.enabled){console.log("[G7DevTools] 비활성화됨 (환경설정 > 고급 설정 > 디버그 모드를 켜세요)");return}console.log("[G7DevTools] 활성화됨"),this.setupGlobalErrorHandler()}checkDebugMode(){try{if(window.G7Config?.debug===!0)return!0;const r=window.G7Core?.state?.get?.();return r?.settings?.advanced?.debug_mode===!0||r?._global?.settings?.advanced?.debug_mode===!0}catch{return!1}}isEnabled(){return this.config.enabled}setupGlobalErrorHandler(){window.addEventListener("error",e=>{this.config.enabled&&this.logAction({id:this.generateId(),type:"globalError",startTime:Date.now(),status:"error",error:{name:"Error",message:e.message,stack:e.error?.stack}})}),window.addEventListener("unhandledrejection",e=>{this.config.enabled&&this.logAction({id:this.generateId(),type:"unhandledRejection",startTime:Date.now(),status:"error",error:{name:"UnhandledRejection",message:String(e.reason)}})})}captureStateSnapshot(e){if(!this.config.enabled)return;const n={id:++this.snapshotIdCounter,timestamp:Date.now(),source:e.source,prev:this.sanitizeObject(e.prev),next:this.sanitizeObject(e.next),diff:e.diff};this.stateHistory.push(n),this.stateHistory.length>this.config.maxHistorySize&&this.stateHistory.shift(),this.notifyStateWatchers(n)}getState(){try{const e=window.G7Core,n=e?.state?.get?.()||{},r={};if(e?._isolatedStates&&typeof e._isolatedStates=="object")for(const[i,l]of Object.entries(e._isolatedStates))r[i]=this.sanitizeObject(l);return{_global:n._global||n,_local:this.currentLocalState,_computed:this.currentComputedState,_isolated:Object.keys(r).length>0?r:void 0,$parent:this.currentParentContext}}catch{return{_global:{},_local:this.currentLocalState,_computed:this.currentComputedState}}}updateLocalState(e){this.config.enabled&&(this.currentLocalState=this.sanitizeObject(e))}updateComputedState(e){this.config.enabled&&(this.currentComputedState=this.sanitizeObject(e))}updateParentContext(e){this.config.enabled&&(this.currentParentContext=e?this.sanitizeObject(e):void 0)}getStateHistory(){return[...this.stateHistory]}getDualStorageMismatch(){const e=this.currentLocalState||{};let n={};try{n=(window.G7Core?.state?.get?.()||{})._local||{}}catch{n={}}const r=this.findMismatchedLeafPaths(e,n);return{hasMismatch:r.length>0,mismatchedPaths:r,storageA:e,storageB:n}}findMismatchedLeafPaths(e,n,r=""){const i=[],l=new Set([...Object.keys(e||{}),...Object.keys(n||{})]);for(const c of l){const d=r?`${r}.${c}`:c,f=e?.[c],h=n?.[c],m=f&&typeof f=="object"&&!Array.isArray(f),y=h&&typeof h=="object"&&!Array.isArray(h);if(m&&y)i.push(...this.findMismatchedLeafPaths(f,h,d));else{const v=f===void 0?"__undefined__":JSON.stringify(f),S=h===void 0?"__undefined__":JSON.stringify(h);v!==S&&i.push(d)}}return i}watchState(e,n){return this.stateWatchers.has(e)||this.stateWatchers.set(e,new Set),this.stateWatchers.get(e).add(n),()=>{this.stateWatchers.get(e)?.delete(n)}}notifyStateWatchers(e){if(this.stateWatchers.get("*")?.forEach(n=>{try{n(e.next,e.prev,"*")}catch(r){console.error("[G7DevTools] State watcher error:",r)}}),e.diff)for(const n of e.diff.changed)this.stateWatchers.get(n.path)?.forEach(r=>{try{r(n.newValue,n.oldValue,n.path)}catch(i){console.error("[G7DevTools] State watcher error:",i)}})}diffSnapshots(e,n){const r=this.stateHistory.find(l=>l.id===e),i=this.stateHistory.find(l=>l.id===n);return!r||!i?null:this.calculateDiff(r.next,i.next)}calculateDiff(e,n,r=""){const i={added:[],removed:[],changed:[]},l=new Set(Object.keys(e)),c=new Set(Object.keys(n));for(const d of c){const f=r?`${r}.${d}`:d;l.has(d)?JSON.stringify(e[d])!==JSON.stringify(n[d])&&i.changed.push({path:f,oldValue:e[d],newValue:n[d]}):i.added.push(f)}for(const d of l){const f=r?`${r}.${d}`:d;c.has(d)||i.removed.push(f)}return i}logAction(e){if(!this.config.enabled)return;const n=this.actionHistory.findIndex(r=>r.id===e.id);n>=0?this.actionHistory[n]={...this.actionHistory[n],...e}:(this.actionHistory.push(e),this.actionHistory.length>this.config.maxHistorySize&&this.actionHistory.shift()),this.isProfiling&&this.profilingData.push({type:"action",action:e.type,timestamp:performance.now(),duration:e.duration}),this.notifyActionWatchers(e)}getActionHistory(){return[...this.actionHistory]}watchActions(e){return this.actionWatchers.add(e),()=>{this.actionWatchers.delete(e)}}notifyActionWatchers(e){this.actionWatchers.forEach(n=>{try{n(e)}catch(r){console.error("[G7DevTools] Action watcher error:",r)}})}getActionMetrics(){const e=this.actionHistory.filter(l=>l.status==="success"),n=this.actionHistory.filter(l=>l.status==="error"),r={};for(const l of this.actionHistory)r[l.type]=(r[l.type]||0)+1;const i=e.reduce((l,c)=>l+(c.duration||0),0);return{totalActions:this.actionHistory.length,successCount:e.length,errorCount:n.length,averageDuration:e.length>0?i/e.length:0,actionsByType:r}}recordCacheHit(){this.config.enabled&&this.cacheStats.hits++}recordCacheMiss(){this.config.enabled&&this.cacheStats.misses++}updateCacheEntries(e){this.config.enabled&&(this.cacheStats.entries=e)}getCacheStats(){const e=this.cacheStats.hits+this.cacheStats.misses;return{...this.cacheStats,hitRate:e>0?this.cacheStats.hits/e:0}}resetCacheStats(){this.cacheStats={hits:0,misses:0,entries:0}}trackCacheDecision(e){if(!this.config.enabled)return;const n={id:`cache-${++this.cacheDecisionIdCounter}`,timestamp:Date.now(),expression:e.expression,decision:e.decision,reason:e.reason,context:{isInIteration:this.isInIteration,isInAction:this.isInActionExecution,renderCycleId:this.currentRenderCycleId||void 0,componentId:e.componentId,skipCacheOption:e.skipCacheOption},cachedValue:e.cachedValue!==void 0?this.safeClone(e.cachedValue):void 0,freshValue:e.freshValue!==void 0?this.safeClone(e.freshValue):void 0,valueMatch:e.cachedValue!==void 0&&e.freshValue!==void 0?JSON.stringify(e.cachedValue)===JSON.stringify(e.freshValue):void 0,duration:e.duration};this.cacheDecisions.push(n),this.cacheDecisions.length>this.maxCacheDecisions&&this.cacheDecisions.shift(),e.decision==="cache_hit"?this.cacheStats.hits++:(e.decision==="cache_miss"||e.decision==="skip_cache")&&this.cacheStats.misses++}startRenderCycle(e){return this.currentRenderCycleId=`render-${Date.now()}-${e||"root"}`,this.currentRenderCycleId}endRenderCycle(){this.currentRenderCycleId=null}startActionContext(){this.isInActionExecution=!0}endActionContext(){this.isInActionExecution=!1}startIterationContext(){this.isInIteration=!0}endIterationContext(){this.isInIteration=!1}getCacheDecisionTrackingInfo(){const e=this.getCacheDecisionStats();return{decisions:[...this.cacheDecisions],stats:e,timestamp:Date.now()}}getCacheDecisionStats(){const e=this.cacheDecisions;let n=0,r=0,i=0,l=0;const c={},d={};for(const h of e)h.decision==="cache_hit"?n++:h.decision==="cache_miss"?r++:h.decision==="skip_cache"?i++:h.decision==="invalidate"&&l++,c[h.reason]=(c[h.reason]||0)+1,h.context.componentId&&(d[h.context.componentId]=(d[h.context.componentId]||0)+1);const f=n+r+i;return{totalDecisions:e.length,cacheHits:n,cacheMisses:r,skipCacheCount:i,invalidateCount:l,byReason:c,byComponent:d,avgHitRate:f>0?n/f:0}}getRecentCacheDecisions(e=20,n){let r=[...this.cacheDecisions];return n&&(r=r.filter(i=>i.decision===n)),r.slice(-e)}clearCacheDecisionData(){this.cacheDecisions=[],this.cacheDecisionIdCounter=0}summarizePropsForLifecycle(e){if(!e||typeof e!="object")return e;const n={},r=["children","ref","key"];for(const[i,l]of Object.entries(e))if(!r.includes(i))if(l==null)n[i]=l;else if(Array.isArray(l))n[i]=`[Array(${l.length})]`;else if(typeof l=="object"){const c=Object.keys(l);c.length<=3?n[i]=`{${c.join(", ")}}`:n[i]=`{${c.slice(0,3).join(", ")}, ... +${c.length-3}}`}else typeof l=="string"&&l.length>50?n[i]=l.substring(0,50)+"...":typeof l=="function"?n[i]="[Function]":n[i]=l;return n}trackMount(e,n){if(!this.config.enabled)return;const r=this.summarizePropsForLifecycle(n.props),i={...n,props:r,id:e,mountTime:Date.now()};this.mountedComponents.set(e,i),this.mountWatchers.forEach(l=>{try{l({componentId:e,componentName:n.name,timestamp:i.mountTime})}catch(c){console.error("[G7DevTools] Mount watcher error:",c)}})}trackUnmount(e){if(!this.config.enabled)return;const n=this.mountedComponents.get(e),r=this.eventListeners.get(e)||[];r.length>0&&console.warn(`[G7DevTools] 컴포넌트 ${e} 언마운트 시 ${r.length}개 리스너 미정리`),n&&this.unmountWatchers.forEach(i=>{try{i({componentId:e,componentName:n.name,timestamp:Date.now(),orphanedListeners:r.length})}catch(l){console.error("[G7DevTools] Unmount watcher error:",l)}}),this.mountedComponents.delete(e)}trackListener(e,n,r){if(!this.config.enabled)return;const i=this.eventListeners.get(e)||[];i.push({type:n,target:r,addedAt:Date.now()}),this.eventListeners.set(e,i)}removeListener(e,n){if(!this.config.enabled)return;const r=this.eventListeners.get(e)||[],i=r.findIndex(l=>l.type===n);i>=0&&r.splice(i,1)}getMountedComponents(){return Array.from(this.mountedComponents.values())}getOrphanedListeners(){const e=[];for(const[n,r]of this.eventListeners)this.mountedComponents.has(n)||e.push(...r.map(i=>({...i,componentId:n})));return e}watchMount(e){return this.mountWatchers.add(e),()=>{this.mountWatchers.delete(e)}}watchUnmount(e){return this.unmountWatchers.add(e),()=>{this.unmountWatchers.delete(e)}}trackRender(e){if(!this.config.enabled)return;const n=this.renderCounts.get(e)||0;this.renderCounts.set(e,n+1),this.isProfiling&&this.profilingData.push({type:"render",component:e,timestamp:performance.now()})}trackBindingEval(e){this.config.enabled&&(this.bindingEvalCount++,this.isProfiling&&e&&this.profilingData.push({type:"binding",expression:e,timestamp:performance.now()}))}getRenderCount(){return new Map(this.renderCounts)}getBindingEvalCount(){return this.bindingEvalCount}getMemoryWarnings(){const e=[];try{const i=this.getState(),l=JSON.stringify(i).length;l>1e6&&e.push({type:"large-state",message:`상태 크기가 ${(l/1024/1024).toFixed(2)}MB입니다`,suggestion:"불필요한 데이터 정리 필요",severity:l>5e6?"error":"warning"})}catch{}this.stateHistory.length>this.config.maxHistorySize*.8&&e.push({type:"large-history",message:`상태 이력이 ${this.stateHistory.length}개입니다`,suggestion:"maxHistory 설정 조정 필요",severity:"warning"});const n=this.getOrphanedListeners().length;n>0&&e.push({type:"orphaned-listeners",message:`${n}개의 정리되지 않은 이벤트 리스너`,suggestion:"useEffect cleanup 또는 removeEventListener 확인",severity:n>10?"error":"warning"});const r=Array.from(this.renderCounts.entries()).filter(([,i])=>i>50);return r.length>0&&e.push({type:"excessive-renders",message:`${r.length}개 컴포넌트가 50회 이상 렌더링`,suggestion:"React.memo, useMemo, useCallback 사용 고려",severity:"warning"}),e}startProfiling(){this.config.enabled&&(this.isProfiling=!0,this.profilingData=[],this.profilingStartTime=performance.now(),console.log("[G7DevTools] 프로파일링 시작"))}stopProfiling(){this.isProfiling=!1;const e=performance.now()-this.profilingStartTime,n=this.analyzeProfile(e);return console.log("[G7DevTools] 프로파일링 완료",n),n}analyzeProfile(e){const n=this.profilingData.filter(h=>h.type==="render"),r=this.profilingData.filter(h=>h.type==="binding"),i=this.profilingData.filter(h=>h.type==="action"),l={};for(const h of n)h.component&&(l[h.component]||(l[h.component]={count:0,durations:[]}),l[h.component].count++,h.duration&&l[h.component].durations.push(h.duration));const c=Object.entries(l).map(([h,m])=>({name:h,renderCount:m.count,avgDuration:m.durations.length>0?m.durations.reduce((y,v)=>y+v,0)/m.durations.length:0})).sort((h,m)=>m.renderCount-h.renderCount).slice(0,10),d={};for(const h of r)h.expression&&(d[h.expression]=(d[h.expression]||0)+1);const f=Object.entries(d).sort(([,h],[,m])=>m-h).slice(0,10).map(([h])=>h);return{duration:e,entries:this.profilingData,summary:{totalRenders:n.length,totalBindings:r.length,totalActions:i.length,slowestComponents:c,hotPaths:f}}}resetPerformanceStats(){this.renderCounts.clear(),this.bindingEvalCount=0,this.profilingData=[]}trackRequest(e,n,r){if(!this.config.enabled)return"";const i=this.generateId(),l=Tx(e),c=xx(e);return this.activeRequests.set(i,{id:i,url:l,fullUrl:e,queryParams:c,method:n,startTime:Date.now(),status:"pending",requestBody:r?.requestBody?this.sanitizeObject(r.requestBody):void 0,dataSourceId:r?.dataSourceId}),this.isProfiling&&this.profilingData.push({type:"network",timestamp:performance.now()}),i}completeRequest(e,n,r){if(!this.config.enabled)return;const i=this.activeRequests.get(e);if(!i)return;const l={...i,status:n>=200&&n<300?"success":"error",statusCode:n,duration:Date.now()-i.startTime,endTime:Date.now(),response:this.sanitizeObject(r)};this.requestHistory.push(l),this.activeRequests.delete(e),this.requestHistory.length>this.config.maxHistorySize&&this.requestHistory.shift(),this.notifyRequestWatchers(l)}failRequest(e,n){if(!this.config.enabled)return;const r=this.activeRequests.get(e);if(!r)return;const i={...r,status:"error",error:n,duration:Date.now()-r.startTime,endTime:Date.now()};this.requestHistory.push(i),this.activeRequests.delete(e),this.notifyRequestWatchers(i)}trackDataSource(e){this.config.enabled&&this.pendingDataSources.add(e)}completeDataSource(e){this.config.enabled&&this.pendingDataSources.delete(e)}getActiveRequests(){return Array.from(this.activeRequests.values())}getRequestHistory(){return[...this.requestHistory]}getPendingDataSources(){return Array.from(this.pendingDataSources)}watchRequest(e){return this.requestWatchers.add(e),()=>{this.requestWatchers.delete(e)}}notifyRequestWatchers(e){this.requestWatchers.forEach(n=>{try{n(e)}catch(r){console.error("[G7DevTools] Request watcher error:",r)}})}trackWebSocketConnection(e,n,r){this.config.enabled&&this.wsConnections.set(e,{id:e,url:n,state:r,connectedAt:r==="open"?Date.now():void 0})}trackWebSocketMessage(e,n,r,i){if(!this.config.enabled)return;const l={id:this.generateId(),connectionId:e,direction:n,type:r,payload:this.sanitizeObject(i),timestamp:Date.now(),sequence:this.wsMessageHistory.length};this.wsMessageHistory.push(l),this.wsMessageHistory.length>this.config.maxHistorySize&&this.wsMessageHistory.shift(),this.notifyWebSocketWatchers(l)}getWebSocketConnections(){return Array.from(this.wsConnections.values())}getWebSocketMessageHistory(){return[...this.wsMessageHistory]}getWebSocketConnectionState(){const e=Array.from(this.wsConnections.values());return e.some(n=>n.state==="open")?"connected":e.some(n=>n.state==="connecting")?"reconnecting":"disconnected"}watchWebSocketMessage(e){return this.wsMessageWatchers.add(e),()=>{this.wsMessageWatchers.delete(e)}}notifyWebSocketWatchers(e){this.wsMessageWatchers.forEach(n=>{try{n(e)}catch(r){console.error("[G7DevTools] WebSocket watcher error:",r)}})}trackIfCondition(e,n,r){if(!this.config.enabled)return;const i=this.ifConditions.get(e),l=i?.evaluatedValue;this.ifConditions.set(e,{id:e,expression:n,evaluatedValue:r,evaluationCount:(i?.evaluationCount||0)+1,lastEvaluated:Date.now()}),i&&l!==r&&this.notifyConditionWatchers({id:e,expression:n,oldValue:l,newValue:r,timestamp:Date.now()})}trackIteration(e,n){this.config.enabled&&this.iterations.set(e,{id:e,...n,lastRendered:Date.now()})}getIfConditions(){return Array.from(this.ifConditions.values())}getIterations(){return Array.from(this.iterations.values())}getScopeChain(e){const n=[],r=this.getState();return n.push({name:"_global",value:r._global,source:"global"}),n.push({name:"_local",value:r._local,source:"local"}),n.push({name:"_computed",value:r._computed,source:"computed"}),n}watchConditionChange(e){return this.conditionWatchers.add(e),()=>{this.conditionWatchers.delete(e)}}notifyConditionWatchers(e){this.conditionWatchers.forEach(n=>{try{n(e)}catch(r){console.error("[G7DevTools] Condition watcher error:",r)}})}trackForm(e,n,r){this.config.enabled&&this.forms.set(e,{id:e,dataKey:n,inputs:r,trackedAt:Date.now()})}trackFormChange(e,n,r){if(!this.config.enabled)return;const i={formId:e,inputName:n,value:r,timestamp:Date.now()};this.notifyFormWatchers(i)}untrackForm(e){this.forms.delete(e)}getForms(){return Array.from(this.forms.values())}getFormState(e){const n=this.getState();return this.getNestedValue(n,e)||{}}getBindingPath(e,n){return`${e}.${n}`}watchFormChange(e){return this.formWatchers.add(e),()=>{this.formWatchers.delete(e)}}notifyFormWatchers(e){this.formWatchers.forEach(n=>{try{n(e)}catch(r){console.error("[G7DevTools] Form watcher error:",r)}})}trackFormBindingIssue(e,n,r){if(!this.config.enabled)return;const i={id:`form-issue-${++this.formBindingIssueIdCounter}`,timestamp:Date.now(),type:e,severity:n,formId:r.formId,formDataKey:r.formDataKey,inputInfo:r.inputInfo,contextInfo:r.contextInfo,description:r.description,suggestion:r.suggestion,docLink:r.docLink};this.formBindingIssues.push(i),this.formBindingIssues.length>this.maxFormBindingIssues&&this.formBindingIssues.shift()}validateFormBinding(e,n,r,i){const l=r.map(f=>{const h=[];let m=!0;return f.name||(h.push("Input에 name 속성이 없습니다"),m=!1,this.trackFormBindingIssue("missing-input-name","error",{formId:e,formDataKey:n,inputInfo:{name:f.name,type:f.type},description:"Input에 name 속성이 없어 자동 바인딩이 동작하지 않습니다",suggestion:"Input에 name 속성을 추가하세요",docLink:"troubleshooting-components-form.md"})),n||(h.push("Form에 dataKey가 없습니다"),m=!1),{inputName:f.name||"(unnamed)",inputType:f.type,bindingPath:n&&f.name?`${n}.${f.name}`:"-",isValid:m,currentValue:f.value,issues:h}});if(n||this.trackFormBindingIssue("missing-datakey","error",{formId:e,description:"Form에 dataKey가 설정되지 않아 자동 바인딩이 동작하지 않습니다",suggestion:"Form 컴포넌트에 dataKey props를 설정하세요",docLink:"troubleshooting-components-form.md"}),i.isContextBroken){const f=i.breakReason?.includes("sortable")?"sortable-context-break":i.breakReason?.includes("modal")?"modal-context-isolation":"context-not-propagated";this.trackFormBindingIssue(f,"warning",{formId:e,formDataKey:n,contextInfo:{hasParentFormContext:i.hasParentContext,parentFormContextProp:i.parentContextProp,isInsideSortable:i.breakReason?.includes("sortable")||!1,isInsideModal:i.breakReason?.includes("modal")||!1,depth:0},description:i.breakReason||"Form 컨텍스트가 제대로 전파되지 않았습니다",suggestion:"parentFormContextProp={undefined}를 확인하거나 중첩 구조를 검토하세요",docLink:"troubleshooting-components-form.md"})}const c={formId:e,dataKey:n,timestamp:Date.now(),contextPropagation:i,inputBindings:l,isValid:l.every(f=>f.isValid)&&!!n&&!i.isContextBroken,issueCount:l.filter(f=>!f.isValid).length+(n?0:1)+(i.isContextBroken?1:0)},d=this.formBindingValidations.findIndex(f=>f.formId===e);return d>=0?this.formBindingValidations[d]=c:this.formBindingValidations.push(c),c}getFormBindingValidationTrackingInfo(){const e=this.getFormBindingValidationStats();return{issues:[...this.formBindingIssues],validations:[...this.formBindingValidations],stats:e,timestamp:Date.now()}}getFormBindingValidationStats(){const e={"missing-datakey":0,"missing-input-name":0,"context-not-propagated":0,"sortable-context-break":0,"modal-context-isolation":0,"duplicate-input-name":0,"value-type-mismatch":0,"binding-path-invalid":0},n={info:0,warning:0,error:0};for(const i of this.formBindingIssues)e[i.type]++,n[i.severity]++;const r=Object.entries(e).filter(([,i])=>i>0).sort((i,l)=>l[1]-i[1]).slice(0,5).map(([i,l])=>({type:i,count:l,description:this.getIssueTypeDescription(i)}));return{totalFormsValidated:this.formBindingValidations.length,validForms:this.formBindingValidations.filter(i=>i.isValid).length,formsWithIssues:this.formBindingValidations.filter(i=>!i.isValid).length,totalIssues:this.formBindingIssues.length,byIssueType:e,bySeverity:n,topIssues:r}}getIssueTypeDescription(e){return{"missing-datakey":"Form에 dataKey 누락","missing-input-name":"Input에 name 속성 누락","context-not-propagated":"Form 컨텍스트 전파 실패","sortable-context-break":"Sortable 컨테이너에서 컨텍스트 단절","modal-context-isolation":"모달에서 부모 Form 컨텍스트 격리","duplicate-input-name":"동일 Form 내 중복 Input name","value-type-mismatch":"값 타입과 Input 타입 불일치","binding-path-invalid":"바인딩 경로 유효하지 않음"}[e]||e}clearFormBindingIssues(){this.formBindingIssues=[],this.formBindingValidations=[],this.formBindingIssueIdCounter=0}trackComputedProperty(e,n,r,i,l,c,d){if(!this.config.enabled)return;const f=`computed-${e}-${c||"global"}`,h={id:f,name:e,expression:n,componentId:c,dependencies:r,currentValue:this.safeClone(i),lastComputedAt:Date.now(),computationTime:l,error:d};this.computedProperties.set(f,h)}trackComputedRecalc(e,n,r,i,l,c,d){if(!this.config.enabled)return;const f=`computed-${e}-${d||"global"}`,h=JSON.stringify(r)!==JSON.stringify(i),m={id:`recalc-${++this.computedRecalcIdCounter}`,computedId:f,computedName:e,timestamp:Date.now(),trigger:n,triggeredBy:c,previousValue:this.safeClone(r),newValue:this.safeClone(i),valueChanged:h,computationTime:l,cascadeCount:0};this.computedRecalcLogs.push(m),this.computedRecalcLogs.length>this.maxComputedRecalcLogs&&this.computedRecalcLogs.shift();const y=this.computedProperties.get(f);y&&(y.currentValue=this.safeClone(i),y.lastComputedAt=Date.now(),y.computationTime=l)}analyzeComputedDependencyChain(e,n){const r=new Set,i=[];let l=!1,c,d=0;const f=(m,y)=>{if(d=Math.max(d,y),r.has(m))return l=!0,c=[...i,m],{name:m,type:"computed",children:[],depth:y};r.add(m),i.push(m);const v=this.computedProperties.get(`computed-${m}-${n||"global"}`),S=[];if(v)for(const _ of v.dependencies)_.type==="computed"?S.push(f(_.path,y+1)):S.push({name:_.path,type:_.type,children:[],depth:y+1});return i.pop(),r.delete(m),{name:m,type:"computed",children:S,depth:y}},h=f(e,0);return{root:e,tree:h,hasCycle:l,cyclePath:c,maxDepth:d}}getComputedDependencyTrackingInfo(){const e=Array.from(this.computedProperties.values()),n=this.getComputedDependencyStats(),r=[];for(const i of e){const l=this.analyzeComputedDependencyChain(i.name,i.componentId);r.push(l)}return{properties:e,recalcLogs:[...this.computedRecalcLogs],dependencyChains:r,stats:n,timestamp:Date.now()}}getComputedDependencyStats(){const e={"state-change":0,"datasource-update":0,"dependency-change":0,manual:0,initial:0},n={},r={};let i=0,l=0;for(const h of this.computedRecalcLogs)e[h.trigger]++,h.valueChanged||l++,n[h.computedName]=(n[h.computedName]||0)+1,r[h.computedName]||(r[h.computedName]=[]),r[h.computedName].push(h.computationTime),i+=h.computationTime;const c=Object.entries(n).sort((h,m)=>m[1]-h[1]).slice(0,5).map(([h,m])=>({name:h,count:m})),d=Object.entries(r).map(([h,m])=>({name:h,avgTime:m.reduce((y,v)=>y+v,0)/m.length})).sort((h,m)=>m.avgTime-h.avgTime).slice(0,5);let f=0;for(const h of this.computedProperties.values())this.analyzeComputedDependencyChain(h.name,h.componentId).hasCycle&&f++;return{totalComputed:this.computedProperties.size,totalRecalculations:this.computedRecalcLogs.length,unnecessaryRecalculations:l,avgComputationTime:this.computedRecalcLogs.length>0?i/this.computedRecalcLogs.length:0,topRecalculated:c,slowestComputed:d,cycleDetectionCount:f,byTrigger:e}}clearComputedTracking(){this.computedProperties.clear(),this.computedRecalcLogs=[],this.computedRecalcIdCounter=0}trackModalOpen(e){if(!this.config.enabled)return;const n={modalId:e.modalId,modalName:e.modalName,openedAt:Date.now(),closedAt:null,scopeType:e.scopeType||"isolated",parentModalId:e.parentModalId,componentId:e.componentId,initialState:e.initialState?{...e.initialState}:{},currentState:e.initialState?{...e.initialState}:{},stateChangeCount:0,isolatedStateKeys:e.isolatedStateKeys||[],sharedStateKeys:e.sharedStateKeys||[]};this.modalStates.set(e.modalId,n),this.validateModalDefinitionExists(e.modalId,e.modalName),e.parentModalId&&(this.modalStateRelations.find(i=>i.parentModalId===e.parentModalId&&i.childModalId===e.modalId)||this.modalStateRelations.push({parentModalId:e.parentModalId,childModalId:e.modalId,sharedKeys:e.sharedStateKeys||[],isolatedKeys:e.isolatedStateKeys||[],relationType:"parent-child"}))}trackModalClose(e,n){if(!this.config.enabled)return;const r=this.modalStates.get(e);r&&(r.closedAt=Date.now(),n&&(r.currentState={...n}),this.detectStateLeakage(r))}validateModalDefinitionExists(e,n){const r=this.currentLayout;if(!r?.layoutJson)return;const i=r.layoutJson.modals;if(!i){this.recordModalStateIssue({type:"missing-definition",modalId:e,modalName:n,severity:"error",description:`모달 "${e}"이(가) modalStack에 추가되었으나, 현재 레이아웃에 modals 섹션이 없습니다. 레이아웃 경로: ${r.layoutPath}`,affectedStateKeys:[],expectedValue:`modals 섹션에 id="${e}" 정의 존재`,actualValue:"modals 섹션 없음"});return}const l=Array.isArray(i)?i:Object.values(i);l.some(d=>d?.id===e||d?.props?.id===e)||this.recordModalStateIssue({type:"missing-definition",modalId:e,modalName:n,severity:"error",description:`모달 "${e}"이(가) modalStack에 추가되었으나, 렌더링된 레이아웃의 modals 섹션에 해당 ID의 정의가 없습니다. partial 로딩 실패, 레이아웃 병합 문제, extends 상속 누락 등을 확인하세요. 레이아웃 경로: ${r.layoutPath}`,affectedStateKeys:[],expectedValue:`modals 배열에 id="${e}" 항목 존재`,actualValue:`modals 배열에 ${l.length}개 모달 정의 (${l.map(d=>d?.id||"unknown").join(", ")})`})}trackModalStateChange(e){if(!this.config.enabled)return;const n=this.modalStates.get(e.modalId);if(!n)return;n.stateChangeCount++,n.currentState[e.stateKey]=e.newValue;const r=this.checkIsolationViolation(n,e.stateKey),i={id:`modal-change-${++this.modalStateChangeLogIdCounter}`,modalId:e.modalId,modalName:n.modalName,timestamp:Date.now(),stateKey:e.stateKey,previousValue:e.previousValue,newValue:e.newValue,changeSource:e.changeSource||"user-action",violatesIsolation:r};this.modalStateChangeLogs.push(i),this.modalStateChangeLogs.length>this.maxModalStateChangeLogs&&this.modalStateChangeLogs.shift(),r&&this.recordModalStateIssue({type:"isolation-violation",modalId:e.modalId,modalName:n.modalName,severity:"warning",description:`모달 '${n.modalName}'에서 격리된 상태 키 '${e.stateKey}'가 변경됨`,affectedStateKeys:[e.stateKey]})}detectStateLeakage(e){if(e.scopeType==="isolated")for(const n of e.isolatedStateKeys){const r=e.initialState[n],i=e.currentState[n];if(r!==i&&e.parentModalId){const l=this.modalStates.get(e.parentModalId);l&&l.currentState.hasOwnProperty(n)&&this.recordModalStateIssue({type:"state-leakage",modalId:e.modalId,modalName:e.modalName,severity:"error",description:`모달 닫힘 후 상태 '${n}'가 부모 모달로 유출될 수 있음`,affectedStateKeys:[n],leakedValue:i,expectedValue:r})}}}checkIsolationViolation(e,n){return e.scopeType==="isolated",!1}recordModalStateIssue(e){const n={id:`modal-issue-${++this.modalStateIssueIdCounter}`,type:e.type,modalId:e.modalId,modalName:e.modalName,timestamp:Date.now(),severity:e.severity,description:e.description,affectedStateKeys:e.affectedStateKeys,leakedValue:e.leakedValue,expectedValue:e.expectedValue,actualValue:e.actualValue,stackInfo:e.stackInfo};this.modalStateIssues.push(n),this.modalStateIssues.length>this.maxModalStateIssues&&this.modalStateIssues.shift()}getModalStateScopeTrackingInfo(){const e=Array.from(this.modalStates.values()),n=this.getModalStateScopeStats();return{modals:e,issues:[...this.modalStateIssues],relations:[...this.modalStateRelations],changeLogs:[...this.modalStateChangeLogs],stats:n,timestamp:Date.now()}}getModalStateScopeStats(){const e=Array.from(this.modalStates.values()),n={warning:0,error:0},r={"state-leakage":0,"isolation-violation":0,"parent-mutation":0,"orphaned-state":0,"scope-mismatch":0,"cleanup-failure":0,"missing-definition":0};for(const l of this.modalStateIssues)n[l.severity]++,r[l.type]++;const i={isolated:0,shared:0,inherited:0};for(const l of e)i[l.scopeType]++;return{totalModals:e.length,openModals:e.filter(l=>l.closedAt===null).length,nestedModals:e.filter(l=>l.parentModalId!==void 0).length,totalIssues:this.modalStateIssues.length,issuesBySeverity:n,issuesByType:r,byScope:i,leakageDetectionCount:r["state-leakage"],cleanupFailureCount:r["cleanup-failure"]}}clearModalStateScopeTracking(){this.modalStates.clear(),this.modalStateIssues=[],this.modalStateRelations=[],this.modalStateChangeLogs=[],this.modalStateIssueIdCounter=0,this.modalStateChangeLogIdCounter=0}setNamedActionDefinitions(e){this.config.enabled&&(this.namedActionDefinitions=e||{})}trackNamedActionRef(e){if(!this.config.enabled)return;const n={...e,id:`named_action_ref_${++this.namedActionRefIdCounter}`};this.namedActionRefLogs.push(n),this.namedActionRefLogs.length>this.maxNamedActionRefLogs&&(this.namedActionRefLogs=this.namedActionRefLogs.slice(-this.maxNamedActionRefLogs))}getNamedActionTrackingInfo(){const e={};for(const r of this.namedActionRefLogs)e[r.actionRefName]=(e[r.actionRefName]||0)+1;const n=Object.keys(this.namedActionDefinitions).filter(r=>!e[r]);return{definitions:this.namedActionDefinitions,refLogs:[...this.namedActionRefLogs],stats:{totalDefinitions:Object.keys(this.namedActionDefinitions).length,totalRefs:this.namedActionRefLogs.length,refCountByName:e,unusedDefinitions:n},timestamp:Date.now()}}clearNamedActionTracking(){this.namedActionDefinitions={},this.namedActionRefLogs=[],this.namedActionRefIdCounter=0}trackExpressionEval(e){if(!this.config.enabled)return;const n={...e,id:`expr-${++this.expressionIdCounter}`,timestamp:Date.now(),warning:this.detectExpressionWarning(e)};this.expressionHistory.push(n),this.expressionHistory.length>500&&this.expressionHistory.shift(),this.isProfiling&&this.profilingData.push({type:"binding",expression:e.expression,timestamp:performance.now(),duration:e.duration}),this.notifyExpressionWatchers(n)}detectExpressionWarning(e){const{expression:n,result:r,resultType:i,method:l,duration:c}=e;if(i==="undefined"&&!n.includes("??")&&!n.includes("||")&&/^[a-zA-Z_$][a-zA-Z0-9_$.]*$/.test(n.replace(/{{|}}/g,"").trim()))return{type:"undefined-result",message:"표현식 결과가 undefined입니다",suggestion:`데이터가 로드되었는지 확인하거나 fallback 값을 추가하세요: ${n} ?? ''`};if(i==="null")return{type:"null-result",message:"표현식 결과가 null입니다",suggestion:`nullish coalescing 연산자를 사용하세요: ${n} ?? ''`};if(l==="resolveBindings"&&i==="string"){const d=String(r);if(d.includes("[object Object]")||/^\[.*\]$/.test(d))return{type:"array-to-string",message:"배열/객체가 문자열로 변환되었습니다",suggestion:"evaluateExpression()을 사용하여 원본 타입을 유지하세요"}}if(l==="resolveBindings"&&i==="string"&&String(r).includes("[object Object]"))return{type:"object-to-string",message:"객체가 [object Object]로 변환되었습니다",suggestion:"evaluateExpression()을 사용하거나 특정 속성에 접근하세요"};if(i==="undefined"&&n.includes(".")&&!n.includes("?.")){const d=n.replace(/{{|}}/g,"").trim().split(".");if(d.length>=2)return{type:"missing-optional-chain",message:"Optional chaining(?.) 누락 가능성",suggestion:`안전한 접근을 위해 ?. 사용을 고려하세요: ${d.join("?.")}`}}if((n.includes("{{item.")||n.includes("{{index}}"))&&Array.from(this.iterations.values()).some(h=>h.itemVar!=="item"))return{type:"wrong-iteration-var",message:"'item'/'index' 대신 iteration에서 정의한 변수명을 사용해야 합니다",suggestion:"iteration의 item_var/index_var에 정의된 변수명을 확인하세요"};if(c&&c>10)return{type:"slow-evaluation",message:`표현식 평가에 ${c.toFixed(2)}ms 소요됨`,suggestion:"복잡한 표현식을 단순화하거나 computed 값으로 분리하세요"}}getExpressions(){return[...this.expressionHistory]}getExpressionWarnings(){return this.expressionHistory.filter(e=>e.warning!=null)}searchExpressions(e){const n=e.toLowerCase();return this.expressionHistory.filter(r=>r.expression.toLowerCase().includes(n)||r.componentName?.toLowerCase().includes(n)||r.propName?.toLowerCase().includes(n))}getExpressionStats(){const e=this.expressionHistory,n=new Set(e.map(f=>f.expression)),r=e.filter(f=>f.warning!=null),i=e.filter(f=>f.fromCache),l=e.filter(f=>f.duration!=null).map(f=>f.duration),c={};for(const f of e)c[f.resultType]=(c[f.resultType]||0)+1;const d={};for(const f of r)f.warning&&(d[f.warning.type]=(d[f.warning.type]||0)+1);return{totalEvaluations:e.length,uniqueExpressions:n.size,warningCount:r.length,cacheHitRate:e.length>0?i.length/e.length:0,averageDuration:l.length>0?l.reduce((f,h)=>f+h,0)/l.length:0,byType:c,byWarning:d}}clearExpressions(){this.expressionHistory=[],this.expressionIdCounter=0}watchExpressions(e){return this.expressionWatchers.add(e),()=>{this.expressionWatchers.delete(e)}}notifyExpressionWatchers(e){this.expressionWatchers.forEach(n=>{try{n(e)}catch(r){console.error("[G7DevTools] Expression watcher error:",r)}})}setLogLevel(e){this.config.logLevel=e}setMaxHistory(e){this.config.maxHistorySize=Math.max(10,Math.min(1e3,e))}setServerEndpoint(e){this.config.serverEndpoint=e}getConfig(){return{...this.config}}generateId(){return`${Date.now()}-${Math.random().toString(36).substr(2,9)}`}sanitizeObject(e,n=new WeakSet){if(e===null||typeof e!="object")return e;if(n.has(e))return"[Circular]";if(n.add(e),Array.isArray(e))return e.map(i=>this.sanitizeObject(i,n));const r={};for(const i of Object.keys(e))try{r[i]=this.sanitizeObject(e[i],n)}catch{r[i]="[Unable to serialize]"}return r}getNestedValue(e,n){if(!n)return e;const r=n.split(".");let i=e;for(const l of r){if(i==null)return;i=i[l]}return i}getLifecycleInfo(){return{mountedComponents:Array.from(this.mountedComponents.values()),orphanedListeners:this.getOrphanedListeners()}}getPerformanceInfo(){return{renderCounts:this.renderCounts,bindingEvalCount:this.bindingEvalCount,memoryWarnings:this.getMemoryWarnings()}}getNetworkInfo(){return{activeRequests:Array.from(this.activeRequests.values()),requestHistory:[...this.requestHistory],pendingDataSources:Array.from(this.pendingDataSources)}}getConditionalInfo(){return{ifConditions:Array.from(this.ifConditions.values()),iterations:Array.from(this.iterations.values())}}getFormInfo(){return Array.from(this.forms.values())}getWebSocketInfo(){return{connections:Array.from(this.wsConnections.values()),messageHistory:[...this.wsMessageHistory],connectionState:this.getWebSocketConnectionState()}}getStateAtTime(e){const n=[...this.stateHistory].reverse().find(r=>r.timestamp<=e);if(n)return{_global:n.next,_local:{}}}reset(){this.stateHistory=[],this.actionHistory=[],this.cacheStats={hits:0,misses:0,entries:0},this.cacheDecisions=[],this.cacheDecisionIdCounter=0,this.currentRenderCycleId=null,this.isInActionExecution=!1,this.isInIteration=!1,this.mountedComponents.clear(),this.eventListeners.clear(),this.renderCounts.clear(),this.bindingEvalCount=0,this.profilingData=[],this.activeRequests.clear(),this.requestHistory=[],this.pendingDataSources.clear(),this.wsConnections.clear(),this.wsMessageHistory=[],this.ifConditions.clear(),this.iterations.clear(),this.forms.clear(),this.expressionHistory=[],this.expressionIdCounter=0,this.dataSources.clear(),this.dataPathTransforms=[],this.nestedContexts=[],this.nestedContextIdCounter=0,this.formBindingIssues=[],this.formBindingValidations=[],this.formBindingIssueIdCounter=0,this.computedProperties.clear(),this.computedRecalcLogs=[],this.computedRecalcIdCounter=0,this.modalStates.clear(),this.modalStateIssues=[],this.modalStateRelations=[],this.modalStateChangeLogs=[],this.modalStateIssueIdCounter=0,this.modalStateChangeLogIdCounter=0,this.logHistory=[],this.logIdCounter=0}getExpressionInfo(){return{expressions:this.getExpressions(),stats:this.getExpressionStats()}}trackLog(e,n,r){if(!this.config.enabled)return;const i=r.map(c=>{if(typeof c=="string")return c;try{return JSON.stringify(c)}catch{return String(c)}}).join(" "),l={id:`log-${++this.logIdCounter}`,level:e,prefix:n,message:i,args:this.sanitizeObject(r),timestamp:Date.now(),stack:e==="error"?new Error().stack:void 0};this.logHistory.push(l),this.logHistory.length>this.maxLogHistory&&this.logHistory.shift()}trackAction(e){if(!this.config.enabled)return;const n={id:`debounce-action-${++this.debounceActionIdCounter}`,...e,timestamp:Date.now()};this.debounceActionHistory.push(n),this.debounceActionHistory.length>100&&this.debounceActionHistory.shift()}trackDataSourceUpdate(e){if(!this.config.enabled)return;const n={id:`ds-update-${++this.dataSourceUpdateIdCounter}`,...e};this.dataSourceUpdateHistory.push(n),this.dataSourceUpdateHistory.length>100&&this.dataSourceUpdateHistory.shift()}getDebounceActionHistory(e){let n=[...this.debounceActionHistory];return e?.handler&&(n=n.filter(r=>r.handler.includes(e.handler))),e?.status&&(n=n.filter(r=>r.status===e.status)),e?.limit&&(n=n.slice(-e.limit)),n}getDataSourceUpdateHistory(e){let n=[...this.dataSourceUpdateHistory];return e?.dataSourceId&&(n=n.filter(r=>r.dataSourceId===e.dataSourceId)),e?.updateType&&(n=n.filter(r=>r.updateType===e.updateType)),e?.limit&&(n=n.slice(-e.limit)),n}getLogs(e){let n=[...this.logHistory];if(e?.level){const r=Array.isArray(e.level)?e.level:[e.level];n=n.filter(i=>r.includes(i.level))}if(e?.prefix){const r=e.prefix.toLowerCase();n=n.filter(i=>i.prefix.toLowerCase().includes(r))}if(e?.search){const r=e.search.toLowerCase();n=n.filter(i=>i.message.toLowerCase().includes(r))}return e?.since&&(n=n.filter(r=>r.timestamp>=e.since)),e?.limit&&(n=n.slice(-e.limit)),n}getLogStats(){const n=Date.now()-6e4,r={log:0,warn:0,error:0,debug:0,info:0},i={};let l=0,c=0;for(const d of this.logHistory)r[d.level]++,i[d.prefix]=(i[d.prefix]||0)+1,d.timestamp>=n&&(d.level==="error"&&l++,d.level==="warn"&&c++);return{totalLogs:this.logHistory.length,byLevel:r,byPrefix:i,recentErrors:l,recentWarnings:c}}clearLogs(){this.logHistory=[],this.logIdCounter=0}getLogInfo(){return{entries:this.getLogs(),stats:this.getLogStats()}}trackDataSourceDefinition(e){this.config.enabled&&this.dataSources.set(e.id,{...e,status:"idle"})}trackDataSourceLoading(e){if(!this.config.enabled)return;const n=this.dataSources.get(e);n&&this.dataSources.set(e,{...n,status:"loading"})}trackDataSourceLoaded(e,n,r){if(!this.config.enabled)return;const i=this.dataSources.get(e);if(!i)return;let l,c,d=r;n&&(Array.isArray(n)?(l=n.length,c=n.length>0&&typeof n[0]=="object"?Object.keys(n[0]):void 0):typeof n=="object"&&(Array.isArray(n.data)?(d=d||"data",l=n.data.length,c=n.data.length>0&&typeof n.data[0]=="object"?Object.keys(n.data[0]):void 0):Array.isArray(n.items)?(d=d||"items",l=n.items.length,c=n.items.length>0&&typeof n.items[0]=="object"?Object.keys(n.items[0]):void 0):c=Object.keys(n))),this.dataSources.set(e,{...i,status:"loaded",dataPath:d,itemCount:l,keys:c,lastLoadedAt:Date.now(),error:void 0})}trackDataSourceError(e,n){if(!this.config.enabled)return;const r=this.dataSources.get(e);r&&this.dataSources.set(e,{...r,status:"error",error:n,lastLoadedAt:Date.now()})}untrackDataSource(e){this.dataSources.delete(e)}getDataSources(){return Array.from(this.dataSources.values())}getDataSource(e){return this.dataSources.get(e)}clearDataSources(){this.dataSources.clear()}trackDataPathTransform(e){if(!this.config.enabled)return;let n=this.dataPathTransforms.find(r=>r.dataSourceId===e.dataSourceId);n||(n={dataSourceId:e.dataSourceId,timestamp:Date.now(),transformSteps:[],warnings:[]},this.dataPathTransforms.push(n),this.dataPathTransforms.length>this.maxDataPathTransforms&&this.dataPathTransforms.shift()),n.transformSteps.push({step:e.step,inputPath:e.inputPath,inputValue:this.safeClone(e.inputValue),outputPath:e.outputPath,outputValue:this.safeClone(e.outputValue),config:e.config}),e.warning&&n.warnings.push(e.warning)}setDataPathFinalBinding(e,n){if(!this.config.enabled)return;const r=this.dataPathTransforms.find(i=>i.dataSourceId===e);r&&(r.finalBinding={expression:n.expression,resolvedPath:n.resolvedPath,value:this.safeClone(n.value)})}getDataPathTransformTrackingInfo(){const e=this.getDataPathTransformStats();return{transforms:[...this.dataPathTransforms],stats:e,timestamp:Date.now()}}getDataPathTransformStats(){const e=this.dataPathTransforms,n={};let r=0;const i={};for(const c of e){n[c.dataSourceId]=(n[c.dataSourceId]||0)+1,r+=c.warnings.length;for(const d of c.warnings)i[d]=(i[d]||0)+1}const l=Object.entries(i).map(([c,d])=>({warning:c,count:d})).sort((c,d)=>d.count-c.count).slice(0,10);return{totalTransforms:e.length,byDataSource:n,warningsCount:r,commonWarnings:l}}getDataPathTransformForDataSource(e){return this.dataPathTransforms.find(n=>n.dataSourceId===e)}clearDataPathTransforms(){this.dataPathTransforms=[]}trackNestedContext(e){if(!this.config.enabled)return"";const n=`nested-${++this.nestedContextIdCounter}`,r=[...new Set([...e.parentContext.available,...e.ownContext.added])],i={...e.parentContext.values,...e.ownContext.values},l={id:n,timestamp:Date.now(),componentId:e.componentId,componentType:e.componentType,parentContext:{available:e.parentContext.available,values:this.safeClone(e.parentContext.values)},ownContext:{added:e.ownContext.added,values:this.safeClone(e.ownContext.values)},mergedContext:{all:r,values:this.safeClone(i)},accessAttempts:[],depth:e.depth,parentId:e.parentId};return this.nestedContexts.push(l),this.nestedContexts.length>this.maxNestedContexts&&this.nestedContexts.shift(),n}trackNestedContextAccess(e,n){if(!this.config.enabled)return;const r=this.nestedContexts.find(i=>i.id===e);r&&r.accessAttempts.push({path:n.path,found:n.found,value:n.found?this.safeClone(n.value):void 0,error:n.error})}getNestedContextTrackingInfo(){const e=this.getNestedContextStats();return{contexts:[...this.nestedContexts],stats:e,timestamp:Date.now()}}getNestedContextStats(){const e=this.nestedContexts,n={expandChildren:0,cellChildren:0,iteration:0,modal:0,slot:0};let r=0,i=0;const l={};for(const d of e){n[d.componentType]++,d.depth>r&&(r=d.depth);for(const f of d.accessAttempts)f.found||(i++,l[f.path]=(l[f.path]||0)+1)}const c=Object.entries(l).map(([d,f])=>({path:d,count:f})).sort((d,f)=>f.count-d.count).slice(0,10);return{totalContexts:e.length,byType:n,maxDepth:r,failedAccessCount:i,commonFailedPaths:c}}getNestedContextForComponent(e){return this.nestedContexts.find(n=>n.componentId===e)}clearNestedContexts(){this.nestedContexts=[],this.nestedContextIdCounter=0}trackHandlerRegistration(e,n="custom",r,i){this.isEnabled&&this.handlers.set(e,{name:e,category:n,description:r,registeredAt:Date.now(),source:i})}trackHandlerUnregistration(e){this.isEnabled&&this.handlers.delete(e)}getHandlers(){return Array.from(this.handlers.values())}getHandler(e){return this.handlers.get(e)}getHandlersByCategory(e){return Array.from(this.handlers.values()).filter(n=>n.category===e)}clearHandlers(){this.handlers.clear()}trackEventSubscribe(e){if(!this.isEnabled)return;const n=this.componentEventSubscriptions.get(e),r=Date.now();n?(n.subscriberCount++,n.lastSubscribedAt=r):this.componentEventSubscriptions.set(e,{eventName:e,subscriberCount:1,firstSubscribedAt:r,lastSubscribedAt:r})}trackEventUnsubscribe(e){if(!this.isEnabled)return;const n=this.componentEventSubscriptions.get(e);n&&(n.subscriberCount--,n.subscriberCount<=0&&this.componentEventSubscriptions.delete(e))}trackEventEmit(e,n,r,i,l){if(!this.isEnabled)return;const c={id:`evt_${++this.componentEventIdCounter}`,eventName:e,data:this.sanitizeForLogging(n),timestamp:Date.now(),listenerCount:r,results:i?this.sanitizeForLogging(i):void 0,hasError:!!l,errorMessage:l?.message};this.componentEventEmitHistory.push(c),this.componentEventEmitHistory.length>this.maxComponentEventHistory&&(this.componentEventEmitHistory=this.componentEventEmitHistory.slice(-this.maxComponentEventHistory))}trackEventOff(e){this.isEnabled&&this.componentEventSubscriptions.delete(e)}trackEventClear(){this.isEnabled&&this.componentEventSubscriptions.clear()}getComponentEventInfo(){const e=Array.from(this.componentEventSubscriptions.values());return{subscriptions:e,emitHistory:[...this.componentEventEmitHistory],totalSubscribers:e.reduce((n,r)=>n+r.subscriberCount,0),totalEmits:this.componentEventEmitHistory.length}}getEventEmitHistory(){return[...this.componentEventEmitHistory]}getEventSubscriptions(){return Array.from(this.componentEventSubscriptions.values())}clearComponentEvents(){this.componentEventSubscriptions.clear(),this.componentEventEmitHistory=[],this.componentEventIdCounter=0}sanitizeForLogging(e){if(e==null||typeof e!="object")return e;try{return JSON.parse(JSON.stringify(e))}catch{return"[Circular or Non-serializable]"}}startStateChange(e,n,r,i){if(!this.config.enabled)return"";const l=`setState_${++this.stateRenderingIdCounter}`;this.currentStateChangeContext={setStateId:l,startTime:performance.now(),changedPath:e,trigger:i||{},renderedComponents:[]};const c={id:`sr_${this.stateRenderingIdCounter}`,setStateId:l,statePath:e,oldValue:this.sanitizeForLogging(n),newValue:this.sanitizeForLogging(r),timestamp:Date.now(),triggeredBy:i||{},renderedComponents:[],totalRenderDuration:0,affectedBindingsCount:0};return this.stateRenderingLogs.push(c),this.stateRenderingLogs.length>this.maxStateRenderingLogs&&this.stateRenderingLogs.shift(),l}trackComponentRender(e,n,r,i=[],l=[],c){if(!this.config.enabled)return;const d=this.componentRenderCounts.get(n)||0;this.componentRenderCounts.set(n,d+1);for(const f of i)this.stateToComponentMap.has(f)||this.stateToComponentMap.set(f,new Set),this.stateToComponentMap.get(f).add(n);if(this.currentStateChangeContext){const f={componentId:e,componentName:n,renderDuration:r,accessedStatePaths:i,evaluatedBindings:l,renderOrder:this.currentStateChangeContext.renderedComponents.length,parentId:c};this.currentStateChangeContext.renderedComponents.push(f)}this.trackRender(n)}completeStateChange(e){if(!this.config.enabled||!this.currentStateChangeContext||this.currentStateChangeContext.setStateId!==e)return;const n=this.currentStateChangeContext,i=performance.now()-n.startTime,l=this.stateRenderingLogs.findIndex(c=>c.setStateId===e);if(l>=0){const c=this.stateRenderingLogs[l];c.renderedComponents=n.renderedComponents,c.totalRenderDuration=i,c.affectedBindingsCount=n.renderedComponents.reduce((d,f)=>d+f.evaluatedBindings.length,0)}this.currentStateChangeContext=null}getCurrentStateChangeContext(){return this.currentStateChangeContext}getStateRenderingLogs(){return[...this.stateRenderingLogs]}getStateRenderingInfo(){const e=this.stateRenderingLogs,n=Object.fromEntries(this.componentRenderCounts),r={};for(const[i,l]of this.stateToComponentMap)r[i]=Array.from(l);return{logs:e,componentRenderCounts:n,stateToComponentMap:r,stats:this.calculateStateRenderingStats()}}calculateStateRenderingStats(){const e=this.stateRenderingLogs;if(e.length===0)return{totalStateChanges:0,totalRenders:0,avgRenderDuration:0,avgComponentsPerChange:0,topRenderedComponents:[],topInfluentialPaths:[]};const n=e.reduce((d,f)=>d+f.renderedComponents.length,0),r=e.reduce((d,f)=>d+f.totalRenderDuration,0)/e.length,i=n/e.length,l=Array.from(this.componentRenderCounts.entries()).sort((d,f)=>f[1]-d[1]).slice(0,5).map(([d,f])=>({name:d,count:f})),c=Array.from(this.stateToComponentMap.entries()).sort((d,f)=>f[1].size-d[1].size).slice(0,5).map(([d,f])=>({path:d,affectedComponents:f.size}));return{totalStateChanges:e.length,totalRenders:n,avgRenderDuration:r,avgComponentsPerChange:i,topRenderedComponents:l,topInfluentialPaths:c}}getComponentsAffectedByState(e){const n=this.stateToComponentMap.get(e);return n?Array.from(n):[]}getComponentRenderHistory(e){return this.stateRenderingLogs.filter(n=>n.renderedComponents.some(r=>r.componentName===e))}clearStateRenderingLogs(){this.stateRenderingLogs=[],this.componentRenderCounts.clear(),this.stateToComponentMap.clear(),this.currentStateChangeContext=null,this.stateRenderingIdCounter=0}trackComponentStateSource(e,n,r,i){this.config.enabled&&this.componentStateSources.set(e,{componentId:e,componentName:n,stateSource:r,stateProvider:i})}trackDynamicState(e,n){this.config.enabled&&this.dynamicStates.set(e,n)}trackContextFlow(e,n,r,i,l,c){if(!this.config.enabled)return;const d={component:n,componentId:e,contextReceived:r,passedToChildren:i,usedInRender:l};if(this.contextFlowNodes.set(e,d),c){const f=this.contextFlowNodes.get(c);f&&(f.children||(f.children=[]),f.children.push(d))}}getStateHierarchyInfo(e=!0){const n=this.buildStateHierarchyLayers(),r=this.detectStateConflicts(n),i=Array.from(this.componentStateSources.values());return{layers:e?n.map(c=>({...c,values:this.summarizeValues(c.values,2)})):n,conflicts:r,componentStateSources:i,timestamp:Date.now()}}summarizeValues(e,n,r=0){if(e==null)return e;if(typeof e!="object")return typeof e=="string"&&e.length>100?e.substring(0,100)+`... (${e.length}자)`:e;if(r>=n){if(Array.isArray(e))return`[Array(${e.length})]`;const c=Object.keys(e);return`{${c.slice(0,5).join(", ")}${c.length>5?`, ... +${c.length-5}`:""}}`}if(Array.isArray(e))return e.length<=3?e.map(c=>this.summarizeValues(c,n,r+1)):{__type:"array",__length:e.length,__preview:e.slice(0,3).map(c=>this.summarizeValues(c,n,r+1))};const i={},l=Object.keys(e);for(const c of l)i[c]=this.summarizeValues(e[c],n,r+1);return i}buildStateHierarchyLayers(){const e=[];try{const r=window.G7Core?.state?.get?.();if(!r)return e;r._local&&e.push({name:"Global _local",type:"global",path:"_local",values:r._local,priority:1}),r._global&&e.push({name:"Global _global",type:"global",path:"_global",values:r._global,priority:1});for(const[l,c]of this.dynamicStates)c&&Object.keys(c).length>0&&e.push({name:`DynamicState (${l})`,type:"dynamicState",componentId:l,values:c,priority:2});const i=this.computeEffectiveState(r._local||{});e.push({name:"Effective _local (merged)",type:"effective",values:i,priority:3})}catch(n){console.warn("[G7DevTools] 상태 계층 빌드 실패:",n)}return e}computeEffectiveState(e){const n={...e};for(const[,r]of this.dynamicStates)r._local&&Object.assign(n,r._local);return n}detectStateConflicts(e){const n=[],r=e.find(l=>l.type==="global"&&l.path==="_local");if(!r)return n;const i=e.filter(l=>l.type==="dynamicState");for(const l of i){const c=l.values._local||l.values;for(const[d,f]of Object.entries(c)){const h=r.values[d];if(JSON.stringify(h)!==JSON.stringify(f)){const m=[],y=[];for(const v of this.componentStateSources.values())v.stateSource.local.some(_=>_===`_local.${d}`||_===d)&&(v.stateProvider.type==="dynamicState"?m.push(v.componentName):v.stateProvider.type==="globalState"&&y.push(v.componentName));n.push({path:`_local.${d}`,globalValue:h,dynamicStateValue:f,effectiveValue:f,usedBy:m,notUsedBy:y,severity:y.length>0?"warning":"info",description:y.length>0?`${y.join(", ")}은(는) globalState._local을 읽어 dynamicState 값을 사용하지 못함`:"dynamicState 값이 globalState와 다르지만 모든 컴포넌트가 dynamicState를 사용"})}}}return n}getContextFlowInfo(){const e=[],n=new Set(this.contextFlowNodes.keys());for(const r of this.contextFlowNodes.values())if(r.children)for(const i of r.children)n.delete(i.componentId);for(const r of n){const i=this.contextFlowNodes.get(r);i&&e.push(i)}return{rootComponent:e.length>0?e[0].component:"Unknown",contextFlow:e,timestamp:Date.now()}}clearStateHierarchyData(){this.componentStateSources.clear(),this.contextFlowNodes.clear(),this.dynamicStates.clear()}trackComponentStyle(e,n,r,i){if(!this.config.enabled)return;const l=this.analyzeTailwindClasses(r),c={componentId:e,componentName:n,classes:r,computedStyles:i,tailwindAnalysis:l};this.componentStyles.set(e,c),this.detectStyleIssues(c)}analyzeTailwindClasses(e){const n=[],r=[],i=[],l=[],c=["sm:","md:","lg:","xl:","2xl:"],d=[/bg-\w+-\d+/,/text-\w+-\d+/,/border-\w+-\d+/];for(const f of e){l.push(f),f.startsWith("dark:")&&n.push(f);for(const h of c)if(f.startsWith(h)){r.push(f);break}for(const h of d)if(h.test(f)){i.push(f);break}}return{usedClasses:l,darkClasses:n,responsiveClasses:r,dynamicClasses:i}}detectStyleIssues(e){const{componentId:n,componentName:r,computedStyles:i,tailwindAnalysis:l}=e;(i.opacity==="0"||i.visibility==="hidden"||i.display==="none"||i.width==="0px"&&i.height==="0px")&&this.addStyleIssue({id:this.generateId(),type:"invisible-element",componentId:n,componentName:r,property:i.opacity==="0"?"opacity":i.visibility==="hidden"?"visibility":i.display==="none"?"display":"width/height",currentValue:i.opacity==="0"?"0":i.visibility==="hidden"?"hidden":i.display==="none"?"none":"0px",severity:"warning",description:"요소가 화면에 보이지 않습니다.",suggestion:"opacity, visibility, display, width/height 값을 확인하세요."});const c=l.usedClasses.filter(f=>f.startsWith("bg-")&&!f.startsWith("dark:")),d=l.darkClasses.filter(f=>f.includes("bg-"));c.length>0&&d.length===0&&this.addStyleIssue({id:this.generateId(),type:"dark-mode-missing",componentId:n,componentName:r,property:"background-color",currentValue:c.join(", "),expectedValue:"dark:bg-* 클래스 필요",severity:"info",description:"다크 모드 배경색 클래스가 누락되었습니다.",suggestion:`${c[0]}에 대응하는 dark: 클래스를 추가하세요.`}),l.dynamicClasses.length>0&&this.addStyleIssue({id:this.generateId(),type:"tailwind-purging",componentId:n,componentName:r,property:"class",currentValue:l.dynamicClasses.join(", "),severity:"info",description:"동적으로 생성된 Tailwind 클래스가 있습니다.",suggestion:"tailwind.config.js의 safelist에 추가하거나 전체 클래스명을 상수로 정의하세요."})}addStyleIssue(e){this.styleIssues.some(r=>r.componentId===e.componentId&&r.type===e.type&&r.property===e.property)||this.styleIssues.push(e)}getStyleValidationInfo(){const e=Array.from(this.componentStyles.values());return{issues:[...this.styleIssues],componentStyles:e,stats:{totalComponents:e.length,invisibleCount:this.styleIssues.filter(n=>n.type==="invisible-element").length,tailwindIssueCount:this.styleIssues.filter(n=>n.type==="tailwind-purging").length,darkModeIssueCount:this.styleIssues.filter(n=>n.type==="dark-mode-missing").length},timestamp:Date.now()}}clearStyleValidationData(){this.styleIssues=[],this.componentStyles.clear()}trackAuthEvent(e,n,r,i){if(!this.config.enabled)return;const l={id:`auth_${++this.authEventIdCounter}`,type:e,timestamp:Date.now(),success:n,error:r,details:i};this.authEvents.push(l),this.authEvents.length>this.maxAuthEventHistory&&(this.authEvents=this.authEvents.slice(-this.maxAuthEventHistory))}trackAuthHeader(e,n,r,i,l){if(!this.config.enabled)return;const c={url:e,hasAuthHeader:n,headerType:r,tokenValid:i??!1,responseStatus:l,timestamp:Date.now()};this.authHeaderHistory.push(c),this.authHeaderHistory.length>this.maxAuthEventHistory&&(this.authHeaderHistory=this.authHeaderHistory.slice(-this.maxAuthEventHistory)),l===401&&this.trackAuthEvent("api-unauthorized",!1,`401 Unauthorized: ${e}`,{url:e})}getAuthState(){try{const e=window.G7Core,n=e?.api;let r=e?.auth;!r&&e?.AuthManager?.getInstance&&(r=e.AuthManager.getInstance());const i=n?.getToken?.()||r?.getAccessToken?.()||localStorage.getItem("auth_token")||localStorage.getItem("access_token"),l=r?.getRefreshToken?.()||localStorage.getItem("refresh_token");let c="unknown",d=r?.getUser?.();if(d&&(c="AuthManager"),!d){const S=e?.state?.get?.();d=S?._global?.user||S?._global?.currentUser||S?.user||S?.currentUser,d&&(c="G7Core.state")}const f=r?.isAuthenticated?.()??!1;let h="memory";localStorage.getItem("auth_token")||localStorage.getItem("access_token")?h="localStorage":(sessionStorage.getItem("auth_token")||sessionStorage.getItem("access_token"))&&(h="sessionStorage");const m=!!i&&(!!d||f);let y;i&&typeof i=="string"&&i.length>20?y=`${i.substring(0,10)}...${i.substring(i.length-6)}`:i&&(y="***");const v=d?{id:d.id,email:d.email,name:d.name,roles:d.roles?.map(S=>typeof S=="string"?{name:S}:{id:S.id,name:S.name,guard_name:S.guard_name}),permissions:d.permissions?.map(S=>typeof S=="string"?{name:S}:{id:S.id,name:S.name,guard_name:S.guard_name}),avatar:d.avatar||d.profile_photo_url||d.profile_image,created_at:d.created_at,updated_at:d.updated_at,last_login_at:d.last_login_at,email_verified_at:d.email_verified_at,...d.phone&&{phone:d.phone},...d.nickname&&{nickname:d.nickname},...d.status&&{status:d.status},...d.locale&&{locale:d.locale}}:void 0;return{isAuthenticated:m,user:v,tokens:{hasAccessToken:!!i,hasRefreshToken:!!l,storage:h,accessTokenPreview:y},lastActivity:Date.now(),source:c}}catch{return{isAuthenticated:!1,tokens:{hasAccessToken:!1,hasRefreshToken:!1,storage:"memory"},source:"unknown"}}}getAuthDebugInfo(){const e=[...this.authEvents];return{state:this.getAuthState(),events:e,headerAnalysis:[...this.authHeaderHistory],stats:{loginAttempts:e.filter(n=>n.type==="login").length,successfulLogins:e.filter(n=>n.type==="login"&&n.success).length,failedLogins:e.filter(n=>n.type==="login"&&!n.success).length,tokenRefreshes:e.filter(n=>n.type==="token-refresh").length,unauthorizedResponses:e.filter(n=>n.type==="api-unauthorized").length},timestamp:Date.now()}}clearAuthData(){this.authEvents=[],this.authHeaderHistory=[],this.authEventIdCounter=0}trackLayoutLoad(e,n,r,i="api"){if(!this.config.enabled)return;const l=Date.now();this.currentLayout={layoutPath:e,templateId:n,layoutJson:this.sanitizeObject(r),loadedAt:l,version:r.version,layoutName:r.layout_name,source:i};const c={id:`layout-${++this.layoutIdCounter}`,layoutPath:e,templateId:n,loadedAt:l,source:i,version:r.version};this.layoutHistory.push(c),this.layoutHistory.length>this.maxLayoutHistory&&this.layoutHistory.shift(),this.layoutStats.totalLoads++,i==="cache"?this.layoutStats.cacheHits++:this.layoutStats.apiLoads++}getCurrentLayout(){return this.currentLayout}getLayoutHistory(){return[...this.layoutHistory]}getLayoutDebugInfo(){return{current:this.currentLayout,history:[...this.layoutHistory],stats:{...this.layoutStats}}}clearLayoutData(){this.currentLayout=null,this.layoutHistory=[],this.layoutIdCounter=0,this.layoutStats={totalLoads:0,cacheHits:0,apiLoads:0}}startHandlerExecution(e){if(!this.config.enabled)return"";const n=`exec_${++this.executionIdCounter}_${Date.now()}`,r={handlerName:e,executionId:n,startTime:Date.now(),stateChanges:[],dataSourceChanges:[],alerts:[]};if(this.executionDetails.set(n,r),this.executionDetails.size>this.maxExecutionDetails){const i=Array.from(this.executionDetails.keys());i.slice(0,i.length-this.maxExecutionDetails).forEach(c=>this.executionDetails.delete(c))}return n}endHandlerExecution(e,n,r,i){if(!this.config.enabled||!e)return;const l=this.executionDetails.get(e);if(!l)return;l.endTime=Date.now(),l.duration=l.endTime-l.startTime,l.exitReason=n,l.exitLocation=r,l.exitDescription=i;const c=this.changeExpectations.get(e);c&&(l.expectedChanges=c,this.checkExpectations(e,l,c),this.changeExpectations.delete(e)),n==="normal"&&l.stateChanges.length===0&&l.dataSourceChanges.length===0&&this.addChangeAlert(e,{type:"no-state-change",severity:"warning",message:`핸들러 "${l.handlerName}"이(가) 상태 변경 없이 완료됨`,description:"핸들러가 성공적으로 완료되었지만 상태나 데이터소스 변경이 없습니다. 의도한 동작인지 확인하세요.",handlerName:l.handlerName,suggestion:'핸들러 내부 조건문을 확인하거나 DevTools의 "변경감지" 탭에서 exitLocation을 확인하세요.',docLink:"troubleshooting-state.md"}),(n==="early-return-condition"||n==="early-return-validation")&&this.addChangeAlert(e,{type:"early-return-detected",severity:"info",message:`핸들러 "${l.handlerName}"이(가) early return으로 종료됨`,description:i||`종료 위치: ${r||"알 수 없음"}`,handlerName:l.handlerName,suggestion:n==="early-return-validation"?"검증 실패로 인한 early return입니다. 입력 데이터를 확인하세요.":"조건부 early return입니다. 조건문의 평가 결과를 확인하세요."})}recordExitReason(e,n,r,i){if(!this.config.enabled||!e)return;const l=this.executionDetails.get(e);l&&(l.exitReason=n,l.exitLocation=r,l.exitDescription=i)}recordStateChange(e,n,r,i,l){if(!this.config.enabled)return;const c={id:`sc_${++this.stateChangeIdCounter}`,path:n,changeType:r,oldValue:this.safeClone(i),newValue:this.safeClone(l),timestamp:Date.now(),comparison:this.compareValues(i,l),executionId:e};if(this.stateChangeHistory.push(c),e){const d=this.executionDetails.get(e);d&&d.stateChanges.push(c)}this.stateChangeHistory.length>this.maxStateChangeHistory&&this.stateChangeHistory.splice(0,this.stateChangeHistory.length-this.maxStateChangeHistory),c.comparison.isDeepEqual&&typeof i=="object"&&i!==null&&this.addChangeAlert(e||"",{type:"object-reference-same",severity:"warning",message:`상태 "${n}"가 변경되었으나 값이 동일함`,description:"객체 참조가 동일하거나 깊은 비교 결과 변경이 없습니다. 변경 감지가 실패할 수 있습니다.",handlerName:e&&this.executionDetails.get(e)?.handlerName||"unknown",statePath:n,suggestion:"불변성을 유지하여 새 객체를 생성하세요. 예: { ...oldObject, newField: value }",docLink:"troubleshooting-state.md#object-mutation"})}recordDataSourceChange(e,n,r,i,l){if(!this.config.enabled)return;const c={dataSourceId:n,changeType:r,timestamp:Date.now(),previousStatus:i,newStatus:l,executionId:e};if(this.dataSourceChangeHistory.push(c),e){const d=this.executionDetails.get(e);d&&d.dataSourceChanges.push(c)}this.dataSourceChangeHistory.length>this.maxStateChangeHistory&&this.dataSourceChangeHistory.splice(0,this.dataSourceChangeHistory.length-this.maxStateChangeHistory)}expectChange(e,n,r){if(!this.config.enabled||!e)return;const i=this.executionDetails.get(e);if(!i)return;const l={expectedStatePaths:n,expectedDataSources:r,setAt:Date.now(),source:i.handlerName};this.changeExpectations.set(e,l)}checkExpectations(e,n,r){const i=new Set(n.stateChanges.map(c=>c.path)),l=new Set(n.dataSourceChanges.map(c=>c.dataSourceId));for(const c of r.expectedStatePaths)i.has(c)||this.addChangeAlert(e,{type:"expected-not-fulfilled",severity:"error",message:`기대한 상태 변경 "${c}"가 발생하지 않음`,description:`핸들러 "${n.handlerName}"이(가) "${c}" 변경을 예고했지만 실제로 변경되지 않았습니다.`,handlerName:n.handlerName,statePath:c,suggestion:"핸들러 내부 로직을 확인하세요. early return이나 조건부 분기로 인해 변경이 생략되었을 수 있습니다.",docLink:"troubleshooting-state.md"});for(const c of r.expectedDataSources)l.has(c)||this.addChangeAlert(e,{type:"expected-not-fulfilled",severity:"error",message:`기대한 데이터소스 갱신 "${c}"가 발생하지 않음`,description:`핸들러 "${n.handlerName}"이(가) "${c}" 갱신을 예고했지만 실제로 갱신되지 않았습니다.`,handlerName:n.handlerName,dataSourceId:c,suggestion:"refetchDataSource 호출이 실행되었는지 확인하세요.",docLink:"data-sources.md"})}addChangeAlert(e,n){const r={...n,id:`alert_${++this.alertIdCounter}`,timestamp:Date.now()};if(this.changeAlerts.push(r),e){const i=this.executionDetails.get(e);i&&i.alerts.push(r)}this.changeAlerts.length>this.maxChangeAlerts&&this.changeAlerts.splice(0,this.changeAlerts.length-this.maxChangeAlerts)}compareValues(e,n){const r=this.getValueType(e),i=this.getValueType(n),l=r!==i;let c=!1,d;return!l&&(r==="object"||r==="array")?(c=this.deepEqual(e,n),!c&&r==="object"&&(d=this.getChangedKeys(e,n))):c=e===n,{typeChanged:l,oldType:r,newType:i,isDeepEqual:c,changedKeys:d}}getValueType(e){return e===null?"null":e===void 0?"undefined":Array.isArray(e)?"array":typeof e}deepEqual(e,n){if(e===n)return!0;if(typeof e!=typeof n)return!1;if(e===null||n===null||typeof e!="object")return e===n;if(Array.isArray(e)!==Array.isArray(n))return!1;const r=Object.keys(e),i=Object.keys(n);if(r.length!==i.length)return!1;for(const l of r)if(!i.includes(l)||!this.deepEqual(e[l],n[l]))return!1;return!0}getChangedKeys(e,n){const r=[],i=new Set([...Object.keys(e),...Object.keys(n)]);for(const l of i)this.deepEqual(e[l],n[l])||r.push(l);return r}getChangeDetectionInfo(){const e=Array.from(this.executionDetails.values()),n={totalExecutions:e.length,executionsWithStateChange:e.filter(r=>r.stateChanges.length>0).length,executionsWithoutStateChange:e.filter(r=>r.stateChanges.length===0&&r.exitReason==="normal").length,earlyReturnCount:e.filter(r=>r.exitReason==="early-return-condition"||r.exitReason==="early-return-validation").length,alertCount:this.changeAlerts.length,alertsByType:this.getAlertsByType()};return{executionDetails:e,stateChangeHistory:[...this.stateChangeHistory],dataSourceChangeHistory:[...this.dataSourceChangeHistory],alerts:[...this.changeAlerts],stats:n,timestamp:Date.now()}}getAlertsByType(){const e={"no-state-change":0,"no-datasource-change":0,"expected-not-fulfilled":0,"object-reference-same":0,"early-return-detected":0,"async-timing-issue":0};for(const n of this.changeAlerts)e[n.type]++;return e}clearChangeDetectionData(){this.executionDetails.clear(),this.stateChangeHistory=[],this.dataSourceChangeHistory=[],this.changeAlerts=[],this.changeExpectations.clear(),this.executionIdCounter=0,this.stateChangeIdCounter=0,this.alertIdCounter=0}getRecentAlerts(e=10,n){let r=[...this.changeAlerts];return n&&(r=r.filter(i=>i.severity===n)),r.slice(-e)}getHandlerExecutions(e,n=10){return Array.from(this.executionDetails.values()).filter(i=>i.handlerName===e).slice(-n)}findExecutionInStack(e){return this.sequenceExecutionStack.find(n=>n.sequenceId===e)}startSequenceExecution(e){if(!this.config.enabled)return"";const n=`seq_${++this.sequenceIdCounter}_${Date.now()}`,r={sequenceId:n,startTime:Date.now(),totalDuration:0,trigger:e,actions:[],status:"running"};return this.sequenceExecutionStack.push(r),n}captureSequenceActionBefore(e,n,r,i,l){if(!this.config.enabled)return;const c=this.findExecutionInStack(e);if(!c)return;const d={index:n,handler:r,params:this.safeClone(i),stateBeforeAction:{_global:this.safeClone(l._global),_local:this.safeClone(l._local),_isolated:l._isolated?this.safeClone(l._isolated):void 0},stateAfterAction:{_global:{},_local:{}},duration:0,status:"success"};c.actions.push(d)}captureSequenceActionAfter(e,n,r,i,l,c){if(!this.config.enabled)return;const d=this.findExecutionInStack(e);if(!d)return;const f=d.actions.find(m=>m.index===n);if(!f)return;f.stateAfterAction={_global:this.safeClone(r._global),_local:this.safeClone(r._local),_isolated:r._isolated?this.safeClone(r._isolated):void 0},f.duration=i,f.result=this.safeClone(l),f.stateDiff={global:this.computeStateDiff(f.stateBeforeAction._global,f.stateAfterAction._global),local:this.computeStateDiff(f.stateBeforeAction._local,f.stateAfterAction._local)},f.stateBeforeAction._isolated&&f.stateAfterAction._isolated&&(f.stateDiff.isolated=this.computeStateDiff(f.stateBeforeAction._isolated,f.stateAfterAction._isolated));const h=window.__g7PendingLocalState;h&&(f.pendingState=this.safeClone(h)),c&&(f.status="error",f.error={name:c.name,message:c.message,stack:c.stack})}endSequenceExecution(e,n){if(!this.config.enabled)return;const r=this.sequenceExecutionStack.findIndex(l=>l.sequenceId===e);if(r===-1)return;const i=this.sequenceExecutionStack[r];if(i.endTime=Date.now(),i.totalDuration=i.endTime-i.startTime,n){i.status="error",i.error={name:n.name,message:n.message,stack:n.stack};const l=i.actions.find(c=>c.status==="error");l&&(i.failedAtIndex=l.index)}else i.status="success";this.sequenceExecutions.push(i),this.sequenceExecutions.length>this.maxSequenceExecutions&&this.sequenceExecutions.shift(),this.sequenceExecutionStack.splice(r,1)}computeStateDiff(e,n){const r=[],i=[],l=[],c=new Set(Object.keys(e)),d=new Set(Object.keys(n));for(const f of d)c.has(f)||r.push(f);for(const f of c)d.has(f)||i.push(f);for(const f of c)if(d.has(f)){const h=e[f],m=n[f];JSON.stringify(h)!==JSON.stringify(m)&&l.push({path:f,oldValue:h,newValue:m})}return{added:r,removed:i,changed:l}}getSequenceTrackingInfo(){const e=this.getSequenceStats(),n=this.sequenceExecutionStack.length>0?this.sequenceExecutionStack[this.sequenceExecutionStack.length-1]:void 0;return{executions:[...this.sequenceExecutions],currentExecution:n,stats:e}}getSequenceStats(){const e=this.sequenceExecutions,n=e.length,r=e.filter(h=>h.status==="success").length,i=e.filter(h=>h.status==="error").length,l=e.reduce((h,m)=>h+m.actions.length,0),c=n>0?e.reduce((h,m)=>h+m.totalDuration,0)/n:0,d={};for(const h of e)for(const m of h.actions)d[m.handler]=(d[m.handler]||0)+1;const f=Object.entries(d).sort(([,h],[,m])=>m-h).slice(0,5).map(([h,m])=>({handler:h,count:m}));return{totalExecutions:n,successCount:r,errorCount:i,totalActions:l,avgDuration:c,topHandlers:f}}clearSequenceData(){this.sequenceExecutions=[],this.sequenceExecutionStack=[],this.sequenceIdCounter=0}getSequenceExecution(e){const n=this.findExecutionInStack(e);return n||this.sequenceExecutions.find(r=>r.sequenceId===e)}getRecentSequences(e=10){return this.sequenceExecutions.slice(-e)}registerStateCaptureForHandler(e,n,r){if(!this.config.enabled)return;const i=new Map,l=Date.now();for(const c of n)i.set(c,{value:this.safeClone(r[c]),capturedAt:l});this.stateCaptureRegistry.set(e,i)}detectStaleClosure(e,n,r,i,l){if(!this.config.enabled)return[];const c=this.stateCaptureRegistry.get(e);if(!c)return[];const d=Date.now(),f=[];for(const[h,m]of c){const y=this.getValueByPath(r,h),v=d-m.capturedAt;if(!this.isDeepEqual(m.value,y)&&v>0){const S=this.createStaleClosureWarning(i,n,h,m.value,m.capturedAt,y,d,v,l);f.push(S),this.addStaleClosureWarning(S)}}return this.stateCaptureRegistry.delete(e),f}trackStaleClosureWarning(e){if(!this.config.enabled)return;const n=Date.now(),r=this.createStaleClosureWarning(e.type,e.location,e.capturedPath,e.capturedValue,e.capturedAt,e.currentValue,n,n-e.capturedAt,e.actionId,e.stackTrace);this.addStaleClosureWarning(r)}createStaleClosureWarning(e,n,r,i,l,c,d,f,h,m){const y=this.getStaleClosureSeverity(e,f),{description:v,suggestion:S,docLink:_}=this.getStaleClosureMessages(e,r,f);return{id:`stale_${++this.staleClosureIdCounter}_${Date.now()}`,timestamp:Date.now(),type:e,location:n,capturedState:{path:r,capturedValue:this.safeClone(i),capturedAt:l},currentState:{path:r,currentValue:this.safeClone(c),retrievedAt:d},timeDiff:f,severity:y,description:v,suggestion:S,docLink:_,actionId:h,stackTrace:m}}getStaleClosureSeverity(e,n){if(n>5e3)return"error";if(n>1e3)return"warning";switch(e){case"async-state-capture":case"sequence-state-mismatch":return"warning";case"callback-state-capture":case"timeout-state-capture":return"info";case"event-handler-stale":return"warning";default:return"info"}}getStaleClosureMessages(e,n,r){const i=r<1e3?`${r}ms`:`${(r/1e3).toFixed(1)}s`;switch(e){case"async-state-capture":return{description:`await 후 ${i} 경과 시점에 '${n}' 상태가 변경됨. 캡처된 상태 대신 최신 상태를 사용해야 함`,suggestion:"await 이후에는 G7Core.state.get()으로 최신 상태를 다시 조회하거나, useRef + getter 패턴을 사용하세요",docLink:"troubleshooting-state-closure.md"};case"callback-state-capture":return{description:`콜백에서 ${i} 전에 캡처된 '${n}' 상태를 사용 중. 최신 상태와 다름`,suggestion:"콜백에서 상태를 참조할 때는 stateRef.current 패턴 또는 G7Core.state.get()를 사용하세요",docLink:"troubleshooting-state-closure.md"};case"timeout-state-capture":return{description:`setTimeout/setInterval 콜백에서 '${n}' 상태가 ${i} 전 값 사용 중`,suggestion:"타이머 콜백 내에서는 G7Core.state.get()으로 최신 상태를 조회하세요",docLink:"troubleshooting-state-closure.md"};case"sequence-state-mismatch":return{description:`sequence 내 '${n}' 상태가 이전 액션 결과와 불일치. context.state 대신 캡처된 값 사용 의심`,suggestion:"sequence 내에서는 context.state 또는 $prev를 사용하여 최신 상태를 참조하세요",docLink:"troubleshooting-state-setstate.md"};case"event-handler-stale":return{description:`이벤트 핸들러에서 ${i} 전에 바인딩된 '${n}' 상태 사용 중`,suggestion:"이벤트 핸들러에서 상태를 참조할 때는 useRef 패턴 또는 G7Core.state.get()를 사용하세요",docLink:"troubleshooting-state-closure.md"};default:return{description:`'${n}' 상태에서 stale closure 감지됨 (${i} 경과)`,suggestion:"상태 참조 시 최신 값을 사용하는지 확인하세요"}}}addStaleClosureWarning(e){this.staleClosureWarnings.push(e),this.staleClosureWarnings.length>this.maxStaleClosureWarnings&&this.staleClosureWarnings.shift()}getValueByPath(e,n){const r=n.split(".");let i=e;for(const l of r){if(i==null)return;i=i[l]}return i}isDeepEqual(e,n){if(e===n)return!0;if(e===null||n===null)return e===n;if(typeof e!=typeof n)return!1;if(typeof e!="object")return e===n;try{return JSON.stringify(e)===JSON.stringify(n)}catch{return!1}}getStaleClosureTrackingInfo(){const e=this.getStaleClosureStats();return{warnings:[...this.staleClosureWarnings],stats:e,timestamp:Date.now()}}getStaleClosureStats(){const e=this.staleClosureWarnings,n={"async-state-capture":0,"callback-state-capture":0,"timeout-state-capture":0,"sequence-state-mismatch":0,"event-handler-stale":0};for(const f of e)n[f.type]++;const r={info:0,warning:0,error:0};for(const f of e)r[f.severity]++;const i={};for(const f of e)i[f.location]=(i[f.location]||0)+1;const l=Object.entries(i).sort(([,f],[,h])=>h-f).slice(0,5).map(([f,h])=>({location:f,count:h})),c={};for(const f of e)c[f.capturedState.path]=(c[f.capturedState.path]||0)+1;const d=Object.entries(c).sort(([,f],[,h])=>h-f).slice(0,5).map(([f,h])=>({path:f,count:h}));return{totalWarnings:e.length,warningsByType:n,warningsBySeverity:r,topLocations:l,topAffectedPaths:d}}clearStaleClosureData(){this.staleClosureWarnings=[],this.staleClosureIdCounter=0,this.stateCaptureRegistry.clear()}getRecentStaleClosureWarnings(e=10,n){let r=[...this.staleClosureWarnings];return n&&(r=r.filter(i=>i.severity===n)),r.slice(-e)}safeClone(e){if(e==null||typeof e!="object")return e;try{return JSON.parse(JSON.stringify(e))}catch{if(Array.isArray(e))return e.map(r=>{try{return JSON.parse(JSON.stringify(r))}catch{return"[Unserializable]"}});const n={};for(const r of Object.keys(e))try{n[r]=JSON.parse(JSON.stringify(e[r]))}catch{n[r]="[Unserializable]"}return n}}};M($i,"instance");let Ha=$i;const Qn=pt("LayoutLoader");class Dr extends Error{constructor(e,n,r){super(e),this.code=n,this.details=r,this.name="LayoutLoaderError"}}class th{constructor(e){M(this,"componentRegistry");M(this,"currentLayout",null);M(this,"layoutCache",new Map);M(this,"cacheVersion",0);this.componentRegistry=e}setCacheVersion(e){this.cacheVersion!==e&&(Qn.log("Cache version updated:",this.cacheVersion,"->",e),this.cacheVersion=e,this.layoutCache.clear())}getCacheVersion(){return this.cacheVersion}async loadLayout(e,n){const r=`${e}:${n}`;if(this.layoutCache.has(r)){Qn.log("Loading layout from cache:",n);const l=await this.layoutCache.get(r),c=JSON.parse(JSON.stringify(l));return this.currentLayout=c,Ha.getInstance().trackLayoutLoad(n,e,c,"cache"),c}const i=this.fetchLayout(e,n);return this.layoutCache.set(r,i),i.catch(()=>{this.layoutCache.get(r)===i&&this.layoutCache.delete(r)}),i}async fetchLayout(e,n,r=!1){try{const i=this.cacheVersion>0?this.cacheVersion:null;let l;if(n.startsWith("__preview__/")){const v=n.replace("__preview__/","");l=Br(`/api/layouts/preview/${v}`,"json",i)}else l=Br(`/api/layouts/${e}/${n}`,"json",i);Qn.log("Fetching layout from API:",l);const c=Yn(),d=r?null:c.getToken(),f={Accept:"application/json"};d&&(f.Authorization=`Bearer ${d}`);const h=await tc(l,{init:{headers:f},label:`layout: ${n}`});let m;try{m=await h.json()}catch{m=null}if(!h.ok){if(h.status===401&&!r&&d){Qn.log("Token invalid, removing and retrying without token"),c.removeToken();try{return await this.fetchLayout(e,n,!0)}catch(S){throw S instanceof Dr&&S.details?.status===401?new Dr(S.message,S.code,{...S.details,hadToken:!0}):S}}const v=m?.message||m?.error;throw new Dr(`Failed to fetch layout: ${h.status} ${h.statusText}`,"FETCH_FAILED",{status:h.status,statusText:h.statusText,url:l,apiMessage:v})}const y=m.data||m;return this.validateLayoutData(y),this.currentLayout=y,Ha.getInstance().trackLayoutLoad(n,e,y,"api"),Qn.log("Layout fetched and cached successfully:",y.layout_name),y}catch(i){const l=`${e}:${n}`;throw this.layoutCache.delete(l),i instanceof Dr?i:new Dr("Failed to load layout","LOAD_FAILED",{originalError:i})}}prefetchLayout(e,n){return this.loadLayout(e,n)}validateLayoutData(e){if(!e.version)throw new Dr("Layout data missing required field: version","VALIDATION_FAILED",{field:"version"});if(!e.layout_name)throw new Dr("Layout data missing required field: layout_name","VALIDATION_FAILED",{field:"layout_name"});if(!Array.isArray(e.components))throw new Dr('Layout data field "components" must be an array',"VALIDATION_FAILED",{field:"components",value:e.components});Qn.log("Layout data validation passed")}renderLayout(e,n){try{const r=n||this.currentLayout;if(!r)throw new Dr("No layout data to render","NO_LAYOUT_DATA");Qn.log("Rendering layout:",r.layout_name),e.innerHTML="",this.renderComponents(e,r.components),Qn.log("Layout rendered successfully")}catch(r){Qn.error("Render error:",r),this.renderErrorState(e,r)}}renderComponents(e,n){for(const r of n){const i=this.renderComponent(r);i&&e.appendChild(i)}}renderComponent(e){try{if(!this.componentRegistry.getComponent(e.type))return Qn.warn(`Component not found: ${e.type}`),this.createPlaceholderElement(e.type);const r=document.createElement("div");return r.setAttribute("data-component",e.type),e.props&&Object.entries(e.props).forEach(([i,l])=>{r.setAttribute(`data-prop-${i}`,JSON.stringify(l))}),e.children&&e.children.length>0?this.renderComponents(r,e.children):e.text&&(r.textContent=e.text),r}catch(n){return Qn.error(`Error rendering component ${e.type}:`,n),this.createErrorElement(e.type,n)}}createPlaceholderElement(e){const n=document.createElement("div");return n.className="component-placeholder",n.setAttribute("data-component-type",e),n.innerHTML=`

Component not found: ${e}

@@ -63,7 +103,7 @@ Error generating stack: `+u.message+`

Component Error: ${e}

${n?.message||"Unknown error"}

- `,r}renderErrorState(e,n){const r=n instanceof Error?n.message:String(n),i=n instanceof Rr?n.code:"UNKNOWN_ERROR";e.innerHTML=` + `,r}renderErrorState(e,n){const r=n instanceof Error?n.message:String(n),i=n instanceof Dr?n.code:"UNKNOWN_ERROR";e.innerHTML=`
페이지 새로고침
- `,Xn.error("Error state rendered:",i,r)}getCurrentLayout(){return this.currentLayout}clear(){this.currentLayout=null,this.layoutCache.clear(),Xn.log("Layout data and cache cleared")}}const Rx=pt("TranslationContext"),ih=I.createContext(null),Uc=({children:s,translationEngine:e,translationContext:n})=>{const r=I.useMemo(()=>(l,c)=>{if(c){const d="|"+Object.entries(c).map(([f,h])=>`${f}=${h}`).join("|");return e.translate(l,n,d)}return e.translate(l,n)},[e,n]),i=I.useMemo(()=>({translationEngine:e,translationContext:n,t:r}),[e,n,r]);return yt.jsx(ih.Provider,{value:i,children:s})},gb=()=>{const s=I.useContext(ih);return s||(Rx.warn("TranslationProvider 외부에서 호출되었습니다. 키를 그대로 반환합니다."),{t:e=>e,translationEngine:null,translationContext:null})},Pa=pt("SlotContext"),Dx={registerToSlot:()=>{Pa.warn("SlotContext not available: registerToSlot called outside SlotProvider")},unregisterFromSlot:()=>{Pa.warn("SlotContext not available: unregisterFromSlot called outside SlotProvider")},getSlotComponents:()=>[],subscribeToSlot:()=>()=>{},clearAllSlots:()=>{},isEnabled:!1},Ox=I.createContext(Dx),sh=({children:s})=>{const e=I.useRef(new Map),n=I.useRef(new Map),r=I.useCallback(m=>{const y=n.current.get(m);y&&y.forEach(v=>{try{v()}catch(S){Pa.error(`Slot subscriber error (slotId: ${m}):`,S)}})},[]),i=I.useCallback((m,y,v)=>{if(!m||!y){Pa.warn("registerToSlot: slotId and componentId are required");return}e.current.has(m)||e.current.set(m,new Map);const S=e.current.get(m),_=S.get(y);_&&_.registrationKey===v.registrationKey||(S.set(y,v),Pa.log(`Component registered to slot: ${y} -> ${m} (order: ${v.order})`),r(m))},[r]),l=I.useCallback((m,y)=>{const v=e.current.get(m);v&&v.has(y)&&(v.delete(y),Pa.log(`Component unregistered from slot: ${y} <- ${m}`),v.size===0&&e.current.delete(m),r(m))},[r]),c=I.useCallback(m=>{const y=e.current.get(m);return y?Array.from(y.values()).sort((v,S)=>v.order-S.order):[]},[]),d=I.useCallback((m,y)=>{n.current.has(m)||n.current.set(m,new Set);const v=n.current.get(m);return v.add(y),()=>{v.delete(y),v.size===0&&n.current.delete(m)}},[]),f=I.useCallback(()=>{e.current.forEach((m,y)=>{r(y)}),e.current.clear(),n.current.clear(),Pa.log("All slots cleared")},[r]),h=I.useMemo(()=>({registerToSlot:i,unregisterFromSlot:l,getSlotComponents:c,subscribeToSlot:d,clearAllSlots:f,isEnabled:!0}),[i,l,c,d,f]);return I.useLayoutEffect(()=>{typeof window<"u"&&(window.__slotContextValue=h,Pa.log("SlotContext exposed to window.__slotContextValue"))},[h]),yt.jsx(Ox.Provider,{value:h,children:s})};class oh{constructor(){this.notificationCreatedEvent=".Illuminate\\Notifications\\Events\\BroadcastNotificationCreated"}listenForWhisper(e,n){return this.listen(".client-"+e,n)}notification(e){return this.listen(this.notificationCreatedEvent,e)}stopListeningForNotification(e){return this.stopListening(this.notificationCreatedEvent,e)}stopListeningForWhisper(e,n){return this.stopListening(".client-"+e,n)}}class mb{constructor(e){this.namespace=e}format(e){return[".","\\"].includes(e.charAt(0))?e.substring(1):(this.namespace&&(e=this.namespace+"."+e),e.replace(/\./g,"\\"))}setNamespace(e){this.namespace=e}}function Lx(s){try{return Reflect.construct(String,[],s),!0}catch{return!1}}class lh extends oh{constructor(e,n,r){super(),this.name=n,this.pusher=e,this.options=r,this.eventFormatter=new mb(this.options.namespace),this.subscribe()}subscribe(){this.subscription=this.pusher.subscribe(this.name)}unsubscribe(){this.pusher.unsubscribe(this.name)}listen(e,n){return this.on(this.eventFormatter.format(e),n),this}listenToAll(e){return this.subscription.bind_global((n,r)=>{if(n.startsWith("pusher:"))return;let i=String(this.options.namespace??"").replace(/\./g,"\\"),l=n.startsWith(i)?n.substring(i.length+1):"."+n;e(l,r)}),this}stopListening(e,n){return n?this.subscription.unbind(this.eventFormatter.format(e),n):this.subscription.unbind(this.eventFormatter.format(e)),this}stopListeningToAll(e){return e?this.subscription.unbind_global(e):this.subscription.unbind_global(),this}subscribed(e){return this.on("pusher:subscription_succeeded",()=>{e()}),this}error(e){return this.on("pusher:subscription_error",n=>{e(n)}),this}on(e,n){return this.subscription.bind(e,n),this}}class yb extends lh{whisper(e,n){return this.pusher.channels.channels[this.name].trigger(`client-${e}`,n),this}}class $x extends lh{whisper(e,n){return this.pusher.channels.channels[this.name].trigger(`client-${e}`,n),this}}class Mx extends yb{here(e){return this.on("pusher:subscription_succeeded",n=>{e(Object.keys(n.members).map(r=>n.members[r]))}),this}joining(e){return this.on("pusher:member_added",n=>{e(n.info)}),this}whisper(e,n){return this.pusher.channels.channels[this.name].trigger(`client-${e}`,n),this}leaving(e){return this.on("pusher:member_removed",n=>{e(n.info)}),this}}class bb extends oh{constructor(e,n,r){super(),this.events={},this.listeners={},this.name=n,this.socket=e,this.options=r,this.eventFormatter=new mb(this.options.namespace),this.subscribe()}subscribe(){this.socket.emit("subscribe",{channel:this.name,auth:this.options.auth||{}})}unsubscribe(){this.unbind(),this.socket.emit("unsubscribe",{channel:this.name,auth:this.options.auth||{}})}listen(e,n){return this.on(this.eventFormatter.format(e),n),this}stopListening(e,n){return this.unbindEvent(this.eventFormatter.format(e),n),this}subscribed(e){return this.on("connect",n=>{e(n)}),this}error(e){return this}on(e,n){return this.listeners[e]=this.listeners[e]||[],this.events[e]||(this.events[e]=(r,i)=>{this.name===r&&this.listeners[e]&&this.listeners[e].forEach(l=>l(i))},this.socket.on(e,this.events[e])),this.listeners[e].push(n),this}unbind(){Object.keys(this.events).forEach(e=>{this.unbindEvent(e)})}unbindEvent(e,n){this.listeners[e]=this.listeners[e]||[],n&&(this.listeners[e]=this.listeners[e].filter(r=>r!==n)),(!n||this.listeners[e].length===0)&&(this.events[e]&&(this.socket.removeListener(e,this.events[e]),delete this.events[e]),delete this.listeners[e])}}class vb extends bb{whisper(e,n){return this.socket.emit("client event",{channel:this.name,event:`client-${e}`,data:n}),this}}class Nx extends vb{here(e){return this.on("presence:subscribed",n=>{e(n.map(r=>r.user_info))}),this}joining(e){return this.on("presence:joining",n=>e(n.user_info)),this}whisper(e,n){return this.socket.emit("client event",{channel:this.name,event:`client-${e}`,data:n}),this}leaving(e){return this.on("presence:leaving",n=>e(n.user_info)),this}}class Bc extends oh{subscribe(){}unsubscribe(){}listen(e,n){return this}listenToAll(e){return this}stopListening(e,n){return this}subscribed(e){return this}error(e){return this}on(e,n){return this}}class Sb extends Bc{whisper(e,n){return this}}class Ix extends Bc{whisper(e,n){return this}}class jx extends Sb{here(e){return this}joining(e){return this}whisper(e,n){return this}leaving(e){return this}}const Jc=class Jc{constructor(e){this.setOptions(e),this.connect()}setOptions(e){this.options={...Jc._defaultOptions,...e,broadcaster:e.broadcaster};let n=this.csrfToken();n&&(this.options.auth.headers["X-CSRF-TOKEN"]=n,this.options.userAuthentication.headers["X-CSRF-TOKEN"]=n),n=this.options.bearerToken,n&&(this.options.auth.headers.Authorization="Bearer "+n,this.options.userAuthentication.headers.Authorization="Bearer "+n)}csrfToken(){return typeof window<"u"&&window.Laravel?.csrfToken?window.Laravel.csrfToken:this.options.csrfToken?this.options.csrfToken:typeof document<"u"&&typeof document.querySelector=="function"?document.querySelector('meta[name="csrf-token"]')?.getAttribute("content")??null:null}};Jc._defaultOptions={auth:{headers:{}},authEndpoint:"/broadcasting/auth",userAuthentication:{endpoint:"/broadcasting/user-auth",headers:{}},csrfToken:null,bearerToken:null,host:null,key:null,namespace:"App.Events"};let Wo=Jc;class qc extends Wo{constructor(){super(...arguments),this.channels={}}connect(){if(typeof this.options.client<"u")this.pusher=this.options.client;else if(this.options.Pusher)this.pusher=new this.options.Pusher(this.options.key,this.options);else if(typeof window<"u"&&typeof window.Pusher<"u")this.pusher=new window.Pusher(this.options.key,this.options);else throw new Error("Pusher client not found. Should be globally available or passed via options.client")}signin(){this.pusher.signin()}listen(e,n,r){return this.channel(e).listen(n,r)}channel(e){return this.channels[e]||(this.channels[e]=new lh(this.pusher,e,this.options)),this.channels[e]}privateChannel(e){return this.channels["private-"+e]||(this.channels["private-"+e]=new yb(this.pusher,"private-"+e,this.options)),this.channels["private-"+e]}encryptedPrivateChannel(e){return this.channels["private-encrypted-"+e]||(this.channels["private-encrypted-"+e]=new $x(this.pusher,"private-encrypted-"+e,this.options)),this.channels["private-encrypted-"+e]}presenceChannel(e){return this.channels["presence-"+e]||(this.channels["presence-"+e]=new Mx(this.pusher,"presence-"+e,this.options)),this.channels["presence-"+e]}leave(e){[e,"private-"+e,"private-encrypted-"+e,"presence-"+e].forEach(n=>{this.leaveChannel(n)})}leaveChannel(e){this.channels[e]&&(this.channels[e].unsubscribe(),delete this.channels[e])}socketId(){return this.pusher.connection.socket_id}connectionStatus(){const e=this.pusher.connection.state;switch(e){case"connected":case"connecting":return e;case"failed":case"unavailable":return"failed";default:return"disconnected"}}onConnectionChange(e){const n=()=>{e(this.connectionStatus())},r=["state_change","connected","disconnected"];return r.forEach(i=>{this.pusher.connection.bind(i,n)}),()=>{r.forEach(i=>{this.pusher.connection.unbind(i,n)})}}disconnect(){this.pusher.disconnect()}}class Hx extends Wo{constructor(){super(...arguments),this.channels={}}connect(){const e=this.getSocketIO();this.socket=e(this.options.host??void 0,this.options),this.socket.io.on("reconnect",()=>{Object.values(this.channels).forEach(n=>{n.subscribe()})})}getSocketIO(){if(typeof this.options.client<"u")return this.options.client;if(typeof window<"u"&&typeof window.io<"u")return window.io;throw new Error("Socket.io client not found. Should be globally available or passed via options.client")}listen(e,n,r){return this.channel(e).listen(n,r)}channel(e){return this.channels[e]||(this.channels[e]=new bb(this.socket,e,this.options)),this.channels[e]}privateChannel(e){return this.channels["private-"+e]||(this.channels["private-"+e]=new vb(this.socket,"private-"+e,this.options)),this.channels["private-"+e]}presenceChannel(e){return this.channels["presence-"+e]||(this.channels["presence-"+e]=new Nx(this.socket,"presence-"+e,this.options)),this.channels["presence-"+e]}leave(e){[e,"private-"+e,"presence-"+e].forEach(n=>{this.leaveChannel(n)})}leaveChannel(e){this.channels[e]&&(this.channels[e].unsubscribe(),delete this.channels[e])}socketId(){return this.socket.id}connectionStatus(){return this.socket.connected?"connected":this.socket.io._reconnecting?"reconnecting":this.socket.id!==void 0?"disconnected":"connecting"}onConnectionChange(e){const n=()=>{e(this.connectionStatus())},r=["connect","disconnect","connect_error","reconnect_attempt","reconnect","reconnect_error","reconnect_failed"];return r.forEach(i=>{this.socket.on(i,n)}),()=>{r.forEach(i=>{this.socket.off(i,n)})}}disconnect(){this.socket.disconnect()}}class wb extends Wo{constructor(){super(...arguments),this.channels={}}connect(){}listen(e,n,r){return new Bc}channel(e){return new Bc}privateChannel(e){return new Sb}encryptedPrivateChannel(e){return new Ix}presenceChannel(e){return new jx}leave(e){}leaveChannel(e){}socketId(){return"fake-socket-id"}connectionStatus(){return"connected"}onConnectionChange(e){return()=>{}}disconnect(){}}class zx{constructor(e){this.options=e,this.connect(),this.options.withoutInterceptors||this.registerInterceptors()}channel(e){return this.connector.channel(e)}connect(){if(this.options.broadcaster==="reverb")this.connector=new qc({...this.options,cluster:""});else if(this.options.broadcaster==="pusher")this.connector=new qc(this.options);else if(this.options.broadcaster==="ably")this.connector=new qc({...this.options,cluster:"",broadcaster:"pusher"});else if(this.options.broadcaster==="socket.io")this.connector=new Hx(this.options);else if(this.options.broadcaster==="null")this.connector=new wb(this.options);else if(typeof this.options.broadcaster=="function"&&Lx(this.options.broadcaster))this.connector=new this.options.broadcaster(this.options);else throw new Error(`Broadcaster ${typeof this.options.broadcaster} ${String(this.options.broadcaster)} is not supported.`)}disconnect(){this.connector.disconnect()}join(e){return this.connector.presenceChannel(e)}leave(e){this.connector.leave(e)}leaveChannel(e){this.connector.leaveChannel(e)}leaveAllChannels(){for(const e in this.connector.channels)this.leaveChannel(e)}listen(e,n,r){return this.connector.listen(e,n,r)}private(e){return this.connector.privateChannel(e)}encryptedPrivate(e){if(this.connectorSupportsEncryptedPrivateChannels(this.connector))return this.connector.encryptedPrivateChannel(e);throw new Error(`Broadcaster ${typeof this.options.broadcaster} ${String(this.options.broadcaster)} does not support encrypted private channels.`)}connectorSupportsEncryptedPrivateChannels(e){return e instanceof qc||e instanceof wb}socketId(){return this.connector.socketId()}connectionStatus(){return this.connector.connectionStatus()}registerInterceptors(){typeof Vue<"u"&&Vue?.http&&this.registerVueRequestInterceptor(),typeof axios=="function"&&this.registerAxiosRequestInterceptor(),typeof jQuery=="function"&&this.registerjQueryAjaxSetup(),typeof Turbo=="object"&&this.registerTurboRequestInterceptor()}registerVueRequestInterceptor(){Vue.http.interceptors.push((e,n)=>{this.socketId()&&e.headers.set("X-Socket-ID",this.socketId()),n()})}registerAxiosRequestInterceptor(){axios.interceptors.request.use(e=>(this.socketId()&&(e.headers["X-Socket-Id"]=this.socketId()),e))}registerjQueryAjaxSetup(){typeof jQuery.ajax<"u"&&jQuery.ajaxPrefilter((e,n,r)=>{this.socketId()&&r.setRequestHeader("X-Socket-Id",this.socketId())})}registerTurboRequestInterceptor(){document.addEventListener("turbo:before-fetch-request",e=>{e.detail.fetchOptions.headers["X-Socket-Id"]=this.socketId()})}}var ch={exports:{}};var Cb;function Px(){return Cb||(Cb=1,(function(s,e){(function(r,i){s.exports=i()})(self,()=>(()=>{var n={594(c,d){var f=this&&this.__extends||(function(){var O=function(k,L){return O=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(B,q){B.__proto__=q}||function(B,q){for(var z in q)q.hasOwnProperty(z)&&(B[z]=q[z])},O(k,L)};return function(k,L){O(k,L);function B(){this.constructor=k}k.prototype=L===null?Object.create(L):(B.prototype=L.prototype,new B)}})();Object.defineProperty(d,"__esModule",{value:!0});var h=256,m=(function(){function O(k){k===void 0&&(k="="),this._paddingCharacter=k}return O.prototype.encodedLength=function(k){return this._paddingCharacter?(k+2)/3*4|0:(k*8+5)/6|0},O.prototype.encode=function(k){for(var L="",B=0;B>>18&63),L+=this._encodeByte(q>>>12&63),L+=this._encodeByte(q>>>6&63),L+=this._encodeByte(q>>>0&63)}var z=k.length-B;if(z>0){var q=k[B]<<16|(z===2?k[B+1]<<8:0);L+=this._encodeByte(q>>>18&63),L+=this._encodeByte(q>>>12&63),z===2?L+=this._encodeByte(q>>>6&63):L+=this._paddingCharacter||"",L+=this._paddingCharacter||""}return L},O.prototype.maxDecodedLength=function(k){return this._paddingCharacter?k/4*3|0:(k*6+7)/8|0},O.prototype.decodedLength=function(k){return this.maxDecodedLength(k.length-this._getPaddingLength(k))},O.prototype.decode=function(k){if(k.length===0)return new Uint8Array(0);for(var L=this._getPaddingLength(k),B=k.length-L,q=new Uint8Array(this.maxDecodedLength(B)),z=0,W=0,de=0,_e=0,Se=0,Ie=0,Fe=0;W>>4,q[z++]=Se<<4|Ie>>>2,q[z++]=Ie<<6|Fe,de|=_e&h,de|=Se&h,de|=Ie&h,de|=Fe&h;if(W>>4,de|=_e&h,de|=Se&h),W>>2,de|=Ie&h),W>>8&6,L+=51-k>>>8&-75,L+=61-k>>>8&-15,L+=62-k>>>8&3,String.fromCharCode(L)},O.prototype._decodeChar=function(k){var L=h;return L+=(42-k&k-44)>>>8&-h+k-43+62,L+=(46-k&k-48)>>>8&-h+k-47+63,L+=(47-k&k-58)>>>8&-h+k-48+52,L+=(64-k&k-91)>>>8&-h+k-65+0,L+=(96-k&k-123)>>>8&-h+k-97+26,L},O.prototype._getPaddingLength=function(k){var L=0;if(this._paddingCharacter){for(var B=k.length-1;B>=0&&k[B]===this._paddingCharacter;B--)L++;if(k.length<4||L>2)throw new Error("Base64Coder: incorrect padding")}return L},O})();d.Coder=m;var y=new m;function v(O){return y.encode(O)}d.encode=v;function S(O){return y.decode(O)}d.decode=S;var _=(function(O){f(k,O);function k(){return O!==null&&O.apply(this,arguments)||this}return k.prototype._encodeByte=function(L){var B=L;return B+=65,B+=25-L>>>8&6,B+=51-L>>>8&-75,B+=61-L>>>8&-13,B+=62-L>>>8&49,String.fromCharCode(B)},k.prototype._decodeChar=function(L){var B=h;return B+=(44-L&L-46)>>>8&-h+L-45+62,B+=(94-L&L-96)>>>8&-h+L-95+63,B+=(47-L&L-58)>>>8&-h+L-48+52,B+=(64-L&L-91)>>>8&-h+L-65+0,B+=(96-L&L-123)>>>8&-h+L-97+26,B},k})(m);d.URLSafeCoder=_;var x=new _;function A(O){return x.encode(O)}d.encodeURLSafe=A;function D(O){return x.decode(O)}d.decodeURLSafe=D,d.encodedLength=function(O){return y.encodedLength(O)},d.maxDecodedLength=function(O){return y.maxDecodedLength(O)},d.decodedLength=function(O){return y.decodedLength(O)}},978(c,d){var f="utf8: invalid source encoding";function h(m){for(var y=[],v=0;v=m.length)throw new Error(f);var x=m[++v];if((x&192)!==128)throw new Error(f);S=(S&31)<<6|x&63,_=128}else if(S<240){if(v>=m.length-1)throw new Error(f);var x=m[++v],A=m[++v];if((x&192)!==128||(A&192)!==128)throw new Error(f);S=(S&15)<<12|(x&63)<<6|A&63,_=2048}else if(S<248){if(v>=m.length-2)throw new Error(f);var x=m[++v],A=m[++v],D=m[++v];if((x&192)!==128||(A&192)!==128||(D&192)!==128)throw new Error(f);S=(S&15)<<18|(x&63)<<12|(A&63)<<6|D&63,_=65536}else throw new Error(f);if(S<_||S>=55296&&S<=57343)throw new Error(f);if(S>=65536){if(S>1114111)throw new Error(f);S-=65536,y.push(String.fromCharCode(55296|S>>10)),S=56320|S&1023}}y.push(String.fromCharCode(S))}return y.join("")}d.D4=h},721(c,d,f){c.exports=f(207).default},207(c,d,f){f.d(d,{default:()=>Is});class h{constructor(b,w){this.lastId=0,this.prefix=b,this.name=w}create(b){this.lastId++;var w=this.lastId,$=this.prefix+w,N=this.name+"["+w+"]",ne=!1,ye=function(){ne||(b.apply(null,arguments),ne=!0)};return this[w]=ye,{number:w,id:$,name:N,callback:ye}}remove(b){delete this[b.number]}}var m=new h("_pusher_script_","Pusher.ScriptReceivers"),y={VERSION:"8.6.0",PROTOCOL:7,wsPort:80,wssPort:443,wsPath:"",httpHost:"sockjs.pusher.com",httpPort:80,httpsPort:443,httpPath:"/pusher",stats_host:"stats.pusher.com",authEndpoint:"/pusher/auth",authTransport:"ajax",activityTimeout:12e4,pongTimeout:3e4,unavailableTimeout:1e4,userAuthentication:{endpoint:"/pusher/user-auth",transport:"ajax"},channelAuthorization:{endpoint:"/pusher/auth",transport:"ajax"},cdn_http:"http://js.pusher.com",cdn_https:"https://js.pusher.com",dependency_suffix:""};const v=y;class S{constructor(b){this.options=b,this.receivers=b.receivers||m,this.loading={}}load(b,w,$){var N=this;if(N.loading[b]&&N.loading[b].length>0)N.loading[b].push($);else{N.loading[b]=[$];var ne=Ue.createScriptRequest(N.getPath(b,w)),ye=N.receivers.create(function(Le){if(N.receivers.remove(ye),N.loading[b]){var We=N.loading[b];delete N.loading[b];for(var ft=function(Vt){Vt||ne.cleanup()},vt=0;vt>>6)+Q(128|b&63):Q(224|b>>>12&15)+Q(128|b>>>6&63)+Q(128|b&63)},ee=function(E){return E.replace(/[^\x00-\x7F]/g,De)},me=function(E){var b=[0,2,1][E.length%3],w=E.charCodeAt(0)<<16|(E.length>1?E.charCodeAt(1):0)<<8|(E.length>2?E.charCodeAt(2):0),$=[ue.charAt(w>>>18),ue.charAt(w>>>12&63),b>=2?"=":ue.charAt(w>>>6&63),b>=1?"=":ue.charAt(w&63)];return $.join("")},H=typeof window<"u"&&window.btoa||function(E){return E.replace(/[\s\S]{1,3}/g,me)};class T{constructor(b,w,$,N){this.clear=w,this.timer=b(()=>{this.timer&&(this.timer=N(this.timer))},$)}isRunning(){return this.timer!==null}ensureAborted(){this.timer&&(this.clear(this.timer),this.timer=null)}}const fe=T;function he(E){window.clearTimeout(E)}function Ae(E){window.clearInterval(E)}class se extends fe{constructor(b,w){super(setTimeout,he,b,function($){return w(),null})}}class ge extends fe{constructor(b,w){super(setInterval,Ae,b,function($){return w(),$})}}var $e={now(){return Date.now?Date.now():new Date().valueOf()},defer(E){return new se(0,E)},method(E,...b){var w=Array.prototype.slice.call(arguments,1);return function($){return $[E].apply($,w.concat(arguments))}}};const Te=$e;function je(E,...b){for(var w=0;w{window.console&&window.console.log&&window.console.log(b)}}debug(...b){this.log(this.globalLog,b)}warn(...b){this.log(this.globalLogWarn,b)}error(...b){this.log(this.globalLogError,b)}globalLogWarn(b){window.console&&window.console.warn?window.console.warn(b):this.globalLog(b)}globalLogError(b){window.console&&window.console.error?window.console.error(b):this.globalLogWarn(b)}log(b,...w){var $=xt.apply(this,arguments);Is.log?Is.log($):Is.logToConsole&&b.bind(this)($)}}const it=new Mn;var Pn=function(E,b,w,$,N){(w.headers!==void 0||w.headersProvider!=null)&&it.warn(`To send headers with the ${$.toString()} request, you must use AJAX, rather than JSONP.`);var ne=E.nextAuthCallbackID.toString();E.nextAuthCallbackID++;var ye=E.getDocument(),Le=ye.createElement("script");E.auth_callbacks[ne]=function(vt){N(null,vt)};var We="Pusher.auth_callbacks['"+ne+"']";Le.src=w.endpoint+"?callback="+encodeURIComponent(We)+"&"+b;var ft=ye.getElementsByTagName("head")[0]||ye.documentElement;ft.insertBefore(Le,ft.firstChild)};const vn=Pn;class da{constructor(b){this.src=b}send(b){var w=this,$="Error loading "+w.src;w.script=document.createElement("script"),w.script.id=b.id,w.script.src=w.src,w.script.type="text/javascript",w.script.charset="UTF-8",w.script.addEventListener?(w.script.onerror=function(){b.callback($)},w.script.onload=function(){b.callback(null)}):w.script.onreadystatechange=function(){(w.script.readyState==="loaded"||w.script.readyState==="complete")&&b.callback(null)},w.script.async===void 0&&document.attachEvent&&/opera/i.test(navigator.userAgent)?(w.errorScript=document.createElement("script"),w.errorScript.id=b.id+"_error",w.errorScript.text=b.name+"('"+$+"');",w.script.async=w.errorScript.async=!1):w.script.async=!0;var N=document.getElementsByTagName("head")[0];N.insertBefore(w.script,N.firstChild),w.errorScript&&N.insertBefore(w.errorScript,w.script.nextSibling)}cleanup(){this.script&&(this.script.onload=this.script.onerror=null,this.script.onreadystatechange=null),this.script&&this.script.parentNode&&this.script.parentNode.removeChild(this.script),this.errorScript&&this.errorScript.parentNode&&this.errorScript.parentNode.removeChild(this.errorScript),this.script=null,this.errorScript=null}}class fr{constructor(b,w){this.url=b,this.data=w}send(b){if(!this.request){var w=$i(this.data),$=this.url+"/"+b.number+"?"+w;this.request=Ue.createScriptRequest($),this.request.send(b)}}cleanup(){this.request&&this.request.cleanup()}}var un=function(E,b){return function(w,$){var N="http"+(b?"s":"")+"://",ne=N+(E.host||E.options.host)+E.options.path,ye=Ue.createJSONPRequest(ne,w),Le=Ue.ScriptReceivers.create(function(We,ft){m.remove(Le),ye.cleanup(),ft&&ft.host&&(E.host=ft.host),$&&$(We,ft)});ye.send(Le)}},Rn={name:"jsonp",getAgent:un};const dn=Rn;function V(E,b,w){var $=E+(b.useTLS?"s":""),N=b.useTLS?b.hostTLS:b.hostNonTLS;return $+"://"+N+w}function ie(E,b){var w="/app/"+E,$="?protocol="+v.PROTOCOL+"&client=js&version="+v.VERSION+(b?"&"+b:"");return w+$}var ce={getInitial:function(E,b){var w=(b.httpPath||"")+ie(E,"flash=false");return V("ws",b,w)}},Z={getInitial:function(E,b){var w=(b.httpPath||"/pusher")+ie(E);return V("http",b,w)}},ve={getInitial:function(E,b){return V("http",b,b.httpPath||"/pusher")},getPath:function(E,b){return ie(E)}};class pe{constructor(){this._callbacks={}}get(b){return this._callbacks[Ee(b)]}add(b,w,$){var N=Ee(b);this._callbacks[N]=this._callbacks[N]||[],this._callbacks[N].push({fn:w,context:$})}remove(b,w,$){if(!b&&!w&&!$){this._callbacks={};return}var N=b?[Ee(b)]:at(this._callbacks);w||$?this.removeCallback(N,w,$):this.removeAllCallbacks(N)}removeCallback(b,w,$){Ht(b,function(N){this._callbacks[N]=mt(this._callbacks[N]||[],function(ne){return w&&w!==ne.fn||$&&$!==ne.context}),this._callbacks[N].length===0&&delete this._callbacks[N]},this)}removeAllCallbacks(b){Ht(b,function(w){delete this._callbacks[w]},this)}}function Ee(E){return"_"+E}class xe{constructor(b){this.callbacks=new pe,this.global_callbacks=[],this.failThrough=b}bind(b,w,$){return this.callbacks.add(b,w,$),this}bind_global(b){return this.global_callbacks.push(b),this}unbind(b,w,$){return this.callbacks.remove(b,w,$),this}unbind_global(b){return b?(this.global_callbacks=mt(this.global_callbacks||[],w=>w!==b),this):(this.global_callbacks=[],this)}unbind_all(){return this.unbind(),this.unbind_global(),this}emit(b,w,$){for(var N=0;N0)for(var N=0;N{this.onError(w),this.changeState("closed")}),!1}return this.bindListeners(),it.debug("Connecting",{transport:this.name,url:b}),this.changeState("connecting"),!0}close(){return this.socket?(this.socket.close(),!0):!1}send(b){return this.state==="open"?(Te.defer(()=>{this.socket&&this.socket.send(b)}),!0):!1}ping(){this.state==="open"&&this.supportsPing()&&this.socket.ping()}onOpen(){this.hooks.beforeOpen&&this.hooks.beforeOpen(this.socket,this.hooks.urls.getPath(this.key,this.options)),this.changeState("open"),this.socket.onopen=void 0}onError(b){this.emit("error",{type:"WebSocketError",error:b}),this.timeline.error(this.buildTimelineMessage({error:b.toString()}))}onClose(b){b?this.changeState("closed",{code:b.code,reason:b.reason,wasClean:b.wasClean}):this.changeState("closed"),this.unbindListeners(),this.socket=void 0}onMessage(b){this.emit("message",b)}onActivity(){this.emit("activity")}bindListeners(){this.socket.onopen=()=>{this.onOpen()},this.socket.onerror=b=>{this.onError(b)},this.socket.onclose=b=>{this.onClose(b)},this.socket.onmessage=b=>{this.onMessage(b)},this.supportsPing()&&(this.socket.onactivity=()=>{this.onActivity()})}unbindListeners(){this.socket&&(this.socket.onopen=void 0,this.socket.onerror=void 0,this.socket.onclose=void 0,this.socket.onmessage=void 0,this.supportsPing()&&(this.socket.onactivity=void 0))}changeState(b,w){this.state=b,this.timeline.info(this.buildTimelineMessage({state:b,params:w})),this.emit(b,w)}buildTimelineMessage(b){return je({cid:this.id},b)}}class ke{constructor(b){this.hooks=b}isSupported(b){return this.hooks.isSupported(b)}createConnection(b,w,$,N){return new Ke(this.hooks,b,w,$,N)}}var Me=new ke({urls:ce,handlesActivityChecks:!1,supportsPing:!1,isInitialized:function(){return!!Ue.getWebSocketAPI()},isSupported:function(){return!!Ue.getWebSocketAPI()},getSocket:function(E){return Ue.createWebSocket(E)}}),Re={urls:Z,handlesActivityChecks:!1,supportsPing:!0,isInitialized:function(){return!0}},tt=je({getSocket:function(E){return Ue.HTTPFactory.createStreamingSocket(E)}},Re),Pe=je({getSocket:function(E){return Ue.HTTPFactory.createPollingSocket(E)}},Re),Ve={isSupported:function(){return Ue.isXHRSupported()}},Ce=new ke(je({},tt,Ve)),He=new ke(je({},Pe,Ve)),nt={ws:Me,xhr_streaming:Ce,xhr_polling:He};const Lt=nt;var Bt=new ke({file:"sockjs",urls:ve,handlesActivityChecks:!0,supportsPing:!1,isSupported:function(){return!0},isInitialized:function(){return window.SockJS!==void 0},getSocket:function(E,b){return new window.SockJS(E,null,{js_path:x.getPath("sockjs",{useTLS:b.useTLS}),ignore_null_origin:b.ignoreNullOrigin})},beforeOpen:function(E,b){E.send(JSON.stringify({path:b}))}}),Nn={isSupported:function(E){var b=Ue.isXDRSupported(E.useTLS);return b}},Gt=new ke(je({},tt,Nn)),Ft=new ke(je({},Pe,Nn));Lt.xdr_streaming=Gt,Lt.xdr_polling=Ft,Lt.sockjs=Bt;const Dn=Lt;class Ba extends xe{constructor(){super();var b=this;typeof window<"u"&&window.addEventListener!==void 0&&(window.addEventListener("online",function(){b.emit("online")},!1),window.addEventListener("offline",function(){b.emit("offline")},!1))}isOnline(){return window.navigator.onLine===void 0?!0:window.navigator.onLine}}var rl=new Ba;class mh{constructor(b,w,$){this.manager=b,this.transport=w,this.minPingDelay=$.minPingDelay,this.maxPingDelay=$.maxPingDelay,this.pingDelay=void 0}createConnection(b,w,$,N){N=je({},N,{activityTimeout:this.pingDelay});var ne=this.transport.createConnection(b,w,$,N),ye=null,Le=function(){ne.unbind("open",Le),ne.bind("closed",We),ye=Te.now()},We=ft=>{if(ne.unbind("closed",We),ft.code===1002||ft.code===1003)this.manager.reportDeath();else if(!ft.wasClean&&ye){var vt=Te.now()-ye;vt<2*this.maxPingDelay&&(this.manager.reportDeath(),this.pingDelay=Math.max(vt/2,this.minPingDelay))}};return ne.bind("open",Le),ne}isSupported(b){return this.manager.isAlive()&&this.transport.isSupported(b)}}const Zc={decodeMessage:function(E){try{var b=JSON.parse(E.data),w=b.data;if(typeof w=="string")try{w=JSON.parse(b.data)}catch{}var $={event:b.event,channel:b.channel,data:w};return b.user_id&&($.user_id=b.user_id),$}catch(N){throw{type:"MessageParseError",error:N,data:E.data}}},encodeMessage:function(E){return JSON.stringify(E)},processHandshake:function(E){var b=Zc.decodeMessage(E);if(b.event==="pusher:connection_established"){if(!b.data.activity_timeout)throw"No activity timeout specified in handshake";return{action:"connected",id:b.data.socket_id,activityTimeout:b.data.activity_timeout*1e3}}else{if(b.event==="pusher:error")return{action:this.getCloseAction(b.data),error:this.getCloseError(b.data)};throw"Invalid handshake"}},getCloseAction:function(E){return E.code<4e3?E.code>=1002&&E.code<=1004?"backoff":null:E.code===4e3?"tls_only":E.code<4100?"refused":E.code<4200?"backoff":E.code<4300?"retry":"refused"},getCloseError:function(E){return E.code!==1e3&&E.code!==1001?{type:"PusherError",data:{code:E.code,message:E.reason||E.message}}:null}},Yr=Zc;class Mi extends xe{constructor(b,w){super(),this.id=b,this.transport=w,this.activityTimeout=w.activityTimeout,this.bindListeners()}handlesActivityChecks(){return this.transport.handlesActivityChecks()}send(b){return this.transport.send(b)}send_event(b,w,$){var N={event:b,data:w};return $&&(N.channel=$),it.debug("Event sent",N),this.send(Yr.encodeMessage(N))}ping(){this.transport.supportsPing()?this.transport.ping():this.send_event("pusher:ping",{})}close(){this.transport.close()}bindListeners(){var b={message:$=>{var N;try{N=Yr.decodeMessage($)}catch(ne){this.emit("error",{type:"MessageParseError",error:ne,data:$.data})}if(N!==void 0){switch(it.debug("Event recd",N),N.event){case"pusher:error":this.emit("error",{type:"PusherError",data:N.data});break;case"pusher:ping":this.emit("ping");break;case"pusher:pong":this.emit("pong");break}this.emit("message",N)}},activity:()=>{this.emit("activity")},error:$=>{this.emit("error",$)},closed:$=>{w(),$&&$.code&&this.handleCloseEvent($),this.transport=null,this.emit("closed")}},w=()=>{gt(b,($,N)=>{this.transport.unbind(N,$)})};gt(b,($,N)=>{this.transport.bind(N,$)})}handleCloseEvent(b){var w=Yr.getCloseAction(b),$=Yr.getCloseError(b);$&&this.emit("error",$),w&&this.emit(w,{action:w,error:$})}}class al{constructor(b,w){this.transport=b,this.callback=w,this.bindListeners()}close(){this.unbindListeners(),this.transport.close()}bindListeners(){this.onMessage=b=>{this.unbindListeners();var w;try{w=Yr.processHandshake(b)}catch($){this.finish("error",{error:$}),this.transport.close();return}w.action==="connected"?this.finish("connected",{connection:new Mi(w.id,this.transport),activityTimeout:w.activityTimeout}):(this.finish(w.action,{error:w.error}),this.transport.close())},this.onClosed=b=>{this.unbindListeners();var w=Yr.getCloseAction(b)||"backoff",$=Yr.getCloseError(b);this.finish(w,{error:$})},this.transport.bind("message",this.onMessage),this.transport.bind("closed",this.onClosed)}unbindListeners(){this.transport.unbind("message",this.onMessage),this.transport.unbind("closed",this.onClosed)}finish(b,w){this.callback(je({transport:this.transport,action:b},w))}}class qa{constructor(b,w){this.timeline=b,this.options=w||{}}send(b,w){this.timeline.isEmpty()||this.timeline.send(Ue.TimelineTransport.getAgent(this,b),w)}}class Xr extends xe{constructor(b,w){super(function($,N){it.debug("No callbacks on "+b+" for "+$)}),this.name=b,this.pusher=w,this.subscribed=!1,this.subscriptionPending=!1,this.subscriptionCancelled=!1}authorize(b,w){return w(null,{auth:""})}trigger(b,w){if(b.indexOf("client-")!==0)throw new L("Event '"+b+"' does not start with 'client-'");if(!this.subscribed){var $=O.buildLogSuffix("triggeringClientEvents");it.warn(`Client event triggered before channel 'subscription_succeeded' event . ${$}`)}return this.pusher.send_event(b,w,this.name)}disconnect(){this.subscribed=!1,this.subscriptionPending=!1}handleEvent(b){var w=b.event,$=b.data;if(w==="pusher_internal:subscription_succeeded")this.handleSubscriptionSucceededEvent(b);else if(w==="pusher_internal:subscription_count")this.handleSubscriptionCountEvent(b);else if(w.indexOf("pusher_internal:")!==0){var N={};this.emit(w,$,N)}}handleSubscriptionSucceededEvent(b){this.subscriptionPending=!1,this.subscribed=!0,this.subscriptionCancelled?this.pusher.unsubscribe(this.name):this.emit("pusher:subscription_succeeded",b.data)}handleSubscriptionCountEvent(b){b.data.subscription_count&&(this.subscriptionCount=b.data.subscription_count),this.emit("pusher:subscription_count",b.data)}subscribe(){this.subscribed||(this.subscriptionPending=!0,this.subscriptionCancelled=!1,this.authorize(this.pusher.connection.socket_id,(b,w)=>{b?(this.subscriptionPending=!1,it.error(b.toString()),this.emit("pusher:subscription_error",Object.assign({},{type:"AuthError",error:b.message},b instanceof Ie?{status:b.status}:{}))):this.pusher.send_event("pusher:subscribe",{auth:w.auth,channel_data:w.channel_data,channel:this.name})}))}unsubscribe(){this.subscribed=!1,this.pusher.send_event("pusher:unsubscribe",{channel:this.name})}cancelSubscription(){this.subscriptionCancelled=!0}reinstateSubscription(){this.subscriptionCancelled=!1}}class fa extends Xr{authorize(b,w){return this.pusher.config.channelAuthorizer({channelName:this.name,socketId:b},w)}}class Ga{constructor(){this.reset()}get(b){return Object.prototype.hasOwnProperty.call(this.members,b)?{id:b,info:this.members[b]}:null}each(b){gt(this.members,(w,$)=>{b(this.get($))})}setMyID(b){this.myID=b}onSubscription(b){this.members=b.presence.hash,this.count=b.presence.count,this.me=this.get(this.myID)}addMember(b){return this.get(b.user_id)===null&&this.count++,this.members[b.user_id]=b.user_info,this.get(b.user_id)}removeMember(b){var w=this.get(b.user_id);return w&&(delete this.members[b.user_id],this.count--),w}reset(){this.members={},this.count=0,this.myID=null,this.me=null}}var nn=function(E,b,w,$){function N(ne){return ne instanceof w?ne:new w(function(ye){ye(ne)})}return new(w||(w=Promise))(function(ne,ye){function Le(vt){try{ft($.next(vt))}catch(Vt){ye(Vt)}}function We(vt){try{ft($.throw(vt))}catch(Vt){ye(Vt)}}function ft(vt){vt.done?ne(vt.value):N(vt.value).then(Le,We)}ft(($=$.apply(E,b||[])).next())})};class eu extends fa{constructor(b,w){super(b,w),this.members=new Ga}authorize(b,w){super.authorize(b,($,N)=>nn(this,void 0,void 0,function*(){if(!$)if(N=N,N.channel_data!=null){var ne=JSON.parse(N.channel_data);this.members.setMyID(ne.user_id)}else if(yield this.pusher.user.signinDonePromise,this.pusher.user.user_data!=null)this.members.setMyID(this.pusher.user.user_data.id);else{let ye=O.buildLogSuffix("authorizationEndpoint");it.error(`Invalid auth response for channel '${this.name}', expected 'channel_data' field. ${ye}, or the user should be signed in.`),w("Invalid auth response");return}w($,N)}))}handleEvent(b){var w=b.event;if(w.indexOf("pusher_internal:")===0)this.handleInternalEvent(b);else{var $=b.data,N={};b.user_id&&(N.user_id=b.user_id),this.emit(w,$,N)}}handleInternalEvent(b){var w=b.event,$=b.data;switch(w){case"pusher_internal:subscription_succeeded":this.handleSubscriptionSucceededEvent(b);break;case"pusher_internal:subscription_count":this.handleSubscriptionCountEvent(b);break;case"pusher_internal:member_added":var N=this.members.addMember($);this.emit("pusher:member_added",N);break;case"pusher_internal:member_removed":var ne=this.members.removeMember($);ne&&this.emit("pusher:member_removed",ne);break}}handleSubscriptionSucceededEvent(b){this.subscriptionPending=!1,this.subscribed=!0,this.subscriptionCancelled?this.pusher.unsubscribe(this.name):(this.members.onSubscription(b.data),this.emit("pusher:subscription_succeeded",this.members))}disconnect(){this.members.reset(),super.disconnect()}}var tu=f(978),$r=f(594);class Fa extends fa{constructor(b,w,$){super(b,w),this.key=null,this.nacl=$}authorize(b,w){super.authorize(b,($,N)=>{if($){w($,N);return}let ne=N.shared_secret;if(!ne){w(new Error(`No shared_secret key in auth payload for encrypted channel: ${this.name}`),null);return}this.key=(0,$r.decode)(ne),delete N.shared_secret,w(null,N)})}trigger(b,w){throw new de("Client events are not currently supported for encrypted channels")}handleEvent(b){var w=b.event,$=b.data;if(w.indexOf("pusher_internal:")===0||w.indexOf("pusher:")===0){super.handleEvent(b);return}this.handleEncryptedEvent(w,$)}handleEncryptedEvent(b,w){if(!this.key){it.debug("Received encrypted event before key has been retrieved from the authEndpoint");return}if(!w.ciphertext||!w.nonce){it.error("Unexpected format for encrypted event, expected object with `ciphertext` and `nonce` fields, got: "+w);return}let $=(0,$r.decode)(w.ciphertext);if($.length{if(ye){it.error(`Failed to make a request to the authEndpoint: ${Le}. Unable to fetch new key, so dropping encrypted event`);return}if(ne=this.nacl.secretbox.open($,N,this.key),ne===null){it.error("Failed to decrypt event with new key. Dropping encrypted event");return}this.emit(b,this.getDataToEmit(ne))});return}this.emit(b,this.getDataToEmit(ne))}getDataToEmit(b){let w=(0,tu.D4)(b);try{return JSON.parse(w)}catch{return w}}}class yh extends xe{constructor(b,w){super(),this.state="initialized",this.connection=null,this.key=b,this.options=w,this.timeline=this.options.timeline,this.usingTLS=this.options.useTLS,this.errorCallbacks=this.buildErrorCallbacks(),this.connectionCallbacks=this.buildConnectionCallbacks(this.errorCallbacks),this.handshakeCallbacks=this.buildHandshakeCallbacks(this.errorCallbacks);var $=Ue.getNetwork();$.bind("online",()=>{this.timeline.info({netinfo:"online"}),(this.state==="connecting"||this.state==="unavailable")&&this.retryIn(0)}),$.bind("offline",()=>{this.timeline.info({netinfo:"offline"}),this.connection&&this.sendActivityCheck()}),this.updateStrategy()}switchCluster(b){this.key=b,this.updateStrategy(),this.retryIn(0)}connect(){if(!(this.connection||this.runner)){if(!this.strategy.isSupported()){this.updateState("failed");return}this.updateState("connecting"),this.startConnecting(),this.setUnavailableTimer()}}send(b){return this.connection?this.connection.send(b):!1}send_event(b,w,$){return this.connection?this.connection.send_event(b,w,$):!1}disconnect(){this.disconnectInternally(),this.updateState("disconnected")}isUsingTLS(){return this.usingTLS}startConnecting(){var b=(w,$)=>{w?this.runner=this.strategy.connect(0,b):$.action==="error"?(this.emit("error",{type:"HandshakeError",error:$.error}),this.timeline.error({handshakeError:$.error})):(this.abortConnecting(),this.handshakeCallbacks[$.action]($))};this.runner=this.strategy.connect(0,b)}abortConnecting(){this.runner&&(this.runner.abort(),this.runner=null)}disconnectInternally(){if(this.abortConnecting(),this.clearRetryTimer(),this.clearUnavailableTimer(),this.connection){var b=this.abandonConnection();b.close()}}updateStrategy(){this.strategy=this.options.getStrategy({key:this.key,timeline:this.timeline,useTLS:this.usingTLS})}retryIn(b){this.timeline.info({action:"retry",delay:b}),b>0&&this.emit("connecting_in",Math.round(b/1e3)),this.retryTimer=new se(b||0,()=>{this.disconnectInternally(),this.connect()})}clearRetryTimer(){this.retryTimer&&(this.retryTimer.ensureAborted(),this.retryTimer=null)}setUnavailableTimer(){this.unavailableTimer=new se(this.options.unavailableTimeout,()=>{this.updateState("unavailable")})}clearUnavailableTimer(){this.unavailableTimer&&this.unavailableTimer.ensureAborted()}sendActivityCheck(){this.stopActivityCheck(),this.connection.ping(),this.activityTimer=new se(this.options.pongTimeout,()=>{this.timeline.error({pong_timed_out:this.options.pongTimeout}),this.retryIn(0)})}resetActivityCheck(){this.stopActivityCheck(),this.connection&&!this.connection.handlesActivityChecks()&&(this.activityTimer=new se(this.activityTimeout,()=>{this.sendActivityCheck()}))}stopActivityCheck(){this.activityTimer&&this.activityTimer.ensureAborted()}buildConnectionCallbacks(b){return je({},b,{message:w=>{this.resetActivityCheck(),this.emit("message",w)},ping:()=>{this.send_event("pusher:pong",{})},activity:()=>{this.resetActivityCheck()},error:w=>{this.emit("error",w)},closed:()=>{this.abandonConnection(),this.shouldRetry()&&this.retryIn(1e3)}})}buildHandshakeCallbacks(b){return je({},b,{connected:w=>{this.activityTimeout=Math.min(this.options.activityTimeout,w.activityTimeout,w.connection.activityTimeout||1/0),this.clearUnavailableTimer(),this.setConnection(w.connection),this.socket_id=this.connection.id,this.updateState("connected",{socket_id:this.socket_id})}})}buildErrorCallbacks(){let b=w=>$=>{$.error&&this.emit("error",{type:"WebSocketError",error:$.error}),w($)};return{tls_only:b(()=>{this.usingTLS=!0,this.updateStrategy(),this.retryIn(0)}),refused:b(()=>{this.disconnect()}),backoff:b(()=>{this.retryIn(1e3)}),retry:b(()=>{this.retryIn(0)})}}setConnection(b){this.connection=b;for(var w in this.connectionCallbacks)this.connection.bind(w,this.connectionCallbacks[w]);this.resetActivityCheck()}abandonConnection(){if(this.connection){this.stopActivityCheck();for(var b in this.connectionCallbacks)this.connection.unbind(b,this.connectionCallbacks[b]);var w=this.connection;return this.connection=null,w}}updateState(b,w){var $=this.state;if(this.state=b,$!==b){var N=b;N==="connected"&&(N+=" with new socket ID "+w.socket_id),it.debug("State changed",$+" -> "+N),this.timeline.info({state:b,params:w}),this.emit("state_change",{previous:$,current:b}),this.emit(b,w)}}shouldRetry(){return this.state==="connecting"||this.state==="connected"}}class nu{constructor(){this.channels={}}add(b,w){return this.channels[b]||(this.channels[b]=ru(b,w)),this.channels[b]}all(){return Yt(this.channels)}find(b){return this.channels[b]}remove(b){var w=this.channels[b];return delete this.channels[b],w}disconnect(){gt(this.channels,function(b){b.disconnect()})}}function ru(E,b){if(E.indexOf("private-encrypted-")===0){if(b.config.nacl)return Un.createEncryptedChannel(E,b,b.config.nacl);let w="Tried to subscribe to a private-encrypted- channel but no nacl implementation available",$=O.buildLogSuffix("encryptedChannelSupport");throw new de(`${w}. ${$}`)}else{if(E.indexOf("private-")===0)return Un.createPrivateChannel(E,b);if(E.indexOf("presence-")===0)return Un.createPresenceChannel(E,b);if(E.indexOf("#")===0)throw new B('Cannot create a channel with name "'+E+'".');return Un.createChannel(E,b)}}var bh={createChannels(){return new nu},createConnectionManager(E,b){return new yh(E,b)},createChannel(E,b){return new Xr(E,b)},createPrivateChannel(E,b){return new fa(E,b)},createPresenceChannel(E,b){return new eu(E,b)},createEncryptedChannel(E,b,w){return new Fa(E,b,w)},createTimelineSender(E,b){return new qa(E,b)},createHandshake(E,b){return new al(E,b)},createAssistantToTheTransportManager(E,b,w){return new mh(E,b,w)}};const Un=bh;class Ni{constructor(b){this.options=b||{},this.livesLeft=this.options.lives||1/0}getAssistant(b){return Un.createAssistantToTheTransportManager(this,b,{minPingDelay:this.options.minPingDelay,maxPingDelay:this.options.maxPingDelay})}isAlive(){return this.livesLeft>0}reportDeath(){this.livesLeft-=1}}class Sn{constructor(b,w){this.strategies=b,this.loop=!!w.loop,this.failFast=!!w.failFast,this.timeout=w.timeout,this.timeoutLimit=w.timeoutLimit}isSupported(){return kn(this.strategies,Te.method("isSupported"))}connect(b,w){var $=this.strategies,N=0,ne=this.timeout,ye=null,Le=(We,ft)=>{ft?w(null,ft):(N=N+1,this.loop&&(N=N%$.length),N<$.length?(ne&&(ne=ne*2,this.timeoutLimit&&(ne=Math.min(ne,this.timeoutLimit))),ye=this.tryStrategy($[N],b,{timeout:ne,failFast:this.failFast},Le)):w(!0))};return ye=this.tryStrategy($[N],b,{timeout:ne,failFast:this.failFast},Le),{abort:function(){ye.abort()},forceMinPriority:function(We){b=We,ye&&ye.forceMinPriority(We)}}}tryStrategy(b,w,$,N){var ne=null,ye=null;return $.timeout>0&&(ne=new se($.timeout,function(){ye.abort(),N(!0)})),ye=b.connect(w,function(Le,We){Le&&ne&&ne.isRunning()&&!$.failFast||(ne&&ne.ensureAborted(),N(Le,We))}),{abort:function(){ne&&ne.ensureAborted(),ye.abort()},forceMinPriority:function(Le){ye.forceMinPriority(Le)}}}}class On{constructor(b){this.strategies=b}isSupported(){return kn(this.strategies,Te.method("isSupported"))}connect(b,w){return au(this.strategies,b,function($,N){return function(ne,ye){if(N[$].error=ne,ne){vh(N)&&w(!0);return}Ht(N,function(Le){Le.forceMinPriority(ye.transport.priority)}),w(null,ye)}})}}function au(E,b,w){var $=bt(E,function(N,ne,ye,Le){return N.connect(b,w(ne,Le))});return{abort:function(){Ht($,il)},forceMinPriority:function(N){Ht($,function(ne){ne.forceMinPriority(N)})}}}function vh(E){return dr(E,function(b){return!!b.error})}function il(E){!E.error&&!E.aborted&&(E.abort(),E.aborted=!0)}class iu{constructor(b,w,$){this.strategy=b,this.transports=w,this.ttl=$.ttl||18e5,this.usingTLS=$.useTLS,this.timeline=$.timeline}isSupported(){return this.strategy.isSupported()}connect(b,w){var $=this.usingTLS,N=Sh($),ne=N&&N.cacheSkipCount?N.cacheSkipCount:0,ye=[this.strategy];if(N&&N.timestamp+this.ttl>=Te.now()){var Le=this.transports[N.transport];Le&&(["ws","wss"].includes(N.transport)||ne>3?(this.timeline.info({cached:!0,transport:N.transport,latency:N.latency}),ye.push(new Sn([Le],{timeout:N.latency*2+1e3,failFast:!0}))):ne++)}var We=Te.now(),ft=ye.pop().connect(b,function vt(Vt,jr){Vt?(Os($),ye.length>0?(We=Te.now(),ft=ye.pop().connect(b,vt)):w(Vt)):(Bn($,jr.transport.name,Te.now()-We,ne),w(null,jr))});return{abort:function(){ft.abort()},forceMinPriority:function(vt){b=vt,ft&&ft.forceMinPriority(vt)}}}}function Va(E){return"pusherTransport"+(E?"TLS":"NonTLS")}function Sh(E){var b=Ue.getLocalStorage();if(b)try{var w=b[Va(E)];if(w)return JSON.parse(w)}catch{Os(E)}return null}function Bn(E,b,w,$){var N=Ue.getLocalStorage();if(N)try{N[Va(E)]=bn({timestamp:Te.now(),transport:b,latency:w,cacheSkipCount:$})}catch{}}function Os(E){var b=Ue.getLocalStorage();if(b)try{delete b[Va(E)]}catch{}}class Ii{constructor(b,{delay:w}){this.strategy=b,this.options={delay:w}}isSupported(){return this.strategy.isSupported()}connect(b,w){var $=this.strategy,N,ne=new se(this.options.delay,function(){N=$.connect(b,w)});return{abort:function(){ne.ensureAborted(),N&&N.abort()},forceMinPriority:function(ye){b=ye,N&&N.forceMinPriority(ye)}}}}class ha{constructor(b,w,$){this.test=b,this.trueBranch=w,this.falseBranch=$}isSupported(){var b=this.test()?this.trueBranch:this.falseBranch;return b.isSupported()}connect(b,w){var $=this.test()?this.trueBranch:this.falseBranch;return $.connect(b,w)}}class Ka{constructor(b){this.strategy=b}isSupported(){return this.strategy.isSupported()}connect(b,w){var $=this.strategy.connect(b,function(N,ne){ne&&$.abort(),w(N,ne)});return $}}function Wa(E){return function(){return E.isSupported()}}var su=function(E,b,w){var $={};function N(vl,Su,wu,Uh,Bh){var Cu=w(E,vl,Su,wu,Uh,Bh);return $[vl]=Cu,Cu}var ne=Object.assign({},b,{hostNonTLS:E.wsHost+":"+E.wsPort,hostTLS:E.wsHost+":"+E.wssPort,httpPath:E.wsPath}),ye=Object.assign({},ne,{useTLS:!0}),Le=Object.assign({},b,{hostNonTLS:E.httpHost+":"+E.httpPort,hostTLS:E.httpHost+":"+E.httpsPort,httpPath:E.httpPath}),We={loop:!0,timeout:15e3,timeoutLimit:6e4},ft=new Ni({minPingDelay:1e4,maxPingDelay:E.activityTimeout}),vt=new Ni({lives:2,minPingDelay:1e4,maxPingDelay:E.activityTimeout}),Vt=N("ws","ws",3,ne,ft),jr=N("wss","ws",3,ye,ft),Hh=N("sockjs","sockjs",1,Le),bu=N("xhr_streaming","xhr_streaming",1,Le,vt),zh=N("xdr_streaming","xdr_streaming",1,Le,vt),ml=N("xhr_polling","xhr_polling",1,Le),vu=N("xdr_polling","xdr_polling",1,Le),Ja=new Sn([Vt],We),Bi=new Sn([jr],We),Ph=new Sn([Hh],We),qi=new Sn([new ha(Wa(bu),bu,zh)],We),yl=new Sn([new ha(Wa(ml),ml,vu)],We),bl=new Sn([new ha(Wa(qi),new On([qi,new Ii(yl,{delay:4e3})]),yl)],We),Gi=new ha(Wa(bl),bl,Ph),Fi;return b.useTLS?Fi=new On([Ja,new Ii(Gi,{delay:2e3})]):Fi=new On([Ja,new Ii(Bi,{delay:2e3}),new Ii(Gi,{delay:5e3})]),new iu(new Ka(new ha(Wa(Vt),Fi,Gi)),$,{ttl:18e5,timeline:b.timeline,useTLS:b.useTLS})};const Ya=su;function wh(){var E=this;E.timeline.info(E.buildTimelineMessage({transport:E.name+(E.options.useTLS?"s":"")})),E.hooks.isInitialized()?E.changeState("initialized"):E.hooks.file?(E.changeState("initializing"),x.load(E.hooks.file,{useTLS:E.options.useTLS},function(b,w){E.hooks.isInitialized()?(E.changeState("initialized"),w(!0)):(b&&E.onError(b),E.onClose(),w(!1))})):E.onClose()}var ou={getRequest:function(E){var b=new window.XDomainRequest;return b.ontimeout=function(){E.emit("error",new q),E.close()},b.onerror=function(w){E.emit("error",w),E.close()},b.onprogress=function(){b.responseText&&b.responseText.length>0&&E.onChunk(200,b.responseText)},b.onload=function(){b.responseText&&b.responseText.length>0&&E.onChunk(200,b.responseText),E.emit("finished",200),E.close()},b},abortRequest:function(E){E.ontimeout=E.onerror=E.onprogress=E.onload=null,E.abort()}};const lu=ou,sl=256*1024;class Ch extends xe{constructor(b,w,$){super(),this.hooks=b,this.method=w,this.url=$}start(b){this.position=0,this.xhr=this.hooks.getRequest(this),this.unloader=()=>{this.close()},Ue.addUnloadListener(this.unloader),this.xhr.open(this.method,this.url,!0),this.xhr.setRequestHeader&&this.xhr.setRequestHeader("Content-Type","application/json"),this.xhr.send(b)}close(){this.unloader&&(Ue.removeUnloadListener(this.unloader),this.unloader=null),this.xhr&&(this.hooks.abortRequest(this.xhr),this.xhr=null)}onChunk(b,w){for(;;){var $=this.advanceBuffer(w);if($)this.emit("chunk",{status:b,data:$});else break}this.isBufferTooLong(w)&&this.emit("buffer_too_long")}advanceBuffer(b){var w=b.slice(this.position),$=w.indexOf(` -`);return $!==-1?(this.position+=$+1,w.slice(0,$)):null}isBufferTooLong(b){return this.position===b.length&&b.length>sl}}var ol;(function(E){E[E.CONNECTING=0]="CONNECTING",E[E.OPEN=1]="OPEN",E[E.CLOSED=3]="CLOSED"})(ol||(ol={}));const hr=ol;var Mr=1;class ll{constructor(b,w){this.hooks=b,this.session=Ls(1e3)+"/"+uu(8),this.location=cl(w),this.readyState=hr.CONNECTING,this.openStream()}send(b){return this.sendRaw(JSON.stringify([b]))}ping(){this.hooks.sendHeartbeat(this)}close(b,w){this.onClose(b,w,!0)}sendRaw(b){if(this.readyState===hr.OPEN)try{return Ue.createSocketRequest("POST",pa(Xa(this.location,this.session))).start(b),!0}catch{return!1}else return!1}reconnect(){this.closeStream(),this.openStream()}onClose(b,w,$){this.closeStream(),this.readyState=hr.CLOSED,this.onclose&&this.onclose({code:b,reason:w,wasClean:$})}onChunk(b){if(b.status===200){this.readyState===hr.OPEN&&this.onActivity();var w,$=b.data.slice(0,1);switch($){case"o":w=JSON.parse(b.data.slice(1)||"{}"),this.onOpen(w);break;case"a":w=JSON.parse(b.data.slice(1)||"[]");for(var N=0;N{this.onChunk(b)}),this.stream.bind("finished",b=>{this.hooks.onFinished(this,b)}),this.stream.bind("buffer_too_long",()=>{this.reconnect()});try{this.stream.start()}catch(b){Te.defer(()=>{this.onError(b),this.onClose(1006,"Could not start streaming",!1)})}}closeStream(){this.stream&&(this.stream.unbind_all(),this.stream.close(),this.stream=null)}}function cl(E){var b=/([^\?]*)\/*(\??.*)/.exec(E);return{base:b[1],queryString:b[2]}}function Xa(E,b){return E.base+"/"+b+"/xhr_send"}function pa(E){var b=E.indexOf("?")===-1?"?":"&";return E+b+"t="+ +new Date+"&n="+Mr++}function cu(E,b){var w=/(https?:\/\/)([^\/:]+)((\/|:)?.*)/.exec(E);return w[1]+b+w[3]}function Ls(E){return Ue.randomInt(E)}function uu(E){for(var b=[],w=0;w0&&E.onChunk(w.status,w.responseText);break;case 4:w.responseText&&w.responseText.length>0&&E.onChunk(w.status,w.responseText),E.emit("finished",w.status),E.close();break}},w},abortRequest:function(E){E.onreadystatechange=null,E.abort()}};const $s=dl;var du={createStreamingSocket(E){return this.createSocket(ul,E)},createPollingSocket(E){return this.createSocket(Jr,E)},createSocket(E,b){return new ji(E,b)},createXHR(E,b){return this.createRequest($s,E,b)},createRequest(E,b,w){return new Ch(E,b,w)}};const zi=du;zi.createXDR=function(E,b){return this.createRequest(lu,E,b)};var fu={nextAuthCallbackID:1,auth_callbacks:{},ScriptReceivers:m,DependenciesReceivers:_,getDefaultStrategy:Ya,Transports:Dn,transportConnectionInitializer:wh,HTTPFactory:zi,TimelineTransport:dn,getXHRAPI(){return window.XMLHttpRequest},getWebSocketAPI(){return window.WebSocket||window.MozWebSocket},setup(E){if(typeof window<"u"){window.Pusher=E;var b=()=>{this.onDocumentBody(E.ready)};window.JSON?b():x.load("json2",{},b)}},getDocument(){return document},getProtocol(){return this.getDocument().location.protocol},getAuthorizers(){return{ajax:ze,jsonp:vn}},onDocumentBody(E){document.body?E():setTimeout(()=>{this.onDocumentBody(E)},0)},createJSONPRequest(E,b){return new fr(E,b)},createScriptRequest(E){return new da(E)},getLocalStorage(){try{return window.localStorage}catch{return}},createXHR(){return this.getXHRAPI()?this.createXMLHttpRequest():this.createMicrosoftXHR()},createXMLHttpRequest(){var E=this.getXHRAPI();return new E},createMicrosoftXHR(){return new ActiveXObject("Microsoft.XMLHTTP")},getNetwork(){return rl},createWebSocket(E){var b=this.getWebSocketAPI();return new b(E)},createSocketRequest(E,b){if(this.isXHRSupported())return this.HTTPFactory.createXHR(E,b);if(this.isXDRSupported(b.indexOf("https:")===0))return this.HTTPFactory.createXDR(E,b);throw"Cross-origin HTTP requests are not supported"},isXHRSupported(){var E=this.getXHRAPI();return!!E&&new E().withCredentials!==void 0},isXDRSupported(E){var b=E?"https:":"http:",w=this.getProtocol();return!!window.XDomainRequest&&w===b},addUnloadListener(E){window.addEventListener!==void 0?window.addEventListener("pagehide",E,!1):window.attachEvent!==void 0&&window.attachEvent("onunload",E)},removeUnloadListener(E){window.addEventListener!==void 0?window.removeEventListener("pagehide",E,!1):window.detachEvent!==void 0&&window.detachEvent("onunload",E)},randomInt(E){const b=window.crypto||window.msCrypto,w=Math.floor(Math.pow(2,32)/E)*E;let $;do $=b.getRandomValues(new Uint32Array(1))[0];while($>=w);return $%E}};const Ue=fu;var Ir;(function(E){E[E.ERROR=3]="ERROR",E[E.INFO=6]="INFO",E[E.DEBUG=7]="DEBUG"})(Ir||(Ir={}));const ga=Ir;class Pi{constructor(b,w,$){this.key=b,this.session=w,this.events=[],this.options=$||{},this.sent=0,this.uniqueID=0}log(b,w){b<=this.options.level&&(this.events.push(je({},w,{timestamp:Te.now()})),this.options.limit&&this.events.length>this.options.limit&&this.events.shift())}error(b){this.log(ga.ERROR,b)}info(b){this.log(ga.INFO,b)}debug(b){this.log(ga.DEBUG,b)}isEmpty(){return this.events.length===0}send(b,w){var $=je({session:this.session,bundle:this.sent+1,key:this.key,lib:"js",version:this.options.version,cluster:this.options.cluster,features:this.options.features,timeline:this.events},this.options.params);return this.events=[],b($,(N,ne)=>{N||this.sent++,w&&w(N,ne)}),!0}generateUniqueID(){return this.uniqueID++,this.uniqueID}}class Eh{constructor(b,w,$,N){this.name=b,this.priority=w,this.transport=$,this.options=N||{}}isSupported(){return this.transport.isSupported({useTLS:this.options.useTLS})}connect(b,w){if(this.isSupported()){if(this.priority{$||(vt(),ne?ne.close():N.close())},forceMinPriority:Vt=>{$||this.priority{var w="socket_id="+encodeURIComponent(E.socketId);for(var $ in b.params)w+="&"+encodeURIComponent($)+"="+encodeURIComponent(b.params[$]);if(b.paramsProvider!=null){let N=b.paramsProvider();for(var $ in N)w+="&"+encodeURIComponent($)+"="+encodeURIComponent(N[$])}return w},Ah=E=>{if(typeof Ue.getAuthorizers()[E.transport]>"u")throw`'${E.transport}' is not a recognized auth transport`;return(b,w)=>{const $=_h(b,E);Ue.getAuthorizers()[E.transport](Ue,$,E,k.UserAuthentication,w)}},pl=(E,b)=>{var w="socket_id="+encodeURIComponent(E.socketId);w+="&channel_name="+encodeURIComponent(E.channelName);for(var $ in b.params)w+="&"+encodeURIComponent($)+"="+encodeURIComponent(b.params[$]);if(b.paramsProvider!=null){let N=b.paramsProvider();for(var $ in N)w+="&"+encodeURIComponent($)+"="+encodeURIComponent(N[$])}return w},xh=E=>{if(typeof Ue.getAuthorizers()[E.transport]>"u")throw`'${E.transport}' is not a recognized auth transport`;return(b,w)=>{const $=pl(b,E);Ue.getAuthorizers()[E.transport](Ue,$,E,k.ChannelAuthorization,w)}},Th=(E,b,w)=>{const $={authTransport:b.transport,authEndpoint:b.endpoint,auth:{params:b.params,headers:b.headers}};return(N,ne)=>{const ye=E.channel(N.channelName);w(ye,$).authorize(N.socketId,ne)}};function pu(E,b){let w={activityTimeout:E.activityTimeout||v.activityTimeout,cluster:E.cluster,httpPath:E.httpPath||v.httpPath,httpPort:E.httpPort||v.httpPort,httpsPort:E.httpsPort||v.httpsPort,pongTimeout:E.pongTimeout||v.pongTimeout,statsHost:E.statsHost||v.stats_host,unavailableTimeout:E.unavailableTimeout||v.unavailableTimeout,wsPath:E.wsPath||v.wsPath,wsPort:E.wsPort||v.wsPort,wssPort:E.wssPort||v.wssPort,enableStats:Oh(E),httpHost:kh(E),useTLS:Dh(E),wsHost:gu(E),userAuthenticator:gl(E),channelAuthorizer:$h(E,b)};return"disabledTransports"in E&&(w.disabledTransports=E.disabledTransports),"enabledTransports"in E&&(w.enabledTransports=E.enabledTransports),"ignoreNullOrigin"in E&&(w.ignoreNullOrigin=E.ignoreNullOrigin),"timelineParams"in E&&(w.timelineParams=E.timelineParams),"nacl"in E&&(w.nacl=E.nacl),w}function kh(E){return E.httpHost?E.httpHost:E.cluster?`sockjs-${E.cluster}.pusher.com`:v.httpHost}function gu(E){return E.wsHost?E.wsHost:Rh(E.cluster)}function Rh(E){return`ws-${E}.pusher.com`}function Dh(E){return Ue.getProtocol()==="https:"?!0:E.forceTLS!==!1}function Oh(E){return"enableStats"in E?E.enableStats:"disableStats"in E?!E.disableStats:!1}const mu=E=>"customHandler"in E&&E.customHandler!=null;function gl(E){const b=Object.assign(Object.assign({},v.userAuthentication),E.userAuthentication);return mu(b)?b.customHandler:Ah(b)}function Lh(E,b){let w;return"channelAuthorization"in E?w=Object.assign(Object.assign({},v.channelAuthorization),E.channelAuthorization):(w={transport:E.authTransport||v.authTransport,endpoint:E.authEndpoint||v.authEndpoint},"auth"in E&&("params"in E.auth&&(w.params=E.auth.params),"headers"in E.auth&&(w.headers=E.auth.headers)),"authorizer"in E&&(w.customHandler=Th(b,w,E.authorizer))),w}function $h(E,b){const w=Lh(E,b);return mu(w)?w.customHandler:xh(w)}class Mh extends xe{constructor(b){super(function(w,$){it.debug(`No callbacks on watchlist events for ${w}`)}),this.pusher=b,this.bindWatchlistInternalEvent()}handleEvent(b){b.data.events.forEach(w=>{this.emit(w.name,w)})}bindWatchlistInternalEvent(){this.pusher.connection.bind("message",b=>{var w=b.event;w==="pusher_internal:watchlist_events"&&this.handleEvent(b)})}}function yu(){let E,b;return{promise:new Promise(($,N)=>{E=$,b=N}),resolve:E,reject:b}}const Nh=yu;class Ih extends xe{constructor(b){super(function(w,$){it.debug("No callbacks on user for "+w)}),this.signin_requested=!1,this.user_data=null,this.serverToUserChannel=null,this.signinDonePromise=null,this._signinDoneResolve=null,this._onAuthorize=(w,$)=>{if(w){it.warn(`Error during signin: ${w}`),this.emit("pusher:signin_error",Object.assign({},{type:"AuthError",error:w.message},w instanceof Ie?{status:w.status}:{})),this._cleanup();return}this.pusher.send_event("pusher:signin",{auth:$.auth,user_data:$.user_data})},this.pusher=b,this.pusher.connection.bind("state_change",({previous:w,current:$})=>{w!=="connected"&&$==="connected"&&this._signin(),w==="connected"&&$!=="connected"&&(this._cleanup(),this._newSigninPromiseIfNeeded())}),this.watchlist=new Mh(b),this.pusher.connection.bind("message",w=>{var $=w.event;$==="pusher:signin_success"&&this._onSigninSuccess(w.data),this.serverToUserChannel&&this.serverToUserChannel.name===w.channel&&this.serverToUserChannel.handleEvent(w)})}signin(){this.signin_requested||(this.signin_requested=!0,this._signin())}_signin(){this.signin_requested&&(this._newSigninPromiseIfNeeded(),this.pusher.connection.state==="connected"&&this.pusher.config.userAuthenticator({socketId:this.pusher.connection.socket_id},this._onAuthorize))}_onSigninSuccess(b){try{this.user_data=JSON.parse(b.user_data)}catch{it.error(`Failed parsing user data after signin: ${b.user_data}`),this._cleanup();return}if(typeof this.user_data.id!="string"||this.user_data.id===""){it.error(`user_data doesn't contain an id. user_data: ${this.user_data}`),this._cleanup();return}this._signinDoneResolve(),this._subscribeChannels()}_subscribeChannels(){const b=w=>{w.subscriptionPending&&w.subscriptionCancelled?w.reinstateSubscription():!w.subscriptionPending&&this.pusher.connection.state==="connected"&&w.subscribe()};this.serverToUserChannel=new Xr(`#server-to-user-${this.user_data.id}`,this.pusher),this.serverToUserChannel.bind_global((w,$)=>{w.indexOf("pusher_internal:")===0||w.indexOf("pusher:")===0||this.emit(w,$)}),b(this.serverToUserChannel)}_cleanup(){this.user_data=null,this.serverToUserChannel&&(this.serverToUserChannel.unbind_all(),this.serverToUserChannel.disconnect(),this.serverToUserChannel=null),this.signin_requested&&this._signinDoneResolve()}_newSigninPromiseIfNeeded(){if(!this.signin_requested||this.signinDonePromise&&!this.signinDonePromise.done)return;const{promise:b,resolve:w}=Nh();b.done=!1;const $=()=>{b.done=!0};b.then($).catch($),this.signinDonePromise=b,this._signinDoneResolve=w}}class fn{static ready(){fn.isReady=!0;for(var b=0,w=fn.instances.length;bUe.getDefaultStrategy(this.config,N,Ui);this.connection=Un.createConnectionManager(this.key,{getStrategy:$,timeline:this.timeline,activityTimeout:this.config.activityTimeout,pongTimeout:this.config.pongTimeout,unavailableTimeout:this.config.unavailableTimeout,useTLS:!!this.config.useTLS}),this.connection.bind("connected",()=>{this.subscribeAll(),this.timelineSender&&this.timelineSender.send(this.connection.isUsingTLS())}),this.connection.bind("message",N=>{var ne=N.event,ye=ne.indexOf("pusher_internal:")===0;if(N.channel){var Le=this.channel(N.channel);Le&&Le.handleEvent(N)}ye||this.global_emitter.emit(N.event,N.data)}),this.connection.bind("connecting",()=>{this.channels.disconnect()}),this.connection.bind("disconnected",()=>{this.channels.disconnect()}),this.connection.bind("error",N=>{it.warn(N)}),fn.instances.push(this),this.timeline.info({instances:fn.instances.length}),this.user=new Ih(this),fn.isReady&&this.connect()}switchCluster(b){const{appKey:w,cluster:$}=b;this.key=w,this.options=Object.assign(Object.assign({},this.options),{cluster:$}),this.config=pu(this.options,this),this.connection.switchCluster(this.key)}channel(b){return this.channels.find(b)}allChannels(){return this.channels.all()}connect(){if(this.connection.connect(),this.timelineSender&&!this.timelineSenderTimer){var b=this.connection.isUsingTLS(),w=this.timelineSender;this.timelineSenderTimer=new ge(6e4,function(){w.send(b)})}}disconnect(){this.connection.disconnect(),this.timelineSenderTimer&&(this.timelineSenderTimer.ensureAborted(),this.timelineSenderTimer=null)}bind(b,w,$){return this.global_emitter.bind(b,w,$),this}unbind(b,w,$){return this.global_emitter.unbind(b,w,$),this}bind_global(b){return this.global_emitter.bind_global(b),this}unbind_global(b){return this.global_emitter.unbind_global(b),this}unbind_all(b){return this.global_emitter.unbind_all(),this}subscribeAll(){var b;for(b in this.channels.channels)this.channels.channels.hasOwnProperty(b)&&this.subscribe(b)}subscribe(b){var w=this.channels.add(b,this);return w.subscriptionPending&&w.subscriptionCancelled?w.reinstateSubscription():!w.subscriptionPending&&this.connection.state==="connected"&&w.subscribe(),w}unsubscribe(b){var w=this.channels.find(b);w&&w.subscriptionPending?w.cancelSubscription():(w=this.channels.remove(b),w&&w.subscribed&&w.unsubscribe())}send_event(b,w,$){return this.connection.send_event(b,w,$)}shouldUseTLS(){return this.config.useTLS}signin(){this.user.signin()}}fn.instances=[],fn.isReady=!1,fn.logToConsole=!1,fn.Runtime=Ue,fn.ScriptReceivers=Ue.ScriptReceivers,fn.DependenciesReceivers=Ue.DependenciesReceivers,fn.auth_callbacks=Ue.auth_callbacks;const Is=fn;function jh(E){if(E==null)throw"You must pass your app key when you instantiate Pusher."}Ue.setup(fn)}},r={};function i(c){var d=r[c];if(d!==void 0)return d.exports;var f=r[c]={exports:{}};return n[c].call(f.exports,f,f.exports,i),f.exports}i.d=(c,d)=>{for(var f in d)i.o(d,f)&&!i.o(c,f)&&Object.defineProperty(c,f,{enumerable:!0,get:d[f]})},i.o=(c,d)=>Object.prototype.hasOwnProperty.call(c,d);var l=i(721);return l})())})(ch)),ch.exports}var Ux=Px();const Eb=ra(Ux),Kt=pt("WebSocketManager");class Bx{constructor(){M(this,"echo",null);M(this,"subscriptions",new Map);M(this,"initialized",!1);M(this,"config",null)}configure(e){if(this.initialized){Kt.warn("[WebSocketManager] 이미 초기화되어 설정을 변경할 수 없습니다.");return}this.config=e,Kt.log("[WebSocketManager] 설정 완료:",{appKey:e.appKey?"***":"(없음)",host:e.host,port:e.port,scheme:e.scheme})}initialize(){if(this.initialized)return;if(!this.config||!this.config.appKey){Kt.warn("[WebSocketManager] WebSocket 설정이 없습니다. initTemplateApp에서 websocket 옵션을 전달해주세요.");return}const{appKey:e,host:n="localhost",port:r=80,scheme:i="https",authEndpoint:l="/api/broadcasting/auth"}=this.config,c=Number(r)||80,d=i==="https";Kt.log("[WebSocketManager] 연결 설정:",{host:n,port:c,scheme:i,useTLS:d,authEndpoint:l}),window.Pusher=Eb;const f=localStorage.getItem("auth_token"),h={wsHost:n,wsPort:c,wssPort:c,forceTLS:d,disableStats:!0,enabledTransports:["ws","wss"],cluster:"mt1",authEndpoint:l,auth:{headers:{Authorization:f?`Bearer ${f}`:"",Accept:"application/json"}}};Kt.log("[WebSocketManager] Pusher 옵션:",h);const m=new Eb(e,h);m.connection.bind("connecting",()=>{Kt.log("[WebSocketManager] 연결 시도 중...")}),m.connection.bind("connected",()=>{Kt.log("[WebSocketManager] 연결 성공! Socket ID:",m.connection.socket_id)}),m.connection.bind("failed",()=>{Kt.error("[WebSocketManager] 연결 실패 - WebSocket을 사용할 수 없습니다.")}),m.connection.bind("error",y=>{Kt.error("[WebSocketManager] 연결 오류:",y)}),m.connection.bind("state_change",y=>{Kt.log(`[WebSocketManager] 상태 변경: ${y.previous} → ${y.current}`)}),m.connection.bind("unavailable",()=>{Kt.error("[WebSocketManager] WebSocket 사용 불가 - 연결할 수 없습니다.")}),m.connection.bind("disconnected",()=>{Kt.warn("[WebSocketManager] 연결이 끊어졌습니다.")}),Kt.log("[WebSocketManager] 초기 연결 상태:",m.connection.state),this.echo=new zx({broadcaster:"reverb",client:m}),Kt.log("[WebSocketManager] 연결 시작 시도..."),m.connect(),window.Echo=this.echo,this.initialized=!0,Kt.log("[WebSocketManager] Echo 초기화 완료")}subscribe(e,n,r,i={}){if(this.initialize(),!this.echo)return Kt.warn("[WebSocketManager] Echo가 초기화되지 않았습니다."),"";const{channelType:l="private"}=i,c=`${e}:${n}`;if(this.subscriptions.has(c))return Kt.log(`[WebSocketManager] 이미 구독 중: ${c}`),c;let d;switch(l){case"public":d=this.echo.channel(e);break;case"presence":d=this.echo.join(e);break;default:d=this.echo.private(e)}return d.listen(`.${n}`,r),this.subscriptions.set(c,d),Kt.log(`[WebSocketManager] 구독 완료: ${c} (${l})`),c}unsubscribe(e){const n=this.subscriptions.get(e);if(!n)return;const r=e.lastIndexOf(":"),i=r>=0?e.substring(r+1):"";if(i)try{n.stopListening(`.${i}`)}catch(l){Kt.warn(`[WebSocketManager] stopListening 실패: ${e}`,l)}this.subscriptions.delete(e),Kt.log(`[WebSocketManager] 구독 해제: ${e}`)}leaveChannel(e){if(!this.echo)return;this.echo.leave(e);const n=[];this.subscriptions.forEach((r,i)=>{i.startsWith(`${e}:`)&&n.push(i)}),n.forEach(r=>this.subscriptions.delete(r)),Kt.log(`[WebSocketManager] 채널 구독 해제: ${e}`)}disconnect(){this.echo&&(this.echo.disconnect(),this.subscriptions.clear(),this.initialized=!1,Kt.log("[WebSocketManager] 연결 종료"))}getEcho(){return this.echo}isInitialized(){return this.initialized}isConfigured(){return this.config!==null&&!!this.config.appKey}getSubscriptionCount(){return this.subscriptions.size}}const Gc=new Bx,Ct=pt("DataSourceManager"),_b=new $n;function Fc(s,e){const n=La(s);if(n!==null)try{return Wn(n)?_b.evaluatePipeExpression(n,e,{skipCache:!0}):_b.evaluateExpression(n,e)}catch(r){return Ct.error("Failed to evaluate param expression:",n,r),s}return hs(s,e,{skipCache:!0})}function Ab(s){const e={};for(const n of s.keys()){if(n in e)continue;const r=s.getAll(n);r.length>1?e[n]=r:r.length===1&&(n.endsWith("[]")?e[n]=r:e[n]=r[0])}return e}function qx(){try{return window.G7Core?.devTools}catch{return}}const Qc=class Qc{constructor(e={}){M(this,"options");M(this,"dataCache",new Map);M(this,"bindingEngine");M(this,"globalHeaders",[]);this.options=e,this.bindingEngine=new $n}setGlobalHeaders(e){this.globalHeaders=e||[]}matchesPattern(e,n){if(n==="*")return!0;const r=n.replace(/[.+?^${}()|[\]\\]/g,"\\$&").replace(/\*/g,".*");return new RegExp(`^${r}$`).test(e)}toFormData(e){const n=new FormData;for(const[r,i]of Object.entries(e))i instanceof File||i instanceof Blob?n.append(r,i):i!=null&&n.append(r,typeof i=="object"?JSON.stringify(i):String(i));return n}getMatchingGlobalHeaders(e,n){const r={};for(const i of this.globalHeaders)this.matchesPattern(e,i.pattern)&&Object.entries(i.headers).forEach(([l,c])=>{const d=Fc(c,n);d!=null&&d!==""&&(r[l]=String(d))});return r}filterByCondition(e,n={}){const r=new Set,i=[];for(const l of e){if(r.has(l.id))continue;if(!l.if&&!l.conditions){r.add(l.id),i.push(l);continue}fs({if:l.if,conditions:l.conditions},n,this.bindingEngine,`data_source:${l.id}`)&&(r.add(l.id),i.push(l))}return i}checkErrorCondition(e,n){if(!e.errorCondition?.if||e.errorCondition?.errorCode===void 0)return null;try{const r={response:n};if(fs({if:e.errorCondition.if},r,this.bindingEngine,`errorCondition:${e.id}`))return Ct.log(`errorCondition matched for ${e.id}, triggering error ${e.errorCondition.errorCode}`),e.errorCondition.errorCode}catch(r){Ct.error(`Failed to evaluate errorCondition for ${e.id}:`,r)}return null}async fetchDataSources(e,n={},r=new URLSearchParams,i,l){const c={},d=!!this.options.sampleProvider,f=e.filter(h=>(d||h.auto_fetch!==!1)&&h.type!=="websocket");return await Promise.all(f.map(async h=>{try{const m=await this.fetchDataSource(h,n,r,i,l);c[h.id]=m,this.dataCache.set(h.id,{data:m,timestamp:Date.now()});const y=this.checkErrorCondition(h,m);if(y!==null){const v={status:y,message:`Error condition matched for ${h.id}`,data:m},S=Tr(),_=S.resolve(y,{errorHandling:h.errorHandling,onError:h.onError});if(_.handler)try{await S.execute(_.handler,v)}catch(x){Ct.error(`Error executing errorCondition handler for ${h.id}:`,x)}h.fallback!==void 0&&(c[h.id]=h.fallback,this.dataCache.set(h.id,{data:h.fallback,timestamp:Date.now()}),Ct.log(`Using fallback data for ${h.id} (errorCondition)`));return}if(h.onSuccess)try{const v={data:m,sourceId:h.id};await this.executeOnSuccessHandler(h.onSuccess,v)}catch(v){Ct.error(`Error executing onSuccess handler for ${h.id}:`,v)}}catch(m){Ct.error(`Failed to fetch data source: ${h.id}`,m);const y=m?.response?.status||m?.status||500,v=m?.response?.data,S=v?.message||m.message||"Unknown error",_={status:y,message:S,errors:v?.errors,data:v,statusText:m?.response?.statusText},x=Tr(),A=x.resolve(y,{errorHandling:h.errorHandling,onError:h.onError});if(A.handler)try{await x.execute(A.handler,_)}catch(D){Ct.error(`Error executing error handler for ${h.id}:`,D)}h.fallback!==void 0&&(c[h.id]=h.fallback,this.dataCache.set(h.id,{data:h.fallback,timestamp:Date.now()}),Ct.log(`Using fallback data for ${h.id}`)),this.options.onError&&this.options.onError(m,h)}})),c}async fetchDataSourcesWithResults(e,n={},r=new URLSearchParams,i,l,c){if(e.some(m=>m.type==="websocket")){const m=e.filter(y=>y.type==="websocket").map(y=>y.id).join(", ");Ct.warn(`fetchDataSourcesWithResults received WebSocket sources (caller should filter them out before calling): ${m}. WebSocket sources are event listeners, not data providers — they should not be passed to fetch.`)}const f=c?.ignoreAutoFetch?e.filter(m=>m.type!=="websocket"):e.filter(m=>m.auto_fetch!==!1&&m.type!=="websocket");return await Promise.all(f.map(async m=>{try{const y=await this.fetchDataSource(m,n,r,i,l);this.dataCache.set(m.id,{data:y,timestamp:Date.now()});const v=this.checkErrorCondition(m,y);if(v!==null){const S={status:v,message:`Error condition matched for ${m.id}`,data:y},_=Tr(),x=_.resolve(v,{errorHandling:m.errorHandling,onError:m.onError});let A=!1;if(x.handler)if(m.loading_strategy==="blocking")_.execute(x.handler,S).catch(D=>{Ct.error(`Error executing errorCondition handler for ${m.id}:`,D)}),A=!0;else try{await _.execute(x.handler,S),A=!0}catch(D){Ct.error(`Error executing errorCondition handler for ${m.id}:`,D)}return m.fallback!==void 0?(this.dataCache.set(m.id,{data:m.fallback,timestamp:Date.now()}),Ct.log(`Using fallback data for ${m.id} (errorCondition)`),{id:m.id,state:"success",data:m.fallback}):{id:m.id,state:"error",error:new Error(`Error condition matched: ${v}`),errorCode:v,errorHandled:A}}if(m.onSuccess)try{const S={data:y,sourceId:m.id};await this.executeOnSuccessHandler(m.onSuccess,S)}catch(S){Ct.error(`Error executing onSuccess handler for ${m.id}:`,S)}return{id:m.id,state:"success",data:y}}catch(y){Ct.error(`Failed to fetch data source: ${m.id}`,y);const v=y?.response?.status||y?.status||500,S=y?.response?.data,_=S?.message||y.message||"Unknown error",x={status:v,message:_,errors:S?.errors,data:S,statusText:y?.response?.statusText},A=Tr(),D=A.resolve(v,{errorHandling:m.errorHandling,onError:m.onError});let O=!1;if(D.handler)if(m.loading_strategy==="blocking")A.execute(D.handler,x).catch(k=>{Ct.error(`Error executing error handler for ${m.id}:`,k)}),O=!0;else try{await A.execute(D.handler,x),O=!0}catch(k){Ct.error(`Error executing error handler for ${m.id}:`,k)}return m.fallback!==void 0?(this.dataCache.set(m.id,{data:m.fallback,timestamp:Date.now()}),Ct.log(`Using fallback data for ${m.id}`),{id:m.id,state:"success",data:m.fallback}):(this.options.onError&&this.options.onError(y,m),{id:m.id,state:"error",error:y,errorCode:v,errorHandled:O})}}))}fetchDataSourcesInBackground(e,n={},r=new URLSearchParams,i){e.filter(c=>c.auto_fetch!==!1&&c.loading_strategy==="background"&&c.type!=="websocket").forEach(c=>{this.fetchDataSource(c,n,r).then(async d=>{this.dataCache.set(c.id,{data:d,timestamp:Date.now()});const f=this.checkErrorCondition(c,d);if(f!==null){const h={status:f,message:`Error condition matched for ${c.id}`,data:d},m=Tr(),y=m.resolve(f,{errorHandling:c.errorHandling,onError:c.onError});if(y.handler)try{await m.execute(y.handler,h)}catch(v){Ct.error(`Error executing errorCondition handler for ${c.id}:`,v)}c.fallback!==void 0&&(this.dataCache.set(c.id,{data:c.fallback,timestamp:Date.now()}),Ct.log(`Using fallback data for ${c.id} (errorCondition)`),i&&i(c.id,c.fallback));return}if(c.onSuccess)try{const h={data:d,sourceId:c.id};await this.executeOnSuccessHandler(c.onSuccess,h)}catch(h){Ct.error(`Error executing onSuccess handler for ${c.id}:`,h)}i&&i(c.id,d)}).catch(async d=>{Ct.error(`Background fetch failed: ${c.id}`,d);const f=d?.response?.status||d?.status||500,h=d?.response?.data,m=h?.message||d.message||"Unknown error",y={status:f,message:m,errors:h?.errors,data:h,statusText:d?.response?.statusText},v=Tr(),S=v.resolve(f,{errorHandling:c.errorHandling,onError:c.onError});if(S.handler)try{await v.execute(S.handler,y)}catch(_){Ct.error(`Error executing error handler for ${c.id}:`,_)}if(c.fallback!==void 0){this.dataCache.set(c.id,{data:c.fallback,timestamp:Date.now()}),Ct.log(`Using fallback data for ${c.id}`),i&&i(c.id,c.fallback);return}this.options.onError&&this.options.onError(d,c)})})}async fetchDataSource(e,n,r,i,l){switch(e.type){case"api":return this.fetchApiDataSource(e,n,r,i,l);case"static":return e.data;case"route_params":return n;case"query_params":return Ab(r);default:throw new Error(`Unknown data source type: ${e.type}`)}}async fetchApiDataSource(e,n,r,i,l){if(this.options.sampleProvider?.has(e.id))return await Promise.resolve(this.options.sampleProvider.resolve(e));if(!e.endpoint)throw new Error(`API data source ${e.id} has no endpoint`);const c={route:n,query:Ab(r)};i&&(c._global=i),l&&(c._local=l);let d=hs(e.endpoint,c,{skipCache:!0});const f=e.method||"GET",m=(e.contentType?typeof e.contentType=="string"&&e.contentType.includes("{{")?hs(e.contentType,c,{skipCache:!0}):e.contentType:"application/json")==="multipart/form-data";let y=m?{...e.params||{}}:JSON.parse(JSON.stringify(e.params||{}));Object.entries(y).forEach(([_,x])=>{typeof x=="string"&&(y[_]=Fc(x,c))}),Object.keys(y).forEach(_=>{y[_]===""&&delete y[_]});const v=qx();let S=null;if(v?.isEnabled()){v.trackDataSourceDefinition({id:e.id,type:e.type,endpoint:d,method:f,autoFetch:e.auto_fetch,initLocal:e.initLocal,initGlobal:e.initGlobal});let _=d;if(f==="GET"&&Object.keys(y).length>0){const x=new URLSearchParams;Object.entries(y).forEach(([D,O])=>{Array.isArray(O)?O.forEach(k=>x.append(D,String(k))):O!=null&&x.append(D,String(O))});const A=x.toString();_=A?`${d}?${A}`:d}S=v.trackRequest(_,f,{requestBody:f!=="GET"?y:void 0,dataSourceId:e.id}),v.trackDataSourceLoading(e.id)}try{let _;const x=e.auth_mode??(e.auth_required===!0?"required":"none"),A=Ar.getInstance().isAuthenticated();if(x==="required"&&!A){Ct.log(`[DataSourceManager] Skipping ${e.id}: auth_mode is 'required' but no token available`),v?.isEnabled()&&(v.trackDataSourceError(e.id,"Skipped: auth_mode required but not authenticated"),S&&v.failRequest(S,"Skipped: not authenticated"));const O=new Error("Auth required but not authenticated");throw O.response={status:401,statusText:"Unauthorized",data:null},O.status=401,O._authSkipped=!0,O}const D=x==="required"||x==="optional"&&A;if(D){const O=qr(),k=d.startsWith("/api/")?d.substring(4):d.startsWith("/api")?d.substring(4)||"/":d;this.options.onError&&Ct.log(`[DataSourceManager] Normalized endpoint: ${d} -> ${k}`);const L=this.getMatchingGlobalHeaders(d,c),B={};e.headers&&Object.entries(e.headers).forEach(([de,_e])=>{const Se=Fc(_e,c);Se!=null&&Se!==""&&(B[de]=String(Se))});const q={...L,...B},z=Object.keys(q).length>0,W=m?this.toFormData(y):y;switch(f){case"GET":_=await O.get(k,{params:y,...z&&{headers:q}});break;case"POST":z?_=await O.post(k,W,{headers:q}):_=await O.post(k,W);break;case"PUT":z?_=await O.put(k,W,{headers:q}):_=await O.put(k,W);break;case"PATCH":z?_=await O.patch(k,W,{headers:q}):_=await O.patch(k,W);break;case"DELETE":_=await O.delete(k,{params:y,...z&&{headers:q}});break;default:throw new Error(`Unsupported HTTP method: ${f}`)}}else{let O=d;if(f==="GET"){const W=new URLSearchParams(y).toString();O=W?`${d}?${W}`:d}const k={...m?{}:{"Content-Type":"application/json"},Accept:"application/json"};if(typeof window<"u"){const W=localStorage.getItem("g7_locale");W&&(k["Accept-Language"]=W)}const L=this.getMatchingGlobalHeaders(d,c);Object.assign(k,L),e.headers&&Object.entries(e.headers).forEach(([W,de])=>{const _e=Fc(de,c);_e!=null&&_e!==""&&(k[W]=String(_e))});let B;f!=="GET"&&(m?(B=this.toFormData(y),delete k["Content-Type"]):B=JSON.stringify(y));const q=await fetch(O,{method:f,headers:k,body:B});let z;try{z=await q.json()}catch{z=null}if(S&&v?.isEnabled()&&(v.completeRequest(S,q.status,z),S=null),!q.ok){v?.isEnabled()&&v.trackDataSourceError(e.id,`HTTP ${q.status}: ${q.statusText}`);const W=new Error(`HTTP error! status: ${q.status}`);throw W.response={status:q.status,statusText:q.statusText,data:z},W.status=q.status,W}_=z,v?.isEnabled()&&v.trackDataSourceLoaded(e.id,_)}return D&&S&&v?.isEnabled()&&(v.completeRequest(S,200,_),v.trackDataSourceLoaded(e.id,_)),_}catch(_){if(S&&v?.isEnabled()){const x=_;x?.response?v.completeRequest(S,x.response.status,x.response.data):v.failRequest(S,_ instanceof Error?_.message:String(_)),v.trackDataSourceError(e.id,_ instanceof Error?_.message:String(_))}throw _}}getCachedData(e){const n=this.dataCache.get(e);if(!n)return;if(Date.now()-n.timestamp>Qc.DATA_CACHE_TTL){this.dataCache.delete(e);return}return n.data}clearCache(){this.dataCache.clear()}subscribeWebSockets(e,n,r={}){const i=[],l=e.filter(c=>c.type==="websocket");return l.length===0||l.forEach(c=>{if(!c.channel||!c.event){Ct.warn(`WebSocket source ${c.id} missing channel or event`);return}let d,f;try{d=hs(c.channel,r,{skipCache:!0}),f=hs(c.event,r,{skipCache:!0})}catch(y){Ct.error(`Failed to resolve WebSocket channel/event for source ${c.id}:`,y);return}const h=y=>!!(!y||y.trim()===""||y.includes("{{")||y.endsWith(".")||y.endsWith(":")||y.startsWith(".")||y.startsWith(":")||y.includes("..")||y.includes("::"));if(h(d)||h(f)){Ct.warn(`WebSocket source ${c.id} has invalid channel/event after expression resolution, skipping. Original channel="${c.channel}", resolved="${d}". Original event="${c.event}", resolved="${f}". Available context keys: [${Object.keys(r).join(", ")}]`);return}const m=Gc.subscribe(d,f,y=>{const v=c.target_source||c.id;this.dataCache.set(v,{data:y,timestamp:Date.now()}),n(v,y)},{channelType:c.channel_type||"private"});m&&i.push(m)}),i}unsubscribeWebSockets(e){e.length!==0&&e.forEach(n=>{Gc.unsubscribe(n)})}async executeOnSuccessHandler(e,n){const r=this.options.actionDispatcher??Cy();if(!r){Ct.warn("ActionDispatcher not available, skipping onSuccess handler");return}const i=Array.isArray(e)?e:[e];for(const l of i)try{await r.dispatchAction({type:"click",...l},{data:{response:n}})}catch(c){throw Ct.error("Failed to execute onSuccess handler:",c),c}}};M(Qc,"DATA_CACHE_TTL",3e5);let Dr=Qc;const Gx=new Dr,ki=pt("ModalDataSourceWrapper");function Fx(s,e){if(!s||!e)return s;const r=e.replace(/\?\./g,".").split(/\.(?![^\[]*\])/).flatMap(l=>{const c=l.match(/^([^\[]*)((?:\[\d+\])*)$/);if(c){const[,d,f]=c,h=[];d&&h.push(d);const m=f.match(/\[\d+\]/g);return m&&h.push(...m),h}return[l]}).filter(l=>l!=="");let i=s;for(const l of r){if(i==null)return;if(l.startsWith("[")&&l.endsWith("]")){const c=parseInt(l.slice(1,-1),10);i=i[c]}else i=i[l]}return i}const xb=({isOpen:s,modalId:e,dataSources:n,dataContext:r,globalStateUpdater:i,bindingEngine:l,debug:c=!1,children:d})=>{const[f,h]=I.useState(!1),[m,y]=I.useState(!1),v=I.useRef(!1),S=I.useRef(null);S.current||(S.current=new Dr);const _=I.useCallback(async()=>{if(!(!n||n.length===0)&&S.current){h(!0);try{const x=r.route||{},A=new URLSearchParams(window.location.search),D=n.map(k=>{let L={...k};if(k.endpoint&&l){const B=l.resolveBindings(k.endpoint,r,{skipCache:!0});L.endpoint=B}if(k.params&&l){const B={};Object.entries(k.params).forEach(([q,z])=>{typeof z=="string"?B[q]=l.resolveBindings(z,r,{skipCache:!0}):B[q]=z}),L.params=B}return L});c&&ki.log(`[ModalDataSourceWrapper] Modal "${e}" fetching data sources:`,D.map(k=>({id:k.id,endpoint:k.endpoint})));const O=await S.current.fetchDataSourcesWithResults(D,x,A);i&&O.forEach(k=>{if(k.state==="success"&&k.data!==void 0){const L=D.find(B=>B.id===k.id);if(L?.initGlobal){const B=k.data?.data??k.data,q=Array.isArray(L.initGlobal)?L.initGlobal:[L.initGlobal];for(const z of q)if(typeof z=="string")i({[z]:B}),c&&ki.log(`[ModalDataSourceWrapper] Modal "${e}" initGlobal: ${k.id}.data -> _global.${z}`);else if(typeof z=="object"&&z.key){const{key:W,path:de}=z,_e=de?Fx(B,de):B;i({[W]:_e}),c&&ki.log(`Modal "${e}" initGlobal: ${k.id}.data${de?"."+de:""} -> _global.${W}`)}}}else k.state==="error"&&ki.error(`Modal "${e}" data source error:`,k.id,k.error)}),c&&ki.log(`Modal "${e}" data sources fetched successfully`)}catch(x){ki.error(`Modal "${e}" data source fetch error:`,x)}finally{h(!1),y(!0)}}},[n,r,i,l,e,c]);return I.useEffect(()=>{if(!n||n.length===0)return;const x=window.__templateApp;return x?.registerModalDataSources&&x.registerModalDataSources(e,n),()=>{x?.unregisterModalDataSources&&x.unregisterModalDataSources(e)}},[e,n]),I.useEffect(()=>{!s&&v.current&&(y(!1),c&&ki.log(`[ModalDataSourceWrapper] Modal "${e}" closed, reset fetch state`)),s&&!v.current&&n&&n.length>0&&!m&&_(),v.current=s},[s,n,m,_,e,c]),yt.jsx(yt.Fragment,{children:d})};class Vc extends Error{constructor(n,r,i={}){super(r);M(this,"code");M(this,"userMessageKey");M(this,"showStack");M(this,"icon");M(this,"recoverable");this.name="TemplateEngineError",this.code=n,this.userMessageKey=r,this.showStack=i.showStack??!0,this.icon=i.icon??"fa-circle-exclamation",this.recoverable=i.recoverable??!0,Error.captureStackTrace&&Error.captureStackTrace(this,this.constructor)}getUserMessage(n,r){if(!r||!n)return this.userMessageKey;try{return r.translate(this.userMessageKey,n)}catch{return this.userMessageKey}}}class Vx extends Vc{constructor(){super("TEMPLATE_NOT_FOUND","$t:core.errors.template_not_found",{showStack:!1,icon:"fa-paint-brush",recoverable:!1}),this.name="TemplateNotFoundError"}}function Tb(s){if(s instanceof Vc)return s;if(s instanceof Error){const e=new Vc("TEMPLATE_INIT_ERROR",s.message||"$t:core.errors.template_init_error",{showStack:!0,icon:"fa-circle-exclamation",recoverable:!0});return e.stack=s.stack,e}return new Vc("TEMPLATE_INIT_ERROR","$t:core.errors.template_init_error",{showStack:!1,icon:"fa-circle-exclamation",recoverable:!0})}const Kx=pt("ErrorDisplay");class uh{static render(e,n){const r=document.getElementById(e);if(!r){Kx.error(`Container #${e} not found`);return}const i=n.debug&&n.showStack&&n.stackTrace,l=window.matchMedia("(prefers-color-scheme: dark)").matches,c={container:` + `,Qn.error("Error state rendered:",i,r)}getCurrentLayout(){return this.currentLayout}clear(){this.currentLayout=null,this.layoutCache.clear(),Qn.log("Layout data and cache cleared")}}const kx=pt("TranslationContext"),nh=N.createContext(null),Pc=({children:s,translationEngine:e,translationContext:n})=>{const r=N.useMemo(()=>(l,c)=>{if(c){const d="|"+Object.entries(c).map(([f,h])=>`${f}=${h}`).join("|");return e.translate(l,n,d)}return e.translate(l,n)},[e,n]),i=N.useMemo(()=>({translationEngine:e,translationContext:n,t:r}),[e,n,r]);return yt.jsx(nh.Provider,{value:i,children:s})},gb=()=>{const s=N.useContext(nh);return s||(kx.warn("TranslationProvider 외부에서 호출되었습니다. 키를 그대로 반환합니다."),{t:e=>e,translationEngine:null,translationContext:null})},za=pt("SlotContext"),Rx={registerToSlot:()=>{za.warn("SlotContext not available: registerToSlot called outside SlotProvider")},unregisterFromSlot:()=>{za.warn("SlotContext not available: unregisterFromSlot called outside SlotProvider")},getSlotComponents:()=>[],subscribeToSlot:()=>()=>{},clearAllSlots:()=>{},isEnabled:!1},Dx=N.createContext(Rx),rh=({children:s})=>{const e=N.useRef(new Map),n=N.useRef(new Map),r=N.useCallback(m=>{const y=n.current.get(m);y&&y.forEach(v=>{try{v()}catch(S){za.error(`Slot subscriber error (slotId: ${m}):`,S)}})},[]),i=N.useCallback((m,y,v)=>{if(!m||!y){za.warn("registerToSlot: slotId and componentId are required");return}e.current.has(m)||e.current.set(m,new Map);const S=e.current.get(m),_=S.get(y);_&&_.registrationKey===v.registrationKey||(S.set(y,v),za.log(`Component registered to slot: ${y} -> ${m} (order: ${v.order})`),r(m))},[r]),l=N.useCallback((m,y)=>{const v=e.current.get(m);v&&v.has(y)&&(v.delete(y),za.log(`Component unregistered from slot: ${y} <- ${m}`),v.size===0&&e.current.delete(m),r(m))},[r]),c=N.useCallback(m=>{const y=e.current.get(m);return y?Array.from(y.values()).sort((v,S)=>v.order-S.order):[]},[]),d=N.useCallback((m,y)=>{n.current.has(m)||n.current.set(m,new Set);const v=n.current.get(m);return v.add(y),()=>{v.delete(y),v.size===0&&n.current.delete(m)}},[]),f=N.useCallback(()=>{e.current.forEach((m,y)=>{r(y)}),e.current.clear(),n.current.clear(),za.log("All slots cleared")},[r]),h=N.useMemo(()=>({registerToSlot:i,unregisterFromSlot:l,getSlotComponents:c,subscribeToSlot:d,clearAllSlots:f,isEnabled:!0}),[i,l,c,d,f]);return N.useLayoutEffect(()=>{typeof window<"u"&&(window.__slotContextValue=h,za.log("SlotContext exposed to window.__slotContextValue"))},[h]),yt.jsx(Dx.Provider,{value:h,children:s})};class ah{constructor(){this.notificationCreatedEvent=".Illuminate\\Notifications\\Events\\BroadcastNotificationCreated"}listenForWhisper(e,n){return this.listen(".client-"+e,n)}notification(e){return this.listen(this.notificationCreatedEvent,e)}stopListeningForNotification(e){return this.stopListening(this.notificationCreatedEvent,e)}stopListeningForWhisper(e,n){return this.stopListening(".client-"+e,n)}}class mb{constructor(e){this.namespace=e}format(e){return[".","\\"].includes(e.charAt(0))?e.substring(1):(this.namespace&&(e=this.namespace+"."+e),e.replace(/\./g,"\\"))}setNamespace(e){this.namespace=e}}function Ox(s){try{return Reflect.construct(String,[],s),!0}catch{return!1}}class ih extends ah{constructor(e,n,r){super(),this.name=n,this.pusher=e,this.options=r,this.eventFormatter=new mb(this.options.namespace),this.subscribe()}subscribe(){this.subscription=this.pusher.subscribe(this.name)}unsubscribe(){this.pusher.unsubscribe(this.name)}listen(e,n){return this.on(this.eventFormatter.format(e),n),this}listenToAll(e){return this.subscription.bind_global((n,r)=>{if(n.startsWith("pusher:"))return;let i=String(this.options.namespace??"").replace(/\./g,"\\"),l=n.startsWith(i)?n.substring(i.length+1):"."+n;e(l,r)}),this}stopListening(e,n){return n?this.subscription.unbind(this.eventFormatter.format(e),n):this.subscription.unbind(this.eventFormatter.format(e)),this}stopListeningToAll(e){return e?this.subscription.unbind_global(e):this.subscription.unbind_global(),this}subscribed(e){return this.on("pusher:subscription_succeeded",()=>{e()}),this}error(e){return this.on("pusher:subscription_error",n=>{e(n)}),this}on(e,n){return this.subscription.bind(e,n),this}}class yb extends ih{whisper(e,n){return this.pusher.channels.channels[this.name].trigger(`client-${e}`,n),this}}class Lx extends ih{whisper(e,n){return this.pusher.channels.channels[this.name].trigger(`client-${e}`,n),this}}class $x extends yb{here(e){return this.on("pusher:subscription_succeeded",n=>{e(Object.keys(n.members).map(r=>n.members[r]))}),this}joining(e){return this.on("pusher:member_added",n=>{e(n.info)}),this}whisper(e,n){return this.pusher.channels.channels[this.name].trigger(`client-${e}`,n),this}leaving(e){return this.on("pusher:member_removed",n=>{e(n.info)}),this}}class bb extends ah{constructor(e,n,r){super(),this.events={},this.listeners={},this.name=n,this.socket=e,this.options=r,this.eventFormatter=new mb(this.options.namespace),this.subscribe()}subscribe(){this.socket.emit("subscribe",{channel:this.name,auth:this.options.auth||{}})}unsubscribe(){this.unbind(),this.socket.emit("unsubscribe",{channel:this.name,auth:this.options.auth||{}})}listen(e,n){return this.on(this.eventFormatter.format(e),n),this}stopListening(e,n){return this.unbindEvent(this.eventFormatter.format(e),n),this}subscribed(e){return this.on("connect",n=>{e(n)}),this}error(e){return this}on(e,n){return this.listeners[e]=this.listeners[e]||[],this.events[e]||(this.events[e]=(r,i)=>{this.name===r&&this.listeners[e]&&this.listeners[e].forEach(l=>l(i))},this.socket.on(e,this.events[e])),this.listeners[e].push(n),this}unbind(){Object.keys(this.events).forEach(e=>{this.unbindEvent(e)})}unbindEvent(e,n){this.listeners[e]=this.listeners[e]||[],n&&(this.listeners[e]=this.listeners[e].filter(r=>r!==n)),(!n||this.listeners[e].length===0)&&(this.events[e]&&(this.socket.removeListener(e,this.events[e]),delete this.events[e]),delete this.listeners[e])}}class vb extends bb{whisper(e,n){return this.socket.emit("client event",{channel:this.name,event:`client-${e}`,data:n}),this}}class Mx extends vb{here(e){return this.on("presence:subscribed",n=>{e(n.map(r=>r.user_info))}),this}joining(e){return this.on("presence:joining",n=>e(n.user_info)),this}whisper(e,n){return this.socket.emit("client event",{channel:this.name,event:`client-${e}`,data:n}),this}leaving(e){return this.on("presence:leaving",n=>e(n.user_info)),this}}class Uc extends ah{subscribe(){}unsubscribe(){}listen(e,n){return this}listenToAll(e){return this}stopListening(e,n){return this}subscribed(e){return this}error(e){return this}on(e,n){return this}}class Sb extends Uc{whisper(e,n){return this}}class Nx extends Uc{whisper(e,n){return this}}class Ix extends Sb{here(e){return this}joining(e){return this}whisper(e,n){return this}leaving(e){return this}}const wb=class a0{constructor(e){this.setOptions(e),this.connect()}setOptions(e){this.options={...a0._defaultOptions,...e,broadcaster:e.broadcaster};let n=this.csrfToken();n&&(this.options.auth.headers["X-CSRF-TOKEN"]=n,this.options.userAuthentication.headers["X-CSRF-TOKEN"]=n),n=this.options.bearerToken,n&&(this.options.auth.headers.Authorization="Bearer "+n,this.options.userAuthentication.headers.Authorization="Bearer "+n)}csrfToken(){var e,n;return typeof window<"u"&&(e=window.Laravel)!=null&&e.csrfToken?window.Laravel.csrfToken:this.options.csrfToken?this.options.csrfToken:typeof document<"u"&&typeof document.querySelector=="function"?((n=document.querySelector('meta[name="csrf-token"]'))==null?void 0:n.getAttribute("content"))??null:null}};wb._defaultOptions={auth:{headers:{}},authEndpoint:"/broadcasting/auth",userAuthentication:{endpoint:"/broadcasting/user-auth",headers:{}},csrfToken:null,bearerToken:null,host:null,key:null,namespace:"App.Events"};let sh=wb;class Bc extends sh{constructor(){super(...arguments),this.channels={}}connect(){if(typeof this.options.client<"u")this.pusher=this.options.client;else if(this.options.Pusher)this.pusher=new this.options.Pusher(this.options.key,this.options);else if(typeof window<"u"&&typeof window.Pusher<"u")this.pusher=new window.Pusher(this.options.key,this.options);else throw new Error("Pusher client not found. Should be globally available or passed via options.client")}signin(){this.pusher.signin()}listen(e,n,r){return this.channel(e).listen(n,r)}channel(e){return this.channels[e]||(this.channels[e]=new ih(this.pusher,e,this.options)),this.channels[e]}privateChannel(e){return this.channels["private-"+e]||(this.channels["private-"+e]=new yb(this.pusher,"private-"+e,this.options)),this.channels["private-"+e]}encryptedPrivateChannel(e){return this.channels["private-encrypted-"+e]||(this.channels["private-encrypted-"+e]=new Lx(this.pusher,"private-encrypted-"+e,this.options)),this.channels["private-encrypted-"+e]}presenceChannel(e){return this.channels["presence-"+e]||(this.channels["presence-"+e]=new $x(this.pusher,"presence-"+e,this.options)),this.channels["presence-"+e]}leave(e){[e,"private-"+e,"private-encrypted-"+e,"presence-"+e].forEach(n=>{this.leaveChannel(n)})}leaveChannel(e){this.channels[e]&&(this.channels[e].unsubscribe(),delete this.channels[e])}socketId(){return this.pusher.connection.socket_id}connectionStatus(){const e=this.pusher.connection.state;switch(e){case"connected":case"connecting":return e;case"failed":case"unavailable":return"failed";default:return"disconnected"}}onConnectionChange(e){const n=()=>{e(this.connectionStatus())},r=["state_change","connected","disconnected"];return r.forEach(i=>{this.pusher.connection.bind(i,n)}),()=>{r.forEach(i=>{this.pusher.connection.unbind(i,n)})}}disconnect(){this.pusher.disconnect()}}class jx extends sh{constructor(){super(...arguments),this.channels={}}connect(){let e=this.getSocketIO();this.socket=e(this.options.host??void 0,this.options),this.socket.io.on("reconnect",()=>{Object.values(this.channels).forEach(n=>{n.subscribe()})})}getSocketIO(){if(typeof this.options.client<"u")return this.options.client;if(typeof window<"u"&&typeof window.io<"u")return window.io;throw new Error("Socket.io client not found. Should be globally available or passed via options.client")}listen(e,n,r){return this.channel(e).listen(n,r)}channel(e){return this.channels[e]||(this.channels[e]=new bb(this.socket,e,this.options)),this.channels[e]}privateChannel(e){return this.channels["private-"+e]||(this.channels["private-"+e]=new vb(this.socket,"private-"+e,this.options)),this.channels["private-"+e]}presenceChannel(e){return this.channels["presence-"+e]||(this.channels["presence-"+e]=new Mx(this.socket,"presence-"+e,this.options)),this.channels["presence-"+e]}leave(e){[e,"private-"+e,"presence-"+e].forEach(n=>{this.leaveChannel(n)})}leaveChannel(e){this.channels[e]&&(this.channels[e].unsubscribe(),delete this.channels[e])}socketId(){return this.socket.id}connectionStatus(){return this.socket.connected?"connected":this.socket.io._reconnecting?"reconnecting":this.socket.id!==void 0?"disconnected":"connecting"}onConnectionChange(e){const n=()=>{e(this.connectionStatus())},r=["connect","disconnect","connect_error","reconnect_attempt","reconnect","reconnect_error","reconnect_failed"];return r.forEach(i=>{this.socket.on(i,n)}),()=>{r.forEach(i=>{this.socket.off(i,n)})}}disconnect(){this.socket.disconnect()}}class Cb extends sh{constructor(){super(...arguments),this.channels={}}connect(){}listen(e,n,r){return new Uc}channel(e){return new Uc}privateChannel(e){return new Sb}encryptedPrivateChannel(e){return new Nx}presenceChannel(e){return new Ix}leave(e){}leaveChannel(e){}socketId(){return"fake-socket-id"}connectionStatus(){return"connected"}onConnectionChange(e){return()=>{}}disconnect(){}}class Hx{constructor(e){this.options=e,this.connect(),this.options.withoutInterceptors||this.registerInterceptors()}channel(e){return this.connector.channel(e)}connect(){if(this.options.broadcaster==="reverb")this.connector=new Bc({...this.options,cluster:""});else if(this.options.broadcaster==="pusher")this.connector=new Bc(this.options);else if(this.options.broadcaster==="ably")this.connector=new Bc({...this.options,cluster:"",broadcaster:"pusher"});else if(this.options.broadcaster==="socket.io")this.connector=new jx(this.options);else if(this.options.broadcaster==="null")this.connector=new Cb(this.options);else if(typeof this.options.broadcaster=="function"&&Ox(this.options.broadcaster))this.connector=new this.options.broadcaster(this.options);else throw new Error(`Broadcaster ${typeof this.options.broadcaster} ${String(this.options.broadcaster)} is not supported.`)}disconnect(){this.connector.disconnect()}join(e){return this.connector.presenceChannel(e)}leave(e){this.connector.leave(e)}leaveChannel(e){this.connector.leaveChannel(e)}leaveAllChannels(){for(const e in this.connector.channels)this.leaveChannel(e)}listen(e,n,r){return this.connector.listen(e,n,r)}private(e){return this.connector.privateChannel(e)}encryptedPrivate(e){if(this.connectorSupportsEncryptedPrivateChannels(this.connector))return this.connector.encryptedPrivateChannel(e);throw new Error(`Broadcaster ${typeof this.options.broadcaster} ${String(this.options.broadcaster)} does not support encrypted private channels.`)}connectorSupportsEncryptedPrivateChannels(e){return e instanceof Bc||e instanceof Cb}socketId(){return this.connector.socketId()}connectionStatus(){return this.connector.connectionStatus()}registerInterceptors(){typeof Vue<"u"&&Vue!=null&&Vue.http&&this.registerVueRequestInterceptor(),typeof axios=="function"&&this.registerAxiosRequestInterceptor(),typeof jQuery=="function"&&this.registerjQueryAjaxSetup(),typeof Turbo=="object"&&this.registerTurboRequestInterceptor()}registerVueRequestInterceptor(){Vue.http.interceptors.push((e,n)=>{this.socketId()&&e.headers.set("X-Socket-ID",this.socketId()),n()})}registerAxiosRequestInterceptor(){axios.interceptors.request.use(e=>(this.socketId()&&(e.headers["X-Socket-Id"]=this.socketId()),e))}registerjQueryAjaxSetup(){typeof jQuery.ajax<"u"&&jQuery.ajaxPrefilter((e,n,r)=>{this.socketId()&&r.setRequestHeader("X-Socket-Id",this.socketId())})}registerTurboRequestInterceptor(){document.addEventListener("turbo:before-fetch-request",e=>{e.detail.fetchOptions.headers["X-Socket-Id"]=this.socketId()})}}var oh={exports:{}};/*! + * Pusher JavaScript Library v8.5.0 + * https://pusher.com/ + * + * Copyright 2020, Pusher + * Released under the MIT licence. + */var Eb;function zx(){return Eb||(Eb=1,(function(s,e){(function(r,i){s.exports=i()})(self,()=>(()=>{var n={594(c,d){var f=this&&this.__extends||(function(){var O=function(k,L){return O=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(B,q){B.__proto__=q}||function(B,q){for(var z in q)q.hasOwnProperty(z)&&(B[z]=q[z])},O(k,L)};return function(k,L){O(k,L);function B(){this.constructor=k}k.prototype=L===null?Object.create(L):(B.prototype=L.prototype,new B)}})();Object.defineProperty(d,"__esModule",{value:!0});var h=256,m=(function(){function O(k){k===void 0&&(k="="),this._paddingCharacter=k}return O.prototype.encodedLength=function(k){return this._paddingCharacter?(k+2)/3*4|0:(k*8+5)/6|0},O.prototype.encode=function(k){for(var L="",B=0;B>>18&63),L+=this._encodeByte(q>>>12&63),L+=this._encodeByte(q>>>6&63),L+=this._encodeByte(q>>>0&63)}var z=k.length-B;if(z>0){var q=k[B]<<16|(z===2?k[B+1]<<8:0);L+=this._encodeByte(q>>>18&63),L+=this._encodeByte(q>>>12&63),z===2?L+=this._encodeByte(q>>>6&63):L+=this._paddingCharacter||"",L+=this._paddingCharacter||""}return L},O.prototype.maxDecodedLength=function(k){return this._paddingCharacter?k/4*3|0:(k*6+7)/8|0},O.prototype.decodedLength=function(k){return this.maxDecodedLength(k.length-this._getPaddingLength(k))},O.prototype.decode=function(k){if(k.length===0)return new Uint8Array(0);for(var L=this._getPaddingLength(k),B=k.length-L,q=new Uint8Array(this.maxDecodedLength(B)),z=0,W=0,de=0,_e=0,Se=0,je=0,Fe=0;W>>4,q[z++]=Se<<4|je>>>2,q[z++]=je<<6|Fe,de|=_e&h,de|=Se&h,de|=je&h,de|=Fe&h;if(W>>4,de|=_e&h,de|=Se&h),W>>2,de|=je&h),W>>8&6,L+=51-k>>>8&-75,L+=61-k>>>8&-15,L+=62-k>>>8&3,String.fromCharCode(L)},O.prototype._decodeChar=function(k){var L=h;return L+=(42-k&k-44)>>>8&-h+k-43+62,L+=(46-k&k-48)>>>8&-h+k-47+63,L+=(47-k&k-58)>>>8&-h+k-48+52,L+=(64-k&k-91)>>>8&-h+k-65+0,L+=(96-k&k-123)>>>8&-h+k-97+26,L},O.prototype._getPaddingLength=function(k){var L=0;if(this._paddingCharacter){for(var B=k.length-1;B>=0&&k[B]===this._paddingCharacter;B--)L++;if(k.length<4||L>2)throw new Error("Base64Coder: incorrect padding")}return L},O})();d.Coder=m;var y=new m;function v(O){return y.encode(O)}d.encode=v;function S(O){return y.decode(O)}d.decode=S;var _=(function(O){f(k,O);function k(){return O!==null&&O.apply(this,arguments)||this}return k.prototype._encodeByte=function(L){var B=L;return B+=65,B+=25-L>>>8&6,B+=51-L>>>8&-75,B+=61-L>>>8&-13,B+=62-L>>>8&49,String.fromCharCode(B)},k.prototype._decodeChar=function(L){var B=h;return B+=(44-L&L-46)>>>8&-h+L-45+62,B+=(94-L&L-96)>>>8&-h+L-95+63,B+=(47-L&L-58)>>>8&-h+L-48+52,B+=(64-L&L-91)>>>8&-h+L-65+0,B+=(96-L&L-123)>>>8&-h+L-97+26,B},k})(m);d.URLSafeCoder=_;var x=new _;function A(O){return x.encode(O)}d.encodeURLSafe=A;function D(O){return x.decode(O)}d.decodeURLSafe=D,d.encodedLength=function(O){return y.encodedLength(O)},d.maxDecodedLength=function(O){return y.maxDecodedLength(O)},d.decodedLength=function(O){return y.decodedLength(O)}},978(c,d){var f="utf8: invalid source encoding";function h(m){for(var y=[],v=0;v=m.length)throw new Error(f);var x=m[++v];if((x&192)!==128)throw new Error(f);S=(S&31)<<6|x&63,_=128}else if(S<240){if(v>=m.length-1)throw new Error(f);var x=m[++v],A=m[++v];if((x&192)!==128||(A&192)!==128)throw new Error(f);S=(S&15)<<12|(x&63)<<6|A&63,_=2048}else if(S<248){if(v>=m.length-2)throw new Error(f);var x=m[++v],A=m[++v],D=m[++v];if((x&192)!==128||(A&192)!==128||(D&192)!==128)throw new Error(f);S=(S&15)<<18|(x&63)<<12|(A&63)<<6|D&63,_=65536}else throw new Error(f);if(S<_||S>=55296&&S<=57343)throw new Error(f);if(S>=65536){if(S>1114111)throw new Error(f);S-=65536,y.push(String.fromCharCode(55296|S>>10)),S=56320|S&1023}}y.push(String.fromCharCode(S))}return y.join("")}d.D4=h},721(c,d,f){c.exports=f(207).default},207(c,d,f){f.d(d,{default:()=>Is});class h{constructor(b,C){this.lastId=0,this.prefix=b,this.name=C}create(b){this.lastId++;var C=this.lastId,$=this.prefix+C,j=this.name+"["+C+"]",ne=!1,ye=function(){ne||(b.apply(null,arguments),ne=!0)};return this[C]=ye,{number:C,id:$,name:j,callback:ye}}remove(b){delete this[b.number]}}var m=new h("_pusher_script_","Pusher.ScriptReceivers"),y={VERSION:"8.5.0",PROTOCOL:7,wsPort:80,wssPort:443,wsPath:"",httpHost:"sockjs.pusher.com",httpPort:80,httpsPort:443,httpPath:"/pusher",stats_host:"stats.pusher.com",authEndpoint:"/pusher/auth",authTransport:"ajax",activityTimeout:12e4,pongTimeout:3e4,unavailableTimeout:1e4,userAuthentication:{endpoint:"/pusher/user-auth",transport:"ajax"},channelAuthorization:{endpoint:"/pusher/auth",transport:"ajax"},cdn_http:"http://js.pusher.com",cdn_https:"https://js.pusher.com",dependency_suffix:""};const v=y;class S{constructor(b){this.options=b,this.receivers=b.receivers||m,this.loading={}}load(b,C,$){var j=this;if(j.loading[b]&&j.loading[b].length>0)j.loading[b].push($);else{j.loading[b]=[$];var ne=Ue.createScriptRequest(j.getPath(b,C)),ye=j.receivers.create(function(Le){if(j.receivers.remove(ye),j.loading[b]){var We=j.loading[b];delete j.loading[b];for(var ft=function(Vt){Vt||ne.cleanup()},vt=0;vt>>6)+Q(128|b&63):Q(224|b>>>12&15)+Q(128|b>>>6&63)+Q(128|b&63)},ee=function(E){return E.replace(/[^\x00-\x7F]/g,De)},me=function(E){var b=[0,2,1][E.length%3],C=E.charCodeAt(0)<<16|(E.length>1?E.charCodeAt(1):0)<<8|(E.length>2?E.charCodeAt(2):0),$=[ue.charAt(C>>>18),ue.charAt(C>>>12&63),b>=2?"=":ue.charAt(C>>>6&63),b>=1?"=":ue.charAt(C&63)];return $.join("")},H=window.btoa||function(E){return E.replace(/[\s\S]{1,3}/g,me)};class T{constructor(b,C,$,j){this.clear=C,this.timer=b(()=>{this.timer&&(this.timer=j(this.timer))},$)}isRunning(){return this.timer!==null}ensureAborted(){this.timer&&(this.clear(this.timer),this.timer=null)}}const fe=T;function he(E){window.clearTimeout(E)}function Ae(E){window.clearInterval(E)}class se extends fe{constructor(b,C){super(setTimeout,he,b,function($){return C(),null})}}class ge extends fe{constructor(b,C){super(setInterval,Ae,b,function($){return C(),$})}}var $e={now(){return Date.now?Date.now():new Date().valueOf()},defer(E){return new se(0,E)},method(E,...b){var C=Array.prototype.slice.call(arguments,1);return function($){return $[E].apply($,C.concat(arguments))}}};const Te=$e;function Ie(E,...b){for(var C=0;C{window.console&&window.console.log&&window.console.log(b)}}debug(...b){this.log(this.globalLog,b)}warn(...b){this.log(this.globalLogWarn,b)}error(...b){this.log(this.globalLogError,b)}globalLogWarn(b){window.console&&window.console.warn?window.console.warn(b):this.globalLog(b)}globalLogError(b){window.console&&window.console.error?window.console.error(b):this.globalLogWarn(b)}log(b,...C){var $=xt.apply(this,arguments);Is.log?Is.log($):Is.logToConsole&&b.bind(this)($)}}const it=new Mn;var Un=function(E,b,C,$,j){(C.headers!==void 0||C.headersProvider!=null)&&it.warn(`To send headers with the ${$.toString()} request, you must use AJAX, rather than JSONP.`);var ne=E.nextAuthCallbackID.toString();E.nextAuthCallbackID++;var ye=E.getDocument(),Le=ye.createElement("script");E.auth_callbacks[ne]=function(vt){j(null,vt)};var We="Pusher.auth_callbacks['"+ne+"']";Le.src=C.endpoint+"?callback="+encodeURIComponent(We)+"&"+b;var ft=ye.getElementsByTagName("head")[0]||ye.documentElement;ft.insertBefore(Le,ft.firstChild)};const vn=Un;class da{constructor(b){this.src=b}send(b){var C=this,$="Error loading "+C.src;C.script=document.createElement("script"),C.script.id=b.id,C.script.src=C.src,C.script.type="text/javascript",C.script.charset="UTF-8",C.script.addEventListener?(C.script.onerror=function(){b.callback($)},C.script.onload=function(){b.callback(null)}):C.script.onreadystatechange=function(){(C.script.readyState==="loaded"||C.script.readyState==="complete")&&b.callback(null)},C.script.async===void 0&&document.attachEvent&&/opera/i.test(navigator.userAgent)?(C.errorScript=document.createElement("script"),C.errorScript.id=b.id+"_error",C.errorScript.text=b.name+"('"+$+"');",C.script.async=C.errorScript.async=!1):C.script.async=!0;var j=document.getElementsByTagName("head")[0];j.insertBefore(C.script,j.firstChild),C.errorScript&&j.insertBefore(C.errorScript,C.script.nextSibling)}cleanup(){this.script&&(this.script.onload=this.script.onerror=null,this.script.onreadystatechange=null),this.script&&this.script.parentNode&&this.script.parentNode.removeChild(this.script),this.errorScript&&this.errorScript.parentNode&&this.errorScript.parentNode.removeChild(this.errorScript),this.script=null,this.errorScript=null}}class pr{constructor(b,C){this.url=b,this.data=C}send(b){if(!this.request){var C=Mi(this.data),$=this.url+"/"+b.number+"?"+C;this.request=Ue.createScriptRequest($),this.request.send(b)}}cleanup(){this.request&&this.request.cleanup()}}var un=function(E,b){return function(C,$){var j="http"+(b?"s":"")+"://",ne=j+(E.host||E.options.host)+E.options.path,ye=Ue.createJSONPRequest(ne,C),Le=Ue.ScriptReceivers.create(function(We,ft){m.remove(Le),ye.cleanup(),ft&&ft.host&&(E.host=ft.host),$&&$(We,ft)});ye.send(Le)}},Rn={name:"jsonp",getAgent:un};const dn=Rn;function V(E,b,C){var $=E+(b.useTLS?"s":""),j=b.useTLS?b.hostTLS:b.hostNonTLS;return $+"://"+j+C}function ie(E,b){var C="/app/"+E,$="?protocol="+v.PROTOCOL+"&client=js&version="+v.VERSION+(b?"&"+b:"");return C+$}var ce={getInitial:function(E,b){var C=(b.httpPath||"")+ie(E,"flash=false");return V("ws",b,C)}},Z={getInitial:function(E,b){var C=(b.httpPath||"/pusher")+ie(E);return V("http",b,C)}},ve={getInitial:function(E,b){return V("http",b,b.httpPath||"/pusher")},getPath:function(E,b){return ie(E)}};class pe{constructor(){this._callbacks={}}get(b){return this._callbacks[Ee(b)]}add(b,C,$){var j=Ee(b);this._callbacks[j]=this._callbacks[j]||[],this._callbacks[j].push({fn:C,context:$})}remove(b,C,$){if(!b&&!C&&!$){this._callbacks={};return}var j=b?[Ee(b)]:at(this._callbacks);C||$?this.removeCallback(j,C,$):this.removeAllCallbacks(j)}removeCallback(b,C,$){Ht(b,function(j){this._callbacks[j]=mt(this._callbacks[j]||[],function(ne){return C&&C!==ne.fn||$&&$!==ne.context}),this._callbacks[j].length===0&&delete this._callbacks[j]},this)}removeAllCallbacks(b){Ht(b,function(C){delete this._callbacks[C]},this)}}function Ee(E){return"_"+E}class xe{constructor(b){this.callbacks=new pe,this.global_callbacks=[],this.failThrough=b}bind(b,C,$){return this.callbacks.add(b,C,$),this}bind_global(b){return this.global_callbacks.push(b),this}unbind(b,C,$){return this.callbacks.remove(b,C,$),this}unbind_global(b){return b?(this.global_callbacks=mt(this.global_callbacks||[],C=>C!==b),this):(this.global_callbacks=[],this)}unbind_all(){return this.unbind(),this.unbind_global(),this}emit(b,C,$){for(var j=0;j0)for(var j=0;j{this.onError(C),this.changeState("closed")}),!1}return this.bindListeners(),it.debug("Connecting",{transport:this.name,url:b}),this.changeState("connecting"),!0}close(){return this.socket?(this.socket.close(),!0):!1}send(b){return this.state==="open"?(Te.defer(()=>{this.socket&&this.socket.send(b)}),!0):!1}ping(){this.state==="open"&&this.supportsPing()&&this.socket.ping()}onOpen(){this.hooks.beforeOpen&&this.hooks.beforeOpen(this.socket,this.hooks.urls.getPath(this.key,this.options)),this.changeState("open"),this.socket.onopen=void 0}onError(b){this.emit("error",{type:"WebSocketError",error:b}),this.timeline.error(this.buildTimelineMessage({error:b.toString()}))}onClose(b){b?this.changeState("closed",{code:b.code,reason:b.reason,wasClean:b.wasClean}):this.changeState("closed"),this.unbindListeners(),this.socket=void 0}onMessage(b){this.emit("message",b)}onActivity(){this.emit("activity")}bindListeners(){this.socket.onopen=()=>{this.onOpen()},this.socket.onerror=b=>{this.onError(b)},this.socket.onclose=b=>{this.onClose(b)},this.socket.onmessage=b=>{this.onMessage(b)},this.supportsPing()&&(this.socket.onactivity=()=>{this.onActivity()})}unbindListeners(){this.socket&&(this.socket.onopen=void 0,this.socket.onerror=void 0,this.socket.onclose=void 0,this.socket.onmessage=void 0,this.supportsPing()&&(this.socket.onactivity=void 0))}changeState(b,C){this.state=b,this.timeline.info(this.buildTimelineMessage({state:b,params:C})),this.emit(b,C)}buildTimelineMessage(b){return Ie({cid:this.id},b)}}class ke{constructor(b){this.hooks=b}isSupported(b){return this.hooks.isSupported(b)}createConnection(b,C,$,j){return new Ke(this.hooks,b,C,$,j)}}var Me=new ke({urls:ce,handlesActivityChecks:!1,supportsPing:!1,isInitialized:function(){return!!Ue.getWebSocketAPI()},isSupported:function(){return!!Ue.getWebSocketAPI()},getSocket:function(E){return Ue.createWebSocket(E)}}),Re={urls:Z,handlesActivityChecks:!1,supportsPing:!0,isInitialized:function(){return!0}},tt=Ie({getSocket:function(E){return Ue.HTTPFactory.createStreamingSocket(E)}},Re),Pe=Ie({getSocket:function(E){return Ue.HTTPFactory.createPollingSocket(E)}},Re),Ve={isSupported:function(){return Ue.isXHRSupported()}},Ce=new ke(Ie({},tt,Ve)),He=new ke(Ie({},Pe,Ve)),nt={ws:Me,xhr_streaming:Ce,xhr_polling:He};const Lt=nt;var Bt=new ke({file:"sockjs",urls:ve,handlesActivityChecks:!0,supportsPing:!1,isSupported:function(){return!0},isInitialized:function(){return window.SockJS!==void 0},getSocket:function(E,b){return new window.SockJS(E,null,{js_path:x.getPath("sockjs",{useTLS:b.useTLS}),ignore_null_origin:b.ignoreNullOrigin})},beforeOpen:function(E,b){E.send(JSON.stringify({path:b}))}}),Nn={isSupported:function(E){var b=Ue.isXDRSupported(E.useTLS);return b}},Gt=new ke(Ie({},tt,Nn)),Ft=new ke(Ie({},Pe,Nn));Lt.xdr_streaming=Gt,Lt.xdr_polling=Ft,Lt.sockjs=Bt;const Dn=Lt;class Ua extends xe{constructor(){super();var b=this;window.addEventListener!==void 0&&(window.addEventListener("online",function(){b.emit("online")},!1),window.addEventListener("offline",function(){b.emit("offline")},!1))}isOnline(){return window.navigator.onLine===void 0?!0:window.navigator.onLine}}var nl=new Ua;class gh{constructor(b,C,$){this.manager=b,this.transport=C,this.minPingDelay=$.minPingDelay,this.maxPingDelay=$.maxPingDelay,this.pingDelay=void 0}createConnection(b,C,$,j){j=Ie({},j,{activityTimeout:this.pingDelay});var ne=this.transport.createConnection(b,C,$,j),ye=null,Le=function(){ne.unbind("open",Le),ne.bind("closed",We),ye=Te.now()},We=ft=>{if(ne.unbind("closed",We),ft.code===1002||ft.code===1003)this.manager.reportDeath();else if(!ft.wasClean&&ye){var vt=Te.now()-ye;vt<2*this.maxPingDelay&&(this.manager.reportDeath(),this.pingDelay=Math.max(vt/2,this.minPingDelay))}};return ne.bind("open",Le),ne}isSupported(b){return this.manager.isAlive()&&this.transport.isSupported(b)}}const Jc={decodeMessage:function(E){try{var b=JSON.parse(E.data),C=b.data;if(typeof C=="string")try{C=JSON.parse(b.data)}catch{}var $={event:b.event,channel:b.channel,data:C};return b.user_id&&($.user_id=b.user_id),$}catch(j){throw{type:"MessageParseError",error:j,data:E.data}}},encodeMessage:function(E){return JSON.stringify(E)},processHandshake:function(E){var b=Jc.decodeMessage(E);if(b.event==="pusher:connection_established"){if(!b.data.activity_timeout)throw"No activity timeout specified in handshake";return{action:"connected",id:b.data.socket_id,activityTimeout:b.data.activity_timeout*1e3}}else{if(b.event==="pusher:error")return{action:this.getCloseAction(b.data),error:this.getCloseError(b.data)};throw"Invalid handshake"}},getCloseAction:function(E){return E.code<4e3?E.code>=1002&&E.code<=1004?"backoff":null:E.code===4e3?"tls_only":E.code<4100?"refused":E.code<4200?"backoff":E.code<4300?"retry":"refused"},getCloseError:function(E){return E.code!==1e3&&E.code!==1001?{type:"PusherError",data:{code:E.code,message:E.reason||E.message}}:null}},Yr=Jc;class Ni extends xe{constructor(b,C){super(),this.id=b,this.transport=C,this.activityTimeout=C.activityTimeout,this.bindListeners()}handlesActivityChecks(){return this.transport.handlesActivityChecks()}send(b){return this.transport.send(b)}send_event(b,C,$){var j={event:b,data:C};return $&&(j.channel=$),it.debug("Event sent",j),this.send(Yr.encodeMessage(j))}ping(){this.transport.supportsPing()?this.transport.ping():this.send_event("pusher:ping",{})}close(){this.transport.close()}bindListeners(){var b={message:$=>{var j;try{j=Yr.decodeMessage($)}catch(ne){this.emit("error",{type:"MessageParseError",error:ne,data:$.data})}if(j!==void 0){switch(it.debug("Event recd",j),j.event){case"pusher:error":this.emit("error",{type:"PusherError",data:j.data});break;case"pusher:ping":this.emit("ping");break;case"pusher:pong":this.emit("pong");break}this.emit("message",j)}},activity:()=>{this.emit("activity")},error:$=>{this.emit("error",$)},closed:$=>{C(),$&&$.code&&this.handleCloseEvent($),this.transport=null,this.emit("closed")}},C=()=>{gt(b,($,j)=>{this.transport.unbind(j,$)})};gt(b,($,j)=>{this.transport.bind(j,$)})}handleCloseEvent(b){var C=Yr.getCloseAction(b),$=Yr.getCloseError(b);$&&this.emit("error",$),C&&this.emit(C,{action:C,error:$})}}class rl{constructor(b,C){this.transport=b,this.callback=C,this.bindListeners()}close(){this.unbindListeners(),this.transport.close()}bindListeners(){this.onMessage=b=>{this.unbindListeners();var C;try{C=Yr.processHandshake(b)}catch($){this.finish("error",{error:$}),this.transport.close();return}C.action==="connected"?this.finish("connected",{connection:new Ni(C.id,this.transport),activityTimeout:C.activityTimeout}):(this.finish(C.action,{error:C.error}),this.transport.close())},this.onClosed=b=>{this.unbindListeners();var C=Yr.getCloseAction(b)||"backoff",$=Yr.getCloseError(b);this.finish(C,{error:$})},this.transport.bind("message",this.onMessage),this.transport.bind("closed",this.onClosed)}unbindListeners(){this.transport.unbind("message",this.onMessage),this.transport.unbind("closed",this.onClosed)}finish(b,C){this.callback(Ie({transport:this.transport,action:b},C))}}class Ba{constructor(b,C){this.timeline=b,this.options=C||{}}send(b,C){this.timeline.isEmpty()||this.timeline.send(Ue.TimelineTransport.getAgent(this,b),C)}}class Xr extends xe{constructor(b,C){super(function($,j){it.debug("No callbacks on "+b+" for "+$)}),this.name=b,this.pusher=C,this.subscribed=!1,this.subscriptionPending=!1,this.subscriptionCancelled=!1}authorize(b,C){return C(null,{auth:""})}trigger(b,C){if(b.indexOf("client-")!==0)throw new L("Event '"+b+"' does not start with 'client-'");if(!this.subscribed){var $=O.buildLogSuffix("triggeringClientEvents");it.warn(`Client event triggered before channel 'subscription_succeeded' event . ${$}`)}return this.pusher.send_event(b,C,this.name)}disconnect(){this.subscribed=!1,this.subscriptionPending=!1}handleEvent(b){var C=b.event,$=b.data;if(C==="pusher_internal:subscription_succeeded")this.handleSubscriptionSucceededEvent(b);else if(C==="pusher_internal:subscription_count")this.handleSubscriptionCountEvent(b);else if(C.indexOf("pusher_internal:")!==0){var j={};this.emit(C,$,j)}}handleSubscriptionSucceededEvent(b){this.subscriptionPending=!1,this.subscribed=!0,this.subscriptionCancelled?this.pusher.unsubscribe(this.name):this.emit("pusher:subscription_succeeded",b.data)}handleSubscriptionCountEvent(b){b.data.subscription_count&&(this.subscriptionCount=b.data.subscription_count),this.emit("pusher:subscription_count",b.data)}subscribe(){this.subscribed||(this.subscriptionPending=!0,this.subscriptionCancelled=!1,this.authorize(this.pusher.connection.socket_id,(b,C)=>{b?(this.subscriptionPending=!1,it.error(b.toString()),this.emit("pusher:subscription_error",Object.assign({},{type:"AuthError",error:b.message},b instanceof je?{status:b.status}:{}))):this.pusher.send_event("pusher:subscribe",{auth:C.auth,channel_data:C.channel_data,channel:this.name})}))}unsubscribe(){this.subscribed=!1,this.pusher.send_event("pusher:unsubscribe",{channel:this.name})}cancelSubscription(){this.subscriptionCancelled=!0}reinstateSubscription(){this.subscriptionCancelled=!1}}class fa extends Xr{authorize(b,C){return this.pusher.config.channelAuthorizer({channelName:this.name,socketId:b},C)}}class qa{constructor(){this.reset()}get(b){return Object.prototype.hasOwnProperty.call(this.members,b)?{id:b,info:this.members[b]}:null}each(b){gt(this.members,(C,$)=>{b(this.get($))})}setMyID(b){this.myID=b}onSubscription(b){this.members=b.presence.hash,this.count=b.presence.count,this.me=this.get(this.myID)}addMember(b){return this.get(b.user_id)===null&&this.count++,this.members[b.user_id]=b.user_info,this.get(b.user_id)}removeMember(b){var C=this.get(b.user_id);return C&&(delete this.members[b.user_id],this.count--),C}reset(){this.members={},this.count=0,this.myID=null,this.me=null}}var nn=function(E,b,C,$){function j(ne){return ne instanceof C?ne:new C(function(ye){ye(ne)})}return new(C||(C=Promise))(function(ne,ye){function Le(vt){try{ft($.next(vt))}catch(Vt){ye(Vt)}}function We(vt){try{ft($.throw(vt))}catch(Vt){ye(Vt)}}function ft(vt){vt.done?ne(vt.value):j(vt.value).then(Le,We)}ft(($=$.apply(E,b||[])).next())})};class Qc extends fa{constructor(b,C){super(b,C),this.members=new qa}authorize(b,C){super.authorize(b,($,j)=>nn(this,void 0,void 0,function*(){if(!$)if(j=j,j.channel_data!=null){var ne=JSON.parse(j.channel_data);this.members.setMyID(ne.user_id)}else if(yield this.pusher.user.signinDonePromise,this.pusher.user.user_data!=null)this.members.setMyID(this.pusher.user.user_data.id);else{let ye=O.buildLogSuffix("authorizationEndpoint");it.error(`Invalid auth response for channel '${this.name}', expected 'channel_data' field. ${ye}, or the user should be signed in.`),C("Invalid auth response");return}C($,j)}))}handleEvent(b){var C=b.event;if(C.indexOf("pusher_internal:")===0)this.handleInternalEvent(b);else{var $=b.data,j={};b.user_id&&(j.user_id=b.user_id),this.emit(C,$,j)}}handleInternalEvent(b){var C=b.event,$=b.data;switch(C){case"pusher_internal:subscription_succeeded":this.handleSubscriptionSucceededEvent(b);break;case"pusher_internal:subscription_count":this.handleSubscriptionCountEvent(b);break;case"pusher_internal:member_added":var j=this.members.addMember($);this.emit("pusher:member_added",j);break;case"pusher_internal:member_removed":var ne=this.members.removeMember($);ne&&this.emit("pusher:member_removed",ne);break}}handleSubscriptionSucceededEvent(b){this.subscriptionPending=!1,this.subscribed=!0,this.subscriptionCancelled?this.pusher.unsubscribe(this.name):(this.members.onSubscription(b.data),this.emit("pusher:subscription_succeeded",this.members))}disconnect(){this.members.reset(),super.disconnect()}}var Zc=f(978),Mr=f(594);class Ga extends fa{constructor(b,C,$){super(b,C),this.key=null,this.nacl=$}authorize(b,C){super.authorize(b,($,j)=>{if($){C($,j);return}let ne=j.shared_secret;if(!ne){C(new Error(`No shared_secret key in auth payload for encrypted channel: ${this.name}`),null);return}this.key=(0,Mr.decode)(ne),delete j.shared_secret,C(null,j)})}trigger(b,C){throw new de("Client events are not currently supported for encrypted channels")}handleEvent(b){var C=b.event,$=b.data;if(C.indexOf("pusher_internal:")===0||C.indexOf("pusher:")===0){super.handleEvent(b);return}this.handleEncryptedEvent(C,$)}handleEncryptedEvent(b,C){if(!this.key){it.debug("Received encrypted event before key has been retrieved from the authEndpoint");return}if(!C.ciphertext||!C.nonce){it.error("Unexpected format for encrypted event, expected object with `ciphertext` and `nonce` fields, got: "+C);return}let $=(0,Mr.decode)(C.ciphertext);if($.length{if(ye){it.error(`Failed to make a request to the authEndpoint: ${Le}. Unable to fetch new key, so dropping encrypted event`);return}if(ne=this.nacl.secretbox.open($,j,this.key),ne===null){it.error("Failed to decrypt event with new key. Dropping encrypted event");return}this.emit(b,this.getDataToEmit(ne))});return}this.emit(b,this.getDataToEmit(ne))}getDataToEmit(b){let C=(0,Zc.D4)(b);try{return JSON.parse(C)}catch{return C}}}class mh extends xe{constructor(b,C){super(),this.state="initialized",this.connection=null,this.key=b,this.options=C,this.timeline=this.options.timeline,this.usingTLS=this.options.useTLS,this.errorCallbacks=this.buildErrorCallbacks(),this.connectionCallbacks=this.buildConnectionCallbacks(this.errorCallbacks),this.handshakeCallbacks=this.buildHandshakeCallbacks(this.errorCallbacks);var $=Ue.getNetwork();$.bind("online",()=>{this.timeline.info({netinfo:"online"}),(this.state==="connecting"||this.state==="unavailable")&&this.retryIn(0)}),$.bind("offline",()=>{this.timeline.info({netinfo:"offline"}),this.connection&&this.sendActivityCheck()}),this.updateStrategy()}switchCluster(b){this.key=b,this.updateStrategy(),this.retryIn(0)}connect(){if(!(this.connection||this.runner)){if(!this.strategy.isSupported()){this.updateState("failed");return}this.updateState("connecting"),this.startConnecting(),this.setUnavailableTimer()}}send(b){return this.connection?this.connection.send(b):!1}send_event(b,C,$){return this.connection?this.connection.send_event(b,C,$):!1}disconnect(){this.disconnectInternally(),this.updateState("disconnected")}isUsingTLS(){return this.usingTLS}startConnecting(){var b=(C,$)=>{C?this.runner=this.strategy.connect(0,b):$.action==="error"?(this.emit("error",{type:"HandshakeError",error:$.error}),this.timeline.error({handshakeError:$.error})):(this.abortConnecting(),this.handshakeCallbacks[$.action]($))};this.runner=this.strategy.connect(0,b)}abortConnecting(){this.runner&&(this.runner.abort(),this.runner=null)}disconnectInternally(){if(this.abortConnecting(),this.clearRetryTimer(),this.clearUnavailableTimer(),this.connection){var b=this.abandonConnection();b.close()}}updateStrategy(){this.strategy=this.options.getStrategy({key:this.key,timeline:this.timeline,useTLS:this.usingTLS})}retryIn(b){this.timeline.info({action:"retry",delay:b}),b>0&&this.emit("connecting_in",Math.round(b/1e3)),this.retryTimer=new se(b||0,()=>{this.disconnectInternally(),this.connect()})}clearRetryTimer(){this.retryTimer&&(this.retryTimer.ensureAborted(),this.retryTimer=null)}setUnavailableTimer(){this.unavailableTimer=new se(this.options.unavailableTimeout,()=>{this.updateState("unavailable")})}clearUnavailableTimer(){this.unavailableTimer&&this.unavailableTimer.ensureAborted()}sendActivityCheck(){this.stopActivityCheck(),this.connection.ping(),this.activityTimer=new se(this.options.pongTimeout,()=>{this.timeline.error({pong_timed_out:this.options.pongTimeout}),this.retryIn(0)})}resetActivityCheck(){this.stopActivityCheck(),this.connection&&!this.connection.handlesActivityChecks()&&(this.activityTimer=new se(this.activityTimeout,()=>{this.sendActivityCheck()}))}stopActivityCheck(){this.activityTimer&&this.activityTimer.ensureAborted()}buildConnectionCallbacks(b){return Ie({},b,{message:C=>{this.resetActivityCheck(),this.emit("message",C)},ping:()=>{this.send_event("pusher:pong",{})},activity:()=>{this.resetActivityCheck()},error:C=>{this.emit("error",C)},closed:()=>{this.abandonConnection(),this.shouldRetry()&&this.retryIn(1e3)}})}buildHandshakeCallbacks(b){return Ie({},b,{connected:C=>{this.activityTimeout=Math.min(this.options.activityTimeout,C.activityTimeout,C.connection.activityTimeout||1/0),this.clearUnavailableTimer(),this.setConnection(C.connection),this.socket_id=this.connection.id,this.updateState("connected",{socket_id:this.socket_id})}})}buildErrorCallbacks(){let b=C=>$=>{$.error&&this.emit("error",{type:"WebSocketError",error:$.error}),C($)};return{tls_only:b(()=>{this.usingTLS=!0,this.updateStrategy(),this.retryIn(0)}),refused:b(()=>{this.disconnect()}),backoff:b(()=>{this.retryIn(1e3)}),retry:b(()=>{this.retryIn(0)})}}setConnection(b){this.connection=b;for(var C in this.connectionCallbacks)this.connection.bind(C,this.connectionCallbacks[C]);this.resetActivityCheck()}abandonConnection(){if(this.connection){this.stopActivityCheck();for(var b in this.connectionCallbacks)this.connection.unbind(b,this.connectionCallbacks[b]);var C=this.connection;return this.connection=null,C}}updateState(b,C){var $=this.state;if(this.state=b,$!==b){var j=b;j==="connected"&&(j+=" with new socket ID "+C.socket_id),it.debug("State changed",$+" -> "+j),this.timeline.info({state:b,params:C}),this.emit("state_change",{previous:$,current:b}),this.emit(b,C)}}shouldRetry(){return this.state==="connecting"||this.state==="connected"}}class eu{constructor(){this.channels={}}add(b,C){return this.channels[b]||(this.channels[b]=tu(b,C)),this.channels[b]}all(){return Yt(this.channels)}find(b){return this.channels[b]}remove(b){var C=this.channels[b];return delete this.channels[b],C}disconnect(){gt(this.channels,function(b){b.disconnect()})}}function tu(E,b){if(E.indexOf("private-encrypted-")===0){if(b.config.nacl)return Bn.createEncryptedChannel(E,b,b.config.nacl);let C="Tried to subscribe to a private-encrypted- channel but no nacl implementation available",$=O.buildLogSuffix("encryptedChannelSupport");throw new de(`${C}. ${$}`)}else{if(E.indexOf("private-")===0)return Bn.createPrivateChannel(E,b);if(E.indexOf("presence-")===0)return Bn.createPresenceChannel(E,b);if(E.indexOf("#")===0)throw new B('Cannot create a channel with name "'+E+'".');return Bn.createChannel(E,b)}}var yh={createChannels(){return new eu},createConnectionManager(E,b){return new mh(E,b)},createChannel(E,b){return new Xr(E,b)},createPrivateChannel(E,b){return new fa(E,b)},createPresenceChannel(E,b){return new Qc(E,b)},createEncryptedChannel(E,b,C){return new Ga(E,b,C)},createTimelineSender(E,b){return new Ba(E,b)},createHandshake(E,b){return new rl(E,b)},createAssistantToTheTransportManager(E,b,C){return new gh(E,b,C)}};const Bn=yh;class Ii{constructor(b){this.options=b||{},this.livesLeft=this.options.lives||1/0}getAssistant(b){return Bn.createAssistantToTheTransportManager(this,b,{minPingDelay:this.options.minPingDelay,maxPingDelay:this.options.maxPingDelay})}isAlive(){return this.livesLeft>0}reportDeath(){this.livesLeft-=1}}class Sn{constructor(b,C){this.strategies=b,this.loop=!!C.loop,this.failFast=!!C.failFast,this.timeout=C.timeout,this.timeoutLimit=C.timeoutLimit}isSupported(){return kn(this.strategies,Te.method("isSupported"))}connect(b,C){var $=this.strategies,j=0,ne=this.timeout,ye=null,Le=(We,ft)=>{ft?C(null,ft):(j=j+1,this.loop&&(j=j%$.length),j<$.length?(ne&&(ne=ne*2,this.timeoutLimit&&(ne=Math.min(ne,this.timeoutLimit))),ye=this.tryStrategy($[j],b,{timeout:ne,failFast:this.failFast},Le)):C(!0))};return ye=this.tryStrategy($[j],b,{timeout:ne,failFast:this.failFast},Le),{abort:function(){ye.abort()},forceMinPriority:function(We){b=We,ye&&ye.forceMinPriority(We)}}}tryStrategy(b,C,$,j){var ne=null,ye=null;return $.timeout>0&&(ne=new se($.timeout,function(){ye.abort(),j(!0)})),ye=b.connect(C,function(Le,We){Le&&ne&&ne.isRunning()&&!$.failFast||(ne&&ne.ensureAborted(),j(Le,We))}),{abort:function(){ne&&ne.ensureAborted(),ye.abort()},forceMinPriority:function(Le){ye.forceMinPriority(Le)}}}}class On{constructor(b){this.strategies=b}isSupported(){return kn(this.strategies,Te.method("isSupported"))}connect(b,C){return nu(this.strategies,b,function($,j){return function(ne,ye){if(j[$].error=ne,ne){bh(j)&&C(!0);return}Ht(j,function(Le){Le.forceMinPriority(ye.transport.priority)}),C(null,ye)}})}}function nu(E,b,C){var $=bt(E,function(j,ne,ye,Le){return j.connect(b,C(ne,Le))});return{abort:function(){Ht($,al)},forceMinPriority:function(j){Ht($,function(ne){ne.forceMinPriority(j)})}}}function bh(E){return hr(E,function(b){return!!b.error})}function al(E){!E.error&&!E.aborted&&(E.abort(),E.aborted=!0)}class ru{constructor(b,C,$){this.strategy=b,this.transports=C,this.ttl=$.ttl||18e5,this.usingTLS=$.useTLS,this.timeline=$.timeline}isSupported(){return this.strategy.isSupported()}connect(b,C){var $=this.usingTLS,j=vh($),ne=j&&j.cacheSkipCount?j.cacheSkipCount:0,ye=[this.strategy];if(j&&j.timestamp+this.ttl>=Te.now()){var Le=this.transports[j.transport];Le&&(["ws","wss"].includes(j.transport)||ne>3?(this.timeline.info({cached:!0,transport:j.transport,latency:j.latency}),ye.push(new Sn([Le],{timeout:j.latency*2+1e3,failFast:!0}))):ne++)}var We=Te.now(),ft=ye.pop().connect(b,function vt(Vt,Hr){Vt?(Os($),ye.length>0?(We=Te.now(),ft=ye.pop().connect(b,vt)):C(Vt)):(qn($,Hr.transport.name,Te.now()-We,ne),C(null,Hr))});return{abort:function(){ft.abort()},forceMinPriority:function(vt){b=vt,ft&&ft.forceMinPriority(vt)}}}}function Fa(E){return"pusherTransport"+(E?"TLS":"NonTLS")}function vh(E){var b=Ue.getLocalStorage();if(b)try{var C=b[Fa(E)];if(C)return JSON.parse(C)}catch{Os(E)}return null}function qn(E,b,C,$){var j=Ue.getLocalStorage();if(j)try{j[Fa(E)]=bn({timestamp:Te.now(),transport:b,latency:C,cacheSkipCount:$})}catch{}}function Os(E){var b=Ue.getLocalStorage();if(b)try{delete b[Fa(E)]}catch{}}class ji{constructor(b,{delay:C}){this.strategy=b,this.options={delay:C}}isSupported(){return this.strategy.isSupported()}connect(b,C){var $=this.strategy,j,ne=new se(this.options.delay,function(){j=$.connect(b,C)});return{abort:function(){ne.ensureAborted(),j&&j.abort()},forceMinPriority:function(ye){b=ye,j&&j.forceMinPriority(ye)}}}}class ha{constructor(b,C,$){this.test=b,this.trueBranch=C,this.falseBranch=$}isSupported(){var b=this.test()?this.trueBranch:this.falseBranch;return b.isSupported()}connect(b,C){var $=this.test()?this.trueBranch:this.falseBranch;return $.connect(b,C)}}class Va{constructor(b){this.strategy=b}isSupported(){return this.strategy.isSupported()}connect(b,C){var $=this.strategy.connect(b,function(j,ne){ne&&$.abort(),C(j,ne)});return $}}function Ka(E){return function(){return E.isSupported()}}var au=function(E,b,C){var $={};function j(bl,bu,vu,Ph,Uh){var Su=C(E,bl,bu,vu,Ph,Uh);return $[bl]=Su,Su}var ne=Object.assign({},b,{hostNonTLS:E.wsHost+":"+E.wsPort,hostTLS:E.wsHost+":"+E.wssPort,httpPath:E.wsPath}),ye=Object.assign({},ne,{useTLS:!0}),Le=Object.assign({},b,{hostNonTLS:E.httpHost+":"+E.httpPort,hostTLS:E.httpHost+":"+E.httpsPort,httpPath:E.httpPath}),We={loop:!0,timeout:15e3,timeoutLimit:6e4},ft=new Ii({minPingDelay:1e4,maxPingDelay:E.activityTimeout}),vt=new Ii({lives:2,minPingDelay:1e4,maxPingDelay:E.activityTimeout}),Vt=j("ws","ws",3,ne,ft),Hr=j("wss","ws",3,ye,ft),jh=j("sockjs","sockjs",1,Le),mu=j("xhr_streaming","xhr_streaming",1,Le,vt),Hh=j("xdr_streaming","xdr_streaming",1,Le,vt),gl=j("xhr_polling","xhr_polling",1,Le),yu=j("xdr_polling","xdr_polling",1,Le),Xa=new Sn([Vt],We),qi=new Sn([Hr],We),zh=new Sn([jh],We),Gi=new Sn([new ha(Ka(mu),mu,Hh)],We),ml=new Sn([new ha(Ka(gl),gl,yu)],We),yl=new Sn([new ha(Ka(Gi),new On([Gi,new ji(ml,{delay:4e3})]),ml)],We),Fi=new ha(Ka(yl),yl,zh),Vi;return b.useTLS?Vi=new On([Xa,new ji(Fi,{delay:2e3})]):Vi=new On([Xa,new ji(qi,{delay:2e3}),new ji(Fi,{delay:5e3})]),new ru(new Va(new ha(Ka(Vt),Vi,Fi)),$,{ttl:18e5,timeline:b.timeline,useTLS:b.useTLS})};const Wa=au;function Sh(){var E=this;E.timeline.info(E.buildTimelineMessage({transport:E.name+(E.options.useTLS?"s":"")})),E.hooks.isInitialized()?E.changeState("initialized"):E.hooks.file?(E.changeState("initializing"),x.load(E.hooks.file,{useTLS:E.options.useTLS},function(b,C){E.hooks.isInitialized()?(E.changeState("initialized"),C(!0)):(b&&E.onError(b),E.onClose(),C(!1))})):E.onClose()}var iu={getRequest:function(E){var b=new window.XDomainRequest;return b.ontimeout=function(){E.emit("error",new q),E.close()},b.onerror=function(C){E.emit("error",C),E.close()},b.onprogress=function(){b.responseText&&b.responseText.length>0&&E.onChunk(200,b.responseText)},b.onload=function(){b.responseText&&b.responseText.length>0&&E.onChunk(200,b.responseText),E.emit("finished",200),E.close()},b},abortRequest:function(E){E.ontimeout=E.onerror=E.onprogress=E.onload=null,E.abort()}};const su=iu,il=256*1024;class wh extends xe{constructor(b,C,$){super(),this.hooks=b,this.method=C,this.url=$}start(b){this.position=0,this.xhr=this.hooks.getRequest(this),this.unloader=()=>{this.close()},Ue.addUnloadListener(this.unloader),this.xhr.open(this.method,this.url,!0),this.xhr.setRequestHeader&&this.xhr.setRequestHeader("Content-Type","application/json"),this.xhr.send(b)}close(){this.unloader&&(Ue.removeUnloadListener(this.unloader),this.unloader=null),this.xhr&&(this.hooks.abortRequest(this.xhr),this.xhr=null)}onChunk(b,C){for(;;){var $=this.advanceBuffer(C);if($)this.emit("chunk",{status:b,data:$});else break}this.isBufferTooLong(C)&&this.emit("buffer_too_long")}advanceBuffer(b){var C=b.slice(this.position),$=C.indexOf(` +`);return $!==-1?(this.position+=$+1,C.slice(0,$)):null}isBufferTooLong(b){return this.position===b.length&&b.length>il}}var sl;(function(E){E[E.CONNECTING=0]="CONNECTING",E[E.OPEN=1]="OPEN",E[E.CLOSED=3]="CLOSED"})(sl||(sl={}));const gr=sl;var Nr=1;class ol{constructor(b,C){this.hooks=b,this.session=Ls(1e3)+"/"+lu(8),this.location=ll(C),this.readyState=gr.CONNECTING,this.openStream()}send(b){return this.sendRaw(JSON.stringify([b]))}ping(){this.hooks.sendHeartbeat(this)}close(b,C){this.onClose(b,C,!0)}sendRaw(b){if(this.readyState===gr.OPEN)try{return Ue.createSocketRequest("POST",pa(Ya(this.location,this.session))).start(b),!0}catch{return!1}else return!1}reconnect(){this.closeStream(),this.openStream()}onClose(b,C,$){this.closeStream(),this.readyState=gr.CLOSED,this.onclose&&this.onclose({code:b,reason:C,wasClean:$})}onChunk(b){if(b.status===200){this.readyState===gr.OPEN&&this.onActivity();var C,$=b.data.slice(0,1);switch($){case"o":C=JSON.parse(b.data.slice(1)||"{}"),this.onOpen(C);break;case"a":C=JSON.parse(b.data.slice(1)||"[]");for(var j=0;j{this.onChunk(b)}),this.stream.bind("finished",b=>{this.hooks.onFinished(this,b)}),this.stream.bind("buffer_too_long",()=>{this.reconnect()});try{this.stream.start()}catch(b){Te.defer(()=>{this.onError(b),this.onClose(1006,"Could not start streaming",!1)})}}closeStream(){this.stream&&(this.stream.unbind_all(),this.stream.close(),this.stream=null)}}function ll(E){var b=/([^\?]*)\/*(\??.*)/.exec(E);return{base:b[1],queryString:b[2]}}function Ya(E,b){return E.base+"/"+b+"/xhr_send"}function pa(E){var b=E.indexOf("?")===-1?"?":"&";return E+b+"t="+ +new Date+"&n="+Nr++}function ou(E,b){var C=/(https?:\/\/)([^\/:]+)((\/|:)?.*)/.exec(E);return C[1]+b+C[3]}function Ls(E){return Ue.randomInt(E)}function lu(E){for(var b=[],C=0;C0&&E.onChunk(C.status,C.responseText);break;case 4:C.responseText&&C.responseText.length>0&&E.onChunk(C.status,C.responseText),E.emit("finished",C.status),E.close();break}},C},abortRequest:function(E){E.onreadystatechange=null,E.abort()}};const $s=ul;var cu={createStreamingSocket(E){return this.createSocket(cl,E)},createPollingSocket(E){return this.createSocket(Jr,E)},createSocket(E,b){return new Hi(E,b)},createXHR(E,b){return this.createRequest($s,E,b)},createRequest(E,b,C){return new wh(E,b,C)}};const Pi=cu;Pi.createXDR=function(E,b){return this.createRequest(su,E,b)};var uu={nextAuthCallbackID:1,auth_callbacks:{},ScriptReceivers:m,DependenciesReceivers:_,getDefaultStrategy:Wa,Transports:Dn,transportConnectionInitializer:Sh,HTTPFactory:Pi,TimelineTransport:dn,getXHRAPI(){return window.XMLHttpRequest},getWebSocketAPI(){return window.WebSocket||window.MozWebSocket},setup(E){window.Pusher=E;var b=()=>{this.onDocumentBody(E.ready)};window.JSON?b():x.load("json2",{},b)},getDocument(){return document},getProtocol(){return this.getDocument().location.protocol},getAuthorizers(){return{ajax:ze,jsonp:vn}},onDocumentBody(E){document.body?E():setTimeout(()=>{this.onDocumentBody(E)},0)},createJSONPRequest(E,b){return new pr(E,b)},createScriptRequest(E){return new da(E)},getLocalStorage(){try{return window.localStorage}catch{return}},createXHR(){return this.getXHRAPI()?this.createXMLHttpRequest():this.createMicrosoftXHR()},createXMLHttpRequest(){var E=this.getXHRAPI();return new E},createMicrosoftXHR(){return new ActiveXObject("Microsoft.XMLHTTP")},getNetwork(){return nl},createWebSocket(E){var b=this.getWebSocketAPI();return new b(E)},createSocketRequest(E,b){if(this.isXHRSupported())return this.HTTPFactory.createXHR(E,b);if(this.isXDRSupported(b.indexOf("https:")===0))return this.HTTPFactory.createXDR(E,b);throw"Cross-origin HTTP requests are not supported"},isXHRSupported(){var E=this.getXHRAPI();return!!E&&new E().withCredentials!==void 0},isXDRSupported(E){var b=E?"https:":"http:",C=this.getProtocol();return!!window.XDomainRequest&&C===b},addUnloadListener(E){window.addEventListener!==void 0?window.addEventListener("unload",E,!1):window.attachEvent!==void 0&&window.attachEvent("onunload",E)},removeUnloadListener(E){window.addEventListener!==void 0?window.removeEventListener("unload",E,!1):window.detachEvent!==void 0&&window.detachEvent("onunload",E)},randomInt(E){return Math.floor(function(){return(window.crypto||window.msCrypto).getRandomValues(new Uint32Array(1))[0]/Math.pow(2,32)}()*E)}};const Ue=uu;var jr;(function(E){E[E.ERROR=3]="ERROR",E[E.INFO=6]="INFO",E[E.DEBUG=7]="DEBUG"})(jr||(jr={}));const ga=jr;class Ui{constructor(b,C,$){this.key=b,this.session=C,this.events=[],this.options=$||{},this.sent=0,this.uniqueID=0}log(b,C){b<=this.options.level&&(this.events.push(Ie({},C,{timestamp:Te.now()})),this.options.limit&&this.events.length>this.options.limit&&this.events.shift())}error(b){this.log(ga.ERROR,b)}info(b){this.log(ga.INFO,b)}debug(b){this.log(ga.DEBUG,b)}isEmpty(){return this.events.length===0}send(b,C){var $=Ie({session:this.session,bundle:this.sent+1,key:this.key,lib:"js",version:this.options.version,cluster:this.options.cluster,features:this.options.features,timeline:this.events},this.options.params);return this.events=[],b($,(j,ne)=>{j||this.sent++,C&&C(j,ne)}),!0}generateUniqueID(){return this.uniqueID++,this.uniqueID}}class Ch{constructor(b,C,$,j){this.name=b,this.priority=C,this.transport=$,this.options=j||{}}isSupported(){return this.transport.isSupported({useTLS:this.options.useTLS})}connect(b,C){if(this.isSupported()){if(this.priority{$||(vt(),ne?ne.close():j.close())},forceMinPriority:Vt=>{$||this.priority{var C="socket_id="+encodeURIComponent(E.socketId);for(var $ in b.params)C+="&"+encodeURIComponent($)+"="+encodeURIComponent(b.params[$]);if(b.paramsProvider!=null){let j=b.paramsProvider();for(var $ in j)C+="&"+encodeURIComponent($)+"="+encodeURIComponent(j[$])}return C},_h=E=>{if(typeof Ue.getAuthorizers()[E.transport]>"u")throw`'${E.transport}' is not a recognized auth transport`;return(b,C)=>{const $=Eh(b,E);Ue.getAuthorizers()[E.transport](Ue,$,E,k.UserAuthentication,C)}},hl=(E,b)=>{var C="socket_id="+encodeURIComponent(E.socketId);C+="&channel_name="+encodeURIComponent(E.channelName);for(var $ in b.params)C+="&"+encodeURIComponent($)+"="+encodeURIComponent(b.params[$]);if(b.paramsProvider!=null){let j=b.paramsProvider();for(var $ in j)C+="&"+encodeURIComponent($)+"="+encodeURIComponent(j[$])}return C},Ah=E=>{if(typeof Ue.getAuthorizers()[E.transport]>"u")throw`'${E.transport}' is not a recognized auth transport`;return(b,C)=>{const $=hl(b,E);Ue.getAuthorizers()[E.transport](Ue,$,E,k.ChannelAuthorization,C)}},xh=(E,b,C)=>{const $={authTransport:b.transport,authEndpoint:b.endpoint,auth:{params:b.params,headers:b.headers}};return(j,ne)=>{const ye=E.channel(j.channelName);C(ye,$).authorize(j.socketId,ne)}};function fu(E,b){let C={activityTimeout:E.activityTimeout||v.activityTimeout,cluster:E.cluster,httpPath:E.httpPath||v.httpPath,httpPort:E.httpPort||v.httpPort,httpsPort:E.httpsPort||v.httpsPort,pongTimeout:E.pongTimeout||v.pongTimeout,statsHost:E.statsHost||v.stats_host,unavailableTimeout:E.unavailableTimeout||v.unavailableTimeout,wsPath:E.wsPath||v.wsPath,wsPort:E.wsPort||v.wsPort,wssPort:E.wssPort||v.wssPort,enableStats:Dh(E),httpHost:Th(E),useTLS:Rh(E),wsHost:hu(E),userAuthenticator:pl(E),channelAuthorizer:Lh(E,b)};return"disabledTransports"in E&&(C.disabledTransports=E.disabledTransports),"enabledTransports"in E&&(C.enabledTransports=E.enabledTransports),"ignoreNullOrigin"in E&&(C.ignoreNullOrigin=E.ignoreNullOrigin),"timelineParams"in E&&(C.timelineParams=E.timelineParams),"nacl"in E&&(C.nacl=E.nacl),C}function Th(E){return E.httpHost?E.httpHost:E.cluster?`sockjs-${E.cluster}.pusher.com`:v.httpHost}function hu(E){return E.wsHost?E.wsHost:kh(E.cluster)}function kh(E){return`ws-${E}.pusher.com`}function Rh(E){return Ue.getProtocol()==="https:"?!0:E.forceTLS!==!1}function Dh(E){return"enableStats"in E?E.enableStats:"disableStats"in E?!E.disableStats:!1}const pu=E=>"customHandler"in E&&E.customHandler!=null;function pl(E){const b=Object.assign(Object.assign({},v.userAuthentication),E.userAuthentication);return pu(b)?b.customHandler:_h(b)}function Oh(E,b){let C;if("channelAuthorization"in E)C=Object.assign(Object.assign({},v.channelAuthorization),E.channelAuthorization);else if(C={transport:E.authTransport||v.authTransport,endpoint:E.authEndpoint||v.authEndpoint},"auth"in E&&("params"in E.auth&&(C.params=E.auth.params),"headers"in E.auth&&(C.headers=E.auth.headers)),"authorizer"in E)return{customHandler:xh(b,C,E.authorizer)};return C}function Lh(E,b){const C=Oh(E,b);return pu(C)?C.customHandler:Ah(C)}class $h extends xe{constructor(b){super(function(C,$){it.debug(`No callbacks on watchlist events for ${C}`)}),this.pusher=b,this.bindWatchlistInternalEvent()}handleEvent(b){b.data.events.forEach(C=>{this.emit(C.name,C)})}bindWatchlistInternalEvent(){this.pusher.connection.bind("message",b=>{var C=b.event;C==="pusher_internal:watchlist_events"&&this.handleEvent(b)})}}function gu(){let E,b;return{promise:new Promise(($,j)=>{E=$,b=j}),resolve:E,reject:b}}const Mh=gu;class Nh extends xe{constructor(b){super(function(C,$){it.debug("No callbacks on user for "+C)}),this.signin_requested=!1,this.user_data=null,this.serverToUserChannel=null,this.signinDonePromise=null,this._signinDoneResolve=null,this._onAuthorize=(C,$)=>{if(C){it.warn(`Error during signin: ${C}`),this._cleanup();return}this.pusher.send_event("pusher:signin",{auth:$.auth,user_data:$.user_data})},this.pusher=b,this.pusher.connection.bind("state_change",({previous:C,current:$})=>{C!=="connected"&&$==="connected"&&this._signin(),C==="connected"&&$!=="connected"&&(this._cleanup(),this._newSigninPromiseIfNeeded())}),this.watchlist=new $h(b),this.pusher.connection.bind("message",C=>{var $=C.event;$==="pusher:signin_success"&&this._onSigninSuccess(C.data),this.serverToUserChannel&&this.serverToUserChannel.name===C.channel&&this.serverToUserChannel.handleEvent(C)})}signin(){this.signin_requested||(this.signin_requested=!0,this._signin())}_signin(){this.signin_requested&&(this._newSigninPromiseIfNeeded(),this.pusher.connection.state==="connected"&&this.pusher.config.userAuthenticator({socketId:this.pusher.connection.socket_id},this._onAuthorize))}_onSigninSuccess(b){try{this.user_data=JSON.parse(b.user_data)}catch{it.error(`Failed parsing user data after signin: ${b.user_data}`),this._cleanup();return}if(typeof this.user_data.id!="string"||this.user_data.id===""){it.error(`user_data doesn't contain an id. user_data: ${this.user_data}`),this._cleanup();return}this._signinDoneResolve(),this._subscribeChannels()}_subscribeChannels(){const b=C=>{C.subscriptionPending&&C.subscriptionCancelled?C.reinstateSubscription():!C.subscriptionPending&&this.pusher.connection.state==="connected"&&C.subscribe()};this.serverToUserChannel=new Xr(`#server-to-user-${this.user_data.id}`,this.pusher),this.serverToUserChannel.bind_global((C,$)=>{C.indexOf("pusher_internal:")===0||C.indexOf("pusher:")===0||this.emit(C,$)}),b(this.serverToUserChannel)}_cleanup(){this.user_data=null,this.serverToUserChannel&&(this.serverToUserChannel.unbind_all(),this.serverToUserChannel.disconnect(),this.serverToUserChannel=null),this.signin_requested&&this._signinDoneResolve()}_newSigninPromiseIfNeeded(){if(!this.signin_requested||this.signinDonePromise&&!this.signinDonePromise.done)return;const{promise:b,resolve:C}=Mh();b.done=!1;const $=()=>{b.done=!0};b.then($).catch($),this.signinDonePromise=b,this._signinDoneResolve=C}}class fn{static ready(){fn.isReady=!0;for(var b=0,C=fn.instances.length;bUe.getDefaultStrategy(this.config,j,Bi);this.connection=Bn.createConnectionManager(this.key,{getStrategy:$,timeline:this.timeline,activityTimeout:this.config.activityTimeout,pongTimeout:this.config.pongTimeout,unavailableTimeout:this.config.unavailableTimeout,useTLS:!!this.config.useTLS}),this.connection.bind("connected",()=>{this.subscribeAll(),this.timelineSender&&this.timelineSender.send(this.connection.isUsingTLS())}),this.connection.bind("message",j=>{var ne=j.event,ye=ne.indexOf("pusher_internal:")===0;if(j.channel){var Le=this.channel(j.channel);Le&&Le.handleEvent(j)}ye||this.global_emitter.emit(j.event,j.data)}),this.connection.bind("connecting",()=>{this.channels.disconnect()}),this.connection.bind("disconnected",()=>{this.channels.disconnect()}),this.connection.bind("error",j=>{it.warn(j)}),fn.instances.push(this),this.timeline.info({instances:fn.instances.length}),this.user=new Nh(this),fn.isReady&&this.connect()}switchCluster(b){const{appKey:C,cluster:$}=b;this.key=C,this.options=Object.assign(Object.assign({},this.options),{cluster:$}),this.config=fu(this.options,this),this.connection.switchCluster(this.key)}channel(b){return this.channels.find(b)}allChannels(){return this.channels.all()}connect(){if(this.connection.connect(),this.timelineSender&&!this.timelineSenderTimer){var b=this.connection.isUsingTLS(),C=this.timelineSender;this.timelineSenderTimer=new ge(6e4,function(){C.send(b)})}}disconnect(){this.connection.disconnect(),this.timelineSenderTimer&&(this.timelineSenderTimer.ensureAborted(),this.timelineSenderTimer=null)}bind(b,C,$){return this.global_emitter.bind(b,C,$),this}unbind(b,C,$){return this.global_emitter.unbind(b,C,$),this}bind_global(b){return this.global_emitter.bind_global(b),this}unbind_global(b){return this.global_emitter.unbind_global(b),this}unbind_all(b){return this.global_emitter.unbind_all(),this}subscribeAll(){var b;for(b in this.channels.channels)this.channels.channels.hasOwnProperty(b)&&this.subscribe(b)}subscribe(b){var C=this.channels.add(b,this);return C.subscriptionPending&&C.subscriptionCancelled?C.reinstateSubscription():!C.subscriptionPending&&this.connection.state==="connected"&&C.subscribe(),C}unsubscribe(b){var C=this.channels.find(b);C&&C.subscriptionPending?C.cancelSubscription():(C=this.channels.remove(b),C&&C.subscribed&&C.unsubscribe())}send_event(b,C,$){return this.connection.send_event(b,C,$)}shouldUseTLS(){return this.config.useTLS}signin(){this.user.signin()}}fn.instances=[],fn.isReady=!1,fn.logToConsole=!1,fn.Runtime=Ue,fn.ScriptReceivers=Ue.ScriptReceivers,fn.DependenciesReceivers=Ue.DependenciesReceivers,fn.auth_callbacks=Ue.auth_callbacks;const Is=fn;function Ih(E){if(E==null)throw"You must pass your app key when you instantiate Pusher."}Ue.setup(fn)}},r={};function i(c){var d=r[c];if(d!==void 0)return d.exports;var f=r[c]={exports:{}};return n[c].call(f.exports,f,f.exports,i),f.exports}i.d=(c,d)=>{for(var f in d)i.o(d,f)&&!i.o(c,f)&&Object.defineProperty(c,f,{enumerable:!0,get:d[f]})},i.o=(c,d)=>Object.prototype.hasOwnProperty.call(c,d);var l=i(721);return l})())})(oh)),oh.exports}var Px=zx();const _b=ra(Px),Kt=pt("WebSocketManager");class Ux{constructor(){M(this,"echo",null);M(this,"subscriptions",new Map);M(this,"initialized",!1);M(this,"config",null)}configure(e){if(this.initialized){Kt.warn("[WebSocketManager] 이미 초기화되어 설정을 변경할 수 없습니다.");return}this.config=e,Kt.log("[WebSocketManager] 설정 완료:",{appKey:e.appKey?"***":"(없음)",host:e.host,port:e.port,scheme:e.scheme})}initialize(){if(this.initialized)return;if(!this.config||!this.config.appKey){Kt.warn("[WebSocketManager] WebSocket 설정이 없습니다. initTemplateApp에서 websocket 옵션을 전달해주세요.");return}const{appKey:e,host:n="localhost",port:r=80,scheme:i="https",authEndpoint:l="/api/broadcasting/auth"}=this.config,c=Number(r)||80,d=i==="https";Kt.log("[WebSocketManager] 연결 설정:",{host:n,port:c,scheme:i,useTLS:d,authEndpoint:l}),window.Pusher=_b;const f=localStorage.getItem("auth_token"),h={wsHost:n,wsPort:c,wssPort:c,forceTLS:d,disableStats:!0,enabledTransports:["ws","wss"],cluster:"mt1",authEndpoint:l,auth:{headers:{Authorization:f?`Bearer ${f}`:"",Accept:"application/json"}}};Kt.log("[WebSocketManager] Pusher 옵션:",h);const m=new _b(e,h);m.connection.bind("connecting",()=>{Kt.log("[WebSocketManager] 연결 시도 중...")}),m.connection.bind("connected",()=>{Kt.log("[WebSocketManager] 연결 성공! Socket ID:",m.connection.socket_id)}),m.connection.bind("failed",()=>{Kt.error("[WebSocketManager] 연결 실패 - WebSocket을 사용할 수 없습니다.")}),m.connection.bind("error",y=>{Kt.error("[WebSocketManager] 연결 오류:",y)}),m.connection.bind("state_change",y=>{Kt.log(`[WebSocketManager] 상태 변경: ${y.previous} → ${y.current}`)}),m.connection.bind("unavailable",()=>{Kt.error("[WebSocketManager] WebSocket 사용 불가 - 연결할 수 없습니다.")}),m.connection.bind("disconnected",()=>{Kt.warn("[WebSocketManager] 연결이 끊어졌습니다.")}),Kt.log("[WebSocketManager] 초기 연결 상태:",m.connection.state),this.echo=new Hx({broadcaster:"reverb",client:m}),Kt.log("[WebSocketManager] 연결 시작 시도..."),m.connect(),window.Echo=this.echo,this.initialized=!0,Kt.log("[WebSocketManager] Echo 초기화 완료")}subscribe(e,n,r,i={}){if(this.initialize(),!this.echo)return Kt.warn("[WebSocketManager] Echo가 초기화되지 않았습니다."),"";const{channelType:l="private"}=i,c=`${e}:${n}`;if(this.subscriptions.has(c))return Kt.log(`[WebSocketManager] 이미 구독 중: ${c}`),c;let d;switch(l){case"public":d=this.echo.channel(e);break;case"presence":d=this.echo.join(e);break;default:d=this.echo.private(e)}return d.listen(`.${n}`,r),this.subscriptions.set(c,d),Kt.log(`[WebSocketManager] 구독 완료: ${c} (${l})`),c}unsubscribe(e){const n=this.subscriptions.get(e);if(!n)return;const r=e.lastIndexOf(":"),i=r>=0?e.substring(r+1):"";if(i)try{n.stopListening(`.${i}`)}catch(l){Kt.warn(`[WebSocketManager] stopListening 실패: ${e}`,l)}this.subscriptions.delete(e),Kt.log(`[WebSocketManager] 구독 해제: ${e}`)}leaveChannel(e){if(!this.echo)return;this.echo.leave(e);const n=[];this.subscriptions.forEach((r,i)=>{i.startsWith(`${e}:`)&&n.push(i)}),n.forEach(r=>this.subscriptions.delete(r)),Kt.log(`[WebSocketManager] 채널 구독 해제: ${e}`)}disconnect(){this.echo&&(this.echo.disconnect(),this.subscriptions.clear(),this.initialized=!1,Kt.log("[WebSocketManager] 연결 종료"))}getEcho(){return this.echo}isInitialized(){return this.initialized}isConfigured(){return this.config!==null&&!!this.config.appKey}getSubscriptionCount(){return this.subscriptions.size}}const qc=new Ux,Ct=pt("DataSourceManager"),Ab=new $n;function Gc(s,e){const n=Oa(s);if(n!==null)try{return jn(n)?Ab.evaluatePipeExpression(n,e,{skipCache:!0}):Ab.evaluateExpression(n,e)}catch(r){return Ct.error("Failed to evaluate param expression:",n,r),s}return ps(s,e,{skipCache:!0})}function xb(s){const e={};for(const n of s.keys()){if(n in e)continue;const r=s.getAll(n);r.length>1?e[n]=r:r.length===1&&(n.endsWith("[]")?e[n]=r:e[n]=r[0])}return e}function Bx(){try{return window.G7Core?.devTools}catch{return}}const Xc=class Xc{constructor(e={}){M(this,"options");M(this,"dataCache",new Map);M(this,"bindingEngine");M(this,"globalHeaders",[]);this.options=e,this.bindingEngine=new $n}setGlobalHeaders(e){this.globalHeaders=e||[]}matchesPattern(e,n){if(n==="*")return!0;const r=n.replace(/[.+?^${}()|[\]\\]/g,"\\$&").replace(/\*/g,".*");return new RegExp(`^${r}$`).test(e)}toFormData(e){const n=new FormData;for(const[r,i]of Object.entries(e))i instanceof File||i instanceof Blob?n.append(r,i):i!=null&&n.append(r,typeof i=="object"?JSON.stringify(i):String(i));return n}getMatchingGlobalHeaders(e,n){const r={};for(const i of this.globalHeaders)this.matchesPattern(e,i.pattern)&&Object.entries(i.headers).forEach(([l,c])=>{const d=Gc(c,n);d!=null&&d!==""&&(r[l]=String(d))});return r}filterByCondition(e,n={}){const r=new Set,i=[];for(const l of e){if(r.has(l.id))continue;if(!l.if&&!l.conditions){r.add(l.id),i.push(l);continue}hs({if:l.if,conditions:l.conditions},n,this.bindingEngine,`data_source:${l.id}`)&&(r.add(l.id),i.push(l))}return i}checkErrorCondition(e,n){if(!e.errorCondition?.if||e.errorCondition?.errorCode===void 0)return null;try{const r={response:n};if(hs({if:e.errorCondition.if},r,this.bindingEngine,`errorCondition:${e.id}`))return Ct.log(`errorCondition matched for ${e.id}, triggering error ${e.errorCondition.errorCode}`),e.errorCondition.errorCode}catch(r){Ct.error(`Failed to evaluate errorCondition for ${e.id}:`,r)}return null}async fetchDataSources(e,n={},r=new URLSearchParams,i,l){const c={},d=!!this.options.sampleProvider,f=e.filter(h=>(d||h.auto_fetch!==!1)&&h.type!=="websocket");return await Promise.all(f.map(async h=>{try{const m=await this.fetchDataSource(h,n,r,i,l);c[h.id]=m,this.dataCache.set(h.id,{data:m,timestamp:Date.now()});const y=this.checkErrorCondition(h,m);if(y!==null){const v={status:y,message:`Error condition matched for ${h.id}`,data:m},S=kr(),_=S.resolve(y,{errorHandling:h.errorHandling,onError:h.onError});if(_.handler)try{await S.execute(_.handler,v)}catch(x){Ct.error(`Error executing errorCondition handler for ${h.id}:`,x)}h.fallback!==void 0&&(c[h.id]=h.fallback,this.dataCache.set(h.id,{data:h.fallback,timestamp:Date.now()}),Ct.log(`Using fallback data for ${h.id} (errorCondition)`));return}if(h.onSuccess)try{const v={data:m,sourceId:h.id};await this.executeOnSuccessHandler(h.onSuccess,v)}catch(v){Ct.error(`Error executing onSuccess handler for ${h.id}:`,v)}}catch(m){Ct.error(`Failed to fetch data source: ${h.id}`,m);const y=m?.response?.status||m?.status||500,v=m?.response?.data,S=v?.message||m.message||"Unknown error",_={status:y,message:S,errors:v?.errors,data:v,statusText:m?.response?.statusText},x=kr(),A=x.resolve(y,{errorHandling:h.errorHandling,onError:h.onError});if(A.handler)try{await x.execute(A.handler,_)}catch(D){Ct.error(`Error executing error handler for ${h.id}:`,D)}h.fallback!==void 0&&(c[h.id]=h.fallback,this.dataCache.set(h.id,{data:h.fallback,timestamp:Date.now()}),Ct.log(`Using fallback data for ${h.id}`)),this.options.onError&&this.options.onError(m,h)}})),c}async fetchDataSourcesWithResults(e,n={},r=new URLSearchParams,i,l,c){if(e.some(m=>m.type==="websocket")){const m=e.filter(y=>y.type==="websocket").map(y=>y.id).join(", ");Ct.warn(`fetchDataSourcesWithResults received WebSocket sources (caller should filter them out before calling): ${m}. WebSocket sources are event listeners, not data providers — they should not be passed to fetch.`)}const f=c?.ignoreAutoFetch?e.filter(m=>m.type!=="websocket"):e.filter(m=>m.auto_fetch!==!1&&m.type!=="websocket");return await Promise.all(f.map(async m=>{try{const y=await this.fetchDataSource(m,n,r,i,l);this.dataCache.set(m.id,{data:y,timestamp:Date.now()});const v=this.checkErrorCondition(m,y);if(v!==null){const S={status:v,message:`Error condition matched for ${m.id}`,data:y},_=kr(),x=_.resolve(v,{errorHandling:m.errorHandling,onError:m.onError});let A=!1;if(x.handler)if(m.loading_strategy==="blocking")_.execute(x.handler,S).catch(D=>{Ct.error(`Error executing errorCondition handler for ${m.id}:`,D)}),A=!0;else try{await _.execute(x.handler,S),A=!0}catch(D){Ct.error(`Error executing errorCondition handler for ${m.id}:`,D)}return m.fallback!==void 0?(this.dataCache.set(m.id,{data:m.fallback,timestamp:Date.now()}),Ct.log(`Using fallback data for ${m.id} (errorCondition)`),{id:m.id,state:"success",data:m.fallback}):{id:m.id,state:"error",error:new Error(`Error condition matched: ${v}`),errorCode:v,errorHandled:A}}if(m.onSuccess)try{const S={data:y,sourceId:m.id};await this.executeOnSuccessHandler(m.onSuccess,S)}catch(S){Ct.error(`Error executing onSuccess handler for ${m.id}:`,S)}return{id:m.id,state:"success",data:y}}catch(y){Ct.error(`Failed to fetch data source: ${m.id}`,y);const v=y?.response?.status||y?.status||500,S=y?.response?.data,_=S?.message||y.message||"Unknown error",x={status:v,message:_,errors:S?.errors,data:S,statusText:y?.response?.statusText},A=kr(),D=A.resolve(v,{errorHandling:m.errorHandling,onError:m.onError});let O=!1;if(D.handler)if(m.loading_strategy==="blocking")A.execute(D.handler,x).catch(k=>{Ct.error(`Error executing error handler for ${m.id}:`,k)}),O=!0;else try{await A.execute(D.handler,x),O=!0}catch(k){Ct.error(`Error executing error handler for ${m.id}:`,k)}return m.fallback!==void 0?(this.dataCache.set(m.id,{data:m.fallback,timestamp:Date.now()}),Ct.log(`Using fallback data for ${m.id}`),{id:m.id,state:"success",data:m.fallback}):(this.options.onError&&this.options.onError(y,m),{id:m.id,state:"error",error:y,errorCode:v,errorHandled:O})}}))}fetchDataSourcesInBackground(e,n={},r=new URLSearchParams,i){e.filter(c=>c.auto_fetch!==!1&&c.loading_strategy==="background"&&c.type!=="websocket").forEach(c=>{this.fetchDataSource(c,n,r).then(async d=>{this.dataCache.set(c.id,{data:d,timestamp:Date.now()});const f=this.checkErrorCondition(c,d);if(f!==null){const h={status:f,message:`Error condition matched for ${c.id}`,data:d},m=kr(),y=m.resolve(f,{errorHandling:c.errorHandling,onError:c.onError});if(y.handler)try{await m.execute(y.handler,h)}catch(v){Ct.error(`Error executing errorCondition handler for ${c.id}:`,v)}c.fallback!==void 0&&(this.dataCache.set(c.id,{data:c.fallback,timestamp:Date.now()}),Ct.log(`Using fallback data for ${c.id} (errorCondition)`),i&&i(c.id,c.fallback));return}if(c.onSuccess)try{const h={data:d,sourceId:c.id};await this.executeOnSuccessHandler(c.onSuccess,h)}catch(h){Ct.error(`Error executing onSuccess handler for ${c.id}:`,h)}i&&i(c.id,d)}).catch(async d=>{Ct.error(`Background fetch failed: ${c.id}`,d);const f=d?.response?.status||d?.status||500,h=d?.response?.data,m=h?.message||d.message||"Unknown error",y={status:f,message:m,errors:h?.errors,data:h,statusText:d?.response?.statusText},v=kr(),S=v.resolve(f,{errorHandling:c.errorHandling,onError:c.onError});if(S.handler)try{await v.execute(S.handler,y)}catch(_){Ct.error(`Error executing error handler for ${c.id}:`,_)}if(c.fallback!==void 0){this.dataCache.set(c.id,{data:c.fallback,timestamp:Date.now()}),Ct.log(`Using fallback data for ${c.id}`),i&&i(c.id,c.fallback);return}this.options.onError&&this.options.onError(d,c)})})}async fetchDataSource(e,n,r,i,l){switch(e.type){case"api":return this.fetchApiDataSource(e,n,r,i,l);case"static":return e.data;case"route_params":return n;case"query_params":return xb(r);default:throw new Error(`Unknown data source type: ${e.type}`)}}async fetchApiDataSource(e,n,r,i,l){if(this.options.sampleProvider?.has(e.id))return await Promise.resolve(this.options.sampleProvider.resolve(e));if(!e.endpoint)throw new Error(`API data source ${e.id} has no endpoint`);const c={route:n,query:xb(r)};i&&(c._global=i),l&&(c._local=l);let d=ps(e.endpoint,c,{skipCache:!0});const f=e.method||"GET",m=(e.contentType?typeof e.contentType=="string"&&e.contentType.includes("{{")?ps(e.contentType,c,{skipCache:!0}):e.contentType:"application/json")==="multipart/form-data";let y=m?{...e.params||{}}:JSON.parse(JSON.stringify(e.params||{}));Object.entries(y).forEach(([_,x])=>{typeof x=="string"&&(y[_]=Gc(x,c))}),Object.keys(y).forEach(_=>{y[_]===""&&delete y[_]});const v=Bx();let S=null;if(v?.isEnabled()){v.trackDataSourceDefinition({id:e.id,type:e.type,endpoint:d,method:f,autoFetch:e.auto_fetch,initLocal:e.initLocal,initGlobal:e.initGlobal});let _=d;if(f==="GET"&&Object.keys(y).length>0){const x=new URLSearchParams;Object.entries(y).forEach(([D,O])=>{Array.isArray(O)?O.forEach(k=>x.append(D,String(k))):O!=null&&x.append(D,String(O))});const A=x.toString();_=A?`${d}?${A}`:d}S=v.trackRequest(_,f,{requestBody:f!=="GET"?y:void 0,dataSourceId:e.id}),v.trackDataSourceLoading(e.id)}try{let _;const x=e.auth_mode??(e.auth_required===!0?"required":"none"),A=Xn.getInstance().isAuthenticated();if(x==="required"&&!A){Ct.log(`[DataSourceManager] Skipping ${e.id}: auth_mode is 'required' but no token available`),v?.isEnabled()&&(v.trackDataSourceError(e.id,"Skipped: auth_mode required but not authenticated"),S&&v.failRequest(S,"Skipped: not authenticated"));const O=new Error("Auth required but not authenticated");throw O.response={status:401,statusText:"Unauthorized",data:null},O.status=401,O._authSkipped=!0,O}const D=x==="required"||x==="optional"&&A;if(D){const O=Yn(),k=d.startsWith("/api/")?d.substring(4):d.startsWith("/api")?d.substring(4)||"/":d;this.options.onError&&Ct.log(`[DataSourceManager] Normalized endpoint: ${d} -> ${k}`);const L=this.getMatchingGlobalHeaders(d,c),B={};e.headers&&Object.entries(e.headers).forEach(([de,_e])=>{const Se=Gc(_e,c);Se!=null&&Se!==""&&(B[de]=String(Se))});const q={...L,...B},z=Object.keys(q).length>0,W=m?this.toFormData(y):y;switch(f){case"GET":_=await O.get(k,{params:y,...z&&{headers:q}});break;case"POST":z?_=await O.post(k,W,{headers:q}):_=await O.post(k,W);break;case"PUT":z?_=await O.put(k,W,{headers:q}):_=await O.put(k,W);break;case"PATCH":z?_=await O.patch(k,W,{headers:q}):_=await O.patch(k,W);break;case"DELETE":_=await O.delete(k,{params:y,...z&&{headers:q}});break;default:throw new Error(`Unsupported HTTP method: ${f}`)}}else{let O=d;if(f==="GET"){const W=new URLSearchParams(y).toString();O=W?`${d}?${W}`:d}const k={...m?{}:{"Content-Type":"application/json"},Accept:"application/json"};if(typeof window<"u"){const W=localStorage.getItem("g7_locale");W&&(k["Accept-Language"]=W)}const L=this.getMatchingGlobalHeaders(d,c);Object.assign(k,L),e.headers&&Object.entries(e.headers).forEach(([W,de])=>{const _e=Gc(de,c);_e!=null&&_e!==""&&(k[W]=String(_e))});let B;f!=="GET"&&(m?(B=this.toFormData(y),delete k["Content-Type"]):B=JSON.stringify(y));const q=await fetch(O,{method:f,headers:k,body:B});let z;try{z=await q.json()}catch{z=null}if(S&&v?.isEnabled()&&(v.completeRequest(S,q.status,z),S=null),!q.ok){v?.isEnabled()&&v.trackDataSourceError(e.id,`HTTP ${q.status}: ${q.statusText}`);const W=new Error(`HTTP error! status: ${q.status}`);throw W.response={status:q.status,statusText:q.statusText,data:z},W.status=q.status,W}_=z,v?.isEnabled()&&v.trackDataSourceLoaded(e.id,_)}return D&&S&&v?.isEnabled()&&(v.completeRequest(S,200,_),v.trackDataSourceLoaded(e.id,_)),_}catch(_){if(S&&v?.isEnabled()){const x=_;x?.response?v.completeRequest(S,x.response.status,x.response.data):v.failRequest(S,_ instanceof Error?_.message:String(_)),v.trackDataSourceError(e.id,_ instanceof Error?_.message:String(_))}throw _}}getCachedData(e){const n=this.dataCache.get(e);if(!n)return;if(Date.now()-n.timestamp>Xc.DATA_CACHE_TTL){this.dataCache.delete(e);return}return n.data}clearCache(){this.dataCache.clear()}subscribeWebSockets(e,n,r={}){const i=[],l=e.filter(c=>c.type==="websocket");return l.length===0||l.forEach(c=>{if(!c.channel||!c.event){Ct.warn(`WebSocket source ${c.id} missing channel or event`);return}let d,f;try{d=ps(c.channel,r,{skipCache:!0}),f=ps(c.event,r,{skipCache:!0})}catch(y){Ct.error(`Failed to resolve WebSocket channel/event for source ${c.id}:`,y);return}const h=y=>!!(!y||y.trim()===""||y.includes("{{")||y.endsWith(".")||y.endsWith(":")||y.startsWith(".")||y.startsWith(":")||y.includes("..")||y.includes("::"));if(h(d)||h(f)){Ct.warn(`WebSocket source ${c.id} has invalid channel/event after expression resolution, skipping. Original channel="${c.channel}", resolved="${d}". Original event="${c.event}", resolved="${f}". Available context keys: [${Object.keys(r).join(", ")}]`);return}const m=qc.subscribe(d,f,y=>{const v=c.target_source||c.id;this.dataCache.set(v,{data:y,timestamp:Date.now()}),n(v,y)},{channelType:c.channel_type||"private"});m&&i.push(m)}),i}unsubscribeWebSockets(e){e.length!==0&&e.forEach(n=>{qc.unsubscribe(n)})}async executeOnSuccessHandler(e,n){const r=this.options.actionDispatcher??Cy();if(!r){Ct.warn("ActionDispatcher not available, skipping onSuccess handler");return}const i=Array.isArray(e)?e:[e];for(const l of i)try{await r.dispatchAction({type:"click",...l},{data:{response:n}})}catch(c){throw Ct.error("Failed to execute onSuccess handler:",c),c}}};M(Xc,"DATA_CACHE_TTL",3e5);let Or=Xc;const qx=new Or,Ri=pt("ModalDataSourceWrapper");function Gx(s,e){if(!s||!e)return s;const r=e.replace(/\?\./g,".").split(/\.(?![^\[]*\])/).flatMap(l=>{const c=l.match(/^([^\[]*)((?:\[\d+\])*)$/);if(c){const[,d,f]=c,h=[];d&&h.push(d);const m=f.match(/\[\d+\]/g);return m&&h.push(...m),h}return[l]}).filter(l=>l!=="");let i=s;for(const l of r){if(i==null)return;if(l.startsWith("[")&&l.endsWith("]")){const c=parseInt(l.slice(1,-1),10);i=i[c]}else i=i[l]}return i}const Tb=({isOpen:s,modalId:e,dataSources:n,dataContext:r,globalStateUpdater:i,bindingEngine:l,debug:c=!1,children:d})=>{const[f,h]=N.useState(!1),[m,y]=N.useState(!1),v=N.useRef(!1),S=N.useRef(null);S.current||(S.current=new Or);const _=N.useCallback(async()=>{if(!(!n||n.length===0)&&S.current){h(!0);try{const x=r.route||{},A=new URLSearchParams(window.location.search),D=n.map(k=>{let L={...k};if(k.endpoint&&l){const B=l.resolveBindings(k.endpoint,r,{skipCache:!0});L.endpoint=B}if(k.params&&l){const B={};Object.entries(k.params).forEach(([q,z])=>{typeof z=="string"?B[q]=l.resolveBindings(z,r,{skipCache:!0}):B[q]=z}),L.params=B}return L});c&&Ri.log(`[ModalDataSourceWrapper] Modal "${e}" fetching data sources:`,D.map(k=>({id:k.id,endpoint:k.endpoint})));const O=await S.current.fetchDataSourcesWithResults(D,x,A);i&&O.forEach(k=>{if(k.state==="success"&&k.data!==void 0){const L=D.find(B=>B.id===k.id);if(L?.initGlobal){const B=k.data?.data??k.data,q=Array.isArray(L.initGlobal)?L.initGlobal:[L.initGlobal];for(const z of q)if(typeof z=="string")i({[z]:B}),c&&Ri.log(`[ModalDataSourceWrapper] Modal "${e}" initGlobal: ${k.id}.data -> _global.${z}`);else if(typeof z=="object"&&z.key){const{key:W,path:de}=z,_e=de?Gx(B,de):B;i({[W]:_e}),c&&Ri.log(`Modal "${e}" initGlobal: ${k.id}.data${de?"."+de:""} -> _global.${W}`)}}}else k.state==="error"&&Ri.error(`Modal "${e}" data source error:`,k.id,k.error)}),c&&Ri.log(`Modal "${e}" data sources fetched successfully`)}catch(x){Ri.error(`Modal "${e}" data source fetch error:`,x)}finally{h(!1),y(!0)}}},[n,r,i,l,e,c]);return N.useEffect(()=>{if(!n||n.length===0)return;const x=window.__templateApp;return x?.registerModalDataSources&&x.registerModalDataSources(e,n),()=>{x?.unregisterModalDataSources&&x.unregisterModalDataSources(e)}},[e,n]),N.useEffect(()=>{!s&&v.current&&(y(!1),c&&Ri.log(`[ModalDataSourceWrapper] Modal "${e}" closed, reset fetch state`)),s&&!v.current&&n&&n.length>0&&!m&&_(),v.current=s},[s,n,m,_,e,c]),yt.jsx(yt.Fragment,{children:d})};class Fc extends Error{constructor(n,r,i={}){super(r);M(this,"code");M(this,"userMessageKey");M(this,"showStack");M(this,"icon");M(this,"recoverable");this.name="TemplateEngineError",this.code=n,this.userMessageKey=r,this.showStack=i.showStack??!0,this.icon=i.icon??"fa-circle-exclamation",this.recoverable=i.recoverable??!0,Error.captureStackTrace&&Error.captureStackTrace(this,this.constructor)}getUserMessage(n,r){if(!r||!n)return this.userMessageKey;try{return r.translate(this.userMessageKey,n)}catch{return this.userMessageKey}}}class Fx extends Fc{constructor(){super("TEMPLATE_NOT_FOUND","$t:core.errors.template_not_found",{showStack:!1,icon:"fa-paint-brush",recoverable:!1}),this.name="TemplateNotFoundError"}}function kb(s){if(s instanceof Fc)return s;if(s instanceof Error){const e=new Fc("TEMPLATE_INIT_ERROR",s.message||"$t:core.errors.template_init_error",{showStack:!0,icon:"fa-circle-exclamation",recoverable:!0});return e.stack=s.stack,e}return new Fc("TEMPLATE_INIT_ERROR","$t:core.errors.template_init_error",{showStack:!1,icon:"fa-circle-exclamation",recoverable:!0})}const Vx=pt("ErrorDisplay");class lh{static render(e,n){const r=document.getElementById(e);if(!r){Vx.error(`Container #${e} not found`);return}const i=n.debug&&n.showStack&&n.stackTrace,l=window.matchMedia("(prefers-color-scheme: dark)").matches,c={container:` display: flex; align-items: center; justify-content: center; @@ -227,4 +273,4 @@ Error generating stack: `+u.message+` `:""} - `}static renderFromError(e,n,r,i=!1,l,c,d){let f,h;if(n.userMessageKey.startsWith("$t:"))if(c&&l)try{f=c.translate(n.userMessageKey,l)}catch{f=n.userMessageKey.replace("$t:","")}else f=n.userMessageKey.replace("$t:","");else f=n.userMessageKey;if(d){const m=d;m.details?.apiMessage?h=m.details.apiMessage:m.response?.data?.message&&(h=m.response.data.message)}this.render(e,{title:r,message:f,detailMessage:h,icon:n.icon,showStack:n.showStack,stackTrace:n.stack,showReloadButton:n.recoverable,debug:i})}static escapeHtml(e){if(!e)return"";const n=document.createElement("div");return n.textContent=e,n.innerHTML}}const _s=pt("Router");class Wx{constructor(e){M(this,"routes",[]);M(this,"templateIdentifier");M(this,"eventHandlers",new Map);M(this,"pendingNavigation",null);M(this,"isNavigating",!1);M(this,"handlePopState",()=>{this.navigateToCurrentPath()});this.templateIdentifier=e,this.initPopstateListener()}initPopstateListener(){window.addEventListener("popstate",this.handlePopState)}on(e,n){this.eventHandlers.has(e)||this.eventHandlers.set(e,[]),this.eventHandlers.get(e).push(n)}async emit(e,...n){const r=this.eventHandlers.get(e);r&&await Promise.all(r.map(i=>i(...n)))}async loadRoutes(e){try{const n=e!==void 0&&e>0?e:null,r=await fetch(Ur(`/api/templates/${this.templateIdentifier}/routes`,"json",n));if(!r.ok)throw new Error(`Failed to load routes: ${r.statusText}`);const i=await r.json();if(!i.success)throw new Error("Failed to load routes from API");if(i.data&&Array.isArray(i.data.routes))this.routes=i.data.routes,_s.log(`Loaded ${this.routes.length} routes${e?` (v=${e})`:""}`);else throw new Error("Invalid routes data format")}catch(n){throw _s.error("Error loading routes:",n),n}}setRoutes(e){this.routes=e,_s.log(`Set ${this.routes.length} routes`)}match(e){const n=this.normalizePathname(e);for(const r of this.routes){const i=this.matchPattern(r.path,n);if(i!==null)return{route:r,params:i}}return null}normalizePathname(e){return e.length>1&&e.endsWith("/")?e.replace(/\/+$/,"")||"/":e}matchPattern(e,n){const r=[];let i=e.replace(/:([^/]+)/g,(f,h)=>(r.push(h),"([^/]+)"));i.startsWith("*/")&&(i="(?:/[^/]+)?"+i.slice(1)),i=i.replace(/\//g,"\\/");const l=new RegExp(`^${i}$`),c=n.match(l);if(!c)return null;const d={};return r.forEach((f,h)=>{d[f]=c[h+1]}),d}getRoutes(){return[...this.routes]}getAuthType(e,n){return e.auth_type?e.auth_type:n.startsWith("/admin")?"admin":"user"}async navigateToCurrentPath(){const e=window.location.pathname,n=window.location.search,r=this.match(e);if(!r){_s.warn(`No route matched for path: ${e}`),this.emit("routeNotFound",e);return}if(r.route.redirect){_s.log(`Redirecting from ${e} to ${r.route.redirect}`),this.navigate(r.route.redirect);return}if(r.route.auth_required){const c=this.getAuthType(r.route,e),d=Ar.getInstance();let f=d.isAuthenticated()&&d.getAuthType()===c;if(f||(f=await d.checkAuth(c)),!f){const h=d.getLoginRedirectUrl(c,e+n);_s.log(`Not authenticated, redirecting to: ${h}`),window.location.href=h;return}}const i=new URLSearchParams(n),l={};for(const c of i.keys()){if(c in l)continue;const d=i.getAll(c);d.length>1?l[c]=d:d.length===1&&(c.endsWith("[]")?l[c]=d:l[c]=d[0])}await this.emit("routeChange",{path:e,layout:r.route.layout,endpoint:r.route.endpoint,params:{...r.route.params||{},...r.params},query:l,auth_required:r.route.auth_required,auth_type:r.route.auth_type,meta:r.route.meta})}navigate(e){if(this.isNavigating){this.pendingNavigation=e;return}this.executeNavigation(e)}async executeNavigation(e){this.isNavigating=!0,this.pendingNavigation=null;try{window.history.pushState({},"",e),await this.navigateToCurrentPath()}finally{if(this.isNavigating=!1,this.pendingNavigation){const n=this.pendingNavigation;this.executeNavigation(n)}}}}const Yx=/^\/admin\/layout-editor\/([^/?#]+)\/?$/;function kb(s){const e=Yx.exec(s);return e?{templateIdentifier:e[1]}:null}const Wt=pt("ErrorPageHandler");class Xx{constructor(e){M(this,"templateId");M(this,"layoutLoader");M(this,"locale");M(this,"debug");M(this,"renderFunction");M(this,"dataSourceManager");M(this,"globalState");M(this,"errorConfig",null);M(this,"configLoaded",!1);this.templateId=e.templateId,this.layoutLoader=e.layoutLoader,this.locale=e.locale,this.debug=e.debug,this.renderFunction=e.renderFunction,this.dataSourceManager=e.dataSourceManager,this.globalState=e.globalState||{}}async loadConfig(){if(this.configLoaded)return this.errorConfig;try{const e=await fetch(Ur(`/api/templates/${this.templateId}/config`,"json"));if(!e.ok)return this.debug&&Wt.warn("Failed to load template config:",e.statusText),this.configLoaded=!0,null;const n=await e.json();if(!n.success||!n.data)return this.debug&&Wt.warn("Invalid template config response"),this.configLoaded=!0,null;const r=n.data;return!r.error_config||!r.error_config.layouts?(this.debug&&Wt.warn("No error_config found in template.json"),this.configLoaded=!0,null):(this.errorConfig=r.error_config,this.configLoaded=!0,this.debug&&Wt.log("Error config loaded:",this.errorConfig),this.errorConfig)}catch(e){return Wt.error("Failed to load error config:",e),this.configLoaded=!0,null}}async renderError(e,n="app"){try{const r=await this.loadConfig();if(!r)return this.debug&&Wt.warn("No error config available, cannot render error page"),!1;const i=r.layouts[e]||r.layouts[String(e)];if(!i)return this.debug&&Wt.warn(`No layout defined for error code: ${e}`),!1;this.debug&&Wt.log(`Loading error layout: ${i} for code: ${e}`);const l=await this.layoutLoader.loadLayout(this.templateId,i);if(!l)return this.debug&&Wt.error(`Failed to load error layout: ${i}`),!1;this.debug&&Wt.log("Error layout loaded:",l);let c={};const d=l.data_sources||[];if(d.length>0){this.debug&&Wt.log("Fetching data sources:",d.map(S=>S.id));const m=d.filter(S=>S.loading_strategy==="blocking"),y=d.filter(S=>!S.loading_strategy||S.loading_strategy==="progressive"),v=[...m,...y];if(v.length>0)try{c=await this.dataSourceManager.fetchDataSources(v,{},new URLSearchParams),this.debug&&Wt.log("Data sources fetched:",Object.keys(c))}catch(S){Wt.error("Failed to fetch data sources:",S)}}const f={};d.length>0&&(this.processInitOptions(d,c,f),this.debug&&Wt.log("initOptions processed:",{globalKeys:Object.keys(this.globalState),localKeys:Object.keys(f)}));const h={...c,errorCode:e,_global:{...this.globalState},_local:{...f}};return await this.renderFunction({containerId:n,layoutJson:l,dataContext:h,translationContext:{templateId:this.templateId,locale:this.locale}}),this.debug&&Wt.log(`Error page ${e} rendered successfully`),!0}catch(r){return Wt.error(`Failed to render error page ${e}:`,r),!1}}processInitOptions(e,n,r){for(const i of e){const l=n[i.id];if(!l)continue;const c=l?.data??l;if(i.initLocal){if(typeof i.initLocal=="string")r[i.initLocal]=c,Wt.log(`initLocal: ${i.id}.data -> _local.${i.initLocal}`);else if(typeof i.initLocal=="object"&&"key"in i.initLocal){const{key:d,path:f}=i.initLocal;r[d]=f?this.getValueByPath(c,f):c,Wt.log(`initLocal: ${i.id}.data${f?"."+f:""} -> _local.${d}`)}}if(i.initGlobal){const d=Array.isArray(i.initGlobal)?i.initGlobal:[i.initGlobal];for(const f of d)if(typeof f=="string")this.globalState[f]=c,Wt.log(`initGlobal: ${i.id}.data -> _global.${f}`);else if(typeof f=="object"&&f!==null&&"key"in f){const{key:h,path:m}=f;this.globalState[h]=m?this.getValueByPath(c,m):c,Wt.log(`initGlobal: ${i.id}.data${m?"."+m:""} -> _global.${h}`)}}}}getValueByPath(e,n){return n.split(".").reduce((r,i)=>r?.[i],e)}updateGlobalState(e){this.globalState={...this.globalState,...e}}updateLocale(e){this.locale=e,this.debug&&Wt.log("Locale updated:",e)}isConfigLoaded(){return this.configLoaded}async hasErrorLayout(e){const n=await this.loadConfig();return n?!!(n.layouts[e]||n.layouts[String(e)]):!1}clearConfigCache(){this.errorConfig=null,this.configLoaded=!1,this.debug&&Wt.log("Config cache cleared")}}const Kc=pt("AssetFailureNotice"),Rb="g7-asset-failure-notice",Jx="g7-system-banners",Kr=new Map;let As=!1,Wc=!1,Yo=null;function Ri(s,e,n){try{const r=window?.G7Core?.t;if(typeof r=="function"){const i=r(s,n);if(typeof i=="string"&&i!==s&&!i.startsWith("core.assets."))return i}}catch{}return Qx(e,n)}function Qx(s,e){return e?Object.entries(e).reduce((n,[r,i])=>n.split("{"+r+"}").join(String(i)),s):s}function xs(s){!s||!s.id||(Kr.set(s.id,s),Wc=!1,Kc.warn(`Asset failure notified: ${s.id} (${s.label})`),Ts())}function Db(s){Kr.delete(s)&&(Kc.log(`Asset failure cleared: ${s}`),Ts())}function Ob(){Kr.size!==0&&(Kr.clear(),Ts())}function Zx(){return Array.from(Kr.values())}function Lb(s,e,n){return Ri(s,e,n)}async function $b(){if(As)return;As=!0,Wc=!1,Ts();const s=Array.from(Kr.values()).filter(n=>typeof n.retry=="function");let e=!1;for(const n of s)try{await n.retry(),Kr.delete(n.id)}catch(r){e=!0,Kc.warn(`Asset retry failed: ${n.id}`,r)}As=!1,Wc=e,Ts()}function eT(){try{return document.documentElement.classList.contains("dark")}catch{return!1}}function Ts(){if(typeof document>"u"||!document.body)return;const s=document.getElementById(Rb);if(Kr.size===0){s?.remove(),oT();return}const e=s??tT();nT(e),e.replaceChildren(rT()),sT()}function tT(){const s=document.createElement("div");return s.id=Rb,s.setAttribute("role","alert"),s.setAttribute("aria-live","polite"),document.body.appendChild(s),s}function nT(s){const n=document.getElementById(Jx)?.offsetHeight??0;s.style.cssText=["position:fixed",`top:${n}px`,"left:0","right:0","z-index:9998","display:flex","justify-content:center","pointer-events:none"].join(";")}function rT(){const s=eT(),e=Array.from(Kr.values()),n=typeof window<"u"&&window.innerWidth<768,r=document.createElement("div");return r.style.cssText=["pointer-events:auto","box-sizing:border-box","width:100%","max-width:960px","margin:8px","padding:12px 16px","border-radius:8px",`border:1px solid ${s?"#78350f":"#fcd34d"}`,`background:${s?"#451a03":"#fffbeb"}`,`color:${s?"#fef3c7":"#78350f"}`,"box-shadow:0 4px 12px rgba(0,0,0,0.12)","font-size:14px","line-height:1.5","display:flex",n?"flex-direction:column":"flex-direction:row",n?"align-items:stretch":"align-items:center","gap:12px"].join(";"),r.style.background=s?"#451a03":"#fffbeb",r.style.color=s?"#fef3c7":"#78350f",r.style.borderColor=s?"#78350f":"#fcd34d",r.appendChild(aT(e,n)),r.appendChild(iT(e,n,s)),r}function aT(s,e){const n=document.createElement("div");n.style.cssText=["flex:1 1 auto","min-width:0","display:flex","gap:8px"].join(";");const r=document.createElement("span");r.setAttribute("aria-hidden","true"),r.textContent="⚠",r.style.cssText="flex:0 0 auto;font-size:16px;line-height:1.4",n.appendChild(r);const i=document.createElement("div");if(i.style.cssText="flex:1 1 auto;min-width:0",s.length===1){const l=s[0],c=document.createElement("div");c.setAttribute("data-asset-failure-id",l.id),c.textContent=l.message??Ri("core.assets.load_failed","{label}을(를) 불러오지 못했습니다.",{label:l.label}),i.appendChild(c)}else{const l=document.createElement("div");l.style.cssText="font-weight:600",l.textContent=Ri("core.assets.load_failed_multiple","{count}개 항목을 불러오지 못했습니다.",{count:s.length}),i.appendChild(l);const c=document.createElement("div");c.style.cssText="margin-top:2px;opacity:0.9;word-break:break-word",c.textContent=s.map(d=>d.label).join(", "),i.appendChild(c)}if(Wc){const l=document.createElement("div");l.style.cssText="margin-top:4px;font-weight:600",l.textContent=Ri("core.assets.retry_failed","다시 시도했지만 실패했습니다. 잠시 후 다시 시도해 주세요."),i.appendChild(l)}return n.appendChild(i),e&&(n.style.alignItems="flex-start"),n}function iT(s,e,n){const r=document.createElement("div");if(r.style.cssText=["flex:0 0 auto","display:flex","gap:8px",e?"width:100%":"","align-items:center"].filter(Boolean).join(";"),s.some(c=>typeof c.retry=="function")){const c=document.createElement("button");c.type="button",c.dataset.action="retry",c.disabled=As,c.textContent=As?Ri("core.assets.retrying","다시 시도 중..."):Ri("core.assets.retry","다시 시도"),c.style.cssText=[e?"flex:1 1 auto":"","padding:6px 14px","border-radius:6px","font-size:14px","font-weight:600","cursor:pointer",`border:1px solid ${n?"#b45309":"#d97706"}`,`background:${n?"#b45309":"#f59e0b"}`,`color:${n?"#fffbeb":"#451a03"}`,As?"opacity:0.6":""].filter(Boolean).join(";"),c.addEventListener("click",()=>{$b()}),r.appendChild(c)}const l=document.createElement("button");return l.type="button",l.dataset.action="close",l.setAttribute("aria-label",Ri("core.assets.close","닫기")),l.textContent="✕",l.style.cssText=["padding:6px 10px","border-radius:6px","font-size:14px","cursor:pointer","border:1px solid transparent","background:transparent",`color:${n?"#fef3c7":"#78350f"}`].join(";"),l.addEventListener("click",()=>{Ob()}),r.appendChild(l),r}function sT(){Yo||typeof MutationObserver>"u"||(Yo=new MutationObserver(()=>{Kr.size>0&&Ts()}),Yo.observe(document.documentElement,{attributes:!0,attributeFilter:["class"]}))}function oT(){Yo?.disconnect(),Yo=null}function lT(){const s=window,e=r=>({id:String(r?.id??""),label:String(r?.label??r?.url??"asset"),retry:typeof r?.retry=="function"?r.retry:void 0}),n=Array.isArray(s.__g7ExternalAssetFailures)?s.__g7ExternalAssetFailures:[];n.forEach(r=>{r?.id&&xs(e(r))}),s.__g7ExternalAssetSink=r=>{r?.id&&xs(e(r))},Kc.log(`템플릿 externals 실패 대기열 ${n.length}건 반영`)}const Rt=pt("ModuleAssetLoader");function cT(s){return s==="plugin"?Lb("core.assets.plugin_styles","플러그인 스타일"):Lb("core.assets.module_styles","모듈 스타일")}class uT{constructor(){M(this,"loadedAssets",new Map);M(this,"loadingPromises",new Map);M(this,"failedJsAssets",new Set);M(this,"failedCssAssets",new Set)}getFailedCssAssets(){return[...this.failedCssAssets]}hasFailedJsAssets(){return this.failedJsAssets.size>0}getFailedJsAssets(){return[...this.failedJsAssets]}async loadActiveExtensionAssets(e){if(!e||e.length===0){Rt.log("No module assets to load");return}const n=[...e].sort((d,f)=>d.priority-f.priority);Rt.log("Loading module assets:",n.map(d=>d.identifier));const r=n.filter(d=>d.css).map(d=>this.loadCSS(d.identifier,d.css)),i=n.filter(d=>d.js).map(d=>this.loadJS(d.identifier,d.js)),l=await Promise.allSettled([...r,...i]),c=l.filter(d=>d.status==="rejected");if(c.length>0){Rt.warn(`Some module assets failed to load (${c.length}/${l.length}); continuing with the rest`,this.getFailedJsAssets());return}Rt.log("All module assets loaded successfully")}async loadBundle(e,n,r){const i=[];if(r&&i.push(this.loadBundleCss(e,r)),n&&i.push(this.loadBundleJs(e,n)),i.length===0){Rt.log(`No bundle assets to load for: ${e}`);return}await Promise.all(i)}async loadCustomAssets(){const e=dT();if(e.length!==0)for(const n of e){const r=`g7-custom-${n.type}-${fT(n.id)}`;if(document.getElementById(r))continue;const i=`${n.type==="style"?"custom CSS":"custom JS"}: ${n.id}`,l=(d,f)=>n.type==="style"?ia(Ln(d),{id:r},{label:i,retries:f}):Cr(Ln(d),{id:r},{label:i,retries:f}),c=jd(n.url);try{if(c!==null)try{await l(n.url,0)}catch{Rt.warn(`Custom asset static miss, falling back to API: ${n.id} (${n.url} -> ${c})`),await l(c)}else await l(n.url);Rt.log(`Custom asset loaded: ${n.id}`)}catch(d){Rt.warn(`Failed to load custom asset: ${n.id} (${n.url})`,d),xs({id:`custom-asset:${n.id}`,label:n.id.split(":").pop()??n.id,retry:async()=>{document.getElementById(r)?.remove(),await l(c??n.url)}})}}}async loadBundleCss(e,n){const r=`ext-bundle-css-${e}`;if(document.getElementById(r)){Rt.log(`Bundle CSS already loaded: ${e}`);return}const i=jd(n);try{if(i!==null)try{await ia(n,{id:r},{label:`bundle CSS: ${e}`,retries:0})}catch{Rt.warn(`Bundle CSS static miss, falling back to API: ${e} (${n} -> ${i})`),await ia(Ln(i),{id:r},{label:`bundle CSS: ${e}`})}else await ia(n,{id:r},{label:`bundle CSS: ${e}`});Rt.log(`Bundle CSS loaded: ${e}`);const l=document.getElementById(r);l&&this.registerLoadedAsset(`bundle-${e}`,{type:"css",element:l})}catch(l){this.surfaceCssFailure(`bundle-${e}`,cT(e),i!==null?Ln(i):n,l)}}async loadBundleJs(e,n){const r=`ext-bundle-js-${e}`;if(document.getElementById(r)){Rt.log(`Bundle JS already loaded: ${e}`);return}const i=this.loadingPromises.get(r);if(i)return Rt.log(`Bundle JS already loading: ${e}`),i;const l=jd(n),d=(l!==null?Cr(n,{id:r},{label:`bundle JS: ${e}`,retries:0}).catch(()=>(Rt.warn(`Bundle JS static miss, falling back to API: ${e} (${n} -> ${l})`),Cr(Ln(l),{id:r},{label:`bundle JS: ${e}`}))):Cr(n,{id:r},{label:`bundle JS: ${e}`})).then(()=>{Rt.log(`Bundle JS loaded: ${e}`);const f=document.getElementById(r);f&&this.registerLoadedAsset(`bundle-${e}`,{type:"js",element:f}),this.loadingPromises.delete(r)}).catch(f=>{throw Rt.warn(`Failed to load bundle JS: ${e} (${n})`,f),this.failedJsAssets.add(e),this.loadingPromises.delete(r),f});return this.loadingPromises.set(r,d),d}async loadCSS(e,n){const r=`module-css-${e}`;if(document.getElementById(r)){Rt.log(`CSS already loaded: ${e}`);return}const i=this.loadingPromises.get(r);if(i)return Rt.log(`CSS already loading: ${e}`),i;const l=ia(n,{id:r},{label:`CSS: ${e}`}).then(()=>{Rt.log(`CSS loaded: ${e}`);const c=document.getElementById(r);c&&this.registerLoadedAsset(e,{type:"css",element:c}),this.loadingPromises.delete(r)}).catch(c=>{this.loadingPromises.delete(r),this.surfaceCssFailure(e,e,n,c)});return this.loadingPromises.set(r,l),l}surfaceCssFailure(e,n,r,i){Rt.warn(`Failed to load CSS after retries: ${e} (${r})`,i),this.failedCssAssets.add(e),xs({id:`ext-css:${e}`,label:n,retry:async()=>{document.getElementById(`module-css-${e}`)?.remove(),document.getElementById(`ext-bundle-css-${e.replace(/^bundle-/,"")}`)?.remove(),await ia(r,{},{label:`CSS retry: ${e}`}),this.failedCssAssets.delete(e)}})}async loadJS(e,n){const r=`module-js-${e}`;if(document.getElementById(r)){Rt.log(`JS already loaded: ${e}`);return}const i=this.loadingPromises.get(e);if(i)return Rt.log(`JS already loading: ${e}`),i;const l=Cr(n,{id:r},{label:`JS: ${e}`}).then(()=>{Rt.log(`JS loaded: ${e}`);const c=document.getElementById(r);c&&this.registerLoadedAsset(e,{type:"js",element:c}),this.loadingPromises.delete(e)}).catch(c=>{throw Rt.warn(`Failed to load JS: ${e} (${n})`,c),this.failedJsAssets.add(e),this.loadingPromises.delete(e),c});return this.loadingPromises.set(e,l),l}registerLoadedAsset(e,n){const r=this.loadedAssets.get(e)||[];r.push(n),this.loadedAssets.set(e,r)}unloadExtensionAsset(e){const n=this.loadedAssets.get(e);if(!n||n.length===0){Rt.log(`No assets to unload for: ${e}`);return}n.forEach(r=>{r.element.parentNode&&(r.element.parentNode.removeChild(r.element),Rt.log(`${r.type.toUpperCase()} unloaded: ${e}`))}),this.loadedAssets.delete(e),Rt.log(`All assets unloaded for: ${e}`)}unloadAllAssets(){Array.from(this.loadedAssets.keys()).forEach(n=>{this.unloadExtensionAsset(n)}),Rt.log("All module assets unloaded")}isLoaded(e){return this.loadedAssets.has(e)}getLoadedModules(){return Array.from(this.loadedAssets.keys())}}let dh=null;function dT(){const s=window?.G7Config?.customAssets;return Array.isArray(s)?s.filter(e=>e&&typeof e.id=="string"&&typeof e.url=="string"&&(e.type==="style"||e.type==="script")):[]}function fT(s){return s.replace(/[^A-Za-z0-9_-]/g,"-")}function fh(){return dh||(dh=new uT),dh}function hT(){if(typeof window>"u")return[];const s=window.G7Config;if(!s?.moduleAssets)return[];const e=[];for(const[n,r]of Object.entries(s.moduleAssets)){const i=r;e.push({identifier:n,js:i.js?Ln(i.js):i.js,css:i.css?Ln(i.css):i.css,priority:i.priority,external:i.external})}return e}function pT(){if(typeof window>"u")return null;const s=window.G7Config;if(!s?.bundleUrls)return null;const e=s.bundleUrls;return{moduleJs:e.moduleJs?Ln(e.moduleJs):e.moduleJs,moduleCss:e.moduleCss?Ln(e.moduleCss):e.moduleCss,pluginJs:e.pluginJs?Ln(e.pluginJs):e.pluginJs,pluginCss:e.pluginCss?Ln(e.pluginCss):e.pluginCss}}function gT(){if(typeof window>"u")return[];const s=window.G7Config;if(!s?.pluginAssets)return[];const e=[];for(const[n,r]of Object.entries(s.pluginAssets)){const i=r;e.push({identifier:n,js:i.js?Ln(i.js):i.js,css:i.css?Ln(i.css):i.css,priority:i.priority,external:i.external})}return e}const hh=pt("SystemBannerManager");class Yc{static show(e){this.banners.set(e.id,e),this.render(),hh.log(`Banner shown: ${e.id}`)}static hide(e){this.banners.delete(e)&&(this.render(),hh.log(`Banner hidden: ${e}`))}static hideAll(){this.banners.clear(),this.render(),hh.log("All banners hidden")}static detectLocale(){try{const n=window.G7Core;if(n?.locale?.current)return n.locale.current()}catch{}return(navigator.language||"ko").split("-")[0]}static resolveMessage(e,n){return typeof e=="string"?e:e[n]||e.en||e.ko||Object.values(e)[0]||""}static render(){if(typeof document>"u")return;let e=document.getElementById(this.containerId);if(this.banners.size===0){e&&(e.remove(),this.adjustAppPadding(0));return}e||(e=document.createElement("div"),e.id=this.containerId,e.style.cssText="position:fixed;top:0;left:0;right:0;z-index:99999;",document.body.prepend(e));const n=Array.from(this.banners.values()).sort((i,l)=>(i.order??0)-(l.order??0)),r=this.detectLocale();e.innerHTML=n.map(i=>{const l=this.resolveMessage(i.message,r),c=i.background||"#f59e0b",d=i.color||"white";return`
${l}
`}).join(""),requestAnimationFrame(()=>{e&&this.adjustAppPadding(e.offsetHeight)})}static adjustAppPadding(e){const n=document.getElementById("app");n&&(n.style.paddingTop=e>0?`${e}px`:"")}}M(Yc,"banners",new Map),M(Yc,"containerId","g7-system-banners");const U=pt("TemplateApp");function Mb(s){const e={};for(const n of s.keys()){if(n in e)continue;const r=s.getAll(n);r.length>1?e[n]=r:r.length===1&&(n.endsWith("[]")?e[n]=r:e[n]=r[0])}return e}const Lr=class Lr{constructor(e){M(this,"router",null);M(this,"layoutLoader",null);M(this,"errorPageHandler",null);M(this,"config");M(this,"globalState");M(this,"globalStateListeners",new Set);M(this,"currentRouteChangeId",0);M(this,"failedLayoutScripts",new Set);M(this,"currentDataSources",[]);M(this,"currentRawDataSources",[]);M(this,"currentRouteParams",{});M(this,"currentQueryParams",new URLSearchParams);M(this,"currentFetchedData",{});M(this,"currentLayoutName","");M(this,"templateErrorHandling",null);M(this,"currentWebSocketSubscriptions",[]);M(this,"extensionCacheVersion",0);M(this,"currentGlobalHeaders",[]);M(this,"transitionOverlayEl",null);M(this,"skeletonOverlayRoot",null);M(this,"skeletonOverlayContainer",null);M(this,"_spinnerState",null);M(this,"modalDataSources",new Map);this.globalState={sidebarOpen:!1},this.loadG7Config(),this.migrateLocaleStorage();const n=this.loadLocaleFromStorage(),r=n||e.locale||"ko";this.config={...e,locale:r},!n&&e.locale&&this.saveLocaleToStorage(e.locale)}loadG7Config(){if(typeof window<"u"&&window.G7Config){const e=window.G7Config;e.settings&&(this.globalState.settings=e.settings,U.log("Loaded settings from G7Config:",Object.keys(e.settings)),e.settings.upload&&(this.globalState.uploadSettings=e.settings.upload)),e.plugins&&(this.globalState.plugins=e.plugins,U.log("Loaded plugin settings from G7Config:",Object.keys(e.plugins))),e.modules&&(this.globalState.modules=e.modules,U.log("Loaded module settings from G7Config:",Object.keys(e.modules))),e.appConfig&&(this.globalState.appConfig=e.appConfig,U.log("Loaded appConfig from G7Config:",Object.keys(e.appConfig)))}}resolveRouteExpressions(e){const n=new $n,r={_global:this.globalState};return e.map(i=>{const l={...i};if(l.path&&l.path.includes("{{")){const c=l.path;l.path=n.resolveBindings(l.path,r),l.path=l.path.replace(/\/\/+/g,"/")||"/",l.path.includes("{{")&&(U.warn("Route expression resolution failed, using fallback:",c),l.path=c.replace(/\{\{[^}]+\}\}/g,"").replace(/\/\/+/g,"/")||"/")}if(l.redirect&&l.redirect.includes("{{")){const c=l.redirect;l.redirect=n.resolveBindings(l.redirect,r),l.redirect=l.redirect.replace(/\/\/+/g,"/")||"/",l.redirect.includes("{{")&&(U.warn("Route redirect expression resolution failed:",c),l.redirect=c.replace(/\{\{[^}]+\}\}/g,"").replace(/\/\/+/g,"/")||"/")}return l})}migrateLocaleStorage(){const e=["locale","g7_template_locale"];for(const n of e)try{const r=localStorage.getItem(n);r&&r!==localStorage.getItem(Lr.LOCALE_STORAGE_KEY)&&(localStorage.setItem(Lr.LOCALE_STORAGE_KEY,r),U.log(`Migrated locale from '${n}' to '${Lr.LOCALE_STORAGE_KEY}'`)),localStorage.removeItem(n)}catch(r){U.warn(`Failed to migrate locale key '${n}':`,r)}}async init(){try{po.getInstance().setDebug(this.config.debug),U.log("Initializing with config:",this.config),S0();const e=Er.getInstance(),n=Ar.getInstance(),r=typeof window<"u"&&Number(window.G7Config?.cache_version)||0,i=r>0?r:this.loadCacheVersionFromStorage()||0,[l,c,d,f,h]=await Promise.all([tl({templateId:this.config.templateId,templateType:this.config.templateType,locale:this.config.locale,debug:this.config.debug,cacheVersion:i}),e.loadComponents(this.config.templateId,this.config.templateType,i),rc(i>0?go(`templates/${this.config.templateId}/routes.json`,i):null,Ur(`/api/templates/${this.config.templateId}/routes`,"json",i>0?i:null),{label:"routes.json"}).then(A=>{if(!A.ok)throw new Error(`Failed to load routes: ${A.statusText}`);return A.json()}).then(A=>{if(!A.success)throw new Error("Failed to load routes from API");return A.data}).catch(A=>{throw U.error("Error loading routes:",A),A}),n.preloadAuth(this.config.templateType==="admin"?"admin":"user"),fetch(Ur(`/api/templates/${this.config.templateId}/config`,"json")).then(A=>A.ok?A.json():null).then(A=>!A?.success||!A?.data?null:A.data).catch(A=>(U.warn("Error loading template config:",A),null))]);if(U.log("Template Engine initialized"),U.log("ComponentRegistry loaded"),U.log("Routes data loaded"),U.log("User info preloaded"),U.log("Template config loaded:",h),h?.cache_version!==void 0){const A=i>0?i:null;if(this.extensionCacheVersion=h.cache_version,this.saveCacheVersionToStorage(this.extensionCacheVersion),U.log("Extension cache version:",this.extensionCacheVersion),A!==null&&A!==this.extensionCacheVersion){U.log("Cache version changed, reloading routes...");const D=await rc(go(`templates/${this.config.templateId}/routes.json`,this.extensionCacheVersion),Ur(`/api/templates/${this.config.templateId}/routes`,"json",this.extensionCacheVersion),{label:"routes.json (reload)"}).then(O=>{if(!O.ok)throw new Error(`Failed to reload routes: ${O.statusText}`);return O.json()}).then(O=>{if(!O.success)throw new Error("Failed to reload routes from API");return O.data});Array.isArray(D.routes)&&(D.routes=this.resolveRouteExpressions(D.routes),Object.assign(d,D),U.log("Routes reloaded with new cache version"));try{const{TranslationEngine:O}=await Promise.resolve().then(()=>Jg),k=O.getInstance();k.setCacheVersion(this.extensionCacheVersion);const L=this.config.locale||"ko",B="en";await k.loadTranslations(this.config.templateId,L,"/api",!0),L!==B&&await k.loadTranslations(this.config.templateId,B,"/api",!0),U.log("Translations reloaded with new cache version")}catch(O){U.error("Failed to reload translations:",O)}}}h?.errorHandling&&(this.templateErrorHandling=h.errorHandling,Tr().setTemplateConfig(this.templateErrorHandling),U.log("Template errorHandling registered:",this.templateErrorHandling)),n.on("logout",()=>{U.log("User logged out")}),n.on("authStateChange",A=>{U.log("Auth state changed:",A)}),U.log("AuthManager event handlers registered");const m=qr(),y=this.config.templateType==="admin"?"admin":"user",v=n.getConfig(y);m.setOnUnauthorized(()=>{U.log("Unauthorized - redirecting to login page"),m.removeToken();const A=window.location.pathname+window.location.search,D=n.getLoginRedirectUrl(y,A,"session_expired");window.location.href=D}),U.log("ApiClient onUnauthorized callback registered"),this.layoutLoader=new ah(e),this.extensionCacheVersion>0&&this.layoutLoader.setCacheVersion(this.extensionCacheVersion),U.log("LayoutLoader initialized:",this.layoutLoader);const S=new Dr({onUnauthorized:()=>{U.warn("Unauthorized request in error page")}});if(this.errorPageHandler=new Xx({templateId:this.config.templateId,layoutLoader:this.layoutLoader,locale:this.config.locale,debug:this.config.debug,renderFunction:Rs,dataSourceManager:S,globalState:this.globalState}),U.log("ErrorPageHandler initialized with DataSourceManager"),await this.handleServerError()){U.log("Server error handled, skipping normal initialization");return}if(this.router=new Wx(this.config.templateId),Array.isArray(d.routes)){const A=this.resolveRouteExpressions(d.routes);this.router.setRoutes(A)}else throw new Error("Invalid routes data format");U.log("Router initialized:",this.router),U.log("Routes set:",this.router.getRoutes());const{getActionDispatcher:_}=await Promise.resolve().then(()=>Wr),x=_();if(x&&(x.setDefaultContext({navigate:A=>this.router?.navigate(A)}),x.setGlobalStateUpdater((A,D)=>this.setGlobalState(A,D)),U.log("Navigate function and setGlobalState injected to ActionDispatcher")),await this.loadExtensionAssets(),this.reinitializeTemplateHandlers(),this.router.on("routeChange",A=>this.handleRouteChange(A)),this.router.on("routeNotFound",A=>this.handleRouteNotFound(A)),typeof window<"u"&&kb(window.location.pathname)){U.log("Layout editor mode detected — skipping router match"),await Rs({containerId:"app",layoutJson:{components:[]},dataContext:{},translationContext:{templateId:this.config.templateId,locale:this.config.locale}}),U.log("Template App initialized in layout editor mode");return}this.router.navigateToCurrentPath(),U.log("Template App initialized successfully")}catch(e){U.error("Initialization failed:",e),this.showInitError(e)}}async loadExtensionAssets(){try{const e=fh(),n=pT();if(!n){await this.loadExtensionAssetsIndividually(),await e.loadCustomAssets();return}await e.loadBundle("module",n.moduleJs,n.moduleCss),await e.loadBundle("plugin",n.pluginJs,n.pluginCss),await e.loadCustomAssets(),U.log("Extension bundle assets loaded successfully")}catch(e){U.warn("Failed to load extension assets:",e)}}async loadExtensionAssetsIndividually(){const e=fh(),n=hT();n.length>0&&(U.log("Loading module assets (individual fallback):",n.map(i=>i.identifier)),await e.loadActiveExtensionAssets(n));const r=gT();r.length>0&&(U.log("Loading plugin assets (individual fallback):",r.map(i=>i.identifier)),await e.loadActiveExtensionAssets(r))}async handleRouteChange(e){const n=++this.currentRouteChangeId;window.__g7ForcedLocalFields=void 0,window.__g7ActionContext=void 0,window.__g7PendingLocalState=void 0,window.__g7LastSetLocalSnapshot=void 0,window.__g7SetLocalOverrideKeys=void 0,window.__g7SequenceLocalSync=void 0,window.__g7AutoBindingPaths=new Map;try{if(U.log("Route changed:",e,"requestId:",n),!this.layoutLoader)throw new Error("LayoutLoader is not initialized");if(!e.layout)throw new Error("Route layout is not defined");let r=e.layout;const i=r==="__preview__";i&&e.params?.token&&(r=`__preview__/${e.params.token}`);const l=await this.layoutLoader.loadLayout(this.config.templateId,r);if(n!==this.currentRouteChangeId){U.log("Route change cancelled (newer request exists):",n);return}U.log("Layout loaded:",l);const c=Tr();l.errorHandling?(c.setLayoutConfig(l.errorHandling),U.log("Layout errorHandling registered:",l.errorHandling)):c.clearLayoutConfig();const d=l.data_sources||[],f=e.query||{},h=new URLSearchParams;for(const[ee,me]of Object.entries(f))if(Array.isArray(me))for(const H of me)h.append(ee,H);else h.set(ee,me);const m={route:e.params||{},query:f,_global:this.globalState};l.scripts&&Array.isArray(l.scripts)&&await this.loadLayoutScripts(l.scripts,m);const{DataSourceManager:y,getActionDispatcher:v}=await Promise.resolve().then(()=>Wr),S=new y;if(i){const ee=v();ee&&ee.setPreviewMode(!0),this.setGlobalState({__isPreview:!0}),Yc.show({id:"preview-mode",message:{ko:"⚠ 미리보기 모드 — 페이지 이동이 비활성화됩니다",en:"⚠ Preview Mode — Navigation is disabled"},background:"linear-gradient(90deg, #f59e0b, #d97706)",color:"white"}),U.log("Preview mode activated")}else{const ee=v();ee?.isPreviewMode()&&(ee.setPreviewMode(!1),Yc.hide("preview-mode"))}if(this.currentGlobalHeaders=l.globalHeaders||[],this.currentGlobalHeaders.length>0){S.setGlobalHeaders(this.currentGlobalHeaders);const ee=v();ee&&ee.setGlobalHeaders(this.currentGlobalHeaders),U.log("globalHeaders set:",this.currentGlobalHeaders.map(me=>me.pattern))}if(l.named_actions&&Object.keys(l.named_actions).length>0){const ee=v();ee&&ee.setNamedActions(l.named_actions)}const _=S.filterByCondition(d,m);d.length!==_.length&&U.log("Data sources filtered by condition:",{before:d.map(ee=>ee.id),after:_.map(ee=>ee.id)});const x=_.filter(ee=>ee.loading_strategy==="blocking"),A=_.filter(ee=>(ee.loading_strategy||"progressive")!=="blocking"&&ee.type!=="websocket"),D=Array.isArray(l.transition_overlay?.wait_for)?l.transition_overlay.wait_for:[],O=D.length>0&&_.some(ee=>D.includes(ee.id)&&ee.type!=="websocket"&&(ee.loading_strategy||"progressive")!=="background");if((x.length>0||O)&&l.transition_overlay){const ee=typeof l.transition_overlay=="boolean"?{enabled:l.transition_overlay,style:"opaque"}:l.transition_overlay;ee.enabled&&ee.style==="skeleton"&&ee.skeleton?.component&&ee.target?this.renderSkeletonOverlay(ee.target,ee.skeleton,l,ee.fallback_target):ee.enabled&&ee.style==="spinner"&&ee.target&&this.renderSpinnerOverlay(ee.target,ee.spinner,ee.fallback_target)}let k={},L={},B={},q={};this.currentWebSocketSubscriptions.length>0&&(U.log("Unsubscribing previous WebSocket subscriptions:",this.currentWebSocketSubscriptions),S.unsubscribeWebSockets(this.currentWebSocketSubscriptions),this.currentWebSocketSubscriptions=[]),this.currentDataSources=_,this.currentRawDataSources=d,this.currentRouteParams=e.params||{},this.currentQueryParams=h,U.log(`handleRouteChange #${n} - queryObject:`,f),U.log(`handleRouteChange #${n} - queryParams:`,h.toString()),x.length>0&&(U.log("Fetching blocking data sources:",x.map(me=>me.id)),(await S.fetchDataSourcesWithResults(x,e.params||{},h)).forEach(me=>{if(me.state==="success"&&me.data!==void 0)k[me.id]=me.data;else if(me.state==="error"&&me.error){const H=me.error,T=H.response?.data?.message;L[me.id]={message:T||me.error.message,status:H.response?.status||H.status}}}),this.processInitOptions(x,k,B,q),this.currentFetchedData={...k},U.log("Blocking data loaded:",Object.keys(k)),Object.keys(L).length>0&&U.log("Data source errors:",L),Object.keys(B).length>0&&U.log("Local state init (blocking):",Object.keys(B)),Object.keys(q).length>0&&U.log("Isolated state init (blocking):",Object.keys(q)));const z=A.length>0;if(z&&(Gr.setPending(!0),U.log("Transition started before rendering")),n!==this.currentRouteChangeId){U.log("Route change cancelled after blocking data (newer request exists):",n);return}const{getState:W}=await Promise.resolve().then(()=>Wr),de=W().currentDataContext||{},_e=A.map(ee=>ee.id),Se={};if(z){_e.forEach(me=>{de[me]!==void 0?Se[me]=de[me]:Se[me]=void 0});const ee=Object.keys(Se).filter(me=>Se[me]!==void 0);ee.length>0&&U.log("Preserving previous progressive data:",ee),U.log("Progressive data sources initialized:",_e)}const Ie=l.defines||{},Fe={...Se,...e.params,...k,route:{...e.params||{},path:e.path},query:f,_global:{...this.globalState},_globalSetState:ee=>this.setGlobalState(ee),_dataSourceErrors:Object.keys(L).length>0?L:void 0,_localInit:Object.keys(B).length>0?B:void 0,_isolatedInit:Object.keys(q).length>0?q:void 0,_defines:Object.keys(Ie).length>0?Ie:void 0};if(l.computed&&Object.keys(l.computed).length>0){const ee=this.calculateComputed(l.computed,Fe);Object.keys(ee).length>0&&(Fe._computed=ee,this.globalState._computed=ee,U.log("Computed values calculated:",Object.keys(ee))),Fe._computedDefinitions=l.computed}const ze=l.layout_name||e.layout;this.currentLayoutName!==""&&this.currentLayoutName!==ze&&(U.log("_local reset due to layout change:",{from:this.currentLayoutName,to:ze}),this.globalState._local={},u_()),this.currentLayoutName=ze;const Q=l.initLocal||l.state;if(Q&&Object.keys(Q).length>0){this.globalState._local||(this.globalState._local={});for(const[ee,me]of Object.entries(Q))this.globalState._local[ee]===void 0&&(this.globalState._local[ee]=JSON.parse(JSON.stringify(me)));Fe._local={...this.globalState._local},U.log("initLocal applied to _local:",Object.keys(Q))}if(l.initGlobal&&Object.keys(l.initGlobal).length>0){for(const[ee,me]of Object.entries(l.initGlobal))this.globalState[ee]===void 0&&(this.globalState[ee]=JSON.parse(JSON.stringify(me)));U.log("initGlobal applied to _global:",Object.keys(l.initGlobal))}if(l.initIsolated&&Object.keys(l.initIsolated).length>0){for(const[ee,me]of Object.entries(l.initIsolated))q[ee]===void 0&&(q[ee]=JSON.parse(JSON.stringify(me)));U.log("initIsolated applied:",Object.keys(l.initIsolated))}Fe._global={...this.globalState,layoutWarnings:l.warnings||[]};const ue=l.initActions||l.init_actions;if(ue&&ue.length>0){await this.executeInitActions(ue,Fe),U.log("initActions executed before render");const ee=this.globalState;if(ee._local){if(Object.keys(B).length>0){for(const[me,H]of Object.entries(B))ee._local[me]!==void 0?ee._local[me]=this.deepMerge(ee._local[me],H):ee._local[me]=H;U.log("localInit merged into _local after initActions:",Object.keys(B))}Fe._local=ee._local,U.log("_local merged into dataContext:",ee._local)}if(Fe._global={...this.globalState,layoutWarnings:l.warnings||[]},U.log("_global merged into dataContext after initActions"),l.computed&&Object.keys(l.computed).length>0){const me=this.calculateComputed(l.computed,Fe);Object.keys(me).length>0&&(Fe._computed=me,this.globalState._computed=me,U.log("Computed values recalculated after init_actions:",Object.keys(me)))}if(Object.keys(this.currentFetchedData).length>0){for(const[me,H]of Object.entries(this.currentFetchedData))Fe[me]=H;U.log("Fetched data sources merged into dataContext after initActions:",Object.keys(this.currentFetchedData))}}if(l.transition_overlay&&!this.skeletonOverlayContainer&&this.showTransitionOverlay(l.transition_overlay,l),await Rs({containerId:"app",layoutJson:l,dataContext:Fe,translationContext:{templateId:this.config.templateId,locale:this.config.locale}}),U.log("Initial render complete with blocking data"),this.reattachSpinnerOverlay(),A.length>0){if(n!==this.currentRouteChangeId){U.log("Route change cancelled before progressive fetch (newer request exists):",n),Gr.setPending(!1),this.hideTransitionOverlay();return}U.log("Fetching progressive/background data sources:",A.map(H=>H.id));const{updateTemplateData:ee,getState:me}=await Promise.resolve().then(()=>Wr);try{const H=A.map(async T=>{try{const he=(await S.fetchDataSourcesWithResults([T],e.params||{},h,this.globalState))[0];if(!he){U.log(`Data source ${T.id} skipped (no fetch result)`);return}if(n!==this.currentRouteChangeId){U.log(`Data source ${T.id} fetch cancelled (newer request exists)`);return}if(he.state==="success"&&he.data!==void 0){U.log(`Progressive data source loaded: ${T.id}`),this.currentFetchedData[T.id]=he.data;const Ae={};this.processInitOptions([T],{[T.id]:he.data},Ae);const se=me();if(se.bindingEngine){const $e=[T.id];T.initGlobal&&$e.push("_global"),T.initLocal&&$e.push("_local"),se.bindingEngine.invalidateCacheByKeys($e)}const ge={[T.id]:he.data};Object.keys(Ae).length>0&&(ge._localInit=Ae,Object.assign(B,Ae)),T.initGlobal&&(ge._global={...this.globalState}),ee(ge)}else if(he.state==="error"&&he.error){const Ae=he.error,se=Ae.response?.data?.message;L[T.id]={message:se||he.error.message,status:Ae.response?.status||Ae.status},U.log(`Progressive data source error: ${T.id}`,L[T.id]),ee({[T.id]:null,_dataSourceErrors:{...L}})}}catch(fe){U.error(`Failed to fetch data source: ${T.id}`,fe)}});if(await Promise.all(H),n!==this.currentRouteChangeId){U.log("Route change cancelled after progressive fetch (newer request exists):",n);return}U.log("All progressive data sources loaded")}finally{Gr.setPending(!1),this.hideTransitionOverlay()}}else this.hideTransitionOverlay();const De=_.filter(ee=>ee.type==="websocket");if(De.length>0){U.log("Subscribing WebSocket data sources:",De.map(T=>T.id));const{updateTemplateData:ee,getState:me}=await Promise.resolve().then(()=>Wr),H={...this.currentFetchedData,...e.params,route:{...e.params||{},path:e.path},query:f,_global:{...this.globalState}};U.log("WebSocket binding context keys:",Object.keys(H)),this.currentWebSocketSubscriptions=S.subscribeWebSockets(_,(T,fe)=>{U.log(`WebSocket data received for: ${T}`,fe),this.currentFetchedData[T]=fe;const he=_.find($e=>$e.id===T),Ae=me();if(Ae.bindingEngine){const $e=[T];he?.initGlobal&&$e.push("_global"),he?.initLocal&&$e.push("_local"),Ae.bindingEngine.invalidateCacheByKeys($e)}ee({[T]:fe});const ge=_.find($e=>$e.type==="websocket"&&($e.target_source||$e.id)===T)?.onReceive;Array.isArray(ge)&&ge.length>0&&window.G7Core?.dispatch&&(async()=>{for(const Te of ge)try{const je=this.getActionDispatcher?.();je&&await je.dispatchAction(Te,{navigate:this.getRouter?.()?(xt,rt)=>this.getRouter().navigate(xt,rt):void 0,setState:xt=>this.setGlobalState(xt),state:this.globalState,data:{...this.globalState,$args:[fe],$event:fe},_isDispatchFallbackContext:!0})}catch(je){U.error(`WebSocket onReceive action failed for ${T}:`,je)}})()},H),U.log("WebSocket subscriptions established:",this.currentWebSocketSubscriptions)}U.log("Layout rendered successfully")}catch(r){U.error("Route change handling failed:",r),this.hideTransitionOverlay(),this.showRouteError(r)}}processInitOptions(e,n,r,i){e.some(c=>c.refetchOnMount===!0&&c.initLocal)&&(r._forceLocalInit=Date.now(),U.log("refetchOnMount detected, forcing local state init")),e.forEach(c=>{const d=n[c.id];if(!d)return;const f=d.data??d;if(c.initLocal){if(typeof c.initLocal=="string"){let h=f;if(c.initLocalDefaults&&typeof c.initLocalDefaults=="object"){const y=this.evaluateDefaults(c.initLocalDefaults,n);h={...y,...f},U.log(`initLocalDefaults applied for ${c.initLocal}:`,Object.keys(y))}const m=this.globalState._local?.[c.initLocal];m!==void 0&&typeof m=="object"&&typeof h=="object"?(r[c.initLocal]=this.deepMerge(m,h),U.log(`initLocal (merged): ${c.id}.data -> _local.${c.initLocal}`)):(r[c.initLocal]=h,U.log(`initLocal: ${c.id}.data -> _local.${c.initLocal}`))}else if(typeof c.initLocal=="object"&&c.initLocal.key){const{key:h,path:m}=c.initLocal;let y=m?this.extractValueByPathOrExpression(f,m,c.id):f;if(c.initLocalDefaults&&typeof c.initLocalDefaults=="object"){const S=this.evaluateDefaults(c.initLocalDefaults,n);y={...S,...y},U.log(`initLocalDefaults applied for ${h}:`,Object.keys(S))}const v=this.globalState._local?.[h];v!==void 0&&typeof v=="object"&&typeof y=="object"?(r[h]=this.deepMerge(v,y),U.log(`initLocal (merged): ${c.id}.data${m?"."+m:""} -> _local.${h}`)):(r[h]=y,U.log(`initLocal: ${c.id}.data${m?"."+m:""} -> _local.${h}`))}else if(typeof c.initLocal=="object"){const h=c.initLocal._merge||"deep",m=this.flattenNestedObjectToMappings(c.initLocal);for(const{targetPath:y,sourcePath:v}of m){const S=this.extractValueByPathOrExpression(f,v,c.id);if(y.includes(".")){const _=y.split(".")[0];r[_]===void 0&&this.globalState._local?.[_]!==void 0&&(r[_]=JSON.parse(JSON.stringify(this.globalState._local[_]))),this.setValueAtPath(r,y,S,h),U.log(`initLocal map (${h}): ${c.id} ${v} -> _local.${y}`)}else{const _=this.globalState._local?.[y];h==="replace"?(r[y]=S,U.log(`initLocal map (replace): ${c.id} ${v} -> _local.${y}`)):h==="shallow"?_!==void 0&&typeof _=="object"&&typeof S=="object"?(r[y]={..._,...S},U.log(`initLocal map (shallow): ${c.id} ${v} -> _local.${y}`)):(r[y]=S,U.log(`initLocal map: ${c.id} ${v} -> _local.${y}`)):_!==void 0&&typeof _=="object"&&typeof S=="object"?(r[y]=this.deepMerge(_,S),U.log(`initLocal map (deep): ${c.id} ${v} -> _local.${y}`)):(r[y]=S,U.log(`initLocal map: ${c.id} ${v} -> _local.${y}`))}}}}if(c.initGlobal)if(typeof c.initGlobal=="object"&&!Array.isArray(c.initGlobal)&&!("key"in c.initGlobal))for(const[m,y]of Object.entries(c.initGlobal)){if(typeof y!="string"){U.warn(`initGlobal map value must be string, got ${typeof y} for key ${m}`);continue}const v=this.extractValueByPathOrExpression(f,y,c.id),S=this.globalState[m];S!==void 0&&typeof S=="object"&&typeof v=="object"?(this.globalState[m]=this.deepMerge(S,v),U.log(`initGlobal map (merged): ${c.id} ${y} -> _global.${m}`)):(this.globalState[m]=v,U.log(`initGlobal map: ${c.id} ${y} -> _global.${m}`))}else{const m=Array.isArray(c.initGlobal)?c.initGlobal:[c.initGlobal];for(const y of m)if(typeof y=="string"){const v=this.globalState[y];v!==void 0&&typeof v=="object"&&typeof f=="object"?(this.globalState[y]=this.deepMerge(v,f),U.log(`initGlobal (merged): ${c.id}.data -> _global.${y}`)):(this.globalState[y]=f,U.log(`initGlobal: ${c.id}.data -> _global.${y}`))}else if(typeof y=="object"&&y.key){const{key:v,path:S}=y,_=S?this.extractValueByPathOrExpression(f,S,c.id):f,x=this.globalState[v];x!==void 0&&typeof x=="object"&&typeof _=="object"?(this.globalState[v]=this.deepMerge(x,_),U.log(`initGlobal (merged): ${c.id}.data${S?"."+S:""} -> _global.${v}`)):(this.globalState[v]=_,U.log(`initGlobal: ${c.id}.data${S?"."+S:""} -> _global.${v}`))}}if(i&&c.initIsolated){if(typeof c.initIsolated=="string"){const h=i[c.initIsolated];h!==void 0&&typeof h=="object"&&typeof f=="object"?(i[c.initIsolated]=this.deepMerge(h,f),U.log(`initIsolated (merged): ${c.id}.data -> _isolated.${c.initIsolated}`)):(i[c.initIsolated]=f,U.log(`initIsolated: ${c.id}.data -> _isolated.${c.initIsolated}`))}else if(typeof c.initIsolated=="object"&&c.initIsolated.key){const{key:h,path:m}=c.initIsolated,y=m?this.extractValueByPathOrExpression(f,m,c.id):f,v=i[h];v!==void 0&&typeof v=="object"&&typeof y=="object"?(i[h]=this.deepMerge(v,y),U.log(`initIsolated (merged): ${c.id}.data${m?"."+m:""} -> _isolated.${h}`)):(i[h]=y,U.log(`initIsolated: ${c.id}.data${m?"."+m:""} -> _isolated.${h}`))}else if(typeof c.initIsolated=="object")for(const[h,m]of Object.entries(c.initIsolated)){if(typeof m!="string"){U.warn(`initIsolated map value must be string, got ${typeof m} for key ${h}`);continue}const y=this.extractValueByPathOrExpression(f,m,c.id),v=i[h];v!==void 0&&typeof v=="object"&&typeof y=="object"?(i[h]=this.deepMerge(v,y),U.log(`initIsolated map (merged): ${c.id} ${m} -> _isolated.${h}`)):(i[h]=y,U.log(`initIsolated map: ${c.id} ${m} -> _isolated.${h}`))}}})}evaluateDefaults(e,n){const r={};for(const[i,l]of Object.entries(e))if(typeof l=="string"&&l.startsWith("{{")&&l.endsWith("}}")){const c=l.slice(2,-2).trim();r[i]=this.evaluateExpression(c,n)}else r[i]=l;return r}evaluateExpression(e,n){try{const r=e.split("??").map(i=>i.trim());for(const i of r){if(/^['"].*['"]$/.test(i))return i.slice(1,-1);if(i==="true")return!0;if(i==="false")return!1;if(/^-?\d+(\.\d+)?$/.test(i))return Number(i);const l=this.getNestedValue(n,i);if(l!=null)return l}return}catch(r){U.warn(`Expression evaluation failed: ${e}`,r);return}}getNestedValue(e,n){const r=n.replace(/\?\./g,".");let i=e;const l=r.split(/\.(?![^\[]*\])/).flatMap(c=>{const d=c.match(/^([^\[]*)((?:\[\d+\])*)$/);if(d){const[,f,h]=d,m=[];f&&m.push(f);const y=h.match(/\[\d+\]/g);return y&&m.push(...y),m}return[c]}).filter(c=>c!=="");for(const c of l){if(i==null)return;if(c.startsWith("[")&&c.endsWith("]")){const d=parseInt(c.slice(1,-1),10);i=i[d]}else i=i[c]}return i}extractValueByPathOrExpression(e,n,r){const i=La(n);if(i!==null){const l=new $n,c={data:e,_global:this.globalState,_local:this.globalState._local||{}};try{const d=Wn(i)?l.evaluatePipeExpression(i,c,{skipCache:!0}):l.evaluateExpression(i,c);return U.log(`initLocal/initGlobal expression evaluated: ${n} -> `,d),d}catch(d){U.warn(`initLocal/initGlobal expression evaluation failed for ${r}:`,n,d);return}}return this.getNestedValue(e,n)}async loadLayoutScripts(e,n){const r=[];for(const i of e){if((i.if!==void 0||i.conditions!==void 0)&&!fs({if:i.if,conditions:i.conditions},n,this.bindingEngine,`script:${i.id}`)){U.log(`Script skipped (condition not met): ${i.id}`);continue}if(document.getElementById(i.id)){U.log(`Script already loaded: ${i.id}`);continue}if(!this.isAllowedScriptSrc(i.src)){U.warn(`Blocked untrusted external script src (same-origin path or declared trusted host required): ${i.id} (${i.src})`);continue}r.push(this.loadLayoutScript(i))}r.length>0&&(await Promise.all(r),U.log(`All scripts loaded: ${e.filter(i=>!document.getElementById(i.id)||r.length>0).map(i=>i.id).join(", ")}`))}async loadLayoutScript(e){const n=e.src.startsWith("/")?Ln(e.src):e.src;try{await Cr(n,{id:e.id},{label:`layout-script:${e.id}`}),this.failedLayoutScripts.delete(e.id),Db(`layout-script:${e.id}`),U.log(`Script loaded successfully: ${e.id}`)}catch(r){this.failedLayoutScripts.add(e.id),U.warn(`Failed to load script after retries: ${e.id} (${n})`,r),xs({id:`layout-script:${e.id}`,label:e.id,retry:async()=>{document.getElementById(e.id)?.remove(),await Cr(n,{id:e.id},{label:`layout-script:${e.id}`}),this.failedLayoutScripts.delete(e.id)}})}}getFailedLayoutScripts(){return Array.from(this.failedLayoutScripts)}isAllowedScriptSrc(e){return Io(e)}static normalizeScriptSrcForOriginCheck(e){return by(e)}extractScriptHost(e){return vy(e)}getTrustedScriptHosts(){return bs()}evaluateScriptCondition(e,n){try{if(e.startsWith("{{")&&e.endsWith("}}")){const r=e.slice(2,-2).trim();return!!af(r,n)}return!!e}catch(r){return U.warn(`Failed to evaluate script condition: ${e}`,r),!1}}showInitError(e){if(tc()){U.warn("Init failed while document is unloading — skipping error screen",e);return}const n=Tb(e);uh.renderFromError("app",n,"초기화 실패",this.config.debug,{templateId:this.config.templateId,locale:this.config.locale},void 0,e)}showTransitionOverlay(e,n){const r=typeof e=="boolean"?{enabled:e,style:"opaque",target:void 0,skeleton:void 0,spinner:void 0}:{enabled:e.enabled,style:e.style||"opaque",target:e.target,skeleton:e.skeleton,spinner:e.spinner,fallback_target:e.fallback_target};if(!r.enabled)return;if(this.hideTransitionOverlay(),r.style==="skeleton"&&r.skeleton?.component&&r.target&&n){this.renderSkeletonOverlay(r.target,r.skeleton,n,r.fallback_target);return}if(r.style==="spinner"&&r.target){this.renderSpinnerOverlay(r.target,r.spinner,r.fallback_target);return}const i=document.documentElement.classList.contains("dark");let l,c="";switch(r.style){case"blur":l=i?"rgba(17,24,39,0.3)":"rgba(255,255,255,0.3)",c="backdrop-filter:blur(4px);-webkit-backdrop-filter:blur(4px);";break;case"fade":l=i?"rgba(17,24,39,0.8)":"rgba(255,255,255,0.8)";break;case"skeleton":l=i?"rgb(17,24,39)":"rgb(249,250,251)";break;default:l=i?"rgb(17,24,39)":"rgb(249,250,251)";break}if(r.target){const d=`#${CSS.escape(r.target)}`,f=document.createElement("style");f.id="g7-transition-overlay",f.textContent=`${d}{position:relative;z-index:0;}${d}::after{content:'';position:absolute;inset:0;background:${l};${c}z-index:2147483647;pointer-events:none;}`,document.head.appendChild(f),this.transitionOverlayEl=f}else{const d=document.createElement("div");d.id="g7-transition-overlay",d.setAttribute("aria-hidden","true"),d.style.position="fixed",d.style.inset="0",d.style.zIndex="9999",d.style.pointerEvents="none",d.style.background=l;for(const f of c.split(";")){const h=f.indexOf(":");h!==-1&&d.style.setProperty(f.slice(0,h).trim(),f.slice(h+1).trim())}document.body.appendChild(d),this.transitionOverlayEl=d}}hideTransitionOverlay(){this.transitionOverlayEl&&(this.transitionOverlayEl.remove(),this.transitionOverlayEl=null),this.hideSkeletonOverlay()}renderSkeletonOverlay(e,n,r,i){const c=Er.getInstance().getComponent(n.component);if(!c){U.log(`Skeleton component "${n.component}" not found in registry, falling back to opaque overlay`),this.showTransitionOverlay({enabled:!0,style:"opaque",target:e});return}let d=document.getElementById(e),f="target";if(!d&&i&&(d=document.getElementById(i),f="fallback"),d||(d=document.getElementById("app"),f="fullpage"),!d){U.log(`Skeleton overlay: no target found (target="#${e}", fallback="${i||"none"}"), falling back to opaque overlay`),this.showTransitionOverlay({enabled:!0,style:"opaque",target:e});return}this.hideSkeletonOverlay();const m=document.documentElement.classList.contains("dark")?"rgb(17,24,39)":"rgb(249,250,251)",y=f==="fullpage"?"app":f==="fallback"?i:e,v=`#${CSS.escape(y)}`,S=document.createElement("style");S.id="g7-skeleton-overlay-style",S.textContent=`${v}{position:relative;z-index:0;}${v}::after{content:'';position:absolute;inset:0;background:${m};z-index:2147483646;pointer-events:none;}`,document.head.appendChild(S),this.transitionOverlayEl=S;const _=document.createElement("div");if(_.id="g7-skeleton-overlay",_.setAttribute("role","status"),_.setAttribute("aria-busy","true"),_.setAttribute("aria-label","Loading..."),f==="fullpage")_.style.cssText=["position:fixed","inset:0","z-index:20","overflow:hidden","pointer-events:none",`background:${m}`].join(";")+";";else{const k=d.getBoundingClientRect(),L=window.scrollX||document.documentElement.scrollLeft,B=window.scrollY||document.documentElement.scrollTop;_.style.cssText=["position:absolute",`top:${k.top+B}px`,`left:${k.left+L}px`,`width:${k.width}px`,`height:${k.height}px`,"z-index:20","overflow:hidden","pointer-events:none",`background:${m}`].join(";")+";"}document.body.appendChild(_),this.skeletonOverlayContainer=_;const x=r.components||[];let A;if(f==="fullpage")A=x;else{const k=f==="fallback"?i:e;A=this.findComponentChildrenById(x,k)}const D=Od.createRoot(_);this.skeletonOverlayRoot=D,ja.flushSync(()=>{D.render(Xe.createElement(c,{components:A,options:{animation:n.animation||"pulse",iteration_count:n.iteration_count||5}}))});const O=f==="fullpage"?"fullpage (#app)":f==="fallback"?`fallback (#${i})`:`target (#${e})`;U.log(`Skeleton overlay rendered [${O}] with "${n.component}" (${A.length} components)`)}findComponentChildrenById(e,n){const r=i=>{for(const l of i){if(l.id===n)return l.children||[];if(l.children&&Array.isArray(l.children)){const c=r(l.children);if(c!==null)return c}}return null};return r(e)||e}renderSpinnerOverlay(e,n,r){let i=document.getElementById(e),l="target";if(!i&&r&&(i=document.getElementById(r),l="fallback"),i||(i=document.getElementById("app"),l="fullpage"),!i){U.log(`Spinner overlay: no target found (target="#${e}", fallback="${r||"none"}"), falling back to opaque overlay`),this.showTransitionOverlay({enabled:!0,style:"opaque",target:e});return}this.hideSkeletonOverlay();const c=l==="fullpage"?"app":l==="fallback"?r:e,d=`#${CSS.escape(c)}`,f=document.createElement("style");f.id="g7-skeleton-overlay-style",f.textContent=[`${d}{position:relative;}`,"@keyframes g7-spin{to{transform:rotate(360deg)}}"].join(""),document.head.appendChild(f),this.transitionOverlayEl=f;const h=n?.text||window.G7Core?.t?.("nav.loading")||"";this._spinnerState={target:e,fallbackTarget:r,spinnerConfig:n,resolvedText:h},this._mountSpinnerInTarget(i);const m=l==="fullpage"?"fullpage (#app)":l==="fallback"?`fallback (#${r})`:`target (#${e})`,y=n?.component||"default spinner";U.log(`Spinner overlay rendered [${m}] with "${y}"`)}_mountSpinnerInTarget(e){if(!this._spinnerState)return;const{spinnerConfig:n,resolvedText:r}=this._spinnerState;if(this.skeletonOverlayRoot){try{this.skeletonOverlayRoot.unmount()}catch{}this.skeletonOverlayRoot=null}if(this.skeletonOverlayContainer){try{this.skeletonOverlayContainer.remove()}catch{}this.skeletonOverlayContainer=null}const i=document.createElement("div");if(i.id="g7-skeleton-overlay",i.setAttribute("role","status"),i.setAttribute("aria-busy","true"),e.appendChild(i),this.skeletonOverlayContainer=i,n?.component){const c=Er.getInstance().getComponent(n.component);if(c){const d=Od.createRoot(i);this.skeletonOverlayRoot=d,ja.flushSync(()=>{d.render(Xe.createElement(c,{options:{text:r}}))});return}U.log(`Spinner component "${n.component}" not found in registry, using default spinner`)}i.innerHTML='
'}reattachSpinnerOverlay(){if(!this._spinnerState)return;const{target:e,fallbackTarget:n}=this._spinnerState;let r=document.getElementById(e);!r&&n&&(r=document.getElementById(n)),r||(r=document.getElementById("app")),r&&(this._mountSpinnerInTarget(r),U.log("Spinner overlay reattached to new DOM target"))}hideSkeletonOverlay(){if(this.skeletonOverlayRoot){try{this.skeletonOverlayRoot.unmount()}catch{}this.skeletonOverlayRoot=null}if(this.skeletonOverlayContainer){try{this.skeletonOverlayContainer.parentNode&&this.skeletonOverlayContainer.remove()}catch{}this.skeletonOverlayContainer=null}const e=document.getElementById("g7-skeleton-overlay-style");e&&e.remove(),this._spinnerState=null}showRouteError(e){if(e instanceof Rr&&e.details?.status===401){const r=this.config.templateId||"",i=window.location.pathname,l=r.includes("admin")||i.startsWith("/admin")?"admin":"user",c=i+window.location.search,d=!!qr().getToken()||e.details?.hadToken===!0,f=Ar.getInstance().getLoginRedirectUrl(l,c,d?"session_expired":void 0);window.location.href=f;return}const n=Tb(e);uh.renderFromError("app",n,"페이지 로딩 실패",this.config.debug,{templateId:this.config.templateId,locale:this.config.locale},void 0,e)}async handleServerError(){const e=window.G7Error;if(!e)return!1;U.warn("Server error detected:",e);try{return this.errorPageHandler&&(e.data&&(this.globalState.errorData=e.data),this.errorPageHandler.updateGlobalState(this.globalState),this.errorPageHandler.updateLocale(this.config.locale),await this.errorPageHandler.renderError(e.code,"app"))?(U.log(`${e.code} error page rendered successfully`),!0):(U.log(`Falling back to ErrorDisplay for ${e.code}`),this.showInitError(new Error(`Service unavailable (${e.code})`)),!0)}catch(n){return U.error(`Failed to render ${e.code} page:`,n),this.showInitError(new Error(`Service unavailable (${e.code})`)),!0}}async handleRouteNotFound(e){U.warn("Route not found:",e);try{if(this.errorPageHandler&&(this.errorPageHandler.updateGlobalState(this.globalState),this.errorPageHandler.updateLocale(this.config.locale),await this.errorPageHandler.renderError(404,"app"))){U.log("404 error page rendered successfully");return}U.log("Falling back to ErrorDisplay for 404"),this.showRouteError(new Error(`Page not found: ${e}`))}catch(n){U.error("Failed to render 404 page:",n),this.showRouteError(new Error(`Page not found: ${e}`))}}getRouter(){return this.router}getConfig(){return this.config}getLayoutLoader(){return this.layoutLoader}async reloadExtensionState(){U.log("reloadExtensionState: start");let e;try{const n=await fetch(Ur(`/api/templates/${this.config.templateId}/config`,"json",null,`_=${Date.now()}`));if(n.ok){const r=await n.json();r?.success&&r?.data?.cache_version!==void 0&&(e=r.data.cache_version)}}catch(n){U.warn("reloadExtensionState: failed to fetch config.json",n)}e!==void 0&&e!==this.extensionCacheVersion&&(U.log(`reloadExtensionState: cache version ${this.extensionCacheVersion} -> ${e}`),this.extensionCacheVersion=e,this.saveCacheVersionToStorage(e));try{this.router&&(await this.router.loadRoutes(this.extensionCacheVersion),U.log("reloadExtensionState: routes reloaded"))}catch(n){U.error("reloadExtensionState: routes reload failed",n)}try{this.layoutLoader&&(this.extensionCacheVersion>0&&this.layoutLoader.setCacheVersion(this.extensionCacheVersion),this.layoutLoader.clear(),U.log("reloadExtensionState: layout cache cleared"))}catch(n){U.error("reloadExtensionState: layout cache clear failed",n)}try{const{TranslationEngine:n}=await Promise.resolve().then(()=>Jg),r=n.getInstance();this.extensionCacheVersion>0&&r.setCacheVersion(this.extensionCacheVersion);const i=this.config.locale||"ko",l="en";await r.loadTranslations(this.config.templateId,i,"/api",!0),i!==l&&await r.loadTranslations(this.config.templateId,l,"/api",!0),U.log("reloadExtensionState: translations reloaded")}catch(n){U.error("reloadExtensionState: translations reload failed",n)}try{const n=await fetch(`/api/locales/active?_=${Date.now()}`);if(n.ok){const i=(await n.json())?.data?.locales;if(Array.isArray(i)&&i.length>0){const l=this.globalState.appConfig??{};this.setGlobalState({appConfig:{...l,supportedLocales:i}}),U.log("reloadExtensionState: supportedLocales refreshed",i)}}}catch(n){U.warn("reloadExtensionState: supportedLocales refresh failed",n)}U.log("reloadExtensionState: done")}getActionDispatcher(){return la().actionDispatcher}async changeLocale(e){try{if(U.log("Changing locale to:",e),this.config.locale===e){U.log("Locale is already",e);return}this.config.locale=e,this.saveLocaleToStorage(e),await this.saveLocaleToDatabase(e),this.layoutLoader&&(this.layoutLoader.clear(),U.log("Layout cache cleared")),this.errorPageHandler&&(this.errorPageHandler.updateLocale(e),U.log("ErrorPageHandler locale updated")),Xc(),await tl({templateId:this.config.templateId,templateType:this.config.templateType,locale:e,debug:this.config.debug,cacheVersion:this.extensionCacheVersion}),U.log("Template Engine re-initialized with new locale");const{getActionDispatcher:n}=await Promise.resolve().then(()=>Wr),r=n();r&&(r.setDefaultContext({navigate:i=>this.router?.navigate(i)}),r.setGlobalStateUpdater((i,l)=>this.setGlobalState(i,l)),U.log("Navigate function and setGlobalState re-injected to ActionDispatcher")),this.reinitializeModuleHandlers(),U.log("Module handlers re-initialized"),this.reinitializeTemplateHandlers(),U.log("Template handlers re-initialized"),this.reinitializePluginHandlers(),U.log("Plugin handlers re-initialized"),this.router&&this.router.navigateToCurrentPath(),U.log("Locale changed successfully to",e)}catch(n){throw U.error("Failed to change locale:",n),n}}loadLocaleFromStorage(){try{return localStorage.getItem(Lr.LOCALE_STORAGE_KEY)}catch(e){return U.warn("Failed to load locale from storage:",e),null}}saveLocaleToStorage(e){try{localStorage.setItem(Lr.LOCALE_STORAGE_KEY,e)}catch(n){U.warn("Failed to save locale to storage:",n)}}async saveLocaleToDatabase(e){const n=localStorage.getItem("auth_token");if(!n){U.log("No auth token, skipping DB locale save");return}let r,i;if(this.config.localeApi)r=this.config.localeApi.endpoint,i=this.config.localeApi.method;else{const l=this.config.templateType==="admin";r=l?"/api/admin/users/me/language":"/api/user/profile/update-language",i=l?"PATCH":"POST"}try{const l=this.getXsrfToken(),c=await fetch(r,{method:i,headers:{"Content-Type":"application/json",Accept:"application/json",...l&&{"X-XSRF-TOKEN":l},Authorization:`Bearer ${n}`},credentials:"include",body:JSON.stringify({language:e})});c.ok?U.log("Locale saved to DB:",e):U.warn("Failed to save locale to DB (UI will still change):",c.statusText)}catch(l){U.warn("Failed to call locale API (UI will still change):",l)}}getXsrfToken(){if(typeof document>"u")return null;const n=`; ${document.cookie}`.split("; XSRF-TOKEN=");return n.length===2?decodeURIComponent(n.pop()?.split(";").shift()||""):null}loadCacheVersionFromStorage(){try{const e=localStorage.getItem(Lr.CACHE_VERSION_STORAGE_KEY);return e?parseInt(e,10):null}catch(e){return U.warn("Failed to load cache version from storage:",e),null}}saveCacheVersionToStorage(e){try{localStorage.setItem(Lr.CACHE_VERSION_STORAGE_KEY,String(e))}catch(n){U.warn("Failed to save cache version to storage:",n)}}reinitializeModuleHandlers(){if(typeof window>"u")return;const e="__",n=window;Object.keys(n).forEach(r=>{if(r.startsWith(e)&&typeof n[r]?.initModule=="function")try{n[r].initModule(),U.log(`Module handler re-initialized: ${r}`)}catch(i){U.warn(`Failed to re-initialize module handlers for ${r}:`,i)}})}reinitializeTemplateHandlers(){if(typeof window>"u")return;const e=window.G7TemplateHandlers;if(!e){U.warn("Template handlers not found on window.G7TemplateHandlers");return}const n=this.getActionDispatcher();if(!n){U.warn("ActionDispatcher not available for template handler registration");return}Object.entries(e).forEach(([r,i])=>{n.registerHandler(r,i)}),U.log(`${Object.keys(e).length} template handler(s) re-registered:`,Object.keys(e))}reinitializePluginHandlers(){if(typeof window>"u")return;const e="__",n=window;Object.keys(n).forEach(r=>{if(r.startsWith(e)&&typeof n[r]?.initPlugin=="function")try{n[r].initPlugin(),U.log(`Plugin handler re-initialized: ${r}`)}catch(i){U.warn(`Failed to re-initialize plugin handlers for ${r}:`,i)}})}getLocale(){return this.config.locale}getErrorPageHandler(){return this.errorPageHandler}getGlobalState(){return{...this.globalState}}setGlobalState(e,n){const r={...this.globalState};typeof e=="function"?this.globalState=e(this.globalState):this.globalState={...this.globalState,...e},U.log("Global state updated:",this.globalState),window.G7Core?.devTools?.captureStateSnapshot?.({source:"setGlobalState",prev:r,next:this.globalState}),this.globalStateListeners.forEach(l=>{l(this.globalState)}),n?.render!==!1&&Promise.resolve().then(()=>Wr).then(({updateTemplateData:l,getState:c})=>{const d=c();d.reactRoot&&d.currentLayoutJson&&l({_global:{...this.globalState},_local:this.globalState._local||{}})})}onGlobalStateChange(e){this.globalStateListeners.add(e)}offGlobalStateChange(e){this.globalStateListeners.delete(e)}async refetchDataSource(e,n){let r=this.currentDataSources.find(i=>i.id===e);if(!r)for(const[,i]of this.modalDataSources){const l=i.find(c=>c.id===e);if(l){r=l;break}}if(!r){U.warn(`Data source not found: ${e}`);return}U.log(`Refetching data source: ${e}`,n?.sync?"(sync mode)":"",n?.globalStateOverride?"(with global override)":"",n?.localStateOverride?"(with local override)":""),Gr.setPending(!0);try{const i=new Dr;this.currentGlobalHeaders.length>0&&i.setGlobalHeaders(this.currentGlobalHeaders);const l=la(),c=l.currentDataContext?._global||{},d=n?.globalStateOverride?{...c,...n.globalStateOverride}:c,f=l.currentDataContext?._local||{},h=n?.localStateOverride?{...f,...n.localStateOverride}:f,y=(await i.fetchDataSourcesWithResults([r],this.currentRouteParams,this.currentQueryParams,d,h,{ignoreAutoFetch:!0}))[0];if(y.state==="success"&&y.data!==void 0){this.currentFetchedData[e]=y.data;const v={};this.processInitOptions([r],{[e]:y.data},v);const S=la();if(S.bindingEngine){const x=[e];r.initGlobal&&x.push("_global"),r.initLocal&&x.push("_local"),S.bindingEngine.invalidateCacheByKeys(x)}const _={[e]:y.data};return Object.keys(v).length>0&&(_._localInit=v),r.initGlobal&&(_._global={...this.globalState}),Ds(_,n?.sync?{sync:!0}:void 0),U.log(`Data source refetched successfully: ${e}`),y.data}else if(y.state==="error"){U.error(`Failed to refetch data source: ${e}`,y.error);return}}catch(i){U.error(`Error refetching data source: ${e}`,i);return}finally{Gr.setPending(!1)}}registerModalDataSources(e,n){this.modalDataSources.set(e,n),U.log(`Modal data sources registered: ${e} (${n.length} sources)`)}unregisterModalDataSources(e){this.modalDataSources.delete(e),U.log(`Modal data sources unregistered: ${e}`)}getDataSource(e){return this.currentFetchedData[e]}setDataSource(e,n,r){if(!e){U.warn("setDataSource: dataSourceId is required");return}const{merge:i=!1,sync:l=!1}=r||{};i&&this.currentFetchedData[e]?this.currentFetchedData[e]={...this.currentFetchedData[e],...n}:this.currentFetchedData[e]=n,Promise.resolve().then(()=>Wr).then(({updateTemplateData:c})=>{c({[e]:this.currentFetchedData[e]},l?{sync:!0}:void 0)}),U.log(`setDataSource: Updated ${e}`,i?"(merged)":"(replaced)")}updateDataSourceItem(e,n,r,i,l){const{idField:c="id",merge:d=!0,skipRender:f=!1}=l||{},h=this.currentFetchedData[e];if(!h)return U.warn(`updateDataSourceItem: DataSource '${e}' not found`),!1;const m=this.parseItemPath(n);let y=h;for(const _ of m)if(y=y?.[_],y===void 0)return U.warn(`updateDataSourceItem: Path '${n}' not found in dataSource`),!1;if(!Array.isArray(y))return U.warn(`updateDataSourceItem: Target at '${n}' is not an array`),!1;const v=y.findIndex(_=>String(_[c])===String(r));if(v===-1)return U.warn(`updateDataSourceItem: Item with ${c}='${r}' not found`),!1;d?y[v]=this.deepMerge(y[v],i):y[v]={...y[v],...i};const S=window.G7Core?.devTools;return S?.isEnabled?.()&&S.trackDataSourceUpdate?.({dataSourceId:e,updateType:"partial",itemPath:n,itemId:r,updates:i,timestamp:Date.now()}),f||Promise.resolve().then(()=>Wr).then(({updateTemplateData:_})=>{_({[e]:this.currentFetchedData[e]},{sync:!1})}),U.log(`updateDataSourceItem: Updated ${e}.${n}[${c}=${r}]`),!0}parseItemPath(e){const n=[],r=/([^\.\[\]]+)|\[(\d+)\]/g;let i;for(;(i=r.exec(e))!==null;)i[1]!==void 0?n.push(i[1]):i[2]!==void 0&&n.push(parseInt(i[2],10));return n}deepMerge(e,n){if(n==null)return e;if(typeof n!="object"||Array.isArray(n))return n;const r={...e};for(const i of Object.keys(n))typeof n[i]=="object"&&n[i]!==null&&!Array.isArray(n[i])?r[i]=this.deepMerge(r[i]||{},n[i]):r[i]=n[i];return r}setValueAtPath(e,n,r,i="deep"){const l=n.split(".");let c=e;for(let h=0;h0?l.filterByCondition(this.currentRawDataSources,d):this.currentDataSources;this.currentDataSources=f;const h=f.filter(x=>x.auto_fetch!==!1&&x.type!=="websocket");if(h.length===0){U.log("No auto_fetch data sources to refetch");return}U.log(`Refetching ${h.length} auto_fetch data sources`),Gr.setPending(!0);const m=la().currentLayoutJson,y=m?.transition_overlay,v=Array.isArray(y?.wait_for)?y.wait_for:[],S=h.some(x=>(x.loading_strategy||"progressive")==="blocking"),_=v.length>0&&h.some(x=>v.includes(x.id)&&x.type!=="websocket"&&(x.loading_strategy||"progressive")!=="background");if((S||_)&&y&&typeof y=="object"){const x=n?.transitionOverlayTarget||y.target;y.enabled&&y.style==="skeleton"&&y.skeleton?.component&&x?this.renderSkeletonOverlay(x,y.skeleton,m,y.fallback_target):y.enabled&&y.style==="spinner"&&x&&this.renderSpinnerOverlay(x,y.spinner,y.fallback_target)}try{const x=new Dr;this.currentGlobalHeaders.length>0&&x.setGlobalHeaders(this.currentGlobalHeaders);const A=la(),D=A.currentDataContext?._global||{},O=await x.fetchDataSourcesWithResults(h,this.currentRouteParams,this.currentQueryParams,D,void 0,{ignoreAutoFetch:!1}),k={},L={},B=new Map(h.map(q=>[q.id,q]));for(const q of O){const z=B.get(q.id);if(!z){U.warn(`Refetch result id not found in autoFetchDataSources: ${q.id}`);continue}const W=q.id;q.state==="success"&&q.data!==void 0?(this.currentFetchedData[W]=q.data,k[W]=q.data,this.processInitOptions([z],{[W]:q.data},L),U.log(`Data source refetched: ${W}`)):q.state==="error"&&U.error(`Failed to refetch data source: ${W}`,q.error)}if(A.bindingEngine){const q=Object.keys(k);q.push("query"),A.bindingEngine.invalidateCacheByKeys(q)}if(Object.keys(L).length>0&&(k._localInit=L),Object.keys(k).length>0){if(k.query=Mb(this.currentQueryParams),A.currentLayoutJson?.computed&&Object.keys(A.currentLayoutJson.computed).length>0){const q={...A.currentDataContext,...k,query:k.query},z=this.calculateComputed(A.currentLayoutJson.computed,q);Object.keys(z).length>0&&(k._computed=z,this.globalState._computed=z,U.log("Computed values recalculated in updateQueryParams:",Object.keys(z)))}Ds(k,{sync:!0}),U.log("Template data updated with new query params")}}catch(x){U.error("Error in updateQueryParams:",x)}finally{Gr.setPending(!1),this.hideTransitionOverlay()}}calculateComputed(e,n){const r={},i=new $n;for(const[l,c]of Object.entries(e))try{let d;if(this.isComputedSwitchDefinition(c))d=i.resolveSwitch(c,n,{skipCache:!0});else if(typeof c=="string")if(c.startsWith("{{")&&c.endsWith("}}")){const f=c.slice(2,-2).trim();d=this.evaluateComputedExpression(f,n)}else d=c;r[l]=d,U.log(`Computed ${l}:`,d)}catch(d){U.warn(`Failed to calculate computed value: ${l}`,d),r[l]=void 0}return r}isComputedSwitchDefinition(e){return e!==null&&typeof e=="object"&&!Array.isArray(e)&&"$switch"in e&&"$cases"in e}evaluateComputedExpression(e,n){try{return af(e,n)}catch(r){U.warn(`Expression evaluation failed: ${e}`,r);return}}async executeInitActions(e,n={}){const{getActionDispatcher:r}=await Promise.resolve().then(()=>Wr),i=r();if(!i){U.warn("ActionDispatcher not available for init_actions");return}U.log("Executing init_actions:",e.map(d=>d.handler)),U.log("Init actions dataContext:",n),U.log("Init actions dataContext._global:",n._global);const l=e.map(d=>d.handler).filter(d=>d.includes("."));l.length>0&&await this.waitForHandlers(i,l);let c={...n};for(const d of e)try{const f={type:"click",handler:d.handler,target:d.target,params:d.params,resultTo:d.resultTo,onSuccess:d.onSuccess,onError:d.onError,if:d.if,conditions:d.conditions,auth_mode:d.auth_mode,auth_required:d.auth_required},h=i.createHandler(f,c),m=new Event("init");await h(m),U.log(`Init action executed: ${d.handler}`);const y=this.globalState;c={...c,_global:{...y},_local:y._local||c._local||{}},U.log(`Data context refreshed after ${d.handler}:`,{cartKey:c._global?.cartKey})}catch(f){U.error(`Failed to execute init action: ${d.handler}`,f)}}async waitForHandlers(e,n,r=5e3){const i=Date.now(),l=50,c=()=>n.every(f=>e.customHandlers?.has(f));if(c()){U.log("All module handlers already registered");return}const d=fh().getFailedJsAssets();if(d.length>0){const f=d.some(y=>y==="module"||y==="plugin"),h=n.filter(y=>!e.customHandlers?.has(y)),m=h.length>0&&h.every(y=>d.some(v=>y.startsWith(`${v}.`)));if(f||m){U.warn("Extension asset load failed — not waiting for handlers that will never register:",{pending:h,failedAssets:d});return}}return U.log("Waiting for module handlers:",n),new Promise(f=>{const h=()=>{if(c()){U.log("All module handlers now registered"),f();return}if(Date.now()-i>=r){const m=n.filter(y=>!e.customHandlers?.has(y));U.warn("Timeout waiting for handlers:",m),f();return}setTimeout(h,l)};h()})}};M(Lr,"LOCALE_STORAGE_KEY","g7_locale"),M(Lr,"CACHE_VERSION_STORAGE_KEY","g7_cache_version");let Xo=Lr;function ph(s){const e=new Xo(s);return s.websocket&&(Gc.configure(s.websocket),U.log("WebSocket 설정 완료")),document.readyState==="loading"?document.addEventListener("DOMContentLoaded",()=>{e.init(),window.__templateApp=e}):(e.init(),window.__templateApp=e),e}typeof window<"u"&&(window.G7Core=window.G7Core||{},window.G7Core.initTemplateApp=ph,window.G7Core.dataSource={refetch:async(s,e)=>{const n=window.__templateApp;if(!n){U.warn("TemplateApp not initialized (G7Core.dataSource.refetch)");return}return n.refetchDataSource(s,e)},get:s=>{const e=window.__templateApp;if(!e){U.warn("TemplateApp not initialized (G7Core.dataSource.get)");return}return e.getDataSource(s)}});function mT(s={}){const{checked:e,value:n,name:r,type:i="checkbox"}=s,c=n!==void 0?n:i==="checkbox"||i==="radio"?e??!1:String(e??""),d={checked:e??!1,value:c,name:r??"",type:i};return{target:d,currentTarget:d,preventDefault:()=>{},stopPropagation:()=>{},nativeEvent:new Event("change"),bubbles:!0,cancelable:!0,defaultPrevented:!1,eventPhase:Event.AT_TARGET,isTrusted:!1,timeStamp:Date.now(),type:"change",isDefaultPrevented:()=>!1,isPropagationStopped:()=>!1,persist:()=>{}}}function yT(s={}){const{button:e=0,clientX:n=0,clientY:r=0}=s;return{button:e,clientX:n,clientY:r,preventDefault:()=>{},stopPropagation:()=>{},nativeEvent:new MouseEvent("click"),bubbles:!0,cancelable:!0,defaultPrevented:!1,eventPhase:Event.AT_TARGET,isTrusted:!1,timeStamp:Date.now(),type:"click",isDefaultPrevented:()=>!1,isPropagationStopped:()=>!1,persist:()=>{},target:document.createElement("div"),currentTarget:document.createElement("div")}}function bT(){return{preventDefault:()=>{},stopPropagation:()=>{},nativeEvent:new Event("submit"),bubbles:!0,cancelable:!0,defaultPrevented:!1,eventPhase:Event.AT_TARGET,isTrusted:!1,timeStamp:Date.now(),type:"submit",isDefaultPrevented:()=>!1,isPropagationStopped:()=>!1,persist:()=>{},target:document.createElement("form"),currentTarget:document.createElement("form")}}function vT(s,e="keydown"){return{key:s,code:s.length===1?`Key${s.toUpperCase()}`:s,preventDefault:()=>{},stopPropagation:()=>{},nativeEvent:new KeyboardEvent(e,{key:s}),bubbles:!0,cancelable:!0,defaultPrevented:!1,eventPhase:Event.AT_TARGET,isTrusted:!1,timeStamp:Date.now(),type:e,isDefaultPrevented:()=>!1,isPropagationStopped:()=>!1,persist:()=>{},target:document.createElement("div"),currentTarget:document.createElement("div"),altKey:!1,ctrlKey:!1,metaKey:!1,shiftKey:!1,repeat:!1}}const ST={justify:/^justify-(start|end|center|between|around|evenly)$/,items:/^items-(start|end|center|baseline|stretch)$/,content:/^content-(start|end|center|between|around|evenly|stretch)$/,self:/^self-(auto|start|end|center|stretch|baseline)$/,flex:/^flex-(row|row-reverse|col|col-reverse|wrap|wrap-reverse|nowrap|1|auto|initial|none)$/,grow:/^(grow|grow-0)$/,shrink:/^(shrink|shrink-0)$/,basis:/^basis-/,order:/^(order-|-)order-/,gap:/^gap(-x|-y)?-/,gridCols:/^grid-cols-/,gridRows:/^grid-rows-/,colSpan:/^col-(span-|start-|end-)/,rowSpan:/^row-(span-|start-|end-)/,display:/^(block|inline-block|inline|flex|inline-flex|table|inline-table|table-caption|table-cell|table-column|table-column-group|table-footer-group|table-header-group|table-row-group|table-row|flow-root|grid|inline-grid|contents|list-item|hidden)$/,position:/^(static|fixed|absolute|relative|sticky)$/,inset:/^(inset|top|right|bottom|left)-/,zIndex:/^z-/,width:/^w-/,minWidth:/^min-w-/,maxWidth:/^max-w-/,height:/^h-/,minHeight:/^min-h-/,maxHeight:/^max-h-/,padding:/^p[xytblr]?-/,margin:/^-?m[xytblr]?-/,space:/^space-(x|y)-/,fontSize:/^text-(xs|sm|base|lg|xl|2xl|3xl|4xl|5xl|6xl|7xl|8xl|9xl)$/,fontWeight:/^font-(thin|extralight|light|normal|medium|semibold|bold|extrabold|black)$/,fontStyle:/^(italic|not-italic)$/,fontFamily:/^font-(sans|serif|mono)/,textAlign:/^text-(left|center|right|justify|start|end)$/,textColor:/^text-(inherit|current|transparent|black|white|slate|gray|zinc|neutral|stone|red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose)-/,textDecoration:/^(underline|overline|line-through|no-underline)$/,textTransform:/^(uppercase|lowercase|capitalize|normal-case)$/,lineHeight:/^leading-/,letterSpacing:/^tracking-/,textOverflow:/^(truncate|text-ellipsis|text-clip)$/,whitespace:/^whitespace-/,wordBreak:/^break-/,bgColor:/^bg-(inherit|current|transparent|black|white|slate|gray|zinc|neutral|stone|red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose)-/,bgGradient:/^bg-gradient-/,bgSize:/^bg-(auto|cover|contain)$/,bgPosition:/^bg-(bottom|center|left|left-bottom|left-top|right|right-bottom|right-top|top)$/,bgRepeat:/^bg-(repeat|no-repeat|repeat-x|repeat-y|repeat-round|repeat-space)$/,bgAttachment:/^bg-(fixed|local|scroll)$/,bgClip:/^bg-clip-/,bgOrigin:/^bg-origin-/,borderWidth:/^border(-[xytblr])?(-0|-2|-4|-8)?$/,borderColor:/^border-(inherit|current|transparent|black|white|slate|gray|zinc|neutral|stone|red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose)-/,borderStyle:/^border-(solid|dashed|dotted|double|hidden|none)$/,borderRadius:/^rounded(-[tblrse]{1,2})?(-none|-sm|-md|-lg|-xl|-2xl|-3xl|-full)?$/,ringWidth:/^ring(-0|-1|-2|-4|-8|-inset)?$/,ringColor:/^ring-(inherit|current|transparent|black|white|slate|gray|zinc|neutral|stone|red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose)-/,ringOffset:/^ring-offset-/,shadow:/^shadow(-sm|-md|-lg|-xl|-2xl|-inner|-none)?$/,opacity:/^opacity-/,mixBlend:/^mix-blend-/,bgBlend:/^bg-blend-/,blur:/^blur(-none|-sm|-md|-lg|-xl|-2xl|-3xl)?$/,brightness:/^brightness-/,contrast:/^contrast-/,grayscale:/^grayscale(-0)?$/,hueRotate:/^-?hue-rotate-/,invert:/^invert(-0)?$/,saturate:/^saturate-/,sepia:/^sepia(-0)?$/,backdropBlur:/^backdrop-blur-/,backdropBrightness:/^backdrop-brightness-/,backdropContrast:/^backdrop-contrast-/,backdropGrayscale:/^backdrop-grayscale-/,backdropHueRotate:/^backdrop-hue-rotate-/,backdropInvert:/^backdrop-invert-/,backdropOpacity:/^backdrop-opacity-/,backdropSaturate:/^backdrop-saturate-/,backdropSepia:/^backdrop-sepia-/,scale:/^scale(-x|-y)?-/,rotate:/^-?rotate-/,translate:/^-?translate-[xy]-/,skew:/^-?skew-[xy]-/,transformOrigin:/^origin-/,transition:/^transition(-none|-all|-colors|-opacity|-shadow|-transform)?$/,duration:/^duration-/,ease:/^ease-(linear|in|out|in-out)$/,delay:/^delay-/,animate:/^animate-/,cursor:/^cursor-/,userSelect:/^select-/,pointerEvents:/^pointer-events-/,resize:/^resize(-none|-x|-y)?$/,scrollBehavior:/^scroll-(auto|smooth)$/,touchAction:/^touch-/,overflow:/^overflow(-x|-y)?-(auto|hidden|clip|visible|scroll)$/,overscroll:/^overscroll(-x|-y)?-(auto|contain|none)$/,visibility:/^(visible|invisible|collapse)$/,aspectRatio:/^aspect-/,columns:/^columns-/,breakAfter:/^break-after-/,breakBefore:/^break-before-/,breakInside:/^break-inside-/,boxDecorationBreak:/^box-decoration-/,boxSizing:/^box-(border|content)$/,float:/^float-(right|left|none)$/,clear:/^clear-(left|right|both|none)$/,isolation:/^(isolate|isolation-auto)$/,objectFit:/^object-(contain|cover|fill|none|scale-down)$/,objectPosition:/^object-/};function Nb(s){const e=s.replace(/^(dark:|hover:|focus:|active:|disabled:|group-hover:|sm:|md:|lg:|xl:|2xl:)+/,"");for(const[n,r]of Object.entries(ST))if(r.test(e))return n;return null}function Ib(s){const e=s.match(/^((dark:|hover:|focus:|active:|disabled:|group-hover:|sm:|md:|lg:|xl:|2xl:)+)/);return e?e[1]:""}function wT(s,e){if(!e||e.trim()==="")return s;if(!s||s.trim()==="")return e;const n=s.split(/\s+/).filter(Boolean),r=e.split(/\s+/).filter(Boolean),i=new Map;for(const c of r){const d=Nb(c);if(d){const f=Ib(c),h=`${d}:${f}`;i.set(h,c)}}return[...n.filter(c=>{const d=Nb(c);if(!d)return!0;const f=Ib(c),h=`${d}:${f}`;return!i.has(h)}),...r].join(" ")}function CT(s){return Object.entries(s).filter(([,e])=>e).map(([e])=>e).join(" ")}function ET(...s){return s.filter(e=>typeof e=="string"&&e.length>0).join(" ")}const be=pt("G7CoreGlobals");function _T(){window.React=Xe,window.ReactDOM={...Ld,createPortal:ja.createPortal,unstable_batchedUpdates:s=>s()},window.ReactJSXRuntime=a_,be.log("전역 객체 window.React, window.ReactDOM, window.ReactJSXRuntime에 노출됨")}function AT(s){const e=new Map,n=za.getInstance();s.componentEvent={on:(r,i)=>(e.has(r)||e.set(r,new Set),e.get(r).add(i),n.trackEventSubscribe(r),()=>{e.get(r)?.delete(i),n.trackEventUnsubscribe(r)}),emit:async(r,i)=>{const l=e.get(r),c=l?.size??0;if(!l||l.size===0)return n.trackEventEmit(r,i,0),[];let d,f=[];try{f=await Promise.all(Array.from(l).map(async h=>{try{return await h(i)}catch(m){throw be.error(`componentEvent: Error in listener for "${r}":`,m),m}}))}catch(h){d=h instanceof Error?h:new Error(String(h))}if(n.trackEventEmit(r,i,c,f,d),d)throw d;return f},off:r=>{e.delete(r),n.trackEventOff(r)},clear:()=>{e.clear(),n.trackEventClear()}},be.log("전역 객체 window.G7Core.componentEvent에 노출됨")}function xT(s){s.__runtime||(s.__runtime={DynamicRenderer:Vr,ComponentRegistry:Er,TranslationEngine:Oa,DataSourceManager:Dr,dataSourceManager:Gx,DataBindingEngine:$n,dataBindingEngine:of,ActionDispatcher:jo,TranslationReactContext:ih,TranslationProvider:Uc,useTranslation:gb,ResponsiveContext:If,ResponsiveProvider:Tc,useResponsive:jf,responsiveManager:Ci,BREAKPOINT_PRESETS:lc,AuthManager:Ar,createLogger:pt,G7DevToolsCore:za},be.log("전역 객체 window.G7Core.__runtime(코어 런타임 표면)에 노출됨"))}function TT(s){if(s.layoutEditor&&s.layoutEditor.__isStub!==!0||s.layoutEditor&&s.layoutEditor.__isStub===!0)return;const e=[],n=[];s.layoutEditor={__isStub:!0,__queue:e,__readyCallbacks:n,registerWidget:(r,i)=>e.push(["widget",r,i]),registerNodeEditor:(r,i)=>e.push(["nodeEditor",r,i]),registerCanvasOverlay:(r,i)=>e.push(["canvasOverlay",r,i]),onReady:r=>{typeof r=="function"&&n.push(r)}},be.log("전역 객체 window.G7Core.layoutEditor 예약 접수함(stub) 노출됨")}function kT(s){s.asset={template:(e,n,r)=>f0(e,n,r),templateDir:(e,n)=>h0(e,n),module:(e,n,r)=>p0(e,n,r),plugin:(e,n,r)=>g0(e,n,r),convertToCurrentMode:e=>Ln(e),loadScript:(e,n,r)=>Io(e,bs())?Cr(e,n,r):Promise.reject(new Error(`Blocked untrusted script src (same-origin path or declared trusted host required): ${e}`)),isAllowedScriptSrc:e=>Io(e,bs()),loadStylesheet:(e,n,r)=>ia(e,n,r)},s.assets={notifyFailure:xs,clearFailure:Db,clearAll:Ob,getFailures:Zx,retryAll:$b},lT(),be.log("전역 객체 window.G7Core.asset / window.G7Core.assets 노출됨")}function RT(s,e){s.useTranslation=gb,s.t=(n,r)=>{const i=e.getState();if(!i.translationEngine||!i.translationContext)return n;if(r){const l="|"+Object.entries(r).map(([c,d])=>`${c}=${d}`).join("|");return i.translationEngine.translate(n,i.translationContext,l)}return i.translationEngine.translate(n,i.translationContext)},be.log("전역 객체 window.G7Core.useTranslation에 노출됨")}function DT(s,e){s.createChangeEvent=mT,s.createClickEvent=yT,s.createSubmitEvent=bT,s.createKeyboardEvent=vT,s.uuid=()=>typeof crypto<"u"&&crypto.randomUUID?crypto.randomUUID():"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,n=>{const r=Math.random()*16|0;return(n==="x"?r:r&3|8).toString(16)}),s.createLogger=pt,be.log("전역 객체 window.G7Core.createLogger에 노출됨"),s.renderItemChildren=(n,r,i,l,c)=>{const d=e.getState(),m={_global:window.__templateApp?.getGlobalState?.()||{},_local:r._local||{},_computed:r._computed||{},...r},y=new Set,v=(_,x)=>{const A=m._global?._remountKeys;return _&&A?.[_]&&!y.has(_)?(y.add(_),`${_}-remount-${A[_]}`):_||x},S={translationContext:d.translationContext,translationEngine:d.translationEngine,bindingEngine:d.bindingEngine,actionDispatcher:d.actionDispatcher,getRemountKey:v,...c};return Eo(n,m,i,l,S)},s.evaluateCondition=(n,r)=>{if(!n)return!0;const i=e.getState(),c=window.__templateApp?.getGlobalState?.()||{},d={_global:c,_local:c._local||{},_computed:c._computed||{},...r||{}},f=i.bindingEngine??of;return dm(n,d,f)},s.getComponentMap=()=>Er.getInstance().getComponentMap(),s.renderComponentLayout=(n,r,i)=>{if(!n||!Array.isArray(n)||n.length===0)return null;const l=Er.getInstance().getComponentMap(),c=e.getState(),f=window.__templateApp?.getGlobalState?.()||{},h={_global:f._global||{},_local:f._local||{},_computed:f._computed||{},...r},m=new Set,y=(v,S)=>{const _=h._global?._remountKeys;return v&&_?.[v]&&!m.has(v)?(m.add(v),`${v}-remount-${_[v]}`):v||S};return Eo(n,h,l,i,{translationContext:c.translationContext,translationEngine:c.translationEngine,bindingEngine:c.bindingEngine,actionDispatcher:c.actionDispatcher,getRemountKey:y})},s.renderExpandContent=n=>{const{children:r,row:i,expandContext:l,componentContext:c,keyPrefix:d}=n;if(!r||!Array.isArray(r)||r.length===0)return null;const f=n.componentMap||Er.getInstance().getComponentMap(),h=e.getState(),m=s.state?.get?.()||{},y=c?.stateRef?.current??c?.state??{},v={...m._local||{},...y},S=c?.computedRef?.current??m._computed??{},_=h.bindingEngine??s.getDataBindingEngine?.(),x={...m,_local:v,_computed:S,row:i,item:i,$item:i};let A={};if(l&&typeof l=="object"&&_)try{for(const[L,B]of Object.entries(l))if(typeof B=="string"){const q=La(B);if(q!==null)try{A[L]=Wn(q)?_.evaluatePipeExpression(q,x,{skipCache:!0}):_.evaluateExpression(q,x,{skipCache:!0})}catch{A[L]=void 0}else A[L]=_.resolveBindings(B,x,{skipCache:!0})}else A[L]=B}catch(L){be.warn("renderExpandContent: expandContext 평가 오류:",L)}const D={row:i,item:i,$item:i,_local:v,_global:m._global||{},_computed:m._computed||{},...A},O=new Set,k=(L,B)=>{const q=D._global?._remountKeys;return L&&q?.[L]&&!O.has(L)?(O.add(L),`${L}-remount-${q[L]}`):L||B};return Eo(r,D,f,d,{translationContext:h.translationContext,translationEngine:h.translationEngine??void 0,bindingEngine:h.bindingEngine??void 0,actionDispatcher:h.actionDispatcher,componentContext:c,getRemountKey:k})},s.$get=function(r,i,l=void 0){if(r==null)return l;const c=Array.isArray(i)?i:[i];if(c.length===0)return r;let d=r;for(const f of c){if(d==null||f==null)return l;d=d[f]}return d??l},be.log("전역 객체 window.G7Core.renderItemChildren에 노출됨"),be.log("전역 객체 window.G7Core.renderComponentLayout에 노출됨"),be.log("전역 객체 window.G7Core.renderExpandContent에 노출됨"),be.log("전역 객체 window.G7Core.$get에 노출됨")}function OT(s){s.dispatch=async(e,n)=>{const r=window.__templateApp;if(!r)return be.warn("G7Core.dispatch: TemplateApp이 초기화되지 않았습니다."),{success:!1,error:new Error("TemplateApp이 초기화되지 않았습니다.")};const i=r.getActionDispatcher?.();if(!i)return be.warn("G7Core.dispatch: ActionDispatcher를 찾을 수 없습니다."),{success:!1,error:new Error("ActionDispatcher를 찾을 수 없습니다.")};const l=r.getRouter?.(),c=r.getGlobalState?.(),d=r.setGlobalState?.bind(r),h=n?.componentContext||window.__g7ActionContext,m=window.__g7PendingLocalState;let y=h?.state??c;m&&h&&(y=m,be.log("[dispatch] Using __g7PendingLocalState for context.state:",m));let v=h?.data??c;m&&h?.data?._local&&(v={...h.data,_local:m},be.log("[dispatch] Updated context.data._local with pendingLocalState"));const S={navigate:l?(_,x)=>l.navigate(_,x):void 0,setState:h?.setState??d,state:y,data:v,_isDispatchFallbackContext:!h?.setState};if(e.debounce){const _=e.debounceKey||`dispatch-${e.handler}`,x=typeof e.debounce=="number"?e.debounce:e.debounce.delay;return i.debouncedCall(_,x,()=>{const{debounce:A,debounceKey:D,...O}=e;i.dispatchAction(O,S)}),{success:!0,debounced:!0}}try{return await i.dispatchAction(e,S)}catch(_){return be.error("G7Core.dispatch: 액션 실행 오류:",_),{success:!1,error:_ instanceof Error?_:new Error(String(_))}}},be.log("전역 객체 window.G7Core.dispatch에 노출됨")}function Jo(s){const e={};for(const[n,r]of Object.entries(s))if(n.includes(".")){const i=n.split(".");let l=e;for(let c=0;c0&&e.every(n=>/^\d+$/.test(n))}function Jn(s,e){if(Array.isArray(s)&&!Array.isArray(e)&<(e)){const r=Object.keys(e).map(l=>parseInt(l,10));if(!((r.length>0?Math.max(...r):0)>=s.length+r.length+10)){const l=[...s];for(const[c,d]of Object.entries(e)){const f=parseInt(c,10);f>=0&&f=l.length&&(l[f]=d)}return l}}const n={...s};for(const[r,i]of Object.entries(e))i!==null&&typeof i=="object"&&!Array.isArray(i)?n[r]!==null&&typeof n[r]=="object"?n[r]=Jn(n[r],i):n[r]={...i}:n[r]=i;return n}function $T(s){s.state={get:()=>window.__templateApp?.getGlobalState?.()||{},set:(e,n)=>{const r=window.__templateApp;if(r?.setGlobalState&&r?.getGlobalState){const i=n?.merge||"deep",l=Jo(e);let c;if(i==="replace")c=l;else if(i==="shallow")c={...r.getGlobalState(),...l};else{const d=r.getGlobalState();c=Jn(d,l)}r.setGlobalState(c,{render:n?.render})}else be.warn("G7Core.state.set: TemplateApp이 초기화되지 않았습니다.")},setLocal:(e,n)=>{const r=window.__templateApp;if(n?.render===!1&&!n?.selfManaged){const A=window.__g7AutoBindingPaths;A&&A.size>0&&jb(Jo(e)).some(O=>A.has(O))&&(be.log("[setLocal] render:false + 자동바인딩 경로 겹침 감지 → render:true 자동 승격 (engine-v1.43.0)"),n={...n,render:!0})}if(n?.debounce){const A=r?.getActionDispatcher?.();if(A){const D=n.debounceKey||`setLocal-${Object.keys(e).join(",")}`,{debounce:O,debounceKey:k,...L}=n;A.debouncedCall(D,n.debounce,()=>{s.state.setLocal(e,L)});return}}const i=n?.scope??"current",l=n?.merge||"deep",c=window.__g7ActionContext,d=Jo(e);if(i==="parent"||i==="root"){const A=window.__g7LayoutContextStack||[];if(A.length>0){const D=i==="parent"?A[A.length-1]:A[0];if(D?.setState){D.setState(O=>l==="replace"?d:l==="shallow"?{...O||{},...d}:Jn(O||{},d)),be.log(`[setLocal] scope=${i}: 타겟 컨텍스트에 상태 업데이트 (mergeMode=${l})`,d);return}}be.warn(`[setLocal] scope=${i}: 레이아웃 컨텍스트 스택이 비어있습니다. current로 폴백합니다.`)}const f=window.__g7PendingLocalState,h=r?.getGlobalState?.()?._local||{},v=!((window.__g7LayoutContextStack||[]).length>0)&&c?.state?c.state:void 0,S=v?Mo(h,v):h,_=f||S;let x;if(l==="replace"?x=d:l==="shallow"?x={..._,...d}:x=Jn(_,d),r?.setGlobalState&&(r.setGlobalState({_local:x},{render:n?.render}),be.log(`[setLocal] globalLocal updated via setGlobalState (mergeMode=${l}, render=${n?.render??!0}):`,d)),window.__g7PendingLocalState=x,be.log("[setLocal] __g7PendingLocalState updated:",x),window.__g7SequenceLocalSync=x,window.__g7LastSetLocalSnapshot=x,l==="replace")window.__g7ForcedLocalFields=d;else{const A=window.__g7ForcedLocalFields||{};window.__g7ForcedLocalFields=Jn(A,d)}if(be.log("[setLocal] __g7ForcedLocalFields updated:",d),l==="replace")window.__g7SetLocalOverrideKeys=d;else{const A=window.__g7SetLocalOverrideKeys||{};window.__g7SetLocalOverrideKeys=Jn(A,d)}be.log("[setLocal] __g7SetLocalOverrideKeys updated:",d),c?.setState&&c.setState(A=>l==="replace"?d:l==="shallow"?{...A||{},...d}:Jn(A||{},d)),r?.setGlobalState||be.warn("G7Core.state.setLocal: TemplateApp이 없습니다.")},getLocal:()=>{const r=(window.__templateApp?.getGlobalState?.()||{})._local||{},i=window.__g7PendingLocalState;if(i)return Jn(r,i);const l=window.__g7LastSetLocalSnapshot;return l&&l!==r?Jn(r,l):r},update:e=>{const n=window.__templateApp;if(n?.getGlobalState&&n?.setGlobalState){const r=n.getGlobalState(),i=e(r);n.setGlobalState(i)}else be.warn("G7Core.state.update: TemplateApp이 초기화되지 않았습니다.")},subscribe:e=>{const n=window.__templateApp;return n?.onGlobalStateChange?n.onGlobalStateChange(e):(be.warn("G7Core.state.subscribe: TemplateApp이 초기화되지 않았습니다."),()=>{})},getDataSource:e=>window.__templateApp?.getDataSource?.(e),getIsolated:e=>e?window.__g7IsolatedStates?.[e]?.state??null:window.__g7ActionContext?.isolatedContext?.state??null,setIsolated:(e,n,r)=>{let i,l,c;typeof e=="string"?(i=e,l=n||{},c=r):(l=e,c=n&&typeof n=="object"&&"merge"in n?n:r);const d=c?.merge||"deep",f=Jo(l);if(i){const y=window.__g7IsolatedStates?.[i];y?.mergeState?y.mergeState(f,d):be.warn(`G7Core.state.setIsolated: scopeId '${i}'를 찾을 수 없습니다.`);return}const h=window.__g7ActionContext;h?.isolatedContext?.mergeState?h.isolatedContext.mergeState(f,d):be.warn("G7Core.state.setIsolated: 액션 컨텍스트에 isolatedContext가 없습니다.")},getParent:()=>{const e=window.__g7LayoutContextStack||[];if(e.length===0)return be.log("[getParent] 레이아웃 컨텍스트 스택이 비어있습니다."),null;const n=e[e.length-1];if(!n)return null;const r=n.dataContext||{};return{_local:n.state||r._local||{},_global:r._global||s.state.get()||{},setState:n.setState}},setParentLocal:(e,n,r)=>{const i=window.__g7LayoutContextStack||[];if(i.length===0){be.warn("[setParentLocal] 레이아웃 컨텍스트 스택이 비어있습니다.");return}const l=i[i.length-1];if(!l?.setState){be.warn("[setParentLocal] 부모 컨텍스트에 setState가 없습니다.");return}let c,d;typeof e=="string"?(c={[e]:n},d=r):(c=e,d=n&&typeof n=="object"&&"merge"in n?n:r);const f=d?.merge||"deep",h=Jo(c),m=l.state?._local!==void 0,y=m?l.state._local:l.state||{};let v;f==="replace"?v=h:f==="shallow"?v={...y,...h}:v=Jn(y,h),window.__g7PendingLocalState=v;const S=window.__templateApp;if(S?.setGlobalState){const _=S.getGlobalState?.()?._local,A=f!=="replace"&&!!_&&typeof _=="object"&&!Array.isArray(_)?f==="shallow"?{..._,...h}:Jn(_,h):void 0;S.setGlobalState({_local:A?Mo(A,v):v})}if(l.setState(v),m?l.state._local=v:l.state=v,l.dataContext&&(l.dataContext._local=v),f==="replace")window.__g7ForcedLocalFields=h;else{const _=window.__g7ForcedLocalFields||{};window.__g7ForcedLocalFields=Jn(_,h)}$f()},setParentGlobal:(e,n)=>{let r;typeof e=="string"?r={[e]:n}:r=e,s.state.set(r),be.log("[setParentGlobal] 전역 상태 업데이트:",r)}},window.__g7IsolatedStates||(window.__g7IsolatedStates={}),s.state.getGlobal=s.state.get,s.state.setGlobal=s.state.set,be.log("전역 객체 window.G7Core.state에 노출됨")}function MT(s){s.dataSource={get:e=>window.__templateApp?.getDataSource?.(e),set:(e,n,r)=>{const i=window.__templateApp;i?.setDataSource?i.setDataSource(e,n,r):be.warn("G7Core.dataSource.set: TemplateApp이 초기화되지 않았습니다.")},refetch:async(e,n)=>{const r=window.__templateApp;if(r?.refetchDataSource)return r.refetchDataSource(e,n);be.warn("G7Core.dataSource.refetch: TemplateApp이 초기화되지 않았습니다.")},updateItem:(e,n,r,i,l)=>{const c=window.__templateApp;return c?.updateDataSourceItem?c.updateDataSourceItem(e,n,r,i,l):(be.warn("G7Core.dataSource.updateItem: TemplateApp이 초기화되지 않았습니다."),!1)},updateData:(e,n,r,i="append")=>{const l=window.__templateApp;if(!l?.getDataSource||!l?.setDataSource)return be.warn("G7Core.dataSource.updateData: TemplateApp이 초기화되지 않았습니다."),!1;const c=l.getDataSource(e);if(!c)return be.warn(`G7Core.dataSource.updateData: 데이터 소스 '${e}'를 찾을 수 없습니다.`),!1;let d,f=c,h=null;if(n){const y=n.split(".");h=y.pop();for(const v of y)if(f&&typeof f=="object"&&v in f)f=f[v];else return be.warn(`G7Core.dataSource.updateData: 경로 '${n}'를 찾을 수 없습니다.`),!1;d=f[h]}else d=c;if(!Array.isArray(d))return be.warn("G7Core.dataSource.updateData: 대상 경로의 데이터가 배열이 아닙니다."),!1;if(!Array.isArray(r))return be.warn("G7Core.dataSource.updateData: newData가 배열이 아닙니다."),!1;const m=i==="prepend"?[...r,...d]:[...d,...r];return n&&h?(f[h]=m,l.setDataSource(e,c,{merge:!1})):l.setDataSource(e,m,{merge:!1}),be.log(`G7Core.dataSource.updateData: '${e}'에 ${r.length}개 항목 ${i==="prepend"?"앞에":"뒤에"} 추가됨`),!0}},be.log("전역 객체 window.G7Core.dataSource에 노출됨")}function NT(s,e){s.locale={current:()=>window.__templateApp?.getLocale?.()||"ko",supported:()=>{const r=window.__templateApp?.globalState?.appConfig?.supportedLocales;return Array.isArray(r)&&r.length>0?r:e.getState().templateMetadata?.locales||["ko","en"]},change:async n=>{const r=window.__templateApp;r?.changeLocale?await r.changeLocale(n):be.warn("G7Core.locale.change: TemplateApp이 초기화되지 않았습니다.")}},be.log("전역 객체 window.G7Core.locale에 노출됨")}function IT(s){s.toast={show:(e,n)=>{s.dispatch({handler:"toast",params:{message:e,type:n?.type||"info",...n?.duration&&{duration:n.duration}}})},success:(e,n)=>{s.toast.show(e,{type:"success",duration:n})},error:(e,n)=>{s.toast.show(e,{type:"error",duration:n})},warning:(e,n)=>{s.toast.show(e,{type:"warning",duration:n})},info:(e,n)=>{s.toast.show(e,{type:"info",duration:n})}},be.log("전역 객체 window.G7Core.toast에 노출됨")}function jT(s){s.modal={open:e=>{s.dispatch({handler:"openModal",target:e})},close:e=>{s.dispatch({handler:"closeModal",...e&&{target:e}})},closeAll:()=>{s.dispatch({handler:"closeAllModals"})},isOpen:e=>{const n=s.state.get();return n._global?.activeModal===e||n._global?.modalStack?.includes(e)||!1},getStack:()=>s.state.get()._global?.modalStack||[]},be.log("전역 객체 window.G7Core.modal에 노출됨")}function HT(s){s.style={mergeClasses:wT,conditionalClass:CT,joinClasses:ET},be.log("전역 객체 window.G7Core.style에 노출됨")}function zT(s,e){const{webSocketManager:n}=e;s.websocket={manager:n,subscribe:(r,i,l,c)=>n.subscribe(r,i,l,c),unsubscribe:r=>n.unsubscribe(r),leaveChannel:r=>n.leaveChannel(r),disconnect:()=>n.disconnect(),isInitialized:()=>n.isInitialized(),getSubscriptionCount:()=>n.getSubscriptionCount()},be.log("전역 객체 window.G7Core.websocket에 노출됨")}function PT(s,e){const{transitionManager:n}=e;s.navigation={isPending:()=>n?.getIsPending?.()||!1,onComplete:r=>{if(!n)return r(),()=>{};let i=n.getIsPending();const l=n.subscribe(c=>{c?i=!0:i&&(r(),l())});return l}},be.log("전역 객체 window.G7Core.navigation에 노출됨")}function UT(s){s.plugin={getSettings:e=>window.G7Config?.plugins?.[e],get:(e,n,r)=>window.G7Config?.plugins?.[e]?.[n]??r,getAll:()=>window.G7Config?.plugins??{}},be.log("전역 객체 window.G7Core.plugin에 노출됨")}function BT(s){s.module={getSettings:e=>window.G7Config?.modules?.[e],get:(e,n,r)=>window.G7Config?.modules?.[e]?.[n]??r,getAll:()=>window.G7Config?.modules??{}},be.log("전역 객체 window.G7Core.module에 노출됨")}function qT(s,e){const{transitionManager:n,responsiveManager:r}=e;s.AuthManager=Ar,s.api=qr(),s.TransitionManager=n,s.useTransitionState=Ay,s.ResponsiveManager=r,s.useResponsive=jf,s.useControllableState=y_,s.shallowArrayEqual=b_,s.shallowObjectEqual=Ty,s.updateQueryParams=async(i,l)=>{const c=window.__templateApp;if(c?.updateQueryParams)return c.updateQueryParams(i,l);be.warn("G7Core.updateQueryParams: TemplateApp이 초기화되지 않았습니다.")},be.log("전역 객체 window.G7Core.AuthManager에 노출됨"),be.log("전역 객체 window.G7Core.api에 노출됨"),be.log("전역 객체 window.G7Core.ResponsiveManager에 노출됨"),be.log("전역 객체 window.G7Core.useResponsive에 노출됨"),be.log("전역 객체 window.G7Core.useControllableState에 노출됨"),be.log("전역 객체 window.G7Core.updateQueryParams에 노출됨")}function GT(s,e){s.getSlotContext=()=>window.__slotContextValue??null,s.getDynamicRenderer=()=>window.__DynamicRenderer??null,s.getComponentRegistry=()=>e.getComponentRegistry?.()??Er.getInstance(),s.getDataBindingEngine=()=>e.getDataBindingEngine?.()??e.getState().bindingEngine,s.getTranslationEngine=()=>e.getTranslationEngine?.()??e.getState().translationEngine,s.getActionDispatcher=()=>e.getActionDispatcher?.()??e.getState().actionDispatcher,be.log("전역 객체 window.G7Core 슬롯 API에 노출됨 (getSlotContext, getDynamicRenderer 등)")}function FT(s){s.identity={setLauncher:At.setLauncher.bind(At),handle:At.handle.bind(At),isIdentityRequired:At.isIdentityRequired.bind(At),hasLauncher:At.hasLauncher.bind(At),markDomainNoticeShown:At.markDomainNoticeShown.bind(At),redirectExternally:At.redirectExternally.bind(At),createDeferred:At.createDeferred.bind(At),resolveDeferred:At.resolveDeferred.bind(At),reset:At.reset.bind(At),redirectStashKey:Tf},be.log("전역 객체 window.G7Core 본인인증(IDV) API에 노출됨 (identity)")}function VT(s){if(typeof window>"u")return;_T(),window.G7Core||(window.G7Core={});const e=window.G7Core;xT(e),AT(e),TT(e),qT(e,s),kT(e),RT(e,s),DT(e,s),OT(e),$T(e),MT(e),NT(e,s),IT(e),jT(e),HT(e),zT(e,s),PT(e,s),UT(e),BT(e),GT(e,s),FT(e),be.log("G7Core 전역 객체 초기화 완료")}function KT(){const s=window.G7Core;if(!s){be.warn("G7Core가 초기화되지 않았습니다.");return}const e=()=>za.getInstance(),n={isEnabled:()=>{try{return e().isEnabled()}catch{return!1}},trackRender:r=>{try{e().isEnabled()&&e().trackRender(r)}catch{}},trackIteration:(r,i,l,c,d)=>{try{e().isEnabled()&&e().trackIteration(r,{source:i,itemVar:l,indexVar:c,sourceLength:d})}catch{}},trackIfCondition:(r,i,l,c)=>{try{e().isEnabled()&&e().trackIfCondition(r,i,l)}catch{}},trackMount:(r,i)=>{try{e().isEnabled()&&e().trackMount(r,{...i,id:r})}catch{}},trackUnmount:r=>{try{e().isEnabled()&&e().trackUnmount(r)}catch{}},trackExpressionEval:r=>{try{e().isEnabled()&&e().trackExpressionEval(r)}catch{}},trackBindingEval:r=>{try{e().isEnabled()&&e().trackBindingEval(r)}catch{}},recordCacheHit:()=>{try{e().isEnabled()&&e().recordCacheHit()}catch{}},recordCacheMiss:()=>{try{e().isEnabled()&&e().recordCacheMiss()}catch{}},trackHandlerRegistration:(r,i,l,c)=>{try{e().isEnabled()&&e().trackHandlerRegistration(r,i,l,c)}catch{}},trackHandlerUnregistration:r=>{try{e().isEnabled()&&e().trackHandlerUnregistration(r)}catch{}},logAction:r=>{try{e().isEnabled()&&e().logAction(r)}catch{}},trackRequest:(r,i)=>{try{return e().isEnabled()?e().trackRequest(r,i):""}catch{return""}},completeRequest:(r,i,l)=>{try{e().isEnabled()&&e().completeRequest(r,i,l)}catch{}},failRequest:(r,i)=>{try{e().isEnabled()&&e().failRequest(r,i)}catch{}},trackDataSourceDefinition:r=>{try{e().isEnabled()&&e().trackDataSourceDefinition(r)}catch{}},trackDataSourceLoading:r=>{try{e().isEnabled()&&e().trackDataSourceLoading(r)}catch{}},trackDataSourceLoaded:(r,i,l)=>{try{e().isEnabled()&&e().trackDataSourceLoaded(r,i,l)}catch{}},trackDataSourceError:(r,i)=>{try{e().isEnabled()&&e().trackDataSourceError(r,i)}catch{}},trackForm:(r,i,l)=>{try{e().isEnabled()&&e().trackForm(r,i,l)}catch{}},untrackForm:r=>{try{e().isEnabled()&&e().untrackForm(r)}catch{}},startStateChange:(r,i,l,c)=>{try{return e().isEnabled()?e().startStateChange(r,i,l,c):""}catch{return""}},completeStateChange:r=>{try{e().isEnabled()&&e().completeStateChange(r)}catch{}},trackComponentRender:(r,i,l,c,d,f)=>{try{e().isEnabled()&&e().trackComponentRender(r,i,l,c,d,f)}catch{}},updateLocalState:r=>{try{e().isEnabled()&&e().updateLocalState(r)}catch{}},updateComputedState:r=>{try{e().isEnabled()&&e().updateComputedState(r)}catch{}},updateParentContext:r=>{try{e().isEnabled()&&e().updateParentContext(r)}catch{}},trackComponentStateSource:(r,i,l,c)=>{try{e().isEnabled()&&e().trackComponentStateSource(r,i,l,c)}catch{}},trackDynamicState:(r,i)=>{try{e().isEnabled()&&e().trackDynamicState(r,i)}catch{}},trackContextFlow:(r,i,l,c,d,f)=>{try{e().isEnabled()&&e().trackContextFlow(r,i,l,c,d,f)}catch{}},trackComponentStyle:(r,i,l,c)=>{try{e().isEnabled()&&e().trackComponentStyle(r,i,l,c)}catch{}},trackAuthEvent:(r,i,l,c)=>{try{e().isEnabled()&&e().trackAuthEvent(r,i,l,c)}catch{}},trackAuthHeader:(r,i,l,c,d)=>{try{e().isEnabled()&&e().trackAuthHeader(r,i,l,c,d)}catch{}},trackLog:(r,i,l)=>{try{e().isEnabled()&&e().trackLog(r,i,l)}catch{}},trackAction:r=>{try{e().isEnabled()&&e().trackAction?.(r)}catch{}},trackDataSourceUpdate:r=>{try{e().isEnabled()&&e().trackDataSourceUpdate?.(r)}catch{}},startSequenceExecution:r=>{try{return e().isEnabled()?e().startSequenceExecution(r):""}catch{return""}},captureSequenceActionBefore:(r,i,l,c,d)=>{try{e().isEnabled()&&e().captureSequenceActionBefore(r,i,l,c,d)}catch{}},captureSequenceActionAfter:(r,i,l,c,d,f)=>{try{e().isEnabled()&&e().captureSequenceActionAfter(r,i,l,c,d,f)}catch{}},endSequenceExecution:(r,i)=>{try{e().isEnabled()&&e().endSequenceExecution(r,i)}catch{}},registerStateCaptureForHandler:(r,i,l)=>{try{e().isEnabled()&&e().registerStateCaptureForHandler(r,i,l)}catch{}},detectStaleClosure:(r,i,l,c,d)=>{try{return e().isEnabled()?e().detectStaleClosure(r,i,l,c,d):[]}catch{return[]}},trackStaleClosureWarning:r=>{try{e().isEnabled()&&e().trackStaleClosureWarning(r)}catch{}},trackModalOpen:r=>{try{e().isEnabled()&&e().trackModalOpen(r)}catch{}},trackModalClose:(r,i)=>{try{e().isEnabled()&&e().trackModalClose(r,i)}catch{}},trackModalStateChange:r=>{try{e().isEnabled()&&e().trackModalStateChange(r)}catch{}},trackNestedContext:r=>{try{return e().isEnabled()?e().trackNestedContext(r):""}catch{return""}},trackNestedContextAccess:(r,i)=>{try{e().isEnabled()&&e().trackNestedContextAccess(r,i)}catch{}},trackComputedProperty:(r,i,l,c,d,f,h)=>{try{e().isEnabled()&&e().trackComputedProperty(r,i,l,c,d,f,h)}catch{}},trackComputedRecalc:(r,i,l,c,d,f,h)=>{try{e().isEnabled()&&e().trackComputedRecalc(r,i,l,c,d,f,h)}catch{}},setNamedActionDefinitions:r=>{try{e().isEnabled()&&e().setNamedActionDefinitions(r)}catch{}},trackNamedActionRef:r=>{try{e().isEnabled()&&e().trackNamedActionRef(r)}catch{}}};s.devTools=n,be.log("G7Core.devTools 인터페이스 초기화 완료")}let Qo=null;function WT(){const s=window.G7Core;return s?.__devtools?Promise.resolve(s.__devtools):Qo||(Qo=new Promise((e,n)=>{const r=window.G7Config?.coreDevToolsAsset||"/build/core/devtools.min.js",i="g7-devtools-bundle",l=()=>{const f=window.G7Core?.__devtools;f?e(f):n(new Error("DevTools 번들 로드됨 — 그러나 __devtools 미노출"))};window.G7Core=window.G7Core||{},window.G7Core.__onDevToolsReady=l;const c=document.getElementById(i);if(c){c.addEventListener("load",l,{once:!0}),c.addEventListener("error",()=>n(new Error(`DevTools 번들 로드 실패: ${r}`)),{once:!0});return}const d=document.createElement("script");d.id=i,d.src=r,d.async=!1,d.addEventListener("load",l,{once:!0}),d.addEventListener("error",()=>{Qo=null,n(new Error(`DevTools 번들 로드 실패: ${r}`))},{once:!0}),document.head.appendChild(d)}),Qo)}function YT(){const s=window.G7Core;if(!s){be.warn("G7Core가 초기화되지 않았습니다.");return}const e=za.getInstance();if(e.initialize(),!e.isEnabled()){window.G7DevTools={isEnabled:()=>!1,enable:()=>{be.log("환경설정 > 고급 설정 > 디버그 모드를 켜세요")}},Md(),be.log("G7DevTools 비활성화됨 (디버그 모드 꺼짐)");return}XT(s,e)}async function XT(s,e){let n;try{n=await WT()}catch(h){be.warn("DevTools 번들 로드 실패 — 최소 API 로 폴백:",h),window.G7DevTools={isEnabled:()=>!1,enable:()=>{}},Md();return}const{DiagnosticEngine:r,getServerConnector:i,getStyleTracker:l}=n;KT(),Md();try{l().enable(),be.log("StyleTracker 활성화됨")}catch(h){be.warn("StyleTracker 활성화 실패:",h)}const c=new r,d=i(),f={isEnabled:()=>e.isEnabled(),state:{get:()=>e.getState(),getHistory:()=>e.getStateHistory(),watch:(h,m)=>e.watchState(h,m)},actions:{getHistory:()=>e.getActionHistory(),watch:h=>e.watchActions(h),getMetrics:()=>e.getActionMetrics()},binding:{evaluate:h=>{const m=s.getDataBindingEngine?.();if(!m){be.warn("DataBindingEngine이 없습니다.");return}return m.evaluateExpression(h,e.getState())},getCacheStats:()=>e.getCacheStats(),clearStats:()=>{e.resetCacheStats(),be.log("캐시 통계 초기화됨")}},diagnose:{analyze:h=>c.analyze(h),suggestFix:h=>c.suggestFix(h),getCommonIssues:()=>c.getCommonIssues(),getRulesByCategory:h=>c.getRulesByCategory(h)},server:{dumpState:h=>d.dumpState(h),sendLog:h=>d.sendLog(h),sendError:(h,m)=>d.sendError(h,m),isConnected:()=>d.isConnected(),testConnection:()=>d.testConnection()},config:{isDebugMode:()=>e.isEnabled(),setLogLevel:h=>e.setLogLevel(h),setMaxHistory:h=>e.setMaxHistory(h)},lifecycle:{getMountedComponents:()=>e.getLifecycleInfo().mountedComponents,getOrphanedListeners:()=>e.getLifecycleInfo().orphanedListeners},performance:{getRenderCount:()=>e.getPerformanceInfo().renderCounts,getBindingEvalCount:()=>e.getPerformanceInfo().bindingEvalCount,getMemoryWarnings:()=>e.getPerformanceInfo().memoryWarnings,startProfiling:()=>e.startProfiling(),stopProfiling:()=>e.stopProfiling()},network:{getActiveRequests:()=>e.getNetworkInfo().activeRequests,getRequestHistory:()=>e.getNetworkInfo().requestHistory,getPendingDataSources:()=>e.getNetworkInfo().pendingDataSources},conditional:{getIfConditions:()=>e.getConditionalInfo().ifConditions,getIterations:()=>e.getConditionalInfo().iterations},form:{getForms:()=>e.getFormInfo()},websocket:{getInfo:()=>e.getWebSocketInfo()}};window.G7DevTools=f,s.devtools=f,be.log("G7DevTools 전역 객체 초기화 완료 (window.G7DevTools)"),JT(n.DevToolsPanel)}function JT(s){if(document.getElementById("g7-devtools-root")){be.log("DevToolsPanel 이미 렌더링됨");return}const e=document.createElement("div");e.id="g7-devtools-root",document.body.appendChild(e);try{Ld.createRoot(e).render(Xe.createElement(s)),be.log("DevToolsPanel UI 렌더링 완료")}catch(n){be.error("DevToolsPanel UI 렌더링 실패:",n)}}const Qe=pt("TemplateEngine"),ae={templateId:null,locale:"ko",isInitialized:!1,reactRoot:null,containerId:null,currentLayoutJson:null,currentDataContext:{},translationContext:{templateId:"",locale:"ko"},registry:null,bindingEngine:null,translationEngine:null,actionDispatcher:null,templateMetadata:null};let Zo=!1;const el=[];async function QT(s){try{Qe.log("템플릿 메타데이터 로드 중...",s);const e=s.split("-").map(r=>r.split("_").map(i=>i.charAt(0).toUpperCase()+i.slice(1)).join("")).join(""),n=window[e];return n?.templateMetadata?(Qe.log("템플릿 메타데이터 로드 완료 (전역 변수)",n.templateMetadata),n.templateMetadata):(Qe.warn(`템플릿 번들에 메타데이터가 없습니다. 전역 변수: ${e}`),{identifier:s,locales:["ko","en"],name:{ko:s,en:s},description:{ko:"",en:""},version:"1.0.0",type:"admin"})}catch(e){return Qe.error("템플릿 메타데이터 로드 실패",e),{identifier:s,locales:["ko","en"],name:{ko:s,en:s},description:{ko:"",en:""},version:"1.0.0",type:"admin"}}}function Hb(){const e=(typeof window<"u"?window.__templateApp:void 0)?.globalState?.appConfig?.supportedLocales,n=ae.templateMetadata?.locales;return{$locale:ae.locale,$locales:Array.isArray(e)&&e.length>0?e:n||["ko","en"],$templateLocales:n||["ko","en"],$templateId:ae.templateId}}async function tl(s){try{if(Qe.log("템플릿 엔진 초기화 시작",s),ae.isInitialized)throw new Error("템플릿 엔진이 이미 초기화되었습니다. destroyTemplate()을 먼저 호출하세요.");if(!s.templateId)throw new Vx;Zo=s.debug??!1,typeof window<"u"&&(window.G7Config||(window.G7Config={}),window.G7Config.debug=Zo),YT(),ae.templateId=s.templateId,ae.locale=s.locale||"ko",Qe.log("엔진 인스턴스 생성 중..."),ae.registry=Er.getInstance(),ae.bindingEngine=new $n,ae.translationEngine=Oa.getInstance(),s.cacheVersion!==void 0&&s.cacheVersion>0&&ae.translationEngine.setCacheVersion(s.cacheVersion),ae.translationContext={templateId:s.templateId,locale:ae.locale},ae.actionDispatcher=new jo({},ae.translationEngine,ae.translationContext),l_(ae.actionDispatcher),Qe.log("다국어 파일 로드 중...",s.templateId,ae.locale);const e="en",n=[ae.translationEngine.loadTranslations(s.templateId,ae.locale).then(()=>{Qe.log(`다국어 파일 로드 완료: ${ae.locale}`)}).catch(r=>{Qe.warn(`다국어 파일 로드 실패 (${ae.locale}):`,r instanceof Error?r.message:r)})];ae.locale!==e&&n.push(ae.translationEngine.loadTranslations(s.templateId,e).then(()=>{Qe.log(`폴백 다국어 파일 로드 완료: ${e}`)}).catch(r=>{Qe.warn(`폴백 다국어 파일 로드 실패 (${e}):`,r instanceof Error?r.message:r)})),await Promise.allSettled(n),Qe.log("템플릿 메타데이터 로드 중...",s.templateId),ae.templateMetadata=await QT(s.templateId),ae.isInitialized=!0,Qe.log("템플릿 엔진 초기화 완료 (ComponentRegistry는 별도 로드)")}catch(e){throw Qe.error("템플릿 엔진 초기화 실패",e),ae.isInitialized=!1,ae.registry=null,ae.bindingEngine=null,ae.translationEngine=null,ae.actionDispatcher=null,e}}let ks=null;const zb="g7-layout-editor-bundle";function gh(){const s=window.G7Core;return s?.__LayoutEditorChrome?Promise.resolve(s.__LayoutEditorChrome):ks||(ks=(async()=>{const e=window.G7Config?.coreEditorAsset||"/build/core/layout-editor.min.js";document.getElementById(zb)?.remove(),window.G7Core=window.G7Core||{},window.G7Core.__onChromeReady=()=>{};try{await Cr(e,{id:zb},{label:"layout-editor bundle"})}catch(r){throw ks=null,Qe.error(`편집기 번들 로드 실패 (${e})`,r),new Error("편집기를 불러오지 못했습니다. 네트워크 상태를 확인한 뒤 다시 시도해 주세요.")}const n=window.G7Core?.__LayoutEditorChrome;if(!n)throw ks=null,Qe.error(`편집기 번들 로드됨 — 그러나 __LayoutEditorChrome 미노출 (${e})`),new Error("편집기를 불러왔으나 초기화하지 못했습니다. 페이지를 새로고침해 주세요.");return n})(),ks)}async function Rs(s){try{if(Qe.log("템플릿 렌더링 시작",s),!ae.isInitialized)throw new Error("템플릿 엔진이 초기화되지 않았습니다. initTemplateEngine()을 먼저 호출하세요.");if(!s.containerId)throw new Error("containerId는 필수입니다.");if(!s.layoutJson)throw new Error("layoutJson은 필수입니다.");const e=document.getElementById(s.containerId);if(!e)throw new Error(`컨테이너를 찾을 수 없습니다: #${s.containerId}`);const n=Hb();if(ae.containerId=s.containerId,ae.currentLayoutJson=s.layoutJson,ae.currentDataContext={...n,...s.dataContext||{}},ae.translationContext=s.translationContext||{templateId:ae.templateId||"",locale:ae.locale},ae.reactRoot||(Qe.log("React Root 생성"),ae.reactRoot=Ld.createRoot(e)),typeof window<"u"){const l=kb(window.location.pathname);if(l){Qe.log("레이아웃 편집기 모드 진입",l);let c;try{c=await gh()}catch(f){Qe.error("레이아웃 편집기 번들 로드 실패",f),uh.render(s.containerId,{title:"레이아웃 편집기 로드 실패",message:f instanceof Error?f.message:"편집기 번들을 불러오지 못했습니다.",icon:"fas fa-triangle-exclamation",showStack:!1,showReloadButton:!0,debug:Zo});return}const d=Xe.createElement(c,{templateIdentifier:l.templateIdentifier,initialLocale:ae.locale});ae.reactRoot.render(Xe.createElement(Uc,{translationEngine:ae.translationEngine,translationContext:ae.translationContext,children:Xe.createElement(Mf,{children:Xe.createElement(Tc,{children:Xe.createElement(sh,{children:d})})})}));return}}const r=s.layoutJson.components||[];if(r.length===0){Qe.warn("렌더링할 컴포넌트가 없습니다.");return}const i=s.layoutJson.modals||[];if(Qe.log("modals 배열:",i),Qe.log("modals 개수:",i.length),Qe.log("DynamicRenderer로 렌더링 시작"),ae.reactRoot.render(Xe.createElement(Uc,{translationEngine:ae.translationEngine,translationContext:ae.translationContext},Xe.createElement(Mf,null,Xe.createElement(Tc,null,Xe.createElement(sh,null,[...r.map((l,c)=>{const d=ae.currentLayoutJson?.layout_name||"";return Xe.createElement(Vr,{key:d&&!l._fromBase?`${l.id}_${d}`:l.id,componentDef:l,dataContext:ae.currentDataContext,translationContext:ae.translationContext,registry:ae.registry,bindingEngine:ae.bindingEngine,translationEngine:ae.translationEngine,actionDispatcher:ae.actionDispatcher,isRootRenderer:c===0,layoutKey:d})}),Xe.createElement(yy,{key:"__modal_parent_context_provider"},i.map(l=>{const c=ae.currentDataContext._global?.modalStack||[],f=c.includes(l.id)||ae.currentDataContext._global?.activeModal===l.id,h=c.indexOf(l.id),m=h>=0?50+h:50,y=window.__g7LayoutContextStack||[],S=y[y.length-1]?.dataContext,_=Xe.createElement(Vr,{key:`modal_${l.id}_renderer`,componentDef:{...l,props:{...l.props,isOpen:f,style:{...l.props?.style,zIndex:m},onClose:ae.actionDispatcher?.createHandler({type:"click",handler:"closeModal"},ae.currentDataContext)}},dataContext:ae.currentDataContext,translationContext:ae.translationContext,registry:ae.registry,bindingEngine:ae.bindingEngine,translationEngine:ae.translationEngine,actionDispatcher:ae.actionDispatcher,parentDataContext:S});return l.data_sources&&l.data_sources.length>0?Xe.createElement(xb,{key:`modal_${l.id}`,isOpen:f,modalId:l.id,dataSources:l.data_sources,dataContext:ae.currentDataContext,globalStateUpdater:ae.actionDispatcher?.getGlobalStateUpdater(),bindingEngine:ae.bindingEngine,debug:Zo,children:_}):_}))]))))),Qe.log("템플릿 렌더링 완료"),el.length>0){Qe.log(`대기 중인 데이터 업데이트 ${el.length}건 적용`);const l=[...el];el.length=0;for(const{data:c,options:d}of l)Ds(c,d)}}catch(e){throw Qe.error("템플릿 렌더링 실패",e),e}}function Ds(s,e){try{if(Qe.log("템플릿 데이터 업데이트 시작",Object.keys(s)),!ae.isInitialized)throw new Error("템플릿 엔진이 초기화되지 않았습니다.");if(!ae.reactRoot||!ae.currentLayoutJson){Qe.log("React 미준비 - 데이터 업데이트 큐잉:",Object.keys(s)),el.push({data:s,options:e});return}const n=Hb(),r={...ae.currentDataContext._global||{},...s._global||{}},i=g_(ae.currentDataContext._localInit,s._localInit);ae.currentDataContext={...n,...ae.currentDataContext,...s,_global:r,_localInit:i},r._local!==void 0&&(ae.currentDataContext._local=r._local),r._computed!==void 0&&(ae.currentDataContext._computed=r._computed);const l=ae.currentLayoutJson.components||[],c=ae.currentLayoutJson.modals||[];Qe.log("updateTemplateData - modals 배열:",c),Qe.log("updateTemplateData - modals 개수:",c.length),Qe.log("updateTemplateData - activeModal:",ae.currentDataContext._global?.activeModal);const d=()=>{ae.reactRoot.render(Xe.createElement(Uc,{translationEngine:ae.translationEngine,translationContext:ae.translationContext},Xe.createElement(Mf,null,Xe.createElement(Tc,null,Xe.createElement(sh,null,[...l.map((f,h)=>{const m=ae.currentLayoutJson?.layout_name||"";return Xe.createElement(Vr,{key:m&&!f._fromBase?`${f.id}_${m}`:f.id,componentDef:f,dataContext:ae.currentDataContext,translationContext:ae.translationContext,registry:ae.registry,bindingEngine:ae.bindingEngine,translationEngine:ae.translationEngine,actionDispatcher:ae.actionDispatcher,isRootRenderer:h===0,layoutKey:m})}),Xe.createElement(yy,{key:"__modal_parent_context_provider_update"},c.map(f=>{const h=ae.currentDataContext._global?.modalStack||[],y=h.includes(f.id)||ae.currentDataContext._global?.activeModal===f.id,v=h.indexOf(f.id),S=v>=0?50+v:50,_=window.__g7LayoutContextStack||[],A=_[_.length-1]?.dataContext,D=Xe.createElement(Vr,{key:`modal_${f.id}_renderer`,componentDef:{...f,props:{...f.props,isOpen:y,style:{...f.props?.style,zIndex:S},onClose:ae.actionDispatcher?.createHandler({type:"click",handler:"closeModal"},ae.currentDataContext)}},dataContext:ae.currentDataContext,translationContext:ae.translationContext,registry:ae.registry,bindingEngine:ae.bindingEngine,translationEngine:ae.translationEngine,actionDispatcher:ae.actionDispatcher,parentDataContext:A});return f.data_sources&&f.data_sources.length>0?Xe.createElement(xb,{key:`modal_${f.id}`,isOpen:y,modalId:f.id,dataSources:f.data_sources,dataContext:ae.currentDataContext,globalStateUpdater:ae.actionDispatcher?.getGlobalStateUpdater(),bindingEngine:ae.bindingEngine,debug:Zo,children:D}):D}))])))))};e?.sync?(Qe.log("재렌더링 시작 (sync mode - 즉시 렌더링)"),d()):(Qe.log("재렌더링 시작 (with startTransition)"),I.startTransition(()=>{d()})),Qe.log("템플릿 데이터 업데이트 완료")}catch(n){throw Qe.error("템플릿 데이터 업데이트 실패",n),n}}function Xc(){try{Qe.log("템플릿 정리 시작"),ae.reactRoot&&(Qe.log("React Root 언마운트"),ae.reactRoot.unmount(),ae.reactRoot=null),ae.templateId=null,ae.locale="ko",ae.isInitialized=!1,ae.containerId=null,ae.currentLayoutJson=null,ae.currentDataContext={},ae.translationContext={templateId:"",locale:"ko"},ae.registry=null,ae.bindingEngine=null,ae.translationEngine=null,ae.actionDispatcher=null,ae.templateMetadata=null,Qe.log("템플릿 정리 완료")}catch(s){throw Qe.error("템플릿 정리 실패",s),s}}function la(){return Object.freeze({...ae})}function Pb(){return ae.actionDispatcher}const nl={initTemplateEngine:tl,renderTemplate:Rs,updateTemplateData:Ds,destroyTemplate:Xc,getState:la};typeof window<"u"&&(window.G7Core||(window.G7Core={}),window.G7Core.TemplateEngine=nl,VT({getState:()=>({translationEngine:ae.translationEngine,translationContext:ae.translationContext,bindingEngine:ae.bindingEngine,actionDispatcher:ae.actionDispatcher,templateMetadata:ae.templateMetadata}),transitionManager:Gr,responsiveManager:Ci,webSocketManager:Gc}),Qe.log("전역 객체 window.G7Core.TemplateEngine에 노출됨"));const Wr=Object.freeze(Object.defineProperty({__proto__:null,DataSourceManager:Dr,LayoutLoader:ah,TemplateApp:Xo,TemplateEngine:nl,default:nl,destroyTemplate:Xc,getActionDispatcher:Pb,getState:la,initTemplateApp:ph,initTemplateEngine:tl,loadLayoutEditorBundle:gh,renderTemplate:Rs,updateTemplateData:Ds},Symbol.toStringTag,{value:"Module"}));tn.DataSourceManager=Dr,tn.LayoutLoader=ah,tn.TemplateApp=Xo,tn.TemplateEngine=nl,tn.default=nl,tn.destroyTemplate=Xc,tn.getActionDispatcher=Pb,tn.getState=la,tn.initTemplateApp=ph,tn.initTemplateEngine=tl,tn.loadLayoutEditorBundle=gh,tn.renderTemplate=Rs,tn.updateTemplateData=Ds,Object.defineProperties(tn,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}})})(this.G7Core=this.G7Core||{}); + `}static renderFromError(e,n,r,i=!1,l,c,d){let f,h;if(n.userMessageKey.startsWith("$t:"))if(c&&l)try{f=c.translate(n.userMessageKey,l)}catch{f=n.userMessageKey.replace("$t:","")}else f=n.userMessageKey.replace("$t:","");else f=n.userMessageKey;if(d){const m=d;m.details?.apiMessage?h=m.details.apiMessage:m.response?.data?.message&&(h=m.response.data.message)}this.render(e,{title:r,message:f,detailMessage:h,icon:n.icon,showStack:n.showStack,stackTrace:n.stack,showReloadButton:n.recoverable,debug:i})}static escapeHtml(e){if(!e)return"";const n=document.createElement("div");return n.textContent=e,n.innerHTML}}const _s=pt("Router");class Kx{constructor(e){M(this,"routes",[]);M(this,"templateIdentifier");M(this,"eventHandlers",new Map);M(this,"pendingNavigation",null);M(this,"isNavigating",!1);M(this,"handlePopState",()=>{this.navigateToCurrentPath()});this.templateIdentifier=e,this.initPopstateListener()}initPopstateListener(){window.addEventListener("popstate",this.handlePopState)}on(e,n){this.eventHandlers.has(e)||this.eventHandlers.set(e,[]),this.eventHandlers.get(e).push(n)}async emit(e,...n){const r=this.eventHandlers.get(e);r&&await Promise.all(r.map(i=>i(...n)))}async loadRoutes(e){try{const n=e!==void 0&&e>0?e:null,r=await fetch(Br(`/api/templates/${this.templateIdentifier}/routes`,"json",n));if(!r.ok)throw new Error(`Failed to load routes: ${r.statusText}`);const i=await r.json();if(!i.success)throw new Error("Failed to load routes from API");if(i.data&&Array.isArray(i.data.routes))this.routes=i.data.routes,_s.log(`Loaded ${this.routes.length} routes${e?` (v=${e})`:""}`);else throw new Error("Invalid routes data format")}catch(n){throw _s.error("Error loading routes:",n),n}}setRoutes(e){this.routes=e,_s.log(`Set ${this.routes.length} routes`)}match(e){const n=this.normalizePathname(e);for(const r of this.routes){const i=this.matchPattern(r.path,n);if(i!==null)return{route:r,params:i}}return null}normalizePathname(e){return e.length>1&&e.endsWith("/")?e.replace(/\/+$/,"")||"/":e}matchPattern(e,n){const r=[];let i=e.replace(/:([^/]+)/g,(f,h)=>(r.push(h),"([^/]+)"));i.startsWith("*/")&&(i="(?:/[^/]+)?"+i.slice(1)),i=i.replace(/\//g,"\\/");const l=new RegExp(`^${i}$`),c=n.match(l);if(!c)return null;const d={};return r.forEach((f,h)=>{d[f]=c[h+1]}),d}getRoutes(){return[...this.routes]}getAuthType(e,n){return e.auth_type?e.auth_type:n.startsWith("/admin")?"admin":"user"}async navigateToCurrentPath(){const e=window.location.pathname,n=window.location.search,r=this.match(e);if(!r){_s.warn(`No route matched for path: ${e}`),this.emit("routeNotFound",e);return}if(r.route.redirect){_s.log(`Redirecting from ${e} to ${r.route.redirect}`),this.navigate(r.route.redirect);return}if(r.route.auth_required){const c=this.getAuthType(r.route,e),d=Xn.getInstance();let f=d.isAuthenticated()&&d.getAuthType()===c;if(f||(f=await d.checkAuth(c)),!f){const h=d.getLoginRedirectUrl(c,e+n);_s.log(`Not authenticated, redirecting to: ${h}`),window.location.href=h;return}}const i=new URLSearchParams(n),l={};for(const c of i.keys()){if(c in l)continue;const d=i.getAll(c);d.length>1?l[c]=d:d.length===1&&(c.endsWith("[]")?l[c]=d:l[c]=d[0])}await this.emit("routeChange",{path:e,layout:r.route.layout,endpoint:r.route.endpoint,params:{...r.route.params||{},...r.params},query:l,auth_required:r.route.auth_required,auth_type:r.route.auth_type,meta:r.route.meta})}navigate(e){if(this.isNavigating){this.pendingNavigation=e;return}this.executeNavigation(e)}async executeNavigation(e){this.isNavigating=!0,this.pendingNavigation=null;try{window.history.pushState({},"",e),await this.navigateToCurrentPath()}finally{if(this.isNavigating=!1,this.pendingNavigation){const n=this.pendingNavigation;this.executeNavigation(n)}}}}const Wx=/^\/admin\/layout-editor\/([^/?#]+)\/?$/;function ch(s){const e=Wx.exec(s);return e?{templateIdentifier:e[1]}:null}const Wt=pt("ErrorPageHandler");class Yx{constructor(e){M(this,"templateId");M(this,"layoutLoader");M(this,"locale");M(this,"debug");M(this,"renderFunction");M(this,"dataSourceManager");M(this,"globalState");M(this,"errorConfig",null);M(this,"configLoaded",!1);this.templateId=e.templateId,this.layoutLoader=e.layoutLoader,this.locale=e.locale,this.debug=e.debug,this.renderFunction=e.renderFunction,this.dataSourceManager=e.dataSourceManager,this.globalState=e.globalState||{}}async loadConfig(){if(this.configLoaded)return this.errorConfig;try{const e=await fetch(Br(`/api/templates/${this.templateId}/config`,"json"));if(!e.ok)return this.debug&&Wt.warn("Failed to load template config:",e.statusText),this.configLoaded=!0,null;const n=await e.json();if(!n.success||!n.data)return this.debug&&Wt.warn("Invalid template config response"),this.configLoaded=!0,null;const r=n.data;return!r.error_config||!r.error_config.layouts?(this.debug&&Wt.warn("No error_config found in template.json"),this.configLoaded=!0,null):(this.errorConfig=r.error_config,this.configLoaded=!0,this.debug&&Wt.log("Error config loaded:",this.errorConfig),this.errorConfig)}catch(e){return Wt.error("Failed to load error config:",e),this.configLoaded=!0,null}}async renderError(e,n="app"){try{const r=await this.loadConfig();if(!r)return this.debug&&Wt.warn("No error config available, cannot render error page"),!1;const i=r.layouts[e]||r.layouts[String(e)];if(!i)return this.debug&&Wt.warn(`No layout defined for error code: ${e}`),!1;this.debug&&Wt.log(`Loading error layout: ${i} for code: ${e}`);const l=await this.layoutLoader.loadLayout(this.templateId,i);if(!l)return this.debug&&Wt.error(`Failed to load error layout: ${i}`),!1;this.debug&&Wt.log("Error layout loaded:",l);let c={};const d=l.data_sources||[];if(d.length>0){this.debug&&Wt.log("Fetching data sources:",d.map(S=>S.id));const m=d.filter(S=>S.loading_strategy==="blocking"),y=d.filter(S=>!S.loading_strategy||S.loading_strategy==="progressive"),v=[...m,...y];if(v.length>0)try{c=await this.dataSourceManager.fetchDataSources(v,{},new URLSearchParams),this.debug&&Wt.log("Data sources fetched:",Object.keys(c))}catch(S){Wt.error("Failed to fetch data sources:",S)}}const f={};d.length>0&&(this.processInitOptions(d,c,f),this.debug&&Wt.log("initOptions processed:",{globalKeys:Object.keys(this.globalState),localKeys:Object.keys(f)}));const h={...c,errorCode:e,_global:{...this.globalState},_local:{...f}};return await this.renderFunction({containerId:n,layoutJson:l,dataContext:h,translationContext:{templateId:this.templateId,locale:this.locale}}),this.debug&&Wt.log(`Error page ${e} rendered successfully`),!0}catch(r){return Wt.error(`Failed to render error page ${e}:`,r),!1}}processInitOptions(e,n,r){for(const i of e){const l=n[i.id];if(!l)continue;const c=l?.data??l;if(i.initLocal){if(typeof i.initLocal=="string")r[i.initLocal]=c,Wt.log(`initLocal: ${i.id}.data -> _local.${i.initLocal}`);else if(typeof i.initLocal=="object"&&"key"in i.initLocal){const{key:d,path:f}=i.initLocal;r[d]=f?this.getValueByPath(c,f):c,Wt.log(`initLocal: ${i.id}.data${f?"."+f:""} -> _local.${d}`)}}if(i.initGlobal){const d=Array.isArray(i.initGlobal)?i.initGlobal:[i.initGlobal];for(const f of d)if(typeof f=="string")this.globalState[f]=c,Wt.log(`initGlobal: ${i.id}.data -> _global.${f}`);else if(typeof f=="object"&&f!==null&&"key"in f){const{key:h,path:m}=f;this.globalState[h]=m?this.getValueByPath(c,m):c,Wt.log(`initGlobal: ${i.id}.data${m?"."+m:""} -> _global.${h}`)}}}}getValueByPath(e,n){return n.split(".").reduce((r,i)=>r?.[i],e)}updateGlobalState(e){this.globalState={...this.globalState,...e}}updateLocale(e){this.locale=e,this.debug&&Wt.log("Locale updated:",e)}isConfigLoaded(){return this.configLoaded}async hasErrorLayout(e){const n=await this.loadConfig();return n?!!(n.layouts[e]||n.layouts[String(e)]):!1}clearConfigCache(){this.errorConfig=null,this.configLoaded=!1,this.debug&&Wt.log("Config cache cleared")}}const Vc=pt("AssetFailureNotice"),Rb="g7-asset-failure-notice",Xx="g7-system-banners",Kr=new Map;let As=!1,Kc=!1,Wo=null;function Di(s,e,n){try{const r=window?.G7Core?.t;if(typeof r=="function"){const i=r(s,n);if(typeof i=="string"&&i!==s&&!i.startsWith("core.assets."))return i}}catch{}return Jx(e,n)}function Jx(s,e){return e?Object.entries(e).reduce((n,[r,i])=>n.split("{"+r+"}").join(String(i)),s):s}function xs(s){!s||!s.id||(Kr.set(s.id,s),Kc=!1,Vc.warn(`Asset failure notified: ${s.id} (${s.label})`),Ts())}function Db(s){Kr.delete(s)&&(Vc.log(`Asset failure cleared: ${s}`),Ts())}function Ob(){Kr.size!==0&&(Kr.clear(),Ts())}function Qx(){return Array.from(Kr.values())}function Lb(s,e,n){return Di(s,e,n)}async function $b(){if(As)return;As=!0,Kc=!1,Ts();const s=Array.from(Kr.values()).filter(n=>typeof n.retry=="function");let e=!1;for(const n of s)try{await n.retry(),Kr.delete(n.id)}catch(r){e=!0,Vc.warn(`Asset retry failed: ${n.id}`,r)}As=!1,Kc=e,Ts()}function Zx(){try{return document.documentElement.classList.contains("dark")}catch{return!1}}function Ts(){if(typeof document>"u"||!document.body)return;const s=document.getElementById(Rb);if(Kr.size===0){s?.remove(),sT();return}const e=s??eT();tT(e),e.replaceChildren(nT()),iT()}function eT(){const s=document.createElement("div");return s.id=Rb,s.setAttribute("role","alert"),s.setAttribute("aria-live","polite"),document.body.appendChild(s),s}function tT(s){const n=document.getElementById(Xx)?.offsetHeight??0;s.style.cssText=["position:fixed",`top:${n}px`,"left:0","right:0","z-index:9998","display:flex","justify-content:center","pointer-events:none"].join(";")}function nT(){const s=Zx(),e=Array.from(Kr.values()),n=typeof window<"u"&&window.innerWidth<768,r=document.createElement("div");return r.style.cssText=["pointer-events:auto","box-sizing:border-box","width:100%","max-width:960px","margin:8px","padding:12px 16px","border-radius:8px",`border:1px solid ${s?"#78350f":"#fcd34d"}`,`background:${s?"#451a03":"#fffbeb"}`,`color:${s?"#fef3c7":"#78350f"}`,"box-shadow:0 4px 12px rgba(0,0,0,0.12)","font-size:14px","line-height:1.5","display:flex",n?"flex-direction:column":"flex-direction:row",n?"align-items:stretch":"align-items:center","gap:12px"].join(";"),r.style.background=s?"#451a03":"#fffbeb",r.style.color=s?"#fef3c7":"#78350f",r.style.borderColor=s?"#78350f":"#fcd34d",r.appendChild(rT(e,n)),r.appendChild(aT(e,n,s)),r}function rT(s,e){const n=document.createElement("div");n.style.cssText=["flex:1 1 auto","min-width:0","display:flex","gap:8px"].join(";");const r=document.createElement("span");r.setAttribute("aria-hidden","true"),r.textContent="⚠",r.style.cssText="flex:0 0 auto;font-size:16px;line-height:1.4",n.appendChild(r);const i=document.createElement("div");if(i.style.cssText="flex:1 1 auto;min-width:0",s.length===1){const l=s[0],c=document.createElement("div");c.setAttribute("data-asset-failure-id",l.id),c.textContent=l.message??Di("core.assets.load_failed","{label}을(를) 불러오지 못했습니다.",{label:l.label}),i.appendChild(c)}else{const l=document.createElement("div");l.style.cssText="font-weight:600",l.textContent=Di("core.assets.load_failed_multiple","{count}개 항목을 불러오지 못했습니다.",{count:s.length}),i.appendChild(l);const c=document.createElement("div");c.style.cssText="margin-top:2px;opacity:0.9;word-break:break-word",c.textContent=s.map(d=>d.label).join(", "),i.appendChild(c)}if(Kc){const l=document.createElement("div");l.style.cssText="margin-top:4px;font-weight:600",l.textContent=Di("core.assets.retry_failed","다시 시도했지만 실패했습니다. 잠시 후 다시 시도해 주세요."),i.appendChild(l)}return n.appendChild(i),e&&(n.style.alignItems="flex-start"),n}function aT(s,e,n){const r=document.createElement("div");if(r.style.cssText=["flex:0 0 auto","display:flex","gap:8px",e?"width:100%":"","align-items:center"].filter(Boolean).join(";"),s.some(c=>typeof c.retry=="function")){const c=document.createElement("button");c.type="button",c.dataset.action="retry",c.disabled=As,c.textContent=As?Di("core.assets.retrying","다시 시도 중..."):Di("core.assets.retry","다시 시도"),c.style.cssText=[e?"flex:1 1 auto":"","padding:6px 14px","border-radius:6px","font-size:14px","font-weight:600","cursor:pointer",`border:1px solid ${n?"#b45309":"#d97706"}`,`background:${n?"#b45309":"#f59e0b"}`,`color:${n?"#fffbeb":"#451a03"}`,As?"opacity:0.6":""].filter(Boolean).join(";"),c.addEventListener("click",()=>{$b()}),r.appendChild(c)}const l=document.createElement("button");return l.type="button",l.dataset.action="close",l.setAttribute("aria-label",Di("core.assets.close","닫기")),l.textContent="✕",l.style.cssText=["padding:6px 10px","border-radius:6px","font-size:14px","cursor:pointer","border:1px solid transparent","background:transparent",`color:${n?"#fef3c7":"#78350f"}`].join(";"),l.addEventListener("click",()=>{Ob()}),r.appendChild(l),r}function iT(){Wo||typeof MutationObserver>"u"||(Wo=new MutationObserver(()=>{Kr.size>0&&Ts()}),Wo.observe(document.documentElement,{attributes:!0,attributeFilter:["class"]}))}function sT(){Wo?.disconnect(),Wo=null}function oT(){const s=window,e=r=>({id:String(r?.id??""),label:String(r?.label??r?.url??"asset"),retry:typeof r?.retry=="function"?r.retry:void 0}),n=Array.isArray(s.__g7ExternalAssetFailures)?s.__g7ExternalAssetFailures:[];n.forEach(r=>{r?.id&&xs(e(r))}),s.__g7ExternalAssetSink=r=>{r?.id&&xs(e(r))},Vc.log(`템플릿 externals 실패 대기열 ${n.length}건 반영`)}const Rt=pt("ModuleAssetLoader");function lT(s){return s==="plugin"?Lb("core.assets.plugin_styles","플러그인 스타일"):Lb("core.assets.module_styles","모듈 스타일")}class cT{constructor(){M(this,"loadedAssets",new Map);M(this,"loadingPromises",new Map);M(this,"failedJsAssets",new Set);M(this,"failedCssAssets",new Set)}getFailedCssAssets(){return[...this.failedCssAssets]}hasFailedJsAssets(){return this.failedJsAssets.size>0}getFailedJsAssets(){return[...this.failedJsAssets]}async loadActiveExtensionAssets(e){if(!e||e.length===0){Rt.log("No module assets to load");return}const n=[...e].sort((d,f)=>d.priority-f.priority);Rt.log("Loading module assets:",n.map(d=>d.identifier));const r=n.filter(d=>d.css).map(d=>this.loadCSS(d.identifier,d.css)),i=n.filter(d=>d.js).map(d=>this.loadJS(d.identifier,d.js)),l=await Promise.allSettled([...r,...i]),c=l.filter(d=>d.status==="rejected");if(c.length>0){Rt.warn(`Some module assets failed to load (${c.length}/${l.length}); continuing with the rest`,this.getFailedJsAssets());return}Rt.log("All module assets loaded successfully")}async loadBundle(e,n,r){const i=[];if(r&&i.push(this.loadBundleCss(e,r)),n&&i.push(this.loadBundleJs(e,n)),i.length===0){Rt.log(`No bundle assets to load for: ${e}`);return}await Promise.all(i)}async loadCustomAssets(){const e=uT();if(e.length!==0)for(const n of e){const r=`g7-custom-${n.type}-${dT(n.id)}`;if(document.getElementById(r))continue;const i=`${n.type==="style"?"custom CSS":"custom JS"}: ${n.id}`,l=(d,f)=>n.type==="style"?ia(Ln(d),{id:r},{label:i,retries:f}):_r(Ln(d),{id:r},{label:i,retries:f}),c=Nd(n.url);try{if(c!==null)try{await l(n.url,0)}catch{Rt.warn(`Custom asset static miss, falling back to API: ${n.id} (${n.url} -> ${c})`),await l(c)}else await l(n.url);Rt.log(`Custom asset loaded: ${n.id}`)}catch(d){Rt.warn(`Failed to load custom asset: ${n.id} (${n.url})`,d),xs({id:`custom-asset:${n.id}`,label:n.id.split(":").pop()??n.id,retry:async()=>{document.getElementById(r)?.remove(),await l(c??n.url)}})}}}async loadBundleCss(e,n){const r=`ext-bundle-css-${e}`;if(document.getElementById(r)){Rt.log(`Bundle CSS already loaded: ${e}`);return}const i=Nd(n);try{if(i!==null)try{await ia(n,{id:r},{label:`bundle CSS: ${e}`,retries:0})}catch{Rt.warn(`Bundle CSS static miss, falling back to API: ${e} (${n} -> ${i})`),await ia(Ln(i),{id:r},{label:`bundle CSS: ${e}`})}else await ia(n,{id:r},{label:`bundle CSS: ${e}`});Rt.log(`Bundle CSS loaded: ${e}`);const l=document.getElementById(r);l&&this.registerLoadedAsset(`bundle-${e}`,{type:"css",element:l})}catch(l){this.surfaceCssFailure(`bundle-${e}`,lT(e),i!==null?Ln(i):n,l)}}async loadBundleJs(e,n){const r=`ext-bundle-js-${e}`;if(document.getElementById(r)){Rt.log(`Bundle JS already loaded: ${e}`);return}const i=this.loadingPromises.get(r);if(i)return Rt.log(`Bundle JS already loading: ${e}`),i;const l=Nd(n),d=(l!==null?_r(n,{id:r},{label:`bundle JS: ${e}`,retries:0}).catch(()=>(Rt.warn(`Bundle JS static miss, falling back to API: ${e} (${n} -> ${l})`),_r(Ln(l),{id:r},{label:`bundle JS: ${e}`}))):_r(n,{id:r},{label:`bundle JS: ${e}`})).then(()=>{Rt.log(`Bundle JS loaded: ${e}`);const f=document.getElementById(r);f&&this.registerLoadedAsset(`bundle-${e}`,{type:"js",element:f}),this.loadingPromises.delete(r)}).catch(f=>{throw Rt.warn(`Failed to load bundle JS: ${e} (${n})`,f),this.failedJsAssets.add(e),this.loadingPromises.delete(r),f});return this.loadingPromises.set(r,d),d}async loadCSS(e,n){const r=`module-css-${e}`;if(document.getElementById(r)){Rt.log(`CSS already loaded: ${e}`);return}const i=this.loadingPromises.get(r);if(i)return Rt.log(`CSS already loading: ${e}`),i;const l=ia(n,{id:r},{label:`CSS: ${e}`}).then(()=>{Rt.log(`CSS loaded: ${e}`);const c=document.getElementById(r);c&&this.registerLoadedAsset(e,{type:"css",element:c}),this.loadingPromises.delete(r)}).catch(c=>{this.loadingPromises.delete(r),this.surfaceCssFailure(e,e,n,c)});return this.loadingPromises.set(r,l),l}surfaceCssFailure(e,n,r,i){Rt.warn(`Failed to load CSS after retries: ${e} (${r})`,i),this.failedCssAssets.add(e),xs({id:`ext-css:${e}`,label:n,retry:async()=>{document.getElementById(`module-css-${e}`)?.remove(),document.getElementById(`ext-bundle-css-${e.replace(/^bundle-/,"")}`)?.remove(),await ia(r,{},{label:`CSS retry: ${e}`}),this.failedCssAssets.delete(e)}})}async loadJS(e,n){const r=`module-js-${e}`;if(document.getElementById(r)){Rt.log(`JS already loaded: ${e}`);return}const i=this.loadingPromises.get(e);if(i)return Rt.log(`JS already loading: ${e}`),i;const l=_r(n,{id:r},{label:`JS: ${e}`}).then(()=>{Rt.log(`JS loaded: ${e}`);const c=document.getElementById(r);c&&this.registerLoadedAsset(e,{type:"js",element:c}),this.loadingPromises.delete(e)}).catch(c=>{throw Rt.warn(`Failed to load JS: ${e} (${n})`,c),this.failedJsAssets.add(e),this.loadingPromises.delete(e),c});return this.loadingPromises.set(e,l),l}registerLoadedAsset(e,n){const r=this.loadedAssets.get(e)||[];r.push(n),this.loadedAssets.set(e,r)}unloadExtensionAsset(e){const n=this.loadedAssets.get(e);if(!n||n.length===0){Rt.log(`No assets to unload for: ${e}`);return}n.forEach(r=>{r.element.parentNode&&(r.element.parentNode.removeChild(r.element),Rt.log(`${r.type.toUpperCase()} unloaded: ${e}`))}),this.loadedAssets.delete(e),Rt.log(`All assets unloaded for: ${e}`)}unloadAllAssets(){Array.from(this.loadedAssets.keys()).forEach(n=>{this.unloadExtensionAsset(n)}),Rt.log("All module assets unloaded")}isLoaded(e){return this.loadedAssets.has(e)}getLoadedModules(){return Array.from(this.loadedAssets.keys())}}let uh=null;function uT(){const s=window?.G7Config?.customAssets;return Array.isArray(s)?s.filter(e=>e&&typeof e.id=="string"&&typeof e.url=="string"&&(e.type==="style"||e.type==="script")):[]}function dT(s){return s.replace(/[^A-Za-z0-9_-]/g,"-")}function dh(){return uh||(uh=new cT),uh}function fT(){if(typeof window>"u")return[];const s=window.G7Config;if(!s?.moduleAssets)return[];const e=[];for(const[n,r]of Object.entries(s.moduleAssets)){const i=r;e.push({identifier:n,js:i.js?Ln(i.js):i.js,css:i.css?Ln(i.css):i.css,priority:i.priority,external:i.external})}return e}function hT(){if(typeof window>"u")return null;const s=window.G7Config;if(!s?.bundleUrls)return null;const e=s.bundleUrls;return{moduleJs:e.moduleJs?Ln(e.moduleJs):e.moduleJs,moduleCss:e.moduleCss?Ln(e.moduleCss):e.moduleCss,pluginJs:e.pluginJs?Ln(e.pluginJs):e.pluginJs,pluginCss:e.pluginCss?Ln(e.pluginCss):e.pluginCss}}function pT(){if(typeof window>"u")return[];const s=window.G7Config;if(!s?.pluginAssets)return[];const e=[];for(const[n,r]of Object.entries(s.pluginAssets)){const i=r;e.push({identifier:n,js:i.js?Ln(i.js):i.js,css:i.css?Ln(i.css):i.css,priority:i.priority,external:i.external})}return e}const fh=pt("SystemBannerManager");class Wc{static show(e){this.banners.set(e.id,e),this.render(),fh.log(`Banner shown: ${e.id}`)}static hide(e){this.banners.delete(e)&&(this.render(),fh.log(`Banner hidden: ${e}`))}static hideAll(){this.banners.clear(),this.render(),fh.log("All banners hidden")}static detectLocale(){try{const n=window.G7Core;if(n?.locale?.current)return n.locale.current()}catch{}return(navigator.language||"ko").split("-")[0]}static resolveMessage(e,n){return typeof e=="string"?e:e[n]||e.en||e.ko||Object.values(e)[0]||""}static render(){if(typeof document>"u")return;let e=document.getElementById(this.containerId);if(this.banners.size===0){e&&(e.remove(),this.adjustAppPadding(0));return}e||(e=document.createElement("div"),e.id=this.containerId,e.style.cssText="position:fixed;top:0;left:0;right:0;z-index:99999;",document.body.prepend(e));const n=Array.from(this.banners.values()).sort((i,l)=>(i.order??0)-(l.order??0)),r=this.detectLocale();e.innerHTML=n.map(i=>{const l=this.resolveMessage(i.message,r),c=i.background||"#f59e0b",d=i.color||"white";return`
${l}
`}).join(""),requestAnimationFrame(()=>{e&&this.adjustAppPadding(e.offsetHeight)})}static adjustAppPadding(e){const n=document.getElementById("app");n&&(n.style.paddingTop=e>0?`${e}px`:"")}}M(Wc,"banners",new Map),M(Wc,"containerId","g7-system-banners");const U=pt("TemplateApp");function Mb(s){const e={};for(const n of s.keys()){if(n in e)continue;const r=s.getAll(n);r.length>1?e[n]=r:r.length===1&&(n.endsWith("[]")?e[n]=r:e[n]=r[0])}return e}const $r=class $r{constructor(e){M(this,"router",null);M(this,"layoutLoader",null);M(this,"errorPageHandler",null);M(this,"config");M(this,"globalState");M(this,"globalStateListeners",new Set);M(this,"currentRouteChangeId",0);M(this,"failedLayoutScripts",new Set);M(this,"currentDataSources",[]);M(this,"currentRawDataSources",[]);M(this,"currentRouteParams",{});M(this,"currentQueryParams",new URLSearchParams);M(this,"currentFetchedData",{});M(this,"currentLayoutName","");M(this,"templateErrorHandling",null);M(this,"currentWebSocketSubscriptions",[]);M(this,"extensionCacheVersion",0);M(this,"currentGlobalHeaders",[]);M(this,"transitionOverlayEl",null);M(this,"skeletonOverlayRoot",null);M(this,"skeletonOverlayContainer",null);M(this,"_spinnerState",null);M(this,"modalDataSources",new Map);this.globalState={sidebarOpen:!1},this.loadG7Config(),this.migrateLocaleStorage();const n=this.loadLocaleFromStorage(),r=n||e.locale||"ko";this.config={...e,locale:r},!n&&e.locale&&this.saveLocaleToStorage(e.locale)}loadG7Config(){if(typeof window<"u"&&window.G7Config){const e=window.G7Config;e.settings&&(this.globalState.settings=e.settings,U.log("Loaded settings from G7Config:",Object.keys(e.settings)),e.settings.upload&&(this.globalState.uploadSettings=e.settings.upload)),e.plugins&&(this.globalState.plugins=e.plugins,U.log("Loaded plugin settings from G7Config:",Object.keys(e.plugins))),e.modules&&(this.globalState.modules=e.modules,U.log("Loaded module settings from G7Config:",Object.keys(e.modules))),e.appConfig&&(this.globalState.appConfig=e.appConfig,U.log("Loaded appConfig from G7Config:",Object.keys(e.appConfig)))}}resolveRouteExpressions(e){const n=new $n,r={_global:this.globalState};return e.map(i=>{const l={...i};if(l.path&&l.path.includes("{{")){const c=l.path;l.path=n.resolveBindings(l.path,r),l.path=l.path.replace(/\/\/+/g,"/")||"/",l.path.includes("{{")&&(U.warn("Route expression resolution failed, using fallback:",c),l.path=c.replace(/\{\{[^}]+\}\}/g,"").replace(/\/\/+/g,"/")||"/")}if(l.redirect&&l.redirect.includes("{{")){const c=l.redirect;l.redirect=n.resolveBindings(l.redirect,r),l.redirect=l.redirect.replace(/\/\/+/g,"/")||"/",l.redirect.includes("{{")&&(U.warn("Route redirect expression resolution failed:",c),l.redirect=c.replace(/\{\{[^}]+\}\}/g,"").replace(/\/\/+/g,"/")||"/")}return l})}migrateLocaleStorage(){const e=["locale","g7_template_locale"];for(const n of e)try{const r=localStorage.getItem(n);r&&r!==localStorage.getItem($r.LOCALE_STORAGE_KEY)&&(localStorage.setItem($r.LOCALE_STORAGE_KEY,r),U.log(`Migrated locale from '${n}' to '${$r.LOCALE_STORAGE_KEY}'`)),localStorage.removeItem(n)}catch(r){U.warn(`Failed to migrate locale key '${n}':`,r)}}async init(){try{po.getInstance().setDebug(this.config.debug),U.log("Initializing with config:",this.config),w0();const e=Ar.getInstance(),n=Xn.getInstance(),r=typeof window<"u"&&Number(window.G7Config?.cache_version)||0,i=r>0?r:this.loadCacheVersionFromStorage()||0,[l,c,d,f,h]=await Promise.all([el({templateId:this.config.templateId,templateType:this.config.templateType,locale:this.config.locale,debug:this.config.debug,cacheVersion:i}),e.loadComponents(this.config.templateId,this.config.templateType,i),nc(i>0?go(`templates/${this.config.templateId}/routes.json`,i):null,Br(`/api/templates/${this.config.templateId}/routes`,"json",i>0?i:null),{label:"routes.json"}).then(A=>{if(!A.ok)throw new Error(`Failed to load routes: ${A.statusText}`);return A.json()}).then(A=>{if(!A.success)throw new Error("Failed to load routes from API");return A.data}).catch(A=>{throw U.error("Error loading routes:",A),A}),n.preloadAuth(this.config.templateType==="admin"?"admin":"user"),fetch(Br(`/api/templates/${this.config.templateId}/config`,"json")).then(A=>A.ok?A.json():null).then(A=>!A?.success||!A?.data?null:A.data).catch(A=>(U.warn("Error loading template config:",A),null))]);if(U.log("Template Engine initialized"),U.log("ComponentRegistry loaded"),U.log("Routes data loaded"),U.log("User info preloaded"),U.log("Template config loaded:",h),h?.cache_version!==void 0){const A=i>0?i:null;if(this.extensionCacheVersion=h.cache_version,this.saveCacheVersionToStorage(this.extensionCacheVersion),U.log("Extension cache version:",this.extensionCacheVersion),A!==null&&A!==this.extensionCacheVersion){U.log("Cache version changed, reloading routes...");const D=await nc(go(`templates/${this.config.templateId}/routes.json`,this.extensionCacheVersion),Br(`/api/templates/${this.config.templateId}/routes`,"json",this.extensionCacheVersion),{label:"routes.json (reload)"}).then(O=>{if(!O.ok)throw new Error(`Failed to reload routes: ${O.statusText}`);return O.json()}).then(O=>{if(!O.success)throw new Error("Failed to reload routes from API");return O.data});Array.isArray(D.routes)&&(D.routes=this.resolveRouteExpressions(D.routes),Object.assign(d,D),U.log("Routes reloaded with new cache version"));try{const{TranslationEngine:O}=await Promise.resolve().then(()=>Jg),k=O.getInstance();k.setCacheVersion(this.extensionCacheVersion);const L=this.config.locale||"ko",B="en";await k.loadTranslations(this.config.templateId,L,"/api",!0),L!==B&&await k.loadTranslations(this.config.templateId,B,"/api",!0),U.log("Translations reloaded with new cache version")}catch(O){U.error("Failed to reload translations:",O)}}}h?.errorHandling&&(this.templateErrorHandling=h.errorHandling,kr().setTemplateConfig(this.templateErrorHandling),U.log("Template errorHandling registered:",this.templateErrorHandling)),n.on("logout",()=>{U.log("User logged out")}),n.on("authStateChange",A=>{U.log("Auth state changed:",A)}),U.log("AuthManager event handlers registered");const m=Yn(),y=this.config.templateType==="admin"?"admin":"user",v=n.getConfig(y);m.setOnUnauthorized(()=>{U.log("Unauthorized - redirecting to login page"),m.removeToken();const A=window.location.pathname+window.location.search,D=n.getLoginRedirectUrl(y,A,"session_expired");window.location.href=D}),U.log("ApiClient onUnauthorized callback registered"),this.layoutLoader=new th(e),this.extensionCacheVersion>0&&this.layoutLoader.setCacheVersion(this.extensionCacheVersion),U.log("LayoutLoader initialized:",this.layoutLoader);const S=new Or({onUnauthorized:()=>{U.warn("Unauthorized request in error page")}});if(this.errorPageHandler=new Yx({templateId:this.config.templateId,layoutLoader:this.layoutLoader,locale:this.config.locale,debug:this.config.debug,renderFunction:Rs,dataSourceManager:S,globalState:this.globalState}),U.log("ErrorPageHandler initialized with DataSourceManager"),await this.handleServerError()){U.log("Server error handled, skipping normal initialization");return}if(this.router=new Kx(this.config.templateId),Array.isArray(d.routes)){const A=this.resolveRouteExpressions(d.routes);this.router.setRoutes(A)}else throw new Error("Invalid routes data format");U.log("Router initialized:",this.router),U.log("Routes set:",this.router.getRoutes());const{getActionDispatcher:_}=await Promise.resolve().then(()=>Wr),x=_();if(x&&(x.setDefaultContext({navigate:A=>this.router?.navigate(A)}),x.setGlobalStateUpdater((A,D)=>this.setGlobalState(A,D)),U.log("Navigate function and setGlobalState injected to ActionDispatcher")),await this.loadExtensionAssets(),this.reinitializeTemplateHandlers(),this.router.on("routeChange",A=>this.handleRouteChange(A)),this.router.on("routeNotFound",A=>this.handleRouteNotFound(A)),typeof window<"u"&&ch(window.location.pathname)){U.log("Layout editor mode detected — skipping router match"),await Rs({containerId:"app",layoutJson:{components:[]},dataContext:{},translationContext:{templateId:this.config.templateId,locale:this.config.locale}}),U.log("Template App initialized in layout editor mode");return}this.router.navigateToCurrentPath(),U.log("Template App initialized successfully")}catch(e){U.error("Initialization failed:",e),this.showInitError(e)}}async loadExtensionAssets(){try{const e=dh(),n=hT();if(!n){await this.loadExtensionAssetsIndividually(),await e.loadCustomAssets();return}await e.loadBundle("module",n.moduleJs,n.moduleCss),await e.loadBundle("plugin",n.pluginJs,n.pluginCss),await e.loadCustomAssets(),U.log("Extension bundle assets loaded successfully")}catch(e){U.warn("Failed to load extension assets:",e)}}async loadExtensionAssetsIndividually(){const e=dh(),n=fT();n.length>0&&(U.log("Loading module assets (individual fallback):",n.map(i=>i.identifier)),await e.loadActiveExtensionAssets(n));const r=pT();r.length>0&&(U.log("Loading plugin assets (individual fallback):",r.map(i=>i.identifier)),await e.loadActiveExtensionAssets(r))}async handleRouteChange(e){const n=++this.currentRouteChangeId;window.__g7ForcedLocalFields=void 0,window.__g7ActionContext=void 0,window.__g7PendingLocalState=void 0,window.__g7LastSetLocalSnapshot=void 0,window.__g7SetLocalOverrideKeys=void 0,window.__g7SequenceLocalSync=void 0,window.__g7AutoBindingPaths=new Map;try{if(U.log("Route changed:",e,"requestId:",n),!this.layoutLoader)throw new Error("LayoutLoader is not initialized");if(!e.layout)throw new Error("Route layout is not defined");let r=e.layout;const i=r==="__preview__";i&&e.params?.token&&(r=`__preview__/${e.params.token}`);const l=await this.layoutLoader.loadLayout(this.config.templateId,r);if(n!==this.currentRouteChangeId){U.log("Route change cancelled (newer request exists):",n);return}U.log("Layout loaded:",l);const c=kr();l.errorHandling?(c.setLayoutConfig(l.errorHandling),U.log("Layout errorHandling registered:",l.errorHandling)):c.clearLayoutConfig();const d=l.data_sources||[],f=e.query||{},h=new URLSearchParams;for(const[ee,me]of Object.entries(f))if(Array.isArray(me))for(const H of me)h.append(ee,H);else h.set(ee,me);const m={route:e.params||{},query:f,_global:this.globalState};l.scripts&&Array.isArray(l.scripts)&&await this.loadLayoutScripts(l.scripts,m);const{DataSourceManager:y,getActionDispatcher:v}=await Promise.resolve().then(()=>Wr),S=new y;if(i){const ee=v();ee&&ee.setPreviewMode(!0),this.setGlobalState({__isPreview:!0}),Wc.show({id:"preview-mode",message:{ko:"⚠ 미리보기 모드 — 페이지 이동이 비활성화됩니다",en:"⚠ Preview Mode — Navigation is disabled"},background:"linear-gradient(90deg, #f59e0b, #d97706)",color:"white"}),U.log("Preview mode activated")}else{const ee=v();ee?.isPreviewMode()&&(ee.setPreviewMode(!1),Wc.hide("preview-mode"))}if(this.currentGlobalHeaders=l.globalHeaders||[],this.currentGlobalHeaders.length>0){S.setGlobalHeaders(this.currentGlobalHeaders);const ee=v();ee&&ee.setGlobalHeaders(this.currentGlobalHeaders),U.log("globalHeaders set:",this.currentGlobalHeaders.map(me=>me.pattern))}if(l.named_actions&&Object.keys(l.named_actions).length>0){const ee=v();ee&&ee.setNamedActions(l.named_actions)}const _=S.filterByCondition(d,m);d.length!==_.length&&U.log("Data sources filtered by condition:",{before:d.map(ee=>ee.id),after:_.map(ee=>ee.id)});const x=_.filter(ee=>ee.loading_strategy==="blocking"),A=_.filter(ee=>(ee.loading_strategy||"progressive")!=="blocking"&&ee.type!=="websocket"),D=Array.isArray(l.transition_overlay?.wait_for)?l.transition_overlay.wait_for:[],O=D.length>0&&_.some(ee=>D.includes(ee.id)&&ee.type!=="websocket"&&(ee.loading_strategy||"progressive")!=="background");if((x.length>0||O)&&l.transition_overlay){const ee=typeof l.transition_overlay=="boolean"?{enabled:l.transition_overlay,style:"opaque"}:l.transition_overlay;ee.enabled&&ee.style==="skeleton"&&ee.skeleton?.component&&ee.target?this.renderSkeletonOverlay(ee.target,ee.skeleton,l,ee.fallback_target):ee.enabled&&ee.style==="spinner"&&ee.target&&this.renderSpinnerOverlay(ee.target,ee.spinner,ee.fallback_target)}let k={},L={},B={},q={};this.currentWebSocketSubscriptions.length>0&&(U.log("Unsubscribing previous WebSocket subscriptions:",this.currentWebSocketSubscriptions),S.unsubscribeWebSockets(this.currentWebSocketSubscriptions),this.currentWebSocketSubscriptions=[]),this.currentDataSources=_,this.currentRawDataSources=d,this.currentRouteParams=e.params||{},this.currentQueryParams=h,U.log(`handleRouteChange #${n} - queryObject:`,f),U.log(`handleRouteChange #${n} - queryParams:`,h.toString()),x.length>0&&(U.log("Fetching blocking data sources:",x.map(me=>me.id)),(await S.fetchDataSourcesWithResults(x,e.params||{},h)).forEach(me=>{if(me.state==="success"&&me.data!==void 0)k[me.id]=me.data;else if(me.state==="error"&&me.error){const H=me.error,T=H.response?.data?.message;L[me.id]={message:T||me.error.message,status:H.response?.status||H.status}}}),this.processInitOptions(x,k,B,q),this.currentFetchedData={...k},U.log("Blocking data loaded:",Object.keys(k)),Object.keys(L).length>0&&U.log("Data source errors:",L),Object.keys(B).length>0&&U.log("Local state init (blocking):",Object.keys(B)),Object.keys(q).length>0&&U.log("Isolated state init (blocking):",Object.keys(q)));const z=A.length>0;if(z&&(Gr.setPending(!0),U.log("Transition started before rendering")),n!==this.currentRouteChangeId){U.log("Route change cancelled after blocking data (newer request exists):",n);return}const{getState:W}=await Promise.resolve().then(()=>Wr),de=W().currentDataContext||{},_e=A.map(ee=>ee.id),Se={};if(z){_e.forEach(me=>{de[me]!==void 0?Se[me]=de[me]:Se[me]=void 0});const ee=Object.keys(Se).filter(me=>Se[me]!==void 0);ee.length>0&&U.log("Preserving previous progressive data:",ee),U.log("Progressive data sources initialized:",_e)}const je=l.defines||{},Fe={...Se,...e.params,...k,route:{...e.params||{},path:e.path},query:f,_global:{...this.globalState},_globalSetState:ee=>this.setGlobalState(ee),_dataSourceErrors:Object.keys(L).length>0?L:void 0,_localInit:Object.keys(B).length>0?B:void 0,_isolatedInit:Object.keys(q).length>0?q:void 0,_defines:Object.keys(je).length>0?je:void 0};if(l.computed&&Object.keys(l.computed).length>0){const ee=this.calculateComputed(l.computed,Fe);Object.keys(ee).length>0&&(Fe._computed=ee,this.globalState._computed=ee,U.log("Computed values calculated:",Object.keys(ee))),Fe._computedDefinitions=l.computed}const ze=l.layout_name||e.layout;this.currentLayoutName!==""&&this.currentLayoutName!==ze&&(U.log("_local reset due to layout change:",{from:this.currentLayoutName,to:ze}),this.globalState._local={},c_()),this.currentLayoutName=ze;const Q=l.initLocal||l.state;if(Q&&Object.keys(Q).length>0){this.globalState._local||(this.globalState._local={});for(const[ee,me]of Object.entries(Q))this.globalState._local[ee]===void 0&&(this.globalState._local[ee]=JSON.parse(JSON.stringify(me)));Fe._local={...this.globalState._local},U.log("initLocal applied to _local:",Object.keys(Q))}if(l.initGlobal&&Object.keys(l.initGlobal).length>0){for(const[ee,me]of Object.entries(l.initGlobal))this.globalState[ee]===void 0&&(this.globalState[ee]=JSON.parse(JSON.stringify(me)));U.log("initGlobal applied to _global:",Object.keys(l.initGlobal))}if(l.initIsolated&&Object.keys(l.initIsolated).length>0){for(const[ee,me]of Object.entries(l.initIsolated))q[ee]===void 0&&(q[ee]=JSON.parse(JSON.stringify(me)));U.log("initIsolated applied:",Object.keys(l.initIsolated))}Fe._global={...this.globalState,layoutWarnings:l.warnings||[]};const ue=l.initActions||l.init_actions;if(ue&&ue.length>0){await this.executeInitActions(ue,Fe),U.log("initActions executed before render");const ee=this.globalState;if(ee._local){if(Object.keys(B).length>0){for(const[me,H]of Object.entries(B))ee._local[me]!==void 0?ee._local[me]=this.deepMerge(ee._local[me],H):ee._local[me]=H;U.log("localInit merged into _local after initActions:",Object.keys(B))}Fe._local=ee._local,U.log("_local merged into dataContext:",ee._local)}if(Fe._global={...this.globalState,layoutWarnings:l.warnings||[]},U.log("_global merged into dataContext after initActions"),l.computed&&Object.keys(l.computed).length>0){const me=this.calculateComputed(l.computed,Fe);Object.keys(me).length>0&&(Fe._computed=me,this.globalState._computed=me,U.log("Computed values recalculated after init_actions:",Object.keys(me)))}if(Object.keys(this.currentFetchedData).length>0){for(const[me,H]of Object.entries(this.currentFetchedData))Fe[me]=H;U.log("Fetched data sources merged into dataContext after initActions:",Object.keys(this.currentFetchedData))}}if(l.transition_overlay&&!this.skeletonOverlayContainer&&this.showTransitionOverlay(l.transition_overlay,l),await Rs({containerId:"app",layoutJson:l,dataContext:Fe,translationContext:{templateId:this.config.templateId,locale:this.config.locale}}),U.log("Initial render complete with blocking data"),this.reattachSpinnerOverlay(),A.length>0){if(n!==this.currentRouteChangeId){U.log("Route change cancelled before progressive fetch (newer request exists):",n),Gr.setPending(!1),this.hideTransitionOverlay();return}U.log("Fetching progressive/background data sources:",A.map(H=>H.id));const{updateTemplateData:ee,getState:me}=await Promise.resolve().then(()=>Wr);try{const H=A.map(async T=>{try{const he=(await S.fetchDataSourcesWithResults([T],e.params||{},h,this.globalState))[0];if(!he){U.log(`Data source ${T.id} skipped (no fetch result)`);return}if(n!==this.currentRouteChangeId){U.log(`Data source ${T.id} fetch cancelled (newer request exists)`);return}if(he.state==="success"&&he.data!==void 0){U.log(`Progressive data source loaded: ${T.id}`),this.currentFetchedData[T.id]=he.data;const Ae={};this.processInitOptions([T],{[T.id]:he.data},Ae);const se=me();if(se.bindingEngine){const $e=[T.id];T.initGlobal&&$e.push("_global"),T.initLocal&&$e.push("_local"),se.bindingEngine.invalidateCacheByKeys($e)}const ge={[T.id]:he.data};Object.keys(Ae).length>0&&(ge._localInit=Ae,Object.assign(B,Ae)),T.initGlobal&&(ge._global={...this.globalState}),ee(ge)}else if(he.state==="error"&&he.error){const Ae=he.error,se=Ae.response?.data?.message;L[T.id]={message:se||he.error.message,status:Ae.response?.status||Ae.status},U.log(`Progressive data source error: ${T.id}`,L[T.id]),ee({[T.id]:null,_dataSourceErrors:{...L}})}}catch(fe){U.error(`Failed to fetch data source: ${T.id}`,fe)}});if(await Promise.all(H),n!==this.currentRouteChangeId){U.log("Route change cancelled after progressive fetch (newer request exists):",n);return}U.log("All progressive data sources loaded")}finally{Gr.setPending(!1),this.hideTransitionOverlay()}}else this.hideTransitionOverlay();const De=_.filter(ee=>ee.type==="websocket");if(De.length>0){U.log("Subscribing WebSocket data sources:",De.map(T=>T.id));const{updateTemplateData:ee,getState:me}=await Promise.resolve().then(()=>Wr),H={...this.currentFetchedData,...e.params,route:{...e.params||{},path:e.path},query:f,_global:{...this.globalState}};U.log("WebSocket binding context keys:",Object.keys(H)),this.currentWebSocketSubscriptions=S.subscribeWebSockets(_,(T,fe)=>{U.log(`WebSocket data received for: ${T}`,fe),this.currentFetchedData[T]=fe;const he=_.find($e=>$e.id===T),Ae=me();if(Ae.bindingEngine){const $e=[T];he?.initGlobal&&$e.push("_global"),he?.initLocal&&$e.push("_local"),Ae.bindingEngine.invalidateCacheByKeys($e)}ee({[T]:fe});const ge=_.find($e=>$e.type==="websocket"&&($e.target_source||$e.id)===T)?.onReceive;Array.isArray(ge)&&ge.length>0&&window.G7Core?.dispatch&&(async()=>{for(const Te of ge)try{const Ie=this.getActionDispatcher?.();Ie&&await Ie.dispatchAction(Te,{navigate:this.getRouter?.()?(xt,rt)=>this.getRouter().navigate(xt,rt):void 0,setState:xt=>this.setGlobalState(xt),state:this.globalState,data:{...this.globalState,$args:[fe],$event:fe},_isDispatchFallbackContext:!0})}catch(Ie){U.error(`WebSocket onReceive action failed for ${T}:`,Ie)}})()},H),U.log("WebSocket subscriptions established:",this.currentWebSocketSubscriptions)}U.log("Layout rendered successfully")}catch(r){U.error("Route change handling failed:",r),this.hideTransitionOverlay(),this.showRouteError(r)}}processInitOptions(e,n,r,i){e.some(c=>c.refetchOnMount===!0&&c.initLocal)&&(r._forceLocalInit=Date.now(),U.log("refetchOnMount detected, forcing local state init")),e.forEach(c=>{const d=n[c.id];if(!d)return;const f=d.data??d;if(c.initLocal){if(typeof c.initLocal=="string"){let h=f;if(c.initLocalDefaults&&typeof c.initLocalDefaults=="object"){const y=this.evaluateDefaults(c.initLocalDefaults,n);h={...y,...f},U.log(`initLocalDefaults applied for ${c.initLocal}:`,Object.keys(y))}const m=this.globalState._local?.[c.initLocal];m!==void 0&&typeof m=="object"&&typeof h=="object"?(r[c.initLocal]=this.deepMerge(m,h),U.log(`initLocal (merged): ${c.id}.data -> _local.${c.initLocal}`)):(r[c.initLocal]=h,U.log(`initLocal: ${c.id}.data -> _local.${c.initLocal}`))}else if(typeof c.initLocal=="object"&&c.initLocal.key){const{key:h,path:m}=c.initLocal;let y=m?this.extractValueByPathOrExpression(f,m,c.id):f;if(c.initLocalDefaults&&typeof c.initLocalDefaults=="object"){const S=this.evaluateDefaults(c.initLocalDefaults,n);y={...S,...y},U.log(`initLocalDefaults applied for ${h}:`,Object.keys(S))}const v=this.globalState._local?.[h];v!==void 0&&typeof v=="object"&&typeof y=="object"?(r[h]=this.deepMerge(v,y),U.log(`initLocal (merged): ${c.id}.data${m?"."+m:""} -> _local.${h}`)):(r[h]=y,U.log(`initLocal: ${c.id}.data${m?"."+m:""} -> _local.${h}`))}else if(typeof c.initLocal=="object"){const h=c.initLocal._merge||"deep",m=this.flattenNestedObjectToMappings(c.initLocal);for(const{targetPath:y,sourcePath:v}of m){const S=this.extractValueByPathOrExpression(f,v,c.id);if(y.includes(".")){const _=y.split(".")[0];r[_]===void 0&&this.globalState._local?.[_]!==void 0&&(r[_]=JSON.parse(JSON.stringify(this.globalState._local[_]))),this.setValueAtPath(r,y,S,h),U.log(`initLocal map (${h}): ${c.id} ${v} -> _local.${y}`)}else{const _=this.globalState._local?.[y];h==="replace"?(r[y]=S,U.log(`initLocal map (replace): ${c.id} ${v} -> _local.${y}`)):h==="shallow"?_!==void 0&&typeof _=="object"&&typeof S=="object"?(r[y]={..._,...S},U.log(`initLocal map (shallow): ${c.id} ${v} -> _local.${y}`)):(r[y]=S,U.log(`initLocal map: ${c.id} ${v} -> _local.${y}`)):_!==void 0&&typeof _=="object"&&typeof S=="object"?(r[y]=this.deepMerge(_,S),U.log(`initLocal map (deep): ${c.id} ${v} -> _local.${y}`)):(r[y]=S,U.log(`initLocal map: ${c.id} ${v} -> _local.${y}`))}}}}if(c.initGlobal)if(typeof c.initGlobal=="object"&&!Array.isArray(c.initGlobal)&&!("key"in c.initGlobal))for(const[m,y]of Object.entries(c.initGlobal)){if(typeof y!="string"){U.warn(`initGlobal map value must be string, got ${typeof y} for key ${m}`);continue}const v=this.extractValueByPathOrExpression(f,y,c.id),S=this.globalState[m];S!==void 0&&typeof S=="object"&&typeof v=="object"?(this.globalState[m]=this.deepMerge(S,v),U.log(`initGlobal map (merged): ${c.id} ${y} -> _global.${m}`)):(this.globalState[m]=v,U.log(`initGlobal map: ${c.id} ${y} -> _global.${m}`))}else{const m=Array.isArray(c.initGlobal)?c.initGlobal:[c.initGlobal];for(const y of m)if(typeof y=="string"){const v=this.globalState[y];v!==void 0&&typeof v=="object"&&typeof f=="object"?(this.globalState[y]=this.deepMerge(v,f),U.log(`initGlobal (merged): ${c.id}.data -> _global.${y}`)):(this.globalState[y]=f,U.log(`initGlobal: ${c.id}.data -> _global.${y}`))}else if(typeof y=="object"&&y.key){const{key:v,path:S}=y,_=S?this.extractValueByPathOrExpression(f,S,c.id):f,x=this.globalState[v];x!==void 0&&typeof x=="object"&&typeof _=="object"?(this.globalState[v]=this.deepMerge(x,_),U.log(`initGlobal (merged): ${c.id}.data${S?"."+S:""} -> _global.${v}`)):(this.globalState[v]=_,U.log(`initGlobal: ${c.id}.data${S?"."+S:""} -> _global.${v}`))}}if(i&&c.initIsolated){if(typeof c.initIsolated=="string"){const h=i[c.initIsolated];h!==void 0&&typeof h=="object"&&typeof f=="object"?(i[c.initIsolated]=this.deepMerge(h,f),U.log(`initIsolated (merged): ${c.id}.data -> _isolated.${c.initIsolated}`)):(i[c.initIsolated]=f,U.log(`initIsolated: ${c.id}.data -> _isolated.${c.initIsolated}`))}else if(typeof c.initIsolated=="object"&&c.initIsolated.key){const{key:h,path:m}=c.initIsolated,y=m?this.extractValueByPathOrExpression(f,m,c.id):f,v=i[h];v!==void 0&&typeof v=="object"&&typeof y=="object"?(i[h]=this.deepMerge(v,y),U.log(`initIsolated (merged): ${c.id}.data${m?"."+m:""} -> _isolated.${h}`)):(i[h]=y,U.log(`initIsolated: ${c.id}.data${m?"."+m:""} -> _isolated.${h}`))}else if(typeof c.initIsolated=="object")for(const[h,m]of Object.entries(c.initIsolated)){if(typeof m!="string"){U.warn(`initIsolated map value must be string, got ${typeof m} for key ${h}`);continue}const y=this.extractValueByPathOrExpression(f,m,c.id),v=i[h];v!==void 0&&typeof v=="object"&&typeof y=="object"?(i[h]=this.deepMerge(v,y),U.log(`initIsolated map (merged): ${c.id} ${m} -> _isolated.${h}`)):(i[h]=y,U.log(`initIsolated map: ${c.id} ${m} -> _isolated.${h}`))}}})}evaluateDefaults(e,n){const r={};for(const[i,l]of Object.entries(e))if(typeof l=="string"&&l.startsWith("{{")&&l.endsWith("}}")){const c=l.slice(2,-2).trim();r[i]=this.evaluateExpression(c,n)}else r[i]=l;return r}evaluateExpression(e,n){try{const r=e.split("??").map(i=>i.trim());for(const i of r){if(/^['"].*['"]$/.test(i))return i.slice(1,-1);if(i==="true")return!0;if(i==="false")return!1;if(/^-?\d+(\.\d+)?$/.test(i))return Number(i);const l=this.getNestedValue(n,i);if(l!=null)return l}return}catch(r){U.warn(`Expression evaluation failed: ${e}`,r);return}}getNestedValue(e,n){const r=n.replace(/\?\./g,".");let i=e;const l=r.split(/\.(?![^\[]*\])/).flatMap(c=>{const d=c.match(/^([^\[]*)((?:\[\d+\])*)$/);if(d){const[,f,h]=d,m=[];f&&m.push(f);const y=h.match(/\[\d+\]/g);return y&&m.push(...y),m}return[c]}).filter(c=>c!=="");for(const c of l){if(i==null)return;if(c.startsWith("[")&&c.endsWith("]")){const d=parseInt(c.slice(1,-1),10);i=i[d]}else i=i[c]}return i}extractValueByPathOrExpression(e,n,r){const i=Oa(n);if(i!==null){const l=new $n,c={data:e,_global:this.globalState,_local:this.globalState._local||{}};try{const d=jn(i)?l.evaluatePipeExpression(i,c,{skipCache:!0}):l.evaluateExpression(i,c);return U.log(`initLocal/initGlobal expression evaluated: ${n} -> `,d),d}catch(d){U.warn(`initLocal/initGlobal expression evaluation failed for ${r}:`,n,d);return}}return this.getNestedValue(e,n)}async loadLayoutScripts(e,n){const r=[];for(const i of e){if((i.if!==void 0||i.conditions!==void 0)&&!hs({if:i.if,conditions:i.conditions},n,this.bindingEngine,`script:${i.id}`)){U.log(`Script skipped (condition not met): ${i.id}`);continue}if(document.getElementById(i.id)){U.log(`Script already loaded: ${i.id}`);continue}if(!this.isAllowedScriptSrc(i.src)){U.warn(`Blocked untrusted external script src (same-origin path or declared trusted host required): ${i.id} (${i.src})`);continue}r.push(this.loadLayoutScript(i))}r.length>0&&(await Promise.all(r),U.log(`All scripts loaded: ${e.filter(i=>!document.getElementById(i.id)||r.length>0).map(i=>i.id).join(", ")}`))}async loadLayoutScript(e){const n=e.src.startsWith("/")?Ln(e.src):e.src;try{await _r(n,{id:e.id},{label:`layout-script:${e.id}`}),this.failedLayoutScripts.delete(e.id),Db(`layout-script:${e.id}`),U.log(`Script loaded successfully: ${e.id}`)}catch(r){this.failedLayoutScripts.add(e.id),U.warn(`Failed to load script after retries: ${e.id} (${n})`,r),xs({id:`layout-script:${e.id}`,label:e.id,retry:async()=>{document.getElementById(e.id)?.remove(),await _r(n,{id:e.id},{label:`layout-script:${e.id}`}),this.failedLayoutScripts.delete(e.id)}})}}getFailedLayoutScripts(){return Array.from(this.failedLayoutScripts)}isAllowedScriptSrc(e){return Io(e)}static normalizeScriptSrcForOriginCheck(e){return by(e)}extractScriptHost(e){return vy(e)}getTrustedScriptHosts(){return bs()}evaluateScriptCondition(e,n){try{if(e.startsWith("{{")&&e.endsWith("}}")){const r=e.slice(2,-2).trim();return!!nf(r,n)}return!!e}catch(r){return U.warn(`Failed to evaluate script condition: ${e}`,r),!1}}showInitError(e){if(ec()){U.warn("Init failed while document is unloading — skipping error screen",e);return}const n=kb(e);lh.renderFromError("app",n,"초기화 실패",this.config.debug,{templateId:this.config.templateId,locale:this.config.locale},void 0,e)}showTransitionOverlay(e,n){const r=typeof e=="boolean"?{enabled:e,style:"opaque",target:void 0,skeleton:void 0,spinner:void 0}:{enabled:e.enabled,style:e.style||"opaque",target:e.target,skeleton:e.skeleton,spinner:e.spinner,fallback_target:e.fallback_target};if(!r.enabled)return;if(this.hideTransitionOverlay(),r.style==="skeleton"&&r.skeleton?.component&&r.target&&n){this.renderSkeletonOverlay(r.target,r.skeleton,n,r.fallback_target);return}if(r.style==="spinner"&&r.target){this.renderSpinnerOverlay(r.target,r.spinner,r.fallback_target);return}const i=document.documentElement.classList.contains("dark");let l,c="";switch(r.style){case"blur":l=i?"rgba(17,24,39,0.3)":"rgba(255,255,255,0.3)",c="backdrop-filter:blur(4px);-webkit-backdrop-filter:blur(4px);";break;case"fade":l=i?"rgba(17,24,39,0.8)":"rgba(255,255,255,0.8)";break;case"skeleton":l=i?"rgb(17,24,39)":"rgb(249,250,251)";break;case"opaque":default:l=i?"rgb(17,24,39)":"rgb(249,250,251)";break}if(r.target){const d=`#${CSS.escape(r.target)}`,f=document.createElement("style");f.id="g7-transition-overlay",f.textContent=`${d}{position:relative;z-index:0;}${d}::after{content:'';position:absolute;inset:0;background:${l};${c}z-index:2147483647;pointer-events:none;}`,document.head.appendChild(f),this.transitionOverlayEl=f}else{const d=document.createElement("div");d.id="g7-transition-overlay",d.setAttribute("aria-hidden","true"),d.style.position="fixed",d.style.inset="0",d.style.zIndex="9999",d.style.pointerEvents="none",d.style.background=l;for(const f of c.split(";")){const h=f.indexOf(":");h!==-1&&d.style.setProperty(f.slice(0,h).trim(),f.slice(h+1).trim())}document.body.appendChild(d),this.transitionOverlayEl=d}}hideTransitionOverlay(){this.transitionOverlayEl&&(this.transitionOverlayEl.remove(),this.transitionOverlayEl=null),this.hideSkeletonOverlay()}renderSkeletonOverlay(e,n,r,i){const c=Ar.getInstance().getComponent(n.component);if(!c){U.log(`Skeleton component "${n.component}" not found in registry, falling back to opaque overlay`),this.showTransitionOverlay({enabled:!0,style:"opaque",target:e});return}let d=document.getElementById(e),f="target";if(!d&&i&&(d=document.getElementById(i),f="fallback"),d||(d=document.getElementById("app"),f="fullpage"),!d){U.log(`Skeleton overlay: no target found (target="#${e}", fallback="${i||"none"}"), falling back to opaque overlay`),this.showTransitionOverlay({enabled:!0,style:"opaque",target:e});return}this.hideSkeletonOverlay();const m=document.documentElement.classList.contains("dark")?"rgb(17,24,39)":"rgb(249,250,251)",y=f==="fullpage"?"app":f==="fallback"?i:e,v=`#${CSS.escape(y)}`,S=document.createElement("style");S.id="g7-skeleton-overlay-style",S.textContent=`${v}{position:relative;z-index:0;}${v}::after{content:'';position:absolute;inset:0;background:${m};z-index:2147483646;pointer-events:none;}`,document.head.appendChild(S),this.transitionOverlayEl=S;const _=document.createElement("div");if(_.id="g7-skeleton-overlay",_.setAttribute("role","status"),_.setAttribute("aria-busy","true"),_.setAttribute("aria-label","Loading..."),f==="fullpage")_.style.cssText=["position:fixed","inset:0","z-index:20","overflow:hidden","pointer-events:none",`background:${m}`].join(";")+";";else{const k=d.getBoundingClientRect(),L=window.scrollX||document.documentElement.scrollLeft,B=window.scrollY||document.documentElement.scrollTop;_.style.cssText=["position:absolute",`top:${k.top+B}px`,`left:${k.left+L}px`,`width:${k.width}px`,`height:${k.height}px`,"z-index:20","overflow:hidden","pointer-events:none",`background:${m}`].join(";")+";"}document.body.appendChild(_),this.skeletonOverlayContainer=_;const x=r.components||[];let A;if(f==="fullpage")A=x;else{const k=f==="fallback"?i:e;A=this.findComponentChildrenById(x,k)}const D=Rd.createRoot(_);this.skeletonOverlayRoot=D,Ia.flushSync(()=>{D.render(Xe.createElement(c,{components:A,options:{animation:n.animation||"pulse",iteration_count:n.iteration_count||5}}))});const O=f==="fullpage"?"fullpage (#app)":f==="fallback"?`fallback (#${i})`:`target (#${e})`;U.log(`Skeleton overlay rendered [${O}] with "${n.component}" (${A.length} components)`)}findComponentChildrenById(e,n){const r=i=>{for(const l of i){if(l.id===n)return l.children||[];if(l.children&&Array.isArray(l.children)){const c=r(l.children);if(c!==null)return c}}return null};return r(e)||e}renderSpinnerOverlay(e,n,r){let i=document.getElementById(e),l="target";if(!i&&r&&(i=document.getElementById(r),l="fallback"),i||(i=document.getElementById("app"),l="fullpage"),!i){U.log(`Spinner overlay: no target found (target="#${e}", fallback="${r||"none"}"), falling back to opaque overlay`),this.showTransitionOverlay({enabled:!0,style:"opaque",target:e});return}this.hideSkeletonOverlay();const c=l==="fullpage"?"app":l==="fallback"?r:e,d=`#${CSS.escape(c)}`,f=document.createElement("style");f.id="g7-skeleton-overlay-style",f.textContent=[`${d}{position:relative;}`,"@keyframes g7-spin{to{transform:rotate(360deg)}}"].join(""),document.head.appendChild(f),this.transitionOverlayEl=f;const h=n?.text||window.G7Core?.t?.("nav.loading")||"";this._spinnerState={target:e,fallbackTarget:r,spinnerConfig:n,resolvedText:h},this._mountSpinnerInTarget(i);const m=l==="fullpage"?"fullpage (#app)":l==="fallback"?`fallback (#${r})`:`target (#${e})`,y=n?.component||"default spinner";U.log(`Spinner overlay rendered [${m}] with "${y}"`)}_mountSpinnerInTarget(e){if(!this._spinnerState)return;const{spinnerConfig:n,resolvedText:r}=this._spinnerState;if(this.skeletonOverlayRoot){try{this.skeletonOverlayRoot.unmount()}catch{}this.skeletonOverlayRoot=null}if(this.skeletonOverlayContainer){try{this.skeletonOverlayContainer.remove()}catch{}this.skeletonOverlayContainer=null}const i=document.createElement("div");if(i.id="g7-skeleton-overlay",i.setAttribute("role","status"),i.setAttribute("aria-busy","true"),e.appendChild(i),this.skeletonOverlayContainer=i,n?.component){const c=Ar.getInstance().getComponent(n.component);if(c){const d=Rd.createRoot(i);this.skeletonOverlayRoot=d,Ia.flushSync(()=>{d.render(Xe.createElement(c,{options:{text:r}}))});return}U.log(`Spinner component "${n.component}" not found in registry, using default spinner`)}i.innerHTML='
'}reattachSpinnerOverlay(){if(!this._spinnerState)return;const{target:e,fallbackTarget:n}=this._spinnerState;let r=document.getElementById(e);!r&&n&&(r=document.getElementById(n)),r||(r=document.getElementById("app")),r&&(this._mountSpinnerInTarget(r),U.log("Spinner overlay reattached to new DOM target"))}hideSkeletonOverlay(){if(this.skeletonOverlayRoot){try{this.skeletonOverlayRoot.unmount()}catch{}this.skeletonOverlayRoot=null}if(this.skeletonOverlayContainer){try{this.skeletonOverlayContainer.parentNode&&this.skeletonOverlayContainer.remove()}catch{}this.skeletonOverlayContainer=null}const e=document.getElementById("g7-skeleton-overlay-style");e&&e.remove(),this._spinnerState=null}showRouteError(e){if(e instanceof Dr&&e.details?.status===401){const r=this.config.templateId||"",i=window.location.pathname,l=r.includes("admin")||i.startsWith("/admin")?"admin":"user",c=i+window.location.search,d=!!Yn().getToken()||e.details?.hadToken===!0,f=Xn.getInstance().getLoginRedirectUrl(l,c,d?"session_expired":void 0);window.location.href=f;return}const n=kb(e);lh.renderFromError("app",n,"페이지 로딩 실패",this.config.debug,{templateId:this.config.templateId,locale:this.config.locale},void 0,e)}async handleServerError(){const e=window.G7Error;if(!e)return!1;U.warn("Server error detected:",e);try{return this.errorPageHandler&&(e.data&&(this.globalState.errorData=e.data),this.errorPageHandler.updateGlobalState(this.globalState),this.errorPageHandler.updateLocale(this.config.locale),await this.errorPageHandler.renderError(e.code,"app"))?(U.log(`${e.code} error page rendered successfully`),!0):(U.log(`Falling back to ErrorDisplay for ${e.code}`),this.showInitError(new Error(`Service unavailable (${e.code})`)),!0)}catch(n){return U.error(`Failed to render ${e.code} page:`,n),this.showInitError(new Error(`Service unavailable (${e.code})`)),!0}}async handleRouteNotFound(e){U.warn("Route not found:",e);try{if(this.errorPageHandler&&(this.errorPageHandler.updateGlobalState(this.globalState),this.errorPageHandler.updateLocale(this.config.locale),await this.errorPageHandler.renderError(404,"app"))){U.log("404 error page rendered successfully");return}U.log("Falling back to ErrorDisplay for 404"),this.showRouteError(new Error(`Page not found: ${e}`))}catch(n){U.error("Failed to render 404 page:",n),this.showRouteError(new Error(`Page not found: ${e}`))}}getRouter(){return this.router}getConfig(){return this.config}getLayoutLoader(){return this.layoutLoader}async reloadExtensionState(){U.log("reloadExtensionState: start");let e;try{const n=await fetch(Br(`/api/templates/${this.config.templateId}/config`,"json",null,`_=${Date.now()}`));if(n.ok){const r=await n.json();r?.success&&r?.data?.cache_version!==void 0&&(e=r.data.cache_version)}}catch(n){U.warn("reloadExtensionState: failed to fetch config.json",n)}e!==void 0&&e!==this.extensionCacheVersion&&(U.log(`reloadExtensionState: cache version ${this.extensionCacheVersion} -> ${e}`),this.extensionCacheVersion=e,this.saveCacheVersionToStorage(e));try{this.router&&(await this.router.loadRoutes(this.extensionCacheVersion),U.log("reloadExtensionState: routes reloaded"))}catch(n){U.error("reloadExtensionState: routes reload failed",n)}try{this.layoutLoader&&(this.extensionCacheVersion>0&&this.layoutLoader.setCacheVersion(this.extensionCacheVersion),this.layoutLoader.clear(),U.log("reloadExtensionState: layout cache cleared"))}catch(n){U.error("reloadExtensionState: layout cache clear failed",n)}try{const{TranslationEngine:n}=await Promise.resolve().then(()=>Jg),r=n.getInstance();this.extensionCacheVersion>0&&r.setCacheVersion(this.extensionCacheVersion);const i=this.config.locale||"ko",l="en";await r.loadTranslations(this.config.templateId,i,"/api",!0),i!==l&&await r.loadTranslations(this.config.templateId,l,"/api",!0),U.log("reloadExtensionState: translations reloaded")}catch(n){U.error("reloadExtensionState: translations reload failed",n)}try{const n=await fetch(`/api/locales/active?_=${Date.now()}`);if(n.ok){const i=(await n.json())?.data?.locales;if(Array.isArray(i)&&i.length>0){const l=this.globalState.appConfig??{};this.setGlobalState({appConfig:{...l,supportedLocales:i}}),U.log("reloadExtensionState: supportedLocales refreshed",i)}}}catch(n){U.warn("reloadExtensionState: supportedLocales refresh failed",n)}U.log("reloadExtensionState: done")}getActionDispatcher(){return la().actionDispatcher}async changeLocale(e){try{if(U.log("Changing locale to:",e),this.config.locale===e){U.log("Locale is already",e);return}this.config.locale=e,this.saveLocaleToStorage(e),await this.saveLocaleToDatabase(e),this.layoutLoader&&(this.layoutLoader.clear(),U.log("Layout cache cleared")),this.errorPageHandler&&(this.errorPageHandler.updateLocale(e),U.log("ErrorPageHandler locale updated")),Yc(),await el({templateId:this.config.templateId,templateType:this.config.templateType,locale:e,debug:this.config.debug,cacheVersion:this.extensionCacheVersion}),U.log("Template Engine re-initialized with new locale");const{getActionDispatcher:n}=await Promise.resolve().then(()=>Wr),r=n();r&&(r.setDefaultContext({navigate:i=>this.router?.navigate(i)}),r.setGlobalStateUpdater((i,l)=>this.setGlobalState(i,l)),U.log("Navigate function and setGlobalState re-injected to ActionDispatcher")),this.reinitializeModuleHandlers(),U.log("Module handlers re-initialized"),this.reinitializeTemplateHandlers(),U.log("Template handlers re-initialized"),this.reinitializePluginHandlers(),U.log("Plugin handlers re-initialized"),this.router&&this.router.navigateToCurrentPath(),U.log("Locale changed successfully to",e)}catch(n){throw U.error("Failed to change locale:",n),n}}loadLocaleFromStorage(){try{return localStorage.getItem($r.LOCALE_STORAGE_KEY)}catch(e){return U.warn("Failed to load locale from storage:",e),null}}saveLocaleToStorage(e){try{localStorage.setItem($r.LOCALE_STORAGE_KEY,e)}catch(n){U.warn("Failed to save locale to storage:",n)}}async saveLocaleToDatabase(e){const n=localStorage.getItem("auth_token");if(!n){U.log("No auth token, skipping DB locale save");return}let r,i;if(this.config.localeApi)r=this.config.localeApi.endpoint,i=this.config.localeApi.method;else{const l=this.config.templateType==="admin";r=l?"/api/admin/users/me/language":"/api/user/profile/update-language",i=l?"PATCH":"POST"}try{const l=this.getXsrfToken(),c=await fetch(r,{method:i,headers:{"Content-Type":"application/json",Accept:"application/json",...l&&{"X-XSRF-TOKEN":l},Authorization:`Bearer ${n}`},credentials:"include",body:JSON.stringify({language:e})});c.ok?U.log("Locale saved to DB:",e):U.warn("Failed to save locale to DB (UI will still change):",c.statusText)}catch(l){U.warn("Failed to call locale API (UI will still change):",l)}}getXsrfToken(){if(typeof document>"u")return null;const n=`; ${document.cookie}`.split("; XSRF-TOKEN=");return n.length===2?decodeURIComponent(n.pop()?.split(";").shift()||""):null}loadCacheVersionFromStorage(){try{const e=localStorage.getItem($r.CACHE_VERSION_STORAGE_KEY);return e?parseInt(e,10):null}catch(e){return U.warn("Failed to load cache version from storage:",e),null}}saveCacheVersionToStorage(e){try{localStorage.setItem($r.CACHE_VERSION_STORAGE_KEY,String(e))}catch(n){U.warn("Failed to save cache version to storage:",n)}}reinitializeModuleHandlers(){if(typeof window>"u")return;const e="__",n=window;Object.keys(n).forEach(r=>{if(r.startsWith(e)&&typeof n[r]?.initModule=="function")try{n[r].initModule(),U.log(`Module handler re-initialized: ${r}`)}catch(i){U.warn(`Failed to re-initialize module handlers for ${r}:`,i)}})}reinitializeTemplateHandlers(){if(typeof window>"u")return;const e=window.G7TemplateHandlers;if(!e){U.warn("Template handlers not found on window.G7TemplateHandlers");return}const n=this.getActionDispatcher();if(!n){U.warn("ActionDispatcher not available for template handler registration");return}Object.entries(e).forEach(([r,i])=>{n.registerHandler(r,i)}),U.log(`${Object.keys(e).length} template handler(s) re-registered:`,Object.keys(e))}reinitializePluginHandlers(){if(typeof window>"u")return;const e="__",n=window;Object.keys(n).forEach(r=>{if(r.startsWith(e)&&typeof n[r]?.initPlugin=="function")try{n[r].initPlugin(),U.log(`Plugin handler re-initialized: ${r}`)}catch(i){U.warn(`Failed to re-initialize plugin handlers for ${r}:`,i)}})}getLocale(){return this.config.locale}getErrorPageHandler(){return this.errorPageHandler}getGlobalState(){return{...this.globalState}}setGlobalState(e,n){const r={...this.globalState};typeof e=="function"?this.globalState=e(this.globalState):this.globalState={...this.globalState,...e},U.log("Global state updated:",this.globalState),window.G7Core?.devTools?.captureStateSnapshot?.({source:"setGlobalState",prev:r,next:this.globalState}),this.globalStateListeners.forEach(l=>{l(this.globalState)}),n?.render!==!1&&Promise.resolve().then(()=>Wr).then(({updateTemplateData:l,getState:c})=>{const d=c();d.reactRoot&&d.currentLayoutJson&&l({_global:{...this.globalState},_local:this.globalState._local||{}})})}onGlobalStateChange(e){this.globalStateListeners.add(e)}offGlobalStateChange(e){this.globalStateListeners.delete(e)}async refetchDataSource(e,n){let r=this.currentDataSources.find(i=>i.id===e);if(!r)for(const[,i]of this.modalDataSources){const l=i.find(c=>c.id===e);if(l){r=l;break}}if(!r){U.warn(`Data source not found: ${e}`);return}U.log(`Refetching data source: ${e}`,n?.sync?"(sync mode)":"",n?.globalStateOverride?"(with global override)":"",n?.localStateOverride?"(with local override)":""),Gr.setPending(!0);try{const i=new Or;this.currentGlobalHeaders.length>0&&i.setGlobalHeaders(this.currentGlobalHeaders);const l=la(),c=l.currentDataContext?._global||{},d=n?.globalStateOverride?{...c,...n.globalStateOverride}:c,f=l.currentDataContext?._local||{},h=n?.localStateOverride?{...f,...n.localStateOverride}:f,y=(await i.fetchDataSourcesWithResults([r],this.currentRouteParams,this.currentQueryParams,d,h,{ignoreAutoFetch:!0}))[0];if(y.state==="success"&&y.data!==void 0){this.currentFetchedData[e]=y.data;const v={};this.processInitOptions([r],{[e]:y.data},v);const S=la();if(S.bindingEngine){const x=[e];r.initGlobal&&x.push("_global"),r.initLocal&&x.push("_local"),S.bindingEngine.invalidateCacheByKeys(x)}const _={[e]:y.data};return Object.keys(v).length>0&&(_._localInit=v),r.initGlobal&&(_._global={...this.globalState}),Ds(_,n?.sync?{sync:!0}:void 0),U.log(`Data source refetched successfully: ${e}`),y.data}else if(y.state==="error"){U.error(`Failed to refetch data source: ${e}`,y.error);return}}catch(i){U.error(`Error refetching data source: ${e}`,i);return}finally{Gr.setPending(!1)}}registerModalDataSources(e,n){this.modalDataSources.set(e,n),U.log(`Modal data sources registered: ${e} (${n.length} sources)`)}unregisterModalDataSources(e){this.modalDataSources.delete(e),U.log(`Modal data sources unregistered: ${e}`)}getDataSource(e){return this.currentFetchedData[e]}setDataSource(e,n,r){if(!e){U.warn("setDataSource: dataSourceId is required");return}const{merge:i=!1,sync:l=!1}=r||{};i&&this.currentFetchedData[e]?this.currentFetchedData[e]={...this.currentFetchedData[e],...n}:this.currentFetchedData[e]=n,Promise.resolve().then(()=>Wr).then(({updateTemplateData:c})=>{c({[e]:this.currentFetchedData[e]},l?{sync:!0}:void 0)}),U.log(`setDataSource: Updated ${e}`,i?"(merged)":"(replaced)")}updateDataSourceItem(e,n,r,i,l){const{idField:c="id",merge:d=!0,skipRender:f=!1}=l||{},h=this.currentFetchedData[e];if(!h)return U.warn(`updateDataSourceItem: DataSource '${e}' not found`),!1;const m=this.parseItemPath(n);let y=h;for(const _ of m)if(y=y?.[_],y===void 0)return U.warn(`updateDataSourceItem: Path '${n}' not found in dataSource`),!1;if(!Array.isArray(y))return U.warn(`updateDataSourceItem: Target at '${n}' is not an array`),!1;const v=y.findIndex(_=>String(_[c])===String(r));if(v===-1)return U.warn(`updateDataSourceItem: Item with ${c}='${r}' not found`),!1;d?y[v]=this.deepMerge(y[v],i):y[v]={...y[v],...i};const S=window.G7Core?.devTools;return S?.isEnabled?.()&&S.trackDataSourceUpdate?.({dataSourceId:e,updateType:"partial",itemPath:n,itemId:r,updates:i,timestamp:Date.now()}),f||Promise.resolve().then(()=>Wr).then(({updateTemplateData:_})=>{_({[e]:this.currentFetchedData[e]},{sync:!1})}),U.log(`updateDataSourceItem: Updated ${e}.${n}[${c}=${r}]`),!0}parseItemPath(e){const n=[],r=/([^\.\[\]]+)|\[(\d+)\]/g;let i;for(;(i=r.exec(e))!==null;)i[1]!==void 0?n.push(i[1]):i[2]!==void 0&&n.push(parseInt(i[2],10));return n}deepMerge(e,n){if(n==null)return e;if(typeof n!="object"||Array.isArray(n))return n;const r={...e};for(const i of Object.keys(n))typeof n[i]=="object"&&n[i]!==null&&!Array.isArray(n[i])?r[i]=this.deepMerge(r[i]||{},n[i]):r[i]=n[i];return r}setValueAtPath(e,n,r,i="deep"){const l=n.split(".");let c=e;for(let h=0;h0?l.filterByCondition(this.currentRawDataSources,d):this.currentDataSources;this.currentDataSources=f;const h=f.filter(x=>x.auto_fetch!==!1&&x.type!=="websocket");if(h.length===0){U.log("No auto_fetch data sources to refetch");return}U.log(`Refetching ${h.length} auto_fetch data sources`),Gr.setPending(!0);const m=la().currentLayoutJson,y=m?.transition_overlay,v=Array.isArray(y?.wait_for)?y.wait_for:[],S=h.some(x=>(x.loading_strategy||"progressive")==="blocking"),_=v.length>0&&h.some(x=>v.includes(x.id)&&x.type!=="websocket"&&(x.loading_strategy||"progressive")!=="background");if((S||_)&&y&&typeof y=="object"){const x=n?.transitionOverlayTarget||y.target;y.enabled&&y.style==="skeleton"&&y.skeleton?.component&&x?this.renderSkeletonOverlay(x,y.skeleton,m,y.fallback_target):y.enabled&&y.style==="spinner"&&x&&this.renderSpinnerOverlay(x,y.spinner,y.fallback_target)}try{const x=new Or;this.currentGlobalHeaders.length>0&&x.setGlobalHeaders(this.currentGlobalHeaders);const A=la(),D=A.currentDataContext?._global||{},O=await x.fetchDataSourcesWithResults(h,this.currentRouteParams,this.currentQueryParams,D,void 0,{ignoreAutoFetch:!1}),k={},L={},B=new Map(h.map(q=>[q.id,q]));for(const q of O){const z=B.get(q.id);if(!z){U.warn(`Refetch result id not found in autoFetchDataSources: ${q.id}`);continue}const W=q.id;q.state==="success"&&q.data!==void 0?(this.currentFetchedData[W]=q.data,k[W]=q.data,this.processInitOptions([z],{[W]:q.data},L),U.log(`Data source refetched: ${W}`)):q.state==="error"&&U.error(`Failed to refetch data source: ${W}`,q.error)}if(A.bindingEngine){const q=Object.keys(k);q.push("query"),A.bindingEngine.invalidateCacheByKeys(q)}if(Object.keys(L).length>0&&(k._localInit=L),Object.keys(k).length>0){if(k.query=Mb(this.currentQueryParams),A.currentLayoutJson?.computed&&Object.keys(A.currentLayoutJson.computed).length>0){const q={...A.currentDataContext,...k,query:k.query},z=this.calculateComputed(A.currentLayoutJson.computed,q);Object.keys(z).length>0&&(k._computed=z,this.globalState._computed=z,U.log("Computed values recalculated in updateQueryParams:",Object.keys(z)))}Ds(k,{sync:!0}),U.log("Template data updated with new query params")}}catch(x){U.error("Error in updateQueryParams:",x)}finally{Gr.setPending(!1),this.hideTransitionOverlay()}}calculateComputed(e,n){const r={},i=new $n;for(const[l,c]of Object.entries(e))try{let d;if(this.isComputedSwitchDefinition(c))d=i.resolveSwitch(c,n,{skipCache:!0});else if(typeof c=="string")if(c.startsWith("{{")&&c.endsWith("}}")){const f=c.slice(2,-2).trim();d=this.evaluateComputedExpression(f,n)}else d=c;r[l]=d,U.log(`Computed ${l}:`,d)}catch(d){U.warn(`Failed to calculate computed value: ${l}`,d),r[l]=void 0}return r}isComputedSwitchDefinition(e){return e!==null&&typeof e=="object"&&!Array.isArray(e)&&"$switch"in e&&"$cases"in e}evaluateComputedExpression(e,n){try{return nf(e,n)}catch(r){U.warn(`Expression evaluation failed: ${e}`,r);return}}async executeInitActions(e,n={}){const{getActionDispatcher:r}=await Promise.resolve().then(()=>Wr),i=r();if(!i){U.warn("ActionDispatcher not available for init_actions");return}U.log("Executing init_actions:",e.map(d=>d.handler)),U.log("Init actions dataContext:",n),U.log("Init actions dataContext._global:",n._global);const l=e.map(d=>d.handler).filter(d=>d.includes("."));l.length>0&&await this.waitForHandlers(i,l);let c={...n};for(const d of e)try{const f={type:"click",handler:d.handler,target:d.target,params:d.params,resultTo:d.resultTo,onSuccess:d.onSuccess,onError:d.onError,if:d.if,conditions:d.conditions,auth_mode:d.auth_mode,auth_required:d.auth_required},h=i.createHandler(f,c),m=new Event("init");await h(m),U.log(`Init action executed: ${d.handler}`);const y=this.globalState;c={...c,_global:{...y},_local:y._local||c._local||{}},U.log(`Data context refreshed after ${d.handler}:`,{cartKey:c._global?.cartKey})}catch(f){U.error(`Failed to execute init action: ${d.handler}`,f)}}async waitForHandlers(e,n,r=5e3){const i=Date.now(),l=50,c=()=>n.every(f=>e.customHandlers?.has(f));if(c()){U.log("All module handlers already registered");return}const d=dh().getFailedJsAssets();if(d.length>0){const f=d.some(y=>y==="module"||y==="plugin"),h=n.filter(y=>!e.customHandlers?.has(y)),m=h.length>0&&h.every(y=>d.some(v=>y.startsWith(`${v}.`)));if(f||m){U.warn("Extension asset load failed — not waiting for handlers that will never register:",{pending:h,failedAssets:d});return}}return U.log("Waiting for module handlers:",n),new Promise(f=>{const h=()=>{if(c()){U.log("All module handlers now registered"),f();return}if(Date.now()-i>=r){const m=n.filter(y=>!e.customHandlers?.has(y));U.warn("Timeout waiting for handlers:",m),f();return}setTimeout(h,l)};h()})}};M($r,"LOCALE_STORAGE_KEY","g7_locale"),M($r,"CACHE_VERSION_STORAGE_KEY","g7_cache_version");let Yo=$r;function hh(s){const e=new Yo(s);return s.websocket&&(qc.configure(s.websocket),U.log("WebSocket 설정 완료")),document.readyState==="loading"?document.addEventListener("DOMContentLoaded",()=>{e.init(),window.__templateApp=e}):(e.init(),window.__templateApp=e),e}typeof window<"u"&&(window.G7Core=window.G7Core||{},window.G7Core.initTemplateApp=hh,window.G7Core.dataSource={refetch:async(s,e)=>{const n=window.__templateApp;if(!n){U.warn("TemplateApp not initialized (G7Core.dataSource.refetch)");return}return n.refetchDataSource(s,e)},get:s=>{const e=window.__templateApp;if(!e){U.warn("TemplateApp not initialized (G7Core.dataSource.get)");return}return e.getDataSource(s)}});function gT(s={}){const{checked:e,value:n,name:r,type:i="checkbox"}=s,c=n!==void 0?n:i==="checkbox"||i==="radio"?e??!1:String(e??""),d={checked:e??!1,value:c,name:r??"",type:i};return{target:d,currentTarget:d,preventDefault:()=>{},stopPropagation:()=>{},nativeEvent:new Event("change"),bubbles:!0,cancelable:!0,defaultPrevented:!1,eventPhase:Event.AT_TARGET,isTrusted:!1,timeStamp:Date.now(),type:"change",isDefaultPrevented:()=>!1,isPropagationStopped:()=>!1,persist:()=>{}}}function mT(s={}){const{button:e=0,clientX:n=0,clientY:r=0}=s;return{button:e,clientX:n,clientY:r,preventDefault:()=>{},stopPropagation:()=>{},nativeEvent:new MouseEvent("click"),bubbles:!0,cancelable:!0,defaultPrevented:!1,eventPhase:Event.AT_TARGET,isTrusted:!1,timeStamp:Date.now(),type:"click",isDefaultPrevented:()=>!1,isPropagationStopped:()=>!1,persist:()=>{},target:document.createElement("div"),currentTarget:document.createElement("div")}}function yT(){return{preventDefault:()=>{},stopPropagation:()=>{},nativeEvent:new Event("submit"),bubbles:!0,cancelable:!0,defaultPrevented:!1,eventPhase:Event.AT_TARGET,isTrusted:!1,timeStamp:Date.now(),type:"submit",isDefaultPrevented:()=>!1,isPropagationStopped:()=>!1,persist:()=>{},target:document.createElement("form"),currentTarget:document.createElement("form")}}function bT(s,e="keydown"){return{key:s,code:s.length===1?`Key${s.toUpperCase()}`:s,preventDefault:()=>{},stopPropagation:()=>{},nativeEvent:new KeyboardEvent(e,{key:s}),bubbles:!0,cancelable:!0,defaultPrevented:!1,eventPhase:Event.AT_TARGET,isTrusted:!1,timeStamp:Date.now(),type:e,isDefaultPrevented:()=>!1,isPropagationStopped:()=>!1,persist:()=>{},target:document.createElement("div"),currentTarget:document.createElement("div"),altKey:!1,ctrlKey:!1,metaKey:!1,shiftKey:!1,repeat:!1}}const vT={justify:/^justify-(start|end|center|between|around|evenly)$/,items:/^items-(start|end|center|baseline|stretch)$/,content:/^content-(start|end|center|between|around|evenly|stretch)$/,self:/^self-(auto|start|end|center|stretch|baseline)$/,flex:/^flex-(row|row-reverse|col|col-reverse|wrap|wrap-reverse|nowrap|1|auto|initial|none)$/,grow:/^(grow|grow-0)$/,shrink:/^(shrink|shrink-0)$/,basis:/^basis-/,order:/^(order-|-)order-/,gap:/^gap(-x|-y)?-/,gridCols:/^grid-cols-/,gridRows:/^grid-rows-/,colSpan:/^col-(span-|start-|end-)/,rowSpan:/^row-(span-|start-|end-)/,display:/^(block|inline-block|inline|flex|inline-flex|table|inline-table|table-caption|table-cell|table-column|table-column-group|table-footer-group|table-header-group|table-row-group|table-row|flow-root|grid|inline-grid|contents|list-item|hidden)$/,position:/^(static|fixed|absolute|relative|sticky)$/,inset:/^(inset|top|right|bottom|left)-/,zIndex:/^z-/,width:/^w-/,minWidth:/^min-w-/,maxWidth:/^max-w-/,height:/^h-/,minHeight:/^min-h-/,maxHeight:/^max-h-/,padding:/^p[xytblr]?-/,margin:/^-?m[xytblr]?-/,space:/^space-(x|y)-/,fontSize:/^text-(xs|sm|base|lg|xl|2xl|3xl|4xl|5xl|6xl|7xl|8xl|9xl)$/,fontWeight:/^font-(thin|extralight|light|normal|medium|semibold|bold|extrabold|black)$/,fontStyle:/^(italic|not-italic)$/,fontFamily:/^font-(sans|serif|mono)/,textAlign:/^text-(left|center|right|justify|start|end)$/,textColor:/^text-(inherit|current|transparent|black|white|slate|gray|zinc|neutral|stone|red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose)-/,textDecoration:/^(underline|overline|line-through|no-underline)$/,textTransform:/^(uppercase|lowercase|capitalize|normal-case)$/,lineHeight:/^leading-/,letterSpacing:/^tracking-/,textOverflow:/^(truncate|text-ellipsis|text-clip)$/,whitespace:/^whitespace-/,wordBreak:/^break-/,bgColor:/^bg-(inherit|current|transparent|black|white|slate|gray|zinc|neutral|stone|red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose)-/,bgGradient:/^bg-gradient-/,bgSize:/^bg-(auto|cover|contain)$/,bgPosition:/^bg-(bottom|center|left|left-bottom|left-top|right|right-bottom|right-top|top)$/,bgRepeat:/^bg-(repeat|no-repeat|repeat-x|repeat-y|repeat-round|repeat-space)$/,bgAttachment:/^bg-(fixed|local|scroll)$/,bgClip:/^bg-clip-/,bgOrigin:/^bg-origin-/,borderWidth:/^border(-[xytblr])?(-0|-2|-4|-8)?$/,borderColor:/^border-(inherit|current|transparent|black|white|slate|gray|zinc|neutral|stone|red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose)-/,borderStyle:/^border-(solid|dashed|dotted|double|hidden|none)$/,borderRadius:/^rounded(-[tblrse]{1,2})?(-none|-sm|-md|-lg|-xl|-2xl|-3xl|-full)?$/,ringWidth:/^ring(-0|-1|-2|-4|-8|-inset)?$/,ringColor:/^ring-(inherit|current|transparent|black|white|slate|gray|zinc|neutral|stone|red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose)-/,ringOffset:/^ring-offset-/,shadow:/^shadow(-sm|-md|-lg|-xl|-2xl|-inner|-none)?$/,opacity:/^opacity-/,mixBlend:/^mix-blend-/,bgBlend:/^bg-blend-/,blur:/^blur(-none|-sm|-md|-lg|-xl|-2xl|-3xl)?$/,brightness:/^brightness-/,contrast:/^contrast-/,grayscale:/^grayscale(-0)?$/,hueRotate:/^-?hue-rotate-/,invert:/^invert(-0)?$/,saturate:/^saturate-/,sepia:/^sepia(-0)?$/,backdropBlur:/^backdrop-blur-/,backdropBrightness:/^backdrop-brightness-/,backdropContrast:/^backdrop-contrast-/,backdropGrayscale:/^backdrop-grayscale-/,backdropHueRotate:/^backdrop-hue-rotate-/,backdropInvert:/^backdrop-invert-/,backdropOpacity:/^backdrop-opacity-/,backdropSaturate:/^backdrop-saturate-/,backdropSepia:/^backdrop-sepia-/,scale:/^scale(-x|-y)?-/,rotate:/^-?rotate-/,translate:/^-?translate-[xy]-/,skew:/^-?skew-[xy]-/,transformOrigin:/^origin-/,transition:/^transition(-none|-all|-colors|-opacity|-shadow|-transform)?$/,duration:/^duration-/,ease:/^ease-(linear|in|out|in-out)$/,delay:/^delay-/,animate:/^animate-/,cursor:/^cursor-/,userSelect:/^select-/,pointerEvents:/^pointer-events-/,resize:/^resize(-none|-x|-y)?$/,scrollBehavior:/^scroll-(auto|smooth)$/,touchAction:/^touch-/,overflow:/^overflow(-x|-y)?-(auto|hidden|clip|visible|scroll)$/,overscroll:/^overscroll(-x|-y)?-(auto|contain|none)$/,visibility:/^(visible|invisible|collapse)$/,aspectRatio:/^aspect-/,columns:/^columns-/,breakAfter:/^break-after-/,breakBefore:/^break-before-/,breakInside:/^break-inside-/,boxDecorationBreak:/^box-decoration-/,boxSizing:/^box-(border|content)$/,float:/^float-(right|left|none)$/,clear:/^clear-(left|right|both|none)$/,isolation:/^(isolate|isolation-auto)$/,objectFit:/^object-(contain|cover|fill|none|scale-down)$/,objectPosition:/^object-/};function Nb(s){const e=s.replace(/^(dark:|hover:|focus:|active:|disabled:|group-hover:|sm:|md:|lg:|xl:|2xl:)+/,"");for(const[n,r]of Object.entries(vT))if(r.test(e))return n;return null}function Ib(s){const e=s.match(/^((dark:|hover:|focus:|active:|disabled:|group-hover:|sm:|md:|lg:|xl:|2xl:)+)/);return e?e[1]:""}function ST(s,e){if(!e||e.trim()==="")return s;if(!s||s.trim()==="")return e;const n=s.split(/\s+/).filter(Boolean),r=e.split(/\s+/).filter(Boolean),i=new Map;for(const c of r){const d=Nb(c);if(d){const f=Ib(c),h=`${d}:${f}`;i.set(h,c)}}return[...n.filter(c=>{const d=Nb(c);if(!d)return!0;const f=Ib(c),h=`${d}:${f}`;return!i.has(h)}),...r].join(" ")}function wT(s){return Object.entries(s).filter(([,e])=>e).map(([e])=>e).join(" ")}function CT(...s){return s.filter(e=>typeof e=="string"&&e.length>0).join(" ")}const be=pt("G7CoreGlobals");function ET(){window.React=Xe,window.ReactDOM={...Dd,createPortal:Ia.createPortal,unstable_batchedUpdates:s=>s()},window.ReactJSXRuntime=r_,be.log("전역 객체 window.React, window.ReactDOM, window.ReactJSXRuntime에 노출됨")}function _T(s){const e=new Map,n=Ha.getInstance();s.componentEvent={on:(r,i)=>(e.has(r)||e.set(r,new Set),e.get(r).add(i),n.trackEventSubscribe(r),()=>{e.get(r)?.delete(i),n.trackEventUnsubscribe(r)}),emit:async(r,i)=>{const l=e.get(r),c=l?.size??0;if(!l||l.size===0)return n.trackEventEmit(r,i,0),[];let d,f=[];try{f=await Promise.all(Array.from(l).map(async h=>{try{return await h(i)}catch(m){throw be.error(`componentEvent: Error in listener for "${r}":`,m),m}}))}catch(h){d=h instanceof Error?h:new Error(String(h))}if(n.trackEventEmit(r,i,c,f,d),d)throw d;return f},off:r=>{e.delete(r),n.trackEventOff(r)},clear:()=>{e.clear(),n.trackEventClear()}},be.log("전역 객체 window.G7Core.componentEvent에 노출됨")}function AT(s){s.__runtime||(s.__runtime={DynamicRenderer:Vr,ComponentRegistry:Ar,TranslationEngine:vi,DataSourceManager:Or,dataSourceManager:qx,DataBindingEngine:$n,dataBindingEngine:oc,ActionDispatcher:jo,TranslationReactContext:nh,TranslationProvider:Pc,useTranslation:gb,ResponsiveContext:$f,ResponsiveProvider:xc,useResponsive:Mf,responsiveManager:Ci,BREAKPOINT_PRESETS:lc,AuthManager:Xn,createLogger:pt,G7DevToolsCore:Ha},be.log("전역 객체 window.G7Core.__runtime(코어 런타임 표면)에 노출됨"))}function xT(s){if(s.layoutEditor&&s.layoutEditor.__isStub!==!0||s.layoutEditor&&s.layoutEditor.__isStub===!0)return;const e=[],n=[];s.layoutEditor={__isStub:!0,__queue:e,__readyCallbacks:n,registerWidget:(r,i)=>e.push(["widget",r,i]),registerNodeEditor:(r,i)=>e.push(["nodeEditor",r,i]),registerCanvasOverlay:(r,i)=>e.push(["canvasOverlay",r,i]),onReady:r=>{typeof r=="function"&&n.push(r)}},be.log("전역 객체 window.G7Core.layoutEditor 예약 접수함(stub) 노출됨")}function TT(s){s.asset={template:(e,n,r)=>h0(e,n,r),templateDir:(e,n)=>p0(e,n),module:(e,n,r)=>g0(e,n,r),plugin:(e,n,r)=>m0(e,n,r),convertToCurrentMode:e=>Ln(e),loadScript:(e,n,r)=>Io(e,bs())?_r(e,n,r):Promise.reject(new Error(`Blocked untrusted script src (same-origin path or declared trusted host required): ${e}`)),isAllowedScriptSrc:e=>Io(e,bs()),loadStylesheet:(e,n,r)=>ia(e,n,r)},s.assets={notifyFailure:xs,clearFailure:Db,clearAll:Ob,getFailures:Qx,retryAll:$b},oT(),be.log("전역 객체 window.G7Core.asset / window.G7Core.assets 노출됨")}function kT(s,e){s.useTranslation=gb,s.t=(n,r)=>{const i=e.getState();if(!i.translationEngine||!i.translationContext)return n;if(r){const l="|"+Object.entries(r).map(([c,d])=>`${c}=${d}`).join("|");return i.translationEngine.translate(n,i.translationContext,l)}return i.translationEngine.translate(n,i.translationContext)},be.log("전역 객체 window.G7Core.useTranslation에 노출됨")}function RT(s,e){s.createChangeEvent=gT,s.createClickEvent=mT,s.createSubmitEvent=yT,s.createKeyboardEvent=bT,s.uuid=()=>typeof crypto<"u"&&crypto.randomUUID?crypto.randomUUID():"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,n=>{const r=Math.random()*16|0;return(n==="x"?r:r&3|8).toString(16)}),s.createLogger=pt,be.log("전역 객체 window.G7Core.createLogger에 노출됨"),s.renderItemChildren=(n,r,i,l,c)=>{const d=e.getState(),m={_global:window.__templateApp?.getGlobalState?.()||{},_local:r._local||{},_computed:r._computed||{},...r},y=new Set,v=(_,x)=>{const A=m._global?._remountKeys;return _&&A?.[_]&&!y.has(_)?(y.add(_),`${_}-remount-${A[_]}`):_||x},S={translationContext:d.translationContext,translationEngine:d.translationEngine,bindingEngine:d.bindingEngine,actionDispatcher:d.actionDispatcher,getRemountKey:v,...c};return Eo(n,m,i,l,S)},s.evaluateCondition=(n,r)=>{if(!n)return!0;const i=e.getState(),c=window.__templateApp?.getGlobalState?.()||{},d={_global:c,_local:c._local||{},_computed:c._computed||{},...r||{}},f=i.bindingEngine??oc;return cm(n,d,f)},s.getComponentMap=()=>Ar.getInstance().getComponentMap(),s.renderComponentLayout=(n,r,i)=>{if(!n||!Array.isArray(n)||n.length===0)return null;const l=Ar.getInstance().getComponentMap(),c=e.getState(),f=window.__templateApp?.getGlobalState?.()||{},h={_global:f._global||{},_local:f._local||{},_computed:f._computed||{},...r},m=new Set,y=(v,S)=>{const _=h._global?._remountKeys;return v&&_?.[v]&&!m.has(v)?(m.add(v),`${v}-remount-${_[v]}`):v||S};return Eo(n,h,l,i,{translationContext:c.translationContext,translationEngine:c.translationEngine,bindingEngine:c.bindingEngine,actionDispatcher:c.actionDispatcher,getRemountKey:y})},s.renderExpandContent=n=>{const{children:r,row:i,expandContext:l,componentContext:c,keyPrefix:d}=n;if(!r||!Array.isArray(r)||r.length===0)return null;const f=n.componentMap||Ar.getInstance().getComponentMap(),h=e.getState(),m=s.state?.get?.()||{},y=c?.stateRef?.current??c?.state??{},v={...m._local||{},...y},S=c?.computedRef?.current??m._computed??{},_=h.bindingEngine??s.getDataBindingEngine?.(),x={...m,_local:v,_computed:S,row:i,item:i,$item:i};let A={};if(l&&typeof l=="object"&&_)try{for(const[L,B]of Object.entries(l))if(typeof B=="string"){const q=Oa(B);if(q!==null)try{A[L]=jn(q)?_.evaluatePipeExpression(q,x,{skipCache:!0}):_.evaluateExpression(q,x,{skipCache:!0})}catch{A[L]=void 0}else A[L]=_.resolveBindings(B,x,{skipCache:!0})}else A[L]=B}catch(L){be.warn("renderExpandContent: expandContext 평가 오류:",L)}const D={row:i,item:i,$item:i,_local:v,_global:m._global||{},_computed:m._computed||{},...A},O=new Set,k=(L,B)=>{const q=D._global?._remountKeys;return L&&q?.[L]&&!O.has(L)?(O.add(L),`${L}-remount-${q[L]}`):L||B};return Eo(r,D,f,d,{translationContext:h.translationContext,translationEngine:h.translationEngine??void 0,bindingEngine:h.bindingEngine??void 0,actionDispatcher:h.actionDispatcher,componentContext:c,getRemountKey:k})},s.$get=function(r,i,l=void 0){if(r==null)return l;const c=Array.isArray(i)?i:[i];if(c.length===0)return r;let d=r;for(const f of c){if(d==null||f==null)return l;d=d[f]}return d??l},be.log("전역 객체 window.G7Core.renderItemChildren에 노출됨"),be.log("전역 객체 window.G7Core.renderComponentLayout에 노출됨"),be.log("전역 객체 window.G7Core.renderExpandContent에 노출됨"),be.log("전역 객체 window.G7Core.$get에 노출됨")}function DT(s){s.dispatch=async(e,n)=>{const r=window.__templateApp;if(!r)return be.warn("G7Core.dispatch: TemplateApp이 초기화되지 않았습니다."),{success:!1,error:new Error("TemplateApp이 초기화되지 않았습니다.")};const i=r.getActionDispatcher?.();if(!i)return be.warn("G7Core.dispatch: ActionDispatcher를 찾을 수 없습니다."),{success:!1,error:new Error("ActionDispatcher를 찾을 수 없습니다.")};const l=r.getRouter?.(),c=r.getGlobalState?.(),d=r.setGlobalState?.bind(r),h=n?.componentContext||window.__g7ActionContext,m=window.__g7PendingLocalState;let y=h?.state??c;m&&h&&(y=m,be.log("[dispatch] Using __g7PendingLocalState for context.state:",m));let v=h?.data??c;m&&h?.data?._local&&(v={...h.data,_local:m},be.log("[dispatch] Updated context.data._local with pendingLocalState"));const S={navigate:l?(_,x)=>l.navigate(_,x):void 0,setState:h?.setState??d,state:y,data:v,_isDispatchFallbackContext:!h?.setState};if(e.debounce){const _=e.debounceKey||`dispatch-${e.handler}`,x=typeof e.debounce=="number"?e.debounce:e.debounce.delay;return i.debouncedCall(_,x,()=>{const{debounce:A,debounceKey:D,...O}=e;i.dispatchAction(O,S)}),{success:!0,debounced:!0}}try{return await i.dispatchAction(e,S)}catch(_){return be.error("G7Core.dispatch: 액션 실행 오류:",_),{success:!1,error:_ instanceof Error?_:new Error(String(_))}}},be.log("전역 객체 window.G7Core.dispatch에 노출됨")}function Xo(s){const e={};for(const[n,r]of Object.entries(s))if(n.includes(".")){const i=n.split(".");let l=e;for(let c=0;c0&&e.every(n=>/^\d+$/.test(n))}function Zn(s,e){if(Array.isArray(s)&&!Array.isArray(e)&&OT(e)){const r=Object.keys(e).map(l=>parseInt(l,10));if(!((r.length>0?Math.max(...r):0)>=s.length+r.length+10)){const l=[...s];for(const[c,d]of Object.entries(e)){const f=parseInt(c,10);f>=0&&f=l.length&&(l[f]=d)}return l}}const n={...s};for(const[r,i]of Object.entries(e))i!==null&&typeof i=="object"&&!Array.isArray(i)?n[r]!==null&&typeof n[r]=="object"?n[r]=Zn(n[r],i):n[r]={...i}:n[r]=i;return n}function LT(s){s.state={get:()=>window.__templateApp?.getGlobalState?.()||{},set:(e,n)=>{const r=window.__templateApp;if(r?.setGlobalState&&r?.getGlobalState){const i=n?.merge||"deep",l=Xo(e);let c;if(i==="replace")c=l;else if(i==="shallow")c={...r.getGlobalState(),...l};else{const d=r.getGlobalState();c=Zn(d,l)}r.setGlobalState(c,{render:n?.render})}else be.warn("G7Core.state.set: TemplateApp이 초기화되지 않았습니다.")},setLocal:(e,n)=>{const r=window.__templateApp;if(n?.render===!1&&!n?.selfManaged){const A=window.__g7AutoBindingPaths;A&&A.size>0&&jb(Xo(e)).some(O=>A.has(O))&&(be.log("[setLocal] render:false + 자동바인딩 경로 겹침 감지 → render:true 자동 승격 (engine-v1.43.0)"),n={...n,render:!0})}if(n?.debounce){const A=r?.getActionDispatcher?.();if(A){const D=n.debounceKey||`setLocal-${Object.keys(e).join(",")}`,{debounce:O,debounceKey:k,...L}=n;A.debouncedCall(D,n.debounce,()=>{s.state.setLocal(e,L)});return}}const i=n?.scope??"current",l=n?.merge||"deep",c=window.__g7ActionContext,d=Xo(e);if(i==="parent"||i==="root"){const A=window.__g7LayoutContextStack||[];if(A.length>0){const D=i==="parent"?A[A.length-1]:A[0];if(D?.setState){D.setState(O=>l==="replace"?d:l==="shallow"?{...O||{},...d}:Zn(O||{},d)),be.log(`[setLocal] scope=${i}: 타겟 컨텍스트에 상태 업데이트 (mergeMode=${l})`,d);return}}be.warn(`[setLocal] scope=${i}: 레이아웃 컨텍스트 스택이 비어있습니다. current로 폴백합니다.`)}const f=window.__g7PendingLocalState,h=r?.getGlobalState?.()?._local||{},v=!((window.__g7LayoutContextStack||[]).length>0)&&c?.state?c.state:void 0,S=v?Mo(h,v):h,_=f||S;let x;if(l==="replace"?x=d:l==="shallow"?x={..._,...d}:x=Zn(_,d),r?.setGlobalState&&(r.setGlobalState({_local:x},{render:n?.render}),be.log(`[setLocal] globalLocal updated via setGlobalState (mergeMode=${l}, render=${n?.render??!0}):`,d)),window.__g7PendingLocalState=x,be.log("[setLocal] __g7PendingLocalState updated:",x),window.__g7SequenceLocalSync=x,window.__g7LastSetLocalSnapshot=x,l==="replace")window.__g7ForcedLocalFields=d;else{const A=window.__g7ForcedLocalFields||{};window.__g7ForcedLocalFields=Zn(A,d)}if(be.log("[setLocal] __g7ForcedLocalFields updated:",d),l==="replace")window.__g7SetLocalOverrideKeys=d;else{const A=window.__g7SetLocalOverrideKeys||{};window.__g7SetLocalOverrideKeys=Zn(A,d)}be.log("[setLocal] __g7SetLocalOverrideKeys updated:",d),c?.setState&&c.setState(A=>l==="replace"?d:l==="shallow"?{...A||{},...d}:Zn(A||{},d)),r?.setGlobalState||be.warn("G7Core.state.setLocal: TemplateApp이 없습니다.")},getLocal:()=>{const r=(window.__templateApp?.getGlobalState?.()||{})._local||{},i=window.__g7PendingLocalState;if(i)return Zn(r,i);const l=window.__g7LastSetLocalSnapshot;return l&&l!==r?Zn(r,l):r},update:e=>{const n=window.__templateApp;if(n?.getGlobalState&&n?.setGlobalState){const r=n.getGlobalState(),i=e(r);n.setGlobalState(i)}else be.warn("G7Core.state.update: TemplateApp이 초기화되지 않았습니다.")},subscribe:e=>{const n=window.__templateApp;return n?.onGlobalStateChange?n.onGlobalStateChange(e):(be.warn("G7Core.state.subscribe: TemplateApp이 초기화되지 않았습니다."),()=>{})},getDataSource:e=>window.__templateApp?.getDataSource?.(e),getIsolated:e=>e?window.__g7IsolatedStates?.[e]?.state??null:window.__g7ActionContext?.isolatedContext?.state??null,setIsolated:(e,n,r)=>{let i,l,c;typeof e=="string"?(i=e,l=n||{},c=r):(l=e,c=n&&typeof n=="object"&&"merge"in n?n:r);const d=c?.merge||"deep",f=Xo(l);if(i){const y=window.__g7IsolatedStates?.[i];y?.mergeState?y.mergeState(f,d):be.warn(`G7Core.state.setIsolated: scopeId '${i}'를 찾을 수 없습니다.`);return}const h=window.__g7ActionContext;h?.isolatedContext?.mergeState?h.isolatedContext.mergeState(f,d):be.warn("G7Core.state.setIsolated: 액션 컨텍스트에 isolatedContext가 없습니다.")},getParent:()=>{const e=window.__g7LayoutContextStack||[];if(e.length===0)return be.log("[getParent] 레이아웃 컨텍스트 스택이 비어있습니다."),null;const n=e[e.length-1];if(!n)return null;const r=n.dataContext||{};return{_local:n.state||r._local||{},_global:r._global||s.state.get()||{},setState:n.setState}},setParentLocal:(e,n,r)=>{const i=window.__g7LayoutContextStack||[];if(i.length===0){be.warn("[setParentLocal] 레이아웃 컨텍스트 스택이 비어있습니다.");return}const l=i[i.length-1];if(!l?.setState){be.warn("[setParentLocal] 부모 컨텍스트에 setState가 없습니다.");return}let c,d;typeof e=="string"?(c={[e]:n},d=r):(c=e,d=n&&typeof n=="object"&&"merge"in n?n:r);const f=d?.merge||"deep",h=Xo(c),m=l.state?._local!==void 0,y=m?l.state._local:l.state||{};let v;f==="replace"?v=h:f==="shallow"?v={...y,...h}:v=Zn(y,h),window.__g7PendingLocalState=v;const S=window.__templateApp;if(S?.setGlobalState){const _=S.getGlobalState?.()?._local,A=f!=="replace"&&!!_&&typeof _=="object"&&!Array.isArray(_)?f==="shallow"?{..._,...h}:Zn(_,h):void 0;S.setGlobalState({_local:A?Mo(A,v):v})}if(l.setState(v),m?l.state._local=v:l.state=v,l.dataContext&&(l.dataContext._local=v),f==="replace")window.__g7ForcedLocalFields=h;else{const _=window.__g7ForcedLocalFields||{};window.__g7ForcedLocalFields=Zn(_,h)}Df()},setParentGlobal:(e,n)=>{let r;typeof e=="string"?r={[e]:n}:r=e,s.state.set(r),be.log("[setParentGlobal] 전역 상태 업데이트:",r)}},window.__g7IsolatedStates||(window.__g7IsolatedStates={}),s.state.getGlobal=s.state.get,s.state.setGlobal=s.state.set,be.log("전역 객체 window.G7Core.state에 노출됨")}function $T(s){s.dataSource={get:e=>window.__templateApp?.getDataSource?.(e),set:(e,n,r)=>{const i=window.__templateApp;i?.setDataSource?i.setDataSource(e,n,r):be.warn("G7Core.dataSource.set: TemplateApp이 초기화되지 않았습니다.")},refetch:async(e,n)=>{const r=window.__templateApp;if(r?.refetchDataSource)return r.refetchDataSource(e,n);be.warn("G7Core.dataSource.refetch: TemplateApp이 초기화되지 않았습니다.")},updateItem:(e,n,r,i,l)=>{const c=window.__templateApp;return c?.updateDataSourceItem?c.updateDataSourceItem(e,n,r,i,l):(be.warn("G7Core.dataSource.updateItem: TemplateApp이 초기화되지 않았습니다."),!1)},updateData:(e,n,r,i="append")=>{const l=window.__templateApp;if(!l?.getDataSource||!l?.setDataSource)return be.warn("G7Core.dataSource.updateData: TemplateApp이 초기화되지 않았습니다."),!1;const c=l.getDataSource(e);if(!c)return be.warn(`G7Core.dataSource.updateData: 데이터 소스 '${e}'를 찾을 수 없습니다.`),!1;let d,f=c,h=null;if(n){const y=n.split(".");h=y.pop();for(const v of y)if(f&&typeof f=="object"&&v in f)f=f[v];else return be.warn(`G7Core.dataSource.updateData: 경로 '${n}'를 찾을 수 없습니다.`),!1;d=f[h]}else d=c;if(!Array.isArray(d))return be.warn("G7Core.dataSource.updateData: 대상 경로의 데이터가 배열이 아닙니다."),!1;if(!Array.isArray(r))return be.warn("G7Core.dataSource.updateData: newData가 배열이 아닙니다."),!1;const m=i==="prepend"?[...r,...d]:[...d,...r];return n&&h?(f[h]=m,l.setDataSource(e,c,{merge:!1})):l.setDataSource(e,m,{merge:!1}),be.log(`G7Core.dataSource.updateData: '${e}'에 ${r.length}개 항목 ${i==="prepend"?"앞에":"뒤에"} 추가됨`),!0}},be.log("전역 객체 window.G7Core.dataSource에 노출됨")}function MT(s,e){s.locale={current:()=>window.__templateApp?.getLocale?.()||"ko",supported:()=>{const r=window.__templateApp?.globalState?.appConfig?.supportedLocales;return Array.isArray(r)&&r.length>0?r:e.getState().templateMetadata?.locales||["ko","en"]},change:async n=>{const r=window.__templateApp;r?.changeLocale?await r.changeLocale(n):be.warn("G7Core.locale.change: TemplateApp이 초기화되지 않았습니다.")}},be.log("전역 객체 window.G7Core.locale에 노출됨")}function NT(s){s.toast={show:(e,n)=>{s.dispatch({handler:"toast",params:{message:e,type:n?.type||"info",...n?.duration&&{duration:n.duration}}})},success:(e,n)=>{s.toast.show(e,{type:"success",duration:n})},error:(e,n)=>{s.toast.show(e,{type:"error",duration:n})},warning:(e,n)=>{s.toast.show(e,{type:"warning",duration:n})},info:(e,n)=>{s.toast.show(e,{type:"info",duration:n})}},be.log("전역 객체 window.G7Core.toast에 노출됨")}function IT(s){s.modal={open:e=>{s.dispatch({handler:"openModal",target:e})},close:e=>{s.dispatch({handler:"closeModal",...e&&{target:e}})},closeAll:()=>{s.dispatch({handler:"closeAllModals"})},isOpen:e=>{const n=s.state.get();return n._global?.activeModal===e||n._global?.modalStack?.includes(e)||!1},getStack:()=>s.state.get()._global?.modalStack||[]},be.log("전역 객체 window.G7Core.modal에 노출됨")}function jT(s){s.style={mergeClasses:ST,conditionalClass:wT,joinClasses:CT},be.log("전역 객체 window.G7Core.style에 노출됨")}function HT(s,e){const{webSocketManager:n}=e;s.websocket={manager:n,subscribe:(r,i,l,c)=>n.subscribe(r,i,l,c),unsubscribe:r=>n.unsubscribe(r),leaveChannel:r=>n.leaveChannel(r),disconnect:()=>n.disconnect(),isInitialized:()=>n.isInitialized(),getSubscriptionCount:()=>n.getSubscriptionCount()},be.log("전역 객체 window.G7Core.websocket에 노출됨")}function zT(s,e){const{transitionManager:n}=e;s.navigation={isPending:()=>n?.getIsPending?.()||!1,onComplete:r=>{if(!n)return r(),()=>{};let i=n.getIsPending();const l=n.subscribe(c=>{c?i=!0:i&&(r(),l())});return l}},be.log("전역 객체 window.G7Core.navigation에 노출됨")}function PT(s){s.plugin={getSettings:e=>window.G7Config?.plugins?.[e],get:(e,n,r)=>window.G7Config?.plugins?.[e]?.[n]??r,getAll:()=>window.G7Config?.plugins??{}},be.log("전역 객체 window.G7Core.plugin에 노출됨")}function UT(s){s.module={getSettings:e=>window.G7Config?.modules?.[e],get:(e,n,r)=>window.G7Config?.modules?.[e]?.[n]??r,getAll:()=>window.G7Config?.modules??{}},be.log("전역 객체 window.G7Core.module에 노출됨")}function BT(s,e){const{transitionManager:n,responsiveManager:r}=e;s.AuthManager=Xn,s.api=Yn(),s.TransitionManager=n,s.useTransitionState=Ay,s.ResponsiveManager=r,s.useResponsive=Mf,s.useControllableState=m_,s.shallowArrayEqual=y_,s.shallowObjectEqual=Ty,s.updateQueryParams=async(i,l)=>{const c=window.__templateApp;if(c?.updateQueryParams)return c.updateQueryParams(i,l);be.warn("G7Core.updateQueryParams: TemplateApp이 초기화되지 않았습니다.")},be.log("전역 객체 window.G7Core.AuthManager에 노출됨"),be.log("전역 객체 window.G7Core.api에 노출됨"),be.log("전역 객체 window.G7Core.ResponsiveManager에 노출됨"),be.log("전역 객체 window.G7Core.useResponsive에 노출됨"),be.log("전역 객체 window.G7Core.useControllableState에 노출됨"),be.log("전역 객체 window.G7Core.updateQueryParams에 노출됨")}function qT(s,e){s.getSlotContext=()=>window.__slotContextValue??null,s.getDynamicRenderer=()=>window.__DynamicRenderer??null,s.getComponentRegistry=()=>e.getComponentRegistry?.()??Ar.getInstance(),s.getDataBindingEngine=()=>e.getDataBindingEngine?.()??e.getState().bindingEngine,s.getTranslationEngine=()=>e.getTranslationEngine?.()??e.getState().translationEngine,s.getActionDispatcher=()=>e.getActionDispatcher?.()??e.getState().actionDispatcher,be.log("전역 객체 window.G7Core 슬롯 API에 노출됨 (getSlotContext, getDynamicRenderer 등)")}function GT(s){s.identity={setLauncher:At.setLauncher.bind(At),handle:At.handle.bind(At),isIdentityRequired:At.isIdentityRequired.bind(At),hasLauncher:At.hasLauncher.bind(At),markDomainNoticeShown:At.markDomainNoticeShown.bind(At),redirectExternally:At.redirectExternally.bind(At),createDeferred:At.createDeferred.bind(At),resolveDeferred:At.resolveDeferred.bind(At),reset:At.reset.bind(At),redirectStashKey:_f},be.log("전역 객체 window.G7Core 본인인증(IDV) API에 노출됨 (identity)")}function FT(s){if(typeof window>"u")return;ET(),window.G7Core||(window.G7Core={});const e=window.G7Core;AT(e),_T(e),xT(e),BT(e,s),TT(e),kT(e,s),RT(e,s),DT(e),LT(e),$T(e),MT(e,s),NT(e),IT(e),jT(e),HT(e,s),zT(e,s),PT(e),UT(e),qT(e,s),GT(e),be.log("G7Core 전역 객체 초기화 완료")}function VT(){const s=window.G7Core;if(!s){be.warn("G7Core가 초기화되지 않았습니다.");return}const e=()=>Ha.getInstance(),n={isEnabled:()=>{try{return e().isEnabled()}catch{return!1}},trackRender:r=>{try{e().isEnabled()&&e().trackRender(r)}catch{}},trackIteration:(r,i,l,c,d)=>{try{e().isEnabled()&&e().trackIteration(r,{source:i,itemVar:l,indexVar:c,sourceLength:d})}catch{}},trackIfCondition:(r,i,l,c)=>{try{e().isEnabled()&&e().trackIfCondition(r,i,l)}catch{}},trackMount:(r,i)=>{try{e().isEnabled()&&e().trackMount(r,{...i,id:r})}catch{}},trackUnmount:r=>{try{e().isEnabled()&&e().trackUnmount(r)}catch{}},trackExpressionEval:r=>{try{e().isEnabled()&&e().trackExpressionEval(r)}catch{}},trackBindingEval:r=>{try{e().isEnabled()&&e().trackBindingEval(r)}catch{}},recordCacheHit:()=>{try{e().isEnabled()&&e().recordCacheHit()}catch{}},recordCacheMiss:()=>{try{e().isEnabled()&&e().recordCacheMiss()}catch{}},trackHandlerRegistration:(r,i,l,c)=>{try{e().isEnabled()&&e().trackHandlerRegistration(r,i,l,c)}catch{}},trackHandlerUnregistration:r=>{try{e().isEnabled()&&e().trackHandlerUnregistration(r)}catch{}},logAction:r=>{try{e().isEnabled()&&e().logAction(r)}catch{}},trackRequest:(r,i)=>{try{return e().isEnabled()?e().trackRequest(r,i):""}catch{return""}},completeRequest:(r,i,l)=>{try{e().isEnabled()&&e().completeRequest(r,i,l)}catch{}},failRequest:(r,i)=>{try{e().isEnabled()&&e().failRequest(r,i)}catch{}},trackDataSourceDefinition:r=>{try{e().isEnabled()&&e().trackDataSourceDefinition(r)}catch{}},trackDataSourceLoading:r=>{try{e().isEnabled()&&e().trackDataSourceLoading(r)}catch{}},trackDataSourceLoaded:(r,i,l)=>{try{e().isEnabled()&&e().trackDataSourceLoaded(r,i,l)}catch{}},trackDataSourceError:(r,i)=>{try{e().isEnabled()&&e().trackDataSourceError(r,i)}catch{}},trackForm:(r,i,l)=>{try{e().isEnabled()&&e().trackForm(r,i,l)}catch{}},untrackForm:r=>{try{e().isEnabled()&&e().untrackForm(r)}catch{}},startStateChange:(r,i,l,c)=>{try{return e().isEnabled()?e().startStateChange(r,i,l,c):""}catch{return""}},completeStateChange:r=>{try{e().isEnabled()&&e().completeStateChange(r)}catch{}},trackComponentRender:(r,i,l,c,d,f)=>{try{e().isEnabled()&&e().trackComponentRender(r,i,l,c,d,f)}catch{}},updateLocalState:r=>{try{e().isEnabled()&&e().updateLocalState(r)}catch{}},updateComputedState:r=>{try{e().isEnabled()&&e().updateComputedState(r)}catch{}},updateParentContext:r=>{try{e().isEnabled()&&e().updateParentContext(r)}catch{}},trackComponentStateSource:(r,i,l,c)=>{try{e().isEnabled()&&e().trackComponentStateSource(r,i,l,c)}catch{}},trackDynamicState:(r,i)=>{try{e().isEnabled()&&e().trackDynamicState(r,i)}catch{}},trackContextFlow:(r,i,l,c,d,f)=>{try{e().isEnabled()&&e().trackContextFlow(r,i,l,c,d,f)}catch{}},trackComponentStyle:(r,i,l,c)=>{try{e().isEnabled()&&e().trackComponentStyle(r,i,l,c)}catch{}},trackAuthEvent:(r,i,l,c)=>{try{e().isEnabled()&&e().trackAuthEvent(r,i,l,c)}catch{}},trackAuthHeader:(r,i,l,c,d)=>{try{e().isEnabled()&&e().trackAuthHeader(r,i,l,c,d)}catch{}},trackLog:(r,i,l)=>{try{e().isEnabled()&&e().trackLog(r,i,l)}catch{}},trackAction:r=>{try{e().isEnabled()&&e().trackAction?.(r)}catch{}},trackDataSourceUpdate:r=>{try{e().isEnabled()&&e().trackDataSourceUpdate?.(r)}catch{}},startSequenceExecution:r=>{try{return e().isEnabled()?e().startSequenceExecution(r):""}catch{return""}},captureSequenceActionBefore:(r,i,l,c,d)=>{try{e().isEnabled()&&e().captureSequenceActionBefore(r,i,l,c,d)}catch{}},captureSequenceActionAfter:(r,i,l,c,d,f)=>{try{e().isEnabled()&&e().captureSequenceActionAfter(r,i,l,c,d,f)}catch{}},endSequenceExecution:(r,i)=>{try{e().isEnabled()&&e().endSequenceExecution(r,i)}catch{}},registerStateCaptureForHandler:(r,i,l)=>{try{e().isEnabled()&&e().registerStateCaptureForHandler(r,i,l)}catch{}},detectStaleClosure:(r,i,l,c,d)=>{try{return e().isEnabled()?e().detectStaleClosure(r,i,l,c,d):[]}catch{return[]}},trackStaleClosureWarning:r=>{try{e().isEnabled()&&e().trackStaleClosureWarning(r)}catch{}},trackModalOpen:r=>{try{e().isEnabled()&&e().trackModalOpen(r)}catch{}},trackModalClose:(r,i)=>{try{e().isEnabled()&&e().trackModalClose(r,i)}catch{}},trackModalStateChange:r=>{try{e().isEnabled()&&e().trackModalStateChange(r)}catch{}},trackNestedContext:r=>{try{return e().isEnabled()?e().trackNestedContext(r):""}catch{return""}},trackNestedContextAccess:(r,i)=>{try{e().isEnabled()&&e().trackNestedContextAccess(r,i)}catch{}},trackComputedProperty:(r,i,l,c,d,f,h)=>{try{e().isEnabled()&&e().trackComputedProperty(r,i,l,c,d,f,h)}catch{}},trackComputedRecalc:(r,i,l,c,d,f,h)=>{try{e().isEnabled()&&e().trackComputedRecalc(r,i,l,c,d,f,h)}catch{}},setNamedActionDefinitions:r=>{try{e().isEnabled()&&e().setNamedActionDefinitions(r)}catch{}},trackNamedActionRef:r=>{try{e().isEnabled()&&e().trackNamedActionRef(r)}catch{}}};s.devTools=n,be.log("G7Core.devTools 인터페이스 초기화 완료")}let Jo=null;function KT(){const s=window.G7Core;return s?.__devtools?Promise.resolve(s.__devtools):Jo||(Jo=new Promise((e,n)=>{const r=window.G7Config?.coreDevToolsAsset||"/build/core/devtools.min.js",i="g7-devtools-bundle",l=()=>{const f=window.G7Core?.__devtools;f?e(f):n(new Error("DevTools 번들 로드됨 — 그러나 __devtools 미노출"))};window.G7Core=window.G7Core||{},window.G7Core.__onDevToolsReady=l;const c=document.getElementById(i);if(c){c.addEventListener("load",l,{once:!0}),c.addEventListener("error",()=>n(new Error(`DevTools 번들 로드 실패: ${r}`)),{once:!0});return}const d=document.createElement("script");d.id=i,d.src=r,d.async=!1,d.addEventListener("load",l,{once:!0}),d.addEventListener("error",()=>{Jo=null,n(new Error(`DevTools 번들 로드 실패: ${r}`))},{once:!0}),document.head.appendChild(d)}),Jo)}function WT(){const s=window.G7Core;if(!s){be.warn("G7Core가 초기화되지 않았습니다.");return}const e=Ha.getInstance();if(e.initialize(),!e.isEnabled()){window.G7DevTools={isEnabled:()=>!1,enable:()=>{be.log("환경설정 > 고급 설정 > 디버그 모드를 켜세요")}},Ld(),be.log("G7DevTools 비활성화됨 (디버그 모드 꺼짐)");return}YT(s,e)}async function YT(s,e){let n;try{n=await KT()}catch(h){be.warn("DevTools 번들 로드 실패 — 최소 API 로 폴백:",h),window.G7DevTools={isEnabled:()=>!1,enable:()=>{}},Ld();return}const{DiagnosticEngine:r,getServerConnector:i,getStyleTracker:l}=n;VT(),Ld();try{l().enable(),be.log("StyleTracker 활성화됨")}catch(h){be.warn("StyleTracker 활성화 실패:",h)}const c=new r,d=i(),f={isEnabled:()=>e.isEnabled(),state:{get:()=>e.getState(),getHistory:()=>e.getStateHistory(),watch:(h,m)=>e.watchState(h,m)},actions:{getHistory:()=>e.getActionHistory(),watch:h=>e.watchActions(h),getMetrics:()=>e.getActionMetrics()},binding:{evaluate:h=>{const m=s.getDataBindingEngine?.();if(!m){be.warn("DataBindingEngine이 없습니다.");return}return m.evaluateExpression(h,e.getState())},getCacheStats:()=>e.getCacheStats(),clearStats:()=>{e.resetCacheStats(),be.log("캐시 통계 초기화됨")}},diagnose:{analyze:h=>c.analyze(h),suggestFix:h=>c.suggestFix(h),getCommonIssues:()=>c.getCommonIssues(),getRulesByCategory:h=>c.getRulesByCategory(h)},server:{dumpState:h=>d.dumpState(h),sendLog:h=>d.sendLog(h),sendError:(h,m)=>d.sendError(h,m),isConnected:()=>d.isConnected(),testConnection:()=>d.testConnection()},config:{isDebugMode:()=>e.isEnabled(),setLogLevel:h=>e.setLogLevel(h),setMaxHistory:h=>e.setMaxHistory(h)},lifecycle:{getMountedComponents:()=>e.getLifecycleInfo().mountedComponents,getOrphanedListeners:()=>e.getLifecycleInfo().orphanedListeners},performance:{getRenderCount:()=>e.getPerformanceInfo().renderCounts,getBindingEvalCount:()=>e.getPerformanceInfo().bindingEvalCount,getMemoryWarnings:()=>e.getPerformanceInfo().memoryWarnings,startProfiling:()=>e.startProfiling(),stopProfiling:()=>e.stopProfiling()},network:{getActiveRequests:()=>e.getNetworkInfo().activeRequests,getRequestHistory:()=>e.getNetworkInfo().requestHistory,getPendingDataSources:()=>e.getNetworkInfo().pendingDataSources},conditional:{getIfConditions:()=>e.getConditionalInfo().ifConditions,getIterations:()=>e.getConditionalInfo().iterations},form:{getForms:()=>e.getFormInfo()},websocket:{getInfo:()=>e.getWebSocketInfo()}};window.G7DevTools=f,s.devtools=f,be.log("G7DevTools 전역 객체 초기화 완료 (window.G7DevTools)"),XT(n.DevToolsPanel)}function XT(s){if(document.getElementById("g7-devtools-root")){be.log("DevToolsPanel 이미 렌더링됨");return}const e=document.createElement("div");e.id="g7-devtools-root",document.body.appendChild(e);try{Dd.createRoot(e).render(Xe.createElement(s)),be.log("DevToolsPanel UI 렌더링 완료")}catch(n){be.error("DevToolsPanel UI 렌더링 실패:",n)}}const Je=pt("TemplateEngine"),ae={templateId:null,locale:"ko",isInitialized:!1,reactRoot:null,containerId:null,currentLayoutJson:null,currentDataContext:{},translationContext:{templateId:"",locale:"ko"},registry:null,bindingEngine:null,translationEngine:null,actionDispatcher:null,templateMetadata:null};let Qo=!1;const Zo=[];async function JT(s){try{Je.log("템플릿 메타데이터 로드 중...",s);const e=s.split("-").map(r=>r.split("_").map(i=>i.charAt(0).toUpperCase()+i.slice(1)).join("")).join(""),n=window[e];return n?.templateMetadata?(Je.log("템플릿 메타데이터 로드 완료 (전역 변수)",n.templateMetadata),n.templateMetadata):(Je.warn(`템플릿 번들에 메타데이터가 없습니다. 전역 변수: ${e}`),{identifier:s,locales:["ko","en"],name:{ko:s,en:s},description:{ko:"",en:""},version:"1.0.0",type:"admin"})}catch(e){return Je.error("템플릿 메타데이터 로드 실패",e),{identifier:s,locales:["ko","en"],name:{ko:s,en:s},description:{ko:"",en:""},version:"1.0.0",type:"admin"}}}function Hb(){const e=(typeof window<"u"?window.__templateApp:void 0)?.globalState?.appConfig?.supportedLocales,n=ae.templateMetadata?.locales;return{$locale:ae.locale,$locales:Array.isArray(e)&&e.length>0?e:n||["ko","en"],$templateLocales:n||["ko","en"],$templateId:ae.templateId}}async function el(s){try{if(Je.log("템플릿 엔진 초기화 시작",s),ae.isInitialized)throw new Error("템플릿 엔진이 이미 초기화되었습니다. destroyTemplate()을 먼저 호출하세요.");if(!s.templateId)throw new Fx;Qo=s.debug??!1,typeof window<"u"&&(window.G7Config||(window.G7Config={}),window.G7Config.debug=Qo),WT(),ae.templateId=s.templateId,ae.locale=s.locale||"ko",Je.log("엔진 인스턴스 생성 중..."),ae.registry=Ar.getInstance(),ae.bindingEngine=new $n,ae.translationEngine=vi.getInstance(),s.cacheVersion!==void 0&&s.cacheVersion>0&&ae.translationEngine.setCacheVersion(s.cacheVersion),ae.translationContext={templateId:s.templateId,locale:ae.locale},ae.actionDispatcher=new jo({},ae.translationEngine,ae.translationContext),o_(ae.actionDispatcher),Je.log("다국어 파일 로드 중...",s.templateId,ae.locale);const e="en",n=[ae.translationEngine.loadTranslations(s.templateId,ae.locale).then(()=>{Je.log(`다국어 파일 로드 완료: ${ae.locale}`)}).catch(r=>{Je.warn(`다국어 파일 로드 실패 (${ae.locale}):`,r instanceof Error?r.message:r)})];ae.locale!==e&&n.push(ae.translationEngine.loadTranslations(s.templateId,e).then(()=>{Je.log(`폴백 다국어 파일 로드 완료: ${e}`)}).catch(r=>{Je.warn(`폴백 다국어 파일 로드 실패 (${e}):`,r instanceof Error?r.message:r)})),await Promise.allSettled(n),Je.log("템플릿 메타데이터 로드 중...",s.templateId),ae.templateMetadata=await JT(s.templateId),ae.isInitialized=!0,Je.log("템플릿 엔진 초기화 완료 (ComponentRegistry는 별도 로드)")}catch(e){throw Je.error("템플릿 엔진 초기화 실패",e),ae.isInitialized=!1,ae.registry=null,ae.bindingEngine=null,ae.translationEngine=null,ae.actionDispatcher=null,e}}let ks=null;const zb="g7-layout-editor-bundle";function ph(){const s=window.G7Core;return s?.__LayoutEditorChrome?Promise.resolve(s.__LayoutEditorChrome):ks||(ks=(async()=>{const e=window.G7Config?.coreEditorAsset||"/build/core/layout-editor.min.js";document.getElementById(zb)?.remove(),window.G7Core=window.G7Core||{},window.G7Core.__onChromeReady=()=>{};try{await _r(e,{id:zb},{label:"layout-editor bundle"})}catch(r){throw ks=null,Je.error(`편집기 번들 로드 실패 (${e})`,r),new Error("편집기를 불러오지 못했습니다. 네트워크 상태를 확인한 뒤 다시 시도해 주세요.")}const n=window.G7Core?.__LayoutEditorChrome;if(!n)throw ks=null,Je.error(`편집기 번들 로드됨 — 그러나 __LayoutEditorChrome 미노출 (${e})`),new Error("편집기를 불러왔으나 초기화하지 못했습니다. 페이지를 새로고침해 주세요.");return n})(),ks)}async function Rs(s){try{if(Je.log("템플릿 렌더링 시작",s),!ae.isInitialized)throw new Error("템플릿 엔진이 초기화되지 않았습니다. initTemplateEngine()을 먼저 호출하세요.");if(!s.containerId)throw new Error("containerId는 필수입니다.");if(!s.layoutJson)throw new Error("layoutJson은 필수입니다.");const e=document.getElementById(s.containerId);if(!e)throw new Error(`컨테이너를 찾을 수 없습니다: #${s.containerId}`);const n=Hb();if(ae.containerId=s.containerId,ae.currentLayoutJson=s.layoutJson,ae.currentDataContext={...n,...s.dataContext||{}},ae.translationContext=s.translationContext||{templateId:ae.templateId||"",locale:ae.locale},ae.reactRoot||(Je.log("React Root 생성"),ae.reactRoot=Dd.createRoot(e)),typeof window<"u"){const l=ch(window.location.pathname);if(l){Je.log("레이아웃 편집기 모드 진입",l);let c;try{c=await ph()}catch(f){Je.error("레이아웃 편집기 번들 로드 실패",f),lh.render(s.containerId,{title:"레이아웃 편집기 로드 실패",message:f instanceof Error?f.message:"편집기 번들을 불러오지 못했습니다.",icon:"fas fa-triangle-exclamation",showStack:!1,showReloadButton:!0,debug:Qo});return}const d=Xe.createElement(c,{templateIdentifier:l.templateIdentifier,initialLocale:ae.locale});ae.reactRoot.render(Xe.createElement(Pc,{translationEngine:ae.translationEngine,translationContext:ae.translationContext,children:Xe.createElement(Of,{children:Xe.createElement(xc,{children:Xe.createElement(rh,{children:d})})})}));return}}const r=s.layoutJson.components||[];if(r.length===0){Je.warn("렌더링할 컴포넌트가 없습니다.");return}const i=s.layoutJson.modals||[];if(Je.log("modals 배열:",i),Je.log("modals 개수:",i.length),Je.log("DynamicRenderer로 렌더링 시작"),ae.reactRoot.render(Xe.createElement(Pc,{translationEngine:ae.translationEngine,translationContext:ae.translationContext},Xe.createElement(Of,null,Xe.createElement(xc,null,Xe.createElement(rh,null,[...r.map((l,c)=>{const d=ae.currentLayoutJson?.layout_name||"";return Xe.createElement(Vr,{key:d&&!l._fromBase?`${l.id}_${d}`:l.id,componentDef:l,dataContext:ae.currentDataContext,translationContext:ae.translationContext,registry:ae.registry,bindingEngine:ae.bindingEngine,translationEngine:ae.translationEngine,actionDispatcher:ae.actionDispatcher,isRootRenderer:c===0,layoutKey:d})}),Xe.createElement(yy,{key:"__modal_parent_context_provider"},i.map(l=>{const c=ae.currentDataContext._global?.modalStack||[],f=c.includes(l.id)||ae.currentDataContext._global?.activeModal===l.id,h=c.indexOf(l.id),m=h>=0?50+h:50,y=window.__g7LayoutContextStack||[],S=y[y.length-1]?.dataContext,_=Xe.createElement(Vr,{key:`modal_${l.id}_renderer`,componentDef:{...l,props:{...l.props,isOpen:f,style:{...l.props?.style,zIndex:m},onClose:ae.actionDispatcher?.createHandler({type:"click",handler:"closeModal"},ae.currentDataContext)}},dataContext:ae.currentDataContext,translationContext:ae.translationContext,registry:ae.registry,bindingEngine:ae.bindingEngine,translationEngine:ae.translationEngine,actionDispatcher:ae.actionDispatcher,parentDataContext:S});return l.data_sources&&l.data_sources.length>0?Xe.createElement(Tb,{key:`modal_${l.id}`,isOpen:f,modalId:l.id,dataSources:l.data_sources,dataContext:ae.currentDataContext,globalStateUpdater:ae.actionDispatcher?.getGlobalStateUpdater(),bindingEngine:ae.bindingEngine,debug:Qo,children:_}):_}))]))))),Je.log("템플릿 렌더링 완료"),Zo.length>0){Je.log(`대기 중인 데이터 업데이트 ${Zo.length}건 적용`);const l=[...Zo];Zo.length=0;for(const{data:c,options:d}of l)Ds(c,d)}}catch(e){throw Je.error("템플릿 렌더링 실패",e),e}}function Ds(s,e){try{if(Je.log("템플릿 데이터 업데이트 시작",Object.keys(s)),!ae.isInitialized)throw new Error("템플릿 엔진이 초기화되지 않았습니다.");if(!ae.reactRoot||!ae.currentLayoutJson){Je.log("React 미준비 - 데이터 업데이트 큐잉:",Object.keys(s)),Zo.push({data:s,options:e});return}const n=Hb(),r={...ae.currentDataContext._global||{},...s._global||{}},i=p_(ae.currentDataContext._localInit,s._localInit);if(ae.currentDataContext={...n,...ae.currentDataContext,...s,_global:r,_localInit:i},r._local!==void 0&&(ae.currentDataContext._local=r._local),r._computed!==void 0&&(ae.currentDataContext._computed=r._computed),typeof window<"u"&&ch(window.location.pathname)){Je.log("레이아웃 편집기 모드 — 재렌더 건너뜀 (편집기 트리 보존)");return}const l=ae.currentLayoutJson.components||[],c=ae.currentLayoutJson.modals||[];Je.log("updateTemplateData - modals 배열:",c),Je.log("updateTemplateData - modals 개수:",c.length),Je.log("updateTemplateData - activeModal:",ae.currentDataContext._global?.activeModal);const d=()=>{ae.reactRoot.render(Xe.createElement(Pc,{translationEngine:ae.translationEngine,translationContext:ae.translationContext},Xe.createElement(Of,null,Xe.createElement(xc,null,Xe.createElement(rh,null,[...l.map((f,h)=>{const m=ae.currentLayoutJson?.layout_name||"";return Xe.createElement(Vr,{key:m&&!f._fromBase?`${f.id}_${m}`:f.id,componentDef:f,dataContext:ae.currentDataContext,translationContext:ae.translationContext,registry:ae.registry,bindingEngine:ae.bindingEngine,translationEngine:ae.translationEngine,actionDispatcher:ae.actionDispatcher,isRootRenderer:h===0,layoutKey:m})}),Xe.createElement(yy,{key:"__modal_parent_context_provider_update"},c.map(f=>{const h=ae.currentDataContext._global?.modalStack||[],y=h.includes(f.id)||ae.currentDataContext._global?.activeModal===f.id,v=h.indexOf(f.id),S=v>=0?50+v:50,_=window.__g7LayoutContextStack||[],A=_[_.length-1]?.dataContext,D=Xe.createElement(Vr,{key:`modal_${f.id}_renderer`,componentDef:{...f,props:{...f.props,isOpen:y,style:{...f.props?.style,zIndex:S},onClose:ae.actionDispatcher?.createHandler({type:"click",handler:"closeModal"},ae.currentDataContext)}},dataContext:ae.currentDataContext,translationContext:ae.translationContext,registry:ae.registry,bindingEngine:ae.bindingEngine,translationEngine:ae.translationEngine,actionDispatcher:ae.actionDispatcher,parentDataContext:A});return f.data_sources&&f.data_sources.length>0?Xe.createElement(Tb,{key:`modal_${f.id}`,isOpen:y,modalId:f.id,dataSources:f.data_sources,dataContext:ae.currentDataContext,globalStateUpdater:ae.actionDispatcher?.getGlobalStateUpdater(),bindingEngine:ae.bindingEngine,debug:Qo,children:D}):D}))])))))};e?.sync?(Je.log("재렌더링 시작 (sync mode - 즉시 렌더링)"),d()):(Je.log("재렌더링 시작 (with startTransition)"),N.startTransition(()=>{d()})),Je.log("템플릿 데이터 업데이트 완료")}catch(n){throw Je.error("템플릿 데이터 업데이트 실패",n),n}}function Yc(){try{Je.log("템플릿 정리 시작"),ae.reactRoot&&(Je.log("React Root 언마운트"),ae.reactRoot.unmount(),ae.reactRoot=null),ae.templateId=null,ae.locale="ko",ae.isInitialized=!1,ae.containerId=null,ae.currentLayoutJson=null,ae.currentDataContext={},ae.translationContext={templateId:"",locale:"ko"},ae.registry=null,ae.bindingEngine=null,ae.translationEngine=null,ae.actionDispatcher=null,ae.templateMetadata=null,Je.log("템플릿 정리 완료")}catch(s){throw Je.error("템플릿 정리 실패",s),s}}function la(){return Object.freeze({...ae})}function Pb(){return ae.actionDispatcher}const tl={initTemplateEngine:el,renderTemplate:Rs,updateTemplateData:Ds,destroyTemplate:Yc,getState:la};typeof window<"u"&&(window.G7Core||(window.G7Core={}),window.G7Core.TemplateEngine=tl,FT({getState:()=>({translationEngine:ae.translationEngine,translationContext:ae.translationContext,bindingEngine:ae.bindingEngine,actionDispatcher:ae.actionDispatcher,templateMetadata:ae.templateMetadata}),transitionManager:Gr,responsiveManager:Ci,webSocketManager:qc}),Je.log("전역 객체 window.G7Core.TemplateEngine에 노출됨"));const Wr=Object.freeze(Object.defineProperty({__proto__:null,DataSourceManager:Or,LayoutLoader:th,TemplateApp:Yo,TemplateEngine:tl,default:tl,destroyTemplate:Yc,getActionDispatcher:Pb,getState:la,initTemplateApp:hh,initTemplateEngine:el,loadLayoutEditorBundle:ph,renderTemplate:Rs,updateTemplateData:Ds},Symbol.toStringTag,{value:"Module"}));tn.DataSourceManager=Or,tn.LayoutLoader=th,tn.TemplateApp=Yo,tn.TemplateEngine=tl,tn.default=tl,tn.destroyTemplate=Yc,tn.getActionDispatcher=Pb,tn.getState=la,tn.initTemplateApp=hh,tn.initTemplateEngine=el,tn.loadLayoutEditorBundle=ph,tn.renderTemplate=Rs,tn.updateTemplateData=Ds,Object.defineProperties(tn,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}})})(this.G7Core=this.G7Core||{}); diff --git a/public/install/api/check-configuration.php b/public/install/api/check-configuration.php index 85b88911..b8fed700 100644 --- a/public/install/api/check-configuration.php +++ b/public/install/api/check-configuration.php @@ -78,6 +78,7 @@ class ValidationApi 'required_files' => $this->checkRequiredFiles(), 'https' => $this->checkHttps(), 'opcache' => $this->checkOpcache(), + 'vendor_dev_packages' => $this->checkVendorDevPackages(), 'asset_url_mode' => $this->checkAssetUrlMode(), ]; @@ -604,6 +605,52 @@ class ValidationApi ]; } + /** + * 기존 vendor 의 개발용(require-dev) 패키지 포함 여부 (선택 항목 — 경고만, 설치 차단 없음) + * + * 인스톨러는 vendor 가 이미 있으면 그대로 재사용한다. 그 vendor 가 `composer install` + * (옵션 없음)의 결과면 개발용 패키지가 섞여 있고, 그 목록이 패키지 매니페스트에 등재된다. + * 이후 코어 업데이트가 vendor 를 `--no-dev` 로 교체하면 매니페스트에만 남은 provider 를 + * 찾다 부팅이 깨지므로, 설치 시점에 미리 알린다. + * + * `dev` 가 null 이면 판정 불가(installed.json 부재·형식 불명)로, 경고도 차단도 하지 않는다. + * + * @return array{required: bool, vendor_exists: bool, dev: bool|null, packages: array, message: string} + */ + private function checkVendorDevPackages(): array + { + $vendorExists = is_dir(BASE_PATH.'/vendor') && file_exists(BASE_PATH.'/vendor/autoload.php'); + + if (! $vendorExists) { + return [ + 'required' => false, + 'vendor_exists' => false, + 'dev' => null, + 'packages' => [], + 'message' => lang('vendor_dev_packages_no_vendor'), + ]; + } + + require_once __DIR__.'/../includes/vendor-bundle-installer.php'; + $info = detectDevVendorInstall(BASE_PATH); + + if ($info['dev'] === true) { + $message = lang('vendor_dev_packages_detected_warning', ['count' => count($info['packages'])]); + } elseif ($info['dev'] === false) { + $message = lang('vendor_dev_packages_none'); + } else { + $message = lang('vendor_dev_packages_unknown'); + } + + return [ + 'required' => false, // 개발용 패키지 감지는 선택 항목 (권장 사항) + 'vendor_exists' => true, + 'dev' => $info['dev'], + 'packages' => $info['packages'], + 'message' => $message, + ]; + } + /** * 자산 URL 방식 항목 정의 (권장 사항 — 설치를 차단하지 않음) * @@ -1133,7 +1180,8 @@ class ValidationApi return false; } - // HTTPS / OPcache 는 선택 사항이므로 검증하지 않음 (경고만 표시하고 설치는 진행) + // HTTPS / OPcache / Composer 의존성 구성은 선택 사항이므로 검증하지 않음 + // (경고만 표시하고 설치는 진행) return true; } diff --git a/public/install/api/rollback-functions.php b/public/install/api/rollback-functions.php index ae135aff..a771202c 100644 --- a/public/install/api/rollback-functions.php +++ b/public/install/api/rollback-functions.php @@ -482,7 +482,10 @@ function getManualCommands(string $taskId, ?string $target = null): array switch ($taskId) { case 'composer_install': - $commands[] = "{$composer} install --no-interaction --prefer-dist --optimize-autoloader"; + // --no-dev 는 마법사가 실제로 실행하는 명령(task-runner.php)과 같아야 한다. 이 안내를 + // 옵션 없이 따라 하면 개발용(require-dev) 패키지가 섞인 vendor 가 만들어지고, 그 상태는 + // 이후 코어 업데이트가 vendor 를 교체할 때 이전 패키지 매니페스트와 어긋나 부팅을 깨뜨린다. + $commands[] = "{$composer} install --no-interaction --no-dev --prefer-dist --optimize-autoloader"; break; case 'env_create': diff --git a/public/install/assets/js/installer.js b/public/install/assets/js/installer.js index 11dbb72e..4dbd831f 100644 --- a/public/install/assets/js/installer.js +++ b/public/install/assets/js/installer.js @@ -487,6 +487,39 @@ ); } + // 9-1. Composer 의존성 구성 카드 (권장 사항, 필수 아님) + // failedRequirements.push() 를 하지 않는다 — 개발용 패키지가 섞여 있어도 설치는 진행된다. + // vendor 자체가 없으면 마법사가 운영용 구성으로 자동 설치하므로 카드를 그리지 않는다. + if (data.vendor_dev_packages && data.vendor_dev_packages.vendor_exists) { + const vendorInfo = data.vendor_dev_packages; + let vendorStatusClass, vendorStatusText, vendorHint = ''; + if (vendorInfo.dev === true) { + // 단순히 '개발용 포함' 만 보이면 이후 코어 업데이트가 깨진다는 실질이 전달되지 않는다. + vendorStatusClass = 'status-warning'; + vendorStatusText = lang('vendor_dev_packages_detected_short') + .replace(':count', (vendorInfo.packages || []).length); + vendorHint = lang('vendor_dev_packages_detected_warning') + .replace(':count', (vendorInfo.packages || []).length); + } else if (vendorInfo.dev === false) { + vendorStatusClass = 'status-pass'; + vendorStatusText = lang('vendor_dev_packages_none_short'); + } else { + // installed.json 부재·형식 불명 — 확인 불가 (경고도 차단도 아님) + vendorStatusClass = 'status-pass'; + vendorStatusText = lang('vendor_dev_packages_unknown'); + } + html += renderSingleItemCard( + lang('vendor_dev_packages'), + vendorStatusClass, + vendorStatusText, + '', + false + ); + if (vendorHint) { + html += `

${vendorHint}

`; + } + } + // 10. 자산 URL 방식 카드 (안내 항목, 필수 아님) // 서버는 판정할 수 없으므로(loopback 이 vhost·프록시를 우회) "확인 중" 으로 먼저 // 그리고 refreshAssetUrlModeCard() 가 브라우저 프로브 결과로 갱신한다. diff --git a/public/install/includes/task-runner.php b/public/install/includes/task-runner.php index 95e0c2ec..925e63b9 100644 --- a/public/install/includes/task-runner.php +++ b/public/install/includes/task-runner.php @@ -490,7 +490,28 @@ if (! function_exists('installComposerDependenciesSSE')) { $lockExists = file_exists(BASE_PATH.'/composer.lock'); if ($vendorExists && $lockExists) { - sendSSEEvent('log', ['message' => lang('log_composer_already_installed')]); + // 이미 준비된 vendor 를 그대로 쓴다. 그 vendor 가 개발용(require-dev 포함) 설치면 + // 설치는 계속하되 운영자에게 알린다 — 그대로 두면 이후 코어 업데이트가 vendor 를 + // --no-dev 로 교체할 때 이전 매니페스트에만 남은 provider 를 찾다 부팅이 깨진다. + $devInfo = detectDevVendorInstall(BASE_PATH); + if ($devInfo['dev'] === true) { + sendSSEEvent('log', ['message' => lang('log_composer_dev_packages_detected', [ + 'count' => count($devInfo['packages']), + 'packages' => implode(', ', array_slice($devInfo['packages'], 0, 5)).(count($devInfo['packages']) > 5 ? ' …' : ''), + ])]); + // sendSSEEvent('log') 는 progress emitter 안에서 installation.log 에도 기록한다 + // (SSE·폴링 두 모드 공통) — 여기서 addLog 를 또 부르면 로그에 같은 줄이 두 번 남는다. + sendSSEEvent('log', ['message' => lang('warning_composer_dev_packages_kept')]); + } else { + sendSSEEvent('log', ['message' => lang('log_composer_already_installed')]); + } + + // 재사용 경로에서도 이전 환경의 컴파일 캐시를 정리한다 — 종전에는 정리 없이 + // return 해, key_generate 가 이미 완료된 재개 설치에서는 어디서도 정리되지 않았다. + if (! empty(clearLaravelCompiledCache(BASE_PATH))) { + sendSSEEvent('log', ['message' => lang('log_composer_cache_cleared')]); + } + sendSSEEvent('log', ['message' => lang('log_task_completed', ['task' => $taskName])]); sendSSEEvent('log', ['message' => lang('log_separator')]); diff --git a/public/install/includes/vendor-bundle-installer.php b/public/install/includes/vendor-bundle-installer.php index 5690f687..1a61fcb4 100644 --- a/public/install/includes/vendor-bundle-installer.php +++ b/public/install/includes/vendor-bundle-installer.php @@ -1,5 +1,7 @@ 실제로 삭제된 파일명 목록 (basename) + * @return array 실제로 삭제된 파일명 목록 (basename) */ function clearLaravelCompiledCache(string $basePath): array { @@ -318,6 +319,31 @@ function clearLaravelCompiledCache(string $basePath): array return $cleared; } +/** + * 이미 준비된 vendor 가 개발용(require-dev 포함) 설치인지 조사합니다. + * + * 판정은 코어와 공유하는 `App\Support\ComposerInstallInfo` 가 단독으로 소유한다 — 인스톨러와 + * 코어 업데이트가 같은 vendor 를 두고 서로 다른 답을 내놓지 않도록. + * + * 클래스 파일 경로는 인자 `$basePath` 가 아니라 **인스톨러 자신의 트리** 기준으로 읽는다. + * 단위 테스트는 임시 디렉토리를 `$basePath` 로 넘기는데 그 경로에는 `app/Support` 가 없다. + * + * @param string $basePath 프로젝트 루트 (vendor 의 상위 디렉토리) + * @return array{dev: bool|null, packages: array} dev 가 null 이면 판정 불가 + */ +function detectDevVendorInstall(string $basePath): array +{ + if (! class_exists('App\\Support\\ComposerInstallInfo')) { + $classFile = dirname(__DIR__, 3).'/app/Support/ComposerInstallInfo.php'; + if (! is_file($classFile)) { + return ['dev' => null, 'packages' => []]; + } + require_once $classFile; + } + + return ComposerInstallInfo::inspect(rtrim($basePath, '/\\').'/vendor'); +} + /** * Composer 실행이 현재 환경에서 가능한지 검사합니다. * diff --git a/public/install/lang/en.php b/public/install/lang/en.php index 56bdb054..59a9efdb 100644 --- a/public/install/lang/en.php +++ b/public/install/lang/en.php @@ -243,6 +243,15 @@ return [ 'opcache_disabled_warning' => 'OPcache is disabled. In this state, every single page view re-parses all of your site\'s PHP code from scratch. On identical hardware this can make responses several times slower, and the gap widens as traffic grows. You can continue the installation as-is, but if this server will be used in production we strongly recommend setting opcache.enable=1 in php.ini and restarting your web server.', 'opcache_unknown' => 'OPcache status could not be determined. Reading PHP settings is restricted on this server.', + // Composer Dependency Set Messages + 'vendor_dev_packages' => 'Composer dependencies', + 'vendor_dev_packages_none_short' => 'Production set (no dev packages)', + 'vendor_dev_packages_none' => 'No development packages in vendor. (recommended)', + 'vendor_dev_packages_detected_short' => ':count development package(s) present — not recommended for production', + 'vendor_dev_packages_detected_warning' => 'The existing vendor directory contains :count development (require-dev) package(s). Installation can continue, but on a production server run composer install --no-dev --optimize-autoloader from the project root after installation to remove them. (The setup wizard installs the production set automatically only when vendor is absent.)', + 'vendor_dev_packages_unknown' => 'Could not inspect vendor (installed.json missing or unrecognized).', + 'vendor_dev_packages_no_vendor' => 'No vendor directory — the wizard installs the production set automatically.', + // API Response Messages 'api_method_not_allowed' => 'Only POST requests are allowed.', 'api_invalid_request' => 'Invalid request data.', @@ -318,6 +327,8 @@ return [ 'log_composer_installing_from_lock' => 'Installing dependencies from composer.lock...', 'log_composer_fresh_install' => 'Installing new Composer dependencies...', 'log_composer_cache_cleared' => 'Cleared previous package cache', + 'log_composer_dev_packages_detected' => 'Existing vendor contains :count development package(s) (:packages)', + 'warning_composer_dev_packages_kept' => 'Keeping the vendor directory that contains development packages. On a production site, run "composer install --no-dev --optimize-autoloader" after installation (installation continues)', // Error Messages - Worker (.env) 'error_env_example_not_found' => '.env.example file not found', diff --git a/public/install/lang/ko.php b/public/install/lang/ko.php index 13244077..95d392ad 100644 --- a/public/install/lang/ko.php +++ b/public/install/lang/ko.php @@ -243,6 +243,15 @@ return [ 'opcache_disabled_warning' => 'OPcache가 비활성화되어 있습니다. 이 상태에서는 방문자가 페이지를 열 때마다 사이트의 모든 PHP 코드를 처음부터 다시 해석합니다. 같은 서버, 같은 사양에서도 응답 속도가 몇 배까지 느려지며 방문자가 많을수록 격차가 커집니다. 설치는 이대로 진행할 수 있지만, 운영에 사용할 서버라면 php.ini에서 opcache.enable=1 을 켠 뒤 웹서버를 재시작하시기를 강력히 권장합니다.', 'opcache_unknown' => 'OPcache 상태를 확인할 수 없습니다. 서버에서 설정 조회가 제한되어 있습니다.', + // Composer 의존성 구성 메시지 + 'vendor_dev_packages' => 'Composer 의존성 구성', + 'vendor_dev_packages_none_short' => '운영용 구성 (개발용 패키지 없음)', + 'vendor_dev_packages_none' => 'vendor 에 개발용 패키지가 없습니다. (권장)', + 'vendor_dev_packages_detected_short' => '개발용 패키지 :count개 포함 — 운영 사이트에는 권장하지 않음', + 'vendor_dev_packages_detected_warning' => '이미 준비된 vendor 에 개발용(require-dev) 패키지 :count개가 포함되어 있습니다. 설치는 이대로 진행할 수 있지만, 운영에 사용할 서버라면 설치 후 프로젝트 루트에서 composer install --no-dev --optimize-autoloader 를 실행해 개발용 패키지를 제거하시기를 권장합니다. (설치 마법사는 vendor 가 없을 때만 운영용 구성으로 자동 설치합니다.)', + 'vendor_dev_packages_unknown' => 'vendor 구성을 확인할 수 없습니다 (installed.json 없음 또는 형식 불명).', + 'vendor_dev_packages_no_vendor' => 'vendor 없음 — 설치 마법사가 운영용 구성으로 자동 설치합니다.', + // API 응답 메시지 'api_method_not_allowed' => 'POST 요청만 허용됩니다.', 'api_invalid_request' => '잘못된 요청 데이터입니다.', @@ -318,6 +327,8 @@ return [ 'log_composer_installing_from_lock' => 'composer.lock 파일을 사용하여 의존성을 설치합니다...', 'log_composer_fresh_install' => '새로운 Composer 의존성을 설치합니다...', 'log_composer_cache_cleared' => '이전 패키지 캐시를 삭제했습니다', + 'log_composer_dev_packages_detected' => '기존 vendor 에 개발용 패키지 :count개가 포함되어 있습니다 (:packages)', + 'warning_composer_dev_packages_kept' => '개발용 패키지가 포함된 vendor 를 그대로 사용합니다. 운영 사이트라면 설치 후 "composer install --no-dev --optimize-autoloader" 를 실행하세요 (설치는 계속 진행됩니다)', // 에러 메시지 - Worker (.env) 'error_env_example_not_found' => '.env.example 파일을 찾을 수 없습니다', diff --git a/resources/js/core/__tests__/template-engine.layoutEditorRerender.test.tsx b/resources/js/core/__tests__/template-engine.layoutEditorRerender.test.tsx new file mode 100644 index 00000000..982cb486 --- /dev/null +++ b/resources/js/core/__tests__/template-engine.layoutEditorRerender.test.tsx @@ -0,0 +1,190 @@ +/** + * template-engine.ts — 레이아웃 편집기 모드 재렌더 보호 회귀 테스트 + * + * 결함: 편집기 모드에서 `TemplateApp.init` 은 `renderTemplate({ layoutJson: { components: [] } })` + * 로 부르고, `renderTemplate` 의 편집기 분기가 그 빈 배열 대신 `LayoutEditorChrome` 을 렌더한다. + * 그런데 `updateTemplateData` 에는 그 분기가 없어 `state.currentLayoutJson.components`(= 빈 배열) + * 로 **같은 reactRoot 에 두 번째 render** 를 걸었고, React 가 그 커밋에서 편집기 트리를 통째로 + * 제거해 화면이 백지가 됐다. + * + * 예외도 콘솔 오류도 남지 않는다 — 편집기가 잠깐 보였다가 사라지는 것이 유일한 증상이며, + * `renderTemplate` 의 편집기 분기가 비동기(`loadLayoutEditorBundle`)라 부팅 중 `setGlobalState` + * 가 그 커밋 뒤에 도착할 때만 발현하는 **경합**이라 간헐적으로 보인다. + * + * 브라우저 실측(2026-09-08): `app-children-up 1` (668ms) → `APP-EMPTIED removed=1` (704ms), + * 제거 스택은 `init` → startTransition → React commit. `destroyTemplate` 은 호출되지 않았다. + */ + +import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'; +import { act } from '@testing-library/react'; +import React from 'react'; +import { + initTemplateEngine, + renderTemplate, + updateTemplateData, + destroyTemplate, +} from '../template-engine'; + +const flushReactScheduler = async (): Promise => { + await act(async () => { + await new Promise((resolve) => setTimeout(resolve, 0)); + }); +}; + +vi.mock('../template-engine/ComponentRegistry', () => { + const mockInstance = { + loadComponents: vi.fn().mockResolvedValue(undefined), + getComponent: vi.fn().mockReturnValue(() => null), + hasComponent: vi.fn().mockReturnValue(true), + getInstance: vi.fn(), + }; + mockInstance.getInstance.mockReturnValue(mockInstance); + return { ComponentRegistry: { getInstance: vi.fn(() => mockInstance) } }; +}); + +vi.mock('../template-engine/DataBindingEngine', () => { + const DataBindingEngine = vi.fn(function (this: any) { + this.bind = vi.fn(); + this.unbind = vi.fn(); + this.invalidateCacheByKeys = vi.fn(); + }); + return { DataBindingEngine, dataBindingEngine: new (DataBindingEngine as any)() }; +}); + +vi.mock('../template-engine/TranslationEngine', () => { + const mockInstance = { + translate: vi.fn((key: string) => key), + setLocale: vi.fn(), + loadTranslations: vi.fn().mockResolvedValue({}), + resolveTranslations: vi.fn((text: string) => text), + clearCache: vi.fn(), + }; + return { + TranslationEngine: { getInstance: vi.fn(() => mockInstance), resetInstance: vi.fn() }, + TranslationContext: {} as any, + }; +}); + +vi.mock('../template-engine/ActionDispatcher', () => ({ + ActionDispatcher: vi.fn(function (this: any) { + this.dispatch = vi.fn(); + this.register = vi.fn(); + this.createHandler = vi.fn(() => vi.fn()); + }), + setActionDispatcherInstance: vi.fn(), + getActionDispatcher: vi.fn(() => ({ dispatch: vi.fn(), register: vi.fn() })), +})); + +vi.mock('../template-engine/DynamicRenderer', () => ({ default: vi.fn(() => null) })); + +vi.mock('../template-engine/ResponsiveManager', () => ({ + responsiveManager: { + getWidth: vi.fn(() => 1024), + subscribe: vi.fn(() => () => {}), + getMatchingKey: vi.fn(() => null), + parseRange: vi.fn(() => null), + }, + BREAKPOINT_PRESETS: {}, +})); + +vi.mock('../template-engine/ResponsiveContext', () => ({ + ResponsiveContext: {}, + ResponsiveProvider: ({ children }: { children: React.ReactNode }) => children, + useResponsive: vi.fn(() => ({ + width: 1024, + isMobile: false, + isTablet: false, + isDesktop: true, + matchedPreset: 'desktop', + })), +})); + +const EDITOR_PATH = '/admin/layout-editor/sirsoft-basic'; + +/** 편집기 lazy 번들 stub — `loadLayoutEditorBundle` 이 즉시 반환하는 경로를 탄다. */ +function EditorChromeStub(): React.ReactElement { + return React.createElement('div', { 'data-testid': 'editor-chrome' }, 'EDITOR'); +} + +describe('[case:state-advanced-editor-blank] template-engine — 레이아웃 편집기 모드 재렌더 보호', () => { + let container: HTMLDivElement; + + beforeEach(async () => { + destroyTemplate(); + vi.clearAllMocks(); + + container = document.createElement('div'); + container.id = 'app'; + document.body.appendChild(container); + + // `loadLayoutEditorBundle` 은 `G7Core.__LayoutEditorChrome` 이 있으면 즉시 resolve 한다. + (window as any).G7Core = { ...(window as any).G7Core, __LayoutEditorChrome: EditorChromeStub }; + + window.history.replaceState({}, '', EDITOR_PATH); + + await initTemplateEngine({ templateId: 'sirsoft-admin_basic', locale: 'ko' }); + }); + + afterEach(async () => { + destroyTemplate(); + await flushReactScheduler(); + container.remove(); + delete (window as any).G7Core.__LayoutEditorChrome; + window.history.replaceState({}, '', '/'); + }); + + it('편집기 렌더 후 updateTemplateData 가 들어와도 편집기 트리가 지워지지 않는다', async () => { + // TemplateApp.init 이 편집기 모드에서 넘기는 것과 동일한 빈 레이아웃 + await act(async () => { + await renderTemplate({ + containerId: 'app', + layoutJson: { components: [] } as any, + dataContext: {}, + translationContext: { templateId: 'sirsoft-admin_basic', locale: 'ko' }, + }); + }); + await flushReactScheduler(); + + expect( + container.querySelector('[data-testid="editor-chrome"]'), + '편집기 분기가 LayoutEditorChrome 을 렌더해야 한다(사전 조건)', + ).not.toBeNull(); + + // 부팅 중 setGlobalState → updateTemplateData 가 도착하는 상황 + await act(async () => { + updateTemplateData({ _global: { sidebarOpen: false } }); + }); + await flushReactScheduler(); + + expect( + container.querySelector('[data-testid="editor-chrome"]'), + 'updateTemplateData 재렌더가 편집기 트리를 제거하면 안 된다(백지 회귀)', + ).not.toBeNull(); + expect(container.childElementCount, '#app 이 비어서는 안 된다').toBeGreaterThan(0); + }); + + it('편집기 모드가 아니면 updateTemplateData 재렌더가 종전대로 동작한다 (회귀 가드)', async () => { + window.history.replaceState({}, '', '/admin/dashboard'); + + await act(async () => { + await renderTemplate({ + containerId: 'app', + layoutJson: { components: [{ type: 'basic', name: 'Div' }] } as any, + dataContext: {}, + translationContext: { templateId: 'sirsoft-admin_basic', locale: 'ko' }, + }); + }); + await flushReactScheduler(); + + // DynamicRenderer 가 null 을 반환하는 mock 이라 DOM 노드는 없지만, + // 편집기 stub 이 렌더되지 않았다는 것이 이 케이스의 핵심이다. + expect(container.querySelector('[data-testid="editor-chrome"]')).toBeNull(); + + await act(async () => { + updateTemplateData({ _global: { x: 1 } }); + }); + await flushReactScheduler(); + + expect(container.querySelector('[data-testid="editor-chrome"]')).toBeNull(); + }); +}); diff --git a/resources/js/core/api/ApiClient.ts b/resources/js/core/api/ApiClient.ts index bf96dab7..6a4e4a70 100644 --- a/resources/js/core/api/ApiClient.ts +++ b/resources/js/core/api/ApiClient.ts @@ -117,8 +117,20 @@ class ApiClient { /** * 토큰 저장 + * + * 문자열이 아닌 값은 저장하지 않는다. `localStorage` 는 무엇을 넣든 문자열로 바꿔 + * 저장하므로 `undefined` 를 넘기면 `"undefined"` 라는 truthy 문자열이 남고, 이후 + * 모든 요청이 `Bearer undefined` 로 나가 401 로 튕긴다 — 화면에는 "세션이 만료되었습니다" + * 로 보여 원인을 추적할 단서가 남지 않는다. + * + * @since engine-v1.65.0 */ setToken(token: string): void { + if (typeof token !== 'string' || token === '') { + logger.warn('Ignoring invalid auth token — token must be a non-empty string.'); + return; + } + if (typeof window !== 'undefined') { localStorage.setItem(this.TOKEN_KEY, token); } diff --git a/resources/js/core/api/__tests__/ApiClient.test.ts b/resources/js/core/api/__tests__/ApiClient.test.ts index 1b57c900..e878147f 100644 --- a/resources/js/core/api/__tests__/ApiClient.test.ts +++ b/resources/js/core/api/__tests__/ApiClient.test.ts @@ -98,6 +98,28 @@ describe('ApiClient', () => { it('토큰이 없으면 null을 반환해야 함', () => { expect(apiClient.getToken()).toBeNull(); }); + + it('문자열이 아닌 토큰은 저장하지 않아야 함', () => { + // localStorage 는 무엇을 넣든 문자열로 바꾼다 — undefined 를 넘기면 "undefined" 라는 + // truthy 문자열이 남아 이후 모든 요청이 Bearer undefined 로 나가고 401 로 튕긴다. + apiClient.setToken(undefined as unknown as string); + + expect(localStorage.getItem('auth_token')).toBeNull(); + expect(apiClient.getToken()).toBeNull(); + }); + + it('빈 문자열 토큰도 저장하지 않아야 함', () => { + apiClient.setToken(''); + + expect(localStorage.getItem('auth_token')).toBeNull(); + }); + + it('기존 토큰이 있어도 잘못된 값으로 덮어쓰지 않아야 함', () => { + apiClient.setToken('valid-token'); + apiClient.setToken(null as unknown as string); + + expect(apiClient.getToken()).toBe('valid-token'); + }); }); describe('HTTP 메서드', () => { diff --git a/resources/js/core/auth/AuthManager.ts b/resources/js/core/auth/AuthManager.ts index 36142339..75b1af1c 100644 --- a/resources/js/core/auth/AuthManager.ts +++ b/resources/js/core/auth/AuthManager.ts @@ -36,8 +36,38 @@ export interface AuthConfig { loginEndpoint: string; logoutEndpoint: string; refreshEndpoint: string; + /** 2단계 인증 코드 확인 엔드포인트 */ + twoFactorEndpoint: string; + /** 2단계 인증 코드 재발송 엔드포인트 */ + twoFactorResendEndpoint: string; } +/** + * 2단계 인증 challenge 정보 + * + * 비밀번호 확인만 통과한 상태다 — 토큰은 아직 발급되지 않았고, 코드 확인에 성공해야 + * 세션이 열린다. + * + * @since engine-v1.65.0 + */ +export interface TwoFactorChallenge { + challengeId: string; + providerId: string; + expiresAt: string | null; +} + +/** + * 로그인 결과 + * + * 서버는 보안 환경설정에 따라 **두 가지 형태의 200** 을 돌려준다. 한 형태만 가정하면 + * 다른 형태에서 토큰·사용자 필드가 없어 화면이 알 수 없는 오류로 멈춘다. + * + * @since engine-v1.65.0 + */ +export type LoginResult = + | { status: 'authenticated'; user: AuthUser } + | { status: 'two_factor_required'; challenge: TwoFactorChallenge }; + /** * 인증된 사용자 정보 */ @@ -62,6 +92,20 @@ export interface AuthState { */ type EventHandler = (...args: any[]) => void; +/** + * 로그인·2단계 인증 응답의 data 페이로드 + * + * 두 형태(토큰 발급 / challenge 발급)가 같은 자리에 오므로 전부 선택 필드다. + */ +interface LoginResponseData { + token?: string; + user?: AuthUser; + two_factor_required?: boolean; + challenge_id?: string; + provider_id?: string; + expires_at?: string | null; +} + /** * 기본 인증 설정 */ @@ -74,6 +118,8 @@ const defaultConfigs: Record = { loginEndpoint: '/auth/admin/login', logoutEndpoint: '/admin/auth/logout', refreshEndpoint: '/admin/auth/refresh', + twoFactorEndpoint: '/auth/admin/login/two-factor', + twoFactorResendEndpoint: '/auth/admin/login/two-factor/resend', }, user: { type: 'user', @@ -83,6 +129,8 @@ const defaultConfigs: Record = { loginEndpoint: '/auth/login', logoutEndpoint: '/auth/logout', refreshEndpoint: '/auth/refresh', + twoFactorEndpoint: '/auth/login/two-factor', + twoFactorResendEndpoint: '/auth/login/two-factor/resend', }, }; @@ -238,15 +286,83 @@ export class AuthManager { /** * 로그인 처리 * + * 서버는 두 가지 형태의 200 을 돌려준다 — 토큰과 사용자가 실린 정상 응답, 그리고 + * 2단계 인증이 켜져 있을 때의 challenge 응답이다. 후자에는 토큰도 사용자도 없으므로 + * 세션을 열지 않고 challenge 만 돌려준다. + * * @param type - 인증 타입 * @param credentials - 로그인 자격 증명 * @param options - 추가 옵션 (headers 등) - * @returns 인증된 사용자 정보 + * @returns 인증 완료 또는 2단계 인증 요구 + * @since engine-v1.65.0 반환 타입이 `AuthUser` 에서 `LoginResult` 로 바뀌었습니다. */ async login( type: AuthType, credentials: { email: string; password: string }, options?: { headers?: Record } + ): Promise { + const apiClient = getApiClient(); + const config = this.config.get(type); + + if (!config) { + throw new Error(`Unknown auth type: ${type}`); + } + + try { + // 추가 헤더 설정 (globalHeaders 지원) + const requestConfig = options?.headers ? { headers: options.headers } : undefined; + + const response = await apiClient.post<{ + success: boolean; + data: LoginResponseData; + }>(config.loginEndpoint, credentials, requestConfig); + + if (response.success && response.data) { + // 2단계 인증 요구 — 토큰이 없으므로 상태를 건드리지 않는다. + if (response.data.two_factor_required === true) { + trackAuthEvent('login', true, undefined, { type, two_factor: true }); + + return { + status: 'two_factor_required', + challenge: { + challengeId: String(response.data.challenge_id ?? ''), + providerId: String(response.data.provider_id ?? ''), + expiresAt: response.data.expires_at ?? null, + }, + }; + } + + // 두 필드가 모두 있을 때만 세션을 연다 — 한쪽만 보고 진행하면 undefined 토큰이 + // 저장되어 이후 모든 요청이 401 이 된다. + if (response.data.token && response.data.user) { + return { + status: 'authenticated', + user: this.establishSession(type, response.data.token, response.data.user), + }; + } + } + + throw new Error('Login failed'); + } catch (error: any) { + this.clearState(); + + throw this.enhanceAuthError(error, type, 'Login failed'); + } + } + + /** + * 2단계 인증 코드를 확인하고 로그인을 완료합니다. + * + * @param type - 인증 타입 + * @param payload - challenge 식별자와 사용자가 받은 인증번호 + * @param options - 추가 옵션 (headers 등) + * @returns 인증된 사용자 정보 + * @since engine-v1.65.0 + */ + async completeTwoFactor( + type: AuthType, + payload: { challengeId: string; code: string }, + options?: { headers?: Record } ): Promise { const apiClient = getApiClient(); const config = this.config.get(type); @@ -255,88 +371,167 @@ export class AuthManager { throw new Error(`Unknown auth type: ${type}`); } - // 로그인 엔드포인트 결정 - const loginEndpoint = type === 'admin' - ? defaultConfigs.admin.loginEndpoint - : defaultConfigs.user.loginEndpoint; - try { - // 추가 헤더 설정 (globalHeaders 지원) const requestConfig = options?.headers ? { headers: options.headers } : undefined; const response = await apiClient.post<{ success: boolean; - data: { - token: string; - user: AuthUser; - }; - }>(loginEndpoint, credentials, requestConfig); + data: LoginResponseData; + }>( + config.twoFactorEndpoint, + { challenge_id: payload.challengeId, code: payload.code }, + requestConfig + ); - if (response.success && response.data) { - // 토큰 저장 - apiClient.setToken(response.data.token); - - // 사용자의 language 설정 확인 및 로케일 변경 처리 - const userLanguage = response.data.user.language; - const currentLocale = localStorage.getItem(AuthManager.LOCALE_STORAGE_KEY); - const localeChanged = userLanguage && userLanguage !== currentLocale; - - if (userLanguage) { - try { - localStorage.setItem(AuthManager.LOCALE_STORAGE_KEY, userLanguage); - } catch (error) { - logger.warn('Failed to save user language to localStorage:', error); - } - } - - // 상태 업데이트 - this.state = { - isAuthenticated: true, - user: response.data.user, - type: type, - }; - - this.emit('login', this.state); - this.emit('authStateChange', this.state); - - // DevTools 추적 - trackAuthEvent('login', true, undefined, { - userId: response.data.user.uuid, - email: response.data.user.email, - type, - }); - - // 로케일이 변경된 경우 TemplateApp 재초기화 - if (localeChanged && (window as any).__templateApp) { - // changeLocale은 비동기이므로 await 하지 않고 실행 - // navigate가 먼저 실행되고, changeLocale이 완료되면 UI가 업데이트됨 - (window as any).__templateApp.changeLocale(userLanguage); - } - - return response.data.user; + if (response.success && response.data?.token && response.data?.user) { + return this.establishSession(type, response.data.token, response.data.user); } throw new Error('Login failed'); } catch (error: any) { this.clearState(); - // Axios 에러에서 API 응답 메시지 추출 - // error.response.data.message가 실제 서버 응답 메시지 - const apiMessage = error.response?.data?.message; - const enhancedError: any = new Error(apiMessage || error.message || 'Login failed'); - enhancedError.response = error.response; - enhancedError.status = error.response?.status; - - // DevTools 추적 - trackAuthEvent('login', false, enhancedError.message, { - type, - status: error.response?.status, - }); - - throw enhancedError; + throw this.enhanceAuthError(error, type, 'Login failed'); } } + /** + * 2단계 인증 코드를 재발송합니다. + * + * 서버가 기존 challenge 를 취소하고 새로 발행하므로, 앞서 받은 인증번호는 더 이상 + * 통하지 않습니다 — 호출자는 반드시 새 challenge 로 교체해야 합니다. + * + * @param type - 인증 타입 + * @param payload - 재발송할 challenge 식별자 + * @param options - 추가 옵션 (headers 등) + * @returns 새 challenge 정보 + * @since engine-v1.65.0 + */ + async resendTwoFactor( + type: AuthType, + payload: { challengeId: string }, + options?: { headers?: Record } + ): Promise { + const apiClient = getApiClient(); + const config = this.config.get(type); + + if (!config) { + throw new Error(`Unknown auth type: ${type}`); + } + + try { + const requestConfig = options?.headers ? { headers: options.headers } : undefined; + + const response = await apiClient.post<{ + success: boolean; + data: LoginResponseData; + }>( + config.twoFactorResendEndpoint, + { challenge_id: payload.challengeId }, + requestConfig + ); + + if (response.success && response.data?.challenge_id) { + return { + challengeId: String(response.data.challenge_id), + providerId: String(response.data.provider_id ?? ''), + expiresAt: response.data.expires_at ?? null, + }; + } + + throw new Error('Resend failed'); + } catch (error: any) { + // 재발송 실패는 세션 상태와 무관하다 — 이미 열린 세션이 없으므로 상태를 지우지 않는다. + throw this.enhanceAuthError(error, type, 'Resend failed'); + } + } + + /** + * 토큰을 저장하고 인증 상태·로케일을 확정합니다. + * + * 정상 로그인과 2단계 인증 완료가 같은 후처리를 공유하도록 단일 지점에 둔다 — + * 갈라지면 한쪽 경로에서만 로케일 전환이나 이벤트 발행이 빠진다. + * + * @param type - 인증 타입 + * @param token - 발급된 토큰 + * @param user - 인증된 사용자 + * @returns 인증된 사용자 정보 + */ + private establishSession(type: AuthType, token: string, user: AuthUser): AuthUser { + const apiClient = getApiClient(); + + // 토큰 저장 + apiClient.setToken(token); + + // 사용자의 language 설정 확인 및 로케일 변경 처리 + const userLanguage = user.language; + const currentLocale = localStorage.getItem(AuthManager.LOCALE_STORAGE_KEY); + const localeChanged = userLanguage && userLanguage !== currentLocale; + + if (userLanguage) { + try { + localStorage.setItem(AuthManager.LOCALE_STORAGE_KEY, userLanguage); + } catch (error) { + logger.warn('Failed to save user language to localStorage:', error); + } + } + + // 상태 업데이트 + this.state = { + isAuthenticated: true, + user, + type, + }; + + this.emit('login', this.state); + this.emit('authStateChange', this.state); + + // DevTools 추적 + trackAuthEvent('login', true, undefined, { + userId: user.uuid, + email: user.email, + type, + }); + + // 로케일이 변경된 경우 TemplateApp 재초기화 + if (localeChanged && (window as any).__templateApp) { + // changeLocale은 비동기이므로 await 하지 않고 실행 + // navigate가 먼저 실행되고, changeLocale이 완료되면 UI가 업데이트됨 + (window as any).__templateApp.changeLocale(userLanguage); + } + + return user; + } + + /** + * 인증 실패 오류에 서버 응답 정보를 실어 다시 던질 오류를 만듭니다. + * + * `code` 를 보존해야 호출자가 네트워크 실패(`ERR_NETWORK` 등)와 HTTP 오류를 구분해 + * 다국어 문구로 안내할 수 있다 — axios 오류는 `TypeError` 가 아니다. + * + * @param error - 원본 오류 + * @param type - 인증 타입 + * @param fallbackMessage - 서버 메시지가 없을 때 쓸 기본 문구 + * @returns 보강된 오류 + */ + private enhanceAuthError(error: any, type: AuthType, fallbackMessage: string): any { + // Axios 에러에서 API 응답 메시지 추출 + // error.response.data.message가 실제 서버 응답 메시지 + const apiMessage = error?.response?.data?.message; + const enhancedError: any = new Error(apiMessage || error?.message || fallbackMessage); + enhancedError.response = error?.response; + enhancedError.status = error?.response?.status; + enhancedError.code = error?.code; + + // DevTools 추적 + trackAuthEvent('login', false, enhancedError.message, { + type, + status: error?.response?.status, + }); + + return enhancedError; + } + /** * 로그아웃 처리 */ diff --git a/resources/js/core/auth/__tests__/AuthManager.test.ts b/resources/js/core/auth/__tests__/AuthManager.test.ts index e4b3d179..8d5c66ce 100644 --- a/resources/js/core/auth/__tests__/AuthManager.test.ts +++ b/resources/js/core/auth/__tests__/AuthManager.test.ts @@ -308,7 +308,7 @@ describe('AuthManager', () => { password: 'password', }); - expect(result).toEqual(mockUser); + expect(result).toEqual({ status: 'authenticated', user: mockUser }); expect(mockApiClient.setToken).toHaveBeenCalledWith('new-token'); expect(authManager.isAuthenticated()).toBe(true); expect(authManager.getUser()).toEqual(mockUser); @@ -381,5 +381,155 @@ describe('AuthManager', () => { undefined ); }); + + it('2단계 인증 응답에서는 토큰을 저장하지 않고 challenge 를 돌려줘야 합니다', async () => { + mockApiClient.post.mockResolvedValue({ + success: true, + data: { + two_factor_required: true, + challenge_id: 'challenge-uuid', + provider_id: 'g7:core.mail', + expires_at: '2026-09-07T14:03:00+09:00', + }, + }); + + const result = await authManager.login('user', { + email: 'test@example.com', + password: 'password', + }); + + expect(result).toEqual({ + status: 'two_factor_required', + challenge: { + challengeId: 'challenge-uuid', + providerId: 'g7:core.mail', + expiresAt: '2026-09-07T14:03:00+09:00', + }, + }); + // 토큰도 사용자도 없는 응답이다 — 저장하면 이후 모든 요청이 Bearer undefined 로 나간다. + expect(mockApiClient.setToken).not.toHaveBeenCalled(); + expect(authManager.isAuthenticated()).toBe(false); + }); + + it('updateConfig 로 바꾼 loginEndpoint 를 사용해야 합니다', async () => { + mockApiClient.post.mockResolvedValue({ + success: true, + data: { token: 'new-token', user: { id: 1, name: 'T', email: 't@e.com' } }, + }); + + authManager.updateConfig('user', { loginEndpoint: '/auth/custom-login' }); + + await authManager.login('user', { email: 'test@example.com', password: 'password' }); + + expect(mockApiClient.post).toHaveBeenCalledWith( + '/auth/custom-login', + { email: 'test@example.com', password: 'password' }, + undefined + ); + }); + + it('네트워크 오류의 code 를 보존해야 합니다', async () => { + const networkError: any = new Error('Network Error'); + networkError.code = 'ERR_NETWORK'; + mockApiClient.post.mockRejectedValue(networkError); + + // TypeError 가 아니므로 code 가 사라지면 네트워크 실패 판정이 불가능해진다. + await expect( + authManager.login('user', { email: 'test@example.com', password: 'password' }) + ).rejects.toMatchObject({ code: 'ERR_NETWORK' }); + }); + }); + + describe('completeTwoFactor', () => { + it('코드 확인에 성공하면 토큰을 저장하고 로그인 상태가 되어야 합니다', async () => { + const mockUser: AuthUser = { + id: 1, + name: 'Test User', + email: 'test@example.com', + }; + + mockApiClient.post.mockResolvedValue({ + success: true, + data: { token: 'two-factor-token', user: mockUser }, + }); + + const loginHandler = vi.fn(); + authManager.on('login', loginHandler); + + const result = await authManager.completeTwoFactor('user', { + challengeId: 'challenge-uuid', + code: '135790', + }); + + expect(result).toEqual(mockUser); + expect(mockApiClient.post).toHaveBeenCalledWith( + '/auth/login/two-factor', + { challenge_id: 'challenge-uuid', code: '135790' }, + undefined + ); + expect(mockApiClient.setToken).toHaveBeenCalledWith('two-factor-token'); + expect(authManager.isAuthenticated()).toBe(true); + expect(loginHandler).toHaveBeenCalled(); + }); + + it('관리자 타입은 관리자 전용 엔드포인트를 사용해야 합니다', async () => { + mockApiClient.post.mockResolvedValue({ + success: true, + data: { token: 't', user: { id: 1, name: 'T', email: 't@e.com' } }, + }); + + await authManager.completeTwoFactor('admin', { + challengeId: 'challenge-uuid', + code: '135790', + }); + + expect(mockApiClient.post).toHaveBeenCalledWith( + '/auth/admin/login/two-factor', + { challenge_id: 'challenge-uuid', code: '135790' }, + undefined + ); + }); + + it('실패 시 서버 메시지를 실은 에러를 throw 해야 합니다', async () => { + const error: any = new Error('Request failed'); + error.response = { status: 401, data: { message: '인증번호가 올바르지 않습니다.' } }; + mockApiClient.post.mockRejectedValue(error); + + await expect( + authManager.completeTwoFactor('user', { challengeId: 'c', code: '000000' }) + ).rejects.toThrow('인증번호가 올바르지 않습니다.'); + + expect(authManager.isAuthenticated()).toBe(false); + }); + }); + + describe('resendTwoFactor', () => { + it('새 challenge 를 돌려주고 토큰은 건드리지 않아야 합니다', async () => { + mockApiClient.post.mockResolvedValue({ + success: true, + data: { + two_factor_required: true, + challenge_id: 'new-challenge', + provider_id: 'g7:core.mail', + expires_at: null, + }, + }); + + const result = await authManager.resendTwoFactor('user', { + challengeId: 'old-challenge', + }); + + expect(result).toEqual({ + challengeId: 'new-challenge', + providerId: 'g7:core.mail', + expiresAt: null, + }); + expect(mockApiClient.post).toHaveBeenCalledWith( + '/auth/login/two-factor/resend', + { challenge_id: 'old-challenge' }, + undefined + ); + expect(mockApiClient.setToken).not.toHaveBeenCalled(); + }); }); }); diff --git a/resources/js/core/auth/index.ts b/resources/js/core/auth/index.ts index 6d7959d8..c133b655 100644 --- a/resources/js/core/auth/index.ts +++ b/resources/js/core/auth/index.ts @@ -4,4 +4,6 @@ export { type AuthConfig, type AuthUser, type AuthState, + type TwoFactorChallenge, + type LoginResult, } from './AuthManager'; \ No newline at end of file diff --git a/resources/js/core/template-engine.ts b/resources/js/core/template-engine.ts index eda5b9ec..29f84b3a 100644 --- a/resources/js/core/template-engine.ts +++ b/resources/js/core/template-engine.ts @@ -777,6 +777,23 @@ function updateTemplateData(data: Record, options?: UpdateOptions): state.currentDataContext._computed = mergedGlobalState._computed; } + // 레이아웃 편집기 모드 — 재렌더 금지 (renderTemplate 의 편집기 분기와 대칭). + // + // 편집기 모드에서 `TemplateApp.init` 은 `renderTemplate({ layoutJson: { components: [] } })` + // 으로 부르고, renderTemplate 의 편집기 분기가 그 빈 배열 대신 LayoutEditorChrome 을 + // 같은 reactRoot 에 렌더한다. 그래서 `state.currentLayoutJson.components` 는 **빈 배열**이다. + // 여기서 그대로 재렌더하면 같은 루트에 빈 트리를 커밋해 편집기를 통째로 제거한다 — 화면이 + // 백지가 되고 예외도 콘솔 오류도 남지 않는다. + // + // renderTemplate 의 편집기 분기는 비동기(`loadLayoutEditorBundle`)라, 부팅 중 도착한 + // setGlobalState 한 번이 그 커밋 뒤에 실행되면 발현하는 **경합**이다(간헐 재현). + // 데이터 병합은 위에서 이미 끝났으므로 여기서는 렌더만 건너뛴다 — 편집기 트리는 + // 자기 상태를 스스로 관리하고 currentLayoutJson 에 의존하지 않는다. + if (typeof window !== 'undefined' && checkLayoutEditorMode(window.location.pathname)) { + logger.log('레이아웃 편집기 모드 — 재렌더 건너뜀 (편집기 트리 보존)'); + return; + } + const components = state.currentLayoutJson.components || []; const modals = state.currentLayoutJson.modals || []; logger.log('updateTemplateData - modals 배열:', modals); diff --git a/resources/js/core/template-engine/ActionDispatcher.ts b/resources/js/core/template-engine/ActionDispatcher.ts index 05c1c803..14f06b18 100644 --- a/resources/js/core/template-engine/ActionDispatcher.ts +++ b/resources/js/core/template-engine/ActionDispatcher.ts @@ -132,6 +132,8 @@ export type ActionType = | 'replaceUrl' // URL만 변경 (데이터소스 refetch 없음) | 'apiCall' // API 호출 | 'login' // 로그인 (토큰 저장 포함) + | 'loginTwoFactor' // 2단계 인증 코드 확인 (로그인 완료) + | 'loginTwoFactorResend' // 2단계 인증 코드 재발송 | 'logout' // 로그아웃 | 'setState' // 상태 변경 | 'setError' // 에러 상태 설정 @@ -2464,6 +2466,22 @@ export class ActionDispatcher { ); break; + case 'loginTwoFactor': + result = await this.handleLoginTwoFactor( + resolvedTarget!, + resolvedParams, + context + ); + break; + + case 'loginTwoFactorResend': + result = await this.handleLoginTwoFactorResend( + resolvedTarget!, + resolvedParams, + context + ); + break; + case 'logout': result = await this.handleLogout(resolvedTarget!, context); break; @@ -3828,8 +3846,7 @@ export class ActionDispatcher { ); } - // target을 인증 타입으로 사용 (admin 또는 user) - const authType: AuthType = target === 'user' ? 'user' : 'admin'; + const authType = this.resolveAuthType(target); // 로그인 엔드포인트 결정 (globalHeaders 패턴 매칭용) // ApiClient는 baseURL이 '/api'이므로 실제 요청 경로에 '/api' prefix 추가 @@ -3837,49 +3854,184 @@ export class ActionDispatcher { ? '/api/auth/admin/login' : '/api/auth/login'; - // globalHeaders에서 패턴 매칭되는 헤더 추출 - // Stale Closure 방지: G7Core.state.getGlobal/getLocal()로 최신 상태 조회 - // (cartKey 재발급 등 중간에 상태가 변경된 경우에도 최신 값 사용) - const currentGlobalState = (window as any).G7Core?.state?.getGlobal?.() || _context.state?._global || {}; - const currentLocalState = (window as any).G7Core?.state?.getLocal?.() || _context.state?._local || {}; - - const expressionContext: Record = { - _global: currentGlobalState, - _local: currentLocalState, - }; - const globalHeadersResolved = this.getMatchingGlobalHeaders(loginEndpoint, expressionContext); - const authManager = AuthManager.getInstance(); try { // AuthManager.login()을 통해 로그인 및 토큰 저장 // globalHeaders가 있으면 options.headers로 전달 - const loginOptions = Object.keys(globalHeadersResolved).length > 0 - ? { headers: globalHeadersResolved } - : undefined; - - const user = await authManager.login( + const result = await authManager.login( authType, { email: body.email, password: body.password }, - loginOptions + this.buildAuthRequestOptions(loginEndpoint, _context) + ); + + // 2단계 인증이 켜진 사이트에서는 아직 로그인이 끝나지 않았다. 레이아웃이 그 사실을 + // 알 통로가 없으면 인증번호 입력 단계로 넘어갈 방법이 없다. + if (result.status === 'two_factor_required') { + return { + user: null, + two_factor_required: true, + challenge_id: result.challenge.challengeId, + provider_id: result.challenge.providerId, + expires_at: result.challenge.expiresAt, + }; + } + + // `user` 는 종전 계약 그대로 유지한다 — 기존 레이아웃이 `response.user` 를 읽는다. + return { user: result.user, two_factor_required: false }; + } catch (error: any) { + throw this.toLoginActionError(error, 'Login failed'); + } + } + + /** + * loginTwoFactor 액션을 처리합니다. + * + * 비밀번호 확인 단계가 돌려준 challenge 와 사용자가 받은 인증번호로 로그인을 완료합니다. + * + * @param target 인증 타입 (admin 또는 user) + * @param params 요청 파라미터 (body에 challenge_id, code 포함) + * @param context 액션 컨텍스트 + * @since engine-v1.65.0 + */ + private async handleLoginTwoFactor( + target: string, + params: Record, + context: ActionContext + ): Promise { + const { body } = params; + + if (!body || !body.challenge_id || !body.code) { + throw new ActionError( + 'loginTwoFactor requires challenge_id and code in body params' + ); + } + + const authType = this.resolveAuthType(target); + const endpoint = authType === 'admin' + ? '/api/auth/admin/login/two-factor' + : '/api/auth/login/two-factor'; + + try { + const user = await AuthManager.getInstance().completeTwoFactor( + authType, + { challengeId: String(body.challenge_id), code: String(body.code) }, + this.buildAuthRequestOptions(endpoint, context) ); return { user }; } catch (error: any) { - // AuthManager에서 이미 API 응답 메시지를 추출한 에러를 throw하므로 - // error.message에 실제 서버 응답 메시지가 들어있음 - const errorMessage = error.message || 'Login failed'; + throw this.toLoginActionError(error, 'Login failed'); + } + } - const apiError: any = new Error(errorMessage); - apiError.response = error.response?.data || {}; - apiError.status = error.status || error.response?.status || 500; + /** + * loginTwoFactorResend 액션을 처리합니다. + * + * 서버가 기존 challenge 를 취소하고 새로 발행하므로, 레이아웃은 반환된 새 + * `challenge_id` 로 반드시 교체해야 합니다. + * + * @param target 인증 타입 (admin 또는 user) + * @param params 요청 파라미터 (body에 challenge_id 포함) + * @param context 액션 컨텍스트 + * @since engine-v1.65.0 + */ + private async handleLoginTwoFactorResend( + target: string, + params: Record, + context: ActionContext + ): Promise { + const { body } = params; + if (!body || !body.challenge_id) { throw new ActionError( - errorMessage, - undefined, - apiError + 'loginTwoFactorResend requires challenge_id in body params' ); } + + const authType = this.resolveAuthType(target); + const endpoint = authType === 'admin' + ? '/api/auth/admin/login/two-factor/resend' + : '/api/auth/login/two-factor/resend'; + + try { + const challenge = await AuthManager.getInstance().resendTwoFactor( + authType, + { challengeId: String(body.challenge_id) }, + this.buildAuthRequestOptions(endpoint, context) + ); + + return { + two_factor_required: true, + challenge_id: challenge.challengeId, + provider_id: challenge.providerId, + expires_at: challenge.expiresAt, + }; + } catch (error: any) { + throw this.toLoginActionError(error, 'Resend failed'); + } + } + + /** + * 액션 target 을 인증 타입으로 해석합니다. + * + * @param target 액션 target + * @returns 인증 타입 + */ + private resolveAuthType(target: string): AuthType { + return target === 'user' ? 'user' : 'admin'; + } + + /** + * 인증 요청에 실을 globalHeaders 옵션을 만듭니다. + * + * 세 인증 핸들러가 같은 규칙을 공유하도록 단일 지점에 둔다 — 갈라지면 한 경로에만 + * 공통 헤더가 빠져 그 요청만 조용히 다르게 나간다. + * + * @param endpoint 요청 경로 (globalHeaders 패턴 매칭용, '/api' prefix 포함) + * @param context 액션 컨텍스트 + * @returns headers 옵션 (매칭된 헤더가 없으면 undefined) + */ + private buildAuthRequestOptions( + endpoint: string, + context: ActionContext + ): { headers: Record } | undefined { + // Stale Closure 방지: G7Core.state.getGlobal/getLocal()로 최신 상태 조회 + // (cartKey 재발급 등 중간에 상태가 변경된 경우에도 최신 값 사용) + const currentGlobalState = (window as any).G7Core?.state?.getGlobal?.() || context.state?._global || {}; + const currentLocalState = (window as any).G7Core?.state?.getLocal?.() || context.state?._local || {}; + + const expressionContext: Record = { + _global: currentGlobalState, + _local: currentLocalState, + }; + + const resolved = this.getMatchingGlobalHeaders(endpoint, expressionContext); + + return Object.keys(resolved).length > 0 ? { headers: resolved } : undefined; + } + + /** + * AuthManager 오류를 액션 오류로 재포장합니다. + * + * `code` 를 보존해야 호출자가 네트워크 실패와 HTTP 오류를 구분해 다국어 문구로 + * 안내할 수 있다 — axios 오류는 `TypeError` 가 아니다. + * + * @param error 원본 오류 + * @param fallbackMessage 서버 메시지가 없을 때 쓸 기본 문구 + * @returns 액션 오류 + */ + private toLoginActionError(error: any, fallbackMessage: string): ActionError { + // AuthManager에서 이미 API 응답 메시지를 추출한 에러를 throw하므로 + // error.message에 실제 서버 응답 메시지가 들어있음 + const errorMessage = error?.message || fallbackMessage; + + const apiError: any = new Error(errorMessage); + apiError.response = error?.response?.data || {}; + apiError.status = error?.status || error?.response?.status || 500; + apiError.code = error?.code; + + return new ActionError(errorMessage, undefined, apiError); } /** diff --git a/resources/js/core/template-engine/CHANGELOG.md b/resources/js/core/template-engine/CHANGELOG.md index 8afe53a5..b03e2292 100644 --- a/resources/js/core/template-engine/CHANGELOG.md +++ b/resources/js/core/template-engine/CHANGELOG.md @@ -5,6 +5,79 @@ > > 형식: [Keep a Changelog](https://keepachangelog.com/ko/1.1.0/) +## [engine-v1.66.1] - 2026-09-09 + +### Fixed + +#### 확장 편집 모드 저장 — overlay injections 보존 +- 호스트 병합 모드에서 추출한 확장 노드에 `__injectionIndex` 가 없어 `reassembleContent` 가 전부 버리고 `injections[].components: []` 를 PUT 하던 결함 수정 — 백엔드가 `__source.injectionIndex` 로 실어 주는 순번을 읽고, 없으면 원본 injection 의 노드 id 로 되돌린다 (useExtensionDocument.ts `reassembleOverlayContent`) +- 되돌리지 못한 노드가 있고 원본에 잃을 컴포넌트가 있으면 PUT 하지 않고 `guard_extension_reassembly` 를 돌려준다 — `SaveFeedbackBanner` 가 자동 dismiss 없는 오류 배너로 표시 (`g7le-save-banner-guard-extension-reassembly`) +- `NodeSource.injectionIndex` 타입 추가 (layoutTreeUtils.ts) + +#### 409 배너 버전 표기 +- 서버(`ResponseHelper::error`)가 `errors` 아래에 싣는 `current_version`/`your_version` 을 읽지 못해 「최신 버전: -1」 로 표시되던 결함 수정 — `utils/conflictVersion.ts` `readConflictVersion` 단일 판독(`errors.{key}` 우선, 최상위 폴백)을 레이아웃 저장·확장 저장·inject_props 교차 저장 세 경로가 공유 + +### Notes +- 편집기 재로드 stale(부팅 시점 `cache_version` 키)은 서버 `PublicLayoutController::serve` 캐시 키를 서버 현재 버전으로 고정해 해소 — 클라이언트 nonce 규약(`?v={cacheVersion}.{nonce}`)은 그대로다 + +## [engine-v1.66.0] - 2026-09-08 + +### Added + +#### `number` 위젯 — 숫자 prop 편집 +- `number` 위젯을 코어 레지스트리에 등록 — 종전 미등록이라 `widget:"number"` 컨트롤이 속성 모달에서 「지원하지 않는 컨트롤」로 폴백해 편집 자체가 불가했다 (registerCoreWidgets.ts) +- `NumberWidget` 신설 — blur/Enter 커밋, `0` 은 유효값이라 삭제되지 않고, 비숫자는 미방출(저장본 무손실), 바인딩 문자열은 읽기전용 디그레이드 (StyleControlWidgets.tsx) +- `EditorControlSpec` 에 `min`/`max`/`step` 명시 선언 (specTypes.ts) + +#### `nodeKey` apply 프리미티브 — 노드 최상위 구조키 패치 +- `applyRecipe`/`reverseResolve` 에 `nodeKey` 분기 구현 — `coreProps` 가 선언만 하고 엔진 switch 에 case 가 없어 무음 no-op 이던 것을 실동작으로 (recipeEngine.ts) +- 예약 노드키(`children`/`props`/`type` 등) 가드 — 템플릿이 그 키를 선언해도 노드가 파괴되지 않는다 (recipeEngine.ts) +- 전용 UI 소유 키(`isolatedState`/`isolatedScopeId`)를 `resolveCorePropKeys` 렌더 목록에서 제외 — 같은 노드 키를 두 UI 가 쓰는 이중 경로 차단 (coreProps.ts) + +### Fixed + +#### `image` 위젯 값이 `[object Object]` 로 기록되던 문제 +- 단일 값 슬롯(`propValue`/`cssVar`/단일 `styleProp`)에 이미지 값 객체가 통째로 기록되던 문제 — 공용 헬퍼 `scalarizeImageValue` 로 url 만 축약한다. 게이트는 위젯 이름이며 값 형태 sniffing 이 아니다(`{position:'left'}` 같은 정당한 객체 prop 오인 삭제 방지) (recipeEngine.ts) +- 축약 저장된 문자열을 위젯이 읽지 못하던 문제 — `propValue` 역해석이 `{url}` 로 되감는다. 표현식 문자열도 감싸 빈 피커로 보이지 않게 한다 (recipeEngine.ts) +- 단일 값 슬롯 컨트롤에서 표시모드(채움/맞춤/타일) 버튼이 저장되지 않는데도 눌리던 문제 — 컨테이너째 미렌더하고 미리보기는 `contain` 으로 고정한다(저장되지 않는 값을 흉내내는 거짓 미리보기 제거) (ImagePickerControl.tsx) +- 데이터 연결 값이 빈 피커로 보여 업로드 1클릭에 표현식이 소실되던 문제 — 원문 배지 표시 + 파괴적 조작 잠금 + 「직접 지정으로 바꾸기」 해제 경로 (ImagePickerControl.tsx) + +#### 레이아웃 편집기가 열린 직후 백지가 되던 문제 +- 편집기 모드에서 `updateTemplateData` 가 빈 레이아웃(`components: []`)으로 재렌더해 같은 reactRoot 에 빈 트리를 커밋, `LayoutEditorChrome` 을 통째로 제거하던 문제 — 편집기 모드면 데이터 병합 후 렌더만 건너뛴다. `renderTemplate` 의 편집기 분기가 비동기라 부팅 중 `setGlobalState` 가 그 커밋 뒤에 도착할 때만 발현하는 경합이었다 (template-engine.ts) + +#### 데이터 연결 값이 조작 한 번에 소실되던 문제 +- prop 자리에 저장된 `{{...}}`·설정 참조를 위젯이 해석하지 못해 빈 컨트롤로 보이고, 조작 시 그 연결이 사라지던 문제 — `ControlRenderer` 단일 게이트에서 원문 배지로 디그레이드하고 「직접 지정으로 바꾸기」로만 연다. 위젯마다 구현하지 않으므로 신규 위젯에도 자동 적용된다 (ControlRenderer.tsx, boundValueGuard.tsx) +- 「직접 지정으로 바꾸기」가 편도라 원문을 되찾을 수 없던 문제 — 「되돌리기」 추가(해제 직후엔 취소, 값을 넣은 뒤엔 원문 복구) +- `image` 위젯에서 업로드·제거·썸네일만 잠기고 「이미지 관리」 진입이 열려 있어, 그 창의 「배경」 버튼이 같은 값을 덮어쓰던 문제 — 진입 자체를 함께 잠근다 (ImagePickerControl.tsx) +- `number` 위젯의 자체 바인딩 분기가 공용 해제 경로를 막던 문제 — 위젯 자체 분기를 제거하고 공용 게이트로 일원화 (StyleControlWidgets.tsx) + +#### 공통·확장 레이아웃 노드를 편집해도 저장되지 않던 문제 +- 라우트 편집 모드에서 상속(base)·주입(extension) 노드 중 바인딩을 가진 것이 `data_bound`(편집 허용)로 분류되던 문제 — 출처 잠금이 항상 우선하도록 판정 순서를 통일했다. 저장 시 마스킹이 그 노드를 통째로 폐기하므로 편집분이 오류도 경고도 없이 사라졌다 (useElementSelection.ts) +- 잠긴 노드의 드래그·인라인 편집·복제·`Delete` 키·잘라내기가 무방비이던 문제 — 단일 판정 헬퍼 `isEditableLockKind` 로 전 표면 게이트 (EditorCanvasOverlay.tsx, useCanvasDnd.ts, DndCanvasLayer.tsx) +- 거부된 드래그가 `activeDragPath` 를 남겨 「옮길 수 있다」는 거짓 어포던스를 주던 문제 (useCanvasDnd.ts) +- 조상 산출 구현이 두 벌로 갈라져 잠금 판정 입력이 어긋날 수 있던 문제 — `collectAncestors` 로 승격 (layoutTreeUtils.ts) + +## [engine-v1.65.0] - 2026-09-07 + +### Added + +#### 2단계 인증 로그인 단계 지원 +- 로그인 응답을 `LoginResult` 판별 유니온으로 표현 — 정상 로그인과 인증번호 요구(challenge)를 타입으로 구분 (AuthManager.ts) +- `completeTwoFactor()` / `resendTwoFactor()` 추가 — 인증번호 확인·재발송 (AuthManager.ts) +- `AuthConfig` 에 `twoFactorEndpoint` / `twoFactorResendEndpoint` 추가 (AuthManager.ts) +- 액션 핸들러 `loginTwoFactor` / `loginTwoFactorResend` 추가 — 레이아웃에서 인증번호 확인·재발송 (ActionDispatcher.ts) +- `login` 핸들러 반환에 `two_factor_required` / `challenge_id` / `provider_id` / `expires_at` 추가 (기존 `user` 필드 유지) (ActionDispatcher.ts) +- 레이아웃 편집기 [화면 동작] 탭에 두 핸들러 등록 (coreActionRecipes.ts, ActionAddPicker.tsx) + +### Fixed + +#### 2단계 인증이 켜진 사이트에서 로그인 화면이 영문 오류로 멈추던 문제 +- 로그인 응답 형태를 하나로 가정해 인증번호 요구 응답에서 `TypeError` 원문이 오류 박스에 노출되던 문제 (AuthManager.ts) +- 문자열이 아닌 토큰이 저장되어 이후 모든 요청이 401 로 튕기던 문제 (ApiClient.ts) +- `updateConfig({ loginEndpoint })` 로 지정한 엔드포인트가 무시되던 문제 (AuthManager.ts) +- axios 네트워크 오류가 네트워크 실패로 판정되지 않아 영문 원문이 노출되던 문제 (networkResilience.ts, ActionDispatcher.ts) +- 다국어 파라미터 값의 파이프 필터(`$t:key|until={{x | datetime}}`)가 평가되지 않아 문장에서 값만 사라지던 문제 (TranslationEngine.ts) + ## [engine-v1.64.7] - 2026-09-04 ### Fixed diff --git a/resources/js/core/template-engine/TranslationEngine.ts b/resources/js/core/template-engine/TranslationEngine.ts index ad0c3279..350b864d 100644 --- a/resources/js/core/template-engine/TranslationEngine.ts +++ b/resources/js/core/template-engine/TranslationEngine.ts @@ -19,6 +19,7 @@ import { fetchStaticFirst } from '../support/fetchStaticFirst'; // 양쪽 모두 모듈 평가 시점이 아니라 메서드 실행 시점에만 서로를 참조하므로 // live binding 이 채워진 뒤에 사용된다. import { dataBindingEngine } from './DataBindingEngine'; +import { hasPipes } from './PipeRegistry'; const logger = createLogger('TranslationEngine'); @@ -505,6 +506,20 @@ export class TranslationEngine { if (value.startsWith('{{') && value.endsWith('}}')) { const expression = value.slice(2, -2).trim(); + // 파이프 필터(`{{x | datetime}}`)는 표현식 평가기가 모른다 — `|` 를 비트 연산자로 + // 읽어 평가에 실패하고, 아래 catch 가 빈 문자열을 돌려주므로 문장에서 값만 조용히 + // 사라진다("유효시간 까지"). 파이프 전용 평가기로 먼저 처리한다. + // @since engine-v1.65.0 + if (hasPipes(expression) && dataContext) { + try { + const piped = dataBindingEngine.evaluatePipeExpression(expression, dataContext); + return String(piped ?? ''); + } catch (error) { + logger.error('Pipe expression evaluation failed:', expression, error); + return ''; + } + } + // 복잡한 표현식인지 확인 (연산자, 괄호, 메서드 호출 등 포함) // 산술 연산자(+, -, *, /, %), 비교 연산자(<, >, =), 논리 연산자, 공백(피연산자 분리)도 포함 const isComplexExpression = /[|&()[\]!?:+\-*/%<>=\s]/.test(expression); diff --git a/resources/js/core/template-engine/__tests__/ActionDispatcher.globalHeaders.test.ts b/resources/js/core/template-engine/__tests__/ActionDispatcher.globalHeaders.test.ts index 92f8d544..b796afad 100644 --- a/resources/js/core/template-engine/__tests__/ActionDispatcher.globalHeaders.test.ts +++ b/resources/js/core/template-engine/__tests__/ActionDispatcher.globalHeaders.test.ts @@ -10,7 +10,7 @@ import { GlobalHeaderRule } from '../LayoutLoader'; import { Logger } from '../../utils/Logger'; // AuthManager mock - login 호출 시 전달된 인자 추적 -const mockLogin = vi.fn().mockResolvedValue({ id: 1, name: 'Test User' }); +const mockLogin = vi.fn().mockResolvedValue({ status: 'authenticated', user: { id: 1, name: 'Test User' } }); const mockLogout = vi.fn().mockResolvedValue(undefined); vi.mock('../../auth/AuthManager', () => ({ @@ -40,7 +40,7 @@ describe('ActionDispatcher - globalHeaders', () => { mockNavigate = vi.fn(); mockGetToken.mockReset(); mockLogin.mockReset(); - mockLogin.mockResolvedValue({ id: 1, name: 'Test User' }); + mockLogin.mockResolvedValue({ status: 'authenticated', user: { id: 1, name: 'Test User' } }); dispatcher = new ActionDispatcher({ navigate: mockNavigate }); Logger.getInstance().setDebug(false); diff --git a/resources/js/core/template-engine/__tests__/ActionDispatcher.loginTwoFactor.test.ts b/resources/js/core/template-engine/__tests__/ActionDispatcher.loginTwoFactor.test.ts new file mode 100644 index 00000000..986bd04e --- /dev/null +++ b/resources/js/core/template-engine/__tests__/ActionDispatcher.loginTwoFactor.test.ts @@ -0,0 +1,218 @@ +/** + * ActionDispatcher 2단계 인증 핸들러 테스트 + * + * 서버는 보안 환경설정에 따라 로그인에 **두 가지 형태의 200** 을 돌려준다. 레이아웃이 + * 그 사실을 알 통로가 없으면 인증번호 입력 단계로 넘어갈 방법이 없고, 화면은 알 수 없는 + * 오류로 멈춘다(공개 #133). + * + * @since engine-v1.65.0 + */ + +import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'; +import { ActionDispatcher, ActionDefinition } from '../ActionDispatcher'; +import { Logger } from '../../utils/Logger'; + +const mockLogin = vi.fn(); +const mockCompleteTwoFactor = vi.fn(); +const mockResendTwoFactor = vi.fn(); +const mockLogout = vi.fn().mockResolvedValue(undefined); + +vi.mock('../../auth/AuthManager', () => ({ + AuthManager: { + getInstance: vi.fn(() => ({ + login: mockLogin, + logout: mockLogout, + completeTwoFactor: mockCompleteTwoFactor, + resendTwoFactor: mockResendTwoFactor, + })), + }, +})); + +vi.mock('../../api/ApiClient', () => ({ + getApiClient: vi.fn(() => ({ + getToken: vi.fn(), + })), +})); + +describe('ActionDispatcher - 2단계 인증', () => { + let dispatcher: ActionDispatcher; + + beforeEach(() => { + mockLogin.mockReset(); + mockCompleteTwoFactor.mockReset(); + mockResendTwoFactor.mockReset(); + dispatcher = new ActionDispatcher({ navigate: vi.fn() }); + Logger.getInstance().setDebug(false); + }); + + afterEach(() => { + Logger.getInstance().setDebug(false); + }); + + const context = () => ({ state: { _global: {}, _local: {} } }); + + const run = async (action: ActionDefinition) => { + const result = await dispatcher.dispatchAction(action, context() as any); + + if (!result.success) { + throw result.error ?? new Error('action failed'); + } + + return result.data; + }; + + describe('login', () => { + it('challenge 응답을 레이아웃이 읽을 수 있는 형태로 돌려준다', async () => { + mockLogin.mockResolvedValue({ + status: 'two_factor_required', + challenge: { + challengeId: 'challenge-uuid', + providerId: 'g7:core.mail', + expiresAt: '2026-09-07T14:03:00+09:00', + }, + }); + + const result = await run({ + type: 'click', + handler: 'login', + target: 'user', + params: { body: { email: 'a@b.c', password: 'pw' } }, + }); + + expect(result).toMatchObject({ + user: null, + two_factor_required: true, + challenge_id: 'challenge-uuid', + provider_id: 'g7:core.mail', + expires_at: '2026-09-07T14:03:00+09:00', + }); + }); + + it('정상 로그인은 종전대로 user 를 돌려준다', async () => { + const user = { id: 1, name: 'Test User' }; + mockLogin.mockResolvedValue({ status: 'authenticated', user }); + + const result = await run({ + type: 'click', + handler: 'login', + target: 'user', + params: { body: { email: 'a@b.c', password: 'pw' } }, + }); + + // 기존 레이아웃이 `response.user` 를 읽으므로 이 계약은 유지되어야 한다. + expect(result).toMatchObject({ user, two_factor_required: false }); + }); + }); + + describe('loginTwoFactor', () => { + it('challenge_id 와 code 로 로그인을 완료한다', async () => { + const user = { id: 1, name: 'Test User' }; + mockCompleteTwoFactor.mockResolvedValue(user); + + const result = await run({ + type: 'click', + handler: 'loginTwoFactor', + target: 'user', + params: { body: { challenge_id: 'challenge-uuid', code: '135790' } }, + }); + + expect(mockCompleteTwoFactor).toHaveBeenCalledWith( + 'user', + { challengeId: 'challenge-uuid', code: '135790' }, + undefined + ); + expect(result).toEqual({ user }); + }); + + it('globalHeaders 패턴이 관리자 확인 요청에도 적용된다', async () => { + mockCompleteTwoFactor.mockResolvedValue({ id: 1 }); + dispatcher.setGlobalHeaders([ + { pattern: '/api/auth/*', headers: { 'X-Cart-Key': 'ck_admin456' } }, + ]); + + await run({ + type: 'click', + handler: 'loginTwoFactor', + target: 'admin', + params: { body: { challenge_id: 'c', code: '135790' } }, + }); + + expect(mockCompleteTwoFactor).toHaveBeenCalledWith( + 'admin', + { challengeId: 'c', code: '135790' }, + { headers: { 'X-Cart-Key': 'ck_admin456' } } + ); + }); + + it('code 가 없으면 요청을 보내지 않는다', async () => { + await expect( + run({ + type: 'click', + handler: 'loginTwoFactor', + target: 'user', + params: { body: { challenge_id: 'c' } }, + }) + ).rejects.toThrow(); + + expect(mockCompleteTwoFactor).not.toHaveBeenCalled(); + }); + + it('서버 오류 메시지를 그대로 실어 재포장한다', async () => { + const error: any = new Error('인증번호가 올바르지 않거나 유효시간이 지났습니다.'); + error.response = { status: 401, data: { message: '인증번호가 올바르지 않거나 유효시간이 지났습니다.' } }; + error.status = 401; + mockCompleteTwoFactor.mockRejectedValue(error); + + await expect( + run({ + type: 'click', + handler: 'loginTwoFactor', + target: 'user', + params: { body: { challenge_id: 'c', code: '000000' } }, + }) + ).rejects.toThrow('인증번호가 올바르지 않거나 유효시간이 지났습니다.'); + }); + }); + + describe('loginTwoFactorResend', () => { + it('새 challenge 를 돌려준다', async () => { + mockResendTwoFactor.mockResolvedValue({ + challengeId: 'new-challenge', + providerId: 'g7:core.mail', + expiresAt: null, + }); + + const result = await run({ + type: 'click', + handler: 'loginTwoFactorResend', + target: 'user', + params: { body: { challenge_id: 'old-challenge' } }, + }); + + expect(mockResendTwoFactor).toHaveBeenCalledWith( + 'user', + { challengeId: 'old-challenge' }, + undefined + ); + expect(result).toEqual({ + two_factor_required: true, + challenge_id: 'new-challenge', + provider_id: 'g7:core.mail', + expires_at: null, + }); + }); + + it('challenge_id 가 없으면 요청을 보내지 않는다', async () => { + await expect( + run({ + type: 'click', + handler: 'loginTwoFactorResend', + target: 'user', + params: { body: {} }, + }) + ).rejects.toThrow(); + + expect(mockResendTwoFactor).not.toHaveBeenCalled(); + }); + }); +}); diff --git a/resources/js/core/template-engine/__tests__/TranslationEngine.test.ts b/resources/js/core/template-engine/__tests__/TranslationEngine.test.ts index 4334fe80..7369a0a8 100644 --- a/resources/js/core/template-engine/__tests__/TranslationEngine.test.ts +++ b/resources/js/core/template-engine/__tests__/TranslationEngine.test.ts @@ -594,6 +594,9 @@ describe('TranslationEngine', () => { complex: { message: '{{user}}님이 {{action}}을 수행했습니다 ({{count}}건)', }, + auth: { + valid_until: '유효시간 {{until}} 까지', + }, }; (global.fetch as any).mockResolvedValueOnce({ @@ -710,6 +713,32 @@ describe('TranslationEngine', () => { ); expect(result).toBe('총 100명 중 1-10명 표시'); }); + + // 파라미터 값 안의 파이프는 **필터**다. 표현식 평가기는 `|` 를 비트 연산자로 읽어 + // 평가에 실패하고 빈 문자열을 돌려주므로, 문장에서 값만 조용히 사라진다 + // ("유효시간 까지"). 오류도 경고도 남지 않아 화면을 보지 않으면 드러나지 않는다. + // @since engine-v1.65.0 + it('파라미터 값의 파이프 필터가 적용된다', () => { + const result = engine.translate( + 'auth.valid_until', + context, + '|until={{expires_at | datetime}}', + { expires_at: '2026-09-07T14:03:00' } + ); + + expect(result).toBe('유효시간 2026-09-07 14:03 까지'); + }); + + it('resolveTranslations 에서도 파라미터 파이프 필터가 적용된다', () => { + const result = engine.resolveTranslations( + '$t:auth.valid_until', + context, + { expires_at: '2026-09-07T14:03:00' } + ); + + // 파라미터가 없으면 자리표시자는 그대로 둔다 (기존 동작 유지) + expect(result).toContain('유효시간'); + }); }); describe('resolveTranslations 재귀 처리', () => { diff --git a/resources/js/core/template-engine/__tests__/actionDispatcherNetworkFailureMessage.test.ts b/resources/js/core/template-engine/__tests__/actionDispatcherNetworkFailureMessage.test.ts index 7e75bb56..d72f4327 100644 --- a/resources/js/core/template-engine/__tests__/actionDispatcherNetworkFailureMessage.test.ts +++ b/resources/js/core/template-engine/__tests__/actionDispatcherNetworkFailureMessage.test.ts @@ -33,6 +33,26 @@ describe('resolveActionFailureMessage', () => { ); }); + it('axios 네트워크 오류도 네트워크 계열로 안내한다', () => { + // axios 는 TypeError 가 아니라 AxiosError 로 reject 하며 종류는 code 에만 남는다. + // TypeError 만 보면 로그인 화면에 영문 원문(Network Error)이 그대로 노출된다. + const axiosError: any = new Error('Network Error'); + axiosError.code = 'ERR_NETWORK'; + + expect(resolveActionFailureMessage(axiosError, 'login', undefined)).toBe( + '$t:core.errors.network_request_failed' + ); + }); + + it('요청 시간 초과도 네트워크 계열로 안내한다', () => { + const timeout: any = new Error('timeout of 30000ms exceeded'); + timeout.code = 'ECONNABORTED'; + + expect(resolveActionFailureMessage(timeout, 'login', undefined)).toBe( + '$t:core.errors.network_request_failed' + ); + }); + it('서버가 메시지를 주면 그대로 쓴다', () => { expect( resolveActionFailureMessage(new Error('boom'), 'apiCall', '유효하지 않은 레이아웃 전략입니다.') diff --git a/resources/js/core/template-engine/layout-editor/__tests__/components/ControlRenderer.boundValueGuard.test.tsx b/resources/js/core/template-engine/layout-editor/__tests__/components/ControlRenderer.boundValueGuard.test.tsx new file mode 100644 index 00000000..37b2304e --- /dev/null +++ b/resources/js/core/template-engine/layout-editor/__tests__/components/ControlRenderer.boundValueGuard.test.tsx @@ -0,0 +1,145 @@ +/** + * ControlRenderer.boundValueGuard.test.tsx — 데이터 연결 값 보호 **공용 게이트** + * + * 저장값이 `{{...}}`(또는 설정 참조)이면 위젯은 그 문자열을 해석하지 못해 **빈 컨트롤**로 + * 보이고, 운영자가 조작하는 순간 환경설정과의 연결이 소리 없이 끊긴다. 원문이 화면 어디에도 + * 남지 않아 되돌릴 수단조차 없다. + * + * 이 보호를 위젯마다 넣으면 한 곳이 빠져도 오류가 나지 않고 그 한 곳이 우회로가 된다 + * (공개 #135 후속 실측: 「이미지 관리」 진입 하나만 열려 있었다). 그래서 `ControlRenderer` + * 한 곳에서 걸고, 본 테스트가 **위젯 종류와 무관하게** 걸리는 것을 잠근다 — 새 위젯을 + * 등록해도 자동 적용된다는 계약이다. + * + * @effects bound_value_protection_is_a_single_shared_gate_for_every_widget, restore_affordance_recovers_the_original_binding_after_replacement, bound_value_shows_expression_badge_and_locks_destructive_controls, replace_affordance_reopens_editing_without_emitting_a_change + * @since engine-v1.66.0 + */ + +import React from 'react'; +import { describe, it, expect, vi, afterEach, beforeAll } from 'vitest'; +import { render, screen, fireEvent, cleanup } from '@testing-library/react'; +import { ControlRenderer } from '../../components/property-controls/ControlRenderer'; +import { registerCoreWidgets } from '../../spec/registerCoreWidgets'; +import { LayoutEditorProvider } from '../../LayoutEditorContext'; +import { EditorModalProvider } from '../../EditorModalContext'; +import type { EditorControlSpec } from '../../spec/specTypes'; +import type { EditorNode } from '../../utils/layoutTreeUtils'; + +// 레지스트리 디스패치를 타는 테스트라 코어 위젯이 등록돼 있어야 한다 +// (미등록이면 ControlRenderer 가 "지원하지 않는 위젯" 폴백을 그린다). +beforeAll(() => registerCoreWidgets()); + +const t = (key: string): string => key; +const EXPR = '{{_global.settings?.general?.site_logo_url}}'; + +/** 스칼라를 prop 자리에 기록하는 평범한 컨트롤 — image 가 아니다 */ +const numberCtrl: EditorControlSpec = { + widget: 'number', + apply: { type: 'propValue', propKey: 'maxVisibleBoards' }, +} as never; + +const selectCtrl: EditorControlSpec = { + widget: 'select', + options: [{ value: 'sm', label: 'S' }, { value: 'lg', label: 'L' }], + apply: { type: 'propValue', propKey: 'size' }, +} as never; + +afterEach(() => cleanup()); + +describe('ControlRenderer — 데이터 연결 값 보호 공용 게이트', () => { + it('number 위젯도 바인딩 값이면 원문 배지로 디그레이드된다 (image 전용이 아니다)', () => { + const node: EditorNode = { type: 'basic', name: 'Header', props: { maxVisibleBoards: EXPR } }; + render(); + + expect(screen.getByTestId('g7le-control-hdrMax-expression').textContent).toBe(EXPR); + // 위젯 자체는 렌더되지 않는다 — 해석 못 하는 값을 흉내내면 거짓 컨트롤이다. + expect(screen.queryByTestId('g7le-number-input')).toBeNull(); + }); + + it('select 위젯도 동일하게 걸린다 (닫힌 집합이라 표현식을 표시할 수 없다)', () => { + const node: EditorNode = { type: 'basic', name: 'Div', props: { size: EXPR } }; + render(); + + expect(screen.getByTestId('g7le-control-sz-expression').textContent).toBe(EXPR); + }); + + it('「직접 지정으로 바꾸기」 → 위젯이 열리고, 그 클릭만으로는 값을 바꾸지 않는다', () => { + const onPatch = vi.fn(); + const node: EditorNode = { type: 'basic', name: 'Header', props: { maxVisibleBoards: EXPR } }; + render(); + + fireEvent.click(screen.getByTestId('g7le-control-hdrMax-expression-replace')); + + expect(screen.queryByTestId('g7le-control-hdrMax-expression')).toBeNull(); + expect(screen.getByTestId('g7le-widget-number')).toBeInTheDocument(); + expect(onPatch).not.toHaveBeenCalled(); + }); + + it('해제 뒤 「되돌리기」가 노출되고, 원문을 다시 기록한다 (편도 금지)', () => { + const patches: EditorNode[] = []; + + // 실제 편집기처럼 패치가 노드로 되돌아오는 controlled 하네스 — + // 고정 prop 으로는 "값을 바꾼 뒤" 상태를 재현할 수 없다. + function Harness(): React.ReactElement { + const [node, setNode] = React.useState({ + type: 'basic', + name: 'Header', + props: { maxVisibleBoards: EXPR }, + }); + return ( + { + patches.push(n); + setNode(n); + }} + /> + ); + } + + render(); + + fireEvent.click(screen.getByTestId('g7le-control-hdrMax-expression-replace')); + + const input = screen.getByTestId('g7le-number-input'); + fireEvent.change(input, { target: { value: '7' } }); + fireEvent.blur(input, { target: { value: '7' } }); + expect(patches[patches.length - 1].props?.maxVisibleBoards).toBe(7); + + // 원문이 화면에서 사라진 뒤에도 되돌릴 수 있어야 한다. + fireEvent.click(screen.getByTestId('g7le-control-hdrMax-expression-restore')); + expect(patches[patches.length - 1].props?.maxVisibleBoards).toBe(EXPR); + // 보호 상태로 복귀 + expect(screen.getByTestId('g7le-control-hdrMax-expression').textContent).toBe(EXPR); + }); + + it('바인딩이 아닌 평범한 값은 종전대로 위젯이 렌더된다 (회귀 가드)', () => { + const node: EditorNode = { type: 'basic', name: 'Header', props: { maxVisibleBoards: 5 } }; + render(); + + expect(screen.queryByTestId('g7le-control-hdrMax-expression')).toBeNull(); + expect((screen.getByTestId('g7le-number-input') as HTMLInputElement).value).toBe('5'); + }); + + it('자체 처리 위젯(image)은 공용 게이트를 타지 않는다 — 위젯 자신의 배지를 쓴다', () => { + const imageCtrl: EditorControlSpec = { + widget: 'image', + apply: { type: 'propValue', propKey: 'logo' }, + } as never; + const node: EditorNode = { type: 'composite', name: 'Header', props: { logo: EXPR } }; + // ImagePickerControl 은 편집기 컨텍스트(첨부 목록·모달)를 요구한다. + render( + + + + + , + ); + + // 공용 게이트의 testid 가 아니라 위젯 자신의 testid 로 뜬다. + expect(screen.queryByTestId('g7le-control-hdrLogo-expression')).toBeNull(); + expect(screen.getByTestId('g7le-image-expression').textContent).toBe(EXPR); + }); +}); diff --git a/resources/js/core/template-engine/layout-editor/__tests__/components/DndCanvasLayer.test.tsx b/resources/js/core/template-engine/layout-editor/__tests__/components/DndCanvasLayer.test.tsx index 6e64b4cc..5b8df75a 100644 --- a/resources/js/core/template-engine/layout-editor/__tests__/components/DndCanvasLayer.test.tsx +++ b/resources/js/core/template-engine/layout-editor/__tests__/components/DndCanvasLayer.test.tsx @@ -433,3 +433,146 @@ describe('DndCanvasLayer — 드래그 핸들 렌더 가드', () => { expect(container.firstChild).toBeNull(); }); }); + +// ============================================================================ +// 상속·주입 노드 드래그 핸들 미렌더 (A3) +// +// 이 노드들은 종전에 `data_bound` 로 분류돼 핸들이 렌더됐고, 옮겨도 저장 시 폐기됐다. +// 핸들 자체를 만들지 않아 "옮길 수 있다" 는 어포던스를 주지 않는다. +// ============================================================================ +describe('DndCanvasLayer — 상속·주입 노드 드래그 핸들 차단 (A3)', () => { + /** 펼침 인스턴스 2개를 owner 아래에 깔아 가상 묶음 합성 조건을 만든다. */ + function appendIterationInstances(frame: HTMLElement, ownerPath: string): void { + const owner = frame.querySelector(`[data-editor-path="${ownerPath}"]`)!; + for (const [i, top] of [0, 50].entries()) { + const inst = document.createElement('div'); + const p = `${ownerPath}.iteration.${i}`; + inst.dataset.editorPath = p; + inst.setAttribute('data-editor-path', p); + vi.spyOn(inst, 'getBoundingClientRect').mockReturnValue( + domRect({ left: 0, top, width: 400, height: 50 }), + ); + owner.appendChild(inst); + } + } + + function renderLayer(components: EditorNode[]) { + const frame = buildFrame([ + { path: '0', rect: { left: 0, top: 0, width: 400, height: 100 } }, + { path: '1', rect: { left: 0, top: 100, width: 400, height: 100 } }, + ]); + render( + , + ); + } + + it('N13 base 출처 + 바인딩 노드 → 핸들 미렌더 (route 소유 형제는 렌더)', () => { + renderLayer([ + { name: 'Div', id: 'a', __source: { kind: 'base' }, text: '{{site.title}}' }, + { name: 'Div', id: 'b', __source: { kind: 'route' }, text: '{{user.name}}' }, + ]); + expect(screen.queryByTestId('g7le-dnd-handle-0')).not.toBeInTheDocument(); + // 회귀 가드 — route 소유 data_bound 는 그대로 핸들이 있다. + expect(screen.queryByTestId('g7le-dnd-handle-1')).toBeInTheDocument(); + }); + + it('N14 base 출처 iteration 노드 → 가상 묶음 핸들도 미렌더', () => { + renderLayer([ + { + name: 'Div', + id: 'list', + __source: { kind: 'base' }, + iteration: { source: '{{posts.data}}' }, + }, + { name: 'Div', id: 'b', __source: { kind: 'route' } }, + ]); + expect(screen.queryByTestId('g7le-dnd-handle-0')).not.toBeInTheDocument(); + expect(screen.queryByTestId('g7le-dnd-handle-1')).toBeInTheDocument(); + }); + + /** + * N14b — 가상 묶음 합성 루프(`DndCanvasLayer` 의 iterationGroups 순회)를 **실제로 진입**시켜 + * 원본 노드의 출처 잠금이 그 루프에서도 걸리는지 잰다. + * + * N14 는 펼침 인스턴스(`0.iteration.N`) DOM 을 만들지 않아 묶음 합성 루프에 애초에 + * 들어가지 않았다 — 그래서 그 안의 잠금 게이트를 지워도 초록이었다(라운드3 probe 실측). + * 여기서는 인스턴스를 실제로 깔아 묶음이 합성되는 조건을 만든 뒤 부재를 단언한다. + */ + it('N14b base 출처 iteration 노드 → 펼침 인스턴스가 있어도 가상 묶음 핸들 미합성', () => { + const frame = buildFrame([{ path: '0', rect: { left: 0, top: 0, width: 400, height: 100 } }]); + appendIterationInstances(frame, '0'); + render( + , + ); + expect(screen.queryByTestId('g7le-dnd-handle-0')).not.toBeInTheDocument(); + }); + + /** + * N14c — N14b 의 「부재」가 공허하지 않음을 증명하는 반증 가드. + * 같은 픽스처에서 출처만 route 로 바꾸면 묶음 핸들이 실제로 합성된다. + */ + it('N14c 회귀 가드 — route 출처 iteration 노드는 가상 묶음 핸들이 합성된다', () => { + const frame = buildFrame([{ path: '0', rect: { left: 0, top: 0, width: 400, height: 100 } }]); + appendIterationInstances(frame, '0'); + render( + , + ); + const groupHandle = screen.queryByTestId('g7le-dnd-handle-0'); + expect(groupHandle).toBeInTheDocument(); + expect(groupHandle?.getAttribute('data-dnd-iteration-group')).toBe('true'); + }); + + it('N15 extension 출처 + 바인딩 노드 → 핸들 미렌더', () => { + renderLayer([ + { + name: 'Div', + id: 'a', + __source: { kind: 'extension', extensionId: 35 }, + text: '{{content}}', + }, + { name: 'Div', id: 'b', __source: { kind: 'route' } }, + ]); + expect(screen.queryByTestId('g7le-dnd-handle-0')).not.toBeInTheDocument(); + expect(screen.queryByTestId('g7le-dnd-handle-1')).toBeInTheDocument(); + }); +}); diff --git a/resources/js/core/template-engine/layout-editor/__tests__/components/EditorCanvasOverlay.entryPointGuard.test.tsx b/resources/js/core/template-engine/layout-editor/__tests__/components/EditorCanvasOverlay.entryPointGuard.test.tsx new file mode 100644 index 00000000..2ec8b957 --- /dev/null +++ b/resources/js/core/template-engine/layout-editor/__tests__/components/EditorCanvasOverlay.entryPointGuard.test.tsx @@ -0,0 +1,203 @@ +/** + * EditorCanvasOverlay.entryPointGuard.test.tsx — 구조 편집 콜백의 선두 가드 (A3) + * + * `handleDuplicate` 는 `ElementOverlay` 의 ⓘ 컨텍스트 메뉴에서만 호출되고, 그 ⓘ 는 + * 같은 잠금 판정으로 이미 가려진다(`ElementOverlay` 가 `isContextMenuAllowed` 로 게이트). + * 그래서 ⓘ 를 통과하는 테스트만으로는 **콜백 자신의 선두 가드가 지워져도 초록**이다 + * (라운드3 probe 로 전 스위트 3,587 green 실측). + * + * 그러나 이 저장소는 같은 자리에서 이미 반대 입장을 취하고 있다 — + * `handleEditProps` 는 "ElementOverlay 가 ⓘ 를 미표시하지만, 외부 호출자(Chrome stub) 등 + * 다른 경로로도 들어올 수 있으므로 여기서도 방어한다" 는 주석과 함께 자체 가드를 둔다. + * 이 파일은 그 계약을 `handleDuplicate` 에도 적용해 **호출자를 신뢰하지 않는다**를 잠근다: + * 오버레이를 스텁으로 바꿔 잠금 여부와 무관하게 콜백을 넘겨받아 직접 호출한다. + * + * @since engine-v1.66.0 + */ + +import React from 'react'; +import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; +import { render, act, cleanup, fireEvent, waitFor } from '@testing-library/react'; +import type { UseLayoutDocumentResult, LoadedLayoutDocument } from '../../hooks/useLayoutDocument'; +import type { EditorNode } from '../../utils/layoutTreeUtils'; + +// ⓘ 게이트를 우회하는 스텁 — lockKind 와 무관하게 onDuplicate 트리거를 항상 렌더한다. +// 「다른 경로로 들어온 호출자」를 그대로 흉내낸다. +vi.mock('../../components/ElementOverlay', () => ({ + ElementOverlay: ({ + onDuplicate, + lockKind, + }: { + onDuplicate: () => void; + lockKind: string; + }): React.ReactElement => ( + + ), +})); + +import { EditorCanvasOverlay } from '../../components/EditorCanvasOverlay'; +import { LayoutEditorProvider, useLayoutEditor } from '../../LayoutEditorContext'; +import { LayoutDocumentProvider } from '../../LayoutDocumentContext'; +import { EditorModalProvider } from '../../EditorModalContext'; +import { TranslationProvider } from '../../../TranslationContext'; +import { TranslationEngine } from '../../../TranslationEngine'; + +interface Rect { + left: number; + top: number; + width: number; + height: number; +} +function domRect(r: Rect): DOMRect { + return { + ...r, + right: r.left + r.width, + bottom: r.top + r.height, + x: r.left, + y: r.top, + toJSON: () => ({}), + } as DOMRect; +} + +function buildFrame(children: Array<{ path: string; rect: Rect }>): HTMLElement { + const wrapper = document.createElement('div'); + const frame = document.createElement('div'); + vi.spyOn(frame, 'getBoundingClientRect').mockReturnValue( + domRect({ left: 0, top: 0, width: 400, height: 400 }), + ); + for (const c of children) { + const el = document.createElement('div'); + el.dataset.editorPath = c.path; + el.dataset.editorId = `id-${c.path}`; + el.setAttribute('data-editor-path', c.path); + vi.spyOn(el, 'getBoundingClientRect').mockReturnValue(domRect(c.rect)); + frame.appendChild(el); + } + wrapper.appendChild(frame); + document.body.appendChild(wrapper); + return frame; +} + +function buildDocCtx(initialComponents: EditorNode[]): { + ctx: UseLayoutDocumentResult; + patchSpy: ReturnType; +} { + let document_: LoadedLayoutDocument = { + layoutName: 'home', + raw: { components: initialComponents }, + lockVersion: 1, + }; + const patchSpy = vi.fn(); + const ctx: UseLayoutDocumentResult = { + document: document_, + isLoading: false, + error: null, + isDirty: false, + saveSuccessCounter: 0, + reload: async () => {}, + patchLayout: (patcher) => { + patchSpy(patcher); + const current = (document_.raw.components as EditorNode[]) ?? []; + const next = patcher(current); + document_ = { ...document_, raw: { ...document_.raw, components: next } }; + (ctx as any).document = document_; + }, + setLayoutComponents: (next) => { + document_ = { ...document_, raw: { ...document_.raw, components: next } }; + (ctx as any).document = document_; + }, + save: async () => ({ kind: 'success', newLockVersion: 2 }), + }; + return { ctx, patchSpy }; +} + +function RouteSeeder({ children }: { children: React.ReactNode }): React.ReactElement { + const { dispatch } = useLayoutEditor(); + React.useEffect(() => { + dispatch({ type: 'SELECT_ROUTE', route: { path: '/', layoutName: 'home' } }); + }, [dispatch]); + return <>{children}; +} + +const BASE_TEXT: EditorNode = { + name: 'Span', + id: 'base-span', + __source: { kind: 'base', layout: '_user_base' }, + text: '공통 문구', +}; +const ROUTE_TEXT: EditorNode = { name: 'Span', id: 'route-span', __source: { kind: 'route' }, text: '라우트 문구' }; + +function mount(components: EditorNode[]) { + const frame = buildFrame([ + { path: '0', rect: { left: 0, top: 0, width: 400, height: 100 } }, + { path: '1', rect: { left: 0, top: 100, width: 400, height: 100 } }, + ]); + const { ctx, patchSpy } = buildDocCtx(components); + const engine = new TranslationEngine(); + + render( + + + + + + + + + + + , + ); + + const selectNode = (path: string): void => { + const el = frame.querySelector(`[data-editor-path="${path}"]`) as HTMLElement; + act(() => { + fireEvent.click(el); + }); + }; + return { selectNode, patchSpy }; +} + +afterEach(() => { + cleanup(); + vi.restoreAllMocks(); + document.body.innerHTML = ''; +}); +beforeEach(() => { + delete (window as any).__g7LayoutEditorHistory; +}); + +describe('EditorCanvasOverlay — 구조 편집 콜백의 선두 가드 (호출자 비신뢰)', () => { + it('N24f base 출처 노드 — ⓘ 를 우회해 onDuplicate 를 직접 호출해도 patchLayout 미호출', async () => { + const { selectNode, patchSpy } = mount([BASE_TEXT, ROUTE_TEXT]); + await waitFor(() => expect((window as any).__g7LayoutEditorHistory).toBeTruthy()); + selectNode('0'); + + const btn = document.querySelector('[data-testid="stub-duplicate"]') as HTMLElement; + expect(btn, '스텁 오버레이가 잠금 여부와 무관하게 트리거를 내야 한다').not.toBeNull(); + expect(btn.getAttribute('data-lock-kind')).toBe('base'); + act(() => { + fireEvent.click(btn); + }); + expect(patchSpy).not.toHaveBeenCalled(); + }); + + it('N24g 회귀 가드 — route 출처 노드는 같은 경로로 실제 복제된다', async () => { + // N24f 의 「부재」가 공허하지 않음을 증명한다 — 이 하네스에서 복제는 도달 가능하다. + const { selectNode, patchSpy } = mount([BASE_TEXT, ROUTE_TEXT]); + await waitFor(() => expect((window as any).__g7LayoutEditorHistory).toBeTruthy()); + selectNode('1'); + + const btn = document.querySelector('[data-testid="stub-duplicate"]') as HTMLElement; + expect(btn.getAttribute('data-lock-kind')).toBe('none'); + act(() => { + fireEvent.click(btn); + }); + expect(patchSpy).toHaveBeenCalled(); + }); +}); diff --git a/resources/js/core/template-engine/layout-editor/__tests__/components/EditorCanvasOverlay.sourceLockGuard.test.tsx b/resources/js/core/template-engine/layout-editor/__tests__/components/EditorCanvasOverlay.sourceLockGuard.test.tsx new file mode 100644 index 00000000..610cbc25 --- /dev/null +++ b/resources/js/core/template-engine/layout-editor/__tests__/components/EditorCanvasOverlay.sourceLockGuard.test.tsx @@ -0,0 +1,290 @@ +/** + * EditorCanvasOverlay.sourceLockGuard.test.tsx — 상속·주입 노드 편집 진입 차단 (A3) + * + * `route` 편집 모드에서 상속(base)·주입(extension) 출처 노드는 저장 시 + * `stripInheritedNode` 가 통째로 폐기하므로 편집분이 오류도 경고도 없이 사라진다. + * (저장은 200 으로 성공하고 `history.clear()` 로 undo 도 불가능하다.) + * + * 종전에는 잠금 판정이 ⓘ 컨텍스트 메뉴와 DnD 핸들에만 걸려 있었고, 아래 4개 진입점은 + * 무방비였다 — 더블클릭 인라인 편집 / 복제 / 키보드 `Delete` / 잘라내기. + * 이 파일은 그 4개 표면 각각의 차단과, 라우트 소유 노드의 정상 동작(회귀 가드)을 잠근다. + * + * @since engine-v1.66.0 + */ + +import React from 'react'; +import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; +import { render, act, cleanup, fireEvent, waitFor } from '@testing-library/react'; +import { EditorCanvasOverlay } from '../../components/EditorCanvasOverlay'; +import { LayoutEditorProvider, useLayoutEditor } from '../../LayoutEditorContext'; +import { LayoutDocumentProvider } from '../../LayoutDocumentContext'; +import { EditorModalProvider } from '../../EditorModalContext'; +import { TranslationProvider } from '../../../TranslationContext'; +import { TranslationEngine } from '../../../TranslationEngine'; +import type { UseLayoutDocumentResult, LoadedLayoutDocument } from '../../hooks/useLayoutDocument'; +import type { EditorNode } from '../../utils/layoutTreeUtils'; +import { readClipboard, clearClipboard } from '../../utils/editorClipboard'; + +interface Rect { + left: number; + top: number; + width: number; + height: number; +} +function domRect(r: Rect): DOMRect { + return { + ...r, + right: r.left + r.width, + bottom: r.top + r.height, + x: r.left, + y: r.top, + toJSON: () => ({}), + } as DOMRect; +} + +/** 미리보기 프레임 + 편집 노드 DOM 모킹 (wrapper 로 감싼다 — 리스너 등록 대상) */ +function buildFrame(children: Array<{ path: string; rect: Rect }>): HTMLElement { + const wrapper = document.createElement('div'); + const frame = document.createElement('div'); + vi.spyOn(frame, 'getBoundingClientRect').mockReturnValue( + domRect({ left: 0, top: 0, width: 400, height: 400 }), + ); + for (const c of children) { + const el = document.createElement('div'); + el.dataset.editorPath = c.path; + el.dataset.editorId = `id-${c.path}`; + el.setAttribute('data-editor-path', c.path); + vi.spyOn(el, 'getBoundingClientRect').mockReturnValue(domRect(c.rect)); + frame.appendChild(el); + } + wrapper.appendChild(frame); + document.body.appendChild(wrapper); + return frame; +} + +function buildDocCtx(initialComponents: EditorNode[]): { + ctx: UseLayoutDocumentResult; + patchSpy: ReturnType; +} { + let document_: LoadedLayoutDocument = { + layoutName: 'home', + raw: { components: initialComponents }, + lockVersion: 1, + }; + const patchSpy = vi.fn(); + const ctx: UseLayoutDocumentResult = { + document: document_, + isLoading: false, + error: null, + isDirty: false, + saveSuccessCounter: 0, + reload: async () => {}, + patchLayout: (patcher) => { + patchSpy(patcher); + const current = (document_.raw.components as EditorNode[]) ?? []; + const next = patcher(current); + document_ = { ...document_, raw: { ...document_.raw, components: next } }; + (ctx as any).document = document_; + }, + setLayoutComponents: (next) => { + document_ = { ...document_, raw: { ...document_.raw, components: next } }; + (ctx as any).document = document_; + }, + save: async () => ({ kind: 'success', newLockVersion: 2 }), + }; + return { ctx, patchSpy }; +} + +function RouteSeeder({ children }: { children: React.ReactNode }): React.ReactElement { + const { dispatch } = useLayoutEditor(); + React.useEffect(() => { + dispatch({ type: 'SELECT_ROUTE', route: { path: '/', layoutName: 'home' } }); + }, [dispatch]); + return <>{children}; +} + +function mount(components: EditorNode[]) { + const frame = buildFrame([ + { path: '0', rect: { left: 0, top: 0, width: 400, height: 100 } }, + { path: '1', rect: { left: 0, top: 100, width: 400, height: 100 } }, + ]); + const { ctx, patchSpy } = buildDocCtx(components); + const engine = new TranslationEngine(); + const manifest = { components: {} } as any; + + render( + + + + + + + + + + + , + ); + + const nodeEl = (path: string): HTMLElement => + frame.querySelector(`[data-editor-path="${path}"]`) as HTMLElement; + + /** 캔버스 노드 클릭 → 선택 (frame 부모 wrapper 의 capture 리스너가 잡는다) */ + const selectNode = (path: string): void => { + act(() => { + fireEvent.click(nodeEl(path), { clientX: 10, clientY: 10 }); + }); + }; + + return { frame, nodeEl, selectNode, patchSpy }; +} + +/** 상속(base) 출처 + 평문 텍스트 — 더블클릭 인라인 편집의 대상이 되는 형태 */ +const BASE_TEXT: EditorNode = { + name: 'Span', + type: 'basic', + __source: { kind: 'base', layout: '_user_base' }, + text: '푸터 문구', +}; +/** 라우트 소유 + 평문 텍스트 — 회귀 가드용 대조군 */ +const ROUTE_TEXT: EditorNode = { + name: 'Span', + type: 'basic', + __source: { kind: 'route' }, + text: '본문 문구', +}; + +afterEach(() => { + cleanup(); + vi.restoreAllMocks(); + document.body.innerHTML = ''; +}); +beforeEach(() => { + delete (window as any).__g7LayoutEditorHistory; + // 클립보드는 sessionStorage 라 케이스 간 누수된다 — N24d 의 「부재」 단언이 공허해진다. + clearClipboard(); +}); + +describe('EditorCanvasOverlay — 상속·주입 노드 인라인 편집 차단 (A3)', () => { + it('N21 base 출처 평문 노드 더블클릭 → 인라인 편집기 미마운트', async () => { + const { nodeEl } = mount([BASE_TEXT, ROUTE_TEXT]); + await waitFor(() => expect((window as any).__g7LayoutEditorHistory).toBeTruthy()); + act(() => { + fireEvent.doubleClick(nodeEl('0'), { clientX: 10, clientY: 10 }); + }); + expect(document.querySelector('[data-testid="g7le-inline-text-editor"]')).toBeNull(); + }); + + it('N22 회귀 가드 — route 출처 평문 노드 더블클릭은 그대로 진입한다', async () => { + const { nodeEl } = mount([BASE_TEXT, ROUTE_TEXT]); + await waitFor(() => expect((window as any).__g7LayoutEditorHistory).toBeTruthy()); + act(() => { + fireEvent.doubleClick(nodeEl('1'), { clientX: 10, clientY: 150 }); + }); + expect(document.querySelector('[data-testid="g7le-inline-text-editor"]')).not.toBeNull(); + }); +}); + +describe('EditorCanvasOverlay — 상속·주입 노드 구조 편집 차단 (A3)', () => { + it('N23 base 출처 노드 선택 + 키보드 Delete → patchLayout 미호출', async () => { + const { selectNode, patchSpy } = mount([BASE_TEXT, ROUTE_TEXT]); + await waitFor(() => expect((window as any).__g7LayoutEditorHistory).toBeTruthy()); + selectNode('0'); + act(() => { + fireEvent.keyDown(window, { key: 'Delete' }); + }); + expect(patchSpy).not.toHaveBeenCalled(); + }); + + it('N24 base 출처 노드 잘라내기(Ctrl+X) → patchLayout 미호출', async () => { + const { selectNode, patchSpy } = mount([BASE_TEXT, ROUTE_TEXT]); + await waitFor(() => expect((window as any).__g7LayoutEditorHistory).toBeTruthy()); + selectNode('0'); + act(() => { + fireEvent.keyDown(window, { key: 'x', ctrlKey: true }); + }); + expect(patchSpy).not.toHaveBeenCalled(); + }); + + /** + * N24d — `handleCut` 의 **선두 가드 고유 목적**을 잰다. + * + * N24 는 `patchLayout` 미호출만 단언하는데, 그것은 `handleCut` 이 호출하는 + * `handleDelete` 의 가드(G7)만으로도 성립한다 — 그래서 `handleCut` 자신의 가드를 지워도 + * 초록이었다(라운드3 probe 실측). 계획서 §7.4 가 이 가드를 따로 둔 이유는 + * 「복사는 됐는데 원본이 남는」 어긋난 상태 방지, 즉 **클립보드에 기록되지 않는 것**이다. + */ + it('N24d base 출처 노드 잘라내기 → 클립보드에도 기록되지 않는다', async () => { + const { selectNode } = mount([BASE_TEXT, ROUTE_TEXT]); + await waitFor(() => expect((window as any).__g7LayoutEditorHistory).toBeTruthy()); + selectNode('0'); + act(() => { + fireEvent.keyDown(window, { key: 'x', ctrlKey: true }); + }); + expect(readClipboard()).toBeNull(); + }); + + /** + * N24e — N24d 의 「부재」가 공허하지 않음을 증명하는 반증 가드. + * 같은 하네스에서 route 출처 노드는 실제로 클립보드에 실린다. + */ + it('N24e 회귀 가드 — route 출처 노드 잘라내기는 클립보드에 실린다', async () => { + const { selectNode } = mount([BASE_TEXT, ROUTE_TEXT]); + await waitFor(() => expect((window as any).__g7LayoutEditorHistory).toBeTruthy()); + selectNode('1'); + act(() => { + fireEvent.keyDown(window, { key: 'x', ctrlKey: true }); + }); + expect(readClipboard()).not.toBeNull(); + }); + + it('N24b base 출처 노드는 복제 진입 경로 자체가 없다 (ⓘ 미렌더)', async () => { + const { selectNode, patchSpy } = mount([BASE_TEXT, ROUTE_TEXT]); + await waitFor(() => expect((window as any).__g7LayoutEditorHistory).toBeTruthy()); + selectNode('0'); + + // ⓘ 가 없으면 「복제」 메뉴 항목에 도달할 방법이 없다 — 복제의 유일한 호출부다. + expect(document.querySelector('[data-testid="g7le-overlay-info-button"]')).toBeNull(); + expect(document.querySelector('[data-testid="g7le-context-menu-duplicate"]')).toBeNull(); + expect(patchSpy).not.toHaveBeenCalled(); + }); + + it('N24c 회귀 가드 — route 출처 노드는 ⓘ→복제로 실제 복제된다', async () => { + // 이 케이스가 없으면 N24b 의 「부재」 단언이 공허해진다 — + // 복제 경로가 이 하네스에서 애초에 도달 가능함을 여기서 증명한다. + const { selectNode, patchSpy } = mount([BASE_TEXT, ROUTE_TEXT]); + await waitFor(() => expect((window as any).__g7LayoutEditorHistory).toBeTruthy()); + selectNode('1'); + + const info = document.querySelector('[data-testid="g7le-overlay-info-button"]') as HTMLElement; + expect(info).not.toBeNull(); + act(() => { + fireEvent.click(info); + }); + + const dup = document.querySelector('[data-testid="g7le-context-menu-duplicate"]') as HTMLElement; + expect(dup).not.toBeNull(); + act(() => { + fireEvent.click(dup); + }); + expect(patchSpy).toHaveBeenCalled(); + }); + + it('N25 회귀 가드 — route 출처 노드는 Delete 로 정상 삭제된다', async () => { + const { selectNode, patchSpy } = mount([BASE_TEXT, ROUTE_TEXT]); + await waitFor(() => expect((window as any).__g7LayoutEditorHistory).toBeTruthy()); + selectNode('1'); + act(() => { + fireEvent.keyDown(window, { key: 'Delete' }); + }); + expect(patchSpy).toHaveBeenCalled(); + }); +}); diff --git a/resources/js/core/template-engine/layout-editor/__tests__/components/PropertyEditorModal.propsTab.test.tsx b/resources/js/core/template-engine/layout-editor/__tests__/components/PropertyEditorModal.propsTab.test.tsx index a3fe0fa9..5c64dcfc 100644 --- a/resources/js/core/template-engine/layout-editor/__tests__/components/PropertyEditorModal.propsTab.test.tsx +++ b/resources/js/core/template-engine/layout-editor/__tests__/components/PropertyEditorModal.propsTab.test.tsx @@ -10,7 +10,7 @@ import React from 'react'; import { describe, it, expect, vi, beforeAll, beforeEach } from 'vitest'; -import { render, screen } from '@testing-library/react'; +import { render, screen, fireEvent } from '@testing-library/react'; import { PropertyEditorModal } from '../../components/PropertyEditorModal'; import { registerCoreWidgets } from '../../spec/registerCoreWidgets'; import { @@ -196,3 +196,42 @@ describe('PropertyEditorModal — 데이터 연결(dataProps) 영역', () => { expect(screen.queryByTestId('g7le-iteration-binding-section')).toBeNull(); }); }); + +// ============================================================================ +// 코어 dataKey 컨트롤 — nodeKey apply 통합 (A2) +// +// 엔진 switch 에 `nodeKey` case 가 없던 동안 이 컨트롤은 값을 넣어도 아무 일도 +// 일어나지 않는 무음 no-op 이었다. 통합 축으로 "패치가 노드 최상위에 간다" 를 잠근다. +// ============================================================================ +describe('PropertyEditorModal — 코어 dataKey(nodeKey apply) 통합', () => { + const dataKeySpec: EditorSpec = { + controls: {}, + componentCapabilities: { + // 폼 컨테이너 opt-in — coreProps 에 dataKey 를 명시해야 노출된다. + Form: { coreProps: ['id', 'dataKey'] }, + }, + }; + + it('5-15 dataKey 입력 → onPatchNode 인자의 node.dataKey 설정, props.dataKey 부재', () => { + const onPatchNode = vi.fn(); + render( + , + ); + const input = screen.getByTestId('g7le-widget-core-datakey'); + fireEvent.change(input, { target: { value: 'orderer' } }); + fireEvent.blur(input, { target: { value: 'orderer' } }); + + expect(onPatchNode).toHaveBeenCalled(); + const patched = onPatchNode.mock.calls.at(-1)![0] as EditorNode & Record; + expect(patched.dataKey).toBe('orderer'); + expect(patched.props?.dataKey).toBeUndefined(); + }); +}); diff --git a/resources/js/core/template-engine/layout-editor/__tests__/components/dimension-and-attachments.test.tsx b/resources/js/core/template-engine/layout-editor/__tests__/components/dimension-and-attachments.test.tsx index dc0413a6..0966b7f4 100644 --- a/resources/js/core/template-engine/layout-editor/__tests__/components/dimension-and-attachments.test.tsx +++ b/resources/js/core/template-engine/layout-editor/__tests__/components/dimension-and-attachments.test.tsx @@ -177,4 +177,36 @@ describe('LayoutAttachmentManager', () => { fireEvent.click(screen.getByTestId('g7le-attachment-delete-1')); expect(fn.mock.calls.some((c) => (c[1] as RequestInit)?.method === 'DELETE')).toBe(false); }); + /** + * 공개 자산 스토리지를 켜면 서버가 교차 출처 절대 URL 을 돌려준다 (공개 #134). + * 화면은 그 문자열을 가공 없이 그대로 배경 CSS 와 선택 콜백에 흘려야 한다 — + * same-origin 상대 경로를 가정해 접두사를 붙이면 CDN 주소가 깨진다. + * + * @scenario public_asset_disk=public,row_disk=public,filter_url_hook=absent + * + * @effects editor_thumbnail_renders_cross_origin, direct_url_when_row_matches_public_disk + */ + it('교차 출처 CDN 절대 URL 을 썸네일·선택 콜백에 그대로 보존', async () => { + const cdnUrl = 'https://cdn.example.test/template-layout-attachments/sirsoft-basic/hero.png'; + const cdnList = [{ ...sampleList[0], url: cdnUrl }]; + mockFetch(() => ({ ok: true, json: async () => ({ success: true, data: cdnList }) })); + + const onSelect = vi.fn(); + render( + , + ); + + const card = await screen.findByTestId('g7le-attachment-card-1'); + const thumb = card.firstElementChild as HTMLElement; + expect(thumb.style.backgroundImage).toContain(cdnUrl); + + fireEvent.click(screen.getByTestId('g7le-attachment-use-1')); + expect(onSelect).toHaveBeenCalledWith(cdnUrl); + }); }); diff --git a/resources/js/core/template-engine/layout-editor/__tests__/components/property-controls.test.tsx b/resources/js/core/template-engine/layout-editor/__tests__/components/property-controls.test.tsx index 739ed085..7c794a60 100644 --- a/resources/js/core/template-engine/layout-editor/__tests__/components/property-controls.test.tsx +++ b/resources/js/core/template-engine/layout-editor/__tests__/components/property-controls.test.tsx @@ -24,6 +24,7 @@ import { TagInputControl } from '../../components/property-controls/TagInputCont import { CompositeSettingsForm } from '../../components/property-controls/CompositeSettingsForm'; import { AdvancedPropsForm } from '../../components/property-controls/AdvancedPropsForm'; import { ControlRenderer } from '../../components/property-controls/ControlRenderer'; +import { NumberWidget } from '../../components/property-controls/StyleControlWidgets'; import { registerWidget, clearWidgetRegistry } from '../../spec/widgetRegistry'; import { registerCoreWidgets, resetCoreWidgetRegistration } from '../../spec/registerCoreWidgets'; import { LayoutEditorProvider } from '../../LayoutEditorContext'; @@ -201,6 +202,51 @@ describe('ImagePickerControl', () => { await vi.waitFor(() => expect(screen.getByTestId('g7le-image-error')).toBeTruthy()); }); + /** + * 공개 자산 스토리지를 켜면 업로드 응답의 url 이 교차 출처 절대 주소(CDN)로 온다 (공개 #134). + * 위젯은 그 문자열을 가공 없이 값으로 세워야 한다 — same-origin 상대 경로를 가정해 + * 접두사를 붙이거나 정규화하면 CDN 주소가 깨지고, 그 값이 레이아웃에 그대로 저장된다. + * + * @scenario public_asset_disk=public,row_disk=public,filter_url_hook=absent + * + * @effects direct_url_when_row_matches_public_disk, editor_thumbnail_renders_cross_origin + */ + it('업로드 응답의 교차 출처 CDN 절대 URL 을 값으로 그대로 세운다', async () => { + const cdnUrl = 'https://cdn.example.test/template-layout-attachments/sirsoft-basic/hero.png'; + vi.stubGlobal( + 'fetch', + vi.fn().mockResolvedValue({ + ok: true, + json: async () => ({ success: true, data: { url: cdnUrl } }), + }), + ); + const onChange = renderImage(undefined); + const file = new File(['x'], 'hero.png', { type: 'image/png' }); + fireEvent.change(screen.getByTestId('g7le-image-file'), { target: { files: [file] } }); + await vi.waitFor(() => expect(onChange).toHaveBeenCalled()); + const v = onChange.mock.calls.at(-1)![0] as Record; + expect(v.url).toBe(cdnUrl); + expect(v.size).toBe('cover'); // 기본 fill 모드 — 교차 출처여도 분해 규칙은 동일 + }); + + /** + * 교차 출처 절대 URL 도 미리보기 배경 CSS 로 분해돼야 한다 (공개 #134). + * + * @scenario public_asset_disk=public,row_disk=public,filter_url_hook=absent + * + * @effects editor_thumbnail_renders_cross_origin + */ + it('교차 출처 CDN 절대 URL 을 배경 CSS 와 입력칸에 그대로 반영한다', () => { + const cdnUrl = 'https://cdn.example.test/template-layout-attachments/sirsoft-basic/hero.png'; + renderImage({ url: cdnUrl, size: 'contain', repeat: 'no-repeat', position: 'top' }); + const preview = screen.getByTestId('g7le-image-preview'); + expect(preview.style.backgroundImage).toContain(cdnUrl); + expect(preview.style.backgroundSize).toBe('contain'); + // jsdom 은 background-position 단일 키워드를 'center top' 으로 정규화한다 + expect(preview.style.backgroundPosition).toContain('top'); + expect(screen.getByTestId('g7le-image-url')).toHaveValue(cdnUrl); + }); + it('`기본` → onChange(undefined)', () => { const onChange = renderImage({ url: 'https://x/a.png' }); fireEvent.click(screen.getByTestId('g7le-image-clear')); @@ -208,6 +254,187 @@ describe('ImagePickerControl', () => { }); }); +// ============================================================================ +// ImagePickerControl — 단일 값 슬롯 / 데이터 연결 보호 (공개 #135) +// +// `propValue`·`cssVar`·단일 `styleProp` 은 값 슬롯이 하나뿐이라 size/repeat/position +// 을 저장할 자리가 없다. 그런 컨트롤에서 표시모드 버튼은 눌러도 저장되지 않는 죽은 +// 컨트롤이므로 **컨테이너째 미렌더**한다(disabled 는 "URL 을 넣으면 살아난다"는 거짓 +// 정보를 준다). 판정은 블랙리스트라 `apply` 미선언 컨트롤은 현행대로 모드 버튼을 유지한다. +// ============================================================================ +describe('ImagePickerControl — 단일 값 슬롯 / 데이터 연결 보호 (공개 #135)', () => { + /** 실물 `hdrLogo` — sirsoft-basic 헤더 「로고 이미지」 */ + const logoCtrl: EditorControlSpec = { + widget: 'image', + apply: { type: 'propValue', propKey: 'logo' }, + }; + /** 실물 `backgroundImage` — 배경 4속성 묶음 */ + const bundleCtrl: EditorControlSpec = { + widget: 'image', + apply: { + type: 'styleProp', + props: ['backgroundImage', 'backgroundSize', 'backgroundRepeat', 'backgroundPosition'], + }, + }; + + function renderWith(control: EditorControlSpec, value: unknown, onChange = vi.fn()) { + render( + + + + + , + ); + return onChange; + } + + beforeEach(() => { + vi.stubGlobal( + 'fetch', + vi.fn().mockResolvedValue({ ok: true, json: async () => ({ success: true, data: [] }) }), + ); + }); + afterEach(() => vi.restoreAllMocks()); + + /** @effects single_value_slot_control_hides_display_mode_buttons_entirely */ + it('3-1 단일 슬롯(propValue) — 표시모드 컨테이너와 3버튼이 모두 미렌더', () => { + renderWith(logoCtrl, { url: '/a.png' }); + expect(screen.queryByTestId('g7le-image-modes')).toBeNull(); + expect(screen.queryByTestId('g7le-image-mode-fill')).toBeNull(); + expect(screen.queryByTestId('g7le-image-mode-fit')).toBeNull(); + expect(screen.queryByTestId('g7le-image-mode-tile')).toBeNull(); + }); + + /** @effects single_value_slot_control_emits_url_only_object_without_size_repeat_position */ + it('3-2 단일 슬롯 — URL blur 시 onChange 인자가 정확히 { url } (다른 키 없음)', () => { + const onChange = renderWith(logoCtrl, undefined); + const url = screen.getByTestId('g7le-image-url'); + fireEvent.change(url, { target: { value: '/b.png' } }); + fireEvent.blur(url, { target: { value: '/b.png' } }); + expect(onChange).toHaveBeenCalledWith({ url: '/b.png' }); + }); + + /** @effects single_value_slot_preview_is_pinned_to_contain_not_a_false_preview */ + it('3-3 단일 슬롯 — 미리보기는 contain 고정 (저장되지 않는 size 를 흉내내지 않는다)', () => { + renderWith(logoCtrl, { url: '/a.png', size: 'cover' }); + const preview = screen.getByTestId('g7le-image-preview') as HTMLElement; + expect(preview.style.backgroundSize).toBe('contain'); + }); + + /** @effects bundle_control_keeps_display_mode_buttons_and_active_state */ + it('3-4 bundle 컨트롤 — 모드 버튼 존재 + 현행 활성 표시 보존', () => { + renderWith(bundleCtrl, { url: '/a.png', size: 'cover', repeat: 'no-repeat' }); + expect(screen.getByTestId('g7le-image-modes')).toBeTruthy(); + expect(screen.getByTestId('g7le-image-mode-fill').getAttribute('data-active')).toBe('true'); + }); + + /** @effects apply_undeclared_control_keeps_display_mode_buttons_blacklist_not_whitelist */ + it('3-5 apply 미선언 컨트롤 — 판정 보류라 모드 버튼 유지 (기존 계약 보존)', () => { + renderWith({ widget: 'image' }, { url: '/a.png' }); + expect(screen.getByTestId('g7le-image-modes')).toBeTruthy(); + }); + + it('3-6 옵션-only apply 도 합집합으로 판정한다 (control.apply 부재에 속지 않음)', () => { + const optionOnly = { + widget: 'image', + options: [{ value: 'a', apply: { type: 'propValue', propKey: 'logo' } }], + } as unknown as EditorControlSpec; + renderWith(optionOnly, { url: '/a.png' }); + expect(screen.queryByTestId('g7le-image-modes')).toBeNull(); + }); + + /** @effects bound_value_shows_expression_badge_and_locks_destructive_controls */ + it('3-7 데이터 연결 값 — 미리보기 대신 원문 배지, 업로드·제거 비활성', () => { + renderWith(logoCtrl, { url: '{{_global.settings?.general?.site_logo_url}}' }); + expect(screen.queryByTestId('g7le-image-preview')).toBeNull(); + const badge = screen.getByTestId('g7le-image-expression'); + expect(badge.textContent).toBe('{{_global.settings?.general?.site_logo_url}}'); + expect((screen.getByTestId('g7le-image-file') as HTMLInputElement).disabled).toBe(true); + expect((screen.getByTestId('g7le-image-clear') as HTMLButtonElement).disabled).toBe(true); + // 원문은 입력칸에도 남아 있고 편집만 막힌다 — 사라지지 않는다. + expect((screen.getByTestId('g7le-image-url') as HTMLInputElement).readOnly).toBe(true); + }); + + /** @effects replace_affordance_reopens_editing_without_emitting_a_change */ + it('3-8 「직접 지정으로 바꾸기」 → 편집 개방, 그 클릭만으로는 값을 바꾸지 않는다', () => { + const onChange = renderWith(logoCtrl, { + url: '{{_global.settings?.general?.site_logo_url}}', + }); + fireEvent.click(screen.getByTestId('g7le-image-expression-replace')); + expect((screen.getByTestId('g7le-image-file') as HTMLInputElement).disabled).toBe(false); + expect((screen.getByTestId('g7le-image-url') as HTMLInputElement).readOnly).toBe(false); + expect(screen.queryByTestId('g7le-image-expression')).toBeNull(); + expect(onChange).not.toHaveBeenCalled(); + }); + + /** @effects bound_value_shows_expression_badge_and_locks_destructive_controls */ + it('3-9 데이터 연결 값 — 「이미지 관리」 진입도 잠근다 (창 안의 「배경」이 우회로다)', () => { + renderWith(logoCtrl, { url: '{{_global.settings?.general?.site_logo_url}}' }); + // 관리 모달은 onSelect 를 받아 각 카드에 「배경」 버튼을 띄우고, 그 클릭이 같은 + // setUrl 을 호출한다 — 인라인 썸네일 「사용」과 동일 동작의 다른 렌더 위치다. + // 하나만 잠그면 나머지 하나가 조용한 우회로가 된다. + expect((screen.getByTestId('g7le-image-manage') as HTMLButtonElement).disabled).toBe(true); + }); + + /** @effects replace_affordance_reopens_editing_without_emitting_a_change */ + it('3-10 「직접 지정으로 바꾸기」 후 되돌리기 — 아직 안 바꿨으면 값 변경 없이 배지로 복귀', () => { + const expr = '{{_global.settings?.general?.site_logo_url}}'; + const onChange = renderWith(logoCtrl, { url: expr }); + + fireEvent.click(screen.getByTestId('g7le-image-expression-replace')); + expect(screen.queryByTestId('g7le-image-expression')).toBeNull(); + + fireEvent.click(screen.getByTestId('g7le-image-expression-restore')); + // 배지 복귀 + 잠금 재적용 + expect(screen.getByTestId('g7le-image-expression').textContent).toBe(expr); + expect((screen.getByTestId('g7le-image-manage') as HTMLButtonElement).disabled).toBe(true); + // 바꾼 것이 없으므로 불필요한 history push 를 만들지 않는다 + expect(onChange).not.toHaveBeenCalled(); + }); + + /** @effects replace_affordance_reopens_editing_without_emitting_a_change */ + it('3-11 URL 을 넣은 뒤에도 되돌리기로 원래 연결값을 복구한다', () => { + const expr = '{{_global.settings?.general?.site_logo_url}}'; + const seen: unknown[] = []; + + // 실제 편집기처럼 값이 되돌아오는 controlled 하네스 — + // 고정 prop 으로는 "값을 바꾼 뒤" 상태를 재현할 수 없다. + function Harness(): React.ReactElement { + const [v, setV] = React.useState({ url: expr }); + return ( + { + seen.push(next); + setV(next); + }} + t={t} + /> + ); + } + + render( + + + + + , + ); + + fireEvent.click(screen.getByTestId('g7le-image-expression-replace')); + const url = screen.getByTestId('g7le-image-url'); + fireEvent.change(url, { target: { value: '/uploaded.png' } }); + fireEvent.blur(url, { target: { value: '/uploaded.png' } }); + expect(seen[seen.length - 1]).toEqual({ url: '/uploaded.png' }); + + // 원문이 화면에서 사라진 뒤에도 되돌릴 수 있어야 한다 — 없으면 편도가 된다. + fireEvent.click(screen.getByTestId('g7le-image-expression-restore')); + expect(seen[seen.length - 1]).toEqual({ url: expr }); + expect(screen.getByTestId('g7le-image-expression').textContent).toBe(expr); + }); +}); + // ============================================================================ // TagInputControl // ============================================================================ @@ -694,3 +921,96 @@ describe('SpacingWidget (여백 방향+크기)', () => { expect((screen.getByTestId('g7le-spacing-all-enabled') as HTMLInputElement).checked).toBe(false); }); }); + +// ============================================================================ +// NumberWidget — 숫자 prop 편집 (A1) +// +// 이 위젯이 없어 `widget:"number"` 컨트롤은 「지원하지 않는 컨트롤」로 폴백했다. +// 출력 타입이 `number` 여야 컴포넌트 prop 계약(`maxVisibleBoards?: number`)을 만족한다. +// ============================================================================ +describe('NumberWidget', () => { + const ctrl: EditorControlSpec = { widget: 'number' }; + + it('4-6 숫자 입력 후 blur → onChange(숫자) — 문자열이 아니다', () => { + const onChange = vi.fn(); + render(); + const input = screen.getByTestId('g7le-number-input'); + fireEvent.change(input, { target: { value: '7' } }); + fireEvent.blur(input, { target: { value: '7' } }); + expect(onChange).toHaveBeenCalledWith(7); + expect(typeof onChange.mock.calls.at(-1)![0]).toBe('number'); + }); + + it('4-7 빈 입력 → onChange(undefined) (prop 삭제)', () => { + const onChange = vi.fn(); + render(); + const input = screen.getByTestId('g7le-number-input'); + fireEvent.change(input, { target: { value: '' } }); + fireEvent.blur(input, { target: { value: '' } }); + expect(onChange).toHaveBeenCalledWith(undefined); + }); + + it('4-8 비숫자 입력 → onChange 미호출 (저장본 무손실 보존)', () => { + const onChange = vi.fn(); + render(); + const input = screen.getByTestId('g7le-number-input'); + fireEvent.blur(input, { target: { value: 'abc' } }); + expect(onChange).not.toHaveBeenCalled(); + }); + + it('4-9 0 은 유효값이라 삭제가 아니라 0 으로 방출된다', () => { + const onChange = vi.fn(); + render(); + fireEvent.blur(screen.getByTestId('g7le-number-input'), { target: { value: '0' } }); + expect(onChange).toHaveBeenCalledWith(0); + }); + + it('4-10 Enter 키도 blur 와 같이 커밋한다', () => { + const onChange = vi.fn(); + render(); + const input = screen.getByTestId('g7le-number-input'); + fireEvent.change(input, { target: { value: '3' } }); + fireEvent.keyDown(input, { key: 'Enter' }); + expect(onChange).toHaveBeenCalledWith(3); + }); + + it('4-11 min/max/step 을 HTML 속성으로 전달한다 (클램프하지 않는다)', () => { + const bounded: EditorControlSpec = { widget: 'number', min: 1, max: 10, step: 1 }; + const onChange = vi.fn(); + render(); + const input = screen.getByTestId('g7le-number-input') as HTMLInputElement; + expect(input.getAttribute('min')).toBe('1'); + expect(input.getAttribute('max')).toBe('10'); + expect(input.getAttribute('step')).toBe('1'); + // 범위 밖 값도 사용자가 넣은 그대로 방출된다 — 조용한 변조 금지. + fireEvent.blur(input, { target: { value: '99' } }); + expect(onChange).toHaveBeenCalledWith(99); + }); + + // 계약 이전(engine-v1.66.0): 바인딩 값 보호는 **위젯이 아니라** `ControlRenderer` 의 + // 공용 게이트(`boundValueGuard`)가 담당한다. 종전에는 이 위젯이 자체 읽기전용 배지를 + // 가졌는데, 그 방식이면 위젯마다 복붙이 필요하고 한 곳이 빠져도 오류가 나지 않는다 — + // 실제로 image 위젯에서 「이미지 관리」 진입 하나만 잠기지 않은 채 남아 있었다. + // 게다가 위젯 자체 분기는 공용 게이트의 「직접 지정으로 바꾸기」 해제 경로까지 막았다. + // 공용 게이트의 동작은 ControlRenderer.boundValueGuard.test.tsx 가 잠근다. + it('4-12 위젯 자체는 바인딩 값을 특별 취급하지 않는다 (보호는 공용 게이트 소관)', () => { + const onChange = vi.fn(); + render(); + // 위젯 전용 배지는 더 이상 존재하지 않는다 + expect(screen.queryByTestId('g7le-number-binding')).toBeNull(); + // 해제된 뒤에는 정상 입력칸이어야 한다 — 여기서 막으면 해제가 무의미해진다 + expect(screen.getByTestId('g7le-number-input')).toBeInTheDocument(); + // 렌더만으로는 값을 건드리지 않는다 + expect(onChange).not.toHaveBeenCalled(); + }); + + it('4-13 외부 value 변경 시 입력칸 draft 를 동기화한다', () => { + const onChange = vi.fn(); + const { rerender } = render( + , + ); + expect((screen.getByTestId('g7le-number-input') as HTMLInputElement).value).toBe('5'); + rerender(); + expect((screen.getByTestId('g7le-number-input') as HTMLInputElement).value).toBe('9'); + }); +}); diff --git a/resources/js/core/template-engine/layout-editor/__tests__/hooks/useCanvasDnd.test.ts b/resources/js/core/template-engine/layout-editor/__tests__/hooks/useCanvasDnd.test.ts index ff2c8d60..f377ab71 100644 --- a/resources/js/core/template-engine/layout-editor/__tests__/hooks/useCanvasDnd.test.ts +++ b/resources/js/core/template-engine/layout-editor/__tests__/hooks/useCanvasDnd.test.ts @@ -802,8 +802,8 @@ describe('useCanvasDnd — 모달 편집 편집루트 confine', () => { }) ); // 딤 호스트 자식(Span, path 0.children.0) 드래그 시작 시도 — 편집 루트(모달[1]) 밖. - // onDragStart 는 activeDragPath 를 항상 설정하나(기존 설계 — 시각 표시), 편집 루트 밖이면 - // decision='denied_data_bound' 로 적재되고, onDragMove 가 드롭존을 계산하지 않으며, + // 편집 루트 밖이면 decision='denied_data_bound' 로 적재되고, 거부된 드래그는 + // activeDragPath 를 남기지 않으며, onDragMove 가 드롭존을 계산하지 않고 // onDragEnd 의 commit confine 가 차단한다(실제 이동 불가). act(() => result.current.onDragStart(startEvent('0.children.0'))); const denied = trackSpy.mock.calls.find( @@ -824,3 +824,113 @@ describe('useCanvasDnd — 모달 편집 편집루트 confine', () => { expect(result.current.activeDropZone, '편집 루트 밖 드래그는 드롭존이 없어야 한다').toBeNull(); }); }); + +// ============================================================================ +// 상속·주입 노드 드래그 차단 — 시작 / 이동 / commit 3층 (A3) +// +// 종전에는 route 모드에서 이 노드들이 `data_bound` 로 분류돼 드래그가 열렸고, 옮겨도 +// 저장 시 `stripInheritedNode` 가 통째로 폐기했다. onDragEnd 에는 잠금 재검사가 아예 +// 없어 `zone === null` 에 기댄 간접 방어뿐이었다. +// ============================================================================ +describe('useCanvasDnd — 상속·주입 노드 드래그 차단 (A3)', () => { + afterEach(() => { + vi.restoreAllMocks(); + document.body.innerHTML = ''; + }); + + function scene(components: EditorNode[]) { + const patchSpy = vi.fn(); + const pushSpy = vi.fn(); + const frame = buildFrame([ + { path: '0', rect: { left: 0, top: 0, width: 400, height: 100 } }, + { path: '1', rect: { left: 0, top: 100, width: 400, height: 100 } }, + ]); + const { result } = renderHook(() => + useCanvasDnd({ + frameEl: frame, + nesting: NESTING, + editMode: 'route', + components, + patchLayout: patchSpy, + pushHistory: pushSpy, + }), + ); + return { result, patchSpy, pushSpy }; + } + + const BASE_BOUND: EditorNode = { + name: 'Div', + id: 'a', + __source: { kind: 'base' }, + text: '{{site.title}}', + }; + const EXT_BOUND: EditorNode = { + name: 'Div', + id: 'a', + __source: { kind: 'extension', extensionId: 35 }, + text: '{{content}}', + }; + const ROUTE_BOUND: EditorNode = { + name: 'Div', + id: 'b', + __source: { kind: 'route' }, + props: { src: '{{product.image}}' }, + }; + + it('N16 base 출처 → onDragStart 가 denied_base_locked + activeDragPath 미보관', () => { + const trackSpy = vi.spyOn(trackers, 'trackEditorDnd'); + const { result } = scene([BASE_BOUND, ROUTE_BOUND]); + act(() => result.current.onDragStart(startEvent('0'))); + const denied = trackSpy.mock.calls.find((c) => c[0].result === 'denied'); + expect(denied?.[0].decision).toBe('denied_base_locked'); + // 거부된 드래그가 activeDragPath 를 남기면 DragOverlay 가 따라다녀 거짓 어포던스가 된다. + expect(result.current.activeDragPath).toBeNull(); + }); + + it('N17 extension 출처 → denied_extension_locked', () => { + const trackSpy = vi.spyOn(trackers, 'trackEditorDnd'); + const { result } = scene([EXT_BOUND, ROUTE_BOUND]); + act(() => result.current.onDragStart(startEvent('0'))); + const denied = trackSpy.mock.calls.find((c) => c[0].result === 'denied'); + expect(denied?.[0].decision).toBe('denied_extension_locked'); + expect(result.current.activeDragPath).toBeNull(); + }); + + it('N18 onDragMove — 잠긴 노드는 드롭존을 계산하지 않는다', () => { + const { result } = scene([BASE_BOUND, ROUTE_BOUND]); + act(() => result.current.onDragStart(startEvent('0'))); + act(() => + result.current.onDragMove({ + active: { id: '0' }, + over: { id: slot('', 1) }, + activatorEvent: { clientX: 0, clientY: 0 } as PointerEvent, + delta: { x: 0, y: 0 }, + } as any), + ); + expect(result.current.activeDropZone).toBeNull(); + }); + + it('N19 onDragEnd 최종 가드 — 유효 zone 을 강제 주입해도 commit 되지 않는다', () => { + const trackSpy = vi.spyOn(trackers, 'trackEditorDnd'); + const { result, patchSpy, pushSpy } = scene([BASE_BOUND, ROUTE_BOUND]); + act(() => result.current.onDragStart(startEvent('0'))); + // stale 슬롯/직접 호출 등으로 유효 zone 이 들어와도 commit 직전 가드가 막아야 한다. + act(() => result.current.onDragEnd(endEvent('0', slot('', 2)))); + expect(patchSpy).not.toHaveBeenCalled(); + expect(pushSpy).not.toHaveBeenCalled(); + const denied = trackSpy.mock.calls.find( + (c) => c[0].result === 'denied' && c[0].decision === 'denied_base_locked', + ); + expect(denied).toBeTruthy(); + }); + + it('N20 회귀 가드 — route 출처 data_bound 노드는 드래그·이동 모두 허용', () => { + const trackSpy = vi.spyOn(trackers, 'trackEditorDnd'); + const { result, patchSpy } = scene([ROUTE_BOUND, { name: 'Div', id: 'c' }]); + act(() => result.current.onDragStart(startEvent('0'))); + expect(trackSpy.mock.calls.find((c) => c[0].result === 'denied')).toBeUndefined(); + expect(result.current.activeDragPath).toBe('0'); + act(() => result.current.onDragEnd(endEvent('0', slot('', 2)))); + expect(patchSpy).toHaveBeenCalled(); + }); +}); diff --git a/resources/js/core/template-engine/layout-editor/__tests__/hooks/useElementSelection.normalize.test.tsx b/resources/js/core/template-engine/layout-editor/__tests__/hooks/useElementSelection.normalize.test.tsx index b7867be6..c0e8113a 100644 --- a/resources/js/core/template-engine/layout-editor/__tests__/hooks/useElementSelection.normalize.test.tsx +++ b/resources/js/core/template-engine/layout-editor/__tests__/hooks/useElementSelection.normalize.test.tsx @@ -433,3 +433,63 @@ describe('useElementSelection — 식별 라벨 (base 파일명 / 데이터소 expect(result.current.selectedDataSourceId).toBe('_local'); }); }); + +// ============================================================================ +// route 모드 상속·주입 노드 선택 → 진입 어포던스 배선 (A3) +// +// 종전에는 이 노드들이 `data_bound` 로 분류돼 「데이터 영역」 라벨만 뜨고 편집이 열렸다. +// 이제 출처 잠금으로 분류되어 「🔒 공통 레이아웃 편집」/「🔒 확장 편집」 어포던스가 뜬다. +// 어포던스 노출의 핵심 게이트는 `selectedBaseLayout` 이므로 그것까지 함께 단언한다. +// ============================================================================ +describe('useElementSelection — 상속·주입 노드 진입 어포던스 (A3)', () => { + const affordanceTree: EditorNode = { + children: [ + // base 출처 + props 바인딩 — 종전 data_bound, 이제 base + { + name: 'Header', + __source: { kind: 'base', layout: '_user_base' }, + props: { logo: '{{_global.settings?.general?.site_logo_url}}' }, + }, + // extension 출처 + 텍스트 바인딩 — 종전 data_bound, 이제 extension + { + name: 'Span', + __source: { kind: 'extension', extensionId: 35 }, + text: '{{content}}', + }, + ], + }; + + it('N10 base 출처 + 바인딩 선택 → lockKind=base AND selectedBaseLayout=_user_base', () => { + const { result } = renderHook(() => + useElementSelection({ rootNode: affordanceTree, editMode: 'route' }), + ); + act(() => { + result.current.handleSelect('', selectByPath('0')); + }); + expect(result.current.selectedLockKind).toBe('base'); + // 이 값이 「🔒 공통 레이아웃 편집」 버튼 노출의 게이트다. + expect(result.current.selectedBaseLayout).toBe('_user_base'); + }); + + it('N11 같은 노드 → 데이터 영역 라벨·반복 진입은 사라진다 (잠금이 이겼으므로)', () => { + const { result } = renderHook(() => + useElementSelection({ rootNode: affordanceTree, editMode: 'route' }), + ); + act(() => { + result.current.handleSelect('', selectByPath('0')); + }); + expect(result.current.selectedDataSourceId).toBeNull(); + expect(result.current.selectedIsIteration).toBe(false); + }); + + it('N12 extension 출처 + 바인딩 선택 → lockKind=extension + 확장 PK 로 진입 가능', () => { + const { result } = renderHook(() => + useElementSelection({ rootNode: affordanceTree, editMode: 'route' }), + ); + act(() => { + result.current.handleSelect('', selectByPath('1')); + }); + expect(result.current.selectedLockKind).toBe('extension'); + expect(result.current.selectedNode?.__source?.extensionId).toBe(35); + }); +}); diff --git a/resources/js/core/template-engine/layout-editor/__tests__/hooks/useElementSelection.test.ts b/resources/js/core/template-engine/layout-editor/__tests__/hooks/useElementSelection.test.ts index d5f8f031..76b389de 100644 --- a/resources/js/core/template-engine/layout-editor/__tests__/hooks/useElementSelection.test.ts +++ b/resources/js/core/template-engine/layout-editor/__tests__/hooks/useElementSelection.test.ts @@ -14,6 +14,9 @@ import { isContextMenuAllowed, sliceDomPathToDepth, resolveSourceExtensionId, + isEditableLockKind, + resolveDndDenial, + type SelectionLockKind, } from '../../hooks/useElementSelection'; import type { EditorNode } from '../../utils/layoutTreeUtils'; @@ -198,16 +201,32 @@ describe('classifyLockKind', () => { expect(classifyLockKind(node, 'base')).toBe('extension'); }); - // route 모드는 종전 동작(data_bound 우선)을 그대로 보존해야 한다. - it('route 모드 + 확장 출처 data_bound 노드 → data_bound (종전 동작 보존, 회귀 방지)', () => { - // 확장 주입 영역 안의 바인딩 노드. route 모드에서는 data_bound 로 분류돼 "데이터 영역" 표식 - // 을 유지하고, 잠긴 확장 루트는 별도로 extension 어포던스를 띄운다(별도 노드). + // 계약 변경(의도적): 종전에는 route 모드에서 data_bound 를 먼저 판정해 이 노드가 + // `data_bound`(= 편집 허용)로 분류됐다. 그런데 저장 시 `stripInheritedNode` 가 확장 출처 + // 노드를 통째로 폐기하므로 편집분이 오류도 경고도 없이 사라졌다(저장은 200 성공, history + // clear 로 undo 불가). 종전 기대값이 고정하던 것은 "route 는 한 줄도 바꾸지 않는다" 는 + // 보수성 선언이었고, 그 보수성이 곧 결함이었다. + it('route 모드 + 확장 출처 data_bound 노드 → extension (편집 차단 + 확장 편집 유도)', () => { const node: EditorNode = { name: 'Span', __source: { kind: 'extension', extensionId: 35 }, text: '{{content}}', }; + expect(classifyLockKind(node, 'route')).toBe('extension'); + expect(isContextMenuAllowed(classifyLockKind(node, 'route'))).toBe(false); + }); + + // ⚠ 위 변경이 좁힌 것은 "어느 노드가 data_bound 로 분류되는가" 뿐이다. + // `data_bound ⇒ 선택·드래그·구조 편집 허용` 계약은 그대로다 — 아래가 그 반증 가드이며, + // 반대 방향으로 되돌리는 변경은 여기서 막힌다. + it('route 모드 + route 출처 data_bound 노드 → data_bound (편집 허용 계약 보존)', () => { + const node: EditorNode = { + name: 'Img', + __source: { kind: 'route' }, + props: { src: '{{product.image}}' }, + }; expect(classifyLockKind(node, 'route')).toBe('data_bound'); + expect(isContextMenuAllowed(classifyLockKind(node, 'route'))).toBe(true); }); it('route 모드 + 확장 출처 (바인딩 없음) 노드 → extension 잠금(확장 편집 어포던스)', () => { @@ -282,3 +301,136 @@ describe('resolveSourceExtensionId', () => { expect(resolveSourceExtensionId(null)).toBeNull(); }); }); + +// ============================================================================ +// route 모드 출처 잠금 우선 — 상속·주입 노드 편집 소실 차단 (A3) +// +// 종전에는 route 모드만 `data_bound` 를 먼저 판정해, 상속(base)·주입(extension) 노드 중 +// props 값 하나라도 `{{ }}` 인 것이 "편집 가능" 으로 분류됐다. 그런데 저장 시 +// `stripInheritedNode` 가 그 노드를 통째로 폐기하므로 편집분이 오류도 경고도 없이 +// 사라졌다(저장은 200 성공 + history clear 로 undo 불가). +// +// N5~N9 는 `data_bound ⇒ 선택·드래그·구조 편집 허용` 계약의 **반증 가드**다 — +// 되돌리는 변경은 여기서 막힌다. +// ============================================================================ +describe('classifyLockKind — route 모드 출처 잠금 우선 (A3)', () => { + const menu = (kind: SelectionLockKind) => isContextMenuAllowed(kind); + const drag = (kind: SelectionLockKind) => resolveDndDenial(kind) === null; + + it('N1 base 출처 + 텍스트 바인딩 → base (ⓘ·드래그 차단)', () => { + const node: EditorNode = { + name: 'H1', + __source: { kind: 'base', layout: '_user_base' }, + text: '{{site.title}}', + }; + const kind = classifyLockKind(node, 'route'); + expect(kind).toBe('base'); + expect(menu(kind)).toBe(false); + expect(drag(kind)).toBe(false); + }); + + it('N2 base 출처 + props 바인딩 → base', () => { + const node: EditorNode = { + name: 'Div', + __source: { kind: 'base' }, + props: { className: '{{theme}}' }, + }; + const kind = classifyLockKind(node, 'route'); + expect(kind).toBe('base'); + expect(menu(kind)).toBe(false); + expect(drag(kind)).toBe(false); + }); + + it('N3 base 출처 + iteration → base (반복 항목 편집 진입도 함께 닫힌다)', () => { + const node: EditorNode = { + name: 'List', + __source: { kind: 'base' }, + iteration: { source: '{{recent.data}}' }, + }; + const kind = classifyLockKind(node, 'route'); + expect(kind).toBe('base'); + expect(menu(kind)).toBe(false); + expect(drag(kind)).toBe(false); + }); + + it('N4 extension 출처 + 바인딩 → extension', () => { + const node: EditorNode = { + name: 'Span', + __source: { kind: 'extension', extensionId: 35 }, + text: '{{content}}', + }; + const kind = classifyLockKind(node, 'route'); + expect(kind).toBe('extension'); + expect(menu(kind)).toBe(false); + expect(drag(kind)).toBe(false); + }); + + it('N5 회귀 가드 — route 출처 + props 바인딩은 data_bound (편집 허용)', () => { + const node: EditorNode = { + name: 'Img', + __source: { kind: 'route' }, + props: { src: '{{product.image}}' }, + }; + const kind = classifyLockKind(node, 'route'); + expect(kind).toBe('data_bound'); + expect(menu(kind)).toBe(true); + expect(drag(kind)).toBe(true); + }); + + it('N6 회귀 가드 — route 출처 + iteration 도 data_bound (편집 허용)', () => { + const node: EditorNode = { + name: 'Div', + __source: { kind: 'route' }, + iteration: { source: '{{posts.data}}' }, + }; + const kind = classifyLockKind(node, 'route'); + expect(kind).toBe('data_bound'); + expect(menu(kind)).toBe(true); + expect(drag(kind)).toBe(true); + }); + + it('N7 회귀 가드 — 조상이 route 출처 iteration 이면 data_bound', () => { + const node: EditorNode = { name: 'Span' }; + const ancestors: EditorNode[] = [ + { name: 'List', __source: { kind: 'route' }, iteration: { source: '{{posts.data}}' } }, + ]; + const kind = classifyLockKind(node, 'route', undefined, ancestors); + expect(kind).toBe('data_bound'); + expect(menu(kind)).toBe(true); + }); + + it('N8 회귀 가드 — __source 미부여(신규 삽입) + 바인딩은 data_bound', () => { + const node: EditorNode = { name: 'Span', text: '{{x}}' }; + const kind = classifyLockKind(node, 'route'); + expect(kind).toBe('data_bound'); + expect(menu(kind)).toBe(true); + }); + + it('N9 회귀 가드 — 조상만 base 이고 자신은 미태깅이면 none (조상은 보지 않는다)', () => { + const node: EditorNode = { name: 'Span' }; + const ancestors: EditorNode[] = [{ name: 'Div', __source: { kind: 'base' } }]; + const kind = classifyLockKind(node, 'route', undefined, ancestors); + expect(kind).toBe('none'); + expect(menu(kind)).toBe(true); + expect(drag(kind)).toBe(true); + }); + + it('isEditableLockKind / resolveDndDenial 이 같은 판정을 공유한다 (조건 복사 금지)', () => { + const kinds: SelectionLockKind[] = [ + 'none', + 'data_bound', + 'base', + 'partial', + 'extension', + 'extension_point', + ]; + for (const k of kinds) { + expect(resolveDndDenial(k) === null).toBe(isEditableLockKind(k)); + expect(isContextMenuAllowed(k)).toBe(isEditableLockKind(k)); + } + expect(resolveDndDenial('base')).toBe('denied_base_locked'); + expect(resolveDndDenial('partial')).toBe('denied_base_locked'); + expect(resolveDndDenial('extension')).toBe('denied_extension_locked'); + expect(resolveDndDenial('extension_point')).toBe('denied_extension_locked'); + }); +}); diff --git a/resources/js/core/template-engine/layout-editor/__tests__/hooks/useExtensionDocument.test.ts b/resources/js/core/template-engine/layout-editor/__tests__/hooks/useExtensionDocument.test.ts index 6684c76c..d9a19fa7 100644 --- a/resources/js/core/template-engine/layout-editor/__tests__/hooks/useExtensionDocument.test.ts +++ b/resources/js/core/template-engine/layout-editor/__tests__/hooks/useExtensionDocument.test.ts @@ -681,3 +681,148 @@ describe('useExtensionDocument — 로드/저장', () => { expect(result.current.document.needsHostPicker).toBe(false); }); }); + +// ============================================================================ +// [case:backend-34] overlay 확장 — 호스트 병합 모드 저장이 injections 를 보존해야 한다 +// ============================================================================ +describe('[case:backend-34] overlay 확장 호스트 병합 저장 — injections 보존', () => { + let fetchSpy: ReturnType; + + beforeEach(() => { + fetchSpy = vi.fn(); + (global as any).fetch = fetchSpy; + if (typeof window !== 'undefined') window.localStorage?.clear(); + }); + + function enterExtension(result: any, extId: string) { + act(() => { + result.current.editor.dispatch({ type: 'ENTER_EXTENSION_EDIT', extensionId: extId }); + }); + } + + const overlayContent = { + target_layout: '_user_base', + injections: [ + { target_id: 'anchor', position: 'append_child', components: [{ id: 'ext-sel', name: 'Div' }] }, + ], + priority: 320, + }; + + /** 확장 GET + 호스트 GET(with_source_meta) mock — 호스트 앵커 아래 주입 노드 1개 */ + function mockOverlay(hostChild: Record) { + fetchSpy.mockImplementation((url: string) => { + if (url.includes('/layout-extensions/')) { + return Promise.resolve( + extResponse({ + id: 7, + extension_type: 'overlay', + lock_version: 0, + content: JSON.stringify(overlayContent), + host_layouts: ['_user_base'], + }), + ); + } + return Promise.resolve( + hostResponse([{ id: 'anchor', name: 'Div', __source: { kind: 'base' }, children: [hostChild] }]), + ); + }); + } + + it('reassembleContent — __injectionIndex 없이 백엔드 __source.injectionIndex 만 있어도 원래 injection 으로 분배', () => { + const roots = [ + { id: 'ext-sel', name: 'Div', __source: { kind: 'extension', extensionId: 7, injectionIndex: 0 } }, + ] as any; + const out = reassembleContent(overlayContent as any, roots) as any; + expect(out.injections[0].components).toEqual([{ id: 'ext-sel', name: 'Div' }]); + }); + + it('호스트 병합 모드 무변경 저장 — 주입 컴포넌트가 그대로 PUT 된다(비워지지 않음)', async () => { + mockOverlay({ + id: 'ext-sel', + name: 'Div', + __source: { kind: 'extension', extensionId: 7, injectionIndex: 0 }, + }); + const { result } = renderHook(() => combinedHook(), { wrapper: makeWrapper() }); + enterExtension(result, '7'); + await waitFor(() => expect(result.current.document.document).not.toBeNull()); + expect(result.current.document.document!.editability).toBe('ok'); + + fetchSpy.mockImplementationOnce(() => + Promise.resolve({ ok: true, status: 200, json: async () => ({ data: { lock_version: 1 } }) }), + ); + let saveResult: any; + await act(async () => { + saveResult = await result.current.document.save(); + }); + expect(saveResult.kind).toBe('success'); + const putCall = fetchSpy.mock.calls.find((c) => c[1]?.method === 'PUT')!; + const body = JSON.parse(putCall[1].body); + // 종전: injections[0].components 가 [] 로 비워져 PUT — 사이트에서 그 확장 UI 가 조용히 사라졌다. + expect(body.content.injections[0].components).toEqual([{ id: 'ext-sel', name: 'Div' }]); + expect(body.content.priority).toBe(320); + expect(body.content.target_layout).toBe('_user_base'); + }); + + it('injection 을 되찾을 수 없는 노드가 있으면 PUT 하지 않고 guard 를 돌려준다(손실 차단)', async () => { + // 순번 메타도 없고 id 도 원본 injection 과 맞지 않는 노드 — 어느 injection 인지 알 수 없다. + mockOverlay({ id: 'unknown-node', name: 'Div', __source: { kind: 'extension', extensionId: 7 } }); + const { result } = renderHook(() => combinedHook(), { wrapper: makeWrapper() }); + enterExtension(result, '7'); + await waitFor(() => expect(result.current.document.document).not.toBeNull()); + + let saveResult: any; + await act(async () => { + saveResult = await result.current.document.save(); + }); + expect(saveResult.kind).toBe('guard_extension_reassembly'); + expect(fetchSpy.mock.calls.find((c) => c[1]?.method === 'PUT')).toBeUndefined(); + }); + + it('순번 메타가 없어도 id 가 원본 injection 의 노드와 맞으면 그 injection 으로 되돌린다(구 백엔드 호환)', async () => { + mockOverlay({ id: 'ext-sel', name: 'Div', __source: { kind: 'extension', extensionId: 7 } }); + const { result } = renderHook(() => combinedHook(), { wrapper: makeWrapper() }); + enterExtension(result, '7'); + await waitFor(() => expect(result.current.document.document).not.toBeNull()); + fetchSpy.mockImplementationOnce(() => + Promise.resolve({ ok: true, status: 200, json: async () => ({ data: { lock_version: 1 } }) }), + ); + let saveResult: any; + await act(async () => { + saveResult = await result.current.document.save(); + }); + expect(saveResult.kind).toBe('success'); + const putCall = fetchSpy.mock.calls.find((c) => c[1]?.method === 'PUT')!; + expect(JSON.parse(putCall[1].body).content.injections[0].components).toEqual([ + { id: 'ext-sel', name: 'Div' }, + ]); + }); + + it('409 — 서버가 errors 아래에 넣는 current_version/your_version 을 읽는다(종전 -1)', async () => { + fetchSpy.mockResolvedValue( + extResponse({ + id: 1, + extension_type: 'extension_point', + lock_version: 3, + content: JSON.stringify({ components: [{ id: 'root' }] }), + }), + ); + const { result } = renderHook(() => combinedHook(), { wrapper: makeWrapper() }); + enterExtension(result, '1'); + await waitFor(() => expect(result.current.document.document).not.toBeNull()); + + fetchSpy.mockResolvedValueOnce({ + ok: false, + status: 409, + json: async () => ({ + success: false, + message: '다른 사용자가 먼저 저장했습니다.', + errors: { error: 'concurrent_modification', current_version: 5, your_version: 3 }, + }), + }); + let saveResult: any; + await act(async () => { + saveResult = await result.current.document.save(); + }); + expect(saveResult).toEqual({ kind: 'concurrent_modification', currentVersion: 5, yourVersion: 3 }); + }); +}); diff --git a/resources/js/core/template-engine/layout-editor/__tests__/hooks/useLayoutDocument.saveGuard.test.tsx b/resources/js/core/template-engine/layout-editor/__tests__/hooks/useLayoutDocument.saveGuard.test.tsx index c55377bc..61d92a3c 100644 --- a/resources/js/core/template-engine/layout-editor/__tests__/hooks/useLayoutDocument.saveGuard.test.tsx +++ b/resources/js/core/template-engine/layout-editor/__tests__/hooks/useLayoutDocument.saveGuard.test.tsx @@ -319,3 +319,36 @@ describe('useLayoutDocument — 중첩 경로 layoutName PUT URL 슬래시 보 expect(putUrl).not.toContain('auth%2Fforgot_password'); }); }); + +describe('useLayoutDocument — 409 본문이 errors 아래에 버전을 담는 실제 서버 형식', () => { + beforeEach(() => { + const fetchMock = vi.fn(); + fetchMock.mockResolvedValueOnce(initialLoadResponse as any).mockResolvedValueOnce( + mockStatus(409, { + success: false, + message: '다른 사용자가 먼저 저장했습니다. (리소스: template_layouts:1, 현재 버전: 9, 보낸 버전: 7)', + errors: { error: 'concurrent_modification', current_version: 9, your_version: 7 }, + }) + ); + (globalThis as any).fetch = fetchMock; + }); + + afterEach(() => { + delete (globalThis as any).fetch; + }); + + it('ResponseHelper 형식(errors.current_version) 에서도 최신/내 버전을 읽는다 — 종전 배너 「최신 버전: -1」', async () => { + const { result } = renderHook(() => useLayoutDocument(), { wrapper }); + await waitFor(() => expect(result.current.document).not.toBeNull()); + + let saveResult: any; + await act(async () => { + saveResult = await result.current.save(); + }); + expect(saveResult).toEqual({ + kind: 'concurrent_modification', + currentVersion: 9, + yourVersion: 7, + }); + }); +}); diff --git a/resources/js/core/template-engine/layout-editor/__tests__/spec/coreActionRecipes.test.ts b/resources/js/core/template-engine/layout-editor/__tests__/spec/coreActionRecipes.test.ts index 98d04b32..b92bd30d 100644 --- a/resources/js/core/template-engine/layout-editor/__tests__/spec/coreActionRecipes.test.ts +++ b/resources/js/core/template-engine/layout-editor/__tests__/spec/coreActionRecipes.test.ts @@ -24,7 +24,7 @@ const EXPECTED_HANDLERS = [ // C14~C16 데이터 'refetchDataSource', 'appendDataSource', 'updateDataSource', // C17~C22 기타 - 'scrollIntoView', 'login', 'logout', 'setLocale', 'emitEvent', 'apiCall', + 'scrollIntoView', 'login', 'loginTwoFactor', 'loginTwoFactorResend', 'logout', 'setLocale', 'emitEvent', 'apiCall', // C24~C27 제어흐름(C23 top-level if 는 엔진 처리 — 카탈로그 비포함) + C29 conditions 'conditions', 'sequence', 'parallel', 'switch', 'suppress', // 결제 진입(requestPgPayment)은 코어 카탈로그에서 제외됨 — 결제는 커머스 도메인이라 diff --git a/resources/js/core/template-engine/layout-editor/__tests__/spec/coreProps.dataKey.test.ts b/resources/js/core/template-engine/layout-editor/__tests__/spec/coreProps.dataKey.test.ts index 8d72620b..4bf558ba 100644 --- a/resources/js/core/template-engine/layout-editor/__tests__/spec/coreProps.dataKey.test.ts +++ b/resources/js/core/template-engine/layout-editor/__tests__/spec/coreProps.dataKey.test.ts @@ -47,4 +47,21 @@ describe('coreProps — dataKey', () => { expect(CORE_PROP_CONTROLS.dataKey.label).toContain('layout_editor.core_props.dataKey'); expect(CORE_PROP_CONTROLS.dataKey.placeholder).toContain('layout_editor.core_props.dataKey'); }); + + // nodeKey apply 구현으로 격리 2키가 ControlRenderer 경유로도 렌더 가능해졌다. + // 그러면 「격리 영역」 전용 UI 와 같은 노드 키를 두 경로가 쓰게 되고, 그 순간 값 타입이 + // 갈린다 — IsolatedScopeControl 은 ON 시 `{}`(빈 객체), toggle 위젯은 `true`. + it('5-12 전용 UI 소유 키(isolatedState)는 렌더 목록에서 제외한다 (이중 경로 차단)', () => { + expect(resolveCorePropKeys(['id', 'isolatedState'])).toEqual(['id']); + expect(resolveCorePropKeys(['id', 'isolatedScopeId'])).toEqual(['id']); + expect(resolveCorePropKeys(['isolatedState', 'isolatedScopeId'])).toEqual([]); + }); + + it('5-13 제외는 격리 2키에만 — id·dataKey 는 회귀 없음', () => { + expect(resolveCorePropKeys(['id', 'dataKey'])).toEqual(['id', 'dataKey']); + expect(resolveCorePropKeys(undefined)).toEqual(['id']); + // SSoT 선언 자체는 남는다 — 전용 UI 가 이 정의를 읽는다. + expect(CORE_PROP_CONTROLS.isolatedState).toBeTruthy(); + expect(CORE_PROP_CONTROLS.isolatedScopeId).toBeTruthy(); + }); }); diff --git a/resources/js/core/template-engine/layout-editor/__tests__/spec/recipeEngine.test.ts b/resources/js/core/template-engine/layout-editor/__tests__/spec/recipeEngine.test.ts index 6f996324..ebbe4724 100644 --- a/resources/js/core/template-engine/layout-editor/__tests__/spec/recipeEngine.test.ts +++ b/resources/js/core/template-engine/layout-editor/__tests__/spec/recipeEngine.test.ts @@ -1,3 +1,7 @@ +// @scenario apply_type=cssVar, consumer=none, storage_scope=template_layouts, stored_shape=object, widget_output=image_object +// @scenario apply_type=propValue, consumer=none, storage_scope=template_layouts, stored_shape=string, widget_output=scalar_string +// @scenario apply_type=propValue, consumer=none, storage_scope=template_layouts, stored_shape=string, widget_output=image_object_empty_url +// @scenario apply_type=classToken, consumer=none, storage_scope=template_layouts, stored_shape=absent, widget_output=undefined_cleared /** * recipeEngine.test.ts — 컨트롤 레시피 ↔ 노드 패치 변환 * @@ -528,3 +532,335 @@ describe('propValue 임의 prop 편집 (icon-picker / options-list / text)', () expect(out).toBe(node); }); }); + +// ============================================================================ +// image 위젯 × 단일 값 슬롯 — 값 축약 / 역조립 (공개 #135) +// +// `image` 위젯은 배경 이미지용으로 설계되어 `{url,size,repeat,position}` **객체**를 +// 내보낸다. 그 값을 노드에 기록하는 apply 4종 중 `styleProp` 의 다중 props(배경 묶음) +// 만 객체를 4속성으로 분해했고, `propValue`·`cssVar`·단일 `styleProp` 은 객체를 그대로 +// 기록했다 → 소비 컴포넌트가 `` 로 받아 `[object Object]` 가 URL 이 됐다. +// +// 게이트는 **위젯 이름**(`widget === 'image'`)이며 값 형태 sniffing 이 아니다 — +// `isImageValueObject` 는 4키 중 하나만 있어도 참이라, 값만 보면 `{position:'left'}` +// 같은 정당한 객체 prop 을 이미지로 오인해 삭제한다(1-6/1-7 이 그 가드다). +// ============================================================================ +describe('image 위젯 × 단일 값 슬롯 (공개 #135)', () => { + /** 실물 `hdrLogo` — sirsoft-basic 헤더 「로고 이미지」 */ + const logoCtrl: EditorControlSpec = { + widget: 'image', + apply: { type: 'propValue', propKey: 'logo' }, + }; + const bgBundle: EditorControlSpec = { + widget: 'image', + group: 'bg-image', + apply: { + type: 'styleProp', + props: ['backgroundImage', 'backgroundSize', 'backgroundRepeat', 'backgroundPosition'], + }, + }; + + /** @effects image_object_narrows_to_url_string_in_propvalue_slot */ + it('1-1 값 객체를 url 문자열로 축약해 props 에 기록한다', () => { + const next = applyRecipe({ name: 'Header' }, logoCtrl, { + url: '/a.png', + size: 'cover', + repeat: 'no-repeat', + position: 'center', + }); + expect(next.props?.logo).toBe('/a.png'); + expect(typeof next.props?.logo).toBe('string'); + }); + + /** @effects legacy_css_url_wrapping_is_stripped_for_component_prop_sink */ + it('1-2 레거시 url(...) 래핑을 벗긴다 (컴포넌트 prop 은 CSS 문맥이 아니다)', () => { + const next = applyRecipe({ name: 'Header' }, logoCtrl, { url: 'url(/a.png)' }); + expect(next.props?.logo).toBe('/a.png'); + }); + + /** @effects image_object_without_url_or_with_empty_url_deletes_the_prop */ + it('1-3 url 없이 모드만 담긴 값은 prop 을 삭제한다 (기존 빈값 술어에 위임)', () => { + const seeded: EditorNode = { name: 'Header', props: { logo: '/old.png' } }; + expect(applyRecipe(seeded, logoCtrl, { size: 'contain' }).props?.logo).toBeUndefined(); + }); + + it('1-4 빈 url 은 prop 을 삭제한다', () => { + expect(applyRecipe({ name: 'Header' }, logoCtrl, { url: '' }).props?.logo).toBeUndefined(); + }); + + it('1-5 undefined(기본으로 되돌리기) 는 prop 을 삭제한다', () => { + const seeded: EditorNode = { name: 'Header', props: { logo: '/old.png' } }; + expect(applyRecipe(seeded, logoCtrl, undefined).props?.logo).toBeUndefined(); + }); + + /** @effects non_image_widget_object_and_array_props_are_preserved_intact */ + it('1-6 오탐 가드 — 비-image 위젯의 객체 prop 은 통째로 보존한다', () => { + // `{position:'left'}` 는 isImageValueObject 를 통과한다(4키 중 하나 보유). + // 값 sniffing 으로 게이트했다면 여기서 정당한 prop 이 삭제됐을 것이다. + const tooltipCtrl: EditorControlSpec = { + widget: 'select', + apply: { type: 'propValue', propKey: 'tooltip' }, + }; + const next = applyRecipe({ name: 'Button' }, tooltipCtrl, { position: 'left' }); + expect(next.props?.tooltip).toEqual({ position: 'left' }); + }); + + it('1-7 오탐 가드 — options-list 배열은 그대로 보존한다', () => { + const optionsCtrl: EditorControlSpec = { + widget: 'options-list', + apply: { type: 'propValue', propKey: 'options' }, + }; + const next = applyRecipe({ name: 'Select' }, optionsCtrl, [{ value: 'a' }]); + expect(next.props?.options).toEqual([{ value: 'a' }]); + }); + + /** @effects image_object_narrows_to_wrapped_css_url_in_cssvar_and_single_styleprop */ + it('1-8 cssVar 슬롯은 CSS 문맥이므로 url(...) 로 감싼다', () => { + const heroVar: EditorControlSpec = { + widget: 'image', + apply: { type: 'cssVar', varName: '--hero' }, + }; + const next = applyRecipe({ name: 'Div' }, heroVar, { url: '/a.png', size: 'cover' }); + expect((next.props?.style as Record)['--hero']).toBe('url(/a.png)'); + }); + + it('1-9 단일 styleProp 슬롯도 CSS 문맥이므로 url(...) 로 감싼다', () => { + const singleBg: EditorControlSpec = { + widget: 'image', + apply: { type: 'styleProp', prop: 'backgroundImage' }, + }; + const next = applyRecipe({ name: 'Div' }, singleBg, { url: '/a.png', size: 'cover' }); + expect((next.props?.style as Record).backgroundImage).toBe('url(/a.png)'); + }); + + /** @effects styleprop_bundle_four_property_decomposition_is_unchanged */ + it('1-10 bundle(다중 props) 경로는 무회귀 — 4속성 분해가 그대로', () => { + const value = { url: '/a.png', size: 'cover', repeat: 'no-repeat', position: 'center' }; + const style = applyRecipe({ name: 'Div' }, bgBundle, value).props?.style as Record< + string, + unknown + >; + expect(style).toEqual({ + backgroundImage: 'url(/a.png)', + backgroundSize: 'cover', + backgroundRepeat: 'no-repeat', + backgroundPosition: 'center', + }); + }); + + /** @effects apply_fixed_value_wins_over_control_value_then_gets_narrowed */ + it('1-11 apply.value 고정값이 값보다 우선하고, 그 뒤에 축약이 적용된다', () => { + const fixed: EditorControlSpec = { + widget: 'image', + apply: { type: 'propValue', propKey: 'logo', value: '/fixed.png' }, + }; + expect(applyRecipe({ name: 'Header' }, fixed, { url: '/x.png' }).props?.logo).toBe( + '/fixed.png', + ); + }); + + /** @effects stored_string_is_rewrapped_as_url_object_for_the_widget */ + it('2-1 저장된 문자열을 위젯이 이해하는 객체로 되감는다', () => { + const node: EditorNode = { name: 'Header', props: { logo: '/a.png' } }; + expect(reverseResolve(node, logoCtrl)).toEqual({ value: { url: '/a.png' }, matched: true }); + }); + + it('2-2 값이 없으면 미매칭', () => { + expect(reverseResolve({ name: 'Header', props: {} }, logoCtrl)).toEqual({ + value: undefined, + matched: false, + }); + }); + + /** @effects binding_expression_is_rewrapped_so_one_click_upload_cannot_silently_lose_it */ + it('2-3 표현식 문자열도 감싼다 — 빈 피커로 보여 1클릭에 소실되지 않도록', () => { + const expr = '{{_global.settings?.general?.site_logo_url}}'; + const node: EditorNode = { name: 'Header', props: { logo: expr } }; + expect(reverseResolve(node, logoCtrl)).toEqual({ value: { url: expr }, matched: true }); + }); + + it('2-4 레거시 url(...) 저장값은 언래핑해 되감는다', () => { + const node: EditorNode = { name: 'Header', props: { logo: 'url(/a.png)' } }; + expect(reverseResolve(node, logoCtrl)).toEqual({ value: { url: '/a.png' }, matched: true }); + }); + + /** @effects legacy_object_stored_value_is_returned_losslessly */ + it('2-5 레거시 객체 저장값은 무손실로 그대로 돌려준다', () => { + const legacy = { url: '/a.png', size: 'cover' }; + const node: EditorNode = { name: 'Header', props: { logo: legacy } }; + expect(reverseResolve(node, logoCtrl)).toEqual({ value: legacy, matched: true }); + }); + + /** @effects non_image_widget_values_are_not_rewrapped */ + it('2-6 비-image 위젯은 감싸지 않는다 (문자열 그대로)', () => { + const widthProp: EditorControlSpec = { + widget: 'select', + apply: { type: 'propValue', propKey: 'size' }, + }; + const node: EditorNode = { name: 'Button', props: { size: 'sm' } }; + expect(reverseResolve(node, widthProp)).toEqual({ value: 'sm', matched: true }); + }); + + /** @effects node_axis_roundtrip_is_a_fixed_point_after_first_pass */ + it('2-7 노드 축 고정점 — apply → reverse → apply 2회차가 1회차와 동일', () => { + const first = applyRecipe({ name: 'Header' }, logoCtrl, { + url: '/a.png', + size: 'cover', + repeat: 'no-repeat', + position: 'center', + }); + const back = reverseResolve(first, logoCtrl); + const second = applyRecipe(first, logoCtrl, back.value); + expect(second).toEqual(first); + }); + + /** @effects dark_scope_stays_readonly_for_inline_apply */ + it('2-8 다크 scope 는 인라인이라 읽기 전용', () => { + const node: EditorNode = { name: 'Header', props: { logo: '/a.png' } }; + expect(reverseResolve(node, logoCtrl, { colorScheme: 'dark', breakpoint: 'base' })).toEqual({ + value: undefined, + matched: false, + darkReadonly: true, + }); + }); +}); + +// ============================================================================ +// number 위젯 값 — propValue 숫자 기록 (A1) +// +// `applyPropValue` 는 값을 가공하지 않으므로 위젯이 `number` 를 내보내야 한다. +// `0` 은 유효값이며 빈값 술어(`''|null|undefined`)에 걸리지 않는다. +// ============================================================================ +describe('number 위젯 값 — propValue 숫자 기록', () => { + const maxBoards: EditorControlSpec = { + widget: 'number', + apply: { type: 'propValue', propKey: 'maxVisibleBoards' }, + }; + + it('4-1 숫자를 그대로 props 에 기록한다 (문자열로 변질되지 않음)', () => { + const next = applyRecipe({ name: 'Header' }, maxBoards, 5); + expect(next.props?.maxVisibleBoards).toBe(5); + expect(typeof next.props?.maxVisibleBoards).toBe('number'); + }); + + it('4-2 0 은 유효값이라 삭제되지 않는다', () => { + const next = applyRecipe({ name: 'Header' }, maxBoards, 0); + expect(next.props?.maxVisibleBoards).toBe(0); + }); + + it('4-3 빈 문자열·undefined 는 prop 을 삭제한다', () => { + const seeded: EditorNode = { name: 'Header', props: { maxVisibleBoards: 5 } }; + expect(applyRecipe(seeded, maxBoards, '').props?.maxVisibleBoards).toBeUndefined(); + expect(applyRecipe(seeded, maxBoards, undefined).props?.maxVisibleBoards).toBeUndefined(); + }); + + it('4-4 라운드트립 — 숫자 그대로 역해석', () => { + const applied = applyRecipe({ name: 'Header' }, maxBoards, 7); + expect(reverseResolve(applied, maxBoards)).toEqual({ value: 7, matched: true }); + // 0 도 역해석된다(undefined 와 구분). + const zero = applyRecipe({ name: 'Header' }, maxBoards, 0); + expect(reverseResolve(zero, maxBoards)).toEqual({ value: 0, matched: true }); + }); +}); + +// ============================================================================ +// nodeKey apply — 노드 최상위 구조키 패치 (A2) +// +// `coreProps.ts` 가 `{type:'nodeKey', nodeKey:'dataKey'}` 를 선언했는데 엔진 switch 에 +// case 가 없어 **무음 no-op** 이었다 — 값을 넣어도 아무 일도 일어나지 않고 역해석도 +// 항상 undefined 였다. props 로 흘리면 `props.dataKey` 가 돼 런타임이 영영 읽지 않는다. +// ============================================================================ +describe('nodeKey apply — 노드 최상위 구조키 (A2)', () => { + const dataKeyCtrl: EditorControlSpec = { + widget: 'core-datakey', + apply: { type: 'nodeKey', nodeKey: 'dataKey' } as unknown as string, + }; + + it('5-1 노드 최상위에 기록하고 props 를 오염시키지 않는다', () => { + const next = applyRecipe({ name: 'Form' }, dataKeyCtrl, 'orderer') as EditorNode & + Record; + expect(next.dataKey).toBe('orderer'); + expect(next.props?.dataKey).toBeUndefined(); + }); + + it('5-2 빈 문자열은 키를 삭제한다', () => { + const seeded = { name: 'Form', dataKey: 'orderer' } as EditorNode; + const next = applyRecipe(seeded, dataKeyCtrl, '') as EditorNode & Record; + expect('dataKey' in next).toBe(false); + }); + + it('5-3 undefined 도 키를 삭제한다', () => { + const seeded = { name: 'Form', dataKey: 'orderer' } as EditorNode; + const next = applyRecipe(seeded, dataKeyCtrl, undefined) as EditorNode & + Record; + expect('dataKey' in next).toBe(false); + }); + + it('5-4 역해석 — 노드 최상위에서 읽는다', () => { + const node = { name: 'Form', dataKey: 'orderer' } as EditorNode; + expect(reverseResolve(node, dataKeyCtrl)).toEqual({ value: 'orderer', matched: true }); + }); + + it('5-5 역해석 — 값이 없으면 미매칭', () => { + expect(reverseResolve({ name: 'Form' }, dataKeyCtrl)).toEqual({ + value: undefined, + matched: false, + }); + }); + + it('5-6 예약키 가드 — nodeKey:"children" 은 no-op (노드 파괴 차단)', () => { + const evil: EditorControlSpec = { + widget: 'text', + apply: { type: 'nodeKey', nodeKey: 'children' } as unknown as string, + }; + const node: EditorNode = { name: 'Div', children: [{ name: 'Span' }] }; + const out = applyRecipe(node, evil, 'boom'); + expect(out).toBe(node); // 참조 동일 — 사본조차 만들지 않는다 + expect(node.children).toEqual([{ name: 'Span' }]); + expect(reverseResolve(node, evil)).toEqual({ value: undefined, matched: false }); + }); + + it('5-7 디바이스 scope 도 최상위에 쓴다 (responsive 브랜치는 런타임이 안 읽는다)', () => { + const next = applyRecipe({ name: 'Form' }, dataKeyCtrl, 'orderer', { + colorScheme: 'light', + breakpoint: 'mobile', + }) as EditorNode & Record; + expect(next.dataKey).toBe('orderer'); + expect(next.responsive).toBeUndefined(); + }); + + it('5-8 디바이스 scope 역해석도 최상위에서 — placeholder 흐림을 만들지 않는다', () => { + const node = { name: 'Form', dataKey: 'orderer' } as EditorNode; + expect( + reverseResolve(node, dataKeyCtrl, { colorScheme: 'light', breakpoint: 'mobile' }), + ).toEqual({ value: 'orderer', matched: true }); + }); + + it('5-9 다크 scope 는 no-op (인라인 무손실 보존)', () => { + const node = { name: 'Form', dataKey: 'orderer' } as EditorNode; + expect(applyRecipe(node, dataKeyCtrl, 'other', { colorScheme: 'dark', breakpoint: 'base' })).toBe( + node, + ); + }); + + it('5-10 입력 노드는 변경되지 않는다 (불변)', () => { + const node = { name: 'Form' } as EditorNode; + const snapshot = JSON.stringify(node); + applyRecipe(node, dataKeyCtrl, 'orderer'); + expect(JSON.stringify(node)).toBe(snapshot); + }); + + it('5-11 비-문자열/빈 nodeKey 는 no-op', () => { + const bad: EditorControlSpec = { + widget: 'text', + apply: { type: 'nodeKey', nodeKey: 42 } as unknown as string, + }; + const node: EditorNode = { name: 'Form' }; + expect(applyRecipe(node, bad, 'x')).toBe(node); + const empty: EditorControlSpec = { + widget: 'text', + apply: { type: 'nodeKey', nodeKey: '' } as unknown as string, + }; + expect(applyRecipe(node, empty, 'x')).toBe(node); + }); +}); diff --git a/resources/js/core/template-engine/layout-editor/__tests__/spec/widgetRegistry.test.ts b/resources/js/core/template-engine/layout-editor/__tests__/spec/widgetRegistry.test.ts index 75fbcd83..5a8de6b4 100644 --- a/resources/js/core/template-engine/layout-editor/__tests__/spec/widgetRegistry.test.ts +++ b/resources/js/core/template-engine/layout-editor/__tests__/spec/widgetRegistry.test.ts @@ -60,6 +60,14 @@ describe('registerCoreWidgets — Phase 4 위젯 7종', () => { expect(isCoreWidgetsRegistered()).toBe(true); }); + it('4-5 number 위젯을 등록한다 (미등록 시 「지원하지 않는 컨트롤」 폴백)', () => { + // `widget:"number"` 컨트롤(예 Header 「탭 표시 게시판 수」)이 속성 모달에서 편집 + // 불가였던 원인. 레지스트리 등록만이 그 폴백을 없앤다. + registerCoreWidgets(); + expect(getWidget('number')).toBeTruthy(); + expect(getRegisteredWidgetNames()).toContain('number'); + }); + it('component-target-picker 위젯을 등록한다', () => { // 캔버스 컴포넌트 영역 picker. [로딩 화면] target/fallback·navigate transition_overlay_target· // 향후 요소 ID param 공용. editor-spec param widget 타입으로 어느 폼에서나 선언 가능. diff --git a/resources/js/core/template-engine/layout-editor/__tests__/utils/injectedPropsCrossSave.test.ts b/resources/js/core/template-engine/layout-editor/__tests__/utils/injectedPropsCrossSave.test.ts index 3fe4f99e..4e6cafbf 100644 --- a/resources/js/core/template-engine/layout-editor/__tests__/utils/injectedPropsCrossSave.test.ts +++ b/resources/js/core/template-engine/layout-editor/__tests__/utils/injectedPropsCrossSave.test.ts @@ -74,4 +74,16 @@ describe('saveInjectedPropsToExtension', () => { const result = await saveInjectedPropsToExtension(tpl, 99, 'h', {}, fetchMock as any); expect(result.kind).toBe('not_found'); }); + it('409 본문이 errors 아래에 버전을 담아도(ResponseHelper 형식) conflict 버전을 읽는다', async () => { + const content = { injections: [{ target_id: 'h', position: 'inject_props', props: {} }] }; + const fetchMock = vi.fn(); + fetchMock.mockResolvedValueOnce( + jsonRes(200, { data: { id: 2, content: JSON.stringify(content), lock_version: 3 } }), + ); + fetchMock.mockResolvedValueOnce( + jsonRes(409, { success: false, errors: { current_version: 7, your_version: 3 } }, false), + ); + const result = await saveInjectedPropsToExtension(tpl, 2, 'h', { a: 1 }, fetchMock as any); + expect(result).toEqual({ kind: 'conflict', currentVersion: 7, yourVersion: 3 }); + }); }); diff --git a/resources/js/core/template-engine/layout-editor/__tests__/utils/layoutAttachments.test.ts b/resources/js/core/template-engine/layout-editor/__tests__/utils/layoutAttachments.test.ts new file mode 100644 index 00000000..3f438a33 --- /dev/null +++ b/resources/js/core/template-engine/layout-editor/__tests__/utils/layoutAttachments.test.ts @@ -0,0 +1,136 @@ +/** + * layoutAttachments.test.ts — 레이아웃 첨부 API 클라이언트 단위 테스트 + * + * 서버가 돌려주는 `url` 은 설정에 따라 두 형태다 — 공개 서빙 라우트(프록시) 상대 경로, + * 또는 공개 자산 디스크(CDN)의 교차 출처 절대 URL (공개 #134). 클라이언트는 형태를 + * 해석하거나 가공하지 않고 **서버가 준 문자열을 그대로 보존**해야 한다. 여기서 상대 + * 경로를 가정해 접두사를 붙이거나 origin 을 붙이면 CDN 주소가 깨진다. + * + * @scenario public_asset_disk=public,row_disk=public,filter_url_hook=absent + * + * @effects direct_url_when_row_matches_public_disk, proxy_url_otherwise + */ + +import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; +import { + listLayoutAttachments, + uploadLayoutAttachment, + deleteLayoutAttachment, +} from '../../utils/layoutAttachments'; + +const PROXY_URL = '/api/templates/sirsoft-basic/layout-attachments/1/file'; +const CDN_URL = 'https://cdn.example.test/template-layout-attachments/sirsoft-basic/bg.png'; + +function jsonResponse(body: unknown, status = 200): Response { + return { + ok: status >= 200 && status < 300, + status, + json: async () => body, + } as unknown as Response; +} + +function attachment(url: string) { + return { + id: 1, + layout_name: 'home', + original_name: 'bg.png', + mime_type: 'image/png', + size: 178, + url, + }; +} + +describe('layoutAttachments — 첨부 API 클라이언트', () => { + beforeEach(() => { + vi.stubGlobal('fetch', vi.fn()); + // buildAuthHeaders 가 읽는 저장소 — 없어도 동작해야 하지만 명시해 둔다 + try { + localStorage.setItem('auth_token', 'test-token'); + } catch { + /* 저장소 미지원 환경 무시 */ + } + }); + + afterEach(() => { + vi.unstubAllGlobals(); + vi.restoreAllMocks(); + }); + + it('목록 응답의 프록시 URL 을 가공 없이 보존한다', async () => { + (fetch as unknown as ReturnType).mockResolvedValue( + jsonResponse({ success: true, data: [attachment(PROXY_URL)] }), + ); + + const result = await listLayoutAttachments('sirsoft-basic', 'home'); + + expect(result.ok).toBe(true); + if (result.ok) expect(result.data[0].url).toBe(PROXY_URL); + }); + + it('목록 응답의 교차 출처 CDN 절대 URL 을 가공 없이 보존한다', async () => { + (fetch as unknown as ReturnType).mockResolvedValue( + jsonResponse({ success: true, data: [attachment(CDN_URL)] }), + ); + + const result = await listLayoutAttachments('sirsoft-basic', 'home'); + + expect(result.ok).toBe(true); + if (result.ok) expect(result.data[0].url).toBe(CDN_URL); + }); + + it('업로드 응답의 CDN 절대 URL 을 가공 없이 보존한다', async () => { + (fetch as unknown as ReturnType).mockResolvedValue( + jsonResponse({ success: true, data: attachment(CDN_URL) }), + ); + + const file = new File(['x'], 'bg.png', { type: 'image/png' }); + const result = await uploadLayoutAttachment('sirsoft-basic', 'home', file); + + expect(result.ok).toBe(true); + if (result.ok) expect(result.data.url).toBe(CDN_URL); + }); + + it('업로드 응답에 url 이 없으면 실패로 처리한다', async () => { + (fetch as unknown as ReturnType).mockResolvedValue( + jsonResponse({ success: true, data: { id: 1 } }), + ); + + const file = new File(['x'], 'bg.png', { type: 'image/png' }); + const result = await uploadLayoutAttachment('sirsoft-basic', 'home', file); + + expect(result.ok).toBe(false); + }); + + it('레이아웃 이름의 slash 를 query 로 인코딩한다', async () => { + (fetch as unknown as ReturnType).mockResolvedValue( + jsonResponse({ success: true, data: [] }), + ); + + await listLayoutAttachments('sirsoft-basic', 'auth/login'); + + const calledUrl = (fetch as unknown as ReturnType).mock.calls[0][0] as string; + expect(calledUrl).toContain('layout_name=auth%2Flogin'); + }); + + it('삭제는 첨부 id 경로로 DELETE 를 보낸다', async () => { + (fetch as unknown as ReturnType).mockResolvedValue( + jsonResponse({ success: true }), + ); + + const result = await deleteLayoutAttachment(1); + + expect(result.ok).toBe(true); + const [calledUrl, init] = (fetch as unknown as ReturnType).mock.calls[0]; + expect(calledUrl).toBe('/api/admin/templates/layout-attachments/1'); + expect((init as RequestInit).method).toBe('DELETE'); + }); + + it('네트워크 실패를 상태 0 의 에러로 돌려준다', async () => { + (fetch as unknown as ReturnType).mockRejectedValue(new Error('boom')); + + const result = await listLayoutAttachments('sirsoft-basic', 'home'); + + expect(result.ok).toBe(false); + if (!result.ok) expect(result.status).toBe(0); + }); +}); diff --git a/resources/js/core/template-engine/layout-editor/components/DndCanvasLayer.tsx b/resources/js/core/template-engine/layout-editor/components/DndCanvasLayer.tsx index d54e0912..3627e44d 100644 --- a/resources/js/core/template-engine/layout-editor/components/DndCanvasLayer.tsx +++ b/resources/js/core/template-engine/layout-editor/components/DndCanvasLayer.tsx @@ -37,8 +37,12 @@ import { type OverlayBox, } from '../utils/overlayGeometry'; import { isDraggableNode } from '../dnd/nestingRules'; -import { findNodeByPath, isInsideIterationInstance, serializeEditorPath, isResponsiveSegment, type EditorNode, type ComponentPath } from '../utils/layoutTreeUtils'; -import { classifyLockKind, parseEditorPath } from '../hooks/useElementSelection'; +import { collectAncestors, findNodeByPath, isInsideIterationInstance, serializeEditorPath, type EditorNode } from '../utils/layoutTreeUtils'; +import { + classifyLockKind, + isEditableLockKind, + parseEditorPath, +} from '../hooks/useElementSelection'; import { useCanvasDnd, type UseCanvasDndParams } from '../hooks/useCanvasDnd'; import { buildDropSlots, type DropSlot } from '../dnd/dropSlots'; import { dndHandleZIndex, DND_DROP_SLOT, DND_DRAG_OVERLAY } from '../utils/overlayZIndex'; @@ -311,7 +315,7 @@ export function DndCanvasLayer(props: DndCanvasLayerProps): React.ReactElement | // 제외 조건에서 data_bound 를 빼야 한다 — 종전에는 `classifyLockKind !== 'none'` // 으로 data_bound 까지 핸들을 막아, 데이터 바인딩 composite(상품 이미지 갤러리 등)가 // 선택·이동 불가였고 클릭이 조상 핸들에 가로채였다. - const ancestors = ancestorsOf(root, indexes); + const ancestors = collectAncestors(root, indexes); // 반복(iteration) 인스턴스 **내부** 노드는 핸들 제외 — 펼침 인스턴스를 개별로 // 선택/드래그하면 안 된다. 묶음 단위 편집은 아래 가상 묶음으로. // 예외: 반복 항목 편집 모드의 편집 대상 iteration 인스턴스 내부는 개별 핸들 허용. @@ -323,11 +327,7 @@ export function DndCanvasLayer(props: DndCanvasLayerProps): React.ReactElement | } const lockKind = classifyLockKind(node, editMode, currentExtensionId, ancestors); // 편집 대상 iteration 인스턴스 내부는 그 모드의 편집 대상이므로 잠금/데이터바운드 무관 허용. - if ( - lockKind !== 'none' && - lockKind !== 'data_bound' && - !isInsideEditableIteration(path, editableRootSourcePath) - ) { + if (!isEditableLockKind(lockKind) && !isInsideEditableIteration(path, editableRootSourcePath)) { return; } const box = measureOverlay(el, frameEl); @@ -353,9 +353,9 @@ export function DndCanvasLayer(props: DndCanvasLayerProps): React.ReactElement | if (!name) continue; // 원본 노드 자체의 잠금만 검사(base/partial/extension). 원본은 iteration 정의 // 노드라 data_bound 지만 대로 묶음 선택/이동은 허용. - const ancestors = ancestorsOf(root, indexes); + const ancestors = collectAncestors(root, indexes); const lockKind = classifyLockKind(ownerNode, editMode, currentExtensionId, ancestors); - if (lockKind !== 'none' && lockKind !== 'data_bound') continue; + if (!isEditableLockKind(lockKind)) continue; const box = unionBoxes(boxes); if (!box) continue; // 인스턴스를 frame 가시 영역으로 이미 걸렀으나, union 결과도 frame 과 겹치는지 @@ -782,23 +782,3 @@ function DraggableHandle(props: { ); } -/** path 조상 노드 배열(루트→부모, 자기 제외) — classifyLockKind ancestors 입력 */ -function ancestorsOf(root: EditorNode, path: ComponentPath): EditorNode[] { - const out: EditorNode[] = []; - let current: EditorNode = root; - let childArray: EditorNode[] = Array.isArray(root.children) ? (root.children as EditorNode[]) : []; - for (let i = 0; i < path.length - 1; i++) { - const seg = path[i]!; - if (isResponsiveSegment(seg)) { - const branch = current.responsive?.[seg.responsive]; - childArray = branch && Array.isArray(branch.children) ? (branch.children as EditorNode[]) : []; - continue; - } - const next = childArray[seg] ?? null; - if (!next) break; - out.push(next); - current = next; - childArray = Array.isArray(next.children) ? (next.children as EditorNode[]) : []; - } - return out; -} diff --git a/resources/js/core/template-engine/layout-editor/components/EditorCanvasOverlay.tsx b/resources/js/core/template-engine/layout-editor/components/EditorCanvasOverlay.tsx index 7f2764a9..a16c4c2c 100644 --- a/resources/js/core/template-engine/layout-editor/components/EditorCanvasOverlay.tsx +++ b/resources/js/core/template-engine/layout-editor/components/EditorCanvasOverlay.tsx @@ -18,12 +18,13 @@ import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { useLayoutEditor } from '../LayoutEditorContext'; -import type { RouteTreeNode } from '../LayoutEditorContext'; import { useLayoutDocumentContext } from '../LayoutDocumentContext'; import { useElementSelection, parseEditorPath, isContextMenuAllowed, + isEditableLockKind, + classifyLockKind, } from '../hooks/useElementSelection'; import { useInsertionPoints, type InsertionPoint } from '../hooks/useInsertionPoints'; import { useEditorHistory } from '../hooks/useEditorHistory'; @@ -35,6 +36,7 @@ import { type OverlayBox, } from '../utils/overlayGeometry'; import { + collectAncestors, duplicateNode as duplicateNodeUtil, insertNode, removeNode, @@ -299,6 +301,16 @@ export function EditorCanvasOverlay(props: EditorCanvasOverlayProps): React.Reac if (!pathIdx) return; const root: EditorNode = { children: liveDataRef.current.components }; const node = findNodeByPath(root, pathIdx); + // 출처 잠금 가드 — 상속(base)·주입(extension) 노드는 인라인 편집해도 저장 시 + // `stripInheritedNode` 가 통째로 폐기해 편집분이 조용히 사라진다. 진입 자체를 막고 + // 「🔒 공통 레이아웃 편집」/「🔒 확장 편집」 어포던스로 유도한다. + const lockKind = classifyLockKind( + node, + state.editMode, + currentExtensionId, + collectAncestors(root, pathIdx), + ); + if (!isEditableLockKind(lockKind)) return; const cls = inlineEdit.classify(node); if (!cls.editable) { trackEditorI18n({ @@ -341,7 +353,7 @@ export function EditorCanvasOverlay(props: EditorCanvasOverlayProps): React.Reac timestamp: Date.now(), }); }, - [frameEl, inlineEdit], + [frameEl, inlineEdit, state.editMode, currentExtensionId], ); // 코어 placeholder 오버레이는 회수됨. 신규 노드 시각화는 템플릿의 @@ -999,6 +1011,8 @@ export function EditorCanvasOverlay(props: EditorCanvasOverlayProps): React.Reac // 컨텍스트 메뉴 — 복사 / 삭제 const handleDuplicate = useCallback((): void => { if (!docCtx || !selection.selectedNode || !selectedPathIndexes) return; + // 출처 잠금 가드 — 사본도 `__source` 를 물고 가 저장 시 폐기된다(무반영 복제). + if (!isEditableLockKind(selection.selectedLockKind)) return; const dup = duplicateNodeUtil(selection.selectedNode); let nextComponentsCaptured: EditorNode[] = []; docCtx.patchLayout((current) => { @@ -1011,10 +1025,13 @@ export function EditorCanvasOverlay(props: EditorCanvasOverlayProps): React.Reac return nextComponents; }); history.push({ actionKind: 'insert', label: 'duplicate', snapshot: nextComponentsCaptured }); - }, [docCtx, selection.selectedNode, selectedPathIndexes, history]); + }, [docCtx, selection.selectedNode, selection.selectedLockKind, selectedPathIndexes, history]); const handleDelete = useCallback((): void => { if (!docCtx || !selectedPathIndexes) return; + // 출처 잠금 가드 — 키보드 `Delete` 는 ⓘ 메뉴를 거치지 않아 무방비였다. 상속·주입 노드는 + // 지워도 저장에 반영되지 않으므로(마스킹이 어차피 되살린다) 조작 자체를 막는다. + if (!isEditableLockKind(selection.selectedLockKind)) return; let nextComponentsCaptured: EditorNode[] = []; docCtx.patchLayout((current) => { const root: EditorNode = { children: current }; @@ -1151,9 +1168,12 @@ export function EditorCanvasOverlay(props: EditorCanvasOverlayProps): React.Reac // 잘라내기: 복사 후 선택 노드 삭제. const handleCut = useCallback((): void => { if (!selection.selectedNode) return; + // 출처 잠금 가드 — 잘라내기는 삭제를 동반하므로 같은 기준으로 막는다. `handleDelete` + // 안의 가드에만 기대면 "복사는 됐는데 원본이 남는" 어긋난 상태가 된다. + if (!isEditableLockKind(selection.selectedLockKind)) return; writeClipboard(selection.selectedNode); handleDelete(); - }, [selection.selectedNode, handleDelete]); + }, [selection.selectedNode, selection.selectedLockKind, handleDelete]); // 붙여넣기: 버퍼 노드를 선택 노드의 **다음 형제**로 삽입(선택 없으면 루트 끝). 다른 // 레이아웃으로 이동해도 sessionStorage 라 동작. duplicateNodeUtil 로 새 id 부여(중복 id 회피). diff --git a/resources/js/core/template-engine/layout-editor/components/SaveFeedbackBanner.tsx b/resources/js/core/template-engine/layout-editor/components/SaveFeedbackBanner.tsx index b9e93735..b77939fe 100644 --- a/resources/js/core/template-engine/layout-editor/components/SaveFeedbackBanner.tsx +++ b/resources/js/core/template-engine/layout-editor/components/SaveFeedbackBanner.tsx @@ -38,7 +38,12 @@ export function SaveFeedbackBanner(props: SaveFeedbackBannerProps): React.ReactE // 는 5초 후 자동 dismiss. concurrent / validation 은 사용자가 명시적으로 닫음. useEffect(() => { if (!result) return; - if (result.kind === 'concurrent_modification' || result.kind === 'validation_failed') return; + if ( + result.kind === 'concurrent_modification' || + result.kind === 'validation_failed' || + result.kind === 'guard_extension_reassembly' + ) + return; const timer = setTimeout(onDismiss, 5000); return () => clearTimeout(timer); }, [result, onDismiss]); @@ -98,6 +103,22 @@ export function SaveFeedbackBanner(props: SaveFeedbackBannerProps): React.ReactE ); } + if (result.kind === 'guard_extension_reassembly') { + // 확장 편집 모드 저장 가드 — 자동 dismiss 없음(저장이 안 됐다는 사실을 운영자가 봐야 한다). + return ( + + {t('layout_editor.save.guard_extension_reassembly_title')} +
+ {t('layout_editor.save.guard_extension_reassembly', { count: String(result.unassigned) })} +
+
+ ); + } + if (result.kind === 'guard_no_document') { return ( diff --git a/resources/js/core/template-engine/layout-editor/components/page-settings/ActionAddPicker.tsx b/resources/js/core/template-engine/layout-editor/components/page-settings/ActionAddPicker.tsx index f7e8cd31..b7f0e45d 100644 --- a/resources/js/core/template-engine/layout-editor/components/page-settings/ActionAddPicker.tsx +++ b/resources/js/core/template-engine/layout-editor/components/page-settings/ActionAddPicker.tsx @@ -58,6 +58,8 @@ const GROUP_OF: Record = { apiCall: 'data', scrollIntoView: 'etc', login: 'etc', + loginTwoFactor: 'etc', + loginTwoFactorResend: 'etc', logout: 'etc', setLocale: 'locale', emitEvent: 'etc', diff --git a/resources/js/core/template-engine/layout-editor/components/property-controls/ControlRenderer.tsx b/resources/js/core/template-engine/layout-editor/components/property-controls/ControlRenderer.tsx index 0f45a99d..97421de1 100644 --- a/resources/js/core/template-engine/layout-editor/components/property-controls/ControlRenderer.tsx +++ b/resources/js/core/template-engine/layout-editor/components/property-controls/ControlRenderer.tsx @@ -22,6 +22,7 @@ import { applyRecipe, reverseResolve } from '../../spec/recipeEngine'; import { BASE_SCOPE, isDarkEditable, type StyleScope } from '../../spec/styleScope'; import { I18nTextField } from './I18nTextField'; import { DataChipValueInput } from '../page-settings/DataChipValueInput'; +import { useBoundValueGuard, BoundValueNotice, BoundValueRestore } from './boundValueGuard'; export interface ControlRendererProps { /** 컨트롤 키 (`textAlign` 등) — 라벨 fallback 에 사용 */ @@ -60,6 +61,19 @@ function resolveLabel(controlKey: string, control: EditorControlSpec, t: Control return controlKey; } +/** + * 자체 바인딩 처리를 가진 위젯 — `ControlRenderer` 의 공용 게이트를 타지 않는다. + * + * 새 위젯을 여기에 넣기 전에 **그 위젯이 실제로 원문 표시·해제·복구 셋을 모두 제공하는지** + * 확인한다. 그렇지 않은데 등재하면 공용 보호가 그 위젯에서만 사라진다. + */ +const SELF_GUARDED_WIDGETS: ReadonlySet = new Set([ + 'image', // ImagePickerControl — 배지 + 업로드/제거/갤러리/관리 잠금 + 되돌리기 + 'core-id', // CoreIdControl — chipEditing 디그레이드(선례) + 'i18n-text', // I18nTextField — 표현식 칩 분해가 설계된 처리 + 'text', // DataChipValueInput — 동일 +]); + export function ControlRenderer({ controlKey, control, @@ -122,6 +136,29 @@ export function ControlRenderer({ [node, control, onPatch, scope, darkReadonly], ); + /** + * 데이터 연결 값 보호 — **모든 위젯 공용 단일 게이트**. + * + * 저장값이 `{{...}}`·설정 참조이면 위젯은 그것을 해석하지 못해 빈 컨트롤로 보이고, + * 운영자가 조작하는 순간 **환경설정과의 연결**이 소리 없이 끊긴다. 원문은 화면 + * 어디에도 남지 않아 되돌릴 수단조차 없다. + * + * 이 판정을 위젯마다 넣으면 한 곳이 빠져도 오류가 나지 않고 그 한 곳이 우회로가 + * 된다(공개 #135 후속 실측에서 실제로 「이미지 관리」 진입 하나만 열려 있었다). + * 그래서 위젯이 아니라 **여기 한 곳**에서 건다 — 새 위젯을 등록해도 자동 적용된다. + */ + const boundGuard = useBoundValueGuard(resolution.value, handleChange); + + /** + * 자체 처리 위젯은 이 게이트를 타지 않는다. + * + * - `image` — 배지·잠금·복구를 위젯 안에서 더 세밀하게 제공한다(갤러리·업로드까지 잠금) + * - `core-id` — `chipEditing` 으로 같은 형태의 디그레이드를 이미 갖는다 + * - `i18n-text`/`text`(propValue) — 위 분기에서 표현식을 **칩으로 분해해 보여 주는** 것이 + * 설계된 처리다. 여기서 가리면 그 기능이 통째로 사라진다. + */ + const guardApplies = !SELF_GUARDED_WIDGETS.has(control.widget ?? ''); + return (
@@ -169,8 +206,26 @@ export function ControlRenderer({ > {t('layout_editor.property_modal.dark_code_only')} + ) : guardApplies && boundGuard.bound ? ( + // 데이터 연결 값 — 위젯은 이 문자열을 해석하지 못해 빈 컨트롤로 보이고, + // 조작하는 순간 연결이 소리 없이 끊긴다. 원문을 보여 주고 해제 경로로만 연다. + ) : Widget ? ( - + <> + + {guardApplies && boundGuard.replacing && boundGuard.original !== null && ( + + )} + ) : ( {t('layout_editor.property_modal.unsupported_widget')} diff --git a/resources/js/core/template-engine/layout-editor/components/property-controls/ImagePickerControl.tsx b/resources/js/core/template-engine/layout-editor/components/property-controls/ImagePickerControl.tsx index d8ac35f6..8c589c61 100644 --- a/resources/js/core/template-engine/layout-editor/components/property-controls/ImagePickerControl.tsx +++ b/resources/js/core/template-engine/layout-editor/components/property-controls/ImagePickerControl.tsx @@ -22,6 +22,8 @@ import React, { useCallback, useEffect, useRef, useState } from 'react'; import type { WidgetProps } from '../../spec/widgetRegistry'; +import type { EditorControlSpec } from '../../spec/specTypes'; +import { useBoundValueGuard, BoundValueNotice, BoundValueRestore } from './boundValueGuard'; import { useLayoutEditor } from '../../LayoutEditorContext'; import { listLayoutAttachments, @@ -55,8 +57,54 @@ function modeOf(v: ImageValue): string { return m?.value ?? 'fill'; } -export function ImagePickerControl({ value, onChange, t }: WidgetProps): React.ReactElement { +/** + * 이 컨트롤의 `apply` 가 **값 슬롯을 하나만** 가지는가. + * + * 그렇다면 `size`/`repeat`/`position` 을 저장할 곳이 없으므로 표시모드(채움/맞춤/타일) + * 버튼은 죽은 컨트롤이다 — 눌러도 저장되지 않는데 눌리는 표면이 남으면 "고칠 수 있는 + * 것처럼 보이지만 저장되지 않는" 상태가 된다. + * + * 판정 대상은 **control-level apply + 모든 option-level apply 의 합집합**이다. + * `control.apply` 만 보면 옵션-only 컨트롤에서 `undefined` → 보류 → 표시로 조용히 + * 잘못된 결론에 도달한다. + * + * **화이트리스트가 아니라 블랙리스트다** — `apply` 미선언(합집합이 공집합)이면 판정을 + * 보류하고 `false`(현행대로 표시)를 돌려준다. 화이트리스트("bundle 일 때만 표시")로 + * 짜면 apply 없이 렌더되는 기존 계약이 깨진다. + */ +function hasSingleValueSlot(control: EditorControlSpec | undefined): boolean { + if (!control) return false; + const applies: Array<{ type?: unknown; props?: unknown }> = []; + const own = control.apply as unknown; + if (own && typeof own === 'object') applies.push(own as { type?: unknown; props?: unknown }); + for (const opt of (control.options ?? []) as Array<{ apply?: unknown }>) { + if (opt && typeof opt === 'object' && opt.apply && typeof opt.apply === 'object') { + applies.push(opt.apply as { type?: unknown; props?: unknown }); + } + } + if (applies.length === 0) return false; // 판정 보류 — 현행 동작 보존 + return applies.every( + (a) => + a.type === 'propValue' || + a.type === 'cssVar' || + (a.type === 'styleProp' && !Array.isArray(a.props)), + ); +} + +export function ImagePickerControl({ control, value, onChange, t }: WidgetProps): React.ReactElement { const current = readValue(value); + /** 단일 값 슬롯 — size/repeat/position 을 저장할 자리가 없다 */ + const singleSlot = hasSingleValueSlot(control); + /** + * 데이터 연결 값 보호 — 판정·배지·해제·복구를 공용 프리미티브에 위임한다. + * 위젯마다 복붙하면 한 곳이 빠져도 오류가 나지 않고 그 한 곳이 우회로가 된다. + */ + const guard = useBoundValueGuard( + value, + (original) => onChange({ url: original }), + (v) => readValue(v).url, + ); + const bound = guard.bound; const { state } = useLayoutEditor(); const modal = useEditorModal(); const [uploading, setUploading] = useState(false); @@ -91,6 +139,12 @@ export function ImagePickerControl({ value, onChange, t }: WidgetProps): React.R onChange(undefined); return; } + // 단일 값 슬롯 — size/repeat/position 은 저장될 자리가 없다. 만들지 않으면 + // 엔진 축약(scalarizeImageValue)이 버릴 것도 없어 라운드트립이 애초에 대칭이 된다. + if (singleSlot) { + onChange({ url }); + return; + } const mode = DISPLAY_MODES.find((d) => d.value === modeOf(current)) ?? DISPLAY_MODES[0]; onChange({ url, @@ -155,21 +209,34 @@ export function ImagePickerControl({ value, onChange, t }: WidgetProps): React.R return (
- {current.url && ( -
+ ) : ( + current.url && ( +
)의 클래스가 정하므로 편집기가 흉내낼 수 없다. + backgroundSize: singleSlot ? 'contain' : current.size ?? 'cover', + backgroundRepeat: singleSlot ? 'no-repeat' : current.repeat ?? 'no-repeat', + backgroundPosition: singleSlot ? 'center' : current.position ?? 'center', + }} + /> + ) )}
-
@@ -191,33 +264,50 @@ export function ImagePickerControl({ value, onChange, t }: WidgetProps): React.R data-testid="g7le-image-url" placeholder="https://… / URL" value={urlDraft} + // 데이터 연결 값은 원문을 보이되 편집을 막는다 — 지워지지 않고 남는다. + readOnly={bound} onChange={(e) => setUrlDraft(e.target.value)} - onBlur={(e) => setUrl(e.target.value.trim() || undefined)} + onBlur={(e) => { + if (bound) return; + setUrl(e.target.value.trim() || undefined); + }} style={urlInput} /> -
- {DISPLAY_MODES.map((m) => { - const active = modeOf(current) === m.value; - return ( - - ); - })} -
+ {/* 보호를 해제한 동안의 편도 방지 — 아직 안 바꿨으면 취소로, 이미 값을 넣었으면 + 원래 연결값 복구로 동작한다. 이 경로가 없으면 실수로 한 번 누른 운영자가 + 원문을 되찾을 방법이 없다. */} + {guard.replacing && guard.original !== null && ( + + )} + + {/* 단일 값 슬롯이면 표시모드는 저장될 자리가 없다 — 컨테이너째 미렌더. + `disabled` 로 두면 "URL 을 넣으면 살아나겠지"라는 거짓 정보를 준다 + (기존 `disabled={!current.url}` 은 일시적 비활성이라 의미가 다르다). */} + {!singleSlot && ( +
+ {DISPLAY_MODES.map((m) => { + const active = modeOf(current) === m.value; + return ( + + ); + })} +
+ )} {/* 인라인 미니 갤러리 — 현재 레이아웃 첨부 썸네일 가로 스트립 + 관리 링크 */}
@@ -225,11 +315,15 @@ export function ImagePickerControl({ value, onChange, t }: WidgetProps): React.R {t('layout_editor.attachment_manager.recent')} + {/* 관리 모달은 각 카드에 「배경」 버튼을 띄우고 그 클릭이 같은 setUrl 을 부른다 — + 인라인 썸네일 「사용」과 동일 동작의 다른 렌더 위치다. 하나만 잠그면 + 나머지 하나가 조용한 우회로가 되므로 진입 자체를 함께 잠근다. */} @@ -241,8 +335,13 @@ export function ImagePickerControl({ value, onChange, t }: WidgetProps): React.R +
+
+ ); +} + /** * Spacing — 여백(안쪽/바깥쪽)의 **측별 독립 크기** 편집 (§항목B 재설계). * diff --git a/resources/js/core/template-engine/layout-editor/components/property-controls/boundValueGuard.tsx b/resources/js/core/template-engine/layout-editor/components/property-controls/boundValueGuard.tsx new file mode 100644 index 00000000..63b406d2 --- /dev/null +++ b/resources/js/core/template-engine/layout-editor/components/property-controls/boundValueGuard.tsx @@ -0,0 +1,193 @@ +/** + * boundValueGuard.tsx — 데이터 연결 값(바인딩 표현식) 보호 공용 프리미티브 + * + * 레이아웃의 prop 자리에는 `{{_global.settings?.general?.site_logo_url}}` 같은 **표현식 + * 문자열**이 저장돼 있을 수 있다. 그 값을 편집기 위젯이 그대로 읽으면 위젯은 그것을 + * 해석하지 못해 **빈 컨트롤**로 보이고, 운영자가 무심코 조작하는 순간 그 연결이 + * 소리 없이 사라진다 — 값 하나가 아니라 **환경설정과의 연결**이 끊기고, 원문이 화면 + * 어디에도 남지 않아 되돌릴 수단조차 없다. + * + * 그래서 바인딩 값은 ① 원문을 배지로 보여 주고 ② 파괴적 조작을 잠그고 + * ③ 「직접 지정으로 바꾸기」로만 명시적으로 열고 ④ 「되돌리기」로 원래 연결을 복구한다. + * + * **이 판정과 UI 를 위젯마다 복붙하지 않는다.** 복붙하면 한 곳이 빠져도 오류가 나지 + * 않고, 그 한 곳이 조용한 우회로가 된다(공개 #135 후속 실측에서 실제로 「이미지 관리」 + * 진입 하나만 열려 있었다). 새 위젯을 만들 때도 본 훅만 쓴다. + * + * 선례: `CoreIdControl`(chipEditing) · `I18nTextField`(settingsEditing) 이 같은 형태의 + * 디그레이드를 이미 갖고 있다. 본 모듈은 그 패턴을 위젯 공용으로 승격한 것이다. + * + * @since engine-v1.66.0 + */ + +import React, { useCallback, useState } from 'react'; +import { hasInlineBinding, hasSettingsRef } from '../../spec/inlineBindingUtils'; + +/** 다국어 해석 함수 — `WidgetProps.t` 와 동일 시그니처 */ +export type TranslateFn = (key: string, params?: Record) => string; + +/** + * 값이 데이터 연결(바인딩/설정 참조) 문자열인지 판정한다. + * + * 인라인 바인딩(`{{...}}`)과 설정 참조를 모두 본다 — 둘 다 위젯이 해석하지 못하고 + * 덮어쓰면 소실되는 형태다. + * + * @param value 판정할 값 (문자열이 아니면 false) + * @return 데이터 연결 문자열이면 true + */ +export function isBoundValue(value: unknown): value is string { + return typeof value === 'string' && (hasInlineBinding(value) || hasSettingsRef(value)); +} + +/** `useBoundValueGuard` 반환 계약 */ +export interface BoundValueGuard { + /** 지금 보호 상태인가 — true 면 파괴적 조작을 잠그고 배지를 렌더한다 */ + readonly bound: boolean; + /** 보호를 해제하고 편집 중인가 (「직접 지정으로 바꾸기」 이후) */ + readonly replacing: boolean; + /** 보호 해제 시점에 붙잡아 둔 원문 — 「되돌리기」의 복구 대상 */ + readonly original: string | null; + /** 「직접 지정으로 바꾸기」 — 원문을 붙잡고 편집을 연다 */ + readonly beginReplace: () => void; + /** 「되돌리기」 — 원문을 복구하고 보호 상태로 되돌린다 */ + readonly restore: () => void; +} + +/** + * 바인딩 값 보호 상태를 관리한다. + * + * 위젯의 값이 객체인 경우(예 `image` 는 `{url,…}`)를 위해 문자열 추출자를 받는다. + * + * @param rawValue 현재 값 (위젯이 받은 원본) + * @param onRestore 원문 복구를 위해 호출할 쓰기 함수. 값이 이미 원문과 같으면 호출하지 + * 않는다 — 바꾼 것이 없는데 history push 를 만들지 않기 위함이다. + * @param extract 값에서 판정 대상 문자열을 뽑는다 (기본: 값 자신) + * @return 보호 상태와 조작 + */ +export function useBoundValueGuard( + rawValue: unknown, + onRestore: (original: string) => void, + extract: (value: unknown) => unknown = (v) => v, +): BoundValueGuard { + const [replacing, setReplacing] = useState(false); + const [original, setOriginal] = useState(null); + + const current = extract(rawValue); + const bound = isBoundValue(current) && !replacing; + + const beginReplace = useCallback(() => { + // 해제 시점의 원문을 붙잡는다 — 이후 값이 바뀌어도 복구 대상은 이것이다. + setOriginal(isBoundValue(current) ? current : null); + setReplacing(true); + }, [current]); + + const restore = useCallback(() => { + if (original !== null && current !== original) onRestore(original); + setReplacing(false); + setOriginal(null); + }, [original, current, onRestore]); + + return { bound, replacing, original, beginReplace, restore }; +} + +const noticeWrap: React.CSSProperties = { + display: 'flex', + flexDirection: 'column', + gap: 4, + padding: '8px 10px', + border: '1px dashed #cbd5e1', + borderRadius: 6, + background: '#f8fafc', +}; + +const noticeCode: React.CSSProperties = { + fontFamily: 'ui-monospace, SFMono-Regular, Menlo, monospace', + fontSize: 11, + color: '#0f172a', + wordBreak: 'break-all', +}; + +const noticeLink: React.CSSProperties = { + alignSelf: 'flex-start', + background: 'none', + border: 'none', + padding: 0, + fontSize: 11, + color: '#2563eb', + cursor: 'pointer', + textDecoration: 'underline', +}; + +/** + * 보호 상태에서 원문과 해제 경로를 보여 주는 공용 배지. + * + * 미리보기·입력칸을 대신해 렌더한다 — 위젯이 해석하지 못하는 값을 흉내 내면 거짓 + * 미리보기가 되기 때문이다. + * + * @param expression 표시할 원문 + * @param t 다국어 해석 함수 + * @param onReplace 「직접 지정으로 바꾸기」 클릭 핸들러 + * @param testIdPrefix testid 접두사 (위젯별 구분 — 예 `g7le-image`) + */ +export function BoundValueNotice({ + expression, + t, + onReplace, + testIdPrefix, +}: { + expression: string; + t: TranslateFn; + onReplace: () => void; + testIdPrefix: string; +}): React.ReactElement { + return ( +
+ + {expression} + + + {t('layout_editor.control.bound_value.notice')} + + +
+ ); +} + +/** + * 보호를 해제한 동안 노출하는 「되돌리기」 어포던스. + * + * 해제 직후(아직 안 바꿈)에는 취소로, URL 등을 이미 넣은 뒤에는 원래 연결값 복구로 + * 동작한다 — 둘 다 같은 버튼이다. 이 경로가 없으면 「직접 지정으로 바꾸기」가 편도가 + * 되어, 실수로 한 번 누른 운영자가 원문을 되찾을 방법이 없다. + * + * @param t 다국어 해석 함수 + * @param onRestore 「되돌리기」 클릭 핸들러 + * @param testIdPrefix testid 접두사 + */ +export function BoundValueRestore({ + t, + onRestore, + testIdPrefix, +}: { + t: TranslateFn; + onRestore: () => void; + testIdPrefix: string; +}): React.ReactElement { + return ( + + ); +} diff --git a/resources/js/core/template-engine/layout-editor/hooks/useCanvasDnd.ts b/resources/js/core/template-engine/layout-editor/hooks/useCanvasDnd.ts index e73ef104..8bba30cb 100644 --- a/resources/js/core/template-engine/layout-editor/hooks/useCanvasDnd.ts +++ b/resources/js/core/template-engine/layout-editor/hooks/useCanvasDnd.ts @@ -37,6 +37,7 @@ export interface DropZone { index: number; } import { + collectAncestors, findNodeByPath, isInsideIterationInstance, moveNode, @@ -47,7 +48,12 @@ import { type EditorNode, type ComponentPath, } from '../utils/layoutTreeUtils'; -import { classifyLockKind, parseEditorPath } from './useElementSelection'; +import { + classifyLockKind, + isEditableLockKind, + parseEditorPath, + resolveDndDenial, +} from './useElementSelection'; import { trackEditorDnd, type EditorDndDecision } from '../devtools/editorTrackers'; import type { NestingSpec } from '../spec/specTypes'; @@ -301,13 +307,15 @@ export function useCanvasDnd(params: UseCanvasDndParams): UseCanvasDndReturn { // 단, 편집 대상 iteration/모달(원본 노드 및 그 자손)은 그 모드의 편집 대상이므로 // data_bound 잠금을 무시하고 컨테이너로 허용한다. if (!isInsideEditableRootContainer) { - const ancestors = ancestorsOf(rootOf(live.components), path); + const ancestors = collectAncestors(rootOf(live.components), path); const lockKind = classifyLockKind( containerNode, live.editMode, live.currentExtensionId, ancestors ); + // 여기는 의도적으로 `data_bound` 도 거부한다 — 데이터가 결정하는 컨테이너 안에 + // 정적 노드를 넣으면 반복/바인딩 결과와 어긋난다(`isEditableLockKind` 보다 엄격). if (lockKind !== 'none') return false; } @@ -497,7 +505,7 @@ export function useCanvasDnd(params: UseCanvasDndParams): UseCanvasDndReturn { if (!name || !isDraggableNode(name, live.nesting)) { decision = 'denied_no_draggable'; } else if (node) { - const ancestors = ancestorsOf(rootOf(live.components), indexes); + const ancestors = collectAncestors(rootOf(live.components), indexes); const lockKind = classifyLockKind(node, live.editMode, live.currentExtensionId, ancestors); // 편집 루트 confine(modal/iteration_item) — 편집 대상 루트 서브트리 **밖** 노드의 드래그 // 시작을 거부한다. 선택 자체가 편집 루트로 제한되나(useElementSelection), @@ -520,9 +528,18 @@ export function useCanvasDnd(params: UseCanvasDndParams): UseCanvasDndReturn { } // 자신 바인딩 data_bound(상품 이미지 갤러리 등)는 명세상 선택·드래그·구조 // 편집 허용 — 위치 이동은 정당한 구조 편집이므로 막지 않는다. - else if (lockKind === 'base' || lockKind === 'partial') decision = 'denied_base_locked'; - else if (lockKind === 'extension' || lockKind === 'extension_point') - decision = 'denied_extension_locked'; + // 그 외 잠금 종류의 거부 사유는 `resolveDndDenial` 단일 지점이 정한다(조건 복사 금지). + else { + const denial = resolveDndDenial(lockKind); + if (denial) decision = denial; + } + } + + // 거부된 드래그는 `activeDragPath` 도 남기지 않는다 — 남으면 DragOverlay 가 잡힌 + // 노드를 따라다녀 "옮길 수 있다" 는 거짓 어포던스를 준다(종전 결함). + // `activeDragName` 은 지우지 않는다 — onDragEnd 가 그것으로 취소 사유를 적재한다. + if (decision !== 'allowed') { + setActiveDragPath(null); } trackEditorDnd({ @@ -558,7 +575,7 @@ export function useCanvasDnd(params: UseCanvasDndParams): UseCanvasDndReturn { setActiveDropZone(null); return; } - const ancestors = ancestorsOf(rootOf(live.components), indexes); + const ancestors = collectAncestors(rootOf(live.components), indexes); // 편집 루트 confine(modal/iteration_item) — 편집 대상 루트 밖 노드는 드롭존 미계산. if ( live.editableRootSourcePath && @@ -574,7 +591,7 @@ export function useCanvasDnd(params: UseCanvasDndParams): UseCanvasDndReturn { const insideEditableIter = isInsideEditableIteration(draggedPath, live.editableRootSourcePath); if ( (isInsideIterationInstance(ancestors) && !insideEditableIter) || - (moveLockKind !== 'none' && moveLockKind !== 'data_bound' && !insideEditableIter) + (!isEditableLockKind(moveLockKind) && !insideEditableIter) ) { setActiveDropZone(null); return; @@ -670,6 +687,35 @@ export function useCanvasDnd(params: UseCanvasDndParams): UseCanvasDndReturn { return; } + // 드래그 노드 출처 잠금 — commit 직전 최종 가드. + // + // 종전에는 `zone === null` 에 기댄 간접 방어뿐이라, stale 슬롯이나 유효 zone 이 들어오면 + // 상속·주입 노드가 그대로 이동 commit 됐다(그리고 저장 시 통째로 폐기됐다). + const draggedNodeAtEnd = findNodeByPath(rootOf(live.components), fromPath); + const endDenial = draggedNodeAtEnd + ? resolveDndDenial( + classifyLockKind( + draggedNodeAtEnd, + live.editMode, + live.currentExtensionId, + collectAncestors(rootOf(live.components), fromPath), + ), + ) + : null; + if (endDenial) { + trackEditorDnd({ + source: 'drag', + draggedComponentName: draggedName, + targetContainerName, + targetContainerPath: zone.containerPath || null, + decision: endDenial, + result: 'denied', + timestamp: Date.now(), + }); + finishDrag(); + return; + } + // no-op 가드 — 같은 위치면 변형/이력 생략. // // zone.index 는 **원본 트리 인덱스**. @@ -758,28 +804,6 @@ export function useCanvasDnd(params: UseCanvasDndParams): UseCanvasDndReturn { }; } -/** path 의 조상 노드 배열(루트→부모, 자기 자신 제외) — classifyLockKind 의 ancestors 입력 */ -function ancestorsOf(root: EditorNode, path: ComponentPath): EditorNode[] { - const out: EditorNode[] = []; - let current: EditorNode = root; - let childArray: EditorNode[] = Array.isArray(root.children) ? (root.children as EditorNode[]) : []; - // 마지막 세그먼트(자기 자신)는 제외. responsive 세그먼트는 노드를 내리지 않고 - // childArray 만 분기로 전환 — 조상 목록에 추가하지 않는다. - for (let i = 0; i < path.length - 1; i++) { - const seg = path[i]!; - if (isResponsiveSegment(seg)) { - const branch = current.responsive?.[seg.responsive]; - childArray = branch && Array.isArray(branch.children) ? (branch.children as EditorNode[]) : []; - continue; - } - const next = childArray[seg] ?? null; - if (!next) break; - out.push(next); - current = next; - childArray = Array.isArray(next.children) ? (next.children as EditorNode[]) : []; - } - return out; -} function pathsEqual(a: ComponentPath, b: ComponentPath): boolean { if (a.length !== b.length) return false; diff --git a/resources/js/core/template-engine/layout-editor/hooks/useElementSelection.ts b/resources/js/core/template-engine/layout-editor/hooks/useElementSelection.ts index 9cbab340..0ff8d95d 100644 --- a/resources/js/core/template-engine/layout-editor/hooks/useElementSelection.ts +++ b/resources/js/core/template-engine/layout-editor/hooks/useElementSelection.ts @@ -398,34 +398,63 @@ export function classifyLockKind( return 'extension_point'; } - // 별도 편집 모드(extension/base/modal/iteration)에서만 **출처 잠금을 data_bound 보다 우선** - // 적용한다. 그 모드들에서는 호스트 본체 노드가 폼 입력 - // 등으로 data_bound 인 경우가 많은데, data_bound 를 먼저 판정하면 잠겨야 할 호스트 노드가 - // "텍스트만 잠금(선택/스타일 허용)"으로 새어 편집된다. 그래서 잠긴 노드는 출처로 분류한다. + // **출처 잠금이 data_bound 보다 항상 우선**한다 (편집 모드 무관). // - // route 모드는 **종전 동작을 그대로 보존**한다 — data_bound 를 먼저 판정해, - // 확장 주입 영역의 data_bound 노드가 "데이터 영역" 표식을 유지하고, 잠긴 확장/ base 노드는 - // 아래 출처 분류로 "확장 편집"/"공통 레이아웃 편집" 어포던스를 띄운다. route 동작을 한 줄도 - // 바꾸지 않아 기존 확장 주입 영역 표식/진입이 그대로 복원된다. - if (editMode !== 'route' && isNodeLocked(node, editMode, currentExtensionId)) { + // 종전에는 route 모드만 예외로 두어 data_bound 를 먼저 판정했다. 그 결과 상속(base)· + // 주입(extension) 노드 중 props 값 하나라도 `{{ }}` 인 것은 `data_bound` 로 분류됐고, + // `data_bound` 는 편집 허용이라 속성 모달·드래그·인라인 편집이 모두 열렸다. 그런데 저장 + // 시 `stripInheritedNode` 가 그 노드를 통째로 폐기하므로 **편집분이 오류도 경고도 없이 + // 사라진다**(저장은 200 으로 성공하고 history 는 clear 돼 undo 도 불가). 그 "종전 동작 + // 보존" 은 계약이 아니라 보수성 선언이었고, 그 보수성이 곧 결함이었다. + // + // `data_bound` 의 **의미는 그대로 두고**(= 편집 가능, 텍스트만 잠금) 어느 노드가 + // data_bound 로 분류되는가만 좁힌다. `isNodeLocked('route')` 는 `__source.kind` 하나만 + // 보고 조상은 보지 않으므로, 라우트 소유 노드는 계속 편집 가능하다(회귀 없음). + if (isNodeLocked(node, editMode, currentExtensionId)) { const src = node.__source?.kind; if (src === 'base') return 'base'; if (src === 'partial') return 'partial'; if (src === 'extension') return 'extension'; - // 출처 메타가 없는데 잠금(별도 모드 한정) — 보수적으로 base 취급(선택 차단). + // 출처 메타가 없는데 잠금 — 보수적으로 base 취급(선택 차단). return 'base'; } - // ── 종전 분류 순서 (route 모드 + 별도 모드의 미잠금 노드) ── + // ── 미잠금(라우트 소유·신규 삽입) 노드 ── if (isDataBoundNode(node, ancestors)) return 'data_bound'; - if (!isNodeLocked(node, editMode, currentExtensionId)) return 'none'; - const src = node.__source?.kind; - if (src === 'base') return 'base'; - if (src === 'partial') return 'partial'; - if (src === 'extension') return 'extension'; return 'none'; } +/** + * 이 노드가 현재 편집 중인 레이아웃의 **소유 노드**인가 — 즉 저장 마스킹 + * (`stripInheritedFromLayoutContent`)을 통과해 영속되는가. + * + * `false` 인 노드(base/partial/extension/extension_point)는 편집해도 저장 시 폐기되므로 + * 속성·구조·드래그·인라인 편집을 **모두 같은 기준으로** 차단하고 진입 어포던스 + * (「🔒 공통 레이아웃 편집」 / 「🔒 확장 편집」)로 유도한다. + * + * 새 편집 진입점을 만들 때 반드시 본 함수를 게이트로 쓸 것 — 조건을 복사하면 + * 한 곳만 빠져도 같은 소실 결함이 재발한다(이번 결함의 원인이 정확히 그것이다). + */ +export function isEditableLockKind(lockKind: SelectionLockKind): boolean { + return lockKind === 'none' || lockKind === 'data_bound'; +} + +/** + * 잠금 종류 → DnD 거부 사유(devtools `EditorDndDecision`). `null` = 허용. + * + * `isEditableLockKind` 와 같은 판정을 쓰되 거부 사유 문자열까지 단일 지점에서 정한다. + * 사유 어휘는 기존 `EditorDndDecision` 그대로다(신설 없음) — base/partial 은 + * `denied_base_locked`, extension/extension_point 는 `denied_extension_locked`. + */ +export function resolveDndDenial( + lockKind: SelectionLockKind, +): 'denied_base_locked' | 'denied_extension_locked' | null { + if (isEditableLockKind(lockKind)) return null; + return lockKind === 'extension' || lockKind === 'extension_point' + ? 'denied_extension_locked' + : 'denied_base_locked'; +} + /** * 노드의 확장 출처 PK 를 해석한다 (devtools editor-selection __sourceExtensionId). * @@ -536,7 +565,8 @@ function normalizeDomPathToExtensionEntry( * @returns ⓘ 메뉴를 띄워도 되면 true */ export function isContextMenuAllowed(lockKind: SelectionLockKind): boolean { - return lockKind === 'none' || lockKind === 'data_bound'; + // 판정은 `isEditableLockKind` 단일 지점 — 조건을 복사하면 한 곳만 빠져도 결함이 남는다. + return isEditableLockKind(lockKind); } /** @@ -580,8 +610,8 @@ export function useElementSelection(params: UseElementSelectionParams): UseEleme if (!node) return true; const ancestors = resolveAncestors(rootNode, path, pathParser); const kind = classifyLockKind(node, editMode, currentExtensionId, ancestors); - // none = 편집 가능, data_bound = 편집 중 대상의 데이터 영역(선택 허용). 그 외는 잠금 → 차단. - return kind === 'none' || kind === 'data_bound'; + // 판정은 `isEditableLockKind` 단일 지점 — 조건을 복사하면 한 곳만 빠져도 결함이 남는다. + return isEditableLockKind(kind); }, [rootNode, pathParser, editMode, currentExtensionId, editableRootPath] ); diff --git a/resources/js/core/template-engine/layout-editor/hooks/useExtensionDocument.ts b/resources/js/core/template-engine/layout-editor/hooks/useExtensionDocument.ts index b1630600..202afb82 100644 --- a/resources/js/core/template-engine/layout-editor/hooks/useExtensionDocument.ts +++ b/resources/js/core/template-engine/layout-editor/hooks/useExtensionDocument.ts @@ -45,6 +45,7 @@ import type { EditorNode, NodeSource } from '../utils/layoutTreeUtils'; import { trackEditorDocument } from '../devtools/editorTrackers'; import { readSanctumToken } from '../utils/authToken'; import { getCacheBustNonce, bumpCacheBustNonce } from '../utils/editorCacheBust'; +import { readConflictVersion } from '../utils/conflictVersion'; import type { SaveResult } from './useLayoutDocument'; import { suffixed } from '../../../support/assetUrl'; @@ -114,25 +115,92 @@ export function reassembleContent( return { ...content, components: cleaned }; } if (Array.isArray(content.injections)) { - const injections = (content.injections as any[]).map((inj) => ({ ...inj })); - // components 를 가진 injection 의 components 를 비운 뒤 편집 결과로 재분배. - injections.forEach((inj) => { - if (Array.isArray(inj.components)) inj.components = []; - }); - for (const node of roots) { - const idx = (node as any).__injectionIndex; - const target = - typeof idx === 'number' && idx >= 0 && idx < injections.length ? injections[idx] : null; - if (target) { - if (!Array.isArray(target.components)) target.components = []; - target.components.push(stripExtensionEditorMeta(node)); - } - } - return { ...content, injections }; + return reassembleOverlayContent(content, roots).content; } return content; } +/** overlay 재조립 결과 — 되돌리지 못한 노드 수를 함께 돌려준다(저장 가드 근거). */ +export interface OverlayReassembly { + /** 저장용 content */ + content: Record; + /** 어느 injection 에서 왔는지 알 수 없어 버려질 뻔한 루트 노드 수 — 0 이 아니면 PUT 하지 않는다 */ + unassigned: number; +} + +/** 트리의 모든 노드 id 를 모은다(원본 injection 의 id 집합 — 순번 메타 없는 노드의 폴백 매칭용). */ +function collectNodeIds(nodes: unknown, out: Set): void { + if (!Array.isArray(nodes)) return; + for (const n of nodes) { + if (!n || typeof n !== 'object') continue; + const id = (n as { id?: unknown }).id; + if (typeof id === 'string' && id !== '') out.add(id); + collectNodeIds((n as { children?: unknown }).children, out); + } +} + +/** + * overlay content 의 injections 를 편집 결과 루트로 되돌린다. + * + * 노드가 어느 injection 에서 왔는지는 세 단계로 판정한다 — ① 조각 단독 모드가 붙인 + * `__injectionIndex` ② 호스트 병합 모드에서 백엔드가 `__source.injectionIndex` 로 실어 준 순번 + * ③ 둘 다 없으면 원본 injection 의 노드 id 집합과 대조(구 백엔드 응답 호환). 셋 다 실패한 + * 노드는 **버리지 않고 센다** — 종전엔 조용히 버려져 호스트 병합 모드의 무변경 저장만으로 + * `injections[].components` 가 통째로 비워졌다(이커머스 → `_user_base` 헤더 통화 선택기 소실). + * + * @param content 원본 파싱 content(비편집 키 보존용) + * @param roots 편집된 루트 노드 배열 + * @returns 저장용 content + 되돌리지 못한 노드 수 + */ +export function reassembleOverlayContent( + content: Record, + roots: EditorNode[], +): OverlayReassembly { + const original = Array.isArray(content.injections) ? (content.injections as any[]) : []; + const injections = original.map((inj) => ({ ...inj })); + const originalIds = original.map((inj) => { + const ids = new Set(); + if (inj && Array.isArray(inj.components)) collectNodeIds(inj.components, ids); + return ids; + }); + // components 를 가진 injection 의 components 를 비운 뒤 편집 결과로 재분배. + injections.forEach((inj) => { + if (Array.isArray(inj.components)) inj.components = []; + }); + let unassigned = 0; + for (const node of roots) { + const explicit = (node as any).__injectionIndex; + const fromSource = (node as any).__source?.injectionIndex; + let idx: number | undefined = + typeof explicit === 'number' ? explicit : typeof fromSource === 'number' ? fromSource : undefined; + if (idx === undefined) { + const id = (node as { id?: unknown }).id; + if (typeof id === 'string') { + const found = originalIds.findIndex((ids) => ids.has(id)); + if (found >= 0) idx = found; + } + } + const target = + typeof idx === 'number' && idx >= 0 && idx < injections.length ? injections[idx] : null; + if (!target) { + unassigned += 1; + continue; + } + if (!Array.isArray(target.components)) target.components = []; + target.components.push(stripExtensionEditorMeta(node)); + } + return { content: { ...content, injections }, unassigned }; +} + +/** 원본 overlay content 가 가진 주입 컴포넌트 루트 수 — 0 이면 잃을 것이 없어 가드가 불필요하다. */ +function countInjectionComponents(content: Record): number { + if (!Array.isArray(content.injections)) return 0; + return (content.injections as any[]).reduce( + (n, inj) => n + (inj && Array.isArray(inj.components) ? inj.components.length : 0), + 0, + ); +} + /** * 편집기 전용/호스트 합성 메타(`__injectionIndex`/`__source`/`__injectedProps`/`_fromBase`)를 * 노드 트리에서 재귀 제거한다(저장 직렬화 — 확장 content 원본 형태 복원). @@ -641,7 +709,29 @@ export function useExtensionDocument(): UseExtensionDocumentResult { const extracted = extractCurrentExtensionNodes(current.components, current.extensionId); const fragmentRoots = extracted.length > 0 ? extracted : current.components; // 편집한 components 를 원본 content 형태로 재조립(비편집 키 보존, 메타 제거). - const contentToSave = reassembleContent(contentRef.current, fragmentRoots); + let contentToSave: Record; + const isOverlay = + Array.isArray(contentRef.current.injections) && !Array.isArray(contentRef.current.components); + if (isOverlay) { + // 손실 가드 — 되돌리지 못한 노드가 있고 원본에 잃을 컴포넌트가 있으면 PUT 하지 않는다. + // (종전엔 그 노드를 조용히 버려 무변경 저장만으로 injections 가 비워졌다.) + const reassembled = reassembleOverlayContent(contentRef.current, fragmentRoots); + if (reassembled.unassigned > 0 && countInjectionComponents(contentRef.current) > 0) { + trackEditorDocument({ + op: 'save', + layoutName: `extension:${current.extensionId}`, + editMode: 'extension', + saveTarget: 'layout_extension', + endpoint: url, + isDirty, + timestamp: Date.now(), + }); + return { kind: 'guard_extension_reassembly', unassigned: reassembled.unassigned }; + } + contentToSave = reassembled.content; + } else { + contentToSave = reassembleContent(contentRef.current, fragmentRoots); + } trackEditorDocument({ op: 'save', @@ -710,8 +800,8 @@ export function useExtensionDocument(): UseExtensionDocumentResult { if (response.status === 409) { return { kind: 'concurrent_modification', - currentVersion: (body as any)?.current_version ?? -1, - yourVersion: (body as any)?.your_version ?? current.lockVersion, + currentVersion: readConflictVersion(body, 'current_version') ?? -1, + yourVersion: readConflictVersion(body, 'your_version') ?? current.lockVersion, }; } if (response.status === 422) { diff --git a/resources/js/core/template-engine/layout-editor/hooks/useLayoutDocument.ts b/resources/js/core/template-engine/layout-editor/hooks/useLayoutDocument.ts index d7ce6765..95c1be63 100644 --- a/resources/js/core/template-engine/layout-editor/hooks/useLayoutDocument.ts +++ b/resources/js/core/template-engine/layout-editor/hooks/useLayoutDocument.ts @@ -14,6 +14,7 @@ */ import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import { readConflictVersion } from '../utils/conflictVersion'; import { useLayoutEditor } from '../LayoutEditorContext'; import { buildEditorAccessError, @@ -130,7 +131,12 @@ export type SaveResult = | { kind: 'concurrent_modification'; currentVersion: number; yourVersion: number } | { kind: 'blocked_inactive_extension'; blockedPaths: string[] } | { kind: 'network_error'; message: string } - | { kind: 'guard_no_document' }; + | { kind: 'guard_no_document' } + /** + * 확장 편집 모드(overlay) 저장 가드 — 추출한 노드 중 어느 injection 에서 왔는지 알 수 없는 것이 + * 있어 PUT 하지 않았다. 그대로 저장하면 그 노드가 버려져 injections 가 비워진다. + */ + | { kind: 'guard_extension_reassembly'; unassigned: number }; export interface UseLayoutDocumentResult { /** 현재 로드된 문서 (null = 로드 전 / 라우트 미선택) */ @@ -940,12 +946,13 @@ export function useLayoutDocument(): UseLayoutDocumentResult { }; } - // 409 — 동시 수정 + // 409 — 동시 수정. 서버(ResponseHelper::error)는 버전을 `errors` 아래에 싣는다 — + // 최상위만 읽으면 배너가 「최신 버전: -1」 을 표시한다. if (response.status === 409) { return { kind: 'concurrent_modification', - currentVersion: (body as any)?.current_version ?? -1, - yourVersion: (body as any)?.your_version ?? current.lockVersion, + currentVersion: readConflictVersion(body, 'current_version') ?? -1, + yourVersion: readConflictVersion(body, 'your_version') ?? current.lockVersion, }; } diff --git a/resources/js/core/template-engine/layout-editor/spec/coreActionRecipes.ts b/resources/js/core/template-engine/layout-editor/spec/coreActionRecipes.ts index f4575be7..fccf9560 100644 --- a/resources/js/core/template-engine/layout-editor/spec/coreActionRecipes.ts +++ b/resources/js/core/template-engine/layout-editor/spec/coreActionRecipes.ts @@ -285,6 +285,18 @@ export const CORE_ACTION_RECIPES: Record = { params: [{ key: 'body', label: `$t:${L}.login.param_body`, widget: 'key-value' }], build: { handler: 'login', params: { body: '{{body}}' } }, }, + // C18-1 2단계 인증 코드 확인 (로그인 완료) + loginTwoFactor: { + label: `$t:${L}.login_two_factor.label`, + params: [{ key: 'body', label: `$t:${L}.login_two_factor.param_body`, widget: 'key-value' }], + build: { handler: 'loginTwoFactor', params: { body: '{{body}}' } }, + }, + // C18-2 2단계 인증 코드 재발송 + loginTwoFactorResend: { + label: `$t:${L}.login_two_factor_resend.label`, + params: [{ key: 'body', label: `$t:${L}.login_two_factor_resend.param_body`, widget: 'key-value' }], + build: { handler: 'loginTwoFactorResend', params: { body: '{{body}}' } }, + }, // C19 로그아웃 처리 logout: { label: `$t:${L}.logout.label`, diff --git a/resources/js/core/template-engine/layout-editor/spec/coreProps.ts b/resources/js/core/template-engine/layout-editor/spec/coreProps.ts index f4e38648..cb3b5f1d 100644 --- a/resources/js/core/template-engine/layout-editor/spec/coreProps.ts +++ b/resources/js/core/template-engine/layout-editor/spec/coreProps.ts @@ -98,7 +98,23 @@ export const DEFAULT_CORE_PROP_KEYS: CorePropKey[] = ['id']; export function resolveCorePropKeys(coreProps: unknown): CorePropKey[] { if (coreProps === false) return []; if (Array.isArray(coreProps)) { - return coreProps.filter((k): k is CorePropKey => k in CORE_PROP_CONTROLS); + return coreProps.filter( + (k): k is CorePropKey => k in CORE_PROP_CONTROLS && !DEDICATED_UI_KEYS.has(k as CorePropKey), + ); } - return [...DEFAULT_CORE_PROP_KEYS]; + return DEFAULT_CORE_PROP_KEYS.filter((k) => !DEDICATED_UI_KEYS.has(k)); } + +/** + * 전용 UI(`IsolatedScopeControl`)가 SSoT 인 코어 속성 키 — `ControlRenderer` 경유 렌더 금지. + * + * 이 두 키는 「격리 영역」 그룹이 직접 렌더한다. 어떤 템플릿이든 capability 에 + * `coreProps:["id","isolatedState"]` 한 줄을 쓰면 **같은 키를 두 UI 가 쓰는 이중 경로**가 + * 생기고, 그 순간 값 타입이 갈린다 — `IsolatedScopeControl` 은 ON 시 `isolatedState = {}` + * (빈 객체)를 쓰는데 `toggle` 위젯은 `true` 를 낸다. 렌더 목록 층에서만 제외하고 + * `CORE_PROP_CONTROLS` 의 정의 자체는 남긴다(SSoT 선언 유지). + */ +const DEDICATED_UI_KEYS: ReadonlySet = new Set([ + 'isolatedState', + 'isolatedScopeId', +]); diff --git a/resources/js/core/template-engine/layout-editor/spec/recipeEngine.ts b/resources/js/core/template-engine/layout-editor/spec/recipeEngine.ts index 710ec59f..04e88e03 100644 --- a/resources/js/core/template-engine/layout-editor/spec/recipeEngine.ts +++ b/resources/js/core/template-engine/layout-editor/spec/recipeEngine.ts @@ -35,7 +35,7 @@ import { } from './styleScope'; /** apply 프리미티브 종류 */ -export type RecipeApplyType = 'classToken' | 'styleProp' | 'propValue' | 'cssVar'; +export type RecipeApplyType = 'classToken' | 'styleProp' | 'propValue' | 'cssVar' | 'nodeKey'; /** * 컨트롤/옵션의 `apply` 선언. 컨트롤 자체에 직접 두거나(`color`/`image`/`width` @@ -65,6 +65,13 @@ export interface RecipeApply { propKey?: string; /** cssVar — CSS 변수명 (`--brand-color` 등) */ varName?: string; + /** + * nodeKey — 노드 **최상위** 키 이름(`dataKey`/`isolatedState` 등). + * + * `props` 가 아니라 노드 자신에 쓰는 구조키다. `propValue` 로 흘리면 + * `props.dataKey` 가 되어 런타임 엔진이 영영 읽지 않는다. + */ + nodeKey?: string; } /** @@ -110,6 +117,16 @@ interface NormalizedControl { * 이전 방향 토큰 제거). buildGroupTokenMatcher 의 빈 prefix 전체 매칭은 배제한다. */ groupPrefixes?: string[]; + /** + * 이 컨트롤이 쓰는 위젯 이름(`image`/`text`/`select` …). + * + * 엔진은 원칙적으로 위젯을 모르지만, **값 형태가 스칼라가 아닌 위젯**은 단일 값 + * 슬롯(`propValue`/`cssVar`/단일 `styleProp`)에 그대로 기록하면 `[object Object]` + * 가 저장된다. 그 축약·역조립의 게이트로 쓰기 위해 위젯 이름을 보존한다. + * **값 형태 sniffing 으로 대신할 수 없다** — `isImageValueObject` 는 4키 중 하나만 + * 있어도 참이라 `{position:'left'}` 같은 정당한 객체 prop 을 이미지로 오인한다. + */ + widget?: string; } /** EditorControlSpec → 정규화 (작성자 자유 필드 보존하되 엔진이 쓰는 키만 추출) */ @@ -117,6 +134,7 @@ function normalize(control: EditorControlSpec): NormalizedControl { const gp = (control as { groupPrefixes?: unknown }).groupPrefixes; return { group: typeof control.group === 'string' ? control.group : undefined, + widget: typeof control.widget === 'string' ? control.widget : undefined, apply: (control.apply && typeof control.apply === 'object' ? (control.apply as unknown as RecipeApply) : undefined) as RecipeApply | undefined, @@ -298,6 +316,34 @@ function isImageBundleProps(props: string[]): boolean { return props.includes('backgroundImage'); } +/** + * `image` 위젯 값 객체(`{ url, size, repeat, position }`)를 **단일 값 슬롯** + * (`propValue` / `cssVar` / 단일 `styleProp`)에 쓸 스칼라로 축약한다. + * + * 단일 슬롯은 값을 하나만 담을 수 있으므로 `url` 만 살아남는다(size/repeat/position 은 + * 저장할 자리가 없다 — `ImagePickerControl` 이 단일 슬롯 컨트롤에서는 애초에 그 3필드를 + * 만들지 않는다). + * + * **호출 게이트는 `widget === 'image'` 이며 값 형태 sniffing 이 아니다.** + * `isImageValueObject` 는 4키 중 **하나만** 있어도 참이므로, 값만 보고 게이트하면 + * `props.tooltip = { position: 'left' }` 같은 정당한 객체 prop 을 이미지로 오인해 + * 삭제한다. 그래서 이 함수 안의 `isImageValueObject` 는 2중 가드일 뿐이고, 1차 + * 게이트는 호출부의 위젯 판정이다. + * + * @param value 컨트롤이 만든 값 + * @param cssContext CSS 값 문맥(styleProp/cssVar)이면 true — `url(...)` 로 다시 감싼다 + * @return 이미지 값 객체가 아니면 value 그대로, 맞으면 축약된 스칼라 + */ +function scalarizeImageValue(value: unknown, cssContext: boolean): unknown { + if (!isImageValueObject(value)) return value; + const raw = (value as Record).url; + // url 부재/빈 문자열/비-문자열 → '' 로 축약해 호출부의 **기존 빈값 삭제 술어**에 위임한다 + // (새 술어를 신설하지 않는다). "url 없이 모드만" 은 위젯이 표현할 수 없는 상태다. + if (typeof raw !== 'string' || raw.trim() === '') return ''; + const bare = unwrapCssUrl(raw); + return cssContext ? wrapCssUrl(bare) : bare; +} + /** * 레거시 손상값 정화 — 과거(수정 이전) 저장본은 각 background 속성에 image 값 객체가 * 통째로 들어가 있을 수 있다(`backgroundPosition: { url, size, ... }`). 그런 객체/배열은 @@ -342,11 +388,67 @@ function readImageObject(style: Record): Record = new Set([ + 'type', + 'name', + 'props', + 'children', + 'text', + 'if', + 'iteration', + 'responsive', + '__source', + '__injectedProps', +]); + +/** + * nodeKey 적용 — 노드 **최상위**(`node[key]`)에 값을 쓴다(불변, 새 사본 반환). + * + * `props`/`style` 컨테이너를 다루지 않으므로 `withScopedProps` 를 경유하지 **않는다** — + * 그 함수는 mutator 에 `(props, style)` 만 넘기고 `{...node, props}` 로 재조립하므로 + * 노드 최상위 임의 키를 만질 방법이 없고, 빈 props 프루닝 로직도 nodeKey 에는 개념이 + * 없다. `setScopedIf`(styleScope) 가 `node.if` 라는 최상위 키를 별도 함수로 다루는 + * 선례를 그대로 따른다. + * + * **디바이스(breakpoint) 축은 무시한다** — 런타임 `DynamicRenderer` 의 responsive 병합은 + * `props`/`children`/`text`/`if`/`iteration` 5키만 본다. `responsive.mobile.dataKey` 는 + * 런타임이 영원히 읽지 않는 죽은 데이터이므로 항상 최상위에 쓴다. bp≠base 에서 no-op 으로 + * 만드는 대안은 "모바일 탭에서 입력했는데 아무 일도 안 일어남" 이라는 또 다른 무음 결함이다. + * + * @param node 대상 노드 (변경되지 않음) + * @param apply nodeKey apply 선언 + * @param value 쓸 값. 빈값(undefined|null|'')이면 키 삭제 + * @return 패치된 노드 사본. 예약키/비-문자열 nodeKey 는 no-op(원본 반환) + */ +function applyNodeKey(node: EditorNode, apply: RecipeApply, value: unknown): EditorNode { + const key = apply.nodeKey; + if (typeof key !== 'string' || key === '' || RESERVED_NODE_KEYS.has(key)) return node; + const next = { ...node } as EditorNode & Record; + if (value === undefined || value === null || value === '') { + delete next[key]; + } else { + next[key] = value; + } + return next; +} + +/** + * styleProp 적용 — 단일/다중 CSS 속성 설정 (value=null/undefined 면 제거) + * + * @param imageWidget 컨트롤 위젯이 `image` 인지 — 단일 `apply.prop` 경로에서 값 객체를 + * 축약할지 결정한다. 다중 `apply.props`(배경 묶음) 경로는 무영향. + */ function applyStyleProp( style: Record, apply: RecipeApply, value: unknown, + imageWidget = false, ): void { const setOne = (prop: string, v: unknown): void => { if (v === undefined || v === null || v === '') { @@ -387,14 +489,27 @@ function applyStyleProp( } if (typeof apply.prop === 'string') { - setOne(apply.prop, apply.value !== undefined ? apply.value : value); + // 단일 값 슬롯 — 고정값(apply.value) 선택 **이후**에 축약한다. 정규화 지점이 한 곳으로 + // 수렴하고, 작성자가 `apply.value: {url:…}` 를 실수로 선언해도 같은 규칙으로 구제된다. + const raw = apply.value !== undefined ? apply.value : value; + setOne(apply.prop, imageWidget ? scalarizeImageValue(raw, true) : raw); } } -/** cssVar 적용 — props.style 에 `--var: value` 설정 */ -function applyCssVar(style: Record, apply: RecipeApply, value: unknown): void { +/** + * cssVar 적용 — props.style 에 `--var: value` 설정 + * + * @param imageWidget 컨트롤 위젯이 `image` 인지 — 값 객체를 CSS `url(...)` 스칼라로 축약 + */ +function applyCssVar( + style: Record, + apply: RecipeApply, + value: unknown, + imageWidget = false, +): void { if (typeof apply.varName !== 'string') return; - const v = apply.value !== undefined ? apply.value : value; + const raw = apply.value !== undefined ? apply.value : value; + const v = imageWidget ? scalarizeImageValue(raw, true) : raw; if (v === undefined || v === null || v === '') { delete style[apply.varName]; } else { @@ -402,14 +517,22 @@ function applyCssVar(style: Record, apply: RecipeApply, value: } } -/** propValue 적용 — props[key] 설정 */ +/** + * propValue 적용 — props[key] 설정 + * + * @param imageWidget 컨트롤 위젯이 `image` 인지 — 값 객체를 **맨 url 문자열**로 축약한다. + * 이 값은 `` 같은 컴포넌트 prop 으로 흐르므로 `url(...)` + * 래핑을 반드시 벗긴다(CSS 문맥이 아니다). + */ function applyPropValue( props: Record, apply: RecipeApply, value: unknown, + imageWidget = false, ): void { if (typeof apply.propKey !== 'string') return; - const v = apply.value !== undefined ? apply.value : value; + const raw = apply.value !== undefined ? apply.value : value; + const v = imageWidget ? scalarizeImageValue(raw, false) : raw; if (v === undefined || v === null || v === '') { delete props[apply.propKey]; } else { @@ -490,6 +613,12 @@ export function applyRecipe( return node; } + // nodeKey — 노드 최상위 구조키. props/style 컨테이너를 다루지 않으므로 withScopedProps + // 앞에서 처리한다(scope 컨테이너 개념이 없다). 다크는 위 short-circuit 으로 도달 불가. + if (effectiveApply?.type === 'nodeKey') { + return applyNodeKey(node, effectiveApply, clearGroup ? undefined : value); + } + // B안 className 시드. // // DynamicRenderer 의 responsive 머지는 props 얕은 머지(`{...base.props, ...override.props}`) @@ -536,19 +665,22 @@ export function applyRecipe( } } } else if (apply) { + // 비-스칼라 값을 내보내는 위젯 — 단일 값 슬롯에 그대로 쓰면 `[object Object]` 가 + // 저장된다(공개 #135). 게이트는 위젯 이름이며 값 형태 sniffing 이 아니다. + const imageWidget = normalized.widget === 'image'; switch (apply.type) { case 'classToken': applyClassToken(props, normalized, apply, value, dark); break; case 'styleProp': // 다크는 위 short-circuit 으로 도달 불가 (인라인 no-op) - applyStyleProp(style, apply, value); + applyStyleProp(style, apply, value, imageWidget); break; case 'cssVar': - applyCssVar(style, apply, value); + applyCssVar(style, apply, value, imageWidget); break; case 'propValue': - applyPropValue(props, apply, value); + applyPropValue(props, apply, value, imageWidget); break; } } @@ -720,9 +852,19 @@ function resolveFromContainer( case 'propValue': { if (!apply.propKey) return { value: undefined, matched: false }; const v = props[apply.propKey]; - return v === undefined - ? { value: undefined, matched: false } - : { value: v, matched: true }; + if (v === undefined) return { value: undefined, matched: false }; + // image 위젯은 객체(`{url,…}`)를 이해한다 — 축약 저장된 문자열을 그 형태로 되감아야 + // 위젯이 현재 값을 읽을 수 있다(쓰기 4.1 과 대칭). `apply.type` 만으로는 판정 불가하고 + // (propValue 는 text/toggle/icon-picker 가 대부분 쓴다), 값이 평범한 문자열이라 형태 + // sniffing 도 불가하다 — **widget 이 유일한 신호다.** + // + // 표현식 문자열(`{{…}}`)도 감싼다. 감싸지 않으면 위젯이 빈 피커로 보이고 운영자가 + // 업로드 1클릭에 그 표현식을 소리 없이 잃는다. 깨진 미리보기 억제와 실수 덮어쓰기 + // 방지는 미리보기·업로드 UI 를 소유한 위젯이 맡는다(ImagePickerControl). + if (normalized.widget === 'image' && typeof v === 'string') { + return { value: { url: unwrapCssUrl(v) }, matched: true }; + } + return { value: v, matched: true }; } case 'classToken': { const tokens = scopedClassTokens(props.className, dark); @@ -790,6 +932,18 @@ export function reverseResolve( return { value: undefined, matched: false, darkReadonly: true }; } + // nodeKey — 노드 최상위에서 읽는다(쓰기와 대칭). scope 컨테이너 개념이 없으므로 + // scopedValue/baseFallback 을 부여하지 않는다 — 디바이스 탭에서 placeholder 흐림이 + // 생기면 "그 디바이스에만 값이 없다" 는 거짓 정보가 된다. + if (normalized.apply?.type === 'nodeKey') { + const key = normalized.apply.nodeKey; + if (typeof key !== 'string' || key === '' || RESERVED_NODE_KEYS.has(key)) { + return { value: undefined, matched: false }; + } + const v = (node as EditorNode & Record)[key]; + return v === undefined ? { value: undefined, matched: false } : { value: v, matched: true }; + } + // scope 컨테이너에서 역해석 const scopedProps = getScopedProps(node, scope); const scopedStyle = styleOf(scopedProps); diff --git a/resources/js/core/template-engine/layout-editor/spec/registerCoreWidgets.ts b/resources/js/core/template-engine/layout-editor/spec/registerCoreWidgets.ts index 54b72c3c..fb6a9b06 100644 --- a/resources/js/core/template-engine/layout-editor/spec/registerCoreWidgets.ts +++ b/resources/js/core/template-engine/layout-editor/spec/registerCoreWidgets.ts @@ -16,6 +16,7 @@ import { SelectWidget, ToggleWidget, DimensionWidget, + NumberWidget, SpacingWidget, } from '../components/property-controls/StyleControlWidgets'; import { ColorPickerControl } from '../components/property-controls/ColorPickerControl'; @@ -46,6 +47,10 @@ export function registerCoreWidgets(): void { registerWidget('slider', SliderWidget); registerWidget('select', SelectWidget); registerWidget('dimension', DimensionWidget); + // 숫자 prop(propValue) 편집 위젯. 종전 미등록이라 `widget:"number"` 컨트롤 + // (예 Header 「탭 표시 게시판 수」)이 속성 모달에서 "지원하지 않는 컨트롤" 로 폴백돼 + // 편집 자체가 불가했다. `number` 타입을 직접 내보내 컴포넌트 prop 계약을 만족시킨다. + registerWidget('number', NumberWidget); registerWidget('spacing', SpacingWidget); registerWidget('toggle', ToggleWidget); registerWidget('color', ColorPickerControl); diff --git a/resources/js/core/template-engine/layout-editor/spec/specTypes.ts b/resources/js/core/template-engine/layout-editor/spec/specTypes.ts index 728de8c1..f41bafea 100644 --- a/resources/js/core/template-engine/layout-editor/spec/specTypes.ts +++ b/resources/js/core/template-engine/layout-editor/spec/specTypes.ts @@ -328,6 +328,20 @@ export interface EditorControlSpec { iconColumns?: number; /** 검색 입력 placeholder — `$t:...` 키 */ iconSearchPlaceholder?: string; + /** + * `number` 위젯이 입력칸에 그대로 실어 보내는 HTML 입력 제약 선언. + * + * 그 입력칸은 `type="text"`(비숫자 입력을 원문 그대로 쥐고 있어야 미방출 정책이 + * 성립한다) 이므로 **브라우저 검증도 스피너도 동작하지 않는다** — 이 세 필드는 값을 + * 제한하는 장치가 아니라 작성자 의도의 선언이다. + * + * 위젯은 이 값으로 **클램프하지 않는다** — 사용자가 넣은 값은 보존한다(조용한 데이터 + * 변조 금지). 아래 세 필드는 index signature 가 이미 허용하므로 하위호환 100% 이고, + * 명시 선언은 발견성만 개선한다. + */ + min?: number; + max?: number; + step?: number; /** 작성자 자유 필드 — 코어는 widget/apply 외 필드를 그대로 보존만 한다 */ [key: string]: unknown; } diff --git a/resources/js/core/template-engine/layout-editor/utils/conflictVersion.ts b/resources/js/core/template-engine/layout-editor/utils/conflictVersion.ts new file mode 100644 index 00000000..b0e0a45d --- /dev/null +++ b/resources/js/core/template-engine/layout-editor/utils/conflictVersion.ts @@ -0,0 +1,25 @@ +/** + * 409(동시 수정) 응답 본문의 버전 번호 판독. + * + * 서버(`ResponseHelper::error`)는 `current_version`/`your_version` 을 최상위가 아니라 `errors` + * 아래에 싣는다. 최상위만 읽으면 값이 없어 -1 로 떨어지고 배너가 「최신 버전: -1」 을 표시한다. + * 레이아웃 저장·확장 저장·inject_props 교차 저장 세 경로가 같은 판독을 쓴다. + * + * @since engine-v1.66.1 + */ + +/** + * @param body 파싱된 응답 본문(형식 미상) + * @param key `current_version` | `your_version` + * @returns 숫자 또는 undefined — `errors.{key}` 우선, 최상위 폴백(구 응답 형식 호환) + */ +export function readConflictVersion( + body: unknown, + key: 'current_version' | 'your_version', +): number | undefined { + const b = body as ({ errors?: Record } & Record) | null | undefined; + const nested = b?.errors?.[key]; + if (typeof nested === 'number') return nested; + const top = b?.[key]; + return typeof top === 'number' ? top : undefined; +} diff --git a/resources/js/core/template-engine/layout-editor/utils/injectedPropsCrossSave.ts b/resources/js/core/template-engine/layout-editor/utils/injectedPropsCrossSave.ts index e5cc48a6..1edc0a92 100644 --- a/resources/js/core/template-engine/layout-editor/utils/injectedPropsCrossSave.ts +++ b/resources/js/core/template-engine/layout-editor/utils/injectedPropsCrossSave.ts @@ -15,6 +15,7 @@ */ import { buildAuthHeaders } from './authToken'; +import { readConflictVersion } from './conflictVersion'; /** 교차 저장 결과 */ export type InjectedPropsSaveResult = @@ -112,8 +113,8 @@ export async function saveInjectedPropsToExtension( if (putRes.status === 409) { return { kind: 'conflict', - currentVersion: (putBody as any)?.current_version ?? -1, - yourVersion: (putBody as any)?.your_version ?? lockVersion, + currentVersion: readConflictVersion(putBody, 'current_version') ?? -1, + yourVersion: readConflictVersion(putBody, 'your_version') ?? lockVersion, }; } return { kind: 'error', message: (putBody as any)?.message ?? `HTTP ${putRes.status}` }; diff --git a/resources/js/core/template-engine/layout-editor/utils/layoutTreeUtils.ts b/resources/js/core/template-engine/layout-editor/utils/layoutTreeUtils.ts index fb295ce5..025ff555 100644 --- a/resources/js/core/template-engine/layout-editor/utils/layoutTreeUtils.ts +++ b/resources/js/core/template-engine/layout-editor/utils/layoutTreeUtils.ts @@ -31,6 +31,11 @@ export interface NodeSource { extensionIdentifier?: string; /** 확장 로케일 표시명(예: `게시판`) — 식별자와 함께 표시 */ extensionName?: string; + /** + * overlay 확장 `injections[]` 순번 — 백엔드가 주입 시점에 부여. 확장 편집 모드 저장이 호스트 + * 병합 트리에서 추출한 노드를 원래 injection 으로 되돌리는 열쇠(extension_point 주입은 없음). + */ + injectionIndex?: number; } /** @@ -450,6 +455,39 @@ function asResponsiveChildren(node: EditorNode, key: string): EditorNode[] { return branch && Array.isArray(branch.children) ? (branch.children as EditorNode[]) : []; } +/** + * path 의 조상 노드 배열 (루트의 **자식**부터 부모까지, 자기 자신·루트 제외). + * + * `classifyLockKind` 의 `ancestors` 입력 SSoT. responsive 세그먼트는 노드를 내리지 않고 + * childArray 만 분기로 전환하므로 조상 목록에 추가하지 않는다. + * + * 종전에는 `useCanvasDnd` · `DndCanvasLayer` 가 바이트 동일 사본을 각자 들고 있었다 — + * 잠금 판정의 입력이 갈리면 "핸들은 있는데 드래그는 거부" 같은 어긋남이 조용히 생긴다. + * (`useElementSelection.resolveAncestors` 는 **루트를 포함**하는 다른 계약이라 별개다.) + * + * @param root 루트 노드 + * @param path 대상 노드의 인덱스 경로 + * @returns 조상 노드 배열 (루트 자식 → 부모 순) + */ +export function collectAncestors(root: EditorNode, path: ComponentPath): EditorNode[] { + const out: EditorNode[] = []; + let current: EditorNode = root; + let childArray: EditorNode[] = asChildrenArray(root); + for (let i = 0; i < path.length - 1; i++) { + const seg = path[i]!; + if (isResponsiveSegment(seg)) { + childArray = asResponsiveChildren(current, seg.responsive); + continue; + } + const next = childArray[seg] ?? null; + if (!next) break; + out.push(next); + current = next; + childArray = asChildrenArray(next); + } + return out; +} + /** * 경로로 노드 조회. 경로가 유효하지 않으면 null. * diff --git a/resources/js/core/template-engine/networkResilience.ts b/resources/js/core/template-engine/networkResilience.ts index 4ee0a907..c2c1c47e 100644 --- a/resources/js/core/template-engine/networkResilience.ts +++ b/resources/js/core/template-engine/networkResilience.ts @@ -59,13 +59,22 @@ export function isAbortError(error: unknown): boolean { * fetch 스펙상 네트워크 오류는 TypeError 로 reject 된다. 취소(AbortError)는 * 호출부의 의도이므로 재시도 대상이 아니다. * + * axios 경로(로그인 등 ApiClient 를 쓰는 요청)는 TypeError 가 아니라 `AxiosError` 로 + * reject 되며 종류는 `code` 에만 남는다. TypeError 만 보면 그 경로의 네트워크 실패가 + * 판정에서 빠져 영문 원문(`Network Error`)이 그대로 화면에 노출된다. + * * @param error 검사할 에러 * @return bool 재시도할 가치가 있는 네트워크 실패이면 true * @since engine-v1.53.0 + * @since engine-v1.65.0 axios 네트워크 오류 코드(ERR_NETWORK/ECONNABORTED) 인식 */ export function isNetworkFailure(error: unknown): boolean { if (isAbortError(error)) return false; - return error instanceof TypeError; + if (error instanceof TypeError) return true; + + const code = (error as { code?: string } | null)?.code; + + return code === 'ERR_NETWORK' || code === 'ECONNABORTED'; } let unloading = false; diff --git a/routes/api.php b/routes/api.php index e66ab10f..e207d8dc 100644 --- a/routes/api.php +++ b/routes/api.php @@ -256,6 +256,9 @@ Route::prefix('auth')->group(function () { // 로그인과 같은 제한을 적용해 코드 대입 시도를 함께 억제한다. Route::post('login/two-factor', [UserAuthController::class, 'verifyTwoFactor']) ->name('api.auth.login.two-factor'); + // 인증번호 재발송 — 기존 challenge 를 취소하고 새로 발행한다. + Route::post('login/two-factor/resend', [UserAuthController::class, 'resendTwoFactor']) + ->name('api.auth.login.two-factor.resend'); }); // 공개 인증 라우트 (세션 불필요) @@ -280,6 +283,13 @@ Route::prefix('auth')->group(function () { Route::post('login', [AdminAuthController::class, 'login']) ->middleware(['throttle:auth-login', 'start.api.session']) ->name('api.auth.admin.login'); + // 관리자 2단계 인증 확인·재발송 — 사용자 경로와 같은 제한을 적용한다. + Route::post('login/two-factor', [AdminAuthController::class, 'verifyTwoFactor']) + ->middleware(['throttle:auth-login', 'start.api.session']) + ->name('api.auth.admin.login.two-factor'); + Route::post('login/two-factor/resend', [AdminAuthController::class, 'resendTwoFactor']) + ->middleware(['throttle:auth-login', 'start.api.session']) + ->name('api.auth.admin.login.two-factor.resend'); }); }); diff --git a/templates/_bundled/sirsoft-admin_basic/AGENTS.md b/templates/_bundled/sirsoft-admin_basic/AGENTS.md index c90cc81e..8dd77c2c 100644 --- a/templates/_bundled/sirsoft-admin_basic/AGENTS.md +++ b/templates/_bundled/sirsoft-admin_basic/AGENTS.md @@ -104,6 +104,7 @@ admin/`)이 이 템플릿의 베이스(`_admin_base`)를 extends 하고 이 템 - [ ] `_admin_base.json` 슬롯 구조(`content` 슬롯 등) 변경 시 그 슬롯에 의존하는 모든 화면(145개 레이아웃 대다수) 영향 검토 - [ ] AdminSidebar 의 `MenuItem`/`AdminSidebarProps` 인터페이스 확장 시 이 문서의 §docs/components.md "AdminSidebar 상세" 동기화 - [ ] 레이아웃·컴포넌트·`data_source` 를 건드렸다면 [`docs/editor-spec.md`](docs/editor-spec.md) 의 동반 의무 표를 따라 `editor-spec/` 블록을 함께 갱신 — 컴포넌트는 팔레트·역량·중첩 **넷 다** 손대야 편집기에서 온전히 동작하고, 하나만 빠지면 절반만 동작한다. 반영은 `php artisan template:update sirsoft-admin_basic --force` (편집기는 활성 디렉토리만 읽는다) +- [ ] 로그인 화면의 2단계 인증 단계를 고쳤다면 1단계·2단계 `if` 의 상보성과 `login`/`loginTwoFactor` 의 상호배타 `if` 를 함께 확인 — 한쪽이 빠지면 인증번호 단계에서 Enter 가 새 challenge 를 발급한다 ## 6. 금지 패턴 @@ -114,6 +115,7 @@ admin/`)이 이 템플릿의 베이스(`_admin_base`)를 extends 하고 이 템 | 필수 컴포넌트 목록 밖의 이 템플릿 전용 컴포넌트를 모듈 레이아웃에서 사용 | 필수 컴포넌트(config/template.php) 만 사용 | 다른 admin 템플릿으로 교체 시 그 화면만 깨진다 | | 사이드바 접힘 상태를 레이아웃 `init_actions` 로 매번 복원 | 템플릿 부트스트랩(`src/index.ts`)에서 1회 복원 | `init_actions` 는 화면 진입마다 재실행되어 불필요한 반복 처리가 된다 | | `_admin_base` 를 상속하는데 로그인 화면처럼 `initTheme`/메뉴 초기화를 다시 호출 | `_admin_base` 상속 화면은 이미 초기화된 전역 상태를 그대로 사용 | 중복 호출은 낭비이며, 두 초기화 지점의 결과가 어긋나면 화면 간 상태 불일치가 생긴다 | +| `onSuccess`·시퀀스 안에서 방금 저장한 상태(`_global.*`/`_local.*`)를 형제 액션의 `if`·값으로 재독 | 그 자리에서는 `{{response.*}}` 만 읽는다 | 그 시점 컨텍스트는 아직 갱신 전이라 stale 값으로 조용히 분기한다 | ## 7. 테스트 실행 @@ -122,9 +124,9 @@ admin/`)이 이 템플릿의 베이스(`_admin_base`)를 extends 하고 이 템 | 종류 | 개수 | 위치 | |---|---|---| | PHPUnit | 0개 | — | -| Vitest | 208개 | `vitest.config.ts` | +| Vitest | 210개 | `vitest.config.ts` | | Playwright | 9개 | `tests/Playwright` | -| 시나리오 매니페스트 | 2개 | `tests/scenarios` | +| 시나리오 매니페스트 | 3개 | `tests/scenarios` | ```bash # Vitest (확장 디렉토리에서) (PowerShell) diff --git a/templates/_bundled/sirsoft-admin_basic/CHANGELOG.md b/templates/_bundled/sirsoft-admin_basic/CHANGELOG.md index f801de95..65113c3c 100644 --- a/templates/_bundled/sirsoft-admin_basic/CHANGELOG.md +++ b/templates/_bundled/sirsoft-admin_basic/CHANGELOG.md @@ -4,6 +4,21 @@ 형식은 [Keep a Changelog](https://keepachangelog.com/ko/1.1.0/)를 따르며, [Semantic Versioning](https://semver.org/lang/ko/)을 준수합니다. +## [1.0.9] - 2026-09-09 + +### Added + +- 2단계 인증을 켠 사이트의 로그인 화면에 인증번호 입력 단계가 추가되었습니다. 비밀번호를 확인하면 같은 카드 안에서 인증번호 입력으로 넘어가고, 「인증번호 다시 받기」로 새 번호를 받거나 「처음부터」로 되돌아갈 수 있습니다. 인증번호의 유효 시각도 함께 표시됩니다. (#133 @keidichoi-gif 님께서 제보해주셨습니다.) + +### Changed + +- 코어 최소 요구 버전을 7.0.11 로 상향했습니다. + +### Fixed + +- 로그인 시도 초과로 계정이 잠겼을 때 해제 시각이 화면에 표시되지 않던 문제를 수정했습니다. 언제 다시 시도할 수 있는지 알 수 없어 계속 눌러 보게 되었습니다. +- 환경설정 > 드라이버의 「공개 자산 스토리지」 설명에 레이아웃 배경 이미지가 대상에 포함된다는 점과, 설정을 켠 뒤 올린 파일만 저장소 주소로 바뀌며 그 주소가 저장된 레이아웃에 남는다는 점을 덧붙였습니다. + ## [1.0.8] - 2026-09-06 ### Added diff --git a/templates/_bundled/sirsoft-admin_basic/README.md b/templates/_bundled/sirsoft-admin_basic/README.md index 1696b342..3eb77f98 100644 --- a/templates/_bundled/sirsoft-admin_basic/README.md +++ b/templates/_bundled/sirsoft-admin_basic/README.md @@ -5,9 +5,9 @@

- version 1.0.8 + version 1.0.9 type 템플릿 - 그누보드7 >=7.0.10 + 그누보드7 >=7.0.11 license MIT

@@ -68,7 +68,7 @@ flowchart TD | 항목 | 값 | |---|---| -| 그누보드7 코어 | `>=7.0.10` | +| 그누보드7 코어 | `>=7.0.11` | | PHP | `^8.2` | diff --git a/templates/_bundled/sirsoft-admin_basic/__tests__/layouts/admin-dashboard-alert-severity.test.tsx b/templates/_bundled/sirsoft-admin_basic/__tests__/layouts/admin-dashboard-alert-severity.test.tsx index ffe943c7..51eb4475 100644 --- a/templates/_bundled/sirsoft-admin_basic/__tests__/layouts/admin-dashboard-alert-severity.test.tsx +++ b/templates/_bundled/sirsoft-admin_basic/__tests__/layouts/admin-dashboard-alert-severity.test.tsx @@ -8,7 +8,7 @@ * 한 번도 쓰이지 않았다. 목록에 없는 subtype 은 전부 else 로 떨어져 **경고가 회색 * 일반 안내로** 렌더됐다(`static_publish_*` 가 이미 그 상태였다). * - 게다가 시스템 알림 카드는 대시보드 **맨 아래** 섹션이라, 화면이 정상으로 보이는 - * 구성에서는 운영자가 경고에 도달하지 못할 수 있었다. 그래서 PO 결정(2026-08-28)으로 + * 구성에서는 운영자가 경고에 도달하지 못할 수 있었다. 그래서 설계 결정(2026-08-28)으로 * **경고 등급 알림은 상단 배너로 승격**하고 나머지는 하단 카드에 남긴다. * * 이 테스트가 잠그는 계약: diff --git a/templates/_bundled/sirsoft-admin_basic/__tests__/layouts/admin-login-two-factor-render.test.tsx b/templates/_bundled/sirsoft-admin_basic/__tests__/layouts/admin-login-two-factor-render.test.tsx new file mode 100644 index 00000000..5f4c0123 --- /dev/null +++ b/templates/_bundled/sirsoft-admin_basic/__tests__/layouts/admin-login-two-factor-render.test.tsx @@ -0,0 +1,337 @@ +/** + * @file admin-login-two-factor-render.test.tsx + * @description 관리자 로그인 2단계 인증 단계 **렌더링** 회귀 테스트 (sirsoft-admin_basic) + * + * 형제 파일 `admin-login-two-factor-step.test.tsx` 는 레이아웃 JSON 의 구조를 단언한다. + * 이 파일은 그 JSON 을 실제로 렌더해 화면에 무엇이 나타나는지를 단언한다. + * + * 관리자가 들어갈 수 없으면 설정을 되돌릴 수단까지 사라지므로 사용자 화면보다 파급이 크다 + * (공개 #133). + * + * @vitest-environment jsdom + * @since engine-v1.65.0 + */ + +import React from 'react'; +import { describe, it, expect, beforeEach, vi } from 'vitest'; +import { createLayoutTest } from '@/core/template-engine/__tests__/utils/layoutTestUtils'; +import { ComponentRegistry } from '@/core/template-engine/ComponentRegistry'; + +import adminLogin from '../../layouts/admin_login.json'; + +// 공개 #133 의 원인은 레이아웃이 아니라 **응답 형태를 하나로 가정한 코어**였다. 상태를 직접 +// 주입해 그린 화면만 단언하면 그 경로를 한 번도 태우지 않으므로, 코어가 다시 challenge 응답에서 +// 던지더라도 이 파일은 초록으로 남는다. API 를 모킹해 login 액션을 실제로 통과시킨다. +const apiPost = vi.fn(); + +vi.mock('@core/api/ApiClient', async () => { + const actual = await vi.importActual( + '@core/api/ApiClient' + ); + const stub = { + post: (...args: unknown[]) => apiPost(...args), + get: vi.fn(), + put: vi.fn(), + delete: vi.fn(), + getToken: vi.fn(() => null), + setToken: vi.fn(), + removeToken: vi.fn(), + setLocale: vi.fn(), + }; + return { ...actual, getApiClient: () => stub, createApiClient: () => stub }; +}); + +type Common = { + className?: string; + children?: React.ReactNode; + text?: string; +}; + +const TestDiv: React.FC = ({ className, children, role }) => ( +
{children}
+); +const TestSpan: React.FC = ({ className, children, text }) => ( + {children || text} +); +const TestP: React.FC = ({ className, children, text, role }) => ( +

{children || text}

+); +const TestH1: React.FC = ({ className, children, text }) => ( +

{children || text}

+); +const TestLabel: React.FC = ({ className, children, text, htmlFor }) => ( + +); +const TestForm: React.FC void }> = ({ + className, + children, + onSubmit, +}) =>
{children}
; +const TestButton: React.FC = ({ + type, + className, + disabled, + children, + text, +}) => ( + +); +const TestInput: React.FC<{ + id?: string; + type?: string; + name?: string; + value?: string; + placeholder?: string; + disabled?: boolean; + className?: string; + inputMode?: string; + autoComplete?: string; + maxLength?: number; +}> = ({ id, type, name, value, placeholder, disabled, className, inputMode, autoComplete, maxLength }) => ( + +); +const TestSelect: React.FC<{ className?: string; value?: string }> = ({ className }) => ( + +); + +const TestPasswordInput: React.FC<{ name?: string; disabled?: boolean; className?: string }> = ({ + name, + disabled, + className, +}) => ; + +const TestFragment: React.FC<{ children?: React.ReactNode }> = ({ children }) => <>{children}; + +function setupRegistry(): ComponentRegistry { + const registry = ComponentRegistry.getInstance(); + + (registry as any).registry = { + Div: { component: TestDiv, metadata: { name: 'Div', type: 'basic' } }, + Span: { component: TestSpan, metadata: { name: 'Span', type: 'basic' } }, + P: { component: TestP, metadata: { name: 'P', type: 'basic' } }, + Label: { component: TestLabel, metadata: { name: 'Label', type: 'basic' } }, + Form: { component: TestForm, metadata: { name: 'Form', type: 'basic' } }, + Button: { component: TestButton, metadata: { name: 'Button', type: 'basic' } }, + Input: { component: TestInput, metadata: { name: 'Input', type: 'basic' } }, + PasswordInput: { component: TestPasswordInput, metadata: { name: 'PasswordInput', type: 'basic' } }, + Fragment: { component: TestFragment, metadata: { name: 'Fragment', type: 'layout' } }, + }; + + return registry; +} + +/** 실제 파셜을 단독 레이아웃으로 감싼다 — 컴포넌트 트리는 그대로다. */ +const layout = { + version: '1.0.0', + layout_name: 'auth/login', + components: [loginForm as any], +}; + +/** 챌린지를 받은 뒤의 전역 상태 */ +const CHALLENGE_STATE = { + _global: { + twoFactor: { + required: true, + challenge_id: '9f1c2f2e-0b3a-4f0a-9a1e-5c1b7f9d2c40', + provider_id: 'g7:core.mail', + expires_at: '2026-09-07T14:03:00+09:00', + code: '', + error: null, + verifying: false, + resending: false, + resent: false, + }, + }, +}; + +describe('sirsoft-basic 로그인 폼 렌더링 — 2단계 인증', () => { + beforeEach(() => { + setupRegistry(); + }); + + /** + * @scenario step=credentials, response=ok, action=submit + * + * @effects credential_step_hidden_on_challenge + */ + it('초기 상태에서는 이메일·비밀번호만 보이고 인증번호 입력은 없다', async () => { + const t = createLayoutTest(layout, { componentRegistry: setupRegistry() }); + await t.render(); + + expect(document.querySelector('input[name="email"]')).not.toBeNull(); + expect(document.querySelector('input[name="password"]')).not.toBeNull(); + expect(document.querySelector('input[name="two_factor_code"]')).toBeNull(); + + t.cleanup(); + }); + + /** + * @scenario step=code, response=challenge, action=submit + * + * @effects code_step_rendered_on_challenge, credential_step_hidden_on_challenge + */ + it('challenge 를 받으면 인증번호 입력으로 바뀌고 자격 증명 입력은 사라진다', async () => { + const t = createLayoutTest(layout, { + componentRegistry: setupRegistry(), + initialState: CHALLENGE_STATE, + }); + await t.render(); + + const code = document.querySelector('input[name="two_factor_code"]'); + expect(code, '챌린지 상태인데 인증번호 입력이 렌더되지 않았습니다').not.toBeNull(); + expect(code?.getAttribute('inputmode')).toBe('numeric'); + expect(code?.getAttribute('autocomplete')).toBe('one-time-code'); + + // 두 단계가 함께 보이면 사용자가 어느 쪽을 채워야 하는지 알 수 없다. + expect(document.querySelector('input[name="email"]')).toBeNull(); + expect(document.querySelector('input[name="password"]')).toBeNull(); + + t.cleanup(); + }); + + /** + * @scenario step=code, response=401, action=submit + * + * @effects no_raw_typeerror_text + */ + it('오류 문구 자리에 영문 TypeError 원문이 나타나지 않는다', async () => { + const t = createLayoutTest(layout, { + componentRegistry: setupRegistry(), + initialState: { + _global: { + ...CHALLENGE_STATE._global, + twoFactor: { + ...CHALLENGE_STATE._global.twoFactor, + error: '인증번호가 올바르지 않거나 유효시간이 지났습니다.', + }, + }, + }, + }); + await t.render(); + + const body = document.body.textContent ?? ''; + + expect(body).toContain('인증번호가 올바르지 않거나 유효시간이 지났습니다.'); + // 공개 #133 의 증상 — 응답 형태를 하나로 가정했을 때 화면에 그대로 실리던 문구다. + expect(body).not.toContain('Cannot read properties of undefined'); + expect(body).not.toContain('undefined is not an object'); + + t.cleanup(); + }); + + /** + * @scenario step=credentials, response=423, action=submit + * + * @effects locked_until_rendered + */ + it('계정이 잠기면 해제 시각 줄이 함께 렌더된다', async () => { + const t = createLayoutTest(layout, { + componentRegistry: setupRegistry(), + initialState: { + _global: { + loginError: '로그인 시도 횟수 초과로 계정이 잠겼습니다.', + loginErrors: { locked_until: '2026-09-07T14:05:00+09:00', permanent: false }, + }, + }, + }); + await t.render(); + + // 잠금 안내와 별개로 해제 시각 줄이 존재해야 한다 (문구 자체는 다국어 키 해석 대상). + const paragraphs = Array.from(document.querySelectorAll('p')); + expect(paragraphs.length).toBeGreaterThanOrEqual(2); + expect(document.body.textContent ?? '').toContain('로그인 시도 횟수 초과로 계정이 잠겼습니다.'); + + t.cleanup(); + }); + + /** + * 상태 주입이 아니라 **서버 응답**에서 출발한다. 이 레이아웃의 login 액션을 그대로 실행해 + * 코어(AuthManager → 액션 반환값 → onSuccess 매핑)를 통과시킨 뒤 화면을 본다. + * + * 자격 증명 값만 리터럴로 바꾼다 — 폼 입력 값(`{{form.*}}`)은 렌더러가 들고 있고 이 하네스가 + * 관측하지 못하는 유일한 조각이기 때문이다. 그 밖의 경로는 전부 실물이다. + * + * @scenario step=credentials, response=challenge, action=submit + * + * @effects code_step_rendered_on_challenge, credential_step_hidden_on_challenge, no_raw_typeerror_text + */ + it('challenge 응답을 실제로 받으면 2단계로 넘어가고 오류 원문이 남지 않는다', async () => { + apiPost.mockReset(); + apiPost.mockResolvedValue({ + success: true, + data: { + two_factor_required: true, + challenge_id: '11111111-2222-3333-4444-555555555555', + provider_id: 'g7:core.mail', + expires_at: '2026-09-07T14:03:00+09:00', + }, + }); + + const t = createLayoutTest(layout, { componentRegistry: setupRegistry() }); + await t.render(); + + // 이 레이아웃이 실제로 선언한 login 액션 — 자격 증명만 리터럴로 채운다. + const submitSequence = (loginForm as any).actions[0]; + const loginAction = submitSequence.actions.find( + (a: any) => a.handler === 'login' + ); + expect(loginAction, 'login 액션이 제출 시퀀스에서 사라졌습니다').toBeDefined(); + + await t.triggerAction({ + ...loginAction, + if: undefined, + params: { body: { email: 'user@example.com', password: 'pw' } }, + }); + + // 코어가 challenge 응답에서 던지면(=#133) 여기까지 오지 못한다. + expect(apiPost).toHaveBeenCalled(); + const twoFactor = t.getState()._global?.twoFactor; + expect(twoFactor?.required, 'challenge 응답인데 2단계 상태가 서지 않았습니다').toBe(true); + expect(twoFactor?.challenge_id).toBe('11111111-2222-3333-4444-555555555555'); + expect(twoFactor?.code).toBe(''); + + await t.rerender(); + + expect(document.querySelector('input[name="two_factor_code"]')).not.toBeNull(); + expect(document.querySelector('input[name="email"]')).toBeNull(); + + const body = document.body.textContent ?? ''; + expect(body).not.toContain('Cannot read properties of undefined'); + + t.cleanup(); + }); + + /** + * 응답이 아예 없었던 실패(네트워크 끊김)는 HTTP 오류가 아니다. 그 자리에서 axios 오류 원문이나 + * 내부 식별 문구(`Failed to execute action: login`)가 새면 오류 박스에 영문이 그대로 실린다. + * 로그인 화면의 오류 문구는 사용자가 읽는 유일한 안내라 그 판정이 곧 화면 품질이다. + * + * @scenario step=credentials, response=network, action=submit + * + * @effects network_message_translated + */ + it('네트워크 실패는 영문 원문이 아니라 다국어 안내로 오류 박스에 실린다', async () => { + apiPost.mockReset(); + apiPost.mockRejectedValue(Object.assign(new Error('Network Error'), { + code: 'ERR_NETWORK', + })); + + const t = createLayoutTest(layout, { componentRegistry: setupRegistry() }); + await t.render(); + + const loginAction = (loginForm as any).actions[0].actions.find( + (a: any) => a.handler === 'login' + ); + + await t.triggerAction({ + ...loginAction, + if: undefined, + params: { body: { email: 'user@example.com', password: 'pw' } }, + }); + + const loginError = String(t.getState()._global?.loginError ?? ''); + + // 다국어 안내로 해석되는 값이어야 한다 — 번역기가 붙지 않은 하네스에서는 그 키가 남는다. + expect(loginError).toContain('core.errors.network_request_failed'); + // 판정이 무너지면 이 두 형태 중 하나가 그대로 화면에 실린다. + expect(loginError).not.toContain('Failed to execute action'); + expect(loginError).not.toBe('Network Error'); + + t.cleanup(); + }); +}); diff --git a/templates/_bundled/sirsoft-basic/__tests__/layouts/login-two-factor-step.test.tsx b/templates/_bundled/sirsoft-basic/__tests__/layouts/login-two-factor-step.test.tsx new file mode 100644 index 00000000..37628ac7 --- /dev/null +++ b/templates/_bundled/sirsoft-basic/__tests__/layouts/login-two-factor-step.test.tsx @@ -0,0 +1,211 @@ +/** + * @file login-two-factor-step.test.tsx + * @description 로그인 2단계 인증 단계 구조 회귀 테스트 (sirsoft-basic) + * + * 2단계 인증이 켜진 사이트에서 서버는 로그인에 **두 가지 형태의 200** 을 돌려준다. + * 화면이 한 형태만 가정하면 인증번호 요구 응답에서 영문 오류가 노출되고 로그인이 + * 불가능해진다(공개 #133). + * + * 검증 대상: + * 1. 1단계·2단계 블록의 `if` 가 상보적이다 (동시 노출 금지) + * 2. 인증번호 입력이 controlled — `value` + `onChange` 쌍, `events:{}` 래퍼 없음 + * 3. `login` 과 `loginTwoFactor` 가 상호배타 `if` 를 갖는다 + * 4. 재발송 onSuccess 가 새 challenge 로 교체하고 입력값을 비운다 + * 5. `login` 의 성공 후속 액션이 challenge 응답에서는 실행되지 않는다 + * 6. 로그인 화면 진입 시 2단계 상태가 초기화된다 + * + * @since engine-v1.65.0 + */ + +import { describe, it, expect } from 'vitest'; + +import loginForm from '../../layouts/partials/auth/_login_form.json'; +import loginPage from '../../layouts/auth/login.json'; + +type Action = { + event?: string; + type?: string; + handler?: string; + target?: string; + if?: string; + params?: Record; + actions?: Action[]; + onSuccess?: Action[]; + onError?: Action[]; +}; + +type Node = { + id?: string; + name?: string; + if?: string; + props?: Record; + children?: Node[] | string; + text?: string; + events?: Record; + actions?: Action[]; +}; + +/** 트리를 평탄화한다. */ +function flatten(node: Node | Node[] | undefined): Node[] { + if (!node) return []; + if (Array.isArray(node)) return node.flatMap(flatten); + const children = Array.isArray(node.children) ? node.children.flatMap(flatten) : []; + return [node, ...children]; +} + +/** 시퀀스를 평탄화해 모든 액션을 모은다. */ +function flattenActions(actions: Action[] | undefined): Action[] { + if (!actions) return []; + return actions.flatMap((a) => [ + a, + ...flattenActions(a.actions), + ...flattenActions(a.onSuccess), + ...flattenActions(a.onError), + ]); +} + +const nodes = flatten(loginForm as unknown as Node); +const allActions = flattenActions((loginForm as unknown as Node).actions); + +describe('sirsoft-basic 로그인 2단계 인증 단계', () => { + /** + * @scenario step=code, response=challenge, action=submit + * + * @effects code_step_rendered_on_challenge, credential_step_hidden_on_challenge + */ + it('1단계 입력과 2단계 블록의 조건이 상보적이다', () => { + // 두 블록이 같은 조건을 쓰면 인증번호 단계에서 이메일·비밀번호가 함께 보인다. + const stepOne = nodes.filter((n) => n.if === '{{!_global.twoFactor?.required}}'); + const stepTwo = nodes.filter((n) => n.if === '{{_global.twoFactor?.required}}'); + + expect(stepOne.length).toBeGreaterThanOrEqual(3); + expect(stepTwo.length).toBe(1); + }); + + /** + * @scenario step=code, response=challenge, action=submit + * + * @effects code_input_is_controlled_without_events_wrapper + */ + it('인증번호 입력이 controlled 이고 events 래퍼를 쓰지 않는다', () => { + const codeInput = nodes.find((n) => n.props?.name === 'two_factor_code'); + + expect(codeInput).toBeDefined(); + expect(codeInput?.props?.value).toBe("{{_global.twoFactor?.code ?? ''}}"); + expect(codeInput?.props?.autoComplete).toBe('one-time-code'); + expect(codeInput?.props?.inputMode).toBe('numeric'); + // 값의 소유자가 상태여야 재발송 시 입력값을 비울 수 있다. + expect(codeInput?.events).toBeUndefined(); + + const onChange = codeInput?.actions?.find((a) => a.event === 'onChange'); + expect(onChange?.handler).toBe('setState'); + expect(onChange?.params?.['twoFactor.code']).toBe('{{$event.target.value}}'); + }); + + /** + * @scenario step=code, response=ok, action=enter_key + * + * @effects login_and_verify_are_mutually_exclusive + */ + it('login 과 loginTwoFactor 가 상호배타 조건을 갖는다', () => { + const login = allActions.find((a) => a.handler === 'login'); + const verify = allActions.find((a) => a.handler === 'loginTwoFactor'); + + expect(login?.if).toBe('{{!_global.twoFactor?.required}}'); + expect(verify?.if).toBe('{{_global.twoFactor?.required}}'); + // 조건이 빠지면 인증번호 단계에서 Enter 를 누를 때 새 challenge 가 발급된다. + expect(login?.target).toBe('user'); + expect(verify?.target).toBe('user'); + }); + + /** + * @scenario step=code, response=ok, action=submit + * + * @effects no_raw_typeerror_text + */ + it('loginTwoFactor 가 challenge_id 와 code 를 함께 보낸다', () => { + const verify = allActions.find((a) => a.handler === 'loginTwoFactor'); + + expect(verify?.params?.body?.challenge_id).toBe('{{_global.twoFactor?.challenge_id}}'); + expect(verify?.params?.body?.code).toBe('{{_global.twoFactor?.code}}'); + }); + + /** + * @scenario step=credentials, response=challenge, action=submit + * + * @effects credential_step_hidden_on_challenge + */ + it('login 의 성공 후속 액션이 challenge 응답에서는 실행되지 않는다', () => { + const login = allActions.find((a) => a.handler === 'login'); + const onSuccess = login?.onSuccess ?? []; + + expect(onSuccess.length).toBeGreaterThan(0); + for (const action of onSuccess) { + // 조건이 없는 후속 액션이 하나라도 남으면 인증 전에 홈으로 이동하거나 + // 빈 사용자 정보가 전역 상태에 실린다. + expect(action.if, `후속 액션 ${action.handler} 에 조건이 없습니다`).toBeDefined(); + } + + const challengeBranch = onSuccess.find((a) => a.if === '{{response.two_factor_required}}'); + expect(challengeBranch?.params?.twoFactor?.challenge_id).toBe('{{response.challenge_id}}'); + // 같은 시퀀스 안에서는 _global 이 아직 갱신 전이므로 응답 값만 읽어야 한다. + expect(JSON.stringify(challengeBranch?.params)).not.toContain('_global.twoFactor'); + }); + + /** + * @scenario step=code, response=ok, action=resend + * + * @effects resend_clears_code_and_replaces_challenge + */ + it('재발송 성공 시 새 challenge 로 교체하고 입력값을 비운다', () => { + const resend = allActions.find((a) => a.handler === 'loginTwoFactorResend'); + expect(resend).toBeUndefined(); + + // 재발송은 버튼 노드의 액션에 있다 — 폼 submit 시퀀스가 아니다. + const resendButton = nodes.find((n) => + flattenActions(n.actions).some((a) => a.handler === 'loginTwoFactorResend') + ); + const resendAction = flattenActions(resendButton?.actions).find( + (a) => a.handler === 'loginTwoFactorResend' + ); + + expect(resendAction?.target).toBe('user'); + expect(resendAction?.params?.body?.challenge_id).toBe('{{_global.twoFactor?.challenge_id}}'); + + const success = resendAction?.onSuccess?.[0]; + expect(success?.params?.twoFactor?.challenge_id).toBe('{{response.challenge_id}}'); + // 이전 코드가 남아 있으면 새 코드를 받았는데 옛 코드로 제출된다. + expect(success?.params?.twoFactor?.code).toBe(''); + expect(success?.params?.twoFactor?.resent).toBe(true); + }); + + /** + * @scenario step=credentials, response=ok, action=restart + * + * @effects init_actions_reset_two_factor_state, restart_resets_to_credential_step + */ + it('로그인 화면 진입 시 2단계 상태를 초기화한다', () => { + // 전역 상태라 화면을 떠나도 남는다 — 리셋이 없으면 다시 들어왔을 때 1단계가 보이지 않는다. + const init = (loginPage as any).init_actions as Action[]; + const reset = init.find( + (a) => a.handler === 'setState' && a.params?.target === 'global' && 'twoFactor' in (a.params ?? {}) + ); + + expect(reset).toBeDefined(); + expect(reset?.params?.twoFactor).toBeNull(); + expect(reset?.if).toBeUndefined(); + }); + + /** + * @scenario step=credentials, response=423, action=submit + * + * @effects locked_until_rendered + */ + it('계정 잠금 해제 시각을 렌더하는 지점이 있다', () => { + const lockedUntil = nodes.find((n) => n.if === '{{_global.loginErrors?.locked_until}}'); + const permanent = nodes.find((n) => n.if === '{{_global.loginErrors?.permanent === true}}'); + + expect(lockedUntil?.text).toContain('$t:auth.locked_until'); + expect(permanent?.text).toBe('$t:auth.locked_permanent'); + }); +}); diff --git a/templates/_bundled/sirsoft-basic/dist/js/components.iife.js b/templates/_bundled/sirsoft-basic/dist/js/components.iife.js index 4665837f..7c5f9dfd 100644 --- a/templates/_bundled/sirsoft-basic/dist/js/components.iife.js +++ b/templates/_bundled/sirsoft-basic/dist/js/components.iife.js @@ -1,4 +1,4 @@ -var SirsoftBasic=(function(ee,d,C,tr){"use strict";function bd(e){const t=Object.create(null,{[Symbol.toStringTag]:{value:"Module"}});if(e){for(const r in e)if(r!=="default"){const n=Object.getOwnPropertyDescriptor(e,r);Object.defineProperty(t,r,n.get?n:{enumerable:!0,get:()=>e[r]})}}return t.default=e,Object.freeze(t)}const A=bd(C),yd=()=>window.G7Core,R=C.forwardRef(({children:e,variant:t="primary",size:r="md",className:n="",...o},a)=>{const s="inline-flex items-center justify-center",l=yd()?.style?.mergeClasses?.(s,n)??`${s} ${n}`;return d.jsx("button",{ref:a,className:l,...o,children:e})});R.displayName="Button";const j=C.forwardRef(({children:e,className:t="",...r},n)=>d.jsx("div",{ref:n,className:t,...r,children:e})),te=({children:e,className:t="",...r})=>d.jsx("span",{className:t,...r,children:e}),En=({children:e,className:t="",...r})=>d.jsx("svg",{className:t,...r,children:e}),At=C.forwardRef(({label:e,error:t,className:r="",onChange:n,onKeyPress:o,value:a,defaultValue:s,type:l="text",checked:f,defaultChecked:h,...c},i)=>{const u=l==="radio"||l==="checkbox",g=C.useRef(!1),p=C.useRef(null);C.useImperativeHandle(i,()=>p.current);const[m,x]=C.useState(a??s??"");C.useEffect(()=>{!u&&!g.current&&a!==void 0&&x(a)},[a,u]);const k=C.useCallback(()=>{g.current=!0},[]),M=C.useCallback(y=>{if(g.current=!1,n){const E=y.target.value;x(E);const O=new Event("change",{bubbles:!0});Object.defineProperty(O,"target",{writable:!1,value:{value:E}}),Object.defineProperty(O,"currentTarget",{writable:!1,value:{value:E}}),n(O)}},[n]),v=C.useCallback(y=>{const E=y.target.value;x(E),!g.current&&n&&n(y)},[n]),T=C.useCallback(y=>{g.current||o&&o(y)},[o]),{loadingactions:I,formdata:S,...N}=c;if(u)return d.jsx("input",{ref:p,type:l,className:r,onChange:n,checked:f,defaultChecked:h,value:a,...N});const w=a!==void 0?{value:m}:{defaultValue:s};return d.jsx("input",{ref:p,type:l,className:r,onChange:v,onKeyPress:T,onCompositionStart:k,onCompositionEnd:M,...w,...N})});At.displayName="Input";const ds=C.forwardRef(({accept:e,maxSize:t,onChange:r,onError:n,buttonText:o="Browse",placeholder:a="No file selected",className:s="",disabled:l,...f},h)=>{const c=C.useRef(null),i=h||c,[u,g]=C.useState(""),p={},m={};Object.entries(f).forEach(([v,T])=>{v.startsWith("data-editor-")||v==="id"||v==="onMouseMove"||v==="onMouseLeave"?p[v]=T:m[v]=T});const x=C.useCallback(()=>{!l&&i.current&&i.current.click()},[l,i]),k=C.useCallback(v=>{const T=v.target.files?.[0]||null;if(!T){g(""),r?.(null);return}if(t&&T.size>t*1024*1024){const I=`File size exceeds ${t}MB limit`;n?.(I),i.current&&(i.current.value=""),g("");return}g(T.name),r?.(T)},[t,r,n,i]),M=C.useCallback(v=>{v.stopPropagation(),i.current&&(i.current.value=""),g(""),r?.(null)},[r,i]);return d.jsxs(j,{className:`flex items-center gap-2 ${s}`,...p,children:[d.jsx(At,{ref:i,type:"file",accept:e,onChange:k,disabled:l,className:"hidden",...m}),d.jsx(R,{type:"button",onClick:x,disabled:l,className:"px-4 py-2 bg-gray-100 dark:bg-gray-700 border border-gray-300 dark:border-gray-600 rounded-md text-sm font-medium text-gray-700 dark:text-gray-200 hover:bg-gray-200 dark:hover:bg-gray-600 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 disabled:opacity-50 disabled:cursor-not-allowed",children:o}),d.jsx(te,{className:"flex-1 text-sm text-gray-600 dark:text-gray-400 truncate",children:u||a}),u&&!l&&d.jsx(R,{type:"button",onClick:M,className:"p-1 text-gray-400 hover:text-gray-600 dark:hover:text-gray-200","aria-label":"Clear file",children:d.jsx(En,{className:"w-4 h-4",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:d.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M6 18L18 6M6 6l12 12"})})})]})});ds.displayName="FileInput";var nt=(e=>(e.User="fa-user",e.Users="fa-users",e.UserCircle="fa-user-circle",e.UserPlus="fa-user-plus",e.UserMinus="fa-user-minus",e.UserShield="fa-user-shield",e.Search="fa-search",e.Edit="fa-edit",e.Pencil="fa-pencil-alt",e.Trash="fa-trash",e.Save="fa-save",e.Plus="fa-plus",e.Minus="fa-minus",e.Check="fa-check",e.Times="fa-times",e.Download="fa-download",e.Upload="fa-upload",e.Copy="fa-copy",e.ExternalLink="fa-external-link-alt",e.Home="fa-home",e.ChevronLeft="fa-chevron-left",e.ChevronRight="fa-chevron-right",e.ChevronUp="fa-chevron-up",e.ChevronDown="fa-chevron-down",e.AngleLeft="fa-angle-left",e.AngleRight="fa-angle-right",e.AngleUp="fa-angle-up",e.AngleDown="fa-angle-down",e.ArrowUp="fa-arrow-up",e.ArrowDown="fa-arrow-down",e.ArrowLeft="fa-arrow-left",e.ArrowRight="fa-arrow-right",e.Bars="fa-bars",e.EllipsisVertical="fa-ellipsis-v",e.EllipsisHorizontal="fa-ellipsis-h",e.CheckCircle="fa-check-circle",e.TimesCircle="fa-times-circle",e.ExclamationCircle="fa-exclamation-circle",e.ExclamationTriangle="fa-exclamation-triangle",e.InfoCircle="fa-info-circle",e.QuestionCircle="fa-question-circle",e.XCircle="fa-times-circle",e.ShoppingCart="fa-shopping-cart",e.ShoppingBag="fa-shopping-bag",e.CreditCard="fa-credit-card",e.Tag="fa-tag",e.Tags="fa-tags",e.File="fa-file",e.FileAlt="fa-file-alt",e.Folder="fa-folder",e.FolderOpen="fa-folder-open",e.Envelope="fa-envelope",e.Phone="fa-phone",e.Comment="fa-comment",e.Comments="fa-comments",e.Cog="fa-cog",e.Cogs="fa-cogs",e.Settings="fa-cog",e.Wrench="fa-wrench",e.Sliders="fa-sliders-h",e.Image="fa-image",e.Images="fa-images",e.Video="fa-video",e.Camera="fa-camera",e.Heart="fa-heart",e.Star="fa-star",e.ThumbsUp="fa-thumbs-up",e.ThumbsDown="fa-thumbs-down",e.Share="fa-share",e.Clock="fa-clock",e.Calendar="fa-calendar",e.CalendarAlt="fa-calendar-alt",e.Lock="fa-lock",e.Unlock="fa-unlock",e.Eye="fa-eye",e.EyeSlash="fa-eye-slash",e.Bell="fa-bell",e.Flag="fa-flag",e.Map="fa-map",e.MapMarker="fa-map-marker-alt",e.Globe="fa-globe",e.Link="fa-link",e.Unlink="fa-unlink",e.Sun="fa-sun",e.Moon="fa-moon",e.ChartBar="fa-chart-bar",e.ChartLine="fa-chart-line",e.ChartPie="fa-chart-pie",e.ChartArea="fa-chart-area",e.Spinner="fa-spinner",e.CircleNotch="fa-circle-notch",e.CheckBadge="fa-badge-check",e.BellSlash="fa-bell-slash",e.BookOpen="fa-book-open",e.Building="fa-building",e.FileCircleCheck="fa-file-circle-check",e.WandMagicSparkles="fa-wand-magic-sparkles",e.UserXmark="fa-user-xmark",e.Cube="fa-cube",e.Cubes="fa-cubes",e.Plug="fa-plug",e.PuzzlePiece="fa-puzzle-piece",e.Fire="fa-fire",e.CommentDots="fa-comment-dots",e.DollarSign="fa-dollar-sign",e.Bars2="fa-bars",e.X="fa-times",e.List="fa-list",e.ThLarge="fa-th-large",e.Inbox="fa-inbox",e.Box="fa-box",e.PaperPlane="fa-paper-plane",e.FileText="fa-file-alt",e.Paperclip="fa-paperclip",e.Truck="fa-truck",e.Sync="fa-sync",e.RotateLeft="fa-rotate-left",e.MapPin="fa-map-marker-alt",e.HelpCircle="fa-question-circle",e))(nt||{});const vd={user:"fa-user",users:"fa-users","user-circle":"fa-user-circle","user-plus":"fa-user-plus","user-minus":"fa-user-minus","user-shield":"fa-user-shield",search:"fa-search",edit:"fa-edit",pencil:"fa-pencil-alt",trash:"fa-trash",save:"fa-save",plus:"fa-plus",minus:"fa-minus",check:"fa-check",times:"fa-times",download:"fa-download",upload:"fa-upload",copy:"fa-copy","external-link":"fa-external-link-alt",home:"fa-home","chevron-left":"fa-chevron-left","chevron-right":"fa-chevron-right","chevron-up":"fa-chevron-up","chevron-down":"fa-chevron-down","angle-left":"fa-angle-left","angle-right":"fa-angle-right","angle-up":"fa-angle-up","angle-down":"fa-angle-down","arrow-up":"fa-arrow-up","arrow-down":"fa-arrow-down","arrow-left":"fa-arrow-left","arrow-right":"fa-arrow-right",bars:"fa-bars","ellipsis-vertical":"fa-ellipsis-v","ellipsis-v":"fa-ellipsis-v","ellipsis-horizontal":"fa-ellipsis-h","ellipsis-h":"fa-ellipsis-h","check-circle":"fa-check-circle","times-circle":"fa-times-circle","x-circle":"fa-times-circle","exclamation-circle":"fa-exclamation-circle","exclamation-triangle":"fa-exclamation-triangle","info-circle":"fa-info-circle","question-circle":"fa-question-circle","shopping-cart":"fa-shopping-cart","shopping-bag":"fa-shopping-bag","credit-card":"fa-credit-card",tag:"fa-tag",tags:"fa-tags",file:"fa-file","file-alt":"fa-file-alt",folder:"fa-folder","folder-open":"fa-folder-open",envelope:"fa-envelope",phone:"fa-phone",comment:"fa-comment",comments:"fa-comments",cog:"fa-cog",cogs:"fa-cogs",settings:"fa-cog",wrench:"fa-wrench",sliders:"fa-sliders-h",image:"fa-image",images:"fa-images",video:"fa-video",camera:"fa-camera",heart:"fa-heart",star:"fa-star","thumbs-up":"fa-thumbs-up","thumbs-down":"fa-thumbs-down",share:"fa-share",clock:"fa-clock",calendar:"fa-calendar","calendar-alt":"fa-calendar-alt",lock:"fa-lock",unlock:"fa-unlock",eye:"fa-eye","eye-slash":"fa-eye-slash",bell:"fa-bell",flag:"fa-flag",map:"fa-map","map-marker":"fa-map-marker-alt",globe:"fa-globe",link:"fa-link",unlink:"fa-unlink",sun:"fa-sun",moon:"fa-moon","chart-bar":"fa-chart-bar","chart-line":"fa-chart-line","chart-pie":"fa-chart-pie","chart-area":"fa-chart-area",spinner:"fa-spinner","circle-notch":"fa-circle-notch","check-badge":"fa-badge-check","badge-check":"fa-badge-check",cube:"fa-cube",cubes:"fa-cubes",plug:"fa-plug","puzzle-piece":"fa-puzzle-piece",flame:"fa-fire",fire:"fa-fire","message-square":"fa-comment-dots","dollar-sign":"fa-dollar-sign",menu:"fa-bars",x:"fa-times",list:"fa-list","layout-grid":"fa-th-large","th-large":"fa-th-large",grid:"fa-th-large",inbox:"fa-inbox",package:"fa-box",box:"fa-box",send:"fa-paper-plane","paper-plane":"fa-paper-plane","file-text":"fa-file-alt",paperclip:"fa-paperclip",truck:"fa-truck","refresh-cw":"fa-sync",sync:"fa-sync","undo-2":"fa-rotate-left","rotate-left":"fa-rotate-left","map-pin":"fa-map-marker-alt","help-circle":"fa-question-circle","alert-triangle":"fa-exclamation-triangle","bell-off":"fa-bell-slash","book-open":"fa-book-open",building:"fa-building","edit-2":"fa-edit","edit-3":"fa-edit","file-check":"fa-file-circle-check",mail:"fa-envelope",sparkles:"fa-wand-magic-sparkles","trash-2":"fa-trash","user-x":"fa-user-xmark"},fe=({name:e,iconStyle:t="solid",size:r,color:n,spin:o=!1,pulse:a=!1,fixedWidth:s=!1,ariaLabel:l,className:f="",style:h,...c})=>{const i={solid:"fas",regular:"far",light:"fal",duotone:"fad",brands:"fab"},u=typeof e=="string"?e:String(e),g=vd[u],p=g?String(g).replace(/^fa-/,""):u.replace(/^fa-/,""),m=[i[t],`fa-${p}`,r&&`fa-${r}`,o&&"fa-spin",a&&"fa-pulse",s&&"fa-fw",n,f].filter(Boolean).join(" "),x=l||p.replace(/-/g," ");return d.jsx("i",{className:m,style:h,"aria-label":x,role:"img",...c})},Tr=(e,t)=>window.G7Core?.t?.(e,t)??e,ko={minLength:{key:"minLength",labelKey:"auth.password_input.rules.minLength",defaultParam:6,validate:(e,t=6)=>e.length>=t},maxLength:{key:"maxLength",labelKey:"auth.password_input.rules.maxLength",defaultParam:20,validate:(e,t=20)=>e.length<=t},hasUppercase:{key:"hasUppercase",labelKey:"auth.password_input.rules.hasUppercase",validate:e=>/[A-Z]/.test(e)},hasLowercase:{key:"hasLowercase",labelKey:"auth.password_input.rules.hasLowercase",validate:e=>/[a-z]/.test(e)},hasNumber:{key:"hasNumber",labelKey:"auth.password_input.rules.hasNumber",validate:e=>/[0-9]/.test(e)},hasSpecial:{key:"hasSpecial",labelKey:"auth.password_input.rules.hasSpecial",validate:e=>/[!@#$%^&*(),.?":{}|<>]/.test(e)},noSpaces:{key:"noSpaces",labelKey:"auth.password_input.rules.noSpaces",validate:e=>!/\s/.test(e)},minTypes:{key:"minTypes",labelKey:"auth.password_input.rules.minTypes",defaultParam:3,validate:(e,t=3)=>{let r=0;return/[A-Z]/.test(e)&&r++,/[a-z]/.test(e)&&r++,/[0-9]/.test(e)&&r++,/[!@#$%^&*(),.?":{}|<>]/.test(e)&&r++,r>=t}}},us=[ko.minLength];function wd(e){const[t,r]=e.split(":"),n=ko[t];if(!n)return null;const o=r?parseInt(r,10):n.defaultParam;return{rule:n,param:isNaN(o)?n.defaultParam:o}}const fs=C.forwardRef(({label:e,error:t,showToggle:r=!0,showValidation:n=!1,isConfirmField:o=!1,confirmTarget:a,onMatchChange:s,onValidityChange:l,rules:f,enableRules:h,showRules:c,wrapperClassName:i="",validationClassName:u="",className:g="",onChange:p,value:m,defaultValue:x,disabled:k,...M},v)=>{const T=C.useRef(null);C.useImperativeHandle(v,()=>T.current);const[I,S]=C.useState(!1),[N,w]=C.useState(!1),[y,E]=C.useState(m??x??"");C.useEffect(()=>{m!==void 0&&E(m)},[m]);const O=C.useMemo(()=>f&&f.length>0?f.map(H=>({rule:H,param:H.defaultParam})):h&&h.length>0?h.map(H=>wd(H)).filter(H=>H!==null):us.map(H=>({rule:H,param:H.defaultParam})),[f,h]),$=C.useMemo(()=>!c||c.length===0?O:O.filter(H=>c.includes(H.rule.key)),[O,c]),P=C.useMemo(()=>!n||o?{}:$.reduce((H,{rule:W,param:Q})=>(H[W.key]=W.validate(y,Q),H),{}),[y,$,n,o]),L=C.useMemo(()=>!o||a===void 0?!0:y===a&&y.length>0,[o,y,a]),U=C.useMemo(()=>n?Object.values(P).every(H=>H):!0,[P,n]);C.useEffect(()=>{if(l&&n&&!o){const H=Object.entries(P).filter(([,W])=>!W).map(([W])=>W);l(U,H)}},[U,P,l,n,o]),C.useEffect(()=>{s&&o&&s(L)},[L,s,o]);const _=C.useCallback(()=>{S(H=>!H)},[]),b=C.useCallback(H=>{const W=H.target.value;E(W),p&&p(H)},[p]),z=C.useCallback(H=>{w(H.getModifierState("CapsLock"))},[]),{loadingactions:F,formdata:G,...X}=M,B={},Y={};for(const[H,W]of Object.entries(X))H.startsWith("data-editor-")||H==="onMouseMove"||H==="onMouseLeave"?B[H]=W:Y[H]=W;return d.jsxs("div",{className:`relative ${i}`,...B,children:[d.jsxs("div",{className:"relative",children:[d.jsx("input",{ref:T,type:I?"text":"password",className:`${g} ${r?"pr-10":""}`,value:m!==void 0?m:void 0,defaultValue:m===void 0?x:void 0,onChange:b,onKeyDown:z,onKeyUp:z,disabled:k,...Y}),r&&d.jsx("button",{type:"button",onClick:_,disabled:k,className:"absolute right-2 top-1/2 -translate-y-1/2 p-1 text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200 disabled:opacity-50 disabled:cursor-not-allowed dark:disabled:opacity-50","aria-label":Tr(I?"auth.password_input.hide":"auth.password_input.show"),children:d.jsx(fe,{name:I?"eye-slash":"eye",className:"w-5 h-5"})})]}),N&&!I&&d.jsxs("div",{className:"mt-2 flex items-center gap-1.5 text-sm text-yellow-600 dark:text-yellow-400",children:[d.jsx(fe,{name:"triangle-exclamation",className:"w-4 h-4"}),d.jsx("span",{children:Tr("auth.password_input.caps_lock_on")})]}),o&&a!==void 0&&y.length>0&&d.jsxs("div",{className:`mt-2 flex items-center gap-1.5 text-sm ${L?"text-green-600 dark:text-green-400":"text-red-600 dark:text-red-400"}`,children:[d.jsx(fe,{name:L?"check-circle":"xmark-circle",className:"w-4 h-4"}),d.jsx("span",{children:Tr(L?"auth.password_input.match":"auth.password_input.mismatch")})]}),n&&!o&&y.length>0&&d.jsx("div",{className:`mt-2 space-y-1 ${u}`,children:$.map(({rule:H,param:W})=>{const Q=P[H.key],be=W!==void 0?{count:W}:void 0;return d.jsxs("div",{className:`flex items-center gap-1.5 text-sm ${Q?"text-green-600 dark:text-green-400":"text-gray-500 dark:text-gray-400"}`,children:[d.jsx(fe,{name:Q?"check-circle":"circle",className:"w-4 h-4"}),d.jsx("span",{children:Tr(H.labelKey,be)})]},H.key)})}),t&&d.jsx("p",{className:"mt-1 text-sm text-red-600 dark:text-red-400",children:t})]})});fs.displayName="PasswordInput";const Co=({label:e,error:t,className:r="",onChange:n,value:o,defaultValue:a,...s})=>{const l=C.useRef(!1),f=C.useRef(null),[h,c]=C.useState(o??a??"");C.useEffect(()=>{!l.current&&o!==void 0&&c(o)},[o]);const i=C.useCallback(()=>{l.current=!0},[]),u=C.useCallback(M=>{if(l.current=!1,n){const v=M.target.value;c(v);const T=new Event("change",{bubbles:!0});Object.defineProperty(T,"target",{writable:!1,value:{value:v}}),Object.defineProperty(T,"currentTarget",{writable:!1,value:{value:v}}),n(T)}},[n]),g=C.useCallback(M=>{const v=M.target.value;c(v),!l.current&&n&&n(M)},[n]),{loadingactions:p,formdata:m,...x}=s,k=o!==void 0?{value:h}:{defaultValue:a};return d.jsx("textarea",{ref:f,className:r,onChange:g,onCompositionStart:i,onCompositionEnd:u,...k,...x})},Ar=({children:e,className:t="",...r})=>d.jsx("label",{className:t,...r,children:e}),Gt=({children:e,className:t="",...r})=>d.jsx("p",{className:t,...r,children:e}),jt=({className:e="",alt:t="",...r})=>d.jsx("img",{className:e,alt:t,...r}),xd=({children:e,className:t="",...r})=>d.jsx("h1",{className:t,...r,children:e}),gs=({children:e,className:t="",...r})=>d.jsx("h2",{className:t,...r,children:e}),Tn=({children:e,className:t="",...r})=>d.jsx("h3",{className:t,...r,children:e}),hs=({children:e,className:t="",...r})=>d.jsx("h4",{className:t,...r,children:e}),_o=({children:e,className:t="",...r})=>d.jsx("ul",{className:t,...r,children:e}),kd=({children:e,className:t="",...r})=>d.jsx("ol",{className:t,...r,children:e}),er=({children:e,className:t="",...r})=>d.jsx("li",{className:t,...r,children:e}),So=({children:e,className:t="",...r})=>d.jsx("a",{className:t,...r,children:e}),Eo=({children:e,className:t="",...r})=>d.jsx("form",{className:t,...r,children:e}),Cd=window.G7Core?.createLogger?.("Comp:Select")??{log:(...e)=>console.log("[Comp:Select]",...e),warn:(...e)=>console.warn("[Comp:Select]",...e),error:(...e)=>console.error("[Comp:Select]",...e)};function _d(e){const t=window.G7Core?.state?.get?.("_global.appConfig.localeNames")?.[e];return t||{ko:"한국어",en:"English",ja:"日本語",zh:"中文",es:"Español",fr:"Français",de:"Deutsch"}[e]||e}const ps=({children:e,label:t,error:r,options:n,className:o="",value:a,onChange:s,disabled:l,searchable:f=!1,searchPlaceholder:h,editorAttrs:c,...i})=>{const[u,g]=C.useState(!1),[p,m]=C.useState(""),[x,k]=C.useState(null),M=C.useRef(null),v=C.useRef(null),T=C.useRef(null),I=C.useRef(null),S=C.useMemo(()=>n?Array.isArray(n)?n.length===0?[]:typeof n[0]=="string"?n.map(_=>({value:_,label:_d(_)})):n:(Cd.warn("options prop is not an array:",n),null):null,[n]),N=C.useMemo(()=>S&&S.find(b=>String(b.value)===String(a))?.label||"",[S,a]),w=C.useMemo(()=>{if(!S)return null;if(!f||p.trim()==="")return S;const _=p.trim().toLowerCase();return S.filter(b=>b.label.toLowerCase().includes(_)||String(b.value).toLowerCase().includes(_))},[S,f,p]);C.useEffect(()=>{if(u&&f){m("");const _=setTimeout(()=>T.current?.focus(),0);return()=>clearTimeout(_)}u||m("")},[u,f]);const y=C.useCallback(()=>{if(!v.current)return;const _=v.current.getBoundingClientRect(),b=window.innerHeight-_.bottom,F=b<280&&_.top>b;k(F?{bottom:window.innerHeight-_.top+4,left:_.left,width:_.width}:{top:_.bottom+4,left:_.left,width:_.width})},[]);C.useEffect(()=>{if(!u){k(null);return}return y(),window.addEventListener("scroll",y,!0),window.addEventListener("resize",y),()=>{window.removeEventListener("scroll",y,!0),window.removeEventListener("resize",y)}},[u,y]),C.useEffect(()=>{const _=b=>{const z=b.target;M.current&&!M.current.contains(z)&&(!I.current||!I.current.contains(z))&&g(!1)};return u&&document.addEventListener("mousedown",_),()=>{document.removeEventListener("mousedown",_)}},[u]),C.useEffect(()=>{const _=b=>{b.key==="Escape"&&u&&(g(!1),v.current?.focus())};return document.addEventListener("keydown",_),()=>document.removeEventListener("keydown",_)},[u]);const E=()=>{l||g(!u)},O=_=>{s&&s({target:{value:_},preventDefault:()=>{},stopPropagation:()=>{},type:"change"}),g(!1),v.current?.focus()};if(!S)return d.jsx("select",{className:o,value:a,onChange:s,disabled:l,...c,...i,children:e});const $=o&&o.includes("bg-"),P=o&&/text-(gray|red|blue|green|yellow|white|black|indigo|purple|pink|orange|amber|emerald|teal|cyan|slate)-\d+|text-white|text-black/.test(o),L=$?`${o}${P?"":" text-gray-700 dark:text-gray-200"}`:`w-full px-4 py-2.5 bg-gray-100 dark:bg-gray-700 border-0 rounded-xl text-gray-700 dark:text-gray-200 font-medium focus:ring-2 focus:ring-blue-500 focus:outline-none ${o}`,U={...c,...i};return d.jsxs(j,{ref:M,className:"relative",...U,children:[d.jsxs(R,{ref:v,type:"button",onClick:E,disabled:l,className:`${L} flex items-center justify-between gap-2 text-left cursor-pointer disabled:opacity-50 disabled:cursor-not-allowed`,"aria-haspopup":"listbox","aria-expanded":u,children:[d.jsx(te,{className:"truncate",children:N||" "}),d.jsx(En,{className:`w-4 h-4 flex-shrink-0 transition-transform duration-200 ${u?"rotate-180":""}`,fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:d.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M19 9l-7 7-7-7"})})]}),u&&x&&tr.createPortal(d.jsxs(j,{ref:I,className:"fixed z-[9999] bg-white dark:bg-gray-800 rounded-2xl shadow-lg border border-gray-200 dark:border-gray-600 overflow-hidden",style:{top:x.top,bottom:x.bottom,left:x.left,width:x.width},role:"listbox",children:[f&&d.jsx(j,{className:"p-2 border-b border-gray-200 dark:border-gray-600",children:d.jsx(At,{ref:T,type:"text",value:p,onChange:_=>m(_.target.value),placeholder:h??"Search...",className:"w-full px-3 py-2 bg-gray-100 dark:bg-gray-700 border-0 rounded-lg text-sm text-gray-700 dark:text-gray-200 placeholder-gray-400 dark:placeholder-gray-500 focus:ring-2 focus:ring-blue-500 focus:outline-none",role:"searchbox","aria-label":h??"Search"})}),d.jsxs(j,{className:"py-2 max-h-60 overflow-auto",children:[w&&w.length===0&&d.jsx(j,{className:"px-4 py-3 text-sm text-gray-500 dark:text-gray-400 text-center",children:p?"No results":""}),(w??[]).map(_=>{const b=String(_.value)===String(a);return d.jsxs(R,{type:"button",onClick:()=>!_.disabled&&O(_.value),disabled:_.disabled,className:`w-full px-4 py-3 text-left flex items-center justify-between hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors ${b?"text-blue-600 dark:text-blue-400 font-medium":"text-gray-700 dark:text-gray-200"} ${_.disabled?"opacity-50 cursor-not-allowed":"cursor-pointer"}`,role:"option","aria-selected":b,children:[d.jsx(te,{children:_.label}),b&&d.jsx(En,{className:"w-5 h-5 text-blue-600 dark:text-blue-400",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:d.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M5 13l4 4L19 7"})})]},_.value)})]})]}),document.body)]})},Sd=({children:e,className:t="",...r})=>d.jsx("option",{className:t,...r,children:e}),Ed=({children:e,className:t="",...r})=>d.jsx("optgroup",{className:t,...r,children:e}),ms=C.forwardRef(({label:e,className:t="",...r},n)=>d.jsx("input",{ref:n,type:"checkbox",className:t,...r}));ms.displayName="Checkbox";const Td=({children:e,className:t="",...r})=>d.jsx("table",{className:t,...r,children:e}),Ad=({children:e,className:t="",...r})=>d.jsx("thead",{className:t,...r,children:e}),Nd=({children:e,className:t="",...r})=>d.jsx("tbody",{className:t,...r,children:e}),Od=({children:e,className:t="",...r})=>d.jsx("tr",{className:t,...r,children:e}),Md=({children:e,className:t="",...r})=>d.jsx("th",{className:t,...r,children:e}),Id=({children:e,className:t="",...r})=>d.jsx("td",{className:t,...r,children:e}),Zr=C.forwardRef(({children:e,className:t="",...r},n)=>d.jsx("nav",{ref:n,className:t,...r,children:e}));Zr.displayName="Nav";const bs=({children:e,className:t="",...r})=>d.jsx("section",{className:t,...r,children:e}),Dd=({children:e,className:t="",...r})=>d.jsx("code",{className:t,...r,children:e}),To=C.forwardRef(({className:e="",...t},r)=>d.jsx("hr",{ref:r,className:e,...t})),Pd=C.forwardRef(({children:e,className:t="",...r},n)=>d.jsx("header",{ref:n,className:t,...r,children:e})),ys=e=>e==="auto"?window.matchMedia("(prefers-color-scheme: dark)").matches?"dark":"light":e,Ao=e=>{const t=ys(e);document.documentElement.setAttribute("data-theme",t),t==="dark"?document.documentElement.classList.add("dark"):document.documentElement.classList.remove("dark")},Ld=()=>{const e=localStorage.getItem("g7_color_scheme");return e&&["auto","light","dark"].includes(e)?e:"auto"},An=({onThemeChange:e,className:t="",autoText:r="System",lightText:n="Light",darkText:o="Dark",id:a,editorAttrs:s})=>{const l=Ld(),[f,h]=C.useState(l),[c,i]=C.useState(!1),u=C.useRef(null);C.useEffect(()=>{Ao(f)},[]),C.useEffect(()=>{if(f!=="auto")return;const m=window.matchMedia("(prefers-color-scheme: dark)"),x=()=>{Ao("auto")};return m.addEventListener("change",x),()=>m.removeEventListener("change",x)},[f]),C.useEffect(()=>{const m=x=>{u.current&&!u.current.contains(x.target)&&i(!1)};return document.addEventListener("mousedown",m),()=>document.removeEventListener("mousedown",m)},[]);const g=m=>{h(m),localStorage.setItem("g7_color_scheme",m),Ao(m),i(!1),e?.(m)},p=()=>ys(f)==="dark"?nt.Moon:nt.Sun;return d.jsxs(j,{ref:u,className:`relative ${t}`,id:a,...s,children:[d.jsx(R,{onClick:()=>i(!c),className:"p-2 hover:bg-gray-100 dark:hover:bg-gray-800 rounded-lg transition-colors text-gray-600 dark:text-gray-400","aria-label":"Toggle theme",children:d.jsx(fe,{name:p(),className:"w-5 h-5"})}),c&&d.jsx(j,{className:"absolute right-0 mt-2 w-48 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg shadow-lg z-50",children:d.jsxs(j,{className:"py-2",children:[d.jsxs(R,{onClick:()=>g("auto"),className:` +var SirsoftBasic=(function(ee,d,C,tr){"use strict";function bd(e){const t=Object.create(null,{[Symbol.toStringTag]:{value:"Module"}});if(e){for(const r in e)if(r!=="default"){const n=Object.getOwnPropertyDescriptor(e,r);Object.defineProperty(t,r,n.get?n:{enumerable:!0,get:()=>e[r]})}}return t.default=e,Object.freeze(t)}const A=bd(C),yd=()=>window.G7Core,R=C.forwardRef(({children:e,variant:t="primary",size:r="md",className:n="",...o},a)=>{const s="inline-flex items-center justify-center",l=yd()?.style?.mergeClasses?.(s,n)??`${s} ${n}`;return d.jsx("button",{ref:a,className:l,...o,children:e})});R.displayName="Button";const j=C.forwardRef(({children:e,className:t="",...r},n)=>d.jsx("div",{ref:n,className:t,...r,children:e})),te=({children:e,className:t="",...r})=>d.jsx("span",{className:t,...r,children:e}),En=({children:e,className:t="",...r})=>d.jsx("svg",{className:t,...r,children:e}),At=C.forwardRef(({label:e,error:t,className:r="",onChange:n,onKeyPress:o,value:a,defaultValue:s,type:l="text",checked:f,defaultChecked:h,...c},i)=>{const u=l==="radio"||l==="checkbox",g=C.useRef(!1),p=C.useRef(null);C.useImperativeHandle(i,()=>p.current);const[m,x]=C.useState(a??s??"");C.useEffect(()=>{!u&&!g.current&&a!==void 0&&x(a)},[a,u]);const k=C.useCallback(()=>{g.current=!0},[]),M=C.useCallback(y=>{if(g.current=!1,n){const E=y.target.value;x(E);const O=new Event("change",{bubbles:!0});Object.defineProperty(O,"target",{writable:!1,value:{value:E}}),Object.defineProperty(O,"currentTarget",{writable:!1,value:{value:E}}),n(O)}},[n]),v=C.useCallback(y=>{const E=y.target.value;x(E),!g.current&&n&&n(y)},[n]),T=C.useCallback(y=>{g.current||o&&o(y)},[o]),{loadingactions:I,formdata:S,...N}=c;if(u)return d.jsx("input",{ref:p,type:l,className:r,onChange:n,checked:f,defaultChecked:h,value:a,...N});const w=a!==void 0?{value:m}:{defaultValue:s};return d.jsx("input",{ref:p,type:l,className:r,onChange:v,onKeyPress:T,onCompositionStart:k,onCompositionEnd:M,...w,...N})});At.displayName="Input";const ds=C.forwardRef(({accept:e,maxSize:t,onChange:r,onError:n,buttonText:o="Browse",placeholder:a="No file selected",className:s="",disabled:l,...f},h)=>{const c=C.useRef(null),i=h||c,[u,g]=C.useState(""),p={},m={};Object.entries(f).forEach(([v,T])=>{v.startsWith("data-editor-")||v==="id"||v==="onMouseMove"||v==="onMouseLeave"?p[v]=T:m[v]=T});const x=C.useCallback(()=>{!l&&i.current&&i.current.click()},[l,i]),k=C.useCallback(v=>{const T=v.target.files?.[0]||null;if(!T){g(""),r?.(null);return}if(t&&T.size>t*1024*1024){const I=`File size exceeds ${t}MB limit`;n?.(I),i.current&&(i.current.value=""),g("");return}g(T.name),r?.(T)},[t,r,n,i]),M=C.useCallback(v=>{v.stopPropagation(),i.current&&(i.current.value=""),g(""),r?.(null)},[r,i]);return d.jsxs(j,{className:`flex items-center gap-2 ${s}`,...p,children:[d.jsx(At,{ref:i,type:"file",accept:e,onChange:k,disabled:l,className:"hidden",...m}),d.jsx(R,{type:"button",onClick:x,disabled:l,className:"px-4 py-2 bg-gray-100 dark:bg-gray-700 border border-gray-300 dark:border-gray-600 rounded-md text-sm font-medium text-gray-700 dark:text-gray-200 hover:bg-gray-200 dark:hover:bg-gray-600 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 disabled:opacity-50 disabled:cursor-not-allowed",children:o}),d.jsx(te,{className:"flex-1 text-sm text-gray-600 dark:text-gray-400 truncate",children:u||a}),u&&!l&&d.jsx(R,{type:"button",onClick:M,className:"p-1 text-gray-400 hover:text-gray-600 dark:hover:text-gray-200","aria-label":"Clear file",children:d.jsx(En,{className:"w-4 h-4",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:d.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M6 18L18 6M6 6l12 12"})})})]})});ds.displayName="FileInput";var nt=(e=>(e.User="fa-user",e.Users="fa-users",e.UserCircle="fa-user-circle",e.UserPlus="fa-user-plus",e.UserMinus="fa-user-minus",e.UserShield="fa-user-shield",e.Search="fa-search",e.Edit="fa-edit",e.Pencil="fa-pencil-alt",e.Trash="fa-trash",e.Save="fa-save",e.Plus="fa-plus",e.Minus="fa-minus",e.Check="fa-check",e.Times="fa-times",e.Download="fa-download",e.Upload="fa-upload",e.Copy="fa-copy",e.ExternalLink="fa-external-link-alt",e.Home="fa-home",e.ChevronLeft="fa-chevron-left",e.ChevronRight="fa-chevron-right",e.ChevronUp="fa-chevron-up",e.ChevronDown="fa-chevron-down",e.AngleLeft="fa-angle-left",e.AngleRight="fa-angle-right",e.AngleUp="fa-angle-up",e.AngleDown="fa-angle-down",e.ArrowUp="fa-arrow-up",e.ArrowDown="fa-arrow-down",e.ArrowLeft="fa-arrow-left",e.ArrowRight="fa-arrow-right",e.Bars="fa-bars",e.EllipsisVertical="fa-ellipsis-v",e.EllipsisHorizontal="fa-ellipsis-h",e.CheckCircle="fa-check-circle",e.TimesCircle="fa-times-circle",e.ExclamationCircle="fa-exclamation-circle",e.ExclamationTriangle="fa-exclamation-triangle",e.InfoCircle="fa-info-circle",e.QuestionCircle="fa-question-circle",e.XCircle="fa-times-circle",e.ShoppingCart="fa-shopping-cart",e.ShoppingBag="fa-shopping-bag",e.CreditCard="fa-credit-card",e.Tag="fa-tag",e.Tags="fa-tags",e.File="fa-file",e.FileAlt="fa-file-alt",e.Folder="fa-folder",e.FolderOpen="fa-folder-open",e.Envelope="fa-envelope",e.Phone="fa-phone",e.Comment="fa-comment",e.Comments="fa-comments",e.Cog="fa-cog",e.Cogs="fa-cogs",e.Settings="fa-cog",e.Wrench="fa-wrench",e.Sliders="fa-sliders-h",e.Image="fa-image",e.Images="fa-images",e.Video="fa-video",e.Camera="fa-camera",e.Heart="fa-heart",e.Star="fa-star",e.ThumbsUp="fa-thumbs-up",e.ThumbsDown="fa-thumbs-down",e.Share="fa-share",e.Clock="fa-clock",e.Calendar="fa-calendar",e.CalendarAlt="fa-calendar-alt",e.Lock="fa-lock",e.Unlock="fa-unlock",e.Eye="fa-eye",e.EyeSlash="fa-eye-slash",e.Bell="fa-bell",e.Flag="fa-flag",e.Map="fa-map",e.MapMarker="fa-map-marker-alt",e.Globe="fa-globe",e.Link="fa-link",e.Unlink="fa-unlink",e.Sun="fa-sun",e.Moon="fa-moon",e.ChartBar="fa-chart-bar",e.ChartLine="fa-chart-line",e.ChartPie="fa-chart-pie",e.ChartArea="fa-chart-area",e.Spinner="fa-spinner",e.CircleNotch="fa-circle-notch",e.CheckBadge="fa-badge-check",e.BellSlash="fa-bell-slash",e.BookOpen="fa-book-open",e.Building="fa-building",e.FileCircleCheck="fa-file-circle-check",e.WandMagicSparkles="fa-wand-magic-sparkles",e.UserXmark="fa-user-xmark",e.Cube="fa-cube",e.Cubes="fa-cubes",e.Plug="fa-plug",e.PuzzlePiece="fa-puzzle-piece",e.Fire="fa-fire",e.CommentDots="fa-comment-dots",e.DollarSign="fa-dollar-sign",e.Bars2="fa-bars",e.X="fa-times",e.List="fa-list",e.ThLarge="fa-th-large",e.Inbox="fa-inbox",e.Box="fa-box",e.PaperPlane="fa-paper-plane",e.FileText="fa-file-alt",e.Paperclip="fa-paperclip",e.Truck="fa-truck",e.Sync="fa-sync",e.RotateLeft="fa-rotate-left",e.MapPin="fa-map-marker-alt",e.HelpCircle="fa-question-circle",e))(nt||{});const vd={user:"fa-user",users:"fa-users","user-circle":"fa-user-circle","user-plus":"fa-user-plus","user-minus":"fa-user-minus","user-shield":"fa-user-shield",search:"fa-search",edit:"fa-edit",pencil:"fa-pencil-alt",trash:"fa-trash",save:"fa-save",plus:"fa-plus",minus:"fa-minus",check:"fa-check",times:"fa-times",download:"fa-download",upload:"fa-upload",copy:"fa-copy","external-link":"fa-external-link-alt",home:"fa-home","chevron-left":"fa-chevron-left","chevron-right":"fa-chevron-right","chevron-up":"fa-chevron-up","chevron-down":"fa-chevron-down","angle-left":"fa-angle-left","angle-right":"fa-angle-right","angle-up":"fa-angle-up","angle-down":"fa-angle-down","arrow-up":"fa-arrow-up","arrow-down":"fa-arrow-down","arrow-left":"fa-arrow-left","arrow-right":"fa-arrow-right",bars:"fa-bars","ellipsis-vertical":"fa-ellipsis-v","ellipsis-v":"fa-ellipsis-v","ellipsis-horizontal":"fa-ellipsis-h","ellipsis-h":"fa-ellipsis-h","check-circle":"fa-check-circle","times-circle":"fa-times-circle","x-circle":"fa-times-circle","exclamation-circle":"fa-exclamation-circle","exclamation-triangle":"fa-exclamation-triangle","info-circle":"fa-info-circle","question-circle":"fa-question-circle","shopping-cart":"fa-shopping-cart","shopping-bag":"fa-shopping-bag","credit-card":"fa-credit-card",tag:"fa-tag",tags:"fa-tags",file:"fa-file","file-alt":"fa-file-alt",folder:"fa-folder","folder-open":"fa-folder-open",envelope:"fa-envelope",phone:"fa-phone",comment:"fa-comment",comments:"fa-comments",cog:"fa-cog",cogs:"fa-cogs",settings:"fa-cog",wrench:"fa-wrench",sliders:"fa-sliders-h",image:"fa-image",images:"fa-images",video:"fa-video",camera:"fa-camera",heart:"fa-heart",star:"fa-star","thumbs-up":"fa-thumbs-up","thumbs-down":"fa-thumbs-down",share:"fa-share",clock:"fa-clock",calendar:"fa-calendar","calendar-alt":"fa-calendar-alt",lock:"fa-lock",unlock:"fa-unlock",eye:"fa-eye","eye-slash":"fa-eye-slash",bell:"fa-bell",flag:"fa-flag",map:"fa-map","map-marker":"fa-map-marker-alt",globe:"fa-globe",link:"fa-link",unlink:"fa-unlink",sun:"fa-sun",moon:"fa-moon","chart-bar":"fa-chart-bar","chart-line":"fa-chart-line","chart-pie":"fa-chart-pie","chart-area":"fa-chart-area",spinner:"fa-spinner","circle-notch":"fa-circle-notch","check-badge":"fa-badge-check","badge-check":"fa-badge-check",cube:"fa-cube",cubes:"fa-cubes",plug:"fa-plug","puzzle-piece":"fa-puzzle-piece",flame:"fa-fire",fire:"fa-fire","message-square":"fa-comment-dots","dollar-sign":"fa-dollar-sign",menu:"fa-bars",x:"fa-times",list:"fa-list","layout-grid":"fa-th-large","th-large":"fa-th-large",grid:"fa-th-large",inbox:"fa-inbox",package:"fa-box",box:"fa-box",send:"fa-paper-plane","paper-plane":"fa-paper-plane","file-text":"fa-file-alt",paperclip:"fa-paperclip",truck:"fa-truck","refresh-cw":"fa-sync",sync:"fa-sync","undo-2":"fa-rotate-left","rotate-left":"fa-rotate-left","map-pin":"fa-map-marker-alt","help-circle":"fa-question-circle","alert-triangle":"fa-exclamation-triangle","bell-off":"fa-bell-slash","book-open":"fa-book-open",building:"fa-building","edit-2":"fa-edit","edit-3":"fa-edit","file-check":"fa-file-circle-check",mail:"fa-envelope",sparkles:"fa-wand-magic-sparkles","trash-2":"fa-trash","user-x":"fa-user-xmark"},fe=({name:e,iconStyle:t="solid",size:r,color:n,spin:o=!1,pulse:a=!1,fixedWidth:s=!1,ariaLabel:l,className:f="",style:h,...c})=>{const i={solid:"fas",regular:"far",light:"fal",duotone:"fad",brands:"fab"},u=typeof e=="string"?e:String(e),g=vd[u],p=g?String(g).replace(/^fa-/,""):u.replace(/^fa-/,""),m=[i[t],`fa-${p}`,r&&`fa-${r}`,o&&"fa-spin",a&&"fa-pulse",s&&"fa-fw",n,f].filter(Boolean).join(" "),x=l||p.replace(/-/g," ");return d.jsx("i",{className:m,style:h,"aria-label":x,role:"img",...c})},Tr=(e,t)=>window.G7Core?.t?.(e,t)??e,ko={minLength:{key:"minLength",labelKey:"auth.password_input.rules.minLength",defaultParam:6,validate:(e,t=6)=>e.length>=t},maxLength:{key:"maxLength",labelKey:"auth.password_input.rules.maxLength",defaultParam:20,validate:(e,t=20)=>e.length<=t},hasUppercase:{key:"hasUppercase",labelKey:"auth.password_input.rules.hasUppercase",validate:e=>/[A-Z]/.test(e)},hasLowercase:{key:"hasLowercase",labelKey:"auth.password_input.rules.hasLowercase",validate:e=>/[a-z]/.test(e)},hasNumber:{key:"hasNumber",labelKey:"auth.password_input.rules.hasNumber",validate:e=>/[0-9]/.test(e)},hasSpecial:{key:"hasSpecial",labelKey:"auth.password_input.rules.hasSpecial",validate:e=>/[!@#$%^&*(),.?":{}|<>]/.test(e)},noSpaces:{key:"noSpaces",labelKey:"auth.password_input.rules.noSpaces",validate:e=>!/\s/.test(e)},minTypes:{key:"minTypes",labelKey:"auth.password_input.rules.minTypes",defaultParam:3,validate:(e,t=3)=>{let r=0;return/[A-Z]/.test(e)&&r++,/[a-z]/.test(e)&&r++,/[0-9]/.test(e)&&r++,/[!@#$%^&*(),.?":{}|<>]/.test(e)&&r++,r>=t}}},us=[ko.minLength];function wd(e){const[t,r]=e.split(":"),n=ko[t];if(!n)return null;const o=r?parseInt(r,10):n.defaultParam;return{rule:n,param:isNaN(o)?n.defaultParam:o}}const fs=C.forwardRef(({label:e,error:t,showToggle:r=!0,showValidation:n=!1,isConfirmField:o=!1,confirmTarget:a,onMatchChange:s,onValidityChange:l,rules:f,enableRules:h,showRules:c,wrapperClassName:i="",validationClassName:u="",className:g="",onChange:p,value:m,defaultValue:x,disabled:k,...M},v)=>{const T=C.useRef(null);C.useImperativeHandle(v,()=>T.current);const[I,S]=C.useState(!1),[N,w]=C.useState(!1),[y,E]=C.useState(m??x??"");C.useEffect(()=>{m!==void 0&&E(m)},[m]);const O=C.useMemo(()=>f&&f.length>0?f.map(H=>({rule:H,param:H.defaultParam})):h&&h.length>0?h.map(H=>wd(H)).filter(H=>H!==null):us.map(H=>({rule:H,param:H.defaultParam})),[f,h]),$=C.useMemo(()=>!c||c.length===0?O:O.filter(H=>c.includes(H.rule.key)),[O,c]),P=C.useMemo(()=>!n||o?{}:$.reduce((H,{rule:W,param:Q})=>(H[W.key]=W.validate(y,Q),H),{}),[y,$,n,o]),L=C.useMemo(()=>!o||a===void 0?!0:y===a&&y.length>0,[o,y,a]),U=C.useMemo(()=>n?Object.values(P).every(H=>H):!0,[P,n]);C.useEffect(()=>{if(l&&n&&!o){const H=Object.entries(P).filter(([,W])=>!W).map(([W])=>W);l(U,H)}},[U,P,l,n,o]),C.useEffect(()=>{s&&o&&s(L)},[L,s,o]);const _=C.useCallback(()=>{S(H=>!H)},[]),b=C.useCallback(H=>{const W=H.target.value;E(W),p&&p(H)},[p]),z=C.useCallback(H=>{w(H.getModifierState("CapsLock"))},[]),{loadingactions:F,formdata:G,...X}=M,B={},Y={};for(const[H,W]of Object.entries(X))H.startsWith("data-editor-")||H==="onMouseMove"||H==="onMouseLeave"?B[H]=W:Y[H]=W;return d.jsxs("div",{className:`relative ${i}`,...B,children:[d.jsxs("div",{className:"relative",children:[d.jsx("input",{ref:T,type:I?"text":"password",className:`${g} ${r?"pr-10":""}`,value:m!==void 0?m:void 0,defaultValue:m===void 0?x:void 0,onChange:b,onKeyDown:z,onKeyUp:z,disabled:k,...Y}),r&&d.jsx("button",{type:"button",onClick:_,disabled:k,className:"absolute right-2 top-1/2 -translate-y-1/2 p-1 text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200 disabled:opacity-50 disabled:cursor-not-allowed dark:disabled:opacity-50","aria-label":Tr(I?"auth.password_input.hide":"auth.password_input.show"),children:d.jsx(fe,{name:I?"eye-slash":"eye",className:"w-5 h-5"})})]}),N&&!I&&d.jsxs("div",{className:"mt-2 flex items-center gap-1.5 text-sm text-yellow-600 dark:text-yellow-400",children:[d.jsx(fe,{name:"triangle-exclamation",className:"w-4 h-4"}),d.jsx("span",{children:Tr("auth.password_input.caps_lock_on")})]}),o&&a!==void 0&&y.length>0&&d.jsxs("div",{className:`mt-2 flex items-center gap-1.5 text-sm ${L?"text-green-600 dark:text-green-400":"text-red-600 dark:text-red-400"}`,children:[d.jsx(fe,{name:L?"check-circle":"xmark-circle",className:"w-4 h-4"}),d.jsx("span",{children:Tr(L?"auth.password_input.match":"auth.password_input.mismatch")})]}),n&&!o&&y.length>0&&d.jsx("div",{className:`mt-2 space-y-1 ${u}`,children:$.map(({rule:H,param:W})=>{const Q=P[H.key],be=W!==void 0?{count:W}:void 0;return d.jsxs("div",{className:`flex items-center gap-1.5 text-sm ${Q?"text-green-600 dark:text-green-400":"text-gray-500 dark:text-gray-400"}`,children:[d.jsx(fe,{name:Q?"check-circle":"circle",className:"w-4 h-4"}),d.jsx("span",{children:Tr(H.labelKey,be)})]},H.key)})}),t&&d.jsx("p",{className:"mt-1 text-sm text-red-600 dark:text-red-400",children:t})]})});fs.displayName="PasswordInput";const Co=({label:e,error:t,className:r="",onChange:n,value:o,defaultValue:a,...s})=>{const l=C.useRef(!1),f=C.useRef(null),[h,c]=C.useState(o??a??"");C.useEffect(()=>{!l.current&&o!==void 0&&c(o)},[o]);const i=C.useCallback(()=>{l.current=!0},[]),u=C.useCallback(M=>{if(l.current=!1,n){const v=M.target.value;c(v);const T=new Event("change",{bubbles:!0});Object.defineProperty(T,"target",{writable:!1,value:{value:v}}),Object.defineProperty(T,"currentTarget",{writable:!1,value:{value:v}}),n(T)}},[n]),g=C.useCallback(M=>{const v=M.target.value;c(v),!l.current&&n&&n(M)},[n]),{loadingactions:p,formdata:m,...x}=s,k=o!==void 0?{value:h}:{defaultValue:a};return d.jsx("textarea",{ref:f,className:r,onChange:g,onCompositionStart:i,onCompositionEnd:u,...k,...x})},Ar=({children:e,className:t="",...r})=>d.jsx("label",{className:t,...r,children:e}),Gt=({children:e,className:t="",...r})=>d.jsx("p",{className:t,...r,children:e}),xd=["url","size","repeat","position"];function kd(e){if(typeof e=="string")return e===""?void 0:e;if(!e||typeof e!="object"||Array.isArray(e)||!Object.keys(e).every(n=>xd.includes(n)))return;const r=e.url;return typeof r=="string"&&r!==""?r:void 0}const jt=({className:e="",alt:t="",src:r,...n})=>d.jsx("img",{className:e,alt:t,src:kd(r),...n}),Cd=({children:e,className:t="",...r})=>d.jsx("h1",{className:t,...r,children:e}),gs=({children:e,className:t="",...r})=>d.jsx("h2",{className:t,...r,children:e}),Tn=({children:e,className:t="",...r})=>d.jsx("h3",{className:t,...r,children:e}),hs=({children:e,className:t="",...r})=>d.jsx("h4",{className:t,...r,children:e}),_o=({children:e,className:t="",...r})=>d.jsx("ul",{className:t,...r,children:e}),_d=({children:e,className:t="",...r})=>d.jsx("ol",{className:t,...r,children:e}),er=({children:e,className:t="",...r})=>d.jsx("li",{className:t,...r,children:e}),So=({children:e,className:t="",...r})=>d.jsx("a",{className:t,...r,children:e}),Eo=({children:e,className:t="",...r})=>d.jsx("form",{className:t,...r,children:e}),Sd=window.G7Core?.createLogger?.("Comp:Select")??{log:(...e)=>console.log("[Comp:Select]",...e),warn:(...e)=>console.warn("[Comp:Select]",...e),error:(...e)=>console.error("[Comp:Select]",...e)};function Ed(e){const t=window.G7Core?.state?.get?.("_global.appConfig.localeNames")?.[e];return t||{ko:"한국어",en:"English",ja:"日本語",zh:"中文",es:"Español",fr:"Français",de:"Deutsch"}[e]||e}const ps=({children:e,label:t,error:r,options:n,className:o="",value:a,onChange:s,disabled:l,searchable:f=!1,searchPlaceholder:h,editorAttrs:c,...i})=>{const[u,g]=C.useState(!1),[p,m]=C.useState(""),[x,k]=C.useState(null),M=C.useRef(null),v=C.useRef(null),T=C.useRef(null),I=C.useRef(null),S=C.useMemo(()=>n?Array.isArray(n)?n.length===0?[]:typeof n[0]=="string"?n.map(_=>({value:_,label:Ed(_)})):n:(Sd.warn("options prop is not an array:",n),null):null,[n]),N=C.useMemo(()=>S&&S.find(b=>String(b.value)===String(a))?.label||"",[S,a]),w=C.useMemo(()=>{if(!S)return null;if(!f||p.trim()==="")return S;const _=p.trim().toLowerCase();return S.filter(b=>b.label.toLowerCase().includes(_)||String(b.value).toLowerCase().includes(_))},[S,f,p]);C.useEffect(()=>{if(u&&f){m("");const _=setTimeout(()=>T.current?.focus(),0);return()=>clearTimeout(_)}u||m("")},[u,f]);const y=C.useCallback(()=>{if(!v.current)return;const _=v.current.getBoundingClientRect(),b=window.innerHeight-_.bottom,F=b<280&&_.top>b;k(F?{bottom:window.innerHeight-_.top+4,left:_.left,width:_.width}:{top:_.bottom+4,left:_.left,width:_.width})},[]);C.useEffect(()=>{if(!u){k(null);return}return y(),window.addEventListener("scroll",y,!0),window.addEventListener("resize",y),()=>{window.removeEventListener("scroll",y,!0),window.removeEventListener("resize",y)}},[u,y]),C.useEffect(()=>{const _=b=>{const z=b.target;M.current&&!M.current.contains(z)&&(!I.current||!I.current.contains(z))&&g(!1)};return u&&document.addEventListener("mousedown",_),()=>{document.removeEventListener("mousedown",_)}},[u]),C.useEffect(()=>{const _=b=>{b.key==="Escape"&&u&&(g(!1),v.current?.focus())};return document.addEventListener("keydown",_),()=>document.removeEventListener("keydown",_)},[u]);const E=()=>{l||g(!u)},O=_=>{s&&s({target:{value:_},preventDefault:()=>{},stopPropagation:()=>{},type:"change"}),g(!1),v.current?.focus()};if(!S)return d.jsx("select",{className:o,value:a,onChange:s,disabled:l,...c,...i,children:e});const $=o&&o.includes("bg-"),P=o&&/text-(gray|red|blue|green|yellow|white|black|indigo|purple|pink|orange|amber|emerald|teal|cyan|slate)-\d+|text-white|text-black/.test(o),L=$?`${o}${P?"":" text-gray-700 dark:text-gray-200"}`:`w-full px-4 py-2.5 bg-gray-100 dark:bg-gray-700 border-0 rounded-xl text-gray-700 dark:text-gray-200 font-medium focus:ring-2 focus:ring-blue-500 focus:outline-none ${o}`,U={...c,...i};return d.jsxs(j,{ref:M,className:"relative",...U,children:[d.jsxs(R,{ref:v,type:"button",onClick:E,disabled:l,className:`${L} flex items-center justify-between gap-2 text-left cursor-pointer disabled:opacity-50 disabled:cursor-not-allowed`,"aria-haspopup":"listbox","aria-expanded":u,children:[d.jsx(te,{className:"truncate",children:N||" "}),d.jsx(En,{className:`w-4 h-4 flex-shrink-0 transition-transform duration-200 ${u?"rotate-180":""}`,fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:d.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M19 9l-7 7-7-7"})})]}),u&&x&&tr.createPortal(d.jsxs(j,{ref:I,className:"fixed z-[9999] bg-white dark:bg-gray-800 rounded-2xl shadow-lg border border-gray-200 dark:border-gray-600 overflow-hidden",style:{top:x.top,bottom:x.bottom,left:x.left,width:x.width},role:"listbox",children:[f&&d.jsx(j,{className:"p-2 border-b border-gray-200 dark:border-gray-600",children:d.jsx(At,{ref:T,type:"text",value:p,onChange:_=>m(_.target.value),placeholder:h??"Search...",className:"w-full px-3 py-2 bg-gray-100 dark:bg-gray-700 border-0 rounded-lg text-sm text-gray-700 dark:text-gray-200 placeholder-gray-400 dark:placeholder-gray-500 focus:ring-2 focus:ring-blue-500 focus:outline-none",role:"searchbox","aria-label":h??"Search"})}),d.jsxs(j,{className:"py-2 max-h-60 overflow-auto",children:[w&&w.length===0&&d.jsx(j,{className:"px-4 py-3 text-sm text-gray-500 dark:text-gray-400 text-center",children:p?"No results":""}),(w??[]).map(_=>{const b=String(_.value)===String(a);return d.jsxs(R,{type:"button",onClick:()=>!_.disabled&&O(_.value),disabled:_.disabled,className:`w-full px-4 py-3 text-left flex items-center justify-between hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors ${b?"text-blue-600 dark:text-blue-400 font-medium":"text-gray-700 dark:text-gray-200"} ${_.disabled?"opacity-50 cursor-not-allowed":"cursor-pointer"}`,role:"option","aria-selected":b,children:[d.jsx(te,{children:_.label}),b&&d.jsx(En,{className:"w-5 h-5 text-blue-600 dark:text-blue-400",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:d.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M5 13l4 4L19 7"})})]},_.value)})]})]}),document.body)]})},Td=({children:e,className:t="",...r})=>d.jsx("option",{className:t,...r,children:e}),Ad=({children:e,className:t="",...r})=>d.jsx("optgroup",{className:t,...r,children:e}),ms=C.forwardRef(({label:e,className:t="",...r},n)=>d.jsx("input",{ref:n,type:"checkbox",className:t,...r}));ms.displayName="Checkbox";const Nd=({children:e,className:t="",...r})=>d.jsx("table",{className:t,...r,children:e}),Od=({children:e,className:t="",...r})=>d.jsx("thead",{className:t,...r,children:e}),Md=({children:e,className:t="",...r})=>d.jsx("tbody",{className:t,...r,children:e}),Id=({children:e,className:t="",...r})=>d.jsx("tr",{className:t,...r,children:e}),Dd=({children:e,className:t="",...r})=>d.jsx("th",{className:t,...r,children:e}),Pd=({children:e,className:t="",...r})=>d.jsx("td",{className:t,...r,children:e}),Zr=C.forwardRef(({children:e,className:t="",...r},n)=>d.jsx("nav",{ref:n,className:t,...r,children:e}));Zr.displayName="Nav";const bs=({children:e,className:t="",...r})=>d.jsx("section",{className:t,...r,children:e}),Ld=({children:e,className:t="",...r})=>d.jsx("code",{className:t,...r,children:e}),To=C.forwardRef(({className:e="",...t},r)=>d.jsx("hr",{ref:r,className:e,...t})),$d=C.forwardRef(({children:e,className:t="",...r},n)=>d.jsx("header",{ref:n,className:t,...r,children:e})),ys=e=>e==="auto"?window.matchMedia("(prefers-color-scheme: dark)").matches?"dark":"light":e,Ao=e=>{const t=ys(e);document.documentElement.setAttribute("data-theme",t),t==="dark"?document.documentElement.classList.add("dark"):document.documentElement.classList.remove("dark")},Fd=()=>{const e=localStorage.getItem("g7_color_scheme");return e&&["auto","light","dark"].includes(e)?e:"auto"},An=({onThemeChange:e,className:t="",autoText:r="System",lightText:n="Light",darkText:o="Dark",id:a,editorAttrs:s})=>{const l=Fd(),[f,h]=C.useState(l),[c,i]=C.useState(!1),u=C.useRef(null);C.useEffect(()=>{Ao(f)},[]),C.useEffect(()=>{if(f!=="auto")return;const m=window.matchMedia("(prefers-color-scheme: dark)"),x=()=>{Ao("auto")};return m.addEventListener("change",x),()=>m.removeEventListener("change",x)},[f]),C.useEffect(()=>{const m=x=>{u.current&&!u.current.contains(x.target)&&i(!1)};return document.addEventListener("mousedown",m),()=>document.removeEventListener("mousedown",m)},[]);const g=m=>{h(m),localStorage.setItem("g7_color_scheme",m),Ao(m),i(!1),e?.(m)},p=()=>ys(f)==="dark"?nt.Moon:nt.Sun;return d.jsxs(j,{ref:u,className:`relative ${t}`,id:a,...s,children:[d.jsx(R,{onClick:()=>i(!c),className:"p-2 hover:bg-gray-100 dark:hover:bg-gray-800 rounded-lg transition-colors text-gray-600 dark:text-gray-400","aria-label":"Toggle theme",children:d.jsx(fe,{name:p(),className:"w-5 h-5"})}),c&&d.jsx(j,{className:"absolute right-0 mt-2 w-48 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg shadow-lg z-50",children:d.jsxs(j,{className:"py-2",children:[d.jsxs(R,{onClick:()=>g("auto"),className:` w-full px-4 py-2 text-left hover:bg-gray-50 dark:hover:bg-gray-700 flex items-center gap-3 transition-colors ${f==="auto"?"bg-gray-50 dark:bg-gray-700":""} `,children:[d.jsx(fe,{name:nt.Settings,className:"w-5 h-5 text-gray-600 dark:text-gray-400"}),d.jsx(te,{className:"flex-1 text-left text-gray-900 dark:text-white",children:r}),f==="auto"&&d.jsx(fe,{name:nt.Check,className:"w-4 h-4 text-blue-600 dark:text-blue-400 ml-auto"})]}),d.jsxs(R,{onClick:()=>g("light"),className:` @@ -7,13 +7,13 @@ var SirsoftBasic=(function(ee,d,C,tr){"use strict";function bd(e){const t=Object `,children:[d.jsx(fe,{name:nt.Sun,className:"w-5 h-5 text-gray-600 dark:text-gray-400"}),d.jsx(te,{className:"flex-1 text-left text-gray-900 dark:text-white",children:n}),f==="light"&&d.jsx(fe,{name:nt.Check,className:"w-4 h-4 text-blue-600 dark:text-blue-400 ml-auto"})]}),d.jsxs(R,{onClick:()=>g("dark"),className:` w-full px-4 py-2 text-left hover:bg-gray-50 dark:hover:bg-gray-700 flex items-center gap-3 transition-colors ${f==="dark"?"bg-gray-50 dark:bg-gray-700":""} - `,children:[d.jsx(fe,{name:nt.Moon,className:"w-5 h-5 text-gray-600 dark:text-gray-400"}),d.jsx(te,{className:"flex-1 text-left text-gray-900 dark:text-white",children:o}),f==="dark"&&d.jsx(fe,{name:nt.Check,className:"w-4 h-4 text-blue-600 dark:text-blue-400 ml-auto"})]})]})})]})},$d=Object.freeze(Object.defineProperty({__proto__:null,ThemeToggle:An,default:An},Symbol.toStringTag,{value:"Module"})),vs={xs:"w-6 h-6 text-xs",sm:"w-8 h-8 text-sm",md:"w-10 h-10 text-base",lg:"w-12 h-12 text-lg",xl:"w-16 h-16 text-xl","2xl":"w-24 h-24 text-2xl","3xl":"w-32 h-32 text-3xl","4xl":"w-40 h-40 text-4xl","5xl":"w-48 h-48 text-5xl"},Fd="rounded-full overflow-hidden flex-shrink-0 border border-gray-200 dark:border-gray-700",Ud="grayscale opacity-50",No={normal:"bg-gradient-to-br from-blue-500 to-purple-600",withdrawn:"bg-gray-300 dark:bg-gray-600",guest:"bg-gray-200 dark:bg-gray-700"},Qr=({author:e,name:t,avatar:r,size:n="md",className:o="",text:a,isWithdrawn:s=!1,isGuest:l=!1,id:f,editorAttrs:h})=>{const c=r??e?.avatar,i=a??t??e?.name??"?",u=s||e?.status==="withdrawn",g=l||e?.is_guest||!1,p=C.useMemo(()=>[vs[n]||vs.md,Fd,u?Ud:"",o].filter(Boolean).join(" "),[n,u,o]),m=g?No.guest:u?No.withdrawn:No.normal,x=()=>g?d.jsx(j,{className:`w-full h-full flex items-center justify-center ${m}`,children:d.jsx(fe,{name:"user",className:"text-gray-400 dark:text-gray-500"})}):u?d.jsx(j,{className:`w-full h-full flex items-center justify-center ${m}`,children:d.jsx(fe,{name:"user",className:"text-gray-500 dark:text-gray-400"})}):d.jsx(j,{className:`w-full h-full flex items-center justify-center text-white font-semibold ${m}`,children:i.charAt(0).toUpperCase()});return d.jsx(j,{className:p,id:f,...h,children:c&&!g&&!u?d.jsx(jt,{src:c,alt:i,className:"w-full h-full object-cover"}):x()})},zd=Object.freeze(Object.defineProperty({__proto__:null,Avatar:Qr},Symbol.toStringTag,{value:"Module"})),Hd=window.G7Core?.createLogger?.("Comp:SlotContainer")??{log:(...e)=>console.log("[Comp:SlotContainer]",...e),warn:(...e)=>console.warn("[Comp:SlotContainer]",...e),error:(...e)=>console.error("[Comp:SlotContainer]",...e)};function ws(e,t){return t&&e&&typeof e.id=="string"&&e.id.length>0?{...e,id:`${e.id}__${t}`}:e}const Nn=({slotId:e,className:t="",emptyContent:r,style:n,id:o,children:a})=>{const[s,l]=C.useState([]),[,f]=C.useState({}),h=C.useCallback(()=>{const p=window.__slotContextValue;if(p)return p;const m=window.G7Core;return m&&m.getSlotContext?.()||null},[]),c=C.useCallback(()=>{const p=h();p&&p.isEnabled&&l(p.getSlotComponents(e))},[e,h]);C.useEffect(()=>{const p=h();return!p||!p.isEnabled?void 0:(c(),p.subscribeToSlot(e,()=>{c(),f({})}))},[e,h,c]);const i=C.useCallback(()=>{const p=window.G7Core;return p?p.getDynamicRenderer?.():null},[]);if(s.length===0)return r?d.jsx(j,{className:t,style:n,id:o,children:r}):a?d.jsx(j,{className:t,style:n,id:o,children:a}):null;const u=i(),g=window.G7Core;return!u||!g?(Hd.error("G7Core or DynamicRenderer not available"),null):d.jsxs(j,{className:t,style:n,id:o,children:[s.map(p=>{const{componentDef:m,dataContext:x,parentFormContext:k,getParentComponentContext:M,translationContext:v}=p,T=ws(m,o);return d.jsx(u,{componentDef:T,dataContext:x,translationContext:v,registry:g.getComponentRegistry(),bindingEngine:g.getDataBindingEngine(),translationEngine:g.getTranslationEngine(),actionDispatcher:g.getActionDispatcher(),parentComponentContext:M?.(),parentFormContextProp:k,isRootRenderer:!1},T.id||`slot-${e}-${p.order}`)}),a]})},Gd=Object.freeze(Object.defineProperty({__proto__:null,SlotContainer:Nn,default:Nn,scopeSlotChildDef:ws},Symbol.toStringTag,{value:"Module"})),On=({notifications:e=[],unreadCount:t=0,hasMore:r=!1,loading:n=!1,titleText:o="알림",emptyText:a="알림이 없습니다.",markAllReadText:s="모두 읽음",deleteAllText:l="모두 삭제",unreadOnlyText:f="안 읽은 알림만",unreadOnly:h=!1,onNotificationClick:c,onClose:i,onLoadMore:u,onMarkAllRead:g,onDeleteAll:p,onDelete:m,onUnreadOnlyToggle:x,dropdownAlign:k="right",className:M="",id:v,editorAttrs:T})=>{const[I,S]=C.useState(!1),N=C.useRef(null),w=C.useRef(null),y=C.useRef(null),E=C.useRef(null),O=C.useRef(new Set),$=C.useRef(u),P=C.useRef(r),L=C.useRef(n),U=C.useRef(e.length),_=C.useRef(-1);C.useEffect(()=>{$.current=u,P.current=r,L.current=n,U.current=e.length}),C.useEffect(()=>{if(!E.current||!y.current||!I)return;_.current=-1;const G=new IntersectionObserver(([X])=>{X.isIntersecting&&(L.current||!P.current||_.current!==U.current&&(_.current=U.current,$.current?.()))},{root:y.current,threshold:.1});return G.observe(E.current),()=>G.disconnect()},[I]),C.useEffect(()=>{if(!y.current||!I)return;const G=new IntersectionObserver(B=>{B.forEach(Y=>{const H=Y.target.getAttribute("data-notification-id"),W=Y.target.getAttribute("data-unread")==="true";H&&W&&Y.isIntersecting&&O.current.add(H)})},{root:y.current,threshold:.5});return y.current.querySelectorAll("[data-notification-id]").forEach(B=>G.observe(B)),()=>G.disconnect()},[I,e]);const b=t>0?t:e.filter(G=>!G.read).length,z=C.useCallback(()=>{if(I){if(b>0){const G=Array.from(O.current).filter(X=>{const B=e.find(Y=>String(Y.id)===String(X));return B&&!B.read});G.length>0&&i?.(G)}O.current.clear(),S(!1)}},[I,i,b,e]);C.useEffect(()=>{if(!I)return;const G=X=>{N.current&&!N.current.contains(X.target)&&z()};return document.addEventListener("mousedown",G),()=>document.removeEventListener("mousedown",G)},[I,z]);const F=C.useCallback(()=>{I?z():S(!0)},[I,z]);return C.useLayoutEffect(()=>{const G=w.current;if(!I||!G)return;G.style.transform="";const X=G.getBoundingClientRect(),B=typeof window<"u"?window.innerWidth:0;if(X.width===0&&X.height===0)return;const Y=8;let H=0;X.right>B-Y?H=-(X.right-(B-Y)):X.left0&&d.jsx(te,{className:"absolute -top-1 -right-1 w-5 h-5 flex items-center justify-center text-xs font-bold text-white bg-red-500 dark:bg-red-600 rounded-full",children:b>99?"99+":b})]}),I&&d.jsxs(j,{ref:w,className:`absolute ${k==="left"?"left-0":"right-0"} mt-2 w-80 max-w-[calc(100vw-1rem)] bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg shadow-lg z-50`,children:[d.jsxs(j,{className:"px-4 py-3 border-b border-gray-200 dark:border-gray-700 flex items-center justify-between gap-2",children:[d.jsxs(j,{className:"flex items-center gap-3 min-w-0",children:[d.jsx(te,{className:"font-semibold text-gray-900 dark:text-white",children:o}),x&&d.jsxs(j,{className:"flex items-center gap-1.5 cursor-pointer select-none",onClick:()=>x(!h),children:[d.jsx(At,{type:"checkbox",checked:h,onChange:G=>{G.stopPropagation(),x(G.target.checked)},onClick:G=>G.stopPropagation(),className:"w-3.5 h-3.5 rounded border-gray-300 dark:border-gray-600 text-blue-600 dark:text-blue-400 focus:ring-blue-500 dark:focus:ring-blue-400 cursor-pointer"}),d.jsx(te,{className:"text-xs text-gray-600 dark:text-gray-400",children:f})]})]}),d.jsxs(j,{className:"flex items-center gap-3 flex-shrink-0",children:[g&&b>0&&d.jsx(R,{onClick:()=>{g(),O.current.clear()},className:"text-xs text-blue-600 dark:text-blue-400 hover:text-blue-800 dark:hover:text-blue-300 hover:underline cursor-pointer",children:s}),p&&e.length>0&&d.jsx(R,{onClick:()=>{p()},className:"text-xs text-red-600 dark:text-red-400 hover:text-red-800 dark:hover:text-red-300 hover:underline cursor-pointer",children:l})]})]}),d.jsx(j,{ref:y,className:"max-h-96 overflow-y-auto",children:e.length===0&&!n?d.jsx(j,{className:"px-4 py-8 text-center text-gray-500 dark:text-gray-400",children:d.jsx(te,{children:a})}):d.jsxs(d.Fragment,{children:[e.map(G=>{const X=!G.read;return d.jsxs(j,{"data-notification-id":G.id,"data-unread":X?"true":"false",onClick:()=>{G.onClick?.(),c?.(G),S(!1)},className:`group flex items-start gap-3 px-4 py-3 text-left border-b border-gray-100 dark:border-gray-700 transition-colors cursor-pointer ${X?"bg-blue-50/70 dark:bg-blue-900/20 hover:bg-blue-50 dark:hover:bg-blue-900/30":"bg-white dark:bg-gray-800 hover:bg-gray-50 dark:hover:bg-gray-700/50 opacity-70"}`,children:[G.iconName&&d.jsx(fe,{name:G.iconName,className:`w-5 h-5 mt-0.5 flex-shrink-0 ${X?"text-blue-600 dark:text-blue-400":"text-gray-400 dark:text-gray-500"}`}),d.jsxs(j,{className:"flex-1 min-w-0",children:[d.jsx(j,{className:`text-sm truncate ${X?"font-semibold text-gray-900 dark:text-white":"font-normal text-gray-600 dark:text-gray-400"}`,children:G.title}),d.jsx(j,{className:`text-sm mt-1 line-clamp-2 ${X?"text-gray-700 dark:text-gray-300":"text-gray-500 dark:text-gray-500"}`,children:G.message}),d.jsx(j,{className:"text-gray-400 dark:text-gray-500 text-xs mt-1 font-mono",children:G.time})]}),d.jsxs(j,{className:"flex flex-col items-center gap-2 flex-shrink-0",children:[X&&d.jsx(te,{className:"w-2 h-2 bg-blue-500 dark:bg-blue-400 rounded-full mt-1.5"}),m&&d.jsx(R,{onClick:B=>{B.stopPropagation(),m(G)},className:"p-1 text-gray-400 dark:text-gray-500 hover:text-red-600 dark:hover:text-red-400 rounded hover:bg-red-50 dark:hover:bg-red-900/20 cursor-pointer opacity-0 group-hover:opacity-100 transition-opacity","aria-label":"Delete notification",children:d.jsx(fe,{name:nt.Trash,className:"w-4 h-4"})})]})]},G.id)}),r&&d.jsx(j,{ref:E,className:"px-4 py-3 text-center",children:n?d.jsx(te,{className:"text-sm text-gray-400 dark:text-gray-500",children:"..."}):d.jsx(te,{className:"text-sm text-gray-400 dark:text-gray-500"})}),n&&!r&&e.length>0&&d.jsx(j,{className:"px-4 py-2 text-center",children:d.jsx(te,{className:"text-sm text-gray-400 dark:text-gray-500",children:"..."})})]})})]})]})},jd=Object.freeze(Object.defineProperty({__proto__:null,NotificationCenter:On,default:On},Symbol.toStringTag,{value:"Module"})),Qe=(e,t)=>window.G7Core?.t?.(e,t)??e,vt=e=>{window.G7Core?.dispatch?.({handler:"navigate",params:{path:e}})},xs=({logo:e,siteName:t="그누보드7",user:r,cartCount:n=0,notificationCount:o=0,boards:a=[],maxVisibleBoards:s=5,onMobileMenuOpen:l,availableLocales:f=[],currentLocale:h="ko",shopBase:c="/shop",className:i="",editorAttrs:u,notifications:g=[],notificationHasMore:p=!1,notificationLoading:m=!1,notificationUnreadOnly:x=!1,notificationTitleText:k,notificationEmptyText:M,notificationMarkAllReadText:v,notificationDeleteAllText:T,notificationUnreadOnlyText:I,onNotificationClose:S,onNotificationClick:N,onNotificationLoadMore:w,onNotificationMarkAllRead:y,onNotificationDeleteAll:E,onNotificationDelete:O,onNotificationUnreadOnlyToggle:$})=>{const[P,L]=C.useState(""),[U,_]=C.useState(!1),[b,z]=C.useState(!1),[F,G]=C.useState(!1),[X,B]=C.useState({top:0,left:0}),[Y,H]=C.useState(window.location.pathname),W=C.useRef(null),Q=C.useRef(null),be=C.useRef(null),pe=window.G7Core?.useResponsive,ne=pe?.(),Ae=ne?ne.width<768:typeof window<"u"&&window.innerWidth<768;C.useEffect(()=>{const K=()=>H(window.location.pathname);window.addEventListener("popstate",K);const oe=history.pushState;return history.pushState=function(...ae){oe.apply(this,ae),H(window.location.pathname)},()=>{window.removeEventListener("popstate",K),history.pushState=oe}},[]);const ve=(K,oe=!1)=>oe?Y===K:Y===K||Y.startsWith(K+"/"),We=()=>{const K=c==="/"?"":c;return Y.startsWith(`${K}/products`)||Y.startsWith(`${K}/cart`)||Y.startsWith(`${K}/checkout`)||Y.startsWith(`${K}/category`)},me=K=>{const oe="px-3 py-2 text-sm font-medium whitespace-nowrap cursor-pointer rounded-lg transition-colors";return K?`${oe} bg-gray-900 text-white dark:bg-white dark:text-gray-900`:`${oe} text-gray-700 dark:text-gray-300 hover:text-gray-900 dark:hover:text-white hover:bg-gray-100 dark:hover:bg-gray-800`};C.useEffect(()=>{const K=oe=>{W.current&&!W.current.contains(oe.target)&&_(!1),Q.current&&!Q.current.contains(oe.target)&&z(!1),be.current&&!be.current.contains(oe.target)&&G(!1)};return document.addEventListener("mousedown",K),()=>document.removeEventListener("mousedown",K)},[]);const Te=a.slice(0,s),ge=a.slice(s),Oe=K=>{K.preventDefault(),P.trim()&&vt(`/search?q=${encodeURIComponent(P)}`)},we=async()=>{window.G7Core?.dispatch?.({handler:"logout"})},$e=K=>{window.G7Core?.dispatch?.({handler:"setLocale",target:K}),G(!1)},ze=K=>window.G7Core?.state?.get?.("_global.appConfig.localeNames")?.[K]??(K==="ko"?"한국어":K==="en"?"English":K==="ja"?"日本語":K==="zh"?"中文":K==="es"?"Español":K==="fr"?"Français":K==="de"?"Deutsch":K.toUpperCase());return d.jsxs(Pd,{...u??{},className:`sticky top-0 z-50 bg-white dark:bg-gray-900 border-b border-gray-200 dark:border-gray-800 ${i}`,children:[d.jsx(j,{className:"max-w-7xl mx-auto px-4 sm:px-6 lg:px-8",children:d.jsxs(j,{className:"flex items-center justify-between h-16",children:[d.jsx(R,{onClick:()=>vt("/"),className:"flex items-center gap-2 flex-shrink-0 cursor-pointer",children:e?d.jsx(jt,{src:e,alt:t,className:"h-8"}):d.jsx(te,{className:"text-xl font-bold text-gray-900 dark:text-white",children:t})}),!Ae&&d.jsx(Eo,{onSubmit:Oe,className:"flex flex-1 max-w-lg mx-8",children:d.jsxs(j,{className:"relative w-full",children:[d.jsx(At,{type:"text",value:P,onChange:K=>L(K.target.value),placeholder:Qe("common.search_placeholder"),className:"w-full px-4 py-2 pl-10 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-white focus:ring-2 focus:ring-blue-500 focus:border-blue-500"}),d.jsx(fe,{name:"search",className:"absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-gray-400 dark:text-gray-500"})]})}),d.jsxs(j,{className:"flex items-center gap-2",children:[d.jsx(An,{autoText:Qe("common.theme.auto"),lightText:Qe("common.theme.light"),darkText:Qe("common.theme.dark")}),r?.uuid&&d.jsx(On,{notifications:g,unreadCount:o,hasMore:p,loading:m,unreadOnly:x,titleText:k??Qe("mypage.notifications.title"),emptyText:M??Qe("mypage.notifications.empty"),markAllReadText:v??Qe("mypage.notifications.mark_all_read"),deleteAllText:T??Qe("mypage.notifications.delete_all"),unreadOnlyText:I??Qe("mypage.notifications.unread_only"),onClose:S,onNotificationClick:N,onLoadMore:w,onMarkAllRead:y,onDeleteAll:E,onDelete:O,onUnreadOnlyToggle:$,dropdownAlign:"right"}),d.jsxs(R,{onClick:()=>vt(`${c}/cart`),className:"relative p-2 text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white cursor-pointer",children:[d.jsx(fe,{name:"shopping-cart",className:"w-5 h-5"}),n>0&&d.jsx(te,{className:"absolute -top-1 -right-1 w-5 h-5 flex items-center justify-center text-xs bg-blue-500 text-white rounded-full",children:n>99?"99+":n})]}),f&&f.length>1&&d.jsxs(j,{ref:be,className:"relative",children:[d.jsxs(R,{onClick:()=>G(!F),className:"flex items-center gap-1.5 px-2.5 py-2 text-sm text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg cursor-pointer transition-colors","aria-haspopup":"listbox","aria-expanded":F,"aria-label":Qe("common.language"),children:[d.jsx(fe,{name:"globe",className:"w-4 h-4"}),d.jsx(te,{className:"font-medium uppercase",children:h}),d.jsx(fe,{name:"chevron-down",className:"w-3 h-3"})]}),F&&d.jsx(j,{role:"listbox",className:"absolute right-0 mt-2 w-44 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg shadow-lg py-1 z-50 overflow-hidden",children:f.map(K=>{const oe=K===h;return d.jsxs(R,{role:"option","aria-selected":oe,onClick:()=>$e(K),className:`w-full px-4 py-2.5 text-left text-sm flex items-center gap-3 cursor-pointer transition-colors ${oe?"bg-blue-50 dark:bg-blue-900/20 text-blue-600 dark:text-blue-400":"text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700"}`,children:[d.jsx(fe,{name:"globe",className:"w-4 h-4"}),d.jsx(te,{className:"flex-1 font-medium",children:ze(K)}),oe&&d.jsx(fe,{name:"check",className:"w-4 h-4"})]},K)})})]}),d.jsx(Nn,{slotId:"header_currency",id:"header_currency_slot_desktop",className:"flex items-center"}),r?.uuid?d.jsxs(j,{ref:W,className:"relative",children:[d.jsxs(R,{onClick:()=>_(!U),className:"flex items-center gap-2 p-2 text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white",children:[d.jsx(Qr,{avatar:r.avatar,name:r.name,size:"sm"}),d.jsx(te,{className:"hidden sm:inline text-sm font-medium",children:r.name}),d.jsx(fe,{name:"chevron-down",className:"w-4 h-4"})]}),U&&d.jsxs(j,{className:"absolute right-0 mt-2 w-56 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg shadow-lg py-1 z-50",children:[d.jsx(j,{className:"px-4 py-3 border-b border-gray-200 dark:border-gray-700",children:d.jsxs(j,{className:"flex items-center gap-3",children:[d.jsx(Qr,{avatar:r.avatar,name:r.name,size:"md"}),d.jsxs(j,{children:[d.jsx(j,{className:"text-sm font-medium text-gray-900 dark:text-white",children:r.name}),d.jsx(j,{className:"text-xs text-gray-500 dark:text-gray-400",children:Qe("common.member")})]})]})}),d.jsxs(j,{className:"py-1",children:[r.is_admin&&d.jsxs(d.Fragment,{children:[d.jsxs(So,{href:"/admin",className:"block w-full text-left px-4 py-2 text-sm text-blue-600 dark:text-blue-400 hover:bg-gray-100 dark:hover:bg-gray-700 cursor-pointer font-medium",children:[d.jsx(fe,{name:"settings",className:"inline w-4 h-4 mr-2"}),Qe("common.admin_menu")]}),d.jsx(To,{className:"my-1 border-gray-200 dark:border-gray-700"})]}),d.jsxs(R,{onClick:()=>{vt("/mypage"),_(!1)},className:"block w-full text-left px-4 py-2 text-sm text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 cursor-pointer",children:[d.jsx(fe,{name:"user",className:"inline w-4 h-4 mr-2"}),Qe("common.mypage")]}),d.jsxs(R,{onClick:()=>{vt("/mypage/orders"),_(!1)},className:"block w-full text-left px-4 py-2 text-sm text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 cursor-pointer",children:[d.jsx(fe,{name:"shopping-bag",className:"inline w-4 h-4 mr-2"}),Qe("mypage.tabs.orders")]}),d.jsxs(R,{onClick:()=>{vt("/mypage/wishlist"),_(!1)},className:"block w-full text-left px-4 py-2 text-sm text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 cursor-pointer",children:[d.jsx(fe,{name:"heart",className:"inline w-4 h-4 mr-2"}),Qe("mypage.tabs.wishlist")]})]}),d.jsx(To,{className:"my-1 border-gray-200 dark:border-gray-700"}),d.jsxs(R,{onClick:we,className:"block w-full text-left px-4 py-2 text-sm text-red-600 dark:text-red-400 hover:bg-gray-100 dark:hover:bg-gray-700 cursor-pointer",children:[d.jsx(fe,{name:"log-out",className:"inline w-4 h-4 mr-2"}),Qe("auth.logout")]})]})]}):d.jsxs(j,{className:"flex items-center gap-1.5",children:[d.jsx(R,{onClick:()=>vt("/login?redirect=/mypage/orders"),className:"px-2.5 py-2 text-sm font-medium text-gray-700 dark:text-gray-300 hover:text-gray-900 dark:hover:text-white cursor-pointer",children:Qe("shop.guest_order_form.nav_link")}),d.jsx(R,{onClick:()=>vt("/login"),className:"px-2.5 py-2 text-sm font-medium text-gray-700 dark:text-gray-300 hover:text-gray-900 dark:hover:text-white cursor-pointer",children:Qe("auth.login")}),d.jsx(R,{onClick:()=>vt("/register"),className:"ml-1 px-3 py-2 text-sm font-medium text-white bg-emerald-500 rounded-lg hover:bg-emerald-600 cursor-pointer",children:Qe("auth.register_link")})]}),Ae&&d.jsx(R,{onClick:l,className:"p-2 text-gray-600 dark:text-gray-400",children:d.jsx(fe,{name:"menu",className:"w-6 h-6"})})]})]})}),!Ae&&d.jsx(Zr,{className:"border-t border-gray-200 dark:border-gray-800",children:d.jsx(j,{className:"max-w-7xl mx-auto px-4 sm:px-6 lg:px-8",children:d.jsxs(j,{className:"flex items-center gap-1 h-12 overflow-x-auto",children:[d.jsx(R,{onClick:()=>vt("/"),className:me(ve("/",!0)),"data-testid":"nav-home",children:Qe("nav.home")}),d.jsxs(R,{onClick:()=>vt("/boards/popular"),className:`flex items-center gap-1 ${me(ve("/boards/popular"))}`,"data-testid":"nav-popular",children:[d.jsx(te,{className:"text-orange-500",children:"🔥"}),Qe("nav.popular")]}),d.jsxs(R,{onClick:()=>vt(`${c==="/"?"":c}/products`),className:`flex items-center gap-1 ${me(We())}`,"data-testid":"nav-shop",children:[d.jsx(te,{children:"🛒"}),Qe("nav.shop")]}),Te.map(K=>d.jsxs(R,{onClick:()=>vt(`/board/${K.slug}`),className:me(ve(`/board/${K.slug}`)),children:[K.icon&&d.jsx(te,{className:"mr-1",children:K.icon}),K.name]},K.id)),ge.length>0&&d.jsxs(j,{ref:Q,className:"relative",children:[d.jsxs(R,{onClick:()=>{if(!b&&Q.current){const K=Q.current.getBoundingClientRect();B({top:K.bottom+4,left:K.left})}z(!b)},className:"flex items-center gap-1 px-3 py-2 text-sm font-medium text-gray-700 dark:text-gray-300 hover:text-gray-900 dark:hover:text-white",children:[Qe("nav.more"),d.jsx(fe,{name:"chevron-down",className:"w-4 h-4"})]}),b&&d.jsx(j,{className:"fixed w-48 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg shadow-lg py-1 z-50",style:{top:`${X.top}px`,left:`${X.left}px`},children:ge.map(K=>{const oe=ve(`/board/${K.slug}`);return d.jsxs(R,{onClick:()=>{vt(`/board/${K.slug}`),z(!1)},className:`block w-full text-left px-4 py-2 text-sm cursor-pointer ${oe?"bg-gray-900 text-white dark:bg-white dark:text-gray-900":"text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700"}`,children:[K.icon&&d.jsx(te,{className:"mr-1",children:K.icon}),K.name]},K.id)})})]})]})})})]})},Bd=Object.freeze(Object.defineProperty({__proto__:null,default:xs},Symbol.toStringTag,{value:"Module"})),Wd=C.forwardRef(({children:e,className:t="",...r},n)=>d.jsx("footer",{ref:n,className:t,...r,children:e})),Nt=(e,t)=>window.G7Core?.t?.(e,t)??e,Vd=768,ks=1024,Kd=e=>{window.G7Core?.dispatch?.({handler:"navigate",params:{path:e}})},Cs=({siteName:e="그누보드7",siteDescription:t,copyrightText:r,socialLinks:n={},linkGroups:o,className:a="",editorAttrs:s})=>{const l=new Date().getFullYear(),h=window.G7Core?.useResponsive,i=h?.()?.width??(typeof window<"u"?window.innerWidth:ks),u=i=ks,p=u?1:g?5:2,m=[{title:Nt("footer.community"),links:[{label:Nt("nav.home"),href:"/"},{label:Nt("nav.popular"),href:"/boards/popular"},{label:Nt("footer.all_boards"),href:"/boards"}]},{title:Nt("footer.info"),links:[{label:Nt("footer.about"),href:"/page/about"},{label:Nt("footer.faq"),href:"/page/faq"},{label:Nt("footer.contact"),href:"/page/contact"}]},{title:Nt("footer.policy"),links:[{label:Nt("footer.terms"),href:"/page/terms"},{label:Nt("footer.privacy"),href:"/page/privacy"},{label:Nt("footer.refund"),href:"/page/refund"}]}],x=o||m,k={github:"github",twitter:"twitter",discord:"discord",facebook:"facebook",instagram:"instagram"};return d.jsx(Wd,{...s??{},className:`bg-gray-50 dark:bg-gray-900 border-t border-gray-200 dark:border-gray-800 ${a}`,children:d.jsxs(j,{className:"max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-12",children:[d.jsxs(j,{className:"grid gap-8",style:{gridTemplateColumns:`repeat(${p}, minmax(0, 1fr))`},children:[d.jsxs(j,{style:g?{gridColumn:"span 2 / span 2"}:void 0,children:[d.jsx(Tn,{className:"text-lg font-bold text-gray-900 dark:text-white",children:e}),t&&d.jsx(Gt,{className:"mt-2 text-sm text-gray-600 dark:text-gray-400",children:t}),d.jsx(j,{className:"mt-4 flex items-center gap-4",children:Object.entries(n).map(([M,v])=>v?d.jsx(So,{href:v,target:"_blank",rel:"noopener noreferrer",className:"text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200","aria-label":M,children:d.jsx(fe,{name:k[M],className:"w-5 h-5"})},M):null)})]}),x.map((M,v)=>d.jsxs(j,{children:[d.jsx(hs,{className:"text-sm font-semibold text-gray-900 dark:text-white uppercase tracking-wider",children:M.title}),d.jsx(_o,{className:"mt-4 space-y-2",children:M.links.map((T,I)=>d.jsx(er,{children:d.jsx(R,{onClick:()=>Kd(T.href),className:"text-sm text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white cursor-pointer",children:T.label})},I))})]},v))]}),d.jsxs(j,{className:"mt-8 pt-8 border-t border-gray-200 dark:border-gray-800 flex justify-between items-center gap-4",style:{flexDirection:u?"column":"row"},children:[d.jsx(Gt,{className:"text-sm text-gray-500 dark:text-gray-400",children:r||`© ${l} ${e}. All rights reserved.`}),d.jsx(Gt,{className:"text-sm text-gray-500 dark:text-gray-400",children:Nt("footer.powered_by")})]})]})})},Yd=Object.freeze(Object.defineProperty({__proto__:null,default:Cs},Symbol.toStringTag,{value:"Module"})),Bt=(e,t)=>window.G7Core?.t?.(e,t)??e,Lt=e=>{window.G7Core?.dispatch?.({handler:"navigate",params:{path:e}})},_s=({isOpen:e,onClose:t,logo:r,siteName:n="그누보드7",user:o,boards:a=[],cartCount:s=0})=>{const l=C.useRef(null),h=window.G7Core?.useResponsive,c=h?.(),i=c?c.width<1024:typeof window<"u"&&window.innerWidth<1024;C.useEffect(()=>{const g=p=>{p.key==="Escape"&&t()};return e&&(document.addEventListener("keydown",g),document.body.style.overflow="hidden"),()=>{document.removeEventListener("keydown",g),document.body.style.overflow=""}},[e,t]);const u=g=>{g.target===g.currentTarget&&t()};return!e||!i?null:d.jsxs(j,{className:"fixed inset-0 z-50",onClick:u,children:[d.jsx(j,{className:"fixed inset-0 bg-black/50 transition-opacity"}),d.jsxs(j,{ref:l,className:`fixed inset-y-0 left-0 w-80 max-w-[85vw] bg-white dark:bg-gray-900 shadow-xl transform transition-transform duration-300 ${e?"translate-x-0":"-translate-x-full"}`,children:[d.jsxs(j,{className:"flex items-center justify-between p-4 border-b border-gray-200 dark:border-gray-800",children:[d.jsx(R,{onClick:()=>{t(),Lt("/")},className:"flex items-center gap-2 cursor-pointer",children:r?d.jsx(jt,{src:r,alt:n,className:"h-8"}):d.jsx(te,{className:"text-xl font-bold text-gray-900 dark:text-white",children:n})}),d.jsx(R,{onClick:t,className:"p-2 text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200","aria-label":"메뉴 닫기",children:d.jsx(fe,{name:"x",className:"w-6 h-6"})})]}),o?d.jsx(j,{className:"p-4 border-b border-gray-200 dark:border-gray-800",children:d.jsxs(j,{className:"flex items-center gap-3",children:[o.avatar?d.jsx(jt,{src:o.avatar,alt:o.name,className:"w-12 h-12 rounded-full"}):d.jsx(j,{className:"w-12 h-12 rounded-full bg-blue-500 flex items-center justify-center text-white text-lg font-medium",children:(o.name||"U").charAt(0).toUpperCase()}),d.jsxs(j,{children:[d.jsx(Gt,{className:"font-medium text-gray-900 dark:text-white",children:o.name}),d.jsx(R,{onClick:()=>{t(),Lt("/mypage")},className:"text-sm text-blue-600 dark:text-blue-400 hover:underline cursor-pointer",children:Bt("common.mypage")})]})]})}):d.jsx(j,{className:"p-4 border-b border-gray-200 dark:border-gray-800",children:d.jsxs(j,{className:"flex gap-2",children:[d.jsx(R,{onClick:()=>{t(),Lt("/login")},className:"flex-1 py-2 text-center text-sm font-medium text-white bg-gray-900 dark:bg-white dark:text-gray-900 rounded-lg cursor-pointer",children:Bt("auth.login")}),d.jsx(R,{onClick:()=>{t(),Lt("/register")},className:"flex-1 py-2 text-center text-sm font-medium text-gray-700 dark:text-gray-300 border border-gray-300 dark:border-gray-600 rounded-lg cursor-pointer",children:Bt("auth.register")})]})}),d.jsx(Zr,{className:"p-4 overflow-y-auto max-h-[calc(100vh-200px)]",children:d.jsxs(_o,{className:"space-y-1",children:[d.jsx(er,{children:d.jsxs(R,{onClick:()=>{t(),Lt("/")},className:"flex items-center gap-3 px-3 py-2 w-full text-left text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-800 rounded-lg cursor-pointer",children:[d.jsx(fe,{name:"home",className:"w-5 h-5"}),Bt("nav.home")]})}),d.jsx(er,{children:d.jsxs(R,{onClick:()=>{t(),Lt("/popular")},className:"flex items-center gap-3 px-3 py-2 w-full text-left text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-800 rounded-lg cursor-pointer",children:[d.jsx(te,{className:"text-orange-500",children:"🔥"}),Bt("nav.popular")]})}),d.jsx(er,{children:d.jsxs(R,{onClick:()=>{t(),Lt("/shop")},className:"flex items-center gap-3 px-3 py-2 w-full text-left text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-800 rounded-lg cursor-pointer",children:[d.jsx(te,{children:"🛒"}),Bt("nav.shop")]})}),d.jsx(er,{children:d.jsxs(R,{onClick:()=>{t(),Lt("/cart")},className:"flex items-center gap-3 px-3 py-2 w-full text-left text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-800 rounded-lg cursor-pointer",children:[d.jsx(fe,{name:"shopping-cart",className:"w-5 h-5"}),Bt("nav.cart"),s>0&&d.jsx(te,{className:"ml-auto px-2 py-0.5 text-xs bg-blue-500 text-white rounded-full",children:s})]})}),d.jsx(er,{className:"my-4 border-t border-gray-200 dark:border-gray-800"}),d.jsx(er,{className:"px-3 py-2 text-xs font-semibold text-gray-500 dark:text-gray-400 uppercase tracking-wider",children:Bt("nav.boards")}),a.map(g=>d.jsx(er,{children:d.jsxs(R,{onClick:()=>{t(),Lt(`/board/${g.slug}`)},className:"flex items-center gap-3 px-3 py-2 w-full text-left text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-800 rounded-lg cursor-pointer",children:[g.icon&&d.jsx(te,{children:g.icon}),g.name]})},g.id))]})}),d.jsx(j,{className:"absolute bottom-0 left-0 right-0 p-4 border-t border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-900",children:d.jsxs(j,{className:"flex items-center justify-between text-sm text-gray-500 dark:text-gray-400",children:[d.jsx(R,{onClick:()=>{t(),Lt("/about")},className:"hover:text-gray-700 dark:hover:text-gray-200 cursor-pointer",children:Bt("footer.about")}),d.jsx(R,{onClick:()=>{t(),Lt("/terms")},className:"hover:text-gray-700 dark:hover:text-gray-200 cursor-pointer",children:Bt("footer.terms")}),d.jsx(R,{onClick:()=>{t(),Lt("/privacy")},className:"hover:text-gray-700 dark:hover:text-gray-200 cursor-pointer",children:Bt("footer.privacy")})]})})]})]})},Xd=Object.freeze(Object.defineProperty({__proto__:null,default:_s},Symbol.toStringTag,{value:"Module"}));function Ss(e,t){(t==null||t>e.length)&&(t=e.length);for(var r=0,n=Array(t);r2?n-2:0),a=2;a1?r-1:0),o=1;o"u"?null:tt(BigInt.prototype.toString),Ds=typeof Symbol>"u"?null:tt(Symbol.prototype.toString),wt=tt(Object.prototype.hasOwnProperty),tn=tt(Object.prototype.toString),gt=tt(RegExp.prototype.test),ur=iu(TypeError);function tt(e){return function(t){t instanceof RegExp&&(t.lastIndex=0);for(var r=arguments.length,n=new Array(r>1?r-1:0),o=1;o2&&arguments[2]!==void 0?arguments[2]:Rr;if(Ts&&Ts(e,null),!Or(t))return e;let n=t.length;for(;n--;){let o=t[n];if(typeof o=="string"){const a=r(o);a!==o&&(eu(t)||(t[n]=a),o=a)}e[o]=!0}return e}function cu(e){for(let t=0;t/g),mu=lt(/\${[\w\W]*/g),bu=lt(/^data-[\-\w.\u00B7-\uFFFF]+$/),yu=lt(/^aria-[\-\w]+$/),Us=lt(/^(?:(?:(?:f|ht)tps?|mailto|tel|callto|sms|cid|xmpp|matrix):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i),vu=lt(/^(?:\w+script|data):/i),wu=lt(/[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205F\u3000]/g),xu=lt(/^html$/i),ku=lt(/^[a-z][.\w]*(-[.\w]+)+$/i),zs=lt(/<[/\w!]/g),Hs=lt(/<[/\w]/g),Cu=lt(/<\/no(script|embed|frames)/i),_u=lt(/\/>/i),Mt={element:1,attribute:2,text:3,cdataSection:4,entityReference:5,entityNode:6,processingInstruction:7,comment:8,document:9,documentType:10,documentFragment:11,notation:12},Gs=["style","script","xmp","iframe","noembed","noframes","plaintext","noscript"],Su=ot(Ge({},Gs)),Eu=(function(){const e={};return dr(Gs,t=>{e[t]=lt(new RegExp("])","i"))}),ot(e)})(),Tu=function(){return typeof window>"u"?null:window},Au=function(t,r){if(typeof t!="object"||typeof t.createPolicy!="function")return null;let n=null;const o="data-tt-policy-suffix";r&&r.hasAttribute(o)&&(n=r.getAttribute(o));const a="dompurify"+(n?"#"+n:"");try{return t.createPolicy(a,{createHTML(s){return s},createScriptURL(s){return s}})}catch{return console.warn("TrustedTypes policy "+a+" could not be created."),null}},js=function(){return{afterSanitizeAttributes:[],afterSanitizeElements:[],afterSanitizeShadowDOM:[],beforeSanitizeAttributes:[],beforeSanitizeElements:[],beforeSanitizeShadowDOM:[],uponSanitizeAttribute:[],uponSanitizeElement:[],uponSanitizeShadowNode:[]}},rr=function(t,r,n,o){return wt(t,r)&&Or(t[r])?Ge(o.base?Ot(o.base):{},t[r],o.transform):n},Fo=function(t,r,n){const o=wt(t,r)?t[r]:void 0;return o&&typeof o=="object"?Ot(o):n()};function Bs(){let e=arguments.length>0&&arguments[0]!==void 0?arguments[0]:Tu();const t=J=>Bs(J);if(t.version="3.4.14",t.removed=[],!e||!e.document||e.document.nodeType!==Mt.document||!e.Element)return t.isSupported=!1,t;let r=e.document;const n=r,o=n.currentScript;e.DocumentFragment;const a=e.HTMLTemplateElement,s=e.Node,l=e.Element,f=e.NodeFilter,h=e.NamedNodeMap;h===void 0&&(e.NamedNodeMap||e.MozNamedAttrMap),e.HTMLFormElement;const c=e.DOMParser,i=e.trustedTypes,u=l.prototype,g=Wt(u,"cloneNode"),p=Wt(u,"remove"),m=Wt(u,"nextSibling"),x=Wt(u,"childNodes"),k=Wt(u,"parentNode"),M=Wt(u,"shadowRoot"),v=Wt(u,"attributes"),T=s&&s.prototype?Wt(s.prototype,"nodeType"):null,I=s&&s.prototype?Wt(s.prototype,"nodeName"):null,S=s&&s.prototype?Wt(s.prototype,"ownerDocument"):null,N=function(D){return T?T(D):D.nodeType},w=function(D){return I?I(D):D.nodeName};if(typeof a=="function"){const J=r.createElement("template");J.content&&J.content.ownerDocument&&(r=J.content.ownerDocument)}let y,E="",O,$=!1,P=0;const L=function(){if(P>0)throw ur('A configured TRUSTED_TYPES_POLICY callback (createHTML or createScriptURL) must not call DOMPurify.sanitize, as that causes infinite recursion. Do not pass a policy whose callbacks wrap DOMPurify as TRUSTED_TYPES_POLICY; see the "DOMPurify and Trusted Types" section of the README.')},U=function(D){L(),P++;try{return y.createHTML(D)}finally{P--}},_=function(D){L(),P++;try{return y.createScriptURL(D)}finally{P--}},b=function(){return $||(O=Au(i,o),$=!0),O},z=r,F=z.implementation,G=z.createNodeIterator,X=z.createDocumentFragment,B=z.getElementsByTagName,Y=n.importNode;let H=js();t.isSupported=typeof Es=="function"&&typeof k=="function"&&F&&F.createHTMLDocument!==void 0;const W=hu,Q=pu,be=mu,se=bu,pe=yu,ne=vu,Ae=wu,ve=ku;let We=Us,me=null;const Te=Ge({},[...Ps,...Do,...Po,...Lo,...Ls]);let ge=null;const Oe=Ge({},[...$s,...$o,...Fs,...Mn]);let we=Object.seal(Nr(null,{tagNameCheck:{writable:!0,configurable:!1,enumerable:!0,value:null},attributeNameCheck:{writable:!0,configurable:!1,enumerable:!0,value:null},allowCustomizedBuiltInElements:{writable:!0,configurable:!1,enumerable:!0,value:!1}})),$e=null,ze=null;const K=Object.seal(Nr(null,{tagCheck:{writable:!0,configurable:!1,enumerable:!0,value:null},attributeCheck:{writable:!0,configurable:!1,enumerable:!0,value:null}}));let oe=!0,ae=!0,ce=!1,ke=!0,Ee=!1,Be=!0,Me=!1,Ke=!1,Pe=null,q=null,le=!1,de=!1,ue=!1,ie=!1,Fe=!0,Ce=!1;const Ie="user-content-";let Ne=!0,He=!1,je={},_e=null;const xe=Ge({},["annotation-xml","audio","colgroup","desc","foreignobject","head","iframe","math","mi","mn","mo","ms","mtext","noembed","noframes","noscript","plaintext","script","selectedcontent","style","svg","template","thead","title","video","xmp"]);let De=null;const Ye=Ge({},["audio","video","img","source","image","track"]);let rt=null;const qe=Ge({},["alt","class","for","id","label","name","pattern","placeholder","role","summary","title","value","style","xmlns"]),Je="http://www.w3.org/1998/Math/MathML",Zt="http://www.w3.org/2000/svg",Ve="http://www.w3.org/1999/xhtml";let bt=Ve,St=!1,Pt=null;const _r=Ge({},[Je,Zt,Ve],Io),lr=ot(["mi","mo","mn","ms","mtext"]);let Ht=Ge({},lr);const ir=ot(["annotation-xml"]);let Sr=Ge({},ir);const Vm=Ge({},["title","style","font","a","script"]);let _n=null;const Km=["application/xhtml+xml","text/html"],Ym="text/html";let et=null,Xr=null;const Xm=r.createElement("form"),od=function(D){return D instanceof RegExp||D instanceof Function},ss=function(){let D=arguments.length>0&&arguments[0]!==void 0?arguments[0]:{};if(Xr&&Xr===D)return;(!D||typeof D!="object")&&(D={}),D=Ot(D),_n=Km.indexOf(D.PARSER_MEDIA_TYPE)===-1?Ym:D.PARSER_MEDIA_TYPE,et=_n==="application/xhtml+xml"?Io:Rr,me=rr(D,"ALLOWED_TAGS",Te,{transform:et}),ge=rr(D,"ALLOWED_ATTR",Oe,{transform:et}),Pt=rr(D,"ALLOWED_NAMESPACES",_r,{transform:Io}),rt=rr(D,"ADD_URI_SAFE_ATTR",qe,{transform:et,base:qe}),De=rr(D,"ADD_DATA_URI_TAGS",Ye,{transform:et,base:Ye}),_e=rr(D,"FORBID_CONTENTS",xe,{transform:et}),$e=rr(D,"FORBID_TAGS",Ot({}),{transform:et}),ze=rr(D,"FORBID_ATTR",Ot({}),{transform:et}),je=wt(D,"USE_PROFILES")?D.USE_PROFILES&&typeof D.USE_PROFILES=="object"?Ot(D.USE_PROFILES):D.USE_PROFILES:!1,oe=D.ALLOW_ARIA_ATTR!==!1,ae=D.ALLOW_DATA_ATTR!==!1,ce=D.ALLOW_UNKNOWN_PROTOCOLS||!1,ke=D.ALLOW_SELF_CLOSE_IN_ATTR!==!1,Ee=D.SAFE_FOR_TEMPLATES||!1,Be=D.SAFE_FOR_XML!==!1,Me=D.WHOLE_DOCUMENT||!1,de=D.RETURN_DOM||!1,ue=D.RETURN_DOM_FRAGMENT||!1,ie=D.RETURN_TRUSTED_TYPE||!1,le=D.FORCE_BODY||!1,Fe=D.SANITIZE_DOM!==!1,Ce=D.SANITIZE_NAMED_PROPS||!1,Ne=D.KEEP_CONTENT!==!1,He=D.IN_PLACE||!1,We=uu(D.ALLOWED_URI_REGEXP)?D.ALLOWED_URI_REGEXP:Us,bt=typeof D.NAMESPACE=="string"?D.NAMESPACE:Ve,Ht=Fo(D,"MATHML_TEXT_INTEGRATION_POINTS",()=>Ge({},lr)),Sr=Fo(D,"HTML_INTEGRATION_POINTS",()=>Ge({},ir));const V=Fo(D,"CUSTOM_ELEMENT_HANDLING",()=>Nr(null));if(we=Nr(null),wt(V,"tagNameCheck")&&od(V.tagNameCheck)&&(we.tagNameCheck=V.tagNameCheck),wt(V,"attributeNameCheck")&&od(V.attributeNameCheck)&&(we.attributeNameCheck=V.attributeNameCheck),wt(V,"allowCustomizedBuiltInElements")&&typeof V.allowCustomizedBuiltInElements=="boolean"&&(we.allowCustomizedBuiltInElements=V.allowCustomizedBuiltInElements),lt(we),Ee&&(ae=!1),ue&&(de=!0),je&&(me=Ge({},Ls),ge=Nr(null),je.html===!0&&(Ge(me,Ps),Ge(ge,$s)),je.svg===!0&&(Ge(me,Do),Ge(ge,$o),Ge(ge,Mn)),je.svgFilters===!0&&(Ge(me,Po),Ge(ge,$o),Ge(ge,Mn)),je.mathMl===!0&&(Ge(me,Lo),Ge(ge,Fs),Ge(ge,Mn))),K.tagCheck=null,K.attributeCheck=null,wt(D,"ADD_TAGS")&&(typeof D.ADD_TAGS=="function"?K.tagCheck=D.ADD_TAGS:Or(D.ADD_TAGS)&&(me===Te&&(me=Ot(me)),Ge(me,D.ADD_TAGS,et))),wt(D,"ADD_ATTR")&&(typeof D.ADD_ATTR=="function"?K.attributeCheck=D.ADD_ATTR:Or(D.ADD_ATTR)&&(ge===Oe&&(ge=Ot(ge)),Ge(ge,D.ADD_ATTR,et))),wt(D,"ADD_FORBID_CONTENTS")&&Or(D.ADD_FORBID_CONTENTS)&&(_e===xe&&(_e=Ot(_e)),Ge(_e,D.ADD_FORBID_CONTENTS,et)),Ne&&(me["#text"]=!0),Me&&Ge(me,["html","head","body"]),me.table&&(Ge(me,["tbody"]),delete $e.tbody),D.TRUSTED_TYPES_POLICY){if(typeof D.TRUSTED_TYPES_POLICY.createHTML!="function")throw ur('TRUSTED_TYPES_POLICY configuration option must provide a "createHTML" hook.');if(typeof D.TRUSTED_TYPES_POLICY.createScriptURL!="function")throw ur('TRUSTED_TYPES_POLICY configuration option must provide a "createScriptURL" hook.');const Z=y;y=D.TRUSTED_TYPES_POLICY;try{E=U("")}catch(re){throw y=Z,re}}else D.TRUSTED_TYPES_POLICY===null?(y=void 0,E=""):(y===void 0&&(y=b()),y&&typeof E=="string"&&(E=U("")));ot&&ot(D),Xr=D},ad=Ge({},[...Do,...Po,...fu]),sd=Ge({},[...Lo,...gu]),qm=function(D,V,Z){return V.namespaceURI===Ve?D==="svg":V.namespaceURI===Je?D==="svg"&&(Z==="annotation-xml"||Ht[Z]):!!ad[D]},Zm=function(D,V,Z){return V.namespaceURI===Ve?D==="math":V.namespaceURI===Zt?D==="math"&&Sr[Z]:!!sd[D]},Qm=function(D,V,Z){return V.namespaceURI===Zt&&!Sr[Z]||V.namespaceURI===Je&&!Ht[Z]?!1:!sd[D]&&(Vm[D]||!ad[D])},Jm=function(D){let V=k(D);(!V||!V.tagName)&&(V={namespaceURI:bt,tagName:"template"});const Z=Rr(D.tagName),re=Rr(V.tagName);return Pt[D.namespaceURI]?D.namespaceURI===Zt?qm(Z,V,re):D.namespaceURI===Je?Zm(Z,V,re):D.namespaceURI===Ve?Qm(Z,V,re):!!(_n==="application/xhtml+xml"&&Pt[D.namespaceURI]):!1},cr=function(D){Jr(t.removed,{element:D});try{k(D).removeChild(D)}catch{if(p(D),!k(D))throw ur("a node selected for removal could not be detached from its tree and cannot be safely returned; refusing to sanitize in place")}},ld=function(D,V,Z){try{D.removeAttributeNode(V)}catch{try{D.removeAttribute(Z)}catch{}}},mo=function(D){bo(D);const V=x(D);if(V){const re=[];dr(V,he=>{Jr(re,he)}),dr(re,he=>{try{p(he)}catch{}})}const Z=v(D);if(Z)for(let re=Z.length-1;re>=0;--re){const he=Z[re],Se=he&&he.name;typeof Se=="string"&&ld(D,he,Se)}},Er=function(D,V,Z){if(!Z)try{Z=V.getAttributeNode(D)}catch{Z=null}Jr(t.removed,{attribute:Z||null,from:V});try{Z?V.removeAttributeNode(Z):V.removeAttribute(D)}catch{try{V.removeAttribute(D)}catch{}}if(D==="is")if(de||ue)try{cr(V)}catch{}else try{V.setAttribute(D,"")}catch{}},Rm=function(D){const V=v(D);if(V)for(let Z=V.length-1;Z>=0;--Z){const re=V[Z],he=re&&re.name;typeof he!="string"||ge[et(he)]||ld(D,re,he)}},bo=function(D){const V=[D];for(;V.length>0;){const Z=V.pop();N(Z)===Mt.element&&Rm(Z);const he=x(Z);if(he)for(let Se=he.length-1;Se>=0;--Se)V.push(he[Se])}},id=function(D,V){return Be?D==="patchsrc"?!0:D==="for"&&V!=="label"&&V!=="output":!1},eb=function(D){if(!Be)return;const V=[D];for(;V.length>0;){const Z=V.pop(),re=N(Z);if(re===Mt.processingInstruction||re===Mt.comment&>(Hs,Z.data)){try{p(Z)}catch{}continue}if(re===Mt.element){const Se=Z,Ze=et(w(Z));try{Se.hasAttribute&&Se.hasAttribute("patchsrc")&&Se.removeAttribute("patchsrc"),Se.hasAttribute&&Se.hasAttribute("for")&&id("for",Ze)&&Se.removeAttribute("for")}catch{}}const he=x(Z);if(he)for(let Se=he.length-1;Se>=0;--Se)V.push(he[Se])}},cd=function(D){let V=null,Z=null;if(le)D=""+D;else{const Se=Os(D,/^[\r\n\t ]+/);Z=Se&&Se[0]}_n==="application/xhtml+xml"&&bt===Ve&&(D=''+D+"");const re=y?U(D):D;if(bt===Ve)try{V=new c().parseFromString(re,_n)}catch{}if(!V||!V.documentElement){V=F.createDocument(bt,"template",null);try{V.documentElement.innerHTML=St?E:re}catch{}}const he=V.body||V.documentElement;return D&&Z&&he.insertBefore(r.createTextNode(Z),he.childNodes[0]||null),bt===Ve?B.call(V,Me?"html":"body")[0]:Me?V.documentElement:he},dd=function(D){const V=S?S(D):D.ownerDocument;return G.call(V||D,D,f.SHOW_ELEMENT|f.SHOW_COMMENT|f.SHOW_TEXT|f.SHOW_PROCESSING_INSTRUCTION|f.SHOW_CDATA_SECTION,null)},yo=function(D){return D=en(D,W," "),D=en(D,Q," "),D=en(D,be," "),D},ls=function(D){var V;D.normalize();const Z=S?S(D):D.ownerDocument,re=G.call(Z||D,D,f.SHOW_TEXT|f.SHOW_COMMENT|f.SHOW_CDATA_SECTION|f.SHOW_PROCESSING_INSTRUCTION,null);let he=re.nextNode();for(;he;)he.data=yo(he.data),he=re.nextNode();const Se=(V=D.querySelectorAll)===null||V===void 0?void 0:V.call(D,"template");Se&&dr(Se,Ze=>{qr(Ze.content)&&ls(Ze.content)})},vo=function(D){const V=I?I(D):null;return typeof V!="string"||et(V)!=="form"?!1:typeof D.nodeName!="string"||typeof D.textContent!="string"||typeof D.removeChild!="function"||D.attributes!==v(D)||typeof D.removeAttribute!="function"||typeof D.setAttribute!="function"||typeof D.namespaceURI!="string"||typeof D.insertBefore!="function"||typeof D.hasChildNodes!="function"||D.nodeType!==T(D)||D.childNodes!==x(D)},qr=function(D){if(!T||typeof D!="object"||D===null)return!1;try{return T(D)===Mt.documentFragment}catch{return!1}},Sn=function(D){if(!T||typeof D!="object"||D===null)return!1;try{return typeof T(D)=="number"}catch{return!1}};function Rt(J,D,V){J.length!==0&&dr(J,Z=>{Z.call(t,D,V,Xr)})}const tb=function(D,V){return!!(Be&&D.hasChildNodes()&&!Sn(D.firstElementChild)&>(zs,D.textContent)&>(zs,D.innerHTML)||Be&&D.namespaceURI===Ve&&Su[V]&&(Sn(D.firstElementChild)||typeof D.textContent=="string"&>(Eu[V],D.textContent))||D.nodeType===Mt.processingInstruction||Be&&D.nodeType===Mt.comment&>(Hs,D.data))},wo=function(D,V){if(D instanceof RegExp)return gt(D,V);if(D instanceof Function){for(var Z=arguments.length,re=new Array(Z>2?Z-2:0),he=2;he=0;--Ze){const Re=D===Z?g(he[Ze],!0):he[Ze];re.insertBefore(Re,m(D))}}}return cr(D),!0},ud=function(D,V,Z,re){return D.length===0?V:V===Z||V===re?Ot(V):V},fd=function(D,V){return D===V||k(D)!==null?!1:(He&&bo(D),!0)},gd=function(D,V){if(Rt(H.beforeSanitizeElements,D,null),fd(D,V))return!0;if(vo(D))return cr(D),!0;const Z=et(w(D));if(me=ud(H.uponSanitizeElement,me,Te,Pe),Rt(H.uponSanitizeElement,D,{tagName:Z,allowedTags:me}),fd(D,V))return!0;if(tb(D,Z))return cr(D),!0;if($e[Z]||!(K.tagCheck instanceof Function&&K.tagCheck(Z))&&!me[Z]){const he=rb(D,Z,V);return he===!1&&Rt(H.afterSanitizeElements,D,null),he}if(N(D)===Mt.element&&!Jm(D)||(Z==="noscript"||Z==="noembed"||Z==="noframes")&>(Cu,D.innerHTML))return cr(D),!0;if(Ee&&D.nodeType===Mt.text){const he=yo(D.textContent);D.textContent!==he&&(Jr(t.removed,{element:D.cloneNode()}),D.textContent=he)}return Rt(H.afterSanitizeElements,D,null),!1},hd=function(D,V,Z){if(ze[V]||id(V,D)||Fe&&(V==="id"||V==="name")&&(Z in r||Z in Xm))return!1;const re=ge[V]||K.attributeCheck instanceof Function&&K.attributeCheck(V,D);return ae&>(se,V)||oe&>(pe,V)?!0:re?rt[V]||gt(We,en(Z,Ae,""))||(V==="src"||V==="xlink:href"||V==="href")&&D!=="script"&&Ms(Z,"data:")===0&&De[D]||ce&&!gt(ne,en(Z,Ae,""))?!0:!Z:pd(D)&&wo(we.tagNameCheck,D)&&wo(we.attributeNameCheck,V,D)||V==="is"&&we.allowCustomizedBuiltInElements&&wo(we.tagNameCheck,Z)},nb=Ge({},["annotation-xml","color-profile","font-face","font-face-format","font-face-name","font-face-src","font-face-uri","missing-glyph"]),pd=function(D){return!nb[Rr(D)]&>(ve,D)},ob=function(D,V,Z,re){if(y&&typeof i=="object"&&typeof i.getAttributeType=="function"&&!Z)switch(i.getAttributeType(D,V)){case"TrustedHTML":return U(re);case"TrustedScriptURL":return _(re)}return re},ab=function(D,V,Z,re){try{Z?D.setAttributeNS(Z,V,re):D.setAttribute(V,re),vo(D)?cr(D):Ns(t.removed)}catch{Er(V,D)}},md=function(D){Rt(H.beforeSanitizeAttributes,D,null);const V=D.attributes;if(!V||vo(D))return;ge=ud(H.uponSanitizeAttribute,ge,Oe,q);const Z={attrName:"",attrValue:"",keepAttr:!0,allowedAttributes:ge,forceKeepAttr:void 0};let re=V.length;const he=et(D.nodeName);for(;re--;){const Se=V[re],Ze=Se.name,Re=Se.namespaceURI,Et=Se.value,Tt=et(Ze),cs=Et;let yt=Ze==="value"?cs:au(cs);if(Z.attrName=Tt,Z.attrValue=yt,Z.keepAttr=!0,Z.forceKeepAttr=void 0,Rt(H.uponSanitizeAttribute,D,Z),yt=Z.attrValue,Ce&&(Tt==="id"||Tt==="name")&&Ms(yt,Ie)!==0&&(Er(Ze,D,Se),yt=Ie+yt),Be&>(/((--!?|])>)|<\/(style|script|title|xmp|textarea|noscript|iframe|noembed|noframes)/i,yt)){Er(Ze,D,Se);continue}if(Tt==="attributename"&&Os(yt,"href")){Er(Ze,D,Se);continue}if(!Z.forceKeepAttr){if(!Z.keepAttr){Er(Ze,D,Se);continue}if(!ke&>(_u,yt)){Er(Ze,D,Se);continue}if(Ee&&(yt=yo(yt)),!hd(he,Tt,yt)){Er(Ze,D,Se);continue}yt=ob(he,Tt,Re,yt),yt!==cs&&ab(D,Ze,Re,yt)}}Rt(H.afterSanitizeAttributes,D,null)},xo=function(D){let V=null;const Z=dd(D);for(Rt(H.beforeSanitizeShadowDOM,D,null);V=Z.nextNode();)if(Rt(H.uponSanitizeShadowNode,V,null),gd(V,D),md(V),qr(V.content)&&xo(V.content),N(V)===Mt.element){const re=M(V);qr(re)&&(is(re),xo(re))}Rt(H.afterSanitizeShadowDOM,D,null)},is=function(D){const V=[{node:D,shadow:null}];for(;V.length>0;){const Z=V.pop();if(Z.shadow){xo(Z.shadow);continue}const re=Z.node,Se=N(re)===Mt.element,Ze=x(re);if(Ze)for(let Re=Ze.length-1;Re>=0;--Re)V.push({node:Ze[Re],shadow:null});if(Se){const Re=I?I(re):null;if(typeof Re=="string"&&et(Re)==="template"){const Et=re.content;qr(Et)&&V.push({node:Et,shadow:null})}}if(Se){const Re=M(re);qr(Re)&&V.push({node:null,shadow:Re},{node:Re,shadow:null})}}};return t.sanitize=function(J){let D=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{},V=null,Z=null,re=null,he=null;if(St=!J,St&&(J=""),typeof J!="string"&&!Sn(J)&&(J=du(J),typeof J!="string"))throw ur("dirty is not a string, aborting");if(!t.isSupported)return J;Ke?(me=Pe,ge=q):ss(D),(H.uponSanitizeElement.length>0||H.uponSanitizeAttribute.length>0)&&(me=Ot(me)),H.uponSanitizeAttribute.length>0&&(ge=Ot(ge)),t.removed=[];const Se=He&&typeof J!="string"&&Sn(J);if(Se){eb(J);const Et=w(J);if(typeof Et=="string"){const Tt=et(Et);if(!me[Tt]||$e[Tt])throw mo(J),ur("root node is forbidden and cannot be sanitized in-place")}if(vo(J))throw mo(J),ur("root node is clobbered and cannot be sanitized in-place");try{is(J)}catch(Tt){throw mo(J),Tt}}else if(Sn(J))V=cd(""),Z=V.ownerDocument.importNode(J,!0),Z.nodeType===Mt.element&&Z.nodeName==="BODY"||Z.nodeName==="HTML"?V=Z:V.appendChild(Z),is(Z);else{if(!de&&!Ee&&!Me&&J.indexOf("<")===-1)return y&&ie?U(J):J;if(V=cd(J),!V)return de?null:ie?E:""}V&&le&&cr(V.firstChild);const Ze=Se?J:V;try{const Et=dd(Ze);for(;re=Et.nextNode();)gd(re,Ze),md(re),qr(re.content)&&xo(re.content)}catch(Et){throw Se&&(mo(J),dr(t.removed,Tt=>{Tt.element&&bo(Tt.element)})),Et}if(Se)return dr(t.removed,Et=>{Et.element&&bo(Et.element)}),Ee&&ls(J),J;if(de){if(Ee&&ls(V),ue)for(he=X.call(V.ownerDocument);V.firstChild;)he.appendChild(V.firstChild);else he=V;return(ge.shadowroot||ge.shadowrootmode)&&(he=Y.call(n,he,!0)),he}let Re=Me?V.outerHTML:V.innerHTML;return Me&&me["!doctype"]&&V.ownerDocument&&V.ownerDocument.doctype&&V.ownerDocument.doctype.name&>(xu,V.ownerDocument.doctype.name)&&(Re=" -`+Re),Ee&&(Re=yo(Re)),y&&ie?U(Re):Re},t.setConfig=function(){let J=arguments.length>0&&arguments[0]!==void 0?arguments[0]:{};ss(J),Ke=!0,Pe=me,q=ge},t.clearConfig=function(){Xr=null,Ke=!1,Pe=null,q=null,y=O,E=""},t.isValidAttribute=function(J,D,V){Xr||ss({});const Z=et(J),re=et(D);return hd(Z,re,V)},t.addHook=function(J,D){typeof D=="function"&&wt(H,J)&&Jr(H[J],D)},t.removeHook=function(J,D){if(wt(H,J)){if(D!==void 0){const V=nu(H[J],D);return V===-1?void 0:ou(H[J],V,1)[0]}return Ns(H[J])}},t.removeHooks=function(J){wt(H,J)&&(H[J]=[])},t.removeAllHooks=function(){H=js()},t}var Uo=Bs();const Nu=()=>{const e=()=>{const n=window.G7Core?.state?.get?.();return n&&(n.preferredCurrency||n.defaultCurrency)||""},[t,r]=C.useState(e);return C.useEffect(()=>{const n=window.G7Core?.state?.subscribe;if(r(e()),typeof n!="function")return;const o=n(a=>{r(a&&(a.preferredCurrency||a.defaultCurrency)||"")});return typeof o=="function"?o:void 0},[]),t},Ou=(e,t=!1)=>{window.G7Core?.dispatch?.({handler:"navigate",params:t?{path:e,mergeQuery:!0,query:{}}:{path:e}})},Mu=e=>{const t=Math.round(e*2)/2;return[1,2,3,4,5].map(r=>t>=r?"full":t>=r-.5?"half":"empty")},Ws=({product:e,onClick:t,shopBase:r="/shop",preserveListQuery:n=!1,className:o="",id:a,editorAttrs:s})=>{const l=Nu(),f=v=>{const T=`multi_currency_${v}`,I=e[T];if(I&&I[l])return I[l].formatted;const S=`${v}_formatted`;return e[S]},h=e.name_localized??(typeof e.name=="string"?e.name:"")??"",c=f("selling_price"),i=f("list_price"),u=e.discount_rate&&e.discount_rate>0,g=e.sales_status&&e.sales_status!=="on_sale",p=e.labels??[],m=v=>Uo.sanitize(v,{ALLOWED_TAGS:["mark"],ALLOWED_ATTR:[]}),x=e.name_highlighted,k=e.description_highlighted??e.short_description,M=()=>{if(t)t(e.id);else{const v=r==="/"?"":r,T=e.product_code??e.id;Ou(`${v}/products/${T}`,n)}};return d.jsxs(R,{onClick:M,className:`block w-full text-left group bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700 overflow-hidden hover:shadow-lg transition-shadow cursor-pointer ${o}`,id:a,...s,children:[d.jsxs(j,{className:"relative aspect-square overflow-hidden bg-gray-100 dark:bg-gray-700",children:[d.jsx(jt,{src:e.thumbnail_url,alt:h,className:"w-full h-full object-cover group-hover:scale-105 transition-transform duration-200",loading:"lazy"}),g&&d.jsx(j,{className:"absolute inset-0 bg-black/50 flex items-center justify-center",children:d.jsx(te,{className:`text-lg font-bold ${e.sales_status==="sold_out"?"text-red-300":e.sales_status==="suspended"?"text-amber-300":"text-blue-300"}`,style:{textShadow:"0 1px 4px rgba(0,0,0,0.6)"},children:e.sales_status_label??e.sales_status})}),u&&e.sales_status!=="sold_out"&&e.sales_status!=="suspended"&&d.jsxs(te,{className:"absolute top-2 left-2 px-2 py-1 bg-red-500 text-white text-xs font-bold rounded",children:[e.discount_rate,"%"]}),p.length>0&&d.jsx(j,{className:"absolute bottom-2 right-2 flex gap-1 flex-wrap justify-end",children:p.map((v,T)=>d.jsx(te,{className:"text-xs text-white font-medium px-1.5 py-0.5 rounded",style:{backgroundColor:v.color||"#6b7280"},children:v.name},T))})]}),d.jsxs(j,{className:"p-4",children:[d.jsxs(j,{className:"flex items-center gap-1.5 flex-wrap",children:[e.primary_category&&d.jsx(te,{className:"inline-block text-xs font-medium text-blue-600 dark:text-blue-400 bg-blue-50 dark:bg-blue-900/30 px-2 py-0.5 rounded",children:e.primary_category}),e.brand_name&&d.jsx(te,{className:"text-xs text-gray-500 dark:text-gray-400",children:e.brand_name})]}),x?d.jsx(j,{className:"mt-1 font-medium text-gray-900 dark:text-white line-clamp-1 [&_mark]:bg-yellow-200 [&_mark]:dark:bg-yellow-500/40 [&_mark]:px-0.5 [&_mark]:rounded",dangerouslySetInnerHTML:{__html:m(x)}}):d.jsx(Tn,{className:"mt-1 font-medium text-gray-900 dark:text-white line-clamp-1",children:h}),k&&d.jsx(j,{className:"mt-1 text-sm text-gray-600 dark:text-gray-400 line-clamp-2 [&_mark]:bg-yellow-200 [&_mark]:dark:bg-yellow-500/40 [&_mark]:px-0.5 [&_mark]:rounded",dangerouslySetInnerHTML:{__html:m(k)}}),d.jsxs(j,{className:"mt-1.5 flex items-center gap-1",children:[Mu(e.rating_avg??0).map((v,T)=>d.jsxs(te,{className:`text-xs ${v==="empty"?"text-gray-300 dark:text-gray-600":"text-yellow-400"}`,children:[v==="full"&&d.jsx("i",{className:"fa-solid fa-star"}),v==="half"&&d.jsx("i",{className:"fa-solid fa-star-half-stroke"}),v==="empty"&&d.jsx("i",{className:"fa-regular fa-star"})]},T)),d.jsx(te,{className:"text-xs text-gray-600 dark:text-gray-400 ml-0.5",children:(e.rating_avg??0).toFixed(1)}),d.jsxs(te,{className:"text-xs text-gray-400 dark:text-gray-500",children:["(",e.review_count??0,")"]})]}),d.jsxs(j,{className:"mt-2 flex items-baseline gap-2",children:[d.jsx(te,{className:"font-bold text-gray-900 dark:text-white",children:c}),i&&u&&d.jsx(te,{className:"text-sm text-gray-500 dark:text-gray-400 line-through",children:i})]})]})]})},Iu=Object.freeze(Object.defineProperty({__proto__:null,default:Ws},Symbol.toStringTag,{value:"Module"})),Du={ALLOWED_TAGS:["svg","g","defs","title","desc","rect","circle","ellipse","line","polyline","polygon","path","text","tspan","linearGradient","radialGradient","stop","clipPath"],ALLOWED_ATTR:["xmlns","width","height","viewBox","role","aria-label","aria-hidden","fill","fill-opacity","fill-rule","stroke","stroke-width","stroke-linecap","stroke-linejoin","opacity","transform","x","y","x1","y1","x2","y2","cx","cy","r","rx","ry","d","points","width","height","text-anchor","dominant-baseline","font-family","font-size","font-weight","offset","stop-color","stop-opacity","gradientUnits","id","clip-path"],FORBID_TAGS:["script","foreignObject","image","use","a","style"],FORBID_ATTR:["onload","onerror","onclick","onmouseover","onfocus","onblur","href","xlink:href"]},zo=({brandMark:e,size:t=32,className:r="",id:n,editorAttrs:o})=>{const a=e?.svg,s=e?.text,l=C.useMemo(()=>!a||a.trim()===""?"":Uo.sanitize(a,Du),[a]),f={width:`${t}px`,height:`${t}px`,flex:`0 0 ${t}px`};if(l)return d.jsx(j,{id:n,"aria-hidden":"true",className:`inline-flex items-center justify-center ${r}`.trim(),style:f,dangerouslySetInnerHTML:{__html:l},...o});if(s&&s.trim()!==""){const h=e?.class??"bg-gray-500 text-white";return d.jsx(te,{id:n,"aria-hidden":"true",className:`inline-flex items-center justify-center rounded-lg text-xs font-bold ${h} ${r}`.trim(),style:f,...o,children:s})}return null},Pu=Object.freeze(Object.defineProperty({__proto__:null,BrandMark:zo,default:zo},Symbol.toStringTag,{value:"Module"})),Vs="carousel",rn="controller",Lu="navigation",$u="no-scroll",Ho="portal",Fu="root",Ks="toolbar",Uu="counter",nn="fullscreen",Ys="slideshow",on="thumbnails",Xs="zoom",Mr="loading",Go="playing",an="error",sn="complete",zu="placeholder",ln=e=>`active-slide-${e}`,Hu=ln(Mr),Gu=ln(Go),ju=ln(an),Bu=ln(sn),qs="fullsize",Ir="flex_center",Wu="no_scroll",Zs="no_scroll_padding",jo="slide",Qs="slide_wrapper",Vu="slide_wrapper_interactive",nr="prev",or="next",Bo="swipe",Dr="close",Js="onPointerDown",Rs="onPointerMove",el="onPointerUp",tl="onPointerLeave",rl="onPointerCancel",Wo="onKeyDown",Ku="onKeyUp",Vo="onWheel",Yu="Escape",Xu="ArrowLeft",qu="ArrowRight",Zu="button",In="icon",nl="contain",ol="cover",al="Unknown action type",sl="yarl__";function ht(...e){return[...e].filter(Boolean).join(" ")}function ye(e){return`${sl}${e}`}function Xe(e){return`--${sl}${e}`}function fr(e,t){return`${e}${t?`_${t}`:""}`}function gr(e){return t=>fr(e,t)}function hr(e,t){var r;return(r=e?.[t])!==null&&r!==void 0?r:t}function ll(e,t,r){return hr(e,"{index} of {total}").replace(/\{index}/g,`${qo(r,t.length)+1}`).replace(/\{total}/g,`${t.length}`)}function Pr(...e){return()=>{e.forEach(t=>{t()})}}function $t(e,t,r){return()=>{const n=A.useContext(r);if(!n)throw new Error(`${e} must be used within a ${t}.Provider`);return n}}function Ko(){return typeof window<"u"}function Yo(e,t=0){const r=10**t;return Math.round((e+Number.EPSILON)*r)/r}function pr(e){return e.type===void 0||e.type==="image"}function Xo(e,t){return e.imageFit===ol||e.imageFit!==nl&&t===ol}function Dn(e){return typeof e=="string"?Number.parseInt(e,10):e}function Pn(e){if(typeof e=="number")return{pixel:e};if(typeof e=="string"){const t=Dn(e);return e.endsWith("%")?{percent:t}:{pixel:t}}return{pixel:0}}function Qu(e,t){const r=Pn(t),n=r.percent!==void 0?e.width/100*r.percent:r.pixel;return{width:Math.max(e.width-2*n,0),height:Math.max(e.height-2*n,0)}}function Ju(){return(Ko()?window?.devicePixelRatio:void 0)||1}function qo(e,t){return t>0?(e%t+t)%t:0}function Zo(e){return e.length>0}function Qo(e,t){return e[qo(t,e.length)]}function Jo(e,t){return Zo(e)?Qo(e,t):void 0}function il(e){return pr(e)?e.src:void 0}function Ln(e,t,r){if(!r)return e;const{buttons:n,...o}=e,a=n.findIndex(l=>l===t),s=A.isValidElement(r)?A.cloneElement(r,{key:t},null):r;if(a>=0){const l=[...n];return l.splice(a,1,s),{buttons:l,...o}}return{buttons:[s,...n],...o}}function cl(e,t,r=0){return Math.min(e.preload,Math.max(e.finite?t.length-1:Math.floor(t.length/2),r))}const Ru=Number(A.version.split(".")[0])>=19;function ef(e){return{inert:Ru?e:e?"":void 0}}function tf(e){e.scrollTop}const Ro={open:!1,close:()=>{},index:0,slides:[],render:{},plugins:[],toolbar:{buttons:[Dr]},labels:{},animation:{fade:250,swipe:500,easing:{fade:"ease",swipe:"ease-out",navigation:"ease-in-out"}},carousel:{finite:!1,preload:2,padding:"16px",spacing:"30%",imageFit:nl,imageProps:{}},controller:{ref:null,focus:!0,aria:!1,touchAction:"none",closeOnPullUp:!1,closeOnPullDown:!1,closeOnBackdropClick:!1,preventDefaultWheelX:!0,preventDefaultWheelY:!1,disableSwipeNavigation:!1},portal:{},noScroll:{disabled:!1},on:{},styles:{},className:""};function Ft(e,t){return{name:e,component:t}}function it(e,t){return{module:e,children:t}}function dl(e,t,r){return e.module.name===t?r(e):e.children?[it(e.module,e.children.flatMap(n=>{var o;return(o=dl(n,t,r))!==null&&o!==void 0?o:[]}))]:[e]}function Lr(e,t,r){return e.flatMap(n=>{var o;return(o=dl(n,t,r))!==null&&o!==void 0?o:[]})}function rf(e,t=[],r=[]){let n=e;const o=g=>{const p=[...n];for(;p.length>0;){const m=p.pop();if(m?.module.name===g)return!0;m?.children&&p.push(...m.children)}return!1},a=(g,p)=>{if(g===""){n=[it(p,n)];return}n=Lr(n,g,m=>[it(p,[m])])},s=(g,p)=>{n=Lr(n,g,m=>[it(m.module,[it(p,m.children)])])},l=(g,p,m)=>{n=Lr(n,g,x=>{var k;return[it(x.module,[...m?[it(p)]:[],...(k=x.children)!==null&&k!==void 0?k:[],...m?[]:[it(p)]])]})},f=(g,p,m)=>{n=Lr(n,g,x=>[...m?[it(p)]:[],x,...m?[]:[it(p)]])},h=g=>{s(rn,g)},c=(g,p)=>{n=Lr(n,g,m=>[it(p,m.children)])},i=g=>{n=Lr(n,g,p=>p.children)},u=g=>{r.push(g)};return t.forEach(g=>{g({contains:o,addParent:a,append:s,addChild:l,addSibling:f,addModule:h,replace:c,remove:i,augment:u})}),{config:n,augmentation:g=>r.reduce((p,m)=>m(p),g)}}const ul=A.createContext(null),ea=$t("useA11yContext","A11yContext",ul);function nf({children:e}){const[t,r]=A.useState(!1),[n,o]=A.useState(!1),a=A.useMemo(()=>({focusWithin:t,trackFocusWithin:(l,f)=>{const h=c=>i=>{var u;i.currentTarget.contains(i.relatedTarget)||r(c),(u=c?l:f)===null||u===void 0||u(i)};return{onFocus:h(!0),onBlur:h(!1)}},autoPlaying:n,setAutoPlaying:o}),[t,n]);return A.createElement(ul.Provider,{value:a},e)}const fl=A.createContext(null),$r=$t("useDocument","DocumentContext",fl);function of({nodeRef:e,children:t}){const r=A.useMemo(()=>{const n=a=>{var s;return((s=a||e.current)===null||s===void 0?void 0:s.ownerDocument)||document};return{getOwnerDocument:n,getOwnerWindow:a=>{var s;return((s=n(a))===null||s===void 0?void 0:s.defaultView)||window}}},[e]);return A.createElement(fl.Provider,{value:r},t)}const gl=A.createContext(null),Fr=$t("useEvents","EventsContext",gl);function af({children:e}){const[t]=A.useState({});A.useEffect(()=>()=>{Object.keys(t).forEach(n=>delete t[n])},[t]);const r=A.useMemo(()=>{const n=(s,l)=>{var f;(f=t[s])===null||f===void 0||f.splice(0,t[s].length,...t[s].filter(h=>h!==l))};return{publish:(...[s,l])=>{var f;(f=t[s])===null||f===void 0||f.forEach(h=>h(l))},subscribe:(s,l)=>(t[s]||(t[s]=[]),t[s].push(l),()=>n(s,l)),unsubscribe:n}},[t]);return A.createElement(gl.Provider,{value:r},e)}const hl=A.createContext(null),ct=$t("useLightboxProps","LightboxPropsContext",hl);function pl({children:e,...t}){return A.createElement(hl.Provider,{value:t},e)}const ml=A.createContext(null),Ut=$t("useLightboxState","LightboxStateContext",ml),bl=A.createContext(null),sf=$t("useLightboxDispatch","LightboxDispatchContext",bl);function lf(e,t){switch(t.type){case"swipe":{const{slides:r}=e,n=t?.increment||0,o=e.globalIndex+n,a=qo(o,r.length),s=Jo(r,a),l=n||t.duration!==void 0?{increment:n,duration:t.duration,easing:t.easing}:void 0;return{slides:r,currentIndex:a,globalIndex:o,currentSlide:s,animation:l}}case"update":return t.slides!==e.slides||t.index!==e.currentIndex?{slides:t.slides,currentIndex:t.index,globalIndex:t.index,currentSlide:Jo(t.slides,t.index)}:e;default:throw new Error(al)}}function cf({slides:e,index:t,children:r}){const[n,o]=A.useReducer(lf,{slides:e,currentIndex:t,globalIndex:t,currentSlide:Jo(e,t)});A.useEffect(()=>{o({type:"update",slides:e,index:t})},[e,t]);const a=A.useMemo(()=>({...n,state:n,dispatch:o}),[n,o]);return A.createElement(bl.Provider,{value:o},A.createElement(ml.Provider,{value:a},r))}const yl=A.createContext(null),cn=$t("useTimeouts","TimeoutsContext",yl);function df({children:e}){const[t]=A.useState([]);A.useEffect(()=>()=>{t.forEach(n=>window.clearTimeout(n)),t.splice(0,t.length)},[t]);const r=A.useMemo(()=>{const n=s=>{t.splice(0,t.length,...t.filter(l=>l!==s))};return{setTimeout:(s,l)=>{const f=window.setTimeout(()=>{n(f),s()},l);return t.push(f),f},clearTimeout:s=>{s!==void 0&&(n(s),window.clearTimeout(s))}}},[t]);return A.createElement(yl.Provider,{value:r},e)}const Ur=A.forwardRef(function({label:t,className:r,icon:n,renderIcon:o,onClick:a,style:s,...l},f){const{styles:h,labels:c}=ct(),i=hr(c,t);return A.createElement("button",{ref:f,type:"button",title:i,"aria-label":i,className:ht(ye(Zu),r),onClick:a,style:{...s,...h.button},...l},o?o():A.createElement(n,{className:ye(In),style:h.icon}))});function vl(e,t){const r=n=>A.createElement("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",width:"24",height:"24","aria-hidden":"true",focusable:"false",...n},t);return r.displayName=e,r}function xt(e,t){return vl(e,A.createElement("g",{fill:"currentColor"},A.createElement("path",{d:"M0 0h24v24H0z",fill:"none"}),t))}function uf(e,t){return vl(e,A.createElement(A.Fragment,null,A.createElement("defs",null,A.createElement("mask",{id:"strike"},A.createElement("path",{d:"M0 0h24v24H0z",fill:"white"}),A.createElement("path",{d:"M0 0L24 24",stroke:"black",strokeWidth:4}))),A.createElement("path",{d:"M0.70707 2.121320L21.878680 23.292883",stroke:"currentColor",strokeWidth:2}),A.createElement("g",{fill:"currentColor",mask:"url(#strike)"},A.createElement("path",{d:"M0 0h24v24H0z",fill:"none"}),t)))}const ff=xt("Close",A.createElement("path",{d:"M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"})),gf=xt("Previous",A.createElement("path",{d:"M15.41 7.41L14 6l-6 6 6 6 1.41-1.41L10.83 12z"})),hf=xt("Next",A.createElement("path",{d:"M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z"})),pf=xt("Loading",A.createElement(A.Fragment,null,Array.from({length:8}).map((e,t,r)=>A.createElement("line",{key:t,x1:"12",y1:"6.5",x2:"12",y2:"1.8",strokeLinecap:"round",strokeWidth:"2.6",stroke:"currentColor",strokeOpacity:1/r.length*(t+1),transform:`rotate(${360/r.length*t}, 12, 12)`})))),mf=xt("Error",A.createElement("path",{d:"M21.9,21.9l-8.49-8.49l0,0L3.59,3.59l0,0L2.1,2.1L0.69,3.51L3,5.83V19c0,1.1,0.9,2,2,2h13.17l2.31,2.31L21.9,21.9z M5,18 l3.5-4.5l2.5,3.01L12.17,15l3,3H5z M21,18.17L5.83,3H19c1.1,0,2,0.9,2,2V18.17z"})),Vt=Ko()?A.useLayoutEffect:A.useEffect;function ta(){const[e,t]=A.useState(!1);return A.useEffect(()=>{var r,n;const o=(r=window.matchMedia)===null||r===void 0?void 0:r.call(window,"(prefers-reduced-motion: reduce)");t(o?.matches);const a=s=>t(s.matches);return(n=o?.addEventListener)===null||n===void 0||n.call(o,"change",a),()=>{var s;return(s=o?.removeEventListener)===null||s===void 0?void 0:s.call(o,"change",a)}},[]),e}function bf(e){let t=0,r=0,n=0;const a=window.getComputedStyle(e).transform.match(/matrix.*\((.+)\)/);if(a){const s=a[1].split(",").map(Dn);s.length===6?(t=s[4],r=s[5]):s.length===16&&(t=s[12],r=s[13],n=s[14])}return{x:t,y:r,z:n}}function ra(e,t){const r=A.useRef(void 0),n=A.useRef(void 0),o=ta();return Vt(()=>{var a,s,l;if(e.current&&r.current!==void 0&&!o){const{keyframes:f,duration:h,easing:c,onfinish:i}=t(r.current,e.current.getBoundingClientRect(),bf(e.current))||{};if(f&&h){(a=n.current)===null||a===void 0||a.cancel(),n.current=void 0;try{n.current=(l=(s=e.current).animate)===null||l===void 0?void 0:l.call(s,f,{duration:h,easing:c})}catch(u){console.error(u)}n.current&&(n.current.onfinish=()=>{n.current=void 0,i?.()})}}r.current=void 0}),{prepareAnimation:a=>{r.current=a},isAnimationPlaying:()=>{var a;return((a=n.current)===null||a===void 0?void 0:a.playState)==="running"}}}function wl(){const e=A.useRef(null),t=A.useRef(void 0),[r,n]=A.useState();return{setContainerRef:A.useCallback(a=>{e.current=a,t.current&&(t.current.disconnect(),t.current=void 0);const s=()=>{if(a){const l=window.getComputedStyle(a),f=h=>parseFloat(h)||0;n({width:Math.round(a.clientWidth-f(l.paddingLeft)-f(l.paddingRight)),height:Math.round(a.clientHeight-f(l.paddingTop)-f(l.paddingBottom))})}else n(void 0)};s(),a&&typeof ResizeObserver<"u"&&(t.current=new ResizeObserver(s),t.current.observe(a))},[]),containerRef:e,containerRect:r}}function $n(){const e=A.useRef(void 0),{setTimeout:t,clearTimeout:r}=cn();return A.useCallback((n,o)=>{r(e.current),e.current=t(n,o>0?o:0)},[t,r])}function Le(e){const t=A.useRef(e);return Vt(()=>{t.current=e}),A.useCallback((...r)=>{var n;return(n=t.current)===null||n===void 0?void 0:n.call(t,...r)},[])}function xl(e,t){typeof e=="function"?e(t):e&&(e.current=t)}function na(e,t){return A.useMemo(()=>e==null&&t==null?null:r=>{xl(e,r),xl(t,r)},[e,t])}function kl(e,t=!1){const r=A.useRef(!1);Vt(()=>{t&&r.current&&(r.current=!1,e())},[t,e]);const n=A.useCallback(()=>{r.current=!0},[]),o=A.useCallback(()=>{r.current=!1},[]);return{onFocus:n,onBlur:o}}function Fn(){const[e,t]=A.useState(!1);return Vt(()=>{t(window.getComputedStyle(window.document.documentElement).direction==="rtl")},[]),e}function Cl(){const[e]=A.useState({}),t=A.useCallback((o,a)=>{var s;(s=e[o])===null||s===void 0||s.forEach(l=>{a.isPropagationStopped()||l(a)})},[e]),r=A.useMemo(()=>({onPointerDown:o=>t(Js,o),onPointerMove:o=>t(Rs,o),onPointerUp:o=>t(el,o),onPointerLeave:o=>t(tl,o),onPointerCancel:o=>t(rl,o),onKeyDown:o=>t(Wo,o),onKeyUp:o=>t(Ku,o),onWheel:o=>t(Vo,o)}),[t]),n=A.useCallback((o,a)=>(e[o]||(e[o]=[]),e[o].unshift(a),()=>{const s=e[o];s&&s.splice(0,s.length,...s.filter(l=>l!==a))}),[e]);return{registerSensors:r,subscribeSensors:n}}function _l(e,t){const r=A.useRef(0),n=$n(),o=Le((...a)=>{r.current=Date.now(),e(a)});return A.useCallback((...a)=>{n(()=>{o(a)},t-(Date.now()-r.current))},[t,o,n])}const oa=gr("slide"),aa=gr("slide_image");function mr({slide:e,offset:t,render:r,rect:n,imageFit:o,imageProps:a,onClick:s,onLoad:l,onError:f,style:h}){var c,i,u,g,p,m,x,k;const[M,v]=A.useState(Mr),{publish:T}=Fr(),{setTimeout:I}=cn(),S=A.useRef(null);A.useEffect(()=>{t===0&&T(ln(M))},[t,M,T]);const N=Le(B=>{("decode"in B?B.decode():Promise.resolve()).catch(()=>{}).then(()=>{B.parentNode&&(v(sn),I(()=>{l?.(B)},0))})}),w=A.useCallback(B=>{S.current=B,B?.complete&&N(B)},[N]),y=A.useCallback(B=>{N(B.currentTarget)},[N]),E=Le(()=>{v(an),f?.()}),O=Xo(e,o),$=(B,Y)=>Number.isFinite(B)?B:Y,P=$(Math.max(...((i=(c=e.srcSet)===null||c===void 0?void 0:c.map(B=>B.width))!==null&&i!==void 0?i:[]).concat(e.width?[e.width]:[]).filter(Boolean)),((u=S.current)===null||u===void 0?void 0:u.naturalWidth)||0),L=$(Math.max(...((p=(g=e.srcSet)===null||g===void 0?void 0:g.map(B=>B.height))!==null&&p!==void 0?p:[]).concat(e.height?[e.height]:[]).filter(Boolean)),((m=S.current)===null||m===void 0?void 0:m.naturalHeight)||0),U=P&&L?{maxWidth:`min(${P}px, 100%)`,maxHeight:`min(${L}px, 100%)`}:{maxWidth:"100%",maxHeight:"100%"},_=(x=e.srcSet)===null||x===void 0?void 0:x.sort((B,Y)=>B.width-Y.width).map(B=>`${B.src} ${B.width}w`).join(", "),b=()=>n&&!O&&e.width&&e.height?n.height/e.height*e.width:Number.MAX_VALUE,z=_&&n&&Ko()?`${Math.round(Math.min(b(),n.width))}px`:void 0,{style:F,className:G,...X}=a||{};return A.createElement(A.Fragment,null,A.createElement("img",{ref:w,onLoad:y,onError:E,onClick:s,draggable:!1,className:ht(ye(aa()),O&&ye(aa("cover")),M!==sn&&ye(aa("loading")),G),style:{...U,...h,...F},...X,alt:(k=e.alt)!==null&&k!==void 0?k:"",sizes:z,srcSet:_,src:e.src}),M!==sn&&A.createElement("div",{className:ye(oa(zu))},M===Mr&&(r?.iconLoading?r.iconLoading():A.createElement(pf,{className:ht(ye(In),ye(oa(Mr)))})),M===an&&(r?.iconError?r.iconError():A.createElement(mf,{className:ht(ye(In),ye(oa(an)))}))))}const yf=A.forwardRef(function({className:t,children:r,onFocus:n,onBlur:o,...a},s){const l=A.useRef(null),{trackFocusWithin:f}=ea();return A.createElement(of,{nodeRef:l},A.createElement("div",{ref:na(s,l),className:ht(ye("root"),t),...f(n,o),...a},r))});var pt;(function(e){e[e.NONE=0]="NONE",e[e.SWIPE=1]="SWIPE",e[e.PULL=2]="PULL",e[e.ANIMATION=3]="ANIMATION"})(pt||(pt={}));function Sl(e,t,r,n,o){A.useEffect(()=>o?()=>{}:Pr(e(Js,t),e(Rs,r),e(el,n),e(tl,n),e(rl,n)),[e,t,r,n,o])}var Kt;(function(e){e[e.NONE=0]="NONE",e[e.SWIPE=1]="SWIPE",e[e.PULL=2]="PULL"})(Kt||(Kt={}));const sa=30;function vf({disableSwipeNavigation:e,closeOnBackdropClick:t},r,n,o,a,s,l,f,h,c,i,u,g,p,m,x){const k=A.useRef(0),M=A.useRef([]),v=A.useRef(void 0),T=A.useRef(0),I=A.useRef(Kt.NONE),S=A.useCallback(P=>{v.current===P.pointerId&&(v.current=void 0,I.current=Kt.NONE);const L=M.current;L.splice(0,L.length,...L.filter(U=>U.pointerId!==P.pointerId))},[]),N=A.useCallback(P=>{S(P),P.persist(),M.current.push(P)},[S]),w=A.useCallback(P=>M.current.find(({pointerId:L})=>P.pointerId===L),[]),y=Le(P=>{N(P)}),E=(P,L)=>i&&P>L||c&&P<-L,O=Le(P=>{const L=w(P);if(L)if(v.current===P.pointerId){const U=Date.now()-T.current,_=k.current;I.current===Kt.SWIPE?Math.abs(_)>.3*o||Math.abs(_)>5&&U{const L=w(P);if(L){const U=v.current===P.pointerId;if(P.buttons===0){U&&k.current!==0?O(P):S(L);return}const _=P.clientX-L.clientX,b=P.clientY-L.clientY;if(v.current===void 0){const z=F=>{N(P),v.current=P.pointerId,T.current=Date.now(),I.current=F};Math.abs(_)>Math.abs(b)&&Math.abs(_)>sa&&n(_)?e||(z(Kt.SWIPE),s()):Math.abs(b)>Math.abs(_)&&E(b,sa)&&(z(Kt.PULL),u())}else U&&(I.current===Kt.SWIPE?(k.current=_,l(_)):I.current===Kt.PULL&&(k.current=b,g(b)))}});Sl(r,y,$,O)}function wf({preventDefaultWheelX:e,preventDefaultWheelY:t}){const r=A.useRef(null),n=Le(o=>{const a=Math.abs(o.deltaX)>Math.abs(o.deltaY);(a&&e||!a&&t||o.ctrlKey)&&o.preventDefault()});return A.useCallback(o=>{var a;o?o.addEventListener("wheel",n,{passive:!1}):(a=r.current)===null||a===void 0||a.removeEventListener("wheel",n),r.current=o},[n])}function xf(e,t,r,n,o,a,s,l,f){const h=A.useRef(0),c=A.useRef(0),i=A.useRef(void 0),u=A.useRef(void 0),g=A.useRef(0),p=A.useRef(void 0),m=A.useRef(0),{setTimeout:x,clearTimeout:k}=cn(),M=A.useCallback(()=>{i.current&&(k(i.current),i.current=void 0)},[k]),v=A.useCallback(()=>{u.current&&(k(u.current),u.current=void 0)},[k]),T=Le(()=>{e!==pt.SWIPE&&(h.current=0,m.current=0,M(),v())});A.useEffect(T,[e,T]);const I=Le(N=>{u.current=void 0,h.current===N&&f(h.current)}),S=Le(N=>{if(N.ctrlKey||Math.abs(N.deltaY)>Math.abs(N.deltaX))return;const w=y=>{g.current=y,k(p.current),p.current=y>0?x(()=>{g.current=0,p.current=void 0},300):void 0};if(e===pt.NONE){if(Math.abs(N.deltaX)<=1.2*Math.abs(g.current)){w(N.deltaX);return}if(!r(-N.deltaX))return;if(c.current+=N.deltaX,M(),Math.abs(c.current)>30)c.current=0,w(0),m.current=Date.now(),a();else{const y=c.current;i.current=x(()=>{i.current=void 0,y===c.current&&(c.current=0)},o)}}else if(e===pt.SWIPE){let y=h.current-N.deltaX;if(y=Math.min(Math.abs(y),n)*Math.sign(y),h.current=y,s(y),v(),Math.abs(y)>.2*n){w(N.deltaX),l(y,Date.now()-m.current);return}u.current=x(()=>I(y),2*o)}else w(N.deltaX)});A.useEffect(()=>t(Vo,S),[t,S])}const El=gr("container"),Tl=A.createContext(null),Yt=$t("useController","ControllerContext",Tl);function kf({children:e,...t}){var r;const{carousel:n,animation:o,controller:a,on:s,styles:l,render:f}=t,{closeOnPullUp:h,closeOnPullDown:c,preventDefaultWheelX:i,preventDefaultWheelY:u}=a,[g,p]=A.useState(),m=Ut(),x=sf(),[k,M]=A.useState(pt.NONE),v=A.useRef(0),T=A.useRef(0),I=A.useRef(1),{registerSensors:S,subscribeSensors:N}=Cl(),{subscribe:w,publish:y}=Fr(),E=$n(),O=$n(),$=$n(),{containerRef:P,setContainerRef:L,containerRect:U}=wl(),_=na(wf({preventDefaultWheelX:i,preventDefaultWheelY:u}),L),b=A.useRef(null),z=na(b,void 0),{getOwnerDocument:F}=$r(),G=Fn(),X=K=>(G?-1:1)*(typeof K=="number"?K:1),B=Le(()=>{var K;return(K=P.current)===null||K===void 0?void 0:K.focus()}),Y=Le(()=>t),H=Le(()=>m),W=A.useCallback(K=>y(nr,K),[y]),Q=A.useCallback(K=>y(or,K),[y]),be=A.useCallback(()=>y(Dr),[y]),se=K=>!(n.finite&&(X(K)>0&&m.currentIndex===0||X(K)<0&&m.currentIndex===m.slides.length-1)),pe=K=>{var oe;v.current=K,(oe=P.current)===null||oe===void 0||oe.style.setProperty(Xe("swipe_offset"),`${Math.round(K)}px`)},ne=K=>{var oe,ae;T.current=K,I.current=(()=>{const Ee=c&&K>0?K:h&&K<0?-K:0;return Math.min(Math.max(Yo(1-Ee/60*(1-.5),2),.5),1)})(),(oe=P.current)===null||oe===void 0||oe.style.setProperty(Xe("pull_offset"),`${Math.round(K)}px`),(ae=P.current)===null||ae===void 0||ae.style.setProperty(Xe("pull_opacity"),`${I.current}`)},{prepareAnimation:Ae}=ra(b,(K,oe,ae)=>{if(b.current&&U)return{keyframes:[{transform:`translate(0, ${K.rect.y-oe.y+ae.y}px)`,opacity:K.opacity},{transform:"translate(0, 0)",opacity:1}],duration:K.duration,easing:o.easing.fade}}),ve=(K,oe)=>{if(h||c){ne(K);let ae=0;b.current&&(ae=o.fade*(oe?2:1),Ae({rect:b.current.getBoundingClientRect(),opacity:I.current,duration:ae})),$(()=>{ne(0),M(pt.NONE)},ae),M(pt.ANIMATION),oe||be()}},{prepareAnimation:We,isAnimationPlaying:me}=ra(b,(K,oe,ae)=>{var ce;if(b.current&&U&&(!((ce=m.animation)===null||ce===void 0)&&ce.duration)){const ke=Pn(n.spacing),Ee=(ke.percent?ke.percent*U.width/100:ke.pixel)||0;return{keyframes:[{transform:`translate(${X(m.globalIndex-K.index)*(U.width+Ee)+K.rect.x-oe.x+ae.x}px, 0)`},{transform:"translate(0, 0)"}],duration:m.animation.duration,easing:m.animation.easing}}}),Te=Le(K=>{var oe,ae;const ce=K.offset||0,ke=ce?o.swipe:(oe=o.navigation)!==null&&oe!==void 0?oe:o.swipe,Ee=!ce&&!me()?o.easing.navigation:o.easing.swipe;let{direction:Be}=K;const Me=(ae=K.count)!==null&&ae!==void 0?ae:1;let Ke=pt.ANIMATION,Pe=ke*Me;if(!Be){const le=U?.width,de=K.duration||0,ue=le?ke/le*Math.abs(ce):ke;Me!==0?(de0?nr:or):Pe=ke/2}let q=0;Be===nr?se(X(1))?q=-Me:(Ke=pt.NONE,Pe=ke):Be===or&&(se(X(-1))?q=Me:(Ke=pt.NONE,Pe=ke)),Pe=Math.round(Pe),O(()=>{pe(0),M(pt.NONE)},Pe),b.current&&We({rect:b.current.getBoundingClientRect(),index:m.globalIndex}),M(Ke),y(Bo,{type:"swipe",increment:q,duration:Pe,easing:Ee})});A.useEffect(()=>{var K,oe;!((K=m.animation)===null||K===void 0)&&K.increment&&(!((oe=m.animation)===null||oe===void 0)&&oe.duration)&&E(()=>x({type:"swipe",increment:0}),m.animation.duration)},[m.animation,x,E]);const ge=[N,se,U?.width||0,o.swipe,()=>M(pt.SWIPE),K=>pe(K),(K,oe)=>Te({offset:K,duration:oe,count:1}),K=>Te({offset:K,count:0})],Oe=[()=>{c&&M(pt.PULL)},K=>ne(K),K=>ve(K),K=>ve(K,!0)];vf(a,...ge,h,c,...Oe,be),xf(k,...ge);const we=Le(()=>{a.focus&&F().querySelector(`.${ye(Ho)} .${ye(El())}`)&&B()});A.useEffect(we,[we]);const $e=Le(()=>{var K;(K=s.view)===null||K===void 0||K.call(s,{index:m.currentIndex})});A.useEffect($e,[m.globalIndex,$e]),A.useEffect(()=>Pr(w(nr,K=>Te({direction:nr,...K})),w(or,K=>Te({direction:or,...K})),w(Bo,K=>x(K))),[w,Te,x]);const ze=A.useMemo(()=>({prev:W,next:Q,close:be,focus:B,slideRect:U?Qu(U,n.padding):{width:0,height:0},containerRect:U||{width:0,height:0},subscribeSensors:N,containerRef:P,setCarouselRef:z,toolbarWidth:g,setToolbarWidth:p}),[W,Q,be,B,N,U,P,z,g,p,n.padding]);return A.useImperativeHandle(a.ref,()=>({prev:W,next:Q,close:be,focus:B,getLightboxProps:Y,getLightboxState:H}),[W,Q,be,B,Y,H]),A.createElement("div",{ref:_,className:ht(ye(El()),ye(Ir)),style:{...k===pt.SWIPE?{[Xe("swipe_offset")]:`${Math.round(v.current)}px`}:null,...k===pt.PULL?{[Xe("pull_offset")]:`${Math.round(T.current)}px`,[Xe("pull_opacity")]:`${I.current}`}:null,...a.touchAction!=="none"?{[Xe("controller_touch_action")]:a.touchAction}:null,...l.container},tabIndex:-1,...S},U&&A.createElement(Tl.Provider,{value:ze},e,(r=f.controls)===null||r===void 0?void 0:r.call(f)))}const Cf=Ft(rn,kf);function br(e){return fr(Vs,e)}function Al(e){return fr(jo,e)}function _f({slide:e,offset:t}){const r=A.useRef(null),{currentIndex:n,slides:o}=Ut(),{slideRect:a,focus:s}=Yt(),{render:l,carousel:{imageFit:f,imageProps:h},on:{click:c},styles:{slide:i},labels:u}=ct(),{getOwnerDocument:g}=$r(),p=t!==0;A.useEffect(()=>{var x;p&&(!((x=r.current)===null||x===void 0)&&x.contains(g().activeElement))&&s()},[p,s,g]);const m=()=>{var x,k,M,v;let T=(x=l.slide)===null||x===void 0?void 0:x.call(l,{slide:e,offset:t,rect:a});return!T&&pr(e)&&(T=A.createElement(mr,{slide:e,offset:t,render:l,rect:a,imageFit:f,imageProps:h,onClick:p?void 0:()=>c?.({index:n})})),T?A.createElement(A.Fragment,null,(k=l.slideHeader)===null||k===void 0?void 0:k.call(l,{slide:e}),((M=l.slideContainer)!==null&&M!==void 0?M:(({children:I})=>I))({slide:e,children:T}),(v=l.slideFooter)===null||v===void 0?void 0:v.call(l,{slide:e})):null};return A.createElement("div",{ref:r,className:ht(ye(Al()),!p&&ye(Al("current")),ye(Ir)),...ef(p),style:i,role:"group","aria-roledescription":hr(u,"Slide"),"aria-label":ll(u,o,n+t)},m())}function Sf(){const e=ct().styles.slide;return A.createElement("div",{className:ye(jo),style:e})}function Ef({carousel:e,labels:t}){const{slides:r,currentIndex:n,globalIndex:o}=Ut(),{setCarouselRef:a}=Yt(),{autoPlaying:s,focusWithin:l}=ea(),f=Pn(e.spacing),h=Pn(e.padding),c=cl(e,r,1),i=[];if(Zo(r))for(let u=n-c;u<=n+c;u+=1){const g=Qo(r,u),p=o-n+u,m=e.finite&&(u<0||u>r.length-1);i.push(m?{key:p}:{key:[`${p}`,il(g)].filter(Boolean).join("|"),offset:u-n,slide:g})}return A.createElement("div",{ref:a,className:ht(ye(br()),i.length>0&&ye(br("with_slides"))),style:{[`${Xe(br("slides_count"))}`]:i.length,[`${Xe(br("spacing_px"))}`]:f.pixel||0,[`${Xe(br("spacing_percent"))}`]:f.percent||0,[`${Xe(br("padding_px"))}`]:h.pixel||0,[`${Xe(br("padding_percent"))}`]:h.percent||0},role:"region","aria-live":s&&!l?"off":"polite","aria-roledescription":hr(t,"Carousel"),"aria-label":hr(t,"Photo gallery")},i.map(({key:u,slide:g,offset:p})=>g?A.createElement(_f,{key:u,slide:g,offset:p}):A.createElement(Sf,{key:u})))}const Tf=Ft(Vs,Ef);function Nl(){const{carousel:e}=ct(),{slides:t,currentIndex:r}=Ut(),n=t.length===0||e.finite&&r===0,o=t.length===0||e.finite&&r===t.length-1;return{prevDisabled:n,nextDisabled:o}}function Ol(e){var t;const r=Fn(),{publish:n}=Fr(),{animation:o}=ct(),{prevDisabled:a,nextDisabled:s}=Nl(),l=((t=o.navigation)!==null&&t!==void 0?t:o.swipe)/2,f=_l(()=>n(nr),l),h=_l(()=>n(or),l),c=Le(i=>{switch(i.key){case Yu:n(Dr);break;case Xu:(r?s:a)||(r?h:f)();break;case qu:(r?a:s)||(r?f:h)();break}});A.useEffect(()=>e(Wo,c),[e,c])}function Ml({label:e,icon:t,renderIcon:r,action:n,onClick:o,disabled:a,style:s}){return A.createElement(Ur,{label:e,icon:t,renderIcon:r,className:ye(`navigation_${n}`),disabled:a,onClick:o,style:s,...kl(Yt().focus,a)})}function Af({render:{buttonPrev:e,buttonNext:t,iconPrev:r,iconNext:n},styles:o}){const{prev:a,next:s,subscribeSensors:l}=Yt(),{prevDisabled:f,nextDisabled:h}=Nl();return Ol(l),A.createElement(A.Fragment,null,e?e():A.createElement(Ml,{label:"Previous",action:nr,icon:gf,renderIcon:r,style:o.navigationPrev,disabled:f,onClick:a}),t?t():A.createElement(Ml,{label:"Next",action:or,icon:hf,renderIcon:n,style:o.navigationNext,disabled:h,onClick:s}))}const Nf=Ft(Lu,Af),Il=ye(Wu),Of=ye(Zs);function Mf(e){return"style"in e}function Dl(e,t,r){const n=window.getComputedStyle(e),o=r?"padding-left":"padding-right",a=r?n.paddingLeft:n.paddingRight,s=e.style.getPropertyValue(o);return e.style.setProperty(o,`${(Dn(a)||0)+t}px`),()=>{s?e.style.setProperty(o,s):e.style.removeProperty(o)}}function If({noScroll:{disabled:e},children:t}){const r=Fn(),{getOwnerDocument:n,getOwnerWindow:o}=$r();return A.useEffect(()=>{if(e)return()=>{};const a=[],s=o(),{body:l,documentElement:f}=n(),h=Math.round(s.innerWidth-f.clientWidth);if(h>0){a.push(Dl(l,h,r));const c=l.getElementsByTagName("*");for(let i=0;i{l.classList.remove(Il),a.forEach(c=>c())}},[r,e,n,o]),A.createElement(A.Fragment,null,t)}const Df=Ft($u,If);function Pl(e){return fr(Ho,e)}function Ll(e,t,r){const n=e.getAttribute(t);return e.setAttribute(t,r),()=>{n?e.setAttribute(t,n):e.removeAttribute(t)}}function Pf({children:e,animation:t,styles:r,className:n,on:o,portal:a,close:s,labels:l}){const[f,h]=A.useState(!1),[c,i]=A.useState(!1),u=A.useRef([]),g=A.useRef(null),{setTimeout:p}=cn(),{subscribe:m}=Fr(),k=ta()?0:t.fade;A.useEffect(()=>(h(!0),()=>{h(!1),i(!1)}),[]);const M=Le(()=>{u.current.forEach(S=>S()),u.current=[]}),v=Le(()=>{var S;i(!1),M(),(S=o.exiting)===null||S===void 0||S.call(o),p(()=>{var N;(N=o.exited)===null||N===void 0||N.call(o),s()},k)});A.useEffect(()=>m(Dr,v),[m,v]);const T=Le(S=>{var N,w,y;tf(S),i(!0),(N=o.entering)===null||N===void 0||N.call(o);const E=(y=(w=S.parentNode)===null||w===void 0?void 0:w.children)!==null&&y!==void 0?y:[];for(let O=0;O{var O,$;($=(O=g.current)===null||O===void 0?void 0:O.focus)===null||$===void 0||$.call(O)}),p(()=>{var O;(O=o.entered)===null||O===void 0||O.call(o)},k)}),I=A.useCallback(S=>{S?T(S):M()},[T,M]);return f?tr.createPortal(A.createElement(yf,{ref:I,className:ht(n,ye(Pl()),ye(Zs),c&&ye(Pl("open"))),"aria-modal":!0,role:"dialog","aria-label":hr(l,"Lightbox"),style:{...t.fade!==Ro.animation.fade?{[Xe("fade_animation_duration")]:`${k}ms`}:null,...t.easing.fade!==Ro.animation.easing.fade?{[Xe("fade_animation_timing_function")]:t.easing.fade}:null,...r.root},onFocus:S=>{g.current||(g.current=S.relatedTarget)}},e),a.root||document.body):null}const Lf=Ft(Ho,Pf);function $f({children:e}){return A.createElement(A.Fragment,null,e)}const Ff=Ft(Fu,$f);function Uf(e){return fr(Ks,e)}function zf({toolbar:{buttons:e},render:{buttonClose:t,iconClose:r},styles:n}){const{close:o,setToolbarWidth:a}=Yt(),{setContainerRef:s,containerRect:l}=wl();Vt(()=>{a(l?.width)},[a,l?.width]);const f=()=>t?t():A.createElement(Ur,{key:Dr,label:"Close",icon:ff,renderIcon:r,onClick:o});return A.createElement("div",{ref:s,style:n.toolbar,className:ye(Uf())},e?.map(h=>h===Dr?f():h))}const Hf=Ft(Ks,zf);function $l(e,t){var r;return A.createElement(e.module.component,{key:e.module.name,...t},(r=e.children)===null||r===void 0?void 0:r.map(n=>$l(n,t)))}function Gf(e,t={}){const{easing:r,...n}=e,{easing:o,...a}=t;return{easing:{...r,...o},...n,...a}}function jf({carousel:e,animation:t,render:r,toolbar:n,controller:o,noScroll:a,on:s,plugins:l,slides:f,index:h,...c}){const{animation:i,carousel:u,render:g,toolbar:p,controller:m,noScroll:x,on:k,slides:M,index:v,plugins:T,...I}=Ro,{config:S,augmentation:N}=rf([it(Lf,[it(Df,[it(Cf,[it(Tf),it(Hf),it(Nf)])])])],l||T),w=N({animation:Gf(i,t),carousel:{...u,...e},render:{...g,...r},toolbar:{...p,...n},controller:{...m,...o},noScroll:{...x,...a},on:{...k,...s},...I,...c});return w.open?A.createElement(pl,{...w},A.createElement(cf,{slides:f||M,index:Dn(h||v)},A.createElement(df,null,A.createElement(af,null,A.createElement(nf,null,$l(it(Ff,S),w)))))):null}const Bf={minZoom:1,maxZoomPixelRatio:1,zoomInMultiplier:2,doubleTapDelay:300,doubleClickDelay:500,doubleClickMaxStops:2,keyboardMoveDistance:50,wheelZoomDistanceFactor:100,pinchZoomDistanceFactor:100,pinchZoomV4:!1,scrollToZoom:!1};function Wf(e){return Math.min(Math.max(e,Number.EPSILON),1)}function Fl(e){const{minZoom:t,...r}={...Bf,...e};return{minZoom:Wf(t),...r}}function Vf(e,t,r,n){const o=A.useRef(void 0),a=A.useRef(void 0),{zoom:s}=ct().animation,l=ta(),f=Le(()=>{var h,c,i;if((h=o.current)===null||h===void 0||h.cancel(),o.current=void 0,a.current&&n?.current){try{o.current=(i=(c=n.current).animate)===null||i===void 0?void 0:i.call(c,[{transform:a.current},{transform:`scale(${e}) translateX(${t}px) translateY(${r}px)`}],{duration:l?0:s??500,easing:o.current?"ease-out":"ease-in-out"})}catch(u){console.error(u)}a.current=void 0,o.current&&(o.current.onfinish=()=>{o.current=void 0})}});return Vt(f,[e,t,r,f]),A.useCallback(()=>{a.current=n?.current?window.getComputedStyle(n.current).transform:void 0},[n])}function Kf(e,t){const{on:r}=ct(),n=Le(()=>{var o;t||(o=r.zoom)===null||o===void 0||o.call(r,{zoom:e})});A.useEffect(n,[e,n])}function Un(){const{zoom:e}=ct();return Fl(e)}function Yf(e,t){var r,n;let o={width:0,height:0},a={width:0,height:0};const{currentSlide:s}=Ut(),{imageFit:l}=ct().carousel,{maxZoomPixelRatio:f}=Un();if(e&&s){const c={...s,...t};if(pr(c)){const i=Xo(c,l),u=Math.max(...(((r=c.srcSet)===null||r===void 0?void 0:r.map(p=>p.width))||[]).concat(c.width?[c.width]:[])),g=Math.max(...(((n=c.srcSet)===null||n===void 0?void 0:n.map(p=>p.height))||[]).concat(c.height?[c.height]:[]));u>0&&g>0&&e.width>0&&e.height>0&&(a=i?{width:Math.round(Math.min(u,e.width/e.height*g)),height:Math.round(Math.min(g,e.height/e.width*u))}:{width:u,height:g},a={width:a.width*f,height:a.height*f},o=i?{width:Math.min(e.width,a.width,u),height:Math.min(e.height,a.height,g)}:{width:Math.round(Math.min(e.width,e.height/g*u,u)),height:Math.round(Math.min(e.height,e.width/u*g,g))})}}const h=o.width?Math.max(Yo(a.width/o.width,5),1):1;return{imageRect:o,maxZoom:h}}function Ul(e,t){return Math.hypot(e.clientX-t.clientX,e.clientY-t.clientY)}function zl(e,t,r=100,n=2){return e*Math.min(1+Math.abs(t/r),n)**Math.sign(t)}function Xf(e,t,r,n,o,a,s,l,f){const h=A.useRef([]),c=A.useRef(0),i=A.useRef(void 0),{globalIndex:u}=Ut(),{getOwnerWindow:g}=$r(),{containerRef:p,subscribeSensors:m}=Yt(),{keyboardMoveDistance:x,zoomInMultiplier:k,wheelZoomDistanceFactor:M,scrollToZoom:v,doubleTapDelay:T,doubleClickDelay:I,doubleClickMaxStops:S,pinchZoomDistanceFactor:N,pinchZoomV4:w}=Un(),y=A.useCallback(z=>{if(p.current){const{pageX:F,pageY:G}=z,{scrollX:X,scrollY:B}=g(),{left:Y,top:H,width:W,height:Q}=p.current.getBoundingClientRect();return[F-Y-X-W/2,G-H-B-Q/2]}return[]},[p,g]),E=Le(z=>{const{key:F,metaKey:G,ctrlKey:X}=z,B=G||X,Y=()=>{z.preventDefault(),z.stopPropagation()};if(e>1){const H=(W,Q)=>{Y(),l(W,Q)};F==="ArrowDown"?H(0,x):F==="ArrowUp"?H(0,-x):F==="ArrowLeft"?H(-x,0):F==="ArrowRight"&&H(x,0)}F==="+"||B&&F==="="?(Y(),o()):F==="-"||B&&F==="_"?(Y(),a()):B&&F==="0"&&(Y(),s(1))}),O=Le(z=>{if((z.ctrlKey||v)&&Math.abs(z.deltaY)>Math.abs(z.deltaX)){z.stopPropagation(),s(zl(e,-z.deltaY,M),!0,...y(z));return}e>1&&(z.stopPropagation(),v||l(z.deltaX,z.deltaY))}),$=A.useCallback(z=>{const F=h.current;F.splice(0,F.length,...F.filter(G=>G.pointerId!==z.pointerId))},[]),P=A.useCallback(z=>{$(z),z.persist(),h.current.push(z)},[$]),L=Le(z=>{var F;const G=h.current;if(z.pointerType==="mouse"&&z.buttons>1||!(!((F=f?.current)===null||F===void 0)&&F.contains(z.target)))return;e>1&&z.stopPropagation();const{timeStamp:X}=z;if(G.length===0&&X-c.current<(z.pointerType==="touch"?T:I)){c.current=0;const B=e>=1?e!==r?e*Math.max(r**(1/S),k):1:e!==t?e/Math.max(t**(-1/S),k):1;s(B,!1,...y(z))}else c.current=X;if(P(z),G.length===2){const B=Ul(G[0],G[1]);i.current={previousDistance:B,initialDistance:Math.max(B,1),initialZoom:e}}}),U=Le(z=>{const F=h.current,G=F.find(X=>X.pointerId===z.pointerId);if(F.length===2&&i.current){z.stopPropagation(),P(z);const X=Ul(F[0],F[1]),B=w?i.current.initialZoom/i.current.initialDistance*X:zl(e,X-i.current.previousDistance,N);s(B,!0,...F.map(Y=>y(Y)).reduce((Y,H)=>H.map((W,Q)=>Y[Q]+W/2))),i.current.previousDistance=X;return}e>1&&(z.stopPropagation(),G&&(F.length===1&&l((G.clientX-z.clientX)/e,(G.clientY-z.clientY)/e),P(z)))}),_=A.useCallback(z=>{const F=h.current;F.length===2&&F.find(G=>G.pointerId===z.pointerId)&&(i.current=void 0),$(z)},[$]),b=A.useCallback(()=>{const z=h.current;z.splice(0,z.length),c.current=0,i.current=void 0},[]);Sl(m,L,U,_,n),A.useEffect(b,[u,b]),A.useEffect(()=>n?()=>{}:Pr(b,m(Wo,E),m(Vo,O)),[n,m,b,E,O])}function qf(e,t,r){const[n,o]=A.useState(1),[a,s]=A.useState(0),[l,f]=A.useState(0),h=Vf(n,a,l,r),{currentSlide:c,globalIndex:i}=Ut(),{containerRect:u,slideRect:g}=Yt(),{minZoom:p,zoomInMultiplier:m}=Un(),x=c&&pr(c)?c.src:void 0,k=!x||!r?.current;Vt(()=>{o(1),s(0),f(0)},[i,x]);const M=A.useCallback((N,w,y)=>{const E=y||n,O=a-(N||0),$=l-(w||0),P=(e.width*E-g.width)/2/E,L=(e.height*E-g.height)/2/E;s(Math.min(Math.abs(O),Math.max(P,0))*Math.sign(O)),f(Math.min(Math.abs($),Math.max(L,0))*Math.sign($))},[n,a,l,g,e.width,e.height]),v=A.useCallback((N,w,y,E)=>{const O=Yo(N+.01p?N:p:t,5);w||h(),M(y?y*(1/n-1/O):0,E?E*(1/n-1/O):0,O),o(O)},[n,p,t,M,h]),T=Le(()=>{n>1&&(n>t&&v(t,!0),M())});Vt(T,[u.width,u.height,T]);const I=A.useCallback(()=>{const N=n*m;v(n<1&&N>1?1:N)},[n,m,v]),S=A.useCallback(()=>{const N=n/m;v(n>1&&N<1?1:N)},[n,m,v]);return{zoom:n,offsetX:a,offsetY:l,disabled:k,changeOffsets:M,changeZoom:v,zoomIn:I,zoomOut:S}}const Hl=A.createContext(null),la=$t("useZoom","ZoomControllerContext",Hl);function Zf({children:e}){const[t,r]=A.useState(),{slideRect:n}=Yt(),{ref:o,minZoom:a}=Un(),{imageRect:s,maxZoom:l}=Yf(n,t?.imageDimensions),{zoom:f,offsetX:h,offsetY:c,disabled:i,changeZoom:u,changeOffsets:g,zoomIn:p,zoomOut:m}=qf(s,l,t?.zoomWrapperRef);Kf(f,i),Xf(f,a,l,i,p,m,u,g,t?.zoomWrapperRef);const x=A.useMemo(()=>({zoom:f,minZoom:a,maxZoom:l,offsetX:h,offsetY:c,disabled:i,zoomIn:p,zoomOut:m,changeZoom:u}),[f,a,l,h,c,i,p,m,u]);A.useImperativeHandle(o,()=>x,[x]);const k=A.useMemo(()=>({...x,setZoomWrapper:r}),[x,r]);return A.createElement(Hl.Provider,{value:k},e)}const Qf=xt("ZoomIn",A.createElement(A.Fragment,null,A.createElement("path",{d:"M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z"}),A.createElement("path",{d:"M12 10h-2v2H9v-2H7V9h2V7h1v2h2v1z"}))),Jf=xt("ZoomOut",A.createElement("path",{d:"M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14zM7 9h5v1H7z"})),Gl=A.forwardRef(function({zoomIn:t,onLoseFocus:r},n){const o=A.useRef(!1),a=A.useRef(!1),{zoom:s,minZoom:l,maxZoom:f,zoomIn:h,zoomOut:c,disabled:i}=la(),{render:u}=ct(),g=i||(t?s>=f:s<=l);return A.useEffect(()=>{g&&o.current&&a.current&&r(),g||(o.current=!0)},[g,r]),A.createElement(Ur,{ref:n,disabled:g,label:t?"Zoom in":"Zoom out",icon:t?Qf:Jf,renderIcon:t?u.iconZoomIn:u.iconZoomOut,onClick:t?h:c,onFocus:()=>{a.current=!0},onBlur:()=>{a.current=!1}})});function Rf(){const e=A.useRef(null),t=A.useRef(null),{focus:r}=Yt(),n=A.useCallback(s=>{var l,f;!((l=s.current)===null||l===void 0)&&l.disabled?r():(f=s.current)===null||f===void 0||f.focus()},[r]),o=A.useCallback(()=>n(e),[n]),a=A.useCallback(()=>n(t),[n]);return A.createElement(A.Fragment,null,A.createElement(Gl,{zoomIn:!0,ref:e,onLoseFocus:a}),A.createElement(Gl,{ref:t,onLoseFocus:o}))}function eg(){const{render:e}=ct(),t=la();return e.buttonZoom?A.createElement(A.Fragment,null,e.buttonZoom(t)):A.createElement(Rf,null)}function tg(e){var t;return(((t=e.srcSet)===null||t===void 0?void 0:t.length)||0)>0}function rg({current:e,preload:t},{type:r,source:n}){switch(r){case"fetch":return e?{current:e,preload:n}:{current:n};case"done":return n===t?{current:n}:{current:e,preload:t};default:throw new Error(al)}}function ng(e){var t,r;const[{current:n,preload:o},a]=A.useReducer(rg,{}),{slide:s,rect:l,imageFit:f,render:h,interactive:c}=e,i=s.srcSet.sort((I,S)=>I.width-S.width),u=(t=s.width)!==null&&t!==void 0?t:i[i.length-1].width,g=(r=s.height)!==null&&r!==void 0?r:i[i.length-1].height,p=Xo(s,f),m=Math.max(...i.map(I=>I.width)),x=Math.min((p?Math.max:Math.min)(l.width,u*(l.height/g)),m),k=Ju(),M=Le(()=>{var I;const S=(I=i.find(N=>N.width>=x*k))!==null&&I!==void 0?I:i[i.length-1];(!n||i.findIndex(N=>N.src===n)N===S))&&a({type:"fetch",source:S.src})});Vt(M,[l.width,l.height,k,M]);const v=Le(I=>a({type:"done",source:I})),T={WebkitTransform:c?"initial":"translateZ(0)"};return p||Object.assign(T,l.width/l.heightv(o),render:{...h,iconLoading:()=>null,iconError:()=>null}}),n&&A.createElement(mr,{key:"current",...e,slide:{...s,src:n,srcSet:void 0},style:T}))}function og({render:e,slide:t,offset:r,rect:n}){var o;const[a,s]=A.useState(),l=A.useRef(null),{zoom:f,maxZoom:h,offsetX:c,offsetY:i,setZoomWrapper:u}=la(),g=f>1,{carousel:p,on:m}=ct(),{currentIndex:x}=Ut();Vt(()=>r===0?(u({zoomWrapperRef:l,imageDimensions:a}),()=>u(void 0)):()=>{},[r,a,u]);let k=(o=e.slide)===null||o===void 0?void 0:o.call(e,{slide:t,offset:r,rect:n,zoom:f,maxZoom:h});if(!k&&pr(t)){const M={slide:t,offset:r,rect:n,render:e,imageFit:p.imageFit,imageProps:p.imageProps,onClick:r===0?()=>{var v;return(v=m.click)===null||v===void 0?void 0:v.call(m,{index:x})}:void 0};k=tg(t)?A.createElement(ng,{...M,slide:t,interactive:g,rect:r===0?{width:n.width*f,height:n.height*f}:n}):A.createElement(mr,{onLoad:v=>s({width:v.naturalWidth,height:v.naturalHeight}),...M})}return k?A.createElement("div",{ref:l,className:ht(ye(qs),ye(Ir),ye(Qs),g&&ye(Vu)),style:r===0?{transform:`scale(${f}) translateX(${c}px) translateY(${i}px)`}:void 0},k):null}const ag=({augment:e,addModule:t})=>{e(({zoom:r,toolbar:n,render:o,controller:a,...s})=>{const l=Fl(r);return{zoom:l,toolbar:Ln(n,Xs,A.createElement(eg,null)),render:{...o,slide:f=>{var h;return pr(f.slide)?A.createElement(og,{render:o,...f}):(h=o.slide)===null||h===void 0?void 0:h.call(o,f)}},controller:{...a,preventDefaultWheelY:l.scrollToZoom},...s}}),t(Ft(Xs,Zf))},sg={separator:"/",container:{}},jl=e=>({...sg,...e});function lg({counter:e}){const{slides:t,currentIndex:r}=Ut(),{separator:n,container:{className:o,...a},className:s,...l}=jl(e);return t.length===0?null:A.createElement("div",{className:ht(ye("counter"),o||s),...l,...a,"aria-hidden":!0},r+1," ",n," ",t.length)}function ig({augment:e,addChild:t}){e(({counter:r,...n})=>({counter:jl(r),...n})),t(rn,Ft(Uu,lg))}const cg={autoplay:!1,delay:3e3,ref:null},Bl=e=>({...cg,...e}),Wl=A.createContext(null),dg=$t("useSlideshow","SlideshowContext",Wl);function ug({slideshow:e,carousel:{finite:t},on:r,children:n}){const{autoplay:o,delay:a,ref:s}=Bl(e),l=A.useRef(o),[f,h]=A.useState(o),c=A.useRef(void 0),i=A.useRef(void 0),{slides:u,currentIndex:g}=Ut(),{setTimeout:p,clearTimeout:m}=cn(),{subscribe:x}=Fr(),{next:k}=Yt(),{setAutoPlaying:M}=ea();A.useEffect(()=>M(f),[f,M]);const v=u.length===0||t&&g===u.length-1,T=A.useCallback(()=>{!f&&!v&&h(!0)},[f,v]),I=A.useCallback(()=>{f&&h(!1)},[f]),S=A.useCallback(()=>{m(c.current),c.current=void 0},[m]),N=Le(()=>{S(),!(!f||v||i.current===Mr||i.current===Go)&&(c.current=p(()=>{f&&(i.current=void 0,k())},a))});A.useEffect(N,[g,f,N]),A.useEffect(()=>{f&&v&&h(!1)},[g,f,v]);const w=Le(()=>{var O;return(O=r.slideshowStart)===null||O===void 0?void 0:O.call(r)}),y=Le(()=>{var O;return(O=r.slideshowStop)===null||O===void 0?void 0:O.call(r)});A.useEffect(()=>{f?w():l.current&&y(),l.current=f},[f,w,y]),A.useEffect(()=>Pr(S,x(Hu,()=>{i.current=Mr,S()}),x(Gu,()=>{i.current=Go,S()}),x(ju,()=>{i.current=an,N()}),x(Bu,()=>{i.current=sn,N()})),[x,S,N]);const E=A.useMemo(()=>({playing:f,disabled:v,play:T,pause:I}),[f,v,T,I]);return A.useImperativeHandle(s,()=>E,[E]),A.createElement(Wl.Provider,{value:E},n)}const fg=xt("Play",A.createElement("path",{d:"M8 5v14l11-7z"})),gg=xt("Pause",A.createElement("path",{d:"M6 19h4V5H6v14zm8-14v14h4V5h-4z"}));function hg(){const{playing:e,disabled:t,play:r,pause:n}=dg(),{render:o}=ct(),a=kl(Yt().focus,t);return o.buttonSlideshow?A.createElement(A.Fragment,null,o.buttonSlideshow({playing:e,disabled:t,play:r,pause:n})):A.createElement(Ur,{label:e?"Pause":"Play",icon:e?gg:fg,renderIcon:e?o.iconSlideshowPause:o.iconSlideshowPlay,onClick:e?n:r,disabled:t,...a})}function pg({augment:e,addModule:t}){e(({slideshow:r,toolbar:n,...o})=>({toolbar:Ln(n,Ys,A.createElement(hg,null)),slideshow:Bl(r),...o})),t(Ft(Ys,ug))}const mg={auto:!1,ref:null},Vl=e=>({...mg,...e}),Kl=A.createContext(null),bg=$t("useFullscreen","FullscreenContext",Kl);function yg({fullscreen:e,on:t,children:r}){const{auto:n,ref:o}=Vl(e),a=A.useRef(null),[s,l]=A.useState(),[f,h]=A.useState(!1),c=A.useRef(!1),{getOwnerDocument:i}=$r();Vt(()=>{var v,T,I,S;const N=i();l(!((S=(I=(T=(v=N.fullscreenEnabled)!==null&&v!==void 0?v:N.webkitFullscreenEnabled)!==null&&T!==void 0?T:N.mozFullScreenEnabled)!==null&&I!==void 0?I:N.msFullscreenEnabled)!==null&&S!==void 0&&S))},[i]);const u=A.useCallback(()=>{var v;const T=i(),I=T.fullscreenElement||T.webkitFullscreenElement||T.mozFullScreenElement||T.msFullscreenElement;return((v=I?.shadowRoot)===null||v===void 0?void 0:v.fullscreenElement)||I},[i]),g=A.useCallback(()=>{const v=a.current;try{v.requestFullscreen?v.requestFullscreen().catch(()=>{}):v.webkitRequestFullscreen?v.webkitRequestFullscreen():v.mozRequestFullScreen?v.mozRequestFullScreen():v.msRequestFullscreen&&v.msRequestFullscreen()}catch{}},[]),p=A.useCallback(()=>{if(!u())return;const v=i();try{v.exitFullscreen?v.exitFullscreen().catch(()=>{}):v.webkitExitFullscreen?v.webkitExitFullscreen():v.mozCancelFullScreen?v.mozCancelFullScreen():v.msExitFullscreen&&v.msExitFullscreen()}catch{}},[u,i]);A.useEffect(()=>{const v=i(),T=()=>{h(u()===a.current)};return Pr(...["fullscreenchange","webkitfullscreenchange","mozfullscreenchange","MSFullscreenChange"].map(I=>(v.addEventListener(I,T),()=>v.removeEventListener(I,T))))},[u,i]);const m=Le(()=>{var v;return(v=t.enterFullscreen)===null||v===void 0?void 0:v.call(t)}),x=Le(()=>{var v;return(v=t.exitFullscreen)===null||v===void 0?void 0:v.call(t)});A.useEffect(()=>{f&&(c.current=!0),c.current&&(f?m:x)()},[f,m,x]);const k=Le(()=>{var v;return(v=n?g:null)===null||v===void 0||v(),p});A.useEffect(k,[k]);const M=A.useMemo(()=>({fullscreen:f,disabled:s,enter:g,exit:p}),[f,s,g,p]);return A.useImperativeHandle(o,()=>M,[M]),A.createElement("div",{ref:a,className:ht(ye(nn),ye(qs))},A.createElement(Kl.Provider,{value:M},r))}const vg=xt("EnterFullscreen",A.createElement("path",{d:"M7 14H5v5h5v-2H7v-3zm-2-4h2V7h3V5H5v5zm12 7h-3v2h5v-5h-2v3zM14 5v2h3v3h2V5h-5z"})),wg=xt("ExitFullscreen",A.createElement("path",{d:"M5 16h3v3h2v-5H5v2zm3-8H5v2h5V5H8v3zm6 11h2v-3h3v-2h-5v5zm2-11V5h-2v5h5V8h-3z"}));function xg(){var e;const{fullscreen:t,disabled:r,enter:n,exit:o}=bg(),{render:a}=ct();return r?null:a.buttonFullscreen?A.createElement(A.Fragment,null,(e=a.buttonFullscreen)===null||e===void 0?void 0:e.call(a,{fullscreen:t,disabled:r,enter:n,exit:o})):A.createElement(Ur,{disabled:r,label:t?"Exit Fullscreen":"Enter Fullscreen",icon:t?wg:vg,renderIcon:t?a.iconExitFullscreen:a.iconEnterFullscreen,onClick:t?o:n})}function kg({augment:e,contains:t,addParent:r}){e(({fullscreen:n,toolbar:o,...a})=>({toolbar:Ln(o,nn,A.createElement(xg,null)),fullscreen:Vl(n),...a})),r(t(on)?on:rn,Ft(nn,yg))}const yr={ref:null,position:"bottom",width:120,height:80,border:1,borderRadius:4,padding:4,gap:16,imageFit:"contain",vignette:!0,hidden:!1,showToggle:!1},ia=e=>({...yr,...e});function Yl(){const{thumbnails:e}=ct();return ia(e)}const vr=e=>fr(on,e),dt=e=>vr(fr("thumbnail",e)),Cg=xt("VideoThumbnail",A.createElement("path",{d:"M10 16.5l6-4.5-6-4.5v9zM12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8z"})),_g=xt("UnknownThumbnail",A.createElement("path",{d:"M23 18V6c0-1.1-.9-2-2-2H3c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h18c1.1 0 2-.9 2-2zM8.5 12.5l2.5 3.01L14.5 11l4.5 6H5l3.5-4.5z"}));function Sg({slide:e,render:t,rect:r,imageFit:n}){var o;const a=(o=t.thumbnail)===null||o===void 0?void 0:o.call(t,{slide:e,render:t,rect:r,imageFit:n});if(a)return a;const s={render:t,rect:r,imageFit:n};if(e.thumbnail)return A.createElement(mr,{slide:{src:e.thumbnail},...s});if(pr(e))return A.createElement(mr,{slide:e,...s});const l=ye(dt(In));return e.type==="video"?A.createElement(A.Fragment,null,e.poster&&A.createElement(mr,{slide:{src:e.poster},...s}),A.createElement(Cg,{className:l})):A.createElement(_g,{className:l})}const Eg=gr("active"),ca=gr("fadein"),da=gr("fadeout"),Tg=gr("placeholder"),Xl="delay",ql="duration";function Ag({slide:e,index:t,onClick:r,fadeIn:n,fadeOut:o,placeholder:a,onLoseFocus:s}){const l=A.useRef(null),{render:f,styles:h,labels:c}=ct(),{slides:i,globalIndex:u}=Ut(),{getOwnerDocument:g}=$r(),{width:p,height:m,imageFit:x}=Yl(),k={width:p,height:m},M=t===u,v=Le(s);return A.useEffect(()=>{o&&g().activeElement===l.current&&v()},[o,v,g]),A.createElement("button",{ref:l,type:"button",className:ht(ye(Ir),ye(dt()),M&&ye(dt(Eg())),n&&ye(dt(ca())),o&&ye(dt(da())),a&&ye(dt(Tg()))),style:{...n?{[Xe(dt(ca(ql)))]:`${n.duration}ms`,[Xe(dt(ca(Xl)))]:`${n.delay}ms`}:null,...o?{[Xe(dt(da(ql)))]:`${o.duration}ms`,[Xe(dt(da(Xl)))]:`${o.delay}ms`}:null,...h.thumbnail},onClick:r,"aria-current":M?!0:void 0,"aria-label":ll(c,i,t)},e&&Sg({slide:e,render:f,rect:k,imageFit:x}))}function Zl(e){return["top","bottom"].includes(e)}function Ql(e,t){return t+2*(e.border+e.padding)+e.gap}function Ng(e){const{thumbnail:t,poster:r}=e||{thumbnail:"placeholder"};return typeof t=="string"&&t||typeof r=="string"&&r||e&&il(e)||void 0}function Jl({visible:e,containerRef:t}){const r=A.useRef(null),n=Fn(),{publish:o,subscribe:a}=Fr(),{carousel:s,styles:l,labels:f}=ct(),{slides:h,globalIndex:c,animation:i}=Ut(),{registerSensors:u,subscribeSensors:g}=Cl();Ol(g);const p=Yl(),{position:m,width:x,height:k,border:M,borderStyle:v,borderColor:T,borderRadius:I,padding:S,gap:N,vignette:w}=p,y=i?.duration!==void 0&&i?.increment||0,E=i?.duration||0,{prepareAnimation:O}=ra(r,_=>({keyframes:Zl(m)?[{transform:`translateX(${(n?-1:1)*Ql(p,x)*y+_}px)`},{transform:"translateX(0)"}]:[{transform:`translateY(${Ql(p,k)*y+_}px)`},{transform:"translateY(0)"}],duration:E,easing:i?.easing})),$=Le(()=>{let _=0;if(t.current&&r.current){const b=t.current.getBoundingClientRect(),z=r.current.getBoundingClientRect();_=Zl(m)?z.left-b.left-(b.width-z.width)/2:z.top-b.top-(b.height-z.height)/2}O(_)});A.useEffect(()=>Pr(a(Bo,$)),[a,$]);const P=cl(s,h),L=[];if(Zo(h))for(let _=c-P-Math.abs(y);_<=c+P+Math.abs(y);_+=1){const z=s.finite&&(_<0||_>h.length-1)||y<0&&_0&&_>c+P?null:Qo(h,_),F=[`${_}`,Ng(z)].filter(Boolean).join("|");L.push({key:F,index:_,slide:z})}const U=_=>()=>{_>c?o(or,{count:_-c}):_{const F=E/Math.abs(y||1),G=y>0&&b>c+P-y&&b<=c+P||y<0&&b=c-P?{duration:F,delay:((y>0?b-(c+P-y):c-P-y-b)-1)*F}:void 0,X=y>0&&bc+P?{duration:F,delay:(y>0?y-(c-P-b):-y-(b-(c+P)))*F}:void 0;return A.createElement(Ag,{key:_,index:b,slide:z,fadeIn:G,fadeOut:X,placeholder:!z,onClick:U(b),onLoseFocus:()=>{var B;return(B=r.current)===null||B===void 0?void 0:B.focus()}})})),w&&A.createElement("div",{className:ye(vr("vignette"))}))}const Rl=A.createContext(null),Og=$t("useThumbnails","ThumbnailsContext",Rl);function Mg({children:e,...t}){const{ref:r,position:n,hidden:o}=ia(t.thumbnails),[a,s]=A.useState(!o),l=A.useRef(null),f=A.useMemo(()=>({visible:a,show:()=>s(!0),hide:()=>s(!1)}),[a]);return A.useImperativeHandle(r,()=>f,[f]),A.createElement(pl,{...t},A.createElement(Rl.Provider,{value:f},A.createElement("div",{ref:l,className:ht(ye(vr()),ye(vr(`${n}`)))},["start","top"].includes(n)&&A.createElement(Jl,{containerRef:l,visible:a}),A.createElement("div",{className:ye(vr("wrapper"))},e),["end","bottom"].includes(n)&&A.createElement(Jl,{containerRef:l,visible:a}))))}const ei=()=>A.createElement(A.Fragment,null,A.createElement("path",{strokeWidth:2,stroke:"currentColor",strokeLinejoin:"round",fill:"none",d:"M3 5l18 0l0 14l-18 0l0-14z"}),A.createElement("path",{d:"M5 14h4v3h-4zM10 14h4v3h-4zM15 14h4v3h-4z"})),Ig=xt("ThumbnailsVisible",ei()),Dg=uf("ThumbnailsHidden",ei());function Pg(){const{visible:e,show:t,hide:r}=Og(),{render:n}=ct();return n.buttonThumbnails?A.createElement(A.Fragment,null,n.buttonThumbnails({visible:e,show:t,hide:r})):A.createElement(Ur,{label:e?"Hide thumbnails":"Show thumbnails",icon:e?Ig:Dg,renderIcon:e?n.iconThumbnailsVisible:n.iconThumbnailsHidden,onClick:e?r:t})}function Lg({augment:e,contains:t,append:r,addParent:n}){e(({thumbnails:a,toolbar:s,...l})=>{const f=ia(a);return{toolbar:Ln(s,on,f.showToggle?A.createElement(Pg,null):null),thumbnails:f,...l}});const o=Ft(on,Mg);t(nn)?r(nn,o):n(rn,o)}const $g="X-Board-Secret-View-Token";function zn(){const e=window.G7Core?.state?.get?.("_global")?.secretViewToken;return typeof e=="string"&&e!==""?{[$g]:e}:{}}const It=({className:e="",children:t,style:r,...n})=>d.jsx("i",{className:e,style:r,...n,children:t}),ua=e=>{if(!e||typeof window>"u"||!window.location||!/^(https?:)?\/\//i.test(e))return!1;try{return new URL(e,window.location.href).origin!==window.location.origin}catch{return!1}},fa=window.G7Core,Hn=(e,t)=>fa?.t?.(e,t)??e,Fg=async(e,t)=>{try{const r=await fa.api.get(e,{responseType:"blob",headers:zn()});if(r){const n=URL.createObjectURL(r),o=document.createElement("a");o.href=n,o.download=t,document.body.appendChild(o),o.click(),document.body.removeChild(o),URL.revokeObjectURL(n)}}catch(r){console.error("Failed to download file:",r),fa?.toast?.error?.(Hn("common.download_failed"))}},Ug=(e,t)=>{const r=document.createElement("a");r.href=e,r.download=t,r.target="_blank",document.body.appendChild(r),r.click(),document.body.removeChild(r)},ti=async e=>{const t=e.downloadUrl||e.src,r=e.filename||e.title||"image";e.downloadRequiresAuth&&!ua(t)?await Fg(t,r):Ug(t,r)},zg=({image:e,index:t,onDownload:r})=>{const[n,o]=C.useState(!1),a=async s=>{if(s.stopPropagation(),r){r(e,t);return}o(!0);try{await ti(e)}finally{o(!1)}};return d.jsx(R,{type:"button",onClick:a,disabled:n,className:"yarl__button flex items-center justify-center","aria-label":Hn("common.download"),title:Hn("common.download"),children:n?d.jsx(It,{className:"fa-solid fa-spinner fa-spin text-white"}):d.jsx(It,{className:"fa-solid fa-download text-white"})})},zr=({images:e,isOpen:t,onClose:r,startIndex:n=0,enableZoom:o=!0,enableSlideshow:a=!1,enableFullscreen:s=!0,showCounter:l=!0,showDownload:f=!0,showThumbnails:h=!0,onDownload:c,editorAttrs:i,id:u})=>{const[g,p]=C.useState(n),m=C.useRef(n),x=e.map(v=>({src:v.src,title:v.title,description:v.description})),k=[];o&&k.push(ag),a&&k.push(pg),s&&k.push(kg),l&&k.push(ig),h&&k.push(Lg);const M=e[g];if(C.useEffect(()=>{m.current=g},[g]),i){const v=(e??[]).slice(0,4);return d.jsx(j,{id:u,className:"grid grid-cols-2 gap-2 p-3 rounded-lg border border-dashed border-gray-300 dark:border-gray-600 bg-gray-50 dark:bg-gray-800",...i,children:v.length>0?v.map((T,I)=>d.jsx(jt,{src:T.thumbnail||T.src,alt:T.title||"",className:"w-full aspect-square object-cover rounded bg-gray-200 dark:bg-gray-700"},I)):d.jsxs(j,{className:"col-span-2 flex items-center justify-center py-6 text-sm text-gray-400 dark:text-gray-500",children:[d.jsx(It,{className:"fa-regular fa-images mr-2"}),Hn("editor.component.image_gallery")]})})}return d.jsx(jf,{open:t,close:r,slides:x,index:g,plugins:k,on:{view:({index:v})=>{v!==m.current&&p(v)}},zoom:{maxZoomPixelRatio:3,zoomInMultiplier:2,doubleTapDelay:300,doubleClickDelay:300,doubleClickMaxStops:2,keyboardMoveDistance:50,wheelZoomDistanceFactor:100,pinchZoomDistanceFactor:100,scrollToZoom:!0},carousel:{finite:!0,preload:2,padding:"16px",spacing:"30%"},animation:{fade:250,swipe:500,easing:{fade:"ease",swipe:"ease-out",navigation:"ease-in-out"}},controller:{closeOnBackdropClick:!0,closeOnPullDown:!0,closeOnPullUp:!0},thumbnails:{position:"bottom",width:120,height:80,border:2,borderRadius:4,padding:4,gap:16,showToggle:!1,vignette:!0},toolbar:{buttons:[f&&M&&d.jsx(zg,{image:M,index:g,onDownload:c},"download"),"close"].filter(Boolean)},styles:{container:{backgroundColor:"rgba(0, 0, 0, 0.9)"}}})},ri=()=>{const[e,t]=C.useState(!1),[r,n]=C.useState([]),[o,a]=C.useState(0),s=C.useCallback((f,h=0)=>{n(f),a(h),t(!0)},[]),l=C.useCallback(()=>{t(!1)},[]);return{isOpen:e,openGallery:s,closeGallery:l,galleryProps:{images:r,isOpen:e,onClose:l,startIndex:o}}};zr.displayName="ImageGallery";const ni=Object.freeze(Object.defineProperty({__proto__:null,ImageGallery:zr,default:zr,executeImageDownload:ti,useImageGallery:ri},Symbol.toStringTag,{value:"Module"})),Hg=window.G7Core,ga=(e,t)=>Hg?.t?.(e,t)??e,Gn=e=>e.url??e.download_url,jn=({images:e=[],className:t="",id:r,editorAttrs:n})=>{const[o,a]=C.useState(0),{openGallery:s,galleryProps:l}=ri(),f=C.useMemo(()=>e.map(c=>({src:Gn(c),title:c.alt_text_current??"",thumbnail:Gn(c),downloadUrl:c.download_url,filename:c.alt_text_current??`image-${c.id}`})),[e]);if(!e||e.length===0)return d.jsx(j,{className:`flex items-center justify-center bg-gray-100 dark:bg-gray-700 rounded-lg aspect-square ${t}`,id:r,...n,children:d.jsxs(j,{className:"text-center text-gray-400 dark:text-gray-500",children:[d.jsx(fe,{name:"image",size:"3x",className:"mb-3 opacity-50"}),d.jsx(j,{className:"text-sm",children:ga("shop.no_image")})]})});const h=e[o]??e[0];return d.jsxs(j,{id:r,className:t,...n,children:[d.jsx(j,{className:"relative overflow-hidden rounded-lg bg-gray-100 dark:bg-gray-700 aspect-square mb-3",children:d.jsx(R,{type:"button",className:"w-full h-full cursor-zoom-in block",onClick:()=>s(f,o),"aria-label":ga("shop.view_image"),children:d.jsx(jt,{src:Gn(h),alt:h.alt_text_current??"",className:"w-full h-full object-contain"})})}),e.length>1&&d.jsx(j,{className:"flex gap-2 overflow-x-auto pb-1",children:e.map((c,i)=>d.jsx(R,{type:"button",className:`flex-shrink-0 w-16 h-16 rounded-md overflow-hidden border-2 transition-colors cursor-pointer ${i===o?"border-gray-900 dark:border-white":"border-gray-200 dark:border-gray-600 hover:border-gray-400 dark:hover:border-gray-400"}`,onClick:()=>a(i),"aria-label":c.alt_text_current??`${ga("shop.image")} ${i+1}`,children:d.jsx(jt,{src:Gn(c),alt:c.alt_text_current??"",className:"w-full h-full object-cover"})},c.id))}),d.jsx(zr,{...l})]})};jn.displayName="ProductImageViewer";const Gg=Object.freeze(Object.defineProperty({__proto__:null,ProductImageViewer:jn,default:jn},Symbol.toStringTag,{value:"Module"})),oi=({value:e,min:t=1,max:r=999,onChange:n,size:o="md",disabled:a=!1,className:s="",id:l,editorAttrs:f})=>{const c={sm:{container:"h-8",button:"w-8 h-8 text-sm",input:"w-10 text-sm"},md:{container:"h-10",button:"w-10 h-10",input:"w-14"},lg:{container:"h-12",button:"w-12 h-12 text-lg",input:"w-16 text-lg"}}[o],i=()=>{e>t&&!a&&n(e-1)},u=()=>{e{const M=parseInt(k.target.value,10);if(!isNaN(M)){const v=Math.max(t,Math.min(r,M));n(v)}},p=k=>{const M=parseInt(k.target.value,10);isNaN(M)||Mr&&n(r)},m=a||e<=t,x=a||e>=r;return d.jsxs(j,{className:`inline-flex items-center border border-gray-300 dark:border-gray-600 rounded-lg ${c.container} ${s}`,id:l,...f,children:[d.jsx(R,{type:"button",onClick:i,disabled:m,className:`${c.button} flex items-center justify-center text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700 disabled:opacity-50 disabled:cursor-not-allowed rounded-l-lg transition-colors`,"aria-label":"수량 감소",children:d.jsx(fe,{name:"minus",className:"w-4 h-4"})}),d.jsx(At,{type:"number",value:e,onChange:g,onBlur:p,min:t,max:r,disabled:a,className:`${c.input} text-center border-x border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-800 text-gray-900 dark:text-white focus:outline-none disabled:opacity-50 disabled:cursor-not-allowed [appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none`,"aria-label":"수량"}),d.jsx(R,{type:"button",onClick:u,disabled:x,className:`${c.button} flex items-center justify-center text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700 disabled:opacity-50 disabled:cursor-not-allowed rounded-r-lg transition-colors`,"aria-label":"수량 증가",children:d.jsx(fe,{name:"plus",className:"w-4 h-4"})})]})},jg=Object.freeze(Object.defineProperty({__proto__:null,default:oi},Symbol.toStringTag,{value:"Module"})),Bg=window.G7Core?.createLogger?.("Comp:PostReactions")??{log:(...e)=>console.log("[Comp:PostReactions]",...e),warn:(...e)=>console.warn("[Comp:PostReactions]",...e),error:(...e)=>console.error("[Comp:PostReactions]",...e)},ai=(e,t)=>window.G7Core?.t?.(e,t)??e,Wg=[{type:"like",emoji:"👍",labelKey:"board.reactions.like",activeColor:"bg-blue-100 dark:bg-blue-900/30 text-blue-600 dark:text-blue-400"},{type:"funny",emoji:"😂",labelKey:"board.reactions.funny",activeColor:"bg-yellow-100 dark:bg-yellow-900/30 text-yellow-600 dark:text-yellow-400"},{type:"agree",emoji:"👌",labelKey:"board.reactions.agree",activeColor:"bg-green-100 dark:bg-green-900/30 text-green-600 dark:text-green-400"},{type:"thanks",emoji:"🙏",labelKey:"board.reactions.thanks",activeColor:"bg-purple-100 dark:bg-purple-900/30 text-purple-600 dark:text-purple-400"},{type:"wow",emoji:"😮",labelKey:"board.reactions.wow",activeColor:"bg-orange-100 dark:bg-orange-900/30 text-orange-600 dark:text-orange-400"}],si=({reactions:e,userReaction:t,postId:r,onReact:n,size:o="md",className:a="",id:s,editorAttrs:l})=>{const f={sm:"px-2 py-1 text-xs gap-1",md:"px-3 py-1.5 text-sm gap-1.5",lg:"px-4 py-2 text-base gap-2"},h={sm:"text-sm",md:"text-base",lg:"text-lg"},c=u=>e.find(p=>p.type===u)?.count??0,i=async u=>{if(n){n(u);return}try{if(!(await fetch(`/api/posts/${r}/reactions`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({type:u}),credentials:"include"})).ok)throw new Error("리액션 처리 실패");window.location.reload()}catch(g){Bg.error("리액션 오류:",g)}};return d.jsx(j,{className:`flex flex-wrap gap-2 ${a}`,id:s,...l,children:Wg.map(u=>{const g=c(u.type),p=t===u.type;return d.jsxs(R,{onClick:()=>i(u.type),className:` + `,children:[d.jsx(fe,{name:nt.Moon,className:"w-5 h-5 text-gray-600 dark:text-gray-400"}),d.jsx(te,{className:"flex-1 text-left text-gray-900 dark:text-white",children:o}),f==="dark"&&d.jsx(fe,{name:nt.Check,className:"w-4 h-4 text-blue-600 dark:text-blue-400 ml-auto"})]})]})})]})},Ud=Object.freeze(Object.defineProperty({__proto__:null,ThemeToggle:An,default:An},Symbol.toStringTag,{value:"Module"})),vs={xs:"w-6 h-6 text-xs",sm:"w-8 h-8 text-sm",md:"w-10 h-10 text-base",lg:"w-12 h-12 text-lg",xl:"w-16 h-16 text-xl","2xl":"w-24 h-24 text-2xl","3xl":"w-32 h-32 text-3xl","4xl":"w-40 h-40 text-4xl","5xl":"w-48 h-48 text-5xl"},zd="rounded-full overflow-hidden flex-shrink-0 border border-gray-200 dark:border-gray-700",Hd="grayscale opacity-50",No={normal:"bg-gradient-to-br from-blue-500 to-purple-600",withdrawn:"bg-gray-300 dark:bg-gray-600",guest:"bg-gray-200 dark:bg-gray-700"},Qr=({author:e,name:t,avatar:r,size:n="md",className:o="",text:a,isWithdrawn:s=!1,isGuest:l=!1,id:f,editorAttrs:h})=>{const c=r??e?.avatar,i=a??t??e?.name??"?",u=s||e?.status==="withdrawn",g=l||e?.is_guest||!1,p=C.useMemo(()=>[vs[n]||vs.md,zd,u?Hd:"",o].filter(Boolean).join(" "),[n,u,o]),m=g?No.guest:u?No.withdrawn:No.normal,x=()=>g?d.jsx(j,{className:`w-full h-full flex items-center justify-center ${m}`,children:d.jsx(fe,{name:"user",className:"text-gray-400 dark:text-gray-500"})}):u?d.jsx(j,{className:`w-full h-full flex items-center justify-center ${m}`,children:d.jsx(fe,{name:"user",className:"text-gray-500 dark:text-gray-400"})}):d.jsx(j,{className:`w-full h-full flex items-center justify-center text-white font-semibold ${m}`,children:i.charAt(0).toUpperCase()});return d.jsx(j,{className:p,id:f,...h,children:c&&!g&&!u?d.jsx(jt,{src:c,alt:i,className:"w-full h-full object-cover"}):x()})},Gd=Object.freeze(Object.defineProperty({__proto__:null,Avatar:Qr},Symbol.toStringTag,{value:"Module"})),jd=window.G7Core?.createLogger?.("Comp:SlotContainer")??{log:(...e)=>console.log("[Comp:SlotContainer]",...e),warn:(...e)=>console.warn("[Comp:SlotContainer]",...e),error:(...e)=>console.error("[Comp:SlotContainer]",...e)};function ws(e,t){return t&&e&&typeof e.id=="string"&&e.id.length>0?{...e,id:`${e.id}__${t}`}:e}const Nn=({slotId:e,className:t="",emptyContent:r,style:n,id:o,children:a})=>{const[s,l]=C.useState([]),[,f]=C.useState({}),h=C.useCallback(()=>{const p=window.__slotContextValue;if(p)return p;const m=window.G7Core;return m&&m.getSlotContext?.()||null},[]),c=C.useCallback(()=>{const p=h();p&&p.isEnabled&&l(p.getSlotComponents(e))},[e,h]);C.useEffect(()=>{const p=h();return!p||!p.isEnabled?void 0:(c(),p.subscribeToSlot(e,()=>{c(),f({})}))},[e,h,c]);const i=C.useCallback(()=>{const p=window.G7Core;return p?p.getDynamicRenderer?.():null},[]);if(s.length===0)return r?d.jsx(j,{className:t,style:n,id:o,children:r}):a?d.jsx(j,{className:t,style:n,id:o,children:a}):null;const u=i(),g=window.G7Core;return!u||!g?(jd.error("G7Core or DynamicRenderer not available"),null):d.jsxs(j,{className:t,style:n,id:o,children:[s.map(p=>{const{componentDef:m,dataContext:x,parentFormContext:k,getParentComponentContext:M,translationContext:v}=p,T=ws(m,o);return d.jsx(u,{componentDef:T,dataContext:x,translationContext:v,registry:g.getComponentRegistry(),bindingEngine:g.getDataBindingEngine(),translationEngine:g.getTranslationEngine(),actionDispatcher:g.getActionDispatcher(),parentComponentContext:M?.(),parentFormContextProp:k,isRootRenderer:!1},T.id||`slot-${e}-${p.order}`)}),a]})},Bd=Object.freeze(Object.defineProperty({__proto__:null,SlotContainer:Nn,default:Nn,scopeSlotChildDef:ws},Symbol.toStringTag,{value:"Module"})),On=({notifications:e=[],unreadCount:t=0,hasMore:r=!1,loading:n=!1,titleText:o="알림",emptyText:a="알림이 없습니다.",markAllReadText:s="모두 읽음",deleteAllText:l="모두 삭제",unreadOnlyText:f="안 읽은 알림만",unreadOnly:h=!1,onNotificationClick:c,onClose:i,onLoadMore:u,onMarkAllRead:g,onDeleteAll:p,onDelete:m,onUnreadOnlyToggle:x,dropdownAlign:k="right",className:M="",id:v,editorAttrs:T})=>{const[I,S]=C.useState(!1),N=C.useRef(null),w=C.useRef(null),y=C.useRef(null),E=C.useRef(null),O=C.useRef(new Set),$=C.useRef(u),P=C.useRef(r),L=C.useRef(n),U=C.useRef(e.length),_=C.useRef(-1);C.useEffect(()=>{$.current=u,P.current=r,L.current=n,U.current=e.length}),C.useEffect(()=>{if(!E.current||!y.current||!I)return;_.current=-1;const G=new IntersectionObserver(([X])=>{X.isIntersecting&&(L.current||!P.current||_.current!==U.current&&(_.current=U.current,$.current?.()))},{root:y.current,threshold:.1});return G.observe(E.current),()=>G.disconnect()},[I]),C.useEffect(()=>{if(!y.current||!I)return;const G=new IntersectionObserver(B=>{B.forEach(Y=>{const H=Y.target.getAttribute("data-notification-id"),W=Y.target.getAttribute("data-unread")==="true";H&&W&&Y.isIntersecting&&O.current.add(H)})},{root:y.current,threshold:.5});return y.current.querySelectorAll("[data-notification-id]").forEach(B=>G.observe(B)),()=>G.disconnect()},[I,e]);const b=t>0?t:e.filter(G=>!G.read).length,z=C.useCallback(()=>{if(I){if(b>0){const G=Array.from(O.current).filter(X=>{const B=e.find(Y=>String(Y.id)===String(X));return B&&!B.read});G.length>0&&i?.(G)}O.current.clear(),S(!1)}},[I,i,b,e]);C.useEffect(()=>{if(!I)return;const G=X=>{N.current&&!N.current.contains(X.target)&&z()};return document.addEventListener("mousedown",G),()=>document.removeEventListener("mousedown",G)},[I,z]);const F=C.useCallback(()=>{I?z():S(!0)},[I,z]);return C.useLayoutEffect(()=>{const G=w.current;if(!I||!G)return;G.style.transform="";const X=G.getBoundingClientRect(),B=typeof window<"u"?window.innerWidth:0;if(X.width===0&&X.height===0)return;const Y=8;let H=0;X.right>B-Y?H=-(X.right-(B-Y)):X.left0&&d.jsx(te,{className:"absolute -top-1 -right-1 w-5 h-5 flex items-center justify-center text-xs font-bold text-white bg-red-500 dark:bg-red-600 rounded-full",children:b>99?"99+":b})]}),I&&d.jsxs(j,{ref:w,className:`absolute ${k==="left"?"left-0":"right-0"} mt-2 w-80 max-w-[calc(100vw-1rem)] bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg shadow-lg z-50`,children:[d.jsxs(j,{className:"px-4 py-3 border-b border-gray-200 dark:border-gray-700 flex items-center justify-between gap-2",children:[d.jsxs(j,{className:"flex items-center gap-3 min-w-0",children:[d.jsx(te,{className:"font-semibold text-gray-900 dark:text-white",children:o}),x&&d.jsxs(j,{className:"flex items-center gap-1.5 cursor-pointer select-none",onClick:()=>x(!h),children:[d.jsx(At,{type:"checkbox",checked:h,onChange:G=>{G.stopPropagation(),x(G.target.checked)},onClick:G=>G.stopPropagation(),className:"w-3.5 h-3.5 rounded border-gray-300 dark:border-gray-600 text-blue-600 dark:text-blue-400 focus:ring-blue-500 dark:focus:ring-blue-400 cursor-pointer"}),d.jsx(te,{className:"text-xs text-gray-600 dark:text-gray-400",children:f})]})]}),d.jsxs(j,{className:"flex items-center gap-3 flex-shrink-0",children:[g&&b>0&&d.jsx(R,{onClick:()=>{g(),O.current.clear()},className:"text-xs text-blue-600 dark:text-blue-400 hover:text-blue-800 dark:hover:text-blue-300 hover:underline cursor-pointer",children:s}),p&&e.length>0&&d.jsx(R,{onClick:()=>{p()},className:"text-xs text-red-600 dark:text-red-400 hover:text-red-800 dark:hover:text-red-300 hover:underline cursor-pointer",children:l})]})]}),d.jsx(j,{ref:y,className:"max-h-96 overflow-y-auto",children:e.length===0&&!n?d.jsx(j,{className:"px-4 py-8 text-center text-gray-500 dark:text-gray-400",children:d.jsx(te,{children:a})}):d.jsxs(d.Fragment,{children:[e.map(G=>{const X=!G.read;return d.jsxs(j,{"data-notification-id":G.id,"data-unread":X?"true":"false",onClick:()=>{G.onClick?.(),c?.(G),S(!1)},className:`group flex items-start gap-3 px-4 py-3 text-left border-b border-gray-100 dark:border-gray-700 transition-colors cursor-pointer ${X?"bg-blue-50/70 dark:bg-blue-900/20 hover:bg-blue-50 dark:hover:bg-blue-900/30":"bg-white dark:bg-gray-800 hover:bg-gray-50 dark:hover:bg-gray-700/50 opacity-70"}`,children:[G.iconName&&d.jsx(fe,{name:G.iconName,className:`w-5 h-5 mt-0.5 flex-shrink-0 ${X?"text-blue-600 dark:text-blue-400":"text-gray-400 dark:text-gray-500"}`}),d.jsxs(j,{className:"flex-1 min-w-0",children:[d.jsx(j,{className:`text-sm truncate ${X?"font-semibold text-gray-900 dark:text-white":"font-normal text-gray-600 dark:text-gray-400"}`,children:G.title}),d.jsx(j,{className:`text-sm mt-1 line-clamp-2 ${X?"text-gray-700 dark:text-gray-300":"text-gray-500 dark:text-gray-500"}`,children:G.message}),d.jsx(j,{className:"text-gray-400 dark:text-gray-500 text-xs mt-1 font-mono",children:G.time})]}),d.jsxs(j,{className:"flex flex-col items-center gap-2 flex-shrink-0",children:[X&&d.jsx(te,{className:"w-2 h-2 bg-blue-500 dark:bg-blue-400 rounded-full mt-1.5"}),m&&d.jsx(R,{onClick:B=>{B.stopPropagation(),m(G)},className:"p-1 text-gray-400 dark:text-gray-500 hover:text-red-600 dark:hover:text-red-400 rounded hover:bg-red-50 dark:hover:bg-red-900/20 cursor-pointer opacity-0 group-hover:opacity-100 transition-opacity","aria-label":"Delete notification",children:d.jsx(fe,{name:nt.Trash,className:"w-4 h-4"})})]})]},G.id)}),r&&d.jsx(j,{ref:E,className:"px-4 py-3 text-center",children:n?d.jsx(te,{className:"text-sm text-gray-400 dark:text-gray-500",children:"..."}):d.jsx(te,{className:"text-sm text-gray-400 dark:text-gray-500"})}),n&&!r&&e.length>0&&d.jsx(j,{className:"px-4 py-2 text-center",children:d.jsx(te,{className:"text-sm text-gray-400 dark:text-gray-500",children:"..."})})]})})]})]})},Wd=Object.freeze(Object.defineProperty({__proto__:null,NotificationCenter:On,default:On},Symbol.toStringTag,{value:"Module"})),Qe=(e,t)=>window.G7Core?.t?.(e,t)??e,vt=e=>{window.G7Core?.dispatch?.({handler:"navigate",params:{path:e}})},xs=({logo:e,siteName:t="그누보드7",user:r,cartCount:n=0,notificationCount:o=0,boards:a=[],maxVisibleBoards:s=5,onMobileMenuOpen:l,availableLocales:f=[],currentLocale:h="ko",shopBase:c="/shop",className:i="",editorAttrs:u,notifications:g=[],notificationHasMore:p=!1,notificationLoading:m=!1,notificationUnreadOnly:x=!1,notificationTitleText:k,notificationEmptyText:M,notificationMarkAllReadText:v,notificationDeleteAllText:T,notificationUnreadOnlyText:I,onNotificationClose:S,onNotificationClick:N,onNotificationLoadMore:w,onNotificationMarkAllRead:y,onNotificationDeleteAll:E,onNotificationDelete:O,onNotificationUnreadOnlyToggle:$})=>{const[P,L]=C.useState(""),[U,_]=C.useState(!1),[b,z]=C.useState(!1),[F,G]=C.useState(!1),[X,B]=C.useState({top:0,left:0}),[Y,H]=C.useState(window.location.pathname),W=C.useRef(null),Q=C.useRef(null),be=C.useRef(null),pe=window.G7Core?.useResponsive,ne=pe?.(),Ae=ne?ne.width<768:typeof window<"u"&&window.innerWidth<768;C.useEffect(()=>{const K=()=>H(window.location.pathname);window.addEventListener("popstate",K);const oe=history.pushState;return history.pushState=function(...ae){oe.apply(this,ae),H(window.location.pathname)},()=>{window.removeEventListener("popstate",K),history.pushState=oe}},[]);const ve=(K,oe=!1)=>oe?Y===K:Y===K||Y.startsWith(K+"/"),We=()=>{const K=c==="/"?"":c;return Y.startsWith(`${K}/products`)||Y.startsWith(`${K}/cart`)||Y.startsWith(`${K}/checkout`)||Y.startsWith(`${K}/category`)},me=K=>{const oe="px-3 py-2 text-sm font-medium whitespace-nowrap cursor-pointer rounded-lg transition-colors";return K?`${oe} bg-gray-900 text-white dark:bg-white dark:text-gray-900`:`${oe} text-gray-700 dark:text-gray-300 hover:text-gray-900 dark:hover:text-white hover:bg-gray-100 dark:hover:bg-gray-800`};C.useEffect(()=>{const K=oe=>{W.current&&!W.current.contains(oe.target)&&_(!1),Q.current&&!Q.current.contains(oe.target)&&z(!1),be.current&&!be.current.contains(oe.target)&&G(!1)};return document.addEventListener("mousedown",K),()=>document.removeEventListener("mousedown",K)},[]);const Te=a.slice(0,s),ge=a.slice(s),Oe=K=>{K.preventDefault(),P.trim()&&vt(`/search?q=${encodeURIComponent(P)}`)},we=async()=>{window.G7Core?.dispatch?.({handler:"logout"})},$e=K=>{window.G7Core?.dispatch?.({handler:"setLocale",target:K}),G(!1)},ze=K=>window.G7Core?.state?.get?.("_global.appConfig.localeNames")?.[K]??(K==="ko"?"한국어":K==="en"?"English":K==="ja"?"日本語":K==="zh"?"中文":K==="es"?"Español":K==="fr"?"Français":K==="de"?"Deutsch":K.toUpperCase());return d.jsxs($d,{...u??{},className:`sticky top-0 z-50 bg-white dark:bg-gray-900 border-b border-gray-200 dark:border-gray-800 ${i}`,children:[d.jsx(j,{className:"max-w-7xl mx-auto px-4 sm:px-6 lg:px-8",children:d.jsxs(j,{className:"flex items-center justify-between h-16",children:[d.jsx(R,{onClick:()=>vt("/"),className:"flex items-center gap-2 flex-shrink-0 cursor-pointer",children:e?d.jsx(jt,{src:e,alt:t,className:"h-8"}):d.jsx(te,{className:"text-xl font-bold text-gray-900 dark:text-white",children:t})}),!Ae&&d.jsx(Eo,{onSubmit:Oe,className:"flex flex-1 max-w-lg mx-8",children:d.jsxs(j,{className:"relative w-full",children:[d.jsx(At,{type:"text",value:P,onChange:K=>L(K.target.value),placeholder:Qe("common.search_placeholder"),className:"w-full px-4 py-2 pl-10 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-white focus:ring-2 focus:ring-blue-500 focus:border-blue-500"}),d.jsx(fe,{name:"search",className:"absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-gray-400 dark:text-gray-500"})]})}),d.jsxs(j,{className:"flex items-center gap-2",children:[d.jsx(An,{autoText:Qe("common.theme.auto"),lightText:Qe("common.theme.light"),darkText:Qe("common.theme.dark")}),r?.uuid&&d.jsx(On,{notifications:g,unreadCount:o,hasMore:p,loading:m,unreadOnly:x,titleText:k??Qe("mypage.notifications.title"),emptyText:M??Qe("mypage.notifications.empty"),markAllReadText:v??Qe("mypage.notifications.mark_all_read"),deleteAllText:T??Qe("mypage.notifications.delete_all"),unreadOnlyText:I??Qe("mypage.notifications.unread_only"),onClose:S,onNotificationClick:N,onLoadMore:w,onMarkAllRead:y,onDeleteAll:E,onDelete:O,onUnreadOnlyToggle:$,dropdownAlign:"right"}),d.jsxs(R,{onClick:()=>vt(`${c}/cart`),className:"relative p-2 text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white cursor-pointer",children:[d.jsx(fe,{name:"shopping-cart",className:"w-5 h-5"}),n>0&&d.jsx(te,{className:"absolute -top-1 -right-1 w-5 h-5 flex items-center justify-center text-xs bg-blue-500 text-white rounded-full",children:n>99?"99+":n})]}),f&&f.length>1&&d.jsxs(j,{ref:be,className:"relative",children:[d.jsxs(R,{onClick:()=>G(!F),className:"flex items-center gap-1.5 px-2.5 py-2 text-sm text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg cursor-pointer transition-colors","aria-haspopup":"listbox","aria-expanded":F,"aria-label":Qe("common.language"),children:[d.jsx(fe,{name:"globe",className:"w-4 h-4"}),d.jsx(te,{className:"font-medium uppercase",children:h}),d.jsx(fe,{name:"chevron-down",className:"w-3 h-3"})]}),F&&d.jsx(j,{role:"listbox",className:"absolute right-0 mt-2 w-44 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg shadow-lg py-1 z-50 overflow-hidden",children:f.map(K=>{const oe=K===h;return d.jsxs(R,{role:"option","aria-selected":oe,onClick:()=>$e(K),className:`w-full px-4 py-2.5 text-left text-sm flex items-center gap-3 cursor-pointer transition-colors ${oe?"bg-blue-50 dark:bg-blue-900/20 text-blue-600 dark:text-blue-400":"text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700"}`,children:[d.jsx(fe,{name:"globe",className:"w-4 h-4"}),d.jsx(te,{className:"flex-1 font-medium",children:ze(K)}),oe&&d.jsx(fe,{name:"check",className:"w-4 h-4"})]},K)})})]}),d.jsx(Nn,{slotId:"header_currency",id:"header_currency_slot_desktop",className:"flex items-center"}),r?.uuid?d.jsxs(j,{ref:W,className:"relative",children:[d.jsxs(R,{onClick:()=>_(!U),className:"flex items-center gap-2 p-2 text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white",children:[d.jsx(Qr,{avatar:r.avatar,name:r.name,size:"sm"}),d.jsx(te,{className:"hidden sm:inline text-sm font-medium",children:r.name}),d.jsx(fe,{name:"chevron-down",className:"w-4 h-4"})]}),U&&d.jsxs(j,{className:"absolute right-0 mt-2 w-56 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg shadow-lg py-1 z-50",children:[d.jsx(j,{className:"px-4 py-3 border-b border-gray-200 dark:border-gray-700",children:d.jsxs(j,{className:"flex items-center gap-3",children:[d.jsx(Qr,{avatar:r.avatar,name:r.name,size:"md"}),d.jsxs(j,{children:[d.jsx(j,{className:"text-sm font-medium text-gray-900 dark:text-white",children:r.name}),d.jsx(j,{className:"text-xs text-gray-500 dark:text-gray-400",children:Qe("common.member")})]})]})}),d.jsxs(j,{className:"py-1",children:[r.is_admin&&d.jsxs(d.Fragment,{children:[d.jsxs(So,{href:"/admin",className:"block w-full text-left px-4 py-2 text-sm text-blue-600 dark:text-blue-400 hover:bg-gray-100 dark:hover:bg-gray-700 cursor-pointer font-medium",children:[d.jsx(fe,{name:"settings",className:"inline w-4 h-4 mr-2"}),Qe("common.admin_menu")]}),d.jsx(To,{className:"my-1 border-gray-200 dark:border-gray-700"})]}),d.jsxs(R,{onClick:()=>{vt("/mypage"),_(!1)},className:"block w-full text-left px-4 py-2 text-sm text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 cursor-pointer",children:[d.jsx(fe,{name:"user",className:"inline w-4 h-4 mr-2"}),Qe("common.mypage")]}),d.jsxs(R,{onClick:()=>{vt("/mypage/orders"),_(!1)},className:"block w-full text-left px-4 py-2 text-sm text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 cursor-pointer",children:[d.jsx(fe,{name:"shopping-bag",className:"inline w-4 h-4 mr-2"}),Qe("mypage.tabs.orders")]}),d.jsxs(R,{onClick:()=>{vt("/mypage/wishlist"),_(!1)},className:"block w-full text-left px-4 py-2 text-sm text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 cursor-pointer",children:[d.jsx(fe,{name:"heart",className:"inline w-4 h-4 mr-2"}),Qe("mypage.tabs.wishlist")]})]}),d.jsx(To,{className:"my-1 border-gray-200 dark:border-gray-700"}),d.jsxs(R,{onClick:we,className:"block w-full text-left px-4 py-2 text-sm text-red-600 dark:text-red-400 hover:bg-gray-100 dark:hover:bg-gray-700 cursor-pointer",children:[d.jsx(fe,{name:"log-out",className:"inline w-4 h-4 mr-2"}),Qe("auth.logout")]})]})]}):d.jsxs(j,{className:"flex items-center gap-1.5",children:[d.jsx(R,{onClick:()=>vt("/login?redirect=/mypage/orders"),className:"px-2.5 py-2 text-sm font-medium text-gray-700 dark:text-gray-300 hover:text-gray-900 dark:hover:text-white cursor-pointer",children:Qe("shop.guest_order_form.nav_link")}),d.jsx(R,{onClick:()=>vt("/login"),className:"px-2.5 py-2 text-sm font-medium text-gray-700 dark:text-gray-300 hover:text-gray-900 dark:hover:text-white cursor-pointer",children:Qe("auth.login")}),d.jsx(R,{onClick:()=>vt("/register"),className:"ml-1 px-3 py-2 text-sm font-medium text-white bg-emerald-500 rounded-lg hover:bg-emerald-600 cursor-pointer",children:Qe("auth.register_link")})]}),Ae&&d.jsx(R,{onClick:l,className:"p-2 text-gray-600 dark:text-gray-400",children:d.jsx(fe,{name:"menu",className:"w-6 h-6"})})]})]})}),!Ae&&d.jsx(Zr,{className:"border-t border-gray-200 dark:border-gray-800",children:d.jsx(j,{className:"max-w-7xl mx-auto px-4 sm:px-6 lg:px-8",children:d.jsxs(j,{className:"flex items-center gap-1 h-12 overflow-x-auto",children:[d.jsx(R,{onClick:()=>vt("/"),className:me(ve("/",!0)),"data-testid":"nav-home",children:Qe("nav.home")}),d.jsxs(R,{onClick:()=>vt("/boards/popular"),className:`flex items-center gap-1 ${me(ve("/boards/popular"))}`,"data-testid":"nav-popular",children:[d.jsx(te,{className:"text-orange-500",children:"🔥"}),Qe("nav.popular")]}),d.jsxs(R,{onClick:()=>vt(`${c==="/"?"":c}/products`),className:`flex items-center gap-1 ${me(We())}`,"data-testid":"nav-shop",children:[d.jsx(te,{children:"🛒"}),Qe("nav.shop")]}),Te.map(K=>d.jsxs(R,{onClick:()=>vt(`/board/${K.slug}`),className:me(ve(`/board/${K.slug}`)),children:[K.icon&&d.jsx(te,{className:"mr-1",children:K.icon}),K.name]},K.id)),ge.length>0&&d.jsxs(j,{ref:Q,className:"relative",children:[d.jsxs(R,{onClick:()=>{if(!b&&Q.current){const K=Q.current.getBoundingClientRect();B({top:K.bottom+4,left:K.left})}z(!b)},className:"flex items-center gap-1 px-3 py-2 text-sm font-medium text-gray-700 dark:text-gray-300 hover:text-gray-900 dark:hover:text-white",children:[Qe("nav.more"),d.jsx(fe,{name:"chevron-down",className:"w-4 h-4"})]}),b&&d.jsx(j,{className:"fixed w-48 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg shadow-lg py-1 z-50",style:{top:`${X.top}px`,left:`${X.left}px`},children:ge.map(K=>{const oe=ve(`/board/${K.slug}`);return d.jsxs(R,{onClick:()=>{vt(`/board/${K.slug}`),z(!1)},className:`block w-full text-left px-4 py-2 text-sm cursor-pointer ${oe?"bg-gray-900 text-white dark:bg-white dark:text-gray-900":"text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700"}`,children:[K.icon&&d.jsx(te,{className:"mr-1",children:K.icon}),K.name]},K.id)})})]})]})})})]})},Vd=Object.freeze(Object.defineProperty({__proto__:null,default:xs},Symbol.toStringTag,{value:"Module"})),Kd=C.forwardRef(({children:e,className:t="",...r},n)=>d.jsx("footer",{ref:n,className:t,...r,children:e})),Nt=(e,t)=>window.G7Core?.t?.(e,t)??e,Yd=768,ks=1024,Xd=e=>{window.G7Core?.dispatch?.({handler:"navigate",params:{path:e}})},Cs=({siteName:e="그누보드7",siteDescription:t,copyrightText:r,socialLinks:n={},linkGroups:o,className:a="",editorAttrs:s})=>{const l=new Date().getFullYear(),h=window.G7Core?.useResponsive,i=h?.()?.width??(typeof window<"u"?window.innerWidth:ks),u=i=ks,p=u?1:g?5:2,m=[{title:Nt("footer.community"),links:[{label:Nt("nav.home"),href:"/"},{label:Nt("nav.popular"),href:"/boards/popular"},{label:Nt("footer.all_boards"),href:"/boards"}]},{title:Nt("footer.info"),links:[{label:Nt("footer.about"),href:"/page/about"},{label:Nt("footer.faq"),href:"/page/faq"},{label:Nt("footer.contact"),href:"/page/contact"}]},{title:Nt("footer.policy"),links:[{label:Nt("footer.terms"),href:"/page/terms"},{label:Nt("footer.privacy"),href:"/page/privacy"},{label:Nt("footer.refund"),href:"/page/refund"}]}],x=o||m,k={github:"github",twitter:"twitter",discord:"discord",facebook:"facebook",instagram:"instagram"};return d.jsx(Kd,{...s??{},className:`bg-gray-50 dark:bg-gray-900 border-t border-gray-200 dark:border-gray-800 ${a}`,children:d.jsxs(j,{className:"max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-12",children:[d.jsxs(j,{className:"grid gap-8",style:{gridTemplateColumns:`repeat(${p}, minmax(0, 1fr))`},children:[d.jsxs(j,{style:g?{gridColumn:"span 2 / span 2"}:void 0,children:[d.jsx(Tn,{className:"text-lg font-bold text-gray-900 dark:text-white",children:e}),t&&d.jsx(Gt,{className:"mt-2 text-sm text-gray-600 dark:text-gray-400",children:t}),d.jsx(j,{className:"mt-4 flex items-center gap-4",children:Object.entries(n).map(([M,v])=>v?d.jsx(So,{href:v,target:"_blank",rel:"noopener noreferrer",className:"text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200","aria-label":M,children:d.jsx(fe,{name:k[M],className:"w-5 h-5"})},M):null)})]}),x.map((M,v)=>d.jsxs(j,{children:[d.jsx(hs,{className:"text-sm font-semibold text-gray-900 dark:text-white uppercase tracking-wider",children:M.title}),d.jsx(_o,{className:"mt-4 space-y-2",children:M.links.map((T,I)=>d.jsx(er,{children:d.jsx(R,{onClick:()=>Xd(T.href),className:"text-sm text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white cursor-pointer",children:T.label})},I))})]},v))]}),d.jsxs(j,{className:"mt-8 pt-8 border-t border-gray-200 dark:border-gray-800 flex justify-between items-center gap-4",style:{flexDirection:u?"column":"row"},children:[d.jsx(Gt,{className:"text-sm text-gray-500 dark:text-gray-400",children:r||`© ${l} ${e}. All rights reserved.`}),d.jsx(Gt,{className:"text-sm text-gray-500 dark:text-gray-400",children:Nt("footer.powered_by")})]})]})})},qd=Object.freeze(Object.defineProperty({__proto__:null,default:Cs},Symbol.toStringTag,{value:"Module"})),Bt=(e,t)=>window.G7Core?.t?.(e,t)??e,Lt=e=>{window.G7Core?.dispatch?.({handler:"navigate",params:{path:e}})},_s=({isOpen:e,onClose:t,logo:r,siteName:n="그누보드7",user:o,boards:a=[],cartCount:s=0})=>{const l=C.useRef(null),h=window.G7Core?.useResponsive,c=h?.(),i=c?c.width<1024:typeof window<"u"&&window.innerWidth<1024;C.useEffect(()=>{const g=p=>{p.key==="Escape"&&t()};return e&&(document.addEventListener("keydown",g),document.body.style.overflow="hidden"),()=>{document.removeEventListener("keydown",g),document.body.style.overflow=""}},[e,t]);const u=g=>{g.target===g.currentTarget&&t()};return!e||!i?null:d.jsxs(j,{className:"fixed inset-0 z-50",onClick:u,children:[d.jsx(j,{className:"fixed inset-0 bg-black/50 transition-opacity"}),d.jsxs(j,{ref:l,className:`fixed inset-y-0 left-0 w-80 max-w-[85vw] bg-white dark:bg-gray-900 shadow-xl transform transition-transform duration-300 ${e?"translate-x-0":"-translate-x-full"}`,children:[d.jsxs(j,{className:"flex items-center justify-between p-4 border-b border-gray-200 dark:border-gray-800",children:[d.jsx(R,{onClick:()=>{t(),Lt("/")},className:"flex items-center gap-2 cursor-pointer",children:r?d.jsx(jt,{src:r,alt:n,className:"h-8"}):d.jsx(te,{className:"text-xl font-bold text-gray-900 dark:text-white",children:n})}),d.jsx(R,{onClick:t,className:"p-2 text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200","aria-label":"메뉴 닫기",children:d.jsx(fe,{name:"x",className:"w-6 h-6"})})]}),o?d.jsx(j,{className:"p-4 border-b border-gray-200 dark:border-gray-800",children:d.jsxs(j,{className:"flex items-center gap-3",children:[o.avatar?d.jsx(jt,{src:o.avatar,alt:o.name,className:"w-12 h-12 rounded-full"}):d.jsx(j,{className:"w-12 h-12 rounded-full bg-blue-500 flex items-center justify-center text-white text-lg font-medium",children:(o.name||"U").charAt(0).toUpperCase()}),d.jsxs(j,{children:[d.jsx(Gt,{className:"font-medium text-gray-900 dark:text-white",children:o.name}),d.jsx(R,{onClick:()=>{t(),Lt("/mypage")},className:"text-sm text-blue-600 dark:text-blue-400 hover:underline cursor-pointer",children:Bt("common.mypage")})]})]})}):d.jsx(j,{className:"p-4 border-b border-gray-200 dark:border-gray-800",children:d.jsxs(j,{className:"flex gap-2",children:[d.jsx(R,{onClick:()=>{t(),Lt("/login")},className:"flex-1 py-2 text-center text-sm font-medium text-white bg-gray-900 dark:bg-white dark:text-gray-900 rounded-lg cursor-pointer",children:Bt("auth.login")}),d.jsx(R,{onClick:()=>{t(),Lt("/register")},className:"flex-1 py-2 text-center text-sm font-medium text-gray-700 dark:text-gray-300 border border-gray-300 dark:border-gray-600 rounded-lg cursor-pointer",children:Bt("auth.register")})]})}),d.jsx(Zr,{className:"p-4 overflow-y-auto max-h-[calc(100vh-200px)]",children:d.jsxs(_o,{className:"space-y-1",children:[d.jsx(er,{children:d.jsxs(R,{onClick:()=>{t(),Lt("/")},className:"flex items-center gap-3 px-3 py-2 w-full text-left text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-800 rounded-lg cursor-pointer",children:[d.jsx(fe,{name:"home",className:"w-5 h-5"}),Bt("nav.home")]})}),d.jsx(er,{children:d.jsxs(R,{onClick:()=>{t(),Lt("/popular")},className:"flex items-center gap-3 px-3 py-2 w-full text-left text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-800 rounded-lg cursor-pointer",children:[d.jsx(te,{className:"text-orange-500",children:"🔥"}),Bt("nav.popular")]})}),d.jsx(er,{children:d.jsxs(R,{onClick:()=>{t(),Lt("/shop")},className:"flex items-center gap-3 px-3 py-2 w-full text-left text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-800 rounded-lg cursor-pointer",children:[d.jsx(te,{children:"🛒"}),Bt("nav.shop")]})}),d.jsx(er,{children:d.jsxs(R,{onClick:()=>{t(),Lt("/cart")},className:"flex items-center gap-3 px-3 py-2 w-full text-left text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-800 rounded-lg cursor-pointer",children:[d.jsx(fe,{name:"shopping-cart",className:"w-5 h-5"}),Bt("nav.cart"),s>0&&d.jsx(te,{className:"ml-auto px-2 py-0.5 text-xs bg-blue-500 text-white rounded-full",children:s})]})}),d.jsx(er,{className:"my-4 border-t border-gray-200 dark:border-gray-800"}),d.jsx(er,{className:"px-3 py-2 text-xs font-semibold text-gray-500 dark:text-gray-400 uppercase tracking-wider",children:Bt("nav.boards")}),a.map(g=>d.jsx(er,{children:d.jsxs(R,{onClick:()=>{t(),Lt(`/board/${g.slug}`)},className:"flex items-center gap-3 px-3 py-2 w-full text-left text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-800 rounded-lg cursor-pointer",children:[g.icon&&d.jsx(te,{children:g.icon}),g.name]})},g.id))]})}),d.jsx(j,{className:"absolute bottom-0 left-0 right-0 p-4 border-t border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-900",children:d.jsxs(j,{className:"flex items-center justify-between text-sm text-gray-500 dark:text-gray-400",children:[d.jsx(R,{onClick:()=>{t(),Lt("/about")},className:"hover:text-gray-700 dark:hover:text-gray-200 cursor-pointer",children:Bt("footer.about")}),d.jsx(R,{onClick:()=>{t(),Lt("/terms")},className:"hover:text-gray-700 dark:hover:text-gray-200 cursor-pointer",children:Bt("footer.terms")}),d.jsx(R,{onClick:()=>{t(),Lt("/privacy")},className:"hover:text-gray-700 dark:hover:text-gray-200 cursor-pointer",children:Bt("footer.privacy")})]})})]})]})},Zd=Object.freeze(Object.defineProperty({__proto__:null,default:_s},Symbol.toStringTag,{value:"Module"}));function Ss(e,t){(t==null||t>e.length)&&(t=e.length);for(var r=0,n=Array(t);r2?n-2:0),a=2;a1?r-1:0),o=1;o"u"?null:tt(BigInt.prototype.toString),Ds=typeof Symbol>"u"?null:tt(Symbol.prototype.toString),wt=tt(Object.prototype.hasOwnProperty),tn=tt(Object.prototype.toString),gt=tt(RegExp.prototype.test),ur=du(TypeError);function tt(e){return function(t){t instanceof RegExp&&(t.lastIndex=0);for(var r=arguments.length,n=new Array(r>1?r-1:0),o=1;o2&&arguments[2]!==void 0?arguments[2]:Rr;if(Ts&&Ts(e,null),!Or(t))return e;let n=t.length;for(;n--;){let o=t[n];if(typeof o=="string"){const a=r(o);a!==o&&(ru(t)||(t[n]=a),o=a)}e[o]=!0}return e}function uu(e){for(let t=0;t/g),yu=lt(/\${[\w\W]*/g),vu=lt(/^data-[\-\w.\u00B7-\uFFFF]+$/),wu=lt(/^aria-[\-\w]+$/),Us=lt(/^(?:(?:(?:f|ht)tps?|mailto|tel|callto|sms|cid|xmpp|matrix):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i),xu=lt(/^(?:\w+script|data):/i),ku=lt(/[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205F\u3000]/g),Cu=lt(/^html$/i),_u=lt(/^[a-z][.\w]*(-[.\w]+)+$/i),zs=lt(/<[/\w!]/g),Hs=lt(/<[/\w]/g),Su=lt(/<\/no(script|embed|frames)/i),Eu=lt(/\/>/i),Mt={element:1,attribute:2,text:3,cdataSection:4,entityReference:5,entityNode:6,processingInstruction:7,comment:8,document:9,documentType:10,documentFragment:11,notation:12},Gs=["style","script","xmp","iframe","noembed","noframes","plaintext","noscript"],Tu=ot(Ge({},Gs)),Au=(function(){const e={};return dr(Gs,t=>{e[t]=lt(new RegExp("])","i"))}),ot(e)})(),Nu=function(){return typeof window>"u"?null:window},Ou=function(t,r){if(typeof t!="object"||typeof t.createPolicy!="function")return null;let n=null;const o="data-tt-policy-suffix";r&&r.hasAttribute(o)&&(n=r.getAttribute(o));const a="dompurify"+(n?"#"+n:"");try{return t.createPolicy(a,{createHTML(s){return s},createScriptURL(s){return s}})}catch{return console.warn("TrustedTypes policy "+a+" could not be created."),null}},js=function(){return{afterSanitizeAttributes:[],afterSanitizeElements:[],afterSanitizeShadowDOM:[],beforeSanitizeAttributes:[],beforeSanitizeElements:[],beforeSanitizeShadowDOM:[],uponSanitizeAttribute:[],uponSanitizeElement:[],uponSanitizeShadowNode:[]}},rr=function(t,r,n,o){return wt(t,r)&&Or(t[r])?Ge(o.base?Ot(o.base):{},t[r],o.transform):n},Fo=function(t,r,n){const o=wt(t,r)?t[r]:void 0;return o&&typeof o=="object"?Ot(o):n()};function Bs(){let e=arguments.length>0&&arguments[0]!==void 0?arguments[0]:Nu();const t=J=>Bs(J);if(t.version="3.4.14",t.removed=[],!e||!e.document||e.document.nodeType!==Mt.document||!e.Element)return t.isSupported=!1,t;let r=e.document;const n=r,o=n.currentScript;e.DocumentFragment;const a=e.HTMLTemplateElement,s=e.Node,l=e.Element,f=e.NodeFilter,h=e.NamedNodeMap;h===void 0&&(e.NamedNodeMap||e.MozNamedAttrMap),e.HTMLFormElement;const c=e.DOMParser,i=e.trustedTypes,u=l.prototype,g=Wt(u,"cloneNode"),p=Wt(u,"remove"),m=Wt(u,"nextSibling"),x=Wt(u,"childNodes"),k=Wt(u,"parentNode"),M=Wt(u,"shadowRoot"),v=Wt(u,"attributes"),T=s&&s.prototype?Wt(s.prototype,"nodeType"):null,I=s&&s.prototype?Wt(s.prototype,"nodeName"):null,S=s&&s.prototype?Wt(s.prototype,"ownerDocument"):null,N=function(D){return T?T(D):D.nodeType},w=function(D){return I?I(D):D.nodeName};if(typeof a=="function"){const J=r.createElement("template");J.content&&J.content.ownerDocument&&(r=J.content.ownerDocument)}let y,E="",O,$=!1,P=0;const L=function(){if(P>0)throw ur('A configured TRUSTED_TYPES_POLICY callback (createHTML or createScriptURL) must not call DOMPurify.sanitize, as that causes infinite recursion. Do not pass a policy whose callbacks wrap DOMPurify as TRUSTED_TYPES_POLICY; see the "DOMPurify and Trusted Types" section of the README.')},U=function(D){L(),P++;try{return y.createHTML(D)}finally{P--}},_=function(D){L(),P++;try{return y.createScriptURL(D)}finally{P--}},b=function(){return $||(O=Ou(i,o),$=!0),O},z=r,F=z.implementation,G=z.createNodeIterator,X=z.createDocumentFragment,B=z.getElementsByTagName,Y=n.importNode;let H=js();t.isSupported=typeof Es=="function"&&typeof k=="function"&&F&&F.createHTMLDocument!==void 0;const W=mu,Q=bu,be=yu,se=vu,pe=wu,ne=xu,Ae=ku,ve=_u;let We=Us,me=null;const Te=Ge({},[...Ps,...Do,...Po,...Lo,...Ls]);let ge=null;const Oe=Ge({},[...$s,...$o,...Fs,...Mn]);let we=Object.seal(Nr(null,{tagNameCheck:{writable:!0,configurable:!1,enumerable:!0,value:null},attributeNameCheck:{writable:!0,configurable:!1,enumerable:!0,value:null},allowCustomizedBuiltInElements:{writable:!0,configurable:!1,enumerable:!0,value:!1}})),$e=null,ze=null;const K=Object.seal(Nr(null,{tagCheck:{writable:!0,configurable:!1,enumerable:!0,value:null},attributeCheck:{writable:!0,configurable:!1,enumerable:!0,value:null}}));let oe=!0,ae=!0,ce=!1,ke=!0,Ee=!1,Be=!0,Me=!1,Ke=!1,Pe=null,q=null,le=!1,de=!1,ue=!1,ie=!1,Fe=!0,Ce=!1;const Ie="user-content-";let Ne=!0,He=!1,je={},_e=null;const xe=Ge({},["annotation-xml","audio","colgroup","desc","foreignobject","head","iframe","math","mi","mn","mo","ms","mtext","noembed","noframes","noscript","plaintext","script","selectedcontent","style","svg","template","thead","title","video","xmp"]);let De=null;const Ye=Ge({},["audio","video","img","source","image","track"]);let rt=null;const qe=Ge({},["alt","class","for","id","label","name","pattern","placeholder","role","summary","title","value","style","xmlns"]),Je="http://www.w3.org/1998/Math/MathML",Zt="http://www.w3.org/2000/svg",Ve="http://www.w3.org/1999/xhtml";let bt=Ve,St=!1,Pt=null;const _r=Ge({},[Je,Zt,Ve],Io),lr=ot(["mi","mo","mn","ms","mtext"]);let Ht=Ge({},lr);const ir=ot(["annotation-xml"]);let Sr=Ge({},ir);const Ym=Ge({},["title","style","font","a","script"]);let _n=null;const Xm=["application/xhtml+xml","text/html"],qm="text/html";let et=null,Xr=null;const Zm=r.createElement("form"),od=function(D){return D instanceof RegExp||D instanceof Function},ss=function(){let D=arguments.length>0&&arguments[0]!==void 0?arguments[0]:{};if(Xr&&Xr===D)return;(!D||typeof D!="object")&&(D={}),D=Ot(D),_n=Xm.indexOf(D.PARSER_MEDIA_TYPE)===-1?qm:D.PARSER_MEDIA_TYPE,et=_n==="application/xhtml+xml"?Io:Rr,me=rr(D,"ALLOWED_TAGS",Te,{transform:et}),ge=rr(D,"ALLOWED_ATTR",Oe,{transform:et}),Pt=rr(D,"ALLOWED_NAMESPACES",_r,{transform:Io}),rt=rr(D,"ADD_URI_SAFE_ATTR",qe,{transform:et,base:qe}),De=rr(D,"ADD_DATA_URI_TAGS",Ye,{transform:et,base:Ye}),_e=rr(D,"FORBID_CONTENTS",xe,{transform:et}),$e=rr(D,"FORBID_TAGS",Ot({}),{transform:et}),ze=rr(D,"FORBID_ATTR",Ot({}),{transform:et}),je=wt(D,"USE_PROFILES")?D.USE_PROFILES&&typeof D.USE_PROFILES=="object"?Ot(D.USE_PROFILES):D.USE_PROFILES:!1,oe=D.ALLOW_ARIA_ATTR!==!1,ae=D.ALLOW_DATA_ATTR!==!1,ce=D.ALLOW_UNKNOWN_PROTOCOLS||!1,ke=D.ALLOW_SELF_CLOSE_IN_ATTR!==!1,Ee=D.SAFE_FOR_TEMPLATES||!1,Be=D.SAFE_FOR_XML!==!1,Me=D.WHOLE_DOCUMENT||!1,de=D.RETURN_DOM||!1,ue=D.RETURN_DOM_FRAGMENT||!1,ie=D.RETURN_TRUSTED_TYPE||!1,le=D.FORCE_BODY||!1,Fe=D.SANITIZE_DOM!==!1,Ce=D.SANITIZE_NAMED_PROPS||!1,Ne=D.KEEP_CONTENT!==!1,He=D.IN_PLACE||!1,We=gu(D.ALLOWED_URI_REGEXP)?D.ALLOWED_URI_REGEXP:Us,bt=typeof D.NAMESPACE=="string"?D.NAMESPACE:Ve,Ht=Fo(D,"MATHML_TEXT_INTEGRATION_POINTS",()=>Ge({},lr)),Sr=Fo(D,"HTML_INTEGRATION_POINTS",()=>Ge({},ir));const V=Fo(D,"CUSTOM_ELEMENT_HANDLING",()=>Nr(null));if(we=Nr(null),wt(V,"tagNameCheck")&&od(V.tagNameCheck)&&(we.tagNameCheck=V.tagNameCheck),wt(V,"attributeNameCheck")&&od(V.attributeNameCheck)&&(we.attributeNameCheck=V.attributeNameCheck),wt(V,"allowCustomizedBuiltInElements")&&typeof V.allowCustomizedBuiltInElements=="boolean"&&(we.allowCustomizedBuiltInElements=V.allowCustomizedBuiltInElements),lt(we),Ee&&(ae=!1),ue&&(de=!0),je&&(me=Ge({},Ls),ge=Nr(null),je.html===!0&&(Ge(me,Ps),Ge(ge,$s)),je.svg===!0&&(Ge(me,Do),Ge(ge,$o),Ge(ge,Mn)),je.svgFilters===!0&&(Ge(me,Po),Ge(ge,$o),Ge(ge,Mn)),je.mathMl===!0&&(Ge(me,Lo),Ge(ge,Fs),Ge(ge,Mn))),K.tagCheck=null,K.attributeCheck=null,wt(D,"ADD_TAGS")&&(typeof D.ADD_TAGS=="function"?K.tagCheck=D.ADD_TAGS:Or(D.ADD_TAGS)&&(me===Te&&(me=Ot(me)),Ge(me,D.ADD_TAGS,et))),wt(D,"ADD_ATTR")&&(typeof D.ADD_ATTR=="function"?K.attributeCheck=D.ADD_ATTR:Or(D.ADD_ATTR)&&(ge===Oe&&(ge=Ot(ge)),Ge(ge,D.ADD_ATTR,et))),wt(D,"ADD_FORBID_CONTENTS")&&Or(D.ADD_FORBID_CONTENTS)&&(_e===xe&&(_e=Ot(_e)),Ge(_e,D.ADD_FORBID_CONTENTS,et)),Ne&&(me["#text"]=!0),Me&&Ge(me,["html","head","body"]),me.table&&(Ge(me,["tbody"]),delete $e.tbody),D.TRUSTED_TYPES_POLICY){if(typeof D.TRUSTED_TYPES_POLICY.createHTML!="function")throw ur('TRUSTED_TYPES_POLICY configuration option must provide a "createHTML" hook.');if(typeof D.TRUSTED_TYPES_POLICY.createScriptURL!="function")throw ur('TRUSTED_TYPES_POLICY configuration option must provide a "createScriptURL" hook.');const Z=y;y=D.TRUSTED_TYPES_POLICY;try{E=U("")}catch(re){throw y=Z,re}}else D.TRUSTED_TYPES_POLICY===null?(y=void 0,E=""):(y===void 0&&(y=b()),y&&typeof E=="string"&&(E=U("")));ot&&ot(D),Xr=D},ad=Ge({},[...Do,...Po,...hu]),sd=Ge({},[...Lo,...pu]),Qm=function(D,V,Z){return V.namespaceURI===Ve?D==="svg":V.namespaceURI===Je?D==="svg"&&(Z==="annotation-xml"||Ht[Z]):!!ad[D]},Jm=function(D,V,Z){return V.namespaceURI===Ve?D==="math":V.namespaceURI===Zt?D==="math"&&Sr[Z]:!!sd[D]},Rm=function(D,V,Z){return V.namespaceURI===Zt&&!Sr[Z]||V.namespaceURI===Je&&!Ht[Z]?!1:!sd[D]&&(Ym[D]||!ad[D])},eb=function(D){let V=k(D);(!V||!V.tagName)&&(V={namespaceURI:bt,tagName:"template"});const Z=Rr(D.tagName),re=Rr(V.tagName);return Pt[D.namespaceURI]?D.namespaceURI===Zt?Qm(Z,V,re):D.namespaceURI===Je?Jm(Z,V,re):D.namespaceURI===Ve?Rm(Z,V,re):!!(_n==="application/xhtml+xml"&&Pt[D.namespaceURI]):!1},cr=function(D){Jr(t.removed,{element:D});try{k(D).removeChild(D)}catch{if(p(D),!k(D))throw ur("a node selected for removal could not be detached from its tree and cannot be safely returned; refusing to sanitize in place")}},ld=function(D,V,Z){try{D.removeAttributeNode(V)}catch{try{D.removeAttribute(Z)}catch{}}},mo=function(D){bo(D);const V=x(D);if(V){const re=[];dr(V,he=>{Jr(re,he)}),dr(re,he=>{try{p(he)}catch{}})}const Z=v(D);if(Z)for(let re=Z.length-1;re>=0;--re){const he=Z[re],Se=he&&he.name;typeof Se=="string"&&ld(D,he,Se)}},Er=function(D,V,Z){if(!Z)try{Z=V.getAttributeNode(D)}catch{Z=null}Jr(t.removed,{attribute:Z||null,from:V});try{Z?V.removeAttributeNode(Z):V.removeAttribute(D)}catch{try{V.removeAttribute(D)}catch{}}if(D==="is")if(de||ue)try{cr(V)}catch{}else try{V.setAttribute(D,"")}catch{}},tb=function(D){const V=v(D);if(V)for(let Z=V.length-1;Z>=0;--Z){const re=V[Z],he=re&&re.name;typeof he!="string"||ge[et(he)]||ld(D,re,he)}},bo=function(D){const V=[D];for(;V.length>0;){const Z=V.pop();N(Z)===Mt.element&&tb(Z);const he=x(Z);if(he)for(let Se=he.length-1;Se>=0;--Se)V.push(he[Se])}},id=function(D,V){return Be?D==="patchsrc"?!0:D==="for"&&V!=="label"&&V!=="output":!1},rb=function(D){if(!Be)return;const V=[D];for(;V.length>0;){const Z=V.pop(),re=N(Z);if(re===Mt.processingInstruction||re===Mt.comment&>(Hs,Z.data)){try{p(Z)}catch{}continue}if(re===Mt.element){const Se=Z,Ze=et(w(Z));try{Se.hasAttribute&&Se.hasAttribute("patchsrc")&&Se.removeAttribute("patchsrc"),Se.hasAttribute&&Se.hasAttribute("for")&&id("for",Ze)&&Se.removeAttribute("for")}catch{}}const he=x(Z);if(he)for(let Se=he.length-1;Se>=0;--Se)V.push(he[Se])}},cd=function(D){let V=null,Z=null;if(le)D=""+D;else{const Se=Os(D,/^[\r\n\t ]+/);Z=Se&&Se[0]}_n==="application/xhtml+xml"&&bt===Ve&&(D=''+D+"");const re=y?U(D):D;if(bt===Ve)try{V=new c().parseFromString(re,_n)}catch{}if(!V||!V.documentElement){V=F.createDocument(bt,"template",null);try{V.documentElement.innerHTML=St?E:re}catch{}}const he=V.body||V.documentElement;return D&&Z&&he.insertBefore(r.createTextNode(Z),he.childNodes[0]||null),bt===Ve?B.call(V,Me?"html":"body")[0]:Me?V.documentElement:he},dd=function(D){const V=S?S(D):D.ownerDocument;return G.call(V||D,D,f.SHOW_ELEMENT|f.SHOW_COMMENT|f.SHOW_TEXT|f.SHOW_PROCESSING_INSTRUCTION|f.SHOW_CDATA_SECTION,null)},yo=function(D){return D=en(D,W," "),D=en(D,Q," "),D=en(D,be," "),D},ls=function(D){var V;D.normalize();const Z=S?S(D):D.ownerDocument,re=G.call(Z||D,D,f.SHOW_TEXT|f.SHOW_COMMENT|f.SHOW_CDATA_SECTION|f.SHOW_PROCESSING_INSTRUCTION,null);let he=re.nextNode();for(;he;)he.data=yo(he.data),he=re.nextNode();const Se=(V=D.querySelectorAll)===null||V===void 0?void 0:V.call(D,"template");Se&&dr(Se,Ze=>{qr(Ze.content)&&ls(Ze.content)})},vo=function(D){const V=I?I(D):null;return typeof V!="string"||et(V)!=="form"?!1:typeof D.nodeName!="string"||typeof D.textContent!="string"||typeof D.removeChild!="function"||D.attributes!==v(D)||typeof D.removeAttribute!="function"||typeof D.setAttribute!="function"||typeof D.namespaceURI!="string"||typeof D.insertBefore!="function"||typeof D.hasChildNodes!="function"||D.nodeType!==T(D)||D.childNodes!==x(D)},qr=function(D){if(!T||typeof D!="object"||D===null)return!1;try{return T(D)===Mt.documentFragment}catch{return!1}},Sn=function(D){if(!T||typeof D!="object"||D===null)return!1;try{return typeof T(D)=="number"}catch{return!1}};function Rt(J,D,V){J.length!==0&&dr(J,Z=>{Z.call(t,D,V,Xr)})}const nb=function(D,V){return!!(Be&&D.hasChildNodes()&&!Sn(D.firstElementChild)&>(zs,D.textContent)&>(zs,D.innerHTML)||Be&&D.namespaceURI===Ve&&Tu[V]&&(Sn(D.firstElementChild)||typeof D.textContent=="string"&>(Au[V],D.textContent))||D.nodeType===Mt.processingInstruction||Be&&D.nodeType===Mt.comment&>(Hs,D.data))},wo=function(D,V){if(D instanceof RegExp)return gt(D,V);if(D instanceof Function){for(var Z=arguments.length,re=new Array(Z>2?Z-2:0),he=2;he=0;--Ze){const Re=D===Z?g(he[Ze],!0):he[Ze];re.insertBefore(Re,m(D))}}}return cr(D),!0},ud=function(D,V,Z,re){return D.length===0?V:V===Z||V===re?Ot(V):V},fd=function(D,V){return D===V||k(D)!==null?!1:(He&&bo(D),!0)},gd=function(D,V){if(Rt(H.beforeSanitizeElements,D,null),fd(D,V))return!0;if(vo(D))return cr(D),!0;const Z=et(w(D));if(me=ud(H.uponSanitizeElement,me,Te,Pe),Rt(H.uponSanitizeElement,D,{tagName:Z,allowedTags:me}),fd(D,V))return!0;if(nb(D,Z))return cr(D),!0;if($e[Z]||!(K.tagCheck instanceof Function&&K.tagCheck(Z))&&!me[Z]){const he=ob(D,Z,V);return he===!1&&Rt(H.afterSanitizeElements,D,null),he}if(N(D)===Mt.element&&!eb(D)||(Z==="noscript"||Z==="noembed"||Z==="noframes")&>(Su,D.innerHTML))return cr(D),!0;if(Ee&&D.nodeType===Mt.text){const he=yo(D.textContent);D.textContent!==he&&(Jr(t.removed,{element:D.cloneNode()}),D.textContent=he)}return Rt(H.afterSanitizeElements,D,null),!1},hd=function(D,V,Z){if(ze[V]||id(V,D)||Fe&&(V==="id"||V==="name")&&(Z in r||Z in Zm))return!1;const re=ge[V]||K.attributeCheck instanceof Function&&K.attributeCheck(V,D);return ae&>(se,V)||oe&>(pe,V)?!0:re?rt[V]||gt(We,en(Z,Ae,""))||(V==="src"||V==="xlink:href"||V==="href")&&D!=="script"&&Ms(Z,"data:")===0&&De[D]||ce&&!gt(ne,en(Z,Ae,""))?!0:!Z:pd(D)&&wo(we.tagNameCheck,D)&&wo(we.attributeNameCheck,V,D)||V==="is"&&we.allowCustomizedBuiltInElements&&wo(we.tagNameCheck,Z)},ab=Ge({},["annotation-xml","color-profile","font-face","font-face-format","font-face-name","font-face-src","font-face-uri","missing-glyph"]),pd=function(D){return!ab[Rr(D)]&>(ve,D)},sb=function(D,V,Z,re){if(y&&typeof i=="object"&&typeof i.getAttributeType=="function"&&!Z)switch(i.getAttributeType(D,V)){case"TrustedHTML":return U(re);case"TrustedScriptURL":return _(re)}return re},lb=function(D,V,Z,re){try{Z?D.setAttributeNS(Z,V,re):D.setAttribute(V,re),vo(D)?cr(D):Ns(t.removed)}catch{Er(V,D)}},md=function(D){Rt(H.beforeSanitizeAttributes,D,null);const V=D.attributes;if(!V||vo(D))return;ge=ud(H.uponSanitizeAttribute,ge,Oe,q);const Z={attrName:"",attrValue:"",keepAttr:!0,allowedAttributes:ge,forceKeepAttr:void 0};let re=V.length;const he=et(D.nodeName);for(;re--;){const Se=V[re],Ze=Se.name,Re=Se.namespaceURI,Et=Se.value,Tt=et(Ze),cs=Et;let yt=Ze==="value"?cs:lu(cs);if(Z.attrName=Tt,Z.attrValue=yt,Z.keepAttr=!0,Z.forceKeepAttr=void 0,Rt(H.uponSanitizeAttribute,D,Z),yt=Z.attrValue,Ce&&(Tt==="id"||Tt==="name")&&Ms(yt,Ie)!==0&&(Er(Ze,D,Se),yt=Ie+yt),Be&>(/((--!?|])>)|<\/(style|script|title|xmp|textarea|noscript|iframe|noembed|noframes)/i,yt)){Er(Ze,D,Se);continue}if(Tt==="attributename"&&Os(yt,"href")){Er(Ze,D,Se);continue}if(!Z.forceKeepAttr){if(!Z.keepAttr){Er(Ze,D,Se);continue}if(!ke&>(Eu,yt)){Er(Ze,D,Se);continue}if(Ee&&(yt=yo(yt)),!hd(he,Tt,yt)){Er(Ze,D,Se);continue}yt=sb(he,Tt,Re,yt),yt!==cs&&lb(D,Ze,Re,yt)}}Rt(H.afterSanitizeAttributes,D,null)},xo=function(D){let V=null;const Z=dd(D);for(Rt(H.beforeSanitizeShadowDOM,D,null);V=Z.nextNode();)if(Rt(H.uponSanitizeShadowNode,V,null),gd(V,D),md(V),qr(V.content)&&xo(V.content),N(V)===Mt.element){const re=M(V);qr(re)&&(is(re),xo(re))}Rt(H.afterSanitizeShadowDOM,D,null)},is=function(D){const V=[{node:D,shadow:null}];for(;V.length>0;){const Z=V.pop();if(Z.shadow){xo(Z.shadow);continue}const re=Z.node,Se=N(re)===Mt.element,Ze=x(re);if(Ze)for(let Re=Ze.length-1;Re>=0;--Re)V.push({node:Ze[Re],shadow:null});if(Se){const Re=I?I(re):null;if(typeof Re=="string"&&et(Re)==="template"){const Et=re.content;qr(Et)&&V.push({node:Et,shadow:null})}}if(Se){const Re=M(re);qr(Re)&&V.push({node:null,shadow:Re},{node:Re,shadow:null})}}};return t.sanitize=function(J){let D=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{},V=null,Z=null,re=null,he=null;if(St=!J,St&&(J=""),typeof J!="string"&&!Sn(J)&&(J=fu(J),typeof J!="string"))throw ur("dirty is not a string, aborting");if(!t.isSupported)return J;Ke?(me=Pe,ge=q):ss(D),(H.uponSanitizeElement.length>0||H.uponSanitizeAttribute.length>0)&&(me=Ot(me)),H.uponSanitizeAttribute.length>0&&(ge=Ot(ge)),t.removed=[];const Se=He&&typeof J!="string"&&Sn(J);if(Se){rb(J);const Et=w(J);if(typeof Et=="string"){const Tt=et(Et);if(!me[Tt]||$e[Tt])throw mo(J),ur("root node is forbidden and cannot be sanitized in-place")}if(vo(J))throw mo(J),ur("root node is clobbered and cannot be sanitized in-place");try{is(J)}catch(Tt){throw mo(J),Tt}}else if(Sn(J))V=cd(""),Z=V.ownerDocument.importNode(J,!0),Z.nodeType===Mt.element&&Z.nodeName==="BODY"||Z.nodeName==="HTML"?V=Z:V.appendChild(Z),is(Z);else{if(!de&&!Ee&&!Me&&J.indexOf("<")===-1)return y&&ie?U(J):J;if(V=cd(J),!V)return de?null:ie?E:""}V&&le&&cr(V.firstChild);const Ze=Se?J:V;try{const Et=dd(Ze);for(;re=Et.nextNode();)gd(re,Ze),md(re),qr(re.content)&&xo(re.content)}catch(Et){throw Se&&(mo(J),dr(t.removed,Tt=>{Tt.element&&bo(Tt.element)})),Et}if(Se)return dr(t.removed,Et=>{Et.element&&bo(Et.element)}),Ee&&ls(J),J;if(de){if(Ee&&ls(V),ue)for(he=X.call(V.ownerDocument);V.firstChild;)he.appendChild(V.firstChild);else he=V;return(ge.shadowroot||ge.shadowrootmode)&&(he=Y.call(n,he,!0)),he}let Re=Me?V.outerHTML:V.innerHTML;return Me&&me["!doctype"]&&V.ownerDocument&&V.ownerDocument.doctype&&V.ownerDocument.doctype.name&>(Cu,V.ownerDocument.doctype.name)&&(Re=" +`+Re),Ee&&(Re=yo(Re)),y&&ie?U(Re):Re},t.setConfig=function(){let J=arguments.length>0&&arguments[0]!==void 0?arguments[0]:{};ss(J),Ke=!0,Pe=me,q=ge},t.clearConfig=function(){Xr=null,Ke=!1,Pe=null,q=null,y=O,E=""},t.isValidAttribute=function(J,D,V){Xr||ss({});const Z=et(J),re=et(D);return hd(Z,re,V)},t.addHook=function(J,D){typeof D=="function"&&wt(H,J)&&Jr(H[J],D)},t.removeHook=function(J,D){if(wt(H,J)){if(D!==void 0){const V=au(H[J],D);return V===-1?void 0:su(H[J],V,1)[0]}return Ns(H[J])}},t.removeHooks=function(J){wt(H,J)&&(H[J]=[])},t.removeAllHooks=function(){H=js()},t}var Uo=Bs();const Mu=()=>{const e=()=>{const n=window.G7Core?.state?.get?.();return n&&(n.preferredCurrency||n.defaultCurrency)||""},[t,r]=C.useState(e);return C.useEffect(()=>{const n=window.G7Core?.state?.subscribe;if(r(e()),typeof n!="function")return;const o=n(a=>{r(a&&(a.preferredCurrency||a.defaultCurrency)||"")});return typeof o=="function"?o:void 0},[]),t},Iu=(e,t=!1)=>{window.G7Core?.dispatch?.({handler:"navigate",params:t?{path:e,mergeQuery:!0,query:{}}:{path:e}})},Du=e=>{const t=Math.round(e*2)/2;return[1,2,3,4,5].map(r=>t>=r?"full":t>=r-.5?"half":"empty")},Ws=({product:e,onClick:t,shopBase:r="/shop",preserveListQuery:n=!1,className:o="",id:a,editorAttrs:s})=>{const l=Mu(),f=v=>{const T=`multi_currency_${v}`,I=e[T];if(I&&I[l])return I[l].formatted;const S=`${v}_formatted`;return e[S]},h=e.name_localized??(typeof e.name=="string"?e.name:"")??"",c=f("selling_price"),i=f("list_price"),u=e.discount_rate&&e.discount_rate>0,g=e.sales_status&&e.sales_status!=="on_sale",p=e.labels??[],m=v=>Uo.sanitize(v,{ALLOWED_TAGS:["mark"],ALLOWED_ATTR:[]}),x=e.name_highlighted,k=e.description_highlighted??e.short_description,M=()=>{if(t)t(e.id);else{const v=r==="/"?"":r,T=e.product_code??e.id;Iu(`${v}/products/${T}`,n)}};return d.jsxs(R,{onClick:M,className:`block w-full text-left group bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700 overflow-hidden hover:shadow-lg transition-shadow cursor-pointer ${o}`,id:a,...s,children:[d.jsxs(j,{className:"relative aspect-square overflow-hidden bg-gray-100 dark:bg-gray-700",children:[d.jsx(jt,{src:e.thumbnail_url,alt:h,className:"w-full h-full object-cover group-hover:scale-105 transition-transform duration-200",loading:"lazy"}),g&&d.jsx(j,{className:"absolute inset-0 bg-black/50 flex items-center justify-center",children:d.jsx(te,{className:`text-lg font-bold ${e.sales_status==="sold_out"?"text-red-300":e.sales_status==="suspended"?"text-amber-300":"text-blue-300"}`,style:{textShadow:"0 1px 4px rgba(0,0,0,0.6)"},children:e.sales_status_label??e.sales_status})}),u&&e.sales_status!=="sold_out"&&e.sales_status!=="suspended"&&d.jsxs(te,{className:"absolute top-2 left-2 px-2 py-1 bg-red-500 text-white text-xs font-bold rounded",children:[e.discount_rate,"%"]}),p.length>0&&d.jsx(j,{className:"absolute bottom-2 right-2 flex gap-1 flex-wrap justify-end",children:p.map((v,T)=>d.jsx(te,{className:"text-xs text-white font-medium px-1.5 py-0.5 rounded",style:{backgroundColor:v.color||"#6b7280"},children:v.name},T))})]}),d.jsxs(j,{className:"p-4",children:[d.jsxs(j,{className:"flex items-center gap-1.5 flex-wrap",children:[e.primary_category&&d.jsx(te,{className:"inline-block text-xs font-medium text-blue-600 dark:text-blue-400 bg-blue-50 dark:bg-blue-900/30 px-2 py-0.5 rounded",children:e.primary_category}),e.brand_name&&d.jsx(te,{className:"text-xs text-gray-500 dark:text-gray-400",children:e.brand_name})]}),x?d.jsx(j,{className:"mt-1 font-medium text-gray-900 dark:text-white line-clamp-1 [&_mark]:bg-yellow-200 [&_mark]:dark:bg-yellow-500/40 [&_mark]:px-0.5 [&_mark]:rounded",dangerouslySetInnerHTML:{__html:m(x)}}):d.jsx(Tn,{className:"mt-1 font-medium text-gray-900 dark:text-white line-clamp-1",children:h}),k&&d.jsx(j,{className:"mt-1 text-sm text-gray-600 dark:text-gray-400 line-clamp-2 [&_mark]:bg-yellow-200 [&_mark]:dark:bg-yellow-500/40 [&_mark]:px-0.5 [&_mark]:rounded",dangerouslySetInnerHTML:{__html:m(k)}}),d.jsxs(j,{className:"mt-1.5 flex items-center gap-1",children:[Du(e.rating_avg??0).map((v,T)=>d.jsxs(te,{className:`text-xs ${v==="empty"?"text-gray-300 dark:text-gray-600":"text-yellow-400"}`,children:[v==="full"&&d.jsx("i",{className:"fa-solid fa-star"}),v==="half"&&d.jsx("i",{className:"fa-solid fa-star-half-stroke"}),v==="empty"&&d.jsx("i",{className:"fa-regular fa-star"})]},T)),d.jsx(te,{className:"text-xs text-gray-600 dark:text-gray-400 ml-0.5",children:(e.rating_avg??0).toFixed(1)}),d.jsxs(te,{className:"text-xs text-gray-400 dark:text-gray-500",children:["(",e.review_count??0,")"]})]}),d.jsxs(j,{className:"mt-2 flex items-baseline gap-2",children:[d.jsx(te,{className:"font-bold text-gray-900 dark:text-white",children:c}),i&&u&&d.jsx(te,{className:"text-sm text-gray-500 dark:text-gray-400 line-through",children:i})]})]})]})},Pu=Object.freeze(Object.defineProperty({__proto__:null,default:Ws},Symbol.toStringTag,{value:"Module"})),Lu={ALLOWED_TAGS:["svg","g","defs","title","desc","rect","circle","ellipse","line","polyline","polygon","path","text","tspan","linearGradient","radialGradient","stop","clipPath"],ALLOWED_ATTR:["xmlns","width","height","viewBox","role","aria-label","aria-hidden","fill","fill-opacity","fill-rule","stroke","stroke-width","stroke-linecap","stroke-linejoin","opacity","transform","x","y","x1","y1","x2","y2","cx","cy","r","rx","ry","d","points","width","height","text-anchor","dominant-baseline","font-family","font-size","font-weight","offset","stop-color","stop-opacity","gradientUnits","id","clip-path"],FORBID_TAGS:["script","foreignObject","image","use","a","style"],FORBID_ATTR:["onload","onerror","onclick","onmouseover","onfocus","onblur","href","xlink:href"]},zo=({brandMark:e,size:t=32,className:r="",id:n,editorAttrs:o})=>{const a=e?.svg,s=e?.text,l=C.useMemo(()=>!a||a.trim()===""?"":Uo.sanitize(a,Lu),[a]),f={width:`${t}px`,height:`${t}px`,flex:`0 0 ${t}px`};if(l)return d.jsx(j,{id:n,"aria-hidden":"true",className:`inline-flex items-center justify-center ${r}`.trim(),style:f,dangerouslySetInnerHTML:{__html:l},...o});if(s&&s.trim()!==""){const h=e?.class??"bg-gray-500 text-white";return d.jsx(te,{id:n,"aria-hidden":"true",className:`inline-flex items-center justify-center rounded-lg text-xs font-bold ${h} ${r}`.trim(),style:f,...o,children:s})}return null},$u=Object.freeze(Object.defineProperty({__proto__:null,BrandMark:zo,default:zo},Symbol.toStringTag,{value:"Module"})),Vs="carousel",rn="controller",Fu="navigation",Uu="no-scroll",Ho="portal",zu="root",Ks="toolbar",Hu="counter",nn="fullscreen",Ys="slideshow",on="thumbnails",Xs="zoom",Mr="loading",Go="playing",an="error",sn="complete",Gu="placeholder",ln=e=>`active-slide-${e}`,ju=ln(Mr),Bu=ln(Go),Wu=ln(an),Vu=ln(sn),qs="fullsize",Ir="flex_center",Ku="no_scroll",Zs="no_scroll_padding",jo="slide",Qs="slide_wrapper",Yu="slide_wrapper_interactive",nr="prev",or="next",Bo="swipe",Dr="close",Js="onPointerDown",Rs="onPointerMove",el="onPointerUp",tl="onPointerLeave",rl="onPointerCancel",Wo="onKeyDown",Xu="onKeyUp",Vo="onWheel",qu="Escape",Zu="ArrowLeft",Qu="ArrowRight",Ju="button",In="icon",nl="contain",ol="cover",al="Unknown action type",sl="yarl__";function ht(...e){return[...e].filter(Boolean).join(" ")}function ye(e){return`${sl}${e}`}function Xe(e){return`--${sl}${e}`}function fr(e,t){return`${e}${t?`_${t}`:""}`}function gr(e){return t=>fr(e,t)}function hr(e,t){var r;return(r=e?.[t])!==null&&r!==void 0?r:t}function ll(e,t,r){return hr(e,"{index} of {total}").replace(/\{index}/g,`${qo(r,t.length)+1}`).replace(/\{total}/g,`${t.length}`)}function Pr(...e){return()=>{e.forEach(t=>{t()})}}function $t(e,t,r){return()=>{const n=A.useContext(r);if(!n)throw new Error(`${e} must be used within a ${t}.Provider`);return n}}function Ko(){return typeof window<"u"}function Yo(e,t=0){const r=10**t;return Math.round((e+Number.EPSILON)*r)/r}function pr(e){return e.type===void 0||e.type==="image"}function Xo(e,t){return e.imageFit===ol||e.imageFit!==nl&&t===ol}function Dn(e){return typeof e=="string"?Number.parseInt(e,10):e}function Pn(e){if(typeof e=="number")return{pixel:e};if(typeof e=="string"){const t=Dn(e);return e.endsWith("%")?{percent:t}:{pixel:t}}return{pixel:0}}function Ru(e,t){const r=Pn(t),n=r.percent!==void 0?e.width/100*r.percent:r.pixel;return{width:Math.max(e.width-2*n,0),height:Math.max(e.height-2*n,0)}}function ef(){return(Ko()?window?.devicePixelRatio:void 0)||1}function qo(e,t){return t>0?(e%t+t)%t:0}function Zo(e){return e.length>0}function Qo(e,t){return e[qo(t,e.length)]}function Jo(e,t){return Zo(e)?Qo(e,t):void 0}function il(e){return pr(e)?e.src:void 0}function Ln(e,t,r){if(!r)return e;const{buttons:n,...o}=e,a=n.findIndex(l=>l===t),s=A.isValidElement(r)?A.cloneElement(r,{key:t},null):r;if(a>=0){const l=[...n];return l.splice(a,1,s),{buttons:l,...o}}return{buttons:[s,...n],...o}}function cl(e,t,r=0){return Math.min(e.preload,Math.max(e.finite?t.length-1:Math.floor(t.length/2),r))}const tf=Number(A.version.split(".")[0])>=19;function rf(e){return{inert:tf?e:e?"":void 0}}function nf(e){e.scrollTop}const Ro={open:!1,close:()=>{},index:0,slides:[],render:{},plugins:[],toolbar:{buttons:[Dr]},labels:{},animation:{fade:250,swipe:500,easing:{fade:"ease",swipe:"ease-out",navigation:"ease-in-out"}},carousel:{finite:!1,preload:2,padding:"16px",spacing:"30%",imageFit:nl,imageProps:{}},controller:{ref:null,focus:!0,aria:!1,touchAction:"none",closeOnPullUp:!1,closeOnPullDown:!1,closeOnBackdropClick:!1,preventDefaultWheelX:!0,preventDefaultWheelY:!1,disableSwipeNavigation:!1},portal:{},noScroll:{disabled:!1},on:{},styles:{},className:""};function Ft(e,t){return{name:e,component:t}}function it(e,t){return{module:e,children:t}}function dl(e,t,r){return e.module.name===t?r(e):e.children?[it(e.module,e.children.flatMap(n=>{var o;return(o=dl(n,t,r))!==null&&o!==void 0?o:[]}))]:[e]}function Lr(e,t,r){return e.flatMap(n=>{var o;return(o=dl(n,t,r))!==null&&o!==void 0?o:[]})}function of(e,t=[],r=[]){let n=e;const o=g=>{const p=[...n];for(;p.length>0;){const m=p.pop();if(m?.module.name===g)return!0;m?.children&&p.push(...m.children)}return!1},a=(g,p)=>{if(g===""){n=[it(p,n)];return}n=Lr(n,g,m=>[it(p,[m])])},s=(g,p)=>{n=Lr(n,g,m=>[it(m.module,[it(p,m.children)])])},l=(g,p,m)=>{n=Lr(n,g,x=>{var k;return[it(x.module,[...m?[it(p)]:[],...(k=x.children)!==null&&k!==void 0?k:[],...m?[]:[it(p)]])]})},f=(g,p,m)=>{n=Lr(n,g,x=>[...m?[it(p)]:[],x,...m?[]:[it(p)]])},h=g=>{s(rn,g)},c=(g,p)=>{n=Lr(n,g,m=>[it(p,m.children)])},i=g=>{n=Lr(n,g,p=>p.children)},u=g=>{r.push(g)};return t.forEach(g=>{g({contains:o,addParent:a,append:s,addChild:l,addSibling:f,addModule:h,replace:c,remove:i,augment:u})}),{config:n,augmentation:g=>r.reduce((p,m)=>m(p),g)}}const ul=A.createContext(null),ea=$t("useA11yContext","A11yContext",ul);function af({children:e}){const[t,r]=A.useState(!1),[n,o]=A.useState(!1),a=A.useMemo(()=>({focusWithin:t,trackFocusWithin:(l,f)=>{const h=c=>i=>{var u;i.currentTarget.contains(i.relatedTarget)||r(c),(u=c?l:f)===null||u===void 0||u(i)};return{onFocus:h(!0),onBlur:h(!1)}},autoPlaying:n,setAutoPlaying:o}),[t,n]);return A.createElement(ul.Provider,{value:a},e)}const fl=A.createContext(null),$r=$t("useDocument","DocumentContext",fl);function sf({nodeRef:e,children:t}){const r=A.useMemo(()=>{const n=a=>{var s;return((s=a||e.current)===null||s===void 0?void 0:s.ownerDocument)||document};return{getOwnerDocument:n,getOwnerWindow:a=>{var s;return((s=n(a))===null||s===void 0?void 0:s.defaultView)||window}}},[e]);return A.createElement(fl.Provider,{value:r},t)}const gl=A.createContext(null),Fr=$t("useEvents","EventsContext",gl);function lf({children:e}){const[t]=A.useState({});A.useEffect(()=>()=>{Object.keys(t).forEach(n=>delete t[n])},[t]);const r=A.useMemo(()=>{const n=(s,l)=>{var f;(f=t[s])===null||f===void 0||f.splice(0,t[s].length,...t[s].filter(h=>h!==l))};return{publish:(...[s,l])=>{var f;(f=t[s])===null||f===void 0||f.forEach(h=>h(l))},subscribe:(s,l)=>(t[s]||(t[s]=[]),t[s].push(l),()=>n(s,l)),unsubscribe:n}},[t]);return A.createElement(gl.Provider,{value:r},e)}const hl=A.createContext(null),ct=$t("useLightboxProps","LightboxPropsContext",hl);function pl({children:e,...t}){return A.createElement(hl.Provider,{value:t},e)}const ml=A.createContext(null),Ut=$t("useLightboxState","LightboxStateContext",ml),bl=A.createContext(null),cf=$t("useLightboxDispatch","LightboxDispatchContext",bl);function df(e,t){switch(t.type){case"swipe":{const{slides:r}=e,n=t?.increment||0,o=e.globalIndex+n,a=qo(o,r.length),s=Jo(r,a),l=n||t.duration!==void 0?{increment:n,duration:t.duration,easing:t.easing}:void 0;return{slides:r,currentIndex:a,globalIndex:o,currentSlide:s,animation:l}}case"update":return t.slides!==e.slides||t.index!==e.currentIndex?{slides:t.slides,currentIndex:t.index,globalIndex:t.index,currentSlide:Jo(t.slides,t.index)}:e;default:throw new Error(al)}}function uf({slides:e,index:t,children:r}){const[n,o]=A.useReducer(df,{slides:e,currentIndex:t,globalIndex:t,currentSlide:Jo(e,t)});A.useEffect(()=>{o({type:"update",slides:e,index:t})},[e,t]);const a=A.useMemo(()=>({...n,state:n,dispatch:o}),[n,o]);return A.createElement(bl.Provider,{value:o},A.createElement(ml.Provider,{value:a},r))}const yl=A.createContext(null),cn=$t("useTimeouts","TimeoutsContext",yl);function ff({children:e}){const[t]=A.useState([]);A.useEffect(()=>()=>{t.forEach(n=>window.clearTimeout(n)),t.splice(0,t.length)},[t]);const r=A.useMemo(()=>{const n=s=>{t.splice(0,t.length,...t.filter(l=>l!==s))};return{setTimeout:(s,l)=>{const f=window.setTimeout(()=>{n(f),s()},l);return t.push(f),f},clearTimeout:s=>{s!==void 0&&(n(s),window.clearTimeout(s))}}},[t]);return A.createElement(yl.Provider,{value:r},e)}const Ur=A.forwardRef(function({label:t,className:r,icon:n,renderIcon:o,onClick:a,style:s,...l},f){const{styles:h,labels:c}=ct(),i=hr(c,t);return A.createElement("button",{ref:f,type:"button",title:i,"aria-label":i,className:ht(ye(Ju),r),onClick:a,style:{...s,...h.button},...l},o?o():A.createElement(n,{className:ye(In),style:h.icon}))});function vl(e,t){const r=n=>A.createElement("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",width:"24",height:"24","aria-hidden":"true",focusable:"false",...n},t);return r.displayName=e,r}function xt(e,t){return vl(e,A.createElement("g",{fill:"currentColor"},A.createElement("path",{d:"M0 0h24v24H0z",fill:"none"}),t))}function gf(e,t){return vl(e,A.createElement(A.Fragment,null,A.createElement("defs",null,A.createElement("mask",{id:"strike"},A.createElement("path",{d:"M0 0h24v24H0z",fill:"white"}),A.createElement("path",{d:"M0 0L24 24",stroke:"black",strokeWidth:4}))),A.createElement("path",{d:"M0.70707 2.121320L21.878680 23.292883",stroke:"currentColor",strokeWidth:2}),A.createElement("g",{fill:"currentColor",mask:"url(#strike)"},A.createElement("path",{d:"M0 0h24v24H0z",fill:"none"}),t)))}const hf=xt("Close",A.createElement("path",{d:"M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"})),pf=xt("Previous",A.createElement("path",{d:"M15.41 7.41L14 6l-6 6 6 6 1.41-1.41L10.83 12z"})),mf=xt("Next",A.createElement("path",{d:"M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z"})),bf=xt("Loading",A.createElement(A.Fragment,null,Array.from({length:8}).map((e,t,r)=>A.createElement("line",{key:t,x1:"12",y1:"6.5",x2:"12",y2:"1.8",strokeLinecap:"round",strokeWidth:"2.6",stroke:"currentColor",strokeOpacity:1/r.length*(t+1),transform:`rotate(${360/r.length*t}, 12, 12)`})))),yf=xt("Error",A.createElement("path",{d:"M21.9,21.9l-8.49-8.49l0,0L3.59,3.59l0,0L2.1,2.1L0.69,3.51L3,5.83V19c0,1.1,0.9,2,2,2h13.17l2.31,2.31L21.9,21.9z M5,18 l3.5-4.5l2.5,3.01L12.17,15l3,3H5z M21,18.17L5.83,3H19c1.1,0,2,0.9,2,2V18.17z"})),Vt=Ko()?A.useLayoutEffect:A.useEffect;function ta(){const[e,t]=A.useState(!1);return A.useEffect(()=>{var r,n;const o=(r=window.matchMedia)===null||r===void 0?void 0:r.call(window,"(prefers-reduced-motion: reduce)");t(o?.matches);const a=s=>t(s.matches);return(n=o?.addEventListener)===null||n===void 0||n.call(o,"change",a),()=>{var s;return(s=o?.removeEventListener)===null||s===void 0?void 0:s.call(o,"change",a)}},[]),e}function vf(e){let t=0,r=0,n=0;const a=window.getComputedStyle(e).transform.match(/matrix.*\((.+)\)/);if(a){const s=a[1].split(",").map(Dn);s.length===6?(t=s[4],r=s[5]):s.length===16&&(t=s[12],r=s[13],n=s[14])}return{x:t,y:r,z:n}}function ra(e,t){const r=A.useRef(void 0),n=A.useRef(void 0),o=ta();return Vt(()=>{var a,s,l;if(e.current&&r.current!==void 0&&!o){const{keyframes:f,duration:h,easing:c,onfinish:i}=t(r.current,e.current.getBoundingClientRect(),vf(e.current))||{};if(f&&h){(a=n.current)===null||a===void 0||a.cancel(),n.current=void 0;try{n.current=(l=(s=e.current).animate)===null||l===void 0?void 0:l.call(s,f,{duration:h,easing:c})}catch(u){console.error(u)}n.current&&(n.current.onfinish=()=>{n.current=void 0,i?.()})}}r.current=void 0}),{prepareAnimation:a=>{r.current=a},isAnimationPlaying:()=>{var a;return((a=n.current)===null||a===void 0?void 0:a.playState)==="running"}}}function wl(){const e=A.useRef(null),t=A.useRef(void 0),[r,n]=A.useState();return{setContainerRef:A.useCallback(a=>{e.current=a,t.current&&(t.current.disconnect(),t.current=void 0);const s=()=>{if(a){const l=window.getComputedStyle(a),f=h=>parseFloat(h)||0;n({width:Math.round(a.clientWidth-f(l.paddingLeft)-f(l.paddingRight)),height:Math.round(a.clientHeight-f(l.paddingTop)-f(l.paddingBottom))})}else n(void 0)};s(),a&&typeof ResizeObserver<"u"&&(t.current=new ResizeObserver(s),t.current.observe(a))},[]),containerRef:e,containerRect:r}}function $n(){const e=A.useRef(void 0),{setTimeout:t,clearTimeout:r}=cn();return A.useCallback((n,o)=>{r(e.current),e.current=t(n,o>0?o:0)},[t,r])}function Le(e){const t=A.useRef(e);return Vt(()=>{t.current=e}),A.useCallback((...r)=>{var n;return(n=t.current)===null||n===void 0?void 0:n.call(t,...r)},[])}function xl(e,t){typeof e=="function"?e(t):e&&(e.current=t)}function na(e,t){return A.useMemo(()=>e==null&&t==null?null:r=>{xl(e,r),xl(t,r)},[e,t])}function kl(e,t=!1){const r=A.useRef(!1);Vt(()=>{t&&r.current&&(r.current=!1,e())},[t,e]);const n=A.useCallback(()=>{r.current=!0},[]),o=A.useCallback(()=>{r.current=!1},[]);return{onFocus:n,onBlur:o}}function Fn(){const[e,t]=A.useState(!1);return Vt(()=>{t(window.getComputedStyle(window.document.documentElement).direction==="rtl")},[]),e}function Cl(){const[e]=A.useState({}),t=A.useCallback((o,a)=>{var s;(s=e[o])===null||s===void 0||s.forEach(l=>{a.isPropagationStopped()||l(a)})},[e]),r=A.useMemo(()=>({onPointerDown:o=>t(Js,o),onPointerMove:o=>t(Rs,o),onPointerUp:o=>t(el,o),onPointerLeave:o=>t(tl,o),onPointerCancel:o=>t(rl,o),onKeyDown:o=>t(Wo,o),onKeyUp:o=>t(Xu,o),onWheel:o=>t(Vo,o)}),[t]),n=A.useCallback((o,a)=>(e[o]||(e[o]=[]),e[o].unshift(a),()=>{const s=e[o];s&&s.splice(0,s.length,...s.filter(l=>l!==a))}),[e]);return{registerSensors:r,subscribeSensors:n}}function _l(e,t){const r=A.useRef(0),n=$n(),o=Le((...a)=>{r.current=Date.now(),e(a)});return A.useCallback((...a)=>{n(()=>{o(a)},t-(Date.now()-r.current))},[t,o,n])}const oa=gr("slide"),aa=gr("slide_image");function mr({slide:e,offset:t,render:r,rect:n,imageFit:o,imageProps:a,onClick:s,onLoad:l,onError:f,style:h}){var c,i,u,g,p,m,x,k;const[M,v]=A.useState(Mr),{publish:T}=Fr(),{setTimeout:I}=cn(),S=A.useRef(null);A.useEffect(()=>{t===0&&T(ln(M))},[t,M,T]);const N=Le(B=>{("decode"in B?B.decode():Promise.resolve()).catch(()=>{}).then(()=>{B.parentNode&&(v(sn),I(()=>{l?.(B)},0))})}),w=A.useCallback(B=>{S.current=B,B?.complete&&N(B)},[N]),y=A.useCallback(B=>{N(B.currentTarget)},[N]),E=Le(()=>{v(an),f?.()}),O=Xo(e,o),$=(B,Y)=>Number.isFinite(B)?B:Y,P=$(Math.max(...((i=(c=e.srcSet)===null||c===void 0?void 0:c.map(B=>B.width))!==null&&i!==void 0?i:[]).concat(e.width?[e.width]:[]).filter(Boolean)),((u=S.current)===null||u===void 0?void 0:u.naturalWidth)||0),L=$(Math.max(...((p=(g=e.srcSet)===null||g===void 0?void 0:g.map(B=>B.height))!==null&&p!==void 0?p:[]).concat(e.height?[e.height]:[]).filter(Boolean)),((m=S.current)===null||m===void 0?void 0:m.naturalHeight)||0),U=P&&L?{maxWidth:`min(${P}px, 100%)`,maxHeight:`min(${L}px, 100%)`}:{maxWidth:"100%",maxHeight:"100%"},_=(x=e.srcSet)===null||x===void 0?void 0:x.sort((B,Y)=>B.width-Y.width).map(B=>`${B.src} ${B.width}w`).join(", "),b=()=>n&&!O&&e.width&&e.height?n.height/e.height*e.width:Number.MAX_VALUE,z=_&&n&&Ko()?`${Math.round(Math.min(b(),n.width))}px`:void 0,{style:F,className:G,...X}=a||{};return A.createElement(A.Fragment,null,A.createElement("img",{ref:w,onLoad:y,onError:E,onClick:s,draggable:!1,className:ht(ye(aa()),O&&ye(aa("cover")),M!==sn&&ye(aa("loading")),G),style:{...U,...h,...F},...X,alt:(k=e.alt)!==null&&k!==void 0?k:"",sizes:z,srcSet:_,src:e.src}),M!==sn&&A.createElement("div",{className:ye(oa(Gu))},M===Mr&&(r?.iconLoading?r.iconLoading():A.createElement(bf,{className:ht(ye(In),ye(oa(Mr)))})),M===an&&(r?.iconError?r.iconError():A.createElement(yf,{className:ht(ye(In),ye(oa(an)))}))))}const wf=A.forwardRef(function({className:t,children:r,onFocus:n,onBlur:o,...a},s){const l=A.useRef(null),{trackFocusWithin:f}=ea();return A.createElement(sf,{nodeRef:l},A.createElement("div",{ref:na(s,l),className:ht(ye("root"),t),...f(n,o),...a},r))});var pt;(function(e){e[e.NONE=0]="NONE",e[e.SWIPE=1]="SWIPE",e[e.PULL=2]="PULL",e[e.ANIMATION=3]="ANIMATION"})(pt||(pt={}));function Sl(e,t,r,n,o){A.useEffect(()=>o?()=>{}:Pr(e(Js,t),e(Rs,r),e(el,n),e(tl,n),e(rl,n)),[e,t,r,n,o])}var Kt;(function(e){e[e.NONE=0]="NONE",e[e.SWIPE=1]="SWIPE",e[e.PULL=2]="PULL"})(Kt||(Kt={}));const sa=30;function xf({disableSwipeNavigation:e,closeOnBackdropClick:t},r,n,o,a,s,l,f,h,c,i,u,g,p,m,x){const k=A.useRef(0),M=A.useRef([]),v=A.useRef(void 0),T=A.useRef(0),I=A.useRef(Kt.NONE),S=A.useCallback(P=>{v.current===P.pointerId&&(v.current=void 0,I.current=Kt.NONE);const L=M.current;L.splice(0,L.length,...L.filter(U=>U.pointerId!==P.pointerId))},[]),N=A.useCallback(P=>{S(P),P.persist(),M.current.push(P)},[S]),w=A.useCallback(P=>M.current.find(({pointerId:L})=>P.pointerId===L),[]),y=Le(P=>{N(P)}),E=(P,L)=>i&&P>L||c&&P<-L,O=Le(P=>{const L=w(P);if(L)if(v.current===P.pointerId){const U=Date.now()-T.current,_=k.current;I.current===Kt.SWIPE?Math.abs(_)>.3*o||Math.abs(_)>5&&U{const L=w(P);if(L){const U=v.current===P.pointerId;if(P.buttons===0){U&&k.current!==0?O(P):S(L);return}const _=P.clientX-L.clientX,b=P.clientY-L.clientY;if(v.current===void 0){const z=F=>{N(P),v.current=P.pointerId,T.current=Date.now(),I.current=F};Math.abs(_)>Math.abs(b)&&Math.abs(_)>sa&&n(_)?e||(z(Kt.SWIPE),s()):Math.abs(b)>Math.abs(_)&&E(b,sa)&&(z(Kt.PULL),u())}else U&&(I.current===Kt.SWIPE?(k.current=_,l(_)):I.current===Kt.PULL&&(k.current=b,g(b)))}});Sl(r,y,$,O)}function kf({preventDefaultWheelX:e,preventDefaultWheelY:t}){const r=A.useRef(null),n=Le(o=>{const a=Math.abs(o.deltaX)>Math.abs(o.deltaY);(a&&e||!a&&t||o.ctrlKey)&&o.preventDefault()});return A.useCallback(o=>{var a;o?o.addEventListener("wheel",n,{passive:!1}):(a=r.current)===null||a===void 0||a.removeEventListener("wheel",n),r.current=o},[n])}function Cf(e,t,r,n,o,a,s,l,f){const h=A.useRef(0),c=A.useRef(0),i=A.useRef(void 0),u=A.useRef(void 0),g=A.useRef(0),p=A.useRef(void 0),m=A.useRef(0),{setTimeout:x,clearTimeout:k}=cn(),M=A.useCallback(()=>{i.current&&(k(i.current),i.current=void 0)},[k]),v=A.useCallback(()=>{u.current&&(k(u.current),u.current=void 0)},[k]),T=Le(()=>{e!==pt.SWIPE&&(h.current=0,m.current=0,M(),v())});A.useEffect(T,[e,T]);const I=Le(N=>{u.current=void 0,h.current===N&&f(h.current)}),S=Le(N=>{if(N.ctrlKey||Math.abs(N.deltaY)>Math.abs(N.deltaX))return;const w=y=>{g.current=y,k(p.current),p.current=y>0?x(()=>{g.current=0,p.current=void 0},300):void 0};if(e===pt.NONE){if(Math.abs(N.deltaX)<=1.2*Math.abs(g.current)){w(N.deltaX);return}if(!r(-N.deltaX))return;if(c.current+=N.deltaX,M(),Math.abs(c.current)>30)c.current=0,w(0),m.current=Date.now(),a();else{const y=c.current;i.current=x(()=>{i.current=void 0,y===c.current&&(c.current=0)},o)}}else if(e===pt.SWIPE){let y=h.current-N.deltaX;if(y=Math.min(Math.abs(y),n)*Math.sign(y),h.current=y,s(y),v(),Math.abs(y)>.2*n){w(N.deltaX),l(y,Date.now()-m.current);return}u.current=x(()=>I(y),2*o)}else w(N.deltaX)});A.useEffect(()=>t(Vo,S),[t,S])}const El=gr("container"),Tl=A.createContext(null),Yt=$t("useController","ControllerContext",Tl);function _f({children:e,...t}){var r;const{carousel:n,animation:o,controller:a,on:s,styles:l,render:f}=t,{closeOnPullUp:h,closeOnPullDown:c,preventDefaultWheelX:i,preventDefaultWheelY:u}=a,[g,p]=A.useState(),m=Ut(),x=cf(),[k,M]=A.useState(pt.NONE),v=A.useRef(0),T=A.useRef(0),I=A.useRef(1),{registerSensors:S,subscribeSensors:N}=Cl(),{subscribe:w,publish:y}=Fr(),E=$n(),O=$n(),$=$n(),{containerRef:P,setContainerRef:L,containerRect:U}=wl(),_=na(kf({preventDefaultWheelX:i,preventDefaultWheelY:u}),L),b=A.useRef(null),z=na(b,void 0),{getOwnerDocument:F}=$r(),G=Fn(),X=K=>(G?-1:1)*(typeof K=="number"?K:1),B=Le(()=>{var K;return(K=P.current)===null||K===void 0?void 0:K.focus()}),Y=Le(()=>t),H=Le(()=>m),W=A.useCallback(K=>y(nr,K),[y]),Q=A.useCallback(K=>y(or,K),[y]),be=A.useCallback(()=>y(Dr),[y]),se=K=>!(n.finite&&(X(K)>0&&m.currentIndex===0||X(K)<0&&m.currentIndex===m.slides.length-1)),pe=K=>{var oe;v.current=K,(oe=P.current)===null||oe===void 0||oe.style.setProperty(Xe("swipe_offset"),`${Math.round(K)}px`)},ne=K=>{var oe,ae;T.current=K,I.current=(()=>{const Ee=c&&K>0?K:h&&K<0?-K:0;return Math.min(Math.max(Yo(1-Ee/60*(1-.5),2),.5),1)})(),(oe=P.current)===null||oe===void 0||oe.style.setProperty(Xe("pull_offset"),`${Math.round(K)}px`),(ae=P.current)===null||ae===void 0||ae.style.setProperty(Xe("pull_opacity"),`${I.current}`)},{prepareAnimation:Ae}=ra(b,(K,oe,ae)=>{if(b.current&&U)return{keyframes:[{transform:`translate(0, ${K.rect.y-oe.y+ae.y}px)`,opacity:K.opacity},{transform:"translate(0, 0)",opacity:1}],duration:K.duration,easing:o.easing.fade}}),ve=(K,oe)=>{if(h||c){ne(K);let ae=0;b.current&&(ae=o.fade*(oe?2:1),Ae({rect:b.current.getBoundingClientRect(),opacity:I.current,duration:ae})),$(()=>{ne(0),M(pt.NONE)},ae),M(pt.ANIMATION),oe||be()}},{prepareAnimation:We,isAnimationPlaying:me}=ra(b,(K,oe,ae)=>{var ce;if(b.current&&U&&(!((ce=m.animation)===null||ce===void 0)&&ce.duration)){const ke=Pn(n.spacing),Ee=(ke.percent?ke.percent*U.width/100:ke.pixel)||0;return{keyframes:[{transform:`translate(${X(m.globalIndex-K.index)*(U.width+Ee)+K.rect.x-oe.x+ae.x}px, 0)`},{transform:"translate(0, 0)"}],duration:m.animation.duration,easing:m.animation.easing}}}),Te=Le(K=>{var oe,ae;const ce=K.offset||0,ke=ce?o.swipe:(oe=o.navigation)!==null&&oe!==void 0?oe:o.swipe,Ee=!ce&&!me()?o.easing.navigation:o.easing.swipe;let{direction:Be}=K;const Me=(ae=K.count)!==null&&ae!==void 0?ae:1;let Ke=pt.ANIMATION,Pe=ke*Me;if(!Be){const le=U?.width,de=K.duration||0,ue=le?ke/le*Math.abs(ce):ke;Me!==0?(de0?nr:or):Pe=ke/2}let q=0;Be===nr?se(X(1))?q=-Me:(Ke=pt.NONE,Pe=ke):Be===or&&(se(X(-1))?q=Me:(Ke=pt.NONE,Pe=ke)),Pe=Math.round(Pe),O(()=>{pe(0),M(pt.NONE)},Pe),b.current&&We({rect:b.current.getBoundingClientRect(),index:m.globalIndex}),M(Ke),y(Bo,{type:"swipe",increment:q,duration:Pe,easing:Ee})});A.useEffect(()=>{var K,oe;!((K=m.animation)===null||K===void 0)&&K.increment&&(!((oe=m.animation)===null||oe===void 0)&&oe.duration)&&E(()=>x({type:"swipe",increment:0}),m.animation.duration)},[m.animation,x,E]);const ge=[N,se,U?.width||0,o.swipe,()=>M(pt.SWIPE),K=>pe(K),(K,oe)=>Te({offset:K,duration:oe,count:1}),K=>Te({offset:K,count:0})],Oe=[()=>{c&&M(pt.PULL)},K=>ne(K),K=>ve(K),K=>ve(K,!0)];xf(a,...ge,h,c,...Oe,be),Cf(k,...ge);const we=Le(()=>{a.focus&&F().querySelector(`.${ye(Ho)} .${ye(El())}`)&&B()});A.useEffect(we,[we]);const $e=Le(()=>{var K;(K=s.view)===null||K===void 0||K.call(s,{index:m.currentIndex})});A.useEffect($e,[m.globalIndex,$e]),A.useEffect(()=>Pr(w(nr,K=>Te({direction:nr,...K})),w(or,K=>Te({direction:or,...K})),w(Bo,K=>x(K))),[w,Te,x]);const ze=A.useMemo(()=>({prev:W,next:Q,close:be,focus:B,slideRect:U?Ru(U,n.padding):{width:0,height:0},containerRect:U||{width:0,height:0},subscribeSensors:N,containerRef:P,setCarouselRef:z,toolbarWidth:g,setToolbarWidth:p}),[W,Q,be,B,N,U,P,z,g,p,n.padding]);return A.useImperativeHandle(a.ref,()=>({prev:W,next:Q,close:be,focus:B,getLightboxProps:Y,getLightboxState:H}),[W,Q,be,B,Y,H]),A.createElement("div",{ref:_,className:ht(ye(El()),ye(Ir)),style:{...k===pt.SWIPE?{[Xe("swipe_offset")]:`${Math.round(v.current)}px`}:null,...k===pt.PULL?{[Xe("pull_offset")]:`${Math.round(T.current)}px`,[Xe("pull_opacity")]:`${I.current}`}:null,...a.touchAction!=="none"?{[Xe("controller_touch_action")]:a.touchAction}:null,...l.container},tabIndex:-1,...S},U&&A.createElement(Tl.Provider,{value:ze},e,(r=f.controls)===null||r===void 0?void 0:r.call(f)))}const Sf=Ft(rn,_f);function br(e){return fr(Vs,e)}function Al(e){return fr(jo,e)}function Ef({slide:e,offset:t}){const r=A.useRef(null),{currentIndex:n,slides:o}=Ut(),{slideRect:a,focus:s}=Yt(),{render:l,carousel:{imageFit:f,imageProps:h},on:{click:c},styles:{slide:i},labels:u}=ct(),{getOwnerDocument:g}=$r(),p=t!==0;A.useEffect(()=>{var x;p&&(!((x=r.current)===null||x===void 0)&&x.contains(g().activeElement))&&s()},[p,s,g]);const m=()=>{var x,k,M,v;let T=(x=l.slide)===null||x===void 0?void 0:x.call(l,{slide:e,offset:t,rect:a});return!T&&pr(e)&&(T=A.createElement(mr,{slide:e,offset:t,render:l,rect:a,imageFit:f,imageProps:h,onClick:p?void 0:()=>c?.({index:n})})),T?A.createElement(A.Fragment,null,(k=l.slideHeader)===null||k===void 0?void 0:k.call(l,{slide:e}),((M=l.slideContainer)!==null&&M!==void 0?M:(({children:I})=>I))({slide:e,children:T}),(v=l.slideFooter)===null||v===void 0?void 0:v.call(l,{slide:e})):null};return A.createElement("div",{ref:r,className:ht(ye(Al()),!p&&ye(Al("current")),ye(Ir)),...rf(p),style:i,role:"group","aria-roledescription":hr(u,"Slide"),"aria-label":ll(u,o,n+t)},m())}function Tf(){const e=ct().styles.slide;return A.createElement("div",{className:ye(jo),style:e})}function Af({carousel:e,labels:t}){const{slides:r,currentIndex:n,globalIndex:o}=Ut(),{setCarouselRef:a}=Yt(),{autoPlaying:s,focusWithin:l}=ea(),f=Pn(e.spacing),h=Pn(e.padding),c=cl(e,r,1),i=[];if(Zo(r))for(let u=n-c;u<=n+c;u+=1){const g=Qo(r,u),p=o-n+u,m=e.finite&&(u<0||u>r.length-1);i.push(m?{key:p}:{key:[`${p}`,il(g)].filter(Boolean).join("|"),offset:u-n,slide:g})}return A.createElement("div",{ref:a,className:ht(ye(br()),i.length>0&&ye(br("with_slides"))),style:{[`${Xe(br("slides_count"))}`]:i.length,[`${Xe(br("spacing_px"))}`]:f.pixel||0,[`${Xe(br("spacing_percent"))}`]:f.percent||0,[`${Xe(br("padding_px"))}`]:h.pixel||0,[`${Xe(br("padding_percent"))}`]:h.percent||0},role:"region","aria-live":s&&!l?"off":"polite","aria-roledescription":hr(t,"Carousel"),"aria-label":hr(t,"Photo gallery")},i.map(({key:u,slide:g,offset:p})=>g?A.createElement(Ef,{key:u,slide:g,offset:p}):A.createElement(Tf,{key:u})))}const Nf=Ft(Vs,Af);function Nl(){const{carousel:e}=ct(),{slides:t,currentIndex:r}=Ut(),n=t.length===0||e.finite&&r===0,o=t.length===0||e.finite&&r===t.length-1;return{prevDisabled:n,nextDisabled:o}}function Ol(e){var t;const r=Fn(),{publish:n}=Fr(),{animation:o}=ct(),{prevDisabled:a,nextDisabled:s}=Nl(),l=((t=o.navigation)!==null&&t!==void 0?t:o.swipe)/2,f=_l(()=>n(nr),l),h=_l(()=>n(or),l),c=Le(i=>{switch(i.key){case qu:n(Dr);break;case Zu:(r?s:a)||(r?h:f)();break;case Qu:(r?a:s)||(r?f:h)();break}});A.useEffect(()=>e(Wo,c),[e,c])}function Ml({label:e,icon:t,renderIcon:r,action:n,onClick:o,disabled:a,style:s}){return A.createElement(Ur,{label:e,icon:t,renderIcon:r,className:ye(`navigation_${n}`),disabled:a,onClick:o,style:s,...kl(Yt().focus,a)})}function Of({render:{buttonPrev:e,buttonNext:t,iconPrev:r,iconNext:n},styles:o}){const{prev:a,next:s,subscribeSensors:l}=Yt(),{prevDisabled:f,nextDisabled:h}=Nl();return Ol(l),A.createElement(A.Fragment,null,e?e():A.createElement(Ml,{label:"Previous",action:nr,icon:pf,renderIcon:r,style:o.navigationPrev,disabled:f,onClick:a}),t?t():A.createElement(Ml,{label:"Next",action:or,icon:mf,renderIcon:n,style:o.navigationNext,disabled:h,onClick:s}))}const Mf=Ft(Fu,Of),Il=ye(Ku),If=ye(Zs);function Df(e){return"style"in e}function Dl(e,t,r){const n=window.getComputedStyle(e),o=r?"padding-left":"padding-right",a=r?n.paddingLeft:n.paddingRight,s=e.style.getPropertyValue(o);return e.style.setProperty(o,`${(Dn(a)||0)+t}px`),()=>{s?e.style.setProperty(o,s):e.style.removeProperty(o)}}function Pf({noScroll:{disabled:e},children:t}){const r=Fn(),{getOwnerDocument:n,getOwnerWindow:o}=$r();return A.useEffect(()=>{if(e)return()=>{};const a=[],s=o(),{body:l,documentElement:f}=n(),h=Math.round(s.innerWidth-f.clientWidth);if(h>0){a.push(Dl(l,h,r));const c=l.getElementsByTagName("*");for(let i=0;i{l.classList.remove(Il),a.forEach(c=>c())}},[r,e,n,o]),A.createElement(A.Fragment,null,t)}const Lf=Ft(Uu,Pf);function Pl(e){return fr(Ho,e)}function Ll(e,t,r){const n=e.getAttribute(t);return e.setAttribute(t,r),()=>{n?e.setAttribute(t,n):e.removeAttribute(t)}}function $f({children:e,animation:t,styles:r,className:n,on:o,portal:a,close:s,labels:l}){const[f,h]=A.useState(!1),[c,i]=A.useState(!1),u=A.useRef([]),g=A.useRef(null),{setTimeout:p}=cn(),{subscribe:m}=Fr(),k=ta()?0:t.fade;A.useEffect(()=>(h(!0),()=>{h(!1),i(!1)}),[]);const M=Le(()=>{u.current.forEach(S=>S()),u.current=[]}),v=Le(()=>{var S;i(!1),M(),(S=o.exiting)===null||S===void 0||S.call(o),p(()=>{var N;(N=o.exited)===null||N===void 0||N.call(o),s()},k)});A.useEffect(()=>m(Dr,v),[m,v]);const T=Le(S=>{var N,w,y;nf(S),i(!0),(N=o.entering)===null||N===void 0||N.call(o);const E=(y=(w=S.parentNode)===null||w===void 0?void 0:w.children)!==null&&y!==void 0?y:[];for(let O=0;O{var O,$;($=(O=g.current)===null||O===void 0?void 0:O.focus)===null||$===void 0||$.call(O)}),p(()=>{var O;(O=o.entered)===null||O===void 0||O.call(o)},k)}),I=A.useCallback(S=>{S?T(S):M()},[T,M]);return f?tr.createPortal(A.createElement(wf,{ref:I,className:ht(n,ye(Pl()),ye(Zs),c&&ye(Pl("open"))),"aria-modal":!0,role:"dialog","aria-label":hr(l,"Lightbox"),style:{...t.fade!==Ro.animation.fade?{[Xe("fade_animation_duration")]:`${k}ms`}:null,...t.easing.fade!==Ro.animation.easing.fade?{[Xe("fade_animation_timing_function")]:t.easing.fade}:null,...r.root},onFocus:S=>{g.current||(g.current=S.relatedTarget)}},e),a.root||document.body):null}const Ff=Ft(Ho,$f);function Uf({children:e}){return A.createElement(A.Fragment,null,e)}const zf=Ft(zu,Uf);function Hf(e){return fr(Ks,e)}function Gf({toolbar:{buttons:e},render:{buttonClose:t,iconClose:r},styles:n}){const{close:o,setToolbarWidth:a}=Yt(),{setContainerRef:s,containerRect:l}=wl();Vt(()=>{a(l?.width)},[a,l?.width]);const f=()=>t?t():A.createElement(Ur,{key:Dr,label:"Close",icon:hf,renderIcon:r,onClick:o});return A.createElement("div",{ref:s,style:n.toolbar,className:ye(Hf())},e?.map(h=>h===Dr?f():h))}const jf=Ft(Ks,Gf);function $l(e,t){var r;return A.createElement(e.module.component,{key:e.module.name,...t},(r=e.children)===null||r===void 0?void 0:r.map(n=>$l(n,t)))}function Bf(e,t={}){const{easing:r,...n}=e,{easing:o,...a}=t;return{easing:{...r,...o},...n,...a}}function Wf({carousel:e,animation:t,render:r,toolbar:n,controller:o,noScroll:a,on:s,plugins:l,slides:f,index:h,...c}){const{animation:i,carousel:u,render:g,toolbar:p,controller:m,noScroll:x,on:k,slides:M,index:v,plugins:T,...I}=Ro,{config:S,augmentation:N}=of([it(Ff,[it(Lf,[it(Sf,[it(Nf),it(jf),it(Mf)])])])],l||T),w=N({animation:Bf(i,t),carousel:{...u,...e},render:{...g,...r},toolbar:{...p,...n},controller:{...m,...o},noScroll:{...x,...a},on:{...k,...s},...I,...c});return w.open?A.createElement(pl,{...w},A.createElement(uf,{slides:f||M,index:Dn(h||v)},A.createElement(ff,null,A.createElement(lf,null,A.createElement(af,null,$l(it(zf,S),w)))))):null}const Vf={minZoom:1,maxZoomPixelRatio:1,zoomInMultiplier:2,doubleTapDelay:300,doubleClickDelay:500,doubleClickMaxStops:2,keyboardMoveDistance:50,wheelZoomDistanceFactor:100,pinchZoomDistanceFactor:100,pinchZoomV4:!1,scrollToZoom:!1};function Kf(e){return Math.min(Math.max(e,Number.EPSILON),1)}function Fl(e){const{minZoom:t,...r}={...Vf,...e};return{minZoom:Kf(t),...r}}function Yf(e,t,r,n){const o=A.useRef(void 0),a=A.useRef(void 0),{zoom:s}=ct().animation,l=ta(),f=Le(()=>{var h,c,i;if((h=o.current)===null||h===void 0||h.cancel(),o.current=void 0,a.current&&n?.current){try{o.current=(i=(c=n.current).animate)===null||i===void 0?void 0:i.call(c,[{transform:a.current},{transform:`scale(${e}) translateX(${t}px) translateY(${r}px)`}],{duration:l?0:s??500,easing:o.current?"ease-out":"ease-in-out"})}catch(u){console.error(u)}a.current=void 0,o.current&&(o.current.onfinish=()=>{o.current=void 0})}});return Vt(f,[e,t,r,f]),A.useCallback(()=>{a.current=n?.current?window.getComputedStyle(n.current).transform:void 0},[n])}function Xf(e,t){const{on:r}=ct(),n=Le(()=>{var o;t||(o=r.zoom)===null||o===void 0||o.call(r,{zoom:e})});A.useEffect(n,[e,n])}function Un(){const{zoom:e}=ct();return Fl(e)}function qf(e,t){var r,n;let o={width:0,height:0},a={width:0,height:0};const{currentSlide:s}=Ut(),{imageFit:l}=ct().carousel,{maxZoomPixelRatio:f}=Un();if(e&&s){const c={...s,...t};if(pr(c)){const i=Xo(c,l),u=Math.max(...(((r=c.srcSet)===null||r===void 0?void 0:r.map(p=>p.width))||[]).concat(c.width?[c.width]:[])),g=Math.max(...(((n=c.srcSet)===null||n===void 0?void 0:n.map(p=>p.height))||[]).concat(c.height?[c.height]:[]));u>0&&g>0&&e.width>0&&e.height>0&&(a=i?{width:Math.round(Math.min(u,e.width/e.height*g)),height:Math.round(Math.min(g,e.height/e.width*u))}:{width:u,height:g},a={width:a.width*f,height:a.height*f},o=i?{width:Math.min(e.width,a.width,u),height:Math.min(e.height,a.height,g)}:{width:Math.round(Math.min(e.width,e.height/g*u,u)),height:Math.round(Math.min(e.height,e.width/u*g,g))})}}const h=o.width?Math.max(Yo(a.width/o.width,5),1):1;return{imageRect:o,maxZoom:h}}function Ul(e,t){return Math.hypot(e.clientX-t.clientX,e.clientY-t.clientY)}function zl(e,t,r=100,n=2){return e*Math.min(1+Math.abs(t/r),n)**Math.sign(t)}function Zf(e,t,r,n,o,a,s,l,f){const h=A.useRef([]),c=A.useRef(0),i=A.useRef(void 0),{globalIndex:u}=Ut(),{getOwnerWindow:g}=$r(),{containerRef:p,subscribeSensors:m}=Yt(),{keyboardMoveDistance:x,zoomInMultiplier:k,wheelZoomDistanceFactor:M,scrollToZoom:v,doubleTapDelay:T,doubleClickDelay:I,doubleClickMaxStops:S,pinchZoomDistanceFactor:N,pinchZoomV4:w}=Un(),y=A.useCallback(z=>{if(p.current){const{pageX:F,pageY:G}=z,{scrollX:X,scrollY:B}=g(),{left:Y,top:H,width:W,height:Q}=p.current.getBoundingClientRect();return[F-Y-X-W/2,G-H-B-Q/2]}return[]},[p,g]),E=Le(z=>{const{key:F,metaKey:G,ctrlKey:X}=z,B=G||X,Y=()=>{z.preventDefault(),z.stopPropagation()};if(e>1){const H=(W,Q)=>{Y(),l(W,Q)};F==="ArrowDown"?H(0,x):F==="ArrowUp"?H(0,-x):F==="ArrowLeft"?H(-x,0):F==="ArrowRight"&&H(x,0)}F==="+"||B&&F==="="?(Y(),o()):F==="-"||B&&F==="_"?(Y(),a()):B&&F==="0"&&(Y(),s(1))}),O=Le(z=>{if((z.ctrlKey||v)&&Math.abs(z.deltaY)>Math.abs(z.deltaX)){z.stopPropagation(),s(zl(e,-z.deltaY,M),!0,...y(z));return}e>1&&(z.stopPropagation(),v||l(z.deltaX,z.deltaY))}),$=A.useCallback(z=>{const F=h.current;F.splice(0,F.length,...F.filter(G=>G.pointerId!==z.pointerId))},[]),P=A.useCallback(z=>{$(z),z.persist(),h.current.push(z)},[$]),L=Le(z=>{var F;const G=h.current;if(z.pointerType==="mouse"&&z.buttons>1||!(!((F=f?.current)===null||F===void 0)&&F.contains(z.target)))return;e>1&&z.stopPropagation();const{timeStamp:X}=z;if(G.length===0&&X-c.current<(z.pointerType==="touch"?T:I)){c.current=0;const B=e>=1?e!==r?e*Math.max(r**(1/S),k):1:e!==t?e/Math.max(t**(-1/S),k):1;s(B,!1,...y(z))}else c.current=X;if(P(z),G.length===2){const B=Ul(G[0],G[1]);i.current={previousDistance:B,initialDistance:Math.max(B,1),initialZoom:e}}}),U=Le(z=>{const F=h.current,G=F.find(X=>X.pointerId===z.pointerId);if(F.length===2&&i.current){z.stopPropagation(),P(z);const X=Ul(F[0],F[1]),B=w?i.current.initialZoom/i.current.initialDistance*X:zl(e,X-i.current.previousDistance,N);s(B,!0,...F.map(Y=>y(Y)).reduce((Y,H)=>H.map((W,Q)=>Y[Q]+W/2))),i.current.previousDistance=X;return}e>1&&(z.stopPropagation(),G&&(F.length===1&&l((G.clientX-z.clientX)/e,(G.clientY-z.clientY)/e),P(z)))}),_=A.useCallback(z=>{const F=h.current;F.length===2&&F.find(G=>G.pointerId===z.pointerId)&&(i.current=void 0),$(z)},[$]),b=A.useCallback(()=>{const z=h.current;z.splice(0,z.length),c.current=0,i.current=void 0},[]);Sl(m,L,U,_,n),A.useEffect(b,[u,b]),A.useEffect(()=>n?()=>{}:Pr(b,m(Wo,E),m(Vo,O)),[n,m,b,E,O])}function Qf(e,t,r){const[n,o]=A.useState(1),[a,s]=A.useState(0),[l,f]=A.useState(0),h=Yf(n,a,l,r),{currentSlide:c,globalIndex:i}=Ut(),{containerRect:u,slideRect:g}=Yt(),{minZoom:p,zoomInMultiplier:m}=Un(),x=c&&pr(c)?c.src:void 0,k=!x||!r?.current;Vt(()=>{o(1),s(0),f(0)},[i,x]);const M=A.useCallback((N,w,y)=>{const E=y||n,O=a-(N||0),$=l-(w||0),P=(e.width*E-g.width)/2/E,L=(e.height*E-g.height)/2/E;s(Math.min(Math.abs(O),Math.max(P,0))*Math.sign(O)),f(Math.min(Math.abs($),Math.max(L,0))*Math.sign($))},[n,a,l,g,e.width,e.height]),v=A.useCallback((N,w,y,E)=>{const O=Yo(N+.01p?N:p:t,5);w||h(),M(y?y*(1/n-1/O):0,E?E*(1/n-1/O):0,O),o(O)},[n,p,t,M,h]),T=Le(()=>{n>1&&(n>t&&v(t,!0),M())});Vt(T,[u.width,u.height,T]);const I=A.useCallback(()=>{const N=n*m;v(n<1&&N>1?1:N)},[n,m,v]),S=A.useCallback(()=>{const N=n/m;v(n>1&&N<1?1:N)},[n,m,v]);return{zoom:n,offsetX:a,offsetY:l,disabled:k,changeOffsets:M,changeZoom:v,zoomIn:I,zoomOut:S}}const Hl=A.createContext(null),la=$t("useZoom","ZoomControllerContext",Hl);function Jf({children:e}){const[t,r]=A.useState(),{slideRect:n}=Yt(),{ref:o,minZoom:a}=Un(),{imageRect:s,maxZoom:l}=qf(n,t?.imageDimensions),{zoom:f,offsetX:h,offsetY:c,disabled:i,changeZoom:u,changeOffsets:g,zoomIn:p,zoomOut:m}=Qf(s,l,t?.zoomWrapperRef);Xf(f,i),Zf(f,a,l,i,p,m,u,g,t?.zoomWrapperRef);const x=A.useMemo(()=>({zoom:f,minZoom:a,maxZoom:l,offsetX:h,offsetY:c,disabled:i,zoomIn:p,zoomOut:m,changeZoom:u}),[f,a,l,h,c,i,p,m,u]);A.useImperativeHandle(o,()=>x,[x]);const k=A.useMemo(()=>({...x,setZoomWrapper:r}),[x,r]);return A.createElement(Hl.Provider,{value:k},e)}const Rf=xt("ZoomIn",A.createElement(A.Fragment,null,A.createElement("path",{d:"M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z"}),A.createElement("path",{d:"M12 10h-2v2H9v-2H7V9h2V7h1v2h2v1z"}))),eg=xt("ZoomOut",A.createElement("path",{d:"M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14zM7 9h5v1H7z"})),Gl=A.forwardRef(function({zoomIn:t,onLoseFocus:r},n){const o=A.useRef(!1),a=A.useRef(!1),{zoom:s,minZoom:l,maxZoom:f,zoomIn:h,zoomOut:c,disabled:i}=la(),{render:u}=ct(),g=i||(t?s>=f:s<=l);return A.useEffect(()=>{g&&o.current&&a.current&&r(),g||(o.current=!0)},[g,r]),A.createElement(Ur,{ref:n,disabled:g,label:t?"Zoom in":"Zoom out",icon:t?Rf:eg,renderIcon:t?u.iconZoomIn:u.iconZoomOut,onClick:t?h:c,onFocus:()=>{a.current=!0},onBlur:()=>{a.current=!1}})});function tg(){const e=A.useRef(null),t=A.useRef(null),{focus:r}=Yt(),n=A.useCallback(s=>{var l,f;!((l=s.current)===null||l===void 0)&&l.disabled?r():(f=s.current)===null||f===void 0||f.focus()},[r]),o=A.useCallback(()=>n(e),[n]),a=A.useCallback(()=>n(t),[n]);return A.createElement(A.Fragment,null,A.createElement(Gl,{zoomIn:!0,ref:e,onLoseFocus:a}),A.createElement(Gl,{ref:t,onLoseFocus:o}))}function rg(){const{render:e}=ct(),t=la();return e.buttonZoom?A.createElement(A.Fragment,null,e.buttonZoom(t)):A.createElement(tg,null)}function ng(e){var t;return(((t=e.srcSet)===null||t===void 0?void 0:t.length)||0)>0}function og({current:e,preload:t},{type:r,source:n}){switch(r){case"fetch":return e?{current:e,preload:n}:{current:n};case"done":return n===t?{current:n}:{current:e,preload:t};default:throw new Error(al)}}function ag(e){var t,r;const[{current:n,preload:o},a]=A.useReducer(og,{}),{slide:s,rect:l,imageFit:f,render:h,interactive:c}=e,i=s.srcSet.sort((I,S)=>I.width-S.width),u=(t=s.width)!==null&&t!==void 0?t:i[i.length-1].width,g=(r=s.height)!==null&&r!==void 0?r:i[i.length-1].height,p=Xo(s,f),m=Math.max(...i.map(I=>I.width)),x=Math.min((p?Math.max:Math.min)(l.width,u*(l.height/g)),m),k=ef(),M=Le(()=>{var I;const S=(I=i.find(N=>N.width>=x*k))!==null&&I!==void 0?I:i[i.length-1];(!n||i.findIndex(N=>N.src===n)N===S))&&a({type:"fetch",source:S.src})});Vt(M,[l.width,l.height,k,M]);const v=Le(I=>a({type:"done",source:I})),T={WebkitTransform:c?"initial":"translateZ(0)"};return p||Object.assign(T,l.width/l.heightv(o),render:{...h,iconLoading:()=>null,iconError:()=>null}}),n&&A.createElement(mr,{key:"current",...e,slide:{...s,src:n,srcSet:void 0},style:T}))}function sg({render:e,slide:t,offset:r,rect:n}){var o;const[a,s]=A.useState(),l=A.useRef(null),{zoom:f,maxZoom:h,offsetX:c,offsetY:i,setZoomWrapper:u}=la(),g=f>1,{carousel:p,on:m}=ct(),{currentIndex:x}=Ut();Vt(()=>r===0?(u({zoomWrapperRef:l,imageDimensions:a}),()=>u(void 0)):()=>{},[r,a,u]);let k=(o=e.slide)===null||o===void 0?void 0:o.call(e,{slide:t,offset:r,rect:n,zoom:f,maxZoom:h});if(!k&&pr(t)){const M={slide:t,offset:r,rect:n,render:e,imageFit:p.imageFit,imageProps:p.imageProps,onClick:r===0?()=>{var v;return(v=m.click)===null||v===void 0?void 0:v.call(m,{index:x})}:void 0};k=ng(t)?A.createElement(ag,{...M,slide:t,interactive:g,rect:r===0?{width:n.width*f,height:n.height*f}:n}):A.createElement(mr,{onLoad:v=>s({width:v.naturalWidth,height:v.naturalHeight}),...M})}return k?A.createElement("div",{ref:l,className:ht(ye(qs),ye(Ir),ye(Qs),g&&ye(Yu)),style:r===0?{transform:`scale(${f}) translateX(${c}px) translateY(${i}px)`}:void 0},k):null}const lg=({augment:e,addModule:t})=>{e(({zoom:r,toolbar:n,render:o,controller:a,...s})=>{const l=Fl(r);return{zoom:l,toolbar:Ln(n,Xs,A.createElement(rg,null)),render:{...o,slide:f=>{var h;return pr(f.slide)?A.createElement(sg,{render:o,...f}):(h=o.slide)===null||h===void 0?void 0:h.call(o,f)}},controller:{...a,preventDefaultWheelY:l.scrollToZoom},...s}}),t(Ft(Xs,Jf))},ig={separator:"/",container:{}},jl=e=>({...ig,...e});function cg({counter:e}){const{slides:t,currentIndex:r}=Ut(),{separator:n,container:{className:o,...a},className:s,...l}=jl(e);return t.length===0?null:A.createElement("div",{className:ht(ye("counter"),o||s),...l,...a,"aria-hidden":!0},r+1," ",n," ",t.length)}function dg({augment:e,addChild:t}){e(({counter:r,...n})=>({counter:jl(r),...n})),t(rn,Ft(Hu,cg))}const ug={autoplay:!1,delay:3e3,ref:null},Bl=e=>({...ug,...e}),Wl=A.createContext(null),fg=$t("useSlideshow","SlideshowContext",Wl);function gg({slideshow:e,carousel:{finite:t},on:r,children:n}){const{autoplay:o,delay:a,ref:s}=Bl(e),l=A.useRef(o),[f,h]=A.useState(o),c=A.useRef(void 0),i=A.useRef(void 0),{slides:u,currentIndex:g}=Ut(),{setTimeout:p,clearTimeout:m}=cn(),{subscribe:x}=Fr(),{next:k}=Yt(),{setAutoPlaying:M}=ea();A.useEffect(()=>M(f),[f,M]);const v=u.length===0||t&&g===u.length-1,T=A.useCallback(()=>{!f&&!v&&h(!0)},[f,v]),I=A.useCallback(()=>{f&&h(!1)},[f]),S=A.useCallback(()=>{m(c.current),c.current=void 0},[m]),N=Le(()=>{S(),!(!f||v||i.current===Mr||i.current===Go)&&(c.current=p(()=>{f&&(i.current=void 0,k())},a))});A.useEffect(N,[g,f,N]),A.useEffect(()=>{f&&v&&h(!1)},[g,f,v]);const w=Le(()=>{var O;return(O=r.slideshowStart)===null||O===void 0?void 0:O.call(r)}),y=Le(()=>{var O;return(O=r.slideshowStop)===null||O===void 0?void 0:O.call(r)});A.useEffect(()=>{f?w():l.current&&y(),l.current=f},[f,w,y]),A.useEffect(()=>Pr(S,x(ju,()=>{i.current=Mr,S()}),x(Bu,()=>{i.current=Go,S()}),x(Wu,()=>{i.current=an,N()}),x(Vu,()=>{i.current=sn,N()})),[x,S,N]);const E=A.useMemo(()=>({playing:f,disabled:v,play:T,pause:I}),[f,v,T,I]);return A.useImperativeHandle(s,()=>E,[E]),A.createElement(Wl.Provider,{value:E},n)}const hg=xt("Play",A.createElement("path",{d:"M8 5v14l11-7z"})),pg=xt("Pause",A.createElement("path",{d:"M6 19h4V5H6v14zm8-14v14h4V5h-4z"}));function mg(){const{playing:e,disabled:t,play:r,pause:n}=fg(),{render:o}=ct(),a=kl(Yt().focus,t);return o.buttonSlideshow?A.createElement(A.Fragment,null,o.buttonSlideshow({playing:e,disabled:t,play:r,pause:n})):A.createElement(Ur,{label:e?"Pause":"Play",icon:e?pg:hg,renderIcon:e?o.iconSlideshowPause:o.iconSlideshowPlay,onClick:e?n:r,disabled:t,...a})}function bg({augment:e,addModule:t}){e(({slideshow:r,toolbar:n,...o})=>({toolbar:Ln(n,Ys,A.createElement(mg,null)),slideshow:Bl(r),...o})),t(Ft(Ys,gg))}const yg={auto:!1,ref:null},Vl=e=>({...yg,...e}),Kl=A.createContext(null),vg=$t("useFullscreen","FullscreenContext",Kl);function wg({fullscreen:e,on:t,children:r}){const{auto:n,ref:o}=Vl(e),a=A.useRef(null),[s,l]=A.useState(),[f,h]=A.useState(!1),c=A.useRef(!1),{getOwnerDocument:i}=$r();Vt(()=>{var v,T,I,S;const N=i();l(!((S=(I=(T=(v=N.fullscreenEnabled)!==null&&v!==void 0?v:N.webkitFullscreenEnabled)!==null&&T!==void 0?T:N.mozFullScreenEnabled)!==null&&I!==void 0?I:N.msFullscreenEnabled)!==null&&S!==void 0&&S))},[i]);const u=A.useCallback(()=>{var v;const T=i(),I=T.fullscreenElement||T.webkitFullscreenElement||T.mozFullScreenElement||T.msFullscreenElement;return((v=I?.shadowRoot)===null||v===void 0?void 0:v.fullscreenElement)||I},[i]),g=A.useCallback(()=>{const v=a.current;try{v.requestFullscreen?v.requestFullscreen().catch(()=>{}):v.webkitRequestFullscreen?v.webkitRequestFullscreen():v.mozRequestFullScreen?v.mozRequestFullScreen():v.msRequestFullscreen&&v.msRequestFullscreen()}catch{}},[]),p=A.useCallback(()=>{if(!u())return;const v=i();try{v.exitFullscreen?v.exitFullscreen().catch(()=>{}):v.webkitExitFullscreen?v.webkitExitFullscreen():v.mozCancelFullScreen?v.mozCancelFullScreen():v.msExitFullscreen&&v.msExitFullscreen()}catch{}},[u,i]);A.useEffect(()=>{const v=i(),T=()=>{h(u()===a.current)};return Pr(...["fullscreenchange","webkitfullscreenchange","mozfullscreenchange","MSFullscreenChange"].map(I=>(v.addEventListener(I,T),()=>v.removeEventListener(I,T))))},[u,i]);const m=Le(()=>{var v;return(v=t.enterFullscreen)===null||v===void 0?void 0:v.call(t)}),x=Le(()=>{var v;return(v=t.exitFullscreen)===null||v===void 0?void 0:v.call(t)});A.useEffect(()=>{f&&(c.current=!0),c.current&&(f?m:x)()},[f,m,x]);const k=Le(()=>{var v;return(v=n?g:null)===null||v===void 0||v(),p});A.useEffect(k,[k]);const M=A.useMemo(()=>({fullscreen:f,disabled:s,enter:g,exit:p}),[f,s,g,p]);return A.useImperativeHandle(o,()=>M,[M]),A.createElement("div",{ref:a,className:ht(ye(nn),ye(qs))},A.createElement(Kl.Provider,{value:M},r))}const xg=xt("EnterFullscreen",A.createElement("path",{d:"M7 14H5v5h5v-2H7v-3zm-2-4h2V7h3V5H5v5zm12 7h-3v2h5v-5h-2v3zM14 5v2h3v3h2V5h-5z"})),kg=xt("ExitFullscreen",A.createElement("path",{d:"M5 16h3v3h2v-5H5v2zm3-8H5v2h5V5H8v3zm6 11h2v-3h3v-2h-5v5zm2-11V5h-2v5h5V8h-3z"}));function Cg(){var e;const{fullscreen:t,disabled:r,enter:n,exit:o}=vg(),{render:a}=ct();return r?null:a.buttonFullscreen?A.createElement(A.Fragment,null,(e=a.buttonFullscreen)===null||e===void 0?void 0:e.call(a,{fullscreen:t,disabled:r,enter:n,exit:o})):A.createElement(Ur,{disabled:r,label:t?"Exit Fullscreen":"Enter Fullscreen",icon:t?kg:xg,renderIcon:t?a.iconExitFullscreen:a.iconEnterFullscreen,onClick:t?o:n})}function _g({augment:e,contains:t,addParent:r}){e(({fullscreen:n,toolbar:o,...a})=>({toolbar:Ln(o,nn,A.createElement(Cg,null)),fullscreen:Vl(n),...a})),r(t(on)?on:rn,Ft(nn,wg))}const yr={ref:null,position:"bottom",width:120,height:80,border:1,borderRadius:4,padding:4,gap:16,imageFit:"contain",vignette:!0,hidden:!1,showToggle:!1},ia=e=>({...yr,...e});function Yl(){const{thumbnails:e}=ct();return ia(e)}const vr=e=>fr(on,e),dt=e=>vr(fr("thumbnail",e)),Sg=xt("VideoThumbnail",A.createElement("path",{d:"M10 16.5l6-4.5-6-4.5v9zM12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8z"})),Eg=xt("UnknownThumbnail",A.createElement("path",{d:"M23 18V6c0-1.1-.9-2-2-2H3c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h18c1.1 0 2-.9 2-2zM8.5 12.5l2.5 3.01L14.5 11l4.5 6H5l3.5-4.5z"}));function Tg({slide:e,render:t,rect:r,imageFit:n}){var o;const a=(o=t.thumbnail)===null||o===void 0?void 0:o.call(t,{slide:e,render:t,rect:r,imageFit:n});if(a)return a;const s={render:t,rect:r,imageFit:n};if(e.thumbnail)return A.createElement(mr,{slide:{src:e.thumbnail},...s});if(pr(e))return A.createElement(mr,{slide:e,...s});const l=ye(dt(In));return e.type==="video"?A.createElement(A.Fragment,null,e.poster&&A.createElement(mr,{slide:{src:e.poster},...s}),A.createElement(Sg,{className:l})):A.createElement(Eg,{className:l})}const Ag=gr("active"),ca=gr("fadein"),da=gr("fadeout"),Ng=gr("placeholder"),Xl="delay",ql="duration";function Og({slide:e,index:t,onClick:r,fadeIn:n,fadeOut:o,placeholder:a,onLoseFocus:s}){const l=A.useRef(null),{render:f,styles:h,labels:c}=ct(),{slides:i,globalIndex:u}=Ut(),{getOwnerDocument:g}=$r(),{width:p,height:m,imageFit:x}=Yl(),k={width:p,height:m},M=t===u,v=Le(s);return A.useEffect(()=>{o&&g().activeElement===l.current&&v()},[o,v,g]),A.createElement("button",{ref:l,type:"button",className:ht(ye(Ir),ye(dt()),M&&ye(dt(Ag())),n&&ye(dt(ca())),o&&ye(dt(da())),a&&ye(dt(Ng()))),style:{...n?{[Xe(dt(ca(ql)))]:`${n.duration}ms`,[Xe(dt(ca(Xl)))]:`${n.delay}ms`}:null,...o?{[Xe(dt(da(ql)))]:`${o.duration}ms`,[Xe(dt(da(Xl)))]:`${o.delay}ms`}:null,...h.thumbnail},onClick:r,"aria-current":M?!0:void 0,"aria-label":ll(c,i,t)},e&&Tg({slide:e,render:f,rect:k,imageFit:x}))}function Zl(e){return["top","bottom"].includes(e)}function Ql(e,t){return t+2*(e.border+e.padding)+e.gap}function Mg(e){const{thumbnail:t,poster:r}=e||{thumbnail:"placeholder"};return typeof t=="string"&&t||typeof r=="string"&&r||e&&il(e)||void 0}function Jl({visible:e,containerRef:t}){const r=A.useRef(null),n=Fn(),{publish:o,subscribe:a}=Fr(),{carousel:s,styles:l,labels:f}=ct(),{slides:h,globalIndex:c,animation:i}=Ut(),{registerSensors:u,subscribeSensors:g}=Cl();Ol(g);const p=Yl(),{position:m,width:x,height:k,border:M,borderStyle:v,borderColor:T,borderRadius:I,padding:S,gap:N,vignette:w}=p,y=i?.duration!==void 0&&i?.increment||0,E=i?.duration||0,{prepareAnimation:O}=ra(r,_=>({keyframes:Zl(m)?[{transform:`translateX(${(n?-1:1)*Ql(p,x)*y+_}px)`},{transform:"translateX(0)"}]:[{transform:`translateY(${Ql(p,k)*y+_}px)`},{transform:"translateY(0)"}],duration:E,easing:i?.easing})),$=Le(()=>{let _=0;if(t.current&&r.current){const b=t.current.getBoundingClientRect(),z=r.current.getBoundingClientRect();_=Zl(m)?z.left-b.left-(b.width-z.width)/2:z.top-b.top-(b.height-z.height)/2}O(_)});A.useEffect(()=>Pr(a(Bo,$)),[a,$]);const P=cl(s,h),L=[];if(Zo(h))for(let _=c-P-Math.abs(y);_<=c+P+Math.abs(y);_+=1){const z=s.finite&&(_<0||_>h.length-1)||y<0&&_0&&_>c+P?null:Qo(h,_),F=[`${_}`,Mg(z)].filter(Boolean).join("|");L.push({key:F,index:_,slide:z})}const U=_=>()=>{_>c?o(or,{count:_-c}):_{const F=E/Math.abs(y||1),G=y>0&&b>c+P-y&&b<=c+P||y<0&&b=c-P?{duration:F,delay:((y>0?b-(c+P-y):c-P-y-b)-1)*F}:void 0,X=y>0&&bc+P?{duration:F,delay:(y>0?y-(c-P-b):-y-(b-(c+P)))*F}:void 0;return A.createElement(Og,{key:_,index:b,slide:z,fadeIn:G,fadeOut:X,placeholder:!z,onClick:U(b),onLoseFocus:()=>{var B;return(B=r.current)===null||B===void 0?void 0:B.focus()}})})),w&&A.createElement("div",{className:ye(vr("vignette"))}))}const Rl=A.createContext(null),Ig=$t("useThumbnails","ThumbnailsContext",Rl);function Dg({children:e,...t}){const{ref:r,position:n,hidden:o}=ia(t.thumbnails),[a,s]=A.useState(!o),l=A.useRef(null),f=A.useMemo(()=>({visible:a,show:()=>s(!0),hide:()=>s(!1)}),[a]);return A.useImperativeHandle(r,()=>f,[f]),A.createElement(pl,{...t},A.createElement(Rl.Provider,{value:f},A.createElement("div",{ref:l,className:ht(ye(vr()),ye(vr(`${n}`)))},["start","top"].includes(n)&&A.createElement(Jl,{containerRef:l,visible:a}),A.createElement("div",{className:ye(vr("wrapper"))},e),["end","bottom"].includes(n)&&A.createElement(Jl,{containerRef:l,visible:a}))))}const ei=()=>A.createElement(A.Fragment,null,A.createElement("path",{strokeWidth:2,stroke:"currentColor",strokeLinejoin:"round",fill:"none",d:"M3 5l18 0l0 14l-18 0l0-14z"}),A.createElement("path",{d:"M5 14h4v3h-4zM10 14h4v3h-4zM15 14h4v3h-4z"})),Pg=xt("ThumbnailsVisible",ei()),Lg=gf("ThumbnailsHidden",ei());function $g(){const{visible:e,show:t,hide:r}=Ig(),{render:n}=ct();return n.buttonThumbnails?A.createElement(A.Fragment,null,n.buttonThumbnails({visible:e,show:t,hide:r})):A.createElement(Ur,{label:e?"Hide thumbnails":"Show thumbnails",icon:e?Pg:Lg,renderIcon:e?n.iconThumbnailsVisible:n.iconThumbnailsHidden,onClick:e?r:t})}function Fg({augment:e,contains:t,append:r,addParent:n}){e(({thumbnails:a,toolbar:s,...l})=>{const f=ia(a);return{toolbar:Ln(s,on,f.showToggle?A.createElement($g,null):null),thumbnails:f,...l}});const o=Ft(on,Dg);t(nn)?r(nn,o):n(rn,o)}const Ug="X-Board-Secret-View-Token";function zn(){const e=window.G7Core?.state?.get?.("_global")?.secretViewToken;return typeof e=="string"&&e!==""?{[Ug]:e}:{}}const It=({className:e="",children:t,style:r,...n})=>d.jsx("i",{className:e,style:r,...n,children:t}),ua=e=>{if(!e||typeof window>"u"||!window.location||!/^(https?:)?\/\//i.test(e))return!1;try{return new URL(e,window.location.href).origin!==window.location.origin}catch{return!1}},fa=window.G7Core,Hn=(e,t)=>fa?.t?.(e,t)??e,zg=async(e,t)=>{try{const r=await fa.api.get(e,{responseType:"blob",headers:zn()});if(r){const n=URL.createObjectURL(r),o=document.createElement("a");o.href=n,o.download=t,document.body.appendChild(o),o.click(),document.body.removeChild(o),URL.revokeObjectURL(n)}}catch(r){console.error("Failed to download file:",r),fa?.toast?.error?.(Hn("common.download_failed"))}},Hg=(e,t)=>{const r=document.createElement("a");r.href=e,r.download=t,r.target="_blank",document.body.appendChild(r),r.click(),document.body.removeChild(r)},ti=async e=>{const t=e.downloadUrl||e.src,r=e.filename||e.title||"image";e.downloadRequiresAuth&&!ua(t)?await zg(t,r):Hg(t,r)},Gg=({image:e,index:t,onDownload:r})=>{const[n,o]=C.useState(!1),a=async s=>{if(s.stopPropagation(),r){r(e,t);return}o(!0);try{await ti(e)}finally{o(!1)}};return d.jsx(R,{type:"button",onClick:a,disabled:n,className:"yarl__button flex items-center justify-center","aria-label":Hn("common.download"),title:Hn("common.download"),children:n?d.jsx(It,{className:"fa-solid fa-spinner fa-spin text-white"}):d.jsx(It,{className:"fa-solid fa-download text-white"})})},zr=({images:e,isOpen:t,onClose:r,startIndex:n=0,enableZoom:o=!0,enableSlideshow:a=!1,enableFullscreen:s=!0,showCounter:l=!0,showDownload:f=!0,showThumbnails:h=!0,onDownload:c,editorAttrs:i,id:u})=>{const[g,p]=C.useState(n),m=C.useRef(n),x=e.map(v=>({src:v.src,title:v.title,description:v.description})),k=[];o&&k.push(lg),a&&k.push(bg),s&&k.push(_g),l&&k.push(dg),h&&k.push(Fg);const M=e[g];if(C.useEffect(()=>{m.current=g},[g]),i){const v=(e??[]).slice(0,4);return d.jsx(j,{id:u,className:"grid grid-cols-2 gap-2 p-3 rounded-lg border border-dashed border-gray-300 dark:border-gray-600 bg-gray-50 dark:bg-gray-800",...i,children:v.length>0?v.map((T,I)=>d.jsx(jt,{src:T.thumbnail||T.src,alt:T.title||"",className:"w-full aspect-square object-cover rounded bg-gray-200 dark:bg-gray-700"},I)):d.jsxs(j,{className:"col-span-2 flex items-center justify-center py-6 text-sm text-gray-400 dark:text-gray-500",children:[d.jsx(It,{className:"fa-regular fa-images mr-2"}),Hn("editor.component.image_gallery")]})})}return d.jsx(Wf,{open:t,close:r,slides:x,index:g,plugins:k,on:{view:({index:v})=>{v!==m.current&&p(v)}},zoom:{maxZoomPixelRatio:3,zoomInMultiplier:2,doubleTapDelay:300,doubleClickDelay:300,doubleClickMaxStops:2,keyboardMoveDistance:50,wheelZoomDistanceFactor:100,pinchZoomDistanceFactor:100,scrollToZoom:!0},carousel:{finite:!0,preload:2,padding:"16px",spacing:"30%"},animation:{fade:250,swipe:500,easing:{fade:"ease",swipe:"ease-out",navigation:"ease-in-out"}},controller:{closeOnBackdropClick:!0,closeOnPullDown:!0,closeOnPullUp:!0},thumbnails:{position:"bottom",width:120,height:80,border:2,borderRadius:4,padding:4,gap:16,showToggle:!1,vignette:!0},toolbar:{buttons:[f&&M&&d.jsx(Gg,{image:M,index:g,onDownload:c},"download"),"close"].filter(Boolean)},styles:{container:{backgroundColor:"rgba(0, 0, 0, 0.9)"}}})},ri=()=>{const[e,t]=C.useState(!1),[r,n]=C.useState([]),[o,a]=C.useState(0),s=C.useCallback((f,h=0)=>{n(f),a(h),t(!0)},[]),l=C.useCallback(()=>{t(!1)},[]);return{isOpen:e,openGallery:s,closeGallery:l,galleryProps:{images:r,isOpen:e,onClose:l,startIndex:o}}};zr.displayName="ImageGallery";const ni=Object.freeze(Object.defineProperty({__proto__:null,ImageGallery:zr,default:zr,executeImageDownload:ti,useImageGallery:ri},Symbol.toStringTag,{value:"Module"})),jg=window.G7Core,ga=(e,t)=>jg?.t?.(e,t)??e,Gn=e=>e.url??e.download_url,jn=({images:e=[],className:t="",id:r,editorAttrs:n})=>{const[o,a]=C.useState(0),{openGallery:s,galleryProps:l}=ri(),f=C.useMemo(()=>e.map(c=>({src:Gn(c),title:c.alt_text_current??"",thumbnail:Gn(c),downloadUrl:c.download_url,filename:c.alt_text_current??`image-${c.id}`})),[e]);if(!e||e.length===0)return d.jsx(j,{className:`flex items-center justify-center bg-gray-100 dark:bg-gray-700 rounded-lg aspect-square ${t}`,id:r,...n,children:d.jsxs(j,{className:"text-center text-gray-400 dark:text-gray-500",children:[d.jsx(fe,{name:"image",size:"3x",className:"mb-3 opacity-50"}),d.jsx(j,{className:"text-sm",children:ga("shop.no_image")})]})});const h=e[o]??e[0];return d.jsxs(j,{id:r,className:t,...n,children:[d.jsx(j,{className:"relative overflow-hidden rounded-lg bg-gray-100 dark:bg-gray-700 aspect-square mb-3",children:d.jsx(R,{type:"button",className:"w-full h-full cursor-zoom-in block",onClick:()=>s(f,o),"aria-label":ga("shop.view_image"),children:d.jsx(jt,{src:Gn(h),alt:h.alt_text_current??"",className:"w-full h-full object-contain"})})}),e.length>1&&d.jsx(j,{className:"flex gap-2 overflow-x-auto pb-1",children:e.map((c,i)=>d.jsx(R,{type:"button",className:`flex-shrink-0 w-16 h-16 rounded-md overflow-hidden border-2 transition-colors cursor-pointer ${i===o?"border-gray-900 dark:border-white":"border-gray-200 dark:border-gray-600 hover:border-gray-400 dark:hover:border-gray-400"}`,onClick:()=>a(i),"aria-label":c.alt_text_current??`${ga("shop.image")} ${i+1}`,children:d.jsx(jt,{src:Gn(c),alt:c.alt_text_current??"",className:"w-full h-full object-cover"})},c.id))}),d.jsx(zr,{...l})]})};jn.displayName="ProductImageViewer";const Bg=Object.freeze(Object.defineProperty({__proto__:null,ProductImageViewer:jn,default:jn},Symbol.toStringTag,{value:"Module"})),oi=({value:e,min:t=1,max:r=999,onChange:n,size:o="md",disabled:a=!1,className:s="",id:l,editorAttrs:f})=>{const c={sm:{container:"h-8",button:"w-8 h-8 text-sm",input:"w-10 text-sm"},md:{container:"h-10",button:"w-10 h-10",input:"w-14"},lg:{container:"h-12",button:"w-12 h-12 text-lg",input:"w-16 text-lg"}}[o],i=()=>{e>t&&!a&&n(e-1)},u=()=>{e{const M=parseInt(k.target.value,10);if(!isNaN(M)){const v=Math.max(t,Math.min(r,M));n(v)}},p=k=>{const M=parseInt(k.target.value,10);isNaN(M)||Mr&&n(r)},m=a||e<=t,x=a||e>=r;return d.jsxs(j,{className:`inline-flex items-center border border-gray-300 dark:border-gray-600 rounded-lg ${c.container} ${s}`,id:l,...f,children:[d.jsx(R,{type:"button",onClick:i,disabled:m,className:`${c.button} flex items-center justify-center text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700 disabled:opacity-50 disabled:cursor-not-allowed rounded-l-lg transition-colors`,"aria-label":"수량 감소",children:d.jsx(fe,{name:"minus",className:"w-4 h-4"})}),d.jsx(At,{type:"number",value:e,onChange:g,onBlur:p,min:t,max:r,disabled:a,className:`${c.input} text-center border-x border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-800 text-gray-900 dark:text-white focus:outline-none disabled:opacity-50 disabled:cursor-not-allowed [appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none`,"aria-label":"수량"}),d.jsx(R,{type:"button",onClick:u,disabled:x,className:`${c.button} flex items-center justify-center text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700 disabled:opacity-50 disabled:cursor-not-allowed rounded-r-lg transition-colors`,"aria-label":"수량 증가",children:d.jsx(fe,{name:"plus",className:"w-4 h-4"})})]})},Wg=Object.freeze(Object.defineProperty({__proto__:null,default:oi},Symbol.toStringTag,{value:"Module"})),Vg=window.G7Core?.createLogger?.("Comp:PostReactions")??{log:(...e)=>console.log("[Comp:PostReactions]",...e),warn:(...e)=>console.warn("[Comp:PostReactions]",...e),error:(...e)=>console.error("[Comp:PostReactions]",...e)},ai=(e,t)=>window.G7Core?.t?.(e,t)??e,Kg=[{type:"like",emoji:"👍",labelKey:"board.reactions.like",activeColor:"bg-blue-100 dark:bg-blue-900/30 text-blue-600 dark:text-blue-400"},{type:"funny",emoji:"😂",labelKey:"board.reactions.funny",activeColor:"bg-yellow-100 dark:bg-yellow-900/30 text-yellow-600 dark:text-yellow-400"},{type:"agree",emoji:"👌",labelKey:"board.reactions.agree",activeColor:"bg-green-100 dark:bg-green-900/30 text-green-600 dark:text-green-400"},{type:"thanks",emoji:"🙏",labelKey:"board.reactions.thanks",activeColor:"bg-purple-100 dark:bg-purple-900/30 text-purple-600 dark:text-purple-400"},{type:"wow",emoji:"😮",labelKey:"board.reactions.wow",activeColor:"bg-orange-100 dark:bg-orange-900/30 text-orange-600 dark:text-orange-400"}],si=({reactions:e,userReaction:t,postId:r,onReact:n,size:o="md",className:a="",id:s,editorAttrs:l})=>{const f={sm:"px-2 py-1 text-xs gap-1",md:"px-3 py-1.5 text-sm gap-1.5",lg:"px-4 py-2 text-base gap-2"},h={sm:"text-sm",md:"text-base",lg:"text-lg"},c=u=>e.find(p=>p.type===u)?.count??0,i=async u=>{if(n){n(u);return}try{if(!(await fetch(`/api/posts/${r}/reactions`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({type:u}),credentials:"include"})).ok)throw new Error("리액션 처리 실패");window.location.reload()}catch(g){Vg.error("리액션 오류:",g)}};return d.jsx(j,{className:`flex flex-wrap gap-2 ${a}`,id:s,...l,children:Kg.map(u=>{const g=c(u.type),p=t===u.type;return d.jsxs(R,{onClick:()=>i(u.type),className:` inline-flex items-center rounded-full border transition-colors ${f[o]} ${p?`${u.activeColor} border-transparent`:"border-gray-300 dark:border-gray-600 text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700"} - `,title:ai(u.labelKey),children:[d.jsx(te,{className:h[o],children:u.emoji}),d.jsx(te,{children:ai(u.labelKey)}),g>0&&d.jsx(te,{className:"font-medium",children:g})]},u.type)})})},Vg=Object.freeze(Object.defineProperty({__proto__:null,default:si},Symbol.toStringTag,{value:"Module"})),Bn=window.G7Core?.createLogger?.("Comp:RichTextEditor")??{log:(...e)=>console.log("[Comp:RichTextEditor]",...e),warn:(...e)=>console.warn("[Comp:RichTextEditor]",...e),error:(...e)=>console.error("[Comp:RichTextEditor]",...e)},Dt=(e,t)=>window.G7Core?.t?.(e,t)??e,li=({name:e,initialValue:t="",placeholder:r,onChange:n,imageUploadUrl:o="/api/upload/image",minHeight:a="300px",disabled:s=!1,className:l="",id:f,editorAttrs:h})=>{const c=C.useRef(null),[i,u]=C.useState(t),[g,p]=C.useState(!1),[m,x]=C.useState(!1),[k,M]=C.useState("");C.useEffect(()=>{c.current&&!c.current.innerHTML&&(c.current.innerHTML=t)},[t]),C.useEffect(()=>{n&&n(i)},[i,n]);const v=E=>{Bn.log("Apply format:",E)},T=async E=>{const O=new FormData;O.append("file",E);try{const $=await fetch(o,{method:"POST",body:O,credentials:"include"});if(!$.ok)throw new Error("업로드 실패");const P=await $.json();Bn.log("Image uploaded:",P.url)}catch($){Bn.error("이미지 업로드 오류:",$)}},I=E=>{const O=E.target.files?.[0];O&&T(O)},S=()=>{k&&(Bn.log("Insert link:",k),x(!1),M(""))},N=[{format:"bold",icon:"B",title:Dt("editor.toolbar.bold")},{format:"italic",icon:"I",title:Dt("editor.toolbar.italic")},{format:"underline",icon:"U",title:Dt("editor.toolbar.underline")},{format:"strike",icon:"S",title:Dt("editor.toolbar.strike")},{format:"code",icon:"",title:Dt("editor.toolbar.code")}],w=[{format:"h1",label:"H1"},{format:"h2",label:"H2"},{format:"h3",label:"H3"}],y=[{format:"bulletList",icon:"•",title:Dt("editor.toolbar.bullet_list")},{format:"orderedList",icon:"1.",title:Dt("editor.toolbar.ordered_list")},{format:"blockquote",icon:"❝",title:Dt("editor.toolbar.blockquote")}];return d.jsxs(j,{className:`border border-gray-300 dark:border-gray-600 rounded-lg overflow-hidden ${l}`,id:f,...h,children:[d.jsxs(j,{className:"flex flex-wrap items-center gap-1 p-2 bg-gray-50 dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700",children:[d.jsx(j,{className:"flex items-center gap-1 pr-2 border-r border-gray-200 dark:border-gray-700",children:N.map(E=>d.jsx(R,{type:"button",onClick:()=>v(E.format),disabled:s,className:"w-8 h-8 flex items-center justify-center text-sm font-medium text-gray-700 dark:text-gray-300 hover:bg-gray-200 dark:hover:bg-gray-700 rounded disabled:opacity-50",title:E.title,children:E.icon},E.format))}),d.jsx(j,{className:"flex items-center gap-1 pr-2 border-r border-gray-200 dark:border-gray-700",children:w.map(E=>d.jsx(R,{type:"button",onClick:()=>v(E.format),disabled:s,className:"px-2 h-8 text-sm font-medium text-gray-700 dark:text-gray-300 hover:bg-gray-200 dark:hover:bg-gray-700 rounded disabled:opacity-50",children:E.label},E.format))}),d.jsx(j,{className:"flex items-center gap-1 pr-2 border-r border-gray-200 dark:border-gray-700",children:y.map(E=>d.jsx(R,{type:"button",onClick:()=>v(E.format),disabled:s,className:"w-8 h-8 flex items-center justify-center text-sm text-gray-700 dark:text-gray-300 hover:bg-gray-200 dark:hover:bg-gray-700 rounded disabled:opacity-50",title:E.title,children:E.icon},E.format))}),d.jsxs(j,{className:"flex items-center gap-1 pr-2 border-r border-gray-200 dark:border-gray-700",children:[d.jsx(R,{type:"button",onClick:()=>x(!0),disabled:s,className:"w-8 h-8 flex items-center justify-center text-gray-700 dark:text-gray-300 hover:bg-gray-200 dark:hover:bg-gray-700 rounded disabled:opacity-50",title:Dt("editor.toolbar.link"),children:"🔗"}),d.jsxs(Ar,{className:"w-8 h-8 flex items-center justify-center text-gray-700 dark:text-gray-300 hover:bg-gray-200 dark:hover:bg-gray-700 rounded cursor-pointer",children:["🖼",d.jsx(At,{type:"file",accept:"image/*",onChange:I,disabled:s,className:"hidden"})]})]}),d.jsxs(R,{type:"button",onClick:()=>p(!g),disabled:s,className:`px-2 h-8 text-sm text-gray-700 dark:text-gray-300 hover:bg-gray-200 dark:hover:bg-gray-700 rounded disabled:opacity-50 ${g?"bg-gray-200 dark:bg-gray-700":""}`,children:["",Dt("editor.toolbar.code_view")]})]}),g?d.jsx(Co,{value:i,onChange:E=>u(E.target.value),placeholder:r,disabled:s,className:"w-full p-4 bg-gray-900 text-gray-100 font-mono text-sm focus:outline-none resize-none",style:{minHeight:a}}):d.jsx(j,{ref:c,contentEditable:!s,onInput:E=>u(E.currentTarget.innerHTML),className:"p-4 bg-white dark:bg-gray-900 text-gray-900 dark:text-white focus:outline-none prose dark:prose-invert max-w-none",style:{minHeight:a},"data-placeholder":r}),d.jsx(At,{type:"hidden",name:e,value:i}),m&&d.jsx(j,{className:"fixed inset-0 z-50 flex items-center justify-center bg-black/50",children:d.jsxs(j,{className:"bg-white dark:bg-gray-800 rounded-lg p-6 w-96",children:[d.jsx(Tn,{className:"text-lg font-medium text-gray-900 dark:text-white mb-4",children:Dt("editor.link_modal.title")}),d.jsx(At,{type:"url",value:k,onChange:E=>M(E.target.value),placeholder:"https://example.com",className:"w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white mb-4"}),d.jsxs(j,{className:"flex justify-end gap-2",children:[d.jsx(R,{type:"button",onClick:()=>{x(!1),M("")},className:"px-4 py-2 text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg",children:Dt("common.cancel")}),d.jsx(R,{type:"button",onClick:S,className:"px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700",children:Dt("editor.link_modal.insert")})]})]})})]})},Kg=Object.freeze(Object.defineProperty({__proto__:null,default:li},Symbol.toStringTag,{value:"Module"})),ha=({content:e,text:t,isHtml:r=!0,className:n="",purifyConfig:o,id:a,editorAttrs:s})=>{const l=t??e??"";if(!l||l.trim()==="")return null;if(!r){const i=` + `,title:ai(u.labelKey),children:[d.jsx(te,{className:h[o],children:u.emoji}),d.jsx(te,{children:ai(u.labelKey)}),g>0&&d.jsx(te,{className:"font-medium",children:g})]},u.type)})})},Yg=Object.freeze(Object.defineProperty({__proto__:null,default:si},Symbol.toStringTag,{value:"Module"})),Bn=window.G7Core?.createLogger?.("Comp:RichTextEditor")??{log:(...e)=>console.log("[Comp:RichTextEditor]",...e),warn:(...e)=>console.warn("[Comp:RichTextEditor]",...e),error:(...e)=>console.error("[Comp:RichTextEditor]",...e)},Dt=(e,t)=>window.G7Core?.t?.(e,t)??e,li=({name:e,initialValue:t="",placeholder:r,onChange:n,imageUploadUrl:o="/api/upload/image",minHeight:a="300px",disabled:s=!1,className:l="",id:f,editorAttrs:h})=>{const c=C.useRef(null),[i,u]=C.useState(t),[g,p]=C.useState(!1),[m,x]=C.useState(!1),[k,M]=C.useState("");C.useEffect(()=>{c.current&&!c.current.innerHTML&&(c.current.innerHTML=t)},[t]),C.useEffect(()=>{n&&n(i)},[i,n]);const v=E=>{Bn.log("Apply format:",E)},T=async E=>{const O=new FormData;O.append("file",E);try{const $=await fetch(o,{method:"POST",body:O,credentials:"include"});if(!$.ok)throw new Error("업로드 실패");const P=await $.json();Bn.log("Image uploaded:",P.url)}catch($){Bn.error("이미지 업로드 오류:",$)}},I=E=>{const O=E.target.files?.[0];O&&T(O)},S=()=>{k&&(Bn.log("Insert link:",k),x(!1),M(""))},N=[{format:"bold",icon:"B",title:Dt("editor.toolbar.bold")},{format:"italic",icon:"I",title:Dt("editor.toolbar.italic")},{format:"underline",icon:"U",title:Dt("editor.toolbar.underline")},{format:"strike",icon:"S",title:Dt("editor.toolbar.strike")},{format:"code",icon:"",title:Dt("editor.toolbar.code")}],w=[{format:"h1",label:"H1"},{format:"h2",label:"H2"},{format:"h3",label:"H3"}],y=[{format:"bulletList",icon:"•",title:Dt("editor.toolbar.bullet_list")},{format:"orderedList",icon:"1.",title:Dt("editor.toolbar.ordered_list")},{format:"blockquote",icon:"❝",title:Dt("editor.toolbar.blockquote")}];return d.jsxs(j,{className:`border border-gray-300 dark:border-gray-600 rounded-lg overflow-hidden ${l}`,id:f,...h,children:[d.jsxs(j,{className:"flex flex-wrap items-center gap-1 p-2 bg-gray-50 dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700",children:[d.jsx(j,{className:"flex items-center gap-1 pr-2 border-r border-gray-200 dark:border-gray-700",children:N.map(E=>d.jsx(R,{type:"button",onClick:()=>v(E.format),disabled:s,className:"w-8 h-8 flex items-center justify-center text-sm font-medium text-gray-700 dark:text-gray-300 hover:bg-gray-200 dark:hover:bg-gray-700 rounded disabled:opacity-50",title:E.title,children:E.icon},E.format))}),d.jsx(j,{className:"flex items-center gap-1 pr-2 border-r border-gray-200 dark:border-gray-700",children:w.map(E=>d.jsx(R,{type:"button",onClick:()=>v(E.format),disabled:s,className:"px-2 h-8 text-sm font-medium text-gray-700 dark:text-gray-300 hover:bg-gray-200 dark:hover:bg-gray-700 rounded disabled:opacity-50",children:E.label},E.format))}),d.jsx(j,{className:"flex items-center gap-1 pr-2 border-r border-gray-200 dark:border-gray-700",children:y.map(E=>d.jsx(R,{type:"button",onClick:()=>v(E.format),disabled:s,className:"w-8 h-8 flex items-center justify-center text-sm text-gray-700 dark:text-gray-300 hover:bg-gray-200 dark:hover:bg-gray-700 rounded disabled:opacity-50",title:E.title,children:E.icon},E.format))}),d.jsxs(j,{className:"flex items-center gap-1 pr-2 border-r border-gray-200 dark:border-gray-700",children:[d.jsx(R,{type:"button",onClick:()=>x(!0),disabled:s,className:"w-8 h-8 flex items-center justify-center text-gray-700 dark:text-gray-300 hover:bg-gray-200 dark:hover:bg-gray-700 rounded disabled:opacity-50",title:Dt("editor.toolbar.link"),children:"🔗"}),d.jsxs(Ar,{className:"w-8 h-8 flex items-center justify-center text-gray-700 dark:text-gray-300 hover:bg-gray-200 dark:hover:bg-gray-700 rounded cursor-pointer",children:["🖼",d.jsx(At,{type:"file",accept:"image/*",onChange:I,disabled:s,className:"hidden"})]})]}),d.jsxs(R,{type:"button",onClick:()=>p(!g),disabled:s,className:`px-2 h-8 text-sm text-gray-700 dark:text-gray-300 hover:bg-gray-200 dark:hover:bg-gray-700 rounded disabled:opacity-50 ${g?"bg-gray-200 dark:bg-gray-700":""}`,children:["",Dt("editor.toolbar.code_view")]})]}),g?d.jsx(Co,{value:i,onChange:E=>u(E.target.value),placeholder:r,disabled:s,className:"w-full p-4 bg-gray-900 text-gray-100 font-mono text-sm focus:outline-none resize-none",style:{minHeight:a}}):d.jsx(j,{ref:c,contentEditable:!s,onInput:E=>u(E.currentTarget.innerHTML),className:"p-4 bg-white dark:bg-gray-900 text-gray-900 dark:text-white focus:outline-none prose dark:prose-invert max-w-none",style:{minHeight:a},"data-placeholder":r}),d.jsx(At,{type:"hidden",name:e,value:i}),m&&d.jsx(j,{className:"fixed inset-0 z-50 flex items-center justify-center bg-black/50",children:d.jsxs(j,{className:"bg-white dark:bg-gray-800 rounded-lg p-6 w-96",children:[d.jsx(Tn,{className:"text-lg font-medium text-gray-900 dark:text-white mb-4",children:Dt("editor.link_modal.title")}),d.jsx(At,{type:"url",value:k,onChange:E=>M(E.target.value),placeholder:"https://example.com",className:"w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white mb-4"}),d.jsxs(j,{className:"flex justify-end gap-2",children:[d.jsx(R,{type:"button",onClick:()=>{x(!1),M("")},className:"px-4 py-2 text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg",children:Dt("common.cancel")}),d.jsx(R,{type:"button",onClick:S,className:"px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700",children:Dt("editor.link_modal.insert")})]})]})})]})},Xg=Object.freeze(Object.defineProperty({__proto__:null,default:li},Symbol.toStringTag,{value:"Module"})),ha=({content:e,text:t,isHtml:r=!0,className:n="",purifyConfig:o,id:a,editorAttrs:s})=>{const l=t??e??"";if(!l||l.trim()==="")return null;if(!r){const i=` whitespace-pre-wrap text-gray-900 dark:text-gray-100 font-sans @@ -40,7 +40,7 @@ In order to be iterable, non-array objects must have a [Symbol.iterator]() metho [&_tr:hover]:bg-gray-50 dark:[&_tr:hover]:bg-gray-700/50 after:content-[''] after:block after:clear-both ${n} - `.trim().replace(/\s+/g," ");return d.jsx(j,{id:a,className:c,dangerouslySetInnerHTML:{__html:h},...s})},Yg=Object.freeze(Object.defineProperty({__proto__:null,HtmlContent:ha},Symbol.toStringTag,{value:"Module"})),pa=()=>window.G7Core,ma=(e,t)=>pa()?.t?.(e,t)??e,ii=({content:e="",onChange:t,isHtml:r=!1,onIsHtmlChange:n,rows:o=15,placeholder:a="",label:s,showHtmlModeToggle:l=!0,contentClassName:f="",purifyConfig:h,className:c="",name:i="content",htmlFieldName:u="content_mode",readOnly:g=!1,id:p,editorAttrs:m})=>{const[x,k]=C.useState(r),[M,v]=C.useState(e),[T,I]=C.useState(!1);C.useEffect(()=>{k(r)},[r]),C.useEffect(()=>{v(e)},[e]);const S=C.useCallback(y=>{const E=y.target.value;if(v(E),t){const O=pa()?.createChangeEvent?.({value:E,name:i,type:"textarea"})??{target:{name:i,value:E}};t(O)}},[t,i]),N=C.useCallback(y=>{const E=y.target.checked;if(k(E),E||I(!1),n){const O=pa()?.createChangeEvent?.({checked:E,name:u,type:"checkbox"})??{target:{name:u,checked:E}};n(O)}},[n,u]),w=C.useCallback(()=>{I(y=>!y)},[]);return d.jsxs(j,{className:`space-y-2 ${c}`,id:p,...m,children:[d.jsxs(j,{className:"flex items-center justify-between",children:[s&&d.jsx(Ar,{className:"block text-sm font-medium text-gray-500 dark:text-gray-400",children:s}),d.jsxs(j,{className:"flex items-center gap-3",children:[x&&!g&&d.jsx(R,{type:"button",onClick:w,className:`px-3 py-1.5 text-xs font-bold rounded-lg focus:outline-none focus:ring-2 ${T?"text-gray-700 dark:text-gray-200 bg-gray-200 dark:bg-gray-600 border border-gray-300 dark:border-gray-500 hover:bg-gray-300 dark:hover:bg-gray-500 focus:ring-gray-400 dark:focus:ring-gray-500":"text-blue-600 dark:text-blue-400 bg-white dark:bg-gray-700 border border-blue-300 dark:border-blue-600 hover:bg-blue-50 dark:hover:bg-gray-600 focus:ring-blue-500 dark:focus:ring-blue-600"}`,children:ma(T?"common.preview_off":"common.preview")}),l&&d.jsxs(Ar,{className:"flex items-center gap-2 p-2 cursor-pointer",children:[d.jsx(At,{type:"checkbox",name:u,checked:x,onChange:N,disabled:g,className:"w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600"}),d.jsx(j,{className:"text-sm font-medium text-gray-700 dark:text-gray-300",children:ma("common.html_mode")})]})]})]}),!T&&d.jsx(Co,{name:i,value:M,onChange:S,placeholder:a,rows:o,readOnly:g,className:`block w-full rounded-lg border px-3 py-2 text-sm placeholder-gray-400 dark:placeholder-gray-500 focus:outline-none focus:ring-1 disabled:opacity-50 disabled:cursor-not-allowed ${x?"font-mono bg-white dark:bg-gray-800 border-blue-300 dark:border-blue-600 text-gray-800 dark:text-gray-200 focus:border-blue-500 focus:ring-blue-500":"bg-white dark:bg-gray-800 border-gray-300 dark:border-gray-600 text-gray-900 dark:text-white focus:border-blue-500 focus:ring-blue-500"}`}),T&&d.jsx(j,{className:"p-4 bg-gray-50 dark:bg-gray-900 rounded-lg border border-gray-200 dark:border-gray-800 min-h-[400px]",children:d.jsx(ha,{content:M,isHtml:!0,className:f||"prose dark:prose-invert max-w-none text-gray-900 dark:text-gray-100",purifyConfig:h})})]})},Xg=Object.freeze(Object.defineProperty({__proto__:null,HtmlEditor:ii},Symbol.toStringTag,{value:"Module"})),ci=e=>window.G7Core?.t?.(e)??e,ba=({maxHeight:e=500,expandText:t,collapseText:r,className:n="",children:o,id:a,editorAttrs:s})=>{const[l,f]=C.useState(!1),[h,c]=C.useState(!1),i=C.useRef(null),u=C.useCallback(()=>{if(i.current){const x=i.current.scrollHeight;c(x>e)}},[e]);C.useEffect(()=>{u();const x=i.current;if(!x)return;const k=x.querySelectorAll("img");k.forEach(v=>{v.complete||v.addEventListener("load",u)});let M=null;return typeof ResizeObserver<"u"&&(M=new ResizeObserver(u),M.observe(x)),()=>{k.forEach(v=>{v.removeEventListener("load",u)}),M?.disconnect()}},[u,o]);const g=()=>{f(x=>!x)},p=t||ci("sirsoft-ecommerce.shop.product.expand_detail"),m=r||ci("sirsoft-ecommerce.shop.product.collapse_detail");return d.jsxs(j,{className:n,id:a,...s,children:[d.jsxs(j,{className:"relative",children:[d.jsx(j,{ref:i,className:!l&&h?"overflow-hidden transition-[max-height] duration-300 ease-in-out":"transition-[max-height] duration-300 ease-in-out",style:!l&&h?{maxHeight:`${e}px`}:void 0,children:o}),!l&&h&&d.jsx(j,{className:"absolute bottom-0 left-0 right-0 h-24 bg-gradient-to-t from-white dark:from-gray-900 to-transparent pointer-events-none"})]}),h&&d.jsxs(R,{type:"button",onClick:g,className:"flex items-center justify-center w-full py-3.5 bg-white dark:bg-gray-900 border border-gray-200 dark:border-gray-700 hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors cursor-pointer gap-1.5",children:[d.jsx(te,{className:"text-sm font-semibold text-gray-600 dark:text-gray-400",children:l?m:p}),d.jsx(fe,{name:l?"chevron-up":"chevron-down",size:"sm"})]})]})},qg=Object.freeze(Object.defineProperty({__proto__:null,ExpandableContent:ba,default:ba},Symbol.toStringTag,{value:"Module"})),Zg=(e,t)=>window.G7Core?.t?.(e,t)??e,ya=({isOpen:e,onClose:t,title:r,icon:n,iconClassName:o="text-gray-500 dark:text-gray-400",closeOnBackdropClick:a=!0,closeOnOverlayClick:s,showCloseButton:l=!0,closeOnEscape:f=!0,children:h,width:c="500px",className:i="",style:u})=>{const g=s!==void 0?s:a,p=C.useRef(null),m=C.useRef(null);if(C.useEffect(()=>{const k=M=>{M.key==="Escape"&&e&&f&&t()};if(e&&f)return document.addEventListener("keydown",k),()=>document.removeEventListener("keydown",k)},[e,t,f]),C.useEffect(()=>{if(e&&p.current){m.current=document.activeElement;const k=p.current.querySelectorAll('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'),M=k[0],v=k[k.length-1];M?.focus();const T=I=>{I.key==="Tab"&&(I.shiftKey?document.activeElement===M&&(I.preventDefault(),v?.focus()):document.activeElement===v&&(I.preventDefault(),M?.focus()))};return document.addEventListener("keydown",T),()=>{document.removeEventListener("keydown",T),m.current?.focus()}}},[e]),C.useEffect(()=>{if(e)return document.body.style.overflow="hidden",()=>{document.body.style.overflow=""}},[e]),!e)return null;const x=u?.zIndex??50;return d.jsxs(j,{className:"fixed inset-0 flex items-center justify-center",style:{zIndex:x},onClick:g?t:void 0,children:[d.jsx(j,{className:"absolute inset-0 bg-black/50","aria-hidden":"true"}),d.jsxs(j,{ref:p,className:`relative bg-white dark:bg-gray-800 rounded-lg shadow-xl max-h-[90vh] max-w-[calc(100vw-2rem)] overflow-hidden ${i}`,style:{width:c,...u?{...u,zIndex:void 0}:{}},onClick:k=>k.stopPropagation(),role:"dialog","aria-modal":"true","aria-labelledby":r?"modal-title":void 0,children:[(r||n||l)&&d.jsxs(j,{className:"flex items-center justify-between p-4",children:[(r||n)&&d.jsxs(j,{id:"modal-title",className:"flex items-center gap-3 text-lg font-semibold text-gray-900 dark:text-white",children:[n&&d.jsx(fe,{name:n,size:"md",className:o}),r]}),l&&d.jsx(R,{onClick:t,className:"ml-auto text-gray-400 dark:text-gray-500 hover:text-gray-600 dark:hover:text-gray-300 focus:outline-none","aria-label":Zg("common.close_modal"),children:d.jsx("svg",{className:"w-6 h-6",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:d.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M6 18L18 6M6 6l12 12"})})})]}),d.jsx(j,{className:"px-6 py-4 overflow-y-auto max-h-[calc(90vh-140px)]",children:h})]})]})},Qg=Object.freeze(Object.defineProperty({__proto__:null,Modal:ya},Symbol.toStringTag,{value:"Module"})),va=(e,t)=>window.G7Core?.t?.(e,t)??e,wa=({isOpen:e,onClose:t,title:r,message:n,confirmText:o,cancelText:a,onConfirm:s,onCancel:l,confirmButtonVariant:f="primary",cancelButtonVariant:h="secondary",width:c="500px",closeOnOverlay:i=!0,isLoading:u=!1,loadingText:g})=>{const p=o??va("common.confirm"),m=a??va("common.cancel"),x=g??va("common.loading"),k=()=>{s()},M=()=>{u||(l?.(),t())},v=()=>{u||t()},T={primary:"bg-blue-600 text-white hover:bg-blue-700",secondary:"bg-gray-200 dark:bg-gray-700 text-gray-800 dark:text-gray-200 hover:bg-gray-300 dark:hover:bg-gray-600",danger:"bg-red-600 text-white hover:bg-red-700",ghost:"bg-transparent text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700"};return d.jsxs(ya,{isOpen:e,onClose:v,title:r,width:c,children:[d.jsx(j,{className:"py-2",children:typeof n=="string"?d.jsx(Gt,{className:"text-gray-700 dark:text-gray-300",children:n}):n}),d.jsxs(j,{className:"flex justify-end gap-3 mt-6",children:[d.jsx(R,{onClick:M,disabled:u,className:"px-4 py-2 bg-white dark:bg-gray-800 text-gray-700 dark:text-gray-300 border border-gray-300 dark:border-gray-600 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700 disabled:opacity-50 disabled:cursor-not-allowed",children:d.jsx(te,{children:m})}),d.jsxs(R,{onClick:k,disabled:u,className:`flex items-center gap-2 px-4 py-2 rounded-lg disabled:opacity-50 disabled:cursor-not-allowed ${T[f]}`,children:[u&&d.jsx(It,{className:"fa-solid fa-spinner w-4 h-4 animate-spin"}),d.jsx(te,{children:u?x:p})]})]})]})},Jg=Object.freeze(Object.defineProperty({__proto__:null,ConfirmDialog:wa},Symbol.toStringTag,{value:"Module"})),di=C.createContext(null),ui=({value:e,children:t})=>d.jsx(di.Provider,{value:e,children:t}),Rg=()=>{const e=C.useContext(di);if(!e)throw new Error("useFileUploaderContext must be used within FileUploaderProvider");return e};function eh(e,t){return t.forEach((function(r){r&&typeof r!="string"&&!Array.isArray(r)&&Object.keys(r).forEach((function(n){if(n!=="default"&&!(n in e)){var o=Object.getOwnPropertyDescriptor(r,n);Object.defineProperty(e,n,o.get?o:{enumerable:!0,get:function(){return r[n]}})}}))})),Object.freeze(e)}function fi(e,t){return new Promise((function(r,n){let o;return th(e).then((function(a){try{return o=a,r(new Blob([t.slice(0,2),o,t.slice(2)],{type:"image/jpeg"}))}catch(s){return n(s)}}),n)}))}const th=e=>new Promise(((t,r)=>{const n=new FileReader;n.addEventListener("load",(({target:{result:o}})=>{const a=new DataView(o);let s=0;if(a.getUint16(s)!==65496)return r("not a valid JPEG");for(s+=2;;){const l=a.getUint16(s);if(l===65498)break;const f=a.getUint16(s+2);if(l===65505&&a.getUint32(s+4)===1165519206){const h=s+10;let c;switch(a.getUint16(h)){case 18761:c=!0;break;case 19789:c=!1;break;default:return r("TIFF header contains invalid endian")}if(a.getUint16(h+2,c)!==42)return r("TIFF header contains invalid version");const i=a.getUint32(h+4,c),u=h+i+2+12*a.getUint16(h+i,c);for(let g=h+i+2;g>>24&255,l[s+1]=f>>>16&255,l[s+2]=f>>>8&255,l[s+3]=f>>>0&255,new Uint8Array(l.buffer,0,s+4)},n.deflateRaw=function(o,a){a==null&&(a={level:6});var s=new Uint8Array(50+Math.floor(1.1*o.length)),l=n.F.deflateRaw(o,s,l,a.level);return new Uint8Array(s.buffer,0,l)},n.encode=function(o,a){a==null&&(a=!1);var s=0,l=n.bin.writeUint,f=n.bin.writeUshort,h={};for(var c in o){var i=!n._noNeed(c)&&!a,u=o[c],g=n.crc.crc(u,0,u.length);h[c]={cpr:i,usize:u.length,crc:g,file:i?n.deflateRaw(u):u}}for(var c in h)s+=h[c].file.length+30+46+2*n.bin.sizeUTF8(c);s+=22;var p=new Uint8Array(s),m=0,x=[];for(var c in h){var k=h[c];x.push(m),m=n._writeHeader(p,m,c,k,0)}var M=0,v=m;for(var c in h)k=h[c],x.push(m),m=n._writeHeader(p,m,c,k,1,x[M++]);var T=m-v;return l(p,m,101010256),m+=4,f(p,m+=4,M),f(p,m+=2,M),l(p,m+=2,T),l(p,m+=4,v),m+=4,m+=2,p.buffer},n._noNeed=function(o){var a=o.split(".").pop().toLowerCase();return"png,jpg,jpeg,zip".indexOf(a)!=-1},n._writeHeader=function(o,a,s,l,f,h){var c=n.bin.writeUint,i=n.bin.writeUshort,u=l.file;return c(o,a,f==0?67324752:33639248),a+=4,f==1&&(a+=2),i(o,a,20),i(o,a+=2,0),i(o,a+=2,l.cpr?8:0),c(o,a+=2,0),c(o,a+=4,l.crc),c(o,a+=4,u.length),c(o,a+=4,l.usize),i(o,a+=4,n.bin.sizeUTF8(s)),i(o,a+=2,0),a+=2,f==1&&(a+=2,a+=2,c(o,a+=6,h),a+=4),a+=n.bin.writeUTF8(o,a,s),f==0&&(o.set(u,a),a+=u.length),a},n.crc={table:(function(){for(var o=new Uint32Array(256),a=0;a<256;a++){for(var s=a,l=0;l<8;l++)1&s?s=3988292384^s>>>1:s>>>=1;o[a]=s}return o})(),update:function(o,a,s,l){for(var f=0;f>>8;return o},crc:function(o,a,s){return 4294967295^n.crc.update(4294967295,o,a,s)}},n.adler=function(o,a,s){for(var l=1,f=0,h=a,c=a+s;h>8&255},readUint:function(o,a){return 16777216*o[a+3]+(o[a+2]<<16|o[a+1]<<8|o[a])},writeUint:function(o,a,s){o[a]=255&s,o[a+1]=s>>8&255,o[a+2]=s>>16&255,o[a+3]=s>>24&255},readASCII:function(o,a,s){for(var l="",f=0;f>6,o[a+f+1]=128|c>>0&63,f+=2;else if((4294901760&c)==0)o[a+f]=224|c>>12,o[a+f+1]=128|c>>6&63,o[a+f+2]=128|c>>0&63,f+=3;else{if((4292870144&c)!=0)throw"e";o[a+f]=240|c>>18,o[a+f+1]=128|c>>12&63,o[a+f+2]=128|c>>6&63,o[a+f+3]=128|c>>0&63,f+=4}}return f},sizeUTF8:function(o){for(var a=o.length,s=0,l=0;l>>3}var x=h.lits,k=h.strt,M=h.prev,v=0,T=0,I=0,S=0,N=0,w=0;for(m>2&&(k[w=n.F._hash(o,0)]=0),u=0;u14e3||T>26697)&&m-u>100&&(p>>16,$=65535&E;if(E!=0){$=65535&E;var P=c(O=E>>>16,h.of0);h.lhst[257+P]++;var L=c($,h.df0);h.dhst[L]++,S+=h.exb[P]+h.dxb[L],x[v]=O<<23|u-p,x[v+1]=$<<16|P<<8|L,v+=2,p=u+O}else h.lhst[o[u]]++;T++}}for(I==u&&o.length!=0||(p>>3},n.F._bestMatch=function(o,a,s,l,f,h){var c=32767&a,i=s[c],u=c-i+32768&32767;if(i==c||l!=n.F._hash(o,a-u))return 0;for(var g=0,p=0,m=Math.min(32767,a);u<=m&&--h!=0&&i!=c;){if(g==0||o[a+g]==o[a+g-u]){var x=n.F._howLong(o,a,u);if(x>g){if(p=u,(g=x)>=f)break;u+2k&&(k=T,i=v)}}}u+=(c=i)-(i=s[c])+32768&32767}return g<<16|p},n.F._howLong=function(o,a,s){if(o[a]!=o[a-s]||o[a+1]!=o[a+1-s]||o[a+2]!=o[a+2-s])return 0;var l=a,f=Math.min(o.length,a+258);for(a+=3;a>>23,X=b+(8388607&F);b>16,H=B>>8&255,W=255&B;w(i,u=n.F._writeLit(257+H,L,i,u),G-S.of0[H]),u+=S.exb[H],N(i,u=n.F._writeLit(W,U,i,u),Y-S.df0[W]),u+=S.dxb[W],b+=G}}u=n.F._writeLit(256,L,i,u)}return u},n.F._copyExact=function(o,a,s,l,f){var h=f>>>3;return l[h]=s,l[h+1]=s>>>8,l[h+2]=255-l[h],l[h+3]=255-l[h+1],h+=4,l.set(new Uint8Array(o.buffer,a,s),h),f+(s+4<<3)},n.F.getTrees=function(){for(var o=n.F.U,a=n.F._hufTree(o.lhst,o.ltree,15),s=n.F._hufTree(o.dhst,o.dtree,15),l=[],f=n.F._lenCodes(o.ltree,l),h=[],c=n.F._lenCodes(o.dtree,h),i=0;i4&&o.itree[1+(o.ordr[g-1]<<1)]==0;)g--;return[a,s,u,f,c,g,l,h]},n.F.getSecond=function(o){for(var a=[],s=0;s>1)+",");return a},n.F.contSize=function(o,a){for(var s=0,l=0;l15&&(n.F._putsE(s,l,c,i),l+=i)}return l},n.F._lenCodes=function(o,a){for(var s=o.length;s!=2&&o[s-1]==0;)s-=2;for(var l=0;l>>1,138))<11?a.push(17,g-3):a.push(18,g-11),l+=2*g-2}else if(f==i&&h==f&&c==f){for(u=l+5;u+2>>1,6);a.push(16,g-3),l+=2*g-2}else a.push(f,0)}return s>>>1},n.F._hufTree=function(o,a,s){var l=[],f=o.length,h=a.length,c=0;for(c=0;cs&&(n.F.restrictDepth(u,s,v),v=s),c=0;ca;l++){var c=o[l].d;o[l].d=a,h+=f-(1<>>=s-a;h>0;)(c=o[l].d)=0;l--)o[l].d==a&&h<0&&(o[l].d--,h++);h!=0&&console.log("debt left")},n.F._goodIndex=function(o,a){var s=0;return a[16|s]<=o&&(s|=16),a[8|s]<=o&&(s|=8),a[4|s]<=o&&(s|=4),a[2|s]<=o&&(s|=2),a[1|s]<=o&&(s|=1),s},n.F._writeLit=function(o,a,s,l){return n.F._putsF(s,l,a[o<<1]),l+a[1+(o<<1)]},n.F.inflate=function(o,a){var s=Uint8Array;if(o[0]==3&&o[1]==0)return a||new s(0);var l=n.F,f=l._bitsF,h=l._bitsE,c=l._decodeTiny,i=l.makeCodes,u=l.codes2map,g=l._get17,p=l.U,m=a==null;m&&(a=new s(o.length>>>2<<3));for(var x,k,M=0,v=0,T=0,I=0,S=0,N=0,w=0,y=0,E=0;M==0;)if(M=f(o,E,1),v=f(o,E+1,2),E+=3,v!=0){if(m&&(a=n.F._check(a,y+131072)),v==1&&(x=p.flmap,k=p.fdmap,N=511,w=31),v==2){T=h(o,E,5)+257,I=h(o,E+5,5)+1,S=h(o,E+10,4)+4,E+=14;for(var O=0;O<38;O+=2)p.itree[O]=0,p.itree[O+1]=0;var $=1;for(O=0;O$&&($=P)}E+=3*S,i(p.itree,$),u(p.itree,$,p.imap),x=p.lmap,k=p.dmap,E=c(p.imap,(1<<$)-1,T+I,o,E,p.ttree);var L=l._copyOut(p.ttree,0,T,p.ltree);N=(1<>>4;if(!(b>>>8))a[y++]=b;else{if(b==256)break;var z=y+b-254;if(b>264){var F=p.ldef[b-257];z=y+(F>>>3)+h(o,E,7&F),E+=7&F}var G=k[g(o,E)&w];E+=15&G;var X=G>>>4,B=p.ddef[X],Y=(B>>>4)+f(o,E,15&B);for(E+=15&B,m&&(a=n.F._check(a,y+131072));y>>3),W=o[H-4]|o[H-3]<<8;m&&(a=n.F._check(a,y+W)),a.set(new s(o.buffer,o.byteOffset+H,W),y),E=H+W<<3,y+=W}return a.length==y?a:a.slice(0,y)},n.F._check=function(o,a){var s=o.length;if(a<=s)return o;var l=new Uint8Array(Math.max(s<<1,a));return l.set(o,0),l},n.F._decodeTiny=function(o,a,s,l,f,h){for(var c=n.F._bitsE,i=n.F._get17,u=0;u>>4;if(p<=15)h[u]=p,u++;else{var m=0,x=0;p==16?(x=3+c(l,f,2),f+=2,m=h[u-1]):p==17?(x=3+c(l,f,3),f+=3):p==18&&(x=11+c(l,f,7),f+=7);for(var k=u+x;u>>1;hf&&(f=i),h++}for(;h>1,i=o[h+1],u=c<<4|i,g=a-i,p=o[h]<>>15-a]=u,p++},n.F.revCodes=function(o,a){for(var s=n.F.U.rev15,l=15-a,f=0;f>>l}},n.F._putsE=function(o,a,s){s<<=7&a;var l=a>>>3;o[l]|=s,o[l+1]|=s>>>8},n.F._putsF=function(o,a,s){s<<=7&a;var l=a>>>3;o[l]|=s,o[l+1]|=s>>>8,o[l+2]|=s>>>16},n.F._bitsE=function(o,a,s){return(o[a>>>3]|o[1+(a>>>3)]<<8)>>>(7&a)&(1<>>3]|o[1+(a>>>3)]<<8|o[2+(a>>>3)]<<16)>>>(7&a)&(1<>>3]|o[1+(a>>>3)]<<8|o[2+(a>>>3)]<<16)>>>(7&a)},n.F._get25=function(o,a){return(o[a>>>3]|o[1+(a>>>3)]<<8|o[2+(a>>>3)]<<16|o[3+(a>>>3)]<<24)>>>(7&a)},n.F.U=(t=Uint16Array,r=Uint32Array,{next_code:new t(16),bl_count:new t(16),ordr:[16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15],of0:[3,4,5,6,7,8,9,10,11,13,15,17,19,23,27,31,35,43,51,59,67,83,99,115,131,163,195,227,258,999,999,999],exb:[0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0,0,0,0],ldef:new t(32),df0:[1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193,257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577,65535,65535],dxb:[0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13,0,0],ddef:new r(32),flmap:new t(512),fltree:[],fdmap:new t(32),fdtree:[],lmap:new t(32768),ltree:[],ttree:[],dmap:new t(32768),dtree:[],imap:new t(512),itree:[],rev15:new t(32768),lhst:new r(286),dhst:new r(30),ihst:new r(19),lits:new r(15e3),strt:new t(65536),prev:new t(32768)}),(function(){for(var o=n.F.U,a=0;a<32768;a++){var s=a;s=(4278255360&(s=(4042322160&(s=(3435973836&(s=(2863311530&s)>>>1|(1431655765&s)<<1))>>>2|(858993459&s)<<2))>>>4|(252645135&s)<<4))>>>8|(16711935&s)<<8,o.rev15[a]=(s>>>16|s<<16)>>>17}function l(f,h,c){for(;h--!=0;)f.push(0,c)}for(a=0;a<32;a++)o.ldef[a]=o.of0[a]<<3|o.exb[a],o.ddef[a]=o.df0[a]<<4|o.dxb[a];l(o.fltree,144,8),l(o.fltree,112,9),l(o.fltree,24,7),l(o.fltree,8,8),n.F.makeCodes(o.fltree,9),n.F.codes2map(o.fltree,9,o.flmap),n.F.revCodes(o.fltree,9),l(o.fdtree,32,5),n.F.makeCodes(o.fdtree,5),n.F.codes2map(o.fdtree,5,o.fdmap),n.F.revCodes(o.fdtree,5),l(o.itree,19,0),l(o.ltree,286,0),l(o.dtree,30,0),l(o.ttree,320,0)})()})();var nh=eh({__proto__:null,default:Wn},[Wn]);const Xt=(function(){var e={nextZero(c,i){for(;c[i]!=0;)i++;return i},readUshort:(c,i)=>c[i]<<8|c[i+1],writeUshort(c,i,u){c[i]=u>>8&255,c[i+1]=255&u},readUint:(c,i)=>16777216*c[i]+(c[i+1]<<16|c[i+2]<<8|c[i+3]),writeUint(c,i,u){c[i]=u>>24&255,c[i+1]=u>>16&255,c[i+2]=u>>8&255,c[i+3]=255&u},readASCII(c,i,u){let g="";for(let p=0;pc.length<2?`0${c}`:c,readUTF8(c,i,u){let g,p="";for(let m=0;m>3)]>>7-((7&S)<<0)&1);k[y]=F[P],k[y+1]=F[P+1],k[y+2]=F[P+2],k[y+3]=L>2)]>>6-((3&S)<<1)&3),k[y]=F[P],k[y+1]=F[P+1],k[y+2]=F[P+2],k[y+3]=L>1)]>>4-((1&S)<<2)&15),k[y]=F[P],k[y+1]=F[P+1],k[y+2]=F[P+2],k[y+3]=L>>3)]>>>7-(7&b)&1))==255*w?0:255;M[G+b]=z<<24|U<<16|U<<8|U}else if(T==2)for(b=0;b>>2)]>>>6-((3&b)<<1)&3))==85*w?0:255,M[G+b]=z<<24|U<<16|U<<8|U;else if(T==4)for(b=0;b>>1)]>>>4-((1&b)<<2)&15))==17*w?0:255,M[G+b]=z<<24|U<<16|U<<8|U;else if(T==8)for(b=0;b>3,w=Math.ceil(T*S/8),y=new Uint8Array(I*w);let E=0;const O=[0,0,4,0,2,0,1],$=[0,4,0,2,0,1,0],P=[8,8,8,4,4,2,2],L=[8,8,4,4,2,2,1];let U=0;for(;U<7;){const b=P[U],z=L[U];let F=0,G=0,X=O[U];for(;X>3])>>7-(7&be)&1,y[W*w+(Q>>3)]|=_<<7-((7&Q)<<0)),S==2&&(_=(_=M[be>>3])>>6-(7&be)&3,y[W*w+(Q>>2)]|=_<<6-((3&Q)<<1)),S==4&&(_=(_=M[be>>3])>>4-(7&be)&15,y[W*w+(Q>>1)]|=_<<4-((1&Q)<<2)),S>=8){const se=W*w+Q*N;for(let pe=0;pe>3)+pe]}be+=S,Q+=z}H++,W+=b}F*G!=0&&(E+=G*(1+Y)),U+=1}return y})(i,c)),i}function n(c,i){return o(new Uint8Array(c.buffer,2,c.length-6),i)}var o=(function(){const c={H:{}};return c.H.N=function(i,u){const g=Uint8Array;let p,m,x=0,k=0,M=0,v=0,T=0,I=0,S=0,N=0,w=0;if(i[0]==3&&i[1]==0)return u||new g(0);const y=c.H,E=y.b,O=y.e,$=y.R,P=y.n,L=y.A,U=y.Z,_=y.m,b=u==null;for(b&&(u=new g(i.length>>>2<<5));x==0;)if(x=E(i,w,1),k=E(i,w+1,2),w+=3,k!=0){if(b&&(u=c.H.W(u,N+(1<<17))),k==1&&(p=_.J,m=_.h,I=511,S=31),k==2){M=O(i,w,5)+257,v=O(i,w+5,5)+1,T=O(i,w+10,4)+4,w+=14;let F=1;for(var z=0;z<38;z+=2)_.Q[z]=0,_.Q[z+1]=0;for(z=0;zF&&(F=B)}w+=3*T,P(_.Q,F),L(_.Q,F,_.u),p=_.w,m=_.d,w=$(_.u,(1<>>4;if(!(G>>>8))u[N++]=G;else{if(G==256)break;{let X=N+G-254;if(G>264){const Q=_.q[G-257];X=N+(Q>>>3)+O(i,w,7&Q),w+=7&Q}const B=m[U(i,w)&S];w+=15&B;const Y=B>>>4,H=_.c[Y],W=(H>>>4)+E(i,w,15&H);for(w+=15&H;N>>3),G=i[F-4]|i[F-3]<<8;b&&(u=c.H.W(u,N+G)),u.set(new g(i.buffer,i.byteOffset+F,G),N),w=F+G<<3,N+=G}return u.length==N?u:u.slice(0,N)},c.H.W=function(i,u){const g=i.length;if(u<=g)return i;const p=new Uint8Array(g<<1);return p.set(i,0),p},c.H.R=function(i,u,g,p,m,x){const k=c.H.e,M=c.H.Z;let v=0;for(;v>>4;if(I<=15)x[v]=I,v++;else{let S=0,N=0;I==16?(N=3+k(p,m,2),m+=2,S=x[v-1]):I==17?(N=3+k(p,m,3),m+=3):I==18&&(N=11+k(p,m,7),m+=7);const w=v+N;for(;v>>1;for(;xm&&(m=M),x++}for(;x>1,M=i[x+1],v=k<<4|M,T=u-M;let I=i[x]<>>15-u]=v,I++}},c.H.l=function(i,u){const g=c.H.m.r,p=15-u;for(let m=0;m>>p}},c.H.M=function(i,u,g){g<<=7&u;const p=u>>>3;i[p]|=g,i[p+1]|=g>>>8},c.H.I=function(i,u,g){g<<=7&u;const p=u>>>3;i[p]|=g,i[p+1]|=g>>>8,i[p+2]|=g>>>16},c.H.e=function(i,u,g){return(i[u>>>3]|i[1+(u>>>3)]<<8)>>>(7&u)&(1<>>3]|i[1+(u>>>3)]<<8|i[2+(u>>>3)]<<16)>>>(7&u)&(1<>>3]|i[1+(u>>>3)]<<8|i[2+(u>>>3)]<<16)>>>(7&u)},c.H.i=function(i,u){return(i[u>>>3]|i[1+(u>>>3)]<<8|i[2+(u>>>3)]<<16|i[3+(u>>>3)]<<24)>>>(7&u)},c.H.m=(function(){const i=Uint16Array,u=Uint32Array;return{K:new i(16),j:new i(16),X:[16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15],S:[3,4,5,6,7,8,9,10,11,13,15,17,19,23,27,31,35,43,51,59,67,83,99,115,131,163,195,227,258,999,999,999],T:[0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0,0,0,0],q:new i(32),p:[1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193,257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577,65535,65535],z:[0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13,0,0],c:new u(32),J:new i(512),_:[],h:new i(32),$:[],w:new i(32768),C:[],v:[],d:new i(32768),D:[],u:new i(512),Q:[],r:new i(32768),s:new u(286),Y:new u(30),a:new u(19),t:new u(15e3),k:new i(65536),g:new i(32768)}})(),(function(){const i=c.H.m;for(var u=0;u<32768;u++){let p=u;p=(2863311530&p)>>>1|(1431655765&p)<<1,p=(3435973836&p)>>>2|(858993459&p)<<2,p=(4042322160&p)>>>4|(252645135&p)<<4,p=(4278255360&p)>>>8|(16711935&p)<<8,i.r[u]=(p>>>16|p<<16)>>>17}function g(p,m,x){for(;m--!=0;)p.push(0,x)}for(u=0;u<32;u++)i.q[u]=i.S[u]<<3|i.T[u],i.c[u]=i.p[u]<<4|i.z[u];g(i._,144,8),g(i._,112,9),g(i._,24,7),g(i._,8,8),c.H.n(i._,9),c.H.A(i._,9,i.J),c.H.l(i._,9),g(i.$,32,5),c.H.n(i.$,5),c.H.A(i.$,5,i.h),c.H.l(i.$,5),g(i.Q,19,0),g(i.C,286,0),g(i.D,30,0),g(i.v,320,0)})(),c.H.N})();function a(c){return[1,null,3,1,2,null,4][c.ctype]*c.depth}function s(c,i,u,g,p){let m=a(i);const x=Math.ceil(g*m/8);let k,M;m=Math.ceil(m/8);let v=c[u],T=0;if(v>1&&(c[u]=[0,0,1][v-2]),v==3)for(T=m;T>>1)&255;for(let I=0;I>>1);for(;T>>1)}else{for(;T=0&&k>=0?(I=U*i+_<<2,S=(k+U)*p+x+_<<2):(I=(-k+U)*i-x+_<<2,S=U*p+_<<2),M==0)g[S]=c[I],g[S+1]=c[I+1],g[S+2]=c[I+2],g[S+3]=c[I+3];else if(M==1){var N=c[I+3]*.00392156862745098,w=c[I]*N,y=c[I+1]*N,E=c[I+2]*N,O=g[S+3]*(1/255),$=g[S]*O,P=g[S+1]*O,L=g[S+2]*O;const b=1-N,z=N+O*b,F=z==0?0:1/z;g[S+3]=255*z,g[S+0]=(w+$*b)*F,g[S+1]=(y+P*b)*F,g[S+2]=(E+L*b)*F}else if(M==2)N=c[I+3],w=c[I],y=c[I+1],E=c[I+2],O=g[S+3],$=g[S],P=g[S+1],L=g[S+2],N==O&&w==$&&y==P&&E==L?(g[S]=0,g[S+1]=0,g[S+2]=0,g[S+3]=0):(g[S]=w,g[S+1]=y,g[S+2]=E,g[S+3]=N);else if(M==3){if(N=c[I+3],w=c[I],y=c[I+1],E=c[I+2],O=g[S+3],$=g[S],P=g[S+1],L=g[S+2],N==O&&w==$&&y==P&&E==L)continue;if(N<220&&O>20)return!1}return!0}return{decode:function(i){const u=new Uint8Array(i);let g=8;const p=e,m=p.readUshort,x=p.readUint,k={tabs:{},frames:[]},M=new Uint8Array(u.length);let v,T=0,I=0;const S=[137,80,78,71,13,10,26,10];for(var N=0;N<8;N++)if(u[N]!=S[N])throw"The input is not a PNG file!";for(;g>>1:E>>>=1;w[y]=E}return w})(),update(w,y,E,O){for(let $=0;$>>8;return w},crc:(w,y,E)=>4294967295^n.update(4294967295,w,y,E)};function o(w,y,E,O){y[E]+=w[0]*O>>4,y[E+1]+=w[1]*O>>4,y[E+2]+=w[2]*O>>4,y[E+3]+=w[3]*O>>4}function a(w){return Math.max(0,Math.min(255,w))}function s(w,y){const E=w[0]-y[0],O=w[1]-y[1],$=w[2]-y[2],P=w[3]-y[3];return E*E+O*O+$*$+P*P}function l(w,y,E,O,$,P,L){L==null&&(L=1);const U=O.length,_=[];for(var b=0;b>>0&255,W>>>8&255,W>>>16&255,W>>>24&255])}for(b=0;b>2]=z,X[b>>2]=O[z]}}function f(w,y,E,O,$){$==null&&($={});const{crc:P}=n,L=t.writeUint,U=t.writeUshort,_=t.writeASCII;let b=8;const z=w.frames.length>1;let F,G=!1,X=33+(z?20:0);if($.sRGB!=null&&(X+=13),$.pHYs!=null&&(X+=21),$.iCCP!=null&&(F=pako.deflate($.iCCP),X+=21+F.length+4),w.ctype==3){for(var B=w.plte.length,Y=0;Y>>24!=255&&(G=!0);X+=8+3*B+4+(G?8+1*B+4:0)}for(var H=0;H>>8&255,We=ne>>>16&255;W[b+pe+0]=Ae,W[b+pe+1]=ve,W[b+pe+2]=We}if(b+=3*B,L(W,b,P(W,b-3*B-4,3*B+4)),b+=4,G){for(L(W,b,B),b+=4,_(W,b,"tRNS"),b+=4,Y=0;Y>>24&255;b+=B,L(W,b,P(W,b-B-4,B+4)),b+=4}}let be=0;for(H=0;HDe&&(De=Je),qeYe&&(Ye=qe));De==-1&&(_e=xe=De=Ye=0),K&&((1&_e)==1&&_e--,(1&xe)==1&&xe--);const rt=(De-_e+1)*(Ye-xe+1);rt>2,ze>>2);pe.push(ne);const K=new Uint8Array(we.abuf,$e,ze);z&&l(Ae.img,Ae.rect.width,Ae.rect.height,se,K,ne),Ae.img.set(K),$e+=ze}}else for(B=0;Bve&&Oe==ge[H-ve])ne[H]=ne[H-ve];else{let we=be[Oe];if(we==null&&(be[Oe]=we=se.length,se.push(Oe),se.length>=300))break;ne[H]=we}}}const We=se.length;for(We<=256&&b==0&&(G=We<=2?1:We<=4?2:We<=16?4:8,G=Math.max(G,_)),B=0;B>1)]|=ze[oe+Te]<<4-4*(1&Te);else if(G==2)for(Te=0;Te>2)]|=ze[oe+Te]<<6-2*(3&Te);else if(G==1)for(Te=0;Te>3)]|=ze[oe+Te]<<7-1*(7&Te)}Oe=me,F=3,$e=1}else if(W==0&&Q.length==1){me=new Uint8Array(ve*ge*3);const ze=ve*ge;for(H=0;HH&&(H=pe),neW&&(W=ne))}H==-1&&(B=Y=H=W=0),L&&((1&B)==1&&B--,(1&Y)==1&&Y--),P={x:B,y:Y,width:H-B+1,height:W-Y+1};const Q=O[$];Q.rect=P,Q.blend=1,Q.img=new Uint8Array(P.width*P.height*4),O[$-1].dispose==0?(e(b,y,E,Q.img,P.width,P.height,-P.x,-P.y,0),u(G,y,E,Q.img,P)):e(G,y,E,Q.img,P.width,P.height,-P.x,-P.y,0)}function u(w,y,E,O,$){e(w,y,E,O,$.width,$.height,-$.x,-$.y,2)}function g(w,y,E,O,$,P,L){const U=[];let _,b=[0,1,2,3,4];P!=-1?b=[P]:(y*O>5e5||E==1)&&(b=[0]),L&&(_={level:0});const z=nh;for(var F=0;F>1)+256&255;if(P==4)for(_=$;_>1)&255;for(_=$;_>1)&255}if(P==4){for(_=0;_<$;_++)w[U+_]=y[L+_]+256-r(0,y[L+_-O],0)&255;for(_=$;_>2);let z;if(E.length<2e7)for(var F=0;F<_;F+=4)z=k(L,G=E[F]*.00392156862745098,X=E[F+1]*.00392156862745098,B=E[F+2]*.00392156862745098,Y=E[F+3]*.00392156862745098),b[F>>2]=z.ind,$[F>>2]=z.est.rgba;else for(F=0;F<_;F+=4){var G=E[F]*.00392156862745098,X=E[F+1]*(1/255),B=E[F+2]*(1/255),Y=E[F+3]*(1/255);for(z=L;z.left;)z=M(z.est,G,X,B,Y)<=0?z.left:z.right;b[F>>2]=z.ind,$[F>>2]=z.est.rgba}return{abuf:O.buffer,inds:b,plte:U}}function x(w,y,E){E==null&&(E=1e-4);const O=new Uint32Array(w.buffer),$={i0:0,i1:w.length,bst:null,est:null,tdst:0,left:null,right:null};$.bst=I(w,$.i0,$.i1),$.est=S($.bst);const P=[$];for(;P.lengthU&&(U=P[L].est.L,_=L);if(U=z||b.i1<=z){b.est.L=0;continue}const F={i0:b.i0,i1:z,bst:null,est:null,tdst:0,left:null,right:null};F.bst=I(w,F.i0,F.i1),F.est=S(F.bst);const G={i0:z,i1:b.i1,bst:null,est:null,tdst:0,left:null,right:null};for(G.bst={R:[],m:[],N:b.bst.N-F.bst.N},L=0;L<16;L++)G.bst.R[L]=b.bst.R[L]-F.bst.R[L];for(L=0;L<4;L++)G.bst.m[L]=b.bst.m[L]-F.bst.m[L];G.est=S(G.bst),b.left=F,b.right=G,P[_]=F,P.push(G)}for(P.sort(((U,_)=>_.bst.N-U.bst.N)),L=0;L0&&(L=w.right,U=w.left);const _=k(L,y,E,O,$);if(_.tdst<=P*P)return _;const b=k(U,y,E,O,$);return b.tdst<_.tdst?b:_}function M(w,y,E,O,$){const{e:P}=w;return P[0]*y+P[1]*E+P[2]*O+P[3]*$-w.eMq}function v(w,y,E,O,$,P){for(O-=4;EP;)O-=4;if(E>=O)break;const L=y[E>>2];y[E>>2]=y[O>>2],y[O>>2]=L,E+=4,O-=4}for(;T(w,E,$)>P;)E-=4;return E+4}function T(w,y,E){return w[y]*E[0]+w[y+1]*E[1]+w[y+2]*E[2]+w[y+3]*E[3]}function I(w,y,E){const O=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],$=[0,0,0,0],P=E-y>>2;for(let L=y;L>>0}}var N={multVec:(w,y)=>[w[0]*y[0]+w[1]*y[1]+w[2]*y[2]+w[3]*y[3],w[4]*y[0]+w[5]*y[1]+w[6]*y[2]+w[7]*y[3],w[8]*y[0]+w[9]*y[1]+w[10]*y[2]+w[11]*y[3],w[12]*y[0]+w[13]*y[1]+w[14]*y[2]+w[15]*y[3]],dot:(w,y)=>w[0]*y[0]+w[1]*y[1]+w[2]*y[2]+w[3]*y[3],sml:(w,y)=>[w*y[0],w*y[1],w*y[2],w*y[3]]};Xt.encode=function(y,E,O,$,P,L,U){$==null&&($=0),U==null&&(U=!1);const _=c(y,E,O,$,[!1,!1,!1,0,U,!1]);return h(_,-1),f(_,E,O,P,L)},Xt.encodeLL=function(y,E,O,$,P,L,U,_){const b={ctype:0+($==1?0:2)+(P==0?0:4),depth:L,frames:[]},z=($+P)*L,F=z*E;for(let G=0;G>>0),I(1),I(32),S(3),S(f),S(2835),S(2835),N(8),S(16711680),S(65280),S(255),S(4278190080),S(1466527264),(function w(){for(;M0;){for(x=122+M*l,g=0;g>>24,i.setUint32(x+g,p<<8|m),g+=4;M++}T{t(new Blob([r],{type:"image/bmp"}))}))},_dly:9};var kt={CHROME:"CHROME",FIREFOX:"FIREFOX",DESKTOP_SAFARI:"DESKTOP_SAFARI",IE:"IE",IOS:"IOS",ETC:"ETC"},oh={[kt.CHROME]:16384,[kt.FIREFOX]:11180,[kt.DESKTOP_SAFARI]:16384,[kt.IE]:8192,[kt.IOS]:4096,[kt.ETC]:8192};const xa=typeof window<"u",hi=typeof WorkerGlobalScope<"u"&&self instanceof WorkerGlobalScope,Vn=xa&&window.cordova&&window.cordova.require&&window.cordova.require("cordova/modulemapper"),ah=(xa||hi)&&(Vn&&Vn.getOriginalSymbol(window,"File")||typeof File<"u"&&File),pi=(xa||hi)&&(Vn&&Vn.getOriginalSymbol(window,"FileReader")||typeof FileReader<"u"&&FileReader);function ka(e,t,r=Date.now()){return new Promise((n=>{const o=e.split(","),a=o[0].match(/:(.*?);/)[1],s=globalThis.atob(o[1]);let l=s.length;const f=new Uint8Array(l);for(;l--;)f[l]=s.charCodeAt(l);const h=new Blob([f],{type:a});h.name=t,h.lastModified=r,n(h)}))}function mi(e){return new Promise(((t,r)=>{const n=new pi;n.onload=()=>t(n.result),n.onerror=o=>r(o),n.readAsDataURL(e)}))}function bi(e){return new Promise(((t,r)=>{const n=new Image;n.onload=()=>t(n),n.onerror=o=>r(o),n.src=e}))}function wr(){if(wr.cachedResult!==void 0)return wr.cachedResult;let e=kt.ETC;const{userAgent:t}=navigator;return/Chrom(e|ium)/i.test(t)?e=kt.CHROME:/iP(ad|od|hone)/i.test(t)&&/WebKit/i.test(t)?e=kt.IOS:/Safari/i.test(t)?e=kt.DESKTOP_SAFARI:/Firefox/i.test(t)?e=kt.FIREFOX:(/MSIE/i.test(t)||document.documentMode)&&(e=kt.IE),wr.cachedResult=e,wr.cachedResult}function yi(e,t){const r=wr(),n=oh[r];let o=e,a=t,s=o*a;const l=o>a?a/o:o/a;for(;s>n*n;){const f=(n+o)/2,h=(n+a)/2;fgi.toBlob(e,c))).then(function(c){try{return l=c,l.name=r,l.lastModified=n,h.call(this)}catch(i){return s(i)}}.bind(this),s);{let c=function(){return h.call(this)};if(typeof OffscreenCanvas=="function"&&e instanceof OffscreenCanvas)return e.convertToBlob({type:t,quality:o}).then(function(i){try{return l=i,l.name=r,l.lastModified=n,c.call(this)}catch(u){return s(u)}}.bind(this),s);{let i;return i=e.toDataURL(t,o),ka(i,r,n).then(function(u){try{return l=u,c.call(this)}catch(g){return s(g)}}.bind(this),s)}}}function f(){return a(l)}}))}function Qt(e){e.width=0,e.height=0}function Hr(){return new Promise((function(e,t){let r,n,o,a;return Hr.cachedResult!==void 0?e(Hr.cachedResult):ka("data:image/jpeg;base64,/9j/4QAiRXhpZgAATU0AKgAAAAgAAQESAAMAAAABAAYAAAAAAAD/2wCEAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAf/AABEIAAEAAgMBEQACEQEDEQH/xABKAAEAAAAAAAAAAAAAAAAAAAALEAEAAAAAAAAAAAAAAAAAAAAAAQEAAAAAAAAAAAAAAAAAAAAAEQEAAAAAAAAAAAAAAAAAAAAA/9oADAMBAAIRAxEAPwA/8H//2Q==","test.jpg",Date.now()).then((function(s){try{return r=s,Xn(r).then((function(l){try{return n=l[1],qn(n,r.type,r.name,r.lastModified).then((function(f){try{return o=f,Qt(n),Xn(o).then((function(h){try{return a=h[0],Hr.cachedResult=a.width===1&&a.height===2,e(Hr.cachedResult)}catch(c){return t(c)}}),t)}catch(h){return t(h)}}),t)}catch(f){return t(f)}}),t)}catch(l){return t(l)}}),t)}))}function wi(e){return new Promise(((t,r)=>{const n=new pi;n.onload=o=>{const a=new DataView(o.target.result);if(a.getUint16(0,!1)!=65496)return t(-2);const s=a.byteLength;let l=2;for(;lr(o),n.readAsArrayBuffer(e)}))}function xi(e,t){const{width:r}=e,{height:n}=e,{maxWidthOrHeight:o}=t;let a,s=e;return isFinite(o)&&(r>o||n>o)&&([s,a]=Kn(r,n),r>n?(s.width=o,s.height=n/r*o):(s.width=r/n*o,s.height=o),a.drawImage(e,0,0,s.width,s.height),Qt(e)),s}function ki(e,t){const{width:r}=e,{height:n}=e,[o,a]=Kn(r,n);switch(t>4&&t<9?(o.width=n,o.height=r):(o.width=r,o.height=n),t){case 2:a.transform(-1,0,0,1,r,0);break;case 3:a.transform(-1,0,0,-1,r,n);break;case 4:a.transform(1,0,0,-1,0,n);break;case 5:a.transform(0,1,1,0,0,0);break;case 6:a.transform(0,1,-1,0,n,0);break;case 7:a.transform(0,-1,-1,0,n,r);break;case 8:a.transform(0,-1,1,0,0,r)}return a.drawImage(e,0,0,r,n),Qt(e),o}function Ci(e,t,r=0){return new Promise((function(n,o){let a,s,l,f,h,c,i,u,g,p,m,x,k,M,v,T,I,S,N,w;function y(O=5){if(t.signal&&t.signal.aborted)throw t.signal.reason;a+=O,t.onProgress(Math.min(a,100))}function E(O){if(t.signal&&t.signal.aborted)throw t.signal.reason;a=Math.min(Math.max(O,a),100),t.onProgress(a)}return a=r,s=t.maxIteration||10,l=1024*t.maxSizeMB*1024,y(),Xn(e,t).then(function(O){try{return[,f]=O,y(),h=xi(f,t),y(),new Promise((function($,P){var L;if(!(L=t.exifOrientation))return wi(e).then(function(_){try{return L=_,U.call(this)}catch(b){return P(b)}}.bind(this),P);function U(){return $(L)}return U.call(this)})).then(function($){try{return c=$,y(),Hr().then(function(P){try{return i=P?h:ki(h,c),y(),u=t.initialQuality||1,g=t.fileType||e.type,qn(i,g,e.name,e.lastModified,u).then(function(L){try{{let _=function(){if(s--&&(v>l||v>k)){let z,F;return z=w?.95*N.width:N.width,F=w?.95*N.height:N.height,[I,S]=Kn(z,F),S.drawImage(N,0,0,z,F),u*=g==="image/png"?.85:.95,qn(I,g,e.name,e.lastModified,u).then((function(G){try{return T=G,Qt(N),N=I,v=T.size,E(Math.min(99,Math.floor((M-v)/(M-l)*100))),_}catch(X){return o(X)}}),o)}return[1]},b=function(){return Qt(N),Qt(I),Qt(h),Qt(i),Qt(f),E(100),n(T)};if(p=L,y(),m=p.size>l,x=p.size>e.size,!m&&!x)return E(100),n(p);var U;return k=e.size,M=p.size,v=M,N=i,w=!t.alwaysKeepResolution&&m,(U=function(z){for(;z;){if(z.then)return void z.then(U,o);try{if(z.pop){if(z.length)return z.pop()?b.call(this):z;z=_}else z=z.call(this)}catch(F){return o(F)}}}.bind(this))(_)}}catch(_){return o(_)}}.bind(this),o)}catch(L){return o(L)}}.bind(this),o)}catch(P){return o(P)}}.bind(this),o)}catch($){return o($)}}.bind(this),o)}))}const sh=` + `.trim().replace(/\s+/g," ");return d.jsx(j,{id:a,className:c,dangerouslySetInnerHTML:{__html:h},...s})},qg=Object.freeze(Object.defineProperty({__proto__:null,HtmlContent:ha},Symbol.toStringTag,{value:"Module"})),pa=()=>window.G7Core,ma=(e,t)=>pa()?.t?.(e,t)??e,ii=({content:e="",onChange:t,isHtml:r=!1,onIsHtmlChange:n,rows:o=15,placeholder:a="",label:s,showHtmlModeToggle:l=!0,contentClassName:f="",purifyConfig:h,className:c="",name:i="content",htmlFieldName:u="content_mode",readOnly:g=!1,id:p,editorAttrs:m})=>{const[x,k]=C.useState(r),[M,v]=C.useState(e),[T,I]=C.useState(!1);C.useEffect(()=>{k(r)},[r]),C.useEffect(()=>{v(e)},[e]);const S=C.useCallback(y=>{const E=y.target.value;if(v(E),t){const O=pa()?.createChangeEvent?.({value:E,name:i,type:"textarea"})??{target:{name:i,value:E}};t(O)}},[t,i]),N=C.useCallback(y=>{const E=y.target.checked;if(k(E),E||I(!1),n){const O=pa()?.createChangeEvent?.({checked:E,name:u,type:"checkbox"})??{target:{name:u,checked:E}};n(O)}},[n,u]),w=C.useCallback(()=>{I(y=>!y)},[]);return d.jsxs(j,{className:`space-y-2 ${c}`,id:p,...m,children:[d.jsxs(j,{className:"flex items-center justify-between",children:[s&&d.jsx(Ar,{className:"block text-sm font-medium text-gray-500 dark:text-gray-400",children:s}),d.jsxs(j,{className:"flex items-center gap-3",children:[x&&!g&&d.jsx(R,{type:"button",onClick:w,className:`px-3 py-1.5 text-xs font-bold rounded-lg focus:outline-none focus:ring-2 ${T?"text-gray-700 dark:text-gray-200 bg-gray-200 dark:bg-gray-600 border border-gray-300 dark:border-gray-500 hover:bg-gray-300 dark:hover:bg-gray-500 focus:ring-gray-400 dark:focus:ring-gray-500":"text-blue-600 dark:text-blue-400 bg-white dark:bg-gray-700 border border-blue-300 dark:border-blue-600 hover:bg-blue-50 dark:hover:bg-gray-600 focus:ring-blue-500 dark:focus:ring-blue-600"}`,children:ma(T?"common.preview_off":"common.preview")}),l&&d.jsxs(Ar,{className:"flex items-center gap-2 p-2 cursor-pointer",children:[d.jsx(At,{type:"checkbox",name:u,checked:x,onChange:N,disabled:g,className:"w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600"}),d.jsx(j,{className:"text-sm font-medium text-gray-700 dark:text-gray-300",children:ma("common.html_mode")})]})]})]}),!T&&d.jsx(Co,{name:i,value:M,onChange:S,placeholder:a,rows:o,readOnly:g,className:`block w-full rounded-lg border px-3 py-2 text-sm placeholder-gray-400 dark:placeholder-gray-500 focus:outline-none focus:ring-1 disabled:opacity-50 disabled:cursor-not-allowed ${x?"font-mono bg-white dark:bg-gray-800 border-blue-300 dark:border-blue-600 text-gray-800 dark:text-gray-200 focus:border-blue-500 focus:ring-blue-500":"bg-white dark:bg-gray-800 border-gray-300 dark:border-gray-600 text-gray-900 dark:text-white focus:border-blue-500 focus:ring-blue-500"}`}),T&&d.jsx(j,{className:"p-4 bg-gray-50 dark:bg-gray-900 rounded-lg border border-gray-200 dark:border-gray-800 min-h-[400px]",children:d.jsx(ha,{content:M,isHtml:!0,className:f||"prose dark:prose-invert max-w-none text-gray-900 dark:text-gray-100",purifyConfig:h})})]})},Zg=Object.freeze(Object.defineProperty({__proto__:null,HtmlEditor:ii},Symbol.toStringTag,{value:"Module"})),ci=e=>window.G7Core?.t?.(e)??e,ba=({maxHeight:e=500,expandText:t,collapseText:r,className:n="",children:o,id:a,editorAttrs:s})=>{const[l,f]=C.useState(!1),[h,c]=C.useState(!1),i=C.useRef(null),u=C.useCallback(()=>{if(i.current){const x=i.current.scrollHeight;c(x>e)}},[e]);C.useEffect(()=>{u();const x=i.current;if(!x)return;const k=x.querySelectorAll("img");k.forEach(v=>{v.complete||v.addEventListener("load",u)});let M=null;return typeof ResizeObserver<"u"&&(M=new ResizeObserver(u),M.observe(x)),()=>{k.forEach(v=>{v.removeEventListener("load",u)}),M?.disconnect()}},[u,o]);const g=()=>{f(x=>!x)},p=t||ci("sirsoft-ecommerce.shop.product.expand_detail"),m=r||ci("sirsoft-ecommerce.shop.product.collapse_detail");return d.jsxs(j,{className:n,id:a,...s,children:[d.jsxs(j,{className:"relative",children:[d.jsx(j,{ref:i,className:!l&&h?"overflow-hidden transition-[max-height] duration-300 ease-in-out":"transition-[max-height] duration-300 ease-in-out",style:!l&&h?{maxHeight:`${e}px`}:void 0,children:o}),!l&&h&&d.jsx(j,{className:"absolute bottom-0 left-0 right-0 h-24 bg-gradient-to-t from-white dark:from-gray-900 to-transparent pointer-events-none"})]}),h&&d.jsxs(R,{type:"button",onClick:g,className:"flex items-center justify-center w-full py-3.5 bg-white dark:bg-gray-900 border border-gray-200 dark:border-gray-700 hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors cursor-pointer gap-1.5",children:[d.jsx(te,{className:"text-sm font-semibold text-gray-600 dark:text-gray-400",children:l?m:p}),d.jsx(fe,{name:l?"chevron-up":"chevron-down",size:"sm"})]})]})},Qg=Object.freeze(Object.defineProperty({__proto__:null,ExpandableContent:ba,default:ba},Symbol.toStringTag,{value:"Module"})),Jg=(e,t)=>window.G7Core?.t?.(e,t)??e,ya=({isOpen:e,onClose:t,title:r,icon:n,iconClassName:o="text-gray-500 dark:text-gray-400",closeOnBackdropClick:a=!0,closeOnOverlayClick:s,showCloseButton:l=!0,closeOnEscape:f=!0,children:h,width:c="500px",className:i="",style:u})=>{const g=s!==void 0?s:a,p=C.useRef(null),m=C.useRef(null);if(C.useEffect(()=>{const k=M=>{M.key==="Escape"&&e&&f&&t()};if(e&&f)return document.addEventListener("keydown",k),()=>document.removeEventListener("keydown",k)},[e,t,f]),C.useEffect(()=>{if(e&&p.current){m.current=document.activeElement;const k=p.current.querySelectorAll('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'),M=k[0],v=k[k.length-1];M?.focus();const T=I=>{I.key==="Tab"&&(I.shiftKey?document.activeElement===M&&(I.preventDefault(),v?.focus()):document.activeElement===v&&(I.preventDefault(),M?.focus()))};return document.addEventListener("keydown",T),()=>{document.removeEventListener("keydown",T),m.current?.focus()}}},[e]),C.useEffect(()=>{if(e)return document.body.style.overflow="hidden",()=>{document.body.style.overflow=""}},[e]),!e)return null;const x=u?.zIndex??50;return d.jsxs(j,{className:"fixed inset-0 flex items-center justify-center",style:{zIndex:x},onClick:g?t:void 0,children:[d.jsx(j,{className:"absolute inset-0 bg-black/50","aria-hidden":"true"}),d.jsxs(j,{ref:p,className:`relative bg-white dark:bg-gray-800 rounded-lg shadow-xl max-h-[90vh] max-w-[calc(100vw-2rem)] overflow-hidden ${i}`,style:{width:c,...u?{...u,zIndex:void 0}:{}},onClick:k=>k.stopPropagation(),role:"dialog","aria-modal":"true","aria-labelledby":r?"modal-title":void 0,children:[(r||n||l)&&d.jsxs(j,{className:"flex items-center justify-between p-4",children:[(r||n)&&d.jsxs(j,{id:"modal-title",className:"flex items-center gap-3 text-lg font-semibold text-gray-900 dark:text-white",children:[n&&d.jsx(fe,{name:n,size:"md",className:o}),r]}),l&&d.jsx(R,{onClick:t,className:"ml-auto text-gray-400 dark:text-gray-500 hover:text-gray-600 dark:hover:text-gray-300 focus:outline-none","aria-label":Jg("common.close_modal"),children:d.jsx("svg",{className:"w-6 h-6",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:d.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M6 18L18 6M6 6l12 12"})})})]}),d.jsx(j,{className:"px-6 py-4 overflow-y-auto max-h-[calc(90vh-140px)]",children:h})]})]})},Rg=Object.freeze(Object.defineProperty({__proto__:null,Modal:ya},Symbol.toStringTag,{value:"Module"})),va=(e,t)=>window.G7Core?.t?.(e,t)??e,wa=({isOpen:e,onClose:t,title:r,message:n,confirmText:o,cancelText:a,onConfirm:s,onCancel:l,confirmButtonVariant:f="primary",cancelButtonVariant:h="secondary",width:c="500px",closeOnOverlay:i=!0,isLoading:u=!1,loadingText:g})=>{const p=o??va("common.confirm"),m=a??va("common.cancel"),x=g??va("common.loading"),k=()=>{s()},M=()=>{u||(l?.(),t())},v=()=>{u||t()},T={primary:"bg-blue-600 text-white hover:bg-blue-700",secondary:"bg-gray-200 dark:bg-gray-700 text-gray-800 dark:text-gray-200 hover:bg-gray-300 dark:hover:bg-gray-600",danger:"bg-red-600 text-white hover:bg-red-700",ghost:"bg-transparent text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700"};return d.jsxs(ya,{isOpen:e,onClose:v,title:r,width:c,children:[d.jsx(j,{className:"py-2",children:typeof n=="string"?d.jsx(Gt,{className:"text-gray-700 dark:text-gray-300",children:n}):n}),d.jsxs(j,{className:"flex justify-end gap-3 mt-6",children:[d.jsx(R,{onClick:M,disabled:u,className:"px-4 py-2 bg-white dark:bg-gray-800 text-gray-700 dark:text-gray-300 border border-gray-300 dark:border-gray-600 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700 disabled:opacity-50 disabled:cursor-not-allowed",children:d.jsx(te,{children:m})}),d.jsxs(R,{onClick:k,disabled:u,className:`flex items-center gap-2 px-4 py-2 rounded-lg disabled:opacity-50 disabled:cursor-not-allowed ${T[f]}`,children:[u&&d.jsx(It,{className:"fa-solid fa-spinner w-4 h-4 animate-spin"}),d.jsx(te,{children:u?x:p})]})]})]})},eh=Object.freeze(Object.defineProperty({__proto__:null,ConfirmDialog:wa},Symbol.toStringTag,{value:"Module"})),di=C.createContext(null),ui=({value:e,children:t})=>d.jsx(di.Provider,{value:e,children:t}),th=()=>{const e=C.useContext(di);if(!e)throw new Error("useFileUploaderContext must be used within FileUploaderProvider");return e};function rh(e,t){return t.forEach((function(r){r&&typeof r!="string"&&!Array.isArray(r)&&Object.keys(r).forEach((function(n){if(n!=="default"&&!(n in e)){var o=Object.getOwnPropertyDescriptor(r,n);Object.defineProperty(e,n,o.get?o:{enumerable:!0,get:function(){return r[n]}})}}))})),Object.freeze(e)}function fi(e,t){return new Promise((function(r,n){let o;return nh(e).then((function(a){try{return o=a,r(new Blob([t.slice(0,2),o,t.slice(2)],{type:"image/jpeg"}))}catch(s){return n(s)}}),n)}))}const nh=e=>new Promise(((t,r)=>{const n=new FileReader;n.addEventListener("load",(({target:{result:o}})=>{const a=new DataView(o);let s=0;if(a.getUint16(s)!==65496)return r("not a valid JPEG");for(s+=2;;){const l=a.getUint16(s);if(l===65498)break;const f=a.getUint16(s+2);if(l===65505&&a.getUint32(s+4)===1165519206){const h=s+10;let c;switch(a.getUint16(h)){case 18761:c=!0;break;case 19789:c=!1;break;default:return r("TIFF header contains invalid endian")}if(a.getUint16(h+2,c)!==42)return r("TIFF header contains invalid version");const i=a.getUint32(h+4,c),u=h+i+2+12*a.getUint16(h+i,c);for(let g=h+i+2;g>>24&255,l[s+1]=f>>>16&255,l[s+2]=f>>>8&255,l[s+3]=f>>>0&255,new Uint8Array(l.buffer,0,s+4)},n.deflateRaw=function(o,a){a==null&&(a={level:6});var s=new Uint8Array(50+Math.floor(1.1*o.length)),l=n.F.deflateRaw(o,s,l,a.level);return new Uint8Array(s.buffer,0,l)},n.encode=function(o,a){a==null&&(a=!1);var s=0,l=n.bin.writeUint,f=n.bin.writeUshort,h={};for(var c in o){var i=!n._noNeed(c)&&!a,u=o[c],g=n.crc.crc(u,0,u.length);h[c]={cpr:i,usize:u.length,crc:g,file:i?n.deflateRaw(u):u}}for(var c in h)s+=h[c].file.length+30+46+2*n.bin.sizeUTF8(c);s+=22;var p=new Uint8Array(s),m=0,x=[];for(var c in h){var k=h[c];x.push(m),m=n._writeHeader(p,m,c,k,0)}var M=0,v=m;for(var c in h)k=h[c],x.push(m),m=n._writeHeader(p,m,c,k,1,x[M++]);var T=m-v;return l(p,m,101010256),m+=4,f(p,m+=4,M),f(p,m+=2,M),l(p,m+=2,T),l(p,m+=4,v),m+=4,m+=2,p.buffer},n._noNeed=function(o){var a=o.split(".").pop().toLowerCase();return"png,jpg,jpeg,zip".indexOf(a)!=-1},n._writeHeader=function(o,a,s,l,f,h){var c=n.bin.writeUint,i=n.bin.writeUshort,u=l.file;return c(o,a,f==0?67324752:33639248),a+=4,f==1&&(a+=2),i(o,a,20),i(o,a+=2,0),i(o,a+=2,l.cpr?8:0),c(o,a+=2,0),c(o,a+=4,l.crc),c(o,a+=4,u.length),c(o,a+=4,l.usize),i(o,a+=4,n.bin.sizeUTF8(s)),i(o,a+=2,0),a+=2,f==1&&(a+=2,a+=2,c(o,a+=6,h),a+=4),a+=n.bin.writeUTF8(o,a,s),f==0&&(o.set(u,a),a+=u.length),a},n.crc={table:(function(){for(var o=new Uint32Array(256),a=0;a<256;a++){for(var s=a,l=0;l<8;l++)1&s?s=3988292384^s>>>1:s>>>=1;o[a]=s}return o})(),update:function(o,a,s,l){for(var f=0;f>>8;return o},crc:function(o,a,s){return 4294967295^n.crc.update(4294967295,o,a,s)}},n.adler=function(o,a,s){for(var l=1,f=0,h=a,c=a+s;h>8&255},readUint:function(o,a){return 16777216*o[a+3]+(o[a+2]<<16|o[a+1]<<8|o[a])},writeUint:function(o,a,s){o[a]=255&s,o[a+1]=s>>8&255,o[a+2]=s>>16&255,o[a+3]=s>>24&255},readASCII:function(o,a,s){for(var l="",f=0;f>6,o[a+f+1]=128|c>>0&63,f+=2;else if((4294901760&c)==0)o[a+f]=224|c>>12,o[a+f+1]=128|c>>6&63,o[a+f+2]=128|c>>0&63,f+=3;else{if((4292870144&c)!=0)throw"e";o[a+f]=240|c>>18,o[a+f+1]=128|c>>12&63,o[a+f+2]=128|c>>6&63,o[a+f+3]=128|c>>0&63,f+=4}}return f},sizeUTF8:function(o){for(var a=o.length,s=0,l=0;l>>3}var x=h.lits,k=h.strt,M=h.prev,v=0,T=0,I=0,S=0,N=0,w=0;for(m>2&&(k[w=n.F._hash(o,0)]=0),u=0;u14e3||T>26697)&&m-u>100&&(p>>16,$=65535&E;if(E!=0){$=65535&E;var P=c(O=E>>>16,h.of0);h.lhst[257+P]++;var L=c($,h.df0);h.dhst[L]++,S+=h.exb[P]+h.dxb[L],x[v]=O<<23|u-p,x[v+1]=$<<16|P<<8|L,v+=2,p=u+O}else h.lhst[o[u]]++;T++}}for(I==u&&o.length!=0||(p>>3},n.F._bestMatch=function(o,a,s,l,f,h){var c=32767&a,i=s[c],u=c-i+32768&32767;if(i==c||l!=n.F._hash(o,a-u))return 0;for(var g=0,p=0,m=Math.min(32767,a);u<=m&&--h!=0&&i!=c;){if(g==0||o[a+g]==o[a+g-u]){var x=n.F._howLong(o,a,u);if(x>g){if(p=u,(g=x)>=f)break;u+2k&&(k=T,i=v)}}}u+=(c=i)-(i=s[c])+32768&32767}return g<<16|p},n.F._howLong=function(o,a,s){if(o[a]!=o[a-s]||o[a+1]!=o[a+1-s]||o[a+2]!=o[a+2-s])return 0;var l=a,f=Math.min(o.length,a+258);for(a+=3;a>>23,X=b+(8388607&F);b>16,H=B>>8&255,W=255&B;w(i,u=n.F._writeLit(257+H,L,i,u),G-S.of0[H]),u+=S.exb[H],N(i,u=n.F._writeLit(W,U,i,u),Y-S.df0[W]),u+=S.dxb[W],b+=G}}u=n.F._writeLit(256,L,i,u)}return u},n.F._copyExact=function(o,a,s,l,f){var h=f>>>3;return l[h]=s,l[h+1]=s>>>8,l[h+2]=255-l[h],l[h+3]=255-l[h+1],h+=4,l.set(new Uint8Array(o.buffer,a,s),h),f+(s+4<<3)},n.F.getTrees=function(){for(var o=n.F.U,a=n.F._hufTree(o.lhst,o.ltree,15),s=n.F._hufTree(o.dhst,o.dtree,15),l=[],f=n.F._lenCodes(o.ltree,l),h=[],c=n.F._lenCodes(o.dtree,h),i=0;i4&&o.itree[1+(o.ordr[g-1]<<1)]==0;)g--;return[a,s,u,f,c,g,l,h]},n.F.getSecond=function(o){for(var a=[],s=0;s>1)+",");return a},n.F.contSize=function(o,a){for(var s=0,l=0;l15&&(n.F._putsE(s,l,c,i),l+=i)}return l},n.F._lenCodes=function(o,a){for(var s=o.length;s!=2&&o[s-1]==0;)s-=2;for(var l=0;l>>1,138))<11?a.push(17,g-3):a.push(18,g-11),l+=2*g-2}else if(f==i&&h==f&&c==f){for(u=l+5;u+2>>1,6);a.push(16,g-3),l+=2*g-2}else a.push(f,0)}return s>>>1},n.F._hufTree=function(o,a,s){var l=[],f=o.length,h=a.length,c=0;for(c=0;cs&&(n.F.restrictDepth(u,s,v),v=s),c=0;ca;l++){var c=o[l].d;o[l].d=a,h+=f-(1<>>=s-a;h>0;)(c=o[l].d)=0;l--)o[l].d==a&&h<0&&(o[l].d--,h++);h!=0&&console.log("debt left")},n.F._goodIndex=function(o,a){var s=0;return a[16|s]<=o&&(s|=16),a[8|s]<=o&&(s|=8),a[4|s]<=o&&(s|=4),a[2|s]<=o&&(s|=2),a[1|s]<=o&&(s|=1),s},n.F._writeLit=function(o,a,s,l){return n.F._putsF(s,l,a[o<<1]),l+a[1+(o<<1)]},n.F.inflate=function(o,a){var s=Uint8Array;if(o[0]==3&&o[1]==0)return a||new s(0);var l=n.F,f=l._bitsF,h=l._bitsE,c=l._decodeTiny,i=l.makeCodes,u=l.codes2map,g=l._get17,p=l.U,m=a==null;m&&(a=new s(o.length>>>2<<3));for(var x,k,M=0,v=0,T=0,I=0,S=0,N=0,w=0,y=0,E=0;M==0;)if(M=f(o,E,1),v=f(o,E+1,2),E+=3,v!=0){if(m&&(a=n.F._check(a,y+131072)),v==1&&(x=p.flmap,k=p.fdmap,N=511,w=31),v==2){T=h(o,E,5)+257,I=h(o,E+5,5)+1,S=h(o,E+10,4)+4,E+=14;for(var O=0;O<38;O+=2)p.itree[O]=0,p.itree[O+1]=0;var $=1;for(O=0;O$&&($=P)}E+=3*S,i(p.itree,$),u(p.itree,$,p.imap),x=p.lmap,k=p.dmap,E=c(p.imap,(1<<$)-1,T+I,o,E,p.ttree);var L=l._copyOut(p.ttree,0,T,p.ltree);N=(1<>>4;if(!(b>>>8))a[y++]=b;else{if(b==256)break;var z=y+b-254;if(b>264){var F=p.ldef[b-257];z=y+(F>>>3)+h(o,E,7&F),E+=7&F}var G=k[g(o,E)&w];E+=15&G;var X=G>>>4,B=p.ddef[X],Y=(B>>>4)+f(o,E,15&B);for(E+=15&B,m&&(a=n.F._check(a,y+131072));y>>3),W=o[H-4]|o[H-3]<<8;m&&(a=n.F._check(a,y+W)),a.set(new s(o.buffer,o.byteOffset+H,W),y),E=H+W<<3,y+=W}return a.length==y?a:a.slice(0,y)},n.F._check=function(o,a){var s=o.length;if(a<=s)return o;var l=new Uint8Array(Math.max(s<<1,a));return l.set(o,0),l},n.F._decodeTiny=function(o,a,s,l,f,h){for(var c=n.F._bitsE,i=n.F._get17,u=0;u>>4;if(p<=15)h[u]=p,u++;else{var m=0,x=0;p==16?(x=3+c(l,f,2),f+=2,m=h[u-1]):p==17?(x=3+c(l,f,3),f+=3):p==18&&(x=11+c(l,f,7),f+=7);for(var k=u+x;u>>1;hf&&(f=i),h++}for(;h>1,i=o[h+1],u=c<<4|i,g=a-i,p=o[h]<>>15-a]=u,p++},n.F.revCodes=function(o,a){for(var s=n.F.U.rev15,l=15-a,f=0;f>>l}},n.F._putsE=function(o,a,s){s<<=7&a;var l=a>>>3;o[l]|=s,o[l+1]|=s>>>8},n.F._putsF=function(o,a,s){s<<=7&a;var l=a>>>3;o[l]|=s,o[l+1]|=s>>>8,o[l+2]|=s>>>16},n.F._bitsE=function(o,a,s){return(o[a>>>3]|o[1+(a>>>3)]<<8)>>>(7&a)&(1<>>3]|o[1+(a>>>3)]<<8|o[2+(a>>>3)]<<16)>>>(7&a)&(1<>>3]|o[1+(a>>>3)]<<8|o[2+(a>>>3)]<<16)>>>(7&a)},n.F._get25=function(o,a){return(o[a>>>3]|o[1+(a>>>3)]<<8|o[2+(a>>>3)]<<16|o[3+(a>>>3)]<<24)>>>(7&a)},n.F.U=(t=Uint16Array,r=Uint32Array,{next_code:new t(16),bl_count:new t(16),ordr:[16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15],of0:[3,4,5,6,7,8,9,10,11,13,15,17,19,23,27,31,35,43,51,59,67,83,99,115,131,163,195,227,258,999,999,999],exb:[0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0,0,0,0],ldef:new t(32),df0:[1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193,257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577,65535,65535],dxb:[0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13,0,0],ddef:new r(32),flmap:new t(512),fltree:[],fdmap:new t(32),fdtree:[],lmap:new t(32768),ltree:[],ttree:[],dmap:new t(32768),dtree:[],imap:new t(512),itree:[],rev15:new t(32768),lhst:new r(286),dhst:new r(30),ihst:new r(19),lits:new r(15e3),strt:new t(65536),prev:new t(32768)}),(function(){for(var o=n.F.U,a=0;a<32768;a++){var s=a;s=(4278255360&(s=(4042322160&(s=(3435973836&(s=(2863311530&s)>>>1|(1431655765&s)<<1))>>>2|(858993459&s)<<2))>>>4|(252645135&s)<<4))>>>8|(16711935&s)<<8,o.rev15[a]=(s>>>16|s<<16)>>>17}function l(f,h,c){for(;h--!=0;)f.push(0,c)}for(a=0;a<32;a++)o.ldef[a]=o.of0[a]<<3|o.exb[a],o.ddef[a]=o.df0[a]<<4|o.dxb[a];l(o.fltree,144,8),l(o.fltree,112,9),l(o.fltree,24,7),l(o.fltree,8,8),n.F.makeCodes(o.fltree,9),n.F.codes2map(o.fltree,9,o.flmap),n.F.revCodes(o.fltree,9),l(o.fdtree,32,5),n.F.makeCodes(o.fdtree,5),n.F.codes2map(o.fdtree,5,o.fdmap),n.F.revCodes(o.fdtree,5),l(o.itree,19,0),l(o.ltree,286,0),l(o.dtree,30,0),l(o.ttree,320,0)})()})();var ah=rh({__proto__:null,default:Wn},[Wn]);const Xt=(function(){var e={nextZero(c,i){for(;c[i]!=0;)i++;return i},readUshort:(c,i)=>c[i]<<8|c[i+1],writeUshort(c,i,u){c[i]=u>>8&255,c[i+1]=255&u},readUint:(c,i)=>16777216*c[i]+(c[i+1]<<16|c[i+2]<<8|c[i+3]),writeUint(c,i,u){c[i]=u>>24&255,c[i+1]=u>>16&255,c[i+2]=u>>8&255,c[i+3]=255&u},readASCII(c,i,u){let g="";for(let p=0;pc.length<2?`0${c}`:c,readUTF8(c,i,u){let g,p="";for(let m=0;m>3)]>>7-((7&S)<<0)&1);k[y]=F[P],k[y+1]=F[P+1],k[y+2]=F[P+2],k[y+3]=L>2)]>>6-((3&S)<<1)&3),k[y]=F[P],k[y+1]=F[P+1],k[y+2]=F[P+2],k[y+3]=L>1)]>>4-((1&S)<<2)&15),k[y]=F[P],k[y+1]=F[P+1],k[y+2]=F[P+2],k[y+3]=L>>3)]>>>7-(7&b)&1))==255*w?0:255;M[G+b]=z<<24|U<<16|U<<8|U}else if(T==2)for(b=0;b>>2)]>>>6-((3&b)<<1)&3))==85*w?0:255,M[G+b]=z<<24|U<<16|U<<8|U;else if(T==4)for(b=0;b>>1)]>>>4-((1&b)<<2)&15))==17*w?0:255,M[G+b]=z<<24|U<<16|U<<8|U;else if(T==8)for(b=0;b>3,w=Math.ceil(T*S/8),y=new Uint8Array(I*w);let E=0;const O=[0,0,4,0,2,0,1],$=[0,4,0,2,0,1,0],P=[8,8,8,4,4,2,2],L=[8,8,4,4,2,2,1];let U=0;for(;U<7;){const b=P[U],z=L[U];let F=0,G=0,X=O[U];for(;X>3])>>7-(7&be)&1,y[W*w+(Q>>3)]|=_<<7-((7&Q)<<0)),S==2&&(_=(_=M[be>>3])>>6-(7&be)&3,y[W*w+(Q>>2)]|=_<<6-((3&Q)<<1)),S==4&&(_=(_=M[be>>3])>>4-(7&be)&15,y[W*w+(Q>>1)]|=_<<4-((1&Q)<<2)),S>=8){const se=W*w+Q*N;for(let pe=0;pe>3)+pe]}be+=S,Q+=z}H++,W+=b}F*G!=0&&(E+=G*(1+Y)),U+=1}return y})(i,c)),i}function n(c,i){return o(new Uint8Array(c.buffer,2,c.length-6),i)}var o=(function(){const c={H:{}};return c.H.N=function(i,u){const g=Uint8Array;let p,m,x=0,k=0,M=0,v=0,T=0,I=0,S=0,N=0,w=0;if(i[0]==3&&i[1]==0)return u||new g(0);const y=c.H,E=y.b,O=y.e,$=y.R,P=y.n,L=y.A,U=y.Z,_=y.m,b=u==null;for(b&&(u=new g(i.length>>>2<<5));x==0;)if(x=E(i,w,1),k=E(i,w+1,2),w+=3,k!=0){if(b&&(u=c.H.W(u,N+(1<<17))),k==1&&(p=_.J,m=_.h,I=511,S=31),k==2){M=O(i,w,5)+257,v=O(i,w+5,5)+1,T=O(i,w+10,4)+4,w+=14;let F=1;for(var z=0;z<38;z+=2)_.Q[z]=0,_.Q[z+1]=0;for(z=0;zF&&(F=B)}w+=3*T,P(_.Q,F),L(_.Q,F,_.u),p=_.w,m=_.d,w=$(_.u,(1<>>4;if(!(G>>>8))u[N++]=G;else{if(G==256)break;{let X=N+G-254;if(G>264){const Q=_.q[G-257];X=N+(Q>>>3)+O(i,w,7&Q),w+=7&Q}const B=m[U(i,w)&S];w+=15&B;const Y=B>>>4,H=_.c[Y],W=(H>>>4)+E(i,w,15&H);for(w+=15&H;N>>3),G=i[F-4]|i[F-3]<<8;b&&(u=c.H.W(u,N+G)),u.set(new g(i.buffer,i.byteOffset+F,G),N),w=F+G<<3,N+=G}return u.length==N?u:u.slice(0,N)},c.H.W=function(i,u){const g=i.length;if(u<=g)return i;const p=new Uint8Array(g<<1);return p.set(i,0),p},c.H.R=function(i,u,g,p,m,x){const k=c.H.e,M=c.H.Z;let v=0;for(;v>>4;if(I<=15)x[v]=I,v++;else{let S=0,N=0;I==16?(N=3+k(p,m,2),m+=2,S=x[v-1]):I==17?(N=3+k(p,m,3),m+=3):I==18&&(N=11+k(p,m,7),m+=7);const w=v+N;for(;v>>1;for(;xm&&(m=M),x++}for(;x>1,M=i[x+1],v=k<<4|M,T=u-M;let I=i[x]<>>15-u]=v,I++}},c.H.l=function(i,u){const g=c.H.m.r,p=15-u;for(let m=0;m>>p}},c.H.M=function(i,u,g){g<<=7&u;const p=u>>>3;i[p]|=g,i[p+1]|=g>>>8},c.H.I=function(i,u,g){g<<=7&u;const p=u>>>3;i[p]|=g,i[p+1]|=g>>>8,i[p+2]|=g>>>16},c.H.e=function(i,u,g){return(i[u>>>3]|i[1+(u>>>3)]<<8)>>>(7&u)&(1<>>3]|i[1+(u>>>3)]<<8|i[2+(u>>>3)]<<16)>>>(7&u)&(1<>>3]|i[1+(u>>>3)]<<8|i[2+(u>>>3)]<<16)>>>(7&u)},c.H.i=function(i,u){return(i[u>>>3]|i[1+(u>>>3)]<<8|i[2+(u>>>3)]<<16|i[3+(u>>>3)]<<24)>>>(7&u)},c.H.m=(function(){const i=Uint16Array,u=Uint32Array;return{K:new i(16),j:new i(16),X:[16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15],S:[3,4,5,6,7,8,9,10,11,13,15,17,19,23,27,31,35,43,51,59,67,83,99,115,131,163,195,227,258,999,999,999],T:[0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0,0,0,0],q:new i(32),p:[1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193,257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577,65535,65535],z:[0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13,0,0],c:new u(32),J:new i(512),_:[],h:new i(32),$:[],w:new i(32768),C:[],v:[],d:new i(32768),D:[],u:new i(512),Q:[],r:new i(32768),s:new u(286),Y:new u(30),a:new u(19),t:new u(15e3),k:new i(65536),g:new i(32768)}})(),(function(){const i=c.H.m;for(var u=0;u<32768;u++){let p=u;p=(2863311530&p)>>>1|(1431655765&p)<<1,p=(3435973836&p)>>>2|(858993459&p)<<2,p=(4042322160&p)>>>4|(252645135&p)<<4,p=(4278255360&p)>>>8|(16711935&p)<<8,i.r[u]=(p>>>16|p<<16)>>>17}function g(p,m,x){for(;m--!=0;)p.push(0,x)}for(u=0;u<32;u++)i.q[u]=i.S[u]<<3|i.T[u],i.c[u]=i.p[u]<<4|i.z[u];g(i._,144,8),g(i._,112,9),g(i._,24,7),g(i._,8,8),c.H.n(i._,9),c.H.A(i._,9,i.J),c.H.l(i._,9),g(i.$,32,5),c.H.n(i.$,5),c.H.A(i.$,5,i.h),c.H.l(i.$,5),g(i.Q,19,0),g(i.C,286,0),g(i.D,30,0),g(i.v,320,0)})(),c.H.N})();function a(c){return[1,null,3,1,2,null,4][c.ctype]*c.depth}function s(c,i,u,g,p){let m=a(i);const x=Math.ceil(g*m/8);let k,M;m=Math.ceil(m/8);let v=c[u],T=0;if(v>1&&(c[u]=[0,0,1][v-2]),v==3)for(T=m;T>>1)&255;for(let I=0;I>>1);for(;T>>1)}else{for(;T=0&&k>=0?(I=U*i+_<<2,S=(k+U)*p+x+_<<2):(I=(-k+U)*i-x+_<<2,S=U*p+_<<2),M==0)g[S]=c[I],g[S+1]=c[I+1],g[S+2]=c[I+2],g[S+3]=c[I+3];else if(M==1){var N=c[I+3]*.00392156862745098,w=c[I]*N,y=c[I+1]*N,E=c[I+2]*N,O=g[S+3]*(1/255),$=g[S]*O,P=g[S+1]*O,L=g[S+2]*O;const b=1-N,z=N+O*b,F=z==0?0:1/z;g[S+3]=255*z,g[S+0]=(w+$*b)*F,g[S+1]=(y+P*b)*F,g[S+2]=(E+L*b)*F}else if(M==2)N=c[I+3],w=c[I],y=c[I+1],E=c[I+2],O=g[S+3],$=g[S],P=g[S+1],L=g[S+2],N==O&&w==$&&y==P&&E==L?(g[S]=0,g[S+1]=0,g[S+2]=0,g[S+3]=0):(g[S]=w,g[S+1]=y,g[S+2]=E,g[S+3]=N);else if(M==3){if(N=c[I+3],w=c[I],y=c[I+1],E=c[I+2],O=g[S+3],$=g[S],P=g[S+1],L=g[S+2],N==O&&w==$&&y==P&&E==L)continue;if(N<220&&O>20)return!1}return!0}return{decode:function(i){const u=new Uint8Array(i);let g=8;const p=e,m=p.readUshort,x=p.readUint,k={tabs:{},frames:[]},M=new Uint8Array(u.length);let v,T=0,I=0;const S=[137,80,78,71,13,10,26,10];for(var N=0;N<8;N++)if(u[N]!=S[N])throw"The input is not a PNG file!";for(;g>>1:E>>>=1;w[y]=E}return w})(),update(w,y,E,O){for(let $=0;$>>8;return w},crc:(w,y,E)=>4294967295^n.update(4294967295,w,y,E)};function o(w,y,E,O){y[E]+=w[0]*O>>4,y[E+1]+=w[1]*O>>4,y[E+2]+=w[2]*O>>4,y[E+3]+=w[3]*O>>4}function a(w){return Math.max(0,Math.min(255,w))}function s(w,y){const E=w[0]-y[0],O=w[1]-y[1],$=w[2]-y[2],P=w[3]-y[3];return E*E+O*O+$*$+P*P}function l(w,y,E,O,$,P,L){L==null&&(L=1);const U=O.length,_=[];for(var b=0;b>>0&255,W>>>8&255,W>>>16&255,W>>>24&255])}for(b=0;b>2]=z,X[b>>2]=O[z]}}function f(w,y,E,O,$){$==null&&($={});const{crc:P}=n,L=t.writeUint,U=t.writeUshort,_=t.writeASCII;let b=8;const z=w.frames.length>1;let F,G=!1,X=33+(z?20:0);if($.sRGB!=null&&(X+=13),$.pHYs!=null&&(X+=21),$.iCCP!=null&&(F=pako.deflate($.iCCP),X+=21+F.length+4),w.ctype==3){for(var B=w.plte.length,Y=0;Y>>24!=255&&(G=!0);X+=8+3*B+4+(G?8+1*B+4:0)}for(var H=0;H>>8&255,We=ne>>>16&255;W[b+pe+0]=Ae,W[b+pe+1]=ve,W[b+pe+2]=We}if(b+=3*B,L(W,b,P(W,b-3*B-4,3*B+4)),b+=4,G){for(L(W,b,B),b+=4,_(W,b,"tRNS"),b+=4,Y=0;Y>>24&255;b+=B,L(W,b,P(W,b-B-4,B+4)),b+=4}}let be=0;for(H=0;HDe&&(De=Je),qeYe&&(Ye=qe));De==-1&&(_e=xe=De=Ye=0),K&&((1&_e)==1&&_e--,(1&xe)==1&&xe--);const rt=(De-_e+1)*(Ye-xe+1);rt>2,ze>>2);pe.push(ne);const K=new Uint8Array(we.abuf,$e,ze);z&&l(Ae.img,Ae.rect.width,Ae.rect.height,se,K,ne),Ae.img.set(K),$e+=ze}}else for(B=0;Bve&&Oe==ge[H-ve])ne[H]=ne[H-ve];else{let we=be[Oe];if(we==null&&(be[Oe]=we=se.length,se.push(Oe),se.length>=300))break;ne[H]=we}}}const We=se.length;for(We<=256&&b==0&&(G=We<=2?1:We<=4?2:We<=16?4:8,G=Math.max(G,_)),B=0;B>1)]|=ze[oe+Te]<<4-4*(1&Te);else if(G==2)for(Te=0;Te>2)]|=ze[oe+Te]<<6-2*(3&Te);else if(G==1)for(Te=0;Te>3)]|=ze[oe+Te]<<7-1*(7&Te)}Oe=me,F=3,$e=1}else if(W==0&&Q.length==1){me=new Uint8Array(ve*ge*3);const ze=ve*ge;for(H=0;HH&&(H=pe),neW&&(W=ne))}H==-1&&(B=Y=H=W=0),L&&((1&B)==1&&B--,(1&Y)==1&&Y--),P={x:B,y:Y,width:H-B+1,height:W-Y+1};const Q=O[$];Q.rect=P,Q.blend=1,Q.img=new Uint8Array(P.width*P.height*4),O[$-1].dispose==0?(e(b,y,E,Q.img,P.width,P.height,-P.x,-P.y,0),u(G,y,E,Q.img,P)):e(G,y,E,Q.img,P.width,P.height,-P.x,-P.y,0)}function u(w,y,E,O,$){e(w,y,E,O,$.width,$.height,-$.x,-$.y,2)}function g(w,y,E,O,$,P,L){const U=[];let _,b=[0,1,2,3,4];P!=-1?b=[P]:(y*O>5e5||E==1)&&(b=[0]),L&&(_={level:0});const z=ah;for(var F=0;F>1)+256&255;if(P==4)for(_=$;_>1)&255;for(_=$;_>1)&255}if(P==4){for(_=0;_<$;_++)w[U+_]=y[L+_]+256-r(0,y[L+_-O],0)&255;for(_=$;_>2);let z;if(E.length<2e7)for(var F=0;F<_;F+=4)z=k(L,G=E[F]*.00392156862745098,X=E[F+1]*.00392156862745098,B=E[F+2]*.00392156862745098,Y=E[F+3]*.00392156862745098),b[F>>2]=z.ind,$[F>>2]=z.est.rgba;else for(F=0;F<_;F+=4){var G=E[F]*.00392156862745098,X=E[F+1]*(1/255),B=E[F+2]*(1/255),Y=E[F+3]*(1/255);for(z=L;z.left;)z=M(z.est,G,X,B,Y)<=0?z.left:z.right;b[F>>2]=z.ind,$[F>>2]=z.est.rgba}return{abuf:O.buffer,inds:b,plte:U}}function x(w,y,E){E==null&&(E=1e-4);const O=new Uint32Array(w.buffer),$={i0:0,i1:w.length,bst:null,est:null,tdst:0,left:null,right:null};$.bst=I(w,$.i0,$.i1),$.est=S($.bst);const P=[$];for(;P.lengthU&&(U=P[L].est.L,_=L);if(U=z||b.i1<=z){b.est.L=0;continue}const F={i0:b.i0,i1:z,bst:null,est:null,tdst:0,left:null,right:null};F.bst=I(w,F.i0,F.i1),F.est=S(F.bst);const G={i0:z,i1:b.i1,bst:null,est:null,tdst:0,left:null,right:null};for(G.bst={R:[],m:[],N:b.bst.N-F.bst.N},L=0;L<16;L++)G.bst.R[L]=b.bst.R[L]-F.bst.R[L];for(L=0;L<4;L++)G.bst.m[L]=b.bst.m[L]-F.bst.m[L];G.est=S(G.bst),b.left=F,b.right=G,P[_]=F,P.push(G)}for(P.sort(((U,_)=>_.bst.N-U.bst.N)),L=0;L0&&(L=w.right,U=w.left);const _=k(L,y,E,O,$);if(_.tdst<=P*P)return _;const b=k(U,y,E,O,$);return b.tdst<_.tdst?b:_}function M(w,y,E,O,$){const{e:P}=w;return P[0]*y+P[1]*E+P[2]*O+P[3]*$-w.eMq}function v(w,y,E,O,$,P){for(O-=4;EP;)O-=4;if(E>=O)break;const L=y[E>>2];y[E>>2]=y[O>>2],y[O>>2]=L,E+=4,O-=4}for(;T(w,E,$)>P;)E-=4;return E+4}function T(w,y,E){return w[y]*E[0]+w[y+1]*E[1]+w[y+2]*E[2]+w[y+3]*E[3]}function I(w,y,E){const O=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],$=[0,0,0,0],P=E-y>>2;for(let L=y;L>>0}}var N={multVec:(w,y)=>[w[0]*y[0]+w[1]*y[1]+w[2]*y[2]+w[3]*y[3],w[4]*y[0]+w[5]*y[1]+w[6]*y[2]+w[7]*y[3],w[8]*y[0]+w[9]*y[1]+w[10]*y[2]+w[11]*y[3],w[12]*y[0]+w[13]*y[1]+w[14]*y[2]+w[15]*y[3]],dot:(w,y)=>w[0]*y[0]+w[1]*y[1]+w[2]*y[2]+w[3]*y[3],sml:(w,y)=>[w*y[0],w*y[1],w*y[2],w*y[3]]};Xt.encode=function(y,E,O,$,P,L,U){$==null&&($=0),U==null&&(U=!1);const _=c(y,E,O,$,[!1,!1,!1,0,U,!1]);return h(_,-1),f(_,E,O,P,L)},Xt.encodeLL=function(y,E,O,$,P,L,U,_){const b={ctype:0+($==1?0:2)+(P==0?0:4),depth:L,frames:[]},z=($+P)*L,F=z*E;for(let G=0;G>>0),I(1),I(32),S(3),S(f),S(2835),S(2835),N(8),S(16711680),S(65280),S(255),S(4278190080),S(1466527264),(function w(){for(;M0;){for(x=122+M*l,g=0;g>>24,i.setUint32(x+g,p<<8|m),g+=4;M++}T{t(new Blob([r],{type:"image/bmp"}))}))},_dly:9};var kt={CHROME:"CHROME",FIREFOX:"FIREFOX",DESKTOP_SAFARI:"DESKTOP_SAFARI",IE:"IE",IOS:"IOS",ETC:"ETC"},sh={[kt.CHROME]:16384,[kt.FIREFOX]:11180,[kt.DESKTOP_SAFARI]:16384,[kt.IE]:8192,[kt.IOS]:4096,[kt.ETC]:8192};const xa=typeof window<"u",hi=typeof WorkerGlobalScope<"u"&&self instanceof WorkerGlobalScope,Vn=xa&&window.cordova&&window.cordova.require&&window.cordova.require("cordova/modulemapper"),lh=(xa||hi)&&(Vn&&Vn.getOriginalSymbol(window,"File")||typeof File<"u"&&File),pi=(xa||hi)&&(Vn&&Vn.getOriginalSymbol(window,"FileReader")||typeof FileReader<"u"&&FileReader);function ka(e,t,r=Date.now()){return new Promise((n=>{const o=e.split(","),a=o[0].match(/:(.*?);/)[1],s=globalThis.atob(o[1]);let l=s.length;const f=new Uint8Array(l);for(;l--;)f[l]=s.charCodeAt(l);const h=new Blob([f],{type:a});h.name=t,h.lastModified=r,n(h)}))}function mi(e){return new Promise(((t,r)=>{const n=new pi;n.onload=()=>t(n.result),n.onerror=o=>r(o),n.readAsDataURL(e)}))}function bi(e){return new Promise(((t,r)=>{const n=new Image;n.onload=()=>t(n),n.onerror=o=>r(o),n.src=e}))}function wr(){if(wr.cachedResult!==void 0)return wr.cachedResult;let e=kt.ETC;const{userAgent:t}=navigator;return/Chrom(e|ium)/i.test(t)?e=kt.CHROME:/iP(ad|od|hone)/i.test(t)&&/WebKit/i.test(t)?e=kt.IOS:/Safari/i.test(t)?e=kt.DESKTOP_SAFARI:/Firefox/i.test(t)?e=kt.FIREFOX:(/MSIE/i.test(t)||document.documentMode)&&(e=kt.IE),wr.cachedResult=e,wr.cachedResult}function yi(e,t){const r=wr(),n=sh[r];let o=e,a=t,s=o*a;const l=o>a?a/o:o/a;for(;s>n*n;){const f=(n+o)/2,h=(n+a)/2;fgi.toBlob(e,c))).then(function(c){try{return l=c,l.name=r,l.lastModified=n,h.call(this)}catch(i){return s(i)}}.bind(this),s);{let c=function(){return h.call(this)};if(typeof OffscreenCanvas=="function"&&e instanceof OffscreenCanvas)return e.convertToBlob({type:t,quality:o}).then(function(i){try{return l=i,l.name=r,l.lastModified=n,c.call(this)}catch(u){return s(u)}}.bind(this),s);{let i;return i=e.toDataURL(t,o),ka(i,r,n).then(function(u){try{return l=u,c.call(this)}catch(g){return s(g)}}.bind(this),s)}}}function f(){return a(l)}}))}function Qt(e){e.width=0,e.height=0}function Hr(){return new Promise((function(e,t){let r,n,o,a;return Hr.cachedResult!==void 0?e(Hr.cachedResult):ka("data:image/jpeg;base64,/9j/4QAiRXhpZgAATU0AKgAAAAgAAQESAAMAAAABAAYAAAAAAAD/2wCEAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAf/AABEIAAEAAgMBEQACEQEDEQH/xABKAAEAAAAAAAAAAAAAAAAAAAALEAEAAAAAAAAAAAAAAAAAAAAAAQEAAAAAAAAAAAAAAAAAAAAAEQEAAAAAAAAAAAAAAAAAAAAA/9oADAMBAAIRAxEAPwA/8H//2Q==","test.jpg",Date.now()).then((function(s){try{return r=s,Xn(r).then((function(l){try{return n=l[1],qn(n,r.type,r.name,r.lastModified).then((function(f){try{return o=f,Qt(n),Xn(o).then((function(h){try{return a=h[0],Hr.cachedResult=a.width===1&&a.height===2,e(Hr.cachedResult)}catch(c){return t(c)}}),t)}catch(h){return t(h)}}),t)}catch(f){return t(f)}}),t)}catch(l){return t(l)}}),t)}))}function wi(e){return new Promise(((t,r)=>{const n=new pi;n.onload=o=>{const a=new DataView(o.target.result);if(a.getUint16(0,!1)!=65496)return t(-2);const s=a.byteLength;let l=2;for(;lr(o),n.readAsArrayBuffer(e)}))}function xi(e,t){const{width:r}=e,{height:n}=e,{maxWidthOrHeight:o}=t;let a,s=e;return isFinite(o)&&(r>o||n>o)&&([s,a]=Kn(r,n),r>n?(s.width=o,s.height=n/r*o):(s.width=r/n*o,s.height=o),a.drawImage(e,0,0,s.width,s.height),Qt(e)),s}function ki(e,t){const{width:r}=e,{height:n}=e,[o,a]=Kn(r,n);switch(t>4&&t<9?(o.width=n,o.height=r):(o.width=r,o.height=n),t){case 2:a.transform(-1,0,0,1,r,0);break;case 3:a.transform(-1,0,0,-1,r,n);break;case 4:a.transform(1,0,0,-1,0,n);break;case 5:a.transform(0,1,1,0,0,0);break;case 6:a.transform(0,1,-1,0,n,0);break;case 7:a.transform(0,-1,-1,0,n,r);break;case 8:a.transform(0,-1,1,0,0,r)}return a.drawImage(e,0,0,r,n),Qt(e),o}function Ci(e,t,r=0){return new Promise((function(n,o){let a,s,l,f,h,c,i,u,g,p,m,x,k,M,v,T,I,S,N,w;function y(O=5){if(t.signal&&t.signal.aborted)throw t.signal.reason;a+=O,t.onProgress(Math.min(a,100))}function E(O){if(t.signal&&t.signal.aborted)throw t.signal.reason;a=Math.min(Math.max(O,a),100),t.onProgress(a)}return a=r,s=t.maxIteration||10,l=1024*t.maxSizeMB*1024,y(),Xn(e,t).then(function(O){try{return[,f]=O,y(),h=xi(f,t),y(),new Promise((function($,P){var L;if(!(L=t.exifOrientation))return wi(e).then(function(_){try{return L=_,U.call(this)}catch(b){return P(b)}}.bind(this),P);function U(){return $(L)}return U.call(this)})).then(function($){try{return c=$,y(),Hr().then(function(P){try{return i=P?h:ki(h,c),y(),u=t.initialQuality||1,g=t.fileType||e.type,qn(i,g,e.name,e.lastModified,u).then(function(L){try{{let _=function(){if(s--&&(v>l||v>k)){let z,F;return z=w?.95*N.width:N.width,F=w?.95*N.height:N.height,[I,S]=Kn(z,F),S.drawImage(N,0,0,z,F),u*=g==="image/png"?.85:.95,qn(I,g,e.name,e.lastModified,u).then((function(G){try{return T=G,Qt(N),N=I,v=T.size,E(Math.min(99,Math.floor((M-v)/(M-l)*100))),_}catch(X){return o(X)}}),o)}return[1]},b=function(){return Qt(N),Qt(I),Qt(h),Qt(i),Qt(f),E(100),n(T)};if(p=L,y(),m=p.size>l,x=p.size>e.size,!m&&!x)return E(100),n(p);var U;return k=e.size,M=p.size,v=M,N=i,w=!t.alwaysKeepResolution&&m,(U=function(z){for(;z;){if(z.then)return void z.then(U,o);try{if(z.pop){if(z.length)return z.pop()?b.call(this):z;z=_}else z=z.call(this)}catch(F){return o(F)}}}.bind(this))(_)}}catch(_){return o(_)}}.bind(this),o)}catch(L){return o(L)}}.bind(this),o)}catch(P){return o(P)}}.bind(this),o)}catch($){return o($)}}.bind(this),o)}))}const ih=` let scriptImported = false self.addEventListener('message', async (e) => { const { file, id, imageCompressionLibUrl, options } = e.data @@ -59,20 +59,20 @@ self.addEventListener('message', async (e) => { self.postMessage({ error: e.message + '\\n' + e.stack, id }) } }) -`;let Ca;function lh(e,t){return new Promise(((r,n)=>{Ca||(Ca=(function(s){const l=[];return l.push(s),URL.createObjectURL(new Blob(l))})(sh));const o=new Worker(Ca);o.addEventListener("message",(function(s){if(t.signal&&t.signal.aborted)o.terminate();else if(s.data.progress===void 0){if(s.data.error)return n(new Error(s.data.error)),void o.terminate();r(s.data.file),o.terminate()}else t.onProgress(s.data.progress)})),o.addEventListener("error",n),t.signal&&t.signal.addEventListener("abort",(()=>{n(t.signal.reason),o.terminate()})),o.postMessage({file:e,imageCompressionLibUrl:t.libURL,options:{...t,onProgress:void 0,signal:void 0}})}))}function mt(e,t){return new Promise((function(r,n){let o,a,s,l,f,h;if(o={...t},s=0,{onProgress:l}=o,o.maxSizeMB=o.maxSizeMB||Number.POSITIVE_INFINITY,f=typeof o.useWebWorker!="boolean"||o.useWebWorker,delete o.useWebWorker,o.onProgress=g=>{s=g,typeof l=="function"&&l(s)},!(e instanceof Blob||e instanceof ah))return n(new Error("The file given is not an instance of Blob or File"));if(!/^image/.test(e.type))return n(new Error("The file given is not an image"));if(h=typeof WorkerGlobalScope<"u"&&self instanceof WorkerGlobalScope,!f||typeof Worker!="function"||h)return Ci(e,o).then(function(g){try{return a=g,u.call(this)}catch(p){return n(p)}}.bind(this),n);var c=function(){try{return u.call(this)}catch(g){return n(g)}}.bind(this),i=function(g){try{return Ci(e,o).then((function(p){try{return a=p,c()}catch(m){return n(m)}}),n)}catch(p){return n(p)}};try{return o.libURL=o.libURL||"https://cdn.jsdelivr.net/npm/browser-image-compression@2.0.2/dist/browser-image-compression.js",lh(e,o).then((function(g){try{return a=g,c()}catch{return i()}}),i)}catch{i()}function u(){try{a.name=e.name,a.lastModified=e.lastModified}catch{}try{o.preserveExif&&e.type==="image/jpeg"&&(!o.fileType||o.fileType&&o.fileType===e.type)&&(a=fi(e,a))}catch{}return r(a)}}))}mt.getDataUrlFromFile=mi,mt.getFilefromDataUrl=ka,mt.loadImage=bi,mt.drawImageInCanvas=vi,mt.drawFileInCanvas=Xn,mt.canvasToFile=qn,mt.getExifOrientation=wi,mt.handleMaxWidthOrHeight=xi,mt.followExifOrientation=ki,mt.cleanupCanvasMemory=Qt,mt.isAutoOrientationInBrowser=Hr,mt.approximateBelowMaximumCanvasSizeOfBrowser=yi,mt.copyExifWithoutOrientation=fi,mt.getBrowserName=wr,mt.version="2.0.2";const ih=window.G7Core,Zn=e=>{const t=["B","KB","MB","GB"];let r=0,n=e;for(;n>=1024&&r{if(e&&typeof e=="object"){const t=e;if(t.response?.data?.message)return t.response.data.message;if(t.response?.data?.errors){const r=Object.values(t.response.data.errors)[0];if(Array.isArray(r)&&r[0])return r[0]}if(t.message)return t.message}return e instanceof Error?e.message:"업로드 실패"},Si=e=>e?e.startsWith("image/")?"fa-file-image":e.startsWith("video/")?"fa-file-video":e.startsWith("audio/")?"fa-file-audio":e==="application/pdf"?"fa-file-pdf":e.includes("word")||e.includes("document")?"fa-file-word":e.includes("excel")||e.includes("spreadsheet")?"fa-file-excel":e.includes("zip")||e.includes("compressed")?"fa-file-zipper":"fa-file":"fa-file",at=(e,t)=>ih?.t?.(e,t)??e,ch="2.0.2";function dh(){const e=window?.G7Core?.asset;if(typeof e?.template=="function")return e.template("sirsoft-basic",`vendor/browser-image-compression/${ch}/browser-image-compression.js`)}const xr=window.G7Core;function Ei(e){const{attachmentableType:t,attachmentableId:r,collection:n,maxFiles:o,maxSize:a,maxConcurrentUploads:s,accept:l,imageCompression:f,roleIds:h,autoUpload:c,initialFiles:i,endpoints:u,onFilesChange:g,onUploadComplete:p,onUploadError:m,onRemove:x,onReorder:k,uploadTriggerEvent:M,confirmBeforeRemove:v,uploadParams:T}=e,[I,S]=C.useState(i),[N,w]=C.useState([]),[y,E]=C.useState(!1),O=C.useRef(null),$=C.useRef(0),P=C.useRef(new Set),L=C.useRef(new Map),[U,_]=C.useState(!1),[b,z]=C.useState(null),[F,G]=C.useState(!1),X=C.useRef(!1),[B,Y]=C.useState([]),H=C.useRef(i),[W,Q]=C.useState(!1),[be,se]=C.useState(0),pe=C.useRef(0),[ne,Ae]=C.useState(new Map),ve=C.useRef(new Map);C.useEffect(()=>{g?.(N)},[N,g]);const We=C.useMemo(()=>{const q=H.current;return q===i?q:q.length!==i.length?(H.current=i,i):q.length===0&&i.length===0?q:i.some((de,ue)=>de.id!==q[ue]?.id)?(H.current=i,i):q},[i]);C.useEffect(()=>{if(X.current)return;const q=We.filter(ue=>!P.current.has(ue.id)&&(!ue.hash||!P.current.has(ue.hash))),le=new Set(q.map(ue=>ue.id)),de=Array.from(L.current.values()).filter(ue=>!le.has(ue.id)&&!P.current.has(ue.id));S([...q,...de])},[We]);const me=C.useCallback(async q=>{const le={maxSizeMB:f?.maxSizeMB??1,maxWidthOrHeight:f?.maxWidthOrHeight??1920,useWebWorker:!0,libURL:dh()};return await mt(q,le)},[f]),Te=C.useCallback(async q=>{const le=I.length+N.length,de=Math.max(0,o-le),ue=Array.from(q).slice(0,de);if(q.length>de){const ie=Array.from(q).slice(de),Fe=at("attachment.upload_limit_exceeded",{max:o});m?.(Fe,ie[0]??q[0])}for(const ie of ue){if(ie.size>a*1024*1024){m?.(`[${ie.name}] 파일 크기(${Zn(ie.size)})가 최대 허용 크기(${a}MB)를 초과합니다.`,ie);continue}if(l){const Ce="."+ie.name.split(".").pop()?.toLowerCase();if(!l.split(",").map(He=>He.trim().toLowerCase()).some(He=>{if(He.includes("/")){if(He.endsWith("/*")){const je=He.slice(0,-2);return ie.type.toLowerCase().startsWith(je+"/")}return ie.type.toLowerCase()===He}return Ce===He})){m?.(`[${ie.name}] 허용되지 않는 파일 형식입니다. (허용: ${l})`,ie);continue}}const Fe={id:`pending-${Date.now()}-${Math.random().toString(36).substr(2,9)}`,file:ie,original_filename:ie.name,mime_type:ie.type,size:ie.size,size_formatted:Zn(ie.size),status:"pending",progress:0};if(ie.type.startsWith("image/")&&(Fe.preview=URL.createObjectURL(ie)),w(Ce=>[...Ce,Fe]),ie.type.startsWith("image/")&&f){w(Ce=>Ce.map(Ie=>Ie.id===Fe.id?{...Ie,status:"compressing"}:Ie));try{const Ce=await me(ie);w(Ie=>Ie.map(Ne=>Ne.id===Fe.id?{...Ne,compressedBlob:Ce,size:Ce.size,size_formatted:Zn(Ce.size),status:"pending",progress:0}:Ne))}catch{w(Ce=>Ce.map(Ie=>Ie.id===Fe.id?{...Ie,status:"error",error:"이미지 압축 실패"}:Ie))}}}},[I.length,N.length,o,a,l,f,me,m]),ge=C.useCallback(async q=>{const le=new FormData,de=q.compressedBlob?new File([q.compressedBlob],q.original_filename,{type:q.mime_type}):q.file;le.append("file",de),t&&le.append("attachmentable_type",t),r&&le.append("attachmentable_id",String(r)),le.append("collection",n),h.forEach(ue=>{le.append("role_ids[]",String(ue))}),T&&Object.entries(T).forEach(([ue,ie])=>{le.append(ue,ie)});try{const ue=await xr.api.post(u.upload,le,{headers:{"Content-Type":"multipart/form-data"},onUploadProgress:Fe=>{const Ce=Math.round(Fe.loaded*100/(Fe.total??1));w(Ie=>Ie.map(Ne=>Ne.id===q.id?{...Ne,progress:Ce}:Ne))}});return ue?.data?.data??ue?.data}catch(ue){const ie=_i(ue);throw new Error(ie)}},[t,r,n,h,u.upload]),Oe=C.useCallback(async()=>{const q=N.filter(Ce=>Ce.status==="pending"||Ce.status==="error");if(q.length===0)return[];const le=[],de=new Map,ue=[...q],ie=async()=>{for(;ue.length>0&&$.currentIe.map(Ne=>Ne.id===Ce.id?{...Ne,status:"uploading",progress:0}:Ne));try{const Ie=await ge(Ce);if(Ie)le.push(Ie),de.set(String(Ce.id),String(Ie.hash)),L.current.set(Ie.id,Ie),w(Ne=>Ne.filter(He=>He.id!==Ce.id)),S(Ne=>[...Ne,Ie]),Y(Ne=>{if(Ne.length===0)return Ne;const He=String(Ce.id);return Ne.some(_e=>String(_e)===He)?Ne.map(_e=>String(_e)===He?String(Ie.hash):_e):Ne}),Ce.preview&&URL.revokeObjectURL(Ce.preview);else{const Ne=at("attachment.upload_response_invalid");w(He=>He.map(je=>je.id===Ce.id?{...je,status:"error",error:Ne}:je)),m?.(`[${Ce.original_filename}] ${Ne}`,Ce.file)}}catch(Ie){const Ne=Ie instanceof Error?Ie.message:"업로드 실패";w(He=>He.map(je=>je.id===Ce.id?{...je,status:"error",error:Ne}:je)),m?.(`[${Ce.original_filename}] ${Ne}`,Ce.file)}finally{$.current--}}},Fe=Array(Math.min(s,q.length)).fill(null).map(()=>ie());if(await Promise.all(Fe),le.length>0&&(p?.(le),u.reorder&&B.length>0&&k)){const Ce=[...I,...le],Ie=B.map(je=>de.get(String(je))??je),Ne=new Map(Ie.map((je,_e)=>[String(je),_e])),He=[...Ce].sort((je,_e)=>{const xe=Ne.get(String(je.hash))??Ce.length,De=Ne.get(String(_e.hash))??Ce.length;return xe-De}).map((je,_e)=>({...je,sort_order:_e,order:_e+1}));k(He)}return le},[N,s,ge,p,m,B,I,k,u.reorder]);C.useEffect(()=>{if(!M)return;const q=xr.componentEvent.on(M,async()=>{const le=await Oe();return{uploadedAttachments:le,existingFiles:I,allFiles:[...I,...le]}});return()=>{q()}},[M,Oe,I]),C.useEffect(()=>{c&&N.some(q=>q.status==="pending")&&Oe()},[c,N,Oe]);const we=C.useCallback(async q=>{G(!0);try{if(q.id&&u.delete){const de=u.delete.replace(":id",String(q.id));await xr.api.delete(de)}const le=q.hash||q.id;q.id&&(P.current.add(q.id),L.current.delete(q.id)),q.hash&&P.current.add(q.hash),S(de=>de.filter(ue=>(ue.hash||ue.id)!==le)),x?.(q.hash||q.id),_(!1),z(null)}catch{console.error("첨부파일 삭제 실패")}finally{G(!1)}},[x,u.delete]),$e=C.useCallback(async q=>{"hash"in q?v?(z(q),_(!0)):await we(q):(q.preview&&URL.revokeObjectURL(q.preview),w(le=>le.filter(de=>de.id!==q.id)))},[v,we]),ze=C.useCallback(q=>{w(le=>le.map(de=>de.id===q.id?{...de,status:"pending",error:void 0}:de))},[]),K=C.useCallback(async q=>{const{arrayMove:le}=await Promise.resolve().then(()=>V0),{active:de,over:ue}=q;if(!ue||de.id===ue.id)return;const ie=[...I,...N];let Fe;if(B.length===0)Fe=ie;else{const xe=new Map(B.map((De,Ye)=>[String(De),Ye]));Fe=[...ie].sort((De,Ye)=>{const rt="hash"in De?String(De.hash):String(De.id),qe="hash"in Ye?String(Ye.hash):String(Ye.id);return(xe.get(rt)??ie.length)-(xe.get(qe)??ie.length)})}const Ce=Fe.findIndex(xe=>"hash"in xe?xe.hash===de.id:xe.id===de.id),Ie=Fe.findIndex(xe=>"hash"in xe?xe.hash===ue.id:xe.id===ue.id),Ne=le(Fe,Ce,Ie);Y(Ne.map(xe=>"hash"in xe?xe.hash:xe.id));const He=Ne.filter(xe=>"hash"in xe),je=Ne.filter(xe=>!("hash"in xe));X.current=!0,S(He),w(je);const _e=He.map((xe,De)=>({...xe,sort_order:De,order:De+1}));if(He.length>0&&u.reorder){const xe=He.map((De,Ye)=>({id:De.id,order:Ye+1}));try{const De=await xr.api.patch(u.reorder,{order:xe});xr.toast.success(De.message)}catch(De){const Ye=De instanceof Error?De.message:at("attachment.reorder_failed");xr.toast.error(Ye)}}k?.(_e)},[I,N,B,u.reorder,k]),oe=C.useMemo(()=>[...I,...N].filter(q=>"is_image"in q?q.is_image:q.mime_type?.startsWith("image/")??!1),[I,N]),ae=C.useCallback(q=>{const le=oe.findIndex(de=>"hash"in q&&"hash"in de?de.hash===q.hash:"id"in q&&"id"in de&&!("hash"in q)&&!("hash"in de)?de.id===q.id:!1);le!==-1&&(pe.current+=1,se(le),Q(!0))},[oe]);C.useEffect(()=>{let q=!1;return(async()=>{const de=I.filter(ie=>ie.is_image),ue=new Set(de.map(ie=>ie.id));for(const[ie,Fe]of ve.current)ue.has(ie)||(URL.revokeObjectURL(Fe),ve.current.delete(ie));for(const ie of de){if(q)break;if(!ve.current.has(ie.id)){if(ua(ie.download_url)){ve.current.set(ie.id,ie.download_url),Ae(new Map(ve.current));continue}try{const Fe=await xr.api.get(ie.download_url,{responseType:"blob",headers:zn()});if(!q&&Fe){const Ce=URL.createObjectURL(Fe);ve.current.set(ie.id,Ce),Ae(new Map(ve.current))}}catch(Fe){console.error("Failed to load authenticated image:",Fe)}}}})(),()=>{q=!0}},[I]),C.useEffect(()=>()=>{ve.current.forEach(q=>URL.revokeObjectURL(q)),ve.current.clear()},[]);const ce=C.useCallback(async q=>{const{executeImageDownload:le}=await Promise.resolve().then(()=>ni);await le({src:q.download_url,downloadUrl:q.download_url,filename:q.original_filename,downloadRequiresAuth:!0})},[]),ke=C.useMemo(()=>{const q=[...I,...N];if(B.length===0)return q;const le=new Map(B.map((de,ue)=>[String(de),ue]));return[...q].sort((de,ue)=>{const ie="hash"in de?String(de.hash):String(de.id),Fe="hash"in ue?String(ue.hash):String(ue.id),Ce=le.get(ie)??q.length,Ie=le.get(Fe)??q.length;return Ce-Ie})},[I,N,B]),Ee=ke.length,Be=Ee>0,Me=Eew([]),[]),Pe=C.useCallback(()=>N,[N]);return{existingFiles:I,pendingFiles:N,isDragOver:y,setIsDragOver:E,isDeleting:F,totalCount:Ee,hasFiles:Be,canAddMore:Me,allItems:ke,imageFiles:oe,handleFiles:Te,handleRemove:$e,handleRetry:ze,handleUploadAll:Oe,handleOpenGallery:ae,handleDownload:ce,handleDragEnd:K,inputRef:O,galleryOpen:W,setGalleryOpen:Q,galleryStartIndex:be,galleryKeyRef:pe,confirmDialogOpen:U,setConfirmDialogOpen:_,itemToDelete:b,executeRemoveAttachment:we,authenticatedImageUrls:ne,clear:Ke,getPendingFiles:Pe}}const Ti=({isDragOver:e,canAddMore:t,onFiles:r,setIsDragOver:n,inputRef:o,accept:a,maxFiles:s,maxSize:l,children:f})=>d.jsxs(j,{className:` +`;let Ca;function ch(e,t){return new Promise(((r,n)=>{Ca||(Ca=(function(s){const l=[];return l.push(s),URL.createObjectURL(new Blob(l))})(ih));const o=new Worker(Ca);o.addEventListener("message",(function(s){if(t.signal&&t.signal.aborted)o.terminate();else if(s.data.progress===void 0){if(s.data.error)return n(new Error(s.data.error)),void o.terminate();r(s.data.file),o.terminate()}else t.onProgress(s.data.progress)})),o.addEventListener("error",n),t.signal&&t.signal.addEventListener("abort",(()=>{n(t.signal.reason),o.terminate()})),o.postMessage({file:e,imageCompressionLibUrl:t.libURL,options:{...t,onProgress:void 0,signal:void 0}})}))}function mt(e,t){return new Promise((function(r,n){let o,a,s,l,f,h;if(o={...t},s=0,{onProgress:l}=o,o.maxSizeMB=o.maxSizeMB||Number.POSITIVE_INFINITY,f=typeof o.useWebWorker!="boolean"||o.useWebWorker,delete o.useWebWorker,o.onProgress=g=>{s=g,typeof l=="function"&&l(s)},!(e instanceof Blob||e instanceof lh))return n(new Error("The file given is not an instance of Blob or File"));if(!/^image/.test(e.type))return n(new Error("The file given is not an image"));if(h=typeof WorkerGlobalScope<"u"&&self instanceof WorkerGlobalScope,!f||typeof Worker!="function"||h)return Ci(e,o).then(function(g){try{return a=g,u.call(this)}catch(p){return n(p)}}.bind(this),n);var c=function(){try{return u.call(this)}catch(g){return n(g)}}.bind(this),i=function(g){try{return Ci(e,o).then((function(p){try{return a=p,c()}catch(m){return n(m)}}),n)}catch(p){return n(p)}};try{return o.libURL=o.libURL||"https://cdn.jsdelivr.net/npm/browser-image-compression@2.0.2/dist/browser-image-compression.js",ch(e,o).then((function(g){try{return a=g,c()}catch{return i()}}),i)}catch{i()}function u(){try{a.name=e.name,a.lastModified=e.lastModified}catch{}try{o.preserveExif&&e.type==="image/jpeg"&&(!o.fileType||o.fileType&&o.fileType===e.type)&&(a=fi(e,a))}catch{}return r(a)}}))}mt.getDataUrlFromFile=mi,mt.getFilefromDataUrl=ka,mt.loadImage=bi,mt.drawImageInCanvas=vi,mt.drawFileInCanvas=Xn,mt.canvasToFile=qn,mt.getExifOrientation=wi,mt.handleMaxWidthOrHeight=xi,mt.followExifOrientation=ki,mt.cleanupCanvasMemory=Qt,mt.isAutoOrientationInBrowser=Hr,mt.approximateBelowMaximumCanvasSizeOfBrowser=yi,mt.copyExifWithoutOrientation=fi,mt.getBrowserName=wr,mt.version="2.0.2";const dh=window.G7Core,Zn=e=>{const t=["B","KB","MB","GB"];let r=0,n=e;for(;n>=1024&&r{if(e&&typeof e=="object"){const t=e;if(t.response?.data?.message)return t.response.data.message;if(t.response?.data?.errors){const r=Object.values(t.response.data.errors)[0];if(Array.isArray(r)&&r[0])return r[0]}if(t.message)return t.message}return e instanceof Error?e.message:"업로드 실패"},Si=e=>e?e.startsWith("image/")?"fa-file-image":e.startsWith("video/")?"fa-file-video":e.startsWith("audio/")?"fa-file-audio":e==="application/pdf"?"fa-file-pdf":e.includes("word")||e.includes("document")?"fa-file-word":e.includes("excel")||e.includes("spreadsheet")?"fa-file-excel":e.includes("zip")||e.includes("compressed")?"fa-file-zipper":"fa-file":"fa-file",at=(e,t)=>dh?.t?.(e,t)??e,uh="2.0.2";function fh(){const e=window?.G7Core?.asset;if(typeof e?.template=="function")return e.template("sirsoft-basic",`vendor/browser-image-compression/${uh}/browser-image-compression.js`)}const xr=window.G7Core;function Ei(e){const{attachmentableType:t,attachmentableId:r,collection:n,maxFiles:o,maxSize:a,maxConcurrentUploads:s,accept:l,imageCompression:f,roleIds:h,autoUpload:c,initialFiles:i,endpoints:u,onFilesChange:g,onUploadComplete:p,onUploadError:m,onRemove:x,onReorder:k,uploadTriggerEvent:M,confirmBeforeRemove:v,uploadParams:T}=e,[I,S]=C.useState(i),[N,w]=C.useState([]),[y,E]=C.useState(!1),O=C.useRef(null),$=C.useRef(0),P=C.useRef(new Set),L=C.useRef(new Map),[U,_]=C.useState(!1),[b,z]=C.useState(null),[F,G]=C.useState(!1),X=C.useRef(!1),[B,Y]=C.useState([]),H=C.useRef(i),[W,Q]=C.useState(!1),[be,se]=C.useState(0),pe=C.useRef(0),[ne,Ae]=C.useState(new Map),ve=C.useRef(new Map);C.useEffect(()=>{g?.(N)},[N,g]);const We=C.useMemo(()=>{const q=H.current;return q===i?q:q.length!==i.length?(H.current=i,i):q.length===0&&i.length===0?q:i.some((de,ue)=>de.id!==q[ue]?.id)?(H.current=i,i):q},[i]);C.useEffect(()=>{if(X.current)return;const q=We.filter(ue=>!P.current.has(ue.id)&&(!ue.hash||!P.current.has(ue.hash))),le=new Set(q.map(ue=>ue.id)),de=Array.from(L.current.values()).filter(ue=>!le.has(ue.id)&&!P.current.has(ue.id));S([...q,...de])},[We]);const me=C.useCallback(async q=>{const le={maxSizeMB:f?.maxSizeMB??1,maxWidthOrHeight:f?.maxWidthOrHeight??1920,useWebWorker:!0,libURL:fh()};return await mt(q,le)},[f]),Te=C.useCallback(async q=>{const le=I.length+N.length,de=Math.max(0,o-le),ue=Array.from(q).slice(0,de);if(q.length>de){const ie=Array.from(q).slice(de),Fe=at("attachment.upload_limit_exceeded",{max:o});m?.(Fe,ie[0]??q[0])}for(const ie of ue){if(ie.size>a*1024*1024){m?.(`[${ie.name}] 파일 크기(${Zn(ie.size)})가 최대 허용 크기(${a}MB)를 초과합니다.`,ie);continue}if(l){const Ce="."+ie.name.split(".").pop()?.toLowerCase();if(!l.split(",").map(He=>He.trim().toLowerCase()).some(He=>{if(He.includes("/")){if(He.endsWith("/*")){const je=He.slice(0,-2);return ie.type.toLowerCase().startsWith(je+"/")}return ie.type.toLowerCase()===He}return Ce===He})){m?.(`[${ie.name}] 허용되지 않는 파일 형식입니다. (허용: ${l})`,ie);continue}}const Fe={id:`pending-${Date.now()}-${Math.random().toString(36).substr(2,9)}`,file:ie,original_filename:ie.name,mime_type:ie.type,size:ie.size,size_formatted:Zn(ie.size),status:"pending",progress:0};if(ie.type.startsWith("image/")&&(Fe.preview=URL.createObjectURL(ie)),w(Ce=>[...Ce,Fe]),ie.type.startsWith("image/")&&f){w(Ce=>Ce.map(Ie=>Ie.id===Fe.id?{...Ie,status:"compressing"}:Ie));try{const Ce=await me(ie);w(Ie=>Ie.map(Ne=>Ne.id===Fe.id?{...Ne,compressedBlob:Ce,size:Ce.size,size_formatted:Zn(Ce.size),status:"pending",progress:0}:Ne))}catch{w(Ce=>Ce.map(Ie=>Ie.id===Fe.id?{...Ie,status:"error",error:"이미지 압축 실패"}:Ie))}}}},[I.length,N.length,o,a,l,f,me,m]),ge=C.useCallback(async q=>{const le=new FormData,de=q.compressedBlob?new File([q.compressedBlob],q.original_filename,{type:q.mime_type}):q.file;le.append("file",de),t&&le.append("attachmentable_type",t),r&&le.append("attachmentable_id",String(r)),le.append("collection",n),h.forEach(ue=>{le.append("role_ids[]",String(ue))}),T&&Object.entries(T).forEach(([ue,ie])=>{le.append(ue,ie)});try{const ue=await xr.api.post(u.upload,le,{headers:{"Content-Type":"multipart/form-data"},onUploadProgress:Fe=>{const Ce=Math.round(Fe.loaded*100/(Fe.total??1));w(Ie=>Ie.map(Ne=>Ne.id===q.id?{...Ne,progress:Ce}:Ne))}});return ue?.data?.data??ue?.data}catch(ue){const ie=_i(ue);throw new Error(ie)}},[t,r,n,h,u.upload]),Oe=C.useCallback(async()=>{const q=N.filter(Ce=>Ce.status==="pending"||Ce.status==="error");if(q.length===0)return[];const le=[],de=new Map,ue=[...q],ie=async()=>{for(;ue.length>0&&$.currentIe.map(Ne=>Ne.id===Ce.id?{...Ne,status:"uploading",progress:0}:Ne));try{const Ie=await ge(Ce);if(Ie)le.push(Ie),de.set(String(Ce.id),String(Ie.hash)),L.current.set(Ie.id,Ie),w(Ne=>Ne.filter(He=>He.id!==Ce.id)),S(Ne=>[...Ne,Ie]),Y(Ne=>{if(Ne.length===0)return Ne;const He=String(Ce.id);return Ne.some(_e=>String(_e)===He)?Ne.map(_e=>String(_e)===He?String(Ie.hash):_e):Ne}),Ce.preview&&URL.revokeObjectURL(Ce.preview);else{const Ne=at("attachment.upload_response_invalid");w(He=>He.map(je=>je.id===Ce.id?{...je,status:"error",error:Ne}:je)),m?.(`[${Ce.original_filename}] ${Ne}`,Ce.file)}}catch(Ie){const Ne=Ie instanceof Error?Ie.message:"업로드 실패";w(He=>He.map(je=>je.id===Ce.id?{...je,status:"error",error:Ne}:je)),m?.(`[${Ce.original_filename}] ${Ne}`,Ce.file)}finally{$.current--}}},Fe=Array(Math.min(s,q.length)).fill(null).map(()=>ie());if(await Promise.all(Fe),le.length>0&&(p?.(le),u.reorder&&B.length>0&&k)){const Ce=[...I,...le],Ie=B.map(je=>de.get(String(je))??je),Ne=new Map(Ie.map((je,_e)=>[String(je),_e])),He=[...Ce].sort((je,_e)=>{const xe=Ne.get(String(je.hash))??Ce.length,De=Ne.get(String(_e.hash))??Ce.length;return xe-De}).map((je,_e)=>({...je,sort_order:_e,order:_e+1}));k(He)}return le},[N,s,ge,p,m,B,I,k,u.reorder]);C.useEffect(()=>{if(!M)return;const q=xr.componentEvent.on(M,async()=>{const le=await Oe();return{uploadedAttachments:le,existingFiles:I,allFiles:[...I,...le]}});return()=>{q()}},[M,Oe,I]),C.useEffect(()=>{c&&N.some(q=>q.status==="pending")&&Oe()},[c,N,Oe]);const we=C.useCallback(async q=>{G(!0);try{if(q.id&&u.delete){const de=u.delete.replace(":id",String(q.id));await xr.api.delete(de)}const le=q.hash||q.id;q.id&&(P.current.add(q.id),L.current.delete(q.id)),q.hash&&P.current.add(q.hash),S(de=>de.filter(ue=>(ue.hash||ue.id)!==le)),x?.(q.hash||q.id),_(!1),z(null)}catch{console.error("첨부파일 삭제 실패")}finally{G(!1)}},[x,u.delete]),$e=C.useCallback(async q=>{"hash"in q?v?(z(q),_(!0)):await we(q):(q.preview&&URL.revokeObjectURL(q.preview),w(le=>le.filter(de=>de.id!==q.id)))},[v,we]),ze=C.useCallback(q=>{w(le=>le.map(de=>de.id===q.id?{...de,status:"pending",error:void 0}:de))},[]),K=C.useCallback(async q=>{const{arrayMove:le}=await Promise.resolve().then(()=>Y0),{active:de,over:ue}=q;if(!ue||de.id===ue.id)return;const ie=[...I,...N];let Fe;if(B.length===0)Fe=ie;else{const xe=new Map(B.map((De,Ye)=>[String(De),Ye]));Fe=[...ie].sort((De,Ye)=>{const rt="hash"in De?String(De.hash):String(De.id),qe="hash"in Ye?String(Ye.hash):String(Ye.id);return(xe.get(rt)??ie.length)-(xe.get(qe)??ie.length)})}const Ce=Fe.findIndex(xe=>"hash"in xe?xe.hash===de.id:xe.id===de.id),Ie=Fe.findIndex(xe=>"hash"in xe?xe.hash===ue.id:xe.id===ue.id),Ne=le(Fe,Ce,Ie);Y(Ne.map(xe=>"hash"in xe?xe.hash:xe.id));const He=Ne.filter(xe=>"hash"in xe),je=Ne.filter(xe=>!("hash"in xe));X.current=!0,S(He),w(je);const _e=He.map((xe,De)=>({...xe,sort_order:De,order:De+1}));if(He.length>0&&u.reorder){const xe=He.map((De,Ye)=>({id:De.id,order:Ye+1}));try{const De=await xr.api.patch(u.reorder,{order:xe});xr.toast.success(De.message)}catch(De){const Ye=De instanceof Error?De.message:at("attachment.reorder_failed");xr.toast.error(Ye)}}k?.(_e)},[I,N,B,u.reorder,k]),oe=C.useMemo(()=>[...I,...N].filter(q=>"is_image"in q?q.is_image:q.mime_type?.startsWith("image/")??!1),[I,N]),ae=C.useCallback(q=>{const le=oe.findIndex(de=>"hash"in q&&"hash"in de?de.hash===q.hash:"id"in q&&"id"in de&&!("hash"in q)&&!("hash"in de)?de.id===q.id:!1);le!==-1&&(pe.current+=1,se(le),Q(!0))},[oe]);C.useEffect(()=>{let q=!1;return(async()=>{const de=I.filter(ie=>ie.is_image),ue=new Set(de.map(ie=>ie.id));for(const[ie,Fe]of ve.current)ue.has(ie)||(URL.revokeObjectURL(Fe),ve.current.delete(ie));for(const ie of de){if(q)break;if(!ve.current.has(ie.id)){if(ua(ie.download_url)){ve.current.set(ie.id,ie.download_url),Ae(new Map(ve.current));continue}try{const Fe=await xr.api.get(ie.download_url,{responseType:"blob",headers:zn()});if(!q&&Fe){const Ce=URL.createObjectURL(Fe);ve.current.set(ie.id,Ce),Ae(new Map(ve.current))}}catch(Fe){console.error("Failed to load authenticated image:",Fe)}}}})(),()=>{q=!0}},[I]),C.useEffect(()=>()=>{ve.current.forEach(q=>URL.revokeObjectURL(q)),ve.current.clear()},[]);const ce=C.useCallback(async q=>{const{executeImageDownload:le}=await Promise.resolve().then(()=>ni);await le({src:q.download_url,downloadUrl:q.download_url,filename:q.original_filename,downloadRequiresAuth:!0})},[]),ke=C.useMemo(()=>{const q=[...I,...N];if(B.length===0)return q;const le=new Map(B.map((de,ue)=>[String(de),ue]));return[...q].sort((de,ue)=>{const ie="hash"in de?String(de.hash):String(de.id),Fe="hash"in ue?String(ue.hash):String(ue.id),Ce=le.get(ie)??q.length,Ie=le.get(Fe)??q.length;return Ce-Ie})},[I,N,B]),Ee=ke.length,Be=Ee>0,Me=Eew([]),[]),Pe=C.useCallback(()=>N,[N]);return{existingFiles:I,pendingFiles:N,isDragOver:y,setIsDragOver:E,isDeleting:F,totalCount:Ee,hasFiles:Be,canAddMore:Me,allItems:ke,imageFiles:oe,handleFiles:Te,handleRemove:$e,handleRetry:ze,handleUploadAll:Oe,handleOpenGallery:ae,handleDownload:ce,handleDragEnd:K,inputRef:O,galleryOpen:W,setGalleryOpen:Q,galleryStartIndex:be,galleryKeyRef:pe,confirmDialogOpen:U,setConfirmDialogOpen:_,itemToDelete:b,executeRemoveAttachment:we,authenticatedImageUrls:ne,clear:Ke,getPendingFiles:Pe}}const Ti=({isDragOver:e,canAddMore:t,onFiles:r,setIsDragOver:n,inputRef:o,accept:a,maxFiles:s,maxSize:l,children:f})=>d.jsxs(j,{className:` relative border-2 border-dashed rounded-lg transition-colors ${e?"border-blue-500 bg-blue-50 dark:bg-blue-900/20":"border-gray-300 dark:border-gray-600"} - `,onDragOver:h=>{h.preventDefault(),t&&n(!0)},onDragLeave:()=>n(!1),onDrop:h=>{h.preventDefault(),n(!1),t&&r(h.dataTransfer.files)},children:[d.jsx("input",{ref:o,type:"file",multiple:!0,accept:a,onChange:h=>h.target.files&&r(h.target.files),className:"hidden"}),!f&&d.jsxs(j,{className:"p-8 text-center cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800/50 transition-colors",onClick:()=>o.current?.click(),children:[d.jsx(It,{className:"fa-solid fa-cloud-arrow-up text-4xl text-gray-400 dark:text-gray-500 mb-4"}),d.jsx(Gt,{className:"text-sm text-gray-600 dark:text-gray-400",children:at("attachment.drop_or_click")}),d.jsxs(Gt,{className:"text-xs text-gray-500 dark:text-gray-500 mt-2",children:[at("attachment.upload_limit",{maxFiles:s,maxSize:l}),a&&` (${a})`]})]}),f]});function uh(){for(var e=arguments.length,t=new Array(e),r=0;rn=>{t.forEach(o=>o(n))},t)}const Qn=typeof window<"u"&&typeof window.document<"u"&&typeof window.document.createElement<"u";function Gr(e){const t=Object.prototype.toString.call(e);return t==="[object Window]"||t==="[object global]"}function _a(e){return"nodeType"in e}function Ct(e){var t,r;return e?Gr(e)?e:_a(e)&&(t=(r=e.ownerDocument)==null?void 0:r.defaultView)!=null?t:window:window}function Sa(e){const{Document:t}=Ct(e);return e instanceof t}function dn(e){return Gr(e)?!1:e instanceof Ct(e).HTMLElement}function Ai(e){return e instanceof Ct(e).SVGElement}function jr(e){return e?Gr(e)?e.document:_a(e)?Sa(e)?e:dn(e)||Ai(e)?e.ownerDocument:document:document:document}const Jt=Qn?C.useLayoutEffect:C.useEffect;function Ea(e){const t=C.useRef(e);return Jt(()=>{t.current=e}),C.useCallback(function(){for(var r=arguments.length,n=new Array(r),o=0;o{e.current=setInterval(n,o)},[]),r=C.useCallback(()=>{e.current!==null&&(clearInterval(e.current),e.current=null)},[]);return[t,r]}function un(e,t){t===void 0&&(t=[e]);const r=C.useRef(e);return Jt(()=>{r.current!==e&&(r.current=e)},t),r}function fn(e,t){const r=C.useRef();return C.useMemo(()=>{const n=e(r.current);return r.current=n,n},[...t])}function Jn(e){const t=Ea(e),r=C.useRef(null),n=C.useCallback(o=>{o!==r.current&&t?.(o,r.current),r.current=o},[]);return[r,n]}function Ta(e){const t=C.useRef();return C.useEffect(()=>{t.current=e},[e]),t.current}let Aa={};function gn(e,t){return C.useMemo(()=>{if(t)return t;const r=Aa[e]==null?0:Aa[e]+1;return Aa[e]=r,e+"-"+r},[e,t])}function Ni(e){return function(t){for(var r=arguments.length,n=new Array(r>1?r-1:0),o=1;o{const l=Object.entries(s);for(const[f,h]of l){const c=a[f];c!=null&&(a[f]=c+e*h)}return a},{...t})}}const Br=Ni(1),hn=Ni(-1);function gh(e){return"clientX"in e&&"clientY"in e}function Na(e){if(!e)return!1;const{KeyboardEvent:t}=Ct(e.target);return t&&e instanceof t}function hh(e){if(!e)return!1;const{TouchEvent:t}=Ct(e.target);return t&&e instanceof t}function Oa(e){if(hh(e)){if(e.touches&&e.touches.length){const{clientX:t,clientY:r}=e.touches[0];return{x:t,y:r}}else if(e.changedTouches&&e.changedTouches.length){const{clientX:t,clientY:r}=e.changedTouches[0];return{x:t,y:r}}}return gh(e)?{x:e.clientX,y:e.clientY}:null}const pn=Object.freeze({Translate:{toString(e){if(!e)return;const{x:t,y:r}=e;return"translate3d("+(t?Math.round(t):0)+"px, "+(r?Math.round(r):0)+"px, 0)"}},Scale:{toString(e){if(!e)return;const{scaleX:t,scaleY:r}=e;return"scaleX("+t+") scaleY("+r+")"}},Transform:{toString(e){if(e)return[pn.Translate.toString(e),pn.Scale.toString(e)].join(" ")}},Transition:{toString(e){let{property:t,duration:r,easing:n}=e;return t+" "+r+"ms "+n}}}),Oi="a,frame,iframe,input:not([type=hidden]):not(:disabled),select:not(:disabled),textarea:not(:disabled),button:not(:disabled),*[tabindex]";function ph(e){return e.matches(Oi)?e:e.querySelector(Oi)}const mh={display:"none"};function bh(e){let{id:t,value:r}=e;return C.createElement("div",{id:t,style:mh},r)}function yh(e){let{id:t,announcement:r,ariaLiveType:n="assertive"}=e;const o={position:"fixed",top:0,left:0,width:1,height:1,margin:-1,border:0,padding:0,overflow:"hidden",clip:"rect(0 0 0 0)",clipPath:"inset(100%)",whiteSpace:"nowrap"};return C.createElement("div",{id:t,style:o,role:"status","aria-live":n,"aria-atomic":!0},r)}function vh(){const[e,t]=C.useState("");return{announce:C.useCallback(n=>{n!=null&&t(n)},[]),announcement:e}}const Mi=C.createContext(null);function wh(e){const t=C.useContext(Mi);C.useEffect(()=>{if(!t)throw new Error("useDndMonitor must be used within a children of ");return t(e)},[e,t])}function xh(){const[e]=C.useState(()=>new Set),t=C.useCallback(n=>(e.add(n),()=>e.delete(n)),[e]);return[C.useCallback(n=>{let{type:o,event:a}=n;e.forEach(s=>{var l;return(l=s[o])==null?void 0:l.call(s,a)})},[e]),t]}const kh={draggable:` + `,onDragOver:h=>{h.preventDefault(),t&&n(!0)},onDragLeave:()=>n(!1),onDrop:h=>{h.preventDefault(),n(!1),t&&r(h.dataTransfer.files)},children:[d.jsx("input",{ref:o,type:"file",multiple:!0,accept:a,onChange:h=>h.target.files&&r(h.target.files),className:"hidden"}),!f&&d.jsxs(j,{className:"p-8 text-center cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800/50 transition-colors",onClick:()=>o.current?.click(),children:[d.jsx(It,{className:"fa-solid fa-cloud-arrow-up text-4xl text-gray-400 dark:text-gray-500 mb-4"}),d.jsx(Gt,{className:"text-sm text-gray-600 dark:text-gray-400",children:at("attachment.drop_or_click")}),d.jsxs(Gt,{className:"text-xs text-gray-500 dark:text-gray-500 mt-2",children:[at("attachment.upload_limit",{maxFiles:s,maxSize:l}),a&&` (${a})`]})]}),f]});function gh(){for(var e=arguments.length,t=new Array(e),r=0;rn=>{t.forEach(o=>o(n))},t)}const Qn=typeof window<"u"&&typeof window.document<"u"&&typeof window.document.createElement<"u";function Gr(e){const t=Object.prototype.toString.call(e);return t==="[object Window]"||t==="[object global]"}function _a(e){return"nodeType"in e}function Ct(e){var t,r;return e?Gr(e)?e:_a(e)&&(t=(r=e.ownerDocument)==null?void 0:r.defaultView)!=null?t:window:window}function Sa(e){const{Document:t}=Ct(e);return e instanceof t}function dn(e){return Gr(e)?!1:e instanceof Ct(e).HTMLElement}function Ai(e){return e instanceof Ct(e).SVGElement}function jr(e){return e?Gr(e)?e.document:_a(e)?Sa(e)?e:dn(e)||Ai(e)?e.ownerDocument:document:document:document}const Jt=Qn?C.useLayoutEffect:C.useEffect;function Ea(e){const t=C.useRef(e);return Jt(()=>{t.current=e}),C.useCallback(function(){for(var r=arguments.length,n=new Array(r),o=0;o{e.current=setInterval(n,o)},[]),r=C.useCallback(()=>{e.current!==null&&(clearInterval(e.current),e.current=null)},[]);return[t,r]}function un(e,t){t===void 0&&(t=[e]);const r=C.useRef(e);return Jt(()=>{r.current!==e&&(r.current=e)},t),r}function fn(e,t){const r=C.useRef();return C.useMemo(()=>{const n=e(r.current);return r.current=n,n},[...t])}function Jn(e){const t=Ea(e),r=C.useRef(null),n=C.useCallback(o=>{o!==r.current&&t?.(o,r.current),r.current=o},[]);return[r,n]}function Ta(e){const t=C.useRef();return C.useEffect(()=>{t.current=e},[e]),t.current}let Aa={};function gn(e,t){return C.useMemo(()=>{if(t)return t;const r=Aa[e]==null?0:Aa[e]+1;return Aa[e]=r,e+"-"+r},[e,t])}function Ni(e){return function(t){for(var r=arguments.length,n=new Array(r>1?r-1:0),o=1;o{const l=Object.entries(s);for(const[f,h]of l){const c=a[f];c!=null&&(a[f]=c+e*h)}return a},{...t})}}const Br=Ni(1),hn=Ni(-1);function ph(e){return"clientX"in e&&"clientY"in e}function Na(e){if(!e)return!1;const{KeyboardEvent:t}=Ct(e.target);return t&&e instanceof t}function mh(e){if(!e)return!1;const{TouchEvent:t}=Ct(e.target);return t&&e instanceof t}function Oa(e){if(mh(e)){if(e.touches&&e.touches.length){const{clientX:t,clientY:r}=e.touches[0];return{x:t,y:r}}else if(e.changedTouches&&e.changedTouches.length){const{clientX:t,clientY:r}=e.changedTouches[0];return{x:t,y:r}}}return ph(e)?{x:e.clientX,y:e.clientY}:null}const pn=Object.freeze({Translate:{toString(e){if(!e)return;const{x:t,y:r}=e;return"translate3d("+(t?Math.round(t):0)+"px, "+(r?Math.round(r):0)+"px, 0)"}},Scale:{toString(e){if(!e)return;const{scaleX:t,scaleY:r}=e;return"scaleX("+t+") scaleY("+r+")"}},Transform:{toString(e){if(e)return[pn.Translate.toString(e),pn.Scale.toString(e)].join(" ")}},Transition:{toString(e){let{property:t,duration:r,easing:n}=e;return t+" "+r+"ms "+n}}}),Oi="a,frame,iframe,input:not([type=hidden]):not(:disabled),select:not(:disabled),textarea:not(:disabled),button:not(:disabled),*[tabindex]";function bh(e){return e.matches(Oi)?e:e.querySelector(Oi)}const yh={display:"none"};function vh(e){let{id:t,value:r}=e;return C.createElement("div",{id:t,style:yh},r)}function wh(e){let{id:t,announcement:r,ariaLiveType:n="assertive"}=e;const o={position:"fixed",top:0,left:0,width:1,height:1,margin:-1,border:0,padding:0,overflow:"hidden",clip:"rect(0 0 0 0)",clipPath:"inset(100%)",whiteSpace:"nowrap"};return C.createElement("div",{id:t,style:o,role:"status","aria-live":n,"aria-atomic":!0},r)}function xh(){const[e,t]=C.useState("");return{announce:C.useCallback(n=>{n!=null&&t(n)},[]),announcement:e}}const Mi=C.createContext(null);function kh(e){const t=C.useContext(Mi);C.useEffect(()=>{if(!t)throw new Error("useDndMonitor must be used within a children of ");return t(e)},[e,t])}function Ch(){const[e]=C.useState(()=>new Set),t=C.useCallback(n=>(e.add(n),()=>e.delete(n)),[e]);return[C.useCallback(n=>{let{type:o,event:a}=n;e.forEach(s=>{var l;return(l=s[o])==null?void 0:l.call(s,a)})},[e]),t]}const _h={draggable:` To pick up a draggable item, press the space bar. While dragging, use the arrow keys to move the item. Press space again to drop the item in its new position, or press escape to cancel. - `},Ch={onDragStart(e){let{active:t}=e;return"Picked up draggable item "+t.id+"."},onDragOver(e){let{active:t,over:r}=e;return r?"Draggable item "+t.id+" was moved over droppable area "+r.id+".":"Draggable item "+t.id+" is no longer over a droppable area."},onDragEnd(e){let{active:t,over:r}=e;return r?"Draggable item "+t.id+" was dropped over droppable area "+r.id:"Draggable item "+t.id+" was dropped."},onDragCancel(e){let{active:t}=e;return"Dragging was cancelled. Draggable item "+t.id+" was dropped."}};function _h(e){let{announcements:t=Ch,container:r,hiddenTextDescribedById:n,screenReaderInstructions:o=kh}=e;const{announce:a,announcement:s}=vh(),l=gn("DndLiveRegion"),[f,h]=C.useState(!1);if(C.useEffect(()=>{h(!0)},[]),wh(C.useMemo(()=>({onDragStart(i){let{active:u}=i;a(t.onDragStart({active:u}))},onDragMove(i){let{active:u,over:g}=i;t.onDragMove&&a(t.onDragMove({active:u,over:g}))},onDragOver(i){let{active:u,over:g}=i;a(t.onDragOver({active:u,over:g}))},onDragEnd(i){let{active:u,over:g}=i;a(t.onDragEnd({active:u,over:g}))},onDragCancel(i){let{active:u,over:g}=i;a(t.onDragCancel({active:u,over:g}))}}),[a,t])),!f)return null;const c=C.createElement(C.Fragment,null,C.createElement(bh,{id:n,value:o.draggable}),C.createElement(yh,{id:l,announcement:s}));return r?tr.createPortal(c,r):c}var st;(function(e){e.DragStart="dragStart",e.DragMove="dragMove",e.DragEnd="dragEnd",e.DragCancel="dragCancel",e.DragOver="dragOver",e.RegisterDroppable="registerDroppable",e.SetDroppableDisabled="setDroppableDisabled",e.UnregisterDroppable="unregisterDroppable"})(st||(st={}));function Rn(){}function Ii(e,t){return C.useMemo(()=>({sensor:e,options:t??{}}),[e,t])}function Sh(){for(var e=arguments.length,t=new Array(e),r=0;r[...t].filter(n=>n!=null),[...t])}const qt=Object.freeze({x:0,y:0});function Di(e,t){return Math.sqrt(Math.pow(e.x-t.x,2)+Math.pow(e.y-t.y,2))}function Pi(e,t){let{data:{value:r}}=e,{data:{value:n}}=t;return r-n}function Eh(e,t){let{data:{value:r}}=e,{data:{value:n}}=t;return n-r}function Li(e){let{left:t,top:r,height:n,width:o}=e;return[{x:t,y:r},{x:t+o,y:r},{x:t,y:r+n},{x:t+o,y:r+n}]}function $i(e,t){if(!e||e.length===0)return null;const[r]=e;return r[t]}function Fi(e,t,r){return t===void 0&&(t=e.left),r===void 0&&(r=e.top),{x:t+e.width*.5,y:r+e.height*.5}}const Th=e=>{let{collisionRect:t,droppableRects:r,droppableContainers:n}=e;const o=Fi(t,t.left,t.top),a=[];for(const s of n){const{id:l}=s,f=r.get(l);if(f){const h=Di(Fi(f),o);a.push({id:l,data:{droppableContainer:s,value:h}})}}return a.sort(Pi)},Ah=e=>{let{collisionRect:t,droppableRects:r,droppableContainers:n}=e;const o=Li(t),a=[];for(const s of n){const{id:l}=s,f=r.get(l);if(f){const h=Li(f),c=o.reduce((u,g,p)=>u+Di(h[p],g),0),i=Number((c/4).toFixed(4));a.push({id:l,data:{droppableContainer:s,value:i}})}}return a.sort(Pi)};function Nh(e,t){const r=Math.max(t.top,e.top),n=Math.max(t.left,e.left),o=Math.min(t.left+t.width,e.left+e.width),a=Math.min(t.top+t.height,e.top+e.height),s=o-n,l=a-r;if(n{let{collisionRect:t,droppableRects:r,droppableContainers:n}=e;const o=[];for(const a of n){const{id:s}=a,l=r.get(s);if(l){const f=Nh(l,t);f>0&&o.push({id:s,data:{droppableContainer:a,value:f}})}}return o.sort(Eh)};function Mh(e,t,r){return{...e,scaleX:t&&r?t.width/r.width:1,scaleY:t&&r?t.height/r.height:1}}function Ui(e,t){return e&&t?{x:e.left-t.left,y:e.top-t.top}:qt}function Ih(e){return function(r){for(var n=arguments.length,o=new Array(n>1?n-1:0),a=1;a({...s,top:s.top+e*l.y,bottom:s.bottom+e*l.y,left:s.left+e*l.x,right:s.right+e*l.x}),{...r})}}const Dh=Ih(1);function Ph(e){if(e.startsWith("matrix3d(")){const t=e.slice(9,-1).split(/, /);return{x:+t[12],y:+t[13],scaleX:+t[0],scaleY:+t[5]}}else if(e.startsWith("matrix(")){const t=e.slice(7,-1).split(/, /);return{x:+t[4],y:+t[5],scaleX:+t[0],scaleY:+t[3]}}return null}function Lh(e,t,r){const n=Ph(t);if(!n)return e;const{scaleX:o,scaleY:a,x:s,y:l}=n,f=e.left-s-(1-o)*parseFloat(r),h=e.top-l-(1-a)*parseFloat(r.slice(r.indexOf(" ")+1)),c=o?e.width/o:e.width,i=a?e.height/a:e.height;return{width:c,height:i,top:h,right:f+c,bottom:h+i,left:f}}const $h={ignoreTransform:!1};function Wr(e,t){t===void 0&&(t=$h);let r=e.getBoundingClientRect();if(t.ignoreTransform){const{transform:h,transformOrigin:c}=Ct(e).getComputedStyle(e);h&&(r=Lh(r,h,c))}const{top:n,left:o,width:a,height:s,bottom:l,right:f}=r;return{top:n,left:o,width:a,height:s,bottom:l,right:f}}function zi(e){return Wr(e,{ignoreTransform:!0})}function Fh(e){const t=e.innerWidth,r=e.innerHeight;return{top:0,left:0,right:t,bottom:r,width:t,height:r}}function Uh(e,t){return t===void 0&&(t=Ct(e).getComputedStyle(e)),t.position==="fixed"}function zh(e,t){t===void 0&&(t=Ct(e).getComputedStyle(e));const r=/(auto|scroll|overlay)/;return["overflow","overflowX","overflowY"].some(o=>{const a=t[o];return typeof a=="string"?r.test(a):!1})}function eo(e,t){const r=[];function n(o){if(t!=null&&r.length>=t||!o)return r;if(Sa(o)&&o.scrollingElement!=null&&!r.includes(o.scrollingElement))return r.push(o.scrollingElement),r;if(!dn(o)||Ai(o)||r.includes(o))return r;const a=Ct(e).getComputedStyle(o);return o!==e&&zh(o,a)&&r.push(o),Uh(o,a)?r:n(o.parentNode)}return e?n(e):r}function Hi(e){const[t]=eo(e,1);return t??null}function Ma(e){return!Qn||!e?null:Gr(e)?e:_a(e)?Sa(e)||e===jr(e).scrollingElement?window:dn(e)?e:null:null}function Gi(e){return Gr(e)?e.scrollX:e.scrollLeft}function ji(e){return Gr(e)?e.scrollY:e.scrollTop}function Ia(e){return{x:Gi(e),y:ji(e)}}var ut;(function(e){e[e.Forward=1]="Forward",e[e.Backward=-1]="Backward"})(ut||(ut={}));function Bi(e){return!Qn||!e?!1:e===document.scrollingElement}function Wi(e){const t={x:0,y:0},r=Bi(e)?{height:window.innerHeight,width:window.innerWidth}:{height:e.clientHeight,width:e.clientWidth},n={x:e.scrollWidth-r.width,y:e.scrollHeight-r.height},o=e.scrollTop<=t.y,a=e.scrollLeft<=t.x,s=e.scrollTop>=n.y,l=e.scrollLeft>=n.x;return{isTop:o,isLeft:a,isBottom:s,isRight:l,maxScroll:n,minScroll:t}}const Hh={x:.2,y:.2};function Gh(e,t,r,n,o){let{top:a,left:s,right:l,bottom:f}=r;n===void 0&&(n=10),o===void 0&&(o=Hh);const{isTop:h,isBottom:c,isLeft:i,isRight:u}=Wi(e),g={x:0,y:0},p={x:0,y:0},m={height:t.height*o.y,width:t.width*o.x};return!h&&a<=t.top+m.height?(g.y=ut.Backward,p.y=n*Math.abs((t.top+m.height-a)/m.height)):!c&&f>=t.bottom-m.height&&(g.y=ut.Forward,p.y=n*Math.abs((t.bottom-m.height-f)/m.height)),!u&&l>=t.right-m.width?(g.x=ut.Forward,p.x=n*Math.abs((t.right-m.width-l)/m.width)):!i&&s<=t.left+m.width&&(g.x=ut.Backward,p.x=n*Math.abs((t.left+m.width-s)/m.width)),{direction:g,speed:p}}function jh(e){if(e===document.scrollingElement){const{innerWidth:a,innerHeight:s}=window;return{top:0,left:0,right:a,bottom:s,width:a,height:s}}const{top:t,left:r,right:n,bottom:o}=e.getBoundingClientRect();return{top:t,left:r,right:n,bottom:o,width:e.clientWidth,height:e.clientHeight}}function Vi(e){return e.reduce((t,r)=>Br(t,Ia(r)),qt)}function Bh(e){return e.reduce((t,r)=>t+Gi(r),0)}function Wh(e){return e.reduce((t,r)=>t+ji(r),0)}function Vh(e,t){if(t===void 0&&(t=Wr),!e)return;const{top:r,left:n,bottom:o,right:a}=t(e);Hi(e)&&(o<=0||a<=0||r>=window.innerHeight||n>=window.innerWidth)&&e.scrollIntoView({block:"center",inline:"center"})}const Kh=[["x",["left","right"],Bh],["y",["top","bottom"],Wh]];class Da{constructor(t,r){this.rect=void 0,this.width=void 0,this.height=void 0,this.top=void 0,this.bottom=void 0,this.right=void 0,this.left=void 0;const n=eo(r),o=Vi(n);this.rect={...t},this.width=t.width,this.height=t.height;for(const[a,s,l]of Kh)for(const f of s)Object.defineProperty(this,f,{get:()=>{const h=l(n),c=o[a]-h;return this.rect[f]+c},enumerable:!0});Object.defineProperty(this,"rect",{enumerable:!1})}}class mn{constructor(t){this.target=void 0,this.listeners=[],this.removeAll=()=>{this.listeners.forEach(r=>{var n;return(n=this.target)==null?void 0:n.removeEventListener(...r)})},this.target=t}add(t,r,n){var o;(o=this.target)==null||o.addEventListener(t,r,n),this.listeners.push([t,r,n])}}function Yh(e){const{EventTarget:t}=Ct(e);return e instanceof t?e:jr(e)}function Pa(e,t){const r=Math.abs(e.x),n=Math.abs(e.y);return typeof t=="number"?Math.sqrt(r**2+n**2)>t:"x"in t&&"y"in t?r>t.x&&n>t.y:"x"in t?r>t.x:"y"in t?n>t.y:!1}var zt;(function(e){e.Click="click",e.DragStart="dragstart",e.Keydown="keydown",e.ContextMenu="contextmenu",e.Resize="resize",e.SelectionChange="selectionchange",e.VisibilityChange="visibilitychange"})(zt||(zt={}));function Ki(e){e.preventDefault()}function Xh(e){e.stopPropagation()}var Ue;(function(e){e.Space="Space",e.Down="ArrowDown",e.Right="ArrowRight",e.Left="ArrowLeft",e.Up="ArrowUp",e.Esc="Escape",e.Enter="Enter",e.Tab="Tab"})(Ue||(Ue={}));const Yi={start:[Ue.Space,Ue.Enter],cancel:[Ue.Esc],end:[Ue.Space,Ue.Enter,Ue.Tab]},qh=(e,t)=>{let{currentCoordinates:r}=t;switch(e.code){case Ue.Right:return{...r,x:r.x+25};case Ue.Left:return{...r,x:r.x-25};case Ue.Down:return{...r,y:r.y+25};case Ue.Up:return{...r,y:r.y-25}}};class La{constructor(t){this.props=void 0,this.autoScrollEnabled=!1,this.referenceCoordinates=void 0,this.listeners=void 0,this.windowListeners=void 0,this.props=t;const{event:{target:r}}=t;this.props=t,this.listeners=new mn(jr(r)),this.windowListeners=new mn(Ct(r)),this.handleKeyDown=this.handleKeyDown.bind(this),this.handleCancel=this.handleCancel.bind(this),this.attach()}attach(){this.handleStart(),this.windowListeners.add(zt.Resize,this.handleCancel),this.windowListeners.add(zt.VisibilityChange,this.handleCancel),setTimeout(()=>this.listeners.add(zt.Keydown,this.handleKeyDown))}handleStart(){const{activeNode:t,onStart:r}=this.props,n=t.node.current;n&&Vh(n),r(qt)}handleKeyDown(t){if(Na(t)){const{active:r,context:n,options:o}=this.props,{keyboardCodes:a=Yi,coordinateGetter:s=qh,scrollBehavior:l="smooth"}=o,{code:f}=t;if(a.end.includes(f)){this.handleEnd(t);return}if(a.cancel.includes(f)){this.handleCancel(t);return}const{collisionRect:h}=n.current,c=h?{x:h.left,y:h.top}:qt;this.referenceCoordinates||(this.referenceCoordinates=c);const i=s(t,{active:r,context:n.current,currentCoordinates:c});if(i){const u=hn(i,c),g={x:0,y:0},{scrollableAncestors:p}=n.current;for(const m of p){const x=t.code,{isTop:k,isRight:M,isLeft:v,isBottom:T,maxScroll:I,minScroll:S}=Wi(m),N=jh(m),w={x:Math.min(x===Ue.Right?N.right-N.width/2:N.right,Math.max(x===Ue.Right?N.left:N.left+N.width/2,i.x)),y:Math.min(x===Ue.Down?N.bottom-N.height/2:N.bottom,Math.max(x===Ue.Down?N.top:N.top+N.height/2,i.y))},y=x===Ue.Right&&!M||x===Ue.Left&&!v,E=x===Ue.Down&&!T||x===Ue.Up&&!k;if(y&&w.x!==i.x){const O=m.scrollLeft+u.x,$=x===Ue.Right&&O<=I.x||x===Ue.Left&&O>=S.x;if($&&!u.y){m.scrollTo({left:O,behavior:l});return}$?g.x=m.scrollLeft-O:g.x=x===Ue.Right?m.scrollLeft-I.x:m.scrollLeft-S.x,g.x&&m.scrollBy({left:-g.x,behavior:l});break}else if(E&&w.y!==i.y){const O=m.scrollTop+u.y,$=x===Ue.Down&&O<=I.y||x===Ue.Up&&O>=S.y;if($&&!u.x){m.scrollTo({top:O,behavior:l});return}$?g.y=m.scrollTop-O:g.y=x===Ue.Down?m.scrollTop-I.y:m.scrollTop-S.y,g.y&&m.scrollBy({top:-g.y,behavior:l});break}}this.handleMove(t,Br(hn(i,this.referenceCoordinates),g))}}}handleMove(t,r){const{onMove:n}=this.props;t.preventDefault(),n(r)}handleEnd(t){const{onEnd:r}=this.props;t.preventDefault(),this.detach(),r()}handleCancel(t){const{onCancel:r}=this.props;t.preventDefault(),this.detach(),r()}detach(){this.listeners.removeAll(),this.windowListeners.removeAll()}}La.activators=[{eventName:"onKeyDown",handler:(e,t,r)=>{let{keyboardCodes:n=Yi,onActivation:o}=t,{active:a}=r;const{code:s}=e.nativeEvent;if(n.start.includes(s)){const l=a.activatorNode.current;return l&&e.target!==l?!1:(e.preventDefault(),o?.({event:e.nativeEvent}),!0)}return!1}}];function Xi(e){return!!(e&&"distance"in e)}function qi(e){return!!(e&&"delay"in e)}class $a{constructor(t,r,n){var o;n===void 0&&(n=Yh(t.event.target)),this.props=void 0,this.events=void 0,this.autoScrollEnabled=!0,this.document=void 0,this.activated=!1,this.initialCoordinates=void 0,this.timeoutId=null,this.listeners=void 0,this.documentListeners=void 0,this.windowListeners=void 0,this.props=t,this.events=r;const{event:a}=t,{target:s}=a;this.props=t,this.events=r,this.document=jr(s),this.documentListeners=new mn(this.document),this.listeners=new mn(n),this.windowListeners=new mn(Ct(s)),this.initialCoordinates=(o=Oa(a))!=null?o:qt,this.handleStart=this.handleStart.bind(this),this.handleMove=this.handleMove.bind(this),this.handleEnd=this.handleEnd.bind(this),this.handleCancel=this.handleCancel.bind(this),this.handleKeydown=this.handleKeydown.bind(this),this.removeTextSelection=this.removeTextSelection.bind(this),this.attach()}attach(){const{events:t,props:{options:{activationConstraint:r,bypassActivationConstraint:n}}}=this;if(this.listeners.add(t.move.name,this.handleMove,{passive:!1}),this.listeners.add(t.end.name,this.handleEnd),t.cancel&&this.listeners.add(t.cancel.name,this.handleCancel),this.windowListeners.add(zt.Resize,this.handleCancel),this.windowListeners.add(zt.DragStart,Ki),this.windowListeners.add(zt.VisibilityChange,this.handleCancel),this.windowListeners.add(zt.ContextMenu,Ki),this.documentListeners.add(zt.Keydown,this.handleKeydown),r){if(n!=null&&n({event:this.props.event,activeNode:this.props.activeNode,options:this.props.options}))return this.handleStart();if(qi(r)){this.timeoutId=setTimeout(this.handleStart,r.delay),this.handlePending(r);return}if(Xi(r)){this.handlePending(r);return}}this.handleStart()}detach(){this.listeners.removeAll(),this.windowListeners.removeAll(),setTimeout(this.documentListeners.removeAll,50),this.timeoutId!==null&&(clearTimeout(this.timeoutId),this.timeoutId=null)}handlePending(t,r){const{active:n,onPending:o}=this.props;o(n,t,this.initialCoordinates,r)}handleStart(){const{initialCoordinates:t}=this,{onStart:r}=this.props;t&&(this.activated=!0,this.documentListeners.add(zt.Click,Xh,{capture:!0}),this.removeTextSelection(),this.documentListeners.add(zt.SelectionChange,this.removeTextSelection),r(t))}handleMove(t){var r;const{activated:n,initialCoordinates:o,props:a}=this,{onMove:s,options:{activationConstraint:l}}=a;if(!o)return;const f=(r=Oa(t))!=null?r:qt,h=hn(o,f);if(!n&&l){if(Xi(l)){if(l.tolerance!=null&&Pa(h,l.tolerance))return this.handleCancel();if(Pa(h,l.distance))return this.handleStart()}if(qi(l)&&Pa(h,l.tolerance))return this.handleCancel();this.handlePending(l,h);return}t.cancelable&&t.preventDefault(),s(f)}handleEnd(){const{onAbort:t,onEnd:r}=this.props;this.detach(),this.activated||t(this.props.active),r()}handleCancel(){const{onAbort:t,onCancel:r}=this.props;this.detach(),this.activated||t(this.props.active),r()}handleKeydown(t){t.code===Ue.Esc&&this.handleCancel()}removeTextSelection(){var t;(t=this.document.getSelection())==null||t.removeAllRanges()}}const Zh={cancel:{name:"pointercancel"},move:{name:"pointermove"},end:{name:"pointerup"}};class Fa extends $a{constructor(t){const{event:r}=t,n=jr(r.target);super(t,Zh,n)}}Fa.activators=[{eventName:"onPointerDown",handler:(e,t)=>{let{nativeEvent:r}=e,{onActivation:n}=t;return!r.isPrimary||r.button!==0?!1:(n?.({event:r}),!0)}}];const Qh={move:{name:"mousemove"},end:{name:"mouseup"}};var Ua;(function(e){e[e.RightClick=2]="RightClick"})(Ua||(Ua={}));class Jh extends $a{constructor(t){super(t,Qh,jr(t.event.target))}}Jh.activators=[{eventName:"onMouseDown",handler:(e,t)=>{let{nativeEvent:r}=e,{onActivation:n}=t;return r.button===Ua.RightClick?!1:(n?.({event:r}),!0)}}];const za={cancel:{name:"touchcancel"},move:{name:"touchmove"},end:{name:"touchend"}};class Rh extends $a{constructor(t){super(t,za)}static setup(){return window.addEventListener(za.move.name,t,{capture:!1,passive:!1}),function(){window.removeEventListener(za.move.name,t)};function t(){}}}Rh.activators=[{eventName:"onTouchStart",handler:(e,t)=>{let{nativeEvent:r}=e,{onActivation:n}=t;const{touches:o}=r;return o.length>1?!1:(n?.({event:r}),!0)}}];var bn;(function(e){e[e.Pointer=0]="Pointer",e[e.DraggableRect=1]="DraggableRect"})(bn||(bn={}));var to;(function(e){e[e.TreeOrder=0]="TreeOrder",e[e.ReversedTreeOrder=1]="ReversedTreeOrder"})(to||(to={}));function e0(e){let{acceleration:t,activator:r=bn.Pointer,canScroll:n,draggingRect:o,enabled:a,interval:s=5,order:l=to.TreeOrder,pointerCoordinates:f,scrollableAncestors:h,scrollableAncestorRects:c,delta:i,threshold:u}=e;const g=r0({delta:i,disabled:!a}),[p,m]=fh(),x=C.useRef({x:0,y:0}),k=C.useRef({x:0,y:0}),M=C.useMemo(()=>{switch(r){case bn.Pointer:return f?{top:f.y,bottom:f.y,left:f.x,right:f.x}:null;case bn.DraggableRect:return o}},[r,o,f]),v=C.useRef(null),T=C.useCallback(()=>{const S=v.current;if(!S)return;const N=x.current.x*k.current.x,w=x.current.y*k.current.y;S.scrollBy(N,w)},[]),I=C.useMemo(()=>l===to.TreeOrder?[...h].reverse():h,[l,h]);C.useEffect(()=>{if(!a||!h.length||!M){m();return}for(const S of I){if(n?.(S)===!1)continue;const N=h.indexOf(S),w=c[N];if(!w)continue;const{direction:y,speed:E}=Gh(S,w,M,t,u);for(const O of["x","y"])g[O][y[O]]||(E[O]=0,y[O]=0);if(E.x>0||E.y>0){m(),v.current=S,p(T,s),x.current=E,k.current=y;return}}x.current={x:0,y:0},k.current={x:0,y:0},m()},[t,T,n,m,a,s,JSON.stringify(M),JSON.stringify(g),p,h,I,c,JSON.stringify(u)])}const t0={x:{[ut.Backward]:!1,[ut.Forward]:!1},y:{[ut.Backward]:!1,[ut.Forward]:!1}};function r0(e){let{delta:t,disabled:r}=e;const n=Ta(t);return fn(o=>{if(r||!n||!o)return t0;const a={x:Math.sign(t.x-n.x),y:Math.sign(t.y-n.y)};return{x:{[ut.Backward]:o.x[ut.Backward]||a.x===-1,[ut.Forward]:o.x[ut.Forward]||a.x===1},y:{[ut.Backward]:o.y[ut.Backward]||a.y===-1,[ut.Forward]:o.y[ut.Forward]||a.y===1}}},[r,t,n])}function n0(e,t){const r=t!=null?e.get(t):void 0,n=r?r.node.current:null;return fn(o=>{var a;return t==null?null:(a=n??o)!=null?a:null},[n,t])}function o0(e,t){return C.useMemo(()=>e.reduce((r,n)=>{const{sensor:o}=n,a=o.activators.map(s=>({eventName:s.eventName,handler:t(s.handler,n)}));return[...r,...a]},[]),[e,t])}var yn;(function(e){e[e.Always=0]="Always",e[e.BeforeDragging=1]="BeforeDragging",e[e.WhileDragging=2]="WhileDragging"})(yn||(yn={}));var Ha;(function(e){e.Optimized="optimized"})(Ha||(Ha={}));const Zi=new Map;function a0(e,t){let{dragging:r,dependencies:n,config:o}=t;const[a,s]=C.useState(null),{frequency:l,measure:f,strategy:h}=o,c=C.useRef(e),i=x(),u=un(i),g=C.useCallback(function(k){k===void 0&&(k=[]),!u.current&&s(M=>M===null?k:M.concat(k.filter(v=>!M.includes(v))))},[u]),p=C.useRef(null),m=fn(k=>{if(i&&!r)return Zi;if(!k||k===Zi||c.current!==e||a!=null){const M=new Map;for(let v of e){if(!v)continue;if(a&&a.length>0&&!a.includes(v.id)&&v.rect.current){M.set(v.id,v.rect.current);continue}const T=v.node.current,I=T?new Da(f(T),T):null;v.rect.current=I,I&&M.set(v.id,I)}return M}return k},[e,a,r,i,f]);return C.useEffect(()=>{c.current=e},[e]),C.useEffect(()=>{i||g()},[r,i]),C.useEffect(()=>{a&&a.length>0&&s(null)},[JSON.stringify(a)]),C.useEffect(()=>{i||typeof l!="number"||p.current!==null||(p.current=setTimeout(()=>{g(),p.current=null},l))},[l,i,g,...n]),{droppableRects:m,measureDroppableContainers:g,measuringScheduled:a!=null};function x(){switch(h){case yn.Always:return!1;case yn.BeforeDragging:return r;default:return!r}}}function Qi(e,t){return fn(r=>e?r||(typeof t=="function"?t(e):e):null,[t,e])}function s0(e,t){return Qi(e,t)}function l0(e){let{callback:t,disabled:r}=e;const n=Ea(t),o=C.useMemo(()=>{if(r||typeof window>"u"||typeof window.MutationObserver>"u")return;const{MutationObserver:a}=window;return new a(n)},[n,r]);return C.useEffect(()=>()=>o?.disconnect(),[o]),o}function ro(e){let{callback:t,disabled:r}=e;const n=Ea(t),o=C.useMemo(()=>{if(r||typeof window>"u"||typeof window.ResizeObserver>"u")return;const{ResizeObserver:a}=window;return new a(n)},[r]);return C.useEffect(()=>()=>o?.disconnect(),[o]),o}function i0(e){return new Da(Wr(e),e)}function Ji(e,t,r){t===void 0&&(t=i0);const[n,o]=C.useState(null);function a(){o(f=>{if(!e)return null;if(e.isConnected===!1){var h;return(h=f??r)!=null?h:null}const c=t(e);return JSON.stringify(f)===JSON.stringify(c)?f:c})}const s=l0({callback(f){if(e)for(const h of f){const{type:c,target:i}=h;if(c==="childList"&&i instanceof HTMLElement&&i.contains(e)){a();break}}}}),l=ro({callback:a});return Jt(()=>{a(),e?(l?.observe(e),s?.observe(document.body,{childList:!0,subtree:!0})):(l?.disconnect(),s?.disconnect())},[e]),n}function c0(e){const t=Qi(e);return Ui(e,t)}const Ri=[];function d0(e){const t=C.useRef(e),r=fn(n=>e?n&&n!==Ri&&e&&t.current&&e.parentNode===t.current.parentNode?n:eo(e):Ri,[e]);return C.useEffect(()=>{t.current=e},[e]),r}function u0(e){const[t,r]=C.useState(null),n=C.useRef(e),o=C.useCallback(a=>{const s=Ma(a.target);s&&r(l=>l?(l.set(s,Ia(s)),new Map(l)):null)},[]);return C.useEffect(()=>{const a=n.current;if(e!==a){s(a);const l=e.map(f=>{const h=Ma(f);return h?(h.addEventListener("scroll",o,{passive:!0}),[h,Ia(h)]):null}).filter(f=>f!=null);r(l.length?new Map(l):null),n.current=e}return()=>{s(e),s(a)};function s(l){l.forEach(f=>{const h=Ma(f);h?.removeEventListener("scroll",o)})}},[o,e]),C.useMemo(()=>e.length?t?Array.from(t.values()).reduce((a,s)=>Br(a,s),qt):Vi(e):qt,[e,t])}function ec(e,t){t===void 0&&(t=[]);const r=C.useRef(null);return C.useEffect(()=>{r.current=null},t),C.useEffect(()=>{const n=e!==qt;n&&!r.current&&(r.current=e),!n&&r.current&&(r.current=null)},[e]),r.current?hn(e,r.current):qt}function f0(e){C.useEffect(()=>{if(!Qn)return;const t=e.map(r=>{let{sensor:n}=r;return n.setup==null?void 0:n.setup()});return()=>{for(const r of t)r?.()}},e.map(t=>{let{sensor:r}=t;return r}))}function g0(e,t){return C.useMemo(()=>e.reduce((r,n)=>{let{eventName:o,handler:a}=n;return r[o]=s=>{a(s,t)},r},{}),[e,t])}function tc(e){return C.useMemo(()=>e?Fh(e):null,[e])}const rc=[];function h0(e,t){t===void 0&&(t=Wr);const[r]=e,n=tc(r?Ct(r):null),[o,a]=C.useState(rc);function s(){a(()=>e.length?e.map(f=>Bi(f)?n:new Da(t(f),f)):rc)}const l=ro({callback:s});return Jt(()=>{l?.disconnect(),s(),e.forEach(f=>l?.observe(f))},[e]),o}function p0(e){if(!e)return null;if(e.children.length>1)return e;const t=e.children[0];return dn(t)?t:e}function m0(e){let{measure:t}=e;const[r,n]=C.useState(null),o=C.useCallback(h=>{for(const{target:c}of h)if(dn(c)){n(i=>{const u=t(c);return i?{...i,width:u.width,height:u.height}:u});break}},[t]),a=ro({callback:o}),s=C.useCallback(h=>{const c=p0(h);a?.disconnect(),c&&a?.observe(c),n(c?t(c):null)},[t,a]),[l,f]=Jn(s);return C.useMemo(()=>({nodeRef:l,rect:r,setRef:f}),[r,l,f])}const b0=[{sensor:Fa,options:{}},{sensor:La,options:{}}],y0={current:{}},no={draggable:{measure:zi},droppable:{measure:zi,strategy:yn.WhileDragging,frequency:Ha.Optimized},dragOverlay:{measure:Wr}};class vn extends Map{get(t){var r;return t!=null&&(r=super.get(t))!=null?r:void 0}toArray(){return Array.from(this.values())}getEnabled(){return this.toArray().filter(t=>{let{disabled:r}=t;return!r})}getNodeFor(t){var r,n;return(r=(n=this.get(t))==null?void 0:n.node.current)!=null?r:void 0}}const v0={activatorEvent:null,active:null,activeNode:null,activeNodeRect:null,collisions:null,containerNodeRect:null,draggableNodes:new Map,droppableRects:new Map,droppableContainers:new vn,over:null,dragOverlay:{nodeRef:{current:null},rect:null,setRef:Rn},scrollableAncestors:[],scrollableAncestorRects:[],measuringConfiguration:no,measureDroppableContainers:Rn,windowRect:null,measuringScheduled:!1},w0={activatorEvent:null,activators:[],active:null,activeNodeRect:null,ariaDescribedById:{draggable:""},dispatch:Rn,draggableNodes:new Map,over:null,measureDroppableContainers:Rn},oo=C.createContext(w0),nc=C.createContext(v0);function x0(){return{draggable:{active:null,initialCoordinates:{x:0,y:0},nodes:new Map,translate:{x:0,y:0}},droppable:{containers:new vn}}}function k0(e,t){switch(t.type){case st.DragStart:return{...e,draggable:{...e.draggable,initialCoordinates:t.initialCoordinates,active:t.active}};case st.DragMove:return e.draggable.active==null?e:{...e,draggable:{...e.draggable,translate:{x:t.coordinates.x-e.draggable.initialCoordinates.x,y:t.coordinates.y-e.draggable.initialCoordinates.y}}};case st.DragEnd:case st.DragCancel:return{...e,draggable:{...e.draggable,active:null,initialCoordinates:{x:0,y:0},translate:{x:0,y:0}}};case st.RegisterDroppable:{const{element:r}=t,{id:n}=r,o=new vn(e.droppable.containers);return o.set(n,r),{...e,droppable:{...e.droppable,containers:o}}}case st.SetDroppableDisabled:{const{id:r,key:n,disabled:o}=t,a=e.droppable.containers.get(r);if(!a||n!==a.key)return e;const s=new vn(e.droppable.containers);return s.set(r,{...a,disabled:o}),{...e,droppable:{...e.droppable,containers:s}}}case st.UnregisterDroppable:{const{id:r,key:n}=t,o=e.droppable.containers.get(r);if(!o||n!==o.key)return e;const a=new vn(e.droppable.containers);return a.delete(r),{...e,droppable:{...e.droppable,containers:a}}}default:return e}}function C0(e){let{disabled:t}=e;const{active:r,activatorEvent:n,draggableNodes:o}=C.useContext(oo),a=Ta(n),s=Ta(r?.id);return C.useEffect(()=>{if(!t&&!n&&a&&s!=null){if(!Na(a)||document.activeElement===a.target)return;const l=o.get(s);if(!l)return;const{activatorNode:f,node:h}=l;if(!f.current&&!h.current)return;requestAnimationFrame(()=>{for(const c of[f.current,h.current]){if(!c)continue;const i=ph(c);if(i){i.focus();break}}})}},[n,t,o,s,a]),null}function _0(e,t){let{transform:r,...n}=t;return e!=null&&e.length?e.reduce((o,a)=>a({transform:o,...n}),r):r}function S0(e){return C.useMemo(()=>({draggable:{...no.draggable,...e?.draggable},droppable:{...no.droppable,...e?.droppable},dragOverlay:{...no.dragOverlay,...e?.dragOverlay}}),[e?.draggable,e?.droppable,e?.dragOverlay])}function E0(e){let{activeNode:t,measure:r,initialRect:n,config:o=!0}=e;const a=C.useRef(!1),{x:s,y:l}=typeof o=="boolean"?{x:o,y:o}:o;Jt(()=>{if(!s&&!l||!t){a.current=!1;return}if(a.current||!n)return;const h=t?.node.current;if(!h||h.isConnected===!1)return;const c=r(h),i=Ui(c,n);if(s||(i.x=0),l||(i.y=0),a.current=!0,Math.abs(i.x)>0||Math.abs(i.y)>0){const u=Hi(h);u&&u.scrollBy({top:i.y,left:i.x})}},[t,s,l,n,r])}const oc=C.createContext({...qt,scaleX:1,scaleY:1});var ar;(function(e){e[e.Uninitialized=0]="Uninitialized",e[e.Initializing=1]="Initializing",e[e.Initialized=2]="Initialized"})(ar||(ar={}));const T0=C.memo(function(t){var r,n,o,a;let{id:s,accessibility:l,autoScroll:f=!0,children:h,sensors:c=b0,collisionDetection:i=Oh,measuring:u,modifiers:g,...p}=t;const m=C.useReducer(k0,void 0,x0),[x,k]=m,[M,v]=xh(),[T,I]=C.useState(ar.Uninitialized),S=T===ar.Initialized,{draggable:{active:N,nodes:w,translate:y},droppable:{containers:E}}=x,O=N!=null?w.get(N):null,$=C.useRef({initial:null,translated:null}),P=C.useMemo(()=>{var _e;return N!=null?{id:N,data:(_e=O?.data)!=null?_e:y0,rect:$}:null},[N,O]),L=C.useRef(null),[U,_]=C.useState(null),[b,z]=C.useState(null),F=un(p,Object.values(p)),G=gn("DndDescribedBy",s),X=C.useMemo(()=>E.getEnabled(),[E]),B=S0(u),{droppableRects:Y,measureDroppableContainers:H,measuringScheduled:W}=a0(X,{dragging:S,dependencies:[y.x,y.y],config:B.droppable}),Q=n0(w,N),be=C.useMemo(()=>b?Oa(b):null,[b]),se=je(),pe=s0(Q,B.draggable.measure);E0({activeNode:N!=null?w.get(N):null,config:se.layoutShiftCompensation,initialRect:pe,measure:B.draggable.measure});const ne=Ji(Q,B.draggable.measure,pe),Ae=Ji(Q?Q.parentElement:null),ve=C.useRef({activatorEvent:null,active:null,activeNode:Q,collisionRect:null,collisions:null,droppableRects:Y,draggableNodes:w,draggingNode:null,draggingNodeRect:null,droppableContainers:E,over:null,scrollableAncestors:[],scrollAdjustedTranslate:null}),We=E.getNodeFor((r=ve.current.over)==null?void 0:r.id),me=m0({measure:B.dragOverlay.measure}),Te=(n=me.nodeRef.current)!=null?n:Q,ge=S?(o=me.rect)!=null?o:ne:null,Oe=!!(me.nodeRef.current&&me.rect),we=c0(Oe?null:ne),$e=tc(Te?Ct(Te):null),ze=d0(S?We??Q:null),K=h0(ze),oe=_0(g,{transform:{x:y.x-we.x,y:y.y-we.y,scaleX:1,scaleY:1},activatorEvent:b,active:P,activeNodeRect:ne,containerNodeRect:Ae,draggingNodeRect:ge,over:ve.current.over,overlayNodeRect:me.rect,scrollableAncestors:ze,scrollableAncestorRects:K,windowRect:$e}),ae=be?Br(be,y):null,ce=u0(ze),ke=ec(ce),Ee=ec(ce,[ne]),Be=Br(oe,ke),Me=ge?Dh(ge,oe):null,Ke=P&&Me?i({active:P,collisionRect:Me,droppableRects:Y,droppableContainers:X,pointerCoordinates:ae}):null,Pe=$i(Ke,"id"),[q,le]=C.useState(null),de=Oe?oe:Br(oe,Ee),ue=Mh(de,(a=q?.rect)!=null?a:null,ne),ie=C.useRef(null),Fe=C.useCallback((_e,xe)=>{let{sensor:De,options:Ye}=xe;if(L.current==null)return;const rt=w.get(L.current);if(!rt)return;const qe=_e.nativeEvent,Je=new De({active:L.current,activeNode:rt,event:qe,options:Ye,context:ve,onAbort(Ve){if(!w.get(Ve))return;const{onDragAbort:St}=F.current,Pt={id:Ve};St?.(Pt),M({type:"onDragAbort",event:Pt})},onPending(Ve,bt,St,Pt){if(!w.get(Ve))return;const{onDragPending:lr}=F.current,Ht={id:Ve,constraint:bt,initialCoordinates:St,offset:Pt};lr?.(Ht),M({type:"onDragPending",event:Ht})},onStart(Ve){const bt=L.current;if(bt==null)return;const St=w.get(bt);if(!St)return;const{onDragStart:Pt}=F.current,_r={activatorEvent:qe,active:{id:bt,data:St.data,rect:$}};tr.unstable_batchedUpdates(()=>{Pt?.(_r),I(ar.Initializing),k({type:st.DragStart,initialCoordinates:Ve,active:bt}),M({type:"onDragStart",event:_r}),_(ie.current),z(qe)})},onMove(Ve){k({type:st.DragMove,coordinates:Ve})},onEnd:Zt(st.DragEnd),onCancel:Zt(st.DragCancel)});ie.current=Je;function Zt(Ve){return async function(){const{active:St,collisions:Pt,over:_r,scrollAdjustedTranslate:lr}=ve.current;let Ht=null;if(St&&lr){const{cancelDrop:ir}=F.current;Ht={activatorEvent:qe,active:St,collisions:Pt,delta:lr,over:_r},Ve===st.DragEnd&&typeof ir=="function"&&await Promise.resolve(ir(Ht))&&(Ve=st.DragCancel)}L.current=null,tr.unstable_batchedUpdates(()=>{k({type:Ve}),I(ar.Uninitialized),le(null),_(null),z(null),ie.current=null;const ir=Ve===st.DragEnd?"onDragEnd":"onDragCancel";if(Ht){const Sr=F.current[ir];Sr?.(Ht),M({type:ir,event:Ht})}})}}},[w]),Ce=C.useCallback((_e,xe)=>(De,Ye)=>{const rt=De.nativeEvent,qe=w.get(Ye);if(L.current!==null||!qe||rt.dndKit||rt.defaultPrevented)return;const Je={active:qe};_e(De,xe.options,Je)===!0&&(rt.dndKit={capturedBy:xe.sensor},L.current=Ye,Fe(De,xe))},[w,Fe]),Ie=o0(c,Ce);f0(c),Jt(()=>{ne&&T===ar.Initializing&&I(ar.Initialized)},[ne,T]),C.useEffect(()=>{const{onDragMove:_e}=F.current,{active:xe,activatorEvent:De,collisions:Ye,over:rt}=ve.current;if(!xe||!De)return;const qe={active:xe,activatorEvent:De,collisions:Ye,delta:{x:Be.x,y:Be.y},over:rt};tr.unstable_batchedUpdates(()=>{_e?.(qe),M({type:"onDragMove",event:qe})})},[Be.x,Be.y]),C.useEffect(()=>{const{active:_e,activatorEvent:xe,collisions:De,droppableContainers:Ye,scrollAdjustedTranslate:rt}=ve.current;if(!_e||L.current==null||!xe||!rt)return;const{onDragOver:qe}=F.current,Je=Ye.get(Pe),Zt=Je&&Je.rect.current?{id:Je.id,rect:Je.rect.current,data:Je.data,disabled:Je.disabled}:null,Ve={active:_e,activatorEvent:xe,collisions:De,delta:{x:rt.x,y:rt.y},over:Zt};tr.unstable_batchedUpdates(()=>{le(Zt),qe?.(Ve),M({type:"onDragOver",event:Ve})})},[Pe]),Jt(()=>{ve.current={activatorEvent:b,active:P,activeNode:Q,collisionRect:Me,collisions:Ke,droppableRects:Y,draggableNodes:w,draggingNode:Te,draggingNodeRect:ge,droppableContainers:E,over:q,scrollableAncestors:ze,scrollAdjustedTranslate:Be},$.current={initial:ge,translated:Me}},[P,Q,Ke,Me,w,Te,ge,Y,E,q,ze,Be]),e0({...se,delta:y,draggingRect:Me,pointerCoordinates:ae,scrollableAncestors:ze,scrollableAncestorRects:K});const Ne=C.useMemo(()=>({active:P,activeNode:Q,activeNodeRect:ne,activatorEvent:b,collisions:Ke,containerNodeRect:Ae,dragOverlay:me,draggableNodes:w,droppableContainers:E,droppableRects:Y,over:q,measureDroppableContainers:H,scrollableAncestors:ze,scrollableAncestorRects:K,measuringConfiguration:B,measuringScheduled:W,windowRect:$e}),[P,Q,ne,b,Ke,Ae,me,w,E,Y,q,H,ze,K,B,W,$e]),He=C.useMemo(()=>({activatorEvent:b,activators:Ie,active:P,activeNodeRect:ne,ariaDescribedById:{draggable:G},dispatch:k,draggableNodes:w,over:q,measureDroppableContainers:H}),[b,Ie,P,ne,k,G,w,q,H]);return C.createElement(Mi.Provider,{value:v},C.createElement(oo.Provider,{value:He},C.createElement(nc.Provider,{value:Ne},C.createElement(oc.Provider,{value:ue},h)),C.createElement(C0,{disabled:l?.restoreFocus===!1})),C.createElement(_h,{...l,hiddenTextDescribedById:G}));function je(){const _e=U?.autoScrollEnabled===!1,xe=typeof f=="object"?f.enabled===!1:f===!1,De=S&&!_e&&!xe;return typeof f=="object"?{...f,enabled:De}:{enabled:De}}}),A0=C.createContext(null),ac="button",N0="Draggable";function O0(e){let{id:t,data:r,disabled:n=!1,attributes:o}=e;const a=gn(N0),{activators:s,activatorEvent:l,active:f,activeNodeRect:h,ariaDescribedById:c,draggableNodes:i,over:u}=C.useContext(oo),{role:g=ac,roleDescription:p="draggable",tabIndex:m=0}=o??{},x=f?.id===t,k=C.useContext(x?oc:A0),[M,v]=Jn(),[T,I]=Jn(),S=g0(s,t),N=un(r);Jt(()=>(i.set(t,{id:t,key:a,node:M,activatorNode:T,data:N}),()=>{const y=i.get(t);y&&y.key===a&&i.delete(t)}),[i,t]);const w=C.useMemo(()=>({role:g,tabIndex:m,"aria-disabled":n,"aria-pressed":x&&g===ac?!0:void 0,"aria-roledescription":p,"aria-describedby":c.draggable}),[n,g,m,x,p,c.draggable]);return{active:f,activatorEvent:l,activeNodeRect:h,attributes:w,isDragging:x,listeners:n?void 0:S,node:M,over:u,setNodeRef:v,setActivatorNodeRef:I,transform:k}}function M0(){return C.useContext(nc)}const I0="Droppable",D0={timeout:25};function P0(e){let{data:t,disabled:r=!1,id:n,resizeObserverConfig:o}=e;const a=gn(I0),{active:s,dispatch:l,over:f,measureDroppableContainers:h}=C.useContext(oo),c=C.useRef({disabled:r}),i=C.useRef(!1),u=C.useRef(null),g=C.useRef(null),{disabled:p,updateMeasurementsFor:m,timeout:x}={...D0,...o},k=un(m??n),M=C.useCallback(()=>{if(!i.current){i.current=!0;return}g.current!=null&&clearTimeout(g.current),g.current=setTimeout(()=>{h(Array.isArray(k.current)?k.current:[k.current]),g.current=null},x)},[x]),v=ro({callback:M,disabled:p||!s}),T=C.useCallback((w,y)=>{v&&(y&&(v.unobserve(y),i.current=!1),w&&v.observe(w))},[v]),[I,S]=Jn(T),N=un(t);return C.useEffect(()=>{!v||!I.current||(v.disconnect(),i.current=!1,v.observe(I.current))},[I,v]),C.useEffect(()=>(l({type:st.RegisterDroppable,element:{id:n,key:a,disabled:r,node:I,rect:u,data:N}}),()=>l({type:st.UnregisterDroppable,key:a,id:n})),[n]),C.useEffect(()=>{r!==c.current.disabled&&(l({type:st.SetDroppableDisabled,id:n,key:a,disabled:r}),c.current.disabled=r)},[n,a,r,l]),{active:s,rect:u,isOver:f?.id===n,node:I,over:f,setNodeRef:S}}function Ga(e,t,r){const n=e.slice();return n.splice(r<0?n.length+r:r,0,n.splice(t,1)[0]),n}function L0(e,t){return e.reduce((r,n,o)=>{const a=t.get(n);return a&&(r[o]=a),r},Array(e.length))}function ao(e){return e!==null&&e>=0}function $0(e,t){if(e===t)return!0;if(e.length!==t.length)return!1;for(let r=0;r{let{rects:t,activeIndex:r,overIndex:n,index:o}=e;const a=Ga(t,n,r),s=t[o],l=a[o];return!l||!s?null:{x:l.left-s.left,y:l.top-s.top,scaleX:l.width/s.width,scaleY:l.height/s.height}},sc="Sortable",lc=C.createContext({activeIndex:-1,containerId:sc,disableTransforms:!1,items:[],overIndex:-1,useDragOverlay:!1,sortedRects:[],strategy:so,disabled:{draggable:!1,droppable:!1}});function ic(e){let{children:t,id:r,items:n,strategy:o=so,disabled:a=!1}=e;const{active:s,dragOverlay:l,droppableRects:f,over:h,measureDroppableContainers:c}=M0(),i=gn(sc,r),u=l.rect!==null,g=C.useMemo(()=>n.map(S=>typeof S=="object"&&"id"in S?S.id:S),[n]),p=s!=null,m=s?g.indexOf(s.id):-1,x=h?g.indexOf(h.id):-1,k=C.useRef(g),M=!$0(g,k.current),v=x!==-1&&m===-1||M,T=F0(a);Jt(()=>{M&&p&&c(g)},[M,g,p,c]),C.useEffect(()=>{k.current=g},[g]);const I=C.useMemo(()=>({activeIndex:m,containerId:i,disabled:T,disableTransforms:v,items:g,overIndex:x,useDragOverlay:u,sortedRects:L0(g,f),strategy:o}),[m,i,T.draggable,T.droppable,v,g,x,f,u,o]);return C.createElement(lc.Provider,{value:I},t)}const cc=e=>{let{id:t,items:r,activeIndex:n,overIndex:o}=e;return Ga(r,n,o).indexOf(t)},dc=e=>{let{containerId:t,isSorting:r,wasDragging:n,index:o,items:a,newIndex:s,previousItems:l,previousContainerId:f,transition:h}=e;return!h||!n||l!==a&&o===s?!1:r?!0:s!==o&&t===f},U0={duration:200,easing:"ease"},uc="transform",z0=pn.Transition.toString({property:uc,duration:0,easing:"linear"}),H0={roleDescription:"sortable"};function G0(e){let{disabled:t,index:r,node:n,rect:o}=e;const[a,s]=C.useState(null),l=C.useRef(r);return Jt(()=>{if(!t&&r!==l.current&&n.current){const f=o.current;if(f){const h=Wr(n.current,{ignoreTransform:!0}),c={x:f.left-h.left,y:f.top-h.top,scaleX:f.width/h.width,scaleY:f.height/h.height};(c.x||c.y)&&s(c)}}r!==l.current&&(l.current=r)},[t,r,n,o]),C.useEffect(()=>{a&&s(null)},[a]),a}function fc(e){let{animateLayoutChanges:t=dc,attributes:r,disabled:n,data:o,getNewIndex:a=cc,id:s,strategy:l,resizeObserverConfig:f,transition:h=U0}=e;const{items:c,containerId:i,activeIndex:u,disabled:g,disableTransforms:p,sortedRects:m,overIndex:x,useDragOverlay:k,strategy:M}=C.useContext(lc),v=j0(n,g),T=c.indexOf(s),I=C.useMemo(()=>({sortable:{containerId:i,index:T,items:c},...o}),[i,o,T,c]),S=C.useMemo(()=>c.slice(c.indexOf(s)),[c,s]),{rect:N,node:w,isOver:y,setNodeRef:E}=P0({id:s,data:I,disabled:v.droppable,resizeObserverConfig:{updateMeasurementsFor:S,...f}}),{active:O,activatorEvent:$,activeNodeRect:P,attributes:L,setNodeRef:U,listeners:_,isDragging:b,over:z,setActivatorNodeRef:F,transform:G}=O0({id:s,data:I,attributes:{...H0,...r},disabled:v.draggable}),X=uh(E,U),B=!!O,Y=B&&!p&&ao(u)&&ao(x),H=!k&&b,W=H&&Y?G:null,be=Y?W??(l??M)({rects:m,activeNodeRect:P,activeIndex:u,overIndex:x,index:T}):null,se=ao(u)&&ao(x)?a({id:s,items:c,activeIndex:u,overIndex:x}):T,pe=O?.id,ne=C.useRef({activeId:pe,items:c,newIndex:se,containerId:i}),Ae=c!==ne.current.items,ve=t({active:O,containerId:i,isDragging:b,isSorting:B,id:s,index:T,items:c,newIndex:ne.current.newIndex,previousItems:ne.current.items,previousContainerId:ne.current.containerId,transition:h,wasDragging:ne.current.activeId!=null}),We=G0({disabled:!ve,index:T,node:w,rect:N});return C.useEffect(()=>{B&&ne.current.newIndex!==se&&(ne.current.newIndex=se),i!==ne.current.containerId&&(ne.current.containerId=i),c!==ne.current.items&&(ne.current.items=c)},[B,se,i,c]),C.useEffect(()=>{if(pe===ne.current.activeId)return;if(pe!=null&&ne.current.activeId==null){ne.current.activeId=pe;return}const Te=setTimeout(()=>{ne.current.activeId=pe},50);return()=>clearTimeout(Te)},[pe]),{active:O,activeIndex:u,attributes:L,data:I,rect:N,index:T,newIndex:se,items:c,isOver:y,isSorting:B,isDragging:b,listeners:_,node:w,overIndex:x,over:z,setNodeRef:X,setActivatorNodeRef:F,setDroppableNodeRef:E,setDraggableNodeRef:U,transform:We??be,transition:me()};function me(){if(We||Ae&&ne.current.newIndex===T)return z0;if(!(H&&!Na($)||!h)&&(B||ve))return pn.Transition.toString({...h,property:uc})}}function j0(e,t){var r,n;return typeof e=="boolean"?{draggable:e,droppable:!1}:{draggable:(r=e?.draggable)!=null?r:t.draggable,droppable:(n=e?.droppable)!=null?n:t.droppable}}function wn(e){if(!e)return!1;const t=e.data.current;return!!(t&&"sortable"in t&&typeof t.sortable=="object"&&"containerId"in t.sortable&&"items"in t.sortable&&"index"in t.sortable)}const B0=[Ue.Down,Ue.Right,Ue.Up,Ue.Left],gc=(e,t)=>{let{context:{active:r,collisionRect:n,droppableRects:o,droppableContainers:a,over:s,scrollableAncestors:l}}=t;if(B0.includes(e.code)){if(e.preventDefault(),!r||!n)return;const f=[];a.getEnabled().forEach(i=>{if(!i||i!=null&&i.disabled)return;const u=o.get(i.id);if(u)switch(e.code){case Ue.Down:n.topu.top&&f.push(i);break;case Ue.Left:n.left>u.left&&f.push(i);break;case Ue.Right:n.left1&&(c=h[1].id),c!=null){const i=a.get(r.id),u=a.get(c),g=u?o.get(u.id):null,p=u?.node.current;if(p&&g&&i&&u){const x=eo(p).some((S,N)=>l[N]!==S),k=hc(i,u),M=W0(i,u),v=x||!k?{x:0,y:0}:{x:M?n.width-g.width:0,y:M?n.height-g.height:0},T={x:g.left,y:g.top};return v.x&&v.y?T:hn(T,v)}}}};function hc(e,t){return!wn(e)||!wn(t)?!1:e.data.current.sortable.containerId===t.data.current.sortable.containerId}function W0(e,t){return!wn(e)||!wn(t)||!hc(e,t)?!1:e.data.current.sortable.index{const{attributes:l,listeners:f,setNodeRef:h,transform:c,transition:i,isDragging:u}=fc({id:"hash"in e?e.hash:e.id}),g={transform:pn.Transform.toString(c),transition:i,opacity:u?.5:1},p="status"in e&&e.status!==void 0,m=p?e.status:"done",x=m==="uploading",k=m==="compressing",M=m==="error",v=p?e.progress:100,T=p?e.preview:void 0,I=p?e.mime_type?.startsWith("image/")??!1:e.is_image,[S,N]=C.useState(void 0),w=!p&&I?e.download_url:void 0;C.useEffect(()=>{if(!w){N(void 0);return}if(ua(w)){N(w);return}let E=!0,O;return(async()=>{try{const P=await K0.api.get(w,{responseType:"blob",headers:zn()});E&&P&&(O=URL.createObjectURL(P),N(O))}catch(P){console.error("Failed to load authenticated image:",P)}})(),()=>{E=!1,O&&URL.revokeObjectURL(O)}},[w]);const y=S;return d.jsxs(j,{ref:h,style:g,className:"relative group bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg overflow-hidden",children:[d.jsxs(j,{className:`aspect-square w-full flex items-center justify-center bg-gray-50 dark:bg-gray-900 relative ${I||!p?"cursor-pointer":""}`,onClick:()=>{x||k||(I&&n?n():!p&&o&&o())},children:[T||y?d.jsx(jt,{src:T||y,alt:e.original_filename,className:"w-full h-full object-cover"}):d.jsx(It,{className:`fa-solid ${Si(e.mime_type)} text-4xl text-gray-400 dark:text-gray-500`}),(x||k)&&d.jsxs(j,{className:"absolute inset-0 bg-black/50 flex flex-col items-center justify-center",children:[d.jsx(j,{className:"w-3/4 h-2 bg-gray-700 dark:bg-gray-600 rounded-full overflow-hidden",children:d.jsx(j,{className:"h-full bg-blue-500 dark:bg-blue-400 transition-all",style:{width:`${v}%`}})}),d.jsxs(te,{className:"text-white text-xs mt-1",children:[v,"%"]})]}),M&&d.jsx(j,{className:"absolute inset-0 bg-red-500/50 dark:bg-red-600/50 flex items-center justify-center",children:d.jsx(It,{className:"fa-solid fa-exclamation-triangle text-2xl text-white"})}),s&&d.jsx(R,{type:"button",onClick:E=>{E.stopPropagation(),s()},className:`absolute top-1.5 left-1.5 z-10 p-1 rounded-full transition-colors ${a?"bg-yellow-500 text-white":"bg-gray-800/50 text-gray-300 hover:bg-yellow-500 hover:text-white opacity-0 group-hover:opacity-100"}`,title:at(a?"attachment.primary_image":"attachment.set_as_primary"),children:d.jsx(It,{className:"fa-solid fa-star text-xs"})}),d.jsxs(j,{className:"absolute inset-0 bg-black/0 group-hover:bg-black/40 transition-colors flex items-start justify-end p-1 gap-1 opacity-0 group-hover:opacity-100",children:[d.jsx(R,{type:"button",...l,...f,className:"p-1.5 bg-white/90 dark:bg-gray-800/90 rounded text-gray-600 dark:text-gray-300 hover:bg-white cursor-grab active:cursor-grabbing",title:at("attachment.drag_to_reorder"),onClick:E=>E.stopPropagation(),children:d.jsx(It,{className:"fa-solid fa-grip-vertical text-sm"})}),!p&&o&&d.jsx(R,{type:"button",onClick:E=>{E.stopPropagation(),o()},className:"p-1.5 bg-white/90 dark:bg-gray-800/90 rounded text-blue-500 hover:bg-white",title:at("common.download"),children:d.jsx(It,{className:"fa-solid fa-download text-sm"})}),M&&r&&d.jsx(R,{type:"button",onClick:E=>{E.stopPropagation(),r()},className:"p-1.5 bg-white/90 dark:bg-gray-800/90 rounded text-orange-500 hover:bg-white",title:at("common.retry"),children:d.jsx(It,{className:"fa-solid fa-arrow-rotate-right text-sm"})}),d.jsx(R,{type:"button",onClick:E=>{E.stopPropagation(),t()},disabled:x||k,className:"p-1.5 bg-white/90 dark:bg-gray-800/90 rounded text-red-500 hover:bg-white disabled:opacity-50",title:at("common.delete"),children:d.jsx(It,{className:"fa-solid fa-times text-sm"})})]})]}),d.jsxs(j,{className:`p-2 ${!p&&o?"cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-700":""}`,onClick:()=>{!p&&o&&o()},children:[d.jsx(Gt,{className:"text-xs font-medium text-gray-900 dark:text-white truncate",title:e.original_filename,children:e.original_filename}),d.jsx(Gt,{className:"text-xs text-gray-500 dark:text-gray-400",children:e.size_formatted})]})]})},mc=({allItems:e,canAddMore:t,totalCount:r,maxFiles:n,maxSize:o,accept:a,onRemove:s,onRetry:l,onImageClick:f,onDownload:h,onDragEnd:c,onAddClick:i,enablePrimarySelection:u,primaryFileId:g,onPrimaryChange:p})=>{const m=Sh(Ii(Fa,{activationConstraint:{distance:8}}),Ii(La,{coordinateGetter:gc}));return d.jsxs(j,{className:"p-3",children:[d.jsx(T0,{sensors:m,collisionDetection:Th,onDragEnd:c,children:d.jsx(ic,{items:e.map(x=>"hash"in x?x.hash:x.id),strategy:so,children:d.jsxs(j,{className:"grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 gap-3",children:[e.map(x=>{const k="hash"in x,M=k?x.is_image:x.mime_type?.startsWith("image/")??!1;return d.jsx(pc,{file:x,onRemove:()=>s(x),onRetry:"status"in x&&x.status==="error"?()=>l(x):void 0,onImageClick:M?()=>f(x):void 0,onDownload:k?()=>h(x):void 0,isPrimary:u&&k&&(x.hash||x.id)===g,onPrimaryClick:u&&k?()=>p?.(x.hash||x.id):void 0},k?x.hash:x.id)}),t&&d.jsxs(j,{className:"aspect-square flex flex-col items-center justify-center border-2 border-dashed border-gray-300 dark:border-gray-600 rounded-lg cursor-pointer hover:border-gray-400 dark:hover:border-gray-500 hover:bg-gray-50 dark:hover:bg-gray-800/50 transition-colors",onClick:i,children:[d.jsx(It,{className:"fa-solid fa-plus text-2xl text-gray-400 dark:text-gray-500 mb-1"}),d.jsx(te,{className:"text-xs text-gray-500 dark:text-gray-400",children:at("common.add")})]})]})})}),d.jsxs(j,{className:"mt-3 pt-3 border-t border-gray-200 dark:border-gray-700 flex justify-between items-center text-xs text-gray-500 dark:text-gray-400",children:[d.jsx(te,{children:at("attachment.attached_count",{count:r,max:n})}),d.jsxs(te,{children:[at("attachment.max_size",{size:o}),a&&` (${a})`]})]})]})},Y0=[],X0=[],lo=C.forwardRef(({uploadTriggerEvent:e,attachmentableType:t,attachmentableId:r,collection:n="default",maxFiles:o=10,maxSize:a=10,maxConcurrentUploads:s=3,accept:l,imageCompression:f,roleIds:h=X0,autoUpload:c=!0,onFilesChange:i,onChange:u,onUploadComplete:g,onUploadError:p,onRemove:m,onReorder:x,initialFiles:k=Y0,className:M="",confirmBeforeRemove:v=!1,confirmRemoveTitle:T,confirmRemoveMessage:I,apiEndpoints:S,uploadParams:N,enablePrimarySelection:w=!1,primaryFileId:y,onPrimaryChange:E,id:O,editorAttrs:$},P)=>{const L={upload:S?.upload??"/api/attachments",delete:S?.delete??"/api/attachments/:id",reorder:S?.reorder??"/api/attachments/reorder"},U=C.useRef(u??i);U.current=u??i;const _=C.useCallback(G=>{const X=U.current;if(!X)return;X({target:{value:G,files:G},preventDefault:()=>{},stopPropagation:()=>{}})},[]),b=Ei({attachmentableType:t,attachmentableId:r,collection:n,maxFiles:o,maxSize:a,maxConcurrentUploads:s,accept:l,imageCompression:f,roleIds:h,autoUpload:c,initialFiles:k,endpoints:L,onFilesChange:_,onUploadComplete:g,onUploadError:p,onRemove:m,onReorder:x,uploadTriggerEvent:e,confirmBeforeRemove:v,uploadParams:N});C.useImperativeHandle(P,()=>({uploadAll:b.handleUploadAll,clear:b.clear,getPendingFiles:b.getPendingFiles}));const z=C.useMemo(()=>b.imageFiles.map(G=>{const X=!("hash"in G),B=G,Y=G;return X?{src:B.preview||"",filename:B.original_filename,title:B.original_filename,downloadRequiresAuth:!1}:{src:b.authenticatedImageUrls.get(Y.id)||Y.download_url,downloadUrl:Y.download_url,filename:Y.original_filename,title:Y.original_filename,downloadRequiresAuth:!0}}),[b.imageFiles,b.authenticatedImageUrls]),F=C.useMemo(()=>({existingFiles:b.existingFiles,pendingFiles:b.pendingFiles,isDragOver:b.isDragOver,isDeleting:b.isDeleting,maxFiles:o,maxSize:a,accept:l,endpoints:L,confirmBeforeRemove:v,totalCount:b.totalCount,hasFiles:b.hasFiles,canAddMore:b.canAddMore,allItems:b.allItems,imageFiles:b.imageFiles,handleFiles:b.handleFiles,handleRemove:b.handleRemove,handleRetry:b.handleRetry,handleUploadAll:b.handleUploadAll,handleOpenGallery:b.handleOpenGallery,handleDownload:b.handleDownload,handleDragEnd:b.handleDragEnd,setIsDragOver:b.setIsDragOver,inputRef:b.inputRef,galleryOpen:b.galleryOpen,setGalleryOpen:b.setGalleryOpen,galleryStartIndex:b.galleryStartIndex,galleryKeyRef:b.galleryKeyRef,confirmDialogOpen:b.confirmDialogOpen,setConfirmDialogOpen:b.setConfirmDialogOpen,itemToDelete:b.itemToDelete,executeRemoveAttachment:b.executeRemoveAttachment,authenticatedImageUrls:b.authenticatedImageUrls}),[b,o,a,l,L,v]);return d.jsx(ui,{value:F,children:d.jsxs(j,{id:O,className:M,...$,children:[d.jsx(Ti,{isDragOver:b.isDragOver,canAddMore:b.canAddMore,onFiles:b.handleFiles,setIsDragOver:b.setIsDragOver,inputRef:b.inputRef,accept:l,maxFiles:o,maxSize:a,children:b.hasFiles&&d.jsx(mc,{allItems:b.allItems,canAddMore:b.canAddMore,totalCount:b.totalCount,maxFiles:o,maxSize:a,accept:l,onRemove:b.handleRemove,onRetry:b.handleRetry,onImageClick:b.handleOpenGallery,onDownload:b.handleDownload,onDragEnd:b.handleDragEnd,onAddClick:()=>b.inputRef.current?.click(),enablePrimarySelection:w,primaryFileId:y,onPrimaryChange:E})}),v&&d.jsx(wa,{isOpen:b.confirmDialogOpen,onClose:()=>{b.isDeleting||b.setConfirmDialogOpen(!1)},title:T??at("attachment.delete_confirm_title"),message:I??at("attachment.delete_confirm_message"),confirmText:at("common.delete"),confirmButtonVariant:"danger",onConfirm:()=>{b.itemToDelete&&b.executeRemoveAttachment(b.itemToDelete)},isLoading:b.isDeleting,loadingText:at("attachment.deleting")}),z.length>0&&b.galleryOpen&&d.jsx(zr,{images:z,isOpen:!0,onClose:()=>b.setGalleryOpen(!1),startIndex:b.galleryStartIndex,showDownload:!0},`gallery-${b.galleryKeyRef.current}`)]})})});lo.displayName="FileUploader";const q0=Object.freeze(Object.defineProperty({__proto__:null,FileDropZone:Ti,FileList:mc,FileUploader:lo,FileUploaderProvider:ui,SortableThumbnailItem:pc,default:lo,extractErrorMessage:_i,fileUploaderT:at,formatFileSize:Zn,getFileIcon:Si,useFileUploader:Ei,useFileUploaderContext:Rg},Symbol.toStringTag,{value:"Module"})),bc=({provider:e,className:t="w-5 h-5"})=>{switch(e){case"google":return d.jsxs("svg",{className:t,viewBox:"0 0 24 24",children:[d.jsx("path",{fill:"#4285F4",d:"M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z"}),d.jsx("path",{fill:"#34A853",d:"M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z"}),d.jsx("path",{fill:"#FBBC05",d:"M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z"}),d.jsx("path",{fill:"#EA4335",d:"M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z"})]});case"naver":return d.jsx("svg",{className:t,viewBox:"0 0 24 24",children:d.jsx("path",{fill:"currentColor",d:"M16.273 12.845L7.376 0H0v24h7.727V11.155L16.624 24H24V0h-7.727z"})});case"kakao":return d.jsx("svg",{className:t,viewBox:"0 0 24 24",children:d.jsx("path",{fill:"currentColor",d:"M12 3c5.799 0 10.5 3.664 10.5 8.185 0 4.52-4.701 8.184-10.5 8.184a13.5 13.5 0 0 1-1.727-.11l-4.408 2.883c-.501.265-.678.236-.472-.413l.892-3.678c-2.88-1.46-4.785-3.99-4.785-6.866C1.5 6.665 6.201 3 12 3zm5.519 8.625h-.634v2.916h-.97v-2.916h-.633v-.848h2.237v.848zm-3.294 2.916h-.97V10.77h.97v3.771zm-1.97 0h-.97v-2.77l-.658 1.53h-.629l-.665-1.53v2.77h-.97V10.77h1.13l.822 1.912.814-1.912h1.126v3.771zm-4.49 0h-.97V10.77h.97v3.771z"})});case"facebook":return d.jsx("svg",{className:t,viewBox:"0 0 24 24",children:d.jsx("path",{fill:"currentColor",d:"M24 12.073c0-6.627-5.373-12-12-12s-12 5.373-12 12c0 5.99 4.388 10.954 10.125 11.854v-8.385H7.078v-3.47h3.047V9.43c0-3.007 1.792-4.669 4.533-4.669 1.312 0 2.686.235 2.686.235v2.953H15.83c-1.491 0-1.956.925-1.956 1.874v2.25h3.328l-.532 3.47h-2.796v8.385C19.612 23.027 24 18.062 24 12.073z"})});case"apple":return d.jsx("svg",{className:t,viewBox:"0 0 24 24",children:d.jsx("path",{fill:"currentColor",d:"M12.152 6.896c-.948 0-2.415-1.078-3.96-1.04-2.04.027-3.91 1.183-4.961 3.014-2.117 3.675-.546 9.103 1.519 12.09 1.013 1.454 2.208 3.09 3.792 3.039 1.52-.065 2.09-.987 3.935-.987 1.831 0 2.35.987 3.96.948 1.637-.026 2.676-1.48 3.676-2.948 1.156-1.688 1.636-3.325 1.662-3.415-.039-.013-3.182-1.221-3.22-4.857-.026-3.04 2.48-4.494 2.597-4.559-1.429-2.09-3.623-2.324-4.39-2.376-2-.156-3.675 1.09-4.61 1.09zM15.53 3.83c.843-1.012 1.4-2.427 1.245-3.83-1.207.052-2.662.805-3.532 1.818-.78.896-1.454 2.338-1.273 3.714 1.338.104 2.715-.688 3.559-1.701"})});default:return null}},yc=(e,t)=>window.G7Core?.t?.(e,t)??e,Z0=[{id:"google",name:"Google",color:"bg-white dark:bg-gray-800",textColor:"text-gray-700 dark:text-gray-300"},{id:"naver",name:"네이버",color:"bg-[#03C75A]",textColor:"text-white"},{id:"kakao",name:"카카오",color:"bg-[#FEE500]",textColor:"text-[#000000]/85"},{id:"facebook",name:"Facebook",color:"bg-[#1877F2]",textColor:"text-white"},{id:"apple",name:"Apple",color:"bg-black dark:bg-white",textColor:"text-white dark:text-black"}],vc=({providers:e=["google","naver","kakao"],mode:t="login",variant:r="full",className:n="",id:o,editorAttrs:a})=>{const s=h=>{const c=encodeURIComponent(window.location.href);window.location.href=`/api/auth/${h}?redirect=${c}`},l=h=>t==="register"?yc("auth.social.register_with",{provider:h}):yc("auth.social.login_with",{provider:h}),f=Z0.filter(h=>e.includes(h.id));return r==="icon"?d.jsx(j,{className:`flex justify-center gap-4 ${n}`,id:o,...a,children:f.map(h=>d.jsx(R,{onClick:()=>s(h.id),className:`w-12 h-12 flex items-center justify-center rounded-full ${h.color} ${h.textColor} border border-gray-200 dark:border-gray-700 hover:opacity-90 transition-opacity`,"aria-label":l(h.name),children:d.jsx(bc,{provider:h.id,className:"w-5 h-5"})},h.id))}):d.jsx(j,{className:`space-y-3 ${n}`,id:o,...a,children:f.map(h=>d.jsxs(R,{onClick:()=>s(h.id),className:`w-full flex items-center justify-center gap-3 px-4 py-3 rounded-lg font-medium border border-gray-200 dark:border-gray-700 ${h.color} ${h.textColor} hover:opacity-90 transition-opacity`,children:[d.jsx(bc,{provider:h.id,className:"w-5 h-5"}),d.jsx(te,{children:l(h.name)})]},h.id))})},Q0=Object.freeze(Object.defineProperty({__proto__:null,default:vc},Symbol.toStringTag,{value:"Module"})),J0=(e,t)=>window.G7Core?.t?.(e,t)??e,R0={success:nt.CheckCircle,error:nt.XCircle,warning:nt.ExclamationTriangle,info:nt.InfoCircle},wc={success:{bg:"bg-green-50 dark:bg-green-900/20 border-green-200 dark:border-green-800",text:"text-green-800 dark:text-green-300"},error:{bg:"bg-red-50 dark:bg-red-900/20 border-red-200 dark:border-red-800",text:"text-red-800 dark:text-red-300"},warning:{bg:"bg-yellow-50 dark:bg-yellow-900/20 border-yellow-200 dark:border-yellow-800",text:"text-yellow-800 dark:text-yellow-300"},info:{bg:"bg-blue-50 dark:bg-blue-900/20 border-blue-200 dark:border-blue-800",text:"text-blue-800 dark:text-blue-300"}},ep={"top-left":"top-4 left-4","top-center":"top-4 left-1/2 -translate-x-1/2","top-right":"top-4 right-4","bottom-left":"bottom-4 left-4","bottom-center":"bottom-4 left-1/2 -translate-x-1/2","bottom-right":"bottom-4 right-4"},ja=({toasts:e,position:t="bottom-center",duration:r=3e3,onRemove:n})=>{const[o,a]=C.useState(new Map),s=C.useCallback(l=>{a(f=>{const h=new Map(f);return h.set(l,!1),h}),setTimeout(()=>{if(n)n(l);else{const f=window.G7Core;f?.state?.update&&f.state.update(h=>({toasts:(Array.isArray(h.toasts)?h.toasts:[]).filter(i=>i.id!==l)}))}},300)},[n]);return C.useEffect(()=>{!e||e.length===0||e.forEach(l=>{if(o.has(l.id))return;a(h=>{const c=new Map(h);return c.set(l.id,!0),c});const f=l.duration||r;f>0&&setTimeout(()=>{s(l.id)},f)})},[e,r,s,o]),!e||e.length===0?null:d.jsx(j,{className:`fixed z-[9999] flex flex-col gap-2 ${ep[t]}`,"aria-live":"polite","aria-atomic":"true",children:e.map(l=>d.jsx(tp,{toast:l,position:t,isVisible:o.get(l.id)??!0,onClose:()=>s(l.id)},l.id))})},tp=({toast:e,position:t,isVisible:r,onClose:n})=>{const o=wc[e.type]||wc.info,a=e.icon?e.icon:R0[e.type],s=t.startsWith("bottom")?"translate-y-2":"-translate-y-2";return d.jsxs(j,{className:` + `},Sh={onDragStart(e){let{active:t}=e;return"Picked up draggable item "+t.id+"."},onDragOver(e){let{active:t,over:r}=e;return r?"Draggable item "+t.id+" was moved over droppable area "+r.id+".":"Draggable item "+t.id+" is no longer over a droppable area."},onDragEnd(e){let{active:t,over:r}=e;return r?"Draggable item "+t.id+" was dropped over droppable area "+r.id:"Draggable item "+t.id+" was dropped."},onDragCancel(e){let{active:t}=e;return"Dragging was cancelled. Draggable item "+t.id+" was dropped."}};function Eh(e){let{announcements:t=Sh,container:r,hiddenTextDescribedById:n,screenReaderInstructions:o=_h}=e;const{announce:a,announcement:s}=xh(),l=gn("DndLiveRegion"),[f,h]=C.useState(!1);if(C.useEffect(()=>{h(!0)},[]),kh(C.useMemo(()=>({onDragStart(i){let{active:u}=i;a(t.onDragStart({active:u}))},onDragMove(i){let{active:u,over:g}=i;t.onDragMove&&a(t.onDragMove({active:u,over:g}))},onDragOver(i){let{active:u,over:g}=i;a(t.onDragOver({active:u,over:g}))},onDragEnd(i){let{active:u,over:g}=i;a(t.onDragEnd({active:u,over:g}))},onDragCancel(i){let{active:u,over:g}=i;a(t.onDragCancel({active:u,over:g}))}}),[a,t])),!f)return null;const c=C.createElement(C.Fragment,null,C.createElement(vh,{id:n,value:o.draggable}),C.createElement(wh,{id:l,announcement:s}));return r?tr.createPortal(c,r):c}var st;(function(e){e.DragStart="dragStart",e.DragMove="dragMove",e.DragEnd="dragEnd",e.DragCancel="dragCancel",e.DragOver="dragOver",e.RegisterDroppable="registerDroppable",e.SetDroppableDisabled="setDroppableDisabled",e.UnregisterDroppable="unregisterDroppable"})(st||(st={}));function Rn(){}function Ii(e,t){return C.useMemo(()=>({sensor:e,options:t??{}}),[e,t])}function Th(){for(var e=arguments.length,t=new Array(e),r=0;r[...t].filter(n=>n!=null),[...t])}const qt=Object.freeze({x:0,y:0});function Di(e,t){return Math.sqrt(Math.pow(e.x-t.x,2)+Math.pow(e.y-t.y,2))}function Pi(e,t){let{data:{value:r}}=e,{data:{value:n}}=t;return r-n}function Ah(e,t){let{data:{value:r}}=e,{data:{value:n}}=t;return n-r}function Li(e){let{left:t,top:r,height:n,width:o}=e;return[{x:t,y:r},{x:t+o,y:r},{x:t,y:r+n},{x:t+o,y:r+n}]}function $i(e,t){if(!e||e.length===0)return null;const[r]=e;return r[t]}function Fi(e,t,r){return t===void 0&&(t=e.left),r===void 0&&(r=e.top),{x:t+e.width*.5,y:r+e.height*.5}}const Nh=e=>{let{collisionRect:t,droppableRects:r,droppableContainers:n}=e;const o=Fi(t,t.left,t.top),a=[];for(const s of n){const{id:l}=s,f=r.get(l);if(f){const h=Di(Fi(f),o);a.push({id:l,data:{droppableContainer:s,value:h}})}}return a.sort(Pi)},Oh=e=>{let{collisionRect:t,droppableRects:r,droppableContainers:n}=e;const o=Li(t),a=[];for(const s of n){const{id:l}=s,f=r.get(l);if(f){const h=Li(f),c=o.reduce((u,g,p)=>u+Di(h[p],g),0),i=Number((c/4).toFixed(4));a.push({id:l,data:{droppableContainer:s,value:i}})}}return a.sort(Pi)};function Mh(e,t){const r=Math.max(t.top,e.top),n=Math.max(t.left,e.left),o=Math.min(t.left+t.width,e.left+e.width),a=Math.min(t.top+t.height,e.top+e.height),s=o-n,l=a-r;if(n{let{collisionRect:t,droppableRects:r,droppableContainers:n}=e;const o=[];for(const a of n){const{id:s}=a,l=r.get(s);if(l){const f=Mh(l,t);f>0&&o.push({id:s,data:{droppableContainer:a,value:f}})}}return o.sort(Ah)};function Dh(e,t,r){return{...e,scaleX:t&&r?t.width/r.width:1,scaleY:t&&r?t.height/r.height:1}}function Ui(e,t){return e&&t?{x:e.left-t.left,y:e.top-t.top}:qt}function Ph(e){return function(r){for(var n=arguments.length,o=new Array(n>1?n-1:0),a=1;a({...s,top:s.top+e*l.y,bottom:s.bottom+e*l.y,left:s.left+e*l.x,right:s.right+e*l.x}),{...r})}}const Lh=Ph(1);function $h(e){if(e.startsWith("matrix3d(")){const t=e.slice(9,-1).split(/, /);return{x:+t[12],y:+t[13],scaleX:+t[0],scaleY:+t[5]}}else if(e.startsWith("matrix(")){const t=e.slice(7,-1).split(/, /);return{x:+t[4],y:+t[5],scaleX:+t[0],scaleY:+t[3]}}return null}function Fh(e,t,r){const n=$h(t);if(!n)return e;const{scaleX:o,scaleY:a,x:s,y:l}=n,f=e.left-s-(1-o)*parseFloat(r),h=e.top-l-(1-a)*parseFloat(r.slice(r.indexOf(" ")+1)),c=o?e.width/o:e.width,i=a?e.height/a:e.height;return{width:c,height:i,top:h,right:f+c,bottom:h+i,left:f}}const Uh={ignoreTransform:!1};function Wr(e,t){t===void 0&&(t=Uh);let r=e.getBoundingClientRect();if(t.ignoreTransform){const{transform:h,transformOrigin:c}=Ct(e).getComputedStyle(e);h&&(r=Fh(r,h,c))}const{top:n,left:o,width:a,height:s,bottom:l,right:f}=r;return{top:n,left:o,width:a,height:s,bottom:l,right:f}}function zi(e){return Wr(e,{ignoreTransform:!0})}function zh(e){const t=e.innerWidth,r=e.innerHeight;return{top:0,left:0,right:t,bottom:r,width:t,height:r}}function Hh(e,t){return t===void 0&&(t=Ct(e).getComputedStyle(e)),t.position==="fixed"}function Gh(e,t){t===void 0&&(t=Ct(e).getComputedStyle(e));const r=/(auto|scroll|overlay)/;return["overflow","overflowX","overflowY"].some(o=>{const a=t[o];return typeof a=="string"?r.test(a):!1})}function eo(e,t){const r=[];function n(o){if(t!=null&&r.length>=t||!o)return r;if(Sa(o)&&o.scrollingElement!=null&&!r.includes(o.scrollingElement))return r.push(o.scrollingElement),r;if(!dn(o)||Ai(o)||r.includes(o))return r;const a=Ct(e).getComputedStyle(o);return o!==e&&Gh(o,a)&&r.push(o),Hh(o,a)?r:n(o.parentNode)}return e?n(e):r}function Hi(e){const[t]=eo(e,1);return t??null}function Ma(e){return!Qn||!e?null:Gr(e)?e:_a(e)?Sa(e)||e===jr(e).scrollingElement?window:dn(e)?e:null:null}function Gi(e){return Gr(e)?e.scrollX:e.scrollLeft}function ji(e){return Gr(e)?e.scrollY:e.scrollTop}function Ia(e){return{x:Gi(e),y:ji(e)}}var ut;(function(e){e[e.Forward=1]="Forward",e[e.Backward=-1]="Backward"})(ut||(ut={}));function Bi(e){return!Qn||!e?!1:e===document.scrollingElement}function Wi(e){const t={x:0,y:0},r=Bi(e)?{height:window.innerHeight,width:window.innerWidth}:{height:e.clientHeight,width:e.clientWidth},n={x:e.scrollWidth-r.width,y:e.scrollHeight-r.height},o=e.scrollTop<=t.y,a=e.scrollLeft<=t.x,s=e.scrollTop>=n.y,l=e.scrollLeft>=n.x;return{isTop:o,isLeft:a,isBottom:s,isRight:l,maxScroll:n,minScroll:t}}const jh={x:.2,y:.2};function Bh(e,t,r,n,o){let{top:a,left:s,right:l,bottom:f}=r;n===void 0&&(n=10),o===void 0&&(o=jh);const{isTop:h,isBottom:c,isLeft:i,isRight:u}=Wi(e),g={x:0,y:0},p={x:0,y:0},m={height:t.height*o.y,width:t.width*o.x};return!h&&a<=t.top+m.height?(g.y=ut.Backward,p.y=n*Math.abs((t.top+m.height-a)/m.height)):!c&&f>=t.bottom-m.height&&(g.y=ut.Forward,p.y=n*Math.abs((t.bottom-m.height-f)/m.height)),!u&&l>=t.right-m.width?(g.x=ut.Forward,p.x=n*Math.abs((t.right-m.width-l)/m.width)):!i&&s<=t.left+m.width&&(g.x=ut.Backward,p.x=n*Math.abs((t.left+m.width-s)/m.width)),{direction:g,speed:p}}function Wh(e){if(e===document.scrollingElement){const{innerWidth:a,innerHeight:s}=window;return{top:0,left:0,right:a,bottom:s,width:a,height:s}}const{top:t,left:r,right:n,bottom:o}=e.getBoundingClientRect();return{top:t,left:r,right:n,bottom:o,width:e.clientWidth,height:e.clientHeight}}function Vi(e){return e.reduce((t,r)=>Br(t,Ia(r)),qt)}function Vh(e){return e.reduce((t,r)=>t+Gi(r),0)}function Kh(e){return e.reduce((t,r)=>t+ji(r),0)}function Yh(e,t){if(t===void 0&&(t=Wr),!e)return;const{top:r,left:n,bottom:o,right:a}=t(e);Hi(e)&&(o<=0||a<=0||r>=window.innerHeight||n>=window.innerWidth)&&e.scrollIntoView({block:"center",inline:"center"})}const Xh=[["x",["left","right"],Vh],["y",["top","bottom"],Kh]];class Da{constructor(t,r){this.rect=void 0,this.width=void 0,this.height=void 0,this.top=void 0,this.bottom=void 0,this.right=void 0,this.left=void 0;const n=eo(r),o=Vi(n);this.rect={...t},this.width=t.width,this.height=t.height;for(const[a,s,l]of Xh)for(const f of s)Object.defineProperty(this,f,{get:()=>{const h=l(n),c=o[a]-h;return this.rect[f]+c},enumerable:!0});Object.defineProperty(this,"rect",{enumerable:!1})}}class mn{constructor(t){this.target=void 0,this.listeners=[],this.removeAll=()=>{this.listeners.forEach(r=>{var n;return(n=this.target)==null?void 0:n.removeEventListener(...r)})},this.target=t}add(t,r,n){var o;(o=this.target)==null||o.addEventListener(t,r,n),this.listeners.push([t,r,n])}}function qh(e){const{EventTarget:t}=Ct(e);return e instanceof t?e:jr(e)}function Pa(e,t){const r=Math.abs(e.x),n=Math.abs(e.y);return typeof t=="number"?Math.sqrt(r**2+n**2)>t:"x"in t&&"y"in t?r>t.x&&n>t.y:"x"in t?r>t.x:"y"in t?n>t.y:!1}var zt;(function(e){e.Click="click",e.DragStart="dragstart",e.Keydown="keydown",e.ContextMenu="contextmenu",e.Resize="resize",e.SelectionChange="selectionchange",e.VisibilityChange="visibilitychange"})(zt||(zt={}));function Ki(e){e.preventDefault()}function Zh(e){e.stopPropagation()}var Ue;(function(e){e.Space="Space",e.Down="ArrowDown",e.Right="ArrowRight",e.Left="ArrowLeft",e.Up="ArrowUp",e.Esc="Escape",e.Enter="Enter",e.Tab="Tab"})(Ue||(Ue={}));const Yi={start:[Ue.Space,Ue.Enter],cancel:[Ue.Esc],end:[Ue.Space,Ue.Enter,Ue.Tab]},Qh=(e,t)=>{let{currentCoordinates:r}=t;switch(e.code){case Ue.Right:return{...r,x:r.x+25};case Ue.Left:return{...r,x:r.x-25};case Ue.Down:return{...r,y:r.y+25};case Ue.Up:return{...r,y:r.y-25}}};class La{constructor(t){this.props=void 0,this.autoScrollEnabled=!1,this.referenceCoordinates=void 0,this.listeners=void 0,this.windowListeners=void 0,this.props=t;const{event:{target:r}}=t;this.props=t,this.listeners=new mn(jr(r)),this.windowListeners=new mn(Ct(r)),this.handleKeyDown=this.handleKeyDown.bind(this),this.handleCancel=this.handleCancel.bind(this),this.attach()}attach(){this.handleStart(),this.windowListeners.add(zt.Resize,this.handleCancel),this.windowListeners.add(zt.VisibilityChange,this.handleCancel),setTimeout(()=>this.listeners.add(zt.Keydown,this.handleKeyDown))}handleStart(){const{activeNode:t,onStart:r}=this.props,n=t.node.current;n&&Yh(n),r(qt)}handleKeyDown(t){if(Na(t)){const{active:r,context:n,options:o}=this.props,{keyboardCodes:a=Yi,coordinateGetter:s=Qh,scrollBehavior:l="smooth"}=o,{code:f}=t;if(a.end.includes(f)){this.handleEnd(t);return}if(a.cancel.includes(f)){this.handleCancel(t);return}const{collisionRect:h}=n.current,c=h?{x:h.left,y:h.top}:qt;this.referenceCoordinates||(this.referenceCoordinates=c);const i=s(t,{active:r,context:n.current,currentCoordinates:c});if(i){const u=hn(i,c),g={x:0,y:0},{scrollableAncestors:p}=n.current;for(const m of p){const x=t.code,{isTop:k,isRight:M,isLeft:v,isBottom:T,maxScroll:I,minScroll:S}=Wi(m),N=Wh(m),w={x:Math.min(x===Ue.Right?N.right-N.width/2:N.right,Math.max(x===Ue.Right?N.left:N.left+N.width/2,i.x)),y:Math.min(x===Ue.Down?N.bottom-N.height/2:N.bottom,Math.max(x===Ue.Down?N.top:N.top+N.height/2,i.y))},y=x===Ue.Right&&!M||x===Ue.Left&&!v,E=x===Ue.Down&&!T||x===Ue.Up&&!k;if(y&&w.x!==i.x){const O=m.scrollLeft+u.x,$=x===Ue.Right&&O<=I.x||x===Ue.Left&&O>=S.x;if($&&!u.y){m.scrollTo({left:O,behavior:l});return}$?g.x=m.scrollLeft-O:g.x=x===Ue.Right?m.scrollLeft-I.x:m.scrollLeft-S.x,g.x&&m.scrollBy({left:-g.x,behavior:l});break}else if(E&&w.y!==i.y){const O=m.scrollTop+u.y,$=x===Ue.Down&&O<=I.y||x===Ue.Up&&O>=S.y;if($&&!u.x){m.scrollTo({top:O,behavior:l});return}$?g.y=m.scrollTop-O:g.y=x===Ue.Down?m.scrollTop-I.y:m.scrollTop-S.y,g.y&&m.scrollBy({top:-g.y,behavior:l});break}}this.handleMove(t,Br(hn(i,this.referenceCoordinates),g))}}}handleMove(t,r){const{onMove:n}=this.props;t.preventDefault(),n(r)}handleEnd(t){const{onEnd:r}=this.props;t.preventDefault(),this.detach(),r()}handleCancel(t){const{onCancel:r}=this.props;t.preventDefault(),this.detach(),r()}detach(){this.listeners.removeAll(),this.windowListeners.removeAll()}}La.activators=[{eventName:"onKeyDown",handler:(e,t,r)=>{let{keyboardCodes:n=Yi,onActivation:o}=t,{active:a}=r;const{code:s}=e.nativeEvent;if(n.start.includes(s)){const l=a.activatorNode.current;return l&&e.target!==l?!1:(e.preventDefault(),o?.({event:e.nativeEvent}),!0)}return!1}}];function Xi(e){return!!(e&&"distance"in e)}function qi(e){return!!(e&&"delay"in e)}class $a{constructor(t,r,n){var o;n===void 0&&(n=qh(t.event.target)),this.props=void 0,this.events=void 0,this.autoScrollEnabled=!0,this.document=void 0,this.activated=!1,this.initialCoordinates=void 0,this.timeoutId=null,this.listeners=void 0,this.documentListeners=void 0,this.windowListeners=void 0,this.props=t,this.events=r;const{event:a}=t,{target:s}=a;this.props=t,this.events=r,this.document=jr(s),this.documentListeners=new mn(this.document),this.listeners=new mn(n),this.windowListeners=new mn(Ct(s)),this.initialCoordinates=(o=Oa(a))!=null?o:qt,this.handleStart=this.handleStart.bind(this),this.handleMove=this.handleMove.bind(this),this.handleEnd=this.handleEnd.bind(this),this.handleCancel=this.handleCancel.bind(this),this.handleKeydown=this.handleKeydown.bind(this),this.removeTextSelection=this.removeTextSelection.bind(this),this.attach()}attach(){const{events:t,props:{options:{activationConstraint:r,bypassActivationConstraint:n}}}=this;if(this.listeners.add(t.move.name,this.handleMove,{passive:!1}),this.listeners.add(t.end.name,this.handleEnd),t.cancel&&this.listeners.add(t.cancel.name,this.handleCancel),this.windowListeners.add(zt.Resize,this.handleCancel),this.windowListeners.add(zt.DragStart,Ki),this.windowListeners.add(zt.VisibilityChange,this.handleCancel),this.windowListeners.add(zt.ContextMenu,Ki),this.documentListeners.add(zt.Keydown,this.handleKeydown),r){if(n!=null&&n({event:this.props.event,activeNode:this.props.activeNode,options:this.props.options}))return this.handleStart();if(qi(r)){this.timeoutId=setTimeout(this.handleStart,r.delay),this.handlePending(r);return}if(Xi(r)){this.handlePending(r);return}}this.handleStart()}detach(){this.listeners.removeAll(),this.windowListeners.removeAll(),setTimeout(this.documentListeners.removeAll,50),this.timeoutId!==null&&(clearTimeout(this.timeoutId),this.timeoutId=null)}handlePending(t,r){const{active:n,onPending:o}=this.props;o(n,t,this.initialCoordinates,r)}handleStart(){const{initialCoordinates:t}=this,{onStart:r}=this.props;t&&(this.activated=!0,this.documentListeners.add(zt.Click,Zh,{capture:!0}),this.removeTextSelection(),this.documentListeners.add(zt.SelectionChange,this.removeTextSelection),r(t))}handleMove(t){var r;const{activated:n,initialCoordinates:o,props:a}=this,{onMove:s,options:{activationConstraint:l}}=a;if(!o)return;const f=(r=Oa(t))!=null?r:qt,h=hn(o,f);if(!n&&l){if(Xi(l)){if(l.tolerance!=null&&Pa(h,l.tolerance))return this.handleCancel();if(Pa(h,l.distance))return this.handleStart()}if(qi(l)&&Pa(h,l.tolerance))return this.handleCancel();this.handlePending(l,h);return}t.cancelable&&t.preventDefault(),s(f)}handleEnd(){const{onAbort:t,onEnd:r}=this.props;this.detach(),this.activated||t(this.props.active),r()}handleCancel(){const{onAbort:t,onCancel:r}=this.props;this.detach(),this.activated||t(this.props.active),r()}handleKeydown(t){t.code===Ue.Esc&&this.handleCancel()}removeTextSelection(){var t;(t=this.document.getSelection())==null||t.removeAllRanges()}}const Jh={cancel:{name:"pointercancel"},move:{name:"pointermove"},end:{name:"pointerup"}};class Fa extends $a{constructor(t){const{event:r}=t,n=jr(r.target);super(t,Jh,n)}}Fa.activators=[{eventName:"onPointerDown",handler:(e,t)=>{let{nativeEvent:r}=e,{onActivation:n}=t;return!r.isPrimary||r.button!==0?!1:(n?.({event:r}),!0)}}];const Rh={move:{name:"mousemove"},end:{name:"mouseup"}};var Ua;(function(e){e[e.RightClick=2]="RightClick"})(Ua||(Ua={}));class e0 extends $a{constructor(t){super(t,Rh,jr(t.event.target))}}e0.activators=[{eventName:"onMouseDown",handler:(e,t)=>{let{nativeEvent:r}=e,{onActivation:n}=t;return r.button===Ua.RightClick?!1:(n?.({event:r}),!0)}}];const za={cancel:{name:"touchcancel"},move:{name:"touchmove"},end:{name:"touchend"}};class t0 extends $a{constructor(t){super(t,za)}static setup(){return window.addEventListener(za.move.name,t,{capture:!1,passive:!1}),function(){window.removeEventListener(za.move.name,t)};function t(){}}}t0.activators=[{eventName:"onTouchStart",handler:(e,t)=>{let{nativeEvent:r}=e,{onActivation:n}=t;const{touches:o}=r;return o.length>1?!1:(n?.({event:r}),!0)}}];var bn;(function(e){e[e.Pointer=0]="Pointer",e[e.DraggableRect=1]="DraggableRect"})(bn||(bn={}));var to;(function(e){e[e.TreeOrder=0]="TreeOrder",e[e.ReversedTreeOrder=1]="ReversedTreeOrder"})(to||(to={}));function r0(e){let{acceleration:t,activator:r=bn.Pointer,canScroll:n,draggingRect:o,enabled:a,interval:s=5,order:l=to.TreeOrder,pointerCoordinates:f,scrollableAncestors:h,scrollableAncestorRects:c,delta:i,threshold:u}=e;const g=o0({delta:i,disabled:!a}),[p,m]=hh(),x=C.useRef({x:0,y:0}),k=C.useRef({x:0,y:0}),M=C.useMemo(()=>{switch(r){case bn.Pointer:return f?{top:f.y,bottom:f.y,left:f.x,right:f.x}:null;case bn.DraggableRect:return o}},[r,o,f]),v=C.useRef(null),T=C.useCallback(()=>{const S=v.current;if(!S)return;const N=x.current.x*k.current.x,w=x.current.y*k.current.y;S.scrollBy(N,w)},[]),I=C.useMemo(()=>l===to.TreeOrder?[...h].reverse():h,[l,h]);C.useEffect(()=>{if(!a||!h.length||!M){m();return}for(const S of I){if(n?.(S)===!1)continue;const N=h.indexOf(S),w=c[N];if(!w)continue;const{direction:y,speed:E}=Bh(S,w,M,t,u);for(const O of["x","y"])g[O][y[O]]||(E[O]=0,y[O]=0);if(E.x>0||E.y>0){m(),v.current=S,p(T,s),x.current=E,k.current=y;return}}x.current={x:0,y:0},k.current={x:0,y:0},m()},[t,T,n,m,a,s,JSON.stringify(M),JSON.stringify(g),p,h,I,c,JSON.stringify(u)])}const n0={x:{[ut.Backward]:!1,[ut.Forward]:!1},y:{[ut.Backward]:!1,[ut.Forward]:!1}};function o0(e){let{delta:t,disabled:r}=e;const n=Ta(t);return fn(o=>{if(r||!n||!o)return n0;const a={x:Math.sign(t.x-n.x),y:Math.sign(t.y-n.y)};return{x:{[ut.Backward]:o.x[ut.Backward]||a.x===-1,[ut.Forward]:o.x[ut.Forward]||a.x===1},y:{[ut.Backward]:o.y[ut.Backward]||a.y===-1,[ut.Forward]:o.y[ut.Forward]||a.y===1}}},[r,t,n])}function a0(e,t){const r=t!=null?e.get(t):void 0,n=r?r.node.current:null;return fn(o=>{var a;return t==null?null:(a=n??o)!=null?a:null},[n,t])}function s0(e,t){return C.useMemo(()=>e.reduce((r,n)=>{const{sensor:o}=n,a=o.activators.map(s=>({eventName:s.eventName,handler:t(s.handler,n)}));return[...r,...a]},[]),[e,t])}var yn;(function(e){e[e.Always=0]="Always",e[e.BeforeDragging=1]="BeforeDragging",e[e.WhileDragging=2]="WhileDragging"})(yn||(yn={}));var Ha;(function(e){e.Optimized="optimized"})(Ha||(Ha={}));const Zi=new Map;function l0(e,t){let{dragging:r,dependencies:n,config:o}=t;const[a,s]=C.useState(null),{frequency:l,measure:f,strategy:h}=o,c=C.useRef(e),i=x(),u=un(i),g=C.useCallback(function(k){k===void 0&&(k=[]),!u.current&&s(M=>M===null?k:M.concat(k.filter(v=>!M.includes(v))))},[u]),p=C.useRef(null),m=fn(k=>{if(i&&!r)return Zi;if(!k||k===Zi||c.current!==e||a!=null){const M=new Map;for(let v of e){if(!v)continue;if(a&&a.length>0&&!a.includes(v.id)&&v.rect.current){M.set(v.id,v.rect.current);continue}const T=v.node.current,I=T?new Da(f(T),T):null;v.rect.current=I,I&&M.set(v.id,I)}return M}return k},[e,a,r,i,f]);return C.useEffect(()=>{c.current=e},[e]),C.useEffect(()=>{i||g()},[r,i]),C.useEffect(()=>{a&&a.length>0&&s(null)},[JSON.stringify(a)]),C.useEffect(()=>{i||typeof l!="number"||p.current!==null||(p.current=setTimeout(()=>{g(),p.current=null},l))},[l,i,g,...n]),{droppableRects:m,measureDroppableContainers:g,measuringScheduled:a!=null};function x(){switch(h){case yn.Always:return!1;case yn.BeforeDragging:return r;default:return!r}}}function Qi(e,t){return fn(r=>e?r||(typeof t=="function"?t(e):e):null,[t,e])}function i0(e,t){return Qi(e,t)}function c0(e){let{callback:t,disabled:r}=e;const n=Ea(t),o=C.useMemo(()=>{if(r||typeof window>"u"||typeof window.MutationObserver>"u")return;const{MutationObserver:a}=window;return new a(n)},[n,r]);return C.useEffect(()=>()=>o?.disconnect(),[o]),o}function ro(e){let{callback:t,disabled:r}=e;const n=Ea(t),o=C.useMemo(()=>{if(r||typeof window>"u"||typeof window.ResizeObserver>"u")return;const{ResizeObserver:a}=window;return new a(n)},[r]);return C.useEffect(()=>()=>o?.disconnect(),[o]),o}function d0(e){return new Da(Wr(e),e)}function Ji(e,t,r){t===void 0&&(t=d0);const[n,o]=C.useState(null);function a(){o(f=>{if(!e)return null;if(e.isConnected===!1){var h;return(h=f??r)!=null?h:null}const c=t(e);return JSON.stringify(f)===JSON.stringify(c)?f:c})}const s=c0({callback(f){if(e)for(const h of f){const{type:c,target:i}=h;if(c==="childList"&&i instanceof HTMLElement&&i.contains(e)){a();break}}}}),l=ro({callback:a});return Jt(()=>{a(),e?(l?.observe(e),s?.observe(document.body,{childList:!0,subtree:!0})):(l?.disconnect(),s?.disconnect())},[e]),n}function u0(e){const t=Qi(e);return Ui(e,t)}const Ri=[];function f0(e){const t=C.useRef(e),r=fn(n=>e?n&&n!==Ri&&e&&t.current&&e.parentNode===t.current.parentNode?n:eo(e):Ri,[e]);return C.useEffect(()=>{t.current=e},[e]),r}function g0(e){const[t,r]=C.useState(null),n=C.useRef(e),o=C.useCallback(a=>{const s=Ma(a.target);s&&r(l=>l?(l.set(s,Ia(s)),new Map(l)):null)},[]);return C.useEffect(()=>{const a=n.current;if(e!==a){s(a);const l=e.map(f=>{const h=Ma(f);return h?(h.addEventListener("scroll",o,{passive:!0}),[h,Ia(h)]):null}).filter(f=>f!=null);r(l.length?new Map(l):null),n.current=e}return()=>{s(e),s(a)};function s(l){l.forEach(f=>{const h=Ma(f);h?.removeEventListener("scroll",o)})}},[o,e]),C.useMemo(()=>e.length?t?Array.from(t.values()).reduce((a,s)=>Br(a,s),qt):Vi(e):qt,[e,t])}function ec(e,t){t===void 0&&(t=[]);const r=C.useRef(null);return C.useEffect(()=>{r.current=null},t),C.useEffect(()=>{const n=e!==qt;n&&!r.current&&(r.current=e),!n&&r.current&&(r.current=null)},[e]),r.current?hn(e,r.current):qt}function h0(e){C.useEffect(()=>{if(!Qn)return;const t=e.map(r=>{let{sensor:n}=r;return n.setup==null?void 0:n.setup()});return()=>{for(const r of t)r?.()}},e.map(t=>{let{sensor:r}=t;return r}))}function p0(e,t){return C.useMemo(()=>e.reduce((r,n)=>{let{eventName:o,handler:a}=n;return r[o]=s=>{a(s,t)},r},{}),[e,t])}function tc(e){return C.useMemo(()=>e?zh(e):null,[e])}const rc=[];function m0(e,t){t===void 0&&(t=Wr);const[r]=e,n=tc(r?Ct(r):null),[o,a]=C.useState(rc);function s(){a(()=>e.length?e.map(f=>Bi(f)?n:new Da(t(f),f)):rc)}const l=ro({callback:s});return Jt(()=>{l?.disconnect(),s(),e.forEach(f=>l?.observe(f))},[e]),o}function b0(e){if(!e)return null;if(e.children.length>1)return e;const t=e.children[0];return dn(t)?t:e}function y0(e){let{measure:t}=e;const[r,n]=C.useState(null),o=C.useCallback(h=>{for(const{target:c}of h)if(dn(c)){n(i=>{const u=t(c);return i?{...i,width:u.width,height:u.height}:u});break}},[t]),a=ro({callback:o}),s=C.useCallback(h=>{const c=b0(h);a?.disconnect(),c&&a?.observe(c),n(c?t(c):null)},[t,a]),[l,f]=Jn(s);return C.useMemo(()=>({nodeRef:l,rect:r,setRef:f}),[r,l,f])}const v0=[{sensor:Fa,options:{}},{sensor:La,options:{}}],w0={current:{}},no={draggable:{measure:zi},droppable:{measure:zi,strategy:yn.WhileDragging,frequency:Ha.Optimized},dragOverlay:{measure:Wr}};class vn extends Map{get(t){var r;return t!=null&&(r=super.get(t))!=null?r:void 0}toArray(){return Array.from(this.values())}getEnabled(){return this.toArray().filter(t=>{let{disabled:r}=t;return!r})}getNodeFor(t){var r,n;return(r=(n=this.get(t))==null?void 0:n.node.current)!=null?r:void 0}}const x0={activatorEvent:null,active:null,activeNode:null,activeNodeRect:null,collisions:null,containerNodeRect:null,draggableNodes:new Map,droppableRects:new Map,droppableContainers:new vn,over:null,dragOverlay:{nodeRef:{current:null},rect:null,setRef:Rn},scrollableAncestors:[],scrollableAncestorRects:[],measuringConfiguration:no,measureDroppableContainers:Rn,windowRect:null,measuringScheduled:!1},k0={activatorEvent:null,activators:[],active:null,activeNodeRect:null,ariaDescribedById:{draggable:""},dispatch:Rn,draggableNodes:new Map,over:null,measureDroppableContainers:Rn},oo=C.createContext(k0),nc=C.createContext(x0);function C0(){return{draggable:{active:null,initialCoordinates:{x:0,y:0},nodes:new Map,translate:{x:0,y:0}},droppable:{containers:new vn}}}function _0(e,t){switch(t.type){case st.DragStart:return{...e,draggable:{...e.draggable,initialCoordinates:t.initialCoordinates,active:t.active}};case st.DragMove:return e.draggable.active==null?e:{...e,draggable:{...e.draggable,translate:{x:t.coordinates.x-e.draggable.initialCoordinates.x,y:t.coordinates.y-e.draggable.initialCoordinates.y}}};case st.DragEnd:case st.DragCancel:return{...e,draggable:{...e.draggable,active:null,initialCoordinates:{x:0,y:0},translate:{x:0,y:0}}};case st.RegisterDroppable:{const{element:r}=t,{id:n}=r,o=new vn(e.droppable.containers);return o.set(n,r),{...e,droppable:{...e.droppable,containers:o}}}case st.SetDroppableDisabled:{const{id:r,key:n,disabled:o}=t,a=e.droppable.containers.get(r);if(!a||n!==a.key)return e;const s=new vn(e.droppable.containers);return s.set(r,{...a,disabled:o}),{...e,droppable:{...e.droppable,containers:s}}}case st.UnregisterDroppable:{const{id:r,key:n}=t,o=e.droppable.containers.get(r);if(!o||n!==o.key)return e;const a=new vn(e.droppable.containers);return a.delete(r),{...e,droppable:{...e.droppable,containers:a}}}default:return e}}function S0(e){let{disabled:t}=e;const{active:r,activatorEvent:n,draggableNodes:o}=C.useContext(oo),a=Ta(n),s=Ta(r?.id);return C.useEffect(()=>{if(!t&&!n&&a&&s!=null){if(!Na(a)||document.activeElement===a.target)return;const l=o.get(s);if(!l)return;const{activatorNode:f,node:h}=l;if(!f.current&&!h.current)return;requestAnimationFrame(()=>{for(const c of[f.current,h.current]){if(!c)continue;const i=bh(c);if(i){i.focus();break}}})}},[n,t,o,s,a]),null}function E0(e,t){let{transform:r,...n}=t;return e!=null&&e.length?e.reduce((o,a)=>a({transform:o,...n}),r):r}function T0(e){return C.useMemo(()=>({draggable:{...no.draggable,...e?.draggable},droppable:{...no.droppable,...e?.droppable},dragOverlay:{...no.dragOverlay,...e?.dragOverlay}}),[e?.draggable,e?.droppable,e?.dragOverlay])}function A0(e){let{activeNode:t,measure:r,initialRect:n,config:o=!0}=e;const a=C.useRef(!1),{x:s,y:l}=typeof o=="boolean"?{x:o,y:o}:o;Jt(()=>{if(!s&&!l||!t){a.current=!1;return}if(a.current||!n)return;const h=t?.node.current;if(!h||h.isConnected===!1)return;const c=r(h),i=Ui(c,n);if(s||(i.x=0),l||(i.y=0),a.current=!0,Math.abs(i.x)>0||Math.abs(i.y)>0){const u=Hi(h);u&&u.scrollBy({top:i.y,left:i.x})}},[t,s,l,n,r])}const oc=C.createContext({...qt,scaleX:1,scaleY:1});var ar;(function(e){e[e.Uninitialized=0]="Uninitialized",e[e.Initializing=1]="Initializing",e[e.Initialized=2]="Initialized"})(ar||(ar={}));const N0=C.memo(function(t){var r,n,o,a;let{id:s,accessibility:l,autoScroll:f=!0,children:h,sensors:c=v0,collisionDetection:i=Ih,measuring:u,modifiers:g,...p}=t;const m=C.useReducer(_0,void 0,C0),[x,k]=m,[M,v]=Ch(),[T,I]=C.useState(ar.Uninitialized),S=T===ar.Initialized,{draggable:{active:N,nodes:w,translate:y},droppable:{containers:E}}=x,O=N!=null?w.get(N):null,$=C.useRef({initial:null,translated:null}),P=C.useMemo(()=>{var _e;return N!=null?{id:N,data:(_e=O?.data)!=null?_e:w0,rect:$}:null},[N,O]),L=C.useRef(null),[U,_]=C.useState(null),[b,z]=C.useState(null),F=un(p,Object.values(p)),G=gn("DndDescribedBy",s),X=C.useMemo(()=>E.getEnabled(),[E]),B=T0(u),{droppableRects:Y,measureDroppableContainers:H,measuringScheduled:W}=l0(X,{dragging:S,dependencies:[y.x,y.y],config:B.droppable}),Q=a0(w,N),be=C.useMemo(()=>b?Oa(b):null,[b]),se=je(),pe=i0(Q,B.draggable.measure);A0({activeNode:N!=null?w.get(N):null,config:se.layoutShiftCompensation,initialRect:pe,measure:B.draggable.measure});const ne=Ji(Q,B.draggable.measure,pe),Ae=Ji(Q?Q.parentElement:null),ve=C.useRef({activatorEvent:null,active:null,activeNode:Q,collisionRect:null,collisions:null,droppableRects:Y,draggableNodes:w,draggingNode:null,draggingNodeRect:null,droppableContainers:E,over:null,scrollableAncestors:[],scrollAdjustedTranslate:null}),We=E.getNodeFor((r=ve.current.over)==null?void 0:r.id),me=y0({measure:B.dragOverlay.measure}),Te=(n=me.nodeRef.current)!=null?n:Q,ge=S?(o=me.rect)!=null?o:ne:null,Oe=!!(me.nodeRef.current&&me.rect),we=u0(Oe?null:ne),$e=tc(Te?Ct(Te):null),ze=f0(S?We??Q:null),K=m0(ze),oe=E0(g,{transform:{x:y.x-we.x,y:y.y-we.y,scaleX:1,scaleY:1},activatorEvent:b,active:P,activeNodeRect:ne,containerNodeRect:Ae,draggingNodeRect:ge,over:ve.current.over,overlayNodeRect:me.rect,scrollableAncestors:ze,scrollableAncestorRects:K,windowRect:$e}),ae=be?Br(be,y):null,ce=g0(ze),ke=ec(ce),Ee=ec(ce,[ne]),Be=Br(oe,ke),Me=ge?Lh(ge,oe):null,Ke=P&&Me?i({active:P,collisionRect:Me,droppableRects:Y,droppableContainers:X,pointerCoordinates:ae}):null,Pe=$i(Ke,"id"),[q,le]=C.useState(null),de=Oe?oe:Br(oe,Ee),ue=Dh(de,(a=q?.rect)!=null?a:null,ne),ie=C.useRef(null),Fe=C.useCallback((_e,xe)=>{let{sensor:De,options:Ye}=xe;if(L.current==null)return;const rt=w.get(L.current);if(!rt)return;const qe=_e.nativeEvent,Je=new De({active:L.current,activeNode:rt,event:qe,options:Ye,context:ve,onAbort(Ve){if(!w.get(Ve))return;const{onDragAbort:St}=F.current,Pt={id:Ve};St?.(Pt),M({type:"onDragAbort",event:Pt})},onPending(Ve,bt,St,Pt){if(!w.get(Ve))return;const{onDragPending:lr}=F.current,Ht={id:Ve,constraint:bt,initialCoordinates:St,offset:Pt};lr?.(Ht),M({type:"onDragPending",event:Ht})},onStart(Ve){const bt=L.current;if(bt==null)return;const St=w.get(bt);if(!St)return;const{onDragStart:Pt}=F.current,_r={activatorEvent:qe,active:{id:bt,data:St.data,rect:$}};tr.unstable_batchedUpdates(()=>{Pt?.(_r),I(ar.Initializing),k({type:st.DragStart,initialCoordinates:Ve,active:bt}),M({type:"onDragStart",event:_r}),_(ie.current),z(qe)})},onMove(Ve){k({type:st.DragMove,coordinates:Ve})},onEnd:Zt(st.DragEnd),onCancel:Zt(st.DragCancel)});ie.current=Je;function Zt(Ve){return async function(){const{active:St,collisions:Pt,over:_r,scrollAdjustedTranslate:lr}=ve.current;let Ht=null;if(St&&lr){const{cancelDrop:ir}=F.current;Ht={activatorEvent:qe,active:St,collisions:Pt,delta:lr,over:_r},Ve===st.DragEnd&&typeof ir=="function"&&await Promise.resolve(ir(Ht))&&(Ve=st.DragCancel)}L.current=null,tr.unstable_batchedUpdates(()=>{k({type:Ve}),I(ar.Uninitialized),le(null),_(null),z(null),ie.current=null;const ir=Ve===st.DragEnd?"onDragEnd":"onDragCancel";if(Ht){const Sr=F.current[ir];Sr?.(Ht),M({type:ir,event:Ht})}})}}},[w]),Ce=C.useCallback((_e,xe)=>(De,Ye)=>{const rt=De.nativeEvent,qe=w.get(Ye);if(L.current!==null||!qe||rt.dndKit||rt.defaultPrevented)return;const Je={active:qe};_e(De,xe.options,Je)===!0&&(rt.dndKit={capturedBy:xe.sensor},L.current=Ye,Fe(De,xe))},[w,Fe]),Ie=s0(c,Ce);h0(c),Jt(()=>{ne&&T===ar.Initializing&&I(ar.Initialized)},[ne,T]),C.useEffect(()=>{const{onDragMove:_e}=F.current,{active:xe,activatorEvent:De,collisions:Ye,over:rt}=ve.current;if(!xe||!De)return;const qe={active:xe,activatorEvent:De,collisions:Ye,delta:{x:Be.x,y:Be.y},over:rt};tr.unstable_batchedUpdates(()=>{_e?.(qe),M({type:"onDragMove",event:qe})})},[Be.x,Be.y]),C.useEffect(()=>{const{active:_e,activatorEvent:xe,collisions:De,droppableContainers:Ye,scrollAdjustedTranslate:rt}=ve.current;if(!_e||L.current==null||!xe||!rt)return;const{onDragOver:qe}=F.current,Je=Ye.get(Pe),Zt=Je&&Je.rect.current?{id:Je.id,rect:Je.rect.current,data:Je.data,disabled:Je.disabled}:null,Ve={active:_e,activatorEvent:xe,collisions:De,delta:{x:rt.x,y:rt.y},over:Zt};tr.unstable_batchedUpdates(()=>{le(Zt),qe?.(Ve),M({type:"onDragOver",event:Ve})})},[Pe]),Jt(()=>{ve.current={activatorEvent:b,active:P,activeNode:Q,collisionRect:Me,collisions:Ke,droppableRects:Y,draggableNodes:w,draggingNode:Te,draggingNodeRect:ge,droppableContainers:E,over:q,scrollableAncestors:ze,scrollAdjustedTranslate:Be},$.current={initial:ge,translated:Me}},[P,Q,Ke,Me,w,Te,ge,Y,E,q,ze,Be]),r0({...se,delta:y,draggingRect:Me,pointerCoordinates:ae,scrollableAncestors:ze,scrollableAncestorRects:K});const Ne=C.useMemo(()=>({active:P,activeNode:Q,activeNodeRect:ne,activatorEvent:b,collisions:Ke,containerNodeRect:Ae,dragOverlay:me,draggableNodes:w,droppableContainers:E,droppableRects:Y,over:q,measureDroppableContainers:H,scrollableAncestors:ze,scrollableAncestorRects:K,measuringConfiguration:B,measuringScheduled:W,windowRect:$e}),[P,Q,ne,b,Ke,Ae,me,w,E,Y,q,H,ze,K,B,W,$e]),He=C.useMemo(()=>({activatorEvent:b,activators:Ie,active:P,activeNodeRect:ne,ariaDescribedById:{draggable:G},dispatch:k,draggableNodes:w,over:q,measureDroppableContainers:H}),[b,Ie,P,ne,k,G,w,q,H]);return C.createElement(Mi.Provider,{value:v},C.createElement(oo.Provider,{value:He},C.createElement(nc.Provider,{value:Ne},C.createElement(oc.Provider,{value:ue},h)),C.createElement(S0,{disabled:l?.restoreFocus===!1})),C.createElement(Eh,{...l,hiddenTextDescribedById:G}));function je(){const _e=U?.autoScrollEnabled===!1,xe=typeof f=="object"?f.enabled===!1:f===!1,De=S&&!_e&&!xe;return typeof f=="object"?{...f,enabled:De}:{enabled:De}}}),O0=C.createContext(null),ac="button",M0="Draggable";function I0(e){let{id:t,data:r,disabled:n=!1,attributes:o}=e;const a=gn(M0),{activators:s,activatorEvent:l,active:f,activeNodeRect:h,ariaDescribedById:c,draggableNodes:i,over:u}=C.useContext(oo),{role:g=ac,roleDescription:p="draggable",tabIndex:m=0}=o??{},x=f?.id===t,k=C.useContext(x?oc:O0),[M,v]=Jn(),[T,I]=Jn(),S=p0(s,t),N=un(r);Jt(()=>(i.set(t,{id:t,key:a,node:M,activatorNode:T,data:N}),()=>{const y=i.get(t);y&&y.key===a&&i.delete(t)}),[i,t]);const w=C.useMemo(()=>({role:g,tabIndex:m,"aria-disabled":n,"aria-pressed":x&&g===ac?!0:void 0,"aria-roledescription":p,"aria-describedby":c.draggable}),[n,g,m,x,p,c.draggable]);return{active:f,activatorEvent:l,activeNodeRect:h,attributes:w,isDragging:x,listeners:n?void 0:S,node:M,over:u,setNodeRef:v,setActivatorNodeRef:I,transform:k}}function D0(){return C.useContext(nc)}const P0="Droppable",L0={timeout:25};function $0(e){let{data:t,disabled:r=!1,id:n,resizeObserverConfig:o}=e;const a=gn(P0),{active:s,dispatch:l,over:f,measureDroppableContainers:h}=C.useContext(oo),c=C.useRef({disabled:r}),i=C.useRef(!1),u=C.useRef(null),g=C.useRef(null),{disabled:p,updateMeasurementsFor:m,timeout:x}={...L0,...o},k=un(m??n),M=C.useCallback(()=>{if(!i.current){i.current=!0;return}g.current!=null&&clearTimeout(g.current),g.current=setTimeout(()=>{h(Array.isArray(k.current)?k.current:[k.current]),g.current=null},x)},[x]),v=ro({callback:M,disabled:p||!s}),T=C.useCallback((w,y)=>{v&&(y&&(v.unobserve(y),i.current=!1),w&&v.observe(w))},[v]),[I,S]=Jn(T),N=un(t);return C.useEffect(()=>{!v||!I.current||(v.disconnect(),i.current=!1,v.observe(I.current))},[I,v]),C.useEffect(()=>(l({type:st.RegisterDroppable,element:{id:n,key:a,disabled:r,node:I,rect:u,data:N}}),()=>l({type:st.UnregisterDroppable,key:a,id:n})),[n]),C.useEffect(()=>{r!==c.current.disabled&&(l({type:st.SetDroppableDisabled,id:n,key:a,disabled:r}),c.current.disabled=r)},[n,a,r,l]),{active:s,rect:u,isOver:f?.id===n,node:I,over:f,setNodeRef:S}}function Ga(e,t,r){const n=e.slice();return n.splice(r<0?n.length+r:r,0,n.splice(t,1)[0]),n}function F0(e,t){return e.reduce((r,n,o)=>{const a=t.get(n);return a&&(r[o]=a),r},Array(e.length))}function ao(e){return e!==null&&e>=0}function U0(e,t){if(e===t)return!0;if(e.length!==t.length)return!1;for(let r=0;r{let{rects:t,activeIndex:r,overIndex:n,index:o}=e;const a=Ga(t,n,r),s=t[o],l=a[o];return!l||!s?null:{x:l.left-s.left,y:l.top-s.top,scaleX:l.width/s.width,scaleY:l.height/s.height}},sc="Sortable",lc=C.createContext({activeIndex:-1,containerId:sc,disableTransforms:!1,items:[],overIndex:-1,useDragOverlay:!1,sortedRects:[],strategy:so,disabled:{draggable:!1,droppable:!1}});function ic(e){let{children:t,id:r,items:n,strategy:o=so,disabled:a=!1}=e;const{active:s,dragOverlay:l,droppableRects:f,over:h,measureDroppableContainers:c}=D0(),i=gn(sc,r),u=l.rect!==null,g=C.useMemo(()=>n.map(S=>typeof S=="object"&&"id"in S?S.id:S),[n]),p=s!=null,m=s?g.indexOf(s.id):-1,x=h?g.indexOf(h.id):-1,k=C.useRef(g),M=!U0(g,k.current),v=x!==-1&&m===-1||M,T=z0(a);Jt(()=>{M&&p&&c(g)},[M,g,p,c]),C.useEffect(()=>{k.current=g},[g]);const I=C.useMemo(()=>({activeIndex:m,containerId:i,disabled:T,disableTransforms:v,items:g,overIndex:x,useDragOverlay:u,sortedRects:F0(g,f),strategy:o}),[m,i,T.draggable,T.droppable,v,g,x,f,u,o]);return C.createElement(lc.Provider,{value:I},t)}const cc=e=>{let{id:t,items:r,activeIndex:n,overIndex:o}=e;return Ga(r,n,o).indexOf(t)},dc=e=>{let{containerId:t,isSorting:r,wasDragging:n,index:o,items:a,newIndex:s,previousItems:l,previousContainerId:f,transition:h}=e;return!h||!n||l!==a&&o===s?!1:r?!0:s!==o&&t===f},H0={duration:200,easing:"ease"},uc="transform",G0=pn.Transition.toString({property:uc,duration:0,easing:"linear"}),j0={roleDescription:"sortable"};function B0(e){let{disabled:t,index:r,node:n,rect:o}=e;const[a,s]=C.useState(null),l=C.useRef(r);return Jt(()=>{if(!t&&r!==l.current&&n.current){const f=o.current;if(f){const h=Wr(n.current,{ignoreTransform:!0}),c={x:f.left-h.left,y:f.top-h.top,scaleX:f.width/h.width,scaleY:f.height/h.height};(c.x||c.y)&&s(c)}}r!==l.current&&(l.current=r)},[t,r,n,o]),C.useEffect(()=>{a&&s(null)},[a]),a}function fc(e){let{animateLayoutChanges:t=dc,attributes:r,disabled:n,data:o,getNewIndex:a=cc,id:s,strategy:l,resizeObserverConfig:f,transition:h=H0}=e;const{items:c,containerId:i,activeIndex:u,disabled:g,disableTransforms:p,sortedRects:m,overIndex:x,useDragOverlay:k,strategy:M}=C.useContext(lc),v=W0(n,g),T=c.indexOf(s),I=C.useMemo(()=>({sortable:{containerId:i,index:T,items:c},...o}),[i,o,T,c]),S=C.useMemo(()=>c.slice(c.indexOf(s)),[c,s]),{rect:N,node:w,isOver:y,setNodeRef:E}=$0({id:s,data:I,disabled:v.droppable,resizeObserverConfig:{updateMeasurementsFor:S,...f}}),{active:O,activatorEvent:$,activeNodeRect:P,attributes:L,setNodeRef:U,listeners:_,isDragging:b,over:z,setActivatorNodeRef:F,transform:G}=I0({id:s,data:I,attributes:{...j0,...r},disabled:v.draggable}),X=gh(E,U),B=!!O,Y=B&&!p&&ao(u)&&ao(x),H=!k&&b,W=H&&Y?G:null,be=Y?W??(l??M)({rects:m,activeNodeRect:P,activeIndex:u,overIndex:x,index:T}):null,se=ao(u)&&ao(x)?a({id:s,items:c,activeIndex:u,overIndex:x}):T,pe=O?.id,ne=C.useRef({activeId:pe,items:c,newIndex:se,containerId:i}),Ae=c!==ne.current.items,ve=t({active:O,containerId:i,isDragging:b,isSorting:B,id:s,index:T,items:c,newIndex:ne.current.newIndex,previousItems:ne.current.items,previousContainerId:ne.current.containerId,transition:h,wasDragging:ne.current.activeId!=null}),We=B0({disabled:!ve,index:T,node:w,rect:N});return C.useEffect(()=>{B&&ne.current.newIndex!==se&&(ne.current.newIndex=se),i!==ne.current.containerId&&(ne.current.containerId=i),c!==ne.current.items&&(ne.current.items=c)},[B,se,i,c]),C.useEffect(()=>{if(pe===ne.current.activeId)return;if(pe!=null&&ne.current.activeId==null){ne.current.activeId=pe;return}const Te=setTimeout(()=>{ne.current.activeId=pe},50);return()=>clearTimeout(Te)},[pe]),{active:O,activeIndex:u,attributes:L,data:I,rect:N,index:T,newIndex:se,items:c,isOver:y,isSorting:B,isDragging:b,listeners:_,node:w,overIndex:x,over:z,setNodeRef:X,setActivatorNodeRef:F,setDroppableNodeRef:E,setDraggableNodeRef:U,transform:We??be,transition:me()};function me(){if(We||Ae&&ne.current.newIndex===T)return G0;if(!(H&&!Na($)||!h)&&(B||ve))return pn.Transition.toString({...h,property:uc})}}function W0(e,t){var r,n;return typeof e=="boolean"?{draggable:e,droppable:!1}:{draggable:(r=e?.draggable)!=null?r:t.draggable,droppable:(n=e?.droppable)!=null?n:t.droppable}}function wn(e){if(!e)return!1;const t=e.data.current;return!!(t&&"sortable"in t&&typeof t.sortable=="object"&&"containerId"in t.sortable&&"items"in t.sortable&&"index"in t.sortable)}const V0=[Ue.Down,Ue.Right,Ue.Up,Ue.Left],gc=(e,t)=>{let{context:{active:r,collisionRect:n,droppableRects:o,droppableContainers:a,over:s,scrollableAncestors:l}}=t;if(V0.includes(e.code)){if(e.preventDefault(),!r||!n)return;const f=[];a.getEnabled().forEach(i=>{if(!i||i!=null&&i.disabled)return;const u=o.get(i.id);if(u)switch(e.code){case Ue.Down:n.topu.top&&f.push(i);break;case Ue.Left:n.left>u.left&&f.push(i);break;case Ue.Right:n.left1&&(c=h[1].id),c!=null){const i=a.get(r.id),u=a.get(c),g=u?o.get(u.id):null,p=u?.node.current;if(p&&g&&i&&u){const x=eo(p).some((S,N)=>l[N]!==S),k=hc(i,u),M=K0(i,u),v=x||!k?{x:0,y:0}:{x:M?n.width-g.width:0,y:M?n.height-g.height:0},T={x:g.left,y:g.top};return v.x&&v.y?T:hn(T,v)}}}};function hc(e,t){return!wn(e)||!wn(t)?!1:e.data.current.sortable.containerId===t.data.current.sortable.containerId}function K0(e,t){return!wn(e)||!wn(t)||!hc(e,t)?!1:e.data.current.sortable.index{const{attributes:l,listeners:f,setNodeRef:h,transform:c,transition:i,isDragging:u}=fc({id:"hash"in e?e.hash:e.id}),g={transform:pn.Transform.toString(c),transition:i,opacity:u?.5:1},p="status"in e&&e.status!==void 0,m=p?e.status:"done",x=m==="uploading",k=m==="compressing",M=m==="error",v=p?e.progress:100,T=p?e.preview:void 0,I=p?e.mime_type?.startsWith("image/")??!1:e.is_image,[S,N]=C.useState(void 0),w=!p&&I?e.download_url:void 0;C.useEffect(()=>{if(!w){N(void 0);return}if(ua(w)){N(w);return}let E=!0,O;return(async()=>{try{const P=await X0.api.get(w,{responseType:"blob",headers:zn()});E&&P&&(O=URL.createObjectURL(P),N(O))}catch(P){console.error("Failed to load authenticated image:",P)}})(),()=>{E=!1,O&&URL.revokeObjectURL(O)}},[w]);const y=S;return d.jsxs(j,{ref:h,style:g,className:"relative group bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg overflow-hidden",children:[d.jsxs(j,{className:`aspect-square w-full flex items-center justify-center bg-gray-50 dark:bg-gray-900 relative ${I||!p?"cursor-pointer":""}`,onClick:()=>{x||k||(I&&n?n():!p&&o&&o())},children:[T||y?d.jsx(jt,{src:T||y,alt:e.original_filename,className:"w-full h-full object-cover"}):d.jsx(It,{className:`fa-solid ${Si(e.mime_type)} text-4xl text-gray-400 dark:text-gray-500`}),(x||k)&&d.jsxs(j,{className:"absolute inset-0 bg-black/50 flex flex-col items-center justify-center",children:[d.jsx(j,{className:"w-3/4 h-2 bg-gray-700 dark:bg-gray-600 rounded-full overflow-hidden",children:d.jsx(j,{className:"h-full bg-blue-500 dark:bg-blue-400 transition-all",style:{width:`${v}%`}})}),d.jsxs(te,{className:"text-white text-xs mt-1",children:[v,"%"]})]}),M&&d.jsx(j,{className:"absolute inset-0 bg-red-500/50 dark:bg-red-600/50 flex items-center justify-center",children:d.jsx(It,{className:"fa-solid fa-exclamation-triangle text-2xl text-white"})}),s&&d.jsx(R,{type:"button",onClick:E=>{E.stopPropagation(),s()},className:`absolute top-1.5 left-1.5 z-10 p-1 rounded-full transition-colors ${a?"bg-yellow-500 text-white":"bg-gray-800/50 text-gray-300 hover:bg-yellow-500 hover:text-white opacity-0 group-hover:opacity-100"}`,title:at(a?"attachment.primary_image":"attachment.set_as_primary"),children:d.jsx(It,{className:"fa-solid fa-star text-xs"})}),d.jsxs(j,{className:"absolute inset-0 bg-black/0 group-hover:bg-black/40 transition-colors flex items-start justify-end p-1 gap-1 opacity-0 group-hover:opacity-100",children:[d.jsx(R,{type:"button",...l,...f,className:"p-1.5 bg-white/90 dark:bg-gray-800/90 rounded text-gray-600 dark:text-gray-300 hover:bg-white cursor-grab active:cursor-grabbing",title:at("attachment.drag_to_reorder"),onClick:E=>E.stopPropagation(),children:d.jsx(It,{className:"fa-solid fa-grip-vertical text-sm"})}),!p&&o&&d.jsx(R,{type:"button",onClick:E=>{E.stopPropagation(),o()},className:"p-1.5 bg-white/90 dark:bg-gray-800/90 rounded text-blue-500 hover:bg-white",title:at("common.download"),children:d.jsx(It,{className:"fa-solid fa-download text-sm"})}),M&&r&&d.jsx(R,{type:"button",onClick:E=>{E.stopPropagation(),r()},className:"p-1.5 bg-white/90 dark:bg-gray-800/90 rounded text-orange-500 hover:bg-white",title:at("common.retry"),children:d.jsx(It,{className:"fa-solid fa-arrow-rotate-right text-sm"})}),d.jsx(R,{type:"button",onClick:E=>{E.stopPropagation(),t()},disabled:x||k,className:"p-1.5 bg-white/90 dark:bg-gray-800/90 rounded text-red-500 hover:bg-white disabled:opacity-50",title:at("common.delete"),children:d.jsx(It,{className:"fa-solid fa-times text-sm"})})]})]}),d.jsxs(j,{className:`p-2 ${!p&&o?"cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-700":""}`,onClick:()=>{!p&&o&&o()},children:[d.jsx(Gt,{className:"text-xs font-medium text-gray-900 dark:text-white truncate",title:e.original_filename,children:e.original_filename}),d.jsx(Gt,{className:"text-xs text-gray-500 dark:text-gray-400",children:e.size_formatted})]})]})},mc=({allItems:e,canAddMore:t,totalCount:r,maxFiles:n,maxSize:o,accept:a,onRemove:s,onRetry:l,onImageClick:f,onDownload:h,onDragEnd:c,onAddClick:i,enablePrimarySelection:u,primaryFileId:g,onPrimaryChange:p})=>{const m=Th(Ii(Fa,{activationConstraint:{distance:8}}),Ii(La,{coordinateGetter:gc}));return d.jsxs(j,{className:"p-3",children:[d.jsx(N0,{sensors:m,collisionDetection:Nh,onDragEnd:c,children:d.jsx(ic,{items:e.map(x=>"hash"in x?x.hash:x.id),strategy:so,children:d.jsxs(j,{className:"grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 gap-3",children:[e.map(x=>{const k="hash"in x,M=k?x.is_image:x.mime_type?.startsWith("image/")??!1;return d.jsx(pc,{file:x,onRemove:()=>s(x),onRetry:"status"in x&&x.status==="error"?()=>l(x):void 0,onImageClick:M?()=>f(x):void 0,onDownload:k?()=>h(x):void 0,isPrimary:u&&k&&(x.hash||x.id)===g,onPrimaryClick:u&&k?()=>p?.(x.hash||x.id):void 0},k?x.hash:x.id)}),t&&d.jsxs(j,{className:"aspect-square flex flex-col items-center justify-center border-2 border-dashed border-gray-300 dark:border-gray-600 rounded-lg cursor-pointer hover:border-gray-400 dark:hover:border-gray-500 hover:bg-gray-50 dark:hover:bg-gray-800/50 transition-colors",onClick:i,children:[d.jsx(It,{className:"fa-solid fa-plus text-2xl text-gray-400 dark:text-gray-500 mb-1"}),d.jsx(te,{className:"text-xs text-gray-500 dark:text-gray-400",children:at("common.add")})]})]})})}),d.jsxs(j,{className:"mt-3 pt-3 border-t border-gray-200 dark:border-gray-700 flex justify-between items-center text-xs text-gray-500 dark:text-gray-400",children:[d.jsx(te,{children:at("attachment.attached_count",{count:r,max:n})}),d.jsxs(te,{children:[at("attachment.max_size",{size:o}),a&&` (${a})`]})]})]})},q0=[],Z0=[],lo=C.forwardRef(({uploadTriggerEvent:e,attachmentableType:t,attachmentableId:r,collection:n="default",maxFiles:o=10,maxSize:a=10,maxConcurrentUploads:s=3,accept:l,imageCompression:f,roleIds:h=Z0,autoUpload:c=!0,onFilesChange:i,onChange:u,onUploadComplete:g,onUploadError:p,onRemove:m,onReorder:x,initialFiles:k=q0,className:M="",confirmBeforeRemove:v=!1,confirmRemoveTitle:T,confirmRemoveMessage:I,apiEndpoints:S,uploadParams:N,enablePrimarySelection:w=!1,primaryFileId:y,onPrimaryChange:E,id:O,editorAttrs:$},P)=>{const L={upload:S?.upload??"/api/attachments",delete:S?.delete??"/api/attachments/:id",reorder:S?.reorder??"/api/attachments/reorder"},U=C.useRef(u??i);U.current=u??i;const _=C.useCallback(G=>{const X=U.current;if(!X)return;X({target:{value:G,files:G},preventDefault:()=>{},stopPropagation:()=>{}})},[]),b=Ei({attachmentableType:t,attachmentableId:r,collection:n,maxFiles:o,maxSize:a,maxConcurrentUploads:s,accept:l,imageCompression:f,roleIds:h,autoUpload:c,initialFiles:k,endpoints:L,onFilesChange:_,onUploadComplete:g,onUploadError:p,onRemove:m,onReorder:x,uploadTriggerEvent:e,confirmBeforeRemove:v,uploadParams:N});C.useImperativeHandle(P,()=>({uploadAll:b.handleUploadAll,clear:b.clear,getPendingFiles:b.getPendingFiles}));const z=C.useMemo(()=>b.imageFiles.map(G=>{const X=!("hash"in G),B=G,Y=G;return X?{src:B.preview||"",filename:B.original_filename,title:B.original_filename,downloadRequiresAuth:!1}:{src:b.authenticatedImageUrls.get(Y.id)||Y.download_url,downloadUrl:Y.download_url,filename:Y.original_filename,title:Y.original_filename,downloadRequiresAuth:!0}}),[b.imageFiles,b.authenticatedImageUrls]),F=C.useMemo(()=>({existingFiles:b.existingFiles,pendingFiles:b.pendingFiles,isDragOver:b.isDragOver,isDeleting:b.isDeleting,maxFiles:o,maxSize:a,accept:l,endpoints:L,confirmBeforeRemove:v,totalCount:b.totalCount,hasFiles:b.hasFiles,canAddMore:b.canAddMore,allItems:b.allItems,imageFiles:b.imageFiles,handleFiles:b.handleFiles,handleRemove:b.handleRemove,handleRetry:b.handleRetry,handleUploadAll:b.handleUploadAll,handleOpenGallery:b.handleOpenGallery,handleDownload:b.handleDownload,handleDragEnd:b.handleDragEnd,setIsDragOver:b.setIsDragOver,inputRef:b.inputRef,galleryOpen:b.galleryOpen,setGalleryOpen:b.setGalleryOpen,galleryStartIndex:b.galleryStartIndex,galleryKeyRef:b.galleryKeyRef,confirmDialogOpen:b.confirmDialogOpen,setConfirmDialogOpen:b.setConfirmDialogOpen,itemToDelete:b.itemToDelete,executeRemoveAttachment:b.executeRemoveAttachment,authenticatedImageUrls:b.authenticatedImageUrls}),[b,o,a,l,L,v]);return d.jsx(ui,{value:F,children:d.jsxs(j,{id:O,className:M,...$,children:[d.jsx(Ti,{isDragOver:b.isDragOver,canAddMore:b.canAddMore,onFiles:b.handleFiles,setIsDragOver:b.setIsDragOver,inputRef:b.inputRef,accept:l,maxFiles:o,maxSize:a,children:b.hasFiles&&d.jsx(mc,{allItems:b.allItems,canAddMore:b.canAddMore,totalCount:b.totalCount,maxFiles:o,maxSize:a,accept:l,onRemove:b.handleRemove,onRetry:b.handleRetry,onImageClick:b.handleOpenGallery,onDownload:b.handleDownload,onDragEnd:b.handleDragEnd,onAddClick:()=>b.inputRef.current?.click(),enablePrimarySelection:w,primaryFileId:y,onPrimaryChange:E})}),v&&d.jsx(wa,{isOpen:b.confirmDialogOpen,onClose:()=>{b.isDeleting||b.setConfirmDialogOpen(!1)},title:T??at("attachment.delete_confirm_title"),message:I??at("attachment.delete_confirm_message"),confirmText:at("common.delete"),confirmButtonVariant:"danger",onConfirm:()=>{b.itemToDelete&&b.executeRemoveAttachment(b.itemToDelete)},isLoading:b.isDeleting,loadingText:at("attachment.deleting")}),z.length>0&&b.galleryOpen&&d.jsx(zr,{images:z,isOpen:!0,onClose:()=>b.setGalleryOpen(!1),startIndex:b.galleryStartIndex,showDownload:!0},`gallery-${b.galleryKeyRef.current}`)]})})});lo.displayName="FileUploader";const Q0=Object.freeze(Object.defineProperty({__proto__:null,FileDropZone:Ti,FileList:mc,FileUploader:lo,FileUploaderProvider:ui,SortableThumbnailItem:pc,default:lo,extractErrorMessage:_i,fileUploaderT:at,formatFileSize:Zn,getFileIcon:Si,useFileUploader:Ei,useFileUploaderContext:th},Symbol.toStringTag,{value:"Module"})),bc=({provider:e,className:t="w-5 h-5"})=>{switch(e){case"google":return d.jsxs("svg",{className:t,viewBox:"0 0 24 24",children:[d.jsx("path",{fill:"#4285F4",d:"M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z"}),d.jsx("path",{fill:"#34A853",d:"M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z"}),d.jsx("path",{fill:"#FBBC05",d:"M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z"}),d.jsx("path",{fill:"#EA4335",d:"M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z"})]});case"naver":return d.jsx("svg",{className:t,viewBox:"0 0 24 24",children:d.jsx("path",{fill:"currentColor",d:"M16.273 12.845L7.376 0H0v24h7.727V11.155L16.624 24H24V0h-7.727z"})});case"kakao":return d.jsx("svg",{className:t,viewBox:"0 0 24 24",children:d.jsx("path",{fill:"currentColor",d:"M12 3c5.799 0 10.5 3.664 10.5 8.185 0 4.52-4.701 8.184-10.5 8.184a13.5 13.5 0 0 1-1.727-.11l-4.408 2.883c-.501.265-.678.236-.472-.413l.892-3.678c-2.88-1.46-4.785-3.99-4.785-6.866C1.5 6.665 6.201 3 12 3zm5.519 8.625h-.634v2.916h-.97v-2.916h-.633v-.848h2.237v.848zm-3.294 2.916h-.97V10.77h.97v3.771zm-1.97 0h-.97v-2.77l-.658 1.53h-.629l-.665-1.53v2.77h-.97V10.77h1.13l.822 1.912.814-1.912h1.126v3.771zm-4.49 0h-.97V10.77h.97v3.771z"})});case"facebook":return d.jsx("svg",{className:t,viewBox:"0 0 24 24",children:d.jsx("path",{fill:"currentColor",d:"M24 12.073c0-6.627-5.373-12-12-12s-12 5.373-12 12c0 5.99 4.388 10.954 10.125 11.854v-8.385H7.078v-3.47h3.047V9.43c0-3.007 1.792-4.669 4.533-4.669 1.312 0 2.686.235 2.686.235v2.953H15.83c-1.491 0-1.956.925-1.956 1.874v2.25h3.328l-.532 3.47h-2.796v8.385C19.612 23.027 24 18.062 24 12.073z"})});case"apple":return d.jsx("svg",{className:t,viewBox:"0 0 24 24",children:d.jsx("path",{fill:"currentColor",d:"M12.152 6.896c-.948 0-2.415-1.078-3.96-1.04-2.04.027-3.91 1.183-4.961 3.014-2.117 3.675-.546 9.103 1.519 12.09 1.013 1.454 2.208 3.09 3.792 3.039 1.52-.065 2.09-.987 3.935-.987 1.831 0 2.35.987 3.96.948 1.637-.026 2.676-1.48 3.676-2.948 1.156-1.688 1.636-3.325 1.662-3.415-.039-.013-3.182-1.221-3.22-4.857-.026-3.04 2.48-4.494 2.597-4.559-1.429-2.09-3.623-2.324-4.39-2.376-2-.156-3.675 1.09-4.61 1.09zM15.53 3.83c.843-1.012 1.4-2.427 1.245-3.83-1.207.052-2.662.805-3.532 1.818-.78.896-1.454 2.338-1.273 3.714 1.338.104 2.715-.688 3.559-1.701"})});default:return null}},yc=(e,t)=>window.G7Core?.t?.(e,t)??e,J0=[{id:"google",name:"Google",color:"bg-white dark:bg-gray-800",textColor:"text-gray-700 dark:text-gray-300"},{id:"naver",name:"네이버",color:"bg-[#03C75A]",textColor:"text-white"},{id:"kakao",name:"카카오",color:"bg-[#FEE500]",textColor:"text-[#000000]/85"},{id:"facebook",name:"Facebook",color:"bg-[#1877F2]",textColor:"text-white"},{id:"apple",name:"Apple",color:"bg-black dark:bg-white",textColor:"text-white dark:text-black"}],vc=({providers:e=["google","naver","kakao"],mode:t="login",variant:r="full",className:n="",id:o,editorAttrs:a})=>{const s=h=>{const c=encodeURIComponent(window.location.href);window.location.href=`/api/auth/${h}?redirect=${c}`},l=h=>t==="register"?yc("auth.social.register_with",{provider:h}):yc("auth.social.login_with",{provider:h}),f=J0.filter(h=>e.includes(h.id));return r==="icon"?d.jsx(j,{className:`flex justify-center gap-4 ${n}`,id:o,...a,children:f.map(h=>d.jsx(R,{onClick:()=>s(h.id),className:`w-12 h-12 flex items-center justify-center rounded-full ${h.color} ${h.textColor} border border-gray-200 dark:border-gray-700 hover:opacity-90 transition-opacity`,"aria-label":l(h.name),children:d.jsx(bc,{provider:h.id,className:"w-5 h-5"})},h.id))}):d.jsx(j,{className:`space-y-3 ${n}`,id:o,...a,children:f.map(h=>d.jsxs(R,{onClick:()=>s(h.id),className:`w-full flex items-center justify-center gap-3 px-4 py-3 rounded-lg font-medium border border-gray-200 dark:border-gray-700 ${h.color} ${h.textColor} hover:opacity-90 transition-opacity`,children:[d.jsx(bc,{provider:h.id,className:"w-5 h-5"}),d.jsx(te,{children:l(h.name)})]},h.id))})},R0=Object.freeze(Object.defineProperty({__proto__:null,default:vc},Symbol.toStringTag,{value:"Module"})),ep=(e,t)=>window.G7Core?.t?.(e,t)??e,tp={success:nt.CheckCircle,error:nt.XCircle,warning:nt.ExclamationTriangle,info:nt.InfoCircle},wc={success:{bg:"bg-green-50 dark:bg-green-900/20 border-green-200 dark:border-green-800",text:"text-green-800 dark:text-green-300"},error:{bg:"bg-red-50 dark:bg-red-900/20 border-red-200 dark:border-red-800",text:"text-red-800 dark:text-red-300"},warning:{bg:"bg-yellow-50 dark:bg-yellow-900/20 border-yellow-200 dark:border-yellow-800",text:"text-yellow-800 dark:text-yellow-300"},info:{bg:"bg-blue-50 dark:bg-blue-900/20 border-blue-200 dark:border-blue-800",text:"text-blue-800 dark:text-blue-300"}},rp={"top-left":"top-4 left-4","top-center":"top-4 left-1/2 -translate-x-1/2","top-right":"top-4 right-4","bottom-left":"bottom-4 left-4","bottom-center":"bottom-4 left-1/2 -translate-x-1/2","bottom-right":"bottom-4 right-4"},ja=({toasts:e,position:t="bottom-center",duration:r=3e3,onRemove:n})=>{const[o,a]=C.useState(new Map),s=C.useCallback(l=>{a(f=>{const h=new Map(f);return h.set(l,!1),h}),setTimeout(()=>{if(n)n(l);else{const f=window.G7Core;f?.state?.update&&f.state.update(h=>({toasts:(Array.isArray(h.toasts)?h.toasts:[]).filter(i=>i.id!==l)}))}},300)},[n]);return C.useEffect(()=>{!e||e.length===0||e.forEach(l=>{if(o.has(l.id))return;a(h=>{const c=new Map(h);return c.set(l.id,!0),c});const f=l.duration||r;f>0&&setTimeout(()=>{s(l.id)},f)})},[e,r,s,o]),!e||e.length===0?null:d.jsx(j,{className:`fixed z-[9999] flex flex-col gap-2 ${rp[t]}`,"aria-live":"polite","aria-atomic":"true",children:e.map(l=>d.jsx(np,{toast:l,position:t,isVisible:o.get(l.id)??!0,onClose:()=>s(l.id)},l.id))})},np=({toast:e,position:t,isVisible:r,onClose:n})=>{const o=wc[e.type]||wc.info,a=e.icon?e.icon:tp[e.type],s=t.startsWith("bottom")?"translate-y-2":"-translate-y-2";return d.jsxs(j,{className:` flex items-center gap-3 p-4 rounded-lg border shadow-lg min-w-[300px] max-w-md transition-all duration-300 ${o.bg} ${r?"opacity-100 translate-y-0":`opacity-0 ${s}`} - `,role:"alert",children:[d.jsx(fe,{name:a,className:`w-5 h-5 flex-shrink-0 ${o.text}`}),d.jsx(te,{className:`flex-1 text-sm font-medium ${o.text}`,children:e.message}),d.jsx(R,{onClick:n,className:`flex-shrink-0 p-1 rounded hover:bg-black/10 dark:hover:bg-white/10 ${o.text} focus:outline-none`,"aria-label":J0("common.close"),children:d.jsx(fe,{name:nt.Times,className:"w-4 h-4"})})]})},rp=Object.freeze(Object.defineProperty({__proto__:null,Toast:ja,default:ja},Symbol.toStringTag,{value:"Module"})),np=window.G7Core?.createLogger?.("Comp:PageTransition")??{log:(...e)=>console.log("[Comp:PageTransition]",...e),warn:(...e)=>console.warn("[Comp:PageTransition]",...e),error:(...e)=>console.error("[Comp:PageTransition]",...e)},Ba=({className:e="",style:t})=>{const[r,n]=C.useState(!1);return C.useEffect(()=>{const o=window.G7Core?.TransitionManager;if(!o){np.warn("TransitionManager를 찾을 수 없습니다.");return}return n(o.getIsPending()),o.subscribe(s=>{n(s)})},[]),r?d.jsxs(j,{className:`fixed top-0 left-0 right-0 z-50 ${e}`,style:t,role:"progressbar","aria-label":"페이지 로딩 중",children:[d.jsx(j,{className:"h-1 bg-gradient-to-r from-blue-500 via-blue-600 to-blue-500 animate-pulse",children:d.jsx(j,{className:"h-full bg-blue-700 animate-[loading_1.5s_ease-in-out_infinite]",style:{width:"30%",animation:"loading 1.5s ease-in-out infinite"}})}),d.jsx("style",{children:` + `,role:"alert",children:[d.jsx(fe,{name:a,className:`w-5 h-5 flex-shrink-0 ${o.text}`}),d.jsx(te,{className:`flex-1 text-sm font-medium ${o.text}`,children:e.message}),d.jsx(R,{onClick:n,className:`flex-shrink-0 p-1 rounded hover:bg-black/10 dark:hover:bg-white/10 ${o.text} focus:outline-none`,"aria-label":ep("common.close"),children:d.jsx(fe,{name:nt.Times,className:"w-4 h-4"})})]})},op=Object.freeze(Object.defineProperty({__proto__:null,Toast:ja,default:ja},Symbol.toStringTag,{value:"Module"})),ap=window.G7Core?.createLogger?.("Comp:PageTransition")??{log:(...e)=>console.log("[Comp:PageTransition]",...e),warn:(...e)=>console.warn("[Comp:PageTransition]",...e),error:(...e)=>console.error("[Comp:PageTransition]",...e)},Ba=({className:e="",style:t})=>{const[r,n]=C.useState(!1);return C.useEffect(()=>{const o=window.G7Core?.TransitionManager;if(!o){ap.warn("TransitionManager를 찾을 수 없습니다.");return}return n(o.getIsPending()),o.subscribe(s=>{n(s)})},[]),r?d.jsxs(j,{className:`fixed top-0 left-0 right-0 z-50 ${e}`,style:t,role:"progressbar","aria-label":"페이지 로딩 중",children:[d.jsx(j,{className:"h-1 bg-gradient-to-r from-blue-500 via-blue-600 to-blue-500 animate-pulse",children:d.jsx(j,{className:"h-full bg-blue-700 animate-[loading_1.5s_ease-in-out_infinite]",style:{width:"30%",animation:"loading 1.5s ease-in-out infinite"}})}),d.jsx("style",{children:` @keyframes loading { 0% { transform: translateX(-100%); @@ -84,4 +84,4 @@ self.addEventListener('message', async (e) => { transform: translateX(-100%); } } - `})]}):null},op=Object.freeze(Object.defineProperty({__proto__:null,PageTransitionIndicator:Ba,default:Ba},Symbol.toStringTag,{value:"Module"})),xc=window.G7Core?.createLogger?.("Comp:PageTransitionBlur")??{log:(...e)=>console.log("[Comp:PageTransitionBlur]",...e),warn:(...e)=>console.warn("[Comp:PageTransitionBlur]",...e)},Wa=({className:e="",style:t})=>{const[r,n]=C.useState(!1);return C.useEffect(()=>{const o=window.G7Core?.TransitionManager;if(!o){xc.warn("TransitionManager를 찾을 수 없습니다.");return}return n(o.getIsPending()),o.subscribe(s=>{xc.log("isPending changed:",s),n(s)})},[]),r?d.jsx(j,{className:`fixed inset-0 z-40 bg-white/30 dark:bg-gray-900/30 backdrop-blur-sm transition-opacity duration-150 pointer-events-none ${e}`,style:t,role:"status","aria-label":"페이지 전환 중","data-testid":"page-transition-blur"}):null},ap=Object.freeze(Object.defineProperty({__proto__:null,PageTransitionBlur:Wa,default:Wa},Symbol.toStringTag,{value:"Module"})),kc={sm:640,md:768,lg:1024,xl:1280,"2xl":1536},Cc=new Set(["hidden","block","inline-block","inline","flex","inline-flex","grid","inline-grid","table","table-row","table-cell","contents","list-item","flow-root"]),sp=new Set(["Div","Flex","Grid","Section","Article","Nav","Aside","Main","Header","Footer","Fragment","Form","Container"]),lp=new Set(["H1","H2","H3","H4","H5","H6","P","Span","Label","A","Strong","Em","Small","Badge"]),ip=new Set(["Input","Textarea","Select","Checkbox","Radio","Toggle","DatePicker","TagInput","SearchBar"]),cp=new Set(["Img","Image","Avatar","AvatarUploader","ImageGallery","ProductImageViewer","FileUploader"]),_c={DataGrid:(e,t)=>d.jsxs("div",{className:"w-full border border-gray-200 dark:border-gray-700 rounded-lg overflow-hidden",children:[d.jsx("div",{className:`h-10 bg-gray-100 dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700 ${e}`,children:d.jsxs("div",{className:"flex items-center h-full px-4 gap-8",children:[d.jsx("div",{className:"h-3 w-16 bg-gray-300 dark:bg-gray-600 rounded"}),d.jsx("div",{className:"h-3 w-24 bg-gray-300 dark:bg-gray-600 rounded"}),d.jsx("div",{className:"h-3 w-20 bg-gray-300 dark:bg-gray-600 rounded"}),d.jsx("div",{className:"h-3 w-16 bg-gray-300 dark:bg-gray-600 rounded"})]})}),Array.from({length:t},(r,n)=>d.jsx("div",{className:`h-12 border-b border-gray-100 dark:border-gray-800 ${e}`,style:{animationDelay:`${n*75}ms`},children:d.jsxs("div",{className:"flex items-center h-full px-4 gap-8",children:[d.jsx("div",{className:"h-3 w-12 bg-gray-200 dark:bg-gray-700 rounded"}),d.jsx("div",{className:"h-3 flex-1 bg-gray-200 dark:bg-gray-700 rounded"}),d.jsx("div",{className:"h-3 w-16 bg-gray-100 dark:bg-gray-800 rounded"}),d.jsx("div",{className:"h-3 w-14 bg-gray-100 dark:bg-gray-800 rounded"})]})},n))]}),Pagination:e=>d.jsx("div",{className:"flex justify-center gap-1.5 py-3",children:Array.from({length:5},(t,r)=>d.jsx("div",{className:`w-8 h-8 bg-gray-200 dark:bg-gray-700 rounded ${e}`},r))}),Tabs:e=>d.jsxs("div",{children:[d.jsx("div",{className:"flex gap-4 border-b border-gray-200 dark:border-gray-700 pb-2 mb-3",children:Array.from({length:3},(t,r)=>d.jsx("div",{className:`h-4 w-20 bg-gray-200 dark:bg-gray-700 rounded ${e}`},r))}),d.jsx("div",{className:`h-32 bg-gray-100 dark:bg-gray-800 rounded ${e}`})]}),TabNavigation:e=>d.jsx("div",{className:"flex gap-4 border-b border-gray-200 dark:border-gray-700 pb-2",children:Array.from({length:3},(t,r)=>d.jsx("div",{className:`h-4 w-20 bg-gray-200 dark:bg-gray-700 rounded ${e}`},r))}),ProductCard:e=>d.jsxs("div",{className:"rounded-lg overflow-hidden",children:[d.jsx("div",{className:`h-48 w-full bg-gray-200 dark:bg-gray-700 ${e}`}),d.jsxs("div",{className:"p-3 space-y-2",children:[d.jsx("div",{className:`h-3.5 w-3/4 bg-gray-200 dark:bg-gray-700 rounded ${e}`}),d.jsx("div",{className:`h-4 w-1/3 bg-gray-300 dark:bg-gray-600 rounded ${e}`})]})]}),UserInfo:e=>d.jsx("div",{className:`h-3 w-14 bg-gray-200 dark:bg-gray-700 rounded ${e}`}),QuantitySelector:e=>d.jsx("div",{className:`h-9 w-28 bg-gray-100 dark:bg-gray-800 rounded border border-gray-200 dark:border-gray-700 ${e}`}),Header:e=>d.jsx("header",{className:"bg-white dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700",children:d.jsx("div",{className:"max-w-7xl mx-auto px-4 sm:px-6 lg:px-8",children:d.jsxs("div",{className:"flex items-center justify-between h-16",children:[d.jsxs("div",{className:"flex items-center gap-8",children:[d.jsx("div",{className:`h-8 w-24 bg-gray-200 dark:bg-gray-700 rounded ${e}`}),d.jsx("div",{className:"hidden lg:flex items-center gap-6",children:Array.from({length:4},(t,r)=>d.jsx("div",{className:`h-3.5 w-16 bg-gray-200 dark:bg-gray-700 rounded ${e}`},r))})]}),d.jsx("div",{className:"flex items-center gap-4",children:Array.from({length:3},(t,r)=>d.jsx("div",{className:`h-8 w-8 bg-gray-200 dark:bg-gray-700 rounded-full ${e}`},r))})]})})}),Footer:e=>d.jsx("footer",{className:"bg-white dark:bg-gray-800 border-t border-gray-200 dark:border-gray-700 mt-auto",children:d.jsx("div",{className:"max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8",children:d.jsxs("div",{className:"flex flex-col items-center gap-4",children:[d.jsx("div",{className:`h-4 w-32 bg-gray-200 dark:bg-gray-700 rounded ${e}`}),d.jsx("div",{className:`h-3 w-48 bg-gray-100 dark:bg-gray-800 rounded ${e}`}),d.jsx("div",{className:"flex gap-3",children:Array.from({length:3},(t,r)=>d.jsx("div",{className:`h-6 w-6 bg-gray-200 dark:bg-gray-700 rounded-full ${e}`},r))}),d.jsx("div",{className:`h-3 w-40 bg-gray-100 dark:bg-gray-800 rounded ${e}`})]})})}),Modal:()=>d.jsx(d.Fragment,{}),Toast:()=>d.jsx(d.Fragment,{}),ConfirmDialog:()=>d.jsx(d.Fragment,{})};function dp(e){if(!e||e.length===0)return[];const t=e.filter(n=>n.if),r=e.filter(n=>!n.if);if(r.length>0&&t.length>0){const n=t.filter(o=>!Sc(o.if));return n.length>1&&up(n)?[...r,Ec(n)]:[...r,...n]}if(t.length>1&&r.length===0){const n=t.filter(o=>!Sc(o.if));return n.length===0?[]:n.length===1?n:[Ec(n)]}return e}function Sc(e){const t=e.replace(/^\{\{|\}\}$/g,"").trim();return t.startsWith("!")||/\b(hasError|isError|isLoading|error|Error)\b/.test(t)||/===\s*0\s*(\)|\}|$)/.test(t)}function up(e){if(e.length<2)return!1;const t=[];for(const n of e){const o=(n.if||"").replace(/^\{\{|\}\}$/g,"").trim(),a=o.match(/^(.+?)\s*===\s*['"]/);if(a)t.push(a[1].trim());else{const s=o.match(/(.+?)\s*===\s*['"]/);if(!s)return!1;t.push(s[1].trim())}}const r=t[0];return t.every(n=>n===r)}function Ec(e){return e.reduce((t,r)=>Va(r)>Va(t)?r:t)}function Va(e){return e.children?e.children.length+e.children.reduce((t,r)=>t+Va(r),0):0}function fp(e){return e.includes("{{")?e.replace(/\{\{([^}]+)\}\}/g,(t,r)=>{const n=r.match(/\?[\s]*['"]([^'"]*)['"]\s*:\s*['"]/);return n?n[1]:""}):e}function Ka(e,t){if(!e)return"";let r=e;return r=r.replace(/\b(bg-\S+|text-\S+|border\b|border-\S+|shadow\S*|ring-\S+|outline-\S+|divide-\S+|opacity-\S+|font-\S+|tracking-\S+|leading-\S+|whitespace-\S+|truncate|line-clamp-\S+|underline|no-underline|decoration-\S+|cursor-\S+|hover:\S+|focus:\S+|active:\S+|group-hover:\S+|transition\S*|duration-\S+|ease-\S+|animate-\S+|dark:\S+)\b/g,"").replace(/\s{2,}/g," ").trim(),(t==="Grid"||/\bgrid-cols-\d/.test(r))&&(r=r.replace(/\bgrid-cols-(\d+)/g,(n,o)=>{const a=parseInt(o);return a>4?`grid-cols-${Math.ceil(a/2)}`:`grid-cols-${a}`})),r}function gp(e){const{name:t,props:r}=e;if(!r||!t)return"";const n=[];if(t==="Flex"){n.push("flex");const o=r.direction||"row",a={row:"flex-row","row-reverse":"flex-row-reverse",col:"flex-col","col-reverse":"flex-col-reverse"};a[o]&&n.push(a[o]);const s=r.justify||"start",l={start:"justify-start",end:"justify-end",center:"justify-center",between:"justify-between",around:"justify-around",evenly:"justify-evenly"};l[s]&&n.push(l[s]);const f=r.align||"stretch",h={start:"items-start",end:"items-end",center:"items-center",baseline:"items-baseline",stretch:"items-stretch"};h[f]&&n.push(h[f]);const c=r.wrap||"nowrap",i={wrap:"flex-wrap",nowrap:"flex-nowrap","wrap-reverse":"flex-wrap-reverse"};i[c]&&n.push(i[c]),r.gap&&r.gap>0&&n.push(`gap-${r.gap}`)}if(t==="Grid"){n.push("grid");const o=r.cols||1;if(n.push(`grid-cols-${o}`),r.responsive){const a=r.responsive;a.sm&&n.push(`sm:grid-cols-${a.sm}`),a.md&&n.push(`md:grid-cols-${a.md}`),a.lg&&n.push(`lg:grid-cols-${a.lg}`),a.xl&&n.push(`xl:grid-cols-${a.xl}`),a["2xl"]&&n.push(`2xl:grid-cols-${a["2xl"]}`)}r.gap!==void 0&&r.gap>0&&n.push(`gap-${r.gap}`),r.rowGap!==void 0&&r.rowGap>0&&n.push(`gap-y-${r.rowGap}`),r.colGap!==void 0&&r.colGap>0&&n.push(`gap-x-${r.colGap}`)}return n.join(" ")}function hp(e){switch(e){case"pulse":return"animate-pulse";case"wave":return"animate-pulse";case"none":return"";default:return"animate-pulse"}}function io(e,t,r,n){return!e||e.length===0?[]:dp(e).map((a,s)=>Tc(a,t,r,s,n))}function pp(e,t){const r=e.props?.className||e.className||"";return e.responsive?t>=1024&&e.responsive.desktop?.props?.className?e.responsive.desktop.props.className:t>=768&&e.responsive.tablet?.props?.className?e.responsive.tablet.props.className:r:r}function mp(e,t){const r=e.split(/\s+/).filter(Boolean),n=[],o=[];for(const f of r){const h=f.match(/^(sm|md|lg|xl|2xl):(.+)$/);if(h){const[,c,i]=h;if(Cc.has(i)){n.push({breakpoint:kc[c]||0,display:i});continue}(kc[c]||0)<=t&&o.push(i);continue}if(Cc.has(f)){n.push({breakpoint:0,display:f});continue}o.push(f)}if(n.length===0)return{visible:!0,className:e};n.sort((f,h)=>f.breakpoint-h.breakpoint);let a="";for(const f of n)f.breakpoint<=t&&(a=f.display);a||(a=n[0].display);const s=a!=="hidden",l=s?[a,...o].join(" "):"";return{visible:s,className:l}}function bp(e){const t=e.match(/\bgrid-cols-\[([^\]]+)\]/);if(!t)return{className:e};const r=t[1].replace(/_/g," ");return{className:e.replace(/\bgrid-cols-\[([^\]]+)\]/,"").replace(/\s{2,}/g," ").trim(),style:{gridTemplateColumns:r}}}function Tc(e,t,r,n,o){const{name:a,children:s,iteration:l}=e,f=pp(e,o),h=gp(e),c=h?`${h} ${f}`.trim():f,i=fp(c),u=mp(i,o);if(!a||!u.visible)return null;const g=u.className;if(l)return d.jsx(C.Fragment,{children:Array.from({length:r},(m,x)=>Tc({...e,iteration:void 0},t,r,x,o))},n);if(_c[a])return d.jsx("div",{className:g,children:_c[a](t,r)},n);if(sp.has(a)){const m=Ka(g,a);if(!s||s.length===0){const T=/\b(h-\S|w-\S)/.test(m)?`${m} bg-gray-200 dark:bg-gray-700 rounded ${t}`:`${m} h-3.5 w-full bg-gray-200 dark:bg-gray-700 rounded ${t}`.trim();return d.jsx("div",{className:T},n)}const x=io(s,t,r,o);if(!(x.length>0&&x.some(v=>v!==null)))return null;const M=bp(m);return d.jsx("div",{className:M.className,style:M.style,children:x},n)}const p=Ka(g,a);if(lp.has(a)){const m={H1:"h-7",H2:"h-6",H3:"h-5",H4:"h-5",H5:"h-4",H6:"h-4",P:"h-3.5",Span:"h-3",Label:"h-3",A:"h-3.5",Badge:"h-5 w-16"},x={H1:"w-3/5",H2:"w-2/5",H3:"w-1/3",H4:"w-1/3",H5:"w-1/4",H6:"w-1/4",P:"w-full",Span:"w-1/4",Label:"w-1/4",A:"w-1/3",Strong:"w-1/5",Em:"w-1/4",Small:"w-1/5",Badge:"w-16"},k=m[a]||"h-3.5",M=x[a]||"w-full",v=a.startsWith("H")?"mb-2 mt-1":"mb-1.5";return d.jsx("div",{className:`${k} ${M} bg-gray-200 dark:bg-gray-700 rounded ${t} ${v} ${p}`.trim()},n)}if(ip.has(a))return a==="Textarea"?d.jsx("div",{className:`h-20 w-full bg-gray-100 dark:bg-gray-800 rounded border border-gray-200 dark:border-gray-700 mb-2 ${t}`},n):a==="Checkbox"||a==="Radio"||a==="Toggle"?d.jsx("div",{className:`h-5 w-5 bg-gray-200 dark:bg-gray-700 rounded mb-1.5 ${t}`},n):d.jsx("div",{className:`h-9 w-full bg-gray-100 dark:bg-gray-800 rounded border border-gray-200 dark:border-gray-700 mb-2 ${t}`},n);if(cp.has(a))return a==="Avatar"||a==="AvatarUploader"?d.jsx("div",{className:`h-10 w-10 bg-gray-200 dark:bg-gray-700 rounded-full ${t}`},n):a==="ProductImageViewer"?d.jsx("div",{className:`h-80 w-full bg-gray-200 dark:bg-gray-700 rounded ${t}`},n):d.jsx("div",{className:`h-40 w-full bg-gray-200 dark:bg-gray-700 rounded mb-2 ${t}`},n);if(a==="Button"){if(s&&s.length>0){const m=Ka(g,a),x=io(s,t,r,o);if(!(x.length>0&&x.some(T=>T!==null)))return null;const v=/\b(border|rounded|px-\d|py-\d)/.test(f)?"border border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-800/50 rounded-lg":"";return d.jsx("div",{className:`${m} ${v}`.trim(),children:x},n)}return d.jsx("div",{className:`h-9 w-24 bg-gray-200 dark:bg-gray-700 rounded ${t}`},n)}return a==="Icon"?d.jsx("div",{className:`h-5 w-5 bg-gray-200 dark:bg-gray-700 rounded ${t}`},n):s&&s.length>0?d.jsx("div",{className:p,children:io(s,t,r,o)},n):d.jsx("div",{className:`h-3.5 w-full bg-gray-200 dark:bg-gray-700 rounded mb-1.5 ${t}`},n)}const Ya=({components:e,options:t})=>{const r=hp(t.animation),o=window.G7Core?.useResponsive,a=o?.(),s=a?a.width:typeof window<"u"?window.innerWidth:1024;return d.jsx("div",{className:"w-full",role:"status","aria-busy":"true","aria-label":"Loading...",children:io(e,r,t.iteration_count,s)})},yp=Object.freeze(Object.defineProperty({__proto__:null,PageSkeleton:Ya,default:Ya},Symbol.toStringTag,{value:"Module"})),vp=(e,t)=>window.G7Core?.t?.(e,t)??e,Ac=({options:e})=>{const t=document.documentElement.classList.contains("dark"),r=t?"rgb(17,24,39)":"rgb(249,250,251)",n=t?"#6b7280":"#9ca3af",o=t?"#9ca3af":"#6b7280";return d.jsxs("div",{style:{position:"absolute",inset:0,zIndex:2147483647,overflow:"hidden",background:r,display:"flex",flexDirection:"column",alignItems:"center",justifyContent:"flex-start",paddingTop:"15%",gap:"12px"},children:[d.jsx("div",{style:{width:"32px",height:"32px",border:`3px solid ${n}`,borderTopColor:"transparent",borderRadius:"50%",animation:"g7-spin 0.8s linear infinite"}}),d.jsx("span",{style:{color:o,fontSize:"14px"},children:e?.text||vp("nav.loading")})]})},wp=Object.freeze(Object.defineProperty({__proto__:null,default:Ac},Symbol.toStringTag,{value:"Module"})),Vr=(e,t)=>window.G7Core?.t?.(e,t)??e,Nc=({currentPage:e,totalPages:t,onPageChange:r,maxVisiblePages:n=5,showFirstLast:o=!0,showFirst:a,showLast:s,hasMorePages:l,className:f="",style:h,prevText:c,nextText:i,id:u,editorAttrs:g})=>{const p=t!=null,m=p?t:0,x=p?e{const N=[];if(!p){const w=Math.floor(n/2),y=e<=n?1:e-w;y>1&&(N.push(1),y>2&&N.push("..."));for(let E=y;E<=e;E++)N.push(E);return l&&(N.push(e+1),N.push("...")),N}if(m<=n+2)for(let w=1;w<=m;w++)N.push(w);else{const w=Math.floor(n/2);let y=Math.max(2,e-w),E=Math.min(m-1,e+w);e<=w+1?E=n:e>=m-w&&(y=m-n+1),N.push(1),y>2&&N.push("...");for(let O=y;O<=E;O++)N.push(O);E1&&N.push(m)}return N},[e,m,p,n,l]),T=N=>{N<1||N===e||p&&N>m||r(N)},I=()=>{e>1&&r(e-1)},S=()=>{x&&r(e+1)};return d.jsxs(j,{className:`flex items-center gap-2 ${f}`,style:h,role:"navigation","aria-label":Vr("common.pagination"),id:u,...g,children:[k&&d.jsx(R,{onClick:()=>T(1),disabled:e===1,className:"px-3 py-1 text-sm border border-gray-300 dark:border-gray-600 rounded hover:bg-gray-100 dark:hover:bg-gray-700 disabled:opacity-50 disabled:cursor-not-allowed text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-800","aria-label":Vr("common.first_page"),children:"«"}),d.jsx(R,{onClick:I,disabled:e===1,className:"px-3 py-1 text-sm border border-gray-300 dark:border-gray-600 rounded hover:bg-gray-100 dark:hover:bg-gray-700 disabled:opacity-50 disabled:cursor-not-allowed text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-800","aria-label":Vr("common.prev_page"),children:c||d.jsx(d.Fragment,{children:"‹"})}),d.jsx(j,{className:"flex items-center gap-1",children:v.map((N,w)=>{if(N==="...")return d.jsx(te,{className:"px-3 py-1 text-sm text-gray-500 dark:text-gray-400",children:"..."},`ellipsis-${w}`);const y=N,E=y===e;return d.jsx(R,{onClick:()=>T(y),className:`px-3 py-1 text-sm border rounded ${E?"bg-blue-500 dark:bg-blue-600 text-white border-blue-500 dark:border-blue-600":"border-gray-300 dark:border-gray-600 hover:bg-gray-100 dark:hover:bg-gray-700 text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-800"}`,"aria-label":Vr("common.page_n",{n:y}),"aria-current":E?"page":void 0,children:y},y)})}),d.jsx(R,{onClick:S,disabled:!x,className:"px-3 py-1 text-sm border border-gray-300 dark:border-gray-600 rounded hover:bg-gray-100 dark:hover:bg-gray-700 disabled:opacity-50 disabled:cursor-not-allowed text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-800","aria-label":Vr("common.next_page"),children:i||d.jsx(d.Fragment,{children:"›"})}),M&&d.jsx(R,{onClick:()=>T(m),disabled:e===m,className:"px-3 py-1 text-sm border border-gray-300 dark:border-gray-600 rounded hover:bg-gray-100 dark:hover:bg-gray-700 disabled:opacity-50 disabled:cursor-not-allowed text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-800","aria-label":Vr("common.last_page"),children:"»"})]})},xp=Object.freeze(Object.defineProperty({__proto__:null,Pagination:Nc},Symbol.toStringTag,{value:"Module"})),Oc=(e,t)=>window.G7Core?.t?.(e,t)??e,Mc=({name:e="search",placeholder:t,value:r,onChange:n,onSubmit:o,showButton:a=!1,suggestions:s=[],onSuggestionClick:l,showSuggestions:f=!1,className:h="",style:c,id:i,editorAttrs:u})=>{const g=t??Oc("common.search_placeholder"),[p,m]=C.useState(""),[x,k]=C.useState(!1),M=C.useRef(null),v=C.useRef(""),T=r!==void 0?r:p,I=f&&x&&s.length>0&&T.length>0;C.useEffect(()=>{r!==void 0&&(v.current=r)},[r]);const S=y=>{const E=y.target.value;m(E),n?.(y),v.current!==""&&E===""&&!y.nativeEvent.inputType&&setTimeout(()=>{const O=M.current?.querySelector("form");O&&O.requestSubmit()},0),v.current=E},N=y=>{m(y.text);const E={target:{value:y.text,name:e}};n?.(E),l?.(y),k(!1)},w=y=>{y.preventDefault(),o?.(y)};return d.jsxs(j,{ref:M,className:`relative ${h}`,style:c,id:i,...u,children:[d.jsx(Eo,{onSubmit:w,className:"relative",children:d.jsxs(j,{className:`relative flex items-center ${a?"gap-2":""}`,children:[d.jsxs(j,{className:"relative flex-1",children:[d.jsx(j,{className:"absolute left-3 top-1/2 -translate-y-1/2 pointer-events-none",children:d.jsx(fe,{name:nt.Search,className:"w-5 h-5 text-gray-400 dark:text-gray-500"})}),d.jsx(At,{type:"search",name:e,value:T,onChange:S,onFocus:()=>k(!0),onBlur:()=>setTimeout(()=>k(!1),200),placeholder:g,className:"w-full pl-10 pr-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white text-sm"})]}),a&&d.jsx(R,{type:"submit",className:"px-4 py-2 bg-gray-800 dark:bg-gray-600 text-white rounded-lg hover:bg-gray-900 dark:hover:bg-gray-500 transition-colors text-sm font-medium cursor-pointer h-[42px]",children:Oc("common.search")})]})}),I&&d.jsx(j,{className:"absolute z-10 w-full mt-2 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg shadow-lg max-h-64 overflow-y-auto",children:s.map(y=>d.jsx(j,{className:"px-4 py-3 hover:bg-gray-50 dark:hover:bg-gray-700 cursor-pointer border-b border-gray-100 dark:border-gray-700 last:border-b-0 transition-colors",onClick:()=>N(y),children:d.jsxs(j,{className:"flex items-center gap-2",children:[d.jsx(fe,{name:nt.Search,className:"w-4 h-4 text-gray-400 dark:text-gray-500"}),d.jsx(te,{className:"text-sm text-gray-700 dark:text-gray-300",children:y.text})]})},y.id))})]})},kp=Object.freeze(Object.defineProperty({__proto__:null,SearchBar:Mc},Symbol.toStringTag,{value:"Module"})),_t=window.G7Core,Cp={sm:"md",md:"lg",lg:"xl",xl:"2xl","2xl":"3xl"};function Xa({src:e,fallbackText:t="U",size:r="lg",uploadEndpoint:n="/api/me/avatar",deleteEndpoint:o="/api/me/avatar",onUploadSuccess:a,onUploadError:s,onDeleteSuccess:l,onDeleteError:f,showDeleteButton:h=!0,accept:c="image/*",maxSize:i=5,className:u="",uploadButtonText:g,deleteButtonText:p,readOnly:m=!1,confirmDelete:x=!0,deleteConfirmMessage:k,confirmUpload:M=!0,uploadConfirmMessage:v,uploadSuccessActions:T,uploadErrorActions:I,deleteSuccessActions:S,deleteErrorActions:N,id:w,editorAttrs:y}){const E=C.useRef(null),[O,$]=C.useState(!1),[P,L]=C.useState(!1),[U,_]=C.useState(void 0),[b,z]=C.useState(null),[F,G]=C.useState(!1),[X,B]=C.useState(!1),[Y,H]=C.useState(null),[W,Q]=C.useState(null),be=C.useRef(`avatar-upload-${Math.random().toString(36).slice(2,11)}`).current;C.useEffect(()=>{if(Y){const ae=URL.createObjectURL(Y);return Q(ae),()=>URL.revokeObjectURL(ae)}else Q(null)},[Y]);const se=C.useCallback(ae=>_t?.t?.(ae)??ae,[]),pe=C.useCallback(()=>{const ae=document.cookie.match(/XSRF-TOKEN=([^;]+)/);return ae?decodeURIComponent(ae[1]):null},[]),ne=C.useCallback(()=>_t?.api?.getToken?.()??null,[]),Ae=C.useCallback(async ae=>{B(!1),H(null),$(!0),z(null);try{const ce=new FormData;ce.append("avatar",ae);const ke=pe(),Ee=ne(),Be=await fetch(n,{method:"POST",headers:{Accept:"application/json",...ke&&{"X-XSRF-TOKEN":ke},...Ee&&{Authorization:`Bearer ${Ee}`}},credentials:"include",body:ce});if(!Be.ok){const Pe=await Be.json().catch(()=>({}));throw new Error(Pe.message||se("attachment.upload_failed"))}const Me=await Be.json(),Ke=Me.data||Me;Ke.avatar&&_(Ke.avatar),a?.(Ke),_t?.toast?.success?.(se("mypage.profile.avatar_uploaded")),T?.length?T.forEach(Pe=>_t?.dispatch?.(Pe)):_t?.refetchDataSource?.("user")}catch(ce){const ke=ce instanceof Error?ce:new Error(String(ce));z(ke.message),s?.(ke),_t?.toast?.error?.(ke.message),I?.length&&I.forEach(Ee=>_t?.dispatch?.(Ee))}finally{$(!1),E.current&&(E.current.value="")}},[n,a,s,pe,ne,se]),ve=C.useCallback(async ae=>{const ce=ae.target.files?.[0];if(!ce)return;z(null);const ke=i*1024*1024;if(ce.size>ke){const Ee=new Error(se("attachment.file_too_large").replace(":max",`${i}MB`));z(Ee.message),s?.(Ee),_t?.toast?.error?.(Ee.message);return}if(c&&!ce.type.match(c.replace("*",".*"))){const Ee=new Error(se("attachment.invalid_file_type"));z(Ee.message),s?.(Ee),_t?.toast?.error?.(Ee.message);return}M?(H(ce),B(!0)):Ae(ce)},[i,c,s,M,Ae,se]),We=C.useCallback(async()=>{G(!1),L(!0),z(null);try{const ae=pe(),ce=ne(),ke=await fetch(o,{method:"DELETE",headers:{Accept:"application/json","Content-Type":"application/json",...ae&&{"X-XSRF-TOKEN":ae},...ce&&{Authorization:`Bearer ${ce}`}},credentials:"include"});if(!ke.ok){const Ee=await ke.json().catch(()=>({}));throw new Error(Ee.message||se("attachment.delete_failed"))}_(null),l?.(),_t?.toast?.success?.(se("mypage.profile.avatar_deleted")),S?.length?S.forEach(Ee=>_t?.dispatch?.(Ee)):_t?.refetchDataSource?.("user")}catch(ae){const ce=ae instanceof Error?ae:new Error(String(ae));z(ce.message),f?.(ce),_t?.toast?.error?.(ce.message),N?.length&&N.forEach(ke=>_t?.dispatch?.(ke))}finally{L(!1)}},[o,l,f,pe,ne,se]),me=C.useCallback(()=>{x?G(!0):We()},[x,We]),Te=C.useCallback(()=>{G(!1)},[]),ge=C.useCallback(()=>{B(!1),H(null),E.current&&(E.current.value="")},[]),Oe=C.useCallback(()=>{Y&&Ae(Y)},[Y,Ae]),we=U===void 0?e:U,$e=!!we,ze=Cp[r]||"lg",K=F||X,oe=()=>d.jsxs(j,{className:"flex items-center justify-center gap-2",children:[d.jsx(At,{ref:E,id:be,type:"file",accept:c,className:"hidden",onChange:ve,disabled:O}),!$e&&d.jsxs(Ar,{htmlFor:be,className:`inline-flex items-center gap-2 px-4 py-2 border border-gray-300 dark:border-gray-600 text-gray-700 dark:text-gray-300 rounded-lg text-sm font-medium hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors cursor-pointer ${O?"opacity-50 cursor-not-allowed":""}`,children:[O?d.jsx(fe,{name:"spinner",size:"sm",className:"animate-spin"}):d.jsx(fe,{name:"image",size:"sm"}),d.jsx(te,{children:g||se("mypage.profile.register_avatar")})]}),$e&&d.jsxs(d.Fragment,{children:[d.jsxs(Ar,{htmlFor:be,className:`inline-flex items-center gap-2 px-4 py-2 border border-gray-300 dark:border-gray-600 text-gray-700 dark:text-gray-300 rounded-lg text-sm font-medium hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors cursor-pointer ${O?"opacity-50 cursor-not-allowed":""}`,children:[O?d.jsx(fe,{name:"spinner",size:"sm",className:"animate-spin"}):d.jsx(fe,{name:"upload",size:"sm"}),d.jsx(te,{children:g||se("common.change")})]}),h&&d.jsxs(R,{type:"button",className:"inline-flex items-center gap-2 px-4 py-2 border border-red-300 dark:border-red-600 text-red-600 dark:text-red-400 rounded-lg text-sm font-medium hover:bg-red-50 dark:hover:bg-red-900/20 transition-colors disabled:opacity-50 disabled:cursor-not-allowed",onClick:me,disabled:P,children:[P?d.jsx(fe,{name:"spinner",size:"sm",className:"animate-spin"}):d.jsx(fe,{name:"trash-2",size:"sm"}),d.jsx(te,{children:p||se("common.delete")})]})]})]});return d.jsxs(j,{className:`flex flex-col items-center gap-3 ${u}`,id:w,...y,children:[X&&W?d.jsxs(j,{className:"relative",children:[d.jsx(jt,{src:W,alt:"Preview",className:`rounded-full object-cover ${r==="sm"?"w-10 h-10":r==="md"?"w-12 h-12":r==="lg"?"w-16 h-16":r==="xl"?"w-24 h-24":"w-32 h-32"}`}),d.jsx(j,{className:"absolute inset-0 rounded-full bg-blue-500/20 border-2 border-blue-500 border-dashed"})]}):d.jsx(Qr,{avatar:we||void 0,name:t,size:ze}),!m&&d.jsxs(j,{className:"w-[220px] flex flex-col items-center gap-2",children:[X&&Y&&d.jsxs(j,{className:"w-full p-3 bg-blue-50 dark:bg-blue-950/50 border border-blue-200 dark:border-blue-800 rounded-lg",children:[d.jsxs(j,{className:"flex items-center gap-2 mb-2",children:[d.jsx(fe,{name:"upload",size:"sm",className:"text-blue-600 dark:text-blue-400 shrink-0"}),d.jsx(te,{className:"text-sm font-medium text-blue-800 dark:text-blue-300",children:v||se("mypage.profile.upload_avatar_confirm")})]}),d.jsxs(j,{className:"text-xs text-blue-600 dark:text-blue-400 truncate mb-3",title:Y.name,children:[Y.name," (",(Y.size/1024/1024).toFixed(2),"MB)"]}),d.jsxs(j,{className:"grid grid-cols-2 gap-2",children:[d.jsxs(R,{type:"button",className:"inline-flex items-center justify-center gap-1.5 px-3 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-md text-sm font-medium transition-colors disabled:opacity-50",onClick:Oe,disabled:O,children:[O?d.jsx(fe,{name:"spinner",size:"sm",className:"animate-spin"}):d.jsx(fe,{name:"check",size:"sm"}),d.jsx(te,{children:se("common.confirm")})]}),d.jsx(R,{type:"button",className:"inline-flex items-center justify-center gap-1.5 px-3 py-2 bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-600 text-gray-700 dark:text-gray-300 rounded-md text-sm font-medium hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors",onClick:ge,children:d.jsx(te,{children:se("common.cancel")})})]})]}),F&&d.jsxs(j,{className:"w-full p-3 bg-red-50 dark:bg-red-950/50 border border-red-200 dark:border-red-800 rounded-lg",children:[d.jsxs(j,{className:"flex items-center gap-2 mb-2",children:[d.jsx(fe,{name:"alert-triangle",size:"sm",className:"text-red-600 dark:text-red-400 shrink-0"}),d.jsx(te,{className:"text-sm font-medium text-red-800 dark:text-red-300",children:k||se("mypage.profile.delete_avatar_confirm")})]}),d.jsxs(j,{className:"grid grid-cols-2 gap-2",children:[d.jsxs(R,{type:"button",className:"inline-flex items-center justify-center gap-1.5 px-3 py-2 bg-red-600 hover:bg-red-700 text-white rounded-md text-sm font-medium transition-colors disabled:opacity-50",onClick:We,disabled:P,children:[P?d.jsx(fe,{name:"spinner",size:"sm",className:"animate-spin"}):d.jsx(fe,{name:"trash-2",size:"sm"}),d.jsx(te,{children:se("common.delete")})]}),d.jsx(R,{type:"button",className:"inline-flex items-center justify-center gap-1.5 px-3 py-2 bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-600 text-gray-700 dark:text-gray-300 rounded-md text-sm font-medium hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors",onClick:Te,children:d.jsx(te,{children:se("common.cancel")})})]})]}),!K&&oe()]}),b&&!K&&d.jsxs(j,{className:"flex items-center gap-2 text-sm text-red-600 dark:text-red-400",children:[d.jsx(fe,{name:"alert-circle",size:"sm"}),d.jsx(te,{children:b})]})]})}const _p=Object.freeze(Object.defineProperty({__proto__:null,AvatarUploader:Xa,default:Xa},Symbol.toStringTag,{value:"Module"})),Ic=()=>window.G7Core,co=(e,t)=>window.G7Core?.t?.(e,t)??e,Dc={horizontal:"flex items-center gap-1.5",vertical:"flex flex-col items-start"},Sp=()=>[{key:"view_profile",label:co("userinfo.view_profile"),icon:"user",path:"/users/{{userId}}"},{key:"view_posts",label:co("userinfo.view_posts"),icon:"file-lines",path:"/users/{{userId}}/posts"}],qa=({text:e,title:t})=>e?d.jsx(te,{className:"text-sm text-gray-500 dark:text-gray-400",title:t,children:e}):null,Pc=({author:e,name:t,userId:r,subText:n,subTextTitle:o,isGuest:a=!1,isWithdrawn:s=!1,showDropdown:l=!0,clickable:f=!0,profilePath:h="/users/{userId}",className:c="",layout:i="vertical",text:u,stopPropagation:g=!1,menuItems:p,hideMenuItems:m=[],appendMenuItems:x=[],id:k,editorAttrs:M})=>{const v=r??e?.uuid,T=a||e?.is_guest||!1,I=s||e?.status==="withdrawn",S=I?co("userinfo.withdrawn_user"):u??t??e?.name??"",[N,w]=C.useState(!1),[y,E]=C.useState({top:0,left:0}),O=C.useRef(null),$=C.useRef(null),P=C.useRef(null),L=h.replace("{userId}",String(v??"")),U=`${Dc[i]} ${c}`;C.useEffect(()=>{if(!N)return;const H=Q=>{const be=Q.target,se=O.current?.contains(be),pe=$.current?.contains(be);!se&&!pe&&w(!1)},W=()=>w(!1);return document.addEventListener("mousedown",H),window.addEventListener("scroll",W,!0),window.addEventListener("resize",W),()=>{document.removeEventListener("mousedown",H),window.removeEventListener("scroll",W,!0),window.removeEventListener("resize",W)}},[N]);const _=C.useCallback(()=>{if(p&&p.length>0)return p.filter(Q=>Q.show!==!1);let W=Sp().filter(Q=>!m.includes(Q.key)&&Q.show!==!1);return x&&x.length>0&&(W=[...W,...x.filter(Q=>Q.show!==!1)]),W},[p,m,x]),b=C.useCallback(()=>{if(P.current){const H=P.current.getBoundingClientRect();E({top:H.bottom+4,left:H.left})}},[]),z=C.useCallback((H,W)=>{if(W.stopPropagation(),W.preventDefault(),w(!1),H.onClick)H.onClick();else if(H.path){const Q=H.path.replace(/\{\{userId\}\}/g,String(v??"")),be=Ic();be?.navigate?be.navigate(Q):window.location.href=Q}},[v]),F=C.useCallback(H=>{H.preventDefault(),g&&H.stopPropagation(),!(T||!v)&&(l?(N||b(),w(W=>!W)):f&&Ic()?.navigate?.(L))},[g,T,v,l,N,b,f,L]);if(T||!v)return d.jsxs(j,{className:U,id:k,...M,children:[d.jsxs(j,{className:"flex items-center gap-1.5 text-gray-400 dark:text-gray-500",children:[d.jsx(te,{children:S}),d.jsx(te,{className:"text-xs px-1.5 py-0.5 bg-gray-100 dark:bg-gray-700 text-gray-600 dark:text-gray-300 rounded",children:co("userinfo.guest_badge")})]}),d.jsx(qa,{text:n,title:o})]});if(I)return d.jsxs(j,{className:U,id:k,...M,children:[d.jsx(te,{className:"text-gray-400 dark:text-gray-500 line-through",children:S}),d.jsx(qa,{text:n,title:o})]});const G=_(),B=/\btext-\S+/.test(c)?"":"text-gray-900 dark:text-white",Y=l||f?`${B} hover:text-blue-600 dark:hover:text-blue-400 cursor-pointer font-medium`.trim():`${B} font-medium`.trim();return d.jsxs(j,{ref:O,className:`relative inline-block ${c}`,id:k,...M,children:[d.jsxs(j,{className:Dc[i],children:[d.jsx(R,{ref:P,onClick:F,className:Y,children:S}),d.jsx(qa,{text:n,title:o})]}),l&&N&&G.length>0&&tr.createPortal(d.jsx(j,{ref:$,className:"fixed w-40 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg shadow-lg py-1 z-[9999]",style:{top:y.top,left:y.left},"data-testid":"author-dropdown-menu",children:G.map(H=>d.jsxs(R,{onClick:W=>z(H,W),className:"flex items-center justify-start gap-2 w-full text-left px-3 py-2 text-sm text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 cursor-pointer","data-testid":`menu-item-${H.key}`,children:[H.icon&&d.jsx(fe,{name:H.icon,className:"w-4 h-4"}),d.jsx(te,{children:H.label})]},H.key))}),document.body)]})},Ep=Object.freeze(Object.defineProperty({__proto__:null,UserInfo:Pc},Symbol.toStringTag,{value:"Module"})),Lc=({tabs:e,activeTabId:t,onTabChange:r,variant:n="underline",className:o="",style:a,hiddenTabIds:s=[],mobileBreakpoint:l=768,id:f,editorAttrs:h})=>{const i=window.G7Core?.useResponsive,u=i?.(),g=u?u.width{!v.disabled&&v.id!==t&&r?.(v.id)},m=v=>{const T=v.target.value,I=Number(T),S=typeof T=="string"&&T!==""&&!Number.isNaN(I)?I:T,N=e.find(w=>String(w.id)===String(S));N&&p(N)},x=s.length>0?e.filter(v=>!s.includes(v.id)):e;if(g)return d.jsx(j,{className:`relative ${o}`,style:a,id:f,...h,children:d.jsx(ps,{value:t!==void 0?String(t):"",onChange:m,options:x.map(v=>({value:String(v.id),label:v.badge!==void 0?`${v.label} (${v.badge})`:v.label,disabled:v.disabled})),className:"w-full"})});const k=v=>{const T=v.id===t,I="flex items-center gap-2 px-3 py-2 font-medium text-sm transition-all shrink-0 whitespace-nowrap";if(v.disabled)return`${I} opacity-50 cursor-not-allowed text-gray-400 dark:text-gray-600`;switch(n){case"pills":return T?`${I} bg-blue-600 dark:bg-blue-500 text-white rounded-lg`:`${I} text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg`;case"underline":return T?`${I} text-gray-900 dark:text-white border-b-2 border-gray-900 dark:border-white`:`${I} text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-200 border-b-2 border-transparent hover:border-gray-300 dark:hover:border-gray-600`;default:return T?`${I} text-blue-600 dark:text-blue-400 bg-blue-50 dark:bg-blue-900/20 border-b-2 border-blue-600 dark:border-blue-400`:`${I} text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-200 hover:bg-gray-50 dark:hover:bg-gray-700 border-b-2 border-transparent`}},M=n==="underline"?"flex gap-0 border-b border-gray-200 dark:border-gray-700":"flex gap-2";return d.jsx(j,{className:`relative ${o}`,style:a,id:f,...h,children:d.jsx(Zr,{className:M,children:x.map(v=>d.jsxs(R,{type:"button",onClick:()=>p(v),disabled:v.disabled,className:k(v),children:[v.iconName&&d.jsx(fe,{name:v.iconName,size:"sm"}),d.jsx(te,{children:v.label}),v.badge!==void 0&&d.jsx(j,{className:"flex items-center justify-center min-w-5 h-5 px-1.5 bg-red-500 text-white text-xs font-bold rounded-full",children:d.jsx(te,{children:v.badge})})]},v.id))})})},Tp=Object.freeze(Object.defineProperty({__proto__:null,TabNavigation:Lc},Symbol.toStringTag,{value:"Module"})),Ap={Header:()=>Promise.resolve().then(()=>Bd),Footer:()=>Promise.resolve().then(()=>Yd),MobileNav:()=>Promise.resolve().then(()=>Xd),SlotContainer:()=>Promise.resolve().then(()=>Gd),NotificationCenter:()=>Promise.resolve().then(()=>jd),ProductCard:()=>Promise.resolve().then(()=>Iu),BrandMark:()=>Promise.resolve().then(()=>Pu),ImageGallery:()=>Promise.resolve().then(()=>ni),ProductImageViewer:()=>Promise.resolve().then(()=>Gg),QuantitySelector:()=>Promise.resolve().then(()=>jg),PostReactions:()=>Promise.resolve().then(()=>Vg),RichTextEditor:()=>Promise.resolve().then(()=>Kg),HtmlContent:()=>Promise.resolve().then(()=>Yg),HtmlEditor:()=>Promise.resolve().then(()=>Xg),ExpandableContent:()=>Promise.resolve().then(()=>qg),FileUploader:()=>Promise.resolve().then(()=>q0),ConfirmDialog:()=>Promise.resolve().then(()=>Jg),SocialLoginButtons:()=>Promise.resolve().then(()=>Q0),Toast:()=>Promise.resolve().then(()=>rp),PageTransitionIndicator:()=>Promise.resolve().then(()=>op),PageTransitionBlur:()=>Promise.resolve().then(()=>ap),PageSkeleton:()=>Promise.resolve().then(()=>yp),PageLoading:()=>Promise.resolve().then(()=>wp),ThemeToggle:()=>Promise.resolve().then(()=>$d),Pagination:()=>Promise.resolve().then(()=>xp),SearchBar:()=>Promise.resolve().then(()=>kp),Avatar:()=>Promise.resolve().then(()=>zd),AvatarUploader:()=>Promise.resolve().then(()=>_p),UserInfo:()=>Promise.resolve().then(()=>Ep),Modal:()=>Promise.resolve().then(()=>Qg),TabNavigation:()=>Promise.resolve().then(()=>Tp)},Np=({id:e,className:t,style:r,children:n,editorAttrs:o})=>d.jsx(j,{id:e,className:t,style:r,...o,children:n}),Op=({id:e,children:t,cols:r=1,responsive:n,gap:o,rowGap:a,colGap:s,autoRows:l,autoCols:f,flow:h="row",className:c="",style:i,onClick:u,editorAttrs:g})=>{const p=["grid"];if(r&&p.push(`grid-cols-${r}`),n&&(n.sm&&p.push(`sm:grid-cols-${n.sm}`),n.md&&p.push(`md:grid-cols-${n.md}`),n.lg&&p.push(`lg:grid-cols-${n.lg}`),n.xl&&p.push(`xl:grid-cols-${n.xl}`),n["2xl"]&&p.push(`2xl:grid-cols-${n["2xl"]}`)),o!==void 0&&o>0&&p.push(`gap-${o}`),a!==void 0&&a>0&&p.push(`gap-y-${a}`),s!==void 0&&s>0&&p.push(`gap-x-${s}`),l){const x={auto:"auto-rows-auto",min:"auto-rows-min",max:"auto-rows-max",fr:"auto-rows-fr"};p.push(x[l])}if(f){const x={auto:"auto-cols-auto",min:"auto-cols-min",max:"auto-cols-max",fr:"auto-cols-fr"};p.push(x[f])}const m={row:"grid-flow-row",col:"grid-flow-col",dense:"grid-flow-dense","row-dense":"grid-flow-row-dense","col-dense":"grid-flow-col-dense"};return p.push(m[h]),c&&p.push(c),d.jsx(j,{id:e,className:p.join(" "),style:i,onClick:u,...g,children:t})},Mp=({id:e,children:t,direction:r="row",justify:n="start",align:o="stretch",wrap:a="nowrap",gap:s=0,grow:l=!1,shrink:f=!0,className:h="",style:c,onClick:i,editorAttrs:u})=>{const g=["flex"],p={row:"flex-row","row-reverse":"flex-row-reverse",col:"flex-col","col-reverse":"flex-col-reverse"};g.push(p[r]);const m={wrap:"flex-wrap",nowrap:"flex-nowrap","wrap-reverse":"flex-wrap-reverse"};g.push(m[a]);const x={start:"justify-start",end:"justify-end",center:"justify-center",between:"justify-between",around:"justify-around",evenly:"justify-evenly"};g.push(x[n]);const k={start:"items-start",end:"items-end",center:"items-center",baseline:"items-baseline",stretch:"items-stretch"};return g.push(k[o]),s>0&&g.push(`gap-${s}`),l===!0?g.push("flex-grow"):typeof l=="number"&&l>0&&g.push(`flex-grow-${l}`),f===!1?g.push("shrink-0"):f===!0?g.push("flex-shrink"):typeof f=="number"&&f>0&&g.push(`flex-shrink-${f}`),h&&g.push(h),d.jsx(j,{id:e,className:g.join(" "),style:c,onClick:i,...u,children:t})},Ip=({id:e,title:t,subtitle:r,children:n,padding:o="md",background:a="none",maxWidth:s,centered:l=!1,border:f=!1,shadow:h="none",rounded:c=!1,className:i="",style:u,onClick:g,editorAttrs:p})=>{const m=[],x={none:"p-0",sm:"p-2",md:"p-4",lg:"p-6",xl:"p-8"};m.push(x[o]);const k={none:"",white:"bg-white dark:bg-gray-700",gray:"bg-gray-50 dark:bg-gray-800",primary:"bg-blue-50 dark:bg-blue-900/20",secondary:"bg-gray-50 dark:bg-gray-800"};if(a!=="none"&&m.push(k[a]),s){const v={sm:"max-w-sm",md:"max-w-md",lg:"max-w-lg",xl:"max-w-xl","2xl":"max-w-2xl","4xl":"max-w-4xl","6xl":"max-w-6xl","7xl":"max-w-7xl",full:"max-w-full"};m.push(v[s])}l&&m.push("mx-auto"),f&&m.push("border","border-gray-200","dark:border-gray-700");const M={none:"",sm:"shadow-sm",md:"shadow-md",lg:"shadow-lg",xl:"shadow-xl"};return h!=="none"&&m.push(M[h]),c&&m.push("rounded-lg"),i&&m.push(i),d.jsxs(bs,{id:e,className:m.join(" "),style:u,onClick:g,...p,children:[(t||r)&&d.jsxs(j,{className:"mb-4",children:[t&&d.jsx(gs,{className:"text-2xl font-bold text-gray-900 dark:text-white",children:t}),r&&d.jsx(Gt,{className:"mt-1 text-sm text-gray-600 dark:text-gray-400",children:r})]}),n]})},Dp=({id:e,leftWidth:t="250px",rightWidth:r="300px",leftSlot:n,centerSlot:o,rightSlot:a,className:s="",style:l,editorAttrs:f})=>{const h=`flex flex-row w-full h-full ${s}`.trim(),c={width:t,flexShrink:0},i={flex:1,minWidth:0},u={width:r,flexShrink:0};return d.jsxs(j,{id:e,className:h,style:l,...f,children:[d.jsx(j,{className:"flex flex-col",style:c,children:n}),d.jsx(j,{className:"flex flex-col",style:i,children:o}),d.jsx(j,{className:"flex flex-col",style:u,children:a})]})},Pp={identifier:"sirsoft-basic",vendor:"sirsoft",name:{ko:"Basic",en:"Basic"},version:"1.1.3",license:"MIT",description:{ko:"그누보드7 기본 사용자 템플릿",en:"Gnuboard7 default user template"},type:"user",locales:["ko","en"],author:{name:"sirsoft",email:"contact@sirsoft.com",url:"https://sirsoft.com"},release_date:"2026-01-07",g7_version:">=7.0.10",dependencies:{modules:{"sirsoft-board":">=1.0.0","sirsoft-ecommerce":">=1.1.0","sirsoft-page":">=1.1.0"},plugins:{"sirsoft-daum_postcode":">=1.0.0"}},assets:{css:["dist/css/components.css"],js:["dist/js/components.iife.js"],fonts:[],images:[]},components:{basic:["a","button","checkbox","div","form","h1","h2","h3","h4","hr","icon","img","input","label","li","nav","p","section","select","span","svg","table","tbody","td","textarea","th","thead","tr","ul"],composite:["header","footer","mobile-nav","product-card","rich-text-editor","image-gallery","quantity-selector","post-reactions","social-login-buttons","file-uploader","card","modal","toast","pagination","stat-card","status-badge","tab-navigation","empty-state","loading-spinner","breadcrumb","search-bar","dropdown"],layout:["container","flex","grid","section"]},preview:{thumbnail:"preview/thumbnail.png",screenshots:["preview/screenshot1.png","preview/screenshot2.png"]},error_config:{layouts:{401:"errors/401",403:"errors/403",404:"errors/404",500:"errors/500",503:"errors/503",maintenance:"errors/maintenance"}},features:{dark_mode:!0,responsive:!0,multi_language:!0,multi_currency:!0},github_url:"https://github.com/gnuboard/g7-template-sirsoft-basic",github_changelog_url:"https://github.com/gnuboard/g7-template-sirsoft-basic/blob/main/CHANGELOG.md",externals:[{id:"fontawesome",type:"style",asset:"vendor/font-awesome/6.4.0/css/all.inlined.css"},{id:"pretendard",type:"webfont",asset:"vendor/pretendard/1.3.9/pretendard-variable.css"}]},Lp={KRW:0,JPY:0};function $p(){try{const t=(window.G7Core?.state?.get?.()||{})?.modules?.["sirsoft-ecommerce"]?.language_currency;return Array.isArray(t?.currencies)?t.currencies:[]}catch{return[]}}function Kr(e,t){if(!Number.isFinite(e))return"0";const r=$p(),n=t||r.find(a=>a.is_default)?.code;if(!n)return e.toLocaleString();const o=r.find(a=>a.code===n)?.decimal_places??Lp[n]??2;try{return new Intl.NumberFormat(void 0,{style:"currency",currency:n,minimumFractionDigits:o,maximumFractionDigits:o}).format(e)}catch{return`${e.toLocaleString(void 0,{minimumFractionDigits:o,maximumFractionDigits:o})} ${n}`}}function uo(e,t,r,n=0){if(!e)return;const o=f=>f?.value??f?.price??0;let a;for(const[f,h]of Object.entries(e))if(h?.is_default){a=f;break}a=a??(e.KRW?"KRW":void 0);const s=a?o(e[a]):0,l={};for(const[f,h]of Object.entries(e)){const c=o(h);let i;r?i=r[f]?.value??0:n>0&&f===a?i=n:n>0&&s>0?i=n*c/s:i=0;const u=(c+i)*t;l[f]={value:u,formatted:Kr(u,f)}}return l}function fo(e){const t={};for(const n of e){const o=n?.multiCurrencyTotalPrice;if(o)for(const[a,s]of Object.entries(o)){const l=s?.value??s?.price??0;t[a]=(t[a]??0)+l}}const r={};for(const[n,o]of Object.entries(t))r[n]={value:o,formatted:Kr(o,n)};return r}function $c(e,t){if(!e||!t?.length)return 0;let r=0;for(const n of t){const o=e[n.id];if(o==null)continue;const a=n.values?.find(s=>s.id===Number(o));a&&(r+=a.price_adjustment??0)}return r}function Fc(e,t){if(!e||!t?.length)return;const r={};let n=!1;for(const a of t){const s=e[a.id];if(s==null)continue;const f=a.values?.find(h=>h.id===Number(s))?.multi_currency_price_adjustment;if(f){n=!0;for(const[h,c]of Object.entries(f)){const i=c?.value??c?.price??0;r[h]=(r[h]??0)+i}}}if(!n)return;const o={};for(const[a,s]of Object.entries(r))o[a]={value:s};return o}function go(e){return e.name_localized?e.name_localized:typeof e.name=="string"?e.name:e.name?.ko??""}function Fp(e,t,r){if(t&&r in t)return t[r];if(Array.isArray(e)){const n=e.find(o=>typeof o.key=="string"?o.key===r:o.key?.ko===r||Object.values(o.key).includes(r));return n?typeof n.value=="string"?n.value:n.value?.ko??Object.values(n.value)[0]:void 0}return e[r]}function Up(e,t){if(t)return t;if(!Array.isArray(e))return e;const r={};for(const n of e){const o=typeof n.key=="string"?n.key:n.key?.ko??Object.values(n.key)[0]??"",a=typeof n.value=="string"?n.value:n.value?.ko??Object.values(n.value)[0]??"";o&&(r[o]=a)}return r}function zp(e,t,r){return r.find(n=>!n?.option_values||!n?.is_active?!1:e.every(o=>{const a=go(o),s=t?.[a];return Fp(n.option_values,n.option_values_localized,a)===s}))}function Hp(e,t){const r=e.params;if(!r)return;const{optionGroups:n,options:o,selectedOptionItems:a,preferredCurrency:s}=r;let l;if(r.newGroupName&&r.newValue?l={...r.currentSelection??{},[r.newGroupName]:r.newValue}:l=r.currentSelection??{},!n?.every(S=>{const N=go(S);return l?.[N]}))return;const h=zp(n,l,o??[]);if(!h){t.setState?.({currentSelection:{},__mergeMode:"shallow"});return}const c=n.map(S=>l[go(S)]).join("_"),i=a??[];if(i.find(S=>S.id===c)){const S=window.G7Core;S?.toast?.warning?.(S?.t?.("sirsoft-ecommerce.shop.already_added_option")??"이미 추가된 옵션입니다."),t.setState?.({currentSelection:{},__mergeMode:"shallow"});return}const g=h.selling_price??0,p={};n.forEach(S=>{const N=go(S);p[N]=l[N]});const m=r.additionalOptionGroups??[],x={};for(const S of m){if(!S.is_required)continue;const N=S.values?.find(w=>w.is_default);N&&(x[S.id]=N.id)}const k=$c(x,m),M=Fc(x,m),v=g+k,T={id:c,optionId:h.id,options:p,optionValues:Up(h.option_values,h.option_values_localized),quantity:1,stock:h.stock_quantity??999,unitPrice:g,unitPriceFormatted:Kr(g,s),totalPrice:v,totalPriceFormatted:Kr(v,s),multiCurrencyUnitPrice:h.multi_currency_selling_price,multiCurrencyTotalPrice:uo(h.multi_currency_selling_price,1,M,k),additionalOptionSelections:x,additionalOptionsTotal:k,additionalOptionsMultiCurrencyTotal:M},I=[...i,T];t.setState?.({selectedOptionItems:I,selectedTotalMultiCurrency:fo(I),currentSelection:{},__mergeMode:"shallow"})}function Gp(e,t){const r=e.params;if(!r)return;const{selectedOptionItems:n,preferredCurrency:o}=r,a=Number(r.itemIndex)||0,s=Number(r.newQuantity)||1,l=(n??[]).map((f,h)=>{if(h!==a)return f;const c=f?.unitPrice??0,i=f?.additionalOptionsTotal??0,u=(c+i)*s;return{...f,quantity:s,totalPrice:u,totalPriceFormatted:Kr(u,o),multiCurrencyTotalPrice:uo(f?.multiCurrencyUnitPrice,s,f?.additionalOptionsMultiCurrencyTotal,i)}});t.setState?.({selectedOptionItems:l,selectedTotalMultiCurrency:fo(l)})}function jp(e,t){const r=e.params;if(!r)return;const{selectedOptionItems:n,additionalOptionGroups:o,preferredCurrency:a}=r,s=Number(r.itemIndex)||0,l=Number(r.additionalOptionId),f=Object.prototype.hasOwnProperty.call(r,"customText"),h=r.valueId===""||r.valueId==null?null:Number(r.valueId),c=(n??[]).map((i,u)=>{if(u!==s)return i;const g={...i.additionalOptionSelections??{}},p={...i.additionalOptionCustomTexts??{}};if(f){const T=String(r.customText??"");T.trim()===""?delete p[l]:p[l]=T}else h==null?(delete g[l],delete p[l]):(g[l]=h,(o??[]).find(S=>S.id===l)?.values?.find(S=>S.id===h)?.allow_custom_text||delete p[l]);const m=$c(g,o),x=Fc(g,o),k=i?.unitPrice??0,M=i?.quantity??1,v=(k+m)*M;return{...i,additionalOptionSelections:g,additionalOptionCustomTexts:p,additionalOptionsTotal:m,additionalOptionsMultiCurrencyTotal:x,totalPrice:v,totalPriceFormatted:Kr(v,a),multiCurrencyTotalPrice:uo(i?.multiCurrencyUnitPrice,M,x,m)}});t.setState?.({selectedOptionItems:c,selectedTotalMultiCurrency:fo(c)})}function Bp(e,t){const r=e.params;if(!r)return;const n=Number(r.itemIndex)||0,o=(r.selectedOptionItems??[]).filter((a,s)=>s!==n);t.setState?.({selectedOptionItems:o,selectedTotalMultiCurrency:fo(o)})}function Wp(e,t){const r=e.params;if(!r)return;const n=Math.max(1,Number(r.newQuantity)||1);t.setState?.({noOptionQuantity:n,noOptionTotalMultiCurrency:uo(r.multiCurrencyUnitPrice,n)})}const xn=window.G7Core?.createLogger?.("Handler:Cart")??{log:(...e)=>console.log("[Handler:Cart]",...e),warn:(...e)=>console.warn("[Handler:Cart]",...e),error:(...e)=>console.error("[Handler:Cart]",...e)};function kr(e){const t=window.G7Core;t?.state?.setLocal?(t.state.setLocal(e),xn.log("Local state updated:",e)):xn.warn("G7Core.state.setLocal not available")}function Uc(){return window.G7Core?.state?.getLocal?.()||{}}function Vp(){return window.G7Core?.state?.get?.()||{}}function Kp(e,t="info"){const r=window.G7Core;r?.toast?.show?r.toast.show(e,t):xn.warn("G7Core.toast.show not available, message:",e)}function Yp(e){const t=window.G7Core;return t?.t?t.t(e):e}function zc(){const e=window.G7Core;if(e?.dispatch){const r=Uc().selectedItems||[];e.dispatch({handler:"refetchDataSource",params:{dataSourceId:"cartItems"}}),xn.log("Refetching cartItems with selected_ids:",r)}else xn.warn("G7Core.dispatch not available")}function Xp(e,t){const{itemId:r,selectedItems:n}=e?.params||{},o=n||[];let a;o.includes(r)?a=o.filter(h=>h!==r):a=[...o,r];const l=Vp().cartItems?.data?.items||[],f=l.length>0&&a.length===l.length;kr({selectedItems:a,allSelected:f}),zc()}function qp(e,t){const{allItemIds:r,isSelected:n}=e?.params||{};kr({selectedItems:n?r:[],allSelected:n}),zc()}function Zp(e,t){const{optionName:r,optionValue:n}=e?.params||{},a=Uc().selectedOptions||{};kr({selectedOptions:{...a,[r]:n}})}function Qp(e,t){const{targetIds:r}=e?.params||{};if(!r||r.length===0){Kp(Yp("shop.cart.select_items_to_delete"),"warning");return}kr({deleteModal:{visible:!0,targetIds:r}})}function Jp(e,t){const{targetItem:r}=e?.params||{};kr({optionModal:{visible:!0,targetItem:r},selectedOptions:r?.options||{},selectedQuantity:r?.quantity})}function Rp(e,t){const{selectedItems:r}=e?.params||{};if(!r||r.length===0){kr({isCalculating:!1});return}kr({isCalculating:!0})}const Cr=window.G7Core?.createLogger?.("Handler:CartOptionChange")??{log:(...e)=>console.log("[Handler:CartOptionChange]",...e),warn:(...e)=>console.warn("[Handler:CartOptionChange]",...e),error:(...e)=>console.error("[Handler:CartOptionChange]",...e)};function Za(e){return e.name_localized?e.name_localized:typeof e.name=="string"?e.name:e.name?.ko??""}function em(e,t,r){if(t&&r in t)return t[r];if(Array.isArray(e)){const n=e.find(o=>typeof o.key=="string"?o.key===r:o.key?.ko===r||Object.values(o.key).includes(r));return n?typeof n.value=="string"?n.value:n.value?.ko??Object.values(n.value)[0]:void 0}return e[r]}function Hc(e,t,r){return!e||!t?.length?null:t.every(a=>{const s=Za(a);return e[s]})?r?.find(a=>t.every(s=>{const l=Za(s);return em(a.option_values,a.option_values_localized,l)===e[l]}))||null:(Cr.log("Not all options selected yet"),null)}function tm(e,t){const{selectedOptions:r,optionGroups:n,options:o}=e?.params||{};Cr.log("findMatchingOption called:",{selectedOptions:r,optionGroups:n,optionsCount:o?.length});const a=Hc(r,n,o);return Cr.log("Matched option:",a),{matchedOption:a}}function rm(e,t){const{currentOptionValues:r,optionGroups:n,options:o}=e?.params||{};if(Cr.log("initCartOptionSelection called:",{currentOptionValues:r,optionGroups:n,optionsCount:o?.length}),!n?.length||!r)return Cr.log("No option groups or current values, returning empty"),{selectedOptions:{},matchedOption:null};const a={};for(const l of n){const f=Za(l),h=r[f],c=l.values_localized??(Array.isArray(l.values)?l.values.map(i=>typeof i=="string"?i:i?.ko??""):[]);h&&c.includes(h)&&(a[f]=h)}Cr.log("Initial selectedOptions:",a);const s=Hc(a,n,o||[]);return Cr.log("Matched option:",s),{selectedOptions:a,matchedOption:s}}const kn="g7_cart_key",nm="/api/modules/sirsoft-ecommerce/cart/key",Qa="g7_guest_order_token",Gc="g7_guest_order_number",Ja="g7_guest_order_expires_at",ft=window.G7Core?.createLogger?.("Handler:Storage")??{log:(...e)=>console.log("[Handler:Storage]",...e),warn:(...e)=>console.warn("[Handler:Storage]",...e),error:(...e)=>console.error("[Handler:Storage]",...e)};async function jc(){try{const e=await fetch(nm,{method:"POST",headers:{"Content-Type":"application/json",Accept:"application/json"}});return e.ok?(await e.json()).data?.cart_key||null:(ft.error("Failed to issue cartKey from API:",e.status),null)}catch(e){return ft.error("Error issuing cartKey from API:",e),null}}function sr(e){const t=window.G7Core;t?.state?.set?(t.state.set(e),ft.log("Global state updated:",e)):ft.warn("G7Core.state.set not available")}function om(){try{const e=window.G7Config;return e?.user?e.user:(window.G7Core?.state?.get?.()||{}).currentUser||null}catch{return null}}async function am(e,t){let r=null;try{r=localStorage.getItem(kn)}catch{}if(r)ft.log("Existing cartKey loaded:",r);else if(r=await jc(),r)try{localStorage.setItem(kn,r),ft.log("New cartKey issued from API:",r)}catch{}else ft.error("Failed to issue cartKey from API");sr({cartKey:r})}function sm(e,t){try{return localStorage.getItem(kn)}catch{return null}}function Bc(e,t){try{localStorage.removeItem(kn),ft.log("cartKey removed from localStorage")}catch{}sr({cartKey:null})}async function lm(e,t){if(om()){Bc();return}const n=await jc();if(n){try{localStorage.setItem(kn,n),ft.log("New cartKey regenerated from API:",n)}catch{}sr({cartKey:n})}else ft.error("Failed to regenerate cartKey from API")}function im(e,t){const{key:r,value:n}=e?.params||{};if(!r){ft.warn("saveToStorage: key is required");return}try{localStorage.setItem(r,n),ft.log("Saved to storage:",r,n)}catch{}}function cm(e,t){const{key:r,defaultValue:n}=e?.params||{};if(!r)return ft.warn("loadFromStorage: key is required"),n||null;try{return localStorage.getItem(r)||n||null}catch{return n||null}}function Ra(){try{sessionStorage.removeItem(Qa),sessionStorage.removeItem(Gc),sessionStorage.removeItem(Ja)}catch{}}function dm(e,t){let r=null,n=null;try{r=sessionStorage.getItem(Qa),n=sessionStorage.getItem(Ja)}catch{}let o=!1;if(r){const a=n?new Date(n).getTime():NaN;(isNaN(a)||a<=Date.now())&&(Ra(),r=null,o=!0)}if(r){sr({guestOrderToken:r}),ft.log("Guest order token loaded from sessionStorage");return}if(!o){const s=window.G7Core?.state?.get?.("_global")?.guestOrderToken;if(typeof s=="string"&&s!==""){ft.log("Guest order token preserved from in-memory _global (sessionStorage empty)");return}}sr({guestOrderToken:null}),ft.log("No guest order token (visitor not authenticated)")}function um(e,t){const{token:r,orderNumber:n,expiresAt:o}=e?.params||{};if(!r||!n||!o){ft.warn("saveGuestOrderToken: token, orderNumber, expiresAt are required");return}try{sessionStorage.setItem(Qa,r),sessionStorage.setItem(Gc,n),sessionStorage.setItem(Ja,o)}catch{}sr({guestOrderToken:r})}function fm(e,t){Ra(),sr({guestOrderToken:null})}function gm(e,t){Ra(),sr({guestOrderToken:null})}function Wc(e){const t=window.G7Core?.state?.get?.()?.[e];return typeof t=="string"&&t.length>0?t:void 0}function hm(e,t){const r=e?.params??{},{product:n,priceField:o,currencyCode:a}=r,s=a||Wc("preferredCurrency")||Wc("defaultCurrency")||"",l=`multi_currency_${o}`,f=n[l];if(f&&f[s])return f[s].formatted;const h=`${o}_formatted`;return n[h]||String(n[o])}const Vc={KRW:"₩",USD:"$",JPY:"¥",CNY:"元",EUR:"€",GBP:"£"},pm={KRW:0,JPY:0};function Kc(){try{const t=(window.G7Core?.state?.get?.()||{})?.modules?.["sirsoft-ecommerce"]?.language_currency;return Array.isArray(t?.currencies)?t.currencies:[]}catch{return[]}}function Yc(e){const t=window.G7Core?.state?.get?.()?.[e];return typeof t=="string"&&t.length>0?t:void 0}function mm(e,t){const r=e?.params??{},{value:n,currencyCode:o,locale:a}=r,s=Kc(),l=o||Yc("preferredCurrency")||Yc("defaultCurrency")||s.find(u=>u.is_default)?.code;if(!l)return n.toLocaleString(a);const f=s.find(u=>u.code===l),h=f?.symbol&&f.symbol.length>0?f.symbol:Vc[l]??"",c=f?.decimal_places??pm[l]??2,i=n.toLocaleString(a,{minimumFractionDigits:c,maximumFractionDigits:c});return h?l==="KRW"||h==="₩"||h==="원"?`${i}원`:`${h}${i}`:`${i} ${l}`}function bm(e){const t=Kc().find(r=>r.code===e);return t?.symbol&&t.symbol.length>0?t.symbol:Vc[e]||e}const es=window.G7Core?.createLogger?.("Handler:Currency")??{log:(...e)=>console.log("[Handler:Currency]",...e),warn:(...e)=>console.warn("[Handler:Currency]",...e),error:(...e)=>console.error("[Handler:Currency]",...e)},Xc="g7_preferred_currency";function ym(){try{const e=window.G7Core?.state?.get?.()||{};return e.defaultCurrency||e?.modules?.["sirsoft-ecommerce"]?.language_currency?.default_currency||""}catch{return""}}function qc(e){window.G7Core?.state?.set?.({preferredCurrency:e})}function vm(e,t){const r=e?.params??{},{defaultCurrency:n=ym()}=r;let o=n;try{if(typeof window<"u"&&window.localStorage){const a=localStorage.getItem(Xc);a&&Zc(a)&&(o=a)}}catch{es.warn("localStorage 접근 불가, 기본 통화 사용:",n)}return qc(o),o}function wm(e,t){const{currencyCode:r}=e?.params??{};if(!Zc(r)){es.warn("유효하지 않은 통화 코드:",r);return}try{typeof window<"u"&&window.localStorage&&localStorage.setItem(Xc,r)}catch{es.warn("localStorage 저장 실패")}qc(r)}function Zc(e){return typeof e=="string"&&/^[A-Z]{3}$/.test(e)}const Yr=window.G7Core?.createLogger?.("Handler:SetTheme")??{log:(...e)=>console.log("[Handler:SetTheme]",...e),warn:(...e)=>console.warn("[Handler:SetTheme]",...e),error:(...e)=>console.error("[Handler:SetTheme]",...e)},ts=["auto","light","dark"],Qc="g7_color_scheme",xm=e=>e==="auto"?window.matchMedia("(prefers-color-scheme: dark)").matches?"dark":"light":e,ho=e=>{const t=xm(e);document.documentElement.setAttribute("data-theme",t),t==="dark"?document.documentElement.classList.add("dark"):document.documentElement.classList.remove("dark")};function km(){try{const e=localStorage.getItem(Qc),t=e&&ts.includes(e)?e:"auto";ho(t),Yr.log("Initial theme applied:",t)}catch(e){Yr.warn("Failed to load initial theme:",e),ho("auto")}}async function Cm(e,t){const r=e?.target;if(r&&typeof r=="string"&&ts.includes(r)){ho(r);return}km()}async function _m(e,t){let r;if(e?.target&&typeof e.target=="string"&&!e.target.includes("{{")&&(r=e.target),!r||typeof r!="string"){Yr.warn("Invalid theme:",r);return}if(!ts.includes(r)){Yr.warn("Unsupported theme:",r);return}const n=r;try{localStorage.setItem(Qc,n)}catch(o){Yr.error("Failed to save theme to localStorage:",o);return}ho(n),Yr.log("Theme changed to:",n)}const Sm=window.G7Core?.createLogger?.("Handler:RedirectToLoginWithReturn")??{log:(...e)=>console.log("[Handler:RedirectToLoginWithReturn]",...e),warn:(...e)=>console.warn("[Handler:RedirectToLoginWithReturn]",...e),error:(...e)=>console.error("[Handler:RedirectToLoginWithReturn]",...e)};async function Em(e,t){const r=window.G7Core;if(!r?.dispatch){Sm.error("G7Core.dispatch 가 초기화되지 않아 로그인 리다이렉트를 실행할 수 없습니다.");return}const n=window.location.pathname+window.location.search;await r.dispatch({handler:"navigate",params:{path:"/login",query:{redirect:n}}})}const rs=window.G7Core?.createLogger?.("Handler:DownloadAttachment")??{log:(...e)=>console.log("[Handler:DownloadAttachment]",...e),warn:(...e)=>console.warn("[Handler:DownloadAttachment]",...e),error:(...e)=>console.error("[Handler:DownloadAttachment]",...e)};async function Tm(e,t){const r=window.G7Core,{url:n,filename:o}=e?.params||{};if(!n){rs.warn("다운로드 URL 이 없어 요청을 건너뜁니다.");return}if(!r?.api?.get){rs.error("G7Core.api.get 이 초기화되지 않아 다운로드를 실행할 수 없습니다.");return}try{const a=await r.api.get(n,{responseType:"blob",headers:zn()});if(a){const s=URL.createObjectURL(a),l=document.createElement("a");l.href=s,l.download=o||"",document.body.appendChild(l),l.click(),document.body.removeChild(l),URL.revokeObjectURL(s)}}catch(a){rs.error("첨부파일 다운로드 실패",a),r?.toast?.error?.(r?.t?.("common.download_failed")??"Download failed")}}const po={"sirsoft-basic.addSelectedItemIfComplete":Hp,"sirsoft-basic.updateSelectedItemQuantity":Gp,"sirsoft-basic.removeSelectedItem":Bp,"sirsoft-basic.updateNoOptionQuantity":Wp,"sirsoft-basic.setBlockAdditionalOption":jp,"sirsoft-basic.getDisplayPrice":hm,"sirsoft-basic.formatCurrency":mm,"sirsoft-basic.getCurrencySymbol":bm,"sirsoft-basic.loadPreferredCurrency":vm,"sirsoft-basic.savePreferredCurrency":wm,setTheme:_m,initTheme:Cm,redirectToLoginWithReturn:Em,downloadAttachment:Tm,toggleCartItemSelection:Xp,selectAllCartItems:qp,setCartOption:Zp,openCartDeleteModal:Qp,openCartOptionModal:Jp,recalculateCart:Rp,findMatchingOption:tm,initCartOptionSelection:rm,initCartKey:am,getCartKey:sm,clearCartKey:Bc,regenerateCartKey:lm,saveToStorage:im,loadFromStorage:cm,initGuestOrderToken:dm,saveGuestOrderToken:um,clearGuestOrderToken:fm,clearGuestTokenOnEntry:gm},Cn=window.G7Core?.createLogger?.("Template:sirsoft-basic:IdentityLauncher")??{log:(...e)=>console.log("[Template:sirsoft-basic:IdentityLauncher]",...e),warn:(...e)=>console.warn("[Template:sirsoft-basic:IdentityLauncher]",...e),error:(...e)=>console.error("[Template:sirsoft-basic:IdentityLauncher]",...e)};function Am(e){const t=window.G7Core,r=t?.state?.getLocal?.()??{},n=t?.state?.getGlobal?.()??t?.state?.get?.()??{},o=(...f)=>f.find(h=>typeof h=="string"&&h.length>0),a=o(e.target?.email,n?.currentUser?.email,n?.user?.email,n?.auth?.user?.email,e.purpose==="signup"?r?.registerForm?.email:void 0,e.purpose==="password_reset"?r?.passwordResetForm?.email:void 0,e.purpose==="password_reset"?r?.email:void 0),s=o(e.target?.phone,n?.currentUser?.phone,n?.user?.phone,n?.auth?.user?.phone);if(!a&&!s)return null;const l={};return a&&(l.email=a),s&&(l.phone=s),l}function Nm(){const t=window.G7Core?.api?.getToken?.()??null;return t?{Authorization:`Bearer ${t}`}:{}}function Om(){try{const e=typeof window<"u"?window.localStorage?.getItem("g7_locale"):null;return e?{"Accept-Language":e}:{}}catch{return{}}}async function Mm(e,t){const r={purpose:e.purpose};e.provider_id&&(r.provider_id=e.provider_id),t&&(r.target=t);const n=e.challenge_start_url||"/api/identity/challenges",o=await fetch(n,{method:"POST",credentials:"include",headers:{"Content-Type":"application/json",Accept:"application/json",...Om(),...Nm()},body:JSON.stringify(r)}),a=await o.json().catch(()=>null);if(!o.ok||!a?.success){const l=a?.message??`Challenge 시작 실패 (HTTP ${o.status})`;throw new Error(l)}const s=a.data??a;return{id:s.id,expires_at:s.expires_at,render_hint:s.render_hint??e.render_hint??"text_code",public_payload:s.public_payload,redirect_url:s.redirect_url,max_attempts:typeof s.max_attempts=="number"?s.max_attempts:void 0}}async function Im(e){const t=window.G7Core,r=t?.identity;if(!t?.dispatch||!t?.state||!r)return Cn.error("G7Core 또는 G7Core.identity 가 초기화되지 않아 launcher 를 실행할 수 없습니다."),{status:"failed",failureCode:"G7_NOT_READY"};if(e.render_hint==="external_redirect"||e.redirect_url)return r.redirectExternally(e);const n=Am(e);let o;try{o=await Mm(e,n)}catch(p){Cn.error("Challenge 시작 실패:",p);try{await t.dispatch({handler:"toast",params:{type:"error",message:p instanceof Error?p.message:"본인인증 시작에 실패했습니다."}})}catch{}return{status:"failed",failureCode:"CHALLENGE_START_FAILED",reason:p instanceof Error?p.message:String(p)}}if(o.redirect_url)return r.redirectExternally({...e,render_hint:"external_redirect",redirect_url:o.redirect_url});const a=o.expires_at?new Date(o.expires_at).getTime():0,s=a>0?Math.max(0,Math.floor((a-Date.now())/1e3)):0,l=typeof o.max_attempts=="number"?o.max_attempts:0;t.state.set({identityChallenge:{policy_key:e.policy_key,purpose:e.purpose,provider_id:e.provider_id??null,render_hint:o.render_hint,challenge_id:o.id,expires_at:o.expires_at,public_payload:o.public_payload??{},target:n??null,code:"",error:null,attempts:0,maxAttempts:l,remainingSeconds:s,resendCooldown:0}});let f=a,h=0;const c=window.setInterval(()=>{const p=f>0?Math.max(0,Math.floor((f-Date.now())/1e3)):0;h=Math.max(0,h-1),t.state.set({identityChallenge:{remainingSeconds:p,resendCooldown:h}})},1e3);let i=null;try{i=t.state.subscribe?.(p=>{const m=p?.identityChallenge?.expires_at;if(typeof m=="string"){const k=new Date(m).getTime();Number.isFinite(k)&&k!==f&&(f=k)}const x=p?.identityChallenge?.resendCooldown;typeof x=="number"&&x>h&&(h=x)})??null}catch{}const u=r.createDeferred();try{await t.dispatch({handler:"openModal",target:"identity-challenge-modal"})}catch(p){return Cn.error("openModal 실패 — 풀페이지로 폴백:",p),window.clearInterval(c),i?.(),r.redirectExternally({...e,render_hint:"external_redirect",redirect_url:`/identity/challenge?challenge_id=${encodeURIComponent(o.id)}&return=${encodeURIComponent(window.location.href)}`})}const g=await u;return window.clearInterval(c),i?.(),g}function Dm(){const e=window.G7Core?.identity;if(!e?.setLauncher){Cn.warn("G7Core.identity 가 아직 초기화되지 않아 launcher 등록을 건너뜁니다. 코어 부트스트랩 순서를 확인하세요.");return}e.setLauncher(Im),Cn.log("IDV launcher registered (sirsoft-basic)")}const Jc=window.G7Core?.createLogger?.("Template:sirsoft-basic:device")??{log:(...e)=>console.log("[Template:sirsoft-basic:device]",...e),warn:(...e)=>console.warn("[Template:sirsoft-basic:device]",...e)};function Pm(){if(typeof navigator>"u")return!1;const e=navigator,t=(e.userAgent||"").toLowerCase(),r=((e.userAgentData?.platform??e.platform)||"").toLowerCase(),n=e.maxTouchPoints??0;return/iphone|ipad|ipod/.test(t)||/iphone|ipad|ipod|ios/.test(r)?!0:/macintosh|mac os x/.test(t)&&n>1}function Lm(){const e=window.G7Core;if(!e?.state?.get||!e?.state?.set){Jc.warn("G7Core.state not available; skip iOS device correction");return}if(!Pm())return;const r=(e.state.get()||{}).appConfig||{};r.isIos!==!0&&(e.state.set({appConfig:{...r,isIos:!0}}),Jc.log("appConfig.isIos corrected to true by client signals (iPadOS desktop-UA)"))}function $m(e){const t=e.icons;return Array.isArray(t)?t.filter(r=>!!r&&typeof r=="object"&&typeof r.value=="string"):[]}function Fm(e,t){const r=e.label;return typeof r=="string"?r.startsWith("$t:")?t(r.slice(3)):r:e.value}function Um(e,t,r){if(r==="")return!0;const n=r.toLowerCase();return e.value.toLowerCase().includes(n)||t.toLowerCase().includes(n)?!0:(e.keywords??[]).some(o=>o.toLowerCase().includes(n))}function zm({entry:e}){const t=e.preview;return t?.html?d.jsx("span",{className:"g7le-icon-preview",dangerouslySetInnerHTML:{__html:t.html},style:os}):t?.className?d.jsx("span",{className:`g7le-icon-preview ${t.className}`,style:os}):d.jsx("i",{className:`fas fa-${e.value}`,style:os,"aria-hidden":"true"})}function Hm({control:e,value:t,onChange:r,t:n}){const o=$m(e),a=t==null?"":String(t),s=typeof e.iconColumns=="number"?e.iconColumns:6,l=e.iconSearchPlaceholder,f=typeof l=="string"&&l.startsWith("$t:")?n(l.slice(3)):n("layout_editor.icon_picker.search_placeholder"),[h,c]=C.useState("");if(o.length===0)return d.jsx("div",{className:"g7le-widget g7le-widget--icon-picker","data-testid":"g7le-widget-icon-picker",style:Rc,children:d.jsx("input",{type:"text",className:"g7le-widget g7le-widget--icon-free","data-testid":"g7le-widget-icon-picker-free",value:a,placeholder:n("layout_editor.icon_picker.free_input_hint"),onChange:g=>r(g.target.value===""?void 0:g.target.value),style:ed})});const i=g=>Fm(g,n),u=o.filter(g=>Um(g,i(g),h));return d.jsxs("div",{className:"g7le-widget g7le-widget--icon-picker","data-testid":"g7le-widget-icon-picker",style:Rc,children:[d.jsx("input",{type:"text",className:"g7le-widget g7le-widget--icon-search","data-testid":"g7le-widget-icon-picker-search",value:h,placeholder:f,onChange:g=>c(g.target.value),style:ed}),d.jsxs("div",{className:"g7le-icon-grid","data-testid":"g7le-widget-icon-picker-grid",style:{...Gm,gridTemplateColumns:`repeat(${s}, minmax(0, 1fr))`},children:[d.jsx("button",{type:"button","data-testid":"g7le-icon-cell-none",title:n("layout_editor.icon_picker.none"),onClick:()=>r(void 0),"aria-pressed":a==="",style:a===""?td:ns,children:d.jsx("span",{style:jm,children:"—"})}),u.map(g=>{const p=g.value===a;return d.jsx("button",{type:"button","data-testid":`g7le-icon-cell-${g.value}`,title:i(g),"aria-pressed":p,onClick:()=>r(g.value),style:p?td:ns,children:d.jsx(zm,{entry:g})},g.value)})]}),u.length===0&&d.jsx("div",{"data-testid":"g7le-widget-icon-picker-empty",style:Bm,children:n("layout_editor.icon_picker.no_results")})]})}const Rc={display:"flex",flexDirection:"column",gap:6,width:"100%"},ed={padding:"5px 8px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,width:"100%"},Gm={display:"grid",gap:4,maxHeight:220,overflowY:"auto",padding:2},ns={display:"flex",alignItems:"center",justifyContent:"center",minHeight:34,padding:4,border:"1px solid #e2e8f0",borderRadius:6,background:"#fff",cursor:"pointer"},td={...ns,border:"2px solid #2563eb",background:"#eff6ff"},os={display:"inline-flex",alignItems:"center",justifyContent:"center",fontSize:16,lineHeight:1},jm={fontSize:10,color:"#475569",fontFamily:"monospace",overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap",maxWidth:"100%"},Bm={fontSize:11,color:"#94a3b8",fontStyle:"italic",padding:"4px 2px"},rd=window.G7Core?.createLogger?.("Template:sirsoft-basic:EditorWidgets")??{log:(...e)=>console.log("[Template:sirsoft-basic:EditorWidgets]",...e),warn:(...e)=>console.warn("[Template:sirsoft-basic:EditorWidgets]",...e)};function Wm(){if(typeof window>"u")return;const e=window.G7Core?.layoutEditor;if(!e?.registerWidget){rd.warn("G7Core.layoutEditor 미노출 — icon-picker 위젯 등록 보류");return}e.registerWidget("icon-picker",Hm),rd.log("편집기 위젯 'icon-picker' 등록됨(Font Awesome 검색 그리드)")}const as=window.G7Core?.createLogger?.("Template:sirsoft-basic")??{log:(...e)=>console.log("[Template:sirsoft-basic]",...e),warn:(...e)=>console.warn("[Template:sirsoft-basic]",...e),error:(...e)=>console.error("[Template:sirsoft-basic]",...e)};typeof window<"u"&&(window.G7TemplateHandlers=po);function nd(){if(typeof window<"u"){let e=0;const t=50,r=()=>{const n=window.G7Core?.getActionDispatcher?.();n?(Object.entries(po).forEach(([o,a])=>{n.registerHandler(o,a)}),as.log(`${Object.keys(po).length} custom handler(s) registered:`,Object.keys(po)),Dm(),Lm()):(e++,e<=t?(as.warn(`ActionDispatcher not found, retrying... (${e}/${t})`),setTimeout(r,100)):as.error("Failed to register handlers: ActionDispatcher not available after maximum retries"))};document.readyState==="complete"?r():window.addEventListener("load",r)}}return Wm(),nd(),ee.A=So,ee.Avatar=Qr,ee.AvatarUploader=Xa,ee.BrandMark=zo,ee.Button=R,ee.Checkbox=ms,ee.Code=Dd,ee.ConfirmDialog=wa,ee.Container=Np,ee.Div=j,ee.ExpandableContent=ba,ee.FileInput=ds,ee.FileUploader=lo,ee.Flex=Mp,ee.Footer=Cs,ee.Form=Eo,ee.Grid=Op,ee.H1=xd,ee.H2=gs,ee.H3=Tn,ee.H4=hs,ee.Header=xs,ee.Hr=To,ee.HtmlContent=ha,ee.HtmlEditor=ii,ee.Icon=fe,ee.IconName=nt,ee.ImageGallery=zr,ee.Img=jt,ee.Input=At,ee.Label=Ar,ee.Li=er,ee.MobileNav=_s,ee.Modal=ya,ee.Nav=Zr,ee.NotificationCenter=On,ee.Ol=kd,ee.Optgroup=Ed,ee.Option=Sd,ee.P=Gt,ee.PageLoading=Ac,ee.PageSkeleton=Ya,ee.PageTransitionBlur=Wa,ee.PageTransitionIndicator=Ba,ee.Pagination=Nc,ee.PasswordInput=fs,ee.PostReactions=si,ee.ProductCard=Ws,ee.ProductImageViewer=jn,ee.QuantitySelector=oi,ee.RichTextEditor=li,ee.SearchBar=Mc,ee.Section=bs,ee.SectionLayout=Ip,ee.Select=ps,ee.SlotContainer=Nn,ee.SocialLoginButtons=vc,ee.Span=te,ee.Svg=En,ee.TabNavigation=Lc,ee.Table=Td,ee.Tbody=Nd,ee.Td=Id,ee.Textarea=Co,ee.Th=Md,ee.Thead=Ad,ee.ThemeToggle=An,ee.ThreeColumnLayout=Dp,ee.Toast=ja,ee.Tr=Od,ee.Ul=_o,ee.UserInfo=Pc,ee.availablePasswordRules=ko,ee.compositeComponents=Ap,ee.defaultPasswordRules=us,ee.initTemplate=nd,ee.templateMetadata=Pp,Object.defineProperty(ee,Symbol.toStringTag,{value:"Module"}),ee})({},ReactJSXRuntime,React,ReactDOM); + `})]}):null},sp=Object.freeze(Object.defineProperty({__proto__:null,PageTransitionIndicator:Ba,default:Ba},Symbol.toStringTag,{value:"Module"})),xc=window.G7Core?.createLogger?.("Comp:PageTransitionBlur")??{log:(...e)=>console.log("[Comp:PageTransitionBlur]",...e),warn:(...e)=>console.warn("[Comp:PageTransitionBlur]",...e)},Wa=({className:e="",style:t})=>{const[r,n]=C.useState(!1);return C.useEffect(()=>{const o=window.G7Core?.TransitionManager;if(!o){xc.warn("TransitionManager를 찾을 수 없습니다.");return}return n(o.getIsPending()),o.subscribe(s=>{xc.log("isPending changed:",s),n(s)})},[]),r?d.jsx(j,{className:`fixed inset-0 z-40 bg-white/30 dark:bg-gray-900/30 backdrop-blur-sm transition-opacity duration-150 pointer-events-none ${e}`,style:t,role:"status","aria-label":"페이지 전환 중","data-testid":"page-transition-blur"}):null},lp=Object.freeze(Object.defineProperty({__proto__:null,PageTransitionBlur:Wa,default:Wa},Symbol.toStringTag,{value:"Module"})),kc={sm:640,md:768,lg:1024,xl:1280,"2xl":1536},Cc=new Set(["hidden","block","inline-block","inline","flex","inline-flex","grid","inline-grid","table","table-row","table-cell","contents","list-item","flow-root"]),ip=new Set(["Div","Flex","Grid","Section","Article","Nav","Aside","Main","Header","Footer","Fragment","Form","Container"]),cp=new Set(["H1","H2","H3","H4","H5","H6","P","Span","Label","A","Strong","Em","Small","Badge"]),dp=new Set(["Input","Textarea","Select","Checkbox","Radio","Toggle","DatePicker","TagInput","SearchBar"]),up=new Set(["Img","Image","Avatar","AvatarUploader","ImageGallery","ProductImageViewer","FileUploader"]),_c={DataGrid:(e,t)=>d.jsxs("div",{className:"w-full border border-gray-200 dark:border-gray-700 rounded-lg overflow-hidden",children:[d.jsx("div",{className:`h-10 bg-gray-100 dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700 ${e}`,children:d.jsxs("div",{className:"flex items-center h-full px-4 gap-8",children:[d.jsx("div",{className:"h-3 w-16 bg-gray-300 dark:bg-gray-600 rounded"}),d.jsx("div",{className:"h-3 w-24 bg-gray-300 dark:bg-gray-600 rounded"}),d.jsx("div",{className:"h-3 w-20 bg-gray-300 dark:bg-gray-600 rounded"}),d.jsx("div",{className:"h-3 w-16 bg-gray-300 dark:bg-gray-600 rounded"})]})}),Array.from({length:t},(r,n)=>d.jsx("div",{className:`h-12 border-b border-gray-100 dark:border-gray-800 ${e}`,style:{animationDelay:`${n*75}ms`},children:d.jsxs("div",{className:"flex items-center h-full px-4 gap-8",children:[d.jsx("div",{className:"h-3 w-12 bg-gray-200 dark:bg-gray-700 rounded"}),d.jsx("div",{className:"h-3 flex-1 bg-gray-200 dark:bg-gray-700 rounded"}),d.jsx("div",{className:"h-3 w-16 bg-gray-100 dark:bg-gray-800 rounded"}),d.jsx("div",{className:"h-3 w-14 bg-gray-100 dark:bg-gray-800 rounded"})]})},n))]}),Pagination:e=>d.jsx("div",{className:"flex justify-center gap-1.5 py-3",children:Array.from({length:5},(t,r)=>d.jsx("div",{className:`w-8 h-8 bg-gray-200 dark:bg-gray-700 rounded ${e}`},r))}),Tabs:e=>d.jsxs("div",{children:[d.jsx("div",{className:"flex gap-4 border-b border-gray-200 dark:border-gray-700 pb-2 mb-3",children:Array.from({length:3},(t,r)=>d.jsx("div",{className:`h-4 w-20 bg-gray-200 dark:bg-gray-700 rounded ${e}`},r))}),d.jsx("div",{className:`h-32 bg-gray-100 dark:bg-gray-800 rounded ${e}`})]}),TabNavigation:e=>d.jsx("div",{className:"flex gap-4 border-b border-gray-200 dark:border-gray-700 pb-2",children:Array.from({length:3},(t,r)=>d.jsx("div",{className:`h-4 w-20 bg-gray-200 dark:bg-gray-700 rounded ${e}`},r))}),ProductCard:e=>d.jsxs("div",{className:"rounded-lg overflow-hidden",children:[d.jsx("div",{className:`h-48 w-full bg-gray-200 dark:bg-gray-700 ${e}`}),d.jsxs("div",{className:"p-3 space-y-2",children:[d.jsx("div",{className:`h-3.5 w-3/4 bg-gray-200 dark:bg-gray-700 rounded ${e}`}),d.jsx("div",{className:`h-4 w-1/3 bg-gray-300 dark:bg-gray-600 rounded ${e}`})]})]}),UserInfo:e=>d.jsx("div",{className:`h-3 w-14 bg-gray-200 dark:bg-gray-700 rounded ${e}`}),QuantitySelector:e=>d.jsx("div",{className:`h-9 w-28 bg-gray-100 dark:bg-gray-800 rounded border border-gray-200 dark:border-gray-700 ${e}`}),Header:e=>d.jsx("header",{className:"bg-white dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700",children:d.jsx("div",{className:"max-w-7xl mx-auto px-4 sm:px-6 lg:px-8",children:d.jsxs("div",{className:"flex items-center justify-between h-16",children:[d.jsxs("div",{className:"flex items-center gap-8",children:[d.jsx("div",{className:`h-8 w-24 bg-gray-200 dark:bg-gray-700 rounded ${e}`}),d.jsx("div",{className:"hidden lg:flex items-center gap-6",children:Array.from({length:4},(t,r)=>d.jsx("div",{className:`h-3.5 w-16 bg-gray-200 dark:bg-gray-700 rounded ${e}`},r))})]}),d.jsx("div",{className:"flex items-center gap-4",children:Array.from({length:3},(t,r)=>d.jsx("div",{className:`h-8 w-8 bg-gray-200 dark:bg-gray-700 rounded-full ${e}`},r))})]})})}),Footer:e=>d.jsx("footer",{className:"bg-white dark:bg-gray-800 border-t border-gray-200 dark:border-gray-700 mt-auto",children:d.jsx("div",{className:"max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8",children:d.jsxs("div",{className:"flex flex-col items-center gap-4",children:[d.jsx("div",{className:`h-4 w-32 bg-gray-200 dark:bg-gray-700 rounded ${e}`}),d.jsx("div",{className:`h-3 w-48 bg-gray-100 dark:bg-gray-800 rounded ${e}`}),d.jsx("div",{className:"flex gap-3",children:Array.from({length:3},(t,r)=>d.jsx("div",{className:`h-6 w-6 bg-gray-200 dark:bg-gray-700 rounded-full ${e}`},r))}),d.jsx("div",{className:`h-3 w-40 bg-gray-100 dark:bg-gray-800 rounded ${e}`})]})})}),Modal:()=>d.jsx(d.Fragment,{}),Toast:()=>d.jsx(d.Fragment,{}),ConfirmDialog:()=>d.jsx(d.Fragment,{})};function fp(e){if(!e||e.length===0)return[];const t=e.filter(n=>n.if),r=e.filter(n=>!n.if);if(r.length>0&&t.length>0){const n=t.filter(o=>!Sc(o.if));return n.length>1&&gp(n)?[...r,Ec(n)]:[...r,...n]}if(t.length>1&&r.length===0){const n=t.filter(o=>!Sc(o.if));return n.length===0?[]:n.length===1?n:[Ec(n)]}return e}function Sc(e){const t=e.replace(/^\{\{|\}\}$/g,"").trim();return t.startsWith("!")||/\b(hasError|isError|isLoading|error|Error)\b/.test(t)||/===\s*0\s*(\)|\}|$)/.test(t)}function gp(e){if(e.length<2)return!1;const t=[];for(const n of e){const o=(n.if||"").replace(/^\{\{|\}\}$/g,"").trim(),a=o.match(/^(.+?)\s*===\s*['"]/);if(a)t.push(a[1].trim());else{const s=o.match(/(.+?)\s*===\s*['"]/);if(!s)return!1;t.push(s[1].trim())}}const r=t[0];return t.every(n=>n===r)}function Ec(e){return e.reduce((t,r)=>Va(r)>Va(t)?r:t)}function Va(e){return e.children?e.children.length+e.children.reduce((t,r)=>t+Va(r),0):0}function hp(e){return e.includes("{{")?e.replace(/\{\{([^}]+)\}\}/g,(t,r)=>{const n=r.match(/\?[\s]*['"]([^'"]*)['"]\s*:\s*['"]/);return n?n[1]:""}):e}function Ka(e,t){if(!e)return"";let r=e;return r=r.replace(/\b(bg-\S+|text-\S+|border\b|border-\S+|shadow\S*|ring-\S+|outline-\S+|divide-\S+|opacity-\S+|font-\S+|tracking-\S+|leading-\S+|whitespace-\S+|truncate|line-clamp-\S+|underline|no-underline|decoration-\S+|cursor-\S+|hover:\S+|focus:\S+|active:\S+|group-hover:\S+|transition\S*|duration-\S+|ease-\S+|animate-\S+|dark:\S+)\b/g,"").replace(/\s{2,}/g," ").trim(),(t==="Grid"||/\bgrid-cols-\d/.test(r))&&(r=r.replace(/\bgrid-cols-(\d+)/g,(n,o)=>{const a=parseInt(o);return a>4?`grid-cols-${Math.ceil(a/2)}`:`grid-cols-${a}`})),r}function pp(e){const{name:t,props:r}=e;if(!r||!t)return"";const n=[];if(t==="Flex"){n.push("flex");const o=r.direction||"row",a={row:"flex-row","row-reverse":"flex-row-reverse",col:"flex-col","col-reverse":"flex-col-reverse"};a[o]&&n.push(a[o]);const s=r.justify||"start",l={start:"justify-start",end:"justify-end",center:"justify-center",between:"justify-between",around:"justify-around",evenly:"justify-evenly"};l[s]&&n.push(l[s]);const f=r.align||"stretch",h={start:"items-start",end:"items-end",center:"items-center",baseline:"items-baseline",stretch:"items-stretch"};h[f]&&n.push(h[f]);const c=r.wrap||"nowrap",i={wrap:"flex-wrap",nowrap:"flex-nowrap","wrap-reverse":"flex-wrap-reverse"};i[c]&&n.push(i[c]),r.gap&&r.gap>0&&n.push(`gap-${r.gap}`)}if(t==="Grid"){n.push("grid");const o=r.cols||1;if(n.push(`grid-cols-${o}`),r.responsive){const a=r.responsive;a.sm&&n.push(`sm:grid-cols-${a.sm}`),a.md&&n.push(`md:grid-cols-${a.md}`),a.lg&&n.push(`lg:grid-cols-${a.lg}`),a.xl&&n.push(`xl:grid-cols-${a.xl}`),a["2xl"]&&n.push(`2xl:grid-cols-${a["2xl"]}`)}r.gap!==void 0&&r.gap>0&&n.push(`gap-${r.gap}`),r.rowGap!==void 0&&r.rowGap>0&&n.push(`gap-y-${r.rowGap}`),r.colGap!==void 0&&r.colGap>0&&n.push(`gap-x-${r.colGap}`)}return n.join(" ")}function mp(e){switch(e){case"pulse":return"animate-pulse";case"wave":return"animate-pulse";case"none":return"";default:return"animate-pulse"}}function io(e,t,r,n){return!e||e.length===0?[]:fp(e).map((a,s)=>Tc(a,t,r,s,n))}function bp(e,t){const r=e.props?.className||e.className||"";return e.responsive?t>=1024&&e.responsive.desktop?.props?.className?e.responsive.desktop.props.className:t>=768&&e.responsive.tablet?.props?.className?e.responsive.tablet.props.className:r:r}function yp(e,t){const r=e.split(/\s+/).filter(Boolean),n=[],o=[];for(const f of r){const h=f.match(/^(sm|md|lg|xl|2xl):(.+)$/);if(h){const[,c,i]=h;if(Cc.has(i)){n.push({breakpoint:kc[c]||0,display:i});continue}(kc[c]||0)<=t&&o.push(i);continue}if(Cc.has(f)){n.push({breakpoint:0,display:f});continue}o.push(f)}if(n.length===0)return{visible:!0,className:e};n.sort((f,h)=>f.breakpoint-h.breakpoint);let a="";for(const f of n)f.breakpoint<=t&&(a=f.display);a||(a=n[0].display);const s=a!=="hidden",l=s?[a,...o].join(" "):"";return{visible:s,className:l}}function vp(e){const t=e.match(/\bgrid-cols-\[([^\]]+)\]/);if(!t)return{className:e};const r=t[1].replace(/_/g," ");return{className:e.replace(/\bgrid-cols-\[([^\]]+)\]/,"").replace(/\s{2,}/g," ").trim(),style:{gridTemplateColumns:r}}}function Tc(e,t,r,n,o){const{name:a,children:s,iteration:l}=e,f=bp(e,o),h=pp(e),c=h?`${h} ${f}`.trim():f,i=hp(c),u=yp(i,o);if(!a||!u.visible)return null;const g=u.className;if(l)return d.jsx(C.Fragment,{children:Array.from({length:r},(m,x)=>Tc({...e,iteration:void 0},t,r,x,o))},n);if(_c[a])return d.jsx("div",{className:g,children:_c[a](t,r)},n);if(ip.has(a)){const m=Ka(g,a);if(!s||s.length===0){const T=/\b(h-\S|w-\S)/.test(m)?`${m} bg-gray-200 dark:bg-gray-700 rounded ${t}`:`${m} h-3.5 w-full bg-gray-200 dark:bg-gray-700 rounded ${t}`.trim();return d.jsx("div",{className:T},n)}const x=io(s,t,r,o);if(!(x.length>0&&x.some(v=>v!==null)))return null;const M=vp(m);return d.jsx("div",{className:M.className,style:M.style,children:x},n)}const p=Ka(g,a);if(cp.has(a)){const m={H1:"h-7",H2:"h-6",H3:"h-5",H4:"h-5",H5:"h-4",H6:"h-4",P:"h-3.5",Span:"h-3",Label:"h-3",A:"h-3.5",Badge:"h-5 w-16"},x={H1:"w-3/5",H2:"w-2/5",H3:"w-1/3",H4:"w-1/3",H5:"w-1/4",H6:"w-1/4",P:"w-full",Span:"w-1/4",Label:"w-1/4",A:"w-1/3",Strong:"w-1/5",Em:"w-1/4",Small:"w-1/5",Badge:"w-16"},k=m[a]||"h-3.5",M=x[a]||"w-full",v=a.startsWith("H")?"mb-2 mt-1":"mb-1.5";return d.jsx("div",{className:`${k} ${M} bg-gray-200 dark:bg-gray-700 rounded ${t} ${v} ${p}`.trim()},n)}if(dp.has(a))return a==="Textarea"?d.jsx("div",{className:`h-20 w-full bg-gray-100 dark:bg-gray-800 rounded border border-gray-200 dark:border-gray-700 mb-2 ${t}`},n):a==="Checkbox"||a==="Radio"||a==="Toggle"?d.jsx("div",{className:`h-5 w-5 bg-gray-200 dark:bg-gray-700 rounded mb-1.5 ${t}`},n):d.jsx("div",{className:`h-9 w-full bg-gray-100 dark:bg-gray-800 rounded border border-gray-200 dark:border-gray-700 mb-2 ${t}`},n);if(up.has(a))return a==="Avatar"||a==="AvatarUploader"?d.jsx("div",{className:`h-10 w-10 bg-gray-200 dark:bg-gray-700 rounded-full ${t}`},n):a==="ProductImageViewer"?d.jsx("div",{className:`h-80 w-full bg-gray-200 dark:bg-gray-700 rounded ${t}`},n):d.jsx("div",{className:`h-40 w-full bg-gray-200 dark:bg-gray-700 rounded mb-2 ${t}`},n);if(a==="Button"){if(s&&s.length>0){const m=Ka(g,a),x=io(s,t,r,o);if(!(x.length>0&&x.some(T=>T!==null)))return null;const v=/\b(border|rounded|px-\d|py-\d)/.test(f)?"border border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-800/50 rounded-lg":"";return d.jsx("div",{className:`${m} ${v}`.trim(),children:x},n)}return d.jsx("div",{className:`h-9 w-24 bg-gray-200 dark:bg-gray-700 rounded ${t}`},n)}return a==="Icon"?d.jsx("div",{className:`h-5 w-5 bg-gray-200 dark:bg-gray-700 rounded ${t}`},n):s&&s.length>0?d.jsx("div",{className:p,children:io(s,t,r,o)},n):d.jsx("div",{className:`h-3.5 w-full bg-gray-200 dark:bg-gray-700 rounded mb-1.5 ${t}`},n)}const Ya=({components:e,options:t})=>{const r=mp(t.animation),o=window.G7Core?.useResponsive,a=o?.(),s=a?a.width:typeof window<"u"?window.innerWidth:1024;return d.jsx("div",{className:"w-full",role:"status","aria-busy":"true","aria-label":"Loading...",children:io(e,r,t.iteration_count,s)})},wp=Object.freeze(Object.defineProperty({__proto__:null,PageSkeleton:Ya,default:Ya},Symbol.toStringTag,{value:"Module"})),xp=(e,t)=>window.G7Core?.t?.(e,t)??e,Ac=({options:e})=>{const t=document.documentElement.classList.contains("dark"),r=t?"rgb(17,24,39)":"rgb(249,250,251)",n=t?"#6b7280":"#9ca3af",o=t?"#9ca3af":"#6b7280";return d.jsxs("div",{style:{position:"absolute",inset:0,zIndex:2147483647,overflow:"hidden",background:r,display:"flex",flexDirection:"column",alignItems:"center",justifyContent:"flex-start",paddingTop:"15%",gap:"12px"},children:[d.jsx("div",{style:{width:"32px",height:"32px",border:`3px solid ${n}`,borderTopColor:"transparent",borderRadius:"50%",animation:"g7-spin 0.8s linear infinite"}}),d.jsx("span",{style:{color:o,fontSize:"14px"},children:e?.text||xp("nav.loading")})]})},kp=Object.freeze(Object.defineProperty({__proto__:null,default:Ac},Symbol.toStringTag,{value:"Module"})),Vr=(e,t)=>window.G7Core?.t?.(e,t)??e,Nc=({currentPage:e,totalPages:t,onPageChange:r,maxVisiblePages:n=5,showFirstLast:o=!0,showFirst:a,showLast:s,hasMorePages:l,className:f="",style:h,prevText:c,nextText:i,id:u,editorAttrs:g})=>{const p=t!=null,m=p?t:0,x=p?e{const N=[];if(!p){const w=Math.floor(n/2),y=e<=n?1:e-w;y>1&&(N.push(1),y>2&&N.push("..."));for(let E=y;E<=e;E++)N.push(E);return l&&(N.push(e+1),N.push("...")),N}if(m<=n+2)for(let w=1;w<=m;w++)N.push(w);else{const w=Math.floor(n/2);let y=Math.max(2,e-w),E=Math.min(m-1,e+w);e<=w+1?E=n:e>=m-w&&(y=m-n+1),N.push(1),y>2&&N.push("...");for(let O=y;O<=E;O++)N.push(O);E1&&N.push(m)}return N},[e,m,p,n,l]),T=N=>{N<1||N===e||p&&N>m||r(N)},I=()=>{e>1&&r(e-1)},S=()=>{x&&r(e+1)};return d.jsxs(j,{className:`flex items-center gap-2 ${f}`,style:h,role:"navigation","aria-label":Vr("common.pagination"),id:u,...g,children:[k&&d.jsx(R,{onClick:()=>T(1),disabled:e===1,className:"px-3 py-1 text-sm border border-gray-300 dark:border-gray-600 rounded hover:bg-gray-100 dark:hover:bg-gray-700 disabled:opacity-50 disabled:cursor-not-allowed text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-800","aria-label":Vr("common.first_page"),children:"«"}),d.jsx(R,{onClick:I,disabled:e===1,className:"px-3 py-1 text-sm border border-gray-300 dark:border-gray-600 rounded hover:bg-gray-100 dark:hover:bg-gray-700 disabled:opacity-50 disabled:cursor-not-allowed text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-800","aria-label":Vr("common.prev_page"),children:c||d.jsx(d.Fragment,{children:"‹"})}),d.jsx(j,{className:"flex items-center gap-1",children:v.map((N,w)=>{if(N==="...")return d.jsx(te,{className:"px-3 py-1 text-sm text-gray-500 dark:text-gray-400",children:"..."},`ellipsis-${w}`);const y=N,E=y===e;return d.jsx(R,{onClick:()=>T(y),className:`px-3 py-1 text-sm border rounded ${E?"bg-blue-500 dark:bg-blue-600 text-white border-blue-500 dark:border-blue-600":"border-gray-300 dark:border-gray-600 hover:bg-gray-100 dark:hover:bg-gray-700 text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-800"}`,"aria-label":Vr("common.page_n",{n:y}),"aria-current":E?"page":void 0,children:y},y)})}),d.jsx(R,{onClick:S,disabled:!x,className:"px-3 py-1 text-sm border border-gray-300 dark:border-gray-600 rounded hover:bg-gray-100 dark:hover:bg-gray-700 disabled:opacity-50 disabled:cursor-not-allowed text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-800","aria-label":Vr("common.next_page"),children:i||d.jsx(d.Fragment,{children:"›"})}),M&&d.jsx(R,{onClick:()=>T(m),disabled:e===m,className:"px-3 py-1 text-sm border border-gray-300 dark:border-gray-600 rounded hover:bg-gray-100 dark:hover:bg-gray-700 disabled:opacity-50 disabled:cursor-not-allowed text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-800","aria-label":Vr("common.last_page"),children:"»"})]})},Cp=Object.freeze(Object.defineProperty({__proto__:null,Pagination:Nc},Symbol.toStringTag,{value:"Module"})),Oc=(e,t)=>window.G7Core?.t?.(e,t)??e,Mc=({name:e="search",placeholder:t,value:r,onChange:n,onSubmit:o,showButton:a=!1,suggestions:s=[],onSuggestionClick:l,showSuggestions:f=!1,className:h="",style:c,id:i,editorAttrs:u})=>{const g=t??Oc("common.search_placeholder"),[p,m]=C.useState(""),[x,k]=C.useState(!1),M=C.useRef(null),v=C.useRef(""),T=r!==void 0?r:p,I=f&&x&&s.length>0&&T.length>0;C.useEffect(()=>{r!==void 0&&(v.current=r)},[r]);const S=y=>{const E=y.target.value;m(E),n?.(y),v.current!==""&&E===""&&!y.nativeEvent.inputType&&setTimeout(()=>{const O=M.current?.querySelector("form");O&&O.requestSubmit()},0),v.current=E},N=y=>{m(y.text);const E={target:{value:y.text,name:e}};n?.(E),l?.(y),k(!1)},w=y=>{y.preventDefault(),o?.(y)};return d.jsxs(j,{ref:M,className:`relative ${h}`,style:c,id:i,...u,children:[d.jsx(Eo,{onSubmit:w,className:"relative",children:d.jsxs(j,{className:`relative flex items-center ${a?"gap-2":""}`,children:[d.jsxs(j,{className:"relative flex-1",children:[d.jsx(j,{className:"absolute left-3 top-1/2 -translate-y-1/2 pointer-events-none",children:d.jsx(fe,{name:nt.Search,className:"w-5 h-5 text-gray-400 dark:text-gray-500"})}),d.jsx(At,{type:"search",name:e,value:T,onChange:S,onFocus:()=>k(!0),onBlur:()=>setTimeout(()=>k(!1),200),placeholder:g,className:"w-full pl-10 pr-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white text-sm"})]}),a&&d.jsx(R,{type:"submit",className:"px-4 py-2 bg-gray-800 dark:bg-gray-600 text-white rounded-lg hover:bg-gray-900 dark:hover:bg-gray-500 transition-colors text-sm font-medium cursor-pointer h-[42px]",children:Oc("common.search")})]})}),I&&d.jsx(j,{className:"absolute z-10 w-full mt-2 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg shadow-lg max-h-64 overflow-y-auto",children:s.map(y=>d.jsx(j,{className:"px-4 py-3 hover:bg-gray-50 dark:hover:bg-gray-700 cursor-pointer border-b border-gray-100 dark:border-gray-700 last:border-b-0 transition-colors",onClick:()=>N(y),children:d.jsxs(j,{className:"flex items-center gap-2",children:[d.jsx(fe,{name:nt.Search,className:"w-4 h-4 text-gray-400 dark:text-gray-500"}),d.jsx(te,{className:"text-sm text-gray-700 dark:text-gray-300",children:y.text})]})},y.id))})]})},_p=Object.freeze(Object.defineProperty({__proto__:null,SearchBar:Mc},Symbol.toStringTag,{value:"Module"})),_t=window.G7Core,Sp={sm:"md",md:"lg",lg:"xl",xl:"2xl","2xl":"3xl"};function Xa({src:e,fallbackText:t="U",size:r="lg",uploadEndpoint:n="/api/me/avatar",deleteEndpoint:o="/api/me/avatar",onUploadSuccess:a,onUploadError:s,onDeleteSuccess:l,onDeleteError:f,showDeleteButton:h=!0,accept:c="image/*",maxSize:i=5,className:u="",uploadButtonText:g,deleteButtonText:p,readOnly:m=!1,confirmDelete:x=!0,deleteConfirmMessage:k,confirmUpload:M=!0,uploadConfirmMessage:v,uploadSuccessActions:T,uploadErrorActions:I,deleteSuccessActions:S,deleteErrorActions:N,id:w,editorAttrs:y}){const E=C.useRef(null),[O,$]=C.useState(!1),[P,L]=C.useState(!1),[U,_]=C.useState(void 0),[b,z]=C.useState(null),[F,G]=C.useState(!1),[X,B]=C.useState(!1),[Y,H]=C.useState(null),[W,Q]=C.useState(null),be=C.useRef(`avatar-upload-${Math.random().toString(36).slice(2,11)}`).current;C.useEffect(()=>{if(Y){const ae=URL.createObjectURL(Y);return Q(ae),()=>URL.revokeObjectURL(ae)}else Q(null)},[Y]);const se=C.useCallback(ae=>_t?.t?.(ae)??ae,[]),pe=C.useCallback(()=>{const ae=document.cookie.match(/XSRF-TOKEN=([^;]+)/);return ae?decodeURIComponent(ae[1]):null},[]),ne=C.useCallback(()=>_t?.api?.getToken?.()??null,[]),Ae=C.useCallback(async ae=>{B(!1),H(null),$(!0),z(null);try{const ce=new FormData;ce.append("avatar",ae);const ke=pe(),Ee=ne(),Be=await fetch(n,{method:"POST",headers:{Accept:"application/json",...ke&&{"X-XSRF-TOKEN":ke},...Ee&&{Authorization:`Bearer ${Ee}`}},credentials:"include",body:ce});if(!Be.ok){const Pe=await Be.json().catch(()=>({}));throw new Error(Pe.message||se("attachment.upload_failed"))}const Me=await Be.json(),Ke=Me.data||Me;Ke.avatar&&_(Ke.avatar),a?.(Ke),_t?.toast?.success?.(se("mypage.profile.avatar_uploaded")),T?.length?T.forEach(Pe=>_t?.dispatch?.(Pe)):_t?.refetchDataSource?.("user")}catch(ce){const ke=ce instanceof Error?ce:new Error(String(ce));z(ke.message),s?.(ke),_t?.toast?.error?.(ke.message),I?.length&&I.forEach(Ee=>_t?.dispatch?.(Ee))}finally{$(!1),E.current&&(E.current.value="")}},[n,a,s,pe,ne,se]),ve=C.useCallback(async ae=>{const ce=ae.target.files?.[0];if(!ce)return;z(null);const ke=i*1024*1024;if(ce.size>ke){const Ee=new Error(se("attachment.file_too_large").replace(":max",`${i}MB`));z(Ee.message),s?.(Ee),_t?.toast?.error?.(Ee.message);return}if(c&&!ce.type.match(c.replace("*",".*"))){const Ee=new Error(se("attachment.invalid_file_type"));z(Ee.message),s?.(Ee),_t?.toast?.error?.(Ee.message);return}M?(H(ce),B(!0)):Ae(ce)},[i,c,s,M,Ae,se]),We=C.useCallback(async()=>{G(!1),L(!0),z(null);try{const ae=pe(),ce=ne(),ke=await fetch(o,{method:"DELETE",headers:{Accept:"application/json","Content-Type":"application/json",...ae&&{"X-XSRF-TOKEN":ae},...ce&&{Authorization:`Bearer ${ce}`}},credentials:"include"});if(!ke.ok){const Ee=await ke.json().catch(()=>({}));throw new Error(Ee.message||se("attachment.delete_failed"))}_(null),l?.(),_t?.toast?.success?.(se("mypage.profile.avatar_deleted")),S?.length?S.forEach(Ee=>_t?.dispatch?.(Ee)):_t?.refetchDataSource?.("user")}catch(ae){const ce=ae instanceof Error?ae:new Error(String(ae));z(ce.message),f?.(ce),_t?.toast?.error?.(ce.message),N?.length&&N.forEach(ke=>_t?.dispatch?.(ke))}finally{L(!1)}},[o,l,f,pe,ne,se]),me=C.useCallback(()=>{x?G(!0):We()},[x,We]),Te=C.useCallback(()=>{G(!1)},[]),ge=C.useCallback(()=>{B(!1),H(null),E.current&&(E.current.value="")},[]),Oe=C.useCallback(()=>{Y&&Ae(Y)},[Y,Ae]),we=U===void 0?e:U,$e=!!we,ze=Sp[r]||"lg",K=F||X,oe=()=>d.jsxs(j,{className:"flex items-center justify-center gap-2",children:[d.jsx(At,{ref:E,id:be,type:"file",accept:c,className:"hidden",onChange:ve,disabled:O}),!$e&&d.jsxs(Ar,{htmlFor:be,className:`inline-flex items-center gap-2 px-4 py-2 border border-gray-300 dark:border-gray-600 text-gray-700 dark:text-gray-300 rounded-lg text-sm font-medium hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors cursor-pointer ${O?"opacity-50 cursor-not-allowed":""}`,children:[O?d.jsx(fe,{name:"spinner",size:"sm",className:"animate-spin"}):d.jsx(fe,{name:"image",size:"sm"}),d.jsx(te,{children:g||se("mypage.profile.register_avatar")})]}),$e&&d.jsxs(d.Fragment,{children:[d.jsxs(Ar,{htmlFor:be,className:`inline-flex items-center gap-2 px-4 py-2 border border-gray-300 dark:border-gray-600 text-gray-700 dark:text-gray-300 rounded-lg text-sm font-medium hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors cursor-pointer ${O?"opacity-50 cursor-not-allowed":""}`,children:[O?d.jsx(fe,{name:"spinner",size:"sm",className:"animate-spin"}):d.jsx(fe,{name:"upload",size:"sm"}),d.jsx(te,{children:g||se("common.change")})]}),h&&d.jsxs(R,{type:"button",className:"inline-flex items-center gap-2 px-4 py-2 border border-red-300 dark:border-red-600 text-red-600 dark:text-red-400 rounded-lg text-sm font-medium hover:bg-red-50 dark:hover:bg-red-900/20 transition-colors disabled:opacity-50 disabled:cursor-not-allowed",onClick:me,disabled:P,children:[P?d.jsx(fe,{name:"spinner",size:"sm",className:"animate-spin"}):d.jsx(fe,{name:"trash-2",size:"sm"}),d.jsx(te,{children:p||se("common.delete")})]})]})]});return d.jsxs(j,{className:`flex flex-col items-center gap-3 ${u}`,id:w,...y,children:[X&&W?d.jsxs(j,{className:"relative",children:[d.jsx(jt,{src:W,alt:"Preview",className:`rounded-full object-cover ${r==="sm"?"w-10 h-10":r==="md"?"w-12 h-12":r==="lg"?"w-16 h-16":r==="xl"?"w-24 h-24":"w-32 h-32"}`}),d.jsx(j,{className:"absolute inset-0 rounded-full bg-blue-500/20 border-2 border-blue-500 border-dashed"})]}):d.jsx(Qr,{avatar:we||void 0,name:t,size:ze}),!m&&d.jsxs(j,{className:"w-[220px] flex flex-col items-center gap-2",children:[X&&Y&&d.jsxs(j,{className:"w-full p-3 bg-blue-50 dark:bg-blue-950/50 border border-blue-200 dark:border-blue-800 rounded-lg",children:[d.jsxs(j,{className:"flex items-center gap-2 mb-2",children:[d.jsx(fe,{name:"upload",size:"sm",className:"text-blue-600 dark:text-blue-400 shrink-0"}),d.jsx(te,{className:"text-sm font-medium text-blue-800 dark:text-blue-300",children:v||se("mypage.profile.upload_avatar_confirm")})]}),d.jsxs(j,{className:"text-xs text-blue-600 dark:text-blue-400 truncate mb-3",title:Y.name,children:[Y.name," (",(Y.size/1024/1024).toFixed(2),"MB)"]}),d.jsxs(j,{className:"grid grid-cols-2 gap-2",children:[d.jsxs(R,{type:"button",className:"inline-flex items-center justify-center gap-1.5 px-3 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-md text-sm font-medium transition-colors disabled:opacity-50",onClick:Oe,disabled:O,children:[O?d.jsx(fe,{name:"spinner",size:"sm",className:"animate-spin"}):d.jsx(fe,{name:"check",size:"sm"}),d.jsx(te,{children:se("common.confirm")})]}),d.jsx(R,{type:"button",className:"inline-flex items-center justify-center gap-1.5 px-3 py-2 bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-600 text-gray-700 dark:text-gray-300 rounded-md text-sm font-medium hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors",onClick:ge,children:d.jsx(te,{children:se("common.cancel")})})]})]}),F&&d.jsxs(j,{className:"w-full p-3 bg-red-50 dark:bg-red-950/50 border border-red-200 dark:border-red-800 rounded-lg",children:[d.jsxs(j,{className:"flex items-center gap-2 mb-2",children:[d.jsx(fe,{name:"alert-triangle",size:"sm",className:"text-red-600 dark:text-red-400 shrink-0"}),d.jsx(te,{className:"text-sm font-medium text-red-800 dark:text-red-300",children:k||se("mypage.profile.delete_avatar_confirm")})]}),d.jsxs(j,{className:"grid grid-cols-2 gap-2",children:[d.jsxs(R,{type:"button",className:"inline-flex items-center justify-center gap-1.5 px-3 py-2 bg-red-600 hover:bg-red-700 text-white rounded-md text-sm font-medium transition-colors disabled:opacity-50",onClick:We,disabled:P,children:[P?d.jsx(fe,{name:"spinner",size:"sm",className:"animate-spin"}):d.jsx(fe,{name:"trash-2",size:"sm"}),d.jsx(te,{children:se("common.delete")})]}),d.jsx(R,{type:"button",className:"inline-flex items-center justify-center gap-1.5 px-3 py-2 bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-600 text-gray-700 dark:text-gray-300 rounded-md text-sm font-medium hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors",onClick:Te,children:d.jsx(te,{children:se("common.cancel")})})]})]}),!K&&oe()]}),b&&!K&&d.jsxs(j,{className:"flex items-center gap-2 text-sm text-red-600 dark:text-red-400",children:[d.jsx(fe,{name:"alert-circle",size:"sm"}),d.jsx(te,{children:b})]})]})}const Ep=Object.freeze(Object.defineProperty({__proto__:null,AvatarUploader:Xa,default:Xa},Symbol.toStringTag,{value:"Module"})),Ic=()=>window.G7Core,co=(e,t)=>window.G7Core?.t?.(e,t)??e,Dc={horizontal:"flex items-center gap-1.5",vertical:"flex flex-col items-start"},Tp=()=>[{key:"view_profile",label:co("userinfo.view_profile"),icon:"user",path:"/users/{{userId}}"},{key:"view_posts",label:co("userinfo.view_posts"),icon:"file-lines",path:"/users/{{userId}}/posts"}],qa=({text:e,title:t})=>e?d.jsx(te,{className:"text-sm text-gray-500 dark:text-gray-400",title:t,children:e}):null,Pc=({author:e,name:t,userId:r,subText:n,subTextTitle:o,isGuest:a=!1,isWithdrawn:s=!1,showDropdown:l=!0,clickable:f=!0,profilePath:h="/users/{userId}",className:c="",layout:i="vertical",text:u,stopPropagation:g=!1,menuItems:p,hideMenuItems:m=[],appendMenuItems:x=[],id:k,editorAttrs:M})=>{const v=r??e?.uuid,T=a||e?.is_guest||!1,I=s||e?.status==="withdrawn",S=I?co("userinfo.withdrawn_user"):u??t??e?.name??"",[N,w]=C.useState(!1),[y,E]=C.useState({top:0,left:0}),O=C.useRef(null),$=C.useRef(null),P=C.useRef(null),L=h.replace("{userId}",String(v??"")),U=`${Dc[i]} ${c}`;C.useEffect(()=>{if(!N)return;const H=Q=>{const be=Q.target,se=O.current?.contains(be),pe=$.current?.contains(be);!se&&!pe&&w(!1)},W=()=>w(!1);return document.addEventListener("mousedown",H),window.addEventListener("scroll",W,!0),window.addEventListener("resize",W),()=>{document.removeEventListener("mousedown",H),window.removeEventListener("scroll",W,!0),window.removeEventListener("resize",W)}},[N]);const _=C.useCallback(()=>{if(p&&p.length>0)return p.filter(Q=>Q.show!==!1);let W=Tp().filter(Q=>!m.includes(Q.key)&&Q.show!==!1);return x&&x.length>0&&(W=[...W,...x.filter(Q=>Q.show!==!1)]),W},[p,m,x]),b=C.useCallback(()=>{if(P.current){const H=P.current.getBoundingClientRect();E({top:H.bottom+4,left:H.left})}},[]),z=C.useCallback((H,W)=>{if(W.stopPropagation(),W.preventDefault(),w(!1),H.onClick)H.onClick();else if(H.path){const Q=H.path.replace(/\{\{userId\}\}/g,String(v??"")),be=Ic();be?.navigate?be.navigate(Q):window.location.href=Q}},[v]),F=C.useCallback(H=>{H.preventDefault(),g&&H.stopPropagation(),!(T||!v)&&(l?(N||b(),w(W=>!W)):f&&Ic()?.navigate?.(L))},[g,T,v,l,N,b,f,L]);if(T||!v)return d.jsxs(j,{className:U,id:k,...M,children:[d.jsxs(j,{className:"flex items-center gap-1.5 text-gray-400 dark:text-gray-500",children:[d.jsx(te,{children:S}),d.jsx(te,{className:"text-xs px-1.5 py-0.5 bg-gray-100 dark:bg-gray-700 text-gray-600 dark:text-gray-300 rounded",children:co("userinfo.guest_badge")})]}),d.jsx(qa,{text:n,title:o})]});if(I)return d.jsxs(j,{className:U,id:k,...M,children:[d.jsx(te,{className:"text-gray-400 dark:text-gray-500 line-through",children:S}),d.jsx(qa,{text:n,title:o})]});const G=_(),B=/\btext-\S+/.test(c)?"":"text-gray-900 dark:text-white",Y=l||f?`${B} hover:text-blue-600 dark:hover:text-blue-400 cursor-pointer font-medium`.trim():`${B} font-medium`.trim();return d.jsxs(j,{ref:O,className:`relative inline-block ${c}`,id:k,...M,children:[d.jsxs(j,{className:Dc[i],children:[d.jsx(R,{ref:P,onClick:F,className:Y,children:S}),d.jsx(qa,{text:n,title:o})]}),l&&N&&G.length>0&&tr.createPortal(d.jsx(j,{ref:$,className:"fixed w-40 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg shadow-lg py-1 z-[9999]",style:{top:y.top,left:y.left},"data-testid":"author-dropdown-menu",children:G.map(H=>d.jsxs(R,{onClick:W=>z(H,W),className:"flex items-center justify-start gap-2 w-full text-left px-3 py-2 text-sm text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 cursor-pointer","data-testid":`menu-item-${H.key}`,children:[H.icon&&d.jsx(fe,{name:H.icon,className:"w-4 h-4"}),d.jsx(te,{children:H.label})]},H.key))}),document.body)]})},Ap=Object.freeze(Object.defineProperty({__proto__:null,UserInfo:Pc},Symbol.toStringTag,{value:"Module"})),Lc=({tabs:e,activeTabId:t,onTabChange:r,variant:n="underline",className:o="",style:a,hiddenTabIds:s=[],mobileBreakpoint:l=768,id:f,editorAttrs:h})=>{const i=window.G7Core?.useResponsive,u=i?.(),g=u?u.width{!v.disabled&&v.id!==t&&r?.(v.id)},m=v=>{const T=v.target.value,I=Number(T),S=typeof T=="string"&&T!==""&&!Number.isNaN(I)?I:T,N=e.find(w=>String(w.id)===String(S));N&&p(N)},x=s.length>0?e.filter(v=>!s.includes(v.id)):e;if(g)return d.jsx(j,{className:`relative ${o}`,style:a,id:f,...h,children:d.jsx(ps,{value:t!==void 0?String(t):"",onChange:m,options:x.map(v=>({value:String(v.id),label:v.badge!==void 0?`${v.label} (${v.badge})`:v.label,disabled:v.disabled})),className:"w-full"})});const k=v=>{const T=v.id===t,I="flex items-center gap-2 px-3 py-2 font-medium text-sm transition-all shrink-0 whitespace-nowrap";if(v.disabled)return`${I} opacity-50 cursor-not-allowed text-gray-400 dark:text-gray-600`;switch(n){case"pills":return T?`${I} bg-blue-600 dark:bg-blue-500 text-white rounded-lg`:`${I} text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg`;case"underline":return T?`${I} text-gray-900 dark:text-white border-b-2 border-gray-900 dark:border-white`:`${I} text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-200 border-b-2 border-transparent hover:border-gray-300 dark:hover:border-gray-600`;default:return T?`${I} text-blue-600 dark:text-blue-400 bg-blue-50 dark:bg-blue-900/20 border-b-2 border-blue-600 dark:border-blue-400`:`${I} text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-200 hover:bg-gray-50 dark:hover:bg-gray-700 border-b-2 border-transparent`}},M=n==="underline"?"flex gap-0 border-b border-gray-200 dark:border-gray-700":"flex gap-2";return d.jsx(j,{className:`relative ${o}`,style:a,id:f,...h,children:d.jsx(Zr,{className:M,children:x.map(v=>d.jsxs(R,{type:"button",onClick:()=>p(v),disabled:v.disabled,className:k(v),children:[v.iconName&&d.jsx(fe,{name:v.iconName,size:"sm"}),d.jsx(te,{children:v.label}),v.badge!==void 0&&d.jsx(j,{className:"flex items-center justify-center min-w-5 h-5 px-1.5 bg-red-500 text-white text-xs font-bold rounded-full",children:d.jsx(te,{children:v.badge})})]},v.id))})})},Np=Object.freeze(Object.defineProperty({__proto__:null,TabNavigation:Lc},Symbol.toStringTag,{value:"Module"})),Op={Header:()=>Promise.resolve().then(()=>Vd),Footer:()=>Promise.resolve().then(()=>qd),MobileNav:()=>Promise.resolve().then(()=>Zd),SlotContainer:()=>Promise.resolve().then(()=>Bd),NotificationCenter:()=>Promise.resolve().then(()=>Wd),ProductCard:()=>Promise.resolve().then(()=>Pu),BrandMark:()=>Promise.resolve().then(()=>$u),ImageGallery:()=>Promise.resolve().then(()=>ni),ProductImageViewer:()=>Promise.resolve().then(()=>Bg),QuantitySelector:()=>Promise.resolve().then(()=>Wg),PostReactions:()=>Promise.resolve().then(()=>Yg),RichTextEditor:()=>Promise.resolve().then(()=>Xg),HtmlContent:()=>Promise.resolve().then(()=>qg),HtmlEditor:()=>Promise.resolve().then(()=>Zg),ExpandableContent:()=>Promise.resolve().then(()=>Qg),FileUploader:()=>Promise.resolve().then(()=>Q0),ConfirmDialog:()=>Promise.resolve().then(()=>eh),SocialLoginButtons:()=>Promise.resolve().then(()=>R0),Toast:()=>Promise.resolve().then(()=>op),PageTransitionIndicator:()=>Promise.resolve().then(()=>sp),PageTransitionBlur:()=>Promise.resolve().then(()=>lp),PageSkeleton:()=>Promise.resolve().then(()=>wp),PageLoading:()=>Promise.resolve().then(()=>kp),ThemeToggle:()=>Promise.resolve().then(()=>Ud),Pagination:()=>Promise.resolve().then(()=>Cp),SearchBar:()=>Promise.resolve().then(()=>_p),Avatar:()=>Promise.resolve().then(()=>Gd),AvatarUploader:()=>Promise.resolve().then(()=>Ep),UserInfo:()=>Promise.resolve().then(()=>Ap),Modal:()=>Promise.resolve().then(()=>Rg),TabNavigation:()=>Promise.resolve().then(()=>Np)},Mp=({id:e,className:t,style:r,children:n,editorAttrs:o})=>d.jsx(j,{id:e,className:t,style:r,...o,children:n}),Ip=({id:e,children:t,cols:r=1,responsive:n,gap:o,rowGap:a,colGap:s,autoRows:l,autoCols:f,flow:h="row",className:c="",style:i,onClick:u,editorAttrs:g})=>{const p=["grid"];if(r&&p.push(`grid-cols-${r}`),n&&(n.sm&&p.push(`sm:grid-cols-${n.sm}`),n.md&&p.push(`md:grid-cols-${n.md}`),n.lg&&p.push(`lg:grid-cols-${n.lg}`),n.xl&&p.push(`xl:grid-cols-${n.xl}`),n["2xl"]&&p.push(`2xl:grid-cols-${n["2xl"]}`)),o!==void 0&&o>0&&p.push(`gap-${o}`),a!==void 0&&a>0&&p.push(`gap-y-${a}`),s!==void 0&&s>0&&p.push(`gap-x-${s}`),l){const x={auto:"auto-rows-auto",min:"auto-rows-min",max:"auto-rows-max",fr:"auto-rows-fr"};p.push(x[l])}if(f){const x={auto:"auto-cols-auto",min:"auto-cols-min",max:"auto-cols-max",fr:"auto-cols-fr"};p.push(x[f])}const m={row:"grid-flow-row",col:"grid-flow-col",dense:"grid-flow-dense","row-dense":"grid-flow-row-dense","col-dense":"grid-flow-col-dense"};return p.push(m[h]),c&&p.push(c),d.jsx(j,{id:e,className:p.join(" "),style:i,onClick:u,...g,children:t})},Dp=({id:e,children:t,direction:r="row",justify:n="start",align:o="stretch",wrap:a="nowrap",gap:s=0,grow:l=!1,shrink:f=!0,className:h="",style:c,onClick:i,editorAttrs:u})=>{const g=["flex"],p={row:"flex-row","row-reverse":"flex-row-reverse",col:"flex-col","col-reverse":"flex-col-reverse"};g.push(p[r]);const m={wrap:"flex-wrap",nowrap:"flex-nowrap","wrap-reverse":"flex-wrap-reverse"};g.push(m[a]);const x={start:"justify-start",end:"justify-end",center:"justify-center",between:"justify-between",around:"justify-around",evenly:"justify-evenly"};g.push(x[n]);const k={start:"items-start",end:"items-end",center:"items-center",baseline:"items-baseline",stretch:"items-stretch"};return g.push(k[o]),s>0&&g.push(`gap-${s}`),l===!0?g.push("flex-grow"):typeof l=="number"&&l>0&&g.push(`flex-grow-${l}`),f===!1?g.push("shrink-0"):f===!0?g.push("flex-shrink"):typeof f=="number"&&f>0&&g.push(`flex-shrink-${f}`),h&&g.push(h),d.jsx(j,{id:e,className:g.join(" "),style:c,onClick:i,...u,children:t})},Pp=({id:e,title:t,subtitle:r,children:n,padding:o="md",background:a="none",maxWidth:s,centered:l=!1,border:f=!1,shadow:h="none",rounded:c=!1,className:i="",style:u,onClick:g,editorAttrs:p})=>{const m=[],x={none:"p-0",sm:"p-2",md:"p-4",lg:"p-6",xl:"p-8"};m.push(x[o]);const k={none:"",white:"bg-white dark:bg-gray-700",gray:"bg-gray-50 dark:bg-gray-800",primary:"bg-blue-50 dark:bg-blue-900/20",secondary:"bg-gray-50 dark:bg-gray-800"};if(a!=="none"&&m.push(k[a]),s){const v={sm:"max-w-sm",md:"max-w-md",lg:"max-w-lg",xl:"max-w-xl","2xl":"max-w-2xl","4xl":"max-w-4xl","6xl":"max-w-6xl","7xl":"max-w-7xl",full:"max-w-full"};m.push(v[s])}l&&m.push("mx-auto"),f&&m.push("border","border-gray-200","dark:border-gray-700");const M={none:"",sm:"shadow-sm",md:"shadow-md",lg:"shadow-lg",xl:"shadow-xl"};return h!=="none"&&m.push(M[h]),c&&m.push("rounded-lg"),i&&m.push(i),d.jsxs(bs,{id:e,className:m.join(" "),style:u,onClick:g,...p,children:[(t||r)&&d.jsxs(j,{className:"mb-4",children:[t&&d.jsx(gs,{className:"text-2xl font-bold text-gray-900 dark:text-white",children:t}),r&&d.jsx(Gt,{className:"mt-1 text-sm text-gray-600 dark:text-gray-400",children:r})]}),n]})},Lp=({id:e,leftWidth:t="250px",rightWidth:r="300px",leftSlot:n,centerSlot:o,rightSlot:a,className:s="",style:l,editorAttrs:f})=>{const h=`flex flex-row w-full h-full ${s}`.trim(),c={width:t,flexShrink:0},i={flex:1,minWidth:0},u={width:r,flexShrink:0};return d.jsxs(j,{id:e,className:h,style:l,...f,children:[d.jsx(j,{className:"flex flex-col",style:c,children:n}),d.jsx(j,{className:"flex flex-col",style:i,children:o}),d.jsx(j,{className:"flex flex-col",style:u,children:a})]})},$p={identifier:"sirsoft-basic",vendor:"sirsoft",name:{ko:"Basic",en:"Basic"},version:"1.1.4",license:"MIT",description:{ko:"그누보드7 기본 사용자 템플릿",en:"Gnuboard7 default user template"},type:"user",locales:["ko","en"],author:{name:"sirsoft",email:"contact@sirsoft.com",url:"https://sirsoft.com"},release_date:"2026-01-07",g7_version:">=7.0.11",dependencies:{modules:{"sirsoft-board":">=1.0.0","sirsoft-ecommerce":">=1.1.0","sirsoft-page":">=1.1.0"},plugins:{"sirsoft-daum_postcode":">=1.0.0"}},assets:{css:["dist/css/components.css"],js:["dist/js/components.iife.js"],fonts:[],images:[]},components:{basic:["a","button","checkbox","div","form","h1","h2","h3","h4","hr","icon","img","input","label","li","nav","p","section","select","span","svg","table","tbody","td","textarea","th","thead","tr","ul"],composite:["header","footer","mobile-nav","product-card","rich-text-editor","image-gallery","quantity-selector","post-reactions","social-login-buttons","file-uploader","card","modal","toast","pagination","stat-card","status-badge","tab-navigation","empty-state","loading-spinner","breadcrumb","search-bar","dropdown"],layout:["container","flex","grid","section"]},preview:{thumbnail:"preview/thumbnail.png",screenshots:["preview/screenshot1.png","preview/screenshot2.png"]},error_config:{layouts:{401:"errors/401",403:"errors/403",404:"errors/404",500:"errors/500",503:"errors/503",maintenance:"errors/maintenance"}},features:{dark_mode:!0,responsive:!0,multi_language:!0,multi_currency:!0},github_url:"https://github.com/gnuboard/g7-template-sirsoft-basic",github_changelog_url:"https://github.com/gnuboard/g7-template-sirsoft-basic/blob/main/CHANGELOG.md",externals:[{id:"fontawesome",type:"style",asset:"vendor/font-awesome/6.4.0/css/all.inlined.css"},{id:"pretendard",type:"webfont",asset:"vendor/pretendard/1.3.9/pretendard-variable.css"}]},Fp={KRW:0,JPY:0};function Up(){try{const t=(window.G7Core?.state?.get?.()||{})?.modules?.["sirsoft-ecommerce"]?.language_currency;return Array.isArray(t?.currencies)?t.currencies:[]}catch{return[]}}function Kr(e,t){if(!Number.isFinite(e))return"0";const r=Up(),n=t||r.find(a=>a.is_default)?.code;if(!n)return e.toLocaleString();const o=r.find(a=>a.code===n)?.decimal_places??Fp[n]??2;try{return new Intl.NumberFormat(void 0,{style:"currency",currency:n,minimumFractionDigits:o,maximumFractionDigits:o}).format(e)}catch{return`${e.toLocaleString(void 0,{minimumFractionDigits:o,maximumFractionDigits:o})} ${n}`}}function uo(e,t,r,n=0){if(!e)return;const o=f=>f?.value??f?.price??0;let a;for(const[f,h]of Object.entries(e))if(h?.is_default){a=f;break}a=a??(e.KRW?"KRW":void 0);const s=a?o(e[a]):0,l={};for(const[f,h]of Object.entries(e)){const c=o(h);let i;r?i=r[f]?.value??0:n>0&&f===a?i=n:n>0&&s>0?i=n*c/s:i=0;const u=(c+i)*t;l[f]={value:u,formatted:Kr(u,f)}}return l}function fo(e){const t={};for(const n of e){const o=n?.multiCurrencyTotalPrice;if(o)for(const[a,s]of Object.entries(o)){const l=s?.value??s?.price??0;t[a]=(t[a]??0)+l}}const r={};for(const[n,o]of Object.entries(t))r[n]={value:o,formatted:Kr(o,n)};return r}function $c(e,t){if(!e||!t?.length)return 0;let r=0;for(const n of t){const o=e[n.id];if(o==null)continue;const a=n.values?.find(s=>s.id===Number(o));a&&(r+=a.price_adjustment??0)}return r}function Fc(e,t){if(!e||!t?.length)return;const r={};let n=!1;for(const a of t){const s=e[a.id];if(s==null)continue;const f=a.values?.find(h=>h.id===Number(s))?.multi_currency_price_adjustment;if(f){n=!0;for(const[h,c]of Object.entries(f)){const i=c?.value??c?.price??0;r[h]=(r[h]??0)+i}}}if(!n)return;const o={};for(const[a,s]of Object.entries(r))o[a]={value:s};return o}function go(e){return e.name_localized?e.name_localized:typeof e.name=="string"?e.name:e.name?.ko??""}function zp(e,t,r){if(t&&r in t)return t[r];if(Array.isArray(e)){const n=e.find(o=>typeof o.key=="string"?o.key===r:o.key?.ko===r||Object.values(o.key).includes(r));return n?typeof n.value=="string"?n.value:n.value?.ko??Object.values(n.value)[0]:void 0}return e[r]}function Hp(e,t){if(t)return t;if(!Array.isArray(e))return e;const r={};for(const n of e){const o=typeof n.key=="string"?n.key:n.key?.ko??Object.values(n.key)[0]??"",a=typeof n.value=="string"?n.value:n.value?.ko??Object.values(n.value)[0]??"";o&&(r[o]=a)}return r}function Gp(e,t,r){return r.find(n=>!n?.option_values||!n?.is_active?!1:e.every(o=>{const a=go(o),s=t?.[a];return zp(n.option_values,n.option_values_localized,a)===s}))}function jp(e,t){const r=e.params;if(!r)return;const{optionGroups:n,options:o,selectedOptionItems:a,preferredCurrency:s}=r;let l;if(r.newGroupName&&r.newValue?l={...r.currentSelection??{},[r.newGroupName]:r.newValue}:l=r.currentSelection??{},!n?.every(S=>{const N=go(S);return l?.[N]}))return;const h=Gp(n,l,o??[]);if(!h){t.setState?.({currentSelection:{},__mergeMode:"shallow"});return}const c=n.map(S=>l[go(S)]).join("_"),i=a??[];if(i.find(S=>S.id===c)){const S=window.G7Core;S?.toast?.warning?.(S?.t?.("sirsoft-ecommerce.shop.already_added_option")??"이미 추가된 옵션입니다."),t.setState?.({currentSelection:{},__mergeMode:"shallow"});return}const g=h.selling_price??0,p={};n.forEach(S=>{const N=go(S);p[N]=l[N]});const m=r.additionalOptionGroups??[],x={};for(const S of m){if(!S.is_required)continue;const N=S.values?.find(w=>w.is_default);N&&(x[S.id]=N.id)}const k=$c(x,m),M=Fc(x,m),v=g+k,T={id:c,optionId:h.id,options:p,optionValues:Hp(h.option_values,h.option_values_localized),quantity:1,stock:h.stock_quantity??999,unitPrice:g,unitPriceFormatted:Kr(g,s),totalPrice:v,totalPriceFormatted:Kr(v,s),multiCurrencyUnitPrice:h.multi_currency_selling_price,multiCurrencyTotalPrice:uo(h.multi_currency_selling_price,1,M,k),additionalOptionSelections:x,additionalOptionsTotal:k,additionalOptionsMultiCurrencyTotal:M},I=[...i,T];t.setState?.({selectedOptionItems:I,selectedTotalMultiCurrency:fo(I),currentSelection:{},__mergeMode:"shallow"})}function Bp(e,t){const r=e.params;if(!r)return;const{selectedOptionItems:n,preferredCurrency:o}=r,a=Number(r.itemIndex)||0,s=Number(r.newQuantity)||1,l=(n??[]).map((f,h)=>{if(h!==a)return f;const c=f?.unitPrice??0,i=f?.additionalOptionsTotal??0,u=(c+i)*s;return{...f,quantity:s,totalPrice:u,totalPriceFormatted:Kr(u,o),multiCurrencyTotalPrice:uo(f?.multiCurrencyUnitPrice,s,f?.additionalOptionsMultiCurrencyTotal,i)}});t.setState?.({selectedOptionItems:l,selectedTotalMultiCurrency:fo(l)})}function Wp(e,t){const r=e.params;if(!r)return;const{selectedOptionItems:n,additionalOptionGroups:o,preferredCurrency:a}=r,s=Number(r.itemIndex)||0,l=Number(r.additionalOptionId),f=Object.prototype.hasOwnProperty.call(r,"customText"),h=r.valueId===""||r.valueId==null?null:Number(r.valueId),c=(n??[]).map((i,u)=>{if(u!==s)return i;const g={...i.additionalOptionSelections??{}},p={...i.additionalOptionCustomTexts??{}};if(f){const T=String(r.customText??"");T.trim()===""?delete p[l]:p[l]=T}else h==null?(delete g[l],delete p[l]):(g[l]=h,(o??[]).find(S=>S.id===l)?.values?.find(S=>S.id===h)?.allow_custom_text||delete p[l]);const m=$c(g,o),x=Fc(g,o),k=i?.unitPrice??0,M=i?.quantity??1,v=(k+m)*M;return{...i,additionalOptionSelections:g,additionalOptionCustomTexts:p,additionalOptionsTotal:m,additionalOptionsMultiCurrencyTotal:x,totalPrice:v,totalPriceFormatted:Kr(v,a),multiCurrencyTotalPrice:uo(i?.multiCurrencyUnitPrice,M,x,m)}});t.setState?.({selectedOptionItems:c,selectedTotalMultiCurrency:fo(c)})}function Vp(e,t){const r=e.params;if(!r)return;const n=Number(r.itemIndex)||0,o=(r.selectedOptionItems??[]).filter((a,s)=>s!==n);t.setState?.({selectedOptionItems:o,selectedTotalMultiCurrency:fo(o)})}function Kp(e,t){const r=e.params;if(!r)return;const n=Math.max(1,Number(r.newQuantity)||1);t.setState?.({noOptionQuantity:n,noOptionTotalMultiCurrency:uo(r.multiCurrencyUnitPrice,n)})}const xn=window.G7Core?.createLogger?.("Handler:Cart")??{log:(...e)=>console.log("[Handler:Cart]",...e),warn:(...e)=>console.warn("[Handler:Cart]",...e),error:(...e)=>console.error("[Handler:Cart]",...e)};function kr(e){const t=window.G7Core;t?.state?.setLocal?(t.state.setLocal(e),xn.log("Local state updated:",e)):xn.warn("G7Core.state.setLocal not available")}function Uc(){return window.G7Core?.state?.getLocal?.()||{}}function Yp(){return window.G7Core?.state?.get?.()||{}}function Xp(e,t="info"){const r=window.G7Core;r?.toast?.show?r.toast.show(e,t):xn.warn("G7Core.toast.show not available, message:",e)}function qp(e){const t=window.G7Core;return t?.t?t.t(e):e}function zc(){const e=window.G7Core;if(e?.dispatch){const r=Uc().selectedItems||[];e.dispatch({handler:"refetchDataSource",params:{dataSourceId:"cartItems"}}),xn.log("Refetching cartItems with selected_ids:",r)}else xn.warn("G7Core.dispatch not available")}function Zp(e,t){const{itemId:r,selectedItems:n}=e?.params||{},o=n||[];let a;o.includes(r)?a=o.filter(h=>h!==r):a=[...o,r];const l=Yp().cartItems?.data?.items||[],f=l.length>0&&a.length===l.length;kr({selectedItems:a,allSelected:f}),zc()}function Qp(e,t){const{allItemIds:r,isSelected:n}=e?.params||{};kr({selectedItems:n?r:[],allSelected:n}),zc()}function Jp(e,t){const{optionName:r,optionValue:n}=e?.params||{},a=Uc().selectedOptions||{};kr({selectedOptions:{...a,[r]:n}})}function Rp(e,t){const{targetIds:r}=e?.params||{};if(!r||r.length===0){Xp(qp("shop.cart.select_items_to_delete"),"warning");return}kr({deleteModal:{visible:!0,targetIds:r}})}function em(e,t){const{targetItem:r}=e?.params||{};kr({optionModal:{visible:!0,targetItem:r},selectedOptions:r?.options||{},selectedQuantity:r?.quantity})}function tm(e,t){const{selectedItems:r}=e?.params||{};if(!r||r.length===0){kr({isCalculating:!1});return}kr({isCalculating:!0})}const Cr=window.G7Core?.createLogger?.("Handler:CartOptionChange")??{log:(...e)=>console.log("[Handler:CartOptionChange]",...e),warn:(...e)=>console.warn("[Handler:CartOptionChange]",...e),error:(...e)=>console.error("[Handler:CartOptionChange]",...e)};function Za(e){return e.name_localized?e.name_localized:typeof e.name=="string"?e.name:e.name?.ko??""}function rm(e,t,r){if(t&&r in t)return t[r];if(Array.isArray(e)){const n=e.find(o=>typeof o.key=="string"?o.key===r:o.key?.ko===r||Object.values(o.key).includes(r));return n?typeof n.value=="string"?n.value:n.value?.ko??Object.values(n.value)[0]:void 0}return e[r]}function Hc(e,t,r){return!e||!t?.length?null:t.every(a=>{const s=Za(a);return e[s]})?r?.find(a=>t.every(s=>{const l=Za(s);return rm(a.option_values,a.option_values_localized,l)===e[l]}))||null:(Cr.log("Not all options selected yet"),null)}function nm(e,t){const{selectedOptions:r,optionGroups:n,options:o}=e?.params||{};Cr.log("findMatchingOption called:",{selectedOptions:r,optionGroups:n,optionsCount:o?.length});const a=Hc(r,n,o);return Cr.log("Matched option:",a),{matchedOption:a}}function om(e,t){const{currentOptionValues:r,optionGroups:n,options:o}=e?.params||{};if(Cr.log("initCartOptionSelection called:",{currentOptionValues:r,optionGroups:n,optionsCount:o?.length}),!n?.length||!r)return Cr.log("No option groups or current values, returning empty"),{selectedOptions:{},matchedOption:null};const a={};for(const l of n){const f=Za(l),h=r[f],c=l.values_localized??(Array.isArray(l.values)?l.values.map(i=>typeof i=="string"?i:i?.ko??""):[]);h&&c.includes(h)&&(a[f]=h)}Cr.log("Initial selectedOptions:",a);const s=Hc(a,n,o||[]);return Cr.log("Matched option:",s),{selectedOptions:a,matchedOption:s}}const kn="g7_cart_key",am="/api/modules/sirsoft-ecommerce/cart/key",Qa="g7_guest_order_token",Gc="g7_guest_order_number",Ja="g7_guest_order_expires_at",ft=window.G7Core?.createLogger?.("Handler:Storage")??{log:(...e)=>console.log("[Handler:Storage]",...e),warn:(...e)=>console.warn("[Handler:Storage]",...e),error:(...e)=>console.error("[Handler:Storage]",...e)};async function jc(){try{const e=await fetch(am,{method:"POST",headers:{"Content-Type":"application/json",Accept:"application/json"}});return e.ok?(await e.json()).data?.cart_key||null:(ft.error("Failed to issue cartKey from API:",e.status),null)}catch(e){return ft.error("Error issuing cartKey from API:",e),null}}function sr(e){const t=window.G7Core;t?.state?.set?(t.state.set(e),ft.log("Global state updated:",e)):ft.warn("G7Core.state.set not available")}function sm(){try{const e=window.G7Config;return e?.user?e.user:(window.G7Core?.state?.get?.()||{}).currentUser||null}catch{return null}}async function lm(e,t){let r=null;try{r=localStorage.getItem(kn)}catch{}if(r)ft.log("Existing cartKey loaded:",r);else if(r=await jc(),r)try{localStorage.setItem(kn,r),ft.log("New cartKey issued from API:",r)}catch{}else ft.error("Failed to issue cartKey from API");sr({cartKey:r})}function im(e,t){try{return localStorage.getItem(kn)}catch{return null}}function Bc(e,t){try{localStorage.removeItem(kn),ft.log("cartKey removed from localStorage")}catch{}sr({cartKey:null})}async function cm(e,t){if(sm()){Bc();return}const n=await jc();if(n){try{localStorage.setItem(kn,n),ft.log("New cartKey regenerated from API:",n)}catch{}sr({cartKey:n})}else ft.error("Failed to regenerate cartKey from API")}function dm(e,t){const{key:r,value:n}=e?.params||{};if(!r){ft.warn("saveToStorage: key is required");return}try{localStorage.setItem(r,n),ft.log("Saved to storage:",r,n)}catch{}}function um(e,t){const{key:r,defaultValue:n}=e?.params||{};if(!r)return ft.warn("loadFromStorage: key is required"),n||null;try{return localStorage.getItem(r)||n||null}catch{return n||null}}function Ra(){try{sessionStorage.removeItem(Qa),sessionStorage.removeItem(Gc),sessionStorage.removeItem(Ja)}catch{}}function fm(e,t){let r=null,n=null;try{r=sessionStorage.getItem(Qa),n=sessionStorage.getItem(Ja)}catch{}let o=!1;if(r){const a=n?new Date(n).getTime():NaN;(isNaN(a)||a<=Date.now())&&(Ra(),r=null,o=!0)}if(r){sr({guestOrderToken:r}),ft.log("Guest order token loaded from sessionStorage");return}if(!o){const s=window.G7Core?.state?.get?.("_global")?.guestOrderToken;if(typeof s=="string"&&s!==""){ft.log("Guest order token preserved from in-memory _global (sessionStorage empty)");return}}sr({guestOrderToken:null}),ft.log("No guest order token (visitor not authenticated)")}function gm(e,t){const{token:r,orderNumber:n,expiresAt:o}=e?.params||{};if(!r||!n||!o){ft.warn("saveGuestOrderToken: token, orderNumber, expiresAt are required");return}try{sessionStorage.setItem(Qa,r),sessionStorage.setItem(Gc,n),sessionStorage.setItem(Ja,o)}catch{}sr({guestOrderToken:r})}function hm(e,t){Ra(),sr({guestOrderToken:null})}function pm(e,t){Ra(),sr({guestOrderToken:null})}function Wc(e){const t=window.G7Core?.state?.get?.()?.[e];return typeof t=="string"&&t.length>0?t:void 0}function mm(e,t){const r=e?.params??{},{product:n,priceField:o,currencyCode:a}=r,s=a||Wc("preferredCurrency")||Wc("defaultCurrency")||"",l=`multi_currency_${o}`,f=n[l];if(f&&f[s])return f[s].formatted;const h=`${o}_formatted`;return n[h]||String(n[o])}const Vc={KRW:"₩",USD:"$",JPY:"¥",CNY:"元",EUR:"€",GBP:"£"},bm={KRW:0,JPY:0};function Kc(){try{const t=(window.G7Core?.state?.get?.()||{})?.modules?.["sirsoft-ecommerce"]?.language_currency;return Array.isArray(t?.currencies)?t.currencies:[]}catch{return[]}}function Yc(e){const t=window.G7Core?.state?.get?.()?.[e];return typeof t=="string"&&t.length>0?t:void 0}function ym(e,t){const r=e?.params??{},{value:n,currencyCode:o,locale:a}=r,s=Kc(),l=o||Yc("preferredCurrency")||Yc("defaultCurrency")||s.find(u=>u.is_default)?.code;if(!l)return n.toLocaleString(a);const f=s.find(u=>u.code===l),h=f?.symbol&&f.symbol.length>0?f.symbol:Vc[l]??"",c=f?.decimal_places??bm[l]??2,i=n.toLocaleString(a,{minimumFractionDigits:c,maximumFractionDigits:c});return h?l==="KRW"||h==="₩"||h==="원"?`${i}원`:`${h}${i}`:`${i} ${l}`}function vm(e){const t=Kc().find(r=>r.code===e);return t?.symbol&&t.symbol.length>0?t.symbol:Vc[e]||e}const es=window.G7Core?.createLogger?.("Handler:Currency")??{log:(...e)=>console.log("[Handler:Currency]",...e),warn:(...e)=>console.warn("[Handler:Currency]",...e),error:(...e)=>console.error("[Handler:Currency]",...e)},Xc="g7_preferred_currency";function wm(){try{const e=window.G7Core?.state?.get?.()||{};return e.defaultCurrency||e?.modules?.["sirsoft-ecommerce"]?.language_currency?.default_currency||""}catch{return""}}function qc(e){window.G7Core?.state?.set?.({preferredCurrency:e})}function xm(e,t){const r=e?.params??{},{defaultCurrency:n=wm()}=r;let o=n;try{if(typeof window<"u"&&window.localStorage){const a=localStorage.getItem(Xc);a&&Zc(a)&&(o=a)}}catch{es.warn("localStorage 접근 불가, 기본 통화 사용:",n)}return qc(o),o}function km(e,t){const{currencyCode:r}=e?.params??{};if(!Zc(r)){es.warn("유효하지 않은 통화 코드:",r);return}try{typeof window<"u"&&window.localStorage&&localStorage.setItem(Xc,r)}catch{es.warn("localStorage 저장 실패")}qc(r)}function Zc(e){return typeof e=="string"&&/^[A-Z]{3}$/.test(e)}const Yr=window.G7Core?.createLogger?.("Handler:SetTheme")??{log:(...e)=>console.log("[Handler:SetTheme]",...e),warn:(...e)=>console.warn("[Handler:SetTheme]",...e),error:(...e)=>console.error("[Handler:SetTheme]",...e)},ts=["auto","light","dark"],Qc="g7_color_scheme",Cm=e=>e==="auto"?window.matchMedia("(prefers-color-scheme: dark)").matches?"dark":"light":e,ho=e=>{const t=Cm(e);document.documentElement.setAttribute("data-theme",t),t==="dark"?document.documentElement.classList.add("dark"):document.documentElement.classList.remove("dark")};function _m(){try{const e=localStorage.getItem(Qc),t=e&&ts.includes(e)?e:"auto";ho(t),Yr.log("Initial theme applied:",t)}catch(e){Yr.warn("Failed to load initial theme:",e),ho("auto")}}async function Sm(e,t){const r=e?.target;if(r&&typeof r=="string"&&ts.includes(r)){ho(r);return}_m()}async function Em(e,t){let r;if(e?.target&&typeof e.target=="string"&&!e.target.includes("{{")&&(r=e.target),!r||typeof r!="string"){Yr.warn("Invalid theme:",r);return}if(!ts.includes(r)){Yr.warn("Unsupported theme:",r);return}const n=r;try{localStorage.setItem(Qc,n)}catch(o){Yr.error("Failed to save theme to localStorage:",o);return}ho(n),Yr.log("Theme changed to:",n)}const Tm=window.G7Core?.createLogger?.("Handler:RedirectToLoginWithReturn")??{log:(...e)=>console.log("[Handler:RedirectToLoginWithReturn]",...e),warn:(...e)=>console.warn("[Handler:RedirectToLoginWithReturn]",...e),error:(...e)=>console.error("[Handler:RedirectToLoginWithReturn]",...e)};async function Am(e,t){const r=window.G7Core;if(!r?.dispatch){Tm.error("G7Core.dispatch 가 초기화되지 않아 로그인 리다이렉트를 실행할 수 없습니다.");return}const n=window.location.pathname+window.location.search;await r.dispatch({handler:"navigate",params:{path:"/login",query:{redirect:n}}})}const rs=window.G7Core?.createLogger?.("Handler:DownloadAttachment")??{log:(...e)=>console.log("[Handler:DownloadAttachment]",...e),warn:(...e)=>console.warn("[Handler:DownloadAttachment]",...e),error:(...e)=>console.error("[Handler:DownloadAttachment]",...e)};async function Nm(e,t){const r=window.G7Core,{url:n,filename:o}=e?.params||{};if(!n){rs.warn("다운로드 URL 이 없어 요청을 건너뜁니다.");return}if(!r?.api?.get){rs.error("G7Core.api.get 이 초기화되지 않아 다운로드를 실행할 수 없습니다.");return}try{const a=await r.api.get(n,{responseType:"blob",headers:zn()});if(a){const s=URL.createObjectURL(a),l=document.createElement("a");l.href=s,l.download=o||"",document.body.appendChild(l),l.click(),document.body.removeChild(l),URL.revokeObjectURL(s)}}catch(a){rs.error("첨부파일 다운로드 실패",a),r?.toast?.error?.(r?.t?.("common.download_failed")??"Download failed")}}const po={"sirsoft-basic.addSelectedItemIfComplete":jp,"sirsoft-basic.updateSelectedItemQuantity":Bp,"sirsoft-basic.removeSelectedItem":Vp,"sirsoft-basic.updateNoOptionQuantity":Kp,"sirsoft-basic.setBlockAdditionalOption":Wp,"sirsoft-basic.getDisplayPrice":mm,"sirsoft-basic.formatCurrency":ym,"sirsoft-basic.getCurrencySymbol":vm,"sirsoft-basic.loadPreferredCurrency":xm,"sirsoft-basic.savePreferredCurrency":km,setTheme:Em,initTheme:Sm,redirectToLoginWithReturn:Am,downloadAttachment:Nm,toggleCartItemSelection:Zp,selectAllCartItems:Qp,setCartOption:Jp,openCartDeleteModal:Rp,openCartOptionModal:em,recalculateCart:tm,findMatchingOption:nm,initCartOptionSelection:om,initCartKey:lm,getCartKey:im,clearCartKey:Bc,regenerateCartKey:cm,saveToStorage:dm,loadFromStorage:um,initGuestOrderToken:fm,saveGuestOrderToken:gm,clearGuestOrderToken:hm,clearGuestTokenOnEntry:pm},Cn=window.G7Core?.createLogger?.("Template:sirsoft-basic:IdentityLauncher")??{log:(...e)=>console.log("[Template:sirsoft-basic:IdentityLauncher]",...e),warn:(...e)=>console.warn("[Template:sirsoft-basic:IdentityLauncher]",...e),error:(...e)=>console.error("[Template:sirsoft-basic:IdentityLauncher]",...e)};function Om(e){const t=window.G7Core,r=t?.state?.getLocal?.()??{},n=t?.state?.getGlobal?.()??t?.state?.get?.()??{},o=(...f)=>f.find(h=>typeof h=="string"&&h.length>0),a=o(e.target?.email,n?.currentUser?.email,n?.user?.email,n?.auth?.user?.email,e.purpose==="signup"?r?.registerForm?.email:void 0,e.purpose==="password_reset"?r?.passwordResetForm?.email:void 0,e.purpose==="password_reset"?r?.email:void 0),s=o(e.target?.phone,n?.currentUser?.phone,n?.user?.phone,n?.auth?.user?.phone);if(!a&&!s)return null;const l={};return a&&(l.email=a),s&&(l.phone=s),l}function Mm(){const t=window.G7Core?.api?.getToken?.()??null;return t?{Authorization:`Bearer ${t}`}:{}}function Im(){try{const e=typeof window<"u"?window.localStorage?.getItem("g7_locale"):null;return e?{"Accept-Language":e}:{}}catch{return{}}}async function Dm(e,t){const r={purpose:e.purpose};e.provider_id&&(r.provider_id=e.provider_id),t&&(r.target=t);const n=e.challenge_start_url||"/api/identity/challenges",o=await fetch(n,{method:"POST",credentials:"include",headers:{"Content-Type":"application/json",Accept:"application/json",...Im(),...Mm()},body:JSON.stringify(r)}),a=await o.json().catch(()=>null);if(!o.ok||!a?.success){const l=a?.message??`Challenge 시작 실패 (HTTP ${o.status})`;throw new Error(l)}const s=a.data??a;return{id:s.id,expires_at:s.expires_at,render_hint:s.render_hint??e.render_hint??"text_code",public_payload:s.public_payload,redirect_url:s.redirect_url,max_attempts:typeof s.max_attempts=="number"?s.max_attempts:void 0}}async function Pm(e){const t=window.G7Core,r=t?.identity;if(!t?.dispatch||!t?.state||!r)return Cn.error("G7Core 또는 G7Core.identity 가 초기화되지 않아 launcher 를 실행할 수 없습니다."),{status:"failed",failureCode:"G7_NOT_READY"};if(e.render_hint==="external_redirect"||e.redirect_url)return r.redirectExternally(e);const n=Om(e);let o;try{o=await Dm(e,n)}catch(p){Cn.error("Challenge 시작 실패:",p);try{await t.dispatch({handler:"toast",params:{type:"error",message:p instanceof Error?p.message:"본인인증 시작에 실패했습니다."}})}catch{}return{status:"failed",failureCode:"CHALLENGE_START_FAILED",reason:p instanceof Error?p.message:String(p)}}if(o.redirect_url)return r.redirectExternally({...e,render_hint:"external_redirect",redirect_url:o.redirect_url});const a=o.expires_at?new Date(o.expires_at).getTime():0,s=a>0?Math.max(0,Math.floor((a-Date.now())/1e3)):0,l=typeof o.max_attempts=="number"?o.max_attempts:0;t.state.set({identityChallenge:{policy_key:e.policy_key,purpose:e.purpose,provider_id:e.provider_id??null,render_hint:o.render_hint,challenge_id:o.id,expires_at:o.expires_at,public_payload:o.public_payload??{},target:n??null,code:"",error:null,attempts:0,maxAttempts:l,remainingSeconds:s,resendCooldown:0}});let f=a,h=0;const c=window.setInterval(()=>{const p=f>0?Math.max(0,Math.floor((f-Date.now())/1e3)):0;h=Math.max(0,h-1),t.state.set({identityChallenge:{remainingSeconds:p,resendCooldown:h}})},1e3);let i=null;try{i=t.state.subscribe?.(p=>{const m=p?.identityChallenge?.expires_at;if(typeof m=="string"){const k=new Date(m).getTime();Number.isFinite(k)&&k!==f&&(f=k)}const x=p?.identityChallenge?.resendCooldown;typeof x=="number"&&x>h&&(h=x)})??null}catch{}const u=r.createDeferred();try{await t.dispatch({handler:"openModal",target:"identity-challenge-modal"})}catch(p){return Cn.error("openModal 실패 — 풀페이지로 폴백:",p),window.clearInterval(c),i?.(),r.redirectExternally({...e,render_hint:"external_redirect",redirect_url:`/identity/challenge?challenge_id=${encodeURIComponent(o.id)}&return=${encodeURIComponent(window.location.href)}`})}const g=await u;return window.clearInterval(c),i?.(),g}function Lm(){const e=window.G7Core?.identity;if(!e?.setLauncher){Cn.warn("G7Core.identity 가 아직 초기화되지 않아 launcher 등록을 건너뜁니다. 코어 부트스트랩 순서를 확인하세요.");return}e.setLauncher(Pm),Cn.log("IDV launcher registered (sirsoft-basic)")}const Jc=window.G7Core?.createLogger?.("Template:sirsoft-basic:device")??{log:(...e)=>console.log("[Template:sirsoft-basic:device]",...e),warn:(...e)=>console.warn("[Template:sirsoft-basic:device]",...e)};function $m(){if(typeof navigator>"u")return!1;const e=navigator,t=(e.userAgent||"").toLowerCase(),r=((e.userAgentData?.platform??e.platform)||"").toLowerCase(),n=e.maxTouchPoints??0;return/iphone|ipad|ipod/.test(t)||/iphone|ipad|ipod|ios/.test(r)?!0:/macintosh|mac os x/.test(t)&&n>1}function Fm(){const e=window.G7Core;if(!e?.state?.get||!e?.state?.set){Jc.warn("G7Core.state not available; skip iOS device correction");return}if(!$m())return;const r=(e.state.get()||{}).appConfig||{};r.isIos!==!0&&(e.state.set({appConfig:{...r,isIos:!0}}),Jc.log("appConfig.isIos corrected to true by client signals (iPadOS desktop-UA)"))}function Um(e){const t=e.icons;return Array.isArray(t)?t.filter(r=>!!r&&typeof r=="object"&&typeof r.value=="string"):[]}function zm(e,t){const r=e.label;return typeof r=="string"?r.startsWith("$t:")?t(r.slice(3)):r:e.value}function Hm(e,t,r){if(r==="")return!0;const n=r.toLowerCase();return e.value.toLowerCase().includes(n)||t.toLowerCase().includes(n)?!0:(e.keywords??[]).some(o=>o.toLowerCase().includes(n))}function Gm({entry:e}){const t=e.preview;return t?.html?d.jsx("span",{className:"g7le-icon-preview",dangerouslySetInnerHTML:{__html:t.html},style:os}):t?.className?d.jsx("span",{className:`g7le-icon-preview ${t.className}`,style:os}):d.jsx("i",{className:`fas fa-${e.value}`,style:os,"aria-hidden":"true"})}function jm({control:e,value:t,onChange:r,t:n}){const o=Um(e),a=t==null?"":String(t),s=typeof e.iconColumns=="number"?e.iconColumns:6,l=e.iconSearchPlaceholder,f=typeof l=="string"&&l.startsWith("$t:")?n(l.slice(3)):n("layout_editor.icon_picker.search_placeholder"),[h,c]=C.useState("");if(o.length===0)return d.jsx("div",{className:"g7le-widget g7le-widget--icon-picker","data-testid":"g7le-widget-icon-picker",style:Rc,children:d.jsx("input",{type:"text",className:"g7le-widget g7le-widget--icon-free","data-testid":"g7le-widget-icon-picker-free",value:a,placeholder:n("layout_editor.icon_picker.free_input_hint"),onChange:g=>r(g.target.value===""?void 0:g.target.value),style:ed})});const i=g=>zm(g,n),u=o.filter(g=>Hm(g,i(g),h));return d.jsxs("div",{className:"g7le-widget g7le-widget--icon-picker","data-testid":"g7le-widget-icon-picker",style:Rc,children:[d.jsx("input",{type:"text",className:"g7le-widget g7le-widget--icon-search","data-testid":"g7le-widget-icon-picker-search",value:h,placeholder:f,onChange:g=>c(g.target.value),style:ed}),d.jsxs("div",{className:"g7le-icon-grid","data-testid":"g7le-widget-icon-picker-grid",style:{...Bm,gridTemplateColumns:`repeat(${s}, minmax(0, 1fr))`},children:[d.jsx("button",{type:"button","data-testid":"g7le-icon-cell-none",title:n("layout_editor.icon_picker.none"),onClick:()=>r(void 0),"aria-pressed":a==="",style:a===""?td:ns,children:d.jsx("span",{style:Wm,children:"—"})}),u.map(g=>{const p=g.value===a;return d.jsx("button",{type:"button","data-testid":`g7le-icon-cell-${g.value}`,title:i(g),"aria-pressed":p,onClick:()=>r(g.value),style:p?td:ns,children:d.jsx(Gm,{entry:g})},g.value)})]}),u.length===0&&d.jsx("div",{"data-testid":"g7le-widget-icon-picker-empty",style:Vm,children:n("layout_editor.icon_picker.no_results")})]})}const Rc={display:"flex",flexDirection:"column",gap:6,width:"100%"},ed={padding:"5px 8px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,width:"100%"},Bm={display:"grid",gap:4,maxHeight:220,overflowY:"auto",padding:2},ns={display:"flex",alignItems:"center",justifyContent:"center",minHeight:34,padding:4,border:"1px solid #e2e8f0",borderRadius:6,background:"#fff",cursor:"pointer"},td={...ns,border:"2px solid #2563eb",background:"#eff6ff"},os={display:"inline-flex",alignItems:"center",justifyContent:"center",fontSize:16,lineHeight:1},Wm={fontSize:10,color:"#475569",fontFamily:"monospace",overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap",maxWidth:"100%"},Vm={fontSize:11,color:"#94a3b8",fontStyle:"italic",padding:"4px 2px"},rd=window.G7Core?.createLogger?.("Template:sirsoft-basic:EditorWidgets")??{log:(...e)=>console.log("[Template:sirsoft-basic:EditorWidgets]",...e),warn:(...e)=>console.warn("[Template:sirsoft-basic:EditorWidgets]",...e)};function Km(){if(typeof window>"u")return;const e=window.G7Core?.layoutEditor;if(!e?.registerWidget){rd.warn("G7Core.layoutEditor 미노출 — icon-picker 위젯 등록 보류");return}e.registerWidget("icon-picker",jm),rd.log("편집기 위젯 'icon-picker' 등록됨(Font Awesome 검색 그리드)")}const as=window.G7Core?.createLogger?.("Template:sirsoft-basic")??{log:(...e)=>console.log("[Template:sirsoft-basic]",...e),warn:(...e)=>console.warn("[Template:sirsoft-basic]",...e),error:(...e)=>console.error("[Template:sirsoft-basic]",...e)};typeof window<"u"&&(window.G7TemplateHandlers=po);function nd(){if(typeof window<"u"){let e=0;const t=50,r=()=>{const n=window.G7Core?.getActionDispatcher?.();n?(Object.entries(po).forEach(([o,a])=>{n.registerHandler(o,a)}),as.log(`${Object.keys(po).length} custom handler(s) registered:`,Object.keys(po)),Lm(),Fm()):(e++,e<=t?(as.warn(`ActionDispatcher not found, retrying... (${e}/${t})`),setTimeout(r,100)):as.error("Failed to register handlers: ActionDispatcher not available after maximum retries"))};document.readyState==="complete"?r():window.addEventListener("load",r)}}return Km(),nd(),ee.A=So,ee.Avatar=Qr,ee.AvatarUploader=Xa,ee.BrandMark=zo,ee.Button=R,ee.Checkbox=ms,ee.Code=Ld,ee.ConfirmDialog=wa,ee.Container=Mp,ee.Div=j,ee.ExpandableContent=ba,ee.FileInput=ds,ee.FileUploader=lo,ee.Flex=Dp,ee.Footer=Cs,ee.Form=Eo,ee.Grid=Ip,ee.H1=Cd,ee.H2=gs,ee.H3=Tn,ee.H4=hs,ee.Header=xs,ee.Hr=To,ee.HtmlContent=ha,ee.HtmlEditor=ii,ee.Icon=fe,ee.IconName=nt,ee.ImageGallery=zr,ee.Img=jt,ee.Input=At,ee.Label=Ar,ee.Li=er,ee.MobileNav=_s,ee.Modal=ya,ee.Nav=Zr,ee.NotificationCenter=On,ee.Ol=_d,ee.Optgroup=Ad,ee.Option=Td,ee.P=Gt,ee.PageLoading=Ac,ee.PageSkeleton=Ya,ee.PageTransitionBlur=Wa,ee.PageTransitionIndicator=Ba,ee.Pagination=Nc,ee.PasswordInput=fs,ee.PostReactions=si,ee.ProductCard=Ws,ee.ProductImageViewer=jn,ee.QuantitySelector=oi,ee.RichTextEditor=li,ee.SearchBar=Mc,ee.Section=bs,ee.SectionLayout=Pp,ee.Select=ps,ee.SlotContainer=Nn,ee.SocialLoginButtons=vc,ee.Span=te,ee.Svg=En,ee.TabNavigation=Lc,ee.Table=Nd,ee.Tbody=Md,ee.Td=Pd,ee.Textarea=Co,ee.Th=Dd,ee.Thead=Od,ee.ThemeToggle=An,ee.ThreeColumnLayout=Lp,ee.Toast=ja,ee.Tr=Id,ee.Ul=_o,ee.UserInfo=Pc,ee.availablePasswordRules=ko,ee.compositeComponents=Op,ee.defaultPasswordRules=us,ee.initTemplate=nd,ee.templateMetadata=$p,Object.defineProperty(ee,Symbol.toStringTag,{value:"Module"}),ee})({},ReactJSXRuntime,React,ReactDOM); diff --git a/templates/_bundled/sirsoft-basic/docs/layouts.md b/templates/_bundled/sirsoft-basic/docs/layouts.md index f30f4c8d..2cc596ec 100644 --- a/templates/_bundled/sirsoft-basic/docs/layouts.md +++ b/templates/_bundled/sirsoft-basic/docs/layouts.md @@ -572,6 +572,30 @@ slots.content: - `navigate` — 성공 후 홈/대시보드로 이동 - `setState` — 폼 에러, 로딩 상태 +**2단계 인증(인증번호) 단계** + +관리자가 보안 설정에서 2단계 인증을 켠 사이트에서는 로그인 응답이 두 형태로 갈립니다 — +정상 로그인과 인증번호 요구(challenge)입니다. `_login_form.json` 은 그 둘을 **같은 카드 안에서** +단계 전환으로 처리합니다. + +- 1단계(이메일·비밀번호) 블록은 `if: "{{!_global.twoFactor?.required}}"`, 2단계(인증번호) 블록은 + 그 부정형을 답니다. 두 `if` 는 언제나 상보여야 합니다 — 한쪽만 고치면 두 단계가 겹쳐 보이거나 + 둘 다 사라집니다. +- 제출 시퀀스의 `login` 과 `loginTwoFactor` 도 같은 쌍으로 상호배타입니다. `loginTwoFactor` 쪽 + `if` 가 빠지면 인증번호 단계에서 Enter 를 눌렀을 때 새 challenge 가 발급되어 흐름이 끊깁니다. +- 인증번호 입력은 controlled 입니다(`value` + `onChange` 의 `setState` 쌍). `events: {}` 래퍼는 + 쓰지 않습니다. +- 화면 진입 시 `auth/login.json` 의 `init_actions` 첫 액션이 `twoFactor` 를 `null` 로 되돌립니다. + 전역 상태라 화면을 떠나도 남기 때문에, 이 리셋이 없으면 다시 들어왔을 때 1단계가 보이지 않습니다. +- 만료 시각은 정적 표기입니다(`| datetime`). 카운트다운을 쓰지 않습니다 — `startInterval` 은 등록 + 시점 컨텍스트를 고정하고 정지 호출처가 없어 화면을 떠난 뒤에도 남습니다. + +**금지 — 같은 시퀀스·onSuccess 안에서 방금 저장한 상태를 다시 읽지 않습니다** + +`setState target:global` 직후 형제 액션의 `if` 나 값으로 `_global.*` 를 재독하면 갱신 이전 값을 +읽습니다(시퀀스 처리기는 `_local` 만 갱신합니다). 그 자리에서는 `{{response.*}}` 만 씁니다. +오류도 경고도 남지 않고 분기만 조용히 어긋나므로 화면을 보지 않으면 드러나지 않습니다. + --- #### 게시판 패턴 diff --git a/templates/_bundled/sirsoft-basic/lang/partial/en/auth.json b/templates/_bundled/sirsoft-basic/lang/partial/en/auth.json index 9b4687d0..354f5bec 100644 --- a/templates/_bundled/sirsoft-basic/lang/partial/en/auth.json +++ b/templates/_bundled/sirsoft-basic/lang/partial/en/auth.json @@ -150,5 +150,18 @@ "minTypes": "At least {{count}} types (uppercase, lowercase, number, special)" } }, + "two_factor": { + "sent": "We sent a verification code. Enter it to finish signing in.", + "resent": "We sent a new verification code. Enter the new code.", + "code_label": "Verification code", + "code_placeholder": "Enter the code you received", + "valid_until": "Valid until {{until}}", + "verify": "Verify code", + "verifying": "Verifying...", + "resend": "Resend code", + "restart": "Start over" + }, + "locked_until": "Unlocks at {{until}}", + "locked_permanent": "Contact an administrator to unlock your account.", "session_expired_toast": "Your session has expired. Please log in again." } \ No newline at end of file diff --git a/templates/_bundled/sirsoft-basic/lang/partial/ko/auth.json b/templates/_bundled/sirsoft-basic/lang/partial/ko/auth.json index e27eb8e2..badf5889 100644 --- a/templates/_bundled/sirsoft-basic/lang/partial/ko/auth.json +++ b/templates/_bundled/sirsoft-basic/lang/partial/ko/auth.json @@ -150,5 +150,18 @@ "minTypes": "{{count}}가지 이상 조합 (대/소문자, 숫자, 특수문자)" } }, + "two_factor": { + "sent": "인증번호를 보냈습니다. 받은 번호를 입력해 로그인을 완료해주세요.", + "resent": "인증번호를 다시 보냈습니다. 새로 받은 번호를 입력해주세요.", + "code_label": "인증번호", + "code_placeholder": "받은 인증번호를 입력하세요", + "valid_until": "유효시간 {{until}} 까지", + "verify": "인증번호 확인", + "verifying": "확인 중...", + "resend": "인증번호 다시 받기", + "restart": "처음부터" + }, + "locked_until": "해제 예정: {{until}}", + "locked_permanent": "관리자에게 문의해 잠금을 해제해주세요.", "session_expired_toast": "세션이 만료되었습니다. 다시 로그인해 주세요." } \ No newline at end of file diff --git a/templates/_bundled/sirsoft-basic/layouts/auth/login.json b/templates/_bundled/sirsoft-basic/layouts/auth/login.json index 10a6400d..026acbca 100644 --- a/templates/_bundled/sirsoft-basic/layouts/auth/login.json +++ b/templates/_bundled/sirsoft-basic/layouts/auth/login.json @@ -7,6 +7,11 @@ "guest_only": true }, "init_actions": [ + { + "_comment": "2단계 인증 단계 상태는 전역이라 화면을 떠나도 남는다 — 진입할 때마다 1단계로 되돌린다.", + "handler": "setState", + "params": { "target": "global", "twoFactor": null } + }, { "if": "{{query?.reason === 'session_expired'}}", "handler": "toast", diff --git a/templates/_bundled/sirsoft-basic/layouts/partials/auth/_login_form.json b/templates/_bundled/sirsoft-basic/layouts/partials/auth/_login_form.json index 95eb1353..6eb15234 100644 --- a/templates/_bundled/sirsoft-basic/layouts/partials/auth/_login_form.json +++ b/templates/_bundled/sirsoft-basic/layouts/partials/auth/_login_form.json @@ -1,7 +1,7 @@ { "meta": { "is_partial": true, - "description": "로그인 폼 (이메일/비밀번호)" + "description": "로그인 폼 (1단계 이메일/비밀번호 → 2단계 인증번호)" }, "type": "basic", "name": "Form", @@ -21,13 +21,29 @@ "name": "P", "props": { "className": "text-sm text-red-600 dark:text-red-400" }, "text": "{{_global.loginError}}" + }, + { + "comment": "계정 잠금 해제 시각 — 언제 다시 시도할 수 있는지 알려주지 않으면 사용자는 계속 눌러 보게 된다", + "type": "basic", + "name": "P", + "if": "{{_global.loginErrors?.locked_until}}", + "props": { "className": "mt-1 text-sm text-red-600 dark:text-red-400" }, + "text": "$t:auth.locked_until|until={{_global.loginErrors?.locked_until | datetime}}" + }, + { + "type": "basic", + "name": "P", + "if": "{{_global.loginErrors?.permanent === true}}", + "props": { "className": "mt-1 text-sm text-red-600 dark:text-red-400" }, + "text": "$t:auth.locked_permanent" } ] }, { - "comment": "이메일 입력 필드", + "comment": "이메일 입력 필드 (1단계)", "type": "basic", "name": "Div", + "if": "{{!_global.twoFactor?.required}}", "children": [ { "type": "basic", @@ -58,9 +74,10 @@ ] }, { - "comment": "비밀번호 입력 필드", + "comment": "비밀번호 입력 필드 (1단계)", "type": "basic", "name": "Div", + "if": "{{!_global.twoFactor?.required}}", "children": [ { "type": "basic", @@ -84,8 +101,10 @@ ] }, { + "comment": "로그인 버튼 (1단계)", "type": "basic", "name": "Button", + "if": "{{!_global.twoFactor?.required}}", "props": { "type": "submit", "className": "w-full py-3 mt-12 bg-gray-900 dark:bg-white text-white dark:text-gray-900 rounded-lg font-medium hover:bg-gray-800 dark:hover:bg-gray-100 transition-colors disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center gap-2", @@ -112,6 +131,223 @@ "text": "$t:auth.login_processing" } ] + }, + { + "comment": "2단계 인증 — 비밀번호 확인만 통과한 상태다. 인증번호를 확인해야 로그인이 완료된다.", + "type": "basic", + "name": "Div", + "if": "{{_global.twoFactor?.required}}", + "props": { "className": "space-y-6" }, + "children": [ + { + "type": "basic", + "name": "Div", + "props": { + "className": "p-4 rounded-lg bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-800", + "role": "status", + "aria-live": "polite" + }, + "children": [ + { + "type": "basic", + "name": "P", + "if": "{{!_global.twoFactor?.resent}}", + "props": { "className": "text-sm text-blue-700 dark:text-blue-300" }, + "text": "$t:auth.two_factor.sent" + }, + { + "type": "basic", + "name": "P", + "if": "{{_global.twoFactor?.resent}}", + "props": { "className": "text-sm text-blue-700 dark:text-blue-300" }, + "text": "$t:auth.two_factor.resent" + } + ] + }, + { + "comment": "인증번호 오류", + "type": "basic", + "name": "Div", + "if": "{{_global.twoFactor?.error}}", + "props": { + "className": "p-4 rounded-lg bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800", + "role": "alert" + }, + "children": [ + { + "type": "basic", + "name": "P", + "props": { "className": "text-sm text-red-600 dark:text-red-400" }, + "text": "{{_global.twoFactor?.error ?? ''}}" + } + ] + }, + { + "type": "basic", + "name": "Div", + "children": [ + { + "type": "basic", + "name": "Label", + "props": { "className": "block text-sm font-medium text-gray-700 dark:text-gray-300 mb-3" }, + "text": "$t:auth.two_factor.code_label" + }, + { + "comment": "자동바인딩을 쓰지 않는다 — 재발송 시 입력값을 비워야 하므로 상태가 값을 소유한다", + "type": "basic", + "name": "Input", + "props": { + "type": "text", + "name": "two_factor_code", + "value": "{{_global.twoFactor?.code ?? ''}}", + "inputMode": "numeric", + "autoComplete": "one-time-code", + "maxLength": 10, + "placeholder": "$t:auth.two_factor.code_placeholder", + "className": "w-full px-4 py-3 text-center text-xl tracking-widest font-mono border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white focus:ring-2 focus:ring-blue-500 focus:border-blue-500 disabled:opacity-50 disabled:cursor-not-allowed transition-colors duration-200", + "disabled": "{{_global.isLoggingIn}}" + }, + "actions": [ + { + "event": "onChange", + "handler": "setState", + "params": { + "target": "global", + "twoFactor.code": "{{$event.target.value}}", + "twoFactor.error": null + } + } + ] + }, + { + "comment": "유효시각 정적 표기 — 남은 시간 카운트다운은 SPA 이탈 후에도 타이머가 남아 쓰지 않는다", + "type": "basic", + "name": "P", + "if": "{{_global.twoFactor?.expires_at}}", + "props": { "className": "mt-2 text-xs text-gray-500 dark:text-gray-400" }, + "text": "$t:auth.two_factor.valid_until|until={{_global.twoFactor?.expires_at | datetime}}" + } + ] + }, + { + "comment": "인증번호 확인 — submit 이라 Enter 키로도 제출된다", + "type": "basic", + "name": "Button", + "props": { + "type": "submit", + "className": "w-full py-3 bg-gray-900 dark:bg-white text-white dark:text-gray-900 rounded-lg font-medium hover:bg-gray-800 dark:hover:bg-gray-100 transition-colors disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center gap-2", + "disabled": "{{!_global.twoFactor?.code || _global.twoFactor.code.length < 4 || _global.isLoggingIn}}" + }, + "children": [ + { + "type": "basic", + "name": "Div", + "if": "{{_global.isLoggingIn}}", + "props": { "className": "w-5 h-5 border-2 border-white/30 border-t-white rounded-full animate-spin" } + }, + { + "type": "basic", + "name": "Span", + "if": "{{!_global.isLoggingIn}}", + "text": "$t:auth.two_factor.verify" + }, + { + "type": "basic", + "name": "Span", + "if": "{{_global.isLoggingIn}}", + "text": "$t:auth.two_factor.verifying" + } + ] + }, + { + "type": "basic", + "name": "Div", + "props": { "className": "flex flex-col sm:flex-row gap-2" }, + "children": [ + { + "type": "basic", + "name": "Button", + "props": { + "type": "button", + "className": "flex-1 py-3 border border-gray-300 dark:border-gray-600 text-gray-700 dark:text-gray-300 rounded-lg font-medium hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors disabled:opacity-50 disabled:cursor-not-allowed cursor-pointer", + "disabled": "{{_global.twoFactor?.resending || _global.isLoggingIn}}" + }, + "text": "$t:auth.two_factor.resend", + "actions": [ + { + "type": "click", + "handler": "sequence", + "actions": [ + { + "handler": "setState", + "params": { "target": "global", "twoFactor.resending": true, "twoFactor.error": null } + }, + { + "handler": "loginTwoFactorResend", + "target": "user", + "params": { + "body": { "challenge_id": "{{_global.twoFactor?.challenge_id}}" } + }, + "onSuccess": [ + { + "_comment": "같은 시퀀스 안에서는 _global 이 아직 갱신 전이므로 응답 값만 읽는다.", + "handler": "setState", + "params": { + "target": "global", + "twoFactor": { + "required": true, + "challenge_id": "{{response.challenge_id}}", + "provider_id": "{{response.provider_id}}", + "expires_at": "{{response.expires_at}}", + "code": "", + "error": null, + "verifying": false, + "resending": false, + "resent": true + } + } + } + ], + "onError": [ + { + "handler": "setState", + "params": { + "target": "global", + "twoFactor.resending": false, + "twoFactor.error": "{{error.message}}" + } + } + ] + } + ] + } + ] + }, + { + "type": "basic", + "name": "Button", + "props": { + "type": "button", + "className": "flex-1 py-3 border border-gray-300 dark:border-gray-600 text-gray-700 dark:text-gray-300 rounded-lg font-medium hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors disabled:opacity-50 disabled:cursor-not-allowed cursor-pointer", + "disabled": "{{_global.isLoggingIn}}" + }, + "text": "$t:auth.two_factor.restart", + "actions": [ + { + "type": "click", + "handler": "setState", + "params": { + "target": "global", + "twoFactor": null, + "loginError": null, + "loginErrors": null + } + } + ] + } + ] + } + ] } ], "actions": [ @@ -136,8 +372,10 @@ } }, { + "_comment": "1·2단계는 상호배타다. if 는 시퀀스 시작 시점 스냅샷으로 평가되므로 한 제출에 정확히 하나만 실행된다 — 이 if 가 빠지면 인증번호 단계에서 Enter 를 누를 때 새 challenge 가 발급되어 흐름이 깨진다.", "handler": "login", "target": "user", + "if": "{{!_global.twoFactor?.required}}", "params": { "body": { "email": "{{form.email}}", @@ -145,17 +383,67 @@ } }, "onSuccess": [ - { "handler": "setState", "params": { "target": "global", "currentUser": "{{response.user}}" } }, - { "handler": "setState", "params": { "target": "global", "isLoggingIn": false } }, + { + "_comment": "2단계 인증이 켜진 사이트는 아직 로그인이 끝나지 않았다 — 토큰도 사용자도 없다.", + "handler": "setState", + "if": "{{response.two_factor_required}}", + "params": { + "target": "global", + "twoFactor": { + "required": true, + "challenge_id": "{{response.challenge_id}}", + "provider_id": "{{response.provider_id}}", + "expires_at": "{{response.expires_at}}", + "code": "", + "error": null, + "verifying": false, + "resending": false, + "resent": false + }, + "isLoggingIn": false + } + }, + { "handler": "setState", "if": "{{!response.two_factor_required}}", "params": { "target": "global", "currentUser": "{{response.user}}" } }, + { "handler": "setState", "if": "{{!response.two_factor_required}}", "params": { "target": "global", "isLoggingIn": false } }, { "_comment": "로그인 직전에 비회원으로 발급받은 주문 조회 토큰은 회원 컨텍스트에 무효 — 잔존 시 동일 브라우저로 회원/비회원 페이지 동시 열람 가능. sessionStorage 3개 키 (token/orderNumber/expiresAt) 와 _global.guestOrderToken 을 한 번에 정리하는 모듈 커스텀 핸들러 사용.", - "handler": "clearGuestOrderToken" + "handler": "clearGuestOrderToken", + "if": "{{!response.two_factor_required}}" }, + { "handler": "toast", "if": "{{!response.two_factor_required}}", "params": { "type": "success", "message": "$t:auth.login_success" } }, + { "handler": "navigate", "if": "{{!response.two_factor_required}}", "params": { "path": "{{query.redirect ?? '/'}}" } } + ], + "onError": [ + { "handler": "setState", "params": { "target": "global", "isLoggingIn": false, "loginError": "{{error.message}}", "loginErrors": "{{error.errors}}" } } + ] + }, + { + "handler": "loginTwoFactor", + "target": "user", + "if": "{{_global.twoFactor?.required}}", + "params": { + "body": { + "challenge_id": "{{_global.twoFactor?.challenge_id}}", + "code": "{{_global.twoFactor?.code}}" + } + }, + "onSuccess": [ + { "handler": "setState", "params": { "target": "global", "currentUser": "{{response.user}}", "isLoggingIn": false, "twoFactor": null } }, + { "handler": "clearGuestOrderToken" }, { "handler": "toast", "params": { "type": "success", "message": "$t:auth.login_success" } }, { "handler": "navigate", "params": { "path": "{{query.redirect ?? '/'}}" } } ], "onError": [ - { "handler": "setState", "params": { "target": "global", "isLoggingIn": false, "loginError": "{{error.message}}", "loginErrors": "{{error.errors}}" } } + { + "_comment": "423 잠금은 1단계와 같은 문구 규칙을 따르도록 loginErrors 도 함께 싣는다.", + "handler": "setState", + "params": { + "target": "global", + "isLoggingIn": false, + "twoFactor.error": "{{error.message}}", + "loginErrors": "{{error.errors}}" + } + } ] } ] diff --git a/templates/_bundled/sirsoft-basic/package-lock.json b/templates/_bundled/sirsoft-basic/package-lock.json index 63f94c1a..5704373e 100644 --- a/templates/_bundled/sirsoft-basic/package-lock.json +++ b/templates/_bundled/sirsoft-basic/package-lock.json @@ -1,12 +1,12 @@ { "name": "sirsoft-basic", - "version": "1.1.3", + "version": "1.1.4", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "sirsoft-basic", - "version": "1.1.3", + "version": "1.1.4", "license": "MIT", "dependencies": { "@dnd-kit/core": "^6.3.1", diff --git a/templates/_bundled/sirsoft-basic/package.json b/templates/_bundled/sirsoft-basic/package.json index 786b3832..e69b9cd1 100644 --- a/templates/_bundled/sirsoft-basic/package.json +++ b/templates/_bundled/sirsoft-basic/package.json @@ -1,6 +1,6 @@ { "name": "sirsoft-basic", - "version": "1.1.3", + "version": "1.1.4", "description": "Gnuboard7 Basic User Template Components - Nexibase Style", "type": "module", "main": "dist/components.js", diff --git a/templates/_bundled/sirsoft-basic/src/__tests__/layouts/login.test.tsx b/templates/_bundled/sirsoft-basic/src/__tests__/layouts/login.test.tsx index f4744cbc..fa73484a 100644 --- a/templates/_bundled/sirsoft-basic/src/__tests__/layouts/login.test.tsx +++ b/templates/_bundled/sirsoft-basic/src/__tests__/layouts/login.test.tsx @@ -166,10 +166,12 @@ const loginFormFixture = { ], }, // 이메일 입력 필드 + // 2단계 인증이 요구되면 자격 증명 입력은 숨는다 — 실제 `_login_form.json` 과 같은 조건. { id: 'email-field', type: 'basic', name: 'Div', + if: '{{!_global.twoFactor?.required}}', props: { 'data-testid': 'email-field' }, children: [ { @@ -197,6 +199,7 @@ const loginFormFixture = { id: 'password-field', type: 'basic', name: 'Div', + if: '{{!_global.twoFactor?.required}}', props: { 'data-testid': 'password-field' }, children: [ { @@ -224,6 +227,7 @@ const loginFormFixture = { id: 'submit-button', type: 'basic', name: 'Button', + if: '{{!_global.twoFactor?.required}}', props: { type: 'submit', className: 'w-full py-3 mt-12 bg-gray-900 dark:bg-white text-white dark:text-gray-900 rounded-lg font-medium hover:bg-gray-800 dark:hover:bg-gray-100 transition-colors disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center gap-2', @@ -457,4 +461,48 @@ describe('로그인 폼 레이아웃 렌더링 (Issue #72)', () => { testUtils.cleanup(); }); }); + + // 2단계 인증이 켜진 사이트에서는 로그인 응답이 인증번호 요구로 갈린다. 그때 자격 증명 입력이 + // 남아 있으면 두 단계가 겹쳐 보이고, 사용자가 이메일·비밀번호를 다시 제출해 새 challenge 를 + // 발급받는다. 이 fixture 의 `if` 는 실제 `_login_form.json` 과 같은 조건이므로, 한쪽이 바뀌면 + // 이 케이스가 red 가 된다. + describe('2단계 인증 단계 전환', () => { + it('twoFactor 가 없으면 자격 증명 입력이 보인다', async () => { + // Given + const testUtils = createLayoutTest(loginFormFixture, { + componentRegistry: registry, + initialState: { _global: {} }, + }); + + // When + await testUtils.render(); + + // Then + expect(screen.getByTestId('email-field')).toBeInTheDocument(); + expect(screen.getByTestId('password-field')).toBeInTheDocument(); + expect(screen.getByTestId('login-submit-btn')).toBeInTheDocument(); + + testUtils.cleanup(); + }); + + it('인증번호가 요구되면 자격 증명 입력과 제출 버튼이 사라진다', async () => { + // Given + const testUtils = createLayoutTest(loginFormFixture, { + componentRegistry: registry, + initialState: { + _global: { twoFactor: { required: true, challenge_id: 'c-1', code: '' } }, + }, + }); + + // When + await testUtils.render(); + + // Then + expect(screen.queryByTestId('email-field')).not.toBeInTheDocument(); + expect(screen.queryByTestId('password-field')).not.toBeInTheDocument(); + expect(screen.queryByTestId('login-submit-btn')).not.toBeInTheDocument(); + + testUtils.cleanup(); + }); + }); }); diff --git a/templates/_bundled/sirsoft-basic/src/components/basic/Img.tsx b/templates/_bundled/sirsoft-basic/src/components/basic/Img.tsx index c4ecd711..fecc640c 100644 --- a/templates/_bundled/sirsoft-basic/src/components/basic/Img.tsx +++ b/templates/_bundled/sirsoft-basic/src/components/basic/Img.tsx @@ -2,18 +2,50 @@ import React from 'react'; export interface ImgProps extends React.ImgHTMLAttributes {} +/** + * `image` 위젯 값 객체의 키 집합 — 레이아웃 편집기가 만들던 `{url,size,repeat,position}`. + * + * 편집기는 이제 단일 값 슬롯에 url 문자열만 기록하고(코어 변환 계층), 이미 잘못 저장된 + * 레이아웃은 업그레이드 스텝이 정정한다. 이 상수는 **업그레이드 전 화면**을 위한 + * 런타임 방어이며, 판정 강도를 백필과 **완전히 같게** 유지한다 — 두 방어선의 기준이 + * 어긋나면 한쪽만 통과하는 값이 생긴다. + */ +const IMAGE_VALUE_KEYS = ['url', 'size', 'repeat', 'position'] as const; + +/** + * `src` 로 들어온 값에서 실제 이미지 URL 을 해석한다. + * + * 문자열이면 그대로(회귀 0). 이미지 값 객체(키 집합 ⊆ 4키 **AND** `url` 보유)면 그 url. + * 그 외(배열·숫자·불리언·4키 밖 키를 가진 객체·비문자열 url)는 `undefined` 를 돌려주어 + * `src` 속성을 **아예 붙이지 않는다** — `src="[object Object]"`(엑박)도 + * `src=""`(현재 문서 재요청)도 만들지 않는다. + * + * `ImgProps` 를 넓히지 않는다(`src?: string` 계약 유지) — export 하면 그 자체가 공개 + * 계약이 되어 느슨한 타입이 다시 새어 나간다. + */ +function resolveImageSrc(src: unknown): string | undefined { + if (typeof src === 'string') return src === '' ? undefined : src; + if (!src || typeof src !== 'object' || Array.isArray(src)) return undefined; + const keys = Object.keys(src as Record); + if (!keys.every((k) => (IMAGE_VALUE_KEYS as readonly string[]).includes(k))) return undefined; + const url = (src as { url?: unknown }).url; + return typeof url === 'string' && url !== '' ? url : undefined; +} + /** * 기본 이미지 컴포넌트 */ export const Img: React.FC = ({ className = '', alt = '', + src, ...props }) => { return ( {alt} ); diff --git a/templates/_bundled/sirsoft-basic/src/components/basic/__tests__/Img.test.tsx b/templates/_bundled/sirsoft-basic/src/components/basic/__tests__/Img.test.tsx new file mode 100644 index 00000000..2c707416 --- /dev/null +++ b/templates/_bundled/sirsoft-basic/src/components/basic/__tests__/Img.test.tsx @@ -0,0 +1,116 @@ +// @scenario apply_type=propValue, consumer=img_src, storage_scope=template_layouts, stored_shape=string, widget_output=scalar_string +// @scenario apply_type=propValue, consumer=mobilenav_logo, storage_scope=template_layouts, stored_shape=absent, widget_output=image_object +/** + * Img.test.tsx — `src` 값 형태 방어 (공개 #135 런타임 방어선) + * + * 레이아웃 편집기의 `image` 위젯은 `{url,size,repeat,position}` 객체를 내보내는데, + * 그 값이 `propValue` 로 컴포넌트 prop 에 그대로 기록되던 시기가 있었다. 그렇게 저장된 + * 레이아웃은 `` 가 되어 브라우저가 `[object Object]` 를 URL 로 해석했고, + * SPA catch-all 때문에 404 조차 아니라 200(HTML)을 받아 **엑박만 남고 아무 오류도 + * 남지 않았다.** + * + * 편집기·백필이 고쳐졌어도 업그레이드 전 화면은 그대로이므로 이 방어가 필요하다. + * 판정 강도는 백필 마이그레이션과 **완전히 같다**(키 집합 ⊆ 4키 AND url 문자열) — + * 두 방어선의 기준이 어긋나면 한쪽만 통과하는 값이 생긴다. + */ + +import { describe, it, expect } from 'vitest'; +import { render } from '@testing-library/react'; +import React from 'react'; +import { Img } from '../Img'; + +/** 렌더된 의 src 속성 원문(미부착이면 null) */ +function srcAttr(container: HTMLElement): string | null { + const img = container.querySelector('img'); + expect(img).toBeTruthy(); + return img!.getAttribute('src'); +} + +describe('Img — src 값 형태 방어', () => { + /** @effects img_string_src_passes_through_unchanged */ + it('1 정상 문자열은 그대로 통과한다 (회귀 0)', () => { + const { container } = render(); + expect(srcAttr(container)).toBe('/img/logo.png'); + }); + + /** @effects img_corrupted_value_attaches_no_src_neither_object_object_nor_empty_string */ + it('2 빈 문자열은 src 를 붙이지 않는다 (현재 문서 재요청 차단)', () => { + const { container } = render(); + expect(srcAttr(container)).toBeNull(); + }); + + it('3 src 미전달도 예외 없이 미부착', () => { + const { container } = render(x); + expect(srcAttr(container)).toBeNull(); + }); + + /** @effects img_image_value_object_resolves_to_its_url */ + it('4 이미지 값 객체(4키 전부) → url 만 채택', () => { + const value = { + url: '/api/attachment/X', + size: 'cover', + repeat: 'no-repeat', + position: 'center', + }; + const { container } = render(); + expect(srcAttr(container)).toBe('/api/attachment/X'); + }); + + it('5 부분 키(url 만) 객체도 채택', () => { + const { container } = render(); + expect(srcAttr(container)).toBe('/a.png'); + }); + + it('6 url 이 빈 문자열이면 미부착 — [object Object] 도 빈 문자열도 아니다', () => { + const { container } = render( + , + ); + expect(srcAttr(container)).toBeNull(); + }); + + it('7 url 키가 없는 객체는 미부착 (정상 props 와 정적 구분 불가 → 건드리지 않는다)', () => { + const { container } = render( + , + ); + expect(srcAttr(container)).toBeNull(); + }); + + it('8 url 이 비문자열이면 미부착', () => { + const { container } = render(); + expect(srcAttr(container)).toBeNull(); + }); + + /** @effects img_object_with_key_outside_the_four_is_rejected_same_strictness_as_backfill */ + it('9 4키 밖 키가 섞이면 미부착 — 백필과 동일 엄격도', () => { + const { container } = render(); + expect(srcAttr(container)).toBeNull(); + }); + + it('10 배열은 미부착', () => { + const { container } = render(); + expect(srcAttr(container)).toBeNull(); + }); + + it('11 숫자·불리언은 예외 없이 미부착', () => { + expect(srcAttr(render().container)).toBeNull(); + expect(srcAttr(render().container)).toBeNull(); + }); + + /** @effects img_other_attributes_pass_through_spread_regression_zero */ + it('12 다른 속성은 그대로 통과한다 (스프레드 회귀 0)', () => { + const { container } = render( + 로고, + ); + const img = container.querySelector('img')!; + expect(img.getAttribute('src')).toBe('/a.png'); + expect(img.getAttribute('alt')).toBe('로고'); + expect(img.getAttribute('class')).toBe('h-8'); + expect(img.getAttribute('loading')).toBe('lazy'); + expect(img.getAttribute('width')).toBe('32'); + }); + + it('13 alt 기본값은 빈 문자열로 유지된다', () => { + const { container } = render(); + expect(container.querySelector('img')!.getAttribute('alt')).toBe(''); + }); +}); diff --git a/templates/_bundled/sirsoft-basic/template.json b/templates/_bundled/sirsoft-basic/template.json index e157030c..4bacbf5c 100644 --- a/templates/_bundled/sirsoft-basic/template.json +++ b/templates/_bundled/sirsoft-basic/template.json @@ -5,7 +5,7 @@ "ko": "Basic", "en": "Basic" }, - "version": "1.1.3", + "version": "1.1.4", "license": "MIT", "description": { "ko": "그누보드7 기본 사용자 템플릿", @@ -22,7 +22,7 @@ "url": "https://sirsoft.com" }, "release_date": "2026-01-07", - "g7_version": ">=7.0.10", + "g7_version": ">=7.0.11", "dependencies": { "modules": { "sirsoft-board": ">=1.0.0", diff --git a/templates/_bundled/sirsoft-basic/tests/scenarios/login-two-factor-step.yaml b/templates/_bundled/sirsoft-basic/tests/scenarios/login-two-factor-step.yaml new file mode 100644 index 00000000..e13ac504 --- /dev/null +++ b/templates/_bundled/sirsoft-basic/tests/scenarios/login-two-factor-step.yaml @@ -0,0 +1,45 @@ +# audit:allow test-scenario-coverage reason: response 축(ok/challenge/401/403/423/429/503/network)은 서버 응답 +# 종류를 열거한 것이라 레이아웃 구조 테스트가 케이스별로 나눠 단언할 실체가 아니다 — 구조 테스트는 화면이 +# 그 응답들을 받을 수 있는 형태인지(상보 조건·controlled 입력·상호배타 액션)를 본다. 실제 회귀 가드는 +# effects 이며 전부 @effects 로 귀속되어 있다(미검증 0건). 응답별 화면 실측은 Playwright spec 이 담당한다. + +feature: 로그인 화면 2단계 인증 단계 (sirsoft-basic) + +description: | + 2단계 인증이 켜진 사이트에서 로그인 화면은 비밀번호 단계와 인증번호 단계를 같은 카드 안에서 + 전환한다. 서버가 두 가지 형태의 200 을 돌려주므로, 화면이 한 형태만 가정하면 영문 오류가 + 노출되고 로그인이 불가능해진다(공개 #133). + + 핵심 동작: + - 1단계 블록과 2단계 블록의 조건이 상보적이다 (동시 노출 금지). + - 제출 시퀀스의 login 과 loginTwoFactor 가 상호배타 조건을 갖는다. 조건이 빠지면 + 인증번호 단계에서 Enter 를 누를 때 새 challenge 가 발급되어 흐름이 깨진다. + - 인증번호 입력은 상태가 값을 소유한다 (재발송 시 입력을 비워야 하므로). + - 인증 단계 상태는 전역이라 화면 진입 시 초기화한다. + +axes: + step: [credentials, code] + response: [ok, challenge, 401, 423, 429, 503, network] + action: [submit, resend, restart, enter_key] + +exclusions: + - { step: credentials, action: resend, reason: "재발송 버튼은 인증번호 단계에만 있다" } + - { step: credentials, action: restart, reason: "동일" } + - { step: code, response: challenge, reason: "challenge 는 비밀번호 단계의 응답이다" } + +effects: + - no_raw_typeerror_text + - code_step_rendered_on_challenge + - credential_step_hidden_on_challenge + - login_and_verify_are_mutually_exclusive + - code_input_is_controlled_without_events_wrapper + - resend_clears_code_and_replaces_challenge + - restart_resets_to_credential_step + - locked_until_rendered + - init_actions_reset_two_factor_state + - network_message_translated + +test_files: + - templates/_bundled/sirsoft-basic/__tests__/layouts/login-two-factor-step.test.tsx + - templates/_bundled/sirsoft-basic/__tests__/layouts/login-two-factor-render.test.tsx + - tests/Playwright/specs/auth/two-factor-login.spec.ts diff --git a/tests/Feature/Api/Admin/TemplateLayoutAttachmentControllerTest.php b/tests/Feature/Api/Admin/TemplateLayoutAttachmentControllerTest.php index aebe2566..9b0485c6 100644 --- a/tests/Feature/Api/Admin/TemplateLayoutAttachmentControllerTest.php +++ b/tests/Feature/Api/Admin/TemplateLayoutAttachmentControllerTest.php @@ -7,11 +7,13 @@ use App\Extension\HookManager; use App\Models\Permission; use App\Models\Role; use App\Models\Template; +use App\Models\TemplateLayout; use App\Models\TemplateLayoutAttachment; use App\Models\User; use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Http\UploadedFile; use Illuminate\Support\Facades\Storage; +use Illuminate\Testing\TestResponse; use Tests\TestCase; /** @@ -326,4 +328,205 @@ class TemplateLayoutAttachmentControllerTest extends TestCase $response->assertStatus(404); } + + /** + * 공개 자산 디스크로 쓸 가짜 CDN 디스크를 등록합니다. + * + * `Storage::fake()` 는 해석된 디스크 인스턴스만 교체하고 `filesystems.disks.*` + * config 는 건드리지 않는다. 공개 자산 디스크 게이트는 그 config 존재를 보므로, + * fake 만으로는 고아 디스크로 판정되어 프록시가 나온다. + */ + private function registerFakeCdnDisk(): void + { + config(['filesystems.disks.fake_cdn' => [ + 'driver' => 'local', + 'root' => storage_path('framework/testing/disks/fake_cdn'), + 'url' => 'https://cdn.test/assets', + ]]); + Storage::fake('fake_cdn', ['url' => 'https://cdn.test/assets']); + } + + // ───────────────────────────────────────────────────────────────────────── + // 공개 자산 디스크 — 업로드 저장 위치와 응답 URL 형태 (공개 #134) + // ───────────────────────────────────────────────────────────────────────── + + /** + * 공개 자산 디스크 미설정이면 기존 첨부 디스크에 저장되고 프록시 URL 이어야 합니다. + * + * @scenario public_asset_disk=unset,row_disk=attachments,filter_url_hook=absent + * + * @effects upload_stores_on_public_disk, proxy_url_otherwise + */ + public function test_upload_without_public_asset_disk_uses_attachment_disk(): void + { + config(['core.storage.public_asset_disk' => '']); + + $response = $this->withHeaders($this->authHeaders()) + ->postJson("/api/admin/templates/{$this->template->identifier}/layout-attachments", [ + 'file' => UploadedFile::fake()->image('bg.png', 20, 20), + 'layout_name' => 'home', + ]); + + $response->assertStatus(200); + + $attachment = TemplateLayoutAttachment::first(); + $this->assertSame(config('attachment.disk', 'attachments'), $attachment->disk); + $this->assertStringContainsString('/layout-attachments/', (string) $response->json('data.url')); + $this->assertStringContainsString('/file', (string) $response->json('data.url')); + } + + /** + * 공개 자산 디스크가 선언되면 그 디스크에 저장되고 직접 URL 이 발급돼야 합니다. + * + * @scenario public_asset_disk=public,row_disk=public,filter_url_hook=absent + * + * @effects upload_stores_on_public_disk, direct_url_when_row_matches_public_disk + */ + public function test_upload_with_public_asset_disk_stores_there_and_returns_direct_url(): void + { + $this->registerFakeCdnDisk(); + config(['core.storage.public_asset_disk' => 'fake_cdn']); + + $response = $this->withHeaders($this->authHeaders()) + ->postJson("/api/admin/templates/{$this->template->identifier}/layout-attachments", [ + 'file' => UploadedFile::fake()->image('cdn.png', 20, 20), + 'layout_name' => 'home', + ]); + + $response->assertStatus(200); + + $attachment = TemplateLayoutAttachment::first(); + $this->assertSame('fake_cdn', $attachment->disk); + Storage::disk('fake_cdn')->assertExists('template-layout-attachments/'.$attachment->path); + + $this->assertStringStartsWith('https://cdn.test/assets', (string) $response->json('data.url')); + } + + /** + * 공개 자산 디스크를 켜도 그 이전에 올라간(다른 disk) 행은 프록시를 유지해야 합니다. + * + * @scenario public_asset_disk=public,row_disk=attachments,filter_url_hook=absent + * + * @effects proxy_url_otherwise + */ + public function test_legacy_row_keeps_proxy_url_after_enabling_public_asset_disk(): void + { + $legacy = $this->makeStoredAttachment(); + + $this->registerFakeCdnDisk(); + config(['core.storage.public_asset_disk' => 'fake_cdn']); + + $response = $this->withHeaders($this->authHeaders()) + ->getJson("/api/admin/templates/{$this->template->identifier}/layout-attachments"); + + $response->assertStatus(200); + $url = (string) $response->json('data.0.url'); + $this->assertStringContainsString("/layout-attachments/{$legacy->id}/file", $url); + $this->assertStringNotContainsString('cdn.test', $url); + } + // ───────────────────────────────────────────────────────────────────────── + // 업로드 응답 url → 레이아웃 저장 게이트 왕복 + // + // 헤더 「로고 이미지」처럼 값 슬롯이 하나뿐인 컨트롤은 업로드 응답 url 을 props 에 그대로 + // 넣는다. 그 값이 저장 요청의 NoExternalUrls 를 통과하지 못하면 운영자는 "업로드는 + // 됐는데 저장은 422" 를 본다. 배경 이미지는 style 로 들어가 스캔되지 않아 드러나지 않았다. + // ───────────────────────────────────────────────────────────────────────── + + /** + * 업로드 응답 url 을 props 에 넣은 레이아웃을 PUT 합니다. + * + * @param string $url 업로드 응답 url + * @return TestResponse + */ + private function saveLayoutWithLogo(string $url) + { + $layout = TemplateLayout::factory()->create([ + 'template_id' => $this->template->id, + 'name' => 'home', + ]); + + $content = $layout->content; + // 팩토리 기본 endpoint(`/api/{단어}`)는 WhitelistedEndpoint 에 걸린다 — 이 테스트의 축이 아니다. + $content['endpoint'] = '/api/public/home'; + $content['components'] = [[ + 'id' => 'header', + 'type' => 'composite', + 'name' => 'Header', + 'props' => ['logo' => $url], + ]]; + + return $this->withHeaders($this->authHeaders()) + ->putJson("/api/admin/templates/{$this->template->identifier}/layouts/home", [ + 'expected_lock_version' => (int) ($layout->lock_version ?? 0), + 'content' => $content, + ]); + } + + /** + * 프록시 변종 — 업로드 응답 url 이 저장 요청을 통과해야 합니다. + * + * @scenario url_host=site_relative_path + * + * @effects proxy_url_is_site_relative, issued_asset_url_passes_storage_gate + */ + public function test_uploaded_proxy_url_is_accepted_by_layout_save(): void + { + $upload = $this->withHeaders($this->authHeaders()) + ->postJson("/api/admin/templates/{$this->template->identifier}/layout-attachments", [ + 'file' => UploadedFile::fake()->image('logo.png', 48, 24), + 'layout_name' => 'home', + ]); + $upload->assertStatus(200); + $url = (string) $upload->json('data.url'); + + $this->assertStringStartsWith('/api/', $url, '프록시 URL 은 사이트 상대 경로여야 합니다'); + + $save = $this->saveLayoutWithLogo($url); + + $save->assertStatus(200); + $this->assertSame($url, TemplateLayout::where('name', 'home')->first()->content['components'][0]['props']['logo']); + } + + /** + * 직접 URL(CDN) 변종 — 운영자가 선언한 공개 자산 디스크의 절대 URL 도 저장 요청을 통과해야 합니다. + * + * @scenario url_host=public_asset_disk + * + * @effects issued_asset_url_passes_storage_gate + */ + public function test_uploaded_direct_url_is_accepted_by_layout_save(): void + { + $this->registerFakeCdnDisk(); + config(['core.storage.public_asset_disk' => 'fake_cdn']); + + $upload = $this->withHeaders($this->authHeaders()) + ->postJson("/api/admin/templates/{$this->template->identifier}/layout-attachments", [ + 'file' => UploadedFile::fake()->image('logo.png', 48, 24), + 'layout_name' => 'home', + ]); + $upload->assertStatus(200); + $url = (string) $upload->json('data.url'); + + $this->assertStringStartsWith('https://cdn.test/assets', $url); + + $save = $this->saveLayoutWithLogo($url); + + $save->assertStatus(200); + $this->assertSame($url, TemplateLayout::where('name', 'home')->first()->content['components'][0]['props']['logo']); + } + + /** + * 선언되지 않은 외부 host 는 여전히 차단되어야 합니다 — 허용 범위가 "서버가 발급하는 주소" 를 + * 넘어 넓어지지 않았는지 고정한다. + * + * @scenario url_host=external_undeclared + * + * @effects external_host_still_rejected + */ + public function test_external_host_url_is_still_rejected_by_layout_save(): void + { + $save = $this->saveLayoutWithLogo('https://attacker.example/template-layout-attachments/x.png'); + + $save->assertStatus(422); + } } diff --git a/tests/Feature/Api/Public/ExtensionBundleServingTest.php b/tests/Feature/Api/Public/ExtensionBundleServingTest.php index 1b22b2b0..37c251b5 100644 --- a/tests/Feature/Api/Public/ExtensionBundleServingTest.php +++ b/tests/Feature/Api/Public/ExtensionBundleServingTest.php @@ -9,7 +9,7 @@ use Mockery; use Tests\TestCase; /** - * 확장 프론트엔드 병합 번들 서빙 엔드포인트 Feature 테스트 + * [case:backend-27] 확장 프론트엔드 병합 번들 서빙 엔드포인트 Feature 테스트 * * /api/{modules,plugins}/bundle.{js,css} 의 응답 계약(Content-Type, ETag, * 304 Not Modified, 빈 번들 처리)을 검증한다. ExtensionBundleService 를 diff --git a/tests/Feature/Api/Public/LayoutOverlayInjectionIndexMetaTest.php b/tests/Feature/Api/Public/LayoutOverlayInjectionIndexMetaTest.php new file mode 100644 index 00000000..c784618f --- /dev/null +++ b/tests/Feature/Api/Public/LayoutOverlayInjectionIndexMetaTest.php @@ -0,0 +1,137 @@ + + */ + protected array $requiredExtensions = [ + 'plugins/sirsoft-gdpr', + ]; + + #[Test] + public function overlay_injected_nodes_carry_injection_index_in_source_meta(): void + { + $template = Template::factory()->create([ + 'identifier' => 'sirsoft-basic', + 'type' => 'user', + 'status' => ExtensionStatus::Active->value, + ]); + + $extension = LayoutExtension::create([ + 'template_id' => $template->id, + 'extension_type' => LayoutExtensionType::Overlay, + 'target_name' => '_user_base', + // 출처를 템플릿 자신으로 두어 모듈 활성 여부 게이트(isExtensionSourceActive)에 걸리지 않게 한다. + 'source_type' => LayoutSourceType::Template, + 'source_identifier' => 'sirsoft-basic', + 'content' => [ + 'target_layout' => '_user_base', + 'injections' => [ + // 0 — inject_props (components 없음). 순번은 이 injection 도 센다. + ['target_id' => 'anchor_a', 'position' => 'inject_props', 'props' => ['className' => 'x']], + // 1 — 컴포넌트 주입 + [ + 'target_id' => 'anchor_b', + 'position' => 'append_child', + 'components' => [ + [ + 'type' => 'basic', + 'name' => 'Span', + 'id' => 'ext_b', + 'children' => [ + ['type' => 'basic', 'name' => 'Span', 'id' => 'ext_b_child'], + ], + ], + ], + ], + ], + 'priority' => 320, + ], + 'priority' => 320, + 'is_active' => true, + ]); + + $layout = [ + 'layout_name' => '_user_base', + 'meta' => ['title' => 'Base'], + 'components' => [ + ['type' => 'basic', 'name' => 'Div', 'id' => 'anchor_a'], + ['type' => 'basic', 'name' => 'Div', 'id' => 'anchor_b'], + ], + ]; + + // 편집 모드(with_source_meta) 병합 — PublicLayoutController::serve 가 같은 인자로 호출한다. + $merged = $this->app->make(LayoutExtensionService::class) + ->applyExtensions($layout, $template->id, true); + + $injected = $this->findNodeById($merged['components'], 'ext_b'); + $this->assertNotNull($injected, '주입 노드 ext_b 가 병합 결과에 있어야 한다'); + $this->assertSame('extension', $injected['__source']['kind'] ?? null); + $this->assertSame($extension->id, $injected['__source']['extensionId'] ?? null); + $this->assertSame( + 1, + $injected['__source']['injectionIndex'] ?? null, + '주입 노드 진입점의 __source 에 원래 injection 순번(1)이 실려야 편집기가 저장 시 되돌릴 수 있다' + ); + + // 자식도 같은 순번을 상속(진입점 판정은 자식으로 내려가지 않지만 메타는 일관). + $child = $this->findNodeById($merged['components'], 'ext_b_child'); + $this->assertSame(1, $child['__source']['injectionIndex'] ?? null); + + // 운영 렌더(with_source_meta=false)에는 메타가 전혀 없다 — 비파괴. + $plain = $this->app->make(LayoutExtensionService::class) + ->applyExtensions($layout, $template->id, false); + $plainInjected = $this->findNodeById($plain['components'], 'ext_b'); + $this->assertNotNull($plainInjected); + $this->assertArrayNotHasKey('__source', $plainInjected); + } + + /** + * 트리에서 id 로 노드를 찾는다. + * + * @param array|null $nodes 컴포넌트 배열 + * @param string $id 찾을 id + * @return array|null 노드 + */ + private function findNodeById(?array $nodes, string $id): ?array + { + foreach ($nodes ?? [] as $node) { + if (! is_array($node)) { + continue; + } + if (($node['id'] ?? null) === $id) { + return $node; + } + $found = $this->findNodeById($node['children'] ?? null, $id); + if ($found !== null) { + return $found; + } + } + + return null; + } +} diff --git a/tests/Feature/Api/Public/LayoutServingStaleClientVersionKeyTest.php b/tests/Feature/Api/Public/LayoutServingStaleClientVersionKeyTest.php new file mode 100644 index 00000000..ff206717 --- /dev/null +++ b/tests/Feature/Api/Public/LayoutServingStaleClientVersionKeyTest.php @@ -0,0 +1,98 @@ + + */ + protected array $requiredExtensions = [ + 'plugins/sirsoft-gdpr', + ]; + + #[Test] + public function serve_with_boot_time_client_version_returns_fresh_content_after_two_saves(): void + { + $template = Template::create([ + 'identifier' => 'sirsoft-admin_basic', + 'vendor' => 'sirsoft', + 'name' => ['ko' => '기본 관리자 템플릿', 'en' => 'Basic Admin Template'], + 'version' => '1.0.0', + 'type' => 'admin', + 'status' => ExtensionStatus::Active->value, + 'description' => ['ko' => '기본 관리자 템플릿', 'en' => 'Basic Admin Template'], + ]); + + $layout = TemplateLayout::create([ + 'template_id' => $template->id, + 'name' => 'dashboard', + 'content' => $this->content('Before'), + ]); + + // 편집기 부팅 시점의 확장 캐시 버전 — 이후 요청은 모두 이 정수에 nonce 만 붙인다. + Cache::put('g7:core:ext.cache_version', 1000); + $url = "/api/layouts/{$template->identifier}/dashboard.json?v=1000."; + + $this->assertSame('Before', $this->getJson($url.'0')->assertStatus(200)->json('data.meta.title')); + + $service = app(LayoutService::class); + + // 저장 1 — 서빙 캐시 무효화 + cache_version bump(time()). + $service->updateLayout($template->id, 'dashboard', [ + 'content' => $this->content('After 1'), + 'expected_lock_version' => (int) $layout->fresh()->lock_version, + ]); + // 편집기 재로드(초기화·복원 뒤) — 부팅 버전 키가 다시 채워진다. + $this->assertSame('After 1', $this->getJson($url.'1')->assertStatus(200)->json('data.meta.title')); + + // 저장 2 — 서버는 현재(bump 된) 버전 키만 지운다. + $service->updateLayout($template->id, 'dashboard', [ + 'content' => $this->content('After 2'), + 'expected_lock_version' => (int) $layout->fresh()->lock_version, + ]); + + // 편집기 재로드 — 부팅 버전으로 요청해도 최신이어야 한다(종전: 'After 1' stale). + $this->assertSame( + 'After 2', + $this->getJson($url.'2')->assertStatus(200)->json('data.meta.title'), + '부팅 시점 cache_version 으로 조립된 서빙 캐시 키가 두 번째 저장 뒤 stale 로 남는다' + ); + } + + /** + * @param string $title meta.title + * @return array + */ + private function content(string $title): array + { + return [ + 'meta' => ['title' => $title], + 'data_sources' => [], + 'components' => [ + ['type' => 'basic', 'name' => 'Div', 'props' => ['className' => 'container'], 'children' => []], + ], + ]; + } +} diff --git a/tests/Feature/Auth/LoginThrottleTest.php b/tests/Feature/Auth/LoginThrottleTest.php index 88161b2c..ecc28f33 100644 --- a/tests/Feature/Auth/LoginThrottleTest.php +++ b/tests/Feature/Auth/LoginThrottleTest.php @@ -127,7 +127,7 @@ class LoginThrottleTest extends TestCase /** * @test */ - public function login_attempt_enabled_가_OFF_이면_무제한_시도_허용된다(): void + public function login_attempt_enabled_가_off_이면_무제한_시도_허용된다(): void { $this->setSecuritySettings([ 'login_attempt_enabled' => false, @@ -190,7 +190,7 @@ class LoginThrottleTest extends TestCase /** * @test */ - public function 존재하지_않는_이메일은_DB에_카운트_저장되지_않는다(): void + public function 존재하지_않는_이메일은_db에_카운트_저장되지_않는다(): void { $response = $this->postJson('/api/auth/login', [ 'email' => 'ghost@example.com', @@ -202,6 +202,41 @@ class LoginThrottleTest extends TestCase $this->assertDatabaseMissing('users', ['email' => 'ghost@example.com']); } + /** + * @test + * + * @scenario two_factor=off, controller=user, delivery=sent + * + * @effects too_many_attempts_message_translated + */ + public function 분당_요청_상한을_넘으면_다국어_문구로_응답한다(): void + { + // 기본 응답은 영문 "Too Many Attempts." 이다 — 로그인 화면이 그 문구를 그대로 + // 노출하므로, 한국어 사이트에서 영문 원문이 오류 박스에 뜬다. + $limit = max(30, 3 * 6); + + for ($i = 0; $i <= $limit; $i++) { + $response = $this->postJson('/api/auth/login', [ + 'email' => 'ghost@example.com', + 'password' => 'wrong-password', + ]); + + if ($response->getStatusCode() === 429) { + break; + } + } + + $response->assertStatus(429); + $this->assertNotSame('Too Many Attempts.', $response->json('message')); + $this->assertStringNotContainsString(':seconds', (string) $response->json('message')); + $this->assertSame( + __('auth.too_many_attempts', ['seconds' => (int) $response->headers->get('Retry-After')]), + $response->json('message') + ); + // 재시도 시점 안내는 그대로 유지되어야 한다. + $this->assertNotNull($response->headers->get('Retry-After')); + } + private function setSecuritySettings(array $values): void { $existing = (array) config('g7_settings.core.security', []); diff --git a/tests/Feature/Auth/TwoFactorAuthTest.php b/tests/Feature/Auth/TwoFactorAuthTest.php index 3da1924e..5d7dae3f 100644 --- a/tests/Feature/Auth/TwoFactorAuthTest.php +++ b/tests/Feature/Auth/TwoFactorAuthTest.php @@ -3,11 +3,14 @@ namespace Tests\Feature\Auth; use App\Enums\IdentityVerificationPurpose; +use App\Enums\IdentityVerificationStatus; use App\Enums\UserStatus; use App\Models\IdentityVerificationLog; +use App\Models\Role; use App\Models\User; use App\Services\IdentityVerificationService; use Database\Seeders\IdentityMessageDefinitionSeeder; +use Database\Seeders\RolePermissionSeeder; use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Support\Facades\Hash; use Illuminate\Support\Facades\Mail; @@ -16,7 +19,7 @@ use PHPUnit\Framework\Attributes\Test; use Tests\TestCase; /** - * 로그인 2단계 인증 테스트 + * [case:backend-29] 로그인 2단계 인증 테스트 * * `security.two_factor_auth` 는 설정 화면에만 있고 구현이 없어, 켜도 아무 일도 일어나지 * 않았습니다. 관리자는 2단계 인증이 걸린 줄 알지만 실제로는 비밀번호 하나로 로그인됩니다. @@ -71,6 +74,9 @@ class TwoFactorAuthTest extends TestCase ]); } + /** + * @scenario two_factor=off, controller=user + */ #[Test] public function login_issues_a_token_directly_when_two_factor_is_off(): void { @@ -82,6 +88,11 @@ class TwoFactorAuthTest extends TestCase $this->assertNotEmpty($response->json('data.token'), '2단계 인증이 꺼져 있으면 종전대로 바로 로그인되어야 합니다.'); } + /** + * @scenario two_factor=on, controller=user, delivery=sent + * + * @effects challenge_response_has_no_token, challenge_response_has_no_user + */ #[Test] public function login_withholds_the_token_when_two_factor_is_on(): void { @@ -102,6 +113,9 @@ class TwoFactorAuthTest extends TestCase $this->assertNotEmpty($response->json('data.challenge_id')); } + /** + * @scenario two_factor=on, controller=user, code=valid + */ #[Test] public function verifying_the_challenge_completes_the_login(): void { @@ -119,6 +133,9 @@ class TwoFactorAuthTest extends TestCase $this->assertSame('two-factor@test.com', $response->json('data.user.email')); } + /** + * @scenario two_factor=on, controller=user, code=invalid + */ #[Test] public function a_wrong_code_does_not_issue_a_token(): void { @@ -135,6 +152,9 @@ class TwoFactorAuthTest extends TestCase $this->assertNull($response->json('data.token')); } + /** + * @scenario two_factor=on, controller=user, code=invalid + */ #[Test] public function a_challenge_issued_for_another_purpose_cannot_be_used_to_log_in(): void { @@ -157,6 +177,11 @@ class TwoFactorAuthTest extends TestCase $this->assertNull($response->json('data.token')); } + /** + * @scenario two_factor=on, controller=user, delivery=failed + * + * @effects delivery_failure_503_with_reason + */ #[Test] public function login_fails_clearly_when_the_code_cannot_be_delivered(): void { @@ -169,13 +194,289 @@ class TwoFactorAuthTest extends TestCase $response = $this->login(); - $response->assertStatus(401); + // 자격 증명은 올바르다 — 401 로 답하면 사용자는 비밀번호를 의심하며 같은 실패를 + // 반복하고, 운영자는 메일 설정이 깨진 사실을 알 방법이 없다. + $response->assertStatus(503); + $this->assertSame(__('auth.two_factor_delivery_failed'), $response->json('message')); $this->assertNull( $response->json('data.token'), '발송 실패 시 2단계 인증을 건너뛰고 로그인시키면 보안 통제가 조용히 열립니다.' ); } + /** + * @scenario two_factor=on, controller=admin, actor=admin, delivery=sent + * + * @effects admin_login_returns_challenge_not_500 + */ + #[Test] + public function admin_login_returns_a_challenge_instead_of_failing(): void + { + $this->setTwoFactor(true); + $this->makeAdmin($this->user); + + $response = $this->postJson('/api/auth/admin/login', [ + 'email' => 'two-factor@test.com', + 'password' => 'Passw0rd!2fa', + ]); + + // 관리자 로그인이 2단계 인증에서 500 이 되면 설정을 되돌릴 수단까지 사라진다. + $response->assertStatus(200); + $this->assertTrue((bool) $response->json('data.two_factor_required')); + $this->assertNull($response->json('data.token')); + $this->assertNotEmpty($response->json('data.challenge_id')); + } + + /** + * @scenario two_factor=on, controller=admin, actor=admin, code=valid + * + * @effects admin_two_factor_issues_token_for_admin + */ + #[Test] + public function admin_two_factor_completes_the_login(): void + { + $this->setTwoFactor(true); + $this->makeAdmin($this->user); + + $challengeId = $this->postJson('/api/auth/admin/login', [ + 'email' => 'two-factor@test.com', + 'password' => 'Passw0rd!2fa', + ])->json('data.challenge_id'); + + $response = $this->postJson('/api/auth/admin/login/two-factor', [ + 'challenge_id' => $challengeId, + 'code' => $this->issuedCode($challengeId), + ]); + + $response->assertStatus(200); + $this->assertNotEmpty($response->json('data.token')); + } + + /** + * @scenario two_factor=on, controller=admin, actor=non_admin, code=valid + * + * @effects non_admin_two_factor_revokes_token + */ + #[Test] + public function a_non_admin_completing_admin_two_factor_keeps_no_token(): void + { + $this->setTwoFactor(true); + + $challengeId = $this->postJson('/api/auth/admin/login', [ + 'email' => 'two-factor@test.com', + 'password' => 'Passw0rd!2fa', + ])->json('data.challenge_id'); + + $response = $this->postJson('/api/auth/admin/login/two-factor', [ + 'challenge_id' => $challengeId, + 'code' => $this->issuedCode($challengeId), + ]); + + $response->assertStatus(403); + // 코드 확인 시점에 토큰이 이미 발급된다 — 회수하지 않으면 관리자가 아닌 사용자가 + // 응답만 403 을 받을 뿐 유효한 세션을 손에 쥔다. + $this->assertSame(0, $this->user->tokens()->count()); + } + + /** + * @scenario two_factor=off, controller=admin, actor=non_admin + * + * @effects non_admin_admin_login_revokes_token + */ + #[Test] + public function a_non_admin_rejected_at_admin_login_keeps_no_token(): void + { + $this->setTwoFactor(false); + + $response = $this->postJson('/api/auth/admin/login', [ + 'email' => 'two-factor@test.com', + 'password' => 'Passw0rd!2fa', + ]); + + $response->assertStatus(403); + $this->assertSame(0, $this->user->tokens()->count()); + } + + /** + * @scenario two_factor=on, controller=user, resend=active + * + * @effects resend_cancels_previous_challenge + */ + #[Test] + public function resending_cancels_the_previous_challenge_and_issues_a_new_one(): void + { + $this->setTwoFactor(true); + + $first = $this->login()->json('data.challenge_id'); + + $response = $this->postJson('/api/auth/login/two-factor/resend', [ + 'challenge_id' => $first, + ]); + + $response->assertStatus(200); + $second = $response->json('data.challenge_id'); + + $this->assertNotEmpty($second); + $this->assertNotSame($first, $second, '재발송이 같은 challenge 를 돌려주면 새 코드가 발송되지 않은 것입니다.'); + $this->assertNull($response->json('data.token')); + + // 앞선 코드가 계속 통하면 유효한 코드가 여러 개 살아 있어 대입 시도의 표적이 넓어진다. + $this->assertSame( + IdentityVerificationStatus::Cancelled->value, + IdentityVerificationLog::find($first)->status->value + ); + } + + /** + * @scenario two_factor=on, controller=user, resend=verified + * + * @effects resend_rejects_verified_challenge + */ + #[Test] + public function a_verified_challenge_cannot_be_resent(): void + { + $this->setTwoFactor(true); + + $challengeId = $this->login()->json('data.challenge_id'); + + $this->postJson('/api/auth/login/two-factor', [ + 'challenge_id' => $challengeId, + 'code' => $this->issuedCode($challengeId), + ])->assertStatus(200); + + $this->postJson('/api/auth/login/two-factor/resend', [ + 'challenge_id' => $challengeId, + ])->assertStatus(422); + } + + /** + * @scenario two_factor=on, controller=user, resend=expired + * + * @effects resend_rejects_expired_challenge + */ + #[Test] + public function an_expired_challenge_cannot_be_resent(): void + { + $this->setTwoFactor(true); + + $challengeId = $this->login()->json('data.challenge_id'); + + $log = IdentityVerificationLog::find($challengeId); + $log->expires_at = now()->subMinute(); + $log->save(); + + $this->postJson('/api/auth/login/two-factor/resend', [ + 'challenge_id' => $challengeId, + ])->assertStatus(422); + } + + /** + * @scenario two_factor=on, controller=user, resend=cancelled + * + * @effects resend_rejects_cancelled_challenge + */ + #[Test] + public function a_cancelled_challenge_cannot_be_resent(): void + { + $this->setTwoFactor(true); + + $challengeId = $this->login()->json('data.challenge_id'); + + app(IdentityVerificationService::class)->cancel($challengeId); + + $this->postJson('/api/auth/login/two-factor/resend', [ + 'challenge_id' => $challengeId, + ])->assertStatus(422); + } + + /** + * @scenario two_factor=on, controller=user, resend=active, lock_state=active + * + * @effects resend_refused_while_locked + */ + #[Test] + public function resending_is_refused_while_the_account_is_locked(): void + { + $this->setTwoFactor(true); + + $challengeId = $this->login()->json('data.challenge_id'); + + // challenge 를 받아 둔 뒤 잠긴 계정에는 새 코드를 보내지 않는다. + $this->user->forceFill(['locked_until' => now()->addMinutes(10)])->save(); + + $this->postJson('/api/auth/login/two-factor/resend', [ + 'challenge_id' => $challengeId, + ])->assertStatus(423); + } + + /** + * @scenario two_factor=on, controller=admin, actor=admin, resend=active + * + * @effects admin_resend_returns_new_challenge + */ + #[Test] + public function an_admin_can_resend_through_the_admin_endpoint(): void + { + $this->setTwoFactor(true); + $this->makeAdmin($this->user); + + $challengeId = $this->postJson('/api/auth/admin/login', [ + 'email' => 'two-factor@test.com', + 'password' => 'Passw0rd!2fa', + ])->json('data.challenge_id'); + + $response = $this->postJson('/api/auth/admin/login/two-factor/resend', [ + 'challenge_id' => $challengeId, + ]); + + $response->assertStatus(200); + $this->assertNotEmpty($response->json('data.challenge_id')); + $this->assertNotSame($challengeId, $response->json('data.challenge_id')); + $this->assertNull($response->json('data.token')); + // 해석된 사용자는 관리자 등급 판정에만 쓰고 응답에는 실지 않는다. + $this->assertNull($response->json('data.user')); + } + + /** + * @scenario two_factor=on, controller=admin, actor=non_admin, resend=active + * + * @effects admin_resend_refuses_non_admin + */ + #[Test] + public function a_non_admin_cannot_resend_through_the_admin_endpoint(): void + { + $this->setTwoFactor(true); + + $challengeId = $this->postJson('/api/auth/admin/login', [ + 'email' => 'two-factor@test.com', + 'password' => 'Passw0rd!2fa', + ])->json('data.challenge_id'); + + // 완료할 수 없는 상대에게 새 인증번호를 계속 보내지 않는다. + $this->postJson('/api/auth/admin/login/two-factor/resend', [ + 'challenge_id' => $challengeId, + ])->assertStatus(403); + + $this->assertSame(0, $this->user->tokens()->count()); + } + + /** + * 사용자에게 admin 역할을 부여합니다. + * + * @param User $user 대상 사용자 + */ + private function makeAdmin(User $user): void + { + // isAdmin() 은 역할 존재가 아니라 admin 타입 권한 보유로 판정한다 — + // 역할만 만들어 붙이면 관리자로 인정되지 않아 403 을 측정하게 된다. + $this->seed(RolePermissionSeeder::class); + + $role = Role::where('identifier', 'admin')->firstOrFail(); + + $user->roles()->syncWithoutDetaching([$role->id => ['assigned_at' => now(), 'assigned_by' => null]]); + $user->refresh(); + } + /** * challenge 에 알려진 인증 코드를 심고 그 값을 돌려줍니다. * diff --git a/tests/Feature/Console/Commands/ExecuteUpgradeStepsStandaloneTest.php b/tests/Feature/Console/Commands/ExecuteUpgradeStepsStandaloneTest.php index de52bc8b..14346e6a 100644 --- a/tests/Feature/Console/Commands/ExecuteUpgradeStepsStandaloneTest.php +++ b/tests/Feature/Console/Commands/ExecuteUpgradeStepsStandaloneTest.php @@ -165,6 +165,55 @@ class ExecuteUpgradeStepsStandaloneTest extends TestCase $this->assertSame(0, $exitCode); } + /** + * 캐시 정리 블록과 함께 상주 큐 워커 재시작 신호가 나간다 (7.0.11). + * + * 큐 워커는 부팅이 한 번뿐이라 코어 코드·config 를 기동 시점 상태로 물고 있다. 업데이트가 + * 파일을 전부 교체해도 워커는 옛 코드로 잡을 계속 처리하며 그 사실이 오류로 드러나지 않는다. + * + * @effects core_update_step11_signals_queue_restart + */ + public function test_standalone_post_step_signals_queue_restart(): void + { + [$service, $module, $plugin, $template, $langPack] = $this->bindMocks(); + + $service->shouldReceive('runMigrations')->once(); + $service->shouldReceive('reloadCoreConfigAndResync')->once(); + $service->shouldReceive('runUpgradeSteps')->once(); + $service->shouldReceive('updateVersionInEnv')->once(); + $service->shouldReceive('clearAllCaches')->once(); + $service->shouldReceive('signalQueueRestart')->once(); + $service->shouldReceive('collectBundledExtensionUpdates')->once()->andReturn([ + 'modules' => [], 'plugins' => [], 'templates' => [], + ]); + $langPack->shouldReceive('collectBundledLangPackUpdates')->once()->andReturn([]); + + $this->assertSame(0, $this->runCommand([])); + } + + /** + * 캐시 정리를 건너뛰는 경로(spawn 자식)에서는 신호도 보내지 않는다 — 부모가 보낸다. + * + * @effects core_update_step11_signals_queue_restart + */ + public function test_skip_cache_clear_also_skips_queue_restart_signal(): void + { + [$service, $module, $plugin, $template, $langPack] = $this->bindMocks(); + + $service->shouldReceive('runMigrations')->once(); + $service->shouldReceive('reloadCoreConfigAndResync')->once(); + $service->shouldReceive('runUpgradeSteps')->once(); + $service->shouldReceive('updateVersionInEnv')->once(); + $service->shouldNotReceive('clearAllCaches'); + $service->shouldNotReceive('signalQueueRestart'); + $service->shouldReceive('collectBundledExtensionUpdates')->once()->andReturn([ + 'modules' => [], 'plugins' => [], 'templates' => [], + ]); + $langPack->shouldReceive('collectBundledLangPackUpdates')->once()->andReturn([]); + + $this->assertSame(0, $this->runCommand(['--skip-cache-clear' => true])); + } + public function test_skip_bundled_updates_option_bypasses_bundled_prompt(): void { [$service, $module, $plugin, $template, $langPack] = $this->bindMocks(); @@ -457,6 +506,9 @@ class ExecuteUpgradeStepsStandaloneTest extends TestCase // 종료부의 빈 격리 디렉토리 청소(7.0.10 도입)도 모든 경로에서 호출된다 — 전용 케이스가 // once() 로 단언하고, 나머지 케이스는 기본 허용. $service->shouldReceive('sweepEmptyStagingDirectories')->andReturn(0)->byDefault(); + // 캐시 정리 블록이 도는 경로에서는 상주 큐 워커 재시작 신호도 함께 나간다 (7.0.11 도입). + // 전용 케이스가 once()/never() 로 단언하고, 나머지 케이스는 기본 허용. + $service->shouldReceive('signalQueueRestart')->andReturnNull()->byDefault(); $module = Mockery::mock(ModuleManager::class); $plugin = Mockery::mock(PluginManager::class); $template = Mockery::mock(TemplateManager::class); diff --git a/tests/Feature/Console/CoreUpdateCommandSpawnFailureTest.php b/tests/Feature/Console/CoreUpdateCommandSpawnFailureTest.php index 47dd3691..8fbd76e2 100644 --- a/tests/Feature/Console/CoreUpdateCommandSpawnFailureTest.php +++ b/tests/Feature/Console/CoreUpdateCommandSpawnFailureTest.php @@ -39,6 +39,8 @@ class CoreUpdateCommandSpawnFailureTest extends TestCase private ?string $originalEnvVersion = null; + private ?string $originalUpdateFlag = null; + protected function setUp(): void { parent::setUp(); @@ -46,6 +48,12 @@ class CoreUpdateCommandSpawnFailureTest extends TestCase $this->failingStepPath = base_path('upgrades/Upgrade_0_0_1_test_spawn_failure_fail.php'); $this->silentStepPath = base_path('upgrades/Upgrade_0_0_1_test_spawn_failure_silent.php'); + // 업데이트 트리 플래그도 원값을 보관하고 비운다 — env APP_VERSION 우선 판독이 이 플래그로 + // 게이트되므로, 다른 테스트가 남긴 플래그가 있으면 부모/자식 시나리오 구분이 무너진다. + $flag = $_ENV['G7_UPDATE_IN_PROGRESS'] ?? $_SERVER['G7_UPDATE_IN_PROGRESS'] ?? getenv('G7_UPDATE_IN_PROGRESS'); + $this->originalUpdateFlag = ($flag === false || $flag === null) ? null : (string) $flag; + $this->setUpdateFlag(false); + // stale 가드는 spawn 자식이 받는 env APP_VERSION 을 먼저 읽는다 (config 캐시가 있으면 // config('app.version') 은 캐시에 박힌 구버전이라 신뢰할 수 없다 — 7.0.9→7.0.10 실사례). // 각 테스트가 부모/자식 시나리오에 맞춰 env 를 직접 세우도록 원값을 보관하고 비운다. @@ -63,9 +71,36 @@ class CoreUpdateCommandSpawnFailureTest extends TestCase $this->setEnvVersion($this->originalEnvVersion); + if ($this->originalUpdateFlag === null) { + $this->setUpdateFlag(false); + } else { + $_ENV['G7_UPDATE_IN_PROGRESS'] = $this->originalUpdateFlag; + $_SERVER['G7_UPDATE_IN_PROGRESS'] = $this->originalUpdateFlag; + putenv('G7_UPDATE_IN_PROGRESS='.$this->originalUpdateFlag); + } + parent::tearDown(); } + /** + * 업데이트 트리 플래그를 세 채널($_ENV/$_SERVER/putenv)에 세우거나 지웁니다. + * + * @param bool $enabled true 면 플래그 설정, false 면 세 채널 모두 제거 + */ + private function setUpdateFlag(bool $enabled): void + { + if (! $enabled) { + unset($_ENV['G7_UPDATE_IN_PROGRESS'], $_SERVER['G7_UPDATE_IN_PROGRESS']); + putenv('G7_UPDATE_IN_PROGRESS'); + + return; + } + + $_ENV['G7_UPDATE_IN_PROGRESS'] = '1'; + $_SERVER['G7_UPDATE_IN_PROGRESS'] = '1'; + putenv('G7_UPDATE_IN_PROGRESS=1'); + } + /** * 프로세스 env 의 APP_VERSION 을 세우거나(문자열) 비운다(null). * @@ -436,6 +471,10 @@ PHP); public function run_upgrade_steps_spawn_자식은_config_캐시가_stale_해도_env_버전으로_가드를_통과한다(): void { // 자식이 받는 env 는 toVersion, 캐시로 부팅한 config 는 fromVersion. + // 실제 spawn 은 `APP_VERSION` 과 `G7_UPDATE_IN_PROGRESS=1` 을 **함께** 넘긴다 + // (CoreUpdateCommand::spawnUpgradeStepsProcess 의 $env). env 우선 판독은 그 플래그가 + // 그리는 업데이트 트리 안에서만 켜지므로, 자식 시뮬레이션은 둘 다 세워야 실제와 같다. + $this->setUpdateFlag(true); $this->setEnvVersion('7.0.10'); config(['app.version' => '7.0.9']); config(['app.update.spawn_failure_mode' => 'abort']); diff --git a/tests/Feature/Console/CoreUpdateCommandStalePackageManifestTest.php b/tests/Feature/Console/CoreUpdateCommandStalePackageManifestTest.php new file mode 100644 index 00000000..30e1fa64 --- /dev/null +++ b/tests/Feature/Console/CoreUpdateCommandStalePackageManifestTest.php @@ -0,0 +1,393 @@ + 테스트가 덮어쓴 env 의 원값 (3채널 복원용) */ + private array $originalEnv = []; + + /** @var string base_path 상대 — packages.php 캐시 경로 */ + private string $relativePackagesPath; + + /** @var string base_path 상대 — services.php 캐시 경로 */ + private string $relativeServicesPath; + + private string $packagesPath; + + private string $servicesPath; + + protected function setUp(): void + { + parent::setUp(); + + $uniq = uniqid(); + $this->relativePackagesPath = "storage/framework/testing/stale-manifest-{$uniq}/packages.php"; + $this->relativeServicesPath = "storage/framework/testing/stale-manifest-{$uniq}/services.php"; + $this->packagesPath = base_path($this->relativePackagesPath); + $this->servicesPath = base_path($this->relativeServicesPath); + + File::ensureDirectoryExists(dirname($this->packagesPath)); + + $this->setEnv('APP_PACKAGES_CACHE', $this->relativePackagesPath); + $this->setEnv('APP_SERVICES_CACHE', $this->relativeServicesPath); + + // 부모 PHPUnit 프로세스에 다른 테스트가 남긴 플래그가 있으면 대조군(R4)이 무의미해진다. + $this->setEnv('G7_UPDATE_IN_PROGRESS', null); + } + + protected function tearDown(): void + { + foreach ($this->originalEnv as $key => $value) { + if ($value === null) { + unset($_ENV[$key], $_SERVER[$key]); + putenv($key); + } else { + $_ENV[$key] = $value; + $_SERVER[$key] = $value; + putenv($key.'='.$value); + } + } + $this->originalEnv = []; + + $dir = dirname($this->packagesPath); + if (File::isDirectory($dir)) { + File::deleteDirectory($dir); + } + + parent::tearDown(); + } + + /** + * 계층 ①: 부모는 자식이 부팅에 실패하더라도 그 전에 두 매니페스트를 비운다. + * + * 자식을 확실히 실패시키려고 `php_binary` 를 존재하지 않는 경로로 둔다 — 정리는 + * `proc_open` 앞에서 일어나므로 자식의 성패와 무관하게 관측된다. + * + * @effects spawnUpgradeStepsProcess_clears_package_manifests_before_proc_open + */ + #[Test] + public function spawn_직전에_패키지_매니페스트를_비운다_자식이_부팅하지_못해도(): void + { + if (! function_exists('proc_open')) { + $this->markTestSkipped('proc_open 미지원 환경'); + } + + config(['app.update.spawn_failure_mode' => 'abort']); + config(['process.php_binary' => base_path('storage/framework/testing/no-such-php-binary')]); + + $this->writeStaleManifests(); + + $this->assertSame($this->packagesPath, $this->app->getCachedPackagesPath(), '전제: APP_PACKAGES_CACHE 가 경로를 정한다'); + $this->assertFileExists($this->packagesPath, '전제: 부모 시점에 packages.php 가 존재한다'); + $this->assertFileExists($this->servicesPath, '전제: 부모 시점에 services.php 가 존재한다'); + + try { + $this->invokeSpawn(); + $this->fail('자식이 부팅할 수 없으므로 abort 모드에서 UpgradeHandoffException 이 발생해야 한다'); + } catch (UpgradeHandoffException) { + // 기대된 경로 — 아래 단언이 본 검증이다. + } + + $this->assertFileDoesNotExist($this->packagesPath, 'spawn 전에 packages.php 를 비워야 자식이 새 vendor 기준으로 다시 만든다'); + $this->assertFileDoesNotExist($this->servicesPath, 'spawn 전에 services.php 도 함께 비워야 한다'); + } + + /** + * 계층 ①: stale dev 매니페스트가 있어도 자식은 정상 부팅해 스텝 0건을 통과한다. + * + * 계층 ①②가 모두 없으면 자식이 `Class ... not found` 로 죽어 abort 예외가 난다. + * + * @effects spawn_child_boots_with_regenerated_package_manifest_when_parent_left_stale_dev_manifest + */ + #[Test] + public function stale_dev_매니페스트가_있어도_spawn_자식은_정상_부팅해_스텝_0건을_통과한다(): void + { + if (! function_exists('proc_open')) { + $this->markTestSkipped('proc_open 미지원 환경'); + } + + config(['app.update.spawn_failure_mode' => 'abort']); + + $this->writeStaleManifests(); + + $result = $this->invokeSpawn(); + + $this->assertTrue($result, 'stale 매니페스트가 정리되면 자식은 정상 부팅해 스텝 0건으로 성공해야 한다'); + $this->assertStringNotContainsString( + 'NonExistentServiceProvider', + (string) @file_get_contents($this->packagesPath), + '재생성된 매니페스트에 존재하지 않는 provider 가 남아 있으면 안 된다' + ); + } + + /** + * 계층 ②: 업데이트 플래그를 물려받은 자식은 부모가 비우지 않았어도 스스로 비우고 부팅한다. + * + * 7.0.11 미만 부모(이미 배포된 7.0.9·7.0.10)는 spawn 직전에 비우지 않으므로, 그 부모 + * 아래에서 도는 신버전 자식에게는 이것이 유일한 방어다. 부모 역할은 `proc_open` 으로 + * 직접 재현한다 — 정리 없이 stale 매니페스트를 남긴 채 자식을 띄운다. + * + * @effects bootstrap_app_unlinks_stale_package_manifest_when_update_flag_present + */ + #[Test] + public function 업데이트_플래그가_있는_자식은_stale_매니페스트를_스스로_지우고_부팅한다(): void + { + if (! function_exists('proc_open')) { + $this->markTestSkipped('proc_open 미지원 환경'); + } + + $this->writeStaleManifests(); + + [$exitCode, $output] = $this->runArtisanChild(['G7_UPDATE_IN_PROGRESS' => '1']); + + $this->assertSame(0, $exitCode, "업데이트 플래그가 있는 자식은 stale 매니페스트를 스스로 지우고 부팅해야 한다. 출력:\n{$output}"); + $this->assertStringNotContainsString('NonExistentServiceProvider', $output, '자가 치유 후에는 없는 provider 를 참조하지 않는다'); + } + + /** + * 대조군: 업데이트 트리 밖 프로세스(웹 요청·큐 워커·운영자 셸)는 매니페스트를 건드리지 않는다. + * + * 자가 치유가 트리 밖으로 새면 평상시 모든 부팅이 매니페스트를 지웠다 다시 만들게 된다. + * 이 테스트는 계층 ② 도입 전후로 항상 green 이어야 한다. + * + * @effects bootstrap_app_leaves_package_manifest_untouched_without_update_flag + */ + #[Test] + public function 업데이트_플래그가_없는_프로세스는_매니페스트를_건드리지_않는다(): void + { + if (! function_exists('proc_open')) { + $this->markTestSkipped('proc_open 미지원 환경'); + } + + $this->writeStaleManifests(); + $before = md5_file($this->packagesPath); + + [$exitCode, $output] = $this->runArtisanChild(['G7_UPDATE_IN_PROGRESS' => null]); + + $this->assertNotSame(0, $exitCode, '플래그 없는 자식은 stale 매니페스트 그대로 부팅해 실패해야 한다 (자가 치유가 트리 밖으로 새지 않았다는 증거)'); + $this->assertStringContainsString('NonExistentServiceProvider', $output, '실패 원인이 stale provider 여야 한다'); + $this->assertFileExists($this->packagesPath, '트리 밖 프로세스는 매니페스트를 지우지 않는다'); + $this->assertSame($before, md5_file($this->packagesPath), '매니페스트 내용이 변경되지 않아야 한다'); + } + + /** + * 계층 ①: spawn 직전에 매니페스트를 지우지 못하면 그 파일을 업그레이드 로그에 남긴다. + * + * 권한·소유권 불일치로 삭제가 막히면 자식의 자가 치유도 같은 권한으로 같은 이유로 실패해 + * 증상은 이전 설치본 provider 의 「Class not found」 그대로다 — 이 경고가 원인이 권한이라는 + * 유일한 흔적이다. 삭제 실패는 실제로 만든다(Windows: 열린 핸들 / POSIX: 부모 디렉토리 쓰기 + * 권한 제거). 자식은 존재하지 않는 php 바이너리로 부팅 불가하게 두어 `proc_open` 앞의 동작만 + * 관측한다. + * + * @effects spawnUpgradeStepsProcess_logs_manifest_files_it_could_not_remove + */ + #[Test] + public function spawn_직전_매니페스트를_지우지_못하면_업그레이드_로그에_남은_파일을_경고한다(): void + { + if (! function_exists('proc_open')) { + $this->markTestSkipped('proc_open 미지원 환경'); + } + + config(['app.update.spawn_failure_mode' => 'abort']); + config(['process.php_binary' => base_path('storage/framework/testing/no-such-php-binary')]); + + $this->writeStaleManifests(); + + $release = $this->makeUndeletable($this->packagesPath); + $logged = []; + + try { + try { + $this->invokeSpawn(function (string $line) use (&$logged): void { + $logged[] = $line; + }); + $this->fail('자식이 부팅할 수 없으므로 abort 모드에서 UpgradeHandoffException 이 발생해야 한다'); + } catch (UpgradeHandoffException) { + // 기대된 경로 — 아래 단언이 본 검증이다. + } + } finally { + $release(); + } + + $this->assertFileExists($this->packagesPath, '삭제가 막힌 packages.php 는 남는다'); + $this->assertFileDoesNotExist($this->servicesPath, '지울 수 있는 services.php 는 지운다'); + + $warnings = array_values(array_filter($logged, static fn (string $line): bool => str_contains($line, '패키지 매니페스트 삭제 실패'))); + $this->assertCount(1, $warnings, '지우지 못한 파일이 있으면 업그레이드 로그에 경고 한 줄을 남긴다'); + $this->assertStringContainsString($this->packagesPath, $warnings[0], '경고는 지우지 못한 파일의 경로를 지목한다'); + $this->assertStringNotContainsString($this->servicesPath, $warnings[0], '지운 파일은 경고에 싣지 않는다'); + } + + /** + * 파일을 현재 프로세스가 삭제할 수 없는 상태로 만들고, 되돌리는 클로저를 반환합니다. + * + * Windows 는 읽기 전용 속성(0444)이 삭제를 막고(PHP 7.3+ 는 파일을 `FILE_SHARE_DELETE` 로 열어 + * 열린 핸들로는 막히지 않는다 — 실측), POSIX 는 부모 디렉토리의 쓰기 권한이 삭제를 막는다(root 는 + * 권한을 우회하므로 이 방법으로 실패를 만들 수 없다). + * + * @param string $path 삭제를 막을 파일 + * @return \Closure 원상 복구 클로저 + */ + private function makeUndeletable(string $path): \Closure + { + if (PHP_OS_FAMILY === 'Windows') { + $this->assertTrue(chmod($path, 0444), '전제: 읽기 전용 속성을 건다'); + + return static function () use ($path): void { + if (is_file($path)) { + chmod($path, 0644); + } + }; + } + + if (function_exists('posix_geteuid') && posix_geteuid() === 0) { + $this->markTestSkipped('root 는 디렉토리 권한으로 삭제 실패를 만들 수 없다 — 비-root 로 실행해야 이 축이 측정된다'); + } + + $dir = dirname($path); + $this->assertTrue(chmod($dir, 0555), '전제: 부모 디렉토리의 쓰기 권한을 뺀다'); + + return static function () use ($dir): void { + chmod($dir, 0755); + }; + } + + /** + * 존재하지 않는 provider 를 등재한 stale 매니페스트 두 개를 만든다. + */ + private function writeStaleManifests(): void + { + File::ensureDirectoryExists(dirname($this->packagesPath)); + + File::put($this->packagesPath, " ['providers' => ['Tests\\\\Fixtures\\\\Stale\\\\NonExistentServiceProvider']]];\n"); + File::put($this->servicesPath, " ['Tests\\\\Fixtures\\\\Stale\\\\NonExistentServiceProvider'], 'eager' => ['Tests\\\\Fixtures\\\\Stale\\\\NonExistentServiceProvider'], 'deferred' => [], 'when' => []];\n"); + + clearstatcache(); + } + + /** + * `spawnUpgradeStepsProcess` 를 리플렉션으로 호출합니다. + * + * @param \Closure|null $log 업그레이드 로그 클로저. 생략하면 버린다 + * @return bool spawn 성공 여부 + */ + private function invokeSpawn(?\Closure $log = null): bool + { + $command = app(CoreUpdateCommand::class); + + $input = new ArrayInput([]); + $output = new BufferedOutput; + $style = new OutputStyle($input, $output); + + $reflection = new \ReflectionClass($command); + $property = $reflection->getProperty('output'); + $property->setAccessible(true); + $property->setValue($command, $style); + + if ($reflection->hasProperty('input')) { + $inputProp = $reflection->getProperty('input'); + $inputProp->setAccessible(true); + $inputProp->setValue($command, $input); + } + + $method = new \ReflectionMethod(CoreUpdateCommand::class, 'spawnUpgradeStepsProcess'); + $method->setAccessible(true); + + return (bool) $method->invoke($command, '9.9.8', '9.9.9', true, $log ?? fn () => null); + } + + /** + * `php artisan --version` 을 별도 프로세스로 실행합니다. + * + * 운영 코드(`spawnUpgradeStepsProcess`)와 동형으로 `getenv()` 기반 env 합집합을 넘긴다 — + * `variables_order` 에 `E` 가 없으면 `$_ENV` 만으로는 자식이 캐시 경로 재지정을 못 받는다. + * + * @param array $overrides 덮어쓸 env (null 이면 자식 env 에서 제거) + * @return array{0: int, 1: string} 종료 코드와 stdout+stderr 합본 + */ + private function runArtisanChild(array $overrides): array + { + $env = array_merge(getenv(), $_ENV); + foreach ($overrides as $key => $value) { + if ($value === null) { + unset($env[$key]); + } else { + $env[$key] = $value; + } + } + + $commandLine = sprintf('%s %s --version', escapeshellarg(PHP_BINARY), escapeshellarg(base_path('artisan'))); + + $descriptors = [0 => ['pipe', 'r'], 1 => ['pipe', 'w'], 2 => ['pipe', 'w']]; + $process = proc_open($commandLine, $descriptors, $pipes, base_path(), $env); + + if (! is_resource($process)) { + $this->fail('proc_open 자원 생성 실패'); + } + + fclose($pipes[0]); + $out = (string) stream_get_contents($pipes[1]); + $err = (string) stream_get_contents($pipes[2]); + fclose($pipes[1]); + fclose($pipes[2]); + + return [proc_close($process), $out."\n".$err]; + } + + /** + * env 를 세 채널($_ENV/$_SERVER/putenv)에 세우고 원값을 복원용으로 보관합니다. + * + * @param string $key 환경변수 이름 + * @param string|null $value 세울 값. null 이면 제거 + */ + private function setEnv(string $key, ?string $value): void + { + if (! array_key_exists($key, $this->originalEnv)) { + $current = $_ENV[$key] ?? $_SERVER[$key] ?? getenv($key); + $this->originalEnv[$key] = ($current === false || $current === null) ? null : (string) $current; + } + + if ($value === null) { + unset($_ENV[$key], $_SERVER[$key]); + putenv($key); + + return; + } + + $_ENV[$key] = $value; + $_SERVER[$key] = $value; + putenv($key.'='.$value); + } +} diff --git a/tests/Feature/Console/Vendor/BuildVendorBundleCommandTest.php b/tests/Feature/Console/Vendor/BuildVendorBundleCommandTest.php index d189834a..7202a080 100644 --- a/tests/Feature/Console/Vendor/BuildVendorBundleCommandTest.php +++ b/tests/Feature/Console/Vendor/BuildVendorBundleCommandTest.php @@ -217,6 +217,63 @@ class BuildVendorBundleCommandTest extends TestCase ])->assertExitCode(1); } + /** + * 미설치 확장(활성 디렉토리 없음)도 --check 는 _bundled 의 composer.json 을 기준으로 판정한다. + * + * 종전에는 활성 디렉토리 부재를 "소스 경로 없음" 으로 보고하고 판정을 건너뛰어, _bundled 에 + * 외부 의존성이 선언됐는데 번들이 없거나 stale 인 상태가 --check 를 통과했다. + */ + public function test_module_vendor_bundle_check_judges_bundled_when_extension_not_installed(): void + { + $identifier = 'test-uninstalled-'.uniqid(); + $bundledPath = base_path('modules/_bundled/'.$identifier); + File::ensureDirectoryExists($bundledPath); + File::put($bundledPath.'/composer.json', json_encode([ + 'name' => 'test/uninstalled', + 'require' => ['php' => '^8.2', 'test/lib' => '^1.0'], + ])); + + try { + $this->assertDirectoryDoesNotExist(base_path('modules/'.$identifier)); + + $this->artisan('module:vendor-bundle', [ + 'identifier' => $identifier, + '--check' => true, + ]) + ->doesntExpectOutputToContain('소스 경로 없음') + ->expectsOutputToContain('STALE') + ->assertExitCode(1); + } finally { + File::deleteDirectory($bundledPath); + } + } + + /** + * 미설치 + 외부 의존성 없음은 종전과 같이 SKIPPED (번들 대상 아님). + */ + public function test_module_vendor_bundle_check_skips_uninstalled_extension_without_external_dependencies(): void + { + $identifier = 'test-uninstalled-'.uniqid(); + $bundledPath = base_path('modules/_bundled/'.$identifier); + File::ensureDirectoryExists($bundledPath); + File::put($bundledPath.'/composer.json', json_encode([ + 'name' => 'test/uninstalled-plain', + 'require' => ['php' => '^8.2'], + ])); + + try { + $this->artisan('module:vendor-bundle', [ + 'identifier' => $identifier, + '--check' => true, + ]) + ->doesntExpectOutputToContain('소스 경로 없음') + ->expectsOutputToContain('SKIPPED (외부 composer 의존성 없음)') + ->assertExitCode(0); + } finally { + File::deleteDirectory($bundledPath); + } + } + public function test_module_vendor_bundle_check_reports_up_to_date_after_build(): void { $this->artisan('module:vendor-bundle', ['identifier' => $this->fakeModuleIdentifier]) diff --git a/tests/Feature/Database/ModifyUrlInSeoCacheStatsMigrationTest.php b/tests/Feature/Database/ModifyUrlInSeoCacheStatsMigrationTest.php new file mode 100644 index 00000000..b9c1016c --- /dev/null +++ b/tests/Feature/Database/ModifyUrlInSeoCacheStatsMigrationTest.php @@ -0,0 +1,97 @@ +assertSame(768, $this->urlLength()); + + Artisan::call('migrate:rollback', ['--path' => [self::MIGRATION], '--force' => true]); + $this->assertSame(255, $this->urlLength()); + + Artisan::call('migrate', ['--path' => [self::MIGRATION], '--force' => true]); + $this->assertSame(768, $this->urlLength()); + + $this->assertContains( + 'idx_seo_cache_stats_url', + array_column(Schema::getIndexes('seo_cache_stats'), 'name'), + 'url 인덱스는 컬럼 변경 뒤에도 남아야 한다' + ); + } + + /** + * down 은 255자를 넘는 행을 먼저 지운다 — 엄격 모드에서는 잘리는 값이 하나라도 있으면 + * ALTER 자체가 실패해 되돌리기가 막힌다. + * + * @scenario bot_state=bot, cache_state=hit, ip_budget=within, query_shape=normal, store_state=under_caps + * + * @effects stats_url_column_fits_normalized_cache_url + */ + public function test_down_removes_rows_that_would_not_fit_before_shrinking(): void + { + $long = '/shop?'.str_repeat('a=0123456789&', 30); + + $this->assertGreaterThan(255, strlen($long)); + + DB::table('seo_cache_stats')->insert([ + ['url' => $long, 'locale' => 'ko', 'type' => 'hit'], + ['url' => '/short', 'locale' => 'ko', 'type' => 'hit'], + ]); + + try { + Artisan::call('migrate:rollback', ['--path' => [self::MIGRATION], '--force' => true]); + + $this->assertSame(255, $this->urlLength()); + $this->assertDatabaseMissing('seo_cache_stats', ['url' => $long]); + $this->assertDatabaseHas('seo_cache_stats', ['url' => '/short']); + } finally { + // DDL 이 테스트 트랜잭션을 암묵 커밋하므로 남는 행은 직접 치운다. + Artisan::call('migrate', ['--path' => [self::MIGRATION], '--force' => true]); + DB::table('seo_cache_stats')->where('url', '/short')->delete(); + } + } + + /** + * url 컬럼의 선언 길이를 읽습니다. + * + * @return int 문자 수 (varchar(N) 의 N) + */ + private function urlLength(): int + { + foreach (Schema::getColumns('seo_cache_stats') as $column) { + if ($column['name'] === 'url') { + preg_match('/\((\d+)\)/', (string) $column['type'], $matches); + + return (int) ($matches[1] ?? 0); + } + } + + $this->fail('seo_cache_stats.url 컬럼이 없다'); + } +} diff --git a/tests/Feature/DevTools/DevtoolsDebugGateTest.php b/tests/Feature/DevTools/DevtoolsDebugGateTest.php index b17d0cd0..7625658d 100644 --- a/tests/Feature/DevTools/DevtoolsDebugGateTest.php +++ b/tests/Feature/DevTools/DevtoolsDebugGateTest.php @@ -229,7 +229,7 @@ class DevtoolsDebugGateTest extends TestCase } /** - * GET 4종은 디버그 ON 에서 정상 응답한다 (게이트만 걸고 기능은 유지 — PO 결정). + * GET 4종은 디버그 ON 에서 정상 응답한다 (게이트만 걸고 기능은 유지 — 설계 결정). */ public function test_get_endpoints_respond_when_debug_enabled(): void { diff --git a/tests/Feature/Documentation/ExtensionDocContractTest.php b/tests/Feature/Documentation/ExtensionDocContractTest.php index 199e28ea..6256fd33 100644 --- a/tests/Feature/Documentation/ExtensionDocContractTest.php +++ b/tests/Feature/Documentation/ExtensionDocContractTest.php @@ -1043,7 +1043,7 @@ class ExtensionDocContractTest extends TestCase /** * 확장 README 의 첫 화면이 이미지 배지가 아니라 H1 제목인지 단언합니다. * - * PO 결정(2026-08-31): 확장 README 상단의 확장명은 shields.io 이미지 배지가 아니라 평범한 + * 설계 결정(2026-08-31): 확장 README 상단의 확장명은 shields.io 이미지 배지가 아니라 평범한 * H1 마크다운 제목이다. 확장은 20개가 병렬로 존재하는 대등한 구성요소이고, 각자가 코어와 * 같은 히어로 브랜딩을 받으면 "이 확장이 곧 독립 프로젝트" 라는 착시를 준다. * @@ -1146,7 +1146,7 @@ class ExtensionDocContractTest extends TestCase /** * 진입 문서 3종의 제목이 「그누보드7 {확장명} {유형}」 을 담는지 단언합니다. * - * PO 결정(2026-09-04): 확장 README 제목이 확장명뿐(`# 게시판`)이면 그 문서만 연 사람이 + * 설계 결정(2026-09-04): 확장 README 제목이 확장명뿐(`# 게시판`)이면 그 문서만 연 사람이 * 이것이 그누보드7의 확장인지, 모듈인지 템플릿인지 알 수 없다. README · AGENTS.md · * docs/README.md 는 제3자가 처음 여는 진입 문서이므로 셋 다 같은 제목을 쓴다. * diff --git a/tests/Feature/Extension/ExtensionAssetCssRewriteContractTest.php b/tests/Feature/Extension/ExtensionAssetCssRewriteContractTest.php index d5c06a94..cf5ba967 100644 --- a/tests/Feature/Extension/ExtensionAssetCssRewriteContractTest.php +++ b/tests/Feature/Extension/ExtensionAssetCssRewriteContractTest.php @@ -111,12 +111,13 @@ class ExtensionAssetCssRewriteContractTest extends TestCase '병합 CSS 번들 라우트가 하나도 잡히지 않았습니다 — 이 테스트가 공허하게 통과하고 있습니다.' ); - $source = $this->methodSource(ExtensionBundleService::class, 'buildCssBundle'); + // 병합 루프는 mergeCss() 에 있다 — buildCssBundle()/buildBundleContent()/캐시 경로가 모두 이 루프를 거친다. + $source = $this->methodSource(ExtensionBundleService::class, 'mergeCss'); $this->assertStringContainsString( self::REWRITER, $source, - 'ExtensionBundleService::buildCssBundle() 이 '.self::REWRITER.' 를 거치지 않습니다. ' + 'ExtensionBundleService::mergeCss() 이 '.self::REWRITER.' 를 거치지 않습니다. ' .'개별 자산 서빙과 병합 번들이 서로 다른 규칙을 쓰면 한쪽만 고쳐진 채 남습니다.' ); } diff --git a/tests/Feature/Identity/IdentityLoginPurposeGateTest.php b/tests/Feature/Identity/IdentityLoginPurposeGateTest.php new file mode 100644 index 00000000..b44a3faa --- /dev/null +++ b/tests/Feature/Identity/IdentityLoginPurposeGateTest.php @@ -0,0 +1,162 @@ +seed(RolePermissionSeeder::class); + $this->seed(IdentityMessageDefinitionSeeder::class); + + $this->user = User::factory()->create([ + 'email' => 'purpose-gate@test.com', + 'password' => Hash::make('Passw0rd!2fa'), + 'status' => UserStatus::Active->value, + ]); + + config(['g7_settings.core.security.two_factor_auth' => true]); + } + + /** + * @scenario two_factor=on, controller=user, code=consumed_by_public_verify + * + * @effects public_verify_rejects_login_purpose + */ + #[Test] + public function the_public_verify_endpoint_refuses_a_login_challenge(): void + { + $challengeId = $this->startLoginChallenge(); + + $response = $this->postJson("/api/identity/challenges/{$challengeId}/verify", [ + 'code' => $this->issuedCode($challengeId), + ]); + + $response->assertStatus(403); + $this->assertSame('PURPOSE_NOT_ALLOWED', $response->json('errors.failure_code')); + } + + /** + * @scenario two_factor=on, controller=user, code=consumed_by_public_verify + * + * @effects public_cancel_rejects_login_purpose + */ + #[Test] + public function the_public_cancel_endpoint_refuses_a_login_challenge(): void + { + $challengeId = $this->startLoginChallenge(); + + $this->postJson("/api/identity/challenges/{$challengeId}/cancel") + ->assertStatus(403); + } + + /** + * @scenario two_factor=on, controller=user, code=valid + * + * @effects login_completes_after_public_endpoints_refuse + */ + #[Test] + public function a_login_challenge_still_completes_after_the_public_endpoints_refuse_it(): void + { + $challengeId = $this->startLoginChallenge(); + $code = $this->issuedCode($challengeId); + + $this->postJson("/api/identity/challenges/{$challengeId}/verify", ['code' => $code]); + $this->postJson("/api/identity/challenges/{$challengeId}/cancel"); + + // 게이트가 상태를 전혀 바꾸지 않아야 로그인이 그대로 완료된다. + $response = $this->postJson('/api/auth/login/two-factor', [ + 'challenge_id' => $challengeId, + 'code' => $code, + ]); + + $response->assertStatus(200); + $this->assertNotEmpty($response->json('data.token')); + } + + /** + * @scenario two_factor=on, controller=user, code=valid + * + * @effects signup_purpose_unaffected_by_login_gate + */ + #[Test] + public function a_signup_challenge_is_unaffected_by_the_gate(): void + { + // 대조군 — 게이트가 로그인 이외 목적까지 막으면 가입·비밀번호 재설정 화면이 통째로 멈춘다. + $challenge = app(IdentityVerificationService::class)->start( + IdentityVerificationPurpose::Signup->value, + $this->user, + ['origin_type' => 'route', 'origin_identifier' => 'test'] + ); + + $response = $this->postJson("/api/identity/challenges/{$challenge->id}/verify", [ + 'code' => $this->issuedCode($challenge->id), + ]); + + $this->assertNotSame(403, $response->getStatusCode(), '로그인 이외 목적까지 막으면 가입 흐름이 멈춥니다.'); + } + + /** + * 로그인 2단계 인증 challenge 를 발급하고 그 id 를 돌려줍니다. + * + * @return string challenge UUID + */ + private function startLoginChallenge(): string + { + return $this->postJson('/api/auth/login', [ + 'email' => 'purpose-gate@test.com', + 'password' => 'Passw0rd!2fa', + ])->json('data.challenge_id'); + } + + /** + * challenge 에 알려진 인증 코드를 심고 그 값을 돌려줍니다. + * + * @param string $challengeId challenge UUID + * @return string 심어 둔 인증 코드 + */ + private function issuedCode(string $challengeId): string + { + $code = '135790'; + + $log = IdentityVerificationLog::find($challengeId); + $metadata = $log->metadata ?? []; + $metadata['code_hash'] = Hash::make($code); + + $log->metadata = $metadata; + $log->save(); + + return $code; + } +} diff --git a/tests/Feature/Installer/DevVendorDetectionTest.php b/tests/Feature/Installer/DevVendorDetectionTest.php new file mode 100644 index 00000000..b2592bda --- /dev/null +++ b/tests/Feature/Installer/DevVendorDetectionTest.php @@ -0,0 +1,325 @@ +testBase = storage_path('app/test-dev-vendor-'.uniqid()); + $this->vendorPath = $this->testBase.'/vendor'; + File::ensureDirectoryExists($this->vendorPath.'/composer'); + + require_once base_path('public/install/includes/vendor-bundle-installer.php'); + } + + protected function tearDown(): void + { + if (File::isDirectory($this->testBase)) { + File::deleteDirectory($this->testBase); + } + + parent::tearDown(); + } + + /** + * dev 설치본을 감지한다. + * + * @effects installer_detects_dev_vendor_from_installed_json + */ + #[Test] + public function detect_dev_vendor_install_은_dev_설치본을_감지한다(): void + { + $this->writeInstalledJson(['packages' => [], 'dev' => true, 'dev-package-names' => ['laravel/boost', 'laravel/mcp']]); + + $info = detectDevVendorInstall($this->testBase); + + $this->assertTrue($info['dev']); + $this->assertSame(['laravel/boost', 'laravel/mcp'], $info['packages']); + } + + /** + * `--no-dev` 설치본은 경고 대상이 아니다. + */ + #[Test] + public function detect_dev_vendor_install_은_운영용_설치본을_경고하지_않는다(): void + { + $this->writeInstalledJson(['packages' => [], 'dev' => false, 'dev-package-names' => []]); + + $info = detectDevVendorInstall($this->testBase); + + $this->assertFalse($info['dev']); + $this->assertSame([], $info['packages']); + } + + /** + * installed.json 이 없으면 판정 불가(null) — 경고도 차단도 하지 않는다. + * + * @effects installer_treats_missing_installed_json_as_unknown + */ + #[Test] + public function detect_dev_vendor_install_은_installed_json_부재를_판정_불가로_다룬다(): void + { + $info = detectDevVendorInstall($this->testBase); + + $this->assertNull($info['dev']); + $this->assertSame([], $info['packages']); + } + + /** + * 인스톨러 shim 과 코어 판정기가 같은 답을 낸다 — 갈라지면 화면과 로그가 어긋난다. + * + * @effects installer_shim_matches_core_composer_install_info + */ + #[Test] + public function 인스톨러_shim_과_코어_판정기의_결과가_같다(): void + { + foreach ([ + ['packages' => [], 'dev' => true, 'dev-package-names' => ['laravel/mcp']], + ['packages' => [], 'dev' => false, 'dev-package-names' => []], + ] as $payload) { + $this->writeInstalledJson($payload); + + $this->assertSame( + ComposerInstallInfo::inspect($this->vendorPath), + detectDevVendorInstall($this->testBase), + 'shim 은 코어 판정기에 위임하기만 해야 한다' + ); + } + } + + /** + * 재사용 분기가 dev 를 감지해 경고하되 설치를 계속한다 (소스 계약). + * + * 감지·경고·"설치 계속" 셋이 한 분기 안에 있어야 한다 — 차단으로 바뀌면 설계 결정(D1) 위반이다. + * + * @effects installer_warns_and_keeps_dev_vendor + */ + #[Test] + public function 재사용_분기가_dev_를_감지해_경고하고_설치를_계속한다(): void + { + $source = File::get(base_path('public/install/includes/task-runner.php')); + + $this->assertStringContainsString('detectDevVendorInstall(BASE_PATH)', $source, '재사용 분기가 dev 판정을 호출해야 한다'); + $this->assertStringContainsString('log_composer_dev_packages_detected', $source, '감지 사실을 설치 로그에 남겨야 한다'); + $this->assertStringContainsString('warning_composer_dev_packages_kept', $source, '정리 명령 안내 경고를 남겨야 한다'); + + $branch = $this->reuseBranchSource($source); + $this->assertStringContainsString("return ['success' => true]", $branch, '경고는 설치를 차단하지 않는다 (D1)'); + } + + /** + * 재사용 경로도 컴파일 캐시를 정리하고, 그 정리가 완료 표시보다 앞선다 (소스 계약). + * + * 종전에는 정리 없이 return 해, `key_generate` 가 이미 완료된 재개 설치에서는 어디서도 + * 정리되지 않았다. + * + * @effects installer_clears_compiled_cache_on_reuse_path + */ + #[Test] + public function 재사용_분기가_컴파일_캐시를_정리하고_완료_표시보다_앞선다(): void + { + $branch = $this->reuseBranchSource(File::get(base_path('public/install/includes/task-runner.php'))); + + $clearPos = strpos($branch, 'clearLaravelCompiledCache(BASE_PATH)'); + $completePos = strpos($branch, "markTaskCompleted('composer_install')"); + + $this->assertNotFalse($clearPos, '재사용 분기가 컴파일 캐시를 정리해야 한다'); + $this->assertNotFalse($completePos, '전제: 재사용 분기가 작업 완료를 표시한다'); + $this->assertLessThan($completePos, $clearPos, '캐시 정리는 완료 표시보다 앞서야 한다'); + } + + /** + * 신규 언어 키가 ko/en 양쪽에 있고 치환 자리가 보존되어 있다. + * + * 한쪽만 있으면 그 로케일에서 키 문자열이 그대로 화면에 노출되고, 치환 자리가 빠지면 + * 건수·패키지 목록이 사라진 문장이 나간다 — 둘 다 오류 없이 통과한다. + * + * @effects installer_lang_keys_exist_in_both_locales + */ + #[Test] + public function 신규_언어_키가_ko_와_en_양쪽에_있고_치환_자리를_보존한다(): void + { + $ko = require base_path('public/install/lang/ko.php'); + $en = require base_path('public/install/lang/en.php'); + + $keys = [ + 'vendor_dev_packages', + 'vendor_dev_packages_none_short', + 'vendor_dev_packages_none', + 'vendor_dev_packages_detected_short', + 'vendor_dev_packages_detected_warning', + 'vendor_dev_packages_unknown', + 'vendor_dev_packages_no_vendor', + 'log_composer_dev_packages_detected', + 'warning_composer_dev_packages_kept', + ]; + + foreach ($keys as $key) { + $this->assertArrayHasKey($key, $ko, "ko 에 {$key} 가 있어야 한다"); + $this->assertArrayHasKey($key, $en, "en 에 {$key} 가 있어야 한다"); + $this->assertNotSame('', trim((string) $ko[$key]), "ko 의 {$key} 는 비어 있으면 안 된다"); + $this->assertNotSame('', trim((string) $en[$key]), "en 의 {$key} 는 비어 있으면 안 된다"); + } + + foreach (['vendor_dev_packages_detected_short', 'vendor_dev_packages_detected_warning'] as $key) { + $this->assertStringContainsString(':count', $ko[$key], "ko 의 {$key} 는 :count 치환 자리를 가져야 한다"); + $this->assertStringContainsString(':count', $en[$key], "en 의 {$key} 는 :count 치환 자리를 가져야 한다"); + } + + foreach ([':count', ':packages'] as $placeholder) { + $this->assertStringContainsString($placeholder, $ko['log_composer_dev_packages_detected']); + $this->assertStringContainsString($placeholder, $en['log_composer_dev_packages_detected']); + } + } + + /** + * `installComposerDependenciesSSE()` 안의 vendor 재사용 분기 본문을 잘라냅니다. + * + * @param string $source task-runner.php 전문 + * @return string 재사용 분기 블록 + */ + private function reuseBranchSource(string $source): string + { + $start = strpos($source, 'if ($vendorExists && $lockExists) {'); + $this->assertNotFalse($start, '전제: vendor 재사용 분기가 존재한다'); + + $end = strpos($source, 'if ($vendorExists && ! $lockExists) {', $start); + $this->assertNotFalse($end, '전제: 재사용 분기 다음에 lock 부재 분기가 온다'); + + return substr($source, $start, $end - $start); + } + + /** + * 인스톨러·코어가 다루는 composer install 명령은 전부 `--no-dev` 를 갖는다. + * + * 실행되는 명령만이 아니라 **실패 시 운영자에게 안내하는 명령**도 대상이다. 안내를 옵션 없이 + * 따라 하면 개발용(require-dev) 패키지가 섞인 vendor 가 만들어지고, 그 상태는 이후 코어 + * 업데이트가 vendor 를 교체할 때 이전 패키지 매니페스트와 어긋나 부팅을 깨뜨린다 — 이 저장소가 + * 막으려는 바로 그 상태다. 실제로 `rollback-functions.php` 의 안내가 그렇게 어긋나 있었다. + * + * 모집단은 소스에서 정규식으로 파생한다 — **파일 목록도 파생 대상이다.** 검사할 파일을 손으로 + * 열거하면 네 번째 파일이 안내 명령을 갖게 돼도 케이스 없이 통과한다(그 형태가 이 결함을 + * 처음 놓친 원인이었다: 리터럴 `"composer install"` 만 보는 파생식이 `{$composer} install` 을 + * 못 봤다). 그래서 디렉토리 트리를 훑어 매칭되는 파일 자체를 찾는다. 파생이 비면 그 자체로 + * 실패시켜 "검사했으나 0건" 과 "보지 않음" 을 구분한다. + * + * @effects installer_composer_commands_all_carry_no_dev + */ + #[Test] + public function 안내하는_것까지_포함해_모든_composer_install_명령이_no_dev_를_갖는다(): void + { + $found = []; + + foreach ($this->phpSourcesUnder(['public/install', 'app', 'upgrades', 'bootstrap']) as $relative) { + $source = File::get(base_path($relative)); + + preg_match_all( + '/\$\{?([A-Za-z_][A-Za-z0-9_]*)\}?[^\n]{0,15}install [^\n\'"]*/', + $source, + $matches, + PREG_SET_ORDER + ); + + foreach ($matches as $match) { + // 변수명에 composer 가 들어간 것만 — 정규식 안에서 걸러내면 첫 문자 소비 때문에 + // `$composer` 자신이 매칭에서 빠진다. + if (stripos($match[1], 'composer') === false) { + continue; + } + + $found[] = [$relative, $match[0]]; + } + } + + $this->assertNotEmpty( + $found, + 'composer install 명령을 하나도 찾지 못했다 — 파생식이 소스 형태를 따라가지 못하면 이 검사는 공허하게 통과한다' + ); + + foreach ($found as [$relative, $command]) { + $this->assertStringContainsString( + '--no-dev', + $command, + "{$relative} 의 composer install 명령에 --no-dev 가 있어야 한다: {$command}" + ); + } + } + + /** + * 주어진 디렉토리 트리 아래의 PHP 소스 파일 경로를 저장소에서 파생합니다. + * + * 검사 대상 파일을 손으로 열거하지 않기 위한 것이다 — 새 파일이 composer 명령을 갖게 되면 + * 목록을 고치지 않아도 모집단에 들어온다. `vendor/` 는 제3자 코드라 제외한다. + * + * @param array $roots base_path 기준 상대 디렉토리 + * @return array base_path 기준 상대 파일 경로 (POSIX 구분자) + */ + private function phpSourcesUnder(array $roots): array + { + $files = []; + + foreach ($roots as $root) { + $absoluteRoot = base_path($root); + if (! is_dir($absoluteRoot)) { + continue; + } + + $iterator = new \RecursiveIteratorIterator( + new \RecursiveDirectoryIterator($absoluteRoot, \FilesystemIterator::SKIP_DOTS) + ); + + foreach ($iterator as $file) { + if (! $file->isFile() || strtolower($file->getExtension()) !== 'php') { + continue; + } + + $relative = str_replace('\\', '/', substr($file->getPathname(), strlen(base_path()) + 1)); + + if (str_contains($relative, '/vendor/')) { + continue; + } + + $files[] = $relative; + } + } + + sort($files); + + return $files; + } + + /** + * installed.json 픽스처를 씁니다. + * + * @param array $payload JSON 으로 직렬화할 내용 + */ + private function writeInstalledJson(array $payload): void + { + File::put($this->vendorPath.'/'.ComposerInstallInfo::INSTALLED_JSON, json_encode($payload)); + } +} diff --git a/tests/Feature/Rules/NoExternalUrlsTest.php b/tests/Feature/Rules/NoExternalUrlsTest.php index e6e3d063..b23c8c30 100644 --- a/tests/Feature/Rules/NoExternalUrlsTest.php +++ b/tests/Feature/Rules/NoExternalUrlsTest.php @@ -7,6 +7,7 @@ use App\Http\Requests\Layout\UpdateLayoutContentRequest; use App\Http\Requests\Layout\UpdateLayoutExtensionContentRequest; use App\Http\Requests\Layout\UpdateLayoutRequest; use App\Rules\NoExternalUrls; +use Illuminate\Support\Facades\Config; use Tests\TestCase; class NoExternalUrlsTest extends TestCase @@ -495,6 +496,160 @@ class NoExternalUrlsTest extends TestCase // 트리에 이 규칙이 붙어 있지 않으면, init_actions·props·actions 의 외부 URL 차단이 // 그 경로에서만 조용히 발화하지 않는다. + // ───────────────────────────────────────────────────────────────────────── + // 사이트 자산 host — 서버가 스스로 발급한 주소는 외부가 아니다 + // + // 레이아웃 첨부 API 가 돌려준 URL(프록시 경로 또는 운영자가 선언한 공개 자산 + // 디스크의 직접 URL)을 image 위젯이 props 에 넣는데, 그 값을 이 규칙이 "외부" 로 + // 차단하면 업로드 → 저장이 422 로 끝난다. 배경 이미지는 style 로 들어가 스캔되지 + // 않아 같은 URL 이 통과했으므로, 증상은 로고(propValue) 에서만 나타났다. + // ───────────────────────────────────────────────────────────────────────── + + /** + * props.logo 하나를 가진 최소 레이아웃을 만듭니다. + * + * @param string $url logo 값 + * @return array + */ + private function layoutWithLogo(string $url): array + { + return [ + 'version' => '1.0.0', + 'layout_name' => 'test', + 'components' => [ + [ + 'id' => 'header', + 'type' => 'composite', + 'name' => 'Header', + 'props' => ['logo' => $url], + 'children' => [], + ], + ], + ]; + } + + /** + * 규칙 통과 여부를 돌려줍니다. + * + * @param array $layout 검사 대상 + * @return string|null 실패 메시지 (통과면 null) + */ + private function failureOf(array $layout): ?string + { + $message = null; + $this->rule->validate('content', $layout, function ($m) use (&$message) { + $message = (string) $m; + }); + + return $message; + } + + /** + * 사이트 자기 host(app.url)의 절대 URL 은 통과해야 합니다 — 프록시 서빙 라우트가 절대 + * 형태로 발급되던 배포본(7.0.10 이하)에서 저장된 값과, 운영자가 자기 사이트 주소를 + * 직접 적은 값이 여기에 해당한다. + * + * @scenario url_host=own_site_absolute + * + * @effects issued_asset_url_passes_storage_gate + */ + public function test_passes_with_own_site_absolute_url_in_props(): void + { + Config::set('app.url', 'https://shop.example.test'); + + foreach ([ + 'https://shop.example.test/api/templates/sirsoft-basic/layout-attachments/4/file', + 'http://shop.example.test/storage/template-layout-attachments/sirsoft-basic/logo.png', + 'https://SHOP.example.TEST/api/templates/sirsoft-basic/layout-attachments/4/file', + ] as $url) { + $this->assertNull($this->failureOf($this->layoutWithLogo($url)), "자기 host 절대 URL 은 통과해야 합니다: {$url}"); + } + } + + /** + * 운영자가 선언한 공개 자산 디스크의 host 는 통과해야 합니다 — 직접 URL(CDN) 변종에서 + * 첨부 API 가 돌려주는 값이다. + * + * @scenario url_host=public_asset_disk + * + * @effects issued_asset_url_passes_storage_gate + */ + public function test_passes_with_public_asset_disk_host_in_props(): void + { + Config::set('app.url', 'https://shop.example.test'); + Config::set('core.storage.public_asset_disk', 'public'); + Config::set('filesystems.disks.public.url', 'https://cdn.example.test/assets'); + + $url = 'https://cdn.example.test/assets/template-layout-attachments/sirsoft-basic/bg.png'; + + $this->assertNull($this->failureOf($this->layoutWithLogo($url)), '공개 자산 디스크 host 는 통과해야 합니다'); + } + + /** + * 같은 host 라도 공개 자산 디스크로 선언되지 않았으면 여전히 외부입니다 — 허용의 근거는 + * "URL 을 만들 수 있는 디스크가 있다" 가 아니라 운영자 선언이다. + * + * @scenario url_host=external_undeclared + * + * @effects external_host_still_rejected + */ + public function test_fails_with_cdn_host_when_public_asset_disk_is_not_declared(): void + { + Config::set('app.url', 'https://shop.example.test'); + Config::set('core.storage.public_asset_disk', ''); + Config::set('filesystems.disks.public.url', 'https://cdn.example.test/assets'); + + $url = 'https://cdn.example.test/assets/template-layout-attachments/sirsoft-basic/bg.png'; + + $this->assertNotNull($this->failureOf($this->layoutWithLogo($url)), '선언되지 않은 디스크 host 는 차단되어야 합니다'); + } + + /** + * 자기 host 를 흉내 낸 URL 은 전부 차단되어야 합니다 — 접두 문자열 비교로 허용하면 + * 이 형태들이 통과한다. 판정은 브라우저와 같은 정규화 뒤의 host 등가 비교여야 한다. + * + * @scenario url_host=lookalike + * + * @effects external_host_still_rejected + */ + public function test_fails_with_lookalike_of_own_host(): void + { + Config::set('app.url', 'https://shop.example.test'); + Config::set('core.storage.public_asset_disk', ''); + + foreach ([ + 'https://shop.example.test.evil.com/x.png', + 'https://shop.example.test@evil.com/x.png', + 'https://evil.com\\@shop.example.test/x.png', + 'https://evil.com/shop.example.test/x.png', + 'https://evil.com/?u=https://shop.example.test/x.png', + 'https://shop.example.test%2eevil.com/x.png', + // protocol-relative 는 자기 host 라도 종전대로 차단한다 (별도 판정 축) + '//shop.example.test/x.png', + ] as $url) { + $this->assertNotNull($this->failureOf($this->layoutWithLogo($url)), "자기 host 흉내는 차단되어야 합니다: {$url}"); + } + } + + /** + * 자기 host 허용은 http/https 에만 적용됩니다 — 위험 스킴은 host 가 같아도 차단한다. + * + * @scenario url_host=dangerous_scheme + * + * @effects external_host_still_rejected + */ + public function test_fails_with_dangerous_scheme_even_on_own_host(): void + { + Config::set('app.url', 'https://shop.example.test'); + + foreach ([ + 'ftp://shop.example.test/x.png', + 'file://shop.example.test/x.png', + ] as $url) { + $this->assertNotNull($this->failureOf($this->layoutWithLogo($url)), "위험 스킴은 host 와 무관하게 차단되어야 합니다: {$url}"); + } + } + /** * FormRequest 의 content 배열 규칙에서 NoExternalUrls 인스턴스를 찾는다. * diff --git a/tests/Feature/Seo/SeoMiddlewareTest.php b/tests/Feature/Seo/SeoMiddlewareTest.php index 649098d6..dc3799d1 100644 --- a/tests/Feature/Seo/SeoMiddlewareTest.php +++ b/tests/Feature/Seo/SeoMiddlewareTest.php @@ -5,8 +5,11 @@ namespace Tests\Feature\Seo; use App\Seo\BotDetector; use App\Seo\Contracts\SeoCacheManagerInterface; use App\Seo\Contracts\SeoRendererInterface; +use App\Seo\SeoCacheManager; +use App\Seo\SeoCacheStatsService; use App\Seo\SeoMiddleware; use Illuminate\Http\Request; +use Illuminate\Support\Facades\RateLimiter; use Tests\TestCase; /** @@ -24,6 +27,8 @@ class SeoMiddlewareTest extends TestCase private SeoRendererInterface $renderer; + private SeoCacheStatsService $statsService; + /** * 테스트 환경 설정 */ @@ -34,12 +39,18 @@ class SeoMiddlewareTest extends TestCase $this->botDetector = $this->createMock(BotDetector::class); $this->cacheManager = $this->createMock(SeoCacheManagerInterface::class); $this->renderer = $this->createMock(SeoRendererInterface::class); + $this->statsService = $this->createMock(SeoCacheStatsService::class); $this->middleware = new SeoMiddleware( $this->botDetector, $this->cacheManager, $this->renderer, + $this->statsService, ); + + // 렌더·통계 예산은 IP 단위 카운터다 — 테스트 간 이월되면 순서에 따라 결과가 갈린다. + RateLimiter::clear('seo-render:127.0.0.1'); + RateLimiter::clear('seo-stats:127.0.0.1'); } /** @@ -403,6 +414,7 @@ class SeoMiddlewareTest extends TestCase app(BotDetector::class), $this->cacheManager, $this->renderer, + $this->statsService, ); $response = $middleware->handle($request, $this->spaNext()); @@ -435,6 +447,7 @@ class SeoMiddlewareTest extends TestCase app(BotDetector::class), $this->cacheManager, $this->renderer, + $this->statsService, ); $response = $middleware->handle($request, $this->spaNext()); @@ -469,6 +482,7 @@ class SeoMiddlewareTest extends TestCase app(BotDetector::class), $this->cacheManager, $this->renderer, + $this->statsService, ); $response = $middleware->handle($request, $this->spaNext()); @@ -501,4 +515,257 @@ class SeoMiddlewareTest extends TestCase $this->middleware->handle($request, $this->spaNext()); } + + // ======================================== + // 캐시 상한 / 렌더 예산 테스트 (KVE-2026-2191 동형) + // ======================================== + + /** + * IP 당 미스 렌더 예산을 넘기면 렌더도 저장도 하지 않고 SPA 를 돌려준다. + * + * 봇 판정은 User-Agent 문자열뿐이라 위장이 가능하고, 캐시 키에 쿼리가 들어가므로 + * 값만 바꾼 반복 요청이 매번 미스가 된다. 미스 1건은 레이아웃 병합·표현식 평가·자기 + * API 루프백 호출을 유발하므로 요청 하나가 워커 여러 개를 묶는다. + * + * @effects bot_miss_over_limit_gets_spa_bypass + */ + public function test_bot_miss_over_render_limit_gets_spa_bypass_without_render(): void + { + config([ + 'g7_settings.core.seo.bot_detection_enabled' => true, + 'core.seo_cache_limits.render_misses_per_minute' => 2, + ]); + + $this->botDetector->method('isBot')->willReturn(true); + $this->cacheManager->method('get')->willReturn(null); + $this->renderer->expects($this->never())->method('render'); + $this->cacheManager->expects($this->never())->method('putWithLayout'); + + RateLimiter::hit('seo-render:127.0.0.1', 60); + RateLimiter::hit('seo-render:127.0.0.1', 60); + + $response = $this->middleware->handle( + $this->createRequest('/products', 'Googlebot/2.1'), + $this->spaNext() + ); + + $this->assertSame('SPA Fallback', $response->getContent()); + $this->assertSame('BYPASS', $response->headers->get('X-SEO-Cache')); + } + + /** + * 예산을 넘긴 IP 라도 **캐시 적중**은 그대로 서빙한다 — 비용이 없기 때문이다. + * + * @effects bot_hit_served_regardless_of_limit + */ + public function test_cache_hit_is_served_even_when_render_limit_exceeded(): void + { + config([ + 'g7_settings.core.seo.bot_detection_enabled' => true, + 'core.seo_cache_limits.render_misses_per_minute' => 1, + ]); + + $this->botDetector->method('isBot')->willReturn(true); + $this->cacheManager->method('get')->willReturn('cached'); + + RateLimiter::hit('seo-render:127.0.0.1', 60); + + $response = $this->middleware->handle( + $this->createRequest('/products', 'Googlebot/2.1'), + $this->spaNext() + ); + + $this->assertSame(200, $response->getStatusCode()); + $this->assertSame('HIT', $response->headers->get('X-SEO-Cache')); + } + + /** + * `_escaped_fragment_` 는 봇 렌더 요청 표식일 뿐이라 캐시 키를 가르지 않는다. + * + * @effects system_query_params_excluded_from_key + */ + public function test_escaped_fragment_param_does_not_change_cache_key(): void + { + config(['g7_settings.core.seo.bot_detection_enabled' => true]); + + $this->botDetector->method('isBot')->willReturn(true); + + $seen = []; + $this->cacheManager->method('get')->willReturnCallback(function (string $url) use (&$seen) { + $seen[] = $url; + + return 'cached'; + }); + + $this->middleware->handle($this->createRequest('/products', 'Googlebot/2.1'), $this->spaNext()); + $this->middleware->handle( + $this->createRequest('/products', 'Googlebot/2.1', ['_escaped_fragment_' => '']), + $this->spaNext() + ); + + $this->assertCount(2, $seen); + $this->assertSame($seen[0], $seen[1]); + } + + /** + * 정규화할 수 없을 만큼 큰 쿼리는 색인 대상이 아니다 — 캐시 조회도 렌더도 하지 않는다. + * + * @effects oversized_query_bypasses_cache_and_render + */ + public function test_oversized_query_bypasses_cache_and_render(): void + { + config([ + 'g7_settings.core.seo.bot_detection_enabled' => true, + 'core.seo_cache_limits.max_query_params' => 10, + ]); + + $this->botDetector->method('isBot')->willReturn(true); + $this->cacheManager->expects($this->never())->method('get'); + $this->renderer->expects($this->never())->method('render'); + + $query = []; + for ($i = 0; $i < 11; $i++) { + $query['p'.$i] = '1'; + } + + $response = $this->middleware->handle( + $this->createRequest('/products', 'Googlebot/2.1', $query), + $this->spaNext() + ); + + $this->assertSame('SPA Fallback', $response->getContent()); + $this->assertSame('BYPASS', $response->headers->get('X-SEO-Cache')); + } + + /** + * 캐시 적중·미적중이 통계에 기록된다. + * + * 기록 호출처가 없으면 `seo:stats` 와 관리자 통계가 항상 0 이라, 공격이 진행돼도 + * 운영자 화면은 아무것도 달라지지 않는다. + * + * @effects cache_hit_and_miss_are_recorded_in_stats + */ + public function test_cache_hit_records_stat_hit(): void + { + config(['g7_settings.core.seo.bot_detection_enabled' => true]); + + $this->botDetector->method('isBot')->willReturn(true); + $this->cacheManager->method('get')->willReturn('cached'); + + $this->statsService->expects($this->once())->method('recordHit'); + $this->statsService->expects($this->never())->method('recordMiss'); + + $this->middleware->handle($this->createRequest('/products', 'Googlebot/2.1'), $this->spaNext()); + } + + /** + * @effects cache_hit_and_miss_are_recorded_in_stats + */ + public function test_cache_miss_records_stat_miss_with_response_time(): void + { + config(['g7_settings.core.seo.bot_detection_enabled' => true]); + + $this->botDetector->method('isBot')->willReturn(true); + $this->cacheManager->method('get')->willReturn(null); + $this->renderer->method('render')->willReturn('rendered'); + + $this->statsService->expects($this->once()) + ->method('recordMiss') + ->with( + $this->anything(), + $this->anything(), + $this->anything(), + $this->anything(), + $this->greaterThanOrEqual(0) + ); + + $this->middleware->handle($this->createRequest('/products', 'Googlebot/2.1'), $this->spaNext()); + } + + /** + * 통계 기록도 IP 당 상한을 넘기면 멈춘다 — 통계 테이블이 새 증식 축이 되면 안 된다. + * + * @effects stats_recording_capped_per_ip + */ + public function test_stats_recording_is_capped_per_ip(): void + { + config([ + 'g7_settings.core.seo.bot_detection_enabled' => true, + 'core.seo_cache_limits.stats_records_per_minute' => 1, + ]); + + $this->botDetector->method('isBot')->willReturn(true); + $this->cacheManager->method('get')->willReturn('cached'); + + RateLimiter::hit('seo-stats:127.0.0.1', 60); + + $this->statsService->expects($this->never())->method('recordHit'); + + $this->middleware->handle($this->createRequest('/products', 'Googlebot/2.1'), $this->spaNext()); + } + + /** + * 캐시 적중(HIT)은 렌더러를 거치지 않으므로 레이아웃명을 요청 속성에서 얻을 수 없다 — + * 캐시 항목에 함께 저장된 레이아웃명으로 통계에 귀속한다. 없으면 화면별 표에서 모든 + * 화면의 적중이 0 이 되고 'N/A' 행에만 쌓인다. + * + * @effects cache_hit_carries_layout_name_from_cache_entry + */ + public function test_cache_hit_records_stat_hit_with_layout_from_cache_entry(): void + { + config(['g7_settings.core.seo.bot_detection_enabled' => true]); + + $cacheManager = $this->createMock(SeoCacheManager::class); + $cacheManager->method('getEntry')->willReturn(['html' => 'cached', 'layout' => 'shop/show']); + + $middleware = new SeoMiddleware($this->botDetector, $cacheManager, $this->renderer, $this->statsService); + + $this->botDetector->method('isBot')->willReturn(true); + $this->statsService->expects($this->once()) + ->method('recordHit') + ->with('/products', config('app.locale'), 'shop/show'); + + $response = $middleware->handle($this->createRequest('/products', 'Googlebot/2.1'), $this->spaNext()); + + $this->assertSame('cached', $response->getContent()); + $this->assertSame('HIT', $response->headers->get('X-SEO-Cache')); + } + + /** + * 렌더러가 "그릴 게 없음"(null)을 돌려주면 방금 뺀 렌더 예산을 되돌린다 — 미라우트 404 나 + * SEO 비활성 화면은 캐시에 남지 않아 올 때마다 다시 예산을 쓰므로, 죽은 주소 재크롤이 + * 정상 페이지의 예산을 태운다. + * + * @effects null_render_refunds_render_budget + */ + public function test_null_render_refunds_render_budget(): void + { + config(['g7_settings.core.seo.bot_detection_enabled' => true]); + + $this->botDetector->method('isBot')->willReturn(true); + $this->cacheManager->method('get')->willReturn(null); + $this->renderer->method('render')->willReturn(null); + + $this->middleware->handle($this->createRequest('/gone', 'Googlebot/2.1'), $this->spaNext()); + + $this->assertSame(0, (int) RateLimiter::attempts('seo-render:127.0.0.1')); + } + + /** + * 렌더 중 예외는 비용을 이미 치른 것이므로 예산을 되돌리지 않는다 (회귀 가드). + * + * @effects null_render_refunds_render_budget + */ + public function test_render_exception_keeps_render_budget_charged(): void + { + config(['g7_settings.core.seo.bot_detection_enabled' => true]); + + $this->botDetector->method('isBot')->willReturn(true); + $this->cacheManager->method('get')->willReturn(null); + $this->renderer->method('render')->willThrowException(new \RuntimeException('boom')); + + $this->middleware->handle($this->createRequest('/products', 'Googlebot/2.1'), $this->spaNext()); + + $this->assertSame(1, (int) RateLimiter::attempts('seo-render:127.0.0.1')); + } } diff --git a/tests/Feature/Services/ExtensionStaticCacheServiceTest.php b/tests/Feature/Services/ExtensionStaticCacheServiceTest.php index 2e081844..160706c9 100644 --- a/tests/Feature/Services/ExtensionStaticCacheServiceTest.php +++ b/tests/Feature/Services/ExtensionStaticCacheServiceTest.php @@ -1461,4 +1461,52 @@ class ExtensionStaticCacheServiceTest extends TestCase return $next === 1 ? substr($rest, 0, $m[0][1] + 1) : $rest; } + + /** + * 병합 결과가 비어 있어도(선언 산출물이 전부 존재하거나 선언이 0) 번들은 게시된다. + * + * 게시하지 않으면 그 구성의 자산 URL 이 API 로 폴백해, 방문자의 **모든 페이지 로드**가 + * PHP 를 거친다. 오류도 로그도 남지 않고 화면은 정상이라 드러나지 않는 경로다. + * + * @scenario publish_state=unpublished, artifact_integrity=intact, filesystem_writable=writable, environment=production, trigger=manual_command, process_user=web + * + * @effects zero_byte_bundle_is_statically_published + */ + public function test_publish_includes_zero_byte_bundle_when_declared_artifacts_are_empty(): void + { + $this->app['env'] = 'production'; + app()->detectEnvironment(fn () => 'production'); + + $this->createActiveTemplate(); + + // 활성 확장 0건 = 병합 결과가 빈 문자열인 대표 구성(기본 설치) + $service = $this->service(); + + try { + $this->assertTrue($service->publishCurrent(), $this->publishDiagnostics($service)); + + $bundleDir = public_path('build/ext/'.self::VERSION.'/bundles'); + + foreach (['modules.css', 'modules.js', 'plugins.css', 'plugins.js'] as $name) { + $this->assertFileExists($bundleDir.DIRECTORY_SEPARATOR.$name, "빈 번들도 게시되어야 한다: {$name}"); + $this->assertSame(0, filesize($bundleDir.DIRECTORY_SEPARATOR.$name)); + } + + $manifest = json_decode( + (string) file_get_contents(public_path('build/ext/'.self::VERSION.'/manifest.json')), + true + ); + + $this->assertIsArray($manifest); + $this->assertContains('bundles/modules.css', $manifest['files'] ?? []); + } finally { + // 번들 캐시는 격리된 public 루트 밖(storage/app/ext-bundles)에 쓰이므로 직접 치운다 — + // 남기면 실 설치본의 번들 디렉토리에 테스트 버전 파일이 쌓인다. + foreach (['module', 'plugin'] as $type) { + foreach (['js', 'css'] as $kind) { + @unlink(storage_path("app/ext-bundles/{$type}.".self::VERSION.".{$kind}")); + } + } + } + } } diff --git a/tests/Feature/Settings/EnvPriorityContractTest.php b/tests/Feature/Settings/EnvPriorityContractTest.php index 5dcc3e34..8031cf4b 100644 --- a/tests/Feature/Settings/EnvPriorityContractTest.php +++ b/tests/Feature/Settings/EnvPriorityContractTest.php @@ -6,7 +6,7 @@ use App\Support\EnvPriority; use Tests\TestCase; /** - * `.env` 키 단위 우선 규약의 계약 테스트. + * [case:backend-26] `.env` 키 단위 우선 규약의 계약 테스트. * * 이 결함군은 오류를 남기지 않는다 — 매핑이 뒤처지거나 배선이 빠지면 잠금이 조용히 * 미발동하고, 그 상태는 "그 키가 `.env` 에 없다"와 화면상 구분되지 않는다. 그래서 diff --git a/tests/Feature/Template/LayoutEditorCacheInvalidationTest.php b/tests/Feature/Template/LayoutEditorCacheInvalidationTest.php index 8a1caa69..91cc265a 100644 --- a/tests/Feature/Template/LayoutEditorCacheInvalidationTest.php +++ b/tests/Feature/Template/LayoutEditorCacheInvalidationTest.php @@ -8,6 +8,7 @@ use App\Extension\Cache\CoreCacheDriver; use App\Extension\Traits\InvalidatesLayoutCache; use App\Models\Template; use App\Models\TemplateLayout; +use App\Services\ExtensionStaticCacheService; use Illuminate\Foundation\Testing\RefreshDatabase; use PHPUnit\Framework\Attributes\Test; use Tests\TestCase; @@ -70,7 +71,9 @@ class LayoutEditorCacheInvalidationTest extends TestCase ]); $cache = $this->coreCache(); - $version = (int) $cache->get('ext.cache_version', 0); + // 트레이트 게터와 같은 근거 — 원시 키 읽기는 키 부재 시 0 을 돌려주어 실제 무효화 키(`.v{time}`)와 + // 어긋난 `.v0` 키를 만든다(트레이트는 부재 시 재생성). 무효화 대상 키는 게터가 SSoT 다. + $version = ExtensionStaticCacheService::getExtensionCacheVersion(); // PublicLayoutController::serve() 가 쓰는 두 캐시 키 — 일반 + 편집기(.meta) $generalKey = "layout.sirsoft-basic.home.v{$version}"; @@ -105,7 +108,9 @@ class LayoutEditorCacheInvalidationTest extends TestCase ]); $cache = $this->coreCache(); - $version = (int) $cache->get('ext.cache_version', 0); + // 트레이트 게터와 같은 근거 — 원시 키 읽기는 키 부재 시 0 을 돌려주어 실제 무효화 키(`.v{time}`)와 + // 어긋난 `.v0` 키를 만든다(트레이트는 부재 시 재생성). 무효화 대상 키는 게터가 SSoT 다. + $version = ExtensionStaticCacheService::getExtensionCacheVersion(); $metaKey = "layout.sirsoft-basic.home.v{$version}.meta"; $cache->put($metaKey, ['stale' => 'editor-old-content'], 3600); diff --git a/tests/Feature/Template/PublicLayoutControllerTest.php b/tests/Feature/Template/PublicLayoutControllerTest.php index 14833fb4..2c014f3a 100644 --- a/tests/Feature/Template/PublicLayoutControllerTest.php +++ b/tests/Feature/Template/PublicLayoutControllerTest.php @@ -232,15 +232,20 @@ class PublicLayoutControllerTest extends TestCase } /** - * 편집기 캐시-버스트 nonce(`?v={version}.{nonce}`) 요청 시 서버 캐시 키는 **정수 버전**만 쓴다. + * 편집기 캐시-버스트 nonce(`?v={version}.{nonce}`) 요청 시 서버 캐시 키는 **서버 현재 버전**만 쓴다. * * 레이아웃 편집기는 같은 세션 저장/복원 후 브라우저 HTTP 캐시를 우회하려고 `?v={cacheVersion}.{nonce}` * 형식으로 요청한다. 종전엔 `serve` 가 이 문자열을 그대로 캐시 키에 써(`v{ver}.{nonce}.meta`), * 저장 경로 `LayoutService::clearPublicServingCache` 가 `(int) ext.cache_version` 으로 nonce 없는 키만 - * forget 하므로 무효화가 빗나가 편집기가 stale 응답을 받았다. 본 테스트는 nonce 가 붙은 요청도 - * **정수 버전 키**(`v{ver}.meta`)로만 캐싱돼, 저장 경로 무효화 키와 정합함을 잠근다. + * forget 하므로 무효화가 빗나가 편집기가 stale 응답을 받았다(#588 — 정수부만 키에 사용). + * + * 그 뒤에도 사각이 남았다 — 편집기는 **부팅 시점** 버전을 계속 보내고 서버는 저장·복원마다 버전을 + * 올리며 **현재** 버전 키만 지우므로, 두 번째 bump 부터 부팅 버전 키가 영영 남아 초기화·복원·409 + * 「최신 불러오기」가 옛 content 를 받았다. 본 테스트는 클라이언트 `?v` 가 서버 버전과 달라도 서버 + * 캐시 키가 **서버 현재 버전**(`v{server}.meta`)으로만 만들어져, 무효화 키와 굽기 키가 항상 같은 + * 키임을 잠근다. */ - public function test_editor_nonce_versioned_request_uses_integer_cache_key(): void + public function test_editor_nonce_versioned_request_uses_server_cache_version_key(): void { $template = Template::create([ 'identifier' => 'sirsoft-admin_basic', @@ -258,8 +263,11 @@ class PublicLayoutControllerTest extends TestCase ]); $coreCache = app(CacheInterface::class); + // 클라이언트(편집기 부팅 시점) 버전과 서버 현재 버전을 다르게 둔다 — 저장 뒤의 실제 상태. $version = 1781151505; + $serverVersion = 1781151999; $nonce = 7; + Cache::put('g7:core:ext.cache_version', $serverVersion); // 편집 모드(.meta) — `core.templates.layouts.edit` 권한 보유 사용자로 인증. $admin = User::factory()->create(); $role = Role::create([ @@ -275,7 +283,8 @@ class PublicLayoutControllerTest extends TestCase ]); $role->permissions()->attach($perm->id); $admin->roles()->attach($role->id); - // 정수 버전 키(저장 경로가 forget 하는 키)와 nonce 부착 키를 모두 비워 둔다. + // 서버 버전 키(저장 경로가 forget 하는 키)·클라이언트 정수 키·nonce 부착 키를 모두 비워 둔다. + $coreCache->forget("layout.{$template->identifier}.{$layout->name}.v{$serverVersion}.meta"); $coreCache->forget("layout.{$template->identifier}.{$layout->name}.v{$version}.meta"); $coreCache->forget("layout.{$template->identifier}.{$layout->name}.v{$version}.{$nonce}.meta"); @@ -284,10 +293,16 @@ class PublicLayoutControllerTest extends TestCase ->getJson("/api/layouts/{$template->identifier}/{$layout->name}.json?with_source_meta=1&v={$version}.{$nonce}") ->assertStatus(200); - // 서버 캐시 키는 **정수 버전**(nonce 없는 키)에 생성돼야 한다 — 저장 경로 무효화 키와 일치. + // 서버 캐시 키는 **서버 현재 버전**(nonce 없는 키)에 생성돼야 한다 — 저장 경로 무효화 키와 일치. $this->assertTrue( + $coreCache->has("layout.{$template->identifier}.{$layout->name}.v{$serverVersion}.meta"), + '요청의 ?v 와 무관하게 서버 현재 버전 캐시 키(`v{server}.meta`)로 캐싱돼야 저장 경로 무효화와 정합함', + ); + // 클라이언트가 보낸 정수 버전 키는 생성되지 않아야 한다 — 저장 경로가 지우지 않는 키라 + // 두 번째 저장부터 stale 로 남는다(편집기 초기화·복원·409 「최신 불러오기」 옛 content 회귀 차단). + $this->assertFalse( $coreCache->has("layout.{$template->identifier}.{$layout->name}.v{$version}.meta"), - 'nonce 부착 요청도 정수 버전 캐시 키(`v{ver}.meta`)로 캐싱돼야 저장 경로 무효화와 정합함', + '클라이언트 부팅 버전으로 키를 만들면 저장 경로(서버 현재 버전 키 forget)가 무효화하지 못해 stale 회귀', ); // nonce 가 박힌 키(`v{ver}.{nonce}.meta`)는 생성되지 않아야 한다(무효화 빗나감 회귀 차단). $this->assertFalse( diff --git a/tests/Feature/Upgrades/Upgrade7011ImageObjectPropBackfillTest.php b/tests/Feature/Upgrades/Upgrade7011ImageObjectPropBackfillTest.php new file mode 100644 index 00000000..d7f73682 --- /dev/null +++ b/tests/Feature/Upgrades/Upgrade7011ImageObjectPropBackfillTest.php @@ -0,0 +1,698 @@ +` 를 받던 결함(공개 #135)의 기존 데이터 보정. + * + * 검증 축은 셋이다: + * ① **판정식 엄격도** — 느슨한 판정식(4키 중 하나라도)을 쓰면 정상 props 를 파괴한다. + * 케이스 9·10·11·14 가 그 반증 가드다. + * ② **순회 범위** — prop 자리는 키 allowlist 로 정의 불가하므로 모드 플래그 전역 재귀. + * 중첩 노드·responsive 분기·확장 injections 가 포섭되고 style 자리는 배제된다. + * ③ **안전성** — 멱등 / 무변경 행 UPDATE 금지 / chunkById / lock_version / + * original_content_hash 불변 / 버전·미리보기 스냅샷 미개입. + */ +class Upgrade7011ImageObjectPropBackfillTest extends TestCase +{ + use RefreshDatabase; + + /** 청크 크기(100) 를 넘겨야 OFFSET 누락이 드러난다 — Beta2LayoutHashBackfillTest 관례 계승 */ + private const SEED_COUNT = 250; + + private NarrowImageObjectPropsInLayouts $migration; + + private UpgradeContext $context; + + private int $templateId; + + protected function setUp(): void + { + parent::setUp(); + + // upgrade data 파일은 composer autoload 대상이 아니며 AbstractUpgradeStep 이 + // 실행 시점에 require_once 로 수동 로드한다. 테스트에서도 동일하게 수동 로드. + require_once base_path('upgrades/data/7.0.11/migrations/01_NarrowImageObjectPropsInLayouts.php'); + + $this->migration = new NarrowImageObjectPropsInLayouts; + $this->context = new UpgradeContext( + fromVersion: '7.0.10', + toVersion: '7.0.11', + currentStep: '7.0.11', + ); + + $this->templateId = DB::table('templates')->insertGetId([ + 'identifier' => 'test-imgobj_template', + 'vendor' => 'test', + 'name' => json_encode(['ko' => '이미지 축약 테스트', 'en' => 'Image Narrow Test']), + 'version' => '1.0.0', + 'type' => 'user', + 'status' => 'active', + 'created_at' => now(), + 'updated_at' => now(), + ]); + } + + /** 완전한 4키 이미지 값 객체 */ + private function imageValue(string $url = '/api/attachment/X'): array + { + return ['url' => $url, 'size' => 'cover', 'repeat' => 'no-repeat', 'position' => 'center']; + } + + /** + * 레이아웃 1건을 시드하고 id 를 돌려줍니다. + * + * @param array $content 레이아웃 content 배열 + * @param array $extra 추가 컬럼 오버라이드 + * @return int 생성된 레이아웃 id + */ + private function seedLayout(array $content, array $extra = []): int + { + static $seq = 0; + $seq++; + + return DB::table('template_layouts')->insertGetId(array_merge([ + 'template_id' => $this->templateId, + 'name' => 'imgobj/layout_'.$seq, + 'content' => json_encode($content, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), + 'source_type' => 'template', + 'source_identifier' => 'test-imgobj_template', + 'lock_version' => 0, + 'created_at' => now(), + 'updated_at' => now(), + ], $extra)); + } + + /** 저장된 content 를 배열로 되읽는다 */ + private function readContent(int $id, string $table = 'template_layouts'): array + { + return json_decode(DB::table($table)->where('id', $id)->value('content'), true); + } + + private function runMigration(): void + { + $this->migration->run($this->context); + } + + /** + * 로거를 감시 대역으로 바꾼 컨텍스트로 마이그레이션을 실행합니다. + * + * 파괴적 동작(prop 키 제거)의 유일한 흔적은 `upgrade` 채널 로그다 — 그 호출이 + * 사라져도 데이터 단언만으로는 드러나지 않으므로 로그 발생 자체를 계약으로 고정한다. + * `UpgradeContext` 는 생성자에서 `Log::channel()` 을 1회 해석하므로 페이크를 + * **컨텍스트 생성 전에** 심어야 한다. + * + * @return MockInterface 감시 대역 로거 + */ + private function runMigrationWithLogSpy(): MockInterface + { + $spy = \Mockery::spy(LoggerInterface::class); + Log::shouldReceive('channel') + ->with('upgrade') + ->andReturn($spy); + + $this->migration->run(new UpgradeContext( + fromVersion: '7.0.10', + toVersion: '7.0.11', + currentStep: '7.0.11', + )); + + return $spy; + } + + // ── ① 기본 축약 ──────────────────────────────────────────────────────── + + /** @effects backfill_narrows_prop_slot_objects_in_layouts_and_extensions */ + public function test_1_prop_slot_image_object_is_narrowed_to_url(): void + { + $id = $this->seedLayout([ + 'components' => [ + ['name' => 'Header', 'props' => ['logo' => $this->imageValue(), 'siteName' => '샘플']], + ], + ]); + + $this->runMigration(); + + $content = $this->readContent($id); + $this->assertSame('/api/attachment/X', $content['components'][0]['props']['logo']); + $this->assertSame('샘플', $content['components'][0]['props']['siteName'], '나머지 props 는 불변이어야 합니다.'); + } + + public function test_2_string_value_is_untouched_and_row_is_not_updated(): void + { + $id = $this->seedLayout([ + 'components' => [['name' => 'Header', 'props' => ['logo' => '/img/a.png']]], + ]); + $before = DB::table('template_layouts')->where('id', $id)->first(); + + $this->runMigration(); + + $after = DB::table('template_layouts')->where('id', $id)->first(); + $this->assertSame($before->content, $after->content); + $this->assertSame($before->updated_at, $after->updated_at, '무변경 행은 UPDATE 되지 않아야 합니다.'); + $this->assertSame($before->lock_version, $after->lock_version); + } + + public function test_3_binding_expression_string_is_untouched(): void + { + $expr = '{{_global.settings?.general?.site_logo_url}}'; + $id = $this->seedLayout([ + 'components' => [['name' => 'Header', 'props' => ['logo' => $expr]]], + ]); + $before = DB::table('template_layouts')->where('id', $id)->first(); + + $this->runMigration(); + + $after = DB::table('template_layouts')->where('id', $id)->first(); + $this->assertSame($expr, $this->readContent($id)['components'][0]['props']['logo']); + $this->assertSame($before->updated_at, $after->updated_at); + } + + // ── ② style 자리는 대상이 아니다 ─────────────────────────────────────── + + /** @effects backfill_excludes_style_slot_where_background_decomposition_lives */ + public function test_4_style_background_four_props_are_untouched(): void + { + $style = [ + 'backgroundImage' => 'url(/a.png)', + 'backgroundSize' => 'cover', + 'backgroundRepeat' => 'no-repeat', + 'backgroundPosition' => 'center', + ]; + $id = $this->seedLayout([ + 'components' => [['name' => 'Div', 'props' => ['style' => $style]]], + ]); + $before = DB::table('template_layouts')->where('id', $id)->first(); + + $this->runMigration(); + + $this->assertSame($style, $this->readContent($id)['components'][0]['props']['style']); + $this->assertSame($before->updated_at, DB::table('template_layouts')->where('id', $id)->first()->updated_at); + } + + public function test_5_corrupted_value_inside_style_slot_is_untouched(): void + { + // style 자리의 손상값 복구는 엔진 `sanitizeBgScalar` 소관 — 백필은 건드리지 않는다. + $style = ['backgroundPosition' => $this->imageValue()]; + $id = $this->seedLayout([ + 'components' => [['name' => 'Div', 'props' => ['style' => $style]]], + ]); + + $this->runMigration(); + + $this->assertSame($style, $this->readContent($id)['components'][0]['props']['style']); + } + + // ── ③ 순회 범위 ──────────────────────────────────────────────────────── + + /** @effects backfill_traverses_responsive_branches_nested_nodes_and_injections */ + public function test_6_responsive_branch_props_are_narrowed(): void + { + $id = $this->seedLayout([ + 'components' => [ + [ + 'name' => 'Header', + 'responsive' => ['md' => ['props' => ['logo' => $this->imageValue('/md.png')]]], + ], + ], + ]); + + $this->runMigration(); + + $this->assertSame('/md.png', $this->readContent($id)['components'][0]['responsive']['md']['props']['logo']); + } + + public function test_7_nested_node_inside_props_is_narrowed(): void + { + $id = $this->seedLayout([ + 'components' => [ + [ + 'name' => 'DataGrid', + 'props' => [ + 'cardColumns' => [ + ['cellChildren' => [['name' => 'Header', 'props' => ['logo' => $this->imageValue('/nested.png')]]]], + ], + ], + ], + ], + ]); + + $this->runMigration(); + + $content = $this->readContent($id); + $this->assertSame( + '/nested.png', + $content['components'][0]['props']['cardColumns'][0]['cellChildren'][0]['props']['logo'] + ); + } + + public function test_8_style_inside_nested_node_is_still_excluded(): void + { + $style = ['backgroundImage' => 'url(/n.png)', 'backgroundSize' => 'cover']; + $id = $this->seedLayout([ + 'components' => [ + [ + 'name' => 'Div', + 'props' => [ + 'expandChildren' => [['name' => 'Div', 'props' => ['style' => $style]]], + ], + ], + ], + ]); + + $this->runMigration(); + + $content = $this->readContent($id); + $this->assertSame($style, $content['components'][0]['props']['expandChildren'][0]['props']['style']); + } + + // ── ④ 판정식 엄격도 (느슨한 판정식이면 여기서 파괴된다) ─────────────── + + /** @effects backfill_strict_predicate_leaves_normal_props_untouched */ + public function test_9_normal_props_with_size_key_are_untouched(): void + { + // 실측 674건 패턴. 코어 엔진의 느슨한 판정식(4키 중 하나라도)이면 이 props 가 + // 이미지 값 객체로 오인되어 파괴된다. + $props = ['className' => 'x', 'name' => 'user', 'size' => 'lg']; + $id = $this->seedLayout(['components' => [['name' => 'Icon', 'props' => $props]]]); + $before = DB::table('template_layouts')->where('id', $id)->first(); + + $this->runMigration(); + + $this->assertSame($props, $this->readContent($id)['components'][0]['props']); + $this->assertSame($before->updated_at, DB::table('template_layouts')->where('id', $id)->first()->updated_at); + } + + public function test_10_normal_props_with_position_key_are_untouched(): void + { + // 실측 26건 패턴. + $props = ['items' => [['label' => 'a']], 'position' => 'bottom']; + $id = $this->seedLayout(['components' => [['name' => 'Toast', 'props' => $props]]]); + + $this->runMigration(); + + $this->assertSame($props, $this->readContent($id)['components'][0]['props']); + } + + public function test_11_object_with_key_outside_the_four_is_untouched(): void + { + $value = ['url' => '/x', 'label' => 'y']; + $id = $this->seedLayout(['components' => [['name' => 'X', 'props' => ['foo' => $value]]]]); + + $this->runMigration(); + + $this->assertSame($value, $this->readContent($id)['components'][0]['props']['foo']); + } + + public function test_14_image_object_without_url_key_is_untouched(): void + { + // `{size,repeat,position}` 은 정상 props 와 정적으로 구분 불가 — 건드리면 오탐. + $value = ['size' => 'cover', 'repeat' => 'no-repeat', 'position' => 'center']; + $id = $this->seedLayout(['components' => [['name' => 'X', 'props' => ['bg' => $value]]]]); + + $this->runMigration(); + + $this->assertSame($value, $this->readContent($id)['components'][0]['props']['bg']); + } + + public function test_27_props_container_itself_is_never_treated_as_an_image_value(): void + { + // `props` 는 값이 아니라 **컨테이너**다. `url` prop 을 받는 컴포넌트(임베드·영상 등)는 + // props 맵 자신이 「4키 부분집합 + url 보유」 형태가 되는데, 그것을 값으로 오인하면 + // props 가 통째로 문자열로 붕괴해 그 노드의 설정이 전부 사라진다. + $props = ['url' => '/embed/x', 'size' => 'lg']; + $id = $this->seedLayout(['components' => [['name' => 'Iframe', 'props' => $props]]]); + + $this->runMigration(); + + $this->assertSame($props, $this->readContent($id)['components'][0]['props']); + } + + public function test_28_props_container_with_corrupted_url_is_never_dropped(): void + { + // 같은 오인이 손상 url 분기로 가면 props 키 자체가 unset 되어 노드가 빈 껍데기가 된다. + $props = ['url' => ['nested' => 1], 'size' => 'lg']; + $id = $this->seedLayout(['components' => [['name' => 'Video', 'props' => $props]]]); + + $this->runMigration(); + + $node = $this->readContent($id)['components'][0]; + $this->assertArrayHasKey('props', $node, 'props 컨테이너는 손상 url 분기의 대상이 아니다.'); + $this->assertSame($props, $node['props']); + } + + public function test_29_style_container_itself_is_never_treated_as_an_image_value(): void + { + // `style` 도 같은 컨테이너 키다 — prop 자리 안에 있어도 값으로 평가하지 않는다. + $style = ['url' => '/legacy.png', 'position' => 'center']; + $id = $this->seedLayout(['components' => [['name' => 'X', 'props' => ['style' => $style]]]]); + + $this->runMigration(); + + $this->assertSame($style, $this->readContent($id)['components'][0]['props']['style']); + } + + // ── ⑤ 손상 url ───────────────────────────────────────────────────────── + + /** @effects backfill_drops_prop_key_on_corrupted_url_with_warning_log */ + public function test_12_empty_url_drops_the_prop_key(): void + { + $id = $this->seedLayout([ + 'components' => [['name' => 'Header', 'props' => ['logo' => $this->imageValue(''), 'siteName' => '샘플']]], + ]); + + $spy = $this->runMigrationWithLogSpy(); + + $props = $this->readContent($id)['components'][0]['props']; + $this->assertArrayNotHasKey('logo', $props, 'src="" 는 현재 문서 재요청이므로 키를 제거해 폴백을 살린다.'); + $this->assertSame('샘플', $props['siteName']); + + // 파괴적 동작이므로 원본이 로그에 남아야 한다 — 행 id·JSON 경로·원본 값 3요소. + $spy->shouldHaveReceived('warning') + ->withArgs(fn (string $m) => str_contains($m, "id={$id}") + && str_contains($m, 'components[0].props.logo') + && str_contains($m, '"url":""')) + ->once(); + } + + public function test_13_non_string_url_drops_the_prop_key(): void + { + $id = $this->seedLayout([ + 'components' => [['name' => 'Header', 'props' => ['logo' => ['url' => ['nested' => 1], 'size' => 'cover']]]], + ]); + + $spy = $this->runMigrationWithLogSpy(); + + $this->assertArrayNotHasKey('logo', $this->readContent($id)['components'][0]['props']); + + $spy->shouldHaveReceived('warning') + ->withArgs(fn (string $m) => str_contains($m, "id={$id}") + && str_contains($m, 'components[0].props.logo') + && str_contains($m, '"nested":1')) + ->once(); + } + + // ── ⑥ 확장 테이블 ────────────────────────────────────────────────────── + + /** + * 확장 레이아웃 1건을 시드합니다. + * + * @param array $content content 배열 + * @return int 생성된 행 id + */ + private function seedExtension(array $content): int + { + static $seq = 0; + $seq++; + + return DB::table('template_layout_extensions')->insertGetId([ + 'template_id' => $this->templateId, + 'extension_type' => 'module', + 'target_name' => 'home_'.$seq, + 'source_type' => 'module', + 'source_identifier' => 'test-module', + 'content' => json_encode($content, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), + 'priority' => 10, + 'is_active' => true, + 'lock_version' => 0, + 'created_at' => now(), + 'updated_at' => now(), + ]); + } + + public function test_15_extension_injection_component_props_are_narrowed(): void + { + $id = $this->seedExtension([ + 'injections' => [ + ['components' => [['name' => 'Header', 'props' => ['logo' => $this->imageValue('/ext.png')]]]], + ], + ]); + + $this->runMigration(); + + $content = $this->readContent($id, 'template_layout_extensions'); + $this->assertSame('/ext.png', $content['injections'][0]['components'][0]['props']['logo']); + } + + public function test_16_extension_inject_props_slot_is_narrowed(): void + { + $id = $this->seedExtension([ + 'injections' => [['props' => ['logo' => $this->imageValue('/inject.png')]]], + ]); + + $this->runMigration(); + + $content = $this->readContent($id, 'template_layout_extensions'); + $this->assertSame('/inject.png', $content['injections'][0]['props']['logo']); + } + + /** @effects backfill_traverses_modals_in_both_list_and_map_shape */ + public function test_17_modals_in_both_list_and_map_shape_are_narrowed(): void + { + $listId = $this->seedLayout([ + 'modals' => [['id' => 'm1', 'components' => [['name' => 'Img', 'props' => ['src' => $this->imageValue('/list.png')]]]]], + ]); + $mapId = $this->seedLayout([ + 'modals' => [ + 'channel_edit_modal' => [ + 'components' => [['name' => 'Img', 'props' => ['src' => $this->imageValue('/map.png')]]], + ], + ], + ]); + + $this->runMigration(); + + $this->assertSame('/list.png', $this->readContent($listId)['modals'][0]['components'][0]['props']['src']); + $this->assertSame( + '/map.png', + $this->readContent($mapId)['modals']['channel_edit_modal']['components'][0]['props']['src'] + ); + } + + // ── ⑦ 안전성 ─────────────────────────────────────────────────────────── + + /** @effects backfill_is_idempotent_and_performs_no_update_on_unchanged_rows */ + public function test_18_second_run_is_idempotent_and_performs_no_update(): void + { + $id = $this->seedLayout([ + 'components' => [['name' => 'Header', 'props' => ['logo' => $this->imageValue()]]], + ]); + + $this->runMigration(); + $afterFirst = DB::table('template_layouts')->where('id', $id)->first(); + + $this->runMigration(); + $afterSecond = DB::table('template_layouts')->where('id', $id)->first(); + + $this->assertSame($afterFirst->content, $afterSecond->content); + $this->assertSame($afterFirst->lock_version, $afterSecond->lock_version, '2회차는 UPDATE 가 없어야 합니다.'); + $this->assertSame($afterFirst->updated_at, $afterSecond->updated_at); + } + + /** @effects backfill_increments_lock_version_only_on_changed_rows */ + public function test_19_lock_version_is_incremented_only_on_changed_rows(): void + { + $dirty = $this->seedLayout( + ['components' => [['name' => 'Header', 'props' => ['logo' => $this->imageValue()]]]], + ['lock_version' => 3], + ); + $clean = $this->seedLayout( + ['components' => [['name' => 'Header', 'props' => ['logo' => '/ok.png']]]], + ['lock_version' => 3], + ); + + $this->runMigration(); + + $this->assertSame(4, (int) DB::table('template_layouts')->where('id', $dirty)->value('lock_version')); + $this->assertSame(3, (int) DB::table('template_layouts')->where('id', $clean)->value('lock_version')); + } + + /** @effects backfill_preserves_original_content_hash_so_modified_judgement_holds */ + public function test_20_original_content_hash_is_preserved(): void + { + $id = $this->seedLayout( + ['components' => [['name' => 'Header', 'props' => ['logo' => $this->imageValue()]]]], + ['original_content_hash' => str_repeat('a', 64), 'original_content_size' => 123], + ); + + $this->runMigration(); + + $row = DB::table('template_layouts')->where('id', $id)->first(); + $this->assertSame(str_repeat('a', 64), $row->original_content_hash, '재계산하면 사용자 편집본이 「원본 그대로」로 위장된다.'); + $this->assertSame(123, (int) $row->original_content_size); + // 백필 후에도 content 는 원본 파일과 다르므로 「수정됨」 판정이 유지된다. + $this->assertNotSame(hash('sha256', $row->content), $row->original_content_hash); + } + + /** @effects backfill_uses_keyset_chunking_so_no_row_is_skipped_past_the_boundary */ + public function test_21_all_rows_are_converted_beyond_the_chunk_boundary(): void + { + $ids = []; + for ($i = 0; $i < self::SEED_COUNT; $i++) { + $ids[] = $this->seedLayout([ + 'components' => [['name' => 'Header', 'props' => ['logo' => $this->imageValue('/a'.$i.'.png')]]], + ]); + } + + $this->runMigration(); + + $remaining = 0; + foreach ($ids as $i => $id) { + $logo = $this->readContent($id)['components'][0]['props']['logo']; + if (! is_string($logo)) { + $remaining++; + + continue; + } + $this->assertSame('/a'.$i.'.png', $logo); + } + + $this->assertSame(0, $remaining, 'OFFSET 순회면 청크 경계 이후 행이 조용히 누락된다 (chunkById 필요).'); + } + + /** @effects backfill_skips_broken_json_rows_without_aborting_the_run */ + public function test_22_broken_json_row_is_skipped_without_aborting_the_run(): void + { + $broken = $this->seedLayout(['components' => []]); + DB::table('template_layouts')->where('id', $broken)->update(['content' => '{not json']); + $good = $this->seedLayout([ + 'components' => [['name' => 'Header', 'props' => ['logo' => $this->imageValue('/good.png')]]], + ]); + + $spy = $this->runMigrationWithLogSpy(); + + $this->assertSame('{not json', DB::table('template_layouts')->where('id', $broken)->value('content')); + $this->assertSame('/good.png', $this->readContent($good)['components'][0]['props']['logo']); + + // 건너뛴 행은 조용히 사라지면 안 된다 — 그 행 id 가 로그에 남아야 운영자가 찾아간다. + $spy->shouldHaveReceived('warning') + ->withArgs(fn (string $m) => str_contains($m, "id={$broken}")) + ->atLeast()->once(); + } + + /** @effects backfill_leaves_version_and_preview_snapshots_untouched */ + public function test_23_version_and_preview_snapshots_are_untouched(): void + { + $layoutId = $this->seedLayout(['components' => []]); + $poisoned = json_encode( + ['components' => [['name' => 'Header', 'props' => ['logo' => $this->imageValue('/snap.png')]]]], + JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES, + ); + + $versionId = DB::table('template_layout_versions')->insertGetId([ + 'layout_id' => $layoutId, + 'version' => 1, + 'content' => $poisoned, + 'created_at' => now(), + ]); + $previewId = DB::table('template_layout_previews')->insertGetId([ + 'token' => 'imgobj-preview-token', + 'template_id' => $this->templateId, + 'layout_name' => 'imgobj/preview', + 'preview_type' => 'layout', + 'content' => $poisoned, + 'admin_id' => 1, + 'expires_at' => now()->addHour(), + 'created_at' => now(), + ]); + + $this->runMigration(); + + $this->assertSame( + $poisoned, + DB::table('template_layout_versions')->where('id', $versionId)->value('content'), + '과거 스냅샷을 고치면 「이전 버전으로 되돌리기」가 원본과 달라진다.' + ); + $this->assertSame( + $poisoned, + DB::table('template_layout_previews')->where('id', $previewId)->value('content'), + '미리보기 스냅샷도 백필 대상이 아니다.' + ); + } + + /** @effects backfill_converts_soft_deleted_rows_for_restore_safety */ + public function test_24_soft_deleted_rows_are_converted(): void + { + $id = $this->seedLayout( + ['components' => [['name' => 'Header', 'props' => ['logo' => $this->imageValue('/deleted.png')]]]], + ['deleted_at' => now()], + ); + + $this->runMigration(); + + $this->assertSame( + '/deleted.png', + $this->readContent($id)['components'][0]['props']['logo'], + '삭제된 레이아웃은 복원 가능하므로 고치지 않으면 복원 시 결함이 되살아난다.' + ); + } + + public function test_25_missing_table_is_skipped_without_error(): void + { + // 실제 `Schema::drop` 은 쓰지 않는다 — 이 테이블은 versions 테이블의 FK 대상이라 + // 드롭 자체가 실패하고, DDL 은 RefreshDatabase 트랜잭션을 암묵 커밋시켜 뒤따르는 + // 테스트까지 오염시킨다. 검증 대상은 `Schema::hasTable` 게이트이므로 존재하지 않는 + // 테이블명으로 같은 코드 경로를 직접 태운다. + $ref = new ReflectionClass($this->migration); + $method = $ref->getMethod('processTable'); + $method->setAccessible(true); + + $this->assertFalse(Schema::hasTable('g7_no_such_layout_table')); + $method->invoke($this->migration, $this->context, 'g7_no_such_layout_table'); + + // 예외 없이 통과 + 실제 테이블은 그대로다. + $this->assertTrue(Schema::hasTable('template_layout_extensions')); + } + + // ── ⑧ 변환 함수 직접 호출 (DB 없이 판정식만) ────────────────────────── + + public function test_26_transform_is_callable_directly_and_matches_db_path_results(): void + { + $ref = new ReflectionClass($this->migration); + $method = $ref->getMethod('transform'); + $method->setAccessible(true); + + $call = function (array $input) use ($method) { + $changed = 0; + $args = [$input, false, &$changed, $this->context, 't', '1', '']; + + return [$method->invokeArgs($this->migration, $args), $changed]; + }; + + // 케이스 1 — 축약 + [$out, $changed] = $call(['components' => [['props' => ['logo' => $this->imageValue()]]]]); + $this->assertSame('/api/attachment/X', $out['components'][0]['props']['logo']); + $this->assertSame(1, $changed); + + // 케이스 4 — style 자리 불변 + $style = ['backgroundImage' => 'url(/a.png)', 'backgroundSize' => 'cover']; + [$out, $changed] = $call(['components' => [['props' => ['style' => $style]]]]); + $this->assertSame($style, $out['components'][0]['props']['style']); + $this->assertSame(0, $changed); + + // 케이스 9 — 정상 props 불변 + $props = ['className' => 'x', 'name' => 'user', 'size' => 'lg']; + [$out, $changed] = $call(['components' => [['props' => $props]]]); + $this->assertSame($props, $out['components'][0]['props']); + $this->assertSame(0, $changed); + } +} diff --git a/tests/Playwright/fixtures/seed-layouts/sirsoft-basic.e2e_sandbox.json b/tests/Playwright/fixtures/seed-layouts/sirsoft-basic.e2e_sandbox.json index 5ebbb7c5..38a52377 100644 --- a/tests/Playwright/fixtures/seed-layouts/sirsoft-basic.e2e_sandbox.json +++ b/tests/Playwright/fixtures/seed-layouts/sirsoft-basic.e2e_sandbox.json @@ -18,6 +18,14 @@ "className": "py-6 px-4" }, "children": [ + { + "id": "e2e_sandbox_header", + "type": "composite", + "name": "Header", + "props": { + "siteName": "E2E 샌드박스" + } + }, { "id": "e2e_sandbox_root", "type": "basic", diff --git a/tests/Playwright/fixtures/smtp-sink.ts b/tests/Playwright/fixtures/smtp-sink.ts new file mode 100644 index 00000000..10590b9a --- /dev/null +++ b/tests/Playwright/fixtures/smtp-sink.ts @@ -0,0 +1,131 @@ +/** + * 최소 SMTP 싱크 (테스트 전용). + * + * 2단계 인증 실측은 **인증번호 발송이 성공해야** 코드 입력 단계까지 갈 수 있다. 발송이 + * 실패하면 서버가 로그인을 503 으로 끊기 때문이다(그것도 정당한 동작이라 PHPUnit 이 따로 잰다). + * + * 그래서 실제 메일을 보내는 대신 받아서 버리는 SMTP 서버를 잠깐 띄우고 사이트의 메일 설정을 + * 그쪽으로 돌린다. `log` 메일러를 쓰지 않는 이유는 그것이 이 제품의 설정 스키마에 없어서다 — + * 등록된 메일 드라이버(smtp·mailgun·ses) 밖의 값은 저장 검증에서 거부되고, 파일을 직접 고쳐도 + * 드라이버 해석 단계에서 smtp 로 되돌아간다. + * + * 의존성을 늘리지 않으려고 Node 기본 `net` 만 쓴다. 받은 메일은 어디에도 남기지 않는다. + */ +import { createServer, type Server, type Socket } from 'node:net'; + +/** 띄운 싱크의 핸들 */ +export type SmtpSink = { + port: number; + /** 받은 메시지 수 (진단용) */ + received(): number; + close(): Promise; +}; + +/** + * 한 연결의 SMTP 대화를 처리한다. + * + * 인증도 TLS 도 요구하지 않는다 — 테스트 전용이며 127.0.0.1 에만 바인딩한다. + * + * @param socket 클라이언트 소켓 + * @param onMessage 메시지 1건 수신 시 호출 + */ +function handleConnection(socket: Socket, onMessage: () => void): void { + let inData = false; + let buffer = ''; + + socket.setEncoding('utf-8'); + socket.write('220 g7-test-sink ESMTP\r\n'); + + socket.on('data', (chunk: string) => { + buffer += chunk; + + // 본문 수신 중에는 종료 표식(.)만 본다. + if (inData) { + const terminator = buffer.indexOf('\r\n.\r\n'); + if (terminator === -1) return; + + buffer = buffer.slice(terminator + 5); + inData = false; + onMessage(); + socket.write('250 2.0.0 Ok: queued\r\n'); + } + + let newline = buffer.indexOf('\r\n'); + while (! inData && newline !== -1) { + const line = buffer.slice(0, newline); + buffer = buffer.slice(newline + 2); + + const verb = line.split(' ')[0].toUpperCase(); + + if (verb === 'EHLO' || verb === 'HELO') { + // 마지막 줄만 하이픈 없이 — 그래야 클라이언트가 목록의 끝을 안다. + socket.write('250-g7-test-sink\r\n250 SIZE 10485760\r\n'); + } else if (verb === 'DATA') { + socket.write('354 End data with .\r\n'); + inData = true; + } else if (verb === 'QUIT') { + socket.write('221 2.0.0 Bye\r\n'); + socket.end(); + return; + } else { + // MAIL FROM / RCPT TO / RSET / NOOP 등 — 전부 수락한다. + socket.write('250 2.0.0 Ok\r\n'); + } + + newline = buffer.indexOf('\r\n'); + } + }); + + socket.on('error', () => { + // 클라이언트가 먼저 끊는 것은 정상이다 — 테스트를 실패시키지 않는다. + }); +} + +/** + * SMTP 싱크를 띄운다. + * + * @param preferredPort 우선 시도할 포트 (사용 중이면 다음 포트로) + * @returns 싱크 핸들 + */ +export async function startSmtpSink(preferredPort = 2525): Promise { + let count = 0; + + const server: Server = createServer((socket) => { + handleConnection(socket, () => { + count += 1; + }); + }); + + const port = await new Promise((resolvePort, rejectPort) => { + let attempt = 0; + + const tryListen = (candidate: number): void => { + server.once('error', (error: NodeJS.ErrnoException) => { + if (error.code === 'EADDRINUSE' && attempt < 20) { + attempt += 1; + tryListen(candidate + 1); + return; + } + rejectPort(error); + }); + + server.listen(candidate, '127.0.0.1', () => { + const address = server.address(); + resolvePort(typeof address === 'object' && address ? address.port : candidate); + }); + }; + + tryListen(preferredPort); + }); + + return { + port, + received: () => count, + close: () => + new Promise((resolveClose) => { + server.close(() => resolveClose()); + // 열려 있는 연결이 남아도 테스트 종료를 막지 않는다. + server.unref(); + }), + }; +} diff --git a/tests/Playwright/fixtures/two-factor.ts b/tests/Playwright/fixtures/two-factor.ts new file mode 100644 index 00000000..a5a8f02b --- /dev/null +++ b/tests/Playwright/fixtures/two-factor.ts @@ -0,0 +1,283 @@ +/** + * Playwright 2단계 인증 fixture (코어 영역). + * + * 로그인 2단계 인증은 **사이트 설정과 메일 발송**에 의존한다. 브라우저만으로는 재현할 수 없으므로 + * 이 fixture 가 세 가지를 가역적으로 준비한다. + * + * ① 보안 환경설정 `security.two_factor_auth` 를 켜고, 끝나면 원래 값으로 되돌린다. + * ② 메일을 받아서 버리는 로컬 SMTP 싱크를 띄우고 메일 설정을 그쪽으로 돌린다. 끝나면 + * 원래 값으로 되돌린다. (실제 메일을 보내면 테스트가 외부 상태를 바꾼다) + * `log` 메일러를 쓰지 않는 이유는 `smtp-sink.ts` 머리말 참조. + * ③ 인증번호는 해시로만 저장되어 되읽을 수 없으므로, 알려진 코드를 심는다 + * (`php artisan playwright:seed-two-factor --plant=…`). + * + * 보안 설정은 **관리자 설정 API** 로 바꾼다 — 운영자가 화면에서 하는 것과 같은 경로다. + * 메일 설정만 파일을 직접 다룬다: 원래 값(빈 SMTP 호스트)은 저장 검증을 통과하지 못해 + * API 로는 **되돌릴 수 없기 때문**이다. 파일은 바이트 단위로 백업했다가 그대로 복원한다. + * + * 원복은 실패해도 조용히 넘기지 않는다 — 되돌리지 못한 채 끝나면 사이트가 2단계 인증이 켜진 + * 상태로 남아 이후 모든 로그인이 막힌다. + */ +import type { APIRequestContext } from '@playwright/test'; +import { startSmtpSink, type SmtpSink } from './smtp-sink'; +import { execSync } from 'node:child_process'; +import { copyFileSync, existsSync, readFileSync, rmSync, writeFileSync } from 'node:fs'; +import { dirname, resolve } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const __dirname = dirname(fileURLToPath(import.meta.url)); + +/** artisan 이 있는 코어 루트 */ +function coreRoot(): string { + return process.env.G7_ROOT || resolve(__dirname, '../../../'); +} + +/** + * 관리자 설정 API 경로. + * + * 절대 URL 을 만들지 않는다 — Playwright 의 `request` 컨텍스트가 `baseURL` 과 + * `ignoreHTTPSErrors` 를 이미 갖고 있다. Node 의 `fetch` 를 쓰면 자체 서명 인증서를 쓰는 + * 개발 호스트에서 TLS 로 막히고, 그것을 우회하려면 이 파일이 TLS 검증을 끄게 된다. + */ +const SETTINGS_PATH = '/api/admin/settings'; + +/** + * `playwright:seed-two-factor` 를 실행하고 마지막 출력 줄을 돌려준다. + * + * @param args 커맨드 인자 (예: ['--plant=', '--code=135790']) + * @returns stdout 의 마지막 비어있지 않은 줄 + */ +export function runSeedTwoFactor(args: string[]): string { + const command = `php artisan playwright:seed-two-factor ${args.join(' ')}`.trim(); + + let lastError: unknown; + for (let attempt = 0; attempt < 3; attempt += 1) { + try { + const stdout = execSync(command, { + cwd: coreRoot(), + encoding: 'utf-8', + env: { ...process.env, G7_PLAYWRIGHT_BYPASS: '1' }, + }); + const lines = stdout.split(/\r?\n/).filter((line) => line.trim().length > 0); + if (lines.length === 0) { + throw new Error(`playwright:seed-two-factor 가 빈 응답을 반환했습니다: ${command}`); + } + return lines[lines.length - 1].trim(); + } catch (error) { + lastError = error; + if (attempt < 2) { + Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 400); + } + } + } + throw lastError instanceof Error ? lastError : new Error(String(lastError)); +} + +/** + * 알려진 인증번호를 challenge 에 심는다. + * + * @param challengeId 로그인 응답이 돌려준 challenge UUID + * @param code 심을 인증번호 (기본 135790) + * @returns 심어 둔 인증번호 + */ +export function plantTwoFactorCode(challengeId: string, code = '135790'): string { + return runSeedTwoFactor([`--plant=${challengeId}`, `--code=${code}`, '--gc-hours=0']); +} + +/** + * 실측이 만든 테스트 계정을 전부 제거한다. + * + * 이 계정들은 알려진 비밀번호를 갖고, 관리자용은 관리자 역할까지 갖는다 — 나이 기준 정리를 + * 기다리면 그 사이가 그대로 열린 문이다. 실측이 끝나면 즉시 지운다. + * + * @returns 커맨드의 마지막 출력 줄 + */ +export function purgeTwoFactorUsers(): string { + return runSeedTwoFactor(['--purge-users', '--gc-hours=0']); +} + +/** + * 알려진 비밀번호를 가진 Active 테스트 계정을 준비한다. + * + * @param suffix 계정 구분 접미사 (예: 'user' / 'nonadmin') + * @param options admin 역할 부여 여부와 비밀번호 + * @returns 준비된 계정 이메일 + */ +export function ensureTwoFactorUser( + suffix: string, + options: { admin?: boolean; password?: string } = {} +): string { + const args = [`--ensure-user=${suffix}`, `--password=${options.password ?? 'Passw0rd!2fa'}`]; + if (options.admin) args.push('--admin'); + return runSeedTwoFactor(args); +} + +/** 되돌리기 위해 보관하는 상태 */ +type TwoFactorFixtureState = { + /** 보안 탭 전체 값 (API 로 그대로 되쓴다) */ + security: Record; + /** 메일 설정 파일 백업 경로 (없으면 파일 자체가 없던 설치) */ + mailBackupPath: string | null; + sink: SmtpSink; +}; + +/** 메일 설정 파일 경로 */ +function mailSettingsPath(): string { + return resolve(coreRoot(), 'storage/app/settings/mail.json'); +} + +/** + * 메일 설정을 SMTP 싱크로 돌린다. + * + * 값 3개만 줄 단위로 치환한다 — 재직렬화하면 운영자 파일의 서식이 통째로 바뀐다. + * `encryption` 을 비우는 이유는 싱크가 STARTTLS 를 제공하지 않기 때문이다. + * + * @param port 싱크 포트 + * @returns 백업 파일 경로 (설정 파일이 없으면 null) + */ +function redirectMailToSink(port: number): string | null { + const path = mailSettingsPath(); + if (! existsSync(path)) { + return null; + } + + const backupPath = `${path}.bak-playwright-2fa`; + copyFileSync(path, backupPath); + + const original = readFileSync(path, 'utf-8'); + const swapped = original + .replace(/"host"(\s*):(\s*)"[^"]*"/, `"host"$1:$2"127.0.0.1"`) + .replace(/"port"(\s*):(\s*)\d+/, `"port"$1:$2${port}`) + .replace(/"encryption"(\s*):(\s*)("[^"]*"|null)/, '"encryption"$1:$2""'); + + writeFileSync(path, swapped, 'utf-8'); + + return backupPath; +} + +/** + * 설정 탭 하나를 통째로 저장한다. + * + * @param request Playwright API 요청 컨텍스트 + * @param token 관리자 Sanctum 토큰 + * @param tab 설정 탭 + * @param values 저장할 탭 전체 값 + */ +async function writeSettingsTab( + request: APIRequestContext, + token: string, + tab: string, + values: Record +): Promise { + const write = await request.post(SETTINGS_PATH, { + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + Authorization: `Bearer ${token}`, + }, + data: { _tab: tab, [tab]: values }, + }); + + if (!write.ok()) { + throw new Error(`${tab} 설정 저장 실패: HTTP ${write.status()} — ${await write.text()}`); + } +} + +/** + * 설정 탭 하나를 읽고, 지정한 키만 바꿔 저장한다. + * + * 저장 페이로드는 `{ _tab, <탭>: { ... } }` 형태이고 탭 **전체**가 검증되므로, 바꿀 키만 + * 보내면 나머지 필수 항목이 422 로 거부된다. 현재 값을 그대로 되돌려 보내 다른 설정은 + * 건드리지 않는다. + * + * @param request Playwright API 요청 컨텍스트 (baseURL·ignoreHTTPSErrors 상속) + * @param token 관리자 Sanctum 토큰 + * @param tab 설정 탭 (`security` · `mail` 등) + * @param patch 바꿀 키-값 + * @returns 바꾸기 전의 탭 전체 값 (원복에 그대로 쓴다) + */ +async function patchSettingsTab( + request: APIRequestContext, + token: string, + tab: string, + patch: Record +): Promise> { + const read = await request.get(`${SETTINGS_PATH}?tab=${tab}`, { + headers: { + Accept: 'application/json', + Authorization: `Bearer ${token}`, + }, + }); + + if (!read.ok()) { + throw new Error(`${tab} 설정 조회 실패: HTTP ${read.status()}`); + } + + const body = await read.json(); + const current = (body?.data?.[tab] ?? {}) as Record; + + // `_meta` 같은 내부 키는 저장 페이로드에서 제외한다. + const previous = Object.fromEntries( + Object.entries(current).filter(([key]) => !key.startsWith('_')) + ); + + await writeSettingsTab(request, token, tab, { ...previous, ...patch }); + + return previous; +} + +/** + * 2단계 인증 실측 환경을 준비한다. + * + * @param request Playwright API 요청 컨텍스트 + * @param token 관리자 Sanctum 토큰 (`core.settings.read` · `core.settings.update`) + * @returns 되돌리기에 필요한 상태 + */ +export async function enableTwoFactorFixture( + request: APIRequestContext, + token: string +): Promise { + // 메일 경로를 먼저 돌린다 — 2단계 인증을 켠 뒤에 코드가 발행되면 실제 메일이 나간다. + const sink = await startSmtpSink(); + const mailBackupPath = redirectMailToSink(sink.port); + + const security = await patchSettingsTab(request, token, 'security', { two_factor_auth: true }); + + return { security, mailBackupPath, sink }; +} + +/** + * 실측 환경을 되돌린다. + * + * 되돌리지 못한 채 끝나면 사이트가 2단계 인증이 켜진 상태로 남아 이후 모든 로그인이 막힌다 — + * 실패는 삼키지 않고 그대로 던진다. 보안 설정을 먼저 되돌리고, 그 실패가 메일 설정 복원을 + * 가리지 않도록 둘 다 시도한 뒤에 던진다. + * + * @param request Playwright API 요청 컨텍스트 + * @param token 관리자 Sanctum 토큰 + * @param state `enableTwoFactorFixture` 가 돌려준 상태 + */ +export async function restoreTwoFactorFixture( + request: APIRequestContext, + token: string, + state: TwoFactorFixtureState +): Promise { + let firstError: unknown; + + try { + await writeSettingsTab(request, token, 'security', state.security); + } catch (error) { + firstError = error; + } + + if (state.mailBackupPath && existsSync(state.mailBackupPath)) { + copyFileSync(state.mailBackupPath, mailSettingsPath()); + rmSync(state.mailBackupPath, { force: true }); + } + + await state.sink.close(); + + if (firstError) { + throw firstError; + } +} diff --git a/tests/Playwright/specs/auth/two-factor-login.spec.ts b/tests/Playwright/specs/auth/two-factor-login.spec.ts new file mode 100644 index 00000000..e28783cb --- /dev/null +++ b/tests/Playwright/specs/auth/two-factor-login.spec.ts @@ -0,0 +1,520 @@ +/** + * 로그인 2단계 인증 종단 검증 (공개 #133). + * + * 2단계 인증을 켜면 서버가 로그인에 **두 가지 형태의 200** 을 돌려준다. 화면이 한 형태만 + * 가정하면 영문 오류(`Cannot read properties of undefined`)가 뜨고 로그인이 불가능해지며, + * 관리자 로그인은 서버 오류가 되어 설정을 되돌릴 수단까지 사라진다. + * + * 이 spec 은 사이트 설정과 메일 설정을 **가역적으로** 바꾼다 — `test.afterAll` 이 실패해도 + * 되돌아가도록 fixture 가 원복을 담당한다. 되돌리지 못하면 이후 모든 로그인이 막히므로 + * 반드시 `mode: 'serial'` 로 한 워커에서만 실행한다. + * + * // @scenario controller=user,admin | two_factor=on | code=valid,invalid | resend=active + * // @effects challenge_response_has_no_token, code_step_rendered_on_challenge, no_raw_typeerror_text, resend_cancels_previous_challenge, non_admin_two_factor_revokes_token + */ +import { test, expect, issueToken } from '../../fixtures/auth'; +import { + enableTwoFactorFixture, + ensureTwoFactorUser, + plantTwoFactorCode, + purgeTwoFactorUsers, + restoreTwoFactorFixture, +} from '../../fixtures/two-factor'; + +test.describe.configure({ mode: 'serial' }); + +const PASSWORD = 'Passw0rd!2fa'; + +let adminToken: string; +let fixtureRequest: import('@playwright/test').APIRequestContext | undefined; +let fixtureState: Awaited>; +let memberEmail: string; +let adminEmail: string; +let nonAdminEmail: string; + +test.beforeAll(async ({ playwright }) => { + adminToken = issueToken('core.settings.read', 'core.settings.update'); + + memberEmail = ensureTwoFactorUser('user', { password: PASSWORD }); + adminEmail = ensureTwoFactorUser('admin', { admin: true, password: PASSWORD }); + nonAdminEmail = ensureTwoFactorUser('nonadmin', { password: PASSWORD }); + + // worker 범위 request 컨텍스트 — baseURL·ignoreHTTPSErrors 는 설정에서 온다. + fixtureRequest = await playwright.request.newContext({ + baseURL: process.env.PLAYWRIGHT_BASE_URL, + ignoreHTTPSErrors: true, + }); + + fixtureState = await enableTwoFactorFixture(fixtureRequest, adminToken); +}); + +test.afterAll(async () => { + // 되돌리지 못한 채 끝나면 사이트의 모든 로그인이 2단계 인증을 요구하게 된다. + if (fixtureState && fixtureRequest) { + await restoreTwoFactorFixture(fixtureRequest, adminToken, fixtureState); + } + + await fixtureRequest?.dispose(); + + // 알려진 비밀번호를 가진 계정(관리자 포함)을 남기지 않는다. + purgeTwoFactorUsers(); +}); + +/** + * 로그인 응답에서 challenge_id 를 읽는다. + * + * @param page Playwright 페이지 + * @param urlPart 대기할 요청 경로 조각 + * @param submit 제출을 수행하는 함수 + */ +async function submitAndReadChallenge( + page: import('@playwright/test').Page, + urlPart: string, + submit: () => Promise +): Promise { + const [response] = await Promise.all([ + page.waitForResponse((r) => r.url().includes(urlPart) && r.request().method() === 'POST'), + submit(), + ]); + + const body = await response.json(); + expect(response.status(), '2단계 인증이 켜져 있으면 로그인은 200 챌린지를 돌려준다').toBe(200); + expect(body?.data?.two_factor_required).toBe(true); + // 코드 확인 전에 토큰이 실리면 2단계 인증이 없는 것과 같다. + expect(body?.data?.token, '코드 확인 전에 토큰이 발급되었습니다').toBeFalsy(); + + return String(body.data.challenge_id); +} + +test('사용자 로그인 — 인증번호 단계로 전환되고 코드 확인 후 로그인된다', async ({ page }) => { + await page.goto('/login'); + await page.waitForLoadState('domcontentloaded', { timeout: 30_000 }); + + const challengeId = await submitAndReadChallenge(page, '/api/auth/login', async () => { + await page.fill('input[name="email"]', memberEmail); + await page.fill('input[name="password"]', PASSWORD); + await page.click('form button[type="submit"]'); + }); + + // 2단계 입력이 나타나고 1단계 입력은 사라진다. + const codeInput = page.locator('input[name="two_factor_code"]'); + await expect(codeInput).toBeVisible({ timeout: 10_000 }); + await expect(page.locator('input[name="email"]')).toHaveCount(0); + + // 영문 TypeError 원문이 화면에 남으면 안 된다. + await expect(page.locator('body')).not.toContainText('Cannot read properties of undefined'); + + // 오답 → 오류 문구, 로그인 미완료 + await codeInput.fill('000000'); + await page.click('form button[type="submit"]'); + await expect(page.locator('[role="alert"]')).toBeVisible({ timeout: 10_000 }); + expect(page.url()).toContain('/login'); + + // 정답 → 홈 이동 + const code = plantTwoFactorCode(challengeId); + await codeInput.fill(code); + await page.click('form button[type="submit"]'); + + await page.waitForFunction(() => !window.location.pathname.startsWith('/login'), { + timeout: 15_000, + }); + const token = await page.evaluate(() => localStorage.getItem('auth_token')); + expect(token, '로그인 완료 후 토큰이 저장되어야 합니다').toBeTruthy(); + expect(token).not.toBe('undefined'); +}); + +test('사용자 로그인 — 인증번호 다시 받기는 새 challenge 를 발급하고 입력을 비운다', async ({ page }) => { + await page.goto('/login'); + await page.waitForLoadState('domcontentloaded', { timeout: 30_000 }); + + const first = await submitAndReadChallenge(page, '/api/auth/login', async () => { + await page.fill('input[name="email"]', memberEmail); + await page.fill('input[name="password"]', PASSWORD); + await page.click('form button[type="submit"]'); + }); + + const codeInput = page.locator('input[name="two_factor_code"]'); + await expect(codeInput).toBeVisible({ timeout: 10_000 }); + await codeInput.fill('111111'); + + const [resendResponse] = await Promise.all([ + page.waitForResponse((r) => r.url().includes('/two-factor/resend') && r.request().method() === 'POST'), + page.getByRole('button', { name: /다시|Resend|再送/ }).click(), + ]); + + const resendBody = await resendResponse.json(); + expect(resendResponse.status()).toBe(200); + const second = String(resendBody.data.challenge_id); + expect(second, '재발송이 같은 challenge 를 돌려주면 새 코드가 발송되지 않은 것입니다').not.toBe(first); + + // 앞서 입력한 값이 남아 있으면 새 코드를 받았는데 옛 값으로 제출된다. + await expect(codeInput).toHaveValue(''); + + const code = plantTwoFactorCode(second); + await codeInput.fill(code); + await page.click('form button[type="submit"]'); + + await page.waitForFunction(() => !window.location.pathname.startsWith('/login'), { + timeout: 15_000, + }); +}); + +/** + * 인증 단계 상태는 전역이라 화면을 떠나도 남는다. 그래서 되돌리는 통로가 셋 다 살아 있어야 한다 — + * 「처음부터」 버튼 · 새로고침 · 다른 화면으로 나갔다 돌아오기. 하나라도 빠지면 사용자가 1단계로 + * 돌아오지 못한 채 이미 만료된 challenge 앞에 갇힌다. + * + * 새로고침 축은 잘못 저장된 토큰(`"undefined"`) 회귀도 함께 잡는다 — 그 값이 남으면 이후 요청이 + * 전부 401 이 되어 `/login?reason=session_expired` 로 튕겼다(공개 #133 의 두 번째 증상). + * + * // @scenario controller=user | two_factor=on | action=restart + * // @effects restart_resets_to_credential_step, init_actions_reset_two_factor_state + */ +test('인증번호 단계 — 처음부터·새로고침·재진입 모두 1단계로 돌아온다', async ({ page }) => { + await page.goto('/login'); + await page.waitForLoadState('domcontentloaded', { timeout: 30_000 }); + + const enterCodeStep = async () => { + await submitAndReadChallenge(page, '/api/auth/login', async () => { + await page.fill('input[name="email"]', memberEmail); + await page.fill('input[name="password"]', PASSWORD); + await page.click('form button[type="submit"]'); + }); + await expect(page.locator('input[name="two_factor_code"]')).toBeVisible({ timeout: 10_000 }); + }; + + const expectCredentialStep = async () => { + await expect(page.locator('input[name="email"]')).toBeVisible({ timeout: 10_000 }); + await expect(page.locator('input[name="two_factor_code"]')).toHaveCount(0); + }; + + // ① 「처음부터」 버튼 + await enterCodeStep(); + await page.getByRole('button', { name: /처음부터|Start over|最初から/ }).click(); + await expectCredentialStep(); + + // ② 새로고침 — 잘못된 토큰이 남았다면 여기서 session_expired 로 튕긴다. + await enterCodeStep(); + await page.reload({ waitUntil: 'domcontentloaded' }); + expect(page.url(), '새로고침이 만료 안내로 튕기면 토큰이 잘못 저장된 것입니다').not.toContain( + 'reason=session_expired' + ); + const staleToken = await page.evaluate(() => localStorage.getItem('auth_token')); + expect(staleToken, '코드 확인 전에는 토큰이 저장되면 안 됩니다').not.toBe('undefined'); + await expectCredentialStep(); + + // ③ 다른 화면으로 나갔다 돌아오기 — init_actions 리셋이 없으면 2단계가 그대로 남는다. + await enterCodeStep(); + await page.goto('/register'); + await page.waitForLoadState('domcontentloaded', { timeout: 30_000 }); + await page.goto('/login'); + await page.waitForLoadState('domcontentloaded', { timeout: 30_000 }); + await expectCredentialStep(); +}); + +test('관리자 로그인 — 챌린지 응답이 서버 오류가 아니고 코드 확인 후 관리자 화면으로 간다', async ({ page }) => { + await page.goto('/admin/login'); + await page.waitForLoadState('domcontentloaded', { timeout: 30_000 }); + + const challengeId = await submitAndReadChallenge(page, '/api/auth/admin/login', async () => { + await page.fill('input[name="email"]', adminEmail); + await page.fill('input[name="password"]', PASSWORD); + await page.click('#login_submit_button'); + }); + + const codeInput = page.locator('#two_factor_code'); + await expect(codeInput).toBeVisible({ timeout: 10_000 }); + await expect(page.locator('body')).not.toContainText('Cannot read properties of undefined'); + + await codeInput.fill(plantTwoFactorCode(challengeId)); + await page.locator('#login_two_factor_submit').click(); + + await page.waitForFunction(() => !window.location.pathname.startsWith('/admin/login'), { + timeout: 15_000, + }); +}); + +test('관리자 로그인 — 관리자가 아닌 계정은 코드 확인에 성공해도 거부되고 토큰이 남지 않는다', async ({ page }) => { + await page.goto('/admin/login'); + await page.waitForLoadState('domcontentloaded', { timeout: 30_000 }); + + const challengeId = await submitAndReadChallenge(page, '/api/auth/admin/login', async () => { + await page.fill('input[name="email"]', nonAdminEmail); + await page.fill('input[name="password"]', PASSWORD); + await page.click('#login_submit_button'); + }); + + const codeInput = page.locator('#two_factor_code'); + await expect(codeInput).toBeVisible({ timeout: 10_000 }); + await codeInput.fill(plantTwoFactorCode(challengeId)); + + const [verifyResponse] = await Promise.all([ + page.waitForResponse((r) => r.url().includes('/admin/login/two-factor') && r.request().method() === 'POST'), + page.locator('#login_two_factor_submit').click(), + ]); + + expect(verifyResponse.status()).toBe(403); + + // 코드 확인 시점에 발급된 토큰이 회수되지 않으면 유효한 세션이 남는다. + const token = await page.evaluate(() => localStorage.getItem('auth_token')); + expect(token, '거부된 로그인이 토큰을 남겼습니다').toBeFalsy(); + expect(page.url()).toContain('/admin/login'); +}); + +test('인증번호 단계 — Enter 키는 확인만 보내고 새 challenge 를 발급하지 않는다', async ({ page }) => { + await page.goto('/login'); + await page.waitForLoadState('domcontentloaded', { timeout: 30_000 }); + + const challengeId = await submitAndReadChallenge(page, '/api/auth/login', async () => { + await page.fill('input[name="email"]', memberEmail); + await page.fill('input[name="password"]', PASSWORD); + await page.click('form button[type="submit"]'); + }); + + const codeInput = page.locator('input[name="two_factor_code"]'); + await expect(codeInput).toBeVisible({ timeout: 10_000 }); + + // 세 자리까지는 확인 버튼이 눌리지 않는다. + await codeInput.fill('123'); + const verifyButton = page.locator('form button[type="submit"]'); + await expect(verifyButton).toBeDisabled(); + + await codeInput.fill(plantTwoFactorCode(challengeId)); + await expect(verifyButton).toBeEnabled(); + + const posts: string[] = []; + page.on('request', (req) => { + if (req.method() === 'POST' && req.url().includes('/api/auth/')) posts.push(req.url()); + }); + + await codeInput.press('Enter'); + await page.waitForFunction(() => !window.location.pathname.startsWith('/login'), { + timeout: 15_000, + }); + + const verifyCalls = posts.filter((u) => u.includes('/login/two-factor')).length; + const loginCalls = posts.filter((u) => u.endsWith('/api/auth/login')).length; + + expect(verifyCalls, 'Enter 가 인증번호 확인을 보내지 않았습니다').toBe(1); + // 상호배타 조건이 빠지면 Enter 가 새 challenge 를 발급해 흐름이 깨진다. + expect(loginCalls, 'Enter 가 비밀번호 단계를 다시 호출했습니다').toBe(0); +}); + +test('인증번호 단계 — 모바일 폭과 일본어 로케일에서 문구·배치가 깨지지 않는다', async ({ page }) => { + await page.setViewportSize({ width: 375, height: 812 }); + await page.addInitScript(() => localStorage.setItem('g7_locale', 'ja')); + + await page.goto('/login'); + await page.waitForLoadState('domcontentloaded', { timeout: 30_000 }); + + await submitAndReadChallenge(page, '/api/auth/login', async () => { + await page.fill('input[name="email"]', memberEmail); + await page.fill('input[name="password"]', PASSWORD); + await page.click('form button[type="submit"]'); + }); + + await expect(page.locator('input[name="two_factor_code"]')).toBeVisible({ timeout: 10_000 }); + + // 다국어 키가 해석되지 않으면 `$t:` 토큰이 그대로 화면에 남는다. + const body = (await page.locator('body').innerText()) ?? ''; + expect(body).not.toContain('$t:'); + expect(body).not.toContain('auth.two_factor'); + + // 다시 받기·처음부터 두 버튼이 좁은 폭에서도 컨테이너를 넘지 않는다. + const buttons = page.locator('form button[type="button"]'); + const count = await buttons.count(); + expect(count).toBeGreaterThanOrEqual(2); + + for (let i = 0; i < count; i += 1) { + const box = await buttons.nth(i).boundingBox(); + if (box) { + expect(box.width, '버튼이 375px 화면을 넘칩니다').toBeLessThanOrEqual(375); + } + } +}); + +test('공개 본인인증 화면으로는 로그인 challenge 를 소진할 수 없다', async ({ page }) => { + await page.goto('/login'); + await page.waitForLoadState('domcontentloaded', { timeout: 30_000 }); + + const challengeId = await submitAndReadChallenge(page, '/api/auth/login', async () => { + await page.fill('input[name="email"]', memberEmail); + await page.fill('input[name="password"]', PASSWORD); + await page.click('form button[type="submit"]'); + }); + + const code = plantTwoFactorCode(challengeId); + + const status = await page.evaluate(async (id) => { + const res = await fetch(`/api/identity/challenges/${id}/verify`, { + method: 'POST', + headers: { 'Content-Type': 'application/json', Accept: 'application/json' }, + body: JSON.stringify({ code: '135790' }), + }); + return res.status; + }, challengeId); + + expect(status, '로그인 challenge 가 공개 본인인증 경로로 소진되었습니다').toBe(403); + + // 거부는 상태를 바꾸지 않으므로 로그인은 그대로 완료된다. + await page.locator('input[name="two_factor_code"]').fill(code); + await page.click('form button[type="submit"]'); + await page.waitForFunction(() => !window.location.pathname.startsWith('/login'), { + timeout: 15_000, + }); +}); + +/** + * 인증번호를 여러 번 틀려도 그 challenge 로 계속 시도할 수 있어야 한다. + * + * 오답 때마다 입력을 비우면 오타 한 글자를 고치려던 사용자가 전체를 다시 친다 — + * 입력을 남기는 것이 확정된 동작이다(레이아웃의 `loginTwoFactor` onError 는 code 를 + * 건드리지 않는다). 비우도록 바뀌면 아래 `toHaveValue` 가 red 가 된다. + * + * // @scenario controller=user | two_factor=on | code=invalid,valid + * // @effects invalid_code_retries_until_success + */ +test('인증번호 단계 — 세 번 틀려도 입력이 남고 네 번째 정답으로 로그인된다', async ({ page }) => { + await page.goto('/login'); + await page.waitForLoadState('domcontentloaded', { timeout: 30_000 }); + + const challengeId = await submitAndReadChallenge(page, '/api/auth/login', async () => { + await page.fill('input[name="email"]', memberEmail); + await page.fill('input[name="password"]', PASSWORD); + await page.click('form button[type="submit"]'); + }); + + const codeInput = page.locator('input[name="two_factor_code"]'); + await expect(codeInput).toBeVisible({ timeout: 10_000 }); + + // 본인인증 정책 상한(max_attempts=5) 안이라 세 번까지는 challenge 가 살아 있다. + for (let attempt = 1; attempt <= 3; attempt += 1) { + await codeInput.fill('000000'); + + const [response] = await Promise.all([ + page.waitForResponse( + (r) => + r.url().includes('/api/auth/login/two-factor') && + !r.url().includes('/resend') && + r.request().method() === 'POST' + ), + page.click('form button[type="submit"]'), + ]); + + expect(response.status(), `${attempt}회째 오답이 거부되지 않았습니다`).toBe(401); + await expect(page.locator('[role="alert"]')).toBeVisible({ timeout: 10_000 }); + + // 오답 뒤에도 입력은 남는다. + await expect(codeInput).toHaveValue('000000'); + expect(page.url()).toContain('/login'); + } + + await codeInput.fill(plantTwoFactorCode(challengeId)); + await page.click('form button[type="submit"]'); + + await page.waitForFunction(() => !window.location.pathname.startsWith('/login'), { + timeout: 15_000, + }); +}); + +/** + * 로그인이 끝난 직후의 상태 — 2단계 흔적이 남으면 다음에 로그인 화면을 열었을 때 + * 비밀번호 단계가 아니라 코드 단계가 뜬다. + * + * // @scenario controller=user | two_factor=on | code=valid + * // @effects session_state_cleared_after_two_factor_success + */ +test('인증 완료 직후 — 2단계 상태가 지워지고 사용자·토큰이 자리 잡는다', async ({ page }) => { + await page.goto('/login'); + await page.waitForLoadState('domcontentloaded', { timeout: 30_000 }); + + const challengeId = await submitAndReadChallenge(page, '/api/auth/login', async () => { + await page.fill('input[name="email"]', memberEmail); + await page.fill('input[name="password"]', PASSWORD); + await page.click('form button[type="submit"]'); + }); + + await page.locator('input[name="two_factor_code"]').fill(plantTwoFactorCode(challengeId)); + await page.click('form button[type="submit"]'); + await page.waitForFunction(() => !window.location.pathname.startsWith('/login'), { + timeout: 15_000, + }); + + const state = await page.evaluate(() => { + const w = window as unknown as Record; + const globalState = w.__templateApp?.getGlobalState?.() ?? w.G7Core?.state?.get?.() ?? {}; + + return { + twoFactor: globalState.twoFactor ?? null, + isLoggingIn: globalState.isLoggingIn ?? null, + uuid: String(globalState.currentUser?.uuid ?? ''), + email: String(globalState.currentUser?.email ?? ''), + token: String(localStorage.getItem('auth_token') ?? ''), + }; + }); + + expect(state.twoFactor, '로그인이 끝났는데 2단계 상태가 남아 있습니다').toBeNull(); + expect(state.isLoggingIn, '로딩 표시가 켜진 채 남았습니다').toBe(false); + expect(state.uuid.length, '로그인한 사용자가 전역 상태에 실리지 않았습니다').toBeGreaterThan(0); + expect(state.email).toBe(memberEmail); + // `"undefined"` 가 저장되던 결함(#133)은 길이 9 라 존재 검사만으로는 통과한다. + expect(state.token.length, '저장된 토큰이 실제 토큰이 아닙니다').toBeGreaterThan(20); +}); + +/** + * 확인 버튼을 빠르게 두 번 눌러도 인증 요청은 한 번만 나가야 한다. + * + * 두 번 나가면 두 번째가 이미 소진된 challenge 를 확인하려다 실패해, 로그인은 됐는데 + * 오류 문구가 함께 뜨는 상태가 된다. + * + * // @scenario controller=user | two_factor=on | code=valid + * // @effects double_submit_sends_single_verify_request + */ +test('인증번호 단계 — 확인 버튼을 두 번 눌러도 확인 요청은 한 번만 나간다', async ({ page }) => { + await page.goto('/login'); + await page.waitForLoadState('domcontentloaded', { timeout: 30_000 }); + + const challengeId = await submitAndReadChallenge(page, '/api/auth/login', async () => { + await page.fill('input[name="email"]', memberEmail); + await page.fill('input[name="password"]', PASSWORD); + await page.click('form button[type="submit"]'); + }); + + const codeInput = page.locator('input[name="two_factor_code"]'); + await expect(codeInput).toBeVisible({ timeout: 10_000 }); + await codeInput.fill(plantTwoFactorCode(challengeId)); + + const verifyCalls: string[] = []; + page.on('request', (req) => { + if ( + req.method() === 'POST' && + req.url().includes('/api/auth/login/two-factor') && + !req.url().includes('/resend') + ) { + verifyCalls.push(req.url()); + } + }); + + const verifyButton = page.locator('form button[type="submit"]'); + const startedAt = Date.now(); + await verifyButton.click(); + + // 두 번째 클릭이 도달하기 전에 버튼이 잠겨야 한다. 잠금은 제출 시퀀스가 세우는 + // `isLoggingIn` 이 화면에 반영될 때 걸리므로, 그 반영이 사람의 두 번째 클릭보다 + // 빨라야 한다는 뜻이다. + await expect(verifyButton).toBeDisabled({ timeout: 1_000 }); + const lockedAfterMs = Date.now() - startedAt; + expect( + lockedAfterMs, + `확인 버튼이 잠기기까지 ${lockedAfterMs}ms 가 걸렸습니다 — 사람의 두 번째 클릭이 그 사이에 들어옵니다` + ).toBeLessThan(200); + + // 잠긴 뒤의 두 번째 클릭은 브라우저가 비활성 버튼에 전달하지 않는다. + await verifyButton.click({ force: true, timeout: 2_000 }).catch(() => undefined); + + await page.waitForFunction(() => !window.location.pathname.startsWith('/login'), { + timeout: 15_000, + }); + + expect(verifyCalls.length, '확인 요청이 두 번 나갔습니다').toBe(1); +}); diff --git a/tests/Playwright/specs/extension-bundle-loading.spec.ts b/tests/Playwright/specs/extension-bundle-loading.spec.ts index a6466fcc..e4a61968 100644 --- a/tests/Playwright/specs/extension-bundle-loading.spec.ts +++ b/tests/Playwright/specs/extension-bundle-loading.spec.ts @@ -109,8 +109,12 @@ test.describe('확장 병합 번들 로딩', () => { * @effects bundle_css_failure_banner_uses_user_vocabulary */ test('번들 CSS 가 503 이면 안내 항목명이 사용자 어휘다', async ({ page }) => { - await page.route(/\/api\/(modules|plugins)\/bundle[./]css/, (route) => - route.fulfill({ status: 503, contentType: 'text/css', body: '' }), + // 번들 CSS 는 구성에 따라 정적 게시본(`/build/ext/{v}/bundles/*.css`) 또는 API 로 + // 나간다 — 빈 번들도 0바이트로 게시되므로 기본 구성은 정적 URL 이다. 한쪽만 가로채면 + // 그 구성에서 이 시나리오가 발화하지 않은 채 통과한다. + await page.route( + /(\/api\/(modules|plugins)\/bundle[./]css|\/build\/ext\/\d+\/bundles\/(modules|plugins)\.css)/, + (route) => route.fulfill({ status: 503, contentType: 'text/css', body: '' }), ); await page.goto('/'); diff --git a/tests/Playwright/specs/layout-editor/image-widget-value-shape.spec.ts b/tests/Playwright/specs/layout-editor/image-widget-value-shape.spec.ts new file mode 100644 index 00000000..c8c75e29 --- /dev/null +++ b/tests/Playwright/specs/layout-editor/image-widget-value-shape.spec.ts @@ -0,0 +1,377 @@ +/** + * Layout Editor — image 위젯 값 형태(단일 값 슬롯 축약 / 표시모드 억제 / 표현식 보호). + * + * 결함(공개 #135): 코어 `image` 위젯이 내보내는 `{url,size,repeat,position}` 객체를 + * `propValue` 경로가 그대로 `props[key]` 에 기록해, 소비 컴포넌트가 `` 를 + * 받고 브라우저가 `[object Object]` 를 URL 로 해석했다. 이 결함은 **브라우저에서만** + * 드러난다 — 깨진 요청은 SPA catch-all 때문에 404 조차 아니라 200(HTML)을 받고, 편집기 + * 위젯의 미리보기는 정상이며, 서버 로그에도 흔적이 없다. 화면의 엑박이 유일한 증상이다. + * + * 단위(Vitest)는 `applyRecipe` 반환 노드까지만 볼 수 있어 "실제 `` 의 src 가 문자열이고 + * naturalWidth > 0" 을 포착하지 못한다. 그래서 캔버스 DOM 을 직접 재는 spec 을 둔다. + * + * 축 요약(마커 아님 — 평문): image_propvalue_narrowing, display_mode_suppression, binding_expression_guard, styleprop_bundle_regression. + * 효과 요약(마커 아님 — 평문): image_object_narrows_to_url_string_in_propvalue_slot, single_value_slot_control_hides_display_mode_buttons_entirely, bound_value_shows_expression_badge_and_locks_destructive_controls, styleprop_bundle_four_property_decomposition_is_unchanged. + */ +import { test, expect, issueToken, authenticatePage } from '../../fixtures/auth'; +import { SANDBOX_ROUTE, sandboxRouteParam } from '../../fixtures/seed-layout'; +import type { Page } from '@playwright/test'; + +const EDITOR_URL = '/admin/layout-editor/sirsoft-basic?route=%2F'; + +/** + * 공통 레이아웃 편집 모드 — `Header` 의 「로고 이미지」는 `_user_base` 소유다. + * + * 라우트 모드에서 그 노드는 **상속(base) 출처**라 ⓘ 대신 「🔒 공통 레이아웃 편집」이 뜬다. + * 편집해도 저장 시 마스킹이 통째로 폐기하므로 편집 표면을 열지 않는 것이 계약이다 + * (계획서 D9~D11). 따라서 그 컨트롤을 여는 정상 경로는 이 모드다. + */ +const BASE_EDITOR_URL = '/admin/layout-editor/sirsoft-basic?edit=__base__%2F_user_base'; + +/** + * 편집기에 진입해 캔버스 편집 노드가 렌더될 때까지 기다린다. + */ +async function openEditor(page: Page, url: string = EDITOR_URL): Promise { + const token = issueToken('core.templates.layouts.edit'); + await authenticatePage(page, token); + await page.goto(url); + await page.waitForLoadState('domcontentloaded', { timeout: 30_000 }); + await page.waitForSelector('[data-testid="g7le-preview-frame"]', { timeout: 30_000 }); + await page.waitForFunction( + () => document.querySelectorAll('[data-editor-path]').length > 0, + { timeout: 20_000 }, + ); +} + +/** + * 캔버스 위임 click 핸들러로 노드를 선택한다. 패널/오버레이가 좌표를 가로채는 것을 피해 + * 편집기가 실제로 받는 pointer/click 시퀀스를 노드 중앙에 직접 발사한다. + */ +async function selectNode(page: Page, path: string): Promise { + await page.evaluate((p) => { + const el = document.querySelector(`[data-editor-path="${p}"]`); + if (!el) return; + el.scrollIntoView({ block: 'center' }); + const r = el.getBoundingClientRect(); + const cx = r.left + r.width / 2; + const cy = r.top + r.height / 2; + for (const type of ['pointerover', 'pointermove', 'pointerdown', 'mousedown', 'pointerup', 'mouseup', 'click']) { + el.dispatchEvent(new MouseEvent(type, { bubbles: true, clientX: cx, clientY: cy })); + } + }, path); +} + +/** ⓘ → 「속성 설정」 → 속성 편집 모달. 지정한 탭을 연다. */ +async function openPropertyTab(page: Page, tab: 'props' | 'style'): Promise { + await page.waitForSelector('[data-testid="g7le-overlay-info-button"]', { timeout: 10_000 }); + await page.getByTestId('g7le-overlay-info-button').click(); + await page.waitForSelector('[data-testid="g7le-context-menu-edit-props"]', { timeout: 5_000 }); + await page.getByTestId('g7le-context-menu-edit-props').click(); + await page.waitForSelector('[data-testid="g7le-property-modal"]', { timeout: 10_000 }); + await page.getByTestId(`g7le-property-tab-${tab}`).click(); +} + +/** + * `image` 위젯이 열린 상태에서 **단일 값 슬롯 컨트롤**을 찾을 수 있는 노드를 고른다. + * + * 리터럴 `data-editor-path` 를 쓰지 않는다 — 베이스 루트 인덱스가 밀리면 통째로 깨진다. + * 대신 캔버스에서 `Header` 컴포넌트를 이름으로 찾는다(로고 컨트롤의 소유 컴포넌트). + */ +async function findHeaderPath(page: Page): Promise { + return page.evaluate(() => { + const el = document.querySelector('[data-editor-path][data-editor-name="Header"]'); + return el?.getAttribute('data-editor-path') ?? null; + }); +} + +/** + * 시드 화면이 소유한 `Header` 의 편집 경로를 id 로 지목한다. + * + * 시드 화면은 `_user_base` 를 extends 하므로 캔버스에 `Header` 가 **둘** 뜬다 — 상속받은 + * 베이스 헤더(잠금: ⓘ 대신 「공통 레이아웃 편집」)와 시드가 직접 둔 라우트 소유 헤더. + * 이름으로만 찾으면 앞의 잠긴 쪽이 잡혀 속성 모달에 도달하지 못한다. + */ +async function findSandboxHeaderPath(page: Page): Promise { + return page.evaluate(() => { + const el = document.querySelector('[data-editor-path][data-editor-id="e2e_sandbox_header"]'); + return el?.getAttribute('data-editor-path') ?? null; + }); +} + +/** 배경이 보일 만한 박스형 Div — styleProp 묶음(회귀 가드)의 대상 */ +async function pickBoxAreaPath(page: Page): Promise { + return page.evaluate(() => { + const cands = Array.from( + document.querySelectorAll('[data-editor-path][data-editor-name="Div"]'), + ).filter((el) => { + const r = el.getBoundingClientRect(); + return r.width > 200 && r.width < 800 && r.height > 80 && r.height < 320 && r.left > 360; + }); + return cands[0]?.getAttribute('data-editor-path') ?? null; + }); +} + +/** + * 저장 페이로드에서 `logo` prop 값을 찾아 돌려준다 (트리 어디에 있든). + * + * 노드 경로를 리터럴로 박으면 베이스 레이아웃 구조가 한 칸만 바뀌어도 통째로 깨지므로 + * 키 이름으로 전역 탐색한다. + * + * @param value 저장 요청 body (JSON) + * @returns 찾은 `logo` 값, 없으면 undefined + */ +function findLogoProp(value: unknown): unknown { + // 저장 페이로드의 `content` 는 **JSON 문자열**로 실린다 — 파싱하지 않으면 트리를 볼 수 없다. + if (typeof value === 'string') { + if (!value.trim().startsWith('{')) return undefined; + try { + return findLogoProp(JSON.parse(value)); + } catch { + return undefined; + } + } + if (Array.isArray(value)) { + for (const item of value) { + const hit = findLogoProp(item); + if (hit !== undefined) return hit; + } + return undefined; + } + if (!value || typeof value !== 'object') return undefined; + + const record = value as Record; + const props = record.props; + if (props && typeof props === 'object' && 'logo' in (props as Record)) { + return (props as Record).logo; + } + for (const child of Object.values(record)) { + const hit = findLogoProp(child); + if (hit !== undefined) return hit; + } + return undefined; +} + +/** 1x1 PNG — 첨부 업로드용 최소 실물 이미지(naturalWidth>0 을 재려면 실제 파일이어야 한다) */ +const PNG_1PX = Buffer.from( + 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==', + 'base64', +); + +test.describe('@layout-editor image 위젯 값 형태 (공개 #135)', () => { + /** @effects image_object_narrows_to_url_string_in_propvalue_slot, single_value_slot_control_hides_display_mode_buttons_entirely */ + test('로고 이미지 URL 입력 → 캔버스 img src 가 문자열, [object Object] 0건, 모드 버튼 미노출', async ({ page }) => { + await openEditor(page, BASE_EDITOR_URL); + + const headerPath = await findHeaderPath(page); + test.skip(headerPath === null, '캔버스에서 Header 노드를 찾지 못했습니다'); + await selectNode(page, headerPath as string); + await openPropertyTab(page, 'props'); + + const urlInput = page.getByTestId('g7le-image-url'); + test.skip(!(await urlInput.isVisible().catch(() => false)), '로고 이미지 컨트롤이 노출되지 않았습니다'); + + // 저장값이 데이터 연결 표현식이면 컨트롤은 보호 상태로 뜬다(입력칸 readOnly). + // 값을 바꾸려면 「직접 지정으로 바꾸기」로 명시적으로 열어야 한다 — 그 한 번의 + // 의사표시 없이는 환경설정과의 연결이 덮이지 않는다는 것이 계약이다. + const replace = page.getByTestId('g7le-image-expression-replace'); + if (await replace.isVisible().catch(() => false)) { + await replace.click(); + // 해제되면 되돌리기 경로가 함께 열린다(편도 금지). + await expect(page.getByTestId('g7le-image-expression-restore')).toBeVisible(); + } + + // 단일 값 슬롯(propValue) 이므로 표시모드 버튼은 **컨테이너째** 미렌더되어야 한다. + // `disabled` 로 남기면 "URL 을 넣으면 살아나겠지" 라는 거짓 정보를 준다. + await expect(page.getByTestId('g7le-image-modes')).toHaveCount(0); + await expect(page.getByTestId('g7le-image-mode-fill')).toHaveCount(0); + + const url = '/api/templates/sirsoft-basic/layout-attachments/5/file'; + await urlInput.fill(url); + await urlInput.blur(); + + // 핵심 검증 — 캔버스의 실제 가 문자열 src 를 받아야 한다. + // 종전에는 여기에 "[object Object]" 가 들어가 엑박이 됐고, SPA catch-all 때문에 + // 그 요청은 404 조차 아니라 200(HTML) 이라 네트워크 탭으로도 드러나지 않았다. + await expect + .poll( + async () => + page.evaluate((p) => { + const host = document.querySelector(`[data-editor-path="${p}"]`); + const img = host?.querySelector('img'); + return img?.getAttribute('src') ?? ''; + }, headerPath), + { timeout: 8_000 }, + ) + .toContain('layout-attachments/5/file'); + + const objectObjectCount = await page.evaluate(() => + Array.from(document.images).filter((i) => (i.getAttribute('src') ?? '').includes('[object Object]')).length, + ); + expect(objectObjectCount, '문서 어디에도 [object Object] src 가 없어야 합니다').toBe(0); + }); + + /** + * 저장 → 사용자 화면 3단 — 이 결함의 **최종 소비 지점**이다. + * + * 위 케이스는 편집기 캔버스까지만 본다. 그런데 #135 가 실제로 드러난 자리는 저장 이후의 + * 사용자 화면이고, 깨진 요청은 SPA catch-all 때문에 404 조차 아니라 200(HTML) 이라 + * 네트워크 탭·서버 로그 어디에도 흔적이 없다. 그래서 「저장 페이로드가 문자열인가」와 + * 「그 문자열이 실제로 그려지는가(naturalWidth>0)」를 여기서 함께 잰다. + * + * 대상은 **시드 화면(`e2e_sandbox`)** 이다. 이 spec 은 저장(PUT)까지 수행하므로 제품 + * 레이아웃(`_user_base`)을 쓰면 편집 결과가 그대로 영속돼 개발 사이트가 오염된다 + * (`fixtures/seed-layout.ts` 규약). 시드 화면에 라우트 소유 `Header` 를 두어 로고 컨트롤을 + * 라우트 모드에서 열 수 있게 했고, 시드는 globalTeardown 이 통째로 제거한다. + * + * @effects image_object_narrows_to_url_string_in_propvalue_slot, img_string_src_passes_through_unchanged + */ + test('저장 → 사용자 화면에서 문자열 src 로 실제 렌더된다', async ({ page }) => { + // 편집기 진입 + 저장 왕복 + 사용자 화면 재방문 3단이라 기본 30s 로는 부족하다. + test.setTimeout(120_000); + + // 실제로 그려지는지(naturalWidth>0)를 재려면 실존하는 이미지가 필요하다. 고정 id 를 + // 박으면 그 첨부가 지워진 환경에서 조용히 0 이 되므로, 이 spec 이 직접 올리고 지운다. + const token = issueToken('core.templates.layouts.edit'); + const headers = { Authorization: `Bearer ${token}`, Accept: 'application/json' }; + const upload = await page.request.post('/api/admin/templates/sirsoft-basic/layout-attachments', { + headers, + multipart: { + file: { name: 'e2e-logo.png', mimeType: 'image/png', buffer: PNG_1PX }, + }, + }); + test.skip(!upload.ok(), `첨부 업로드 실패(${upload.status()})`); + const attachmentId = (await upload.json())?.data?.id; + test.skip(!attachmentId, '첨부 id 를 받지 못했습니다'); + const url = `/api/templates/sirsoft-basic/layout-attachments/${attachmentId}/file`; + + try { + await openEditor(page, `/admin/layout-editor/sirsoft-basic?route=${sandboxRouteParam()}`); + + const headerPath = await findSandboxHeaderPath(page); + test.skip(headerPath === null, '시드 화면에서 Header 노드를 찾지 못했습니다'); + await selectNode(page, headerPath as string); + await openPropertyTab(page, 'props'); + + const urlInput = page.getByTestId('g7le-image-url'); + test.skip(!(await urlInput.isVisible().catch(() => false)), '로고 이미지 컨트롤이 노출되지 않았습니다'); + + const replace = page.getByTestId('g7le-image-expression-replace'); + if (await replace.isVisible().catch(() => false)) await replace.click(); + + await urlInput.fill(url); + await urlInput.blur(); + + // 속성 모달을 닫고 툴바에서 저장한다. + await page.getByTestId('g7le-property-modal-done').click(); + + // 저장 페이로드의 `logo` 가 **문자열**이어야 한다 — 객체면 수정 전 상태다. + const [put] = await Promise.all([ + page.waitForResponse( + (r) => r.url().includes('/layouts/e2e_sandbox') && r.request().method() === 'PUT', + { timeout: 30_000 }, + ), + page.getByTestId('g7le-toolbar-save').click(), + ]); + expect(put.status(), '저장이 200 이어야 합니다').toBe(200); + + const logo = findLogoProp(put.request().postDataJSON()); + expect(typeof logo, `저장 페이로드의 logo 가 문자열이어야 합니다 (실제: ${JSON.stringify(logo)})`).toBe('string'); + expect(logo).toContain('layout-attachments/5/file'); + + // 3단 — 사용자 화면에서 실제로 그려지는가. + await page.goto(SANDBOX_ROUTE['sirsoft-basic']); + await page.waitForLoadState('domcontentloaded'); + + const measured = await page.evaluate(async () => { + await new Promise((r) => setTimeout(r, 1500)); + const imgs = Array.from(document.images); + const hit = imgs.find((i) => (i.getAttribute('src') ?? '').includes('layout-attachments/5/file')); + return { + src: hit?.getAttribute('src') ?? null, + natural: hit?.naturalWidth ?? 0, + objectObject: imgs.filter((i) => (i.getAttribute('src') ?? '').includes('[object Object]')).length, + }; + }); + + expect(measured.objectObject, '사용자 화면에 [object Object] src 가 없어야 합니다').toBe(0); + expect(measured.src, '사용자 화면의 로고 src 가 저장한 문자열이어야 합니다').toContain( + 'layout-attachments/5/file', + ); + expect(measured.natural, '로고가 실제로 로드되어야 합니다(naturalWidth>0)').toBeGreaterThan(0); + } finally { + // 올린 첨부는 반드시 지운다 — 실패로 중단돼도 서버에 잔여물을 남기지 않는다. + await page.request + .delete(`/api/admin/templates/layout-attachments/${attachmentId}`, { headers }) + .catch(() => undefined); + } + }); + + /** @effects bound_value_shows_expression_badge_and_locks_destructive_controls */ + test('저장값이 데이터 연결 표현식이면 원문 배지 + 파괴적 조작 잠금', async ({ page }) => { + await openEditor(page, BASE_EDITOR_URL); + + const headerPath = await findHeaderPath(page); + test.skip(headerPath === null, '캔버스에서 Header 노드를 찾지 못했습니다'); + await selectNode(page, headerPath as string); + await openPropertyTab(page, 'props'); + + const badge = page.getByTestId('g7le-image-expression'); + // 번들 레이아웃의 기본 저장값은 사이트 로고 설정 표현식이다. 표현식 상태가 아니면 + // (운영자가 이미 고정 URL 을 넣은 사이트) 이 축은 검증 대상이 아니다. + test.skip(!(await badge.isVisible().catch(() => false)), '로고가 표현식 상태가 아닙니다'); + + // 원문이 그대로 보여야 한다 — 빈 피커로 보이면 업로드 1클릭에 표현식이 소리 없이 사라진다. + await expect(badge).toContainText('{{'); + // 미리보기는 깨지므로 렌더하지 않는다(거짓 미리보기 금지). + await expect(page.getByTestId('g7le-image-preview')).toHaveCount(0); + // 파괴적 조작은 잠긴다 — 단, 「직접 지정으로 바꾸기」로 즉시 해제되는 일시적 상태다. + await expect(page.getByTestId('g7le-image-clear')).toBeDisabled(); + await expect(page.getByTestId('g7le-image-url')).toHaveAttribute('readonly', /.*/); + + await page.getByTestId('g7le-image-expression-replace').click(); + await expect(page.getByTestId('g7le-image-expression')).toHaveCount(0); + await expect(page.getByTestId('g7le-image-clear')).toBeEnabled(); + }); + + /** @effects styleprop_bundle_four_property_decomposition_is_unchanged */ + test('회귀 가드 — 배경 이미지(styleProp 묶음) 컨트롤은 모드 버튼과 4속성 분해를 유지', async ({ page }) => { + await openEditor(page); + + const targetPath = await pickBoxAreaPath(page); + test.skip(targetPath === null, '배경을 넣을 박스형 Div 를 찾지 못했습니다'); + await selectNode(page, targetPath as string); + await openPropertyTab(page, 'style'); + + const urlInput = page.getByTestId('g7le-image-url'); + test.skip(!(await urlInput.isVisible().catch(() => false)), '배경 이미지 컨트롤이 노출되지 않았습니다'); + + // 다중 props 묶음이라 표시모드 버튼은 그대로 있어야 한다(단일 슬롯 억제가 여기까지 + // 번지면 배경 편집 기능이 통째로 죽는다). + await expect(page.getByTestId('g7le-image-modes')).toBeVisible(); + + const url = 'https://example.com/api/templates/sirsoft-basic/layout-attachments/5/file'; + await urlInput.fill(url); + await urlInput.blur(); + + await expect + .poll( + async () => + page.evaluate((p) => { + const el = document.querySelector(`[data-editor-path="${p}"]`); + return el ? getComputedStyle(el).backgroundImage : ''; + }, targetPath), + { timeout: 8_000 }, + ) + .toContain('layout-attachments/5/file'); + + const snap = await page.evaluate((p) => { + const el = document.querySelector(`[data-editor-path="${p}"]`) as HTMLElement | null; + const cs = el ? getComputedStyle(el) : null; + return { size: cs?.backgroundSize, repeat: cs?.backgroundRepeat, image: cs?.backgroundImage }; + }, targetPath); + expect(snap.size).toBe('cover'); + expect(snap.repeat).toBe('no-repeat'); + expect(snap.image, 'CSS 값 문맥이므로 url(...) 로 감싸져야 합니다').toContain('url('); + expect(snap.image).not.toContain('[object Object]'); + }); +}); diff --git a/tests/Playwright/specs/layout-editor/layout-attachment-url-mode.spec.ts b/tests/Playwright/specs/layout-editor/layout-attachment-url-mode.spec.ts new file mode 100644 index 00000000..d61a26b3 --- /dev/null +++ b/tests/Playwright/specs/layout-editor/layout-attachment-url-mode.spec.ts @@ -0,0 +1,436 @@ +/** + * Layout Editor — 레이아웃 첨부 URL 발급 모드 왕복 (공개 #134). + * + * 결함: 업로드 응답의 `url` 이 스토리지 설정과 무관하게 항상 공개 서빙 라우트(프록시)로 + * 발급되어, 공개 자산 디스크(S3+CDN)를 설정해도 방문자 요청이 매번 오리진 PHP 를 거쳤다. + * + * 이 spec 은 첨부를 **실제로 업로드해 왕복**한다 — 기존 background-image-render.spec 은 + * URL 을 손으로 입력할 뿐 업로드 경로를 타지 않아 이 결함을 원리상 포착할 수 없었다. + * 기본 설치(공개 자산 디스크 미설정) 상태에서 업로드 응답 URL 이 프록시 형태이고 그 + * 주소가 인증 없이 200 으로 열리는지, 그리고 그 URL 이 위젯 값·캔버스에 그대로 + * 반영되는지를 본다. 직접 URL(CDN) 축은 서버 설정 변경이 필요하므로 브라우저 실측 + * 매트릭스가 담당하고, 여기서는 서버 응답 형태를 가공 없이 보존하는지를 고정한다. + * + * 축 요약(마커 아님 — 평문): public_asset_disk=unset, row_disk=attachments, + * filter_url_hook=absent. + * + * 뒤의 두 테스트는 별도 매니페스트(layout-attachment-url-storage-gate.yaml)의 축이다 — + * 발급 url 이 사이트 상대 경로여야 헤더 「로고 이미지」 같은 propValue 컨트롤의 저장이 + * 외부 URL 차단 규칙에 걸리지 않는다. 절대 URL 이던 시절 그 저장은 422 였다. + */ +import { test, expect, issueToken, authenticatePage } from '../../fixtures/auth'; +import { bodyRootPath } from '../../fixtures/layout-editor'; +import { SANDBOX_ROOT_ID, SANDBOX_ROUTE, sandboxRouteParam } from '../../fixtures/seed-layout'; +import type { Page } from '@playwright/test'; + +/** 1x1 투명 PNG (base64) — 업로드 픽스처 */ +const PNG_BASE64 = + 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg=='; + +/** + * 캔버스의 배경이 보일 만한 박스형 Div 영역을 골라 path 를 돌려준다. + * 좌측 라우트 트리 패널과 겹치지 않도록 캔버스 안쪽(left > 360)으로 한정한다. + */ +async function pickBoxAreaPath(page: Page): Promise { + return page.evaluate(() => { + const cands = Array.from( + document.querySelectorAll('[data-editor-path][data-editor-name="Div"]'), + ).filter((el) => { + const r = el.getBoundingClientRect(); + return r.width > 200 && r.width < 800 && r.height > 80 && r.height < 320 && r.left > 360; + }); + return cands[0]?.getAttribute('data-editor-path') ?? null; + }); +} + +/** 캔버스 위임 click 핸들러로 노드를 선택한다(오버레이가 좌표를 가로채는 것 회피). */ +async function selectNode(page: Page, path: string): Promise { + await page.evaluate((p) => { + const el = document.querySelector(`[data-editor-path="${p}"]`); + if (!el) return; + el.scrollIntoView({ block: 'center' }); + const r = el.getBoundingClientRect(); + const cx = r.left + r.width / 2; + const cy = r.top + r.height / 2; + const types = [ + 'pointerover', + 'pointermove', + 'pointerdown', + 'mousedown', + 'pointerup', + 'mouseup', + 'click', + ]; + for (const type of types) { + el.dispatchEvent(new MouseEvent(type, { bubbles: true, clientX: cx, clientY: cy })); + } + }, path); +} + +async function openStyleTab(page: Page): Promise { + await page.waitForSelector('[data-testid="g7le-overlay-info-button"]', { timeout: 10_000 }); + await page.getByTestId('g7le-overlay-info-button').click(); + await page.waitForSelector('[data-testid="g7le-context-menu-edit-props"]', { timeout: 5_000 }); + await page.getByTestId('g7le-context-menu-edit-props').click(); + await page.waitForSelector('[data-testid="g7le-property-modal"]', { timeout: 10_000 }); + await page.getByTestId('g7le-property-tab-style').click(); +} + +/** ⓘ → 속성 편집 → props 탭 (헤더 「로고 이미지」 컨트롤이 있는 탭). */ +async function openPropsTab(page: Page): Promise { + await page.waitForSelector('[data-testid="g7le-overlay-info-button"]', { timeout: 10_000 }); + await page.getByTestId('g7le-overlay-info-button').click(); + await page.waitForSelector('[data-testid="g7le-context-menu-edit-props"]', { timeout: 5_000 }); + await page.getByTestId('g7le-context-menu-edit-props').click(); + await page.waitForSelector('[data-testid="g7le-property-modal"]', { timeout: 10_000 }); + await page.getByTestId('g7le-property-tab-props').click(); +} + +async function enterEditor(page: Page): Promise { + await page.goto('/admin/layout-editor/sirsoft-basic?route=%2F'); + await page.waitForLoadState('domcontentloaded', { timeout: 30_000 }); + await page.waitForSelector('[data-testid="g7le-preview-frame"]', { timeout: 30_000 }); + await page.waitForFunction(() => document.querySelectorAll('[data-editor-path]').length > 0, { + timeout: 20_000, + }); +} + +/** 첨부 1건을 업로드하고 `{ id, url }` 을 돌려준다. */ +async function uploadAttachment( + page: Page, + token: string, + filename: string, +): Promise<{ id: number | null; url: string | null; status: number }> { + return page.evaluate( + async ({ b64, bearer, name }) => { + const bin = atob(b64); + const bytes = new Uint8Array(bin.length); + for (let i = 0; i < bin.length; i += 1) bytes[i] = bin.charCodeAt(i); + const form = new FormData(); + form.append('file', new File([bytes], name, { type: 'image/png' }), name); + form.append('layout_name', 'home'); + const res = await fetch('/api/admin/templates/sirsoft-basic/layout-attachments', { + method: 'POST', + headers: { Authorization: `Bearer ${bearer}`, Accept: 'application/json' }, + body: form, + credentials: 'same-origin', + }); + const body = await res.json().catch(() => null); + return { status: res.status, id: body?.data?.id ?? null, url: body?.data?.url ?? null }; + }, + { b64: PNG_BASE64, bearer: token, name: filename }, + ); +} + +/** 이 spec 이 만든 첨부를 지운다 (공유 사이트에 잔여물을 남기지 않는다). */ +async function deleteAttachment(page: Page, token: string, id: number | null): Promise { + if (id === null) return; + await page.evaluate( + async ({ attachmentId, bearer }) => { + await fetch(`/api/admin/templates/layout-attachments/${attachmentId}`, { + method: 'DELETE', + headers: { Authorization: `Bearer ${bearer}`, Accept: 'application/json' }, + credentials: 'same-origin', + }); + }, + { attachmentId: id, bearer: token }, + ); +} + +test.describe('@layout-editor 레이아웃 첨부 URL 발급 모드', () => { + /** + * @scenario public_asset_disk=unset,row_disk=attachments,filter_url_hook=absent + * + * @effects proxy_url_otherwise + */ + test('업로드 → 응답 URL 이 프록시 형태이고 인증 없이 열린다', async ({ page }) => { + const token = issueToken('core.templates.layouts.edit'); + await authenticatePage(page, token); + // 이 축은 서버 응답 형태만 보므로 편집기 진입이 필요 없다 — same-origin 페이지면 충분하다. + await page.goto('/'); + await page.waitForLoadState('domcontentloaded', { timeout: 30_000 }); + + const uploaded = await uploadAttachment(page, token, 'e2e-url-mode.png'); + + expect(uploaded.status).toBe(200); + expect(uploaded.url).toBeTruthy(); + + // 공개 자산 디스크 미설정 = 기본 설치 → 공개 서빙 라우트(프록시) 형태 + expect(uploaded.url as string).toContain(`/layout-attachments/${uploaded.id}/file`); + // 사이트 상대 경로 — 절대 URL 이면 저장 게이트(NoExternalUrls)가 서버 자신의 주소를 외부로 차단한다 + expect(uploaded.url as string).toMatch(/^\/api\//); + + // 그 주소는 인증 없이 이미지로 열려야 한다 (발행 배경은 방문자에게 로드된다) + const served = await page.evaluate(async (url: string) => { + const res = await fetch(url, { credentials: 'omit' }); + return { status: res.status, type: res.headers.get('content-type') }; + }, uploaded.url as string); + expect(served.status).toBe(200); + expect(served.type ?? '').toContain('image/'); + + await deleteAttachment(page, token, uploaded.id); + }); + + /** + * 서버가 준 URL 문자열이 위젯 값과 캔버스 배경에 가공 없이 반영돼야 한다. + * 상대 경로를 가정해 접두사를 붙이면 직접 URL(CDN) 모드에서 주소가 깨진다. + * + * @scenario public_asset_disk=unset,row_disk=attachments,filter_url_hook=absent + * + * @effects editor_thumbnail_renders_cross_origin + */ + test('업로드한 첨부 URL 이 위젯 값·캔버스 배경에 그대로 반영된다', async ({ page }) => { + const token = issueToken('core.templates.layouts.edit'); + await authenticatePage(page, token); + await enterEditor(page); + + const uploaded = await uploadAttachment(page, token, 'e2e-bind.png'); + expect(uploaded.url).toBeTruthy(); + + const targetPath = await pickBoxAreaPath(page); + expect(targetPath).not.toBeNull(); + await selectNode(page, targetPath as string); + await openStyleTab(page); + + const urlInput = page.getByTestId('g7le-image-url'); + await urlInput.fill(uploaded.url as string); + await urlInput.blur(); + + // 입력칸이 서버 URL 을 그대로 보존 + await expect(urlInput).toHaveValue(uploaded.url as string); + + // 캔버스 inline background-image 에도 같은 주소가 실린다 + await expect + .poll( + async () => + page.evaluate((p) => { + const el = document.querySelector(`[data-editor-path="${p}"]`); + return el ? getComputedStyle(el).backgroundImage : ''; + }, targetPath), + { timeout: 8_000 }, + ) + .toContain(`/layout-attachments/${uploaded.id}/file`); + + // 저장하지 않았으므로 레이아웃은 그대로. 첨부만 삭제한다. + await deleteAttachment(page, token, uploaded.id); + }); + + /** + * 저장까지 왕복해 **방문자 화면이 실제로 요청하는 주소**를 확인한다. + * + * 앞의 두 테스트는 업로드 응답과 편집기 화면까지만 본다. 이 결함의 최종 증상은 방문자 + * 요청이 어디로 나가느냐이므로, 저장된 레이아웃이 그리는 화면에서 그 주소가 실제로 + * 요청되고 200 으로 응답하는지까지 봐야 축이 닫힌다. + * + * 저장(PUT)은 편집 결과가 그대로 영속되므로 제품 화면(`home`)이 아니라 E2E 전용 시드 + * 화면(`e2e_sandbox`)을 대상으로 한다 — globalSetup 이 매 실행 fixture 원본으로 덮어쓰므로 + * 원복 절차가 필요 없다(원복 자체가 또 한 번의 저장이라 실패 시 잔여물이 남는다). + * + * @scenario public_asset_disk=unset,row_disk=attachments,filter_url_hook=absent + * + * @effects proxy_url_otherwise, saved_layout_url_is_what_visitor_requests + */ + test('배경 적용 → 저장 → 방문자 화면이 그 주소를 요청한다', async ({ page }) => { + test.setTimeout(90_000); // 업로드 + 저장 + 방문자 렌더 합산 + + const token = issueToken('core.templates.layouts.edit'); + await authenticatePage(page, token); + + await page.goto(`/admin/layout-editor/sirsoft-basic?route=${sandboxRouteParam()}`); + await page.waitForLoadState('domcontentloaded', { timeout: 30_000 }); + await page.waitForSelector('[data-testid="g7le-preview-frame"]', { timeout: 30_000 }); + await page.waitForFunction(() => document.querySelectorAll('[data-editor-path]').length > 0, { + timeout: 20_000, + }); + + const uploaded = await uploadAttachment(page, token, 'e2e-save-roundtrip.png'); + expect(uploaded.url).toBeTruthy(); + + // 시드 화면은 본문 컨테이너 id 가 고정이라 후보 순회 없이 곧바로 지목할 수 있다. + const rootPath = await bodyRootPath(page, SANDBOX_ROOT_ID); + await selectNode(page, rootPath); + await openStyleTab(page); + + const urlInput = page.getByTestId('g7le-image-url'); + await urlInput.fill(uploaded.url as string); + await urlInput.blur(); + await expect(urlInput).toHaveValue(uploaded.url as string); + + // 모달을 확실히 닫은 뒤 툴바 저장 — 모달이 열린 채면 안쪽 버튼이 잡혀 PUT 이 안 난다. + await page + .getByTestId('g7le-property-modal-done') + .click({ timeout: 2_000 }) + .catch(() => undefined); + await page + .getByRole('button', { name: /^(닫기|Close)$/ }) + .first() + .click({ timeout: 3_000 }) + .catch(() => undefined); + await page.waitForTimeout(300); + + const savePromise = page.waitForResponse( + (r) => + /\/api\/admin\/templates\/sirsoft-basic\/layouts\//.test(r.url()) && + r.request().method() === 'PUT', + { timeout: 20_000 }, + ); + await page.getByTestId('g7le-toolbar-save').click(); + const saveRes = await savePromise; + expect(saveRes.status()).toBe(200); + + // 방문자 화면 — 저장된 문자열이 실제 요청으로 나가는지 본다. + const visitor = await page.context().newPage(); + // 발급 url 은 사이트 상대 경로다 — 응답 URL(절대)과는 pathname 으로 비교한다. + const uploadedPath = new URL(uploaded.url as string, 'https://placeholder.invalid').pathname; + const attachmentRequests: number[] = []; + visitor.on('response', (res) => { + if (new URL(res.url()).pathname === uploadedPath) attachmentRequests.push(res.status()); + }); + await visitor.goto(SANDBOX_ROUTE['sirsoft-basic']); + await visitor.waitForLoadState('domcontentloaded', { timeout: 30_000 }); + await expect + .poll(() => attachmentRequests.length, { timeout: 15_000 }) + .toBeGreaterThan(0); + expect(attachmentRequests.every((s) => s === 200)).toBe(true); + await visitor.close(); + + // 시드 화면은 다음 실행에서 fixture 로 덮이므로 첨부만 정리한다. + await deleteAttachment(page, token, uploaded.id); + }); + /** + * 헤더 「로고 이미지」는 값 슬롯이 하나뿐인 propValue 컨트롤이라 업로드 응답 url 이 그대로 + * props 에 들어간다. 그 값이 사이트 상대 경로여야 저장 게이트를 통과한다 — 절대 URL 이던 + * 시절 이 흐름은 "업로드는 되는데 저장은 422" 였다. + * + * 공통 레이아웃(_user_base)은 제품 화면이므로 여기서는 저장하지 않는다 — 위젯 값과 캔버스 + * src 까지만 본다. 저장 왕복은 다음 테스트가 샌드박스 화면에서 잠근다. + */ + // @scenario url_host=site_relative_path + // @effects editor_logo_control_receives_site_relative_url, proxy_url_is_site_relative + test('헤더 로고 컨트롤에서 파일 업로드 → 위젯 값·캔버스 src 가 사이트 상대 경로다', async ({ page }) => { + test.setTimeout(90_000); + const token = issueToken('core.templates.layouts.edit'); + await authenticatePage(page, token); + + await page.goto('/admin/layout-editor/sirsoft-basic?route=%2F&edit=__base__%2F_user_base'); + await page.waitForLoadState('domcontentloaded', { timeout: 30_000 }); + await page.waitForSelector('[data-testid="g7le-preview-frame"]', { timeout: 30_000 }); + await page.waitForFunction(() => document.querySelectorAll('[data-editor-path]').length > 0, { + timeout: 20_000, + }); + + const headerPath = await page.evaluate( + () => + document + .querySelector('[data-editor-path][data-editor-name="Header"]') + ?.getAttribute('data-editor-path') ?? null, + ); + expect(headerPath, '공통 레이아웃에 Header 노드가 있어야 합니다').not.toBeNull(); + await selectNode(page, headerPath as string); + await openPropsTab(page); + + // 로고 값이 환경설정과 연결된 표현식이면 「직접 지정으로 바꾸기」로 열어야 업로드가 가능하다. + const replace = page.getByTestId('g7le-image-expression-replace'); + if (await replace.isVisible().catch(() => false)) { + await replace.click(); + } + + const uploadPromise = page.waitForResponse( + (r) => + r.request().method() === 'POST' && /\/layout-attachments$/.test(new URL(r.url()).pathname), + { timeout: 20_000 }, + ); + await page + .locator('[data-testid="g7le-property-modal"] input[type="file"]') + .setInputFiles({ name: 'e2e-logo.png', mimeType: 'image/png', buffer: Buffer.from(PNG_BASE64, 'base64') }); + const uploadRes = await uploadPromise; + expect(uploadRes.status()).toBe(200); + const body = (await uploadRes.json()) as { data?: { id?: number; url?: string } }; + const url = body?.data?.url ?? null; + const id = body?.data?.id ?? null; + expect(url).not.toBeNull(); + expect(url as string).toMatch(/^\/api\//); + + // 위젯 값 = 발급 url (가공 없음), 캔버스 Header 도 같은 값 + await expect(page.getByTestId('g7le-image-url')).toHaveValue(url as string); + await expect + .poll( + () => + page.evaluate( + (p) => document.querySelector(`[data-editor-path="${p}"] img`)?.getAttribute('src') ?? null, + headerPath, + ), + { timeout: 8_000 }, + ) + .toBe(url); + + await deleteAttachment(page, token, id); + }); + + /** + * 발급 url 을 이미지 prop(`Img.src` — 로고와 같은 props sink)에 넣고 저장하면 200 이어야 하고, + * 그 화면을 연 방문자가 같은 주소를 200 으로 받아야 한다. 절대 URL 이던 시절 이 PUT 은 + * 「HTTPS 프로토콜 URL은 허용되지 않습니다」 422 였다. + * + * 저장은 E2E 전용 시드 화면(e2e_sandbox)에 한다 — globalSetup 이 매 실행 fixture 로 덮는다. + */ + // @scenario url_host=site_relative_path + // @effects issued_asset_url_passes_storage_gate, saved_logo_renders_for_visitor + test('발급 url 을 이미지 prop 에 넣고 저장 → 200, 방문자 화면이 그 주소를 200 으로 받는다', async ({ page }) => { + test.setTimeout(90_000); + const token = issueToken('core.templates.layouts.edit'); + await authenticatePage(page, token); + await page.goto('/'); + await page.waitForLoadState('domcontentloaded', { timeout: 30_000 }); + + const uploaded = await uploadAttachment(page, token, 'e2e-prop-gate.png'); + expect(uploaded.status).toBe(200); + expect(uploaded.url as string).toMatch(/^\/api\//); + + const saved = await page.evaluate( + async ({ bearer, url }) => { + const headers = { Authorization: `Bearer ${bearer}`, Accept: 'application/json' }; + const show = await fetch('/api/admin/templates/sirsoft-basic/layouts/e2e_sandbox', { headers }); + const showBody = await show.json().catch(() => null); + const data = showBody?.data ?? {}; + // show 응답의 content 는 편집 페이지용 JSON 문자열이다 (LayoutResource). + const content = typeof data.content === 'string' ? JSON.parse(data.content) : data.content; + if (!content?.slots?.content?.[0]) return { status: -1, reason: 'sandbox content shape' }; + const root = content.slots.content[0]; + root.children = Array.isArray(root.children) ? root.children : []; + root.children.push({ + id: 'e2e_prop_gate_img', + type: 'basic', + name: 'Img', + props: { src: url, alt: 'e2e prop gate', className: 'h-6' }, + }); + const put = await fetch('/api/admin/templates/sirsoft-basic/layouts/e2e_sandbox', { + method: 'PUT', + headers: { ...headers, 'Content-Type': 'application/json' }, + body: JSON.stringify({ expected_lock_version: data.lock_version ?? 0, content }), + credentials: 'same-origin', + }); + const putBody = await put.json().catch(() => null); + return { status: put.status, message: putBody?.message ?? null }; + }, + { bearer: token, url: uploaded.url as string }, + ); + expect(saved, saved.message ?? '').toMatchObject({ status: 200 }); + + // 방문자 — 저장된 문자열이 실제 요청으로 나가고 200 으로 응답하는지 + const visitor = await page.context().newPage(); + const uploadedPath = new URL(uploaded.url as string, 'https://placeholder.invalid').pathname; + const statuses: number[] = []; + visitor.on('response', (res) => { + if (new URL(res.url()).pathname === uploadedPath) statuses.push(res.status()); + }); + await visitor.goto(SANDBOX_ROUTE['sirsoft-basic']); + await visitor.waitForLoadState('domcontentloaded', { timeout: 30_000 }); + await expect.poll(() => statuses.length, { timeout: 15_000 }).toBeGreaterThan(0); + expect(statuses.every((s) => s === 200)).toBe(true); + await expect(visitor.locator(`img[src="${uploaded.url}"]`)).toHaveCount(1); + await visitor.close(); + + await deleteAttachment(page, token, uploaded.id); + }); +}); diff --git a/tests/TestCase.php b/tests/TestCase.php index 917c5b07..4052046e 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -104,11 +104,21 @@ abstract class TestCase extends BaseTestCase * 기본 모드를 전제한 테스트(자산 URL 생성·blade 렌더)가 그 환경에서만 무더기로 깨진다. * 실제로 이 환경에서 8건이 그렇게 실패했다. * - * 다른 모드를 검증해야 하는 테스트는 `AssetUrl::forceMode()` 로 자기 전제를 명시한다. + * `security.two_factor_auth` 도 같다. 켜 둔 사이트에서는 `/api/auth/login` 이 토큰이 + * 아니라 인증 요청(challenge)을 돌려주므로, 로그인 성공을 전제한 테스트가 그 환경에서만 + * 깨진다. 게다가 테스트 메일러로는 인증번호를 보낼 수 없어 503 이 되므로 실패 메시지가 + * 원인을 가리키지도 않는다. 실제로 이 환경에서 3건이 그렇게 실패했다. + * + * 다른 모드를 검증해야 하는 테스트는 `AssetUrl::forceMode()` 로, 2단계 인증을 켜야 하는 + * 테스트는 `config(['g7_settings.core.security.two_factor_auth' => true])` 로 자기 전제를 + * 명시한다 (자식 `setUp()` 은 `parent::setUp()` 뒤에 실행되므로 그 지정이 이긴다). */ private function pinEnvironmentDependentSettings(): void { - config(['g7_settings.core.general.asset_url_mode' => AssetUrl::MODE_EXTENSION]); + config([ + 'g7_settings.core.general.asset_url_mode' => AssetUrl::MODE_EXTENSION, + 'g7_settings.core.security.two_factor_auth' => false, + ]); } /** diff --git a/tests/Unit/Cache/Migration/SeoCacheTest.php b/tests/Unit/Cache/Migration/SeoCacheTest.php index 4bb65ec0..3b25dd49 100644 --- a/tests/Unit/Cache/Migration/SeoCacheTest.php +++ b/tests/Unit/Cache/Migration/SeoCacheTest.php @@ -60,7 +60,8 @@ class SeoCacheTest extends TestCase $this->manager->put('/board/notice/123', 'ko', 'Hello'); $expectedKey = 'seo.page.'.md5('/board/notice/123|ko'); - $this->assertSame('Hello', $this->cache->get($expectedKey)); + // 페이지는 레이아웃명과 함께 저장된다 — 적중 경로가 통계를 화면별로 귀속할 출처다. + $this->assertSame('Hello', $this->cache->get($expectedKey)['html'] ?? null); $this->assertSame( 'g7:core:'.$expectedKey, $this->cache->resolveKey($expectedKey) diff --git a/tests/Unit/Extension/CoreVersionCheckerEnvPriorityTest.php b/tests/Unit/Extension/CoreVersionCheckerEnvPriorityTest.php index c8936e50..c500f2bd 100644 --- a/tests/Unit/Extension/CoreVersionCheckerEnvPriorityTest.php +++ b/tests/Unit/Extension/CoreVersionCheckerEnvPriorityTest.php @@ -3,24 +3,45 @@ namespace Tests\Unit\Extension; use App\Extension\CoreVersionChecker; +use PHPUnit\Framework\Attributes\Test; use Tests\TestCase; /** - * CoreVersionChecker::getCoreVersion() env 우선 읽기 회귀 테스트. + * CoreVersionChecker::getCoreVersion() 의 env 우선 판독 **범위** 회귀 테스트. * - * 코어 업그레이드 중 spawn 프로세스(proc_open)가 `APP_VERSION={toVersion}` env 를 전달하지만 - * `bootstrap/cache/config.php` 가 존재하면 `LoadConfiguration` 이 캐시된 리터럴을 사용해 - * env 오버라이드가 반영되지 않는 회귀가 있었다. 본 테스트는 `getCoreVersion()` 이 - * env 를 우선 읽어 config cache 유무와 무관하게 spawn 이 전달한 버전을 신뢰함을 검증한다. + * 두 방향의 회귀를 함께 잠근다. + * + * 1. 업데이트 트리 **안**: spawn 프로세스(proc_open)가 `APP_VERSION={toVersion}` env 를 전달하지만 + * `bootstrap/cache/config.php` 가 있으면 `LoadConfiguration` 이 캐시된 리터럴을 써서 env + * 오버라이드가 반영되지 않는다. 여기서는 env 가 우선해야 확장이 자동 비활성화되지 않는다. + * 2. 업데이트 트리 **밖**: `php artisan serve` · 큐 워커처럼 오래 사는 프로세스는 기동 시점의 + * `APP_VERSION` 을 물고 있다. 업데이트가 끝나도 그 프로세스만 옛 버전으로 판정해, 새 코어를 + * 요구하는 확장을 `incompatible_core` 로 꺼 버린다(관리자 템플릿이 꺼지면 복구 UI 도 사라진다 — + * 2026-09-07 실측). 여기서는 config 가 유일한 근거여야 한다. */ class CoreVersionCheckerEnvPriorityTest extends TestCase { private ?string $originalEnvVersion = null; + private ?string $originalUpdateFlag = null; + + /** @var array */ + private array $originalArgv = []; + protected function setUp(): void { parent::setUp(); + $this->originalEnvVersion = $_ENV['APP_VERSION'] ?? null; + + $flag = $_ENV['G7_UPDATE_IN_PROGRESS'] ?? $_SERVER['G7_UPDATE_IN_PROGRESS'] ?? getenv('G7_UPDATE_IN_PROGRESS'); + $this->originalUpdateFlag = ($flag === false || $flag === null) ? null : (string) $flag; + + $this->originalArgv = $_SERVER['argv'] ?? []; + + // 각 테스트가 트리 안/밖을 명시적으로 세우도록 기본은 "트리 밖" 으로 둔다. + $this->setUpdateFlag(false); + $_SERVER['argv'] = ['artisan', 'inspire']; } protected function tearDown(): void @@ -33,23 +54,48 @@ class CoreVersionCheckerEnvPriorityTest extends TestCase } else { putenv('APP_VERSION'); } + + if ($this->originalUpdateFlag === null) { + $this->setUpdateFlag(false); + } else { + $_ENV['G7_UPDATE_IN_PROGRESS'] = $this->originalUpdateFlag; + $_SERVER['G7_UPDATE_IN_PROGRESS'] = $this->originalUpdateFlag; + putenv('G7_UPDATE_IN_PROGRESS='.$this->originalUpdateFlag); + } + + $_SERVER['argv'] = $this->originalArgv; + parent::tearDown(); } + /** + * 업데이트 트리 안에서는 env APP_VERSION 이 config 보다 우선한다. + * + * @effects getCoreVersion_prefers_env_APP_VERSION_only_inside_update_tree + */ + #[Test] public function test_env_app_version_overrides_config(): void { + $this->setUpdateFlag(true); config()->set('app.version', '7.0.0-beta.1'); // 캐시된 stale 값 시뮬레이션 $_ENV['APP_VERSION'] = '7.0.0-beta.2'; // spawn 이 전달한 toVersion $this->assertSame( '7.0.0-beta.2', CoreVersionChecker::getCoreVersion(), - 'env APP_VERSION 이 config 보다 우선해야 합니다.' + '업데이트 트리 안에서는 env APP_VERSION 이 config 보다 우선해야 합니다.' ); } + /** + * 업데이트 트리 안이어도 env 가 없으면 config 로 내려간다. + * + * @effects getCoreVersion_falls_back_to_config_when_env_absent_inside_update_tree + */ + #[Test] public function test_falls_back_to_config_when_env_missing(): void { + $this->setUpdateFlag(true); unset($_ENV['APP_VERSION'], $_SERVER['APP_VERSION']); putenv('APP_VERSION'); config()->set('app.version', '7.0.0-beta.1'); @@ -61,9 +107,14 @@ class CoreVersionCheckerEnvPriorityTest extends TestCase ); } + /** + * spawn 자식 시나리오: 캐시된 config 는 fromVersion, env 는 toVersion → 확장이 살아남는다. + */ + #[Test] public function test_spawn_scenario_cached_config_with_env_override(): void { // spawn 환경 시뮬레이션: config 는 fromVersion 으로 캐시, env 는 toVersion + $this->setUpdateFlag(true); config()->set('app.version', '7.0.0-beta.1'); $_ENV['APP_VERSION'] = '7.0.0-beta.2'; @@ -73,4 +124,72 @@ class CoreVersionCheckerEnvPriorityTest extends TestCase 'spawn 에서 env 로 전달된 toVersion 기준으로 호환성이 평가되어 확장이 자동 비활성화되지 않아야 합니다.' ); } + + /** + * 업데이트 트리 **밖**에서는 프로세스 env 의 옛 APP_VERSION 을 무시하고 config 를 쓴다. + * + * 업데이트 전에 띄운 `php artisan serve` 가 `7.0.9` 를 물고 있는 상황을 시뮬레이션한다. + * env 를 그대로 믿으면 `>=7.0.10` 을 요구하는 관리자 템플릿이 비활성화되어 화면이 통째로 + * 사라진다 — 그 상태에서는 복구 UI 에도 도달할 수 없다. + * + * @effects getCoreVersion_ignores_env_APP_VERSION_outside_update_tree + */ + #[Test] + public function test_env_app_version_is_ignored_outside_update_tree(): void + { + $this->setUpdateFlag(false); + config()->set('app.version', '7.0.10'); // 업데이트가 끝나 .env·config 는 새 버전 + $_ENV['APP_VERSION'] = '7.0.9'; // 기동 시점 env 를 물고 있는 상주 프로세스 + $_SERVER['APP_VERSION'] = '7.0.9'; + putenv('APP_VERSION=7.0.9'); + + $this->assertSame( + '7.0.10', + CoreVersionChecker::getCoreVersion(), + '업데이트 트리 밖에서는 config 버전이 근거여야 합니다.' + ); + + $this->assertTrue( + CoreVersionChecker::isCompatible('>=7.0.10'), + '상주 프로세스의 옛 env 때문에 새 코어를 요구하는 확장이 비활성화되면 안 됩니다.' + ); + } + + /** + * env 플래그가 전파되지 않아도 argv 가 업데이트 커맨드면 env 우선이 켜진다. + */ + #[Test] + public function test_argv_update_command_enables_env_priority(): void + { + $this->setUpdateFlag(false); + $_SERVER['argv'] = ['artisan', 'core:execute-upgrade-steps', '--from=7.0.9', '--to=7.0.10']; + + config()->set('app.version', '7.0.9'); + $_ENV['APP_VERSION'] = '7.0.10'; + + $this->assertSame( + '7.0.10', + CoreVersionChecker::getCoreVersion(), + 'argv 보조 판정으로도 업데이트 트리로 인식해 env 를 우선해야 합니다.' + ); + } + + /** + * 업데이트 트리 플래그를 세 채널($_ENV/$_SERVER/putenv)에 세우거나 지웁니다. + * + * @param bool $enabled true 면 플래그 설정, false 면 세 채널 모두 제거 + */ + private function setUpdateFlag(bool $enabled): void + { + if (! $enabled) { + unset($_ENV['G7_UPDATE_IN_PROGRESS'], $_SERVER['G7_UPDATE_IN_PROGRESS']); + putenv('G7_UPDATE_IN_PROGRESS'); + + return; + } + + $_ENV['G7_UPDATE_IN_PROGRESS'] = '1'; + $_SERVER['G7_UPDATE_IN_PROGRESS'] = '1'; + putenv('G7_UPDATE_IN_PROGRESS=1'); + } } diff --git a/tests/Unit/Extension/Storage/StorageCategoryDiskTest.php b/tests/Unit/Extension/Storage/StorageCategoryDiskTest.php index 71961ebc..45e7ae0a 100644 --- a/tests/Unit/Extension/Storage/StorageCategoryDiskTest.php +++ b/tests/Unit/Extension/Storage/StorageCategoryDiskTest.php @@ -83,6 +83,11 @@ class StorageCategoryDiskTest extends TestCase { return ['ko' => '테스트', 'en' => 'Test']; } + + public function exposedResolvePublicAssetDisk(?string $override = null): ?string + { + return $this->resolvePublicAssetDisk($override); + } }; } @@ -209,4 +214,83 @@ class StorageCategoryDiskTest extends TestCase $this->assertNull($module->exposedResolvePublicAssetDisk()); $this->assertNull($module->exposedResolvePublicAssetDisk('vanished_plugin_disk')); } + + /** + * 플러그인 사본도 모듈과 동일한 해석 규칙을 따라야 합니다. + * + * 위임 이관(공개 #134) 전까지 플러그인 사본은 직접 테스트가 0건이라 오타가 나도 + * 아무도 잡지 못하는 자리였다. 모듈과 같은 4축을 플러그인 더블에도 돌린다. + * + * @effects none_override_forces_streaming_over_global, orphan_disk_falls_back_to_streaming + */ + #[Test] + public function plugin_resolve_public_asset_disk_matches_module_rules(): void + { + $plugin = $this->makePlugin(); + + // 미설정 → 스트리밍 + $this->assertNull($plugin->exposedResolvePublicAssetDisk()); + + // 전역 설정 → 그 값 + Config::set('core.storage.public_asset_disk', 'fake_cdn'); + $this->assertSame('fake_cdn', $plugin->exposedResolvePublicAssetDisk()); + $this->assertSame('fake_cdn', $plugin->exposedResolvePublicAssetDisk('')); + + // override 우선 + $this->assertSame('public', $plugin->exposedResolvePublicAssetDisk('public')); + + // 'none' override → 스트리밍 강제 + $this->assertNull($plugin->exposedResolvePublicAssetDisk('none')); + + // 고아 디스크 → 스트리밍 폴백 + Config::set('core.storage.public_asset_disk', 'vanished_plugin_disk'); + $this->assertNull($plugin->exposedResolvePublicAssetDisk()); + } + + /** + * getStorageUrl()/getStorageBasePath() 가 카테고리에 배선된 디스크를 따라야 합니다. + * + * 기본 디스크를 보던 시절에는 images 를 공개 자산 디스크로 옮긴 확장이 이 API 로 + * 항상 null 을 받았다 (공개 #134 전수조사 F4·F7). + * + * @effects url_returned_for_public_and_url_configured_disk + */ + #[Test] + public function storage_url_and_base_path_follow_category_disk(): void + { + $module = new class extends AbstractModule + { + public function getName(): array + { + return ['ko' => '테스트', 'en' => 'Test']; + } + + public function getVersion(): string + { + return '1.0.0'; + } + + public function getDescription(): array + { + return ['ko' => '테스트', 'en' => 'Test']; + } + + public function getStorageDiskFor(string $category): string + { + return $category === 'images' ? 'fake_cdn' : $this->getStorageDisk(); + } + }; + + // 배선된 카테고리 → fake_cdn 의 url 설정을 따라 직접 URL + $url = $module->getStorageUrl('images', 'a/b.png'); + $this->assertNotNull($url); + $this->assertStringStartsWith('https://cdn.test/assets', $url); + + // 미배선 카테고리는 기본 디스크(직접 URL 불가) → null + $this->assertNull($module->getStorageUrl('settings', 'a/b.json')); + + // 기본 경로도 배선된 디스크 기준 + $this->assertStringContainsString('fake_cdn', $module->getStorageBasePath('images')); + $this->assertStringNotContainsString('fake_cdn', $module->getStorageBasePath('settings')); + } } diff --git a/tests/Unit/Extension/TemplateUpdateConfigCacheInvalidationTest.php b/tests/Unit/Extension/TemplateUpdateConfigCacheInvalidationTest.php index 14ce1b3a..e7534ca7 100644 --- a/tests/Unit/Extension/TemplateUpdateConfigCacheInvalidationTest.php +++ b/tests/Unit/Extension/TemplateUpdateConfigCacheInvalidationTest.php @@ -6,7 +6,7 @@ use PHPUnit\Framework\Attributes\Test; use Tests\TestCase; /** - * 템플릿 업데이트 성공 cleanup 의 config 캐시 무효화 구조 테스트 (#588, 공개 #119) + * [case:cache-17] 템플릿 업데이트 성공 cleanup 의 config 캐시 무효화 구조 테스트 (#588, 공개 #119) * * `template.config.{identifier}` 는 버전 접미사 없는 고정 키라 캐시 버전 bump 로 * 무효화되지 않는다. updateTemplate() 성공 cleanup 이 clearTemplateCache() 를 diff --git a/tests/Unit/Helpers/SettingsMigratorOwnershipTest.php b/tests/Unit/Helpers/SettingsMigratorOwnershipTest.php index eca34568..fd2daa22 100644 --- a/tests/Unit/Helpers/SettingsMigratorOwnershipTest.php +++ b/tests/Unit/Helpers/SettingsMigratorOwnershipTest.php @@ -8,7 +8,7 @@ use Illuminate\Support\Facades\File; use Tests\TestCase; /** - * SettingsMigrator owner 상속 회귀 테스트. + * [case:backend-28] SettingsMigrator owner 상속 회귀 테스트. * * sudo update 흐름에서 모듈/플러그인 upgrade step 이 root 로 실행될 때 * `SettingsMigrator::writeJsonFile` 가 만드는 *.json 파일이 root 소유로 영구 잔존하는 diff --git a/tests/Unit/Installer/RequirementsVendorDevPackagesTest.php b/tests/Unit/Installer/RequirementsVendorDevPackagesTest.php new file mode 100644 index 00000000..9b8f3669 --- /dev/null +++ b/tests/Unit/Installer/RequirementsVendorDevPackagesTest.php @@ -0,0 +1,202 @@ +markTestSkipped(self::$skipReason); + } + } + + /** + * vendor 가 없는 신규 설치: 판정 불가로 두고, 마법사가 자동 설치한다는 안내만 낸다. + * + * @effects installer_env_check_shows_dev_vendor_card_without_gating + */ + #[Test] + public function vendor_가_없으면_자동_설치_안내로_보고한다(): void + { + $result = $this->invokeCheck(); + + $this->assertFalse($result['required'], 'Composer 의존성 구성은 선택 항목이다'); + $this->assertFalse($result['vendor_exists']); + $this->assertNull($result['dev'], 'vendor 가 없으면 판정 불가다'); + $this->assertSame([], $result['packages']); + $this->assertNotSame('', trim($result['message'])); + } + + /** + * dev vendor 를 감지하면 목록과 함께 보고하되 `required` 는 그대로 false 다. + */ + #[Test] + public function dev_vendor_를_감지해도_필수_항목으로_승격되지_않는다(): void + { + $this->writeVendor(['packages' => [], 'dev' => true, 'dev-package-names' => ['laravel/boost', 'laravel/mcp']]); + + $result = $this->invokeCheck(); + + $this->assertFalse($result['required'], 'dev 감지는 설치를 차단하지 않는다 (D1)'); + $this->assertTrue($result['vendor_exists']); + $this->assertTrue($result['dev']); + $this->assertSame(['laravel/boost', 'laravel/mcp'], $result['packages']); + } + + /** + * `--no-dev` vendor 는 통과로 보고한다. + */ + #[Test] + public function 운영용_vendor_는_통과로_보고한다(): void + { + $this->writeVendor(['packages' => [], 'dev' => false, 'dev-package-names' => []]); + + $result = $this->invokeCheck(); + + $this->assertFalse($result['required']); + $this->assertTrue($result['vendor_exists']); + $this->assertFalse($result['dev']); + $this->assertSame([], $result['packages']); + } + + /** + * 게이트(`isAllRequiredPassed`)가 이 항목을 보지 않는다 — 소스 계약. + * + * 반환값 검사만으로는 부족하다: 게이트가 나중에 이 키를 참조하도록 바뀌어도 + * `required=false` 자체는 그대로라 행위 테스트가 통과한다. + */ + #[Test] + public function 필수_통과_판정이_이_항목을_참조하지_않는다(): void + { + $source = (string) file_get_contents(dirname(__DIR__, 3).'/public/install/api/check-configuration.php'); + + $start = strpos($source, 'private function isAllRequiredPassed('); + $this->assertNotFalse($start, '전제: 게이트 메서드가 존재한다'); + + $end = strpos($source, "\n }", $start); + $this->assertNotFalse($end, '전제: 게이트 메서드 본문을 잘라낼 수 있다'); + + $body = substr($source, $start, $end - $start); + + $this->assertStringNotContainsString( + 'vendor_dev_packages', + $body, + '선택 항목이 필수 통과 판정에 들어가면 dev vendor 로 준비한 설치가 시작조차 되지 않는다' + ); + } + + /** + * 화면 카드가 존재하고 실패 목록에 넣지 않는다 — 소스 계약. + */ + #[Test] + public function 설치_화면_카드가_실패_목록에_넣지_않는다(): void + { + $js = (string) file_get_contents(dirname(__DIR__, 3).'/public/install/assets/js/installer.js'); + + $start = strpos($js, 'data.vendor_dev_packages && data.vendor_dev_packages.vendor_exists'); + $this->assertNotFalse($start, 'vendor_dev_packages 카드 렌더 블록이 있어야 한다'); + + $end = strpos($js, '// 10. 자산 URL 방식 카드', $start); + $this->assertNotFalse($end, '전제: 다음 카드 블록이 경계를 이룬다'); + + $block = substr($js, $start, $end - $start); + + $this->assertStringContainsString("lang('vendor_dev_packages')", $block, '카드 제목을 언어 키로 그려야 한다'); + $this->assertStringNotContainsString('failedRequirements.push', $block, '선택 항목은 실패 목록에 넣지 않는다'); + } + + /** + * `checkVendorDevPackages()` 를 리플렉션으로 호출합니다. + * + * @return array 판정 결과 + */ + private function invokeCheck(): array + { + $api = new \ValidationApi; + $method = new \ReflectionMethod(\ValidationApi::class, 'checkVendorDevPackages'); + $method->setAccessible(true); + + /** @var array $result */ + $result = $method->invoke($api); + + return $result; + } + + /** + * 임시 BASE_PATH 아래에 vendor 픽스처를 만듭니다. + * + * @param array $installedJson installed.json 내용 + */ + private function writeVendor(array $installedJson): void + { + $vendor = self::$sharedBase.'/vendor'; + if (! is_dir($vendor.'/composer')) { + mkdir($vendor.'/composer', 0755, true); + } + + file_put_contents($vendor.'/autoload.php', " $a @param array $b @return array{0: int, 1: int} */ + public function counts(array $a, array $b): array + { + return $this->diffLineCounts($a, $b); + } + + /** @param array $content @return array */ + public function lines(array $content): array + { + return $this->contentToLines($content); + } + }; + } + + /** + * 종전 구현(전체 DP 표 + backtrack) — 카운트 동치의 참조 기준. + * + * @param array $a 이전 라인 + * @param array $b 새 라인 + * @return array{0: int, 1: int} [추가, 삭제] + */ + private function referenceCounts(array $a, array $b): array + { + $na = count($a); + $nb = count($b); + $dp = array_fill(0, $na + 1, array_fill(0, $nb + 1, 0)); + for ($i = $na - 1; $i >= 0; $i--) { + for ($j = $nb - 1; $j >= 0; $j--) { + $dp[$i][$j] = $a[$i] === $b[$j] + ? $dp[$i + 1][$j + 1] + 1 + : max($dp[$i + 1][$j], $dp[$i][$j + 1]); + } + } + $added = 0; + $removed = 0; + $i = 0; + $j = 0; + while ($i < $na && $j < $nb) { + if ($a[$i] === $b[$j]) { + $i++; + $j++; + } elseif ($dp[$i + 1][$j] >= $dp[$i][$j + 1]) { + $removed++; + $i++; + } else { + $added++; + $j++; + } + } + + return [$added + ($nb - $j), $removed + ($na - $i)]; + } + + /** + * 직렬화하면 2,300줄을 넘는 레이아웃 형태의 content 를 만듭니다. + * + * @return array + */ + private function largeLayout(): array + { + $components = []; + for ($i = 0; $i < 260; $i++) { + $components[] = [ + 'id' => "node_{$i}", + 'type' => 'basic', + 'name' => 'Div', + 'props' => ['className' => "p-{$i} flex", 'title' => "노드 {$i}"], + 'children' => [], + ]; + } + + return [ + 'components' => $components, + 'layout_name' => 'big', + 'version' => '1.0.0', + ]; + } + + /** + * 변경 영역이 파일 전체인 큰 레이아웃의 카운트 계산이 메모리 예산 안에 머물러야 합니다. + * + * 이전 content 에는 정렬 시 맨 앞에 오는 `comment` 와 맨 뒤에 오는 `zzz` 가 있고 새 + * content 에는 둘 다 없다 — 편집기 첫 저장이 만드는 정확히 그 형태다. 종전 구현은 여기서 + * 146MB 를 더 썼다. + * + * @effects changes_summary_memory_bounded_for_full_span_change + */ + public function test_full_span_change_of_large_layout_stays_within_memory_budget(): void + { + $subject = $this->subject(); + $new = $this->largeLayout(); + $old = ['comment' => '상속원 설명'] + $new + ['zzz' => 'x']; + + $oldLines = $subject->lines($old); + $newLines = $subject->lines($new); + $this->assertGreaterThan(2300, count($oldLines), '재현 조건: 직렬화 2,300줄 이상'); + + gc_collect_cycles(); + $before = memory_get_peak_usage(); + [$added, $removed] = $subject->counts($oldLines, $newLines); + $grown = memory_get_peak_usage() - $before; + + $this->assertLessThan( + 16 * 1024 * 1024, + $grown, + sprintf('카운트 계산이 %.1fMB 를 더 썼습니다 — (줄 수)² 표를 만들고 있습니다', $grown / 1048576) + ); + // comment 삭제 + zzz 삭제 + `"version": "1.0.0",` → `"version": "1.0.0"` (삭제 1·추가 1) + $this->assertSame([1, 3], [$added, $removed]); + } + + /** + * 카운트는 종전 구현(전체 표 + backtrack)과 무작위 입력에서 항상 같아야 합니다 — + * 목록의 +N/-N 이 diff 뷰(프론트 lineDiff.ts)와 일치한다는 S11 계약의 근거다. + * + * @effects changes_summary_uses_line_lcs_unit_matching_diff_view + */ + public function test_counts_equal_full_table_reference_on_random_inputs(): void + { + $subject = $this->subject(); + mt_srand(20260909); + + for ($case = 0; $case < 600; $case++) { + $alphabet = mt_rand(1, 6); // 작은 알파벳 = 중복 라인(`}`, `],`)이 많은 레이아웃 형태 + $a = []; + $b = []; + for ($i = 0, $n = mt_rand(0, 40); $i < $n; $i++) { + $a[] = chr(97 + mt_rand(0, $alphabet)); + } + for ($i = 0, $m = mt_rand(0, 40); $i < $m; $i++) { + $b[] = chr(97 + mt_rand(0, $alphabet)); + } + + $this->assertSame( + $this->referenceCounts($a, $b), + $subject->counts($a, $b), + sprintf('case %d: %s vs %s', $case, implode('', $a), implode('', $b)) + ); + } + } + + /** + * 레이아웃 형태의 변형(노드 추가·삭제·prop 변경·순서 변경)에서도 참조 구현과 같아야 합니다. + * + * @effects changes_summary_uses_line_lcs_unit_matching_diff_view + */ + public function test_counts_equal_reference_on_layout_shaped_mutations(): void + { + $subject = $this->subject(); + $base = [ + 'components' => [ + ['id' => 'a', 'type' => 'basic', 'name' => 'Div', 'props' => ['className' => 'x'], 'children' => []], + ['id' => 'b', 'type' => 'basic', 'name' => 'Span', 'props' => ['text' => '안녕'], 'children' => []], + ['id' => 'c', 'type' => 'composite', 'name' => 'Header', 'props' => ['logo' => '/a.png']], + ], + 'layout_name' => 'home', + 'version' => '1.0.0', + ]; + + $mutations = [ + 'prop 변경' => (function () use ($base) { + $base['components'][2]['props']['logo'] = '/api/templates/x/layout-attachments/1/file'; + + return $base; + })(), + '노드 추가' => (function () use ($base) { + $base['components'][] = ['id' => 'd', 'type' => 'basic', 'name' => 'P', 'props' => []]; + + return $base; + })(), + '노드 삭제' => (function () use ($base) { + unset($base['components'][1]); + $base['components'] = array_values($base['components']); + + return $base; + })(), + '순서 변경' => (function () use ($base) { + $base['components'] = [$base['components'][2], $base['components'][0], $base['components'][1]]; + + return $base; + })(), + '빈 content' => [], + ]; + + $baseLines = $subject->lines($base); + foreach ($mutations as $label => $mutated) { + $mutatedLines = $subject->lines($mutated); + $this->assertSame($this->referenceCounts($baseLines, $mutatedLines), $subject->counts($baseLines, $mutatedLines), $label); + $this->assertSame($this->referenceCounts($mutatedLines, $baseLines), $subject->counts($mutatedLines, $baseLines), $label.' (역방향)'); + } + } +} diff --git a/tests/Unit/Repositories/LayoutExtensionVersionRepositoryTest.php b/tests/Unit/Repositories/LayoutExtensionVersionRepositoryTest.php index 6e69e7e7..84662144 100644 --- a/tests/Unit/Repositories/LayoutExtensionVersionRepositoryTest.php +++ b/tests/Unit/Repositories/LayoutExtensionVersionRepositoryTest.php @@ -5,6 +5,7 @@ namespace Tests\Unit\Repositories; use App\Models\LayoutExtension; use App\Models\Template; use App\Models\TemplateLayoutExtensionVersion; +use App\Models\User; use App\Repositories\LayoutExtensionVersionRepository; use Illuminate\Database\Eloquent\ModelNotFoundException; use Illuminate\Foundation\Testing\RefreshDatabase; @@ -212,4 +213,37 @@ class LayoutExtensionVersionRepositoryTest extends TestCase $this->assertSame([], $this->repository->getCurrentVersionsByExtensionIds([])); $this->assertSame([], $this->repository->getCurrentVersionsByExtensionIds([$this->extension->id])); } + + /** + * saveVersion 은 인증 사용자를 created_by 로 기록한다(레이아웃 본체와 동형). + */ + public function test_save_version_records_authenticated_user_as_creator(): void + { + $user = User::factory()->create(); + $this->actingAs($user); + + $version = $this->repository->saveVersion($this->extension->id, ['extension_point' => 'header', 'components' => []]); + + $this->assertSame($user->id, $version->created_by); + } + + /** + * restoreVersion 은 확장 lock_version 을 올린다(레이아웃 본체와 동형). + */ + public function test_restore_version_increments_extension_lock_version(): void + { + $oldContent = ['extension_point' => 'header', 'components' => [['type' => 'basic', 'name' => 'Span']]]; + $this->extension->forceFill(['lock_version' => 2, 'content' => ['extension_point' => 'footer']])->save(); + $target = TemplateLayoutExtensionVersion::factory()->create([ + 'extension_id' => $this->extension->id, + 'version' => 1, + 'content' => $oldContent, + ]); + + $this->repository->restoreVersion($this->extension->id, $target->id); + + $this->extension->refresh(); + $this->assertSame(3, (int) $this->extension->lock_version); + $this->assertEquals($oldContent, $this->extension->content); + } } diff --git a/tests/Unit/Repositories/LayoutVersionRepositoryTest.php b/tests/Unit/Repositories/LayoutVersionRepositoryTest.php index d270dda0..10908357 100644 --- a/tests/Unit/Repositories/LayoutVersionRepositoryTest.php +++ b/tests/Unit/Repositories/LayoutVersionRepositoryTest.php @@ -5,6 +5,7 @@ namespace Tests\Unit\Repositories; use App\Models\Template; use App\Models\TemplateLayout; use App\Models\TemplateLayoutVersion; +use App\Models\User; use App\Repositories\LayoutVersionRepository; use Illuminate\Foundation\Testing\RefreshDatabase; use Tests\TestCase; @@ -687,4 +688,41 @@ class LayoutVersionRepositoryTest extends TestCase $this->assertSame([], $map); } + + /** + * saveVersion 은 인증 사용자를 created_by 로 기록한다 — 종전엔 항상 NULL 이라 버전 목록의 + * 저장자가 「알 수 없음」으로만 표시됐다. + */ + public function test_save_version_records_authenticated_user_as_creator(): void + { + $user = User::factory()->create(); + $this->actingAs($user); + + $version = $this->repository->saveVersion($this->layout->id, ['components' => []]); + + $this->assertSame($user->id, $version->created_by); + } + + /** + * restoreVersion 은 레이아웃 lock_version 을 올린다 — 복원 직전 화면을 열어 둔 다른 편집기가 + * 복원 결과를 409 없이 덮지 못하게 한다. + */ + public function test_restore_version_increments_layout_lock_version(): void + { + $this->layout->forceFill([ + 'lock_version' => 4, + 'content' => ['components' => [['type' => 'basic', 'name' => 'Span']]], + ])->save(); + $target = TemplateLayoutVersion::factory()->create([ + 'layout_id' => $this->layout->id, + 'version' => 1, + 'content' => ['components' => []], + ]); + + $this->repository->restoreVersion($this->layout->id, $target->id); + + $this->layout->refresh(); + $this->assertSame(5, (int) $this->layout->lock_version); + $this->assertEquals(['components' => []], $this->layout->content); + } } diff --git a/tests/Unit/Seo/SeoCacheBoundsTest.php b/tests/Unit/Seo/SeoCacheBoundsTest.php new file mode 100644 index 00000000..9b33d533 --- /dev/null +++ b/tests/Unit/Seo/SeoCacheBoundsTest.php @@ -0,0 +1,188 @@ +assertSame( + ['page' => '2'], + SeoCacheBounds::normalizeQuery(['locale' => 'en', '_escaped_fragment_' => '', 'page' => '2']) + ); + } + + /** + * 같은 조합은 순서가 달라도 같은 키가 되어야 한다. + * + * @effects system_query_params_excluded_from_key + */ + public function test_normalize_query_sorts_keys(): void + { + $this->assertSame( + ['a' => '1', 'b' => '2', 'c' => '3'], + SeoCacheBounds::normalizeQuery(['c' => '3', 'a' => '1', 'b' => '2']) + ); + } + + /** + * 파라미터 수 상한을 넘기면 캐시 불가(null)다. + * + * @effects oversized_query_bypasses_cache_and_render + */ + public function test_normalize_query_returns_null_when_too_many_parameters(): void + { + config(['core.seo_cache_limits.max_query_params' => 10]); + + $query = []; + for ($i = 0; $i < 11; $i++) { + $query['p'.$i] = '1'; + } + + $this->assertNull(SeoCacheBounds::normalizeQuery($query)); + + array_pop($query); + $this->assertIsArray(SeoCacheBounds::normalizeQuery($query), '상한과 같은 개수는 허용된다'); + } + + /** + * 정규화된 쿼리 문자열 길이 상한을 넘기면 캐시 불가다. + * + * @effects oversized_query_bypasses_cache_and_render + */ + public function test_normalize_query_returns_null_when_query_string_too_long(): void + { + config(['core.seo_cache_limits.max_query_length' => 32]); + + $this->assertNull(SeoCacheBounds::normalizeQuery(['q' => str_repeat('x', 64)])); + $this->assertIsArray(SeoCacheBounds::normalizeQuery(['q' => 'x'])); + } + + /** + * 시스템 파라미터는 길이·개수 상한 계산에서도 빠진다. + * + * @effects system_query_params_excluded_from_key + */ + public function test_normalize_query_excludes_system_parameters_from_limits(): void + { + config(['core.seo_cache_limits.max_query_params' => 1]); + + $this->assertSame( + ['page' => '2'], + SeoCacheBounds::normalizeQuery(['locale' => 'en', '_escaped_fragment_' => '', 'page' => '2']) + ); + } + + /** + * 같은 경로의 변종 수가 상한에 닿으면 새 URL 을 저장하지 않는다. + * + * @effects store_skips_write_at_path_variant_cap + */ + public function test_can_store_rejects_new_variant_at_path_cap(): void + { + config([ + 'core.seo_cache_limits.max_variants_per_path' => 3, + 'core.seo_cache_limits.max_entries' => 20000, + ]); + + $index = [ + 'k1' => ['url' => '/shop?page=1', 'locale' => 'ko'], + 'k2' => ['url' => '/shop?page=2', 'locale' => 'ko'], + 'k3' => ['url' => '/shop?page=3', 'locale' => 'ko'], + ]; + + $this->assertFalse(SeoCacheBounds::canStore($index, '/shop?page=4', 'ko')); + // 다른 경로는 자기 예산을 따로 쓴다 + $this->assertTrue(SeoCacheBounds::canStore($index, '/board?page=1', 'ko')); + } + + /** + * 전체 항목 수 상한에 닿으면 어떤 경로도 저장하지 않는다. + * + * @effects store_skips_write_at_global_cap + */ + public function test_can_store_rejects_when_global_cap_reached(): void + { + config([ + 'core.seo_cache_limits.max_entries' => 2, + 'core.seo_cache_limits.max_variants_per_path' => 50, + ]); + + $index = [ + 'k1' => ['url' => '/a', 'locale' => 'ko'], + 'k2' => ['url' => '/b', 'locale' => 'ko'], + ]; + + $this->assertFalse(SeoCacheBounds::canStore($index, '/c', 'ko')); + } + + /** + * 렌더 예산은 IP 단위로 소진된다. + * + * @effects bot_miss_over_limit_gets_spa_bypass + */ + public function test_render_budget_is_per_ip(): void + { + config(['core.seo_cache_limits.render_misses_per_minute' => 2]); + + $this->assertTrue(SeoCacheBounds::renderAllowed('10.0.0.1')); + + SeoCacheBounds::recordRender('10.0.0.1'); + SeoCacheBounds::recordRender('10.0.0.1'); + + $this->assertFalse(SeoCacheBounds::renderAllowed('10.0.0.1')); + $this->assertTrue(SeoCacheBounds::renderAllowed('10.0.0.2'), '다른 IP 는 자기 예산을 쓴다'); + } + + /** + * 통계 기록 예산도 IP 단위다 — 통계 테이블이 새 증식 축이 되지 않아야 한다. + * + * @effects stats_recording_capped_per_ip + */ + public function test_stats_budget_is_per_ip(): void + { + config(['core.seo_cache_limits.stats_records_per_minute' => 1]); + + $this->assertTrue(SeoCacheBounds::statsAllowed('10.0.0.3')); + + SeoCacheBounds::recordStat('10.0.0.3'); + + $this->assertFalse(SeoCacheBounds::statsAllowed('10.0.0.3')); + } + + /** + * 경로당 변종 상한은 언어별로 따로 센다 — 인덱스 항목은 url|locale 별이므로 경로만 보고 + * 합산하면 언어 수만큼 실효 상한이 줄어든다. + * + * @effects store_counts_path_variants_per_locale + */ + public function test_can_store_counts_path_variants_per_locale(): void + { + config([ + 'core.seo_cache_limits.max_variants_per_path' => 2, + 'core.seo_cache_limits.max_entries' => 20000, + ]); + + $index = [ + 'k1' => ['url' => '/shop?page=1', 'locale' => 'ko'], + 'k2' => ['url' => '/shop?page=2', 'locale' => 'ko'], + 'k3' => ['url' => '/shop?page=1', 'locale' => 'en'], + ]; + + $this->assertFalse(SeoCacheBounds::canStore($index, '/shop?page=3', 'ko')); + $this->assertTrue(SeoCacheBounds::canStore($index, '/shop?page=2', 'en'), '다른 언어는 자기 예산을 따로 쓴다'); + } +} diff --git a/tests/Unit/Seo/SeoCacheManagerTest.php b/tests/Unit/Seo/SeoCacheManagerTest.php index 93c85526..e62bb1f4 100644 --- a/tests/Unit/Seo/SeoCacheManagerTest.php +++ b/tests/Unit/Seo/SeoCacheManagerTest.php @@ -210,4 +210,276 @@ class SeoCacheManagerTest extends TestCase $this->assertNull($result); $this->assertEmpty($this->cacheManager->getCachedUrls()); } + + /** + * 같은 경로의 변종 수가 상한에 닿으면 페이지도 인덱스도 쓰지 않는다. + * + * @effects store_skips_write_at_path_variant_cap + */ + public function test_put_with_layout_skips_write_when_path_variant_cap_reached(): void + { + config([ + 'core.seo_cache_limits.max_variants_per_path' => 3, + 'core.seo_cache_limits.max_entries' => 20000, + ]); + + for ($i = 1; $i <= 3; $i++) { + $this->cacheManager->putWithLayout('/shop?page='.$i, 'ko', ''.$i.'', 'shop'); + } + + $before = $this->cacheManager->getCachedUrls(); + + $this->cacheManager->putWithLayout('/shop?page=4', 'ko', '4', 'shop'); + + $this->assertSame($before, $this->cacheManager->getCachedUrls()); + $this->assertNull($this->cacheManager->get('/shop?page=4', 'ko')); + } + + /** + * 전체 항목 수 상한에 닿으면 새 URL 을 저장하지 않는다. + * + * @effects store_skips_write_at_global_cap + */ + public function test_put_with_layout_skips_write_when_global_cap_reached(): void + { + config([ + 'core.seo_cache_limits.max_entries' => 2, + 'core.seo_cache_limits.max_variants_per_path' => 50, + ]); + + $this->cacheManager->putWithLayout('/a', 'ko', 'a', 'la'); + $this->cacheManager->putWithLayout('/b', 'ko', 'b', 'lb'); + $this->cacheManager->putWithLayout('/c', 'ko', 'c', 'lc'); + + $this->assertNull($this->cacheManager->get('/c', 'ko')); + $this->assertCount(2, $this->cacheManager->getCachedUrls()); + } + + /** + * 이미 있는 키의 **갱신**은 상한과 무관하다 — 저장 규모가 늘지 않는다. + * + * @effects store_skips_write_at_global_cap + */ + public function test_put_with_layout_updates_existing_key_regardless_of_caps(): void + { + config([ + 'core.seo_cache_limits.max_entries' => 1, + 'core.seo_cache_limits.max_variants_per_path' => 1, + ]); + + $this->cacheManager->putWithLayout('/a', 'ko', 'old', 'la'); + $this->cacheManager->putWithLayout('/a', 'ko', 'new', 'la'); + + $this->assertSame('new', $this->cacheManager->get('/a', 'ko')); + $this->assertCount(1, $this->cacheManager->getCachedUrls()); + } + + /** + * 페이지만 만료시킵니다 — 인덱스 항목은 그대로 남는 실제 만료 상태를 만듭니다. + * + * @param CoreCacheDriver $driver 매니저가 쓰는 캐시 드라이버 + */ + private function expirePages(CoreCacheDriver $driver): void + { + foreach ($driver->get('seo.cached_urls', []) as $entry) { + $driver->forget($entry['key']); + } + } + + /** + * 상한이 세는 항목에는 페이지가 이미 만료된 것이 섞인다 — 인덱스는 페이지보다 훨씬 + * 오래 살고(30일 vs 2시간) 스스로 줄지 않는다. 상한에 닿았을 때 한 번 정리하고 다시 + * 판정하지 않으면, 한 번 닿은 경로는 실제 캐시가 비어도 영영 저장이 막힌다. + * + * @effects expired_index_entries_are_pruned_before_cap_verdict + */ + public function test_put_with_layout_prunes_expired_entries_when_path_variant_cap_reached(): void + { + $driver = new CoreCacheDriver('array'); + $manager = new SeoCacheManager($driver); + + config([ + 'core.seo_cache_limits.max_variants_per_path' => 3, + 'core.seo_cache_limits.max_entries' => 20000, + ]); + + for ($i = 1; $i <= 3; $i++) { + $manager->putWithLayout('/shop?page='.$i, 'ko', ''.$i.'', 'shop'); + } + + $this->expirePages($driver); + + $manager->putWithLayout('/shop?page=4', 'ko', '4', 'shop'); + + $this->assertSame('4', $manager->get('/shop?page=4', 'ko')); + $this->assertSame(['/shop?page=4'], array_values($manager->getCachedUrls())); + } + + /** + * 전체 항목 수 상한에서도 같다 — 정리 후 자리가 나면 저장한다. + * + * @effects expired_index_entries_are_pruned_before_cap_verdict + */ + public function test_put_with_layout_prunes_expired_entries_when_global_cap_reached(): void + { + $driver = new CoreCacheDriver('array'); + $manager = new SeoCacheManager($driver); + + config([ + 'core.seo_cache_limits.max_entries' => 2, + 'core.seo_cache_limits.max_variants_per_path' => 50, + ]); + + $manager->putWithLayout('/a', 'ko', 'a', 'la'); + $manager->putWithLayout('/b', 'ko', 'b', 'lb'); + + $this->expirePages($driver); + + $manager->putWithLayout('/c', 'ko', 'c', 'lc'); + + $this->assertSame('c', $manager->get('/c', 'ko')); + $this->assertCount(1, $manager->getCachedUrls()); + } + + /** + * `put()` 은 `putWithLayout()` 과 같은 자원(페이지 + 인덱스)을 쓰는 형제 공개 메서드다. + * 상한이 한쪽에만 있으면 다른 쪽이 우회로가 된다 — 확장은 인터페이스를 직접 호출한다. + * + * @effects put_and_put_with_layout_share_the_storage_cap + */ + public function test_put_applies_the_same_storage_cap_as_put_with_layout(): void + { + config([ + 'core.seo_cache_limits.max_entries' => 2, + 'core.seo_cache_limits.max_variants_per_path' => 50, + ]); + + $this->cacheManager->put('/a', 'ko', 'a'); + $this->cacheManager->put('/b', 'ko', 'b'); + $this->cacheManager->put('/c', 'ko', 'c'); + + $this->assertNull($this->cacheManager->get('/c', 'ko')); + $this->assertCount(2, $this->cacheManager->getCachedUrls()); + } + + /** + * 만료 항목 정리도 형제 메서드가 함께 갖는다. + * + * @effects put_and_put_with_layout_share_the_storage_cap + */ + public function test_put_prunes_expired_entries_when_cap_reached(): void + { + $driver = new CoreCacheDriver('array'); + $manager = new SeoCacheManager($driver); + + config([ + 'core.seo_cache_limits.max_entries' => 2, + 'core.seo_cache_limits.max_variants_per_path' => 50, + ]); + + $manager->put('/a', 'ko', 'a'); + $manager->put('/b', 'ko', 'b'); + + $this->expirePages($driver); + + $manager->put('/c', 'ko', 'c'); + + $this->assertSame('c', $manager->get('/c', 'ko')); + $this->assertCount(1, $manager->getCachedUrls()); + } + + /** + * 정리는 인덱스 전체를 훑으므로(항목마다 캐시 조회) 상한에 닿을 때마다 돌면 안 된다 — + * 살아 있는 항목만으로 상한에 닿은 경로는 저장 시도마다 그 스캔을 반복하게 되고, + * 그 빈도는 봇 미스 렌더 예산만큼이다. 그래서 정리는 간격 표식으로 묶는다. + * + * @effects index_prune_is_throttled_to_one_scan_per_interval + */ + public function test_index_prune_is_throttled_to_one_scan_per_interval(): void + { + $driver = new CoreCacheDriver('array'); + $manager = new SeoCacheManager($driver); + + config([ + 'core.seo_cache_limits.max_variants_per_path' => 3, + 'core.seo_cache_limits.max_entries' => 20000, + ]); + + for ($i = 1; $i <= 3; $i++) { + $manager->putWithLayout('/shop?page='.$i, 'ko', ''.$i.'', 'shop'); + } + + // 전부 살아 있는 상태에서 상한 도달 → 1회 스캔하고 표식을 남긴다 (저장은 안 됨) + $manager->putWithLayout('/shop?page=4', 'ko', '4', 'shop'); + $this->assertNull($manager->get('/shop?page=4', 'ko')); + + // 이후 만료되어도 표식이 살아 있는 동안은 다시 훑지 않는다 + $this->expirePages($driver); + $manager->putWithLayout('/shop?page=5', 'ko', '5', 'shop'); + $this->assertNull($manager->get('/shop?page=5', 'ko')); + + // 표식이 사라지면 다시 정리하고 저장한다 + $driver->forget('seo.index_pruned_at'); + $manager->putWithLayout('/shop?page=6', 'ko', '6', 'shop'); + $this->assertSame('6', $manager->get('/shop?page=6', 'ko')); + } + + /** + * 페이지와 함께 저장한 레이아웃명을 꺼낼 수 있다 — 캐시 적중(HIT)은 렌더러를 거치지 + * 않으므로 요청 속성에 레이아웃명이 없고, 캐시 항목이 그것을 알아야 통계가 화면별로 귀속된다. + * + * @effects cache_hit_carries_layout_name_from_cache_entry + */ + public function test_get_entry_returns_layout_stored_with_page(): void + { + $this->cacheManager->putWithLayout('/products/1', 'ko', 'p', 'shop/show'); + + $this->assertSame( + ['html' => 'p', 'layout' => 'shop/show'], + $this->cacheManager->getEntry('/products/1', 'ko') + ); + $this->assertSame('p', $this->cacheManager->get('/products/1', 'ko')); + $this->assertNull($this->cacheManager->getEntry('/nope', 'ko')); + } + + /** + * 이전 버전이 문자열로만 저장한 항목도 그대로 읽힌다 — 배포 직후 살아 있는 캐시를 버리지 않는다. + * + * @effects cache_hit_carries_layout_name_from_cache_entry + */ + public function test_get_reads_legacy_string_entries(): void + { + $driver = new CoreCacheDriver('array'); + $manager = new SeoCacheManager($driver); + + $driver->put('seo.page.'.md5('/legacy|ko'), 'legacy', 3600); + + $this->assertSame('legacy', $manager->get('/legacy', 'ko')); + $this->assertSame(['html' => 'legacy', 'layout' => null], $manager->getEntry('/legacy', 'ko')); + } + + /** + * 경로당 변종 상한은 언어별로 따로 센다 — 인덱스 항목은 url|locale 별인데 경로만 보고 + * 합산하면 언어 수만큼 실효 상한이 줄어, 다국어 사이트의 목록 뒤쪽 페이지가 캐시에서 빠진다. + * + * @effects store_counts_path_variants_per_locale + */ + public function test_path_variant_cap_is_counted_per_locale(): void + { + config([ + 'core.seo_cache_limits.max_variants_per_path' => 3, + 'core.seo_cache_limits.max_entries' => 20000, + ]); + + for ($i = 1; $i <= 3; $i++) { + $this->cacheManager->putWithLayout('/shop?page='.$i, 'ko', 'ko'.$i.'', 'shop'); + } + + // ko 는 상한 도달, en 은 자기 예산을 따로 쓴다 + $this->cacheManager->putWithLayout('/shop?page=4', 'ko', 'ko4', 'shop'); + $this->cacheManager->putWithLayout('/shop?page=1', 'en', 'en1', 'shop'); + + $this->assertNull($this->cacheManager->get('/shop?page=4', 'ko')); + $this->assertSame('en1', $this->cacheManager->get('/shop?page=1', 'en')); + } } diff --git a/tests/Unit/Seo/SeoCacheStatsServiceTest.php b/tests/Unit/Seo/SeoCacheStatsServiceTest.php index 4acfb753..068fb566 100644 --- a/tests/Unit/Seo/SeoCacheStatsServiceTest.php +++ b/tests/Unit/Seo/SeoCacheStatsServiceTest.php @@ -281,4 +281,22 @@ class SeoCacheStatsServiceTest extends TestCase $this->assertDatabaseHas('seo_cache_stats', ['url' => '/recent/1']); $this->assertDatabaseHas('seo_cache_stats', ['url' => '/today/1']); } + + /** + * 통계의 url 컬럼은 캐시 키 URL(경로 + 정규화 쿼리 최대 512바이트)을 그대로 담을 수 있어야 + * 한다 — 짧으면 긴 주소의 기록이 조용히 실패한다(예외는 서비스가 삼키고 warning 만 남긴다). + * + * @effects stats_url_column_fits_normalized_cache_url + */ + public function test_record_persists_url_as_long_as_the_cache_url_bound(): void + { + $url = '/shop/products?'.str_repeat('filters[a]=0123456789&', 24).'page=2'; + + $this->assertGreaterThan(255, strlen($url)); + $this->assertLessThanOrEqual(768, strlen($url)); + + $this->statsService->recordHit($url, 'ko', 'shop/index'); + + $this->assertDatabaseHas('seo_cache_stats', ['url' => $url, 'type' => 'hit']); + } } diff --git a/tests/Unit/Seo/SeoRendererTest.php b/tests/Unit/Seo/SeoRendererTest.php index c6354768..d759004c 100644 --- a/tests/Unit/Seo/SeoRendererTest.php +++ b/tests/Unit/Seo/SeoRendererTest.php @@ -766,6 +766,11 @@ class SeoRendererTest extends TestCase ], ], JSON_PRETTY_PRINT)); + // 선언한 산출물이 실재해야 링크된다 — 없는 경로의 는 봇 화면에서만 404 가 + // 되고 어디에도 흔적이 남지 않으므로 렌더러가 실재 파일만 싣는다. + mkdir("{$configDir}/dist/css", 0755, true); + file_put_contents("{$configDir}/dist/css/components.css", ''); + // 자산 URL 모드를 고정 — 운영 설정(general.asset_url_mode)에 따라 // 확장자 유지/제거 두 형태가 나오므로 검증 대상 형태를 명시한다. AssetUrl::forceMode(AssetUrl::MODE_EXTENSION); @@ -818,6 +823,9 @@ class SeoRendererTest extends TestCase $this->assertNotNull($result); } finally { AssetUrl::forceMode(null); + @unlink("{$configDir}/dist/css/components.css"); + @rmdir("{$configDir}/dist/css"); + @rmdir("{$configDir}/dist"); @unlink("{$configDir}/template.json"); @rmdir($configDir); } diff --git a/tests/Unit/Services/CoreUpdateServiceQueueRestartTest.php b/tests/Unit/Services/CoreUpdateServiceQueueRestartTest.php new file mode 100644 index 00000000..179096a4 --- /dev/null +++ b/tests/Unit/Services/CoreUpdateServiceQueueRestartTest.php @@ -0,0 +1,117 @@ +once()->with('queue:restart')->andReturn(0); + + app(CoreUpdateService::class)->signalQueueRestart(); + + $this->assertTrue(true, 'Artisan 기대치는 tearDown 에서 검증된다'); + } + + /** + * 신호 전송 실패는 업데이트를 되돌리지 않는다 — 예외를 삼키고 계속한다. + */ + #[Test] + public function signal_queue_restart_는_실패해도_예외를_전파하지_않는다(): void + { + Artisan::shouldReceive('call') + ->once() + ->with('queue:restart') + ->andThrow(new \RuntimeException('큐 재시작 신호 전송 실패 시뮬레이션')); + + app(CoreUpdateService::class)->signalQueueRestart(); + + $this->assertTrue(true, '예외가 전파되면 이 지점에 도달하지 못한다'); + } + + /** + * 업데이트를 마치는 세 지점이 캐시 정리 **직후** 신호를 보낸다 (호출 지점 계약). + * + * 행위 테스트로는 이 축을 덮을 수 없다 — `CoreUpdateCommandHandoffTest` 의 docblock 이 + * 밝히듯 `handle()` 의 catch cleanup 시퀀스는 GitHub 연동·migration·vendor 설치까지 + * 끌고 들어와야 하는 통합 면적이라 그 테스트 범위 밖이다. 그래서 세 지점의 배치를 + * 소스로 잠근다: 새 마무리 지점이 신호 없이 추가되면 여기서 걸린다. + * + * 롤백 catch 는 의도적 제외다 — 백업으로 되돌린 **옛** 코드가 다시 도는 자리라 워커를 + * 재기동시킬 이유가 없다. + * + * @effects core_update_step11_signals_queue_restart + */ + #[Test] + public function 캐시_정리_뒤_큐_신호를_보내는_지점이_셋이고_롤백은_제외된다(): void + { + $sites = []; + + foreach ([ + 'app/Console/Commands/Core/CoreUpdateCommand.php', + 'app/Console/Commands/Core/ExecuteUpgradeStepsCommand.php', + ] as $relative) { + $source = (string) file_get_contents(base_path($relative)); + + $offset = 0; + while (($pos = strpos($source, '$service->clearAllCaches();', $offset)) !== false) { + // 정리 직후 구간(다음 400자)에 신호가 있는지 본다 — 사이에 낀 주석은 허용하되 + // 다른 마무리 단계를 한참 지나 붙는 것은 "직후" 가 아니다. + $window = substr($source, $pos, 400); + $sites[] = [ + 'file' => $relative, + 'line' => substr_count(substr($source, 0, $pos), "\n") + 1, + 'signals' => str_contains($window, '$service->signalQueueRestart();'), + // 롤백 지점은 백업 복원 직후라 바로 뒤에 "롤백 후 캐시 정리 완료" 로그가 온다. + 'is_rollback' => str_contains($window, '롤백 후 캐시 정리'), + ]; + $offset = $pos + 1; + } + } + + $this->assertNotEmpty($sites, '전제: clearAllCaches 호출 지점을 찾을 수 있어야 한다'); + + $signalling = array_values(array_filter($sites, fn (array $s): bool => $s['signals'])); + $silent = array_values(array_filter($sites, fn (array $s): bool => ! $s['signals'])); + + $this->assertCount( + 3, + $signalling, + 'Step 11 · 핸드오프 cleanup · 단독 실행 사후 단계 셋이 신호를 보내야 한다. 실제: ' + .json_encode($sites, JSON_UNESCAPED_SLASHES) + ); + + $this->assertCount( + 1, + $silent, + '신호를 보내지 않는 지점은 롤백 catch 하나뿐이어야 한다. 실제: ' + .json_encode($silent, JSON_UNESCAPED_SLASHES) + ); + + $this->assertTrue( + $silent[0]['is_rollback'], + '제외된 지점은 롤백 catch 여야 한다 (옛 코드로 되돌린 자리라 워커 재기동 대상이 아니다). 실제: ' + .json_encode($silent[0], JSON_UNESCAPED_SLASHES) + ); + } +} diff --git a/tests/Unit/Services/CoreUpdateServiceTest.php b/tests/Unit/Services/CoreUpdateServiceTest.php index 746c09c6..e1db8893 100644 --- a/tests/Unit/Services/CoreUpdateServiceTest.php +++ b/tests/Unit/Services/CoreUpdateServiceTest.php @@ -39,14 +39,72 @@ class CoreUpdateServiceTest extends TestCase */ private array $tempFiles = []; + /** + * 진입 시점의 업데이트 트리 플래그 (tearDown 에서 복원) + */ + private string|false $originalUpdateFlag = false; + + /** + * 진입 시점의 APP_VERSION 3채널 값 (tearDown 에서 복원) + * + * @var array{env: string|null, server: string|null, putenv: string|false} + */ + private array $originalAppVersion = ['env' => null, 'server' => null, 'putenv' => false]; + protected function setUp(): void { parent::setUp(); + + // `updateVersionInEnv()` 는 `.env` 파일만이 아니라 프로세스 환경(3채널)도 갱신한다. + // 그 부수효과가 클래스 안에 남으면 **뒤이은 테스트가 부팅하는 앱의 `config('app.version')`** + // 이 오염된다(`config/app.php` 가 `env('APP_VERSION', …)` 이므로). 실제로 그 순서 의존으로 + // `test_check_updates_command_shows_success` 가 "현재 최신 버전입니다" 대신 업데이트 있음으로 + // 갈렸다. 진입 시 스냅샷을 떠 두고 종료 시 되돌린다. + $this->originalAppVersion = [ + 'env' => $_ENV['APP_VERSION'] ?? null, + 'server' => $_SERVER['APP_VERSION'] ?? null, + 'putenv' => getenv('APP_VERSION'), + ]; + + // 이 클래스의 한 케이스가 `core:update` 를 실제로 호출하는데, 부모 커맨드는 "프로세스 + // 종료와 함께 소멸" 을 전제로 `G7_UPDATE_IN_PROGRESS` 를 정리하지 않는다. PHPUnit 은 + // 프로세스가 이어지므로 그 플래그가 이후 모든 테스트로 새어 나가, 부팅마다 + // `bootstrap/app.php` 의 매니페스트 자가 치유가 발동한다(내용은 같아도 불필요한 churn). + // 진입 시 비우고 종료 시 되돌린다 — ExecuteUpgradeStepsStandaloneTest 와 같은 규율. + $this->originalUpdateFlag = getenv('G7_UPDATE_IN_PROGRESS'); + putenv('G7_UPDATE_IN_PROGRESS'); + unset($_ENV['G7_UPDATE_IN_PROGRESS'], $_SERVER['G7_UPDATE_IN_PROGRESS']); + $this->service = new CoreUpdateService; } protected function tearDown(): void { + // APP_VERSION 3채널 복원 (updateVersionInEnv 부수효과 격리) + foreach (['env' => &$_ENV, 'server' => &$_SERVER] as $slot => &$bag) { + if ($this->originalAppVersion[$slot] === null) { + unset($bag['APP_VERSION']); + } else { + $bag['APP_VERSION'] = $this->originalAppVersion[$slot]; + } + } + unset($bag); + + if ($this->originalAppVersion['putenv'] === false) { + putenv('APP_VERSION'); + } else { + putenv('APP_VERSION='.$this->originalAppVersion['putenv']); + } + + if ($this->originalUpdateFlag === false) { + putenv('G7_UPDATE_IN_PROGRESS'); + unset($_ENV['G7_UPDATE_IN_PROGRESS'], $_SERVER['G7_UPDATE_IN_PROGRESS']); + } else { + putenv('G7_UPDATE_IN_PROGRESS='.$this->originalUpdateFlag); + $_ENV['G7_UPDATE_IN_PROGRESS'] = $this->originalUpdateFlag; + $_SERVER['G7_UPDATE_IN_PROGRESS'] = $this->originalUpdateFlag; + } + // 임시 파일 정리 foreach ($this->tempFiles as $file) { if (File::exists($file)) { @@ -425,6 +483,55 @@ MD; }); } + /** + * `.env` 파일만이 아니라 프로세스 환경(3채널)도 함께 갱신하는지 검증합니다. + * + * 부모가 config 캐시 없이 부팅했다면 Dotenv 가 `$_ENV['APP_VERSION']` 에 이전 버전을 채워 두고, + * env 저장소가 불변이라 뒤이어 부팅하는 `config:cache` 프로세스가 `.env` 를 다시 읽어도 그 값이 + * 바뀌지 않는다. 그러면 `bootstrap/cache/config.php` 에 **이전 버전**이 박제되어 이후 모든 웹 + * 요청이 옛 버전으로 판정한다 — 새 코어를 요구하는 확장이 `incompatible_core` 로 꺼지는 경로다. + * (2026-09-07 실측: `config:clear` 후 7.0.10 → 7.0.11 업데이트가 config 캐시에 7.0.10 을 구웠다.) + */ + public function test_update_version_in_env_also_updates_process_env(): void + { + $originalEnv = $_ENV['APP_VERSION'] ?? null; + $originalServer = $_SERVER['APP_VERSION'] ?? null; + $originalPutenv = getenv('APP_VERSION'); + + try { + // 업데이트 전 프로세스 환경 = 이전 버전 (캐시 없이 부팅한 부모의 상태) + $_ENV['APP_VERSION'] = '1.0.0'; + $_SERVER['APP_VERSION'] = '1.0.0'; + putenv('APP_VERSION=1.0.0'); + + $this->withIsolatedBasePath(function (string $tempBase): void { + File::put($tempBase.DIRECTORY_SEPARATOR.'.env', "APP_NAME=G7\nAPP_VERSION=1.0.0\n"); + + $this->service->updateVersionInEnv('2.5.0'); + }); + + $this->assertSame('2.5.0', $_ENV['APP_VERSION'], '$_ENV 채널이 갱신되어야 한다'); + $this->assertSame('2.5.0', $_SERVER['APP_VERSION'], '$_SERVER 채널이 갱신되어야 한다'); + $this->assertSame('2.5.0', getenv('APP_VERSION'), 'putenv 채널이 갱신되어야 한다'); + } finally { + if ($originalEnv === null) { + unset($_ENV['APP_VERSION']); + } else { + $_ENV['APP_VERSION'] = $originalEnv; + } + if ($originalServer === null) { + unset($_SERVER['APP_VERSION']); + } else { + $_SERVER['APP_VERSION'] = $originalServer; + } + if ($originalPutenv === false) { + putenv('APP_VERSION'); + } else { + putenv('APP_VERSION='.$originalPutenv); + } + } + } + /** * .env 파일에 APP_VERSION 이 없을 때 새로 추가하는지 검증합니다. */ diff --git a/tests/Unit/Services/ExtensionBundleServiceTest.php b/tests/Unit/Services/ExtensionBundleServiceTest.php index 9aa5d1f5..f1246ca7 100644 --- a/tests/Unit/Services/ExtensionBundleServiceTest.php +++ b/tests/Unit/Services/ExtensionBundleServiceTest.php @@ -5,12 +5,15 @@ namespace Tests\Unit\Services; use App\Extension\ModuleManager; use App\Extension\PluginManager; use App\Services\ExtensionBundleService; +use Illuminate\Contracts\Cache\Lock; +use Illuminate\Contracts\Cache\LockTimeoutException; +use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\File; use Mockery; use Tests\TestCase; /** - * ExtensionBundleService 단위 테스트 + * [case:backend-30] ExtensionBundleService 단위 테스트 * * 활성 확장 IIFE/CSS 의 priority 정렬, `\n;\n` 구분자 병합, sourceMappingURL * 처리, 확장별 fault tolerance, 캐시 파일 생성/정리를 검증한다. @@ -128,7 +131,8 @@ class ExtensionBundleServiceTest extends TestCase int $priority, array $assets, string $strategy = 'global', - ?array $declaredPaths = null + ?array $declaredPaths = null, + ?array $builtPaths = null ): object { $ext = Mockery::mock(); $ext->shouldReceive('hasAssets')->andReturn($assets !== []); @@ -139,8 +143,10 @@ class ExtensionBundleServiceTest extends TestCase 'dependencies' => [], ]); $ext->shouldReceive('getAssets')->andReturn($assets); + // 실제 확장은 존재하는 산출물만 built 경로로 돌려준다(getBuiltAssetPaths 가 file_exists 로 거른다). + // 기본값은 "산출물 없음" 상태를 흉내 내는 부재 경로다 — 존재하는 산출물을 흉내 내려면 $builtPaths 로 지정한다. $ext->shouldReceive('getBuiltAssetAbsolutePaths')->andReturn( - array_map(fn () => $this->fixtureDir.'/missing-'.$identifier.'.out', $assets) + $builtPaths ?? array_map(fn () => $this->fixtureDir.'/missing-'.$identifier.'.out', $assets) ); $ext->shouldReceive('getBuiltAssetPaths')->andReturn( array_map(fn () => 'dist/css/module.css', $assets) @@ -437,8 +443,8 @@ class ExtensionBundleServiceTest extends TestCase app()->detectEnvironment(fn () => 'production'); $a = $this->writeFixture('a.js', '(function(){window.A=1})()'); - // getActiveModules 는 두 번 호출될 수 있으므로 안정적으로 반환 - $this->moduleManager->shouldReceive('getActiveModules')->andReturn([ + // 캐시 적중 경로는 활성 확장을 다시 열거하지 않아야 하므로 정확히 1회로 조인다 + $this->moduleManager->shouldReceive('getActiveModules')->once()->andReturn([ 'ext-a' => $this->fakeExtension('ext-a', 10, $a, null), ]); @@ -449,9 +455,12 @@ class ExtensionBundleServiceTest extends TestCase $this->assertFileExists($path1); $this->assertStringContainsString('module.999.js', $path1); + $content1 = (string) file_get_contents($path1); + // 같은 version 재요청 → 동일 파일 (캐시 히트) $path2 = $svc->getBundleFilePath('module', 'js', 999); $this->assertSame($path1, $path2); + $this->assertSame($content1, (string) file_get_contents($path2)); } /** @@ -711,4 +720,310 @@ class ExtensionBundleServiceTest extends TestCase $this->assertSame([$absent], $this->service()->findMissingDeclaredAssets('plugin', 'js')); } + + /** + * 프로덕션에서 같은 version 캐시가 있으면 **빌드도 원본 읽기도 하지 않는다**. + * + * 캐시 적중 판정이 빌드 뒤에 있으면 캐시가 있어도 요청마다 활성 확장을 열거하고 + * 산출물 파일을 전부 읽는다. 응답은 정상 200 이라 타이밍 말고는 드러나는 증상이 + * 없고, 원본이 사라진 순간에는 캐시가 멀쩡한데도 빈 경로가 반환되어 503 이 된다. + * + * @effects prod_cache_hit_skips_build_and_source_reads + */ + public function test_prod_cache_hit_returns_cached_path_without_enumerating_or_reading_sources(): void + { + $this->app['env'] = 'production'; + app()->detectEnvironment(fn () => 'production'); + + $a = $this->writeFixture('cache-hit.js', '(function(){window.A=1})()'); + + $enumerations = 0; + $this->moduleManager->shouldReceive('getActiveModules')->andReturnUsing(function () use (&$enumerations, $a) { + $enumerations++; + + return ['ext-a' => $this->fakeExtension('ext-a', 10, $a, null)]; + }); + + $svc = $this->service(); + + $path1 = $svc->getBundleFilePath('module', 'js', 424242); + $this->assertNotSame('', $path1); + $this->assertFileExists($path1); + $this->assertSame(1, $enumerations); + + $before = (string) file_get_contents($path1); + + // 원본 소실 — 캐시가 있으므로 응답에 영향이 없어야 한다 + @unlink($a); + + $path2 = $svc->getBundleFilePath('module', 'js', 424242); + + $this->assertSame($path1, $path2); + $this->assertSame(1, $enumerations, '캐시 적중 시 활성 확장을 다시 열거하면 안 된다'); + $this->assertSame($before, (string) file_get_contents($path2)); + } + + /** + * 프로덕션은 같은 version 이면 원본이 바뀌어도 캐시를 그대로 서빙한다. + * + * 비프로덕션 거울 테스트(test_non_production_rebuilds_every_request_without_cache_reuse)와 + * 짝을 이룬다 — 원본 교체는 version bump 로만 반영된다. + * + * @effects prod_cache_hit_skips_build_and_source_reads + */ + public function test_prod_cache_hit_does_not_rebuild_when_source_changes_on_same_version(): void + { + $this->app['env'] = 'production'; + app()->detectEnvironment(fn () => 'production'); + + $a = $this->writeFixture('prod-stable.js', '(function(){window.A=1})()'); + $this->moduleManager->shouldReceive('getActiveModules')->andReturn([ + 'ext-a' => $this->fakeExtension('ext-a', 10, $a, null), + ]); + + $svc = $this->service(); + + $path1 = $svc->getBundleFilePath('module', 'js', 424243); + $this->assertStringContainsString('window.A=1', (string) file_get_contents($path1)); + + File::put($a, '(function(){window.A=2})()'); + $path2 = $svc->getBundleFilePath('module', 'js', 424243); + + $this->assertSame($path1, $path2); + $this->assertStringContainsString('window.A=1', (string) file_get_contents($path2)); + $this->assertStringNotContainsString('window.A=2', (string) file_get_contents($path2)); + } + + /** + * 캐시 미스는 같은 (type, kind, version) 잠금으로 1회 빌드에 수렴하고, + * 잠금 뒤에 캐시를 **다시 확인**한다. + * + * 다른 프로세스가 대기 중에 캐시를 완성했다면 이쪽은 빌드하지 않아야 한다. + * + * @effects prod_cache_miss_builds_once_under_lock_and_rechecks + */ + public function test_prod_cache_miss_serializes_concurrent_builds_and_rechecks_cache_inside_lock(): void + { + $this->app['env'] = 'production'; + app()->detectEnvironment(fn () => 'production'); + + $bundleDir = storage_path('app/ext-bundles'); + File::ensureDirectoryExists($bundleDir); + $cachePath = $bundleDir.'/module.424244.js'; + @unlink($cachePath); + + $enumerations = 0; + $this->moduleManager->shouldReceive('getActiveModules')->andReturnUsing(function () use (&$enumerations) { + $enumerations++; + + return []; + }); + + $lock = Mockery::mock(Lock::class); + // 대기 중 다른 프로세스가 캐시를 완성한 상황 + $lock->shouldReceive('block')->once()->andReturnUsing(function () use ($cachePath) { + File::put($cachePath, '(function(){window.OTHER=1})()'); + + return true; + }); + $lock->shouldReceive('release')->once()->andReturn(true); + + Cache::shouldReceive('lock')->once()->andReturn($lock); + + $path = $this->service()->getBundleFilePath('module', 'js', 424244); + + // 경로 구분자는 스토리지 드라이버가 정한다 — 같은 파일을 가리키는지만 본다 + $this->assertSame(realpath($cachePath), realpath($path)); + $this->assertSame(0, $enumerations, '잠금 뒤 재확인이 적중하면 빌드하지 않아야 한다'); + + @unlink($cachePath); + } + + /** + * 잠금 대기가 초과되어도 실패로 바꾸지 않는다 — 잠금 없이 각자 빌드한다. + * + * @effects prod_build_lock_failure_falls_back_to_unlocked_build + */ + public function test_prod_cache_miss_builds_without_lock_when_lock_times_out(): void + { + $this->app['env'] = 'production'; + app()->detectEnvironment(fn () => 'production'); + + $a = $this->writeFixture('lock-timeout.js', '(function(){window.A=1})()'); + $this->moduleManager->shouldReceive('getActiveModules')->andReturn([ + 'ext-a' => $this->fakeExtension('ext-a', 10, $a, null), + ]); + + $lock = Mockery::mock(Lock::class); + $lock->shouldReceive('block')->once()->andThrow(new LockTimeoutException); + $lock->shouldReceive('release')->never(); + + Cache::shouldReceive('lock')->once()->andReturn($lock); + + $path = $this->service()->getBundleFilePath('module', 'js', 424245); + + $this->assertNotSame('', $path); + $this->assertFileExists($path); + $this->assertStringContainsString('window.A=1', (string) file_get_contents($path)); + } + + /** + * 캐시 저장소가 잠금을 제공하지 못해도(드라이버 미지원·권한 등) 빌드는 계속한다. + * + * @effects prod_build_lock_failure_falls_back_to_unlocked_build + */ + public function test_prod_cache_miss_builds_without_lock_when_store_cannot_lock(): void + { + $this->app['env'] = 'production'; + app()->detectEnvironment(fn () => 'production'); + + $a = $this->writeFixture('lock-unavailable.js', '(function(){window.A=1})()'); + $this->moduleManager->shouldReceive('getActiveModules')->andReturn([ + 'ext-a' => $this->fakeExtension('ext-a', 10, $a, null), + ]); + + Cache::shouldReceive('lock')->once()->andThrow(new \RuntimeException('lock unsupported')); + + $path = $this->service()->getBundleFilePath('module', 'js', 424246); + + $this->assertNotSame('', $path); + $this->assertFileExists($path); + $this->assertStringContainsString('window.A=1', (string) file_get_contents($path)); + } + + /** + * 선언 산출물이 **전부 존재하되 비어 있으면** 0바이트 캐시 파일을 만든다. + * + * 그래야 정적 게시 대상이 되어 브라우저가 웹서버에서 직접 받는다. 만들지 않으면 + * 그 구성의 모든 페이지 로드가 PHP 를 경유하고, 요청마다 컨트롤러가 열거를 세 번 + * 반복한다(경로 조회 → 재빌드 → 소실 판정). + * + * @effects prod_empty_result_with_present_artifacts_is_cached_as_zero_byte_file + */ + public function test_prod_caches_zero_byte_bundle_when_declared_artifacts_exist_but_empty(): void + { + $this->app['env'] = 'production'; + app()->detectEnvironment(fn () => 'production'); + + $cssPath = $this->writeFixture('zero-byte.css', ''); + + $enumerations = 0; + $this->moduleManager->shouldReceive('getActiveModules')->andReturnUsing(function () use (&$enumerations, $cssPath) { + $enumerations++; + + return [ + 'ext-empty-css' => $this->fakeExtensionWithAssets( + 'ext-empty-css', + 100, + ['css' => ['output' => 'dist/css/module.css']], + 'global', + ['css' => $cssPath], + ['css' => $cssPath] + ), + ]; + }); + + $svc = $this->service(); + + $path = $svc->getBundleFilePath('module', 'css', 424247); + + $this->assertNotSame('', $path); + $this->assertFileExists($path); + $this->assertSame(0, filesize($path)); + + $enumerationsAfterFirst = $enumerations; + $this->assertSame($path, $svc->getBundleFilePath('module', 'css', 424247)); + $this->assertSame($enumerationsAfterFirst, $enumerations, '0바이트 캐시도 적중하면 열거하지 않는다'); + } + + /** + * 선언 산출물이 **소실**이면 캐시하지 않는다 — 503 계약을 보존한다. + * + * 0바이트 캐시가 소실 상태까지 가리면 배포 중 dist 가 빈 장애가 정상(빈 200)으로 + * 위장된다. + * + * @effects prod_empty_result_with_missing_artifact_is_not_cached + */ + public function test_prod_does_not_cache_when_declared_artifact_missing(): void + { + $this->app['env'] = 'production'; + app()->detectEnvironment(fn () => 'production'); + + $absent = $this->fixtureDir.'/absent-prod.css'; + @unlink($absent); + + $this->moduleManager->shouldReceive('getActiveModules')->andReturn([ + 'ext-gone' => $this->fakeExtensionWithAssets( + 'ext-gone', + 100, + ['css' => ['output' => 'dist/css/module.css']], + 'global', + ['css' => $absent] + ), + ]); + + $path = $this->service()->getBundleFilePath('module', 'css', 424248); + + $this->assertSame('', $path); + $this->assertFileDoesNotExist(storage_path('app/ext-bundles/module.424248.css')); + } + + /** + * 선언한 확장이 **하나도 없어도** 0바이트 캐시를 만든다(정적 게시 가능). + * + * @effects prod_empty_result_with_present_artifacts_is_cached_as_zero_byte_file + */ + public function test_prod_caches_zero_byte_bundle_when_nothing_declared(): void + { + $this->app['env'] = 'production'; + app()->detectEnvironment(fn () => 'production'); + + $this->moduleManager->shouldReceive('getActiveModules')->andReturn([]); + + $path = $this->service()->getBundleFilePath('module', 'css', 424249); + + $this->assertNotSame('', $path); + $this->assertFileExists($path); + $this->assertSame(0, filesize($path)); + } + + /** + * 병합 단계에서 건너뛴 확장이 하나라도 있으면 캐시하지 않는다 — 파일은 존재·판독 가능한데 + * 읽기·치환이 실패한 상태가 0바이트(또는 일부 빠진) 캐시로 굳으면 버전 bump 전까지 그 + * 확장 스타일이 사라진 채 고정된다. 종전처럼 매 요청 재시도해 원인이 사라지면 회복한다. + * + * @effects prod_build_with_skipped_extension_is_not_cached + */ + public function test_prod_does_not_cache_when_an_extension_was_skipped_during_merge(): void + { + $this->app['env'] = 'production'; + app()->detectEnvironment(fn () => 'production'); + + $cssPath = $this->writeFixture('unreadable.css', '.a{color:red}'); + + $this->moduleManager->shouldReceive('getActiveModules')->andReturn([ + 'ext-unreadable' => $this->fakeExtension('ext-unreadable', 100, null, $cssPath), + ]); + + $svc = new class($this->moduleManager, $this->pluginManager) extends ExtensionBundleService + { + public string $failOn = ''; + + protected function readAssetSource(string $path): string|false + { + return $path === $this->failOn ? false : parent::readAssetSource($path); + } + }; + $svc->failOn = $cssPath; + + $this->assertSame('', $svc->getBundleFilePath('module', 'css', 424250)); + $this->assertFileDoesNotExist(storage_path('app/ext-bundles/module.424250.css')); + + // 원인이 사라지면 다음 요청이 정상 캐시한다 (매 요청 재시도) + $svc->failOn = ''; + $path = $svc->getBundleFilePath('module', 'css', 424250); + + $this->assertNotSame('', $path); + $this->assertStringEqualsFile($path, '.a{color:red}'); + } } diff --git a/tests/Unit/Services/SettingsServiceSetSettingSideEffectsTest.php b/tests/Unit/Services/SettingsServiceSetSettingSideEffectsTest.php index 796cda5e..87788adf 100644 --- a/tests/Unit/Services/SettingsServiceSetSettingSideEffectsTest.php +++ b/tests/Unit/Services/SettingsServiceSetSettingSideEffectsTest.php @@ -13,7 +13,7 @@ use Illuminate\Support\Facades\Cache; use Tests\TestCase; /** - * 코어 단건 설정 저장(setSetting)의 부수효과 정합 테스트 (공개 #114 동종, B-3) + * [case:cache-18] 코어 단건 설정 저장(setSetting)의 부수효과 정합 테스트 (공개 #114 동종, B-3) * * 단건 저장은 `PUT /api/admin/settings/{key}` 로 실제 도달 가능한 경로인데, 벌크 저장이 * 수행하는 부수효과 중 일부를 건너뛰었다: diff --git a/tests/Unit/Services/TemplateLayoutAttachmentServingTest.php b/tests/Unit/Services/TemplateLayoutAttachmentServingTest.php index b215918b..696839d1 100644 --- a/tests/Unit/Services/TemplateLayoutAttachmentServingTest.php +++ b/tests/Unit/Services/TemplateLayoutAttachmentServingTest.php @@ -121,4 +121,35 @@ class TemplateLayoutAttachmentServingTest extends TestCase $this->assertNull($result); } + + /** + * 고아 disk 행은 withDisk 없이 주입 스토리지로 서빙돼야 합니다 (500 회귀 가드). + * + * 공개 자산 디스크가 플러그인 등록 디스크일 수 있으므로, 그 플러그인이 비활성화되면 + * 행의 disk 가 config 에서 사라진다. 미등록 disk 로 withDisk 를 만들면 이후 + * response() 가 InvalidArgumentException 을 던져 **무인증 공개 서빙 라우트가 500** + * 이 된다. 파일 도달 불가는 404 로 끝나는 것이 정상 degradation 이다. + * + * @scenario public_asset_disk=ghost_disk,row_disk=attachments,filter_url_hook=absent + * + * @effects orphan_row_disk_serves_without_exception, proxy_url_otherwise + */ + public function test_orphan_row_disk_falls_back_to_injected_storage(): void + { + $template = new Template; + $template->id = 7; + $this->templateRepository->shouldReceive('findByIdentifier') + ->with('sirsoft-basic')->andReturn($template); + + // 고아 disk 로는 withDisk 가 불려서는 안 된다 + $this->storage->shouldNotReceive('withDisk'); + $this->storage->shouldReceive('response')->once()->andReturnNull(); + + $result = $this->service->getServableResponse( + 'sirsoft-basic', + $this->makeAttachment(7, 'vanished_plugin_disk') + ); + + $this->assertNull($result); + } } diff --git a/tests/Unit/Services/TemplateLayoutAttachmentUrlResolutionTest.php b/tests/Unit/Services/TemplateLayoutAttachmentUrlResolutionTest.php new file mode 100644 index 00000000..9b803e77 --- /dev/null +++ b/tests/Unit/Services/TemplateLayoutAttachmentUrlResolutionTest.php @@ -0,0 +1,278 @@ +storage = Mockery::mock(StorageInterface::class); + + $this->service = new TemplateLayoutAttachmentService( + Mockery::mock(TemplateLayoutAttachmentRepositoryInterface::class), + Mockery::mock(TemplateRepositoryInterface::class), + $this->storage + ); + } + + /** 템플릿 관계가 로드된 첨부를 만듭니다 (DB 미저장). */ + private function makeAttachment(string $disk): TemplateLayoutAttachment + { + $template = new Template; + $template->id = 7; + $template->identifier = 'sirsoft-basic'; + + $attachment = new TemplateLayoutAttachment([ + 'layout_name' => 'home', + 'disk' => $disk, + 'path' => 'sirsoft-basic/2026/09/08/bg.png', + 'original_name' => 'bg.png', + 'mime_type' => 'image/png', + 'size' => 178, + ]); + $attachment->id = 1; + $attachment->template_id = 7; + $attachment->setRelation('template', $template); + + return $attachment; + } + + /** 프록시(공개 서빙 라우트) URL 인지 단언합니다. */ + private function assertProxyUrl(string $url): void + { + $this->assertStringContainsString('/layout-attachments/1/file', $url); + } + + /** + * 공개 자산 디스크 미설정 — 기본 설치. 프록시 URL 이어야 합니다. + * + * @scenario public_asset_disk=unset,row_disk=attachments,filter_url_hook=absent + * + * @effects proxy_url_otherwise + */ + public function test_unset_public_asset_disk_returns_proxy_url(): void + { + Config::set('core.storage.public_asset_disk', ''); + $this->storage->shouldNotReceive('withDisk'); + + $this->assertProxyUrl($this->service->resolveUrl($this->makeAttachment('attachments'))); + } + + /** + * 공개 자산 디스크 미설정 + 행 disk 가 s3 + s3.url 설정 — 프록시여야 합니다. + * + * 이 케이스가 §1 의 함정이다. `url()` non-null 이면 우선한다는 규칙을 문자 + * 그대로 적용하면, 비공개 버킷 + `AWS_URL` 설정 환경에서 발급된 직접 URL 이 + * 403 이 되어 배경 이미지가 전부 깨진다. 회귀 방지 축으로 가장 중요하다. + * + * @scenario public_asset_disk=unset,row_disk=s3,filter_url_hook=absent + * + * @effects proxy_url_otherwise + */ + public function test_private_s3_row_with_url_config_still_returns_proxy_url(): void + { + Config::set('core.storage.public_asset_disk', ''); + Config::set('attachment.disk', 's3'); + Config::set('filesystems.disks.s3.url', 'https://bucket.s3.ap-northeast-2.amazonaws.com'); + $this->storage->shouldNotReceive('withDisk'); + + $this->assertProxyUrl($this->service->resolveUrl($this->makeAttachment('s3'))); + } + + /** + * 공개 자산 디스크 설정 + 행 disk 일치 — 직접 URL 이어야 합니다. + * + * @scenario public_asset_disk=public,row_disk=public,filter_url_hook=absent + * + * @effects direct_url_when_row_matches_public_disk + */ + public function test_matching_public_asset_disk_returns_direct_url(): void + { + Config::set('core.storage.public_asset_disk', 'public'); + + $this->storage->shouldReceive('withDisk')->once()->with('public')->andReturnSelf(); + $this->storage->shouldReceive('url')->once() + ->with('template-layout-attachments', 'sirsoft-basic/2026/09/08/bg.png') + ->andReturn('https://cdn.example.test/template-layout-attachments/sirsoft-basic/2026/09/08/bg.png'); + + $this->assertSame( + 'https://cdn.example.test/template-layout-attachments/sirsoft-basic/2026/09/08/bg.png', + $this->service->resolveUrl($this->makeAttachment('public')) + ); + } + + /** + * 공개 자산 디스크 설정 + 행 disk 불일치(설정 이전 업로드분) — 프록시여야 합니다. + * + * @scenario public_asset_disk=public,row_disk=attachments,filter_url_hook=absent + * + * @effects proxy_url_otherwise + */ + public function test_legacy_row_on_other_disk_returns_proxy_url(): void + { + Config::set('core.storage.public_asset_disk', 'public'); + $this->storage->shouldNotReceive('withDisk'); + + $this->assertProxyUrl($this->service->resolveUrl($this->makeAttachment('attachments'))); + } + + /** + * 설정된 공개 자산 디스크가 config 에 없으면(고아) 프록시여야 합니다. + * + * @scenario public_asset_disk=ghost_disk,row_disk=attachments,filter_url_hook=absent + * + * @effects proxy_url_otherwise + */ + public function test_orphan_public_asset_disk_returns_proxy_url(): void + { + Config::set('core.storage.public_asset_disk', 'vanished_plugin_disk'); + $this->storage->shouldNotReceive('withDisk'); + + $this->assertProxyUrl($this->service->resolveUrl($this->makeAttachment('vanished_plugin_disk'))); + } + + /** + * `core.storage.filter_url` 훅이 URL 을 차단해 url() 이 null 이면 프록시로 폴백해야 합니다. + * + * @scenario public_asset_disk=public,row_disk=public,filter_url_hook=blocks + * + * @effects proxy_url_otherwise + */ + public function test_blocked_url_hook_falls_back_to_proxy_url(): void + { + Config::set('core.storage.public_asset_disk', 'public'); + + $this->storage->shouldReceive('withDisk')->once()->with('public')->andReturnSelf(); + $this->storage->shouldReceive('url')->once()->andReturnNull(); + + $this->assertProxyUrl($this->service->resolveUrl($this->makeAttachment('public'))); + } + + /** + * 공개 자산 디스크가 s3(비공개 버킷)이고 행 disk 도 s3 — 직접 URL 이 발급돼야 합니다. + * + * 운영자가 "이 저장소는 공개다" 라고 **선언한** 결과이므로 시스템은 그 선언을 따른다. + * 버킷이 실제로는 비공개여서 그 주소가 403 이 되는 것은 설정 책임이며, 문서의 + * "버킷/CDN 쪽 요구사항" 이 그 조건을 명시한다. 선언하지 않은 상태(unset)에서 + * 같은 행이 프록시로 남는 것과 대비되는 축이다. + * + * @scenario public_asset_disk=s3_private,row_disk=s3,filter_url_hook=absent + * + * @effects direct_url_when_row_matches_public_disk + */ + public function test_declared_s3_disk_with_matching_row_returns_direct_url(): void + { + Config::set('core.storage.public_asset_disk', 's3'); + Config::set('filesystems.disks.s3.url', 'https://bucket.s3.ap-northeast-2.amazonaws.com'); + + $this->storage->shouldReceive('withDisk')->once()->with('s3')->andReturnSelf(); + $this->storage->shouldReceive('url')->once() + ->andReturn('https://bucket.s3.ap-northeast-2.amazonaws.com/template-layout-attachments/sirsoft-basic/2026/09/08/bg.png'); + + $this->assertSame( + 'https://bucket.s3.ap-northeast-2.amazonaws.com/template-layout-attachments/sirsoft-basic/2026/09/08/bg.png', + $this->service->resolveUrl($this->makeAttachment('s3')) + ); + } + + /** + * 공개 자산 디스크가 s3 인데 행 disk 가 public — 불일치이므로 프록시여야 합니다. + * + * 공개 자산 디스크를 public → s3 로 재구성한 뒤 남은 과거 행의 상황이다. + * 파일은 public 에 그대로 있고 프록시가 행 disk 로 서빙하므로 화면은 정상이다. + * + * @scenario public_asset_disk=s3_private,row_disk=public,filter_url_hook=absent + * + * @effects proxy_url_otherwise + */ + public function test_reconfigured_public_asset_disk_demotes_old_rows_to_proxy(): void + { + Config::set('core.storage.public_asset_disk', 's3'); + Config::set('filesystems.disks.s3.url', 'https://bucket.s3.ap-northeast-2.amazonaws.com'); + $this->storage->shouldNotReceive('withDisk'); + + $this->assertProxyUrl($this->service->resolveUrl($this->makeAttachment('public'))); + } + + /** + * 공개 자산 디스크가 public 인데 행 disk 가 s3 — 불일치이므로 프록시여야 합니다. + * + * 첨부 디스크(s3)와 공개 자산 디스크(public)를 다르게 둔 이상적 혼재 구성에서, + * 설정 이전에 s3 로 올라간 행이 직접 URL 로 승격되지 않아야 한다. + * + * @scenario public_asset_disk=public,row_disk=s3,filter_url_hook=absent + * + * @effects proxy_url_otherwise + */ + public function test_mixed_disks_keep_old_s3_rows_on_proxy(): void + { + Config::set('core.storage.public_asset_disk', 'public'); + Config::set('attachment.disk', 's3'); + Config::set('filesystems.disks.s3.url', 'https://bucket.s3.ap-northeast-2.amazonaws.com'); + $this->storage->shouldNotReceive('withDisk'); + + $this->assertProxyUrl($this->service->resolveUrl($this->makeAttachment('s3'))); + } + + /** + * 훅이 차단하는데 행 disk 가 공개 자산 디스크와 불일치 — 훅이 발화하지도 않고 프록시여야 합니다. + * + * 게이트가 url() 호출 앞에 있으므로 훅은 개입할 기회조차 얻지 못한다. + * + * @scenario public_asset_disk=public,row_disk=s3,filter_url_hook=blocks + * + * @effects proxy_url_otherwise + */ + public function test_hook_never_fires_when_row_disk_does_not_match(): void + { + Config::set('core.storage.public_asset_disk', 'public'); + $this->storage->shouldNotReceive('withDisk'); + $this->storage->shouldNotReceive('url'); + + $this->assertProxyUrl($this->service->resolveUrl($this->makeAttachment('s3'))); + } + + /** + * 프록시 URL 은 사이트 상대 경로여야 합니다. + * + * 절대 URL 로 발급하면 두 가지가 어긋난다 — ① 저장 게이트(NoExternalUrls)가 서버가 + * 스스로 발급한 주소를 외부로 차단해 업로드 → 저장이 422 로 끝나고, ② 저장된 레이아웃이 + * 발급 시점의 도메인·스킴에 묶여 주소가 바뀌면 그 이미지가 전부 깨진다. + * + * @scenario url_host=site_relative_path + * + * @effects proxy_url_is_site_relative, issued_asset_url_passes_storage_gate + */ + public function test_proxy_url_is_site_relative_path(): void + { + $url = $this->service->resolveUrl($this->makeAttachment('attachments')); + + $this->assertStringStartsWith('/api/', $url); + $this->assertDoesNotMatchRegularExpression('#^[a-z][a-z0-9+.\-]*:#i', $url, '프록시 URL 에 스킴이 있으면 안 됩니다'); + $this->assertStringNotContainsString('//', $url); + } +} diff --git a/tests/Unit/Support/ComposerInstallInfoTest.php b/tests/Unit/Support/ComposerInstallInfoTest.php new file mode 100644 index 00000000..52970dc6 --- /dev/null +++ b/tests/Unit/Support/ComposerInstallInfoTest.php @@ -0,0 +1,181 @@ +basePath = storage_path('framework/testing/composer-install-info-'.uniqid()); + $this->vendorPath = $this->basePath.'/vendor'; + + File::ensureDirectoryExists($this->vendorPath.'/composer'); + } + + protected function tearDown(): void + { + if (File::isDirectory($this->basePath)) { + File::deleteDirectory($this->basePath); + } + + parent::tearDown(); + } + + /** + * `dev: true` + 이름 목록 → 개발용 설치. + * + * @effects core_detects_dev_vendor_from_installed_json + */ + #[Test] + public function dev_true_와_이름_목록이_있으면_개발용_설치로_판정한다(): void + { + $this->writeInstalledJson([ + 'packages' => [], + 'dev' => true, + 'dev-package-names' => ['laravel/boost', 'laravel/mcp', 'laravel/pail'], + ]); + + $info = ComposerInstallInfo::inspect($this->vendorPath); + + $this->assertTrue($info['dev']); + $this->assertSame(['laravel/boost', 'laravel/mcp', 'laravel/pail'], $info['packages']); + $this->assertTrue(ComposerInstallInfo::isDevInstall($this->vendorPath)); + $this->assertCount(3, ComposerInstallInfo::devPackageNames($this->vendorPath)); + } + + /** + * `--no-dev` 설치 → 개발용 아님. + * + * @effects core_reports_no_dev_packages_for_no_dev_vendor + */ + #[Test] + public function dev_false_이고_이름_목록이_비면_운영용_설치로_판정한다(): void + { + $this->writeInstalledJson([ + 'packages' => [], + 'dev' => false, + 'dev-package-names' => [], + ]); + + $info = ComposerInstallInfo::inspect($this->vendorPath); + + $this->assertFalse($info['dev']); + $this->assertSame([], $info['packages']); + $this->assertFalse(ComposerInstallInfo::isDevInstall($this->vendorPath)); + $this->assertSame([], ComposerInstallInfo::devPackageNames($this->vendorPath)); + } + + /** + * `dev` 는 false 인데 이름 목록이 남아 있으면 개발용으로 본다 — 두 신호는 OR 다. + * Composer 버전에 따라 한쪽만 채워질 수 있고, 등재된 provider 가 있다는 사실이 중요하다. + */ + #[Test] + public function dev_false_라도_이름_목록이_비어있지_않으면_개발용으로_판정한다(): void + { + $this->writeInstalledJson([ + 'packages' => [], + 'dev' => false, + 'dev-package-names' => ['laravel/mcp'], + ]); + + $this->assertTrue(ComposerInstallInfo::isDevInstall($this->vendorPath)); + } + + /** + * installed.json 이 없으면 판정 불가(null) — "개발용 아님"(false)과 구분한다. + * + * @effects core_returns_unknown_when_installed_json_missing + */ + #[Test] + public function installed_json_이_없으면_판정_불가다(): void + { + $info = ComposerInstallInfo::inspect($this->vendorPath); + + $this->assertNull($info['dev']); + $this->assertSame([], $info['packages']); + $this->assertNull(ComposerInstallInfo::isDevInstall($this->vendorPath)); + } + + /** + * JSON 파싱 실패도 판정 불가다. + */ + #[Test] + public function json_이_깨졌으면_판정_불가다(): void + { + File::put($this->vendorPath.'/'.ComposerInstallInfo::INSTALLED_JSON, '{ not json'); + + $this->assertNull(ComposerInstallInfo::isDevInstall($this->vendorPath)); + } + + /** + * Composer 1 형식(최상위가 패키지 배열)은 dev 정보가 없어 판정 불가다. + */ + #[Test] + public function composer1_형식은_판정_불가다(): void + { + File::put($this->vendorPath.'/'.ComposerInstallInfo::INSTALLED_JSON, json_encode([ + ['name' => 'vendor/pkg', 'version' => '1.0.0'], + ])); + + $this->assertNull(ComposerInstallInfo::isDevInstall($this->vendorPath)); + } + + /** + * 이름 목록의 비문자열·공백 항목은 걸러낸다 (건수를 그대로 화면에 표시하므로). + */ + #[Test] + public function 이름_목록의_비문자열과_공백_항목을_걸러낸다(): void + { + $this->writeInstalledJson([ + 'packages' => [], + 'dev' => true, + 'dev-package-names' => ['laravel/mcp', '', ' ', 123, null, ['nested'], 'laravel/pail'], + ]); + + $this->assertSame(['laravel/mcp', 'laravel/pail'], ComposerInstallInfo::devPackageNames($this->vendorPath)); + } + + /** + * 경로 끝의 구분자 유무와 무관하게 같은 결과를 낸다. + */ + #[Test] + public function 경로_끝_구분자_유무와_무관하게_동작한다(): void + { + $this->writeInstalledJson([ + 'packages' => [], + 'dev' => true, + 'dev-package-names' => ['laravel/mcp'], + ]); + + $this->assertTrue(ComposerInstallInfo::isDevInstall($this->vendorPath.'/')); + $this->assertTrue(ComposerInstallInfo::isDevInstall($this->vendorPath)); + } + + /** + * installed.json 픽스처를 씁니다. + * + * @param array $payload JSON 으로 직렬화할 내용 + */ + private function writeInstalledJson(array $payload): void + { + File::put($this->vendorPath.'/'.ComposerInstallInfo::INSTALLED_JSON, json_encode($payload)); + } +} diff --git a/tests/Unit/Support/ConfigCacheHelperContainerTest.php b/tests/Unit/Support/ConfigCacheHelperContainerTest.php index e0919d56..00d6c90f 100644 --- a/tests/Unit/Support/ConfigCacheHelperContainerTest.php +++ b/tests/Unit/Support/ConfigCacheHelperContainerTest.php @@ -4,6 +4,8 @@ namespace Tests\Unit\Support; use App\Support\ConfigCacheHelper; use Illuminate\Container\Container; +use Illuminate\Contracts\Console\Kernel; +use Illuminate\Support\Facades\Facade; use PHPUnit\Framework\Attributes\Test; use Tests\TestCase; @@ -59,6 +61,84 @@ class ConfigCacheHelperContainerTest extends TestCase $this->assertSame($original, Container::getInstance()); } + /** + * 콜백이 파사드 애플리케이션을 바꿔 놓아도 실행 후 원래 앱으로 돌아온다. + * + * `config:cache` 가 부팅하는 일회용 앱의 `registerBaseBindings()` 는 컨테이너 인스턴스만이 아니라 + * `Facade::clearResolvedInstances()` + `Facade::setFacadeApplication()` 도 호출한다. 컨테이너만 + * 되돌리면 그 뒤의 **모든 파사드 호출**(`Artisan`·`Log`·`DB`·`Cache` …)이 종료된 일회용 앱에서 + * 인스턴스를 해석한다. + * + * 코어 업데이트에서 드러난 형태: Step 11 의 `ConfigCacheHelper::rebuild()` 뒤에 오는 + * `RouteCacheHelper::rebuild()` 의 `Artisan::call('route:clear')` 가 일회용 앱에서 **새 콘솔 Kernel** + * 을 해석하고, 그 Kernel 이 콘솔 Application 을 처음부터 구성하며 등록된 커맨드를 전부 resolve 해 + * `Target class [command.tinker] does not exist.` 로 업데이트가 실패·롤백했다 (2026-09-07 실측). + * + * @effects config_cache_rebuild_preserves_facade_application + */ + #[Test] + public function 콜백이_파사드_애플리케이션을_바꿔도_원래_앱으로_되돌린다(): void + { + $original = Facade::getFacadeApplication(); + $this->assertNotNull($original, '전제: 파사드 애플리케이션이 설정되어 있다'); + + ConfigCacheHelper::withPreservedContainer(static function (): void { + // 일회용 앱 부팅이 하는 일과 같은 형태 — 해석 캐시를 비우고 다른 앱을 가리킨다. + Facade::clearResolvedInstances(); + Facade::setFacadeApplication(new Container); + }); + + $this->assertSame( + $original, + Facade::getFacadeApplication(), + 'config:cache 뒤 파사드가 일회용 앱을 가리킨 채 남으면 후속 Artisan::call 이 새 콘솔 Kernel 을 만든다' + ); + } + + /** + * 콜백이 예외를 던져도 파사드 애플리케이션은 되돌린다. + * + * @effects config_cache_rebuild_preserves_facade_application + */ + #[Test] + public function 콜백이_실패해도_파사드_애플리케이션을_되돌린다(): void + { + $original = Facade::getFacadeApplication(); + + try { + ConfigCacheHelper::withPreservedContainer(static function (): void { + Facade::setFacadeApplication(new Container); + + throw new \RuntimeException('boom'); + }); + $this->fail('예외가 전파되어야 한다'); + } catch (\RuntimeException) { + // 기대한 예외 + } + + $this->assertSame($original, Facade::getFacadeApplication()); + } + + /** + * 되돌린 뒤 파사드가 원래 앱의 인스턴스를 해석한다 (해석 캐시 오염 없음). + * + * @effects config_cache_rebuild_preserves_facade_application + */ + #[Test] + public function 되돌린_뒤_파사드가_원래_앱의_인스턴스를_해석한다(): void + { + $original = Facade::getFacadeApplication(); + $kernelBefore = app(Kernel::class); + + ConfigCacheHelper::withPreservedContainer(static function (): void { + Facade::clearResolvedInstances(); + Facade::setFacadeApplication(new Container); + }); + + $this->assertSame($original, Facade::getFacadeApplication()); + $this->assertSame($kernelBefore, app(Kernel::class), '콘솔 Kernel 이 새로 만들어지면 등록된 커맨드를 전부 다시 resolve 한다'); + } + /** * `rebuild()` 가 `config:cache` 호출을 보존 래퍼로 감싼다 (소스 구조). * diff --git a/tests/Unit/Support/CoreUpdateContextTest.php b/tests/Unit/Support/CoreUpdateContextTest.php new file mode 100644 index 00000000..17979453 --- /dev/null +++ b/tests/Unit/Support/CoreUpdateContextTest.php @@ -0,0 +1,279 @@ + 원래 argv */ + private array $originalArgv = []; + + protected function setUp(): void + { + parent::setUp(); + + $current = $_ENV['G7_UPDATE_IN_PROGRESS'] ?? $_SERVER['G7_UPDATE_IN_PROGRESS'] ?? getenv('G7_UPDATE_IN_PROGRESS'); + $this->originalFlag = ($current === false || $current === null) ? null : (string) $current; + $this->originalArgv = $_SERVER['argv'] ?? []; + + $this->clearFlag(); + $_SERVER['argv'] = ['artisan', 'inspire']; + } + + protected function tearDown(): void + { + if ($this->originalFlag === null) { + $this->clearFlag(); + } else { + $_ENV['G7_UPDATE_IN_PROGRESS'] = $this->originalFlag; + $_SERVER['G7_UPDATE_IN_PROGRESS'] = $this->originalFlag; + putenv('G7_UPDATE_IN_PROGRESS='.$this->originalFlag); + } + + $_SERVER['argv'] = $this->originalArgv; + + parent::tearDown(); + } + + /** + * 플래그도 업데이트 argv 도 없으면 트리 밖이다. + * + * @effects CoreUpdateContext_isInProgress_detects_env_flag_or_update_argv + */ + #[Test] + public function 플래그와_argv_가_모두_없으면_트리_밖이다(): void + { + $this->assertFalse(CoreUpdateContext::isInProgress()); + $this->assertFalse(CoreUpdateContext::hasEnvFlag()); + } + + /** + * `$_ENV` 채널만으로도 감지한다. + */ + #[Test] + public function env_배열_채널의_플래그를_감지한다(): void + { + $_ENV['G7_UPDATE_IN_PROGRESS'] = '1'; + + $this->assertTrue(CoreUpdateContext::isInProgress()); + $this->assertTrue(CoreUpdateContext::hasEnvFlag()); + } + + /** + * `$_SERVER` 채널만으로도 감지한다 (`variables_order` 에 `E` 가 없으면 `$_ENV` 가 빈다). + */ + #[Test] + public function server_배열_채널의_플래그를_감지한다(): void + { + $_SERVER['G7_UPDATE_IN_PROGRESS'] = '1'; + + $this->assertTrue(CoreUpdateContext::isInProgress()); + $this->assertTrue(CoreUpdateContext::hasEnvFlag()); + } + + /** + * putenv 채널만으로도 감지한다 (proc_open 이 자식에 넘긴 프로세스 환경 테이블). + */ + #[Test] + public function putenv_채널의_플래그를_감지한다(): void + { + putenv('G7_UPDATE_IN_PROGRESS=1'); + + $this->assertTrue(CoreUpdateContext::isInProgress()); + $this->assertTrue(CoreUpdateContext::hasEnvFlag()); + } + + /** + * '1' 이 아닌 값은 플래그가 아니다. + */ + #[Test] + public function 플래그_값이_1_이_아니면_트리_밖이다(): void + { + $_ENV['G7_UPDATE_IN_PROGRESS'] = '0'; + + $this->assertFalse(CoreUpdateContext::isInProgress()); + $this->assertFalse(CoreUpdateContext::hasEnvFlag()); + } + + /** + * 플래그가 전파되지 않은 극단 상황 대비 — argv 로도 판정한다. + */ + #[Test] + public function 업데이트_커맨드_argv_로도_트리_안으로_판정한다(): void + { + foreach (['core:update', 'core:execute-upgrade-steps'] as $command) { + $_SERVER['argv'] = ['artisan', $command, '--force']; + + $this->assertTrue(CoreUpdateContext::isInProgress(), "argv {$command} 는 업데이트 트리로 판정한다"); + } + } + + /** + * 웹 SAPI 에서는 argv 를 인정하지 않는다. + * + * CGI/FPM 은 `register_argc_argv=On` 이면 `$_SERVER['argv']` 를 쿼리스트링을 `+` 로 쪼갠 값으로 + * 채운다 — `GET /?x+core:update` 가 `argv[1] === 'core:update'` 를 만든다(2026-09-08 php-cgi 실측). + * 그 SAPI 에서 argv 를 믿으면 비인증 요청이 업데이트 트리로 판정되어 `bootstrap/app.php` 의 + * 자가 치유가 요청마다 매니페스트를 지운다. + * + * @effects CoreUpdateContext_ignores_argv_outside_console_sapi + */ + #[Test] + public function 웹_sapi_에서는_위조_가능한_argv_를_인정하지_않는다(): void + { + // register_argc_argv=On 인 FPM 이 `?x+core:update` 로 만드는 형태. + $_SERVER['argv'] = ['x', 'core:update']; + + foreach (['fpm-fcgi', 'cgi-fcgi', 'apache2handler', 'litespeed', 'cli-server'] as $sapi) { + $this->assertFalse(CoreUpdateContext::isInProgress($sapi), "{$sapi} 에서는 argv 로 트리 안이 되면 안 된다"); + } + + foreach (['cli', 'phpdbg'] as $sapi) { + $this->assertTrue(CoreUpdateContext::isInProgress($sapi), "{$sapi} 에서는 argv 보조 판정이 유효하다"); + } + } + + /** + * env 플래그 채널은 SAPI 와 무관하다 — 웹 요청으로는 주입할 수 없고, 웹 요청 안에서 시작하는 + * 업데이트 흐름이 프로세스 안에서 세우는 채널이다. + * + * @effects CoreUpdateContext_env_flag_is_honored_regardless_of_sapi + */ + #[Test] + public function env_플래그는_웹_sapi_에서도_인정한다(): void + { + $_ENV['G7_UPDATE_IN_PROGRESS'] = '1'; + $_SERVER['argv'] = ['index.php']; + + foreach (['fpm-fcgi', 'cgi-fcgi', 'cli'] as $sapi) { + $this->assertTrue(CoreUpdateContext::isInProgress($sapi), "{$sapi} 에서도 env 플래그는 트리 안이다"); + } + } + + /** + * `hasEnvFlag()` 는 argv 보조 판정을 섞지 않는다 — 섞으면 단독 실행이 spawn 자식으로 + * 오판되어 사전·사후 단계를 통째로 건너뛴다. + */ + #[Test] + public function has_env_flag_는_argv_만으로는_참이_되지_않는다(): void + { + $_SERVER['argv'] = ['artisan', 'core:execute-upgrade-steps', '--force']; + + $this->assertTrue(CoreUpdateContext::isInProgress(), 'isInProgress 는 argv 로도 참'); + $this->assertFalse(CoreUpdateContext::hasEnvFlag(), 'hasEnvFlag 는 env 플래그만 본다'); + } + + /** + * `bootstrap/app.php` 자가 치유 블록의 판정 조건이 이 클래스와 동형이다. + * + * 그 블록은 부팅 전이라 `App\` 클래스를 참조할 수 없어 같은 판정을 순수 PHP 로 **복제**한다. + * 복제본은 어긋나도 예외를 내지 않는다 — 옛 부모(7.0.9·7.0.10) 아래에서 도는 신버전 자식의 + * 유일한 방어가 조용히 사라지고, 증상은 그 조합에서만 나타나므로 개발 저장소에서는 영영 + * 드러나지 않는다. 그래서 두 조건이 같은지를 여기서 잠근다 — 종전에는 양쪽 주석의 상호 + * 참조가 유일한 방어였다. + * + * 대조 항목은 판정을 이루는 다섯 축 전부다: 환경변수 이름 · 읽는 채널 3종 · 참으로 받는 값 + * 3종 · argv 로 인정하는 커맨드 목록 · argv 를 신뢰하는 SAPI 목록(뒤의 둘은 리플렉션으로 이 + * 클래스에서 파생 — 손으로 적으면 항목이 하나 늘어도 통과한다). + */ + #[Test] + public function bootstrap_자가치유_조건이_이_판정기와_동형이다(): void + { + $bootstrap = (string) file_get_contents(base_path('bootstrap/app.php')); + + $start = strpos($bootstrap, 'Core Update: Stale Package Manifest Self-Heal'); + $this->assertNotFalse($start, '자가 치유 블록의 주석 마커를 찾지 못했다 — 마커가 바뀌었다면 이 검사부터 고친다'); + + $end = strpos($bootstrap, 'return $app;', $start); + $this->assertNotFalse($end, '자가 치유 블록의 끝(return $app;)을 찾지 못했다'); + + $block = substr($bootstrap, $start, $end - $start); + + // ① 환경변수 이름 · ② 읽는 채널 3종 — CoreUpdateContext::hasEnvFlag() 와 같아야 한다. + foreach ([ + "\$_ENV['G7_UPDATE_IN_PROGRESS']", + "\$_SERVER['G7_UPDATE_IN_PROGRESS']", + "getenv('G7_UPDATE_IN_PROGRESS')", + ] as $channel) { + $this->assertStringContainsString( + $channel, + $block, + "자가 치유 블록이 판정 채널 {$channel} 을 읽지 않는다 — 그 채널만 살아 있는 호스팅에서 방어가 빠진다" + ); + } + + // ③ 참으로 받는 값 3종. + foreach (["=== '1'", '=== 1', '=== true'] as $accepted) { + $this->assertStringContainsString( + $accepted, + $block, + "자가 치유 블록이 플래그 값 비교 `{$accepted}` 를 갖지 않는다" + ); + } + + // ④ argv 로 인정하는 커맨드 — 목록은 이 클래스에서 파생한다. + $reflection = new \ReflectionClass(CoreUpdateContext::class); + $commands = $reflection->getConstant('UPDATE_COMMANDS'); + + $this->assertIsArray($commands); + $this->assertNotEmpty($commands, '커맨드 목록이 비면 이 대조는 공허하게 통과한다'); + + foreach ($commands as $command) { + $this->assertStringContainsString( + "'{$command}'", + $block, + "자가 치유 블록의 argv 판정에 `{$command}` 가 없다 — 그 커맨드로 도는 자식은 방어를 받지 못한다" + ); + } + + // ⑤ argv 를 신뢰하는 SAPI — 목록은 이 클래스에서 파생한다. 게이트가 빠지면 register_argc_argv=On 인 + // CGI/FPM 에서 `?x+core:update` 한 번에 자가 치유가 켜진다. + $sapis = $reflection->getConstant('CONSOLE_SAPIS'); + + $this->assertIsArray($sapis); + $this->assertNotEmpty($sapis, 'SAPI 목록이 비면 이 대조는 공허하게 통과한다'); + $this->assertStringContainsString( + 'PHP_SAPI', + $block, + '자가 치유 블록의 argv 판정에 SAPI 게이트가 없다 — 웹 요청이 쿼리스트링으로 매니페스트 삭제를 켤 수 있다' + ); + + foreach ($sapis as $sapi) { + $this->assertStringContainsString( + "'{$sapi}'", + $block, + "자가 치유 블록의 SAPI 게이트에 `{$sapi}` 가 없다 — 그 SAPI 로 도는 명령줄 자식은 argv 방어를 받지 못한다" + ); + } + + foreach (['fpm-fcgi', 'cgi-fcgi', 'apache2handler'] as $webSapi) { + $this->assertStringNotContainsString( + "'{$webSapi}'", + $block, + "자가 치유 블록이 웹 SAPI `{$webSapi}` 의 argv 를 신뢰한다" + ); + } + } + + /** + * 세 채널($_ENV/$_SERVER/putenv)에서 플래그를 제거합니다. + */ + private function clearFlag(): void + { + unset($_ENV['G7_UPDATE_IN_PROGRESS'], $_SERVER['G7_UPDATE_IN_PROGRESS']); + putenv('G7_UPDATE_IN_PROGRESS'); + } +} diff --git a/tests/Unit/Support/CustomAssetsPublishableFilesTest.php b/tests/Unit/Support/CustomAssetsPublishableFilesTest.php index 7bc90212..c228f0d5 100644 --- a/tests/Unit/Support/CustomAssetsPublishableFilesTest.php +++ b/tests/Unit/Support/CustomAssetsPublishableFilesTest.php @@ -8,7 +8,7 @@ use PHPUnit\Framework\Attributes\Test; use Tests\TestCase; /** - * `CustomAssets::publishableFiles()` — 게시·변경 감지가 공유하는 열거자 테스트 (#651 F7). + * [case:cache-19] `CustomAssets::publishableFiles()` — 게시·변경 감지가 공유하는 열거자 테스트 (#651 F7). * * 정적 게시는 `custom/**` 를 재귀로 복사하고, 변경 감지는 종전에 최상위 css/js 의 mtime 만 * 서명했다. 두 범위를 서로 다른 코드가 정의하면 어긋나고, 그 어긋남은 "글꼴을 바꿨는데 diff --git a/tests/Unit/Support/ExtensionDoc/ExtensionInventoryTest.php b/tests/Unit/Support/ExtensionDoc/ExtensionInventoryTest.php index 7649524e..1c3f6ff8 100644 --- a/tests/Unit/Support/ExtensionDoc/ExtensionInventoryTest.php +++ b/tests/Unit/Support/ExtensionDoc/ExtensionInventoryTest.php @@ -10,7 +10,7 @@ use PHPUnit\Framework\TestCase; /** * 진입 문서 제목 조립 규칙(`ExtensionInventory::docTitle`)을 고정합니다. * - * 제목은 「그누보드7 {확장명} {유형}」 이다 (PO 결정 2026-09-04). 확장명이 이미 유형으로 + * 제목은 「그누보드7 {확장명} {유형}」 이다 (설계 결정 2026-09-04). 확장명이 이미 유형으로 * 끝나면 겹쳐 붙이지 않는다 — 이 예외가 깨지면 「그누보드7 Hello 모듈 모듈」 이 되는데, * 계약 테스트는 같은 헬퍼로 기대값을 만들므로 그 형태도 초록으로 통과한다. 그래서 규칙 * 자체는 여기서 리터럴로 고정한다. diff --git a/tests/Unit/Support/PackageManifestCacheHelperTest.php b/tests/Unit/Support/PackageManifestCacheHelperTest.php new file mode 100644 index 00000000..e4e234a7 --- /dev/null +++ b/tests/Unit/Support/PackageManifestCacheHelperTest.php @@ -0,0 +1,210 @@ + 테스트가 덮어쓴 env 의 원값 (3채널 복원용) */ + private array $originalEnv = []; + + private string $packagesPath; + + private string $servicesPath; + + protected function setUp(): void + { + parent::setUp(); + + $uniq = uniqid(); + $relPackages = "storage/framework/testing/manifest-helper-{$uniq}/packages.php"; + $relServices = "storage/framework/testing/manifest-helper-{$uniq}/services.php"; + + $this->packagesPath = base_path($relPackages); + $this->servicesPath = base_path($relServices); + + File::ensureDirectoryExists(dirname($this->packagesPath)); + + $this->setEnv('APP_PACKAGES_CACHE', $relPackages); + $this->setEnv('APP_SERVICES_CACHE', $relServices); + } + + protected function tearDown(): void + { + foreach ($this->originalEnv as $key => $value) { + if ($value === null) { + unset($_ENV[$key], $_SERVER[$key]); + putenv($key); + } else { + $_ENV[$key] = $value; + $_SERVER[$key] = $value; + putenv($key.'='.$value); + } + } + $this->originalEnv = []; + + $dir = dirname($this->packagesPath); + if (File::isDirectory($dir)) { + File::deleteDirectory($dir); + } + + parent::tearDown(); + } + + /** + * env 로 재지정한 경로의 두 파일을 지운다 (하드코딩된 bootstrap/cache 가 아니라). + * + * @effects PackageManifestCacheHelper_clear_unlinks_packages_and_services_at_configured_paths + */ + #[Test] + public function clear_는_설정된_경로의_packages_와_services_를_지운다(): void + { + File::put($this->packagesPath, "servicesPath, "assertSame($this->packagesPath, $this->app->getCachedPackagesPath(), '전제: env 재지정이 경로를 정한다'); + $this->assertSame($this->servicesPath, $this->app->getCachedServicesPath(), '전제: env 재지정이 경로를 정한다'); + + $remaining = PackageManifestCacheHelper::clear(); + + $this->assertFileDoesNotExist($this->packagesPath); + $this->assertFileDoesNotExist($this->servicesPath); + $this->assertSame([], $remaining, '전부 지웠으면 남은 파일이 없다'); + } + + /** + * 파일이 없어도 예외를 던지지 않는다 (부팅 직전에 불리므로 실패가 부팅을 막으면 안 된다). + */ + #[Test] + public function clear_는_파일이_없으면_아무_일도_하지_않는다(): void + { + $this->assertFileDoesNotExist($this->packagesPath, '전제: 파일이 없다'); + + $remaining = PackageManifestCacheHelper::clear(); + + $this->assertFileDoesNotExist($this->packagesPath); + $this->assertFileDoesNotExist($this->servicesPath); + $this->assertSame([], $remaining, '원래 없던 파일은 "지우지 못한 파일" 이 아니다'); + } + + /** + * 지우지 못한 파일은 경로로 돌려주고, 지울 수 있는 형제 파일은 그대로 지운다. + * + * 호출부(spawn 직전)가 이 목록을 업그레이드 로그에 남긴다 — 권한·소유권 불일치면 자식의 자가 + * 치유도 같은 이유로 실패해 증상은 「Class not found」 그대로인데, 그 기록이 원인을 가리키는 + * 유일한 흔적이다. 삭제 실패는 플랫폼마다 실제로 만든다: Windows 는 열린 핸들이, POSIX 는 부모 + * 디렉토리의 쓰기 권한이 삭제를 막는다. + * + * @effects PackageManifestCacheHelper_clear_returns_paths_it_could_not_remove + */ + #[Test] + public function clear_는_지우지_못한_파일의_경로를_돌려준다(): void + { + File::put($this->packagesPath, "servicesPath, "makeUndeletable($this->packagesPath); + + try { + $remaining = PackageManifestCacheHelper::clear(); + } finally { + $release(); + } + + $this->assertSame([$this->packagesPath], $remaining, '지우지 못한 파일만 경로로 돌려준다'); + $this->assertFileExists($this->packagesPath, '삭제가 막힌 파일은 그대로 남는다'); + $this->assertFileDoesNotExist($this->servicesPath, '지울 수 있는 형제 파일은 지운다'); + } + + /** + * 파일을 현재 프로세스가 삭제할 수 없는 상태로 만들고, 되돌리는 클로저를 반환합니다. + * + * Windows 는 읽기 전용 속성(0444)이 삭제를 막고(PHP 7.3+ 는 파일을 `FILE_SHARE_DELETE` 로 열어 + * 열린 핸들로는 막히지 않는다 — 실측), POSIX 는 부모 디렉토리의 쓰기 권한이 삭제를 막는다(root 는 + * 권한을 우회하므로 이 방법으로 실패를 만들 수 없다). + * + * @param string $path 삭제를 막을 파일 + * @return \Closure 원상 복구 클로저 + */ + private function makeUndeletable(string $path): \Closure + { + if (PHP_OS_FAMILY === 'Windows') { + $this->assertTrue(chmod($path, 0444), '전제: 읽기 전용 속성을 건다'); + + return static function () use ($path): void { + if (is_file($path)) { + chmod($path, 0644); + } + }; + } + + if (function_exists('posix_geteuid') && posix_geteuid() === 0) { + $this->markTestSkipped('root 는 디렉토리 권한으로 삭제 실패를 만들 수 없다 — 비-root 로 실행해야 이 축이 측정된다'); + } + + $dir = dirname($path); + $this->assertTrue(chmod($dir, 0555), '전제: 부모 디렉토리의 쓰기 권한을 뺀다'); + + return static function () use ($dir): void { + chmod($dir, 0755); + }; + } + + /** + * rebuild() 는 삭제 후 package:discover 로 다시 만든다. + * + * @effects clearAllCaches_rebuilds_package_manifest_via_helper + */ + #[Test] + public function rebuild_는_지운_뒤_package_discover_를_한_번_호출한다(): void + { + File::put($this->packagesPath, "servicesPath, "once()->with('package:discover')->andReturn(0); + + PackageManifestCacheHelper::rebuild(); + + $this->assertFileDoesNotExist($this->packagesPath, 'rebuild 는 먼저 지운다'); + $this->assertFileDoesNotExist($this->servicesPath, 'rebuild 는 먼저 지운다'); + } + + /** + * env 를 세 채널($_ENV/$_SERVER/putenv)에 세우고 원값을 복원용으로 보관합니다. + * + * @param string $key 환경변수 이름 + * @param string|null $value 세울 값. null 이면 제거 + */ + private function setEnv(string $key, ?string $value): void + { + if (! array_key_exists($key, $this->originalEnv)) { + $current = $_ENV[$key] ?? $_SERVER[$key] ?? getenv($key); + $this->originalEnv[$key] = ($current === false || $current === null) ? null : (string) $current; + } + + if ($value === null) { + unset($_ENV[$key], $_SERVER[$key]); + putenv($key); + + return; + } + + $_ENV[$key] = $value; + $_SERVER[$key] = $value; + putenv($key.'='.$value); + } +} diff --git a/tests/Unit/Support/PublicAssetDiskTest.php b/tests/Unit/Support/PublicAssetDiskTest.php new file mode 100644 index 00000000..a18b05aa --- /dev/null +++ b/tests/Unit/Support/PublicAssetDiskTest.php @@ -0,0 +1,129 @@ +assertNull(PublicAssetDisk::resolve()); + } + + /** + * 'none' 은 명시적 스트리밍 유지 선언이므로 null 이어야 합니다. + */ + public function test_none_resolves_to_null(): void + { + Config::set('core.storage.public_asset_disk', 'none'); + + $this->assertNull(PublicAssetDisk::resolve()); + } + + /** + * config 에 존재하지 않는 디스크(고아 플러그인 디스크)는 null 이어야 합니다. + * + * @scenario public_asset_disk=ghost_disk + * + * @effects proxy_url_otherwise + */ + public function test_orphan_disk_resolves_to_null(): void + { + Config::set('core.storage.public_asset_disk', 'vanished_plugin_disk'); + + $this->assertNull(PublicAssetDisk::resolve()); + } + + /** + * config 에 존재하는 디스크는 그대로 돌려주어야 합니다. + */ + public function test_existing_disk_resolves_to_itself(): void + { + Config::set('core.storage.public_asset_disk', 'public'); + + $this->assertSame('public', PublicAssetDisk::resolve()); + } + + /** + * override 가 비어 있지 않으면 코어 전역 설정보다 우선해야 합니다. + */ + public function test_override_takes_precedence_over_global(): void + { + Config::set('core.storage.public_asset_disk', 'public'); + + $this->assertSame('local', PublicAssetDisk::resolve('local')); + } + + /** + * override 가 ''/null 이면 코어 전역 설정을 사용해야 합니다. + */ + public function test_blank_override_falls_back_to_global(): void + { + Config::set('core.storage.public_asset_disk', 'public'); + + $this->assertSame('public', PublicAssetDisk::resolve('')); + $this->assertSame('public', PublicAssetDisk::resolve(null)); + } + + /** + * override 로 'none' 을 주면 전역 설정이 있어도 스트리밍을 강제해야 합니다. + */ + public function test_none_override_forces_streaming_over_global(): void + { + Config::set('core.storage.public_asset_disk', 'public'); + + $this->assertNull(PublicAssetDisk::resolve('none')); + } + + /** + * isCurrent() 는 resolve() 를 경유하므로 ''/'none'/고아 디스크가 + * 등가 비교로 통과하지 않아야 합니다. + */ + public function test_is_current_never_matches_blank_none_or_orphan(): void + { + Config::set('core.storage.public_asset_disk', ''); + $this->assertFalse(PublicAssetDisk::isCurrent('')); + + Config::set('core.storage.public_asset_disk', 'none'); + $this->assertFalse(PublicAssetDisk::isCurrent('none')); + + Config::set('core.storage.public_asset_disk', 'vanished_plugin_disk'); + $this->assertFalse(PublicAssetDisk::isCurrent('vanished_plugin_disk')); + + Config::set('core.storage.public_asset_disk', 'public'); + $this->assertTrue(PublicAssetDisk::isCurrent('public')); + $this->assertFalse(PublicAssetDisk::isCurrent('attachments')); + $this->assertFalse(PublicAssetDisk::isCurrent(null)); + } + + /** + * memoize 금지 — 런타임 Config::set 변경이 즉시 반영되어야 합니다. + */ + public function test_resolution_is_not_memoized(): void + { + Config::set('core.storage.public_asset_disk', 'public'); + $this->assertSame('public', PublicAssetDisk::resolve()); + + Config::set('core.storage.public_asset_disk', ''); + $this->assertNull(PublicAssetDisk::resolve()); + } +} diff --git a/tests/Unit/Support/SiteAssetHostsTest.php b/tests/Unit/Support/SiteAssetHostsTest.php new file mode 100644 index 00000000..ce775789 --- /dev/null +++ b/tests/Unit/Support/SiteAssetHostsTest.php @@ -0,0 +1,133 @@ +assertContains('shop.example.test', SiteAssetHosts::hosts()); + } + + /** + * 공개 자산 디스크가 선언되어 있으면 그 디스크의 기준 URL host 가 포함되어야 합니다. + * + * @scenario url_host=public_asset_disk + * + * @effects issued_asset_url_passes_storage_gate + */ + public function test_hosts_include_declared_public_asset_disk_host(): void + { + Config::set('core.storage.public_asset_disk', 'public'); + Config::set('filesystems.disks.public.url', 'https://cdn.example.test/assets'); + + $this->assertContains('cdn.example.test', SiteAssetHosts::hosts()); + } + + /** + * 미선언·'none'·고아 디스크는 host 를 보태지 않아야 합니다 — 판정은 PublicAssetDisk::resolve() + * 를 경유하므로 디스크에 url 이 설정돼 있어도 선언 없이는 외부다. + * + * @scenario url_host=external_undeclared + * + * @effects external_host_still_rejected + */ + public function test_undeclared_disk_host_is_not_included(): void + { + Config::set('filesystems.disks.public.url', 'https://cdn.example.test/assets'); + + foreach (['', 'none', 'vanished_plugin_disk'] as $declared) { + Config::set('core.storage.public_asset_disk', $declared); + + $this->assertNotContains('cdn.example.test', SiteAssetHosts::hosts(), "선언값 '{$declared}' 에서 host 가 새면 안 됩니다"); + } + } + + /** + * http/https 절대 URL 이고 host 가 목록에 있을 때만 참이어야 합니다. + * + * @scenario url_host=own_site_absolute + * + * @effects issued_asset_url_passes_storage_gate + */ + public function test_is_site_asset_url_accepts_only_http_urls_on_listed_hosts(): void + { + $this->assertTrue(SiteAssetHosts::isSiteAssetUrl('https://shop.example.test/api/x')); + $this->assertTrue(SiteAssetHosts::isSiteAssetUrl('http://shop.example.test/storage/x.png')); + $this->assertTrue(SiteAssetHosts::isSiteAssetUrl(" https://shop.example.test/x\n")); + + // 경로만 있는 값은 host 가 없으므로 이 판정의 대상이 아니다 (호출측이 경로 규칙으로 다룬다) + $this->assertFalse(SiteAssetHosts::isSiteAssetUrl('/api/x')); + // protocol-relative 는 별도 판정 축 — 자기 host 라도 여기서는 참이 아니다 + $this->assertFalse(SiteAssetHosts::isSiteAssetUrl('//shop.example.test/x')); + // 위험 스킴은 host 와 무관 + $this->assertFalse(SiteAssetHosts::isSiteAssetUrl('ftp://shop.example.test/x')); + $this->assertFalse(SiteAssetHosts::isSiteAssetUrl('javascript://shop.example.test/%0aalert(1)')); + } + + /** + * 자기 host 를 흉내 낸 URL 은 거짓이어야 합니다 — 브라우저와 같은 정규화 뒤 host 를 비교한다. + * + * @scenario url_host=lookalike + * + * @effects external_host_still_rejected + */ + public function test_is_site_asset_url_rejects_lookalikes(): void + { + foreach ([ + 'https://shop.example.test.evil.com/x', + 'https://shop.example.test@evil.com/x', + 'https://evil.com\\@shop.example.test/x', + 'https://evil.com/shop.example.test/x', + 'https://evil.com#shop.example.test', + "https://evil.com\t.shop.example.test/x", + ] as $url) { + $this->assertFalse(SiteAssetHosts::isSiteAssetUrl($url), "흉내 URL 은 거짓이어야 합니다: {$url}"); + } + } + + /** + * app.url 이 비어 있거나 host 를 못 읽는 값이면 빈 목록이어야 합니다 (예외 없음). + * + * @scenario url_host=external_undeclared + * + * @effects external_host_still_rejected + */ + public function test_blank_app_url_yields_no_host(): void + { + foreach (['', 'not a url', '/relative'] as $value) { + Config::set('app.url', $value); + + $this->assertSame([], SiteAssetHosts::hosts(), "app.url='{$value}' 에서 host 가 생기면 안 됩니다"); + $this->assertFalse(SiteAssetHosts::isSiteAssetUrl('https://shop.example.test/x')); + } + } +} diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 880bb2ca..d8fd9d7f 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -174,6 +174,39 @@ putenv('APP_ROUTES_CACHE='.$testingRoutesCache); $_ENV['APP_ROUTES_CACHE'] = $testingRoutesCache; $_SERVER['APP_ROUTES_CACHE'] = $testingRoutesCache; +/* +|-------------------------------------------------------------------------- +| 패키지 매니페스트 캐시 격리 (테스트 환경 보장) +|-------------------------------------------------------------------------- +| +| `bootstrap/cache/packages.php` · `services.php` 도 라우트 캐시와 같이 **경로를 돌린다.** +| 코어 업데이트 흐름은 이 두 파일을 지우고 다시 만드는 코드를 여럿 갖고 있고(spawn 직전 선정리, +| `bootstrap/app.php` 의 업데이트 트리 자가 치유, `clearAllCaches()` 의 `package:discover`), 그 +| 코드를 태우는 테스트 가운데 몇은 `proc_open` 으로 **진짜 자식 프로세스**를 띄운다. 자식은 부모의 +| 테스트 격리(임시 base path·목)를 하나도 물려받지 못하고 env 만 물려받으므로, 경로를 env 로 +| 돌려 두는 것이 부모와 자식을 한 번에 격리하는 유일한 수단이다. 돌리지 않으면 테스트를 한 번 +| 돌릴 때마다 개발 클론의 실제 매니페스트가 지워지고 다시 만들어진다 — 오늘은 재생성 내용이 같아 +| 무해하지만, 그 사이 다른 프로세스가 부팅하면 매번 재빌드를 겪고, 테스트가 실제 파일을 건드리는 +| 구조 자체가 `.env` 삭제 사고와 같은 부류다. +| +| 자기 경로를 따로 쓰는 테스트(`CoreUpdateCommandStalePackageManifestTest` 등)는 여기 값을 +| 원값으로 보관·복원하므로 그대로 동작한다. +| +*/ +$testingManifestDir = __DIR__.'/../storage/framework/testing'; +if (! is_dir($testingManifestDir)) { + @mkdir($testingManifestDir, 0777, true); +} +foreach ([ + 'APP_PACKAGES_CACHE' => 'storage/framework/testing/packages.php', + 'APP_SERVICES_CACHE' => 'storage/framework/testing/services.php', +] as $manifestEnvKey => $manifestRelativePath) { + putenv($manifestEnvKey.'='.$manifestRelativePath); + $_ENV[$manifestEnvKey] = $manifestRelativePath; + $_SERVER[$manifestEnvKey] = $manifestRelativePath; +} +unset($testingManifestDir, $manifestEnvKey, $manifestRelativePath); + // Composer 오토로더 로드 $loader = require __DIR__.'/../vendor/autoload.php'; diff --git a/tests/scenarios/auth-two-factor-login.yaml b/tests/scenarios/auth-two-factor-login.yaml new file mode 100644 index 00000000..da9f1ad3 --- /dev/null +++ b/tests/scenarios/auth-two-factor-login.yaml @@ -0,0 +1,94 @@ +# audit:allow test-scenario-coverage reason: 7축 cross product 는 조건부 축(actor 는 controller=admin 에서만, +# lock_state 는 세션을 여는 단계에서만, delivery 는 비밀번호 단계에서만 의미가 있다)을 곱해 낸 명목상 조합이라 +# 개별 케이스에 docblock 을 매핑할 실체가 없다. 실제 회귀 가드는 effects 22건이며 전부 test_files 의 +# PHPUnit·Playwright 테스트에 @effects 로 귀속되어 있다(미검증 0건). + +feature: 로그인 2단계 인증 (인증번호 확인·재발송) + +description: | + 보안 환경설정의 「2단계 인증」이 켜져 있으면 비밀번호가 맞아도 토큰을 발급하지 않고 + 인증 요청(challenge)만 돌려준다. 즉 로그인 응답은 **두 가지 형태의 200** 이다. + + 핵심 동작: + - 사용자·관리자 두 경로가 같은 규칙으로 challenge 를 발급한다. 관리자 판정은 + 코드 확인에 성공한 뒤에 수행한다 (그 전에는 사용자도 토큰도 없다). + - 코드 확인 시점에 토큰이 발급되므로, 그 뒤 관리자 판정으로 거부하는 경로는 + 발급분을 반드시 회수한다. + - 재발송은 기존 challenge 를 취소하고 새로 발행한다 — 유효한 코드를 여러 개 + 살려 두면 대입 시도의 표적이 넓어진다. + - 인증번호를 보내지 못하면 401 이 아니라 503 으로 답한다. 자격 증명은 올바른데 + 401 로 뭉개면 사용자는 비밀번호를 의심하고 운영자는 원인을 알 수 없다. + - 로그인 challenge 는 공개 본인인증 경로(verify/cancel)로 소진할 수 없다. + 소진되면 그 challenge 로 영영 로그인할 수 없게 된다(자기 DoS). + +axes: + controller: [user, admin] + two_factor: [on, off] + delivery: [sent, failed] + code: [valid, invalid, expired, consumed_by_public_verify] + resend: [none, active, expired, verified, cancelled] + actor: [admin, non_admin] + lock_state: [none, active] + +exclusions: + - { two_factor: off, code: valid, reason: "2단계 인증이 꺼져 있으면 코드 확인 단계가 없다" } + - { two_factor: off, code: invalid, reason: "동일" } + - { two_factor: off, code: expired, reason: "동일" } + - { two_factor: off, code: consumed_by_public_verify, reason: "동일" } + - { two_factor: off, resend: active, reason: "challenge 자체가 발급되지 않는다" } + - { two_factor: off, resend: expired, reason: "동일" } + - { two_factor: off, resend: verified, reason: "동일" } + - { two_factor: off, resend: cancelled, reason: "동일" } + - { two_factor: off, delivery: failed, reason: "발송 경로를 타지 않는다" } + - { delivery: failed, code: valid, reason: "발송에 실패하면 확인할 코드가 없다" } + - { delivery: failed, code: invalid, reason: "동일" } + - { delivery: failed, code: expired, reason: "동일" } + - { delivery: failed, code: consumed_by_public_verify, reason: "동일" } + - { delivery: failed, resend: active, reason: "동일 — 재발송할 challenge 가 없다" } + - { delivery: failed, resend: expired, reason: "동일" } + - { delivery: failed, resend: verified, reason: "동일" } + - { delivery: failed, resend: cancelled, reason: "동일" } + - { controller: user, actor: non_admin, reason: "사용자 경로는 관리자 판정을 하지 않는다" } + - { resend: active, code: invalid, reason: "재발송은 코드 확인 이전 단계라 코드 상태와 직교하지 않는다" } + - { resend: active, code: expired, reason: "동일" } + - { resend: active, code: consumed_by_public_verify, reason: "동일" } + - { resend: expired, code: invalid, reason: "동일" } + - { resend: expired, code: expired, reason: "동일" } + - { resend: expired, code: consumed_by_public_verify, reason: "동일" } + - { resend: verified, code: invalid, reason: "동일" } + - { resend: verified, code: expired, reason: "동일" } + - { resend: verified, code: consumed_by_public_verify, reason: "동일" } + - { resend: cancelled, code: invalid, reason: "동일" } + - { resend: cancelled, code: expired, reason: "동일" } + - { resend: cancelled, code: consumed_by_public_verify, reason: "동일" } + +effects: + - challenge_response_has_no_token + - challenge_response_has_no_user + - delivery_failure_503_with_reason + - admin_login_returns_challenge_not_500 + - admin_two_factor_issues_token_for_admin + - non_admin_two_factor_revokes_token + - non_admin_admin_login_revokes_token + - public_verify_rejects_login_purpose + - public_cancel_rejects_login_purpose + - login_completes_after_public_endpoints_refuse + - signup_purpose_unaffected_by_login_gate + - resend_cancels_previous_challenge + - resend_rejects_verified_challenge + - resend_rejects_expired_challenge + - resend_rejects_cancelled_challenge + - resend_refused_while_locked + - admin_resend_returns_new_challenge + - admin_resend_refuses_non_admin + - too_many_attempts_message_translated + - invalid_code_retries_until_success + - session_state_cleared_after_two_factor_success + - double_submit_sends_single_verify_request + +test_files: + - tests/Feature/Auth/TwoFactorAuthTest.php + - tests/Feature/Auth/TwoFactorAccountLockTest.php + - tests/Feature/Auth/LoginThrottleTest.php + - tests/Feature/Identity/IdentityLoginPurposeGateTest.php + - tests/Playwright/specs/auth/two-factor-login.spec.ts diff --git a/tests/scenarios/core-update-spawn-failure-mode.yaml b/tests/scenarios/core-update-spawn-failure-mode.yaml index 22c46cb6..7a99a925 100644 --- a/tests/scenarios/core-update-spawn-failure-mode.yaml +++ b/tests/scenarios/core-update-spawn-failure-mode.yaml @@ -52,6 +52,14 @@ axes: staging_source_shape: [inner_extracted_root, staging_root_itself, external_source_dir] # cleanupPending 이 받는 경로 형태 — ZIP·GitHub / --local·--source 복제 / --source 외부 staging_leftover_content: [empty_skeleton, has_files, non_core_dir] # 자식 청소 술어 — 빈 껍데기만 대상 resume_exec_context: [non_root, root_web_known, root_web_symmetric, root_web_unknown] # 핸드오프 재실행 권한 안내 분기 (실행 사용자 × 웹서버 계정 식별성) + package_manifest_state: [fresh, stale_dev_provider] # bootstrap/cache/packages.php 가 이전 설치본(dev composer)의 provider 를 담고 있는가 + parent_generation: [pre_7_0_11, 7_0_11_plus] # 부모가 spawn 직전 매니페스트를 비우는 세대인가 (이미 배포된 7.0.9·7.0.10 은 비우지 않는다) + child_self_heal_flag: [present, absent] # 자식이 G7_UPDATE_IN_PROGRESS 를 물려받았는가 (자가 치유 게이트) + process_context: [update_tree, long_lived_outside_update] # 버전 판독 범위 — 상주 프로세스(artisan serve·큐 워커)는 트리 밖이다 + argv_sapi: [console, web_with_forged_query_argv] # argv 보조 판정을 읽는 SAPI — CGI/FPM 은 register_argc_argv=On 이면 `?x+core:update` 로 argv 를 위조할 수 있다 + manifest_unlink_result: [removed, left_by_permission] # spawn 직전 매니페스트 삭제 결과 — 지우지 못한 파일은 업그레이드 로그에 남긴다 + vendor_dev_packages: [present, absent, unknown] # 운영 vendor 에 require-dev 가 섞여 있는가 (installed.json 판정) + composer_step_branch: [reinstalled, skipped_unchanged] # Step 6 이 vendor 를 교체했는가 스킵했는가 exclusions: - { spawn_branch: proc_open_disabled, parent_memory_version: equal_to_to, reason: "proc_open 비활성 시점에 부모는 항상 fromVersion 메모리 — equal 케이스 시뮬 불가" } @@ -61,6 +69,10 @@ exclusions: - { child_process_origin: beta_4_or_earlier, steps_discovered_signal: discovered_zero, reason: "동일 — 구버전 자식은 discovered 필드 미발행(absent)" } - { spawn_branch: child_zero_steps_no_step_files, steps_discovered_signal: discovered_positive, reason: "스텝 파일 부재 브랜치는 정의상 discovered=0 — positive 모순" } - { spawn_branch: child_zero_steps_with_discovered, steps_discovered_signal: discovered_zero, reason: "스텝 파일 존재 브랜치는 정의상 discovered>0 — zero 모순" } + - { parent_generation: 7_0_11_plus, package_manifest_state: stale_dev_provider, reason: "7.0.11+ 부모는 spawn 직전에 매니페스트를 비우므로 자식이 stale 을 보는 상태 자체가 성립하지 않는다 — 계층 ② 검증은 pre_7_0_11 부모에서만 의미가 있다" } + - { process_context: long_lived_outside_update, child_self_heal_flag: present, reason: "플래그를 물고 있으면 정의상 업데이트 트리 안이다 — 모순" } + - { vendor_dev_packages: unknown, composer_step_branch: skipped_unchanged, reason: "판정 불가(installed.json 부재)에서는 감지 로그 자체가 출력되지 않아 분기가 구분되지 않는다" } + - { argv_sapi: web_with_forged_query_argv, child_self_heal_flag: present, reason: "env 플래그는 웹 요청으로 주입할 수 없다 — 위조 argv 축은 플래그 부재 상태에서만 의미가 있다" } effects: # §2 spawn_failure_mode + failSpawnWithMode @@ -113,6 +125,23 @@ effects: - snapshotOwnershipDetailed_excludes_current_run_staging_root - execute_bundled_updates_child_sweeps_empty_staging_directories_left_by_parent - apply_mode_incremental_prune_hint_does_not_reference_rollback_command + # #658 stale 패키지 매니페스트 3계층 (부모 선정리 / 자식 자가 치유 / 버전 판독 범위) + - spawnUpgradeStepsProcess_clears_package_manifests_before_proc_open + - spawn_child_boots_with_regenerated_package_manifest_when_parent_left_stale_dev_manifest + - bootstrap_app_unlinks_stale_package_manifest_when_update_flag_present + - bootstrap_app_leaves_package_manifest_untouched_without_update_flag + - PackageManifestCacheHelper_clear_unlinks_packages_and_services_at_configured_paths + - clearAllCaches_rebuilds_package_manifest_via_helper + - CoreUpdateContext_isInProgress_detects_env_flag_or_update_argv + - CoreUpdateContext_ignores_argv_outside_console_sapi + - CoreUpdateContext_env_flag_is_honored_regardless_of_sapi + - PackageManifestCacheHelper_clear_returns_paths_it_could_not_remove + - spawnUpgradeStepsProcess_logs_manifest_files_it_could_not_remove + - getCoreVersion_prefers_env_APP_VERSION_only_inside_update_tree + - getCoreVersion_ignores_env_APP_VERSION_outside_update_tree + - getCoreVersion_falls_back_to_config_when_env_absent_inside_update_tree + # #658 상주 큐 워커 재시작 신호 + - core_update_step11_signals_queue_restart test_files: - tests/Feature/Console/CoreUpdateCommandSpawnFailureTest.php @@ -125,6 +154,11 @@ test_files: - tests/Unit/Extension/Helpers/FilePermissionHelperSymlinkTest.php - tests/Unit/Services/CoreUpdateServiceStagingCleanupTest.php - tests/Feature/Console/ExecuteBundledUpdatesCommandTest.php + - tests/Feature/Console/CoreUpdateCommandStalePackageManifestTest.php + - tests/Unit/Support/PackageManifestCacheHelperTest.php + - tests/Unit/Support/CoreUpdateContextTest.php + - tests/Unit/Extension/CoreVersionCheckerEnvPriorityTest.php + - tests/Unit/Services/CoreUpdateServiceQueueRestartTest.php # 본 매니페스트의 axes cross product 는 800+ 케이스이나, 실제 회귀 가드는 test_files 의 # 통과 테스트들이 SSoT — 매니페스트는 매트릭스 SSoT 역할. diff --git a/tests/scenarios/extension-bundle-loading.yaml b/tests/scenarios/extension-bundle-loading.yaml index d922321e..56dd260f 100644 --- a/tests/scenarios/extension-bundle-loading.yaml +++ b/tests/scenarios/extension-bundle-loading.yaml @@ -30,7 +30,6 @@ axes: exclusions: - { strategy: layout, active_combo: none, reason: "layout/lazy 전략은 번들 대상 아님 — 개별 레이아웃 처리" } - - { active_combo: none, version_state: cache_hit, reason: "활성 에셋 0건이면 캐시 파일을 만들지 않음 (빈 경로)" } effects: - ordered_by_priority_ascending_only_no_name_hardcode @@ -65,6 +64,13 @@ effects: - empty_result_with_missing_declared_artifact_returns_503 - missing_artifact_paths_are_logged - bundle_css_failure_banner_uses_user_vocabulary + - prod_cache_hit_skips_build_and_source_reads + - prod_cache_miss_builds_once_under_lock_and_rechecks + - prod_build_lock_failure_falls_back_to_unlocked_build + - prod_empty_result_with_present_artifacts_is_cached_as_zero_byte_file + - prod_empty_result_with_missing_artifact_is_not_cached + - prod_build_with_skipped_extension_is_not_cached + - zero_byte_bundle_is_statically_published test_files: - tests/Unit/Services/ExtensionBundleServiceTest.php @@ -72,6 +78,7 @@ test_files: - tests/Unit/Extension/AbstractPluginDeclaredAssetsTest.php - tests/Unit/Extension/BundledManifestAssetDeclarationTest.php - tests/Feature/Api/Public/ExtensionBundleServingTest.php + - tests/Feature/Services/ExtensionStaticCacheServiceTest.php - resources/js/core/modules/__tests__/ModuleAssetLoader.test.ts - tests/Playwright/specs/extension-bundle-loading.spec.ts diff --git a/tests/scenarios/installer-composer-skip-dev-vendor.yaml b/tests/scenarios/installer-composer-skip-dev-vendor.yaml new file mode 100644 index 00000000..94f6e28b --- /dev/null +++ b/tests/scenarios/installer-composer-skip-dev-vendor.yaml @@ -0,0 +1,71 @@ +# audit:allow test-scenario-coverage [cross_product] reason: task-runner.php 는 진입과 함께 SSE 를 실행하는 파일이라 include 불가 — 조합은 순수 함수 단위 effect 로 검증하고, 분기 순서는 소스 문자열로 잠근다. + +feature: 인스톨러 vendor 재사용 경로의 개발용(require-dev) 패키지 감지·안내와 컴파일 캐시 정리 + +description: | + 인스톨러는 `vendor/autoload.php` 와 `composer.lock` 이 둘 다 있으면 composer 단계를 통째로 + 건너뛰고 그 vendor 를 그대로 쓴다. 그런데 그 vendor 가 `composer install`(옵션 없음)의 + 결과면 require-dev 와 그 전이 의존성까지 들어 있고, 그 목록이 `bootstrap/cache/packages.php` + 에 provider 로 등재된다. 종전에는 이 사실을 아무 데도 표시하지 않았다. + + 그 상태로 운영하면 이후 코어 업데이트가 vendor 를 `--no-dev` 로 교체할 때 매니페스트에만 + 남은 provider 를 찾다 spawn 자식이 부팅 단계에서 죽고, 업데이트가 「Class ... not found」 + 와 수동 재개 안내로 멈춘다 (sir.kr 제보, 7.0.9 → 7.0.10). + + 같은 재사용 분기가 컴파일 캐시도 정리하지 않고 return 했다. `key_generate` 가 이미 완료된 + 재개 설치에서는 인스톨러 어느 지점에서도 정리가 일어나지 않아, 이전 환경의 캐시가 그대로 + 남았다. + + 처치 (설계 결정 D1 — 설치를 차단하지 않는다): + 1. 설치 환경 확인(Step 2) 에 「Composer 의존성 구성」 카드를 **선택 항목**으로 추가. + 경고 상태여도 `isAllRequiredPassed()` 는 이를 보지 않고 `다음` 버튼도 활성이다. + 2. 설치 로그(Step 5) 에 감지 사실과 정리 명령 안내를 남긴다. + 3. 재사용 경로에서도 `clearLaravelCompiledCache()` 를 호출한다. + + 판정은 코어와 공유하는 `App\Support\ComposerInstallInfo` 가 단독으로 소유한다 — 인스톨러와 + 코어 업데이트가 같은 vendor 를 두고 서로 다른 답을 내놓으면 화면과 로그가 어긋난다. + "판정 불가"(installed.json 부재·JSON 오류·Composer 1 형식)는 "개발용 아님" 과 구분한다. + +axes: + vendor_state: [absent, present_no_dev, present_dev, present_without_lock] + composer_available: [yes, no] + install_resume_state: [fresh, resumed_key_generate_done] + surface: [env_check_card, install_log] + +exclusions: + - { vendor_state: absent, surface: env_check_card, reason: "vendor 가 없으면 카드를 그리지 않는다 — 마법사가 운영용 구성으로 자동 설치하므로 안내할 것이 없다" } + - { vendor_state: absent, install_resume_state: resumed_key_generate_done, reason: "vendor 가 없는 상태로 key_generate 까지 도달하는 흐름이 없다 — composer 단계가 선행한다" } + - { vendor_state: present_without_lock, surface: env_check_card, reason: "lock 부재는 재사용 분기에 들어가지 않고 vendor 삭제 후 재설치로 간다 (기존 경로, 이번 변경 대상 아님)" } + - { vendor_state: present_without_lock, install_resume_state: resumed_key_generate_done, reason: "동일 — 재사용 분기 밖" } + - { vendor_state: present_no_dev, composer_available: no, reason: "이미 준비된 vendor 를 재사용하므로 composer 실행 가능 여부가 분기를 바꾸지 않는다" } + - { vendor_state: present_dev, composer_available: no, reason: "동일 — 재사용 분기는 composer 를 실행하지 않는다" } + +effects: + # 판정 SSoT (App\Support\ComposerInstallInfo) + - core_detects_dev_vendor_from_installed_json + - core_reports_no_dev_packages_for_no_dev_vendor + - core_returns_unknown_when_installed_json_missing + # 인스톨러 shim (순수 PHP — Laravel 오토로드 없이 로드) + - installer_detects_dev_vendor_from_installed_json + - installer_treats_missing_installed_json_as_unknown + - installer_shim_matches_core_composer_install_info + # 재사용 분기 — 경고하되 설치는 계속하고, 캐시를 정리한다 + - installer_warns_and_keeps_dev_vendor + - installer_clears_compiled_cache_on_reuse_path + # 설치 환경 확인 카드 — 선택 항목이며 게이트로 승격되지 않는다 + - installer_env_check_shows_dev_vendor_card_without_gating + # 다국어 — ko/en 쌍과 치환 자리 + - installer_lang_keys_exist_in_both_locales + + # 실행되는 명령만이 아니라 실패 시 안내하는 명령까지 --no-dev 를 갖는다 + - installer_composer_commands_all_carry_no_dev + +test_files: + - tests/Feature/Installer/DevVendorDetectionTest.php + - tests/Unit/Support/ComposerInstallInfoTest.php + - tests/Unit/Installer/RequirementsVendorDevPackagesTest.php + +manual_verification: | + 브라우저 실측 매트릭스는 구현 계획서 §7.3 (T1 환경 확인 카드 / T3 설치 로그 / T10(a) 재개 설치 + 캐시 정리 / T10(b) vendor 없는 신규 설치) 를 따른다. 인스톨러는 SSE 스트림으로 진행하므로 + 로그 문구·카드 배지는 실제 브라우저에서만 최종 확인된다. diff --git a/tests/scenarios/layout-attachment-url-mode.yaml b/tests/scenarios/layout-attachment-url-mode.yaml new file mode 100644 index 00000000..094ba840 --- /dev/null +++ b/tests/scenarios/layout-attachment-url-mode.yaml @@ -0,0 +1,61 @@ +feature: 레이아웃 첨부 URL 발급 모드 (공개 #134) + +description: | + 레이아웃 편집기에서 올린 배경 이미지의 주소가 항상 공개 서빙 라우트(프록시)로 + 발급되어, 공개 자산 디스크(S3+CDN 등)를 설정해도 방문자 요청이 매번 오리진 PHP 를 + 거치던 결함을 고친다. + + 결정 사슬: + - 업로드 저장 위치: PublicAssetDisk::resolve() ?? config('attachment.disk'). + 행이 자기 disk 를 기록하므로 이후 설정 변경과 무관하게 각 행은 자기 위치를 기억한다. + - URL 해석: 행 disk 가 **지금** 설정된 공개 자산 디스크와 일치할 때만 직접 URL 시도, + 그 외에는 프록시. `filesystems.disks.{disk}.url` 존재 여부는 판정 근거가 아니다 + (비공개 버킷 + 공개 URL 설정 조합에서 직접 URL 은 403). + - 훅이 URL 을 차단하면(url() null) 프록시로 폴백한다. + - 고아 disk 행(디스크를 제공하던 플러그인 비활성화)은 withDisk 없이 주입 스토리지로 + 서빙·삭제 — 무인증 공개 라우트가 500 이 되지 않고 404 로 끝난다. + - 게시판·페이지 첨부는 권한 게이트가 걸려 있어 직접 URL 배선 대상이 아니다. + 게시판 업로드 응답의 url 칸은 게이트가 살아 있는 서빙 URL 로 채운다. + +coverage_strategy: pairwise + +axes: + public_asset_disk: [unset, public, s3_private, ghost_disk] + row_disk: [attachments, s3, public] + filter_url_hook: [absent, blocks] + +exclusions: + - { public_asset_disk: unset, row_disk: public, reason: "공개 디스크 미설정이면 public 행이 새로 생기지 않는다" } + - { public_asset_disk: ghost_disk, row_disk: public, reason: "고아 디스크는 resolve 단계에서 null — row_disk 축 도달 전 차단" } + - { public_asset_disk: ghost_disk, row_disk: s3, reason: "동일 — resolve 가 null 이라 행 disk 와 무관하게 프록시" } + - { public_asset_disk: s3_private, row_disk: attachments, reason: "비공개 s3 는 판정 근거가 아니므로 unset 축과 결과가 동일" } + - { filter_url_hook: blocks, public_asset_disk: unset, reason: "직접 URL 시도 자체가 없어 훅이 발화하지 않는다" } + - { filter_url_hook: blocks, public_asset_disk: ghost_disk, reason: "동일 — 게이트에서 차단되어 url() 미호출" } + +effects: + - direct_url_when_row_matches_public_disk + - proxy_url_otherwise + - upload_stores_on_public_disk + - orphan_row_disk_serves_without_exception + - board_upload_response_url_uses_gated_route + - editor_thumbnail_renders_cross_origin + # 저장까지 왕복해 방문자 화면이 실제로 요청하는 주소를 본다 (E2E 시드 화면 대상) + - saved_layout_url_is_what_visitor_requests + +test_files: + - tests/Unit/Support/PublicAssetDiskTest.php + - tests/Unit/Services/TemplateLayoutAttachmentUrlResolutionTest.php + - tests/Unit/Services/TemplateLayoutAttachmentServingTest.php + - tests/Unit/Extension/Storage/StorageCategoryDiskTest.php + - tests/Feature/Api/Admin/TemplateLayoutAttachmentControllerTest.php + - modules/_bundled/sirsoft-board/tests/Unit/AttachmentServiceTest.php + - resources/js/core/template-engine/layout-editor/__tests__/utils/layoutAttachments.test.ts + - resources/js/core/template-engine/layout-editor/__tests__/components/dimension-and-attachments.test.tsx + - resources/js/core/template-engine/layout-editor/__tests__/components/property-controls.test.tsx + - tests/Playwright/specs/layout-editor/layout-attachment-url-mode.spec.ts + +rules_layer_coverage: + - rule: no-storage-disk-direct + coverage: 모든 접근이 StorageInterface 경유 — Storage facade 직접 호출 도입 없음 + - rule: repository-interface-injection + coverage: 서비스 시그니처 무변경 — 해석 규칙만 교체 diff --git a/tests/scenarios/layout-attachment-url-storage-gate.yaml b/tests/scenarios/layout-attachment-url-storage-gate.yaml new file mode 100644 index 00000000..10ab0390 --- /dev/null +++ b/tests/scenarios/layout-attachment-url-storage-gate.yaml @@ -0,0 +1,43 @@ +feature: 레이아웃 첨부 URL 저장 게이트 통과 (서버가 발급한 주소는 외부가 아니다) + +description: | + 레이아웃 편집기의 image 위젯으로 파일을 올리면 첨부 API 가 돌려준 url 이 props 에 들어간다. + 그 url 이 절대 형태(`https://<사이트>/api/templates/.../file`)라 저장 요청의 외부 URL + 차단 규칙(NoExternalUrls)이 서버 자신이 발급한 주소를 외부로 차단해, 헤더 「로고 이미지」 + 처럼 값 슬롯이 하나뿐인 컨트롤(propValue)에서 업로드는 되고 저장은 422 로 끝났다. 배경 + 이미지는 style 로 들어가 스캔되지 않아 같은 주소가 통과했으므로 증상이 로고에서만 났다. + + 결정 사슬: + - 생산자: 프록시(공개 서빙 라우트) URL 은 사이트 상대 경로(`/api/...`)로 발급한다. 절대 URL 은 + 저장 게이트 충돌뿐 아니라 저장된 레이아웃을 발급 시점 도메인·스킴에 묶는다. + - 게이트: 사이트 자기 host(app.url)와 운영자가 선언한 공개 자산 디스크(PublicAssetDisk:: + resolve())의 host 는 외부가 아니다 (SiteAssetHosts 단일 판정). 요청 Host 헤더는 위조 + 가능하므로 근거로 쓰지 않는다. + - 판정은 접두 문자열 비교가 아니라 브라우저와 같은 정규화 뒤 host 등가 비교 — 흉내 host + (`host.evil.com`·`host@evil.com`·`evil.com\@host`)를 통과시키지 않는다. + - protocol-relative(`//host/...`)와 http/https 밖의 스킴은 host 가 같아도 종전대로 차단한다. + +coverage_strategy: full + +axes: + url_host: [site_relative_path, own_site_absolute, public_asset_disk, external_undeclared, lookalike, dangerous_scheme] + +effects: + - proxy_url_is_site_relative + - issued_asset_url_passes_storage_gate + - external_host_still_rejected + # 편집기 로고 컨트롤(propValue)이 파일 업로드 후 받는 값이 사이트 상대 경로다 (E2E) + - editor_logo_control_receives_site_relative_url + # 발급 url 을 prop 에 넣어 저장한 화면을 방문자가 열면 그 주소가 200 으로 응답한다 (E2E) + - saved_logo_renders_for_visitor + +test_files: + - tests/Feature/Rules/NoExternalUrlsTest.php + - tests/Unit/Support/SiteAssetHostsTest.php + - tests/Unit/Services/TemplateLayoutAttachmentUrlResolutionTest.php + - tests/Feature/Api/Admin/TemplateLayoutAttachmentControllerTest.php + - tests/Playwright/specs/layout-editor/layout-attachment-url-mode.spec.ts + +rules_layer_coverage: + - rule: no-storage-disk-direct + coverage: 공개 자산 디스크 host 해석은 CoreStorageDriver::url() 경유 — Storage facade 직접 호출 도입 없음 diff --git a/tests/scenarios/layout-editor-image-widget-value-shape.yaml b/tests/scenarios/layout-editor-image-widget-value-shape.yaml new file mode 100644 index 00000000..7d663abf --- /dev/null +++ b/tests/scenarios/layout-editor-image-widget-value-shape.yaml @@ -0,0 +1,127 @@ +feature: 레이아웃 편집기 image 위젯 값 형태 — 단일 값 슬롯 축약 / 런타임 방어 / 저장 데이터 백필 + +# 입력 axis 는 5축 265조합이다. 전수 마킹은 실효가 없어(테스트 1건이 조합 1개만 +# 덮으므로 265건이 필요하다) **pairwise** 로 좁힌다 — 어떤 두 축의 조합도 최소 한 번은 +# 지나가므로 축 간 상호작용 결함은 그대로 잡히고, 마킹 대상은 실측 9건으로 줄어든다. +# effects 축은 면제하지 않는다 — 모든 effect 가 테스트 마킹으로 도달해야 한다. +coverage_strategy: pairwise + +description: | + 코어 `image` 위젯은 배경 이미지용으로 설계되어 `{url,size,repeat,position}` **객체**를 + 내보낸다. 그 값을 노드에 기록하는 apply 4종 중 `styleProp` 의 다중 props(배경 묶음)만 + 객체를 CSS 4속성으로 분해했고, `propValue`·`cssVar`·단일 `styleProp` 은 객체를 그대로 + 기록했다. 소비 컴포넌트가 `` 로 받으면 브라우저가 `[object Object]` 를 + URL 로 해석해 이미지가 깨진다(공개 #135, `sirsoft-basic` 헤더 「로고 이미지」). + + 이 결함은 예외도 콘솔 오류도 서버 로그도 남기지 않는다 — 깨진 이미지 요청은 SPA + catch-all 때문에 404 조차 아니라 200(HTML)을 받고, 편집기 위젯의 미리보기는 정상이라 + 조작 중에는 이상이 보이지 않는다. 유일한 증상은 화면의 엑박이다. + + 방어선은 넷이다: + 1. **쓰기 축약** — 단일 값 슬롯 writer 3종이 공용 헬퍼 `scalarizeImageValue` 를 경유해 + url 문자열만 남긴다. 게이트는 `widget === 'image'` 이며 값 형태 sniffing 이 아니다 + (`isImageValueObject` 는 4키 중 하나만 있어도 참이라, 값만 보면 `{position:'left'}` + 같은 정당한 객체 prop 을 이미지로 오인해 삭제한다). + 2. **읽기 역조립** — 저장된 문자열을 `{url}` 로 되감아 위젯이 현재 값을 읽게 한다. + 표현식 문자열도 감싼다 — 감싸지 않으면 빈 피커로 보이고 업로드 1클릭에 그 표현식이 + 소리 없이 소실된다. 깨진 미리보기 억제와 실수 덮어쓰기 방지는 위젯이 맡는다. + 3. **런타임 방어** — 업그레이드 전 화면을 위해 템플릿 `Img` 가 객체값에서 url 을 + 해석하고, 손상값이면 `src` 를 아예 붙이지 않는다(엑박도 빈 문자열도 아니다). + 4. **저장 데이터 백필** — 업그레이드 스텝이 기설치본의 prop 자리 객체를 축약한다. + 판정식은 런타임 방어와 **완전히 같은 엄격도**(키 집합 ⊆ 4키 AND url 문자열)를 쓴다 — + 두 방어선의 기준이 어긋나면 한쪽만 통과하는 값이 생긴다. + + 단일 값 슬롯 컨트롤에서는 표시모드(채움/맞춤/타일) 버튼을 **컨테이너째 미렌더**한다. + `size`/`repeat`/`position` 을 저장할 자리가 없어 눌러도 저장되지 않는 죽은 컨트롤이기 + 때문이다. `disabled` 로 두면 "URL 을 넣으면 살아나겠지" 라는 거짓 정보를 준다. + +axes: + widget_output: + - scalar_string # 이미 문자열 (회귀 0) + - image_object # {url,size,repeat,position} 전체 + - image_object_empty_url # url 이 빈 문자열 + - image_object_nonstring_url # url 이 객체/배열/숫자 + - undefined_cleared # 「제거」 → undefined + apply_type: [propValue, styleProp, cssVar, classToken] + stored_shape: [string, object, binding_expression, absent] + consumer: [img_src, header_logo, mobilenav_logo, div_background, none] + storage_scope: + - template_layouts + - template_layout_extensions + - layout_versions_untouched + - layout_previews_untouched + +exclusions: + - { apply_type: styleProp, consumer: [img_src, header_logo, mobilenav_logo], reason: "styleProp 은 인라인 style 로 흐른다 — sink 에 도달하지 않는다" } + - { apply_type: propValue, consumer: div_background, reason: "배경은 style 자리 — propValue 로 흐르지 않는다" } + - { apply_type: cssVar, consumer: [img_src, header_logo, mobilenav_logo, div_background], reason: "CSS 변수는 소비 sink 가 선언되지 않았다 — 축약만 검증(정적 룰이 신규 선언을 차단)" } + - { apply_type: classToken, widget_output: [image_object, image_object_empty_url, image_object_nonstring_url], reason: "image × classToken 은 정적 룰이 저장 이전에 차단해 런타임 도달 불가" } + - { widget_output: undefined_cleared, consumer: [img_src, div_background], reason: "해제는 폴백 축 — header_logo 에서 대표 검증(사이트명 텍스트 폴백)" } + - { widget_output: undefined_cleared, stored_shape: [object, string, binding_expression], reason: "해제 결과는 absent 하나뿐" } + - { stored_shape: binding_expression, widget_output: [image_object, image_object_empty_url, image_object_nonstring_url], reason: "표현식은 문자열이라 객체 형태와 배타" } + - { storage_scope: [layout_versions_untouched, layout_previews_untouched], apply_type: [styleProp, cssVar, classToken], reason: "스냅샷 미보존 단언은 apply 무관 단일 축 — propValue 에서 대표 검증" } + - { storage_scope: template_layout_extensions, consumer: [mobilenav_logo, div_background, none], reason: "확장 주입 경로는 injections[].props 순회 포섭만 검증 — 소비 sink 는 코어 경로와 동일" } + +effects: + # ① 쓰기 축약 (코어 엔진) + - image_object_narrows_to_url_string_in_propvalue_slot + - image_object_narrows_to_wrapped_css_url_in_cssvar_and_single_styleprop + - legacy_css_url_wrapping_is_stripped_for_component_prop_sink + - image_object_without_url_or_with_empty_url_deletes_the_prop + - non_image_widget_object_and_array_props_are_preserved_intact + - styleprop_bundle_four_property_decomposition_is_unchanged + - apply_fixed_value_wins_over_control_value_then_gets_narrowed + # ② 읽기 역조립 + - stored_string_is_rewrapped_as_url_object_for_the_widget + - binding_expression_is_rewrapped_so_one_click_upload_cannot_silently_lose_it + - legacy_object_stored_value_is_returned_losslessly + - non_image_widget_values_are_not_rewrapped + - node_axis_roundtrip_is_a_fixed_point_after_first_pass + - dark_scope_stays_readonly_for_inline_apply + # ③ 위젯 표면 + - single_value_slot_control_hides_display_mode_buttons_entirely + - single_value_slot_control_emits_url_only_object_without_size_repeat_position + - single_value_slot_preview_is_pinned_to_contain_not_a_false_preview + - bundle_control_keeps_display_mode_buttons_and_active_state + - apply_undeclared_control_keeps_display_mode_buttons_blacklist_not_whitelist + - bound_value_shows_expression_badge_and_locks_destructive_controls + - replace_affordance_reopens_editing_without_emitting_a_change + - bound_value_protection_is_a_single_shared_gate_for_every_widget + - restore_affordance_recovers_the_original_binding_after_replacement + # ④ 런타임 방어 (템플릿 Img) + - img_string_src_passes_through_unchanged + - img_image_value_object_resolves_to_its_url + - img_corrupted_value_attaches_no_src_neither_object_object_nor_empty_string + - img_object_with_key_outside_the_four_is_rejected_same_strictness_as_backfill + - img_other_attributes_pass_through_spread_regression_zero + # ⑤ 백필 (업그레이드 스텝) + - backfill_narrows_prop_slot_objects_in_layouts_and_extensions + - backfill_strict_predicate_leaves_normal_props_untouched + - backfill_excludes_style_slot_where_background_decomposition_lives + - backfill_traverses_responsive_branches_nested_nodes_and_injections + - backfill_traverses_modals_in_both_list_and_map_shape + - backfill_drops_prop_key_on_corrupted_url_with_warning_log + - backfill_is_idempotent_and_performs_no_update_on_unchanged_rows + - backfill_increments_lock_version_only_on_changed_rows + - backfill_preserves_original_content_hash_so_modified_judgement_holds + - backfill_leaves_version_and_preview_snapshots_untouched + - backfill_converts_soft_deleted_rows_for_restore_safety + - backfill_uses_keyset_chunking_so_no_row_is_skipped_past_the_boundary + - backfill_skips_broken_json_rows_without_aborting_the_run + +test_files: + - resources/js/core/template-engine/layout-editor/__tests__/spec/recipeEngine.test.ts + - resources/js/core/template-engine/layout-editor/__tests__/components/property-controls.test.tsx + - resources/js/core/template-engine/layout-editor/__tests__/components/ControlRenderer.boundValueGuard.test.tsx + - templates/_bundled/sirsoft-basic/src/components/basic/__tests__/Img.test.tsx + - tests/Feature/Upgrades/Upgrade7011ImageObjectPropBackfillTest.php + - tests/Playwright/specs/layout-editor/image-widget-value-shape.spec.ts + +validation: + note: | + 백필 축(⑤)은 DB 데이터 상태라 브라우저 E2E 로 재현할 수 없다. PHPUnit 26케이스가 + 판정식 엄격도·순회 범위·멱등·lock_version·hash 불변·스냅샷 미개입을 전수 잠근다. + + `image` 위젯을 값이 스칼라로 축약되지 않는 apply 경로(classToken/cssVar/apply 미선언)에 + 새로 연결하는 것은 정적 검사가 저장 이전에 차단한다 — 그래서 그 조합은 런타임에 도달 + 불가이며 exclusions 에 명시했다. diff --git a/tests/scenarios/layout-editor-version-preview.yaml b/tests/scenarios/layout-editor-version-preview.yaml index c43f4317..039b98ca 100644 --- a/tests/scenarios/layout-editor-version-preview.yaml +++ b/tests/scenarios/layout-editor-version-preview.yaml @@ -54,7 +54,9 @@ effects: - stable_stringify_sorts_keys_preserves_array_order - changes_summary_uses_line_lcs_unit_matching_diff_view - changes_summary_recalculated_for_existing_versions + - changes_summary_memory_bounded_for_full_span_change test_files: + - tests/Unit/Repositories/Concerns/CalculatesJsonContentDiffTest.php - resources/js/core/template-engine/layout-editor/__tests__/components/VersionHistoryModal.test.tsx - resources/js/core/template-engine/layout-editor/__tests__/components/EditorToolbar.versionPreview.test.tsx - resources/js/core/template-engine/layout-editor/__tests__/components/VersionDiffView.test.tsx diff --git a/tests/scenarios/seo-bot-cache-bounds.yaml b/tests/scenarios/seo-bot-cache-bounds.yaml new file mode 100644 index 00000000..f42f6896 --- /dev/null +++ b/tests/scenarios/seo-bot-cache-bounds.yaml @@ -0,0 +1,56 @@ +# audit:allow test-scenario-coverage[cross_product] reason: 입력 axis 의 완전 cross-product(5축)는 조합 폭발이라 대표 조합만 마킹한다. effects 축은 면제하지 않는다 — 모든 effect 가 테스트 마킹으로 도달해야 한다. + +feature: SEO 봇 캐시 상한 (키 정규화 · 렌더 예산 · 저장 규모 · 통계 기록) + +description: | + 봇 판정은 User-Agent 문자열뿐이라 누구나 위장할 수 있는데, 캐시 키가 경로 + 전체 쿼리였다. + 물음표 뒤 값만 바꾸면 매 요청이 미스가 되고, 미스 1건은 레이아웃 병합 · 표현식 평가 · + 자기 API 루프백 HTTP 호출을 유발하며 그 결과가 무제한으로 저장됐다. 요청 하나가 워커 + 여러 개를 묶고 캐시 저장소를 계속 키우는 통로였고, 통계 기록 호출처가 없어 그 진행이 + 운영자 화면에 전혀 나타나지 않았다. + + 핵심 동작: + - SeoCacheBounds 가 상한 판정의 단일 출처 (config/core.php seo_cache_limits + env) + - 키 정규화: locale · _escaped_fragment_ 제거 + ksort, 개수·길이 상한 초과 시 캐시 불가 + - 렌더 예산: IP 당 분당 미스 렌더 상한, 초과분은 SPA + X-SEO-Cache: BYPASS (429 아님) + - 저장 규모: 경로당 변종 수 · 전체 항목 수 상한, 기존 키 갱신은 상한과 무관 + - 통계: 미들웨어가 HIT/MISS 를 기록하되 IP 당 분당 상한 안에서만 + - 캐시 항목은 HTML + 레이아웃명 — HIT 통계가 화면별로 귀속된다 (이전 문자열 항목 호환) + - 경로당 변종 상한은 언어별로 센다 (인덱스 항목이 url|locale 별이므로) + - 렌더러가 null 을 돌려준 요청은 렌더 예산을 되돌린다 (예외는 되돌리지 않는다) + - 통계 url 컬럼(768)은 캐시 키 URL 상한을 담는다 — 마이그레이션 왕복 안전 + +axes: + bot_state: [bot, browser] + cache_state: [hit, miss] + ip_budget: [within, exhausted] + query_shape: [none, system_only, normal, oversized] + store_state: [under_caps, path_cap, global_cap] + +exclusions: + - { bot_state: browser, cache_state: hit, reason: "비봇 요청은 SEO 파이프라인에 들어가지 않아 캐시를 보지 않는다" } + - { cache_state: hit, store_state: path_cap, reason: "저장 상한은 미스 경로에서만 판정된다" } + +effects: + - bot_miss_over_limit_gets_spa_bypass + - bot_hit_served_regardless_of_limit + - system_query_params_excluded_from_key + - oversized_query_bypasses_cache_and_render + - cache_hit_and_miss_are_recorded_in_stats + - stats_recording_capped_per_ip + - store_skips_write_at_path_variant_cap + - store_skips_write_at_global_cap + - expired_index_entries_are_pruned_before_cap_verdict + - put_and_put_with_layout_share_the_storage_cap + - index_prune_is_throttled_to_one_scan_per_interval + - cache_hit_carries_layout_name_from_cache_entry + - store_counts_path_variants_per_locale + - null_render_refunds_render_budget + - stats_url_column_fits_normalized_cache_url + +test_files: + - tests/Unit/Seo/SeoCacheBoundsTest.php + - tests/Unit/Seo/SeoCacheManagerTest.php + - tests/Feature/Seo/SeoMiddlewareTest.php + - tests/Unit/Seo/SeoCacheStatsServiceTest.php + - tests/Feature/Database/ModifyUrlInSeoCacheStatsMigrationTest.php diff --git a/upgrades/Upgrade_7_0_11.php b/upgrades/Upgrade_7_0_11.php new file mode 100644 index 00000000..dc5c2351 --- /dev/null +++ b/upgrades/Upgrade_7_0_11.php @@ -0,0 +1,37 @@ +` 로 받아 `[object Object]` 를 URL 로 + * 해석하던 결함(공개 #135)의 **기존 데이터 보정**이다. 편집기 쪽 소스 교정만으로는 + * 이미 저장된 설치본이 낫지 않는다. + * + * 실행 순서는 파일명 정렬(`sort()`)을 따른다. + * + * 본 클래스는 `AbstractUpgradeStep` 의 default `run()` 에 위임 — 별도 override 없음 + * (`run()` 은 final 이라 override 자체가 불가능하다). + * + * @upgrade-path 7.0.x → 7.0.11 + * + * 의존성 제약: 본 스텝은 변환/핫픽스를 `data/7.0.11/migrations/` 의 버전 namespace + * 클래스에 위임한다. 미래 버전에서 *그 디렉토리는 동결* (수정 금지) 되어 "각 스텝별 동작 + * 100% 동일 보장" invariant 가 성립. + * + * 상세: docs/extension/upgrade-step-guide.md §13 "버전별 데이터 스냅샷" + */ +class Upgrade_7_0_11 extends AbstractUpgradeStep +{ + // 모든 로직 위임 — data/7.0.11/ 가 SSoT. +} diff --git a/upgrades/data/7.0.11/migrations/01_NarrowImageObjectPropsInLayouts.php b/upgrades/data/7.0.11/migrations/01_NarrowImageObjectPropsInLayouts.php new file mode 100644 index 00000000..02c5bd50 --- /dev/null +++ b/upgrades/data/7.0.11/migrations/01_NarrowImageObjectPropsInLayouts.php @@ -0,0 +1,360 @@ +` 로 받으면 브라우저가 + * `[object Object]` 를 URL 로 해석해 이미지가 깨진다(공개 #135). + * + * 이 결함은 예외도 콘솔 오류도 서버 로그도 남기지 않는다 — 깨진 이미지 요청은 SPA + * catch-all 때문에 404 조차 아니라 200(HTML)을 받고, 편집기 위젯의 미리보기는 정상이라 + * 조작 중에는 이상이 보이지 않는다. 소스 교정만으로는 이미 저장된 설치본이 낫지 않으므로 + * 여기서 보정한다. + * + * ── 판정식 엄격도 (이 마이그레이션에서 가장 중요한 사실) ───────────────────────── + * + * 코어 편집기 엔진의 `isImageValueObject` 는 4키 중 **하나라도** 있으면 참이다. 그것을 + * 여기에 이식하면 저장된 레이아웃이 대량 파괴된다 — 레이아웃 JSON 전수 실측에서 느슨한 + * 판정식은 2,219건을 매치했고 그중 정상 props 가 대부분이었다(`{className,name,size}` + * 674건 · `{name,size}` 552건 · `{items,position}` 26건). 엄격 판정식(키 집합 ⊆ 4키 + * **AND** `url` 키 존재)의 매치는 0건이었다. + * + * 두 판정식이 다른 것은 실수가 아니라 의도다 — 엔진은 "방금 위젯이 만든 값"을 보므로 + * 느슨해도 안전하고, 백필은 "임의 사용자 레이아웃 전수"를 보므로 정밀도 요구가 다르다. + * + * ── 순회 범위 ──────────────────────────────────────────────────────────────── + * + * "prop 자리" 는 키 이름 allowlist 로 정의할 수 없다 — 실측상 `props` 안에 컴포넌트 노드가 + * 1,150건 산다(`props.cardColumns[0].cellChildren[0]`, `props.itemTemplate` …). 임의의 + * prop 키가 노드를 품을 수 있으므로 **모드 플래그 전역 재귀**를 쓴다: `props` 키를 만나면 + * 모드 ON, `style` 키를 만나면 OFF(배경 4속성 분해값은 정상), 그 외는 현재 모드 승계. + * 이 한 규칙으로 `node.props.*` · `responsive.{bp}.props.*` · 중첩 노드의 props · + * 확장 content 의 `injections[].props` 가 전부 포섭되고, `children`/`slots`/`modals` + * 등은 그냥 통과 지점이라 열거할 필요가 없다. + * + * ── 안전성 ─────────────────────────────────────────────────────────────────── + * + * - **멱등**: 이미 문자열인 값은 `is_array` 에서 탈락하므로 2회차 변경 건수는 구조적으로 0. + * - **무변경 행 UPDATE 금지**: 변경 카운터로 판정한다. 재직렬화 결과 비교로 판정하면 + * 키 순서·escape 차이로 전 행을 오탐 UPDATE 시킨다. + * - **`chunkById`**: 콜백이 `content` 를 update 하므로 OFFSET 방식은 커서가 밀려 + * 미처리 행을 조용히 건너뛴다. + * - **사전 필터 없음**: `content LIKE '%"repeat"%'` 는 인덱스를 못 타 어차피 풀스캔이고, + * escape·공백 변형에 따라 오탐이 아니라 **누락**을 만든다 — 누락은 흔적이 없다. + * - **soft delete 행 포함**: 삭제된 레이아웃은 복원 가능하므로 고치지 않으면 복원 시 + * 결함이 되살아난다. `DB::table()` 은 SoftDeletes 스코프를 타지 않아 기본 동작이 곧 + * 원하는 동작이다. + * - **`lock_version` 은 변경된 행만 +1**: 업데이트 직전에 편집기 탭을 열어 둔 운영자가 + * 업데이트 후 저장하면, 올리지 않은 경우 그의 stale content(= 객체값 그대로)가 조용히 + * 백필을 되돌린다. 올리면 "다른 곳에서 수정됨" 으로 정확히 거부된다. + * - **`updated_by` 미변경**: 시스템 보정이지 사람의 편집이 아니다. + * - **`original_content_hash` 미변경**: 재계산하면 사용자 편집본이 "원본 그대로" 로 + * 위장되어 다음 `template:update --preserve-modified` 가 그 레이아웃을 덮어쓴다. + * - **버전·미리보기 스냅샷 미개입**: `template_layout_versions` · + * `template_layout_previews` 는 대상이 아니다. 과거 스냅샷을 고치면 「이전 버전으로 + * 되돌리기」가 원본과 달라진다. 그리고 그 미개입이 곧 본 마이그레이션의 안전망이다 — + * 되돌리기는 스텝 규격에 없으므로(`DataMigration` 은 `name()`+`run()` 뿐) 운영자의 + * 개별 복구 경로는 관리자 UI 의 레이아웃 이력이다. + * + * V-1 안전: `Illuminate\Support\Facades\{DB,Schema}` + 로컬 private 헬퍼만 사용한다 + * (Service/Manager/Repository 컨테이너 해석 금지 — 그 클래스들은 스텝 실행 시점에 이전 + * 버전 표면일 수 있다). + */ +class NarrowImageObjectPropsInLayouts implements DataMigration +{ + /** 이미지 값 객체가 가질 수 있는 키의 전부 */ + private const IMAGE_VALUE_KEYS = ['url', 'size', 'repeat', 'position']; + + /** + * 모드 전이 키 → 그 아래의 prop 자리 여부. + * + * 이 목록에 있는 키는 **컨테이너지 값이 아니다** — 두 사실(모드를 어떻게 바꾸는가, + * 값 판정에서 제외해야 하는가)이 같은 목록에서 나와야 한다. 따로 적어 두면 나중에 + * 전이 키가 하나 늘 때 한쪽만 갱신되어, 그 컨테이너가 값으로 평가된다. + */ + private const CONTAINER_MODE = ['props' => true, 'style' => false]; + + /** 대상 테이블 — 버전/미리보기 스냅샷은 의도적으로 제외한다 */ + private const TARGET_TABLES = ['template_layouts', 'template_layout_extensions']; + + /** 한 청크의 행 수 */ + private const CHUNK_SIZE = 100; + + /** 손상 url 로 prop 키를 제거한 건수(로그용 누적) */ + private int $droppedCount = 0; + + /** + * 마이그레이션 식별자를 반환합니다. + * + * @return string 마이그레이션 이름 + */ + public function name(): string + { + return 'NarrowImageObjectPropsInLayouts'; + } + + /** + * 대상 테이블의 `content` JSON 을 순회하며 prop 자리의 이미지 값 객체를 축약합니다. + * + * @param UpgradeContext $context 업그레이드 컨텍스트 + * @return void + */ + public function run(UpgradeContext $context): void + { + foreach (self::TARGET_TABLES as $table) { + $this->processTable($context, $table); + } + } + + /** + * 한 테이블을 청크 단위로 순회하며 보정합니다. + * + * @param UpgradeContext $context 업그레이드 컨텍스트 + * @param string $table 대상 테이블명 + * @return void + */ + private function processTable(UpgradeContext $context, string $table): void + { + if (! Schema::hasTable($table)) { + $context->logger->info("[core:7.0.11] {$table} 테이블 부재 — 이미지 값 객체 축약 스킵"); + + return; + } + + if (! Schema::hasColumn($table, 'content')) { + $context->logger->info("[core:7.0.11] {$table}.content 컬럼 부재 — 이미지 값 객체 축약 스킵"); + + return; + } + + $hasLockVersion = Schema::hasColumn($table, 'lock_version'); + $updatedRows = 0; + $changedProps = 0; + $failedRows = 0; + $this->droppedCount = 0; + + DB::table($table) + ->select(['id', 'content']) + ->orderBy('id') + ->chunkById(self::CHUNK_SIZE, function ($rows) use ( + $context, + $table, + $hasLockVersion, + &$updatedRows, + &$changedProps, + &$failedRows + ) { + foreach ($rows as $row) { + try { + $changed = $this->processRow($context, $table, $row, $hasLockVersion); + if ($changed > 0) { + $updatedRows++; + $changedProps += $changed; + } + } catch (Throwable $e) { + // 행 단위 격리 — 예외를 전파하면 CoreUpdateCommand 가 백업 복원(롤백)을 + // 트리거한다. 손상 레이아웃 1건 때문에 코어 업데이트 전체를 되돌리는 것은 + // 비례하지 않는다. + $failedRows++; + $context->logger->warning( + "[core:7.0.11] {$table} id={$row->id} 이미지 값 객체 축약 실패(건너뜀) — ".$e->getMessage() + ); + } + } + }); + + if ($failedRows > 0) { + $context->logger->warning( + "[core:7.0.11] {$table} 처리 실패 행 {$failedRows}건 — 위 경고의 id 를 확인하세요" + ); + } + + if ($updatedRows === 0) { + $context->logger->info("[core:7.0.11] {$table} 보정 대상 없음 — 이미 정상"); + + return; + } + + $context->logger->info( + "[core:7.0.11] {$table} 이미지 값 객체 축약 완료 — 행 {$updatedRows}건 / prop {$changedProps}건" + .($this->droppedCount > 0 ? " / 손상 url 로 제거한 prop {$this->droppedCount}건" : '') + ); + } + + /** + * 한 행의 content JSON 을 보정하고, 변경이 있을 때만 UPDATE 합니다. + * + * @param UpgradeContext $context 업그레이드 컨텍스트 + * @param string $table 대상 테이블명 + * @param object $row `id` + `content` 를 가진 행 + * @param bool $hasLockVersion lock_version 컬럼 보유 여부 + * @return int 변경된 prop 개수 (0이면 UPDATE 미수행) + */ + private function processRow(UpgradeContext $context, string $table, object $row, bool $hasLockVersion): int + { + if (! is_string($row->content) || $row->content === '') { + return 0; + } + + $decoded = json_decode($row->content, true); + if (! is_array($decoded)) { + $context->logger->warning( + "[core:7.0.11] {$table} id={$row->id} content JSON 파싱 실패(건너뜀)" + ); + + return 0; + } + + $changed = 0; + $next = $this->transform($decoded, false, $changed, $context, $table, (string) $row->id, ''); + + if ($changed === 0) { + // 무변경 행은 UPDATE 하지 않는다 — updated_at·lock_version 을 건드리면 + // 「수정됨」 판정과 낙관적 잠금이 이유 없이 흔들린다. + return 0; + } + + $encoded = json_encode($next, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); + if (! is_string($encoded)) { + $context->logger->warning( + "[core:7.0.11] {$table} id={$row->id} content 재직렬화 실패(건너뜀)" + ); + + return 0; + } + + $update = ['content' => $encoded]; + if ($hasLockVersion) { + // 편집기 탭을 열어 둔 운영자의 stale 저장이 백필을 되돌리지 않도록 올린다. + $update['lock_version'] = DB::raw('lock_version + 1'); + } + + DB::table($table)->where('id', $row->id)->update($update); + + return $changed; + } + + /** + * content 트리를 재귀 순회하며 prop 자리의 이미지 값 객체를 축약합니다. + * + * `props` 키 아래로 들어가면 모드 ON, `style` 키 아래로 들어가면 OFF, 그 외는 현재 + * 모드를 승계한다. 이 한 규칙이 임의 깊이의 중첩 노드까지 포섭한다. + * + * @param mixed $value 현재 노드(맵/리스트/스칼라) + * @param bool $inPropSlot 현재 위치가 prop 자리인지 + * @param int $changed 변경 카운터 (참조 누적) + * @param UpgradeContext $context 업그레이드 컨텍스트 + * @param string $table 대상 테이블명 (로그용) + * @param string $rowId 행 id (로그용) + * @param string $path JSON 경로 (로그용) + * @return mixed 보정된 값 + */ + private function transform( + mixed $value, + bool $inPropSlot, + int &$changed, + UpgradeContext $context, + string $table, + string $rowId, + string $path + ): mixed { + if (! is_array($value)) { + return $value; + } + + // 리스트 — 모드를 그대로 승계하며 각 원소를 내려간다. + if (array_is_list($value)) { + foreach ($value as $i => $item) { + $value[$i] = $this->transform( + $item, $inPropSlot, $changed, $context, $table, $rowId, $path.'['.$i.']' + ); + } + + return $value; + } + + foreach ($value as $key => $child) { + $childPath = $path === '' ? (string) $key : $path.'.'.$key; + + // 모드 전이 — `props` 는 prop 자리 진입, `style` 은 CSS 자리(배경 4속성 분해값이 + // 정상적으로 사는 곳)라 이탈. 그 외 키는 현재 모드를 그대로 승계한다. + $isContainerKey = array_key_exists($key, self::CONTAINER_MODE); + $childMode = $isContainerKey ? self::CONTAINER_MODE[$key] : $inPropSlot; + + // 값 판정은 **자식이 놓인 슬롯**($inPropSlot) 기준이다. $childMode 는 자식 *안으로* + // 내려갈 때의 모드라, 그것으로 게이트하면 `props` 컨테이너 자신이 값으로 평가된다 — + // `url` prop 을 받는 컴포넌트(`{"url":"/embed/x","size":"lg"}`)는 props 맵 자체가 + // 「4키 부분집합 + url 보유」라 판정식을 통과해, props 가 통째로 문자열로 붕괴하거나 + // (손상 url 이면) 키째 삭제된다. 컨테이너 키는 값이 아니므로 판정에서 제외한다. + if ($inPropSlot && ! $isContainerKey && $this->isImageValueObject($child)) { + $url = $child['url'] ?? null; + + if (is_string($url) && $url !== '') { + $value[$key] = $url; + $changed++; + + continue; + } + + // 손상값 — 스칼라가 아니거나 빈 문자열이면 어떤 렌더 경로에서도 유효한 src 가 + // 될 수 없다. prop 키를 제거해 컴포넌트의 폴백(사이트명 텍스트 등)을 살린다. + // 파괴적 동작이므로 원본을 로그에 남긴다. + $context->logger->warning( + "[core:7.0.11] {$table} id={$rowId} {$childPath} — url 이 유효하지 않아 prop 제거: " + .json_encode($child, JSON_UNESCAPED_UNICODE) + ); + unset($value[$key]); + $changed++; + $this->droppedCount++; + + continue; + } + + $value[$key] = $this->transform( + $child, $childMode, $changed, $context, $table, $rowId, $childPath + ); + } + + return $value; + } + + /** + * 값이 이미지 값 객체인지 **엄격하게** 판정합니다. + * + * 키 집합이 4키의 부분집합이고 `url` 키를 실제로 보유해야 한다. 코어 엔진의 느슨한 + * 판정식(4키 중 하나라도)을 여기에 쓰면 정상 props 를 대량 파괴한다 — 클래스 docblock + * 의 실측 수치 참조. + * + * @param mixed $v 판정 대상 + * @return bool 이미지 값 객체이면 true + */ + private function isImageValueObject(mixed $v): bool + { + if (! is_array($v) || $v === []) { + return false; + } + + if (array_is_list($v)) { + return false; + } + + foreach (array_keys($v) as $k) { + if (! in_array($k, self::IMAGE_VALUE_KEYS, true)) { + return false; + } + } + + return array_key_exists('url', $v); + } +} diff --git a/vendor-bundle.json b/vendor-bundle.json index 754e0137..c2258e1d 100644 --- a/vendor-bundle.json +++ b/vendor-bundle.json @@ -1,15 +1,15 @@ { "schema_version": "1.0", - "generated_at": "2026-08-28T13:27:33+00:00", + "generated_at": "2026-09-08T10:12:39+00:00", "generator": "g7 vendor-bundle:build", "target": "core", "composer_json_sha256": "3c7e6e17966f09f12a0282b12b18eba46c5c4f06d63c2355a514a3160af1d77a", - "composer_lock_sha256": "46c98077af8fb505bc14f783dd55bcaa65713c2bd7a59e62e808e63c740b5587", - "zip_sha256": "7e8fae4dc821a54d9c931761538bf520355db629c884a80224dd63f2f39e03a4", - "zip_size": 15281555, + "composer_lock_sha256": "281fa66445390c31f404d4d603856bc0f6fce955ac8f6af0384d4839521e0aaa", + "zip_sha256": "4f70a6cc30471f445ef7054740c49797e03c01cc1a4ff7b2cfe940a72a54446e", + "zip_size": 16024185, "package_count": 106, "php_requirement": "^8.2", - "g7_version": "7.0.10", + "g7_version": "7.0.11", "packages": [ { "name": "aws/aws-crt-php", @@ -18,7 +18,7 @@ }, { "name": "aws/aws-sdk-php", - "version": "3.386.1", + "version": "3.394.9", "type": "library" }, { @@ -28,7 +28,7 @@ }, { "name": "carbonphp/carbon-doctrine-types", - "version": "3.2.0", + "version": "3.2.1", "type": "library" }, { @@ -43,7 +43,7 @@ }, { "name": "composer/ca-bundle", - "version": "1.5.12", + "version": "1.5.14", "type": "library" }, { @@ -88,12 +88,12 @@ }, { "name": "geoip2/geoip2", - "version": "v3.3.0", + "version": "v3.4.0", "type": "library" }, { "name": "graham-campbell/result-type", - "version": "v1.1.4", + "version": "v1.2.0", "type": "library" }, { @@ -113,42 +113,42 @@ }, { "name": "guzzlehttp/uri-template", - "version": "v1.0.8", + "version": "v1.0.11", "type": "library" }, { "name": "jaybizzle/crawler-detect", - "version": "v1.4.0", + "version": "v1.4.1", "type": "library" }, { "name": "laravel/framework", - "version": "v12.62.0", + "version": "v12.69.1", "type": "library" }, { "name": "laravel/prompts", - "version": "v0.3.21", + "version": "v0.3.24", "type": "library" }, { "name": "laravel/reverb", - "version": "v1.10.2", + "version": "v1.11.1", "type": "library" }, { "name": "laravel/sanctum", - "version": "v4.3.2", + "version": "v4.3.3", "type": "library" }, { "name": "laravel/scout", - "version": "v11.3.0", + "version": "v11.6.1", "type": "library" }, { "name": "laravel/serializable-closure", - "version": "v2.0.13", + "version": "v2.0.16", "type": "library" }, { @@ -158,7 +158,7 @@ }, { "name": "league/commonmark", - "version": "2.10.0", + "version": "2.10.1", "type": "library" }, { @@ -168,22 +168,22 @@ }, { "name": "league/flysystem", - "version": "3.35.1", + "version": "3.36.0", "type": "library" }, { "name": "league/flysystem-aws-s3-v3", - "version": "3.35.2", + "version": "3.35.3", "type": "library" }, { "name": "league/flysystem-local", - "version": "3.31.0", + "version": "3.35.3", "type": "library" }, { "name": "league/mime-type-detection", - "version": "1.16.0", + "version": "1.17.0", "type": "library" }, { @@ -208,17 +208,17 @@ }, { "name": "monolog/monolog", - "version": "3.10.0", + "version": "3.11.0", "type": "library" }, { "name": "mtdowling/jmespath.php", - "version": "2.9.1", + "version": "2.9.2", "type": "library" }, { "name": "nesbot/carbon", - "version": "3.13.0", + "version": "3.13.2", "type": "library" }, { @@ -233,7 +233,7 @@ }, { "name": "nikic/php-parser", - "version": "v5.7.0", + "version": "v5.8.0", "type": "library" }, { @@ -243,7 +243,7 @@ }, { "name": "phpoption/phpoption", - "version": "1.9.5", + "version": "1.10.0", "type": "library" }, { @@ -293,12 +293,12 @@ }, { "name": "psy/psysh", - "version": "v0.12.23", + "version": "v0.12.24", "type": "library" }, { "name": "pusher/pusher-php-server", - "version": "7.2.8", + "version": "7.3.0", "type": "library" }, { @@ -363,12 +363,12 @@ }, { "name": "symfony/console", - "version": "v7.4.14", + "version": "v7.4.18", "type": "library" }, { "name": "symfony/css-selector", - "version": "v7.4.9", + "version": "v7.4.18", "type": "library" }, { @@ -378,62 +378,62 @@ }, { "name": "symfony/error-handler", - "version": "v7.4.14", + "version": "v7.4.17", "type": "library" }, { "name": "symfony/event-dispatcher", - "version": "v7.4.14", + "version": "v7.4.17", "type": "library" }, { "name": "symfony/event-dispatcher-contracts", - "version": "v3.7.0", + "version": "v3.7.1", "type": "library" }, { "name": "symfony/filesystem", - "version": "v7.4.11", + "version": "v7.4.18", "type": "library" }, { "name": "symfony/finder", - "version": "v7.4.14", + "version": "v7.4.17", "type": "library" }, { "name": "symfony/http-client", - "version": "v7.4.14", + "version": "v7.4.18", "type": "library" }, { "name": "symfony/http-client-contracts", - "version": "v3.7.0", + "version": "v3.7.3", "type": "library" }, { "name": "symfony/http-foundation", - "version": "v7.4.14", + "version": "v7.4.18", "type": "library" }, { "name": "symfony/http-kernel", - "version": "v7.4.14", + "version": "v7.4.18", "type": "library" }, { "name": "symfony/mailer", - "version": "v7.4.14", + "version": "v7.4.17", "type": "library" }, { "name": "symfony/mailgun-mailer", - "version": "v7.4.14", + "version": "v7.4.18", "type": "symfony-mailer-bridge" }, { "name": "symfony/mime", - "version": "v7.4.13", + "version": "v7.4.18", "type": "library" }, { @@ -443,17 +443,17 @@ }, { "name": "symfony/polyfill-intl-grapheme", - "version": "v1.38.1", + "version": "v1.41.0", "type": "library" }, { "name": "symfony/polyfill-intl-idn", - "version": "v1.38.1", + "version": "v1.42.0", "type": "library" }, { "name": "symfony/polyfill-intl-normalizer", - "version": "v1.38.0", + "version": "v1.42.0", "type": "library" }, { @@ -468,7 +468,7 @@ }, { "name": "symfony/polyfill-php83", - "version": "v1.38.2", + "version": "v1.41.0", "type": "library" }, { @@ -478,7 +478,7 @@ }, { "name": "symfony/polyfill-php85", - "version": "v1.38.1", + "version": "v1.41.0", "type": "library" }, { @@ -488,42 +488,42 @@ }, { "name": "symfony/process", - "version": "v7.4.13", + "version": "v7.4.18", "type": "library" }, { "name": "symfony/routing", - "version": "v7.4.13", + "version": "v7.4.18", "type": "library" }, { "name": "symfony/service-contracts", - "version": "v3.7.0", + "version": "v3.7.3", "type": "library" }, { "name": "symfony/string", - "version": "v7.4.13", + "version": "v7.4.15", "type": "library" }, { "name": "symfony/translation", - "version": "v7.4.14", + "version": "v7.4.17", "type": "library" }, { "name": "symfony/translation-contracts", - "version": "v3.7.0", + "version": "v3.7.1", "type": "library" }, { "name": "symfony/uid", - "version": "v7.4.9", + "version": "v7.4.17", "type": "library" }, { "name": "symfony/var-dumper", - "version": "v7.4.14", + "version": "v7.4.18", "type": "library" }, { @@ -533,7 +533,7 @@ }, { "name": "vlucas/phpdotenv", - "version": "v5.6.3", + "version": "v5.7.0", "type": "library" }, { diff --git a/vendor-bundle.zip b/vendor-bundle.zip index d03d15a6..90be3a17 100644 Binary files a/vendor-bundle.zip and b/vendor-bundle.zip differ