Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Uncaught TypeError: node.parentNode is null #7020

Closed
MarcGodard opened this issue Dec 16, 2021 · 5 comments
Closed

Uncaught TypeError: node.parentNode is null #7020

MarcGodard opened this issue Dec 16, 2021 · 5 comments

Comments

@MarcGodard
Copy link

Describe the bug

See: sveltejs/kit#3056

Reproduction

Can't save my REPL right now due to: sveltejs/sites#189

Logs

No response

System Info

System:
    OS: macOS 12.1
    CPU: (8) x64 Intel(R) Core(TM) i7-4980HQ CPU @ 2.80GHz
    Memory: 1.34 GB / 16.00 GB
    Shell: 5.8 - /bin/zsh
  Binaries:
    Node: 16.13.1 - /usr/local/Cellar/node@16/16.13.1/bin/node
    Yarn: 1.22.17 - /usr/local/bin/yarn
    npm: 8.1.2 - /usr/local/Cellar/node@16/16.13.1/bin/npm
  Browsers:
    Brave Browser: 95.1.31.91
    Chrome: 96.0.4664.110
    Firefox: 95.0
    Safari: 15.2
  npmPackages:
    @sveltejs/kit: ^1.0.0-next.196 => 1.0.0-next.202 
    svelte: ^3.44.2 => 3.44.3

Severity

annoyance

@rmunn
Copy link
Contributor

rmunn commented Dec 16, 2021

Try logging into the REPL, then opening a new tab and saving. Failing that, please post your reproduction from sveltejs/kit#3056 (comment) as an issue comment, because so far I've been unable to reproduce this.

@MarcGodard
Copy link
Author

Dropdown.svelte:

<script>
  import { onMount } from 'svelte'
  import { fade } from 'svelte/transition'

  export let id = Math.ceil(Math.random() * 100)
  export let small = false
  export let extraSmall = false
  export let hideValues = false
  export let hideLabels = false
  export let label = 'Label Missing'
  export let value
  export let valueTitle = label
  export let dropdownOptions = []
  export let disabled = false
  export let addSearch = false
  export let searchPlaceholder = '...'
  export let required = false
  export let errors = []
  export let hideErrors = false
  export let locked = false
  export let elseLocked = true
  export let helper = ''
  export let errorHelper = ''
  let defaultIcon = ''
  let hasError = false

  $: {
    errors.forEach(error => {
      if (error.errorId === id) {
        hasError = true
        if (error.errorMessage) errorHelper = error.errorMessage
      }
    })
  }

  $: if (!value || Object.keys(value).length === 0) found = false

  let show = false // menu state
  let found = false

  let clientHeight = 0
  let filterListText = ''
  let searchInput

  function setItem (title, newValue, icon) {
    if (!disabled) {
      valueTitle = title
      value = newValue || title
      defaultIcon = icon || ''
      show = false
      found = true
      removeErrors()
    }
  }

  function removeErrors () {
    let newErrors = []
    errors.forEach(error => {
      if (error.errorId !== id) newErrors.push(error)
    })
    errors = newErrors
    hasError = false
  }

  function toggle () {
    show = (!disabled && !show)
    if (show && addSearch) {
      setTimeout(() => { if (searchInput) searchInput.focus() }, 10)
    }
    if (!show) filterListText = ''
  }

  const handleKeyupSearch = (event) => {
    filterListText = event.target.value
    if (event.code === 'Enter') {
      let selectedIndex
      dropdownOptions.forEach((item, index) => {
        if (!selectedIndex && item.title.toLowerCase().includes(filterListText.toLowerCase())) {
          selectedIndex = index
        }
      })
      if (selectedIndex) {
        let selected = dropdownOptions[selectedIndex]
        setItem(selected.title, selected.value || selected.title, selected.icon)
      }
    }
  }

  let showBottom
  $: {
    showBottom = clientHeight
  }

  onMount(() => {
    if (dropdownOptions && dropdownOptions.length > 1) {
      dropdownOptions.forEach(item => {
        if (value && item.value === value && valueTitle && item.title === valueTitle) {
          valueTitle = item.title
          value = (typeof item.value !== 'undefined' ? item.value : item.title)
          defaultIcon = item.icon
          found = true
        }
      })
      if (!found) {
        dropdownOptions.forEach(item => {
          if ((item.value === value && value) || (item.title === value && value)) {
            valueTitle = item.title
            value = (typeof item.value !== 'undefined' ? item.value : item.title)
            defaultIcon = item.icon
            found = true
          }
        })
      }
    } else if (dropdownOptions && dropdownOptions.length === 1) {
      valueTitle = dropdownOptions[0].title
      value = (typeof dropdownOptions[0].value !== 'undefined' ? dropdownOptions[0].value : dropdownOptions[0].title)
      defaultIcon = dropdownOptions[0].icon
      found = true
    }

    const handleEscape = (event) => {
      if (show && event.key === 'Escape') {
        show = false
      }
    }
    // add events when element is added to the DOM
    document.addEventListener('keyup', handleEscape, false)
    // remove events when element is removed from the DOM
    return () => {
      document.removeEventListener('keyup', handleEscape, false)
    }
  })
