|
function randInt(min, max) {
|
|
min = Math.ceil(min)
|
|
max = Math.floor(max)
|
|
return Math.floor(Math.random() * (max - min + 1)) + min
|
|
}
|
|
|
|
class EventHandler {
|
|
constructor() {
|
|
this.events = {}
|
|
this.eventCount = 0
|
|
this.suppresEvents = false
|
|
this.debugEvents = false
|
|
}
|
|
|
|
on(event, listener) {
|
|
this.events[event] ??= { name: name, listeners: [], callCount: 0 }
|
|
this.events[event].listeners.push(listener)
|
|
}
|
|
|
|
off(event, listenerToRemove) {
|
|
if (!this.events[event]) return
|
|
this.events[event].listeners = this.events[event].listeners.filter(
|
|
(listener) => listener !== listenerToRemove,
|
|
)
|
|
}
|
|
|
|
emit(event, data) {
|
|
if (!this.events[event]) return []
|
|
if (this.suppresEvents) return []
|
|
this.eventCount++
|
|
const returnValue = this.events[event].listeners.map((listener) => {
|
|
return listener(data) ?? null
|
|
})
|
|
this.events[event].callCount++
|
|
if (this.debugEvents) {
|
|
console.debug("debugEvent", {
|
|
event: event,
|
|
arg: data,
|
|
callCount: this.events[event].callCount,
|
|
number: this.eventCount,
|
|
returnValues: returnValue,
|
|
})
|
|
}
|
|
return returnValue
|
|
}
|
|
|
|
suppres(fn) {
|
|
const originallySuppressed = this.suppresEvents
|
|
this.suppresEvents = true
|
|
fn(this)
|
|
this.suppresEvents = originallySuppressed
|
|
return originallySuppressed
|
|
}
|
|
}
|
|
|
|
class Col extends EventHandler {
|
|
id = 0
|
|
values = []
|
|
initial = false
|
|
_marked = false
|
|
row = null
|
|
index = 0
|
|
valid = true
|
|
_selected = false
|
|
_value = 0
|
|
|
|
_isValidXy() {
|
|
if (!this._value) return true
|
|
return (
|
|
this.row.puzzle.fields
|
|
.filter((field) => {
|
|
return (
|
|
(field.index == this.index && field.value == this._value) ||
|
|
(field.row.index == this.row.index && field.value == this._value)
|
|
)
|
|
})
|
|
.filter((field) => field != this).length == 0
|
|
)
|
|
}
|
|
|
|
mark() {
|
|
this.marked = true
|
|
}
|
|
|
|
unmark() {
|
|
this.marked = false
|
|
}
|
|
|
|
select() {
|
|
this.selected = true
|
|
}
|
|
|
|
unselect() {
|
|
this.selected = false
|
|
}
|
|
|
|
_isValidBox() {
|
|
if (!this._value) return true
|
|
const startRow =
|
|
this.row.index - (this.row.index % (this.row.puzzle.size / 3))
|
|
const startCol = this.index - (this.index % (this.row.puzzle.size / 3))
|
|
|
|
for (let i = 0; i < this.row.puzzle.size / 3; i++) {
|
|
for (let j = 0; j < this.row.puzzle.size / 3; j++) {
|
|
const field = this.row.puzzle.get(i + startRow, j + startCol)
|
|
if (field != this && field.value == this._value) {
|
|
return false
|
|
}
|
|
}
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
validate() {
|
|
if (!this.row.puzzle.initalized) {
|
|
return this.valid
|
|
}
|
|
if (this.initial) {
|
|
this.valid = true
|
|
return this.valid
|
|
}
|
|
if (!this.value && !this.valid) {
|
|
this.valid = true
|
|
this.emit("update", this)
|
|
return this.valid
|
|
}
|
|
const oldValue = this.valid
|
|
this.valid = this._isValidXy() && this._isValidBox()
|
|
if (oldValue != this.valid) {
|
|
this.emit("update", this)
|
|
}
|
|
return this.valid
|
|
}
|
|
|
|
set value(val) {
|
|
if (this.initial) return
|
|
const digit = Number(val)
|
|
const validDigit = digit >= 0 && digit <= 9
|
|
const update = validDigit && digit != this.value
|
|
if (update) {
|
|
this._value = Number(digit)
|
|
this.validate()
|
|
this.emit("update", this)
|
|
}
|
|
}
|
|
|
|
get value() {
|
|
return this._value
|
|
}
|
|
|
|
get selected() {
|
|
return this._selected
|
|
}
|
|
|
|
set selected(val) {
|
|
if (val != this._selected) {
|
|
this._selected = val
|
|
if (this.row.puzzle.initalized) this.emit("update", this)
|
|
}
|
|
}
|
|
|
|
get marked() {
|
|
return this._marked
|
|
}
|
|
|
|
set marked(val) {
|
|
if (val != this._marked) {
|
|
this._marked = val
|
|
if (this.row.puzzle.initalized) {
|
|
this.emit("update", this)
|
|
}
|
|
}
|
|
}
|
|
|
|
constructor(row) {
|
|
super()
|
|
this.row = row
|
|
this.index = this.row.cols.length
|
|
this.id = this.row.puzzle.rows.length * this.row.puzzle.size + this.index
|
|
this.initial = false
|
|
this.selected = false
|
|
this._value = 0
|
|
this.marked = false
|
|
this.valid = true
|
|
}
|
|
|
|
update() {
|
|
this.emit("update", this)
|
|
}
|
|
|
|
toggleSelected() {
|
|
this.selected = !this.selected
|
|
}
|
|
|
|
toggleMarked() {
|
|
this.marked = !this.marked
|
|
}
|
|
|
|
get data() {
|
|
return {
|
|
values: this.values,
|
|
value: this.value,
|
|
index: this.index,
|
|
id: this.id,
|
|
row: this.row.index,
|
|
col: this.index,
|
|
valid: this.valid,
|
|
initial: this.initial,
|
|
selected: this.selected,
|
|
marked: this.marked,
|
|
}
|
|
}
|
|
|
|
toString() {
|
|
return String(this.value)
|
|
}
|
|
|
|
toText() {
|
|
return this.toString().replace("0", " ")
|
|
}
|
|
}
|
|
class Row extends EventHandler {
|
|
cols = []
|
|
puzzle = null
|
|
index = 0
|
|
initialized = false
|
|
constructor(puzzle) {
|
|
super()
|
|
this.puzzle = puzzle
|
|
this.cols = []
|
|
this.index = this.puzzle.rows.length
|
|
this.initialized = false
|
|
for (let i = 0; i < puzzle.size; i++) {
|
|
const col = new Col(this)
|
|
this.cols.push(col)
|
|
col.on("update", (field) => {
|
|
this.emit("update", field)
|
|
})
|
|
}
|
|
this.initialized = true
|
|
}
|
|
get data() {
|
|
return {
|
|
cols: this.cols.map((col) => col.data),
|
|
index: this.index,
|
|
}
|
|
}
|
|
toText() {
|
|
let result = ""
|
|
for (const col of this.cols) {
|
|
result += col.toText()
|
|
}
|
|
return result
|
|
}
|
|
toString() {
|
|
return this.toText().replaceAll(" ", "0")
|
|
}
|
|
}
|
|
|
|
class Puzzle extends EventHandler {
|
|
rows = []
|
|
size = 0
|
|
hash = 0
|
|
states = []
|
|
parsing = false
|
|
_initialized = false
|
|
initalized = false
|
|
_fields = null
|
|
constructor(arg) {
|
|
super()
|
|
this.debugEvents = true
|
|
this.initalized = false
|
|
this.rows = []
|
|
if (isNaN(arg)) {
|
|
// load session
|
|
} else {
|
|
this.size = Number(arg)
|
|
}
|
|
for (let i = 0; i < this.size; i++) {
|
|
const row = new Row(this)
|
|
this.rows.push(row)
|
|
row.on("update", (field) => {
|
|
this.onFieldUpdate(field)
|
|
})
|
|
}
|
|
this._initialized = true
|
|
this.initalized = true
|
|
this.commitState()
|
|
}
|
|
validate() {
|
|
return this.valid
|
|
}
|
|
_onEventHandler() {
|
|
this.eventCount++
|
|
}
|
|
makeInvalid() {
|
|
if (!app.valid) {
|
|
const invalid = this.invalid
|
|
return invalid[invalid.length - 1]
|
|
}
|
|
|
|
for (const row of this.rows) {
|
|
for (const col of row.cols) {
|
|
if (col.value) {
|
|
let modify = null
|
|
if (col.index == this.size) {
|
|
modify = this.get(row.index, col.index - 2)
|
|
} else {
|
|
modify = this.get(row.index, col.index + 1)
|
|
}
|
|
modify.value = col.value
|
|
// last one is invalid
|
|
return modify.index > col.index ? modify : col
|
|
}
|
|
col.valid = false
|
|
}
|
|
}
|
|
|
|
this.get(0, 0).value = 1
|
|
this.get(0, 1).value = 1
|
|
return this.get(0, 1)
|
|
}
|
|
reset() {
|
|
this._initialized = false
|
|
this.initalized = false
|
|
this.parsing = true
|
|
|
|
for (const field of this.fields) {
|
|
field.initial = false
|
|
field.selected = false
|
|
field.marked = false
|
|
field.value = 0
|
|
}
|
|
this.hash = 0
|
|
this.states = []
|
|
this.parsing = false
|
|
this.initalized = true
|
|
this._initialized = true
|
|
this.commitState()
|
|
}
|
|
get valid() {
|
|
return this.invalid.length == 0
|
|
}
|
|
get invalid() {
|
|
this.emit("validating", this)
|
|
const result = this.fields.filter((field) => !field.validate())
|
|
this.emit("validated", this)
|
|
return result
|
|
}
|
|
get selected() {
|
|
return this.fields.filter((field) => field.selected)
|
|
}
|
|
get marked() {
|
|
return this.fields.filter((field) => field.marked)
|
|
}
|
|
loadString(content) {
|
|
this.emit("parsing", this)
|
|
this.reset()
|
|
this.parsing = true
|
|
this.initalized = false
|
|
this._initialized = false
|
|
|
|
const regex = /\d/g
|
|
const matches = [...content.matchAll(regex)]
|
|
const max = this.size * this.size
|
|
|
|
for (const [index, match] of matches.entries()) {
|
|
const digit = Number(match[0])
|
|
const field = this.fields[index]
|
|
field.value = digit
|
|
field.initial = digit != 0
|
|
}
|
|
|
|
this._initialized = true
|
|
this.parsing = false
|
|
this.deselect()
|
|
this.initalized = true
|
|
this.suppres(() => {
|
|
for (const field of this.fields) {
|
|
field.validate()
|
|
}
|
|
})
|
|
this.commitState()
|
|
this.emit("parsed", this)
|
|
this.emit("update", this)
|
|
}
|
|
get state() {
|
|
return this.getData(true)
|
|
}
|
|
get previousState() {
|
|
if (this.states.length == 0) return null
|
|
return this.states.at(this.states.length - 1)
|
|
}
|
|
get stateChanged() {
|
|
if (!this._initialized) return false
|
|
return !this.previousState || this.state != this.previousState
|
|
}
|
|
|
|
commitState() {
|
|
if (!this.initalized) return false
|
|
this.hash = this._generateHash()
|
|
if (this.stateChanged) {
|
|
this.states.push(this.state)
|
|
this.emit("commitState", this)
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
onFieldUpdate(field) {
|
|
if (!this.initalized) return false
|
|
if (!this._initialized) return
|
|
this.validate()
|
|
this.commitState()
|
|
this.emit("update", this)
|
|
}
|
|
|
|
get data() {
|
|
return this.getData(true)
|
|
}
|
|
|
|
popState() {
|
|
let prevState = this.previousState
|
|
if (!prevState) {
|
|
this.deselect()
|
|
return null
|
|
}
|
|
while (prevState && prevState.hash == this.state.hash)
|
|
prevState = this.states.pop()
|
|
if (!prevState) {
|
|
this.deselect()
|
|
return null
|
|
}
|
|
this.applyState(prevState)
|
|
this.emit("popState", this)
|
|
return prevState
|
|
}
|
|
applyState(newState) {
|
|
this._initialized = false
|
|
|
|
for (const stateField of newState.fields) {
|
|
const field = this.get(stateField.row, stateField.col)
|
|
field.selected = stateField.selected
|
|
field.values = stateField.values
|
|
field.value = stateField.value
|
|
field.initial = stateField.initial
|
|
field.validate()
|
|
}
|
|
this._initialized = true
|
|
this.emit("stateApplied", this)
|
|
this.emit("update", this)
|
|
}
|
|
getData(withHash = false) {
|
|
const result = {
|
|
fields: this.fields.map((field) => field.data),
|
|
size: this.size,
|
|
valid: this.valid,
|
|
}
|
|
if (withHash) {
|
|
result.hash = this._generateHash()
|
|
}
|
|
return result
|
|
}
|
|
get(row, col) {
|
|
if (!this.initalized) return null
|
|
if (!this.rows.length) return null
|
|
if (!this.rows[row]) return null
|
|
return this.rows[row].cols[col]
|
|
}
|
|
get fields() {
|
|
if (this._fields == null) {
|
|
this._fields = []
|
|
for (const row of this.rows) {
|
|
for (const col of row.cols) {
|
|
this._fields.push(col)
|
|
}
|
|
}
|
|
}
|
|
return this._fields
|
|
}
|
|
_generateHash() {
|
|
return JSON.stringify(this.getData(false))
|
|
.split("")
|
|
.map((char) => {
|
|
return char.charCodeAt(0) - "0".charCodeAt(0)
|
|
})
|
|
.reduce((result, num) => {
|
|
return result + num + 26
|
|
}, 0)
|
|
}
|
|
get text() {
|
|
let result = ""
|
|
for (const row of this.rows) {
|
|
result += `${row.toText()}\n`
|
|
}
|
|
result = result.slice(0, result.length - 1)
|
|
return result
|
|
}
|
|
get initialFields() {
|
|
return this.fields.filter((field) => field.initial)
|
|
}
|
|
get json() {
|
|
return JSON.stringify(this.data)
|
|
}
|
|
get zeroedText() {
|
|
return this.text.replaceAll(" ", "0")
|
|
}
|
|
get string() {
|
|
return this.toString()
|
|
}
|
|
toString() {
|
|
return this.text.replaceAll("\n", "").replaceAll(" ", "0")
|
|
}
|
|
get humanFormat() {
|
|
return ` ${this.text.replaceAll(" ", "0").split("").join(" ")}`
|
|
}
|
|
getRandomField() {
|
|
const emptyFields = this.empty
|
|
return emptyFields[randInt(0, emptyFields.length - 1)]
|
|
}
|
|
update(callback) {
|
|
this.commitState()
|
|
this.intalized = false
|
|
callback(this)
|
|
this.intalized = true
|
|
this.validate()
|
|
this.commitState()
|
|
this.intalized = false
|
|
if (this.valid) this.deselect()
|
|
this.intalized = true
|
|
this.emit("update", this)
|
|
}
|
|
get empty() {
|
|
return this.fields.filter((field) => field.value == 0)
|
|
}
|
|
getRandomEmptyField() {
|
|
const field = this.getRandomField()
|
|
if (!field) return null
|
|
return field
|
|
}
|
|
deselect() {
|
|
for (const field of this.fields) {
|
|
field.selected = false
|
|
}
|
|
}
|
|
generate() {
|
|
this.reset()
|
|
this.initalized = false
|
|
for (let i = 0; i < 17; i++) {
|
|
this.fillRandomField()
|
|
}
|
|
this.deselect()
|
|
this.initalized = true
|
|
this.commitState()
|
|
this.emit("update", this)
|
|
}
|
|
fillRandomField() {
|
|
const field = this.getRandomEmptyField()
|
|
if (!field) return
|
|
this.deselect()
|
|
field.selected = true
|
|
let number = 0
|
|
number++
|
|
|
|
while (number <= 9) {
|
|
field.value = randInt(1, 9)
|
|
field.update()
|
|
if (this.validate()) {
|
|
field.initial = true
|
|
return field
|
|
}
|
|
number++
|
|
}
|
|
return false
|
|
}
|
|
autoSolve() {
|
|
window.requestAnimationFrame(() => {
|
|
if (this.fillRandomField()) {
|
|
if (this.empty.length) return this.autoSolve()
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
class PuzzleManager {
|
|
constructor(size) {
|
|
this.size = size
|
|
this.puzzles = []
|
|
this._activePuzzle = null
|
|
}
|
|
|
|
addPuzzle(puzzle) {
|
|
this.puzzles.push(puzzle)
|
|
}
|
|
get active() {
|
|
return this.activePuzzle
|
|
}
|
|
set activePuzzle(puzzle) {
|
|
this._activePuzzle = puzzle
|
|
}
|
|
get activePuzzle() {
|
|
return this._activePuzzle
|
|
}
|
|
}
|
|
|
|
const puzzleManager = new PuzzleManager(9)
|
|
|
|
class Sudoku extends HTMLElement {
|
|
styleSheet = `
|
|
.sudoku {
|
|
font-size: 13px;
|
|
color:#222;
|
|
display: grid;
|
|
grid-template-columns: repeat(9, 1fr);
|
|
grid-template-rows: auto;
|
|
gap: 0px;
|
|
user-select: none;
|
|
-webkit-user-select: none;
|
|
-moz-user-select: none;
|
|
-ms-user-select: none;
|
|
background-color: #e5e5e5;
|
|
border-radius: 5px;
|
|
aspect-ratio: 1/1;
|
|
}
|
|
.sudoku-field-initial {
|
|
color: #777;
|
|
}
|
|
.sudoku-field-selected {
|
|
background-color: lightgreen;
|
|
}
|
|
.soduku-field-marked {
|
|
background-color: blue;
|
|
}
|
|
.sudoku-field-invalid {
|
|
color: red;
|
|
}
|
|
.sudoku-field {
|
|
border: 1px solid #ccc;
|
|
text-align: center;
|
|
padding: 2px;
|
|
aspect-ratio: 1/1;
|
|
width: 1em;
|
|
line-height: 1;
|
|
}`
|
|
set fieldSize(val) {
|
|
this._fieldSize = val ? Number(val) : null
|
|
|
|
for (const field of this.fieldElements) {
|
|
field.style.fontSize = this._fieldSize
|
|
? `${this._fieldSize.toString()}px`
|
|
: ""
|
|
}
|
|
}
|
|
get fieldSize() {
|
|
return this._fieldSize
|
|
}
|
|
get eventCount() {
|
|
return this.puzzle.eventCount
|
|
}
|
|
get puzzleContent() {
|
|
return this.puzzle.humanFormat
|
|
}
|
|
set puzzleContent(val) {
|
|
if (val == "generate") {
|
|
this.puzzle.generate()
|
|
} else if (val) {
|
|
this.puzzle.loadString(val)
|
|
} else {
|
|
this.puzzle.reset()
|
|
}
|
|
}
|
|
connectedCallback() {
|
|
this.puzzleContent = this.getAttribute("puzzle")
|
|
? this.getAttribute("puzzle")
|
|
: null
|
|
this._fieldSize = null
|
|
this.fieldSize = this.getAttribute("size")
|
|
? this.getAttribute("size")
|
|
: null
|
|
this.readOnly = !!this.getAttribute("read-only")
|
|
this.attachShadow({ mode: "open" })
|
|
this.shadowRoot.appendChild(this.styleElement)
|
|
this.shadowRoot.appendChild(this.puzzleDiv)
|
|
}
|
|
toString() {
|
|
return this.puzzleContent
|
|
}
|
|
set active(val) {
|
|
this._active = val
|
|
if (this._active) this.manager.activePuzzle = this
|
|
}
|
|
get active() {
|
|
return this._active
|
|
}
|
|
set readOnly(val) {
|
|
this._readOnly = !!val
|
|
}
|
|
get readOnly() {
|
|
return this._readOnly
|
|
}
|
|
constructor() {
|
|
super()
|
|
this._readOnly = false
|
|
this._active = false
|
|
this.fieldElements = []
|
|
this.puzzle = new Puzzle(9)
|
|
this.fields = []
|
|
this.styleElement = document.createElement("style")
|
|
this.styleElement.textContent = this.styleSheet
|
|
this.puzzleDiv = document.createElement("div")
|
|
this.puzzleDiv.classList.add("sudoku")
|
|
this._bind()
|
|
this.manager.addPuzzle(this)
|
|
}
|
|
get manager() {
|
|
return puzzleManager
|
|
}
|
|
_bind() {
|
|
this._bindFields()
|
|
this._bindEvents()
|
|
this._sync()
|
|
}
|
|
_bindFields() {
|
|
for (const row of this.puzzle.rows) {
|
|
for (const field of row.cols) {
|
|
const fieldElement = document.createElement("div")
|
|
fieldElement.classList.add("sudoku-field")
|
|
fieldElement.field = field
|
|
field.on("update", (field) => {
|
|
this._sync()
|
|
})
|
|
fieldElement.addEventListener("click", (e) => {
|
|
if (!this.readOnly) field.toggleSelected()
|
|
})
|
|
fieldElement.addEventListener("contextmenu", (e) => {
|
|
e.preventDefault()
|
|
field.row.puzzle.update(() => {
|
|
field.selected = false
|
|
field.value = 0
|
|
})
|
|
})
|
|
this.fields.push(field)
|
|
this.fieldElements.push(fieldElement)
|
|
this.puzzleDiv.appendChild(fieldElement)
|
|
}
|
|
}
|
|
}
|
|
_bindEvents() {
|
|
this.puzzle.on("update", () => {
|
|
this._sync()
|
|
})
|
|
this.puzzleDiv.addEventListener("mouseenter", (e) => {
|
|
this.active = true
|
|
})
|
|
this.puzzleDiv.addEventListener("mouseexit", (e) => {
|
|
this.active = false
|
|
})
|
|
document.addEventListener("keydown", (e) => {
|
|
if (this.readOnly) return
|
|
if (!puzzleManager.active) return
|
|
const puzzle = puzzleManager.active.puzzle
|
|
|
|
if (!isNaN(e.key)) {
|
|
puzzle.update((target) => {
|
|
for (const field of puzzle.selected) {
|
|
field.value = Number(e.key)
|
|
}
|
|
})
|
|
} else {
|
|
const keyLookup = {
|
|
u() {
|
|
puzzle.popState()
|
|
},
|
|
d() {
|
|
puzzle.update((target) => {
|
|
for (const field of puzzle.selected) {
|
|
field.value = 0
|
|
}
|
|
})
|
|
},
|
|
a() {
|
|
puzzle.autoSolve()
|
|
},
|
|
r() {
|
|
puzzle.fillRandomField()
|
|
},
|
|
m() {
|
|
const fields = []
|
|
puzzle.update((target) => {
|
|
for (const field of target.selected) {
|
|
field.selected = false
|
|
fields.push(field)
|
|
}
|
|
})
|
|
puzzle.update((target) => {
|
|
for (const field of fields) {
|
|
field.toggleMarked()
|
|
}
|
|
})
|
|
puzzle.emit("update", puzzle)
|
|
},
|
|
}
|
|
keyLookup[e.key]?.()
|
|
}
|
|
})
|
|
}
|
|
get(row, col) {
|
|
return this.puzzle.get(row, col)
|
|
}
|
|
_syncField(fieldElement) {
|
|
const field = fieldElement.field
|
|
fieldElement.classList.remove(
|
|
"sudoku-field-selected",
|
|
"sudoku-field-empty",
|
|
"sudoku-field-invalid",
|
|
"sudoku-field-initial",
|
|
"sudoku-field-marked",
|
|
)
|
|
console.info("Removed marked class")
|
|
fieldElement.innerHTML = field.value ? field.value.toString() : " "
|
|
|
|
if (field.selected) {
|
|
fieldElement.classList.add("sudoku-field-selected")
|
|
window.selected = field.field
|
|
}
|
|
if (!field.valid) {
|
|
fieldElement.classList.add("sudoku-field-invalid")
|
|
}
|
|
if (!field.value) {
|
|
fieldElement.classList.add("sudoku-field-empty")
|
|
}
|
|
if (field.initial) {
|
|
fieldElement.classList.add("sudoku-field-initial")
|
|
}
|
|
if (field.marked) {
|
|
fieldElement.classList.add("sudoku-field-marked")
|
|
console.info("added marked lcass")
|
|
}
|
|
}
|
|
_sync() {
|
|
for (const fieldElement of this.fieldElements) {
|
|
this._syncField(fieldElement)
|
|
}
|
|
}
|
|
}
|
|
customElements.define("my-sudoku", Sudoku)
|
|
|
|
function generateIdByPosition(element) {
|
|
const parent = element.parentNode
|
|
const index = Array.prototype.indexOf.call(parent.children, element)
|
|
const generatedId = `${element.tagName.toLowerCase()}-${index}`
|
|
element.id = generatedId.replace("div-", "session-key-")
|
|
return element.id
|
|
}
|