Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,23 @@
"directory": "components/language-chooser/svelte/language-chooser-svelte-daisyui"
},
"type": "module",
"main": "./index.js",
"types": "./index.d.ts",
"svelte": "./index.ts",
"main": "./dist/index.cjs",
"module": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
".": {
"types": "./dist/index.d.ts",
"svelte": "./index.ts",
"import": "./dist/index.js",
"require": "./dist/index.cjs"
}
},
"files": [
"dist",
"src/lib",
"index.ts"
],
"scripts": {
"dev": "vite --open",
"typecheck": "tsc",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,18 +28,49 @@
customDetails: viewModel.customizations,
});

let languageTag = $derived(createTagFromOrthography(orthography));
function safeCreateTagFromOrthography(
maybeOrthography: IOrthography
): string | undefined {
const language = maybeOrthography?.language;

// During SSR/initial render, selectedLanguage can be partially populated.
// createTagFromOrthography expects language.scripts to be an array.
if (!language || !Array.isArray(language.scripts)) {
return undefined;
}

try {
return createTagFromOrthography(maybeOrthography);
} catch {
return undefined;
}
}
Comment on lines +31 to +47

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚩 safeCreateTagFromOrthography guards are correct but asymmetric with controller usage

The safeCreateTagFromOrthography wrapper (lines 31-47) guards against SSR scenarios where language or language.scripts might not be properly initialized. This is sound: createTagFromOrthography at languageTagUtils.ts:323 accesses language?.scripts.length which would throw if language exists but scripts is not an array (optional chaining only protects against language being nullish, not scripts). However, it's worth noting that the controller's _updateTagPreview (language-chooser.ts:181) calls createTagFromOrthography directly without this guard. If SSR ever reaches the controller's code path, the same crash could occur there. The current guard only protects the Svelte component's $derived expression.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.


let languageTag = $derived(safeCreateTagFromOrthography(orthography));

let listedLanguages = $derived(
Array.isArray(viewModel.listedLanguages) ? viewModel.listedLanguages : []
);
let listedScripts = $derived(
Array.isArray(viewModel.listedScripts) ? viewModel.listedScripts : []
);

let closeModal = $state(() => {});
let scrollContainer: HTMLElement;

viewModel.promptForCustomTag = (_default?: string) => {
if (typeof window === "undefined") {
return;
}

const tag = window.prompt(
"If this user interface is not offering you a language tag that you know is valid ISO 639 code, you can enter it here:",
_default
);
if (tag && !isValidBcp47Tag(tag)) {
alert(`This is not in a valid IETF BCP 47 format: ${tag}`);
if (typeof window !== "undefined") {
window.alert(`This is not in a valid IETF BCP 47 format: ${tag}`);
}
} else if (tag) {
viewModel.customLanguageTag = tag;
closeModal();
Expand Down Expand Up @@ -68,6 +99,14 @@
behavior: "smooth",
});
}

function onSearchInput(event: Event) {
viewModel.searchString = (event.currentTarget as HTMLInputElement).value;
}

function onDisplayNameInput(event: Event) {
viewModel.displayName = (event.currentTarget as HTMLInputElement).value;
}
</script>

<div class="h-full flex flex-col">
Expand All @@ -83,27 +122,28 @@
<input
type="search"
placeholder="Search by name, code, or country"
bind:value={viewModel.searchString}
value={viewModel.searchString ?? ""}
oninput={onSearchInput}
Comment on lines +125 to +126

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚩 Controlled input pattern replaces bind:value — subtle behavioral difference

The bind:valuevalue={...} + oninput={...} migration (lines 125-126, 192-193) changes from Svelte's native two-way binding to a controlled input pattern. This works correctly because SvelteFieldImpl's setter (field.svelte.ts:19-22) synchronously updates $state before triggering requestUpdate. However, Svelte's bind:value has special internal handling for cursor position preservation that the manual pattern does not. In practice, since the reactive update is synchronous, cursor jumping is unlikely, but it's worth noting this behavioral difference — especially for the search input where rapid typing is common.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

/>
</label>
</div>

<div class="flex-1 overflow-y-auto min-h-0" bind:this={scrollContainer}>
{#each viewModel.listedLanguages
<div class="flex-1 overflow-y-auto min-h-0" bind:this={scrollContainer}>
{#each listedLanguages
.slice(0, 100)
.map(svelteViewModel) as lang}
<LanguageCard
viewModel={lang}
searchText={viewModel.searchString}
onSelect={onLanguageSelected}
/>
{#if lang.isSelected && viewModel.listedScripts.length > 0}
{#if lang.isSelected && listedScripts.length > 0}
<div class="ml-8 mb-4">
<div class="py-2">
<p class="font-semibold text-sm">Select a script:</p>
</div>
<div class="grid grid-cols-3 gap-4">
{#each viewModel.listedScripts.map(svelteViewModel) as script}
{#each listedScripts.map(svelteViewModel) as script}
<ScriptCard viewModel={script} />
{/each}
</div>
Expand Down Expand Up @@ -149,7 +189,8 @@
>
<input
class="input input-xl w-full"
bind:value={viewModel.displayName}
value={viewModel.displayName ?? ""}
oninput={onDisplayNameInput}
/>
</label>
<div class="font-mono opacity-70 p-2">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@
"skipLibCheck": true
},
"exclude": [
"src/main.ts",
"src/App.svelte",
"src/app.css",
"langtagProcessing.ts",
"**/*.spec.ts",
"**/*.test.ts",
Expand All @@ -23,11 +26,8 @@
"**/*.test.jsx"
],
"include": [
"./**/*.js",
"./**/*.jsx",
"./**/*.ts",
"./**/*.tsx",
"./**/*.svelte",
"../index.ts"
"src/lib/**/*.ts",
"src/lib/**/*.svelte",
"index.ts"
]
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,5 +38,15 @@ export default defineConfig({
fileName: "index",
formats: ["es", "cjs"],
},
rollupOptions: {
external: [
"svelte",
/^svelte\//,
"@ethnolib/find-language",
"@ethnolib/language-chooser-controller",
"@ethnolib/state-management-svelte",
/^@ethnolib\//,
],
},
},
});
Loading