</script>

<div>
  {#if !locked}
    <div class="relative">
      {#if !small && label && !hideLabels}
        <label for={id} class="input-label" class:error={hasError}>
          {label} {#if required}<span class="text-danger">*</span>{/if}
        </label>
      {/if}
      <div id={id}>
        <button on:click={() => toggle()} class:open="{show}" class:error={hasError} class:disabled={disabled}
                type="button" class:input-field-sm={small} class:input-field={!small}
                class="inline-flex w-full input-primary mt-0 overflow-hidden" id="options-menu"
                aria-haspopup="true" aria-expanded="true">
          {#if !found}
            <span class="pr-6 font-normal text-opacity-50 whitespace-nowrap" class:text-gray-300={show}
                  class:dark:text-gray-500={show} class:text-gray-500={!show} class:dark:text-gray-700={!show} >{label}...</span>
          {:else}
            <span class="pr-6 flex text-gray-800 dark:text-gray-800 whitespace-nowrap" class:text-sm={extraSmall}>
              {#if defaultIcon}<img src="{defaultIcon}" alt="{valueTitle}" class="h-6 w-6 m-auto">{/if}
              <span class:ml-5={defaultIcon}>{valueTitle} {#if !hideValues && value}({value}){/if}</span>
            </span>
          {/if}
        </button>
      </div>
      {#if show && dropdownOptions.length}
        <div id="box-{id}" transition:fade={{ duration: 150 }} bind:clientHeight={clientHeight} class="dropdown-box z-20"
             class:up={showBottom} role="menu" aria-orientation="vertical"
             aria-labelledby="options-menu">
          <div class="py-1 max-h-96 overflow-scroll" class:text-sm={extraSmall}>
            {#if addSearch}
              <div class="dropdown-box-non-item">
                <input bind:this={searchInput} placeholder="{searchPlaceholder}" on:keyup="{handleKeyupSearch}"
                       class="input-field-sm input-primary text-gray-400 dark:text-gray-700 mt-0">
              </div>
              <div class="dropdown-box-divider"><hr></div>
            {/if}
            {#each dropdownOptions as item}
              {#if item.title}
                {#if !filterListText || item.title.toLowerCase().includes(filterListText.toLowerCase())}
                  {#if item.disabled}
                    <div class="flex dropdown-box-item disabled">
                      {#if item.icon}<img src="{item.icon}" alt="{item.title}" class="h-6 w-6 m-auto">{/if}
                      <span class="w-full text-left" class:ml-5={item.icon} role="menuitem">{item.title} {#if !hideValues && item.value}({item.value}){/if}</span>
                    </div>
                  {:else}
                    <div on:click={() => setItem(item.title, item.value, item.icon)} class="flex dropdown-box-item">
                      {#if item.icon}<img src="{item.icon}" alt="{item.title}" class="h-6 w-6 m-auto">{/if}
                      <span class="w-full text-left" class:ml-5={item.icon} role="menuitem">{item.title} {#if !hideValues && item.value}({item.value}){/if}</span>
                    </div>
                  {/if}
                {/if}
              {/if}
            {/each}
          </div>
        </div>
      {/if}
      {#if helper}<div class="input-helper">{helper}</div>{/if}
      {#if errorHelper && hasError && !hideErrors}<div class="input-error-helper">{errorHelper}</div>{/if}
    </div>
  {:else if elseLocked}
    {#if !small && label && !hideLabels}<label for="input-locked" class="input-label">{label}</label>{/if}
    <div id="input-locked" class="input-locked">
      <span class="flex" class:text-sm={extraSmall}>
        {#if defaultIcon}<img src="{defaultIcon}" alt="{valueTitle}" class="h-6 w-6 m-auto">{/if}
        <span class:ml-5={defaultIcon}>{valueTitle} {#if !hideValues && value}({value}){/if}</span>
      </span>
    </div>
  {/if}
</div>

and App.svelte:

<script>
	import Dropdown from './Dropdown.svelte'
	let show = 'one';
	let options = [{ title: 'one' }, { title:'two'} ]
	
</script>

{#if show === 'one'}
	<Dropdown bind:value={show} dropdownOptions={options} label="Show Dropdown" />
{:else}
	{#each options as item}
		{#if item.title === show}
			{show} | {item}
		{/if}
	{/each}
{/if}

@MarcGodard
Copy link
Author

Please note there are some simplifications to work with REPL, however the above recreated the console log error.

@SiddHyper
Copy link

+1

@Rich-Harris
Copy link
Member

I can't reproduce this in Svelte 4 or 5, so I'll assume it was fixed

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants