Skip to content

Commit

Permalink
Merge branch 'surface-ui:main' into click-away
Browse files Browse the repository at this point in the history
  • Loading branch information
bryannaegele authored Nov 7, 2022
2 parents 829b437 + 95a011a commit 76fbd8b
Show file tree
Hide file tree
Showing 18 changed files with 606 additions and 165 deletions.
4 changes: 4 additions & 0 deletions lib/mix/tasks/compile/surface/validate_components.ex
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,10 @@ defmodule Mix.Tasks.Compile.Surface.ValidateComponents do
module.__get_prop__(attr.name)
end

defp validate_attribute(%{name: :__caller_scope_id__}, _prop, _node_alias, _file, _processed_attrs) do
nil
end

defp validate_attribute(attr, nil, node_alias, file, _) do
message = "Unknown property \"#{attr.name}\" for component <#{node_alias}>"
warning(message, file, attr.line)
Expand Down
2 changes: 2 additions & 0 deletions lib/mix/tasks/surface/surface.init.ex
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
defmodule Mix.Tasks.Surface.Init do
@shortdoc "Configures Surface on an phoenix project"

@moduledoc """
Configures Surface on an phoenix project.
Expand Down
19 changes: 14 additions & 5 deletions lib/surface/base_component.ex
Original file line number Diff line number Diff line change
Expand Up @@ -99,12 +99,21 @@ defmodule Surface.BaseComponent do
end

@doc false
def restore_private_assigns(socket, %{__context__: context}) do
socket
|> Phoenix.Component.assign(:__context__, context)
end
def restore_private_assigns(socket, assigns) do
socket =
if Map.has_key?(assigns, :__context__) do
Phoenix.Component.assign(socket, :__context__, assigns[:__context__])
else
socket
end

socket =
if Map.has_key?(assigns, :__caller_scope_id__) do
Phoenix.Component.assign(socket, :__caller_scope_id__, assigns[:__caller_scope_id__])
else
socket
end

def restore_private_assigns(socket, _assigns) do
socket
end

Expand Down
89 changes: 81 additions & 8 deletions lib/surface/compiler.ex
Original file line number Diff line number Diff line change
Expand Up @@ -690,7 +690,7 @@ defmodule Surface.Compiler do
meta: meta
}

{:ok, ast}
{:ok, maybe_transform_component_ast(ast, compile_meta)}
else
error -> handle_convert_node_to_ast_error(name, error, meta)
end
Expand Down Expand Up @@ -727,7 +727,7 @@ defmodule Surface.Compiler do
meta: meta
}

{:ok, maybe_call_transform(ast)}
{:ok, maybe_transform_component_ast(ast, compile_meta)}
else
error -> handle_convert_node_to_ast_error(name, error, meta)
end
Expand Down Expand Up @@ -775,7 +775,7 @@ defmodule Surface.Compiler do
}
end

{:ok, maybe_call_transform(result)}
{:ok, maybe_transform_component_ast(result, compile_meta)}
else
error -> handle_convert_node_to_ast_error(name, error, meta)
end
Expand Down Expand Up @@ -822,6 +822,16 @@ defmodule Surface.Compiler do
end
end

defp maybe_transform_component_ast(%AST.FunctionComponent{} = node, compile_meta) do
maybe_add_caller_scope_id_prop(node, compile_meta)
end

defp maybe_transform_component_ast(node, compile_meta) do
node
|> maybe_call_transform()
|> maybe_add_caller_scope_id_prop(compile_meta)
end

defp maybe_call_transform(%{module: module} = node) do
if function_exported?(module, :transform, 1) do
module.transform(node)
Expand All @@ -830,6 +840,69 @@ defmodule Surface.Compiler do
end
end

defp maybe_add_caller_scope_id_prop(node, %mod{style: %{} = style})
when mod in [CompileMeta, AST.Meta] do
%{meta: meta, props: props} = node
%{scope_id: scope_id} = style

prop = %AST.Attribute{
meta: meta,
name: :__caller_scope_id__,
type: :string,
value: %AST.Literal{value: scope_id}
}

%{node | props: [prop | props]}
end

defp maybe_add_caller_scope_id_prop(node, _compile_meta) do
node
end

defp maybe_add_caller_scope_id_attr_to_root_node(%type{} = node, _style) when type in [AST.Tag, AST.VoidTag] do
is_caller_a_component? = !!Helpers.get_module_attribute(node.meta.caller.module, :component_type, nil)
# TODO: when support for `attr` is added, check for :css_class types instead
function_component? = node.meta.caller.function != {:render, 1}

has_css_class_prop? = fn ->
props = Helpers.get_module_attribute(node.meta.caller.module, :prop, [])
Enum.any?(props, &(&1.type == :css_class))
end

passing_class_expr? = fn node ->
class = AST.find_attribute_value(node.attributes, :class)
match?(%AST.AttributeExpr{}, class)
end

attributes =
if is_caller_a_component? and passing_class_expr?.(node) and (has_css_class_prop?.() or function_component?) do
prefix = CSSTranslator.scope_attr_prefix()
# Quoted expression for ["the-prefix-#{@__caller_scope_id__}": !!@__caller_scope_id__]
expr =
quote do
[
"#{unquote(prefix)}#{var!(assigns)[:__caller_scope_id__]}":
{{:boolean, []}, !!var!(assigns)[:__caller_scope_id__]}
]
end

data_caller_scope_id_attr = %AST.DynamicAttribute{
meta: node.meta,
expr: AST.AttributeExpr.new(expr, "", node.meta)
}

[data_caller_scope_id_attr | node.attributes]
else
node.attributes
end

%{node | attributes: attributes}
end

defp maybe_add_caller_scope_id_attr_to_root_node(node, _style) do
node
end

defp attribute_value_as_atom(attributes, attr_name, default) do
Enum.find_value(attributes, default, fn {name, value, _} ->
if name == attr_name do
Expand Down Expand Up @@ -1492,18 +1565,18 @@ defmodule Surface.Compiler do
node
|> maybe_add_data_s_attrs_to_root_node(style)
|> maybe_add_or_update_style_attr_of_root_node(style)
|> maybe_add_caller_scope_id_attr_to_root_node(style)
end)
end

defp maybe_transform_ast(nodes, _compile_meta) do
nodes
end

defp maybe_add_data_s_attrs_to_root_node(%AST.Tag{} = node, %{requires_data_attrs_on_root: true} = style)
when style != nil do
defp maybe_add_data_s_attrs_to_root_node(%AST.Tag{} = node, %{requires_data_attrs_on_root: true} = style) do
%AST.Tag{attributes: attributes, meta: meta} = node
%{scope_id: scope_id} = style
data_s_scope = :"data-s-#{scope_id}"
data_s_scope = :"#{CSSTranslator.scope_attr_prefix()}#{scope_id}"

attributes =
if not AST.has_attribute?(attributes, data_s_scope) do
Expand All @@ -1521,7 +1594,7 @@ defmodule Surface.Compiler do

data_self_attr = %AST.Attribute{
meta: meta,
name: :"data-s-self",
name: :"#{CSSTranslator.self_attr()}",
type: :string,
value: %AST.Literal{value: true}
}
Expand Down Expand Up @@ -1600,7 +1673,7 @@ defmodule Surface.Compiler do
maybe_in_selectors?(element, attributes, selectors) do
s_data_attr = %AST.Attribute{
meta: meta,
name: :"data-s-#{scope_id}",
name: :"#{CSSTranslator.scope_attr_prefix()}#{scope_id}",
type: :string,
value: %AST.Literal{value: true}
}
Expand Down
59 changes: 45 additions & 14 deletions lib/surface/compiler/css_translator.ex
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,55 @@ defmodule Surface.Compiler.CSSTranslator do
"[" => "]"
}

@scope_attr_prefix "s-"
@self_attr "s-self"
@scope_id_length 5
@var_name_length 5

def scope_attr(component, func) do
@scope_attr_prefix <> scope_id(component, func)
end

def scope_id(component, func) do
hash("#{inspect(component)}.#{func}", @scope_id_length)
end

def var_name(scope, expr) do
"--#{hash(scope <> expr, @var_name_length)}"
end

def scope_attr_prefix do
@scope_attr_prefix
end

def self_attr do
@self_attr
end

defmacro scope_id do
mod = __CALLER__.module
{func, _} = __CALLER__.function
Surface.Compiler.CSSTranslator.scope_id(mod, func)
end

defmacro scope_attr do
mod = __CALLER__.module
{func, _} = __CALLER__.function
Surface.Compiler.CSSTranslator.scope_attr(mod, func)
end

def translate!(css, opts \\ []) do
module = Keyword.get(opts, :module)
func = Keyword.get(opts, :func)
scope_id = Keyword.get(opts, :scope_id) || scope_id(module, func)
scope_attr_prefix = Keyword.get(opts, :scope_attr_prefix) || @scope_attr_prefix
file = Keyword.get(opts, :file)
line = Keyword.get(opts, :line) || 1
env = Keyword.get(opts, :env) || :dev

state = %{
scope_id: scope_id,
scope_attr_prefix: scope_attr_prefix,
file: file,
line: line,
env: env,
Expand Down Expand Up @@ -116,15 +155,15 @@ defmodule Surface.Compiler.CSSTranslator do
defp translate_selector_list([{:text, ":deep"}, {:block, "(", arg, _meta} | rest], acc, state) do
{updated_tokens, state} = translate(arg, [], state)
state = %{state | requires_data_attrs_on_root: true}
acc = [updated_tokens, "[data-s-self][data-s-#{state.scope_id}] " | acc]
acc = [updated_tokens, "[#{@self_attr}][#{state.scope_attr_prefix}#{state.scope_id}] " | acc]
translate_selector(rest, acc, state)
end

defp translate_selector_list(tokens, acc, state) do
translate_selector(tokens, acc, state)
end

# TODO: check if it's maybe better to just always add [data-s-self][data-s-xxxxxx]
# TODO: check if it's maybe better to just always add [the-prefix-self][the-prefix-xxxxxx]
defp translate_selector([{:text, ":deep"}, {:block, "(", arg, _meta} | rest], acc, state) do
{updated_tokens, state} = translate(arg, [], state)
acc = [updated_tokens | acc]
Expand Down Expand Up @@ -173,7 +212,7 @@ defmodule Surface.Compiler.CSSTranslator do

acc =
if scoped? do
["#{text}[data-s-#{state.scope_id}]" | acc]
["#{text}[#{state.scope_attr_prefix}#{state.scope_id}]" | acc]
else
[text | acc]
end
Expand Down Expand Up @@ -202,18 +241,10 @@ defmodule Surface.Compiler.CSSTranslator do
expr
end

defp var_name(scope, expr) do
"--#{hash(scope <> expr)}"
end

defp scope_id(component, func) do
hash("#{inspect(component)}.#{func}")
end

defp hash(text) do
defp hash(text, length) do
:crypto.hash(:md5, text)
|> Base.encode16(case: :lower)
|> String.slice(0..6)
|> Base.encode32(case: :lower, padding: false)
|> String.slice(0, length)
end

defp put_selector(state, group, selector) do
Expand Down
Loading

0 comments on commit 76fbd8b

Please sign in to comment.