Skip to content
Merged
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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ Add `corex` to your `mix.exs` dependencies:
```elixir
def deps do
[
{:corex, "~> 0.1.0-alpha.23"}
{:corex, "~> 0.1.0-alpha.24"}
]
end
```
Expand Down
12 changes: 4 additions & 8 deletions assets/components/combobox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,14 +94,11 @@ export class Combobox extends Component<Props, Api> {
.querySelectorAll('[data-scope="combobox"][data-part="item-group"]:not([data-template])')
.forEach((el) => el.remove());

const items = this.api.collection.items;

const groups = this.api.collection.group?.() ?? [];
const hasGroupsInCollection = groups.some(([group]) => group != null);

if (hasGroupsInCollection) {
if (this.hasGroups) {
const groups = this.api.collection.group?.() ?? [];
this.renderGroupedItems(contentEl, templatesContainer, groups);
} else {
const items = this.options?.length ? this.options : this.allOptions;
this.renderFlatItems(contentEl, templatesContainer, items);
}
}
Expand Down Expand Up @@ -159,9 +156,8 @@ export class Combobox extends Component<Props, Api> {
}

cloneItem(templatesContainer: HTMLElement, item: ComboboxItem): HTMLElement | null {
const value = this.api.collection.getItemValue(item);
const value = (this.api.collection.getItemValue?.(item) as string | undefined) ?? item.id ?? "";

// Find template by data-value (templates are rendered per item in Elixir when custom slots are used)
const template = templatesContainer.querySelector<HTMLElement>(
`[data-scope="combobox"][data-part="item"][data-value="${value}"][data-template]`
);
Expand Down
18 changes: 6 additions & 12 deletions assets/components/signature-pad.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,24 +95,18 @@ export class SignaturePad extends Component<Props, Api> {
);
if (clearBtn) {
this.spreadProps(clearBtn, this.api.getClearTriggerProps());
const hasPaths = this.api.paths.length > 0 || !!this.api.currentPath;
clearBtn.hidden = !hasPaths;
}

const hiddenInput = rootEl.querySelector<HTMLInputElement>(
'[data-scope="signature-pad"][data-part="hidden-input"]'
);
if (hiddenInput) {
const pathsForValue = this.paths.length > 0 ? this.paths : this.api.paths;
if (this.paths.length === 0 && this.api.paths.length > 0) {
this.paths = [...this.api.paths];
}
const pathsValue = pathsForValue.length > 0 ? JSON.stringify(pathsForValue) : "";
this.spreadProps(hiddenInput, this.api.getHiddenInputProps({ value: pathsValue }));
if (this.name) {
hiddenInput.name = this.name;
}
hiddenInput.value = pathsValue;
this.spreadProps(
hiddenInput,
this.api.getHiddenInputProps({
value: this.api.paths.length > 0 ? JSON.stringify(this.api.paths) : "",
})
);
}

this.syncPaths();
Expand Down
24 changes: 19 additions & 5 deletions assets/hooks/combobox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ const ComboboxHook: Hook<object & ComboboxHookState, HTMLElement> = {
const pushEvent = this.pushEvent.bind(this);

const allItems = JSON.parse(el.dataset.collection || "[]");
const hasGroups = allItems.some((item: { group?: unknown }) => item.group !== undefined);
const hasGroups = allItems.some((item: { group?: unknown }) => Boolean(item.group));

const props: Props = {
id: el.id,
Expand All @@ -55,7 +55,7 @@ const ComboboxHook: Hook<object & ComboboxHookState, HTMLElement> = {
invalid: getBoolean(el, "invalid"),
allowCustomValue: false,
selectionBehavior: "replace",
name: getString(el, "name"),
name: "",
form: getString(el, "form"),
readOnly: getBoolean(el, "readOnly"),
required: getBoolean(el, "required"),
Expand Down Expand Up @@ -126,12 +126,18 @@ const ComboboxHook: Hook<object & ComboboxHookState, HTMLElement> = {
'[data-scope="combobox"][data-part="value-input"]'
);
if (valueInput) {
const toId = (val: string) => {
const item = allItems.find(
(i: { id?: string; label?: string }) => String(i.id ?? "") === val || i.label === val
);
return item ? String(item.id ?? "") : val;
};
const idValue =
details.value.length === 0
? ""
: details.value.length === 1
? String(details.value[0])
: details.value.map(String).join(",");
? toId(String(details.value[0]))
: details.value.map((v) => toId(String(v))).join(",");
valueInput.value = idValue;

const formId = valueInput.getAttribute("form");
Expand Down Expand Up @@ -205,6 +211,14 @@ const ComboboxHook: Hook<object & ComboboxHookState, HTMLElement> = {
combobox.setAllOptions(allItems);
combobox.init();

const visibleInput = el.querySelector<HTMLInputElement>(
'[data-scope="combobox"][data-part="input"]'
);
if (visibleInput) {
visibleInput.removeAttribute("name");
visibleInput.removeAttribute("form");
}

const initialValue = getBoolean(el, "controlled")
? getStringList(el, "value")
: getStringList(el, "defaultValue");
Expand Down Expand Up @@ -236,7 +250,7 @@ const ComboboxHook: Hook<object & ComboboxHookState, HTMLElement> = {

updated(this: object & HookInterface<HTMLElement> & ComboboxHookState) {
const newCollection = JSON.parse(this.el.dataset.collection || "[]");
const hasGroups = newCollection.some((item: { group?: unknown }) => item.group !== undefined);
const hasGroups = newCollection.some((item: { group?: unknown }) => Boolean(item.group));

if (this.combobox) {
this.combobox.hasGroups = hasGroups;
Expand Down
32 changes: 4 additions & 28 deletions assets/hooks/signature-pad.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,11 +49,14 @@ const SignaturePadHook: Hook<object & SignaturePadHookState, HTMLElement> = {
drawing: buildDrawingOptions(el),
onDrawEnd: (details) => {
signaturePad.setPaths(details.paths);

const hiddenInput = el.querySelector<HTMLInputElement>(
'[data-scope="signature-pad"][data-part="hidden-input"]'
);
if (hiddenInput) {
hiddenInput.value = JSON.stringify(details.paths);
hiddenInput.value = details.paths.length > 0 ? JSON.stringify(details.paths) : "";
hiddenInput.dispatchEvent(new Event("input", { bubbles: true }));
hiddenInput.dispatchEvent(new Event("change", { bubbles: true }));
}

details.getDataUrl("image/png").then((url) => {
Expand Down Expand Up @@ -87,29 +90,10 @@ const SignaturePadHook: Hook<object & SignaturePadHookState, HTMLElement> = {
signaturePad.init();
this.signaturePad = signaturePad;

const initialPaths = controlled ? paths : defaultPaths;
if (initialPaths.length > 0) {
const hiddenInput = el.querySelector<HTMLInputElement>(
'[data-scope="signature-pad"][data-part="hidden-input"]'
);
if (hiddenInput) {
hiddenInput.dispatchEvent(new Event("input", { bubbles: true }));
hiddenInput.dispatchEvent(new Event("change", { bubbles: true }));
}
}

this.onClear = (event: Event) => {
const { id: targetId } = (event as CustomEvent<{ id: string }>).detail;
if (targetId && targetId !== el.id) return;
signaturePad.api.clear();
signaturePad.imageURL = "";
signaturePad.setPaths([]);
const hiddenInput = el.querySelector<HTMLInputElement>(
'[data-scope="signature-pad"][data-part="hidden-input"]'
);
if (hiddenInput) {
hiddenInput.value = "";
}
};
el.addEventListener("phx:signature-pad:clear", this.onClear);

Expand All @@ -120,14 +104,6 @@ const SignaturePadHook: Hook<object & SignaturePadHookState, HTMLElement> = {
const targetId = payload.signature_pad_id;
if (targetId && targetId !== el.id) return;
signaturePad.api.clear();
signaturePad.imageURL = "";
signaturePad.setPaths([]);
const hiddenInput = el.querySelector<HTMLInputElement>(
'[data-scope="signature-pad"][data-part="hidden-input"]'
);
if (hiddenInput) {
hiddenInput.value = "";
}
})
);
},
Expand Down
2 changes: 1 addition & 1 deletion e2e/lib/e2e_web/live/admin_live/form.ex
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ defmodule E2eWeb.AdminLive.Form do
{msg}
</:error>
</.date_picker>
<.signature_pad field={@form[:signature]} class="signature-pad" controlled>
<.signature_pad field={@form[:signature]} class="signature-pad">
<:label>Sign here</:label>
<:clear_trigger>
<.icon name="hero-x-mark" />
Expand Down
2 changes: 1 addition & 1 deletion guides/installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ Add `corex` to your `mix.exs` dependencies:
```elixir
def deps do
[
{:corex, "~> 0.1.0-alpha.23"}
{:corex, "~> 0.1.0-alpha.24"}
]
end
```
Expand Down
2 changes: 1 addition & 1 deletion lib/components/combobox.ex
Original file line number Diff line number Diff line change
Expand Up @@ -319,7 +319,7 @@ defmodule Corex.Combobox do
bubble: @bubble, disabled: @disabled
})}>
<div {Connect.root(%Root{id: @id, invalid: @invalid, read_only: @read_only})}>
<input type="hidden" name={@name} form={@form} id={"#{@id}-value"} data-scope="combobox" data-part="value-input" value={@value_for_hidden_input} />
<input type="hidden" name={@name} form={@form} id={"#{@id}-value"} data-scope="combobox" data-part="value-input" value={@value_for_hidden_input} required={@required} />

<div :if={!Enum.empty?(@label)} {Connect.label(%Label{id: @id, invalid: @invalid, read_only: @read_only, required: @required, disabled: @disabled, dir: @dir})}>
{render_slot(@label)}
Expand Down
1 change: 0 additions & 1 deletion lib/components/combobox/connect.ex
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,6 @@ defmodule Corex.Combobox.Connect do
"data-invalid" => get_boolean(assigns.invalid),
"aria-controls" => "combobox:#{assigns.id}:content",
"placeholder" => assigns.placeholder,
"required" => get_boolean(assigns.required),
"autoFocus" => get_boolean(assigns.auto_focus)
}
end
Expand Down
26 changes: 20 additions & 6 deletions lib/components/signature_pad.ex
Original file line number Diff line number Diff line change
Expand Up @@ -128,10 +128,6 @@ defmodule Corex.SignaturePad do

### With Ecto changeset

When using Ecto changeset for validation and inside a Live view you must enable the controlled mode.

This allows the Live View to be the source of truth and the component to be in sync accordingly.

First create your schema and changeset:

```elixir
Expand Down Expand Up @@ -174,7 +170,6 @@ defmodule Corex.SignaturePad do
field={@form[:signature]}
id="my-signature-pad"
class="signature-pad"
controlled
>
<:label>Sign here</:label>
<:clear_trigger>
Expand Down Expand Up @@ -418,7 +413,13 @@ defmodule Corex.SignaturePad do
{render_slot(@label)}
</label>
<div {Connect.control(%Control{id: @id, dir: @dir})}>
<svg {Connect.segment(%Segment{id: @id, dir: @dir})}>
<svg {Connect.segment(%Segment{id: @id, dir: @dir})} fill={@drawing_fill}>
<path
:for={path <- parse_paths(@paths)}
data-scope="signature-pad"
data-part="path"
d={path}
/>
</svg>
<button
:if={@clear_trigger != []}
Expand Down Expand Up @@ -492,4 +493,17 @@ defmodule Corex.SignaturePad do
end

defp has_paths?(_), do: false

defp parse_paths(nil), do: []
defp parse_paths(""), do: []
defp parse_paths(paths) when is_list(paths), do: Enum.filter(paths, &is_binary/1)

defp parse_paths(paths) when is_binary(paths) do
case Corex.Json.encoder().decode(paths) do
{:ok, list} when is_list(list) -> Enum.filter(list, &is_binary/1)
_ -> []
end
end

defp parse_paths(_), do: []
end
3 changes: 2 additions & 1 deletion lib/components/tree_view.ex
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ defmodule Corex.TreeView do
<div
id={@id}
phx-hook="TreeView"
phx-update="ignore"
{@rest}
{Connect.props(%Props{
id: @id,
Expand All @@ -141,7 +142,7 @@ defmodule Corex.TreeView do
on_expanded_change: @on_expanded_change
})}
>
<div phx-update="ignore" {Connect.root(%Root{id: @id, dir: @dir})}>
<div {Connect.root(%Root{id: @id, dir: @dir})}>
<%= if @label != [] do %>
<h3 {Connect.label(%Label{id: @id, dir: @dir})}>
<%= render_slot(@label) %>
Expand Down
2 changes: 1 addition & 1 deletion mix.exs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
defmodule Corex.MixProject do
use Mix.Project

@version "0.1.0-alpha.23"
@version "0.1.0-alpha.24"
@elixir_requirement "~> 1.15"

def project do
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "corex",
"version": "0.1.0-alpha.23",
"version": "0.1.0-alpha.24",
"description": "The official JavaScript client for the Corex Phoenix Hooks.",
"license": "MIT",
"module": "./priv/static/corex.mjs",
Expand Down
30 changes: 21 additions & 9 deletions priv/static/combobox.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -1665,12 +1665,11 @@ var Combobox = class extends Component {
if (!templatesContainer) return;
contentEl.querySelectorAll('[data-scope="combobox"][data-part="item"]:not([data-template])').forEach((el) => el.remove());
contentEl.querySelectorAll('[data-scope="combobox"][data-part="item-group"]:not([data-template])').forEach((el) => el.remove());
const items = this.api.collection.items;
const groups = this.api.collection.group?.() ?? [];
const hasGroupsInCollection = groups.some(([group]) => group != null);
if (hasGroupsInCollection) {
if (this.hasGroups) {
const groups = this.api.collection.group?.() ?? [];
this.renderGroupedItems(contentEl, templatesContainer, groups);
} else {
const items = this.options?.length ? this.options : this.allOptions;
this.renderFlatItems(contentEl, templatesContainer, items);
}
}
Expand Down Expand Up @@ -1709,7 +1708,7 @@ var Combobox = class extends Component {
}
}
cloneItem(templatesContainer, item) {
const value = this.api.collection.getItemValue(item);
const value = this.api.collection.getItemValue?.(item) ?? item.id ?? "";
const template = templatesContainer.querySelector(
`[data-scope="combobox"][data-part="item"][data-value="${value}"][data-template]`
);
Expand Down Expand Up @@ -1769,7 +1768,7 @@ var ComboboxHook = {
const el = this.el;
const pushEvent = this.pushEvent.bind(this);
const allItems = JSON.parse(el.dataset.collection || "[]");
const hasGroups = allItems.some((item) => item.group !== void 0);
const hasGroups = allItems.some((item) => Boolean(item.group));
const props2 = {
id: el.id,
...getBoolean(el, "controlled") ? { value: getStringList(el, "value") } : { defaultValue: getStringList(el, "defaultValue") },
Expand All @@ -1785,7 +1784,7 @@ var ComboboxHook = {
invalid: getBoolean(el, "invalid"),
allowCustomValue: false,
selectionBehavior: "replace",
name: getString(el, "name"),
name: "",
form: getString(el, "form"),
readOnly: getBoolean(el, "readOnly"),
required: getBoolean(el, "required"),
Expand Down Expand Up @@ -1854,7 +1853,13 @@ var ComboboxHook = {
'[data-scope="combobox"][data-part="value-input"]'
);
if (valueInput) {
const idValue = details.value.length === 0 ? "" : details.value.length === 1 ? String(details.value[0]) : details.value.map(String).join(",");
const toId = (val) => {
const item = allItems.find(
(i) => String(i.id ?? "") === val || i.label === val
);
return item ? String(item.id ?? "") : val;
};
const idValue = details.value.length === 0 ? "" : details.value.length === 1 ? toId(String(details.value[0])) : details.value.map((v) => toId(String(v))).join(",");
valueInput.value = idValue;
const formId = valueInput.getAttribute("form");
let form = null;
Expand Down Expand Up @@ -1919,6 +1924,13 @@ var ComboboxHook = {
combobox.hasGroups = hasGroups;
combobox.setAllOptions(allItems);
combobox.init();
const visibleInput = el.querySelector(
'[data-scope="combobox"][data-part="input"]'
);
if (visibleInput) {
visibleInput.removeAttribute("name");
visibleInput.removeAttribute("form");
}
const initialValue = getBoolean(el, "controlled") ? getStringList(el, "value") : getStringList(el, "defaultValue");
if (initialValue && initialValue.length > 0) {
const selectedItems = allItems.filter(
Expand All @@ -1943,7 +1955,7 @@ var ComboboxHook = {
},
updated() {
const newCollection = JSON.parse(this.el.dataset.collection || "[]");
const hasGroups = newCollection.some((item) => item.group !== void 0);
const hasGroups = newCollection.some((item) => Boolean(item.group));
if (this.combobox) {
this.combobox.hasGroups = hasGroups;
this.combobox.setAllOptions(newCollection);
Expand Down
Loading
Loading