88import { registerToolBootContract } from "../shared/toolBootContract.js" ;
99
1010const CUSTOM_PALETTES_STORAGE_KEY = "toolboxaid.paletteBrowser.customPalettes" ;
11+ const HIDDEN_BUILTIN_PALETTES_STORAGE_KEY = "toolboxaid.paletteBrowser.hiddenBuiltins" ;
1112
1213const refs = {
1314 searchInput : document . getElementById ( "paletteSearchInput" ) ,
@@ -24,6 +25,7 @@ const refs = {
2425 newPaletteButton : document . getElementById ( "newPaletteButton" ) ,
2526 duplicatePaletteButton : document . getElementById ( "duplicatePaletteButton" ) ,
2627 renamePaletteButton : document . getElementById ( "renamePaletteButton" ) ,
28+ deletePaletteButton : document . getElementById ( "deletePaletteButton" ) ,
2729 addSwatchButton : document . getElementById ( "addSwatchButton" ) ,
2830 deleteSwatchButton : document . getElementById ( "deleteSwatchButton" ) ,
2931 validationText : document . getElementById ( "paletteValidationText" ) ,
@@ -38,9 +40,18 @@ const state = {
3840 search : "" ,
3941 selectedPaletteId : "" ,
4042 selectedSwatchIndex : 0 ,
41- customPalettes : loadCustomPalettes ( )
43+ customPalettes : loadCustomPalettes ( ) ,
44+ hiddenBuiltInPaletteIds : loadHiddenBuiltInPaletteIds ( )
4245} ;
4346
47+ function hasDeleteOverrideParam ( ) {
48+ const params = new URLSearchParams ( window . location . search ) ;
49+ const raw = params . get ( "overridReserveWorkBlock" )
50+ ?? params . get ( "overrideReserveWordBlock" )
51+ ?? "" ;
52+ return / ^ ( 1 | t r u e | y e s | o n ) $ / i. test ( raw . trim ( ) ) ;
53+ }
54+
4455function applyLaunchContext ( ) {
4556 const context = getSharedLaunchContext ( ) ;
4657 const sourceLabel = context . sourceToolId
@@ -68,6 +79,23 @@ function saveCustomPalettes() {
6879 localStorage . setItem ( CUSTOM_PALETTES_STORAGE_KEY , JSON . stringify ( state . customPalettes ) ) ;
6980}
7081
82+ function loadHiddenBuiltInPaletteIds ( ) {
83+ try {
84+ const raw = localStorage . getItem ( HIDDEN_BUILTIN_PALETTES_STORAGE_KEY ) ;
85+ if ( ! raw ) {
86+ return [ ] ;
87+ }
88+ const parsed = JSON . parse ( raw ) ;
89+ return Array . isArray ( parsed ) ? parsed . filter ( ( entry ) => typeof entry === "string" ) : [ ] ;
90+ } catch {
91+ return [ ] ;
92+ }
93+ }
94+
95+ function saveHiddenBuiltInPaletteIds ( ) {
96+ localStorage . setItem ( HIDDEN_BUILTIN_PALETTES_STORAGE_KEY , JSON . stringify ( state . hiddenBuiltInPaletteIds ) ) ;
97+ }
98+
7199function getBuiltInPalettes ( ) {
72100 const paletteSource = globalThis . palettesList && typeof globalThis . palettesList === "object"
73101 ? globalThis . palettesList
@@ -83,7 +111,8 @@ function getBuiltInPalettes() {
83111 hex : typeof entry ?. hex === "string" ? entry . hex : "" ,
84112 name : typeof entry ?. name === "string" ? entry . name : ""
85113 } ) )
86- } ) ) ;
114+ } ) )
115+ . filter ( ( palette ) => ! state . hiddenBuiltInPaletteIds . includes ( palette . id ) ) ;
87116}
88117
89118function getAllPalettes ( ) {
@@ -108,6 +137,23 @@ function isCustomPalette(palette) {
108137 return Boolean ( palette && palette . source === "custom" ) ;
109138}
110139
140+ function normalizePaletteNameForReservedCheck ( name ) {
141+ return String ( name || "" ) . toLowerCase ( ) . replace ( / [ ^ a - z 0 - 9 ] / g, "" ) ;
142+ }
143+
144+ function isReadOnlyPalette ( palette ) {
145+ if ( ! palette ) {
146+ return true ;
147+ }
148+ if ( ! isCustomPalette ( palette ) ) {
149+ return true ;
150+ }
151+ const normalizedName = normalizePaletteNameForReservedCheck ( palette . name ) ;
152+ return normalizedName . includes ( "crayola" )
153+ || normalizedName . includes ( "w3c" )
154+ || normalizedName . includes ( "javascript" ) ;
155+ }
156+
111157function validatePalette ( palette ) {
112158 if ( ! palette ) {
113159 return [ "Select a palette to validate." ] ;
@@ -131,6 +177,17 @@ function validatePalette(palette) {
131177 return issues ;
132178}
133179
180+ function formatSwatchNameForDisplay ( name ) {
181+ return String ( name || "Unnamed" ) . replace ( / ( [ a - z 0 - 9 ] ) ( [ A - Z ] ) / g, "$1\u200b$2" ) ;
182+ }
183+
184+ function hasReservedPaletteKeyword ( name ) {
185+ const normalizedName = normalizePaletteNameForReservedCheck ( name ) ;
186+ return normalizedName . includes ( "crayola" )
187+ || normalizedName . includes ( "w3c" )
188+ || normalizedName . includes ( "javascript" ) ;
189+ }
190+
134191function renderPaletteList ( ) {
135192 const palettes = getVisiblePalettes ( ) ;
136193 refs . countText . textContent = `${ palettes . length } palette${ palettes . length === 1 ? "" : "s" } ` ;
@@ -139,7 +196,7 @@ function renderPaletteList() {
139196 return `
140197 <button type="button" data-palette-id="${ palette . id } " class="${ currentClass . trim ( ) } ">
141198 <strong>${ palette . name } </strong>
142- <span>${ palette . entries . length } swatches | ${ palette . source } </span>
199+ <span>( ${ palette . entries . length } ) swatches | ${ palette . source } </span>
143200 </button>
144201 ` ;
145202 } ) . join ( "" ) ;
@@ -156,6 +213,14 @@ function renderSelectedPalette() {
156213 refs . paletteSummaryText . textContent = "Select a palette to inspect its swatches." ;
157214 refs . paletteSwatches . innerHTML = "" ;
158215 refs . paletteNameInput . value = "" ;
216+ refs . paletteNameInput . disabled = true ;
217+ refs . swatchColorInput . disabled = true ;
218+ refs . swatchNameInput . disabled = true ;
219+ refs . swatchSymbolInput . disabled = true ;
220+ refs . renamePaletteButton . disabled = true ;
221+ refs . deletePaletteButton . disabled = true ;
222+ refs . addSwatchButton . disabled = true ;
223+ refs . deleteSwatchButton . disabled = true ;
159224 refs . jsonPreview . textContent = "Palette JSON preview will appear here." ;
160225 refs . validationText . textContent = "Validation summary will appear here." ;
161226 return ;
@@ -164,14 +229,23 @@ function renderSelectedPalette() {
164229 refs . paletteTitle . textContent = palette . name ;
165230 refs . paletteSummaryText . textContent = `${ palette . entries . length } swatches | source: ${ palette . source } ` ;
166231 refs . paletteNameInput . value = palette . name ;
167- refs . paletteNameInput . disabled = ! isCustomPalette ( palette ) ;
232+ const readOnly = isReadOnlyPalette ( palette ) ;
233+ const canOverrideDeleteGuard = hasDeleteOverrideParam ( ) ;
234+ refs . paletteNameInput . disabled = readOnly ;
235+ refs . swatchColorInput . disabled = readOnly ;
236+ refs . swatchNameInput . disabled = readOnly ;
237+ refs . swatchSymbolInput . disabled = readOnly ;
238+ refs . renamePaletteButton . disabled = readOnly ;
239+ refs . deletePaletteButton . disabled = canOverrideDeleteGuard ? false : readOnly ;
240+ refs . addSwatchButton . disabled = readOnly ;
241+ refs . deleteSwatchButton . disabled = readOnly ;
168242
169243 refs . paletteSwatches . innerHTML = palette . entries . map ( ( entry , index ) => {
170244 const currentClass = index === state . selectedSwatchIndex ? " is-current" : "" ;
171245 return `
172246 <button type="button" data-swatch-index="${ index } " class="${ currentClass . trim ( ) } ">
173247 <span class="palette-browser__swatch-chip" style="background:${ entry . hex } "></span>
174- <strong>${ entry . name || "Unnamed" } </strong>
248+ <strong class="palette-browser__swatch-name" >${ formatSwatchNameForDisplay ( entry . name || "Unnamed" ) } </strong>
175249 <span>${ entry . symbol || "-" } </span>
176250 </button>
177251 ` ;
@@ -215,7 +289,16 @@ function createCustomPalette(name, entries) {
215289}
216290
217291function createNewPalette ( ) {
218- const palette = createCustomPalette ( "new-palette" , [
292+ const requestedName = window . prompt ( "Name for new palette:" , "new-palette" ) ;
293+ if ( requestedName === null ) {
294+ return ;
295+ }
296+ const nextName = requestedName . trim ( ) || "new-palette" ;
297+ if ( hasReservedPaletteKeyword ( nextName ) ) {
298+ refs . validationText . textContent = "Palette name cannot include reserved terms: crayola, w3c, javascript." ;
299+ return ;
300+ }
301+ const palette = createCustomPalette ( nextName , [
219302 { symbol : "A" , hex : "#ffffff" , name : "White" }
220303 ] ) ;
221304 state . customPalettes . unshift ( palette ) ;
@@ -228,19 +311,39 @@ function duplicateSelectedPalette() {
228311 if ( ! palette ) {
229312 return ;
230313 }
231- const duplicate = createCustomPalette ( `${ palette . name } -copy` , palette . entries ) ;
314+ const suggestedName = `${ palette . name } -copy` ;
315+ let nextName = suggestedName ;
316+ while ( true ) {
317+ const requestedName = window . prompt ( "Name for duplicated palette:" , nextName ) ;
318+ if ( requestedName === null ) {
319+ return ;
320+ }
321+ nextName = requestedName . trim ( ) || suggestedName ;
322+ if ( ! hasReservedPaletteKeyword ( nextName ) ) {
323+ break ;
324+ }
325+ refs . validationText . textContent = "Palette name cannot include reserved terms: crayola, w3c, javascript." ;
326+ }
327+ const duplicate = createCustomPalette ( nextName , palette . entries ) ;
232328 state . customPalettes . unshift ( duplicate ) ;
233329 saveCustomPalettes ( ) ;
234330 setSelectedPalette ( duplicate . id ) ;
235331}
236332
237333function renameSelectedPalette ( ) {
238334 const palette = getSelectedPalette ( ) ;
239- if ( ! isCustomPalette ( palette ) ) {
335+ if ( isReadOnlyPalette ( palette ) ) {
240336 refs . validationText . textContent = "Duplicate a built-in palette before renaming it." ;
241337 return ;
242338 }
243- palette . name = refs . paletteNameInput . value . trim ( ) || palette . name ;
339+ const requestedName = refs . paletteNameInput . value . trim ( ) || palette . name ;
340+ if ( hasReservedPaletteKeyword ( requestedName ) ) {
341+ refs . validationText . textContent = "Palette name cannot include reserved terms: crayola, w3c, javascript." ;
342+ window . alert ( "Palette not renamed. Reserved terms are not allowed: crayola, w3c, javascript." ) ;
343+ refs . paletteNameInput . value = palette . name ;
344+ return ;
345+ }
346+ palette . name = requestedName ;
244347 saveCustomPalettes ( ) ;
245348 renderPaletteList ( ) ;
246349 renderSelectedPalette ( ) ;
@@ -262,6 +365,26 @@ function addSwatchToSelectedPalette() {
262365 renderSelectedPalette ( ) ;
263366}
264367
368+ function updateSelectedSwatchFromInputs ( ) {
369+ const palette = getSelectedPalette ( ) ;
370+ if ( isReadOnlyPalette ( palette ) ) {
371+ return ;
372+ }
373+ if ( ! palette || ! Array . isArray ( palette . entries ) || palette . entries . length === 0 ) {
374+ return ;
375+ }
376+ const index = Math . min ( state . selectedSwatchIndex , palette . entries . length - 1 ) ;
377+ const entry = palette . entries [ index ] ;
378+ if ( ! entry ) {
379+ return ;
380+ }
381+ entry . hex = refs . swatchColorInput . value ;
382+ entry . name = refs . swatchNameInput . value . trim ( ) || "Unnamed" ;
383+ entry . symbol = refs . swatchSymbolInput . value . trim ( ) . slice ( 0 , 2 ) ;
384+ saveCustomPalettes ( ) ;
385+ renderSelectedPalette ( ) ;
386+ }
387+
265388function deleteSelectedSwatch ( ) {
266389 const palette = getSelectedPalette ( ) ;
267390 if ( ! isCustomPalette ( palette ) ) {
@@ -333,6 +456,32 @@ function usePaletteInActiveTools() {
333456 : "Shared palette handoff was not updated because the payload was invalid." ;
334457}
335458
459+ function deleteSelectedPalette ( ) {
460+ const palette = getSelectedPalette ( ) ;
461+ if ( ! palette ) {
462+ return ;
463+ }
464+ const canOverrideDeleteGuard = hasDeleteOverrideParam ( ) ;
465+ if ( isReadOnlyPalette ( palette ) && ! canOverrideDeleteGuard ) {
466+ refs . validationText . textContent = "Built-in palettes cannot be deleted. Duplicate to create an editable copy." ;
467+ return ;
468+ }
469+ if ( isCustomPalette ( palette ) ) {
470+ state . customPalettes = state . customPalettes . filter ( ( entry ) => entry . id !== palette . id ) ;
471+ saveCustomPalettes ( ) ;
472+ } else if ( canOverrideDeleteGuard ) {
473+ if ( ! state . hiddenBuiltInPaletteIds . includes ( palette . id ) ) {
474+ state . hiddenBuiltInPaletteIds . push ( palette . id ) ;
475+ saveHiddenBuiltInPaletteIds ( ) ;
476+ }
477+ }
478+ const nextPalette = getVisiblePalettes ( ) [ 0 ] ?? getAllPalettes ( ) [ 0 ] ?? null ;
479+ state . selectedPaletteId = nextPalette ?. id ?? "" ;
480+ state . selectedSwatchIndex = 0 ;
481+ renderPaletteList ( ) ;
482+ renderSelectedPalette ( ) ;
483+ }
484+
336485function renderStoredSelection ( ) {
337486 const handoff = readSharedPaletteHandoff ( ) ;
338487 if ( ! handoff ) {
@@ -369,8 +518,12 @@ function bindEvents() {
369518 refs . newPaletteButton . addEventListener ( "click" , createNewPalette ) ;
370519 refs . duplicatePaletteButton . addEventListener ( "click" , duplicateSelectedPalette ) ;
371520 refs . renamePaletteButton . addEventListener ( "click" , renameSelectedPalette ) ;
521+ refs . deletePaletteButton . addEventListener ( "click" , deleteSelectedPalette ) ;
372522 refs . addSwatchButton . addEventListener ( "click" , addSwatchToSelectedPalette ) ;
373523 refs . deleteSwatchButton . addEventListener ( "click" , deleteSelectedSwatch ) ;
524+ refs . swatchColorInput . addEventListener ( "input" , updateSelectedSwatchFromInputs ) ;
525+ refs . swatchNameInput . addEventListener ( "input" , updateSelectedSwatchFromInputs ) ;
526+ refs . swatchSymbolInput . addEventListener ( "input" , updateSelectedSwatchFromInputs ) ;
374527 refs . copyPaletteJsonButton . addEventListener ( "click" , copyPaletteJson ) ;
375528 refs . exportPaletteJsonButton . addEventListener ( "click" , exportPaletteJson ) ;
376529 refs . usePaletteButton . addEventListener ( "click" , usePaletteInActiveTools ) ;
0 commit comments