IntellijAlpine
IntellijAlpine copied to clipboard
0.6.4 still creates bugs in the editor
Hi,
I tried to remove the maximum of useless plugins for my projects but still Alpine makes the editor crash on complex blade files like this one :
@props([
'items' => '', // json of items
'label' => '',
'type' => 'default', // default, filterBar, modal
'size' => 'md', // sm, md
'searchBar' => false,
'searchBarPlaceholder' => 'Search',
'searchBarEmptyText' => 'No item found',
'name' => '',
'optGroup' => false,
])
@php
$defaultClasses = $type === 'default' ? 'bg-white dark:bg-shade-800 border border-shade-200 rounded dark:border-shade-600 w-full shadow-sm' : '';
@endphp
<div
x-data="{
open: false,
items: {{ $items }},
itemSelected: null,
name: '{{ $name }}',
query: '',
optGroup: {{ $optGroup ? 'true' : 'false' }},
resetField() {
this.itemSelected = this.items[0];
},
toggle() {
if(this.open) {
return this.close;
}
this.$refs.button.focus();
this.open = true;
},
close(focusAfter) {
if (!this.open) return
this.open = false
focusAfter && this.$refs.button.focus()
},
get displayItems() {
return this.query === ''
? this.items.map(item => ({ ...item }))
: this.items
.filter(item => item.label.toLowerCase().includes(this.query.toLowerCase()))
.map(item => ({ ...item }));
},
selectItem(value) {
if (this.optGroup) {
this.items.forEach(group => {
const foundItem = group.choices.find(item => item.value === value);
if (foundItem) this.itemSelected = foundItem;
});
} else {
const selectedItem = this.items.find(item => item.value === value);
this.itemSelected = selectedItem || null;
}
this.close(this.$refs.button);
},
resetSearch() {
this.query = '';
},
init() {
// get selected item from json / object
if (this.optGroup) {
this.items.forEach(group => {
group.choices.forEach(item => {
if (item.selected) {
this.itemSelected = item;
}
});
});
} else {
// get selected item from json / object for non-optgroup
this.items.forEach(item => {
if(item.selected === true) {
this.itemSelected = item;
}
});
}
// if not specified in items, set to first item (0)
if (!this.itemSelected && this.items.length > 0) {
if (this.optGroup) {
this.itemSelected = this.items.find(group => group.choices.length > 0)?.choices[0];
} else {
this.itemSelected = this.items[0];
}
};
// filterBar only
@if($type === 'filterBar')
// Watch the itemSelected and update the filters array
this.$watch('filtering', filtering => {
!filtering && this.resetField();
});
@endif
// modal only
@if($type === 'modal')
// force item selected when LV updating $modalSelectsValues
this.$watch('modalSelectsValues', modalSelectsValues => {
this.items.forEach( (item) => {
if(item.value == modalSelectsValues[this.name]) {
this.itemSelected = item;
}
});
});
@endif
},
}"
{{ $attributes->merge(['class' => "min-w-[8rem] relative h-full $defaultClasses"]) }}
x-on:keydown.escape.prevent.stop="close($refs.button)"
:class="{'!border-primary-500': open}"
wire:ignore
>
<label class="sr-only">{{ $label }}</label>
<input
type="hidden"
:name="name"
x-model="itemSelected.value"
:id="name"
/>
<button
x-ref="button"
x-on:click="toggle()"
:aria-expanded="open"
type="button"
class="flex items-center justify-between h-full gap-2 w-full truncate text-shade-700 dark:text-shade-50 {{ $size === 'sm' ? 'pl-2 pr-1 py-1 text-sm' : 'pl-3 pr-3 py-2 text-shade-950' }}"
>
<span
x-text="itemSelected ? itemSelected.label : 'Select item'"
class="truncate"
></span>
<x-heroicon-s-chevron-up-down class="{{ $size === 'sm' ? 'icon-s' : 'icon-md' }} ml-2 text-shade-500"/>
</button>
<div
x-ref="panel"
x-show="open"
x-transition.origin.top.right
x-on:click.outside="close($refs.button)"
x-cloak
class="dropdown-container right-0 top-full z-10 origin-top-right {{ $type === 'default' ? '!w-full' : '' }} {{ $size === 'sm' ? 'mt-1' : 'mt-2' }}"
:class="{'!border-primary-500': open}"
x-trap="open"
>
@if($searchBar)
<div
class="relative border-b border-primary-500"
x-on:keyup.down.prevent.stop="$focus.within($refs.listboxOptions).first()"
x-on:keyup.up.prevent.stop="$focus.within($refs.listboxOptions).last()"
x-on:keydown.tab="$focus.next()"
>
<input
type="text"
class="unique-input text-sm text-shade-700 dark:text-shade-50 w-full border-none focus-visible:border-none focus:border-none focus:ring-primary-500 bg-transparent dark:bg-transparent {{ $size === 'md' ? 'px-4 py-2.5' : 'px-2 py-1.5 text-sm leading-none'}}"
placeholder="{{ $searchBarPlaceholder }}"
x-model="query"
x-ref="inputSearch"
/>
<button
type="button"
class="absolute transform -translate-y-1/2 right-2 top-1/2"
x-show="query !== ''"
x-on:click="resetSearch()"
x-on:keydown.enter.stop="resetSearch(); $refs['inputSearch'].focus();"
>
<x-heroicon-s-x-mark class="icon-s text-shade-500"/>
</button>
</div>
@endif
<!-- more divs and so here -->
</div>
</div>
Also, you can see this ticket : https://youtrack.jetbrains.com/issue/WEB-64951
My editor doesn't see and read the JS correctly
Thank you @inxilpro
@joffreypersia hm. Does it crash, or does it just not handle the syntax highlighting properly? There's a known bug when combining Alpine and Blade that is very tricky—it's hard to inject the correct language into the file when they're mixed in certain ways.
I pasted your example into PhpStorm and didn't get any IDE errors…
Hi @inxilpro,
Try this code :
@props([
'tableOnly' => false,
])
<div
x-data="{
viewTableMosaic: {{ $tableOnly ? '\'table\'' : '(localStorage.getItem(\'table-filter\') ? localStorage.getItem(\'table-filter\') : \'table\')' }},
tableOnly: {{ $tableOnly ? 'true' : 'false'}},
allowClickingOnAllRow: false,
showFilters: false,
showDeleteModal: @entangle('showDeleteModal'),
nbOfRows: 0,
selected: @entangle('selected'),
firstItem: null,
lastItem: null,
init() {
this.nbOfRows = document.querySelectorAll('tbody tr').length;
this.checkSelectedItems();
$nextTick(() => {
// Call the shadowScrolling function
this.shadowScrolling();
let isUpdated = false;
Livewire.hook('morph.updated', ({ el, component }) => {
if (!isUpdated) {
isUpdated = true;
setTimeout(() => {
// Call checkSelectedItems
this.checkSelectedItems();
this.checkNumberOfRows();
// Reset the flag
isUpdated = false;
// Call the shadowScrolling function
this.shadowScrolling();
}, 10);
}
});
});
},
resetSelectAll() {
document.querySelector('thead tr input[type=checkbox]').checked = false;
},
checkSelectedItems() {
// timeout of 100ms to be sure that the function will run after the wire:model has been updated
setTimeout(() => {
let nbOfChecked = this.selected.length;
if (nbOfChecked === this.nbOfRows) {
document.querySelector('thead tr input[type=checkbox]').checked = true;
} else {
document.querySelector('thead tr input[type=checkbox]').checked = false;
}
}, 100);
},
toggleAllCheckboxes(el) {
document.querySelectorAll('tbody tr input[type=checkbox]').forEach((checkbox) => {
const isChecked = el.checked;
if (checkbox.checked !== isChecked) {
checkbox.checked = isChecked;
// Manually dispatch the change event
checkbox.dispatchEvent(new Event('change'));
}
});
},
checkNumberOfRows() {
this.nbOfRows = document.querySelectorAll('tbody tr').length;
},
shadowScrolling() {
const scrollBoxes = document.querySelectorAll('.scroll-shadows');
let isShadowBottomActive = false; // Flag to track the current state of the shadow
scrollBoxes.forEach(scrollBox => {
// Check if the element is currently visible (not having display: none)
if (getComputedStyle(scrollBox).display !== 'none') {
// At page load / refresh
if (scrollBox.scrollHeight > scrollBox.clientHeight) {
const el = document.querySelector('#shadowContainer');
el.classList.add('shadowBottom');
isShadowBottomActive = true;
} else {
const el = document.querySelector('#shadowContainer');
el.classList.remove('shadowBottom');
isShadowBottomActive = false;
}
scrollBox.addEventListener('scroll', function(e) {
const shouldShowShadow = e.currentTarget.scrollHeight - e.currentTarget.clientHeight - e.currentTarget.scrollTop > 30;
if (shouldShowShadow && !isShadowBottomActive) {
const el = document.querySelector('#shadowContainer');
el.classList.add('shadowBottom');
isShadowBottomActive = true;
} else if (!shouldShowShadow && isShadowBottomActive) {
const el = document.querySelector('#shadowContainer');
el.classList.remove('shadowBottom');
isShadowBottomActive = false;
}
});
}
});
}
}"
class="space-y-4 flex-grow flex flex-col"
>
@include('model-index-pages.table-filter')
<div class="relative flex flex-col flex-grow">
<!-- Shadow container -->
<div id="shadowContainer" class="absolute w-full h-full z-10 pointer-events-none rounded"></div>
<!-- Table view -->
@include('model-index-pages.table', array('tableStyle' => ' divide-y divide-shade-200 dark:divide-shade-700')) {{-- tableStyle is used here to style header / body separation --}}
@if($tableOnly === false )
<!-- Mosaic view -->
@include('model-index-pages.mosaique')
@endif
</div>
@include('model-index-pages.pagination')
<!-- Delete Modal -->
@teleport('body')
<form wire:submit="deleteSelected">
<x-modal.confirmation wire:model="showDeleteModal" maxWidth="xl">
<x-slot name="title">Delete Confirmation</x-slot>
<x-slot name="content">
<p>Are you sure you want to delete these items ? This action is irreversible.</p>
</x-slot>
<x-slot name="footer">
<button type="button" x-on:click="show = false" class="btn btn-white w-full sm:w-fit">Cancel</button>
<button type="submit" class="btn btn-danger w-full sm:w-fit">Delete</button>
</x-slot>
</x-modal.confirmation>
</form>
@endteleport
</div>
I documented this subject in the ticket https://youtrack.jetbrains.com/issue/WEB-64951